@codyswann/lisa 2.280.0 → 2.282.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.
Files changed (148) hide show
  1. package/all/copy-contents/gitignore +2 -0
  2. package/dist/cli/doctor-readiness-capabilities.d.ts +33 -0
  3. package/dist/cli/doctor-readiness-capabilities.d.ts.map +1 -0
  4. package/dist/cli/doctor-readiness-capabilities.js +26 -0
  5. package/dist/cli/doctor-readiness-capabilities.js.map +1 -0
  6. package/dist/cli/doctor-readiness-domain.d.ts +17 -0
  7. package/dist/cli/doctor-readiness-domain.d.ts.map +1 -0
  8. package/dist/cli/doctor-readiness-domain.js +197 -0
  9. package/dist/cli/doctor-readiness-domain.js.map +1 -0
  10. package/dist/cli/doctor-readiness-guardrails.d.ts +15 -0
  11. package/dist/cli/doctor-readiness-guardrails.d.ts.map +1 -0
  12. package/dist/cli/doctor-readiness-guardrails.js +219 -0
  13. package/dist/cli/doctor-readiness-guardrails.js.map +1 -0
  14. package/dist/cli/doctor-readiness-operations.d.ts +103 -0
  15. package/dist/cli/doctor-readiness-operations.d.ts.map +1 -0
  16. package/dist/cli/doctor-readiness-operations.js +207 -0
  17. package/dist/cli/doctor-readiness-operations.js.map +1 -0
  18. package/dist/cli/doctor-readiness-workflows.d.ts +20 -0
  19. package/dist/cli/doctor-readiness-workflows.d.ts.map +1 -1
  20. package/dist/cli/doctor-readiness-workflows.js +29 -7
  21. package/dist/cli/doctor-readiness-workflows.js.map +1 -1
  22. package/dist/cli/doctor-readiness.d.ts.map +1 -1
  23. package/dist/cli/doctor-readiness.js +18 -12
  24. package/dist/cli/doctor-readiness.js.map +1 -1
  25. package/dist/cli/index.d.ts +3 -0
  26. package/dist/cli/index.d.ts.map +1 -1
  27. package/dist/cli/index.js +20 -15
  28. package/dist/cli/index.js.map +1 -1
  29. package/dist/cli/standards-proof-cmd.d.ts +7 -0
  30. package/dist/cli/standards-proof-cmd.d.ts.map +1 -0
  31. package/dist/cli/standards-proof-cmd.js +14 -0
  32. package/dist/cli/standards-proof-cmd.js.map +1 -0
  33. package/dist/cli/ui-setup-readiness-local.d.ts +2 -1
  34. package/dist/cli/ui-setup-readiness-local.d.ts.map +1 -1
  35. package/dist/cli/ui-setup-readiness-local.js +4 -2
  36. package/dist/cli/ui-setup-readiness-local.js.map +1 -1
  37. package/dist/cli/ui-setup-readiness.d.ts.map +1 -1
  38. package/dist/cli/ui-setup-readiness.js +3 -2
  39. package/dist/cli/ui-setup-readiness.js.map +1 -1
  40. package/dist/cli/update-check-hook.d.ts +19 -0
  41. package/dist/cli/update-check-hook.d.ts.map +1 -0
  42. package/dist/cli/update-check-hook.js +36 -0
  43. package/dist/cli/update-check-hook.js.map +1 -0
  44. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  45. package/dist/core/upstream-evidence-manifest.js +47 -6
  46. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  47. package/dist/standards/capture.d.ts +41 -0
  48. package/dist/standards/capture.d.ts.map +1 -0
  49. package/dist/standards/capture.js +196 -0
  50. package/dist/standards/capture.js.map +1 -0
  51. package/dist/standards/contract.d.ts +45 -0
  52. package/dist/standards/contract.d.ts.map +1 -0
  53. package/dist/standards/contract.js +175 -0
  54. package/dist/standards/contract.js.map +1 -0
  55. package/dist/standards/git-state.d.ts +30 -0
  56. package/dist/standards/git-state.d.ts.map +1 -0
  57. package/dist/standards/git-state.js +179 -0
  58. package/dist/standards/git-state.js.map +1 -0
  59. package/dist/standards/index.d.ts +8 -0
  60. package/dist/standards/index.d.ts.map +1 -0
  61. package/dist/standards/index.js +8 -0
  62. package/dist/standards/index.js.map +1 -0
  63. package/dist/standards/readiness.d.ts +11 -0
  64. package/dist/standards/readiness.d.ts.map +1 -0
  65. package/dist/standards/readiness.js +170 -0
  66. package/dist/standards/readiness.js.map +1 -0
  67. package/dist/standards/registry.d.ts +42 -0
  68. package/dist/standards/registry.d.ts.map +1 -0
  69. package/dist/standards/registry.js +330 -0
  70. package/dist/standards/registry.js.map +1 -0
  71. package/dist/standards/storage-reasons.d.ts +15 -0
  72. package/dist/standards/storage-reasons.d.ts.map +1 -0
  73. package/dist/standards/storage-reasons.js +48 -0
  74. package/dist/standards/storage-reasons.js.map +1 -0
  75. package/dist/standards/storage.d.ts +36 -0
  76. package/dist/standards/storage.d.ts.map +1 -0
  77. package/dist/standards/storage.js +330 -0
  78. package/dist/standards/storage.js.map +1 -0
  79. package/dist/standards/test-evidence.d.ts +19 -0
  80. package/dist/standards/test-evidence.d.ts.map +1 -0
  81. package/dist/standards/test-evidence.js +195 -0
  82. package/dist/standards/test-evidence.js.map +1 -0
  83. package/package.json +1 -1
  84. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  85. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  86. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +21 -3
  87. package/plugins/lisa/scripts/doctor-report.mjs +266 -22
  88. package/plugins/lisa/skills/lisa-doctor/SKILL.md +21 -3
  89. package/plugins/lisa-agy/plugin.json +1 -1
  90. package/plugins/lisa-agy/scripts/doctor-report.mjs +266 -22
  91. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +21 -3
  92. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  95. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-copilot/scripts/doctor-report.mjs +266 -22
  99. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +21 -3
  100. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-cursor/scripts/doctor-report.mjs +266 -22
  102. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +21 -3
  103. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-expo-agy/plugin.json +1 -1
  106. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  111. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  116. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  121. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  125. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  126. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  130. package/plugins/lisa-rails-agy/plugin.json +1 -1
  131. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  134. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  135. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  136. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  138. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  140. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  141. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  142. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  143. package/plugins/src/base/scripts/doctor-report.mjs +266 -22
  144. package/plugins/src/base/skills/lisa-doctor/SKILL.md +21 -3
  145. package/rails/copy-overwrite/lefthook.yml +9 -11
  146. package/rails/copy-overwrite/scripts/lisa-clean-git-env.sh +13 -0
  147. package/typescript/copy-contents/.husky/pre-push +9 -0
  148. package/ui/index.html +3 -2
@@ -6,7 +6,7 @@
6
6
  * repo adds real readiness probes. Keep this file dependency-free so future
7
7
  * doctor scripts can reuse it from plugin distributions and downstream repos.
8
8
  */
9
- import { existsSync } from "node:fs";
9
+ import { existsSync, readFileSync } from "node:fs";
10
10
  import path from "node:path";
11
11
  import process from "node:process";
12
12
 
@@ -121,9 +121,12 @@ export function createPluginSyncDoctorGroup(root = process.cwd()) {
121
121
  /**
122
122
  * The eight repository-readiness ownership dimensions, in fixed render order,
123
123
  * defined once by the `readiness-rubric` rule. This group consumes that rubric;
124
- * it does not redefine the vocabulary. Evidence gathering for each dimension is
125
- * wired by later PRD #1739 tickets, so every dimension renders `SKIP` with a
126
- * reason here reported, never silently omitted, per the shipped contract.
124
+ * it does not redefine the vocabulary. Evidence gathering lives in the
125
+ * TypeScript CLI producers, which persist the authoritative per-dimension
126
+ * result to `.lisa/readiness.json`; this surface projects that result. When no
127
+ * usable report is on disk, each dimension renders `SKIP` carrying the reason
128
+ * it was not assessed — reported, never silently omitted, per the shipped
129
+ * contract.
127
130
  * @type {readonly { id: string, question: string, skipReason: string }[]}
128
131
  */
129
132
  const REPOSITORY_READINESS_DIMENSIONS = [
@@ -132,81 +135,322 @@ const REPOSITORY_READINESS_DIMENSIONS = [
132
135
  question:
133
136
  "Can an agent recover the real job from what is written down (integration-access-layer, wiki-knowledge-source, config-resolution)?",
134
137
  skipReason:
135
- "Context/routing evidence is assessed by the agent-ready wiring (RRR-4, #1856); no readiness probe is wired in this Lisa version.",
138
+ "Context/routing evidence is assessed by the agent-ready wiring (RRR-4, #1856); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
136
139
  },
137
140
  {
138
141
  id: "capabilities-tools",
139
142
  question:
140
143
  "Is every tool the work needs provably reachable, not merely installed (tool-access-gate)?",
141
144
  skipReason:
142
- "Capabilities/tools evidence is gathered by the journey-execution wiring (RRR-6, #1858); no readiness probe is wired in this Lisa version.",
145
+ "Tool reachability needs a live read-only probe and can never be established from an offline read: the `tool-access-gate` rule names presence — a binary on `PATH`, a dependency in a manifest, a configured MCP server — as the anti-pattern, because it proves a tool was declared, never that a request through it succeeded. This dimension is therefore a deliberate, permanent reasoned SKIP on an offline pass rather than a not-yet-wired one; only a run that actually exercises the tools can answer it.",
143
146
  },
144
147
  {
145
148
  id: "domain-ownership",
146
149
  question:
147
150
  "Are the business rules, glossary, and danger zones owned and written down (agent-ready domain phase wiki pages)?",
148
151
  skipReason:
149
- "Domain-ownership evidence sources from agent-ready's danger-zone wiki pages, read by RRR-4 (#1856); no readiness probe is wired in this Lisa version.",
152
+ "Domain-ownership evidence sources from agent-ready's danger-zone wiki pages, read by RRR-4 (#1856); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
150
153
  },
151
154
  {
152
155
  id: "execution-proof",
153
156
  question:
154
157
  "Can the claimed user-visible outcome be proved by running the system (verification, empirical-inquiry, claim-evidence-mapping)?",
155
158
  skipReason:
156
- "Execution/proof consumes qualification evidence and representative journeys wired by RRR-6 (#1858); no readiness probe is wired in this Lisa version.",
159
+ "Execution/proof consumes qualification evidence and representative journeys wired by RRR-6 (#1858); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
157
160
  },
158
161
  {
159
162
  id: "feedback-guardrails",
160
163
  question:
161
164
  "Does a failing loop produce a named outcome and a runbook (automation-runbook-contract, observability-audit)?",
162
165
  skipReason:
163
- "Feedback/guardrails evidence is assessed by RRR-4 (#1856); no readiness probe is wired in this Lisa version.",
166
+ "Feedback/guardrails evidence is assessed by RRR-4 (#1856); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
164
167
  },
165
168
  {
166
169
  id: "dependencies-supply-chain",
167
170
  question:
168
171
  "Is there a confidence model for what the repo depends on (security-audit-handling)?",
169
172
  skipReason:
170
- "Dependencies/supply-chain evidence is assessed by RRR-4 (#1856); no readiness probe is wired in this Lisa version.",
173
+ "Dependencies/supply-chain evidence is assessed by RRR-4 (#1856); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
171
174
  },
172
175
  {
173
176
  id: "delivery-authority",
174
177
  question:
175
178
  "Does the thing that ships equal the thing that was validated, and does the shipping credential carry only the authority it needs (claim-archaeology, security-audit-handling)?",
176
179
  skipReason:
177
- "Delivery/authority blockers are populated by the blocker gate in RRR-5 (#1857); no readiness probe is wired in this Lisa version.",
180
+ "Delivery/authority blockers are populated by the blocker gate in RRR-5 (#1857); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
178
181
  },
179
182
  {
180
183
  id: "proportionality",
181
184
  question:
182
185
  "Is the machinery proportional to the job, or is there scaffolding to subtract (repo-scope-split, #1742 subtraction candidates)?",
183
186
  skipReason:
184
- "Proportionality reuses the scaffolding-subtraction candidates surfaced by the journey work (RRR-6, #1858); no readiness probe is wired in this Lisa version.",
187
+ "Proportionality reuses the scaffolding-subtraction candidates surfaced by the journey work (RRR-6, #1858); no usable `.lisa/readiness.json` was found, so this dimension was not assessed here. Run `lisa doctor --offline --readiness` to produce the report, then re-run doctor.",
185
188
  },
186
189
  ];
187
190
 
191
+ /**
192
+ * `schema_version` this surface knows how to project. A report stamped with any
193
+ * other version is treated as unreadable rather than guessed at: the whole point
194
+ * of the version stamp is that a reader may not invent a mapping it was never
195
+ * told about.
196
+ */
197
+ const READINESS_REPORT_SCHEMA_VERSION = 1;
198
+
199
+ /**
200
+ * Longest operator-facing summary projected out of a CLI finding. Findings can
201
+ * carry kilobytes of concatenated evidence (every offending workflow job, for
202
+ * example); the doctor line is a headline, and the full text stays available in
203
+ * `.lisa/readiness.json`.
204
+ */
205
+ const READINESS_SUMMARY_MAX_LENGTH = 320;
206
+
207
+ /**
208
+ * Resolve the single location the CLI writes the readiness report to. This
209
+ * mirrors the CLI's `resolveReadinessReportPath` so relocating the artifact
210
+ * stays a two-line change across both scorers.
211
+ * @param {string} repoRoot
212
+ * @returns {string}
213
+ */
214
+ function resolveReadinessReportPath(repoRoot) {
215
+ return path.join(repoRoot, ".lisa", "readiness.json");
216
+ }
217
+
218
+ /**
219
+ * Read the CLI-authored readiness report, or `null` when there is nothing
220
+ * trustworthy to project. Absence, unparseable JSON, an unknown
221
+ * `schema_version`, and a missing `dimensions` array are all the same answer —
222
+ * "the CLI readiness pass has not produced a result this surface can read" —
223
+ * and none of them is evidence about the repository itself.
224
+ * @param {string} repoRoot
225
+ * @returns {{ dimensions: readonly Record<string, unknown>[], blockers: readonly Record<string, unknown>[], narrowedClaim: string, provenance: string } | null}
226
+ */
227
+ function readReadinessReport(repoRoot) {
228
+ const reportPath = resolveReadinessReportPath(repoRoot);
229
+ if (!existsSync(reportPath)) {
230
+ return null;
231
+ }
232
+ try {
233
+ const parsed = JSON.parse(readFileSync(reportPath, "utf8"));
234
+ if (
235
+ parsed === null ||
236
+ typeof parsed !== "object" ||
237
+ parsed.schema_version !== READINESS_REPORT_SCHEMA_VERSION ||
238
+ !Array.isArray(parsed.dimensions)
239
+ ) {
240
+ return null;
241
+ }
242
+ return {
243
+ dimensions: parsed.dimensions,
244
+ blockers: Array.isArray(parsed.blockers) ? parsed.blockers : [],
245
+ // The narrowed claim is the sentence `readiness-rubric` requires whenever
246
+ // a blocker stands ("...IS ready for supervised, single-ticket agent
247
+ // work..."). Dropping it would hand the agent operator a bare NOT_READY
248
+ // where the CLI hands them the fallback they can actually act on.
249
+ narrowedClaim:
250
+ typeof parsed.narrowed_claim === "string" ? parsed.narrowed_claim : "",
251
+ // Stamped into every projected check so a months-old report cannot read
252
+ // as current truth: this surface reflects a file, not a live assessment.
253
+ provenance: describeReportProvenance(parsed),
254
+ };
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Describe when the projected report was produced and by which Lisa version.
262
+ * @param {Record<string, unknown>} parsed
263
+ * @returns {string}
264
+ */
265
+ function describeReportProvenance(parsed) {
266
+ const generatedAt =
267
+ typeof parsed.generated_at === "string" && parsed.generated_at.length > 0
268
+ ? parsed.generated_at
269
+ : "an unrecorded time";
270
+ const version =
271
+ typeof parsed.lisa_version === "string" && parsed.lisa_version.length > 0
272
+ ? ` by Lisa ${parsed.lisa_version}`
273
+ : "";
274
+ return `generated ${generatedAt}${version}`;
275
+ }
276
+
277
+ /**
278
+ * Pull the operator-facing sentence out of a dimension's findings. The CLI
279
+ * producers write human text under `evidence` (an assessed dimension),
280
+ * `reason` (a deliberately-unassessed one), or `observation` (a neutral note);
281
+ * the rest of the finding is machine bookkeeping the blocker engine owns.
282
+ * Reading only two of the three would print the dimension's question under a
283
+ * FAIL, which reads as "nothing to report" exactly when there is most to say.
284
+ * @param {readonly unknown[]} findings
285
+ * @returns {string}
286
+ */
287
+ function summarizeReadinessFindings(findings) {
288
+ const sentences = findings
289
+ .filter(finding => finding !== null && typeof finding === "object")
290
+ .flatMap(finding => [finding.evidence, finding.reason, finding.observation])
291
+ .filter(text => typeof text === "string" && text.trim().length > 0)
292
+ .map(text => text.trim());
293
+ if (sentences.length === 0) {
294
+ return "";
295
+ }
296
+ const joined = sentences.join(" ");
297
+ return joined.length > READINESS_SUMMARY_MAX_LENGTH
298
+ ? `${joined.slice(0, READINESS_SUMMARY_MAX_LENGTH - 1).trimEnd()}…`
299
+ : joined;
300
+ }
301
+
302
+ /**
303
+ * Carry the CLI's not-established caveat onto every unassessed dimension. The
304
+ * CLI headline says it out loud — "N of 8 dimensions were never assessed, so
305
+ * this cannot say whether an unattended fleet may operate here" — and a reader
306
+ * who sees a lone `SKIP` line without it can mistake silence for a clean bill
307
+ * of health on that dimension.
308
+ * @param {DoctorCheck} check
309
+ * @param {readonly DoctorCheck[]} checks
310
+ * @returns {DoctorCheck}
311
+ */
312
+ function applyNotEstablishedCaveat(check, checks) {
313
+ const unassessed = checks.filter(entry => entry.status === "SKIP").length;
314
+ if (check.status !== "SKIP" || unassessed === 0) {
315
+ return check;
316
+ }
317
+ return {
318
+ ...check,
319
+ observed: `${check.observed} NOT ESTABLISHED: ${unassessed} of ${checks.length} dimensions were never assessed, so this cannot say whether an unattended fleet may operate here.`,
320
+ };
321
+ }
322
+
323
+ /**
324
+ * Decide whether a standing blocker was evidenced in this dimension.
325
+ *
326
+ * `dimension_id` is the authority: the CLI's `buildDetectedBlocker` documents it
327
+ * as "the dimension the evidencing finding was found in". `owning_dimensions` is
328
+ * the blocker's static spec and can name dimensions that did NOT evidence it —
329
+ * B4 is specced as `[domain-ownership, feedback-guardrails]` while only the
330
+ * guardrails producer ever emits the B4 finding. Escalating on the spec would
331
+ * stamp `FAIL` on a dimension whose own recorded evidence says it found nothing,
332
+ * contradicting both that evidence and the CLI's per-dimension result. The spec
333
+ * is used only as a fallback, for a blocker record carrying no attribution.
334
+ * @param {Record<string, unknown>} blocker
335
+ * @param {{ id: string }} dimension
336
+ * @returns {boolean}
337
+ */
338
+ function blockerEvidencedIn(blocker, dimension) {
339
+ if (
340
+ typeof blocker.dimension_id === "string" &&
341
+ blocker.dimension_id.length > 0
342
+ ) {
343
+ return blocker.dimension_id === dimension.id;
344
+ }
345
+ return (
346
+ Array.isArray(blocker.owning_dimensions) &&
347
+ blocker.owning_dimensions.includes(dimension.id)
348
+ );
349
+ }
350
+
351
+ /**
352
+ * Collect the ids of the standing ship blockers evidenced in a dimension.
353
+ * Blockers with no usable id are dropped rather than stringified, so a malformed
354
+ * record can never render "Standing ship blocker(s): undefined" at an operator.
355
+ * @param {{ id: string }} dimension
356
+ * @param {{ blockers: readonly Record<string, unknown>[] }} report
357
+ * @returns {readonly string[]}
358
+ */
359
+ function standingBlockerIds(dimension, report) {
360
+ return report.blockers
361
+ .filter(
362
+ blocker =>
363
+ blocker !== null &&
364
+ typeof blocker === "object" &&
365
+ blockerEvidencedIn(blocker, dimension)
366
+ )
367
+ .map(blocker => blocker.id)
368
+ .filter(id => typeof id === "string" && id.length > 0)
369
+ .filter((id, index, ids) => ids.indexOf(id) === index);
370
+ }
371
+
372
+ /**
373
+ * Project one CLI dimension record into this surface's check shape, or `null`
374
+ * when the record is absent or carries a status outside the shipped vocabulary
375
+ * (the caller then falls back to the reasoned SKIP).
376
+ * @param {{ id: string, question: string, skipReason: string }} dimension
377
+ * @param {{ dimensions: readonly Record<string, unknown>[], blockers: readonly Record<string, unknown>[], narrowedClaim: string, provenance: string }} report
378
+ * @returns {DoctorCheck | null}
379
+ */
380
+ function projectReadinessDimension(dimension, report) {
381
+ const record = report.dimensions.find(
382
+ entry =>
383
+ entry !== null && typeof entry === "object" && entry.id === dimension.id
384
+ );
385
+ if (!record || !DOCTOR_STATUSES.includes(record.status)) {
386
+ return null;
387
+ }
388
+ const findings = Array.isArray(record.findings) ? record.findings : [];
389
+ const summary = summarizeReadinessFindings(findings);
390
+ const blockerIds = standingBlockerIds(dimension, report);
391
+
392
+ return {
393
+ id: dimension.id,
394
+ // A standing blocker outranks the recorded label. Three CLI producers (B1
395
+ // domain-ownership, B4 feedback-guardrails, B6 context-routing) record
396
+ // `WARN` while standing a blocker, each stating in-line that "the blocker
397
+ // engine never reads this status, so the finding flips the repository to
398
+ // NOT_READY exactly as a FAIL would". Projecting that `WARN` verbatim would
399
+ // score READY_WITH_WARNINGS on a repository the CLI calls NOT_READY — the
400
+ // precise disagreement this bridge exists to eliminate. This still reflects
401
+ // the CLI's decision rather than re-scoring: the CLI decided the blocker
402
+ // stands and wrote it into `blockers[]`.
403
+ status: blockerIds.length > 0 ? "FAIL" : record.status,
404
+ summary: summary.length > 0 ? summary : dimension.question,
405
+ observed: `${dimension.question} Projected from \`.lisa/readiness.json\` (schema_version ${READINESS_REPORT_SCHEMA_VERSION}, ${report.provenance}), the report the Lisa CLI readiness pass wrote.`,
406
+ ...(blockerIds.length > 0
407
+ ? {
408
+ remediation:
409
+ `Standing ship blocker(s): ${blockerIds.join(", ")}. See \`.lisa/readiness.json\` for the full evidence and the \`readiness-rubric\` rule for the blocker definitions.` +
410
+ (report.narrowedClaim.length > 0 ? ` ${report.narrowedClaim}` : ""),
411
+ }
412
+ : {}),
413
+ };
414
+ }
415
+
188
416
  /**
189
417
  * Build the orthogonal "Repository readiness" doctor group ("may an agent fleet
190
418
  * operate here unattended?"), scored against the eight `readiness-rubric`
191
419
  * ownership dimensions. It is separate from the installation-readiness groups
192
420
  * and is appended in a fixed position by the readiness-mode caller; the eight
193
- * dimension checks render in fixed order. In this Lisa version every dimension
194
- * is `SKIP` with a stated reason because the evidence-gathering surfaces ship
195
- * with later PRD #1739 tickets reported, never silently omitted.
421
+ * dimension checks render in fixed order.
422
+ *
423
+ * The blocker engine and the evidence producers live in the TypeScript CLI,
424
+ * which is the single source of truth; this surface is a bridge, not a second
425
+ * implementation (#1902). It projects whatever the CLI persisted to
426
+ * `.lisa/readiness.json` so an operator running `/lisa:doctor` through any
427
+ * coding agent gets the same readiness answer the CLI gives. When no usable
428
+ * report exists, every unmatched dimension renders `SKIP` with its reason:
429
+ * absence means the readiness pass has not run, never that the repository is
430
+ * clean, so a pass or fail is never manufactured from it.
196
431
  * @param {string} root
197
432
  * @returns {DoctorGroup}
198
433
  */
199
434
  export function createRepositoryReadinessDoctorGroup(root = process.cwd()) {
200
- void path.resolve(root);
435
+ const report = readReadinessReport(path.resolve(root));
436
+ const checks = REPOSITORY_READINESS_DIMENSIONS.map(
437
+ dimension =>
438
+ (report === null
439
+ ? null
440
+ : projectReadinessDimension(dimension, report)) ?? {
441
+ id: dimension.id,
442
+ status: "SKIP",
443
+ summary: dimension.question,
444
+ observed:
445
+ report === null
446
+ ? dimension.skipReason
447
+ : `${dimension.question} \`.lisa/readiness.json\` carries no usable record for this dimension, so it was not assessed here. Re-run \`lisa doctor --offline --readiness\` to regenerate the report.`,
448
+ }
449
+ );
201
450
  return {
202
451
  id: "repository-readiness",
203
452
  title: "Repository readiness",
204
- checks: REPOSITORY_READINESS_DIMENSIONS.map(dimension => ({
205
- id: dimension.id,
206
- status: "SKIP",
207
- summary: dimension.question,
208
- observed: dimension.skipReason,
209
- })),
453
+ checks: checks.map(check => applyNotEstablishedCaveat(check, checks)),
210
454
  };
211
455
  }
212
456
 
@@ -319,9 +319,27 @@ default doctor path never renders it and stays byte-identical.
319
319
  contract; do not restate or fork that vocabulary here.
320
320
  3. **`SKIP` carries a reason and is never blank.** A dimension with no applicable evidence renders
321
321
  `SKIP` with a stated reason ("no deployment target configured, so delivery/authority was not
322
- assessed"). An unassessed dimension is a known unknown, and the report says so. In Lisa versions
323
- where the evidence-gathering surfaces (RRR-4/5/6) are not yet wired, every dimension renders `SKIP`
324
- with that reason.
322
+ assessed"). An unassessed dimension is a known unknown, and the report says so.
323
+
324
+ **This surface reflects the CLI; it does not re-score.** The evidence producers and the blocker
325
+ engine live in the Lisa CLI, which is the single source of truth. When `.lisa/readiness.json` is
326
+ present and readable (parses, matching `schema_version`, carries a `dimensions` array), this group
327
+ **projects** each recorded dimension's status and its operator-facing evidence/reason text, so an
328
+ operator running `/lisa:doctor` through any coding agent sees the same readiness answer the CLI
329
+ gives. When the report is absent, unparseable, or stamped with an unknown `schema_version` — or
330
+ records nothing for a given dimension — that dimension falls back to `SKIP` with the reason it was
331
+ not assessed. Absence means the readiness pass has not run, never that the repository is clean: a
332
+ pass or a fail is never manufactured from a missing report. Run
333
+ `lisa doctor --offline --readiness` to produce the report first.
334
+
335
+ **A standing blocker outranks the recorded status.** Some CLI producers record `WARN` while
336
+ standing a ship blocker — the blocker engine never reads the per-dimension status, so the finding
337
+ flips the repository to `NOT_READY` exactly as a `FAIL` would. A dimension that owns an entry in
338
+ the report's `blockers[]` therefore projects as `FAIL` regardless of its recorded label, and its
339
+ remediation names the blocker ids and repeats the report's `narrowed_claim` (the "IS ready for
340
+ supervised, single-ticket agent work" fallback `readiness-rubric` requires whenever a blocker
341
+ stands). Projected checks also carry the report's `generated_at`/`lisa_version` so a stale report
342
+ cannot read as current truth, and unassessed dimensions carry the CLI's not-established caveat.
325
343
  4. **Reuse the shipped verdict ladder and consequence ordering.** No new verdict value and no new
326
344
  severity: reuse `READY` / `READY_WITH_WARNINGS` / `NOT_READY`. `READY` requires *positive*
327
345
  evidence — every readiness dimension assessed and clean, with no blocker standing. An unassessed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.280.0",
3
+ "version": "2.282.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"