@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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
3
|
"displayName": "GSD Core",
|
|
4
|
-
"version": "1.6.0-rc.
|
|
4
|
+
"version": "1.6.0-rc.3",
|
|
5
5
|
"description": "GSD Core is a meta-prompting, context engineering, and spec-driven development system for AI coding agents.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "open-gsd",
|
|
@@ -19,5 +19,6 @@
|
|
|
19
19
|
"gsd"
|
|
20
20
|
],
|
|
21
21
|
"commands": "./commands/gsd/",
|
|
22
|
+
"skills": "./skills/",
|
|
22
23
|
"hooks": "./hooks/hooks.json"
|
|
23
24
|
}
|
package/agents/gsd-planner.md
CHANGED
|
@@ -380,11 +380,11 @@ Output: [Artifacts created]
|
|
|
380
380
|
|
|
381
381
|
## STRIDE Threat Register
|
|
382
382
|
|
|
383
|
-
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
|
384
|
-
|
|
385
|
-
| T-{phase}-01 | {S/T/R/I/D/E} | {function/endpoint/file} | mitigate | {specific
|
|
386
|
-
| T-{phase}-02 | {category} | {component} | accept | {rationale
|
|
387
|
-
| T-{phase}-SC | Tampering | npm/pip/cargo installs | mitigate | slopcheck + blocking human checkpoint for [ASSUMED]/[SUS] |
|
|
383
|
+
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
|
384
|
+
|-----------|----------|-----------|----------|-------------|-----------------|
|
|
385
|
+
| T-{phase}-01 | {S/T/R/I/D/E} | {function/endpoint/file} | {critical\|high\|medium\|low} | mitigate | {specific mitigation action} |
|
|
386
|
+
| T-{phase}-02 | {category} | {component} | low | accept | {rationale for acceptance} |
|
|
387
|
+
| T-{phase}-SC | Tampering | npm/pip/cargo installs | high | mitigate | slopcheck + blocking human checkpoint for [ASSUMED]/[SUS] |
|
|
388
388
|
</threat_model>
|
|
389
389
|
|
|
390
390
|
<verification>
|
|
@@ -459,7 +459,7 @@ Only include what Claude literally cannot do.
|
|
|
459
459
|
**Step 0: Extract Requirement IDs**
|
|
460
460
|
Read ROADMAP.md `**Requirements:**` line for this phase. Strip brackets if present (e.g., `[AUTH-01, AUTH-02]` → `AUTH-01, AUTH-02`). Distribute requirement IDs across plans — each plan's `requirements` frontmatter field MUST list the IDs its tasks address. **CRITICAL:** Every requirement ID MUST appear in at least one plan. Plans with an empty `requirements` field are invalid.
|
|
461
461
|
|
|
462
|
-
**Security (when `security_enforcement` enabled — absent = enabled):** Identify trust boundaries in this phase's scope. Map STRIDE categories to applicable tech stack from RESEARCH.md security domain. For each threat: assign
|
|
462
|
+
**Security (when `security_enforcement` enabled — absent = enabled):** Identify trust boundaries in this phase's scope. Map STRIDE categories to applicable tech stack from RESEARCH.md security domain. For each threat: assign a **severity** (critical|high|medium|low) based on impact × likelihood, and a disposition (`mitigate`/`accept`/`transfer`) per the configured OWASP ASVS level — see @~/.claude/gsd-core/references/security-asvs-levels.md. Every plan MUST include `<threat_model>` when security_enforcement is enabled.
|
|
463
463
|
|
|
464
464
|
**Package legitimacy gate (npm/pip/cargo only):**
|
|
465
465
|
- Require RESEARCH.md `## Package Legitimacy Audit` before package-manager install tasks.
|
|
@@ -477,66 +477,16 @@ Take phase goal from ROADMAP.md. Must be outcome-shaped, not task-shaped.
|
|
|
477
477
|
**Step 2: Derive Observable Truths**
|
|
478
478
|
"What must be TRUE for this goal to be achieved?" List 3-7 truths from USER's perspective.
|
|
479
479
|
|
|
480
|
-
For "working chat interface":
|
|
481
|
-
- User can see existing messages
|
|
482
|
-
- User can type a new message
|
|
483
|
-
- User can send the message
|
|
484
|
-
- Sent message appears in the list
|
|
485
|
-
- Messages persist across page refresh
|
|
486
|
-
|
|
487
|
-
**Test:** Each truth verifiable by a human using the application.
|
|
488
|
-
|
|
489
480
|
**Step 3: Derive Required Artifacts**
|
|
490
481
|
For each truth: "What must EXIST for this to be true?"
|
|
491
482
|
|
|
492
|
-
"User can see existing messages" requires:
|
|
493
|
-
- Message list component (renders Message[])
|
|
494
|
-
- Messages state (loaded from somewhere)
|
|
495
|
-
- API route or data source (provides messages)
|
|
496
|
-
- Message type definition (shapes the data)
|
|
497
|
-
|
|
498
|
-
**Test:** Each artifact = a specific file or database object.
|
|
499
|
-
|
|
500
483
|
**Step 4: Derive Required Wiring**
|
|
501
484
|
For each artifact: "What must be CONNECTED for this to function?"
|
|
502
485
|
|
|
503
|
-
Message list component wiring:
|
|
504
|
-
- Imports Message type (not using `any`)
|
|
505
|
-
- Receives messages prop or fetches from API
|
|
506
|
-
- Maps over messages to render (not hardcoded)
|
|
507
|
-
- Handles empty state (not just crashes)
|
|
508
|
-
|
|
509
486
|
**Step 5: Identify Key Links**
|
|
510
487
|
"Where is this most likely to break?" Key links = critical connections where breakage causes cascading failures.
|
|
511
488
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
```yaml
|
|
515
|
-
must_haves:
|
|
516
|
-
truths:
|
|
517
|
-
- "User can see existing messages"
|
|
518
|
-
- "User can send a message"
|
|
519
|
-
- "Messages persist across refresh"
|
|
520
|
-
artifacts:
|
|
521
|
-
- path: "src/components/Chat.tsx"
|
|
522
|
-
provides: "Message list rendering"
|
|
523
|
-
min_lines: 30
|
|
524
|
-
- path: "src/app/api/chat/route.ts"
|
|
525
|
-
provides: "Message CRUD operations"
|
|
526
|
-
exports: ["GET", "POST"]
|
|
527
|
-
- path: "prisma/schema.prisma"
|
|
528
|
-
provides: "Message model"
|
|
529
|
-
contains: "model Message"
|
|
530
|
-
key_links:
|
|
531
|
-
- from: "src/components/Chat.tsx"
|
|
532
|
-
to: "src/app/api/chat/route.ts"
|
|
533
|
-
via: "fetch in useEffect — calls /api/chat endpoint"
|
|
534
|
-
pattern: "fetch.*api/chat"
|
|
535
|
-
- from: "src/app/api/chat/route.ts"
|
|
536
|
-
to: "prisma/schema.prisma"
|
|
537
|
-
via: "database query via prisma.message"
|
|
538
|
-
pattern: "prisma\\.message\\.(find|create)"
|
|
539
|
-
```
|
|
489
|
+
See @~/.claude/gsd-core/references/planner-guidance.md for a worked example and the `must_haves` YAML format.
|
|
540
490
|
|
|
541
491
|
</goal_backward>
|
|
542
492
|
|
|
@@ -1037,6 +987,7 @@ Phase planning complete when:
|
|
|
1037
987
|
- [ ] User knows next steps and wave structure
|
|
1038
988
|
- [ ] `<threat_model>` present with STRIDE register (when `security_enforcement` enabled)
|
|
1039
989
|
- [ ] Every threat has a disposition (mitigate / accept / transfer)
|
|
990
|
+
- [ ] Every threat has a Severity (critical|high|medium|low)
|
|
1040
991
|
- [ ] Mitigations reference specific implementation (not generic advice)
|
|
1041
992
|
|
|
1042
993
|
## Gap Closure Mode
|
|
@@ -33,18 +33,19 @@ Does NOT scan blindly for new vulnerabilities. Verifies each threat in `<threat_
|
|
|
33
33
|
- Marking CLOSED based on code structure ("looks like it validates input") without finding the actual validation call
|
|
34
34
|
|
|
35
35
|
**Required finding classification:**
|
|
36
|
-
- **BLOCKER** — `OPEN_THREATS`: a declared mitigation is absent in implemented code; phase must not ship
|
|
36
|
+
- **BLOCKER** — `OPEN_THREATS`: a declared mitigation is absent in implemented code AND the threat's severity ≥ `block_on` threshold; phase must not ship until resolved
|
|
37
|
+
- **OPEN — non-blocking** — mitigation absent BUT the threat's severity is below the `block_on` threshold; tracked in SECURITY.md, does NOT count toward `threats_open`, does not block ship
|
|
37
38
|
- **WARNING** — `unregistered_flag`: new attack surface appeared during implementation with no threat mapping
|
|
38
|
-
Every threat must resolve to CLOSED, OPEN (
|
|
39
|
+
Every threat must resolve to CLOSED, OPEN-blocking (severity ≥ block_on), OPEN-non-blocking (severity below block_on), or documented accepted risk.
|
|
39
40
|
</adversarial_stance>
|
|
40
41
|
|
|
41
42
|
<execution_flow>
|
|
42
43
|
|
|
43
44
|
<step name="load_context">
|
|
44
45
|
Read ALL files from `<required_reading>`. Extract:
|
|
45
|
-
- PLAN.md `<threat_model>` block: full threat register with IDs, categories, dispositions, mitigation plans
|
|
46
|
+
- PLAN.md `<threat_model>` block: full threat register with IDs, categories, severities, dispositions, mitigation plans
|
|
46
47
|
- SUMMARY.md `## Threat Flags` section: new attack surface detected by executor during implementation
|
|
47
|
-
- `<config>` block: `asvs_level` (1/2/3), `block_on` (
|
|
48
|
+
- `<config>` block: `asvs_level` (1/2/3), `block_on` (critical | high | medium | low | none) — severity ordering: critical > high > medium > low; none = never block
|
|
48
49
|
- Implementation files: exports, auth patterns, input handling, data flows
|
|
49
50
|
|
|
50
51
|
**Context budget:** Load project skills first (lightweight). Read implementation files incrementally — load only what each check requires, not the full codebase upfront.
|
|
@@ -60,7 +61,7 @@ This ensures project-specific patterns, conventions, and best practices are appl
|
|
|
60
61
|
</step>
|
|
61
62
|
|
|
62
63
|
<step name="analyze_threats">
|
|
63
|
-
For each threat in `<threat_model>`,
|
|
64
|
+
For each threat in `<threat_model>`, read its `severity` field (critical|high|medium|low). If building the register retroactively (no `<threat_model>` in PLAN.md), assign a severity to each threat you construct based on impact × likelihood. Determine verification method by disposition:
|
|
64
65
|
|
|
65
66
|
| Disposition | Verification Method |
|
|
66
67
|
|-------------|---------------------|
|
|
@@ -69,16 +70,27 @@ For each threat in `<threat_model>`, determine verification method by dispositio
|
|
|
69
70
|
| `transfer` | Verify transfer documentation present (insurance, vendor SLA, etc.) |
|
|
70
71
|
|
|
71
72
|
Classify each threat before verification. Record classification for every threat — no threat skipped.
|
|
73
|
+
|
|
74
|
+
**Verification depth scales with `asvs_level`** (see @~/.claude/gsd-core/references/security-asvs-levels.md for full definitions):
|
|
75
|
+
- L1: verify mitigation is PRESENT in the cited file (grep-level — pattern exists).
|
|
76
|
+
- L2: verify the mitigation ADDRESSES the threat vector and is placed at the correct boundary (a check in the wrong layer does not close the threat).
|
|
77
|
+
- L3: deep trace — follow the data flow end-to-end, check edge cases and ordering, confirm no bypass path exists.
|
|
72
78
|
</step>
|
|
73
79
|
|
|
74
80
|
<step name="verify_and_write">
|
|
75
|
-
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`.
|
|
81
|
+
For each `mitigate` threat: grep for declared mitigation pattern in cited files → found = `CLOSED`, not found = `OPEN`. Apply depth per `asvs_level` (see analyze_threats step).
|
|
76
82
|
For `accept` threats: check SECURITY.md accepted risks log → entry present = `CLOSED`, absent = `OPEN`.
|
|
77
83
|
For `transfer` threats: check for transfer documentation → present = `CLOSED`, absent = `OPEN`.
|
|
78
84
|
|
|
79
85
|
For each `threat_flag` in SUMMARY.md `## Threat Flags`: if maps to existing threat ID → informational. If no mapping → log as `unregistered_flag` in SECURITY.md (not a blocker).
|
|
80
86
|
|
|
81
|
-
|
|
87
|
+
**Severity-aware `threats_open` computation (severity order: critical > high > medium > low):**
|
|
88
|
+
`threats_open` (the SECURITY.md frontmatter gate field) = the count of threats whose status is OPEN AND whose severity rank ≥ the `block_on` rank. `block_on: none` ⇒ 0 (nothing ever blocks). `block_on: low` ⇒ all open threats block. `block_on: high` (default) ⇒ only high and critical open threats block.
|
|
89
|
+
Open threats BELOW the block threshold are recorded in SECURITY.md as **open — below {block_on} threshold (non-blocking)** and MUST NOT be counted in `threats_open`.
|
|
90
|
+
|
|
91
|
+
**Fail-closed for missing severity:** if an OPEN threat has no severity or an unparseable severity (e.g. a legacy register predating the Severity column), treat it as `critical` for this computation — it COUNTS toward `threats_open` (blocking). Never silently drop an unranked open threat.
|
|
92
|
+
|
|
93
|
+
Write SECURITY.md. Set `threats_open` to the severity-filtered count. Return structured result.
|
|
82
94
|
</step>
|
|
83
95
|
|
|
84
96
|
</execution_flow>
|
|
@@ -95,9 +107,9 @@ Write SECURITY.md. Set `threats_open` count. Return structured result.
|
|
|
95
107
|
**ASVS Level:** {1/2/3}
|
|
96
108
|
|
|
97
109
|
### Threat Verification
|
|
98
|
-
| Threat ID | Category | Disposition | Evidence |
|
|
99
|
-
|
|
100
|
-
| {id} | {category} | {mitigate/accept/transfer} | {file:line or doc reference} |
|
|
110
|
+
| Threat ID | Category | Severity | Disposition | Evidence |
|
|
111
|
+
|-----------|----------|----------|-------------|----------|
|
|
112
|
+
| {id} | {category} | {critical\|high\|medium\|low} | {mitigate/accept/transfer} | {file:line or doc reference} |
|
|
101
113
|
|
|
102
114
|
### Unregistered Flags
|
|
103
115
|
{none / list from SUMMARY.md ## Threat Flags with no threat mapping}
|
|
@@ -115,14 +127,21 @@ SECURITY.md: {path}
|
|
|
115
127
|
**ASVS Level:** {1/2/3}
|
|
116
128
|
|
|
117
129
|
### Closed
|
|
118
|
-
| Threat ID | Category | Disposition | Evidence |
|
|
119
|
-
|
|
120
|
-
| {id} | {category} | {disposition} | {evidence} |
|
|
121
|
-
|
|
122
|
-
### Open
|
|
123
|
-
| Threat ID | Category | Mitigation Expected | Files Searched |
|
|
124
|
-
|
|
125
|
-
| {id} | {category} | {pattern not found} | {file paths} |
|
|
130
|
+
| Threat ID | Category | Severity | Disposition | Evidence |
|
|
131
|
+
|-----------|----------|----------|-------------|----------|
|
|
132
|
+
| {id} | {category} | {critical\|high\|medium\|low} | {disposition} | {evidence} |
|
|
133
|
+
|
|
134
|
+
### Open (blocking — severity ≥ block_on threshold)
|
|
135
|
+
| Threat ID | Category | Severity | Mitigation Expected | Files Searched |
|
|
136
|
+
|-----------|----------|----------|---------------------|----------------|
|
|
137
|
+
| {id} | {category} | {critical\|high\|medium\|low} | {pattern not found} | {file paths} |
|
|
138
|
+
|
|
139
|
+
### Open (non-blocking — severity below block_on threshold)
|
|
140
|
+
| Threat ID | Category | Severity | Mitigation Expected | Files Searched |
|
|
141
|
+
|-----------|----------|----------|---------------------|----------------|
|
|
142
|
+
| {id} | {category} | {critical\|high\|medium\|low} | {pattern not found} | {file paths} |
|
|
143
|
+
|
|
144
|
+
*Only blocking-open threats count toward `threats_open` in SECURITY.md frontmatter.*
|
|
126
145
|
|
|
127
146
|
Next: Implement mitigations or document as accepted in SECURITY.md accepted risks log, then re-run /gsd:secure-phase.
|
|
128
147
|
|
package/bin/install.js
CHANGED
|
@@ -1517,11 +1517,17 @@ function convertGeminiToolName(claudeTool) {
|
|
|
1517
1517
|
// Task/Agent: exclude — agents are auto-registered as callable tools.
|
|
1518
1518
|
// AskUserQuestion: exclude — Gemini CLI does not expose an ask_user tool;
|
|
1519
1519
|
// emitting it causes frontmatter validation errors (#3362).
|
|
1520
|
+
// Skill/SlashCommand: exclude — Gemini CLI has no 'skill' built-in tool;
|
|
1521
|
+
// the lowercase fallback would emit an invalid 'skill'/'slashcommand' name
|
|
1522
|
+
// that fails frontmatter validation (tools.N: Invalid tool name) and aborts
|
|
1523
|
+
// the entire agent load (#1394).
|
|
1520
1524
|
if (
|
|
1521
1525
|
claudeTool === 'Task' ||
|
|
1522
1526
|
claudeTool === 'Agent' ||
|
|
1523
1527
|
claudeTool === 'AskUserQuestion' ||
|
|
1524
|
-
claudeTool === 'ask_user'
|
|
1528
|
+
claudeTool === 'ask_user' ||
|
|
1529
|
+
claudeTool === 'Skill' ||
|
|
1530
|
+
claudeTool === 'SlashCommand'
|
|
1525
1531
|
) {
|
|
1526
1532
|
return null;
|
|
1527
1533
|
}
|
|
@@ -2451,20 +2457,18 @@ function convertClaudeToWindsurfMarkdown(content) {
|
|
|
2451
2457
|
// Replace subagent_type from Claude to Windsurf format
|
|
2452
2458
|
converted = converted.replace(/subagent_type="general-purpose"/g, 'subagent_type="generalPurpose"');
|
|
2453
2459
|
converted = converted.replace(/\$ARGUMENTS\b/g, '{{GSD_ARGS}}');
|
|
2454
|
-
// Replace project-level Claude conventions with Windsurf
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
converted = converted.replace(
|
|
2458
|
-
converted = converted.replace(
|
|
2459
|
-
converted = converted.replace(
|
|
2460
|
-
converted = converted.replace(
|
|
2461
|
-
converted = converted.replace(/\.claude
|
|
2462
|
-
converted = converted.replace(/\.\/\.claude\//g, './.devin/');
|
|
2463
|
-
converted = converted.replace(/\.claude\//g, '.devin/');
|
|
2460
|
+
// Replace project-level Claude conventions with Windsurf equivalents.
|
|
2461
|
+
converted = converted.replace(/`\.\/CLAUDE\.md`/g, '`.windsurf/rules`');
|
|
2462
|
+
converted = converted.replace(/\.\/CLAUDE\.md/g, '.windsurf/rules');
|
|
2463
|
+
converted = converted.replace(/`CLAUDE\.md`/g, '`.windsurf/rules`');
|
|
2464
|
+
converted = converted.replace(/\bCLAUDE\.md\b/g, '.windsurf/rules');
|
|
2465
|
+
converted = converted.replace(/\.claude\/skills\//g, '.windsurf/skills/');
|
|
2466
|
+
converted = converted.replace(/\.\/\.claude\//g, './.windsurf/');
|
|
2467
|
+
converted = converted.replace(/\.claude\//g, '.windsurf/');
|
|
2464
2468
|
// Bare forms (no trailing slash) — after slash forms to avoid double-rewrite.
|
|
2465
2469
|
// Use negative lookahead (?![\w-]) to preserve .claude-plugin and .claudeignore.
|
|
2466
|
-
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.
|
|
2467
|
-
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.
|
|
2470
|
+
converted = converted.replace(/~\/\.claude(?![\w-])/g, '~/.windsurf');
|
|
2471
|
+
converted = converted.replace(/\$HOME\/\.claude(?![\w-])/g, '$HOME/.windsurf');
|
|
2468
2472
|
// Environment variable name rewrite
|
|
2469
2473
|
converted = converted.replace(/\bCLAUDE_CONFIG_DIR\b/g, 'WINDSURF_CONFIG_DIR');
|
|
2470
2474
|
// Remove Claude Code-specific bug workarounds before brand replacement
|
|
@@ -2518,6 +2522,33 @@ function convertClaudeCommandToWindsurfSkill(content, skillName) {
|
|
|
2518
2522
|
return `---\nname: ${yamlIdentifier(skillName)}\ndescription: ${yamlQuote(shortDescription)}\n---\n\n${adapter}\n\n${body.trimStart()}`;
|
|
2519
2523
|
}
|
|
2520
2524
|
|
|
2525
|
+
function convertClaudeCommandToWindsurfWorkflow(content, commandName) {
|
|
2526
|
+
// #1615 security: commandName flows unsanitized into a markdown body that
|
|
2527
|
+
// Windsurf loads as an LLM-readable workflow. Validate at entry to prevent
|
|
2528
|
+
// (a) prompt injection via newlines / markdown structure in the filename,
|
|
2529
|
+
// (b) path-component injection via .., /, \ in stem → @-reference target.
|
|
2530
|
+
// Pattern: optional gsd- prefix + lowercase alphanumeric + dashes; rejects
|
|
2531
|
+
// everything else. See DEFECT.PROMPT-INJECTION-SCAN-COLLISION and the
|
|
2532
|
+
// PR #1622 security review.
|
|
2533
|
+
if (typeof commandName !== 'string' || !/^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(commandName)) {
|
|
2534
|
+
const preview = typeof commandName === 'string' ? JSON.stringify(commandName.slice(0, 60)) : String(commandName);
|
|
2535
|
+
throw new Error(
|
|
2536
|
+
`convertClaudeCommandToWindsurfWorkflow: rejected commandName ${preview}; ` +
|
|
2537
|
+
'must match /^(?:gsd-)?[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/ (no slashes, backslashes, spaces, dots, trailing dash, or control chars — prevents prompt injection and path-component injection into the workflow body)'
|
|
2538
|
+
);
|
|
2539
|
+
}
|
|
2540
|
+
const converted = convertClaudeToWindsurfMarkdown(content);
|
|
2541
|
+
const { frontmatter } = extractFrontmatterAndBody(converted);
|
|
2542
|
+
const description = frontmatter ? extractFrontmatterField(frontmatter, 'description') : '';
|
|
2543
|
+
const stem = commandName.startsWith('gsd-') ? commandName.slice(4) : commandName;
|
|
2544
|
+
const workflow = `# ${commandName}\n\n${toSingleLine(description || `Run ${commandName}.`)}\n\nRead and execute the GSD command at @~/.claude/gsd-core/commands/gsd/${stem}.md end-to-end. Treat the user's message after /${commandName} as the command arguments.`;
|
|
2545
|
+
const byteLength = Buffer.byteLength(workflow, 'utf8');
|
|
2546
|
+
if (byteLength > 12000) {
|
|
2547
|
+
throw new Error(`Windsurf workflow ${commandName} exceeds 12000 bytes (${byteLength}); extract references before installing`);
|
|
2548
|
+
}
|
|
2549
|
+
return workflow;
|
|
2550
|
+
}
|
|
2551
|
+
|
|
2521
2552
|
/**
|
|
2522
2553
|
* Convert Claude Code agent markdown to Windsurf agent format.
|
|
2523
2554
|
* Strips frontmatter fields Windsurf doesn't support (color, skills),
|
|
@@ -3223,6 +3254,66 @@ function cleanupCodexSkillMetadataSidecars(skillsDir) {
|
|
|
3223
3254
|
}
|
|
3224
3255
|
}
|
|
3225
3256
|
|
|
3257
|
+
/**
|
|
3258
|
+
* Remove legacy Windsurf skill artifacts from .devin/skills/gsd- directories.
|
|
3259
|
+
*
|
|
3260
|
+
* Pre-#1615 Windsurf installs wrote skills under .devin/ (Devin Desktop
|
|
3261
|
+
* preferred dir, #1085). #1615 moved Windsurf to .windsurf/workflows/.
|
|
3262
|
+
* Old .devin/skills/gsd- dirs linger on disk indefinitely and confuse
|
|
3263
|
+
* users who see two GSD trees.
|
|
3264
|
+
*
|
|
3265
|
+
* Preserves user-owned content:
|
|
3266
|
+
* - non-gsd-* dirs under .devin/skills/ (user-authored skills)
|
|
3267
|
+
* - gsd-dev-preferences/ (user-owned per #2973)
|
|
3268
|
+
* - any files (not dirs) under .devin/skills/
|
|
3269
|
+
*
|
|
3270
|
+
* @param {string} workspaceDir - workspace root (process.cwd() for local installs)
|
|
3271
|
+
* @returns {number} count of removed legacy gsd-* skill directories
|
|
3272
|
+
*/
|
|
3273
|
+
function cleanupWindsurfLegacyDevinSkills(workspaceDir) {
|
|
3274
|
+
const legacySkillsDir = path.join(workspaceDir, '.devin', 'skills');
|
|
3275
|
+
if (!fs.existsSync(legacySkillsDir)) return 0;
|
|
3276
|
+
|
|
3277
|
+
// Mirror the user-owned list from cleanupCodexSkillMetadataSidecars (#2973).
|
|
3278
|
+
const _userOwnedSkillDirs = new Set(['gsd-dev-preferences']);
|
|
3279
|
+
let removed = 0;
|
|
3280
|
+
|
|
3281
|
+
for (const entry of fs.readdirSync(legacySkillsDir, { withFileTypes: true })) {
|
|
3282
|
+
if (!entry.isDirectory() || !entry.name.startsWith('gsd-')) continue;
|
|
3283
|
+
if (_userOwnedSkillDirs.has(entry.name)) continue;
|
|
3284
|
+
|
|
3285
|
+
const dirToRemove = path.join(legacySkillsDir, entry.name);
|
|
3286
|
+
try {
|
|
3287
|
+
// Symlink guard: if the gsd-* dir is itself a symlink pointing outside
|
|
3288
|
+
// the .devin tree, deleting through it could escape the tree. Skip.
|
|
3289
|
+
const stat = fs.lstatSync(dirToRemove);
|
|
3290
|
+
if (stat.isSymbolicLink()) continue;
|
|
3291
|
+
|
|
3292
|
+
fs.rmSync(dirToRemove, { recursive: true, force: true });
|
|
3293
|
+
removed++;
|
|
3294
|
+
} catch (_err) {
|
|
3295
|
+
// Fail open — a single bad dir must not block the install.
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
|
|
3299
|
+
// If .devin/skills/ is now empty, prune it. If .devin/ itself is then empty,
|
|
3300
|
+
// prune that too — leaves the workspace clean for the new .windsurf/ layout.
|
|
3301
|
+
// Never remove non-empty containers (user may have other Devin content).
|
|
3302
|
+
try {
|
|
3303
|
+
if (fs.existsSync(legacySkillsDir) && fs.readdirSync(legacySkillsDir).length === 0) {
|
|
3304
|
+
fs.rmdirSync(legacySkillsDir);
|
|
3305
|
+
const devinDir = path.join(workspaceDir, '.devin');
|
|
3306
|
+
if (fs.existsSync(devinDir) && fs.readdirSync(devinDir).length === 0) {
|
|
3307
|
+
fs.rmdirSync(devinDir);
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
} catch (_err) {
|
|
3311
|
+
// best-effort container cleanup
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
return removed;
|
|
3315
|
+
}
|
|
3316
|
+
|
|
3226
3317
|
/**
|
|
3227
3318
|
* Generate the GSD config block for Codex config.toml.
|
|
3228
3319
|
* @param {Array<{name: string, description: string}>} agents
|
|
@@ -9592,6 +9683,17 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9592
9683
|
cleanupCodexSkillMetadataSidecars(path.join(targetDir, 'skills'));
|
|
9593
9684
|
}
|
|
9594
9685
|
|
|
9686
|
+
// #1629 Finding B: Windsurf local only — remove legacy .devin/skills/gsd-*
|
|
9687
|
+
// dirs from pre-#1615 installs. #1615 moved Windsurf to .windsurf/workflows/
|
|
9688
|
+
// but never cleaned up the old .devin/skills/ layout (#1085). User-owned
|
|
9689
|
+
// content is preserved (non-gsd- dirs, gsd-dev-preferences, symlinks).
|
|
9690
|
+
if (isWindsurf && !isGlobal) {
|
|
9691
|
+
const removedCount = cleanupWindsurfLegacyDevinSkills(process.cwd());
|
|
9692
|
+
if (removedCount > 0) {
|
|
9693
|
+
console.log(` ${green}✓${reset} Removed ${removedCount} legacy .devin/skills/gsd-* dir(s) (pre-#1615 Windsurf layout)`);
|
|
9694
|
+
}
|
|
9695
|
+
}
|
|
9696
|
+
|
|
9595
9697
|
// Hermes only: write DESCRIPTION.md for the gsd/ category after layout install
|
|
9596
9698
|
if (isHermes) {
|
|
9597
9699
|
writeHermesCategoryDescription(path.join(targetDir, 'skills', 'gsd'));
|
|
@@ -9632,6 +9734,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9632
9734
|
} else {
|
|
9633
9735
|
failures.push('agents/gsd.yaml');
|
|
9634
9736
|
}
|
|
9737
|
+
} else if (isWindsurf) {
|
|
9738
|
+
if (isGlobal) {
|
|
9739
|
+
console.log(` ${green}✓${reset} Windsurf global install skipped workflow artifacts (workspace-only)`);
|
|
9740
|
+
} else {
|
|
9741
|
+
const workflowsDir = path.join(targetDir, 'workflows');
|
|
9742
|
+
if (fs.existsSync(workflowsDir)) {
|
|
9743
|
+
const workflowCount = fs.readdirSync(workflowsDir)
|
|
9744
|
+
.filter(f => f.startsWith('gsd-') && f.endsWith('.md')).length;
|
|
9745
|
+
if (workflowCount > 0) {
|
|
9746
|
+
console.log(` ${green}✓${reset} Installed ${workflowCount} workflows to workflows/`);
|
|
9747
|
+
} else {
|
|
9748
|
+
failures.push('workflows/gsd-*');
|
|
9749
|
+
}
|
|
9750
|
+
} else {
|
|
9751
|
+
failures.push('workflows/gsd-*');
|
|
9752
|
+
}
|
|
9753
|
+
}
|
|
9635
9754
|
} else {
|
|
9636
9755
|
const skillsDir = path.join(targetDir, 'skills');
|
|
9637
9756
|
if (fs.existsSync(skillsDir)) {
|
|
@@ -9857,6 +9976,23 @@ function install(isGlobal, runtime = 'claude', options = {}) {
|
|
|
9857
9976
|
failures.push('gsd-core');
|
|
9858
9977
|
}
|
|
9859
9978
|
|
|
9979
|
+
// #1629 critical fix: Windsurf workflow wrappers (convertClaudeCommandToWindsurfWorkflow)
|
|
9980
|
+
// delegate to command bodies at <targetDir>/gsd-core/commands/gsd/${stem}.md via a
|
|
9981
|
+
// hardcoded @~/.claude/gsd-core/commands/gsd/ path that _applyRuntimeRewrites rewrites
|
|
9982
|
+
// to the install target. The source gsd-core/ dir does NOT ship with commands/ —
|
|
9983
|
+
// the canonical command source lives at the package root (commands/gsd/). Without
|
|
9984
|
+
// this copy, every /gsd-* workflow in Cascade references a missing file and the LLM
|
|
9985
|
+
// cannot execute the command body. Surfaced by the #1629 regression test after the
|
|
9986
|
+
// original adversarial review of #1622 missed it.
|
|
9987
|
+
if (isWindsurf && !isGlobal) {
|
|
9988
|
+
const commandsSrc = path.join(src, 'commands', 'gsd');
|
|
9989
|
+
const commandsDest = path.join(skillDest, 'commands', 'gsd');
|
|
9990
|
+
if (fs.existsSync(commandsSrc)) {
|
|
9991
|
+
copyWithPathReplacement(commandsSrc, commandsDest, pathPrefix, runtime, true, isGlobal);
|
|
9992
|
+
console.log(` ${green}✓${reset} Installed command bodies to gsd-core/commands/gsd/ (workflow delegation targets)`);
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
|
|
9860
9996
|
// Copy shared manifests into the gsd-core payload
|
|
9861
9997
|
// at the co-located path that CJS modules resolve first:
|
|
9862
9998
|
// gsd-core/bin/shared/*.json
|
|
@@ -11913,6 +12049,7 @@ module.exports = {
|
|
|
11913
12049
|
convertClaudeAgentToCodexAgent,
|
|
11914
12050
|
generateCodexAgentToml,
|
|
11915
12051
|
cleanupCodexSkillMetadataSidecars,
|
|
12052
|
+
cleanupWindsurfLegacyDevinSkills,
|
|
11916
12053
|
generateCodexConfigBlock,
|
|
11917
12054
|
stripGsdFromCodexConfig,
|
|
11918
12055
|
migrateCodexHooksMapFormat,
|
|
@@ -11974,6 +12111,7 @@ module.exports = {
|
|
|
11974
12111
|
skillFrontmatterName,
|
|
11975
12112
|
convertClaudeToWindsurfMarkdown,
|
|
11976
12113
|
convertClaudeCommandToWindsurfSkill,
|
|
12114
|
+
convertClaudeCommandToWindsurfWorkflow,
|
|
11977
12115
|
convertClaudeAgentToWindsurfAgent,
|
|
11978
12116
|
convertClaudeToAugmentMarkdown,
|
|
11979
12117
|
convertClaudeCommandToAugmentSkill,
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.6.0-rc.
|
|
3
|
+
"version": "1.6.0-rc.3",
|
|
4
4
|
"description": "GSD Core — a meta-prompting, context engineering, and spec-driven development system for AI coding agents. Loads gsd's operating context into every Gemini CLI session.",
|
|
5
5
|
"contextFileName": "GEMINI.md"
|
|
6
6
|
}
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
* UAT Audit:
|
|
86
86
|
* audit-uat Scan all phases for unresolved UAT/verification items
|
|
87
87
|
* uat render-checkpoint --file <path> Render the current UAT checkpoint block
|
|
88
|
+
* uat classify-coverage --summary <path> Classify a SUMMARY coverage block into auto-passed vs human-UAT (#1602)
|
|
88
89
|
*
|
|
89
90
|
* Open Artifact Audit:
|
|
90
91
|
* audit-open [--json] Scan all .planning/ artifact types for unresolved items
|
|
@@ -638,7 +639,7 @@ async function main() {
|
|
|
638
639
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
639
640
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
640
641
|
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
641
|
-
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
642
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
642
643
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
643
644
|
'Global flags:\n' +
|
|
644
645
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -689,6 +690,10 @@ async function main() {
|
|
|
689
690
|
'worktree', 'prompt-budget',
|
|
690
691
|
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
691
692
|
'user-story', // pure string validation — no .planning/ access needed
|
|
693
|
+
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
|
|
694
|
+
// .planning/ access needed, and resolving project root would break workflow
|
|
695
|
+
// invocations that run before .planning/ exists (new-project Step 1).
|
|
696
|
+
'project-instruction-file',
|
|
692
697
|
]);
|
|
693
698
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
694
699
|
cwd = findProjectRoot(cwd);
|
|
@@ -1103,6 +1108,29 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1103
1108
|
break;
|
|
1104
1109
|
}
|
|
1105
1110
|
|
|
1111
|
+
case 'project-instruction-file': {
|
|
1112
|
+
// #1529: pure runtime→filename projection. Backs the
|
|
1113
|
+
// `gsd_run query project-instruction-file --runtime <r>` call in
|
|
1114
|
+
// new-project.md so the bash workflow and profile-output.cjs share one
|
|
1115
|
+
// source of truth (getProjectInstructionFile in runtime-name-policy.cjs).
|
|
1116
|
+
// No SDK bridge — pure local lookup, runs before .planning/ exists.
|
|
1117
|
+
const { getProjectInstructionFile } = require('./lib/runtime-name-policy.cjs');
|
|
1118
|
+
// Parse --runtime <value> (space or = form); default to empty so the
|
|
1119
|
+
// safe AGENTS.md cross-agent default applies.
|
|
1120
|
+
const pifArgs = args.slice(1);
|
|
1121
|
+
let pifRuntime = '';
|
|
1122
|
+
for (let i = 0; i < pifArgs.length; i++) {
|
|
1123
|
+
const a = pifArgs[i];
|
|
1124
|
+
if (a === '--runtime' && pifArgs[i + 1] !== undefined) { pifRuntime = pifArgs[++i]; continue; }
|
|
1125
|
+
if (a.startsWith('--runtime=')) { pifRuntime = a.slice('--runtime='.length); continue; }
|
|
1126
|
+
// First positional that isn't a flag also works (lenient); otherwise ignore unknown flags.
|
|
1127
|
+
if (!a.startsWith('-') && !pifRuntime) { pifRuntime = a; }
|
|
1128
|
+
}
|
|
1129
|
+
const filename = getProjectInstructionFile(pifRuntime);
|
|
1130
|
+
process.stdout.write(filename + '\n');
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1106
1134
|
case 'list-todos': {
|
|
1107
1135
|
commands.cmdListTodos(cwd, args[1], raw);
|
|
1108
1136
|
break;
|
|
@@ -1333,12 +1361,16 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1333
1361
|
|
|
1334
1362
|
case 'uat': {
|
|
1335
1363
|
const subcommand = args[1];
|
|
1336
|
-
const uat = require('./lib/uat.cjs');
|
|
1337
1364
|
if (subcommand === 'render-checkpoint') {
|
|
1365
|
+
const uat = require('./lib/uat.cjs');
|
|
1338
1366
|
const options = parseNamedArgs(args, ['file']);
|
|
1339
1367
|
uat.cmdRenderCheckpoint(cwd, options, raw);
|
|
1368
|
+
} else if (subcommand === 'classify-coverage') {
|
|
1369
|
+
const coverage = require('./lib/coverage.cjs');
|
|
1370
|
+
const options = parseNamedArgs(args, ['summary', 'file']);
|
|
1371
|
+
coverage.cmdClassify(cwd, options, raw);
|
|
1340
1372
|
} else {
|
|
1341
|
-
error('Unknown uat subcommand. Available: render-checkpoint', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1373
|
+
error('Unknown uat subcommand. Available: render-checkpoint, classify-coverage', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1342
1374
|
}
|
|
1343
1375
|
break;
|
|
1344
1376
|
}
|
|
@@ -25,6 +25,10 @@
|
|
|
25
25
|
*/
|
|
26
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
27
|
const io = require("./io.cjs");
|
|
28
|
+
// Phase 2 (#1646): route through the Hub per ADR-959 §III(B) line 75.
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
30
|
+
const cjsCommandRouterAdapter = require("./cjs-command-router-adapter.cjs");
|
|
31
|
+
const { routeHubCommandFamily } = cjsCommandRouterAdapter;
|
|
28
32
|
// ─── routeAuditUat ────────────────────────────────────────────────────────────
|
|
29
33
|
function routeAuditUat({ args, cwd, raw, error, _uat }) {
|
|
30
34
|
// Suppress unused-variable warnings for args/error — this command has no
|
|
@@ -33,27 +37,61 @@ function routeAuditUat({ args, cwd, raw, error, _uat }) {
|
|
|
33
37
|
void error;
|
|
34
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
35
39
|
const u = _uat ?? require('./uat.cjs');
|
|
36
|
-
|
|
40
|
+
// Phase 2 (#1646): routes through the Hub for uniform observability and
|
|
41
|
+
// HandlerFailure taxonomy. audit-uat has no subcommands — a synthetic 'run'
|
|
42
|
+
// defaultSubcommand gives the Hub a single-handler manifest. The dispatch is
|
|
43
|
+
// trivial but the observability seam (DispatchEvent, GSD_AUDIT=1 trace) is
|
|
44
|
+
// now consistent with graphify/intel/host routers.
|
|
45
|
+
routeHubCommandFamily({
|
|
46
|
+
family: 'audit-uat',
|
|
47
|
+
args,
|
|
48
|
+
subcommands: ['run'],
|
|
49
|
+
defaultSubcommand: 'run',
|
|
50
|
+
handlers: {
|
|
51
|
+
run: () => u.cmdAuditUat(cwd, raw),
|
|
52
|
+
},
|
|
53
|
+
unknownMessage: (subcommand) => `Unknown audit-uat subcommand: "${subcommand}". audit-uat takes no subcommands.`,
|
|
54
|
+
error,
|
|
55
|
+
cwd,
|
|
56
|
+
raw,
|
|
57
|
+
});
|
|
37
58
|
}
|
|
38
59
|
// ─── routeAuditOpen ──────────────────────────────────────────────────────────
|
|
39
60
|
function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
|
|
40
|
-
// Suppress unused-variable warning for error — audit-open has no subcommand
|
|
41
|
-
// dispatch that would call error(); only flag parsing occurs here.
|
|
42
|
-
void error;
|
|
43
61
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
44
62
|
const a = _audit ?? require('./audit.cjs');
|
|
45
63
|
const c = _core ?? io;
|
|
64
|
+
// Phase 2 (#1646): routes through the Hub for uniform observability.
|
|
65
|
+
// `--json` is a flag, not a subcommand — capture it in the closure and strip
|
|
66
|
+
// it from args before Hub dispatch so it isn't mistaken for a subcommand by
|
|
67
|
+
// the manifest check. The handler then branches on wantJson for the two
|
|
68
|
+
// output shapes (JSON object vs human-readable formatted report).
|
|
46
69
|
const wantJson = args.includes('--json');
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
const hubArgs = wantJson ? args.filter((arg) => arg !== '--json') : args;
|
|
71
|
+
routeHubCommandFamily({
|
|
72
|
+
family: 'audit-open',
|
|
73
|
+
args: hubArgs,
|
|
74
|
+
subcommands: ['run'],
|
|
75
|
+
defaultSubcommand: 'run',
|
|
76
|
+
handlers: {
|
|
77
|
+
run: () => {
|
|
78
|
+
const result = a.auditOpenArtifacts(cwd);
|
|
79
|
+
if (wantJson) {
|
|
80
|
+
// io.output JSON-stringifies its first arg; pass the object directly.
|
|
81
|
+
c.output(result, raw);
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Human-readable report must bypass JSON encoding — use the rawValue
|
|
85
|
+
// form (third arg) which io.output emits verbatim.
|
|
86
|
+
c.output(null, true, a.formatAuditReport(result));
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
unknownMessage: (subcommand) => `Unknown audit-open subcommand: "${subcommand}". audit-open takes no subcommands (use --json for JSON output).`,
|
|
91
|
+
error,
|
|
92
|
+
cwd,
|
|
93
|
+
raw,
|
|
94
|
+
});
|
|
57
95
|
}
|
|
58
96
|
module.exports = {
|
|
59
97
|
routeAuditUat,
|