@codyswann/lisa 4.83.2 → 4.84.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 (160) hide show
  1. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  2. package/dist/core/lisa-owned-hash-ledger.js +6 -0
  3. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  4. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  5. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  6. package/dist/core/upstream-evidence-manifest.js +17 -13
  7. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  8. package/package.json +4 -4
  9. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  10. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  11. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  12. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/SKILL.md +39 -0
  13. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/references/runbook-template.md +6 -0
  14. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  15. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  16. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  17. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/config.mjs +1 -0
  18. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  19. package/plugins/lisa/.codex-plugin/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  20. package/plugins/lisa/.codex-plugin/skills/lisa-setup-session-manager/SKILL.md +8 -1
  21. package/plugins/lisa/rules/reference/config-resolution.md +1 -0
  22. package/plugins/lisa/scripts/ready-queue-order.mjs +109 -6
  23. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  24. package/plugins/lisa/skills/lisa-session-manager/SKILL.md +39 -0
  25. package/plugins/lisa/skills/lisa-session-manager/references/runbook-template.md +6 -0
  26. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  27. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  28. package/plugins/lisa/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  29. package/plugins/lisa/skills/lisa-session-manager/scripts/config.mjs +1 -0
  30. package/plugins/lisa/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  31. package/plugins/lisa/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  32. package/plugins/lisa/skills/lisa-setup-session-manager/SKILL.md +8 -1
  33. package/plugins/lisa-agy/plugin.json +1 -1
  34. package/plugins/lisa-agy/scripts/ready-queue-order.mjs +109 -6
  35. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  36. package/plugins/lisa-agy/skills/lisa-session-manager/SKILL.md +39 -0
  37. package/plugins/lisa-agy/skills/lisa-session-manager/references/runbook-template.md +6 -0
  38. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  39. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  40. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  41. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/config.mjs +1 -0
  42. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  43. package/plugins/lisa-agy/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  44. package/plugins/lisa-agy/skills/lisa-setup-session-manager/SKILL.md +8 -1
  45. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  48. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-cdk-devin/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-cdk-devin/.devin-plugin/plugin.json +1 -1
  52. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/rules/reference/config-resolution.md +1 -0
  54. package/plugins/lisa-copilot/scripts/ready-queue-order.mjs +109 -6
  55. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  56. package/plugins/lisa-copilot/skills/lisa-session-manager/SKILL.md +39 -0
  57. package/plugins/lisa-copilot/skills/lisa-session-manager/references/runbook-template.md +6 -0
  58. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  59. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  60. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  61. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/config.mjs +1 -0
  62. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  63. package/plugins/lisa-copilot/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  64. package/plugins/lisa-copilot/skills/lisa-setup-session-manager/SKILL.md +8 -1
  65. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +1 -0
  67. package/plugins/lisa-cursor/scripts/ready-queue-order.mjs +109 -6
  68. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  69. package/plugins/lisa-cursor/skills/lisa-session-manager/SKILL.md +39 -0
  70. package/plugins/lisa-cursor/skills/lisa-session-manager/references/runbook-template.md +6 -0
  71. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  72. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  73. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  74. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/config.mjs +1 -0
  75. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  76. package/plugins/lisa-cursor/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  77. package/plugins/lisa-cursor/skills/lisa-setup-session-manager/SKILL.md +8 -1
  78. package/plugins/lisa-devin/.claude-plugin/plugin.json +1 -1
  79. package/plugins/lisa-devin/.devin-plugin/plugin.json +1 -1
  80. package/plugins/lisa-devin/rules/reference/config-resolution.md +1 -0
  81. package/plugins/lisa-devin/scripts/ready-queue-order.mjs +109 -6
  82. package/plugins/lisa-devin/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  83. package/plugins/lisa-devin/skills/lisa-session-manager/SKILL.md +39 -0
  84. package/plugins/lisa-devin/skills/lisa-session-manager/references/runbook-template.md +6 -0
  85. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  86. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  87. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  88. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/config.mjs +1 -0
  89. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  90. package/plugins/lisa-devin/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  91. package/plugins/lisa-devin/skills/lisa-setup-session-manager/SKILL.md +8 -1
  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-expo-devin/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-expo-devin/.devin-plugin/plugin.json +1 -1
  99. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  101. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  102. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-harper-fabric-devin/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-harper-fabric-devin/.devin-plugin/plugin.json +1 -1
  106. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  108. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  109. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  110. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-nestjs-devin/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-nestjs-devin/.devin-plugin/plugin.json +1 -1
  113. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  116. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-openclaw-devin/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-openclaw-devin/.devin-plugin/plugin.json +1 -1
  120. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  122. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  123. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  125. package/plugins/lisa-phaser-devin/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-phaser-devin/.devin-plugin/plugin.json +1 -1
  127. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  129. package/plugins/lisa-rails-agy/plugin.json +1 -1
  130. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-rails-devin/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-rails-devin/.devin-plugin/plugin.json +1 -1
  134. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  135. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  136. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  137. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  138. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-typescript-devin/.claude-plugin/plugin.json +1 -1
  140. package/plugins/lisa-typescript-devin/.devin-plugin/plugin.json +1 -1
  141. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  142. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  143. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  144. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  145. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  146. package/plugins/lisa-wiki-devin/.claude-plugin/plugin.json +1 -1
  147. package/plugins/lisa-wiki-devin/.devin-plugin/plugin.json +1 -1
  148. package/plugins/src/base/rules/reference/config-resolution.md +1 -0
  149. package/plugins/src/base/scripts/ready-queue-order.mjs +109 -6
  150. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +28 -0
  151. package/plugins/src/base/skills/lisa-session-manager/SKILL.md +39 -0
  152. package/plugins/src/base/skills/lisa-session-manager/references/runbook-template.md +6 -0
  153. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-cycle.mjs +10 -0
  154. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-queue.mjs +145 -18
  155. package/plugins/src/base/skills/lisa-session-manager/scripts/cli-usage.mjs +13 -0
  156. package/plugins/src/base/skills/lisa-session-manager/scripts/config.mjs +1 -0
  157. package/plugins/src/base/skills/lisa-session-manager/scripts/ledger.mjs +70 -0
  158. package/plugins/src/base/skills/lisa-session-manager/scripts/session-manager.mjs +2 -0
  159. package/plugins/src/base/skills/lisa-setup-session-manager/SKILL.md +8 -1
  160. package/typescript/copy-overwrite/scripts/check-skipped-required-checks.mjs +276 -6
@@ -214,6 +214,17 @@ Skip while `capacity` reports `paused`. Otherwise:
214
214
  createdAt, assignee, labels, description, creator and children with their state type.
215
215
  - `ordered` is the dispatch order: assigned to an operator, then to "me", then unassigned; each
216
216
  group by priority, then age. `excluded` needs no action.
217
+ - **Backlog tier** (only when `sessionManager.backlogPickup` is `true`). GitHub: `queue` fetches
218
+ it itself (open issues with no build or PRD lifecycle label). Jira / Linear: also fetch the
219
+ open leaf items in **no** lifecycle role — Linear workflow state type `backlog` or `unstarted`
220
+ other than the configured `ready` state; Jira status category "To Do" other than the configured
221
+ `ready` status — paged to completion, including each item's `updatedAt` / `updated` and its
222
+ blockers (Linear `inverseRelations` with `type` and the blocking issue's `state.type`; Jira
223
+ `issuelinks`). Put them in the same input file as
224
+ `"backlog":{"complete":true,"pages":<n>,"items":[...]}`; `queue` refuses a missing or partial
225
+ backlog envelope while the key is on. It returns `backlogOrdered` (same grouping and order,
226
+ minus items with an unfinished blocker and items whose cached validation failure still matches
227
+ their `updatedAt`) and `backlogExcluded`.
217
228
  2. **While `capacity` says `can_spawn`** and items remain, take the next item that has no pending
218
229
  question in the ledger:
219
230
  1. Read it fully (`lisa-tracker-read`): title, description, labels, project, parent, comments.
@@ -244,6 +255,34 @@ Skip while `capacity` reports `paused`. Otherwise:
244
255
  `recovery-required` with that message as the summary. It is not a failed attempt; do not
245
256
  dispatch to that repo again until the operator fixes it.
246
257
  - any other exit: move the item back to the `ready` role, notify Telegram, continue.
258
+ 3. **Backlog tier, lowest priority.** Only when `backlogPickup` is on, every `ordered` item has been
259
+ taken or skipped, and `capacity` still says `can_spawn`. Ready work always wins: never promote a
260
+ backlog item while a dispatchable Ready item remains. For the next `backlogOrdered` item with no
261
+ pending question in the ledger:
262
+ 1. Read it fully (`lisa-tracker-read`). If it gained an assignee who is neither "me" nor an
263
+ operator, a lifecycle role, open child work, a human hold or an unfinished blocker since the
264
+ queue was listed, skip it.
265
+ 2. **Validate it** with `lisa-tracker-validate` against the live item (identifier mode, all
266
+ gates, including the stateless-pickup dry-run). This is what "can be taken to completion"
267
+ means. On a definitive `FAIL`, record
268
+ `ledger validation-failed <ref> --updated-at <the item's updatedAt>` with a one-line summary
269
+ of the failing gates on STDIN, and skip it; the queue leaves it out until the item changes.
270
+ On could-not-validate (an outage or unreadable item, not a verdict), skip it WITHOUT
271
+ recording, so the next cycle retries it. Either way, post nothing to Telegram: backlog
272
+ grooming is not the operator's inbox.
273
+ 3. **Route the repo** as in step 2.2, but never ask: an ambiguous or multi-repo backlog item is
274
+ skipped.
275
+ 4. **Promote** in ONE `lisa-tracker-write` update: assignee = the first `operatorIds` entry, move
276
+ to the `ready` role, comment
277
+ `[lisa-session-manager] Promoted from backlog: validation passed; assigned to the operator.`
278
+ Read it back with `lisa-tracker-read` and continue only when the role is `ready` and the
279
+ assignee is that operator. Then `ledger validation-cleared <ref>`.
280
+ 5. Continue with steps 2.3 and 2.4 (claim in the child repo, re-read, spawn, handle the exit)
281
+ exactly as for a Ready item, with one change: on exit 0, post
282
+ `[<REF>] promoted from backlog and started in <repo> (<name>)` in place of the plain start
283
+ notice. Every other exit keeps its step 2.4 handling.
284
+ A promotion written and read back counts as a state change for the run outcome; a spawn that
285
+ then fails follows step 2.4's exits (the item stays in Ready, assigned to the operator).
247
286
 
248
287
  ### 3. Report and record
249
288
 
@@ -23,6 +23,12 @@ then to the manager's own tracker identity, then unassigned; within each, by pri
23
23
  Items assigned to someone else, containers, human-held items and (when configured) items by
24
24
  untrusted authors are skipped. As many as the machine has capacity for.
25
25
 
26
+ When `sessionManager.backlogPickup` is on and capacity remains after every Ready item, a lowest tier
27
+ follows: backlog items (open leaves in no lifecycle role) in the same order, minus those with an
28
+ unfinished blocker and those that already failed validation and have not changed since. Each one
29
+ must pass the full tracker validation before it is assigned to the first operator, moved to Ready
30
+ and dispatched like any other item.
31
+
26
32
  ## Scope/bounds
27
33
 
28
34
  Only the repos listed in `sessionManager.repos` (or this repo). It never writes code itself, never
@@ -24,9 +24,11 @@ import {
24
24
  import { parseJson, readJsonFile } from "./cli-queue.mjs";
25
25
  import {
26
26
  clearPendingQuestion,
27
+ clearValidationFailure,
27
28
  readLedger,
28
29
  recordFinish,
29
30
  recordSpawn,
31
+ recordValidationFailure,
30
32
  setControl,
31
33
  setPendingQuestion,
32
34
  } from "./ledger.mjs";
@@ -130,6 +132,14 @@ const LEDGER_ACTIONS = {
130
132
  reason: stdinText(ctx, "reason", { required: false }),
131
133
  });
132
134
  },
135
+ "validation-failed": ctx =>
136
+ recordValidationFailure(ctx.root, {
137
+ ref: requirePositional(ctx, "ref"),
138
+ updatedAt: requireValue(ctx, "updated-at"),
139
+ reason: stdinText(ctx, "reason", { required: false }),
140
+ }),
141
+ "validation-cleared": ctx =>
142
+ clearValidationFailure(ctx.root, { ref: requirePositional(ctx, "ref") }),
133
143
  control: ctx =>
134
144
  setControl(
135
145
  ctx.root,
@@ -9,6 +9,10 @@
9
9
  * `{complete, pages, items}`; anything but a literal `complete: true` is
10
10
  * refused with exit 2, because ordering a partial queue would dispatch the
11
11
  * wrong item with full confidence.
12
+ *
13
+ * With `sessionManager.backlogPickup` on, `queue` also ranks the backlog tier
14
+ * (open leaves in no lifecycle role) as `backlogOrdered`, under the same
15
+ * paging rule. The manager walks it only after the whole Ready queue.
12
16
  * @module session-manager/cli-queue
13
17
  */
14
18
  import { readFileSync } from "node:fs";
@@ -18,6 +22,7 @@ import {
18
22
  normalizeGithubIssue,
19
23
  normalizeJiraIssue,
20
24
  normalizeLinearIssue,
25
+ orderBacklogQueue,
21
26
  orderReadyQueue,
22
27
  } from "../../../scripts/ready-queue-order.mjs";
23
28
  import { resolveRole } from "../../../scripts/resolve-lifecycle-role.mjs";
@@ -61,12 +66,12 @@ export function readJsonFile(file) {
61
66
  }
62
67
 
63
68
  /**
64
- * Fetch every open GitHub issue carrying the ready label, all pages.
69
+ * The GitHub repo to read: `--github-repo`, else github.org/github.repo.
65
70
  * @param {object} ctx - CLI context
66
71
  * @param {ReturnType<typeof loadProject>} project - Loaded project
67
- * @returns {{ items: object[], pages: number }} Normalized items
72
+ * @returns {string} `owner/repo`
68
73
  */
69
- function fetchGithubQueue(ctx, project) {
74
+ function githubRepo(ctx, project) {
70
75
  const merged = { ...project.global?.github, ...project.local?.github };
71
76
  const repo =
72
77
  ctx.values["github-repo"] ??
@@ -77,6 +82,68 @@ function fetchGithubQueue(ctx, project) {
77
82
  2
78
83
  );
79
84
  }
85
+ return repo;
86
+ }
87
+
88
+ /**
89
+ * Every string leaf of an object (label names, including env-keyed `done`).
90
+ * @param {unknown} value - Config subtree
91
+ * @returns {string[]} Leaves
92
+ */
93
+ function stringLeaves(value) {
94
+ if (typeof value === "string") return value ? [value] : [];
95
+ if (!value || typeof value !== "object") return [];
96
+ return Object.values(value).flatMap(stringLeaves);
97
+ }
98
+
99
+ /**
100
+ * Fetch every open GitHub issue carrying no lifecycle label (build or PRD),
101
+ * all pages: the GitHub backlog tier.
102
+ * @param {object} ctx - CLI context
103
+ * @param {ReturnType<typeof loadProject>} project - Loaded project
104
+ * @returns {{ items: object[], pages: number }} Normalized items
105
+ */
106
+ function fetchGithubBacklog(ctx, project) {
107
+ const repo = githubRepo(ctx, project);
108
+ const lifecycle = new Set(
109
+ [project.global?.github?.labels, project.local?.github?.labels]
110
+ .flatMap(labels => [
111
+ ...stringLeaves(labels?.build),
112
+ ...stringLeaves(labels?.prd),
113
+ ])
114
+ .map(label => label.toLowerCase())
115
+ );
116
+ const stdout = mustRun(
117
+ "gh",
118
+ [
119
+ "api",
120
+ "--paginate",
121
+ "--slurp",
122
+ `repos/${repo}/issues?state=open&per_page=100`,
123
+ ],
124
+ { env: scrubWorkerEnv(ctx.env) }
125
+ );
126
+ const pages = parseJson(stdout, "gh api output");
127
+ const rows = Array.isArray(pages) ? pages.flat() : [];
128
+ return {
129
+ items: rows
130
+ .map(row => normalizeGithubIssue(row, { repo }))
131
+ .filter(Boolean)
132
+ .filter(
133
+ item => !item.labels.some(label => lifecycle.has(label.toLowerCase()))
134
+ ),
135
+ pages: Array.isArray(pages) ? pages.length : 0,
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Fetch every open GitHub issue carrying the ready label, all pages.
141
+ * @param {object} ctx - CLI context
142
+ * @param {ReturnType<typeof loadProject>} project - Loaded project
143
+ * @returns {{ items: object[], pages: number }} Normalized items
144
+ */
145
+ function fetchGithubQueue(ctx, project) {
146
+ const repo = githubRepo(ctx, project);
80
147
  const role = resolveRole({
81
148
  role: "ready",
82
149
  vendor: "github",
@@ -101,24 +168,51 @@ function fetchGithubQueue(ctx, project) {
101
168
  }
102
169
 
103
170
  /**
104
- * Read a vendor-skill envelope and normalize its raw issues.
105
- * @param {object} ctx - CLI context
171
+ * Check one paged envelope and normalize its raw issues.
172
+ * @param {unknown} envelope - `{complete, pages, items}`
173
+ * @param {string} where - Where it came from, for messages
106
174
  * @param {"jira"|"linear"} tracker - Vendor
107
175
  * @returns {{ items: object[], pages: unknown }} Normalized items
108
176
  */
109
- function readEnvelopeQueue(ctx, tracker) {
177
+ function normalizeEnvelope(envelope, where, tracker) {
178
+ asSetupError(() => assertPagedToCompletion(envelope));
179
+ if (!Array.isArray(envelope.items)) {
180
+ throw new CliError(`${where}: items must be an array.`, 2);
181
+ }
182
+ return {
183
+ items: envelope.items.map(NORMALIZERS[tracker]),
184
+ pages: envelope.pages ?? null,
185
+ };
186
+ }
187
+
188
+ /**
189
+ * Read a vendor-skill envelope and normalize its raw issues. With
190
+ * `withBacklog`, the envelope must also carry `backlog`, a second
191
+ * `{complete, pages, items}` envelope held to the same paging rule.
192
+ * @param {object} ctx - CLI context
193
+ * @param {"jira"|"linear"} tracker - Vendor
194
+ * @param {boolean} withBacklog - Whether the backlog tier is required
195
+ * @returns {{ items: object[], pages: unknown, backlog: { items: object[], pages: unknown } | null }} Normalized items
196
+ */
197
+ function readEnvelopeQueue(ctx, tracker, withBacklog) {
110
198
  const file = ctx.values.input;
111
199
  if (!file) {
112
200
  throw new CliError(`queue --tracker ${tracker} needs --input <file>.`, 2);
113
201
  }
114
202
  const envelope = readJsonFile(file);
115
- asSetupError(() => assertPagedToCompletion(envelope));
116
- if (!Array.isArray(envelope.items)) {
117
- throw new CliError(`${file}: items must be an array.`, 2);
203
+ const ready = normalizeEnvelope(envelope, file, tracker);
204
+ if (!withBacklog) return { ...ready, backlog: null };
205
+ if (!envelope.backlog) {
206
+ throw new CliError(
207
+ `${file}: sessionManager.backlogPickup is on, so the input needs a ` +
208
+ `"backlog" envelope {complete:true,pages,items} of open leaf items ` +
209
+ `in no lifecycle role.`,
210
+ 2
211
+ );
118
212
  }
119
213
  return {
120
- items: envelope.items.map(NORMALIZERS[tracker]),
121
- pages: envelope.pages ?? null,
214
+ ...ready,
215
+ backlog: normalizeEnvelope(envelope.backlog, `${file} backlog`, tracker),
122
216
  };
123
217
  }
124
218
 
@@ -126,8 +220,12 @@ function readEnvelopeQueue(ctx, tracker) {
126
220
  * `queue --tracker <github|jira|linear>`: the Ready queue, the operator's
127
221
  * items first (`sessionManager.operatorIds`), then mine, then unassigned.
128
222
  * @param {object} ctx - CLI context
223
+ * With `sessionManager.backlogPickup` on it also returns the backlog tier,
224
+ * ordered the same way and always dispatched after every Ready item.
225
+ * @param {object} ctx - CLI context
129
226
  * @returns {Promise<{ json: object }>}
130
- * `{tracker,me,operators,total,pages,ordered,excluded}`
227
+ * `{tracker,me,operators,total,pages,ordered,excluded,backlogPickup[,
228
+ * backlogTotal,backlogPages,backlogOrdered,backlogExcluded]}`
131
229
  */
132
230
  export async function queue(ctx) {
133
231
  const tracker = ctx.values.tracker;
@@ -136,19 +234,48 @@ export async function queue(ctx) {
136
234
  }
137
235
  const project = loadProject(ctx.root);
138
236
  const config = validOrAbsent(project);
139
- const { items, pages } =
237
+ const backlogPickup = config?.backlogPickup === true;
238
+ const { items, pages, backlog } =
140
239
  tracker === "github"
141
- ? fetchGithubQueue(ctx, project)
142
- : readEnvelopeQueue(ctx, tracker);
143
- const me = ctx.values.me ?? (await readLedger(ctx.root)).control.me ?? null;
240
+ ? {
241
+ ...fetchGithubQueue(ctx, project),
242
+ backlog: backlogPickup ? fetchGithubBacklog(ctx, project) : null,
243
+ }
244
+ : readEnvelopeQueue(ctx, tracker, backlogPickup);
245
+ const ledger = await readLedger(ctx.root);
246
+ const me = ctx.values.me ?? ledger.control.me ?? null;
144
247
  const operators = config?.operatorIds ?? [];
145
- const order = orderReadyQueue(items, {
248
+ const policy = {
146
249
  me,
147
250
  operators,
148
251
  trustedAuthorIds: config?.trustedAuthorIds ?? undefined,
149
252
  humanNeededLabel: humanNeededLabel(project, tracker),
253
+ };
254
+ const order = orderReadyQueue(items, policy);
255
+ const json = {
256
+ tracker,
257
+ me,
258
+ operators,
259
+ total: items.length,
260
+ pages,
261
+ ...order,
262
+ backlogPickup,
263
+ };
264
+ if (!backlog) return { json };
265
+ // An item both Ready and in the backlog fetch is Ready work; never twice.
266
+ const readyRefs = new Set(items.map(item => item.ref));
267
+ const backlogItems = backlog.items.filter(item => !readyRefs.has(item.ref));
268
+ const tier = orderBacklogQueue(backlogItems, {
269
+ ...policy,
270
+ validationFailures: ledger.validationFailures,
150
271
  });
151
272
  return {
152
- json: { tracker, me, operators, total: items.length, pages, ...order },
273
+ json: {
274
+ ...json,
275
+ backlogTotal: backlogItems.length,
276
+ backlogPages: backlog.pages,
277
+ backlogOrdered: tier.ordered,
278
+ backlogExcluded: tier.excluded,
279
+ },
153
280
  };
154
281
  }
@@ -117,6 +117,13 @@ Per cycle
117
117
  first, then to --me, then unassigned; each group by priority, then age.
118
118
  Items assigned to anyone else are excluded (assigned-to-other).
119
119
  --me defaults to the ledger's control.me.
120
+ With sessionManager.backlogPickup true, also prints backlogTotal,
121
+ backlogPages, backlogOrdered and backlogExcluded: open leaves in no
122
+ lifecycle role, same order, always after every ready item; excluded
123
+ also for blocked-by-open-item and failed-validation-unchanged (the
124
+ ledger's validationFailures entry matches the item's updatedAt).
125
+ GitHub fetches them itself; jira|linear need <file>.backlog =
126
+ {complete:true,pages,items}, else exit 2.
120
127
  ledger get [<ref>]
121
128
  ledger record-spawn <ref> --worker <name> [--session-id <id>] [--repo <key>]
122
129
  ledger set-question <ref> [--telegram-message-id <n>] [--sender <name>]
@@ -124,6 +131,12 @@ Per cycle
124
131
  ledger clear-question <ref>
125
132
  ledger finish <ref> --status DONE|FAILED [--sender <name>] STDIN: reason
126
133
  --sender: exit 2 unless the ledger's worker for <ref> is exactly <name>.
134
+ ledger validation-failed <ref> --updated-at <ts> STDIN: failing gates
135
+ Caches a backlog item's failed validation; queue skips it until the
136
+ item's updatedAt changes.
137
+ ledger validation-cleared <ref>
138
+ Forgets that failure: after a promotion, or by hand to have the next
139
+ cycle re-validate an item without editing it. stdout {cleared}.
127
140
  ledger control [--paused true|false] [--max-workers <n>|none] [--me <id>|none]
128
141
  route [--reply-to <telegram message id>] [--owner <name>] STDIN: message
129
142
  stdout {kind:"answer",ref,body[,message]} | {kind:"command",name,...}
@@ -67,6 +67,7 @@ export const SESSION_MANAGER_DEFAULTS = Object.freeze({
67
67
  * notify: NotifyBlock,
68
68
  * trustedAuthorIds: string[] | null,
69
69
  * operatorIds?: string[] | null,
70
+ * backlogPickup?: boolean,
70
71
  * }} SessionManagerConfig
71
72
  * @typedef {{
72
73
  * name: string,
@@ -81,6 +81,17 @@ const FINISH_STATUSES = new Set(["DONE", "FAILED"]);
81
81
  * @property {number} version - Shape version
82
82
  * @property {Record<string, LedgerItem>} items - Items keyed by ref
83
83
  * @property {{paused: boolean, maxWorkers: number|null, me: string|null}} control - Operator controls
84
+ * @property {Record<string, ValidationFailure>} validationFailures - Backlog
85
+ * items that failed validation, keyed by ref, so an unchanged item is not
86
+ * re-validated every cycle
87
+ */
88
+
89
+ /**
90
+ * A backlog item's last failed validation.
91
+ * @typedef {object} ValidationFailure
92
+ * @property {string} updatedAt - The item's tracker `updatedAt` when it failed
93
+ * @property {string} at - When the failure was recorded (ISO)
94
+ * @property {string|null} reason - Short summary of the failing gates
84
95
  */
85
96
 
86
97
  /**
@@ -119,6 +130,7 @@ function emptyLedger() {
119
130
  version: LEDGER_VERSION,
120
131
  items: {},
121
132
  control: { paused: false, maxWorkers: null, me: null },
133
+ validationFailures: {},
122
134
  };
123
135
  }
124
136
 
@@ -159,6 +171,7 @@ export async function readLedger(projectRoot, seams) {
159
171
  version: parsed.version ?? empty.version,
160
172
  items: parsed.items ?? {},
161
173
  control: { ...empty.control, ...parsed.control },
174
+ validationFailures: parsed.validationFailures ?? {},
162
175
  };
163
176
  }
164
177
 
@@ -384,6 +397,63 @@ export function recordFinish(projectRoot, { ref, status, reason }, seams) {
384
397
  });
385
398
  }
386
399
 
400
+ /**
401
+ * Remember that a backlog item failed validation at its current tracker
402
+ * `updatedAt`. The backlog queue skips it until the item changes, so an
403
+ * unready item costs one validation per edit, not one per cycle.
404
+ * @param {string} projectRoot - Absolute project root
405
+ * @param {{ref: string, updatedAt: string, reason?: string|null}} failure - What failed
406
+ * @param {LedgerSeams} [seams] - Injected seams
407
+ * @returns {Promise<ValidationFailure>} The stored record
408
+ */
409
+ export function recordValidationFailure(
410
+ projectRoot,
411
+ { ref, updatedAt, reason = null },
412
+ seams
413
+ ) {
414
+ if (typeof ref !== "string" || ref === "") {
415
+ return Promise.reject(new Error("validation failure needs a ref"));
416
+ }
417
+ if (typeof updatedAt !== "string" || updatedAt === "") {
418
+ return Promise.reject(
419
+ new Error(
420
+ "validation failure needs the item's updatedAt; without it the cache could never expire"
421
+ )
422
+ );
423
+ }
424
+ return updateLedger(projectRoot, seams, (ledger, at) => {
425
+ const entry = { updatedAt, at, reason: reason || null };
426
+ return {
427
+ ledger: {
428
+ ...ledger,
429
+ validationFailures: { ...ledger.validationFailures, [ref]: entry },
430
+ },
431
+ result: entry,
432
+ };
433
+ });
434
+ }
435
+
436
+ /**
437
+ * Forget a backlog item's validation failure (it was promoted, or a human
438
+ * asked for it to be judged again).
439
+ * @param {string} projectRoot - Absolute project root
440
+ * @param {{ref: string}} target - Item to forget
441
+ * @param {LedgerSeams} [seams] - Injected seams
442
+ * @returns {Promise<{cleared: boolean}>} Whether a record existed
443
+ */
444
+ export function clearValidationFailure(projectRoot, { ref }, seams) {
445
+ return updateLedger(projectRoot, seams, ledger => {
446
+ if (!Object.hasOwn(ledger.validationFailures, ref)) {
447
+ return { ledger, result: { cleared: false } };
448
+ }
449
+ const { [ref]: _removed, ...rest } = ledger.validationFailures;
450
+ return {
451
+ ledger: { ...ledger, validationFailures: rest },
452
+ result: { cleared: true },
453
+ };
454
+ });
455
+ }
456
+
387
457
  /**
388
458
  * Check a control patch before it is written.
389
459
  * @param {{paused?: unknown, maxWorkers?: unknown, me?: unknown}} patch - Patch
@@ -130,6 +130,8 @@ const LEDGER_SPECS = {
130
130
  },
131
131
  "clear-question": { options: {}, positionals: 1 },
132
132
  finish: { options: { status: text, sender: text }, positionals: 1 },
133
+ "validation-failed": { options: { "updated-at": text }, positionals: 1 },
134
+ "validation-cleared": { options: {}, positionals: 1 },
133
135
  control: {
134
136
  options: { paused: text, "max-workers": text, me: text },
135
137
  positionals: 0,
@@ -68,7 +68,14 @@ writes `$SM`, and pass `--project-root "$ROOT"` (the absolute project root) on e
68
68
  "me", spelled as the tracker API returns it: the GitHub **login** (`gh api users/<login> --jq
69
69
  .login`), the Jira `accountId`, or the Linear user id (the operator's entry in the team's member
70
70
  list, or `viewer.id` only when the operator runs the lookup with their own credential).
71
- 5. `node "$SM/session-manager.mjs" validate --project-root "$ROOT"` must print `valid: true`.
71
+ 5. Ask whether to set `sessionManager.backlogPickup` to `true` (default off). When the Ready queue
72
+ is exhausted and a worker slot is still free, the manager then looks at backlog items (open
73
+ leaves in no lifecycle role) that nobody else is assigned to, that are not human-held and have
74
+ no unfinished blocker. Each one that passes the full tracker validation, including the
75
+ stateless-pickup check, is assigned to the first `operatorIds` entry, moved to Ready and
76
+ dispatched. Say plainly that this lets the manager start work nobody marked Ready, so it needs
77
+ `operatorIds` and should go with `trustedAuthorIds` on any tracker outsiders can write to.
78
+ 6. `node "$SM/session-manager.mjs" validate --project-root "$ROOT"` must print `valid: true`.
72
79
 
73
80
  ## Step 3: Store the bot token
74
81
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo and React Native-specific skills, agents, rules, and MCP servers.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-expo",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Expo/React Native-specific skills, agents, rules, and MCP servers",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "4.83.2",
3
+ "version": "4.84.1",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"