@hone-ai/cli 1.33.0 → 1.35.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/bin/hone-mcp.js +1 -0
- package/hone-cli.js +1289 -759
- package/lib/agent-eval-pacing.js +102 -0
- package/lib/agent-eval-probes-boundary.js +0 -0
- package/lib/agent-eval-probes-stateful.js +72 -0
- package/lib/agent-eval-score.js +70 -0
- package/lib/agent-executor.js +26 -4
- package/lib/asset-fetch-list.js +72 -0
- package/lib/auto-verify.js +67 -3
- package/lib/batch-follow.js +333 -0
- package/lib/block-grammar.js +126 -0
- package/lib/bundle-context.js +43 -4
- package/lib/bundle-paths.js +698 -1
- package/lib/bundle-source-dirs.js +79 -0
- package/lib/doctor-bind-default.js +192 -18
- package/lib/doctor-local-run.js +139 -1
- package/lib/emit-pr.js +39 -6
- package/lib/git-helpers.js +19 -8
- package/lib/hard-pause-remedy.js +101 -0
- package/lib/headroom.js +63 -0
- package/lib/include-paths.js +155 -0
- package/lib/learnings-parse.js +51 -6
- package/lib/materialize-diff.js +134 -64
- package/lib/mcp-tools.js +36 -11
- package/lib/pipeline-config.js +182 -0
- package/lib/private-temp.js +34 -0
- package/lib/promote-payload.js +77 -0
- package/lib/refresh-knowledge.js +15 -6
- package/lib/repo-tree.js +22 -3
- package/lib/run-context.js +698 -0
- package/lib/run-report.js +36 -0
- package/lib/sandbox-lifecycle.js +357 -0
- package/lib/schedule-cron.js +1 -1
- package/lib/skill-assertions.js +61 -7
- package/lib/skill-audit-render.js +1 -1
- package/lib/story-classifier-extract.js +16 -8
- package/lib/story-classifier.js +64 -0
- package/lib/story-compose.js +69 -0
- package/lib/story-line.js +40 -0
- package/lib/story-provenance.js +267 -0
- package/lib/timeout-option.js +42 -0
- package/lib/tree-artifact.js +166 -0
- package/lib/untrusted-code-env.js +51 -0
- package/lib/verdict-env.js +31 -0
- package/lib/verify-patch.js +74 -4
- package/lib/verify-pr.js +2 -1
- package/lib/worktree-provision.js +62 -2
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* untrusted-code-env.js — HC-GF-269. The environment for code Hone did not write.
|
|
4
|
+
*
|
|
5
|
+
* verify-patch, emit-pr and verify-pr run a shell command in a tree that holds step_4's patch — an
|
|
6
|
+
* LLM's output, and its tests — and auto-verify (HC-GF-064) runs it the moment a run pauses, before
|
|
7
|
+
* anyone has read it. The worktree isolates the FILES. The environment was the operator's whole
|
|
8
|
+
* shell. Measured on local run 7a4b09b2 (2026-09-24): `tests/e2e/dashboard.smoke.test.js:27` read
|
|
9
|
+
* `process.env.HONE_TOKEN` inside `hone verify-patch`, received the operator's local dogfood token and
|
|
10
|
+
* sent it to production — the target `ci.e2e` pins — which answered 401. Story text can come from a
|
|
11
|
+
* GitHub issue, so whoever can file one can try to steer the builder into a test that sends it
|
|
12
|
+
* somewhere else.
|
|
13
|
+
*
|
|
14
|
+
* The rule: Hone's own namespace never reaches code Hone did not write. Every `HONE_*` variable goes,
|
|
15
|
+
* by PREFIX rather than by list — the CLI reads six credential or endpoint variables today, and a
|
|
16
|
+
* list is out of date the day a seventh is added. The adopter's own variables all stay: database
|
|
17
|
+
* URLs, cloud profiles, proxies, toolchain. The cost is Hone's own SETTINGS an adopter's CI may read
|
|
18
|
+
* (`HONE_SOURCE_DIRS`, docs/sdlc/setup.md): exported in the operator's shell they no longer reach the
|
|
19
|
+
* command, so set them in the command itself — `ci.local_command` or `--command`.
|
|
20
|
+
*
|
|
21
|
+
* Sibling to `git-env.js` (variables that select a REPOSITORY) and `verdict-env.js` (variables that
|
|
22
|
+
* invert a RESULT), and separate for the same reason they are: this scrub withholds CREDENTIALS, and
|
|
23
|
+
* unlike those two it also applies to installs — a patched package.json's `postinstall` is code the
|
|
24
|
+
* builder wrote too.
|
|
25
|
+
*
|
|
26
|
+
* NOT A SANDBOX. The child still runs as the operator, with $HOME and the network: `~/.honerc` holds
|
|
27
|
+
* the same token and any test can read it. This closes the leak that was measured — a credential
|
|
28
|
+
* handed to generated code in its environment — not a patch written to steal one. HC-GF-270 keeps an
|
|
29
|
+
* outside party's patch from running unattended at all (cli/lib/story-provenance.js); OS isolation of
|
|
30
|
+
* the child remains a separate opt-in.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** Hone's namespace: the CLI's and the server's variables, credentials among them. */
|
|
34
|
+
const HONE_PREFIX = 'HONE_';
|
|
35
|
+
|
|
36
|
+
/** Case-folded: Windows resolves `process.env.HONE_TOKEN` against a key spelled `Hone_Token`. */
|
|
37
|
+
function isHoneVar(key) {
|
|
38
|
+
return String(key).toUpperCase().startsWith(HONE_PREFIX);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* @param {NodeJS.ProcessEnv} [base] Base env; defaults to process.env. Injectable for tests.
|
|
43
|
+
* @returns {NodeJS.ProcessEnv} a copy — the caller's environment is untouched
|
|
44
|
+
*/
|
|
45
|
+
function withoutHoneVars(base) {
|
|
46
|
+
const env = { ...(base || process.env) };
|
|
47
|
+
for (const key of Object.keys(env)) if (isHoneVar(key)) delete env[key];
|
|
48
|
+
return env;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = { withoutHoneVars, isHoneVar, HONE_PREFIX };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* verdict-env.js — HC-GF-144. The environment for a subprocess whose VERDICT we read.
|
|
4
|
+
*
|
|
5
|
+
* `node --test` exports NODE_TEST_CONTEXT and NODE_TEST_WORKER_ID to every process it spawns, and
|
|
6
|
+
* a nested `node --test` that inherits them exits 0 EVEN WHEN TESTS FAIL (measured: exit 1 with a
|
|
7
|
+
* clean env, exit 0 with the variable). Anything whose exit code is believed must run without them.
|
|
8
|
+
*
|
|
9
|
+
* Sibling to `git-env.js`, and deliberately separate: that one removes the variables that select a
|
|
10
|
+
* REPOSITORY, this one removes the variables that invert a RESULT. Merging them would apply each
|
|
11
|
+
* scrub where it is not wanted.
|
|
12
|
+
*
|
|
13
|
+
* NOT for subprocesses that exist to USE the operator's toolchain — pip, npm and playwright
|
|
14
|
+
* installs need the real environment (proxy, registry, an active virtualenv), and scrubbing it
|
|
15
|
+
* there is the bug rather than the fix.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Variables a parent test runner exports that flip a child runner's exit code. */
|
|
19
|
+
const VERDICT_FLIPPING_VARS = ['NODE_TEST_CONTEXT', 'NODE_TEST_WORKER_ID'];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {NodeJS.ProcessEnv} [base] Base env; defaults to process.env. Injectable for tests.
|
|
23
|
+
* @returns {NodeJS.ProcessEnv}
|
|
24
|
+
*/
|
|
25
|
+
function verdictEnv(base) {
|
|
26
|
+
const env = { ...(base || process.env) };
|
|
27
|
+
for (const key of VERDICT_FLIPPING_VARS) delete env[key];
|
|
28
|
+
return env;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { verdictEnv, VERDICT_FLIPPING_VARS };
|
package/lib/verify-patch.js
CHANGED
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
* implemented" — the existing tests may not cover the new behaviour.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
+
const { withoutHoneVars } = require('./untrusted-code-env');
|
|
22
|
+
|
|
21
23
|
/** Exit codes, matching the check-patch convention so scripts can branch. */
|
|
22
24
|
const EXIT = {
|
|
23
25
|
PASS: 0, // tests passed on the applied diff
|
|
@@ -28,6 +30,16 @@ const EXIT = {
|
|
|
28
30
|
// HC-GF-008: the command itself could not start, so nothing about the patch is known. Not 5 or 6:
|
|
29
31
|
// emit-pr re-exits with these codes, and those two are its BRANCH_CONFLICT and PUBLISH_FAILED.
|
|
30
32
|
UNRESOLVED: 7,
|
|
33
|
+
// HC-GF-201: the environment was incomplete, so nothing was observed about the diff.
|
|
34
|
+
// Distinct from TESTS_FAILED on purpose — emit-pr re-exits these codes, and a run that
|
|
35
|
+
// could not run is not a result (HC-CI-016).
|
|
36
|
+
NOT_RUN: 8,
|
|
37
|
+
// HC-GF-270: this run's story text came from a source Hone does not trust, so the patch was
|
|
38
|
+
// never executed and NO verdict was reported. A refusal, not a failure — nothing is known
|
|
39
|
+
// about the diff. Pass --trust-source to run it having read it.
|
|
40
|
+
// MUST stay numerically identical to emit-pr.js's EXIT.UNTRUSTED_SOURCE: emit-pr re-exits
|
|
41
|
+
// these codes, so the two maps are byte-compatible by contract.
|
|
42
|
+
UNTRUSTED_SOURCE: 9,
|
|
31
43
|
};
|
|
32
44
|
|
|
33
45
|
/**
|
|
@@ -53,6 +65,16 @@ function _npmTestScript(cwd) {
|
|
|
53
65
|
return (typeof t === 'string' && t.trim() && !/no test specified/i.test(t)) ? 'npm test' : null;
|
|
54
66
|
} catch { return null; }
|
|
55
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* HC-GF-223: the sources that are a DECLARATION rather than this resolver's own guess. Overriding a
|
|
70
|
+
* declared command narrows the evidence; overriding the terminal `make ci` guess supplies evidence
|
|
71
|
+
* where there was none, and must not be penalised (HC-GF-008 named that tier a guess for the same
|
|
72
|
+
* reason).
|
|
73
|
+
*/
|
|
74
|
+
const DECLARED_SOURCES = [
|
|
75
|
+
'ci.local_command (config file)', 'run config', 'Makefile ci target', 'package.json test script',
|
|
76
|
+
];
|
|
77
|
+
|
|
56
78
|
/**
|
|
57
79
|
* HC-COMM-010-followup-7: tiered resolution. Without `opts.cwd` this is the
|
|
58
80
|
* original pure function (explicit → configured → `make ci`). With `opts.cwd`:
|
|
@@ -63,11 +85,21 @@ function _npmTestScript(cwd) {
|
|
|
63
85
|
* 4. `make ci` if the Makefile has a ci target
|
|
64
86
|
* 5. `npm test` if package.json has a real test script
|
|
65
87
|
* 6. `make ci` (documented default; fails loudly)
|
|
66
|
-
* @returns {{command:string, source:string}}
|
|
88
|
+
* @returns {{command:string, source:string, replaced?:{command:string,source:string}|null}}
|
|
89
|
+
* `replaced` (HC-GF-223) is what an explicit --command displaced, when that was a declaration.
|
|
67
90
|
*/
|
|
68
91
|
function resolveCommandDetailed(explicit, configured, opts = {}) {
|
|
69
92
|
const e = (typeof explicit === 'string' ? explicit : '').trim();
|
|
70
|
-
if (e)
|
|
93
|
+
if (e) {
|
|
94
|
+
// What `--command` replaced. An operator narrows this command because the configured one cannot
|
|
95
|
+
// run in the worktree verify-patch creates, and a subset that passes is not a verification of
|
|
96
|
+
// the change — so the caller needs to know what did NOT run, not just what did.
|
|
97
|
+
const base = resolveCommandDetailed(null, configured, opts);
|
|
98
|
+
const replaced = DECLARED_SOURCES.includes(base.source) && base.command !== e
|
|
99
|
+
? { command: base.command, source: base.source }
|
|
100
|
+
: null;
|
|
101
|
+
return { command: e, source: '--command', replaced };
|
|
102
|
+
}
|
|
71
103
|
const cwd = opts && opts.cwd;
|
|
72
104
|
if (cwd) {
|
|
73
105
|
try {
|
|
@@ -145,10 +177,15 @@ function commandAbsence(command, { code, stdout = '', stderr = '' } = {}) {
|
|
|
145
177
|
* enforce approves the gate.
|
|
146
178
|
* The verified command is the adopter's own top-level invocation: level 0, no inherited flags. Returns a
|
|
147
179
|
* copy; the caller's environment is untouched.
|
|
180
|
+
*
|
|
181
|
+
* HC-GF-269: and none of Hone's own variables. The command runs the builder's patch and tests, so it
|
|
182
|
+
* gets no `HONE_*` credential or endpoint (see untrusted-code-env.js). The scrub runs LAST, after the
|
|
183
|
+
* caller has merged in whatever it publishes on purpose (HC-GF-192's `plan.env`, HC-GF-193's
|
|
184
|
+
* `plan.controlEnv`) — so a sandbox cannot be used to hand a Hone credential to generated code either.
|
|
148
185
|
*/
|
|
149
186
|
const INHERITED_MAKE_VARS = ['MAKELEVEL', 'MAKEFLAGS', 'GNUMAKEFLAGS'];
|
|
150
187
|
function verifyCommandEnv(base = process.env) {
|
|
151
|
-
const env =
|
|
188
|
+
const env = withoutHoneVars(base);
|
|
152
189
|
for (const key of INHERITED_MAKE_VARS) delete env[key];
|
|
153
190
|
return env;
|
|
154
191
|
}
|
|
@@ -162,11 +199,43 @@ function verifyCommandEnv(base = process.env) {
|
|
|
162
199
|
* @param {{ code: number|null, timedOut: boolean, stdout?: string, stderr?: string, command?: string, source?: string }} run
|
|
163
200
|
* @returns {{ verdict: 'pass'|'fail'|'timeout'|'unresolved', exitCode: number, detail: string }}
|
|
164
201
|
*/
|
|
165
|
-
function verdictFromRun(run) {
|
|
202
|
+
function verdictFromRun(run, provisioning = null) {
|
|
203
|
+
// HC-GF-201: provisioning state is an INPUT to the verdict, not a side note. The
|
|
204
|
+
// run that exposed this printed `provisioning: linked node_modules` honestly and
|
|
205
|
+
// then blamed the patch for the roots it had not linked, because this function had
|
|
206
|
+
// no parameter for it and the `ok:false` was discarded before the verdict was computed.
|
|
207
|
+
if (provisioning && provisioning.ok === false) {
|
|
208
|
+
const roots = (provisioning.unprovisioned || []).map((u) => u.root).join(', ');
|
|
209
|
+
return {
|
|
210
|
+
verdict: 'not_run',
|
|
211
|
+
exitCode: EXIT.NOT_RUN,
|
|
212
|
+
detail: `environment incomplete — could not provision: ${roots}. `
|
|
213
|
+
+ 'Nothing was observed about this change.',
|
|
214
|
+
};
|
|
215
|
+
}
|
|
166
216
|
if (run.timedOut) {
|
|
167
217
|
return { verdict: 'timeout', exitCode: EXIT.TIMEOUT, detail: 'test command exceeded the timeout' };
|
|
168
218
|
}
|
|
169
219
|
if (run.code === 0) {
|
|
220
|
+
// HC-GF-223: a pass under an operator-supplied command is evidence about a SUBSET. Measured on
|
|
221
|
+
// run `437246a7`: `--command "node --test <3 files>"` reported `pass` to the server and the same
|
|
222
|
+
// diff then failed `make ci` with 6 failures, from Redis state the subset could not see. The
|
|
223
|
+
// enforce gate (HC-GF-099) was satisfied by a verification that examined a third of the surface.
|
|
224
|
+
//
|
|
225
|
+
// The EXIT CODE stays PASS on purpose: the tests that ran did pass, and changing what exit 0
|
|
226
|
+
// means would silently change the contract for every adopter and every chained command (the same
|
|
227
|
+
// argument HC-GF-026 makes for lint). The VERDICT is what the server gates on, and both readers
|
|
228
|
+
// — `orchestrate.js` (`isGreen = verdict === 'pass'`) and `verification-refusal.js` — treat any
|
|
229
|
+
// other value as not green, so naming it here is what closes the false pass.
|
|
230
|
+
if (run.replaced && run.replaced.command) {
|
|
231
|
+
return {
|
|
232
|
+
verdict: 'pass_narrowed',
|
|
233
|
+
exitCode: EXIT.PASS,
|
|
234
|
+
detail: `tests passed under an operator-supplied command, but \`${run.replaced.command}\` `
|
|
235
|
+
+ `(${run.replaced.source}) did not run — this is evidence about a subset, not a `
|
|
236
|
+
+ 'verification of the change',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
170
239
|
return { verdict: 'pass', exitCode: EXIT.PASS, detail: 'tests passed on the applied diff' };
|
|
171
240
|
}
|
|
172
241
|
const absent = commandAbsence(run.command, run);
|
|
@@ -348,6 +417,7 @@ function classifyGateRun(run) {
|
|
|
348
417
|
}
|
|
349
418
|
|
|
350
419
|
module.exports = {
|
|
420
|
+
DECLARED_SOURCES,
|
|
351
421
|
EXIT, resolveCommand, resolveCommandDetailed, DEFAULT_COMMAND,
|
|
352
422
|
AUX_GATES, AUX_DETECT, resolveAuxGates, classifyGateRun,
|
|
353
423
|
verdictFromRun, commandAbsence, verifyCommandEnv, lastMeaningfulLine, summarizeFailure, FAILURE_LINE_RE,
|
package/lib/verify-pr.js
CHANGED
|
@@ -21,11 +21,12 @@
|
|
|
21
21
|
|
|
22
22
|
/** Exit codes, extending the check/verify-patch convention. */
|
|
23
23
|
const EXIT = {
|
|
24
|
-
PASS: 0, // all required CI gates green
|
|
24
|
+
PASS: 0, // all required CI gates green, and no BLOCKER skill assertion fired
|
|
25
25
|
NO_PR: 1, // could not resolve a PR / branch to verify
|
|
26
26
|
GATE_RED: 2, // a required CI gate is failing
|
|
27
27
|
GATE_PENDING: 3, // checks did not reach a terminal state within the timeout
|
|
28
28
|
ERROR: 4, // gh / command execution error
|
|
29
|
+
SKILL_BLOCKER: 5, // CI passed, but a BLOCKER skill assertion fired in `hone step-5b` (HC-GF-227)
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
const GATE_MODES = ['github', 'local', 'both', 'none'];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
*/
|
|
14
14
|
const fs = require('node:fs');
|
|
15
15
|
const path = require('node:path');
|
|
16
|
+
const { withoutHoneVars } = require('./untrusted-code-env');
|
|
16
17
|
|
|
17
18
|
const LOCKFILES = [
|
|
18
19
|
['package-lock.json', 'npm ci --no-audit --no-fund'],
|
|
@@ -104,7 +105,7 @@ function planProvision({ worktreeDir, parentDir, installCommand = null, fsMod =
|
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
/** @returns {{ok:boolean, did:'none'|'link'|'install', summary:string, command?:string}} */
|
|
107
|
-
function provisionWorktree(plan, { worktreeDir, exec, fsMod = fs }) {
|
|
108
|
+
function provisionWorktree(plan, { worktreeDir, exec, fsMod = fs, env = process.env }) {
|
|
108
109
|
if (!plan || plan.kind === 'none') return { ok: true, did: 'none', summary: `skipped (${plan ? plan.reason : 'no plan'})` };
|
|
109
110
|
// HC-GF-024: recognised-but-unautomated. ok:true keeps the fail-open contract (the
|
|
110
111
|
// test command still runs), but the summary names the manifest so a later
|
|
@@ -120,14 +121,73 @@ function provisionWorktree(plan, { worktreeDir, exec, fsMod = fs }) {
|
|
|
120
121
|
} catch (e) { return { ok: false, did: 'link', summary: `link failed (${e.message}) — tests run unprovisioned` }; }
|
|
121
122
|
}
|
|
122
123
|
try {
|
|
123
|
-
|
|
124
|
+
// HC-GF-269: the install runs the PATCHED manifest, so a `postinstall` step_4 wrote runs here —
|
|
125
|
+
// before the test command, and whether or not it passes. It keeps the operator's toolchain
|
|
126
|
+
// (proxy, registry, virtualenv: HC-GF-144) and loses only Hone's own `HONE_*` variables.
|
|
127
|
+
const run = exec(plan.command, { cwd: worktreeDir, shell: true, encoding: 'utf8', timeout: INSTALL_TIMEOUT_MS, stdio: ['ignore', 'pipe', 'pipe'], env: withoutHoneVars(env) });
|
|
124
128
|
const ok = run && run.status === 0;
|
|
125
129
|
const tail = ok ? '' : ` — ${String((run && (run.stderr || run.stdout)) || '').trim().split('\n').filter(Boolean).pop() || 'install failed'}`;
|
|
126
130
|
return { ok, did: 'install', command: plan.command, summary: `${ok ? 'ran' : 'FAILED'} \`${plan.command}\` (${plan.reason})${tail}` };
|
|
127
131
|
} catch (e) { return { ok: false, did: 'install', command: plan.command, summary: `install threw (${e.message}) — tests run unprovisioned` }; }
|
|
128
132
|
}
|
|
129
133
|
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Every directory in `dir` that owns a package manifest — HC-GF-201.
|
|
137
|
+
*
|
|
138
|
+
* `planProvision` tests exactly one directory, and this repo has three roots:
|
|
139
|
+
* `node_modules`, `cli/node_modules`, `server/node_modules`. The two it skipped are
|
|
140
|
+
* where `ci.unit` runs (`node --test cli/lib/*.test.js`), so a worktree built by
|
|
141
|
+
* `verify-patch` ran those tests against absent modules and the resulting
|
|
142
|
+
* `Cannot find module` lines were counted as the PATCH failing tests.
|
|
143
|
+
*
|
|
144
|
+
* Bounded depth on purpose: a manifest inside `node_modules` is a dependency's, not
|
|
145
|
+
* a root of the repo under test.
|
|
146
|
+
*/
|
|
147
|
+
function discoverPackageRoots(dir, { fsMod = fs, maxDepth = 3 } = {}) {
|
|
148
|
+
const SKIP = new Set(['node_modules', '.git', '.hg', 'dist', 'build', 'coverage', 'vendor']);
|
|
149
|
+
const out = [];
|
|
150
|
+
const walk = (rel, depth) => {
|
|
151
|
+
const abs = rel === '.' ? dir : path.join(dir, rel);
|
|
152
|
+
let entries = [];
|
|
153
|
+
try { entries = fsMod.readdirSync(abs, { withFileTypes: true }); } catch { return; }
|
|
154
|
+
if (entries.some((e) => e.isFile() && e.name === 'package.json')) out.push(rel);
|
|
155
|
+
if (depth >= maxDepth) return;
|
|
156
|
+
for (const e of entries) {
|
|
157
|
+
if (!e.isDirectory() || SKIP.has(e.name) || e.name.startsWith('.')) continue;
|
|
158
|
+
walk(rel === '.' ? e.name : path.join(rel, e.name), depth + 1);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
walk('.', 0);
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Provision every discovered root, reporting the ones we MEANT to provision and could not.
|
|
167
|
+
*
|
|
168
|
+
* The distinction is load-bearing and was named in this story's step_1 plan: `kind:'none'`
|
|
169
|
+
* (no manifest) and `kind:'unsupported'` (Python/Go/Rust — deliberately not automated) mean
|
|
170
|
+
* there is nothing to do, NOT that provisioning failed. Counting them would make every
|
|
171
|
+
* non-Node adopter's every run report NOT RUN forever.
|
|
172
|
+
*/
|
|
173
|
+
function provisionAllRoots(entries, { provision = provisionWorktree } = {}) {
|
|
174
|
+
const results = [];
|
|
175
|
+
const unprovisioned = [];
|
|
176
|
+
for (const { root, plan, worktreeDir, exec } of entries) {
|
|
177
|
+
if (plan.kind === 'none' || plan.kind === 'unsupported') {
|
|
178
|
+
results.push({ root, did: plan.kind, summary: plan.reason || plan.kind });
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
const r = provision(plan, { worktreeDir, exec, root });
|
|
182
|
+
results.push({ root, did: r.did || 'failed', summary: r.summary });
|
|
183
|
+
if (!r.ok) unprovisioned.push({ root, reason: r.summary || 'provisioning failed' });
|
|
184
|
+
}
|
|
185
|
+
return { ok: unprovisioned.length === 0, results, unprovisioned };
|
|
186
|
+
}
|
|
187
|
+
|
|
130
188
|
module.exports = {
|
|
189
|
+
discoverPackageRoots,
|
|
190
|
+
provisionAllRoots,
|
|
131
191
|
planProvision, provisionWorktree,
|
|
132
192
|
LOCKFILES, RECOGNISED_MANIFESTS, ALL_MANIFEST_NAMES, INSTALL_TIMEOUT_MS,
|
|
133
193
|
};
|