qualia-framework 6.2.9 → 6.3.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/AGENTS.md +1 -0
- package/CLAUDE.md +1 -0
- package/README.md +26 -30
- 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 +160 -16
- package/bin/command-surface.js +71 -0
- package/bin/contract-runner.js +219 -0
- package/bin/harness-eval.js +296 -0
- package/bin/host-adapters.js +66 -0
- package/bin/install.js +116 -172
- package/bin/knowledge-flush.js +21 -10
- package/bin/knowledge.js +1 -1
- package/bin/plan-contract.js +99 -2
- package/bin/planning-hygiene.js +262 -0
- package/bin/project-snapshot.js +20 -0
- package/bin/report-payload.js +18 -0
- package/bin/runtime-manifest.js +35 -0
- package/bin/state-ledger.js +184 -0
- package/bin/state.js +330 -20
- package/bin/trust-score.js +268 -0
- package/bin/work-packet.js +228 -0
- package/docs/erp-contract.md +81 -1
- package/docs/onboarding.html +4 -14
- package/guide.md +16 -16
- package/hooks/fawzi-approval-guard.js +143 -0
- package/hooks/pre-deploy-gate.js +74 -1
- package/hooks/session-start.js +29 -1
- package/package.json +1 -1
- package/qualia-design/design-rubric.md +17 -5
- package/qualia-design/frontend.md +6 -2
- package/qualia-design/graphics.md +47 -0
- package/rules/codex-goal.md +1 -1
- package/rules/command-output.md +35 -0
- package/rules/one-opinion.md +2 -2
- package/rules/speed.md +0 -1
- package/skills/qualia/SKILL.md +12 -12
- package/skills/qualia-build/SKILL.md +20 -14
- package/skills/qualia-discuss/SKILL.md +10 -10
- package/skills/qualia-doctor/SKILL.md +140 -0
- package/skills/qualia-feature/SKILL.md +24 -22
- package/skills/qualia-fix/SKILL.md +216 -0
- package/skills/qualia-handoff/SKILL.md +9 -9
- package/skills/qualia-learn/SKILL.md +11 -11
- 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-plan/SKILL.md +23 -13
- package/skills/qualia-polish/REFERENCE.md +15 -15
- package/skills/qualia-polish/SKILL.md +81 -21
- package/skills/qualia-polish/scripts/loop.mjs +3 -3
- package/skills/qualia-polish/scripts/score.mjs +9 -3
- package/skills/{qualia-vibe/scripts/extract.mjs → qualia-polish/scripts/vibe-extract.mjs} +5 -5
- package/skills/{qualia-vibe/scripts/tokens.mjs → qualia-polish/scripts/vibe-tokens.mjs} +6 -6
- 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-review/SKILL.md +28 -12
- package/skills/qualia-road/SKILL.md +30 -22
- package/skills/qualia-ship/SKILL.md +31 -24
- package/skills/qualia-test/SKILL.md +5 -5
- package/skills/qualia-verify/SKILL.md +45 -23
- package/skills/zoho-workflow/SKILL.md +1 -1
- package/templates/help.html +11 -20
- package/tests/bin.test.sh +178 -76
- package/tests/hooks.test.sh +81 -1
- package/tests/install-smoke.test.sh +35 -5
- package/tests/lib.test.sh +432 -0
- package/tests/published-install-smoke.test.sh +4 -3
- package/tests/refs.test.sh +9 -4
- package/tests/runner.js +32 -28
- package/tests/skills.test.sh +4 -4
- package/tests/state.test.sh +133 -3
- package/skills/qualia-debug/SKILL.md +0 -185
- package/skills/qualia-flush/SKILL.md +0 -198
- package/skills/qualia-help/SKILL.md +0 -74
- package/skills/qualia-hook-gen/SKILL.md +0 -206
- package/skills/qualia-idk/SKILL.md +0 -166
- package/skills/qualia-issues/SKILL.md +0 -151
- package/skills/qualia-pause/SKILL.md +0 -68
- package/skills/qualia-resume/SKILL.md +0 -52
- package/skills/qualia-skill-new/SKILL.md +0 -173
- package/skills/qualia-triage/SKILL.md +0 -152
- package/skills/qualia-vibe/SKILL.md +0 -226
- package/skills/qualia-zoom/SKILL.md +0 -51
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
|
}
|
|
@@ -451,6 +468,14 @@ function checkPreconditions(current, target, opts) {
|
|
|
451
468
|
return fail("MISSING_FILE", `Verification file not found: ${vFile}`);
|
|
452
469
|
if (!opts.verification || !["pass", "fail"].includes(opts.verification))
|
|
453
470
|
return fail("MISSING_ARG", "--verification must be 'pass' or 'fail'");
|
|
471
|
+
if (opts.verification === "pass") {
|
|
472
|
+
const vContent = fs.readFileSync(vFile, "utf8");
|
|
473
|
+
if (/\bINSUFFICIENT EVIDENCE\b/.test(vContent)) {
|
|
474
|
+
return fail("INSUFFICIENT_EVIDENCE", `${vFile} contains INSUFFICIENT EVIDENCE; PASS is not allowed`);
|
|
475
|
+
}
|
|
476
|
+
const evidenceCheck = checkMachineEvidence(phase);
|
|
477
|
+
if (!evidenceCheck.ok) return evidenceCheck;
|
|
478
|
+
}
|
|
454
479
|
}
|
|
455
480
|
|
|
456
481
|
if (target === "shipped") {
|
|
@@ -484,6 +509,41 @@ function fail(error, message) {
|
|
|
484
509
|
return { ok: false, error, message };
|
|
485
510
|
}
|
|
486
511
|
|
|
512
|
+
function checkMachineEvidence(phase) {
|
|
513
|
+
const contractFile = path.join(PLANNING, `phase-${phase}-contract.json`);
|
|
514
|
+
if (!fs.existsSync(contractFile)) return { ok: true };
|
|
515
|
+
|
|
516
|
+
const evidenceFile = path.join(PLANNING, "evidence", `phase-${phase}-contract-run.json`);
|
|
517
|
+
if (!fs.existsSync(evidenceFile)) {
|
|
518
|
+
return fail(
|
|
519
|
+
"MISSING_EVIDENCE",
|
|
520
|
+
`Contract exists for phase ${phase}, but machine evidence is missing: ${evidenceFile}. Run contract-runner.js or qualia-framework eval --run --write.`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
let evidence;
|
|
524
|
+
try {
|
|
525
|
+
evidence = JSON.parse(fs.readFileSync(evidenceFile, "utf8"));
|
|
526
|
+
} catch (e) {
|
|
527
|
+
return fail("INVALID_EVIDENCE", `Could not parse ${evidenceFile}: ${e.message}`);
|
|
528
|
+
}
|
|
529
|
+
if (!evidence || evidence.ok !== true) {
|
|
530
|
+
return fail("FAILING_EVIDENCE", `${evidenceFile} does not prove the contract passed`);
|
|
531
|
+
}
|
|
532
|
+
return { ok: true };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function recordLedgerEvent(meta) {
|
|
536
|
+
try {
|
|
537
|
+
return stateLedger.append(process.cwd(), {
|
|
538
|
+
command: `state.js ${process.argv.slice(2).join(" ")}`.trim(),
|
|
539
|
+
...meta,
|
|
540
|
+
});
|
|
541
|
+
} catch (err) {
|
|
542
|
+
try { _trace("state-ledger", "error", { action: meta && meta.action, error: err.message }); } catch {}
|
|
543
|
+
return { ok: false, error: err.message };
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
487
547
|
// ─── Next Command Logic ──────────────────────────────────
|
|
488
548
|
function nextCommand(status, phase, totalPhases, verification) {
|
|
489
549
|
switch (status) {
|
|
@@ -687,8 +747,10 @@ function cmdTransition(opts) {
|
|
|
687
747
|
const target = opts.to;
|
|
688
748
|
if (!target) return output(fail("MISSING_ARG", "--to is required"));
|
|
689
749
|
|
|
690
|
-
const
|
|
691
|
-
const
|
|
750
|
+
const beforeStateRaw = readState();
|
|
751
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
752
|
+
const t = parseTrackingRaw(beforeTrackingRaw);
|
|
753
|
+
const s = parseStateMd(beforeStateRaw);
|
|
692
754
|
if (!t || !s) {
|
|
693
755
|
return output(fail("NO_PROJECT", "No .planning/ found. Run /qualia-new."));
|
|
694
756
|
}
|
|
@@ -705,7 +767,31 @@ function cmdTransition(opts) {
|
|
|
705
767
|
|
|
706
768
|
// Note/activity short-circuit (no status change, no precondition check)
|
|
707
769
|
const noteResult = applyNoteOrActivity(target, s, t, opts);
|
|
708
|
-
if (noteResult)
|
|
770
|
+
if (noteResult) {
|
|
771
|
+
const ledger = recordLedgerEvent({
|
|
772
|
+
action: target,
|
|
773
|
+
phase_before: s.phase,
|
|
774
|
+
phase_after: s.phase,
|
|
775
|
+
status_before: s.status,
|
|
776
|
+
status_after: s.status,
|
|
777
|
+
state_before: s,
|
|
778
|
+
state_after: s,
|
|
779
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
780
|
+
tracking_after: t,
|
|
781
|
+
state_raw_before: beforeStateRaw,
|
|
782
|
+
state_raw_after: readState(),
|
|
783
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
784
|
+
tracking_raw_after: readTrackingRaw(),
|
|
785
|
+
evidence_refs: opts.evidence ? [opts.evidence] : [],
|
|
786
|
+
});
|
|
787
|
+
if (ledger.ok) {
|
|
788
|
+
noteResult.ledger_event_id = ledger.event_id;
|
|
789
|
+
noteResult.ledger_event_hash = ledger.event_hash;
|
|
790
|
+
} else {
|
|
791
|
+
noteResult.ledger_error = ledger.error;
|
|
792
|
+
}
|
|
793
|
+
return output(noteResult);
|
|
794
|
+
}
|
|
709
795
|
|
|
710
796
|
const phase = parseInt(opts.phase) || s.phase;
|
|
711
797
|
|
|
@@ -746,6 +832,23 @@ function cmdTransition(opts) {
|
|
|
746
832
|
const writeError = commitTransitionAtomic(s, t);
|
|
747
833
|
if (writeError) return output(writeError);
|
|
748
834
|
|
|
835
|
+
const ledger = recordLedgerEvent({
|
|
836
|
+
action: target,
|
|
837
|
+
phase_before: phase,
|
|
838
|
+
phase_after: s.phase,
|
|
839
|
+
status_before: prevStatus,
|
|
840
|
+
status_after: s.status,
|
|
841
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
842
|
+
state_after: s,
|
|
843
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
844
|
+
tracking_after: t,
|
|
845
|
+
state_raw_before: beforeStateRaw,
|
|
846
|
+
state_raw_after: readState(),
|
|
847
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
848
|
+
tracking_raw_after: readTrackingRaw(),
|
|
849
|
+
evidence_refs: opts.evidence ? [opts.evidence] : [],
|
|
850
|
+
});
|
|
851
|
+
|
|
749
852
|
// Trace transition for analytics
|
|
750
853
|
_trace("state-transition", "allow", {
|
|
751
854
|
phase: s.phase,
|
|
@@ -755,7 +858,7 @@ function cmdTransition(opts) {
|
|
|
755
858
|
gap_closure: prevStatus === "verified" && target === "planned",
|
|
756
859
|
});
|
|
757
860
|
|
|
758
|
-
|
|
861
|
+
const result = {
|
|
759
862
|
ok: true,
|
|
760
863
|
phase: s.phase,
|
|
761
864
|
phase_name: s.phase_name,
|
|
@@ -764,7 +867,14 @@ function cmdTransition(opts) {
|
|
|
764
867
|
verification: t.verification,
|
|
765
868
|
gap_cycles: (t.gap_cycles || {})[String(s.phase)] || 0,
|
|
766
869
|
next_command: nextCommand(s.status, s.phase, s.total_phases, t.verification),
|
|
767
|
-
}
|
|
870
|
+
};
|
|
871
|
+
if (ledger.ok) {
|
|
872
|
+
result.ledger_event_id = ledger.event_id;
|
|
873
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
874
|
+
} else {
|
|
875
|
+
result.ledger_error = ledger.error;
|
|
876
|
+
}
|
|
877
|
+
output(result);
|
|
768
878
|
}
|
|
769
879
|
|
|
770
880
|
function cmdInit(opts) {
|
|
@@ -825,6 +935,8 @@ function cmdInit(opts) {
|
|
|
825
935
|
const date = now.split("T")[0];
|
|
826
936
|
|
|
827
937
|
// Read existing tracking for lifetime data preservation across milestone resets
|
|
938
|
+
const beforeStateRaw = readState();
|
|
939
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
828
940
|
const prev = readTracking();
|
|
829
941
|
const prevLife = prev ? ensureLifetime(prev) : null;
|
|
830
942
|
|
|
@@ -906,7 +1018,23 @@ function cmdInit(opts) {
|
|
|
906
1018
|
writeStateMd(s);
|
|
907
1019
|
writeTracking(t);
|
|
908
1020
|
|
|
909
|
-
|
|
1021
|
+
const ledger = recordLedgerEvent({
|
|
1022
|
+
action: "init",
|
|
1023
|
+
phase_before: null,
|
|
1024
|
+
phase_after: 1,
|
|
1025
|
+
status_before: null,
|
|
1026
|
+
status_after: "setup",
|
|
1027
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
1028
|
+
state_after: s,
|
|
1029
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1030
|
+
tracking_after: t,
|
|
1031
|
+
state_raw_before: beforeStateRaw,
|
|
1032
|
+
state_raw_after: readState(),
|
|
1033
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1034
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
const result = {
|
|
910
1038
|
ok: true,
|
|
911
1039
|
action: "init",
|
|
912
1040
|
project: opts.project,
|
|
@@ -914,10 +1042,19 @@ function cmdInit(opts) {
|
|
|
914
1042
|
total_phases: totalPhases,
|
|
915
1043
|
status: "setup",
|
|
916
1044
|
next_command: "/qualia-plan 1",
|
|
917
|
-
}
|
|
1045
|
+
};
|
|
1046
|
+
if (ledger.ok) {
|
|
1047
|
+
result.ledger_event_id = ledger.event_id;
|
|
1048
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1049
|
+
} else {
|
|
1050
|
+
result.ledger_error = ledger.error;
|
|
1051
|
+
}
|
|
1052
|
+
output(result);
|
|
918
1053
|
}
|
|
919
1054
|
|
|
920
1055
|
function cmdFix(opts) {
|
|
1056
|
+
const beforeStateRaw = readState();
|
|
1057
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
921
1058
|
const raw = readState();
|
|
922
1059
|
const t = readTracking();
|
|
923
1060
|
if (!raw && !t) {
|
|
@@ -980,17 +1117,41 @@ function cmdFix(opts) {
|
|
|
980
1117
|
return output(fail("WRITE_ERROR", e.message));
|
|
981
1118
|
}
|
|
982
1119
|
|
|
983
|
-
|
|
1120
|
+
const ledger = recordLedgerEvent({
|
|
1121
|
+
action: "fix",
|
|
1122
|
+
phase_before: parsed.phase,
|
|
1123
|
+
phase_after: s.phase,
|
|
1124
|
+
status_before: parsed.status,
|
|
1125
|
+
status_after: s.status,
|
|
1126
|
+
state_before: parsed,
|
|
1127
|
+
state_after: s,
|
|
1128
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1129
|
+
tracking_after: t,
|
|
1130
|
+
state_raw_before: beforeStateRaw,
|
|
1131
|
+
state_raw_after: readState(),
|
|
1132
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1133
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
const result = {
|
|
984
1137
|
ok: true,
|
|
985
1138
|
action: "fix",
|
|
986
1139
|
previous_errors: previousErrors,
|
|
987
1140
|
fixed: true,
|
|
988
|
-
}
|
|
1141
|
+
};
|
|
1142
|
+
if (ledger.ok) {
|
|
1143
|
+
result.ledger_event_id = ledger.event_id;
|
|
1144
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1145
|
+
} else {
|
|
1146
|
+
result.ledger_error = ledger.error;
|
|
1147
|
+
}
|
|
1148
|
+
output(result);
|
|
989
1149
|
}
|
|
990
1150
|
|
|
991
1151
|
function cmdValidatePlan(opts) {
|
|
992
1152
|
const phase = parseInt(opts.phase) || 1;
|
|
993
1153
|
const planFile = path.join(PLANNING, `phase-${phase}-plan.md`);
|
|
1154
|
+
const contractFile = path.join(PLANNING, `phase-${phase}-contract.json`);
|
|
994
1155
|
|
|
995
1156
|
if (!fs.existsSync(planFile)) {
|
|
996
1157
|
return output(fail("MISSING_FILE", `Plan file not found: ${planFile}`));
|
|
@@ -1036,6 +1197,47 @@ function cmdValidatePlan(opts) {
|
|
|
1036
1197
|
const warnings = [];
|
|
1037
1198
|
const VALID_CHECK_TYPES = ["file-exists", "grep-match", "command-exit", "behavioral"];
|
|
1038
1199
|
let contractCount = 0;
|
|
1200
|
+
let contractStatus = "missing";
|
|
1201
|
+
let contractErrors = [];
|
|
1202
|
+
|
|
1203
|
+
// JSON contracts are the new machine-readable trust layer. Keep missing
|
|
1204
|
+
// contracts as a warning by default for in-flight legacy projects; callers can
|
|
1205
|
+
// opt into the hard gate with --require-contract.
|
|
1206
|
+
try {
|
|
1207
|
+
const pc = require("./plan-contract.js");
|
|
1208
|
+
if (fs.existsSync(contractFile)) {
|
|
1209
|
+
const loaded = pc.readContractFile(contractFile);
|
|
1210
|
+
if (!loaded.ok) {
|
|
1211
|
+
contractStatus = "invalid";
|
|
1212
|
+
contractErrors = [loaded.message || loaded.error || "contract unreadable"];
|
|
1213
|
+
} else {
|
|
1214
|
+
contractErrors = pc.validate(loaded.contract);
|
|
1215
|
+
const drift = pc.checkDrift(contractFile, planFile);
|
|
1216
|
+
if (contractErrors.length > 0) {
|
|
1217
|
+
contractStatus = "invalid";
|
|
1218
|
+
} else if (drift.ok && drift.drift) {
|
|
1219
|
+
contractStatus = "drifted";
|
|
1220
|
+
contractErrors = [`source_plan_hash drift: stored ${drift.stored}, current ${drift.current}`];
|
|
1221
|
+
} else {
|
|
1222
|
+
contractStatus = "valid";
|
|
1223
|
+
contractCount = Array.isArray(loaded.contract.tasks) ? loaded.contract.tasks.length : 0;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
} catch (e) {
|
|
1228
|
+
contractStatus = "unavailable";
|
|
1229
|
+
contractErrors = [e.message];
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (contractStatus === "missing") {
|
|
1233
|
+
const msg = `JSON contract missing: ${contractFile}`;
|
|
1234
|
+
if (opts.require_contract) errors.push(msg);
|
|
1235
|
+
else warnings.push(msg);
|
|
1236
|
+
} else if (contractStatus !== "valid") {
|
|
1237
|
+
const msg = `JSON contract ${contractStatus}: ${contractErrors.join("; ")}`;
|
|
1238
|
+
if (opts.require_contract) errors.push(msg);
|
|
1239
|
+
else warnings.push(msg);
|
|
1240
|
+
}
|
|
1039
1241
|
|
|
1040
1242
|
if (/^## Verification Contract/m.test(content)) {
|
|
1041
1243
|
// Extract the contract section (from header to next ## or end of file)
|
|
@@ -1049,9 +1251,10 @@ function cmdValidatePlan(opts) {
|
|
|
1049
1251
|
if (nextH2 !== -1) contractSection = contractSection.substring(0, nextH2);
|
|
1050
1252
|
// Each contract starts with ### Contract for Task N
|
|
1051
1253
|
const contractBlocks = contractSection.match(/^### Contract for Task \d+/gm);
|
|
1052
|
-
|
|
1254
|
+
const markdownContractCount = contractBlocks ? contractBlocks.length : 0;
|
|
1255
|
+
if (contractStatus !== "valid") contractCount = markdownContractCount;
|
|
1053
1256
|
|
|
1054
|
-
if (
|
|
1257
|
+
if (markdownContractCount === 0) {
|
|
1055
1258
|
warnings.push("Verification Contract section exists but contains no contract blocks (expected '### Contract for Task N')");
|
|
1056
1259
|
} else {
|
|
1057
1260
|
// Split into individual contract blocks for validation
|
|
@@ -1092,9 +1295,9 @@ function cmdValidatePlan(opts) {
|
|
|
1092
1295
|
}
|
|
1093
1296
|
|
|
1094
1297
|
// Warn if contract count < task count
|
|
1095
|
-
if (taskCount > 0 &&
|
|
1298
|
+
if (taskCount > 0 && markdownContractCount > 0 && markdownContractCount < taskCount) {
|
|
1096
1299
|
warnings.push(
|
|
1097
|
-
`Only ${
|
|
1300
|
+
`Only ${markdownContractCount} Markdown contract(s) for ${taskCount} task(s) — not all tasks have verification contracts`
|
|
1098
1301
|
);
|
|
1099
1302
|
}
|
|
1100
1303
|
}
|
|
@@ -1119,6 +1322,9 @@ function cmdValidatePlan(opts) {
|
|
|
1119
1322
|
done_when_count: doneWhenCount,
|
|
1120
1323
|
ac_count: acCount,
|
|
1121
1324
|
contract_count: contractCount,
|
|
1325
|
+
contract_file: contractFile,
|
|
1326
|
+
contract_status: contractStatus,
|
|
1327
|
+
contract_errors: contractErrors.length > 0 ? contractErrors : undefined,
|
|
1122
1328
|
warnings: warnings.length > 0 ? warnings : undefined,
|
|
1123
1329
|
});
|
|
1124
1330
|
}
|
|
@@ -1129,6 +1335,8 @@ function cmdValidatePlan(opts) {
|
|
|
1129
1335
|
// hiccup) does NOT double-count. To re-close a milestone deliberately, pass
|
|
1130
1336
|
// --force.
|
|
1131
1337
|
function cmdCloseMilestone(opts) {
|
|
1338
|
+
const beforeStateRaw = readState();
|
|
1339
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1132
1340
|
const t = readTracking();
|
|
1133
1341
|
const s = parseStateMd(readState());
|
|
1134
1342
|
if (!t || !s) {
|
|
@@ -1252,19 +1460,44 @@ function cmdCloseMilestone(opts) {
|
|
|
1252
1460
|
lifetime: t.lifetime,
|
|
1253
1461
|
});
|
|
1254
1462
|
|
|
1255
|
-
|
|
1463
|
+
const ledger = recordLedgerEvent({
|
|
1464
|
+
action: "close-milestone",
|
|
1465
|
+
phase_before: s.phase,
|
|
1466
|
+
phase_after: s.phase,
|
|
1467
|
+
status_before: s.status,
|
|
1468
|
+
status_after: s.status,
|
|
1469
|
+
state_before: s,
|
|
1470
|
+
state_after: s,
|
|
1471
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1472
|
+
tracking_after: t,
|
|
1473
|
+
state_raw_before: beforeStateRaw,
|
|
1474
|
+
state_raw_after: readState(),
|
|
1475
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1476
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1477
|
+
});
|
|
1478
|
+
|
|
1479
|
+
const result = {
|
|
1256
1480
|
ok: true,
|
|
1257
1481
|
action: "close-milestone",
|
|
1258
1482
|
closed_milestone: closedMilestone,
|
|
1259
1483
|
next_milestone: t.milestone,
|
|
1260
1484
|
lifetime: t.lifetime,
|
|
1261
|
-
}
|
|
1485
|
+
};
|
|
1486
|
+
if (ledger.ok) {
|
|
1487
|
+
result.ledger_event_id = ledger.event_id;
|
|
1488
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1489
|
+
} else {
|
|
1490
|
+
result.ledger_error = ledger.error;
|
|
1491
|
+
}
|
|
1492
|
+
output(result);
|
|
1262
1493
|
}
|
|
1263
1494
|
|
|
1264
1495
|
// ─── Backfill Lifetime ───────────────────────────────────
|
|
1265
1496
|
// Reconstructs lifetime counters from STATE.md roadmap + plan files.
|
|
1266
1497
|
// Safe to run multiple times (idempotent — recalculates from source).
|
|
1267
1498
|
function cmdBackfillLifetime(opts) {
|
|
1499
|
+
const beforeStateRaw = readState();
|
|
1500
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1268
1501
|
const t = readTracking();
|
|
1269
1502
|
const s = parseStateMd(readState());
|
|
1270
1503
|
if (!t || !s) {
|
|
@@ -1327,7 +1560,23 @@ function cmdBackfillLifetime(opts) {
|
|
|
1327
1560
|
phases_scanned: s.phases.length,
|
|
1328
1561
|
});
|
|
1329
1562
|
|
|
1330
|
-
|
|
1563
|
+
const ledger = recordLedgerEvent({
|
|
1564
|
+
action: "backfill-lifetime",
|
|
1565
|
+
phase_before: s.phase,
|
|
1566
|
+
phase_after: s.phase,
|
|
1567
|
+
status_before: s.status,
|
|
1568
|
+
status_after: s.status,
|
|
1569
|
+
state_before: s,
|
|
1570
|
+
state_after: s,
|
|
1571
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1572
|
+
tracking_after: t,
|
|
1573
|
+
state_raw_before: beforeStateRaw,
|
|
1574
|
+
state_raw_after: readState(),
|
|
1575
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1576
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1577
|
+
});
|
|
1578
|
+
|
|
1579
|
+
const result = {
|
|
1331
1580
|
ok: true,
|
|
1332
1581
|
action: "backfill-lifetime",
|
|
1333
1582
|
previous,
|
|
@@ -1335,7 +1584,14 @@ function cmdBackfillLifetime(opts) {
|
|
|
1335
1584
|
phases_scanned: s.phases.length,
|
|
1336
1585
|
phases_completed: phasesCompleted,
|
|
1337
1586
|
tasks_completed: tasksCompleted,
|
|
1338
|
-
}
|
|
1587
|
+
};
|
|
1588
|
+
if (ledger.ok) {
|
|
1589
|
+
result.ledger_event_id = ledger.event_id;
|
|
1590
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1591
|
+
} else {
|
|
1592
|
+
result.ledger_error = ledger.error;
|
|
1593
|
+
}
|
|
1594
|
+
output(result);
|
|
1339
1595
|
}
|
|
1340
1596
|
|
|
1341
1597
|
// ─── Backfill Milestones from JOURNEY.md ─────────────────
|
|
@@ -1353,6 +1609,8 @@ function cmdBackfillLifetime(opts) {
|
|
|
1353
1609
|
// Phase counting handles ranges (`1–13` → 13), comma lists (`14, 15, 16.1–16.6` → 8),
|
|
1354
1610
|
// and "rolling" / "—" / "-" → 0.
|
|
1355
1611
|
function cmdBackfillMilestones(opts) {
|
|
1612
|
+
const beforeStateRaw = readState();
|
|
1613
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1356
1614
|
const t = readTracking();
|
|
1357
1615
|
if (!t) return output(fail("NO_PROJECT", "No .planning/ found."));
|
|
1358
1616
|
ensureLifetime(t);
|
|
@@ -1507,7 +1765,24 @@ function cmdBackfillMilestones(opts) {
|
|
|
1507
1765
|
lifetime: t.lifetime,
|
|
1508
1766
|
});
|
|
1509
1767
|
|
|
1510
|
-
|
|
1768
|
+
const afterState = parseStateMd(readState());
|
|
1769
|
+
const ledger = recordLedgerEvent({
|
|
1770
|
+
action: "backfill-milestones",
|
|
1771
|
+
phase_before: afterState ? afterState.phase : undefined,
|
|
1772
|
+
phase_after: afterState ? afterState.phase : undefined,
|
|
1773
|
+
status_before: afterState ? afterState.status : undefined,
|
|
1774
|
+
status_after: afterState ? afterState.status : undefined,
|
|
1775
|
+
state_before: parseStateMd(beforeStateRaw),
|
|
1776
|
+
state_after: afterState,
|
|
1777
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1778
|
+
tracking_after: t,
|
|
1779
|
+
state_raw_before: beforeStateRaw,
|
|
1780
|
+
state_raw_after: readState(),
|
|
1781
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1782
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1783
|
+
});
|
|
1784
|
+
|
|
1785
|
+
const result = {
|
|
1511
1786
|
ok: true,
|
|
1512
1787
|
action: "backfill-milestones",
|
|
1513
1788
|
added,
|
|
@@ -1515,7 +1790,14 @@ function cmdBackfillMilestones(opts) {
|
|
|
1515
1790
|
closed: closedSummaries,
|
|
1516
1791
|
open_milestone: openRow ? { num: openRow.num, name: openRow.name } : null,
|
|
1517
1792
|
lifetime: t.lifetime,
|
|
1518
|
-
}
|
|
1793
|
+
};
|
|
1794
|
+
if (ledger.ok) {
|
|
1795
|
+
result.ledger_event_id = ledger.event_id;
|
|
1796
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1797
|
+
} else {
|
|
1798
|
+
result.ledger_error = ledger.error;
|
|
1799
|
+
}
|
|
1800
|
+
output(result);
|
|
1519
1801
|
}
|
|
1520
1802
|
|
|
1521
1803
|
// ─── Next Report ID ──────────────────────────────────────
|
|
@@ -1525,6 +1807,8 @@ function cmdBackfillMilestones(opts) {
|
|
|
1525
1807
|
// to the ERP. If --peek is passed, the next id is returned WITHOUT
|
|
1526
1808
|
// incrementing — useful for --dry-run previews.
|
|
1527
1809
|
function cmdNextReportId(opts) {
|
|
1810
|
+
const beforeStateRaw = readState();
|
|
1811
|
+
const beforeTrackingRaw = readTrackingRaw();
|
|
1528
1812
|
const t = readTracking();
|
|
1529
1813
|
if (!t) return output(fail("NO_PROJECT", "No .planning/ found."));
|
|
1530
1814
|
ensureLifetime(t);
|
|
@@ -1536,7 +1820,33 @@ function cmdNextReportId(opts) {
|
|
|
1536
1820
|
t.last_updated = new Date().toISOString();
|
|
1537
1821
|
writeTracking(t);
|
|
1538
1822
|
}
|
|
1539
|
-
|
|
1823
|
+
const result = { ok: true, action: "next-report-id", report_id: id, report_seq: next, peeked: peek };
|
|
1824
|
+
if (!peek) {
|
|
1825
|
+
const afterState = parseStateMd(readState());
|
|
1826
|
+
const beforeState = parseStateMd(beforeStateRaw);
|
|
1827
|
+
const ledger = recordLedgerEvent({
|
|
1828
|
+
action: "next-report-id",
|
|
1829
|
+
phase_before: beforeState ? beforeState.phase : undefined,
|
|
1830
|
+
phase_after: afterState ? afterState.phase : undefined,
|
|
1831
|
+
status_before: beforeState ? beforeState.status : undefined,
|
|
1832
|
+
status_after: afterState ? afterState.status : undefined,
|
|
1833
|
+
state_before: beforeState,
|
|
1834
|
+
state_after: afterState,
|
|
1835
|
+
tracking_before: parseTrackingRaw(beforeTrackingRaw),
|
|
1836
|
+
tracking_after: t,
|
|
1837
|
+
state_raw_before: beforeStateRaw,
|
|
1838
|
+
state_raw_after: readState(),
|
|
1839
|
+
tracking_raw_before: beforeTrackingRaw,
|
|
1840
|
+
tracking_raw_after: readTrackingRaw(),
|
|
1841
|
+
});
|
|
1842
|
+
if (ledger.ok) {
|
|
1843
|
+
result.ledger_event_id = ledger.event_id;
|
|
1844
|
+
result.ledger_event_hash = ledger.event_hash;
|
|
1845
|
+
} else {
|
|
1846
|
+
result.ledger_error = ledger.error;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
output(result);
|
|
1540
1850
|
}
|
|
1541
1851
|
|
|
1542
1852
|
// ─── Output ──────────────────────────────────────────────
|