@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.
Files changed (150) hide show
  1. package/package.json +1 -1
  2. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  3. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  4. package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +11 -3
  5. package/plugins/lisa/.codex-plugin/skills/lisa-github-validate-issue/SKILL.md +1 -1
  6. package/plugins/lisa/.codex-plugin/skills/lisa-github-write-issue/SKILL.md +9 -3
  7. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +6 -2
  8. package/plugins/lisa/.codex-plugin/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  9. package/plugins/lisa/.codex-plugin/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  10. package/plugins/lisa/.codex-plugin/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  11. package/plugins/lisa/.codex-plugin/skills/lisa-linear-write-issue/SKILL.md +9 -3
  12. package/plugins/lisa/agents/github-agent.md +2 -2
  13. package/plugins/lisa/agents/jira-agent.md +2 -2
  14. package/plugins/lisa/agents/linear-agent.md +2 -2
  15. package/plugins/lisa/rules/eager/config-resolution.md +27 -8
  16. package/plugins/lisa/rules/eager/pre-flight-autofill.md +21 -7
  17. package/plugins/lisa/rules/reference/config-resolution.md +55 -16
  18. package/plugins/lisa/rules/reference/pre-flight-autofill.md +23 -8
  19. package/plugins/lisa/scripts/automation-status-claude-adapter.mjs +64 -8
  20. package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +61 -5
  21. package/plugins/lisa/scripts/automation-status-report.mjs +30 -0
  22. package/plugins/lisa/scripts/automation-status-run-history.mjs +185 -0
  23. package/plugins/lisa/skills/lisa-automation-status/SKILL.md +11 -3
  24. package/plugins/lisa/skills/lisa-github-validate-issue/SKILL.md +1 -1
  25. package/plugins/lisa/skills/lisa-github-write-issue/SKILL.md +9 -3
  26. package/plugins/lisa/skills/lisa-implement/SKILL.md +6 -2
  27. package/plugins/lisa/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  28. package/plugins/lisa/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  29. package/plugins/lisa/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  30. package/plugins/lisa/skills/lisa-linear-write-issue/SKILL.md +9 -3
  31. package/plugins/lisa-agy/agents/github-agent.md +2 -2
  32. package/plugins/lisa-agy/agents/jira-agent.md +2 -2
  33. package/plugins/lisa-agy/agents/linear-agent.md +2 -2
  34. package/plugins/lisa-agy/plugin.json +1 -1
  35. package/plugins/lisa-agy/scripts/automation-status-claude-adapter.mjs +64 -8
  36. package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +61 -5
  37. package/plugins/lisa-agy/scripts/automation-status-report.mjs +30 -0
  38. package/plugins/lisa-agy/scripts/automation-status-run-history.mjs +185 -0
  39. package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +11 -3
  40. package/plugins/lisa-agy/skills/lisa-github-validate-issue/SKILL.md +1 -1
  41. package/plugins/lisa-agy/skills/lisa-github-write-issue/SKILL.md +9 -3
  42. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +6 -2
  43. package/plugins/lisa-agy/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  44. package/plugins/lisa-agy/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  45. package/plugins/lisa-agy/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  46. package/plugins/lisa-agy/skills/lisa-linear-write-issue/SKILL.md +9 -3
  47. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  50. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/agents/github-agent.agent.md +2 -2
  54. package/plugins/lisa-copilot/agents/jira-agent.agent.md +2 -2
  55. package/plugins/lisa-copilot/agents/linear-agent.agent.md +2 -2
  56. package/plugins/lisa-copilot/rules/eager/config-resolution.md +27 -8
  57. package/plugins/lisa-copilot/rules/eager/pre-flight-autofill.md +21 -7
  58. package/plugins/lisa-copilot/rules/reference/config-resolution.md +55 -16
  59. package/plugins/lisa-copilot/rules/reference/pre-flight-autofill.md +23 -8
  60. package/plugins/lisa-copilot/scripts/automation-status-claude-adapter.mjs +64 -8
  61. package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +61 -5
  62. package/plugins/lisa-copilot/scripts/automation-status-report.mjs +30 -0
  63. package/plugins/lisa-copilot/scripts/automation-status-run-history.mjs +185 -0
  64. package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +11 -3
  65. package/plugins/lisa-copilot/skills/lisa-github-validate-issue/SKILL.md +1 -1
  66. package/plugins/lisa-copilot/skills/lisa-github-write-issue/SKILL.md +9 -3
  67. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +6 -2
  68. package/plugins/lisa-copilot/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  69. package/plugins/lisa-copilot/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  70. package/plugins/lisa-copilot/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  71. package/plugins/lisa-copilot/skills/lisa-linear-write-issue/SKILL.md +9 -3
  72. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-cursor/agents/github-agent.md +2 -2
  74. package/plugins/lisa-cursor/agents/jira-agent.md +2 -2
  75. package/plugins/lisa-cursor/agents/linear-agent.md +2 -2
  76. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +55 -16
  77. package/plugins/lisa-cursor/rules/config-resolution.mdc +27 -8
  78. package/plugins/lisa-cursor/rules/pre-flight-autofill-reference.mdc +23 -8
  79. package/plugins/lisa-cursor/rules/pre-flight-autofill.mdc +21 -7
  80. package/plugins/lisa-cursor/scripts/automation-status-claude-adapter.mjs +64 -8
  81. package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +61 -5
  82. package/plugins/lisa-cursor/scripts/automation-status-report.mjs +30 -0
  83. package/plugins/lisa-cursor/scripts/automation-status-run-history.mjs +185 -0
  84. package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +11 -3
  85. package/plugins/lisa-cursor/skills/lisa-github-validate-issue/SKILL.md +1 -1
  86. package/plugins/lisa-cursor/skills/lisa-github-write-issue/SKILL.md +9 -3
  87. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +6 -2
  88. package/plugins/lisa-cursor/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  89. package/plugins/lisa-cursor/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  90. package/plugins/lisa-cursor/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  91. package/plugins/lisa-cursor/skills/lisa-linear-write-issue/SKILL.md +9 -3
  92. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-expo-agy/plugin.json +1 -1
  95. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  100. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  105. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  110. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  114. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  115. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  119. package/plugins/lisa-rails-agy/plugin.json +1 -1
  120. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  124. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  125. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  129. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  130. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  132. package/plugins/src/base/agents/github-agent.md +2 -2
  133. package/plugins/src/base/agents/jira-agent.md +2 -2
  134. package/plugins/src/base/agents/linear-agent.md +2 -2
  135. package/plugins/src/base/rules/eager/config-resolution.md +27 -8
  136. package/plugins/src/base/rules/eager/pre-flight-autofill.md +21 -7
  137. package/plugins/src/base/rules/reference/config-resolution.md +55 -16
  138. package/plugins/src/base/rules/reference/pre-flight-autofill.md +23 -8
  139. package/plugins/src/base/scripts/automation-status-claude-adapter.mjs +64 -8
  140. package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +61 -5
  141. package/plugins/src/base/scripts/automation-status-report.mjs +30 -0
  142. package/plugins/src/base/scripts/automation-status-run-history.mjs +185 -0
  143. package/plugins/src/base/skills/lisa-automation-status/SKILL.md +11 -3
  144. package/plugins/src/base/skills/lisa-github-validate-issue/SKILL.md +1 -1
  145. package/plugins/src/base/skills/lisa-github-write-issue/SKILL.md +9 -3
  146. package/plugins/src/base/skills/lisa-implement/SKILL.md +6 -2
  147. package/plugins/src/base/skills/lisa-jira-validate-ticket/SKILL.md +1 -1
  148. package/plugins/src/base/skills/lisa-jira-write-ticket/SKILL.md +9 -6
  149. package/plugins/src/base/skills/lisa-linear-validate-issue/SKILL.md +1 -1
  150. package/plugins/src/base/skills/lisa-linear-write-issue/SKILL.md +9 -3
@@ -21,13 +21,27 @@ information was already on the ticket; only the structure was missing).
21
21
  Gherkin Acceptance Criteria (one scenario per fix), expected-vs-actual +
22
22
  environment, Repository, Relationship Search (actually run the git+tracker
23
23
  search — don't fabricate "none found"), Validation Journey **draft** (via the
24
- vendor `*-add-journey` skill), Target Backend Environment. For bugs, parse the
25
- reported environment from the item body/reproduction steps before recommending
26
- any default: bare environment names (`dev`, `staging`, `prod`, `production`)
27
- and environment-bearing URLs (for example `staging.<domain>`, `gql.staging.*`,
28
- `dev.<domain>`) are authoritative. Only recommend the repo's remote default branch when
29
- no environment is discoverable anywhere in the work item, and record that as
30
- an assumption.
24
+ vendor `*-add-journey` skill), Target Backend Environment. Preserve a bare
25
+ configured key or `Confirmed: <env>` as human-confirmed. Automation writes
26
+ `Inferred: <env> evidence: <title|body|reproduction|hostname>` or
27
+ `Assumption: <env> remote default branch <branch>`. Without a unique
28
+ reverse-map use `Assumption: remote default branch <branch>`; human confirmation
29
+ replaces the annotation with a bare key or `Confirmed: <env>`. For legacy bare
30
+ values, use managed draft markers and current ticket content only; provider
31
+ edit history is not required. A marker proves automation and requires
32
+ re-annotation; otherwise unknown provenance plus conflicting evidence stops.
33
+ Human-confirmed wins, then validated `Inferred:`
34
+ evidence. Otherwise use one unambiguous exact `deploy.branches` key from the
35
+ title, body, and reproduction steps or a URL hostname, excluding the complete
36
+ `Target Backend Environment` section and other machine-authored metadata/draft
37
+ blocks so annotations cannot become evidence. Evidence supersedes only
38
+ `Assumption:`. Normalize only built-in
39
+ `prod` ↔ `production` when exactly one is configured; no other aliases exist.
40
+ Never infer from arbitrary branch text, URL paths/query strings, or substrings.
41
+ Conflicting signals **stop** autofill. With no environment signal, use the
42
+ remote default and record the applicable `Assumption:` form; a non-unique
43
+ reverse-map alone never blocks. Require any selected mapping and remote branch.
44
+ Never overwrite human prose.
31
45
  - **Tier B — irreducibly human:** real credentials/access that exist nowhere on
32
46
  the item or in repo test-user docs; a genuine product/scoping decision with no
33
47
  defensible default. Still propose a recommended default where one exists; ask
@@ -217,23 +217,47 @@ Declares **where this repo's LLM Wiki lives** so the query/ingest skills can res
217
217
 
218
218
  ### Env → base branch
219
219
 
220
- Implementation flows resolve their PR base from the work item's
221
- `## Target Backend Environment` in the forward direction of `deploy.branches`.
220
+ Implementation flows resolve their PR base from the work item's environment
221
+ evidence in the forward direction of `deploy.branches`.
222
222
  For example, `{ "staging": "staging", "production": "main" }` means a staging
223
223
  work item starts from `origin/staging` and opens its PR against `staging`. This
224
224
  is the reverse/inverse of the env-keyed `done` inference that derives an
225
225
  environment from a merged PR's base branch.
226
226
 
227
- For bug work, the reported environment is authoritative. `pre-flight-autofill`
228
- must parse environment mentions from the description/body and reproduction
229
- steps, including bare names (`dev`, `staging`, `prod`, `production`) and
230
- environment-bearing URLs (`staging.<domain>`, `gql.staging.*`,
231
- `dev.<domain>`). A parsed reported environment wins over any generic autofill
232
- default. If no environment is named anywhere, the flow may fall back to the
233
- remote default branch and record that assumption on the work item.
234
-
235
- If the reported environment is not present in `deploy.branches`, stop and report
236
- the missing mapping. Do not silently default to the integration branch.
227
+ The field grammar records provenance durably: human-confirmed values are a bare
228
+ configured key or `Confirmed: <env>`; automated evidence is
229
+ `Inferred: <env> evidence: <title|body|reproduction|hostname>`; an automated
230
+ fallback is `Assumption: <env> — remote default branch <branch>`. Human
231
+ fallback without a unique reverse-map is
232
+ `Assumption: remote default branch <branch>`. Human
233
+ confirmation replaces an automated annotation with a bare key or
234
+ `Confirmed: <env>`.
235
+
236
+ For legacy bare values created before this grammar, use managed draft markers
237
+ and current ticket content only; provider edit history is neither required nor
238
+ assumed. A managed marker proves automation and requires rewriting to
239
+ `Inferred:` or `Assumption:`. Without one, provenance remains unknown, so the
240
+ bare value is usable only if no conflicting evidence exists; a conflict stops
241
+ for confirmation.
242
+
243
+ Resolve it in this order: human-confirmed wins; validated `Inferred:` evidence
244
+ is next; otherwise accept one unambiguous exact `deploy.branches` key from the
245
+ human-authored title, body, and reproduction steps or a URL hostname. Exclude
246
+ the entire `Target Backend Environment` section and all other machine-authored
247
+ metadata/draft blocks from the evidence scan, so annotations never validate or
248
+ conflict with themselves. Evidence supersedes only
249
+ an `Assumption:`. Treat the reported bug environment as an example of this
250
+ all-work-type evidence rule, not a special case. Normalize only built-in
251
+ `prod` ↔ `production` when exactly one is configured; no other aliases exist.
252
+ Never infer from arbitrary branch text, URL paths/query strings, or substrings.
253
+ Multiple conflicting signals stop. With no signals, use the remote default
254
+ branch and record an assumption: include the environment only for a unique
255
+ reverse-map; otherwise use the branch-only form without inventing an environment
256
+ or blocking solely on the reverse-map.
257
+
258
+ Every selected environment must have a unique `deploy.branches` mapping and its
259
+ mapped branch must exist on the remote. If either validation fails, stop and
260
+ report it. Do not silently default to the integration branch.
237
261
 
238
262
  For non-integration environment bugs, definition of done is two-step:
239
263
 
@@ -504,10 +528,25 @@ If a project's terminal state is the same regardless of env, set `done` to a str
504
528
 
505
529
  `deploy.branches` is also read in the **forward** direction by the build flow (`lisa-implement`): the environment a work item targets determines the branch the work is built on and the branch the PR opens against.
506
530
 
507
- 1. **Resolve the work item's target environment** its `## Target Backend Environment` field.
508
- 2. **If no environment is named**, use the **remote default branch** (`gh repo view --json defaultBranchRef`, or `origin/HEAD`) and record that default-branch assumption.
509
- 3. **If a reported environment exists**, map env → base branch via `deploy.branches` (e.g. `staging → staging`, `production → main`). A reported env absent from `deploy.branches`, or a mapped branch missing from the remote, must stop and report the exact missing environment-to-branch mapping; never guess and never silently fall back.
510
- 4. **Before any code is written**, `lisa-implement` fetches and **rebases the working branch onto `origin/<base>`, resolving conflicts**, so implementation builds on the latest target-environment code. **The PR then opens against that same base branch** (`target_branch=<base>` to `lisa-git-submit-pr`).
531
+ The durable field forms are: bare configured key or `Confirmed: <env>` for a
532
+ human-confirmed value; `Inferred: <env> evidence: <title|body|reproduction|hostname>`
533
+ for automation-backed evidence; and
534
+ `Assumption: <env> remote default branch <branch>` for a generic fallback.
535
+ When the remote default has no unique environment reverse-map, the valid form is
536
+ `Assumption: remote default branch <branch>`.
537
+ Human confirmation replaces the automated annotation with a bare key or
538
+ `Confirmed: <env>`.
539
+
540
+ For a legacy bare value, use managed draft markers and current ticket content
541
+ only; do not require provider edit history. A marker proves automation and
542
+ requires re-annotation; otherwise unknown provenance plus conflicting evidence
543
+ stops for confirmation.
544
+
545
+ 1. **Resolve provenance first.** Human-confirmed wins, then validated `Inferred:` evidence. Otherwise search the human-authored title, body, and reproduction steps or URL hostname for one unambiguous exact `deploy.branches` key. Exclude the complete `Target Backend Environment` section and other machine-authored metadata/draft blocks from the scan so annotations cannot become evidence. Evidence supersedes only an `Assumption:`.
546
+ 2. **Normalize narrowly.** Normalize built-in `prod` ↔ `production` only when exactly one of those keys is configured. No other aliases exist. Never infer from arbitrary branch text, URL paths/query strings, or substrings.
547
+ 3. **Handle absence and conflict explicitly.** Multiple conflicting signals stop. With no signals, use the remote default branch (`gh repo view --json defaultBranchRef`, or `origin/HEAD`) and record the env-bearing assumption only for a unique reverse-map; otherwise use the branch-only assumption without inventing an environment or blocking.
548
+ 4. **Validate the destination.** The selected exact configured key must map uniquely and the mapped remote branch must exist; otherwise stop without guessing or falling back.
549
+ 5. **Before any code is written**, `lisa-implement` fetches and **rebases the working branch onto `origin/<base>`, resolving conflicts**, then opens the PR against that same base (`target_branch=<base>`).
511
550
 
512
551
  This is the exact inverse of the env-keyed `done` "Branch inference" above: `done` derives the env *from* the PR base branch (reverse); the build flow derives the base branch *from* the env (forward). Both use the one `deploy.branches` map, so the branch a PR targets and the `done` status it earns always agree.
513
552
 
@@ -49,14 +49,29 @@ spec.
49
49
  - **Validation Journey (draft)** — run the vendor `*-add-journey` skill to draft
50
50
  the click-path, markers, and viewports from the reproduction steps. This is a
51
51
  draft for human approval, not the final ratified contract.
52
- - **Target Backend Environment** — for bugs, first parse the reported
53
- environment from the title, description/body, screenshots captions, links, and
54
- reproduction steps. Bare words such as `dev`, `staging`, `prod`, and
55
- `production` count, as do environment-bearing URLs such as
56
- `staging.<domain>`, `gql.staging.*`, and `dev.<domain>`. That reported
57
- environment is authoritative for the draft. Only recommend the repo's
58
- remote default branch when no environment is discoverable anywhere in the
59
- work item, and record the default as an explicit assumption.
52
+ - **Target Backend Environment** — preserve a bare configured key or
53
+ `Confirmed: <env>` as human-confirmed. Automated evidence must write
54
+ `Inferred: <env> evidence: <title|body|reproduction|hostname>`; a generic
55
+ fallback must write `Assumption: <env> remote default branch <branch>`.
56
+ If the default branch has no unique environment reverse-map, instead write
57
+ `Assumption: remote default branch <branch>` without inventing an environment.
58
+ Human confirmation replaces an automated annotation with a bare key or
59
+ `Confirmed: <env>`. For legacy bare values, use managed draft markers and
60
+ current ticket content only; provider edit history is not required. A marker
61
+ proves automation and requires re-annotation; otherwise unknown provenance
62
+ plus conflicting evidence stops for confirmation. A human-confirmed value wins, followed by validated
63
+ `Inferred:` evidence. Otherwise inspect the title, body, and reproduction
64
+ steps or a URL hostname for one unambiguous exact `deploy.branches` key,
65
+ excluding the complete `Target Backend Environment` section and all other
66
+ machine-authored metadata/draft blocks so annotations cannot become evidence.
67
+ Clear evidence supersedes only an `Assumption:` value. The sole normalization
68
+ is `prod` ↔ `production` when exactly one key is configured; no other aliases
69
+ exist. Never infer from arbitrary branch text, URL paths or query strings, or
70
+ substrings. Multiple conflicting signals **stop** autofill. With no
71
+ environment signal, use the remote default and record the applicable
72
+ `Assumption:` form; a missing/ambiguous reverse-map alone never blocks.
73
+ Require any selected environment mapping and remote branch. Never overwrite
74
+ human prose.
60
75
 
61
76
  **Tier B — irreducibly human (cannot invent — but still propose a default):**
62
77
 
@@ -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: composeAutomationSummary({
233
- comparison,
234
- runSignal,
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
- remediation: runSignal?.remediation ?? comparison.remediation,
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: composeAutomationSummary({
411
- comparison,
412
- runSignal,
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
- remediation: runSignal?.remediation ?? comparison.remediation,
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
  }