@pareto4/pp-core 1.11.1 → 1.12.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/plugin.json +1 -1
- package/README.md +31 -0
- package/agents/pp-code-fixer.md +1 -1
- package/agents/pp-debug-session-manager.md +1 -1
- package/agents/pp-debugger.md +1 -1
- package/agents/pp-executor.md +20 -10
- package/agents/pp-intel-updater.md +1 -1
- package/agents/pp-phase-researcher.md +1 -1
- package/agents/pp-plan-checker.md +5 -5
- package/agents/pp-planner.md +10 -24
- package/agents/pp-project-researcher.md +1 -1
- package/agents/pp-research-synthesizer.md +1 -1
- package/agents/pp-ui-researcher.md +1 -1
- package/agents/pp-verifier.md +8 -4
- package/bin/install.js +429 -82
- package/commands/pp-autonomous.md +8 -6
- package/commands/pp-discovery-to-build.md +1 -1
- package/commands/pp-discovery.md +3 -2
- package/commands/pp-discuss-phase.md +2 -2
- package/commands/pp-execute-phase.md +3 -1
- package/commands/pp-graphify.md +5 -5
- package/commands/pp-import.md +1 -1
- package/commands/pp-map-codebase.md +1 -1
- package/commands/pp-ns-project.md +2 -3
- package/commands/pp-ns-workflow.md +1 -1
- package/commands/pp-plan-phase.md +2 -1
- package/commands/pp-verify-work.md +2 -1
- package/gemini-extension.json +1 -1
- package/hooks/dist/pp-cursor-session-start.js +2 -2
- package/hooks/dist/pp-session-state.sh +1 -1
- package/hooks/dist/pp-statusline.js +21 -2
- package/hooks/pp-cursor-session-start.js +2 -2
- package/hooks/pp-session-state.sh +1 -1
- package/hooks/pp-statusline.js +21 -2
- package/package.json +18 -5
- package/pareto-skills/pp-project-setup/SKILL.md +5 -63
- package/pp-core/bin/detect-build-commands.sh +182 -0
- package/pp-core/bin/lib/capability-registry.cjs +65 -7
- package/pp-core/bin/lib/check-command-router.cjs +79 -38
- package/pp-core/bin/lib/clusters.cjs +2 -3
- package/pp-core/bin/lib/command-aliases.cjs +36 -0
- package/pp-core/bin/lib/command-arg-projection.cjs +99 -1
- package/pp-core/bin/lib/commands.cjs +21 -6
- package/pp-core/bin/lib/compose-bindings.cjs +70 -6
- package/pp-core/bin/lib/config-schema.cjs +16 -0
- package/pp-core/bin/lib/config.cjs +142 -27
- package/pp-core/bin/lib/core-utils.cjs +43 -7
- package/pp-core/bin/lib/discovery-input-check.cjs +218 -0
- package/pp-core/bin/lib/discovery-rebaseline.cjs +1372 -0
- package/pp-core/bin/lib/discovery-research-track.cjs +979 -0
- package/pp-core/bin/lib/discovery-rollup.cjs +476 -0
- package/pp-core/bin/lib/discovery-screens.cjs +1995 -0
- package/pp-core/bin/lib/discovery-sweep.cjs +390 -0
- package/pp-core/bin/lib/discovery-table.cjs +98 -0
- package/pp-core/bin/lib/dispatch-plan.cjs +472 -0
- package/pp-core/bin/lib/frontmatter.cjs +17 -2
- package/pp-core/bin/lib/init-command-router.cjs +8 -5
- package/pp-core/bin/lib/init-contract.cjs +93 -4
- package/pp-core/bin/lib/init.cjs +124 -134
- package/pp-core/bin/lib/install-profiles.cjs +5 -0
- package/pp-core/bin/lib/install-run.cjs +536 -0
- package/pp-core/bin/lib/io.cjs +15 -0
- package/pp-core/bin/lib/loop-resolver.cjs +24 -0
- package/pp-core/bin/lib/milestone.cjs +345 -48
- package/pp-core/bin/lib/phase-command-router.cjs +23 -1
- package/pp-core/bin/lib/phase-locator.cjs +15 -6
- package/pp-core/bin/lib/phase-scope.cjs +91 -0
- package/pp-core/bin/lib/phase-tier.cjs +467 -0
- package/pp-core/bin/lib/phase.cjs +247 -21
- package/pp-core/bin/lib/phases-command-router.cjs +2 -0
- package/pp-core/bin/lib/plan-graph.cjs +368 -0
- package/pp-core/bin/lib/planning-workspace.cjs +53 -0
- package/pp-core/bin/lib/platform-bindings.cjs +32 -2
- package/pp-core/bin/lib/profile-output.cjs +11 -3
- package/pp-core/bin/lib/prohibition-enforcement.cjs +112 -0
- package/pp-core/bin/lib/roadmap-parser.cjs +366 -99
- package/pp-core/bin/lib/roadmap.cjs +141 -39
- package/pp-core/bin/lib/runtime-hooks-surface.cjs +1 -1
- package/pp-core/bin/lib/shell-command-projection.cjs +24 -0
- package/pp-core/bin/lib/state-command-router.cjs +82 -13
- package/pp-core/bin/lib/state-document.cjs +124 -7
- package/pp-core/bin/lib/state.cjs +784 -257
- package/pp-core/bin/lib/template.cjs +22 -6
- package/pp-core/bin/lib/vault-command-router.cjs +57 -6
- package/pp-core/bin/lib/vault-practices.cjs +6 -36
- package/pp-core/bin/lib/vault-write-back.cjs +20 -4
- package/pp-core/bin/lib/vault.cjs +555 -23
- package/pp-core/bin/lib/verify.cjs +53 -31
- package/pp-core/bin/lib/workstream.cjs +1 -1
- package/pp-core/bin/lib/worktree-safety.cjs +26 -15
- package/pp-core/bin/portable-timeout.sh +89 -0
- package/pp-core/bin/pp-tools.cjs +149 -9
- package/pp-core/bin/shared/config-defaults.manifest.json +7 -0
- package/pp-core/bin/shared/config-schema.manifest.json +3 -0
- package/pp-core/references/autonomous-best-effort.md +461 -0
- package/pp-core/references/autonomous-lifecycle.md +146 -0
- package/pp-core/references/autonomous-smart-discuss.md +16 -12
- package/pp-core/references/checkpoints.md +1 -1
- package/pp-core/references/discovery-id-conventions.md +202 -0
- package/pp-core/references/discovery-rebaseline.md +254 -0
- package/pp-core/references/discovery-research-track.md +283 -0
- package/pp-core/references/discovery-traceability.md +400 -0
- package/pp-core/references/loop-hook-dispatch.md +7 -0
- package/pp-core/references/mandatory-initial-read.md +1 -1
- package/pp-core/references/planner-gap-closure.md +1 -0
- package/pp-core/references/planner-human-verify-mode.md +1 -1
- package/pp-core/references/planner-load-graph-context.md +1 -1
- package/pp-core/references/planner-plan-graph.md +66 -0
- package/pp-core/references/planner-tdd.md +1 -0
- package/pp-core/references/planning-config.md +8 -0
- package/pp-core/references/sketch-tooling.md +4 -0
- package/pp-core/references/tdd.md +1 -0
- package/pp-core/templates/claude-md.md +1 -1
- package/pp-core/templates/phase-prompt.md +6 -2
- package/pp-core/templates/prototype-review/review.js +303 -0
- package/pp-core/templates/state.md +6 -0
- package/pp-core/workflows/_runtime-launcher.snippet.sh +1 -1
- package/pp-core/workflows/add-backlog.md +1 -1
- package/pp-core/workflows/add-phase.md +1 -1
- package/pp-core/workflows/add-tests.md +1 -1
- package/pp-core/workflows/add-todo.md +1 -1
- package/pp-core/workflows/ai-integration-phase.md +2 -2
- package/pp-core/workflows/audit-fix.md +12 -16
- package/pp-core/workflows/audit-milestone.md +1 -1
- package/pp-core/workflows/audit-uat.md +1 -1
- package/pp-core/workflows/autonomous.md +68 -306
- package/pp-core/workflows/check-todos.md +1 -1
- package/pp-core/workflows/cleanup.md +1 -1
- package/pp-core/workflows/code-review-fix.md +1 -1
- package/pp-core/workflows/code-review.md +19 -7
- package/pp-core/workflows/complete-milestone.md +41 -60
- package/pp-core/workflows/debug.md +1 -1
- package/pp-core/workflows/diagnose-issues.md +1 -1
- package/pp-core/workflows/discovery-sprint/gap-analysis.md +140 -0
- package/pp-core/workflows/discovery-sprint/input-package.md +140 -0
- package/pp-core/workflows/discovery-sprint/prefill.md +92 -0
- package/pp-core/workflows/discovery-sprint/prototype.md +257 -0
- package/pp-core/workflows/discovery-sprint/rebaseline.md +276 -0
- package/pp-core/workflows/discovery-sprint/session-13-gates.md +24 -0
- package/pp-core/workflows/discovery-sprint/sessions/01-kick-off.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/02-as-is.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/03-to-be.md +215 -0
- package/pp-core/workflows/discovery-sprint/sessions/04-ux-design.md +146 -0
- package/pp-core/workflows/discovery-sprint/sessions/05-architecture.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/06-security.md +107 -0
- package/pp-core/workflows/discovery-sprint/sessions/07-identity.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/08-integration.md +85 -0
- package/pp-core/workflows/discovery-sprint/sessions/09-qa.md +109 -0
- package/pp-core/workflows/discovery-sprint/sessions/10-infrastructure.md +119 -0
- package/pp-core/workflows/discovery-sprint/sessions/11-roadmap.md +92 -0
- package/pp-core/workflows/discovery-sprint/sessions/12-delivery-model.md +98 -0
- package/pp-core/workflows/discovery-sprint/sessions/13-wrap-up.md +205 -0
- package/pp-core/workflows/discovery-sprint/templates/context-template.md +266 -0
- package/pp-core/workflows/discovery-sprint.md +79 -1714
- package/pp-core/workflows/discovery-to-build/steps/architecture-deliverables.md +5 -5
- package/pp-core/workflows/discovery-to-build/steps/screen-inventory-gate.md +70 -0
- package/pp-core/workflows/discovery-to-build/templates/discovery-references.md +54 -0
- package/pp-core/workflows/discovery-to-build/templates/pp-handoff.md +184 -0
- package/pp-core/workflows/discovery-to-build.md +21 -238
- package/pp-core/workflows/discuss-phase/modes/advisor.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/auto.md +1 -1
- package/pp-core/workflows/discuss-phase/modes/chain.md +1 -1
- package/pp-core/workflows/discuss-phase-assumptions.md +1 -1
- package/pp-core/workflows/discuss-phase.md +9 -2
- package/pp-core/workflows/do.md +4 -4
- package/pp-core/workflows/docs-update.md +1 -1
- package/pp-core/workflows/edit-phase.md +1 -1
- package/pp-core/workflows/eval-review.md +1 -1
- package/pp-core/workflows/execute-phase/steps/checkpoint-handling.md +45 -0
- package/pp-core/workflows/execute-phase/steps/client-context-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/codebase-drift-gate.md +1 -1
- package/pp-core/workflows/execute-phase/steps/cross-ai-delegation.md +90 -0
- package/pp-core/workflows/execute-phase/steps/install-run.md +29 -0
- package/pp-core/workflows/execute-phase/steps/phase-close-rebaseline.md +23 -0
- package/pp-core/workflows/execute-phase/steps/post-merge-gate.md +76 -77
- package/pp-core/workflows/execute-phase/steps/serial-group-dispatch.md +72 -0
- package/pp-core/workflows/execute-phase/steps/vault-fetch.md +5 -5
- package/pp-core/workflows/execute-phase/steps/vault-load-gate.md +19 -112
- package/pp-core/workflows/execute-phase.md +102 -216
- package/pp-core/workflows/execute-plan.md +6 -2
- package/pp-core/workflows/explore.md +1 -1
- package/pp-core/workflows/extract-learnings.md +1 -1
- package/pp-core/workflows/forensics.md +4 -4
- package/pp-core/workflows/graduation.md +1 -1
- package/pp-core/workflows/health.md +1 -1
- package/pp-core/workflows/help/modes/brief.md +1 -0
- package/pp-core/workflows/help/modes/default.md +2 -1
- package/pp-core/workflows/help/modes/full.md +12 -5
- package/pp-core/workflows/import.md +1 -1
- package/pp-core/workflows/ingest-docs.md +1 -1
- package/pp-core/workflows/init.md +18 -2
- package/pp-core/workflows/insert-phase.md +1 -1
- package/pp-core/workflows/list-workspaces.md +1 -1
- package/pp-core/workflows/manager.md +3 -3
- package/pp-core/workflows/map-codebase.md +2 -2
- package/pp-core/workflows/milestone-summary.md +4 -4
- package/pp-core/workflows/mvp-phase.md +1 -1
- package/pp-core/workflows/new-milestone.md +7 -8
- package/pp-core/workflows/new-project.md +6 -1
- package/pp-core/workflows/new-workspace.md +1 -1
- package/pp-core/workflows/next.md +2 -2
- package/pp-core/workflows/note.md +2 -2
- package/pp-core/workflows/pause-work.md +1 -1
- package/pp-core/workflows/plan-milestone-gaps.md +1 -1
- package/pp-core/workflows/plan-phase/steps/chunked-planning.md +111 -0
- package/pp-core/workflows/plan-phase/steps/phase-tier.md +34 -0
- package/pp-core/workflows/plan-phase.md +43 -167
- package/pp-core/workflows/plan-review-convergence.md +2 -2
- package/pp-core/workflows/plant-seed.md +1 -1
- package/pp-core/workflows/pr-branch.md +1 -1
- package/pp-core/workflows/profile-user.md +1 -1
- package/pp-core/workflows/progress.md +3 -3
- package/pp-core/workflows/quick.md +3 -1
- package/pp-core/workflows/remove-phase.md +1 -1
- package/pp-core/workflows/remove-workspace.md +1 -1
- package/pp-core/workflows/resume-project.md +6 -12
- package/pp-core/workflows/review.md +1 -1
- package/pp-core/workflows/scan.md +1 -1
- package/pp-core/workflows/secure-phase.md +1 -1
- package/pp-core/workflows/settings-advanced.md +1 -1
- package/pp-core/workflows/settings-integrations.md +1 -1
- package/pp-core/workflows/settings.md +1 -1
- package/pp-core/workflows/ship.md +25 -8
- package/pp-core/workflows/sketch-wrap-up.md +1 -1
- package/pp-core/workflows/sketch.md +2 -1
- package/pp-core/workflows/spec-phase.md +1 -1
- package/pp-core/workflows/spike-wrap-up.md +1 -1
- package/pp-core/workflows/spike.md +1 -1
- package/pp-core/workflows/stats.md +2 -2
- package/pp-core/workflows/thread.md +1 -1
- package/pp-core/workflows/transition.md +5 -9
- package/pp-core/workflows/ui-phase.md +23 -2
- package/pp-core/workflows/ui-review.md +1 -1
- package/pp-core/workflows/ultraplan-phase.md +2 -2
- package/pp-core/workflows/validate-phase.md +1 -1
- package/pp-core/workflows/vault-write-back.md +1 -1
- package/pp-core/workflows/verify-phase.md +29 -26
- package/pp-core/workflows/verify-work.md +14 -10
- package/scripts/base64-scan.sh +7 -41
- package/scripts/changeset/cli.cjs +25 -5
- package/scripts/dev-env.cjs +462 -0
- package/scripts/lib/allowlist-ratchet.cjs +103 -1
- package/scripts/lib/size-budget.cjs +311 -0
- package/scripts/lint-neutrality-names.cjs +694 -0
- package/scripts/lint-org-coordinates.allowlist.json +35 -0
- package/scripts/lint-org-coordinates.cjs +426 -0
- package/scripts/lint-skill-deps.cjs +48 -9
- package/scripts/lint-test-file-count.cjs +17 -2
- package/scripts/prompt-injection-scan.sh +41 -20
- package/scripts/sync-manifest-versions.cjs +41 -7
- package/scripts/sync-runtime-launcher.cjs +106 -1
- package/scripts/update-size-baseline.cjs +274 -30
- package/scripts/workflow-size.cjs +61 -9
- package/commands/pp-build-handoff.md +0 -46
- package/pp-core/workflows/execute-phase/steps/per-plan-worktree-gate.md +0 -94
|
@@ -15,6 +15,11 @@ exports.stateReplaceFieldWithFallback = stateReplaceFieldWithFallback;
|
|
|
15
15
|
exports.normalizeStateStatus = normalizeStateStatus;
|
|
16
16
|
exports.computeProgressPercent = computeProgressPercent;
|
|
17
17
|
exports.shouldPreserveExistingProgress = shouldPreserveExistingProgress;
|
|
18
|
+
exports.reconcileCompletedPhases = reconcileCompletedPhases;
|
|
19
|
+
exports.matchCurrentPositionSection = matchCurrentPositionSection;
|
|
20
|
+
exports.extractPositionalField = extractPositionalField;
|
|
21
|
+
exports.holdNewerActivityDate = holdNewerActivityDate;
|
|
22
|
+
exports.diffFrontmatterFields = diffFrontmatterFields;
|
|
18
23
|
exports.normalizeProgressNumbers = normalizeProgressNumbers;
|
|
19
24
|
exports.isStateTemplateDefault = isStateTemplateDefault;
|
|
20
25
|
exports.stateReplaceFieldIfTemplate = stateReplaceFieldIfTemplate;
|
|
@@ -76,17 +81,20 @@ function stateExtractField(content, fieldName) {
|
|
|
76
81
|
return tableMatch[4].trim();
|
|
77
82
|
return null;
|
|
78
83
|
}
|
|
84
|
+
// The value separator is `[ \t]*`, as in stateExtractField: `\s*` crossed a newline, so an
|
|
85
|
+
// EMPTY field consumed the next line. An empty field's prefix ends at the colon, so the
|
|
86
|
+
// writer supplies the single space the format puts there.
|
|
79
87
|
function stateReplaceField(content, fieldName, newValue) {
|
|
80
88
|
const escaped = escapeRegex(fieldName);
|
|
81
89
|
// Bold inline format: **FieldName:** value
|
|
82
|
-
const boldPattern = new RegExp(`(\\*\\*${escaped}
|
|
90
|
+
const boldPattern = new RegExp(`(\\*\\*${escaped}:\\*\\*[ \\t]*)(.*)`, 'i');
|
|
83
91
|
if (boldPattern.test(content)) {
|
|
84
|
-
return content.replace(boldPattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
92
|
+
return content.replace(boldPattern, (_match, prefix) => `${prefix}${/[ \t]$/.test(prefix) ? '' : ' '}${newValue}`);
|
|
85
93
|
}
|
|
86
94
|
// Plain line-start format: FieldName: value
|
|
87
|
-
const plainPattern = new RegExp(`(^${escaped}
|
|
95
|
+
const plainPattern = new RegExp(`(^${escaped}:[ \\t]*)(.*)`, 'im');
|
|
88
96
|
if (plainPattern.test(content)) {
|
|
89
|
-
return content.replace(plainPattern, (_match, prefix) => `${prefix}${newValue}`);
|
|
97
|
+
return content.replace(plainPattern, (_match, prefix) => `${prefix}${/[ \t]$/.test(prefix) ? '' : ' '}${newValue}`);
|
|
90
98
|
}
|
|
91
99
|
// Pipe-table format: | FieldName | value |
|
|
92
100
|
// Preserve the surrounding pipe/whitespace structure; only swap the value cell.
|
|
@@ -146,6 +154,20 @@ function computeProgressPercent(completedPlans, totalPlans, completedPhases, tot
|
|
|
146
154
|
const phaseFraction = hasPhaseData ? (completedPhases ?? 0) / (totalPhases ?? 1) : 1;
|
|
147
155
|
return Math.min(100, Math.round(Math.min(planFraction, phaseFraction) * 100));
|
|
148
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Decide whether a stored `progress` block must survive a re-derivation on the
|
|
159
|
+
* read path (`state json`).
|
|
160
|
+
*
|
|
161
|
+
* #3242 Bug A: a curated cross-milestone aggregate legitimately counts phases and
|
|
162
|
+
* plans the local disk scan cannot see, and its signature is a larger TOTAL — so a
|
|
163
|
+
* block whose `total_phases` or `total_plans` exceeds the derived one is kept whole.
|
|
164
|
+
*
|
|
165
|
+
* 261007-ixt D-1: a block whose totals do NOT exceed the derived ones but whose
|
|
166
|
+
* `completed_*` / `percent` are higher is not curation — it is the inflated count
|
|
167
|
+
* the writer used to store (a phase whose plans all have summaries, counted
|
|
168
|
+
* Complete while its ROADMAP row read `Built`). Preserving it would re-report the
|
|
169
|
+
* false number on every read, so the derived value wins.
|
|
170
|
+
*/
|
|
149
171
|
function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
|
|
150
172
|
if (!existingProgress || typeof existingProgress !== 'object')
|
|
151
173
|
return false;
|
|
@@ -154,9 +176,104 @@ function shouldPreserveExistingProgress(existingProgress, derivedProgress) {
|
|
|
154
176
|
const existing = existingProgress;
|
|
155
177
|
const derived = derivedProgress;
|
|
156
178
|
return (existingProgressExceedsDerived(existing, derived, 'total_phases') ||
|
|
157
|
-
existingProgressExceedsDerived(existing, derived, '
|
|
158
|
-
|
|
159
|
-
|
|
179
|
+
existingProgressExceedsDerived(existing, derived, 'total_plans'));
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* The ONE owner of `completed_phases` (261007-ixt D-1).
|
|
183
|
+
*
|
|
184
|
+
* The disk scan counts a phase complete when every PLAN has a SUMMARY — that is
|
|
185
|
+
* BUILT. The ROADMAP progress table's Status column says whether a phase is
|
|
186
|
+
* COMPLETE. When the table yields a count, the record may not claim more than it
|
|
187
|
+
* grants, so the result is the smaller of the two; when the table is silent
|
|
188
|
+
* (`null`), the disk count stands. A forged or stale table can only LOWER the
|
|
189
|
+
* count, never inflate it.
|
|
190
|
+
*/
|
|
191
|
+
function reconcileCompletedPhases(diskCompletedPhases, roadmapCompletedPhases) {
|
|
192
|
+
if (roadmapCompletedPhases === null || !Number.isFinite(roadmapCompletedPhases))
|
|
193
|
+
return diskCompletedPhases;
|
|
194
|
+
return Math.min(diskCompletedPhases, roadmapCompletedPhases);
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Match the `## Current Position` section of a STATE.md body. Group 1 is the
|
|
198
|
+
* section body (up to the next `##` heading or the end). The same pattern text
|
|
199
|
+
* `updateCurrentPositionFields` and the milestone/complete-phase writers use, so
|
|
200
|
+
* the reader and the writers agree on what the section is.
|
|
201
|
+
*/
|
|
202
|
+
function matchCurrentPositionSection(body) {
|
|
203
|
+
return body.match(/##\s*Current Position\s*\n([\s\S]*?)(?=\n##|$)/i);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Read a POSITIONAL source field (`Phase`, `Status`, `Last activity`) for the
|
|
207
|
+
* frontmatter writer (261007-ixt D-2).
|
|
208
|
+
*
|
|
209
|
+
* `stateExtractField` prefers a bold `**Field:**` ANYWHERE in the body over a
|
|
210
|
+
* line-start `Field:`, so a `**Status:**` in a historical log entry shadowed the
|
|
211
|
+
* `## Current Position` line the engine itself maintains. The position section is
|
|
212
|
+
* where the current position lives: read it first, and fall back to the whole
|
|
213
|
+
* body only when the section is absent or does not carry the field (so documents
|
|
214
|
+
* without the section keep their reading).
|
|
215
|
+
*/
|
|
216
|
+
function extractPositionalField(body, fieldName, fallbackFieldName) {
|
|
217
|
+
const section = matchCurrentPositionSection(body);
|
|
218
|
+
if (section) {
|
|
219
|
+
const scoped = stateExtractField(section[1], fieldName)
|
|
220
|
+
?? (fallbackFieldName ? stateExtractField(section[1], fallbackFieldName) : null);
|
|
221
|
+
if (scoped !== null)
|
|
222
|
+
return scoped;
|
|
223
|
+
}
|
|
224
|
+
return stateExtractField(body, fieldName)
|
|
225
|
+
?? (fallbackFieldName ? stateExtractField(body, fallbackFieldName) : null);
|
|
226
|
+
}
|
|
227
|
+
const ISO_DATE_PREFIX = /^(\d{4}-\d{2}-\d{2})/;
|
|
228
|
+
/**
|
|
229
|
+
* A record date never moves backwards (261007-ixt W3a). `last_activity` is re-derived
|
|
230
|
+
* from the body's `Last activity:` line on every write; when that line lags the
|
|
231
|
+
* frontmatter (measured on the pre-close v1.1 record: 2026-09-21 rewritten to
|
|
232
|
+
* 2026-09-20 by `state record-session`), the older date would overwrite the newer
|
|
233
|
+
* one. When both values start with an ISO date and the derived one is OLDER, the
|
|
234
|
+
* stored `last_activity` and its `last_activity_desc` are kept as a pair. Returns the
|
|
235
|
+
* frontmatter to write; the input is not modified.
|
|
236
|
+
*/
|
|
237
|
+
function holdNewerActivityDate(existingFm, derivedFm) {
|
|
238
|
+
const stored = typeof existingFm['last_activity'] === 'string' ? existingFm['last_activity'].match(ISO_DATE_PREFIX) : null;
|
|
239
|
+
const derived = typeof derivedFm['last_activity'] === 'string' ? derivedFm['last_activity'].match(ISO_DATE_PREFIX) : null;
|
|
240
|
+
if (!stored || !derived || derived[1] >= stored[1])
|
|
241
|
+
return derivedFm;
|
|
242
|
+
const held = { ...derivedFm, last_activity: existingFm['last_activity'] };
|
|
243
|
+
if (existingFm['last_activity_desc'] !== undefined)
|
|
244
|
+
held['last_activity_desc'] = existingFm['last_activity_desc'];
|
|
245
|
+
else
|
|
246
|
+
delete held['last_activity_desc'];
|
|
247
|
+
return held;
|
|
248
|
+
}
|
|
249
|
+
function flattenFrontmatterForDiff(fm) {
|
|
250
|
+
const out = {};
|
|
251
|
+
if (!fm || typeof fm !== 'object')
|
|
252
|
+
return out;
|
|
253
|
+
for (const [key, value] of Object.entries(fm)) {
|
|
254
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
255
|
+
for (const [sub, subValue] of Object.entries(value)) {
|
|
256
|
+
out[`${key}.${sub}`] = subValue;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
out[key] = value;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* The dotted frontmatter keys (one level of nesting flattened, e.g.
|
|
267
|
+
* `progress.percent`) whose value differs between two parsed frontmatter objects,
|
|
268
|
+
* sorted. Keys present on only one side are included; nothing is excluded —
|
|
269
|
+
* `last_updated` included — because a report that omits a key it changed is the
|
|
270
|
+
* defect 261007-ixt D-3 closes.
|
|
271
|
+
*/
|
|
272
|
+
function diffFrontmatterFields(before, after) {
|
|
273
|
+
const a = flattenFrontmatterForDiff(before);
|
|
274
|
+
const b = flattenFrontmatterForDiff(after);
|
|
275
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
276
|
+
return [...keys].filter((key) => JSON.stringify(a[key]) !== JSON.stringify(b[key])).sort();
|
|
160
277
|
}
|
|
161
278
|
function normalizeProgressNumbers(progress) {
|
|
162
279
|
if (!progress || typeof progress !== 'object')
|