@opengsd/gsd-core 1.10.0 → 1.11.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/agents/gsd-debug-session-manager.md +11 -0
- package/agents/gsd-doc-synthesizer.md +2 -4
- package/agents/gsd-executor.md +5 -5
- package/agents/gsd-mempalace-curator.md +5 -2
- package/agents/gsd-phase-researcher.md +20 -1
- package/agents/gsd-plan-checker.md +37 -0
- package/agents/gsd-planner.md +44 -46
- package/agents/gsd-user-profiler.md +3 -0
- package/agents/gsd-verifier.md +12 -3
- package/bin/install.js +841 -971
- package/bin/lib/ui-safety-gate.cjs +2 -0
- package/commands/gsd/code-review.md +1 -1
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/map-codebase.md +1 -1
- package/commands/gsd/mempalace-capture.md +1 -1
- package/commands/gsd/mempalace-recall.md +1 -1
- package/commands/gsd/new-milestone.md +1 -1
- package/commands/gsd/quick.md +1 -1
- package/commands/gsd/review-backlog.md +2 -1
- package/commands/gsd/verify-work.md +1 -1
- package/gsd-core/bin/gsd-tools.cjs +469 -88
- package/gsd-core/bin/lib/active-workstream-store.cjs +138 -22
- package/gsd-core/bin/lib/agent-install-check.cjs +230 -32
- package/gsd-core/bin/lib/api-coverage.cjs +3 -5
- package/gsd-core/bin/lib/artifacts.cjs +3 -0
- package/gsd-core/bin/lib/assumption-delta.cjs +2 -4
- package/gsd-core/bin/lib/audit-command-router.cjs +9 -2
- package/gsd-core/bin/lib/audit.cjs +876 -240
- package/gsd-core/bin/lib/broken-windows.cjs +1 -1
- package/gsd-core/bin/lib/capability-consent.cjs +149 -15
- package/gsd-core/bin/lib/capability-lifecycle.cjs +45 -0
- package/gsd-core/bin/lib/capability-registry.cjs +575 -101
- package/gsd-core/bin/lib/capability-source.cjs +92 -0
- package/gsd-core/bin/lib/capability-trust.cjs +444 -25
- package/gsd-core/bin/lib/capability-validator.cjs +495 -22
- package/gsd-core/bin/lib/capability-writer.cjs +3 -2
- package/gsd-core/bin/lib/check-command-router.cjs +71 -37
- package/gsd-core/bin/lib/claude-orchestration.cjs +56 -3
- package/gsd-core/bin/lib/codex-agent-toml.cjs +329 -0
- package/gsd-core/bin/lib/command-aliases.cjs +22 -0
- package/gsd-core/bin/lib/command-roster.cjs +44 -1
- package/gsd-core/bin/lib/commands.cjs +651 -86
- package/gsd-core/bin/lib/commonjs-marker.cjs +12 -6
- package/gsd-core/bin/lib/complexity-trigger.cjs +1172 -0
- package/gsd-core/bin/lib/config-loader.cjs +75 -0
- package/gsd-core/bin/lib/config.cjs +10 -1
- package/gsd-core/bin/lib/core-utils.cjs +127 -29
- package/gsd-core/bin/lib/decisions.cjs +23 -0
- package/gsd-core/bin/lib/fallow-runner.cjs +20 -44
- package/gsd-core/bin/lib/frontmatter.cjs +155 -20
- package/gsd-core/bin/lib/gap-checker.cjs +68 -7
- package/gsd-core/bin/lib/git-base-branch.cjs +102 -0
- package/gsd-core/bin/lib/gsd2-import.cjs +10 -1
- package/gsd-core/bin/lib/health-diagnostic-rules/agent-install.cjs +101 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/config-validation.cjs +348 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/consistency.cjs +145 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/install-surface-shadowing.cjs +98 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/milestone-archive-hygiene.cjs +100 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +222 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +265 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/root-existence.cjs +161 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +303 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/worktree-health.cjs +173 -0
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +68 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +431 -0
- package/gsd-core/bin/lib/host-runtime-detection.cjs +134 -0
- package/gsd-core/bin/lib/init.cjs +321 -129
- package/gsd-core/bin/lib/install-effort-resolver.cjs +73 -30
- package/gsd-core/bin/lib/install-engine.cjs +745 -258
- package/gsd-core/bin/lib/install-fs-adapter.cjs +262 -0
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +203 -0
- package/gsd-core/bin/lib/install-profiles.cjs +134 -57
- package/gsd-core/bin/lib/install-scope.cjs +270 -0
- package/gsd-core/bin/lib/install-shadow-report.cjs +385 -0
- package/gsd-core/bin/lib/installed-surface-resolver.cjs +381 -0
- package/gsd-core/bin/lib/installer-migrations.cjs +138 -31
- package/gsd-core/bin/lib/io.cjs +10 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +2 -1
- package/gsd-core/bin/lib/markdown-table.cjs +133 -20
- package/gsd-core/bin/lib/milestone-lock.cjs +248 -0
- package/gsd-core/bin/lib/milestone.cjs +754 -70
- package/gsd-core/bin/lib/model-catalog.cjs +59 -1
- package/gsd-core/bin/lib/model-resolver.cjs +183 -40
- package/gsd-core/bin/lib/normalize-test-command.cjs +1 -1
- package/gsd-core/bin/lib/pattern.cjs +122 -0
- package/gsd-core/bin/lib/phase-estimation.cjs +1 -1
- package/gsd-core/bin/lib/phase-id.cjs +444 -36
- package/gsd-core/bin/lib/phase-lifecycle.cjs +28 -3
- package/gsd-core/bin/lib/phase-locator.cjs +125 -18
- package/gsd-core/bin/lib/phase.cjs +646 -143
- package/gsd-core/bin/lib/plan-dependency-graph.cjs +72 -1
- package/gsd-core/bin/lib/plan-drift-guard.cjs +120 -0
- package/gsd-core/bin/lib/plan-scan.cjs +86 -2
- package/gsd-core/bin/lib/planning-scope.cjs +31 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +890 -0
- package/gsd-core/bin/lib/planning-workspace.cjs +56 -6
- package/gsd-core/bin/lib/probe-core.cjs +1 -1
- package/gsd-core/bin/lib/profile-output.cjs +1 -1
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +740 -0
- package/gsd-core/bin/lib/retired-artifact-cleanup.cjs +11 -6
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +13 -4
- package/gsd-core/bin/lib/review-lane-invocation.cjs +30 -0
- package/gsd-core/bin/lib/review-lane-runner.cjs +421 -66
- package/gsd-core/bin/lib/review-reviewer-selection.cjs +13 -18
- package/gsd-core/bin/lib/roadmap-command-router.cjs +34 -0
- package/gsd-core/bin/lib/roadmap-parser.cjs +943 -184
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +37 -10
- package/gsd-core/bin/lib/roadmap.cjs +385 -94
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +608 -46
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +14 -2
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +426 -55
- package/gsd-core/bin/lib/runtime-config-adapter-registry.cjs +3 -2
- package/gsd-core/bin/lib/runtime-homes.cjs +69 -3
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +115 -3
- package/gsd-core/bin/lib/runtime-name-policy.cjs +3 -1
- package/gsd-core/bin/lib/runtime-slash.cjs +27 -9
- package/gsd-core/bin/lib/security.cjs +104 -5
- package/gsd-core/bin/lib/shell-command-projection.cjs +275 -3
- package/gsd-core/bin/lib/smart-entry.cjs +142 -22
- package/gsd-core/bin/lib/state-command-router.cjs +5 -1
- package/gsd-core/bin/lib/state-document.cjs +152 -8
- package/gsd-core/bin/lib/state-transition.cjs +371 -117
- package/gsd-core/bin/lib/state.cjs +1794 -357
- package/gsd-core/bin/lib/surface.cjs +23 -9
- package/gsd-core/bin/lib/text-lines.cjs +80 -0
- package/gsd-core/bin/lib/token-scanner.cjs +76 -0
- package/gsd-core/bin/lib/uat-predicate.cjs +9 -3
- package/gsd-core/bin/lib/uat.cjs +399 -56
- package/gsd-core/bin/lib/ui-frontend-evidence.cjs +157 -0
- package/gsd-core/bin/lib/ui-safety-gate.cjs +14 -5
- package/gsd-core/bin/lib/unusable-input.cjs +24 -0
- package/gsd-core/bin/lib/update-context.cjs +8 -2
- package/gsd-core/bin/lib/user-artifact-staging.cjs +705 -0
- package/gsd-core/bin/lib/validate.cjs +20 -6
- package/gsd-core/bin/lib/vendor/README.md +37 -0
- package/gsd-core/bin/lib/vendor/re2js.cjs +6480 -0
- package/gsd-core/bin/lib/vendor/re2js.d.cts +938 -0
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +258 -8
- package/gsd-core/bin/lib/verify.cjs +368 -888
- package/gsd-core/bin/lib/workstream-inventory-builder.cjs +53 -32
- package/gsd-core/bin/lib/workstream-inventory.cjs +63 -10
- package/gsd-core/bin/lib/workstream.cjs +2 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +176 -9
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +7 -1
- package/gsd-core/references/agent-contracts.md +43 -26
- package/gsd-core/references/checkpoints.md +2 -2
- package/gsd-core/references/context-budget.md +1 -1
- package/gsd-core/references/dispatch-isolation-gate.md +138 -0
- package/gsd-core/references/doc-conflict-engine.md +1 -1
- package/gsd-core/references/execute-mvp-tdd.md +3 -3
- package/gsd-core/references/execute-phase-between-wave-reset.md +6 -2
- package/gsd-core/references/execute-phase-context-guard.md +1 -1
- package/gsd-core/references/execute-phase-response-language.md +1 -1
- package/gsd-core/references/execute-phase-wave-guard.md +6 -2
- package/gsd-core/references/gate-prompts.md +1 -1
- package/gsd-core/references/git-planning-commit.md +2 -1
- package/gsd-core/references/loop-hook-dispatch.md +39 -2
- package/gsd-core/references/model-profiles.md +12 -4
- package/gsd-core/references/mvp-concepts.md +9 -9
- package/gsd-core/references/planner-guidance.md +3 -9
- package/gsd-core/references/planner-preconditions.md +1 -1
- package/gsd-core/references/planner-reviews.md +1 -1
- package/gsd-core/references/planning-config.md +8 -6
- package/gsd-core/references/revision-loop.md +1 -1
- package/gsd-core/references/specless-probe-fallback.md +1 -1
- package/gsd-core/references/universal-anti-patterns.md +3 -3
- package/gsd-core/references/verifier-phase-gates.md +192 -0
- package/gsd-core/references/verify-mvp-mode.md +1 -1
- package/gsd-core/references/workstream-flag.md +22 -6
- package/gsd-core/templates/discussion-log.md +1 -1
- package/gsd-core/templates/phase-prompt.md +2 -4
- package/gsd-core/templates/state.md +4 -4
- package/gsd-core/templates/verification-report.md +9 -1
- package/gsd-core/workflows/ai-integration-phase.md +9 -11
- package/gsd-core/workflows/autonomous.md +1 -1
- package/gsd-core/workflows/cleanup.md +62 -3
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +13 -3
- package/gsd-core/workflows/code-review-fix.md +37 -10
- package/gsd-core/workflows/code-review.md +38 -12
- package/gsd-core/workflows/complete-milestone.md +141 -18
- package/gsd-core/workflows/debug.md +7 -5
- package/gsd-core/workflows/diagnose-issues.md +35 -9
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -1
- package/gsd-core/workflows/discuss-phase/modes/default.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +2 -1
- package/gsd-core/workflows/edit-phase.md +26 -1
- package/gsd-core/workflows/eval-review.md +3 -5
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +31 -6
- package/gsd-core/workflows/execute-phase/steps/per-plan-executor-routing.md +77 -0
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +2 -0
- package/gsd-core/workflows/execute-phase.md +38 -50
- package/gsd-core/workflows/execute-plan.md +36 -4
- package/gsd-core/workflows/explore.md +131 -4
- package/gsd-core/workflows/fast.md +10 -2
- package/gsd-core/workflows/health.md +73 -4
- package/gsd-core/workflows/import.md +4 -4
- package/gsd-core/workflows/ingest-docs.md +5 -5
- package/gsd-core/workflows/mvp-phase.md +6 -3
- package/gsd-core/workflows/new-milestone.md +14 -9
- package/gsd-core/workflows/new-project.md +14 -14
- package/gsd-core/workflows/next.md +12 -0
- package/gsd-core/workflows/plan-phase.md +41 -17
- package/gsd-core/workflows/plan-review-convergence.md +50 -2
- package/gsd-core/workflows/progress.md +34 -6
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +4 -4
- package/gsd-core/workflows/quick/steps/quick-verification.md +27 -6
- package/gsd-core/workflows/quick/steps/research-phase.md +2 -2
- package/gsd-core/workflows/quick.md +35 -15
- package/gsd-core/workflows/review.md +26 -5
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -1
- package/gsd-core/workflows/settings.md +66 -2
- package/gsd-core/workflows/ship.md +104 -44
- package/gsd-core/workflows/spec-phase.md +30 -12
- package/gsd-core/workflows/sync-skills.md +63 -8
- package/gsd-core/workflows/transition.md +46 -11
- package/gsd-core/workflows/ui-phase.md +5 -5
- package/gsd-core/workflows/ui-review.md +2 -2
- package/gsd-core/workflows/update.md +1 -1
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +9 -7
- package/hooks/dist/gsd-agent-isolation-guard.js +103 -14
- package/hooks/dist/gsd-check-update-worker.js +56 -13
- package/hooks/dist/gsd-check-update.js +19 -1
- package/hooks/dist/gsd-cursor-pre-tool.js +0 -3
- package/hooks/dist/gsd-cursor-subagent-start.js +77 -2
- package/hooks/dist/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/dist/gsd-prompt-guard.js +21 -20
- package/hooks/dist/gsd-read-injection-scanner.js +38 -24
- package/hooks/dist/gsd-statusline.js +18 -0
- package/hooks/dist/gsd-update-banner.js +22 -1
- package/hooks/dist/gsd-workflow-guard.js +134 -36
- package/hooks/dist/lib/git-cmd.js +92 -59
- package/hooks/dist/lib/injection-patterns.js +45 -0
- package/hooks/dist/lib/isolation-deny-reason.js +39 -0
- package/hooks/dist/lib/isolation-sentinel.js +9 -0
- package/hooks/gsd-agent-isolation-guard.js +103 -14
- package/hooks/gsd-check-update-worker.js +56 -13
- package/hooks/gsd-check-update.js +19 -1
- package/hooks/gsd-cursor-pre-tool.js +0 -3
- package/hooks/gsd-cursor-subagent-start.js +77 -2
- package/hooks/gsd-cursor-subagent-stop.js +3 -2
- package/hooks/gsd-prompt-guard.js +21 -20
- package/hooks/gsd-read-injection-scanner.js +38 -24
- package/hooks/gsd-statusline.js +18 -0
- package/hooks/gsd-update-banner.js +22 -1
- package/hooks/gsd-workflow-guard.js +134 -36
- package/hooks/lib/git-cmd.js +92 -59
- package/hooks/lib/injection-patterns.js +45 -0
- package/hooks/lib/isolation-deny-reason.js +39 -0
- package/hooks/lib/isolation-sentinel.js +9 -0
- package/package.json +21 -9
- package/pi/gsd.cjs +19 -5
- package/scripts/baselines/planning-prompt-drift-baseline.json +4 -0
- package/scripts/baselines/planning-snapshot-bypass-baseline.json +12 -0
- package/scripts/baselines/unreachable-guard-drift-baseline.json +4 -0
- package/scripts/changeset/lint.cjs +60 -5
- package/scripts/check-alias-drift.cjs +7 -43
- package/scripts/check-contract-drift.cjs +297 -0
- package/scripts/ci-test-scope.cjs +19 -2
- package/scripts/command-contract-helpers.cjs +903 -1
- package/scripts/gen-adr-index.cjs +728 -38
- package/scripts/gen-capability-registry.cjs +3 -15
- package/scripts/gen-context-index.cjs +2 -11
- package/scripts/gen-health-docs.cjs +390 -0
- package/scripts/gen-inventory-manifest.cjs +50 -4
- package/scripts/gen-loop-host-contract.cjs +4 -24
- package/scripts/gen-registry.cjs +3 -14
- package/scripts/lib/alias-drift-families.cjs +46 -0
- package/scripts/lib/drift-scan.cjs +278 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +1 -26
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +4 -0
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +3 -0
- package/scripts/lint-canary-version-leak.cjs +73 -0
- package/scripts/lint-command-contract.cjs +96 -13
- package/scripts/lint-completion-predicate-drift.cjs +933 -0
- package/scripts/lint-completion-ratio-drift.cjs +214 -0
- package/scripts/lint-default-flip-documentation.cjs +193 -0
- package/scripts/lint-eslint-glob-coverage.allowlist.json +34 -0
- package/scripts/lint-eslint-glob-coverage.cjs +340 -0
- package/scripts/lint-frontmatter-scalar-broad-grep.cjs +237 -0
- package/scripts/lint-health-diagnostic-rule-table.cjs +404 -0
- package/scripts/lint-hooks-runtime-build-seam.cjs +262 -0
- package/scripts/lint-milestone-window-drift.cjs +468 -0
- package/scripts/lint-phase-enumeration-drift.cjs +479 -0
- package/scripts/lint-plan-count-drift.cjs +318 -0
- package/scripts/lint-planning-artifact-writer-drift.cjs +398 -0
- package/scripts/lint-planning-prompt-drift.cjs +434 -0
- package/scripts/lint-planning-snapshot-bypass-drift.cjs +544 -0
- package/scripts/lint-regression-test-names.cjs +15 -13
- package/scripts/lint-removed-but-needed.cjs +320 -0
- package/scripts/lint-state-field-drift.cjs +805 -0
- package/scripts/lint-state-write-path-drift.cjs +1045 -0
- package/scripts/lint-test-file-count.allowlist.json +21 -10
- package/scripts/lint-unreachable-guard-drift.cjs +843 -0
- package/scripts/lint-vendored-deps.cjs +124 -0
- package/scripts/pr-changed-files.cjs +63 -0
- package/scripts/pr-template-policy.cjs +14 -4
- package/scripts/prompt-injection-scan.sh +25 -0
- package/scripts/require-issue-link-policy.cjs +192 -0
- package/scripts/state-write-path-drift-baseline.json +19 -0
- package/scripts/sync-runtime-launcher.cjs +2 -4
- package/skills/gsd-autonomous/SKILL.md +0 -1
- package/skills/gsd-code-review/SKILL.md +1 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -2
- package/skills/gsd-map-codebase/SKILL.md +1 -1
- package/skills/gsd-mempalace-capture/SKILL.md +1 -1
- package/skills/gsd-mempalace-recall/SKILL.md +1 -1
- package/skills/gsd-new-milestone/SKILL.md +1 -1
- package/skills/gsd-next/SKILL.md +0 -1
- package/skills/gsd-plan-phase/SKILL.md +0 -1
- package/skills/gsd-progress/SKILL.md +0 -1
- package/skills/gsd-quick/SKILL.md +1 -1
- package/skills/gsd-review-backlog/SKILL.md +2 -1
- package/skills/gsd-stats/SKILL.md +0 -1
- package/skills/gsd-verify-work/SKILL.md +1 -1
- package/vscode/package.json +1 -1
- package/gsd-core/workflows/discovery-phase.md +0 -298
- package/gsd-core/workflows/plan-milestone-gaps.md +0 -281
- package/gsd-core/workflows/verify-phase.md +0 -574
- package/scripts/affected-tests-lib.cjs +0 -554
- package/scripts/lint-allow-test-rule-refs.cjs +0 -162
- package/scripts/run-affected-tests.cjs +0 -7
- package/scripts/run-tests.cjs +0 -1051
|
@@ -22,6 +22,15 @@ const WORKSTREAM_SESSION_ENV_KEYS = [
|
|
|
22
22
|
'GSD_SESSION_KEY',
|
|
23
23
|
'CODEX_THREAD_ID',
|
|
24
24
|
'CLAUDE_SESSION_ID',
|
|
25
|
+
// #3557 — Claude Code (≥ 2.1.132) exports its session id to Bash-tool
|
|
26
|
+
// subprocesses as CLAUDE_CODE_SESSION_ID. Without it the probe returned
|
|
27
|
+
// null on Claude Code, so every concurrent session in a working tree
|
|
28
|
+
// shared the single .planning/active-workstream pointer and cross-
|
|
29
|
+
// workstream STATE.md writes landed silently in the wrong file. Inserted
|
|
30
|
+
// beside the other runtime keys without reordering any existing entry;
|
|
31
|
+
// ahead of CLAUDE_CODE_SSE_PORT so the canonical id wins when both are
|
|
32
|
+
// present (runtime identity outranks terminal identity in this list).
|
|
33
|
+
'CLAUDE_CODE_SESSION_ID',
|
|
25
34
|
'CLAUDE_CODE_SSE_PORT',
|
|
26
35
|
'OPENCODE_SESSION_ID',
|
|
27
36
|
'GEMINI_SESSION_ID',
|
|
@@ -183,21 +192,134 @@ function pickActiveWorkstreamAdapter(cwd, opts = {}) {
|
|
|
183
192
|
}
|
|
184
193
|
return createSharedPointerAdapter(cwd);
|
|
185
194
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
195
|
+
/**
|
|
196
|
+
* Read-resolution chain for getActiveWorkstream/peekActiveWorkstream (#3579).
|
|
197
|
+
*
|
|
198
|
+
* pickActiveWorkstreamAdapter (above) picks exactly one adapter and remains
|
|
199
|
+
* the seam for WRITE paths (set/clear), where "which pointer do I mutate" has
|
|
200
|
+
* only one right answer: the session pointer when a session key exists,
|
|
201
|
+
* otherwise the shared marker. Reads are different — a session that has
|
|
202
|
+
* never called `workstream use` has no opinion of its own, so it should
|
|
203
|
+
* inherit the repo-wide `.planning/active-workstream` marker rather than
|
|
204
|
+
* resolve to nothing. This returns an ORDERED chain: [owned, ...fallbacks].
|
|
205
|
+
* `chain[0]` ("owned") is exactly what pickActiveWorkstreamAdapter would have
|
|
206
|
+
* returned — resolveFromChain() self-heals only chain[0], never a fallback,
|
|
207
|
+
* so one session's read can never delete another scope's marker. Fallbacks
|
|
208
|
+
* are consulted ONLY when chain[0].read() comes back absent/empty; a session
|
|
209
|
+
* with its own (even stale/invalid) pointer never falls through — that is
|
|
210
|
+
* the isolation guarantee and it must not be weakened by inheritance.
|
|
211
|
+
*/
|
|
212
|
+
function pickActiveWorkstreamAdapterChain(cwd, opts = {}) {
|
|
213
|
+
if (opts.activeWorkstreamAdapter) {
|
|
214
|
+
return [opts.activeWorkstreamAdapter];
|
|
194
215
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
216
|
+
// #3579 item 3: when a caller supplies `opts.activeWorkstreamAdapters` at
|
|
217
|
+
// all, honor ONLY what it provides. The prior `|| createXPointerAdapter(...)`
|
|
218
|
+
// fallback synthesized a REAL filesystem adapter for whichever half a test
|
|
219
|
+
// double omitted — so a test injecting only `{ session }` silently touched
|
|
220
|
+
// the real shared marker file, and one injecting only `{ shared }` silently
|
|
221
|
+
// touched the real session-scoped tmp file. A missing half now gets a
|
|
222
|
+
// no-op in-memory adapter (always reads null) instead — this preserves the
|
|
223
|
+
// chain[0]-is-owned / rest-are-fallback shape resolveFromChain relies on
|
|
224
|
+
// without ever reaching disk. A caller that wants a real adapter for one
|
|
225
|
+
// half can still construct and pass it explicitly.
|
|
226
|
+
const injected = opts.activeWorkstreamAdapters;
|
|
227
|
+
const sessionKey = getWorkstreamSessionKey();
|
|
228
|
+
if (!sessionKey) {
|
|
229
|
+
const shared = injected
|
|
230
|
+
? (injected.shared ?? createMemoryPointerAdapter(null))
|
|
231
|
+
: createSharedPointerAdapter(cwd);
|
|
232
|
+
return [shared];
|
|
233
|
+
}
|
|
234
|
+
const session = injected
|
|
235
|
+
? (injected.session ?? createMemoryPointerAdapter(null))
|
|
236
|
+
: createSessionScopedPointerAdapter(cwd, sessionKey);
|
|
237
|
+
const shared = injected
|
|
238
|
+
? (injected.shared ?? createMemoryPointerAdapter(null))
|
|
239
|
+
: createSharedPointerAdapter(cwd);
|
|
240
|
+
return session ? [session, shared] : [shared];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Shared "does this stored name resolve" predicate — format-valid AND its
|
|
244
|
+
* workstream directory exists. Factored out so resolveFromChain's owned/
|
|
245
|
+
* fallback arms (and diagnoseUnresolvedActiveWorkstream, #3579 item 1) share
|
|
246
|
+
* one definition of "resolvable" instead of re-deriving the same two checks.
|
|
247
|
+
*/
|
|
248
|
+
function resolvesToExistingWorkstream(cwd, name) {
|
|
249
|
+
if (!name || !validateWorkstreamName(name))
|
|
250
|
+
return false;
|
|
251
|
+
return node_fs_1.default.existsSync(node_path_1.default.join(planningRoot(cwd), 'workstreams', name));
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Resolves a stored workstream name by walking an adapter chain.
|
|
255
|
+
*
|
|
256
|
+
* chain[0] is "owned" by this resolution: an absent/empty read falls through
|
|
257
|
+
* to the next adapter, but a present-and-bad read (invalid name, or a name
|
|
258
|
+
* whose workstream dir no longer exists) is resolved right there — self-
|
|
259
|
+
* healed via adapter.clear() when `selfHeal` is true, and never consulted
|
|
260
|
+
* further. Anything after chain[0] is a read-only fallback (the inherited
|
|
261
|
+
* marker): a bad value there resolves to null WITHOUT ever calling clear(),
|
|
262
|
+
* so a pointer-less session's read can never delete the shared marker that
|
|
263
|
+
* other sessions/scopes still depend on.
|
|
264
|
+
*/
|
|
265
|
+
function resolveFromChain(cwd, chain, selfHeal) {
|
|
266
|
+
if (chain.length === 0)
|
|
198
267
|
return null;
|
|
268
|
+
const [owned, ...fallbacks] = chain;
|
|
269
|
+
const ownedName = owned.read();
|
|
270
|
+
if (ownedName) {
|
|
271
|
+
if (!resolvesToExistingWorkstream(cwd, ownedName)) {
|
|
272
|
+
if (selfHeal)
|
|
273
|
+
owned.clear();
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return ownedName;
|
|
277
|
+
}
|
|
278
|
+
for (const adapter of fallbacks) {
|
|
279
|
+
const name = adapter.read();
|
|
280
|
+
if (resolvesToExistingWorkstream(cwd, name))
|
|
281
|
+
return name;
|
|
282
|
+
}
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Diagnostic sibling of resolveFromChain (#3579 item 1). getActiveWorkstream/
|
|
287
|
+
* peekActiveWorkstream collapse EVERY unresolvable case to `null`, which is
|
|
288
|
+
* exactly right for routing — but a fail-safe guard reporting "no active
|
|
289
|
+
* workstream is set" to an operator needs to distinguish two very different
|
|
290
|
+
* situations that both produce that same `null`:
|
|
291
|
+
*
|
|
292
|
+
* (a) no marker/pointer exists anywhere in the chain at all, vs.
|
|
293
|
+
* (b) a marker/pointer EXISTS (names a value) but that value didn't
|
|
294
|
+
* resolve — either the name fails validateWorkstreamName, or it's a
|
|
295
|
+
* well-formed name whose `workstreams/<name>` directory is missing.
|
|
296
|
+
*
|
|
297
|
+
* Walks the same chain resolveFromChain uses and, for the first adapter that
|
|
298
|
+
* held a non-empty raw value, reports why it didn't resolve. Read-only: never
|
|
299
|
+
* calls adapter.clear() (mirrors peekActiveWorkstream, not getActiveWorkstream
|
|
300
|
+
* — a diagnostic read must not have side effects). Reuses
|
|
301
|
+
* resolvesToExistingWorkstream so this can never disagree with the actual
|
|
302
|
+
* resolution predicate above.
|
|
303
|
+
*/
|
|
304
|
+
function diagnoseUnresolvedActiveWorkstream(cwd, opts = {}) {
|
|
305
|
+
const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
|
|
306
|
+
for (const adapter of chain) {
|
|
307
|
+
const raw = adapter.read();
|
|
308
|
+
if (!raw)
|
|
309
|
+
continue;
|
|
310
|
+
if (resolvesToExistingWorkstream(cwd, raw))
|
|
311
|
+
continue;
|
|
312
|
+
return {
|
|
313
|
+
present: true,
|
|
314
|
+
value: raw,
|
|
315
|
+
reason: validateWorkstreamName(raw) ? 'missing_workstream_dir' : 'invalid_name',
|
|
316
|
+
};
|
|
199
317
|
}
|
|
200
|
-
return
|
|
318
|
+
return { present: false, value: null, reason: null };
|
|
319
|
+
}
|
|
320
|
+
function getActiveWorkstream(cwd, opts = {}) {
|
|
321
|
+
const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
|
|
322
|
+
return resolveFromChain(cwd, chain, true);
|
|
201
323
|
}
|
|
202
324
|
/**
|
|
203
325
|
* Read-only sibling of getActiveWorkstream (#2850): identical resolution —
|
|
@@ -212,16 +334,8 @@ function getActiveWorkstream(cwd, opts = {}) {
|
|
|
212
334
|
* pointer file is left exactly as it was for whatever created it to fix.
|
|
213
335
|
*/
|
|
214
336
|
function peekActiveWorkstream(cwd, opts = {}) {
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
return null;
|
|
218
|
-
const name = adapter.read();
|
|
219
|
-
if (!name || !validateWorkstreamName(name))
|
|
220
|
-
return null;
|
|
221
|
-
const wsDir = node_path_1.default.join(planningRoot(cwd), 'workstreams', name);
|
|
222
|
-
if (!node_fs_1.default.existsSync(wsDir))
|
|
223
|
-
return null;
|
|
224
|
-
return name;
|
|
337
|
+
const chain = pickActiveWorkstreamAdapterChain(cwd, opts);
|
|
338
|
+
return resolveFromChain(cwd, chain, false);
|
|
225
339
|
}
|
|
226
340
|
function setActiveWorkstream(cwd, name, opts = {}) {
|
|
227
341
|
const adapter = pickActiveWorkstreamAdapter(cwd, opts);
|
|
@@ -311,8 +425,10 @@ module.exports = {
|
|
|
311
425
|
createSessionScopedPointerAdapter,
|
|
312
426
|
createMemoryPointerAdapter,
|
|
313
427
|
pickActiveWorkstreamAdapter,
|
|
428
|
+
pickActiveWorkstreamAdapterChain,
|
|
314
429
|
getActiveWorkstream,
|
|
315
430
|
peekActiveWorkstream,
|
|
431
|
+
diagnoseUnresolvedActiveWorkstream,
|
|
316
432
|
setActiveWorkstream,
|
|
317
433
|
clearActiveWorkstream,
|
|
318
434
|
parseCliWorkstream,
|
|
@@ -19,14 +19,52 @@ const modelProfiles = require("./model-profiles.cjs");
|
|
|
19
19
|
const { MODEL_PROFILES } = modelProfiles;
|
|
20
20
|
const runtime_homes_cjs_1 = require("./runtime-homes.cjs");
|
|
21
21
|
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
22
|
+
// #3242 — model-catalog is a genuine leaf (only node:path + its own JSON), which is
|
|
23
|
+
// exactly why Phase 1 (#3241) moved isAnthropicFlavoredModel there: this module can
|
|
24
|
+
// consume it without dragging model-resolver's config-loader chain into a
|
|
25
|
+
// pure read/verify surface.
|
|
26
|
+
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
27
|
+
// #3243 — the Codex `.toml` block-range/BOM/scan primitives moved into the typed
|
|
28
|
+
// IR module (Phase 3), which this reader now imports rather than defining
|
|
29
|
+
// locally. Behavior is unchanged: scanTomlLines/stripBOM here are the exact
|
|
30
|
+
// same lenient functions that used to live in this file — see
|
|
31
|
+
// codex-agent-toml.cts's module header for the reader/writer reconciliation.
|
|
32
|
+
const codex_agent_toml_cjs_1 = require("./codex-agent-toml.cjs");
|
|
33
|
+
/**
|
|
34
|
+
* Frozen reason enum for {@link checkCodexModelPosture}. Per CONTRIBUTING's
|
|
35
|
+
* typed-IR rule ("Error / status / reason → a frozen enum"): callers and tests
|
|
36
|
+
* assert on these wire values, never on prose. Adding a member is a deliberate
|
|
37
|
+
* three-way coordinated change — enum, emitting site, and the enum-lock test.
|
|
38
|
+
*/
|
|
39
|
+
const POSTURE_REASON = Object.freeze({
|
|
40
|
+
ANTHROPIC_FLAVORED_MODEL: 'anthropic_flavored_model',
|
|
41
|
+
ORPHANED_REASONING_EFFORT: 'orphaned_reasoning_effort',
|
|
42
|
+
UNREADABLE: 'unreadable',
|
|
43
|
+
NOT_CODEX: 'not_codex',
|
|
44
|
+
AGENTS_DIR_MISSING: 'agents_dir_missing',
|
|
45
|
+
});
|
|
46
|
+
// Matches the value-truncation convention in bin/install.js's
|
|
47
|
+
// _warnCodexModelOverrideDropped: values over 64 chars are capped so an
|
|
48
|
+
// oversized or secret-shaped config value can never reach a report in full.
|
|
49
|
+
function truncatePostureValue(value) {
|
|
50
|
+
return value.length > 64 ? `${value.slice(0, 64)}…` : value;
|
|
51
|
+
}
|
|
22
52
|
/**
|
|
23
53
|
* Resolve the agents directory for the given runtime.
|
|
24
54
|
*
|
|
25
55
|
* Priority:
|
|
26
56
|
* 1. GSD_AGENTS_DIR env var (explicit override, any runtime)
|
|
27
|
-
* 2. For claude runtime: __dirname-relative path (agents/ sibling of
|
|
28
|
-
*
|
|
29
|
-
* agents/
|
|
57
|
+
* 2. For claude runtime: __dirname-relative path (agents/ sibling of
|
|
58
|
+
* gsd-core/) — correct for repo runs and runtime-config-dir installs,
|
|
59
|
+
* where the sibling agents/ IS the user's agents dir — UNLESS that path
|
|
60
|
+
* carries an exact node_modules segment. gsd-tools.cjs lives inside
|
|
61
|
+
* gsd-core/bin/ in every install shape, but on an npm-global install
|
|
62
|
+
* gsd-core/ sits inside the package (not the runtime config dir) and the
|
|
63
|
+
* package ships its own agents/, so the install-relative path resolves
|
|
64
|
+
* to the bundled copy and the check validates the package against
|
|
65
|
+
* itself — agents_installed can never be false. In that case resolve
|
|
66
|
+
* getGlobalConfigDir('claude')/agents (honours CLAUDE_CONFIG_DIR) like
|
|
67
|
+
* every other runtime (#3203).
|
|
30
68
|
* 3. For non-claude runtimes with a manifest-backed project-local install:
|
|
31
69
|
* <projectRoot>/<localConfigDir>/agents (or <projectRoot>/agents when
|
|
32
70
|
* the runtime's local install targets the project root). Requiring the
|
|
@@ -43,7 +81,16 @@ function getAgentsDir(runtime, projectRoot) {
|
|
|
43
81
|
}
|
|
44
82
|
const resolved = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
|
|
45
83
|
if (resolved === 'claude') {
|
|
46
|
-
|
|
84
|
+
const installRelative = node_path_1.default.join(__dirname, '..', '..', '..', 'agents');
|
|
85
|
+
// #3203: a lexical guard, not an install-shape test. It targets the
|
|
86
|
+
// layouts where the sibling agents/ is the package's own bundled copy and
|
|
87
|
+
// the check would otherwise validate the package against itself; a path
|
|
88
|
+
// merely carrying a directory of that name resolves the same way, and a
|
|
89
|
+
// non-empty GSD_AGENTS_DIR overrides both.
|
|
90
|
+
if (installRelative.split(node_path_1.default.sep).includes('node_modules')) {
|
|
91
|
+
return node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)('claude'), 'agents');
|
|
92
|
+
}
|
|
93
|
+
return installRelative;
|
|
47
94
|
}
|
|
48
95
|
if (projectRoot) {
|
|
49
96
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -94,18 +141,7 @@ function checkAgentsInstalled(runtime, projectRoot) {
|
|
|
94
141
|
};
|
|
95
142
|
}
|
|
96
143
|
for (const agent of expectedAgents) {
|
|
97
|
-
|
|
98
|
-
const agentFileCopilot = node_path_1.default.join(agentsDir, `${agent}.agent.md`);
|
|
99
|
-
const agentFileCodex = node_path_1.default.join(agentsDir, `${agent}.toml`);
|
|
100
|
-
const agentFileKimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${agent}.yaml`);
|
|
101
|
-
const agentFileKimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${agent}.md`);
|
|
102
|
-
const kimiAgentInstalled = resolvedRuntime === 'kimi' &&
|
|
103
|
-
node_fs_1.default.existsSync(agentFileKimiYaml) &&
|
|
104
|
-
node_fs_1.default.existsSync(agentFileKimiPrompt);
|
|
105
|
-
if (node_fs_1.default.existsSync(agentFile) ||
|
|
106
|
-
node_fs_1.default.existsSync(agentFileCopilot) ||
|
|
107
|
-
node_fs_1.default.existsSync(agentFileCodex) ||
|
|
108
|
-
kimiAgentInstalled) {
|
|
144
|
+
if (agentFileExists(agentsDir, agent, resolvedRuntime)) {
|
|
109
145
|
installed.push(agent);
|
|
110
146
|
}
|
|
111
147
|
else {
|
|
@@ -119,22 +155,13 @@ function checkAgentsInstalled(runtime, projectRoot) {
|
|
|
119
155
|
// when the plain presence check above passed (e.g. .md present, .toml absent).
|
|
120
156
|
// If no manifest is found the check is a no-op (graceful for claude/bundled).
|
|
121
157
|
const incomplete = [];
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
'files' in parsed &&
|
|
130
|
-
typeof parsed['files'] === 'object' &&
|
|
131
|
-
parsed['files'] !== null) {
|
|
132
|
-
manifestFiles = parsed['files'];
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch {
|
|
136
|
-
// No manifest or unreadable — completeness check is skipped
|
|
137
|
-
}
|
|
158
|
+
// #2872: the manifest read is the Installer Migration Module's, not a
|
|
159
|
+
// fourth private copy of it. Lazily required — matching this file's own
|
|
160
|
+
// capability-registry idiom — so a pure read/verify surface on the
|
|
161
|
+
// init/verify hot path takes no new static dependency.
|
|
162
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
163
|
+
const { readInstallManifest } = require('./installer-migrations.cjs');
|
|
164
|
+
const manifestFiles = readInstallManifest(node_path_1.default.dirname(agentsDir)).files;
|
|
138
165
|
if (Object.keys(manifestFiles).length > 0) {
|
|
139
166
|
for (const agent of expectedAgents) {
|
|
140
167
|
// Find all manifest keys that belong to this agent:
|
|
@@ -169,7 +196,178 @@ function checkAgentsInstalled(runtime, projectRoot) {
|
|
|
169
196
|
agent_runtime: resolvedRuntime,
|
|
170
197
|
};
|
|
171
198
|
}
|
|
199
|
+
/**
|
|
200
|
+
* Validate Codex `.toml` agent files for Anthropic-flavored `model` pins and
|
|
201
|
+
* orphaned `model_reasoning_effort` values (ADR-2313 D6, #3242).
|
|
202
|
+
*
|
|
203
|
+
* A new sibling export to {@link checkAgentsInstalled}, deliberately — that
|
|
204
|
+
* function carries 33 upstream dependents and cyclomatic complexity 25, so this
|
|
205
|
+
* posture check gets zero new branches there (see 40-design.md "Rejected" #1).
|
|
206
|
+
* Presence is `checkAgentsInstalled`'s job; this function's job starts only once
|
|
207
|
+
* the runtime is confirmed `codex` and only inspects posture, never presence.
|
|
208
|
+
*
|
|
209
|
+
* Read-only: detects, never repairs (repair is Phase 3, #3243).
|
|
210
|
+
*
|
|
211
|
+
* @param runtime - the active runtime name; defaults to GSD_RUNTIME env, then 'claude'
|
|
212
|
+
* @param projectRoot - canonical project root for local-install discovery
|
|
213
|
+
*/
|
|
214
|
+
function checkCodexModelPosture(runtime, projectRoot) {
|
|
215
|
+
// Short-circuit BEFORE any filesystem access: a non-codex runtime must never
|
|
216
|
+
// have its agents directory resolved or a stray .toml inspected, however
|
|
217
|
+
// violating that file's contents would be if it were ever read (#3242 row 25).
|
|
218
|
+
const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
|
|
219
|
+
if (resolvedRuntime !== 'codex') {
|
|
220
|
+
return {
|
|
221
|
+
ok: true,
|
|
222
|
+
violations: [],
|
|
223
|
+
checked: [],
|
|
224
|
+
agents_dir: '',
|
|
225
|
+
agent_runtime: resolvedRuntime,
|
|
226
|
+
reason: POSTURE_REASON.NOT_CODEX,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const agentsDir = getAgentsDir(resolvedRuntime, projectRoot);
|
|
230
|
+
if (!node_fs_1.default.existsSync(agentsDir)) {
|
|
231
|
+
// Presence is checkAgentsInstalled's job — an absent agents dir here is a
|
|
232
|
+
// distinct, non-violating outcome, not a failure of this check.
|
|
233
|
+
return {
|
|
234
|
+
ok: true,
|
|
235
|
+
violations: [],
|
|
236
|
+
checked: [],
|
|
237
|
+
agents_dir: agentsDir,
|
|
238
|
+
agent_runtime: resolvedRuntime,
|
|
239
|
+
reason: POSTURE_REASON.AGENTS_DIR_MISSING,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
// Skip symlinks — matches cmdEffortSync's existing idiom in commands.cts
|
|
243
|
+
// ("Skip symlinks — only write regular files..."). Here the risk is reading
|
|
244
|
+
// (not writing) through a symlink: readFileSync follows symlinks, so an
|
|
245
|
+
// agents-dir symlink pointing at an arbitrary file would let that target's
|
|
246
|
+
// contents be echoed into this function's `value` field. A symlinked agent
|
|
247
|
+
// file is a structural install choice (checkAgentsInstalled's territory),
|
|
248
|
+
// not a model-content posture defect, so it is silently excluded from
|
|
249
|
+
// `checked` rather than reported as a distinct violation — same shape as
|
|
250
|
+
// cmdEffortSync, which silently drops symlinks from its file list rather
|
|
251
|
+
// than inventing a new skip/violation reason.
|
|
252
|
+
const tomlFiles = node_fs_1.default
|
|
253
|
+
.readdirSync(agentsDir)
|
|
254
|
+
.filter((entry) => {
|
|
255
|
+
if (!entry.endsWith('.toml'))
|
|
256
|
+
return false;
|
|
257
|
+
try {
|
|
258
|
+
return node_fs_1.default.lstatSync(node_path_1.default.join(agentsDir, entry)).isFile();
|
|
259
|
+
}
|
|
260
|
+
catch {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
.sort();
|
|
265
|
+
const checked = [];
|
|
266
|
+
const violations = [];
|
|
267
|
+
for (const entry of tomlFiles) {
|
|
268
|
+
const agentName = entry.slice(0, -'.toml'.length);
|
|
269
|
+
const filePath = node_path_1.default.join(agentsDir, entry);
|
|
270
|
+
checked.push(agentName);
|
|
271
|
+
let raw;
|
|
272
|
+
try {
|
|
273
|
+
raw = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
// Never throws, never silently skips — an unreadable file is reported and
|
|
277
|
+
// the loop continues checking the rest (40-design.md "Rejected" #5).
|
|
278
|
+
violations.push({ agent: agentName, file: filePath, reason: POSTURE_REASON.UNREADABLE });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const { model, hasReasoningEffort } = (0, codex_agent_toml_cjs_1.scanTomlLines)((0, codex_agent_toml_cjs_1.stripBOM)(raw));
|
|
282
|
+
if (model !== null && (0, model_catalog_cjs_1.isAnthropicFlavoredModel)(model)) {
|
|
283
|
+
violations.push({
|
|
284
|
+
agent: agentName,
|
|
285
|
+
file: filePath,
|
|
286
|
+
reason: POSTURE_REASON.ANTHROPIC_FLAVORED_MODEL,
|
|
287
|
+
value: truncatePostureValue(model),
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
else if (model === null && hasReasoningEffort) {
|
|
291
|
+
// #838 coupling: a reasoning-effort pin with no model pin means Codex
|
|
292
|
+
// inherits the session model while the effort pin silently disagrees.
|
|
293
|
+
violations.push({
|
|
294
|
+
agent: agentName,
|
|
295
|
+
file: filePath,
|
|
296
|
+
reason: POSTURE_REASON.ORPHANED_REASONING_EFFORT,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
ok: violations.length === 0,
|
|
302
|
+
violations,
|
|
303
|
+
checked,
|
|
304
|
+
agents_dir: agentsDir,
|
|
305
|
+
agent_runtime: resolvedRuntime,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Probe a single agents dir for `<name>` across runtime filename variants.
|
|
310
|
+
* Mirrors {@link checkAgentsInstalled}'s probe (`.md`, `.agent.md`, `.toml`,
|
|
311
|
+
* and the kimi `subagents/<name>.{yaml,md}` pair) so the two can never disagree
|
|
312
|
+
* about which on-disk shapes count as "installed". Not exported — internal to
|
|
313
|
+
* {@link resolveAgentHint}.
|
|
314
|
+
*/
|
|
315
|
+
function agentFileExists(agentsDir, name, runtime) {
|
|
316
|
+
const base = node_path_1.default.join(agentsDir, `${name}.md`);
|
|
317
|
+
const copilot = node_path_1.default.join(agentsDir, `${name}.agent.md`);
|
|
318
|
+
const codex = node_path_1.default.join(agentsDir, `${name}.toml`);
|
|
319
|
+
if (node_fs_1.default.existsSync(base) || node_fs_1.default.existsSync(copilot) || node_fs_1.default.existsSync(codex)) {
|
|
320
|
+
return true;
|
|
321
|
+
}
|
|
322
|
+
// kimi requires BOTH the persona yaml and the prompt md (same as checkAgentsInstalled).
|
|
323
|
+
const kimiYaml = node_path_1.default.join(agentsDir, 'subagents', `${name}.yaml`);
|
|
324
|
+
const kimiPrompt = node_path_1.default.join(agentsDir, 'subagents', `${name}.md`);
|
|
325
|
+
return runtime === 'kimi' && node_fs_1.default.existsSync(kimiYaml) && node_fs_1.default.existsSync(kimiPrompt);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Resolve a per-plan `agent_hint` specialist name to a dispatchable subagent
|
|
329
|
+
* type on the active runtime (#1689). Unlike {@link checkAgentsInstalled},
|
|
330
|
+
* which validates the fixed GSD roster, this answers "does an agent file for
|
|
331
|
+
* this ARBITRARY name exist in the active runtime's agent dir(s)?" — so a plan
|
|
332
|
+
* can opt into a domain specialist (e.g. a Flutter engineer) that shares the
|
|
333
|
+
* gsd-executor contract without being part of the built-in roster.
|
|
334
|
+
*
|
|
335
|
+
* Probes BOTH the runtime-canonical agents dir ({@link getAgentsDir}, which
|
|
336
|
+
* honors `GSD_AGENTS_DIR`, project-local manifest-backed installs, and the
|
|
337
|
+
* claude install-relative path) AND the runtime's global config agents dir, so
|
|
338
|
+
* a specialist installed at either level is recognized. The decision in #1689
|
|
339
|
+
* explicitly requires consulting the active runtime's agent dir rather than
|
|
340
|
+
* only the Claude runtime's user-global and project-local agent dirs.
|
|
341
|
+
*
|
|
342
|
+
* @returns the name when a matching agent file exists; `null` when it does not
|
|
343
|
+
* (the caller falls back to `gsd-executor`). An empty/whitespace name always
|
|
344
|
+
* returns `null`.
|
|
345
|
+
*/
|
|
346
|
+
function resolveAgentHint(name, runtime, projectRoot) {
|
|
347
|
+
const trimmed = String(name ?? '').trim();
|
|
348
|
+
if (trimmed === '')
|
|
349
|
+
return null;
|
|
350
|
+
// A hint is a bare agent name. Reject path separators and `..` so a value
|
|
351
|
+
// like `../../README` cannot path-traverse out of the agents dir via
|
|
352
|
+
// path.join and match an unrelated file — that would echo an invalid
|
|
353
|
+
// subagent_type and block the wave, defeating fail-closed resolution.
|
|
354
|
+
if (trimmed.includes('/') || trimmed.includes('\\') || trimmed.includes('..'))
|
|
355
|
+
return null;
|
|
356
|
+
const resolvedRuntime = runtime ?? (process.env['GSD_RUNTIME'] || 'claude');
|
|
357
|
+
const candidateDirs = new Set();
|
|
358
|
+
candidateDirs.add(getAgentsDir(resolvedRuntime, projectRoot));
|
|
359
|
+
candidateDirs.add(node_path_1.default.join((0, runtime_homes_cjs_1.getGlobalConfigDir)(resolvedRuntime), 'agents'));
|
|
360
|
+
for (const dir of candidateDirs) {
|
|
361
|
+
if (agentFileExists(dir, trimmed, resolvedRuntime)) {
|
|
362
|
+
return trimmed;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
172
367
|
module.exports = {
|
|
173
368
|
getAgentsDir,
|
|
174
369
|
checkAgentsInstalled,
|
|
370
|
+
checkCodexModelPosture,
|
|
371
|
+
POSTURE_REASON,
|
|
372
|
+
resolveAgentHint,
|
|
175
373
|
};
|
|
@@ -63,6 +63,7 @@ exports.parseCoverageMatrix = parseCoverageMatrix;
|
|
|
63
63
|
exports.validateCoverageMatrix = validateCoverageMatrix;
|
|
64
64
|
exports.renderCoverageMatrix = renderCoverageMatrix;
|
|
65
65
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
66
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
66
67
|
/**
|
|
67
68
|
* Curated default trigger vocabulary. ADDITIVE-ONLY (Hyrum's Law). Tunable via
|
|
68
69
|
* the `terms` parameter.
|
|
@@ -156,9 +157,6 @@ function resolveTerms(terms) {
|
|
|
156
157
|
};
|
|
157
158
|
return { verbs: merge('verbs'), nouns: merge('nouns') };
|
|
158
159
|
}
|
|
159
|
-
function escapeRegex(s) {
|
|
160
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
161
|
-
}
|
|
162
160
|
function makeSnippet(line, anchor) {
|
|
163
161
|
const cleaned = line.replace(/\s+/g, ' ').trim();
|
|
164
162
|
if (cleaned.length <= 120)
|
|
@@ -402,10 +400,10 @@ function detectApiIntegration(text, terms) {
|
|
|
402
400
|
// Trailing boundary is a LOOKAHEAD (not consumed) so back-to-back terms
|
|
403
401
|
// separated by one boundary char are both found.
|
|
404
402
|
const verbRe = hasCompoundTerms
|
|
405
|
-
? new RegExp('(^|[^a-zA-Z0-9])(' + effective.verbs.map(escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
|
|
403
|
+
? new RegExp('(^|[^a-zA-Z0-9])(' + effective.verbs.map(pattern_cjs_1.escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
|
|
406
404
|
: null;
|
|
407
405
|
const nounRe = hasCompoundTerms
|
|
408
|
-
? new RegExp('(^|[^a-zA-Z0-9])(' + effective.nouns.map(escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
|
|
406
|
+
? new RegExp('(^|[^a-zA-Z0-9])(' + effective.nouns.map(pattern_cjs_1.escapeRegex).join('|') + ')(?=[^a-zA-Z0-9]|$)', 'gi')
|
|
409
407
|
: null;
|
|
410
408
|
const surfaceRe = new RegExp(SERVICE_SURFACE_API_RE.source, 'g');
|
|
411
409
|
const nounSet = new Set(effective.nouns);
|
|
@@ -27,6 +27,9 @@ exports.CANONICAL_EXACT = new Set([
|
|
|
27
27
|
'config.json',
|
|
28
28
|
'CLAUDE.md',
|
|
29
29
|
'RETROSPECTIVE.md',
|
|
30
|
+
'WINDOWS.md', // #3224: broken-windows ledger (src/broken-windows.cts, LEDGER_FILE_NAME)
|
|
31
|
+
'STATE-ARCHIVE.md', // state.cts's cmdStatePrune writes this at the .planning/ root
|
|
32
|
+
'milestone.lock', // #3311: milestone (phase + session) claim (src/milestone-lock.cts); persistent, unlike the transient STATE.md.lock/WAITING.json
|
|
30
33
|
]);
|
|
31
34
|
// Pattern-match canonical file names (regex tests on the basename)
|
|
32
35
|
// Each pattern includes the name of the workflow that produces it as a comment.
|
|
@@ -37,6 +37,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
37
37
|
exports.DEFAULT_ASSUMPTION_DELTA_TERMS = void 0;
|
|
38
38
|
exports.detectAssumptionDelta = detectAssumptionDelta;
|
|
39
39
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
40
|
+
const pattern_cjs_1 = require("./pattern.cjs");
|
|
40
41
|
/**
|
|
41
42
|
* Curated default trigger vocabulary. Each kind lists cue terms that signal a
|
|
42
43
|
* core-assumption monopoly has been lost. ADDITIVE-ONLY (Hyrum's Law: once
|
|
@@ -168,7 +169,7 @@ function detectAssumptionDelta(text, terms) {
|
|
|
168
169
|
continue;
|
|
169
170
|
// Word-boundary anchored, case-insensitive — same shape as ui-safety-gate.
|
|
170
171
|
// (^|[^a-zA-Z0-9])(TERM)([^a-zA-Z0-9]|$) prevents interior-substring matches.
|
|
171
|
-
const escaped = cueTerms.map(escapeRegex).join('|');
|
|
172
|
+
const escaped = cueTerms.map(pattern_cjs_1.escapeRegex).join('|');
|
|
172
173
|
const pattern = new RegExp('(^|[^a-zA-Z0-9])(' + escaped + ')([^a-zA-Z0-9]|$)', 'gi');
|
|
173
174
|
const seen = new Set();
|
|
174
175
|
for (const line of stripped.split('\n')) {
|
|
@@ -188,9 +189,6 @@ function detectAssumptionDelta(text, terms) {
|
|
|
188
189
|
}
|
|
189
190
|
return { detected: signals.length > 0, signals, terms: effective };
|
|
190
191
|
}
|
|
191
|
-
function escapeRegex(s) {
|
|
192
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
193
|
-
}
|
|
194
192
|
// ── CLI entry point ──────────────────────────────────────────────────────────
|
|
195
193
|
// Reads phase-section text from STDIN (not argv) to avoid OS ARG_MAX limits.
|
|
196
194
|
// Invoked by workflow bash as: echo "$PHASE_SECTION" | node .../assumption-delta.cjs [--json]
|
|
@@ -71,7 +71,7 @@ function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
|
|
|
71
71
|
routeHubCommandFamily({
|
|
72
72
|
family: 'audit-open',
|
|
73
73
|
args: hubArgs,
|
|
74
|
-
subcommands: ['run'],
|
|
74
|
+
subcommands: ['run', 'acknowledge'],
|
|
75
75
|
defaultSubcommand: 'run',
|
|
76
76
|
handlers: {
|
|
77
77
|
run: () => {
|
|
@@ -86,8 +86,15 @@ function routeAuditOpen({ args, cwd, raw, error, _audit, _core }) {
|
|
|
86
86
|
c.output(null, true, a.formatAuditReport(result));
|
|
87
87
|
}
|
|
88
88
|
},
|
|
89
|
+
// #3458 follow-up (design point A4): `audit-open acknowledge --category
|
|
90
|
+
// ... --milestone ...` writes/refreshes an `audit_acknowledged`
|
|
91
|
+
// suppression marker. `hubArgs.slice(2)` drops the family token and the
|
|
92
|
+
// `acknowledge` subcommand token itself, leaving just this
|
|
93
|
+
// subcommand's OWN `--flag value` pairs — the same slice
|
|
94
|
+
// `routeHubCommandFamily` itself passes to `hub.dispatch`'s `args`.
|
|
95
|
+
acknowledge: () => a.cmdAuditAcknowledge(cwd, hubArgs.slice(2), raw),
|
|
89
96
|
},
|
|
90
|
-
unknownMessage: (subcommand) => `Unknown audit-open subcommand: "${subcommand}".
|
|
97
|
+
unknownMessage: (subcommand) => `Unknown audit-open subcommand: "${subcommand}". Available: run (default, use --json for JSON output), acknowledge.`,
|
|
91
98
|
error,
|
|
92
99
|
cwd,
|
|
93
100
|
raw,
|