@opengsd/gsd-core 1.4.3 → 1.5.0-rc.1
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 +1 -1
- package/README.md +3 -3
- package/agents/gsd-executor.md +1 -1
- package/agents/gsd-plan-checker.md +11 -2
- package/agents/gsd-planner.md +11 -7
- package/agents/gsd-verifier.md +3 -3
- package/bin/install.js +825 -149
- package/commands/gsd/autonomous.md +5 -1
- package/commands/gsd/ns-manage.md +8 -1
- package/commands/gsd/ns-project.md +5 -0
- package/commands/gsd/ns-review.md +4 -1
- package/commands/gsd/ns-workflow.md +7 -1
- package/commands/gsd/plan-review-convergence.md +5 -4
- package/commands/gsd/surface.md +12 -5
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +198 -101
- package/gsd-core/bin/lib/audit-command-router.cjs +61 -0
- package/gsd-core/bin/lib/capability-registry.cjs +417 -0
- package/gsd-core/bin/lib/capability-state.cjs +336 -0
- package/gsd-core/bin/lib/cli-exit.cjs +22 -3
- package/gsd-core/bin/lib/config-loader.cjs +716 -0
- package/gsd-core/bin/lib/configuration.cjs +4 -34
- package/gsd-core/bin/lib/core-utils.cjs +198 -0
- package/gsd-core/bin/lib/core.cjs +57 -1817
- package/gsd-core/bin/lib/federated-config.cjs +182 -0
- package/gsd-core/bin/lib/graphify-command-router.cjs +74 -0
- package/gsd-core/bin/lib/init.cjs +58 -12
- package/gsd-core/bin/lib/install-profiles.cjs +157 -3
- package/gsd-core/bin/lib/intel-command-router.cjs +116 -0
- package/gsd-core/bin/lib/io.cjs +167 -0
- package/gsd-core/bin/lib/loop-host-contract.cjs +105 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +460 -0
- package/gsd-core/bin/lib/model-resolver.cjs +426 -0
- package/gsd-core/bin/lib/phase-id.cjs +215 -0
- package/gsd-core/bin/lib/phase-locator.cjs +148 -0
- package/gsd-core/bin/lib/profile-pipeline.cjs +2 -2
- package/gsd-core/bin/lib/roadmap-parser.cjs +443 -0
- package/gsd-core/bin/lib/roadmap.cjs +4 -1
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +96 -9
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +2 -1
- package/gsd-core/bin/lib/runtime-homes.cjs +40 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +1 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +13 -0
- package/gsd-core/bin/lib/state.cjs +114 -2
- package/gsd-core/bin/lib/surface.cjs +66 -14
- package/gsd-core/bin/lib/verify.cjs +1 -1
- package/gsd-core/bin/shared/model-catalog.json +5 -0
- package/gsd-core/bin/shared/runtime-aliases.manifest.json +3 -0
- package/gsd-core/references/planner-reviews.md +5 -2
- package/gsd-core/templates/phase-prompt.md +7 -7
- package/gsd-core/templates/project.md +19 -2
- package/gsd-core/templates/summary-complex.md +1 -0
- package/gsd-core/templates/summary-minimal.md +1 -0
- package/gsd-core/templates/summary-standard.md +1 -0
- package/gsd-core/templates/summary.md +1 -0
- package/gsd-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/gsd-core/workflows/add-backlog.md +1 -1
- package/gsd-core/workflows/add-phase.md +1 -1
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/audit-fix.md +1 -1
- package/gsd-core/workflows/audit-milestone.md +1 -1
- package/gsd-core/workflows/audit-uat.md +1 -1
- package/gsd-core/workflows/autonomous.md +86 -15
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review-fix.md +6 -4
- package/gsd-core/workflows/code-review.md +3 -2
- package/gsd-core/workflows/complete-milestone.md +11 -5
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/gsd-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase.md +8 -1
- package/gsd-core/workflows/do.md +1 -1
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +1 -1
- package/gsd-core/workflows/eval-review.md +4 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +1 -1
- package/gsd-core/workflows/execute-phase.md +8 -1
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/explore.md +1 -1
- package/gsd-core/workflows/extract-learnings.md +1 -1
- package/gsd-core/workflows/forensics.md +1 -1
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +1 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +1 -1
- package/gsd-core/workflows/list-workspaces.md +1 -1
- package/gsd-core/workflows/manager.md +1 -1
- package/gsd-core/workflows/map-codebase.md +1 -1
- package/gsd-core/workflows/milestone-summary.md +1 -1
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +1 -1
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +1 -1
- package/gsd-core/workflows/pause-work.md +1 -1
- package/gsd-core/workflows/plan-milestone-gaps.md +1 -1
- package/gsd-core/workflows/plan-phase.md +27 -2
- package/gsd-core/workflows/plan-review-convergence.md +60 -33
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress.md +1 -1
- package/gsd-core/workflows/quick.md +2 -2
- package/gsd-core/workflows/remove-phase.md +1 -1
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +1 -1
- package/gsd-core/workflows/review.md +1 -1
- package/gsd-core/workflows/scan.md +1 -1
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +1 -1
- package/gsd-core/workflows/settings-integrations.md +1 -1
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +8 -1
- package/gsd-core/workflows/sketch-wrap-up.md +1 -1
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +1 -1
- package/gsd-core/workflows/spike-wrap-up.md +1 -1
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +1 -1
- package/gsd-core/workflows/thread.md +1 -1
- package/gsd-core/workflows/transition.md +1 -1
- package/gsd-core/workflows/ui-phase.md +1 -1
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +8 -1
- package/package.json +7 -2
- package/scripts/base64-scan.sh +1 -1
- package/scripts/changeset/cli.cjs +8 -1
- package/scripts/changeset/lint.cjs +38 -2
- package/scripts/ci-test-scope.cjs +21 -10
- package/scripts/gen-capability-registry.cjs +1808 -0
- package/scripts/gen-loop-host-contract.cjs +471 -0
- package/scripts/lint-regression-test-names.allowlist.json +269 -0
- package/scripts/lint-regression-test-names.cjs +117 -0
- package/scripts/lint-test-file-count.allowlist.json +12 -4
- package/scripts/prompt-injection-scan.sh +4 -4
- package/scripts/run-tests.cjs +79 -16
- package/scripts/secret-scan.sh +3 -3
- package/scripts/run-cross-platform-tests.cjs +0 -67
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:autonomous
|
|
3
3
|
description: Run all remaining phases autonomously — discuss→plan→execute per phase
|
|
4
|
-
argument-hint: "[--from N] [--to N] [--only N] [--interactive]"
|
|
4
|
+
argument-hint: "[--from N] [--to N] [--only N] [--interactive] [--converge]"
|
|
5
5
|
effort: xhigh
|
|
6
6
|
allowed-tools:
|
|
7
7
|
- Read
|
|
@@ -37,6 +37,10 @@ Optional flags:
|
|
|
37
37
|
- `--to N` — stop after phase N completes (halt instead of advancing to next phase).
|
|
38
38
|
- `--only N` — execute only phase N (single-phase mode).
|
|
39
39
|
- `--interactive` — run discuss inline with questions (not auto-answered), then dispatch plan→execute as background agents. Keeps the main context lean while preserving user input on decisions.
|
|
40
|
+
- `--converge` — run each phase's planning step through `gsd-plan-review-convergence` instead of plain `gsd-plan-phase`. Requires `workflow.plan_review_convergence=true`.
|
|
41
|
+
- `--cross-ai` — compatibility alias for `--converge`.
|
|
42
|
+
|
|
43
|
+
When `--converge` or `--cross-ai` is set, reviewer selector flags supported by `gsd-plan-review-convergence` may be passed through: `--codex`, `--gemini`, `--claude`, `--opencode`, `--ollama`, `--lm-studio`, `--llama-cpp`, `--all`, and `--max-cycles N`.
|
|
40
44
|
|
|
41
45
|
Project context, phase list, and state are resolved inside the workflow using init commands (`gsd-tools query init.milestone-op`, `gsd-tools query roadmap.analyze`). No upfront context loading needed.
|
|
42
46
|
</context>
|
|
@@ -5,7 +5,7 @@ argument-hint: ""
|
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Skill
|
|
8
|
-
requires: [config, workspace, workstreams, thread, pause-work, resume-work, update, ship, inbox, pr-branch, undo]
|
|
8
|
+
requires: [config, workspace, workstreams, thread, pause-work, resume-work, update, ship, inbox, pr-branch, undo, cleanup, health, manager, settings, stats, surface, help]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
Route to the appropriate management skill based on the user's intent.
|
|
@@ -25,5 +25,12 @@ Route to the appropriate management skill based on the user's intent.
|
|
|
25
25
|
| Process inbox items | gsd-inbox |
|
|
26
26
|
| Create a clean PR branch | gsd-pr-branch |
|
|
27
27
|
| Undo the last GSD action | gsd-undo |
|
|
28
|
+
| Archive accumulated phase directories | gsd-cleanup |
|
|
29
|
+
| Diagnose planning directory health | gsd-health |
|
|
30
|
+
| Open the interactive command center | gsd-manager |
|
|
31
|
+
| Configure workflow toggles and model profile | gsd-settings |
|
|
32
|
+
| Show project statistics | gsd-stats |
|
|
33
|
+
| Toggle which skills are surfaced | gsd-surface |
|
|
34
|
+
| Show the GSD command guide | gsd-help |
|
|
28
35
|
|
|
29
36
|
Invoke the matched skill directly using the Skill tool.
|
|
@@ -5,6 +5,7 @@ argument-hint: ""
|
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Skill
|
|
8
|
+
requires: [new-project, new-milestone, complete-milestone, audit-milestone, milestone-summary, import, ingest-docs, profile-user, review-backlog]
|
|
8
9
|
---
|
|
9
10
|
|
|
10
11
|
Route to the appropriate project / milestone skill based on the user's intent.
|
|
@@ -18,5 +19,9 @@ inline as part of `gsd-audit-milestone`'s output.
|
|
|
18
19
|
| Complete the current milestone | gsd-complete-milestone |
|
|
19
20
|
| Audit a milestone for issues | gsd-audit-milestone |
|
|
20
21
|
| Summarize milestone status | gsd-milestone-summary |
|
|
22
|
+
| Import an external plan | gsd-import |
|
|
23
|
+
| Bootstrap planning from existing docs | gsd-ingest-docs |
|
|
24
|
+
| Generate a developer profile | gsd-profile-user |
|
|
25
|
+
| Review and promote backlog items | gsd-review-backlog |
|
|
21
26
|
|
|
22
27
|
Invoke the matched skill directly using the Skill tool.
|
|
@@ -5,7 +5,7 @@ argument-hint: ""
|
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Skill
|
|
8
|
-
requires: [code-review, audit-uat, secure-phase, eval-review, ui-review, validate-phase, debug, forensics]
|
|
8
|
+
requires: [code-review, audit-uat, secure-phase, eval-review, ui-review, validate-phase, debug, forensics, audit-fix, review, ui-phase]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
Route to the appropriate quality / review skill based on the user's intent.
|
|
@@ -22,5 +22,8 @@ Route to the appropriate quality / review skill based on the user's intent.
|
|
|
22
22
|
| Validate phase outputs | gsd-validate-phase |
|
|
23
23
|
| Debug a failing feature or error | gsd-debug |
|
|
24
24
|
| Forensic investigation of a broken system | gsd-forensics |
|
|
25
|
+
| Autonomous audit-to-fix pipeline | gsd-audit-fix |
|
|
26
|
+
| Cross-AI peer review of plans | gsd-review |
|
|
27
|
+
| Generate a UI design contract | gsd-ui-phase |
|
|
25
28
|
|
|
26
29
|
Invoke the matched skill directly using the Skill tool.
|
|
@@ -5,7 +5,7 @@ argument-hint: ""
|
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Skill
|
|
8
|
-
requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, ultraplan-phase, plan-review-convergence]
|
|
8
|
+
requires: [discuss-phase, spec-phase, plan-phase, execute-phase, verify-work, phase, progress, ultraplan-phase, plan-review-convergence, add-tests, ai-integration-phase, autonomous, fast, mvp-phase, quick]
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
Route to the appropriate phase-pipeline skill based on the user's intent.
|
|
@@ -24,5 +24,11 @@ absorbs the former next/do commands.
|
|
|
24
24
|
| Advance to the next logical step | gsd-progress |
|
|
25
25
|
| Offload planning to the ultraplan cloud | gsd-ultraplan-phase |
|
|
26
26
|
| Cross-AI plan review convergence loop | gsd-plan-review-convergence |
|
|
27
|
+
| Generate tests for a completed phase | gsd-add-tests |
|
|
28
|
+
| Design an AI-integration phase | gsd-ai-integration-phase |
|
|
29
|
+
| Run all remaining phases autonomously | gsd-autonomous |
|
|
30
|
+
| Execute a trivial task inline | gsd-fast |
|
|
31
|
+
| Plan a phase as a vertical MVP slice | gsd-mvp-phase |
|
|
32
|
+
| Execute a quick task with GSD guarantees | gsd-quick |
|
|
27
33
|
|
|
28
34
|
Invoke the matched skill directly using the Skill tool.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:plan-review-convergence
|
|
3
|
-
description: "Cross-AI plan convergence
|
|
3
|
+
description: "Cross-AI plan convergence - replan until review concerns are resolved."
|
|
4
4
|
argument-hint: "<phase> [--codex] [--gemini] [--claude] [--opencode] [--ollama] [--lm-studio] [--llama-cpp] [--text] [--ws <name>] [--all] [--max-cycles N]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
@@ -9,19 +9,20 @@ allowed-tools:
|
|
|
9
9
|
- Glob
|
|
10
10
|
- Grep
|
|
11
11
|
- Agent
|
|
12
|
+
- Skill
|
|
12
13
|
- AskUserQuestion
|
|
13
14
|
requires: [phase, review]
|
|
14
15
|
---
|
|
15
16
|
|
|
16
17
|
<objective>
|
|
17
18
|
Cross-AI plan convergence loop — an outer revision gate around gsd-review and gsd-planner.
|
|
18
|
-
Repeatedly: review plans with external AI CLIs → if HIGH concerns
|
|
19
|
+
Repeatedly: review plans with external AI CLIs → if HIGH or actionable non-HIGH concerns remain → replan with --reviews feedback → re-review. Stops when no unresolved HIGH concerns or actionable MEDIUM/LOW findings remain outside PLAN.md, or when max cycles is reached.
|
|
19
20
|
|
|
20
|
-
**Flow:** Skill("gsd-plan-phase") → Agent→Skill("gsd-review") → check
|
|
21
|
+
**Flow:** Skill("gsd-plan-phase") → Agent→Skill("gsd-review") → check unresolved HIGH + actionable non-HIGH → Skill("gsd-plan-phase --reviews") → Agent→Skill("gsd-review") → ... → Converge or escalate
|
|
21
22
|
|
|
22
23
|
Replaces gsd-plan-phase's internal gsd-plan-checker with external AI reviewers (codex, gemini, etc.). Plan-phase runs **inline** (bare Skill at depth 0) so it can spawn gsd-planner/gsd-plan-checker at depth 1. Review runs inside an isolated Agent (gsd-review is a Bash leaf — no sub-agents needed). Orchestrator only does loop control.
|
|
23
24
|
|
|
24
|
-
**Orchestrator role:** Parse arguments, validate phase, run plan-phase inline (Skill at depth 0), spawn an Agent for gsd-review, check
|
|
25
|
+
**Orchestrator role:** Parse arguments, validate phase, run plan-phase inline (Skill at depth 0), spawn an Agent for gsd-review, check unresolved HIGH and actionable non-HIGH counts, stall detection, escalation gate.
|
|
25
26
|
</objective>
|
|
26
27
|
|
|
27
28
|
<execution_context>
|
package/commands/gsd/surface.md
CHANGED
|
@@ -36,8 +36,12 @@ Parse the first token of $ARGUMENTS:
|
|
|
36
36
|
|
|
37
37
|
## list / status
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
`gsd-core/bin/lib/surface.cjs`.
|
|
39
|
+
Load the capability registry and call `listSurface(runtimeConfigDir, manifest, CLUSTERS, registry)` from
|
|
40
|
+
`gsd-core/bin/lib/surface.cjs`. The registry is loaded via:
|
|
41
|
+
```js
|
|
42
|
+
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
43
|
+
```
|
|
44
|
+
Display:
|
|
41
45
|
|
|
42
46
|
```
|
|
43
47
|
Enabled (N skills, ~T tokens):
|
|
@@ -67,8 +71,9 @@ Install profile: standard (from .gsd-profile)
|
|
|
67
71
|
3. `writeSurface(runtimeConfigDir, surfaceState)`.
|
|
68
72
|
4. Resolve and re-apply:
|
|
69
73
|
```js
|
|
74
|
+
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
70
75
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
71
|
-
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
|
|
76
|
+
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
72
77
|
```
|
|
73
78
|
5. Confirm: "Surface updated to profile `<name>`. N skills enabled."
|
|
74
79
|
|
|
@@ -84,8 +89,9 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
84
89
|
3. Add cluster to `surfaceState.disabledClusters` (deduplicate).
|
|
85
90
|
4. `writeSurface` → resolve layout → `applySurface`:
|
|
86
91
|
```js
|
|
92
|
+
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
87
93
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
88
|
-
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
|
|
94
|
+
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
89
95
|
```
|
|
90
96
|
5. Confirm: "Disabled cluster `<cluster>`. N skills removed from surface."
|
|
91
97
|
|
|
@@ -97,8 +103,9 @@ Valid cluster names: `core_loop`, `audit_review`, `milestone`, `research_ideate`
|
|
|
97
103
|
2. Remove cluster from `surfaceState.disabledClusters`.
|
|
98
104
|
3. `writeSurface` → resolve layout → `applySurface`:
|
|
99
105
|
```js
|
|
106
|
+
const registry = require('gsd-core/bin/lib/capability-registry.cjs');
|
|
100
107
|
const layout = resolveRuntimeArtifactLayout(runtime, runtimeConfigDir, scope);
|
|
101
|
-
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS);
|
|
108
|
+
applySurface(runtimeConfigDir, layout, manifest, CLUSTERS, registry);
|
|
102
109
|
```
|
|
103
110
|
4. Confirm: "Enabled cluster `<cluster>`. N skills added back to surface."
|
|
104
111
|
|
package/gemini-extension.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gsd-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0-rc.1",
|
|
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
|
}
|
|
@@ -163,6 +163,17 @@
|
|
|
163
163
|
* learnings prune --older-than <dur> Remove entries older than duration (e.g. 90d)
|
|
164
164
|
* learnings delete <id> Delete a learning by ID
|
|
165
165
|
*
|
|
166
|
+
* Loop Extension Point Queries (ADR-857 phase 3c):
|
|
167
|
+
* loop render-hooks <point> Resolve + render active Capability hooks at a loop point
|
|
168
|
+
* Returns JSON envelope { point, activeHooks, rendered }
|
|
169
|
+
* Valid points: discuss:pre/post, plan:pre/post,
|
|
170
|
+
* execute:pre/wave:pre/wave:post/post, verify:pre/post, ship:pre/post
|
|
171
|
+
*
|
|
172
|
+
* Capability State (ADR-857 phase 4b):
|
|
173
|
+
* capability state [--config-dir <path>] Resolve per-capability install/surface/hook-activation state
|
|
174
|
+
* Returns JSON envelope { runtimeConfigDir, capabilities[] }
|
|
175
|
+
* --config-dir: runtime config dir (default: auto-detect current runtime)
|
|
176
|
+
*
|
|
166
177
|
* GSD-2 Migration:
|
|
167
178
|
* from-gsd2 [--path <dir>] [--force] [--dry-run]
|
|
168
179
|
* Import a GSD-2 (.gsd/) project back to GSD v1 (.planning/) format
|
|
@@ -201,6 +212,8 @@ const { routeVerifyCommand } = require('./lib/verify-command-router.cjs');
|
|
|
201
212
|
const { routeVerificationCommand } = require('./lib/verification-command-router.cjs');
|
|
202
213
|
const verification = require('./lib/verification.cjs');
|
|
203
214
|
const { routeInitCommand } = require('./lib/init-command-router.cjs');
|
|
215
|
+
const loopResolver = require('./lib/loop-resolver.cjs');
|
|
216
|
+
const capabilityState = require('./lib/capability-state.cjs');
|
|
204
217
|
const { routePhaseCommand } = require('./lib/phase-command-router.cjs');
|
|
205
218
|
const { routePhasesCommand } = require('./lib/phases-command-router.cjs');
|
|
206
219
|
const { routeValidateCommand } = require('./lib/validate-command-router.cjs');
|
|
@@ -243,6 +256,122 @@ function _dispatchNonFamily({ registryCommand, registryArgs, legacyCommand, lega
|
|
|
243
256
|
return false;
|
|
244
257
|
}
|
|
245
258
|
|
|
259
|
+
// ─── ADR-959: Capability Command Dispatch ─────────────────────────────────────
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Dispatch a command via the capability registry's commandFamilies index.
|
|
263
|
+
*
|
|
264
|
+
* Consulted in the `default` case of `runCommand` BEFORE the unknown-command
|
|
265
|
+
* error is emitted. Returns:
|
|
266
|
+
* true — command was "consumed" (found in registry, or a dispatch error was
|
|
267
|
+
* emitted); "Unknown command" is suppressed in all consumed cases.
|
|
268
|
+
* false — command not found in the registry (including prototype-pollution
|
|
269
|
+
* guard hits and missing/empty commandFamilies); caller falls through
|
|
270
|
+
* to the existing unknown-command error path.
|
|
271
|
+
* Behavior-preserving when commandFamilies is empty ({}).
|
|
272
|
+
*
|
|
273
|
+
* Injectable for tests:
|
|
274
|
+
* - `registry` defaults to require('./lib/capability-registry.cjs')
|
|
275
|
+
* - `requireModule` defaults to a confinement-checked loader that resolves the
|
|
276
|
+
* module path relative to bin/lib/ and asserts it stays within that directory
|
|
277
|
+
* before requiring — defense-in-depth against corrupted/hand-edited registry entries.
|
|
278
|
+
*
|
|
279
|
+
* @param {object} opts
|
|
280
|
+
* @param {string} opts.command The command name (top-level gsd-tools command)
|
|
281
|
+
* @param {string[]} opts.args Remaining args passed to the router
|
|
282
|
+
* @param {string} opts.cwd Project working directory
|
|
283
|
+
* @param {boolean} opts.raw Raw output mode flag
|
|
284
|
+
* @param {Function} opts.error Error reporter (core.error)
|
|
285
|
+
* @param {object} [opts.registry] Injectable registry (for tests)
|
|
286
|
+
* @param {Function} [opts.requireModule] Injectable module loader (for tests)
|
|
287
|
+
* @returns {boolean} true if the command was dispatched, false otherwise
|
|
288
|
+
*/
|
|
289
|
+
function dispatchCapabilityCommand({ command, args, cwd, raw, error, registry, requireModule }) {
|
|
290
|
+
// Prototype-pollution guard: reject reserved property names as command keys
|
|
291
|
+
if (command === '__proto__' || command === 'constructor' || command === 'prototype') {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Resolve defaults (injectable for tests)
|
|
296
|
+
const reg = registry !== undefined ? registry : require('./lib/capability-registry.cjs');
|
|
297
|
+
|
|
298
|
+
// Default requireModule: confined to bin/lib/ — validate the module name is a
|
|
299
|
+
// safe bare .cjs basename (no path separators, no directory traversal), then
|
|
300
|
+
// resolve and assert confinement, then require the RESOLVED absolute path so
|
|
301
|
+
// the checked representation and the required representation are identical.
|
|
302
|
+
const libDir = path.join(__dirname, 'lib');
|
|
303
|
+
const defaultRequireModule = function (m) {
|
|
304
|
+
// Step 1: validate m is a bare .cjs basename — same conservative pattern the
|
|
305
|
+
// generator uses. Rejects any value with path separators (/, \, ..) or
|
|
306
|
+
// missing the .cjs extension before we even touch the filesystem.
|
|
307
|
+
if (typeof m !== 'string' || !/^[A-Za-z0-9._-]+\.cjs$/.test(m)) {
|
|
308
|
+
throw new Error('capability module must be a bare .cjs basename: ' + JSON.stringify(m));
|
|
309
|
+
}
|
|
310
|
+
// Step 2: confinement check — belt-and-suspenders even after the basename
|
|
311
|
+
// validation above. Resolved path must be inside libDir (not equal to it,
|
|
312
|
+
// and must start with libDir + sep so "libDir-suffix" can't sneak through).
|
|
313
|
+
const resolved = path.resolve(libDir, m);
|
|
314
|
+
if (resolved === libDir || !resolved.startsWith(libDir + path.sep)) {
|
|
315
|
+
throw new Error('capability module path escapes bin/lib/: ' + JSON.stringify(m));
|
|
316
|
+
}
|
|
317
|
+
// Step 3: require the resolved absolute path — the SAME representation that
|
|
318
|
+
// was checked above, not the concatenated './lib/' + m string.
|
|
319
|
+
return require(resolved);
|
|
320
|
+
};
|
|
321
|
+
const loadModule = requireModule !== undefined ? requireModule : defaultRequireModule;
|
|
322
|
+
|
|
323
|
+
// Look up the command family in the registry
|
|
324
|
+
const families = reg && reg.commandFamilies;
|
|
325
|
+
if (!families || typeof families !== 'object') return false;
|
|
326
|
+
|
|
327
|
+
const entry = families[command];
|
|
328
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
329
|
+
|
|
330
|
+
// Resolve and call the router
|
|
331
|
+
let mod;
|
|
332
|
+
try {
|
|
333
|
+
mod = loadModule(entry.module);
|
|
334
|
+
} catch (_) {
|
|
335
|
+
// Module not found, load error, or confinement violation — surface a
|
|
336
|
+
// diagnostic and return true (consumed) so "Unknown command" is suppressed.
|
|
337
|
+
error('capability command "' + command + '" module "' + entry.module + '" failed to load');
|
|
338
|
+
return true; // consumed — don't emit "Unknown command"
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Own-property guard: prevent invoking inherited prototype methods
|
|
342
|
+
// (constructor, toString, hasOwnProperty, etc.) as a router when the registry
|
|
343
|
+
// entry names one of those. Must come before the typeof check.
|
|
344
|
+
if (!mod || !Object.prototype.hasOwnProperty.call(mod, entry.router)) {
|
|
345
|
+
error('capability command "' + command + '" router "' + entry.router + '" is not an own export of module "' + entry.module + '"');
|
|
346
|
+
return true; // consumed — don't emit "Unknown command"
|
|
347
|
+
}
|
|
348
|
+
const fn = mod[entry.router];
|
|
349
|
+
if (typeof fn !== 'function') {
|
|
350
|
+
// Router export not found — surface a diagnostic and return true (consumed)
|
|
351
|
+
// so "Unknown command" is suppressed.
|
|
352
|
+
error('capability command "' + command + '" router "' + entry.router + '" is not a function in module "' + entry.module + '"');
|
|
353
|
+
return true; // consumed — don't emit "Unknown command"
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
let _result;
|
|
357
|
+
try {
|
|
358
|
+
_result = fn({ args, cwd, raw, error });
|
|
359
|
+
} catch (e) {
|
|
360
|
+
if (e instanceof ExitError) throw e; // intentional structured error from the router (honors --json-errors) — propagate untouched
|
|
361
|
+
error(
|
|
362
|
+
'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" threw: ' + (e && e.message ? e.message : String(e)),
|
|
363
|
+
ERROR_REASON.SDK_FAIL_FAST,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (_result && typeof _result.then === 'function') {
|
|
367
|
+
error(
|
|
368
|
+
'capability command "' + command + '" router "' + entry.router + '" in module "' + entry.module + '" must be synchronous (returned a Promise); async capability routers are not supported.',
|
|
369
|
+
ERROR_REASON.SDK_FAIL_FAST,
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
return true;
|
|
373
|
+
}
|
|
374
|
+
|
|
246
375
|
// ─── Arg parsing helpers ──────────────────────────────────────────────────────
|
|
247
376
|
|
|
248
377
|
// ─── CLI Router ───────────────────────────────────────────────────────────────
|
|
@@ -379,7 +508,7 @@ async function main() {
|
|
|
379
508
|
'current-timestamp, detect-custom-files, docs-init, effort, extract-messages, find-phase, ' +
|
|
380
509
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
381
510
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
382
|
-
'classify-confidence, learnings, list-todos, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
511
|
+
'capability, classify-confidence, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
383
512
|
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
384
513
|
'task, template, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
385
514
|
'Global flags:\n' +
|
|
@@ -420,6 +549,11 @@ async function main() {
|
|
|
420
549
|
// Multi-repo guard: resolve project root for commands that read/write .planning/.
|
|
421
550
|
// Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary
|
|
422
551
|
// filesystem traversal on every invocation.
|
|
552
|
+
// 'loop' and 'capability' are intentionally NOT in SKIP_ROOT_RESOLUTION.
|
|
553
|
+
// Both are registry/config queries that resolve activation via
|
|
554
|
+
// .planning/config.json; they need the project root (cwd) for correct
|
|
555
|
+
// `when` key resolution. If one is ever moved to SKIP_ROOT_RESOLUTION,
|
|
556
|
+
// move the other at the same time (keep them consistent).
|
|
423
557
|
const SKIP_ROOT_RESOLUTION = new Set([
|
|
424
558
|
'generate-slug', 'current-timestamp', 'verify-path-exists',
|
|
425
559
|
'verify-summary', 'template', 'frontmatter', 'detect-custom-files',
|
|
@@ -1021,7 +1155,8 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1021
1155
|
if (subcommand === 'complete') {
|
|
1022
1156
|
const milestoneName = parseMultiwordArg(args, 'name');
|
|
1023
1157
|
const archivePhases = args.includes('--archive-phases');
|
|
1024
|
-
|
|
1158
|
+
const force = args.includes('--force');
|
|
1159
|
+
milestone.cmdMilestoneComplete(cwd, args[2], { name: milestoneName, archivePhases, force }, raw);
|
|
1025
1160
|
} else {
|
|
1026
1161
|
error('Unknown milestone subcommand. Available: complete', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1027
1162
|
}
|
|
@@ -1046,27 +1181,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1046
1181
|
break;
|
|
1047
1182
|
}
|
|
1048
1183
|
|
|
1049
|
-
case 'audit-uat': {
|
|
1050
|
-
const uat = require('./lib/uat.cjs');
|
|
1051
|
-
uat.cmdAuditUat(cwd, raw);
|
|
1052
|
-
break;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
case 'audit-open': {
|
|
1056
|
-
const { auditOpenArtifacts, formatAuditReport } = require('./lib/audit.cjs');
|
|
1057
|
-
const wantJson = args.includes('--json');
|
|
1058
|
-
const result = auditOpenArtifacts(cwd);
|
|
1059
|
-
if (wantJson) {
|
|
1060
|
-
// core.output JSON-stringifies its first arg; pass the object directly.
|
|
1061
|
-
core.output(result, raw);
|
|
1062
|
-
} else {
|
|
1063
|
-
// Human-readable report must bypass JSON encoding — use the rawValue
|
|
1064
|
-
// form (third arg) which core.output emits verbatim.
|
|
1065
|
-
core.output(null, true, formatAuditReport(result));
|
|
1066
|
-
}
|
|
1067
|
-
break;
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
1184
|
case 'uat': {
|
|
1071
1185
|
const subcommand = args[1];
|
|
1072
1186
|
const uat = require('./lib/uat.cjs');
|
|
@@ -1118,6 +1232,50 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1118
1232
|
break;
|
|
1119
1233
|
}
|
|
1120
1234
|
|
|
1235
|
+
case 'loop': {
|
|
1236
|
+
// loop render-hooks <point>
|
|
1237
|
+
const loopSubcommand = args[1];
|
|
1238
|
+
if (loopSubcommand === 'render-hooks') {
|
|
1239
|
+
loopResolver.cmdLoopRenderHooks(cwd, args[2], raw, {});
|
|
1240
|
+
} else {
|
|
1241
|
+
error(
|
|
1242
|
+
`Unknown loop subcommand: ${loopSubcommand}. Available: render-hooks`,
|
|
1243
|
+
core.ERROR_REASON ? core.ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
case 'capability': {
|
|
1250
|
+
// capability state [--config-dir <path>]
|
|
1251
|
+
// Root resolution: 'capability' is NOT in SKIP_ROOT_RESOLUTION for the
|
|
1252
|
+
// same reason 'loop' is not: both are registry/config queries that need
|
|
1253
|
+
// the project root (cwd) for .planning/config.json activation resolution.
|
|
1254
|
+
// If 'loop' were ever added to SKIP_ROOT_RESOLUTION, 'capability' should
|
|
1255
|
+
// be added at the same time to keep them consistent.
|
|
1256
|
+
const capSubcommand = args[1];
|
|
1257
|
+
if (capSubcommand === 'state') {
|
|
1258
|
+
const configDirIdx = args.indexOf('--config-dir');
|
|
1259
|
+
let configDir = null;
|
|
1260
|
+
if (configDirIdx !== -1) {
|
|
1261
|
+
const configDirVal = args[configDirIdx + 1];
|
|
1262
|
+
// Validate that --config-dir has a following non-flag value.
|
|
1263
|
+
if (!configDirVal || configDirVal.startsWith('--')) {
|
|
1264
|
+
error('Missing value for --config-dir', core.ERROR_REASON ? core.ERROR_REASON.USAGE : undefined);
|
|
1265
|
+
}
|
|
1266
|
+
configDir = configDirVal;
|
|
1267
|
+
}
|
|
1268
|
+
const resolvedConfigDir = configDir ? path.resolve(configDir) : null;
|
|
1269
|
+
capabilityState.cmdCapabilityState(cwd, resolvedConfigDir, raw, {});
|
|
1270
|
+
} else {
|
|
1271
|
+
error(
|
|
1272
|
+
`Unknown capability subcommand: ${capSubcommand}. Available: state`,
|
|
1273
|
+
core.ERROR_REASON ? core.ERROR_REASON.SDK_UNKNOWN_COMMAND : undefined,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
break;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1121
1279
|
case 'phase-plan-index': {
|
|
1122
1280
|
phase.cmdPhasePlanIndex(cwd, args[1], raw);
|
|
1123
1281
|
break;
|
|
@@ -1279,83 +1437,6 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1279
1437
|
break;
|
|
1280
1438
|
}
|
|
1281
1439
|
|
|
1282
|
-
// ─── Intel ────────────────────────────────────────────────────────────
|
|
1283
|
-
|
|
1284
|
-
case 'intel': {
|
|
1285
|
-
const intel = require('./lib/intel.cjs');
|
|
1286
|
-
const subcommand = args[1];
|
|
1287
|
-
if (subcommand === 'query') {
|
|
1288
|
-
const term = args[2];
|
|
1289
|
-
if (!term) error('Usage: gsd-tools intel query <term>', ERROR_REASON.USAGE);
|
|
1290
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1291
|
-
core.output(intel.intelQuery(term, planningDir), raw);
|
|
1292
|
-
} else if (subcommand === 'status') {
|
|
1293
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1294
|
-
const status = intel.intelStatus(planningDir);
|
|
1295
|
-
if (!raw && status.files) {
|
|
1296
|
-
for (const file of Object.values(status.files)) {
|
|
1297
|
-
if (file.updated_at) {
|
|
1298
|
-
file.updated_at = core.timeAgo(new Date(file.updated_at));
|
|
1299
|
-
}
|
|
1300
|
-
}
|
|
1301
|
-
}
|
|
1302
|
-
core.output(status, raw);
|
|
1303
|
-
} else if (subcommand === 'diff') {
|
|
1304
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1305
|
-
core.output(intel.intelDiff(planningDir), raw);
|
|
1306
|
-
} else if (subcommand === 'snapshot') {
|
|
1307
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1308
|
-
core.output(intel.intelSnapshot(planningDir), raw);
|
|
1309
|
-
} else if (subcommand === 'patch-meta') {
|
|
1310
|
-
const filePath = args[2];
|
|
1311
|
-
if (!filePath) error('Usage: gsd-tools intel patch-meta <file-path>', ERROR_REASON.USAGE);
|
|
1312
|
-
core.output(intel.intelPatchMeta(path.resolve(cwd, filePath)), raw);
|
|
1313
|
-
} else if (subcommand === 'validate') {
|
|
1314
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1315
|
-
core.output(intel.intelValidate(planningDir), raw);
|
|
1316
|
-
} else if (subcommand === 'extract-exports') {
|
|
1317
|
-
const filePath = args[2];
|
|
1318
|
-
if (!filePath) error('Usage: gsd-tools intel extract-exports <file-path>', ERROR_REASON.USAGE);
|
|
1319
|
-
core.output(intel.intelExtractExports(path.resolve(cwd, filePath)), raw);
|
|
1320
|
-
} else if (subcommand === 'update') {
|
|
1321
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1322
|
-
core.output(intel.intelUpdate(planningDir), raw);
|
|
1323
|
-
} else if (subcommand === 'api-surface') {
|
|
1324
|
-
const planningDir = path.join(cwd, '.planning');
|
|
1325
|
-
core.output(intel.intelApiSurface(planningDir), raw);
|
|
1326
|
-
} else {
|
|
1327
|
-
error('Unknown intel subcommand. Available: query, status, update, diff, snapshot, patch-meta, validate, extract-exports, api-surface', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1328
|
-
}
|
|
1329
|
-
break;
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
// ─── Graphify ──────────────────────────────────────────────────────────
|
|
1333
|
-
|
|
1334
|
-
case 'graphify': {
|
|
1335
|
-
const graphify = require('./lib/graphify.cjs');
|
|
1336
|
-
const subcommand = args[1];
|
|
1337
|
-
if (subcommand === 'query') {
|
|
1338
|
-
const term = args[2];
|
|
1339
|
-
if (!term) error('Usage: gsd-tools graphify query <term>', ERROR_REASON.USAGE);
|
|
1340
|
-
const budgetIdx = args.indexOf('--budget');
|
|
1341
|
-
const budget = budgetIdx !== -1 ? parseInt(args[budgetIdx + 1], 10) : null;
|
|
1342
|
-
core.output(graphify.graphifyQuery(cwd, term, { budget }), raw);
|
|
1343
|
-
} else if (subcommand === 'status') {
|
|
1344
|
-
core.output(graphify.graphifyStatus(cwd), raw);
|
|
1345
|
-
} else if (subcommand === 'diff') {
|
|
1346
|
-
core.output(graphify.graphifyDiff(cwd), raw);
|
|
1347
|
-
} else if (subcommand === 'build') {
|
|
1348
|
-
if (args[2] === 'snapshot') {
|
|
1349
|
-
core.output(graphify.writeSnapshot(cwd), raw);
|
|
1350
|
-
} else {
|
|
1351
|
-
core.output(graphify.graphifyBuild(cwd), raw);
|
|
1352
|
-
}
|
|
1353
|
-
} else {
|
|
1354
|
-
error('Unknown graphify subcommand. Available: build, query, status, diff', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
1355
|
-
}
|
|
1356
|
-
break;
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
1440
|
// ─── Documentation ────────────────────────────────────────────────────
|
|
1360
1441
|
|
|
1361
1442
|
case 'docs-init': {
|
|
@@ -1904,6 +1985,14 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1904
1985
|
}
|
|
1905
1986
|
|
|
1906
1987
|
default: {
|
|
1988
|
+
// ADR-959: try capability-registry dispatch before emitting the unknown-command error.
|
|
1989
|
+
// An unmigrated command still hits its hardcoded `case` above — untouched.
|
|
1990
|
+
// A migrated command's `case` is removed at cutover, so it reaches here and
|
|
1991
|
+
// dispatchCapabilityCommand routes it to the capability's registered router.
|
|
1992
|
+
// commandFamilies now includes migrated capabilities (e.g. graphify → graphify-command-router.cjs);
|
|
1993
|
+
// this returns true when a registered capability owns the command, false otherwise.
|
|
1994
|
+
if (dispatchCapabilityCommand({ command, args, cwd, raw, error })) break;
|
|
1995
|
+
|
|
1907
1996
|
// #3243: if the caller passed a dotted form (e.g. "foo.bar"), the shim
|
|
1908
1997
|
// above split it so `command` here is the head ("foo"). Use
|
|
1909
1998
|
// originalCommand to reconstruct the original dotted form and suggest
|
|
@@ -1925,4 +2014,12 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1925
2014
|
}
|
|
1926
2015
|
}
|
|
1927
2016
|
|
|
1928
|
-
|
|
2017
|
+
// ─── CLI entry point ──────────────────────────────────────────────────────────
|
|
2018
|
+
if (require.main === module) {
|
|
2019
|
+
runMain(main);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
// ─── Exports (for tests) ──────────────────────────────────────────────────────
|
|
2023
|
+
// ADR-959: export dispatchCapabilityCommand so tests can exercise it with
|
|
2024
|
+
// synthetic registry + requireModule injections.
|
|
2025
|
+
module.exports = { dispatchCapabilityCommand };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Audit command routers — CLI dispatchers for `gsd-tools audit-uat` and
|
|
4
|
+
* `gsd-tools audit-open`.
|
|
5
|
+
*
|
|
6
|
+
* ADR-959 (phase 4d-impl-3): audit command family cutover.
|
|
7
|
+
* Extracted from the hardcoded `case 'audit-uat':` and `case 'audit-open':`
|
|
8
|
+
* arms in gsd-tools.cjs. Behaviour is preserved byte-for-behaviour from the
|
|
9
|
+
* prior inline cases; the dispatch path now flows:
|
|
10
|
+
* default → dispatchCapabilityCommand →
|
|
11
|
+
* require(audit-command-router.cjs) → routeAuditUat | routeAuditOpen.
|
|
12
|
+
*
|
|
13
|
+
* Router signatures: { args, cwd, raw, error } — identical to the existing
|
|
14
|
+
* host routers. No new handler/arg convention; the capability registry
|
|
15
|
+
* discovers these routers by name.
|
|
16
|
+
*
|
|
17
|
+
* Test seam: pass `_uat` / `_audit` / `_core` in the options object to inject
|
|
18
|
+
* recording mocks instead of the real modules. The `_`-prefix follows the
|
|
19
|
+
* repo's established seam convention (see graphify-command-router.cts).
|
|
20
|
+
* Production callers omit them.
|
|
21
|
+
*
|
|
22
|
+
* Lazy requires: uat.cjs and audit.cjs are required INSIDE each route function
|
|
23
|
+
* so the unneeded module is never loaded (preserves equivalence with the old
|
|
24
|
+
* inline case arms which each required only their own module).
|
|
25
|
+
*/
|
|
26
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
27
|
+
const core = require("./core.cjs");
|
|
28
|
+
// ─── routeAuditUat ────────────────────────────────────────────────────────────
|
|
29
|
+
function routeAuditUat({ args, cwd, raw, error, _uat }) {
|
|
30
|
+
// Suppress unused-variable warnings for args/error — this command has no
|
|
31
|
+
// subcommands and passes raw through directly to the uat module.
|
|
32
|
+
void args;
|
|
33
|
+
void error;
|
|
34
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
35
|
+
const u = _uat ?? require('./uat.cjs');
|
|
36
|
+
u.cmdAuditUat(cwd, raw);
|
|
37
|
+
}
|
|
38
|
+
// ─── routeAuditOpen ──────────────────────────────────────────────────────────
|
|
39
|
+
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
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-unsafe-assignment
|
|
44
|
+
const a = _audit ?? require('./audit.cjs');
|
|
45
|
+
const c = _core ?? core;
|
|
46
|
+
const wantJson = args.includes('--json');
|
|
47
|
+
const result = a.auditOpenArtifacts(cwd);
|
|
48
|
+
if (wantJson) {
|
|
49
|
+
// core.output JSON-stringifies its first arg; pass the object directly.
|
|
50
|
+
c.output(result, raw);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
// Human-readable report must bypass JSON encoding — use the rawValue
|
|
54
|
+
// form (third arg) which core.output emits verbatim.
|
|
55
|
+
c.output(null, true, a.formatAuditReport(result));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
module.exports = {
|
|
59
|
+
routeAuditUat,
|
|
60
|
+
routeAuditOpen,
|
|
61
|
+
};
|