agentic-workflow-manager 8.1.1 → 8.1.2
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/sensors/compatibility/contract.js +3 -2
- package/dist/src/commands/sensors/compatibility/discovery.js +2 -1
- package/dist/src/commands/sensors/compatibility/live.js +1 -0
- package/dist/src/commands/sensors/compatibility/probe.js +6 -1
- package/dist/src/commands/sensors/compatibility/resolve.js +14 -1
- package/dist/tests/commands/sensors/compatibility/contract.test.js +5 -3
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +2 -1
- package/dist/tests/commands/sensors/compatibility/probe.test.js +9 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +26 -0
- package/dist/tests/commands/sensors/init.test.js +1 -0
- package/package.json +1 -1
|
@@ -206,7 +206,7 @@ function parseVariant(input, source, location) {
|
|
|
206
206
|
invalid(source, `${location} requires requirements and probe when policyRef is absent`);
|
|
207
207
|
const requirements = policy ? undefined : record(value.requirements, source, `${location}.requirements`);
|
|
208
208
|
if (requirements)
|
|
209
|
-
fields(requirements, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'configFiles'], source, `${location}.requirements`);
|
|
209
|
+
fields(requirements, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'configFiles', 'packageJsonFields'], source, `${location}.requirements`);
|
|
210
210
|
const toolRange = policy?.toolRange ?? text(requirements.toolRange, source, `${location}.requirements.toolRange`);
|
|
211
211
|
const runtimeRange = policy?.runtimeRange ?? text(requirements.runtimeRange, source, `${location}.requirements.runtimeRange`);
|
|
212
212
|
if (semver_1.default.validRange(toolRange) === null || semver_1.default.validRange(runtimeRange) === null)
|
|
@@ -223,7 +223,8 @@ function parseVariant(input, source, location) {
|
|
|
223
223
|
certifiedRange,
|
|
224
224
|
requirements: policy
|
|
225
225
|
? { tool: policy.tool, toolRange, runtime: policy.runtime, runtimeRange }
|
|
226
|
-
: { tool: text(requirements.tool, source, `${location}.requirements.tool`), toolRange, runtime: text(requirements.runtime, source, `${location}.requirements.runtime`), runtimeRange, ...('configFiles' in requirements ? { configFiles: stringArray(requirements.configFiles, source, `${location}.requirements.configFiles`).map((file, index) => asset(file, source, `${location}.requirements.configFiles[${index}]`)) } : {}) },
|
|
226
|
+
: { tool: text(requirements.tool, source, `${location}.requirements.tool`), toolRange, runtime: text(requirements.runtime, source, `${location}.requirements.runtime`), runtimeRange, ...('configFiles' in requirements ? { configFiles: stringArray(requirements.configFiles, source, `${location}.requirements.configFiles`).map((file, index) => asset(file, source, `${location}.requirements.configFiles[${index}]`)) } : {}), ...('packageJsonFields' in requirements ? { packageJsonFields: stringArray(requirements.packageJsonFields, source, `${location}.requirements.packageJsonFields`).map((field, index) => { if (!/^[A-Za-z][A-Za-z0-9]*$/.test(field))
|
|
227
|
+
invalid(source, `${location}.requirements.packageJsonFields[${index}] must be a stable package.json field`); return field; }) } : {}) },
|
|
227
228
|
assets: assetArray(value.assets, source, `${location}.assets`, true),
|
|
228
229
|
formatter: text(value.formatter, source, `${location}.formatter`),
|
|
229
230
|
probe: { kind: policy?.probe ?? probe.kind },
|
|
@@ -190,6 +190,7 @@ function discoverProjectEvidence(cwd, pack, dependencies = {}) {
|
|
|
190
190
|
}
|
|
191
191
|
const configFiles = [...configCandidates].filter(file => safeFile(root, file)).sort();
|
|
192
192
|
const scripts = Object.keys(stringMap(pkg?.scripts)).sort();
|
|
193
|
+
const packageJsonFields = Object.keys(pkg ?? {}).filter(field => /^[A-Za-z][A-Za-z0-9]*$/.test(field)).sort();
|
|
193
194
|
const declaredToolRanges = { ...stringMap(pkg?.dependencies), ...stringMap(pkg?.devDependencies), ...stringMap(pkg?.peerDependencies) };
|
|
194
195
|
const tools = new Set();
|
|
195
196
|
if ('schemaVersion' in pack)
|
|
@@ -202,6 +203,6 @@ function discoverProjectEvidence(cwd, pack, dependencies = {}) {
|
|
|
202
203
|
return {
|
|
203
204
|
cwd: root, os: targetPlatform, runtimeVersions: { node: process.versions.node ?? null, ...(environment ? { python: environment.runtimeVersion } : {}) }, pythonEnvironmentRoot: environment?.rootParts[0] ?? null, declaredToolRanges, toolVersions,
|
|
204
205
|
packageManager: declaredManager ?? (lockManagers.size === 1 ? [...lockManagers][0] : null), packageManagerConflict: lockManagers.size > 1,
|
|
205
|
-
scripts, configFiles, paths: [...new Set([...(safeFile(root, 'package.json') ? ['package.json'] : []), ...locks, ...configFiles])].sort(),
|
|
206
|
+
scripts, configFiles, packageJsonFields, paths: [...new Set([...(safeFile(root, 'package.json') ? ['package.json'] : []), ...locks, ...configFiles])].sort(),
|
|
206
207
|
};
|
|
207
208
|
}
|
|
@@ -82,6 +82,7 @@ async function resolveParsedPackCompatibility(cwd, pack, options = {}) {
|
|
|
82
82
|
toolExecutable: variant.command.executable,
|
|
83
83
|
toolResolution: variant.command.resolution,
|
|
84
84
|
pythonEnvironmentRoot: variant.command.pythonEnvironmentRoot,
|
|
85
|
+
environment: variant.command.environment,
|
|
85
86
|
configFiles: evidence.configFiles,
|
|
86
87
|
scripts: evidence.scripts,
|
|
87
88
|
})
|
|
@@ -16,7 +16,12 @@ function commandFor(kind, evidence) {
|
|
|
16
16
|
return null;
|
|
17
17
|
if (kind === 'config-present')
|
|
18
18
|
return null;
|
|
19
|
-
const command = {
|
|
19
|
+
const command = {
|
|
20
|
+
executable,
|
|
21
|
+
resolution,
|
|
22
|
+
...(resolution === 'python-environment' && evidence.pythonEnvironmentRoot ? { pythonEnvironmentRoot: evidence.pythonEnvironmentRoot } : {}),
|
|
23
|
+
...(evidence.environment ? { environment: evidence.environment } : {}),
|
|
24
|
+
};
|
|
20
25
|
if (kind === 'version')
|
|
21
26
|
return { ...command, args: ['--version'] };
|
|
22
27
|
if (kind === 'eslint-print-config')
|
|
@@ -45,6 +45,17 @@ function toolFor(variant, evidence) {
|
|
|
45
45
|
function runtimeFor(variant, evidence) {
|
|
46
46
|
return evidence.runtimeVersions === undefined ? evidence.runtimeVersion : evidence.runtimeVersions[variant.requirements.runtime];
|
|
47
47
|
}
|
|
48
|
+
/** A variant may name alternative native configuration files that make it
|
|
49
|
+
* applicable. They are alternatives (for example `.eslintrc.*`), not a list
|
|
50
|
+
* that a project must contain in full. A variant without this requirement is
|
|
51
|
+
* configuration-agnostic. */
|
|
52
|
+
function hasRequiredConfig(variant, evidence) {
|
|
53
|
+
const requiredFiles = variant.requirements.configFiles ?? [];
|
|
54
|
+
const requiredFields = variant.requirements.packageJsonFields ?? [];
|
|
55
|
+
return (requiredFiles.length === 0 && requiredFields.length === 0)
|
|
56
|
+
|| requiredFiles.some(file => evidence.paths?.includes(file))
|
|
57
|
+
|| requiredFields.some(field => evidence.packageJsonFields?.includes(field));
|
|
58
|
+
}
|
|
48
59
|
/** Pure precedence resolver. It consumes discovered evidence and neither probes nor executes commands. */
|
|
49
60
|
function resolveSensorCompatibility(sensor, evidence, context) {
|
|
50
61
|
if (!sensor || typeof sensor !== 'object' || !evidence || typeof evidence !== 'object')
|
|
@@ -66,7 +77,9 @@ function resolveSensorCompatibility(sensor, evidence, context) {
|
|
|
66
77
|
const operational = v2.variants.filter(variant => validVersion(toolFor(variant, evidence)) && validVersion(runtimeFor(variant, evidence)));
|
|
67
78
|
if (operational.length === 0)
|
|
68
79
|
return result('unverifiable', 'invalid-or-missing-version-evidence', null, evidence);
|
|
69
|
-
const matches = operational.filter(variant => semver_1.default.satisfies(toolFor(variant, evidence), variant.requirements.toolRange)
|
|
80
|
+
const matches = operational.filter(variant => semver_1.default.satisfies(toolFor(variant, evidence), variant.requirements.toolRange)
|
|
81
|
+
&& semver_1.default.satisfies(runtimeFor(variant, evidence), variant.requirements.runtimeRange)
|
|
82
|
+
&& hasRequiredConfig(variant, evidence));
|
|
70
83
|
if (matches.length === 0)
|
|
71
84
|
return result('incompatible', 'no-operational-variant', null, evidence);
|
|
72
85
|
matches.sort((a, b) => b.priority - a.priority || specificity(b) - specificity(a) || a.id.localeCompare(b.id));
|
|
@@ -150,9 +150,11 @@ describe('sensor pack v2 contract', () => {
|
|
|
150
150
|
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { applicability: { allFiles: [3] }, variants: [variant] } } }, 'pack')).toThrow('allFiles[0]');
|
|
151
151
|
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...variant, probe: { kind: 'version', extra: true } }] } } }, 'pack')).toThrow('unknown field');
|
|
152
152
|
});
|
|
153
|
-
it('preserves
|
|
154
|
-
const variant = { ...validPack().sensors.lint.variants[0], requirements: { ...validPack().sensors.lint.variants[0].requirements, configFiles: ['eslint.config.js'] } };
|
|
155
|
-
expect((0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [variant] } } }, 'pack')).toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{ requirements: { configFiles: ['eslint.config.js'] } }] } } } });
|
|
153
|
+
it('preserves config selectors and validates package.json field names', () => {
|
|
154
|
+
const variant = { ...validPack().sensors.lint.variants[0], requirements: { ...validPack().sensors.lint.variants[0].requirements, configFiles: ['eslint.config.js'], packageJsonFields: ['eslintConfig'] } };
|
|
155
|
+
expect((0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [variant] } } }, 'pack')).toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{ requirements: { configFiles: ['eslint.config.js'], packageJsonFields: ['eslintConfig'] } }] } } } });
|
|
156
|
+
const malformed = { ...variant, requirements: { ...variant.requirements, packageJsonFields: ['eslint-config'] } };
|
|
157
|
+
expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [malformed] } } }, 'pack')).toThrow('packageJsonFields[0]');
|
|
156
158
|
const complete = validPack().sensors.lint.variants[0];
|
|
157
159
|
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, id: '../bad' }])).toThrow('stable lowercase id');
|
|
158
160
|
expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, command: undefined }])).toThrow('command');
|
|
@@ -11,7 +11,7 @@ describe('discoverProjectEvidence', () => {
|
|
|
11
11
|
it('returns only local, relative project evidence and detects conflicting lockfiles', () => {
|
|
12
12
|
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-'));
|
|
13
13
|
try {
|
|
14
|
-
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '10.0.0' } }));
|
|
14
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, eslintConfig: { env: { node: true } }, devDependencies: { eslint: '10.0.0' } }));
|
|
15
15
|
fs_1.default.writeFileSync(path_1.default.join(root, 'package-lock.json'), '{}');
|
|
16
16
|
fs_1.default.writeFileSync(path_1.default.join(root, 'pnpm-lock.yaml'), 'lockfileVersion: 9');
|
|
17
17
|
fs_1.default.writeFileSync(path_1.default.join(root, 'eslint.config.js'), 'export default []');
|
|
@@ -24,6 +24,7 @@ describe('discoverProjectEvidence', () => {
|
|
|
24
24
|
expect(evidence.toolVersions.eslint).toBe('10.4.1');
|
|
25
25
|
expect(evidence.scripts).toContain('lint');
|
|
26
26
|
expect(evidence.configFiles).toContain('eslint.config.js');
|
|
27
|
+
expect(evidence.packageJsonFields).toContain('eslintConfig');
|
|
27
28
|
expect(evidence.paths.every((item) => !path_1.default.isAbsolute(item) && !item.includes('..'))).toBe(true);
|
|
28
29
|
}
|
|
29
30
|
finally {
|
|
@@ -20,6 +20,15 @@ describe('runCompatibilityProbe', () => {
|
|
|
20
20
|
await (0, probe_1.runCompatibilityProbe)({ kind: 'eslint-print-config' }, evidence, fakeExecutor);
|
|
21
21
|
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: 'eslint', resolution: 'node-modules-bin' }), expect.any(Object));
|
|
22
22
|
});
|
|
23
|
+
it('propagates the selected ESLint mode to its compatibility probe', async () => {
|
|
24
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'eslint-print-config' }, {
|
|
25
|
+
...evidence,
|
|
26
|
+
toolExecutable: 'eslint',
|
|
27
|
+
toolResolution: 'node-modules-bin',
|
|
28
|
+
environment: { ESLINT_USE_FLAT_CONFIG: 'true' },
|
|
29
|
+
}, fakeExecutor);
|
|
30
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ environment: { ESLINT_USE_FLAT_CONFIG: 'true' } }), expect.any(Object));
|
|
31
|
+
});
|
|
23
32
|
it('binds Semgrep validation to the contained Python environment', async () => {
|
|
24
33
|
await (0, probe_1.runCompatibilityProbe)({ kind: 'semgrep-validate' }, evidence, fakeExecutor);
|
|
25
34
|
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: 'semgrep', resolution: 'python-environment' }), expect.any(Object));
|
|
@@ -50,6 +50,32 @@ describe('resolveSensorCompatibility', () => {
|
|
|
50
50
|
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants }, evidence({ toolVersions: { eslint: '10.4.1', biome: null }, runtimeVersions: { node: '22.0.0' }, toolVersion: '8.0.0', runtimeVersion: '8.0.0' }), context);
|
|
51
51
|
expect(resolved).toMatchObject({ state: 'certified', variantId: 'eslint', toolVersion: '10.4.1', runtimeVersion: '22.0.0' });
|
|
52
52
|
});
|
|
53
|
+
it('selects the ESLint 8 flat variant from native config evidence before priority', () => {
|
|
54
|
+
const eslintrc = {
|
|
55
|
+
...variant('eslint-8-eslintrc', 40, '>=8 <9', '=8.57.1'),
|
|
56
|
+
requirements: { ...variant('eslint-8-eslintrc').requirements, toolRange: '>=8 <9', configFiles: ['.eslintrc.json'] },
|
|
57
|
+
};
|
|
58
|
+
const flat = {
|
|
59
|
+
...variant('eslint-8-flat', 30, '>=8 <9', '=8.57.1'),
|
|
60
|
+
requirements: { ...variant('eslint-8-flat').requirements, toolRange: '>=8 <9', configFiles: ['eslint.config.mjs'] },
|
|
61
|
+
};
|
|
62
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants: [eslintrc, flat] }, evidence({ toolVersion: '8.57.1', paths: ['package.json', 'eslint.config.mjs'] }), context);
|
|
63
|
+
expect(resolved).toMatchObject({ state: 'certified', variantId: 'eslint-8-flat' });
|
|
64
|
+
});
|
|
65
|
+
it('selects the ESLint 8 eslintrc variant from package.json eslintConfig evidence', () => {
|
|
66
|
+
const eslintrc = {
|
|
67
|
+
...variant('eslint-8-eslintrc', 40, '>=8 <9', '=8.57.1'),
|
|
68
|
+
requirements: { ...variant('eslint-8-eslintrc').requirements, toolRange: '>=8 <9', packageJsonFields: ['eslintConfig'] },
|
|
69
|
+
};
|
|
70
|
+
const flat = {
|
|
71
|
+
...variant('eslint-8-flat', 30, '>=8 <9', '=8.57.1'),
|
|
72
|
+
requirements: { ...variant('eslint-8-flat').requirements, toolRange: '>=8 <9', configFiles: ['eslint.config.mjs'] },
|
|
73
|
+
};
|
|
74
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants: [eslintrc, flat] }, evidence({ toolVersion: '8.57.1', paths: ['package.json'], packageJsonFields: ['eslintConfig'] }), context);
|
|
75
|
+
expect(resolved).toMatchObject({ state: 'certified', variantId: 'eslint-8-eslintrc' });
|
|
76
|
+
expect((0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants: [eslintrc, flat] }, evidence({ toolVersion: '8.57.1', paths: ['package.json'], packageJsonFields: [] }), context))
|
|
77
|
+
.toMatchObject({ state: 'incompatible', variantId: null });
|
|
78
|
+
});
|
|
53
79
|
it('does not reuse scalar evidence for a missing key in a version map', () => {
|
|
54
80
|
const biome = { ...variant('biome'), requirements: { ...variant('biome').requirements, tool: 'biome', runtime: 'bun' } };
|
|
55
81
|
expect((0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants: [biome] }, evidence({ toolVersions: { biome: null }, runtimeVersions: { bun: null }, toolVersion: '10.4.1', runtimeVersion: '22.0.0' }), context))
|
|
@@ -305,6 +305,7 @@ describe('initSensors', () => {
|
|
|
305
305
|
const automatic = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, configure: false });
|
|
306
306
|
expect(automatic.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', sensors: {} });
|
|
307
307
|
expect(automatic.manifest.packSelection).toBeUndefined();
|
|
308
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'generic.config'), 'fixture\n');
|
|
308
309
|
const explicit = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, pack: 'generic' });
|
|
309
310
|
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
310
311
|
expect(explicit.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', packSelection: 'explicit', sensors: { security: { variantId: 'eslint-10' } } });
|