@opengsd/gsd-core 1.12.0 → 1.13.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 +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +239 -67
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +195 -50
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-registry.cjs +514 -114
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +120 -4
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +85 -2
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +337 -13
- package/gsd-core/bin/lib/config-loader.cjs +3 -0
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +132 -15
- package/gsd-core/bin/lib/install-engine.cjs +184 -12
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +22 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/milestone.cjs +22 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-id.cjs +251 -9
- package/gsd-core/bin/lib/phase.cjs +774 -35
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
- package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
- package/gsd-core/bin/lib/roadmap.cjs +187 -58
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +30 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
- package/gsd-core/bin/lib/state-transition.cjs +333 -44
- package/gsd-core/bin/lib/state.cjs +684 -125
- package/gsd-core/bin/lib/surface.cjs +23 -8
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1419 -515
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +273 -12
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +346 -16
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +14 -9
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- 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/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +2 -0
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
- package/gsd-core/workflows/code-review-fix.md +2 -0
- package/gsd-core/workflows/code-review.md +73 -31
- package/gsd-core/workflows/complete-milestone.md +13 -4
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +32 -14
- package/gsd-core/workflows/execute-plan.md +8 -8
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +2 -0
- package/gsd-core/workflows/milestone-summary.md +2 -0
- 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 +5 -3
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +130 -12
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +11 -3
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +13 -3
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +41 -38
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +49 -3
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +283 -12
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-statusline.js +7 -3
- package/hooks/dist/gsd-validate-commit.sh +444 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +283 -12
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-statusline.js +7 -3
- package/hooks/gsd-validate-commit.sh +444 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -5
- package/scripts/build-hooks.js +11 -4
- package/scripts/ci-test-scope.cjs +7 -0
- package/scripts/docs-guard-registry.cjs +10 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-test-file-count.allowlist.json +3 -1
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
|
@@ -114,6 +114,50 @@ function readGsdEffectiveModelOverrides(targetDir = null, options = {}) {
|
|
|
114
114
|
// Per-project wins on conflict; preserve non-conflicting global keys.
|
|
115
115
|
return { ...(global || {}), ...(projectOverrides || {}) };
|
|
116
116
|
}
|
|
117
|
+
function readGsdAgentTools(config) {
|
|
118
|
+
const raw = config?.agent_tools;
|
|
119
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
120
|
+
return null;
|
|
121
|
+
const result = {};
|
|
122
|
+
for (const [selector, value] of Object.entries(raw)) {
|
|
123
|
+
// An explicitly present selector always has a verdict. Invalid values are
|
|
124
|
+
// empty so a project config cannot accidentally restore a global grant.
|
|
125
|
+
result[selector] = Array.isArray(value)
|
|
126
|
+
? value.filter((entry) => typeof entry === 'string')
|
|
127
|
+
.map((entry) => entry.trim())
|
|
128
|
+
.filter((entry) => entry.length > 0
|
|
129
|
+
&& !entry.endsWith(':')
|
|
130
|
+
&& !/[\s\u0000-\u001F\u007F-\u009F,#"'\u2028\u2029]/.test(entry))
|
|
131
|
+
: [];
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve valid install-time `agent_tools` grants from global defaults and
|
|
137
|
+
* the nearest project config. Project selectors replace only matching global
|
|
138
|
+
* selectors; malformed whole files remain harmless absence like the existing
|
|
139
|
+
* model override resolver.
|
|
140
|
+
*/
|
|
141
|
+
function readGsdEffectiveAgentTools(targetDir = null, options = {}) {
|
|
142
|
+
const home = options.homedir ? options.homedir() : node_os_1.default.homedir();
|
|
143
|
+
const globalConfig = _readGsdConfigFile(node_path_1.default.join(home, '.gsd', 'defaults.json'), 'global defaults');
|
|
144
|
+
let projectConfig = null;
|
|
145
|
+
if (targetDir) {
|
|
146
|
+
const candidate = _findAncestorGsdConfigPath(targetDir);
|
|
147
|
+
if (candidate) {
|
|
148
|
+
projectConfig = _readGsdConfigFile(candidate, 'project config');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const global = readGsdAgentTools(globalConfig);
|
|
152
|
+
const project = readGsdAgentTools(projectConfig);
|
|
153
|
+
if (projectConfig
|
|
154
|
+
&& Object.prototype.hasOwnProperty.call(projectConfig, 'agent_tools')
|
|
155
|
+
&& project === null)
|
|
156
|
+
return {};
|
|
157
|
+
if (!global && !project)
|
|
158
|
+
return null;
|
|
159
|
+
return { ...(global || {}), ...(project || {}) };
|
|
160
|
+
}
|
|
117
161
|
/**
|
|
118
162
|
* Build a runtime-aware tier resolver for the install path (#2517).
|
|
119
163
|
*
|
|
@@ -230,6 +274,7 @@ function resolveAgentModelOverride(agentName, modelOverrides, runtimeResolver) {
|
|
|
230
274
|
module.exports = {
|
|
231
275
|
readGsdGlobalModelOverrides,
|
|
232
276
|
readGsdEffectiveModelOverrides,
|
|
277
|
+
readGsdEffectiveAgentTools,
|
|
233
278
|
readGsdRuntimeProfileResolver,
|
|
234
279
|
resolveAgentModelOverride,
|
|
235
280
|
};
|
|
@@ -32,7 +32,7 @@ const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs")
|
|
|
32
32
|
const external_descriptor_trust_cjs_1 = require("./external-descriptor-trust.cjs");
|
|
33
33
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
34
|
const conversionModule = require("./runtime-artifact-conversion.cjs");
|
|
35
|
-
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, } = conversionModule;
|
|
35
|
+
const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _processAttribution, normalizeAgentBodyForRuntime: _normalizeAgentBodyForRuntime, readGsdCommandNames: _readGsdCommandNames, deriveAgentName: _deriveAgentName, applyAgentFrontmatterExtensions: _applyAgentFrontmatterExtensions, appendAgentTools: _appendAgentTools, } = conversionModule;
|
|
36
36
|
// #2995 (epic #1671 Phase 6.4): agent bodies join the fragment model. Markers are
|
|
37
37
|
// stripped at emit BEFORE any path rewrite or converter runs, so a `.claude/` ->
|
|
38
38
|
// `.windsurf/` regex can never reach inside a marker attribute and corrupt it —
|
|
@@ -40,6 +40,8 @@ const { applyAgentPathRewrites: _applyAgentPathRewrites, processAttribution: _pr
|
|
|
40
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
41
41
|
const workflowFragmentsModule = require("./workflow-fragments.cjs");
|
|
42
42
|
const { composeWorkflow: _composeWorkflow } = workflowFragmentsModule;
|
|
43
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
44
|
+
const installModelOverrideResolver = require("./install-model-override-resolver.cjs");
|
|
43
45
|
// ---------------------------------------------------------------------------
|
|
44
46
|
// Profile definitions
|
|
45
47
|
// ---------------------------------------------------------------------------
|
|
@@ -896,14 +898,14 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
896
898
|
* agent loop in bin/install.js exactly:
|
|
897
899
|
* 1. applyAgentPathRewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
898
900
|
* 2. processAttribution (Co-Authored-By policy)
|
|
899
|
-
* 3.
|
|
900
|
-
* 4.
|
|
901
|
+
* 3. appendAgentTools (#4032: validated agent_tools grants, before host conversion)
|
|
902
|
+
* 4. converter (runtime-specific frontmatter/body transform)
|
|
903
|
+
* 5. applyAgentFrontmatterExtensions (#2875 Part 2: effort/disallowedTools,
|
|
901
904
|
* gated by hostBehaviors.agentFrontmatterExtensions — no-op for a runtime
|
|
902
905
|
* that declares nothing, e.g. every non-Claude runtime today)
|
|
903
|
-
*
|
|
904
|
-
* When `agentCtx` is absent, only
|
|
905
|
-
*
|
|
906
|
-
* that handle cross-cutting inside their converters).
|
|
906
|
+
* 6. normalizeAgentBodyForRuntime (colon→hyphen refs; no-op for trivial group)
|
|
907
|
+
* When `agentCtx` is absent, only global `agent_tools` augmentation and the
|
|
908
|
+
* converter run; other cross-cutting remains absent for backward compatibility.
|
|
907
909
|
*
|
|
908
910
|
* @param srcAgentsDir source agents directory (e.g. agents/)
|
|
909
911
|
* @param resolvedProfile profile filter from resolveProfile()
|
|
@@ -917,8 +919,7 @@ function stageSkillsForRuntimeAsSkills(srcCommandsDir, resolvedProfile, converte
|
|
|
917
919
|
* every OTHER converter's contract — converters that don't declare a
|
|
918
920
|
* 3rd parameter simply never read it.
|
|
919
921
|
* @param isGlobal install scope passed through to the converter
|
|
920
|
-
* @param agentCtx optional cross-cutting context (ADR-1235 §1)
|
|
921
|
-
* only the converter is applied (backward compat)
|
|
922
|
+
* @param agentCtx optional cross-cutting context (ADR-1235 §1)
|
|
922
923
|
*/
|
|
923
924
|
function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, converter, isGlobal = false, agentCtx) {
|
|
924
925
|
if (!installFs().existsSync(srcAgentsDir))
|
|
@@ -950,6 +951,7 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
950
951
|
try {
|
|
951
952
|
// Resolve cmdNames once per staging call (not per file) for performance.
|
|
952
953
|
const cmdNames = agentCtx ? _readGsdCommandNames() : [];
|
|
954
|
+
const agentTools = installModelOverrideResolver.readGsdEffectiveAgentTools(agentCtx?.projectDir ?? agentCtx?.targetDir ?? null);
|
|
953
955
|
for (const entry of entries) {
|
|
954
956
|
if (!entry.isFile())
|
|
955
957
|
continue;
|
|
@@ -969,25 +971,31 @@ function stageAgentsForRuntimeWithConverter(srcAgentsDir, resolvedProfile, conve
|
|
|
969
971
|
// throws loudly naming the file for a malformed marker, never emitting a
|
|
970
972
|
// half-composed agent.
|
|
971
973
|
content = _composeWorkflow(content, { sourcePath: agentSourcePath });
|
|
974
|
+
const agentName = _deriveAgentName(entry.name);
|
|
975
|
+
const grants = [
|
|
976
|
+
...(agentTools?.['*'] || []),
|
|
977
|
+
...(agentTools?.[agentName] || []),
|
|
978
|
+
];
|
|
972
979
|
if (agentCtx) {
|
|
973
980
|
// #2875 Part 2 / row I3: derived exactly as the inline loop does —
|
|
974
981
|
// single-sourced via deriveAgentName (runtime-artifact-conversion.cts).
|
|
975
|
-
const agentName = _deriveAgentName(entry.name);
|
|
976
982
|
// ADR-1235 §1: pre-converter cross-cutting (matches inline loop order exactly)
|
|
977
983
|
// Step 1: path rewrites (4 base ~/.claude/ regexes; skipped for copilot/antigravity)
|
|
978
984
|
content = _applyAgentPathRewrites(content, agentCtx.runtime, agentCtx.pathPrefix);
|
|
979
985
|
// Step 2: attribution
|
|
980
986
|
content = _processAttribution(content, agentCtx.attribution);
|
|
981
|
-
// Step 3:
|
|
987
|
+
// Step 3: validated canonical grants, before host conversion.
|
|
988
|
+
content = _appendAgentTools(content, grants);
|
|
989
|
+
// Step 4: converter (runtime-specific frontmatter/body transform)
|
|
982
990
|
content = converter(content, isGlobal, { agentName });
|
|
983
|
-
// Step
|
|
991
|
+
// Step 5: frontmatter extensions (effort/disallowedTools; no-op unless
|
|
984
992
|
// the runtime declares hostBehaviors.agentFrontmatterExtensions)
|
|
985
993
|
content = _applyAgentFrontmatterExtensions(content, { runtime: agentCtx.runtime, agentName, targetDir: agentCtx.targetDir });
|
|
986
|
-
// Step
|
|
994
|
+
// Step 6: normalize colon→hyphen refs (no-op for trivial group)
|
|
987
995
|
content = _normalizeAgentBodyForRuntime(content, agentCtx.runtime, cmdNames);
|
|
988
996
|
}
|
|
989
997
|
else {
|
|
990
|
-
|
|
998
|
+
content = _appendAgentTools(content, grants);
|
|
991
999
|
content = converter(content, isGlobal);
|
|
992
1000
|
}
|
|
993
1001
|
installFs().writeFileSync(node_path_1.default.join(stageDir, entry.name), content, 'utf8');
|
|
@@ -55,6 +55,7 @@ exports.BUNDLED_GSD_HOOK_FILES = Object.freeze(new Set([
|
|
|
55
55
|
'hooks/gsd-prompt-guard.js',
|
|
56
56
|
'hooks/gsd-read-guard.js',
|
|
57
57
|
'hooks/gsd-read-injection-scanner.js',
|
|
58
|
+
'hooks/gsd-secret-read-guard.js',
|
|
58
59
|
'hooks/gsd-session-state.sh',
|
|
59
60
|
'hooks/gsd-statusline.js',
|
|
60
61
|
'hooks/gsd-update-banner.js',
|
package/gsd-core/bin/lib/io.cjs
CHANGED
|
@@ -205,6 +205,40 @@ function output(result, raw, rawValue) {
|
|
|
205
205
|
// and skipping process.exit() lets the event loop drain naturally.
|
|
206
206
|
writeAllSync(1, data);
|
|
207
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Single owner of the "a no-op decline reports the real condition" pairing:
|
|
210
|
+
* a `[gsd-tools] WARNING:` stderr disclosure plus the matching
|
|
211
|
+
* `{ [flagKey]: false, ...computed, reason }` JSON payload (#3957, epic
|
|
212
|
+
* #3473 B9). Before this helper, each CLI command's no-op arm hand-wrote
|
|
213
|
+
* both halves independently, and #3957's own sweep found sites where one
|
|
214
|
+
* half drifted: a decline that discarded values it had already computed, a
|
|
215
|
+
* `reason` string naming a condition that hadn't actually fired, or no
|
|
216
|
+
* stderr disclosure at all (missing the `[gsd-tools] WARNING:` convention
|
|
217
|
+
* #3217, ADR-3180 §7.6 rule 4, established for exactly this situation —
|
|
218
|
+
* a JSON `reason` field alone is easy for a caller piping stdout through
|
|
219
|
+
* `--json` to never read). Routing every no-op decline through one call
|
|
220
|
+
* site makes the convention structural — a call site, not a habit a fifth
|
|
221
|
+
* arm can independently forget — rather than leaving it to per-site
|
|
222
|
+
* discipline.
|
|
223
|
+
*
|
|
224
|
+
* `disclosure` is written to stderr VERBATIM. Like `error()`'s `message`
|
|
225
|
+
* argument (see that function's own doc comment above `formatDiagnosticToken`),
|
|
226
|
+
* this function stays a dumb, faithful writer and does not sanitize it — a
|
|
227
|
+
* caller that interpolates an UNTRUSTED substring (a caller-supplied
|
|
228
|
+
* blocker/session-field string, an argv token) into `disclosure` MUST pass
|
|
229
|
+
* that substring through `formatDiagnosticToken` first. Skipping that step
|
|
230
|
+
* lets an embedded `\n` forge a second, attacker-authored
|
|
231
|
+
* `[gsd-tools] WARNING:` line — the same risk `error()`'s doc comment
|
|
232
|
+
* documents for its own stderr write.
|
|
233
|
+
*/
|
|
234
|
+
function declineNoOp(raw, flagKey, reason, disclosure, computed = {}) {
|
|
235
|
+
// `process.stderr.write`, matching the `[gsd-tools] WARNING:` call sites
|
|
236
|
+
// this helper replaces (state.cts's stateReplaceFieldWithFallback and the
|
|
237
|
+
// cmdStateUpdateProgress decline arms) — not the `writeAllSync(2, …)`
|
|
238
|
+
// primitive `error()` uses, which is reserved for the fatal-exit path.
|
|
239
|
+
process.stderr.write(`[gsd-tools] WARNING: ${disclosure}\n`);
|
|
240
|
+
output({ [flagKey]: false, ...computed, reason }, raw, 'false');
|
|
241
|
+
}
|
|
208
242
|
/**
|
|
209
243
|
* Frozen enum of typed reason codes used by error() for structured errors.
|
|
210
244
|
* Each subcommand contributes its own codes; the enum exists so tests can
|
|
@@ -392,6 +426,7 @@ module.exports = {
|
|
|
392
426
|
ensureGsdTempDir,
|
|
393
427
|
reapStaleTempFiles,
|
|
394
428
|
output,
|
|
429
|
+
declineNoOp,
|
|
395
430
|
serializeForOutput,
|
|
396
431
|
ERROR_REASON,
|
|
397
432
|
setJsonErrorMode,
|
|
@@ -40,7 +40,7 @@ const { resolveCapabilityRuntimeState } = capabilityStateModule;
|
|
|
40
40
|
// ─── Capability-activation engine (single owner for config-key precedence) ────
|
|
41
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
42
|
const capabilityActivationModule = require("./capability-activation.cjs");
|
|
43
|
-
const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, resolveConfigKey } = capabilityActivationModule;
|
|
43
|
+
const { _getNestedConfigValue, _readRawConfigKey, _resolveActivationValue, _resolvePointGate, resolveConfigKey } = capabilityActivationModule;
|
|
44
44
|
// ─── Canonical points (derived from LOOP_HOST_CONTRACT — authoritative 12) ───
|
|
45
45
|
// FIX 2: Derive the authoritative canonical set from LOOP_HOST_CONTRACT so it
|
|
46
46
|
// cannot drift from the host contract. CANONICAL_POINTS_FALLBACK is kept as an
|
|
@@ -125,13 +125,15 @@ function resolveLoopHooks(input) {
|
|
|
125
125
|
// Helper: check activation using single-key precedence resolver (FIX 1 + FIX 3)
|
|
126
126
|
function isActive(hook) {
|
|
127
127
|
const when = hook['when'];
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
if (when !== undefined && when !== null) {
|
|
129
|
+
// FIX 3: `when` present but not a non-empty string → malformed registry data → INACTIVE
|
|
130
|
+
if (typeof when !== 'string' || when.length === 0)
|
|
131
|
+
return false;
|
|
132
|
+
if (!_resolveActivationValue(when, config, cwd, registry))
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
// #3661: optional point-selection gate — see capability-activation.cts.
|
|
136
|
+
return _resolvePointGate(hook['pointFrom'], point, config, cwd, registry);
|
|
135
137
|
}
|
|
136
138
|
function isCapabilityActive(capId) {
|
|
137
139
|
if (!capabilityStatesById)
|
|
@@ -555,6 +557,10 @@ module.exports = {
|
|
|
555
557
|
// Re-exported for identity parity guard (FIX 2: resolveConfigValues in this module
|
|
556
558
|
// calls resolveConfigKey; exporting it here makes the single-owner contract testable).
|
|
557
559
|
resolveConfigKey,
|
|
560
|
+
// #3661: re-exported for the same identity parity guard — isActive calls
|
|
561
|
+
// _resolvePointGate; exporting it here makes the single-owner contract testable
|
|
562
|
+
// (see tests/capability-precedence-parity.test.cjs's identity guard describe block).
|
|
563
|
+
_resolvePointGate,
|
|
558
564
|
CANONICAL_POINTS_FALLBACK,
|
|
559
565
|
CANONICAL_POINTS,
|
|
560
566
|
};
|
|
@@ -23,6 +23,7 @@ exports.findTableBySchema = findTableBySchema;
|
|
|
23
23
|
exports.findTableWithColumns = findTableWithColumns;
|
|
24
24
|
exports.escapeCell = escapeCell;
|
|
25
25
|
exports.appendQuickTaskRow = appendQuickTaskRow;
|
|
26
|
+
exports.migrateQuickTasksTable = migrateQuickTasksTable;
|
|
26
27
|
exports.resetQuickTaskRows = resetQuickTaskRows;
|
|
27
28
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
28
29
|
// ─── Schema registry ──────────────────────────────────────────────────────────
|
|
@@ -776,6 +777,128 @@ function appendQuickTaskRow(stateContent, fields) {
|
|
|
776
777
|
const content = (0, markdown_sectionizer_cjs_1.replaceSection)(stateContent, section, newBody);
|
|
777
778
|
return { ok: true, value: { content, row, variant: match.label } };
|
|
778
779
|
}
|
|
780
|
+
// ─── migrateQuickTasksTable (#3730 option b) ───────────────────────────────
|
|
781
|
+
/**
|
|
782
|
+
* Column-name → canonical-cell mapping for migrating a legacy Quick Tasks table
|
|
783
|
+
* (#3730). Name-based, case-insensitive; every column NOT in this map lands in
|
|
784
|
+
* the Description bucket (joined with ' · ' in original column order, unknown
|
|
785
|
+
* names kept as `name: value`) so no historical datum is dropped.
|
|
786
|
+
*/
|
|
787
|
+
const QUICK_TASKS_COLUMN_ALIASES = {
|
|
788
|
+
'#': '#', id: '#',
|
|
789
|
+
date: 'Date', when: 'Date',
|
|
790
|
+
commit: 'Commit', sha: 'Commit',
|
|
791
|
+
status: 'Status',
|
|
792
|
+
directory: 'Directory', artifacts: 'Directory', path: 'Directory', dir: 'Directory',
|
|
793
|
+
description: 'Description', task: 'Description', summary: 'Description',
|
|
794
|
+
slug: 'Description', scope: 'Description', title: 'Description',
|
|
795
|
+
};
|
|
796
|
+
/**
|
|
797
|
+
* Migrate STATE.md's "Quick Tasks Completed" table onto the canonical
|
|
798
|
+
* `with-status` schema (#3730, maintainer decision 2026-09-02: option b).
|
|
799
|
+
*
|
|
800
|
+
* The pre-registry prose template licensed arbitrary column shapes GSD itself
|
|
801
|
+
* emitted, and `appendQuickTaskRow` fails loud on every one of them — leaving a
|
|
802
|
+
* project permanently unappendable. This is the supported repair path: the
|
|
803
|
+
* `gsd-tools quick-tasks-migrate` subcommand, plus an automatic check on the
|
|
804
|
+
* first `quick`/`fast` run (the workflow calls this before appending), which is
|
|
805
|
+
* a silent no-op when the section is absent, the table is already canonical, or
|
|
806
|
+
* the user never runs quick at all.
|
|
807
|
+
*
|
|
808
|
+
* Same section-selection, parse, and fail-loud posture as `appendQuickTaskRow`
|
|
809
|
+
* (ADR-2143 §7 upheld — append still rejects; this REPAIRS). Returns
|
|
810
|
+
* `{ok:true, value:{content, migrated:false}}` for the no-op cases so callers
|
|
811
|
+
* can stay silent, and `{migrated:true, from, rows}` when a rewrite happened so
|
|
812
|
+
* the CLI can report exactly what moved.
|
|
813
|
+
*/
|
|
814
|
+
function migrateQuickTasksTable(stateContent) {
|
|
815
|
+
const section = selectQuickTasksSection(stateContent);
|
|
816
|
+
if (!section) {
|
|
817
|
+
return { ok: true, value: { content: stateContent, migrated: false } };
|
|
818
|
+
}
|
|
819
|
+
const parsed = parseMarkdownTable(section.body);
|
|
820
|
+
if (!parsed.ok) {
|
|
821
|
+
return { ok: false, reason: `quick-tasks table: ${parsed.reason}` };
|
|
822
|
+
}
|
|
823
|
+
const match = matchTableSchema(parsed.value.columns);
|
|
824
|
+
if (match && match.id === 'QuickTasks') {
|
|
825
|
+
return { ok: true, value: { content: stateContent, migrated: false } };
|
|
826
|
+
}
|
|
827
|
+
const canonical = exports.TABLE_SCHEMAS.QuickTasks.find((v) => v.label === 'with-status').columns;
|
|
828
|
+
// Map each legacy column to its canonical target; unmapped names keep their
|
|
829
|
+
// identity so the Description bucket can render them losslessly.
|
|
830
|
+
const targets = parsed.value.columns.map((c) => QUICK_TASKS_COLUMN_ALIASES[c.trim().toLowerCase()] ?? null);
|
|
831
|
+
const rows = parsed.value.rows.map((row, rowIdx) => {
|
|
832
|
+
const bucket = {
|
|
833
|
+
'#': '', Description: '', Date: '', Commit: '', Status: '', Directory: '',
|
|
834
|
+
};
|
|
835
|
+
const descriptionParts = [];
|
|
836
|
+
// parseMarkdownTable hands back name-keyed records (rows[i][columnName]).
|
|
837
|
+
parsed.value.columns.forEach((col, i) => {
|
|
838
|
+
const raw = (row[col] ?? '').trim();
|
|
839
|
+
const target = targets[i];
|
|
840
|
+
if (target === 'Description') {
|
|
841
|
+
if (raw)
|
|
842
|
+
descriptionParts.push(raw);
|
|
843
|
+
}
|
|
844
|
+
else if (target) {
|
|
845
|
+
// Collision-safe (#3730 review): two legacy columns aliasing the same
|
|
846
|
+
// canonical target must not silently overwrite — the displaced value
|
|
847
|
+
// joins the Description bucket under its own name, same convention as
|
|
848
|
+
// an unknown column, so no historical datum is dropped.
|
|
849
|
+
if (raw && bucket[target]) {
|
|
850
|
+
descriptionParts.push(`${col.trim()}: ${raw}`);
|
|
851
|
+
}
|
|
852
|
+
else if (raw) {
|
|
853
|
+
bucket[target] = raw;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
else if (raw) {
|
|
857
|
+
descriptionParts.push(`${col.trim()}: ${raw}`);
|
|
858
|
+
}
|
|
859
|
+
});
|
|
860
|
+
bucket['#'] = bucket['#'] || String(rowIdx + 1);
|
|
861
|
+
bucket.Description = descriptionParts.join(' · ');
|
|
862
|
+
bucket.Date = bucket.Date || '—';
|
|
863
|
+
bucket.Commit = bucket.Commit || '—';
|
|
864
|
+
bucket.Status = bucket.Status || '—';
|
|
865
|
+
bucket.Directory = bucket.Directory || '—';
|
|
866
|
+
return `| ${canonical.map((c) => escapeCell(bucket[c])).join(' | ')} |`;
|
|
867
|
+
});
|
|
868
|
+
const eol = /\r\n/.test(section.body) ? '\r\n' : '\n';
|
|
869
|
+
const lines = section.body.split(/\r?\n/);
|
|
870
|
+
// CONTIGUOUS-run bound (#3730 review), mirroring resetQuickTaskRows: the
|
|
871
|
+
// section body may carry a SECOND table (a `#### Notes` subsection or a
|
|
872
|
+
// trailing table after a blank line) that parseMarkdownTable never read —
|
|
873
|
+
// scanning for the last pipe line anywhere in the body would splice the
|
|
874
|
+
// rewrite across it and silently destroy it. Only the first CONTIGUOUS run
|
|
875
|
+
// of pipe lines — the table that was parsed — is replaced.
|
|
876
|
+
const firstTableLineIdx = lines.findIndex((l) => l.trim().startsWith('|'));
|
|
877
|
+
let lastTableLineIdx = firstTableLineIdx;
|
|
878
|
+
while (lastTableLineIdx + 1 < lines.length && lines[lastTableLineIdx + 1].trim().startsWith('|')) {
|
|
879
|
+
lastTableLineIdx++;
|
|
880
|
+
}
|
|
881
|
+
const header = `| ${canonical.join(' | ')} |`;
|
|
882
|
+
// Widths match workflows/quick.md's canonical template byte-for-byte
|
|
883
|
+
// (#3730 review): its delimiter is max(3, len+2) per column.
|
|
884
|
+
const delimiter = `| ${canonical.map((c) => '-'.repeat(Math.max(3, c.length + 2))).join(' | ')} |`;
|
|
885
|
+
const newBody = [
|
|
886
|
+
...lines.slice(0, firstTableLineIdx),
|
|
887
|
+
header,
|
|
888
|
+
delimiter,
|
|
889
|
+
...rows,
|
|
890
|
+
...lines.slice(lastTableLineIdx + 1),
|
|
891
|
+
].join(eol);
|
|
892
|
+
return {
|
|
893
|
+
ok: true,
|
|
894
|
+
value: {
|
|
895
|
+
content: (0, markdown_sectionizer_cjs_1.replaceSection)(stateContent, section, newBody),
|
|
896
|
+
migrated: true,
|
|
897
|
+
from: parsed.value.columns,
|
|
898
|
+
rows: rows.length,
|
|
899
|
+
},
|
|
900
|
+
};
|
|
901
|
+
}
|
|
779
902
|
// ─── resetQuickTaskRows (#2142) ────────────────────────────────────────────
|
|
780
903
|
/**
|
|
781
904
|
* Clear every DATA row from STATE.md's "Quick Tasks Completed" table, leaving
|
|
@@ -55,7 +55,7 @@ const { scanPhasePlans } = planScanMod;
|
|
|
55
55
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-locator.cjs is an export= CommonJS module
|
|
56
56
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
57
57
|
const { listMilestonePhaseDirs } = phaseLocatorMod;
|
|
58
|
-
const { planningPaths } = planningWorkspace;
|
|
58
|
+
const { planningPaths, resolvePhaseIdConvention } = planningWorkspace;
|
|
59
59
|
const { extractFrontmatter } = frontmatterMod;
|
|
60
60
|
// ADR-3408 §8.3 / #3469: `writeStateMd` gets sync and NO preservation — the
|
|
61
61
|
// same #3374-shaped exposure the milestone-complete write used to carry (a
|
|
@@ -702,6 +702,22 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
702
702
|
let totalPlans = 0;
|
|
703
703
|
let totalTasks = 0;
|
|
704
704
|
const accomplishments = [];
|
|
705
|
+
// #2761 (round-11 BLOCKER): resolved ONCE, ambiently (no explicit `ws` —
|
|
706
|
+
// this function has none of its own and already resolves everything else
|
|
707
|
+
// off `cwd` via planningPaths(cwd), matching the ambient-workstream
|
|
708
|
+
// contract `resolvePhaseIdConvention`/`planningDir` share), and threaded
|
|
709
|
+
// into the single `listMilestonePhaseDirs` call directly below. Left
|
|
710
|
+
// unthreaded, `phaseIdConvention` stays `undefined` and
|
|
711
|
+
// `getMilestonePhaseFilter` silently resolves it FROM CONFIG itself —
|
|
712
|
+
// which is exactly the behavior the changeset originally (and wrongly)
|
|
713
|
+
// claimed this PR does not reach: on a bracket-convention project that
|
|
714
|
+
// lazily-resolved convention changes `milestonePhaseDirs`/
|
|
715
|
+
// `milestonePhaseScope` below, and this function goes on to ARCHIVE
|
|
716
|
+
// (rename/move) whatever that set names. Resolving explicitly here removes
|
|
717
|
+
// the silent-inherit path without changing behavior for `null` /
|
|
718
|
+
// `milestone-prefixed` projects, whose resolved convention is the same
|
|
719
|
+
// value `undefined` would have lazily produced anyway.
|
|
720
|
+
const phaseConvention = resolvePhaseIdConvention(cwd);
|
|
705
721
|
// #3597 (ADR-3180 Decision 2): SINGLE resolution of "which phase
|
|
706
722
|
// directories belong to the current milestone" AND the SCOPE discriminator
|
|
707
723
|
// that resolution came from — shared verbatim by the read-only stats loop
|
|
@@ -715,7 +731,11 @@ function cmdMilestoneComplete(cwd, version, options, raw) {
|
|
|
715
731
|
// disk, same as before #3597. Only the destructive archive pass (and its
|
|
716
732
|
// --dry-run preview) refuses to act on a non-COMPLETE (non-answer) scope;
|
|
717
733
|
// see the guard built from `milestonePhaseScope` further down.
|
|
718
|
-
const { value: milestonePhaseDirs, scope: milestonePhaseScope } = listMilestonePhaseDirs(phasesDir, {
|
|
734
|
+
const { value: milestonePhaseDirs, scope: milestonePhaseScope } = listMilestonePhaseDirs(phasesDir, {
|
|
735
|
+
cwd,
|
|
736
|
+
versionOverride: version,
|
|
737
|
+
phaseIdConvention: phaseConvention,
|
|
738
|
+
});
|
|
719
739
|
try {
|
|
720
740
|
for (const dir of milestonePhaseDirs) {
|
|
721
741
|
phaseCount++;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Unsupported in this router:
|
|
7
7
|
* - scaffold: routed through top-level scaffold command.
|
|
8
8
|
*
|
|
9
|
-
* CJS-only subcommands: mvp-mode (dispatched directly, before hub).
|
|
9
|
+
* CJS-only subcommands: mvp-mode, tdd-applicable (dispatched directly, before hub).
|
|
10
10
|
*
|
|
11
11
|
* #3788: dispatch is mediated by CommandRoutingHub. The public entry point
|
|
12
12
|
* and observable CLI behaviour are unchanged.
|
|
@@ -57,6 +57,13 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
|
57
57
|
phase.cmdPhaseMvpMode(cwd, args.slice(2), raw);
|
|
58
58
|
return;
|
|
59
59
|
}
|
|
60
|
+
// `tdd-applicable` (#4273): same CJS-native dispatch shape as `mvp-mode`
|
|
61
|
+
// immediately above — a precedence cascade over plan/task/config sources
|
|
62
|
+
// with its own typed JSON result, not routed through the SDK query layer.
|
|
63
|
+
if (subcommand === 'tdd-applicable') {
|
|
64
|
+
phase.cmdPhaseTddApplicable(cwd, args.slice(2), raw);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
60
67
|
// ── Build the CJS registry ──────────────────────────────────────────────────
|
|
61
68
|
// Each handler receives a ctx object from the hub and must return a HubResult.
|
|
62
69
|
const cjsRegistry = {
|
|
@@ -224,14 +231,14 @@ function routePhaseCommand({ phase, args, cwd, raw, error }) {
|
|
|
224
231
|
// ── Build manifest (available subcommands for UnknownCommand detection) ─────
|
|
225
232
|
// `availableSubcommands` is what the error message shows. It excludes
|
|
226
233
|
// unsupported commands (already handled above) but does NOT include 'mvp-mode'
|
|
227
|
-
// because
|
|
228
|
-
// shown in the "Available:" list there either.
|
|
234
|
+
// or 'tdd-applicable' (#4273) because both are absent from PHASE_SUBCOMMANDS
|
|
235
|
+
// and were not shown in the "Available:" list there either.
|
|
229
236
|
//
|
|
230
237
|
// `manifestSubcommands` is the full routing set for the hub — it includes
|
|
231
|
-
// 'mvp-mode'
|
|
232
|
-
// manifest entry) so the hub's UnknownCommand check passes for
|
|
238
|
+
// 'mvp-mode' and 'tdd-applicable' (both routed via a handler even without a
|
|
239
|
+
// manifest entry) so the hub's UnknownCommand check passes for them.
|
|
233
240
|
const availableSubcommands = command_aliases_cjs_1.PHASE_SUBCOMMANDS.filter(s => !UNSUPPORTED[s]);
|
|
234
|
-
const manifestSubcommands = ['mvp-mode', ...availableSubcommands];
|
|
241
|
+
const manifestSubcommands = ['mvp-mode', 'tdd-applicable', ...availableSubcommands];
|
|
235
242
|
const manifest = { phase: manifestSubcommands };
|
|
236
243
|
// ── Construct hub ──────────────────────────────────────────────────────────
|
|
237
244
|
// #175: Hub is CJS-only — no mode param, no sdkLoader.
|