agentic-workflow-manager 9.5.0 → 9.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dist/src/commands/context/index.js +3 -1
  2. package/dist/src/commands/preflight/checks.js +2 -2
  3. package/dist/src/commands/sensors/bootstrap.js +171 -0
  4. package/dist/src/commands/sensors/compatibility/contract.js +18 -15
  5. package/dist/src/commands/sensors/compatibility/manifest.js +29 -1
  6. package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
  7. package/dist/src/commands/sensors/compatibility/safe-file.js +240 -5
  8. package/dist/src/commands/sensors/coverage/index.js +2 -2
  9. package/dist/src/commands/sensors/coverage/resolve.js +20 -10
  10. package/dist/src/commands/sensors/detection.js +31 -0
  11. package/dist/src/commands/sensors/exec.js +116 -11
  12. package/dist/src/commands/sensors/index.js +51 -10
  13. package/dist/src/commands/sensors/init.js +17 -170
  14. package/dist/src/commands/sensors/migrate.js +209 -0
  15. package/dist/src/commands/sensors/run.js +2 -2
  16. package/dist/src/core/discovery.js +79 -4
  17. package/dist/src/core/init/steps.js +24 -11
  18. package/dist/src/core/orchestrators.js +34 -10
  19. package/dist/src/core/secure-fs/native-bridge.js +209 -0
  20. package/dist/src/core/text.js +7 -0
  21. package/dist/tests/commands/context.test.js +9 -1
  22. package/dist/tests/commands/hooks/install.test.js +4 -0
  23. package/dist/tests/commands/hooks/resync.test.js +2 -0
  24. package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
  25. package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
  26. package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
  27. package/dist/tests/commands/sensors/compatibility/manifest.test.js +67 -0
  28. package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
  29. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +34 -1
  30. package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
  31. package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
  32. package/dist/tests/commands/sensors/coverage/resolve.test.js +10 -0
  33. package/dist/tests/commands/sensors/exec.test.js +86 -0
  34. package/dist/tests/commands/sensors/index.test.js +54 -0
  35. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
  36. package/dist/tests/commands/sensors/init.test.js +56 -490
  37. package/dist/tests/commands/sensors/migrate.test.js +513 -0
  38. package/dist/tests/core/context/orchestrator.test.js +2 -0
  39. package/dist/tests/core/discovery-multiroot.test.js +5 -0
  40. package/dist/tests/core/discovery-parent-race.test.js +95 -0
  41. package/dist/tests/core/discovery-safe-read.test.js +115 -0
  42. package/dist/tests/core/discovery.test.js +28 -1
  43. package/dist/tests/core/init/orchestrator.test.js +3 -1
  44. package/dist/tests/core/orchestrators.test.js +171 -14
  45. package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
  46. package/dist/tests/core/text.test.js +10 -0
  47. package/dist/tests/integration/context-orchestrators-e2e.test.js +57 -7
  48. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
  49. package/dist/tests/integration/sensor-compatibility.e2e.test.js +10 -2
  50. package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
  51. package/dist/tests/structural/native-release-artifacts.test.js +22 -0
  52. package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
  53. package/dist/tests/structural/release-registry-provenance.test.js +4 -0
  54. package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
  55. package/package.json +6 -2
  56. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  57. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  58. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  59. package/prebuilds/linux-x64/secure_fs.node +0 -0
  60. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  61. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -1,6 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const text_1 = require("../../src/core/text");
4
+ describe('sanitizeDiagnosticText', () => {
5
+ it.each([
6
+ ['a\r\nb\rc\nd\te', 'a b c d e'],
7
+ ['a\x00\x07\x1b[31m\x7f\x85\x9bz', 'a[31mz'],
8
+ ['a\u2028b\u2029c', 'a b c'],
9
+ ['plain *markdown* — café', 'plain *markdown* — café'],
10
+ ])('sanitizes a single diagnostic field: %j', (input, expected) => {
11
+ expect((0, text_1.sanitizeDiagnosticText)(input)).toBe(expected);
12
+ });
13
+ });
4
14
  describe('stripControlChars', () => {
5
15
  it('elimina ESC y demas bytes de control C0', () => {
6
16
  expect((0, text_1.stripControlChars)('a\x1b[31mb\x00c\x07d')).toBe('a[31mbcd');
@@ -14,13 +14,13 @@ function seedRegistry() {
14
14
  const home = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ctx-e2e-')));
15
15
  const awmHome = path_1.default.join(home, '.awm');
16
16
  const root = path_1.default.join(awmHome, 'registries', 'test-registry');
17
- fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'using-awm'), { recursive: true });
17
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'ejemplo-proceso'), { recursive: true });
18
18
  fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'test-registry', remote: 'https://example.invalid/test.git' }]));
19
19
  fs_1.default.writeFileSync(path_1.default.join(root, 'awm-registry.json'), JSON.stringify({
20
20
  minCliVersion: '8.5.0',
21
21
  orchestrator: { name: 'ejemplo-proceso', appliesWhen: 'cuando hay una tarea sin plan', terminatesTo: 'development-process' },
22
22
  }));
23
- fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'using-awm', 'SKILL.md'), '---\nname: using-awm\nversion: "1.0.0"\n---\n\n# using-awm\n');
23
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'ejemplo-proceso', 'SKILL.md'), '---\nname: ejemplo-proceso\nversion: "1.0.0"\n---\n\n# ejemplo-proceso\n');
24
24
  return home;
25
25
  }
26
26
  /**
@@ -36,7 +36,7 @@ function seedRealisticRegistry() {
36
36
  const home = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ctx-e2e-real-')));
37
37
  const awmHome = path_1.default.join(home, '.awm');
38
38
  const root = path_1.default.join(awmHome, 'registries', 'test-registry');
39
- fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'using-awm'), { recursive: true });
39
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'task_capture_process'), { recursive: true });
40
40
  fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'test-registry', remote: 'https://example.invalid/test.git' }]));
41
41
  fs_1.default.writeFileSync(path_1.default.join(root, 'awm-registry.json'), JSON.stringify({
42
42
  minCliVersion: '8.5.0',
@@ -46,7 +46,7 @@ function seedRealisticRegistry() {
46
46
  terminatesTo: 'development-process',
47
47
  },
48
48
  }));
49
- fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'using-awm', 'SKILL.md'), '---\nname: using-awm\nversion: "1.0.0"\n---\n\n# using-awm\n');
49
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'task_capture_process', 'SKILL.md'), '---\nname: task_capture_process\nversion: "1.0.0"\n---\n\n# task_capture_process\n');
50
50
  return home;
51
51
  }
52
52
  /**
@@ -63,7 +63,7 @@ function seedMixedRegistries() {
63
63
  const awmHome = path_1.default.join(home, '.awm');
64
64
  const sanoRoot = path_1.default.join(awmHome, 'registries', 'sano-registry');
65
65
  const rotoRoot = path_1.default.join(awmHome, 'registries', 'roto-registry');
66
- fs_1.default.mkdirSync(path_1.default.join(sanoRoot, 'skills', 'using-awm'), { recursive: true });
66
+ fs_1.default.mkdirSync(path_1.default.join(sanoRoot, 'skills', 'proceso-sano'), { recursive: true });
67
67
  fs_1.default.mkdirSync(path_1.default.join(rotoRoot, 'skills', 'using-awm'), { recursive: true });
68
68
  fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([
69
69
  { name: 'sano-registry', remote: 'https://example.invalid/sano.git' },
@@ -79,8 +79,20 @@ function seedMixedRegistries() {
79
79
  minCliVersion: '8.5.0',
80
80
  orchestrator: { name: 'proceso-roto', appliesWhen: 'cuando algo pasa' },
81
81
  }));
82
- fs_1.default.writeFileSync(path_1.default.join(sanoRoot, 'skills', 'using-awm', 'SKILL.md'), '---\nname: using-awm\nversion: "1.0.0"\n---\n\n# using-awm\n');
83
- fs_1.default.writeFileSync(path_1.default.join(rotoRoot, 'skills', 'using-awm', 'SKILL.md'), '---\nname: using-awm\nversion: "1.0.0"\n---\n\n# using-awm\n');
82
+ fs_1.default.writeFileSync(path_1.default.join(sanoRoot, 'skills', 'proceso-sano', 'SKILL.md'), '---\nname: proceso-sano\nversion: "1.0.0"\n---\n\n# proceso-sano\n');
83
+ return home;
84
+ }
85
+ function seedPhantomRegistry() {
86
+ const home = fs_1.default.realpathSync(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ctx-e2e-phantom-')));
87
+ const awmHome = path_1.default.join(home, '.awm');
88
+ const root = path_1.default.join(awmHome, 'registries', 'test-registry');
89
+ fs_1.default.mkdirSync(path_1.default.join(root, 'skills', 'real-skill'), { recursive: true });
90
+ fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'test-registry', remote: 'https://example.invalid/test.git' }]));
91
+ fs_1.default.writeFileSync(path_1.default.join(root, 'awm-registry.json'), JSON.stringify({
92
+ minCliVersion: '8.5.0',
93
+ orchestrator: { name: 'phantom-process', appliesWhen: 'cuando hay una tarea sin plan', terminatesTo: 'development-process' },
94
+ }));
95
+ fs_1.default.writeFileSync(path_1.default.join(root, 'skills', 'real-skill', 'SKILL.md'), '---\nname: real-skill\nversion: "1.0.0"\n---\n\n# real-skill\n');
84
96
  return home;
85
97
  }
86
98
  function run(home, ...args) {
@@ -142,3 +154,41 @@ describe('awm context orchestrators -- diagnosticos + sanos mezclados (binario r
142
154
  expect(r.stderr).toContain('terminatesTo');
143
155
  });
144
156
  });
157
+ describe('awm context orchestrators -- declaracion phantom (binario real)', () => {
158
+ let home;
159
+ beforeAll(() => { home = seedPhantomRegistry(); });
160
+ afterAll(() => fs_1.default.rmSync(home, { recursive: true, force: true }));
161
+ it('--verify devuelve 2 y diagnostica una declaracion sin skill resoluble', () => {
162
+ const r = run(home, 'context', 'orchestrators', '--verify', 'phantom-process');
163
+ expect(r.status).toBe(2);
164
+ expect(r.stderr).toMatch(/phantom-process/);
165
+ expect(r.stderr).toMatch(/declaration dropped|skill/i);
166
+ });
167
+ });
168
+ describe('awm context orchestrators -- dropped sanitized collision (real binary)', () => {
169
+ let home;
170
+ beforeAll(() => {
171
+ home = seedPhantomRegistry();
172
+ const awmHome = path_1.default.join(home, '.awm');
173
+ const phantomRoot = path_1.default.join(awmHome, 'registries', 'test-registry');
174
+ const retainedRoot = path_1.default.join(awmHome, 'registries', 'retained');
175
+ fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([
176
+ { name: 'test-registry', remote: 'unused' }, { name: 'retained', remote: 'unused' },
177
+ ]));
178
+ fs_1.default.writeFileSync(path_1.default.join(phantomRoot, 'awm-registry.json'), JSON.stringify({
179
+ orchestrator: { name: 'foo_bar', appliesWhen: 'x', terminatesTo: 'none' },
180
+ }));
181
+ fs_1.default.mkdirSync(path_1.default.join(retainedRoot, 'skills', 'foobar'), { recursive: true });
182
+ fs_1.default.writeFileSync(path_1.default.join(retainedRoot, 'skills', 'foobar', 'SKILL.md'), '# foobar\n');
183
+ fs_1.default.writeFileSync(path_1.default.join(retainedRoot, 'awm-registry.json'), JSON.stringify({
184
+ orchestrator: { name: 'foobar', appliesWhen: 'x', terminatesTo: 'none' },
185
+ }));
186
+ });
187
+ afterAll(() => fs_1.default.rmSync(home, { recursive: true, force: true }));
188
+ it('verifies the retained raw name and rejects the dropped raw name', () => {
189
+ expect(run(home, 'context', 'orchestrators', '--verify', 'foobar').status).toBe(0);
190
+ const dropped = run(home, 'context', 'orchestrators', '--verify', 'foo_bar');
191
+ expect(dropped.status).toBe(2);
192
+ expect(dropped.stderr).toContain('declaration dropped');
193
+ });
194
+ });
@@ -0,0 +1,165 @@
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 crypto_1 = __importDefault(require("crypto"));
7
+ const child_process_1 = require("child_process");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const os_1 = __importDefault(require("os"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const materialize_1 = require("../../src/commands/sensors/compatibility/materialize");
12
+ const pack_source_1 = require("../../src/commands/sensors/compatibility/pack-source");
13
+ const safe_file_1 = require("../../src/commands/sensors/compatibility/safe-file");
14
+ const native_bridge_1 = require("../../src/core/secure-fs/native-bridge");
15
+ const cliRoot = path_1.default.resolve(__dirname, '../..');
16
+ const bin = path_1.default.join(cliRoot, 'dist/src/index.js');
17
+ const fixtureRoot = path_1.default.join(cliRoot, 'tests/fixtures/sensor-compatibility');
18
+ function hashTree(root) {
19
+ const hash = crypto_1.default.createHash('sha256');
20
+ const walk = (directory) => {
21
+ for (const entry of fs_1.default.readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
22
+ const file = path_1.default.join(directory, entry.name);
23
+ hash.update(path_1.default.relative(root, file));
24
+ if (entry.isDirectory())
25
+ walk(file);
26
+ else if (entry.isFile())
27
+ hash.update(fs_1.default.readFileSync(file));
28
+ }
29
+ };
30
+ walk(root);
31
+ return hash.digest('hex');
32
+ }
33
+ function fixture() {
34
+ const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bootstrap-e2e-'));
35
+ const project = path_1.default.join(root, 'project');
36
+ const awmHome = path_1.default.join(root, 'awm-home');
37
+ const registryRoot = path_1.default.join(awmHome, 'registries', 'baseline');
38
+ fs_1.default.cpSync(path_1.default.join(fixtureRoot, 'project'), project, { recursive: true });
39
+ fs_1.default.mkdirSync(path_1.default.join(project, 'node_modules', 'eslint'), { recursive: true });
40
+ fs_1.default.copyFileSync(path_1.default.join(project, 'eslint-tool-package.json'), path_1.default.join(project, 'node_modules', 'eslint', 'package.json'));
41
+ fs_1.default.mkdirSync(path_1.default.dirname(registryRoot), { recursive: true });
42
+ fs_1.default.cpSync(path_1.default.join(fixtureRoot, 'registry'), registryRoot, { recursive: true });
43
+ fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'fixture' }]));
44
+ return { root, project, awmHome, registryRoot };
45
+ }
46
+ function run(fixture, ...args) {
47
+ return (0, child_process_1.spawnSync)(process.execPath, [bin, 'sensors', 'bootstrap', ...args], {
48
+ cwd: fixture.project,
49
+ encoding: 'utf8',
50
+ env: { ...process.env, AWM_HOME: fixture.awmHome, AWM_NO_UPDATE_CHECK: '1' },
51
+ });
52
+ }
53
+ function v2Manifest() {
54
+ return {
55
+ schemaVersion: 2,
56
+ pack: 'js-ts',
57
+ sensors: {
58
+ lint: {
59
+ enabled: true, variantId: 'eslint-10',
60
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
61
+ assets: ['eslint.fixture.mjs'],
62
+ initializedCompatibility: {
63
+ state: 'certified', reason: 'fixture', variantId: 'eslint-10',
64
+ toolVersion: '10.4.1', runtimeVersion: '24.0.0', certifiedRange: '>=10 <11', evidence: [],
65
+ },
66
+ },
67
+ },
68
+ };
69
+ }
70
+ beforeAll(() => {
71
+ if (!fs_1.default.existsSync(bin))
72
+ throw new Error(`Sensor bootstrap E2E requires the compiled CLI at ${bin}; run npm run build before this test.`);
73
+ });
74
+ test('compiled bootstrap creates once and the exact second invocation is a byte-stable no-op', () => {
75
+ const subject = fixture();
76
+ try {
77
+ fs_1.default.rmSync(path_1.default.join(subject.project, '.awm'), { recursive: true, force: true });
78
+ const machineBefore = hashTree(subject.awmHome);
79
+ expect(run(subject, '--mode', 'native-gate', '--reason', 'fixture-gate').status).toBe(0);
80
+ const first = hashTree(subject.project);
81
+ expect(run(subject, '--mode', 'native-gate', '--reason', 'fixture-gate').status).toBe(0);
82
+ expect(hashTree(subject.project)).toBe(first);
83
+ expect(hashTree(subject.awmHome)).toBe(machineBefore);
84
+ expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(subject.project, '.awm', 'sensors.json'), 'utf8'))).toEqual({ schemaVersion: 3, mode: 'native-gate', reason: 'fixture-gate' });
85
+ }
86
+ finally {
87
+ fs_1.default.rmSync(subject.root, { recursive: true, force: true });
88
+ }
89
+ });
90
+ test('compiled project-sensors bootstrap materializes only selected assets and then no-ops', () => {
91
+ const subject = fixture();
92
+ try {
93
+ fs_1.default.rmSync(path_1.default.join(subject.project, '.awm'), { recursive: true, force: true });
94
+ fs_1.default.copyFileSync(path_1.default.join(subject.registryRoot, 'sensor-packs', 'js-ts', 'eslint.fixture.mjs'), path_1.default.join(subject.project, 'eslint.fixture.mjs'));
95
+ const machineBefore = hashTree(subject.awmHome);
96
+ const result = run(subject, '--mode', 'project-sensors');
97
+ if (result.status !== 0)
98
+ throw new Error(`project-sensors bootstrap failed: ${result.stdout}\n${result.stderr}`);
99
+ expect(`${result.stdout}${result.stderr}`).toContain('pack=js-ts');
100
+ expect(fs_1.default.existsSync(path_1.default.join(subject.project, 'eslint.fixture.mjs'))).toBe(true);
101
+ expect(fs_1.default.existsSync(path_1.default.join(subject.project, 'pack.json'))).toBe(false);
102
+ const first = hashTree(subject.project);
103
+ expect(run(subject, '--mode', 'project-sensors').status).toBe(0);
104
+ expect(hashTree(subject.project)).toBe(first);
105
+ expect(hashTree(subject.awmHome)).toBe(machineBefore);
106
+ }
107
+ finally {
108
+ fs_1.default.rmSync(subject.root, { recursive: true, force: true });
109
+ }
110
+ });
111
+ test('injected manifest publication failure compensates created assets without changing unrelated project or machine bytes', () => {
112
+ const subject = fixture();
113
+ try {
114
+ fs_1.default.rmSync(path_1.default.join(subject.project, '.awm'), { recursive: true, force: true });
115
+ const beforePackage = fs_1.default.readFileSync(path_1.default.join(subject.project, 'package.json'));
116
+ const beforeConfig = fs_1.default.readFileSync(path_1.default.join(subject.project, 'eslint.config.mjs'));
117
+ const beforeMachine = hashTree(subject.awmHome);
118
+ const source = (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'baseline', remote: 'fixture', contentRoot: subject.registryRoot }] });
119
+ (0, safe_file_1.setSecureFsForTests)({
120
+ withProjectLease: native_bridge_1.secureFs.withProjectLease,
121
+ readRegularFile: native_bridge_1.secureFs.readRegularFile,
122
+ writeProjectTransaction: ((root, destination, content, options) => {
123
+ if (destination === '.awm/sensors.json')
124
+ throw new Error('injected rename failure');
125
+ native_bridge_1.secureFs.writeProjectTransaction(root, destination, content, options);
126
+ }),
127
+ removeObservedProjectFile: native_bridge_1.secureFs.removeObservedProjectFile,
128
+ });
129
+ expect(() => (0, materialize_1.materializePortableSensors)({
130
+ projectRoot: subject.project, pack: 'js-ts', source,
131
+ sensors: { lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.fixture.mjs'], initializedCompatibility: { state: 'certified', reason: 'fixture', variantId: 'eslint-10', toolVersion: '10.4.1', runtimeVersion: '24.0.0', certifiedRange: '>=10 <11', evidence: [] } } },
132
+ })).toThrow('injected rename failure');
133
+ expect(fs_1.default.readFileSync(path_1.default.join(subject.project, 'package.json'))).toEqual(beforePackage);
134
+ expect(fs_1.default.readFileSync(path_1.default.join(subject.project, 'eslint.config.mjs'))).toEqual(beforeConfig);
135
+ expect(fs_1.default.existsSync(path_1.default.join(subject.project, 'eslint.fixture.mjs'))).toBe(false);
136
+ expect(fs_1.default.existsSync(path_1.default.join(subject.project, '.awm', 'sensors.json'))).toBe(false);
137
+ expect(hashTree(subject.awmHome)).toBe(beforeMachine);
138
+ expect(fs_1.default.existsSync(path_1.default.join(subject.project, '.awm', 'sensors.json.tmp'))).toBe(false);
139
+ }
140
+ finally {
141
+ (0, safe_file_1.setSecureFsForTests)(undefined);
142
+ fs_1.default.rmSync(subject.root, { recursive: true, force: true });
143
+ }
144
+ });
145
+ test('compiled bootstrap migrates v2 once, preserves all non-manifest project bytes, and then no-ops', () => {
146
+ const subject = fixture();
147
+ try {
148
+ const manifest = path_1.default.join(subject.project, '.awm', 'sensors.json');
149
+ fs_1.default.writeFileSync(manifest, JSON.stringify(v2Manifest(), null, 2) + '\n');
150
+ const machineBefore = hashTree(subject.awmHome);
151
+ const assetBefore = fs_1.default.readFileSync(path_1.default.join(subject.project, 'eslint.config.mjs'));
152
+ expect(run(subject).status).toBe(0);
153
+ const first = hashTree(subject.project);
154
+ const migrated = JSON.parse(fs_1.default.readFileSync(manifest, 'utf8'));
155
+ expect(migrated).toMatchObject({ schemaVersion: 3, mode: 'project-sensors', source: { registry: 'baseline' } });
156
+ expect(migrated).not.toHaveProperty('registryRoot');
157
+ expect(fs_1.default.readFileSync(path_1.default.join(subject.project, 'eslint.config.mjs'))).toEqual(assetBefore);
158
+ expect(run(subject).status).toBe(0);
159
+ expect(hashTree(subject.project)).toBe(first);
160
+ expect(hashTree(subject.awmHome)).toBe(machineBefore);
161
+ }
162
+ finally {
163
+ fs_1.default.rmSync(subject.root, { recursive: true, force: true });
164
+ }
165
+ });
@@ -42,6 +42,9 @@ function createFixture() {
42
42
  fs_1.default.copyFileSync(path_1.default.join(project, 'eslint-tool-package.json'), path_1.default.join(project, 'node_modules', 'eslint', 'package.json'));
43
43
  fs_1.default.mkdirSync(path_1.default.dirname(registryRoot), { recursive: true });
44
44
  fs_1.default.cpSync(registryFixture, registryRoot, { recursive: true });
45
+ // The fixture pack's certified probe requires this selected config to be
46
+ // present before bootstrap can prove compatibility and publish v3.
47
+ fs_1.default.copyFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'eslint.fixture.mjs'), path_1.default.join(project, 'eslint.fixture.mjs'));
45
48
  fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'fixture' }]));
46
49
  return { root, project, awmHome, registryRoot };
47
50
  }
@@ -60,7 +63,8 @@ function runAwm(fixture, ...args) {
60
63
  });
61
64
  }
62
65
  function json(result) {
63
- expect(result.status).toBe(0);
66
+ if (result.status !== 0)
67
+ throw new Error(`CLI failed: ${result.stdout ?? ''}\n${result.stderr ?? ''}`);
64
68
  if (!(result.stdout ?? '').trim())
65
69
  throw new Error(`coverage emitted no JSON: stderr=${result.stderr ?? ''}`);
66
70
  return JSON.parse(result.stdout ?? '');
@@ -116,6 +120,7 @@ test('compiled sensors run returns nonzero while preserving parseable not_certif
116
120
  test('compiled status reports static READY without writing or executing the project sensor (R6, R6.2)', () => {
117
121
  const fixture = createFixture();
118
122
  try {
123
+ fs_1.default.rmSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'));
119
124
  const localBin = path_1.default.join(fixture.project, 'node_modules', '.bin', 'eslint');
120
125
  fs_1.default.mkdirSync(path_1.default.dirname(localBin), { recursive: true });
121
126
  fs_1.default.writeFileSync(localBin, 'this fixture must never be executed by status\n');
@@ -143,10 +148,13 @@ test('legacy coverage stays unverified, init migrates explicitly, and version dr
143
148
  id: 'lint-errors', status: 'unverifiable', detectors: expect.arrayContaining([expect.objectContaining({ compatibility: expect.objectContaining({ state: 'compatible-unverified' }) })]),
144
149
  })]));
145
150
  expect([hashTree(fixture.project), hashTree(fixture.awmHome)]).toEqual(before);
151
+ // v1 declarations are intentionally preserved; creation is explicitly
152
+ // restarted once the owner removes the legacy declaration.
153
+ fs_1.default.rmSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'));
146
154
  const migrated = runCli(fixture, 'init', '--registry-root', fixture.registryRoot, '--pack', 'js-ts', '--no-configure');
147
155
  expect(migrated.status).toBe(0);
148
156
  const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'), 'utf8'));
149
- expect(manifest).toMatchObject({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { variantId: 'eslint-10' } } });
157
+ expect(manifest).toMatchObject({ schemaVersion: 3, mode: 'project-sensors', pack: 'js-ts', source: { registry: 'baseline' }, sensors: { lint: { variantId: 'eslint-10' } } });
150
158
  const certified = json(runCli(fixture, 'coverage', '--json'));
151
159
  expect(certified.static.classes).toEqual(expect.arrayContaining([expect.objectContaining({
152
160
  id: 'lint-errors', status: 'covered', detectors: expect.arrayContaining([expect.objectContaining({ compatibility: expect.objectContaining({ state: 'certified' }) })]),
@@ -107,8 +107,10 @@ test('compiled sensors run materializes a v2 registry command and passes its lit
107
107
  assertSuccessfulCli(initialized, 'init');
108
108
  const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(fixture.project, '.awm', 'sensors.json'), 'utf8'));
109
109
  expect(manifest).toMatchObject({
110
- schemaVersion: 2,
110
+ schemaVersion: 3,
111
+ mode: 'project-sensors',
111
112
  pack: 'fixture',
113
+ source: { registry: 'baseline' },
112
114
  sensors: { structured: { variantId: 'fixture-v1', command: { executable: 'node', resolution: 'path', args: ['fixture-sensor.mjs', fixture.literal] } } },
113
115
  });
114
116
  const result = runCli(fixture, 'run', '--fast');
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const child_process_1 = require("child_process");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const CLI_ROOT = path_1.default.resolve(__dirname, '..', '..');
10
+ const REPO_ROOT = path_1.default.resolve(CLI_ROOT, '..');
11
+ describe('native release artifacts', () => {
12
+ it('keeps native prebuilds generated instead of versioned source', () => {
13
+ const gitignore = fs_1.default.readFileSync(path_1.default.join(REPO_ROOT, '.gitignore'), 'utf8');
14
+ const tracked = (0, child_process_1.execFileSync)('git', ['ls-files', '--', 'cli/prebuilds'], {
15
+ cwd: REPO_ROOT,
16
+ encoding: 'utf8',
17
+ }).trim();
18
+ expect(gitignore).toContain('cli/prebuilds/');
19
+ expect(gitignore).toContain('cli/native-artifacts/');
20
+ expect(tracked).toBe('');
21
+ });
22
+ });
@@ -24,6 +24,7 @@ const CLI_ROOT = path_1.default.resolve(__dirname, '..', '..');
24
24
  const REPO_ROOT = path_1.default.resolve(CLI_ROOT, '..');
25
25
  const RELEASE_ENTRY = path_1.default.join(CLI_ROOT, 'src', 'release', 'index.ts');
26
26
  const RELEASE_WORKFLOW = path_1.default.join(REPO_ROOT, '.github', 'workflows', 'release.yml');
27
+ const CI_WORKFLOW = path_1.default.join(REPO_ROOT, '.github', 'workflows', 'ci.yml');
27
28
  // Flags deliberadamente NO expuestos a CI, con la razón por la que no lo están. Sacar uno
28
29
  // de acá sin exponerlo en el workflow rompe el test a propósito.
29
30
  const NOT_FOR_CI = {
@@ -65,3 +66,12 @@ describe('flags del release: implementados <=> alcanzables desde CI', () => {
65
66
  expect(workflowSource).toMatch(/--force \{0\}/);
66
67
  });
67
68
  });
69
+ describe('matriz de compilacion nativa Windows', () => {
70
+ for (const workflowPath of [CI_WORKFLOW, RELEASE_WORKFLOW]) {
71
+ it(`${path_1.default.basename(workflowPath)} fija x64 en Windows Server 2022 y conserva ARM64 nativo`, () => {
72
+ const source = fs_1.default.readFileSync(workflowPath, 'utf8');
73
+ expect(source).toMatch(/- os: windows-2022\s+target: win32-x64/);
74
+ expect(source).toMatch(/- os: windows-11-arm\s+target: win32-arm64/);
75
+ });
76
+ }
77
+ });
@@ -18,4 +18,8 @@ describe('release registry provenance', () => {
18
18
  .toBeLessThan(workflow.indexOf('- name: Release'));
19
19
  expect(workflow).toMatch(/REGISTRY_TAG: v3\.4\.0[\s\S]*AWM_PUBLISHED_REGISTRY_TAG: v3\.4\.0/);
20
20
  });
21
+ it('normalizes npm pack JSON from both array and object output formats', () => {
22
+ expect(workflow).toContain('Array.isArray(packed) ? packed[0] : Object.values(packed)[0]');
23
+ expect(workflow).toContain('npm pack returned no tarball filename');
24
+ });
21
25
  });
@@ -0,0 +1,44 @@
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 path_1 = __importDefault(require("path"));
8
+ const commander_1 = require("commander");
9
+ jest.mock('@clack/prompts', () => ({ log: { error: jest.fn(), success: jest.fn(), info: jest.fn() } }));
10
+ jest.mock('picocolors', () => ({ green: (value) => value, yellow: (value) => value, red: (value) => value }));
11
+ const sensors_1 = require("../../src/commands/sensors");
12
+ const manifest_1 = require("../../src/commands/sensors/compatibility/manifest");
13
+ const root = path_1.default.resolve(__dirname, '../../..');
14
+ function bootstrapHelp() {
15
+ const program = new commander_1.Command().name('awm');
16
+ (0, sensors_1.registerSensorsCommand)(program);
17
+ const sensors = program.commands.find(command => command.name() === 'sensors');
18
+ return sensors.commands.find(command => command.name() === 'bootstrap').helpInformation();
19
+ }
20
+ describe('Publication B portable bootstrap contract', () => {
21
+ it('exposes the exact explicit bootstrap syntax', () => {
22
+ const help = bootstrapHelp();
23
+ expect(help).toContain('--mode <mode>');
24
+ expect(help).toContain('--reason <text>');
25
+ expect(help).toContain('--dry-run');
26
+ });
27
+ it('documents schema v3, explicit migration, dry-run, and environment separation', () => {
28
+ const reference = fs_1.default.readFileSync(path_1.default.join(root, 'docs/cli-reference.md'), 'utf8');
29
+ expect(reference).toContain('awm sensors bootstrap [--mode project-sensors|native-gate|opt-out] [--reason <text>] [--dry-run]');
30
+ expect(reference).toContain('schemaVersion: 3');
31
+ expect(reference).toContain('never writes');
32
+ expect(reference).toContain('each environment updates its own AWM installation separately');
33
+ });
34
+ it('keeps all documented v3 declarations free of machine registry paths', () => {
35
+ const reference = fs_1.default.readFileSync(path_1.default.join(root, 'docs/cli-reference.md'), 'utf8');
36
+ const examples = Array.from(reference.matchAll(/```json\s*\n([\s\S]*?)```/g), match => JSON.parse(match[1]));
37
+ const v3 = examples.filter(example => example?.schemaVersion === 3);
38
+ expect(v3.length).toBeGreaterThan(0);
39
+ v3.forEach(example => {
40
+ expect(() => (0, manifest_1.parseSensorManifest)(example, 'documented v3 declaration')).not.toThrow();
41
+ expect(JSON.stringify(example)).not.toMatch(/registryRoot|[A-Za-z]:\\\\|\/(?:Users|home|srv)\//);
42
+ });
43
+ });
44
+ });
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.5.0",
3
+ "version": "9.6.1",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
7
7
  },
8
8
  "files": [
9
- "dist"
9
+ "dist",
10
+ "prebuilds"
10
11
  ],
11
12
  "repository": {
12
13
  "type": "git",
@@ -17,6 +18,8 @@
17
18
  "doc": "docs"
18
19
  },
19
20
  "scripts": {
21
+ "native:build": "node-gyp rebuild --directory native && node scripts/package-native.js",
22
+ "native:package-check": "node scripts/assert-native-package.js",
20
23
  "build": "tsc && chmod +x dist/src/index.js",
21
24
  "typecheck": "tsc --noEmit",
22
25
  "lint": "eslint . --config eslint.config.awm.mjs --cache --format json",
@@ -59,6 +62,7 @@
59
62
  "eslint": "10.8.1",
60
63
  "jest": "^30.2.0",
61
64
  "js-yaml": "4.1.0",
65
+ "node-gyp": "^11.5.0",
62
66
  "ts-jest": "^29.4.6",
63
67
  "ts-node": "^10.9.2",
64
68
  "typescript": "^5.9.3"