@codyswann/lisa 2.251.1 → 2.253.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 +14 -8
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-tear-down-automations/SKILL.md +25 -5
- package/plugins/lisa/rules/eager/automation-runbook-contract.md +61 -0
- package/plugins/lisa/rules/reference/automation-runbook-contract.md +216 -0
- package/plugins/lisa/scripts/automation-status-claude-adapter.mjs +36 -18
- package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +50 -27
- package/plugins/lisa/scripts/automation-status-expected-fleet.mjs +149 -6
- package/plugins/lisa/skills/lisa-automation-status/SKILL.md +14 -8
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/lisa/skills/lisa-tear-down-automations/SKILL.md +26 -6
- package/plugins/lisa/skills/lisa-tear-down-automations/agents/openai.yaml +2 -2
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/automation-status-claude-adapter.mjs +36 -18
- package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +50 -27
- package/plugins/lisa-agy/scripts/automation-status-expected-fleet.mjs +149 -6
- package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +14 -8
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/lisa-agy/skills/lisa-tear-down-automations/SKILL.md +26 -6
- 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/rules/eager/automation-runbook-contract.md +61 -0
- package/plugins/lisa-copilot/rules/reference/automation-runbook-contract.md +216 -0
- package/plugins/lisa-copilot/scripts/automation-status-claude-adapter.mjs +36 -18
- package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +50 -27
- package/plugins/lisa-copilot/scripts/automation-status-expected-fleet.mjs +149 -6
- package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +14 -8
- package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/lisa-copilot/skills/lisa-tear-down-automations/SKILL.md +26 -6
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/automation-runbook-contract-reference.mdc +221 -0
- package/plugins/lisa-cursor/rules/automation-runbook-contract.mdc +66 -0
- package/plugins/lisa-cursor/scripts/automation-status-claude-adapter.mjs +36 -18
- package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +50 -27
- package/plugins/lisa-cursor/scripts/automation-status-expected-fleet.mjs +149 -6
- package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +14 -8
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/lisa-cursor/skills/lisa-tear-down-automations/SKILL.md +26 -6
- 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/rules/eager/automation-runbook-contract.md +61 -0
- package/plugins/src/base/rules/reference/automation-runbook-contract.md +216 -0
- package/plugins/src/base/scripts/automation-status-claude-adapter.mjs +36 -18
- package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +50 -27
- package/plugins/src/base/scripts/automation-status-expected-fleet.mjs +149 -6
- package/plugins/src/base/skills/lisa-automation-status/SKILL.md +14 -8
- package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +116 -1
- package/plugins/src/base/skills/lisa-tear-down-automations/SKILL.md +26 -6
package/package.json
CHANGED
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"form-data": ">=4.0.6"
|
|
103
103
|
},
|
|
104
104
|
"name": "@codyswann/lisa",
|
|
105
|
-
"version": "2.
|
|
105
|
+
"version": "2.253.0",
|
|
106
106
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
107
107
|
"main": "dist/index.js",
|
|
108
108
|
"exports": {
|
|
@@ -16,14 +16,20 @@ Do **not** ask for confirmation once invoked. This skill inspects scheduler stat
|
|
|
16
16
|
|
|
17
17
|
## Scope
|
|
18
18
|
|
|
19
|
-
Inspect only the Lisa automation fleet for the current project
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- `
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
Inspect only the Lisa automation fleet for the current project. Derive that fleet from
|
|
20
|
+
`scripts/automation-status-expected-fleet.mjs` (`resolveExpectedAutomationFleet`), which resolves
|
|
21
|
+
exactly what `setup-automations` registers for this repo — including the stack-guarded
|
|
22
|
+
`exploratory-bugs` and the opt-in `learnings-audit` gardener. **Membership is registration, not a
|
|
23
|
+
roster** (`automation-runbook-contract`): carry no fixed list of loop names here, so a loop added to
|
|
24
|
+
or removed from the registration set flows through without editing this skill.
|
|
25
|
+
|
|
26
|
+
The gardener is opted into at registration time and has no config key, so **infer it from the
|
|
27
|
+
scheduler**: list the project's automations first, then call
|
|
28
|
+
`inferLearningsAuditRegistration({ automationPrefix, observedAutomationIds })` and pass the result
|
|
29
|
+
as `learningsAudit` when resolving the fleet. A `lisa-auto-<project>-learnings-audit` entry on the
|
|
30
|
+
scheduler means the project opted in, so the gardener is compared like any other loop; no such entry
|
|
31
|
+
means it is reported `UNSUPPORTED` (opted out), never `MISSING`. This skill stays read-only — it
|
|
32
|
+
infers, it never writes the flag anywhere.
|
|
27
33
|
|
|
28
34
|
Resolve the expected project identifier, fleet naming prefix, queue arguments, cadence, and stack-support rules from the same contract used by `setup-automations` and `tear-down-automations`. Do **not** invent a second source of truth for fleet naming or queue resolution.
|
|
29
35
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: lisa-setup-automations
|
|
3
3
|
description: "Set up the recurring Lisa…"
|
|
4
|
-
allowed-tools: ["Skill", "Bash", "Read"]
|
|
4
|
+
allowed-tools: ["Skill", "Bash", "Read", "Write", "Edit"]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Set up Lisa automations: $ARGUMENTS
|
|
@@ -105,6 +105,19 @@ the scheduled commands (for example `/lisa:intake acme/frontend intake_mode=prd`
|
|
|
105
105
|
config/read-context failure cannot silently redirect the cron. A short queueRepo is normalized to
|
|
106
106
|
`github.org` before writing the automation.
|
|
107
107
|
|
|
108
|
+
**Registration prompt shape (so status can read the command back).** Whatever prose the prompt
|
|
109
|
+
carries, it must contain the **literal Lisa command on a line of its own**, exactly as scheduled and
|
|
110
|
+
with every `owner/repo` already baked in:
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
/lisa:intake acme/planning intake_mode=build
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
This holds for loops that take **no arguments** too — `/lisa:monitor` and `/lisa:learnings:audit`
|
|
117
|
+
are written the same way, on their own line, with nothing after them. `/lisa:automation-status`
|
|
118
|
+
reads that line back to compare the live registration against this contract; a loop whose command
|
|
119
|
+
cannot be read back reports as drifted even though it is registered correctly.
|
|
120
|
+
|
|
108
121
|
**Naming + scope (so teardown is precise).** Name each automation with the stable prefix
|
|
109
122
|
`lisa-auto-<project>-` (e.g. `lisa-auto-<project>-intake-tickets`), where `<project>` identifies this
|
|
110
123
|
repo, and scope each Codex automation to the durable project automation checkout described above.
|
|
@@ -115,6 +128,91 @@ repos (don't rely on a bare repo basename when it could collide; qualify it, e.g
|
|
|
115
128
|
**Idempotent.** Re-running this skill updates the existing `lisa-auto-<project>-*` automations in
|
|
116
129
|
place (same names) rather than creating duplicates.
|
|
117
130
|
|
|
131
|
+
## Runbook scaffolding
|
|
132
|
+
|
|
133
|
+
Registration is what pulls a loop under the `automation-runbook-contract` rule, so registration is
|
|
134
|
+
what must produce its runbook. For **every automation actually registered above** — including the
|
|
135
|
+
conditionally-guarded `exploratory-bugs` and the opt-in `learnings-audit` — write a checked-in
|
|
136
|
+
runbook at:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
.lisa/automations/<loop-id>.runbook.md
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`<loop-id>` is the automation's suffix, not its full name: `intake-tickets`, not
|
|
143
|
+
`lisa-auto-<project>-intake-tickets`. These files are **project knowledge and belong in git**, like
|
|
144
|
+
`.lisa/PROJECT_LEARNINGS.md` — commit them; they are not scratch output. Derive the set of runbooks
|
|
145
|
+
from what was registered on this run: a loop that was **skipped gets no runbook**, and a loop
|
|
146
|
+
registered later gets one when this skill next runs. Never write a runbook from a fixed list of
|
|
147
|
+
loop names.
|
|
148
|
+
|
|
149
|
+
Follow the `automation-runbook-contract` rule for the runbook's shape: its ten sections, in its
|
|
150
|
+
order, in its prose register. **Do not restate the template, the run outcomes, or the escalation
|
|
151
|
+
packet here** — cite the rule and instantiate it.
|
|
152
|
+
|
|
153
|
+
**File shape — two halves with different owners.**
|
|
154
|
+
|
|
155
|
+
1. A **machine-resolved header block**, delimited exactly by
|
|
156
|
+
`<!-- lisa:machine-resolved:start -->` and `<!-- lisa:machine-resolved:end -->`, written first
|
|
157
|
+
in the file. Lisa owns it: on every run it is **rewritten wholesale** from the values resolved
|
|
158
|
+
above. It carries the project identity (`<owner>/<repo>` and the `<project>` token), the loop
|
|
159
|
+
id, the full automation name, the exact scheduled command with every `owner/repo` already baked
|
|
160
|
+
in (never a placeholder), the human cadence and its `rrule`, the resolved queue arguments, and
|
|
161
|
+
the resolved flag values that shaped the command (`auto-start-prds` / `auto-start-tickets` /
|
|
162
|
+
`learnings-audit`). Its **first line inside the delimiters is a visible sentence**, so the
|
|
163
|
+
ownership boundary survives rendering to markdown where HTML comments disappear:
|
|
164
|
+
|
|
165
|
+
```text
|
|
166
|
+
Lisa rewrites this section on every /lisa:setup-automations run — edits here are lost. Your prose
|
|
167
|
+
belongs in the sections below.
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Immediately **below** the closing delimiter, write one more visible line — `Everything below is
|
|
171
|
+
yours.` — so an operator reading the rendered page knows where their half starts.
|
|
172
|
+
2. The **ten contract sections** below it. Lisa owns them **only on first write**, then they belong
|
|
173
|
+
to the operator. Seed them deterministically, never improvised:
|
|
174
|
+
- **Intent, Sources of truth, Candidate selection, Scope/bounds, Retirement condition** come
|
|
175
|
+
from the per-loop seed table below.
|
|
176
|
+
- **Proof, Autonomous-vs-approval boundary, Escalation, Recovery, Next-run state** are derived
|
|
177
|
+
from **that loop's own SKILL.md** — named per loop in the table's last column — so the runbook
|
|
178
|
+
describes what the loop actually does rather than a generic paraphrase. Quote its behavior in
|
|
179
|
+
the operator's voice; if the skill file is unreadable, write one sentence saying the section
|
|
180
|
+
was not derivable and which file to read, and continue.
|
|
181
|
+
|
|
182
|
+
Every seeded sentence is written for a non-technical operator (`factory-model` rule 5).
|
|
183
|
+
|
|
184
|
+
### Per-loop seed defaults
|
|
185
|
+
|
|
186
|
+
One line each; expand them into the operator's voice, keep the meaning.
|
|
187
|
+
|
|
188
|
+
| Loop | Intent | Sources of truth | Candidate selection | Scope/bounds | Retirement condition | Derive the other five from |
|
|
189
|
+
|---|---|---|---|---|---|---|
|
|
190
|
+
| **intake-repair** | Keeps the queues unstuck: work that stalled mid-flight gets diagnosed and moved again. | The PRD and build queues for this project, read through the tracker access layer. | Stuck or half-closed items — stalled in an in-progress role, terminal-labeled but still open, rollups whose children are all done — up to the `max_candidates` cap. | Only this project's configured queues; it repairs lifecycle state, never invents or closes real work. | Propose teardown when nothing has needed repair for a long quiet window AND this run found nothing. | `lisa-repair-intake/SKILL.md` |
|
|
191
|
+
| **intake-prd** | Keeps PRDs moving: a PRD a human marked ready gets validated and decomposed into work items. | The configured PRD queue and its lifecycle roles, read through the source access layer. | The oldest PRD in the ready role, one per run. | Only this project's PRD queue; it never authors product intent, only validates and decomposes it. | Propose teardown when no PRD has entered the queue for a long quiet window AND this run proposed nothing. | `lisa-intake/SKILL.md` |
|
|
192
|
+
| **intake-tickets** | Keeps the build queue moving: work items a human marked ready get built, reviewed, and shipped. | The configured tracker's build queue lanes plus each item's comments and linked PRs. | The oldest eligible leaf work item in the ready lane, one per run. | Only this repo's configured queue; containers with open children are repaired out, never built. | Propose teardown when no work item has entered this queue for a long quiet window AND this run proposed nothing. | `lisa-intake/SKILL.md` |
|
|
193
|
+
| **monitor** | Keeps production honest: observability regressions become tracked work instead of silent decay. | The connected observability providers, each through its access layer, plus this project's `monitor.gapTiers` config. | The regressions and coverage gaps this cycle surfaced, capped per run. | Only this project's own services and dashboards; it files findings, it never changes production. | Propose teardown when the project has no connected observability surfaces AND this run found nothing. | `lisa-monitor/SKILL.md` |
|
|
194
|
+
| **exploratory-prds** | Keeps the idea pipeline fed: the product's own gaps become PRDs when the PRD queue has room. | The PRD queue's pressure roles (the same ones `/lisa:queue-status` reports) and the project's own product surfaces. | One ideated PRD per run, and only when the pressure gate says the queue has capacity. | Never more than one PRD per run, and nothing at all while unresolved PRD pressure exists. | Propose teardown when the queue has been at pressure — or the project has shipped no new surface — across a long quiet window AND this run proposed nothing. | `lisa-project-ideation/SKILL.md` |
|
|
195
|
+
| **exploratory-bugs** | Keeps quality visible: bugs and usability problems get found by using the product, before a customer does. | The running application through its exploratory-qa surface, plus this project's existing bug queue for dedupe. | The findings of one exploratory session, capped per run and deduped against already-filed items. | Only this project's own app; it files tickets, it never changes product code. | Propose teardown when no session has produced a finding across a long quiet window AND this run found nothing. | `lisa-exploratory-qa/SKILL.md` (the stack's own copy) |
|
|
196
|
+
| **learnings-audit** | Keeps the project's knowledge true: stale, duplicated, or contradicted knowledge gets proposed for promotion, demotion, or retirement. | The learnings ledger, the rules trees, the skills, and the wiki — the project's knowledge surfaces. | The knowledge entries this audit judged stale, duplicated, or contradicted, capped per run. | It only proposes; every promote/demote/retire decision is a human-gated tracker ticket. | Propose teardown when the audit has proposed nothing across a long quiet window AND this run proposed nothing. | `lisa-learnings-audit/SKILL.md` |
|
|
197
|
+
|
|
198
|
+
A loop not in this table — one registered later — seeds all ten sections from its own SKILL.md the
|
|
199
|
+
same way. The table is seed prose for first write only; it is **not** a roster and never decides
|
|
200
|
+
which automations exist.
|
|
201
|
+
|
|
202
|
+
**Re-run rule (idempotent, never clobber).** When the file already exists: rewrite the
|
|
203
|
+
machine-resolved block in place and **leave every one of the ten sections exactly as found** — an
|
|
204
|
+
operator's edited Intent paragraph survives verbatim. If a contract section is **missing** from an
|
|
205
|
+
existing file, insert it **at its position in the contract's ten-section order** (never merely
|
|
206
|
+
appended at the end, which would leave the file permanently out of order) with its seeded default.
|
|
207
|
+
Never overwrite, reorder, reword, or delete prose already on disk. If the machine-resolved
|
|
208
|
+
delimiters are absent (a hand-written file), insert the block at the top and touch nothing else.
|
|
209
|
+
|
|
210
|
+
**Never a precondition.** A runbook that cannot be written is a **degradation, not a blocker**:
|
|
211
|
+
report the failure and continue registering automations. A loop with a missing runbook still runs —
|
|
212
|
+
on the contract's defaults, saying so in its one-line summary — per the contract's
|
|
213
|
+
never-block-always-degrade rule. Scaffolding failure never aborts setup and never leaves an
|
|
214
|
+
automation unregistered.
|
|
215
|
+
|
|
118
216
|
## Conditions / guards
|
|
119
217
|
|
|
120
218
|
- **exploratory-bugs** is created only when the project ships an `exploratory-qa` command (the
|
|
@@ -134,3 +232,20 @@ place (same names) rather than creating duplicates.
|
|
|
134
232
|
|
|
135
233
|
List each automation created or updated (name, the command it runs, cadence, and the resolved
|
|
136
234
|
`auto-start-prds` / `auto-start-tickets` values), plus any automation skipped and why.
|
|
235
|
+
|
|
236
|
+
Then list each runbook written, by path (`.lisa/automations/<loop-id>.runbook.md`), saying for each
|
|
237
|
+
whether it was **created** (seeded fresh) or **refreshed** (machine-resolved block rewritten,
|
|
238
|
+
operator prose left untouched). Name every skipped loop again here and state plainly that it has no
|
|
239
|
+
runbook because it was not registered, and why — for example, "exploratory-bugs was skipped because
|
|
240
|
+
this project ships no exploratory-qa command, so no runbook was written for it."
|
|
241
|
+
|
|
242
|
+
Close with the two lines that tell the operator what to do next:
|
|
243
|
+
|
|
244
|
+
- When any runbook was newly created: "N new files were written under `.lisa/automations/` — commit
|
|
245
|
+
them so the whole team sees them."
|
|
246
|
+
- When a runbook could not be written: name which one and what failed, then the consequence and the
|
|
247
|
+
fix — "that loop runs on defaults and its summaries will lead with 'Runbook missing' — re-run
|
|
248
|
+
`/lisa:setup-automations` to fix." The automation itself is still registered and still runs.
|
|
249
|
+
|
|
250
|
+
Every line states the consequence and the action, in words a non-technical operator can act on
|
|
251
|
+
without reading code.
|
|
@@ -21,14 +21,34 @@ removes them with its **native** scheduling mechanism.
|
|
|
21
21
|
|
|
22
22
|
## Scope (remove only what setup created)
|
|
23
23
|
|
|
24
|
-
- Remove
|
|
25
|
-
stable `lisa-auto-<project>-` name prefix
|
|
26
|
-
`
|
|
24
|
+
- Remove **every** automation `/setup-automations` registered for the current project — the whole
|
|
25
|
+
set found under the stable `lisa-auto-<project>-` name prefix, whatever it currently contains.
|
|
26
|
+
**Membership is registration, not a roster** (`automation-runbook-contract`): sweep the prefix and
|
|
27
|
+
remove what is there. Do **not** work from a fixed list of loop names — a list drifts the moment a
|
|
28
|
+
loop is added, which is exactly how the opt-in gardener came to be orphaned.
|
|
29
|
+
- This explicitly includes the opt-in **`learnings-audit`** gardener when it is registered:
|
|
30
|
+
`/setup-automations learnings-audit=true` registers it under the same prefix, so teardown removes
|
|
31
|
+
it with the rest. A conditionally-skipped loop (e.g. `exploratory-bugs` on a stack without
|
|
32
|
+
`exploratory-qa`) simply is not in the sweep.
|
|
27
33
|
- **Never** remove automations for a different project, or any non-Lisa automation (e.g. unrelated
|
|
28
34
|
crawlers/ingestors). Match strictly on the `lisa-auto-<project>-` prefix for THIS project; when in
|
|
29
35
|
doubt about an automation's ownership, leave it and report it rather than deleting it.
|
|
30
|
-
- **Idempotent** — an automation that is already absent is a no-op, not an error.
|
|
36
|
+
- **Idempotent** — an automation that is already absent is a no-op, not an error. Re-running when
|
|
37
|
+
the prefix sweep finds nothing is a clean, successful no-op.
|
|
38
|
+
- **Leave the runbooks alone.** The checked-in `.lisa/automations/<loop-id>.runbook.md` files that
|
|
39
|
+
`/setup-automations` scaffolded are project knowledge and the historical record of what these
|
|
40
|
+
loops did. Teardown removes scheduler registrations only; it never deletes, edits, or moves a
|
|
41
|
+
runbook file. An operator who wants them gone removes them deliberately, in git.
|
|
31
42
|
|
|
32
43
|
## Report
|
|
33
44
|
|
|
34
|
-
List each automation removed
|
|
45
|
+
List each automation removed by name. For "already absent", compare against the one source of truth
|
|
46
|
+
— the fleet `scripts/automation-status-expected-fleet.mjs` (`resolveExpectedAutomationFleet`)
|
|
47
|
+
resolves for this project — and name anything it expects that the sweep did not find; that is a
|
|
48
|
+
no-op, not an error. Do not invent an expected set of your own.
|
|
49
|
+
|
|
50
|
+
Then state, in the operator's words, that the runbook files under `.lisa/automations/` were left on
|
|
51
|
+
disk **and why**: they are the written record of what those jobs did, kept on purpose, and if you
|
|
52
|
+
do not want them you delete them yourself in git. Finally, confirm that nothing outside this
|
|
53
|
+
project's `lisa-auto-<project>-` prefix was touched. Write it so a non-technical operator can
|
|
54
|
+
confirm what happened without reading code.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Automation Runbook Contract (load-bearing)
|
|
2
|
+
|
|
3
|
+
**Every registered automation loop carries a checked-in runbook, and every run of that loop ends in
|
|
4
|
+
exactly one of six run outcomes** with a one-line, operator-readable summary. A registered loop with
|
|
5
|
+
no runbook, or a run that stops without naming its outcome, is a contract violation.
|
|
6
|
+
|
|
7
|
+
**One vendor-neutral contract, to be cited by (wired in the loop-conformance ticket)
|
|
8
|
+
`lisa-setup-automations`, `lisa-automation-status`, `lisa-tear-down-automations`, and every
|
|
9
|
+
registered loop skill** (the `leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug,
|
|
10
|
+
never divergent per-loop prose).
|
|
11
|
+
|
|
12
|
+
## Membership
|
|
13
|
+
|
|
14
|
+
Membership is **registration, not skill-existence**: a loop is under this contract the moment it is
|
|
15
|
+
registered as a scheduled automation, and registering a new one pulls it in automatically. There is
|
|
16
|
+
no hardcoded roster of loops anywhere.
|
|
17
|
+
|
|
18
|
+
## The six run outcomes
|
|
19
|
+
|
|
20
|
+
Exactly one per run:
|
|
21
|
+
**`nothing-needed | candidate-proposed | change-proved | approval-requested | recovery-required | policy-obsolete`**
|
|
22
|
+
|
|
23
|
+
Health and operator action are **orthogonal** — a healthy run can still need an answer:
|
|
24
|
+
|
|
25
|
+
- `nothing-needed` — the loop ran and found nothing to act on. **Healthy.** Operator action: none.
|
|
26
|
+
- `candidate-proposed` — the loop proposed work (ticket, PRD, recommendation). **Healthy.** Operator
|
|
27
|
+
action: review the proposed item and flip it ready when you want it built.
|
|
28
|
+
- `change-proved` — the loop made a change and proved it with evidence. **Healthy.** Operator
|
|
29
|
+
action: none (informational).
|
|
30
|
+
- `approval-requested` — the loop reached a boundary it may not cross alone. **Healthy.** Operator
|
|
31
|
+
action: answer the approval question.
|
|
32
|
+
- `recovery-required` — the loop itself could not complete (access, tooling, or substrate broken)
|
|
33
|
+
and escalated a decision-ready packet. **Not healthy.** Operator action: restore the named
|
|
34
|
+
capability, then close the escalation item.
|
|
35
|
+
- `policy-obsolete` — the loop's own retirement policy (the retirement condition written in its
|
|
36
|
+
runbook) tripped, so it proposed its own teardown. **Healthy.** Operator action: approve the
|
|
37
|
+
teardown, decline it (close the proposal; the loop keeps running at cadence), or re-cadence it.
|
|
38
|
+
|
|
39
|
+
## A run outcome is NOT a work-item lifecycle terminal state (CRITICAL)
|
|
40
|
+
|
|
41
|
+
A **run outcome** describes the LOOP ITERATION. A **terminal state** describes a TICKET — Lisa
|
|
42
|
+
already uses that phrase for work-item lifecycles (`lisa-intake`: "`Blocked` is a valid terminal
|
|
43
|
+
state of the downstream lifecycles"). The two vocabularies never merge in an operator-facing report.
|
|
44
|
+
A healthy cycle that routes a work item to `Blocked` with clarifying questions is
|
|
45
|
+
`candidate-proposed` — it produced something — so it is **never `nothing-needed`**, which is
|
|
46
|
+
reserved for runs that found nothing at all, and **never `recovery-required`**, which means the
|
|
47
|
+
loop itself is broken, not that a work item was blocked.
|
|
48
|
+
|
|
49
|
+
## No silent exit
|
|
50
|
+
|
|
51
|
+
Every run — including a trivial early termination — ends by naming exactly one run outcome plus a
|
|
52
|
+
one-line operator summary of what happened, recorded where the status surface can read it —
|
|
53
|
+
there is no silent exit. Silence and health must never look identical to an operator.
|
|
54
|
+
|
|
55
|
+
## Never block, always degrade
|
|
56
|
+
|
|
57
|
+
A missing runbook, an unreadable record surface, or an absent optional dependency **degrades** the
|
|
58
|
+
run — say so in the summary and finish with an outcome — it never crashes the loop, never blocks
|
|
59
|
+
other work, and never leaves the run unreported.
|
|
60
|
+
|
|
61
|
+
Full contract (template, outcome definitions, escalation packet, retirement): [reference/automation-runbook-contract.md](../reference/automation-runbook-contract.md).
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Automation Runbook Contract
|
|
2
|
+
|
|
3
|
+
Scheduled loops are the busiest machinery in the factory and historically the least explicitly
|
|
4
|
+
contracted: what a loop maintains, what it may do on its own, what it must escalate, and when it
|
|
5
|
+
should stop existing all lived implicitly in each loop's prose. The cost lands on the operator — a
|
|
6
|
+
quiet run and a broken run look identical, and "what is this scheduled thing?" has a different
|
|
7
|
+
answer shape for every loop. This contract writes the answer down once.
|
|
8
|
+
|
|
9
|
+
It is a **single vendor-neutral contract** consumed by `lisa-setup-automations` (which scaffolds a
|
|
10
|
+
runbook per registered loop), `lisa-automation-status` (which reads runbooks and run outcomes back
|
|
11
|
+
to an operator), `lisa-tear-down-automations` (which acts on retirement proposals), and every
|
|
12
|
+
registered loop skill (which conforms its own run to it). Each consumer cites this slug; none of
|
|
13
|
+
them redefines it.
|
|
14
|
+
|
|
15
|
+
**Membership is registration, not skill-existence.** A loop falls under this contract when it is
|
|
16
|
+
registered as a scheduled automation on the host project's runtime scheduler — not when its skill
|
|
17
|
+
file happens to exist in the plugin tree. Registering a new loop pulls it in automatically, and
|
|
18
|
+
un-registering drops it. Nothing in this contract, in the scaffolder, or in the status surface may
|
|
19
|
+
carry a hardcoded roster of loops; the worked example below names one loop as illustration only.
|
|
20
|
+
|
|
21
|
+
## The runbook template
|
|
22
|
+
|
|
23
|
+
Every registered loop's runbook has these ten sections, in this order. Each is answered in prose a
|
|
24
|
+
non-technical operator can read (`factory-model` rule 5).
|
|
25
|
+
|
|
26
|
+
| Section | What a good answer looks like |
|
|
27
|
+
|---|---|
|
|
28
|
+
| Intent | One sentence naming what this loop maintains, in outcome terms, not mechanism terms. |
|
|
29
|
+
| Sources of truth | The exact surfaces the loop reads, each reachable through its access layer (`integration-access-layer`) — never a direct vendor API call. |
|
|
30
|
+
| Candidate selection | The query or filter that decides what this run acts on, and the bound on how many. |
|
|
31
|
+
| Scope/bounds | What the loop will never touch, and the cap that keeps one run finite. |
|
|
32
|
+
| Proof | What the loop must observe before claiming it changed anything — the evidence it records. |
|
|
33
|
+
| Autonomous-vs-approval boundary | The named line: what it does alone, and the first thing on the other side that requires a human. |
|
|
34
|
+
| Escalation | What it files when it cannot proceed — always the decision-ready packet below. |
|
|
35
|
+
| Recovery | What a human or the next run does to get the loop working again after `recovery-required`. |
|
|
36
|
+
| Next-run state | What the next run can observe from this one, derived from durable surfaces (tracker, records) rather than in-process memory. |
|
|
37
|
+
| Retirement condition | The stateless, tracker-derived condition under which this loop proposes its own teardown. |
|
|
38
|
+
|
|
39
|
+
### Worked example — the `intake-tickets` loop
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
Loop: intake-tickets
|
|
43
|
+
|
|
44
|
+
Intent
|
|
45
|
+
Keeps the build queue moving: work items a human marked ready get built, reviewed, and shipped
|
|
46
|
+
without anyone having to hand them to an agent.
|
|
47
|
+
|
|
48
|
+
Sources of truth
|
|
49
|
+
The configured tracker's build queue (the ready/claimed/done lanes from .lisa.config.json), read
|
|
50
|
+
through the tracker access layer; the item's own comments and linked PRs.
|
|
51
|
+
|
|
52
|
+
Candidate selection
|
|
53
|
+
The oldest eligible leaf work item currently in the ready lane, one per run. Containers with open
|
|
54
|
+
children are repaired out of the queue, never built.
|
|
55
|
+
|
|
56
|
+
Scope/bounds
|
|
57
|
+
Only items in this repo's configured queue. It never edits the ready lane's meaning, never
|
|
58
|
+
invents work items, and never processes more than its per-run cap.
|
|
59
|
+
|
|
60
|
+
Proof
|
|
61
|
+
A merged pull request plus the project's own verification evidence posted back to the work item;
|
|
62
|
+
a claim of "done" without that evidence is not made.
|
|
63
|
+
|
|
64
|
+
Autonomous-vs-approval boundary
|
|
65
|
+
Alone: claim, plan, implement, open the PR, drive it to merge. Requires a human: anything that
|
|
66
|
+
needs a protected deployment approval, and any item whose requirements it cannot resolve — that
|
|
67
|
+
becomes a blocked work item with clarifying questions, not a guess.
|
|
68
|
+
|
|
69
|
+
Escalation
|
|
70
|
+
When the loop itself cannot proceed (tracker credentials revoked, queue unreadable), it files the
|
|
71
|
+
decision-ready packet below, labeled status:blocked + human-needed.
|
|
72
|
+
|
|
73
|
+
Recovery
|
|
74
|
+
A human restores the named access, then flips the escalation item closed; the next scheduled run
|
|
75
|
+
resumes with no manual replay needed.
|
|
76
|
+
|
|
77
|
+
Next-run state
|
|
78
|
+
Nothing in memory. The tracker lanes and the run records are the state: a claimed item that never
|
|
79
|
+
finished is visible as claimed, and the repair path picks it up.
|
|
80
|
+
|
|
81
|
+
Retirement condition
|
|
82
|
+
Propose teardown when BOTH hold: no work item has entered this queue for 30 consecutive days, AND
|
|
83
|
+
this run proposed nothing. It files one teardown-proposal item and keeps running at its normal
|
|
84
|
+
cadence until someone approves, declines, or re-cadences it.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## The six run outcomes
|
|
88
|
+
|
|
89
|
+
Exactly one per run. The one-line operator summary is not optional — it is what the status surface
|
|
90
|
+
shows and what makes the outcome actionable.
|
|
91
|
+
|
|
92
|
+
Health and operator action are **orthogonal**: several healthy outcomes still want an answer, and
|
|
93
|
+
only `recovery-required` means the machinery itself is broken.
|
|
94
|
+
|
|
95
|
+
| Outcome | Definition | Healthy? | Operator action? | One-line summary must contain |
|
|
96
|
+
|---|---|---|---|---|
|
|
97
|
+
| `nothing-needed` | The loop ran end to end and found nothing to act on. | Healthy — **no operator action** | None. | What was scanned, how much was seen, and "nothing to propose". |
|
|
98
|
+
| `candidate-proposed` | The loop proposed work — a ticket, PRD, or recommendation — for a human or a downstream factory. | Healthy | Review the proposed item and flip it ready when you want it built. | What was proposed and where to find it (item refs). |
|
|
99
|
+
| `change-proved` | The loop made a change and proved it with evidence. | Healthy | None — informational. | What changed and the evidence that proves it. |
|
|
100
|
+
| `approval-requested` | The loop reached its autonomous/approval boundary and asked a human. | Healthy | Answer the approval question. | What is waiting, on whom, and what happens if nobody answers. |
|
|
101
|
+
| `recovery-required` | The loop itself could not complete: access, tooling, or substrate is broken. | **Not healthy** — needs a human | Restore the named capability, then close the escalation item. | What broke, and the escalation item to act on. |
|
|
102
|
+
| `policy-obsolete` | The loop's own retirement policy — the retirement condition written in its runbook — tripped, so it proposed its own teardown. | Healthy | Decide: approve the teardown, decline it (close the proposal; the loop continues at cadence), or re-cadence it. | Why it looks obsolete and the teardown-proposal item. |
|
|
103
|
+
|
|
104
|
+
### Exemplar one-line summaries
|
|
105
|
+
|
|
106
|
+
These set the register every conforming loop copies — plain, specific, and actionable without
|
|
107
|
+
reading code:
|
|
108
|
+
|
|
109
|
+
```text
|
|
110
|
+
nothing-needed Scanned 12 ready items; nothing to propose.
|
|
111
|
+
candidate-proposed Proposed #1810 to fix the failing checkout step; awaiting your flip to ready.
|
|
112
|
+
change-proved Merged #1811 raising coverage to 84%; CI green and evidence posted on the item.
|
|
113
|
+
approval-requested Deploy to production is waiting on your approval; nothing ships until you answer.
|
|
114
|
+
recovery-required Lost access to the tracker; filed #1812 with the one decision needed to restore it.
|
|
115
|
+
policy-obsolete No work has reached this queue in 30 days; proposed teardown in #1813.
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**No silent exit.** Every run posts its outcome and its one-line summary before stopping —
|
|
119
|
+
including the trivial early terminations (empty queue, nothing eligible, already-claimed) — so
|
|
120
|
+
there is no silent exit for an operator to misread as health. A run that ends without a
|
|
121
|
+
recorded outcome is indistinguishable from a crashed scheduler, which is exactly the ambiguity this
|
|
122
|
+
contract removes. This generalizes `lisa-improve-harness`'s result-record discipline — every
|
|
123
|
+
terminal step posts its result record before stopping, so there is no silent exit — from one flow to
|
|
124
|
+
every registered loop. (The run-record substrate that stores outcomes and summaries is named here
|
|
125
|
+
only; it ships with that ticket (#1797), do not assume its file is present in this branch.)
|
|
126
|
+
|
|
127
|
+
## A run outcome is not a work-item lifecycle terminal state
|
|
128
|
+
|
|
129
|
+
This is the highest-risk seam in the contract, so it is stated explicitly.
|
|
130
|
+
|
|
131
|
+
- A **run outcome** describes the LOOP ITERATION — how this scheduled execution ended.
|
|
132
|
+
- A **terminal state** describes a TICKET — Lisa already uses that phrase for work-item lifecycles
|
|
133
|
+
(`lisa-intake`: "`Blocked` is a valid terminal state of the downstream lifecycles"; see
|
|
134
|
+
`prd-lifecycle-rollup`).
|
|
135
|
+
|
|
136
|
+
They never merge in an operator-facing report. A perfectly healthy cycle that routes a work item to
|
|
137
|
+
`Blocked` — because the item's requirements are unresolvable and a human must answer — is
|
|
138
|
+
`candidate-proposed`: the run produced something (a blocked item carrying clarifying questions), so
|
|
139
|
+
it is **never `nothing-needed`**, which is reserved for runs that found nothing at all, and **never
|
|
140
|
+
`recovery-required`**, which means the machinery is broken. Conflating a blocked work item with a
|
|
141
|
+
broken loop would paint healthy intake cycles permanently red and train operators to ignore the
|
|
142
|
+
fleet verdict.
|
|
143
|
+
|
|
144
|
+
## Escalation — the decision-ready packet
|
|
145
|
+
|
|
146
|
+
A loop that cannot proceed escalates by filing a tracker item (through `lisa-tracker-write`, per
|
|
147
|
+
`tracked-work` and `integration-access-layer`) labeled **`status:blocked`** and **`human-needed`**,
|
|
148
|
+
containing exactly these fields:
|
|
149
|
+
|
|
150
|
+
| Field | Content |
|
|
151
|
+
|---|---|
|
|
152
|
+
| Current state | Where things stand right now, in plain terms — what is and is not working. |
|
|
153
|
+
| Work already attempted | What the loop tried on its own before escalating, so nobody repeats it. |
|
|
154
|
+
| Evidence | Links to the runs, items, logs, or records that show the problem (the evidence packet from the evidence PRD is the carrier; this contract names it and defines no evidence formats). |
|
|
155
|
+
| Risk of inaction | What degrades or stops if nobody acts, and how soon. |
|
|
156
|
+
| Smallest unresolved choice | The single smallest decision a human must make — one question, not a list — stated with **named options** (or as a yes/no), and what the loop will do with each answer. |
|
|
157
|
+
| How to answer | The mechanical response path: comment on the item, flip a label, or close it — so the operator never has to guess how to reply. |
|
|
158
|
+
|
|
159
|
+
The wording requirement is binding: a **non-technical operator must be able to act on the packet
|
|
160
|
+
without reading code** (`factory-model` rule 5). A stack trace may be attached as evidence, but the
|
|
161
|
+
packet must stand without it.
|
|
162
|
+
|
|
163
|
+
## Retirement condition
|
|
164
|
+
|
|
165
|
+
A loop that no longer earns its schedule should say so. The condition is **stateless — derived from
|
|
166
|
+
the tracker, never from a counter or a state file** (there is no durable home for a run counter, and
|
|
167
|
+
a tracker-derived condition is headless- and concurrent-safe by construction). This generalizes the
|
|
168
|
+
*Retirement condition* section of `lisa-learnings-audit`, which already retires itself this way; its
|
|
169
|
+
current wording still uses the older three-value "terminal states" vocabulary, and the
|
|
170
|
+
loop-conformance ticket conforms it to the six run outcomes above. A loop proposes retirement when
|
|
171
|
+
BOTH hold:
|
|
172
|
+
|
|
173
|
+
1. **Quiet trailing window** — a date-filtered tracker search finds nothing this loop produced
|
|
174
|
+
within its trailing quiet window (sized to a small multiple of its cadence).
|
|
175
|
+
2. **This run proposes nothing** — the current run yields zero candidates (it is otherwise ending
|
|
176
|
+
`nothing-needed`).
|
|
177
|
+
|
|
178
|
+
When both hold, the run ends `policy-obsolete` and files **exactly ONE marker-deduped**
|
|
179
|
+
teardown-proposal item — matched on the marker, never the title — carrying the date-filtered search
|
|
180
|
+
result and this run's summary as evidence, and proposing either a longer cadence or
|
|
181
|
+
`lisa-tear-down-automations`. The loop **keeps running at its normal cadence** until an operator
|
|
182
|
+
flips that proposal one of three ways: **approve** it (`lisa-tear-down-automations` runs and the
|
|
183
|
+
registration goes away), **decline** it (close the proposal — the loop simply continues), or
|
|
184
|
+
**re-cadence** it (register it at the longer cadence instead of tearing it down). Retirement is a
|
|
185
|
+
recommendation like any other, **never a self-executed exit**: a loop never deletes its own
|
|
186
|
+
registration, and a proposal nobody answers changes nothing.
|
|
187
|
+
|
|
188
|
+
## Never block, always degrade
|
|
189
|
+
|
|
190
|
+
A loop degrades rather than blocking. Three triggers are expected and must never crash a run:
|
|
191
|
+
|
|
192
|
+
- **A missing runbook** — the loop runs on the contract's defaults and says so.
|
|
193
|
+
- **An unreadable record surface** — the loop still reports its outcome and summary in its own run
|
|
194
|
+
output, so the operator is never left with silence.
|
|
195
|
+
- **An absent optional dependency** — a skill or surface that ships with a sibling ticket is not
|
|
196
|
+
yet installed; the loop skips that enrichment and continues.
|
|
197
|
+
|
|
198
|
+
A degraded run still **reports its actual outcome from the same six values** — degradation does not
|
|
199
|
+
mint a seventh token and, on its own, is not `recovery-required`. Its one-line summary **leads with
|
|
200
|
+
the degradation**, then states the outcome:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
Runbook missing — ran on defaults; nothing to propose.
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Degradation becomes `recovery-required` only when it prevents the loop from doing its actual job
|
|
207
|
+
(the queue itself is unreadable, credentials are revoked) — and then it escalates the decision-ready
|
|
208
|
+
packet above. A degraded run never blocks other work and never leaves the run unreported.
|
|
209
|
+
|
|
210
|
+
## Citing adjacent work
|
|
211
|
+
|
|
212
|
+
Cite these by name; each ships with its own ticket, do not assume its file is present in this
|
|
213
|
+
branch: the runbook scaffolding that instantiates this template (#1796), the run-record substrate
|
|
214
|
+
that stores outcomes and summaries (#1797), and the evidence packet defined by the evidence PRD
|
|
215
|
+
(#1738) — named here, with no evidence format defined by this contract. Rejection memory
|
|
216
|
+
extends `rejection-detection`; this contract neither restates nor overrides it.
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { compareAutomationFleet } from "./automation-status-contract-drift.mjs";
|
|
13
|
+
import {
|
|
14
|
+
assignToAutomationGroup,
|
|
15
|
+
createAutomationGroupBins,
|
|
16
|
+
renderAutomationGroups,
|
|
17
|
+
} from "./automation-status-expected-fleet.mjs";
|
|
13
18
|
|
|
14
19
|
const CLAUDE_RUNTIME_LABEL = "Claude /schedule";
|
|
15
20
|
const CLAUDE_ACTIVE_STATUSES = new Set([
|
|
@@ -75,10 +80,7 @@ export function inspectClaudeAutomationFleet(input) {
|
|
|
75
80
|
automationPrefix: expectedFleet.automationPrefix,
|
|
76
81
|
});
|
|
77
82
|
|
|
78
|
-
const expectedGroups =
|
|
79
|
-
["core", []],
|
|
80
|
-
["exploratory", []],
|
|
81
|
-
]);
|
|
83
|
+
const expectedGroups = createAutomationGroupBins();
|
|
82
84
|
|
|
83
85
|
const comparisons = compareAutomationFleet({
|
|
84
86
|
expectedAutomations: expectedFleet.expected,
|
|
@@ -87,7 +89,9 @@ export function inspectClaudeAutomationFleet(input) {
|
|
|
87
89
|
|
|
88
90
|
for (const [index, expected] of expectedFleet.expected.entries()) {
|
|
89
91
|
const comparison = comparisons[index];
|
|
90
|
-
|
|
92
|
+
assignToAutomationGroup(
|
|
93
|
+
expectedGroups,
|
|
94
|
+
expected.group,
|
|
91
95
|
createObservedStatusItem({
|
|
92
96
|
expected,
|
|
93
97
|
comparison,
|
|
@@ -97,7 +101,7 @@ export function inspectClaudeAutomationFleet(input) {
|
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
for (const unsupported of expectedFleet.unsupported) {
|
|
100
|
-
expectedGroups
|
|
104
|
+
assignToAutomationGroup(expectedGroups, unsupported.group, {
|
|
101
105
|
id: unsupported.automationId,
|
|
102
106
|
status: "UNSUPPORTED",
|
|
103
107
|
summary: unsupported.reason,
|
|
@@ -109,18 +113,7 @@ export function inspectClaudeAutomationFleet(input) {
|
|
|
109
113
|
return {
|
|
110
114
|
runtime: `${CLAUDE_RUNTIME_LABEL} listing`,
|
|
111
115
|
generatedAt: now.toISOString(),
|
|
112
|
-
groups:
|
|
113
|
-
{
|
|
114
|
-
id: "1",
|
|
115
|
-
title: "Core automations",
|
|
116
|
-
items: expectedGroups.get("core") ?? [],
|
|
117
|
-
},
|
|
118
|
-
{
|
|
119
|
-
id: "2",
|
|
120
|
-
title: "Exploratory automations",
|
|
121
|
-
items: expectedGroups.get("exploratory") ?? [],
|
|
122
|
-
},
|
|
123
|
-
],
|
|
116
|
+
groups: renderAutomationGroups(expectedGroups),
|
|
124
117
|
observedAutomations,
|
|
125
118
|
};
|
|
126
119
|
}
|
|
@@ -487,6 +480,13 @@ function normalizeClaudeRRule(value) {
|
|
|
487
480
|
) {
|
|
488
481
|
return "FREQ=DAILY;INTERVAL=1";
|
|
489
482
|
}
|
|
483
|
+
if (
|
|
484
|
+
cadence === "once a week" ||
|
|
485
|
+
cadence === "every week" ||
|
|
486
|
+
cadence === "weekly"
|
|
487
|
+
) {
|
|
488
|
+
return "FREQ=WEEKLY;INTERVAL=1";
|
|
489
|
+
}
|
|
490
490
|
|
|
491
491
|
const everyMinutes = cadence.match(/every (\d+) minutes?/);
|
|
492
492
|
if (everyMinutes?.[1]) {
|
|
@@ -503,6 +503,11 @@ function normalizeClaudeRRule(value) {
|
|
|
503
503
|
return `FREQ=DAILY;INTERVAL=${everyDays[1]}`;
|
|
504
504
|
}
|
|
505
505
|
|
|
506
|
+
const everyWeeks = cadence.match(/every (\d+) weeks?/);
|
|
507
|
+
if (everyWeeks?.[1]) {
|
|
508
|
+
return `FREQ=WEEKLY;INTERVAL=${everyWeeks[1]}`;
|
|
509
|
+
}
|
|
510
|
+
|
|
506
511
|
return undefined;
|
|
507
512
|
}
|
|
508
513
|
|
|
@@ -527,6 +532,9 @@ function humanizeClaudeCadence(value) {
|
|
|
527
532
|
if (normalized === "every day") {
|
|
528
533
|
return "once a day";
|
|
529
534
|
}
|
|
535
|
+
if (normalized === "weekly" || normalized === "every week") {
|
|
536
|
+
return "once a week";
|
|
537
|
+
}
|
|
530
538
|
return normalized;
|
|
531
539
|
}
|
|
532
540
|
|
|
@@ -556,6 +564,11 @@ function humanizeRRule(rrule) {
|
|
|
556
564
|
return Number(daily[1]) === 1 ? "once a day" : `every ${daily[1]} days`;
|
|
557
565
|
}
|
|
558
566
|
|
|
567
|
+
const weekly = rrule.match(/^FREQ=WEEKLY;INTERVAL=(\d+)$/);
|
|
568
|
+
if (weekly?.[1]) {
|
|
569
|
+
return Number(weekly[1]) === 1 ? "once a week" : `every ${weekly[1]} weeks`;
|
|
570
|
+
}
|
|
571
|
+
|
|
559
572
|
return rrule;
|
|
560
573
|
}
|
|
561
574
|
|
|
@@ -574,6 +587,11 @@ function cadenceLabelToIntervalMs(label) {
|
|
|
574
587
|
return 24 * 60 * 60_000;
|
|
575
588
|
}
|
|
576
589
|
|
|
590
|
+
const oncePerWeek = new Set(["once a week", "weekly"]);
|
|
591
|
+
if (oncePerWeek.has(label)) {
|
|
592
|
+
return 7 * 24 * 60 * 60_000;
|
|
593
|
+
}
|
|
594
|
+
|
|
577
595
|
return null;
|
|
578
596
|
}
|
|
579
597
|
|