@codyswann/lisa 2.254.0 → 2.256.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/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/lisa/.codex-plugin/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +34 -0
- package/plugins/lisa/scripts/automation-run-record.mjs +110 -0
- package/plugins/lisa/scripts/automation-status-claude-adapter.mjs +64 -8
- package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +61 -5
- package/plugins/lisa/scripts/automation-status-report.mjs +30 -0
- package/plugins/lisa/scripts/automation-status-run-history.mjs +185 -0
- package/plugins/lisa/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/lisa/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/lisa/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/lisa/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/lisa/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/lisa/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +34 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/automation-run-record.mjs +110 -0
- package/plugins/lisa-agy/scripts/automation-status-claude-adapter.mjs +64 -8
- package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +61 -5
- package/plugins/lisa-agy/scripts/automation-status-report.mjs +30 -0
- package/plugins/lisa-agy/scripts/automation-status-run-history.mjs +185 -0
- package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/lisa-agy/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/lisa-agy/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/lisa-agy/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/lisa-agy/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/lisa-agy/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +34 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/scripts/automation-run-record.mjs +110 -0
- package/plugins/lisa-copilot/scripts/automation-status-claude-adapter.mjs +64 -8
- package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +61 -5
- package/plugins/lisa-copilot/scripts/automation-status-report.mjs +30 -0
- package/plugins/lisa-copilot/scripts/automation-status-run-history.mjs +185 -0
- package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/lisa-copilot/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/lisa-copilot/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/lisa-copilot/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/lisa-copilot/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/lisa-copilot/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +34 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/scripts/automation-run-record.mjs +110 -0
- package/plugins/lisa-cursor/scripts/automation-status-claude-adapter.mjs +64 -8
- package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +61 -5
- package/plugins/lisa-cursor/scripts/automation-status-report.mjs +30 -0
- package/plugins/lisa-cursor/scripts/automation-status-run-history.mjs +185 -0
- package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/lisa-cursor/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/lisa-cursor/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/lisa-cursor/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/lisa-cursor/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/lisa-cursor/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +34 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/scripts/automation-run-record.mjs +110 -0
- package/plugins/src/base/scripts/automation-status-claude-adapter.mjs +64 -8
- package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +61 -5
- package/plugins/src/base/scripts/automation-status-report.mjs +30 -0
- package/plugins/src/base/scripts/automation-status-run-history.mjs +185 -0
- package/plugins/src/base/skills/lisa-automation-status/SKILL.md +11 -3
- package/plugins/src/base/skills/lisa-exploratory-qa/SKILL.md +30 -0
- package/plugins/src/base/skills/lisa-intake/SKILL.md +40 -0
- package/plugins/src/base/skills/lisa-learnings-audit/SKILL.md +46 -10
- package/plugins/src/base/skills/lisa-monitor/SKILL.md +36 -0
- package/plugins/src/base/skills/lisa-project-ideation/SKILL.md +36 -0
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +34 -0
|
@@ -22,6 +22,10 @@ import {
|
|
|
22
22
|
createAutomationGroupBins,
|
|
23
23
|
renderAutomationGroups,
|
|
24
24
|
} from "./automation-status-expected-fleet.mjs";
|
|
25
|
+
import {
|
|
26
|
+
resolveAutomationRunDisplay,
|
|
27
|
+
resolveRecoveryEscalation,
|
|
28
|
+
} from "./automation-status-run-history.mjs";
|
|
25
29
|
|
|
26
30
|
const CODEx_RUNTIME_LABEL = "Codex automations";
|
|
27
31
|
const RUN_TIMESTAMP_PATTERN = /20\d{2}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z/;
|
|
@@ -132,6 +136,7 @@ async function execGitWithRetry(args, options, attempts = 4) {
|
|
|
132
136
|
* @param {{
|
|
133
137
|
* readonly expectedFleet: ExpectedFleet
|
|
134
138
|
* readonly automationsDir?: string
|
|
139
|
+
* readonly projectRoot?: string
|
|
135
140
|
* readonly now?: string | Date
|
|
136
141
|
* }} input
|
|
137
142
|
* @returns {Promise<{
|
|
@@ -147,6 +152,10 @@ async function execGitWithRetry(args, options, attempts = 4) {
|
|
|
147
152
|
* readonly expectedCadence?: string
|
|
148
153
|
* readonly expectedCommand?: string
|
|
149
154
|
* readonly observed?: string
|
|
155
|
+
* readonly runbook?: string
|
|
156
|
+
* readonly lastOutcome?: { readonly ts: string, readonly outcome: string, readonly summary: string }
|
|
157
|
+
* readonly outcomeHistory?: readonly string[]
|
|
158
|
+
* readonly olderRecordCount?: number
|
|
150
159
|
* readonly remediation?: string
|
|
151
160
|
* }[]
|
|
152
161
|
* }[]
|
|
@@ -156,11 +165,14 @@ async function execGitWithRetry(args, options, attempts = 4) {
|
|
|
156
165
|
export async function inspectCodexAutomationFleet(input) {
|
|
157
166
|
const expectedFleet = input.expectedFleet;
|
|
158
167
|
const now = normalizeDate(input.now);
|
|
168
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
159
169
|
const observedAutomations = await listCodexAutomations({
|
|
160
170
|
automationsDir: input.automationsDir,
|
|
161
171
|
automationPrefix: expectedFleet.automationPrefix,
|
|
162
172
|
});
|
|
163
173
|
|
|
174
|
+
const runDisplays = await resolveFleetRunDisplays(projectRoot, expectedFleet);
|
|
175
|
+
|
|
164
176
|
const expectedGroups = createAutomationGroupBins();
|
|
165
177
|
|
|
166
178
|
const comparisons = compareAutomationFleet({
|
|
@@ -177,17 +189,23 @@ export async function inspectCodexAutomationFleet(input) {
|
|
|
177
189
|
expected,
|
|
178
190
|
comparison,
|
|
179
191
|
now,
|
|
192
|
+
runDisplay: runDisplays.get(expected.id),
|
|
180
193
|
})
|
|
181
194
|
);
|
|
182
195
|
}
|
|
183
196
|
|
|
184
197
|
for (const unsupported of expectedFleet.unsupported) {
|
|
198
|
+
const runDisplay = runDisplays.get(unsupported.id);
|
|
185
199
|
assignToAutomationGroup(expectedGroups, unsupported.group, {
|
|
186
200
|
id: unsupported.automationId,
|
|
187
201
|
status: "UNSUPPORTED",
|
|
188
202
|
summary: unsupported.reason,
|
|
189
203
|
expectedCadence: unsupported.expectedCadence,
|
|
190
204
|
observed: "No automation is expected for this repo/runtime combination.",
|
|
205
|
+
runbook: runDisplay?.runbook,
|
|
206
|
+
lastOutcome: runDisplay?.lastOutcome,
|
|
207
|
+
outcomeHistory: runDisplay?.outcomeHistory,
|
|
208
|
+
olderRecordCount: runDisplay?.olderRecordCount,
|
|
191
209
|
});
|
|
192
210
|
}
|
|
193
211
|
|
|
@@ -374,15 +392,43 @@ async function readCodexAutomation(automationDir) {
|
|
|
374
392
|
};
|
|
375
393
|
}
|
|
376
394
|
|
|
395
|
+
/**
|
|
396
|
+
* Read the read-only run-history display for every expected and unsupported
|
|
397
|
+
* loop up front, keyed by the loop's short id, so the per-entry item builder
|
|
398
|
+
* stays synchronous.
|
|
399
|
+
*
|
|
400
|
+
* @param {string} projectRoot
|
|
401
|
+
* @param {ExpectedFleet} expectedFleet
|
|
402
|
+
* @returns {Promise<Map<string, import("./automation-status-run-history.mjs").AutomationRunDisplay>>}
|
|
403
|
+
*/
|
|
404
|
+
async function resolveFleetRunDisplays(projectRoot, expectedFleet) {
|
|
405
|
+
const entries = [...expectedFleet.expected, ...expectedFleet.unsupported];
|
|
406
|
+
const displays = await Promise.all(
|
|
407
|
+
entries.map(entry =>
|
|
408
|
+
resolveAutomationRunDisplay({
|
|
409
|
+
projectRoot,
|
|
410
|
+
loopId: entry.id,
|
|
411
|
+
runbookPath: entry.runbookPath,
|
|
412
|
+
})
|
|
413
|
+
)
|
|
414
|
+
);
|
|
415
|
+
return new Map(entries.map((entry, index) => [entry.id, displays[index]]));
|
|
416
|
+
}
|
|
417
|
+
|
|
377
418
|
function createObservedStatusItem(input) {
|
|
378
419
|
const expected = input.expected;
|
|
379
420
|
const comparison = input.comparison;
|
|
380
421
|
const observed = comparison.observedAutomation;
|
|
422
|
+
const runDisplay = input.runDisplay;
|
|
381
423
|
const runSignal = classifyAutomationRunSignal({
|
|
382
424
|
expected,
|
|
383
425
|
observedAutomation: observed,
|
|
384
426
|
now: input.now,
|
|
385
427
|
});
|
|
428
|
+
// Three or more consecutive recorded recovery-required runs flip the loop to
|
|
429
|
+
// FAILING even when the scheduler entry looks fine — the fleet-health signal
|
|
430
|
+
// the raw backing store cannot see. It wins over the scheduler run-signal.
|
|
431
|
+
const escalation = resolveRecoveryEscalation(runDisplay);
|
|
386
432
|
|
|
387
433
|
const observedDetails = [comparison.observed];
|
|
388
434
|
if (observed?.status) {
|
|
@@ -401,20 +447,30 @@ function createObservedStatusItem(input) {
|
|
|
401
447
|
}
|
|
402
448
|
|
|
403
449
|
const status =
|
|
450
|
+
escalation?.status ??
|
|
404
451
|
runSignal?.status ??
|
|
405
452
|
/** @type {"HEALTHY" | "MISSING" | "DRIFTED"} */ (comparison.status);
|
|
406
453
|
|
|
407
454
|
return {
|
|
408
455
|
id: expected.automationId,
|
|
409
456
|
status,
|
|
410
|
-
summary:
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
457
|
+
summary: escalation
|
|
458
|
+
? escalation.summary
|
|
459
|
+
: composeAutomationSummary({
|
|
460
|
+
comparison,
|
|
461
|
+
runSignal,
|
|
462
|
+
}),
|
|
414
463
|
expectedCadence: expected.expectedCadence,
|
|
415
464
|
expectedCommand: expected.expectedCommand,
|
|
416
465
|
observed: observedDetails.join(" "),
|
|
417
|
-
|
|
466
|
+
runbook: runDisplay?.runbook,
|
|
467
|
+
lastOutcome: runDisplay?.lastOutcome,
|
|
468
|
+
outcomeHistory: runDisplay?.outcomeHistory,
|
|
469
|
+
olderRecordCount: runDisplay?.olderRecordCount,
|
|
470
|
+
remediation:
|
|
471
|
+
escalation?.remediation ??
|
|
472
|
+
runSignal?.remediation ??
|
|
473
|
+
comparison.remediation,
|
|
418
474
|
};
|
|
419
475
|
}
|
|
420
476
|
|
|
@@ -26,12 +26,22 @@ export const AUTOMATION_FLEET_VERDICTS = [
|
|
|
26
26
|
* @typedef {"HEALTHY" | "PARTIAL_SUPPORT" | "ATTENTION_NEEDED"} AutomationFleetVerdict
|
|
27
27
|
*
|
|
28
28
|
* @typedef {{
|
|
29
|
+
* readonly ts: string
|
|
30
|
+
* readonly outcome: string
|
|
31
|
+
* readonly summary: string
|
|
32
|
+
* }} AutomationLastOutcome
|
|
33
|
+
*
|
|
34
|
+
* @typedef {{
|
|
29
35
|
* readonly id: string
|
|
30
36
|
* readonly status: AutomationHealthStatus
|
|
31
37
|
* readonly summary: string
|
|
32
38
|
* readonly expectedCadence?: string
|
|
33
39
|
* readonly expectedCommand?: string
|
|
34
40
|
* readonly observed?: string
|
|
41
|
+
* readonly runbook?: string
|
|
42
|
+
* readonly lastOutcome?: AutomationLastOutcome
|
|
43
|
+
* readonly outcomeHistory?: readonly string[]
|
|
44
|
+
* readonly olderRecordCount?: number
|
|
35
45
|
* readonly remediation?: string
|
|
36
46
|
* }} AutomationStatusItem
|
|
37
47
|
*
|
|
@@ -130,6 +140,26 @@ export function renderAutomationStatusReport(input) {
|
|
|
130
140
|
if (item.observed) {
|
|
131
141
|
lines.push(` Observed: ${item.observed}`);
|
|
132
142
|
}
|
|
143
|
+
// Contract + run-history block: observable facts about where the runbook
|
|
144
|
+
// lives and how recent runs ended, kept between Observed and Remediation
|
|
145
|
+
// so operators read the state before the fix. Every line degrades
|
|
146
|
+
// explicitly — an absent runbook or empty history is stated, never blank.
|
|
147
|
+
if (item.runbook !== undefined) {
|
|
148
|
+
lines.push(` Runbook: ${item.runbook}`);
|
|
149
|
+
lines.push(
|
|
150
|
+
item.lastOutcome
|
|
151
|
+
? ` Last run: ${item.lastOutcome.outcome} — ${item.lastOutcome.summary} (${item.lastOutcome.ts})`
|
|
152
|
+
: " Last run: no recorded runs yet"
|
|
153
|
+
);
|
|
154
|
+
if (item.outcomeHistory && item.outcomeHistory.length > 0) {
|
|
155
|
+
const historyLine = ` History: ${item.outcomeHistory.join(", ")} (newest first)`;
|
|
156
|
+
lines.push(
|
|
157
|
+
item.olderRecordCount && item.olderRecordCount > 0
|
|
158
|
+
? `${historyLine}; … and ${item.olderRecordCount} older records`
|
|
159
|
+
: historyLine
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
133
163
|
if (item.remediation) {
|
|
134
164
|
lines.push(` Remediation: ${item.remediation}`);
|
|
135
165
|
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shared read-only run-history projection for `/lisa:automation-status`.
|
|
4
|
+
*
|
|
5
|
+
* The durable run-outcome substrate (RBC-3, `automation-run-record.mjs`) stores
|
|
6
|
+
* one bounded JSONL file per registered loop under `.lisa/automations/runs/`.
|
|
7
|
+
* This module turns that substrate — plus the checked-in per-loop runbook
|
|
8
|
+
* (`automation-runbook-contract`) — into the per-item display fields both
|
|
9
|
+
* runtime adapters attach to their status rows, so the Codex and Claude
|
|
10
|
+
* adapters never diverge on how the contract, last outcome, and bounded history
|
|
11
|
+
* are rendered.
|
|
12
|
+
*
|
|
13
|
+
* STRICTLY READ-ONLY: it stats the runbook and reads the run-record file. It
|
|
14
|
+
* never creates the runs directory, a runbook, or a run record — a status read
|
|
15
|
+
* must leave the fleet byte-identical.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fs from "node:fs/promises";
|
|
19
|
+
import path from "node:path";
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
automationRunRecordPath,
|
|
23
|
+
readAutomationRunRecords,
|
|
24
|
+
} from "./automation-run-record.mjs";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Inline history cap: the newest N outcomes are listed, the remainder is only
|
|
28
|
+
* counted. A bounded view of an already-bounded file, with the trim stated in
|
|
29
|
+
* the output rather than applied silently.
|
|
30
|
+
*/
|
|
31
|
+
export const AUTOMATION_RUN_HISTORY_DISPLAY_LIMIT = 5;
|
|
32
|
+
|
|
33
|
+
/** The recorded outcome that signals a loop could not finish its own work. */
|
|
34
|
+
export const RECOVERY_REQUIRED_OUTCOME = "recovery-required";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* How many trailing `recovery-required` runs in a row flip a loop to `FAILING`.
|
|
38
|
+
* The RBC-5 acceptance criteria pin "last three runs" as the escalation
|
|
39
|
+
* trigger; that wins over the ticket prose's looser "five in a row" pattern.
|
|
40
|
+
*/
|
|
41
|
+
export const RECOVERY_REQUIRED_ESCALATION_THRESHOLD = 3;
|
|
42
|
+
|
|
43
|
+
const RUNBOOK_NOT_SCAFFOLDED_LINE =
|
|
44
|
+
"not scaffolded — run /lisa:setup-automations";
|
|
45
|
+
const NO_RECORDED_RUNS_LINE = "no recorded runs yet";
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* @typedef {{
|
|
49
|
+
* readonly ts: string
|
|
50
|
+
* readonly outcome: string
|
|
51
|
+
* readonly summary: string
|
|
52
|
+
* }} AutomationLastOutcome
|
|
53
|
+
*
|
|
54
|
+
* @typedef {{
|
|
55
|
+
* readonly runbook: string
|
|
56
|
+
* readonly lastOutcome?: AutomationLastOutcome
|
|
57
|
+
* readonly outcomeHistory: readonly string[]
|
|
58
|
+
* readonly olderRecordCount: number
|
|
59
|
+
* readonly recoveryRequiredStreak: number
|
|
60
|
+
* readonly recoverySummaries: readonly string[]
|
|
61
|
+
* readonly skippedCorruptLines: number
|
|
62
|
+
* }} AutomationRunDisplay
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Resolve the read-only run-history display fields for one registered loop.
|
|
67
|
+
*
|
|
68
|
+
* @param {{
|
|
69
|
+
* readonly projectRoot?: string
|
|
70
|
+
* readonly loopId: string
|
|
71
|
+
* readonly runbookPath?: string
|
|
72
|
+
* }} input
|
|
73
|
+
* @returns {Promise<AutomationRunDisplay>}
|
|
74
|
+
*/
|
|
75
|
+
export async function resolveAutomationRunDisplay(input) {
|
|
76
|
+
const projectRoot = path.resolve(input.projectRoot ?? process.cwd());
|
|
77
|
+
const runbook = await resolveRunbookLine(projectRoot, input.runbookPath);
|
|
78
|
+
const { records, skippedCorruptLines } = await readAutomationRunRecords(
|
|
79
|
+
automationRunRecordPath(projectRoot, input.loopId)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const newestFirst = records.toReversed();
|
|
83
|
+
const latest = records.at(-1);
|
|
84
|
+
const lastOutcome = latest
|
|
85
|
+
? { ts: latest.ts, outcome: latest.outcome, summary: latest.summary }
|
|
86
|
+
: undefined;
|
|
87
|
+
const outcomeHistory = newestFirst
|
|
88
|
+
.slice(0, AUTOMATION_RUN_HISTORY_DISPLAY_LIMIT)
|
|
89
|
+
.map(record => record.outcome);
|
|
90
|
+
const olderRecordCount = Math.max(
|
|
91
|
+
0,
|
|
92
|
+
records.length - AUTOMATION_RUN_HISTORY_DISPLAY_LIMIT
|
|
93
|
+
);
|
|
94
|
+
const trailingRecovery = collectTrailingRecoveryRuns(records);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
runbook,
|
|
98
|
+
lastOutcome,
|
|
99
|
+
outcomeHistory,
|
|
100
|
+
olderRecordCount,
|
|
101
|
+
recoveryRequiredStreak: trailingRecovery.length,
|
|
102
|
+
recoverySummaries: trailingRecovery.map(record => record.summary),
|
|
103
|
+
skippedCorruptLines,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Force a `FAILING` verdict when a loop has recorded three or more consecutive
|
|
109
|
+
* `recovery-required` runs — the fleet-health signal the scheduler surface
|
|
110
|
+
* cannot see on its own. Returns a run-signal-shaped escalation, or null when
|
|
111
|
+
* the streak is below the threshold.
|
|
112
|
+
*
|
|
113
|
+
* @param {AutomationRunDisplay | undefined} runDisplay
|
|
114
|
+
* @returns {{ readonly status: "FAILING", readonly summary: string, readonly remediation: string } | null}
|
|
115
|
+
*/
|
|
116
|
+
export function resolveRecoveryEscalation(runDisplay) {
|
|
117
|
+
if (
|
|
118
|
+
!runDisplay ||
|
|
119
|
+
runDisplay.recoveryRequiredStreak < RECOVERY_REQUIRED_ESCALATION_THRESHOLD
|
|
120
|
+
) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Number the citations rather than joining on a delimiter: recorded summaries
|
|
125
|
+
// carry their own `;` and `.`, so a `; `-separated list is ambiguous. `(1) …
|
|
126
|
+
// (2) …` stays legible no matter what punctuation a summary contains.
|
|
127
|
+
const citations = runDisplay.recoverySummaries
|
|
128
|
+
.map((summary, index) => `(${index + 1}) ${summary}`)
|
|
129
|
+
.join(" ");
|
|
130
|
+
return {
|
|
131
|
+
status: "FAILING",
|
|
132
|
+
summary: `the last ${runDisplay.recoveryRequiredStreak} recorded runs all required recovery`,
|
|
133
|
+
remediation: `Repeated recovery-required runs — ${citations} Inspect the loop's runbook and the cited runs, fix the recurring failure, then let the next scheduled run confirm recovery.`,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* @param {string} projectRoot
|
|
139
|
+
* @param {string | undefined} runbookPath
|
|
140
|
+
* @returns {Promise<string>}
|
|
141
|
+
*/
|
|
142
|
+
async function resolveRunbookLine(projectRoot, runbookPath) {
|
|
143
|
+
if (!runbookPath) {
|
|
144
|
+
return RUNBOOK_NOT_SCAFFOLDED_LINE;
|
|
145
|
+
}
|
|
146
|
+
const scaffolded = await runbookIsScaffolded(
|
|
147
|
+
path.join(projectRoot, runbookPath)
|
|
148
|
+
);
|
|
149
|
+
return scaffolded ? runbookPath : RUNBOOK_NOT_SCAFFOLDED_LINE;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Read-only existence check for a checked-in runbook. Any stat failure — the
|
|
154
|
+
* file is absent, unreadable, or not a regular file — degrades to "not
|
|
155
|
+
* scaffolded" so the status read never implies health it could not confirm and
|
|
156
|
+
* never throws mid-report.
|
|
157
|
+
*
|
|
158
|
+
* @param {string} runbookAbsolutePath
|
|
159
|
+
* @returns {Promise<boolean>}
|
|
160
|
+
*/
|
|
161
|
+
async function runbookIsScaffolded(runbookAbsolutePath) {
|
|
162
|
+
try {
|
|
163
|
+
const stat = await fs.stat(runbookAbsolutePath);
|
|
164
|
+
return stat.isFile();
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @param {readonly import("./automation-run-record.mjs").AutomationRunRecord[]} records
|
|
172
|
+
* @returns {readonly import("./automation-run-record.mjs").AutomationRunRecord[]} trailing recovery-required runs, newest first
|
|
173
|
+
*/
|
|
174
|
+
function collectTrailingRecoveryRuns(records) {
|
|
175
|
+
const trailing = [];
|
|
176
|
+
for (let index = records.length - 1; index >= 0; index -= 1) {
|
|
177
|
+
if (records[index].outcome !== RECOVERY_REQUIRED_OUTCOME) {
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
trailing.push(records[index]);
|
|
181
|
+
}
|
|
182
|
+
return trailing;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export { RUNBOOK_NOT_SCAFFOLDED_LINE, NO_RECORDED_RUNS_LINE };
|
|
@@ -53,8 +53,13 @@ For each expected automation, report:
|
|
|
53
53
|
4. Any detected drift in name, cadence, command shape, or queue arguments.
|
|
54
54
|
5. Any available recent-run health signal such as stale last-run timing or repeated failure status.
|
|
55
55
|
6. A concise remediation hint when attention is needed.
|
|
56
|
+
7. Where the loop's checked-in runbook lives (`automation-runbook-contract`), or `not scaffolded — run /lisa:setup-automations` when it is absent.
|
|
57
|
+
8. How the last recorded run ended — its outcome, one-line summary, and timestamp — or `no recorded runs yet` when the loop has never recorded one.
|
|
58
|
+
9. A bounded run history: the five most recent outcomes newest first, plus a count of how many older records exist when the file holds more.
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
The last-run and history facts come from the durable run-outcome substrate recorded by each loop (the RBC-3 records under `.lisa/automations/runs/<loop-id>.jsonl`, read through `readAutomationRunRecords`). That directory is local scheduler state, not project knowledge — read it, never write it.
|
|
61
|
+
|
|
62
|
+
Emit an overall grouped fleet verdict such as `HEALTHY`, `ATTENTION_NEEDED`, or `PARTIAL_SUPPORT`, plus the runtime surface inspected. **Repeated recovery is a fleet-health signal the scheduler cannot see**: when a loop's last three or more consecutive recorded runs are all `recovery-required`, report that loop `FAILING` — and therefore the overall verdict `ATTENTION_NEEDED` — even when its scheduler entry itself looks healthy, and point the remediation at the recorded run summaries.
|
|
58
63
|
|
|
59
64
|
## Operator usage
|
|
60
65
|
|
|
@@ -89,7 +94,7 @@ Status-specific remediation guidance:
|
|
|
89
94
|
- `MISSING`: tell the operator which job is absent and recommend rerunning `/lisa:setup-automations` or recreating the missing job with the expected cadence and command.
|
|
90
95
|
- `DRIFTED`: show the expected versus observed cadence/command mismatch and recommend aligning the scheduler entry with Lisa's current setup contract, usually by rerunning `/lisa:setup-automations`.
|
|
91
96
|
- `STALE`: explain that the job exists but has not run recently enough for its cadence. Recommend inspecting the runtime's recent-run history or failure logs before changing queue state.
|
|
92
|
-
- `FAILING`: surface the failure signal directly and recommend checking the latest runtime error plus the affected queue command (`/lisa:intake`, `/lisa:repair-intake`, or exploratory job) after the scheduler issue is resolved.
|
|
97
|
+
- `FAILING`: surface the failure signal directly and recommend checking the latest runtime error plus the affected queue command (`/lisa:intake`, `/lisa:repair-intake`, or exploratory job) after the scheduler issue is resolved. A loop also reports `FAILING` when its last three or more consecutive recorded runs are all `recovery-required` — even with a healthy scheduler entry — in which case cite the recorded run summaries and point the operator at the loop's runbook.
|
|
93
98
|
- `UNSUPPORTED`: explain why the job is intentionally absent and say that no remediation is required unless the project stack or runtime support changed.
|
|
94
99
|
|
|
95
100
|
Render the report in grouped sections using the shared `scripts/automation-status-report.mjs` contract:
|
|
@@ -104,10 +109,13 @@ Generated at: <ISO timestamp>
|
|
|
104
109
|
- <STATUS> <automation-id>: <summary>
|
|
105
110
|
Expected: <cadence> -> <command>
|
|
106
111
|
Observed: <what the runtime exposed>
|
|
112
|
+
Runbook: <.lisa/automations/<loop-id>.runbook.md, or "not scaffolded — run /lisa:setup-automations">
|
|
113
|
+
Last run: <outcome> — <summary> (<ts>), or "no recorded runs yet"
|
|
114
|
+
History: <outcome>, <outcome>, … (newest first); … and <N> older records
|
|
107
115
|
Remediation: <next step when attention is needed>
|
|
108
116
|
```
|
|
109
117
|
|
|
110
|
-
Keep observable runtime facts separate from remediation guidance so operators can distinguish drift, unsupported jobs, and actual failures quickly.
|
|
118
|
+
The Runbook, Last run, and History lines are observable facts and sit between Observed and Remediation. Keep observable runtime facts separate from remediation guidance so operators can distinguish drift, unsupported jobs, and actual failures quickly.
|
|
111
119
|
|
|
112
120
|
## Rules
|
|
113
121
|
|
|
@@ -85,6 +85,36 @@ No report file. Emit a concise in-session summary:
|
|
|
85
85
|
- **Findings filed**, bucketed by type — each with its **created or referenced ticket ref** and **build-ready state**.
|
|
86
86
|
- **Observed but not filed:** anything noticed but intentionally not ticketed (including forbidden-mutation blocks), with why.
|
|
87
87
|
|
|
88
|
+
## Run outcome
|
|
89
|
+
|
|
90
|
+
As the registered `exploratory-bugs` automation loop, this pass conforms to the
|
|
91
|
+
`automation-runbook-contract` rule: every invocation ends in **exactly one** of the six run outcomes
|
|
92
|
+
and records it, so a quiet run and a broken run are never mistaken for each other.
|
|
93
|
+
|
|
94
|
+
| This cycle's exit path | Run outcome |
|
|
95
|
+
|---|---|
|
|
96
|
+
| Findings filed — one or more `Bug` / `Improvement` tickets created or referenced (§6) | `candidate-proposed` |
|
|
97
|
+
| Clean pass — explored the personas and surfaces, nothing worth filing | `nothing-needed` |
|
|
98
|
+
| Tracker unconfigured — the §1 stop path; findings cannot be filed | `recovery-required` |
|
|
99
|
+
| A degradation that still let the pass explore (e.g. Kane unavailable, one persona unreachable) | the outcome it actually reached above, with the summary **leading with the degradation** — degradation never mints a seventh token |
|
|
100
|
+
|
|
101
|
+
Record **exactly one** outcome per invocation through the run-record CLI, naming this loop's runbook
|
|
102
|
+
(the `--summary` is the operator-readable one-liner in the contract's exemplar voice — plain,
|
|
103
|
+
specific, actionable, e.g. `Explored 4 personas; nothing confusing to file.` for `nothing-needed`):
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/automation-run-record.mjs" \
|
|
107
|
+
--loop-id exploratory-bugs --outcome candidate-proposed \
|
|
108
|
+
--summary "Explored 4 personas; filed 3 findings from the checkout flow — awaiting your flip to ready." \
|
|
109
|
+
--runbook .lisa/automations/exploratory-bugs.runbook.md [--ref <ticket-url>]...
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If `${CLAUDE_PLUGIN_ROOT}` is unset, resolve the plugin scripts directory directly — the built copy
|
|
113
|
+
`plugins/lisa/scripts/automation-run-record.mjs` or the source
|
|
114
|
+
`plugins/src/base/scripts/automation-run-record.mjs`. If recording still fails, **degrade, never
|
|
115
|
+
abort** (per `automation-runbook-contract`): note the recording failure in the run output and finish
|
|
116
|
+
the cycle — a recording failure is a degradation to report, never a reason to block the loop.
|
|
117
|
+
|
|
88
118
|
## Quality bar
|
|
89
119
|
|
|
90
120
|
- Explore as a true first-time user — judge clarity, not whether you (who can read the code) can figure it out.
|
|
@@ -116,6 +116,46 @@ The single-item skills (`lisa-plan`, `lisa-implement`) and the per-vendor batch
|
|
|
116
116
|
5. **Stop after one item** — a claimed Ready item, a safe-blocked container, or a per-item error ends the *ready-claim* portion of the cycle. The per-vendor PRD scanner still runs its rollup and one verify-prd dispatch. Remaining Ready items stay untouched for later scheduler invocations.
|
|
117
117
|
6. **Summary report** — the single processed/skipped/error item, total processed, total errors. Before returning, record intake usage on the persisted cycle-summary artifact via `lisa-usage-accounting` so the summary carries a direct `lisa-intake` entry in the canonical `## Lisa Usage` section. If the claimed / skipped work item's parent-child graph is already known, prefer `record_and_rollup` so ancestor totals refresh in the same cycle; otherwise still write the direct entry, and if runtime usage is unavailable, use `source: unavailable` with nullable token/cost fields instead of skipping the row.
|
|
118
118
|
|
|
119
|
+
## Run outcome
|
|
120
|
+
|
|
121
|
+
As a registered automation loop, each Intake cycle conforms to the `automation-runbook-contract`
|
|
122
|
+
rule: it ends in **exactly one** of the six run outcomes and records it, so a quiet queue and a
|
|
123
|
+
broken loop never look alike. Intake backs **two** registered loop-ids — record under the one that
|
|
124
|
+
matches the mode this cycle ran in: **`intake-prd`** (PRD-side dispatch) or **`intake-tickets`**
|
|
125
|
+
(build-queue dispatch).
|
|
126
|
+
|
|
127
|
+
| This cycle's exit path | Run outcome |
|
|
128
|
+
|---|---|
|
|
129
|
+
| Empty `Ready` set — the idle case (step 3), nothing to claim | `nothing-needed` |
|
|
130
|
+
| A PRD routed to `Blocked` (clarifying questions) or `Ticketed`; a build ticket claimed and dispatched | `candidate-proposed` |
|
|
131
|
+
| A build cycle that shipped and verified (merged PR + evidence), or a shipped PRD moved to `verified` | `change-proved` |
|
|
132
|
+
| A protected deployment (or other autonomy boundary the lifecycle hits) waiting on a human approval | `approval-requested` |
|
|
133
|
+
| The queue itself is misconfigured or unreadable — missing required input (step 1) or an unreachable Status/workflow (step 2/`3` misconfig) so the cycle could not run | `recovery-required` |
|
|
134
|
+
|
|
135
|
+
**Seam warning (the #1 misread in this ticket).** A run outcome describes this *cycle*; `Blocked` is
|
|
136
|
+
a *work item's* lifecycle terminal state — the two never merge in the summary. When Intake correctly
|
|
137
|
+
routes to `Blocked` (an item whose requirements are unresolvable, carrying clarifying questions), the
|
|
138
|
+
cycle **produced something**, so it is a successful run — `candidate-proposed`, and **never
|
|
139
|
+
`recovery-required`** (the machinery is not broken) and **never `nothing-needed`** (the run did not
|
|
140
|
+
find nothing). The summary must say both plainly: the item was blocked *and* the run succeeded.
|
|
141
|
+
|
|
142
|
+
Record **exactly one** outcome per invocation through the run-record CLI, naming this loop's runbook
|
|
143
|
+
(the `--summary` is the operator-readable one-liner in the contract's exemplar voice — plain,
|
|
144
|
+
specific, actionable, e.g. `Scanned 12 ready items; nothing to propose.` for `nothing-needed`):
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/automation-run-record.mjs" \
|
|
148
|
+
--loop-id intake-tickets --outcome candidate-proposed \
|
|
149
|
+
--summary "Routed PRD #1810 to Blocked with clarifying questions; the run succeeded." \
|
|
150
|
+
--runbook .lisa/automations/intake-tickets.runbook.md [--ref <item-url>]...
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
If `${CLAUDE_PLUGIN_ROOT}` is unset, resolve the plugin scripts directory directly — the built copy
|
|
154
|
+
`plugins/lisa/scripts/automation-run-record.mjs` or the source
|
|
155
|
+
`plugins/src/base/scripts/automation-run-record.mjs`. If recording still fails, **degrade, never
|
|
156
|
+
abort** (per `automation-runbook-contract`): note the recording failure in the run output and finish
|
|
157
|
+
the cycle — a recording failure is a degradation to report, never a reason to block the loop.
|
|
158
|
+
|
|
119
159
|
## Schedule examples
|
|
120
160
|
|
|
121
161
|
```text
|
|
@@ -124,7 +124,10 @@ audits the auditor without re-running it.
|
|
|
124
124
|
`drafted_artifact`; the gardener decides whether the evidence clears the
|
|
125
125
|
filing bar and attaches the router's draft to the ticket.
|
|
126
126
|
4. **Emit** (see Ticket emission).
|
|
127
|
-
5. **Report** the
|
|
127
|
+
5. **Report and record** the run outcome and one-line summary — post the
|
|
128
|
+
outcome, then record **exactly one** run-record line through the CLI (see
|
|
129
|
+
Run outcomes), including the escalation path when it ends
|
|
130
|
+
`recovery-required`.
|
|
128
131
|
|
|
129
132
|
## Ticket emission
|
|
130
133
|
|
|
@@ -280,17 +283,50 @@ marker-recoverable per-item tickets and a missing (not half-filled) batch:
|
|
|
280
283
|
the most recoverable state. Never attempt to "roll back" filed tickets —
|
|
281
284
|
close-out decisions belong to humans.
|
|
282
285
|
|
|
283
|
-
##
|
|
286
|
+
## Run outcomes
|
|
287
|
+
|
|
288
|
+
Exactly one per run, from the closed six-value vocabulary of the
|
|
289
|
+
`automation-runbook-contract` rule:
|
|
290
|
+
**`nothing-needed | candidate-proposed | change-proved | approval-requested | recovery-required | policy-obsolete`**.
|
|
291
|
+
|
|
292
|
+
This section supersedes the gardener's earlier three-value *terminal states*
|
|
293
|
+
vocabulary (`nothing-needed | candidates-proposed | blocked`); the mapping is
|
|
294
|
+
exact so nothing regresses:
|
|
295
|
+
|
|
296
|
+
- `candidates-proposed` → **`candidate-proposed`** — per-item and/or batch
|
|
297
|
+
tickets filed. Report each ticket URL, its recommendation (PROMOTE/DEMOTE +
|
|
298
|
+
rung), and the batch ticket URL with its row count.
|
|
299
|
+
- `nothing-needed` → **`nothing-needed`** (unchanged) — no candidate met any
|
|
300
|
+
recommendation threshold. Report one line: surfaces scanned, entries seen,
|
|
301
|
+
"nothing to propose".
|
|
302
|
+
- `blocked` → **`recovery-required`** when the loop itself could not complete
|
|
303
|
+
(tracker unreachable, contradictory config, a ledger-contract parse error) —
|
|
304
|
+
escalated per Escalation with the operator-readable reason; **or**
|
|
305
|
+
**`approval-requested`** when the run finished and is waiting on a human gate
|
|
306
|
+
rather than being broken.
|
|
307
|
+
|
|
308
|
+
The gardener also newly reaches **`policy-obsolete`** — the Retirement
|
|
309
|
+
condition below tripped and it filed its own teardown proposal. It never
|
|
310
|
+
produces **`change-proved`**: the gardener only files tickets and ships no
|
|
311
|
+
change itself, so proving a change is the implementing factory run's job.
|
|
312
|
+
|
|
313
|
+
Record exactly one outcome per run through the run-record CLI, naming this
|
|
314
|
+
loop's runbook (the `--summary` is the operator-readable one-liner in the
|
|
315
|
+
contract's exemplar voice — plain, specific, actionable):
|
|
284
316
|
|
|
285
|
-
|
|
317
|
+
```bash
|
|
318
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/automation-run-record.mjs" \
|
|
319
|
+
--loop-id learnings-audit --outcome candidate-proposed \
|
|
320
|
+
--summary "Proposed 2 promotions and 1 batch confirm; awaiting your flip to ready." \
|
|
321
|
+
--runbook .lisa/automations/learnings-audit.runbook.md [--ref <ticket-url>]...
|
|
322
|
+
```
|
|
286
323
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
operator-readable reason.
|
|
324
|
+
If `${CLAUDE_PLUGIN_ROOT}` is unset, resolve the plugin scripts directory
|
|
325
|
+
directly — the built copy `plugins/lisa/scripts/automation-run-record.mjs` or
|
|
326
|
+
the source `plugins/src/base/scripts/automation-run-record.mjs`. If recording
|
|
327
|
+
still fails, **degrade, never abort** (per `automation-runbook-contract`): note
|
|
328
|
+
the recording failure in the run output and finish the run — a recording
|
|
329
|
+
failure is a degradation to report, never a reason to block the loop.
|
|
294
330
|
|
|
295
331
|
## Retirement condition
|
|
296
332
|
|
|
@@ -139,3 +139,39 @@ After report, file what was found — **only when run standalone**, never under
|
|
|
139
139
|
## Output
|
|
140
140
|
|
|
141
141
|
A single report: the health/anomaly summary (failures, warnings, no-issue confirmations) + the observability audit table (each in-scope dimension as `OK` / `WARN` / `MISSING` / `PRESENT (unverified)`) + the filing summary (tickets filed with refs + fingerprints, duplicates skipped, dropped count if the cap truncated; or would-file tickets under `--dry-run`). For post-deploy verification (when called from `lisa-verify`), the report-only summary becomes evidence on the originating work item.
|
|
142
|
+
|
|
143
|
+
## Run outcome
|
|
144
|
+
|
|
145
|
+
As the registered `monitor` automation loop, the **standalone** cron run conforms to the
|
|
146
|
+
`automation-runbook-contract` rule: it ends in **exactly one** of the six run outcomes and records it,
|
|
147
|
+
so a quiet monitoring run and a broken one never look identical.
|
|
148
|
+
|
|
149
|
+
| This cycle's exit path | Run outcome |
|
|
150
|
+
|---|---|
|
|
151
|
+
| Anomalies or in-scope gaps filed — one or more `Bug` / `Task` / `Improvement` leaves created or referenced | `candidate-proposed` |
|
|
152
|
+
| Clean sweep — health/audit ran end to end, nothing over the bar and no in-scope gaps | `nothing-needed` |
|
|
153
|
+
| Provider/threshold resolution failure — threshold collection fails (a present-but-uninspectable config, an invalid configured threshold) or a signal provider is unreachable so the sweep could not run | `recovery-required` |
|
|
154
|
+
| A degradation that still let the sweep run (an optional `ops-specialist` overlay absent, Kane unavailable) | the outcome it actually reached above, with the summary **leading with the degradation** — degradation never mints a seventh token |
|
|
155
|
+
|
|
156
|
+
Only the **standalone** run records. The nested report-only modes do their own job and do not file or
|
|
157
|
+
record: `--report-only` (including the `lisa-verify` post-deploy call, whose summary is evidence on
|
|
158
|
+
the originating item) and `--dry-run` (a preview that creates nothing) are not registered-loop
|
|
159
|
+
invocations.
|
|
160
|
+
|
|
161
|
+
Record **exactly one** outcome per standalone invocation through the run-record CLI, naming this
|
|
162
|
+
loop's runbook (the `--summary` is the operator-readable one-liner in the contract's exemplar voice —
|
|
163
|
+
plain, specific, actionable, e.g. `Health green; audit clean — nothing to propose.` for
|
|
164
|
+
`nothing-needed`):
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/automation-run-record.mjs" \
|
|
168
|
+
--loop-id monitor --outcome candidate-proposed \
|
|
169
|
+
--summary "Filed #1810 for the p99 latency spike; awaiting your flip to ready." \
|
|
170
|
+
--runbook .lisa/automations/monitor.runbook.md [--ref <ticket-url>]...
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
If `${CLAUDE_PLUGIN_ROOT}` is unset, resolve the plugin scripts directory directly — the built copy
|
|
174
|
+
`plugins/lisa/scripts/automation-run-record.mjs` or the source
|
|
175
|
+
`plugins/src/base/scripts/automation-run-record.mjs`. If recording still fails, **degrade, never
|
|
176
|
+
abort** (per `automation-runbook-contract`): note the recording failure in the run output and finish
|
|
177
|
+
the cycle — a recording failure is a degradation to report, never a reason to block the loop.
|
|
@@ -275,6 +275,42 @@ Emit two distinct in-session sections (do not write a report file):
|
|
|
275
275
|
Always include the **Personas**, **What Already Exists**, **Discovery Spikes**, and **Rejected**
|
|
276
276
|
sections (even if empty) so the user sees what was considered and filtered out.
|
|
277
277
|
|
|
278
|
+
## Run outcome
|
|
279
|
+
|
|
280
|
+
As the registered `exploratory-prds` automation loop, this run conforms to the
|
|
281
|
+
`automation-runbook-contract` rule: it ends in **exactly one** of the six run outcomes and records it,
|
|
282
|
+
so a quiet ideation run and a broken one are never confused.
|
|
283
|
+
|
|
284
|
+
| This run's exit path | Run outcome |
|
|
285
|
+
|---|---|
|
|
286
|
+
| PRD(s) created or reused this run (Step 6/7 **PRDs Created**) | `candidate-proposed` |
|
|
287
|
+
| Nothing to ideate — no Practical Idea cleared the bar; nothing created | `nothing-needed` |
|
|
288
|
+
| The Step 5.5 **PRD-queue-pressure gate** blocked auto-ready creation — a human must drain the queue before another auto-ready PRD is added | `approval-requested` |
|
|
289
|
+
| The loop itself could not run — the PRD source reader failed or the queue is misconfigured (a source-reader failure snapshot, not queue pressure) | `recovery-required` |
|
|
290
|
+
| A degradation that still let ideation run (optional Codex automation memory unavailable, an inspiration source unreachable) | the outcome it actually reached above, with the summary **leading with the degradation** — degradation never mints a seventh token |
|
|
291
|
+
|
|
292
|
+
The pressure gate is `approval-requested`, **not** `recovery-required`: the loop ran fine and
|
|
293
|
+
correctly declined to add queue pressure — it is asking a human to drain the queue, not reporting a
|
|
294
|
+
broken machine. `recovery-required` is reserved for the loop failing to run at all.
|
|
295
|
+
|
|
296
|
+
Record **exactly one** outcome per invocation through the run-record CLI, naming this loop's runbook
|
|
297
|
+
(the `--summary` is the operator-readable one-liner in the contract's exemplar voice — plain,
|
|
298
|
+
specific, actionable, e.g. `Reviewed evidence; no practical idea cleared the bar — nothing to
|
|
299
|
+
propose.` for `nothing-needed`):
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
node "${CLAUDE_PLUGIN_ROOT}/scripts/automation-run-record.mjs" \
|
|
303
|
+
--loop-id exploratory-prds --outcome candidate-proposed \
|
|
304
|
+
--summary "Created PRD #1810 for offline export; awaiting your flip to ready." \
|
|
305
|
+
--runbook .lisa/automations/exploratory-prds.runbook.md [--ref <prd-url>]...
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
If `${CLAUDE_PLUGIN_ROOT}` is unset, resolve the plugin scripts directory directly — the built copy
|
|
309
|
+
`plugins/lisa/scripts/automation-run-record.mjs` or the source
|
|
310
|
+
`plugins/src/base/scripts/automation-run-record.mjs`. If recording still fails, **degrade, never
|
|
311
|
+
abort** (per `automation-runbook-contract`): note the recording failure in the run output and finish
|
|
312
|
+
the run — a recording failure is a degradation to report, never a reason to block the loop.
|
|
313
|
+
|
|
278
314
|
## Out of scope (hard rules)
|
|
279
315
|
|
|
280
316
|
- **No fabricated personas.** No evidence citation → no persona; generic roles banned without
|