@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
|
@@ -11,11 +11,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
11
11
|
};
|
|
12
12
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const text_lines_cjs_1 = require("./text-lines.cjs");
|
|
14
15
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
15
16
|
const security_cjs_1 = require("./security.cjs");
|
|
16
17
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
17
18
|
const ioMod = require("./io.cjs");
|
|
18
|
-
const { output, error } = ioMod;
|
|
19
|
+
const { output, error, ERROR_REASON } = ioMod;
|
|
19
20
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
20
21
|
const configLoaderMod = require("./config-loader.cjs");
|
|
21
22
|
const { loadConfig, isGitIgnored } = configLoaderMod;
|
|
@@ -24,20 +25,28 @@ const coreUtilsMod = require("./core-utils.cjs");
|
|
|
24
25
|
const { toPosixPath, generateSlugInternal, extractOneLinerFromBody } = coreUtilsMod;
|
|
25
26
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
26
27
|
const phaseIdMod = require("./phase-id.cjs");
|
|
27
|
-
const { normalizePhaseName, comparePhaseNum, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE } = phaseIdMod;
|
|
28
|
+
const { normalizePhaseName, comparePhaseNum, extractPhaseToken, PHASE_NUMBER_TOKEN_SOURCE, isSentinelPhaseId } = phaseIdMod;
|
|
28
29
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
29
30
|
const phaseLocatorMod = require("./phase-locator.cjs");
|
|
30
|
-
const { getArchivedPhaseDirs, findPhaseInternal } = phaseLocatorMod;
|
|
31
|
+
const { getArchivedPhaseDirs, findPhaseInternal, listMilestonePhaseDirs } = phaseLocatorMod;
|
|
31
32
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
32
33
|
const roadmapParserMod = require("./roadmap-parser.cjs");
|
|
33
|
-
const { extractCurrentMilestone, stripShippedMilestones: _stripShippedMilestones, getMilestoneInfo,
|
|
34
|
+
const { extractCurrentMilestone, stripShippedMilestones: _stripShippedMilestones, getMilestoneInfo, getRoadmapPhaseInternal } = roadmapParserMod;
|
|
35
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
36
|
+
const planningScopeMod = require("./planning-scope.cjs");
|
|
37
|
+
const { SCOPE } = planningScopeMod;
|
|
34
38
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
35
39
|
const modelResolverMod = require("./model-resolver.cjs");
|
|
36
|
-
const { resolveModelInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
|
|
40
|
+
const { resolveModelInternal, resolveTierInternal, resolveModelForTier, resolveProviderEscalation, resolveEffortInternal, resolveFastModeInternal, resolveEffortForTier, resolveGranularityInternal, assertValidGranularityOverride } = modelResolverMod;
|
|
37
41
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
38
42
|
const agentCommandRouterMod = require("./agent-command-router.cjs");
|
|
39
43
|
const { AGENT_FAILURE_CLASSES } = agentCommandRouterMod;
|
|
40
44
|
const model_catalog_cjs_1 = require("./model-catalog.cjs");
|
|
45
|
+
// #3243 (ADR-2313 D7) — the Codex `.toml` sync's typed IR: parse/render/strip
|
|
46
|
+
// primitives moved from agent-install-check.cts's Phase-2 parsing into this
|
|
47
|
+
// leaf so both consumers share one block-range detector. See
|
|
48
|
+
// codex-agent-toml.cts's module header for the reader/writer reconciliation.
|
|
49
|
+
const codex_agent_toml_cjs_1 = require("./codex-agent-toml.cjs");
|
|
41
50
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
42
51
|
const hostIntegrationMod = require("./host-integration.cjs");
|
|
43
52
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -51,6 +60,13 @@ const modelProfiles = require("./model-profiles.cjs");
|
|
|
51
60
|
const { MODEL_PROFILES, VALID_PHASE_TYPES } = modelProfiles;
|
|
52
61
|
const runtime_slash_cjs_1 = require("./runtime-slash.cjs");
|
|
53
62
|
const clock_cjs_1 = require("./clock.cjs");
|
|
63
|
+
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
65
|
+
const planScanMod = require("./plan-scan.cjs");
|
|
66
|
+
const { scanPhasePlans } = planScanMod;
|
|
67
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- verification.cjs is an export= CommonJS module
|
|
68
|
+
const verificationMod = require("./verification.cjs");
|
|
69
|
+
const { resolveVerificationFile } = verificationMod;
|
|
54
70
|
// ─── Phase Status ─────────────────────────────────────────────────────────────
|
|
55
71
|
/**
|
|
56
72
|
* Phase-status precedence ladder — furthest-along wins (#2408).
|
|
@@ -104,7 +120,16 @@ function determinePhaseStatus(plans, summaries, phaseDir, defaultPending) {
|
|
|
104
120
|
// summaries >= plans — check verification
|
|
105
121
|
try {
|
|
106
122
|
const files = node_fs_1.default.readdirSync(phaseDir);
|
|
107
|
-
|
|
123
|
+
// #3473 F2: routed through the shared resolver (readdir order is
|
|
124
|
+
// filesystem-dependent, so the prior hand-rolled `.find()` could pick
|
|
125
|
+
// either file when a phase held both a canonical report and an ad-hoc
|
|
126
|
+
// `-CORRECTION-VERIFICATION.md` worksheet — see #3357).
|
|
127
|
+
// #3492: pin selection to THIS phase's own token so a stray cross-phase
|
|
128
|
+
// or sentinel-numbered canonically-shaped file cannot outrank this
|
|
129
|
+
// phase's own (possibly non-canonical) report.
|
|
130
|
+
const phaseDirName = node_path_1.default.basename(phaseDir);
|
|
131
|
+
const phaseToken = extractPhaseToken(phaseDirName);
|
|
132
|
+
const verificationFile = resolveVerificationFile(files, { allowBare: true, phaseToken, phaseDirName });
|
|
108
133
|
if (verificationFile) {
|
|
109
134
|
const verificationFilePath = node_path_1.default.join(phaseDir, verificationFile);
|
|
110
135
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(verificationFilePath) || '';
|
|
@@ -143,7 +168,7 @@ function cmdGenerateSlug(text, raw) {
|
|
|
143
168
|
output(result, raw, slug);
|
|
144
169
|
}
|
|
145
170
|
function cmdCurrentTimestamp(format, raw) {
|
|
146
|
-
const now = new Date();
|
|
171
|
+
const now = new Date(clock_cjs_1.realClock.now());
|
|
147
172
|
let result;
|
|
148
173
|
switch (format) {
|
|
149
174
|
case 'date':
|
|
@@ -348,7 +373,14 @@ function cmdHistoryDigest(cwd, raw) {
|
|
|
348
373
|
}
|
|
349
374
|
try {
|
|
350
375
|
for (const { name: dir, fullPath: dirPath } of allPhaseDirs) {
|
|
351
|
-
|
|
376
|
+
// #3183: canonical summary set (root+nested) from the single owner.
|
|
377
|
+
// This call also opens every plan file's frontmatter to check
|
|
378
|
+
// superseded status even though cmdHistoryDigest never uses planFiles
|
|
379
|
+
// or the superseded distinction — that per-phase-dir cost is accepted
|
|
380
|
+
// deliberately (correctness/single-ownership over micro-optimization;
|
|
381
|
+
// summaryFiles itself is not superseded-filtered either way). Do not
|
|
382
|
+
// "optimize" this back into a second hand-rolled summary derivation.
|
|
383
|
+
const summaries = scanPhasePlans(dirPath).summaryFiles;
|
|
352
384
|
for (const summary of summaries) {
|
|
353
385
|
const summaryFilePath = node_path_1.default.join(dirPath, summary);
|
|
354
386
|
const content = (0, shell_command_projection_cjs_1.platformReadSync)(summaryFilePath);
|
|
@@ -419,10 +451,20 @@ function cmdResolveModel(cwd, agentType, raw) {
|
|
|
419
451
|
const profile = config['model_profile'] || 'balanced';
|
|
420
452
|
const model = resolveModelInternal(cwd, agentType);
|
|
421
453
|
const effort = resolveEffortInternal(cwd, agentType);
|
|
422
|
-
|
|
454
|
+
// Own-property guard: agentType is an unvalidated CLI positional, so a
|
|
455
|
+
// prototype-chain value ("toString", "constructor") would otherwise return
|
|
456
|
+
// an inherited truthy member from this plain object and misreport a
|
|
457
|
+
// genuinely unknown agent as known (unknown_agent dropped from the result).
|
|
458
|
+
const agentModelsMap = MODEL_PROFILES;
|
|
459
|
+
const agentModels = Object.hasOwn(agentModelsMap, agentType) ? agentModelsMap[agentType] : undefined;
|
|
460
|
+
// #2229: `tier` is additive — existing keys and their values are untouched, so
|
|
461
|
+
// every `--pick model` / `--pick profile` / `--raw` consumer is unaffected. It
|
|
462
|
+
// exists because the model id is deliberately blank under resolve_model_ids:"omit",
|
|
463
|
+
// which leaves a tier-sensitive guard with nothing to read.
|
|
464
|
+
const tier = resolveTierInternal(cwd, agentType);
|
|
423
465
|
const result = agentModels
|
|
424
|
-
? { model, profile, effort }
|
|
425
|
-
: { model, profile, effort, unknown_agent: true };
|
|
466
|
+
? { model, profile, effort, tier }
|
|
467
|
+
: { model, profile, effort, tier, unknown_agent: true };
|
|
426
468
|
output(result, raw, model);
|
|
427
469
|
}
|
|
428
470
|
function cmdResolveGranularity(cwd, phaseType, raw, override) {
|
|
@@ -489,7 +531,52 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
489
531
|
const runtime = config['runtime'] || 'claude';
|
|
490
532
|
const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, effort);
|
|
491
533
|
const fastModeSupported = model_catalog_cjs_1.RUNTIMES_WITH_FAST_MODE.has(runtime);
|
|
492
|
-
|
|
534
|
+
// #3534 (10a): the effective effort — what the installed agent will actually
|
|
535
|
+
// run at. `effort` above is the config cascade; for the claude runtime the
|
|
536
|
+
// per-agent frontmatter key is the source of truth (Claude Code's Agent tool
|
|
537
|
+
// has no per-spawn effort parameter), so the query reads the installed file.
|
|
538
|
+
// An ABSENT key is a real state — the agent follows the session effort
|
|
539
|
+
// ('inherit'), not drift. No file / no frontmatter / any read failure means
|
|
540
|
+
// no evidence: the resolved value is reported, flagged 'resolved' so a
|
|
541
|
+
// consumer can tell evidence from echo. Additive only — every existing key
|
|
542
|
+
// is unchanged.
|
|
543
|
+
let effortEffectiveSource = 'resolved';
|
|
544
|
+
let effortEffective = effort;
|
|
545
|
+
if (runtime === 'claude') {
|
|
546
|
+
try {
|
|
547
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
548
|
+
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
549
|
+
const agentsDirEff = node_path_1.default.join(getGlobalConfigDir(runtime), 'agents');
|
|
550
|
+
const agentPath = node_path_1.default.join(agentsDirEff, `${agentType}.md`);
|
|
551
|
+
// agentType is an unvalidated CLI positional: keep the read inside the
|
|
552
|
+
// agents dir so `../../x` cannot point it elsewhere (defense in depth —
|
|
553
|
+
// the reflected surface is only a frontmatter effort line).
|
|
554
|
+
if (!node_path_1.default.resolve(agentPath).startsWith(node_path_1.default.resolve(agentsDirEff) + node_path_1.default.sep)) {
|
|
555
|
+
throw new Error('agent path escapes the agents directory');
|
|
556
|
+
}
|
|
557
|
+
const agentContent = node_fs_1.default.readFileSync(agentPath, 'utf8');
|
|
558
|
+
// eslint-disable-next-line local/no-unbounded-quantifier -- same lazy `*?` bounded by the `^---$/m` closing anchor as the sibling frontmatter regexes in this file
|
|
559
|
+
const fmMatchEff = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(agentContent);
|
|
560
|
+
if (fmMatchEff) {
|
|
561
|
+
const effortLine = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchEff[1]);
|
|
562
|
+
if (effortLine) {
|
|
563
|
+
effortEffective = effortLine[1];
|
|
564
|
+
effortEffectiveSource = 'frontmatter';
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
effortEffective = 'inherit';
|
|
568
|
+
effortEffectiveSource = 'frontmatter-absent';
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch { /* no frontmatter evidence — stay on the resolved value */ }
|
|
573
|
+
}
|
|
574
|
+
// Own-property guard: agentType is an unvalidated CLI positional, so a
|
|
575
|
+
// prototype-chain value ("toString", "constructor") would otherwise return
|
|
576
|
+
// an inherited truthy member from this plain object and misreport a
|
|
577
|
+
// genuinely unknown agent as known (unknown_agent dropped from the result).
|
|
578
|
+
const agentModelsMap = MODEL_PROFILES;
|
|
579
|
+
const agentModels = Object.hasOwn(agentModelsMap, agentType) ? agentModelsMap[agentType] : undefined;
|
|
493
580
|
const result = {
|
|
494
581
|
model,
|
|
495
582
|
profile,
|
|
@@ -497,6 +584,8 @@ function cmdResolveExecution(cwd, agentType, raw, opts) {
|
|
|
497
584
|
effort_rendered: rendered.value,
|
|
498
585
|
effort_param: rendered.param,
|
|
499
586
|
effort_propagation: rendered.channel,
|
|
587
|
+
effort_effective: effortEffective,
|
|
588
|
+
effort_effective_source: effortEffectiveSource,
|
|
500
589
|
fast_mode: fastMode,
|
|
501
590
|
fast_mode_supported: fastModeSupported,
|
|
502
591
|
};
|
|
@@ -566,6 +655,29 @@ function setEffortFrontmatter(content, effortValue) {
|
|
|
566
655
|
const closingStart = match.index + openLen + fmBody.length;
|
|
567
656
|
return content.slice(0, closingStart) + `effort: ${effortValue}${eol}` + content.slice(closingStart);
|
|
568
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* #3533 (10d) — remove exactly the frontmatter `effort:` line (and its line
|
|
660
|
+
* ending) so an agent configured for `inherit` carries NO key. Mirrors the
|
|
661
|
+
* codex-agent-toml strip discipline: targeted line removal, EOL-aware, every
|
|
662
|
+
* other byte (comments, sibling keys, the body) untouched.
|
|
663
|
+
*/
|
|
664
|
+
function removeEffortFrontmatter(content) {
|
|
665
|
+
// Scoped to the FIRST frontmatter block (not a whole-file /m match): a
|
|
666
|
+
// preamble or body line starting with `effort:` (a fenced config example,
|
|
667
|
+
// a thematic-break flanked fragment) must never be the line removed.
|
|
668
|
+
const fmRe = /^---\r?\n([\s\S]*?)^---\r?$/m;
|
|
669
|
+
const match = fmRe.exec(content);
|
|
670
|
+
if (!match)
|
|
671
|
+
return content;
|
|
672
|
+
const fmBody = match[1];
|
|
673
|
+
const lineRe = /^effort:[ \t]*.*\r?\n?/m;
|
|
674
|
+
if (!lineRe.test(fmBody))
|
|
675
|
+
return content;
|
|
676
|
+
const strippedFm = fmBody.replace(lineRe, '');
|
|
677
|
+
const openLen = 3 + (/^---\r\n/.test(content) ? 2 : 1);
|
|
678
|
+
const closingStart = match.index + openLen + fmBody.length;
|
|
679
|
+
return content.slice(0, match.index + openLen) + strippedFm + content.slice(closingStart);
|
|
680
|
+
}
|
|
569
681
|
/**
|
|
570
682
|
* #488 — Re-sync effort: frontmatter in all installed gsd-*.md agent files to
|
|
571
683
|
* match the current effort config, without requiring a full reinstall.
|
|
@@ -581,6 +693,14 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
581
693
|
const dryRun = opts.dryRun !== false;
|
|
582
694
|
const config = loadConfig(cwd);
|
|
583
695
|
const runtime = opts.runtime || config['runtime'] || 'claude';
|
|
696
|
+
// ADR-2313 D7 (#3243) — Codex gets its own `.toml` sync path (strip a stale
|
|
697
|
+
// Anthropic/tier `model` and an orphaned `model_reasoning_effort`, leaving a
|
|
698
|
+
// legal pin untouched). Every other non-claude runtime keeps the prior
|
|
699
|
+
// early-return; the claude branch below is untouched byte-for-byte.
|
|
700
|
+
if (runtime === 'codex') {
|
|
701
|
+
cmdEffortSyncCodex(raw, dryRun, opts.configDir);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
584
704
|
if (runtime !== 'claude') {
|
|
585
705
|
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, reason: `runtime '${runtime}' does not use effort: frontmatter` }, raw, '');
|
|
586
706
|
return;
|
|
@@ -619,8 +739,32 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
619
739
|
const content = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
620
740
|
// Resolve using install-time logic: home defaults merged with project config.
|
|
621
741
|
const universalEffort = resolveInstallTimeEffort(effortCfg, agentName);
|
|
742
|
+
// #3533 (10d): 'inherit' means the key must NOT exist. An absent key is
|
|
743
|
+
// the CORRECT state (in sync, skipped) — before #3533 absence read as null
|
|
744
|
+
// drift and the sync re-added a hand-stripped key on every apply. A
|
|
745
|
+
// present key under inherit is stripped, reported as {from, to: null}.
|
|
746
|
+
if (universalEffort === 'inherit') {
|
|
747
|
+
// eslint-disable-next-line local/no-unbounded-quantifier -- same lazy `*?` bounded by the `^---$/m` closing anchor as the concrete-path fmMatch below; duplicated here so the inherit branch validates against the same frontmatter span the strip targets
|
|
748
|
+
const fmMatchInherit = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(content);
|
|
749
|
+
if (!fmMatchInherit) {
|
|
750
|
+
skipped++;
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
const effortMatchInherit = /^effort:[ \t]*(.+?)[ \t]*$/m.exec(fmMatchInherit[1]);
|
|
754
|
+
if (!effortMatchInherit) {
|
|
755
|
+
skipped++;
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
changes.push({ agent: agentName, from: effortMatchInherit[1], to: null });
|
|
759
|
+
synced++;
|
|
760
|
+
if (!dryRun) {
|
|
761
|
+
node_fs_1.default.writeFileSync(filePath, removeEffortFrontmatter(content));
|
|
762
|
+
}
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
622
765
|
const rendered = (0, model_catalog_cjs_1.renderEffortForRuntime)(runtime, universalEffort);
|
|
623
766
|
const newEffortValue = rendered.value;
|
|
767
|
+
// eslint-disable-next-line local/no-unbounded-quantifier -- lazy `*?` bounded by the `^---$/m` closing anchor, no nested quantifier, measured linear to 5MB (no-closing-marker adversarial input)
|
|
624
768
|
const fmMatch = /^---\r?\n([\s\S]*?)^---\r?$/m.exec(content);
|
|
625
769
|
if (!fmMatch) {
|
|
626
770
|
skipped++;
|
|
@@ -640,6 +784,112 @@ function cmdEffortSync(cwd, raw, opts) {
|
|
|
640
784
|
}
|
|
641
785
|
output({ synced, skipped, changes, dry_run: dryRun, agents_dir: agentsDir }, raw, synced > 0 ? 'changed' : 'ok');
|
|
642
786
|
}
|
|
787
|
+
/**
|
|
788
|
+
* ADR-2313 D7 (#3243) — the Codex branch of `cmdEffortSync`. Strips a stale
|
|
789
|
+
* Anthropic-flavored/tier `model` pin and an orphaned `model_reasoning_effort`
|
|
790
|
+
* from every installed `~/.codex/agents/<agent>.toml`, leaving a legal
|
|
791
|
+
* real-Codex pin (and its coupled effort) untouched. Dry-run by default; every
|
|
792
|
+
* strip reported as a structured `{agent, field, from}` change; an unparseable
|
|
793
|
+
* document is refused and reported, never partially rewritten (40-design.md
|
|
794
|
+
* "Reconciliation" — parseCodexAgentToml is the STRICT half of the reader/
|
|
795
|
+
* writer split). Result shape is additive over the claude branch's
|
|
796
|
+
* `{synced, skipped, changes, dry_run, agents_dir}` — `refused` and
|
|
797
|
+
* `write_failures` are new fields, never a reshape of the existing ones.
|
|
798
|
+
*/
|
|
799
|
+
function cmdEffortSyncCodex(raw, dryRun, configDir) {
|
|
800
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/unbound-method
|
|
801
|
+
const { getGlobalConfigDir } = require('./runtime-homes.cjs');
|
|
802
|
+
const agentsDir = node_path_1.default.join(configDir || getGlobalConfigDir('codex'), 'agents');
|
|
803
|
+
if (!node_fs_1.default.existsSync(agentsDir)) {
|
|
804
|
+
output({ synced: 0, skipped: 0, changes: [], dry_run: dryRun, agents_dir: agentsDir, reason: 'agents directory not found' }, raw, '');
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
// Skip symlinks — matches the claude branch's existing guard above (only
|
|
808
|
+
// write regular files, never follow a symlink into clobbering its target).
|
|
809
|
+
const files = node_fs_1.default
|
|
810
|
+
.readdirSync(agentsDir)
|
|
811
|
+
.filter(f => {
|
|
812
|
+
if (!f.endsWith('.toml'))
|
|
813
|
+
return false;
|
|
814
|
+
try {
|
|
815
|
+
return node_fs_1.default.lstatSync(node_path_1.default.join(agentsDir, f)).isFile();
|
|
816
|
+
}
|
|
817
|
+
catch {
|
|
818
|
+
return false;
|
|
819
|
+
}
|
|
820
|
+
})
|
|
821
|
+
.sort();
|
|
822
|
+
const changes = [];
|
|
823
|
+
const refused = [];
|
|
824
|
+
const writeFailures = [];
|
|
825
|
+
let synced = 0;
|
|
826
|
+
let skipped = 0;
|
|
827
|
+
for (const file of files) {
|
|
828
|
+
const agentName = file.replace(/\.toml$/, '');
|
|
829
|
+
const filePath = node_path_1.default.join(agentsDir, file);
|
|
830
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf8');
|
|
831
|
+
const parsed = (0, codex_agent_toml_cjs_1.parseCodexAgentToml)(content);
|
|
832
|
+
if (!parsed.ok) {
|
|
833
|
+
// Never partially rewritten (40-design.md, ADR-2313 reader/writer
|
|
834
|
+
// boundary): an unparseable document is skipped and reported, not
|
|
835
|
+
// guessed at.
|
|
836
|
+
skipped++;
|
|
837
|
+
refused.push({ agent: agentName, file: filePath, reason: parsed.reason });
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
let doc = parsed.doc;
|
|
841
|
+
const stripModelNeeded = doc.model !== null && (0, model_catalog_cjs_1.isAnthropicFlavoredModel)(doc.model);
|
|
842
|
+
// #838 coupling: an orphaned effort (no model) is always stale; a stale
|
|
843
|
+
// model's effort is coupled to it and strips with it. A legal pin's effort
|
|
844
|
+
// (model present, not Anthropic-flavored) is left untouched (rows 4-5).
|
|
845
|
+
const stripEffortNeeded = doc.reasoningEffort !== null && (stripModelNeeded || doc.model === null);
|
|
846
|
+
if (!stripModelNeeded && !stripEffortNeeded) {
|
|
847
|
+
// Posture-clean, OR a legal pin (and its coupled effort) — reported
|
|
848
|
+
// skipped, never synced (ADR-2313 reader/writer boundary).
|
|
849
|
+
skipped++;
|
|
850
|
+
continue;
|
|
851
|
+
}
|
|
852
|
+
const pendingChanges = [];
|
|
853
|
+
if (stripModelNeeded) {
|
|
854
|
+
pendingChanges.push({ agent: agentName, field: 'model', from: doc.model, to: null });
|
|
855
|
+
doc = (0, codex_agent_toml_cjs_1.stripModel)(doc);
|
|
856
|
+
}
|
|
857
|
+
if (stripEffortNeeded) {
|
|
858
|
+
pendingChanges.push({ agent: agentName, field: 'model_reasoning_effort', from: doc.reasoningEffort, to: null });
|
|
859
|
+
doc = (0, codex_agent_toml_cjs_1.stripReasoningEffort)(doc);
|
|
860
|
+
}
|
|
861
|
+
if (!dryRun) {
|
|
862
|
+
// Atomic publish (ADR-2313 "never partially rewritten"): write the
|
|
863
|
+
// rendered TOML to a sibling tmp file, then rename it over the target.
|
|
864
|
+
// Same-filesystem rename is atomic, so filePath is either the old bytes
|
|
865
|
+
// or the new ones, never truncated/half-written mid-crash. Deliberately
|
|
866
|
+
// NOT platformWriteSync — its normalizeContent step rewrites CRLF/
|
|
867
|
+
// trailing-newline bytes, which would break the byte-identical
|
|
868
|
+
// round-trip (A14) this writer must preserve. retryRenameSync (not a
|
|
869
|
+
// bare fs.renameSync) carries the transient-Windows-lock retry per
|
|
870
|
+
// DEFECT.WINDOWS-FS-OPS.
|
|
871
|
+
const tmpPath = `${filePath}.tmp.${process.pid}`;
|
|
872
|
+
try {
|
|
873
|
+
node_fs_1.default.writeFileSync(tmpPath, (0, codex_agent_toml_cjs_1.renderCodexAgentToml)(doc));
|
|
874
|
+
(0, shell_command_projection_cjs_1.retryRenameSync)(tmpPath, filePath);
|
|
875
|
+
}
|
|
876
|
+
catch (err) {
|
|
877
|
+
// Reported, not thrown — the remaining agents still get processed.
|
|
878
|
+
// Clean up the orphaned tmp file; filePath itself was never touched.
|
|
879
|
+
try {
|
|
880
|
+
node_fs_1.default.unlinkSync(tmpPath);
|
|
881
|
+
}
|
|
882
|
+
catch { /* already gone or never created */ }
|
|
883
|
+
skipped++;
|
|
884
|
+
writeFailures.push({ agent: agentName, file: filePath, error: err instanceof Error ? err.message : String(err) });
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
changes.push(...pendingChanges);
|
|
889
|
+
synced++;
|
|
890
|
+
}
|
|
891
|
+
output({ synced, skipped, changes, dry_run: dryRun, agents_dir: agentsDir, refused, write_failures: writeFailures }, raw, synced > 0 ? 'changed' : 'ok');
|
|
892
|
+
}
|
|
643
893
|
/**
|
|
644
894
|
* Detect the phase number for a commit from its `--files` path list.
|
|
645
895
|
*
|
|
@@ -693,6 +943,71 @@ function detectPhaseNumberFromFiles(files) {
|
|
|
693
943
|
}
|
|
694
944
|
return null;
|
|
695
945
|
}
|
|
946
|
+
/**
|
|
947
|
+
* #3587: resolve the `phase_commit_docs.<phase-id>` override for `phaseNum`
|
|
948
|
+
* against `config['phase_commit_docs']` (a `{ "<phase-id>": boolean }` map, the
|
|
949
|
+
* same shape `agent_skills`/`features` use for their dynamic key families).
|
|
950
|
+
* Returns `undefined` — "no override applies" — when: no phase is known (B7),
|
|
951
|
+
* the map carries no entry for THIS phase (B5: no cross-phase leak), or the
|
|
952
|
+
* entry exists but is not a boolean (B6: never silently coerced). Both sides of
|
|
953
|
+
* the comparison route through `normalizePhaseName` so `3`, `03`, and `PROJ-03`
|
|
954
|
+
* all resolve to the same entry (B4/B9), reusing the single-owner phase-id
|
|
955
|
+
* normalizer rather than a second, looser string-equality rule.
|
|
956
|
+
*/
|
|
957
|
+
function resolvePhaseCommitDocsOverride(config, phaseNum) {
|
|
958
|
+
if (!phaseNum)
|
|
959
|
+
return undefined;
|
|
960
|
+
const overrides = config['phase_commit_docs'];
|
|
961
|
+
if (!overrides || typeof overrides !== 'object' || Array.isArray(overrides))
|
|
962
|
+
return undefined;
|
|
963
|
+
const target = normalizePhaseName(phaseNum);
|
|
964
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
965
|
+
if (normalizePhaseName(key) === target) {
|
|
966
|
+
return typeof value === 'boolean' ? value : undefined;
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
return undefined;
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* #3587: the four-tier `commit_docs` precedence chain for a single commit —
|
|
973
|
+
* `phase_commit_docs.<phase-id>` (tier 1, resolved HERE because this call site
|
|
974
|
+
* is the one place that knows the phase — see 40-design.md "Rejected" §1: NOT
|
|
975
|
+
* inside `loadConfig`, which has no phase context and is called by nearly every
|
|
976
|
+
* command), then the pre-existing explicit `commit_docs` (tier 2), `.gitignore`
|
|
977
|
+
* auto-detect (tier 3), and manifest default (tier 4). Tiers 2-4 are byte-for-
|
|
978
|
+
* behaviour identical to the pre-#3587 inline checks (epic #2292 AC4): when no
|
|
979
|
+
* phase override applies, `resolved` matches exactly what those checks computed
|
|
980
|
+
* and `source` merely labels which of the three decided it.
|
|
981
|
+
*
|
|
982
|
+
* `isPlanningGitIgnored` is a thunk, not a plain boolean, so the pre-existing
|
|
983
|
+
* short-circuit is preserved byte-for-behaviour: the original inline checks
|
|
984
|
+
* only ever ran `isGitIgnored` (a real `git check-ignore` subprocess) when
|
|
985
|
+
* `commit_docs` was truthy, and a phase override or an explicit `commit_docs:
|
|
986
|
+
* false` must keep skipping that call entirely, not just its result. Passing
|
|
987
|
+
* a thunk also keeps this function pure and directly property-testable
|
|
988
|
+
* (test matrix F1) without spawning git.
|
|
989
|
+
*/
|
|
990
|
+
function resolveCommitDocsPolicy(config, phaseNum, isPlanningGitIgnored) {
|
|
991
|
+
const phaseOverride = resolvePhaseCommitDocsOverride(config, phaseNum);
|
|
992
|
+
if (phaseOverride !== undefined)
|
|
993
|
+
return { resolved: phaseOverride, source: 'phase' };
|
|
994
|
+
if (!config['commit_docs'])
|
|
995
|
+
return { resolved: false, source: 'config' };
|
|
996
|
+
if (isPlanningGitIgnored())
|
|
997
|
+
return { resolved: false, source: 'gitignore' };
|
|
998
|
+
return { resolved: true, source: 'default' };
|
|
999
|
+
}
|
|
1000
|
+
// Reason string per commit_docs-resolution source, for the tier-1/tier-2 skip
|
|
1001
|
+
// envelope below. `phase` gets its OWN reason (`skipped_commit_docs_phase_false`)
|
|
1002
|
+
// rather than reusing `skipped_commit_docs_false` — telling a user "commit_docs
|
|
1003
|
+
// is false" when their project setting is actually `true` would be actively
|
|
1004
|
+
// misleading (design "Rejected" §3). `config` keeps the pre-existing string
|
|
1005
|
+
// unchanged: `agents/gsd-executor.md` pattern-matches on it (D2).
|
|
1006
|
+
const COMMIT_DOCS_SKIP_REASON = {
|
|
1007
|
+
phase: 'skipped_commit_docs_phase_false',
|
|
1008
|
+
config: 'skipped_commit_docs_false',
|
|
1009
|
+
gitignore: 'skipped_gitignored',
|
|
1010
|
+
};
|
|
696
1011
|
function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
697
1012
|
if (!message && !amend) {
|
|
698
1013
|
error('commit message required');
|
|
@@ -706,18 +1021,20 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
706
1021
|
sanitizedMessage = sanitizeForPrompt(sanitizedMessage);
|
|
707
1022
|
}
|
|
708
1023
|
const config = loadConfig(cwd);
|
|
709
|
-
// Check commit_docs config
|
|
1024
|
+
// Check commit_docs config — #3587: resolved through the tier 1
|
|
1025
|
+
// (phase_commit_docs.<phase-id>) → tier 2 (commit_docs) → tier 3 (.gitignore)
|
|
1026
|
+
// → tier 4 (default) precedence chain; see resolveCommitDocsPolicy above.
|
|
710
1027
|
// `skipped: true` is explicit so agent prompts can match on a first-class
|
|
711
1028
|
// success signal rather than inferring "skip" from "committed is missing"
|
|
712
1029
|
// and improvising raw git fallbacks (#3678).
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
1030
|
+
const commitDocsPolicy = resolveCommitDocsPolicy(config, detectPhaseNumberFromFiles(files), () => isGitIgnored(cwd, '.planning'));
|
|
1031
|
+
if (!commitDocsPolicy.resolved) {
|
|
1032
|
+
const result = {
|
|
1033
|
+
committed: false,
|
|
1034
|
+
skipped: true,
|
|
1035
|
+
hash: null,
|
|
1036
|
+
reason: COMMIT_DOCS_SKIP_REASON[commitDocsPolicy.source],
|
|
1037
|
+
};
|
|
721
1038
|
output(result, raw, 'skipped');
|
|
722
1039
|
return;
|
|
723
1040
|
}
|
|
@@ -752,7 +1069,19 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
752
1069
|
}
|
|
753
1070
|
}
|
|
754
1071
|
else if (branchingStrategy === 'milestone') {
|
|
755
|
-
const
|
|
1072
|
+
const milestoneInfo = getMilestoneInfo(cwd);
|
|
1073
|
+
// #3216 review Finding 3: explicit scope gate instead of plain truthiness.
|
|
1074
|
+
// COMPLETE and TRUNCATED both carry a real `version` (ADR-3180 §7.2 rule
|
|
1075
|
+
// 6 — TRUNCATED means the version resolved but the milestone's NAME did
|
|
1076
|
+
// not), so a TRUNCATED identity is acceptable here: `milestone.version`
|
|
1077
|
+
// only feeds a BRANCH NAME, and `generateSlugInternal(null) || 'milestone'`
|
|
1078
|
+
// already degrades the missing name to the literal "milestone" slug on
|
|
1079
|
+
// purpose. This differs from `archivePhaseDirectories` (milestone.cts),
|
|
1080
|
+
// which uses the same value as a DIRECTORY NAME and therefore demands
|
|
1081
|
+
// COMPLETE only — a real-but-unnamed version is not safe enough there.
|
|
1082
|
+
const milestone = milestoneInfo.scope === SCOPE.COMPLETE || milestoneInfo.scope === SCOPE.TRUNCATED
|
|
1083
|
+
? milestoneInfo.value
|
|
1084
|
+
: null;
|
|
756
1085
|
if (milestone && milestone.version) {
|
|
757
1086
|
branchName = config['milestone_branch_template']
|
|
758
1087
|
.replace('{milestone}', milestone.version)
|
|
@@ -762,22 +1091,28 @@ function cmdCommit(cwd, message, files, raw, amend, noVerify) {
|
|
|
762
1091
|
if (branchName) {
|
|
763
1092
|
const currentBranch = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd });
|
|
764
1093
|
if (currentBranch.exitCode === 0 && currentBranch.stdout.trim() !== branchName) {
|
|
765
|
-
// #2539/#3079:
|
|
766
|
-
//
|
|
767
|
-
//
|
|
768
|
-
//
|
|
769
|
-
//
|
|
770
|
-
//
|
|
771
|
-
//
|
|
772
|
-
//
|
|
773
|
-
//
|
|
774
|
-
//
|
|
775
|
-
//
|
|
1094
|
+
// #2539/#3079/#3207: two cases the prior (#3079) code collapsed into one.
|
|
1095
|
+
// #1278 intent: CREATE the phase/milestone branch before the FIRST commit
|
|
1096
|
+
// on it so the phase's work accumulates there. #3079/#2539 hazard: never
|
|
1097
|
+
// silently switch an already-checked-out working branch onto a DIFFERENT
|
|
1098
|
+
// EXISTING branch — that resurrects merged-and-deleted phase branches and
|
|
1099
|
+
// silently moves HEAD onto a stale ref (#2539 AC2: an auto-checkout
|
|
1100
|
+
// mid-commit must never happen silently).
|
|
1101
|
+
// Reconciliation (#3207): a brand-new branch has no resurrection target,
|
|
1102
|
+
// so create-and-switch is safe here and is exactly the #1278 intent; an
|
|
1103
|
+
// EXISTING branch is never switched to (the else arm logs + commits in
|
|
1104
|
+
// place). The fresh create is logged so the first phase-scoped commit is
|
|
1105
|
+
// not silent about where the work is landing (#3207 AC3).
|
|
776
1106
|
const verify = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--verify', `refs/heads/${branchName}`], { cwd });
|
|
777
1107
|
if (verify.exitCode !== 0) {
|
|
778
|
-
// Branch does not exist —
|
|
779
|
-
|
|
780
|
-
|
|
1108
|
+
// Branch does not exist — CREATE AND SWITCH (the #1278 first-commit
|
|
1109
|
+
// case). checkout -b cannot resurrect anything: the branch was just
|
|
1110
|
+
// verified absent, so it is created fresh at HEAD.
|
|
1111
|
+
const create = (0, shell_command_projection_cjs_1.execGit)(['checkout', '-b', branchName], { cwd });
|
|
1112
|
+
if (create.exitCode === 0) {
|
|
1113
|
+
process.stderr.write(`${branchingStrategy} branch "${branchName}" created; switched to it for this commit.\n`);
|
|
1114
|
+
}
|
|
1115
|
+
else {
|
|
781
1116
|
process.stderr.write(`Warning: could not create ${branchingStrategy} branch "${branchName}" ` +
|
|
782
1117
|
`(${create.stderr.trim()}); committing on the current branch "${currentBranch.stdout.trim()}".\n`);
|
|
783
1118
|
}
|
|
@@ -1384,20 +1719,28 @@ async function cmdWebsearch(query, options, raw) {
|
|
|
1384
1719
|
}
|
|
1385
1720
|
function cmdProgressRender(cwd, format, raw) {
|
|
1386
1721
|
const phasesDir = planningPaths(cwd).phases;
|
|
1387
|
-
const milestone = getMilestoneInfo(cwd);
|
|
1722
|
+
const milestone = getMilestoneInfo(cwd).value;
|
|
1388
1723
|
const phases = [];
|
|
1389
1724
|
let totalPlans = 0;
|
|
1390
1725
|
let totalSummaries = 0;
|
|
1726
|
+
let phaseScope = null;
|
|
1391
1727
|
try {
|
|
1392
|
-
|
|
1393
|
-
|
|
1728
|
+
// #3185 (ADR-3180 Decision 1): the single owner applies the milestone
|
|
1729
|
+
// window AND the sentinel filter and returns dirs already sorted by
|
|
1730
|
+
// comparePhaseNum. This command previously read the phases directory
|
|
1731
|
+
// directly with neither, which is why `query progress` listed 999.*
|
|
1732
|
+
// backlog directories as current-milestone phases (#3167).
|
|
1733
|
+
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, { cwd });
|
|
1734
|
+
phaseScope = scope;
|
|
1394
1735
|
for (const dir of dirs) {
|
|
1395
1736
|
const dm = dir.match(/^(\d+(?:\.\d+)*)-?(.*)/);
|
|
1396
1737
|
const phaseNum = dm ? dm[1] : dir;
|
|
1397
1738
|
const phaseName = dm && dm[2] ? dm[2].replace(/-/g, ' ') : '';
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
const
|
|
1739
|
+
// #3183: canonical plan/summary counts (root+nested, superseded-excluded,
|
|
1740
|
+
// canonical pairing) from the single owner.
|
|
1741
|
+
const phaseScan = scanPhasePlans(node_path_1.default.join(phasesDir, dir));
|
|
1742
|
+
const plans = phaseScan.planCount;
|
|
1743
|
+
const summaries = phaseScan.summaryCount;
|
|
1401
1744
|
totalPlans += plans;
|
|
1402
1745
|
totalSummaries += summaries;
|
|
1403
1746
|
const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Pending');
|
|
@@ -1405,14 +1748,24 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
1405
1748
|
}
|
|
1406
1749
|
}
|
|
1407
1750
|
catch { /* intentionally empty */ }
|
|
1408
|
-
|
|
1751
|
+
// #3217 (ADR-3180 §7.6 rule 4): `phaseScope` was already computed above
|
|
1752
|
+
// (Phase 3, #3222) but never consulted before rendering — a percentage was
|
|
1753
|
+
// rendered from counts the scope said were not answers (TRUNCATED /
|
|
1754
|
+
// UNSCOPED / UNREADABLE). Withhold the percentage itself (never `0` — a
|
|
1755
|
+
// real `0` under COMPLETE must still render, rule 2's territory) when the
|
|
1756
|
+
// scope is not COMPLETE. `phaseScope` stays `null` only if the try block
|
|
1757
|
+
// above threw before assigning it; treat that the same as non-COMPLETE.
|
|
1758
|
+
const percent = phaseScope === SCOPE.COMPLETE
|
|
1759
|
+
? (0, phase_lifecycle_cjs_1.clampPercent)(totalSummaries, totalPlans)
|
|
1760
|
+
: null;
|
|
1409
1761
|
if (format === 'table') {
|
|
1410
1762
|
// Render markdown table
|
|
1411
1763
|
const barWidth = 10;
|
|
1412
|
-
const filled = Math.round((percent / 100) * barWidth);
|
|
1764
|
+
const filled = percent === null ? 0 : Math.round((percent / 100) * barWidth);
|
|
1413
1765
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1414
|
-
|
|
1415
|
-
out
|
|
1766
|
+
const percentSuffix = percent === null ? '' : ` (${percent}%)`;
|
|
1767
|
+
let out = `# ${milestone?.version ?? ''} ${milestone?.name ?? ''}\n\n`;
|
|
1768
|
+
out += `**Progress:** [${bar}] ${totalSummaries}/${totalPlans} plans${percentSuffix}\n\n`;
|
|
1416
1769
|
out += `| Phase | Name | Plans | Status |\n`;
|
|
1417
1770
|
out += `|-------|------|-------|--------|\n`;
|
|
1418
1771
|
for (const p of phases) {
|
|
@@ -1422,20 +1775,24 @@ function cmdProgressRender(cwd, format, raw) {
|
|
|
1422
1775
|
}
|
|
1423
1776
|
else if (format === 'bar') {
|
|
1424
1777
|
const barWidth = 20;
|
|
1425
|
-
const filled = Math.round((percent / 100) * barWidth);
|
|
1778
|
+
const filled = percent === null ? 0 : Math.round((percent / 100) * barWidth);
|
|
1426
1779
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1427
|
-
const
|
|
1780
|
+
const percentSuffix = percent === null ? '' : ` (${percent}%)`;
|
|
1781
|
+
const text = `[${bar}] ${totalSummaries}/${totalPlans} plans${percentSuffix}`;
|
|
1428
1782
|
output({ bar: text, percent, completed: totalSummaries, total: totalPlans }, raw, text);
|
|
1429
1783
|
}
|
|
1430
1784
|
else {
|
|
1431
1785
|
// JSON format
|
|
1432
1786
|
output({
|
|
1433
|
-
milestone_version: milestone
|
|
1434
|
-
milestone_name: milestone
|
|
1787
|
+
milestone_version: milestone?.version ?? null,
|
|
1788
|
+
milestone_name: milestone?.name ?? null,
|
|
1435
1789
|
phases,
|
|
1436
1790
|
total_plans: totalPlans,
|
|
1437
1791
|
total_summaries: totalSummaries,
|
|
1438
1792
|
percent,
|
|
1793
|
+
// #3185 (ADR-3180 Decision 2): the enumeration's scope, so a consumer
|
|
1794
|
+
// can tell a genuinely-empty milestone from one it could not scope.
|
|
1795
|
+
phase_scope: phaseScope,
|
|
1439
1796
|
}, raw, undefined);
|
|
1440
1797
|
}
|
|
1441
1798
|
}
|
|
@@ -1492,7 +1849,9 @@ function cmdTodoMatchPhase(cwd, phase, raw) {
|
|
|
1492
1849
|
if (phaseInfoDisk && phaseInfoDisk['found']) {
|
|
1493
1850
|
try {
|
|
1494
1851
|
const phaseDir = node_path_1.default.join(cwd, phaseInfoDisk['directory']);
|
|
1495
|
-
|
|
1852
|
+
// #3183: canonical plan set (root+nested, superseded-excluded) from the
|
|
1853
|
+
// single owner, rather than a root-only hand-rolled readdirSync filter.
|
|
1854
|
+
const planFiles = scanPhasePlans(phaseDir).planFiles;
|
|
1496
1855
|
for (const pf of planFiles) {
|
|
1497
1856
|
const planContent = (0, shell_command_projection_cjs_1.platformReadSync)(node_path_1.default.join(phaseDir, pf));
|
|
1498
1857
|
if (planContent === null)
|
|
@@ -1629,12 +1988,12 @@ function cmdStats(cwd, format, raw) {
|
|
|
1629
1988
|
const roadmapPath = planningPaths(cwd).roadmap;
|
|
1630
1989
|
const reqPath = planningPaths(cwd).requirements;
|
|
1631
1990
|
const statePath = planningPaths(cwd).state;
|
|
1632
|
-
const milestone = getMilestoneInfo(cwd);
|
|
1633
|
-
const isDirInMilestone = getMilestonePhaseFilter(cwd);
|
|
1991
|
+
const milestone = getMilestoneInfo(cwd).value;
|
|
1634
1992
|
// Phase & plan stats (reuse progress pattern)
|
|
1635
1993
|
const phasesByNumber = new Map();
|
|
1636
1994
|
let totalPlans = 0;
|
|
1637
1995
|
let totalSummaries = 0;
|
|
1996
|
+
let phaseScope = null;
|
|
1638
1997
|
try {
|
|
1639
1998
|
const roadmapRaw = (0, shell_command_projection_cjs_1.platformReadSync)(roadmapPath);
|
|
1640
1999
|
if (roadmapRaw === null)
|
|
@@ -1643,9 +2002,22 @@ function cmdStats(cwd, format, raw) {
|
|
|
1643
2002
|
// Matches both plain numeric (Phase 1:) and milestone-prefixed (Phase 2-01:) headings.
|
|
1644
2003
|
// Also tolerates optional [bracket-token] scope prefix on phase headings.
|
|
1645
2004
|
// #1729: `(?:\s*\([^)\n]{0,200}\))?` tolerates a pre-colon ( ) tag (literal mirror of OPTIONAL_PHASE_TAG_SOURCE).
|
|
1646
|
-
|
|
2005
|
+
// #3569: the id capture is the canonical #3036 shape (digit REQUIRED — incl.
|
|
2006
|
+
// letter-prefixed B7, decimals, milestone 2-01), the same group roadmap.cts's
|
|
2007
|
+
// collectAnalyzePhases uses. The former `([\w][\w.-]*)` matched ANY word, so
|
|
2008
|
+
// prose mentioning `### Phase N:` inside an inline code span produced a phantom
|
|
2009
|
+
// Not-Started row and made phases_total disagree with roadmap analyze.
|
|
2010
|
+
// phase-id-owner: uses the [.-] (dot-or-dash) separator variant, not the canonical dot-only token; a swap to PHASE_NUMBER_TOKEN_SOURCE would drop hyphenated phase-id matches.
|
|
2011
|
+
const headingPattern = /#{2,4}\s*(?:\[[^\]]{1,200}\]\s*)?Phase\s+([A-Za-z]?\d+[A-Z]?(?:[.-]\d+)*)(?:\s*\([^)\n]{0,200}\))?\s*:\s*([^\n]+)/gi;
|
|
1647
2012
|
let match;
|
|
1648
2013
|
while ((match = headingPattern.exec(roadmapContent)) !== null) {
|
|
2014
|
+
// #3185: the heading seed carried no sentinel filter, so a
|
|
2015
|
+
// `### Phase 999.1:` backlog heading produced a stats row even with no
|
|
2016
|
+
// directory on disk. Uses the canonical predicate (phase-id.cts), not a
|
|
2017
|
+
// local literal — the rule had five copies and three regex variants
|
|
2018
|
+
// before this phase, disagreeing about Phase 0.
|
|
2019
|
+
if (isSentinelPhaseId(match[1]))
|
|
2020
|
+
continue;
|
|
1649
2021
|
const key = normalizePhaseName(match[1]);
|
|
1650
2022
|
phasesByNumber.set(key, {
|
|
1651
2023
|
number: key,
|
|
@@ -1658,12 +2030,13 @@ function cmdStats(cwd, format, raw) {
|
|
|
1658
2030
|
}
|
|
1659
2031
|
catch { /* intentionally empty */ }
|
|
1660
2032
|
try {
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
2033
|
+
// #3185 (ADR-3180 Decision 1): route through the single owner. This
|
|
2034
|
+
// previously applied the milestone window but NOT a directory-level
|
|
2035
|
+
// sentinel filter — and getMilestonePhaseFilter degrades to a pass-all
|
|
2036
|
+
// predicate when its heading set is empty, at which point every directory
|
|
2037
|
+
// on disk passed, backlog included (#3167).
|
|
2038
|
+
const { value: dirs, scope } = listMilestonePhaseDirs(phasesDir, { cwd });
|
|
2039
|
+
phaseScope = scope;
|
|
1667
2040
|
for (const dir of dirs) {
|
|
1668
2041
|
// Use extractPhaseToken to correctly parse M-NN-style and code-prefixed dir names.
|
|
1669
2042
|
const phaseToken = extractPhaseToken(dir);
|
|
@@ -1671,9 +2044,11 @@ function cmdStats(cwd, format, raw) {
|
|
|
1671
2044
|
// phaseName is everything after the token (strip leading '-')
|
|
1672
2045
|
const afterToken = dir.slice(phaseToken ? phaseToken.length : 0).replace(/^-/, '');
|
|
1673
2046
|
const phaseName = afterToken ? afterToken.replace(/-/g, ' ') : '';
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
const
|
|
2047
|
+
// #3183: canonical plan/summary counts (root+nested, superseded-excluded,
|
|
2048
|
+
// canonical pairing) from the single owner.
|
|
2049
|
+
const phaseScan = scanPhasePlans(node_path_1.default.join(phasesDir, dir));
|
|
2050
|
+
const plans = phaseScan.planCount;
|
|
2051
|
+
const summaries = phaseScan.summaryCount;
|
|
1677
2052
|
totalPlans += plans;
|
|
1678
2053
|
totalSummaries += summaries;
|
|
1679
2054
|
const status = determinePhaseStatus(plans, summaries, node_path_1.default.join(phasesDir, dir), 'Not Started');
|
|
@@ -1696,8 +2071,12 @@ function cmdStats(cwd, format, raw) {
|
|
|
1696
2071
|
catch { /* intentionally empty */ }
|
|
1697
2072
|
const phases = [...phasesByNumber.values()].sort((a, b) => comparePhaseNum(a.number, b.number));
|
|
1698
2073
|
const completedPhases = phases.filter(p => p.status === 'Complete').length;
|
|
1699
|
-
|
|
1700
|
-
|
|
2074
|
+
// #3217 (ADR-3180 §7.6 rule 4): both percentages here are derived from the
|
|
2075
|
+
// same `phaseScope`-carrying directory enumeration above (Phase 3, #3222) —
|
|
2076
|
+
// withhold both when that scope is not COMPLETE, same rationale as
|
|
2077
|
+
// cmdProgressRender above. A real `0` under COMPLETE still renders.
|
|
2078
|
+
const planPercent = phaseScope === SCOPE.COMPLETE ? (0, phase_lifecycle_cjs_1.clampPercent)(totalSummaries, totalPlans) : null;
|
|
2079
|
+
const percent = phaseScope === SCOPE.COMPLETE ? (0, phase_lifecycle_cjs_1.clampPercent)(completedPhases, phases.length) : null;
|
|
1701
2080
|
// Requirements stats
|
|
1702
2081
|
let requirementsTotal = 0;
|
|
1703
2082
|
let requirementsComplete = 0;
|
|
@@ -1735,8 +2114,8 @@ function cmdStats(cwd, format, raw) {
|
|
|
1735
2114
|
}
|
|
1736
2115
|
}
|
|
1737
2116
|
const result = {
|
|
1738
|
-
milestone_version: milestone
|
|
1739
|
-
milestone_name: milestone
|
|
2117
|
+
milestone_version: milestone?.version ?? null,
|
|
2118
|
+
milestone_name: milestone?.name ?? null,
|
|
1740
2119
|
phases,
|
|
1741
2120
|
phases_completed: completedPhases,
|
|
1742
2121
|
phases_total: phases.length,
|
|
@@ -1749,14 +2128,18 @@ function cmdStats(cwd, format, raw) {
|
|
|
1749
2128
|
git_commits: gitCommits,
|
|
1750
2129
|
git_first_commit_date: gitFirstCommitDate,
|
|
1751
2130
|
last_activity: lastActivity,
|
|
2131
|
+
// #3185 (ADR-3180 Decision 2): the enumeration's scope, so a consumer
|
|
2132
|
+
// can tell a genuinely-empty milestone from one it could not scope.
|
|
2133
|
+
phase_scope: phaseScope,
|
|
1752
2134
|
};
|
|
1753
2135
|
if (format === 'table') {
|
|
1754
2136
|
const barWidth = 10;
|
|
1755
|
-
const filled = Math.round((percent / 100) * barWidth);
|
|
2137
|
+
const filled = percent === null ? 0 : Math.round((percent / 100) * barWidth);
|
|
1756
2138
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
1757
|
-
let out = `# ${milestone
|
|
1758
|
-
|
|
1759
|
-
|
|
2139
|
+
let out = `# ${milestone?.version ?? ''} ${milestone?.name ?? ''} — Statistics\n\n`;
|
|
2140
|
+
const percentSuffix = percent === null ? '' : ` (${percent}%)`;
|
|
2141
|
+
out += `**Progress:** [${bar}] ${completedPhases}/${phases.length} phases${percentSuffix}\n`;
|
|
2142
|
+
if (totalPlans > 0 && planPercent !== null) {
|
|
1760
2143
|
out += `**Plans:** ${totalSummaries}/${totalPlans} complete (${planPercent}%)\n`;
|
|
1761
2144
|
}
|
|
1762
2145
|
out += `**Phases:** ${completedPhases}/${phases.length} complete\n`;
|
|
@@ -1784,30 +2167,204 @@ function cmdStats(cwd, format, raw) {
|
|
|
1784
2167
|
}
|
|
1785
2168
|
}
|
|
1786
2169
|
/**
|
|
1787
|
-
* Check whether a commit should be allowed based on commit_docs
|
|
1788
|
-
*
|
|
1789
|
-
*
|
|
2170
|
+
* Check whether a commit should be allowed based on the `commit_docs`
|
|
2171
|
+
* precedence chain, INCLUDING any `phase_commit_docs.<phase-id>` override
|
|
2172
|
+
* (#3587/#3601). Rejects commits that stage `.planning/` files when the
|
|
2173
|
+
* resolved policy is false. Intended for use as a pre-commit hook guard —
|
|
2174
|
+
* see `commit-docs-guard enable` above.
|
|
2175
|
+
*
|
|
2176
|
+
* The phase is derived from the STAGED `.planning/` paths via the single-
|
|
2177
|
+
* owner `detectPhaseNumberFromFiles` (the same helper `cmdCommit` uses), and
|
|
2178
|
+
* the policy itself is resolved via the single-owner `resolveCommitDocsPolicy`
|
|
2179
|
+
* (also shared with `cmdCommit`) — this function never re-derives phase
|
|
2180
|
+
* detection or precedence, so it cannot diverge from `cmdCommit`'s decision
|
|
2181
|
+
* for the same staged tree (#3588 Part 1: this guard was previously
|
|
2182
|
+
* phase-blind, reading only project-level `commit_docs` and directly
|
|
2183
|
+
* contradicting `gsd-tools query commit`'s phase-aware resolution).
|
|
2184
|
+
*
|
|
2185
|
+
* Staged paths are read via `git diff --cached --name-only -z`, NUL-
|
|
2186
|
+
* delimited, rather than the LF-delimited default. Without `-z`, git
|
|
2187
|
+
* C-style-quotes (wraps in double quotes, octal-escapes) any path containing
|
|
2188
|
+
* a non-ASCII byte, a space-adjacent special character, or a literal quote —
|
|
2189
|
+
* `.planning/café.md` is reported as `".planning/caf\303\251.md"`, which
|
|
2190
|
+
* does not start with `.planning/`, so the old LF-based filter silently
|
|
2191
|
+
* missed it and allowed the commit (#3588 F2: a false negative in the harm
|
|
2192
|
+
* direction this guard exists to prevent). `-z` disables that quoting
|
|
2193
|
+
* entirely and NUL-terminates each path instead, so every staged path is
|
|
2194
|
+
* read as literal, unquoted bytes and no unquoting logic is needed.
|
|
1790
2195
|
*/
|
|
1791
2196
|
function cmdCheckCommit(cwd, raw) {
|
|
1792
2197
|
const config = loadConfig(cwd);
|
|
1793
|
-
|
|
1794
|
-
if (config['commit_docs'] !== false) {
|
|
1795
|
-
output({ allowed: true, reason: 'commit_docs_enabled' }, raw, 'allowed');
|
|
1796
|
-
return;
|
|
1797
|
-
}
|
|
1798
|
-
// commit_docs is false — check if any .planning/ files are staged
|
|
1799
|
-
const stagedResult = (0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only'], { cwd });
|
|
2198
|
+
const stagedResult = (0, shell_command_projection_cjs_1.execGit)(['diff', '--cached', '--name-only', '-z'], { cwd });
|
|
1800
2199
|
if (stagedResult.exitCode === 0) {
|
|
1801
|
-
const
|
|
2200
|
+
const files = stagedResult.stdout.split('\0').filter(Boolean);
|
|
2201
|
+
const planningFiles = files.filter(f => f.startsWith('.planning/'));
|
|
1802
2202
|
if (planningFiles.length > 0) {
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2203
|
+
const policy = resolveCommitDocsPolicy(config, detectPhaseNumberFromFiles(planningFiles), () => isGitIgnored(cwd, '.planning'));
|
|
2204
|
+
if (!policy.resolved) {
|
|
2205
|
+
error(`commit_docs is false but ${planningFiles.length} .planning/ file(s) are staged:\n` +
|
|
2206
|
+
planningFiles.map(f => ` ${f}`).join('\n') +
|
|
2207
|
+
`\n\nTo unstage: git reset HEAD ${planningFiles.join(' ')}`);
|
|
2208
|
+
return;
|
|
2209
|
+
}
|
|
2210
|
+
output({ allowed: true, reason: policy.source === 'phase' ? 'phase_commit_docs_true' : 'commit_docs_enabled' }, raw, 'allowed');
|
|
2211
|
+
return;
|
|
1806
2212
|
}
|
|
1807
2213
|
}
|
|
1808
|
-
// exitCode !== 0
|
|
2214
|
+
// exitCode !== 0 (no staged files / not a git repo) or no .planning/ files staged — allow
|
|
1809
2215
|
output({ allowed: true, reason: 'no_planning_files_staged' }, raw, 'allowed');
|
|
1810
2216
|
}
|
|
2217
|
+
// ─── commit-docs-guard: opt-in pre-commit hook (#3588) ─────────────────────
|
|
2218
|
+
/**
|
|
2219
|
+
* Stable sentinel line identifying a `.git/hooks/pre-commit` file as ours.
|
|
2220
|
+
* Detection is by PRESENCE of this line, not byte-equality (design "Identifying
|
|
2221
|
+
* 'our' hook") — a user who appends a line to a GSD-written hook must not make
|
|
2222
|
+
* it unrecognizable, and a hook lacking this line must never be overwritten or
|
|
2223
|
+
* deleted by `commit-docs-guard enable`/`disable`.
|
|
2224
|
+
*/
|
|
2225
|
+
const COMMIT_DOCS_GUARD_MARKER = '# gsd-core:commit-docs-guard';
|
|
2226
|
+
/**
|
|
2227
|
+
* Locate `gsd-core/workflows/_runtime-launcher.snippet.sh` — the SAME
|
|
2228
|
+
* gsd-tools-resolution chain every shipped workflow/agent bash block uses
|
|
2229
|
+
* (scripts/sync-runtime-launcher.cjs) — by walking up from this module's own
|
|
2230
|
+
* compiled location rather than a fixed literal `../..` join, so the walk
|
|
2231
|
+
* tolerates the module living at a different depth under an alternate build
|
|
2232
|
+
* or bundling layout (same defensive shape as
|
|
2233
|
+
* runtime-artifact-layout.cts#findInstallSourceRoot).
|
|
2234
|
+
*/
|
|
2235
|
+
function findRuntimeLauncherSnippet() {
|
|
2236
|
+
let dir = __dirname;
|
|
2237
|
+
for (let i = 0; i < 8; i++) {
|
|
2238
|
+
const candidate = node_path_1.default.join(dir, 'workflows', '_runtime-launcher.snippet.sh');
|
|
2239
|
+
if (node_fs_1.default.existsSync(candidate))
|
|
2240
|
+
return candidate;
|
|
2241
|
+
const parent = node_path_1.default.dirname(dir);
|
|
2242
|
+
if (parent === dir)
|
|
2243
|
+
break;
|
|
2244
|
+
dir = parent;
|
|
2245
|
+
}
|
|
2246
|
+
throw new Error(`commit-docs-guard: could not locate workflows/_runtime-launcher.snippet.sh from ${__dirname}`);
|
|
2247
|
+
}
|
|
2248
|
+
/**
|
|
2249
|
+
* Build the literal `.git/hooks/pre-commit` content `commit-docs-guard enable`
|
|
2250
|
+
* writes. Reuses the canonical gsd_run resolution preamble byte-for-byte
|
|
2251
|
+
* (read from disk, never hand-copied — see findRuntimeLauncherSnippet) so this
|
|
2252
|
+
* hook resolves `gsd-tools` exactly the way every other shipped workflow bash
|
|
2253
|
+
* block does, and cannot drift from it.
|
|
2254
|
+
*
|
|
2255
|
+
* LF-only (#3588 A2): the snippet file and every literal line here are joined
|
|
2256
|
+
* with `\n`; platformWriteSync additionally normalizes CRLF→LF on write, so a
|
|
2257
|
+
* CRLF shebang — which is not executable under Git Bash — cannot reach disk.
|
|
2258
|
+
*/
|
|
2259
|
+
function buildCommitDocsGuardHookScript() {
|
|
2260
|
+
const snippetPath = findRuntimeLauncherSnippet();
|
|
2261
|
+
const preamble = (0, text_lines_cjs_1.normalizeEol)(node_fs_1.default.readFileSync(snippetPath, 'utf8')).replace(/\n+$/, '');
|
|
2262
|
+
const lines = [
|
|
2263
|
+
'#!/usr/bin/env bash',
|
|
2264
|
+
COMMIT_DOCS_GUARD_MARKER,
|
|
2265
|
+
'# Refuses a commit that stages .planning/ files when `commit_docs` resolves',
|
|
2266
|
+
'# false (honoring any per-phase override). Installed by',
|
|
2267
|
+
'# `gsd-tools commit-docs-guard enable`; remove with',
|
|
2268
|
+
'# `gsd-tools commit-docs-guard disable`. See',
|
|
2269
|
+
'# docs/how-to/keep-planning-docs-private.md.',
|
|
2270
|
+
'set -euo pipefail',
|
|
2271
|
+
'',
|
|
2272
|
+
preamble,
|
|
2273
|
+
'',
|
|
2274
|
+
'gsd_run check-commit --raw',
|
|
2275
|
+
];
|
|
2276
|
+
return lines.join('\n') + '\n';
|
|
2277
|
+
}
|
|
2278
|
+
/**
|
|
2279
|
+
* Resolve the real git hooks directory for `cwd` via `git rev-parse
|
|
2280
|
+
* --git-path hooks` — never a literal `.git/hooks` join (#3588 row 8: a
|
|
2281
|
+
* linked worktree or submodule's `.git` is a FILE pointing elsewhere, and
|
|
2282
|
+
* this is the one git-native call that already resolves that correctly).
|
|
2283
|
+
*/
|
|
2284
|
+
function resolveCommitDocsGuardHooksDir(cwd) {
|
|
2285
|
+
const gitDirResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--git-dir'], { cwd });
|
|
2286
|
+
if (gitDirResult.exitCode !== 0) {
|
|
2287
|
+
return { ok: false, reason: 'not_a_git_repo' };
|
|
2288
|
+
}
|
|
2289
|
+
const hooksPathResult = (0, shell_command_projection_cjs_1.execGit)(['rev-parse', '--git-path', 'hooks'], { cwd });
|
|
2290
|
+
if (hooksPathResult.exitCode !== 0) {
|
|
2291
|
+
return { ok: false, reason: 'not_a_git_repo' };
|
|
2292
|
+
}
|
|
2293
|
+
const hooksDirRaw = hooksPathResult.stdout.trim();
|
|
2294
|
+
const hooksDir = node_path_1.default.isAbsolute(hooksDirRaw) ? hooksDirRaw : node_path_1.default.join(cwd, hooksDirRaw);
|
|
2295
|
+
return { ok: true, dir: hooksDir };
|
|
2296
|
+
}
|
|
2297
|
+
/** Marker presence, not byte-equality (#3588 row B10). */
|
|
2298
|
+
function isCommitDocsGuardHook(content) {
|
|
2299
|
+
return content.includes(COMMIT_DOCS_GUARD_MARKER);
|
|
2300
|
+
}
|
|
2301
|
+
/**
|
|
2302
|
+
* `gsd-tools commit-docs-guard enable` — write `.git/hooks/pre-commit`.
|
|
2303
|
+
* Behavior table (40-design.md rows 1-3, 8-9): refuses to clobber a foreign
|
|
2304
|
+
* hook, refuses when `core.hooksPath` would make our own write inert, and is
|
|
2305
|
+
* idempotent when already enabled.
|
|
2306
|
+
*/
|
|
2307
|
+
function cmdCommitDocsGuardEnable(cwd, raw) {
|
|
2308
|
+
const hooksDir = resolveCommitDocsGuardHooksDir(cwd);
|
|
2309
|
+
if (!hooksDir.ok || !hooksDir.dir) {
|
|
2310
|
+
error('not a git repository (or any of the parent directories)', ERROR_REASON.COMMIT_DOCS_GUARD_NOT_A_REPO);
|
|
2311
|
+
return;
|
|
2312
|
+
}
|
|
2313
|
+
// core.hooksPath already set: our .git/hooks/pre-commit would be inert —
|
|
2314
|
+
// git would never invoke it. Silently writing an ignored file is worse
|
|
2315
|
+
// than refusing (design row 9).
|
|
2316
|
+
const hooksPathConfig = (0, shell_command_projection_cjs_1.execGit)(['config', '--get', 'core.hooksPath'], { cwd });
|
|
2317
|
+
if (hooksPathConfig.exitCode === 0 && hooksPathConfig.stdout.trim() !== '') {
|
|
2318
|
+
const configuredPath = hooksPathConfig.stdout.trim();
|
|
2319
|
+
error(`core.hooksPath is set to "${configuredPath}"; a hook written to ${node_path_1.default.join(hooksDir.dir, 'pre-commit')} ` +
|
|
2320
|
+
`would never run. Wire commit-docs-guard into "${configuredPath}" manually, or unset core.hooksPath first.`, ERROR_REASON.COMMIT_DOCS_GUARD_HOOKS_PATH_SET);
|
|
2321
|
+
return;
|
|
2322
|
+
}
|
|
2323
|
+
const hookPath = node_path_1.default.join(hooksDir.dir, 'pre-commit');
|
|
2324
|
+
const existing = (0, shell_command_projection_cjs_1.platformReadSync)(hookPath);
|
|
2325
|
+
if (existing !== null) {
|
|
2326
|
+
if (!isCommitDocsGuardHook(existing)) {
|
|
2327
|
+
error(`refusing to overwrite an existing pre-commit hook at ${hookPath} that GSD did not write. ` +
|
|
2328
|
+
`Remove or rename it, or wire commit-docs-guard into it by hand.`, ERROR_REASON.COMMIT_DOCS_GUARD_FOREIGN_HOOK);
|
|
2329
|
+
}
|
|
2330
|
+
// Already ours — idempotent no-op (row 3). Leave any user edits intact;
|
|
2331
|
+
// just make sure the executable bit survived.
|
|
2332
|
+
try {
|
|
2333
|
+
node_fs_1.default.chmodSync(hookPath, 0o755);
|
|
2334
|
+
}
|
|
2335
|
+
catch { /* best-effort */ }
|
|
2336
|
+
output({ enabled: true, action: 'already_enabled', path: hookPath }, raw, 'already_enabled');
|
|
2337
|
+
return;
|
|
2338
|
+
}
|
|
2339
|
+
(0, shell_command_projection_cjs_1.platformWriteSync)(hookPath, buildCommitDocsGuardHookScript());
|
|
2340
|
+
node_fs_1.default.chmodSync(hookPath, 0o755);
|
|
2341
|
+
output({ enabled: true, action: 'written', path: hookPath }, raw, 'enabled');
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* `gsd-tools commit-docs-guard disable` — remove `.git/hooks/pre-commit`
|
|
2345
|
+
* ONLY when it is the hook we wrote (marker presence). Never deletes a
|
|
2346
|
+
* foreign hook (design row 5); a missing hook is a no-op success, not an
|
|
2347
|
+
* error (row 6).
|
|
2348
|
+
*/
|
|
2349
|
+
function cmdCommitDocsGuardDisable(cwd, raw) {
|
|
2350
|
+
const hooksDir = resolveCommitDocsGuardHooksDir(cwd);
|
|
2351
|
+
if (!hooksDir.ok || !hooksDir.dir) {
|
|
2352
|
+
error('not a git repository (or any of the parent directories)', ERROR_REASON.COMMIT_DOCS_GUARD_NOT_A_REPO);
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
const hookPath = node_path_1.default.join(hooksDir.dir, 'pre-commit');
|
|
2356
|
+
const existing = (0, shell_command_projection_cjs_1.platformReadSync)(hookPath);
|
|
2357
|
+
if (existing === null) {
|
|
2358
|
+
output({ disabled: true, action: 'noop', path: hookPath }, raw, 'noop');
|
|
2359
|
+
return;
|
|
2360
|
+
}
|
|
2361
|
+
if (!isCommitDocsGuardHook(existing)) {
|
|
2362
|
+
error(`refusing to remove the pre-commit hook at ${hookPath}: it does not carry the ` +
|
|
2363
|
+
`${COMMIT_DOCS_GUARD_MARKER} marker, so GSD did not write it.`, ERROR_REASON.COMMIT_DOCS_GUARD_FOREIGN_HOOK);
|
|
2364
|
+
}
|
|
2365
|
+
node_fs_1.default.unlinkSync(hookPath);
|
|
2366
|
+
output({ disabled: true, action: 'removed', path: hookPath }, raw, 'disabled');
|
|
2367
|
+
}
|
|
1811
2368
|
module.exports = {
|
|
1812
2369
|
groupFilesBySubrepo,
|
|
1813
2370
|
determinePhaseStatus,
|
|
@@ -1824,6 +2381,10 @@ module.exports = {
|
|
|
1824
2381
|
cmdResolveGranularity,
|
|
1825
2382
|
cmdResolveExecution,
|
|
1826
2383
|
cmdEffortSync,
|
|
2384
|
+
detectPhaseNumberFromFiles,
|
|
2385
|
+
resolvePhaseCommitDocsOverride,
|
|
2386
|
+
resolveCommitDocsPolicy,
|
|
2387
|
+
COMMIT_DOCS_SKIP_REASON,
|
|
1827
2388
|
cmdCommit,
|
|
1828
2389
|
cmdCommitToSubrepo,
|
|
1829
2390
|
cmdPrSubrepo,
|
|
@@ -1835,5 +2396,9 @@ module.exports = {
|
|
|
1835
2396
|
cmdScaffold,
|
|
1836
2397
|
cmdStats,
|
|
1837
2398
|
cmdCheckCommit,
|
|
2399
|
+
COMMIT_DOCS_GUARD_MARKER,
|
|
2400
|
+
buildCommitDocsGuardHookScript,
|
|
2401
|
+
cmdCommitDocsGuardEnable,
|
|
2402
|
+
cmdCommitDocsGuardDisable,
|
|
1838
2403
|
_wsParseRetryAfter,
|
|
1839
2404
|
};
|