@codyswann/lisa 2.255.0 → 2.256.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/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-github-validate-issue/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +9 -3
- package/plugins/lisa/agents/github-agent.md +2 -2
- package/plugins/lisa/agents/jira-agent.md +2 -2
- package/plugins/lisa/agents/linear-agent.md +2 -2
- package/plugins/lisa/rules/eager/config-resolution.md +27 -8
- package/plugins/lisa/rules/eager/pre-flight-autofill.md +21 -7
- package/plugins/lisa/rules/reference/config-resolution.md +55 -16
- package/plugins/lisa/rules/reference/pre-flight-autofill.md +23 -8
- 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-github-validate-issue/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/lisa/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +9 -3
- package/plugins/lisa-agy/agents/github-agent.md +2 -2
- package/plugins/lisa-agy/agents/jira-agent.md +2 -2
- package/plugins/lisa-agy/agents/linear-agent.md +2 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- 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-github-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +9 -3
- 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/agents/github-agent.agent.md +2 -2
- package/plugins/lisa-copilot/agents/jira-agent.agent.md +2 -2
- package/plugins/lisa-copilot/agents/linear-agent.agent.md +2 -2
- package/plugins/lisa-copilot/rules/eager/config-resolution.md +27 -8
- package/plugins/lisa-copilot/rules/eager/pre-flight-autofill.md +21 -7
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +55 -16
- package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +23 -8
- 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-github-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +9 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/agents/github-agent.md +2 -2
- package/plugins/lisa-cursor/agents/jira-agent.md +2 -2
- package/plugins/lisa-cursor/agents/linear-agent.md +2 -2
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +55 -16
- package/plugins/lisa-cursor/rules/config-resolution.mdc +27 -8
- package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +23 -8
- package/plugins/lisa-cursor/rules/pre-flight-autofill.mdc +21 -7
- 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-github-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +9 -3
- 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/agents/github-agent.md +2 -2
- package/plugins/src/base/agents/jira-agent.md +2 -2
- package/plugins/src/base/agents/linear-agent.md +2 -2
- package/plugins/src/base/rules/eager/config-resolution.md +27 -8
- package/plugins/src/base/rules/eager/pre-flight-autofill.md +21 -7
- package/plugins/src/base/rules/reference/config-resolution.md +55 -16
- package/plugins/src/base/rules/reference/pre-flight-autofill.md +23 -8
- 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-github-validate-issue/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +9 -3
- package/plugins/src/base/skills/lisa-implement/SKILL.md +6 -2
- package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +9 -6
- package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +1 -1
- package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +9 -3
|
@@ -15,6 +15,10 @@ import {
|
|
|
15
15
|
createAutomationGroupBins,
|
|
16
16
|
renderAutomationGroups,
|
|
17
17
|
} from "./automation-status-expected-fleet.mjs";
|
|
18
|
+
import {
|
|
19
|
+
resolveAutomationRunDisplay,
|
|
20
|
+
resolveRecoveryEscalation,
|
|
21
|
+
} from "./automation-status-run-history.mjs";
|
|
18
22
|
|
|
19
23
|
const CLAUDE_RUNTIME_LABEL = "Claude /schedule";
|
|
20
24
|
const CLAUDE_ACTIVE_STATUSES = new Set([
|
|
@@ -51,9 +55,10 @@ const NEGATED_FAILURE_PATTERN =
|
|
|
51
55
|
* @param {{
|
|
52
56
|
* readonly expectedFleet: ExpectedFleet
|
|
53
57
|
* readonly scheduleListing?: string | readonly unknown[] | Record<string, unknown> | null
|
|
58
|
+
* readonly projectRoot?: string
|
|
54
59
|
* readonly now?: string | Date
|
|
55
60
|
* }} input
|
|
56
|
-
* @returns {{
|
|
61
|
+
* @returns {Promise<{
|
|
57
62
|
* readonly runtime: string
|
|
58
63
|
* readonly generatedAt: string
|
|
59
64
|
* readonly groups: readonly {
|
|
@@ -66,20 +71,27 @@ const NEGATED_FAILURE_PATTERN =
|
|
|
66
71
|
* readonly expectedCadence?: string
|
|
67
72
|
* readonly expectedCommand?: string
|
|
68
73
|
* readonly observed?: string
|
|
74
|
+
* readonly runbook?: string
|
|
75
|
+
* readonly lastOutcome?: { readonly ts: string, readonly outcome: string, readonly summary: string }
|
|
76
|
+
* readonly outcomeHistory?: readonly string[]
|
|
77
|
+
* readonly olderRecordCount?: number
|
|
69
78
|
* readonly remediation?: string
|
|
70
79
|
* }[]
|
|
71
80
|
* }[]
|
|
72
81
|
* readonly observedAutomations: readonly ObservedClaudeAutomation[]
|
|
73
|
-
* }}
|
|
82
|
+
* }>}
|
|
74
83
|
*/
|
|
75
|
-
export function inspectClaudeAutomationFleet(input) {
|
|
84
|
+
export async function inspectClaudeAutomationFleet(input) {
|
|
76
85
|
const expectedFleet = input.expectedFleet;
|
|
77
86
|
const now = normalizeDate(input.now);
|
|
87
|
+
const projectRoot = input.projectRoot ?? process.cwd();
|
|
78
88
|
const observedAutomations = listClaudeAutomations({
|
|
79
89
|
scheduleListing: input.scheduleListing,
|
|
80
90
|
automationPrefix: expectedFleet.automationPrefix,
|
|
81
91
|
});
|
|
82
92
|
|
|
93
|
+
const runDisplays = await resolveFleetRunDisplays(projectRoot, expectedFleet);
|
|
94
|
+
|
|
83
95
|
const expectedGroups = createAutomationGroupBins();
|
|
84
96
|
|
|
85
97
|
const comparisons = compareAutomationFleet({
|
|
@@ -96,17 +108,23 @@ export function inspectClaudeAutomationFleet(input) {
|
|
|
96
108
|
expected,
|
|
97
109
|
comparison,
|
|
98
110
|
now,
|
|
111
|
+
runDisplay: runDisplays.get(expected.id),
|
|
99
112
|
})
|
|
100
113
|
);
|
|
101
114
|
}
|
|
102
115
|
|
|
103
116
|
for (const unsupported of expectedFleet.unsupported) {
|
|
117
|
+
const runDisplay = runDisplays.get(unsupported.id);
|
|
104
118
|
assignToAutomationGroup(expectedGroups, unsupported.group, {
|
|
105
119
|
id: unsupported.automationId,
|
|
106
120
|
status: "UNSUPPORTED",
|
|
107
121
|
summary: unsupported.reason,
|
|
108
122
|
expectedCadence: unsupported.expectedCadence,
|
|
109
123
|
observed: "No automation is expected for this repo/runtime combination.",
|
|
124
|
+
runbook: runDisplay?.runbook,
|
|
125
|
+
lastOutcome: runDisplay?.lastOutcome,
|
|
126
|
+
outcomeHistory: runDisplay?.outcomeHistory,
|
|
127
|
+
olderRecordCount: runDisplay?.olderRecordCount,
|
|
110
128
|
});
|
|
111
129
|
}
|
|
112
130
|
|
|
@@ -195,15 +213,43 @@ function extractClaudeScheduleCadence(command) {
|
|
|
195
213
|
);
|
|
196
214
|
}
|
|
197
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Read the read-only run-history display for every expected and unsupported
|
|
218
|
+
* loop up front, keyed by the loop's short id, so the per-entry item builder
|
|
219
|
+
* stays synchronous.
|
|
220
|
+
*
|
|
221
|
+
* @param {string} projectRoot
|
|
222
|
+
* @param {ExpectedFleet} expectedFleet
|
|
223
|
+
* @returns {Promise<Map<string, import("./automation-status-run-history.mjs").AutomationRunDisplay>>}
|
|
224
|
+
*/
|
|
225
|
+
async function resolveFleetRunDisplays(projectRoot, expectedFleet) {
|
|
226
|
+
const entries = [...expectedFleet.expected, ...expectedFleet.unsupported];
|
|
227
|
+
const displays = await Promise.all(
|
|
228
|
+
entries.map(entry =>
|
|
229
|
+
resolveAutomationRunDisplay({
|
|
230
|
+
projectRoot,
|
|
231
|
+
loopId: entry.id,
|
|
232
|
+
runbookPath: entry.runbookPath,
|
|
233
|
+
})
|
|
234
|
+
)
|
|
235
|
+
);
|
|
236
|
+
return new Map(entries.map((entry, index) => [entry.id, displays[index]]));
|
|
237
|
+
}
|
|
238
|
+
|
|
198
239
|
function createObservedStatusItem(input) {
|
|
199
240
|
const expected = input.expected;
|
|
200
241
|
const comparison = input.comparison;
|
|
201
242
|
const observed = comparison.observedAutomation;
|
|
243
|
+
const runDisplay = input.runDisplay;
|
|
202
244
|
const runSignal = classifyAutomationRunSignal({
|
|
203
245
|
expected,
|
|
204
246
|
observedAutomation: observed,
|
|
205
247
|
now: input.now,
|
|
206
248
|
});
|
|
249
|
+
// Three or more consecutive recorded recovery-required runs flip the loop to
|
|
250
|
+
// FAILING even when the /schedule entry looks fine — the fleet-health signal
|
|
251
|
+
// the scheduler surface cannot see. It wins over the scheduler run-signal.
|
|
252
|
+
const escalation = resolveRecoveryEscalation(runDisplay);
|
|
207
253
|
|
|
208
254
|
const observedDetails = [comparison.observed];
|
|
209
255
|
if (observed?.status) {
|
|
@@ -223,20 +269,30 @@ function createObservedStatusItem(input) {
|
|
|
223
269
|
}
|
|
224
270
|
|
|
225
271
|
const status =
|
|
272
|
+
escalation?.status ??
|
|
226
273
|
runSignal?.status ??
|
|
227
274
|
/** @type {"HEALTHY" | "MISSING" | "DRIFTED"} */ (comparison.status);
|
|
228
275
|
|
|
229
276
|
return {
|
|
230
277
|
id: expected.automationId,
|
|
231
278
|
status,
|
|
232
|
-
summary:
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
279
|
+
summary: escalation
|
|
280
|
+
? escalation.summary
|
|
281
|
+
: composeAutomationSummary({
|
|
282
|
+
comparison,
|
|
283
|
+
runSignal,
|
|
284
|
+
}),
|
|
236
285
|
expectedCadence: expected.expectedCadence,
|
|
237
286
|
expectedCommand: expected.expectedCommand,
|
|
238
287
|
observed: observedDetails.join(" "),
|
|
239
|
-
|
|
288
|
+
runbook: runDisplay?.runbook,
|
|
289
|
+
lastOutcome: runDisplay?.lastOutcome,
|
|
290
|
+
outcomeHistory: runDisplay?.outcomeHistory,
|
|
291
|
+
olderRecordCount: runDisplay?.olderRecordCount,
|
|
292
|
+
remediation:
|
|
293
|
+
escalation?.remediation ??
|
|
294
|
+
runSignal?.remediation ??
|
|
295
|
+
comparison.remediation,
|
|
240
296
|
};
|
|
241
297
|
}
|
|
242
298
|
|
|
@@ -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
|
|
|
@@ -154,7 +154,7 @@ When `issue_type ∉ {Bug, Epic}`, `parent_ref` must be set — **except for a b
|
|
|
154
154
|
|
|
155
155
|
#### S8 — Target Backend Environment
|
|
156
156
|
|
|
157
|
-
When `runtime_behavior_change = true`, body must contain `## Target Backend Environment
|
|
157
|
+
When `runtime_behavior_change = true`, the body must contain `## Target Backend Environment`. Read accepted environments from the exact configured keys of `.lisa.config.json` `deploy.branches`, never from a hardcoded list. Accept a human-confirmed bare exact configured key or `Confirmed: <env>`, automated `Inferred: <env> — evidence: <title|body|reproduction|hostname>`, automated `Assumption: <env> — remote default branch <branch>` for a unique reverse-map, or `Assumption: remote default branch <branch>` when no unique reverse-map exists. Human confirmation replaces an automated annotation with the bare key or `Confirmed: <env>`. For legacy bare values, use managed draft markers and current ticket content only; provider edit history is not required. A marker proves automation and requires re-annotation; otherwise unknown provenance plus conflicting evidence fails for confirmation. Validate the annotation shape/source and remote-default branch; validate `<env>` as an exact configured key whenever present. A valid branch-only assumption must not fail solely because its reverse-map is absent or ambiguous. Normalize built-in `prod` ↔ `production` only when exactly one of those keys is configured. No other aliases are valid. Skipped for doc-only / config-only / type-only / Epic.
|
|
158
158
|
|
|
159
159
|
#### S9 — Sign-in Required
|
|
160
160
|
|
|
@@ -38,7 +38,7 @@ Resolve `<ORG>` and `<REPO>` from the ref or from `.lisa.config.json`.
|
|
|
38
38
|
| Priority | CREATE | Label `priority:<low|medium|high|critical>`. |
|
|
39
39
|
| Acceptance criteria | Story, Task, Bug, Sub-task, Improvement | Gherkin in `## Acceptance Criteria` — see Phase 3. |
|
|
40
40
|
| Validation Journey | Runtime-behavior changes | Delegate to `/github-add-journey`. |
|
|
41
|
-
| Target backend environment | Runtime-behavior changes |
|
|
41
|
+
| Target backend environment | Runtime-behavior changes | For every work type, use an exact `deploy.branches` key when an environment is known. Human: bare key or `Confirmed: <env>`. Automation: `Inferred: <env> — evidence: <title\|body\|reproduction\|hostname>`, `Assumption: <env> — remote default branch <branch>` for a unique reverse-map, or `Assumption: remote default branch <branch>` otherwise. Human confirmation replaces an automated annotation with the bare key or `Confirmed: <env>`. |
|
|
42
42
|
| Sign-in account / credentials | Authenticated-surface tickets | Recorded under `## Sign-in Required`. |
|
|
43
43
|
| Repository | Bug, Task, Sub-task | GitHub Issues live in exactly one repo by definition — record the repo name under `## Repository`, and reject any AC bullet that references a different repo. |
|
|
44
44
|
| Source Requirement | PRD-sourced issues (`prd_source` provided) | `## Source Requirement` with PRD link + verbatim requirement quote(s) — see Phase 3; enforced at every level, sub-issues included. |
|
|
@@ -91,8 +91,14 @@ Scenario: <name>
|
|
|
91
91
|
[Explicit list of what this issue does NOT cover. Forces scope discipline.]
|
|
92
92
|
|
|
93
93
|
## Target Backend Environment
|
|
94
|
-
[Required when the issue changes runtime behavior.
|
|
95
|
-
|
|
94
|
+
[Required when the issue changes runtime behavior. Use an exact
|
|
95
|
+
`deploy.branches` key. A human-confirmed value is a bare key or
|
|
96
|
+
`Confirmed: <env>`. An automated evidence write is
|
|
97
|
+
`Inferred: <env> — evidence: <title|body|reproduction|hostname>`; an automated
|
|
98
|
+
generic default is `Assumption: <env> — remote default branch <branch>`.
|
|
99
|
+
Without a unique reverse-map use `Assumption: remote default branch <branch>`.
|
|
100
|
+
Human confirmation replaces the automated annotation with a bare key or
|
|
101
|
+
`Confirmed: <env>`. Skip only for doc/config/type-only issues.]
|
|
96
102
|
|
|
97
103
|
## Sign-in Required
|
|
98
104
|
[Include this section ONLY if the work touches authenticated surfaces.
|
|
@@ -84,8 +84,12 @@ Using the general-purpose agent in Team Lead session, Determine the name of this
|
|
|
84
84
|
|
|
85
85
|
Using the general-purpose agent in Team Lead session, **determine the base branch from the ticket's target environment, then sync the working branch onto the latest of it before any work** — so implementation always builds on current target-environment code:
|
|
86
86
|
|
|
87
|
-
1. **Resolve the target environment
|
|
88
|
-
|
|
87
|
+
1. **Resolve the target environment with durable provenance.** The `## Target Backend Environment` value has this exact grammar: a human-confirmed value is either a bare configured key or `Confirmed: <env>`; automated evidence writes `Inferred: <env> — evidence: <title|body|reproduction|hostname>`; an automated fallback writes `Assumption: <env> — remote default branch <branch>` when the branch maps uniquely, or `Assumption: remote default branch <branch>` when it does not. Human confirmation replaces an automated annotation with the bare configured key or `Confirmed: <env>`. For a legacy bare value created before this grammar, use managed draft markers and current ticket content only — provider edit history is not required or assumed. A managed marker proves automation and requires rewriting to `Inferred:` or `Assumption:`; without a marker provenance is unknown, so the value may be used only when no conflicting evidence exists, and a conflict **stops for confirmation**.
|
|
88
|
+
- A human-confirmed value wins. Otherwise a validated `Inferred:` value is next.
|
|
89
|
+
- Otherwise inspect the human-authored title, body, and reproduction steps for exactly one unambiguous signal: an exact `deploy.branches` key as a complete token, or that key as a complete label in a URL hostname (`staging.<domain>`, `gql.staging.*`). Exclude the entire `Target Backend Environment` section and all other machine-authored metadata/draft blocks from this evidence scan so an `Inferred:` or `Assumption:` annotation can never validate or conflict with itself. Clear evidence may supersede only an `Assumption:` value, never a human-confirmed value.
|
|
90
|
+
- The only normalization is built-in `prod` ↔ `production`, and only when exactly one of those keys exists in `deploy.branches`; normalize to that configured key. No other aliases exist.
|
|
91
|
+
- Never infer from arbitrary branch text, URL paths or query strings, or substrings inside other words or hostname labels. Multiple conflicting signals after normalization **stop** the flow. If there are no signals, resolve the remote default branch (`gh repo view --json defaultBranchRef -q .defaultBranchRef.name`, or `git remote set-head origin -a` then `origin/HEAD`). When it reverse-maps uniquely, write the env-bearing `Assumption:` form; when the reverse-map is not unique, write the branch-only form and continue on the remote default without inventing an environment or blocking solely for that ambiguity. Record the fallback assumption in the plan/tracker artifact.
|
|
92
|
+
2. **Map the resolved environment to a base branch** through `.lisa.config.json` `deploy.branches` — the forward direction of the env-keyed `done` resolution. The selected exact configured key must map uniquely, and the mapped branch must exist on the remote. A missing/ambiguous mapping or remote branch **stops** the flow; never guess or silently fall back.
|
|
89
93
|
3. **Establish the feature branch off the latest base, conflict-free:**
|
|
90
94
|
- `git fetch origin`.
|
|
91
95
|
- Already on a feature branch with an **open PR** → reuse it. If the PR's base ≠ the resolved base branch, surface the mismatch and re-target only with confirmation — the ticket's environment is the source of truth.
|
|
@@ -155,7 +155,7 @@ When `issue_type ∉ {Bug, Epic}`, `parent_key` must be set — **except for a b
|
|
|
155
155
|
|
|
156
156
|
#### S8 — Target Backend Environment
|
|
157
157
|
|
|
158
|
-
When `runtime_behavior_change = true`, description must contain a `Target Backend Environment` section (`h2.` / `##` in proposed text, or an ADF heading in live JIRA)
|
|
158
|
+
When `runtime_behavior_change = true`, the description must contain a `Target Backend Environment` section (`h2.` / `##` in proposed text, or an ADF heading in live JIRA). Read accepted environments from the exact configured keys of `.lisa.config.json` `deploy.branches`, never from a hardcoded list. Accept a human-confirmed bare exact configured key or `Confirmed: <env>`, automated `Inferred: <env> — evidence: <title|body|reproduction|hostname>`, automated `Assumption: <env> — remote default branch <branch>` for a unique reverse-map, or `Assumption: remote default branch <branch>` when no unique reverse-map exists. Human confirmation replaces an automated annotation with the bare key or `Confirmed: <env>`. For legacy bare values, use managed draft markers and current ticket content only; provider edit history is not required. A marker proves automation and requires re-annotation; otherwise unknown provenance plus conflicting evidence fails for confirmation. Validate the annotation shape/source and remote-default branch; validate `<env>` as an exact configured key whenever present. A valid branch-only assumption must not fail solely because its reverse-map is absent or ambiguous. Normalize built-in `prod` ↔ `production` only when exactly one of those keys is configured. No other aliases are valid. Skipped for doc-only / config-only / type-only / Epic.
|
|
159
159
|
|
|
160
160
|
#### S9 — Sign-in Required
|
|
161
161
|
|