qualia-framework 6.2.9 → 6.2.10
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/README.md +14 -11
- package/agents/builder.md +7 -7
- package/agents/planner.md +39 -3
- package/agents/research-synthesizer.md +1 -1
- package/agents/researcher.md +3 -3
- package/agents/roadmapper.md +7 -7
- package/agents/verifier.md +18 -6
- package/agents/visual-evaluator.md +8 -7
- package/bin/cli.js +111 -14
- package/bin/contract-runner.js +219 -0
- package/bin/host-adapters.js +66 -0
- package/bin/install.js +99 -152
- package/bin/plan-contract.js +99 -2
- package/bin/planning-hygiene.js +262 -0
- package/bin/runtime-manifest.js +32 -0
- package/bin/state-ledger.js +184 -0
- package/bin/state.js +299 -20
- package/bin/trust-score.js +276 -0
- package/docs/onboarding.html +5 -4
- package/guide.md +3 -2
- package/package.json +1 -1
- package/qualia-design/design-rubric.md +17 -5
- package/qualia-design/frontend.md +5 -1
- package/qualia-design/graphics.md +47 -0
- package/rules/command-output.md +35 -0
- package/skills/qualia/SKILL.md +10 -10
- package/skills/qualia-build/SKILL.md +20 -14
- package/skills/qualia-debug/SKILL.md +16 -8
- package/skills/qualia-discuss/SKILL.md +10 -10
- package/skills/qualia-doctor/SKILL.md +140 -0
- package/skills/qualia-feature/SKILL.md +23 -21
- package/skills/qualia-fix/SKILL.md +216 -0
- package/skills/qualia-flush/SKILL.md +9 -9
- package/skills/qualia-handoff/SKILL.md +9 -9
- package/skills/qualia-help/SKILL.md +3 -3
- package/skills/qualia-hook-gen/SKILL.md +1 -1
- package/skills/qualia-idk/SKILL.md +4 -4
- package/skills/qualia-issues/SKILL.md +2 -2
- package/skills/qualia-learn/SKILL.md +10 -10
- package/skills/qualia-map/SKILL.md +2 -2
- package/skills/qualia-milestone/SKILL.md +15 -15
- package/skills/qualia-new/REFERENCE.md +9 -9
- package/skills/qualia-new/SKILL.md +14 -14
- package/skills/qualia-optimize/REFERENCE.md +1 -1
- package/skills/qualia-optimize/SKILL.md +23 -16
- package/skills/qualia-pause/SKILL.md +2 -2
- package/skills/qualia-plan/SKILL.md +23 -13
- package/skills/qualia-polish/REFERENCE.md +14 -14
- package/skills/qualia-polish/SKILL.md +64 -19
- package/skills/qualia-polish/scripts/loop.mjs +3 -3
- package/skills/qualia-polish/scripts/score.mjs +9 -3
- package/skills/qualia-postmortem/SKILL.md +9 -9
- package/skills/qualia-report/SKILL.md +23 -23
- package/skills/qualia-research/SKILL.md +5 -5
- package/skills/qualia-resume/SKILL.md +4 -4
- package/skills/qualia-review/SKILL.md +28 -12
- package/skills/qualia-road/SKILL.md +18 -5
- package/skills/qualia-ship/SKILL.md +22 -22
- package/skills/qualia-skill-new/SKILL.md +13 -13
- package/skills/qualia-test/SKILL.md +5 -5
- package/skills/qualia-triage/SKILL.md +1 -1
- package/skills/qualia-verify/SKILL.md +37 -23
- package/skills/qualia-vibe/SKILL.md +13 -10
- package/skills/qualia-vibe/scripts/extract.mjs +1 -1
- package/skills/zoho-workflow/SKILL.md +1 -1
- package/templates/help.html +12 -10
- package/tests/bin.test.sh +34 -4
- package/tests/install-smoke.test.sh +22 -2
- package/tests/lib.test.sh +290 -0
- package/tests/runner.js +3 -0
- package/tests/skills.test.sh +4 -4
- package/tests/state.test.sh +65 -3
package/bin/state.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require("fs");
|
|
6
6
|
const path = require("path");
|
|
7
|
+
const stateLedger = require("./state-ledger.js");
|
|
7
8
|
|
|
8
9
|
const PLANNING = ".planning";
|
|
9
10
|
const STATE_FILE = path.join(PLANNING, "STATE.md");
|
|
@@ -191,6 +192,22 @@ function readTracking() {
|
|
|
191
192
|
}
|
|
192
193
|
}
|
|
193
194
|
|
|
195
|
+
function readTrackingRaw() {
|
|
196
|
+
try {
|
|
197
|
+
return fs.readFileSync(TRACKING_FILE, "utf8");
|
|
198
|
+
} catch {
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function parseTrackingRaw(raw) {
|
|
204
|
+
try {
|
|
205
|
+
return raw ? JSON.parse(raw) : null;
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
194
211
|
function writeTracking(t) {
|
|
195
212
|
atomicWrite(TRACKING_FILE, JSON.stringify(t, null, 2) + "\n");
|
|
196
213
|
}
|
|
@@ -484,6 +501,18 @@ function fail(error, message) {
|
|
|
484
501
|
return { ok: false, error, message };
|
|
485
502
|
}
|
|
486
503
|
|
|
504
|
+
function recordLedgerEvent(meta) {
|
|
505
|
+
try {
|
|
506
|
+
return stateLedger.append(process.cwd(), {
|
|
507
|
+
command: `state.js ${process.argv.slice(2).join(" ")}`.trim(),
|
|
508
|
+
...meta,
|
|
509
|
+
});
|
|
510
|
+
} catch (err) {
|
|
511
|
+
try { _trace("state-ledger", "error", { action: meta && meta.action, error: err.message }); } catch {}
|
|
512
|
+
return { ok: false, error: err.message };
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
487
516
|
// ─── Next Command Logic ──────────────────────────────────
|
|
488
517
|
function nextCommand(status, phase, totalPhases, verification) {
|
|
489
518
|
switch (status) {
|
|
@@ -687,8 +716,10 @@ function cmdTransition(opts) {
|
|
|
687
716
|
const target = opts.to;
|
|
688
717
|
if (!target) return output(fail("MISSING_ARG", "--to is required"));
|
|
689
718
|
|
|
690
|
-
const
|
|
691
|
-
const
|
|
719
|
+
const beforeStateRaw = readState();
|
|
720
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
721
|
+
const t = parseTrackingRaw(beforeTrackingRaw);
|
|
722
|
+
const s = parseStateMd(beforeStateRaw);
|
|
692
723
|
if (!t || !s) {
|
|
693
724
|
return output(fail("NO_PROJECT", "No .planning/ found. Run /qualia-new."));
|
|
694
725
|
}
|
|
@@ -705,7 +736,31 @@ function cmdTransition(opts) {
|
|
|
705
736
|
|
|
706
737
|
// Note/activity short-circuit (no status change, no precondition check)
|
|
707
738
|
const noteResult = applyNoteOrActivity(target, s, t, opts);
|
|
708
|
-
if (noteResult)
|
|
739
|
+
if (noteResult) {
|
|
740
|
+
const ledger = recordLedgerEvent({
|
|
741
|
+
action: target,
|
|
742
|
+
phase_before: s.phase,
|
|
743
|
+
phase_after: s.phase,
|
|
744
|
+
status_before: s.status,
|
|
745
|
+
status_after: s.status,
|
|
746
|
+
state_before: s,
|
|
747
|
+
state_after: s,
|
|
748
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
749
|
+
tracking_after: t,
|
|
750
|
+
state_raw_before: beforeStateRaw,
|
|
751
|
+
state_raw_after: readState(),
|
|
752
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
753
|
+
tracking_raw_after: readTrackingRaw(),
|
|
754
|
+
evidence_refs: opts.evidence ? [opts.evidence] : [],
|
|
755
|
+
});
|
|
756
|
+
if (ledger.ok) {
|
|
757
|
+
noteResult.ledger_event_id = ledger.event_id;
|
|
758
|
+
noteResult.ledger_event_hash = ledger.event_hash;
|
|
759
|
+
} else {
|
|
760
|
+
noteResult.ledger_error = ledger.error;
|
|
761
|
+
}
|
|
762
|
+
return output(noteResult);
|
|
763
|
+
}
|
|
709
764
|
|
|
710
765
|
const phase = parseInt(opts.phase) || s.phase;
|
|
711
766
|
|
|
@@ -746,6 +801,23 @@ function cmdTransition(opts) {
|
|
|
746
801
|
const writeError = commitTransitionAtomic(s, t);
|
|
747
802
|
if (writeError) return output(writeError);
|
|
748
803
|
|
|
804
|
+
const ledger = recordLedgerEvent({
|
|
805
|
+
action: target,
|
|
806
|
+
phase_before: phase,
|
|
807
|
+
phase_after: s.phase,
|
|
808
|
+
status_before: prevStatus,
|
|
809
|
+
status_after: s.status,
|
|
810
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
811
|
+
state_after: s,
|
|
812
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
813
|
+
tracking_after: t,
|
|
814
|
+
state_raw_before: beforeStateRaw,
|
|
815
|
+
state_raw_after: readState(),
|
|
816
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
817
|
+
tracking_raw_after: readTrackingRaw(),
|
|
818
|
+
evidence_refs: opts.evidence ? [opts.evidence] : [],
|
|
819
|
+
});
|
|
820
|
+
|
|
749
821
|
// Trace transition for analytics
|
|
750
822
|
_trace("state-transition", "allow", {
|
|
751
823
|
phase: s.phase,
|
|
@@ -755,7 +827,7 @@ function cmdTransition(opts) {
|
|
|
755
827
|
gap_closure: prevStatus === "verified" && target === "planned",
|
|
756
828
|
});
|
|
757
829
|
|
|
758
|
-
|
|
830
|
+
const result = {
|
|
759
831
|
ok: true,
|
|
760
832
|
phase: s.phase,
|
|
761
833
|
phase_name: s.phase_name,
|
|
@@ -764,7 +836,14 @@ function cmdTransition(opts) {
|
|
|
764
836
|
verification: t.verification,
|
|
765
837
|
gap_cycles: (t.gap_cycles || {})[String(s.phase)] || 0,
|
|
766
838
|
next_command: nextCommand(s.status, s.phase, s.total_phases, t.verification),
|
|
767
|
-
}
|
|
839
|
+
};
|
|
840
|
+
if (ledger.ok) {
|
|
841
|
+
result.ledger_event_id = ledger.event_id;
|
|
842
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
843
|
+
} else {
|
|
844
|
+
result.ledger_error = ledger.error;
|
|
845
|
+
}
|
|
846
|
+
output(result);
|
|
768
847
|
}
|
|
769
848
|
|
|
770
849
|
function cmdInit(opts) {
|
|
@@ -825,6 +904,8 @@ function cmdInit(opts) {
|
|
|
825
904
|
const date = now.split("T")[0];
|
|
826
905
|
|
|
827
906
|
// Read existing tracking for lifetime data preservation across milestone resets
|
|
907
|
+
const beforeStateRaw = readState();
|
|
908
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
828
909
|
const prev = readTracking();
|
|
829
910
|
const prevLife = prev ? ensureLifetime(prev) : null;
|
|
830
911
|
|
|
@@ -906,7 +987,23 @@ function cmdInit(opts) {
|
|
|
906
987
|
writeStateMd(s);
|
|
907
988
|
writeTracking(t);
|
|
908
989
|
|
|
909
|
-
|
|
990
|
+
const ledger = recordLedgerEvent({
|
|
991
|
+
action: "init",
|
|
992
|
+
phase_before: null,
|
|
993
|
+
phase_after: 1,
|
|
994
|
+
status_before: null,
|
|
995
|
+
status_after: "setup",
|
|
996
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
997
|
+
state_after: s,
|
|
998
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
999
|
+
tracking_after: t,
|
|
1000
|
+
state_raw_before: beforeStateRaw,
|
|
1001
|
+
state_raw_after: readState(),
|
|
1002
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1003
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
const result = {
|
|
910
1007
|
ok: true,
|
|
911
1008
|
action: "init",
|
|
912
1009
|
project: opts.project,
|
|
@@ -914,10 +1011,19 @@ function cmdInit(opts) {
|
|
|
914
1011
|
total_phases: totalPhases,
|
|
915
1012
|
status: "setup",
|
|
916
1013
|
next_command: "/qualia-plan 1",
|
|
917
|
-
}
|
|
1014
|
+
};
|
|
1015
|
+
if (ledger.ok) {
|
|
1016
|
+
result.ledger_event_id = ledger.event_id;
|
|
1017
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1018
|
+
} else {
|
|
1019
|
+
result.ledger_error = ledger.error;
|
|
1020
|
+
}
|
|
1021
|
+
output(result);
|
|
918
1022
|
}
|
|
919
1023
|
|
|
920
1024
|
function cmdFix(opts) {
|
|
1025
|
+
const beforeStateRaw = readState();
|
|
1026
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
921
1027
|
const raw = readState();
|
|
922
1028
|
const t = readTracking();
|
|
923
1029
|
if (!raw && !t) {
|
|
@@ -980,17 +1086,41 @@ function cmdFix(opts) {
|
|
|
980
1086
|
return output(fail("WRITE_ERROR", e.message));
|
|
981
1087
|
}
|
|
982
1088
|
|
|
983
|
-
|
|
1089
|
+
const ledger = recordLedgerEvent({
|
|
1090
|
+
action: "fix",
|
|
1091
|
+
phase_before: parsed.phase,
|
|
1092
|
+
phase_after: s.phase,
|
|
1093
|
+
status_before: parsed.status,
|
|
1094
|
+
status_after: s.status,
|
|
1095
|
+
state_before: parsed,
|
|
1096
|
+
state_after: s,
|
|
1097
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1098
|
+
tracking_after: t,
|
|
1099
|
+
state_raw_before: beforeStateRaw,
|
|
1100
|
+
state_raw_after: readState(),
|
|
1101
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1102
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1103
|
+
});
|
|
1104
|
+
|
|
1105
|
+
const result = {
|
|
984
1106
|
ok: true,
|
|
985
1107
|
action: "fix",
|
|
986
1108
|
previous_errors: previousErrors,
|
|
987
1109
|
fixed: true,
|
|
988
|
-
}
|
|
1110
|
+
};
|
|
1111
|
+
if (ledger.ok) {
|
|
1112
|
+
result.ledger_event_id = ledger.event_id;
|
|
1113
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1114
|
+
} else {
|
|
1115
|
+
result.ledger_error = ledger.error;
|
|
1116
|
+
}
|
|
1117
|
+
output(result);
|
|
989
1118
|
}
|
|
990
1119
|
|
|
991
1120
|
function cmdValidatePlan(opts) {
|
|
992
1121
|
const phase = parseInt(opts.phase) || 1;
|
|
993
1122
|
const planFile = path.join(PLANNING, `phase-${phase}-plan.md`);
|
|
1123
|
+
const contractFile = path.join(PLANNING, `phase-${phase}-contract.json`);
|
|
994
1124
|
|
|
995
1125
|
if (!fs.existsSync(planFile)) {
|
|
996
1126
|
return output(fail("MISSING_FILE", `Plan file not found: ${planFile}`));
|
|
@@ -1036,6 +1166,47 @@ function cmdValidatePlan(opts) {
|
|
|
1036
1166
|
const warnings = [];
|
|
1037
1167
|
const VALID_CHECK_TYPES = ["file-exists", "grep-match", "command-exit", "behavioral"];
|
|
1038
1168
|
let contractCount = 0;
|
|
1169
|
+
let contractStatus = "missing";
|
|
1170
|
+
let contractErrors = [];
|
|
1171
|
+
|
|
1172
|
+
// JSON contracts are the new machine-readable trust layer. Keep missing
|
|
1173
|
+
// contracts as a warning by default for in-flight legacy projects; callers can
|
|
1174
|
+
// opt into the hard gate with --require-contract.
|
|
1175
|
+
try {
|
|
1176
|
+
const pc = require("./plan-contract.js");
|
|
1177
|
+
if (fs.existsSync(contractFile)) {
|
|
1178
|
+
const loaded = pc.readContractFile(contractFile);
|
|
1179
|
+
if (!loaded.ok) {
|
|
1180
|
+
contractStatus = "invalid";
|
|
1181
|
+
contractErrors = [loaded.message || loaded.error || "contract unreadable"];
|
|
1182
|
+
} else {
|
|
1183
|
+
contractErrors = pc.validate(loaded.contract);
|
|
1184
|
+
const drift = pc.checkDrift(contractFile, planFile);
|
|
1185
|
+
if (contractErrors.length > 0) {
|
|
1186
|
+
contractStatus = "invalid";
|
|
1187
|
+
} else if (drift.ok && drift.drift) {
|
|
1188
|
+
contractStatus = "drifted";
|
|
1189
|
+
contractErrors = [`source_plan_hash drift: stored ${drift.stored}, current ${drift.current}`];
|
|
1190
|
+
} else {
|
|
1191
|
+
contractStatus = "valid";
|
|
1192
|
+
contractCount = Array.isArray(loaded.contract.tasks) ? loaded.contract.tasks.length : 0;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
} catch (e) {
|
|
1197
|
+
contractStatus = "unavailable";
|
|
1198
|
+
contractErrors = [e.message];
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
if (contractStatus === "missing") {
|
|
1202
|
+
const msg = `JSON contract missing: ${contractFile}`;
|
|
1203
|
+
if (opts.require_contract) errors.push(msg);
|
|
1204
|
+
else warnings.push(msg);
|
|
1205
|
+
} else if (contractStatus !== "valid") {
|
|
1206
|
+
const msg = `JSON contract ${contractStatus}: ${contractErrors.join("; ")}`;
|
|
1207
|
+
if (opts.require_contract) errors.push(msg);
|
|
1208
|
+
else warnings.push(msg);
|
|
1209
|
+
}
|
|
1039
1210
|
|
|
1040
1211
|
if (/^## Verification Contract/m.test(content)) {
|
|
1041
1212
|
// Extract the contract section (from header to next ## or end of file)
|
|
@@ -1049,9 +1220,10 @@ function cmdValidatePlan(opts) {
|
|
|
1049
1220
|
if (nextH2 !== -1) contractSection = contractSection.substring(0, nextH2);
|
|
1050
1221
|
// Each contract starts with ### Contract for Task N
|
|
1051
1222
|
const contractBlocks = contractSection.match(/^### Contract for Task \d+/gm);
|
|
1052
|
-
|
|
1223
|
+
const markdownContractCount = contractBlocks ? contractBlocks.length : 0;
|
|
1224
|
+
if (contractStatus !== "valid") contractCount = markdownContractCount;
|
|
1053
1225
|
|
|
1054
|
-
if (
|
|
1226
|
+
if (markdownContractCount === 0) {
|
|
1055
1227
|
warnings.push("Verification Contract section exists but contains no contract blocks (expected '### Contract for Task N')");
|
|
1056
1228
|
} else {
|
|
1057
1229
|
// Split into individual contract blocks for validation
|
|
@@ -1092,9 +1264,9 @@ function cmdValidatePlan(opts) {
|
|
|
1092
1264
|
}
|
|
1093
1265
|
|
|
1094
1266
|
// Warn if contract count < task count
|
|
1095
|
-
if (taskCount > 0 &&
|
|
1267
|
+
if (taskCount > 0 && markdownContractCount > 0 && markdownContractCount < taskCount) {
|
|
1096
1268
|
warnings.push(
|
|
1097
|
-
`Only ${
|
|
1269
|
+
`Only ${markdownContractCount} Markdown contract(s) for ${taskCount} task(s) — not all tasks have verification contracts`
|
|
1098
1270
|
);
|
|
1099
1271
|
}
|
|
1100
1272
|
}
|
|
@@ -1119,6 +1291,9 @@ function cmdValidatePlan(opts) {
|
|
|
1119
1291
|
done_when_count: doneWhenCount,
|
|
1120
1292
|
ac_count: acCount,
|
|
1121
1293
|
contract_count: contractCount,
|
|
1294
|
+
contract_file: contractFile,
|
|
1295
|
+
contract_status: contractStatus,
|
|
1296
|
+
contract_errors: contractErrors.length > 0 ? contractErrors : undefined,
|
|
1122
1297
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1123
1298
|
});
|
|
1124
1299
|
}
|
|
@@ -1129,6 +1304,8 @@ function cmdValidatePlan(opts) {
|
|
|
1129
1304
|
// hiccup) does NOT double-count. To re-close a milestone deliberately, pass
|
|
1130
1305
|
// --force.
|
|
1131
1306
|
function cmdCloseMilestone(opts) {
|
|
1307
|
+
const beforeStateRaw = readState();
|
|
1308
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1132
1309
|
const t = readTracking();
|
|
1133
1310
|
const s = parseStateMd(readState());
|
|
1134
1311
|
if (!t || !s) {
|
|
@@ -1252,19 +1429,44 @@ function cmdCloseMilestone(opts) {
|
|
|
1252
1429
|
lifetime: t.lifetime,
|
|
1253
1430
|
});
|
|
1254
1431
|
|
|
1255
|
-
|
|
1432
|
+
const ledger = recordLedgerEvent({
|
|
1433
|
+
action: "close-milestone",
|
|
1434
|
+
phase_before: s.phase,
|
|
1435
|
+
phase_after: s.phase,
|
|
1436
|
+
status_before: s.status,
|
|
1437
|
+
status_after: s.status,
|
|
1438
|
+
state_before: s,
|
|
1439
|
+
state_after: s,
|
|
1440
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1441
|
+
tracking_after: t,
|
|
1442
|
+
state_raw_before: beforeStateRaw,
|
|
1443
|
+
state_raw_after: readState(),
|
|
1444
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1445
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1446
|
+
});
|
|
1447
|
+
|
|
1448
|
+
const result = {
|
|
1256
1449
|
ok: true,
|
|
1257
1450
|
action: "close-milestone",
|
|
1258
1451
|
closed_milestone: closedMilestone,
|
|
1259
1452
|
next_milestone: t.milestone,
|
|
1260
1453
|
lifetime: t.lifetime,
|
|
1261
|
-
}
|
|
1454
|
+
};
|
|
1455
|
+
if (ledger.ok) {
|
|
1456
|
+
result.ledger_event_id = ledger.event_id;
|
|
1457
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1458
|
+
} else {
|
|
1459
|
+
result.ledger_error = ledger.error;
|
|
1460
|
+
}
|
|
1461
|
+
output(result);
|
|
1262
1462
|
}
|
|
1263
1463
|
|
|
1264
1464
|
// ─── Backfill Lifetime ───────────────────────────────────
|
|
1265
1465
|
// Reconstructs lifetime counters from STATE.md roadmap + plan files.
|
|
1266
1466
|
// Safe to run multiple times (idempotent — recalculates from source).
|
|
1267
1467
|
function cmdBackfillLifetime(opts) {
|
|
1468
|
+
const beforeStateRaw = readState();
|
|
1469
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1268
1470
|
const t = readTracking();
|
|
1269
1471
|
const s = parseStateMd(readState());
|
|
1270
1472
|
if (!t || !s) {
|
|
@@ -1327,7 +1529,23 @@ function cmdBackfillLifetime(opts) {
|
|
|
1327
1529
|
phases_scanned: s.phases.length,
|
|
1328
1530
|
});
|
|
1329
1531
|
|
|
1330
|
-
|
|
1532
|
+
const ledger = recordLedgerEvent({
|
|
1533
|
+
action: "backfill-lifetime",
|
|
1534
|
+
phase_before: s.phase,
|
|
1535
|
+
phase_after: s.phase,
|
|
1536
|
+
status_before: s.status,
|
|
1537
|
+
status_after: s.status,
|
|
1538
|
+
state_before: s,
|
|
1539
|
+
state_after: s,
|
|
1540
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1541
|
+
tracking_after: t,
|
|
1542
|
+
state_raw_before: beforeStateRaw,
|
|
1543
|
+
state_raw_after: readState(),
|
|
1544
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1545
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1546
|
+
});
|
|
1547
|
+
|
|
1548
|
+
const result = {
|
|
1331
1549
|
ok: true,
|
|
1332
1550
|
action: "backfill-lifetime",
|
|
1333
1551
|
previous,
|
|
@@ -1335,7 +1553,14 @@ function cmdBackfillLifetime(opts) {
|
|
|
1335
1553
|
phases_scanned: s.phases.length,
|
|
1336
1554
|
phases_completed: phasesCompleted,
|
|
1337
1555
|
tasks_completed: tasksCompleted,
|
|
1338
|
-
}
|
|
1556
|
+
};
|
|
1557
|
+
if (ledger.ok) {
|
|
1558
|
+
result.ledger_event_id = ledger.event_id;
|
|
1559
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1560
|
+
} else {
|
|
1561
|
+
result.ledger_error = ledger.error;
|
|
1562
|
+
}
|
|
1563
|
+
output(result);
|
|
1339
1564
|
}
|
|
1340
1565
|
|
|
1341
1566
|
// ─── Backfill Milestones from JOURNEY.md ─────────────────
|
|
@@ -1353,6 +1578,8 @@ function cmdBackfillLifetime(opts) {
|
|
|
1353
1578
|
// Phase counting handles ranges (`1–13` → 13), comma lists (`14, 15, 16.1–16.6` → 8),
|
|
1354
1579
|
// and "rolling" / "—" / "-" → 0.
|
|
1355
1580
|
function cmdBackfillMilestones(opts) {
|
|
1581
|
+
const beforeStateRaw = readState();
|
|
1582
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1356
1583
|
const t = readTracking();
|
|
1357
1584
|
if (!t) return output(fail("NO_PROJECT", "No .planning/ found."));
|
|
1358
1585
|
ensureLifetime(t);
|
|
@@ -1507,7 +1734,24 @@ function cmdBackfillMilestones(opts) {
|
|
|
1507
1734
|
lifetime: t.lifetime,
|
|
1508
1735
|
});
|
|
1509
1736
|
|
|
1510
|
-
|
|
1737
|
+
const afterState = parseStateMd(readState());
|
|
1738
|
+
const ledger = recordLedgerEvent({
|
|
1739
|
+
action: "backfill-milestones",
|
|
1740
|
+
phase_before: afterState ? afterState.phase : undefined,
|
|
1741
|
+
phase_after: afterState ? afterState.phase : undefined,
|
|
1742
|
+
status_before: afterState ? afterState.status : undefined,
|
|
1743
|
+
status_after: afterState ? afterState.status : undefined,
|
|
1744
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
1745
|
+
state_after: afterState,
|
|
1746
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1747
|
+
tracking_after: t,
|
|
1748
|
+
state_raw_before: beforeStateRaw,
|
|
1749
|
+
state_raw_after: readState(),
|
|
1750
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1751
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
const result = {
|
|
1511
1755
|
ok: true,
|
|
1512
1756
|
action: "backfill-milestones",
|
|
1513
1757
|
added,
|
|
@@ -1515,7 +1759,14 @@ function cmdBackfillMilestones(opts) {
|
|
|
1515
1759
|
closed: closedSummaries,
|
|
1516
1760
|
open_milestone: openRow ? { num: openRow.num, name: openRow.name } : null,
|
|
1517
1761
|
lifetime: t.lifetime,
|
|
1518
|
-
}
|
|
1762
|
+
};
|
|
1763
|
+
if (ledger.ok) {
|
|
1764
|
+
result.ledger_event_id = ledger.event_id;
|
|
1765
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1766
|
+
} else {
|
|
1767
|
+
result.ledger_error = ledger.error;
|
|
1768
|
+
}
|
|
1769
|
+
output(result);
|
|
1519
1770
|
}
|
|
1520
1771
|
|
|
1521
1772
|
// ─── Next Report ID ──────────────────────────────────────
|
|
@@ -1525,6 +1776,8 @@ function cmdBackfillMilestones(opts) {
|
|
|
1525
1776
|
// to the ERP. If --peek is passed, the next id is returned WITHOUT
|
|
1526
1777
|
// incrementing — useful for --dry-run previews.
|
|
1527
1778
|
function cmdNextReportId(opts) {
|
|
1779
|
+
const beforeStateRaw = readState();
|
|
1780
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1528
1781
|
const t = readTracking();
|
|
1529
1782
|
if (!t) return output(fail("NO_PROJECT", "No .planning/ found."));
|
|
1530
1783
|
ensureLifetime(t);
|
|
@@ -1536,7 +1789,33 @@ function cmdNextReportId(opts) {
|
|
|
1536
1789
|
t.last_updated = new Date().toISOString();
|
|
1537
1790
|
writeTracking(t);
|
|
1538
1791
|
}
|
|
1539
|
-
|
|
1792
|
+
const result = { ok: true, action: "next-report-id", report_id: id, report_seq: next, peeked: peek };
|
|
1793
|
+
if (!peek) {
|
|
1794
|
+
const afterState = parseStateMd(readState());
|
|
1795
|
+
const beforeState = parseStateMd(beforeStateRaw);
|
|
1796
|
+
const ledger = recordLedgerEvent({
|
|
1797
|
+
action: "next-report-id",
|
|
1798
|
+
phase_before: beforeState ? beforeState.phase : undefined,
|
|
1799
|
+
phase_after: afterState ? afterState.phase : undefined,
|
|
1800
|
+
status_before: beforeState ? beforeState.status : undefined,
|
|
1801
|
+
status_after: afterState ? afterState.status : undefined,
|
|
1802
|
+
state_before: beforeState,
|
|
1803
|
+
state_after: afterState,
|
|
1804
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1805
|
+
tracking_after: t,
|
|
1806
|
+
state_raw_before: beforeStateRaw,
|
|
1807
|
+
state_raw_after: readState(),
|
|
1808
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1809
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1810
|
+
});
|
|
1811
|
+
if (ledger.ok) {
|
|
1812
|
+
result.ledger_event_id = ledger.event_id;
|
|
1813
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1814
|
+
} else {
|
|
1815
|
+
result.ledger_error = ledger.error;
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
output(result);
|
|
1540
1819
|
}
|
|
1541
1820
|
|
|
1542
1821
|
// ─── Output ──────────────────────────────────────────────
|