@codyswann/lisa 2.281.0 → 2.283.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 (134) hide show
  1. package/all/copy-contents/gitignore +2 -0
  2. package/all/create-only/.lisa/DEPENDENCY_DECISIONS.md +175 -0
  3. package/dist/cli/index.d.ts +3 -0
  4. package/dist/cli/index.d.ts.map +1 -1
  5. package/dist/cli/index.js +20 -15
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/cli/standards-proof-cmd.d.ts +7 -0
  8. package/dist/cli/standards-proof-cmd.d.ts.map +1 -0
  9. package/dist/cli/standards-proof-cmd.js +14 -0
  10. package/dist/cli/standards-proof-cmd.js.map +1 -0
  11. package/dist/cli/ui-setup-readiness-local.d.ts +2 -1
  12. package/dist/cli/ui-setup-readiness-local.d.ts.map +1 -1
  13. package/dist/cli/ui-setup-readiness-local.js +4 -2
  14. package/dist/cli/ui-setup-readiness-local.js.map +1 -1
  15. package/dist/cli/ui-setup-readiness.d.ts.map +1 -1
  16. package/dist/cli/ui-setup-readiness.js +3 -2
  17. package/dist/cli/ui-setup-readiness.js.map +1 -1
  18. package/dist/cli/update-check-hook.d.ts +19 -0
  19. package/dist/cli/update-check-hook.d.ts.map +1 -0
  20. package/dist/cli/update-check-hook.js +36 -0
  21. package/dist/cli/update-check-hook.js.map +1 -0
  22. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  23. package/dist/core/upstream-evidence-manifest.js +49 -6
  24. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  25. package/dist/standards/capture.d.ts +41 -0
  26. package/dist/standards/capture.d.ts.map +1 -0
  27. package/dist/standards/capture.js +196 -0
  28. package/dist/standards/capture.js.map +1 -0
  29. package/dist/standards/contract.d.ts +45 -0
  30. package/dist/standards/contract.d.ts.map +1 -0
  31. package/dist/standards/contract.js +175 -0
  32. package/dist/standards/contract.js.map +1 -0
  33. package/dist/standards/git-state.d.ts +30 -0
  34. package/dist/standards/git-state.d.ts.map +1 -0
  35. package/dist/standards/git-state.js +179 -0
  36. package/dist/standards/git-state.js.map +1 -0
  37. package/dist/standards/index.d.ts +8 -0
  38. package/dist/standards/index.d.ts.map +1 -0
  39. package/dist/standards/index.js +8 -0
  40. package/dist/standards/index.js.map +1 -0
  41. package/dist/standards/readiness.d.ts +11 -0
  42. package/dist/standards/readiness.d.ts.map +1 -0
  43. package/dist/standards/readiness.js +170 -0
  44. package/dist/standards/readiness.js.map +1 -0
  45. package/dist/standards/registry.d.ts +42 -0
  46. package/dist/standards/registry.d.ts.map +1 -0
  47. package/dist/standards/registry.js +330 -0
  48. package/dist/standards/registry.js.map +1 -0
  49. package/dist/standards/storage-reasons.d.ts +15 -0
  50. package/dist/standards/storage-reasons.d.ts.map +1 -0
  51. package/dist/standards/storage-reasons.js +48 -0
  52. package/dist/standards/storage-reasons.js.map +1 -0
  53. package/dist/standards/storage.d.ts +36 -0
  54. package/dist/standards/storage.d.ts.map +1 -0
  55. package/dist/standards/storage.js +330 -0
  56. package/dist/standards/storage.js.map +1 -0
  57. package/dist/standards/test-evidence.d.ts +19 -0
  58. package/dist/standards/test-evidence.d.ts.map +1 -0
  59. package/dist/standards/test-evidence.js +195 -0
  60. package/dist/standards/test-evidence.js.map +1 -0
  61. package/package.json +1 -1
  62. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +21 -3
  65. package/plugins/lisa/rules/eager/dependency-decision-records.md +32 -0
  66. package/plugins/lisa/rules/reference/dependency-decision-records.md +140 -0
  67. package/plugins/lisa/scripts/doctor-report.mjs +266 -22
  68. package/plugins/lisa/skills/lisa-doctor/SKILL.md +21 -3
  69. package/plugins/lisa-agy/plugin.json +1 -1
  70. package/plugins/lisa-agy/scripts/doctor-report.mjs +266 -22
  71. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +21 -3
  72. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  73. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  74. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  75. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  76. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  77. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  78. package/plugins/lisa-copilot/rules/eager/dependency-decision-records.md +32 -0
  79. package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +140 -0
  80. package/plugins/lisa-copilot/scripts/doctor-report.mjs +266 -22
  81. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +21 -3
  82. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  83. package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +145 -0
  84. package/plugins/lisa-cursor/rules/dependency-decision-records.mdc +37 -0
  85. package/plugins/lisa-cursor/scripts/doctor-report.mjs +266 -22
  86. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +21 -3
  87. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  89. package/plugins/lisa-expo-agy/plugin.json +1 -1
  90. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  91. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  94. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  95. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  96. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  99. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  100. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  101. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  104. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  105. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  106. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  109. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  110. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  111. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  114. package/plugins/lisa-rails-agy/plugin.json +1 -1
  115. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  116. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  119. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  120. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  124. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  125. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  126. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  127. package/plugins/src/base/rules/eager/dependency-decision-records.md +32 -0
  128. package/plugins/src/base/rules/reference/dependency-decision-records.md +140 -0
  129. package/plugins/src/base/scripts/doctor-report.mjs +266 -22
  130. package/plugins/src/base/skills/lisa-doctor/SKILL.md +21 -3
  131. package/rails/copy-overwrite/lefthook.yml +9 -11
  132. package/rails/copy-overwrite/scripts/lisa-clean-git-env.sh +13 -0
  133. package/typescript/copy-contents/.husky/pre-push +9 -0
  134. 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-cdk",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.281.0",
3
+ "version": "2.283.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -0,0 +1,32 @@
1
+ # Dependency Decisions (load-bearing)
2
+
3
+ Material dependencies are recorded as decisions, not lockfile trivia. The
4
+ governed record is `.lisa/DEPENDENCY_DECISIONS.md`, seeded once by `lisa apply`
5
+ from the `all/create-only` template surface and never overwritten afterward.
6
+
7
+ A *material* dependency is one that owns a user-visible capability or would cost
8
+ real time to replace. Each record entry must name, in this order: why we keep it
9
+ in plain language, what it is, its owned capability, trust basis, exposure,
10
+ replacement cost, detection evidence, owner/review cadence, and last-reviewed
11
+ date. Field labels lead with the operator's question and keep the technical term
12
+ in parentheses. "Why we keep it" comes first on purpose — an entry that opens
13
+ with a version range opens with the trivia the record exists to replace.
14
+
15
+ When you add, upgrade across a major version, or remove a material dependency,
16
+ update its entry in the same change. When you cannot name the detection evidence
17
+ that would fail if an update broke the owned capability, write "nothing would
18
+ catch it" rather than inventing a check — the gap is the finding.
19
+
20
+ Write every entry so a non-technical operator can tell why the dependency is
21
+ kept and what evidence would detect a bad update. Lead with the outcome it
22
+ protects, not the mechanism.
23
+
24
+ Entries are repeatable blocks appended under `## Records`, so a seeding script
25
+ can add one without rewriting the file. An honest gap is written as
26
+ `_Not yet decided_` — never left blank.
27
+
28
+ Escalate, rather than just recording, when detection evidence says "nothing
29
+ would catch it", when `Last reviewed` predates the dependency's last major
30
+ upgrade, or when the trust basis names no maintainer or owner.
31
+
32
+ Full prose: [reference/dependency-decision-records.md](../reference/dependency-decision-records.md).
@@ -0,0 +1,140 @@
1
+ # Dependency Decisions
2
+
3
+ Lisa already governs dependency *mechanics* — pinning, audit gates, override
4
+ resolution, duplicate detection. What those mechanics cannot tell anyone is
5
+ **why** a dependency is in the project at all, what capability it owns, how much
6
+ blast radius it has, or what would fail if a bad update shipped. Dependency
7
+ decision records close that gap: they turn dependency posture into decisions an
8
+ operator can read, instead of package-manager trivia only an engineer can
9
+ interpret.
10
+
11
+ ## Where the record lives
12
+
13
+ The governed record is the markdown file `.lisa/DEPENDENCY_DECISIONS.md` in the
14
+ host project.
15
+
16
+ It is distributed through Lisa's `all/create-only` template surface, so:
17
+
18
+ - Every project that runs `lisa apply` receives the scaffold, regardless of
19
+ stack — the record is stack-independent because dependency posture is.
20
+ - The strategy is **create-only**: Lisa seeds the file once and never overwrites
21
+ it. Host projects own their content outright, and Lisa upstream never carries
22
+ a copy of any host project's private dependency inventory.
23
+ - It lives in the cold `.lisa/` directory rather than an auto-loaded rules tree,
24
+ so a long record is never injected raw into every session. Agents read the
25
+ entry they need, when they need it.
26
+
27
+ Markdown is deliberate. The record's primary audience is the product gate, where
28
+ a non-technical operator has to make a keep/replace/escalate judgment. A machine
29
+ format would serve tooling and fail that reader.
30
+
31
+ ## What an entry must name
32
+
33
+ Each material-dependency entry names nine things, in this order. Every label
34
+ leads with the operator's question and keeps the technical term in parentheses,
35
+ because the record's primary reader is looking for an answer, not a term:
36
+
37
+ - **Why we keep it** — plain-language reason, written for a non-technical
38
+ reader, leading with the outcome it protects. This is deliberately the FIRST
39
+ bullet: an entry that opens with a version range opens with exactly the
40
+ package-manager trivia this record exists to replace.
41
+ - **What it is (dependency)** — package or service, plus the version range in
42
+ use.
43
+ - **What it does for us (owned capability)** — the single capability the
44
+ dependency is responsible for. A dependency with no nameable capability is
45
+ either not material, or is doing too much and should be split in the record.
46
+ - **Why we believe it's safe (trust basis)** — the evidence that it will still
47
+ be maintained and safe next quarter: maintainer, release cadence,
48
+ security-response history, breadth of adoption, whether the version is pinned.
49
+ - **What breaks if this is compromised (exposure)** — what it can reach and what
50
+ breaks when it misbehaves: build-time versus runtime, and whether it touches
51
+ user data, secrets, the network, or CI credentials.
52
+ - **What it would take to replace (replacement cost)** — the honest effort to
53
+ remove or swap it, what would need rewriting, and whether a viable alternative
54
+ exists today.
55
+ - **What would catch a bad update (detection evidence)** — the named check that
56
+ would **fail** if an update broke the owned capability: a test, suite,
57
+ workflow, or monitor. "Nothing would catch it" is a legitimate and high-value
58
+ answer.
59
+ - **Who owns this and how often we recheck (owner / review cadence)** — who is
60
+ accountable, and how often the entry gets re-read.
61
+ - **Last reviewed** — ISO `YYYY-MM-DD` date of the most recent actual review.
62
+
63
+ ## When to escalate
64
+
65
+ The record exists to produce decisions, so three states are escalations rather
66
+ than to-dos. Raise them to an owner instead of leaving them recorded:
67
+
68
+ - Detection evidence says "nothing would catch it" — the capability is trusted
69
+ blind.
70
+ - `Last reviewed` predates the dependency's last major upgrade — the record
71
+ describes software no longer running.
72
+ - The trust basis names no maintainer, owner, or release history.
73
+
74
+ An entry full of `_Not yet decided_` is not an escalation; it is unstarted work.
75
+ These three are escalations precisely because someone already looked and the
76
+ answer was bad.
77
+
78
+ ## What counts as material
79
+
80
+ Material means: if this dependency vanished, broke, or shipped a hostile update,
81
+ a user would notice, or replacing it would cost real time. Test frameworks,
82
+ linters, HTTP clients, ORMs, auth libraries, UI toolkits, and build tooling are
83
+ usually material. Transitive packages and single-function utilities usually are
84
+ not — record the direct dependency that owns the capability, not everything
85
+ underneath it.
86
+
87
+ Do not attempt to record the whole lockfile. An exhaustive record decays into
88
+ noise and stops being read, which is worse than a short record that is true.
89
+
90
+ ## Maintenance
91
+
92
+ Update the relevant entry in the same change that adds a material dependency,
93
+ upgrades one across a major version, or removes one. Refresh `Last reviewed`
94
+ whenever someone actually re-reads an entry, even when nothing else changes — a
95
+ stale date is the signal that the record has drifted from reality.
96
+
97
+ When detection evidence fails for a dependency, treat the entry as the first
98
+ place to look and the first place to correct: either the evidence was wrong
99
+ about what it protects, or the exposure was understated.
100
+
101
+ ## Entry format
102
+
103
+ Entries are uniform and appendable by design, because a later seeding pass adds
104
+ them mechanically rather than by rewriting the document:
105
+
106
+ - An entry is a level-3 heading (`### <name>`) followed by the nine field
107
+ bullets in the fixed order above.
108
+ - Entries live under `## Records`, the final section of the file, and new
109
+ entries are appended to its end. A `###` heading inside `## Records` is always
110
+ an entry, never guidance — that scoping is what makes the section safe to
111
+ parse and append to.
112
+ - `_Not yet decided_` is the reserved marker for a field nobody has answered
113
+ yet. Never leave a field blank and never guess: a blank field is
114
+ indistinguishable from an overlooked one, and a guess is a wrong answer that
115
+ nobody re-checks.
116
+
117
+ Note that `_Not yet decided_` and "nothing would catch it" are different
118
+ statements. The first means nobody has looked. The second is a finding — a real
119
+ capability with no detection evidence behind it.
120
+
121
+ ## Scaffold, not inventory
122
+
123
+ The seeded file is a scaffold: field structure, guidance, and one clearly marked
124
+ worked example so an operator can see the intended shape. The example is meant
125
+ to be replaced or deleted once real entries exist. Lisa does not pre-populate a
126
+ host project's dependency inventory — only the project knows which of its
127
+ dependencies are material.
128
+
129
+ ## Agent parity
130
+
131
+ The record is a governed markdown artifact, not a runtime behavior, so every
132
+ supported coding agent — Claude Code, Codex, Cursor, OpenCode, Antigravity, and
133
+ Copilot — reaches it identically through the shared rules mirror and the same
134
+ `.lisa/DEPENDENCY_DECISIONS.md` path in the host project. There is no per-agent
135
+ enforcement surface to diverge.
136
+
137
+ The documented gap, uniform across all six runtimes: no agent runtime enforces
138
+ that entries stay accurate or current. Nothing blocks a commit whose dependency
139
+ change skips the record, and nothing fails when `Last reviewed` goes stale.
140
+ Freshness is carried by this rule and by review, not by a hook or a lint gate.