agentic-workflow-manager 9.20.4 → 9.22.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 (42) hide show
  1. package/dist/src/commands/change/index.js +14 -3
  2. package/dist/src/commands/preflight/checks.js +31 -0
  3. package/dist/src/commands/sensors/compatibility/contract.js +56 -3
  4. package/dist/src/commands/sensors/compatibility/manifest.js +16 -2
  5. package/dist/src/commands/sensors/index.js +10 -1
  6. package/dist/src/commands/sensors/init.js +7 -1
  7. package/dist/src/commands/sensors/inputs-digest.js +315 -0
  8. package/dist/src/commands/sensors/prepare.js +10 -0
  9. package/dist/src/commands/sensors/result-cache.js +278 -0
  10. package/dist/src/commands/sensors/result.js +31 -12
  11. package/dist/src/commands/sensors/run.js +265 -8
  12. package/dist/src/commands/sensors/status.js +66 -7
  13. package/dist/src/commands/watch/init.js +3 -15
  14. package/dist/src/core/awm-gitignore.js +211 -0
  15. package/dist/src/core/changes/receipt.js +219 -9
  16. package/dist/src/core/diagnostics/checks.js +11 -0
  17. package/dist/tests/commands/change/new.test.js +1 -1
  18. package/dist/tests/commands/change/validate-unmeasured.test.js +246 -0
  19. package/dist/tests/commands/preflight/awm-gitignore.test.js +52 -0
  20. package/dist/tests/commands/preflight/sensors-inputs.test.js +110 -0
  21. package/dist/tests/commands/sensors/compatibility/contract.test.js +60 -0
  22. package/dist/tests/commands/sensors/compatibility/manifest.test.js +94 -0
  23. package/dist/tests/commands/sensors/init-gitignore.test.js +59 -0
  24. package/dist/tests/commands/sensors/inputs-digest.test.js +373 -0
  25. package/dist/tests/commands/sensors/result-cache.test.js +352 -0
  26. package/dist/tests/commands/sensors/run-cache.test.js +434 -0
  27. package/dist/tests/commands/sensors/run-slice.test.js +171 -0
  28. package/dist/tests/commands/sensors/status-inputs.test.js +271 -0
  29. package/dist/tests/commands/watch/init-gitignore.test.js +34 -0
  30. package/dist/tests/commands/watch/track-freeze.test.js +11 -7
  31. package/dist/tests/commands/watch/watch-init.test.js +3 -1
  32. package/dist/tests/core/awm-gitignore.test.js +58 -0
  33. package/dist/tests/core/changes/receipt.test.js +261 -2
  34. package/dist/tests/core/diagnostics/awm-gitignore.test.js +101 -0
  35. package/dist/tests/core/ledger/gitignore.test.js +6 -1
  36. package/dist/tests/structural/sensor-execution-optimization-docs-contract.test.js +102 -0
  37. package/dist/tests/structural/sensor-slice-decision-contract.test.js +63 -0
  38. package/package.json +2 -1
  39. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  40. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  41. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  42. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,352 @@
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 result_cache_1 = require("../../../src/commands/sensors/result-cache");
10
+ const result_1 = require("../../../src/commands/sensors/result");
11
+ const baseline_1 = require("../../../src/commands/sensors/baseline");
12
+ const exec_fixtures_1 = require("./exec-fixtures");
13
+ function prepared(overrides = {}) {
14
+ return {
15
+ name: 'lint',
16
+ formatter: 'eslint-llm',
17
+ timeoutMs: 30_000,
18
+ timeoutSource: 'project',
19
+ requestedScope: 'full',
20
+ effectiveScope: 'full',
21
+ command: { kind: 'structured', value: { executable: 'eslint', resolution: 'path', args: ['.'] } },
22
+ ...overrides,
23
+ };
24
+ }
25
+ function sampleRaw(overrides = {}) {
26
+ return {
27
+ name: 'lint',
28
+ status: 'pass',
29
+ errors: [],
30
+ ...overrides,
31
+ };
32
+ }
33
+ describe('result cache helpers (RF-2.4, RF-2.10, RF-2.15)', () => {
34
+ const roots = [];
35
+ afterEach(() => {
36
+ for (const root of roots.splice(0))
37
+ fs_1.default.rmSync(root, { recursive: true, force: true });
38
+ });
39
+ it('sets failureKind findings|missing-binary|exit-code on fail results (RF-2.4)', () => {
40
+ const findings = (0, result_1.interpretResult)(prepared(), (0, exec_fixtures_1.exited)(1, JSON.stringify([{
41
+ filePath: 'a.ts',
42
+ messages: [{ line: 1, column: 1, message: 'x', ruleId: 'no-x', severity: 2 }],
43
+ }])));
44
+ expect(findings.status).toBe('fail');
45
+ expect(findings.failureKind).toBe('findings');
46
+ // Name `security` selects the semgrep formatter (no invented findings from shell noise).
47
+ const missing = (0, result_1.interpretResult)(prepared({ name: 'security', formatter: undefined }), (0, exec_fixtures_1.exited)(127, '', 'command not found'));
48
+ expect(missing.status).toBe('fail');
49
+ expect(missing.failureKind).toBe('missing-binary');
50
+ const spawn = (0, result_1.interpretResult)(prepared({ formatter: 'exit-code' }), (0, exec_fixtures_1.spawnFailed)('ENOENT'));
51
+ expect(spawn.status).toBe('fail');
52
+ expect(spawn.failureKind).toBe('missing-binary');
53
+ const exitCode = (0, result_1.interpretResult)(prepared({ formatter: 'exit-code', name: 'iac' }), (0, exec_fixtures_1.exited)(1, 'drift\n'));
54
+ expect(exitCode.status).toBe('fail');
55
+ expect(exitCode.failureKind).toBe('exit-code');
56
+ });
57
+ it('canCacheRawResult allows pass and findings-fail with errors; rejects exit-code/missing-binary/incomplete (RF-2.4)', () => {
58
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({ status: 'pass' }))).toBe(true);
59
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({
60
+ status: 'fail',
61
+ failureKind: 'findings',
62
+ errors: [{ message: 'x', file: 'a.ts', rule: 'r' }],
63
+ }))).toBe(true);
64
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({ status: 'fail', failureKind: 'exit-code', errors: [{ message: 'exit 1' }] }))).toBe(false);
65
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({ status: 'fail', failureKind: 'missing-binary', errors: [{ message: 'gone' }] }))).toBe(false);
66
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({ status: 'fail', failureKind: 'findings', errors: [] }))).toBe(false);
67
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({ status: 'inconclusive', errors: [], skipReason: 'timeout' }))).toBe(false);
68
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({
69
+ status: 'fail',
70
+ failureKind: 'findings',
71
+ errors: [{ message: 'x' }],
72
+ incomplete: 'timeout after 1ms',
73
+ }))).toBe(false);
74
+ expect((0, result_cache_1.canCacheRawResult)((0, result_1.interpretResult)(prepared({ formatter: 'exit-code' }), (0, exec_fixtures_1.exited)(1, 'x')))).toBe(false);
75
+ expect((0, result_cache_1.canCacheRawResult)((0, result_1.interpretResult)(prepared({ name: 'security', formatter: undefined }), (0, exec_fixtures_1.exited)(127, '', 'command not found')))).toBe(false);
76
+ expect((0, result_cache_1.canCacheRawResult)((0, result_1.interpretResult)(prepared(), (0, exec_fixtures_1.ok)()))).toBe(true);
77
+ });
78
+ it('canCacheRawResult / planted pass+errors or pass+failureKind is a miss (not cacheable)', () => {
79
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({
80
+ status: 'pass',
81
+ errors: [{ message: 'planted finding' }],
82
+ }))).toBe(false);
83
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({
84
+ status: 'pass',
85
+ failureKind: 'findings',
86
+ errors: [],
87
+ }))).toBe(false);
88
+ expect((0, result_cache_1.canCacheRawResult)(sampleRaw({
89
+ status: 'pass',
90
+ failureKind: 'exit-code',
91
+ errors: [{ message: 'x' }],
92
+ }))).toBe(false);
93
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-pass-errors-'));
94
+ roots.push(root);
95
+ const base = {
96
+ schema: 1,
97
+ sensorId: 'lint',
98
+ projectKey: 'proj',
99
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
100
+ inputDigest: 'sha256:' + 'b'.repeat(64),
101
+ originCommit: 'c'.repeat(40),
102
+ originDirty: false,
103
+ };
104
+ const lookup = {
105
+ sensorId: 'lint',
106
+ projectKey: 'proj',
107
+ sensorFingerprint: base.sensorFingerprint,
108
+ inputDigest: base.inputDigest,
109
+ };
110
+ const { cacheFileName } = require('../../../src/commands/sensors/result-cache');
111
+ const dir = path_1.default.join(root, '.awm', 'sensors', 'runs');
112
+ fs_1.default.mkdirSync(dir, { recursive: true });
113
+ fs_1.default.writeFileSync(path_1.default.join(dir, cacheFileName(lookup)), JSON.stringify({
114
+ ...base,
115
+ raw: { name: 'lint', status: 'pass', errors: [{ message: 'planted' }] },
116
+ }, null, 2) + '\n');
117
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
118
+ expect(() => (0, result_cache_1.writeResultCache)(root, {
119
+ ...base,
120
+ raw: { name: 'lint', status: 'pass', errors: [{ message: 'planted' }] },
121
+ })).toThrow(/cacheable|refuse|uncacheable|RF-2\.4|errors|failureKind/i);
122
+ });
123
+ it('binds raw.name to sensorId on write and read; mismatch refuses write / misses on read', () => {
124
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-name-bind-'));
125
+ roots.push(root);
126
+ const base = {
127
+ schema: 1,
128
+ sensorId: 'lint',
129
+ projectKey: 'proj',
130
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
131
+ inputDigest: 'sha256:' + 'b'.repeat(64),
132
+ originCommit: 'c'.repeat(40),
133
+ originDirty: false,
134
+ };
135
+ expect(() => (0, result_cache_1.writeResultCache)(root, {
136
+ ...base,
137
+ raw: { name: 'typecheck', status: 'pass', errors: [] },
138
+ })).toThrow(/name|sensorId|mismatch|bind/i);
139
+ (0, result_cache_1.writeResultCache)(root, {
140
+ ...base,
141
+ raw: { name: 'lint', status: 'pass', errors: [] },
142
+ });
143
+ const lookup = {
144
+ sensorId: 'lint',
145
+ projectKey: 'proj',
146
+ sensorFingerprint: base.sensorFingerprint,
147
+ inputDigest: base.inputDigest,
148
+ };
149
+ const { cacheFileName } = require('../../../src/commands/sensors/result-cache');
150
+ const dir = path_1.default.join(root, '.awm', 'sensors', 'runs');
151
+ const cachePath = path_1.default.join(dir, cacheFileName(lookup));
152
+ const record = JSON.parse(fs_1.default.readFileSync(cachePath, 'utf8'));
153
+ record.raw.name = 'typecheck';
154
+ fs_1.default.writeFileSync(cachePath, JSON.stringify(record, null, 2) + '\n');
155
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
156
+ });
157
+ it('constrains cached SensorError message/file to single-line strings', () => {
158
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-error-line-'));
159
+ roots.push(root);
160
+ const base = {
161
+ schema: 1,
162
+ sensorId: 'lint',
163
+ projectKey: 'proj',
164
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
165
+ inputDigest: 'sha256:' + 'b'.repeat(64),
166
+ originCommit: 'c'.repeat(40),
167
+ originDirty: false,
168
+ };
169
+ expect(() => (0, result_cache_1.writeResultCache)(root, {
170
+ ...base,
171
+ raw: {
172
+ name: 'lint',
173
+ status: 'fail',
174
+ failureKind: 'findings',
175
+ errors: [{ message: 'bad\nline', file: 'a.ts' }],
176
+ },
177
+ })).toThrow(/single.?line|message|errors/i);
178
+ expect(() => (0, result_cache_1.writeResultCache)(root, {
179
+ ...base,
180
+ raw: {
181
+ name: 'lint',
182
+ status: 'fail',
183
+ failureKind: 'findings',
184
+ errors: [{ message: 'ok', file: 'a\nts' }],
185
+ },
186
+ })).toThrow(/single.?line|file|errors/i);
187
+ });
188
+ it('computeSensorFingerprint returns null without live tool/runtime versions (RF-2.10)', () => {
189
+ expect((0, result_cache_1.computeSensorFingerprint)({
190
+ variantId: 'eslint-10',
191
+ command: { executable: 'eslint', args: ['.'] },
192
+ cliVersion: '9.21.0',
193
+ inputs: ['**/*.ts'],
194
+ toolVersion: null,
195
+ runtimeVersion: '22.0.0',
196
+ })).toBeNull();
197
+ expect((0, result_cache_1.computeSensorFingerprint)({
198
+ variantId: null,
199
+ command: { executable: 'node', args: ['x'] },
200
+ cliVersion: '9.21.0',
201
+ inputs: ['**/*.ts'],
202
+ toolVersion: undefined,
203
+ runtimeVersion: undefined,
204
+ })).toBeNull();
205
+ const fp = (0, result_cache_1.computeSensorFingerprint)({
206
+ variantId: 'eslint-10',
207
+ command: { executable: 'eslint', args: ['.'] },
208
+ cliVersion: '9.21.0',
209
+ inputs: ['**/*.ts'],
210
+ toolVersion: '10.0.0',
211
+ runtimeVersion: '22.0.0',
212
+ });
213
+ expect(fp).toMatch(/^sha256:[0-9a-f]{64}$/);
214
+ const changedTool = (0, result_cache_1.computeSensorFingerprint)({
215
+ variantId: 'eslint-10',
216
+ command: { executable: 'eslint', args: ['.'] },
217
+ cliVersion: '9.21.0',
218
+ inputs: ['**/*.ts'],
219
+ toolVersion: '10.1.0',
220
+ runtimeVersion: '22.0.0',
221
+ });
222
+ expect(changedTool).not.toBe(fp);
223
+ });
224
+ it('validates cache reads recursively; tamper or symlink yields miss (RF-2.15, RF-E.1)', () => {
225
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-'));
226
+ roots.push(root);
227
+ const record = {
228
+ schema: 1,
229
+ sensorId: 'lint',
230
+ projectKey: 'proj',
231
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
232
+ inputDigest: 'sha256:' + 'b'.repeat(64),
233
+ originCommit: 'c'.repeat(40),
234
+ originDirty: false,
235
+ raw: { name: 'lint', status: 'pass', errors: [] },
236
+ };
237
+ (0, result_cache_1.writeResultCache)(root, record);
238
+ const hit = (0, result_cache_1.readResultCache)(root, {
239
+ sensorId: 'lint',
240
+ projectKey: 'proj',
241
+ sensorFingerprint: record.sensorFingerprint,
242
+ inputDigest: record.inputDigest,
243
+ });
244
+ expect(hit).toEqual(record);
245
+ const file = path_1.default.join(root, '.awm', 'sensors', 'runs', hit ? path_1.default.basename(
246
+ // discover written file
247
+ fs_1.default.readdirSync(path_1.default.join(root, '.awm', 'sensors', 'runs')).find((n) => n.startsWith('cache-'))) : '');
248
+ const cacheDir = path_1.default.join(root, '.awm', 'sensors', 'runs');
249
+ const cacheFiles = fs_1.default.readdirSync(cacheDir).filter((n) => n.startsWith('cache-'));
250
+ expect(cacheFiles).toHaveLength(1);
251
+ const cachePath = path_1.default.join(cacheDir, cacheFiles[0]);
252
+ fs_1.default.writeFileSync(cachePath, JSON.stringify({ ...record, raw: { status: 'pass' } }));
253
+ expect((0, result_cache_1.readResultCache)(root, {
254
+ sensorId: 'lint',
255
+ projectKey: 'proj',
256
+ sensorFingerprint: record.sensorFingerprint,
257
+ inputDigest: record.inputDigest,
258
+ })).toBeNull();
259
+ fs_1.default.writeFileSync(cachePath, JSON.stringify(record));
260
+ fs_1.default.unlinkSync(cachePath);
261
+ fs_1.default.symlinkSync('/tmp/evil', cachePath);
262
+ expect((0, result_cache_1.readResultCache)(root, {
263
+ sensorId: 'lint',
264
+ projectKey: 'proj',
265
+ sensorFingerprint: record.sensorFingerprint,
266
+ inputDigest: record.inputDigest,
267
+ })).toBeNull();
268
+ void file;
269
+ });
270
+ it('rejects cache reads whose raw would fail canCacheRawResult (RF-2.15/2.4)', () => {
271
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-uncacheable-'));
272
+ roots.push(root);
273
+ const base = {
274
+ schema: 1,
275
+ sensorId: 'lint',
276
+ projectKey: 'proj',
277
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
278
+ inputDigest: 'sha256:' + 'b'.repeat(64),
279
+ originCommit: 'c'.repeat(40),
280
+ originDirty: false,
281
+ };
282
+ const lookup = {
283
+ sensorId: 'lint',
284
+ projectKey: 'proj',
285
+ sensorFingerprint: base.sensorFingerprint,
286
+ inputDigest: base.inputDigest,
287
+ };
288
+ const plant = (raw) => {
289
+ // Bypass writeResultCache so uncacheable raw can be planted on disk.
290
+ const dir = path_1.default.join(root, '.awm', 'sensors', 'runs');
291
+ fs_1.default.mkdirSync(dir, { recursive: true });
292
+ const { cacheFileName } = require('../../../src/commands/sensors/result-cache');
293
+ fs_1.default.writeFileSync(path_1.default.join(dir, cacheFileName(lookup)), JSON.stringify({ ...base, raw }, null, 2) + '\n');
294
+ };
295
+ plant({ name: 'lint', status: 'fail', failureKind: 'exit-code', errors: [{ message: 'exit 1' }] });
296
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
297
+ plant({ name: 'lint', status: 'fail', failureKind: 'missing-binary', errors: [{ message: 'gone' }] });
298
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
299
+ plant({ name: 'lint', status: 'fail', failureKind: 'findings', errors: [] });
300
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
301
+ plant({
302
+ name: 'lint',
303
+ status: 'fail',
304
+ failureKind: 'findings',
305
+ errors: [{ message: 'x', file: 'a.ts', rule: 'r' }],
306
+ incomplete: 'timeout after 1ms',
307
+ });
308
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toBeNull();
309
+ plant({
310
+ name: 'lint',
311
+ status: 'fail',
312
+ failureKind: 'findings',
313
+ errors: [{ message: 'x', file: 'a.ts', rule: 'r' }],
314
+ });
315
+ expect((0, result_cache_1.readResultCache)(root, lookup)).toMatchObject({
316
+ raw: { status: 'fail', failureKind: 'findings' },
317
+ });
318
+ });
319
+ it('writeResultCache refuses uncacheable raw; toCacheRaw does not coerce skipped/inconclusive to pass (QA-3)', () => {
320
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-rcache-write-gate-'));
321
+ roots.push(root);
322
+ const base = {
323
+ schema: 1,
324
+ sensorId: 'lint',
325
+ projectKey: 'proj',
326
+ sensorFingerprint: 'sha256:' + 'a'.repeat(64),
327
+ inputDigest: 'sha256:' + 'b'.repeat(64),
328
+ originCommit: 'c'.repeat(40),
329
+ originDirty: false,
330
+ };
331
+ expect(() => (0, result_cache_1.writeResultCache)(root, {
332
+ ...base,
333
+ raw: { name: 'lint', status: 'fail', failureKind: 'exit-code', errors: [{ message: 'exit 1' }] },
334
+ })).toThrow(/cacheable|refuse|uncacheable|RF-2\.4/i);
335
+ expect(() => (0, result_cache_1.toCacheRaw)(sampleRaw({ status: 'skipped', skipReason: 'disabled' }))).toThrow(/pass|fail|cache/i);
336
+ expect(() => (0, result_cache_1.toCacheRaw)(sampleRaw({ status: 'inconclusive', skipReason: 'timeout' }))).toThrow(/pass|fail|cache/i);
337
+ expect((0, result_cache_1.toCacheRaw)(sampleRaw({ status: 'pass' }))).toMatchObject({ status: 'pass', errors: [] });
338
+ });
339
+ it('strips failureKind when applyBaseline yields pass (QA-4)', () => {
340
+ const err = { file: 'a.ts', line: 1, column: 1, message: 'no-x', rule: 'no-x' };
341
+ const raw = {
342
+ name: 'lint',
343
+ status: 'fail',
344
+ failureKind: 'findings',
345
+ errors: [err],
346
+ };
347
+ const suppressed = (0, result_1.applyBaseline)(raw, [(0, baseline_1.fingerprint)('lint', err)]);
348
+ expect(suppressed.status).toBe('pass');
349
+ expect(suppressed.failureKind).toBeUndefined();
350
+ expect(suppressed.errors).toEqual([]);
351
+ });
352
+ });