forge-workflow 0.1.0-beta.4 → 0.1.0-beta.6

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 (196) hide show
  1. package/AGENTS.md +18 -7
  2. package/CHANGELOG.md +79 -1
  3. package/CLAUDE.md +0 -12
  4. package/CODING_STANDARDS.md +72 -0
  5. package/README.md +6 -2
  6. package/bin/forge-cmd.js +20 -0
  7. package/bin/forge.js +28 -375
  8. package/docs/INDEX.md +1 -1
  9. package/docs/guides/BEADS_GITHUB_SYNC.md +2 -31
  10. package/docs/guides/MIGRATION.md +4 -4
  11. package/docs/guides/SETUP.md +16 -16
  12. package/docs/reference/COMMANDS.md +8 -5
  13. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +4 -0
  14. package/docs/reference/INSIGHTS_RECAP.md +9 -20
  15. package/docs/reference/INSTALL.md +4 -0
  16. package/docs/reference/LEGACY_CLAIM_REPAIR.md +112 -0
  17. package/docs/reference/RELEASE.md +5 -3
  18. package/docs/reference/TOOLCHAIN.md +8 -0
  19. package/docs/reference/github-accounts.md +134 -0
  20. package/docs/reference/protected-state-surfaces.md +4 -4
  21. package/docs/reference/shepherd.md +114 -35
  22. package/lefthook.yml +12 -0
  23. package/lib/activation/ensure-forge-home.js +33 -15
  24. package/lib/adapters/pr-state-adapter.js +359 -144
  25. package/lib/audit-evidence.js +71 -110
  26. package/lib/base-remote.js +138 -0
  27. package/lib/beta5-compatibility-evidence.js +1093 -0
  28. package/lib/bun-lockfile-proof.js +413 -0
  29. package/lib/bun-workflow-pins.js +461 -0
  30. package/lib/capabilities/index.js +9 -0
  31. package/lib/capabilities/model.js +141 -0
  32. package/lib/capabilities/probes.js +347 -0
  33. package/lib/capped-jsonl-log.js +236 -0
  34. package/lib/codex-skills.js +2 -2
  35. package/lib/commands/_manifest.js +1 -0
  36. package/lib/commands/_registry.js +50 -20
  37. package/lib/commands/clean.js +252 -32
  38. package/lib/commands/dev.js +4 -33
  39. package/lib/commands/doctor.js +37 -6
  40. package/lib/commands/gate.js +197 -27
  41. package/lib/commands/github.js +215 -0
  42. package/lib/commands/hooks.js +276 -30
  43. package/lib/commands/insights.js +8 -3
  44. package/lib/commands/memory.js +66 -2
  45. package/lib/commands/merge.js +1265 -58
  46. package/lib/commands/plan.js +33 -2
  47. package/lib/commands/pr.js +3 -1
  48. package/lib/commands/preflight.js +21 -4
  49. package/lib/commands/prime.js +21 -8
  50. package/lib/commands/push.js +146 -54
  51. package/lib/commands/recall.js +127 -49
  52. package/lib/commands/recap.js +6 -1
  53. package/lib/commands/release.js +39 -3
  54. package/lib/commands/remember.js +28 -4
  55. package/lib/commands/serve.js +26 -9
  56. package/lib/commands/setup.js +323 -98
  57. package/lib/commands/shepherd.js +591 -73
  58. package/lib/commands/ship.js +36 -91
  59. package/lib/commands/skill.js +127 -11
  60. package/lib/commands/status.js +17 -1
  61. package/lib/commands/team.js +47 -8
  62. package/lib/commands/test.js +187 -38
  63. package/lib/commands/validate.js +65 -21
  64. package/lib/commands/worktree.js +359 -45
  65. package/lib/core/runtime-graph.js +1 -1
  66. package/lib/doc-assertions.js +297 -0
  67. package/lib/existing-tdd-gate.js +253 -0
  68. package/lib/fixtures/beta5-corpus/v1/README.md +9 -0
  69. package/lib/fixtures/beta5-corpus/v1/contract/command-contract.json +26 -0
  70. package/lib/fixtures/beta5-corpus/v1/contract/package-contract.json +13 -0
  71. package/lib/fixtures/beta5-corpus/v1/contract/workflow-stage-matrix.json +8 -0
  72. package/lib/fixtures/beta5-corpus/v1/manifest.json +25 -0
  73. package/lib/fixtures/beta5-corpus/v1/state/comments.jsonl +1 -0
  74. package/lib/fixtures/beta5-corpus/v1/state/config.yaml +6 -0
  75. package/lib/fixtures/beta5-corpus/v1/state/dependencies.jsonl +1 -0
  76. package/lib/fixtures/beta5-corpus/v1/state/issues.jsonl +2 -0
  77. package/lib/fixtures/beta5-corpus/v1/state/kernel.sql +20 -0
  78. package/lib/forge-context.js +1 -4
  79. package/lib/forge-issues.js +134 -32
  80. package/lib/gate-events.js +98 -10
  81. package/lib/git-defaults.js +56 -0
  82. package/lib/github-context.js +308 -0
  83. package/lib/global-flags.js +1 -0
  84. package/lib/harness-capability-matrix.js +3 -3
  85. package/lib/hook-renderer.js +122 -5
  86. package/lib/insights.js +96 -80
  87. package/lib/issue-render.js +19 -0
  88. package/lib/kernel/backing-issue.js +14 -2
  89. package/lib/kernel/broker.js +739 -31
  90. package/lib/kernel/claim-reconciler.js +238 -0
  91. package/lib/kernel/cli-broker-factory.js +12 -1
  92. package/lib/kernel/close-on-merge.js +154 -0
  93. package/lib/kernel/fs-class.js +42 -25
  94. package/lib/kernel/lease-enforcer.js +9 -4
  95. package/lib/kernel/legacy-claim-repair.js +442 -0
  96. package/lib/kernel/live-claim-projection.js +26 -0
  97. package/lib/kernel/migrations.js +118 -3
  98. package/lib/kernel/readiness-model.js +184 -12
  99. package/lib/kernel/schema.js +49 -1
  100. package/lib/kernel/sqlite-driver.js +3435 -172
  101. package/lib/kernel/taxonomy-validator.js +4 -1
  102. package/lib/kernel/windows-private-acl.js +239 -0
  103. package/lib/lefthook-wiring.js +21 -1
  104. package/lib/memory/hygiene.js +191 -0
  105. package/lib/memory/router.js +110 -28
  106. package/lib/memory/usage-evidence.js +4 -0
  107. package/lib/memory-digest.js +106 -15
  108. package/lib/memory-recall-events.js +145 -0
  109. package/lib/memory-recall.js +71 -10
  110. package/lib/merge-rules.js +143 -21
  111. package/lib/npm-publish-workflow.js +465 -0
  112. package/lib/orientation.js +68 -43
  113. package/lib/package-root.js +2 -0
  114. package/lib/plugin-catalog.js +14 -4
  115. package/lib/pr-bundle.js +5 -6
  116. package/lib/pr-monitor/auto-actions.js +169 -28
  117. package/lib/pr-monitor/differ.js +110 -4
  118. package/lib/pr-monitor/events.js +0 -0
  119. package/lib/pr-monitor/flow-monitor.js +1424 -0
  120. package/lib/pr-monitor/gather.js +251 -44
  121. package/lib/pr-monitor/journal.js +18 -39
  122. package/lib/pr-monitor/monitor.js +117 -10
  123. package/lib/pr-monitor/process-identity.js +117 -0
  124. package/lib/pr-monitor/reconcile-executor.js +1129 -470
  125. package/lib/pr-monitor/reconcile.js +0 -0
  126. package/lib/pr-monitor/render-summary.js +293 -0
  127. package/lib/pr-monitor/review-preflight.js +269 -0
  128. package/lib/pr-monitor/shepherd-lease.js +38 -20
  129. package/lib/pr-monitor/verdict.js +438 -0
  130. package/lib/pr-monitor/watch-lifecycle.js +145 -27
  131. package/lib/pr-monitor/watch-owner.js +1414 -0
  132. package/lib/pr-monitor/watch.js +129 -58
  133. package/lib/pr-pull.js +33 -14
  134. package/lib/pr-shepherd.js +51 -11
  135. package/lib/preflight/gates.js +65 -18
  136. package/lib/preflight/runner.js +5 -0
  137. package/lib/project-memory.js +178 -4
  138. package/lib/protected-state-authority.js +1100 -0
  139. package/lib/protected-state-surfaces.js +243 -45
  140. package/lib/release-readiness.js +53 -7
  141. package/lib/review-adapter.js +65 -0
  142. package/lib/shell-utils.js +1 -1
  143. package/lib/skills-sync.js +71 -35
  144. package/lib/smart-merge.js +28 -4
  145. package/lib/symlink-utils.js +74 -26
  146. package/lib/upgrade-safety.js +39 -0
  147. package/lib/using-forge.js +19 -6
  148. package/lib/validation/risk-manifest.js +339 -0
  149. package/lib/workflow/enforce-stage.js +44 -0
  150. package/lib/workflow/plan-authority.js +225 -0
  151. package/package.json +12 -9
  152. package/scripts/commitlint.js +13 -15
  153. package/scripts/doc-asserting-tests.js +158 -0
  154. package/scripts/generate-risk-manifest.js +91 -0
  155. package/scripts/github-context-bridge.sh +10 -0
  156. package/scripts/legacy-claim-repair.js +145 -0
  157. package/scripts/lib/behavioral-eval-runner.js +310 -0
  158. package/scripts/lib/behavioral-eval-runtime.js +457 -0
  159. package/scripts/lib/eval-evidence.js +328 -0
  160. package/scripts/lib/eval-runner.js +81 -41
  161. package/scripts/lib/immutable-eval-corpus.js +309 -0
  162. package/scripts/lib/promotion-evidence-loader.js +94 -0
  163. package/scripts/lib/promotion-scorecard.js +314 -0
  164. package/scripts/npm-release-receipt.js +134 -0
  165. package/scripts/process-tree.js +773 -0
  166. package/scripts/protected-state-check.js +479 -31
  167. package/scripts/run-command-eval.js +29 -1
  168. package/scripts/sync-agent-skills.js +333 -34
  169. package/scripts/sync-d20-audit.js +172 -0
  170. package/scripts/test-full-suite.js +935 -37
  171. package/scripts/test-profile.js +13 -3
  172. package/scripts/test.js +271 -57
  173. package/skills/coverage.json +1 -0
  174. package/skills/review/SKILL.md +6 -11
  175. package/skills/review/evals/scorecard.json +4 -4
  176. package/skills/rollback/SKILL.md +4 -11
  177. package/skills/rollback/evals/scorecard.json +3 -3
  178. package/skills/setup/SKILL.md +18 -0
  179. package/skills/setup/evals/scorecard.json +3 -3
  180. package/skills/shepherd/SKILL.md +39 -16
  181. package/skills/shepherd/evals/scorecard.json +4 -4
  182. package/skills/ship/SKILL.md +4 -12
  183. package/skills/ship/evals/scorecard.json +3 -3
  184. package/skills/validate/SKILL.md +3 -0
  185. package/skills/validate/evals/scorecard.json +1 -1
  186. package/skills/worktree/SKILL.md +6 -1
  187. package/skills/worktree/evals/scorecard.json +2 -2
  188. package/lib/beads-setup.js +0 -538
  189. package/lib/beads-sync-scaffold.js +0 -189
  190. package/lib/pat-setup.js +0 -207
  191. package/lib/pr-monitor/render-sticky.js +0 -206
  192. package/lib/pr-monitor/upsert-sticky.js +0 -169
  193. package/scripts/beads-context.sh +0 -577
  194. package/scripts/beads-migrate-to-dolt.sh +0 -7
  195. package/scripts/beads-upgrade-smoke.sh +0 -284
  196. package/scripts/lib/beads-migrate-to-dolt.mjs +0 -503
@@ -21,30 +21,57 @@ forge shepherd events <pr-number> --since <seq> # read new journal events back
21
21
  There are two ways to run the shepherd, both under the single `forge shepherd`
22
22
  verb:
23
23
 
24
- - **`forge shepherd daemon` — the singleton reconcile daemon (W-S4b), the default
25
- ownership model.** It acquires the machine-wide shepherd lease for this repo
24
+ - **`forge shepherd daemon` — the singleton reconcile daemon, the default
25
+ coordinator.** It acquires the machine-wide election lease for this repo
26
26
  (exiting immediately as a clean no-op if a live daemon already owns it),
27
27
  heartbeats, and converges the *entire* PR world every ~60s: self-registering
28
- hand-opened PRs, restarting killed watchers, reaping verified orphan watchers,
28
+ hand-opened PRs, recovering dead watcher generations, requesting cooperative
29
+ stops,
29
30
  converging CI check state into kernel verdicts, and retiring merged/closed PRs.
30
- It **self-retires** — releases the lease, kills its verified children, exits
31
- once no PRs remain open. Today it is started explicitly in a harness
32
- background shell at session start, or `forge shepherd daemon` — not run by hand
33
- as a detached process; automatic per-command launch (W-S4c) is a planned
34
- follow-up, not yet wired. Once running, an agent does not poll: the daemon owns
35
- the convergence loop.
36
- - **`forge shepherd <pr>` — one bounded pass.** Reads one PR's state, takes at
37
- most one Tier-A action, exits. The point-in-time surface for a single PR (see
38
- *Bounded-pass model* below).
31
+ It **self-retires** — releases the election lease and exits once no PRs
32
+ remain open. Per-PR ownership is never stored in that lease. Forge wakes it
33
+ automatically after a successful
34
+ supported session start, every successful push, and every successful
35
+ non-dry-run ship. These are the only automatic firing seams; ordinary commands
36
+ do not launch it. Once running, an agent does not poll: the daemon owns the
37
+ convergence loop.
38
+ - **`forge shepherd <pr>` one bounded convergence pass.** Runs local review
39
+ preflight, reads one PR's state, takes at most one Tier-A action, persists
40
+ bounded deltas/receipts, and exits. The point-in-time surface for a single PR
41
+ (see *Bounded-pass model* below).
39
42
  - **`forge shepherd watch <pr>` / `watch --adopt`** — foreground streaming watch of
40
- one PR, or (`--adopt`) adopt every currently-open PR into the watcher set.
43
+ one PR, or (`--adopt`) reserve owner rows and start watchers for every
44
+ currently-open PR.
41
45
  - **`forge shepherd events <pr> --since <seq>`** — the event deltas for a PR since a
42
46
  cursor; **`forge shepherd <pr> --pull --json` / `--bundle`** read the kernel verdict
43
47
  + rollup without taking any action.
44
48
 
45
- Session start: if the repo has open PRs, start `forge shepherd daemon` in the
46
- harness background shell so it is reaped with the session. No liveness check is
47
- needed first the O_EXCL singleton lease makes a duplicate start a clean no-op.
49
+ Session start automatically wakes the daemon. When an embedding caller supplies
50
+ an executable harness background-shell capability, Forge uses it so the process is reaped with the session;
51
+ bare CLI use falls back to a detached launch from the stable common repository
52
+ root, never a disposable worktree cwd. No liveness check is needed first — the
53
+ O_EXCL singleton lease makes a duplicate start a clean no-op.
54
+
55
+ The daemon never kills watcher PIDs. A stop is a generation-conditional
56
+ `running -> stop_requested` transition in the Kernel; the matching watcher sees
57
+ that state, exits its loop, and releases only its own generation. A dead watcher
58
+ is recovered only after fresh PID and provider evidence is gathered outside the
59
+ transaction and the exact owner-row snapshot still matches inside it.
60
+
61
+ Embedding boundary:
62
+
63
+ ```js
64
+ hooks.handler(['session-start', '--harness', 'claude'], {}, projectRoot, {
65
+ harness: {
66
+ hasBgShell: true,
67
+ runBgShell: (argv, options) => host.backgroundShell(argv, options),
68
+ },
69
+ });
70
+ ```
71
+
72
+ The host must explicitly provide both `hasBgShell: true` and `runBgShell`;
73
+ Forge never infers this capability from a harness name. The adapter receives
74
+ the stable Git common-root as `options.cwd`; a missing or throwing adapter uses the contained detached fallback.
48
75
  (A `forge prime` open-PR + daemon-liveness line is a planned follow-up — W-S5 —
49
76
  not yet wired.)
50
77
 
@@ -98,26 +125,37 @@ project's documented ergonomic — poll briefly, then stop and hand off. A pass
98
125
  that finds checks still pending returns `PENDING`; the next scheduled pass picks
99
126
  up from there.
100
127
 
128
+ Before remote mutation, the plain pass consolidates a probed CodeRabbit CLI with
129
+ strict lint, structural drift, Sonar parity, and affected tests. The result is
130
+ returned as `localPreflight`; CodeRabbit absence/auth loss is explicitly
131
+ `UNAVAILABLE`/`INCOMPLETE`, not green. Deterministic failure or actual local
132
+ review findings makes the remote decision dry-run for that pass. A local
133
+ checkout that is not the exact PR head is explicitly `NOT_APPLICABLE`. Output is
134
+ bounded to 128 `deltas` plus receipt IDs and one outcome handoff: `review` for
135
+ semantic feedback, explicit human-approved `merge` at `MERGE_READY`, or `verify`
136
+ after merged evidence. Shepherd never resolves threads and never merges.
137
+
101
138
  `--watch`-style behavior, if desired, belongs in an external scheduler (cron, or
102
139
  a `/loop`) that re-invokes the bounded pass with a debounce of at least 60
103
140
  seconds and cancel-in-progress. The shepherd itself never waits in-process.
104
141
 
105
- ## Auto-start on ship (`rail.auto_shepherd`)
142
+ ## Automatic singleton attachment (`rail.auto_shepherd`)
106
143
 
107
- `forge shepherd watch <pr>` is the constant, self-stopping local monitor loop
108
- (≈60 s jittered cadence; appends events to the per-PR NDJSON journal under
109
- `.forge/pr-monitor/<repo>-<pr>/`; self-stops on `PR_MERGED`/`PR_CLOSED`). On a
110
- successful `forge ship`, the new PR's watcher is **auto-started detached** so a
111
- shipped PR is tended without a manual trigger. The spawn is best-effort and
112
- **never fails ship** (a spawn or config-read error degrades to "not started"),
113
- and it is idempotent — the watch-lifecycle PID/journal lock prevents a second
114
- watcher for the same PR.
144
+ Successful supported session start, push, and non-dry-run ship operations wake the
145
+ repository-wide singleton. Push never requires a locally resolved PR number:
146
+ the daemon owns GitHub enumeration, so even a push with no local open-PR match
147
+ still fires and can adopt open or draft PRs created elsewhere. Push and ship no
148
+ longer start separate per-PR detached watchers.
115
149
 
116
150
  This auto-start is governed by the default-ON, unlocked **`rail.auto_shepherd`**
117
151
  rail. Opt out with `forge gate disable rail.auto_shepherd` (re-enable with
118
- `forge gate enable rail.auto_shepherd`); when disabled, `forge ship` skips the
119
- auto-start. This keeps the behavior honestly toggleable through the same config
120
- surface as every other rail.
152
+ `forge gate enable rail.auto_shepherd`). `FORGE_SHEPHERD_DISABLE=1` is the
153
+ environment kill-switch. CI/test detection is deterministic and injectable via
154
+ the trigger environment: `NODE_ENV=test`, `BUN_ENV=test`, or a set `CI`,
155
+ `GITHUB_ACTIONS`, or `GITLAB_CI` disables automatic fire. These environment
156
+ guards run before repository initialization, lease, Kernel-state, or process
157
+ work; dry-run, uninitialized repositories, and a disabled rail are likewise
158
+ zero-side-effect paths. The manual `forge shepherd` surface remains available.
121
159
 
122
160
  ## Surfacing events back to the agent (`forge hooks shepherd-events`)
123
161
 
@@ -146,6 +184,7 @@ same verb (or `forge shepherd events`) on its own cadence.
146
184
  | `MERGE_READY` | Required checks are green and the branch is up to date. The shepherd hands off — **a human merges in the GitHub UI.** |
147
185
  | `ESCALATE` | A Tier-C condition (conflict, unreadable required set, persistent failure, oscillation, budget exhaustion). Context is posted to the PR. |
148
186
  | `PENDING` | A Tier-A action was taken, or checks are still pending. Exit and await the next scheduled pass. |
187
+ | `INCOMPLETE` | Exact-head local review or durable convergence evidence is unavailable. Fail closed and retry after evidence is restored. |
149
188
  | `HARD_STOP` | A permanent auth/scope failure that retrying cannot fix. Escalate to a human to widen token scope. |
150
189
 
151
190
  ## Action ladder
@@ -171,6 +210,13 @@ same verb (or `forge shepherd events`) on its own cadence.
171
210
  - **HEAD-changed abort.** Before any mutating action it re-reads the head SHA; if
172
211
  HEAD moved during the pass, the action aborts. The `shepherd:active` marker is
173
212
  advisory only — it is not mutual exclusion.
213
+ - **One per-PR authority.** `kernel_pr_watch_owners` is the sole watcher owner
214
+ record. The daemon lease elects one reconciler but contains no watcher list;
215
+ journals and legacy PID/marker files cannot authorize a transition.
216
+ - **Fail-closed evidence.** Kernel unavailability, corrupt owner rows, stale
217
+ generations, changed provider/PID/receipt evidence, and an incomplete legacy
218
+ migration gate block mutation and are retried. No unlocked or filesystem
219
+ fallback becomes authority.
174
220
  - **Auth taxonomy.** 401 (expiry) pauses and surfaces; 403 insufficient-scope is
175
221
  a hard-stop; 403 with `Retry-After` honors the delay and resumes next pass.
176
222
 
@@ -210,17 +256,50 @@ workflow behaves exactly as before (no regression) — it just cannot auto-run C
210
256
  the updated head. Forge only wires the code path and reads the secret; **creating
211
257
  the secret is the maintainer's responsibility** — Forge never fabricates a token.
212
258
 
259
+ ## GitHub Actions PR-monitor summary
260
+
261
+ The repository PR-monitor workflow writes detailed review-thread and check
262
+ diagnostics to the Actions job summary (`GITHUB_STEP_SUMMARY`) and keeps the
263
+ machine-readable view available through `forge shepherd <pr> --pull --json`.
264
+ It projects that same canonical verdict onto exactly one `pr-verdict:*` label
265
+ for a cheap agent-agnostic read. The label is visibility only: merge authority
266
+ continues to use live protected required checks, the current head, and
267
+ unresolved review threads. The workflow does not create PR comments or a
268
+ neutral `forge/pr-monitor` check.
269
+
213
270
  ## Per-harness behavior
214
271
 
215
- - **Claude Code / Codex:** invoke `forge shepherd <pr>` directly; an external
216
- scheduler may drive repeated bounded passes.
272
+ - **Claude Code / Codex:** automatic session-start attachment wakes the singleton;
273
+ invoke `forge shepherd <pr>` directly for a bounded point-in-time read.
217
274
  - **Cursor:** manually-invoked only — run it from a terminal. No polling-loop
218
275
  affordance and no hook reliance on this surface.
219
276
 
220
277
  ## State
221
278
 
222
- Progress is durable in GitHub PR comments and labels plus `git`. The one local
223
- store is the constant monitor's per-PR journal under
224
- `.forge/pr-monitor/<repo>-<pr>/` (the append-only `events.ndjson` + snapshot and
225
- consumer cursors) the delivery/replay surface for `forge shepherd watch` and
226
- `events --since`. The bounded shepherd pass itself keeps no separate local state.
279
+ The shared Kernel database stores one versioned `kernel_pr_watch_owners` row per
280
+ canonical `(repo, pr)`. Its generation-conditional phases are `starting`,
281
+ `running`, `stop_requested`, `terminal_pending`, `complete`, and `blocked`.
282
+ Starts reserve a store-minted generation before spawning; the child binds its
283
+ PID to that exact generation; heartbeats, stop requests, terminal receipts,
284
+ recovery, release, and reopen are narrow compare-and-swap transitions. Provider,
285
+ PID, and receipt checks happen outside the short SQLite transaction, followed by
286
+ an exact snapshot check inside it.
287
+
288
+ Public Memory and the per-PR journal under
289
+ `.forge/pr-monitor/<repo>-<pr>/` remain durable delivery and replay surfaces for
290
+ monitor events, verdicts, `forge shepherd watch`, and `events --since`; neither
291
+ is watcher ownership authority. A terminal receipt first moves the owner row to
292
+ `terminal_pending`; a later transaction completes it only after the watcher PID
293
+ is confirmed dead and the receipt/provider evidence still matches.
294
+
295
+ Upgrade from the retired filesystem model is fenced by the repository-local
296
+ `kernel_pr_watch_migration_gate`. The importer publishes `quarantined`, obtains
297
+ two identical bounded snapshots of the old lease/PID/marker evidence, binds that
298
+ snapshot hash before importing one PR per transaction, durably rereads the rows
299
+ and source, and then completes the exact gate/hash. Corrupt, changed, live-PID,
300
+ or unmappable evidence stays fail-closed as `blocked` or `conflict`. After the
301
+ gate is complete, legacy evidence is cleanup-only and never regains authority.
302
+
303
+ For 0.1, no receipt grants continuing lease authority. Consequential paths
304
+ re-probe live ownership, while canonical LeaseReceipt epoch/scope and the
305
+ same-actor/session release-reclaim ABA fix remain explicitly deferred.
package/lefthook.yml CHANGED
@@ -21,6 +21,14 @@ pre-commit:
21
21
  stage_fixed: false
22
22
  tags: skills
23
23
  glob: "skills/**"
24
+ # Keep the D20 Beads call-site kill-list current with the census it audits.
25
+ # Self-gating: the script exits early unless a staged path actually counts toward
26
+ # the census, because those scan roots are resolved at run time (plugin manifests +
27
+ # .forge/sync-manifest.json) and a static glob here would drift from the gate.
28
+ sync-d20-audit:
29
+ run: node scripts/sync-d20-audit.js
30
+ stage_fixed: false
31
+ tags: release
24
32
  protected-state:
25
33
  run: node scripts/protected-state-check.js
26
34
  stage_fixed: false
@@ -50,6 +58,10 @@ pre-push:
50
58
  - run: node scripts/check-forge-token.js
51
59
 
52
60
  # 3. Test suite (cross-platform Node.js script detects package manager)
61
+ # `forge push --quick` sets FORGE_PUSH_LANE=quick on the git push it spawns;
62
+ # scripts/test.js honors that declaration and skips this job with a loud
63
+ # notice so the quick lane is lint-only end to end. Only this job reads it —
64
+ # branch protection and lint above always run, and CI runs the full matrix.
53
65
  tests:
54
66
  run: node scripts/test.js
55
67
  tags: tests
@@ -11,11 +11,11 @@
11
11
  * bare repo stays untouched until the user actually changes state.
12
12
  *
13
13
  * "Bare-minimum" here is strictly LESS than `forge init --minimal`: this writes
14
- * ONLY `.forge/config.yaml` with every gate disabled (the `minimal` adoption
15
- * profile). It installs NO git hooks, NO lefthook.yml, NO protected-paths
16
- * manifest, NO `.mcp.json`, and NO scripts tree — those remain the opt-in
17
- * payload of `forge setup`, never forced. Progressive growth: heavier slices
18
- * initialize themselves on first use.
14
+ * ONLY `.forge/config.yaml` with Forge's canonical default enforcement posture.
15
+ * It installs NO git hooks, NO lefthook.yml, NO protected-paths manifest, NO
16
+ * `.mcp.json`, and NO scripts tree — those remain the opt-in payload of
17
+ * `forge setup`, never forced. Progressive growth: heavier slices initialize
18
+ * themselves on first use.
19
19
  *
20
20
  * @module activation/ensure-forge-home
21
21
  */
@@ -78,26 +78,33 @@ function isMutatingVerb(name, command) {
78
78
  }
79
79
 
80
80
  /**
81
- * Render the bare-minimum, gates-disabled config body.
81
+ * Render the bare-minimum config body with Forge's default enforcement posture.
82
82
  *
83
- * Reuses the canonical `minimal` adoption profile so the produced YAML is
84
- * schema-valid and identical in spirit to `forge init --minimal` (minus the
85
- * hooks/protected-paths side effects). Overridable via `deps.renderConfig` for
86
- * tests that don't want to depend on the profile renderer.
83
+ * Reuses the canonical `standard` adoption profile so the produced YAML is
84
+ * schema-valid and keeps default gates enabled. Overridable via
85
+ * `deps.renderConfig` for tests that don't want to depend on the profile
86
+ * renderer.
87
87
  *
88
88
  * @param {object} [deps]
89
89
  * @returns {string} config.yaml contents
90
90
  */
91
- function renderMinimalConfig(deps = {}) {
92
- const render = deps.renderConfig || (() => renderAdoptionConfigYaml('minimal'));
91
+ function renderDefaultConfig(deps = {}) {
92
+ const render = deps.renderConfig || (() => renderAdoptionConfigYaml('standard'));
93
93
  return render();
94
94
  }
95
95
 
96
+ /**
97
+ * Backward-compatible export name for callers of the previously exported
98
+ * helper. New code should use `renderDefaultConfig`; lazy initialization no
99
+ * longer creates a minimal/disabled configuration.
100
+ */
101
+ const renderMinimalConfig = renderDefaultConfig;
102
+
96
103
  /**
97
104
  * Idempotently create the bare-minimum `.forge/` skeleton for a mutating verb.
98
105
  *
99
- * No-clobber by construction: if `.forge/` already exists (inited repo, or a
100
- * partially-created home), this is a NO-OP and never touches the user's files.
106
+ * No-clobber by construction: an existing config is a NO-OP. A partial home
107
+ * whose `.forge/` directory exists without `config.yaml` is completed safely.
101
108
  * The kernel/issue store (`.forge/kernel/…`) is created lazily by the broker on
102
109
  * the mutating verb itself — this function only guarantees the config skeleton.
103
110
  *
@@ -123,13 +130,24 @@ function ensureForgeHome(projectRoot = process.cwd(), deps = {}) {
123
130
  }
124
131
 
125
132
  fsImpl.mkdirSync(forgeDir, { recursive: true });
126
- fsImpl.writeFileSync(configPath, renderMinimalConfig(deps), 'utf8');
133
+ try {
134
+ fsImpl.writeFileSync(configPath, renderDefaultConfig(deps), {
135
+ encoding: 'utf8',
136
+ flag: 'wx',
137
+ });
138
+ } catch (error) {
139
+ if (error?.code === 'EEXIST') {
140
+ return { created: false, reason: 'config-exists', configPath };
141
+ }
142
+ throw error;
143
+ }
127
144
  return { created: true, configPath };
128
145
  }
129
146
 
130
147
  module.exports = {
131
148
  ensureForgeHome,
132
149
  isMutatingVerb,
150
+ renderDefaultConfig,
133
151
  renderMinimalConfig,
134
152
  MUTATING_VERBS,
135
153
  };