@codyswann/lisa 4.42.6 → 4.43.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 (186) hide show
  1. package/README.md +4 -0
  2. package/all/copy-overwrite/scripts/check-orphaned-branches.mjs +7 -0
  3. package/all/copy-overwrite/scripts/lib/process-tree-runner.mjs +12 -2
  4. package/all/copy-overwrite/scripts/lisa-cross-worktree-guard.mjs +4 -0
  5. package/all/copy-overwrite/scripts/lisa-environment-prepare.mjs +2 -0
  6. package/all/copy-overwrite/scripts/lisa-hooks/worktree-binding-guard.mjs +31 -3
  7. package/all/copy-overwrite/scripts/lisa-run-gates.mjs +4 -0
  8. package/all/copy-overwrite/scripts/lisa-work-item.mjs +13 -0
  9. package/dist/cli/doctor-readiness-delivery.d.ts.map +1 -1
  10. package/dist/cli/doctor-readiness-delivery.js +13 -6
  11. package/dist/cli/doctor-readiness-delivery.js.map +1 -1
  12. package/dist/cli/doctor-readiness-deploy-outcome.d.ts +9 -0
  13. package/dist/cli/doctor-readiness-deploy-outcome.d.ts.map +1 -0
  14. package/dist/cli/doctor-readiness-deploy-outcome.js +87 -0
  15. package/dist/cli/doctor-readiness-deploy-outcome.js.map +1 -0
  16. package/dist/cli/doctor-readiness-workflows.d.ts +8 -0
  17. package/dist/cli/doctor-readiness-workflows.d.ts.map +1 -1
  18. package/dist/cli/doctor-readiness-workflows.js +1 -0
  19. package/dist/cli/doctor-readiness-workflows.js.map +1 -1
  20. package/dist/configs/vitest/base.d.ts +1 -1
  21. package/dist/configs/vitest/base.d.ts.map +1 -1
  22. package/dist/configs/vitest/base.js +1 -0
  23. package/dist/configs/vitest/base.js.map +1 -1
  24. package/dist/configs/vitest/cdk.d.ts +2 -2
  25. package/dist/configs/vitest/cdk.d.ts.map +1 -1
  26. package/dist/configs/vitest/cdk.js +5 -2
  27. package/dist/configs/vitest/cdk.js.map +1 -1
  28. package/dist/configs/vitest/coverage-reports-directory.d.ts +47 -0
  29. package/dist/configs/vitest/coverage-reports-directory.d.ts.map +1 -0
  30. package/dist/configs/vitest/coverage-reports-directory.js +131 -0
  31. package/dist/configs/vitest/coverage-reports-directory.js.map +1 -0
  32. package/dist/configs/vitest/harper-fabric.d.ts +2 -2
  33. package/dist/configs/vitest/harper-fabric.d.ts.map +1 -1
  34. package/dist/configs/vitest/harper-fabric.js +5 -2
  35. package/dist/configs/vitest/harper-fabric.js.map +1 -1
  36. package/dist/configs/vitest/nestjs.d.ts +2 -2
  37. package/dist/configs/vitest/nestjs.d.ts.map +1 -1
  38. package/dist/configs/vitest/nestjs.js +5 -2
  39. package/dist/configs/vitest/nestjs.js.map +1 -1
  40. package/dist/configs/vitest/phaser.d.ts +2 -2
  41. package/dist/configs/vitest/phaser.d.ts.map +1 -1
  42. package/dist/configs/vitest/phaser.js +5 -2
  43. package/dist/configs/vitest/phaser.js.map +1 -1
  44. package/dist/configs/vitest/typescript.d.ts +2 -2
  45. package/dist/configs/vitest/typescript.d.ts.map +1 -1
  46. package/dist/configs/vitest/typescript.js +5 -2
  47. package/dist/configs/vitest/typescript.js.map +1 -1
  48. package/dist/core/deploy-release-dependence.d.ts +72 -0
  49. package/dist/core/deploy-release-dependence.d.ts.map +1 -0
  50. package/dist/core/deploy-release-dependence.js +236 -0
  51. package/dist/core/deploy-release-dependence.js.map +1 -0
  52. package/dist/core/github-actions-condition.d.ts +78 -0
  53. package/dist/core/github-actions-condition.d.ts.map +1 -0
  54. package/dist/core/github-actions-condition.js +307 -0
  55. package/dist/core/github-actions-condition.js.map +1 -0
  56. package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
  57. package/dist/core/lisa-owned-hash-ledger.js +24 -0
  58. package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
  59. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  60. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  61. package/dist/core/upstream-evidence-manifest.js +45 -24
  62. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  63. package/dist/migrations/deploy-outcome-guard-edit.d.ts +11 -0
  64. package/dist/migrations/deploy-outcome-guard-edit.d.ts.map +1 -0
  65. package/dist/migrations/deploy-outcome-guard-edit.js +184 -0
  66. package/dist/migrations/deploy-outcome-guard-edit.js.map +1 -0
  67. package/dist/migrations/deploy-outcome-guard-yaml.d.ts +91 -0
  68. package/dist/migrations/deploy-outcome-guard-yaml.d.ts.map +1 -0
  69. package/dist/migrations/deploy-outcome-guard-yaml.js +180 -0
  70. package/dist/migrations/deploy-outcome-guard-yaml.js.map +1 -0
  71. package/dist/migrations/ensure-deploy-outcome-guard.d.ts +41 -0
  72. package/dist/migrations/ensure-deploy-outcome-guard.d.ts.map +1 -0
  73. package/dist/migrations/ensure-deploy-outcome-guard.js +238 -0
  74. package/dist/migrations/ensure-deploy-outcome-guard.js.map +1 -0
  75. package/dist/migrations/index.d.ts +1 -0
  76. package/dist/migrations/index.d.ts.map +1 -1
  77. package/dist/migrations/index.js +3 -0
  78. package/dist/migrations/index.js.map +1 -1
  79. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +26 -7
  80. package/package.json +5 -4
  81. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  82. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  83. package/plugins/lisa/.codex-plugin/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  84. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  85. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  86. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  87. package/plugins/lisa/.codex-plugin/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  88. package/plugins/lisa/hooks/threshold-ratchet.mjs +6 -0
  89. package/plugins/lisa/hooks/worktree-binding-guard.mjs +31 -3
  90. package/plugins/lisa/scripts/design-variable-ids.mjs +2 -0
  91. package/plugins/lisa/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  92. package/plugins/lisa/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  93. package/plugins/lisa/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  94. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  95. package/plugins/lisa/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  96. package/plugins/lisa-agy/plugin.json +1 -1
  97. package/plugins/lisa-agy/scripts/design-variable-ids.mjs +2 -0
  98. package/plugins/lisa-agy/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  99. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  100. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  101. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  102. package/plugins/lisa-agy/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  103. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  106. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-copilot/hooks/threshold-ratchet.mjs +6 -0
  110. package/plugins/lisa-copilot/hooks/worktree-binding-guard.mjs +31 -3
  111. package/plugins/lisa-copilot/scripts/design-variable-ids.mjs +2 -0
  112. package/plugins/lisa-copilot/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  113. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  114. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  115. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  116. package/plugins/lisa-copilot/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  117. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-cursor/hooks/threshold-ratchet.mjs +6 -0
  119. package/plugins/lisa-cursor/hooks/worktree-binding-guard.mjs +31 -3
  120. package/plugins/lisa-cursor/scripts/design-variable-ids.mjs +2 -0
  121. package/plugins/lisa-cursor/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  122. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  123. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  124. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  125. package/plugins/lisa-cursor/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  126. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  127. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  128. package/plugins/lisa-expo-agy/plugin.json +1 -1
  129. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  130. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  131. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  132. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  133. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  134. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  135. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  136. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  137. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  138. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  139. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  140. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  141. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  142. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  143. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  144. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  145. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  146. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  147. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  148. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  149. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  150. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  151. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  152. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  153. package/plugins/lisa-rails-agy/plugin.json +1 -1
  154. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  155. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  156. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  157. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  158. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  159. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  160. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  161. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  163. package/plugins/lisa-wiki/scripts/ingest-git.mjs +2 -0
  164. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  165. package/plugins/lisa-wiki-agy/scripts/ingest-git.mjs +2 -0
  166. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  167. package/plugins/lisa-wiki-copilot/scripts/ingest-git.mjs +2 -0
  168. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  169. package/plugins/lisa-wiki-cursor/scripts/ingest-git.mjs +2 -0
  170. package/plugins/src/base/hooks/threshold-ratchet.mjs +6 -0
  171. package/plugins/src/base/hooks/worktree-binding-guard.mjs +31 -3
  172. package/plugins/src/base/scripts/design-variable-ids.mjs +2 -0
  173. package/plugins/src/base/skills/lisa-drive-pr-to-merge/SKILL.md +11 -0
  174. package/plugins/src/base/skills/lisa-secrets-access/scripts/bootstrap-store.mjs +3 -0
  175. package/plugins/src/base/skills/lisa-secrets-access/scripts/preflight-secrets.mjs +3 -0
  176. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +4 -0
  177. package/plugins/src/base/skills/lisa-setup-workstation/scripts/workstation.mjs +3 -0
  178. package/plugins/src/wiki/scripts/ingest-git.mjs +2 -0
  179. package/rails/copy-overwrite/scripts/check-threshold-ratchet.mjs +6 -0
  180. package/scripts/check-merge-driver-registration.mjs +2 -0
  181. package/scripts/check-probe-absence-direction.mjs +611 -0
  182. package/scripts/check-test-case-executed.mjs +184 -0
  183. package/scripts/install-generated-artifact-merge-driver.mjs +7 -1
  184. package/scripts/lib/shell-guard-refusal-coverage.mjs +18 -19
  185. package/typescript/copy-overwrite/scripts/check-threshold-ratchet.mjs +6 -0
  186. package/typescript/copy-overwrite/scripts/lisa-mutation.mjs +4 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Repair an already-seeded `deploy.yml` whose deploy job goes SILENT when its
3
+ * release fails (CodySwannGT/lisa#3740).
4
+ *
5
+ * ## Why a migration and not a template fix
6
+ *
7
+ * #3467 found a deploy job that was SKIPPED rather than failed when its release
8
+ * failed. GitHub renders a skipped job as neutral and counts a skipped required
9
+ * check as satisfied, so the deploy that never happened left no signal
10
+ * anywhere: release commit present, versions matching, every status green. A
11
+ * consuming repository ran eight days that way.
12
+ *
13
+ * #3738 fixed the templates. Those templates are `create-only` — Lisa seeds
14
+ * `deploy.yml` once and never overwrites it, because the host owns that file —
15
+ * so the fix reaches new adoptions and reaches nothing that already exists,
16
+ * which is precisely where the defect is live. `copy-overwrite` would clobber
17
+ * host customizations, and no reusable workflow can force a caller's job to
18
+ * run. A migration is the surface that reaches the installed base, exactly as
19
+ * `ensure-nightly-e2e-workflow-pins` does for the same `create-only` wall
20
+ * (#3476, #3485).
21
+ *
22
+ * ## What it will and will not touch
23
+ *
24
+ * It repairs a deploy job only when it can do BOTH halves: suppress the
25
+ * implicit `success()` so the job runs on a failed release, and insert the
26
+ * guard step that then fails it loudly. Half of that repair would be worse than
27
+ * the defect — a deploy job that runs on a failed release with nothing checking
28
+ * the release result would try to ship.
29
+ *
30
+ * It declines, leaving the doctor finding to report the file, when:
31
+ *
32
+ * - the run is `postinstallSafe`. Nobody types `bun install` meaning to rewrite
33
+ * a reviewed, checked-in workflow, and the interface says so outright;
34
+ * - the condition needs a rewrite this cannot make without guessing;
35
+ * - the file's shape cannot be located unambiguously;
36
+ * - the edited file does not parse, or does not come back repaired.
37
+ *
38
+ * The guard body is lifted from Lisa's own shipped template rather than
39
+ * reproduced here, so the copy a test keeps identical across the templates is
40
+ * the copy a host receives.
41
+ * @module migrations/ensure-deploy-outcome-guard
42
+ */
43
+ import { readFile, writeFile } from "node:fs/promises";
44
+ import * as path from "node:path";
45
+ import { deployJobsSkippedByFailedRelease, releaseDependence, } from "../core/deploy-release-dependence.js";
46
+ import { parseRepositoryWorkflows, } from "../cli/doctor-readiness-workflows.js";
47
+ import { isJsonObject } from "../sync/json-path.js";
48
+ import { loadYaml } from "../utils/yaml.js";
49
+ import { GUARD_STEP_NAME, rewrittenCondition, } from "./deploy-outcome-guard-edit.js";
50
+ import { applyGuardEdits, extractGuardStep, } from "./deploy-outcome-guard-yaml.js";
51
+ /**
52
+ * The shipped template the guard step is read out of.
53
+ *
54
+ * The rails one specifically: its guard names the environment as
55
+ * `github.ref_name`, which resolves in any repository, while the expo copy
56
+ * reads an output only that workflow's job graph publishes. The `run:` body is
57
+ * identical in both — a test asserts it — so this choice is about the `env:`
58
+ * block travelling safely, not about the words an operator reads.
59
+ */
60
+ const GUARD_TEMPLATE = path.join("rails", "create-only", ".github", "workflows", "deploy.yml");
61
+ /** The release job id the shipped template's guard step refers to. */
62
+ const TEMPLATE_RELEASE_JOB = "release";
63
+ /**
64
+ * Re-read one job out of an edited workflow source.
65
+ * @param source - The edited workflow source
66
+ * @param jobId - The job to read back
67
+ * @returns The job's condition and first step name, or null when unreadable
68
+ */
69
+ function readBackJob(source, jobId) {
70
+ try {
71
+ const document = loadYaml(source);
72
+ if (!isJsonObject(document) || !isJsonObject(document.jobs))
73
+ return null;
74
+ const job = document.jobs[jobId];
75
+ if (!isJsonObject(job))
76
+ return null;
77
+ const steps = Array.isArray(job.steps) ? job.steps : [];
78
+ const first = steps[0];
79
+ return {
80
+ ifCondition: typeof job.if === "string" ? job.if : "",
81
+ firstStep: isJsonObject(first) && typeof first.name === "string" ? first.name : "",
82
+ };
83
+ }
84
+ catch {
85
+ return null;
86
+ }
87
+ }
88
+ /**
89
+ * Whether an edited source really repaired the job it claims to have repaired.
90
+ *
91
+ * The post-condition, not a formality: a mis-indented insertion produces a file
92
+ * that still parses and still skips, and writing that would replace a reported
93
+ * defect with an unreported one.
94
+ * @param source - The edited workflow source
95
+ * @param job - The job as originally parsed
96
+ * @param release - The upstream release job's id
97
+ * @returns True when the job now survives a failed release and carries the guard
98
+ */
99
+ function repairHolds(source, job, release) {
100
+ const readBack = readBackJob(source, job.id);
101
+ if (readBack === null)
102
+ return false;
103
+ if (readBack.firstStep !== GUARD_STEP_NAME)
104
+ return false;
105
+ return (releaseDependence({ ...job, ifCondition: readBack.ifCondition }, release)
106
+ .kind === "independent");
107
+ }
108
+ /**
109
+ * Turn one parsed job into the shape the dependence analysis reads.
110
+ * @param job - The parsed workflow job
111
+ * @returns The analysis input
112
+ */
113
+ function asDependenceJob(job) {
114
+ return {
115
+ id: job.id,
116
+ name: job.name,
117
+ needs: job.needs,
118
+ ifCondition: job.ifCondition,
119
+ environment: job.environment,
120
+ };
121
+ }
122
+ /**
123
+ * Repair every repairable deploy job in one workflow's source.
124
+ * @param source - The host workflow's source
125
+ * @param workflow - The parsed workflow
126
+ * @param guardStep - The guard step's lines, de-indented to column zero
127
+ * @returns The repaired source and the jobs repaired, or null when none were
128
+ */
129
+ function repairWorkflow(source, workflow, guardStep) {
130
+ const found = deployJobsSkippedByFailedRelease(workflow.jobs.map(asDependenceJob));
131
+ const repaired = found.reduce((carried, entry) => {
132
+ const condition = rewrittenCondition(entry.job, entry.release);
133
+ if (condition === null)
134
+ return carried;
135
+ const guard = guardStep.map(line => line.replaceAll(`needs.${TEMPLATE_RELEASE_JOB}.result`, `needs.${entry.release}.result`));
136
+ const edited = applyGuardEdits(carried.source, entry.job.id, condition, guard);
137
+ if (edited === null || !repairHolds(edited, entry.job, entry.release)) {
138
+ return carried;
139
+ }
140
+ return { source: edited, jobs: [...carried.jobs, entry.job.id] };
141
+ }, { source, jobs: [] });
142
+ return repaired.jobs.length === 0 ? null : repaired;
143
+ }
144
+ /**
145
+ * Bring an already-seeded deploy workflow up to the #3467 outcome contract.
146
+ */
147
+ export class EnsureDeployOutcomeGuardMigration {
148
+ name = "ensure-deploy-outcome-guard";
149
+ description = "Make an already-seeded deploy job fail loudly instead of skipping silently when its release does not succeed";
150
+ /**
151
+ * Whether any host deploy workflow can be repaired.
152
+ * @param ctx - Migration context
153
+ * @returns True when at least one workflow would change
154
+ */
155
+ async applies(ctx) {
156
+ return (await this.plan(ctx)).length > 0;
157
+ }
158
+ /**
159
+ * Rewrite every repairable deploy workflow.
160
+ * @param ctx - Migration context
161
+ * @returns Applied or no-op result
162
+ */
163
+ async apply(ctx) {
164
+ const edits = await this.plan(ctx);
165
+ if (edits.length === 0) {
166
+ return { name: this.name, action: "noop" };
167
+ }
168
+ const changedFiles = edits.map(edit => edit.relative);
169
+ const message = `Made ${edits.flatMap(edit => edit.jobs).length} deploy job(s) fail ` +
170
+ "loudly instead of skipping silently when the release does not succeed";
171
+ if (ctx.dryRun) {
172
+ ctx.logger.dry(`Would update ${changedFiles.join(", ")}`);
173
+ return { name: this.name, action: "applied", changedFiles, message };
174
+ }
175
+ for (const edit of edits) {
176
+ await writeFile(edit.absolute, edit.source);
177
+ }
178
+ ctx.logger.success(message);
179
+ return { name: this.name, action: "applied", changedFiles, message };
180
+ }
181
+ /**
182
+ * Work out which host workflows this run would rewrite.
183
+ * @param ctx - Migration context
184
+ * @returns One entry per workflow that can be repaired
185
+ */
186
+ async plan(ctx) {
187
+ // A dependency install is not somebody asking for a reviewed workflow to be
188
+ // rewritten. The doctor finding reports these instead.
189
+ if (ctx.postinstallSafe === true)
190
+ return [];
191
+ const guardStep = await this.readGuardStep(ctx.lisaDir);
192
+ if (guardStep === null)
193
+ return [];
194
+ const workflows = await parseRepositoryWorkflows(ctx.projectDir);
195
+ const planned = await Promise.all(workflows.map(async (workflow) => await this.planOne(ctx, workflow, guardStep)));
196
+ return planned.filter((edit) => edit !== null);
197
+ }
198
+ /**
199
+ * Plan the repair of one workflow.
200
+ * @param ctx - Migration context
201
+ * @param workflow - The parsed workflow
202
+ * @param guardStep - The guard step's lines
203
+ * @returns The planned edit, or null when nothing here can be repaired
204
+ */
205
+ async planOne(ctx, workflow, guardStep) {
206
+ const absolute = path.join(ctx.projectDir, ...workflow.file.split("/"));
207
+ try {
208
+ const source = await readFile(absolute, "utf8");
209
+ const repaired = repairWorkflow(source, workflow, guardStep);
210
+ return repaired === null
211
+ ? null
212
+ : {
213
+ relative: workflow.file,
214
+ absolute,
215
+ source: repaired.source,
216
+ jobs: repaired.jobs,
217
+ };
218
+ }
219
+ catch {
220
+ return null;
221
+ }
222
+ }
223
+ /**
224
+ * Read the guard step out of Lisa's own shipped deploy template.
225
+ * @param lisaDir - Lisa installation directory
226
+ * @returns The step's lines, or null when the template cannot be read
227
+ */
228
+ async readGuardStep(lisaDir) {
229
+ try {
230
+ const template = await readFile(path.join(lisaDir, GUARD_TEMPLATE), "utf8");
231
+ return extractGuardStep(template, GUARD_STEP_NAME);
232
+ }
233
+ catch {
234
+ return null;
235
+ }
236
+ }
237
+ }
238
+ //# sourceMappingURL=ensure-deploy-outcome-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensure-deploy-outcome-guard.js","sourceRoot":"","sources":["../../src/migrations/ensure-deploy-outcome-guard.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,7 @@
1
1
  import type { Migration, MigrationContext, MigrationResult } from "./migration.interface.js";
2
2
  export type { Migration, MigrationAction, MigrationContext, MigrationResult, } from "./migration.interface.js";
3
3
  export { EnsureAuditIgnoreLocalExclusionsMigration } from "./ensure-audit-ignore-local-exclusions.js";
4
+ export { EnsureDeployOutcomeGuardMigration } from "./ensure-deploy-outcome-guard.js";
4
5
  export { EnsureJestRnMockAccessibilityManagerMigration } from "./ensure-jest-rn-mock-accessibility-manager.js";
5
6
  export { EnsureLearningsGitattributesMigration } from "./ensure-learnings-gitattributes.js";
6
7
  export { EnsureLearningsMergeDriverMigration } from "./ensure-learnings-merge-driver.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,6CAA6C,EAAE,MAAM,gDAAgD,CAAC;AAC/G,OAAO,EAAE,qCAAqC,EAAE,MAAM,qCAAqC,CAAC;AAC5F,OAAO,EAAE,mCAAmC,EAAE,MAAM,oCAAoC,CAAC;AACzF,OAAO,EAAE,uCAAuC,EAAE,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,qCAAqC,EAAE,MAAM,uCAAuC,CAAC;AAC9F,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,wCAAwC,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,mCAAmC,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,yCAAyC,CAAC;AAClG,OAAO,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAC;AAC1F,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,iCAAiC,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,qCAAqC,CAAC;AAC3F,OAAO,EAAE,oCAAoC,EAAE,MAAM,sCAAsC,CAAC;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,qCAAqC,CAAC;AAC3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAElF;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAElD;;;OAGG;gBACS,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE;IA4B7C;;;OAGG;IACH,MAAM,IAAI,SAAS,SAAS,EAAE;IAI9B;;;;OAIG;IACG,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/D;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;CAazE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAE3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EACV,SAAS,EACT,gBAAgB,EAChB,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAElC,YAAY,EACV,SAAS,EACT,eAAe,EACf,gBAAgB,EAChB,eAAe,GAChB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,iCAAiC,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,6CAA6C,EAAE,MAAM,gDAAgD,CAAC;AAC/G,OAAO,EAAE,qCAAqC,EAAE,MAAM,qCAAqC,CAAC;AAC5F,OAAO,EAAE,mCAAmC,EAAE,MAAM,oCAAoC,CAAC;AACzF,OAAO,EAAE,uCAAuC,EAAE,MAAM,wCAAwC,CAAC;AACjG,OAAO,EAAE,qCAAqC,EAAE,MAAM,uCAAuC,CAAC;AAC9F,OAAO,EAAE,8BAA8B,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,EAAE,gCAAgC,EAAE,MAAM,iCAAiC,CAAC;AACnF,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,wCAAwC,EAAE,MAAM,yCAAyC,CAAC;AACnG,OAAO,EAAE,8BAA8B,EAAE,MAAM,+BAA+B,CAAC;AAC/E,OAAO,EAAE,kCAAkC,EAAE,MAAM,mCAAmC,CAAC;AACvF,OAAO,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAC;AACtE,OAAO,EAAE,uCAAuC,EAAE,MAAM,yCAAyC,CAAC;AAClG,OAAO,EAAE,mCAAmC,EAAE,MAAM,qCAAqC,CAAC;AAC1F,OAAO,EAAE,yCAAyC,EAAE,MAAM,2CAA2C,CAAC;AACtG,OAAO,EAAE,iCAAiC,EAAE,MAAM,kCAAkC,CAAC;AACrF,OAAO,EAAE,oCAAoC,EAAE,MAAM,qCAAqC,CAAC;AAC3F,OAAO,EAAE,oCAAoC,EAAE,MAAM,sCAAsC,CAAC;AAC5F,OAAO,EAAE,oCAAoC,EAAE,MAAM,qCAAqC,CAAC;AAC3F,OAAO,EAAE,gCAAgC,EAAE,MAAM,gCAAgC,CAAC;AAElF;;GAEG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAElD;;;OAGG;gBACS,UAAU,CAAC,EAAE,SAAS,SAAS,EAAE;IA6B7C;;;OAGG;IACH,MAAM,IAAI,SAAS,SAAS,EAAE;IAI9B;;;;OAIG;IACG,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ/D;;;;OAIG;IACG,MAAM,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;CAazE;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,iBAAiB,CAE3D"}
@@ -1,4 +1,5 @@
1
1
  import { EnsureAuditIgnoreLocalExclusionsMigration } from "./ensure-audit-ignore-local-exclusions.js";
2
+ import { EnsureDeployOutcomeGuardMigration } from "./ensure-deploy-outcome-guard.js";
2
3
  import { EnsureJestRnMockAccessibilityManagerMigration } from "./ensure-jest-rn-mock-accessibility-manager.js";
3
4
  import { EnsureLearningsGitattributesMigration } from "./ensure-learnings-gitattributes.js";
4
5
  import { EnsureLearningsMergeDriverMigration } from "./ensure-learnings-merge-driver.js";
@@ -20,6 +21,7 @@ import { PruneRetiredClaudeDenyRulesMigration } from "./prune-retired-claude-den
20
21
  import { ReconcileClaudeStackPluginsMigration } from "./reconcile-claude-stack-plugins.js";
21
22
  import { UntrackCodexMarketplaceMigration } from "./untrack-codex-marketplace.js";
22
23
  export { EnsureAuditIgnoreLocalExclusionsMigration } from "./ensure-audit-ignore-local-exclusions.js";
24
+ export { EnsureDeployOutcomeGuardMigration } from "./ensure-deploy-outcome-guard.js";
23
25
  export { EnsureJestRnMockAccessibilityManagerMigration } from "./ensure-jest-rn-mock-accessibility-manager.js";
24
26
  export { EnsureLearningsGitattributesMigration } from "./ensure-learnings-gitattributes.js";
25
27
  export { EnsureLearningsMergeDriverMigration } from "./ensure-learnings-merge-driver.js";
@@ -54,6 +56,7 @@ export class MigrationRegistry {
54
56
  new EnsureTsconfigLocalIncludesMigration(),
55
57
  new EnsureTsconfigLocalFilesFallbackMigration(),
56
58
  new EnsureAuditIgnoreLocalExclusionsMigration(),
59
+ new EnsureDeployOutcomeGuardMigration(),
57
60
  new EnsureJestRnMockAccessibilityManagerMigration(),
58
61
  new EnsureLearningsGitattributesMigration(),
59
62
  new EnsureLearningsMergeDriverMigration(),
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/migrations/index.ts"],"names":[],"mappings":"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"}
@@ -143,9 +143,18 @@ export function showAtRevision(root, revision, relative) {
143
143
 
144
144
  /**
145
145
  * List the feature files that existed at a revision.
146
+ *
147
+ * Three-state on purpose. `git ls-tree` exits 0 with EMPTY output for a
148
+ * revision that genuinely holds no features, so a non-zero exit is always
149
+ * "could not ask" and never "there were none". Handing both back as `[]` let
150
+ * one failed listing read as an empty baseline, and an empty `before` set
151
+ * produces no `scenario-deleted` and no `coverage-regression` finding at all —
152
+ * so the gate reported that nothing regressed having compared against nothing.
153
+ * That is the same hole `loadBaseline` already closes for the coverage map,
154
+ * left open on the feature files beside it.
146
155
  * @param {string} root - Repo root.
147
156
  * @param {string} revision - Commit-ish.
148
- * @returns {string[]} Repo-relative feature paths.
157
+ * @returns {{ok: boolean, files: string[]}} Listing, and whether it was taken.
149
158
  */
150
159
  function featureFilesAt(root, revision) {
151
160
  const result = git(root, [
@@ -156,11 +165,16 @@ function featureFilesAt(root, revision) {
156
165
  "--",
157
166
  "bdd/features",
158
167
  ]);
159
- if (!result.ok) return [];
160
- return result.stdout
161
- .split("\n")
162
- .map(line => line.trim())
163
- .filter(line => line.endsWith(".feature"));
168
+ // probe-direction: fail-closed — `ok: false` reaches `loadBaseline`, which
169
+ // records the baseline UNAVAILABLE instead of empty.
170
+ if (!result.ok) return { ok: false, files: [] };
171
+ return {
172
+ ok: true,
173
+ files: result.stdout
174
+ .split("\n")
175
+ .map(line => line.trim())
176
+ .filter(line => line.endsWith(".feature")),
177
+ };
164
178
  }
165
179
 
166
180
  /**
@@ -198,12 +212,17 @@ export function loadBaseline(root, revision, headPlatforms = new Set()) {
198
212
  error = `bdd/coverage-map.json at that revision is not valid JSON: ${parseError.message}`;
199
213
  }
200
214
  }
215
+ const listing = featureFilesAt(root, revision);
201
216
  if (binary === null) {
202
217
  error = "git was not found, so no base revision could be read";
203
218
  } else if (!revisionExists) {
204
219
  error = "the requested base does not name a readable commit";
220
+ } else if (!listing.ok) {
221
+ error =
222
+ "the base revision's feature files could not be listed, so no scenario " +
223
+ "from it could be compared";
205
224
  }
206
- const documents = featureFilesAt(root, revision)
225
+ const documents = listing.files
207
226
  .map(file => ({ file, source: showAtRevision(root, revision, file) }))
208
227
  .filter(document => document.source !== null);
209
228
  const platforms = new Set([
package/package.json CHANGED
@@ -63,6 +63,7 @@
63
63
  "check:pipeline-status-reads": "node scripts/check-pipeline-status-reads.mjs",
64
64
  "check:whole-output-guards": "node scripts/check-whole-output-guards.mjs",
65
65
  "check:empty-subject-guards": "node scripts/check-empty-subject-guards.mjs",
66
+ "check:probe-absence-direction": "node scripts/check-probe-absence-direction.mjs",
66
67
  "check:shell-guard-refusals": "bun run build && $npm_execpath run lisa-test-run -- --adapter direct -- node scripts/check-shell-guard-refusal-coverage.mjs",
67
68
  "check:workflow-inputs": "node scripts/detect-stale-workflow-inputs.mjs --project . --project typescript/create-only --project rails/create-only --json",
68
69
  "check:conflict-markers": "node all/copy-overwrite/scripts/check-conflict-markers.mjs",
@@ -173,7 +174,7 @@
173
174
  "zod-validation-error": "^4.0.0"
174
175
  },
175
176
  "name": "@codyswann/lisa",
176
- "version": "4.42.6",
177
+ "version": "4.43.0",
177
178
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
178
179
  "main": "dist/index.js",
179
180
  "exports": {
@@ -321,7 +322,7 @@
321
322
  "test": "tests"
322
323
  },
323
324
  "types": "./dist/index.d.ts",
324
- "lisaReleaseCommit": "1148474ed60b8770215ec537dd4c949d259cda45",
325
- "gitHead": "1148474ed60b8770215ec537dd4c949d259cda45",
326
- "lisaReleaseTag": "v4.42.6"
325
+ "lisaReleaseCommit": "e7ce0fdee9ed37c14c80f0925a1e455c2e88c05f",
326
+ "gitHead": "e7ce0fdee9ed37c14c80f0925a1e455c2e88c05f",
327
+ "lisaReleaseTag": "v4.43.0"
327
328
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.42.6",
3
+ "version": "4.43.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.42.6",
3
+ "version": "4.43.0",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -617,6 +617,17 @@ gh api "repos/<owner>/<repo>/actions/runs?head_sha=<head>" --jq .total_count
617
617
  problem. If CI were merely slow you would see runs QUEUED, not absent. Resolve
618
618
  the conflict and the runs appear; nothing else will make them appear.
619
619
 
620
+ **`<head>` must be the FULL 40-character SHA.** `head_sha=` does not match a
621
+ prefix and does not reject one: an abbreviated SHA returns a clean
622
+ `total_count: 0` — not a 404, not a validation error, an absence shaped exactly
623
+ like "no workflow ran". Measured on one pull request's own head: `77f223b9d` →
624
+ `total_count 0`, `77f223b9d8ffe7b172e9cac48b068b3bcac304c3` → `total_count 4`.
625
+ Take the SHA from `gh pr view <pr> --json headRefOid --jq .headRefOid`, never
626
+ from `git log --oneline` or a truncated line in a log or a UI. Copying a short
627
+ SHA here makes EVERY pull request read as having no CI, and the natural next
628
+ action — rebase and force-push — throws away the live run you could not see
629
+ (#3848).
630
+
620
631
  **`mergeable` is computed asynchronously.** GitHub returns `null` while it is
621
632
  still working it out, so a single read on a freshly-opened PR can say `null` on
622
633
  a perfectly clean branch. Treat `null` as "cannot tell yet" and re-read — never
@@ -218,6 +218,9 @@ export function hasBootstrap(key, deps = {}) {
218
218
  );
219
219
  return true;
220
220
  } catch {
221
+ // probe-direction: fail-closed — false means "no bootstrap value proven
222
+ // present", which makes the setup flow ASK for one. A keychain that cannot
223
+ // be queried costs a redundant prompt, never a skipped bootstrap.
221
224
  return false;
222
225
  }
223
226
  }
@@ -270,6 +270,9 @@ export function runProbe(substrate) {
270
270
  });
271
271
  return true;
272
272
  } catch {
273
+ // probe-direction: fail-closed — false makes the caller behave as if no
274
+ // substrate existed and fall through to the provider, which is the stricter
275
+ // path, exactly as the paragraph above states.
273
276
  return false;
274
277
  }
275
278
  }
@@ -178,6 +178,8 @@ function keychainServiceNames() {
178
178
  ([, name]) => name
179
179
  );
180
180
  } catch {
181
+ // probe-direction: neutral — the names only enrich a diagnostic message on a
182
+ // path that has already failed; no verdict reads them.
181
183
  // A locked, absent, or oversized keychain leaves the list empty.
182
184
  return [];
183
185
  }
@@ -253,6 +255,8 @@ export function providerCliPresent(prefix) {
253
255
  ).trim()
254
256
  );
255
257
  } catch {
258
+ // probe-direction: neutral — PATH presence is reported in the explanation of
259
+ // an already-failing lookup; it gates nothing on its own.
256
260
  return false;
257
261
  }
258
262
  }
@@ -66,6 +66,9 @@ export function locate(name, exec = boundedChildOutput) {
66
66
  exec("command", ["-v", name], { encoding: "utf8", shell: true })
67
67
  ).trim();
68
68
  } catch {
69
+ // probe-direction: fail-closed — null means "not proven present", and the
70
+ // setup flow responds by installing. A probe that cannot answer costs a
71
+ // redundant install, never a missing tool reported as present.
69
72
  return null;
70
73
  }
71
74
  }
@@ -121,6 +121,10 @@ function git(args, cwd) {
121
121
  timeout: 30_000,
122
122
  });
123
123
  } catch {
124
+ // probe-direction: fail-closed — null never reaches a verdict as "no
125
+ // weakening found". `changePlan` turns it into `undeterminable`, which
126
+ // REFUSES, and `isPromotion` falls to the strict reading that keeps the
127
+ // allow list pinned to the baseline.
124
128
  return null;
125
129
  }
126
130
  }
@@ -166,6 +170,8 @@ function resolvePlan(mode, root, baseRef, onlyFiles) {
166
170
  try {
167
171
  return fs.readFileSync(path.join(root, f), "utf-8");
168
172
  } catch {
173
+ // probe-direction: fail-closed — an unreadable current file compares as
174
+ // absent against the baseline, which is the strict side of the ratchet.
169
175
  return null;
170
176
  }
171
177
  },
@@ -96,14 +96,35 @@ function git(args, cwd) {
96
96
  stdio: ["ignore", "pipe", "ignore"],
97
97
  }).trim();
98
98
  } catch {
99
+ // probe-direction: neutral — this helper REPORTS, it does not decide. Both
100
+ // callers below read null and each one says out loud what it could not do
101
+ // before returning, so no gate outcome follows from this null silently
102
+ // (#3848). Keep that true: a caller added here that spends null as an
103
+ // answer has to say so at its own line.
99
104
  return null;
100
105
  }
101
106
  }
102
107
 
103
- /** The worktree root containing `cwd`, or null when `cwd` is not in a repo. */
108
+ /**
109
+ * The worktree root containing `cwd`, or null when `cwd` is not in a repo.
110
+ *
111
+ * `--show-toplevel` exits non-zero for two OPPOSITE reasons and hands back the
112
+ * same nothing for both: `cwd` is outside any repository — a real absence, and
113
+ * a call there is genuinely none of this guard's business — or git could not
114
+ * answer at all, where the binding is unproven rather than inapplicable. Only
115
+ * the second is a withdrawal of enforcement, and it is the one that must not
116
+ * pass for the first. `git --version` succeeds wherever git can run, so its
117
+ * failure is what separates them.
118
+ * @param {string} cwd Directory the tool call runs in.
119
+ * @returns {string|null} Absolute worktree root, or null.
120
+ */
104
121
  function worktreeRoot(cwd) {
105
122
  const top = git(["rev-parse", "--show-toplevel"], cwd);
106
- return top ? resolve(top) : null;
123
+ if (top) return resolve(top);
124
+ if (git(["--version"], cwd) === null) {
125
+ say("git could not be run here, so the worktree binding is NOT enforced");
126
+ }
127
+ return null;
107
128
  }
108
129
 
109
130
  /**
@@ -117,7 +138,14 @@ function mainCheckout(cwd) {
117
138
  ["rev-parse", "--path-format=absolute", "--git-common-dir"],
118
139
  cwd
119
140
  );
120
- return common ? dirname(resolve(common)) : null;
141
+ if (common) return dirname(resolve(common));
142
+ // Reached only for an `EnterWorktree` naming a worktree, which means a
143
+ // repository was already in hand — so failing here is "could not ask", and
144
+ // the consequence is that the claim goes unrecorded and the NEXT call's
145
+ // displacement check has nothing to compare against. Said out loud rather
146
+ // than returned as a quiet null (#3848).
147
+ say("git could not resolve the main checkout, so this claim is NOT recorded");
148
+ return null;
121
149
  }
122
150
 
123
151
  function stateFile(sessionId) {
@@ -298,6 +298,8 @@ async function callMcp(token, tool, args) {
298
298
  .map(part => part.text ?? "")
299
299
  .join("");
300
300
  } catch {
301
+ // probe-direction: neutral — this is a design-token lookup for a generator;
302
+ // no gate reads the result, and null renders as a missing token.
301
303
  return null;
302
304
  }
303
305
  }
@@ -617,6 +617,17 @@ gh api "repos/<owner>/<repo>/actions/runs?head_sha=<head>" --jq .total_count
617
617
  problem. If CI were merely slow you would see runs QUEUED, not absent. Resolve
618
618
  the conflict and the runs appear; nothing else will make them appear.
619
619
 
620
+ **`<head>` must be the FULL 40-character SHA.** `head_sha=` does not match a
621
+ prefix and does not reject one: an abbreviated SHA returns a clean
622
+ `total_count: 0` — not a 404, not a validation error, an absence shaped exactly
623
+ like "no workflow ran". Measured on one pull request's own head: `77f223b9d` →
624
+ `total_count 0`, `77f223b9d8ffe7b172e9cac48b068b3bcac304c3` → `total_count 4`.
625
+ Take the SHA from `gh pr view <pr> --json headRefOid --jq .headRefOid`, never
626
+ from `git log --oneline` or a truncated line in a log or a UI. Copying a short
627
+ SHA here makes EVERY pull request read as having no CI, and the natural next
628
+ action — rebase and force-push — throws away the live run you could not see
629
+ (#3848).
630
+
620
631
  **`mergeable` is computed asynchronously.** GitHub returns `null` while it is
621
632
  still working it out, so a single read on a freshly-opened PR can say `null` on
622
633
  a perfectly clean branch. Treat `null` as "cannot tell yet" and re-read — never
@@ -218,6 +218,9 @@ export function hasBootstrap(key, deps = {}) {
218
218
  );
219
219
  return true;
220
220
  } catch {
221
+ // probe-direction: fail-closed — false means "no bootstrap value proven
222
+ // present", which makes the setup flow ASK for one. A keychain that cannot
223
+ // be queried costs a redundant prompt, never a skipped bootstrap.
221
224
  return false;
222
225
  }
223
226
  }
@@ -270,6 +270,9 @@ export function runProbe(substrate) {
270
270
  });
271
271
  return true;
272
272
  } catch {
273
+ // probe-direction: fail-closed — false makes the caller behave as if no
274
+ // substrate existed and fall through to the provider, which is the stricter
275
+ // path, exactly as the paragraph above states.
273
276
  return false;
274
277
  }
275
278
  }
@@ -178,6 +178,8 @@ function keychainServiceNames() {
178
178
  ([, name]) => name
179
179
  );
180
180
  } catch {
181
+ // probe-direction: neutral — the names only enrich a diagnostic message on a
182
+ // path that has already failed; no verdict reads them.
181
183
  // A locked, absent, or oversized keychain leaves the list empty.
182
184
  return [];
183
185
  }
@@ -253,6 +255,8 @@ export function providerCliPresent(prefix) {
253
255
  ).trim()
254
256
  );
255
257
  } catch {
258
+ // probe-direction: neutral — PATH presence is reported in the explanation of
259
+ // an already-failing lookup; it gates nothing on its own.
256
260
  return false;
257
261
  }
258
262
  }
@@ -66,6 +66,9 @@ export function locate(name, exec = boundedChildOutput) {
66
66
  exec("command", ["-v", name], { encoding: "utf8", shell: true })
67
67
  ).trim();
68
68
  } catch {
69
+ // probe-direction: fail-closed — null means "not proven present", and the
70
+ // setup flow responds by installing. A probe that cannot answer costs a
71
+ // redundant install, never a missing tool reported as present.
69
72
  return null;
70
73
  }
71
74
  }