@codyswann/lisa 2.277.4 → 2.279.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 (149) hide show
  1. package/dist/cli/doctor-readiness-credentials.d.ts +46 -0
  2. package/dist/cli/doctor-readiness-credentials.d.ts.map +1 -0
  3. package/dist/cli/doctor-readiness-credentials.js +199 -0
  4. package/dist/cli/doctor-readiness-credentials.js.map +1 -0
  5. package/dist/cli/doctor-readiness-delivery.d.ts +16 -0
  6. package/dist/cli/doctor-readiness-delivery.d.ts.map +1 -0
  7. package/dist/cli/doctor-readiness-delivery.js +243 -0
  8. package/dist/cli/doctor-readiness-delivery.js.map +1 -0
  9. package/dist/cli/doctor-readiness-journey.d.ts +4 -10
  10. package/dist/cli/doctor-readiness-journey.d.ts.map +1 -1
  11. package/dist/cli/doctor-readiness-journey.js +17 -1
  12. package/dist/cli/doctor-readiness-journey.js.map +1 -1
  13. package/dist/cli/doctor-readiness-release-path.d.ts +62 -0
  14. package/dist/cli/doctor-readiness-release-path.d.ts.map +1 -0
  15. package/dist/cli/doctor-readiness-release-path.js +275 -0
  16. package/dist/cli/doctor-readiness-release-path.js.map +1 -0
  17. package/dist/cli/doctor-readiness-types.d.ts +25 -0
  18. package/dist/cli/doctor-readiness-types.d.ts.map +1 -0
  19. package/dist/cli/doctor-readiness-types.js +13 -0
  20. package/dist/cli/doctor-readiness-types.js.map +1 -0
  21. package/dist/cli/doctor-readiness-workflows.d.ts +64 -0
  22. package/dist/cli/doctor-readiness-workflows.d.ts.map +1 -0
  23. package/dist/cli/doctor-readiness-workflows.js +198 -0
  24. package/dist/cli/doctor-readiness-workflows.js.map +1 -0
  25. package/dist/cli/doctor-readiness.d.ts +19 -3
  26. package/dist/cli/doctor-readiness.d.ts.map +1 -1
  27. package/dist/cli/doctor-readiness.js +87 -26
  28. package/dist/cli/doctor-readiness.js.map +1 -1
  29. package/dist/cli/ui-automations-adapters.d.ts +22 -0
  30. package/dist/cli/ui-automations-adapters.d.ts.map +1 -1
  31. package/dist/cli/ui-automations-adapters.js +24 -12
  32. package/dist/cli/ui-automations-adapters.js.map +1 -1
  33. package/dist/cli/ui-automations.d.ts +1 -0
  34. package/dist/cli/ui-automations.d.ts.map +1 -1
  35. package/dist/cli/ui-automations.js +1 -0
  36. package/dist/cli/ui-automations.js.map +1 -1
  37. package/dist/cli/ui-cmd.d.ts +4 -1
  38. package/dist/cli/ui-cmd.d.ts.map +1 -1
  39. package/dist/cli/ui-cmd.js +72 -17
  40. package/dist/cli/ui-cmd.js.map +1 -1
  41. package/dist/cli/ui-confined-project-read.d.ts +22 -0
  42. package/dist/cli/ui-confined-project-read.d.ts.map +1 -0
  43. package/dist/cli/ui-confined-project-read.js +38 -0
  44. package/dist/cli/ui-confined-project-read.js.map +1 -0
  45. package/dist/cli/ui-deploy-pipeline.d.ts.map +1 -1
  46. package/dist/cli/ui-deploy-pipeline.js +4 -15
  47. package/dist/cli/ui-deploy-pipeline.js.map +1 -1
  48. package/dist/cli/ui-detected-stacks.d.ts +9 -8
  49. package/dist/cli/ui-detected-stacks.d.ts.map +1 -1
  50. package/dist/cli/ui-detected-stacks.js +111 -0
  51. package/dist/cli/ui-detected-stacks.js.map +1 -1
  52. package/dist/cli/ui-github-repo-gh.d.ts +6 -0
  53. package/dist/cli/ui-github-repo-gh.d.ts.map +1 -1
  54. package/dist/cli/ui-github-repo-gh.js.map +1 -1
  55. package/dist/cli/ui-github-repo-map.d.ts.map +1 -1
  56. package/dist/cli/ui-github-repo-map.js +26 -1
  57. package/dist/cli/ui-github-repo-map.js.map +1 -1
  58. package/dist/cli/ui-setup-readiness-config.d.ts +28 -0
  59. package/dist/cli/ui-setup-readiness-config.d.ts.map +1 -0
  60. package/dist/cli/ui-setup-readiness-config.js +116 -0
  61. package/dist/cli/ui-setup-readiness-config.js.map +1 -0
  62. package/dist/cli/ui-setup-readiness-contract.d.ts +32 -0
  63. package/dist/cli/ui-setup-readiness-contract.d.ts.map +1 -0
  64. package/dist/cli/ui-setup-readiness-contract.js +61 -0
  65. package/dist/cli/ui-setup-readiness-contract.js.map +1 -0
  66. package/dist/cli/ui-setup-readiness-install.d.ts +24 -0
  67. package/dist/cli/ui-setup-readiness-install.d.ts.map +1 -0
  68. package/dist/cli/ui-setup-readiness-install.js +207 -0
  69. package/dist/cli/ui-setup-readiness-install.js.map +1 -0
  70. package/dist/cli/ui-setup-readiness-local.d.ts +40 -0
  71. package/dist/cli/ui-setup-readiness-local.d.ts.map +1 -0
  72. package/dist/cli/ui-setup-readiness-local.js +235 -0
  73. package/dist/cli/ui-setup-readiness-local.js.map +1 -0
  74. package/dist/cli/ui-setup-readiness-remote.d.ts +30 -0
  75. package/dist/cli/ui-setup-readiness-remote.d.ts.map +1 -0
  76. package/dist/cli/ui-setup-readiness-remote.js +108 -0
  77. package/dist/cli/ui-setup-readiness-remote.js.map +1 -0
  78. package/dist/cli/ui-setup-readiness.d.ts +52 -0
  79. package/dist/cli/ui-setup-readiness.d.ts.map +1 -0
  80. package/dist/cli/ui-setup-readiness.js +125 -0
  81. package/dist/cli/ui-setup-readiness.js.map +1 -0
  82. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  83. package/dist/core/upstream-evidence-manifest.js +24 -2
  84. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  85. package/dist/health/read-only-fs.d.ts +7 -0
  86. package/dist/health/read-only-fs.d.ts.map +1 -1
  87. package/dist/health/read-only-fs.js +40 -1
  88. package/dist/health/read-only-fs.js.map +1 -1
  89. package/dist/sync/registry.d.ts +14 -0
  90. package/dist/sync/registry.d.ts.map +1 -1
  91. package/dist/sync/registry.js +54 -15
  92. package/dist/sync/registry.js.map +1 -1
  93. package/package.json +1 -1
  94. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  95. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  96. package/plugins/lisa/scripts/automation-status-codex-adapter.mjs +270 -23
  97. package/plugins/lisa-agy/plugin.json +1 -1
  98. package/plugins/lisa-agy/scripts/automation-status-codex-adapter.mjs +270 -23
  99. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  100. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  101. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  102. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  105. package/plugins/lisa-copilot/scripts/automation-status-codex-adapter.mjs +270 -23
  106. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-cursor/scripts/automation-status-codex-adapter.mjs +270 -23
  108. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-expo-agy/plugin.json +1 -1
  111. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  116. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  121. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  124. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  125. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  126. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  128. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  129. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  130. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  131. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  133. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  134. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  135. package/plugins/lisa-rails-agy/plugin.json +1 -1
  136. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  138. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  139. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  140. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  141. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  142. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  143. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  144. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  145. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  146. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  147. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  148. package/plugins/src/base/scripts/automation-status-codex-adapter.mjs +270 -23
  149. package/ui/index.html +1 -1
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Release-path assessment for the delivery/authority readiness dimension — ship
3
+ * blocker B2 (PRD #1739, #1896).
4
+ *
5
+ * B2 asks whether the thing that ships equals the thing that was validated. The
6
+ * question is answered offline, from the workflow files alone, which imposes a
7
+ * hard discipline: **absence of evidence is not evidence of a bypass.** A
8
+ * reusable `workflow_call` publish workflow has no validating job of its own
9
+ * because validation is the caller's obligation; a `workflow_run`-triggered
10
+ * deploy is gated by the upstream workflow that fired it; a default-branch push
11
+ * may be gated by branch protection. None of those are visible here, so none of
12
+ * them may be reported as a violation.
13
+ *
14
+ * The rule is therefore: stand B2 only when the bypass is provable from the file
15
+ * alone — a job that self-builds AND publishes with no validating ancestor, in a
16
+ * workflow whose trigger makes in-file validation the expected place for it
17
+ * (a tag push, `workflow_dispatch`, `schedule`, a non-default-branch push, or an
18
+ * unstated trigger). Everything else that cannot be settled offline returns a
19
+ * stated-reason SKIP, never a FAIL.
20
+ * @module cli/doctor-readiness-release-path
21
+ */
22
+ import type { ParsedWorkflow, ParsedWorkflowJob, ParsedWorkflowStep } from "./doctor-readiness-workflows.js";
23
+ /** The action that promotes the artifact CI already built and validated. */
24
+ export declare const PROMOTION_ACTION = "actions/download-artifact";
25
+ /** What assessing one publishing job established. */
26
+ export type ReleasePathOutcome =
27
+ /** The bypass is provable from this file alone. */
28
+ {
29
+ readonly kind: "violation";
30
+ readonly evidence: string;
31
+ }
32
+ /** The proof, if any, lives somewhere not resolvable offline. */
33
+ | {
34
+ readonly kind: "unresolved";
35
+ readonly reason: string;
36
+ }
37
+ /** Validation provably precedes the publish, or the artifact is promoted. */
38
+ | {
39
+ readonly kind: "clean";
40
+ };
41
+ /**
42
+ * Describe a step for persisted evidence: prefer its `name`, else the first
43
+ * non-empty line of its `run`, truncated. A raw multi-line `run` would dump a
44
+ * shell script into `.lisa/readiness.json` and into an operator's face.
45
+ * @param step - The step to describe
46
+ * @returns A single-line label, at most {@link MAX_STEP_LABEL} characters
47
+ */
48
+ export declare function describeStep(step: ParsedWorkflowStep): string;
49
+ /**
50
+ * Find the first step in a job that ships something.
51
+ * @param job - The parsed job
52
+ * @returns The publishing step, or undefined when the job ships nothing
53
+ */
54
+ export declare function findPublishStep(job: ParsedWorkflowJob): ParsedWorkflowStep | undefined;
55
+ /**
56
+ * Assess one job's release path against B2.
57
+ * @param workflow - The workflow declaring the job
58
+ * @param job - The job to assess
59
+ * @returns What this job established, or undefined when it ships nothing
60
+ */
61
+ export declare function assessReleasePath(workflow: ParsedWorkflow, job: ParsedWorkflowJob): ReleasePathOutcome | undefined;
62
+ //# sourceMappingURL=doctor-readiness-release-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-readiness-release-path.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-readiness-release-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,iCAAiC,CAAC;AA+CzC,4EAA4E;AAC5E,eAAO,MAAM,gBAAgB,8BAA8B,CAAC;AAQ5D,qDAAqD;AACrD,MAAM,MAAM,kBAAkB;AAC5B,mDAAmD;AACjD;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAAE;AAC3D,iEAAiE;GAC/D;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAC1D,6EAA6E;GAC3E;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE/B;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAW7D;AAqBD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,iBAAiB,GACrB,kBAAkB,GAAG,SAAS,CAEhC;AAsJD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,cAAc,EACxB,GAAG,EAAE,iBAAiB,GACrB,kBAAkB,GAAG,SAAS,CAyBhC"}
@@ -0,0 +1,275 @@
1
+ /** Commands that put an artifact in front of users. */
2
+ const PUBLISH_VERBS = [
3
+ "npm publish",
4
+ "docker push",
5
+ "gh release upload",
6
+ "aws s3 sync",
7
+ "cdk deploy",
8
+ "eas submit",
9
+ ];
10
+ /**
11
+ * Commands that actually run a test suite. Deliberately narrow: the invariant B2
12
+ * protects is "this artifact was *tested*", and a loose substring match (`echo
13
+ * "test the release notes"`, or a lint step) would manufacture false
14
+ * reassurance — the wrong direction to err for a gate.
15
+ */
16
+ const VALIDATING_COMMANDS = [
17
+ /\b(npm|yarn|pnpm|bun|npx|bunx)\s+(run\s+)?test\b/,
18
+ /\btest:[\w:-]+/,
19
+ /\b(vitest|jest|pytest|rspec|phpunit|minitest)\b/,
20
+ /\b(go|cargo|mvn|gradle|make|dotnet)\s+test\b/,
21
+ ];
22
+ /**
23
+ * Whether a shell command runs a test suite.
24
+ * @param run - The step's `run` text
25
+ * @returns True when the command matches a known test invocation
26
+ */
27
+ function isValidatingCommand(run) {
28
+ return VALIDATING_COMMANDS.some(pattern => pattern.test(run));
29
+ }
30
+ /** Reusable workflows whose whole purpose is validation. */
31
+ const VALIDATING_WORKFLOW = /(quality|quality-rails|test|ci)\.ya?ml/;
32
+ /** Commands that produce an artifact locally, inside the shipping job. */
33
+ const LOCAL_BUILD_COMMAND = /\b(npm pack|npm run build|yarn build|bun run build|bun run build:\w+|pnpm build|docker build|tsc)\b/;
34
+ /** A publish argument naming a local path rather than a registry coordinate. */
35
+ const LOCAL_PATH_ARGUMENT = /\s\.{0,2}\//;
36
+ /** A publish argument naming a packaged artifact file on disk. */
37
+ const LOCAL_ARTIFACT_FILE = /\.(tgz|tar\.gz|zip|whl)\b/;
38
+ /** The action that promotes the artifact CI already built and validated. */
39
+ export const PROMOTION_ACTION = "actions/download-artifact";
40
+ /** Branch names treated as the repository's default branch. */
41
+ const DEFAULT_BRANCHES = new Set(["main", "master"]);
42
+ /** Longest step description carried into persisted evidence. */
43
+ const MAX_STEP_LABEL = 80;
44
+ /**
45
+ * Describe a step for persisted evidence: prefer its `name`, else the first
46
+ * non-empty line of its `run`, truncated. A raw multi-line `run` would dump a
47
+ * shell script into `.lisa/readiness.json` and into an operator's face.
48
+ * @param step - The step to describe
49
+ * @returns A single-line label, at most {@link MAX_STEP_LABEL} characters
50
+ */
51
+ export function describeStep(step) {
52
+ const source = step.name.trim() !== ""
53
+ ? step.name.trim()
54
+ : (step.run
55
+ .split("\n")
56
+ .find(line => line.trim() !== "")
57
+ ?.trim() ?? "");
58
+ return source.length > MAX_STEP_LABEL
59
+ ? `${source.slice(0, MAX_STEP_LABEL).trimEnd()}…`
60
+ : source;
61
+ }
62
+ /**
63
+ * A rehearsal flag: the command goes through the motions and ships nothing.
64
+ */
65
+ const DRY_RUN_FLAG = /--dry[-_]?run\b/;
66
+ /**
67
+ * Whether a step actually puts an artifact in front of users. A `--dry-run`
68
+ * invocation names a publish verb but ships nothing, so faulting its release
69
+ * path would be a finding about something that cannot reach anyone.
70
+ * @param step - The step to classify
71
+ * @returns True when the step really publishes
72
+ */
73
+ function isPublishStep(step) {
74
+ return (PUBLISH_VERBS.some(verb => step.run.includes(verb)) &&
75
+ !DRY_RUN_FLAG.test(step.run));
76
+ }
77
+ /**
78
+ * Find the first step in a job that ships something.
79
+ * @param job - The parsed job
80
+ * @returns The publishing step, or undefined when the job ships nothing
81
+ */
82
+ export function findPublishStep(job) {
83
+ return job.steps.find(isPublishStep);
84
+ }
85
+ /**
86
+ * Whether a job proves anything about the artifact.
87
+ * @param job - The parsed job
88
+ * @returns True when the job runs a test suite or calls a validating workflow
89
+ */
90
+ function isValidatingJob(job) {
91
+ if (job.uses !== "" && VALIDATING_WORKFLOW.test(job.uses)) {
92
+ return true;
93
+ }
94
+ return job.steps.some(step => isValidatingCommand(step.run) ||
95
+ (step.uses !== "" && VALIDATING_WORKFLOW.test(step.uses)));
96
+ }
97
+ /**
98
+ * Walk a job's transitive `needs:` closure within its workflow. The `seen` list
99
+ * makes a `needs:` cycle terminate instead of recursing forever.
100
+ * @param workflow - The workflow declaring the job
101
+ * @param job - The job whose ancestors to resolve
102
+ * @returns Every job the given job transitively depends on
103
+ */
104
+ function ancestorJobs(workflow, job) {
105
+ const byId = new Map(workflow.jobs.map(entry => [entry.id, entry]));
106
+ const walk = (ids, seen) => {
107
+ const fresh = ids.filter(id => !seen.includes(id));
108
+ if (fresh.length === 0) {
109
+ return [];
110
+ }
111
+ const jobs = fresh.flatMap(id => {
112
+ const ancestor = byId.get(id);
113
+ return ancestor ? [ancestor] : [];
114
+ });
115
+ return [
116
+ ...jobs,
117
+ ...walk(jobs.flatMap(ancestor => ancestor.needs), [...seen, ...fresh]),
118
+ ];
119
+ };
120
+ return walk(job.needs, []);
121
+ }
122
+ /**
123
+ * Whether validation provably precedes the publish: either an ancestor job
124
+ * validated, or a step earlier in this same job did.
125
+ * @param workflow - The workflow declaring the job
126
+ * @param job - The publishing job
127
+ * @param publishStep - The step that ships
128
+ * @returns True when something was proved before the artifact left
129
+ */
130
+ function validationPrecedesPublish(workflow, job, publishStep) {
131
+ if (ancestorJobs(workflow, job).some(isValidatingJob)) {
132
+ return true;
133
+ }
134
+ return job.steps
135
+ .slice(0, job.steps.indexOf(publishStep))
136
+ .some(step => isValidatingCommand(step.run));
137
+ }
138
+ /**
139
+ * Whether the job ships an artifact it produced itself rather than promoting the
140
+ * one CI built and validated. Downloading the CI artifact only exempts the job
141
+ * when nothing rebuilds *after* the download — a rebuild past the promotion is
142
+ * the very bypass B2 exists to catch.
143
+ * @param job - The publishing job
144
+ * @param publishStep - The step that ships
145
+ * @returns True when a self-built artifact is shipped
146
+ */
147
+ function shipsSelfBuiltArtifact(job, publishStep) {
148
+ const downloadIndex = job.steps.findIndex(step => step.uses.includes(PROMOTION_ACTION));
149
+ if (downloadIndex >= 0) {
150
+ const rebuildsAfterDownload = job.steps
151
+ .slice(downloadIndex + 1)
152
+ .some(step => LOCAL_BUILD_COMMAND.test(step.run));
153
+ if (!rebuildsAfterDownload) {
154
+ return false;
155
+ }
156
+ }
157
+ return (LOCAL_PATH_ARGUMENT.test(publishStep.run) ||
158
+ LOCAL_ARTIFACT_FILE.test(publishStep.run) ||
159
+ job.steps.some(step => LOCAL_BUILD_COMMAND.test(step.run)));
160
+ }
161
+ /**
162
+ * Decide whether a workflow's trigger makes in-file validation the expected
163
+ * place for it — the only case where absent validation proves a bypass.
164
+ * @param workflow - The workflow to classify
165
+ * @returns The reason validation may live elsewhere, or null when it must be here
166
+ */
167
+ function offlineUnresolvableTrigger(workflow) {
168
+ const events = workflow.on.events;
169
+ if (events.length > 0 && events.every(event => event === "workflow_call")) {
170
+ return ("it is a reusable `workflow_call` workflow, so validation is the calling " +
171
+ "workflow's obligation and callers are not resolved offline");
172
+ }
173
+ if (events.includes("workflow_run")) {
174
+ return ("it is triggered by `workflow_run`, so the validating workflow that fired " +
175
+ "it runs upstream and is not resolved offline");
176
+ }
177
+ if (events.includes("release")) {
178
+ return ("it is triggered by a `release` event, so validation may have run on the " +
179
+ "commit or tag the release was cut from, which is not visible in this file");
180
+ }
181
+ // An unfiltered `on: [push]` is a SUPERSET of a default-branch push, so it
182
+ // cannot be treated more strictly than the spelling that names the branch.
183
+ const unfilteredPush = workflow.on.events.includes("push") &&
184
+ workflow.on.pushBranches.length === 0 &&
185
+ workflow.on.pushTags.length === 0;
186
+ const defaultBranchPush = workflow.on.pushBranches.some(branch => DEFAULT_BRANCHES.has(branch));
187
+ if (unfilteredPush || defaultBranchPush) {
188
+ return ("it is triggered by a push to the default branch, where branch protection " +
189
+ "may already require the validating checks — a rule that is not visible " +
190
+ "in this file");
191
+ }
192
+ return null;
193
+ }
194
+ /**
195
+ * Assess one job's release path against B2.
196
+ * @param workflow - The workflow declaring the job
197
+ * @param job - The job to assess
198
+ * @returns What this job established, or undefined when it ships nothing
199
+ */
200
+ export function assessReleasePath(workflow, job) {
201
+ const publishStep = findPublishStep(job);
202
+ if (!publishStep) {
203
+ return undefined;
204
+ }
205
+ const where = `${workflow.file} job \`${job.id}\` step \`${describeStep(publishStep)}\``;
206
+ const validated = validationPrecedesPublish(workflow, job, publishStep);
207
+ if (shipsSelfBuiltArtifact(job, publishStep)) {
208
+ return validated
209
+ ? rebuildPastValidation(where)
210
+ : unvalidatedSelfBuild(where, workflow);
211
+ }
212
+ if (validated || promotesValidatedArtifact(job)) {
213
+ return { kind: "clean" };
214
+ }
215
+ // Neither built here nor promoted from CI, and nothing validating precedes it:
216
+ // the link between what ships and what was validated is simply not observable
217
+ // in this file. That is unestablished, not clean.
218
+ return {
219
+ kind: "unresolved",
220
+ reason: `${where} neither builds its own artifact nor promotes a CI-built one via ` +
221
+ `\`${PROMOTION_ACTION}\`, and no validating job precedes it, so what it ` +
222
+ "ships cannot be tied to anything that was validated",
223
+ };
224
+ }
225
+ /**
226
+ * The provable bypass that survives every trigger: the pipeline validated the
227
+ * source, then the release job rebuilt and shipped its own artifact. Whatever
228
+ * ran upstream, the bytes that reached users are not the bytes that were
229
+ * checked — so no trigger exempts this.
230
+ * @param where - Evidence location label
231
+ * @returns The violation outcome
232
+ */
233
+ function rebuildPastValidation(where) {
234
+ return {
235
+ kind: "violation",
236
+ evidence: `${where} rebuilds and ships its own artifact after validation ran — the ` +
237
+ "pipeline proved things about the source tree, then shipped bytes that " +
238
+ `were produced afterwards and never validated. Promote the CI-built ` +
239
+ `artifact via \`${PROMOTION_ACTION}\` instead of repacking at release time`,
240
+ };
241
+ }
242
+ /**
243
+ * A self-built, self-shipped artifact with nothing validating in front of it:
244
+ * a violation when the trigger makes in-file validation the expected place, and
245
+ * a stated-reason SKIP when the proof could live somewhere not visible offline.
246
+ * @param where - Evidence location label
247
+ * @param workflow - The workflow declaring the job
248
+ * @returns The violation or unresolved outcome
249
+ */
250
+ function unvalidatedSelfBuild(where, workflow) {
251
+ const unresolvable = offlineUnresolvableTrigger(workflow);
252
+ if (unresolvable !== null) {
253
+ return {
254
+ kind: "unresolved",
255
+ reason: `${where} builds and ships its own artifact with no validating job in ` +
256
+ `its \`needs:\` closure, but ${unresolvable}. Whether what ships equals ` +
257
+ "what was validated is therefore not established either way",
258
+ };
259
+ }
260
+ return {
261
+ kind: "violation",
262
+ evidence: `${where} builds its own artifact and ships it with no validating job in ` +
263
+ `its \`needs:\` closure and no \`${PROMOTION_ACTION}\` promotion of the ` +
264
+ "CI-built one — nothing was proved about the bytes that reached users",
265
+ };
266
+ }
267
+ /**
268
+ * Whether the job promotes the artifact CI already built and validated.
269
+ * @param job - The publishing job
270
+ * @returns True when a download-artifact step is present
271
+ */
272
+ function promotesValidatedArtifact(job) {
273
+ return job.steps.some(step => step.uses.includes(PROMOTION_ACTION));
274
+ }
275
+ //# sourceMappingURL=doctor-readiness-release-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-readiness-release-path.js","sourceRoot":"","sources":["../../src/cli/doctor-readiness-release-path.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The shared readiness record vocabulary (PRD #1739, #1896).
3
+ *
4
+ * Every readiness producer — the journey wiring, the delivery/authority
5
+ * producers, and the collector's own reasoned-SKIP fallback — returns the same
6
+ * per-dimension record. Defining that shape once here is what keeps the
7
+ * collector able to dispatch to any producer by dimension id: a second, drifting
8
+ * copy of this type would make one producer's record silently incompatible with
9
+ * the report the collector persists.
10
+ * @module cli/doctor-readiness-types
11
+ */
12
+ /** Per-dimension status, mirroring the shared doctor `DOCTOR_STATUSES`. */
13
+ export type ReadinessStatus = "PASS" | "WARN" | "FAIL" | "SKIP";
14
+ /** A persisted per-dimension record inside `.lisa/readiness.json`. */
15
+ export interface ReadinessDimensionRecord {
16
+ readonly id: string;
17
+ readonly status: ReadinessStatus;
18
+ /**
19
+ * Findings are intentionally untyped: they are persisted as JSON and read
20
+ * back by the blocker engine, which coerces them itself. Producers may attach
21
+ * extra rubric fields without a schema bump.
22
+ */
23
+ readonly findings: readonly unknown[];
24
+ }
25
+ //# sourceMappingURL=doctor-readiness-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-readiness-types.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-readiness-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,2EAA2E;AAC3E,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEhE,sEAAsE;AACtE,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACvC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The shared readiness record vocabulary (PRD #1739, #1896).
3
+ *
4
+ * Every readiness producer — the journey wiring, the delivery/authority
5
+ * producers, and the collector's own reasoned-SKIP fallback — returns the same
6
+ * per-dimension record. Defining that shape once here is what keeps the
7
+ * collector able to dispatch to any producer by dimension id: a second, drifting
8
+ * copy of this type would make one producer's record silently incompatible with
9
+ * the report the collector persists.
10
+ * @module cli/doctor-readiness-types
11
+ */
12
+ export {};
13
+ //# sourceMappingURL=doctor-readiness-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-readiness-types.js","sourceRoot":"","sources":["../../src/cli/doctor-readiness-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG"}
@@ -0,0 +1,64 @@
1
+ /**
2
+ * A `permissions`/`secrets` declaration: GitHub allows either a map of scopes
3
+ * or a bare scalar (`write-all`, `read-all`, `inherit`); `null` means the block
4
+ * was not declared at all, which is itself load-bearing evidence.
5
+ */
6
+ export type WorkflowBlock = Record<string, unknown> | string | null;
7
+ /**
8
+ * A workflow's `on:` trigger block, normalized across its three legal spellings
9
+ * (scalar, list, map). The trigger is load-bearing for readiness: it decides
10
+ * whether the *absence* of an in-file validating job proves a bypass or merely
11
+ * means the proof lives somewhere this parser cannot see.
12
+ */
13
+ export interface ParsedWorkflowTrigger {
14
+ /** Declared event names, e.g. `push`, `workflow_call`, `workflow_run`. */
15
+ readonly events: readonly string[];
16
+ /** Branch filters on a `push` trigger (empty when unfiltered or absent). */
17
+ readonly pushBranches: readonly string[];
18
+ /** Tag filters on a `push` trigger (empty when unfiltered or absent). */
19
+ readonly pushTags: readonly string[];
20
+ }
21
+ /** One step inside a parsed workflow job. */
22
+ export interface ParsedWorkflowStep {
23
+ readonly name: string;
24
+ /** The shell command the step runs, or `""` for an action step. */
25
+ readonly run: string;
26
+ /** The action the step uses, or `""` for a `run` step. */
27
+ readonly uses: string;
28
+ /** Raw serialized `env` + `with` text, used for credential evidence. */
29
+ readonly inputs: string;
30
+ }
31
+ /** One job inside a parsed workflow. */
32
+ export interface ParsedWorkflowJob {
33
+ readonly id: string;
34
+ /** Repo-relative path of the workflow file declaring this job. */
35
+ readonly workflow: string;
36
+ readonly needs: readonly string[];
37
+ readonly steps: readonly ParsedWorkflowStep[];
38
+ /** The reusable workflow this job calls, or `""` when it runs steps. */
39
+ readonly uses: string;
40
+ /** Job-level `permissions`: a map, a scalar such as `write-all`, or null. */
41
+ readonly permissions: WorkflowBlock;
42
+ /** Job-level `secrets`: a map, the scalar `inherit`, or null. */
43
+ readonly secrets: WorkflowBlock;
44
+ /** Declared deployment environments (a scalar or list, normalized). */
45
+ readonly environment: readonly string[];
46
+ }
47
+ /** One parsed `.github/workflows/*.yml` file. */
48
+ export interface ParsedWorkflow {
49
+ /** Repo-relative path, always with forward slashes. */
50
+ readonly file: string;
51
+ readonly name: string;
52
+ /** Workflow-level `permissions`: a map, a scalar, or null. */
53
+ readonly permissions: WorkflowBlock;
54
+ /** The normalized `on:` trigger block. */
55
+ readonly on: ParsedWorkflowTrigger;
56
+ readonly jobs: readonly ParsedWorkflowJob[];
57
+ }
58
+ /**
59
+ * Read and parse every `.github/workflows/*.yml` file in a repository, offline.
60
+ * @param root - Repository root
61
+ * @returns Parsed workflows in stable file-name order (empty when there are none)
62
+ */
63
+ export declare function parseRepositoryWorkflows(root: string): Promise<readonly ParsedWorkflow[]>;
64
+ //# sourceMappingURL=doctor-readiness-workflows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor-readiness-workflows.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-readiness-workflows.ts"],"names":[],"mappings":"AAwBA;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IACpC,0EAA0E;IAC1E,QAAQ,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,6CAA6C;AAC7C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wEAAwE;IACxE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,wCAAwC;AACxC,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,kEAAkE;IAClE,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC9C,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,6EAA6E;IAC7E,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC;IACpC,iEAAiE;IACjE,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,uEAAuE;IACvE,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC;IACpC,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,EAAE,qBAAqB,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAC7C;AA0LD;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAQpC"}
@@ -0,0 +1,198 @@
1
+ /**
2
+ * Offline GitHub Actions workflow parser for the readiness producers
3
+ * (PRD #1739, #1896).
4
+ *
5
+ * The delivery/authority readiness questions — does the thing that ships equal
6
+ * the thing that was validated, and does the shipping credential carry only the
7
+ * authority it needs — are answered from what the repository's CI *declares*,
8
+ * not from what a live API reports. Parsing is therefore deliberately offline by
9
+ * construction: nothing in this module reaches the network, so `lisa doctor
10
+ * --offline --readiness` needs no flag threading to stay honest.
11
+ *
12
+ * Reading is best-effort by design. A workflow file that cannot be parsed is
13
+ * skipped rather than aborting the readiness run, because one malformed YAML
14
+ * file must not turn the whole report into an error the operator cannot act on.
15
+ * @module cli/doctor-readiness-workflows
16
+ */
17
+ import { readdir, readFile } from "node:fs/promises";
18
+ import * as path from "node:path";
19
+ import yaml from "js-yaml";
20
+ import { isJsonObject } from "../sync/json-path.js";
21
+ /** The workflows directory every GitHub repository declares its CI in. */
22
+ const WORKFLOWS_DIR = path.join(".github", "workflows");
23
+ /**
24
+ * Normalize a YAML scalar-or-list into a list of trimmed strings.
25
+ * @param value - Candidate YAML value
26
+ * @returns Normalized string list (empty when unreadable)
27
+ */
28
+ function asStringList(value) {
29
+ if (typeof value === "string") {
30
+ return value.trim() === "" ? [] : [value.trim()];
31
+ }
32
+ if (Array.isArray(value)) {
33
+ return value
34
+ .filter((entry) => typeof entry === "string")
35
+ .map(entry => entry.trim())
36
+ .filter(entry => entry !== "");
37
+ }
38
+ return [];
39
+ }
40
+ /**
41
+ * Normalize a `permissions`/`secrets` block, which GitHub allows as either a
42
+ * map or a bare scalar (`write-all`, `read-all`, `inherit`).
43
+ * @param value - Candidate YAML value
44
+ * @returns The map, the scalar string, or null when absent
45
+ */
46
+ function asBlock(value) {
47
+ if (typeof value === "string") {
48
+ return value.trim();
49
+ }
50
+ return isJsonObject(value) ? value : null;
51
+ }
52
+ /**
53
+ * Normalize a job's `environment`, which may be a scalar name, a list, or a
54
+ * `{ name, url }` map.
55
+ * @param value - Candidate YAML value
56
+ * @returns Declared environment names
57
+ */
58
+ function asEnvironments(value) {
59
+ if (isJsonObject(value)) {
60
+ return typeof value.name === "string" ? [value.name.trim()] : [];
61
+ }
62
+ return asStringList(value);
63
+ }
64
+ /**
65
+ * Normalize the `on:` block, which GitHub accepts as a scalar (`on: push`), a
66
+ * list (`on: [push, workflow_dispatch]`), or a map with per-event filters.
67
+ * @param value - Candidate YAML `on` value
68
+ * @returns The normalized trigger (all fields empty when absent or unreadable)
69
+ */
70
+ function parseTrigger(value) {
71
+ if (!isJsonObject(value)) {
72
+ return { events: asStringList(value), pushBranches: [], pushTags: [] };
73
+ }
74
+ const push = value.push;
75
+ const filters = isJsonObject(push) ? push : undefined;
76
+ return {
77
+ events: Object.keys(value),
78
+ pushBranches: asStringList(filters?.branches),
79
+ pushTags: asStringList(filters?.tags),
80
+ };
81
+ }
82
+ /**
83
+ * Serialize a step's `env` and `with` blocks to searchable text so credential
84
+ * evidence can quote the offending line without a second traversal.
85
+ * @param step - The raw step object
86
+ * @returns Flattened `key: value` text, one pair per line
87
+ */
88
+ function serializeStepInputs(step) {
89
+ return ["env", "with"]
90
+ .flatMap(key => {
91
+ const block = step[key];
92
+ return isJsonObject(block) ? Object.entries(block) : [];
93
+ })
94
+ .map(([name, value]) => `${name}: ${typeof value === "string" ? value : String(value)}`)
95
+ .join("\n");
96
+ }
97
+ /**
98
+ * Parse one job's `steps` list.
99
+ * @param value - Candidate YAML `steps` value
100
+ * @returns Normalized steps
101
+ */
102
+ function parseSteps(value) {
103
+ if (!Array.isArray(value)) {
104
+ return [];
105
+ }
106
+ return value.filter(isJsonObject).map(step => ({
107
+ name: typeof step.name === "string" ? step.name : "",
108
+ run: typeof step.run === "string" ? step.run.trim() : "",
109
+ uses: typeof step.uses === "string" ? step.uses.trim() : "",
110
+ inputs: serializeStepInputs(step),
111
+ }));
112
+ }
113
+ /**
114
+ * Parse the `jobs` map of one workflow document.
115
+ * @param document - Parsed workflow root
116
+ * @param file - Repo-relative workflow path (stamped on each job)
117
+ * @returns Normalized jobs, in declaration order
118
+ */
119
+ function parseJobs(document, file) {
120
+ const jobs = document.jobs;
121
+ if (!isJsonObject(jobs)) {
122
+ return [];
123
+ }
124
+ return Object.entries(jobs).flatMap(([id, raw]) => {
125
+ if (!isJsonObject(raw)) {
126
+ return [];
127
+ }
128
+ const job = raw;
129
+ return [
130
+ {
131
+ id,
132
+ workflow: file,
133
+ needs: asStringList(job.needs),
134
+ steps: parseSteps(job.steps),
135
+ uses: typeof job.uses === "string" ? job.uses.trim() : "",
136
+ permissions: asBlock(job.permissions),
137
+ secrets: asBlock(job.secrets),
138
+ environment: asEnvironments(job.environment),
139
+ },
140
+ ];
141
+ });
142
+ }
143
+ /**
144
+ * List the workflow files a repository declares, in stable name order.
145
+ * @param root - Repository root
146
+ * @returns Workflow file names, or an empty list when the directory is absent
147
+ */
148
+ async function listWorkflowFiles(root) {
149
+ try {
150
+ const entries = await readdir(path.join(root, WORKFLOWS_DIR));
151
+ return entries
152
+ .filter(entry => entry.endsWith(".yml") || entry.endsWith(".yaml"))
153
+ .sort((a, b) => a.localeCompare(b));
154
+ }
155
+ catch {
156
+ return [];
157
+ }
158
+ }
159
+ /**
160
+ * Parse one workflow file, returning null when it cannot be read or parsed.
161
+ * @param root - Repository root
162
+ * @param fileName - Workflow file name
163
+ * @returns The parsed workflow, or null
164
+ */
165
+ async function parseOneWorkflow(root, fileName) {
166
+ const file = `${WORKFLOWS_DIR.split(path.sep).join("/")}/${fileName}`;
167
+ try {
168
+ const source = await readFile(path.join(root, WORKFLOWS_DIR, fileName), "utf8");
169
+ const document = yaml.load(source);
170
+ if (!isJsonObject(document)) {
171
+ return null;
172
+ }
173
+ const record = document;
174
+ return {
175
+ file,
176
+ name: typeof record.name === "string" ? record.name : fileName,
177
+ permissions: asBlock(record.permissions),
178
+ on: parseTrigger(record.on),
179
+ jobs: parseJobs(record, file),
180
+ };
181
+ }
182
+ catch {
183
+ // A malformed workflow is skipped, never fatal: one bad file must not turn
184
+ // the readiness report into an error the operator cannot act on.
185
+ return null;
186
+ }
187
+ }
188
+ /**
189
+ * Read and parse every `.github/workflows/*.yml` file in a repository, offline.
190
+ * @param root - Repository root
191
+ * @returns Parsed workflows in stable file-name order (empty when there are none)
192
+ */
193
+ export async function parseRepositoryWorkflows(root) {
194
+ const fileNames = await listWorkflowFiles(root);
195
+ const parsed = await Promise.all(fileNames.map(async (fileName) => await parseOneWorkflow(root, fileName)));
196
+ return parsed.filter((workflow) => workflow !== null);
197
+ }
198
+ //# sourceMappingURL=doctor-readiness-workflows.js.map