@mrciphersmith/keryx 0.2.82 → 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.
- package/README.md +2 -1
- package/dist/cli.js +28857 -19477
- package/dist/core.js +25751 -0
- package/package.json +15 -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 +34 -6
- 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/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
|
|
|
@@ -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.
|
|
@@ -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
|
-
});
|