@opengsd/gsd-core 1.6.0-rc.2 → 1.6.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/plugin.json +2 -1
- package/agents/gsd-advisor-researcher.md +2 -0
- package/agents/gsd-ai-researcher.md +2 -0
- package/agents/gsd-assumptions-analyzer.md +2 -0
- package/agents/gsd-doc-classifier.md +2 -0
- package/agents/gsd-doc-synthesizer.md +2 -0
- package/agents/gsd-domain-researcher.md +2 -0
- package/agents/gsd-eval-auditor.md +6 -9
- package/agents/gsd-phase-researcher.md +2 -0
- package/agents/gsd-planner.md +8 -57
- package/agents/gsd-project-researcher.md +2 -0
- package/agents/gsd-research-synthesizer.md +2 -0
- package/agents/gsd-security-auditor.md +37 -18
- package/agents/gsd-ui-researcher.md +2 -0
- package/bin/install.js +370 -18
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +46 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +52 -14
- package/gsd-core/bin/lib/capability-lifecycle.cjs +30 -7
- package/gsd-core/bin/lib/capability-registry.cjs +96 -83
- package/gsd-core/bin/lib/capability-validator.cjs +22 -0
- package/gsd-core/bin/lib/cjs-command-router-adapter.cjs +40 -2
- package/gsd-core/bin/lib/command-aliases.cjs +10 -1
- package/gsd-core/bin/lib/command-routing-hub.cjs +10 -3
- package/gsd-core/bin/lib/config-schema.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +67 -24
- package/gsd-core/bin/lib/coverage.cjs +464 -0
- package/gsd-core/bin/lib/decisions.cjs +27 -0
- package/gsd-core/bin/lib/eval-command-router.cjs +21 -0
- package/gsd-core/bin/lib/eval.cjs +60 -0
- package/gsd-core/bin/lib/frontmatter.cjs +132 -13
- package/gsd-core/bin/lib/graphify-command-router.cjs +53 -36
- package/gsd-core/bin/lib/init.cjs +139 -30
- package/gsd-core/bin/lib/install-profiles.cjs +6 -3
- package/gsd-core/bin/lib/intel-command-router.cjs +79 -60
- package/gsd-core/bin/lib/io.cjs +1 -0
- package/gsd-core/bin/lib/phase.cjs +16 -2
- package/gsd-core/bin/lib/plan-scan.cjs +2 -2
- package/gsd-core/bin/lib/planning-workspace.cjs +157 -13
- package/gsd-core/bin/lib/profile-output.cjs +18 -6
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +53 -16
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +21 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +15 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +47 -2
- package/gsd-core/bin/lib/shell-command-projection.cjs +10 -0
- package/gsd-core/bin/lib/state.cjs +398 -60
- package/gsd-core/bin/lib/surface.cjs +42 -10
- package/gsd-core/bin/lib/uat-predicate.cjs +13 -6
- package/gsd-core/bin/lib/update-context.cjs +2 -2
- package/gsd-core/bin/lib/verification.cjs +67 -6
- package/gsd-core/bin/lib/verify.cjs +8 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +3 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -1
- package/gsd-core/references/planner-guidance.md +66 -0
- package/gsd-core/references/planning-config.md +2 -2
- package/gsd-core/references/security-asvs-levels.md +27 -0
- package/gsd-core/references/untrusted-input-boundary.md +13 -0
- package/gsd-core/templates/SECURITY.md +6 -4
- package/gsd-core/templates/summary-complex.md +4 -0
- package/gsd-core/templates/summary-minimal.md +3 -0
- package/gsd-core/templates/summary-standard.md +4 -0
- package/gsd-core/templates/summary.md +41 -0
- package/gsd-core/workflows/autonomous.md +53 -46
- package/gsd-core/workflows/complete-milestone.md +27 -8
- package/gsd-core/workflows/execute-phase.md +1 -1
- package/gsd-core/workflows/execute-plan.md +5 -0
- package/gsd-core/workflows/manager.md +17 -7
- package/gsd-core/workflows/new-project.md +82 -16
- package/gsd-core/workflows/plan-phase.md +15 -0
- package/gsd-core/workflows/profile-user.md +6 -2
- package/gsd-core/workflows/progress.md +37 -4
- package/gsd-core/workflows/quick.md +3 -1
- package/gsd-core/workflows/secure-phase.md +13 -7
- package/gsd-core/workflows/ship.md +3 -1
- package/gsd-core/workflows/spec-phase.md +3 -1
- package/gsd-core/workflows/transition.md +14 -12
- package/gsd-core/workflows/ui-review.md +2 -6
- package/gsd-core/workflows/verify-work.md +74 -1
- package/hooks/dist/gsd-read-injection-scanner.js +49 -25
- package/hooks/gsd-read-injection-scanner.js +49 -25
- package/hooks/hooks.json +1 -1
- package/package.json +4 -2
- package/scripts/check-alias-drift.cjs +5 -0
- package/scripts/gen-plugin-skills.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/prompt-injection-scan.sh +9 -0
- package/scripts/release-notes/conventional-title.cjs +88 -0
- package/scripts/release-notes/format-github-release-notes.cjs +4 -3
- package/skills/gsd-add-tests/SKILL.md +38 -0
- package/skills/gsd-ai-integration-phase/SKILL.md +37 -0
- package/skills/gsd-audit-fix/SKILL.md +33 -0
- package/skills/gsd-audit-milestone/SKILL.md +37 -0
- package/skills/gsd-audit-uat/SKILL.md +25 -0
- package/skills/gsd-autonomous/SKILL.md +51 -0
- package/skills/gsd-capture/SKILL.md +67 -0
- package/skills/gsd-cleanup/SKILL.md +24 -0
- package/skills/gsd-code-review/SKILL.md +59 -0
- package/skills/gsd-complete-milestone/SKILL.md +142 -0
- package/skills/gsd-config/SKILL.md +56 -0
- package/skills/gsd-debug/SKILL.md +53 -0
- package/skills/gsd-discuss-phase/SKILL.md +77 -0
- package/skills/gsd-docs-update/SKILL.md +49 -0
- package/skills/gsd-eval-review/SKILL.md +33 -0
- package/skills/gsd-execute-phase/SKILL.md +65 -0
- package/skills/gsd-explore/SKILL.md +28 -0
- package/skills/gsd-extract-learnings/SKILL.md +22 -0
- package/skills/gsd-fast/SKILL.md +31 -0
- package/skills/gsd-forensics/SKILL.md +56 -0
- package/skills/gsd-graphify/SKILL.md +204 -0
- package/skills/gsd-health/SKILL.md +31 -0
- package/skills/gsd-help/SKILL.md +29 -0
- package/skills/gsd-import/SKILL.md +46 -0
- package/skills/gsd-inbox/SKILL.md +39 -0
- package/skills/gsd-ingest-docs/SKILL.md +43 -0
- package/skills/gsd-manager/SKILL.md +45 -0
- package/skills/gsd-map-codebase/SKILL.md +83 -0
- package/skills/gsd-mempalace-capture/SKILL.md +71 -0
- package/skills/gsd-mempalace-recall/SKILL.md +102 -0
- package/skills/gsd-milestone-summary/SKILL.md +51 -0
- package/skills/gsd-mvp-phase/SKILL.md +45 -0
- package/skills/gsd-new-milestone/SKILL.md +45 -0
- package/skills/gsd-new-project/SKILL.md +47 -0
- package/skills/gsd-ns-context/SKILL.md +24 -0
- package/skills/gsd-ns-ideate/SKILL.md +23 -0
- package/skills/gsd-ns-manage/SKILL.md +35 -0
- package/skills/gsd-ns-project/SKILL.md +26 -0
- package/skills/gsd-ns-review/SKILL.md +28 -0
- package/skills/gsd-ns-workflow/SKILL.md +33 -0
- package/skills/gsd-pause-work/SKILL.md +43 -0
- package/skills/gsd-phase/SKILL.md +57 -0
- package/skills/gsd-plan-phase/SKILL.md +63 -0
- package/skills/gsd-plan-review-convergence/SKILL.md +60 -0
- package/skills/gsd-pr-branch/SKILL.md +26 -0
- package/skills/gsd-profile-user/SKILL.md +47 -0
- package/skills/gsd-progress/SKILL.md +49 -0
- package/skills/gsd-quick/SKILL.md +174 -0
- package/skills/gsd-resume-work/SKILL.md +31 -0
- package/skills/gsd-review/SKILL.md +42 -0
- package/skills/gsd-review-backlog/SKILL.md +63 -0
- package/skills/gsd-secure-phase/SKILL.md +36 -0
- package/skills/gsd-settings/SKILL.md +29 -0
- package/skills/gsd-ship/SKILL.md +24 -0
- package/skills/gsd-sketch/SKILL.md +60 -0
- package/skills/gsd-spec-phase/SKILL.md +63 -0
- package/skills/gsd-spike/SKILL.md +57 -0
- package/skills/gsd-stats/SKILL.md +20 -0
- package/skills/gsd-surface/SKILL.md +162 -0
- package/skills/gsd-thread/SKILL.md +24 -0
- package/skills/gsd-ui-phase/SKILL.md +35 -0
- package/skills/gsd-ui-review/SKILL.md +33 -0
- package/skills/gsd-ultraplan-phase/SKILL.md +34 -0
- package/skills/gsd-undo/SKILL.md +35 -0
- package/skills/gsd-update/SKILL.md +50 -0
- package/skills/gsd-validate-phase/SKILL.md +36 -0
- package/skills/gsd-verify-work/SKILL.md +39 -0
- package/skills/gsd-workspace/SKILL.md +53 -0
- package/skills/gsd-workstreams/SKILL.md +70 -0
|
@@ -273,7 +273,7 @@ This is a WARNING, not a blocker — routing proceeds normally. The debt is visi
|
|
|
273
273
|
|
|
274
274
|
**Step 1.7: Check verification status for the current phase**
|
|
275
275
|
|
|
276
|
-
A phase whose verification
|
|
276
|
+
A phase whose verification is missing, unknown, `gaps_found`, or `human_needed` is NOT complete, even when every PLAN.md has a matching SUMMARY.md. The count-based status (`roadmap.analyze`) only sees plans/summaries, so without this check such a phase is reported complete and routing skips straight to the next phase. When the phase appears count-complete (`summaries = plans AND plans > 0`), consult the verification report (the same `verification.status` gate `ship` and `execute-phase` use, from #651):
|
|
277
277
|
|
|
278
278
|
```bash
|
|
279
279
|
PHASE_DIR=".planning/phases/[current-phase-dir]"
|
|
@@ -282,7 +282,7 @@ VERIFICATION_STATUS=$(printf '%s' "$VERIFICATION" | jq -r '.status' 2>/dev/null
|
|
|
282
282
|
VERIFICATION_NEXT_ACTION=$(printf '%s' "$VERIFICATION" | jq -r '.next_action' 2>/dev/null || echo "")
|
|
283
283
|
```
|
|
284
284
|
|
|
285
|
-
Track: `verification_status` — the `.status` field (`passed | gaps_found | human_needed | missing | unknown`). The query
|
|
285
|
+
Track: `verification_status` — the `.status` field (`passed | stale | gaps_found | human_needed | missing | unknown`). The query/projection handles a missing VERIFICATION.md (`missing`), unexpected values, and stale verification (`stale`, when summaries are newer than verification). Only `passed` routes as phase complete (Step 3); every other status routes back to close verification debt (Step 2).
|
|
286
286
|
|
|
287
287
|
**Step 2: Route based on counts**
|
|
288
288
|
|
|
@@ -291,12 +291,15 @@ Track: `verification_status` — the `.status` field (`passed | gaps_found | hum
|
|
|
291
291
|
| uat_partial > 0 | UAT testing incomplete | Go to **Route E.2** |
|
|
292
292
|
| uat_with_gaps > 0 | UAT gaps need fix plans | Go to **Route E** |
|
|
293
293
|
| summaries < plans | Unexecuted plans exist | Go to **Route A** |
|
|
294
|
+
| summaries = plans AND plans > 0 AND verification_status = missing | Phase executed; verification report missing | Go to **Route V.missing** |
|
|
295
|
+
| summaries = plans AND plans > 0 AND verification_status = unknown | Phase executed; verification status unknown | Go to **Route V.unknown** |
|
|
296
|
+
| summaries = plans AND plans > 0 AND verification_status = stale | Phase executed; verification is stale | Go to **Route V.stale** |
|
|
294
297
|
| summaries = plans AND plans > 0 AND verification_status = gaps_found | Phase executed; verification found gaps | Go to **Route V.gaps** |
|
|
295
298
|
| summaries = plans AND plans > 0 AND verification_status = human_needed | Phase executed; awaiting human verification | Go to **Route V.human** |
|
|
296
|
-
| summaries = plans AND plans > 0 | Phase complete (verification passed
|
|
299
|
+
| summaries = plans AND plans > 0 AND verification_status = passed | Phase complete (verification passed) | Go to Step 3 |
|
|
297
300
|
| plans = 0 | Phase not yet planned | Go to **Route B** |
|
|
298
301
|
|
|
299
|
-
Rows are evaluated top to bottom; the first matching row wins. The
|
|
302
|
+
Rows are evaluated top to bottom; the first matching row wins. The `verification_status` rows must precede the passed row so non-`passed` verification is not reported as complete.
|
|
300
303
|
|
|
301
304
|
---
|
|
302
305
|
|
|
@@ -448,6 +451,36 @@ UAT.md exists with `status: partial` — testing session ended before all items
|
|
|
448
451
|
|
|
449
452
|
---
|
|
450
453
|
|
|
454
|
+
**Route V.missing: verification report missing**
|
|
455
|
+
|
|
456
|
+
All plans have summaries, but canonical verification has not passed. The phase is implementation-complete, not phase-complete.
|
|
457
|
+
|
|
458
|
+
```
|
|
459
|
+
`/gsd:execute-phase {phase} ${GSD_WS}` — re-run execution verification
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
**Route V.unknown: verification status unknown**
|
|
465
|
+
|
|
466
|
+
VERIFICATION.md has an unexpected status. The phase is implementation-complete, not phase-complete.
|
|
467
|
+
|
|
468
|
+
```
|
|
469
|
+
`/gsd:execute-phase {phase} ${GSD_WS}` — regenerate verification
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
**Route V.stale: verification is stale**
|
|
475
|
+
|
|
476
|
+
VERIFICATION.md has `status: passed`, but one or more SUMMARY.md files are newer than the verification report. The phase is implementation-complete, not phase-complete.
|
|
477
|
+
|
|
478
|
+
```
|
|
479
|
+
`/gsd:verify-work {phase} ${GSD_WS}` — re-run verification against the latest summaries
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
451
484
|
**Route V.gaps: verification found gaps (gaps_found)**
|
|
452
485
|
|
|
453
486
|
VERIFICATION.md exists with `status: gaps_found` — verification identified gaps that need fix plans. The phase is NOT complete.
|
|
@@ -675,7 +675,9 @@ Capture current HEAD before spawning (used for worktree branch check):
|
|
|
675
675
|
```bash
|
|
676
676
|
EXPECTED_BASE=$(git rev-parse HEAD)
|
|
677
677
|
if [ "${USE_WORKTREES:-true}" != "false" ]; then
|
|
678
|
-
|
|
678
|
+
# BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
|
|
679
|
+
# suffixless temp then append the extension — portable across BSD + GNU (#1520).
|
|
680
|
+
QUICK_WORKTREE_MANIFEST=$(mktemp "${TMPDIR:-/tmp}/gsd-quick-worktree-XXXXXX") && mv "$QUICK_WORKTREE_MANIFEST" "${QUICK_WORKTREE_MANIFEST}.json" && QUICK_WORKTREE_MANIFEST="${QUICK_WORKTREE_MANIFEST}.json" || exit 1
|
|
679
681
|
printf '{"worktrees":[]}\n' > "$QUICK_WORKTREE_MANIFEST"
|
|
680
682
|
export QUICK_WORKTREE_MANIFEST
|
|
681
683
|
fi
|
|
@@ -27,6 +27,8 @@ Parse: `phase_dir`, `phase_number`, `phase_name`, `phase_slug`, `padded_phase`.
|
|
|
27
27
|
```bash
|
|
28
28
|
AUDITOR_MODEL=$(gsd_run query resolve-model gsd-security-auditor --raw)
|
|
29
29
|
VERIFY_POST_HOOKS_JSON=$(gsd_run loop render-hooks verify:post --raw)
|
|
30
|
+
SECURITY_ASVS=$(gsd_run query config-get workflow.security_asvs_level --raw 2>/dev/null || echo "1")
|
|
31
|
+
SECURITY_BLOCK_ON=$(gsd_run query config-get workflow.security_block_on --raw 2>/dev/null || echo "high")
|
|
30
32
|
```
|
|
31
33
|
|
|
32
34
|
Resolve active step hooks from `VERIFY_POST_HOOKS_JSON` where `kind == "step"` and `ref.skill == "secure-phase"`.
|
|
@@ -51,7 +53,7 @@ SUMMARY_FILES=$(ls "${PHASE_DIR}"/*-SUMMARY.md 2>/dev/null)
|
|
|
51
53
|
|
|
52
54
|
### 2a. Read Phase Artifacts
|
|
53
55
|
|
|
54
|
-
Read PLAN.md — extract `<threat_model>` block: trust boundaries, STRIDE register (`threat_id`, `category`, `component`, `disposition`, `mitigation_plan`).
|
|
56
|
+
Read PLAN.md — extract `<threat_model>` block: trust boundaries, STRIDE register (`threat_id`, `category`, `component`, `severity`, `disposition`, `mitigation_plan`).
|
|
55
57
|
|
|
56
58
|
### 2b. Read Summary Threat Flags
|
|
57
59
|
|
|
@@ -59,7 +61,7 @@ Read SUMMARY.md — extract `## Threat Flags` entries.
|
|
|
59
61
|
|
|
60
62
|
### 2c. Build Threat Register
|
|
61
63
|
|
|
62
|
-
Per threat: `{ threat_id, category, component, disposition, mitigation_pattern, files_to_check }`
|
|
64
|
+
Per threat: `{ threat_id, category, component, severity, disposition, mitigation_pattern, files_to_check }`
|
|
63
65
|
|
|
64
66
|
Also set `register_authored_at_plan_time: true` if **at least one** PLAN file contained a parseable `<threat_model>` block; `false` if no PLAN files had any `<threat_model>` block (legacy phase authored before formal threat modelling was standard).
|
|
65
67
|
|
|
@@ -72,10 +74,11 @@ Classify each threat:
|
|
|
72
74
|
| CLOSED | mitigation found OR accepted risk documented in SECURITY.md OR transfer documented |
|
|
73
75
|
| OPEN | none of the above |
|
|
74
76
|
|
|
75
|
-
Build: `{ threat_id, category, component, disposition, status, evidence }`
|
|
77
|
+
Build: `{ threat_id, category, component, severity, disposition, status, evidence }`
|
|
76
78
|
|
|
77
79
|
**Short-circuit rule:**
|
|
78
|
-
- If `threats_open: 0 AND register_authored_at_plan_time: true` → skip to Step 6 directly.
|
|
80
|
+
- If `threats_open: 0 AND register_authored_at_plan_time: true AND asvs_level == 1` → skip to Step 6 directly. No open threats at or above the block threshold remain (threats_open: 0); below-threshold open threats may remain and are non-blocking. L1 grep-depth is sufficient; no deeper verification required.
|
|
81
|
+
- If `threats_open: 0 AND register_authored_at_plan_time: true AND asvs_level >= 2` → **do NOT skip**. The preliminary threat classification is grep-level (L1 depth) and is insufficient for L2/L3. Proceed to Step 5 (spawn the auditor) so that L2 boundary-placement checks and L3 end-to-end trace checks are performed. Skipping the auditor here would defeat ASVS level scaling for "clean" phases.
|
|
79
82
|
- If `threats_open: 0 AND register_authored_at_plan_time: false` → **do NOT skip**. Empty-by-no-planning must not rubber-stamp a clean SECURITY.md. Proceed to Step 5 in **retroactive-STRIDE mode** — the auditor builds a register from implementation files first, then verifies mitigations.
|
|
80
83
|
- If `threats_open > 0` → proceed to Step 4 (present threat plan to user).
|
|
81
84
|
|
|
@@ -95,6 +98,8 @@ Call AskUserQuestion with threat table and options:
|
|
|
95
98
|
- `register_authored_at_plan_time: true` — **Verify mitigations exist** — do not scan for new threats. The register is complete; verify each threat's mitigation is present in the implementation.
|
|
96
99
|
- `register_authored_at_plan_time: false` (retroactive-STRIDE mode) — **Retroactive-STRIDE: build a STRIDE register from implementation files first, then verify mitigations.** The phase was authored before formal threat modelling; the auditor must construct the register from scratch before verifying.
|
|
97
100
|
|
|
101
|
+
Substitute `{SECURITY_ASVS}` with the value of `$SECURITY_ASVS` and `{SECURITY_BLOCK_ON}` with the value of `$SECURITY_BLOCK_ON` resolved in Step 0 via `config-get`.
|
|
102
|
+
|
|
98
103
|
Print: `◆ Spawning security auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
|
|
99
104
|
|
|
100
105
|
```
|
|
@@ -141,7 +146,7 @@ Handle return:
|
|
|
141
146
|
|
|
142
147
|
```
|
|
143
148
|
GSD > PHASE {N} SECURITY BLOCKED
|
|
144
|
-
{K} threats open — phase advancement blocked until threats_open: 0
|
|
149
|
+
{K} blocking threats open — phase advancement blocked until threats_open: 0
|
|
145
150
|
▶ Fix mitigations then re-run: /gsd:secure-phase {N}
|
|
146
151
|
▶ Or document accepted risks in SECURITY.md and re-run.
|
|
147
152
|
```
|
|
@@ -159,7 +164,7 @@ gsd_run query commit "docs(phase-${PHASE}): add/update security threat verificat
|
|
|
159
164
|
**Secured (threats_open: 0):**
|
|
160
165
|
```
|
|
161
166
|
GSD > PHASE {N} THREAT-SECURE
|
|
162
|
-
threats_open: 0 —
|
|
167
|
+
threats_open: 0 — no blocking threats remain (threats_open: 0).
|
|
163
168
|
▶ /gsd:validate-phase {N} validate test coverage
|
|
164
169
|
▶ /gsd:verify-work {N} run UAT
|
|
165
170
|
```
|
|
@@ -173,7 +178,8 @@ Display `/clear` reminder.
|
|
|
173
178
|
- [ ] Input state detected (A/B/C) — state C exits cleanly
|
|
174
179
|
- [ ] PLAN.md threat model parsed, register built
|
|
175
180
|
- [ ] SUMMARY.md threat flags incorporated
|
|
176
|
-
- [ ] threats_open: 0 AND register_authored_at_plan_time: true → skip directly to Step 6
|
|
181
|
+
- [ ] threats_open: 0 AND register_authored_at_plan_time: true AND asvs_level == 1 → skip directly to Step 6 (L1 grep-depth sufficient)
|
|
182
|
+
- [ ] threats_open: 0 AND register_authored_at_plan_time: true AND asvs_level >= 2 → do NOT skip; auditor spawned for L2/L3 deep verification
|
|
177
183
|
- [ ] threats_open: 0 AND register_authored_at_plan_time: false → retroactive-STRIDE mode (Step 5), not skipped
|
|
178
184
|
- [ ] User gate with threat table presented
|
|
179
185
|
- [ ] Auditor spawned with complete context
|
|
@@ -280,7 +280,9 @@ Use the exact key order `skill=`, `fallback=`, `exempt=`, `missing=` so downstre
|
|
|
280
280
|
Create the PR using the generated body. Write the body to a temp file first so large generated PRD sections do not hit shell argument limits:
|
|
281
281
|
|
|
282
282
|
```bash
|
|
283
|
-
|
|
283
|
+
# BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
|
|
284
|
+
# suffixless temp then append the extension — portable across BSD + GNU (#1520).
|
|
285
|
+
PR_BODY_FILE=$(mktemp "${TMPDIR:-/tmp}/gsd-pr-body-XXXXXX") && mv "$PR_BODY_FILE" "${PR_BODY_FILE}.md" && PR_BODY_FILE="${PR_BODY_FILE}.md" || exit 1
|
|
284
286
|
trap 'rm -f "${PR_BODY_FILE:-}"' EXIT
|
|
285
287
|
printf '%s\n' "${PR_BODY}" > "${PR_BODY_FILE}"
|
|
286
288
|
|
|
@@ -235,7 +235,9 @@ fi
|
|
|
235
235
|
# canonical coverage compute. Populate the heredoc from the SPEC's Requirements — one object
|
|
236
236
|
# per requirement: {"id","text","shapes"?}. This is the load-bearing step: an empty file makes
|
|
237
237
|
# the probe a no-op, so the guard below fails loud rather than silently skipping (RR-04).
|
|
238
|
-
|
|
238
|
+
# BSD/macOS mktemp only randomizes XXXXXX when it is the final path component, so make a
|
|
239
|
+
# suffixless temp then append the extension — portable across BSD + GNU (#1520).
|
|
240
|
+
REQS_JSON=$(mktemp "${TMPDIR:-/tmp}/edge-probe-reqs-XXXXXX") && mv "$REQS_JSON" "${REQS_JSON}.json" && REQS_JSON="${REQS_JSON}.json" || exit 1
|
|
239
241
|
cat > "$REQS_JSON" <<'JSON'
|
|
240
242
|
[
|
|
241
243
|
{ "id": "R1", "text": "<replace: requirement text from the SPEC>" }
|
|
@@ -77,26 +77,28 @@ cat .planning/config.json 2>/dev/null || true
|
|
|
77
77
|
**Check for verification debt in this phase:**
|
|
78
78
|
|
|
79
79
|
```bash
|
|
80
|
-
#
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
# Run a preliminary frontmatter check via awk — the runtime launcher is not yet
|
|
81
|
+
# defined at this step, so avoid any runtime tool calls here.
|
|
82
|
+
# awk extracts only the status: field between the two --- fences to avoid
|
|
83
|
+
# false positives from historical body text (e.g. previous_status: gaps_found).
|
|
84
|
+
VERIFY_STATUS=$(awk 'NR==1&&/^---$/{in_fm=1;next}in_fm&&/^---$/{exit}in_fm&&/^status: /{print $2}' \
|
|
85
|
+
.planning/phases/XX-current/*-VERIFICATION.md 2>/dev/null | head -1)
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
**If
|
|
88
|
+
**If VERIFY_STATUS is not `passed`:**
|
|
89
89
|
|
|
90
|
-
|
|
90
|
+
Stop before confirming:
|
|
91
91
|
|
|
92
92
|
```
|
|
93
|
-
|
|
94
|
-
{list filenames}
|
|
93
|
+
Verification incomplete: ${VERIFY_STATUS:-missing}
|
|
95
94
|
|
|
96
|
-
|
|
95
|
+
Resolve before transition. Review: `/gsd:audit-uat`
|
|
97
96
|
```
|
|
98
97
|
|
|
99
|
-
This
|
|
98
|
+
This preliminary check blocks obviously unresolved verification before the
|
|
99
|
+
launcher is available. `gsd-tools.cjs query phase.complete` remains the
|
|
100
|
+
authoritative stale-aware gate and fail-closes unless canonical verification
|
|
101
|
+
status is `passed`.
|
|
100
102
|
|
|
101
103
|
**If all plans complete:**
|
|
102
104
|
|
|
@@ -143,13 +143,9 @@ Full review: {path to UI-REVIEW.md}
|
|
|
143
143
|
|
|
144
144
|
## ▶ Next
|
|
145
145
|
|
|
146
|
-
`/clear` then
|
|
146
|
+
`/clear` then:
|
|
147
147
|
|
|
148
|
-
- `/gsd:verify-work {N}` — UAT testing
|
|
149
|
-
- `/gsd:plan-phase {N+1}` — plan next phase
|
|
150
|
-
|
|
151
|
-
- `/gsd:verify-work {N}` — UAT testing
|
|
152
|
-
- `/gsd:plan-phase {N+1}` — plan next phase
|
|
148
|
+
- `/gsd:verify-work {N}` — UAT testing before phase completion
|
|
153
149
|
|
|
154
150
|
───────────────────────────────────────────────────────────────
|
|
155
151
|
```
|
|
@@ -178,7 +178,24 @@ fi
|
|
|
178
178
|
|
|
179
179
|
The verb owns the canonical regex `/^As a .+, I want to .+, so that .+\.$/` and returns slot extractions plus per-error guidance when invalid. Halt UAT generation on failure — never attempt to derive user-flow steps from a non-User-Story goal (low-quality UAT).
|
|
180
180
|
|
|
181
|
-
**
|
|
181
|
+
**Coverage-aware deterministic classification (#1602).** Before deriving checkpoints from prose, classify each SUMMARY's structured `coverage:` block. For each `*-SUMMARY.md`:
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
COVERAGE=$(gsd_run query uat.classify-coverage --summary "$SUMMARY_FILE")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Read the JSON result (`mode`, `total`, `all_auto_covered`, `auto_passed[]`, `present[]`, `errors[]`):
|
|
188
|
+
|
|
189
|
+
- **`mode: legacy`** (no `coverage:` block, OR a malformed block that could not be parsed) → **fall through** to the prose-based extraction below. Behavior is byte-identical to pre-#1602 for un-migrated SUMMARYs; do NOT auto-pass anything. If `errors[]` is non-empty (a `malformed_block`), note the broken coverage block to the user before proceeding so the SUMMARY can be fixed.
|
|
190
|
+
- **`mode: coverage`** →
|
|
191
|
+
- Each `auto_passed[]` entry is recorded in UAT.md as `result: pass`, `source: automated` (see `create_uat_file`) — **do not present it as a checkpoint.** It is deterministically covered by the passing tests in its `verification` refs.
|
|
192
|
+
- Each `present[]` entry becomes a human UAT checkpoint: use its `description` as the test and carry its `rationale` into the checkpoint context. The `reason` (`human_judgment` / `no_verification` / `verification_not_passing` / `validation_failed`) explains why a human is needed.
|
|
193
|
+
- If `all_auto_covered` is `true` (every entry auto-passed, including the `coverage: []` case) → do NOT generate zero checkpoints; present a **single confirmation summary** listing the auto-covered deliverables with their covering tests and ask the user to confirm.
|
|
194
|
+
- Surface any `errors[]` to the user (malformed coverage block) but still treat their entries as human checkpoints — **never drop a deliverable** (fail-safe).
|
|
195
|
+
|
|
196
|
+
The cold-start smoke test injection below still applies in `coverage` mode.
|
|
197
|
+
|
|
198
|
+
**Extract testable deliverables from SUMMARY.md (legacy fallback — used when `mode: legacy`):**
|
|
182
199
|
|
|
183
200
|
Parse for:
|
|
184
201
|
1. **Accomplishments** - Features/functionality added
|
|
@@ -252,6 +269,18 @@ result: [pending]
|
|
|
252
269
|
|
|
253
270
|
...
|
|
254
271
|
|
|
272
|
+
**Coverage auto-passed entries (#1602):** for each `auto_passed[]` entry from `uat classify-coverage`, write a Tests entry pre-resolved as automated — these are NOT presented to the user:
|
|
273
|
+
|
|
274
|
+
```
|
|
275
|
+
### N. [coverage description]
|
|
276
|
+
expected: [coverage description]
|
|
277
|
+
result: pass
|
|
278
|
+
source: automated
|
|
279
|
+
coverage_id: [D-id]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The `source: automated` marker is additive — existing consumers that read only `result:` are unaffected.
|
|
283
|
+
|
|
255
284
|
## Summary
|
|
256
285
|
|
|
257
286
|
total: [N]
|
|
@@ -498,6 +527,50 @@ If an active secure-phase step hook exists AND `SECURITY_FILE` exists: check fro
|
|
|
498
527
|
|
|
499
528
|
If no active secure-phase step hook exists OR (`SECURITY_FILE` exists AND `threats_open` is `0`):
|
|
500
529
|
|
|
530
|
+
If execution verification is waiting only on human UAT and this session recorded zero issues, canonicalize the report before the shared completion predicate:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
PHASE_DIR=$(printf '%s' "$INIT" | jq -r '.phase_dir // empty')
|
|
534
|
+
VERIFICATION_FILE=$(ls "${PHASE_DIR}"/*-VERIFICATION.md 2>/dev/null | head -1)
|
|
535
|
+
VERIFICATION_STATUS=$(gsd_run query verification.status "$PHASE_DIR" 2>/dev/null)
|
|
536
|
+
VERIFICATION_STATUS_VALUE=$(printf '%s' "$VERIFICATION_STATUS" | jq -r '.status // empty' 2>/dev/null || echo "")
|
|
537
|
+
PHASE_VERIFICATION_STATUS="$VERIFICATION_STATUS_VALUE"
|
|
538
|
+
if [ "$VERIFICATION_STATUS_VALUE" = "human_needed" ]; then
|
|
539
|
+
gsd_run query frontmatter.set "$VERIFICATION_FILE" --field status --value passed
|
|
540
|
+
fi
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
If `PHASE_VERIFICATION_STATUS` is `stale`, stop before phase advancement and present:
|
|
544
|
+
|
|
545
|
+
```
|
|
546
|
+
All UAT tests passed, but phase advancement is blocked until canonical verification is fresh.
|
|
547
|
+
|
|
548
|
+
Blocking completion:
|
|
549
|
+
verification is stale
|
|
550
|
+
|
|
551
|
+
- `/gsd:verify-work {phase}` — re-run verification against the latest summaries
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
Otherwise, check the shared UAT-plus-verification completion predicate before transition:
|
|
555
|
+
|
|
556
|
+
```bash
|
|
557
|
+
PHASE_COMPLETE=$(gsd_run phase uat-passed "{phase}" --require-verification)
|
|
558
|
+
PHASE_COMPLETE_PASSED=$(printf '%s' "$PHASE_COMPLETE" | jq -r '.passed' 2>/dev/null || echo "false")
|
|
559
|
+
PHASE_COMPLETE_BLOCKERS=$(printf '%s' "$PHASE_COMPLETE" | jq -r '.blockers[]?' 2>/dev/null || true)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
If `PHASE_COMPLETE_PASSED` is not `true`, stop before phase advancement and present:
|
|
563
|
+
|
|
564
|
+
```
|
|
565
|
+
All UAT tests passed, but phase advancement is blocked until canonical verification passes.
|
|
566
|
+
|
|
567
|
+
Blocking completion:
|
|
568
|
+
{PHASE_COMPLETE_BLOCKERS}
|
|
569
|
+
|
|
570
|
+
- `/gsd:execute-phase {phase}` — regenerate execution verification
|
|
571
|
+
- `/gsd:verify-work {phase}` — resume UAT if blockers remain
|
|
572
|
+
```
|
|
573
|
+
|
|
501
574
|
**Auto-transition: mark phase complete in ROADMAP.md and STATE.md**
|
|
502
575
|
|
|
503
576
|
Execute the transition workflow inline (do NOT use Task — the orchestrator context already holds the UAT results and phase data needed for accurate transition):
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// gsd-hook-version: {{GSD_VERSION}}
|
|
3
3
|
// GSD Read Injection Scanner — PostToolUse hook (#2201)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Pattern-based pre-filter / blocklist: scans content returned by Read, WebFetch,
|
|
5
|
+
// and WebSearch for known prompt-injection patterns (regex + heuristic rules).
|
|
6
|
+
// This is a static pattern match — NOT a semantic guard, NOT PromptArmor.
|
|
7
|
+
// It does NOT understand context, intent, or novel phrasing; it catches
|
|
8
|
+
// known injection signatures at ingestion before they enter conversation context.
|
|
6
9
|
//
|
|
7
10
|
// Defense-in-depth: long GSD sessions hit context compression, and the
|
|
8
11
|
// summariser does not distinguish user instructions from content read from
|
|
9
12
|
// external files. Poisoned instructions that survive compression become
|
|
10
13
|
// indistinguishable from trusted context. This hook warns at ingestion time.
|
|
14
|
+
// Prompt-level self-guard and task-anchor controls (untrusted-input-boundary.md)
|
|
15
|
+
// operate independently as a complementary layer.
|
|
11
16
|
//
|
|
12
|
-
// Triggers on: Read
|
|
13
|
-
// Action: Advisory warning
|
|
17
|
+
// Triggers on: Read, WebFetch, WebSearch PostToolUse events
|
|
18
|
+
// Action: Advisory warning by default; blocks HIGH only when security.injection_blocking=true
|
|
14
19
|
// Severity: LOW (1–2 patterns), HIGH (3+ patterns)
|
|
15
20
|
//
|
|
16
21
|
// False-positive exclusion: .planning/, REVIEW.md, CHECKPOINT, security docs,
|
|
17
22
|
// hook source files — these legitimately contain injection-like strings.
|
|
18
23
|
|
|
19
24
|
const path = require('path');
|
|
25
|
+
const fs = require('fs');
|
|
20
26
|
|
|
21
27
|
// Summarisation-specific patterns (novel — not in gsd-prompt-guard.js).
|
|
22
28
|
// These target instructions specifically designed to survive context compression.
|
|
@@ -108,20 +114,25 @@ process.stdin.on('end', () => {
|
|
|
108
114
|
try {
|
|
109
115
|
const data = JSON.parse(inputBuf);
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
const toolName = data.tool_name;
|
|
118
|
+
const SCANNED_TOOLS = new Set(['Read', 'WebFetch', 'WebSearch']);
|
|
119
|
+
if (!SCANNED_TOOLS.has(toolName)) {
|
|
112
120
|
process.exit(0);
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
// Source label + path-exclusion (path-exclusion applies to file reads only)
|
|
124
|
+
let source;
|
|
125
|
+
if (toolName === 'Read') {
|
|
126
|
+
source = data.tool_input?.file_path || '';
|
|
127
|
+
if (!source) process.exit(0);
|
|
128
|
+
if (isExcludedPath(source)) process.exit(0);
|
|
129
|
+
} else if (toolName === 'WebFetch') {
|
|
130
|
+
source = data.tool_input?.url || 'web';
|
|
131
|
+
} else { // WebSearch
|
|
132
|
+
source = `search: ${data.tool_input?.query || ''}`;
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
process.exit(0);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Extract content from tool_response — string (cat -n output) or object form
|
|
135
|
+
// Extract content from tool_response — string, {content}, or arbitrary object
|
|
125
136
|
let content = '';
|
|
126
137
|
const resp = data.tool_response;
|
|
127
138
|
if (typeof resp === 'string') {
|
|
@@ -132,6 +143,9 @@ process.stdin.on('end', () => {
|
|
|
132
143
|
content = c.map(b => (typeof b === 'string' ? b : b.text || '')).join('\n');
|
|
133
144
|
} else if (c != null) {
|
|
134
145
|
content = String(c);
|
|
146
|
+
} else {
|
|
147
|
+
// WebSearch results etc. — scan the serialized response
|
|
148
|
+
try { content = JSON.stringify(resp); } catch { content = ''; }
|
|
135
149
|
}
|
|
136
150
|
}
|
|
137
151
|
|
|
@@ -179,21 +193,31 @@ process.stdin.on('end', () => {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
const severity = findings.length >= 3 ? 'HIGH' : 'LOW';
|
|
182
|
-
const
|
|
196
|
+
const label = toolName === 'Read' ? path.basename(source) : source;
|
|
183
197
|
const detail = severity === 'HIGH'
|
|
184
|
-
? 'Multiple patterns — strong injection signal. Review
|
|
198
|
+
? 'Multiple patterns — strong injection signal. Review for embedded instructions before proceeding.'
|
|
185
199
|
: 'Single pattern match may be a false positive (e.g., documentation). Proceed with awareness.';
|
|
200
|
+
const advisory =
|
|
201
|
+
`\u26a0\ufe0f INJECTION SCAN [${severity}] (${toolName}): "${label}" triggered ` +
|
|
202
|
+
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
203
|
+
`This content is now in your conversation context. ${detail} Source: ${source}`;
|
|
204
|
+
|
|
205
|
+
// Opt-in blocking: only when configured AND high-confidence
|
|
206
|
+
let blocking = false;
|
|
207
|
+
if (severity === 'HIGH') {
|
|
208
|
+
try {
|
|
209
|
+
const cfgBase = data.cwd || process.cwd();
|
|
210
|
+
const cfgPath = path.join(cfgBase, '.planning', 'config.json');
|
|
211
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
212
|
+
blocking = cfg.security?.injection_blocking === true;
|
|
213
|
+
} catch { /* no config ⇒ advisory */ }
|
|
214
|
+
}
|
|
186
215
|
|
|
187
|
-
const output =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
193
|
-
`This content is now in your conversation context. ${detail} ` +
|
|
194
|
-
`Source: ${filePath}`,
|
|
195
|
-
},
|
|
196
|
-
};
|
|
216
|
+
const output = blocking
|
|
217
|
+
? { decision: 'block',
|
|
218
|
+
reason: `Prompt-injection blocked (${toolName}). ${advisory}`,
|
|
219
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } }
|
|
220
|
+
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } };
|
|
197
221
|
|
|
198
222
|
process.stdout.write(JSON.stringify(output));
|
|
199
223
|
} catch {
|
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// gsd-hook-version: {{GSD_VERSION}}
|
|
3
3
|
// GSD Read Injection Scanner — PostToolUse hook (#2201)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
4
|
+
// Pattern-based pre-filter / blocklist: scans content returned by Read, WebFetch,
|
|
5
|
+
// and WebSearch for known prompt-injection patterns (regex + heuristic rules).
|
|
6
|
+
// This is a static pattern match — NOT a semantic guard, NOT PromptArmor.
|
|
7
|
+
// It does NOT understand context, intent, or novel phrasing; it catches
|
|
8
|
+
// known injection signatures at ingestion before they enter conversation context.
|
|
6
9
|
//
|
|
7
10
|
// Defense-in-depth: long GSD sessions hit context compression, and the
|
|
8
11
|
// summariser does not distinguish user instructions from content read from
|
|
9
12
|
// external files. Poisoned instructions that survive compression become
|
|
10
13
|
// indistinguishable from trusted context. This hook warns at ingestion time.
|
|
14
|
+
// Prompt-level self-guard and task-anchor controls (untrusted-input-boundary.md)
|
|
15
|
+
// operate independently as a complementary layer.
|
|
11
16
|
//
|
|
12
|
-
// Triggers on: Read
|
|
13
|
-
// Action: Advisory warning
|
|
17
|
+
// Triggers on: Read, WebFetch, WebSearch PostToolUse events
|
|
18
|
+
// Action: Advisory warning by default; blocks HIGH only when security.injection_blocking=true
|
|
14
19
|
// Severity: LOW (1–2 patterns), HIGH (3+ patterns)
|
|
15
20
|
//
|
|
16
21
|
// False-positive exclusion: .planning/, REVIEW.md, CHECKPOINT, security docs,
|
|
17
22
|
// hook source files — these legitimately contain injection-like strings.
|
|
18
23
|
|
|
19
24
|
const path = require('path');
|
|
25
|
+
const fs = require('fs');
|
|
20
26
|
|
|
21
27
|
// Summarisation-specific patterns (novel — not in gsd-prompt-guard.js).
|
|
22
28
|
// These target instructions specifically designed to survive context compression.
|
|
@@ -108,20 +114,25 @@ process.stdin.on('end', () => {
|
|
|
108
114
|
try {
|
|
109
115
|
const data = JSON.parse(inputBuf);
|
|
110
116
|
|
|
111
|
-
|
|
117
|
+
const toolName = data.tool_name;
|
|
118
|
+
const SCANNED_TOOLS = new Set(['Read', 'WebFetch', 'WebSearch']);
|
|
119
|
+
if (!SCANNED_TOOLS.has(toolName)) {
|
|
112
120
|
process.exit(0);
|
|
113
121
|
}
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
// Source label + path-exclusion (path-exclusion applies to file reads only)
|
|
124
|
+
let source;
|
|
125
|
+
if (toolName === 'Read') {
|
|
126
|
+
source = data.tool_input?.file_path || '';
|
|
127
|
+
if (!source) process.exit(0);
|
|
128
|
+
if (isExcludedPath(source)) process.exit(0);
|
|
129
|
+
} else if (toolName === 'WebFetch') {
|
|
130
|
+
source = data.tool_input?.url || 'web';
|
|
131
|
+
} else { // WebSearch
|
|
132
|
+
source = `search: ${data.tool_input?.query || ''}`;
|
|
118
133
|
}
|
|
119
134
|
|
|
120
|
-
|
|
121
|
-
process.exit(0);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Extract content from tool_response — string (cat -n output) or object form
|
|
135
|
+
// Extract content from tool_response — string, {content}, or arbitrary object
|
|
125
136
|
let content = '';
|
|
126
137
|
const resp = data.tool_response;
|
|
127
138
|
if (typeof resp === 'string') {
|
|
@@ -132,6 +143,9 @@ process.stdin.on('end', () => {
|
|
|
132
143
|
content = c.map(b => (typeof b === 'string' ? b : b.text || '')).join('\n');
|
|
133
144
|
} else if (c != null) {
|
|
134
145
|
content = String(c);
|
|
146
|
+
} else {
|
|
147
|
+
// WebSearch results etc. — scan the serialized response
|
|
148
|
+
try { content = JSON.stringify(resp); } catch { content = ''; }
|
|
135
149
|
}
|
|
136
150
|
}
|
|
137
151
|
|
|
@@ -179,21 +193,31 @@ process.stdin.on('end', () => {
|
|
|
179
193
|
}
|
|
180
194
|
|
|
181
195
|
const severity = findings.length >= 3 ? 'HIGH' : 'LOW';
|
|
182
|
-
const
|
|
196
|
+
const label = toolName === 'Read' ? path.basename(source) : source;
|
|
183
197
|
const detail = severity === 'HIGH'
|
|
184
|
-
? 'Multiple patterns — strong injection signal. Review
|
|
198
|
+
? 'Multiple patterns — strong injection signal. Review for embedded instructions before proceeding.'
|
|
185
199
|
: 'Single pattern match may be a false positive (e.g., documentation). Proceed with awareness.';
|
|
200
|
+
const advisory =
|
|
201
|
+
`\u26a0\ufe0f INJECTION SCAN [${severity}] (${toolName}): "${label}" triggered ` +
|
|
202
|
+
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
203
|
+
`This content is now in your conversation context. ${detail} Source: ${source}`;
|
|
204
|
+
|
|
205
|
+
// Opt-in blocking: only when configured AND high-confidence
|
|
206
|
+
let blocking = false;
|
|
207
|
+
if (severity === 'HIGH') {
|
|
208
|
+
try {
|
|
209
|
+
const cfgBase = data.cwd || process.cwd();
|
|
210
|
+
const cfgPath = path.join(cfgBase, '.planning', 'config.json');
|
|
211
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
212
|
+
blocking = cfg.security?.injection_blocking === true;
|
|
213
|
+
} catch { /* no config ⇒ advisory */ }
|
|
214
|
+
}
|
|
186
215
|
|
|
187
|
-
const output =
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
`${findings.length} pattern(s): ${findings.join(', ')}. ` +
|
|
193
|
-
`This content is now in your conversation context. ${detail} ` +
|
|
194
|
-
`Source: ${filePath}`,
|
|
195
|
-
},
|
|
196
|
-
};
|
|
216
|
+
const output = blocking
|
|
217
|
+
? { decision: 'block',
|
|
218
|
+
reason: `Prompt-injection blocked (${toolName}). ${advisory}`,
|
|
219
|
+
hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } }
|
|
220
|
+
: { hookSpecificOutput: { hookEventName: 'PostToolUse', additionalContext: advisory } };
|
|
197
221
|
|
|
198
222
|
process.stdout.write(JSON.stringify(output));
|
|
199
223
|
} catch {
|
package/hooks/hooks.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/gsd-core",
|
|
3
|
-
"version": "1.6.0
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gsd-core": "bin/install.js",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"bin",
|
|
12
12
|
"commands",
|
|
13
|
+
"skills",
|
|
13
14
|
"gsd-core",
|
|
14
15
|
"assets",
|
|
15
16
|
"agents",
|
|
@@ -78,11 +79,12 @@
|
|
|
78
79
|
"check:alias-drift": "node scripts/check-alias-drift.cjs",
|
|
79
80
|
"check:identity-drift": "node scripts/lint-package-identity-drift.cjs",
|
|
80
81
|
"check:integrity": "node scripts/check-npm-integrity.cjs",
|
|
81
|
-
"build": "npm run generate:identity && npm run build:lib && npm run gen:loop-host-contract && npm run gen:capability-registry && npm run build:hooks",
|
|
82
|
+
"build": "npm run generate:identity && npm run build:lib && npm run gen:plugin-skills && npm run gen:loop-host-contract && npm run gen:capability-registry && npm run build:hooks",
|
|
82
83
|
"build:hooks": "node scripts/build-hooks.js",
|
|
83
84
|
"build:lib": "tsc -p tsconfig.build.json",
|
|
84
85
|
"generate:identity": "node scripts/generate-package-identity.cjs",
|
|
85
86
|
"gen:loop-host-contract": "node scripts/gen-loop-host-contract.cjs --write",
|
|
87
|
+
"gen:plugin-skills": "node scripts/gen-plugin-skills.cjs --write",
|
|
86
88
|
"gen:capability-registry": "node scripts/gen-capability-registry.cjs --write",
|
|
87
89
|
"prepack": "npm run build:lib",
|
|
88
90
|
"prepare": "npm run build:lib",
|
|
@@ -72,6 +72,11 @@ function main() {
|
|
|
72
72
|
subcommands: 'ROADMAP_SUBCOMMANDS',
|
|
73
73
|
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'roadmap-command-router.cjs'),
|
|
74
74
|
},
|
|
75
|
+
{
|
|
76
|
+
commandAliases: 'EVAL_COMMAND_ALIASES',
|
|
77
|
+
subcommands: 'EVAL_SUBCOMMANDS',
|
|
78
|
+
routerPath: path.join(ROOT, 'gsd-core', 'bin', 'lib', 'eval-command-router.cjs'),
|
|
79
|
+
},
|
|
75
80
|
];
|
|
76
81
|
|
|
77
82
|
for (const family of families) {
|