@opengsd/gsd-core 1.19.0 → 1.20.0
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/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/agents/gsd-code-fixer.compact.md +5 -5
- package/agents/gsd-code-fixer.md +9 -4
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +73 -23
- package/gsd-core/bin/lib/capability-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-registry.cjs +284 -93
- package/gsd-core/bin/lib/capability-validator.cjs +15 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- package/gsd-core/bin/lib/config-loader.cjs +3 -3
- package/gsd-core/bin/lib/core-utils.cjs +11 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +1 -1
- package/gsd-core/bin/lib/gate-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- package/gsd-core/bin/lib/git-info-exclude.cjs +1 -6
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +13 -9
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -3
- package/gsd-core/bin/lib/init.cjs +180 -67
- package/gsd-core/bin/lib/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +34 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- package/gsd-core/bin/lib/milestone.cjs +18 -50
- package/gsd-core/bin/lib/phase-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +609 -35
- package/gsd-core/bin/lib/phase-lifecycle.cjs +9 -74
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +2990 -666
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +12 -7
- package/gsd-core/bin/lib/planning-workspace.cjs +3 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +23 -2
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +22 -0
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +730 -144
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +8 -5
- package/gsd-core/bin/lib/roadmap.cjs +358 -106
- package/gsd-core/bin/lib/shell-command-projection.cjs +36 -9
- package/gsd-core/bin/lib/smart-entry.cjs +22 -11
- package/gsd-core/bin/lib/state-contract.cjs +11 -7
- package/gsd-core/bin/lib/state-document.cjs +332 -23
- package/gsd-core/bin/lib/state-transition.cjs +39 -10
- package/gsd-core/bin/lib/state.cjs +498 -179
- package/gsd-core/bin/lib/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verify.cjs +2 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- package/gsd-core/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/necessity-review.md +48 -0
- package/gsd-core/workflows/code-review-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +3 -0
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +1 -1
- package/gsd-core/workflows/pr-branch.md +2 -0
- package/gsd-core/workflows/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +54 -141
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/ship.md +12 -0
- package/hooks/dist/gsd-graphify-update.sh +6 -0
- package/hooks/dist/gsd-statusline.js +3 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +4 -4
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/gsd-worktree-path-guard.js +4 -4
- package/hooks/dist/lib/git-probe.js +16 -0
- package/hooks/gsd-graphify-update.sh +6 -0
- package/hooks/gsd-statusline.js +3 -2
- package/hooks/gsd-windsurf-pre-write.js +4 -4
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/gsd-worktree-path-guard.js +4 -4
- package/hooks/lib/git-probe.js +16 -0
- package/package.json +3 -3
- package/pi/package-agents/gsd-code-fixer.md +9 -4
- package/pi/package-agents/gsd-code-reviewer.md +53 -14
- package/pi/package-agents/gsd-executor.md +8 -4
- package/pi/package-agents/manifest.json +3 -3
- package/pi/package-skills/gsd-update/SKILL.md +4 -1
- package/scripts/ci-next-health.cjs +246 -23
- package/scripts/docs-guard-registry.cjs +2 -0
- package/scripts/gen-features.cjs +14 -2
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +6 -0
- package/scripts/lib/platform-conformance-tier.generated.cjs +11 -0
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- package/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- package/vscode/package.json +1 -1
|
@@ -256,17 +256,17 @@ Treat this as a finally-block obligation: even on early exit (config error, no f
|
|
|
256
256
|
|
|
257
257
|
<step name="load_context">
|
|
258
258
|
1. Read all `<required_reading>` files if present.
|
|
259
|
-
2. Parse `<config>` block: `phase_dir`, `padded_phase`, `review_path` (full path to REVIEW.md), `fix_scope` ("critical_warning" default, or "all" includes Info), `fix_report_path` (output REVIEW-FIX.md path).
|
|
259
|
+
2. Parse `<config>` block: `phase_dir`, `padded_phase`, `review_path` (full path to REVIEW.md), `fix_scope` ("critical_warning" default, or "all" includes Info and necessity), `fix_report_path` (output REVIEW-FIX.md path).
|
|
260
260
|
3. `cat {review_path}`.
|
|
261
261
|
4. Parse frontmatter `status:`. If `"clean"` or `"skipped"`: exit with "No issues to fix -- REVIEW.md status is {status}." — do NOT create REVIEW-FIX.md, exit 0 (not an error).
|
|
262
262
|
5. Load project context (`<project_context>`): CLAUDE.md, skills.
|
|
263
263
|
</step>
|
|
264
264
|
|
|
265
265
|
<step name="parse_findings">
|
|
266
|
-
1. Extract findings via `<finding_parser>` rules: `id`, `severity` (Critical CR-*/BL-*, Warning WR-*, Info IN-*), `title`, `file` (primary), `files` (all referenced, for multi-file fixes), `line` (or null), `issue`, `fix` (may be multi-line/code fences).
|
|
267
|
-
2. Filter by `fix_scope`: `critical_warning` → CR-*/BL-*/WR-* only; `all` → + IN
|
|
268
|
-
3. Sort: Critical first, then Warning, then Info; same-severity keeps document order.
|
|
269
|
-
4. Record `findings_in_scope` count for frontmatter.
|
|
266
|
+
1. Extract findings via `<finding_parser>` rules: `id`, `severity` (Critical CR-*/BL-*, Warning WR-*, Info IN-*), `category` (`necessity` when the finding carries a `**Category:** necessity` line, #4100 — unnecessary code, not broken code), `title`, `file` (primary), `files` (all referenced, for multi-file fixes), `line` (or null), `issue`, `fix` (may be multi-line/code fences).
|
|
267
|
+
2. Filter by `fix_scope`: `critical_warning` → CR-*/BL-*/WR-* only, and `critical_warning` excludes necessity findings (removal is the human's scope call; the marker overrides the prefix, so a WR-* carrying `**Category:** necessity` is out of scope and skipped with no edit); `all` → + IN-*, and `all` includes necessity findings, ordered last — record each as `fixed: requires human verification`.
|
|
268
|
+
3. Sort: Critical first, then Warning, then Info, then necessity; same-severity keeps document order.
|
|
269
|
+
4. Record `findings_in_scope` count for frontmatter. If `findings_in_scope` is 0, apply nothing: write REVIEW-FIX.md with `fixed: 0`, `skipped: 0`, `status: all_fixed`, and a body stating no finding was in scope and the out-of-scope findings remain in REVIEW.md.
|
|
270
270
|
</step>
|
|
271
271
|
|
|
272
272
|
<step name="apply_fixes">
|
package/agents/gsd-code-fixer.md
CHANGED
|
@@ -158,6 +158,8 @@ Each finding starts with:
|
|
|
158
158
|
|
|
159
159
|
Where ID matches: `CR-\d+` or `BL-\d+` (Critical-tier-equivalent), `WR-\d+` (Warning), or `IN-\d+` (Info)
|
|
160
160
|
|
|
161
|
+
A finding whose body carries a `**Category:** necessity` line is a **necessity finding** (#4100): code the reviewer judged unnecessary, not broken. Its prefix still gives its severity.
|
|
162
|
+
|
|
161
163
|
**Required Fields:**
|
|
162
164
|
|
|
163
165
|
- **File:** line contains primary file path
|
|
@@ -439,7 +441,7 @@ This cleanup is unconditional when a worktree was created — register it mental
|
|
|
439
441
|
- `phase_dir`: Path to phase directory (e.g., `.planning/phases/02-code-review-command`)
|
|
440
442
|
- `padded_phase`: Zero-padded phase number (e.g., "02")
|
|
441
443
|
- `review_path`: Full path to REVIEW.md (e.g., `.planning/phases/02-code-review-command/02-REVIEW.md`)
|
|
442
|
-
- `fix_scope`: "critical_warning" (default) or "all" (includes Info findings)
|
|
444
|
+
- `fix_scope`: "critical_warning" (default) or "all" (includes Info and necessity findings)
|
|
443
445
|
- `fix_report_path`: Full path for REVIEW-FIX.md output (e.g., `.planning/phases/02-code-review-command/02-REVIEW-FIX.md`)
|
|
444
446
|
|
|
445
447
|
**3. Read REVIEW.md:**
|
|
@@ -465,6 +467,7 @@ Read `./CLAUDE.md` and check for `.claude/skills/` or `.agents/skills/` (as desc
|
|
|
465
467
|
For each finding, extract:
|
|
466
468
|
- `id`: Finding identifier (e.g., CR-01, WR-03, IN-12)
|
|
467
469
|
- `severity`: Critical (CR-* or BL-*), Warning (WR-*), Info (IN-*)
|
|
470
|
+
- `category`: `necessity` when the finding carries a `**Category:** necessity` line, otherwise none
|
|
468
471
|
- `title`: Issue title from `### ` heading
|
|
469
472
|
- `file`: Primary file path from **File:** line
|
|
470
473
|
- `files`: ALL file paths referenced in finding (including in Fix section) — for multi-file fixes
|
|
@@ -473,15 +476,17 @@ For each finding, extract:
|
|
|
473
476
|
- `fix`: Full fix content from **Fix:** section (may be multi-line, may contain code fences)
|
|
474
477
|
|
|
475
478
|
**2. Filter by fix_scope:**
|
|
476
|
-
- If `fix_scope == "critical_warning"`: include only CR-*, BL-*, and WR-* findings
|
|
477
|
-
- If `fix_scope == "all"`: include CR-*, BL-*, WR-*, and IN-* findings
|
|
479
|
+
- If `fix_scope == "critical_warning"`: include only CR-*, BL-*, and WR-* findings; `critical_warning` excludes necessity findings (removing code is a scope decision for the human, #4100). The marker overrides the prefix: a WR-* finding carrying `**Category:** necessity` is OUT of scope here, and is skipped without applying any edit
|
|
480
|
+
- If `fix_scope == "all"`: include CR-*, BL-*, WR-*, and IN-* findings; `all` includes necessity findings, ordered last. A necessity fix removes code, so record it as `fixed: requires human verification`
|
|
478
481
|
|
|
479
482
|
**3. Sort findings by severity:**
|
|
480
|
-
- Critical (CR-* and BL-*) first, then Warning, then Info
|
|
483
|
+
- Critical (CR-* and BL-*) first, then Warning, then Info, then necessity findings
|
|
481
484
|
- Within same severity, maintain document order
|
|
482
485
|
|
|
483
486
|
**4. Count findings in scope:**
|
|
484
487
|
Record `findings_in_scope` for REVIEW-FIX.md frontmatter.
|
|
488
|
+
|
|
489
|
+
If `findings_in_scope` is 0, apply nothing: write REVIEW-FIX.md with `fixed: 0`, `skipped: 0`, `status: all_fixed`, and a body stating that no finding was in scope and the out-of-scope findings remain in REVIEW.md.
|
|
485
490
|
</step>
|
|
486
491
|
|
|
487
492
|
<step name="apply_fixes">
|
|
@@ -49,6 +49,8 @@ Read `./CLAUDE.md` if present — follow project guidelines, security requiremen
|
|
|
49
49
|
|
|
50
50
|
**3. Code Quality** — dead code, unused imports/variables, poor naming, missing error handling, inconsistent patterns, overly complex functions (high cyclomatic complexity), code duplication, magic numbers, commented-out code
|
|
51
51
|
|
|
52
|
+
**4. Necessity** (#4100) — reachable code whose guarded-against problem cannot occur in this stack: impossible-state guard, already-defended-elsewhere, cargo-culted pattern, phantom requirement, generality without instantiation. At `standard`/`deep`, Read `~/.claude/gsd-core/references/necessity-review.md` before classifying; if unreadable, say so in the Summary and omit `necessity:`. `quick` does not assess necessity. Warning when provable from the repo, Info when judgment, Never Critical; cite the evidence the state cannot occur and add a `**Category:** necessity` line.
|
|
53
|
+
|
|
52
54
|
**Out of Scope (v1):** performance issues (O(n²) algorithms, memory leaks, inefficient queries) — NOT in scope. Focus on correctness, security, maintainability.
|
|
53
55
|
|
|
54
56
|
</review_scope>
|
|
@@ -56,10 +58,12 @@ Read `./CLAUDE.md` if present — follow project guidelines, security requiremen
|
|
|
56
58
|
<depth_levels>
|
|
57
59
|
|
|
58
60
|
**quick** — pattern-matching only, grep/regex scan for common anti-patterns, no full file reads. Target: <2 min.
|
|
59
|
-
|
|
61
|
+
Checks (commands in `review_by_depth`): Hardcoded secrets; Dangerous functions; Debug artifacts; Empty catch blocks; Commented-out code; Unpinned actions; Non-idempotent install; Missing job timeout.
|
|
62
|
+
The last three: GitHub Actions workflows only.
|
|
60
63
|
|
|
61
64
|
**standard** (default) — Read each changed file, check bugs/security/quality in context, cross-reference imports/exports. Target: 5-15 min.
|
|
62
65
|
Language-aware checks: **JS/TS** unchecked `.length`, missing `await`, unhandled promise rejection, `as any`, `==` vs `===`, null coalescing issues. **Python** bare `except:`, mutable default args, f-string injection, `eval()`, missing `with` for file ops. **Go** unchecked error returns, goroutine leaks, context not passed, `defer` in loops, race conditions. **C/C++** buffer overflow patterns, use-after-free, null pointer deref, missing bounds checks, memory leaks. **Shell** unquoted variables, `eval`, missing `set -e`, command injection via interpolation.
|
|
66
|
+
**CI/CD pipelines** (by path: `.github/workflows/*.yml`, `Dockerfile*`, `docker-compose*.yml`, deploy scripts): Read `~/.claude/gsd-core/references/cicd-review-patterns.md`; if it cannot be read, say so in the Summary and check: non-idempotent steps (`npm install` vs `npm ci`, unlocked `pip install`, unguarded `mkdir`/`rm`), no job `timeout-minutes`, deploy without `concurrency` + `cancel-in-progress`, per-environment rebuild (not build-once), no health check or rollback, load-bearing `continue-on-error`, static credentials where OIDC works, unpinned actions.
|
|
63
67
|
|
|
64
68
|
**deep** — all of standard + cross-file analysis: trace call chains across imports, check type consistency at API boundaries (TS interfaces, API contracts), verify error propagation (thrown errors caught by callers), check state mutation consistency across modules, detect circular dependencies/coupling. Target: 15-30 min.
|
|
65
69
|
|
|
@@ -109,6 +113,7 @@ Use its `files`. A `status` of `unresolvable` (exit 69, JSON still printed), or
|
|
|
109
113
|
NOTE: do NOT exclude all `.md` — commands, workflows, and agents are source code in this codebase.
|
|
110
114
|
|
|
111
115
|
**2. Group by language/type:** JS/TS (`.js`,`.jsx`,`.ts`,`.tsx`), Python (`.py`), Go (`.go`), C/C++ (`.c`,`.cpp`,`.h`,`.hpp`), Shell (`.sh`,`.bash`), other → generic.
|
|
116
|
+
CI/CD (by path): `.github/workflows/*.yml`/`*.yaml`, `Dockerfile*`, `docker-compose*.yml`, `deploy*.sh` (also Shell).
|
|
112
117
|
|
|
113
118
|
**3. Exit early if empty:** create REVIEW.md with `status: skipped`, all finding counts 0. Body: "No source files to review after filtering. All files in scope are documentation, planning artifacts, or generated files. Use `status: skipped` (not `clean`) because no actual review was performed."
|
|
114
119
|
|
|
@@ -116,14 +121,47 @@ NOTE: `status: clean` = reviewed, no issues. `status: skipped` = no reviewable f
|
|
|
116
121
|
</step>
|
|
117
122
|
|
|
118
123
|
<step name="review_by_depth">
|
|
119
|
-
**depth=quick:** run
|
|
124
|
+
**depth=quick:** run every check from `<depth_levels>`:
|
|
120
125
|
```bash
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
grep -n -E "
|
|
126
|
+
# Set FILE per scoped path with: while IFS= read -r FILE (never paste a name into code).
|
|
127
|
+
|
|
128
|
+
# Hardcoded secrets
|
|
129
|
+
grep -n -E -- "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"][^'\"]+['\"]" "$FILE"
|
|
130
|
+
|
|
131
|
+
# Dangerous functions
|
|
132
|
+
grep -n -E -- "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec|passthru" "$FILE"
|
|
133
|
+
|
|
134
|
+
# Debug artifacts
|
|
135
|
+
grep -n -E -- "console\.log|debugger;|TODO|FIXME|XXX|HACK" "$FILE"
|
|
136
|
+
|
|
137
|
+
# Empty catch blocks
|
|
138
|
+
grep -n -E -- "catch\s*\([^)]*\)\s*\{\s*\}" "$FILE"
|
|
139
|
+
|
|
140
|
+
# Commented-out code
|
|
141
|
+
grep -n -E -- "^\s*//.*[{};]|^\s*#[[:space:]]*(if|for|while|def|class|elif|else|try|except|with)[[:space:](].*:[[:space:]]*$|^\s*/\*[^*].*[{};]" "$FILE"
|
|
142
|
+
|
|
143
|
+
# Unpinned actions
|
|
144
|
+
grep -n -E -- "^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*['\"]?[^[:space:]'\"]+@(main|master|latest)['\"]?([[:space:]]|$)" "$FILE"
|
|
145
|
+
|
|
146
|
+
# Non-idempotent install
|
|
147
|
+
[ -f package-lock.json ] && grep -n -E -- "(^|[^[:alnum:]_-])npm[[:space:]]+(install|i)([[:space:]]|$)" "$FILE" | grep -v -E "^[0-9]+:[[:space:]]*#|[[:space:]](-g|--global)([[:space:]]|$)"
|
|
148
|
+
|
|
149
|
+
# Missing job timeout
|
|
150
|
+
awk '
|
|
151
|
+
function flush() { if (job != "" && runs && !timed) print line ": " job ": no job-level timeout-minutes"; job = "" }
|
|
152
|
+
{ sub(/\r$/, "") }
|
|
153
|
+
/^[^[:space:]#]/ { flush(); injobs = ($0 ~ /^jobs:[[:space:]]*(#.*)?$/); jind = -1; next }
|
|
154
|
+
!injobs || /^[[:space:]]*(#.*)?$/ { next }
|
|
155
|
+
{ ind = match($0, /[^ ]/) - 1 }
|
|
156
|
+
jind < 0 { jind = ind }
|
|
157
|
+
ind == jind { flush(); job = $0; sub(/^ +/, "", job); sub(/:.*/, "", job); line = NR; kind = -1; runs = 0; timed = 0; next }
|
|
158
|
+
kind < 0 { kind = ind }
|
|
159
|
+
ind == kind && /^ *runs-on:/ { runs = 1 }
|
|
160
|
+
ind == kind && /^ *timeout-minutes:/ { timed = 1 }
|
|
161
|
+
END { flush() }
|
|
162
|
+
' "$FILE"
|
|
125
163
|
```
|
|
126
|
-
Severity: secrets/dangerous=Critical, debug=Info, empty catch=Warning.
|
|
164
|
+
Severity: secrets/dangerous=Critical, debug and commented-out code=Info, empty catch and the three CI/CD checks=Warning.
|
|
127
165
|
|
|
128
166
|
**depth=standard:** per file — Read full content, apply language-specific checks, check for: functions >50 lines, deep nesting (>4 levels), missing error handling in async functions, hardcoded config values, type safety issues (TS `any`, loose Python typing). Record findings with file path, line number, description.
|
|
129
167
|
|
|
@@ -133,7 +171,7 @@ Severity: secrets/dangerous=Critical, debug=Info, empty catch=Warning.
|
|
|
133
171
|
<step name="classify_findings">
|
|
134
172
|
**Critical** — security vulnerabilities, data loss, crashes, auth bypasses: SQL/command/path-traversal injection, hardcoded secrets in production code, null pointer derefs that crash, auth/authz bypasses, unsafe deserialization, buffer overflows.
|
|
135
173
|
|
|
136
|
-
**Warning** — logic errors, unhandled edge cases, missing error handling, code smells that could cause bugs: unchecked array access, missing async error handling, off-by-one errors, `==` vs `===` coercion, unhandled promise rejections, dead code paths indicating logic errors.
|
|
174
|
+
**Warning** — logic errors, unhandled edge cases, missing error handling, code smells that could cause bugs: unchecked array access, missing async error handling, off-by-one errors, `==` vs `===` coercion, unhandled promise rejections, dead code paths indicating logic errors; CI/CD pipeline findings: every check in the CI/CD entry.
|
|
137
175
|
|
|
138
176
|
**Info** — style, naming, dead code, unused imports, suggestions: unused imports/variables, poor naming (single letters except loop counters), commented-out code, TODO/FIXME, magic numbers, duplication.
|
|
139
177
|
|
|
@@ -158,10 +196,13 @@ findings:
|
|
|
158
196
|
warning: N
|
|
159
197
|
info: N
|
|
160
198
|
total: N
|
|
199
|
+
necessity: N
|
|
161
200
|
status: clean | issues_found
|
|
162
201
|
---
|
|
163
202
|
```
|
|
164
203
|
|
|
204
|
+
`necessity:` only at standard/deep: the necessity findings among `warning` + `info`, never added to `total`.
|
|
205
|
+
|
|
165
206
|
**3. Body sections (required order):**
|
|
166
207
|
1) `## Structural Findings (fallow)` — only if structural findings provided; normalized items first.
|
|
167
208
|
2) `## Narrative Findings (AI reviewer)` — your adversarial findings, including any external claim independently verified (`(external: {slug})`).
|
|
@@ -61,6 +61,8 @@ This ensures project-specific patterns, conventions, and best practices are appl
|
|
|
61
61
|
|
|
62
62
|
**3. Code Quality** — Dead code, unused imports/variables, poor naming conventions, missing error handling, inconsistent patterns, overly complex functions (high cyclomatic complexity), code duplication, magic numbers, commented-out code
|
|
63
63
|
|
|
64
|
+
**4. Necessity** (#4100) — reachable code whose guarded-against problem cannot occur in this stack: impossible-state guard, already-defended-elsewhere, cargo-culted pattern, phantom requirement, generality without instantiation. At `standard`/`deep`, Read `~/.claude/gsd-core/references/necessity-review.md` before classifying findings; if it cannot be read, say so in the Summary and omit `necessity:`. `quick` does not assess necessity. Warning when provable from the repo, Info when it rests on judgment, Never Critical; each finding cites the evidence that the state cannot occur and carries a `**Category:** necessity` line.
|
|
65
|
+
|
|
64
66
|
**Out of Scope (v1):** Performance issues (O(n²) algorithms, memory leaks, inefficient queries) are NOT in scope for v1. Focus on correctness, security, and maintainability.
|
|
65
67
|
|
|
66
68
|
</review_scope>
|
|
@@ -71,12 +73,17 @@ This ensures project-specific patterns, conventions, and best practices are appl
|
|
|
71
73
|
|
|
72
74
|
**quick** — Pattern-matching only. Use grep/regex to scan for common anti-patterns without reading full file contents. Target: under 2 minutes.
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
- Hardcoded secrets
|
|
76
|
-
- Dangerous functions
|
|
77
|
-
- Debug artifacts
|
|
78
|
-
- Empty catch blocks
|
|
79
|
-
- Commented-out code
|
|
76
|
+
Checks (commands in `review_by_depth`):
|
|
77
|
+
- Hardcoded secrets
|
|
78
|
+
- Dangerous functions
|
|
79
|
+
- Debug artifacts
|
|
80
|
+
- Empty catch blocks
|
|
81
|
+
- Commented-out code
|
|
82
|
+
- Unpinned actions
|
|
83
|
+
- Non-idempotent install
|
|
84
|
+
- Missing job timeout
|
|
85
|
+
|
|
86
|
+
The last three: GitHub Actions workflows only.
|
|
80
87
|
|
|
81
88
|
**standard** (default) — Read each changed file. Check for bugs, security issues, and quality problems in context. Cross-reference imports and exports. Target: 5-15 minutes.
|
|
82
89
|
|
|
@@ -86,6 +93,7 @@ Language-aware checks:
|
|
|
86
93
|
- **Go**: Unchecked error returns, goroutine leaks, context not passed, `defer` in loops, race conditions
|
|
87
94
|
- **C/C++**: Buffer overflow patterns, use-after-free indicators, null pointer dereferences, missing bounds checks, memory leaks
|
|
88
95
|
- **Shell**: Unquoted variables, `eval` usage, missing `set -e`, command injection via interpolation
|
|
96
|
+
- **CI/CD pipelines** (by path: `.github/workflows/*.yml`, `Dockerfile*`, `docker-compose*.yml`, deploy scripts): Read `~/.claude/gsd-core/references/cicd-review-patterns.md`; if it cannot be read, say so in the Summary and check: non-idempotent steps (`npm install` vs `npm ci`, unlocked `pip install`, unguarded `mkdir`/`rm`), no job `timeout-minutes`, deploy without `concurrency` + `cancel-in-progress`, per-environment rebuild (not build-once), no health check or rollback, load-bearing `continue-on-error`, static credentials where OIDC works, unpinned actions
|
|
89
97
|
|
|
90
98
|
**deep** — All of standard, plus cross-file analysis. Trace function call chains across imports. Target: 15-30 minutes.
|
|
91
99
|
|
|
@@ -174,6 +182,7 @@ NOTE: Do NOT exclude all `.md` files — commands, workflows, and agents are sou
|
|
|
174
182
|
- Go: `.go`
|
|
175
183
|
- C/C++: `.c`, `.cpp`, `.h`, `.hpp`
|
|
176
184
|
- Shell: `.sh`, `.bash`
|
|
185
|
+
- CI/CD (by path): `.github/workflows/*.yml`/`*.yaml`, `Dockerfile*`, `docker-compose*.yml`, `deploy*.sh` (also Shell)
|
|
177
186
|
- Other: Review generically
|
|
178
187
|
|
|
179
188
|
**3. Exit early if empty:** If no source files remain after filtering, create REVIEW.md with:
|
|
@@ -194,22 +203,48 @@ NOTE: `status: clean` means "reviewed and found no issues." `status: skipped` me
|
|
|
194
203
|
Branch on depth level:
|
|
195
204
|
|
|
196
205
|
**For depth=quick:**
|
|
197
|
-
Run
|
|
206
|
+
Run every check from `<depth_levels>` quick section:
|
|
198
207
|
```bash
|
|
208
|
+
# Set FILE per scoped path with: while IFS= read -r FILE (never paste a name into code).
|
|
209
|
+
|
|
199
210
|
# Hardcoded secrets
|
|
200
|
-
grep -n -E "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"]\
|
|
211
|
+
grep -n -E -- "(password|secret|api_key|token|apikey|api-key)\s*[=:]\s*['\"][^'\"]+['\"]" "$FILE"
|
|
201
212
|
|
|
202
213
|
# Dangerous functions
|
|
203
|
-
grep -n -E "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec"
|
|
214
|
+
grep -n -E -- "eval\(|innerHTML|dangerouslySetInnerHTML|exec\(|system\(|shell_exec|passthru" "$FILE"
|
|
204
215
|
|
|
205
216
|
# Debug artifacts
|
|
206
|
-
grep -n -E "console\.log|debugger;|TODO|FIXME|XXX|HACK"
|
|
207
|
-
|
|
208
|
-
# Empty catch
|
|
209
|
-
grep -n -E "catch\s*\([^)]*\)\s*\{\s*\}"
|
|
217
|
+
grep -n -E -- "console\.log|debugger;|TODO|FIXME|XXX|HACK" "$FILE"
|
|
218
|
+
|
|
219
|
+
# Empty catch blocks
|
|
220
|
+
grep -n -E -- "catch\s*\([^)]*\)\s*\{\s*\}" "$FILE"
|
|
221
|
+
|
|
222
|
+
# Commented-out code
|
|
223
|
+
grep -n -E -- "^\s*//.*[{};]|^\s*#[[:space:]]*(if|for|while|def|class|elif|else|try|except|with)[[:space:](].*:[[:space:]]*$|^\s*/\*[^*].*[{};]" "$FILE"
|
|
224
|
+
|
|
225
|
+
# Unpinned actions
|
|
226
|
+
grep -n -E -- "^[[:space:]]*(-[[:space:]]+)?uses:[[:space:]]*['\"]?[^[:space:]'\"]+@(main|master|latest)['\"]?([[:space:]]|$)" "$FILE"
|
|
227
|
+
|
|
228
|
+
# Non-idempotent install
|
|
229
|
+
[ -f package-lock.json ] && grep -n -E -- "(^|[^[:alnum:]_-])npm[[:space:]]+(install|i)([[:space:]]|$)" "$FILE" | grep -v -E "^[0-9]+:[[:space:]]*#|[[:space:]](-g|--global)([[:space:]]|$)"
|
|
230
|
+
|
|
231
|
+
# Missing job timeout
|
|
232
|
+
awk '
|
|
233
|
+
function flush() { if (job != "" && runs && !timed) print line ": " job ": no job-level timeout-minutes"; job = "" }
|
|
234
|
+
{ sub(/\r$/, "") }
|
|
235
|
+
/^[^[:space:]#]/ { flush(); injobs = ($0 ~ /^jobs:[[:space:]]*(#.*)?$/); jind = -1; next }
|
|
236
|
+
!injobs || /^[[:space:]]*(#.*)?$/ { next }
|
|
237
|
+
{ ind = match($0, /[^ ]/) - 1 }
|
|
238
|
+
jind < 0 { jind = ind }
|
|
239
|
+
ind == jind { flush(); job = $0; sub(/^ +/, "", job); sub(/:.*/, "", job); line = NR; kind = -1; runs = 0; timed = 0; next }
|
|
240
|
+
kind < 0 { kind = ind }
|
|
241
|
+
ind == kind && /^ *runs-on:/ { runs = 1 }
|
|
242
|
+
ind == kind && /^ *timeout-minutes:/ { timed = 1 }
|
|
243
|
+
END { flush() }
|
|
244
|
+
' "$FILE"
|
|
210
245
|
```
|
|
211
246
|
|
|
212
|
-
Record findings with severity: secrets/dangerous=Critical, debug=Info, empty catch=Warning
|
|
247
|
+
Record findings with severity: secrets/dangerous=Critical, debug and commented-out code=Info, empty catch and the three CI/CD checks=Warning
|
|
213
248
|
|
|
214
249
|
**For depth=standard:**
|
|
215
250
|
For each file:
|
|
@@ -253,6 +288,7 @@ For each finding, assign severity:
|
|
|
253
288
|
- Type coercion issues (`==` vs `===`)
|
|
254
289
|
- Unhandled promise rejections
|
|
255
290
|
- Dead code paths that indicate logic errors
|
|
291
|
+
- CI/CD pipeline findings: every check in the CI/CD entry
|
|
256
292
|
|
|
257
293
|
**Info** — Style issues, naming improvements, dead code, unused imports, suggestions:
|
|
258
294
|
- Unused imports/variables
|
|
@@ -287,10 +323,13 @@ findings:
|
|
|
287
323
|
warning: N
|
|
288
324
|
info: N
|
|
289
325
|
total: N
|
|
326
|
+
necessity: N
|
|
290
327
|
status: clean | issues_found
|
|
291
328
|
---
|
|
292
329
|
```
|
|
293
330
|
|
|
331
|
+
Emit `necessity:` only at standard/deep depth. It counts the necessity findings among `warning` + `info` and is never added to `total`.
|
|
332
|
+
|
|
294
333
|
**3. Body sections (required order):**
|
|
295
334
|
1) `## Structural Findings (fallow)` — only when structural findings were provided; list normalized items first.
|
|
296
335
|
2) `## Narrative Findings (AI reviewer)` — your adversarial findings from direct code review, including any external-reviewer claim you independently verified (`(external: {slug})`, see `load_context` step 5).
|
package/agents/gsd-executor.md
CHANGED
|
@@ -766,7 +766,14 @@ Do NOT skip. Do NOT proceed to state updates if self-check fails.
|
|
|
766
766
|
</self_check>
|
|
767
767
|
|
|
768
768
|
<state_updates>
|
|
769
|
-
After SUMMARY.md, update STATE.md
|
|
769
|
+
After SUMMARY.md, update ROADMAP.md progress FIRST, then STATE.md. The ROADMAP write is the step that can decline (`missing_phase_details`), and a decline stops the plan before any STATE.md write, so STATE.md never advances past a plan the ROADMAP did not record:
|
|
770
|
+
|
|
771
|
+
```bash
|
|
772
|
+
# Update ROADMAP.md progress for this phase (plan counts, status). A decline stops here, before STATE.md moves.
|
|
773
|
+
PROGRESS=$(gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}") && REASON=$(jq -er '.reason//""' <<<"$PROGRESS") && [ "$REASON" != missing_phase_details ] || { echo "ERROR: ROADMAP update-plan-progress did not complete for ${PHASE_NUMBER}-${PLAN} (missing_phase_details decline, failed query, or jq not installed). STOP: skip this plan's completion steps." >&2; exit 1; }
|
|
774
|
+
```
|
|
775
|
+
|
|
776
|
+
Then update STATE.md using `gsd-tools query` state handlers (named flags):
|
|
770
777
|
|
|
771
778
|
```bash
|
|
772
779
|
# Advance plan counter (handles edge cases automatically)
|
|
@@ -793,9 +800,6 @@ gsd_run query state.record-session \
|
|
|
793
800
|
```
|
|
794
801
|
|
|
795
802
|
```bash
|
|
796
|
-
# Update ROADMAP.md progress for this phase (plan counts, status)
|
|
797
|
-
gsd_run query roadmap.update-plan-progress "${PHASE_NUMBER}"
|
|
798
|
-
|
|
799
803
|
# Mark completed requirements from PLAN.md frontmatter
|
|
800
804
|
# Extract the `requirements` array from the plan's frontmatter, then mark each complete
|
|
801
805
|
gsd_run query requirements.mark-complete ${REQ_IDS}
|
package/commands/gsd/update.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:update
|
|
3
3
|
description: Update GSD to latest version with changelog display
|
|
4
|
-
argument-hint: "[--sync | --reapply | --next | --rc]"
|
|
4
|
+
argument-hint: "[--sync | --reapply | --next | --rc] [--interactive=exceptions|all]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -35,14 +35,17 @@ Routes to the update workflow which handles:
|
|
|
35
35
|
<flags>
|
|
36
36
|
- **--sync**: Sync managed GSD skills across runtime roots so multi-runtime users stay aligned after an update. Runs the sync-skills workflow (--from, --to, --dry-run, --apply flags supported).
|
|
37
37
|
- **--reapply**: Reapply local modifications after a GSD update. Uses three-way comparison (pristine baseline, user-modified backup, newly installed version) to merge user customizations back. Runs the reapply-patches workflow.
|
|
38
|
+
- **--interactive[=exceptions|all]** (with `--reapply` only): show each user edit next to the stock text with a recommended disposition and wait for a keep / retire / rewrite decision per edit. `all` shows every edit; `exceptions` (bare `--interactive`) shows only edits upstream adopted, partly adopted, reworded around, or orphaned, and keeps the rest. Without it, `--reapply` behaves exactly as before.
|
|
38
39
|
- **--next** (alias **--rc**): Target the `@next` RC dist-tag instead of `@latest` so you can install or refresh a release candidate (e.g. `1.4.0-rc.1`) through the normal update flow — scope/runtime detection, changelog preview, custom-file backup, and cache clearing all still apply. Omitting it keeps targeting `@latest` (no change). See ADR #660 for the RC channel.
|
|
39
40
|
- **(no flag)**: Standard update — check for new version, show changelog, install.
|
|
40
41
|
</flags>
|
|
41
42
|
|
|
42
43
|
<process>
|
|
44
|
+
If the arguments contain `--interactive` (bare or with a value) but not `--reapply`, STOP with: `--interactive applies only to --reapply (for example: /gsd:update --reapply --interactive=all)`. Do nothing else.
|
|
45
|
+
|
|
43
46
|
Parse the first token of the `<arguments>` block:
|
|
44
47
|
- If it is `--sync`: strip the flag, execute the sync-skills workflow (passing remaining args for --from/--to/--dry-run/--apply).
|
|
45
|
-
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow.
|
|
48
|
+
- If it is `--reapply`: strip the flag, execute the reapply-patches workflow, passing the remaining arguments (for example `--interactive=all`) to it.
|
|
46
49
|
- Otherwise (including `--next` / `--rc`): execute the update workflow end-to-end, passing the `<arguments>` block through so the workflow's parse_update_channel step can select the release channel.
|
|
47
50
|
|
|
48
51
|
</process>
|
|
@@ -1631,7 +1631,8 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1631
1631
|
const fsx = require('node:fs');
|
|
1632
1632
|
const os = require('node:os');
|
|
1633
1633
|
const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
|
|
1634
|
-
const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort,
|
|
1634
|
+
const { resolveLanePlan: resolveLanePlanBase, resolveLaneEffort, resolveLaneCap, refuseIfOverCap, renderPromptRefusalStub, artifactPaths, applyPromptOverride } = require('./lib/review-lane-invocation.cjs');
|
|
1635
|
+
const { estimateTokens } = require('./lib/prompt-budget.cjs');
|
|
1635
1636
|
// OpenCode 2.x argv differs from 1.x (no --variant; effort rides on the model as `#variant`;
|
|
1636
1637
|
// ADR-5331 D5/D8). The generation is probed lazily, only when the opencode lane is resolved.
|
|
1637
1638
|
const resolveLanePlan = (laneInput) => {
|
|
@@ -1915,11 +1916,11 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1915
1916
|
};
|
|
1916
1917
|
const effortFor = (lane) => resolveLaneEffort(lane, configGet, renderLaneEffort);
|
|
1917
1918
|
|
|
1918
|
-
// Per-lane prompt
|
|
1919
|
+
// Per-lane prompt cap: `resolveLaneCap` (review-lane-invocation.cjs) owns the #2797 + #4559
|
|
1919
1920
|
// resolution semantics (shared with src/reviewer-step-dispatch.cts, #4209 R3 — was two
|
|
1920
|
-
// verbatim copies).
|
|
1921
|
-
//
|
|
1922
|
-
const
|
|
1921
|
+
// verbatim copies). `plan` reports it; `invoke` REFUSES a lane whose prompt is over it
|
|
1922
|
+
// (#4559) rather than trimming — the prompt is never edited on the way to a lane.
|
|
1923
|
+
const capFor = (lane) => resolveLaneCap(lane, configGet);
|
|
1923
1924
|
|
|
1924
1925
|
// #4209 (ADR-2782 seam) — the ONE interpreter route for a step that declared
|
|
1925
1926
|
// `supportsReviewerLanes: true`. Wires `dispatchReviewerLanes` (src/reviewer-step-dispatch.cts)
|
|
@@ -2059,17 +2060,19 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2059
2060
|
} catch (e) {
|
|
2060
2061
|
return { slug, ok: false, reason: 'malformed_lane', detail: `resolver threw: ${e && e.message ? e.message : String(e)}` };
|
|
2061
2062
|
}
|
|
2062
|
-
return r.
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
:
|
|
2063
|
+
if (!r.ok) return { slug, ok: false, reason: r.reason, detail: r.detail };
|
|
2064
|
+
const laneCap = capFor(lane);
|
|
2065
|
+
return {
|
|
2066
|
+
slug,
|
|
2067
|
+
ok: true,
|
|
2068
|
+
section: lane.reviewsSection,
|
|
2069
|
+
transport: r.plan.transport,
|
|
2070
|
+
promptBudget: laneCap.cap,
|
|
2071
|
+
promptCapSource: laneCap.source,
|
|
2072
|
+
contextWindowTokens: laneCap.contextWindowTokens,
|
|
2073
|
+
promptPath: r.plan.transport === 'spawn' ? r.plan.stdin : r.plan.promptPath,
|
|
2074
|
+
plan: r.plan,
|
|
2075
|
+
};
|
|
2073
2076
|
});
|
|
2074
2077
|
|
|
2075
2078
|
if (sub === 'plan') {
|
|
@@ -2178,14 +2181,61 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2178
2181
|
];
|
|
2179
2182
|
}
|
|
2180
2183
|
|
|
2181
|
-
// `--prompt-file` lets the caller substitute a
|
|
2182
|
-
// channel this lane actually reads from (stdin, argv, an argv-embedded file
|
|
2183
|
-
// http promptPath), so one flag serves every lane (#5049).
|
|
2184
|
+
// `--prompt-file` lets the caller substitute a different prompt (a section subset, say). Applied
|
|
2185
|
+
// to whichever channel this lane actually reads from (stdin, argv, an argv-embedded file
|
|
2186
|
+
// reference, or the http promptPath), so one flag serves every lane (#5049). It is no longer a
|
|
2187
|
+
// trim hand-off: nothing in review.md trims a prompt to fit a lane since #4559.
|
|
2184
2188
|
const promptOverride = flag('--prompt-file');
|
|
2185
2189
|
if (promptOverride) {
|
|
2186
2190
|
entry.plan = applyPromptOverride(entry.plan, promptOverride, repoRoot);
|
|
2187
2191
|
}
|
|
2188
2192
|
|
|
2193
|
+
// #4559: refuse, never trim. Measure the prompt this lane would actually receive — the file
|
|
2194
|
+
// the RESOLVED plan reads (stdin file, or the plan's prompt path for argv / argv-file-ref /
|
|
2195
|
+
// http lanes; `--prompt-file` already retargeted the plan above) — against its cap BEFORE any
|
|
2196
|
+
// consent lookup, probe or spawn. An unreadable file is not a refusal: the runner reports its
|
|
2197
|
+
// own error for it. The decision itself lives in `refuseIfOverCap`; this only does the IO.
|
|
2198
|
+
{
|
|
2199
|
+
const plannedPrompt = entry.plan.transport === 'spawn' && entry.plan.stdin
|
|
2200
|
+
? entry.plan.stdin
|
|
2201
|
+
: entry.plan.promptPath;
|
|
2202
|
+
let promptText = null;
|
|
2203
|
+
try {
|
|
2204
|
+
promptText = fs.readFileSync(plannedPrompt || artifactPaths(runDir, entry.slug).promptPath, 'utf8');
|
|
2205
|
+
} catch { promptText = null; }
|
|
2206
|
+
if (promptText !== null) {
|
|
2207
|
+
const refused = refuseIfOverCap({
|
|
2208
|
+
lane: laneBySlug.get(entry.slug),
|
|
2209
|
+
estimatedTokens: estimateTokens(promptText),
|
|
2210
|
+
configGet,
|
|
2211
|
+
});
|
|
2212
|
+
if (refused) {
|
|
2213
|
+
const reviewPath = entry.plan.reviewPath;
|
|
2214
|
+
let stubWritten = true;
|
|
2215
|
+
try {
|
|
2216
|
+
fs.mkdirSync(path.dirname(reviewPath), { recursive: true });
|
|
2217
|
+
fs.writeFileSync(reviewPath, renderPromptRefusalStub(entry.slug, refused.detail), 'utf8');
|
|
2218
|
+
} catch (e) {
|
|
2219
|
+
stubWritten = false;
|
|
2220
|
+
process.stderr.write(
|
|
2221
|
+
`review-lane invoke: could not write the refusal stub to ${reviewPath}: ` +
|
|
2222
|
+
`${e && e.message ? e.message : String(e)}\n`,
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
output({
|
|
2226
|
+
slug: entry.slug,
|
|
2227
|
+
ok: false,
|
|
2228
|
+
reason: refused.reason,
|
|
2229
|
+
detail: refused.detail,
|
|
2230
|
+
reviewPath,
|
|
2231
|
+
stubWritten,
|
|
2232
|
+
refusal: refused.refusal,
|
|
2233
|
+
}, raw);
|
|
2234
|
+
return;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2189
2239
|
// ADR-2782 D5 rule 4: re-resolve the consented destination at INVOCATION. `undefined` (no
|
|
2190
2240
|
// consent record, or a record predating rule 1) means "nothing to compare" and ALLOWS — a
|
|
2191
2241
|
// first-party lane ships inside the SHA-pinned distribution and is never consent-gated, so
|
|
@@ -4604,7 +4654,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4604
4654
|
// #1956: deterministic STATE.md-vs-ROADMAP.md phase-status drift.
|
|
4605
4655
|
const { planningDir } = require('./lib/planning-workspace.cjs');
|
|
4606
4656
|
const { stateExtractField, stateCurrentPositionSlice } = require('./lib/state-document.cjs');
|
|
4607
|
-
const {
|
|
4657
|
+
const { locateRoadmapProgress } = require('./lib/roadmap-progress.cjs');
|
|
4608
4658
|
const { phaseKeyFromProse } = require('./lib/phase-id.cjs');
|
|
4609
4659
|
// STATE.md's YAML frontmatter carries its own lowercase `status:`
|
|
4610
4660
|
// scalar ahead of the body's `## Current Position` prose "Status:"
|
|
@@ -4702,9 +4752,9 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
4702
4752
|
|
|
4703
4753
|
const stateStatus = stateExtractField(currentPositionBody, 'Status');
|
|
4704
4754
|
|
|
4705
|
-
// #1956/#2012: scoped to `## Progress` first (decoy-avoidance) —
|
|
4706
|
-
//
|
|
4707
|
-
const table =
|
|
4755
|
+
// #1956/#2012: scoped to `## Progress` first (decoy-avoidance) — the
|
|
4756
|
+
// Progress owner's rule (roadmap-progress.cts), shared with every reader and writer (#5762).
|
|
4757
|
+
const { table } = locateRoadmapProgress(roadmapContent);
|
|
4708
4758
|
const matchedRow = table
|
|
4709
4759
|
? table.rows.find((row) => phaseKeyFromProse(row.Phase) === phase && phase !== null)
|
|
4710
4760
|
: undefined;
|
|
@@ -34,7 +34,7 @@ const consentMod = require('./capability-consent.cjs');
|
|
|
34
34
|
const projectRootMod = require('./project-root.cjs');
|
|
35
35
|
// #1459 finding 4: the SHARED hardened lock primitive (single source of truth for lifecycle + consent).
|
|
36
36
|
const lockMod = require('./capability-lock.cjs');
|
|
37
|
-
const { platformWriteSync, retryRenameSync } = require('./shell-command-projection.cjs');
|
|
37
|
+
const { platformWriteSync, retryRenameSync, shellSingleQuote } = require('./shell-command-projection.cjs');
|
|
38
38
|
// #1463: numeric major.minor.patch comparison for the outdated check (the SAME compare the resolver
|
|
39
39
|
// and capability list use). -1 (a<b), 0 (equal), 1 (a>b).
|
|
40
40
|
const semverMod = require('./semver-compare.cjs');
|
|
@@ -308,17 +308,6 @@ function isSafeHookScriptPath(script) {
|
|
|
308
308
|
return false;
|
|
309
309
|
return true;
|
|
310
310
|
}
|
|
311
|
-
/**
|
|
312
|
-
* #1460 (R) HIGH: POSIX single-quote an arbitrary string for safe inclusion in a shell command.
|
|
313
|
-
* The emitted hook `command` is the ABSOLUTE confined script path, which begins with the
|
|
314
|
-
* (non-manifest) install-prefix — commonly a home dir containing spaces/special chars (e.g.
|
|
315
|
-
* "/Users/Bob Smith/.claude/..."). Written unquoted it would word-split (and, with a hostile
|
|
316
|
-
* prefix, could inject). Wrapping in single quotes — with each embedded `'` escaped as `'\''` —
|
|
317
|
-
* makes the whole path a single shell token that no metacharacter inside it can break.
|
|
318
|
-
*/
|
|
319
|
-
function shellSingleQuote(value) {
|
|
320
|
-
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
321
|
-
}
|
|
322
311
|
/**
|
|
323
312
|
* #1634: build the emitted hook `command` for an ABSOLUTE confined script path. For `.js`-family
|
|
324
313
|
* hooks (`.js`/`.cjs`/`.mjs`) prefix with `node` so the hook runs regardless of the source's
|