agentic-workflow-manager 9.7.0 → 9.8.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/README.md +5 -1
- package/dist/src/commands/doctor.js +4 -2
- package/dist/src/commands/export.js +3 -0
- package/dist/src/commands/init.js +10 -2
- package/dist/src/commands/job/exec-wrapper.js +53 -3
- package/dist/src/commands/plan/index.js +124 -6
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +7 -5
- package/dist/src/commands/registry/install-bundles.js +4 -4
- package/dist/src/commands/registry/status.js +4 -4
- package/dist/src/commands/sensors/bootstrap.js +36 -5
- package/dist/src/commands/sensors/compatibility/contract.js +19 -4
- package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
- package/dist/src/commands/sensors/compatibility/live.js +18 -1
- package/dist/src/commands/sensors/compatibility/probe.js +8 -2
- package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
- package/dist/src/commands/sensors/coverage/evidence.js +19 -10
- package/dist/src/commands/sensors/coverage/index.js +11 -2
- package/dist/src/commands/sensors/coverage/resolve.js +10 -0
- package/dist/src/commands/sensors/exec.js +16 -1
- package/dist/src/commands/sensors/migrate.js +62 -2
- package/dist/src/commands/sensors/prepare.js +2 -0
- package/dist/src/commands/sensors/result.js +2 -0
- package/dist/src/commands/sensors/run.js +64 -1
- package/dist/src/commands/sensors/status.js +6 -4
- package/dist/src/commands/sync.js +2 -1
- package/dist/src/commands/update.js +3 -1
- package/dist/src/commands/watch/archive-unused.js +138 -0
- package/dist/src/commands/watch/generations.js +32 -9
- package/dist/src/commands/watch/index.js +108 -2
- package/dist/src/commands/watch/init.js +149 -18
- package/dist/src/commands/watch/supervisor.js +167 -13
- package/dist/src/commands/watch/teardown-driver.js +13 -13
- package/dist/src/commands/watch/track-state.js +59 -0
- package/dist/src/commands/watch/tracks.js +38 -102
- package/dist/src/core/admission/index.js +181 -0
- package/dist/src/core/admission/registry-contracts.js +290 -0
- package/dist/src/core/bundle-install.js +2 -2
- package/dist/src/core/bundles.js +92 -19
- package/dist/src/core/diagnostics/context.js +1 -1
- package/dist/src/core/export/index.js +1 -1
- package/dist/src/core/export/resolve.js +2 -2
- package/dist/src/core/journal/fingerprint.js +35 -1
- package/dist/src/core/journal/paths.js +11 -0
- package/dist/src/core/journal/store.js +146 -4
- package/dist/src/core/journal/types.js +35 -1
- package/dist/src/core/migration/historical-provenance.js +18 -0
- package/dist/src/core/migration/index.js +322 -0
- package/dist/src/core/plan/identity.js +75 -0
- package/dist/src/core/plan/validate.js +178 -64
- package/dist/src/core/reconciliation.js +1 -1
- package/dist/src/index.js +46 -39
- package/dist/src/utils/grouping.js +1 -1
- package/dist/src/utils/registry-view.js +1 -1
- package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
- package/dist/tests/commands/export.test.js +28 -2
- package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +24 -10
- package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
- package/dist/tests/commands/pin.test.js +23 -23
- package/dist/tests/commands/plan/index.test.js +630 -14
- package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
- package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
- package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
- package/dist/tests/commands/sensors/exec.test.js +112 -0
- package/dist/tests/commands/sensors/run-changed.test.js +11 -0
- package/dist/tests/commands/sensors/run.test.js +15 -2
- package/dist/tests/commands/sensors/status.test.js +3 -3
- package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
- package/dist/tests/commands/watch/generations.test.js +113 -4
- package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
- package/dist/tests/commands/watch/track-finalize.test.js +20 -3
- package/dist/tests/commands/watch/track-freeze.test.js +43 -30
- package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
- package/dist/tests/commands/watch/watch-init.test.js +244 -0
- package/dist/tests/core/admission/index.test.js +87 -0
- package/dist/tests/core/bundle-install.test.js +36 -0
- package/dist/tests/core/bundles-multiroot.test.js +24 -2
- package/dist/tests/core/bundles-overrides.test.js +10 -7
- package/dist/tests/core/bundles.test.js +75 -6
- package/dist/tests/core/diagnostics/context.test.js +2 -2
- package/dist/tests/core/export/engine.test.js +1 -1
- package/dist/tests/core/export/resolve.test.js +1 -1
- package/dist/tests/core/init/mutation-targets.test.js +1 -1
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +1 -1
- package/dist/tests/core/journal/fingerprint.test.js +22 -0
- package/dist/tests/core/journal/store.test.js +53 -0
- package/dist/tests/core/journal/types.test.js +7 -0
- package/dist/tests/core/migration/facts.test.js +289 -0
- package/dist/tests/core/migration/historical-provenance.test.js +25 -0
- package/dist/tests/core/plan/validate.test.js +320 -6
- package/dist/tests/core/reconciliation.test.js +1 -1
- package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
- package/dist/tests/integration/pack-e2e.test.js +4 -1
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
- package/dist/tests/providers/injection-config.test.js +14 -6
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
- package/dist/tests/structural/native-release-artifacts.test.js +16 -0
- package/dist/tests/utils/grouping.test.js +2 -2
- package/dist/tests/utils/registry-view.test.js +7 -7
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
package/README.md
CHANGED
|
@@ -41,9 +41,13 @@ This generates the runnable output under `dist/index.js`.
|
|
|
41
41
|
### 4. Running the Tests
|
|
42
42
|
Before submitting a PR, ensure that the core discovery/executor functionality passes the existing test suite:
|
|
43
43
|
```bash
|
|
44
|
+
npm run native:build
|
|
44
45
|
npm test
|
|
45
46
|
```
|
|
46
|
-
|
|
47
|
+
`native:build` creates the ignored, host-specific `secure-fs` artifact required
|
|
48
|
+
by the integration fixtures; it must be run again after a clean checkout or a
|
|
49
|
+
change under `native/`. The goal is to maintain 100% passing tests for any new
|
|
50
|
+
CLI parameter flags.
|
|
47
51
|
|
|
48
52
|
### 5. Local Execution (Testing changes locally)
|
|
49
53
|
|
|
@@ -19,6 +19,7 @@ const collect_1 = require("../core/dashboard/collect");
|
|
|
19
19
|
const render_html_1 = require("../core/dashboard/render-html");
|
|
20
20
|
const render_terminal_1 = require("../core/dashboard/render-terminal");
|
|
21
21
|
const write_html_1 = require("../core/dashboard/write-html");
|
|
22
|
+
const bundles_1 = require("../core/bundles");
|
|
22
23
|
function glyph(status) {
|
|
23
24
|
if (status === 'ok')
|
|
24
25
|
return picocolors_1.default.green('✔');
|
|
@@ -127,6 +128,7 @@ function renderProviderReport(report) {
|
|
|
127
128
|
}
|
|
128
129
|
function runDoctor(opts = {}) {
|
|
129
130
|
const resolveTargets = opts.resolveTargets ?? agent_targets_1.resolveAgentTargets;
|
|
131
|
+
const reporter = opts.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
|
|
130
132
|
const invalid = (message) => { process.stderr.write(`awm doctor: ${message}\n`); return 2; };
|
|
131
133
|
const htmlRequested = opts.html !== undefined;
|
|
132
134
|
if (opts.json && opts.full)
|
|
@@ -143,7 +145,7 @@ function runDoctor(opts = {}) {
|
|
|
143
145
|
const target = htmlRequested ? (0, write_html_1.resolveHtmlTarget)({ cwd, target: opts.html, force: opts.force }) : undefined;
|
|
144
146
|
const targets = resolveTargets({ prefs: (0, config_1.readPreferences)(), explicit: opts.agent });
|
|
145
147
|
const collectSnapshot = opts.collectSnapshot ?? collect_1.collectDashboardSnapshot;
|
|
146
|
-
const context = (0, context_1.gatherContext)({ cwd, agents: targets });
|
|
148
|
+
const context = (0, context_1.gatherContext)({ cwd, agents: targets, reporter });
|
|
147
149
|
const snapshot = collectSnapshot({ cwd, now: new Date().toISOString(), adapters: {
|
|
148
150
|
...(0, collect_1.productionDashboardAdapters)(context),
|
|
149
151
|
} });
|
|
@@ -175,7 +177,7 @@ function runDoctor(opts = {}) {
|
|
|
175
177
|
}
|
|
176
178
|
let report;
|
|
177
179
|
try {
|
|
178
|
-
const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets });
|
|
180
|
+
const ctx = (0, context_1.gatherContext)({ cwd: opts.cwd, agents: targets, reporter });
|
|
179
181
|
const providers = ctx.providers ?? [];
|
|
180
182
|
report = { providers, overall: (0, checks_1.computeProviderOverall)(providers) };
|
|
181
183
|
}
|
|
@@ -8,14 +8,17 @@ exports.registerExportCommand = registerExportCommand;
|
|
|
8
8
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const export_1 = require("../core/export");
|
|
11
|
+
const bundles_1 = require("../core/bundles");
|
|
11
12
|
function runExportCommand(name, flags, deps = {}) {
|
|
12
13
|
const log = deps.log ?? console.log;
|
|
14
|
+
const reporter = deps.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
|
|
13
15
|
const opts = {
|
|
14
16
|
name,
|
|
15
17
|
target: flags.target,
|
|
16
18
|
out: flags.out,
|
|
17
19
|
roots: deps.roots,
|
|
18
20
|
zip: deps.zip,
|
|
21
|
+
reporter,
|
|
19
22
|
};
|
|
20
23
|
const summary = (0, export_1.runExport)(opts);
|
|
21
24
|
log(picocolors_1.default.dim(`Exported ${summary.kind} "${name}"`));
|
|
@@ -138,6 +138,14 @@ async function runInit(opts = {}) {
|
|
|
138
138
|
(0, paths_1.noteWindowsCaveat)((m) => console.log(picocolors_1.default.dim(`ℹ ${m}`)));
|
|
139
139
|
}
|
|
140
140
|
const cwd = opts.cwd ?? process.cwd();
|
|
141
|
+
const report = opts.reporter ?? (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
|
|
142
|
+
const reportedDiagnostics = new Set();
|
|
143
|
+
const reporter = (diagnostic) => {
|
|
144
|
+
if (reportedDiagnostics.has(diagnostic))
|
|
145
|
+
return;
|
|
146
|
+
reportedDiagnostics.add(diagnostic);
|
|
147
|
+
report(diagnostic);
|
|
148
|
+
};
|
|
141
149
|
const agent = opts.agent === undefined ? 'claude-code' : (0, providers_1.requireAgentTarget)(opts.agent);
|
|
142
150
|
// R2: gate BEFORE anything is read or written — an unsupported provider
|
|
143
151
|
// version must never touch preferences.json or any provider file.
|
|
@@ -190,7 +198,7 @@ async function runInit(opts = {}) {
|
|
|
190
198
|
// that exact run won't roll those specific artifacts back — every
|
|
191
199
|
// other target (preferences, hook, injection, previously-installed
|
|
192
200
|
// bundle content) is covered.
|
|
193
|
-
const preSyncBundles = (0, bundles_1.discoverAllBundles)();
|
|
201
|
+
const preSyncBundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
|
|
194
202
|
const mutationTargets = (0, mutation_targets_1.planInitMutationTargets)({
|
|
195
203
|
cwd,
|
|
196
204
|
agent,
|
|
@@ -207,7 +215,7 @@ async function runInit(opts = {}) {
|
|
|
207
215
|
// step's failure instead of being reported as its own cause.
|
|
208
216
|
(0, registries_1.assertSyncedRegistriesUsable)((await mergedActions.syncCache()) ?? []);
|
|
209
217
|
}
|
|
210
|
-
const bundles = (0, bundles_1.discoverAllBundles)();
|
|
218
|
+
const bundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
|
|
211
219
|
const ctx = (0, context_1.gatherContext)({ cwd, bundles, agent });
|
|
212
220
|
// In machineOnly mode, null out the project context so project steps are skipped
|
|
213
221
|
const effectiveCtx = opts.machineOnly
|
|
@@ -17,19 +17,33 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
17
17
|
const path_1 = __importDefault(require("path"));
|
|
18
18
|
const child_process_1 = require("child_process");
|
|
19
19
|
const process_1 = require("../../core/journal/process");
|
|
20
|
+
const paths_1 = require("../../core/journal/paths");
|
|
21
|
+
const types_1 = require("../../core/journal/types");
|
|
22
|
+
const native_bridge_1 = require("../../core/secure-fs/native-bridge");
|
|
20
23
|
const fingerprint_1 = require("../../core/journal/fingerprint");
|
|
21
24
|
const redact_1 = require("../../core/journal/redact");
|
|
22
25
|
const atomic_file_1 = require("../../core/atomic-file");
|
|
26
|
+
function validateSidecarIdentity(logsRoot, jobId, nonce) {
|
|
27
|
+
if (typeof logsRoot !== 'string' || logsRoot.length === 0 || logsRoot.length > 4096 || /[\u0000-\u001F\u007F]/.test(logsRoot)
|
|
28
|
+
|| typeof jobId !== 'string' || !/^[A-Za-z0-9_-][A-Za-z0-9_.-]{0,127}$/.test(jobId)
|
|
29
|
+
|| typeof nonce !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(nonce)
|
|
30
|
+
|| jobId.length + nonce.length > 230)
|
|
31
|
+
throw new Error('unsafe or unbounded sidecar identity');
|
|
32
|
+
}
|
|
23
33
|
function claimPath(logsRoot, jobId, nonce) {
|
|
34
|
+
validateSidecarIdentity(logsRoot, jobId, nonce);
|
|
24
35
|
return path_1.default.join(logsRoot, `${jobId}.${nonce}.claim`);
|
|
25
36
|
}
|
|
26
37
|
function identityPath(logsRoot, jobId, nonce) {
|
|
38
|
+
validateSidecarIdentity(logsRoot, jobId, nonce);
|
|
27
39
|
return path_1.default.join(logsRoot, `${jobId}.${nonce}.identity.json`);
|
|
28
40
|
}
|
|
29
41
|
function resultPath(logsRoot, jobId, nonce) {
|
|
42
|
+
validateSidecarIdentity(logsRoot, jobId, nonce);
|
|
30
43
|
return path_1.default.join(logsRoot, `${jobId}.${nonce}.result.json`);
|
|
31
44
|
}
|
|
32
45
|
function logPath(logsRoot, jobId, nonce) {
|
|
46
|
+
validateSidecarIdentity(logsRoot, jobId, nonce);
|
|
33
47
|
return path_1.default.join(logsRoot, `${jobId}.${nonce}.log`);
|
|
34
48
|
}
|
|
35
49
|
function replayVerdict(logsRoot, jobId, nonce) {
|
|
@@ -45,11 +59,46 @@ const MAX_LOG_BYTES = 1024 * 1024; // retencion acotada (R2.5)
|
|
|
45
59
|
// dispara, tipicamente entregados en 1 tick del event loop) sin acercarse a
|
|
46
60
|
// una espera perceptible si un descendiente hereda los fds y nunca cierra.
|
|
47
61
|
const STDIO_GRACE_MS = 300;
|
|
62
|
+
/** Only a committed generation intent may suppress controller output. A user
|
|
63
|
+
* job with the same name retains normal verifier logs. Never persist model
|
|
64
|
+
* output (which may include prompts/source bodies) in generation custody. */
|
|
65
|
+
function isCommittedController(logsRoot, repoRoot, jobId, nonce, argv) {
|
|
66
|
+
if (!/^controller-gen-[1-9][0-9]*$/.test(jobId))
|
|
67
|
+
return false;
|
|
68
|
+
try {
|
|
69
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(path_1.default.resolve(logsRoot, '..', 'state.json'), 1024 * 1024).bytes;
|
|
70
|
+
const state = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
71
|
+
if (!(0, types_1.isWellFormedState)(state))
|
|
72
|
+
throw new Error('controller custody journal invalid');
|
|
73
|
+
const actual = fs_1.default.lstatSync(logsRoot, { bigint: true });
|
|
74
|
+
const expected = fs_1.default.lstatSync((0, paths_1.logsDir)(repoRoot, state.branch), { bigint: true });
|
|
75
|
+
if (!actual.isDirectory() || actual.isSymbolicLink() || !expected.isDirectory() || expected.isSymbolicLink()
|
|
76
|
+
|| actual.ino === 0n || actual.dev !== expected.dev || actual.ino !== expected.ino)
|
|
77
|
+
throw new Error('controller custody directory identity invalid');
|
|
78
|
+
const committed = state.generations.some(gen => gen.controllerJobId === jobId && jobId === `controller-gen-${gen.n}`
|
|
79
|
+
&& gen.spawnNonce === nonce && gen.resumePrompt === undefined && (0, types_1.isControllerRecoveryAction)(gen.resumeAction)
|
|
80
|
+
&& gen.launchArgvDigest === (0, process_1.argvDigest)(argv));
|
|
81
|
+
if (committed)
|
|
82
|
+
return true;
|
|
83
|
+
const ordinary = state.jobs[jobId];
|
|
84
|
+
if (ordinary?.spawnNonce === nonce && (0, process_1.argvDigest)(ordinary.argv) === (0, process_1.argvDigest)(argv))
|
|
85
|
+
return false;
|
|
86
|
+
throw new Error('controller custody launch identity unproven');
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw new Error('controller custody identity unavailable or unsafe: refusing launch before claim');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
48
92
|
async function runExecWrapper(opts) {
|
|
49
93
|
const { logsRoot, jobId, nonce, argv, cwd } = opts;
|
|
94
|
+
validateSidecarIdentity(logsRoot, jobId, nonce);
|
|
50
95
|
const repoRoot = opts.repoRoot ?? process.cwd();
|
|
51
|
-
if (argv.length === 0
|
|
52
|
-
|
|
96
|
+
if (!Array.isArray(argv) || argv.length === 0 || argv.length > 256
|
|
97
|
+
|| argv.some(argument => typeof argument !== 'string' || argument.length > 65536 || argument.includes('\0'))
|
|
98
|
+
|| argv[0].length === 0 || argv.reduce((bytes, argument) => bytes + Buffer.byteLength(argument, 'utf8'), 0) > 1024 * 1024)
|
|
99
|
+
throw new Error('argv invalid or unbounded');
|
|
100
|
+
const safeCwd = (0, fingerprint_1.resolveWorkingDirectory)(repoRoot, cwd).absolute;
|
|
101
|
+
const omitControllerOutput = isCommittedController(logsRoot, repoRoot, jobId, nonce, argv);
|
|
53
102
|
fs_1.default.mkdirSync(logsRoot, { recursive: true, mode: 0o700 });
|
|
54
103
|
// (1) claim exclusivo DURABLE — wx + fsync de archivo y de directorio
|
|
55
104
|
let fd;
|
|
@@ -77,7 +126,6 @@ async function runExecWrapper(opts) {
|
|
|
77
126
|
// process group por job, independiente del supervisor (R4.7: shell:false,
|
|
78
127
|
// argv como array, secretos solo por referencia de entorno).
|
|
79
128
|
const [exe, ...args] = argv;
|
|
80
|
-
const safeCwd = (0, fingerprint_1.resolveWorkingDirectory)(repoRoot, cwd).absolute;
|
|
81
129
|
const child = (0, child_process_1.spawn)(exe, args, {
|
|
82
130
|
cwd: safeCwd, shell: false, detached: true,
|
|
83
131
|
env: { ...process.env, [process_1.NONCE_ENV]: nonce },
|
|
@@ -105,6 +153,8 @@ async function runExecWrapper(opts) {
|
|
|
105
153
|
const outputChunks = [];
|
|
106
154
|
const logFile = logPath(logsRoot, jobId, nonce);
|
|
107
155
|
const capture = (chunk) => {
|
|
156
|
+
if (omitControllerOutput)
|
|
157
|
+
return;
|
|
108
158
|
if (captured >= MAX_LOG_BYTES)
|
|
109
159
|
return;
|
|
110
160
|
const accepted = chunk.subarray(0, MAX_LOG_BYTES - captured);
|
|
@@ -1,13 +1,39 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.exitCodeFor = exitCodeFor;
|
|
4
7
|
exports.formatReport = formatReport;
|
|
5
8
|
exports.registerPlanCommand = registerPlanCommand;
|
|
6
9
|
const validate_1 = require("../../core/plan/validate");
|
|
10
|
+
const admission_1 = require("../../core/admission");
|
|
11
|
+
const check_1 = require("../../core/currentness/check");
|
|
12
|
+
const run_1 = require("../sensors/run");
|
|
13
|
+
const config_1 = require("../../utils/config");
|
|
14
|
+
const providers_1 = require("../../providers");
|
|
15
|
+
const registries_1 = require("../../core/registries");
|
|
16
|
+
const registry_contracts_1 = require("../../core/admission/registry-contracts");
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const child_process_1 = require("child_process");
|
|
19
|
+
const store_1 = require("../../core/journal/store");
|
|
20
|
+
const migration_1 = require("../../core/migration");
|
|
7
21
|
const SUPPORTED_SCHEMA = 'compact-slices/v1';
|
|
8
22
|
const MAX_PATH_LENGTH = 4096;
|
|
9
23
|
const MAX_DIAGNOSTICS = 20;
|
|
10
24
|
const MAX_DIAGNOSTIC_LENGTH = 4096;
|
|
25
|
+
function journalObservation(cwd) {
|
|
26
|
+
try {
|
|
27
|
+
const branch = (0, child_process_1.execFileSync)('git', ['branch', '--show-current'], { cwd, encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
28
|
+
if (!branch)
|
|
29
|
+
return { journalState: null, journalCorrupt: true };
|
|
30
|
+
const journal = (0, store_1.readJournal)(cwd, branch);
|
|
31
|
+
return { journalState: journal.state, journalCorrupt: journal.corrupt };
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return { journalState: null, journalCorrupt: true };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
11
37
|
function assertText(value, name) {
|
|
12
38
|
if (typeof value !== 'string' || value.length === 0 || value.length > MAX_PATH_LENGTH || value.startsWith('--') || /[\u0000-\u001F\u007F-\u009F]/.test(value)) {
|
|
13
39
|
throw new Error(`${name} must be a non-empty path without control characters`);
|
|
@@ -19,6 +45,15 @@ function terminalSafe(value) {
|
|
|
19
45
|
function assertDependencies(deps) {
|
|
20
46
|
if (!deps || typeof deps.validatePlanFile !== 'function')
|
|
21
47
|
throw new Error('validatePlanFile must be a function');
|
|
48
|
+
if (deps.admitPlan !== undefined && typeof deps.admitPlan !== 'function')
|
|
49
|
+
throw new Error('admitPlan must be a function');
|
|
50
|
+
}
|
|
51
|
+
function admissionOutput(report, json) {
|
|
52
|
+
report = (0, admission_1.sanitizeAdmissionReport)(report);
|
|
53
|
+
if (json)
|
|
54
|
+
return `${JSON.stringify(report)}\n`;
|
|
55
|
+
const diagnostic = report.diagnostics[0];
|
|
56
|
+
return `Plan admission: ${report.state}${report.provider ? ` (${terminalSafe(report.provider)})` : ''}${diagnostic ? `\n- ${terminalSafe(diagnostic.code)}: ${terminalSafe(diagnostic.message)}` : ''}\n`;
|
|
22
57
|
}
|
|
23
58
|
function boundedDiagnostics(diagnostics) {
|
|
24
59
|
if (!Array.isArray(diagnostics))
|
|
@@ -34,16 +69,42 @@ function boundedDiagnostics(diagnostics) {
|
|
|
34
69
|
};
|
|
35
70
|
});
|
|
36
71
|
}
|
|
72
|
+
function assertReport(report) {
|
|
73
|
+
if (!report || typeof report !== 'object' || Array.isArray(report))
|
|
74
|
+
throw new Error('plan validator returned an invalid report');
|
|
75
|
+
switch (report.state) {
|
|
76
|
+
case 'valid':
|
|
77
|
+
(0, validate_1.assertVerifiedValidPlanReport)(report);
|
|
78
|
+
return;
|
|
79
|
+
case 'migration-required':
|
|
80
|
+
if (report.reason !== 'unmarked-plan')
|
|
81
|
+
throw new Error('plan validator returned an invalid migration reason');
|
|
82
|
+
return;
|
|
83
|
+
case 'invalid':
|
|
84
|
+
boundedDiagnostics(report.diagnostics);
|
|
85
|
+
return;
|
|
86
|
+
case 'unsupported':
|
|
87
|
+
if (typeof report.schema !== 'string' || report.schema.length === 0 || report.schema.length > MAX_PATH_LENGTH) {
|
|
88
|
+
throw new Error('plan validator returned an invalid unsupported schema');
|
|
89
|
+
}
|
|
90
|
+
boundedDiagnostics(report.diagnostics);
|
|
91
|
+
return;
|
|
92
|
+
default:
|
|
93
|
+
throw new Error('plan validator returned an unknown report state');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
37
96
|
function reportPayload(report, planPath) {
|
|
97
|
+
assertReport(report);
|
|
38
98
|
switch (report.state) {
|
|
39
99
|
case 'valid':
|
|
40
100
|
return {
|
|
41
101
|
state: report.state, path: planPath, schema: report.schema, planId: report.manifest.planId,
|
|
102
|
+
planDigest: report.planDigest,
|
|
42
103
|
requirements: report.manifest.requirements.length, sources: report.manifest.sources.length,
|
|
43
104
|
commands: report.manifest.commands.length, slices: report.manifest.slices.length, completeOwnership: true,
|
|
44
105
|
};
|
|
45
|
-
case '
|
|
46
|
-
return { state: report.state, path: planPath,
|
|
106
|
+
case 'migration-required':
|
|
107
|
+
return { state: report.state, path: planPath, reason: report.reason };
|
|
47
108
|
case 'invalid':
|
|
48
109
|
return { state: report.state, path: planPath, diagnostics: boundedDiagnostics(report.diagnostics) };
|
|
49
110
|
case 'unsupported':
|
|
@@ -56,15 +117,16 @@ function reportPayload(report, planPath) {
|
|
|
56
117
|
}
|
|
57
118
|
}
|
|
58
119
|
function exitCodeFor(report) {
|
|
59
|
-
|
|
120
|
+
assertReport(report);
|
|
121
|
+
return report.state === 'valid' ? 0 : 2;
|
|
60
122
|
}
|
|
61
123
|
function formatReport(report, planPath) {
|
|
62
124
|
const payload = reportPayload(report, planPath);
|
|
63
125
|
switch (report.state) {
|
|
64
126
|
case 'valid':
|
|
65
127
|
return `Plan validation: valid "${terminalSafe(planPath)}" (${terminalSafe(report.schema)}; ${payload.slices} slices; ${payload.requirements} requirements; complete ownership)\n`;
|
|
66
|
-
case '
|
|
67
|
-
return `Plan validation:
|
|
128
|
+
case 'migration-required':
|
|
129
|
+
return `Plan validation: migration-required "${terminalSafe(planPath)}" (${terminalSafe(report.reason)})\nMigrate this plan to compact-slices/v1 before execution.\n`;
|
|
68
130
|
case 'invalid':
|
|
69
131
|
return `Plan validation: invalid "${terminalSafe(planPath)}"\n${payload.diagnostics.map(diagnostic => `- ${terminalSafe(diagnostic.code)}: ${terminalSafe(diagnostic.message)}${diagnostic.field ? ` (${terminalSafe(diagnostic.field)})` : ''}`).join('\n')}\n`;
|
|
70
132
|
case 'unsupported':
|
|
@@ -73,11 +135,25 @@ function formatReport(report, planPath) {
|
|
|
73
135
|
throw new Error('plan validator returned an unknown report state');
|
|
74
136
|
}
|
|
75
137
|
}
|
|
76
|
-
function registerPlanCommand(program, deps = { validatePlanFile: validate_1.validatePlanFile }) {
|
|
138
|
+
function registerPlanCommand(program, deps = { validatePlanFile: validate_1.validatePlanFile, admitPlan: admission_1.admitPlan }) {
|
|
77
139
|
if (!program || typeof program.command !== 'function')
|
|
78
140
|
throw new Error('program must be a Commander command');
|
|
79
141
|
assertDependencies(deps);
|
|
80
142
|
const plan = program.command('plan').description('inspect plan contracts');
|
|
143
|
+
plan.command('migration-facts <plan-path>')
|
|
144
|
+
.description('collect read-only durable migration facts')
|
|
145
|
+
.option('--cwd <path>')
|
|
146
|
+
.option('--historical-root <path>', 'admitted sibling #148 worktree')
|
|
147
|
+
.requiredOption('--issue <https-url...>', 'durable issue links, including #126')
|
|
148
|
+
.option('--json')
|
|
149
|
+
.action((planPath, options) => {
|
|
150
|
+
assertText(planPath, 'plan path');
|
|
151
|
+
const cwd = options.cwd ?? process.cwd();
|
|
152
|
+
assertText(cwd, '--cwd');
|
|
153
|
+
const report = options.historicalRoot ? (0, migration_1.collectIssue148HistoricalFacts)(options.historicalRoot, options.issue) : (deps.collectMigrationFacts ?? migration_1.collectMigrationFacts)(planPath, cwd, options.issue);
|
|
154
|
+
process.stdout.write(options.json ? `${JSON.stringify(report)}\n` : `Migration facts: ${report.state}\n`);
|
|
155
|
+
process.exitCode = report.state === 'supported-completion' ? 0 : 2;
|
|
156
|
+
});
|
|
81
157
|
plan
|
|
82
158
|
.command('validate <plan-path>')
|
|
83
159
|
.description('validate a compact plan without modifying it')
|
|
@@ -95,4 +171,46 @@ function registerPlanCommand(program, deps = { validatePlanFile: validate_1.vali
|
|
|
95
171
|
const code = exitCodeFor(report);
|
|
96
172
|
process.exitCode = code;
|
|
97
173
|
});
|
|
174
|
+
plan
|
|
175
|
+
.command('admit <plan-path>')
|
|
176
|
+
.description('read-only fail-closed compact-plan admission')
|
|
177
|
+
.requiredOption('--provider <target>', 'target provider')
|
|
178
|
+
.requiredOption('--cwd <path>', 'repository root for plan containment and source resolution')
|
|
179
|
+
.option('--execution-mode <mode>', 'explicit override: interactivo or desatendido')
|
|
180
|
+
.option('--require-current', 'require authoritative consumed-contract currentness')
|
|
181
|
+
.option('--verify-sensors', 'require an empirical sensor pass')
|
|
182
|
+
.option('--json', 'emit one stable JSON report')
|
|
183
|
+
.action(async (planPath, options) => {
|
|
184
|
+
assertText(planPath, 'plan path');
|
|
185
|
+
assertText(options.cwd, '--cwd');
|
|
186
|
+
assertText(options.provider, '--provider');
|
|
187
|
+
const admission = deps.admitPlan ?? admission_1.admitPlan;
|
|
188
|
+
const preferences = deps.readPreferences ?? config_1.readPreferences;
|
|
189
|
+
const currentnessCheck = deps.checkCurrentness ?? check_1.checkCurrentness;
|
|
190
|
+
const sensorRun = deps.runSensors ?? run_1.runSensors;
|
|
191
|
+
const registryInventory = deps.listRegistries ?? registries_1.listRegistries;
|
|
192
|
+
const planReport = deps.validatePlanFile(planPath, options.cwd);
|
|
193
|
+
// The mode comes from the same authenticated bytes/digest that produced
|
|
194
|
+
// planReport. Never reopen the path to parse a mutable header.
|
|
195
|
+
const executionMode = options.executionMode === undefined
|
|
196
|
+
? planReport.state === 'valid' ? (planReport.executionMode ?? 'interactivo') : 'interactivo'
|
|
197
|
+
: options.executionMode === 'desatendido' ? 'desatendido' : options.executionMode === 'interactivo' ? 'interactivo' : options.executionMode;
|
|
198
|
+
const journal = executionMode === 'desatendido' ? journalObservation(options.cwd) : {};
|
|
199
|
+
const normalizedPlanPath = path_1.default.relative(options.cwd, path_1.default.resolve(options.cwd, planPath)).replace(/\\/g, '/');
|
|
200
|
+
const enabledAgents = preferences().enabledAgents;
|
|
201
|
+
// The documented order is plan, provider, currentness, sensors, journal,
|
|
202
|
+
// capabilities. Do not call the full composer early: that would resolve a
|
|
203
|
+
// capability before empirical evidence gates have had their ordered turn.
|
|
204
|
+
const earlyBoundary = planReport.state !== 'valid' || !(0, providers_1.isAgentTarget)(options.provider) || !enabledAgents.includes(options.provider);
|
|
205
|
+
if (earlyBoundary) {
|
|
206
|
+
const report = await admission({ plan: planReport, provider: options.provider, cwd: options.cwd, enabledAgents, executionMode, planPath: normalizedPlanPath, ...journal });
|
|
207
|
+
process.stdout.write(admissionOutput(report, options.json === true));
|
|
208
|
+
process.exitCode = 2;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const report = await (0, registry_contracts_1.admitRegistryPlan)({ plan: planReport, provider: options.provider, cwd: options.cwd, enabledAgents, executionMode, planPath: normalizedPlanPath, ...journal, requireCurrent: options.requireCurrent === true, verifySensors: options.verifySensors === true }, { admitPlan: admission, listRegistries: registryInventory, checkCurrentness: currentnessCheck, runSensors: sensorRun });
|
|
212
|
+
process.stdout.write(admissionOutput(report, options.json === true));
|
|
213
|
+
if (report.state !== 'admitted')
|
|
214
|
+
process.exitCode = 2;
|
|
215
|
+
});
|
|
98
216
|
}
|
|
@@ -26,7 +26,7 @@ function deriveRegistryName(remote) {
|
|
|
26
26
|
const base = remote.replace(/[/\\]+$/, '').split(/[/\\:]/).pop() ?? '';
|
|
27
27
|
return base.replace(/\.git$/, '');
|
|
28
28
|
}
|
|
29
|
-
async function addRegistry(remote, nameOverride) {
|
|
29
|
+
async function addRegistry(remote, nameOverride, reporter) {
|
|
30
30
|
const name = nameOverride ?? deriveRegistryName(remote);
|
|
31
31
|
if (!name || name === '.' || name.includes('..') || /[/\\]/.test(name)) {
|
|
32
32
|
return { ok: false, error: `Invalid registry name "${name}" — use --name <simple-dir-name>` };
|
|
@@ -61,7 +61,7 @@ async function addRegistry(remote, nameOverride) {
|
|
|
61
61
|
(0, discovery_1.discoverSkills)(roots);
|
|
62
62
|
(0, discovery_1.discoverWorkflows)(roots);
|
|
63
63
|
(0, discovery_1.discoverAgents)(roots);
|
|
64
|
-
(0, bundles_1.discoverAllBundles)(roots);
|
|
64
|
+
(0, bundles_1.discoverAllBundles)(roots, reporter);
|
|
65
65
|
}
|
|
66
66
|
catch (e) {
|
|
67
67
|
fs_1.default.rmSync(dest, { recursive: true, force: true });
|
|
@@ -28,10 +28,11 @@ function registerRegistryCommand(program) {
|
|
|
28
28
|
.option('--install-all', 'install every bundle from the new registry for the default agent')
|
|
29
29
|
.option('--no-install', 'skip the bundle install offer')
|
|
30
30
|
.action(async (remote, options) => {
|
|
31
|
+
const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
|
|
31
32
|
(0, prompts_1.intro)(picocolors_1.default.bgCyan(picocolors_1.default.black(' AWM - Add Registry ')));
|
|
32
33
|
const s = (0, prompts_1.spinner)();
|
|
33
34
|
s.start('Cloning and validating registry...');
|
|
34
|
-
const result = await (0, add_1.addRegistry)(remote, options.name);
|
|
35
|
+
const result = await (0, add_1.addRegistry)(remote, options.name, reporter);
|
|
35
36
|
if (!result.ok) {
|
|
36
37
|
s.stop('Failed.');
|
|
37
38
|
console.error(picocolors_1.default.red(result.error));
|
|
@@ -49,7 +50,7 @@ function registerRegistryCommand(program) {
|
|
|
49
50
|
}
|
|
50
51
|
// Bundle install offer — failure NEVER reverts the add.
|
|
51
52
|
try {
|
|
52
|
-
const available = (0, install_bundles_1.bundlesInRegistry)(result.contentRoot);
|
|
53
|
+
const available = (0, install_bundles_1.bundlesInRegistry)(result.contentRoot, reporter);
|
|
53
54
|
if (available.length > 0) {
|
|
54
55
|
const prefs = (0, config_1.getPreferences)();
|
|
55
56
|
const projectRoot = (0, profile_1.findProjectRoot)(process.cwd()) ?? process.cwd();
|
|
@@ -82,7 +83,7 @@ function registerRegistryCommand(program) {
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
if (selection) {
|
|
85
|
-
for (const r of (0, install_bundles_1.installBundlesFromRegistry)(result.contentRoot, selection, agents, projectRoot)) {
|
|
86
|
+
for (const r of (0, install_bundles_1.installBundlesFromRegistry)(result.contentRoot, selection, agents, projectRoot, reporter)) {
|
|
86
87
|
for (const line of r.installed)
|
|
87
88
|
console.log(picocolors_1.default.green(` ✓ ${line}`));
|
|
88
89
|
for (const sk of r.skipped)
|
|
@@ -103,6 +104,7 @@ function registerRegistryCommand(program) {
|
|
|
103
104
|
reg.command('list')
|
|
104
105
|
.description('list configured additional registries')
|
|
105
106
|
.action(() => {
|
|
107
|
+
const reporter = (0, bundles_1.createBundleDiagnosticReporter)((message) => process.stderr.write(`${message}\n`));
|
|
106
108
|
const regs = (0, registries_1.listRegistries)();
|
|
107
109
|
if (regs.length === 0) {
|
|
108
110
|
console.log(picocolors_1.default.dim('No additional registries. Add one with `awm registry add <git-url>`.'));
|
|
@@ -125,12 +127,12 @@ function registerRegistryCommand(program) {
|
|
|
125
127
|
try {
|
|
126
128
|
const counts = [
|
|
127
129
|
`${(0, discovery_1.discoverSkills)([r.contentRoot]).length} skills`,
|
|
128
|
-
`${(0, bundles_1.discoverAllBundles)([r.contentRoot]).length} bundles`,
|
|
130
|
+
`${(0, bundles_1.discoverAllBundles)([r.contentRoot], reporter).length} bundles`,
|
|
129
131
|
`${(0, discovery_1.discoverWorkflows)([r.contentRoot]).length} workflows`,
|
|
130
132
|
`${(0, discovery_1.discoverAgents)([r.contentRoot]).length} agents`,
|
|
131
133
|
].join(', ');
|
|
132
134
|
console.log(`${picocolors_1.default.cyan(r.name)} ${r.remote} ${picocolors_1.default.dim(counts)}`);
|
|
133
|
-
for (const o of (0, status_1.overrideStatus)(r.contentRoot, earlier)) {
|
|
135
|
+
for (const o of (0, status_1.overrideStatus)(r.contentRoot, earlier, reporter)) {
|
|
134
136
|
console.log(o.active
|
|
135
137
|
? picocolors_1.default.yellow(` ↑ override active: ${o.name}`)
|
|
136
138
|
: picocolors_1.default.dim(` ∅ override with no effect: ${o.name}`));
|
|
@@ -9,16 +9,16 @@ const bundles_1 = require("../../core/bundles");
|
|
|
9
9
|
const bundle_install_1 = require("../../core/bundle-install");
|
|
10
10
|
/** Bundles disponibles en un content root concreto (candidatos a instalar tras el add).
|
|
11
11
|
* Uses single-root discovery to avoid surfacing cross-registry collision errors here. */
|
|
12
|
-
function bundlesInRegistry(contentRoot) {
|
|
13
|
-
return (0, bundles_1.discoverBundles)(contentRoot).map((b) => b.name);
|
|
12
|
+
function bundlesInRegistry(contentRoot, reporter) {
|
|
13
|
+
return (0, bundles_1.discoverBundles)(contentRoot, reporter).map((b) => b.name);
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Instala bundles del registry `contentRoot` para los agentes dados.
|
|
17
17
|
* `selection` = 'all' instala todos los del registry; una lista instala solo esos.
|
|
18
18
|
* Las dependencias se resuelven contra TODOS los roots (pueden vivir en el base).
|
|
19
19
|
*/
|
|
20
|
-
function installBundlesFromRegistry(contentRoot, selection, agents, projectRoot) {
|
|
21
|
-
const allBundles = (0, bundles_1.discoverAllBundles)();
|
|
20
|
+
function installBundlesFromRegistry(contentRoot, selection, agents, projectRoot, reporter) {
|
|
21
|
+
const allBundles = (0, bundles_1.discoverAllBundles)(undefined, reporter);
|
|
22
22
|
const candidates = allBundles.filter((b) => b.contentRoot === contentRoot);
|
|
23
23
|
const wanted = selection === 'all' ? candidates : candidates.filter((b) => selection.includes(b.name));
|
|
24
24
|
const results = [];
|
|
@@ -13,7 +13,7 @@ const bundles_1 = require("../../core/bundles");
|
|
|
13
13
|
function canDiscoverRegistryContent(contentRoot, safeRoots) {
|
|
14
14
|
return safeRoots.has(contentRoot);
|
|
15
15
|
}
|
|
16
|
-
function artifactNamesInRoot(root) {
|
|
16
|
+
function artifactNamesInRoot(root, reporter) {
|
|
17
17
|
const names = new Set();
|
|
18
18
|
for (const s of (0, discovery_1.discoverSkills)([root]))
|
|
19
19
|
names.add(s.name);
|
|
@@ -21,17 +21,17 @@ function artifactNamesInRoot(root) {
|
|
|
21
21
|
names.add(w.name);
|
|
22
22
|
for (const a of (0, discovery_1.discoverAgents)([root]))
|
|
23
23
|
names.add(a.name);
|
|
24
|
-
for (const b of (0, bundles_1.discoverAllBundles)([root]))
|
|
24
|
+
for (const b of (0, bundles_1.discoverAllBundles)([root], reporter))
|
|
25
25
|
names.add(b.name);
|
|
26
26
|
return names;
|
|
27
27
|
}
|
|
28
|
-
function overrideStatus(contentRoot, earlierRoots) {
|
|
28
|
+
function overrideStatus(contentRoot, earlierRoots, reporter) {
|
|
29
29
|
const declared = Array.from((0, registries_1.readRegistryManifest)(contentRoot).overrides);
|
|
30
30
|
if (declared.length === 0)
|
|
31
31
|
return [];
|
|
32
32
|
const earlier = new Set();
|
|
33
33
|
for (const root of earlierRoots) {
|
|
34
|
-
for (const n of artifactNamesInRoot(root))
|
|
34
|
+
for (const n of artifactNamesInRoot(root, reporter))
|
|
35
35
|
earlier.add(n);
|
|
36
36
|
}
|
|
37
37
|
return declared.map((name) => ({ name, active: earlier.has(name) }));
|
|
@@ -51,7 +51,7 @@ function declaration(mode, reason) {
|
|
|
51
51
|
return { schemaVersion: 3, mode, reason };
|
|
52
52
|
}
|
|
53
53
|
function digest(bytes) { return (0, crypto_1.createHash)('sha256').update(bytes).digest('hex'); }
|
|
54
|
-
async function projectSensors(projectRoot, registryRoot, requestedPack, packageRoot) {
|
|
54
|
+
async function projectSensors(projectRoot, registryRoot, requestedPack, packageRoot, explicitPackSelection = requestedPack !== undefined) {
|
|
55
55
|
const detectionRoot = packageRoot ? path_1.default.resolve(projectRoot, packageRoot) : projectRoot;
|
|
56
56
|
const detected = (0, detection_1.detectStack)(detectionRoot);
|
|
57
57
|
const pack = requestedPack ?? detected.pack;
|
|
@@ -76,7 +76,9 @@ async function projectSensors(projectRoot, registryRoot, requestedPack, packageR
|
|
|
76
76
|
return { reason: 'source-unsupported', remedy: 'install-a-v2-sensor-pack' };
|
|
77
77
|
let live;
|
|
78
78
|
try {
|
|
79
|
-
live =
|
|
79
|
+
live = explicitPackSelection
|
|
80
|
+
? await (0, live_1.resolveParsedPackCompatibility)(detectionRoot, parsed.pack, { packSelection: 'explicit' })
|
|
81
|
+
: await (0, live_1.resolveParsedPackCompatibility)(detectionRoot, parsed.pack);
|
|
80
82
|
}
|
|
81
83
|
catch {
|
|
82
84
|
return { reason: 'compatibility-unresolvable', remedy: 'repair-project-tools-or-select-another-mode' };
|
|
@@ -84,6 +86,8 @@ async function projectSensors(projectRoot, registryRoot, requestedPack, packageR
|
|
|
84
86
|
const sensors = {};
|
|
85
87
|
for (const [name, packSensor] of Object.entries(live.pack.sensors)) {
|
|
86
88
|
const evidence = live.sensors[name];
|
|
89
|
+
if (evidence.state === 'not-applicable')
|
|
90
|
+
continue;
|
|
87
91
|
const variant = evidence.variantId === null ? undefined : packSensor.variants.find(item => item.id === evidence.variantId);
|
|
88
92
|
if (!variant)
|
|
89
93
|
return { reason: 'sensor-variant-unresolvable', remedy: 'repair-project-tools-or-select-another-mode' };
|
|
@@ -109,8 +113,32 @@ async function planSensorBootstrap(cwd = process.cwd(), input = {}) {
|
|
|
109
113
|
}
|
|
110
114
|
return { kind: 'noop', projectRoot: project.projectRoot, manifestPath: project.manifestPath, changes: [], dryRun: opts.dryRun };
|
|
111
115
|
}
|
|
112
|
-
if (project.manifest.kind === 'legacy')
|
|
113
|
-
|
|
116
|
+
if (project.manifest.kind === 'legacy') {
|
|
117
|
+
// Legacy manifests can carry arbitrary commands and per-sensor
|
|
118
|
+
// semantics that a v3 pack cannot prove equivalent. Replacing
|
|
119
|
+
// them is therefore a deliberate opt-in, never an automatic
|
|
120
|
+
// side effect of inspection or an unqualified bootstrap.
|
|
121
|
+
if (opts.mode !== 'project-sensors') {
|
|
122
|
+
return blocked(project.projectRoot, project.manifestPath, opts.dryRun, 'legacy-replacement-requires-project-sensors-mode', 'rerun-bootstrap-with-mode-project-sensors');
|
|
123
|
+
}
|
|
124
|
+
// The legacy pack name identifies the source to migrate, but must not
|
|
125
|
+
// activate v2 sensors which require an explicit project opt-in.
|
|
126
|
+
const planned = await projectSensors(project.projectRoot, opts.registryRoot, project.manifest.pack.pack, undefined, false);
|
|
127
|
+
if ('reason' in planned)
|
|
128
|
+
return blocked(project.projectRoot, project.manifestPath, opts.dryRun, planned.reason, planned.remedy, 'candidates' in planned ? planned.candidates : undefined);
|
|
129
|
+
try {
|
|
130
|
+
const original = fs_1.default.readFileSync(project.manifestPath);
|
|
131
|
+
return {
|
|
132
|
+
kind: 'migrate', projectRoot: project.projectRoot, manifestPath: project.manifestPath,
|
|
133
|
+
changes: planned.changes.map((change, index) => index === 0 ? { ...change, action: 'replace' } : change),
|
|
134
|
+
dryRun: opts.dryRun, migration: { kind: 'legacy-v1', manifest: planned.manifest }, source: planned.source,
|
|
135
|
+
originalDigest: digest(original),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return blocked(project.projectRoot, project.manifestPath, opts.dryRun, 'legacy-manifest-unreadable', 'repair-the-manifest-before-bootstrap');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
114
142
|
let source;
|
|
115
143
|
try {
|
|
116
144
|
source = (0, source_1.resolveSensorSource)(project.manifest, { registries: (0, registries_1.listRegistries)() });
|
|
@@ -152,7 +180,10 @@ function applySensorBootstrap(plan) {
|
|
|
152
180
|
const current = fs_1.default.readFileSync(plan.manifestPath);
|
|
153
181
|
if (digest(current) !== plan.originalDigest)
|
|
154
182
|
throw new Error('bootstrap migration plan is stale: manifest changed after planning');
|
|
155
|
-
|
|
183
|
+
if ('kind' in plan.migration)
|
|
184
|
+
(0, migrate_1.replaceLegacyManifestWithV3)(plan.manifestPath, plan.migration.manifest, plan.source);
|
|
185
|
+
else
|
|
186
|
+
(0, migrate_2.replaceV2ManifestWithV3)(plan.manifestPath, plan.migration.candidate, plan.source);
|
|
156
187
|
return 'migrated';
|
|
157
188
|
}
|
|
158
189
|
if (plan.manifest.mode === 'project-sensors') {
|