@opengsd/gsd-core 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +2 -1
- package/.opencode/plugins/gsd-core.js +2 -3
- package/agents/gsd-code-fixer.compact.md +2 -2
- package/agents/gsd-code-fixer.md +4 -3
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/gsd-core/bin/gsd-tools.cjs +106 -7
- package/gsd-core/bin/lib/agent-install-check.cjs +29 -3
- package/gsd-core/bin/lib/args-terminator.cjs +114 -0
- package/gsd-core/bin/lib/broken-windows.cjs +59 -1
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +156 -74
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +17 -0
- package/gsd-core/bin/lib/config-loader.cjs +10 -0
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +34 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/gate-context-drift.cjs +8 -3
- package/gsd-core/bin/lib/gate-decision-coverage-plan.cjs +6 -1
- package/gsd-core/bin/lib/gate-decision-coverage-verify.cjs +6 -1
- package/gsd-core/bin/lib/gate-evaluation-scope.cjs +134 -13
- package/gsd-core/bin/lib/gate-tdd-review-checkpoint.cjs +63 -22
- package/gsd-core/bin/lib/gate-ui-safety.cjs +23 -5
- package/gsd-core/bin/lib/git-info-exclude.cjs +57 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +18 -3
- package/gsd-core/bin/lib/health-diagnostic-types.cjs +4 -0
- package/gsd-core/bin/lib/health-diagnostic.cjs +52 -1
- package/gsd-core/bin/lib/host-integration.cjs +13 -6
- package/gsd-core/bin/lib/init.cjs +65 -1
- package/gsd-core/bin/lib/install-engine.cjs +246 -32
- package/gsd-core/bin/lib/install-profiles.cjs +9 -4
- package/gsd-core/bin/lib/installer-migration-report.cjs +2 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +113 -0
- package/gsd-core/bin/lib/milestone-lock.cjs +8 -1
- package/gsd-core/bin/lib/milestone-release-map.cjs +244 -0
- package/gsd-core/bin/lib/milestone-release-tags.cjs +440 -0
- package/gsd-core/bin/lib/milestone.cjs +43 -1
- package/gsd-core/bin/lib/model-catalog.cjs +8 -0
- package/gsd-core/bin/lib/normalize-test-command.cjs +67 -0
- package/gsd-core/bin/lib/phase-id.cjs +21 -4
- package/gsd-core/bin/lib/phase-lifecycle.cjs +37 -4
- package/gsd-core/bin/lib/phase.cjs +115 -26
- package/gsd-core/bin/lib/planning-snapshot.cjs +3 -1
- package/gsd-core/bin/lib/planning-workspace.cjs +18 -0
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +58 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +35 -2
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/roadmap-parser.cjs +12 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +39 -2
- package/gsd-core/bin/lib/roadmap.cjs +33 -25
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +381 -14
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +8 -4
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +60 -16
- package/gsd-core/bin/lib/shell-command-projection.cjs +121 -16
- package/gsd-core/bin/lib/smart-entry.cjs +13 -0
- package/gsd-core/bin/lib/state-contract.cjs +29 -9
- package/gsd-core/bin/lib/state-document.cjs +12 -4
- package/gsd-core/bin/lib/state-transition.cjs +119 -24
- package/gsd-core/bin/lib/state.cjs +50 -28
- package/gsd-core/bin/lib/surface.cjs +9 -0
- package/gsd-core/bin/lib/task-command-router.cjs +5 -25
- package/gsd-core/bin/lib/task-content-resolution.cjs +46 -1
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +2 -0
- package/gsd-core/bin/lib/worktree-safety.cjs +22 -1
- package/gsd-core/bin/shared/config-defaults.manifest.json +1 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +1 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/planning-config.md +2 -0
- package/gsd-core/references/tdd.md +2 -2
- package/gsd-core/templates/DEBUG.md +16 -0
- package/gsd-core/templates/config.json +2 -1
- package/gsd-core/templates/verification-report.md +1 -1
- package/gsd-core/workflows/audit-fix.md +2 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +10 -1
- package/gsd-core/workflows/code-review.md +61 -7
- package/gsd-core/workflows/complete-milestone/steps/project-md-evolution-review.md +145 -0
- package/gsd-core/workflows/complete-milestone/steps/release-map.md +80 -0
- package/gsd-core/workflows/complete-milestone.md +37 -150
- package/gsd-core/workflows/diagnose-issues.md +29 -5
- package/gsd-core/workflows/discuss-phase/modes/power.md +1 -1
- package/gsd-core/workflows/discuss-phase-assumptions.md +33 -2
- package/gsd-core/workflows/discuss-phase-power.md +19 -2
- package/gsd-core/workflows/discuss-phase.md +6 -10
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +13 -7
- package/gsd-core/workflows/execute-phase/steps/post-merge-gate.md +7 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -1
- package/gsd-core/workflows/execute-phase/steps/verify-phase-goal.md +2 -2
- package/gsd-core/workflows/execute-phase/steps/wave-post-gate-hooks.md +11 -2
- package/gsd-core/workflows/execute-phase.md +9 -5
- package/gsd-core/workflows/health.md +2 -1
- package/gsd-core/workflows/import.md +1 -1
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +2 -2
- package/gsd-core/workflows/plan-phase.md +13 -13
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +1 -1
- package/gsd-core/workflows/quick/steps/worktree-pre-dispatch-commit.md +8 -1
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +2 -1
- package/gsd-core/workflows/quick.md +1 -1
- package/gsd-core/workflows/review.md +16 -13
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +3 -4
- package/gsd-core/workflows/spec-phase.md +3 -6
- package/gsd-core/workflows/verify-work.md +4 -3
- package/hooks/dist/gsd-agent-isolation-guard.js +11 -1
- package/hooks/dist/gsd-cursor-subagent-start.js +6 -2
- package/hooks/dist/gsd-path-export.js +95 -0
- package/hooks/dist/gsd-secret-read-guard.js +172 -39
- package/hooks/dist/gsd-statusline.js +35 -2
- package/hooks/dist/gsd-windsurf-pre-write.js +28 -1
- package/hooks/dist/gsd-workflow-guard.js +1 -1
- package/hooks/dist/gsd-worktree-path-guard.js +28 -1
- package/hooks/dist/lib/isolation-sentinel.js +44 -0
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-agent-isolation-guard.js +11 -1
- package/hooks/gsd-cursor-subagent-start.js +6 -2
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +35 -2
- package/hooks/gsd-windsurf-pre-write.js +28 -1
- package/hooks/gsd-workflow-guard.js +1 -1
- package/hooks/gsd-worktree-path-guard.js +28 -1
- package/hooks/hooks.json +1 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +13 -6
- package/pi/gsd.cjs +1047 -10
- package/pi/package-agents/gsd-advisor-researcher.md +113 -0
- package/pi/package-agents/gsd-ai-researcher.md +111 -0
- package/pi/package-agents/gsd-assumptions-analyzer.md +110 -0
- package/pi/package-agents/gsd-code-fixer.md +769 -0
- package/pi/package-agents/gsd-code-reviewer.md +402 -0
- package/pi/package-agents/gsd-codebase-mapper.md +850 -0
- package/pi/package-agents/gsd-debug-session-manager.md +416 -0
- package/pi/package-agents/gsd-debugger.md +1274 -0
- package/pi/package-agents/gsd-doc-classifier.md +269 -0
- package/pi/package-agents/gsd-doc-synthesizer.md +259 -0
- package/pi/package-agents/gsd-doc-verifier.md +212 -0
- package/pi/package-agents/gsd-doc-writer.md +626 -0
- package/pi/package-agents/gsd-dom-verifier.md +164 -0
- package/pi/package-agents/gsd-domain-researcher.md +144 -0
- package/pi/package-agents/gsd-eval-auditor.md +185 -0
- package/pi/package-agents/gsd-eval-planner.md +149 -0
- package/pi/package-agents/gsd-executor.md +894 -0
- package/pi/package-agents/gsd-framework-selector.md +159 -0
- package/pi/package-agents/gsd-integration-checker.md +476 -0
- package/pi/package-agents/gsd-intel-updater.md +338 -0
- package/pi/package-agents/gsd-mempalace-curator.md +49 -0
- package/pi/package-agents/gsd-nyquist-auditor.md +200 -0
- package/pi/package-agents/gsd-pattern-mapper.md +340 -0
- package/pi/package-agents/gsd-phase-researcher.md +897 -0
- package/pi/package-agents/gsd-plan-checker.md +1077 -0
- package/pi/package-agents/gsd-planner.md +1003 -0
- package/pi/package-agents/gsd-project-researcher.md +611 -0
- package/pi/package-agents/gsd-research-synthesizer.md +259 -0
- package/pi/package-agents/gsd-roadmapper.md +779 -0
- package/pi/package-agents/gsd-security-auditor.md +171 -0
- package/pi/package-agents/gsd-ui-auditor.md +581 -0
- package/pi/package-agents/gsd-ui-checker.md +417 -0
- package/pi/package-agents/gsd-ui-researcher.md +469 -0
- package/pi/package-agents/gsd-user-profiler.md +174 -0
- package/pi/package-agents/gsd-verifier.md +963 -0
- package/pi/package-agents/manifest.json +39 -0
- package/pi/package-skills/gsd-add-tests/SKILL.md +39 -0
- package/pi/package-skills/gsd-ai-integration-phase/SKILL.md +36 -0
- package/pi/package-skills/gsd-audit-fix/SKILL.md +34 -0
- package/pi/package-skills/gsd-audit-milestone/SKILL.md +40 -0
- package/pi/package-skills/gsd-audit-uat/SKILL.md +26 -0
- package/pi/package-skills/gsd-autonomous/SKILL.md +52 -0
- package/pi/package-skills/gsd-capture/SKILL.md +68 -0
- package/pi/package-skills/gsd-cleanup/SKILL.md +25 -0
- package/pi/package-skills/gsd-code-review/SKILL.md +71 -0
- package/pi/package-skills/gsd-complete-milestone/SKILL.md +147 -0
- package/pi/package-skills/gsd-config/SKILL.md +60 -0
- package/pi/package-skills/gsd-debug/SKILL.md +64 -0
- package/pi/package-skills/gsd-discuss-phase/SKILL.md +74 -0
- package/pi/package-skills/gsd-docs-update/SKILL.md +50 -0
- package/pi/package-skills/gsd-eval-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-execute-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-explore/SKILL.md +26 -0
- package/pi/package-skills/gsd-extract-learnings/SKILL.md +25 -0
- package/pi/package-skills/gsd-fast/SKILL.md +33 -0
- package/pi/package-skills/gsd-forensics/SKILL.md +59 -0
- package/pi/package-skills/gsd-graphify/SKILL.md +210 -0
- package/pi/package-skills/gsd-health/SKILL.md +36 -0
- package/pi/package-skills/gsd-help/SKILL.md +37 -0
- package/pi/package-skills/gsd-import/SKILL.md +46 -0
- package/pi/package-skills/gsd-inbox/SKILL.md +42 -0
- package/pi/package-skills/gsd-ingest-docs/SKILL.md +43 -0
- package/pi/package-skills/gsd-manager/SKILL.md +46 -0
- package/pi/package-skills/gsd-map-codebase/SKILL.md +85 -0
- package/pi/package-skills/gsd-mempalace-capture/SKILL.md +111 -0
- package/pi/package-skills/gsd-mempalace-recall/SKILL.md +111 -0
- package/pi/package-skills/gsd-milestone-summary/SKILL.md +54 -0
- package/pi/package-skills/gsd-mvp-phase/SKILL.md +45 -0
- package/pi/package-skills/gsd-new-milestone/SKILL.md +49 -0
- package/pi/package-skills/gsd-new-project/SKILL.md +49 -0
- package/pi/package-skills/gsd-next/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-context/SKILL.md +31 -0
- package/pi/package-skills/gsd-ns-ideate/SKILL.md +30 -0
- package/pi/package-skills/gsd-ns-manage/SKILL.md +42 -0
- package/pi/package-skills/gsd-ns-project/SKILL.md +34 -0
- package/pi/package-skills/gsd-ns-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ns-workflow/SKILL.md +44 -0
- package/pi/package-skills/gsd-onboard/SKILL.md +46 -0
- package/pi/package-skills/gsd-pause-work/SKILL.md +48 -0
- package/pi/package-skills/gsd-phase/SKILL.md +61 -0
- package/pi/package-skills/gsd-plan-phase/SKILL.md +62 -0
- package/pi/package-skills/gsd-plan-review-convergence/SKILL.md +63 -0
- package/pi/package-skills/gsd-pr-branch/SKILL.md +31 -0
- package/pi/package-skills/gsd-profile-user/SKILL.md +48 -0
- package/pi/package-skills/gsd-progress/SKILL.md +51 -0
- package/pi/package-skills/gsd-quick/SKILL.md +179 -0
- package/pi/package-skills/gsd-quick-batch/SKILL.md +114 -0
- package/pi/package-skills/gsd-resume-work/SKILL.md +31 -0
- package/pi/package-skills/gsd-review/SKILL.md +50 -0
- package/pi/package-skills/gsd-review-backlog/SKILL.md +65 -0
- package/pi/package-skills/gsd-secure-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-settings/SKILL.md +30 -0
- package/pi/package-skills/gsd-ship/SKILL.md +27 -0
- package/pi/package-skills/gsd-sketch/SKILL.md +56 -0
- package/pi/package-skills/gsd-spec-phase/SKILL.md +63 -0
- package/pi/package-skills/gsd-spike/SKILL.md +53 -0
- package/pi/package-skills/gsd-stats/SKILL.md +22 -0
- package/pi/package-skills/gsd-surface/SKILL.md +177 -0
- package/pi/package-skills/gsd-thread/SKILL.md +29 -0
- package/pi/package-skills/gsd-ui-phase/SKILL.md +35 -0
- package/pi/package-skills/gsd-ui-review/SKILL.md +35 -0
- package/pi/package-skills/gsd-ultraplan-phase/SKILL.md +38 -0
- package/pi/package-skills/gsd-undo/SKILL.md +38 -0
- package/pi/package-skills/gsd-update/SKILL.md +51 -0
- package/pi/package-skills/gsd-validate-phase/SKILL.md +37 -0
- package/pi/package-skills/gsd-verify-work/SKILL.md +41 -0
- package/pi/package-skills/gsd-workspace/SKILL.md +57 -0
- package/pi/package-skills/gsd-workstreams/SKILL.md +73 -0
- package/scripts/build-hooks.js +4 -0
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +20 -7
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +11 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +26 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/npm-audit-baseline.cjs +41 -7
- package/scripts/reviewed-or-maintainer-policy.cjs +181 -0
- package/scripts/select-docs-guards.cjs +1 -1
- package/scripts/self-test-report-policy.cjs +350 -0
- package/vscode/package.json +1 -1
- package/scripts/diagnose-validate-commit-in-tier.cjs +0 -270
- package/scripts/diagnose-validate-commit-phases.cjs +0 -348
- package/scripts/xtrace-phase-timing.cjs +0 -205
|
@@ -30,6 +30,7 @@ exports.applyPreserveWhenUnchanged = applyPreserveWhenUnchanged;
|
|
|
30
30
|
exports.applyStatePreservation = applyStatePreservation;
|
|
31
31
|
exports.transitionCore = transitionCore;
|
|
32
32
|
exports.sliceCurrentPositionSection = sliceCurrentPositionSection;
|
|
33
|
+
exports.isStatePastPhase = isStatePastPhase;
|
|
33
34
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
35
|
const frontmatter = require("./frontmatter.cjs");
|
|
35
36
|
const state_document_cjs_1 = require("./state-document.cjs");
|
|
@@ -37,6 +38,9 @@ const state_document_cjs_2 = require("./state-document.cjs");
|
|
|
37
38
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
38
39
|
const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
39
40
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
41
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports -- phase-id.cjs is an export= CommonJS module
|
|
42
|
+
const phaseIdMod = require("./phase-id.cjs");
|
|
43
|
+
const { comparePhaseNum } = phaseIdMod;
|
|
40
44
|
// #4129: the completion-ratio kernel for the resync-arm ratchet's percent
|
|
41
45
|
// (planning-scope's SCOPE — state-document's own dependency, no cycle here:
|
|
42
46
|
// state-document never imports this module).
|
|
@@ -643,18 +647,24 @@ function mergeResyncProgressRatchet(curatedRecord, derivedRecord) {
|
|
|
643
647
|
merged[key] = value;
|
|
644
648
|
}
|
|
645
649
|
}
|
|
646
|
-
|
|
647
|
-
const recomputed = (0, state_document_cjs_2.computeProgressPercent)((0, state_document_cjs_2.toFiniteNumber)(merged.completed_plans), (0, state_document_cjs_2.toFiniteNumber)(merged.total_plans), (0, state_document_cjs_2.toFiniteNumber)(merged.completed_phases), (0, state_document_cjs_2.toFiniteNumber)(merged.total_phases), planningScopeMod.SCOPE.COMPLETE);
|
|
648
|
-
// Same verbatim rule for percent: assign only when the recomputed value
|
|
649
|
-
// numerically differs, so a string-spelled derived percent ("67") is not
|
|
650
|
-
// churned into a number-spelled 67 (phantom-divergence noise, not a
|
|
651
|
-
// change).
|
|
652
|
-
if (recomputed !== null && recomputed !== (0, state_document_cjs_2.toFiniteNumber)(merged.percent)) {
|
|
653
|
-
merged.percent = recomputed;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
650
|
+
recomputeMergedProgressPercent(merged, derivedRecord);
|
|
656
651
|
return merged;
|
|
657
652
|
}
|
|
653
|
+
/**
|
|
654
|
+
* Make `merged.percent` the progress computation over the counters `merged` actually carries (#5619,
|
|
655
|
+
* #4129). Only when the derived side measured a percent: a derivation that withheld one (an incomplete
|
|
656
|
+
* scope, #1761/#3217) stays withheld, never invented. Same verbatim rule as the counters: assign only
|
|
657
|
+
* when the recomputed value numerically differs, so a string-spelled derived percent ("67") is not
|
|
658
|
+
* churned into a number-spelled 67 (phantom-divergence noise, not a change).
|
|
659
|
+
*/
|
|
660
|
+
function recomputeMergedProgressPercent(merged, derivedRecord) {
|
|
661
|
+
if ((0, state_document_cjs_2.toFiniteNumber)(derivedRecord.percent) === null)
|
|
662
|
+
return;
|
|
663
|
+
const recomputed = (0, state_document_cjs_2.computeProgressPercent)((0, state_document_cjs_2.toFiniteNumber)(merged.completed_plans), (0, state_document_cjs_2.toFiniteNumber)(merged.total_plans), (0, state_document_cjs_2.toFiniteNumber)(merged.completed_phases), (0, state_document_cjs_2.toFiniteNumber)(merged.total_phases), planningScopeMod.SCOPE.COMPLETE);
|
|
664
|
+
if (recomputed !== null && recomputed !== (0, state_document_cjs_2.toFiniteNumber)(merged.percent)) {
|
|
665
|
+
merged.percent = recomputed;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
658
668
|
/**
|
|
659
669
|
* Executor for `preservation: 'preserve-always'` (ADR-3408 §8.1). Only
|
|
660
670
|
* `progress` carries this policy today. Preserves #3242/#1446/#2440/#2969
|
|
@@ -719,18 +729,23 @@ function applyPreserveAlways(field, cls, ctx) {
|
|
|
719
729
|
// completed after the plan count grew) — ratcheting UP only, never
|
|
720
730
|
// deriving downward (preserves the #3242 curated-progress protection
|
|
721
731
|
// for cases unrelated to plan-count growth, e.g. a deleted SUMMARY).
|
|
722
|
-
// percent
|
|
723
|
-
// disk
|
|
724
|
-
//
|
|
732
|
+
// percent is recomputed over the counters actually written (#5619): the
|
|
733
|
+
// disk-derived percent is only coherent with the DERIVED counters, and a
|
|
734
|
+
// stale curated percent would be incoherent against the ratcheted-up
|
|
735
|
+
// completed counts (e.g. 54/54 at 93%).
|
|
725
736
|
const ratchetUpKeys = new Set(['completed_plans', 'completed_phases']);
|
|
726
737
|
for (const [key, value] of Object.entries(curatedRecord)) {
|
|
727
738
|
if (key === 'total_plans' || key === 'total_phases' || key === 'percent')
|
|
728
739
|
continue;
|
|
729
740
|
if (ratchetUpKeys.has(key)) {
|
|
730
|
-
|
|
731
|
-
|
|
741
|
+
// Frontmatter scalars arrive as STRINGS ("2", not 2): coerce through
|
|
742
|
+
// `toFiniteNumber`, never a `typeof === 'number'` test (#5619 — that
|
|
743
|
+
// test made both sides -Infinity and the ratchet unreachable).
|
|
744
|
+
const derivedNum = (0, state_document_cjs_2.toFiniteNumber)(derivedRecord[key]) ?? -Infinity;
|
|
745
|
+
const curatedNum = (0, state_document_cjs_2.toFiniteNumber)(value) ?? -Infinity;
|
|
732
746
|
// Take the derived value only when it ratchets up (strictly
|
|
733
|
-
// greater — #2969's `>` not `>=`); else keep curated
|
|
747
|
+
// greater — #2969's `>` not `>=`); else keep curated, with its
|
|
748
|
+
// spelling, on a tie too.
|
|
734
749
|
if (derivedNum > curatedNum)
|
|
735
750
|
continue;
|
|
736
751
|
merged[key] = value;
|
|
@@ -740,6 +755,7 @@ function applyPreserveAlways(field, cls, ctx) {
|
|
|
740
755
|
}
|
|
741
756
|
}
|
|
742
757
|
}
|
|
758
|
+
recomputeMergedProgressPercent(merged, derivedRecord);
|
|
743
759
|
next = merged;
|
|
744
760
|
}
|
|
745
761
|
else {
|
|
@@ -876,7 +892,36 @@ exports.STATE_MD_SECTIONS = {
|
|
|
876
892
|
* Phases 2–7 add cases for the remaining 9 intent kinds. A missing case is
|
|
877
893
|
* a compile-time error (the function would not return on that path).
|
|
878
894
|
*/
|
|
895
|
+
/**
|
|
896
|
+
* The transitions that rewrite single-line body fields (`Status:`, `Current focus:`, `Plan:` …) with
|
|
897
|
+
* plain line-anchored patterns. Those patterns match a fenced EXAMPLE of a field as readily as the
|
|
898
|
+
* live line (#5460), so these run over the document body with fenced blocks hidden.
|
|
899
|
+
* `milestone*`, `prune` and `rebuild` restructure sections or re-derive the document and are not
|
|
900
|
+
* field-by-field writers.
|
|
901
|
+
*/
|
|
902
|
+
const FENCE_MASKED_TRANSITIONS = new Set([
|
|
903
|
+
'beginPhase', 'advancePlan', 'completePhase', 'plannedPhase', 'patch', 'update', 'sync',
|
|
904
|
+
]);
|
|
879
905
|
function transitionCore(content, intent, deps) {
|
|
906
|
+
if (!FENCE_MASKED_TRANSITIONS.has(intent.kind))
|
|
907
|
+
return transitionCoreUnmasked(content, intent, deps);
|
|
908
|
+
// Only the body is masked: a fence opener inside a frontmatter block scalar must not swallow the
|
|
909
|
+
// closing `---` (the frontmatter is not where the live fields are written).
|
|
910
|
+
const body = stripFrontmatter(content);
|
|
911
|
+
if (!content.endsWith(body))
|
|
912
|
+
return transitionCoreUnmasked(content, intent, deps);
|
|
913
|
+
const prefix = content.slice(0, content.length - body.length);
|
|
914
|
+
const fenceMask = (0, markdown_sectionizer_cjs_1.maskFences)(body);
|
|
915
|
+
const result = transitionCoreUnmasked(prefix + fenceMask.masked, intent, deps);
|
|
916
|
+
const restored = fenceMask.restore(result.content);
|
|
917
|
+
// Cannot happen short of a writer bug (a transition dropped or duplicated a placeholder line); a
|
|
918
|
+
// refusal is the one outcome that cannot report a rewrite that did not land.
|
|
919
|
+
if (restored === null) {
|
|
920
|
+
throw new Error(`${intent.kind}: fenced code blocks could not be restored after the edit; refusing to write STATE.md`);
|
|
921
|
+
}
|
|
922
|
+
return { ...result, content: restored };
|
|
923
|
+
}
|
|
924
|
+
function transitionCoreUnmasked(content, intent, deps) {
|
|
880
925
|
switch (intent.kind) {
|
|
881
926
|
case 'beginPhase':
|
|
882
927
|
return beginPhaseCore(content, intent, deps);
|
|
@@ -1104,7 +1149,9 @@ function mutateCurrentPositionFirstTime(body, intent, today, updated) {
|
|
|
1104
1149
|
const span = locateCurrentPosition(body);
|
|
1105
1150
|
if (span === null)
|
|
1106
1151
|
return body;
|
|
1107
|
-
|
|
1152
|
+
// #5460: the section's fenced examples are hidden while the live lines are rewritten.
|
|
1153
|
+
const fenceMask = (0, markdown_sectionizer_cjs_1.maskFences)(body.slice(span.start, span.end));
|
|
1154
|
+
let sectionBody = fenceMask.masked;
|
|
1108
1155
|
// Phase line — inline first, then pipe-table fallback (#1257).
|
|
1109
1156
|
const phaseLabel = `${intent.phaseNumber}${intent.phaseName ? ` (${intent.phaseName})` : ''} — EXECUTING`;
|
|
1110
1157
|
if (/^Phase:/m.test(sectionBody)) {
|
|
@@ -1146,8 +1193,11 @@ function mutateCurrentPositionFirstTime(body, intent, today, updated) {
|
|
|
1146
1193
|
if (replaced !== null)
|
|
1147
1194
|
sectionBody = replaced;
|
|
1148
1195
|
}
|
|
1196
|
+
const restored = fenceMask.restore(sectionBody);
|
|
1197
|
+
if (restored === null)
|
|
1198
|
+
return body;
|
|
1149
1199
|
updated.push('Current Position');
|
|
1150
|
-
return body.slice(0, span.start) +
|
|
1200
|
+
return body.slice(0, span.start) + restored + body.slice(span.end);
|
|
1151
1201
|
}
|
|
1152
1202
|
/**
|
|
1153
1203
|
* Resume ## Current Position mutation: only update Last activity line
|
|
@@ -1158,7 +1208,8 @@ function mutateCurrentPositionResume(body, intent, today, updated) {
|
|
|
1158
1208
|
const span = locateCurrentPosition(body);
|
|
1159
1209
|
if (span === null)
|
|
1160
1210
|
return body;
|
|
1161
|
-
|
|
1211
|
+
const fenceMask = (0, markdown_sectionizer_cjs_1.maskFences)(body.slice(span.start, span.end));
|
|
1212
|
+
let sectionBody = fenceMask.masked;
|
|
1162
1213
|
const resumeActivity = `Last activity: ${today} — Phase ${intent.phaseNumber} execution resumed (wave continue)`;
|
|
1163
1214
|
if (/^Last activity:/im.test(sectionBody)) {
|
|
1164
1215
|
sectionBody = sectionBody.replace(/^Last activity:.*$/im, resumeActivity);
|
|
@@ -1173,7 +1224,10 @@ function mutateCurrentPositionResume(body, intent, today, updated) {
|
|
|
1173
1224
|
updated.push('Last activity (resume)');
|
|
1174
1225
|
}
|
|
1175
1226
|
}
|
|
1176
|
-
|
|
1227
|
+
const restored = fenceMask.restore(sectionBody);
|
|
1228
|
+
if (restored === null)
|
|
1229
|
+
return body;
|
|
1230
|
+
return body.slice(0, span.start) + restored + body.slice(span.end);
|
|
1177
1231
|
}
|
|
1178
1232
|
const PLAN_SHAPE_N = /^(\d+)(?:\s.*)?$/;
|
|
1179
1233
|
const PLAN_SHAPE_N_OF_M = /^(\d+)\s+of\s+(\d+)(?:\s.*)?$/;
|
|
@@ -1230,7 +1284,8 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc
|
|
|
1230
1284
|
const span = locateCurrentPosition(content);
|
|
1231
1285
|
if (span === null)
|
|
1232
1286
|
return content;
|
|
1233
|
-
|
|
1287
|
+
const fenceMask = (0, markdown_sectionizer_cjs_1.maskFences)(content.slice(span.start, span.end));
|
|
1288
|
+
let sectionBody = fenceMask.masked;
|
|
1234
1289
|
let mutated = false;
|
|
1235
1290
|
// #3395: Phase is always replaced when a caller passes it — system-derived,
|
|
1236
1291
|
// not executor-authored (same rule as Plan below). plannedPhaseCore uses
|
|
@@ -1343,7 +1398,10 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc
|
|
|
1343
1398
|
}
|
|
1344
1399
|
if (!mutated)
|
|
1345
1400
|
return content;
|
|
1346
|
-
|
|
1401
|
+
const restored = fenceMask.restore(sectionBody);
|
|
1402
|
+
if (restored === null)
|
|
1403
|
+
return content;
|
|
1404
|
+
return content.slice(0, span.start) + restored + content.slice(span.end);
|
|
1347
1405
|
}
|
|
1348
1406
|
// ----------------------------------------------------------------------------
|
|
1349
1407
|
// advancePlan — intent implementation (Phase 2)
|
|
@@ -1642,6 +1700,29 @@ function nextPhaseWording(disk) {
|
|
|
1642
1700
|
return { status: 'Ready to plan', currentPlan: 'Not started', stoppedAt: 'ready to plan' };
|
|
1643
1701
|
}
|
|
1644
1702
|
}
|
|
1703
|
+
/**
|
|
1704
|
+
* #5165: is STATE.md already positioned PAST phase `phaseNum`? True when its
|
|
1705
|
+
* current phase (frontmatter `current_phase`, else the body `Current Phase` /
|
|
1706
|
+
* `Phase` field's leading phase token) compares greater than `phaseNum`.
|
|
1707
|
+
* Re-completing a phase STATE has already moved past must not rewind the
|
|
1708
|
+
* position; the caller combines this with "the phase is already complete in
|
|
1709
|
+
* ROADMAP.md" so a first-time out-of-order completion (#2028) still
|
|
1710
|
+
* transitions. An unparseable or absent current phase is "not past".
|
|
1711
|
+
*/
|
|
1712
|
+
function isStatePastPhase(content, phaseNum, sourcePath) {
|
|
1713
|
+
const fm = extractFrontmatter(content, sourcePath);
|
|
1714
|
+
const fromFm = fm['current_phase'];
|
|
1715
|
+
let raw = typeof fromFm === 'string' || typeof fromFm === 'number' ? String(fromFm) : null;
|
|
1716
|
+
if (raw === null || raw.trim() === '') {
|
|
1717
|
+
const body = stripFrontmatter(content);
|
|
1718
|
+
raw = (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase') || (0, state_document_cjs_1.stateExtractField)(body, 'Phase') || null;
|
|
1719
|
+
}
|
|
1720
|
+
// `Phase 4` / `phase 4 of 9` name phase 4 as surely as `4 of 9` does.
|
|
1721
|
+
const m = raw === null ? null : /^\s*(?:phase\s+)?(\d+(?:\.\d+)*)/i.exec(raw);
|
|
1722
|
+
if (!m)
|
|
1723
|
+
return false;
|
|
1724
|
+
return comparePhaseNum(m[1], String(phaseNum)) > 0;
|
|
1725
|
+
}
|
|
1645
1726
|
/**
|
|
1646
1727
|
* Apply a `completePhase` transition to STATE.md content.
|
|
1647
1728
|
*
|
|
@@ -1669,6 +1750,12 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1669
1750
|
const updated = [];
|
|
1670
1751
|
const today = deps.clock.localToday();
|
|
1671
1752
|
const nextWording = nextPhaseWording(intent.isLastPhase ? null : intent.nextPhaseDisk ?? null);
|
|
1753
|
+
// #5165: the milestone the caller resolved (ROADMAP-bound identity first) wins;
|
|
1754
|
+
// else the milestone STATE asserts. It scopes the roadmap-derived counters
|
|
1755
|
+
// below to that milestone's Progress-table rows, the same milestone
|
|
1756
|
+
// `phase complete` scopes its frontmatter progress to.
|
|
1757
|
+
const currentMilestone = (0, phase_lifecycle_cjs_1.milestoneVersionOf)(intent.milestone)
|
|
1758
|
+
?? (0, phase_lifecycle_cjs_1.milestoneVersionOf)(extractFrontmatter(content, deps.sourcePath)['milestone']);
|
|
1672
1759
|
// Consult the field-classification table for the frontmatter keys this
|
|
1673
1760
|
// transition touches (same guard beginPhaseCore applies). A missing row is a
|
|
1674
1761
|
// substrate defect — fail loudly rather than silently re-encoding policy.
|
|
@@ -1812,7 +1899,7 @@ function completePhaseCore(content, intent, deps) {
|
|
|
1812
1899
|
let derivedTotalPhases = null;
|
|
1813
1900
|
const roadmapContent = deps.roadmapProvider ? deps.roadmapProvider() : null;
|
|
1814
1901
|
if (roadmapContent) {
|
|
1815
|
-
const derived = (0, phase_lifecycle_cjs_1.deriveProgressFromRoadmap)(roadmapContent);
|
|
1902
|
+
const derived = (0, phase_lifecycle_cjs_1.deriveProgressFromRoadmap)(roadmapContent, currentMilestone);
|
|
1816
1903
|
// #4901: completed counters only ratchet up, like the frontmatter twin
|
|
1817
1904
|
// (applyAuthoritativeProgressSubkeys, completedOnlyRaise). A derivation
|
|
1818
1905
|
// that lands BELOW the recorded counter read a narrower scope than the
|
|
@@ -1933,8 +2020,16 @@ function plannedPhaseCore(content, intent, deps) {
|
|
|
1933
2020
|
// Phase is system-derived, always replaced (Knuth invariant does not apply);
|
|
1934
2021
|
// Status / Last activity stay template-aware.
|
|
1935
2022
|
const beforePos = body;
|
|
2023
|
+
// #5369: with no --name the label reuses the curated frontmatter name (when it is a real one), so the
|
|
2024
|
+
// body line and the frontmatter agree on it (the #3834 reassert keeps that name in frontmatter). A
|
|
2025
|
+
// status fragment a pre-fix write left there is never copied back into the name slot.
|
|
2026
|
+
const curatedName = existingFm['current_phase_name'];
|
|
2027
|
+
const labelName = intent.phaseName
|
|
2028
|
+
?? (typeof curatedName === 'string' && curatedName.trim() !== '' && !phaseIdMod.isPlannedPhaseStatusPhrase(curatedName)
|
|
2029
|
+
? curatedName.trim()
|
|
2030
|
+
: null);
|
|
1936
2031
|
body = mutateCurrentPositionForAdvance(body, {
|
|
1937
|
-
phase: `${intent.phaseNumber}${
|
|
2032
|
+
phase: `${intent.phaseNumber}${labelName ? ` (${labelName})` : ''} — READY TO EXECUTE`,
|
|
1938
2033
|
status: 'Ready to execute',
|
|
1939
2034
|
lastActivity: `${today} — Phase ${intent.phaseNumber} planning complete`,
|
|
1940
2035
|
}, statusDefaults, lastActivityDefaults);
|
|
@@ -770,7 +770,7 @@ function cmdStateAdvancePlan(cwd, raw) {
|
|
|
770
770
|
// silently absorbed.
|
|
771
771
|
const body = stripFrontmatter(content);
|
|
772
772
|
const positionScope = matchCurrentPositionSection(body) ?? body;
|
|
773
|
-
const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
|
|
773
|
+
const positionPhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
|
|
774
774
|
// #4093: a Current Position section with ZERO labeled fields has no
|
|
775
775
|
// `Phase:` line either; frontmatter `current_phase` is the documented
|
|
776
776
|
// survivor of body drift (the reporter's document still carried it, and
|
|
@@ -1283,7 +1283,7 @@ function cmdStateAddDecision(cwd, options, raw) {
|
|
|
1283
1283
|
if (!phaseId) {
|
|
1284
1284
|
const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
1285
1285
|
const fm = extractFrontmatter(rawState, statePath);
|
|
1286
|
-
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
|
|
1286
|
+
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
|
|
1287
1287
|
}
|
|
1288
1288
|
const entry = `- [Phase ${phaseId || '?'}]: ${summaryText}${rationaleText ? ` — ${rationaleText}` : ''}`;
|
|
1289
1289
|
let _added = false;
|
|
@@ -1428,7 +1428,7 @@ function cmdStateAddRoadmapEvolution(cwd, options, raw) {
|
|
|
1428
1428
|
if (!phaseId) {
|
|
1429
1429
|
const rawState = node_fs_1.default.readFileSync(statePath, 'utf-8');
|
|
1430
1430
|
const fm = extractFrontmatter(rawState, statePath);
|
|
1431
|
-
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState)) ?? undefined;
|
|
1431
|
+
phaseId = resolveCurrentPhaseId(fm, stripFrontmatter(rawState), resolvePhaseIdConvention(cwd)) ?? undefined;
|
|
1432
1432
|
}
|
|
1433
1433
|
const entry = `- Phase ${phaseId || '?'} ${actionText}${afterText}: ${flatNote}${urgentText}`;
|
|
1434
1434
|
let duplicate = false;
|
|
@@ -1995,26 +1995,27 @@ function preferNewerLastActivity(existingFm, derivedFm) {
|
|
|
1995
1995
|
derivedFm['last_activity'] = exRaw;
|
|
1996
1996
|
}
|
|
1997
1997
|
}
|
|
1998
|
-
function parseProsePhaseField(value) {
|
|
1998
|
+
function parseProsePhaseField(value, convention) {
|
|
1999
1999
|
// #2121 Phase 2 (#2125): delegate to the canonical anchored parser so this
|
|
2000
2000
|
// module holds no independent prose phase-id regex. Drives #2111 — the
|
|
2001
2001
|
// anchored parser returns { phase: null } for a "Milestone vX.Y complete"
|
|
2002
2002
|
// body line (the old unanchored regex mined the minor-version digit, e.g.
|
|
2003
2003
|
// v0.5 -> "5"), so syncStateFrontmatter's #905 guard preserves the real
|
|
2004
2004
|
// current_phase instead of clobbering it.
|
|
2005
|
-
|
|
2005
|
+
// #5626: under `milestone-prefixed` the id is the whole `M-NN`; the convention comes from its owner.
|
|
2006
|
+
return parsePhaseFromProse(value, convention);
|
|
2006
2007
|
}
|
|
2007
|
-
function resolveStatePhase(fm, body) {
|
|
2008
|
+
function resolveStatePhase(fm, body, convention) {
|
|
2008
2009
|
const currentPositionScope = matchCurrentPositionSection(body) ?? body;
|
|
2009
2010
|
const frontmatterRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', null).value;
|
|
2010
2011
|
const legacyRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Current Phase').value;
|
|
2011
2012
|
const currentPositionRaw = (0, state_document_cjs_1.stateFieldValue)(fm, currentPositionScope, null, 'Phase').value;
|
|
2012
2013
|
const sources = {
|
|
2013
|
-
frontmatter: parseProsePhaseField(frontmatterRaw).phase,
|
|
2014
|
-
legacy_current_phase: parseProsePhaseField(legacyRaw).phase,
|
|
2015
|
-
current_position_phase: parseProsePhaseField(currentPositionRaw).phase,
|
|
2014
|
+
frontmatter: parseProsePhaseField(frontmatterRaw, convention).phase,
|
|
2015
|
+
legacy_current_phase: parseProsePhaseField(legacyRaw, convention).phase,
|
|
2016
|
+
current_position_phase: parseProsePhaseField(currentPositionRaw, convention).phase,
|
|
2016
2017
|
};
|
|
2017
|
-
const prosePhase = parseProsePhaseField(currentPositionRaw);
|
|
2018
|
+
const prosePhase = parseProsePhaseField(currentPositionRaw, convention);
|
|
2018
2019
|
return {
|
|
2019
2020
|
phase: sources.frontmatter ?? sources.legacy_current_phase ?? sources.current_position_phase,
|
|
2020
2021
|
name: (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase_name', null).value
|
|
@@ -2069,9 +2070,9 @@ function resolveStatePhase(fm, body) {
|
|
|
2069
2070
|
* Reconcile the two only by giving `resolveStatePhase` an explicit scope
|
|
2070
2071
|
* parameter — never by pointing this at it and dropping the difference.
|
|
2071
2072
|
*/
|
|
2072
|
-
function resolveCurrentPhaseId(fm, body) {
|
|
2073
|
+
function resolveCurrentPhaseId(fm, body, convention) {
|
|
2073
2074
|
const positionSection = sliceCurrentPositionSection(body);
|
|
2074
|
-
const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value).phase : null;
|
|
2075
|
+
const prosePhase = positionSection !== null ? parseProsePhaseField((0, state_document_cjs_1.stateFieldValue)(fm, positionSection, null, 'Phase').value, convention).phase : null;
|
|
2075
2076
|
return (0, state_document_cjs_1.stateFieldValue)(fm, body, 'current_phase', 'Current Phase').value ?? prosePhase;
|
|
2076
2077
|
}
|
|
2077
2078
|
function parseProseLastActivityField(value) {
|
|
@@ -2109,7 +2110,7 @@ function cmdStateSnapshot(cwd, raw) {
|
|
|
2109
2110
|
// so it is scopeable exactly like Stopped At under ## Session. Fall back to
|
|
2110
2111
|
// full-body search only when no ## Current Position section exists, so files
|
|
2111
2112
|
// with no section heading keep their current behaviour.
|
|
2112
|
-
const resolvedPhase = resolveStatePhase(fm, body);
|
|
2113
|
+
const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
|
|
2113
2114
|
const currentPhase = resolvedPhase.phase;
|
|
2114
2115
|
const currentPhaseName = resolvedPhase.name;
|
|
2115
2116
|
const totalPhasesRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_phases', 'Total Phases').value;
|
|
@@ -2531,7 +2532,7 @@ milestoneExplicitlyNull) {
|
|
|
2531
2532
|
// section overwrites current_phase here, and the next read surfaces it. Fall
|
|
2532
2533
|
// back to full-body search when no ## Current Position section exists.
|
|
2533
2534
|
const currentPositionScope = matchCurrentPositionSection(bodyContent) ?? bodyContent;
|
|
2534
|
-
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'));
|
|
2535
|
+
const prosePhase = parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(currentPositionScope, 'Phase'), cwd ? resolvePhaseIdConvention(cwd) : null);
|
|
2535
2536
|
const currentPhase = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase') ?? prosePhase.phase;
|
|
2536
2537
|
const currentPhaseName = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Phase Name') ?? prosePhase.name;
|
|
2537
2538
|
const currentPlan = (0, state_document_cjs_1.stateExtractField)(bodyContent, 'Current Plan');
|
|
@@ -2799,8 +2800,14 @@ milestoneExplicitlyNull) {
|
|
|
2799
2800
|
// — no second ROADMAP parser here. A ROADMAP without a canonical
|
|
2800
2801
|
// `## Progress` table resolves no table → floor inert (disk count
|
|
2801
2802
|
// stands), the owner's own answer to "what is countable".
|
|
2803
|
+
// #5165: the milestone-grouped `## Progress` table keeps every
|
|
2804
|
+
// milestone's rows and sits OUTSIDE any narrower roadmapScope, so the
|
|
2805
|
+
// floor names the milestone STATE asserts (assertedMilestoneVersion —
|
|
2806
|
+
// present on every resync path, unlike the optional storedMilestone
|
|
2807
|
+
// parameter) and the owner counts only that milestone's rows. No row
|
|
2808
|
+
// for it => nulls => the floor is inert and the disk count stands.
|
|
2802
2809
|
const roadmapCompletedPhases = roadmapScope !== null && safeToUseRoadmapCount && !diskCountsWithheld
|
|
2803
|
-
? deriveProgressFromRoadmap(roadmapScope).completedPhases
|
|
2810
|
+
? deriveProgressFromRoadmap(roadmapScope, assertedMilestoneVersion ?? storedMilestone).completedPhases
|
|
2804
2811
|
: null;
|
|
2805
2812
|
const flooredCompletedPhases = roadmapCompletedPhases !== null
|
|
2806
2813
|
? Math.max(diskCompletedPhases, roadmapCompletedPhases)
|
|
@@ -3227,6 +3234,8 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
|
|
|
3227
3234
|
for (const [subkey, value] of Object.entries(intent)) {
|
|
3228
3235
|
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
3229
3236
|
continue;
|
|
3237
|
+
if (subkey === 'percent')
|
|
3238
|
+
continue; // composed below, never applied raw (#5165)
|
|
3230
3239
|
if (!getFieldClassification(`progress.${subkey}`))
|
|
3231
3240
|
continue;
|
|
3232
3241
|
const isCompletedCounter = subkey === 'completed_phases' || subkey === 'completed_plans';
|
|
@@ -3244,10 +3253,18 @@ function applyAuthoritativeProgressSubkeys(fm, intent, opts) {
|
|
|
3244
3253
|
}
|
|
3245
3254
|
// percent: applied only when a completed counter moved (coherent with the
|
|
3246
3255
|
// counters that just landed) or when no percent exists to contradict.
|
|
3256
|
+
// #5165 / ADR-4629 §8.5: `computeProgressPercent` is the one composition
|
|
3257
|
+
// owner (min(plan fraction, phase fraction)) that every resyncing write uses.
|
|
3258
|
+
// When the block carries the counters it needs, the percent is composed by
|
|
3259
|
+
// that owner over the counters just landed, so a later resyncing write
|
|
3260
|
+
// cannot move what this transition wrote. The intent's own percent (a
|
|
3261
|
+
// phase-only fraction) is only the fallback for a block without plan counts.
|
|
3247
3262
|
const intentPercent = intent['percent'];
|
|
3248
3263
|
if (typeof intentPercent === 'number' && Number.isFinite(intentPercent) && (completedMoved || (0, state_document_cjs_1.toFiniteNumber)(base['percent']) === null)) {
|
|
3249
|
-
|
|
3250
|
-
|
|
3264
|
+
const composed = (0, state_document_cjs_1.computeProgressPercent)((0, state_document_cjs_1.toFiniteNumber)(base['completed_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['total_plans']), (0, state_document_cjs_1.toFiniteNumber)(base['completed_phases']), (0, state_document_cjs_1.toFiniteNumber)(base['total_phases']), SCOPE.COMPLETE);
|
|
3265
|
+
const nextPercent = composed !== null ? composed : intentPercent;
|
|
3266
|
+
if (!Object.is(base['percent'], nextPercent)) {
|
|
3267
|
+
base['percent'] = nextPercent;
|
|
3251
3268
|
changed = true;
|
|
3252
3269
|
}
|
|
3253
3270
|
}
|
|
@@ -3831,7 +3848,10 @@ function assertStatePreservationOptions(options, caller) {
|
|
|
3831
3848
|
throw err;
|
|
3832
3849
|
}
|
|
3833
3850
|
}
|
|
3834
|
-
function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options
|
|
3851
|
+
function applyPostSyncPreservation(originalContent, transformedContent, syncedContent, statePath, options,
|
|
3852
|
+
// #5626: the project's phase-id convention, resolved by the caller from its cwd (null: unset). Trailing and
|
|
3853
|
+
// optional so `options` stays the fifth parameter the options guard and its callers rely on.
|
|
3854
|
+
phaseIdConvention = null) {
|
|
3835
3855
|
assertStatePreservationOptions(options, 'applyPostSyncPreservation');
|
|
3836
3856
|
const { resync, authoritativeFm, deriveProgressKeys, divergedFields, explicitProgressField, preWriteState } = options;
|
|
3837
3857
|
// Bug #1230: delta heuristic — snapshot pre-transform body source fields so
|
|
@@ -3888,7 +3908,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3888
3908
|
const preCurrentPositionScope = matchCurrentPositionSection(preBody) ?? preBody;
|
|
3889
3909
|
const preBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Plan');
|
|
3890
3910
|
const preBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(preBody, 'Current Phase')
|
|
3891
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase')).phase;
|
|
3911
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(preCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
|
|
3892
3912
|
const preBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(preSessionScope, 'Paused At');
|
|
3893
3913
|
const preBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(preBody, 'Last Activity')
|
|
3894
3914
|
?? (0, state_document_cjs_1.stateExtractField)(preBody, 'Last activity');
|
|
@@ -3914,7 +3934,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
3914
3934
|
const postCurrentPositionScope = matchCurrentPositionSection(postBody) ?? postBody;
|
|
3915
3935
|
const postBodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Plan');
|
|
3916
3936
|
const postBodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(postBody, 'Current Phase')
|
|
3917
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase')).phase;
|
|
3937
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(postCurrentPositionScope, 'Phase'), phaseIdConvention).phase;
|
|
3918
3938
|
const postBodyPausedAt = (0, state_document_cjs_1.stateExtractField)(postSessionScope, 'Paused At');
|
|
3919
3939
|
const postBodyLastActivityRaw = (0, state_document_cjs_1.stateExtractField)(postBody, 'Last Activity')
|
|
3920
3940
|
?? (0, state_document_cjs_1.stateExtractField)(postBody, 'Last activity');
|
|
@@ -4131,7 +4151,7 @@ function applyPostSyncPreservation(originalContent, transformedContent, syncedCo
|
|
|
4131
4151
|
function syncAndPreserveStateMd(originalContent, transformedContent, statePath, cwd, options) {
|
|
4132
4152
|
assertStatePreservationOptions(options, 'syncAndPreserveStateMd');
|
|
4133
4153
|
const synced = syncStateFrontmatter(transformedContent, cwd, options.authoritativeFm);
|
|
4134
|
-
return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options);
|
|
4154
|
+
return applyPostSyncPreservation(originalContent, transformedContent, synced, statePath, options, cwd ? resolvePhaseIdConvention(cwd) : null);
|
|
4135
4155
|
}
|
|
4136
4156
|
/**
|
|
4137
4157
|
* Atomic read-modify-write for STATE.md.
|
|
@@ -4789,8 +4809,9 @@ function stateSectionScopeUnits(units, sectionTargets) {
|
|
|
4789
4809
|
* matching `readStateTarget`'s long-standing special case.
|
|
4790
4810
|
*/
|
|
4791
4811
|
function verifyStateWriteIntent(intent, preContent, postContent, statePath, bodyDeltas) {
|
|
4792
|
-
|
|
4793
|
-
const
|
|
4812
|
+
// #5165: the write lands scoped to what it changed (platformWriteSync reads the file it replaces).
|
|
4813
|
+
const pre = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, preContent, { prior: preContent }).content;
|
|
4814
|
+
const post = (0, shell_command_projection_cjs_1.normalizeContent)(statePath, postContent, { prior: preContent }).content;
|
|
4794
4815
|
const preFm = extractFrontmatter(pre, statePath);
|
|
4795
4816
|
const postFm = extractFrontmatter(post, statePath);
|
|
4796
4817
|
const preBody = stripFrontmatter(pre);
|
|
@@ -4989,7 +5010,7 @@ function applyStateWriteIntent(statePath, intent, transformFn, cwd, options = {}
|
|
|
4989
5010
|
return unwritten(verification);
|
|
4990
5011
|
(0, shell_command_projection_cjs_1.platformWriteSync)(statePath, candidate);
|
|
4991
5012
|
const reread = (0, shell_command_projection_cjs_1.platformReadSync)(statePath) || '';
|
|
4992
|
-
if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate).content) {
|
|
5013
|
+
if (reread !== (0, shell_command_projection_cjs_1.normalizeContent)(statePath, candidate, { prior: pre }).content) {
|
|
4993
5014
|
return Object.freeze({
|
|
4994
5015
|
ok: false,
|
|
4995
5016
|
reasons: Object.freeze([STATE_WRITE_INTENT_FAILURE.REREAD_MISMATCH]),
|
|
@@ -5055,7 +5076,7 @@ function cmdStateJson(cwd, raw) {
|
|
|
5055
5076
|
const bodyPausedAt = (0, state_document_cjs_1.stateExtractField)(sessionScope, 'Paused At');
|
|
5056
5077
|
const bodyPhaseSource = (0, state_document_cjs_1.stateExtractField)(body, 'Phase');
|
|
5057
5078
|
const bodyCurrentPhase = (0, state_document_cjs_1.stateExtractField)(body, 'Current Phase')
|
|
5058
|
-
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase')).phase;
|
|
5079
|
+
?? parseProsePhaseField((0, state_document_cjs_1.stateExtractField)(positionScope, 'Phase'), resolvePhaseIdConvention(cwd)).phase;
|
|
5059
5080
|
const bodyCurrentPlan = (0, state_document_cjs_1.stateExtractField)(body, 'Current Plan');
|
|
5060
5081
|
const bodyStatus = (0, state_document_cjs_1.stateExtractField)(body, 'Status');
|
|
5061
5082
|
// #3836: mirrors applyPostSyncPreservation's own derivation (state.cts
|
|
@@ -5527,7 +5548,8 @@ function cmdStatePlannedPhase(cwd, phaseNumber, phaseName, planCount, raw) {
|
|
|
5527
5548
|
if (!intent.phaseName) {
|
|
5528
5549
|
const preFm = extractFrontmatter(content, statePath);
|
|
5529
5550
|
const curatedName = preFm['current_phase_name'];
|
|
5530
|
-
|
|
5551
|
+
// #5369: a status fragment a pre-fix write left as the name is not curated; do not reassert it.
|
|
5552
|
+
if (typeof curatedName === 'string' && curatedName.trim().length > 0 && !phaseIdMod.isPlannedPhaseStatusPhrase(curatedName)) {
|
|
5531
5553
|
rmwOptions.authoritativeFm = { current_phase_name: curatedName };
|
|
5532
5554
|
}
|
|
5533
5555
|
}
|
|
@@ -5756,7 +5778,7 @@ function cmdStateValidate(cwd, raw, opts = {}) {
|
|
|
5756
5778
|
const { fm, body, scope: initialScope } = readStateFrontmatterScoped(content, statePath);
|
|
5757
5779
|
const scope = initialScope;
|
|
5758
5780
|
const status = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'status', 'Status').value || '';
|
|
5759
|
-
const resolvedPhase = resolveStatePhase(fm, body);
|
|
5781
|
+
const resolvedPhase = resolveStatePhase(fm, body, resolvePhaseIdConvention(cwd));
|
|
5760
5782
|
const currentPhase = resolvedPhase.phase;
|
|
5761
5783
|
const totalPlansRaw = (0, state_document_cjs_1.stateFieldValue)(fm, body, 'total_plans_in_phase', 'Total Plans in Phase').value;
|
|
5762
5784
|
const totalPlansInPhase = totalPlansRaw ? parseInt(totalPlansRaw, 10) : null;
|
|
@@ -6210,7 +6232,7 @@ function cmdStatePrune(cwd, options, raw) {
|
|
|
6210
6232
|
const body = stripFrontmatter(rawState);
|
|
6211
6233
|
// Prune needs an integer cutoff, so it parses the resolved id itself; a
|
|
6212
6234
|
// non-numeric or absent id lands on 0 and prune bails, as before.
|
|
6213
|
-
const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body)), 10) || 0;
|
|
6235
|
+
const currentPhase = parseInt(String(resolveCurrentPhaseId(fm, body, resolvePhaseIdConvention(cwd))), 10) || 0;
|
|
6214
6236
|
const cutoff = currentPhase - keepRecent;
|
|
6215
6237
|
if (cutoff <= 0) {
|
|
6216
6238
|
emit({ pruned: false, reason: `Only ${currentPhase} phases — nothing to prune with --keep-recent ${keepRecent}` }, raw, 'false');
|
|
@@ -36,6 +36,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
36
36
|
const node_path_1 = __importDefault(require("node:path"));
|
|
37
37
|
const shell_command_projection_cjs_1 = require("./shell-command-projection.cjs");
|
|
38
38
|
const runtime_name_policy_cjs_1 = require("./runtime-name-policy.cjs");
|
|
39
|
+
const args_terminator_cjs_1 = require("./args-terminator.cjs");
|
|
39
40
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
40
41
|
const installProfiles = require("./install-profiles.cjs");
|
|
41
42
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
@@ -322,6 +323,14 @@ function applySurface(runtimeConfigDir, layout, manifest, clusterMap, registry,
|
|
|
322
323
|
if (node_path_1.default.resolve(runtimeConfigDir) !== node_path_1.default.resolve(layout.configDir)) {
|
|
323
324
|
throw new TypeError('applySurface runtimeConfigDir must match layout.configDir');
|
|
324
325
|
}
|
|
326
|
+
// #5637: re-staged commands and skills keep the typed-text terminator nonce the install wrote,
|
|
327
|
+
// when it wrote one; this process is not an installer, so nothing armed it.
|
|
328
|
+
// A directory with no nonce (an install from before this change) is re-staged canonical, and a
|
|
329
|
+
// nonce armed earlier in this process for another directory is not carried over.
|
|
330
|
+
if ((0, args_terminator_cjs_1.readInstalledNonce)(layout.configDir) !== null)
|
|
331
|
+
(0, args_terminator_cjs_1.armArgsNonce)(layout.configDir, true);
|
|
332
|
+
else
|
|
333
|
+
(0, args_terminator_cjs_1.disarmArgsNonce)();
|
|
325
334
|
// #3712: the dest selection below prefers `kind.home` over layout.configDir and
|
|
326
335
|
// then hands it to the destructive _syncGsdDir, so surface apply is a third
|
|
327
336
|
// escape route into the developer's real home alongside install/uninstall.
|
|
@@ -20,10 +20,8 @@ const { output, error, ERROR_REASON } = ioMod;
|
|
|
20
20
|
const planDocumentMod = require("./plan-document.cjs");
|
|
21
21
|
const { parsePlanDocument } = planDocumentMod;
|
|
22
22
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
23
|
-
const capabilityLoaderMod = require("./capability-loader.cjs");
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
25
23
|
const taskContentResolutionMod = require("./task-content-resolution.cjs");
|
|
26
|
-
const { resolveTaskContent,
|
|
24
|
+
const { resolveTaskContent, loadResolverCapabilities, isResolverFailure, } = taskContentResolutionMod;
|
|
27
25
|
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
28
26
|
function isBehaviorAddingTaskContent(content) {
|
|
29
27
|
const tddTrue = /\btdd\s*=\s*["']true["']/i.test(content);
|
|
@@ -60,19 +58,6 @@ function isBehaviorAddingTaskContent(content) {
|
|
|
60
58
|
reason: isBehaviorAdding ? null : `Not behavior-adding: ${missing.join('; ')}`,
|
|
61
59
|
};
|
|
62
60
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Default (production) capability loader for `resolve-content`: the merged
|
|
65
|
-
* first-party + validated-installed-overlay registry (ADR-1244 D2), the
|
|
66
|
-
* established runtime read path for "installed capabilities including
|
|
67
|
-
* third-party" — as opposed to `capability-loader.cts`'s heavier build-time
|
|
68
|
-
* validation entry points or the static `capability-registry.cjs` alone
|
|
69
|
-
* (first-party only, would miss a third-party capability's
|
|
70
|
-
* `taskContentResolver` declaration entirely).
|
|
71
|
-
*/
|
|
72
|
-
function defaultLoadCapabilities(cwd) {
|
|
73
|
-
const registry = capabilityLoaderMod.loadRegistry({ includeInstalled: true, cwd });
|
|
74
|
-
return Object.values(registry.capabilities ?? {});
|
|
75
|
-
}
|
|
76
61
|
function parseResolveContentArgs(args) {
|
|
77
62
|
let plan = null;
|
|
78
63
|
let taskId = null;
|
|
@@ -93,12 +78,10 @@ function parseResolveContentArgs(args) {
|
|
|
93
78
|
* (ADR-3646 Decision 2). Resolves one task's content from the external
|
|
94
79
|
* tracker its `tracker-id` attribute names, via `task-content-resolution.cts`.
|
|
95
80
|
*
|
|
96
|
-
* HARD-HALT CONTRACT: a thrown
|
|
97
|
-
* / `ResolverTimeoutError` / `ResolverMalformedOutputError` from
|
|
81
|
+
* HARD-HALT CONTRACT: a thrown resolver failure (`isResolverFailure`) from
|
|
98
82
|
* `resolveTaskContent` is turned into this CLI's own non-zero exit via
|
|
99
83
|
* `error()` — never swallowed into a `{resolved: false}` JSON answer. Any
|
|
100
|
-
* other thrown error is not
|
|
101
|
-
* classes and is allowed to propagate uncaught.
|
|
84
|
+
* other thrown error is not a documented resolver-error class and is allowed to propagate uncaught.
|
|
102
85
|
*/
|
|
103
86
|
function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
104
87
|
const usage = 'Usage: task resolve-content --plan <path> --task-id <tracker-id> --raw';
|
|
@@ -127,7 +110,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
|
127
110
|
error(`No task with tracker-id '${taskId}' found in plan: ${plan}`, ERROR_REASON.USAGE);
|
|
128
111
|
return;
|
|
129
112
|
}
|
|
130
|
-
const loadCapabilities = deps.loadCapabilities ??
|
|
113
|
+
const loadCapabilities = deps.loadCapabilities ?? loadResolverCapabilities;
|
|
131
114
|
const capabilities = loadCapabilities(projectRoot);
|
|
132
115
|
const resolveFn = deps.resolveTaskContentFn ?? resolveTaskContent;
|
|
133
116
|
let result;
|
|
@@ -135,10 +118,7 @@ function routeResolveContent({ args, cwd, raw }, deps = {}) {
|
|
|
135
118
|
result = resolveFn({ trackerId: task.trackerId, capabilities });
|
|
136
119
|
}
|
|
137
120
|
catch (err) {
|
|
138
|
-
if (err
|
|
139
|
-
err instanceof ResolverFailedError ||
|
|
140
|
-
err instanceof ResolverTimeoutError ||
|
|
141
|
-
err instanceof ResolverMalformedOutputError) {
|
|
121
|
+
if (isResolverFailure(err)) {
|
|
142
122
|
error(err.message, ERROR_REASON.UNKNOWN);
|
|
143
123
|
return;
|
|
144
124
|
}
|