agentic-workflow-manager 6.5.2 → 8.1.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.
Files changed (95) hide show
  1. package/dist/scripts/sensor-support-matrix.js +103 -0
  2. package/dist/scripts/support-matrix.js +40 -17
  3. package/dist/src/commands/init.js +1 -3
  4. package/dist/src/commands/ledger/index.js +6 -0
  5. package/dist/src/commands/preflight/checks.js +4 -4
  6. package/dist/src/commands/preflight/index.js +2 -2
  7. package/dist/src/commands/registry/add.js +2 -2
  8. package/dist/src/commands/registry/index.js +5 -0
  9. package/dist/src/commands/registry/status.js +7 -0
  10. package/dist/src/commands/sensors/compatibility/contract.js +382 -0
  11. package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
  12. package/dist/src/commands/sensors/compatibility/live.js +92 -0
  13. package/dist/src/commands/sensors/compatibility/manifest.js +207 -0
  14. package/dist/src/commands/sensors/compatibility/materialize.js +134 -0
  15. package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
  16. package/dist/src/commands/sensors/compatibility/probe.js +47 -0
  17. package/dist/src/commands/sensors/compatibility/resolve.js +99 -0
  18. package/dist/src/commands/sensors/compatibility/types.js +2 -0
  19. package/dist/src/commands/sensors/coverage/contract.js +0 -50
  20. package/dist/src/commands/sensors/coverage/empirical.js +131 -0
  21. package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
  22. package/dist/src/commands/sensors/coverage/evidence.js +5 -1
  23. package/dist/src/commands/sensors/coverage/index.js +44 -7
  24. package/dist/src/commands/sensors/coverage/render.js +214 -14
  25. package/dist/src/commands/sensors/coverage/resolve.js +24 -38
  26. package/dist/src/commands/sensors/exec.js +244 -9
  27. package/dist/src/commands/sensors/index.js +18 -6
  28. package/dist/src/commands/sensors/init.js +95 -6
  29. package/dist/src/commands/sensors/run.js +71 -0
  30. package/dist/src/commands/sensors/status.js +48 -4
  31. package/dist/src/commands/sync.js +7 -0
  32. package/dist/src/core/bundles.js +38 -3
  33. package/dist/src/core/init/steps.js +7 -4
  34. package/dist/src/core/ledger/scan.js +228 -0
  35. package/dist/src/core/ledger/store.js +89 -25
  36. package/dist/src/core/ledger/types.js +54 -0
  37. package/dist/src/core/paths.js +10 -9
  38. package/dist/src/core/registries.js +134 -14
  39. package/dist/src/index.js +24 -1
  40. package/dist/tests/commands/ledger/index.test.js +11 -0
  41. package/dist/tests/commands/preflight/preflight.test.js +64 -64
  42. package/dist/tests/commands/registry/add.test.js +69 -2
  43. package/dist/tests/commands/registry/status.test.js +14 -0
  44. package/dist/tests/commands/sensors/compatibility/contract.test.js +193 -0
  45. package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
  46. package/dist/tests/commands/sensors/compatibility/live.test.js +124 -0
  47. package/dist/tests/commands/sensors/compatibility/manifest.test.js +77 -0
  48. package/dist/tests/commands/sensors/compatibility/materialize.test.js +72 -0
  49. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
  50. package/dist/tests/commands/sensors/compatibility/probe.test.js +37 -0
  51. package/dist/tests/commands/sensors/compatibility/python-venv-fixture.js +29 -0
  52. package/dist/tests/commands/sensors/compatibility/resolve.test.js +83 -0
  53. package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
  54. package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
  55. package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
  56. package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
  57. package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
  58. package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
  59. package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
  60. package/dist/tests/commands/sensors/exec-windows.test.js +140 -2
  61. package/dist/tests/commands/sensors/exec.test.js +115 -5
  62. package/dist/tests/commands/sensors/index.test.js +8 -0
  63. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
  64. package/dist/tests/commands/sensors/init.test.js +204 -48
  65. package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
  66. package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
  67. package/dist/tests/commands/sensors/run.test.js +66 -3
  68. package/dist/tests/commands/sensors/status-windows.test.js +7 -7
  69. package/dist/tests/commands/sensors/status.test.js +74 -23
  70. package/dist/tests/commands/update.test.js +1 -1
  71. package/dist/tests/core/bundles.test.js +37 -0
  72. package/dist/tests/core/discovery.test.js +4 -0
  73. package/dist/tests/core/init/orchestrator.test.js +5 -1
  74. package/dist/tests/core/init/steps.test.js +20 -4
  75. package/dist/tests/core/ledger/scan.test.js +217 -0
  76. package/dist/tests/core/ledger/store.test.js +108 -0
  77. package/dist/tests/core/profile-pins.test.js +2 -1
  78. package/dist/tests/core/project-skill-links.test.js +22 -0
  79. package/dist/tests/core/registries-capability.test.js +10 -0
  80. package/dist/tests/core/registries-sync.test.js +16 -0
  81. package/dist/tests/core/registries.test.js +129 -1
  82. package/dist/tests/core/registry-manifest.test.js +7 -0
  83. package/dist/tests/core/same-existing-path.test.js +35 -0
  84. package/dist/tests/core/sync-gates.test.js +2 -1
  85. package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
  86. package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
  87. package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
  88. package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
  89. package/dist/tests/structural/active-documentation.test.js +128 -0
  90. package/dist/tests/structural/async-entrypoint.test.js +15 -0
  91. package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
  92. package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
  93. package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
  94. package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
  95. package/package.json +8 -2
@@ -0,0 +1,193 @@
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 contract_1 = require("../../../../src/commands/sensors/compatibility/contract");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const coverage = {
11
+ schemaVersion: 1,
12
+ classes: {
13
+ formatting: {
14
+ description: 'Formatting is checked.',
15
+ detectors: [{ sensor: 'lint' }],
16
+ remedy: { summary: 'Add a formatter.', command: 'npm install prettier' },
17
+ },
18
+ },
19
+ };
20
+ function validPack() {
21
+ return {
22
+ schemaVersion: 2,
23
+ name: 'js-ts', description: 'fixture', detects: ['package.json'],
24
+ sensors: {
25
+ lint: {
26
+ applicability: { allFiles: ['package.json'] },
27
+ variants: [{
28
+ id: 'eslint-9',
29
+ priority: 10,
30
+ requirements: { tool: 'eslint', toolRange: '>=9.0.0 <10.0.0', runtime: 'node', runtimeRange: '>=20.0.0' },
31
+ certifiedRange: '>=9.0.0 <10.0.0',
32
+ assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'eslint-print-config' },
33
+ command: {
34
+ executable: 'eslint',
35
+ resolution: 'node-modules-bin',
36
+ args: ['--format', 'json', '{files}'],
37
+ fileInput: { placeholder: '{files}', extensions: ['.ts', '.tsx'] },
38
+ },
39
+ }],
40
+ },
41
+ },
42
+ coverage,
43
+ };
44
+ }
45
+ describe('sensor pack v2 contract', () => {
46
+ it('derives Semgrep compatibility from a contained shared policy reference', () => {
47
+ const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-policy-'));
48
+ const packDir = path_1.default.join(sensorPacks, 'python');
49
+ try {
50
+ fs_1.default.mkdirSync(path_1.default.join(sensorPacks, 'shared'), { recursive: true });
51
+ fs_1.default.mkdirSync(packDir);
52
+ fs_1.default.writeFileSync(path_1.default.join(sensorPacks, 'shared', 'semgrep-policy.json'), JSON.stringify({
53
+ tool: 'semgrep', toolRange: '>=1.0.0', runtime: 'python', runtimeRange: '>=3.9.0', probe: 'semgrep-validate',
54
+ }));
55
+ const pack = validPack();
56
+ pack.sensors.lint.variants[0] = {
57
+ id: 'semgrep-python', priority: 10, certifiedRange: '>=1.0.0', policyRef: 'shared/semgrep-policy.json',
58
+ command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] },
59
+ assets: ['.semgrep.awm.yml'], formatter: 'semgrep',
60
+ };
61
+ const parsed = (0, contract_1.parseSensorPack)(pack, path_1.default.join(packDir, 'pack.json'));
62
+ expect(parsed).toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{
63
+ policyRef: 'shared/semgrep-policy.json', requirements: { tool: 'semgrep', runtime: 'python' }, probe: { kind: 'semgrep-validate' },
64
+ }] } } } });
65
+ }
66
+ finally {
67
+ fs_1.default.rmSync(sensorPacks, { recursive: true, force: true });
68
+ }
69
+ });
70
+ it('fails closed when a policy reference is not the AWM-owned shared Semgrep policy', () => {
71
+ const pack = validPack();
72
+ pack.sensors.lint.variants[0] = {
73
+ id: 'semgrep-python', priority: 10, certifiedRange: '>=1.0.0', policyRef: '../secret.json',
74
+ command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] },
75
+ assets: ['.semgrep.awm.yml'], formatter: 'semgrep',
76
+ };
77
+ expect(() => (0, contract_1.parseSensorPack)(pack, '/tmp/sensor-packs/python/pack.json')).toThrow('policyRef');
78
+ });
79
+ it('parses a valid versioned pack', () => {
80
+ expect((0, contract_1.parseSensorPack)(validPack(), 'pack.json')).toMatchObject({ kind: 'v2', pack: validPack() });
81
+ });
82
+ it('accepts an opt-in hardening asset while variants may require no assets', () => {
83
+ const pack = {
84
+ ...validPack(),
85
+ hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } },
86
+ sensors: {
87
+ lint: {
88
+ ...validPack().sensors.lint,
89
+ variants: [{ ...validPack().sensors.lint.variants[0], assets: [] }],
90
+ },
91
+ },
92
+ };
93
+ expect((0, contract_1.parseSensorPack)(pack, 'pack.json')).toMatchObject({
94
+ kind: 'v2',
95
+ pack: { hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } }, sensors: { lint: { variants: [{ assets: [] }] } } },
96
+ });
97
+ });
98
+ it.each(['true', 'false'])('accepts the exact ESLINT_USE_FLAT_CONFIG=%s environment mapping', flatConfig => {
99
+ const command = {
100
+ executable: 'npm',
101
+ resolution: 'path',
102
+ args: ['run', 'lint'],
103
+ packageManager: 'npm',
104
+ environment: { ESLINT_USE_FLAT_CONFIG: flatConfig },
105
+ };
106
+ expect((0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command }] } } }, 'pack.json'))
107
+ .toMatchObject({ kind: 'v2', pack: { sensors: { lint: { variants: [{ command }] } } } });
108
+ });
109
+ it.each([
110
+ { ESLINT_USE_FLAT_CONFIG: 'yes' },
111
+ { ESLINT_USE_FLAT_CONFIG: 'true', OTHER: 'value' },
112
+ ])('rejects an unknown or expanded ESLint environment mapping', environment => {
113
+ const command = { executable: 'npm', resolution: 'path', args: ['run', 'lint'], packageManager: 'npm', environment };
114
+ expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command }] } } }, 'pack.json'))
115
+ .toThrow('command.environment');
116
+ });
117
+ it('normalizes package-manager executable spelling before enforcing its explicit selection', () => {
118
+ const variant = validPack().sensors.lint.variants[0];
119
+ const withExecutable = (executable, packageManager) => ({
120
+ ...validPack(),
121
+ sensors: { lint: { ...validPack().sensors.lint, variants: [{
122
+ ...variant,
123
+ command: { executable, resolution: 'path', args: ['run', 'lint'], ...(packageManager === undefined ? {} : { packageManager }) },
124
+ }] } },
125
+ });
126
+ expect(() => (0, contract_1.parseSensorPack)(withExecutable('NPM'), 'pack.json')).toThrow('packageManager');
127
+ expect((0, contract_1.parseSensorPack)(withExecutable('npm.exe', 'NPM'), 'pack.json')).toMatchObject({
128
+ kind: 'v2', pack: { sensors: { lint: { variants: [{ command: { executable: 'npm.exe', packageManager: 'npm' } }] } } },
129
+ });
130
+ expect(() => (0, contract_1.parseSensorPack)(withExecutable('NPM', 'pnpm'), 'pack.json')).toThrow('match executable');
131
+ });
132
+ it('keeps an unversioned pack on the legacy compatibility path', () => {
133
+ const legacy = { name: 'legacy', sensors: {} };
134
+ expect((0, contract_1.parseSensorPack)(legacy, 'pack.json')).toMatchObject({ kind: 'legacy', pack: { ...legacy, compatibility: { state: 'compatible-unverified' } } });
135
+ });
136
+ test.each([
137
+ [{ name: 'legacy', detects: [null], sensors: {} }, 'detects[0]'],
138
+ [{ name: 'legacy', detects: ['package.json'], sensors: { lint: { unknown: true } } }, 'unknown field'],
139
+ [{ name: 'legacy', detects: ['package.json'], sensors: { lint: { defaultCmd: 'eslint\u0000 .' } } }, 'defaultCmd'],
140
+ [{ name: 'legacy', detects: ['package.json'], sensors: { lint: { changedExtensions: ['.ts', 3] } } }, 'changedExtensions[1]'],
141
+ [{ name: 'legacy', detects: ['package.json'], sensors: { 'bad/name': {} } }, 'sensor id'],
142
+ ])('rejects recursively malformed legacy content %j', (input, message) => {
143
+ expect(() => (0, contract_1.parseSensorPack)(input, 'legacy-pack.json')).toThrow(message);
144
+ });
145
+ it('preserves legacy config file fallbacks', () => {
146
+ expect((0, contract_1.parseSensorPack)({ name: 'legacy', sensors: { lint: { configFile: 'eslint.config.js', configFileFallback: '.eslintrc.json' } } }, 'legacy')).toMatchObject({ kind: 'legacy', pack: { sensors: { lint: { configFile: 'eslint.config.js', configFileFallback: '.eslintrc.json' } } } });
147
+ });
148
+ it('rejects malformed applicability and probe extensions', () => {
149
+ const variant = validPack().sensors.lint.variants[0];
150
+ expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { applicability: { allFiles: [3] }, variants: [variant] } } }, 'pack')).toThrow('allFiles[0]');
151
+ expect(() => (0, contract_1.parseSensorPack)({ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...variant, probe: { kind: 'version', extra: true } }] } } }, 'pack')).toThrow('unknown field');
152
+ });
153
+ it('preserves configFiles and rejects incomplete public overlap input', () => {
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'] } }] } } } });
156
+ const complete = validPack().sensors.lint.variants[0];
157
+ expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, id: '../bad' }])).toThrow('stable lowercase id');
158
+ expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([{ ...complete, command: undefined }])).toThrow('command');
159
+ });
160
+ it('rejects equal-priority variants with overlapping operational tool ranges', () => {
161
+ const first = validPack().sensors.lint.variants[0];
162
+ const second = { ...first, id: 'eslint-9-alt', certifiedRange: '>=10 <11', requirements: { ...first.requirements, toolRange: '>=9.5 <10.5' } };
163
+ expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([first, second])).toThrow('overlap');
164
+ });
165
+ it('accepts overlapping tool ranges when runtime ranges are disjoint', () => {
166
+ const first = validPack().sensors.lint.variants[0];
167
+ const second = { ...first, id: 'eslint-9-node-old', requirements: { ...first.requirements, toolRange: '>=9.5 <10.5', runtimeRange: '>=18 <20' } };
168
+ expect(() => (0, contract_1.assertNoEqualPriorityOverlap)([first, second])).not.toThrow();
169
+ });
170
+ test.each([
171
+ [{ ...validPack(), schemaVersion: 3 }, 'supported: legacy, 2'],
172
+ [{ ...validPack(), sensors: { lint: { variants: [] } } }, 'variants'],
173
+ [{ ...validPack(), name: '../escape' }, 'name'],
174
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], assets: ['../secret'] }] } } }, 'asset'],
175
+ [{ ...validPack(), sensors: { lint: { variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'sh', resolution: 'path', args: ['-c', 'echo nope'] } }] } } }, 'executable'],
176
+ [{ ...validPack(), coverage: { ...coverage, schemaVersion: 2 } }, 'coverage.schemaVersion'],
177
+ [{ ...validPack(), sensors: { lint: { variants: [{ ...validPack().sensors.lint.variants[0], certifiedRange: 'not-a-range' }] } } }, 'certifiedRange'],
178
+ [{ ...validPack(), sensors: { lint: validPack().sensors.lint, lint2: validPack().sensors.lint } }, 'variant id'],
179
+ [{ ...validPack(), sensors: { lint: { variants: [validPack().sensors.lint.variants[0], { ...validPack().sensors.lint.variants[0], id: 'eslint-9-next', certifiedRange: '>=9.1.0 <10.0.0' }] } } }, 'overlap'],
180
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], assets: ['C:/secret'] }] } } }, 'asset'],
181
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'cmd.exe', resolution: 'path', args: ['x'] } }] } } }, 'executable'],
182
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'npm', resolution: 'path', args: ['run', 'lint'] } }] } } }, 'packageManager'],
183
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { executable: 'npm', resolution: 'path', args: ['run', 'lint'], packageManager: 'pnpm' } }] } } }, 'match executable'],
184
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { ...validPack().sensors.lint.variants[0].command, environment: { NODE_OPTIONS: '--require unsafe' } } }] } } }, 'environment'],
185
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], command: { ...validPack().sensors.lint.variants[0].command, args: ['prefix{files}'] } }] } } }, 'embed'],
186
+ [{ ...validPack(), sensors: { lint: { ...validPack().sensors.lint, variants: [{ ...validPack().sensors.lint.variants[0], formatter: 'bad\nformat' }] } } }, 'formatter'],
187
+ [{ ...validPack(), hardening: { 'typescript-strict': { assets: ['../tsconfig.awm.json'] } } }, 'asset'],
188
+ [{ ...validPack(), hardening: { 'typescript-strict': { assets: [] } } }, 'hardening'],
189
+ [{ ...validPack(), hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'], command: {} } } }, 'unknown field'],
190
+ ])('rejects malformed pack %j', (input, message) => {
191
+ expect(() => (0, contract_1.parseSensorPack)(input, 'pack.json')).toThrow(message);
192
+ });
193
+ });
@@ -0,0 +1,119 @@
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 os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const discovery_1 = require("../../../../src/commands/sensors/compatibility/discovery");
10
+ describe('discoverProjectEvidence', () => {
11
+ it('returns only local, relative project evidence and detects conflicting lockfiles', () => {
12
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-'));
13
+ try {
14
+ fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '10.0.0' } }));
15
+ fs_1.default.writeFileSync(path_1.default.join(root, 'package-lock.json'), '{}');
16
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pnpm-lock.yaml'), 'lockfileVersion: 9');
17
+ fs_1.default.writeFileSync(path_1.default.join(root, 'eslint.config.js'), 'export default []');
18
+ fs_1.default.mkdirSync(path_1.default.join(root, 'node_modules', 'eslint'), { recursive: true });
19
+ fs_1.default.writeFileSync(path_1.default.join(root, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ name: 'eslint', version: '10.4.1' }));
20
+ const evidence = (0, discovery_1.discoverProjectEvidence)(root, { schemaVersion: 2, name: 'js-ts', detects: ['package.json'], sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'eslint', configFiles: [] } }] } } }, { platform: () => 'darwin' });
21
+ expect(evidence.packageManagerConflict).toBe(true);
22
+ expect(evidence.os).toBe('darwin');
23
+ expect(evidence.declaredToolRanges.eslint).toBe('10.0.0');
24
+ expect(evidence.toolVersions.eslint).toBe('10.4.1');
25
+ expect(evidence.scripts).toContain('lint');
26
+ expect(evidence.configFiles).toContain('eslint.config.js');
27
+ expect(evidence.paths.every((item) => !path_1.default.isAbsolute(item) && !item.includes('..'))).toBe(true);
28
+ }
29
+ finally {
30
+ fs_1.default.rmSync(root, { recursive: true, force: true });
31
+ }
32
+ });
33
+ it('includes pack and applicability markers when deciding whether a sensor applies', () => {
34
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-markers-'));
35
+ try {
36
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pack-marker'), 'present');
37
+ fs_1.default.writeFileSync(path_1.default.join(root, 'sensor-marker'), 'present');
38
+ fs_1.default.writeFileSync(path_1.default.join(root, 'one-of-these'), 'present');
39
+ const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
40
+ schemaVersion: 2,
41
+ name: 'custom',
42
+ detects: ['pack-marker'],
43
+ sensors: {
44
+ lint: {
45
+ applicability: { allFiles: ['sensor-marker'], anyFiles: ['one-of-these', 'missing-marker'] },
46
+ variants: [{ requirements: { tool: 'eslint', configFiles: [] } }],
47
+ },
48
+ },
49
+ });
50
+ expect(evidence.paths).toEqual(expect.arrayContaining(['pack-marker', 'sensor-marker', 'one-of-these']));
51
+ }
52
+ finally {
53
+ fs_1.default.rmSync(root, { recursive: true, force: true });
54
+ }
55
+ });
56
+ it('does not report package.json as evidence when the project has none', () => {
57
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-no-package-'));
58
+ try {
59
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
60
+ const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
61
+ schemaVersion: 2,
62
+ name: 'python',
63
+ detects: ['pyproject.toml'],
64
+ sensors: {
65
+ lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'ruff', configFiles: [] } }] },
66
+ },
67
+ });
68
+ expect(evidence.paths).not.toContain('package.json');
69
+ }
70
+ finally {
71
+ fs_1.default.rmSync(root, { recursive: true, force: true });
72
+ }
73
+ });
74
+ test.each([
75
+ ['linux', ['.venv', 'lib', 'python3.12', 'site-packages']],
76
+ ['win32', ['.venv', 'Lib', 'site-packages']],
77
+ ])('discovers exact local Python runtime and tool metadata on %s', (targetPlatform, sitePackages) => {
78
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-'));
79
+ try {
80
+ fs_1.default.mkdirSync(path_1.default.join(root, ...sitePackages), { recursive: true });
81
+ fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = 3.12.4\n');
82
+ for (const [tool, version] of Object.entries({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' })) {
83
+ const info = path_1.default.join(root, ...sitePackages, `${tool}-${version}.dist-info`);
84
+ fs_1.default.mkdirSync(info);
85
+ fs_1.default.writeFileSync(path_1.default.join(info, 'METADATA'), `Metadata-Version: 2.3\nName: ${tool}\nVersion: ${version}\n`);
86
+ }
87
+ const pack = { schemaVersion: 2, name: 'python', detects: ['pyproject.toml'], sensors: {
88
+ quality: { applicability: { allFiles: ['pyproject.toml'] }, variants: Object.keys({ mypy: 0, ruff: 0, pytest: 0, semgrep: 0 }).map(tool => ({ requirements: { tool, runtime: 'python', configFiles: [] } })) },
89
+ } };
90
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
91
+ const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => targetPlatform });
92
+ expect(evidence.runtimeVersions.python).toBe('3.12.4');
93
+ expect(evidence.toolVersions).toMatchObject({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' });
94
+ }
95
+ finally {
96
+ fs_1.default.rmSync(root, { recursive: true, force: true });
97
+ }
98
+ });
99
+ it('does not trust missing or escaping Python environment metadata', () => {
100
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-untrusted-'));
101
+ const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-outside-'));
102
+ try {
103
+ fs_1.default.mkdirSync(path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages'), { recursive: true });
104
+ fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = not-a-version\n');
105
+ const escaped = path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages', 'ruff-0.6.9.dist-info');
106
+ fs_1.default.mkdirSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'));
107
+ fs_1.default.writeFileSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info', 'METADATA'), 'Name: ruff\nVersion: 0.6.9\n');
108
+ fs_1.default.symlinkSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'), escaped, 'dir');
109
+ const pack = { schemaVersion: 2, name: 'python', detects: [], sensors: { lint: { applicability: {}, variants: [{ requirements: { tool: 'ruff', runtime: 'python', configFiles: [] } }] } } };
110
+ const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => 'linux' });
111
+ expect(evidence.runtimeVersions.python).toBeNull();
112
+ expect(evidence.toolVersions.ruff).toBeNull();
113
+ }
114
+ finally {
115
+ fs_1.default.rmSync(root, { recursive: true, force: true });
116
+ fs_1.default.rmSync(outside, { recursive: true, force: true });
117
+ }
118
+ });
119
+ });
@@ -0,0 +1,124 @@
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 os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const live_1 = require("../../../../src/commands/sensors/compatibility/live");
10
+ const exec_1 = require("../../../../src/commands/sensors/exec");
11
+ const python_venv_fixture_1 = require("./python-venv-fixture");
12
+ const itPosix = process.platform !== 'win32' ? it : it.skip;
13
+ describe('resolveParsedPackCompatibility — contained Python commands', () => {
14
+ test.each([
15
+ ['linux', ['.venv', 'bin', 'semgrep']],
16
+ ['win32', ['.venv', 'Scripts', 'semgrep.exe']],
17
+ ])('creates a local Semgrep executable in the %s virtual-environment layout', (targetPlatform, executableParts) => {
18
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-venv-layout-'));
19
+ try {
20
+ (0, python_venv_fixture_1.createSemgrepVenvFixture)(root, targetPlatform);
21
+ expect(fs_1.default.lstatSync(path_1.default.join(root, ...executableParts)).isFile()).toBe(true);
22
+ }
23
+ finally {
24
+ fs_1.default.rmSync(root, { recursive: true, force: true });
25
+ }
26
+ });
27
+ it('resolves the Windows virtual-environment executable from Scripts instead of PATH', async () => {
28
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-windows-resolution-'));
29
+ const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-windows-global-'));
30
+ const originalPlatform = process.platform;
31
+ const savedPath = process.env.PATH;
32
+ const savedPathExt = process.env.PATHEXT;
33
+ try {
34
+ Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
35
+ process.env.PATH = global;
36
+ process.env.PATHEXT = '.EXE';
37
+ (0, python_venv_fixture_1.createSemgrepVenvFixture)(root, 'win32');
38
+ fs_1.default.writeFileSync(path_1.default.join(global, 'semgrep.exe'), 'must-not-run');
39
+ await expect((0, exec_1.runStructuredCommand)({
40
+ executable: 'semgrep', resolution: 'python-environment', pythonEnvironmentRoot: '.venv', args: ['--version'],
41
+ }, { cwd: root, timeout: 5_000 })).resolves.toMatchObject({
42
+ code: 0,
43
+ stdout: expect.stringContaining(process.version),
44
+ });
45
+ }
46
+ finally {
47
+ Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
48
+ if (savedPath === undefined)
49
+ delete process.env.PATH;
50
+ else
51
+ process.env.PATH = savedPath;
52
+ if (savedPathExt === undefined)
53
+ delete process.env.PATHEXT;
54
+ else
55
+ process.env.PATHEXT = savedPathExt;
56
+ fs_1.default.rmSync(root, { recursive: true, force: true });
57
+ fs_1.default.rmSync(global, { recursive: true, force: true });
58
+ }
59
+ });
60
+ itPosix('binds Semgrep discovery, validation probe, and execution to the same local virtual-environment executable', async () => {
61
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-contained-'));
62
+ const global = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-global-'));
63
+ const savedPath = process.env.PATH;
64
+ try {
65
+ (0, python_venv_fixture_1.createSemgrepVenvFixture)(root);
66
+ fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'bin', 'semgrep'), '#!/bin/sh\nif [ "$1" = "--validate" ]; then echo local-semgrep; exit 0; fi\nexit 1\n', { mode: 0o755 });
67
+ fs_1.default.writeFileSync(path_1.default.join(global, 'semgrep'), '#!/bin/sh\necho global-semgrep\n', { mode: 0o755 });
68
+ process.env.PATH = global;
69
+ const pack = {
70
+ schemaVersion: 2,
71
+ name: 'python',
72
+ description: 'test',
73
+ detects: ['pyproject.toml'],
74
+ coverage: {},
75
+ sensors: {
76
+ security: {
77
+ applicability: { allFiles: ['pyproject.toml'] },
78
+ variants: [{
79
+ id: 'semgrep-1', priority: 1, certifiedRange: '>=1 <2',
80
+ requirements: { tool: 'semgrep', toolRange: '>=1 <2', runtime: 'python', runtimeRange: '>=3.12 <4' },
81
+ assets: [], formatter: 'semgrep', probe: { kind: 'semgrep-validate' },
82
+ command: { executable: 'semgrep', resolution: 'path', args: ['--validate'] },
83
+ }],
84
+ },
85
+ },
86
+ };
87
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"\n');
88
+ const live = await (0, live_1.resolveParsedPackCompatibility)(root, pack);
89
+ const command = live.pack.sensors.security.variants[0].command;
90
+ expect(live.sensors.security).toMatchObject({ state: 'certified', toolVersion: '1.91.0' });
91
+ expect(command).toMatchObject({ executable: 'semgrep', resolution: 'python-environment' });
92
+ await expect((0, exec_1.runStructuredCommand)(command, { cwd: root, timeout: 5_000 })).resolves.toMatchObject({ code: 0, stdout: 'local-semgrep\n' });
93
+ }
94
+ finally {
95
+ process.env.PATH = savedPath;
96
+ fs_1.default.rmSync(root, { recursive: true, force: true });
97
+ fs_1.default.rmSync(global, { recursive: true, force: true });
98
+ }
99
+ });
100
+ it('never falls back from the discovered .venv to a sibling venv executable', async () => {
101
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-semgrep-environment-identity-'));
102
+ try {
103
+ (0, python_venv_fixture_1.createSemgrepVenvFixture)(root, process.platform, '.venv', { executable: false });
104
+ (0, python_venv_fixture_1.createSemgrepVenvFixture)(root, process.platform, 'venv');
105
+ fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"\n');
106
+ const pack = {
107
+ schemaVersion: 2, name: 'python', description: 'test', detects: ['pyproject.toml'], coverage: {},
108
+ sensors: { security: { applicability: { allFiles: ['pyproject.toml'] }, variants: [{
109
+ id: 'semgrep-1', priority: 1, certifiedRange: '>=1 <2',
110
+ requirements: { tool: 'semgrep', toolRange: '>=1 <2', runtime: 'python', runtimeRange: '>=3.12 <4' },
111
+ assets: [], formatter: 'semgrep', probe: { kind: 'semgrep-validate' },
112
+ command: { executable: 'semgrep', resolution: 'path', args: ['--validate'] },
113
+ }] } },
114
+ };
115
+ const live = await (0, live_1.resolveParsedPackCompatibility)(root, pack);
116
+ expect(live.sensors.security).toMatchObject({ state: 'unverifiable', reason: 'probe-inconclusive', toolVersion: '1.91.0' });
117
+ expect(() => (0, exec_1.runStructuredCommand)(live.pack.sensors.security.variants[0].command, { cwd: root, timeout: 5_000 }))
118
+ .toThrow('python environment executable is not a contained local regular file');
119
+ }
120
+ finally {
121
+ fs_1.default.rmSync(root, { recursive: true, force: true });
122
+ }
123
+ });
124
+ });
@@ -0,0 +1,77 @@
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 manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ describe('sensor manifest contract', () => {
10
+ it('keeps compatibility contracts on an acyclic import boundary', () => {
11
+ const source = (relative) => fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../src/commands/sensors', relative), 'utf8');
12
+ expect(source('types.ts')).not.toContain('./compatibility/');
13
+ expect(source('compatibility/types.ts')).not.toContain('../coverage/');
14
+ expect(source('coverage/contract.ts')).not.toContain('../compatibility/manifest');
15
+ });
16
+ it('normalizes a legacy string command with compatible-unverified evidence', () => {
17
+ expect((0, manifest_1.parseSensorManifest)({ pack: 'js-ts', sensors: { lint: 'npm run lint' } }, 'sensors.json')).toMatchObject({ kind: 'legacy', pack: {
18
+ pack: 'js-ts',
19
+ sensors: { lint: { cmd: 'npm run lint' } },
20
+ compatibility: (0, manifest_1.legacyCompatibility)(),
21
+ } });
22
+ });
23
+ it('accepts a v2 selected variant and structured command', () => {
24
+ const manifest = {
25
+ schemaVersion: 2,
26
+ pack: 'js-ts',
27
+ sensors: {
28
+ lint: {
29
+ enabled: true, variantId: 'eslint-9',
30
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--format', 'json'] },
31
+ initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
32
+ },
33
+ },
34
+ };
35
+ expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: manifest });
36
+ expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
37
+ });
38
+ it('persists only an explicit v2 pack selection as applicability provenance', () => {
39
+ const manifest = {
40
+ schemaVersion: 2, pack: 'generic', packSelection: 'explicit',
41
+ sensors: {},
42
+ };
43
+ expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: { packSelection: 'explicit' } });
44
+ expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
45
+ expect(() => (0, manifest_1.parseSensorManifest)({ ...manifest, packSelection: 'detected' }, 'sensors.json')).toThrow('packSelection');
46
+ });
47
+ it('accepts optional contained assets and rejects traversal', () => {
48
+ const sensor = { enabled: true, variantId: 'eslint-9', assets: ['eslint.config.awm.mjs'], command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
49
+ expect((0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'sensors.json')).toMatchObject({ kind: 'v2' });
50
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { ...sensor, assets: ['C:/secret'] } } }, 'sensors.json')).toThrow('asset');
51
+ });
52
+ it('rejects mismatched compatibility variant and hostile source labels', () => {
53
+ const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'other', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
54
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, { path: 'bad' })).toThrow('<unknown source>');
55
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source\nleak')).toThrow('<unknown source>');
56
+ });
57
+ it('rejects invalid semver evidence independently', () => {
58
+ const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: 'bad', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
59
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('toolVersion');
60
+ });
61
+ it('rejects certified evidence without a selected variant', () => {
62
+ const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: null, toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
63
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('variantId');
64
+ });
65
+ it('rejects a certified tool version outside its certified range', () => {
66
+ const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '10.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
67
+ expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('certifiedRange');
68
+ });
69
+ test.each([
70
+ [null, 'object'],
71
+ [{}, 'pack'],
72
+ [{ schemaVersion: 3, pack: 'js-ts', sensors: {} }, 'schemaVersion'],
73
+ [{ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'path', args: [] } } } }, 'enabled'],
74
+ ])('rejects malformed manifest %j', (input, message) => {
75
+ expect(() => (0, manifest_1.parseSensorManifest)(input, 'sensors.json')).toThrow(message);
76
+ });
77
+ });
@@ -0,0 +1,72 @@
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 os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const materialize_1 = require("../../../../src/commands/sensors/compatibility/materialize");
10
+ const evidence = { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.0.0', runtimeVersion: '22.0.0', certifiedRange: '>=10 <11', evidence: [] };
11
+ describe('materializeResolvedSensors', () => {
12
+ let projectRoot;
13
+ let packRoot;
14
+ beforeEach(() => {
15
+ projectRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-project-'));
16
+ packRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-pack-'));
17
+ fs_1.default.writeFileSync(path_1.default.join(packRoot, 'eslint.config.awm.mjs'), 'export default [];');
18
+ fs_1.default.writeFileSync(path_1.default.join(packRoot, 'tsconfig.awm.json'), '{}');
19
+ });
20
+ afterEach(() => {
21
+ fs_1.default.rmSync(projectRoot, { recursive: true, force: true });
22
+ fs_1.default.rmSync(packRoot, { recursive: true, force: true });
23
+ });
24
+ it('copies only selected assets and writes a v2 manifest atomically', () => {
25
+ const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
26
+ lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
27
+ } });
28
+ expect(result.configured).toEqual(['eslint.config.awm.mjs']);
29
+ expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'tsconfig.awm.json'))).toBe(false);
30
+ expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), 'utf8'))).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
31
+ expect(fs_1.default.readdirSync(path_1.default.join(projectRoot, '.awm'))).not.toContain('sensors.json.tmp');
32
+ });
33
+ it('preserves a destination that already exists', () => {
34
+ fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'owner content');
35
+ const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
36
+ lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
37
+ } });
38
+ expect(result.preserved).toEqual(['eslint.config.awm.mjs']);
39
+ expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'utf8')).toBe('owner content');
40
+ });
41
+ it('revalidates a shared Semgrep policy reference without materializing the policy itself', () => {
42
+ const sensorPacks = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-policy-'));
43
+ const semgrepPack = path_1.default.join(sensorPacks, 'python');
44
+ try {
45
+ fs_1.default.mkdirSync(path_1.default.join(sensorPacks, 'shared'), { recursive: true });
46
+ fs_1.default.mkdirSync(semgrepPack);
47
+ fs_1.default.writeFileSync(path_1.default.join(semgrepPack, 'pack.json'), '{}');
48
+ fs_1.default.writeFileSync(path_1.default.join(semgrepPack, '.semgrep.awm.yml'), 'rules: []\n');
49
+ fs_1.default.writeFileSync(path_1.default.join(sensorPacks, 'shared', 'semgrep-policy.json'), JSON.stringify({ tool: 'semgrep', toolRange: '>=1.0.0', runtime: 'python', runtimeRange: '>=3.9.0', probe: 'semgrep-validate' }));
50
+ const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot: semgrepPack, pack: 'python', sensors: {
51
+ security: { enabled: true, variantId: 'semgrep-python', command: { executable: 'semgrep', resolution: 'path', args: ['--config', '.semgrep.awm.yml', '--json', '.'] }, assets: ['.semgrep.awm.yml'], policyRef: 'shared/semgrep-policy.json', initializedCompatibility: { ...evidence, variantId: 'semgrep-python' } },
52
+ } });
53
+ expect(result.configured).toEqual(['.semgrep.awm.yml']);
54
+ expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'shared', 'semgrep-policy.json'))).toBe(false);
55
+ }
56
+ finally {
57
+ fs_1.default.rmSync(sensorPacks, { recursive: true, force: true });
58
+ }
59
+ });
60
+ it('reports previous AWM assets as orphaned and never deletes them', () => {
61
+ fs_1.default.mkdirSync(path_1.default.join(projectRoot, '.awm'));
62
+ fs_1.default.writeFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {
63
+ lint: { enabled: true, variantId: 'old', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.cjs'], initializedCompatibility: { ...evidence, variantId: 'old' } },
64
+ } }));
65
+ fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'), 'old');
66
+ const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
67
+ lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
68
+ } });
69
+ expect(result.orphaned).toEqual(['eslint.config.awm.cjs']);
70
+ expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'))).toBe(true);
71
+ });
72
+ });