agentic-workflow-manager 6.4.1 → 6.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/add.js +18 -1
- package/dist/src/commands/init.js +11 -2
- package/dist/src/commands/sensors/coverage/contract.js +156 -0
- package/dist/src/commands/sensors/coverage/evaluate.js +107 -0
- package/dist/src/commands/sensors/coverage/evidence.js +118 -0
- package/dist/src/commands/sensors/coverage/index.js +31 -0
- package/dist/src/commands/sensors/coverage/render.js +152 -0
- package/dist/src/commands/sensors/coverage/resolve.js +126 -0
- package/dist/src/commands/sensors/index.js +16 -0
- package/dist/src/core/provider-version.js +26 -2
- package/dist/src/index.js +10 -5
- package/dist/src/utils/config.js +17 -0
- package/dist/tests/commands/add.test.js +42 -0
- package/dist/tests/commands/init.test.js +23 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +101 -0
- package/dist/tests/commands/sensors/coverage/evaluate.test.js +82 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +251 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +30 -0
- package/dist/tests/commands/sensors/coverage/render.test.js +149 -0
- package/dist/tests/commands/sensors/coverage/resolve.test.js +130 -0
- package/dist/tests/commands/sensors/index.test.js +52 -1
- package/dist/tests/commands/sensors/router.test.js +2 -1
- package/dist/tests/core/provider-version.test.js +41 -0
- package/dist/tests/integration/codex-provider-isolated.test.js +15 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +1 -0
- package/dist/tests/integration/sensor-coverage-provider-evidence.test.js +88 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +113 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +14 -0
- package/dist/tests/structural/sensor-configs-are-present.test.js +10 -0
- package/dist/tests/utils/config.test.js +21 -0
- package/package.json +1 -1
|
@@ -10,6 +10,47 @@ describe('assertProviderSupported', () => {
|
|
|
10
10
|
});
|
|
11
11
|
expect(exec).toHaveBeenCalledWith('codex', ['--version'], expect.any(Object));
|
|
12
12
|
});
|
|
13
|
+
// Regression: npm installs `codex` as `codex.cmd` on Windows, and
|
|
14
|
+
// execFileSync can't CreateProcess a `.cmd` shim without a shell — it threw
|
|
15
|
+
// ENOENT here even on a machine where `codex --version` worked fine typed
|
|
16
|
+
// directly. `provider.versionCommand` is hardcoded first-party config
|
|
17
|
+
// (providers/index.ts), never attacker-controlled, so `shell: true` here
|
|
18
|
+
// carries none of the injection risk core/paths.ts's resolveOnPath was
|
|
19
|
+
// built to avoid for sensors.json's user/registry-supplied `cmd`.
|
|
20
|
+
describe('on native Windows', () => {
|
|
21
|
+
const realPlatform = process.platform;
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
Object.defineProperty(process, 'platform', { value: realPlatform, configurable: true });
|
|
24
|
+
});
|
|
25
|
+
it('runs the version probe through a shell so the .cmd shim resolves', () => {
|
|
26
|
+
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
27
|
+
const exec = jest.fn(() => Buffer.from('codex-cli 0.145.0\n'));
|
|
28
|
+
(0, provider_version_1.assertProviderSupported)('codex', exec);
|
|
29
|
+
expect(exec).toHaveBeenCalledWith('codex', ['--version'], expect.objectContaining({ shell: true }));
|
|
30
|
+
});
|
|
31
|
+
it('does not use a shell on non-Windows platforms', () => {
|
|
32
|
+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
|
|
33
|
+
const exec = jest.fn(() => Buffer.from('codex-cli 0.145.0\n'));
|
|
34
|
+
(0, provider_version_1.assertProviderSupported)('codex', exec);
|
|
35
|
+
expect(exec).toHaveBeenCalledWith('codex', ['--version'], expect.objectContaining({ shell: false }));
|
|
36
|
+
});
|
|
37
|
+
// Regression from the fix above: shipping shell:true changed how a
|
|
38
|
+
// GENUINELY missing binary fails. Without a shell it's a spawn-level
|
|
39
|
+
// ENOENT; through cmd.exe the shell itself starts fine and the missing
|
|
40
|
+
// command surfaces as a non-zero exit with this exact stderr text — no
|
|
41
|
+
// ENOENT anywhere. windows-latest CI (no codex installed) caught this:
|
|
42
|
+
// it started reporting "version probe failed" instead of "not
|
|
43
|
+
// installed" the first time shell:true shipped without this branch.
|
|
44
|
+
it('still reports "not installed" when the shell itself says the command is unknown', () => {
|
|
45
|
+
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
|
|
46
|
+
const shellNotFound = Object.assign(new Error('Command failed: codex --version'), {
|
|
47
|
+
status: 1,
|
|
48
|
+
stderr: Buffer.from("'codex' is not recognized as an internal or external command,\r\noperable program or batch file.\r\n"),
|
|
49
|
+
});
|
|
50
|
+
expect(() => (0, provider_version_1.assertProviderSupported)('codex', () => { throw shellNotFound; }))
|
|
51
|
+
.toThrow('Codex is not installed or not available on PATH');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
13
54
|
it.each(['0.145.1', '0.146.0', '1.0.0'])('accepts stable Codex version %s above the minimum', (version) => {
|
|
14
55
|
expect((0, provider_version_1.assertProviderSupported)('codex', () => Buffer.from(`codex-cli ${version}\n`))).toEqual({ provider: 'codex', version });
|
|
15
56
|
});
|
|
@@ -223,10 +223,24 @@ describe('codex provider — isolated home E2E (Task 9)', () => {
|
|
|
223
223
|
it('doctor reports the isolated Codex install as supported/healthy, not against the real ~/.awm', () => {
|
|
224
224
|
seedPublicRegistryFixture(path_1.default.join(tmpHome, '.awm/registries/baseline'));
|
|
225
225
|
writePrefs(prefsWith(['claude-code', 'codex']));
|
|
226
|
+
const childProcess = require('child_process');
|
|
227
|
+
const realExecFileSync = childProcess.execFileSync;
|
|
228
|
+
const execSpy = jest.spyOn(childProcess, 'execFileSync').mockImplementation(((command, ...args) => {
|
|
229
|
+
if (command === 'codex') {
|
|
230
|
+
throw Object.assign(new Error('spawnSync codex ENOENT'), { code: 'ENOENT' });
|
|
231
|
+
}
|
|
232
|
+
return realExecFileSync(command, ...args);
|
|
233
|
+
}));
|
|
234
|
+
jest.resetModules();
|
|
226
235
|
const { runDoctor } = require('../../src/commands/doctor');
|
|
227
236
|
// R20: doctor resolves a single explicit provider without needing every
|
|
228
237
|
// enabled agent to be independently initialized first.
|
|
229
|
-
|
|
238
|
+
try {
|
|
239
|
+
runDoctor({ cwd: tmpWork, json: true, agent: 'codex' });
|
|
240
|
+
}
|
|
241
|
+
finally {
|
|
242
|
+
execSpy.mockRestore();
|
|
243
|
+
}
|
|
230
244
|
const report = JSON.parse(stdout());
|
|
231
245
|
expect(report.providers.map((p) => p.id)).toEqual(['codex']);
|
|
232
246
|
const codexReport = report.providers[0];
|
|
@@ -49,6 +49,7 @@ describe('copilot provider — isolated home E2E (devCore global-scope guard reg
|
|
|
49
49
|
originalAwmHome = process.env.AWM_HOME;
|
|
50
50
|
process.env.HOME = tmpHome;
|
|
51
51
|
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
52
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpWork, 'package.json'), JSON.stringify({ name: 'copilot-e2e-fixture' }));
|
|
52
53
|
jest.resetModules();
|
|
53
54
|
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
54
55
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
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
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const evidenceDir = path_1.default.resolve(__dirname, '../../../docs/research/r2/evidence');
|
|
11
|
+
const runner = path_1.default.resolve(__dirname, '../../../docs/research/r2/provider-run.mjs');
|
|
12
|
+
const providers = ['claude-code', 'codex'];
|
|
13
|
+
function read(provider) {
|
|
14
|
+
const file = path_1.default.join(evidenceDir, `${provider}.json`);
|
|
15
|
+
return fs_1.default.existsSync(file) ? JSON.parse(fs_1.default.readFileSync(file, 'utf8')) : null;
|
|
16
|
+
}
|
|
17
|
+
function expectEvidenceIntegrity(provider, value) {
|
|
18
|
+
expect(value).toMatchObject({ schema: 1, provider });
|
|
19
|
+
expect(['pass', 'partial', 'fail']).toContain(value.result);
|
|
20
|
+
// This identifies the evidence's source without assuming CI has fetched that object.
|
|
21
|
+
expect(value.sourceHead).toMatch(/^[0-9a-f]{40}$/);
|
|
22
|
+
expect(value.command).toBe('node cli/dist/src/index.js sensors coverage --json');
|
|
23
|
+
expect(value.providerVersion).toMatch(/^\S[^\r\n]*$/);
|
|
24
|
+
expect(value.providerIdentity).toEqual({
|
|
25
|
+
requestedProvider: provider,
|
|
26
|
+
executable: provider === 'claude-code' ? 'claude' : 'codex',
|
|
27
|
+
attestedBy: 'executable--version',
|
|
28
|
+
});
|
|
29
|
+
const serialized = JSON.stringify(value);
|
|
30
|
+
expect(serialized).not.toMatch(/\/home\/[^/"\s]+|\/Users\/[^/"\s]+|\b(?:token|secret|password|api[-_]?key)\b"?\s*[:=]/i);
|
|
31
|
+
if (value.result === 'pass') {
|
|
32
|
+
expect(value.reportSha256).toMatch(/^[0-9a-f]{64}$/);
|
|
33
|
+
expect(value.semanticContract).toBeDefined();
|
|
34
|
+
expect(value.report).toBeDefined();
|
|
35
|
+
expect(crypto_1.default.createHash('sha256').update(`${JSON.stringify(value.report)}\n`).digest('hex'))
|
|
36
|
+
.toBe(value.reportSha256);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
describe('R2 provider evidence integrity', () => {
|
|
40
|
+
it('locates the repository root from docs/research/r2 before invoking the compiled CLI', () => {
|
|
41
|
+
expect(fs_1.default.readFileSync(runner, 'utf8')).toContain("fileURLToPath(import.meta.url)), '../../..')");
|
|
42
|
+
});
|
|
43
|
+
it.each(providers)('%s evidence, when recorded, is a sanitized attested partial (RNF-T.2)', (provider) => {
|
|
44
|
+
const value = read(provider);
|
|
45
|
+
if (value === null) {
|
|
46
|
+
expect({ certification: 'partial', missingProviders: [provider] }).toEqual({
|
|
47
|
+
certification: 'partial',
|
|
48
|
+
missingProviders: ['claude-code'],
|
|
49
|
+
});
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
expectEvidenceIntegrity(provider, value);
|
|
53
|
+
});
|
|
54
|
+
it('accepts a valid but locally unavailable source SHA and rejects malformed source SHAs', () => {
|
|
55
|
+
const value = read('codex');
|
|
56
|
+
if (value === null)
|
|
57
|
+
throw new Error('Codex evidence fixture is required for integrity validation');
|
|
58
|
+
expectEvidenceIntegrity('codex', { ...value, sourceHead: 'f'.repeat(40) });
|
|
59
|
+
expect(() => expectEvidenceIntegrity('codex', { ...value, sourceHead: 'not-a-commit' }))
|
|
60
|
+
.toThrow();
|
|
61
|
+
});
|
|
62
|
+
it('reports RNF-T.2 as partial until two real pass envelopes can be compared', () => {
|
|
63
|
+
const claude = read('claude-code');
|
|
64
|
+
const codex = read('codex');
|
|
65
|
+
const missingPassEvidence = providers.filter((provider) => read(provider)?.result !== 'pass');
|
|
66
|
+
if (missingPassEvidence.length > 0) {
|
|
67
|
+
expect({ certification: 'partial', missingPassEvidence }).toEqual({
|
|
68
|
+
certification: 'partial',
|
|
69
|
+
missingPassEvidence: ['claude-code'],
|
|
70
|
+
});
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (claude === null || codex === null)
|
|
74
|
+
throw new Error('pass evidence must be present before certification');
|
|
75
|
+
expect(claude.sourceHead).toBe(codex.sourceHead);
|
|
76
|
+
expect(claude.semanticContract).toEqual(codex.semanticContract);
|
|
77
|
+
});
|
|
78
|
+
it('does not write Claude evidence when its required executable is unavailable', () => {
|
|
79
|
+
const output = path_1.default.join(evidenceDir, 'claude-code.json');
|
|
80
|
+
expect(fs_1.default.existsSync(output)).toBe(false);
|
|
81
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [runner, 'claude-code', path_1.default.resolve(__dirname, '../..')], {
|
|
82
|
+
cwd: path_1.default.resolve(__dirname, '../../..'), encoding: 'utf8',
|
|
83
|
+
});
|
|
84
|
+
expect(result.status).not.toBe(0);
|
|
85
|
+
expect(`${result.stdout}${result.stderr}`).toContain('claude');
|
|
86
|
+
expect(fs_1.default.existsSync(output)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,113 @@
|
|
|
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
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const cliDir = path_1.default.resolve(__dirname, '../..');
|
|
12
|
+
const bin = path_1.default.join(cliDir, 'dist/src/index.js');
|
|
13
|
+
const fixture = path_1.default.join(cliDir, 'tests/fixtures/sensor-coverage/js-ts-gap');
|
|
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 hashTree = (root) => {
|
|
18
|
+
const hash = crypto_1.default.createHash('sha256');
|
|
19
|
+
const walk = (directory) => {
|
|
20
|
+
for (const entry of fs_1.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
21
|
+
const file = path_1.default.join(directory, entry.name);
|
|
22
|
+
hash.update(path_1.default.relative(root, file));
|
|
23
|
+
if (entry.isDirectory())
|
|
24
|
+
walk(file);
|
|
25
|
+
else if (entry.isFile())
|
|
26
|
+
hash.update(fs_1.default.readFileSync(file));
|
|
27
|
+
else
|
|
28
|
+
hash.update(`link:${fs_1.default.readlinkSync(file)}`);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
walk(root);
|
|
32
|
+
return hash.digest('hex');
|
|
33
|
+
};
|
|
34
|
+
beforeAll(() => {
|
|
35
|
+
if (!fs_1.default.existsSync(bin)) {
|
|
36
|
+
throw new Error(`Coverage E2E requires the compiled CLI at ${bin}; run npm run build before this test.`);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
test('js-ts gap fixture tracks the manifest copied into the coverage project', () => {
|
|
40
|
+
const manifest = path_1.default.join(fixture, '.awm', 'sensors.json');
|
|
41
|
+
expect(fs_1.default.existsSync(manifest)).toBe(true);
|
|
42
|
+
expect(JSON.parse(fs_1.default.readFileSync(manifest, 'utf8'))).toMatchObject({
|
|
43
|
+
sensors: expect.any(Object),
|
|
44
|
+
});
|
|
45
|
+
expect((0, child_process_1.execFileSync)('git', ['ls-files', '--error-unmatch', 'tests/fixtures/sensor-coverage/js-ts-gap/.awm/sensors.json'], {
|
|
46
|
+
cwd: cliDir, encoding: 'utf8',
|
|
47
|
+
}).trim()).toBe('tests/fixtures/sensor-coverage/js-ts-gap/.awm/sensors.json');
|
|
48
|
+
});
|
|
49
|
+
function runWithFixture(mutatePack) {
|
|
50
|
+
const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-coverage-e2e-'));
|
|
51
|
+
const project = path_1.default.join(tmp, 'project');
|
|
52
|
+
const awmHome = path_1.default.join(tmp, 'awm-home');
|
|
53
|
+
const registry = path_1.default.join(awmHome, 'registries', 'baseline');
|
|
54
|
+
fs_1.default.cpSync(fixture, project, { recursive: true });
|
|
55
|
+
fs_1.default.mkdirSync(path_1.default.dirname(registry), { recursive: true });
|
|
56
|
+
fs_1.default.cpSync(registryFixture, registry, { recursive: true });
|
|
57
|
+
fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'fixture' }]));
|
|
58
|
+
if (mutatePack) {
|
|
59
|
+
const packPath = path_1.default.join(registry, 'sensor-packs/js-ts/pack.json');
|
|
60
|
+
const pack = JSON.parse(fs_1.default.readFileSync(packPath, 'utf8'));
|
|
61
|
+
mutatePack(pack);
|
|
62
|
+
fs_1.default.writeFileSync(packPath, JSON.stringify(pack));
|
|
63
|
+
}
|
|
64
|
+
return { tmp, project, awmHome };
|
|
65
|
+
}
|
|
66
|
+
test('compiled CLI reports gaps or fails closed when no-follow safety is unavailable (RF-1.1, RF-1.4)', () => {
|
|
67
|
+
const { tmp, project, awmHome } = runWithFixture();
|
|
68
|
+
try {
|
|
69
|
+
const before = [hashTree(project), hashTree(awmHome)];
|
|
70
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', 'coverage', '--json'], {
|
|
71
|
+
cwd: project, encoding: 'utf8', env: { ...process.env, AWM_HOME: awmHome, AWM_NO_UPDATE_CHECK: '1' },
|
|
72
|
+
});
|
|
73
|
+
if (noFollowUnavailableOnNativeWindows) {
|
|
74
|
+
expect(result.status).toBe(1);
|
|
75
|
+
expect(`${result.stdout ?? ''}${result.stderr ?? ''}`).toContain('platform cannot guarantee no symlink dereference');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
expect(result.status).toBe(0);
|
|
79
|
+
const report = JSON.parse(result.stdout ?? '');
|
|
80
|
+
expect(report.static.classes).toEqual(expect.arrayContaining([
|
|
81
|
+
expect.objectContaining({ id: 'formatting', status: 'missing' }),
|
|
82
|
+
expect.objectContaining({ id: 'project-style-conventions', status: 'missing' }),
|
|
83
|
+
]));
|
|
84
|
+
}
|
|
85
|
+
expect([hashTree(project), hashTree(awmHome)]).toEqual(before);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
const testWithNoFollow = noFollowUnavailableOnNativeWindows ? test.skip : test;
|
|
92
|
+
testWithNoFollow('informative states exit zero; malformed contract exits non-zero (R2.7, R2.9)', () => {
|
|
93
|
+
const run = (mutatePack) => {
|
|
94
|
+
const { tmp, project, awmHome } = runWithFixture(mutatePack);
|
|
95
|
+
try {
|
|
96
|
+
return (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', 'coverage', '--json'], {
|
|
97
|
+
cwd: project, encoding: 'utf8', env: { ...process.env, AWM_HOME: awmHome, AWM_NO_UPDATE_CHECK: '1' },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
fs_1.default.rmSync(tmp, { recursive: true, force: true });
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const gaps = run(() => undefined);
|
|
105
|
+
const noReference = run((pack) => { delete pack.coverage; });
|
|
106
|
+
const malformed = run((pack) => { pack.coverage.schemaVersion = 2; });
|
|
107
|
+
expect(gaps.status).toBe(0);
|
|
108
|
+
expect(JSON.parse(gaps.stdout ?? '')).toMatchObject({ overall: 'gaps' });
|
|
109
|
+
expect(noReference.status).toBe(0);
|
|
110
|
+
expect(JSON.parse(noReference.stdout ?? '')).toMatchObject({ static: { reason: 'no_reference' } });
|
|
111
|
+
expect(malformed.status).toBe(1);
|
|
112
|
+
expect(`${malformed.stdout ?? ''}${malformed.stderr ?? ''}`).toContain('schemaVersion must be 1');
|
|
113
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
const os_1 = __importDefault(require("os"));
|
|
7
|
+
describe('Jest environment isolation', () => {
|
|
8
|
+
it('does not inherit the operator Codex home', () => {
|
|
9
|
+
expect(process.env.CODEX_HOME).toBeUndefined();
|
|
10
|
+
});
|
|
11
|
+
it('uses a suite-owned temporary directory', () => {
|
|
12
|
+
expect(os_1.default.tmpdir()).toContain('awm-jest-');
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -18,6 +18,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
18
18
|
const path_1 = __importDefault(require("path"));
|
|
19
19
|
const CLI_ROOT = path_1.default.resolve(__dirname, '..', '..');
|
|
20
20
|
const SENSORS = path_1.default.join(CLI_ROOT, '.awm', 'sensors.json');
|
|
21
|
+
const BASELINE = path_1.default.join(CLI_ROOT, '.awm', 'sensors.baseline.json');
|
|
21
22
|
/** Extrae los archivos de config que un comando de sensor referencia. Se buscan por la
|
|
22
23
|
* convención `*.awm.*` que usan todos los packs, no por una lista de nombres — un pack
|
|
23
24
|
* nuevo con otro linter queda cubierto igual. */
|
|
@@ -46,4 +47,13 @@ describe('configs de sensores: declarados <=> presentes', () => {
|
|
|
46
47
|
// termina resuelto borrando el sensor, que es la salida equivocada.
|
|
47
48
|
expect(missing.join('\n') || 'todos presentes').toBe('todos presentes');
|
|
48
49
|
});
|
|
50
|
+
it('cada sensor conservado en el baseline sigue declarado en el manifiesto', () => {
|
|
51
|
+
const baseline = JSON.parse(fs_1.default.readFileSync(BASELINE, 'utf8'));
|
|
52
|
+
const missing = Object.keys(baseline).filter((name) => manifest?.sensors?.[name] === undefined);
|
|
53
|
+
expect(missing).toEqual([]);
|
|
54
|
+
});
|
|
55
|
+
it('el sensor de tests cubre la duración observada de la suite con margen de CI', () => {
|
|
56
|
+
const testSensor = manifest?.sensors?.test;
|
|
57
|
+
expect(testSensor?.timeout).toBeGreaterThanOrEqual(300_000);
|
|
58
|
+
});
|
|
49
59
|
});
|
|
@@ -6,6 +6,27 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const fs_1 = __importDefault(require("fs"));
|
|
7
7
|
const os_1 = __importDefault(require("os"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const config_1 = require("../../src/utils/config");
|
|
10
|
+
// Regression for the `awm remove <bundle> --yes` gap found running the issue #55
|
|
11
|
+
// Windows playbook (CORE-17): `--yes` skipped the agent prompt but not the scope
|
|
12
|
+
// one, so a supposedly non-interactive removal still hung waiting on a picker.
|
|
13
|
+
// D-006's own stated rule is "`--yes` implica cero prompts" — this closes the one
|
|
14
|
+
// call site that had drifted from it.
|
|
15
|
+
describe('resolveScopeOption', () => {
|
|
16
|
+
it('falls back to the default when nothing explicit was passed (the --yes path)', () => {
|
|
17
|
+
expect((0, config_1.resolveScopeOption)(undefined, 'local')).toEqual({ ok: true, scope: 'local' });
|
|
18
|
+
expect((0, config_1.resolveScopeOption)(undefined, 'global')).toEqual({ ok: true, scope: 'global' });
|
|
19
|
+
});
|
|
20
|
+
it('an explicit valid value wins over the default', () => {
|
|
21
|
+
expect((0, config_1.resolveScopeOption)('global', 'local')).toEqual({ ok: true, scope: 'global' });
|
|
22
|
+
});
|
|
23
|
+
it('rejects a value that is neither local nor global', () => {
|
|
24
|
+
expect((0, config_1.resolveScopeOption)('bogus', 'local')).toEqual({
|
|
25
|
+
ok: false,
|
|
26
|
+
error: 'Invalid scope "bogus". Use: local or global.',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
});
|
|
9
30
|
describe('Preferences Manager', () => {
|
|
10
31
|
let tmpHome;
|
|
11
32
|
let tmpWork;
|