@opengsd/gsd-core 1.6.0-rc.1 → 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-roadmapper.md +6 -0
- package/agents/gsd-security-auditor.md +37 -18
- package/bin/install.js +243 -345
- package/commands/gsd/capture.md +5 -1
- package/gemini-extension.json +1 -1
- package/gsd-core/bin/gsd-tools.cjs +53 -6
- package/gsd-core/bin/lib/adr-parser.cjs +21 -6
- 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/commands.cjs +247 -0
- package/gsd-core/bin/lib/config-loader.cjs +6 -0
- package/gsd-core/bin/lib/config-schema.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +70 -21
- package/gsd-core/bin/lib/coverage.cjs +464 -0
- package/gsd-core/bin/lib/frontmatter.cjs +7 -3
- 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/phase-id.cjs +25 -11
- package/gsd-core/bin/lib/phase.cjs +4 -4
- package/gsd-core/bin/lib/planning-workspace.cjs +157 -13
- package/gsd-core/bin/lib/probe-core.cjs +7 -0
- package/gsd-core/bin/lib/profile-output.cjs +18 -6
- package/gsd-core/bin/lib/prohibition-enforcement.cjs +59 -26
- package/gsd-core/bin/lib/roadmap-command-router.cjs +16 -3
- package/gsd-core/bin/lib/roadmap-parser.cjs +29 -8
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -17
- package/gsd-core/bin/lib/roadmap.cjs +5 -2
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +473 -16
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +77 -0
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +22 -31
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +15 -0
- package/gsd-core/bin/lib/runtime-name-policy.cjs +89 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +55 -1
- package/gsd-core/bin/lib/state.cjs +364 -52
- package/gsd-core/bin/lib/surface.cjs +47 -22
- package/gsd-core/bin/lib/update-context.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +5 -2
- package/gsd-core/bin/lib/verify.cjs +11 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +202 -0
- package/gsd-core/bin/shared/config-defaults.manifest.json +2 -1
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/context-budget.md +8 -8
- package/gsd-core/references/execute-phase-context-guard.md +16 -0
- package/gsd-core/references/planner-guidance.md +66 -0
- package/gsd-core/references/planning-config.md +3 -2
- package/gsd-core/references/prohibition-probe.md +15 -9
- 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/autonomous.md +33 -33
- package/gsd-core/workflows/diagnose-issues.md +6 -1
- package/gsd-core/workflows/execute-phase.md +8 -6
- package/gsd-core/workflows/execute-plan.md +5 -0
- package/gsd-core/workflows/help/modes/full.md +10 -0
- package/gsd-core/workflows/list-seeds.md +63 -0
- package/gsd-core/workflows/manager.md +37 -37
- package/gsd-core/workflows/new-project.md +4 -4
- package/gsd-core/workflows/plan-phase.md +15 -0
- package/gsd-core/workflows/pr-branch.md +156 -0
- package/gsd-core/workflows/quick.md +6 -1
- package/gsd-core/workflows/review.md +10 -2
- package/gsd-core/workflows/secure-phase.md +13 -7
- package/gsd-core/workflows/spec-phase.md +8 -3
- package/gsd-core/workflows/verify-phase.md +2 -2
- package/gsd-core/workflows/verify-work.md +30 -1
- package/package.json +7 -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 +5 -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
package/commands/gsd/capture.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsd:capture
|
|
3
3
|
description: Capture ideas, tasks, notes, and seeds to their destination
|
|
4
|
-
argument-hint: "[--note | --backlog | --seed | --list] [text]"
|
|
4
|
+
argument-hint: "[--note | --backlog | --seed | --list | --list-seeds] [text]"
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -21,6 +21,7 @@ Mode routing:
|
|
|
21
21
|
- **--backlog**: Add an idea to the backlog parking lot (999.x numbering) → add-backlog workflow
|
|
22
22
|
- **--seed**: Capture a forward-looking idea with trigger conditions → plant-seed workflow
|
|
23
23
|
- **--list**: List pending todos and select one to work on → check-todos workflow
|
|
24
|
+
- **--list-seeds**: List/audit captured seeds (optional status filter) → list-seeds workflow
|
|
24
25
|
</objective>
|
|
25
26
|
|
|
26
27
|
<routing>
|
|
@@ -32,6 +33,7 @@ Mode routing:
|
|
|
32
33
|
| --backlog | ROADMAP.md backlog section (999.x) | add-backlog |
|
|
33
34
|
| --seed | .planning/seeds/SEED-NNN-slug.md | plant-seed |
|
|
34
35
|
| --list | Interactive todo browser + action router | check-todos |
|
|
36
|
+
| --list-seeds | Read-only seed list/audit (optional status filter) | list-seeds |
|
|
35
37
|
|
|
36
38
|
</routing>
|
|
37
39
|
|
|
@@ -41,6 +43,7 @@ Mode routing:
|
|
|
41
43
|
@~/.claude/gsd-core/workflows/add-backlog.md
|
|
42
44
|
@~/.claude/gsd-core/workflows/plant-seed.md
|
|
43
45
|
@~/.claude/gsd-core/workflows/check-todos.md
|
|
46
|
+
@~/.claude/gsd-core/workflows/list-seeds.md
|
|
44
47
|
@~/.claude/gsd-core/references/ui-brand.md
|
|
45
48
|
</execution_context>
|
|
46
49
|
|
|
@@ -51,6 +54,7 @@ Parse the first token of $ARGUMENTS:
|
|
|
51
54
|
- If it is `--note`: strip the flag, pass remainder to note workflow
|
|
52
55
|
- If it is `--backlog`: strip the flag, pass remainder to add-backlog workflow
|
|
53
56
|
- If it is `--seed`: strip the flag, pass remainder to plant-seed workflow
|
|
57
|
+
- If it is `--list-seeds`: strip the flag, pass remainder (optional status filter) to list-seeds workflow
|
|
54
58
|
- If it is `--list`: pass remainder (optional area filter) to check-todos workflow
|
|
55
59
|
- Otherwise: pass all of $ARGUMENTS to add-todo workflow
|
|
56
60
|
</context>
|
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
|
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
* generate-slug <text> Convert text to URL-safe slug
|
|
26
26
|
* current-timestamp [format] Get timestamp (full|date|filename)
|
|
27
27
|
* list-todos [area] Count and enumerate pending todos
|
|
28
|
+
* list-seeds [status] List captured seeds (optional status filter)
|
|
28
29
|
* verify-path-exists <path> Check file/directory existence
|
|
29
30
|
* config-ensure-section Initialize .planning/config.json
|
|
30
31
|
* history-digest Aggregate all SUMMARY.md data
|
|
@@ -84,6 +85,7 @@
|
|
|
84
85
|
* UAT Audit:
|
|
85
86
|
* audit-uat Scan all phases for unresolved UAT/verification items
|
|
86
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)
|
|
87
89
|
*
|
|
88
90
|
* Open Artifact Audit:
|
|
89
91
|
* audit-open [--json] Scan all .planning/ artifact types for unresolved items
|
|
@@ -631,13 +633,13 @@ async function main() {
|
|
|
631
633
|
// discovery; previously it was a partial subset that didn't include
|
|
632
634
|
// phase / roadmap / milestone / progress / etc.
|
|
633
635
|
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
634
|
-
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, ' +
|
|
636
|
+
'Commands: agent, agent-skills, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
635
637
|
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, ' +
|
|
636
638
|
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
637
639
|
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
638
640
|
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
639
|
-
'capability, classify-confidence, git, learnings, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
640
|
-
'profile-sample, progress, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
641
|
+
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
642
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, requirements, research-plan, research-store, resolve-granularity, resolve-model, roadmap, scaffold, state, ' +
|
|
641
643
|
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, workstream, worktree\n\n' +
|
|
642
644
|
'Global flags:\n' +
|
|
643
645
|
' --raw Emit raw output without post-processing\n' +
|
|
@@ -688,6 +690,10 @@ async function main() {
|
|
|
688
690
|
'worktree', 'prompt-budget',
|
|
689
691
|
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
690
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',
|
|
691
697
|
]);
|
|
692
698
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
693
699
|
cwd = findProjectRoot(cwd);
|
|
@@ -959,6 +965,13 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
959
965
|
break;
|
|
960
966
|
}
|
|
961
967
|
|
|
968
|
+
case 'pr-subrepo': {
|
|
969
|
+
const message = args[1];
|
|
970
|
+
const { repo, branch } = parseNamedArgs(args, ['repo', 'branch']);
|
|
971
|
+
commands.cmdPrSubrepo(cwd, repo, branch, message, raw);
|
|
972
|
+
break;
|
|
973
|
+
}
|
|
974
|
+
|
|
962
975
|
case 'verify-summary': {
|
|
963
976
|
const summaryPath = args[1];
|
|
964
977
|
const countIndex = args.indexOf('--check-count');
|
|
@@ -1095,11 +1108,39 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1095
1108
|
break;
|
|
1096
1109
|
}
|
|
1097
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
|
+
|
|
1098
1134
|
case 'list-todos': {
|
|
1099
1135
|
commands.cmdListTodos(cwd, args[1], raw);
|
|
1100
1136
|
break;
|
|
1101
1137
|
}
|
|
1102
1138
|
|
|
1139
|
+
case 'list-seeds': {
|
|
1140
|
+
commands.cmdListSeeds(cwd, args[1], raw);
|
|
1141
|
+
break;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1103
1144
|
case 'verify-path-exists': {
|
|
1104
1145
|
commands.cmdVerifyPathExists(cwd, args[1], raw);
|
|
1105
1146
|
break;
|
|
@@ -1320,12 +1361,16 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
1320
1361
|
|
|
1321
1362
|
case 'uat': {
|
|
1322
1363
|
const subcommand = args[1];
|
|
1323
|
-
const uat = require('./lib/uat.cjs');
|
|
1324
1364
|
if (subcommand === 'render-checkpoint') {
|
|
1365
|
+
const uat = require('./lib/uat.cjs');
|
|
1325
1366
|
const options = parseNamedArgs(args, ['file']);
|
|
1326
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);
|
|
1327
1372
|
} else {
|
|
1328
|
-
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);
|
|
1329
1374
|
}
|
|
1330
1375
|
break;
|
|
1331
1376
|
}
|
|
@@ -2128,6 +2173,8 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2128
2173
|
const worktreeSafety = require('./lib/worktree-safety.cjs');
|
|
2129
2174
|
if (subcommand === 'cleanup-wave') {
|
|
2130
2175
|
worktreeSafety.cmdWorktreeCleanupWave(cwd, args.slice(2));
|
|
2176
|
+
} else if (subcommand === 'record-agent') {
|
|
2177
|
+
worktreeSafety.cmdWorktreeRecordAgent(cwd, args.slice(2));
|
|
2131
2178
|
} else if (subcommand === 'reap-orphans') {
|
|
2132
2179
|
worktreeSafety.cmdWorktreeReapOrphans(cwd);
|
|
2133
2180
|
} else if (subcommand === 'base-check') {
|
|
@@ -2135,7 +2182,7 @@ async function runCommand(command, args, cwd, raw, defaultValue, originalCommand
|
|
|
2135
2182
|
} else if (subcommand === 'set-baseref') {
|
|
2136
2183
|
require('./lib/worktree-base-ref.cjs').cmdWorktreeSetBaseRef(cwd, args.slice(2));
|
|
2137
2184
|
} else {
|
|
2138
|
-
error('Unknown worktree subcommand. Available: cleanup-wave, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2185
|
+
error('Unknown worktree subcommand. Available: cleanup-wave, record-agent, reap-orphans, base-check, set-baseref', ERROR_REASON.SDK_UNKNOWN_COMMAND);
|
|
2139
2186
|
}
|
|
2140
2187
|
break;
|
|
2141
2188
|
}
|
|
@@ -58,7 +58,6 @@ const CANONICAL_HEADERS = {
|
|
|
58
58
|
'candidates',
|
|
59
59
|
'approaches considered',
|
|
60
60
|
'variants',
|
|
61
|
-
'trade-offs',
|
|
62
61
|
'pros and cons of the options',
|
|
63
62
|
'discussion',
|
|
64
63
|
],
|
|
@@ -190,15 +189,31 @@ function normalizeAdrHeader(raw) {
|
|
|
190
189
|
.replace(/[^\w\s]/g, '')
|
|
191
190
|
.trim();
|
|
192
191
|
}
|
|
193
|
-
|
|
192
|
+
// Normalized synonym index (audit M7). classifyHeader receives an ALREADY-normalized
|
|
193
|
+
// header (via normalizeAdrHeader), but historically compared it against the RAW synonym
|
|
194
|
+
// strings. Because normalizeAdrHeader collapses [\s:._-]+ to a space and strips [^\w\s],
|
|
195
|
+
// any synonym carrying a hyphen/apostrophe/etc. ('trade-offs', "won't do", 'post-grilling')
|
|
196
|
+
// could never match a normalized header — it was silently dead, and its ADR section went
|
|
197
|
+
// unmapped. Normalizing BOTH sides closes that abstraction asymmetry once, so every synonym
|
|
198
|
+
// (current and future) is reachable regardless of punctuation. Precomputed at module load to
|
|
199
|
+
// avoid re-normalizing the whole table per call; insertion order is preserved so first-match-
|
|
200
|
+
// wins and the exact-then-prefix precedence stay identical to the prior raw-compare loop.
|
|
201
|
+
const _NORMALIZED_SYNONYM_INDEX = (() => {
|
|
202
|
+
const index = [];
|
|
194
203
|
for (const [canonical, synonyms] of Object.entries(CANONICAL_HEADERS)) {
|
|
195
204
|
for (const synonym of synonyms) {
|
|
196
|
-
|
|
197
|
-
return canonical;
|
|
198
|
-
if (normalizedHeader.startsWith(`${synonym} `))
|
|
199
|
-
return canonical;
|
|
205
|
+
index.push([normalizeAdrHeader(synonym), canonical]);
|
|
200
206
|
}
|
|
201
207
|
}
|
|
208
|
+
return index;
|
|
209
|
+
})();
|
|
210
|
+
function classifyHeader(normalizedHeader) {
|
|
211
|
+
for (const [synonym, canonical] of _NORMALIZED_SYNONYM_INDEX) {
|
|
212
|
+
if (normalizedHeader === synonym)
|
|
213
|
+
return canonical;
|
|
214
|
+
if (normalizedHeader.startsWith(`${synonym} `))
|
|
215
|
+
return canonical;
|
|
216
|
+
}
|
|
202
217
|
return null;
|
|
203
218
|
}
|
|
204
219
|
function splitEntries(blockText) {
|
|
@@ -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,
|
|
@@ -280,6 +280,20 @@ function isSafeHookScriptPath(script) {
|
|
|
280
280
|
function shellSingleQuote(value) {
|
|
281
281
|
return "'" + value.replace(/'/g, "'\\''") + "'";
|
|
282
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* #1634: build the emitted hook `command` for an ABSOLUTE confined script path. For `.js`-family
|
|
285
|
+
* hooks (`.js`/`.cjs`/`.mjs`) prefix with `node` so the hook runs regardless of the source's
|
|
286
|
+
* executable bit — a `git`/tarball source that lost `+x` would otherwise yield
|
|
287
|
+
* `/bin/sh: Permission denied` on every matching call (defect #2). This mirrors first-party hooks
|
|
288
|
+
* (`node "${CLAUDE_PLUGIN_ROOT}/hooks/x.js"`). The path stays POSIX single-quoted (#1460 (R) HIGH)
|
|
289
|
+
* so a space-containing install prefix cannot word-split or inject. Non-JS scripts (e.g. `.sh`)
|
|
290
|
+
* keep the bare single-quoted absolute path (unchanged) — they remain responsible for their own
|
|
291
|
+
* executability, exactly as before; per-runtime command projection is a separate concern (ADR-857 D8).
|
|
292
|
+
*/
|
|
293
|
+
const JS_HOOK_EXT_RE = /\.(?:js|cjs|mjs)$/;
|
|
294
|
+
function runnableHookCommand(absScript) {
|
|
295
|
+
return JS_HOOK_EXT_RE.test(absScript) ? 'node ' + shellSingleQuote(absScript) : shellSingleQuote(absScript);
|
|
296
|
+
}
|
|
283
297
|
/**
|
|
284
298
|
* #1460 CONF-1: resolve a hook `script` (declared RELATIVE to the bundle) against the capability's
|
|
285
299
|
* own install dir and CONFINE it via realpath, returning the ABSOLUTE confined path or null when it
|
|
@@ -459,6 +473,11 @@ function applyCapabilitySharedEdits(args) {
|
|
|
459
473
|
const script = typeof rec['script'] === 'string' ? rec['script'] : '';
|
|
460
474
|
if (!event || !script || isUnsafeKey(event))
|
|
461
475
|
continue;
|
|
476
|
+
// #1634: optional tool-scoping `matcher` (a settings.json concept — entry-level sibling of
|
|
477
|
+
// `hooks`). Absent => match-all (field OMITTED so the existing shipped capabilities' wiring
|
|
478
|
+
// is byte-for-byte unchanged, Hyrum's Law). The validator gates this to a non-empty string.
|
|
479
|
+
const matcherRaw = rec['matcher'];
|
|
480
|
+
const matcher = typeof matcherRaw === 'string' && matcherRaw.length > 0 ? matcherRaw : null;
|
|
462
481
|
// #1460 CONF-1: resolve the declared (relative) script against the capability's OWN install
|
|
463
482
|
// dir and CONFINE via realpath, then write the ABSOLUTE confined path as the hook command —
|
|
464
483
|
// never the raw relative path (which would resolve against the CWD at hook-exec time and could
|
|
@@ -467,14 +486,18 @@ function applyCapabilitySharedEdits(args) {
|
|
|
467
486
|
const absScript = confinedBundleScript(capDir(runtimeDir, capId), script);
|
|
468
487
|
if (absScript === null)
|
|
469
488
|
continue;
|
|
470
|
-
// #1460 (R) HIGH: the hook `command` is consumed by a shell
|
|
471
|
-
// `node
|
|
472
|
-
//
|
|
473
|
-
//
|
|
474
|
-
|
|
475
|
-
const command = shellSingleQuote(absScript);
|
|
489
|
+
// #1460 (R) HIGH + #1634: the hook `command` is consumed by a shell. `runnableHookCommand`
|
|
490
|
+
// emits a `node`-prefixed POSIX-single-quoted absolute path for `.js`-family hooks (runs
|
|
491
|
+
// without `+x`; mirrors first-party) and a bare single-quoted path otherwise. Single-quoting
|
|
492
|
+
// keeps a space-containing install prefix as one shell token (cannot word-split or inject).
|
|
493
|
+
const command = runnableHookCommand(absScript);
|
|
476
494
|
const arr = Array.isArray(hooksObj[event]) ? hooksObj[event] : [];
|
|
477
|
-
|
|
495
|
+
// #1634: stamp the marker so the entry is surgically strippable, and carry the declared
|
|
496
|
+
// `matcher` (entry-level sibling of `hooks`) only when the author declared one.
|
|
497
|
+
const entry = { [CAP_MARKER]: capId, hooks: [{ type: 'command', command }] };
|
|
498
|
+
if (matcher !== null)
|
|
499
|
+
entry['matcher'] = matcher;
|
|
500
|
+
arr.push(entry);
|
|
478
501
|
hooksObj[event] = arr;
|
|
479
502
|
touched = true;
|
|
480
503
|
}
|