@alecsibilia/luca 13.0.0-alpha.1
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/LICENSE +201 -0
- package/README.md +47 -0
- package/bin/luca.js +3 -0
- package/dist/chunks/branch.mjs +47 -0
- package/dist/chunks/bun-runtime.mjs +46 -0
- package/dist/chunks/checks.mjs +53 -0
- package/dist/chunks/claim-verify.mjs +465 -0
- package/dist/chunks/classify.mjs +105 -0
- package/dist/chunks/confidence.mjs +199 -0
- package/dist/chunks/doctor.mjs +158 -0
- package/dist/chunks/hook.mjs +696 -0
- package/dist/chunks/init.mjs +715 -0
- package/dist/chunks/muninndb-health.mjs +66 -0
- package/dist/chunks/phase.mjs +38 -0
- package/dist/chunks/pr-review.mjs +122 -0
- package/dist/chunks/preferences.mjs +61 -0
- package/dist/chunks/repair.mjs +111 -0
- package/dist/chunks/repo.mjs +58 -0
- package/dist/chunks/retro.mjs +86 -0
- package/dist/chunks/roadmap.mjs +58 -0
- package/dist/chunks/rules.mjs +527 -0
- package/dist/chunks/stale-mcp-server.mjs +90 -0
- package/dist/chunks/state.mjs +57 -0
- package/dist/chunks/stray-local-install.mjs +200 -0
- package/dist/chunks/telemetry.mjs +165 -0
- package/dist/chunks/todo.mjs +151 -0
- package/dist/chunks/vault-init.mjs +300 -0
- package/dist/chunks/verification.mjs +95 -0
- package/dist/chunks/version.mjs +70 -0
- package/dist/chunks/workflow.mjs +47 -0
- package/dist/claude/.claude/agents/architect.md +410 -0
- package/dist/claude/.claude/agents/build.md +111 -0
- package/dist/claude/.claude/agents/discuss.md +93 -0
- package/dist/claude/.claude/agents/discussion.md +149 -0
- package/dist/claude/.claude/agents/execute.md +416 -0
- package/dist/claude/.claude/agents/executor.md +161 -0
- package/dist/claude/.claude/agents/fast.md +84 -0
- package/dist/claude/.claude/agents/finalize.md +484 -0
- package/dist/claude/.claude/agents/learner.md +160 -0
- package/dist/claude/.claude/agents/plan-reviewer.md +129 -0
- package/dist/claude/.claude/agents/plan.md +96 -0
- package/dist/claude/.claude/agents/research.md +327 -0
- package/dist/claude/.claude/agents/researcher.md +78 -0
- package/dist/claude/.claude/agents/review.md +283 -0
- package/dist/claude/.claude/agents/reviewer.md +163 -0
- package/dist/claude/.claude/agents/shadow-scanner.md +257 -0
- package/dist/claude/.claude/agents/triage.md +230 -0
- package/dist/claude/.claude/agents/verifier.md +131 -0
- package/dist/claude/.claude/commands/bug-diagnose.md +12 -0
- package/dist/claude/.claude/commands/gh-issue-triage.md +14 -0
- package/dist/claude/.claude/commands/gh-pr-address.md +235 -0
- package/dist/claude/.claude/commands/gh-prepare.md +12 -0
- package/dist/claude/.claude/commands/grill-me.md +12 -0
- package/dist/claude/.claude/commands/lu-review.md +51 -0
- package/dist/claude/.claude/commands/lu.md +75 -0
- package/dist/claude/.claude/commands/luca-init.md +14 -0
- package/dist/claude/.claude/commands/luca-telemetry-report.md +12 -0
- package/dist/claude/.claude/commands/memory-audit.md +12 -0
- package/dist/claude/.claude/commands/milestone-new.md +122 -0
- package/dist/claude/.claude/commands/phase-discuss.md +45 -0
- package/dist/claude/.claude/commands/phase-execute.md +39 -0
- package/dist/claude/.claude/commands/phase-plan.md +53 -0
- package/dist/claude/.claude/commands/repo-cleanup.md +80 -0
- package/dist/claude/.claude/commands/todo-add.md +28 -0
- package/dist/claude/.claude/commands/todo-check.md +36 -0
- package/dist/claude/.claude/hooks/context-refresher.ts +285 -0
- package/dist/claude/.claude/hooks/continuation-messages.ts +215 -0
- package/dist/claude/.claude/hooks/pipeline-guard.ts +182 -0
- package/dist/claude/.claude/settings.json +41 -0
- package/dist/claude/skills/arch-audit/SKILL.md +161 -0
- package/dist/claude/skills/autopilot/SKILL.md +1299 -0
- package/dist/claude/skills/bug-diagnose/SKILL.md +102 -0
- package/dist/claude/skills/choose/SKILL.md +124 -0
- package/dist/claude/skills/gh-issue-triage/SKILL.md +97 -0
- package/dist/claude/skills/gh-pr-address/SKILL.md +235 -0
- package/dist/claude/skills/gh-prepare/SKILL.md +209 -0
- package/dist/claude/skills/grill-me/SKILL.md +46 -0
- package/dist/claude/skills/lu/SKILL.md +112 -0
- package/dist/claude/skills/lu-review/SKILL.md +51 -0
- package/dist/claude/skills/luca-init/SKILL.md +91 -0
- package/dist/claude/skills/luca-telemetry-report/SKILL.md +145 -0
- package/dist/claude/skills/luca-write-surface/SKILL.md +213 -0
- package/dist/claude/skills/memory-audit/SKILL.md +217 -0
- package/dist/claude/skills/milestone-audit/SKILL.md +545 -0
- package/dist/claude/skills/milestone-complete/SKILL.md +168 -0
- package/dist/claude/skills/milestone-gaps/SKILL.md +60 -0
- package/dist/claude/skills/milestone-new/SKILL.md +125 -0
- package/dist/claude/skills/note/SKILL.md +162 -0
- package/dist/claude/skills/phase-add/SKILL.md +91 -0
- package/dist/claude/skills/phase-assumptions/SKILL.md +92 -0
- package/dist/claude/skills/phase-discuss/SKILL.md +165 -0
- package/dist/claude/skills/phase-execute/SKILL.md +1786 -0
- package/dist/claude/skills/phase-insert/SKILL.md +100 -0
- package/dist/claude/skills/phase-plan/SKILL.md +461 -0
- package/dist/claude/skills/phase-remove/SKILL.md +113 -0
- package/dist/claude/skills/phase-research/SKILL.md +80 -0
- package/dist/claude/skills/post-init-tour/SKILL.md +58 -0
- package/dist/claude/skills/progress/SKILL.md +271 -0
- package/dist/claude/skills/project-new/SKILL.md +609 -0
- package/dist/claude/skills/quick/SKILL.md +256 -0
- package/dist/claude/skills/rename-audit/SKILL.md +52 -0
- package/dist/claude/skills/repo-audit/SKILL.md +88 -0
- package/dist/claude/skills/repo-cleanup/SKILL.md +80 -0
- package/dist/claude/skills/seed-memory/SKILL.md +235 -0
- package/dist/claude/skills/session-pause/SKILL.md +126 -0
- package/dist/claude/skills/session-plan/SKILL.md +112 -0
- package/dist/claude/skills/session-resume/SKILL.md +75 -0
- package/dist/claude/skills/todo-add/SKILL.md +85 -0
- package/dist/claude/skills/todo-check/SKILL.md +77 -0
- package/dist/claude/skills/workflow-save/SKILL.md +277 -0
- package/dist/index.d.mts +33 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.mjs +69 -0
- package/dist/shared/luca.B3Mimc0P.mjs +52 -0
- package/dist/shared/luca.B3saVjJm.mjs +163 -0
- package/dist/shared/luca.BYdjkfnz.mjs +217 -0
- package/dist/shared/luca.BmhNkYe2.mjs +56 -0
- package/dist/shared/luca.C4gMUoBd.mjs +358 -0
- package/dist/shared/luca.CQ3g1xrD.mjs +19 -0
- package/dist/shared/luca.CRmaAfXR.mjs +713 -0
- package/dist/shared/luca.CrXzXueR.mjs +57 -0
- package/dist/shared/luca.DTomPq7I.mjs +91 -0
- package/dist/shared/luca.DjDTeDCi.mjs +1904 -0
- package/dist/shared/luca.HZxBTBgD.mjs +201 -0
- package/dist/shared/luca.TSMg1t7I.mjs +10 -0
- package/dist/shared/luca.dM-MKlNE.mjs +25 -0
- package/dist/shared/luca.naWEcQ4B.mjs +7 -0
- package/package.json +76 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import './luca.CRmaAfXR.mjs';
|
|
2
|
+
import 'node:fs';
|
|
3
|
+
import 'node:fs/promises';
|
|
4
|
+
import 'node:path';
|
|
5
|
+
import 'node:crypto';
|
|
6
|
+
import 'node:module';
|
|
7
|
+
import 'node:url';
|
|
8
|
+
import 'node:child_process';
|
|
9
|
+
import { r as readConfidenceJournal, a as readLedgerForRun } from './luca.HZxBTBgD.mjs';
|
|
10
|
+
import { l as listPhaseSlugs, r as readVerificationResult } from './luca.BmhNkYe2.mjs';
|
|
11
|
+
|
|
12
|
+
function fingerprint(input) {
|
|
13
|
+
let hash = 0;
|
|
14
|
+
for (let i = 0; i < input.length; i++) {
|
|
15
|
+
hash = (hash << 5) - hash + input.charCodeAt(i);
|
|
16
|
+
hash |= 0;
|
|
17
|
+
}
|
|
18
|
+
return Math.abs(hash).toString(36);
|
|
19
|
+
}
|
|
20
|
+
const LOW_CONFIDENCE_THRESHOLD = 3;
|
|
21
|
+
function eventDataString(e, key) {
|
|
22
|
+
const v = e.data[key];
|
|
23
|
+
return typeof v === "string" ? v : void 0;
|
|
24
|
+
}
|
|
25
|
+
function mostRecentPhaseAt(entries, at) {
|
|
26
|
+
const target = new Date(at).getTime();
|
|
27
|
+
let best;
|
|
28
|
+
for (const e of entries) {
|
|
29
|
+
if (e.event !== "phase-start") continue;
|
|
30
|
+
if (new Date(e.timestamp).getTime() > target) break;
|
|
31
|
+
const name = eventDataString(e, "phase");
|
|
32
|
+
if (name) best = name;
|
|
33
|
+
}
|
|
34
|
+
return best;
|
|
35
|
+
}
|
|
36
|
+
function analyzeRun(input) {
|
|
37
|
+
const { runId: targetRunId, entries, verifications, confidence } = input;
|
|
38
|
+
const startedAt = entries[0]?.timestamp;
|
|
39
|
+
const endedAt = entries[entries.length - 1]?.timestamp;
|
|
40
|
+
const durationMs = startedAt && endedAt ? new Date(endedAt).getTime() - new Date(startedAt).getTime() : void 0;
|
|
41
|
+
const phaseMap = /* @__PURE__ */ new Map();
|
|
42
|
+
function getPhase(name) {
|
|
43
|
+
let p = phaseMap.get(name);
|
|
44
|
+
if (!p) {
|
|
45
|
+
p = { name, verifications: [], todosMovedToDone: [] };
|
|
46
|
+
phaseMap.set(name, p);
|
|
47
|
+
}
|
|
48
|
+
return p;
|
|
49
|
+
}
|
|
50
|
+
for (const e of entries) {
|
|
51
|
+
const phaseName = eventDataString(e, "phase");
|
|
52
|
+
switch (e.event) {
|
|
53
|
+
case "phase-start": {
|
|
54
|
+
if (phaseName) getPhase(phaseName).startedAt = e.timestamp;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case "phase-complete": {
|
|
58
|
+
if (phaseName) getPhase(phaseName).completedAt = e.timestamp;
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
case "phase-diff-summary": {
|
|
62
|
+
if (!phaseName) break;
|
|
63
|
+
getPhase(phaseName).diff = {
|
|
64
|
+
filesChanged: Array.isArray(e.data.filesChanged) ? e.data.filesChanged : [],
|
|
65
|
+
commitsAdded: Array.isArray(e.data.commitsAdded) ? e.data.commitsAdded : [],
|
|
66
|
+
isEmpty: e.data.isEmpty === true,
|
|
67
|
+
indeterminate: e.data.indeterminate === true
|
|
68
|
+
};
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "phase-empty-justification": {
|
|
72
|
+
if (!phaseName) break;
|
|
73
|
+
getPhase(phaseName).justification = {
|
|
74
|
+
category: eventDataString(e, "category") ?? "unknown",
|
|
75
|
+
reasoning: eventDataString(e, "reasoning") ?? "",
|
|
76
|
+
at: e.timestamp
|
|
77
|
+
};
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case "todo-moved-to-done": {
|
|
81
|
+
const recentPhase = mostRecentPhaseAt(entries, e.timestamp);
|
|
82
|
+
if (recentPhase) {
|
|
83
|
+
getPhase(recentPhase).todosMovedToDone.push({
|
|
84
|
+
slug: eventDataString(e, "slug") ?? "<unknown>",
|
|
85
|
+
verificationRef: e.data.verificationRef ?? null
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
for (const v of verifications) {
|
|
93
|
+
if (v.phase) getPhase(v.phase).verifications.push(v);
|
|
94
|
+
}
|
|
95
|
+
const phases = Array.from(phaseMap.values());
|
|
96
|
+
const violations = [];
|
|
97
|
+
for (const p of phases) {
|
|
98
|
+
if (!p.completedAt) continue;
|
|
99
|
+
if (p.diff?.isEmpty && !p.justification) {
|
|
100
|
+
violations.push({
|
|
101
|
+
severity: "critical",
|
|
102
|
+
code: "EMPTY_PHASE_NO_JUSTIFICATION",
|
|
103
|
+
message: `Phase "${p.name}" completed with zero file changes and zero commits but has no phase-empty-justification entry.`,
|
|
104
|
+
evidence: `phase=${p.name} completedAt=${p.completedAt}`,
|
|
105
|
+
evidenceFingerprint: fingerprint(
|
|
106
|
+
`EMPTY_PHASE:${p.name}:${p.completedAt}`
|
|
107
|
+
)
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const moveBlocked = entries.filter((e) => e.event === "todo-move-blocked");
|
|
112
|
+
for (const e of moveBlocked) {
|
|
113
|
+
violations.push({
|
|
114
|
+
severity: "warning",
|
|
115
|
+
code: "TODO_DONE_NO_VERIFICATION",
|
|
116
|
+
message: `Blocked attempt to move todo "${eventDataString(e, "identifier") ?? "?"}" to done without a valid verificationRef. Tool layer prevented the unsafe transition.`,
|
|
117
|
+
evidence: `reason=${eventDataString(e, "reason") ?? "?"} at=${e.timestamp}`,
|
|
118
|
+
evidenceFingerprint: fingerprint(
|
|
119
|
+
`TODO_BLOCKED:${eventDataString(e, "identifier") ?? ""}:${e.timestamp}`
|
|
120
|
+
)
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const p of phases) {
|
|
124
|
+
for (const t of p.todosMovedToDone) {
|
|
125
|
+
if (!t.verificationRef) {
|
|
126
|
+
violations.push({
|
|
127
|
+
severity: "critical",
|
|
128
|
+
code: "TODO_DONE_NO_VERIFICATION",
|
|
129
|
+
message: `Todo "${t.slug}" moved to done without a verificationRef in phase "${p.name}".`,
|
|
130
|
+
evidence: `phase=${p.name} slug=${t.slug}`,
|
|
131
|
+
evidenceFingerprint: fingerprint(
|
|
132
|
+
`TODO_NOREF:${p.name}:${t.slug}`
|
|
133
|
+
)
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const forced = entries.filter(
|
|
139
|
+
(e) => e.event === "pipeline-forced-transition"
|
|
140
|
+
);
|
|
141
|
+
for (const e of forced) {
|
|
142
|
+
violations.push({
|
|
143
|
+
severity: "warning",
|
|
144
|
+
code: "FORCED_TRANSITION",
|
|
145
|
+
message: `Pipeline-guard force-transitioned the agent (it failed to call switch-mode).`,
|
|
146
|
+
evidence: `from=${eventDataString(e, "from") ?? "?"} to=${eventDataString(e, "to") ?? "?"} at=${e.timestamp}`,
|
|
147
|
+
evidenceFingerprint: fingerprint(
|
|
148
|
+
`FORCED:${e.timestamp}:${eventDataString(e, "from") ?? ""}`
|
|
149
|
+
)
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const lowConfidence = confidence.filter((c) => c.confidence === "low");
|
|
153
|
+
if (lowConfidence.length >= LOW_CONFIDENCE_THRESHOLD) {
|
|
154
|
+
violations.push({
|
|
155
|
+
severity: "warning",
|
|
156
|
+
code: "LOW_CONFIDENCE_THRESHOLD",
|
|
157
|
+
message: `${lowConfidence.length} low-confidence executor decisions (threshold=${LOW_CONFIDENCE_THRESHOLD}). Human review recommended before merge.`,
|
|
158
|
+
evidence: `count=${lowConfidence.length} categories=${lowConfidence.map((c) => c.category).join(",")}`,
|
|
159
|
+
evidenceFingerprint: fingerprint(
|
|
160
|
+
`LOWCONF:${lowConfidence.length}:${targetRunId}`
|
|
161
|
+
)
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
const waveBlocked = entries.filter(
|
|
165
|
+
(e) => e.event === "wave-advance-blocked"
|
|
166
|
+
);
|
|
167
|
+
for (const e of waveBlocked) {
|
|
168
|
+
violations.push({
|
|
169
|
+
severity: "warning",
|
|
170
|
+
code: "WAVE_NO_VERIFICATION",
|
|
171
|
+
message: `Blocked attempt to advance wave without verification-result. Tool layer prevented the unsafe transition.`,
|
|
172
|
+
evidence: `phase=${eventDataString(e, "phase") ?? "?"} wave=${e.data.wave ?? "?"} at=${e.timestamp}`,
|
|
173
|
+
evidenceFingerprint: fingerprint(
|
|
174
|
+
`WAVE_BLOCKED:${eventDataString(e, "phase") ?? ""}:${e.data.wave ?? ""}`
|
|
175
|
+
)
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const reEntered = entries.filter((e) => e.event === "pipeline-re-entered");
|
|
179
|
+
for (const e of reEntered) {
|
|
180
|
+
violations.push({
|
|
181
|
+
severity: "warning",
|
|
182
|
+
code: "PIPELINE_RE_ENTERED",
|
|
183
|
+
message: `Pipeline was re-entered mid-run (often indicates rework).`,
|
|
184
|
+
evidence: `targetMode=${eventDataString(e, "targetMode") ?? "?"} reason=${eventDataString(e, "reason") ?? "?"}`,
|
|
185
|
+
evidenceFingerprint: fingerprint(
|
|
186
|
+
`REENTRY:${e.timestamp}:${eventDataString(e, "targetMode") ?? ""}`
|
|
187
|
+
)
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
const idleBypass = entries.filter(
|
|
191
|
+
(e) => e.event === "pipeline-guard-idle-bypass"
|
|
192
|
+
);
|
|
193
|
+
for (const e of idleBypass) {
|
|
194
|
+
violations.push({
|
|
195
|
+
severity: "warning",
|
|
196
|
+
code: "PIPELINE_GUARD_IDLE_BYPASS",
|
|
197
|
+
message: `Pipeline-guard skipped enforcement because pipelineStep was idle. May indicate stale state contamination.`,
|
|
198
|
+
evidence: `at=${e.timestamp}`,
|
|
199
|
+
evidenceFingerprint: fingerprint(`IDLE_BYPASS:${e.timestamp}`)
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
const emptyDetected = entries.filter(
|
|
203
|
+
(e) => e.event === "phase-empty-detected"
|
|
204
|
+
);
|
|
205
|
+
for (const e of emptyDetected) {
|
|
206
|
+
const fromStep = eventDataString(e, "from") ?? "?";
|
|
207
|
+
const toStep = eventDataString(e, "to") ?? "?";
|
|
208
|
+
const expected = eventDataString(e, "expectedArtifact") ?? "?";
|
|
209
|
+
const slug = eventDataString(e, "slug") ?? "?";
|
|
210
|
+
violations.push({
|
|
211
|
+
severity: "warning",
|
|
212
|
+
code: "STEP_ARTIFACT_MISSING",
|
|
213
|
+
message: `Step "${fromStep}" advanced to "${toStep}" without writing its expected artifact ("${expected}") \u2014 possible empty/skipped step.`,
|
|
214
|
+
evidence: `slug=${slug} expectedArtifact=${expected} at=${e.timestamp}`,
|
|
215
|
+
evidenceFingerprint: fingerprint(
|
|
216
|
+
`STEP_EMPTY:${slug}:${fromStep}:${e.timestamp}`
|
|
217
|
+
)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
const metrics = {
|
|
221
|
+
totalEvents: entries.length,
|
|
222
|
+
modeTransitions: entries.filter((e) => e.event === "mode-transition").length,
|
|
223
|
+
phasesCompleted: entries.filter((e) => e.event === "phase-complete").length,
|
|
224
|
+
emptyPhasesJustified: entries.filter(
|
|
225
|
+
(e) => e.event === "phase-empty-justification"
|
|
226
|
+
).length,
|
|
227
|
+
todosMovedToDone: entries.filter(
|
|
228
|
+
(e) => e.event === "todo-moved-to-done"
|
|
229
|
+
).length,
|
|
230
|
+
lowConfidenceCount: lowConfidence.length,
|
|
231
|
+
forcedTransitions: forced.length,
|
|
232
|
+
moveBlockedCount: moveBlocked.length
|
|
233
|
+
};
|
|
234
|
+
const critical = violations.filter((v) => v.severity === "critical");
|
|
235
|
+
const pitfalls = critical.map((v) => ({
|
|
236
|
+
vault: "default",
|
|
237
|
+
concept: `pitfall:${v.code.toLowerCase().replace(/_/g, "-")}`,
|
|
238
|
+
type: "pitfall",
|
|
239
|
+
content: `## ${v.code}
|
|
240
|
+
|
|
241
|
+
${v.message}
|
|
242
|
+
|
|
243
|
+
**Evidence**: ${v.evidence}
|
|
244
|
+
|
|
245
|
+
**Run**: ${targetRunId}`,
|
|
246
|
+
tags: ["luca", "pipeline", "postmortem", v.code.toLowerCase()],
|
|
247
|
+
op_id: `${targetRunId}:${v.code}:${v.evidenceFingerprint}`
|
|
248
|
+
}));
|
|
249
|
+
return {
|
|
250
|
+
runId: targetRunId,
|
|
251
|
+
startedAt,
|
|
252
|
+
endedAt,
|
|
253
|
+
durationMs,
|
|
254
|
+
phases,
|
|
255
|
+
violations,
|
|
256
|
+
metrics,
|
|
257
|
+
pitfalls
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function computePostmortemExitCode(report) {
|
|
261
|
+
return report.violations.some((v) => v.severity === "critical") ? 1 : 0;
|
|
262
|
+
}
|
|
263
|
+
function renderPostmortemMarkdown(report) {
|
|
264
|
+
const lines = [];
|
|
265
|
+
lines.push(`# Postmortem \u2014 Run ${report.runId}`, "");
|
|
266
|
+
lines.push(`- **Started**: ${report.startedAt ?? "unknown"}`);
|
|
267
|
+
lines.push(`- **Ended**: ${report.endedAt ?? "unknown"}`);
|
|
268
|
+
if (report.durationMs !== void 0) {
|
|
269
|
+
lines.push(`- **Duration**: ${Math.round(report.durationMs / 6e4)} min`);
|
|
270
|
+
}
|
|
271
|
+
lines.push("");
|
|
272
|
+
const critical = report.violations.filter((v) => v.severity === "critical");
|
|
273
|
+
const warnings = report.violations.filter((v) => v.severity === "warning");
|
|
274
|
+
lines.push("## Violations", "");
|
|
275
|
+
lines.push(`- **Critical**: ${critical.length}`);
|
|
276
|
+
lines.push(`- **Warning**: ${warnings.length}`, "");
|
|
277
|
+
if (report.violations.length > 0) {
|
|
278
|
+
lines.push("| Severity | Code | Message |", "| --- | --- | --- |");
|
|
279
|
+
for (const v of report.violations) {
|
|
280
|
+
lines.push(
|
|
281
|
+
`| ${v.severity} | \`${v.code}\` | ${v.message.replace(/\|/g, "\\|")} |`
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
lines.push("");
|
|
285
|
+
}
|
|
286
|
+
lines.push("## Phases", "");
|
|
287
|
+
if (report.phases.length === 0) {
|
|
288
|
+
lines.push("_No phases recorded._", "");
|
|
289
|
+
}
|
|
290
|
+
for (const p of report.phases) {
|
|
291
|
+
lines.push(`### ${p.name}`, "");
|
|
292
|
+
lines.push(
|
|
293
|
+
`- Started: ${p.startedAt ?? "?"} | Completed: ${p.completedAt ?? "?"}`
|
|
294
|
+
);
|
|
295
|
+
if (p.diff) {
|
|
296
|
+
const status = p.diff.indeterminate ? "_(indeterminate \u2014 non-git or no snapshot)_" : p.diff.isEmpty ? "\u26A0 EMPTY" : `${p.diff.filesChanged.length} files, ${p.diff.commitsAdded.length} commits`;
|
|
297
|
+
lines.push(`- Diff: ${status}`);
|
|
298
|
+
}
|
|
299
|
+
if (p.justification) {
|
|
300
|
+
lines.push(
|
|
301
|
+
`- Empty-phase justification: \`${p.justification.category}\` \u2014 ${p.justification.reasoning}`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (p.verifications.length > 0) {
|
|
305
|
+
lines.push(
|
|
306
|
+
`- Verifications: ${p.verifications.map((v) => `wave ${v.wave}: ${v.status}`).join(", ")}`
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
if (p.todosMovedToDone.length > 0) {
|
|
310
|
+
lines.push(
|
|
311
|
+
`- Todos moved to done: ${p.todosMovedToDone.map((t) => t.slug).join(", ")}`
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
lines.push("");
|
|
315
|
+
}
|
|
316
|
+
lines.push("## Metrics", "");
|
|
317
|
+
for (const [k, v] of Object.entries(report.metrics)) {
|
|
318
|
+
lines.push(`- **${k}**: ${v}`);
|
|
319
|
+
}
|
|
320
|
+
lines.push("");
|
|
321
|
+
lines.push("## What to do next", "");
|
|
322
|
+
if (critical.length > 0) {
|
|
323
|
+
lines.push(
|
|
324
|
+
"- \u{1F6D1} **Critical violations present.** Finalize cannot create a PR until these are resolved. Re-enter pipeline at execute or review."
|
|
325
|
+
);
|
|
326
|
+
} else if (warnings.length > 0) {
|
|
327
|
+
lines.push(
|
|
328
|
+
"- \u26A0 Warnings present but non-blocking. Review the table above before merging."
|
|
329
|
+
);
|
|
330
|
+
} else {
|
|
331
|
+
lines.push("- \u2705 No violations detected. Run is clean.");
|
|
332
|
+
}
|
|
333
|
+
lines.push("");
|
|
334
|
+
return lines.join("\n");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function gatherRunArtifacts(opts) {
|
|
338
|
+
const { cwd, runId } = opts;
|
|
339
|
+
const verifications = [];
|
|
340
|
+
const confidence = [];
|
|
341
|
+
for (const slug of listPhaseSlugs(cwd)) {
|
|
342
|
+
const verification = readVerificationResult({
|
|
343
|
+
cwd,
|
|
344
|
+
slug,
|
|
345
|
+
currentRunId: runId
|
|
346
|
+
});
|
|
347
|
+
if (verification) verifications.push(verification);
|
|
348
|
+
confidence.push(...readConfidenceJournal({ cwd, slug }));
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
runId,
|
|
352
|
+
entries: readLedgerForRun({ cwd, runId }),
|
|
353
|
+
verifications,
|
|
354
|
+
confidence
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export { analyzeRun as a, computePostmortemExitCode as c, gatherRunArtifacts as g, renderPostmortemMarkdown as r };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
async function loadCurrentConfig(opts) {
|
|
6
|
+
const configPath = join(opts.cwd, ".luca", "config.json");
|
|
7
|
+
if (!existsSync(configPath)) return {};
|
|
8
|
+
try {
|
|
9
|
+
const raw = JSON.parse(await readFile(configPath, "utf-8"));
|
|
10
|
+
if (raw && typeof raw === "object" && !Array.isArray(raw)) {
|
|
11
|
+
return raw;
|
|
12
|
+
}
|
|
13
|
+
return {};
|
|
14
|
+
} catch {
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { loadCurrentConfig as l };
|