@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.
- package/all/copy-contents/gitignore +2 -0
- package/all/create-only/.lisa/DEPENDENCY_DECISIONS.md +175 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +20 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/standards-proof-cmd.d.ts +7 -0
- package/dist/cli/standards-proof-cmd.d.ts.map +1 -0
- package/dist/cli/standards-proof-cmd.js +14 -0
- package/dist/cli/standards-proof-cmd.js.map +1 -0
- package/dist/cli/ui-setup-readiness-local.d.ts +2 -1
- package/dist/cli/ui-setup-readiness-local.d.ts.map +1 -1
- package/dist/cli/ui-setup-readiness-local.js +4 -2
- package/dist/cli/ui-setup-readiness-local.js.map +1 -1
- package/dist/cli/ui-setup-readiness.d.ts.map +1 -1
- package/dist/cli/ui-setup-readiness.js +3 -2
- package/dist/cli/ui-setup-readiness.js.map +1 -1
- package/dist/cli/update-check-hook.d.ts +19 -0
- package/dist/cli/update-check-hook.d.ts.map +1 -0
- package/dist/cli/update-check-hook.js +36 -0
- package/dist/cli/update-check-hook.js.map +1 -0
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +49 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/standards/capture.d.ts +41 -0
- package/dist/standards/capture.d.ts.map +1 -0
- package/dist/standards/capture.js +196 -0
- package/dist/standards/capture.js.map +1 -0
- package/dist/standards/contract.d.ts +45 -0
- package/dist/standards/contract.d.ts.map +1 -0
- package/dist/standards/contract.js +175 -0
- package/dist/standards/contract.js.map +1 -0
- package/dist/standards/git-state.d.ts +30 -0
- package/dist/standards/git-state.d.ts.map +1 -0
- package/dist/standards/git-state.js +179 -0
- package/dist/standards/git-state.js.map +1 -0
- package/dist/standards/index.d.ts +8 -0
- package/dist/standards/index.d.ts.map +1 -0
- package/dist/standards/index.js +8 -0
- package/dist/standards/index.js.map +1 -0
- package/dist/standards/readiness.d.ts +11 -0
- package/dist/standards/readiness.d.ts.map +1 -0
- package/dist/standards/readiness.js +170 -0
- package/dist/standards/readiness.js.map +1 -0
- package/dist/standards/registry.d.ts +42 -0
- package/dist/standards/registry.d.ts.map +1 -0
- package/dist/standards/registry.js +330 -0
- package/dist/standards/registry.js.map +1 -0
- package/dist/standards/storage-reasons.d.ts +15 -0
- package/dist/standards/storage-reasons.d.ts.map +1 -0
- package/dist/standards/storage-reasons.js +48 -0
- package/dist/standards/storage-reasons.js.map +1 -0
- package/dist/standards/storage.d.ts +36 -0
- package/dist/standards/storage.d.ts.map +1 -0
- package/dist/standards/storage.js +330 -0
- package/dist/standards/storage.js.map +1 -0
- package/dist/standards/test-evidence.d.ts +19 -0
- package/dist/standards/test-evidence.d.ts.map +1 -0
- package/dist/standards/test-evidence.js +195 -0
- package/dist/standards/test-evidence.js.map +1 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +21 -3
- package/plugins/lisa/rules/eager/dependency-decision-records.md +32 -0
- package/plugins/lisa/rules/reference/dependency-decision-records.md +140 -0
- package/plugins/lisa/scripts/doctor-report.mjs +266 -22
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +21 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/scripts/doctor-report.mjs +266 -22
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +21 -3
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/rules/eager/dependency-decision-records.md +32 -0
- package/plugins/lisa-copilot/rules/reference/dependency-decision-records.md +140 -0
- package/plugins/lisa-copilot/scripts/doctor-report.mjs +266 -22
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +21 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/dependency-decision-records-reference.mdc +145 -0
- package/plugins/lisa-cursor/rules/dependency-decision-records.mdc +37 -0
- package/plugins/lisa-cursor/scripts/doctor-report.mjs +266 -22
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +21 -3
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/rules/eager/dependency-decision-records.md +32 -0
- package/plugins/src/base/rules/reference/dependency-decision-records.md +140 -0
- package/plugins/src/base/scripts/doctor-report.mjs +266 -22
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +21 -3
- package/rails/copy-overwrite/lefthook.yml +9 -11
- package/rails/copy-overwrite/scripts/lisa-clean-git-env.sh +13 -0
- package/typescript/copy-contents/.husky/pre-push +9 -0
- 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
|
|
125
|
-
*
|
|
126
|
-
*
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
194
|
-
*
|
|
195
|
-
*
|
|
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
|
-
|
|
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:
|
|
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.
|
|
323
|
-
|
|
324
|
-
|
|
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
|
|
@@ -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.
|