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.
- package/LICENSE +201 -0
- package/NOTICE +7 -0
- package/README.md +24 -0
- package/dist/cli.js +194 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +561 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/daemon.js +791 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/doctor.js +336 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.js +2023 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/registry.js +42 -0
- package/dist/commands/registry.js.map +1 -0
- package/dist/config/check.js +505 -0
- package/dist/config/check.js.map +1 -0
- package/dist/config/io.js +177 -0
- package/dist/config/io.js.map +1 -0
- package/dist/config/model.js +406 -0
- package/dist/config/model.js.map +1 -0
- package/dist/core/errors.js +71 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/git.js +537 -0
- package/dist/core/git.js.map +1 -0
- package/dist/core/json.js +125 -0
- package/dist/core/json.js.map +1 -0
- package/dist/core/layerCoverage.js +141 -0
- package/dist/core/layerCoverage.js.map +1 -0
- package/dist/core/layerGapRemedy.js +62 -0
- package/dist/core/layerGapRemedy.js.map +1 -0
- package/dist/core/nameList.js +23 -0
- package/dist/core/nameList.js.map +1 -0
- package/dist/core/paths.js +153 -0
- package/dist/core/paths.js.map +1 -0
- package/dist/core/prompt.js +206 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/repoPaths.js +55 -0
- package/dist/core/repoPaths.js.map +1 -0
- package/dist/core/report.js +150 -0
- package/dist/core/report.js.map +1 -0
- package/dist/core/templating.js +88 -0
- package/dist/core/templating.js.map +1 -0
- package/dist/core/writer.js +479 -0
- package/dist/core/writer.js.map +1 -0
- package/dist/daemon/backend.js +180 -0
- package/dist/daemon/backend.js.map +1 -0
- package/dist/daemon/units.js +380 -0
- package/dist/daemon/units.js.map +1 -0
- package/dist/detect/nestedApplication.js +79 -0
- package/dist/detect/nestedApplication.js.map +1 -0
- package/dist/detect/presets.js +2033 -0
- package/dist/detect/presets.js.map +1 -0
- package/dist/detect/signals.js +1368 -0
- package/dist/detect/signals.js.map +1 -0
- package/dist/doctor/checks.js +3530 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/generators/claudeContext.js +588 -0
- package/dist/generators/claudeContext.js.map +1 -0
- package/dist/generators/githooks.js +446 -0
- package/dist/generators/githooks.js.map +1 -0
- package/dist/generators/harnessConfig.js +632 -0
- package/dist/generators/harnessConfig.js.map +1 -0
- package/dist/generators/notifications.js +191 -0
- package/dist/generators/notifications.js.map +1 -0
- package/dist/generators/outerLoopScripts.js +165 -0
- package/dist/generators/outerLoopScripts.js.map +1 -0
- package/dist/generators/permissionProfile.js +1172 -0
- package/dist/generators/permissionProfile.js.map +1 -0
- package/dist/generators/projectSettings.js +322 -0
- package/dist/generators/projectSettings.js.map +1 -0
- package/dist/generators/repoRoot.js +417 -0
- package/dist/generators/repoRoot.js.map +1 -0
- package/dist/generators/scripts.js +557 -0
- package/dist/generators/scripts.js.map +1 -0
- package/dist/generators/stateDir.js +221 -0
- package/dist/generators/stateDir.js.map +1 -0
- package/dist/machine/paths.js +111 -0
- package/dist/machine/paths.js.map +1 -0
- package/dist/machine/plugins.js +224 -0
- package/dist/machine/plugins.js.map +1 -0
- package/dist/machine/registry.js +330 -0
- package/dist/machine/registry.js.map +1 -0
- package/package.json +23 -0
- package/scripts/README.md +13 -0
- package/scripts/daemon/launchd.plist.template +59 -0
- package/scripts/daemon/systemd.service.template +58 -0
- package/templates/README.md +15 -0
- package/templates/claude/CLAUDE.md +54 -0
- package/templates/claude/README.md +5 -0
- package/templates/claude/context/api.md +29 -0
- package/templates/claude/context/conventions.md +23 -0
- package/templates/claude/context/data-layer.md +28 -0
- package/templates/claude/context/data-storage.md +29 -0
- package/templates/claude/context/docs-catalog.md +29 -0
- package/templates/claude/context/domain.md +28 -0
- package/templates/claude/context/layer.md +20 -0
- package/templates/claude/context/module.md +30 -0
- package/templates/claude/context/package.md +29 -0
- package/templates/claude/context/presentation.md +32 -0
- package/templates/claude/context/state-slices.md +28 -0
- package/templates/claude/context/tests.md +28 -0
- package/templates/claude/harness-task-offer.md +58 -0
- package/templates/claude/push-notify.env.example +21 -0
- package/templates/claude/qa-accounts.env.example +38 -0
- package/templates/claude/qa_test_scenarios.md +110 -0
- package/templates/claude/settings.autonomous.json +93 -0
- package/templates/claude/settings.autonomous.qa.json +36 -0
- package/templates/githooks/README.md +3 -0
- package/templates/githooks/pre-push +72 -0
- package/templates/repo/README.md +3 -0
- package/templates/repo/gitattributes +16 -0
- package/templates/repo/gitignore +61 -0
- package/templates/repo/gitignore.qa +25 -0
- package/templates/repo/mcp.json +17 -0
- package/templates/scripts/README.md +5 -0
- package/templates/scripts/autonomous-format-stream.sh +95 -0
- package/templates/scripts/autonomous-notify.sh +337 -0
- package/templates/scripts/autonomous-watcher.sh +3087 -0
- package/templates/scripts/cleanup-merged-worktrees.sh +327 -0
- package/templates/scripts/commit-on-branch.sh +288 -0
- package/templates/scripts/create-worktree.sh +360 -0
- package/templates/scripts/deploy.sh +47 -0
- package/templates/scripts/lib/harness-run-lib.sh +1481 -0
- package/templates/scripts/push-branch.sh +140 -0
- package/templates/scripts/refresh-branch.sh +244 -0
- package/templates/scripts/restart-watcher.sh +401 -0
- package/templates/scripts/scratch-run.sh +302 -0
- package/templates/scripts/setup-worktree.sh +262 -0
- package/templates/scripts/start-dev-server.sh +99 -0
- package/templates/scripts/test.sh +50 -0
- package/templates/scripts/typecheck.sh +50 -0
- package/templates/state-dir/README-root.md +13 -0
- package/templates/state-dir/README.md +9 -0
- package/templates/state-dir/architecture_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/architecture_branch_reviews/README.md +9 -0
- package/templates/state-dir/architecture_reviews/README.md +9 -0
- package/templates/state-dir/architecture_user_review_reviews/README.md +9 -0
- package/templates/state-dir/autonomous_inbox/README.md +9 -0
- package/templates/state-dir/autonomous_logs/README.md +9 -0
- package/templates/state-dir/branch_statistics/README.md +9 -0
- package/templates/state-dir/business_parity_branch_review_point_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_branch_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_reviews/README.md +9 -0
- package/templates/state-dir/business_parity_user_review_reviews/README.md +9 -0
- package/templates/state-dir/clarification_digests/README.md +9 -0
- package/templates/state-dir/clarifications/README.md +9 -0
- package/templates/state-dir/code_reviews/README.md +9 -0
- package/templates/state-dir/dispatch_additions/README.md +19 -0
- package/templates/state-dir/docs_catalog/README.md +9 -0
- package/templates/state-dir/flow_progress/README.md +9 -0
- package/templates/state-dir/improvement_observations/README.md +19 -0
- package/templates/state-dir/improvement_suggestions.md +29 -0
- package/templates/state-dir/lessons.md +23 -0
- package/templates/state-dir/qa_review_point_reviews/README.md +9 -0
- package/templates/state-dir/qa_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/review_plan_reviews/README.md +9 -0
- package/templates/state-dir/scratch/README.md +11 -0
- package/templates/state-dir/skeptic_review_plan_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_review_point_reviews/README.md +9 -0
- package/templates/state-dir/skeptic_reviews/README.md +9 -0
- package/templates/state-dir/story_plans/README.md +9 -0
- package/templates/state-dir/task_plan_point_reviews/README.md +9 -0
- package/templates/state-dir/task_plan_reviews/README.md +9 -0
- package/templates/state-dir/task_plans/README.md +9 -0
- package/templates/state-dir/task_prompts/README.md +9 -0
- package/templates/state-dir/ui_test_plan_reviews/README.md +9 -0
- package/templates/state-dir/ui_test_plans/README.md +9 -0
- package/templates/state-dir/user_review_fix_plan_point_reviews/README.md +9 -0
- 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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"}
|