workstream progress trusted the mutable STATE.md `Status` field, so a shipped/archived milestone whose field was left at `executing` was reported as executing — a stale hand-maintained field became the source of truth instead of the authoritative archive/tag/ROADMAP signals. Derive status in the inventory builder from a milestoneShipped signal (archived milestone snapshot under milestones/, or a SHIPPED marker in the workstream ROADMAP), collected by inspectWorkstream. The inventory now reports `status_source` (field|derived) and `status_conflict` (true when the derived value disagrees with the stale field), and a shipped workstream is never reported executing. - src/workstream-inventory-builder.cts: milestoneShipped input + status_source/status_conflict outputs + derivation - src/workstream-inventory.cts: workstreamMilestoneShipped() signal detector wired into inspectWorkstream - tests/workstream-inventory.test.cjs: regression (builder unit + inspectWorkstream integration + negative) Closes #1913
This commit is contained in:
@@ -84,6 +84,30 @@ function readStateProjection(statePath: string): StateProjection {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* #1913: detect an authoritative shipped signal for a workstream so the
|
||||
* inventory status is never trusted from the mutable STATE.md `Status` field
|
||||
* alone. Returns true when EITHER an archived milestone snapshot is present
|
||||
* under `<planningBase>/milestones/` OR the workstream ROADMAP carries a
|
||||
* SHIPPED marker — both are hard to desync, unlike the hand-maintained field.
|
||||
*/
|
||||
function workstreamMilestoneShipped(roadmapPath: string, planningBase: string): boolean {
|
||||
try {
|
||||
const milestonesDir = path.join(planningBase, 'milestones');
|
||||
for (const entry of fs.readdirSync(milestonesDir, { withFileTypes: true })) {
|
||||
if (entry.isFile() && /-ROADMAP\.md$/i.test(entry.name)) return true;
|
||||
}
|
||||
} catch {
|
||||
/* no milestones archive dir */
|
||||
}
|
||||
try {
|
||||
if (/SHIPPED/i.test(fs.readFileSync(roadmapPath, 'utf-8'))) return true;
|
||||
} catch {
|
||||
/* no roadmap */
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function sortWorkstreamInventories(inventories: WorkstreamInventory[], activeWorkstreamName: string | null): WorkstreamInventory[] {
|
||||
return [...inventories].sort((a, b) => {
|
||||
const aActive = a.name === activeWorkstreamName ? 1 : 0;
|
||||
@@ -123,6 +147,7 @@ function inspectWorkstream(cwd: string, name: string, options: InspectWorkstream
|
||||
state: fs.existsSync(p.state),
|
||||
requirements: fs.existsSync(p.requirements),
|
||||
},
|
||||
milestoneShipped: workstreamMilestoneShipped(p.roadmap, p.planning),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user