agentic-workflow-manager 6.5.2 → 8.0.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 (93) 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 +261 -0
  11. package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
  12. package/dist/src/commands/sensors/compatibility/live.js +59 -0
  13. package/dist/src/commands/sensors/compatibility/manifest.js +197 -0
  14. package/dist/src/commands/sensors/compatibility/materialize.js +126 -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 +96 -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 +150 -9
  27. package/dist/src/commands/sensors/index.js +18 -6
  28. package/dist/src/commands/sensors/init.js +92 -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 +98 -0
  45. package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
  46. package/dist/tests/commands/sensors/compatibility/manifest.test.js +68 -0
  47. package/dist/tests/commands/sensors/compatibility/materialize.test.js +53 -0
  48. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
  49. package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
  50. package/dist/tests/commands/sensors/compatibility/resolve.test.js +75 -0
  51. package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
  52. package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
  53. package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
  54. package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
  55. package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
  56. package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
  57. package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
  58. package/dist/tests/commands/sensors/exec-windows.test.js +92 -2
  59. package/dist/tests/commands/sensors/exec.test.js +67 -5
  60. package/dist/tests/commands/sensors/index.test.js +8 -0
  61. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
  62. package/dist/tests/commands/sensors/init.test.js +132 -48
  63. package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
  64. package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
  65. package/dist/tests/commands/sensors/run.test.js +66 -3
  66. package/dist/tests/commands/sensors/status-windows.test.js +7 -7
  67. package/dist/tests/commands/sensors/status.test.js +74 -23
  68. package/dist/tests/commands/update.test.js +1 -1
  69. package/dist/tests/core/bundles.test.js +37 -0
  70. package/dist/tests/core/discovery.test.js +4 -0
  71. package/dist/tests/core/init/orchestrator.test.js +5 -1
  72. package/dist/tests/core/init/steps.test.js +20 -4
  73. package/dist/tests/core/ledger/scan.test.js +217 -0
  74. package/dist/tests/core/ledger/store.test.js +108 -0
  75. package/dist/tests/core/profile-pins.test.js +2 -1
  76. package/dist/tests/core/project-skill-links.test.js +22 -0
  77. package/dist/tests/core/registries-capability.test.js +10 -0
  78. package/dist/tests/core/registries-sync.test.js +16 -0
  79. package/dist/tests/core/registries.test.js +129 -1
  80. package/dist/tests/core/registry-manifest.test.js +7 -0
  81. package/dist/tests/core/same-existing-path.test.js +35 -0
  82. package/dist/tests/core/sync-gates.test.js +2 -1
  83. package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
  84. package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
  85. package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
  86. package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
  87. package/dist/tests/structural/active-documentation.test.js +128 -0
  88. package/dist/tests/structural/async-entrypoint.test.js +15 -0
  89. package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
  90. package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
  91. package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
  92. package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
  93. package/package.json +8 -2
@@ -47,49 +47,49 @@ describe('awm sensors init — detected pack missing from the registry', () => {
47
47
  const manifestOf = (dir) => JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
48
48
  afterAll(() => { for (const d of made)
49
49
  fs_1.default.rmSync(d, { recursive: true, force: true }); });
50
- it('names the missing pack instead of writing an empty manifest silently', () => {
50
+ it('names the missing pack instead of writing an empty manifest silently', async () => {
51
51
  const cwd = project();
52
- const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
52
+ const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
53
53
  expect(result.unavailablePack).toBe('python');
54
54
  expect(result.detection.pack).toBe('python'); // what the tree actually is — unchanged
55
55
  });
56
- it('falls back to a pack the registry does have, so the gate still measures something', () => {
56
+ it('falls back to a pack the registry does have, so the gate still measures something', async () => {
57
57
  const cwd = project();
58
- const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
58
+ const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
59
59
  expect(result.manifest.pack).toBe('generic');
60
60
  expect(Object.keys(result.manifest.sensors)).toEqual(['security']);
61
61
  expect(manifestOf(cwd).pack).toBe('generic');
62
62
  });
63
- it('stays on the detected pack when the registry has no fallback either', () => {
63
+ it('stays on the detected pack when the registry has no fallback either', async () => {
64
64
  // Nothing better exists. The manifest is honestly empty and still says so —
65
65
  // preflight's `manifest` check fails on `total === 0` with a registry remedy.
66
66
  const cwd = project();
67
- const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {} }) });
67
+ const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {} }) });
68
68
  expect(result.unavailablePack).toBe('python');
69
69
  expect(result.manifest.pack).toBe('python');
70
70
  expect(result.manifest.sensors).toEqual({});
71
71
  });
72
- it('is quiet when the registry does have the detected pack', () => {
72
+ it('is quiet when the registry does have the detected pack', async () => {
73
73
  const cwd = project();
74
- const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ python: PYTHON, generic: GENERIC }) });
74
+ const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ python: PYTHON, generic: GENERIC }) });
75
75
  expect(result.unavailablePack).toBeUndefined();
76
76
  expect(result.manifest.pack).toBe('python');
77
77
  expect(Object.keys(result.manifest.sensors)).toEqual(['typecheck']);
78
78
  });
79
- it('does not fall back when there is no registry to check against', () => {
79
+ it('does not fall back when there is no registry to check against', async () => {
80
80
  // No registryRoot means nothing to validate against, the same tolerance
81
81
  // `--pack` already has — not evidence that the pack is missing.
82
82
  const cwd = project();
83
- const result = (0, init_1.initSensors)({ cwd });
83
+ const result = await (0, init_1.initSensors)({ cwd });
84
84
  expect(result.unavailablePack).toBeUndefined();
85
85
  expect(result.manifest.pack).toBe('python');
86
86
  });
87
- it('keeps sensors the user already had when falling back', () => {
87
+ it('keeps sensors the user already had when falling back', async () => {
88
88
  // The fallback must not be a way to lose hand-written configuration.
89
89
  const cwd = project();
90
90
  fs_1.default.mkdirSync(path_1.default.join(cwd, '.awm'), { recursive: true });
91
91
  fs_1.default.writeFileSync(path_1.default.join(cwd, '.awm', 'sensors.json'), JSON.stringify({ pack: 'python', sensors: { test: { cmd: 'pytest -q', fast: false } } }));
92
- const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ generic: GENERIC }) });
92
+ const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ generic: GENERIC }) });
93
93
  expect(result.manifest.sensors.test?.cmd).toBe('pytest -q');
94
94
  expect(Object.keys(result.manifest.sensors).sort()).toEqual(['security', 'test']);
95
95
  });
@@ -40,69 +40,86 @@ function makePythonRegistry() {
40
40
  }));
41
41
  return registryRoot;
42
42
  }
43
+ function makeV2Registry() {
44
+ const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-v2-'));
45
+ const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
46
+ fs_1.default.mkdirSync(packDir, { recursive: true });
47
+ fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
48
+ fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
49
+ schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
50
+ coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
51
+ sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
52
+ id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
53
+ requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
54
+ assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
55
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
56
+ }] } },
57
+ }));
58
+ return registryRoot;
59
+ }
43
60
  describe('detectStack', () => {
44
61
  let tmpDir;
45
62
  beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
46
63
  afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
47
- it('detects js-ts when package.json exists', () => {
64
+ it('detects js-ts when package.json exists', async () => {
48
65
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
49
66
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
50
67
  });
51
- it('detects python when pyproject.toml exists', () => {
68
+ it('detects python when pyproject.toml exists', async () => {
52
69
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
53
70
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
54
71
  });
55
- it('falls back to generic when no indicators found', () => {
72
+ it('falls back to generic when no indicators found', async () => {
56
73
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
57
74
  });
58
- it('detects shell from a root-level *.sh file when no js-ts/python marker exists', () => {
75
+ it('detects shell from a root-level *.sh file when no js-ts/python marker exists', async () => {
59
76
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
60
77
  const result = (0, init_1.detectStack)(tmpDir);
61
78
  expect(result.pack).toBe('shell');
62
79
  expect(result.indicators).toEqual(['deploy.sh']);
63
80
  });
64
- it('detects shell from a scripts/*.sh file when root has nothing', () => {
81
+ it('detects shell from a scripts/*.sh file when root has nothing', async () => {
65
82
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
66
83
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'build.sh'), '#!/bin/sh\n');
67
84
  const result = (0, init_1.detectStack)(tmpDir);
68
85
  expect(result.pack).toBe('shell');
69
86
  expect(result.indicators).toEqual([path_1.default.join('scripts', 'build.sh')]);
70
87
  });
71
- it('js-ts wins over shell when both package.json and a root .sh file exist', () => {
88
+ it('js-ts wins over shell when both package.json and a root .sh file exist', async () => {
72
89
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
73
90
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
74
91
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
75
92
  });
76
- it('python wins over shell when both a python marker and a root .sh file exist', () => {
93
+ it('python wins over shell when both a python marker and a root .sh file exist', async () => {
77
94
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
78
95
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
79
96
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
80
97
  });
81
- it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', () => {
98
+ it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', async () => {
82
99
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
83
100
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'notes.txt'), 'not shell');
84
101
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
85
102
  });
86
- it('detects python from requirements.txt alone', () => {
103
+ it('detects python from requirements.txt alone', async () => {
87
104
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'requirements.txt'), '');
88
105
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
89
106
  });
90
- it('detects python from Pipfile alone', () => {
107
+ it('detects python from Pipfile alone', async () => {
91
108
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
92
109
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
93
110
  });
94
- it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', () => {
111
+ it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', async () => {
95
112
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
96
113
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
97
114
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
98
115
  });
99
- it('does not report a directory named "*.sh" as a shell indicator', () => {
116
+ it('does not report a directory named "*.sh" as a shell indicator', async () => {
100
117
  // Directory literally named `something.sh` (not a file) — findShellIndicators'
101
118
  // `entry.isFile()` guard must exclude it. Nothing else present → generic.
102
119
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'something.sh'));
103
120
  expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
104
121
  });
105
- it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', () => {
122
+ it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', async () => {
106
123
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'notreal.sh'));
107
124
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
108
125
  const result = (0, init_1.detectStack)(tmpDir);
@@ -114,16 +131,16 @@ describe('detectSourceDirs', () => {
114
131
  let tmpDir;
115
132
  beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-')); });
116
133
  afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
117
- it('returns the App-Router-style dirs that exist', () => {
134
+ it('returns the App-Router-style dirs that exist', async () => {
118
135
  for (const d of ['app', 'lib', 'components'])
119
136
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, d));
120
137
  expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app', 'lib', 'components']);
121
138
  });
122
- it('returns src when it exists', () => {
139
+ it('returns src when it exists', async () => {
123
140
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'src'));
124
141
  expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
125
142
  });
126
- it('falls back to ["src"] when no known source dir exists', () => {
143
+ it('falls back to ["src"] when no known source dir exists', async () => {
127
144
  expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
128
145
  });
129
146
  });
@@ -138,26 +155,26 @@ describe('buildManifest', () => {
138
155
  fs_1.default.rmSync(registryRoot, { recursive: true });
139
156
  fs_1.default.rmSync(cwd, { recursive: true });
140
157
  });
141
- it('builds manifest from the pack.json single source', () => {
158
+ it('builds manifest from the pack.json single source', async () => {
142
159
  const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
143
160
  expect(m.pack).toBe('js-ts');
144
161
  expect(m.sensors.typecheck.cmd).toBe('npx tsc --noEmit');
145
162
  expect(m.sensors.lint.cmd).toContain('--config eslint.config.awm.mjs');
146
163
  });
147
- it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', () => {
164
+ it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', async () => {
148
165
  for (const d of ['app', 'lib'])
149
166
  fs_1.default.mkdirSync(path_1.default.join(cwd, d));
150
167
  const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
151
168
  expect(m.sensors.depcheck.cmd).toBe('npx depcruise --config .dep-cruiser.awm.js app lib');
152
169
  expect(m.sensors.depcheck.cmd).not.toContain('{{SOURCE_DIRS}}');
153
170
  });
154
- it('merges conservatively — existing sensor commands are preserved', () => {
171
+ it('merges conservatively — existing sensor commands are preserved', async () => {
155
172
  const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'custom-tsc', fast: true } } };
156
173
  const m = (0, init_1.buildManifest)('js-ts', existing, registryRoot, cwd);
157
174
  expect(m.sensors.typecheck.cmd).toBe('custom-tsc');
158
175
  expect(m.sensors.lint).toBeDefined();
159
176
  });
160
- it('carries the formatter field through from pack.json into the built manifest', () => {
177
+ it('carries the formatter field through from pack.json into the built manifest', async () => {
161
178
  // readPackDefaults must copy `formatter` the same way it already copies
162
179
  // `changedCmd`/`changedExtensions` — this is what lets run.ts's getFormatter
163
180
  // dispatch by real tool (ruff/mypy/shellcheck) instead of guessing from the
@@ -167,14 +184,14 @@ describe('buildManifest', () => {
167
184
  expect(m.sensors.typecheck.formatter).toBe('tsc');
168
185
  expect(m.sensors.lint.formatter).toBe('eslint-llm');
169
186
  });
170
- it('returns an empty sensors object when the pack has no pack.json in the registry', () => {
187
+ it('returns an empty sensors object when the pack has no pack.json in the registry', async () => {
171
188
  // No FALLBACK_DEFAULTS anymore: `python` has no pack dir in this fixture
172
189
  // registry (only js-ts does — see makeRegistry) → the honest floor is `{}`,
173
190
  // never CLI-hardcoded commands that can drift from what the registry ships.
174
191
  const m = (0, init_1.buildManifest)('python', undefined, registryRoot, cwd);
175
192
  expect(m.sensors).toEqual({});
176
193
  });
177
- it('per-field merge: an existing sensor missing a newer pack field still inherits it', () => {
194
+ it('per-field merge: an existing sensor missing a newer pack field still inherits it', async () => {
178
195
  // Regression for Finding 1: a manifest written by the old FALLBACK_DEFAULTS-era
179
196
  // CLI has `typecheck: { cmd: 'mypy .', fast: true }` — no `formatter`, because
180
197
  // that field didn't exist yet. A naive `{ ...defaults, ...existingSensors }`
@@ -209,37 +226,111 @@ describe('initSensors', () => {
209
226
  fs_1.default.rmSync(tmpDir, { recursive: true });
210
227
  fs_1.default.rmSync(registryRoot, { recursive: true });
211
228
  });
212
- it('creates .awm/sensors.json for js-ts project', () => {
229
+ it('creates .awm/sensors.json for js-ts project', async () => {
213
230
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
214
- const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
231
+ const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
215
232
  expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(true);
216
233
  expect(result.detection.pack).toBe('js-ts');
217
234
  expect(result.manifest.sensors.typecheck).toBeDefined();
218
235
  });
219
- it('is idempotent — existing sensor commands survive re-init', () => {
236
+ it('is idempotent — existing sensor commands survive re-init', async () => {
220
237
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
221
238
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
222
239
  const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'my-tsc', fast: true } } };
223
240
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(existing));
224
- (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
241
+ await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
225
242
  const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf-8'));
226
243
  expect(written.sensors.typecheck.cmd).toBe('my-tsc');
227
244
  expect(written.sensors.lint).toBeDefined(); // new sensor added
228
245
  });
229
- it('copies pack config files into the repo by default (configure on)', () => {
246
+ it('copies pack config files into the repo by default (configure on)', async () => {
230
247
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
231
248
  fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
232
- const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
249
+ const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
233
250
  expect(result.configured).toContain('tsconfig.awm.json');
234
251
  expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(true);
235
252
  });
236
- it('does NOT copy config files when configure is false', () => {
253
+ it('does NOT copy config files when configure is false', async () => {
237
254
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
238
255
  fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
239
- const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
256
+ const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
240
257
  expect(result.configured).toEqual([]);
241
258
  expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
242
259
  });
260
+ it('awaits v2 compatibility probes and persists their materialized evidence', async () => {
261
+ const v2Registry = makeV2Registry();
262
+ try {
263
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
264
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
265
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
266
+ const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
267
+ const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
268
+ expect(result.manifest).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
269
+ // `package.json` is a declared detection file, so config-present matches.
270
+ // Without init awaiting the probe the resolver stays `unverifiable`; this
271
+ // exact assertion therefore distinguishes the async probe integration.
272
+ expect(written.sensors.lint.initializedCompatibility).toMatchObject({ variantId: 'eslint-10', state: 'certified', reason: 'range-and-probe' });
273
+ expect(written.registryRoot).toBe(v2Registry);
274
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
275
+ }
276
+ finally {
277
+ fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
278
+ }
279
+ });
280
+ it('preserves enabled and fast choices when a valid v2 manifest is re-initialized', async () => {
281
+ const v2Registry = makeV2Registry();
282
+ try {
283
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
284
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
285
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
286
+ await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
287
+ const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
288
+ const selected = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
289
+ selected.sensors.lint.enabled = false;
290
+ selected.sensors.lint.fast = true;
291
+ fs_1.default.writeFileSync(manifestPath, JSON.stringify(selected));
292
+ await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
293
+ const written = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
294
+ expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true, variantId: 'eslint-10' });
295
+ }
296
+ finally {
297
+ fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
298
+ }
299
+ });
300
+ it('rejects a symlinked v2 pack source instead of reading it through init', async () => {
301
+ const v2Registry = makeV2Registry();
302
+ try {
303
+ const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
304
+ const outside = path_1.default.join(v2Registry, 'outside-pack.json');
305
+ fs_1.default.renameSync(packPath, outside);
306
+ fs_1.default.symlinkSync(outside, packPath);
307
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
308
+ await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).rejects.toThrow(/symbolic|regular|contain/i);
309
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(false);
310
+ }
311
+ finally {
312
+ fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
313
+ }
314
+ });
315
+ it('migrates legacy overrides explicitly without silently certifying or re-enabling them', async () => {
316
+ const v2Registry = makeV2Registry();
317
+ try {
318
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
319
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
320
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
321
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
322
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
323
+ pack: 'js-ts', sensors: { lint: { cmd: 'my-company-eslint .', enabled: false, fast: true } },
324
+ }));
325
+ await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
326
+ const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
327
+ expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true });
328
+ expect(written.sensors.lint.initializedCompatibility).toMatchObject({ state: 'compatible-unverified', reason: expect.stringContaining('legacy custom command') });
329
+ }
330
+ finally {
331
+ fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
332
+ }
333
+ });
243
334
  });
244
335
  describe('initSensors — --pack override', () => {
245
336
  let tmpDir;
@@ -252,38 +343,31 @@ describe('initSensors — --pack override', () => {
252
343
  fs_1.default.rmSync(tmpDir, { recursive: true });
253
344
  fs_1.default.rmSync(registryRoot, { recursive: true });
254
345
  });
255
- it('skips detection and uses the override pack when it exists in the registry', () => {
346
+ it('skips detection and uses the override pack when it exists in the registry', async () => {
256
347
  // No package.json/pyproject.toml here — if detection ran, this would be 'generic'.
257
- const result = (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
348
+ const result = await (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
258
349
  expect(result.detection.pack).toBe('js-ts');
259
350
  // Indicators must reflect an override, not file-based detection.
260
351
  expect(result.detection.indicators).not.toEqual(['package.json']);
261
352
  expect(result.detection.indicators.join(' ')).toMatch(/pack override/i);
262
353
  });
263
- it('throws listing available packs when the override pack is not in the registry', () => {
264
- expect(() => (0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).toThrow(/js-ts/);
265
- try {
266
- (0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir });
267
- throw new Error('expected initSensors to throw');
268
- }
269
- catch (e) {
270
- expect(e.message).toContain('bogus');
271
- expect(e.message).toContain('js-ts');
272
- }
354
+ it('throws listing available packs when the override pack is not in the registry', async () => {
355
+ await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/js-ts/);
356
+ await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/bogus/);
273
357
  });
274
- it('does not throw when no registryRoot is given — nothing to validate against', () => {
275
- expect(() => (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir })).not.toThrow();
276
- const result = (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
358
+ it('does not throw when no registryRoot is given — nothing to validate against', async () => {
359
+ await expect((0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir })).resolves.toBeDefined();
360
+ const result = await (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
277
361
  expect(result.detection.pack).toBe('anything');
278
362
  });
279
- it('throws a distinct message when the registry root has no sensor-packs directory at all', () => {
363
+ it('throws a distinct message when the registry root has no sensor-packs directory at all', async () => {
280
364
  // Different failure shape from "pack not in the list": the registry root
281
365
  // itself is missing sensor-packs/, so there's no list to show — must say
282
366
  // so plainly instead of reporting an empty `available: `.
283
367
  const emptyRegistryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-reg-'));
284
368
  try {
285
- expect(() => (0, init_1.initSensors)({ pack: 'js-ts', registryRoot: emptyRegistryRoot, cwd: tmpDir }))
286
- .toThrow(/no sensor-packs directory/);
369
+ await expect((0, init_1.initSensors)({ pack: 'js-ts', registryRoot: emptyRegistryRoot, cwd: tmpDir }))
370
+ .rejects.toThrow(/no sensor-packs directory/);
287
371
  }
288
372
  finally {
289
373
  fs_1.default.rmSync(emptyRegistryRoot, { recursive: true });
@@ -157,7 +157,7 @@ describe('runSensors — inconclusive: a sensor that could not certify is never
157
157
  expect(out.sensors.find((s) => s.name === 'security').status).toBe('inconclusive');
158
158
  expect(out.overall).toBe('not_certified');
159
159
  });
160
- it('does not degrade the verdict for a disabled sensor alongside healthy ones', async () => {
160
+ it('does not let healthy legacy commands certify a run when another sensor is disabled', async () => {
161
161
  fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'sensors.json'), JSON.stringify({
162
162
  pack: 'js-ts',
163
163
  sensors: {
@@ -168,7 +168,14 @@ describe('runSensors — inconclusive: a sensor that could not certify is never
168
168
  mockRunCommand.mockResolvedValueOnce(ok());
169
169
  const { runSensors } = load();
170
170
  const out = await runSensors({ cwd: root });
171
- expect(out.overall).toBe('pass');
171
+ // `enabled: false` is informational; the non-certification comes solely
172
+ // from this pre-R3 (schema-less) manifest. Legacy commands remain
173
+ // operational, but their shell-backed, unversioned contract cannot issue
174
+ // a certified `pass` verdict (R3 design R1.4 / R7.2).
175
+ expect(out.sensors.find((s) => s.name === 'typecheck').status).toBe('pass');
176
+ expect(out.sensors.find((s) => s.name === 'mutation').status).toBe('skipped');
177
+ expect(out.sensors.find((s) => s.name === 'mutation').skipReason).toBe('disabled');
178
+ expect(out.overall).toBe('not_certified');
172
179
  });
173
180
  it('still refuses to certify a tree whose sensors are all disabled', async () => {
174
181
  fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'sensors.json'), JSON.stringify({
@@ -12,8 +12,10 @@ function mkTmp() {
12
12
  }
13
13
  // Define stable mock before jest.mock hoisting
14
14
  const mockRunCommand = jest.fn();
15
+ const mockRunStructuredCommand = jest.fn();
15
16
  jest.mock('../../../src/commands/sensors/exec', () => ({
16
17
  runCommand: (...args) => mockRunCommand(...args),
18
+ runStructuredCommand: (...args) => mockRunStructuredCommand(...args),
17
19
  }));
18
20
  const { ok, exited, timedOut } = require('./exec-fixtures');
19
21
  const MANIFEST = {
@@ -35,6 +37,7 @@ describe('runSensors', () => {
35
37
  fs_1.default.mkdirSync(path.join(tmpDir, '.awm'), { recursive: true });
36
38
  fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(MANIFEST));
37
39
  mockRunCommand.mockReset();
40
+ mockRunStructuredCommand.mockReset();
38
41
  });
39
42
  afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
40
43
  const load = () => require('../../../src/commands/sensors/run');
@@ -56,7 +59,7 @@ describe('runSensors', () => {
56
59
  const result = await runSensors({ fast: true, cwd: tmpDir });
57
60
  expect(mockRunCommand).toHaveBeenCalledTimes(2); // typecheck + lint (security disabled, mutation disabled)
58
61
  expect(result.sensors.some((s) => s.name === 'security')).toBe(false);
59
- expect(result.overall).toBe('pass');
62
+ expect(result.overall).toBe('not_certified');
60
63
  });
61
64
  it('returns fail when a fast sensor has errors', async () => {
62
65
  mockRunCommand
@@ -175,7 +178,7 @@ describe('runSensors', () => {
175
178
  const tc = second.sensors.find((s) => s.name === 'typecheck');
176
179
  expect(tc.status).toBe('pass');
177
180
  expect(tc.baselineCount).toBe(1);
178
- expect(second.overall).toBe('pass');
181
+ expect(second.overall).toBe('not_certified');
179
182
  });
180
183
  it('baseline lets NEW findings through (still fails)', async () => {
181
184
  const { runSensors } = load();
@@ -200,6 +203,66 @@ describe('runSensors', () => {
200
203
  expect(result.overall).toBe('fail');
201
204
  });
202
205
  });
206
+ describe('runSensors v2 lifecycle contract', () => {
207
+ let project;
208
+ let home;
209
+ const version = '10.0.0';
210
+ beforeEach(() => {
211
+ jest.resetModules();
212
+ project = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-v2-run-project-'));
213
+ home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-v2-run-home-'));
214
+ process.env.AWM_HOME = home;
215
+ const registry = path_1.default.join(home, 'registries', 'baseline', 'sensor-packs', 'js-ts');
216
+ fs_1.default.mkdirSync(registry, { recursive: true });
217
+ fs_1.default.writeFileSync(path_1.default.join(registry, 'eslint.config.awm.mjs'), 'export default []');
218
+ fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'https://example.test/baseline.git' }]));
219
+ fs_1.default.writeFileSync(path_1.default.join(project, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '^10.0.0' } }));
220
+ fs_1.default.mkdirSync(path_1.default.join(project, 'node_modules', 'eslint'), { recursive: true });
221
+ fs_1.default.writeFileSync(path_1.default.join(project, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version }));
222
+ fs_1.default.writeFileSync(path_1.default.join(registry, 'pack.json'), JSON.stringify({
223
+ schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
224
+ coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
225
+ sensors: { lint: { fast: true, applicability: { allFiles: ['package.json'] }, variants: [{
226
+ id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
227
+ requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
228
+ assets: ['eslint.config.awm.mjs'], formatter: 'generic', probe: { kind: 'package-script-present' },
229
+ command: { executable: 'live-eslint', resolution: 'node-modules-bin', args: ['.'] },
230
+ }] } },
231
+ }));
232
+ fs_1.default.mkdirSync(path_1.default.join(project, '.awm'));
233
+ fs_1.default.writeFileSync(path_1.default.join(project, '.awm', 'sensors.json'), JSON.stringify({
234
+ schemaVersion: 2, pack: 'js-ts', registryRoot: path_1.default.join(home, 'registries', 'baseline'), sensors: { lint: {
235
+ enabled: true, fast: true, variantId: 'eslint-10',
236
+ command: { executable: 'stale-eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'],
237
+ initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: version, runtimeVersion: process.versions.node, certifiedRange: '>=10 <11', evidence: [] },
238
+ } },
239
+ }));
240
+ mockRunStructuredCommand.mockReset();
241
+ mockRunStructuredCommand.mockResolvedValue(ok());
242
+ });
243
+ afterEach(() => {
244
+ delete process.env.AWM_HOME;
245
+ fs_1.default.rmSync(project, { recursive: true, force: true });
246
+ fs_1.default.rmSync(home, { recursive: true, force: true });
247
+ });
248
+ it('runs the re-resolved command for an unchanged variant id, never the stale manifest command', async () => {
249
+ const { resolveLiveCompatibility } = require('../../../src/commands/sensors/compatibility/live');
250
+ await expect(resolveLiveCompatibility(project, 'js-ts', path_1.default.join(home, 'registries', 'baseline'))).resolves.toBeDefined();
251
+ const { runSensors } = require('../../../src/commands/sensors/run');
252
+ const result = await runSensors({ cwd: project, fast: true });
253
+ expect(result).toEqual(expect.objectContaining({ overall: 'pass' }));
254
+ expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
255
+ });
256
+ it('honors disabled v2 sensors before dispatching them', async () => {
257
+ const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(project, '.awm', 'sensors.json'), 'utf8'));
258
+ manifest.sensors.lint.enabled = false;
259
+ fs_1.default.writeFileSync(path_1.default.join(project, '.awm', 'sensors.json'), JSON.stringify(manifest));
260
+ const { runSensors } = require('../../../src/commands/sensors/run');
261
+ const result = await runSensors({ cwd: project, fast: true });
262
+ expect(result.sensors).toEqual([expect.objectContaining({ status: 'skipped', skipReason: 'disabled' })]);
263
+ expect(mockRunStructuredCommand).not.toHaveBeenCalled();
264
+ });
265
+ });
203
266
  describe('runSensors — missing tool is a fail, not a skip', () => {
204
267
  let root;
205
268
  afterEach(() => {
@@ -276,7 +339,7 @@ describe('runSensors — not_certified + auto-discovery', () => {
276
339
  const nested = path_1.default.join(tmpDir, 'a', 'b');
277
340
  fs_1.default.mkdirSync(nested, { recursive: true });
278
341
  const out = await (0, run_1.runSensors)({ cwd: nested });
279
- expect(out.overall).toBe('pass');
342
+ expect(out.overall).toBe('not_certified');
280
343
  expect(out.sensors.length).toBe(1);
281
344
  });
282
345
  });
@@ -48,25 +48,25 @@ describe('computeSensorStatus — Windows PATH resolution', () => {
48
48
  sensors: { security: { cmd: 'semgrep --json .', fast: false } }
49
49
  }));
50
50
  }
51
- it('resuelve un binario instalado en win32 via PATHEXT (incluido un shim .cmd)', () => {
51
+ it('resuelve un binario instalado en win32 via PATHEXT (incluido un shim .cmd)', async () => {
52
52
  writeManifest();
53
53
  // En win32 el usuario escribe `semgrep` y en disco existe `semgrep.cmd`.
54
54
  fs_1.default.writeFileSync(path_1.default.join(pathDir, 'semgrep.cmd'), '@echo off\r\n');
55
- const result = (0, status_1.computeSensorStatus)(tmpDir);
56
- expect(result.overall).toBe('HEALTHY');
55
+ const result = await (0, status_1.computeSensorStatus)(tmpDir);
56
+ expect(result.overall).toBe('DEGRADED');
57
57
  expect(result.checks.security.ok).toBe(true);
58
58
  });
59
- it('reporta ok:false en win32 cuando el binario no esta en PATH', () => {
59
+ it('reporta ok:false en win32 cuando el binario no esta en PATH', async () => {
60
60
  writeManifest();
61
61
  // PATH aislado y vacio.
62
- const result = (0, status_1.computeSensorStatus)(tmpDir);
62
+ const result = await (0, status_1.computeSensorStatus)(tmpDir);
63
63
  expect(result.overall).toBe('DEGRADED');
64
64
  expect(result.checks.security.ok).toBe(false);
65
65
  });
66
- it('en win32 no exige bit de ejecucion — un .exe sin permisos POSIX igual resuelve', () => {
66
+ it('en win32 no exige bit de ejecucion — un .exe sin permisos POSIX igual resuelve', async () => {
67
67
  writeManifest();
68
68
  fs_1.default.writeFileSync(path_1.default.join(pathDir, 'semgrep.exe'), '');
69
69
  fs_1.default.chmodSync(path_1.default.join(pathDir, 'semgrep.exe'), 0o644);
70
- expect((0, status_1.computeSensorStatus)(tmpDir).checks.security.ok).toBe(true);
70
+ expect((await (0, status_1.computeSensorStatus)(tmpDir)).checks.security.ok).toBe(true);
71
71
  });
72
72
  });