@mrciphersmith/keryx 0.2.83 → 0.2.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/cli.js +28463 -19413
  2. package/dist/core.js +25751 -0
  3. package/package.json +15 -2
  4. package/src/gdgraph/dangling.ts +204 -0
  5. package/src/gdgraph/find.ts +529 -37
  6. package/src/gdgraph/repomap.ts +140 -12
  7. package/src/gdgraph/staleness.ts +22 -9
  8. package/src/gdgraph/symbol.ts +45 -6
  9. package/src/gdgraph/treesitter/extract.ts +153 -5
  10. package/src/gdgraph/wiki-layer.ts +32 -1
  11. package/src/gdskills/bundled/skills/core/reviewer-skill-creator/SKILL.md +29 -0
  12. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
  13. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
  14. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +1 -1
  15. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
  16. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
  17. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +34 -6
  18. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1 -1
  19. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1 -1
  20. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
  21. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1 -1
  22. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1 -1
  23. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +52 -5
  24. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +52 -5
  25. package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +52 -5
  26. package/src/gdgraph/affected.test.ts +0 -133
  27. package/src/gdgraph/build-integrity.test.ts +0 -193
  28. package/src/gdgraph/build-lang.test.ts +0 -406
  29. package/src/gdgraph/build.test.ts +0 -120
  30. package/src/gdgraph/config.test.ts +0 -47
  31. package/src/gdgraph/core-sources.test.ts +0 -99
  32. package/src/gdgraph/fallback.test.ts +0 -153
  33. package/src/gdgraph/find.test.ts +0 -78
  34. package/src/gdgraph/import-kind.test.ts +0 -525
  35. package/src/gdgraph/path.test.ts +0 -56
  36. package/src/gdgraph/repomap.test.ts +0 -110
  37. package/src/gdgraph/service.test.ts +0 -89
  38. package/src/gdgraph/staleness.test.ts +0 -208
  39. package/src/gdgraph/symbol.test.ts +0 -89
  40. package/src/gdgraph/symbols-capability.test.ts +0 -138
  41. package/src/gdgraph/treesitter/adapter.test.ts +0 -496
  42. package/src/gdgraph/treesitter/extract.test.ts +0 -278
  43. package/src/gdgraph/treesitter/no-treesitter-import.test.ts +0 -51
  44. package/src/gdgraph/treesitter/real-grammar-fixture.test.ts +0 -71
  45. package/src/gdgraph/treesitter/resolve-calls.test.ts +0 -38
  46. package/src/gdgraph/wiki-layer-no-git.test.ts +0 -73
  47. package/src/gdgraph/wiki-layer.test.ts +0 -211
@@ -68,7 +68,7 @@ Auto-detect the project stack and available verification tools.
68
68
  ```bash
69
69
  cd <codebase_path>
70
70
 
71
- if [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
71
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
72
72
  elif [ -f pnpm-lock.yaml ]; then PM=pnpm; RUNNER="pnpm run"
73
73
  elif [ -f yarn.lock ]; then PM=yarn; RUNNER="yarn"
74
74
  elif [ -f package-lock.json ]; then PM=npm; RUNNER="npm run"
@@ -68,7 +68,7 @@ Auto-detect the project stack and available verification tools.
68
68
  ```bash
69
69
  cd <codebase_path>
70
70
 
71
- if [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
71
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
72
72
  elif [ -f pnpm-lock.yaml ]; then PM=pnpm; RUNNER="pnpm run"
73
73
  elif [ -f yarn.lock ]; then PM=yarn; RUNNER="yarn"
74
74
  elif [ -f package-lock.json ]; then PM=npm; RUNNER="npm run"
@@ -68,7 +68,7 @@ Auto-detect the project stack and available verification tools.
68
68
  ```bash
69
69
  cd <codebase_path>
70
70
 
71
- if [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
71
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
72
72
  elif [ -f pnpm-lock.yaml ]; then PM=pnpm; RUNNER="pnpm run"
73
73
  elif [ -f yarn.lock ]; then PM=yarn; RUNNER="yarn"
74
74
  elif [ -f package-lock.json ]; then PM=npm; RUNNER="npm run"
@@ -68,7 +68,7 @@ Auto-detect the project stack and available verification tools.
68
68
  ```bash
69
69
  cd <codebase_path>
70
70
 
71
- if [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
71
+ if [ -f bun.lock ] || [ -f bun.lockb ]; then PM=bun; RUNNER="bun run"
72
72
  elif [ -f pnpm-lock.yaml ]; then PM=pnpm; RUNNER="pnpm run"
73
73
  elif [ -f yarn.lock ]; then PM=yarn; RUNNER="yarn"
74
74
  elif [ -f package-lock.json ]; then PM=npm; RUNNER="npm run"
@@ -119,19 +119,47 @@ it already tried. The flow package does.
119
119
  attempts from your own context** — a resumed session's context starts at
120
120
  zero while the real count does not, and a loop bound computed from zero
121
121
  is not a bound.
122
- 3. Resume at the first task whose `status` is not `done`, respecting
123
- `dependsOn` order.
124
- 4. Before dispatching a worker for that task, record the attempt:
122
+ 3. Ask the record which task is next, rather than deriving it:
123
+
124
+ ```bash
125
+ keryx flow next <id> --json
126
+ ```
127
+
128
+ This is the first task whose `status` is not `done` and whose declared
129
+ `dependsOn` are all `done` — the ordering computed from the package
130
+ instead of re-derived by you from prose.
131
+
132
+ 4. **Read the `resume` field before dispatching anything.** It has three
133
+ answers and they are not interchangeable:
134
+
135
+ - `never-started` — nothing has been tried. Dispatch normally.
136
+ - `ended` — a previous attempt reported how it finished. This is a retry;
137
+ the budget in step 6 applies.
138
+ - `unresolved` — an attempt was opened and **no end was ever recorded**.
139
+ Whether its work already landed is UNKNOWN. Do NOT treat this as
140
+ `never-started`. Inspect the working tree and the task's
141
+ `evidenceRefs` first, decide whether the work is there, and only then
142
+ either close the stale attempt
143
+ (`keryx flow task attempt <id> <Tn> --outcome failed --detail "<what you found>"`)
144
+ or close the task (`keryx flow task done <id> <Tn>`). Re-dispatching
145
+ over an unresolved attempt is how the same work gets done twice.
146
+
147
+ `keryx flow next` also lists every OTHER not-done task carrying an
148
+ unresolved attempt, under `unresolved`. Those are parallel dispatches that
149
+ never reported back; resolve them the same way before assuming the flow is
150
+ idle.
151
+
152
+ 5. Before dispatching a worker for that task, record the attempt:
125
153
 
126
154
  ```bash
127
155
  keryx flow task attempt <id> <Tn> --outcome started --detail "resumed after session restart"
128
156
  ```
129
157
 
130
- 5. Apply the Phase 4 attempt budget against the **persisted** count. If
158
+ 6. Apply the Phase 4 attempt budget against the **persisted** count. If
131
159
  `attempts.count` for the task has already reached **three**, do not
132
160
  re-dispatch the same approach: go to the re-planning step (Phase 4, PR
133
161
  review/fix loop, step 4) and record the decision in `journal.md`.
134
- 6. Run the repetition check before spending an attempt, whatever the count
162
+ 7. Run the repetition check before spending an attempt, whatever the count
135
163
  says:
136
164
 
137
165
  ```bash
@@ -142,7 +170,7 @@ it already tried. The flow package does.
142
170
  rounds produced identical output. Go straight to the re-planning step.
143
171
  Do not spend the remaining attempts on the same approach because the
144
172
  budget has some left — that is the failure this check exists to catch.
145
- 7. If the flow is `blocked`, read the blocking reason from `journal.md`,
173
+ 8. If the flow is `blocked`, read the blocking reason from `journal.md`,
146
174
  resolve or escalate it, then `keryx flow unblock <id>`.
147
175
  4. If the user wants a new flow, continue at 0.1.
148
176
 
@@ -686,7 +686,7 @@ git -C <project_dir> worktree add ../<branch-slug> -b feature/<branch-slug> orig
686
686
  # Result branch: feature/pipeline-validation
687
687
 
688
688
  # Auto-detect package manager and install dependencies
689
- if [ -f <worktree_path>/bun.lockb ]; then
689
+ if [ -f <worktree_path>/bun.lock ] || [ -f <worktree_path>/bun.lockb ]; then
690
690
  PM="bun"; RUNNER="bun run"; bun install --cwd <worktree_path>
691
691
  elif [ -f <worktree_path>/pnpm-lock.yaml ]; then
692
692
  PM="pnpm"; RUNNER="pnpm run"; pnpm install --prefix <worktree_path>
@@ -686,7 +686,7 @@ git -C <project_dir> worktree add ../<branch-slug> -b feature/<branch-slug> orig
686
686
  # Result branch: feature/pipeline-validation
687
687
 
688
688
  # Auto-detect package manager and install dependencies
689
- if [ -f <worktree_path>/bun.lockb ]; then
689
+ if [ -f <worktree_path>/bun.lock ] || [ -f <worktree_path>/bun.lockb ]; then
690
690
  PM="bun"; RUNNER="bun run"; bun install --cwd <worktree_path>
691
691
  elif [ -f <worktree_path>/pnpm-lock.yaml ]; then
692
692
  PM="pnpm"; RUNNER="pnpm run"; pnpm install --prefix <worktree_path>
@@ -686,7 +686,7 @@ git -C <project_dir> worktree add ../<branch-slug> -b feature/<branch-slug> orig
686
686
  # Result branch: feature/pipeline-validation
687
687
 
688
688
  # Auto-detect package manager and install dependencies
689
- if [ -f <worktree_path>/bun.lockb ]; then
689
+ if [ -f <worktree_path>/bun.lock ] || [ -f <worktree_path>/bun.lockb ]; then
690
690
  PM="bun"; RUNNER="bun run"; bun install --cwd <worktree_path>
691
691
  elif [ -f <worktree_path>/pnpm-lock.yaml ]; then
692
692
  PM="pnpm"; RUNNER="pnpm run"; pnpm install --prefix <worktree_path>
@@ -686,7 +686,7 @@ git -C <project_dir> worktree add ../<branch-slug> -b feature/<branch-slug> orig
686
686
  # Result branch: feature/pipeline-validation
687
687
 
688
688
  # Auto-detect package manager and install dependencies
689
- if [ -f <worktree_path>/bun.lockb ]; then
689
+ if [ -f <worktree_path>/bun.lock ] || [ -f <worktree_path>/bun.lockb ]; then
690
690
  PM="bun"; RUNNER="bun run"; bun install --cwd <worktree_path>
691
691
  elif [ -f <worktree_path>/pnpm-lock.yaml ]; then
692
692
  PM="pnpm"; RUNNER="pnpm run"; pnpm install --prefix <worktree_path>
@@ -686,7 +686,7 @@ git -C <project_dir> worktree add ../<branch-slug> -b feature/<branch-slug> orig
686
686
  # Result branch: feature/pipeline-validation
687
687
 
688
688
  # Auto-detect package manager and install dependencies
689
- if [ -f <worktree_path>/bun.lockb ]; then
689
+ if [ -f <worktree_path>/bun.lock ] || [ -f <worktree_path>/bun.lockb ]; then
690
690
  PM="bun"; RUNNER="bun run"; bun install --cwd <worktree_path>
691
691
  elif [ -f <worktree_path>/pnpm-lock.yaml ]; then
692
692
  PM="pnpm"; RUNNER="pnpm run"; pnpm install --prefix <worktree_path>
@@ -8,10 +8,11 @@ triggers:
8
8
  - "Security scan"
9
9
  - "Check for CVEs"
10
10
  - "npm audit"
11
+ - "bun audit"
11
12
  - "Dependency vulnerabilities"
12
13
  metadata:
13
14
  author: "MrCipherSmith"
14
- version: "1.0.0"
15
+ version: "1.1.0"
15
16
  category: "quality"
16
17
  compatible_harnesses: "cursor,codex,zed,opencode,claude"
17
18
  license: "MIT"
@@ -35,22 +36,63 @@ Comprehensive security audit covering dependency vulnerabilities, secrets in cod
35
36
  ## Steps
36
37
 
37
38
  ### Step 1 — Dependency vulnerabilities
38
- Auto-detect: `bun.lockb` → `bun audit` | `package-lock.json` → `npm audit --json` | `yarn.lock` → `yarn audit`
39
+
40
+ Detect from the **lockfile**, not from an installed binary. The lockfile is what
41
+ an audit reads; `bun` being on PATH says nothing about whether `bun audit` has a
42
+ lockfile to resolve.
43
+
44
+ | Lockfile present (repo root) | Command |
45
+ |---|---|
46
+ | `bun.lock` **or** `bun.lockb` | `bun audit --json` |
47
+ | `package-lock.json` **or** `npm-shrinkwrap.json` | `npm audit --json` |
48
+ | `pnpm-lock.yaml` | `pnpm audit --json` |
49
+ | `yarn.lock` | `yarn npm audit --json` (Yarn 2+), else `yarn audit --json` (Yarn 1) |
50
+
51
+ Check **both** Bun names — `bun.lock` and `bun.lockb`. Bun 1.2 replaced the
52
+ binary `bun.lockb` with the text `bun.lock`, so a project on current Bun has
53
+ only `bun.lock`, and a `bun.lockb`-only check (`bun.lock` unmatched) finds
54
+ nothing there.
55
+
56
+ **If no row matches, the dependency audit DID NOT RUN.** Say so:
57
+
58
+ ```
59
+ dependency-audit: NOT RUN — no recognised lockfile in <path>
60
+ ```
61
+
62
+ and carry `not measured` — never `0` — into every severity total in the report.
63
+ Do not fall through to another package manager's audit as a guess.
64
+
65
+ **A command that could not produce results is also NOT RUN.** `npm audit --json`
66
+ without a lockfile exits 1 and prints roughly 240 bytes of
67
+ `{"error":{"code":"ENOLOCK",...}}` — an object with **no `vulnerabilities` key
68
+ at all**. Grouping that by severity yields zero for critical, high, moderate and
69
+ low, which is indistinguishable from a clean project. So before grouping, check
70
+ that the payload actually carries vulnerability data (`vulnerabilities` /
71
+ `advisories` for npm, the per-package arrays for bun). If it does not, the
72
+ outcome is NOT RUN with the tool's own error, not a clean result.
73
+
74
+ Only once a command has produced real vulnerability data:
39
75
 
40
76
  Group by severity: **critical → high → moderate → low**
41
77
 
42
78
  ### Step 2 — Outdated packages
43
- Run `bun outdated` or `npm outdated`. Flag packages more than 2 major versions behind.
79
+ Run `outdated` for the package manager Step 1 detected (`bun outdated`,
80
+ `npm outdated`, `pnpm outdated`, `yarn outdated`). Flag packages more than 2
81
+ major versions behind. If Step 1 found no package manager, this step is
82
+ `NOT RUN` for the same reason.
44
83
 
45
84
  ### Step 3 — Secrets scan
46
85
  - Check git history for `.env`, `.key`, `.pem` files
47
86
  - Grep source for hardcoded passwords/API keys/secrets (excluding node_modules)
48
87
 
49
88
  ### Step 4 — Docker image scan
50
- If Dockerfile present and Docker available: `docker scout cves`
89
+ If a Dockerfile is present and Docker is available: `docker scout cves`.
90
+ Otherwise report `container-scan: NOT RUN — <no Dockerfile | docker unavailable>`.
91
+ "No Dockerfile" and "scanned, nothing found" are different results.
51
92
 
52
93
  ### Report
53
- - Total by severity
94
+ - Per step: `RAN` or `NOT RUN — <reason>`. A step that did not run has no totals.
95
+ - Total by severity, for the steps that ran
54
96
  - Top 3 critical/high with CVE
55
97
  - Recommended immediate actions
56
98
  - Packages safe to ignore (dev-only, not reachable in prod)
@@ -59,3 +101,8 @@ If Dockerfile present and Docker available: `docker scout cves`
59
101
 
60
102
  - Distinguish prod vs dev-only vulnerabilities
61
103
  - Never suggest `npm audit fix --force` without explaining what it changes
104
+ - **A check that did not run is not a check that passed.** Never report a
105
+ severity total — least of all zero — for a step whose command was not
106
+ selected, could not run, or returned no vulnerability data. Report `not
107
+ measured` and name the reason. In a security report, silence read as "clean"
108
+ is the most expensive defect available.
@@ -8,10 +8,11 @@ triggers:
8
8
  - "Security scan"
9
9
  - "Check for CVEs"
10
10
  - "npm audit"
11
+ - "bun audit"
11
12
  - "Dependency vulnerabilities"
12
13
  metadata:
13
14
  author: "MrCipherSmith"
14
- version: "1.0.0"
15
+ version: "1.1.0"
15
16
  category: "quality"
16
17
  compatible_harnesses: "cursor,codex,zed,opencode,claude"
17
18
  license: "MIT"
@@ -35,22 +36,63 @@ Comprehensive security audit covering dependency vulnerabilities, secrets in cod
35
36
  ## Steps
36
37
 
37
38
  ### Step 1 — Dependency vulnerabilities
38
- Auto-detect: `bun.lockb` → `bun audit` | `package-lock.json` → `npm audit --json` | `yarn.lock` → `yarn audit`
39
+
40
+ Detect from the **lockfile**, not from an installed binary. The lockfile is what
41
+ an audit reads; `bun` being on PATH says nothing about whether `bun audit` has a
42
+ lockfile to resolve.
43
+
44
+ | Lockfile present (repo root) | Command |
45
+ |---|---|
46
+ | `bun.lock` **or** `bun.lockb` | `bun audit --json` |
47
+ | `package-lock.json` **or** `npm-shrinkwrap.json` | `npm audit --json` |
48
+ | `pnpm-lock.yaml` | `pnpm audit --json` |
49
+ | `yarn.lock` | `yarn npm audit --json` (Yarn 2+), else `yarn audit --json` (Yarn 1) |
50
+
51
+ Check **both** Bun names — `bun.lock` and `bun.lockb`. Bun 1.2 replaced the
52
+ binary `bun.lockb` with the text `bun.lock`, so a project on current Bun has
53
+ only `bun.lock`, and a `bun.lockb`-only check (`bun.lock` unmatched) finds
54
+ nothing there.
55
+
56
+ **If no row matches, the dependency audit DID NOT RUN.** Say so:
57
+
58
+ ```
59
+ dependency-audit: NOT RUN — no recognised lockfile in <path>
60
+ ```
61
+
62
+ and carry `not measured` — never `0` — into every severity total in the report.
63
+ Do not fall through to another package manager's audit as a guess.
64
+
65
+ **A command that could not produce results is also NOT RUN.** `npm audit --json`
66
+ without a lockfile exits 1 and prints roughly 240 bytes of
67
+ `{"error":{"code":"ENOLOCK",...}}` — an object with **no `vulnerabilities` key
68
+ at all**. Grouping that by severity yields zero for critical, high, moderate and
69
+ low, which is indistinguishable from a clean project. So before grouping, check
70
+ that the payload actually carries vulnerability data (`vulnerabilities` /
71
+ `advisories` for npm, the per-package arrays for bun). If it does not, the
72
+ outcome is NOT RUN with the tool's own error, not a clean result.
73
+
74
+ Only once a command has produced real vulnerability data:
39
75
 
40
76
  Group by severity: **critical → high → moderate → low**
41
77
 
42
78
  ### Step 2 — Outdated packages
43
- Run `bun outdated` or `npm outdated`. Flag packages more than 2 major versions behind.
79
+ Run `outdated` for the package manager Step 1 detected (`bun outdated`,
80
+ `npm outdated`, `pnpm outdated`, `yarn outdated`). Flag packages more than 2
81
+ major versions behind. If Step 1 found no package manager, this step is
82
+ `NOT RUN` for the same reason.
44
83
 
45
84
  ### Step 3 — Secrets scan
46
85
  - Check git history for `.env`, `.key`, `.pem` files
47
86
  - Grep source for hardcoded passwords/API keys/secrets (excluding node_modules)
48
87
 
49
88
  ### Step 4 — Docker image scan
50
- If Dockerfile present and Docker available: `docker scout cves`
89
+ If a Dockerfile is present and Docker is available: `docker scout cves`.
90
+ Otherwise report `container-scan: NOT RUN — <no Dockerfile | docker unavailable>`.
91
+ "No Dockerfile" and "scanned, nothing found" are different results.
51
92
 
52
93
  ### Report
53
- - Total by severity
94
+ - Per step: `RAN` or `NOT RUN — <reason>`. A step that did not run has no totals.
95
+ - Total by severity, for the steps that ran
54
96
  - Top 3 critical/high with CVE
55
97
  - Recommended immediate actions
56
98
  - Packages safe to ignore (dev-only, not reachable in prod)
@@ -59,3 +101,8 @@ If Dockerfile present and Docker available: `docker scout cves`
59
101
 
60
102
  - Distinguish prod vs dev-only vulnerabilities
61
103
  - Never suggest `npm audit fix --force` without explaining what it changes
104
+ - **A check that did not run is not a check that passed.** Never report a
105
+ severity total — least of all zero — for a step whose command was not
106
+ selected, could not run, or returned no vulnerability data. Report `not
107
+ measured` and name the reason. In a security report, silence read as "clean"
108
+ is the most expensive defect available.
@@ -8,10 +8,11 @@ triggers:
8
8
  - "Security scan"
9
9
  - "Check for CVEs"
10
10
  - "npm audit"
11
+ - "bun audit"
11
12
  - "Dependency vulnerabilities"
12
13
  metadata:
13
14
  author: "MrCipherSmith"
14
- version: "1.0.0"
15
+ version: "1.1.0"
15
16
  category: "quality"
16
17
  compatible_harnesses: "cursor,codex,zed,opencode,claude"
17
18
  license: "MIT"
@@ -35,22 +36,63 @@ Comprehensive security audit covering dependency vulnerabilities, secrets in cod
35
36
  ## Steps
36
37
 
37
38
  ### Step 1 — Dependency vulnerabilities
38
- Auto-detect: `bun.lockb` → `bun audit` | `package-lock.json` → `npm audit --json` | `yarn.lock` → `yarn audit`
39
+
40
+ Detect from the **lockfile**, not from an installed binary. The lockfile is what
41
+ an audit reads; `bun` being on PATH says nothing about whether `bun audit` has a
42
+ lockfile to resolve.
43
+
44
+ | Lockfile present (repo root) | Command |
45
+ |---|---|
46
+ | `bun.lock` **or** `bun.lockb` | `bun audit --json` |
47
+ | `package-lock.json` **or** `npm-shrinkwrap.json` | `npm audit --json` |
48
+ | `pnpm-lock.yaml` | `pnpm audit --json` |
49
+ | `yarn.lock` | `yarn npm audit --json` (Yarn 2+), else `yarn audit --json` (Yarn 1) |
50
+
51
+ Check **both** Bun names — `bun.lock` and `bun.lockb`. Bun 1.2 replaced the
52
+ binary `bun.lockb` with the text `bun.lock`, so a project on current Bun has
53
+ only `bun.lock`, and a `bun.lockb`-only check (`bun.lock` unmatched) finds
54
+ nothing there.
55
+
56
+ **If no row matches, the dependency audit DID NOT RUN.** Say so:
57
+
58
+ ```
59
+ dependency-audit: NOT RUN — no recognised lockfile in <path>
60
+ ```
61
+
62
+ and carry `not measured` — never `0` — into every severity total in the report.
63
+ Do not fall through to another package manager's audit as a guess.
64
+
65
+ **A command that could not produce results is also NOT RUN.** `npm audit --json`
66
+ without a lockfile exits 1 and prints roughly 240 bytes of
67
+ `{"error":{"code":"ENOLOCK",...}}` — an object with **no `vulnerabilities` key
68
+ at all**. Grouping that by severity yields zero for critical, high, moderate and
69
+ low, which is indistinguishable from a clean project. So before grouping, check
70
+ that the payload actually carries vulnerability data (`vulnerabilities` /
71
+ `advisories` for npm, the per-package arrays for bun). If it does not, the
72
+ outcome is NOT RUN with the tool's own error, not a clean result.
73
+
74
+ Only once a command has produced real vulnerability data:
39
75
 
40
76
  Group by severity: **critical → high → moderate → low**
41
77
 
42
78
  ### Step 2 — Outdated packages
43
- Run `bun outdated` or `npm outdated`. Flag packages more than 2 major versions behind.
79
+ Run `outdated` for the package manager Step 1 detected (`bun outdated`,
80
+ `npm outdated`, `pnpm outdated`, `yarn outdated`). Flag packages more than 2
81
+ major versions behind. If Step 1 found no package manager, this step is
82
+ `NOT RUN` for the same reason.
44
83
 
45
84
  ### Step 3 — Secrets scan
46
85
  - Check git history for `.env`, `.key`, `.pem` files
47
86
  - Grep source for hardcoded passwords/API keys/secrets (excluding node_modules)
48
87
 
49
88
  ### Step 4 — Docker image scan
50
- If Dockerfile present and Docker available: `docker scout cves`
89
+ If a Dockerfile is present and Docker is available: `docker scout cves`.
90
+ Otherwise report `container-scan: NOT RUN — <no Dockerfile | docker unavailable>`.
91
+ "No Dockerfile" and "scanned, nothing found" are different results.
51
92
 
52
93
  ### Report
53
- - Total by severity
94
+ - Per step: `RAN` or `NOT RUN — <reason>`. A step that did not run has no totals.
95
+ - Total by severity, for the steps that ran
54
96
  - Top 3 critical/high with CVE
55
97
  - Recommended immediate actions
56
98
  - Packages safe to ignore (dev-only, not reachable in prod)
@@ -59,3 +101,8 @@ If Dockerfile present and Docker available: `docker scout cves`
59
101
 
60
102
  - Distinguish prod vs dev-only vulnerabilities
61
103
  - Never suggest `npm audit fix --force` without explaining what it changes
104
+ - **A check that did not run is not a check that passed.** Never report a
105
+ severity total — least of all zero — for a step whose command was not
106
+ selected, could not run, or returned no vulnerability data. Report `not
107
+ measured` and name the reason. In a security report, silence read as "clean"
108
+ is the most expensive defect available.
@@ -1,133 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { expect, test } from "bun:test";
5
- import { computeAffected } from "./affected";
6
- import { getAffected } from "./query";
7
- import type { GraphData } from "./types";
8
-
9
- const FIXTURE_DIR = fileURLToPath(new URL("../../fixtures/transitive-closure/", import.meta.url));
10
-
11
- async function loadFixtureGraph(): Promise<GraphData> {
12
- const raw = JSON.parse(await readFile(path.join(FIXTURE_DIR, "graph.json"), "utf8"));
13
- return { nodes: raw.nodes, edges: raw.edges };
14
- }
15
-
16
- async function loadExpected(): Promise<any> {
17
- return JSON.parse(await readFile(path.join(FIXTURE_DIR, "expected.json"), "utf8"));
18
- }
19
-
20
- // The pre-block default renderer, replicated verbatim so we can assert the new
21
- // path is byte-for-byte identical at depth 1 (AC2.2).
22
- function renderDefault(result: { target: string; dependencies: string[]; dependents: string[] }): string {
23
- const lines: string[] = [];
24
- lines.push(`# Affected context for ${result.target}`);
25
- lines.push("");
26
- lines.push("## Dependencies");
27
- printList(lines, result.dependencies);
28
- lines.push("");
29
- lines.push("## Dependents");
30
- printList(lines, result.dependents);
31
- return lines.join("\n");
32
- }
33
-
34
- function printList(lines: string[], items: string[]): void {
35
- if (items.length === 0) {
36
- lines.push("- none");
37
- return;
38
- }
39
- for (const item of items) {
40
- lines.push(`- ${item}`);
41
- }
42
- }
43
-
44
- test("AC2.1 — exact N-hop dependent closure per target per depth", async () => {
45
- const graph = await loadFixtureGraph();
46
- const expected = await loadExpected();
47
-
48
- for (const [target, byDepth] of Object.entries(expected.closures) as [string, Record<string, string[]>][]) {
49
- for (const [depthKey, expectedSet] of Object.entries(byDepth)) {
50
- const result = computeAffected(graph, target, { depth: Number(depthKey) });
51
- expect(result.dependents).toEqual(expectedSet);
52
- }
53
- }
54
- });
55
-
56
- test("AC2.3 — cyclic fixture terminates and is deterministic across runs", async () => {
57
- const graph = await loadFixtureGraph();
58
- const a = computeAffected(graph, "src/x.ts", { depth: 10 });
59
- const b = computeAffected(graph, "src/x.ts", { depth: 10 });
60
- expect(a.dependents).toEqual(["src/y.ts", "src/z.ts"]);
61
- expect(a.dependents).toEqual(b.dependents);
62
- });
63
-
64
- test("AC2.2 — default / --depth 1 renderer is byte-identical to pre-block getAffected", async () => {
65
- const graph = await loadFixtureGraph();
66
- for (const target of ["src/a.ts", "src/b.ts", "src/e.ts", "src/x.ts"]) {
67
- const legacy = getAffected(graph, target);
68
- const next = computeAffected(graph, target, { depth: 1 });
69
-
70
- // Underlying data is set-equal (dependents + dependencies).
71
- expect(next.dependents).toEqual(legacy.dependents);
72
- expect(next.dependencies).toEqual(legacy.dependencies);
73
- // Rendered stdout is byte-for-byte identical.
74
- expect(renderDefault(next)).toBe(renderDefault(legacy));
75
- }
76
- });
77
-
78
- test("AC2.2 — no-flag default depth equals depth 1", async () => {
79
- const graph = await loadFixtureGraph();
80
- const noFlag = computeAffected(graph, "src/a.ts");
81
- const depth1 = computeAffected(graph, "src/a.ts", { depth: 1 });
82
- expect(noFlag.dependents).toEqual(depth1.dependents);
83
- expect(renderDefault(noFlag)).toBe(renderDefault(depth1));
84
- });
85
-
86
- test("AC2.4 — ranked output ordered hop asc → fanIn desc → path asc", async () => {
87
- const graph = await loadFixtureGraph();
88
- const expected = await loadExpected();
89
- const result = computeAffected(graph, "src/a.ts", { depth: 4, ranked: true });
90
- expect(result.ranked).toEqual(expected.ranked["src/a.ts"]["4"]);
91
- });
92
-
93
- test("AC2.4 — dependencies are the unchanged one-hop forward set", async () => {
94
- const graph = await loadFixtureGraph();
95
- const expected = await loadExpected();
96
- for (const [target, deps] of Object.entries(expected.dependencies) as [string, string[]][]) {
97
- const result = computeAffected(graph, target, { depth: 3 });
98
- expect(result.dependencies).toEqual(deps);
99
- }
100
- });
101
-
102
- // ---------------------------------------------------------------------------
103
- // AFC-11 (flow 234) requirement 3 — a type consumer is visible in impact
104
- // analysis. `edge.importKind === "type-only"` is erased at runtime and is
105
- // excluded from `getCycles`' load-order adjacency, but the edge itself is
106
- // still `kind: "imports"` — a real dependency for "what do I have to
107
- // re-check" — so `computeAffected` (and `getAffected`) must keep surfacing
108
- // it. Constructed in-memory rather than through `buildGraph()`: this is a
109
- // pure-function contract on `GraphData`, unrelated to how the edge's
110
- // `importKind` was derived.
111
- // ---------------------------------------------------------------------------
112
-
113
- test("AFC-11 req3 — a file that only type-imports the target is still a visible dependent", () => {
114
- const graph: GraphData = {
115
- nodes: [
116
- { id: "src/types.ts", kind: "file", path: "src/types.ts", language: "typescript" },
117
- { id: "src/consumer.ts", kind: "file", path: "src/consumer.ts", language: "typescript" },
118
- ],
119
- edges: [
120
- {
121
- id: "edge:1",
122
- from: "src/consumer.ts",
123
- to: "src/types.ts",
124
- kind: "imports",
125
- specifier: "./types",
126
- importKind: "type-only",
127
- },
128
- ],
129
- };
130
-
131
- expect(computeAffected(graph, "src/types.ts").dependents).toEqual(["src/consumer.ts"]);
132
- expect(getAffected(graph, "src/types.ts").dependents).toEqual(["src/consumer.ts"]);
133
- });