agentic-workflow-manager 9.4.0 → 9.5.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/dist/src/commands/preflight/checks.js +56 -25
- package/dist/src/commands/sensors/compatibility/manifest.js +46 -1
- package/dist/src/commands/sensors/compatibility/pack-source.js +61 -17
- package/dist/src/commands/sensors/compatibility/safe-file.js +51 -0
- package/dist/src/commands/sensors/compatibility/source.js +75 -0
- package/dist/src/commands/sensors/coverage/resolve.js +17 -21
- package/dist/src/commands/sensors/init.js +3 -1
- package/dist/src/commands/sensors/project.js +107 -0
- package/dist/src/commands/sensors/run.js +64 -45
- package/dist/src/commands/sensors/status.js +97 -56
- package/dist/src/core/currentness/check.js +4 -5
- package/dist/tests/commands/preflight/preflight.test.js +31 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +23 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +216 -0
- package/dist/tests/commands/sensors/compatibility/source.test.js +121 -0
- package/dist/tests/commands/sensors/coverage/resolve.test.js +26 -34
- package/dist/tests/commands/sensors/project.test.js +102 -0
- package/dist/tests/commands/sensors/run.test.js +107 -4
- package/dist/tests/commands/sensors/status.test.js +106 -2
- package/dist/tests/core/currentness/check.test.js +13 -3
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +5 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +4 -21
- package/dist/tests/integration/sensor-coverage.e2e.test.js +15 -24
- package/package.json +1 -1
|
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const run_1 = require("../../../src/commands/sensors/run");
|
|
10
|
+
const status_1 = require("../../../src/commands/sensors/status");
|
|
10
11
|
const verdict_1 = require("../../../src/commands/sensors/verdict");
|
|
11
12
|
function mkTmp() {
|
|
12
13
|
return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-sensors-'));
|
|
@@ -14,10 +15,18 @@ function mkTmp() {
|
|
|
14
15
|
// Define stable mock before jest.mock hoisting
|
|
15
16
|
const mockRunCommand = jest.fn();
|
|
16
17
|
const mockRunStructuredCommand = jest.fn();
|
|
18
|
+
const mockResolveSensorProject = jest.fn((startCwd) => {
|
|
19
|
+
const actual = jest.requireActual('../../../src/commands/sensors/project');
|
|
20
|
+
return actual.resolveSensorProject(startCwd);
|
|
21
|
+
});
|
|
17
22
|
jest.mock('../../../src/commands/sensors/exec', () => ({
|
|
18
23
|
runCommand: (...args) => mockRunCommand(...args),
|
|
19
24
|
runStructuredCommand: (...args) => mockRunStructuredCommand(...args),
|
|
20
25
|
}));
|
|
26
|
+
jest.mock('../../../src/commands/sensors/project', () => {
|
|
27
|
+
const actual = jest.requireActual('../../../src/commands/sensors/project');
|
|
28
|
+
return { ...actual, resolveSensorProject: (startCwd) => mockResolveSensorProject(startCwd) };
|
|
29
|
+
});
|
|
21
30
|
const { ok, exited, timedOut } = require('./exec-fixtures');
|
|
22
31
|
const MANIFEST = {
|
|
23
32
|
pack: 'js-ts',
|
|
@@ -39,6 +48,7 @@ describe('runSensors', () => {
|
|
|
39
48
|
fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(MANIFEST));
|
|
40
49
|
mockRunCommand.mockReset();
|
|
41
50
|
mockRunStructuredCommand.mockReset();
|
|
51
|
+
mockResolveSensorProject.mockClear();
|
|
42
52
|
});
|
|
43
53
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
44
54
|
const load = () => require('../../../src/commands/sensors/run');
|
|
@@ -282,6 +292,22 @@ describe('runSensors v2 lifecycle contract', () => {
|
|
|
282
292
|
expect(result).toEqual(expect.objectContaining({ overall: 'pass' }));
|
|
283
293
|
expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
|
|
284
294
|
});
|
|
295
|
+
it('rebinds a missing v2 registryRoot through the unique configured source without mutation', async () => {
|
|
296
|
+
const manifestPath = path_1.default.join(project, '.awm', 'sensors.json');
|
|
297
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
298
|
+
delete manifest.registryRoot;
|
|
299
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest));
|
|
300
|
+
const before = fs_1.default.readFileSync(manifestPath, 'utf8');
|
|
301
|
+
const inventoryPath = path_1.default.join(home, 'registries.json');
|
|
302
|
+
const inventoryBefore = fs_1.default.readFileSync(inventoryPath, 'utf8');
|
|
303
|
+
const { runSensors } = require('../../../src/commands/sensors/run');
|
|
304
|
+
const result = await runSensors({ cwd: project, fast: true });
|
|
305
|
+
expect(result).toMatchObject({ overall: 'pass', mode: 'project-sensors', reason: 'legacy-rebound' });
|
|
306
|
+
expect(result.source).toMatchObject({ kind: 'legacy-rebound', registry: 'baseline' });
|
|
307
|
+
expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
|
|
308
|
+
expect(fs_1.default.readFileSync(manifestPath, 'utf8')).toBe(before);
|
|
309
|
+
expect(fs_1.default.readFileSync(inventoryPath, 'utf8')).toBe(inventoryBefore);
|
|
310
|
+
});
|
|
285
311
|
it('scopes applicability detection and execution to packageRoot for a monorepo manifest', async () => {
|
|
286
312
|
// Move the fixture project into a subdirectory ("cli") and point the
|
|
287
313
|
// manifest at it via packageRoot — mirrors a monorepo where the
|
|
@@ -388,15 +414,92 @@ describe('runSensors — not_certified + auto-discovery', () => {
|
|
|
388
414
|
expect(out.overall).toBe('not_certified');
|
|
389
415
|
expect(out.sensors).toEqual([]);
|
|
390
416
|
});
|
|
391
|
-
it('
|
|
417
|
+
it('uses the same stable schema-unsupported reason as status', async () => {
|
|
418
|
+
tmpDir = mkTmp();
|
|
419
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
420
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
|
|
421
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 99 }));
|
|
422
|
+
const status = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
423
|
+
const run = await (0, run_1.runSensors)({ cwd: tmpDir });
|
|
424
|
+
expect(status).toMatchObject({ mode: 'invalid', reason: 'schema-unsupported' });
|
|
425
|
+
expect(run).toMatchObject({ mode: 'invalid', reason: 'schema-unsupported' });
|
|
426
|
+
});
|
|
427
|
+
it('resolves the same legacy project and execution cwd from root and nested CWD', async () => {
|
|
392
428
|
tmpDir = mkTmp();
|
|
429
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
393
430
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
|
|
394
431
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'test', sensors: { noop: { cmd: 'echo ok', fast: true } } }));
|
|
395
432
|
const nested = path_1.default.join(tmpDir, 'a', 'b');
|
|
396
433
|
fs_1.default.mkdirSync(nested, { recursive: true });
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
expect(
|
|
434
|
+
const rootRun = await (0, run_1.runSensors)({ cwd: tmpDir });
|
|
435
|
+
const nestedRun = await (0, run_1.runSensors)({ cwd: nested });
|
|
436
|
+
expect(nestedRun).toEqual(rootRun);
|
|
437
|
+
expect(rootRun.overall).toBe('not_certified');
|
|
438
|
+
expect(rootRun.sensors).toHaveLength(1);
|
|
439
|
+
expect(mockResolveSensorProject).toHaveBeenCalledWith(tmpDir);
|
|
440
|
+
expect(mockResolveSensorProject).toHaveBeenCalledWith(nested);
|
|
441
|
+
expect(mockRunCommand).toHaveBeenNthCalledWith(1, 'echo ok', expect.objectContaining({ cwd: tmpDir }));
|
|
442
|
+
expect(mockRunCommand).toHaveBeenNthCalledWith(2, 'echo ok', expect.objectContaining({ cwd: tmpDir }));
|
|
443
|
+
});
|
|
444
|
+
it.each([
|
|
445
|
+
['native-gate', 'Release-blocking CI is authoritative.'],
|
|
446
|
+
['opt-out', 'This repository intentionally has no local gate.'],
|
|
447
|
+
])('does not dispatch v3 %s declarations', async (mode, declarationReason) => {
|
|
448
|
+
tmpDir = mkTmp();
|
|
449
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
450
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
451
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 3, mode, reason: declarationReason }));
|
|
452
|
+
const out = await (0, run_1.runSensors)({ cwd: tmpDir, all: true });
|
|
453
|
+
expect(out).toMatchObject({
|
|
454
|
+
overall: 'not_certified', mode, reason: `${mode}-declared`,
|
|
455
|
+
declarationReason,
|
|
456
|
+
projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
457
|
+
});
|
|
458
|
+
expect(out.sensors).toEqual([]);
|
|
459
|
+
expect(mockRunCommand).not.toHaveBeenCalled();
|
|
460
|
+
expect(mockRunStructuredCommand).not.toHaveBeenCalled();
|
|
461
|
+
});
|
|
462
|
+
it('reports bounded candidates and a remedy when v2 source resolution is ambiguous', async () => {
|
|
463
|
+
const previousHome = process.env.AWM_HOME;
|
|
464
|
+
const home = mkTmp();
|
|
465
|
+
try {
|
|
466
|
+
process.env.AWM_HOME = home;
|
|
467
|
+
const pack = {
|
|
468
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
469
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
|
|
470
|
+
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
471
|
+
id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
|
|
472
|
+
requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
|
|
473
|
+
assets: [], formatter: 'generic', probe: { kind: 'package-script-present' },
|
|
474
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
475
|
+
}] } },
|
|
476
|
+
};
|
|
477
|
+
const registries = ['alpha', 'beta'];
|
|
478
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify(registries.map(name => ({ name, remote: `https://example.test/${name}.git` }))));
|
|
479
|
+
for (const name of registries) {
|
|
480
|
+
const packDir = path_1.default.join(home, 'registries', name, 'sensor-packs', 'js-ts');
|
|
481
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
482
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify(pack));
|
|
483
|
+
}
|
|
484
|
+
tmpDir = mkTmp();
|
|
485
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
486
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
487
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {} }));
|
|
488
|
+
const out = await (0, run_1.runSensors)({ cwd: tmpDir });
|
|
489
|
+
expect(out).toMatchObject({
|
|
490
|
+
overall: 'not_certified', mode: 'source-ambiguous', reason: 'multiple-compatible-registries',
|
|
491
|
+
remedy: 'configure-one-logical-registry',
|
|
492
|
+
source: { kind: 'source-ambiguous', candidates: ['alpha', 'beta'] },
|
|
493
|
+
});
|
|
494
|
+
expect(out.source.candidates).toHaveLength(2);
|
|
495
|
+
}
|
|
496
|
+
finally {
|
|
497
|
+
if (previousHome === undefined)
|
|
498
|
+
delete process.env.AWM_HOME;
|
|
499
|
+
else
|
|
500
|
+
process.env.AWM_HOME = previousHome;
|
|
501
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
502
|
+
}
|
|
400
503
|
});
|
|
401
504
|
});
|
|
402
505
|
describe('detectPackDrift', () => {
|
|
@@ -46,6 +46,26 @@ describe('computeSensorStatus', () => {
|
|
|
46
46
|
expect(result.overall).toBe('NOT_CONFIGURED');
|
|
47
47
|
expect(result.pack).toBeNull();
|
|
48
48
|
});
|
|
49
|
+
it('resolves the Git-bounded project manifest from a nested CWD', async () => {
|
|
50
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
51
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
52
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', '.bin'), { recursive: true });
|
|
53
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', '.bin', 'tsc'), '');
|
|
54
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
55
|
+
pack: 'js-ts', sensors: { typecheck: { cmd: 'npx tsc --noEmit', fast: true } },
|
|
56
|
+
}));
|
|
57
|
+
const nestedCwd = path_1.default.join(tmpDir, 'packages', 'app');
|
|
58
|
+
fs_1.default.mkdirSync(nestedCwd, { recursive: true });
|
|
59
|
+
await expect((0, status_1.computeSensorStatus)(nestedCwd)).resolves.toMatchObject({
|
|
60
|
+
overall: 'READY',
|
|
61
|
+
pack: 'js-ts',
|
|
62
|
+
mode: 'project-sensors',
|
|
63
|
+
reason: 'legacy-v1',
|
|
64
|
+
projectRoot: tmpDir,
|
|
65
|
+
manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
66
|
+
checks: { typecheck: { ok: true } },
|
|
67
|
+
});
|
|
68
|
+
});
|
|
49
69
|
it.each([
|
|
50
70
|
['valid manifest', 'READY', () => {
|
|
51
71
|
installLocalBin('eslint');
|
|
@@ -218,17 +238,24 @@ describe('computeSensorStatus', () => {
|
|
|
218
238
|
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'node_modules', '.bin', 'eslint'), '');
|
|
219
239
|
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'eslint.config.awm.mjs'), 'export default []');
|
|
220
240
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
221
|
-
|
|
222
|
-
|
|
241
|
+
const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
|
|
242
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify({
|
|
243
|
+
schemaVersion: 2, pack: 'js-ts', packageRoot: 'cli',
|
|
223
244
|
sensors: { lint: {
|
|
224
245
|
enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--config', 'eslint.config.awm.mjs'] },
|
|
225
246
|
assets: ['eslint.config.awm.mjs'],
|
|
226
247
|
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.4.1', runtimeVersion: process.versions.node, certifiedRange: '>=10.0.0 <11.0.0', evidence: [] },
|
|
227
248
|
} },
|
|
228
249
|
}));
|
|
250
|
+
const manifestBefore = fs_1.default.readFileSync(manifestPath);
|
|
251
|
+
const inventoryPath = path_1.default.join(home, 'registries.json');
|
|
252
|
+
const inventoryBefore = fs_1.default.readFileSync(inventoryPath);
|
|
229
253
|
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
230
254
|
expect(result.checks.lint).toMatchObject({ ok: true });
|
|
231
255
|
expect(result.overall).toBe('READY');
|
|
256
|
+
expect(result.source).toMatchObject({ kind: 'legacy-rebound', registry: 'baseline' });
|
|
257
|
+
expect(fs_1.default.readFileSync(manifestPath)).toEqual(manifestBefore);
|
|
258
|
+
expect(fs_1.default.readFileSync(inventoryPath)).toEqual(inventoryBefore);
|
|
232
259
|
expect(runCommand).not.toHaveBeenCalled();
|
|
233
260
|
expect(runStructuredCommand).not.toHaveBeenCalled();
|
|
234
261
|
}
|
|
@@ -304,4 +331,81 @@ describe('computeSensorStatus', () => {
|
|
|
304
331
|
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
305
332
|
}
|
|
306
333
|
});
|
|
334
|
+
it.each([
|
|
335
|
+
['native-gate', 'Release-blocking CI is authoritative.', 'native-gate-declared'],
|
|
336
|
+
['opt-out', 'This repository intentionally has no local gate.', 'opt-out-declared'],
|
|
337
|
+
])('reports v3 %s without dispatching sensors', async (mode, declarationReason, diagnosticReason) => {
|
|
338
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
339
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
340
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 3, mode, reason: declarationReason }));
|
|
341
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
|
|
342
|
+
overall: 'NOT_CONFIGURED', mode, reason: diagnosticReason,
|
|
343
|
+
declarationReason,
|
|
344
|
+
projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
345
|
+
});
|
|
346
|
+
expect(runCommand).not.toHaveBeenCalled();
|
|
347
|
+
expect(runStructuredCommand).not.toHaveBeenCalled();
|
|
348
|
+
});
|
|
349
|
+
it('reports a v2 source-unavailable state instead of accepting a missing pack', async () => {
|
|
350
|
+
const previousHome = process.env.AWM_HOME;
|
|
351
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-missing-source-home-'));
|
|
352
|
+
try {
|
|
353
|
+
process.env.AWM_HOME = home;
|
|
354
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([]));
|
|
355
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
356
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
357
|
+
schemaVersion: 2, pack: 'js-ts', registryRoot: path_1.default.join(home, 'gone'), sensors: {},
|
|
358
|
+
}));
|
|
359
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
|
|
360
|
+
overall: 'DEGRADED', pack: 'js-ts', mode: 'source-unavailable',
|
|
361
|
+
reason: 'no-compatible-registry', remedy: 'install-registry-or-run-awm-update',
|
|
362
|
+
projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
if (previousHome === undefined)
|
|
367
|
+
delete process.env.AWM_HOME;
|
|
368
|
+
else
|
|
369
|
+
process.env.AWM_HOME = previousHome;
|
|
370
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
it('reports bounded candidates and a remedy when v2 source resolution is ambiguous', async () => {
|
|
374
|
+
const previousHome = process.env.AWM_HOME;
|
|
375
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-ambiguous-home-'));
|
|
376
|
+
try {
|
|
377
|
+
process.env.AWM_HOME = home;
|
|
378
|
+
const pack = {
|
|
379
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
380
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
|
|
381
|
+
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
382
|
+
id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
|
|
383
|
+
requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
|
|
384
|
+
assets: [], formatter: 'generic', probe: { kind: 'package-script-present' },
|
|
385
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
386
|
+
}] } },
|
|
387
|
+
};
|
|
388
|
+
const registries = ['alpha', 'beta'];
|
|
389
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify(registries.map(name => ({ name, remote: `https://example.test/${name}.git` }))));
|
|
390
|
+
for (const name of registries) {
|
|
391
|
+
const packDir = path_1.default.join(home, 'registries', name, 'sensor-packs', 'js-ts');
|
|
392
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
393
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify(pack));
|
|
394
|
+
}
|
|
395
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
|
|
396
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
397
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {} }));
|
|
398
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
|
|
399
|
+
overall: 'DEGRADED', mode: 'source-ambiguous', reason: 'multiple-compatible-registries',
|
|
400
|
+
remedy: 'configure-one-logical-registry', candidates: ['alpha', 'beta'],
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
finally {
|
|
404
|
+
if (previousHome === undefined)
|
|
405
|
+
delete process.env.AWM_HOME;
|
|
406
|
+
else
|
|
407
|
+
process.env.AWM_HOME = previousHome;
|
|
408
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
409
|
+
}
|
|
410
|
+
});
|
|
307
411
|
});
|
|
@@ -22,7 +22,7 @@ describe('checkCurrentness', () => {
|
|
|
22
22
|
now: () => 1_700_000_000_000,
|
|
23
23
|
fetch: jest.fn().mockResolvedValue({
|
|
24
24
|
ok: true,
|
|
25
|
-
text: async () => JSON.stringify({
|
|
25
|
+
text: async () => JSON.stringify({ version: '1.2.3' }),
|
|
26
26
|
}),
|
|
27
27
|
readPreferences: () => ({ pins: {} }),
|
|
28
28
|
listRegistries: () => [{ name: 'baseline', remote: 'https://example.test/team/repo.git', contentRoot: '/registry' }],
|
|
@@ -50,6 +50,16 @@ describe('checkCurrentness', () => {
|
|
|
50
50
|
expect(JSON.stringify(result)).not.toContain('secret');
|
|
51
51
|
expect(result.components[1].source).toBe('https://example.test/team/repo.git');
|
|
52
52
|
});
|
|
53
|
+
it('reads npm currentness from the bounded latest metadata endpoint', async () => {
|
|
54
|
+
const fetch = jest.fn().mockResolvedValue({
|
|
55
|
+
ok: true,
|
|
56
|
+
text: async () => JSON.stringify({ version: '1.2.3' }),
|
|
57
|
+
});
|
|
58
|
+
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
59
|
+
const result = await checkCurrentness(root, deps({ fetch }));
|
|
60
|
+
expect(fetch).toHaveBeenCalledWith('https://registry.npmjs.org/agentic-workflow-manager/latest', expect.objectContaining({ signal: expect.any(AbortSignal) }));
|
|
61
|
+
expect(result.components[0]).toEqual(expect.objectContaining({ status: 'current', latest: '1.2.3' }));
|
|
62
|
+
});
|
|
53
63
|
it('marks an older pinned registry as pinned-behind with the unpin remedy', async () => {
|
|
54
64
|
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
55
65
|
const result = await checkCurrentness(root, deps({
|
|
@@ -78,7 +88,7 @@ describe('checkCurrentness', () => {
|
|
|
78
88
|
cliVersion: () => '1.9007199254740992.0',
|
|
79
89
|
fetch: jest.fn().mockResolvedValue({
|
|
80
90
|
ok: true,
|
|
81
|
-
text: async () => JSON.stringify({
|
|
91
|
+
text: async () => JSON.stringify({ version: '1.9007199254740993.0' }),
|
|
82
92
|
}),
|
|
83
93
|
}));
|
|
84
94
|
expect(result.components[0]).toEqual(expect.objectContaining({
|
|
@@ -151,7 +161,7 @@ describe('checkCurrentness', () => {
|
|
|
151
161
|
])('fails closed for %s', async (name, git) => {
|
|
152
162
|
const { checkCurrentness } = require('../../../src/core/currentness/check');
|
|
153
163
|
const result = await checkCurrentness(root, deps(name === 'malformed npm semver'
|
|
154
|
-
? { fetch: jest.fn().mockResolvedValue({ ok: true, text: async () => JSON.stringify({
|
|
164
|
+
? { fetch: jest.fn().mockResolvedValue({ ok: true, text: async () => JSON.stringify({ version: 'latest' }) }) }
|
|
155
165
|
: { git }));
|
|
156
166
|
expect(result.components.some((c) => c.status === 'unverifiable')).toBe(true);
|
|
157
167
|
});
|
|
@@ -59,6 +59,11 @@ test('preserves actionable no-manifest preflight JSON when verify-sensors is pip
|
|
|
59
59
|
expect(result.status).toBe(1);
|
|
60
60
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
61
61
|
status: 'not_configured',
|
|
62
|
+
mode: 'missing',
|
|
63
|
+
reason: 'manifest-absent',
|
|
64
|
+
projectRoot: project,
|
|
65
|
+
manifestPath: path_1.default.join(project, '.awm', 'sensors.json'),
|
|
66
|
+
remedy: expect.any(String),
|
|
62
67
|
checks: expect.arrayContaining([expect.objectContaining({
|
|
63
68
|
id: 'sensors-execution', ok: false,
|
|
64
69
|
detail: 'sensor verdict was not_certified; no sensor established an empirical pass',
|
|
@@ -17,12 +17,6 @@ const bin = path_1.default.join(cliDir, 'dist/src/index.js');
|
|
|
17
17
|
const fixtureRoot = path_1.default.join(cliDir, 'tests/fixtures/sensor-compatibility');
|
|
18
18
|
const projectFixture = path_1.default.join(fixtureRoot, 'project');
|
|
19
19
|
const registryFixture = path_1.default.join(fixtureRoot, 'registry');
|
|
20
|
-
const noFollowUnavailableOnNativeWindows = process.platform === 'win32'
|
|
21
|
-
&& typeof fs_1.default.constants.O_NOFOLLOW !== 'number';
|
|
22
|
-
// Other native platforms exercise coverage through testWithNoFollow; this
|
|
23
|
-
// branch preserves the deliberate Windows fail-closed behavior instead.
|
|
24
|
-
const testWithNoFollow = noFollowUnavailableOnNativeWindows ? test.skip : test;
|
|
25
|
-
const testWithoutNoFollowOnNativeWindows = noFollowUnavailableOnNativeWindows ? test : test.skip;
|
|
26
20
|
function hashTree(root) {
|
|
27
21
|
const hash = crypto_1.default.createHash('sha256');
|
|
28
22
|
const walk = (directory) => {
|
|
@@ -97,7 +91,7 @@ test.each(['linux', 'darwin', 'win32'])('keeps injected resolver semantics consi
|
|
|
97
91
|
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
98
92
|
}
|
|
99
93
|
});
|
|
100
|
-
|
|
94
|
+
test('compiled binary dispatches coverage and emits parseable JSON on the native CI platform', () => {
|
|
101
95
|
const fixture = createFixture();
|
|
102
96
|
try {
|
|
103
97
|
const report = json(runCli(fixture, 'coverage', '--json'));
|
|
@@ -107,7 +101,7 @@ testWithNoFollow('compiled binary dispatches coverage and emits parseable JSON o
|
|
|
107
101
|
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
108
102
|
}
|
|
109
103
|
});
|
|
110
|
-
|
|
104
|
+
test('compiled sensors run returns nonzero while preserving parseable not_certified JSON', () => {
|
|
111
105
|
const fixture = createFixture();
|
|
112
106
|
try {
|
|
113
107
|
fs_1.default.rmSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'));
|
|
@@ -119,7 +113,7 @@ testWithNoFollow('compiled sensors run returns nonzero while preserving parseabl
|
|
|
119
113
|
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
120
114
|
}
|
|
121
115
|
});
|
|
122
|
-
|
|
116
|
+
test('compiled status reports static READY without writing or executing the project sensor (R6, R6.2)', () => {
|
|
123
117
|
const fixture = createFixture();
|
|
124
118
|
try {
|
|
125
119
|
const localBin = path_1.default.join(fixture.project, 'node_modules', '.bin', 'eslint');
|
|
@@ -139,7 +133,7 @@ testWithNoFollow('compiled status reports static READY without writing or execut
|
|
|
139
133
|
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
140
134
|
}
|
|
141
135
|
});
|
|
142
|
-
|
|
136
|
+
test('legacy coverage stays unverified, init migrates explicitly, and version drift is visible (R7.2, R7.8)', () => {
|
|
143
137
|
const fixture = createFixture();
|
|
144
138
|
try {
|
|
145
139
|
const before = [hashTree(fixture.project), hashTree(fixture.awmHome)];
|
|
@@ -167,14 +161,3 @@ testWithNoFollow('legacy coverage stays unverified, init migrates explicitly, an
|
|
|
167
161
|
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
168
162
|
}
|
|
169
163
|
});
|
|
170
|
-
testWithoutNoFollowOnNativeWindows('coverage fails closed when native Windows cannot prevent symlink dereference', () => {
|
|
171
|
-
const fixture = createFixture();
|
|
172
|
-
try {
|
|
173
|
-
const result = runCli(fixture, 'coverage', '--json');
|
|
174
|
-
expect(result.status).toBe(1);
|
|
175
|
-
expect(`${result.stdout ?? ''}${result.stderr ?? ''}`).toContain('platform cannot guarantee no symlink dereference');
|
|
176
|
-
}
|
|
177
|
-
finally {
|
|
178
|
-
fs_1.default.rmSync(fixture.root, { recursive: true, force: true });
|
|
179
|
-
}
|
|
180
|
-
});
|
|
@@ -12,9 +12,6 @@ const cliDir = path_1.default.resolve(__dirname, '../..');
|
|
|
12
12
|
const bin = path_1.default.join(cliDir, 'dist/src/index.js');
|
|
13
13
|
const fixture = path_1.default.join(cliDir, 'tests/fixtures/sensor-coverage/js-ts-gap');
|
|
14
14
|
const registryFixture = path_1.default.join(cliDir, 'tests/fixtures/sensor-coverage/registry');
|
|
15
|
-
const noFollowUnavailableOnNativeWindows = process.platform === 'win32'
|
|
16
|
-
&& typeof fs_1.default.constants.O_NOFOLLOW !== 'number';
|
|
17
|
-
const testWithNoFollow = noFollowUnavailableOnNativeWindows ? test.skip : test;
|
|
18
15
|
const hashTree = (root) => {
|
|
19
16
|
const hash = crypto_1.default.createHash('sha256');
|
|
20
17
|
const walk = (directory) => {
|
|
@@ -71,32 +68,26 @@ test('compiled CLI reports static and read-only empirical coverage without leaki
|
|
|
71
68
|
const result = (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', 'coverage', '--json'], {
|
|
72
69
|
cwd: project, encoding: 'utf8', env: { ...process.env, AWM_HOME: awmHome, AWM_NO_UPDATE_CHECK: '1' },
|
|
73
70
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
unclassified: { occurrences: 1 },
|
|
88
|
-
} });
|
|
89
|
-
expect(result.stdout).not.toContain('fixture-secret-description');
|
|
90
|
-
expect(result.stdout).not.toContain('private-lint-signature');
|
|
91
|
-
expect(result.stdout).not.toContain('windows secret');
|
|
92
|
-
}
|
|
71
|
+
expect(result.status).toBe(0);
|
|
72
|
+
const report = JSON.parse(result.stdout ?? '');
|
|
73
|
+
expect(report.static.classes).toEqual(expect.arrayContaining([
|
|
74
|
+
expect.objectContaining({ id: 'formatting', status: 'unverifiable' }),
|
|
75
|
+
expect.objectContaining({ id: 'project-style-conventions', status: 'unverifiable' }),
|
|
76
|
+
]));
|
|
77
|
+
expect(report).toMatchObject({ schemaVersion: 2, empirical: {
|
|
78
|
+
status: 'partial', classes: [expect.objectContaining({ defectClass: 'lint-errors', occurrences: 2, recurrent: true })],
|
|
79
|
+
unclassified: { occurrences: 1 },
|
|
80
|
+
} });
|
|
81
|
+
expect(result.stdout).not.toContain('fixture-secret-description');
|
|
82
|
+
expect(result.stdout).not.toContain('private-lint-signature');
|
|
83
|
+
expect(result.stdout).not.toContain('windows secret');
|
|
93
84
|
expect([hashTree(project), hashTree(awmHome)]).toEqual(before);
|
|
94
85
|
}
|
|
95
86
|
finally {
|
|
96
87
|
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
97
88
|
}
|
|
98
89
|
});
|
|
99
|
-
|
|
90
|
+
test('compiled coverage keeps static verdict stable while --min changes only recurrence emphasis', () => {
|
|
100
91
|
const { tmp, project, awmHome } = runWithFixture();
|
|
101
92
|
try {
|
|
102
93
|
const before = [hashTree(project), hashTree(awmHome)];
|
|
@@ -115,7 +106,7 @@ testWithNoFollow('compiled coverage keeps static verdict stable while --min chan
|
|
|
115
106
|
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
116
107
|
}
|
|
117
108
|
});
|
|
118
|
-
|
|
109
|
+
test('informative states exit zero; malformed contract exits non-zero (R2.7, R2.9)', () => {
|
|
119
110
|
const run = (mutatePack) => {
|
|
120
111
|
const { tmp, project, awmHome } = runWithFixture(mutatePack);
|
|
121
112
|
try {
|