@opengsd/gsd-core 1.18.0 → 1.20.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 +7 -7
- package/agents/gsd-code-fixer.md +13 -7
- package/agents/gsd-code-reviewer.compact.md +49 -8
- package/agents/gsd-code-reviewer.md +53 -14
- package/agents/gsd-executor.md +8 -4
- package/agents/gsd-verifier.md +1 -1
- package/bin/install.js +422 -103
- package/commands/gsd/update.md +5 -2
- package/gsd-core/bin/gsd-tools.cjs +179 -30
- 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-lifecycle.cjs +1 -12
- package/gsd-core/bin/lib/capability-lock.cjs +13 -2
- package/gsd-core/bin/lib/capability-registry.cjs +376 -103
- package/gsd-core/bin/lib/capability-state.cjs +14 -8
- package/gsd-core/bin/lib/capability-validator.cjs +32 -0
- package/gsd-core/bin/lib/commands.cjs +27 -17
- package/gsd-core/bin/lib/config-loader.cjs +13 -3
- package/gsd-core/bin/lib/config.cjs +256 -58
- package/gsd-core/bin/lib/core-utils.cjs +45 -0
- package/gsd-core/bin/lib/decision-coverage-support.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +21 -3
- package/gsd-core/bin/lib/gate-api-coverage-verify-pre.cjs +2 -2
- package/gsd-core/bin/lib/gate-context-drift.cjs +9 -4
- 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-phase-context.cjs +7 -8
- package/gsd-core/bin/lib/gate-schema-drift.cjs +1 -1
- 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 +52 -0
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +31 -12
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +9 -7
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +3 -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 +245 -68
- 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/io.cjs +6 -0
- package/gsd-core/bin/lib/markdown-line-diff.cjs +147 -0
- package/gsd-core/bin/lib/markdown-sectionizer.cjs +147 -1
- package/gsd-core/bin/lib/markdown-table.cjs +23 -0
- package/gsd-core/bin/lib/mcp-server.cjs +8 -5
- 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 +61 -51
- 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-entry-grammar.cjs +119 -0
- package/gsd-core/bin/lib/phase-id-display.cjs +2 -0
- package/gsd-core/bin/lib/phase-id.cjs +629 -38
- package/gsd-core/bin/lib/phase-lifecycle.cjs +12 -44
- package/gsd-core/bin/lib/phase-locator.cjs +328 -19
- package/gsd-core/bin/lib/phase.cjs +3066 -653
- package/gsd-core/bin/lib/planning-document.cjs +26 -5
- package/gsd-core/bin/lib/planning-inspect.cjs +27 -35
- package/gsd-core/bin/lib/planning-snapshot.cjs +15 -8
- package/gsd-core/bin/lib/planning-workspace.cjs +21 -3
- package/gsd-core/bin/lib/pristine-baseline.cjs +11 -3
- package/gsd-core/bin/lib/read-only-git-env.cjs +79 -0
- package/gsd-core/bin/lib/refactor-trigger-command-router.cjs +2 -2
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +57 -2
- package/gsd-core/bin/lib/review-lane-invocation.cjs +125 -18
- package/gsd-core/bin/lib/review-lane-runner.cjs +33 -4
- package/gsd-core/bin/lib/reviewer-step-dispatch.cjs +11 -9
- package/gsd-core/bin/lib/roadmap-parser.cjs +735 -137
- package/gsd-core/bin/lib/roadmap-progress.cjs +136 -0
- package/gsd-core/bin/lib/roadmap-upgrade.cjs +47 -7
- package/gsd-core/bin/lib/roadmap.cjs +383 -123
- 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 +151 -19
- package/gsd-core/bin/lib/smart-entry.cjs +35 -11
- package/gsd-core/bin/lib/state-contract.cjs +39 -15
- package/gsd-core/bin/lib/state-document.cjs +344 -27
- package/gsd-core/bin/lib/state-transition.cjs +156 -32
- package/gsd-core/bin/lib/state.cjs +527 -186
- 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/template.cjs +2 -2
- package/gsd-core/bin/lib/validate.cjs +3 -2
- package/gsd-core/bin/lib/vendor/re2js.cjs +863 -577
- package/gsd-core/bin/lib/vendor/re2js.d.cts +284 -219
- package/gsd-core/bin/lib/verification.cjs +96 -9
- package/gsd-core/bin/lib/verify.cjs +4 -2
- package/gsd-core/bin/lib/workstream-inventory.cjs +26 -20
- 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/bin/verify-reapply-patches.cjs +411 -10
- package/gsd-core/references/cicd-review-patterns.md +45 -0
- package/gsd-core/references/git-integration.md +11 -0
- package/gsd-core/references/loop-hook-dispatch.md +13 -6
- package/gsd-core/references/necessity-review.md +48 -0
- 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-fix.md +46 -5
- package/gsd-core/workflows/code-review.md +64 -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 +10 -6
- package/gsd-core/workflows/execute-plan.md +1 -1
- 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/steps/stall-detection-helpers.md +13 -9
- package/gsd-core/workflows/plan-phase.md +14 -14
- package/gsd-core/workflows/pr-branch.md +2 -0
- 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/reapply-patches.md +104 -0
- package/gsd-core/workflows/review.md +70 -154
- package/gsd-core/workflows/section-manifest.json +5 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/settings-advanced.md +57 -9
- package/gsd-core/workflows/settings.md +1 -0
- package/gsd-core/workflows/ship.md +15 -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-graphify-update.sh +6 -0
- 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 +37 -3
- package/hooks/dist/gsd-windsurf-pre-write.js +29 -2
- package/hooks/dist/gsd-workflow-guard.js +3 -2
- package/hooks/dist/gsd-worktree-path-guard.js +29 -2
- package/hooks/dist/lib/git-probe.js +16 -0
- 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-graphify-update.sh +6 -0
- package/hooks/gsd-path-export.js +95 -0
- package/hooks/gsd-secret-read-guard.js +172 -39
- package/hooks/gsd-statusline.js +37 -3
- package/hooks/gsd-windsurf-pre-write.js +29 -2
- package/hooks/gsd-workflow-guard.js +3 -2
- package/hooks/gsd-worktree-path-guard.js +29 -2
- package/hooks/hooks.json +1 -0
- package/hooks/lib/git-probe.js +16 -0
- package/hooks/lib/isolation-sentinel.js +44 -0
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +15 -8
- 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 +774 -0
- package/pi/package-agents/gsd-code-reviewer.md +441 -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 +898 -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 +54 -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/ci-next-health.cjs +246 -23
- package/scripts/command-contract-helpers.cjs +1 -1
- package/scripts/docs-guard-registry.cjs +22 -7
- package/scripts/gen-features.cjs +14 -2
- package/scripts/gen-health-docs.cjs +13 -1
- package/scripts/gen-pi-package-skills.cjs +217 -0
- package/scripts/lib/drift-scan.cjs +27 -0
- package/scripts/lib/macos-conformance-tier.generated.cjs +17 -1
- package/scripts/lib/platform-conformance-tier.generated.cjs +37 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +3 -3
- package/scripts/lint-phase-enumeration-drift.cjs +8 -4
- package/scripts/lint-retired-runtime-name.cjs +5 -0
- package/scripts/lint-roadmap-progress-drift.cjs +421 -0
- package/scripts/mutation-matrix.cjs +23 -1
- 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/scripts/sync-next-version.cjs +38 -6
- package/skills/gsd-update/SKILL.md +5 -2
- 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
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* already answers:
|
|
9
9
|
* - `planning-workspace.cjs`'s `planningPaths` for every `.planning/` path
|
|
10
10
|
* (workstream-aware — never re-derived here).
|
|
11
|
-
* - `
|
|
11
|
+
* - `roadmap-progress.cjs`'s `locateRoadmapProgress` (#5761; was
|
|
12
|
+
* `phase-lifecycle`'s `locateProgressTable`) for
|
|
12
13
|
* "where is the Progress table" — the SAME table
|
|
13
14
|
* `deriveProgressFromRoadmap` counts from, so `phases[]` and
|
|
14
15
|
* `deriveProgressFromRoadmap`'s `totalPhases` can never disagree about
|
|
@@ -79,6 +80,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
|
|
|
79
80
|
// documented `state.cjs -> state-contract.cjs -> smart-entry.cjs -> state.cjs`
|
|
80
81
|
// require cycle the file header warns about.
|
|
81
82
|
const phase_status_cjs_1 = require("./phase-status.cjs");
|
|
83
|
+
const phase_entry_grammar_cjs_1 = require("./phase-entry-grammar.cjs");
|
|
82
84
|
// ─── Public wire vocabulary ────────────────────────────────────────────────
|
|
83
85
|
exports.STATE_CONTRACT_VERSION = '1.0.0';
|
|
84
86
|
exports.STATE_CONTRACT_FLAVOR = 'core';
|
|
@@ -114,7 +116,7 @@ function owners() {
|
|
|
114
116
|
/* eslint-disable @typescript-eslint/no-require-imports */
|
|
115
117
|
const planningWorkspaceMod = require('./planning-workspace.cjs');
|
|
116
118
|
const shellCommandProjectionMod = require('./shell-command-projection.cjs');
|
|
117
|
-
const
|
|
119
|
+
const roadmapProgressMod = require('./roadmap-progress.cjs');
|
|
118
120
|
const phaseIdMod = require('./phase-id.cjs');
|
|
119
121
|
const roadmapParserMod = require('./roadmap-parser.cjs');
|
|
120
122
|
const markdownSectionizerMod = require('./markdown-sectionizer.cjs');
|
|
@@ -126,9 +128,10 @@ function owners() {
|
|
|
126
128
|
planningPaths: planningWorkspaceMod.planningPaths,
|
|
127
129
|
platformReadSync: shellCommandProjectionMod.platformReadSync,
|
|
128
130
|
platformWriteSync: shellCommandProjectionMod.platformWriteSync,
|
|
129
|
-
|
|
131
|
+
locateRoadmapProgress: roadmapProgressMod.locateRoadmapProgress,
|
|
130
132
|
isSentinelPhaseId: phaseIdMod.isSentinelPhaseId,
|
|
131
133
|
parsePhaseFromProse: phaseIdMod.parsePhaseFromProse,
|
|
134
|
+
resolvePhaseIdConvention: planningWorkspaceMod.resolvePhaseIdConvention,
|
|
132
135
|
getMilestoneInfo: roadmapParserMod.getMilestoneInfo,
|
|
133
136
|
collectSection: markdownSectionizerMod.collectSection,
|
|
134
137
|
stateFieldValue: stateDocumentMod.stateFieldValue,
|
|
@@ -154,16 +157,31 @@ function buildMilestone(cwd) {
|
|
|
154
157
|
}
|
|
155
158
|
// ─── phases: Progress-table (primary) enumerator ───────────────────────────
|
|
156
159
|
const PHASE_CELL_RE = /^(\d+(?:\.\d+)*)\s*[.:)–—-]?\s*(.*)$/;
|
|
157
|
-
|
|
160
|
+
// #5626: under `milestone-prefixed` the phase id is the whole `M-NN` (`3-01`), so the number capture takes
|
|
161
|
+
// one optional `-NN` segment. Chosen by the convention value alone: every other convention keeps the
|
|
162
|
+
// dotted-only capture above, and the `\d+-\d+` shape stays an ordinary leading integer there.
|
|
163
|
+
const MILESTONE_PREFIXED = 'milestone-prefixed';
|
|
164
|
+
const PHASE_CELL_MILESTONE_RE = /^(\d+(?:-\d+)?(?:\.\d+)*)\s*[.:)–—-]?\s*(.*)$/;
|
|
165
|
+
/** The project's `phase_id_convention` (null when unset or unreadable), through its one owner. */
|
|
166
|
+
function phaseIdConventionOf(cwd) {
|
|
167
|
+
try {
|
|
168
|
+
return owners().resolvePhaseIdConvention(cwd);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function phasesFromProgressTable(table, convention) {
|
|
158
175
|
const { isSentinelPhaseId } = owners();
|
|
159
176
|
const phases = [];
|
|
177
|
+
const cellRe = convention === MILESTONE_PREFIXED ? PHASE_CELL_MILESTONE_RE : PHASE_CELL_RE;
|
|
160
178
|
for (const row of table.rows) {
|
|
161
179
|
const cell = (row['Phase'] ?? '').trim();
|
|
162
180
|
if (!/^\d/.test(cell))
|
|
163
181
|
continue; // stray note row, no leading digit
|
|
164
182
|
if (isSentinelPhaseId(cell))
|
|
165
183
|
continue; // phase 0 / 999.x sentinels
|
|
166
|
-
const m =
|
|
184
|
+
const m = cellRe.exec(cell);
|
|
167
185
|
const number = m ? m[1] : cell;
|
|
168
186
|
const name = m && m[2].trim().length > 0 ? m[2].trim() : null;
|
|
169
187
|
phases.push({ number, name, status: (0, phase_status_cjs_1.parseRoadmapStatusCell)(row['Status']) });
|
|
@@ -172,7 +190,11 @@ function phasesFromProgressTable(table) {
|
|
|
172
190
|
}
|
|
173
191
|
// ─── phases: `## Phases` checkbox fallback ─────────────────────────────────
|
|
174
192
|
// `- [x] **Phase 1: Foundation**` / `- [ ] **Phase 2.1: Hardening**`.
|
|
175
|
-
|
|
193
|
+
// The entry prefix (marker, box, bold token) is the phase-entry grammar's (#5764): capture
|
|
194
|
+
// group 1 is the box character, 2 the number, 3 the name.
|
|
195
|
+
const PHASE_BULLET_RE = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase[ \\t]+(\\d+(?:\\.\\d+)*)[ \\t]*:?[ \\t]*([^*\\n]*)${phase_entry_grammar_cjs_1.PHASE_ENTRY_BOLD_CLOSE_SRC}`, 'g', { state: 'captured', bold: 'required' });
|
|
196
|
+
// #5626: the same bullet under `milestone-prefixed`: the number capture takes one optional `-NN` segment.
|
|
197
|
+
const PHASE_BULLET_MILESTONE_RE = (0, phase_entry_grammar_cjs_1.phaseEntryRegExp)(`Phase[ \\t]+(\\d+(?:-\\d+)?(?:\\.\\d+)*)[ \\t]*:?[ \\t]*([^*\\n]*)${phase_entry_grammar_cjs_1.PHASE_ENTRY_BOLD_CLOSE_SRC}`, 'g', { state: 'captured', bold: 'required' });
|
|
176
198
|
/**
|
|
177
199
|
* STATE.md's current phase, via the same frontmatter-then-body chain
|
|
178
200
|
* `cmdStateCompletePhase` uses (`stateFieldValue` owns the #1760 fallback
|
|
@@ -180,7 +202,7 @@ const PHASE_BULLET_RE = /^[ \t]*[-*][ \t]+\[([ xX])\][ \t]+\*\*Phase[ \t]+(\d+(?
|
|
|
180
202
|
* phase as unknown so every unchecked bullet is PENDING rather than
|
|
181
203
|
* IN_PROGRESS by a guess.
|
|
182
204
|
*/
|
|
183
|
-
function currentPhaseFromState(statePath) {
|
|
205
|
+
function currentPhaseFromState(statePath, convention) {
|
|
184
206
|
const { platformReadSync, extractFrontmatter, stripFrontmatter, stateFieldValue, parsePhaseFromProse } = owners();
|
|
185
207
|
try {
|
|
186
208
|
const raw = platformReadSync(statePath);
|
|
@@ -191,7 +213,7 @@ function currentPhaseFromState(statePath) {
|
|
|
191
213
|
const { value } = stateFieldValue(fm, body, 'current_phase', 'Current Phase');
|
|
192
214
|
if (!value)
|
|
193
215
|
return null;
|
|
194
|
-
return parsePhaseFromProse(value).phase;
|
|
216
|
+
return parsePhaseFromProse(value, convention).phase;
|
|
195
217
|
}
|
|
196
218
|
catch {
|
|
197
219
|
return null;
|
|
@@ -207,15 +229,16 @@ function currentPhaseFromState(statePath) {
|
|
|
207
229
|
* `**Phase N` anchor inside `PHASE_BULLET_RE` is the second layer of that
|
|
208
230
|
* defence — a plan bullet's text never starts with `**Phase`.
|
|
209
231
|
*/
|
|
210
|
-
function phasesFromChecklist(roadmapContent, cwd, statePath) {
|
|
232
|
+
function phasesFromChecklist(roadmapContent, cwd, statePath, convention) {
|
|
211
233
|
const { collectSection, isSentinelPhaseId } = owners();
|
|
212
234
|
const section = collectSection(roadmapContent, (h) => /^phases$/i.test(h.text.trim()));
|
|
213
235
|
if (!section)
|
|
214
236
|
return [];
|
|
215
|
-
const currentPhase = currentPhaseFromState(statePath);
|
|
237
|
+
const currentPhase = currentPhaseFromState(statePath, convention);
|
|
216
238
|
const phases = [];
|
|
217
239
|
let match;
|
|
218
|
-
const
|
|
240
|
+
const bulletRe = convention === MILESTONE_PREFIXED ? PHASE_BULLET_MILESTONE_RE : PHASE_BULLET_RE;
|
|
241
|
+
const pattern = new RegExp(bulletRe.source, bulletRe.flags);
|
|
219
242
|
while ((match = pattern.exec(section.body)) !== null) {
|
|
220
243
|
const checked = match[1] === 'x' || match[1] === 'X';
|
|
221
244
|
const number = match[2];
|
|
@@ -230,17 +253,18 @@ function phasesFromChecklist(roadmapContent, cwd, statePath) {
|
|
|
230
253
|
return phases;
|
|
231
254
|
}
|
|
232
255
|
function buildPhases(cwd, paths) {
|
|
233
|
-
const { platformReadSync,
|
|
256
|
+
const { platformReadSync, locateRoadmapProgress } = owners();
|
|
234
257
|
try {
|
|
235
258
|
let content = platformReadSync(paths.roadmap);
|
|
236
259
|
if (content === null)
|
|
237
260
|
return [];
|
|
238
261
|
if (content.charCodeAt(0) === 0xfeff)
|
|
239
262
|
content = content.slice(1); // strip leading BOM
|
|
240
|
-
const
|
|
263
|
+
const convention = phaseIdConventionOf(cwd);
|
|
264
|
+
const { table } = locateRoadmapProgress(content);
|
|
241
265
|
if (table)
|
|
242
|
-
return phasesFromProgressTable(table);
|
|
243
|
-
return phasesFromChecklist(content, cwd, paths.state);
|
|
266
|
+
return phasesFromProgressTable(table, convention);
|
|
267
|
+
return phasesFromChecklist(content, cwd, paths.state, convention);
|
|
244
268
|
}
|
|
245
269
|
catch {
|
|
246
270
|
return [];
|
|
@@ -8,19 +8,28 @@
|
|
|
8
8
|
* from the prior hand-written .cjs; only types are added.
|
|
9
9
|
*/
|
|
10
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
-
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = exports.STATUS_ANCHORED_PATTERNS = exports.STATUS_EXACT_TOKENS = void 0;
|
|
11
|
+
exports.KNOWN_STATUS_PATTERNS = exports.KNOWN_TEMPLATE_DEFAULTS = exports.STATUS_ANCHORED_PATTERNS = exports.STATUS_EXACT_TOKENS = exports.FIELD_LINE_BREAK_RE = void 0;
|
|
12
12
|
exports.toFiniteNumber = toFiniteNumber;
|
|
13
13
|
exports.isUnfilledFieldValue = isUnfilledFieldValue;
|
|
14
14
|
exports.leadingCalendarDate = leadingCalendarDate;
|
|
15
15
|
exports.isRealCalendarDate = isRealCalendarDate;
|
|
16
16
|
exports.stateFieldContinuation = stateFieldContinuation;
|
|
17
|
+
exports.editedLineContinuation = editedLineContinuation;
|
|
18
|
+
exports.readableEditedBoldLabel = readableEditedBoldLabel;
|
|
19
|
+
exports.markdownFencedBlocks = markdownFencedBlocks;
|
|
20
|
+
exports.maskFencedLines = maskFencedLines;
|
|
21
|
+
exports.writeOutsideFences = writeOutsideFences;
|
|
22
|
+
exports.readableBoldLabels = readableBoldLabels;
|
|
17
23
|
exports.fieldContinuationEnd = fieldContinuationEnd;
|
|
24
|
+
exports.insertMissingSessionFields = insertMissingSessionFields;
|
|
18
25
|
exports.stateExtractField = stateExtractField;
|
|
19
26
|
exports.stateFieldValue = stateFieldValue;
|
|
20
27
|
exports.stateCurrentPositionSlice = stateCurrentPositionSlice;
|
|
21
28
|
exports.stateReplaceField = stateReplaceField;
|
|
22
29
|
exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
|
|
23
30
|
exports.stateReplaceFieldInSession = stateReplaceFieldInSession;
|
|
31
|
+
exports.isSessionHeading = isSessionHeading;
|
|
32
|
+
exports.isSessionContinuityHeading = isSessionContinuityHeading;
|
|
24
33
|
exports.normalizeStateStatus = normalizeStateStatus;
|
|
25
34
|
exports.computeProgressPercent = computeProgressPercent;
|
|
26
35
|
exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
|
|
@@ -32,6 +41,9 @@ const phase_lifecycle_cjs_1 = require("./phase-lifecycle.cjs");
|
|
|
32
41
|
const markdown_sectionizer_cjs_1 = require("./markdown-sectionizer.cjs");
|
|
33
42
|
const pattern_cjs_1 = require("./pattern.cjs");
|
|
34
43
|
const planning_document_cjs_1 = require("./planning-document.cjs");
|
|
44
|
+
Object.defineProperty(exports, "FIELD_LINE_BREAK_RE", { enumerable: true, get: function () { return planning_document_cjs_1.FIELD_LINE_BREAK_RE; } });
|
|
45
|
+
const frontmatter_fence_cjs_1 = require("./frontmatter-fence.cjs");
|
|
46
|
+
const state_md_schema_cjs_1 = require("./state-md-schema.cjs");
|
|
35
47
|
// eslint-disable-next-line @typescript-eslint/no-require-imports -- planning-scope.cjs is an export= CommonJS module
|
|
36
48
|
const planningScopeMod = require("./planning-scope.cjs");
|
|
37
49
|
const { SCOPE } = planningScopeMod;
|
|
@@ -324,8 +336,19 @@ function isRealCalendarDate(year, month, day) {
|
|
|
324
336
|
* produce false S009 fires on well-formed content: an indented code block, an
|
|
325
337
|
* HTML block, and a setext underline (`===`, which the `[-*_]{3,}` rule does not
|
|
326
338
|
* cover — it only knows `-`, `*` and `_`).
|
|
339
|
+
*
|
|
340
|
+
* #4998: a fence, an ATX heading (an empty `##` included), a blockquote or a
|
|
341
|
+
* thematic break opens with up to three leading spaces, as Markdown allows —
|
|
342
|
+
* such a line directly below a field was read as its continuation, so
|
|
343
|
+
* record-session skipped a field that does not wrap. Four spaces or a tab is
|
|
344
|
+
* the indented-code arm. Other indented structure (an indented bullet) stays a
|
|
345
|
+
* continuation on purpose: it fails toward skipping the field, never toward
|
|
346
|
+
* splitting a note from the field it annotates.
|
|
327
347
|
*/
|
|
328
|
-
|
|
348
|
+
// The backtick fence is spelled `\x60{3}`: a literal run of backticks in a
|
|
349
|
+
// regex literal opens a template string for the source scanners in
|
|
350
|
+
// scripts/ (lint-state-field-drift), which then misread the rest of the file.
|
|
351
|
+
const MD_STRUCTURE_LINE_RE = /^(?:\||>| {0,3}(?:#{1,6}(?:\s|$)|\x60{3}|~~~|>|[-*_]{3,}\s*$)|[-*_]{3,}\s*$|=+\s*$|[-*+]\s|\d+[.)]\s|\[[^\]]+\]:|<|(?: {4}|\t))/;
|
|
329
352
|
/**
|
|
330
353
|
* A setext heading's underline — `===` or `---` on its own line. The line ABOVE
|
|
331
354
|
* one of these is a heading TITLE, which is indistinguishable from prose on its
|
|
@@ -334,7 +357,36 @@ const MD_STRUCTURE_LINE_RE = /^(?:#{1,6}\s|\||>|```|~~~|[-*_]{3,}\s*$|=+\s*$|[-*
|
|
|
334
357
|
* continuation text (#3696 review round 2).
|
|
335
358
|
*/
|
|
336
359
|
const SETEXT_UNDERLINE_RE = /^(?:=+|-+)\s*$/;
|
|
337
|
-
|
|
360
|
+
// #4998: a sibling field may be indented as the field above it may be
|
|
361
|
+
// (`stateExtractField`'s bold rung tolerates leading whitespace); up to three
|
|
362
|
+
// spaces, as for MD_STRUCTURE_LINE_RE — four is indented code either way. The
|
|
363
|
+
// label admits `.`, `(`, `)` and `/` as well (`Phase 3.1 status:`,
|
|
364
|
+
// `Next (planned):`): a sibling read as a continuation made record-session
|
|
365
|
+
// refuse a field that does not wrap.
|
|
366
|
+
const STATE_SIBLING_FIELD_LINE_RE = /^ {0,3}\*{0,2}[A-Za-z][A-Za-z0-9 _.()/-]*\*{0,2}:{1,2}\*{0,2}(?:\s|$)/;
|
|
367
|
+
/**
|
|
368
|
+
* #4998 review (trek-e 5638530738, Minor 2): the labels STATE.md defines —
|
|
369
|
+
* every body source the frontmatter sync reads, plus the session and position
|
|
370
|
+
* fields the template writes. Lower-cased.
|
|
371
|
+
*/
|
|
372
|
+
const STATE_FIELD_LABELS = new Set([
|
|
373
|
+
...Object.values(state_md_schema_cjs_1.STATE_FIELD_SCHEMA).flatMap((row) => row.bodySource ?? []),
|
|
374
|
+
'Last session', 'Last Date', 'Resume file', 'Phase', 'Plan', 'Progress', 'Current focus',
|
|
375
|
+
].map((label) => label.toLowerCase()));
|
|
376
|
+
/**
|
|
377
|
+
* A plain `words: text` line below a field is a sibling field or a wrapped
|
|
378
|
+
* tail (`records and verified: all good`); its shape cannot tell which. A
|
|
379
|
+
* line is a sibling beyond doubt only when its label is bold or one STATE.md
|
|
380
|
+
* defines. Each caller resolves the ambiguous rest toward its own safe side:
|
|
381
|
+
* the record-session wrap check (`editedLineContinuation`) reads it as a
|
|
382
|
+
* continuation and leaves the field whole; the validator and the writer that
|
|
383
|
+
* removes a continuation (`fieldContinuationEnd`) end the value there, so a
|
|
384
|
+
* sibling is never deleted.
|
|
385
|
+
*/
|
|
386
|
+
function isUnambiguousSiblingLine(line) {
|
|
387
|
+
const m = /^ {0,3}(\*\*)?([A-Za-z][A-Za-z0-9 _.()/-]*?)(?:\*\*)?:{1,2}(?:\*\*)?(?:\s|$)/.exec(line);
|
|
388
|
+
return m !== null && (m[1] !== undefined || STATE_FIELD_LABELS.has(m[2].trim().toLowerCase()));
|
|
389
|
+
}
|
|
338
390
|
/**
|
|
339
391
|
* Return the prose that FOLLOWS a single-line field but plainly belongs to it —
|
|
340
392
|
* i.e. the remainder `stateExtractField` silently drops when a writer emits a
|
|
@@ -360,6 +412,8 @@ const STATE_SIBLING_FIELD_LINE_RE = /^\*{0,2}[A-Za-z][A-Za-z0-9 _-]*\*{0,2}:{1,2
|
|
|
360
412
|
* cannot wrap), or is followed by end-of-file, a blank line, Markdown
|
|
361
413
|
* structure, or a sibling field.
|
|
362
414
|
*/
|
|
415
|
+
/** The line terminators the STATE.md readers (JS `m`-flag `^`/`$`) treat as a line boundary. */
|
|
416
|
+
const LINE_SEPARATOR_RE = /[\n\u2028\u2029]/;
|
|
363
417
|
function stateFieldContinuation(content, fieldName) {
|
|
364
418
|
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
365
419
|
// Same two single-line grammars stateExtractField uses, in the same order, so
|
|
@@ -371,15 +425,197 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
371
425
|
if (!match)
|
|
372
426
|
return null;
|
|
373
427
|
// `(.+)` stops at the line terminator, so the field's line ends where the
|
|
374
|
-
// match does.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
428
|
+
// match does.
|
|
429
|
+
return continuationAfter(content, match.index + match[0].length);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* #4998: where the line a single-line field write changed starts — found from
|
|
433
|
+
* the edit itself, the first byte where `before` and `after` differ, so it is
|
|
434
|
+
* always the occurrence the writer touched, whichever label grammar or
|
|
435
|
+
* occurrence it matched. The bytes before that point are common to both, so
|
|
436
|
+
* the offset holds in either string. Callers handle `before === after`.
|
|
437
|
+
*/
|
|
438
|
+
function editedLineStart(before, after) {
|
|
439
|
+
const limit = Math.min(before.length, after.length);
|
|
440
|
+
let i = 0;
|
|
441
|
+
while (i < limit && before.charCodeAt(i) === after.charCodeAt(i))
|
|
442
|
+
i++;
|
|
443
|
+
// `lastIndexOf` clamps a negative start to 0, so i = 0 would find a
|
|
444
|
+
// newline AT byte 0 and misplace the edited line onto the next one.
|
|
445
|
+
return i === 0 ? 0 : before.lastIndexOf('\n', i - 1) + 1;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* #4998: the continuation (as `stateFieldContinuation` defines it) of the line
|
|
449
|
+
* a single-line field write CHANGED (`editedLineStart`). `null` when nothing
|
|
450
|
+
* changed — an identical-value write leaves every byte, the continuation
|
|
451
|
+
* included, where it was — when the edited line is the last line, or when it
|
|
452
|
+
* is a pipe-table row (a table cell cannot wrap).
|
|
453
|
+
*/
|
|
454
|
+
function editedLineContinuation(before, after) {
|
|
455
|
+
if (before === after)
|
|
456
|
+
return null;
|
|
457
|
+
const lineStart = editedLineStart(before, after);
|
|
458
|
+
const lineEnd = before.indexOf('\n', lineStart);
|
|
459
|
+
// The edited line only: trimming the rest of the document would let a blank
|
|
460
|
+
// edited line borrow a later table row's pipe.
|
|
461
|
+
const line = before.slice(lineStart, lineEnd === -1 ? before.length : lineEnd).replace(/\r$/, '');
|
|
462
|
+
if (line.trimStart().startsWith('|'))
|
|
463
|
+
return null;
|
|
464
|
+
// A line break INSIDE the edited line (a bare CR, U+2028, U+2029) already
|
|
465
|
+
// wraps the value for the `m`-flag readers, which stop there: the text after
|
|
466
|
+
// it is a continuation, exactly as a following LF line is (Codex review round 3).
|
|
467
|
+
const inner = line.search(planning_document_cjs_1.FIELD_LINE_BREAK_RE);
|
|
468
|
+
if (inner !== -1)
|
|
469
|
+
return line.slice(inner + 1).split(planning_document_cjs_1.FIELD_LINE_BREAK_RE).map((part) => part.trim()).join(' ').trim();
|
|
470
|
+
return lineEnd === -1 ? null : continuationAfter(before, lineEnd, false);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* #4998: `after` with the line a single-line write changed respelled from
|
|
474
|
+
* `**Label**:` to `**Label:**` — the spelling `stateExtractField` reads — so a
|
|
475
|
+
* value `state record-session` just wrote reads back into the frontmatter
|
|
476
|
+
* sync. `stateReplaceField` keeps the author's spelling by contract (#5007),
|
|
477
|
+
* and that contract stands for every other caller: this is applied only by
|
|
478
|
+
* the one writer whose lines the sync must read back, and only to the one
|
|
479
|
+
* line it wrote. Widening the reader to `**Label**:` instead was tried and
|
|
480
|
+
* reverted — it made the sync read fenced examples. Any other line is
|
|
481
|
+
* untouched.
|
|
482
|
+
*/
|
|
483
|
+
function readableEditedBoldLabel(before, after) {
|
|
484
|
+
if (before === after)
|
|
485
|
+
return after;
|
|
486
|
+
const lineStart = editedLineStart(before, after);
|
|
487
|
+
const lineEnd = after.indexOf('\n', lineStart);
|
|
488
|
+
const end = lineEnd === -1 ? after.length : lineEnd;
|
|
489
|
+
const line = after.slice(lineStart, end).replace(/^([ \t]*)\*\*([^*\r\n]+)\*\*:/, '$1**$2:**');
|
|
490
|
+
return after.slice(0, lineStart) + line + after.slice(end);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Index of the closing `---` of a closed frontmatter block at the top of a
|
|
494
|
+
* whole document, or -1 (always -1 outside `document` scope).
|
|
495
|
+
*/
|
|
496
|
+
function lastFrontmatterLine(lines, scope) {
|
|
497
|
+
if (!scope.document)
|
|
498
|
+
return -1;
|
|
499
|
+
const text = lines.join('\n');
|
|
500
|
+
const fm = (0, frontmatter_fence_cjs_1.locateFrontmatterFence)(text);
|
|
501
|
+
return fm && fm.closed ? text.slice(0, fm.closingStart).split('\n').length - 1 : -1;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* #4998: `scanFencedBlocks` over the Markdown of `lines` only. In a whole
|
|
505
|
+
* document the frontmatter block is YAML, where a ``` line is data (a block
|
|
506
|
+
* scalar), not a fence; its lines are blanked for the scan, so they never open
|
|
507
|
+
* a fence that shifts every boundary after them (Codex review). Line indices
|
|
508
|
+
* are those of `lines`.
|
|
509
|
+
*/
|
|
510
|
+
function markdownFencedBlocks(lines, scope = {}) {
|
|
511
|
+
const last = lastFrontmatterLine(lines, scope);
|
|
512
|
+
return last === -1 ? (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(lines) : (0, markdown_sectionizer_cjs_1.scanFencedBlocks)(lines.map((line, i) => (i <= last ? '' : line)));
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Per line of `lines`: whether a field reader or writer must not see it — a
|
|
516
|
+
* fenced block's line (delimiters included) or, in a whole document, a line
|
|
517
|
+
* of frontmatter between its `---` delimiters (YAML data that a Markdown
|
|
518
|
+
* scanner would otherwise read as a heading, a fence or a field).
|
|
519
|
+
*/
|
|
520
|
+
function maskedLineFlags(lines, scope) {
|
|
521
|
+
const masked = lines.map(() => false);
|
|
522
|
+
for (const block of markdownFencedBlocks(lines, scope)) {
|
|
523
|
+
const end = block.closeLineIdx === -1 ? lines.length - 1 : block.closeLineIdx;
|
|
524
|
+
for (let i = block.openLineIdx; i <= end; i++)
|
|
525
|
+
masked[i] = true;
|
|
526
|
+
}
|
|
527
|
+
const last = lastFrontmatterLine(lines, scope);
|
|
528
|
+
for (let i = 1; i < last; i++)
|
|
529
|
+
masked[i] = true;
|
|
530
|
+
return masked;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* #4998: `content` with every fenced line blanked to spaces — same length,
|
|
534
|
+
* same line terminators — for reading a field the way `writeOutsideFences`
|
|
535
|
+
* writes it: an example line in a fence is not a field. With
|
|
536
|
+
* `{ document: true }` the frontmatter's content lines are blanked too (its
|
|
537
|
+
* `---` delimiters kept), so scanners that do not know frontmatter — the
|
|
538
|
+
* heading tokenizer behind `collectSection`, `parsePlanningDoc` — see only
|
|
539
|
+
* the Markdown body (Codex review rounds 2 and 3).
|
|
540
|
+
*/
|
|
541
|
+
function maskFencedLines(content, scope = {}) {
|
|
542
|
+
const lines = content.split('\n');
|
|
543
|
+
const masked = maskedLineFlags(lines, scope);
|
|
544
|
+
return lines.map((line, i) => (masked[i] ? line.replace(/[^\r]/g, ' ') : line)).join('\n');
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* #4998: apply a single-line field write (`stateReplaceField` and kin) to
|
|
548
|
+
* `content` as if its fenced blocks were absent, so a fenced example line is
|
|
549
|
+
* never the occurrence a write replaces — the same rule
|
|
550
|
+
* `insertMissingSessionFields` follows, through the same scanner. `write` runs
|
|
551
|
+
* against a masked copy (`maskFencedLines`); its one edit is then spliced into
|
|
552
|
+
* the real text at the same offsets. Returns what `write` returns for "no
|
|
553
|
+
* match" (`null`, or the input unchanged as `content`), and `null` if the
|
|
554
|
+
* edit cannot be carried back exactly (it touched a fenced line or changed
|
|
555
|
+
* the line structure) — the stateReplaceField no-match contract, never a
|
|
556
|
+
* partial write.
|
|
557
|
+
*/
|
|
558
|
+
function writeOutsideFences(content, write, scope = {}) {
|
|
559
|
+
// Outside a whole document a leading `---` is a horizontal rule
|
|
560
|
+
// (`FenceScope`), but the field writers parse what they are given as a
|
|
561
|
+
// document, where it opens frontmatter and hides the fields below it: a
|
|
562
|
+
// section starting with a rule got a duplicate field inserted (Codex
|
|
563
|
+
// review). They are handed the text after a line break, which is removed again.
|
|
564
|
+
const run = (text) => {
|
|
565
|
+
if (scope.document)
|
|
566
|
+
return write(text);
|
|
567
|
+
const out = write(`\n${text}`);
|
|
568
|
+
return out !== null && out.startsWith('\n') ? out.slice(1) : null;
|
|
569
|
+
};
|
|
570
|
+
const masked = maskFencedLines(content, scope);
|
|
571
|
+
if (masked === content)
|
|
572
|
+
return run(content);
|
|
573
|
+
const result = run(masked);
|
|
574
|
+
if (result === null)
|
|
575
|
+
return null;
|
|
576
|
+
if (result === masked)
|
|
577
|
+
return content;
|
|
578
|
+
const limit = Math.min(masked.length, result.length);
|
|
579
|
+
let p = 0;
|
|
580
|
+
while (p < limit && masked.charCodeAt(p) === result.charCodeAt(p))
|
|
581
|
+
p++;
|
|
582
|
+
let s = 0;
|
|
583
|
+
while (s < limit - p && masked.charCodeAt(masked.length - 1 - s) === result.charCodeAt(result.length - 1 - s))
|
|
584
|
+
s++;
|
|
585
|
+
// The span the write replaced must lie on unfenced lines — an edit that
|
|
586
|
+
// touched a fenced line cannot be carried back, even when the masked and
|
|
587
|
+
// real bytes there happen to agree (a whitespace-only fenced line).
|
|
588
|
+
const lineOf = (offset) => content.slice(0, offset).split('\n').length - 1;
|
|
589
|
+
const fenced = maskedLineFlags(content.split('\n'), scope);
|
|
590
|
+
for (let line = lineOf(p); line <= lineOf(content.length - s); line++)
|
|
591
|
+
if (fenced[line])
|
|
592
|
+
return null;
|
|
593
|
+
const spliced = content.slice(0, p) + result.slice(p, result.length - s) + content.slice(content.length - s);
|
|
594
|
+
return maskFencedLines(spliced, scope) === result ? spliced : null;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* #4998: `content` with every `**Label**:` line respelled `**Label:**`, for
|
|
598
|
+
* READING a field the way the writer finds it: `stateReplaceField` matches
|
|
599
|
+
* both spellings, `stateExtractField` only the second, so a `**Resume
|
|
600
|
+
* file**: plan.md` read as absent while the writer could still replace it.
|
|
601
|
+
* Never written back.
|
|
602
|
+
*/
|
|
603
|
+
function readableBoldLabels(content) {
|
|
604
|
+
// allow-adhoc-markdown: a read-only copy for stateExtractField, never written back — not a document mutation
|
|
605
|
+
return content.replace(/^([ \t]*)\*\*([^*\r\n]+)\*\*:/gm, '$1**$2:**');
|
|
606
|
+
}
|
|
607
|
+
/** The continuation lines following the line that ends at `afterValue`. */
|
|
608
|
+
function continuationAfter(content, afterValue, ambiguousLabelEnds = true) {
|
|
609
|
+
// JS `.` excludes \r as well as \n, so on a CRLF document the \r sits just
|
|
610
|
+
// AFTER a `(.+)` match rather than inside it — hence the strip below before
|
|
611
|
+
// testing for the newline.
|
|
378
612
|
const rest = content.slice(afterValue).replace(/^\r/, '');
|
|
379
|
-
|
|
613
|
+
// #5460: U+2028 / U+2029 are line starts to the `m`-flag readers (and stop `(.+)`), so they end
|
|
614
|
+
// the value's line exactly as `\n` does.
|
|
615
|
+
if (!/^[\n\u2028\u2029]/.test(rest))
|
|
380
616
|
return null; // end of file: nothing follows
|
|
381
|
-
const lines = rest.slice(1).split(
|
|
382
|
-
const continuation = lines.slice(0, continuationLineCount(lines)).map((line) => line.trim());
|
|
617
|
+
const lines = rest.slice(1).split(LINE_SEPARATOR_RE).map((line) => line.replace(/\r$/, ''));
|
|
618
|
+
const continuation = lines.slice(0, continuationLineCount(lines, ambiguousLabelEnds)).map((line) => line.trim());
|
|
383
619
|
return continuation.length ? continuation.join(' ') : null;
|
|
384
620
|
}
|
|
385
621
|
/**
|
|
@@ -387,7 +623,7 @@ function stateFieldContinuation(content, fieldName) {
|
|
|
387
623
|
* `stateFieldContinuation` reports on, shared so a writer that replaces the value removes exactly
|
|
388
624
|
* the lines a validator would call its continuation (#4945). `lines` are CR-free.
|
|
389
625
|
*/
|
|
390
|
-
function continuationLineCount(lines) {
|
|
626
|
+
function continuationLineCount(lines, ambiguousLabelEnds = true) {
|
|
391
627
|
let count = 0;
|
|
392
628
|
for (let i = 0; i < lines.length; i++) {
|
|
393
629
|
const line = lines[i];
|
|
@@ -395,7 +631,9 @@ function continuationLineCount(lines) {
|
|
|
395
631
|
break;
|
|
396
632
|
if (MD_STRUCTURE_LINE_RE.test(line))
|
|
397
633
|
break;
|
|
398
|
-
|
|
634
|
+
// A label line the shape cannot place is a sibling only where the caller
|
|
635
|
+
// fails safe that way (`isUnambiguousSiblingLine`).
|
|
636
|
+
if (STATE_SIBLING_FIELD_LINE_RE.test(line) && (ambiguousLabelEnds || isUnambiguousSiblingLine(line)))
|
|
399
637
|
break;
|
|
400
638
|
// Look ahead one line: a setext underline below makes THIS line a heading
|
|
401
639
|
// title, so stop before consuming it rather than after.
|
|
@@ -414,10 +652,14 @@ function continuationLineCount(lines) {
|
|
|
414
652
|
*/
|
|
415
653
|
function fieldContinuationEnd(content, valueEnd) {
|
|
416
654
|
const tail = content.slice(valueEnd);
|
|
417
|
-
|
|
655
|
+
// #5460: the readers' `m`-flag `^` also starts a line after U+2028 / U+2029, so a continuation
|
|
656
|
+
// scan that split on `\n` alone swallowed `...tail<U+2028>Stopped at: x` as ONE continuation line
|
|
657
|
+
// and deleted the sibling field with it. Each separator is one UTF-16 unit, like `\n`, so the
|
|
658
|
+
// removal arithmetic below is unchanged.
|
|
659
|
+
const first = /^(?:\r?\n|[\u2028\u2029])/.exec(tail);
|
|
418
660
|
if (!first)
|
|
419
661
|
return valueEnd;
|
|
420
|
-
const rawLines = tail.slice(first[0].length).split(
|
|
662
|
+
const rawLines = tail.slice(first[0].length).split(LINE_SEPARATOR_RE);
|
|
421
663
|
const count = continuationLineCount(rawLines.map((line) => line.replace(/\r$/, '')));
|
|
422
664
|
if (count === 0)
|
|
423
665
|
return valueEnd;
|
|
@@ -429,6 +671,63 @@ function fieldContinuationEnd(content, valueEnd) {
|
|
|
429
671
|
removed -= 1;
|
|
430
672
|
return valueEnd + removed;
|
|
431
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* #4998: insert each needed field of `fields` — given in template order — into
|
|
676
|
+
* `body`, beside the sibling it follows in that order (below the sibling's own
|
|
677
|
+
* wrapped lines, never between them), else before the sibling it precedes,
|
|
678
|
+
* else at the top in bold. Plain beside a plain sibling, else bold, in the
|
|
679
|
+
* spellings `stateExtractField` reads back. Every existing line is kept, in
|
|
680
|
+
* order, and lines inside a fenced block are examples: never an anchor, never
|
|
681
|
+
* an insertion point. An inserted line ends as the body's lines do (CRLF when
|
|
682
|
+
* the body uses CRLF).
|
|
683
|
+
*/
|
|
684
|
+
function insertMissingSessionFields(body, fields) {
|
|
685
|
+
const lines = body.split('\n');
|
|
686
|
+
const fenced = maskedLineFlags(lines, {});
|
|
687
|
+
const cr = body.includes('\r\n') ? '\r' : '';
|
|
688
|
+
// The spellings stateReplaceField writes: `**Label:**`, `**Label**:`, `Label:`.
|
|
689
|
+
const lineOf = (label) => {
|
|
690
|
+
const re = new RegExp(`^[ \\t]*(?:\\*\\*)?${(0, pattern_cjs_1.escapeRegex)(label)}(?::\\*\\*|\\*\\*:|:)(?:\\s|$)`, 'i');
|
|
691
|
+
return lines.findIndex((l, i) => !fenced[i] && re.test(l));
|
|
692
|
+
};
|
|
693
|
+
// Where field k goes: below the nearest earlier sibling present (past its
|
|
694
|
+
// wrapped lines, stopping before a fence the continuation scan does not
|
|
695
|
+
// recognize), else above the nearest later one, else the top.
|
|
696
|
+
const placement = (k) => {
|
|
697
|
+
for (let j = k - 1; j >= 0; j--) {
|
|
698
|
+
const anchor = lineOf(fields[j].label);
|
|
699
|
+
if (anchor === -1)
|
|
700
|
+
continue;
|
|
701
|
+
// An ambiguous `words: text` line is taken as the anchor's tail, as the
|
|
702
|
+
// wrap check takes it: inserting inside a wrapped value splits it
|
|
703
|
+
// (Codex review); inserting below a sibling only moves the new line.
|
|
704
|
+
const tailEnd = anchor + 1 + continuationLineCount(lines.slice(anchor + 1).map((l) => l.replace(/\r$/, '')), false);
|
|
705
|
+
let at = anchor + 1;
|
|
706
|
+
while (at < tailEnd && !fenced[at])
|
|
707
|
+
at++;
|
|
708
|
+
return { at, anchor };
|
|
709
|
+
}
|
|
710
|
+
for (let j = k + 1; j < fields.length; j++) {
|
|
711
|
+
const anchor = lineOf(fields[j].label);
|
|
712
|
+
if (anchor !== -1)
|
|
713
|
+
return { at: anchor, anchor };
|
|
714
|
+
}
|
|
715
|
+
return { at: 0, anchor: -1 };
|
|
716
|
+
};
|
|
717
|
+
fields.forEach(({ label, needed, value }, k) => {
|
|
718
|
+
if (!needed)
|
|
719
|
+
return;
|
|
720
|
+
const { at, anchor } = placement(k);
|
|
721
|
+
const bold = anchor === -1 || lines[anchor].trimStart().startsWith('**');
|
|
722
|
+
const line = (bold ? `**${label}:** ` : `${label}: `) + value;
|
|
723
|
+
// Appended after an unterminated last line: that line gains the CR.
|
|
724
|
+
if (at === lines.length)
|
|
725
|
+
lines[at - 1] += cr;
|
|
726
|
+
lines.splice(at, 0, at === lines.length ? line : line + cr);
|
|
727
|
+
fenced.splice(at, 0, false);
|
|
728
|
+
});
|
|
729
|
+
return lines.join('\n');
|
|
730
|
+
}
|
|
432
731
|
function stateExtractField(content, fieldName) {
|
|
433
732
|
const escaped = (0, pattern_cjs_1.escapeRegex)(fieldName);
|
|
434
733
|
// Bold line-start format: **FieldName:** value. Leading same-line whitespace
|
|
@@ -636,14 +935,13 @@ function stateReplaceField(content, fieldName, newValue) {
|
|
|
636
935
|
// is the same, already-precedented widening as the "Depends on"
|
|
637
936
|
// migration (src/phase.cts) — intentional, not scope creep.
|
|
638
937
|
//
|
|
639
|
-
// A parse failure
|
|
640
|
-
// a line break (\r/\n — would forge sibling structure on splice, the same
|
|
641
|
-
// hazard `setFieldValue` refuses unconditionally at #4917/ADR-4910
|
|
642
|
-
// Decision 2 & 4; this local splice has no seam call to inherit that
|
|
643
|
-
// refusal from, so it is re-checked here) falls through to the
|
|
938
|
+
// A parse failure or an absent/case-mismatched label falls through to the
|
|
644
939
|
// plain/pipe-table branches below unchanged — the same silent
|
|
645
940
|
// per-occurrence no-op-on-no-match contract the removed regex had for any
|
|
646
|
-
// input it didn't match.
|
|
941
|
+
// input it didn't match. A value containing a line break
|
|
942
|
+
// (`FIELD_LINE_BREAK_RE` — would forge sibling structure on splice, the
|
|
943
|
+
// same hazard `setFieldValue` refuses at #4917/ADR-4910 Decision 2 & 4)
|
|
944
|
+
// is refused by every rung (#4998): the function returns null.
|
|
647
945
|
const parsed = (0, planning_document_cjs_1.parsePlanningDoc)(content, 'STATE.md');
|
|
648
946
|
if (parsed.ok) {
|
|
649
947
|
let boldField = null;
|
|
@@ -667,11 +965,16 @@ function stateReplaceField(content, fieldName, newValue) {
|
|
|
667
965
|
const newValueStr = `${newValue}`;
|
|
668
966
|
const needsSeparator = newValueStr.length > 0 && !/[ \t]$/.test(spacingText);
|
|
669
967
|
const writeValue = `${needsSeparator ? ' ' : ''}${newValueStr}`;
|
|
670
|
-
if (
|
|
968
|
+
if (!planning_document_cjs_1.FIELD_LINE_BREAK_RE.test(writeValue)) {
|
|
671
969
|
return (content.slice(0, boldField.valueSpan.start) + writeValue + content.slice(boldField.trailingSpan.end));
|
|
672
970
|
}
|
|
673
971
|
}
|
|
674
972
|
}
|
|
973
|
+
// #4998: the plain and table rungs refuse a line break too. Spliced in, it
|
|
974
|
+
// forges sibling lines exactly as the bold rung's refusal above prevents;
|
|
975
|
+
// falling through to them only moved the forgery one rung down.
|
|
976
|
+
if (planning_document_cjs_1.FIELD_LINE_BREAK_RE.test(`${newValue}`))
|
|
977
|
+
return null;
|
|
675
978
|
// Plain line-start format: FieldName: value (same same-line confinement as above)
|
|
676
979
|
const plainPattern = new RegExp(`(^${escaped}:[ \\t]*)(.*)`, 'im');
|
|
677
980
|
if (plainPattern.test(content)) {
|
|
@@ -714,14 +1017,28 @@ function stateReplaceFieldWithFallback(content, primary, fallback, value) {
|
|
|
714
1017
|
* Replace-only (no insertion): returns `content` unchanged when no session
|
|
715
1018
|
* section exists or the field is absent from it, so a STATE.md layout without
|
|
716
1019
|
* the line keeps its shape and the post-sync preservation pass decides the
|
|
717
|
-
* frontmatter value (see #3374).
|
|
1020
|
+
* frontmatter value (see #3374). Lines inside a fenced block are examples and
|
|
1021
|
+
* are never the occurrence replaced (#4998, `writeOutsideFences`).
|
|
718
1022
|
*/
|
|
719
1023
|
function stateReplaceFieldInSession(content, primary, fallback, value) {
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) =>
|
|
1024
|
+
const hasCanonicalSession = (0, markdown_sectionizer_cjs_1.collectSection)(content, isSessionHeading, { levelBounded: true }) !== null;
|
|
1025
|
+
const target = hasCanonicalSession ? isSessionHeading : isSessionContinuityHeading;
|
|
1026
|
+
// #4998: a fenced example line is never the field written, as the session
|
|
1027
|
+
// readers (state.cts sessionFieldScope) never read one.
|
|
1028
|
+
return (0, markdown_sectionizer_cjs_1.withSection)(content, target, (sectionBody) => writeOutsideFences(sectionBody, (visible) => stateReplaceFieldWithFallback(visible, primary, fallback, value)) ?? sectionBody);
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* The two session headings, as every session reader and writer recognises
|
|
1032
|
+
* them: one grammar (the `collectSection` tokenizer), so a heading the readers
|
|
1033
|
+
* take is never one a writer misses. #4998 review: `state record-session`
|
|
1034
|
+
* detected the section with its own regex, which rejected `## Session`, and
|
|
1035
|
+
* appended a duplicate section.
|
|
1036
|
+
*/
|
|
1037
|
+
function isSessionHeading(h) {
|
|
1038
|
+
return h.level === 2 && h.text.trim().toLowerCase() === 'session';
|
|
1039
|
+
}
|
|
1040
|
+
function isSessionContinuityHeading(h) {
|
|
1041
|
+
return h.level === 2 && h.text.trim().toLowerCase() === 'session continuity';
|
|
725
1042
|
}
|
|
726
1043
|
/**
|
|
727
1044
|
* #4186: the DECLARED raw-status vocabulary `normalizeStateStatus` recognizes.
|