Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .agents/skills/euthyna-en/references/dependency-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ Available discovery sources (use whichever is installed — **audit the source b
**The target directory must have one of** `package.json` / `pyproject.toml` / `requirements*.txt` / `go.mod`.
If none exists, **say so and stop** — do not improvise an audit for an ecosystem the collector does not parse.

**Manifests are located from the repository root only.** A multi-package repository's per-package locks are not measured by
`euthyna audit` or `euthyna deps --repo` (🧪 measured 2026-10-10: the root `package-lock.json` pins `root-a` while
`packages/app/package-lock.json` pins `sub-b` and `sub-c`, and the output reports only `root-a`, **with no declaration at
all**). Measuring a sub-package means running `euthyna deps --lockfile <path>` per package; if that is not done, say the
sub-packages were not measured — do not treat the root manifest's facts as facts about the whole monorepo.

**Exactly resolvable**: `package-lock.json`, `npm-shrinkwrap.json`, `uv.lock`, `go.mod` for Go 1.17+.

**Not resolvable**: `yarn.lock`, `pnpm-lock.yaml`, `poetry.lock`.
Expand Down
5 changes: 5 additions & 0 deletions .agents/skills/euthyna/references/dependency-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@
**目标目录必须有** `package.json` / `pyproject.toml` / `requirements*.txt` / `go.mod` 之一。
没有就**直接说没有并停止**,不要为一个采集器不解析的生态即兴审计。

**清单只按仓库根查找。**多包仓库里子包各自的 lock 不会被 `euthyna audit` 或 `euthyna deps --repo` 测到
(🧪 实测 2026-10-10:根 `package-lock.json` 锁 `root-a`,子包 `packages/app/package-lock.json` 锁 `sub-b`/`sub-c`,
输出只有 `root-a`,**并且没有任何声明**)。要管子包就逐包跑 `euthyna deps --lockfile <路径>`;
不跑就明说没测——别把根清单当成整个 monorepo 的事实。

**能精确解析版本的**:`package-lock.json`、`npm-shrinkwrap.json`、`uv.lock`、Go 1.17+ 的 `go.mod`。

**不能的**:`yarn.lock`、`pnpm-lock.yaml`、`poetry.lock`。
Expand Down
6 changes: 3 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -564,8 +564,8 @@ is faithful — that is a reading job, and a mechanical stand-in would only manu

| Path | What it is |
|---|---|
| `bin/` + `src/` | **Fact producers** (zero-dependency Node CLI). `src/contract.js` is the contract in code; `src/facts/history.js`, `src/facts/coverage.js` and `src/facts/deps.js` are the three measurements (`history` has `--origins` to chase the first introducer and classifies by message + deleted-line + diff, **on by default since `euthyna audit`**; `coverage.js` reads c8/V8 JSON, coverage.py JSON (format 3) **and Go's text `-coverprofile`** (the latter needs `--source <module root>`: the profile carries no function names, so the producer parses top-level `func` ranges from the gofmt'd source; a file absent from the profile is notEvaluated, never "never executed"); `deps.js` reads package-lock.json / Cargo.lock / go.mod). `euthyna audit` (0.4.0, `src/cli.js`) runs history + the full dependency surface in one command — the single entry point the CI recipe and the skill both describe. `src/gate.js` is the six-gate report validator behind `euthyna gate <报告> [--verify] [--allow-exec]` — it parses the skill's 裁定格式, downgrades findings that lack evidence/reproduce or contradict their verdict, and executes nothing but `git` unless the caller consents to interpreters |
| `test/` | 235 tests via `node --test` (234 pass + 1 deliberate skip on non-Windows, measured 2026-10-09), no third-party framework. **The history tests build real git repositories rather than mocking**; the coverage tests carry fixtures shaped like c8, coverage.py and Go profile output (`coverage.test.js` + `coverage-go.test.js`); the deps tests carry fixture lockfiles (npm v1/v2/v3, Cargo.lock, go.mod); the contract tests carry the shell-quoting and render-boundary regressions from PR #1; `git.test.js` carries the error-path quoting and stderr-sanitization regressions from issue #2; `gate.test.js` pins the six-gate validator (including that `--verify` does not run an interpreter command without `--allow-exec`); `cli.test.js` pins the `audit` command and its exit codes; `skill.test.js` pins the written discipline (the six gates, the three verdicts, the `euthyna gate` reference) so weakening the skill turns CI red; `plugin.test.js`, `claude-plugin.test.js` and `hermes-plugin.test.js` pin the three manifest surfaces; `manifest-version.test.js` pins `package.json` against the three plugin manifests (0.5.0 shipped them a release behind and nothing compared them); `i18n.test.js` pins language rendering and report parsing (zh/en); `skill-mirror.test.js` compares the two skill editions against each other — file set, heading sequence (ATX and Setext), fenced blocks, table shapes, and the `euthyna <subcommand>` / `--flag` identifier sets — so an unmirrored discipline change goes red instead of relying on memory, `docs-pair.test.js` holds `docs/` to the same rule (every document paired, and each pair equal on structure and identifiers — not on prose fidelity), and `markdown-structure.test.js` pins the shared scanner itself with seven crafted-document cases, because a legal Markdown form it cannot read makes any comparison pass on that form (neither guard compares word counts, which a translation legitimately needs room for) |
| `bin/` + `src/` | **Fact producers** (zero-dependency Node CLI). `src/contract.js` is the contract in code; `src/facts/history.js`, `src/facts/coverage.js` and `src/facts/deps.js` are the three measurements (`history` has `--origins` to chase the first introducer and classifies by message + deleted-line + diff, **on by default since `euthyna audit`**; `coverage.js` reads c8/V8 JSON, coverage.py JSON (format 3) **and Go's text `-coverprofile`** (the latter needs `--source <module root>`: the profile carries no function names, so the producer parses top-level `func` ranges from the gofmt'd source; a file absent from the profile is notEvaluated, never "never executed"); `deps.js` reads package-lock.json / Cargo.lock / go.mod, **located from the repository root only** — a monorepo sub-package lock is measured only by `deps --lockfile <path>`, and `audit` does not notice its absence, which `docs/fact-contract.md` §6.3 records with the numbers from the probe). `audit` compares the manifest it measured with the one at `--head` and records `manifest-not-head` when the content differs or the file is absent there. `euthyna audit` (0.4.0, `src/cli.js`) runs history + the full dependency surface in one command — the single entry point the CI recipe and the skill both describe. `src/gate.js` is the six-gate report validator behind `euthyna gate <报告> [--verify] [--allow-exec]` — it parses the skill's 裁定格式, downgrades findings that lack evidence/reproduce or contradict their verdict, and executes nothing but `git` unless the caller consents to interpreters |
| `test/` | 246 tests via `node --test`, no third-party framework. The pass/skip split is keyed on **tar flavour, not the OS**: `test/bench.test.js` skips `every exploit expectation holds` unless `tar --version` reports bsdtar. Measured on `cdece0f`: ubuntu 245 pass + 1 skip (its log line: *the exploit ground truth is defined against bsdtar, not "tar (GNU tar) 1.35"*), windows 246 pass + 0 skip — and this machine's Git Bash PATH puts `D:\Git\usr\bin\tar.exe` (GNU tar 1.35) ahead of System32's `tar.exe`, so a local run reports 245 + 1 even though it is Windows. **The history tests build real git repositories rather than mocking**; the coverage tests carry fixtures shaped like c8, coverage.py and Go profile output (`coverage.test.js` + `coverage-go.test.js`); the deps tests carry fixture lockfiles (npm v1/v2/v3, Cargo.lock, go.mod); the contract tests carry the shell-quoting and render-boundary regressions from PR #1; `git.test.js` carries the error-path quoting and stderr-sanitization regressions from issue #2; `gate.test.js` pins the six-gate validator (including that `--verify` does not run an interpreter command without `--allow-exec`); `cli.test.js` pins the `audit` command, its exit codes, and the manifest-version declaration — content against `--head`, so a PR merge commit whose lockfile is byte-identical to `--head`'s declares nothing while a gitignored lockfile declares itself; the decision itself is an exported pure function so the fail-closed branch (`manifest-unverified`) is reachable from a test; `skill.test.js` pins the written discipline (the six gates, the three verdicts, the `euthyna gate` reference) so weakening the skill turns CI red; `plugin.test.js`, `claude-plugin.test.js` and `hermes-plugin.test.js` pin the three manifest surfaces; `manifest-version.test.js` pins `package.json` against the three plugin manifests (0.5.0 shipped them a release behind and nothing compared them); `i18n.test.js` pins language rendering and report parsing (zh/en); `skill-mirror.test.js` compares the two skill editions against each other — file set, heading sequence (ATX and Setext), fenced blocks, table shapes, and the `euthyna <subcommand>` / `--flag` identifier sets — so an unmirrored discipline change goes red instead of relying on memory, `docs-pair.test.js` holds `docs/` to the same rule (every document paired, and each pair equal on structure and identifiers — not on prose fidelity), and `markdown-structure.test.js` pins the shared scanner itself with seven crafted-document cases, because a legal Markdown form it cannot read makes any comparison pass on that form (neither guard compares word counts, which a translation legitimately needs room for). **The suite count is written down once, in this row**: README's quickstart carried its own number (207) and was 39 tests stale, so it now says only what `npm test` does, and `package.json` defines that as `node --test` |
| `.agents/skills/euthyna/` | **The skill.** Doubles as source and as a project skill root (rank 200), so it is live in this workspace without a restart |
| `bench/` | **The recall benchmark.** `exploits.js` establishes ground truth by execution (18 cases, 8 near-neighbour pairs); `prepare-blind.js` produces answer-free copies with per-round shuffled ids; `adjudicate.js` closes the loop end to end (blind tree → one adjudicator process per case → `collect-verdicts.js` machine validation → `score.js`), with a deterministic `golden` mode (ground truth written into reports — plumbing self-check only, never a real round) for CI; `collect-verdicts.js` machine-validates reports and extracts verdicts without the orchestrator reading report bodies; `score.js` computes the confusion matrix and the pair view; `perf.js` is the `history` scaling baseline. The verified-platform matrix (bsdtar vs GNU tar, p6 skip) lives in `bench/README.md`. Results: `bench/RESULTS.md` (round 1), `bench/RESULTS-round2.md` (round 2: three independent runs per case, zero flips), `bench/RESULTS-round3.md` (round 3: 18 cases, 54 adjudications, two fixture defects caught by adjudicators), `bench/RESULTS-round4.md` (round 4: 54/54, p6b v3 confirmed by first blind adjudication, cross-model run executed — zero flips across two model families); protocols in `bench/README.md`, design pre-registrations in `bench/DESIGN-round3.md` and `bench/DESIGN-round4.md` |
| `docs/positioning.md` + `-zh` | Competitive analysis across the DSH catalog, including three claims that were tested and refuted |
Expand All @@ -581,7 +581,7 @@ is faithful — that is a reading job, and a mechanical stand-in would only manu
| `tools/check-license-text.mjs` | Verifies `LICENSE` against the canonical Apache-2.0 text, fetched live. **The licence claim is checkable rather than asserted** |
| `tools/probe-skill-roots.mjs` | Drives the **installed** DSH skill provider and prints the roots it reads plus what it discovers, so the discovery-path facts above stay re-measurable instead of remembered. Read-only; needs the harness's module path as its argument |
| `.gitattributes` | Pins LF in checkouts so byte-level checks mean the same thing on every platform |
| `.github/workflows/ci.yml` | CI: test matrix (Linux/Windows × Node 20/22/24), the live licence check, and a `bench-loop` job running the deterministic golden adjudication round (`bench/adjudicate.js --adjudicator golden`). **The exploit ground-truth harness is deliberately not run on Linux** — several cases verify bsdtar-specific semantics that GNU tar does not share (it reports itself as skipped there; Windows jobs run it in full) |
| `.github/workflows/ci.yml` | CI: test matrix (Linux/Windows × Node 20/22/24), the live licence check, and a `bench-loop` job running the deterministic golden adjudication round (`bench/adjudicate.js --adjudicator golden`). **The exploit ground-truth harness is deliberately not run on Linux** — several cases verify bsdtar-specific semantics that GNU tar does not share (skipped there with the reason printed; the Windows runner resolves `tar` to bsdtar and ran it in full — 246 pass / 0 skip against ubuntu's 245 pass / 1 skip, measured on `cdece0f`) |
| `CONTRIBUTING.md` | The house rules in their public form |
| `SECURITY.md` | Reporting policy and scope; notes that the bench fixtures are vulnerable by construction |
| `.github/ISSUE_TEMPLATE/` | Issue forms: bug report / feature proposal / benchmark case proposal. The privacy guidance tells reporters to build synthetic fixtures and redact author names, commit messages, and local paths — `history` output and `coverage-final.json` both carry them |
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ From a checkout instead (development):

```sh
git clone https://github.com/slow-stack/euthyna
cd euthyna && npm test # 207 tests; no install step exists
cd euthyna && npm test # the whole suite; no install step exists
node bin/euthyna.js audit --repo <path> --base main --head HEAD
```

Expand Down
6 changes: 6 additions & 0 deletions docs/fact-contract-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,12 @@ tsserver × c8 的**行号对齐未做集成验证**(fixture 里的调用点
自动检测到的其他格式(`pnpm-lock.yaml`、`yarn.lock`、`poetry.lock` 等)一律报
**notEvaluated** 并点名文件名——绝不猜。

**清单只按仓库根查找。**🧪 实测 2026-10-10:一个根 `package-lock.json` 锁 `root-a`、子包
`packages/app/package-lock.json` 锁 `sub-b`/`sub-c` 的仓库里,`audit` 与 `deps --repo` 都只报出 `root-a`,
且**没有任何声明**(版本一致性那条只比较根清单)。子包的锁定版本要么逐包跑
`euthyna deps --lockfile <路径>`(实测报出 `sub-b`、`sub-c`),要么就明说没测——**不要把根清单的结果
当作整个 monorepo 的事实。**

**拒绝说什么和说什么同样重要。** 只报版本,其他一概不报:没有"有漏洞"、没有严重性、没有 CVE 映射。
版本 → CVE 的映射归判定层(`assertNoVerdictFields` 在机制上强制这一点)。
一个报出"版本 4.17.21 受 CVE-XXXX 影响"的产出器等于替判定层下了结论,而那个结论不可复算。
Expand Down
7 changes: 7 additions & 0 deletions docs/fact-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ record of what is actually resolved.
Anything else detected at auto-detect (`pnpm-lock.yaml`, `yarn.lock`, `poetry.lock`, …) is reported as
*not evaluated* with the filenames named — never guessed at.

**Manifests are located from the repository root only.** 🧪 Measured 2026-10-10 in a repository whose root
`package-lock.json` pins `root-a` while a sub-package's `packages/app/package-lock.json` pins `sub-b` and
`sub-c`: both `audit` and `deps --repo` reported only `root-a`, with **no declaration at all** (the
manifest-version check compares the root manifest and nothing else). A sub-package's pinned versions require
running `euthyna deps --lockfile <path>` per package (measured: it reports `sub-b` and `sub-c`), or an explicit
statement that they were not measured — **do not read the root manifest's facts as facts about the whole monorepo.**

**What it refuses to say is as important as what it says.** It emits versions, and nothing else:
no "vulnerable", no severity, no CVE mapping. The version-to-CVE mapping is the adjudication layer's job
(the `assertNoVerdictFields` guard enforces this mechanically). A producer that emitted "version 4.17.21 is
Expand Down
Loading