@opengsd/gsd-core 1.6.0-rc.2 → 1.6.0-rc.3
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-planner.md +8 -57
- package/agents/gsd-security-auditor.md +37 -18
- package/bin/install.js +151 -13
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +35 -3
- 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-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/graphify-command-router.cjs +53 -36
- package/gsd-core/bin/lib/init.cjs +8 -4
- 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/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/state.cjs +364 -52
- package/gsd-core/bin/lib/surface.cjs +42 -10
- package/gsd-core/bin/lib/update-context.cjs +2 -2
- 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/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/execute-plan.md +5 -0
- package/gsd-core/workflows/new-project.md +4 -4
- package/gsd-core/workflows/plan-phase.md +15 -0
- package/gsd-core/workflows/secure-phase.md +13 -7
- package/gsd-core/workflows/verify-work.md +30 -1
- package/package.json +4 -2
- package/scripts/gen-plugin-skills.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +2 -1
- package/scripts/prompt-injection-scan.sh +4 -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
|
@@ -270,17 +270,49 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry)
|
|
|
270
270
|
// module's deep seam (ADR-1508 / #1511 Phase 2) — no attribution resolver
|
|
271
271
|
// needed here (proven: Co-Authored-By never appears in staged content; see
|
|
272
272
|
// brief PROVEN KEY FACT). No getInstallExports() call required.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
273
|
+
// #1615 adversarial review (PR #1622): commands kind was previously skipped,
|
|
274
|
+
// leaving raw @~/.claude/... references in Windsurf workflow bodies after a
|
|
275
|
+
// /gsd-surface profile change. Same gap affected any runtime with commands
|
|
276
|
+
// kinds (windsurf, opencode, kilo, cursor, augment, codebuddy, gemini).
|
|
277
|
+
//
|
|
278
|
+
// Asymmetry note: rewriteStagedSkillBodies mutates in place (returns void),
|
|
279
|
+
// but rewriteStagedCommandBodies copies to a fresh mkdtemp dir and returns
|
|
280
|
+
// its path (commands .md files are flat; mutating the staged source would
|
|
281
|
+
// corrupt the package source on full-profile runs). Caller MUST sync from
|
|
282
|
+
// the returned dir and clean it up.
|
|
283
|
+
const tempDirsToClean = [];
|
|
284
|
+
try {
|
|
285
|
+
for (const kind of layout.kinds) {
|
|
286
|
+
let staged = kind.stage(resolved);
|
|
287
|
+
if (kind.kind === 'skills') {
|
|
288
|
+
runtimeArtifactConversion.rewriteStagedSkillBodies(staged, {
|
|
289
|
+
runtime: layout.runtime,
|
|
290
|
+
configDir: layout.configDir,
|
|
291
|
+
scope: layout.scope ?? 'global',
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
else if (kind.kind === 'commands') {
|
|
295
|
+
const rewritten = runtimeArtifactConversion.rewriteStagedCommandBodies(staged, {
|
|
296
|
+
runtime: layout.runtime,
|
|
297
|
+
configDir: layout.configDir,
|
|
298
|
+
scope: layout.scope ?? 'global',
|
|
299
|
+
});
|
|
300
|
+
if (rewritten && rewritten !== staged) {
|
|
301
|
+
staged = rewritten;
|
|
302
|
+
tempDirsToClean.push(rewritten);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
const dest = node_path_1.default.join(layout.configDir, kind.destSubpath);
|
|
306
|
+
_syncGsdDir(staged, dest, kind, skillManifest);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
finally {
|
|
310
|
+
for (const dir of tempDirsToClean) {
|
|
311
|
+
try {
|
|
312
|
+
node_fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
catch { /* best-effort cleanup */ }
|
|
281
315
|
}
|
|
282
|
-
const dest = node_path_1.default.join(layout.configDir, kind.destSubpath);
|
|
283
|
-
_syncGsdDir(staged, dest, kind, skillManifest);
|
|
284
316
|
}
|
|
285
317
|
return resolved;
|
|
286
318
|
}
|
|
@@ -31,8 +31,8 @@ exports.RUNTIME_DIRS = [
|
|
|
31
31
|
['antigravity', '.gemini/antigravity'],
|
|
32
32
|
['antigravity', '.agents'], // local Antigravity install dir canonical (#791; bin/install.js getDirName('antigravity'))
|
|
33
33
|
['antigravity', '.agent'], // local Antigravity install dir legacy (#503; backward-compat with pre-#791 installs)
|
|
34
|
-
['windsurf', '.
|
|
35
|
-
['windsurf', '.
|
|
34
|
+
['windsurf', '.windsurf'], // local Windsurf workflow dir canonical (#1615; bin/install.js getDirName('windsurf'))
|
|
35
|
+
['windsurf', '.devin'], // local Devin Desktop install dir legacy (#1085; backward-compat)
|
|
36
36
|
['gemini', '.gemini'],
|
|
37
37
|
['kilo', '.config/kilo'],
|
|
38
38
|
['kilo', '.kilo'],
|
|
@@ -184,3 +184,69 @@ Execute: `/gsd:execute-phase {phase} --gaps-only`
|
|
|
184
184
|
## Checkpoint Reached / Revision Complete
|
|
185
185
|
|
|
186
186
|
Follow templates in checkpoints and revision_mode sections respectively.
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Goal-Backward Worked Example
|
|
191
|
+
|
|
192
|
+
### Step 2: Derive Observable Truths
|
|
193
|
+
|
|
194
|
+
For "working chat interface":
|
|
195
|
+
- User can see existing messages
|
|
196
|
+
- User can type a new message
|
|
197
|
+
- User can send the message
|
|
198
|
+
- Sent message appears in the list
|
|
199
|
+
- Messages persist across page refresh
|
|
200
|
+
|
|
201
|
+
**Test:** Each truth verifiable by a human using the application.
|
|
202
|
+
|
|
203
|
+
### Step 3: Derive Required Artifacts
|
|
204
|
+
|
|
205
|
+
"User can see existing messages" requires:
|
|
206
|
+
- Message list component (renders Message[])
|
|
207
|
+
- Messages state (loaded from somewhere)
|
|
208
|
+
- API route or data source (provides messages)
|
|
209
|
+
- Message type definition (shapes the data)
|
|
210
|
+
|
|
211
|
+
**Test:** Each artifact = a specific file or database object.
|
|
212
|
+
|
|
213
|
+
### Step 4: Derive Required Wiring
|
|
214
|
+
|
|
215
|
+
Message list component wiring:
|
|
216
|
+
- Imports Message type (not using `any`)
|
|
217
|
+
- Receives messages prop or fetches from API
|
|
218
|
+
- Maps over messages to render (not hardcoded)
|
|
219
|
+
- Handles empty state (not just crashes)
|
|
220
|
+
|
|
221
|
+
### Step 5: Identify Key Links
|
|
222
|
+
|
|
223
|
+
"Where is this most likely to break?" Key links = critical connections where breakage causes cascading failures.
|
|
224
|
+
|
|
225
|
+
### Must-Haves Output Format
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
must_haves:
|
|
229
|
+
truths:
|
|
230
|
+
- "User can see existing messages"
|
|
231
|
+
- "User can send a message"
|
|
232
|
+
- "Messages persist across refresh"
|
|
233
|
+
artifacts:
|
|
234
|
+
- path: "src/components/Chat.tsx"
|
|
235
|
+
provides: "Message list rendering"
|
|
236
|
+
min_lines: 30
|
|
237
|
+
- path: "src/app/api/chat/route.ts"
|
|
238
|
+
provides: "Message CRUD operations"
|
|
239
|
+
exports: ["GET", "POST"]
|
|
240
|
+
- path: "prisma/schema.prisma"
|
|
241
|
+
provides: "Message model"
|
|
242
|
+
contains: "model Message"
|
|
243
|
+
key_links:
|
|
244
|
+
- from: "src/components/Chat.tsx"
|
|
245
|
+
to: "src/app/api/chat/route.ts"
|
|
246
|
+
via: "fetch in useEffect — calls /api/chat endpoint"
|
|
247
|
+
pattern: "fetch.*api/chat"
|
|
248
|
+
- from: "src/app/api/chat/route.ts"
|
|
249
|
+
to: "prisma/schema.prisma"
|
|
250
|
+
via: "database query via prisma.message"
|
|
251
|
+
pattern: "prisma\\.message\\.(find|create)"
|
|
252
|
+
```
|
|
@@ -275,8 +275,8 @@ Set via `workflow.*` namespace in config.json (e.g., `"workflow": { "research":
|
|
|
275
275
|
| `workflow.code_review_depth` | string | `"standard"` | `"light"`, `"standard"`, `"deep"` | Depth level for code review analysis in the ship workflow |
|
|
276
276
|
| `workflow._auto_chain_active` | boolean | `false` | `true`, `false` | Internal: tracks whether autonomous chaining is active |
|
|
277
277
|
| `workflow.security_enforcement` | boolean | `true` | `true`, `false` | Enable threat-model-anchored security verification via `/gsd:secure-phase`. When `false`, security checks are skipped entirely |
|
|
278
|
-
| `workflow.security_asvs_level` | number | `1` | `1`, `2`, `3` | OWASP ASVS verification level. Level 1 = opportunistic, Level 2 = standard, Level 3 = comprehensive |
|
|
279
|
-
| `workflow.security_block_on` | string | `"high"` | `"high"`, `"medium"`, `"low"` | Minimum severity that blocks phase advancement |
|
|
278
|
+
| `workflow.security_asvs_level` | number | `1` | `1`, `2`, `3` | OWASP ASVS verification level. Level 1 = opportunistic, Level 2 = standard, Level 3 = comprehensive. Scales both planner threat-disposition rigor (which threats must be mitigated vs. accepted) and auditor verification depth (grep-level → boundary-placement check → full data-flow trace). See `gsd-core/references/security-asvs-levels.md`. |
|
|
279
|
+
| `workflow.security_block_on` | string | `"high"` | `"critical"`, `"high"`, `"medium"`, `"low"`, `"none"` | Minimum threat severity that blocks phase advancement. The auditor counts only open threats at or above this severity toward the blocking gate (SECURITY.md `threats_open`); `none` disables severity blocking. |
|
|
280
280
|
| `workflow.post_planning_gaps` | boolean | `true` | `true`, `false` | Post-planning gap report (#2493). After plans are generated, scans REQUIREMENTS.md and CONTEXT.md `<decisions>` against all PLAN.md files and emits a unified `Source \| Item \| Status` table. Non-blocking. Set to `false` to skip Step 13e of plan-phase. _Alias:_ `post_planning_gaps` is the flat-key form used in `CONFIG_DEFAULTS`; `workflow.post_planning_gaps` is the canonical namespaced form. |
|
|
281
281
|
|
|
282
282
|
### Ship Fields
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Security ASVS Levels
|
|
2
|
+
|
|
3
|
+
GSD threat modeling maps OWASP ASVS levels to planner disposition rigor and auditor verification depth. Higher levels are supersets of lower — L3 includes all L2 and L1 requirements.
|
|
4
|
+
|
|
5
|
+
## L1 — Opportunistic (default)
|
|
6
|
+
|
|
7
|
+
**Scope:** Cover threats on primary trust boundaries and high-impact components.
|
|
8
|
+
|
|
9
|
+
**Planner disposition:** `mitigate` critical/high-severity threats. `mitigate` medium-severity threats if they occur on a primary trust boundary; otherwise `accept` with documented rationale explaining the specific risk tolerance. `accept` low-risk threats with a rationale statement. `transfer` when threat is third-party responsibility.
|
|
10
|
+
|
|
11
|
+
**Auditor verification depth:** Verify each declared mitigation is PRESENT in the cited file (grep-level check — find the pattern, confirm the call exists).
|
|
12
|
+
|
|
13
|
+
## L2 — Standard
|
|
14
|
+
|
|
15
|
+
**Scope:** Map ALL applicable STRIDE categories for every in-scope component.
|
|
16
|
+
|
|
17
|
+
**Planner disposition:** `mitigate` medium-severity-and-above threats. Every `accept` MUST have explicit documented rationale explaining why the risk is tolerable for this specific context.
|
|
18
|
+
|
|
19
|
+
**Auditor verification depth:** Verify the mitigation ACTUALLY ADDRESSES the threat vector (not just that some pattern is present) and is placed at the correct trust boundary. A login check in the wrong layer does not close the threat.
|
|
20
|
+
|
|
21
|
+
## L3 — Comprehensive
|
|
22
|
+
|
|
23
|
+
**Scope:** Exhaustive STRIDE × all components; defense-in-depth for critical threats.
|
|
24
|
+
|
|
25
|
+
**Planner disposition:** `mitigate` all threats except those explicitly accepted with documented sign-off. Defense-in-depth layers required for critical threats (multiple independent controls).
|
|
26
|
+
|
|
27
|
+
**Auditor verification depth:** Deep verification — trace data flow end-to-end, check edge cases and ordering, confirm the mitigation cannot be bypassed via alternate code paths or parameter manipulation.
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
phase: {N}
|
|
3
3
|
slug: {phase-slug}
|
|
4
4
|
status: draft
|
|
5
|
+
# threats_open = count of OPEN threats at or above workflow.security_block_on severity (the blocking gate)
|
|
5
6
|
threats_open: 0
|
|
6
7
|
asvs_level: 1
|
|
7
8
|
created: {date}
|
|
@@ -23,11 +24,12 @@ created: {date}
|
|
|
23
24
|
|
|
24
25
|
## Threat Register
|
|
25
26
|
|
|
26
|
-
| Threat ID | Category | Component | Disposition | Mitigation | Status |
|
|
27
|
-
|
|
28
|
-
| T-{N}-01 | {STRIDE category} | {component} | {mitigate / accept / transfer} | {control or reference} | open |
|
|
27
|
+
| Threat ID | Category | Component | Severity | Disposition | Mitigation | Status |
|
|
28
|
+
|-----------|----------|-----------|----------|-------------|------------|--------|
|
|
29
|
+
| T-{N}-01 | {STRIDE category} | {component} | {critical / high / medium / low} | {mitigate / accept / transfer} | {control or reference} | open |
|
|
29
30
|
|
|
30
|
-
*Status: open · closed*
|
|
31
|
+
*Status: open · closed · open — below {block_on} threshold (non-blocking)*
|
|
32
|
+
*Severity: critical > high > medium > low — only open threats at or above workflow.security_block_on count toward threats_open*
|
|
31
33
|
*Disposition: mitigate (implementation required) · accept (documented risk) · transfer (third-party)*
|
|
32
34
|
|
|
33
35
|
---
|
|
@@ -19,6 +19,10 @@ key-decisions:
|
|
|
19
19
|
- "Decision 1"
|
|
20
20
|
patterns-established:
|
|
21
21
|
- "Pattern 1: description"
|
|
22
|
+
# coverage: (#1602) optional per-deliverable UAT-routing block — see templates/summary.md <coverage_guidance>.
|
|
23
|
+
# Add live `coverage:` entries (id/description/verification[]/human_judgment[/rationale]) to enable
|
|
24
|
+
# deterministic UAT routing in verify-work; OMIT for legacy prose-only SUMMARYs. When coverage is
|
|
25
|
+
# uncertain, default human_judgment: true with a rationale — never auto-skip the human.
|
|
22
26
|
duration: Xmin
|
|
23
27
|
completed: YYYY-MM-DD
|
|
24
28
|
status: complete
|
|
@@ -13,6 +13,9 @@ key-files:
|
|
|
13
13
|
created: [important files created]
|
|
14
14
|
modified: [important files modified]
|
|
15
15
|
key-decisions: []
|
|
16
|
+
# coverage: (#1602) optional per-deliverable UAT-routing block — see templates/summary.md <coverage_guidance>.
|
|
17
|
+
# Add live `coverage:` entries to enable deterministic UAT routing in verify-work; OMIT for legacy
|
|
18
|
+
# prose-only SUMMARYs. When coverage is uncertain, default human_judgment: true — never auto-skip the human.
|
|
16
19
|
duration: Xmin
|
|
17
20
|
completed: YYYY-MM-DD
|
|
18
21
|
status: complete
|
|
@@ -14,6 +14,10 @@ key-files:
|
|
|
14
14
|
modified: [important files modified]
|
|
15
15
|
key-decisions:
|
|
16
16
|
- "Decision 1"
|
|
17
|
+
# coverage: (#1602) optional per-deliverable UAT-routing block — see templates/summary.md <coverage_guidance>.
|
|
18
|
+
# Add live `coverage:` entries (id/description/verification[]/human_judgment[/rationale]) to enable
|
|
19
|
+
# deterministic UAT routing in verify-work; OMIT for legacy prose-only SUMMARYs. When coverage is
|
|
20
|
+
# uncertain, default human_judgment: true with a rationale — never auto-skip the human.
|
|
17
21
|
duration: Xmin
|
|
18
22
|
completed: YYYY-MM-DD
|
|
19
23
|
status: complete
|
|
@@ -40,6 +40,24 @@ patterns-established:
|
|
|
40
40
|
|
|
41
41
|
requirements-completed: [] # REQUIRED — Copy ALL requirement IDs from this plan's `requirements` frontmatter field.
|
|
42
42
|
|
|
43
|
+
# Coverage metadata (#1602) — one entry per shipped deliverable. Drives DETERMINISTIC UAT routing in verify-work.
|
|
44
|
+
# OMIT this whole block for legacy/prose-only SUMMARYs — verify-work then falls back to the ## Accomplishments bullets
|
|
45
|
+
# (byte-identical behavior for un-migrated phases). See <coverage_guidance> below for the contract.
|
|
46
|
+
coverage:
|
|
47
|
+
- id: D1
|
|
48
|
+
description: "[deliverable in human-readable form — what would have been a prose ## Accomplishments bullet]"
|
|
49
|
+
requirement: "[REQ-ID from this plan's `requirements`, or omit if none]"
|
|
50
|
+
verification:
|
|
51
|
+
- kind: unit # unit | integration | e2e | automated_ui | manual_procedural | other
|
|
52
|
+
ref: "[tests/path.test.ts#test name | playwright:shot.png | command invocation]"
|
|
53
|
+
status: pass # pass | fail | unknown — from the latest run
|
|
54
|
+
human_judgment: false # REQUIRED boolean. false => may auto-pass IF every verification status is `pass`.
|
|
55
|
+
- id: D2
|
|
56
|
+
description: "[a deliverable that needs a human to sign off]"
|
|
57
|
+
verification: []
|
|
58
|
+
human_judgment: true
|
|
59
|
+
rationale: "[REQUIRED when human_judgment: true — why automation is insufficient]"
|
|
60
|
+
|
|
43
61
|
# Metrics
|
|
44
62
|
duration: Xmin
|
|
45
63
|
completed: YYYY-MM-DD
|
|
@@ -148,6 +166,29 @@ None - no external service configuration required.
|
|
|
148
166
|
**Population:** Frontmatter is populated during summary creation in execute-plan.md. See `<step name="create_summary">` for field-by-field guidance.
|
|
149
167
|
</frontmatter_guidance>
|
|
150
168
|
|
|
169
|
+
<coverage_guidance>
|
|
170
|
+
**Purpose (#1602):** The `coverage:` block is a per-deliverable Requirements Traceability Matrix. It lets `verify-work`'s `extract_tests` step route deliverables DETERMINISTICALLY — auto-passing those proven by passing tests and reserving human UAT for genuine judgment — instead of re-deriving coverage from prose. Consumed via `gsd-tools uat classify-coverage --summary <SUMMARY>`.
|
|
171
|
+
|
|
172
|
+
**Field semantics:**
|
|
173
|
+
|
|
174
|
+
| Field | Purpose |
|
|
175
|
+
|---|---|
|
|
176
|
+
| `id` | Stable identifier (`D1`, `D2`…) for cross-referencing from UAT.md and audit reports. Must be unique within the SUMMARY. |
|
|
177
|
+
| `description` | The deliverable in human-readable form — what would have been a prose bullet. |
|
|
178
|
+
| `requirement` | Links back to a REQUIREMENTS.md REQ-ID (joins `requirements-completed`). Optional. |
|
|
179
|
+
| `verification[].kind` | Enum: `unit \| integration \| e2e \| automated_ui \| manual_procedural \| other`. |
|
|
180
|
+
| `verification[].ref` | Test path + descriptor (`file#test name`), Playwright screenshot ref, or command invocation. Required per entry. |
|
|
181
|
+
| `verification[].status` | `pass \| fail \| unknown` — populated from the latest test run. |
|
|
182
|
+
| `human_judgment` | Explicit boolean; REQUIRED. `true` always routes to a human. |
|
|
183
|
+
| `rationale` | REQUIRED when `human_judgment: true`. The audit trail for why automation is insufficient. |
|
|
184
|
+
|
|
185
|
+
**Deterministic contract (what the classifier does):**
|
|
186
|
+
- A deliverable auto-passes (no human prompt) **only** when `human_judgment: false` AND `verification` is non-empty AND every `verification[].status` is `pass`. This is the narrow, fully-proven case.
|
|
187
|
+
- **Everything else is presented to a human** — `human_judgment: true`, an empty `verification:`, any non-`pass`/`unknown` status, or any schema error. A false-negative is a redundant prompt (the status quo); a false-positive ships a bug UAT existed to catch.
|
|
188
|
+
- **Fail-safe default:** if you cannot determine coverage for a deliverable, you MUST set `human_judgment: true` with `rationale: "Coverage not determined at authoring time — verifier must classify"`. Never leave a deliverable's `human_judgment` empty, and never set it `false` just to skip the prompt — auto-pass additionally requires a passing `verification` entry, so the flag alone cannot skip the human.
|
|
189
|
+
- `coverage: []` means "no deliverables to classify" (the single-confirmation path). OMITTING the block entirely means "legacy" — `verify-work` falls back to prose `## Accomplishments` extraction unchanged.
|
|
190
|
+
</coverage_guidance>
|
|
191
|
+
|
|
151
192
|
<one_liner_rules>
|
|
152
193
|
The one-liner MUST be substantive:
|
|
153
194
|
|
|
@@ -377,6 +377,11 @@ Create `{phase}-{plan}-SUMMARY.md` at `.planning/phases/XX-name/`. Use `~/.claud
|
|
|
377
377
|
|
|
378
378
|
**Frontmatter:** phase, plan, subsystem, tags | requires/provides/affects | tech-stack.added/patterns | key-files.created/modified | key-decisions | requirements-completed (**MUST** copy `requirements` array from PLAN.md frontmatter verbatim) | duration ($DURATION), completed ($PLAN_END_TIME date).
|
|
379
379
|
|
|
380
|
+
**Coverage block (#1602):** Populate the `coverage:` frontmatter block — one entry per shipped deliverable (the structured form of each `## Accomplishments` bullet). For each deliverable, aggregate the task-level `<verify>` results and tests:
|
|
381
|
+
- A task whose `<verify>` command passed or whose matching test passed → a `verification` entry with `kind` + `ref` (`tests/path#name`, Playwright screenshot ref, or command) + `status: pass`, and `human_judgment: false`.
|
|
382
|
+
- A judgment-dependent deliverable (UX adequacy, external/multi-session behavior, anything no test asserts) → `human_judgment: true` with a `rationale`.
|
|
383
|
+
- **Every deliverable MUST be classified.** If you cannot determine coverage, default to `human_judgment: true` with `rationale: "Coverage not determined at authoring time — verifier must classify"`. Never set `human_judgment: false` without a non-empty all-`pass` `verification` — `verify-work` auto-passes (skips the human) ONLY on that proof, so an unproven `false` still routes to the human but loses the audit trail. Omit the whole block only for a genuinely prose-only SUMMARY (verify-work then uses the legacy `## Accomplishments` path). The block is validated downstream by `gsd-tools uat classify-coverage`.
|
|
384
|
+
|
|
380
385
|
Title: `# Phase [X] Plan [Y]: [Name] Summary`
|
|
381
386
|
|
|
382
387
|
One-liner SUBSTANTIVE: "JWT auth with refresh rotation using jose library" not "Authentication implemented"
|
|
@@ -109,9 +109,9 @@ elif [ -n "$OPENCODE_CONFIG_DIR" ] || [ -n "$OPENCODE_CONFIG" ]; then RUNTIME="o
|
|
|
109
109
|
else RUNTIME="claude"; fi
|
|
110
110
|
```
|
|
111
111
|
|
|
112
|
-
Set the instruction file variable:
|
|
112
|
+
Set the instruction file variable via the shared runtime-name policy adapter (`gsd-tools query project-instruction-file`, backed by `getProjectInstructionFile` in `runtime-name-policy.cjs` — the single source of truth shared with `profile-output.cjs`):
|
|
113
113
|
```bash
|
|
114
|
-
|
|
114
|
+
INSTRUCTION_FILE=$(gsd_run query project-instruction-file --runtime "$RUNTIME")
|
|
115
115
|
```
|
|
116
116
|
|
|
117
117
|
All subsequent references to the project instruction file use `$INSTRUCTION_FILE`.
|
|
@@ -1533,7 +1533,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
|
|
|
1533
1533
|
- `.planning/REQUIREMENTS.md`
|
|
1534
1534
|
- `.planning/ROADMAP.md`
|
|
1535
1535
|
- `.planning/STATE.md`
|
|
1536
|
-
- `$INSTRUCTION_FILE` (`AGENTS.md` for
|
|
1536
|
+
- `$INSTRUCTION_FILE` (runtime-derived via the shared `getProjectInstructionFile` policy: `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude)
|
|
1537
1537
|
|
|
1538
1538
|
</output>
|
|
1539
1539
|
|
|
@@ -1555,7 +1555,7 @@ PHASE1_HAS_UI=$(echo "$PHASE1_SECTION" | grep -qi "UI hint.*yes" && echo "true"
|
|
|
1555
1555
|
- [ ] ROADMAP.md created with phases, requirement mappings, success criteria
|
|
1556
1556
|
- [ ] STATE.md initialized
|
|
1557
1557
|
- [ ] REQUIREMENTS.md traceability updated
|
|
1558
|
-
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (AGENTS.md for
|
|
1558
|
+
- [ ] `$INSTRUCTION_FILE` generated with GSD workflow guidance (runtime-derived via the shared `getProjectInstructionFile` policy — `AGENTS.md` for codex/opencode/kilo/kimi, `.github/copilot-instructions.md` for copilot, `GEMINI.md` for gemini/antigravity, `.claude/CLAUDE.md` for claude; an existing hand-crafted file without GSD markers is left untouched unless `--force`)
|
|
1559
1559
|
- [ ] User knows next step is `/gsd:discuss-phase 1`
|
|
1560
1560
|
|
|
1561
1561
|
**Atomic commits:** Each phase commits its artifacts immediately. If context is lost, artifacts persist.
|
|
@@ -693,6 +693,21 @@ Also available:
|
|
|
693
693
|
|
|
694
694
|
**Exit the plan-phase workflow. Do not continue.**
|
|
695
695
|
|
|
696
|
+
## 5.65. Codebase Map Freshness Pre-Check (drift plan:pre gate)
|
|
697
|
+
|
|
698
|
+
If `activeHooks` (from `PLAN_PRE_HOOKS_JSON`, §5.6) has a `kind == "gate"`, `capId == "drift"`,
|
|
699
|
+
`check.query == "verify.codebase-drift"` entry (`workflow.plan_drift_precheck` on), run the same check the
|
|
700
|
+
execute gate uses; otherwise skip to step 6:
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
DRIFT=$(gsd_run verify codebase-drift 2>/dev/null || echo '{"skipped":true}')
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
This gate is **non-blocking** and **never blocks, never spawns** the mapper at plan time. If `skipped` or
|
|
707
|
+
`action_required` is false, continue silently to step 6. If `action_required` is true, print `message`
|
|
708
|
+
verbatim (it ends with a `/gsd:map-codebase` pointer) and continue — planning proceeds whether or not the
|
|
709
|
+
map is refreshed first. (`drift_action: auto-remap` stays at `execute:wave:post`.)
|
|
710
|
+
|
|
696
711
|
## 6. Check Existing Plans
|
|
697
712
|
|
|
698
713
|
```bash
|
|
@@ -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
|
|
@@ -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]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengsd/gsd-core",
|
|
3
|
-
"version": "1.6.0-rc.
|
|
3
|
+
"version": "1.6.0-rc.3",
|
|
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",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* gen-plugin-skills.cjs — generates skills/gsd-<stem>/SKILL.md from
|
|
6
|
+
* commands/gsd/*.md using convertClaudeCommandToClaudeSkill.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/gen-plugin-skills.cjs # print summary to stdout
|
|
10
|
+
* node scripts/gen-plugin-skills.cjs --write # write skills/ dir
|
|
11
|
+
* node scripts/gen-plugin-skills.cjs --check # exit 1 if committed skills/ is stale
|
|
12
|
+
*
|
|
13
|
+
* #1596 Phase B-provide. The Claude Code plugin contract discovers skills from
|
|
14
|
+
* a skills/ directory (plugins-reference). GSD's source-of-truth commands live
|
|
15
|
+
* in commands/gsd/*.md (command frontmatter); this script converts each to
|
|
16
|
+
* skill format using the same convertClaudeCommandToClaudeSkill the file-copy
|
|
17
|
+
* installer uses, producing a build-generated skills/ dir that ships in the
|
|
18
|
+
* npm package and serves plugin-only installs.
|
|
19
|
+
*
|
|
20
|
+
* Depends on: gsd-core/bin/lib/runtime-artifact-conversion.cjs (compiled from
|
|
21
|
+
* src/runtime-artifact-conversion.cts by `npm run build:lib`). Must run AFTER
|
|
22
|
+
* build:lib in the build chain.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('node:fs');
|
|
26
|
+
const path = require('node:path');
|
|
27
|
+
const { ExitError, runMain } = require('./lib/cli-exit.cjs');
|
|
28
|
+
|
|
29
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
30
|
+
const COMMANDS_DIR = path.join(ROOT, 'commands', 'gsd');
|
|
31
|
+
const SKILLS_DIR = path.join(ROOT, 'skills');
|
|
32
|
+
const CONVERSION_MODULE = path.join(ROOT, 'gsd-core', 'bin', 'lib', 'runtime-artifact-conversion.cjs');
|
|
33
|
+
const PREFIX = 'gsd-';
|
|
34
|
+
const RUNTIME = 'claude';
|
|
35
|
+
|
|
36
|
+
function generateSkills(conversion) {
|
|
37
|
+
const cmdNames = conversion.readGsdCommandNames();
|
|
38
|
+
const files = fs.readdirSync(COMMANDS_DIR).filter(f => f.endsWith('.md'));
|
|
39
|
+
const results = [];
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const stem = file.slice(0, -3);
|
|
42
|
+
const skillName = PREFIX + stem;
|
|
43
|
+
const src = fs.readFileSync(path.join(COMMANDS_DIR, file), 'utf8');
|
|
44
|
+
const converted = conversion.convertClaudeCommandToClaudeSkill(src, skillName, RUNTIME, cmdNames, true);
|
|
45
|
+
results.push({ skillName, content: converted });
|
|
46
|
+
}
|
|
47
|
+
return results;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function main() {
|
|
51
|
+
const args = new Set(process.argv.slice(2));
|
|
52
|
+
const WRITE = args.has('--write');
|
|
53
|
+
const CHECK = args.has('--check');
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(CONVERSION_MODULE)) {
|
|
56
|
+
throw new ExitError(
|
|
57
|
+
1,
|
|
58
|
+
`gen-plugin-skills: ${path.relative(ROOT, CONVERSION_MODULE)} not found.\n` +
|
|
59
|
+
'Run `npm run build:lib` first (this script depends on the compiled converter).'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
const conversion = require(CONVERSION_MODULE);
|
|
63
|
+
const results = generateSkills(conversion);
|
|
64
|
+
|
|
65
|
+
if (WRITE) {
|
|
66
|
+
fs.rmSync(SKILLS_DIR, { recursive: true, force: true });
|
|
67
|
+
fs.mkdirSync(SKILLS_DIR, { recursive: true });
|
|
68
|
+
for (const { skillName, content } of results) {
|
|
69
|
+
const skillDir = path.join(SKILLS_DIR, skillName);
|
|
70
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
71
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
|
|
72
|
+
}
|
|
73
|
+
process.stdout.write(`gen-plugin-skills: wrote ${results.length} skills to ${path.relative(ROOT, SKILLS_DIR)}/\n`);
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (CHECK) {
|
|
78
|
+
if (!fs.existsSync(SKILLS_DIR)) {
|
|
79
|
+
throw new ExitError(1, 'gen-plugin-skills: skills/ missing. Run: npm run gen:plugin-skills -- --write');
|
|
80
|
+
}
|
|
81
|
+
let stale = 0;
|
|
82
|
+
const expectedNames = new Set(results.map(r => r.skillName));
|
|
83
|
+
for (const { skillName, content } of results) {
|
|
84
|
+
const skillMd = path.join(SKILLS_DIR, skillName, 'SKILL.md');
|
|
85
|
+
if (!fs.existsSync(skillMd)) {
|
|
86
|
+
process.stderr.write(`gen-plugin-skills: missing ${path.relative(ROOT, skillMd)}\n`);
|
|
87
|
+
stale++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (fs.readFileSync(skillMd, 'utf8') !== content) {
|
|
91
|
+
process.stderr.write(`gen-plugin-skills: stale ${path.relative(ROOT, skillMd)}\n`);
|
|
92
|
+
stale++;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const existingDirs = fs.readdirSync(SKILLS_DIR, { withFileTypes: true })
|
|
96
|
+
.filter(e => e.isDirectory() && e.name.startsWith(PREFIX));
|
|
97
|
+
for (const dir of existingDirs) {
|
|
98
|
+
if (!expectedNames.has(dir.name)) {
|
|
99
|
+
process.stderr.write(`gen-plugin-skills: stale (no source) ${path.relative(ROOT, path.join(SKILLS_DIR, dir.name))}\n`);
|
|
100
|
+
stale++;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (stale > 0) {
|
|
104
|
+
throw new ExitError(1, `gen-plugin-skills: ${stale} stale skill(s). Run: npm run gen:plugin-skills -- --write`);
|
|
105
|
+
}
|
|
106
|
+
process.stdout.write(`gen-plugin-skills: ${results.length} skills up to date\n`);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
process.stdout.write(
|
|
111
|
+
`gen-plugin-skills: would write ${results.length} skills to ${path.relative(ROOT, SKILLS_DIR)}/\n` +
|
|
112
|
+
' (use --write to generate, --check to verify staleness)\n'
|
|
113
|
+
);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
runMain(main);
|