agentic-workflow-manager 8.0.0 → 8.1.1
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/scripts/sensor-support-matrix.js +110 -12
- package/dist/scripts/support-matrix.js +4 -1
- package/dist/src/commands/preflight/index.js +4 -1
- package/dist/src/commands/sensors/compatibility/contract.js +142 -21
- package/dist/src/commands/sensors/compatibility/discovery.js +1 -1
- package/dist/src/commands/sensors/compatibility/live.js +40 -7
- package/dist/src/commands/sensors/compatibility/manifest.js +13 -3
- package/dist/src/commands/sensors/compatibility/materialize.js +9 -1
- package/dist/src/commands/sensors/compatibility/probe.js +9 -9
- package/dist/src/commands/sensors/compatibility/resolve.js +3 -0
- package/dist/src/commands/sensors/exec.js +106 -12
- package/dist/src/commands/sensors/init.js +6 -3
- package/dist/src/commands/sensors/run.js +1 -1
- package/dist/src/commands/sensors/status.js +1 -1
- package/dist/tests/commands/sensors/compatibility/contract.test.js +95 -0
- package/dist/tests/commands/sensors/compatibility/live.test.js +124 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +9 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +19 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +10 -0
- package/dist/tests/commands/sensors/compatibility/python-venv-fixture.js +29 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +8 -0
- package/dist/tests/commands/sensors/exec-windows.test.js +58 -10
- package/dist/tests/commands/sensors/exec.test.js +51 -3
- package/dist/tests/commands/sensors/init.test.js +72 -0
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +26 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +9 -7
- package/dist/tests/structural/support-matrix-is-current.test.js +78 -6
- package/package.json +3 -2
|
@@ -12,6 +12,14 @@ const sensor = { applicability: { allFiles: ['package.json'] }, variants: [varia
|
|
|
12
12
|
const evidence = (input = {}) => ({ paths: ['package.json'], applicable: true, packageManagerConflict: false, toolVersion: '10.4.1', runtimeVersion: '22.0.0', probe: { status: 'matched' }, ...input });
|
|
13
13
|
const context = { pack: 'js-ts', sensor: 'lint' };
|
|
14
14
|
describe('resolveSensorCompatibility', () => {
|
|
15
|
+
it('keeps an automatically selected generic pack not-applicable despite language markers', () => {
|
|
16
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { kind: 'explicit-or-supported-language' }, variants: [variant('semgrep')] }, evidence({ applicable: undefined, paths: ['pyproject.toml'], toolVersion: '1.0.0', runtimeVersion: '3.12.0', probe: { status: 'matched' } }), { pack: 'generic', sensor: 'security' });
|
|
17
|
+
expect(resolved).toMatchObject({ state: 'not-applicable', reason: 'applicability-not-met' });
|
|
18
|
+
});
|
|
19
|
+
it('treats the persisted explicit generic pack selection as positive capability in project resolution', () => {
|
|
20
|
+
const resolved = (0, resolve_1.resolveProjectCompatibility)({ schemaVersion: 2, name: 'generic', sensors: { security: { applicability: { kind: 'explicit-or-supported-language' }, variants: [variant('semgrep')] } } }, evidence({ applicable: undefined, paths: [], packSelection: 'explicit' }));
|
|
21
|
+
expect(resolved.sensors.security.state).not.toBe('not-applicable');
|
|
22
|
+
});
|
|
15
23
|
test.each([
|
|
16
24
|
['certified', evidence(), 'eslint-main'],
|
|
17
25
|
['compatible-unverified', evidence({ toolVersion: '11.0.0' }), 'eslint-main'],
|
|
@@ -24,6 +24,12 @@ function fakeChild(pid = 4242) {
|
|
|
24
24
|
child.unref = jest.fn();
|
|
25
25
|
return child;
|
|
26
26
|
}
|
|
27
|
+
function restoreEnvironment(name, value) {
|
|
28
|
+
if (value === undefined)
|
|
29
|
+
delete process.env[name];
|
|
30
|
+
else
|
|
31
|
+
process.env[name] = value;
|
|
32
|
+
}
|
|
27
33
|
describe('runCommand — win32', () => {
|
|
28
34
|
const originalPlatform = process.platform;
|
|
29
35
|
beforeEach(() => {
|
|
@@ -58,11 +64,35 @@ describe('runCommand — win32', () => {
|
|
|
58
64
|
expect(mockSpawn).toHaveBeenCalledWith(path_1.default.join(dir, 'tool.exe'), ['literal;&'], expect.objectContaining({ shell: false, detached: false }));
|
|
59
65
|
child.emit('close', 0, null);
|
|
60
66
|
await expect(pending).resolves.toMatchObject({ code: 0 });
|
|
61
|
-
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool.cmd', resolution: 'path', args: [] }, { timeout: 5000, cwd: dir })).toThrow(/wrappers/);
|
|
67
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool.cmd', resolution: 'path', args: ['--version'] }, { timeout: 5000, cwd: dir })).toThrow(/wrappers/);
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
restoreEnvironment('PATH', savedPath);
|
|
71
|
+
restoreEnvironment('PATHEXT', savedPathExt);
|
|
72
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
it('spawns the allowlisted flat-config environment for ESLint 8', async () => {
|
|
76
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-structured-win-env-'));
|
|
77
|
+
const savedPath = process.env.PATH;
|
|
78
|
+
const savedPathExt = process.env.PATHEXT;
|
|
79
|
+
try {
|
|
80
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'tool.exe'), 'fixture');
|
|
81
|
+
process.env.PATH = dir;
|
|
82
|
+
process.env.PATHEXT = '.EXE';
|
|
83
|
+
const child = fakeChild();
|
|
84
|
+
mockSpawn.mockReturnValue(child);
|
|
85
|
+
const pending = (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'path', args: ['--version'], environment: { ESLINT_USE_FLAT_CONFIG: 'true' } }, { timeout: 5000, cwd: dir });
|
|
86
|
+
expect(mockSpawn).toHaveBeenCalledWith(path_1.default.join(dir, 'tool.exe'), ['--version'], expect.objectContaining({
|
|
87
|
+
shell: false,
|
|
88
|
+
env: expect.objectContaining({ ESLINT_USE_FLAT_CONFIG: 'true' }),
|
|
89
|
+
}));
|
|
90
|
+
child.emit('close', 0, null);
|
|
91
|
+
await expect(pending).resolves.toMatchObject({ code: 0 });
|
|
62
92
|
}
|
|
63
93
|
finally {
|
|
64
|
-
|
|
65
|
-
|
|
94
|
+
restoreEnvironment('PATH', savedPath);
|
|
95
|
+
restoreEnvironment('PATHEXT', savedPathExt);
|
|
66
96
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
67
97
|
}
|
|
68
98
|
});
|
|
@@ -80,14 +110,14 @@ describe('runCommand — win32', () => {
|
|
|
80
110
|
process.env.PATHEXT = '.EXE';
|
|
81
111
|
const child = fakeChild();
|
|
82
112
|
mockSpawn.mockReturnValue(child);
|
|
83
|
-
const pending = (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: [] }, { timeout: 5000, cwd: dir });
|
|
84
|
-
expect(mockSpawn).toHaveBeenCalledWith(path_1.default.join(localBin, 'tool.exe'), [], expect.objectContaining({ shell: false }));
|
|
113
|
+
const pending = (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: ['--version'] }, { timeout: 5000, cwd: dir });
|
|
114
|
+
expect(mockSpawn).toHaveBeenCalledWith(path_1.default.join(localBin, 'tool.exe'), ['--version'], expect.objectContaining({ shell: false }));
|
|
85
115
|
child.emit('close', 0, null);
|
|
86
116
|
await expect(pending).resolves.toMatchObject({ code: 0 });
|
|
87
117
|
}
|
|
88
118
|
finally {
|
|
89
|
-
|
|
90
|
-
|
|
119
|
+
restoreEnvironment('PATH', savedPath);
|
|
120
|
+
restoreEnvironment('PATHEXT', savedPathExt);
|
|
91
121
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
92
122
|
fs_1.default.rmSync(global, { recursive: true, force: true });
|
|
93
123
|
}
|
|
@@ -100,16 +130,34 @@ describe('runCommand — win32', () => {
|
|
|
100
130
|
fs_1.default.writeFileSync(path_1.default.join(global, 'tool.exe'), 'global');
|
|
101
131
|
process.env.PATH = global;
|
|
102
132
|
mockSpawn.mockReturnValue(fakeChild());
|
|
103
|
-
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: [] }, { timeout: 5000, cwd: dir }))
|
|
133
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: ['--version'] }, { timeout: 5000, cwd: dir }))
|
|
104
134
|
.toThrow(/node_modules.*not found locally/i);
|
|
105
135
|
expect(mockSpawn).not.toHaveBeenCalled();
|
|
106
136
|
}
|
|
107
137
|
finally {
|
|
108
|
-
|
|
138
|
+
restoreEnvironment('PATH', savedPath);
|
|
109
139
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
110
140
|
fs_1.default.rmSync(global, { recursive: true, force: true });
|
|
111
141
|
}
|
|
112
142
|
});
|
|
143
|
+
it('fails closed when PATHEXT contains no safe executable extension', () => {
|
|
144
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-structured-win-pathext-'));
|
|
145
|
+
const savedPath = process.env.PATH;
|
|
146
|
+
const savedPathExt = process.env.PATHEXT;
|
|
147
|
+
try {
|
|
148
|
+
fs_1.default.writeFileSync(path_1.default.join(dir, 'tool.cmd'), 'wrapper');
|
|
149
|
+
process.env.PATH = dir;
|
|
150
|
+
process.env.PATHEXT = '.CMD';
|
|
151
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'path', args: ['--version'] }, { timeout: 5000, cwd: dir }))
|
|
152
|
+
.toThrow(/safe Windows executable|PATHEXT/i);
|
|
153
|
+
expect(mockSpawn).not.toHaveBeenCalled();
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
restoreEnvironment('PATH', savedPath);
|
|
157
|
+
restoreEnvironment('PATHEXT', savedPathExt);
|
|
158
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
159
|
+
}
|
|
160
|
+
});
|
|
113
161
|
it('rejects a missing Python environment executable instead of falling back to a same-named global command', () => {
|
|
114
162
|
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-missing-win-'));
|
|
115
163
|
const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-global-win-'));
|
|
@@ -123,7 +171,7 @@ describe('runCommand — win32', () => {
|
|
|
123
171
|
expect(mockSpawn).not.toHaveBeenCalled();
|
|
124
172
|
}
|
|
125
173
|
finally {
|
|
126
|
-
|
|
174
|
+
restoreEnvironment('PATH', savedPath);
|
|
127
175
|
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
128
176
|
fs_1.default.rmSync(global, { recursive: true, force: true });
|
|
129
177
|
}
|
|
@@ -27,7 +27,7 @@ describe('runCommand — exit codes and output', () => {
|
|
|
27
27
|
const literal = `;touch ${marker}`;
|
|
28
28
|
const received = path_1.default.join(dir, 'received');
|
|
29
29
|
const result = await (0, exec_1.runStructuredCommand)({
|
|
30
|
-
executable:
|
|
30
|
+
executable: 'node',
|
|
31
31
|
resolution: 'path',
|
|
32
32
|
args: ['-e', "require('fs').writeFileSync(process.argv[1], process.argv[2])", received, literal],
|
|
33
33
|
}, { timeout: 5000, cwd: dir });
|
|
@@ -130,6 +130,36 @@ describe('runCommand — spawn failure', () => {
|
|
|
130
130
|
expect(r.code).not.toBe(0);
|
|
131
131
|
});
|
|
132
132
|
});
|
|
133
|
+
describe('runStructuredCommand — public boundary validation', () => {
|
|
134
|
+
it.each(['sh', 'cmd.exe', '/usr/bin/node', 'nested/tool', 'nested\\tool'])('rejects unsafe executable %j before resolution', executable => {
|
|
135
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable, resolution: 'path', args: ['--version'] }, { timeout: 5000, cwd: process.cwd() }))
|
|
136
|
+
.toThrow(/safe executable name|shell/i);
|
|
137
|
+
});
|
|
138
|
+
it('normalizes package-manager spellings while requiring an equivalent explicit selection', () => {
|
|
139
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'NPM.exe', resolution: 'path', args: ['--version'] }, { timeout: 5000, cwd: process.cwd() }))
|
|
140
|
+
.toThrow(/packageManager/);
|
|
141
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'NPM.exe', resolution: 'path', args: ['--version'], packageManager: 'pnpm' }, { timeout: 5000, cwd: process.cwd() }))
|
|
142
|
+
.toThrow(/packageManager/);
|
|
143
|
+
});
|
|
144
|
+
it.each([
|
|
145
|
+
{ ESLINT_USE_FLAT_CONFIG: 'invalid' },
|
|
146
|
+
{ ESLINT_USE_FLAT_CONFIG: 'true', OTHER: 'value' },
|
|
147
|
+
])('rejects an unknown or expanded environment mapping at the public boundary', environment => {
|
|
148
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'node', resolution: 'path', args: ['--version'], environment }, { timeout: 5000, cwd: process.cwd() }))
|
|
149
|
+
.toThrow(/exact allowlisted/);
|
|
150
|
+
});
|
|
151
|
+
it.each([
|
|
152
|
+
[{ executable: 'node', resolution: 'path', args: [] }, /nonempty array/],
|
|
153
|
+
[{ executable: 'node', resolution: 'path', args: [''] }, /nonempty single-line/],
|
|
154
|
+
[{ executable: 'node', resolution: 'path', args: ['one\ntwo'] }, /single-line/],
|
|
155
|
+
[{ executable: 'node', resolution: 'path', args: ['{files}'] }, /requires fileInput/],
|
|
156
|
+
[{ executable: 'node', resolution: 'path', args: ['prefix{files}'], fileInput: { placeholder: '{files}', extensions: ['.ts'] } }, /must not embed/],
|
|
157
|
+
[{ executable: 'node', resolution: 'path', args: ['{files}', '{files}'], fileInput: { placeholder: '{files}', extensions: ['.ts'] } }, /exactly one/],
|
|
158
|
+
[{ executable: 'node', resolution: 'path', args: ['--check'], fileInput: { placeholder: '{files}', extensions: ['.ts'] } }, /exactly one/],
|
|
159
|
+
])('rejects malformed args or file input at the public boundary', (command, message) => {
|
|
160
|
+
expect(() => (0, exec_1.runStructuredCommand)(command, { timeout: 5000, cwd: process.cwd() })).toThrow(message);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
133
163
|
onPosix('runStructuredCommand — local node_modules binaries', () => {
|
|
134
164
|
it('follows an npm-style contained shim but rejects one escaping node_modules', async () => {
|
|
135
165
|
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-local-bin-symlink-'));
|
|
@@ -143,11 +173,11 @@ onPosix('runStructuredCommand — local node_modules binaries', () => {
|
|
|
143
173
|
fs_1.default.writeFileSync(outside, '#!/bin/sh\necho outside\n', { mode: 0o755 });
|
|
144
174
|
fs_1.default.mkdirSync(bin, { recursive: true });
|
|
145
175
|
fs_1.default.symlinkSync('../tool/bin/tool', path_1.default.join(bin, 'tool'));
|
|
146
|
-
await expect((0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: [] }, { timeout: 5000, cwd: dir }))
|
|
176
|
+
await expect((0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: ['--version'] }, { timeout: 5000, cwd: dir }))
|
|
147
177
|
.resolves.toMatchObject({ code: 0, stdout: 'contained\n' });
|
|
148
178
|
fs_1.default.unlinkSync(path_1.default.join(bin, 'tool'));
|
|
149
179
|
fs_1.default.symlinkSync(outside, path_1.default.join(bin, 'tool'));
|
|
150
|
-
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: [] }, { timeout: 5000, cwd: dir }))
|
|
180
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'tool', resolution: 'node-modules-bin', args: ['--version'] }, { timeout: 5000, cwd: dir }))
|
|
151
181
|
.toThrow(/node_modules.*contain|local/i);
|
|
152
182
|
}
|
|
153
183
|
finally {
|
|
@@ -156,6 +186,24 @@ onPosix('runStructuredCommand — local node_modules binaries', () => {
|
|
|
156
186
|
});
|
|
157
187
|
});
|
|
158
188
|
onPosix('runStructuredCommand — Python environments', () => {
|
|
189
|
+
it.each(['.venv', 'venv'])('rejects a symlinked %s ancestor instead of executing outside the project', environment => {
|
|
190
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-ancestor-'));
|
|
191
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-outside-'));
|
|
192
|
+
const marker = path_1.default.join(dir, 'must-not-exist');
|
|
193
|
+
try {
|
|
194
|
+
const executable = path_1.default.join(outside, 'bin', 'semgrep');
|
|
195
|
+
fs_1.default.mkdirSync(path_1.default.dirname(executable), { recursive: true });
|
|
196
|
+
fs_1.default.writeFileSync(executable, `#!/bin/sh\ntouch ${marker}\n`, { mode: 0o755 });
|
|
197
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(dir, environment));
|
|
198
|
+
expect(() => (0, exec_1.runStructuredCommand)({ executable: 'semgrep', resolution: 'python-environment', pythonEnvironmentRoot: environment, args: ['--validate'] }, { timeout: 5000, cwd: dir }))
|
|
199
|
+
.toThrow(/python.*environment.*local|contained/i);
|
|
200
|
+
expect(fs_1.default.existsSync(marker)).toBe(false);
|
|
201
|
+
}
|
|
202
|
+
finally {
|
|
203
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
204
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
});
|
|
159
207
|
it('rejects a missing project Python executable instead of falling back to PATH', () => {
|
|
160
208
|
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-missing-'));
|
|
161
209
|
const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-env-global-'));
|
|
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const init_1 = require("../../../src/commands/sensors/init");
|
|
10
|
+
const status_1 = require("../../../src/commands/sensors/status");
|
|
10
11
|
// Build a throwaway registry with a js-ts pack.json (the single source of truth
|
|
11
12
|
// init now reads from). `defaultCmd` uses the {{SOURCE_DIRS}} placeholder for depcheck.
|
|
12
13
|
function makeRegistry() {
|
|
@@ -45,9 +46,11 @@ function makeV2Registry() {
|
|
|
45
46
|
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
46
47
|
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
47
48
|
fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
|
|
49
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'tsconfig.awm.json'), '{ "compilerOptions": { "strict": true } }\n');
|
|
48
50
|
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
49
51
|
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
50
52
|
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
|
|
53
|
+
hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } },
|
|
51
54
|
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
52
55
|
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
53
56
|
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
|
|
@@ -57,6 +60,23 @@ function makeV2Registry() {
|
|
|
57
60
|
}));
|
|
58
61
|
return registryRoot;
|
|
59
62
|
}
|
|
63
|
+
function makeGenericV2Registry() {
|
|
64
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-generic-v2-'));
|
|
65
|
+
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'generic');
|
|
66
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
67
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'generic.config'), 'fixture\n');
|
|
68
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
69
|
+
schemaVersion: 2, name: 'generic', description: 'fixture', detects: ['README.md'],
|
|
70
|
+
coverage: { schemaVersion: 1, classes: { security: { description: 'security', detectors: [{ sensor: 'security' }], remedy: { summary: 'run security', command: 'awm sensors init --pack generic' } } } },
|
|
71
|
+
sensors: { security: { applicability: { kind: 'explicit-or-supported-language' }, variants: [{
|
|
72
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
73
|
+
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0', configFiles: ['generic.config'] },
|
|
74
|
+
assets: ['generic.config'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
75
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
76
|
+
}] } },
|
|
77
|
+
}));
|
|
78
|
+
return registryRoot;
|
|
79
|
+
}
|
|
60
80
|
describe('detectStack', () => {
|
|
61
81
|
let tmpDir;
|
|
62
82
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
|
|
@@ -277,6 +297,58 @@ describe('initSensors', () => {
|
|
|
277
297
|
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
278
298
|
}
|
|
279
299
|
});
|
|
300
|
+
it('persists an explicit generic selection for live revalidation without promoting automatic generic fallback', async () => {
|
|
301
|
+
const v2Registry = makeGenericV2Registry();
|
|
302
|
+
try {
|
|
303
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
304
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
305
|
+
const automatic = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, configure: false });
|
|
306
|
+
expect(automatic.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', sensors: {} });
|
|
307
|
+
expect(automatic.manifest.packSelection).toBeUndefined();
|
|
308
|
+
const explicit = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, pack: 'generic' });
|
|
309
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
310
|
+
expect(explicit.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', packSelection: 'explicit', sensors: { security: { variantId: 'eslint-10' } } });
|
|
311
|
+
expect(written.packSelection).toBe('explicit');
|
|
312
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({ overall: 'HEALTHY', checks: { security: { ok: true } } });
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
it('never materializes an opt-in hardening asset during ordinary v2 init', async () => {
|
|
319
|
+
const v2Registry = makeV2Registry();
|
|
320
|
+
try {
|
|
321
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
322
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
323
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
324
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
325
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
326
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
it('initializes a selected v2 variant with an explicitly empty assets list', async () => {
|
|
333
|
+
const v2Registry = makeV2Registry();
|
|
334
|
+
try {
|
|
335
|
+
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
336
|
+
const pack = JSON.parse(fs_1.default.readFileSync(packPath, 'utf8'));
|
|
337
|
+
pack.sensors.lint.variants[0].assets = [];
|
|
338
|
+
fs_1.default.writeFileSync(packPath, JSON.stringify(pack));
|
|
339
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
340
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
341
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
342
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).resolves.toMatchObject({
|
|
343
|
+
manifest: { schemaVersion: 2, sensors: { lint: { assets: [] } } },
|
|
344
|
+
configured: [],
|
|
345
|
+
});
|
|
346
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(false);
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
280
352
|
it('preserves enabled and fast choices when a valid v2 manifest is re-initialized', async () => {
|
|
281
353
|
const v2Registry = makeV2Registry();
|
|
282
354
|
try {
|
|
@@ -0,0 +1,26 @@
|
|
|
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 child_process_1 = require("child_process");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const os_1 = __importDefault(require("os"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const cliRoot = path_1.default.resolve(__dirname, '../..');
|
|
11
|
+
const bin = path_1.default.join(cliRoot, 'dist', 'src', 'index.js');
|
|
12
|
+
test('preserves degraded preflight JSON when stdout is piped', () => {
|
|
13
|
+
const project = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-preflight-pipe-'));
|
|
14
|
+
try {
|
|
15
|
+
const result = (0, child_process_1.spawnSync)(process.execPath, [bin, 'preflight', '--json'], {
|
|
16
|
+
cwd: project,
|
|
17
|
+
encoding: 'utf8',
|
|
18
|
+
env: { ...process.env, AWM_HOME: path_1.default.join(project, 'awm-home'), AWM_NO_UPDATE_CHECK: '1' },
|
|
19
|
+
});
|
|
20
|
+
expect(result.status).toBe(1);
|
|
21
|
+
expect(JSON.parse(result.stdout)).toMatchObject({ status: 'not_configured' });
|
|
22
|
+
}
|
|
23
|
+
finally {
|
|
24
|
+
fs_1.default.rmSync(project, { recursive: true, force: true });
|
|
25
|
+
}
|
|
26
|
+
});
|
|
@@ -8,7 +8,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
const child_process_1 = require("child_process");
|
|
9
9
|
const CLI_ROOT = path_1.default.resolve(__dirname, '../..');
|
|
10
10
|
const DIST_ENTRYPOINT = path_1.default.join(CLI_ROOT, 'dist', 'src', 'index.js');
|
|
11
|
-
const
|
|
11
|
+
const R3_MAJOR_VERSION = 8;
|
|
12
12
|
function readJson(file) {
|
|
13
13
|
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(CLI_ROOT, file), 'utf8'));
|
|
14
14
|
}
|
|
@@ -22,16 +22,18 @@ function runCompiledCli(...args) {
|
|
|
22
22
|
return `${result.stdout}${result.stderr}`;
|
|
23
23
|
}
|
|
24
24
|
describe('R3 public major CLI release contract', () => {
|
|
25
|
-
it('
|
|
25
|
+
it('retains the R3 major version consistently in package metadata and the lockfile root', () => {
|
|
26
26
|
const pkg = readJson('package.json');
|
|
27
27
|
const lock = readJson('package-lock.json');
|
|
28
28
|
const root = lock.packages[''];
|
|
29
|
-
|
|
30
|
-
expect(
|
|
31
|
-
expect(
|
|
29
|
+
const version = pkg.version;
|
|
30
|
+
expect(typeof version).toBe('string');
|
|
31
|
+
expect(version).toMatch(new RegExp(`^${R3_MAJOR_VERSION}\\.`));
|
|
32
|
+
expect(lock.version).toBe(version);
|
|
33
|
+
expect(root.version).toBe(version);
|
|
32
34
|
});
|
|
33
|
-
it('exposes
|
|
34
|
-
expect(runCompiledCli('--version').trim()).toBe(
|
|
35
|
+
it('exposes the package version from the compiled CLI while retaining its help banner', () => {
|
|
36
|
+
expect(runCompiledCli('--version').trim()).toBe(readJson('package.json').version);
|
|
35
37
|
expect(runCompiledCli('--help')).toContain('Usage: awm');
|
|
36
38
|
});
|
|
37
39
|
});
|
|
@@ -16,10 +16,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
16
16
|
// Este test hace que ese desvio sea imposible de mergear. No verifica que la tabla sea
|
|
17
17
|
// "linda": verifica que sea LA MISMA que produce el codigo hoy.
|
|
18
18
|
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const os_1 = __importDefault(require("os"));
|
|
19
20
|
const path_1 = __importDefault(require("path"));
|
|
21
|
+
const child_process_1 = require("child_process");
|
|
20
22
|
const support_matrix_1 = require("../../scripts/support-matrix");
|
|
21
23
|
const sensor_support_matrix_1 = require("../../scripts/sensor-support-matrix");
|
|
22
24
|
const SENSOR_FIXTURE_REGISTRY = path_1.default.join(__dirname, '..', 'fixtures', 'sensor-support-matrix', 'registry');
|
|
25
|
+
const CI_WORKFLOW_PATH = path_1.default.resolve(__dirname, '../../..', '.github', 'workflows', 'ci.yml');
|
|
23
26
|
describe('docs/support-matrix.md refleja el codigo', () => {
|
|
24
27
|
it('el bloque generado esta al dia', () => {
|
|
25
28
|
const doc = fs_1.default.readFileSync(support_matrix_1.DOC_PATH, 'utf-8');
|
|
@@ -33,17 +36,76 @@ describe('docs/support-matrix.md refleja el codigo', () => {
|
|
|
33
36
|
expect(doc).toContain(support_matrix_1.BEGIN_MARKER);
|
|
34
37
|
expect(doc).toContain(support_matrix_1.END_MARKER);
|
|
35
38
|
});
|
|
36
|
-
it('the generated pre-publication v2
|
|
39
|
+
it('keeps the generated pre-publication v2 fixture separately renderable', () => {
|
|
37
40
|
const doc = fs_1.default.readFileSync(support_matrix_1.DOC_PATH, 'utf-8');
|
|
41
|
+
const fixture = (0, sensor_support_matrix_1.renderSensorSupportMatrix)(SENSOR_FIXTURE_REGISTRY);
|
|
42
|
+
expect(fixture).toContain(sensor_support_matrix_1.R3_PREPUBLICATION_FIXTURE_PURPOSE);
|
|
43
|
+
expect(fixture).toContain('not the published `awm-baseline-registry` manifests');
|
|
44
|
+
expect(fixture).not.toContain('Published certification evidence');
|
|
38
45
|
expect(doc).toContain(sensor_support_matrix_1.SENSOR_BEGIN_MARKER);
|
|
39
46
|
expect(doc).toContain(sensor_support_matrix_1.SENSOR_END_MARKER);
|
|
40
|
-
expect(doc).toContain(
|
|
41
|
-
expect(doc).toContain('
|
|
42
|
-
expect(doc).toBe((0, sensor_support_matrix_1.spliceSensorSupportMatrix)(doc, (0, sensor_support_matrix_1.renderSensorSupportMatrix)(SENSOR_FIXTURE_REGISTRY)));
|
|
47
|
+
expect(doc).toContain('published registry evidence');
|
|
48
|
+
expect(doc).toContain('Published certification evidence');
|
|
43
49
|
});
|
|
44
|
-
it('
|
|
50
|
+
it('uses a published registry root for documentation and retains a fixture-only command', () => {
|
|
45
51
|
const packageJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(__dirname, '..', '..', 'package.json'), 'utf8'));
|
|
46
|
-
expect(packageJson.scripts?.['docs:matrix']).toContain(
|
|
52
|
+
expect(packageJson.scripts?.['docs:matrix']).toContain('--registry-root ../../awm-baseline-registry');
|
|
53
|
+
expect(packageJson.scripts?.['docs:matrix:prepublication']).toContain(`--registry-root ${sensor_support_matrix_1.R3_PREPUBLICATION_FIXTURE_RELATIVE_PATH}`);
|
|
54
|
+
});
|
|
55
|
+
it('CI regenerates the published matrix from the immutable registry tag', () => {
|
|
56
|
+
const workflow = fs_1.default.readFileSync(CI_WORKFLOW_PATH, 'utf8');
|
|
57
|
+
expect(workflow).toContain('repository: Kodria/awm-baseline-registry');
|
|
58
|
+
expect(workflow).toContain('ref: v2.0.0');
|
|
59
|
+
expect(workflow).toContain('path: awm-baseline-registry');
|
|
60
|
+
expect(workflow).toContain('Verify published sensor support matrix');
|
|
61
|
+
expect(workflow).toContain('--registry-root ../awm-baseline-registry --registry-tag v2.0.0 --registry-commit c35c087a0801c0b4e69e0a4ac3eafef9ecdf37cd');
|
|
62
|
+
expect(workflow).toContain('git diff --exit-code -- docs/support-matrix.md');
|
|
63
|
+
});
|
|
64
|
+
it('retains the published registry certification rows verbatim instead of inventing fixture evidence', () => {
|
|
65
|
+
const publishedSupport = [
|
|
66
|
+
'# Sensor pack support',
|
|
67
|
+
'',
|
|
68
|
+
'<!-- BEGIN GENERATED: sensor-pack-support -->',
|
|
69
|
+
'Generated from pack manifests and certification pins resolved at `2026-08-14T22:14:34.365Z`.',
|
|
70
|
+
'',
|
|
71
|
+
'Status: `certified` has a matching frozen tool pin; `compatible-unverified` has no matching frozen pin; `not-applicable` is reserved for variants without a tool contract.',
|
|
72
|
+
'',
|
|
73
|
+
'| Pack | Sensor | Variant | Tool | Certified range | Supported OS | OS certification evidence | Status | Evidence |',
|
|
74
|
+
'| --- | --- | --- | --- | --- | --- | --- | --- | --- |',
|
|
75
|
+
'| `js-ts` | `lint` | `eslint-10-flat` | `eslint` | `=10.8.1` | Ubuntu, macOS, Windows | Ubuntu/macOS/Windows: contract | certified | pin: `eslint@10.8.1` |',
|
|
76
|
+
'| `js-ts` | `test` | `npm-script` | `npm` | `>=8.0.0` | Ubuntu, macOS, Windows | Ubuntu/macOS/Windows: contract | compatible-unverified | no matching pinned tool |',
|
|
77
|
+
'',
|
|
78
|
+
'| Certification status | Derived variant count | Meaning |',
|
|
79
|
+
'| --- | --- | --- |',
|
|
80
|
+
'| certified | 15 | Matching frozen tool pin |',
|
|
81
|
+
'| compatible-unverified | 6 | No matching frozen tool pin |',
|
|
82
|
+
'| not-applicable | 0 | Variant has no tool contract |',
|
|
83
|
+
'<!-- END GENERATED: sensor-pack-support -->',
|
|
84
|
+
'',
|
|
85
|
+
].join('\n');
|
|
86
|
+
expect((0, sensor_support_matrix_1.extractPublishedSupportMetadata)(publishedSupport)).toContain('`eslint-10-flat`');
|
|
87
|
+
expect((0, sensor_support_matrix_1.extractPublishedSupportMetadata)(publishedSupport)).toContain('compatible-unverified');
|
|
88
|
+
expect((0, sensor_support_matrix_1.extractPublishedSupportMetadata)(publishedSupport)).not.toContain('Fixture-declared ranges only');
|
|
89
|
+
});
|
|
90
|
+
it('rejects a mutable checkout even when it contains the published v2.0.0 tag', () => {
|
|
91
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-published-registry-'));
|
|
92
|
+
const git = (...args) => (0, child_process_1.execFileSync)('git', ['-C', registryRoot, ...args], { stdio: 'pipe' });
|
|
93
|
+
try {
|
|
94
|
+
git('init');
|
|
95
|
+
git('config', 'user.email', 'test@example.com');
|
|
96
|
+
git('config', 'user.name', 'AWM test');
|
|
97
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'published.txt'), 'published\n');
|
|
98
|
+
git('add', '.');
|
|
99
|
+
git('commit', '-m', 'published registry');
|
|
100
|
+
git('tag', 'v2.0.0');
|
|
101
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'mutable.txt'), 'newer checkout\n');
|
|
102
|
+
git('add', '.');
|
|
103
|
+
git('commit', '-m', 'mutable checkout');
|
|
104
|
+
expect(() => (0, sensor_support_matrix_1.verifyPublishedRegistryIdentity)(registryRoot, 'v2.0.0', 'c35c087a0801c0b4e69e0a4ac3eafef9ecdf37cd')).toThrow(/HEAD .*expected commit/i);
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
fs_1.default.rmSync(registryRoot, { recursive: true, force: true });
|
|
108
|
+
}
|
|
47
109
|
});
|
|
48
110
|
it('la tabla nombra a los seis providers declarados', () => {
|
|
49
111
|
// Que el bloque este "al dia" no sirve si el generador se olvidara de un provider:
|
|
@@ -156,6 +218,16 @@ describe('docs/support-matrix.md refleja el codigo', () => {
|
|
|
156
218
|
expect(out).toContain('linea uno\r\nlinea dos');
|
|
157
219
|
expect(out.split('\r\n').length - 1).toBe(out.split('\n').length - 1); // ni un LF suelto
|
|
158
220
|
});
|
|
221
|
+
it('preserva el EOL del bloque de providers aunque otro bloque sea CRLF', () => {
|
|
222
|
+
const mixedDoc = `intro\r\n${support_matrix_1.BEGIN_MARKER}\nold\n${support_matrix_1.END_MARKER}\nfin\n`;
|
|
223
|
+
const out = (0, support_matrix_1.spliceGenerated)(mixedDoc, 'linea uno\nlinea dos');
|
|
224
|
+
expect(out).toContain(`${support_matrix_1.BEGIN_MARKER}\n\nlinea uno\nlinea dos\n\n${support_matrix_1.END_MARKER}`);
|
|
225
|
+
});
|
|
226
|
+
it('preserva el EOL del bloque de sensores aunque otro bloque sea CRLF', () => {
|
|
227
|
+
const mixedDoc = `intro\r\n${sensor_support_matrix_1.SENSOR_BEGIN_MARKER}\nold\n${sensor_support_matrix_1.SENSOR_END_MARKER}\nfin\n`;
|
|
228
|
+
const out = (0, sensor_support_matrix_1.spliceSensorSupportMatrix)(mixedDoc, 'linea uno\nlinea dos');
|
|
229
|
+
expect(out).toContain(`${sensor_support_matrix_1.SENSOR_BEGIN_MARKER}\n\nlinea uno\nlinea dos\n\n${sensor_support_matrix_1.SENSOR_END_MARKER}`);
|
|
230
|
+
});
|
|
159
231
|
it('marks an unsupported scope rather than leaving it absent', () => {
|
|
160
232
|
// La diferencia entre "no soportado" y una celda vacia es exactamente lo que el
|
|
161
233
|
// documento existe para no dejar ambiguo: Copilot no tiene scope global por
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-workflow-manager",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.1.1",
|
|
4
4
|
"main": "dist/src/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"awm": "./dist/src/index.js"
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"release": "node dist/src/release/index.js",
|
|
28
28
|
"prepack": "npm run build",
|
|
29
29
|
"prepublishOnly": "npm run build",
|
|
30
|
-
"docs:matrix": "ts-node scripts/support-matrix.ts && ts-node scripts/sensor-support-matrix.ts --registry-root
|
|
30
|
+
"docs:matrix": "ts-node scripts/support-matrix.ts && ts-node scripts/sensor-support-matrix.ts --registry-root ../../awm-baseline-registry --registry-tag v2.0.0 --registry-commit c35c087a0801c0b4e69e0a4ac3eafef9ecdf37cd",
|
|
31
|
+
"docs:matrix:prepublication": "ts-node scripts/sensor-support-matrix.ts --registry-root tests/fixtures/sensor-support-matrix/registry --prepublication-fixture --stdout"
|
|
31
32
|
},
|
|
32
33
|
"keywords": [
|
|
33
34
|
"agentic",
|