@opengsd/gsd-core 1.12.0 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.opencode/plugins/gsd-core.js +12 -0
- package/agents/gsd-executor.md +63 -35
- package/agents/gsd-plan-checker.md +76 -57
- package/agents/gsd-planner.md +14 -0
- package/agents/gsd-ui-checker.md +19 -3
- package/agents/gsd-ui-researcher.md +29 -0
- package/agents/gsd-verifier.md +23 -1
- package/bin/install.js +239 -67
- package/commands/gsd/execute-phase.md +1 -1
- package/commands/gsd/ns-workflow.md +2 -1
- package/commands/gsd/phase.md +1 -1
- package/commands/gsd/quick-batch.md +105 -0
- package/commands/gsd/surface.md +18 -8
- package/gsd-core/bin/gsd-tools.cjs +195 -50
- package/gsd-core/bin/lib/capability-activation.cjs +27 -0
- package/gsd-core/bin/lib/capability-registry.cjs +514 -114
- package/gsd-core/bin/lib/capability-state.cjs +7 -1
- package/gsd-core/bin/lib/capability-validator.cjs +120 -4
- package/gsd-core/bin/lib/capability-writer.cjs +14 -4
- package/gsd-core/bin/lib/check-command-router.cjs +85 -2
- package/gsd-core/bin/lib/claude-orchestration.cjs +10 -25
- package/gsd-core/bin/lib/clusters.cjs +1 -0
- package/gsd-core/bin/lib/command-aliases.cjs +16 -0
- package/gsd-core/bin/lib/commands.cjs +337 -13
- package/gsd-core/bin/lib/config-loader.cjs +3 -0
- package/gsd-core/bin/lib/core-utils.cjs +34 -7
- package/gsd-core/bin/lib/decisions.cjs +213 -1
- package/gsd-core/bin/lib/edge-probe.cjs +14 -1
- package/gsd-core/bin/lib/file-overlap-partitioner.cjs +74 -0
- package/gsd-core/bin/lib/frontmatter.cjs +137 -23
- package/gsd-core/bin/lib/gap-checker.cjs +22 -13
- package/gsd-core/bin/lib/git-base-branch.cjs +10 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/phase-structure.cjs +8 -2
- package/gsd-core/bin/lib/health-diagnostic-rules/roadmap-disk-consistency.cjs +54 -11
- package/gsd-core/bin/lib/health-diagnostic-rules/state-consistency.cjs +75 -22
- package/gsd-core/bin/lib/host-integration.cjs +57 -5
- package/gsd-core/bin/lib/init-command-router.cjs +14 -0
- package/gsd-core/bin/lib/init.cjs +132 -15
- package/gsd-core/bin/lib/install-engine.cjs +184 -12
- package/gsd-core/bin/lib/install-model-override-resolver.cjs +45 -0
- package/gsd-core/bin/lib/install-profiles.cjs +22 -14
- package/gsd-core/bin/lib/installer-migration-report.cjs +1 -0
- package/gsd-core/bin/lib/io.cjs +35 -0
- package/gsd-core/bin/lib/loop-resolver.cjs +14 -8
- package/gsd-core/bin/lib/markdown-table.cjs +123 -0
- package/gsd-core/bin/lib/milestone.cjs +22 -2
- package/gsd-core/bin/lib/phase-command-router.cjs +13 -6
- package/gsd-core/bin/lib/phase-id.cjs +251 -9
- package/gsd-core/bin/lib/phase.cjs +774 -35
- package/gsd-core/bin/lib/plan-document.cjs +10 -0
- package/gsd-core/bin/lib/planning-snapshot.cjs +147 -20
- package/gsd-core/bin/lib/planning-workspace.cjs +103 -28
- package/gsd-core/bin/lib/quick-batch-command-router.cjs +285 -0
- package/gsd-core/bin/lib/quick-batch-dispatch.cjs +250 -0
- package/gsd-core/bin/lib/quick-batch.cjs +840 -0
- package/gsd-core/bin/lib/review-lane-descriptor.cjs +53 -5
- package/gsd-core/bin/lib/review-lane-invocation.cjs +73 -1
- package/gsd-core/bin/lib/review-lane-runner.cjs +136 -10
- package/gsd-core/bin/lib/roadmap-parser.cjs +499 -26
- package/gsd-core/bin/lib/roadmap.cjs +187 -58
- package/gsd-core/bin/lib/runtime-artifact-conversion.cjs +233 -33
- package/gsd-core/bin/lib/runtime-artifact-install-plan.cjs +16 -17
- package/gsd-core/bin/lib/runtime-artifact-layout.cjs +286 -108
- package/gsd-core/bin/lib/runtime-hooks-surface.cjs +215 -43
- package/gsd-core/bin/lib/shell-command-projection.cjs +4 -0
- package/gsd-core/bin/lib/smart-entry.cjs +7 -9
- package/gsd-core/bin/lib/state-document.cjs +30 -5
- package/gsd-core/bin/lib/state-md-schema.cjs +23 -13
- package/gsd-core/bin/lib/state-transition.cjs +333 -44
- package/gsd-core/bin/lib/state.cjs +684 -125
- package/gsd-core/bin/lib/surface.cjs +23 -8
- package/gsd-core/bin/lib/tdd-red-evidence.cjs +133 -0
- package/gsd-core/bin/lib/uat.cjs +1419 -515
- package/gsd-core/bin/lib/update-context.cjs +6 -2
- package/gsd-core/bin/lib/validate.cjs +230 -12
- package/gsd-core/bin/lib/verification-command-router.cjs +2 -1
- package/gsd-core/bin/lib/verification.cjs +273 -12
- package/gsd-core/bin/lib/verify-command-router.cjs +1 -0
- package/gsd-core/bin/lib/verify.cjs +346 -16
- package/gsd-core/bin/lib/workstream-inventory.cjs +20 -2
- package/gsd-core/bin/lib/worktree-safety.cjs +8 -0
- package/gsd-core/bin/shared/config-schema.manifest.json +8 -0
- package/gsd-core/bin/verify-reapply-patches.cjs +70 -3
- package/gsd-core/references/agent-contracts.md +3 -3
- package/gsd-core/references/edge-probe.md +17 -13
- package/gsd-core/references/execute-mvp-tdd.md +18 -16
- package/gsd-core/references/execute-phase-response-language.md +6 -0
- package/gsd-core/references/executor-examples.md +42 -0
- package/gsd-core/references/few-shot-examples/plan-checker.md +15 -15
- package/gsd-core/references/mvp-concepts.md +2 -2
- package/gsd-core/references/plan-checker-examples.md +41 -0
- package/gsd-core/references/planner-antipatterns.md +25 -0
- package/gsd-core/references/planner-chunked.md +5 -1
- package/gsd-core/references/planner-coupling.md +42 -0
- package/gsd-core/references/planner-quick-batch.md +71 -0
- package/gsd-core/references/planner-reviews.md +47 -0
- package/gsd-core/references/planner-revision.md +75 -2
- package/gsd-core/references/planning-config.md +2 -1
- package/gsd-core/references/response-language-directive.md +9 -0
- package/gsd-core/references/revision-loop.md +118 -11
- package/gsd-core/references/tdd.md +14 -9
- package/gsd-core/references/verifier-evidence-gate.md +160 -0
- package/gsd-core/templates/phase-prompt.md +4 -0
- package/gsd-core/templates/verification-report.md +5 -0
- package/gsd-core/workflows/add-backlog.md +2 -0
- package/gsd-core/workflows/add-phase.md +2 -0
- package/gsd-core/workflows/add-tests.md +1 -1
- package/gsd-core/workflows/add-todo.md +1 -1
- package/gsd-core/workflows/ai-integration-phase.md +1 -1
- package/gsd-core/workflows/analyze-dependencies.md +2 -0
- package/gsd-core/workflows/audit-fix.md +2 -0
- package/gsd-core/workflows/audit-milestone.md +2 -0
- package/gsd-core/workflows/audit-uat.md +2 -0
- package/gsd-core/workflows/autonomous.md +2 -0
- package/gsd-core/workflows/check-todos.md +1 -1
- package/gsd-core/workflows/cleanup.md +1 -1
- package/gsd-core/workflows/code-review/steps/structural-pre-pass.md +15 -13
- package/gsd-core/workflows/code-review-fix.md +2 -0
- package/gsd-core/workflows/code-review.md +73 -31
- package/gsd-core/workflows/complete-milestone.md +13 -4
- package/gsd-core/workflows/debug.md +1 -1
- package/gsd-core/workflows/diagnose-issues.md +5 -1
- package/gsd-core/workflows/discuss-phase/modes/advisor.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/all.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/analyze.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/auto.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/batch.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/chain.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/default.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/power.md +2 -0
- package/gsd-core/workflows/discuss-phase/modes/text.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/context.md +2 -0
- package/gsd-core/workflows/discuss-phase/templates/discussion-log.md +2 -0
- package/gsd-core/workflows/discuss-phase-assumptions.md +1 -1
- package/gsd-core/workflows/discuss-phase-power.md +2 -0
- package/gsd-core/workflows/discuss-phase.md +1 -1
- package/gsd-core/workflows/do.md +43 -13
- package/gsd-core/workflows/docs-update.md +1 -1
- package/gsd-core/workflows/edit-phase.md +2 -0
- package/gsd-core/workflows/eval-review.md +1 -1
- package/gsd-core/workflows/execute-phase/steps/codebase-drift-gate.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/executor-isolation-dispatch.md +17 -1
- package/gsd-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +8 -2
- package/gsd-core/workflows/execute-phase/steps/regression-gate-run.md +2 -0
- package/gsd-core/workflows/execute-phase/steps/tdd-applicability-resolution.md +25 -0
- package/gsd-core/workflows/execute-phase/steps/worktree-recovery-policy.md +2 -0
- package/gsd-core/workflows/execute-phase.md +32 -14
- package/gsd-core/workflows/execute-plan.md +8 -8
- package/gsd-core/workflows/explore.md +2 -0
- package/gsd-core/workflows/extract-learnings.md +2 -0
- package/gsd-core/workflows/fast.md +6 -0
- package/gsd-core/workflows/forensics.md +2 -0
- package/gsd-core/workflows/graduation.md +1 -1
- package/gsd-core/workflows/health.md +1 -1
- package/gsd-core/workflows/help/modes/brief.md +2 -0
- package/gsd-core/workflows/help/modes/default.md +2 -0
- package/gsd-core/workflows/help/modes/full.md +12 -0
- package/gsd-core/workflows/help/modes/topic.md +2 -0
- package/gsd-core/workflows/help.md +2 -0
- package/gsd-core/workflows/import.md +3 -3
- package/gsd-core/workflows/inbox.md +1 -1
- package/gsd-core/workflows/ingest-docs.md +1 -1
- package/gsd-core/workflows/insert-phase.md +2 -0
- package/gsd-core/workflows/list-phase-assumptions.md +2 -0
- package/gsd-core/workflows/list-seeds.md +2 -0
- package/gsd-core/workflows/list-workspaces.md +2 -0
- package/gsd-core/workflows/manager.md +3 -3
- package/gsd-core/workflows/map-codebase.md +2 -0
- package/gsd-core/workflows/milestone-summary.md +2 -0
- package/gsd-core/workflows/mvp-phase.md +1 -1
- package/gsd-core/workflows/new-milestone.md +1 -1
- package/gsd-core/workflows/new-project.md +5 -3
- package/gsd-core/workflows/new-workspace.md +1 -1
- package/gsd-core/workflows/next.md +2 -0
- package/gsd-core/workflows/node-repair.md +2 -0
- package/gsd-core/workflows/note.md +2 -0
- package/gsd-core/workflows/onboard.md +1 -1
- package/gsd-core/workflows/pause-work.md +19 -4
- package/gsd-core/workflows/plan-phase/steps/chunked-planning-mode.md +100 -18
- package/gsd-core/workflows/plan-phase/steps/prd-express-path.md +2 -0
- package/gsd-core/workflows/plan-phase/steps/stall-detection-helpers.md +9 -0
- package/gsd-core/workflows/plan-phase.md +130 -12
- package/gsd-core/workflows/plan-review-convergence.md +102 -10
- package/gsd-core/workflows/plant-seed.md +1 -1
- package/gsd-core/workflows/pr-branch.md +11 -3
- package/gsd-core/workflows/profile-user.md +1 -1
- package/gsd-core/workflows/progress/steps/forensic-audit.md +1 -1
- package/gsd-core/workflows/progress.md +25 -3
- package/gsd-core/workflows/quick/steps/plan-checker-loop.md +37 -2
- package/gsd-core/workflows/quick/steps/research-phase.md +3 -3
- package/gsd-core/workflows/quick-batch/steps/batch-init.md +55 -0
- package/gsd-core/workflows/quick-batch/steps/completion.md +65 -0
- package/gsd-core/workflows/quick-batch/steps/merge-wave.md +100 -0
- package/gsd-core/workflows/quick-batch/steps/plan-checker-loop.md +147 -0
- package/gsd-core/workflows/quick-batch/steps/planner-wave.md +158 -0
- package/gsd-core/workflows/quick-batch/steps/research-phase.md +95 -0
- package/gsd-core/workflows/quick-batch/steps/resume-mode.md +49 -0
- package/gsd-core/workflows/quick-batch/steps/verification-wave.md +73 -0
- package/gsd-core/workflows/quick-batch/steps/worktree-dispatch.md +169 -0
- package/gsd-core/workflows/quick-batch.md +203 -0
- package/gsd-core/workflows/quick.md +13 -3
- package/gsd-core/workflows/reapply-patches.md +2 -0
- package/gsd-core/workflows/remove-phase.md +2 -0
- package/gsd-core/workflows/remove-workspace.md +1 -1
- package/gsd-core/workflows/resume-project.md +6 -2
- package/gsd-core/workflows/review.md +215 -10
- package/gsd-core/workflows/scan.md +2 -0
- package/gsd-core/workflows/section-manifest.json +12 -0
- package/gsd-core/workflows/secure-phase.md +1 -1
- package/gsd-core/workflows/session-report.md +2 -0
- package/gsd-core/workflows/settings-advanced.md +2 -0
- package/gsd-core/workflows/settings-integrations.md +9 -8
- package/gsd-core/workflows/settings.md +1 -1
- package/gsd-core/workflows/ship.md +10 -10
- package/gsd-core/workflows/sketch-wrap-up.md +2 -0
- package/gsd-core/workflows/sketch.md +1 -1
- package/gsd-core/workflows/smart-entry.md +1 -1
- package/gsd-core/workflows/spec-phase.md +24 -19
- package/gsd-core/workflows/spike-wrap-up.md +2 -0
- package/gsd-core/workflows/spike.md +1 -1
- package/gsd-core/workflows/stats.md +2 -0
- package/gsd-core/workflows/sync-skills.md +12 -4
- package/gsd-core/workflows/thread.md +2 -0
- package/gsd-core/workflows/transition.md +2 -0
- package/gsd-core/workflows/ui-phase.md +26 -5
- package/gsd-core/workflows/ui-review.md +1 -1
- package/gsd-core/workflows/ultraplan-phase.md +2 -0
- package/gsd-core/workflows/undo.md +1 -1
- package/gsd-core/workflows/update.md +41 -38
- package/gsd-core/workflows/validate-phase.md +1 -1
- package/gsd-core/workflows/verify-work.md +49 -3
- package/hooks/dist/gsd-check-update-worker.js +19 -2
- package/hooks/dist/gsd-context-monitor.js +283 -12
- package/hooks/dist/gsd-node-runner.sh +1 -0
- package/hooks/dist/gsd-prompt-guard.js +30 -5
- package/hooks/dist/gsd-read-guard.js +2 -0
- package/hooks/dist/gsd-read-injection-scanner.js +5 -5
- package/hooks/dist/gsd-secret-read-guard.js +1079 -0
- package/hooks/dist/gsd-statusline.js +7 -3
- package/hooks/dist/gsd-validate-commit.sh +444 -7
- package/hooks/dist/gsd-workflow-guard.js +2 -1
- package/hooks/dist/lib/git-cmd.js +210 -1
- package/hooks/dist/lib/injection-patterns.js +36 -6
- package/hooks/dist/managed-hooks-registry.cjs +1 -0
- package/hooks/gsd-check-update-worker.js +19 -2
- package/hooks/gsd-context-monitor.js +283 -12
- package/hooks/gsd-node-runner.sh +1 -0
- package/hooks/gsd-prompt-guard.js +30 -5
- package/hooks/gsd-read-guard.js +2 -0
- package/hooks/gsd-read-injection-scanner.js +5 -5
- package/hooks/gsd-secret-read-guard.js +1079 -0
- package/hooks/gsd-statusline.js +7 -3
- package/hooks/gsd-validate-commit.sh +444 -7
- package/hooks/gsd-workflow-guard.js +2 -1
- package/hooks/hooks.json +6 -0
- package/hooks/lib/git-cmd.js +210 -1
- package/hooks/lib/injection-patterns.js +36 -6
- package/hooks/managed-hooks-registry.cjs +1 -0
- package/package.json +5 -5
- package/scripts/build-hooks.js +11 -4
- package/scripts/ci-test-scope.cjs +7 -0
- package/scripts/docs-guard-registry.cjs +10 -0
- package/scripts/gen-loop-host-contract.cjs +67 -15
- package/scripts/lib/shellcheck-fetch.cjs +247 -0
- package/scripts/lint-allow-test-rule-refs.allowlist.json +0 -6
- package/scripts/lint-allow-test-rule-refs.effective-ceiling.json +1 -1
- package/scripts/lint-allow-test-rule-refs.unverified-ceiling.json +1 -1
- package/scripts/lint-docs-guard-registration.exempt-baseline.cjs +5 -0
- package/scripts/lint-phase-enumeration-drift.cjs +24 -6
- package/scripts/lint-phase-id-drift.cjs +133 -8
- package/scripts/lint-portable-grep.cjs +176 -0
- package/scripts/lint-response-language-coverage.cjs +524 -0
- package/scripts/lint-test-file-count.allowlist.json +3 -1
- package/scripts/lint-workflow-shellcheck-baseline.json +1027 -0
- package/scripts/lint-workflow-shellcheck.cjs +614 -0
- package/scripts/npm-audit-baseline.cjs +376 -0
- package/scripts/prompt-injection-scan.sh +8 -0
- package/scripts/require-issue-link-policy.cjs +16 -1
- package/skills/gsd-execute-phase/SKILL.md +1 -1
- package/skills/gsd-ns-workflow/SKILL.md +1 -0
- package/skills/gsd-phase/SKILL.md +1 -1
- package/skills/gsd-quick-batch/SKILL.md +105 -0
- package/skills/gsd-surface/SKILL.md +18 -8
- package/vscode/package.json +1 -1
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
*/
|
|
17
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
18
|
exports.STATE_MD_SECTIONS = exports.FRONTMATTER_BODY_SOURCE = exports.FIELD_CLASSIFICATION = void 0;
|
|
19
|
+
exports.formatProgressMachineSegment = formatProgressMachineSegment;
|
|
20
|
+
exports.stateReplaceProgressPercent = stateReplaceProgressPercent;
|
|
19
21
|
exports.beginFrontmatterReassembly = beginFrontmatterReassembly;
|
|
20
22
|
exports.getFrontmatterBodySource = getFrontmatterBodySource;
|
|
21
23
|
exports.frontmatterKeyForBodyField = frontmatterKeyForBodyField;
|
|
@@ -38,6 +40,44 @@ const pattern_cjs_1 = require("./pattern.cjs");
|
|
|
38
40
|
const stateMdSchemaMod = require("./state-md-schema.cjs");
|
|
39
41
|
const { STATE_FIELD_SCHEMA } = stateMdSchemaMod;
|
|
40
42
|
const { extractFrontmatter, reconstructFrontmatter, stripFrontmatter, FRONTMATTER_UNPARSEABLE } = frontmatter;
|
|
43
|
+
function formatProgressMachineSegment(percent) {
|
|
44
|
+
// ADR-3180 Decision 7: rounding and the 100 ceiling belong to the
|
|
45
|
+
// completion-ratio kernel. The floor is added here because this helper is
|
|
46
|
+
// also fed persisted frontmatter values (hand-editable, unlike the
|
|
47
|
+
// count-shaped entries into that kernel), and `'░'.repeat` throws on a
|
|
48
|
+
// negative count. Bar and printed percent use the clamped value so the two
|
|
49
|
+
// halves of the segment can never disagree.
|
|
50
|
+
const clamped = Math.max(0, (0, phase_lifecycle_cjs_1.clampPercentFromFraction)(percent / 100));
|
|
51
|
+
const filled = Math.round(clamped / 10);
|
|
52
|
+
return `[${'█'.repeat(filled)}${'░'.repeat(10 - filled)}] ${clamped}%`;
|
|
53
|
+
}
|
|
54
|
+
// Consumers (a future STATE.md writer that bypasses all three reintroduces the
|
|
55
|
+
// #4213 divergence class): `cmdStateUpdateProgress` and `syncCore`'s progress
|
|
56
|
+
// intent (both in this module) plus the post-sync body reconciliation in
|
|
57
|
+
// `applyPostSyncPreservation` (src/state.cts). `cmdStateSync` never reaches
|
|
58
|
+
// that reconciliation — ADR-3408 §8.3: `state sync` lets the body win, so
|
|
59
|
+
// preservation must NOT run — which is why its correctness comes from
|
|
60
|
+
// `syncCore`'s call here.
|
|
61
|
+
function stateReplaceProgressPercent(content, percent) {
|
|
62
|
+
const body = stripFrontmatter(content);
|
|
63
|
+
// #2177: bold `**Progress:**` anywhere in the body wins outright; the plain
|
|
64
|
+
// `^Progress:` form is the fallback only when no bold line exists, so an
|
|
65
|
+
// earlier free-text line starting with `Progress:` cannot capture the
|
|
66
|
+
// rewrite ahead of the real status line.
|
|
67
|
+
const boldProgressPattern = /(\*\*Progress:\*\*[ \t]*)([^\r\n]*)/i;
|
|
68
|
+
const plainProgressPattern = /^(Progress:[ \t]*)([^\r\n]*)/im;
|
|
69
|
+
const pattern = boldProgressPattern.test(body)
|
|
70
|
+
? boldProgressPattern
|
|
71
|
+
: plainProgressPattern.test(body)
|
|
72
|
+
? plainProgressPattern
|
|
73
|
+
: null;
|
|
74
|
+
if (!pattern)
|
|
75
|
+
return null;
|
|
76
|
+
const machineSegment = /(?:\[[^\]\r\n]*\][ \t]*)?\d{1,3}%/;
|
|
77
|
+
const progress = formatProgressMachineSegment(percent);
|
|
78
|
+
const updatedBody = body.replace(pattern, (_match, prefix, value) => (`${prefix}${machineSegment.test(value) ? value.replace(machineSegment, progress) : progress}`));
|
|
79
|
+
return content.slice(0, content.length - body.length) + updatedBody;
|
|
80
|
+
}
|
|
41
81
|
/**
|
|
42
82
|
* ADR-3473 §8.1 (#3881, consequence 2 wiring): does `existingFm` carry the
|
|
43
83
|
* `FRONTMATTER_UNPARSEABLE` marker `extractFrontmatter` sets when a
|
|
@@ -955,6 +995,46 @@ function mutateCurrentPositionResume(body, intent, today, updated) {
|
|
|
955
995
|
}
|
|
956
996
|
return body.slice(0, span.start) + sectionBody + body.slice(span.end);
|
|
957
997
|
}
|
|
998
|
+
const PLAN_SHAPE_N = /^(\d+)(?:\s.*)?$/;
|
|
999
|
+
const PLAN_SHAPE_N_OF_M = /^(\d+)\s+of\s+(\d+)(?:\s.*)?$/;
|
|
1000
|
+
/**
|
|
1001
|
+
* Parse a decimal group into a plan number, or `null` if it is not a value we
|
|
1002
|
+
* are willing to do arithmetic on.
|
|
1003
|
+
*
|
|
1004
|
+
* `parseInt` is deliberately not used on the raw field: it truncates (`"2 of 5"`
|
|
1005
|
+
* -> 2), accepts a sign (`"+2"`), and silently loses precision past
|
|
1006
|
+
* `Number.MAX_SAFE_INTEGER`, where the number we report and the string we write
|
|
1007
|
+
* back stop agreeing. The grammars above already exclude signs and trailing
|
|
1008
|
+
* text, so the only remaining hazard is magnitude.
|
|
1009
|
+
*/
|
|
1010
|
+
function planNumberFrom(digits) {
|
|
1011
|
+
const n = Number(digits);
|
|
1012
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Advance the leading integer of a written plan value, preserving everything
|
|
1016
|
+
* the author wrote around it: the zero-padding width ("04" -> "05") and any
|
|
1017
|
+
* trailing remainder ("2 of 99" -> "3 of 99", and the `\r` of a CRLF file).
|
|
1018
|
+
*
|
|
1019
|
+
* The three parse branches disagree about the field NAME and about whether a
|
|
1020
|
+
* total is carried inline, but they agree completely about this: only the
|
|
1021
|
+
* leading digits are the plan number, and nothing else on the line belongs to
|
|
1022
|
+
* this transition. Writing `String(newPlan)` instead — as the legacy branch
|
|
1023
|
+
* did — discards the author's text on a branch nobody was reading.
|
|
1024
|
+
*
|
|
1025
|
+
* padStart never truncates, so 09 -> 10 widens rather than clipping.
|
|
1026
|
+
*/
|
|
1027
|
+
function bumpLeadingNumber(raw, next) {
|
|
1028
|
+
const digits = /^\d+/.exec(raw);
|
|
1029
|
+
// Total rather than pass-through. `raw.replace(/^\d+/, …)` returns the input
|
|
1030
|
+
// unchanged when there are no leading digits, so `+2` advanced in `data` and
|
|
1031
|
+
// wrote the file untouched — the command reported progress it had not made
|
|
1032
|
+
// and could be re-run forever. The grammars make that unreachable today;
|
|
1033
|
+
// returning null keeps it unreachable if a fourth branch is ever added.
|
|
1034
|
+
if (!digits)
|
|
1035
|
+
return null;
|
|
1036
|
+
return raw.replace(/^\d+/, () => String(next).padStart(digits[0].length, '0'));
|
|
1037
|
+
}
|
|
958
1038
|
/**
|
|
959
1039
|
* Update fields within the ## Current Position section for advancePlan.
|
|
960
1040
|
* Mirrors `updateCurrentPositionFields` (state.cts:496) byte-for-behaviour:
|
|
@@ -1006,19 +1086,80 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc
|
|
|
1006
1086
|
mutated = true;
|
|
1007
1087
|
}
|
|
1008
1088
|
}
|
|
1009
|
-
if (fields.plan) {
|
|
1089
|
+
if (fields.plan || fields.currentPlan) {
|
|
1010
1090
|
// Plan is always replaced — system-derived, not executor-authored.
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1091
|
+
//
|
|
1092
|
+
// Which NAME to write is decided by what the SECTION carries, not by which
|
|
1093
|
+
// header field the value was read from. Mirroring the header was wrong in
|
|
1094
|
+
// both directions: a legacy header with a `Current Plan:` section line left
|
|
1095
|
+
// the section a plan behind, and a hybrid header with a `Plan:` section line
|
|
1096
|
+
// mutated nothing at all. The invariant is per-name — every site spelled
|
|
1097
|
+
// `Current Plan` gets the `Current Plan` value, every `Plan` site gets the
|
|
1098
|
+
// `Plan` value — so both are passed in and each is written where its own
|
|
1099
|
+
// name appears.
|
|
1100
|
+
//
|
|
1101
|
+
// Title-Case LITERALS reach both the regex and stateReplaceField
|
|
1102
|
+
// (ADR-3408 §8.3(b)): a literal cannot collide with a lowercase/snake_case
|
|
1103
|
+
// frontmatter key, whatever the caller passed.
|
|
1104
|
+
//
|
|
1105
|
+
// The replacements go through a replacer FUNCTION, never a replacement
|
|
1106
|
+
// string. `fields.plan` is derived from file content, and `String.replace`
|
|
1107
|
+
// expands `$&`, `` $` `` and `$'` in a replacement string — a STATE.md
|
|
1108
|
+
// carrying `Current Plan: 04 of 06 $&` would splice part of itself into the
|
|
1109
|
+
// document. `stateReplaceField` already uses a function for this reason;
|
|
1110
|
+
// these arms now agree with it.
|
|
1111
|
+
// Each name is written INDEPENDENTLY, and each falls back on its own.
|
|
1112
|
+
//
|
|
1113
|
+
// Two defects lived in the previous shape, both of which produced the
|
|
1114
|
+
// split-brain document this arm exists to prevent:
|
|
1115
|
+
//
|
|
1116
|
+
// - The fallback was guarded by `!mutated`, and `mutated` is FUNCTION-wide
|
|
1117
|
+
// — already set by the `phase`/`status`/`lastActivity` arms above, which
|
|
1118
|
+
// `advancePlanCore` always populates. A section spelled `**Current
|
|
1119
|
+
// Plan:**` (bold) or as a pipe-table row therefore skipped its fallback
|
|
1120
|
+
// because an UNRELATED field had been refreshed, and the section stayed a
|
|
1121
|
+
// plan behind the header.
|
|
1122
|
+
// - The fallback then picked ONE name by ternary. In the legacy shape both
|
|
1123
|
+
// values are populated, so it always chose `Current Plan` and a
|
|
1124
|
+
// `**Plan:**` section line — which base did write — got nothing.
|
|
1125
|
+
//
|
|
1126
|
+
// `planWritten` is local, so nothing outside this arm can satisfy its guard.
|
|
1127
|
+
let planWritten = false;
|
|
1128
|
+
const writePlanField = (name, value) => {
|
|
1129
|
+
if (!value)
|
|
1130
|
+
return;
|
|
1131
|
+
// Plain `Name:` line first. Title-Case LITERALS reach both the regex and
|
|
1132
|
+
// stateReplaceField (ADR-3408 §8.3(b)), and the replacement goes through a
|
|
1133
|
+
// replacer FUNCTION so a `$&` / `` $` `` / `$'` in the author's text is not
|
|
1134
|
+
// expanded into the document.
|
|
1135
|
+
if (name === 'Current Plan') {
|
|
1136
|
+
if (/^Current Plan:/m.test(sectionBody)) {
|
|
1137
|
+
sectionBody = sectionBody.replace(/^Current Plan:.*$/m, () => `Current Plan: ${value}`);
|
|
1138
|
+
planWritten = true;
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
const replaced = (0, state_document_cjs_1.stateReplaceField)(sectionBody, 'Current Plan', value);
|
|
1142
|
+
if (replaced !== null) {
|
|
1143
|
+
sectionBody = replaced;
|
|
1144
|
+
planWritten = true;
|
|
1145
|
+
}
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
if (/^Plan:/m.test(sectionBody)) {
|
|
1149
|
+
sectionBody = sectionBody.replace(/^Plan:.*$/m, () => `Plan: ${value}`);
|
|
1150
|
+
planWritten = true;
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const replaced = (0, state_document_cjs_1.stateReplaceField)(sectionBody, 'Plan', value);
|
|
1017
1154
|
if (replaced !== null) {
|
|
1018
1155
|
sectionBody = replaced;
|
|
1019
|
-
|
|
1156
|
+
planWritten = true;
|
|
1020
1157
|
}
|
|
1021
|
-
}
|
|
1158
|
+
};
|
|
1159
|
+
writePlanField('Current Plan', fields.currentPlan);
|
|
1160
|
+
writePlanField('Plan', fields.plan);
|
|
1161
|
+
if (planWritten)
|
|
1162
|
+
mutated = true;
|
|
1022
1163
|
}
|
|
1023
1164
|
if (!mutated)
|
|
1024
1165
|
return content;
|
|
@@ -1030,8 +1171,10 @@ function mutateCurrentPositionForAdvance(content, fields, statusDefaults, lastAc
|
|
|
1030
1171
|
/**
|
|
1031
1172
|
* Apply an `advancePlan` transition to STATE.md content.
|
|
1032
1173
|
*
|
|
1033
|
-
* Parses Current Plan / Total Plans
|
|
1034
|
-
* "Plan: X of Y"
|
|
1174
|
+
* Parses Current Plan / Total Plans in any of three shapes — the legacy
|
|
1175
|
+
* separate fields, the compound "Plan: X of Y", or the hybrid
|
|
1176
|
+
* "Current Plan: X of Y" (legacy name, compound value, no Total Plans
|
|
1177
|
+
* sibling) — increments the plan number, updates body fields
|
|
1035
1178
|
* and the ## Current Position section. When currentPlan >= totalPlans,
|
|
1036
1179
|
* takes the phase-complete branch (sets Status to "Phase complete — ready
|
|
1037
1180
|
* for verification") instead of advancing.
|
|
@@ -1077,30 +1220,105 @@ function advancePlanCore(content, deps) {
|
|
|
1077
1220
|
};
|
|
1078
1221
|
}
|
|
1079
1222
|
}
|
|
1080
|
-
// Parse plan number — legacy first, then compound.
|
|
1223
|
+
// Parse plan number — legacy pair first, then the hybrid, then compound.
|
|
1224
|
+
//
|
|
1225
|
+
// These branches decide ONE thing: which numbers the advance is computed
|
|
1226
|
+
// from. They deliberately do not record which FIELD supplied them, because
|
|
1227
|
+
// the write path no longer asks — every spelling is written back from its own
|
|
1228
|
+
// raw text (#3791 review round 6, B1/M1). An earlier revision tracked a
|
|
1229
|
+
// `planSourceField`/`planRawValue` pair here and then wrote the OTHER
|
|
1230
|
+
// spelling from this one's numbers, which is precisely how a field ended up
|
|
1231
|
+
// holding a value nothing had derived for it.
|
|
1081
1232
|
const legacyPlan = (0, state_document_cjs_1.stateExtractField)(content, 'Current Plan');
|
|
1082
1233
|
const legacyTotal = (0, state_document_cjs_1.stateExtractField)(content, 'Total Plans in Phase');
|
|
1083
1234
|
const planField = (0, state_document_cjs_1.stateExtractField)(content, 'Plan');
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
}
|
|
1101
|
-
if (
|
|
1235
|
+
// Every branch below reads its numbers out of an ANCHORED match's capture
|
|
1236
|
+
// groups. Nothing here calls parseInt on a raw field value, so a value the
|
|
1237
|
+
// grammar does not fully describe cannot half-parse into a plausible number.
|
|
1238
|
+
const legacyNMatch = legacyPlan ? PLAN_SHAPE_N.exec(legacyPlan) : null;
|
|
1239
|
+
const legacyNofMMatch = legacyPlan ? PLAN_SHAPE_N_OF_M.exec(legacyPlan) : null;
|
|
1240
|
+
const totalNMatch = legacyTotal ? PLAN_SHAPE_N.exec(legacyTotal) : null;
|
|
1241
|
+
const planNofMMatch = planField ? PLAN_SHAPE_N_OF_M.exec(planField) : null;
|
|
1242
|
+
const planNMatch = planField ? PLAN_SHAPE_N.exec(planField) : null;
|
|
1243
|
+
let parsedCurrent = null;
|
|
1244
|
+
let parsedTotal = null;
|
|
1245
|
+
if (legacyPlan && legacyTotal && (legacyNMatch || legacyNofMMatch) && totalNMatch) {
|
|
1246
|
+
// Legacy pair wins whenever both fields are present and both are readable,
|
|
1247
|
+
// even if the Current Plan value also carries an "of M" — the explicit
|
|
1248
|
+
// sibling field is the stated intent, so it supplies the total.
|
|
1249
|
+
parsedCurrent = planNumberFrom((legacyNMatch ?? legacyNofMMatch)[1]);
|
|
1250
|
+
parsedTotal = planNumberFrom(totalNMatch[1]);
|
|
1251
|
+
}
|
|
1252
|
+
else if (legacyNofMMatch) {
|
|
1253
|
+
// Hybrid: legacy field name, compound value, no readable Total Plans
|
|
1254
|
+
// sibling. Written by hand (and by agents) often enough to be worth
|
|
1255
|
+
// reading — #3784.
|
|
1256
|
+
parsedCurrent = planNumberFrom(legacyNofMMatch[1]);
|
|
1257
|
+
parsedTotal = planNumberFrom(legacyNofMMatch[2]);
|
|
1258
|
+
}
|
|
1259
|
+
else if (planNofMMatch) {
|
|
1260
|
+
parsedCurrent = planNumberFrom(planNofMMatch[1]);
|
|
1261
|
+
parsedTotal = planNumberFrom(planNofMMatch[2]);
|
|
1262
|
+
}
|
|
1263
|
+
// No branch for a bare `Plan: N` paired with a `Total Plans in Phase: M`
|
|
1264
|
+
// sibling and no `Current Plan` at all (#3791 review round 6, M2). A revision
|
|
1265
|
+
// of this PR accepted it; base did not (its `else if (planField)` arm had no
|
|
1266
|
+
// `of M` match and errored via NaN), and it is out of #3784's scope, which is
|
|
1267
|
+
// the hybrid `Current Plan: N of M`. It cannot be given the schema-row +
|
|
1268
|
+
// forcing-test coupling the other shapes have, either: `Plan` is body-only,
|
|
1269
|
+
// `buildStateFrontmatter` never reads it into frontmatter, so there is no
|
|
1270
|
+
// `current_*` key to hang a row on. An accepted shape with no schema row and
|
|
1271
|
+
// no forcing test is exactly the drift this diff is otherwise built to
|
|
1272
|
+
// prevent, so the shape is refused and named in the error instead.
|
|
1273
|
+
if (parsedCurrent === null || parsedTotal === null) {
|
|
1102
1274
|
return { content: reassemble(body), updated: [], data: { error: true } };
|
|
1103
1275
|
}
|
|
1276
|
+
const currentPlan = parsedCurrent;
|
|
1277
|
+
const totalPlans = parsedTotal;
|
|
1278
|
+
// Each SPELLING's own plan number, read from its own value (#3791 review
|
|
1279
|
+
// round 6, B1/M1). The parse above picks ONE field to advance FROM; these are
|
|
1280
|
+
// what each field independently claims, and they are the only honest basis
|
|
1281
|
+
// for writing that field back.
|
|
1282
|
+
const legacyOwnCurrent = legacyNMatch || legacyNofMMatch
|
|
1283
|
+
? planNumberFrom((legacyNMatch ?? legacyNofMMatch)[1])
|
|
1284
|
+
: null;
|
|
1285
|
+
const planOwnCurrent = planNofMMatch || planNMatch
|
|
1286
|
+
? planNumberFrom((planNofMMatch ?? planNMatch)[1])
|
|
1287
|
+
: null;
|
|
1288
|
+
// A document carrying BOTH spellings with DIFFERENT plan numbers disagrees
|
|
1289
|
+
// with itself, and no rule here can say which half is right. Refuse.
|
|
1290
|
+
//
|
|
1291
|
+
// This is the #3807 posture one field over: name the conflict, let the caller
|
|
1292
|
+
// resolve it, never pick. The alternative shipped in an earlier revision of
|
|
1293
|
+
// this PR and was the round-6 Blocker — with `Plan` as the parse source, the
|
|
1294
|
+
// write path re-stamped `Current Plan`'s value with the number it had just
|
|
1295
|
+
// derived from `Plan`, so `Current Plan: 7` beside `Plan: 2 of 5` silently
|
|
1296
|
+
// became `Current Plan: 3`. A number with no relationship to the field it was
|
|
1297
|
+
// written into, no error, no diagnostic.
|
|
1298
|
+
//
|
|
1299
|
+
// Placed BEFORE the phase-complete branch deliberately. Guarding only the
|
|
1300
|
+
// normal advance leaves `Current Plan: 7` beside `Plan: 5 of 5` writing a
|
|
1301
|
+
// terminal "Phase complete — ready for verification" into a document whose
|
|
1302
|
+
// two spellings never agreed on where execution was.
|
|
1303
|
+
//
|
|
1304
|
+
// Differing TOTALS are NOT a disagreement about position and are preserved,
|
|
1305
|
+
// not resolved: `Current Plan: 2` / `Total Plans in Phase: 5` beside
|
|
1306
|
+
// `Plan: 2 of 9` advances to `3` and `3 of 9`. Reconciling the two totals
|
|
1307
|
+
// would be this transition inventing an answer to a question nobody asked it.
|
|
1308
|
+
if (legacyOwnCurrent !== null && planOwnCurrent !== null && legacyOwnCurrent !== planOwnCurrent) {
|
|
1309
|
+
return {
|
|
1310
|
+
content: reassemble(body),
|
|
1311
|
+
updated: [],
|
|
1312
|
+
data: {
|
|
1313
|
+
error: true,
|
|
1314
|
+
reason: 'ambiguous_plan_position',
|
|
1315
|
+
plan_candidates: [
|
|
1316
|
+
`Current Plan: ${legacyPlan}`,
|
|
1317
|
+
`Plan: ${planField}`,
|
|
1318
|
+
],
|
|
1319
|
+
},
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1104
1322
|
const updated = [];
|
|
1105
1323
|
const statusDefaults = state_document_cjs_2.KNOWN_TEMPLATE_DEFAULTS['Status'];
|
|
1106
1324
|
const lastActivityDefaults = state_document_cjs_2.KNOWN_TEMPLATE_DEFAULTS['Last Activity'];
|
|
@@ -1122,24 +1340,98 @@ function advancePlanCore(content, deps) {
|
|
|
1122
1340
|
}
|
|
1123
1341
|
// Normal advance branch.
|
|
1124
1342
|
const newPlan = currentPlan + 1;
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1343
|
+
// The value each SPELLING should carry after the advance. A document may hold
|
|
1344
|
+
// both names (a `Current Plan:` header and a `Plan:` line in the section, or
|
|
1345
|
+
// the reverse), and each has always rendered differently — the legacy field
|
|
1346
|
+
// holds a bare/padded number while the section's `Plan:` line holds the
|
|
1347
|
+
// compound `N of M`.
|
|
1348
|
+
//
|
|
1349
|
+
// Each is advanced from ITS OWN raw text, never from the other's numbers
|
|
1350
|
+
// (#3791 review round 6, B1/M1). `bumpLeadingNumber` replaces only the leading
|
|
1351
|
+
// digits, so the field's zero-padding width, its own ` of M` and any trailing
|
|
1352
|
+
// annotation all survive — which is what the changeset claims, and what the
|
|
1353
|
+
// previous revision did only for whichever field happened to be the parse
|
|
1354
|
+
// source. The other field it re-stamped from numbers that were never its own.
|
|
1355
|
+
const advanceOwn = (raw, own) => {
|
|
1356
|
+
if (raw === null)
|
|
1357
|
+
return undefined;
|
|
1358
|
+
// Present but unreadable (`Plan: TBD`). Leave it exactly as authored: this
|
|
1359
|
+
// transition cannot advance what it cannot read, and writing a derived
|
|
1360
|
+
// number over it is the fabrication B1 was filed for. Stale-and-untouched is
|
|
1361
|
+
// honest; refusing the whole document because an unrelated line is
|
|
1362
|
+
// unreadable would be a narrowing #3784 does not license.
|
|
1363
|
+
if (own === null)
|
|
1364
|
+
return undefined;
|
|
1365
|
+
return bumpLeadingNumber(raw, newPlan) ?? undefined;
|
|
1366
|
+
};
|
|
1367
|
+
// Title-Case LITERALS to stateReplaceField (ADR-3408 §8.3(b)): a literal
|
|
1368
|
+
// cannot collide with a lowercase/snake_case frontmatter key, so it is safe
|
|
1369
|
+
// regardless of how the content argument was derived. `body` here is in fact
|
|
1370
|
+
// `stripFrontmatter(content)`, but the write-path drift guard does not do
|
|
1371
|
+
// dataflow tracking (by design), and satisfying its invariant by construction
|
|
1372
|
+
// is better than asking a reader to re-derive that it holds.
|
|
1373
|
+
// One value per SPELLING, then write both names everywhere they appear.
|
|
1374
|
+
//
|
|
1375
|
+
// `Current Plan` — whatever the author wrote, advanced in place: padding
|
|
1376
|
+
// and any ` of M` preserved.
|
|
1377
|
+
// `Plan` — likewise, so its OWN total survives. `Plan: 2 of 9`
|
|
1378
|
+
// beside a `Total Plans in Phase: 5` advances to
|
|
1379
|
+
// `3 of 9`, not `3 of 5`: the two totals disagreeing is
|
|
1380
|
+
// the document's business, not this transition's to
|
|
1381
|
+
// reconcile.
|
|
1382
|
+
//
|
|
1383
|
+
// The two are deliberately different strings for the legacy shape, which is
|
|
1384
|
+
// why this is a per-name value rather than one shared display value. Writing
|
|
1385
|
+
// only the name the value was PARSED from is what left the other name stale:
|
|
1386
|
+
// a `**Plan:** 2 of 6` header beside a `Current Plan:` line advanced one and
|
|
1387
|
+
// not the other, in whichever direction the precedence happened to fall.
|
|
1388
|
+
//
|
|
1389
|
+
// Each write is a no-op when that name is absent (`stateReplaceField` returns
|
|
1390
|
+
// null), so a document carrying only one spelling is unaffected — and
|
|
1391
|
+
// `undefined` means "present but not advanceable", which is left untouched
|
|
1392
|
+
// rather than overwritten.
|
|
1393
|
+
const currentPlanDisplayValue = advanceOwn(legacyPlan, legacyOwnCurrent);
|
|
1394
|
+
const planDisplayValue = planField === null
|
|
1395
|
+
// No top-level `Plan:` field to advance, but the `## Current Position`
|
|
1396
|
+
// section may still carry a `Plan:` line in a shape `stateExtractField`
|
|
1397
|
+
// does not read. There is no raw text here to preserve, so it gets the
|
|
1398
|
+
// compound rendering that line has always carried.
|
|
1399
|
+
? `${newPlan} of ${totalPlans}`
|
|
1400
|
+
: advanceOwn(planField, planOwnCurrent);
|
|
1401
|
+
if (currentPlanDisplayValue !== undefined) {
|
|
1402
|
+
body = (0, state_document_cjs_1.stateReplaceField)(body, 'Current Plan', currentPlanDisplayValue) || body;
|
|
1403
|
+
}
|
|
1404
|
+
// Only touch `Plan` when the document actually declares one. Writing it
|
|
1405
|
+
// unconditionally meant a `stateReplaceField` whose first match could be any
|
|
1406
|
+
// `Plan:` line anywhere in the body — including prose outside
|
|
1407
|
+
// `## Current Position` that was never a field. `planField` is the read of
|
|
1408
|
+
// that same field from the top of this function, so the write is scoped to a
|
|
1409
|
+
// document that has one.
|
|
1410
|
+
if (planField !== null && planDisplayValue !== undefined) {
|
|
1128
1411
|
body = (0, state_document_cjs_1.stateReplaceField)(body, 'Plan', planDisplayValue) || body;
|
|
1129
1412
|
}
|
|
1130
|
-
else {
|
|
1131
|
-
planDisplayValue = `${newPlan} of ${totalPlans}`;
|
|
1132
|
-
body = (0, state_document_cjs_1.stateReplaceField)(body, 'Current Plan', String(newPlan)) || body;
|
|
1133
|
-
}
|
|
1134
1413
|
body = (0, state_document_cjs_1.stateReplaceFieldIfTemplate)(body, 'Status', statusDefaults, 'Ready to execute') || body;
|
|
1135
1414
|
body = (0, state_document_cjs_1.stateReplaceFieldIfTemplate)(body, 'Last Activity', lastActivityDefaults, today) || body;
|
|
1136
1415
|
body = (0, state_document_cjs_1.stateReplaceFieldIfTemplate)(body, 'Last activity', lastActivityDefaults, today) || body;
|
|
1137
1416
|
body = mutateCurrentPositionForAdvance(body, {
|
|
1138
1417
|
status: 'Ready to execute',
|
|
1139
1418
|
lastActivity: today,
|
|
1419
|
+
// Both spellings, each with its own value. The section writes whichever
|
|
1420
|
+
// name it actually carries; passing only the header's name is what left
|
|
1421
|
+
// two sites disagreeing about where execution is.
|
|
1140
1422
|
plan: planDisplayValue,
|
|
1423
|
+
currentPlan: currentPlanDisplayValue,
|
|
1141
1424
|
}, statusDefaults, lastActivityDefaults);
|
|
1142
|
-
|
|
1425
|
+
// Report `Current Plan` only when it actually moved. The write above is
|
|
1426
|
+
// conditional now — a `Current Plan:` that is present but unreadable is left
|
|
1427
|
+
// as authored — so an unconditional push here would report progress this
|
|
1428
|
+
// transition had not made, which is the same sin `bumpLeadingNumber` returns
|
|
1429
|
+
// null to avoid. `reconcileReportedFields` at the `state.cts` caller would
|
|
1430
|
+
// catch it against the persisted bytes, but `transitionCore`'s own `updated`
|
|
1431
|
+
// is consumed directly too and has to be true on its own.
|
|
1432
|
+
if (currentPlanDisplayValue !== undefined)
|
|
1433
|
+
updated.push('Current Plan');
|
|
1434
|
+
updated.push('Status', 'Last Activity', 'Current Position');
|
|
1143
1435
|
return {
|
|
1144
1436
|
content: reassemble(body),
|
|
1145
1437
|
updated,
|
|
@@ -2004,13 +2296,10 @@ function syncCore(content, intent, deps) {
|
|
|
2004
2296
|
if (currentProgress) {
|
|
2005
2297
|
const currentPercent = parseInt(currentProgress.replace(/[^\d]/g, ''), 10);
|
|
2006
2298
|
if (currentPercent !== intent.percent) {
|
|
2007
|
-
const
|
|
2008
|
-
const filled = Math.round((intent.percent / 100) * barWidth);
|
|
2009
|
-
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
2010
|
-
const progressStr = `[${bar}] ${intent.percent}%`;
|
|
2011
|
-
changes.push(`Progress: ${currentProgress} -> ${progressStr}`);
|
|
2012
|
-
const result = (0, state_document_cjs_1.stateReplaceField)(modified, 'Progress', progressStr);
|
|
2299
|
+
const result = stateReplaceProgressPercent(modified, intent.percent);
|
|
2013
2300
|
if (result) {
|
|
2301
|
+
const progressStr = formatProgressMachineSegment(intent.percent);
|
|
2302
|
+
changes.push(`Progress: ${currentProgress} -> ${progressStr}`);
|
|
2014
2303
|
modified = result;
|
|
2015
2304
|
updated.push('Progress');
|
|
2016
2305
|
}
|