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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.consumedRegistryContracts = consumedRegistryContracts;
|
|
7
|
+
exports.registryCompatibility = registryCompatibility;
|
|
8
|
+
exports.admitRegistryPlan = admitRegistryPlan;
|
|
9
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
12
|
+
const providers_1 = require("../../providers");
|
|
13
|
+
const registry_1 = require("../renderers/registry");
|
|
14
|
+
const registries_1 = require("../registries");
|
|
15
|
+
const native_bridge_1 = require("../secure-fs/native-bridge");
|
|
16
|
+
const json_1 = require("../plan/json");
|
|
17
|
+
const cli_version_1 = require("../cli-version");
|
|
18
|
+
const versioning_1 = require("../versioning");
|
|
19
|
+
const check_1 = require("../currentness/check");
|
|
20
|
+
const run_1 = require("../../commands/sensors/run");
|
|
21
|
+
const index_1 = require("./index");
|
|
22
|
+
const FRAMEWORK_CONTRACTS = ['using-awm', 'writing-plans', 'development-process', 'subagent-driven-development', 'executing-plans', 'post-implementation-qa', 'post-implementation-docs', 'harness-retro', 'finishing-a-development-branch'];
|
|
23
|
+
const MAX_CONTRACT_BYTES = 1024 * 1024;
|
|
24
|
+
const MAX_MANIFEST_BYTES = 256 * 1024;
|
|
25
|
+
const MAX_DIRECTORY_ENTRIES = 10000;
|
|
26
|
+
function unknown(message) {
|
|
27
|
+
return { provenance: 'unknown', consumedRegistryComponents: [], provenanceDiagnostic: { code: 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED', message: `${message} Repair the installed runtime artifact or registry provenance before dispatch.` } };
|
|
28
|
+
}
|
|
29
|
+
function within(root, candidate) {
|
|
30
|
+
const relative = path_1.default.relative(root, candidate);
|
|
31
|
+
return relative === '' || (!relative.startsWith(`..${path_1.default.sep}`) && relative !== '..' && !path_1.default.isAbsolute(relative));
|
|
32
|
+
}
|
|
33
|
+
function physicalDirectory(directory) {
|
|
34
|
+
const physical = fs_1.default.realpathSync.native(directory);
|
|
35
|
+
if (!fs_1.default.lstatSync(physical).isDirectory())
|
|
36
|
+
throw new Error('path is not a directory');
|
|
37
|
+
const handle = fs_1.default.opendirSync(physical);
|
|
38
|
+
try {
|
|
39
|
+
let entries = 0;
|
|
40
|
+
while (handle.readSync() !== null)
|
|
41
|
+
if (++entries > MAX_DIRECTORY_ENTRIES)
|
|
42
|
+
throw new Error('directory entry limit exceeded');
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
handle.closeSync();
|
|
46
|
+
}
|
|
47
|
+
return physical;
|
|
48
|
+
}
|
|
49
|
+
function filesystemIdentity(file) {
|
|
50
|
+
const stat = fs_1.default.lstatSync(file, { bigint: true });
|
|
51
|
+
if (stat.isSymbolicLink() || stat.ino === 0n)
|
|
52
|
+
throw new Error('filesystem identity is unavailable');
|
|
53
|
+
return `${stat.dev.toString()}:${stat.ino.toString()}`;
|
|
54
|
+
}
|
|
55
|
+
/** Native descriptor bytes plus exact bigint leaf identity are observed only
|
|
56
|
+
* in memory. No source body or new journal snapshot is persisted. */
|
|
57
|
+
function contractFileIdentity(file) {
|
|
58
|
+
const before = fs_1.default.lstatSync(file, { bigint: true });
|
|
59
|
+
if (!before.isFile() || before.isSymbolicLink() || before.ino === 0n)
|
|
60
|
+
throw new Error('contract identity is unavailable');
|
|
61
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(file, MAX_CONTRACT_BYTES).bytes;
|
|
62
|
+
const after = fs_1.default.lstatSync(file, { bigint: true });
|
|
63
|
+
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino
|
|
64
|
+
|| before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs)
|
|
65
|
+
throw new Error('contract identity changed during inspection');
|
|
66
|
+
return crypto_1.default.createHash('sha256').update(`${before.dev.toString()}:${before.ino.toString()}\0`).update(bytes).digest('hex');
|
|
67
|
+
}
|
|
68
|
+
/** ENOENT proves absence only when the nearest existing parent is readable.
|
|
69
|
+
* A dangling/replaced ancestor never certifies an empty provider install. */
|
|
70
|
+
function directoryExists(directory) {
|
|
71
|
+
let current = directory;
|
|
72
|
+
while (true) {
|
|
73
|
+
try {
|
|
74
|
+
fs_1.default.lstatSync(current);
|
|
75
|
+
physicalDirectory(current);
|
|
76
|
+
return current === directory;
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
if (error.code !== 'ENOENT')
|
|
80
|
+
throw error;
|
|
81
|
+
const parent = path_1.default.dirname(current);
|
|
82
|
+
if (parent === current)
|
|
83
|
+
throw error;
|
|
84
|
+
// An existing dangling symlink causes realpath ENOENT; do not
|
|
85
|
+
// continue above it as though the requested install were absent.
|
|
86
|
+
try {
|
|
87
|
+
if (fs_1.default.lstatSync(current).isSymbolicLink())
|
|
88
|
+
throw new Error('dangling directory ancestor');
|
|
89
|
+
}
|
|
90
|
+
catch (inspection) {
|
|
91
|
+
if (inspection.code !== 'ENOENT')
|
|
92
|
+
throw inspection;
|
|
93
|
+
}
|
|
94
|
+
current = parent;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** Physical sources AND actual provider artifacts define consumption. Copied
|
|
99
|
+
* rendered artifacts have no owner proof; a basename is never provenance. */
|
|
100
|
+
function consumedRegistryContracts(report, cwd, registries, target) {
|
|
101
|
+
if (report.state !== 'valid')
|
|
102
|
+
return unknown('A validated compact plan is required.');
|
|
103
|
+
if (!Array.isArray(registries) || registries.length > 256)
|
|
104
|
+
return unknown('Registry inventory is invalid or unbounded.');
|
|
105
|
+
let root;
|
|
106
|
+
try {
|
|
107
|
+
root = physicalDirectory(path_1.default.resolve(cwd));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return unknown('Project root provenance is unavailable.');
|
|
111
|
+
}
|
|
112
|
+
const physicalRegistries = [];
|
|
113
|
+
for (const registry of registries) {
|
|
114
|
+
try {
|
|
115
|
+
if (!registry || typeof registry.name !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/.test(registry.name)
|
|
116
|
+
|| typeof registry.contentRoot !== 'string' || registry.contentRoot.length > 4096
|
|
117
|
+
|| typeof registry.remote !== 'string' || registry.remote.length > 4096)
|
|
118
|
+
throw new Error('invalid registry identity');
|
|
119
|
+
const physicalRoot = physicalDirectory(registry.contentRoot);
|
|
120
|
+
physicalRegistries.push({ name: registry.name, root: physicalRoot, identity: filesystemIdentity(physicalRoot), remote: registry.remote });
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
const name = typeof registry?.name === 'string' && /^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,63}$/.test(registry.name) ? registry.name : 'unknown';
|
|
124
|
+
return unknown(`Registry ${name} root cannot be inspected.`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const consumed = new Set();
|
|
128
|
+
const identities = [];
|
|
129
|
+
for (const source of report.manifest.sources) {
|
|
130
|
+
try {
|
|
131
|
+
const file = fs_1.default.realpathSync.native(path_1.default.resolve(root, source.path));
|
|
132
|
+
if (!within(root, file))
|
|
133
|
+
return unknown(`Plan source ${source.path} escapes its physical project root.`);
|
|
134
|
+
identities.push(JSON.stringify(['source', source.id, contractFileIdentity(file)]));
|
|
135
|
+
for (const registry of physicalRegistries)
|
|
136
|
+
if (within(registry.root, file))
|
|
137
|
+
consumed.add(`registry:${registry.name}`);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return unknown(`Plan source ${source.path} cannot be inspected as a bounded regular file.`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
const provider = (0, providers_1.providerFor)(target);
|
|
144
|
+
const directories = [provider.skill.global, path_1.default.resolve(root, provider.skill.local)].filter((directory) => directory !== null);
|
|
145
|
+
for (const directory of directories) {
|
|
146
|
+
try {
|
|
147
|
+
if (!directoryExists(directory))
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return unknown(`Provider ${target} install directory ${directory} is unsafe or unreadable.`);
|
|
152
|
+
}
|
|
153
|
+
for (const contract of FRAMEWORK_CONTRACTS) {
|
|
154
|
+
const artifact = path_1.default.join(directory, (0, registry_1.renderedFilename)(contract, provider.skill.renderer));
|
|
155
|
+
try {
|
|
156
|
+
fs_1.default.lstatSync(artifact);
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error.code === 'ENOENT')
|
|
160
|
+
continue;
|
|
161
|
+
return unknown(`Runtime artifact ${artifact} cannot be inspected.`);
|
|
162
|
+
}
|
|
163
|
+
const declaredFile = provider.skill.renderer === 'link' ? path_1.default.join(artifact, 'SKILL.md') : artifact;
|
|
164
|
+
try {
|
|
165
|
+
const file = fs_1.default.realpathSync.native(declaredFile);
|
|
166
|
+
const identity = contractFileIdentity(file);
|
|
167
|
+
const owners = physicalRegistries.filter(registry => within(registry.root, file));
|
|
168
|
+
if (owners.length !== 1)
|
|
169
|
+
return unknown(`Runtime artifact ${artifact} has no unique physical registry owner (copied/rendered content is not ownership proof).`);
|
|
170
|
+
consumed.add(`registry:${owners[0].name}`);
|
|
171
|
+
identities.push(JSON.stringify(['artifact', target, contract, provider.skill.renderer, identity]));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return unknown(`Runtime artifact ${artifact} is dangling, nonregular, unreadable or unbounded.`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
for (const registry of physicalRegistries)
|
|
179
|
+
if (consumed.has(`registry:${registry.name}`))
|
|
180
|
+
identities.push(JSON.stringify(['registry', registry.name, registry.identity, registry.remote]));
|
|
181
|
+
return { provenance: 'proven', consumedRegistryComponents: [...consumed].sort(), identityDigest: crypto_1.default.createHash('sha256').update(identities.sort().join('\0')).digest('hex') };
|
|
182
|
+
}
|
|
183
|
+
/** Only the declared CLI floor is consumed here, not a new registry catalog.
|
|
184
|
+
* The native bounded/no-follow read prevents stat/read races and body leaks. */
|
|
185
|
+
function registryCompatibility(registries, consumed) {
|
|
186
|
+
const diagnostics = [];
|
|
187
|
+
const current = (0, cli_version_1.cliVersion)();
|
|
188
|
+
for (const registry of registries) {
|
|
189
|
+
const component = `registry:${registry.name}`;
|
|
190
|
+
if (!consumed.includes(component))
|
|
191
|
+
continue;
|
|
192
|
+
try {
|
|
193
|
+
const physicalRoot = physicalDirectory(registry.contentRoot);
|
|
194
|
+
const manifest = path_1.default.join(physicalRoot, registries_1.REGISTRY_MANIFEST_NAME);
|
|
195
|
+
try {
|
|
196
|
+
fs_1.default.lstatSync(manifest);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error.code === 'ENOENT')
|
|
200
|
+
continue;
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
const bytes = native_bridge_1.secureFs.readRegularFile(manifest, MAX_MANIFEST_BYTES).bytes;
|
|
204
|
+
const raw = (0, json_1.parseJsonNoDuplicate)(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
205
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw))
|
|
206
|
+
throw new Error('manifest must be a JSON object');
|
|
207
|
+
const min = raw.minCliVersion;
|
|
208
|
+
if (min !== undefined && (typeof min !== 'string' || !/^v?\d+\.\d+\.\d+$/.test(min)
|
|
209
|
+
|| min.replace(/^v/, '').split('.').some(part => !Number.isSafeInteger(Number(part)))))
|
|
210
|
+
throw new Error('invalid CLI floor');
|
|
211
|
+
if (typeof min === 'string' && (0, versioning_1.compareSemver)(current, min) < 0)
|
|
212
|
+
diagnostics.push({ code: 'ADMISSION_REGISTRY_CLI_INCOMPATIBLE', message: `${component} requires CLI >= ${min}; installed CLI is ${current}. Update the CLI before dispatch.` });
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
diagnostics.push({ code: 'ADMISSION_REGISTRY_COMPATIBILITY_UNVERIFIABLE', message: `${component} manifest compatibility could not be verified as bounded regular JSON. Repair the registry before dispatch.` });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return diagnostics;
|
|
219
|
+
}
|
|
220
|
+
/** One admission authority for the public command AND actual watch dispatch.
|
|
221
|
+
* No empirical execution occurs before provenance/currentness/CLI-floor gates. */
|
|
222
|
+
async function admitRegistryPlan(input, dependencies = {}) {
|
|
223
|
+
const admission = dependencies.admitPlan ?? index_1.admitPlan;
|
|
224
|
+
if (input.plan.state !== 'valid' || !(0, providers_1.isAgentTarget)(input.provider) || (input.enabledAgents && !input.enabledAgents.includes(input.provider))
|
|
225
|
+
|| (input.executionMode !== undefined && input.executionMode !== 'interactivo' && input.executionMode !== 'desatendido'))
|
|
226
|
+
return admission(input);
|
|
227
|
+
let registries = [];
|
|
228
|
+
let scope;
|
|
229
|
+
const inventory = dependencies.listRegistries ?? registries_1.listRegistries;
|
|
230
|
+
const target = input.provider;
|
|
231
|
+
try {
|
|
232
|
+
registries = inventory();
|
|
233
|
+
scope = consumedRegistryContracts(input.plan, input.cwd, registries, target);
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
scope = unknown('Registry inventory cannot be read.');
|
|
237
|
+
}
|
|
238
|
+
const refresh = () => {
|
|
239
|
+
try {
|
|
240
|
+
const refreshedRegistries = inventory();
|
|
241
|
+
const refreshed = consumedRegistryContracts(input.plan, input.cwd, refreshedRegistries, target);
|
|
242
|
+
const previousComponents = scope.consumedRegistryComponents.join('\0');
|
|
243
|
+
const previousIdentity = scope.identityDigest;
|
|
244
|
+
registries = refreshedRegistries;
|
|
245
|
+
scope = refreshed.provenance === 'unknown' ? refreshed
|
|
246
|
+
: refreshed.consumedRegistryComponents.join('\0') !== previousComponents || refreshed.identityDigest !== previousIdentity
|
|
247
|
+
? unknown('Consumed registry ownership changed during an asynchronous admission observation. Re-run actual currentness and read-only plan admission.') : refreshed;
|
|
248
|
+
if (scope.provenanceDiagnostic)
|
|
249
|
+
scope = { ...scope, provenanceDiagnostic: { ...scope.provenanceDiagnostic, message: `${scope.provenanceDiagnostic.message} Re-run actual currentness after repair.` } };
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
scope = unknown('Registry inventory changed or cannot be read after asynchronous admission. Re-run actual currentness.');
|
|
253
|
+
}
|
|
254
|
+
return scope.provenance === 'proven';
|
|
255
|
+
};
|
|
256
|
+
const compose = async (fields) => {
|
|
257
|
+
const report = await admission({ ...input, provenance: scope.provenance, consumedRegistryComponents: scope.consumedRegistryComponents, ...fields });
|
|
258
|
+
if (scope.provenanceDiagnostic && report.diagnostics.some(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED')) {
|
|
259
|
+
return { ...report, diagnostics: report.diagnostics.map(diagnostic => diagnostic.code === 'ADMISSION_CURRENTNESS_PROVENANCE_REQUIRED' ? scope.provenanceDiagnostic : diagnostic) };
|
|
260
|
+
}
|
|
261
|
+
return report;
|
|
262
|
+
};
|
|
263
|
+
let currentness = input.currentness;
|
|
264
|
+
let compatibilityDiagnostics;
|
|
265
|
+
if (input.requireCurrent) {
|
|
266
|
+
if (scope.provenance !== 'proven')
|
|
267
|
+
return compose({});
|
|
268
|
+
currentness = currentness ?? await (dependencies.checkCurrentness ?? check_1.checkCurrentness)(input.cwd);
|
|
269
|
+
if (!refresh())
|
|
270
|
+
return compose({ currentness });
|
|
271
|
+
// A missing sensor input stages composition before capability resolution.
|
|
272
|
+
const currentnessGate = await compose({ currentness, verifySensors: true, sensors: undefined });
|
|
273
|
+
if (currentnessGate.currentness !== 'current')
|
|
274
|
+
return currentnessGate;
|
|
275
|
+
compatibilityDiagnostics = registryCompatibility(registries, scope.consumedRegistryComponents);
|
|
276
|
+
const compatibilityGate = await compose({ currentness, compatibilityDiagnostics, verifySensors: true, sensors: undefined });
|
|
277
|
+
if (compatibilityGate.currentness !== 'current')
|
|
278
|
+
return compatibilityGate;
|
|
279
|
+
}
|
|
280
|
+
const sensors = input.verifySensors ? input.sensors ?? await (dependencies.runSensors ?? run_1.runSensors)({ cwd: input.cwd, all: true, readOnly: true }) : undefined;
|
|
281
|
+
if (input.requireCurrent && input.verifySensors) {
|
|
282
|
+
if (!refresh())
|
|
283
|
+
return compose({ currentness, sensors });
|
|
284
|
+
const currentnessGate = await compose({ currentness, verifySensors: true, sensors: undefined });
|
|
285
|
+
if (currentnessGate.currentness !== 'current')
|
|
286
|
+
return currentnessGate;
|
|
287
|
+
compatibilityDiagnostics = registryCompatibility(registries, scope.consumedRegistryComponents);
|
|
288
|
+
}
|
|
289
|
+
return compose({ currentness, compatibilityDiagnostics, sensors });
|
|
290
|
+
}
|
|
@@ -28,8 +28,8 @@ function bundleArtifacts(b, contentDir) {
|
|
|
28
28
|
// estructural sobre la ruta ya resuelta.
|
|
29
29
|
const refs = [];
|
|
30
30
|
for (const s of b.skills) {
|
|
31
|
-
(0, artifact_name_1.assertSafeArtifactName)(s
|
|
32
|
-
refs.push({ name: s
|
|
31
|
+
(0, artifact_name_1.assertSafeArtifactName)(s, 'skill');
|
|
32
|
+
refs.push({ name: s, type: 'skill', installName: s, sourcePath: path_1.default.join(contentDir, 'skills', s) });
|
|
33
33
|
}
|
|
34
34
|
for (const w of b.workflows) {
|
|
35
35
|
(0, artifact_name_1.assertSafeArtifactName)(w, 'workflow');
|
package/dist/src/core/bundles.js
CHANGED
|
@@ -4,15 +4,20 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.readCatalog = readCatalog;
|
|
7
|
+
exports.inspectBundles = inspectBundles;
|
|
8
|
+
exports.createBundleDiagnosticReporter = createBundleDiagnosticReporter;
|
|
7
9
|
exports.discoverBundles = discoverBundles;
|
|
8
10
|
exports.resolveBundleSkills = resolveBundleSkills;
|
|
9
11
|
exports.resolveBundleAgents = resolveBundleAgents;
|
|
10
12
|
exports.defaultScopeForBundle = defaultScopeForBundle;
|
|
13
|
+
exports.inspectAllBundles = inspectAllBundles;
|
|
11
14
|
exports.discoverAllBundles = discoverAllBundles;
|
|
12
15
|
exports.resolveBundleClosure = resolveBundleClosure;
|
|
13
16
|
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
14
18
|
const path_1 = __importDefault(require("path"));
|
|
15
19
|
const registries_1 = require("./registries");
|
|
20
|
+
const text_1 = require("./text");
|
|
16
21
|
function catalogPath(contentDir) {
|
|
17
22
|
return path_1.default.join(contentDir, 'catalog.json');
|
|
18
23
|
}
|
|
@@ -85,29 +90,63 @@ function readCatalog(contentDir) {
|
|
|
85
90
|
// TypeError opaco mas adelante; se descarta aca, nombrando el archivo.
|
|
86
91
|
return bundles.filter((entry) => entry !== null && typeof entry === 'object' && typeof entry.source === 'string');
|
|
87
92
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
function safeManifestIdentity(manifestPath) {
|
|
94
|
+
const sanitized = (0, text_1.sanitizeDiagnosticText)(manifestPath);
|
|
95
|
+
if (sanitized.length <= 240)
|
|
96
|
+
return sanitized;
|
|
97
|
+
const suffix = crypto_1.default.createHash('sha256').update(sanitized).digest('hex').slice(0, 16);
|
|
98
|
+
return `${sanitized.slice(0, 220)}…#${suffix}`;
|
|
99
|
+
}
|
|
100
|
+
function boundedDiagnostic(diagnostic) {
|
|
101
|
+
return (0, text_1.sanitizeDiagnosticText)(diagnostic).slice(0, 512);
|
|
102
|
+
}
|
|
103
|
+
/** Normaliza la única forma de compatibilidad CLI 9 antes de que el manifest
|
|
104
|
+
* alcance consumidores operativos. Los objetos nunca salen de este límite. */
|
|
105
|
+
function normalizeSkillRefs(raw, manifestPath) {
|
|
106
|
+
const identity = safeManifestIdentity(manifestPath);
|
|
107
|
+
if (!Array.isArray(raw)) {
|
|
108
|
+
throw new Error(`${identity}: "skills" must be an array.`);
|
|
109
|
+
}
|
|
110
|
+
const skills = [];
|
|
111
|
+
let legacyCount = 0;
|
|
112
|
+
raw.forEach((ref, index) => {
|
|
113
|
+
const location = `${identity}: skills[${index}]`;
|
|
114
|
+
if (typeof ref === 'string') {
|
|
115
|
+
if (ref === '')
|
|
116
|
+
throw new Error(`${location} must be a non-empty string.`);
|
|
117
|
+
skills.push(ref);
|
|
118
|
+
return;
|
|
99
119
|
}
|
|
100
|
-
|
|
120
|
+
if (ref === null || typeof ref !== 'object' || Array.isArray(ref)) {
|
|
121
|
+
throw new Error(`${location} must be a non-empty string or a supported legacy object.`);
|
|
122
|
+
}
|
|
123
|
+
const legacy = ref;
|
|
124
|
+
const keys = Object.keys(legacy);
|
|
125
|
+
if (!keys.every((key) => key === 'name' || key === 'onSignal')) {
|
|
126
|
+
throw new Error(`${location} legacy object may contain only "name" and optional "onSignal".`);
|
|
127
|
+
}
|
|
128
|
+
if (typeof legacy.name !== 'string' || legacy.name === '') {
|
|
129
|
+
throw new Error(`${location} legacy object "name" must be a non-empty string.`);
|
|
130
|
+
}
|
|
131
|
+
if (legacy.onSignal !== undefined && typeof legacy.onSignal !== 'boolean') {
|
|
132
|
+
throw new Error(`${location} legacy object "onSignal" must be a boolean.`);
|
|
133
|
+
}
|
|
134
|
+
skills.push(legacy.name);
|
|
135
|
+
legacyCount++;
|
|
101
136
|
});
|
|
137
|
+
const diagnostics = legacyCount === 0 ? [] : [boundedDiagnostic(`${identity}: ${legacyCount} legacy object skill reference(s) accepted for CLI 9; ` +
|
|
138
|
+
'replace with canonical skills: ["skill-name"]. Legacy object support is removed in v10.')];
|
|
139
|
+
return { skills, diagnostics };
|
|
102
140
|
}
|
|
103
141
|
/** Array de strings, o vacio. Cualquier otra forma en el contenido del registry
|
|
104
142
|
* se ignora en vez de propagarse hasta un crash aguas abajo. */
|
|
105
143
|
function stringArray(raw) {
|
|
106
144
|
return Array.isArray(raw) ? raw.filter((v) => typeof v === 'string') : [];
|
|
107
145
|
}
|
|
108
|
-
function
|
|
146
|
+
function inspectBundles(contentDir) {
|
|
109
147
|
const entries = readCatalog(contentDir);
|
|
110
148
|
const bundles = [];
|
|
149
|
+
const diagnostics = [];
|
|
111
150
|
for (const entry of entries) {
|
|
112
151
|
const manifestPath = bundleManifestPath(contentDir, entry.source);
|
|
113
152
|
if (!(0, registries_1.assertRegularRegistryFile)(manifestPath))
|
|
@@ -128,6 +167,8 @@ function discoverBundles(contentDir) {
|
|
|
128
167
|
if (typeof m.name !== 'string' || m.name === '') {
|
|
129
168
|
throw new Error(`${manifestPath}: "name" must be a non-empty string.`);
|
|
130
169
|
}
|
|
170
|
+
const normalizedSkills = normalizeSkillRefs(m.skills, manifestPath);
|
|
171
|
+
diagnostics.push(...normalizedSkills.diagnostics);
|
|
131
172
|
bundles.push({
|
|
132
173
|
name: m.name,
|
|
133
174
|
description: typeof m.description === 'string' ? m.description : '',
|
|
@@ -135,13 +176,36 @@ function discoverBundles(contentDir) {
|
|
|
135
176
|
scope: m.scope ?? 'project',
|
|
136
177
|
visibility: m.visibility ?? 'public',
|
|
137
178
|
dependsOn: stringArray(m.dependsOn),
|
|
138
|
-
skills:
|
|
179
|
+
skills: normalizedSkills.skills,
|
|
139
180
|
workflows: stringArray(m.workflows),
|
|
140
181
|
agents: stringArray(m.agents),
|
|
141
182
|
contentRoot: contentDir,
|
|
142
183
|
});
|
|
143
184
|
}
|
|
144
|
-
return bundles;
|
|
185
|
+
return { bundles, diagnostics };
|
|
186
|
+
}
|
|
187
|
+
/** Creates the command-scoped CLI reporter used by discovery facades. */
|
|
188
|
+
function createBundleDiagnosticReporter(write = (message) => console.error(message)) {
|
|
189
|
+
const emitted = new Set();
|
|
190
|
+
return (diagnostic) => {
|
|
191
|
+
const safeDiagnostic = boundedDiagnostic(diagnostic);
|
|
192
|
+
if (emitted.has(safeDiagnostic))
|
|
193
|
+
return;
|
|
194
|
+
emitted.add(safeDiagnostic);
|
|
195
|
+
write(`warning: ${safeDiagnostic}`);
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const defaultBundleDiagnosticReporter = createBundleDiagnosticReporter();
|
|
199
|
+
function reportDiagnostics(diagnostics, reporter) {
|
|
200
|
+
const emit = reporter ?? defaultBundleDiagnosticReporter;
|
|
201
|
+
for (const diagnostic of new Set(diagnostics))
|
|
202
|
+
emit(diagnostic);
|
|
203
|
+
}
|
|
204
|
+
/** Compatibility facade for callers that only need bundle definitions. */
|
|
205
|
+
function discoverBundles(contentDir, reporter) {
|
|
206
|
+
const result = inspectBundles(contentDir);
|
|
207
|
+
reportDiagnostics(result.diagnostics, reporter);
|
|
208
|
+
return result.bundles;
|
|
145
209
|
}
|
|
146
210
|
function resolveBundleSkills(bundleName, bundles) {
|
|
147
211
|
const byName = new Map(bundles.map((b) => [b.name, b]));
|
|
@@ -157,7 +221,7 @@ function resolveBundleSkills(bundleName, bundles) {
|
|
|
157
221
|
for (const dep of b.dependsOn)
|
|
158
222
|
visit(dep);
|
|
159
223
|
for (const s of b.skills)
|
|
160
|
-
skills.add(s
|
|
224
|
+
skills.add(s);
|
|
161
225
|
};
|
|
162
226
|
visit(bundleName);
|
|
163
227
|
return Array.from(skills);
|
|
@@ -192,11 +256,14 @@ function defaultScopeForBundle(scope) {
|
|
|
192
256
|
/** Descubre bundles de TODOS los roots (base + registries adicionales).
|
|
193
257
|
* Colisión de nombre entre roots: override declarado en awm-registry.json
|
|
194
258
|
* del root posterior → reemplaza; no declarado → error nombrando ambas fuentes. */
|
|
195
|
-
function
|
|
259
|
+
function inspectAllBundles(roots = (0, registries_1.contentRoots)()) {
|
|
196
260
|
const byName = new Map();
|
|
261
|
+
const diagnostics = [];
|
|
197
262
|
for (const root of roots) {
|
|
198
263
|
const overrides = (0, registries_1.readRegistryManifest)(root).overrides;
|
|
199
|
-
|
|
264
|
+
const result = inspectBundles(root);
|
|
265
|
+
diagnostics.push(...result.diagnostics);
|
|
266
|
+
for (const b of result.bundles) {
|
|
200
267
|
const prev = byName.get(b.name);
|
|
201
268
|
if (!prev) {
|
|
202
269
|
byName.set(b.name, b);
|
|
@@ -210,7 +277,13 @@ function discoverAllBundles(roots = (0, registries_1.contentRoots)()) {
|
|
|
210
277
|
`Remove or rename one of them, or declare "${b.name}" in "overrides" of the later registry's awm-registry.json.`);
|
|
211
278
|
}
|
|
212
279
|
}
|
|
213
|
-
return Array.from(byName.values());
|
|
280
|
+
return { bundles: Array.from(byName.values()), diagnostics: Array.from(new Set(diagnostics)) };
|
|
281
|
+
}
|
|
282
|
+
/** Compatibility facade for callers that only need bundle definitions. */
|
|
283
|
+
function discoverAllBundles(roots = (0, registries_1.contentRoots)(), reporter) {
|
|
284
|
+
const result = inspectAllBundles(roots);
|
|
285
|
+
reportDiagnostics(result.diagnostics, reporter);
|
|
286
|
+
return result.bundles;
|
|
214
287
|
}
|
|
215
288
|
/**
|
|
216
289
|
* Resolves the dependency closure of a bundle in deps-first order, deduped.
|
|
@@ -307,7 +307,7 @@ function gatherProject(root, bundles, agent = 'claude-code') {
|
|
|
307
307
|
}
|
|
308
308
|
function gatherContext(opts = {}) {
|
|
309
309
|
const cwd = opts.cwd ?? process.cwd();
|
|
310
|
-
const bundles = opts.bundles ?? (0, bundles_1.discoverAllBundles)();
|
|
310
|
+
const bundles = opts.bundles ?? (0, bundles_1.discoverAllBundles)(undefined, opts.reporter);
|
|
311
311
|
const agent = opts.agent ?? 'claude-code';
|
|
312
312
|
const root = (0, profile_1.findProjectRoot)(cwd);
|
|
313
313
|
const agents = opts.agents ?? [agent];
|
|
@@ -23,7 +23,7 @@ function runExport(opts) {
|
|
|
23
23
|
}
|
|
24
24
|
const roots = opts.roots ?? (0, registries_1.contentRoots)();
|
|
25
25
|
const outDir = path_1.default.join(opts.out ?? path_1.default.join(process.cwd(), 'awm-export'), target);
|
|
26
|
-
const resolution = (0, resolve_1.resolveExport)(opts.name, roots);
|
|
26
|
+
const resolution = (0, resolve_1.resolveExport)(opts.name, roots, opts.reporter);
|
|
27
27
|
fs_1.default.mkdirSync(outDir, { recursive: true });
|
|
28
28
|
const exported = [];
|
|
29
29
|
let zipAvailable = true;
|
|
@@ -44,8 +44,8 @@ function assertOverrideConsistency(s) {
|
|
|
44
44
|
throw new Error(`Inconsistent metadata for skill "${s.name}": ${OVERRIDE_FILE} exists but SKILL.md does not declare portable: true.`);
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
|
-
function resolveExport(requested, roots) {
|
|
48
|
-
const bundles = (0, bundles_1.discoverAllBundles)(roots);
|
|
47
|
+
function resolveExport(requested, roots, reporter) {
|
|
48
|
+
const bundles = (0, bundles_1.discoverAllBundles)(roots, reporter);
|
|
49
49
|
if (bundles.some((b) => b.name === requested)) {
|
|
50
50
|
const skills = [];
|
|
51
51
|
const skipped = [];
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.reconcileUnattendedRecovery = reconcileUnattendedRecovery;
|
|
6
7
|
exports.resolveWorkingDirectory = resolveWorkingDirectory;
|
|
7
8
|
exports.computeFingerprint = computeFingerprint;
|
|
8
9
|
const crypto_1 = __importDefault(require("crypto"));
|
|
@@ -19,8 +20,41 @@ function sha(parts) {
|
|
|
19
20
|
// el stderr de git hacia el stderr DEL SUPERVISOR — si ese fd es un pipe roto, el
|
|
20
21
|
// relay dispara un EPIPE no catcheable que crashea el proceso ENTERO (este helper
|
|
21
22
|
// backea computeFingerprint, invocado en CADA tick via FingerprintNow/computeGate).
|
|
23
|
+
/** Git's textual plumbing is hashed, never streamed to a terminal. Keep its
|
|
24
|
+
* memory bounded even for hostile repositories; an incomplete listing must
|
|
25
|
+
* fail recovery rather than certify a partial fingerprint. */
|
|
26
|
+
const MAX_GIT_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
22
27
|
function git(cwd, args) {
|
|
23
|
-
|
|
28
|
+
try {
|
|
29
|
+
return (0, child_process_1.execFileSync)('git', args, { cwd, encoding: 'utf8', maxBuffer: MAX_GIT_OUTPUT_BYTES, stdio: process_1.EXEC_STDIO });
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
const code = error.code;
|
|
33
|
+
if (code === 'ENOBUFS' || error.message.includes('maxBuffer')) {
|
|
34
|
+
throw new Error(`git fingerprint output exceeded ${MAX_GIT_OUTPUT_BYTES} bytes`);
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function reconcileUnattendedRecovery(input) {
|
|
40
|
+
const activeJobIds = [...new Set(input.activeJobIds)].sort();
|
|
41
|
+
const result = (state, nextAction, diagnostics) => ({ state, nextAction, activeJobIds, diagnostics });
|
|
42
|
+
if (input.journalCorrupt || input.journal === null)
|
|
43
|
+
return result('blocked', 'repair-journal', ['journal-corrupt-or-missing']);
|
|
44
|
+
const binding = input.journal.schema === 2 ? input.journal.planBinding : undefined;
|
|
45
|
+
if (!binding || binding.path !== input.plan.path || binding.digest !== input.plan.digest || binding.schema !== input.plan.schema || binding.executionMode !== 'desatendido')
|
|
46
|
+
return result('blocked', 'rebind-plan', ['journal-plan-binding-stale']);
|
|
47
|
+
if (input.git !== 'current')
|
|
48
|
+
return result('blocked', 'reconcile-git', ['git-fingerprint-changed']);
|
|
49
|
+
if (activeJobIds.length > 0)
|
|
50
|
+
return result('ready', 'reconcile-active-jobs', []);
|
|
51
|
+
if (input.verdicts !== 'current')
|
|
52
|
+
return result('blocked', 'repair-verdicts', [`verdicts-${input.verdicts}`]);
|
|
53
|
+
if (input.tests !== 'pass')
|
|
54
|
+
return result('blocked', 'run-tests', [`tests-${input.tests}`]);
|
|
55
|
+
if (input.sensors !== 'pass')
|
|
56
|
+
return result('blocked', 'run-sensors', [`sensors-${input.sensors}`]);
|
|
57
|
+
return result('ready', 'select-work', []);
|
|
24
58
|
}
|
|
25
59
|
function resolveWorkingDirectory(repoRoot, cwdRel) {
|
|
26
60
|
if (typeof cwdRel !== 'string' || cwdRel.length === 0)
|
|
@@ -13,6 +13,7 @@ exports.acksDir = acksDir;
|
|
|
13
13
|
exports.logsDir = logsDir;
|
|
14
14
|
exports.eventsPath = eventsPath;
|
|
15
15
|
exports.exportDir = exportDir;
|
|
16
|
+
exports.bindingPlanPath = bindingPlanPath;
|
|
16
17
|
const fs_1 = __importDefault(require("fs"));
|
|
17
18
|
const path_1 = __importDefault(require("path"));
|
|
18
19
|
function branchSlug(branch) {
|
|
@@ -63,3 +64,13 @@ function eventsPath(repoRoot, branch) {
|
|
|
63
64
|
function exportDir(repoRoot, branch) {
|
|
64
65
|
return path_1.default.join(journalDir(repoRoot, branch), 'export');
|
|
65
66
|
}
|
|
67
|
+
/** A persisted binding path is always a normalized repository-relative POSIX path. */
|
|
68
|
+
function bindingPlanPath(planPath) {
|
|
69
|
+
if (typeof planPath !== 'string' || planPath.length === 0 || planPath.length > 4096 || /[\u0000-\u001F\u007F-\u009F]/.test(planPath))
|
|
70
|
+
throw new Error('plan path inválido para binding');
|
|
71
|
+
const normalized = path_1.default.posix.normalize(planPath.replace(/\\/g, '/'));
|
|
72
|
+
if (path_1.default.posix.isAbsolute(normalized) || path_1.default.win32.isAbsolute(planPath) || normalized === '..' || normalized.startsWith('../') || normalized === '.') {
|
|
73
|
+
throw new Error('plan path fuera del repositorio para binding');
|
|
74
|
+
}
|
|
75
|
+
return normalized;
|
|
76
|
+
}
|