@opengsd/gsd-core 1.9.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -3
- package/.opencode/plugins/gsd-core.js +8 -1
- package/agents/gsd-code-fixer.md +27 -3
- package/agents/gsd-debugger.md +12 -246
- package/agents/gsd-executor.md +7 -5
- package/agents/gsd-integration-checker.md +3 -0
- package/agents/gsd-plan-checker.md +9 -0
- package/agents/gsd-planner.md +5 -8
- package/agents/gsd-roadmapper.md +21 -3
- package/agents/gsd-verifier.md +14 -70
- package/bin/install.js +453 -289
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/plan-phase.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +579 -66
- package/gsd-core/bin/lib/active-workstream-store.cjs +25 -0
- package/gsd-core/bin/lib/agent-install-check.cjs +38 -6
- package/gsd-core/bin/lib/api-coverage.cjs +120 -0
- package/gsd-core/bin/lib/audit.cjs +89 -1
- package/gsd-core/bin/lib/broken-windows.cjs +36 -6
- package/gsd-core/bin/lib/capability-registry.cjs +96 -110
- package/gsd-core/bin/lib/capability-validator.cjs +12 -2
- package/gsd-core/bin/lib/check-command-router.cjs +43 -1
- package/gsd-core/bin/lib/command-aliases.cjs +72 -0
- package/gsd-core/bin/lib/commands.cjs +26 -25
- package/gsd-core/bin/lib/commonjs-marker.cjs +136 -0
- package/gsd-core/bin/lib/config-loader.cjs +1 -0
- package/gsd-core/bin/lib/config.cjs +12 -1
- package/gsd-core/bin/lib/context-composer.cjs +278 -0
- package/gsd-core/bin/lib/context-predicates.cjs +506 -0
- package/gsd-core/bin/lib/core-utils.cjs +91 -12
- package/gsd-core/bin/lib/docs.cjs +3 -2
- package/gsd-core/bin/lib/external-job.cjs +19 -4
- package/gsd-core/bin/lib/frontmatter.cjs +84 -12
- package/gsd-core/bin/lib/gate-predicate-evaluator.cjs +57 -6
- package/gsd-core/bin/lib/git-base-branch.cjs +58 -15
- package/gsd-core/bin/lib/graphify.cjs +142 -27
- package/gsd-core/bin/lib/gsd2-import.cjs +27 -4
- package/gsd-core/bin/lib/host-integration.cjs +13 -1
- package/gsd-core/bin/lib/init-command-router.cjs +83 -8
- package/gsd-core/bin/lib/init.cjs +1021 -57
- package/gsd-core/bin/lib/install-engine.cjs +64 -10
- package/gsd-core/bin/lib/install-profiles.cjs +27 -1
- package/gsd-core/bin/lib/installer-migration-authoring.cjs +3 -1
- package/gsd-core/bin/lib/installer-migration-report.cjs +4 -0
- package/gsd-core/bin/lib/installer-migrations/007-retire-config-root-commonjs-marker.cjs +149 -0
- package/gsd-core/bin/lib/installer-migrations/008-cursor-retire-commands-surface.cjs +55 -0
- package/gsd-core/bin/lib/installer-migrations/009-pi-retire-reserved-hooks-dir.cjs +199 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +87 -1
- package/gsd-core/bin/lib/io.cjs +28 -3
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +6 -0
- package/gsd-core/bin/lib/mcp-catalog.cjs +518 -0
- package/gsd-core/bin/lib/mcp-server.cjs +135 -3
- package/gsd-core/bin/lib/milestone.cjs +75 -47
- package/gsd-core/bin/lib/phase-id.cjs +63 -0
- package/gsd-core/bin/lib/phase-locator.cjs +138 -45
- package/gsd-core/bin/lib/phase.cjs +260 -28
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +232 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +4 -0
- package/gsd-core/bin/lib/prompt-budget.cjs +128 -165
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +80 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +99 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +30 -6
- package/gsd-core/bin/lib/roadmap-command-router.cjs +42 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +100 -18
- package/gsd-core/bin/lib/roadmap.cjs +37 -7
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +195 -62
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +15 -3
- package/gsd-core/bin/lib/runtime-homes.cjs +154 -41
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +105 -41
- package/gsd-core/bin/lib/section-manifest.cjs +209 -0
- package/gsd-core/bin/lib/shell-command-projection.cjs +113 -27
- package/gsd-core/bin/lib/smart-entry.cjs +12 -0
- package/gsd-core/bin/lib/state-transition.cjs +73 -8
- package/gsd-core/bin/lib/state.cjs +151 -62
- package/gsd-core/bin/lib/surface.cjs +12 -1
- package/gsd-core/bin/lib/uat-predicate.cjs +11 -1
- package/gsd-core/bin/lib/uat.cjs +320 -21
- package/gsd-core/bin/lib/unusable-input.cjs +9 -0
- package/gsd-core/bin/lib/verification.cjs +29 -12
- package/gsd-core/bin/lib/verify.cjs +10 -2
- package/gsd-core/bin/lib/workflow-fragments.cjs +557 -0
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +181 -18
- package/gsd-core/bin/lib/workstream-inventory.cjs +519 -27
- package/gsd-core/bin/lib/workstream.cjs +6 -0
- package/gsd-core/bin/lib/worktree-base-ref.cjs +50 -6
- package/gsd-core/bin/lib/worktree-safety.cjs +276 -118
- package/gsd-core/bin/shared/config-schema.manifest.json +2 -0
- package/gsd-core/references/artifact-types.md +10 -3
- package/gsd-core/references/autonomous-ui-design-contract.md +42 -0
- package/gsd-core/references/debugger-techniques.md +255 -0
- package/gsd-core/references/research-documentation-lookup.md +5 -3
- package/gsd-core/references/specless-probe-fallback.md +7 -6
- package/gsd-core/references/verifier-wiring-patterns.md +100 -0
- package/gsd-core/references/worktree-branch-check.md +2 -2
- package/gsd-core/templates/summary-complex.md +2 -0
- package/gsd-core/templates/summary-minimal.md +2 -0
- package/gsd-core/templates/summary-standard.md +2 -0
- package/gsd-core/templates/summary.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +3 -0
- package/gsd-core/workflows/autonomous/steps/converge-banner.md +1 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-bg.md +11 -0
- package/gsd-core/workflows/autonomous/steps/converge-dispatch-inline.md +7 -0
- package/gsd-core/workflows/autonomous/steps/converge-fail-fast.md +21 -0
- package/gsd-core/workflows/autonomous/steps/converge-loop.md +7 -0
- package/gsd-core/workflows/autonomous.md +32 -69
- package/gsd-core/workflows/code-review/steps/dispatch-fix.md +39 -0
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +83 -0
- package/gsd-core/workflows/code-review.md +42 -160
- package/gsd-core/workflows/complete-milestone/steps/git-tag.md +29 -0
- package/gsd-core/workflows/complete-milestone.md +23 -81
- package/gsd-core/workflows/debug.md +9 -12
- package/gsd-core/workflows/diagnose-issues.md +22 -0
- package/gsd-core/workflows/discovery-phase.md +4 -4
- package/gsd-core/workflows/discuss-phase-assumptions/steps/auto-advance-dispatch.md +15 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +5 -16
- package/gsd-core/workflows/docs-update/steps/dispatch-monorepo-packages.md +51 -0
- package/gsd-core/workflows/docs-update.md +8 -51
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +34 -2
- package/gsd-core/workflows/execute-phase/steps/gap-closure-artifacts.md +50 -0
- package/gsd-core/workflows/execute-phase/steps/partial-wave.md +31 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +19 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +42 -0
- package/gsd-core/workflows/execute-phase/steps/regression-gate.md +43 -37
- package/gsd-core/workflows/execute-phase.md +65 -137
- package/gsd-core/workflows/execute-plan.md +1 -1
- package/gsd-core/workflows/help/modes/full.md +6 -1
- package/gsd-core/workflows/ingest-docs.md +2 -1
- package/gsd-core/workflows/new-milestone/steps/project-md-milestone-write.md +16 -0
- package/gsd-core/workflows/new-milestone/steps/reset-phase-safety.md +19 -0
- package/gsd-core/workflows/new-milestone.md +21 -38
- package/gsd-core/workflows/new-project/steps/auto-mode-config.md +176 -0
- package/gsd-core/workflows/new-project/steps/auto-mode-detection.md +32 -0
- package/gsd-core/workflows/new-project/steps/codebase-map-offer.md +18 -0
- package/gsd-core/workflows/new-project.md +13 -226
- package/gsd-core/workflows/plan-phase/steps/adr-ingest-express-path.md +15 -0
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +110 -0
- package/gsd-core/workflows/plan-phase/steps/prd-express-gate.md +8 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-early-exit.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/research-only-modifiers.md +16 -0
- package/gsd-core/workflows/plan-phase/steps/reviews-prerequisite.md +17 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +149 -0
- package/gsd-core/workflows/plan-phase.md +49 -193
- package/gsd-core/workflows/progress/steps/forensic-audit.md +125 -0
- package/gsd-core/workflows/progress/steps/mvp-display.md +18 -0
- package/gsd-core/workflows/progress.md +11 -153
- package/gsd-core/workflows/quick/steps/discussion-phase.md +124 -0
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +111 -0
- package/gsd-core/workflows/quick/steps/quick-verification.md +46 -0
- package/gsd-core/workflows/quick/steps/research-phase.md +72 -0
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +37 -0
- package/gsd-core/workflows/quick.md +20 -390
- package/gsd-core/workflows/resume-project.md +3 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-1.md +4 -0
- package/gsd-core/workflows/review/steps/reviewer-instances-note-2.md +3 -0
- package/gsd-core/workflows/review.md +15 -8
- package/gsd-core/workflows/section-manifest.json +219 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/spec-phase.md +17 -14
- package/gsd-core/workflows/spike-wrap-up.md +20 -5
- package/gsd-core/workflows/spike.md +50 -16
- package/gsd-core/workflows/sync-skills.md +49 -11
- package/gsd-core/workflows/transition/steps/workstream-collision-check.md +17 -0
- package/gsd-core/workflows/transition.md +8 -21
- package/gsd-core/workflows/ui-phase.md +8 -7
- package/gsd-core/workflows/update/steps/channel-banner.md +7 -0
- package/gsd-core/workflows/update.md +18 -7
- package/gsd-core/workflows/verify-phase.md +4 -7
- package/gsd-core/workflows/verify-work/steps/automated-ui-verification.md +36 -0
- package/gsd-core/workflows/verify-work/steps/mvp-uat-framing.md +21 -0
- package/gsd-core/workflows/verify-work.md +8 -58
- package/hooks/dist/gsd-agent-isolation-guard.js +428 -0
- package/hooks/dist/gsd-check-update-worker.js +14 -5
- package/hooks/dist/gsd-cursor-subagent-start.js +532 -26
- package/hooks/dist/gsd-read-injection-scanner.js +7 -0
- package/hooks/dist/gsd-statusline.js +72 -6
- package/hooks/dist/gsd-worktree-path-guard.js +2 -1
- package/hooks/dist/gsd-write-guard.js +359 -0
- package/hooks/dist/lib/isolation-sentinel.js +268 -0
- package/hooks/dist/managed-hooks-registry.cjs +2 -0
- package/hooks/gsd-agent-isolation-guard.js +428 -0
- package/hooks/gsd-check-update-worker.js +14 -5
- package/hooks/gsd-cursor-subagent-start.js +532 -26
- package/hooks/gsd-read-injection-scanner.js +7 -0
- package/hooks/gsd-statusline.js +72 -6
- package/hooks/gsd-worktree-path-guard.js +2 -1
- package/hooks/gsd-write-guard.js +359 -0
- package/hooks/hooks.json +12 -0
- package/hooks/lib/isolation-sentinel.js +268 -0
- package/hooks/managed-hooks-registry.cjs +2 -0
- package/package.json +14 -5
- package/pi/gsd.cjs +57 -12
- package/scripts/build-hooks.js +9 -0
- package/scripts/changeset/lint.cjs +9 -2
- package/scripts/changeset/serialize.cjs +5 -1
- package/scripts/gen-capability-matrix.cjs +1 -1
- package/scripts/gen-context-index.cjs +448 -0
- package/scripts/gen-inventory-manifest.cjs +101 -1
- package/scripts/gen-prompt-budget-parity-corpus.cjs +645 -0
- package/scripts/gen-section-manifest.cjs +638 -0
- package/scripts/generate-package-identity.cjs +4 -2
- package/scripts/lint-allow-test-rule-refs.allowlist.json +17 -31
- package/scripts/lint-compiled-artifact-sync.cjs +6 -1
- package/scripts/lint-docs-command-form.cjs +195 -0
- package/scripts/lint-docs-required.cjs +9 -1
- package/scripts/lint-emitted-drift-ack.cjs +215 -20
- package/scripts/lint-example-parser-parity.cjs +395 -0
- package/scripts/lint-test-file-count.allowlist.json +27 -1
- package/scripts/mutation-matrix.cjs +13 -0
- package/scripts/prompt-injection-scan.sh +27 -6
- package/scripts/run-tests.cjs +3 -2
- package/skills/gsd-autonomous/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-plan-phase/SKILL.md +2 -2
- package/vscode/package.json +1 -1
- package/scripts/gen-emitted-baseline.cjs +0 -145
|
@@ -264,6 +264,9 @@ const { resolveActiveWorkstream, applyResolvedWorkstreamEnv } = require('./lib/a
|
|
|
264
264
|
const state = require('./lib/state.cjs');
|
|
265
265
|
const phase = require('./lib/phase.cjs');
|
|
266
266
|
const roadmap = require('./lib/roadmap.cjs');
|
|
267
|
+
// #3024: resolve skills root for the sync-skills workflow (install.js is not
|
|
268
|
+
// shipped in installed trees; gsd-tools IS shipped, so the workflow calls this).
|
|
269
|
+
const { getGlobalSkillsBase, isRegisteredRuntimeId } = require('./lib/runtime-homes.cjs');
|
|
267
270
|
// #1561 — assumption-delta advisory checkpoint detector (pure function).
|
|
268
271
|
const { detectAssumptionDelta } = require('./lib/assumption-delta.cjs');
|
|
269
272
|
const verify = require('./lib/verify.cjs');
|
|
@@ -1044,6 +1047,37 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1044
1047
|
commands.cmdCurrentTimestamp(args[1] || 'full', raw);
|
|
1045
1048
|
}
|
|
1046
1049
|
|
|
1050
|
+
function routeSkillsRoot({ args, raw, error }) {
|
|
1051
|
+
// #3024: resolve the global skills base directory for a runtime.
|
|
1052
|
+
// The sync-skills workflow previously shelled out to install.js --skills-root,
|
|
1053
|
+
// but install.js is not shipped in installed trees. gsd-tools IS shipped, so
|
|
1054
|
+
// the workflow now calls `gsd-tools query skills-root <runtime>` instead.
|
|
1055
|
+
const runtime = args[1];
|
|
1056
|
+
if (!runtime) {
|
|
1057
|
+
error('Usage: gsd-tools query skills-root <runtime>');
|
|
1058
|
+
}
|
|
1059
|
+
// Defect B (#3024): validate the runtime id against the shipped capability
|
|
1060
|
+
// registry's canonical runtime set BEFORE resolving anything.
|
|
1061
|
+
// getGlobalSkillsBase falls through getGlobalConfigDir's unknown-runtime
|
|
1062
|
+
// branch to claude's skills root for ANY id it doesn't recognize, so an
|
|
1063
|
+
// unknown, empty/whitespace-only, path-traversal, or shell-metacharacter
|
|
1064
|
+
// runtime arg would otherwise silently resolve to claude's path instead of
|
|
1065
|
+
// failing loudly. isRegisteredRuntimeId does an own-property lookup (not a
|
|
1066
|
+
// bare index), rejecting `__proto__`/`constructor`/`prototype` runtime
|
|
1067
|
+
// ids, and is the SAME validator install.js's `--skills-root` entry point
|
|
1068
|
+
// calls, so the two shipped entry points can never diverge on which
|
|
1069
|
+
// runtime ids they accept.
|
|
1070
|
+
if (!isRegisteredRuntimeId(runtime)) {
|
|
1071
|
+
error(`Unknown runtime "${runtime}" — must be a registered runtime id`);
|
|
1072
|
+
}
|
|
1073
|
+
const trimmedRuntime = typeof runtime === 'string' ? runtime.trim() : '';
|
|
1074
|
+
const skillsRoot = getGlobalSkillsBase(trimmedRuntime);
|
|
1075
|
+
if (skillsRoot === null) {
|
|
1076
|
+
error(`No skills root found for runtime "${trimmedRuntime}"`);
|
|
1077
|
+
}
|
|
1078
|
+
output({ skills_root: skillsRoot }, raw, skillsRoot);
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1047
1081
|
function routeProjectInstructionFile({ args, cwd, raw, error }) {
|
|
1048
1082
|
// #1529: pure runtime→filename projection. Backs the
|
|
1049
1083
|
// `gsd_run query project-instruction-file --runtime <r>` call in
|
|
@@ -1145,16 +1179,33 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1145
1179
|
const cp = require('node:child_process');
|
|
1146
1180
|
const fsx = require('node:fs');
|
|
1147
1181
|
const os = require('node:os');
|
|
1148
|
-
const { REVIEWER_LANES } = require('./lib/review-lane-descriptor.cjs');
|
|
1182
|
+
const { REVIEWER_LANES, mergeReviewerLanes } = require('./lib/review-lane-descriptor.cjs');
|
|
1149
1183
|
const { resolveLanePlan } = require('./lib/review-lane-invocation.cjs');
|
|
1150
1184
|
const runner = require('./lib/review-lane-runner.cjs');
|
|
1151
1185
|
const cfgLoader = require('./lib/config-loader.cjs');
|
|
1186
|
+
const capabilityLoader = require('./lib/capability-loader.cjs');
|
|
1152
1187
|
|
|
1153
1188
|
const flag = (name) => {
|
|
1154
1189
|
const i = args.indexOf(name);
|
|
1155
1190
|
return i !== -1 && args[i + 1] && !String(args[i + 1]).startsWith('--') ? args[i + 1] : null;
|
|
1156
1191
|
};
|
|
1157
1192
|
const sub = args[1];
|
|
1193
|
+
// Fail fast on an unrecognized subcommand. Without this check, `sub` fell through
|
|
1194
|
+
// to the `sub !== 'invoke'` usage-error branch far below (after loading the
|
|
1195
|
+
// capability registry AND building a per-lane plan for every lane — which itself
|
|
1196
|
+
// spawns one child `query resolve-execution` process per lane via `effortFor`,
|
|
1197
|
+
// up to 12 subprocess spawns for the default lane set) before ever reporting the
|
|
1198
|
+
// error. That made an invalid subcommand slow instead of instant, and under bench
|
|
1199
|
+
// load (many sequential node spawns) `review-lane bogus` could exceed a caller's
|
|
1200
|
+
// spawn timeout and be killed before writing anything to stderr — the CI-observed
|
|
1201
|
+
// failure was empty stdout AND stderr, not the expected usage message (#3148).
|
|
1202
|
+
// `plan`/`invoke` are the only subs that need the expensive plan-building path
|
|
1203
|
+
// below; `sections`/`flags` return earlier still. Anything else errors here, before
|
|
1204
|
+
// any of that work starts.
|
|
1205
|
+
if (!['plan', 'invoke', 'sections', 'flags'].includes(sub)) {
|
|
1206
|
+
error("Usage: review-lane <plan|invoke|sections|flags> [--selected a,b] [--run-dir D] [--repo-root R]");
|
|
1207
|
+
return;
|
|
1208
|
+
}
|
|
1158
1209
|
const runDir = flag('--run-dir') || '.';
|
|
1159
1210
|
const repoRoot = flag('--repo-root') || cwd;
|
|
1160
1211
|
|
|
@@ -1176,8 +1227,30 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1176
1227
|
|
|
1177
1228
|
const selected = (flag('--selected') || '')
|
|
1178
1229
|
.split(',').map((s) => s.trim()).filter(Boolean);
|
|
1179
|
-
|
|
1180
|
-
|
|
1230
|
+
// ADR-2782 D8 (#2927): the lane map is first-party ∪ INSTALLED overlay
|
|
1231
|
+
// `reviewer` bodies, first-party winning on slug collision. Before this merge
|
|
1232
|
+
// the map was built from the frozen REVIEWER_LANES array alone, so an installed,
|
|
1233
|
+
// consented third-party reviewer lane was roster-visible (deriveReviewerSlugs)
|
|
1234
|
+
// and disclosed at install (collectReviewerLaneSurfaces) but never selectable,
|
|
1235
|
+
// plannable, or invocable — `sections`/`flags`/`plan`/`invoke` all consumed this
|
|
1236
|
+
// one map. The overlay body is field-identical to a ReviewerLane (ADR-2782 D1,
|
|
1237
|
+
// "no translation layer"), so `mergeReviewerLanes` is a pure merge, not a
|
|
1238
|
+
// projection. loadRegistry is TOTAL and never throws on a malformed overlay
|
|
1239
|
+
// (it skips the cap with a warning), and mergeReviewerLanes is total in turn,
|
|
1240
|
+
// so a bad third-party manifest cannot take the first-party lanes down with it.
|
|
1241
|
+
// `includeInstalled` is what merges project + global overlay caps into the
|
|
1242
|
+
// registry; without it the base is first-party-only and this is a no-op.
|
|
1243
|
+
let mergedLanes = REVIEWER_LANES;
|
|
1244
|
+
try {
|
|
1245
|
+
const registry = capabilityLoader.loadRegistry({ includeInstalled: true, cwd });
|
|
1246
|
+
mergedLanes = mergeReviewerLanes(REVIEWER_LANES, registry);
|
|
1247
|
+
} catch {
|
|
1248
|
+
// A registry load failure must never block first-party review. Degrade to the
|
|
1249
|
+
// static set — identical to pre-fix behavior — rather than crashing review-lane.
|
|
1250
|
+
mergedLanes = REVIEWER_LANES;
|
|
1251
|
+
}
|
|
1252
|
+
const laneBySlug = new Map(mergedLanes.map((l) => [l.slug, l]));
|
|
1253
|
+
const chosen = selected.length ? selected : mergedLanes.map((l) => l.slug);
|
|
1181
1254
|
|
|
1182
1255
|
if (sub === 'sections') {
|
|
1183
1256
|
const rows = chosen
|
|
@@ -1307,7 +1380,16 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1307
1380
|
// cannot be interrupted by --test-force-exit and hangs a whole CI chunk to its 10-minute kill.
|
|
1308
1381
|
const deps = {
|
|
1309
1382
|
spawn: (binary, argv, opts) => {
|
|
1310
|
-
|
|
1383
|
+
// #3086: on Windows, reviewer CLIs (gemini, codex, etc.) are installed
|
|
1384
|
+
// as .cmd shims. spawnSync with a bare name + shell:false fails with
|
|
1385
|
+
// ENOENT (CreateProcess cannot start .cmd). Apply the same #2667 shim
|
|
1386
|
+
// gate used in runWithTimeout: detect .cmd/.bat and mediate through
|
|
1387
|
+
// cmd.exe /d /s /c with an explicit argv array (no shell:true).
|
|
1388
|
+
const isWin = process.platform === 'win32';
|
|
1389
|
+
const winShim = isWin && /\.(cmd|bat)$/i.test(path.basename(binary));
|
|
1390
|
+
const spawnBinary = winShim ? (process.env.ComSpec || 'cmd.exe') : binary;
|
|
1391
|
+
const spawnArgv = winShim ? ['/d', '/s', '/c', binary, ...argv] : argv;
|
|
1392
|
+
const r = cp.spawnSync(spawnBinary, spawnArgv, {
|
|
1311
1393
|
input: opts.input,
|
|
1312
1394
|
encoding: 'utf8',
|
|
1313
1395
|
timeout: opts.timeoutMs,
|
|
@@ -1564,6 +1646,24 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1564
1646
|
// `orchestrator-worktree`. It requires `--cwd-target` (the GSD-created
|
|
1565
1647
|
// worktree path) and optionally `--prompt`; without a target there is
|
|
1566
1648
|
// nothing to bind, so `exec` is null.
|
|
1649
|
+
//
|
|
1650
|
+
// #3045 CORE REDESIGN: this is now the SOLE resolver of "what isolation
|
|
1651
|
+
// applies to this dispatch", and — as an unconditional side effect — it
|
|
1652
|
+
// PERSISTS that resolved decision (mode + harnessFlag + phase/plan
|
|
1653
|
+
// identifiers, written together in one atomic write) to the sentinel the
|
|
1654
|
+
// guard hooks read. Previously the sentinel was written by prose-gated
|
|
1655
|
+
// shell blocks in `executor-isolation-dispatch.md` that a model was told
|
|
1656
|
+
// to "read and run" — a prose-gated writer for a guard against
|
|
1657
|
+
// prose-gated values is the same defect class the guard exists to close.
|
|
1658
|
+
// The workflow MUST call this query to learn ISOLATION at all, so
|
|
1659
|
+
// recording here is structurally unskippable. `--phase`/`--plan` are
|
|
1660
|
+
// optional identifiers threaded through from the caller (workflow shell
|
|
1661
|
+
// variables); `--force-isolation <mode>` lets a caller that has
|
|
1662
|
+
// additional context this resolver cannot see (the #2474 per-plan
|
|
1663
|
+
// submodule intersection, computed in shell in
|
|
1664
|
+
// `per-plan-worktree-gate.md`) override the naturally-resolved mode
|
|
1665
|
+
// while still going through this single write path. Best-effort: a
|
|
1666
|
+
// sentinel write failure here must never fail the wave.
|
|
1567
1667
|
const VALID_ISOLATION = new Set(['harness-worktree', 'orchestrator-worktree', 'none']);
|
|
1568
1668
|
let isolation = 'none';
|
|
1569
1669
|
let runtimeId = null;
|
|
@@ -1622,6 +1722,40 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1622
1722
|
harnessFlag = null;
|
|
1623
1723
|
}
|
|
1624
1724
|
|
|
1725
|
+
// `--force-isolation <mode>` overrides the naturally-resolved mode with
|
|
1726
|
+
// context this resolver has no way to see on its own (e.g. the #2474
|
|
1727
|
+
// per-plan submodule intersection). Invalid/unrecognized values are
|
|
1728
|
+
// ignored rather than erroring — this is a best-effort recording call,
|
|
1729
|
+
// not a hard usage gate. Forcing to 'none' clears harnessFlag/exec since
|
|
1730
|
+
// neither applies to sequential dispatch.
|
|
1731
|
+
const forceIdx = args.indexOf('--force-isolation');
|
|
1732
|
+
const forcedIsolation = forceIdx !== -1 ? args[forceIdx + 1] : undefined;
|
|
1733
|
+
if (forcedIsolation && VALID_ISOLATION.has(forcedIsolation)) {
|
|
1734
|
+
isolation = forcedIsolation;
|
|
1735
|
+
if (isolation === 'none') {
|
|
1736
|
+
harnessFlag = null;
|
|
1737
|
+
exec = null;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
const phaseIdx = args.indexOf('--phase');
|
|
1742
|
+
const phaseArg = phaseIdx !== -1 && args[phaseIdx + 1] && !args[phaseIdx + 1].startsWith('--')
|
|
1743
|
+
? args[phaseIdx + 1]
|
|
1744
|
+
: null;
|
|
1745
|
+
const planIdx = args.indexOf('--plan');
|
|
1746
|
+
const planArg = planIdx !== -1 && args[planIdx + 1] && !args[planIdx + 1].startsWith('--')
|
|
1747
|
+
? args[planIdx + 1]
|
|
1748
|
+
: null;
|
|
1749
|
+
|
|
1750
|
+
// Side-effect write (#3045 CORE REDESIGN) — see the doc comment above.
|
|
1751
|
+
// Never allowed to affect this query's own stdout contract or throw.
|
|
1752
|
+
try {
|
|
1753
|
+
writeDispatchIsolationSentinel(cwd, { isolation, harnessFlag, phase: phaseArg, plan: planArg });
|
|
1754
|
+
} catch {
|
|
1755
|
+
// writeDispatchIsolationSentinel already swallows its own errors into
|
|
1756
|
+
// a { recorded: false } result; this catch is defense in depth only.
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1625
1759
|
if (args.indexOf('--json') !== -1) {
|
|
1626
1760
|
output({ runtime: runtimeId, isolation, exec, harnessFlag }, raw);
|
|
1627
1761
|
} else {
|
|
@@ -1629,6 +1763,110 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
1629
1763
|
}
|
|
1630
1764
|
}
|
|
1631
1765
|
|
|
1766
|
+
/**
|
|
1767
|
+
* Atomically persist the resolved dispatch-isolation decision to the
|
|
1768
|
+
* run-scoped sentinel both isolation guard hooks read
|
|
1769
|
+
* (hooks/gsd-agent-isolation-guard.js, hooks/gsd-cursor-subagent-start.js;
|
|
1770
|
+
* shared reader hooks/lib/isolation-sentinel.js). Extracted so
|
|
1771
|
+
* `routeDispatchIsolation` (the #3045 CORE REDESIGN primary write path)
|
|
1772
|
+
* and `routeRecordDispatchIsolation` (the explicit verb, kept for the
|
|
1773
|
+
* per-plan degrade call site and back-compat/tests) share exactly one
|
|
1774
|
+
* write implementation. Never throws — returns `{ recorded, path, error? }`.
|
|
1775
|
+
*/
|
|
1776
|
+
function writeDispatchIsolationSentinel(cwd, { isolation, harnessFlag = null, phase = null, plan = null }) {
|
|
1777
|
+
const nodePath = require('path');
|
|
1778
|
+
const nodeFs = require('fs');
|
|
1779
|
+
const sentinelDir = nodePath.join(cwd, '.gsd');
|
|
1780
|
+
const sentinelPath = nodePath.join(sentinelDir, 'dispatch-isolation-sentinel.json');
|
|
1781
|
+
const payload = {
|
|
1782
|
+
isolation,
|
|
1783
|
+
harness_flag: harnessFlag || null,
|
|
1784
|
+
phase: phase || null,
|
|
1785
|
+
plan: plan || null,
|
|
1786
|
+
written_at: Date.now(),
|
|
1787
|
+
};
|
|
1788
|
+
try {
|
|
1789
|
+
nodeFs.mkdirSync(sentinelDir, { recursive: true });
|
|
1790
|
+
// Atomic write: unique temp file + rename, so a concurrent reader (a
|
|
1791
|
+
// guard hook firing mid-write) never observes a partially-written
|
|
1792
|
+
// sentinel. Unique per-process+time so concurrent orchestrator-worktree
|
|
1793
|
+
// invocations sharing the same sentinelDir never collide on the temp name.
|
|
1794
|
+
const tmpPath = `${sentinelPath}.tmp-${process.pid}-${Date.now()}`;
|
|
1795
|
+
nodeFs.writeFileSync(tmpPath, JSON.stringify(payload));
|
|
1796
|
+
nodeFs.renameSync(tmpPath, sentinelPath);
|
|
1797
|
+
return { recorded: true, path: '.gsd/dispatch-isolation-sentinel.json' };
|
|
1798
|
+
} catch (err) {
|
|
1799
|
+
return { recorded: false, path: '.gsd/dispatch-isolation-sentinel.json', error: err && err.message };
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function routeRecordDispatchIsolation({ args, cwd, raw, error }) {
|
|
1804
|
+
// #3045: `routeDispatchIsolation` (the `dispatch-isolation` query) is now
|
|
1805
|
+
// the PRIMARY write path for the sentinel (CORE REDESIGN) — it records
|
|
1806
|
+
// as an unconditional side effect of resolving ISOLATION, which the
|
|
1807
|
+
// workflow must call to learn the value at all. This verb remains as an
|
|
1808
|
+
// explicit fallback for callers that resolve isolation through some
|
|
1809
|
+
// other means (or need to force a specific value, e.g. a caller with no
|
|
1810
|
+
// access to `--force-isolation` context) and for direct test coverage of
|
|
1811
|
+
// the write primitive. Both verbs share exactly one write implementation
|
|
1812
|
+
// (`writeDispatchIsolationSentinel`) so there is only one atomic-write
|
|
1813
|
+
// code path to reason about.
|
|
1814
|
+
//
|
|
1815
|
+
// Best-effort: a write failure here must never fail the workflow — the
|
|
1816
|
+
// guard hooks' own sentinel-absent path degrades to a conservative
|
|
1817
|
+
// registry+config check, so a missing sentinel is safe, just less precise.
|
|
1818
|
+
//
|
|
1819
|
+
// Output: { recorded: true|false, path, error? }
|
|
1820
|
+
const VALID_ISOLATION = new Set(['harness-worktree', 'orchestrator-worktree', 'none']);
|
|
1821
|
+
const isoIdx = args.indexOf('--isolation');
|
|
1822
|
+
const isolation = isoIdx !== -1 ? args[isoIdx + 1] : undefined;
|
|
1823
|
+
if (!isolation || !VALID_ISOLATION.has(isolation)) {
|
|
1824
|
+
error(
|
|
1825
|
+
'Usage: record-dispatch-isolation --isolation <harness-worktree|orchestrator-worktree|none> ' +
|
|
1826
|
+
'[--harness-flag <flag>|--harness-flag=<flag>] [--phase <n>] [--plan <id>]',
|
|
1827
|
+
ERROR_REASON.USAGE,
|
|
1828
|
+
);
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
// #3045 MAJOR: the space-separated form rejects any value starting with
|
|
1832
|
+
// `--` (to avoid swallowing a missing value followed by another flag),
|
|
1833
|
+
// but that is exactly the shape of Cursor's real `harnessIsolationFlag`
|
|
1834
|
+
// — it declares the bare CLI flag `--worktree`
|
|
1835
|
+
// (gsd-core/bin/lib/capability-registry.cjs), which could therefore
|
|
1836
|
+
// never be persisted. (Windsurf declares NO `harnessIsolationFlag` at
|
|
1837
|
+
// all — its `hostIntegration.dispatch.isolation` is `none`; per
|
|
1838
|
+
// ADR-1239 it "genuinely cannot benefit" from worktree isolation
|
|
1839
|
+
// because it lacks named/concurrent subagent dispatch, so this is not a
|
|
1840
|
+
// gap to close for Windsurf.) The `--harness-flag=<value>` equals form
|
|
1841
|
+
// (mirrors the `--cwd=<path>` convention already used by this
|
|
1842
|
+
// dispatcher's top-level arg parsing above) carries the value
|
|
1843
|
+
// unambiguously and is never subject to that guard — any future runtime
|
|
1844
|
+
// whose registered flag happens to be bare-CLI-shaped benefits the same
|
|
1845
|
+
// way Cursor's does.
|
|
1846
|
+
let harnessFlag = null;
|
|
1847
|
+
const flagEqArg = args.find((a) => a.startsWith('--harness-flag='));
|
|
1848
|
+
if (flagEqArg) {
|
|
1849
|
+
const value = flagEqArg.slice('--harness-flag='.length);
|
|
1850
|
+
harnessFlag = value.length > 0 ? value : null;
|
|
1851
|
+
} else {
|
|
1852
|
+
const flagIdx = args.indexOf('--harness-flag');
|
|
1853
|
+
harnessFlag = flagIdx !== -1 && args[flagIdx + 1] && !args[flagIdx + 1].startsWith('--')
|
|
1854
|
+
? args[flagIdx + 1]
|
|
1855
|
+
: null;
|
|
1856
|
+
}
|
|
1857
|
+
const phaseIdx = args.indexOf('--phase');
|
|
1858
|
+
const phase = phaseIdx !== -1 && args[phaseIdx + 1] && !args[phaseIdx + 1].startsWith('--')
|
|
1859
|
+
? args[phaseIdx + 1]
|
|
1860
|
+
: null;
|
|
1861
|
+
const planIdx = args.indexOf('--plan');
|
|
1862
|
+
const plan = planIdx !== -1 && args[planIdx + 1] && !args[planIdx + 1].startsWith('--')
|
|
1863
|
+
? args[planIdx + 1]
|
|
1864
|
+
: null;
|
|
1865
|
+
|
|
1866
|
+
const result = writeDispatchIsolationSentinel(cwd, { isolation, harnessFlag, phase, plan });
|
|
1867
|
+
output(result, raw);
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1632
1870
|
function routeResolveDispatchType({ args, cwd, raw, error }) {
|
|
1633
1871
|
// #2508 Phase 4 Option A: resolve a requested GSD subagent name to the
|
|
1634
1872
|
// type an Agent() call should use on the current runtime. On
|
|
@@ -2033,6 +2271,86 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2033
2271
|
teamsStatus.cmdTeamsStatus(cwd, { active: args.includes('--active') });
|
|
2034
2272
|
}
|
|
2035
2273
|
|
|
2274
|
+
// #3023 follow-up (adversarial review finding): the shared hook bundle's
|
|
2275
|
+
// directory name is runtime-descriptor-driven (bin/install.js
|
|
2276
|
+
// `hostBehaviors.sharedHooksDirName`; default 'hooks', pi renames it to
|
|
2277
|
+
// 'gsd-hooks'). A hardcoded 'hooks' literal in GSD_PREFIX_MANAGED_DIRS left
|
|
2278
|
+
// this scan blind to a renamed bundle: `fs.existsSync(configDir/hooks)` is
|
|
2279
|
+
// false for a pi install, so the ENTIRE gsd-hooks/ tree — including any
|
|
2280
|
+
// user-added file inside it — was invisible to detect-custom-files and
|
|
2281
|
+
// therefore never backed up before the next clean-install wipe (silent
|
|
2282
|
+
// data loss).
|
|
2283
|
+
//
|
|
2284
|
+
// Resolution order, mirroring bin/install.js's own resolveSharedHooksDirName:
|
|
2285
|
+
// 1. Read the per-install runtime marker written by the installer at
|
|
2286
|
+
// <configDir>/gsd-core/.gsd-runtime (#2297).
|
|
2287
|
+
// 2. Look up that runtime's `hostBehaviors.sharedHooksDirName` in the
|
|
2288
|
+
// SHIPPED capability registry (./lib/capability-registry.cjs — a data
|
|
2289
|
+
// module in the same installed tree as this file). Deliberately NOT
|
|
2290
|
+
// `require('bin/install.js')`: that file is never shipped into an
|
|
2291
|
+
// installed tree (the #3024/#2071 bug class), so only the shipped data
|
|
2292
|
+
// module is read here.
|
|
2293
|
+
//
|
|
2294
|
+
// Asymmetric fallback: when the runtime or its descriptor cannot be
|
|
2295
|
+
// determined (an install predating the marker, an unreadable/corrupt
|
|
2296
|
+
// registry, or an unrecognized runtime id) this does NOT guess a single
|
|
2297
|
+
// name — it returns every known candidate name instead. Over-scanning is
|
|
2298
|
+
// safe here: a candidate directory that does not exist is silently skipped
|
|
2299
|
+
// by the caller's `fs.existsSync` guard, and a file already tracked in the
|
|
2300
|
+
// manifest is never reported as custom. Under-scanning is the actual bug
|
|
2301
|
+
// being fixed: it would make a user's file vanish on the next wipe without
|
|
2302
|
+
// ever being backed up.
|
|
2303
|
+
function resolveSharedHooksDirCandidates(configDir) {
|
|
2304
|
+
const DEFAULT_NAME = 'hooks';
|
|
2305
|
+
// A resolved name is joined onto configDir and read back — reject
|
|
2306
|
+
// anything that isn't a plain, separator-free segment so a corrupt
|
|
2307
|
+
// registry value can never walk the scan outside the config root.
|
|
2308
|
+
const isSafeSegment = (name) =>
|
|
2309
|
+
typeof name === 'string' &&
|
|
2310
|
+
name.trim() !== '' &&
|
|
2311
|
+
name.trim() === name &&
|
|
2312
|
+
name !== '.' &&
|
|
2313
|
+
name !== '..' &&
|
|
2314
|
+
!name.includes('/') &&
|
|
2315
|
+
!name.includes('\\');
|
|
2316
|
+
|
|
2317
|
+
let registry = null;
|
|
2318
|
+
try {
|
|
2319
|
+
registry = require('./lib/capability-registry.cjs');
|
|
2320
|
+
} catch {
|
|
2321
|
+
registry = null;
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
const knownNames = new Set([DEFAULT_NAME]);
|
|
2325
|
+
if (registry && registry.runtimes && typeof registry.runtimes === 'object') {
|
|
2326
|
+
for (const desc of Object.values(registry.runtimes)) {
|
|
2327
|
+
const name = desc && desc.runtime && desc.runtime.hostBehaviors &&
|
|
2328
|
+
desc.runtime.hostBehaviors.sharedHooksDirName;
|
|
2329
|
+
if (isSafeSegment(name)) knownNames.add(name);
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
let runtimeId = null;
|
|
2334
|
+
try {
|
|
2335
|
+
const markerPath = path.join(configDir, 'gsd-core', '.gsd-runtime');
|
|
2336
|
+
const raw = fs.readFileSync(markerPath, 'utf8').trim();
|
|
2337
|
+
runtimeId = raw || null;
|
|
2338
|
+
} catch {
|
|
2339
|
+
runtimeId = null;
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
if (runtimeId && registry && registry.runtimes && registry.runtimes[runtimeId]) {
|
|
2343
|
+
const desc = registry.runtimes[runtimeId];
|
|
2344
|
+
const name = desc && desc.runtime && desc.runtime.hostBehaviors &&
|
|
2345
|
+
desc.runtime.hostBehaviors.sharedHooksDirName;
|
|
2346
|
+
return [isSafeSegment(name) ? name : DEFAULT_NAME];
|
|
2347
|
+
}
|
|
2348
|
+
|
|
2349
|
+
// Runtime undeterminable: scan every known candidate (see asymmetric
|
|
2350
|
+
// fallback comment above).
|
|
2351
|
+
return Array.from(knownNames);
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2036
2354
|
async function routeDetectCustomFiles({ args, cwd, raw, error }) {
|
|
2037
2355
|
const configDirIdx = args.indexOf('--config-dir');
|
|
2038
2356
|
const configDir = configDirIdx !== -1 ? args[configDirIdx + 1] : null;
|
|
@@ -2073,7 +2391,7 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2073
2391
|
];
|
|
2074
2392
|
const GSD_PREFIX_MANAGED_DIRS = [
|
|
2075
2393
|
'agents',
|
|
2076
|
-
|
|
2394
|
+
...resolveSharedHooksDirCandidates(resolvedConfigDir),
|
|
2077
2395
|
'skills',
|
|
2078
2396
|
];
|
|
2079
2397
|
|
|
@@ -2605,6 +2923,133 @@ function dispatchOverlayCapabilityCommand({ command, args, cwd, raw, error, load
|
|
|
2605
2923
|
}
|
|
2606
2924
|
}
|
|
2607
2925
|
|
|
2926
|
+
// `gsd_run query context-predicates` — selector surface for the CONTEXT.md
|
|
2927
|
+
// predicate fact-store (ADR-1671, #2928 Phase 1 row S9). Parses the
|
|
2928
|
+
// repo-root CONTEXT.md LIVE via the compiled context-predicates.cjs on
|
|
2929
|
+
// every call — it never reads the committed docs/CONTEXT-INDEX.json (that
|
|
2930
|
+
// artifact is a CI drift-guard byproduct, not a query source, so it can
|
|
2931
|
+
// never go stale relative to the live predicates it answers about).
|
|
2932
|
+
//
|
|
2933
|
+
// Selectors: --class <CLASS>, --prefix <dotted.prefix>, --contains <text>.
|
|
2934
|
+
// At least one is required. When more than one is given they are ANDed
|
|
2935
|
+
// together — the same documented precedence selectPredicates() itself
|
|
2936
|
+
// implements (see context-predicates.cjs doc comment: "Select predicates
|
|
2937
|
+
// by one or more optional criteria (ANDed together)"); no selector is
|
|
2938
|
+
// silently dropped or overridden by another.
|
|
2939
|
+
//
|
|
2940
|
+
// Flag parsing mirrors routePromptBudget's Map-based flagMap: the three
|
|
2941
|
+
// known flags are recognized in both the space-separated `--flag value`
|
|
2942
|
+
// form and the inline-assignment `--flag=value` form (the latter is the
|
|
2943
|
+
// escape hatch for a flag-shaped selector value, e.g. `--contains=--dry-run`
|
|
2944
|
+
// — #2928 review finding C; the space-separated form has no such escape by
|
|
2945
|
+
// design, since a following `--...` token always reads as a missing value).
|
|
2946
|
+
// `--class=` (empty value) and `--class==A` (double-equals typo shape)
|
|
2947
|
+
// are rejected the same way under either form. On a duplicate flag the
|
|
2948
|
+
// FIRST occurrence wins (`Map.set` only fires when the key is absent),
|
|
2949
|
+
// which is deterministic across repeated invocations with identical argv.
|
|
2950
|
+
//
|
|
2951
|
+
// Prototype-pollution safety: selector values are only ever compared via
|
|
2952
|
+
// `===`/`.startsWith()`/`.includes()` against ordinary string fields — this
|
|
2953
|
+
// route never uses a user-supplied string as an object property key
|
|
2954
|
+
// (`obj[userValue] = ...`), so `--class __proto__` / `constructor` /
|
|
2955
|
+
// `prototype` are just non-matching ordinary strings, not property-access
|
|
2956
|
+
// vectors. `flagMap` itself is a `Map`, immune to prototype pollution by
|
|
2957
|
+
// construction.
|
|
2958
|
+
function routeContextPredicates({ args, cwd, raw, error }) {
|
|
2959
|
+
const { parsePredicates, selectPredicates } = require('./lib/context-predicates.cjs');
|
|
2960
|
+
|
|
2961
|
+
const KNOWN_FLAGS = new Set(['--class', '--prefix', '--contains']);
|
|
2962
|
+
const flagMap = new Map();
|
|
2963
|
+
for (let i = 1; i < args.length; i++) {
|
|
2964
|
+
const current = args[i];
|
|
2965
|
+
if (typeof current !== 'string' || !current.startsWith('--')) continue;
|
|
2966
|
+
|
|
2967
|
+
// Inline-assignment escape hatch (`--flag=value`, mirrors the `--config-dir=`/
|
|
2968
|
+
// `--runtime=` convention in routeUpdateContext elsewhere in this file). This is
|
|
2969
|
+
// the ONLY way to pass a flag-shaped selector value (e.g. searching CONTEXT.md
|
|
2970
|
+
// for the literal substring "--dry-run"): the space-separated form below always
|
|
2971
|
+
// treats a following `--...` token as a missing value, by design, so it has no
|
|
2972
|
+
// escape hatch on its own (#2928 review finding C).
|
|
2973
|
+
const eqFlag = [...KNOWN_FLAGS].find((f) => current.startsWith(`${f}=`));
|
|
2974
|
+
if (eqFlag) {
|
|
2975
|
+
const value = current.slice(eqFlag.length + 1);
|
|
2976
|
+
// Reject an empty value (`--class=`) and the `--class==A` double-equals typo
|
|
2977
|
+
// shape (a value starting with `=`) the same way the pre-existing malformed-
|
|
2978
|
+
// assignment behavior did — never silently accept "=A" as a literal value.
|
|
2979
|
+
if (value === '' || value.startsWith('=')) {
|
|
2980
|
+
error(`context-predicates: ${eqFlag} requires a non-empty value`, ERROR_REASON.USAGE);
|
|
2981
|
+
return;
|
|
2982
|
+
}
|
|
2983
|
+
if (!flagMap.has(eqFlag)) flagMap.set(eqFlag, value);
|
|
2984
|
+
continue;
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
if (!KNOWN_FLAGS.has(current)) {
|
|
2988
|
+
error(`Unknown flag for context-predicates: ${current}`, ERROR_REASON.USAGE);
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
const next = args[i + 1];
|
|
2992
|
+
if (next === undefined || next.startsWith('--')) {
|
|
2993
|
+
if (!flagMap.has(current)) flagMap.set(current, null);
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
if (!flagMap.has(current)) flagMap.set(current, next);
|
|
2997
|
+
i++;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
const hasClass = flagMap.has('--class');
|
|
3001
|
+
const hasPrefix = flagMap.has('--prefix');
|
|
3002
|
+
const hasContains = flagMap.has('--contains');
|
|
3003
|
+
|
|
3004
|
+
if (!hasClass && !hasPrefix && !hasContains) {
|
|
3005
|
+
error(
|
|
3006
|
+
'Usage: gsd-tools query context-predicates --class <CLASS> | --prefix <dotted.prefix> | --contains <text> ' +
|
|
3007
|
+
'(selectors are ANDed when combined)',
|
|
3008
|
+
ERROR_REASON.USAGE,
|
|
3009
|
+
);
|
|
3010
|
+
return;
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
const requireNonEmpty = (flagName, rawValue) => {
|
|
3014
|
+
if (rawValue === null || rawValue === undefined || rawValue.trim() === '') {
|
|
3015
|
+
error(`context-predicates: ${flagName} requires a non-empty value`, ERROR_REASON.USAGE);
|
|
3016
|
+
return null;
|
|
3017
|
+
}
|
|
3018
|
+
return rawValue;
|
|
3019
|
+
};
|
|
3020
|
+
|
|
3021
|
+
const opts = {};
|
|
3022
|
+
if (hasClass) {
|
|
3023
|
+
const v = requireNonEmpty('--class', flagMap.get('--class'));
|
|
3024
|
+
if (v === null) return;
|
|
3025
|
+
opts.klass = v;
|
|
3026
|
+
}
|
|
3027
|
+
if (hasPrefix) {
|
|
3028
|
+
const v = requireNonEmpty('--prefix', flagMap.get('--prefix'));
|
|
3029
|
+
if (v === null) return;
|
|
3030
|
+
opts.prefix = v;
|
|
3031
|
+
}
|
|
3032
|
+
if (hasContains) {
|
|
3033
|
+
const v = requireNonEmpty('--contains', flagMap.get('--contains'));
|
|
3034
|
+
if (v === null) return;
|
|
3035
|
+
opts.contains = v;
|
|
3036
|
+
}
|
|
3037
|
+
|
|
3038
|
+
const contextMdPath = path.join(__dirname, '..', '..', 'CONTEXT.md');
|
|
3039
|
+
let markdown;
|
|
3040
|
+
try {
|
|
3041
|
+
markdown = fs.readFileSync(contextMdPath, 'utf8');
|
|
3042
|
+
} catch (err) {
|
|
3043
|
+
error(`context-predicates: cannot read ${contextMdPath}: ${err && err.message}`, ERROR_REASON.USAGE);
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
|
|
3047
|
+
const { predicates } = parsePredicates(markdown);
|
|
3048
|
+
const matches = selectPredicates(predicates, opts);
|
|
3049
|
+
|
|
3050
|
+
output({ matched: matches.length, predicates: matches }, raw);
|
|
3051
|
+
}
|
|
3052
|
+
|
|
2608
3053
|
function routeUpdateContext({ args, cwd, raw, error }) {
|
|
2609
3054
|
// #498: resolve the installed GSD version, scope, runtime, and config dir
|
|
2610
3055
|
// for /gsd:update. Replaces ~280 lines of inline bash in update.md with a
|
|
@@ -2912,6 +3357,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
2912
3357
|
'normalize-test-command': routeNormalizeTestCommand,
|
|
2913
3358
|
'dispatch-should-flatten': routeDispatchShouldFlatten,
|
|
2914
3359
|
'dispatch-isolation': routeDispatchIsolation,
|
|
3360
|
+
'record-dispatch-isolation': routeRecordDispatchIsolation,
|
|
2915
3361
|
'resolve-dispatch-type': routeResolveDispatchType,
|
|
2916
3362
|
'agent-skills': routeAgentSkills,
|
|
2917
3363
|
'skill-manifest': routeSkillManifest,
|
|
@@ -2940,6 +3386,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
2940
3386
|
'restore-custom-files': routeRestoreCustomFiles,
|
|
2941
3387
|
'from-gsd2': routeFromGsd2,
|
|
2942
3388
|
'prompt-budget': routePromptBudget,
|
|
3389
|
+
'context-predicates': routeContextPredicates,
|
|
2943
3390
|
'review-lane': routeReviewLane,
|
|
2944
3391
|
'update-context': routeUpdateContext,
|
|
2945
3392
|
'classify-confidence': routeClassifyConfidence,
|
|
@@ -2948,6 +3395,7 @@ const HOST_COMMAND_ROUTERS = {
|
|
|
2948
3395
|
'user-story': routeUserStory,
|
|
2949
3396
|
'drift-guard': routeDriftGuard,
|
|
2950
3397
|
'windows': routeWindows,
|
|
3398
|
+
'skills-root': routeSkillsRoot,
|
|
2951
3399
|
};
|
|
2952
3400
|
|
|
2953
3401
|
// Returns true when consumed (suppress "Unknown command"), false to fall
|
|
@@ -3143,6 +3591,121 @@ function runWithTimeout(argv) {
|
|
|
3143
3591
|
|
|
3144
3592
|
// ─── CLI Router ───────────────────────────────────────────────────────────────
|
|
3145
3593
|
|
|
3594
|
+
// Top-level usage string — emitted by `gsd-tools` (no args) and by
|
|
3595
|
+
// `gsd-tools --help` / any `--help` request below.
|
|
3596
|
+
// CR feedback: the command list must enumerate every top-level command
|
|
3597
|
+
// supported by the dispatcher so `--help` is actually useful for
|
|
3598
|
+
// discovery; previously it was a partial subset that didn't include
|
|
3599
|
+
// phase / roadmap / milestone / progress / etc.
|
|
3600
|
+
//
|
|
3601
|
+
// Module-scoped (not function-local) so it can be exported and compared
|
|
3602
|
+
// against HOST_COMMAND_ROUTERS in a parity test (DEFECT.GENERATIVE-FIX) —
|
|
3603
|
+
// this string and HOST_COMMAND_ROUTERS/SKIP_ROOT_RESOLUTION are three
|
|
3604
|
+
// independently hand-maintained sites and nothing previously caught them
|
|
3605
|
+
// drifting apart when a query command was added to only one or two.
|
|
3606
|
+
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
3607
|
+
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
3608
|
+
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
|
|
3609
|
+
'context-predicates, current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
3610
|
+
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
3611
|
+
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
3612
|
+
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
3613
|
+
'profile-sample, progress, project-instruction-file, prompt-budget, quick-tasks-append, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, scaffold, smart-entry, state, ' +
|
|
3614
|
+
'config-set-model-profile, dispatch-isolation, dispatch-should-flatten, record-dispatch-isolation, estimate-calibrate, estimate-calibration, estimate-check, resolve-dispatch-type, ' +
|
|
3615
|
+
'resolve-execution, review-lane, skill-manifest, skills-root, state-snapshot, stats, summary-extract, teams-status, todo, uat, update-context, verification, websearch, windows, ' +
|
|
3616
|
+
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
3617
|
+
'Global flags:\n' +
|
|
3618
|
+
' --raw Emit raw output without post-processing\n' +
|
|
3619
|
+
' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
|
|
3620
|
+
' --cwd <path> Override working directory for project-root resolution\n' +
|
|
3621
|
+
' --ws <name> Override active workstream (or set GSD_WORKSTREAM)\n' +
|
|
3622
|
+
' --json-errors Emit structured JSON error objects on stderr (or set GSD_JSON_ERRORS=1)\n\n' +
|
|
3623
|
+
'For command-specific argument requirements, invoke the command without args ' +
|
|
3624
|
+
'(e.g. `gsd-tools phase add`) — the resulting error lists what is required.';
|
|
3625
|
+
|
|
3626
|
+
// Multi-repo guard: resolve project root for commands that read/write .planning/.
|
|
3627
|
+
// Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary
|
|
3628
|
+
// filesystem traversal on every invocation.
|
|
3629
|
+
// 'loop' and 'capability' are intentionally NOT in SKIP_ROOT_RESOLUTION.
|
|
3630
|
+
// Both are registry/config queries that resolve activation via
|
|
3631
|
+
// .planning/config.json; they need the project root (cwd) for correct
|
|
3632
|
+
// `when` key resolution. If one is ever moved to SKIP_ROOT_RESOLUTION,
|
|
3633
|
+
// move the other at the same time (keep them consistent).
|
|
3634
|
+
//
|
|
3635
|
+
// Module-scoped for the same reason as TOP_LEVEL_USAGE above — kept
|
|
3636
|
+
// module-private and exposed to the dispatch-table/help-string/skip-list
|
|
3637
|
+
// parity test only through the read-only skipsRootResolution() predicate
|
|
3638
|
+
// below (never as the live Set itself; see that function's doc comment).
|
|
3639
|
+
const SKIP_ROOT_RESOLUTION = new Set([
|
|
3640
|
+
'generate-slug', 'current-timestamp', 'verify-path-exists',
|
|
3641
|
+
// #2844: verify-summary was previously skipped, leaving relative file-claim
|
|
3642
|
+
// paths resolved against the raw process.cwd() — invoking from a subdirectory
|
|
3643
|
+
// manufactured "missing files" on an otherwise-correct SUMMARY. It now goes
|
|
3644
|
+
// through findProjectRoot so claims resolve against the project root.
|
|
3645
|
+
'template', 'frontmatter', 'detect-custom-files',
|
|
3646
|
+
// #1854: restore-custom-files operates on a runtime config dir passed
|
|
3647
|
+
// explicitly via --config-dir; it never reads .planning/.
|
|
3648
|
+
'restore-custom-files',
|
|
3649
|
+
'worktree', 'prompt-budget',
|
|
3650
|
+
// context-predicates is a pure repo-root CONTEXT.md read (like
|
|
3651
|
+
// prompt-budget); it never touches .planning/, so it needs no project
|
|
3652
|
+
// root resolution and must work from any cwd (including one with no
|
|
3653
|
+
// .planning/ directory at all).
|
|
3654
|
+
'context-predicates',
|
|
3655
|
+
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
3656
|
+
'user-story', // pure string validation — no .planning/ access needed
|
|
3657
|
+
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
|
|
3658
|
+
// .planning/ access needed, and resolving project root would break workflow
|
|
3659
|
+
// invocations that run before .planning/ exists (new-project Step 1).
|
|
3660
|
+
'project-instruction-file',
|
|
3661
|
+
// #1579: eval.score is pure arithmetic (covered/total + infra weights); it
|
|
3662
|
+
// needs no .planning/ access, so skip the findProjectRoot traversal.
|
|
3663
|
+
'eval',
|
|
3664
|
+
]);
|
|
3665
|
+
|
|
3666
|
+
// Read-only accessor for SKIP_ROOT_RESOLUTION (DEFECT.MUTABLE-EXPORTED-SET,
|
|
3667
|
+
// #2928 review). The Set above stays module-private and mutable internally
|
|
3668
|
+
// (main() only ever calls .has() on it), but exporting the live Set directly
|
|
3669
|
+
// would let any importer call .add()/.delete() on it — Object.freeze() does
|
|
3670
|
+
// not lock Set.prototype.add/delete, so freezing the instance would not have
|
|
3671
|
+
// closed this — and silently change dispatch behavior for every caller in the
|
|
3672
|
+
// process. Export this predicate instead; it exposes membership without
|
|
3673
|
+
// exposing a mutation surface.
|
|
3674
|
+
function skipsRootResolution(command) {
|
|
3675
|
+
return SKIP_ROOT_RESOLUTION.has(command);
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
/**
|
|
3679
|
+
* Resolve the worktree root for a given cwd, warning to stderr when git
|
|
3680
|
+
* could not determine it (reason 'git_timed_out') rather than silently
|
|
3681
|
+
* trusting a best-effort fallback (#3050). Extracted from main() so it can
|
|
3682
|
+
* be driven directly in tests via injected deps.
|
|
3683
|
+
*
|
|
3684
|
+
* @param {string} cwd
|
|
3685
|
+
* @param {{ existsSync?: (p: string) => boolean, resolveWorktreeRoot?: (cwd: string) => { root: string, reason: string }, writeWarning?: (msg: string) => void }} [deps]
|
|
3686
|
+
* @returns {string} resolved cwd
|
|
3687
|
+
*/
|
|
3688
|
+
function resolveMainWorktreeCwd(cwd, deps = {}) {
|
|
3689
|
+
const existsSync = deps.existsSync || fs.existsSync;
|
|
3690
|
+
const resolveWorktreeRoot = deps.resolveWorktreeRoot || require('./lib/worktree-safety.cjs').resolveWorktreeRoot;
|
|
3691
|
+
const writeWarning = deps.writeWarning || ((msg) => process.stderr.write(msg));
|
|
3692
|
+
|
|
3693
|
+
if (existsSync(path.join(cwd, '.planning'))) {
|
|
3694
|
+
return cwd;
|
|
3695
|
+
}
|
|
3696
|
+
const { root: worktreeRoot, reason: worktreeRootReason } = resolveWorktreeRoot(cwd);
|
|
3697
|
+
if (worktreeRootReason === 'git_timed_out') {
|
|
3698
|
+
writeWarning(
|
|
3699
|
+
'WARNING: could not determine the git worktree root (git timed out). ' +
|
|
3700
|
+
'Planning artifacts (STATE.md, ROADMAP.md, etc.) may be written to the ' +
|
|
3701
|
+
`wrong tree — proceeding with "${worktreeRoot}" as a best-effort fallback. ` +
|
|
3702
|
+
'Retry the command; if this persists, check for a stalled filesystem mount ' +
|
|
3703
|
+
'or a stale git index lock (.git/index.lock) in this worktree.\n'
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
return worktreeRoot;
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3146
3709
|
async function main() {
|
|
3147
3710
|
let args = process.argv.slice(2);
|
|
3148
3711
|
|
|
@@ -3200,13 +3763,7 @@ async function main() {
|
|
|
3200
3763
|
// Resolve worktree root: in a linked worktree, .planning/ lives in the main worktree.
|
|
3201
3764
|
// However, in monorepo worktrees where the subdirectory itself owns .planning/,
|
|
3202
3765
|
// skip worktree resolution — the CWD is already the correct project root.
|
|
3203
|
-
|
|
3204
|
-
if (!fs.existsSync(path.join(cwd, '.planning'))) {
|
|
3205
|
-
const worktreeRoot = resolveWorktreeRoot(cwd);
|
|
3206
|
-
if (worktreeRoot !== cwd) {
|
|
3207
|
-
cwd = worktreeRoot;
|
|
3208
|
-
}
|
|
3209
|
-
}
|
|
3766
|
+
cwd = resolveMainWorktreeCwd(cwd);
|
|
3210
3767
|
|
|
3211
3768
|
// Optional workstream override for parallel milestone work.
|
|
3212
3769
|
// Priority: --ws flag > GSD_WORKSTREAM env var > session/shared pointer > null.
|
|
@@ -3276,30 +3833,6 @@ async function main() {
|
|
|
3276
3833
|
}
|
|
3277
3834
|
}
|
|
3278
3835
|
|
|
3279
|
-
// Top-level usage string — emitted by `gsd-tools` (no args) and by
|
|
3280
|
-
// `gsd-tools --help` / any `--help` request below.
|
|
3281
|
-
// CR feedback: the command list must enumerate every top-level command
|
|
3282
|
-
// supported by the dispatcher so `--help` is actually useful for
|
|
3283
|
-
// discovery; previously it was a partial subset that didn't include
|
|
3284
|
-
// phase / roadmap / milestone / progress / etc.
|
|
3285
|
-
const TOP_LEVEL_USAGE = 'Usage: gsd-tools <command> [args] [--raw] [--pick <field>] [--cwd <path>] [--ws <name>] [--json-errors]\n' +
|
|
3286
|
-
'Commands: agent, agent-skills, assumption-delta, audit-open, audit-uat, check, check-commit, commit, commit-to-subrepo, pr-subrepo, ' +
|
|
3287
|
-
'config-ensure-section, config-get, config-new-project, config-path, config-set, migrate-config, normalize-test-command, ' +
|
|
3288
|
-
'current-timestamp, detect-custom-files, docs-init, drift-guard, effort, extract-messages, find-phase, ' +
|
|
3289
|
-
'from-gsd2, frontmatter, gap-analysis, generate-claude-md, generate-claude-profile, ' +
|
|
3290
|
-
'generate-dev-preferences, generate-slug, graphify, history-digest, init, intel, ' +
|
|
3291
|
-
'capability, classify-confidence, git, learnings, list-seeds, list-todos, loop, milestone, package-legitimacy, phase, phase-plan-index, phases, profile-questionnaire, ' +
|
|
3292
|
-
'profile-sample, progress, project-instruction-file, prompt-budget, quick-tasks-append, requirements, research-plan, research-store, resolve-granularity, resolve-model, restore-custom-files, roadmap, scaffold, smart-entry, state, ' +
|
|
3293
|
-
'task, template, user-story, validate, verify, verify-path-exists, verify-summary, eval, workstream, worktree\n\n' +
|
|
3294
|
-
'Global flags:\n' +
|
|
3295
|
-
' --raw Emit raw output without post-processing\n' +
|
|
3296
|
-
' --pick <field> Extract a single field from JSON output (dot/bracket notation)\n' +
|
|
3297
|
-
' --cwd <path> Override working directory for project-root resolution\n' +
|
|
3298
|
-
' --ws <name> Override active workstream (or set GSD_WORKSTREAM)\n' +
|
|
3299
|
-
' --json-errors Emit structured JSON error objects on stderr (or set GSD_JSON_ERRORS=1)\n\n' +
|
|
3300
|
-
'For command-specific argument requirements, invoke the command without args ' +
|
|
3301
|
-
'(e.g. `gsd-tools phase add`) — the resulting error lists what is required.';
|
|
3302
|
-
|
|
3303
3836
|
if (!command) {
|
|
3304
3837
|
error(TOP_LEVEL_USAGE);
|
|
3305
3838
|
}
|
|
@@ -3326,35 +3859,6 @@ async function main() {
|
|
|
3326
3859
|
}
|
|
3327
3860
|
}
|
|
3328
3861
|
|
|
3329
|
-
// Multi-repo guard: resolve project root for commands that read/write .planning/.
|
|
3330
|
-
// Skip for pure-utility commands that don't touch .planning/ to avoid unnecessary
|
|
3331
|
-
// filesystem traversal on every invocation.
|
|
3332
|
-
// 'loop' and 'capability' are intentionally NOT in SKIP_ROOT_RESOLUTION.
|
|
3333
|
-
// Both are registry/config queries that resolve activation via
|
|
3334
|
-
// .planning/config.json; they need the project root (cwd) for correct
|
|
3335
|
-
// `when` key resolution. If one is ever moved to SKIP_ROOT_RESOLUTION,
|
|
3336
|
-
// move the other at the same time (keep them consistent).
|
|
3337
|
-
const SKIP_ROOT_RESOLUTION = new Set([
|
|
3338
|
-
'generate-slug', 'current-timestamp', 'verify-path-exists',
|
|
3339
|
-
// #2844: verify-summary was previously skipped, leaving relative file-claim
|
|
3340
|
-
// paths resolved against the raw process.cwd() — invoking from a subdirectory
|
|
3341
|
-
// manufactured "missing files" on an otherwise-correct SUMMARY. It now goes
|
|
3342
|
-
// through findProjectRoot so claims resolve against the project root.
|
|
3343
|
-
'template', 'frontmatter', 'detect-custom-files',
|
|
3344
|
-
// #1854: restore-custom-files operates on a runtime config dir passed
|
|
3345
|
-
// explicitly via --config-dir; it never reads .planning/.
|
|
3346
|
-
'restore-custom-files',
|
|
3347
|
-
'worktree', 'prompt-budget',
|
|
3348
|
-
'research-store', 'research-plan', 'package-legitimacy', 'classify-confidence',
|
|
3349
|
-
'user-story', // pure string validation — no .planning/ access needed
|
|
3350
|
-
// #1529: pure runtime→filename projection via getProjectInstructionFile; no
|
|
3351
|
-
// .planning/ access needed, and resolving project root would break workflow
|
|
3352
|
-
// invocations that run before .planning/ exists (new-project Step 1).
|
|
3353
|
-
'project-instruction-file',
|
|
3354
|
-
// #1579: eval.score is pure arithmetic (covered/total + infra weights); it
|
|
3355
|
-
// needs no .planning/ access, so skip the findProjectRoot traversal.
|
|
3356
|
-
'eval',
|
|
3357
|
-
]);
|
|
3358
3862
|
if (!SKIP_ROOT_RESOLUTION.has(command)) {
|
|
3359
3863
|
cwd = findProjectRoot(cwd);
|
|
3360
3864
|
}
|
|
@@ -3511,5 +4015,14 @@ if (require.main === module) {
|
|
|
3511
4015
|
// synthetic registry + requireModule injections.
|
|
3512
4016
|
// ADR-1244 Phase 5: export dispatchOverlayCapabilityCommand + defaultRequireFromInstallRoot for
|
|
3513
4017
|
// the third-party overlay dispatch + install-root confinement tests.
|
|
3514
|
-
module.exports = {
|
|
4018
|
+
module.exports = {
|
|
4019
|
+
dispatchCapabilityCommand,
|
|
4020
|
+
dispatchOverlayCapabilityCommand,
|
|
4021
|
+
defaultRequireFromInstallRoot,
|
|
4022
|
+
dispatchHostCommand,
|
|
4023
|
+
HOST_COMMAND_ROUTERS,
|
|
4024
|
+
TOP_LEVEL_USAGE,
|
|
4025
|
+
skipsRootResolution,
|
|
4026
|
+
resolveMainWorktreeCwd,
|
|
4027
|
+
};
|
|
3515
4028
|
|