@mrciphersmith/keryx 0.2.83 → 0.2.88
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.
- package/README.md +1 -1
- package/dist/cli.js +29861 -20525
- package/dist/core.js +25871 -0
- package/package.json +16 -2
- package/src/gdgraph/dangling.ts +204 -0
- package/src/gdgraph/find.ts +529 -37
- package/src/gdgraph/repomap.ts +140 -12
- package/src/gdgraph/staleness.ts +22 -9
- package/src/gdgraph/symbol.ts +45 -6
- package/src/gdgraph/treesitter/extract.ts +153 -5
- package/src/gdgraph/wiki-layer.ts +32 -1
- package/src/gdskills/bundled/skills/core/reviewer-skill-creator/SKILL.md +29 -0
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +50 -13
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +1 -1
- package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +1 -1
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.codex.md +52 -5
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.cursor.md +52 -5
- package/src/gdskills/bundled/skills/quality/security-audit/SKILL.md +52 -5
- package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +12 -0
- package/src/gdgraph/affected.test.ts +0 -133
- package/src/gdgraph/build-integrity.test.ts +0 -193
- package/src/gdgraph/build-lang.test.ts +0 -406
- package/src/gdgraph/build.test.ts +0 -120
- package/src/gdgraph/config.test.ts +0 -47
- package/src/gdgraph/core-sources.test.ts +0 -99
- package/src/gdgraph/fallback.test.ts +0 -153
- package/src/gdgraph/find.test.ts +0 -78
- package/src/gdgraph/import-kind.test.ts +0 -525
- package/src/gdgraph/path.test.ts +0 -56
- package/src/gdgraph/repomap.test.ts +0 -110
- package/src/gdgraph/service.test.ts +0 -89
- package/src/gdgraph/staleness.test.ts +0 -208
- package/src/gdgraph/symbol.test.ts +0 -89
- package/src/gdgraph/symbols-capability.test.ts +0 -138
- package/src/gdgraph/treesitter/adapter.test.ts +0 -496
- package/src/gdgraph/treesitter/extract.test.ts +0 -278
- package/src/gdgraph/treesitter/no-treesitter-import.test.ts +0 -51
- package/src/gdgraph/treesitter/real-grammar-fixture.test.ts +0 -71
- package/src/gdgraph/treesitter/resolve-calls.test.ts +0 -38
- package/src/gdgraph/wiki-layer-no-git.test.ts +0 -73
- 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 ];
|
|
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 ];
|
|
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 ];
|
|
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 ];
|
|
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.
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
|
@@ -170,13 +198,15 @@ keryx flow init --issue <url>
|
|
|
170
198
|
or:
|
|
171
199
|
|
|
172
200
|
```bash
|
|
173
|
-
keryx flow init --title "<short formalized problem>"
|
|
201
|
+
keryx flow init --title "<short formalized problem>" --base "<branch the work must land on>"
|
|
174
202
|
```
|
|
175
203
|
|
|
176
204
|
5. Run `keryx flow status <id>` and read the flow package.
|
|
177
|
-
6.
|
|
178
|
-
`context.md` and `journal.md
|
|
179
|
-
|
|
205
|
+
6. `--base` records the branch in the flow record itself, where the completion
|
|
206
|
+
gate reads it. Note it in `context.md` and `journal.md` as well if it helps a
|
|
207
|
+
reader, but prose is not what the gate checks: before this flag the base
|
|
208
|
+
survived only as something an agent had written down, which is detectable at
|
|
209
|
+
dispatch and undetectable at completion.
|
|
180
210
|
|
|
181
211
|
## Phase 1: Initialize The Flow Package
|
|
182
212
|
|
|
@@ -414,9 +444,16 @@ How should this flow end?
|
|
|
414
444
|
|
|
415
445
|
3. Follow the selected outcome:
|
|
416
446
|
|
|
417
|
-
- **A - Create PR and merge:** create or confirm a PR in the author's name
|
|
418
|
-
|
|
419
|
-
|
|
447
|
+
- **A - Create PR and merge:** create or confirm a PR in the author's name,
|
|
448
|
+
opened against the base recorded at initialization. Do not mark the flow
|
|
449
|
+
implemented or complete before the PR is merged into that branch.
|
|
450
|
+
|
|
451
|
+
`keryx flow complete` now checks this rather than asking you to confirm it by
|
|
452
|
+
eye: its `base-branch` condition compares where the merge landed against the
|
|
453
|
+
base in the record, and refuses when they differ. It reports three states,
|
|
454
|
+
and only one of them is a pass — a flow that recorded no base gets
|
|
455
|
+
`not recorded`, which is not a pass either. So the useful thing to do here is
|
|
456
|
+
make sure the base WAS recorded, not to re-verify the merge yourself.
|
|
420
457
|
|
|
421
458
|
### A dispatched run answers the question from its input
|
|
422
459
|
|
|
@@ -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.
|
|
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
|
-
|
|
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 `
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
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 `
|
|
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
|
-
-
|
|
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.
|
|
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
|
-
|
|
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 `
|
|
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
|
-
-
|
|
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.
|
|
@@ -591,6 +591,7 @@ merge, with every reviewer unanswered.
|
|
|
591
591
|
|
|
592
592
|
```bash
|
|
593
593
|
keryx review comments reply --repo <owner/repo> --pr <n> --outcomes <file|-> \
|
|
594
|
+
--result <this-run's-output.json> \
|
|
594
595
|
--sha <mergedHeadSha> --final [--dry-run] [--flow-link <url>]
|
|
595
596
|
```
|
|
596
597
|
|
|
@@ -598,6 +599,17 @@ Run it **after** the merge, never during the loop: a reply written mid-round sta
|
|
|
598
599
|
an intention, and by the time the reviewer reads it the intention has changed.
|
|
599
600
|
`--final` is required by the command; it is not a reminder that can be skipped.
|
|
600
601
|
|
|
602
|
+
`--result` hands this run's own output to the one keryx-owned point that can
|
|
603
|
+
refuse it. Write the result described in Step 11 to a file first and pass it
|
|
604
|
+
here: the command validates it against `review-pr-feedback-output` BEFORE
|
|
605
|
+
posting anything, so a result that contradicts itself — analyze mode reporting a
|
|
606
|
+
merge, or a screen recorded as never having run while excluding comments — stops
|
|
607
|
+
here instead of being published under your name.
|
|
608
|
+
|
|
609
|
+
Optional in the CLI, and the registry records this contract as `opt-in` rather
|
|
610
|
+
than enforced for exactly that reason: omit the flag and nothing is checked.
|
|
611
|
+
Passing it is the whole of the enforcement.
|
|
612
|
+
|
|
601
613
|
The judgement is yours; the command owns the mechanics. It routes an inline comment
|
|
602
614
|
to its thread (`pulls/{n}/comments/{id}/replies`) and a review-submission body or
|
|
603
615
|
PR-level comment to one top-level comment that names what it answers — GitHub
|
|
@@ -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
|
-
});
|