autonomous-sdlc-harness 0.1.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 (171) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +24 -0
  4. package/dist/cli.js +194 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/config.js +561 -0
  7. package/dist/commands/config.js.map +1 -0
  8. package/dist/commands/daemon.js +791 -0
  9. package/dist/commands/daemon.js.map +1 -0
  10. package/dist/commands/doctor.js +336 -0
  11. package/dist/commands/doctor.js.map +1 -0
  12. package/dist/commands/init.js +2023 -0
  13. package/dist/commands/init.js.map +1 -0
  14. package/dist/commands/registry.js +42 -0
  15. package/dist/commands/registry.js.map +1 -0
  16. package/dist/config/check.js +505 -0
  17. package/dist/config/check.js.map +1 -0
  18. package/dist/config/io.js +177 -0
  19. package/dist/config/io.js.map +1 -0
  20. package/dist/config/model.js +406 -0
  21. package/dist/config/model.js.map +1 -0
  22. package/dist/core/errors.js +71 -0
  23. package/dist/core/errors.js.map +1 -0
  24. package/dist/core/git.js +537 -0
  25. package/dist/core/git.js.map +1 -0
  26. package/dist/core/json.js +125 -0
  27. package/dist/core/json.js.map +1 -0
  28. package/dist/core/layerCoverage.js +141 -0
  29. package/dist/core/layerCoverage.js.map +1 -0
  30. package/dist/core/layerGapRemedy.js +62 -0
  31. package/dist/core/layerGapRemedy.js.map +1 -0
  32. package/dist/core/nameList.js +23 -0
  33. package/dist/core/nameList.js.map +1 -0
  34. package/dist/core/paths.js +153 -0
  35. package/dist/core/paths.js.map +1 -0
  36. package/dist/core/prompt.js +206 -0
  37. package/dist/core/prompt.js.map +1 -0
  38. package/dist/core/repoPaths.js +55 -0
  39. package/dist/core/repoPaths.js.map +1 -0
  40. package/dist/core/report.js +150 -0
  41. package/dist/core/report.js.map +1 -0
  42. package/dist/core/templating.js +88 -0
  43. package/dist/core/templating.js.map +1 -0
  44. package/dist/core/writer.js +479 -0
  45. package/dist/core/writer.js.map +1 -0
  46. package/dist/daemon/backend.js +180 -0
  47. package/dist/daemon/backend.js.map +1 -0
  48. package/dist/daemon/units.js +380 -0
  49. package/dist/daemon/units.js.map +1 -0
  50. package/dist/detect/nestedApplication.js +79 -0
  51. package/dist/detect/nestedApplication.js.map +1 -0
  52. package/dist/detect/presets.js +2033 -0
  53. package/dist/detect/presets.js.map +1 -0
  54. package/dist/detect/signals.js +1368 -0
  55. package/dist/detect/signals.js.map +1 -0
  56. package/dist/doctor/checks.js +3530 -0
  57. package/dist/doctor/checks.js.map +1 -0
  58. package/dist/generators/claudeContext.js +588 -0
  59. package/dist/generators/claudeContext.js.map +1 -0
  60. package/dist/generators/githooks.js +446 -0
  61. package/dist/generators/githooks.js.map +1 -0
  62. package/dist/generators/harnessConfig.js +632 -0
  63. package/dist/generators/harnessConfig.js.map +1 -0
  64. package/dist/generators/notifications.js +191 -0
  65. package/dist/generators/notifications.js.map +1 -0
  66. package/dist/generators/outerLoopScripts.js +165 -0
  67. package/dist/generators/outerLoopScripts.js.map +1 -0
  68. package/dist/generators/permissionProfile.js +1172 -0
  69. package/dist/generators/permissionProfile.js.map +1 -0
  70. package/dist/generators/projectSettings.js +322 -0
  71. package/dist/generators/projectSettings.js.map +1 -0
  72. package/dist/generators/repoRoot.js +417 -0
  73. package/dist/generators/repoRoot.js.map +1 -0
  74. package/dist/generators/scripts.js +557 -0
  75. package/dist/generators/scripts.js.map +1 -0
  76. package/dist/generators/stateDir.js +221 -0
  77. package/dist/generators/stateDir.js.map +1 -0
  78. package/dist/machine/paths.js +111 -0
  79. package/dist/machine/paths.js.map +1 -0
  80. package/dist/machine/plugins.js +224 -0
  81. package/dist/machine/plugins.js.map +1 -0
  82. package/dist/machine/registry.js +330 -0
  83. package/dist/machine/registry.js.map +1 -0
  84. package/package.json +23 -0
  85. package/scripts/README.md +13 -0
  86. package/scripts/daemon/launchd.plist.template +59 -0
  87. package/scripts/daemon/systemd.service.template +58 -0
  88. package/templates/README.md +15 -0
  89. package/templates/claude/CLAUDE.md +54 -0
  90. package/templates/claude/README.md +5 -0
  91. package/templates/claude/context/api.md +29 -0
  92. package/templates/claude/context/conventions.md +23 -0
  93. package/templates/claude/context/data-layer.md +28 -0
  94. package/templates/claude/context/data-storage.md +29 -0
  95. package/templates/claude/context/docs-catalog.md +29 -0
  96. package/templates/claude/context/domain.md +28 -0
  97. package/templates/claude/context/layer.md +20 -0
  98. package/templates/claude/context/module.md +30 -0
  99. package/templates/claude/context/package.md +29 -0
  100. package/templates/claude/context/presentation.md +32 -0
  101. package/templates/claude/context/state-slices.md +28 -0
  102. package/templates/claude/context/tests.md +28 -0
  103. package/templates/claude/harness-task-offer.md +58 -0
  104. package/templates/claude/push-notify.env.example +21 -0
  105. package/templates/claude/qa-accounts.env.example +38 -0
  106. package/templates/claude/qa_test_scenarios.md +110 -0
  107. package/templates/claude/settings.autonomous.json +93 -0
  108. package/templates/claude/settings.autonomous.qa.json +36 -0
  109. package/templates/githooks/README.md +3 -0
  110. package/templates/githooks/pre-push +72 -0
  111. package/templates/repo/README.md +3 -0
  112. package/templates/repo/gitattributes +16 -0
  113. package/templates/repo/gitignore +61 -0
  114. package/templates/repo/gitignore.qa +25 -0
  115. package/templates/repo/mcp.json +17 -0
  116. package/templates/scripts/README.md +5 -0
  117. package/templates/scripts/autonomous-format-stream.sh +95 -0
  118. package/templates/scripts/autonomous-notify.sh +337 -0
  119. package/templates/scripts/autonomous-watcher.sh +3087 -0
  120. package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
  121. package/templates/scripts/commit-on-branch.sh +288 -0
  122. package/templates/scripts/create-worktree.sh +360 -0
  123. package/templates/scripts/deploy.sh +47 -0
  124. package/templates/scripts/lib/harness-run-lib.sh +1481 -0
  125. package/templates/scripts/push-branch.sh +140 -0
  126. package/templates/scripts/refresh-branch.sh +244 -0
  127. package/templates/scripts/restart-watcher.sh +401 -0
  128. package/templates/scripts/scratch-run.sh +302 -0
  129. package/templates/scripts/setup-worktree.sh +262 -0
  130. package/templates/scripts/start-dev-server.sh +99 -0
  131. package/templates/scripts/test.sh +50 -0
  132. package/templates/scripts/typecheck.sh +50 -0
  133. package/templates/state-dir/README-root.md +13 -0
  134. package/templates/state-dir/README.md +9 -0
  135. package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
  136. package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
  137. package/templates/state-dir/architecture_reviews/README.md +9 -0
  138. package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
  139. package/templates/state-dir/autonomous_inbox/README.md +9 -0
  140. package/templates/state-dir/autonomous_logs/README.md +9 -0
  141. package/templates/state-dir/branch_statistics/README.md +9 -0
  142. package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
  143. package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
  144. package/templates/state-dir/business_parity_reviews/README.md +9 -0
  145. package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
  146. package/templates/state-dir/clarification_digests/README.md +9 -0
  147. package/templates/state-dir/clarifications/README.md +9 -0
  148. package/templates/state-dir/code_reviews/README.md +9 -0
  149. package/templates/state-dir/dispatch_additions/README.md +19 -0
  150. package/templates/state-dir/docs_catalog/README.md +9 -0
  151. package/templates/state-dir/flow_progress/README.md +9 -0
  152. package/templates/state-dir/improvement_observations/README.md +19 -0
  153. package/templates/state-dir/improvement_suggestions.md +29 -0
  154. package/templates/state-dir/lessons.md +23 -0
  155. package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
  156. package/templates/state-dir/qa_reviews/README.md +9 -0
  157. package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
  158. package/templates/state-dir/review_plan_reviews/README.md +9 -0
  159. package/templates/state-dir/scratch/README.md +11 -0
  160. package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
  161. package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
  162. package/templates/state-dir/skeptic_reviews/README.md +9 -0
  163. package/templates/state-dir/story_plans/README.md +9 -0
  164. package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
  165. package/templates/state-dir/task_plan_reviews/README.md +9 -0
  166. package/templates/state-dir/task_plans/README.md +9 -0
  167. package/templates/state-dir/task_prompts/README.md +9 -0
  168. package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
  169. package/templates/state-dir/ui_test_plans/README.md +9 -0
  170. package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
  171. package/templates/state-dir/user_reviews/README.md +9 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * The write-policy engine: the `init` re-run contract, implemented once.
3
+ *
4
+ * **The rule this module exists to enforce: a second `init` on a wired repository must not
5
+ * clobber an adopter's edited config.** That is a requirement, not a nicety, and it is a
6
+ * property of this engine rather than of each generator's discipline — every write into an
7
+ * adopting repository is enqueued here as a {@link WriteRequest}, and the policy on the request
8
+ * decides create / merge / keep / back-up-then-overwrite. A generator picks the policy for the
9
+ * file it is writing out of the table below; it does not invent one, and it never calls `fs`
10
+ * itself.
11
+ *
12
+ * **One artifact is deliberately outside this engine**, and it is machine state rather than an
13
+ * adopter's file: the repository registry `machine/registry.ts` owns, which `daemon install`
14
+ * rewrites in place. Its header states why none of the four policies below fits it. The daemon
15
+ * unit and the push-notification settings file *are* on a plan, under `allowOutsideRepo` — they
16
+ * are create-if-absent artifacts an operator goes on to edit, which is exactly what this engine
17
+ * is for.
18
+ *
19
+ * ## The re-run contract, per artifact
20
+ *
21
+ * | Artifact | Policy | Why |
22
+ * |---|---|---|
23
+ * | `harness.config.json` | `create-if-absent`, **not upgraded by `--force`** | The adopter edits it; a re-run must never rewrite it, and neither may `--force`: config is read-if-present, always. `init --reset-config` is the one path that rebuilds it, after a `.bak`. |
24
+ * | The permission profile (`.claude/settings.autonomous.json`) | `create-if-absent` | Hand-tuned after generation; an overwrite silently drops allow entries. |
25
+ * | `.claude/CLAUDE.md` | `create-if-absent` | `/harness-analyze` fills it; a re-run must not undo that. It carries neither the `<!-- harness:unfilled -->` marker nor the `**What belongs here**` block, so its setup state is the `<!-- harness:setup-pending -->` banner and `doctor`'s `setup-analysis` check, not the two-part test below. |
26
+ * | `.claude/harness-task-offer.md` | `create-if-absent` | Nothing fills it — the always-loaded file's `## Where a change request runs` fence points at it by name, so a re-run must keep the adopter's edited copy and `--force` after a `.bak` is the upgrade path. |
27
+ * | `.claude/context/*.md` stubs | `create-if-absent` | `/harness-analyze` fills them; a re-run must not undo that. Filled is checkable, by the two-part test `init`, `doctor` and the command all apply: a stub still carrying its `<!-- harness:unfilled -->` marker **and** its `**What belongs here**` block is an untouched skeleton, and anything else was written — by the command or by hand. |
28
+ * | `.claude/settings.json`, `.mcp.json` | `merge-json` (missing keys only) | Shared with other tooling; the adopter's own entries survive. |
29
+ * | `.gitignore` | `merge-lines` (a managed block, lines not already present) | Every repo already has one. |
30
+ * | The `stateDir` tree and its per-directory READMEs | `ensure-dir` + `create-if-absent` | Creating what is missing is the whole operation; an adopter may have rewritten a contract sentence. |
31
+ * | The ledgers `lessons.md` / `improvement_suggestions.md` | `create-if-absent`, **not upgraded by `--force`** | Ledgers accumulate and nothing can re-derive one; the engine's `.bak` is single-generation, so `--force` may not touch them. |
32
+ * | Git hook `pre-push`, wrapper scripts | `create-if-absent` | Adopter may have edited the guard. The hook alone is also replaced, after a `.bak`, by the one run that rebuilt the config it is rendered from (`init --reset-config`) — and only where its rendered `case` label no longer matches the set that config resolves, never where the label cannot be read. |
33
+ *
34
+ * `--force` upgrades **`create-if-absent` only** to overwrite-after-backup, and only where the
35
+ * request has not fixed its own answer with {@link WriteRequest.forceOverride}. It never applies
36
+ * to `merge-json` or `merge-lines` (those are already non-destructive) and never deletes a
37
+ * directory.
38
+ *
39
+ * That override is what the two `not upgraded by --force` rows above are written against, and both
40
+ * rows are the contract this engine now supports rather than a generator's private discipline. Two
41
+ * callers take it up **for a whole artifact class**: `config/io.ts`'s `saveConfig` for
42
+ * `harness.config.json` — `'never'` on an ordinary `init`, `'always'` on `init --reset-config` —
43
+ * and `generators/stateDir.ts` for the two ledgers, `'never'` and nothing else, because no command
44
+ * rebuilds a ledger. **Two more** read the tree and answer **per request**: `generators/claudeContext.ts`
45
+ * sets `'never'` on a conventions stub that is already a skeleton whose `.bak` holds a filled
46
+ * document, because that one write would replace the only surviving copy of the analysis with a
47
+ * skeleton — and on `.claude/CLAUDE.md` when its bytes already are the text that run renders, because
48
+ * that write would put nothing in the `.bak` and destroy what is in the one already there; and
49
+ * `generators/githooks.ts` sets `'always'` on the `pre-push` hook when the run rebuilt
50
+ * `harness.config.json` (`init --reset-config`) **and** the hook's rendered `case` label no longer
51
+ * matches the set that config resolves, so the run that made the guard wrong is the run that
52
+ * re-renders it. None of them is a further row above — all keep `create-if-absent`, and every other
53
+ * forced run over them still replaces after a `.bak`.
54
+ *
55
+ * The reason the default is create-if-absent rather than overwrite is `docs/config.md` §1: the
56
+ * files `init` generates are ones "the adopter then owns and edits like any other checked-in
57
+ * file … after `init`, the adopter's copy is the only copy". There is no private second copy to
58
+ * fall back on, so a rewrite is a loss.
59
+ *
60
+ * ## Two structural guarantees, both grep-checkable
61
+ *
62
+ * 1. **Every mutating filesystem call in this module is inside {@link commitWrite}** — the one
63
+ * commit boundary — plus {@link probeWritable}, which is `doctor`'s writability probe and is
64
+ * not part of a plan. `--dry-run` computes every outcome and skips that boundary, so a dry
65
+ * run cannot leak a write no matter what a generator enqueued.
66
+ * 2. **Nothing here shells out** — no child-process import at all, so the never-shell-out-a-
67
+ * recursive-removal rule stated in `cli.ts`'s header and in `cli/README.md` cannot be
68
+ * breached from this module. The single removal the engine performs is an in-process
69
+ * `rmSync` on one file, called without `recursive`, so it cannot remove a directory even if
70
+ * it were aimed at one.
71
+ */
72
+ import { chmodSync, copyFileSync, lstatSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
73
+ import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
74
+ import { HarnessError, internal } from './errors.js';
75
+ import { formatJson, mergeMissing, readJsonFile } from './json.js';
76
+ import { nameList } from './nameList.js';
77
+ import { insideRepo } from './paths.js';
78
+ /** Effect → the action kind recorded on a real run. */
79
+ const EFFECT_ACTION = {
80
+ created: 'created',
81
+ merged: 'merged',
82
+ kept: 'kept',
83
+ ensured: 'ensured',
84
+ 'backed-up-and-replaced': 'replaced',
85
+ };
86
+ /** Effect → the action kind recorded under `--dry-run`. */
87
+ const EFFECT_WOULD_ACTION = {
88
+ created: 'would-create',
89
+ merged: 'would-merge',
90
+ kept: 'would-keep',
91
+ ensured: 'would-ensure',
92
+ 'backed-up-and-replaced': 'would-replace',
93
+ };
94
+ /** `lstat` without following the link, or `undefined` when nothing is at `path`. */
95
+ function lstatOrUndefined(path) {
96
+ try {
97
+ return lstatSync(path);
98
+ }
99
+ catch (error) {
100
+ if (error?.code === 'ENOENT')
101
+ return undefined;
102
+ throw error;
103
+ }
104
+ }
105
+ /** Read a text file, or `undefined` when it does not exist. */
106
+ function readTextOrUndefined(path) {
107
+ try {
108
+ return readFileSync(path, 'utf8');
109
+ }
110
+ catch (error) {
111
+ if (error?.code === 'ENOENT')
112
+ return undefined;
113
+ throw error;
114
+ }
115
+ }
116
+ /** The request's content as text, for the policies that write a whole file. */
117
+ function contentAsText(request) {
118
+ const { content } = request;
119
+ if (typeof content === 'string')
120
+ return content;
121
+ if (content === undefined)
122
+ throw internal(`${request.label}: ${request.policy} needs content`);
123
+ return formatJson(content);
124
+ }
125
+ /** The request's content as a JSON object, for `merge-json`. */
126
+ function contentAsObject(request) {
127
+ const { content } = request;
128
+ if (content === undefined || typeof content === 'string') {
129
+ throw internal(`${request.label}: merge-json needs an object as content`);
130
+ }
131
+ return content;
132
+ }
133
+ /**
134
+ * The safety rails, all of which run over the **whole** plan before any commit, so a refusal on
135
+ * the last request cannot leave the first ten written:
136
+ *
137
+ * - the target is absolute, and inside the repository unless `allowOutsideRepo` says otherwise;
138
+ * - nothing at the target is a symlink — the engine writes files, and following a link would
139
+ * write through it to a location none of the checks above ever saw. The `<path>.bak` sibling
140
+ * gets the same refusal in {@link planWrite}, where the backup is decided;
141
+ * - the request carries content the policy can use;
142
+ * - no two requests aim at one path, which would make the second report `kept` against the
143
+ * first's fresh output. Repeated `ensure-dir` on one directory is the exception and is
144
+ * deduplicated, since more than one generator legitimately ensures the same tree.
145
+ */
146
+ function resolveRequests(requests, ctx) {
147
+ const resolved = [];
148
+ const seen = new Map();
149
+ for (const request of requests) {
150
+ if (!isAbsolute(request.path)) {
151
+ throw internal(`${request.label}: write target is not an absolute path: ${request.path}`);
152
+ }
153
+ const path = resolve(request.path);
154
+ if (request.allowOutsideRepo !== true && !insideRepo(ctx.repoRoot, path)) {
155
+ throw new HarnessError(`${request.label}: refusing to write outside ${ctx.repoRoot}: ${path}`);
156
+ }
157
+ const stats = lstatOrUndefined(path);
158
+ if (stats?.isSymbolicLink() === true) {
159
+ throw new HarnessError(`${request.label}: refusing to write through a symlink: ${path}`);
160
+ }
161
+ if (request.policy === 'ensure-dir') {
162
+ if (stats !== undefined && !stats.isDirectory()) {
163
+ throw new HarnessError(`${request.label}: ${path} exists and is not a directory`);
164
+ }
165
+ }
166
+ else if (stats !== undefined && !stats.isFile()) {
167
+ throw new HarnessError(`${request.label}: ${path} exists and is not a regular file`);
168
+ }
169
+ if (request.policy === 'ensure-dir') {
170
+ if (request.content !== undefined)
171
+ throw internal(`${request.label}: ensure-dir takes no content`);
172
+ }
173
+ else if (request.content === undefined) {
174
+ throw internal(`${request.label}: ${request.policy} needs content`);
175
+ }
176
+ if (request.policy === 'merge-json')
177
+ contentAsObject({ ...request, path });
178
+ if (request.policy === 'merge-lines') {
179
+ if (typeof request.content !== 'string')
180
+ throw internal(`${request.label}: merge-lines needs text content`);
181
+ if (request.blockHeader === undefined || request.blockHeader === '') {
182
+ throw internal(`${request.label}: merge-lines needs a blockHeader to mark its managed block`);
183
+ }
184
+ }
185
+ const previous = seen.get(path);
186
+ if (previous !== undefined) {
187
+ if (previous === 'ensure-dir' && request.policy === 'ensure-dir')
188
+ continue;
189
+ throw internal(`${request.label}: two writes target one path in the same plan: ${path}`);
190
+ }
191
+ seen.set(path, request.policy);
192
+ resolved.push({ ...request, path });
193
+ }
194
+ return resolved;
195
+ }
196
+ /**
197
+ * Decide what a request does, reading the filesystem but never changing it.
198
+ *
199
+ * `create-if-absent` writes only when the target is absent and otherwise keeps it, except under
200
+ * `--force`, which copies the existing file to `<path>.bak` and then writes. A request may fix
201
+ * that answer for itself with {@link WriteRequest.forceOverride}, which is read here and nowhere
202
+ * else in the module; either way the backup behaviour is the same. That `.bak` is overwritten if
203
+ * one is already there: a second replacing run therefore produces the same two files as the
204
+ * first, which keeps the output deterministic for the fixture tests. Chaining backups
205
+ * (`.bak.bak`) would make the tree depend on how many times the file had been replaced.
206
+ *
207
+ * A symlink at `<path>.bak` is refused here with the same message the target's rail uses: the
208
+ * backup is a `copyFileSync` destination, which is opened through a link, and this runs over the
209
+ * whole plan before any commit, so the refusal costs nothing already written.
210
+ */
211
+ function planWrite(request, ctx) {
212
+ switch (request.policy) {
213
+ case 'ensure-dir':
214
+ // Always idempotent: `mkdir -p` on an existing directory is a no-op, so there is no
215
+ // "kept" variant to distinguish and the effect is the same on every run.
216
+ return { request, effect: 'ensured' };
217
+ case 'create-if-absent': {
218
+ const exists = lstatOrUndefined(request.path) !== undefined;
219
+ if (!exists)
220
+ return { request, effect: 'created', text: contentAsText(request) };
221
+ const replace = request.forceOverride === undefined ? ctx.force : request.forceOverride === 'always';
222
+ if (!replace)
223
+ return { request, effect: 'kept' };
224
+ const backupPath = `${request.path}.bak`;
225
+ if (lstatOrUndefined(backupPath)?.isSymbolicLink() === true) {
226
+ throw new HarnessError(`${request.label}: refusing to write through a symlink: ${backupPath}`);
227
+ }
228
+ return { request, effect: 'backed-up-and-replaced', text: contentAsText(request), backupPath };
229
+ }
230
+ case 'merge-json': {
231
+ const source = contentAsObject(request);
232
+ const existing = readJsonFile(request.path);
233
+ if (existing === undefined)
234
+ return { request, effect: 'created', text: formatJson(source) };
235
+ if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
236
+ throw new HarnessError(`${request.label}: ${request.path} is not a JSON object, so it cannot be merged into`);
237
+ }
238
+ const merged = structuredClone(existing);
239
+ const added = mergeMissing(merged, source);
240
+ if (added.length === 0)
241
+ return { request, effect: 'kept' };
242
+ return { request, effect: 'merged', text: formatJson(merged), added };
243
+ }
244
+ case 'merge-lines':
245
+ return planMergeLines(request);
246
+ }
247
+ }
248
+ /**
249
+ * `merge-lines`: add only the lines not already somewhere in the file, inside a managed block
250
+ * introduced by `blockHeader`.
251
+ *
252
+ * Presence is judged against the **whole** file, not just the managed block, so a pattern the
253
+ * adopter had already written by hand is never duplicated. Blank lines in the payload are
254
+ * ignored — a blank line is present in almost every file, and emitting one carries no meaning.
255
+ *
256
+ * Where the header is already there, each missing line is spliced in **at its payload position**
257
+ * — see {@link insertIntoBlock} — rather than appended to the end of the block, so the block
258
+ * stays contiguous, the file gains no second header, and a line restored by a re-run comes back
259
+ * where the run that first wrote it had put it.
260
+ *
261
+ * **Position is content here, not tidiness.** A block's lines can carry an ordering relationship
262
+ * — an ignore rule and the negation that excepts one file from it — and git resolves a path by
263
+ * the **last** pattern that matches it. A rule an adopter deleted by hand and a re-run appended
264
+ * below its own negation therefore re-hides the file that negation exists for, in a tree the run
265
+ * has just reported one added line for, with `init`, `doctor` and git all reporting success.
266
+ *
267
+ * Where the header is absent, a fresh block is appended at the end of the file, as it always was.
268
+ */
269
+ function planMergeLines(request) {
270
+ const header = request.blockHeader;
271
+ const wanted = [];
272
+ for (const line of contentAsText(request).split('\n')) {
273
+ const trimmed = line.trim();
274
+ if (trimmed !== '' && !wanted.includes(trimmed))
275
+ wanted.push(trimmed);
276
+ }
277
+ const existingText = readTextOrUndefined(request.path);
278
+ if (existingText === undefined) {
279
+ return { request, effect: 'created', text: `${[header, ...wanted].join('\n')}\n`, added: wanted };
280
+ }
281
+ const lines = existingText.split('\n');
282
+ const present = new Set(lines.map((line) => line.trim()));
283
+ const missing = wanted.filter((line) => !present.has(line));
284
+ if (missing.length === 0)
285
+ return { request, effect: 'kept' };
286
+ // Drop a single trailing empty element so a file ending in a newline is not treated as
287
+ // having a final blank line; it is re-added when the text is joined below.
288
+ if (lines.length > 0 && lines[lines.length - 1] === '')
289
+ lines.pop();
290
+ const headerIndex = lines.findIndex((line) => line.trim() === header.trim());
291
+ if (headerIndex < 0) {
292
+ lines.push('', header, ...missing);
293
+ }
294
+ else {
295
+ insertIntoBlock(lines, headerIndex, wanted, new Set(missing));
296
+ }
297
+ return { request, effect: 'merged', text: `${lines.join('\n')}\n`, added: missing };
298
+ }
299
+ /**
300
+ * Splice every `missing` line into the managed block at the position the payload gives it,
301
+ * mutating `lines` in place.
302
+ *
303
+ * The cursor is the insertion point: it starts immediately after the header and moves to just
304
+ * after each payload line found still present in the block, so a missing line lands after its
305
+ * nearest surviving predecessor and the payload's own order is what the block ends up in. It only
306
+ * moves forward, which is what keeps two missing neighbours in their payload order.
307
+ *
308
+ * Two bounds matter. The search for a surviving predecessor runs **inside the block** — the run of
309
+ * non-blank lines beneath the header — so a payload line the adopter happens to have written
310
+ * elsewhere in the file cannot drag the cursor out of the block; and every insertion is at the
311
+ * cursor, which never passes the block's end, so a rule the adopter wrote directly beneath the
312
+ * block with no blank line between them stays where they put it.
313
+ */
314
+ function insertIntoBlock(lines, headerIndex, wanted, missing) {
315
+ let blockEnd = headerIndex + 1;
316
+ while (blockEnd < lines.length && lines[blockEnd].trim() !== '')
317
+ blockEnd += 1;
318
+ let cursor = headerIndex + 1;
319
+ for (const line of wanted) {
320
+ if (missing.has(line)) {
321
+ lines.splice(cursor, 0, line);
322
+ cursor += 1;
323
+ blockEnd += 1;
324
+ continue;
325
+ }
326
+ for (let scan = cursor; scan < blockEnd; scan += 1) {
327
+ if (lines[scan].trim() === line) {
328
+ cursor = scan + 1;
329
+ break;
330
+ }
331
+ }
332
+ }
333
+ }
334
+ /**
335
+ * **The commit boundary — the only place in this module that changes the filesystem**, apart
336
+ * from `doctor`'s {@link probeWritable}. `--dry-run` is implemented by not calling it.
337
+ *
338
+ * `mkdirSync` is `recursive` so a generator never has to create parent directories itself;
339
+ * nothing here removes anything.
340
+ *
341
+ * An `ensure-dir` request carrying a `mode` gets an explicit `chmodSync` after the creation, the
342
+ * same two steps the shell half's `hr_lane_dir` consumers take (`mkdir -p` then `chmod 700`) and
343
+ * for the same reason: `mkdirSync`'s own `mode` option is masked by the process umask and is
344
+ * ignored outright for a directory that is already there. It is *inside* this function, so the
345
+ * module header's guarantee 1 is untouched and `--dry-run` skips it with everything else.
346
+ */
347
+ function commitWrite(planned) {
348
+ const { request } = planned;
349
+ if (planned.effect === 'ensured') {
350
+ mkdirSync(request.path, { recursive: true });
351
+ if (request.mode !== undefined)
352
+ chmodSync(request.path, request.mode);
353
+ return;
354
+ }
355
+ if (planned.effect === 'kept')
356
+ return;
357
+ if (planned.backupPath !== undefined)
358
+ copyFileSync(request.path, planned.backupPath);
359
+ const text = planned.text;
360
+ if (text === undefined)
361
+ throw internal(`${request.label}: ${planned.effect} with nothing to write`);
362
+ mkdirSync(dirname(request.path), { recursive: true });
363
+ writeFileSync(request.path, text, 'utf8');
364
+ if (request.mode !== undefined)
365
+ chmodSync(request.path, request.mode);
366
+ }
367
+ /** `2 keys added: permissions.allow, hooks` — the qualifier on a `merged` action line. */
368
+ function mergedDetail(policy, added) {
369
+ const noun = policy === 'merge-json' ? 'key' : 'line';
370
+ return `${added.length} ${noun}${added.length === 1 ? '' : 's'} added: ${nameList(added)}`;
371
+ }
372
+ /** The parenthesised qualifier for one action line, or `undefined` when there is nothing to add. */
373
+ function actionDetail(planned) {
374
+ if (planned.effect === 'merged' && planned.added !== undefined) {
375
+ return mergedDetail(planned.request.policy, planned.added);
376
+ }
377
+ if (planned.backupPath !== undefined)
378
+ return `backed up to ${planned.backupPath.split('/').pop()}`;
379
+ return undefined;
380
+ }
381
+ /**
382
+ * The writes one command intends to make.
383
+ *
384
+ * A generator is handed the plan, enqueues its requests and returns; the command applies the
385
+ * plan once, at the end. That order is what makes `--dry-run` trustworthy — a generator has no
386
+ * opportunity to write outside {@link apply}, so there is one place to check rather than one
387
+ * per generator — and it is why a refusal is raised against the whole plan before any of it
388
+ * lands.
389
+ */
390
+ export class WritePlan {
391
+ #requests = [];
392
+ /** Enqueue a request. Returns the plan, so calls chain. */
393
+ add(request) {
394
+ this.#requests.push(request);
395
+ return this;
396
+ }
397
+ /** The enqueued requests, in enqueue order. */
398
+ get requests() {
399
+ return this.#requests;
400
+ }
401
+ /** How many requests are enqueued — for a caller reporting "nothing to do". */
402
+ get size() {
403
+ return this.#requests.length;
404
+ }
405
+ /**
406
+ * Check the whole plan, decide every outcome, then — unless `ctx.dryRun` — commit it, one
407
+ * request at a time in enqueue order, recording each through the reporter's action sink.
408
+ *
409
+ * Under `--dry-run` every outcome is still computed against the real filesystem, including
410
+ * which keys a `merge-json` would add, and recorded as the matching `would-` action; the
411
+ * returned {@link WriteResult.outcome} is `skipped` and `effect` still names what a real run
412
+ * would have done.
413
+ */
414
+ apply(ctx) {
415
+ const resolved = resolveRequests(this.#requests, ctx);
416
+ const planned = resolved.map((request) => planWrite(request, ctx));
417
+ const results = [];
418
+ for (const entry of planned) {
419
+ if (!ctx.dryRun)
420
+ commitWrite(entry);
421
+ const kinds = ctx.dryRun ? EFFECT_WOULD_ACTION : EFFECT_ACTION;
422
+ const detail = actionDetail(entry);
423
+ ctx.report.action({
424
+ kind: kinds[entry.effect],
425
+ path: displayPath(ctx.repoRoot, entry.request.path),
426
+ ...(detail === undefined ? {} : { detail }),
427
+ });
428
+ results.push({
429
+ label: entry.request.label,
430
+ path: entry.request.path,
431
+ policy: entry.request.policy,
432
+ outcome: ctx.dryRun ? 'skipped' : entry.effect,
433
+ effect: entry.effect,
434
+ ...(entry.added === undefined ? {} : { added: entry.added }),
435
+ ...(entry.backupPath === undefined ? {} : { backupPath: entry.backupPath }),
436
+ });
437
+ }
438
+ return results;
439
+ }
440
+ }
441
+ /**
442
+ * How a path is named in the action log: repo-relative when it is inside the repository, and
443
+ * absolute otherwise — which is exactly the `allowOutsideRepo` case, where the absolute path is
444
+ * the informative one.
445
+ */
446
+ export function displayPath(repoRoot, path) {
447
+ return insideRepo(repoRoot, path) ? relative(repoRoot, path) || '.' : path;
448
+ }
449
+ /**
450
+ * Can this directory be written into? `undefined` when yes, otherwise a one-line reason.
451
+ *
452
+ * `doctor` (roadmap item 13) asks this of the configured `stateDir`, because the only reliable
453
+ * answer is to try: a permission bit, a read-only mount and a full filesystem all look alike
454
+ * from a `stat`. It is deliberately outside {@link WritePlan} — it is a check, not a generated
455
+ * artifact — and it never runs as part of an `init`.
456
+ *
457
+ * **This is the engine's one removal.** `rmSync` is called on a single file with `force` and
458
+ * **without** `recursive`, so it cannot remove a directory: aimed at one, it fails rather than
459
+ * emptying it. It is in-process rather than shelled out, per guarantee 2 in the module header.
460
+ */
461
+ export function probeWritable(directory) {
462
+ const probe = join(directory, `write-probe-${process.pid}.tmp`);
463
+ try {
464
+ writeFileSync(probe, '', 'utf8');
465
+ }
466
+ catch (error) {
467
+ const detail = error instanceof Error ? error.message : String(error);
468
+ return `not writable: ${detail}`;
469
+ }
470
+ try {
471
+ rmSync(probe, { force: true });
472
+ }
473
+ catch (error) {
474
+ const detail = error instanceof Error ? error.message : String(error);
475
+ return `wrote a probe file but could not remove it (${probe}): ${detail}`;
476
+ }
477
+ return undefined;
478
+ }
479
+ //# sourceMappingURL=writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writer.js","sourceRoot":"","sources":["../../src/core/writer.ts"],"names":[],"mappings":"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"}