agentic-workflow-manager 9.21.0 → 9.22.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.
- package/dist/src/commands/change/index.js +16 -5
- package/dist/src/commands/init.js +8 -4
- package/dist/src/commands/preflight/checks.js +22 -0
- package/dist/src/commands/registry/add.js +9 -3
- package/dist/src/commands/sensors/compatibility/contract.js +56 -3
- package/dist/src/commands/sensors/compatibility/manifest.js +16 -2
- package/dist/src/commands/sensors/index.js +10 -1
- package/dist/src/commands/sensors/inputs-digest.js +315 -0
- package/dist/src/commands/sensors/prepare.js +10 -0
- package/dist/src/commands/sensors/result-cache.js +278 -0
- package/dist/src/commands/sensors/result.js +31 -12
- package/dist/src/commands/sensors/run.js +265 -8
- package/dist/src/commands/sensors/status.js +66 -7
- package/dist/src/commands/sync.js +11 -5
- package/dist/src/commands/update.js +23 -12
- package/dist/src/core/changes/derive.js +6 -3
- package/dist/src/core/changes/git.js +1 -2
- package/dist/src/core/changes/receipt.js +219 -9
- package/dist/src/core/changes/worktree.js +15 -3
- package/dist/src/core/init/steps.js +3 -8
- package/dist/src/core/registries.js +20 -15
- package/dist/src/core/registry-git-errors.js +107 -0
- package/dist/src/core/registry-git.js +93 -0
- package/dist/src/core/registry-sync-report.js +74 -0
- package/dist/src/core/versioning.js +3 -6
- package/dist/src/index.js +5 -8
- package/dist/tests/commands/change/new.test.js +104 -58
- package/dist/tests/commands/change/validate-unmeasured.test.js +246 -0
- package/dist/tests/commands/change/validate.test.js +164 -108
- package/dist/tests/commands/changes/list.test.js +41 -22
- package/dist/tests/commands/job/gate-reconcile.test.js +13 -2
- package/dist/tests/commands/preflight/sensors-inputs.test.js +110 -0
- package/dist/tests/commands/registry-sync-honesty.test.js +175 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +60 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +94 -0
- package/dist/tests/commands/sensors/exec.test.js +3 -1
- package/dist/tests/commands/sensors/inputs-digest.test.js +373 -0
- package/dist/tests/commands/sensors/result-cache.test.js +352 -0
- package/dist/tests/commands/sensors/run-cache.test.js +434 -0
- package/dist/tests/commands/sensors/run-slice.test.js +171 -0
- package/dist/tests/commands/sensors/status-inputs.test.js +271 -0
- package/dist/tests/commands/update.test.js +8 -8
- package/dist/tests/commands/watch/supervisor-loop.test.js +21 -5
- package/dist/tests/core/changes/helpers.js +15 -3
- package/dist/tests/core/changes/list-paging.test.js +131 -110
- package/dist/tests/core/changes/list.test.js +133 -83
- package/dist/tests/core/changes/receipt.test.js +279 -8
- package/dist/tests/core/diagnostics/provider-checks.test.js +3 -0
- package/dist/tests/core/init/steps-registry-sync.test.js +6 -8
- package/dist/tests/core/registry-git.test.js +153 -0
- package/dist/tests/helpers/commit-seeded-registry.js +18 -0
- package/dist/tests/integration/codex-provider-isolated.test.js +7 -2
- package/dist/tests/integration/copilot-init-isolated.test.js +7 -4
- package/dist/tests/structural/active-documentation.test.js +0 -41
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +0 -12
- package/dist/tests/structural/cli-reference-matches-commander.test.js +94 -0
- package/dist/tests/structural/native-release-artifacts.test.js +116 -107
- package/dist/tests/structural/sensor-documentation-contract.test.js +0 -47
- package/dist/tests/structural/sensor-portability-contract.test.js +0 -7
- package/dist/tests/structural/simple-git-v4-pin.test.js +49 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
- package/dist/tests/structural/cloud-admission-docs-contract.test.js +0 -54
- package/dist/tests/structural/documentation-phase-is-mechanized.test.js +0 -29
- package/dist/tests/structural/operator-environments-docs-contract.test.js +0 -70
- package/dist/tests/structural/process-model-description-contract.test.js +0 -34
- package/dist/tests/structural/project-declared-sensors-docs-contract.test.js +0 -104
- package/dist/tests/structural/r4-plan-currentness-docs.test.js +0 -37
|
@@ -0,0 +1,271 @@
|
|
|
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 status_1 = require("../../../src/commands/sensors/status");
|
|
10
|
+
/**
|
|
11
|
+
* RF-3.4 — status surfaces a non-blocking warning when a configured enabled
|
|
12
|
+
* sensor lacks effective `inputs` (not cacheable). Warnings must not degrade
|
|
13
|
+
* `overall`, and opt-out / native-gate must stay silent.
|
|
14
|
+
*/
|
|
15
|
+
describe('computeSensorStatus inputs warnings (RF-3.4)', () => {
|
|
16
|
+
let tmpDir;
|
|
17
|
+
let previousHome;
|
|
18
|
+
let home;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-inputs-'));
|
|
21
|
+
home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-inputs-home-'));
|
|
22
|
+
previousHome = process.env.AWM_HOME;
|
|
23
|
+
process.env.AWM_HOME = home;
|
|
24
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([]));
|
|
25
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', '.bin'), { recursive: true });
|
|
26
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', '.bin', 'eslint'), '');
|
|
27
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
if (previousHome === undefined)
|
|
31
|
+
delete process.env.AWM_HOME;
|
|
32
|
+
else
|
|
33
|
+
process.env.AWM_HOME = previousHome;
|
|
34
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
35
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
36
|
+
});
|
|
37
|
+
function writeOnlyProject(sensors) {
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
39
|
+
schemaVersion: 3,
|
|
40
|
+
mode: 'project-sensors',
|
|
41
|
+
sensors,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
function projectSensor(extra = {}) {
|
|
45
|
+
return {
|
|
46
|
+
source: 'project',
|
|
47
|
+
enabled: true,
|
|
48
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
49
|
+
formatter: 'exit-code',
|
|
50
|
+
...extra,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
it('warns when an enabled sensor lacks effective inputs (not cacheable)', async () => {
|
|
54
|
+
writeOnlyProject({ custom: projectSensor() });
|
|
55
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
56
|
+
expect(result.overall).toBe('READY');
|
|
57
|
+
expect(result.warnings).toEqual(expect.arrayContaining([
|
|
58
|
+
expect.objectContaining({
|
|
59
|
+
sensor: 'custom',
|
|
60
|
+
detail: expect.stringMatching(/not cacheable|no effective inputs|lacks inputs/i),
|
|
61
|
+
}),
|
|
62
|
+
]));
|
|
63
|
+
});
|
|
64
|
+
it('does not warn when effective inputs are present', async () => {
|
|
65
|
+
writeOnlyProject({ custom: projectSensor({ inputs: ['**/*.ts'] }) });
|
|
66
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
67
|
+
expect(result.overall).toBe('READY');
|
|
68
|
+
expect(result.warnings ?? []).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
it('does not warn for disabled sensors that lack inputs', async () => {
|
|
71
|
+
writeOnlyProject({ custom: projectSensor({ enabled: false }) });
|
|
72
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
73
|
+
expect(result.warnings ?? []).toEqual([]);
|
|
74
|
+
expect(result.checks.custom).toMatchObject({ ok: true, detail: expect.stringMatching(/disabled/i) });
|
|
75
|
+
});
|
|
76
|
+
it.each([
|
|
77
|
+
['native-gate', 'CI is authoritative.'],
|
|
78
|
+
['opt-out', 'No local gate.'],
|
|
79
|
+
])('emits no spurious warnings for v3 %s', async (mode, reason) => {
|
|
80
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
81
|
+
schemaVersion: 3, mode, reason,
|
|
82
|
+
}));
|
|
83
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
84
|
+
expect(result.mode).toBe(mode);
|
|
85
|
+
expect(result.warnings ?? []).toEqual([]);
|
|
86
|
+
});
|
|
87
|
+
it('uses pack-variant inputs as effective inputs (no warning when only the pack declares them)', async () => {
|
|
88
|
+
const registry = path_1.default.join(home, 'registries', 'baseline');
|
|
89
|
+
fs_1.default.mkdirSync(path_1.default.join(registry, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
90
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([
|
|
91
|
+
{ name: 'baseline', remote: 'https://example.test/baseline.git' },
|
|
92
|
+
]));
|
|
93
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'sensor-packs', 'js-ts', 'pack.json'), JSON.stringify({
|
|
94
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
95
|
+
coverage: {
|
|
96
|
+
schemaVersion: 1,
|
|
97
|
+
classes: {
|
|
98
|
+
lint: {
|
|
99
|
+
description: 'lint',
|
|
100
|
+
detectors: [{ sensor: 'lint' }],
|
|
101
|
+
remedy: { summary: 'fix', command: 'awm sensors init' },
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
sensors: {
|
|
106
|
+
lint: {
|
|
107
|
+
applicability: { allFiles: ['package.json'] },
|
|
108
|
+
variants: [{
|
|
109
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10 <11',
|
|
110
|
+
requirements: {
|
|
111
|
+
tool: 'eslint', toolRange: '>=10 <11',
|
|
112
|
+
runtime: 'node', runtimeRange: '>=0',
|
|
113
|
+
},
|
|
114
|
+
assets: [], formatter: 'eslint-llm',
|
|
115
|
+
probe: { kind: 'package-script-present', script: 'lint' },
|
|
116
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
117
|
+
inputs: ['src/**/*.ts'],
|
|
118
|
+
}],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
}));
|
|
122
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({
|
|
123
|
+
scripts: { lint: 'eslint .' },
|
|
124
|
+
devDependencies: { eslint: '^10.0.0' },
|
|
125
|
+
}));
|
|
126
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
127
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
128
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
129
|
+
schemaVersion: 2, pack: 'js-ts',
|
|
130
|
+
sensors: {
|
|
131
|
+
lint: {
|
|
132
|
+
enabled: true, variantId: 'eslint-10',
|
|
133
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
134
|
+
assets: [],
|
|
135
|
+
initializedCompatibility: {
|
|
136
|
+
state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10',
|
|
137
|
+
toolVersion: '10.0.0', runtimeVersion: process.versions.node,
|
|
138
|
+
certifiedRange: '>=10 <11', evidence: [],
|
|
139
|
+
},
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
144
|
+
expect(result.checks.lint).toMatchObject({ ok: true });
|
|
145
|
+
expect(result.warnings ?? []).toEqual([]);
|
|
146
|
+
});
|
|
147
|
+
it('warns for pack-bound enabled sensors when neither project nor pack variant provides inputs', async () => {
|
|
148
|
+
const registry = path_1.default.join(home, 'registries', 'baseline');
|
|
149
|
+
fs_1.default.mkdirSync(path_1.default.join(registry, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
150
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([
|
|
151
|
+
{ name: 'baseline', remote: 'https://example.test/baseline.git' },
|
|
152
|
+
]));
|
|
153
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'sensor-packs', 'js-ts', 'pack.json'), JSON.stringify({
|
|
154
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
155
|
+
coverage: {
|
|
156
|
+
schemaVersion: 1,
|
|
157
|
+
classes: {
|
|
158
|
+
lint: {
|
|
159
|
+
description: 'lint',
|
|
160
|
+
detectors: [{ sensor: 'lint' }],
|
|
161
|
+
remedy: { summary: 'fix', command: 'awm sensors init' },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
sensors: {
|
|
166
|
+
lint: {
|
|
167
|
+
applicability: { allFiles: ['package.json'] },
|
|
168
|
+
variants: [{
|
|
169
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10 <11',
|
|
170
|
+
requirements: {
|
|
171
|
+
tool: 'eslint', toolRange: '>=10 <11',
|
|
172
|
+
runtime: 'node', runtimeRange: '>=0',
|
|
173
|
+
},
|
|
174
|
+
assets: [], formatter: 'eslint-llm',
|
|
175
|
+
probe: { kind: 'package-script-present', script: 'lint' },
|
|
176
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
177
|
+
}],
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
}));
|
|
181
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({
|
|
182
|
+
scripts: { lint: 'eslint .' },
|
|
183
|
+
devDependencies: { eslint: '^10.0.0' },
|
|
184
|
+
}));
|
|
185
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
186
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
187
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
188
|
+
schemaVersion: 2, pack: 'js-ts',
|
|
189
|
+
sensors: {
|
|
190
|
+
lint: {
|
|
191
|
+
enabled: true, variantId: 'eslint-10',
|
|
192
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
193
|
+
assets: [],
|
|
194
|
+
initializedCompatibility: {
|
|
195
|
+
state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10',
|
|
196
|
+
toolVersion: '10.0.0', runtimeVersion: process.versions.node,
|
|
197
|
+
certifiedRange: '>=10 <11', evidence: [],
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
}));
|
|
202
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
203
|
+
expect(result.overall).toBe('READY');
|
|
204
|
+
expect(result.warnings).toEqual(expect.arrayContaining([
|
|
205
|
+
expect.objectContaining({
|
|
206
|
+
sensor: 'lint',
|
|
207
|
+
detail: expect.stringMatching(/not cacheable|no effective inputs|lacks inputs/i),
|
|
208
|
+
}),
|
|
209
|
+
]));
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('awm sensors status CLI renders inputs warnings (RF-3.4)', () => {
|
|
213
|
+
let tmpDir;
|
|
214
|
+
let previousHome;
|
|
215
|
+
let home;
|
|
216
|
+
let previousCwd;
|
|
217
|
+
beforeEach(() => {
|
|
218
|
+
previousCwd = process.cwd();
|
|
219
|
+
tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-inputs-cli-'));
|
|
220
|
+
home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-inputs-cli-home-'));
|
|
221
|
+
previousHome = process.env.AWM_HOME;
|
|
222
|
+
process.env.AWM_HOME = home;
|
|
223
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([]));
|
|
224
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', '.bin'), { recursive: true });
|
|
225
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', '.bin', 'eslint'), '');
|
|
226
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
227
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
228
|
+
schemaVersion: 3,
|
|
229
|
+
mode: 'project-sensors',
|
|
230
|
+
sensors: {
|
|
231
|
+
custom: {
|
|
232
|
+
source: 'project',
|
|
233
|
+
enabled: true,
|
|
234
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
235
|
+
formatter: 'exit-code',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
}));
|
|
239
|
+
process.chdir(tmpDir);
|
|
240
|
+
});
|
|
241
|
+
afterEach(() => {
|
|
242
|
+
process.chdir(previousCwd);
|
|
243
|
+
if (previousHome === undefined)
|
|
244
|
+
delete process.env.AWM_HOME;
|
|
245
|
+
else
|
|
246
|
+
process.env.AWM_HOME = previousHome;
|
|
247
|
+
fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
|
|
248
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
249
|
+
});
|
|
250
|
+
it('prints warning text in human-readable status output without failing READY', async () => {
|
|
251
|
+
const { Command } = require('commander');
|
|
252
|
+
const { registerSensorsCommand } = require('../../../src/commands/sensors/index');
|
|
253
|
+
const program = new Command();
|
|
254
|
+
program.exitOverride();
|
|
255
|
+
registerSensorsCommand(program);
|
|
256
|
+
const output = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
257
|
+
const exitSpy = jest.spyOn(process, 'exit').mockImplementation((() => undefined));
|
|
258
|
+
try {
|
|
259
|
+
await program.parseAsync(['node', 'awm', 'sensors', 'status']);
|
|
260
|
+
const rendered = output.mock.calls.flat().join('\n');
|
|
261
|
+
expect(rendered).toMatch(/READY/);
|
|
262
|
+
expect(rendered).toMatch(/custom/);
|
|
263
|
+
expect(rendered).toMatch(/not cacheable|no effective inputs|lacks inputs/i);
|
|
264
|
+
expect(exitSpy).not.toHaveBeenCalled();
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
output.mockRestore();
|
|
268
|
+
exitSpy.mockRestore();
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
});
|
|
@@ -164,9 +164,9 @@ describe('awm update — honest outcome', () => {
|
|
|
164
164
|
expect(result.code).toBe(1);
|
|
165
165
|
expect(result.registries.failed).toEqual(['baseline']);
|
|
166
166
|
});
|
|
167
|
-
it('un registry que falló
|
|
168
|
-
//
|
|
169
|
-
//
|
|
167
|
+
it('un registry que falló falla cerrado aunque conserve contenido en disco (SG-7)', async () => {
|
|
168
|
+
// Pre-SG-7 update warn-and-continued on usable cache; #238 SG-7 requires
|
|
169
|
+
// non-zero exit for init/update/sync/registry add on any sync failure.
|
|
170
170
|
const registriesDir = path_1.default.join(process.env.AWM_HOME, 'registries');
|
|
171
171
|
fs_1.default.mkdirSync(path_1.default.join(registriesDir, 'docs', 'skills'), { recursive: true });
|
|
172
172
|
fs_1.default.writeFileSync(path_1.default.join(process.env.AWM_HOME, 'registries.json'), JSON.stringify([{ name: 'docs', remote: 'https://example.test/docs.git' }]));
|
|
@@ -177,12 +177,12 @@ describe('awm update — honest outcome', () => {
|
|
|
177
177
|
{ name: 'docs', action: 'error', error: 'network unreachable' },
|
|
178
178
|
],
|
|
179
179
|
}));
|
|
180
|
-
expect(result.code).toBe(
|
|
180
|
+
expect(result.code).toBe(1);
|
|
181
181
|
expect(result.registries).toEqual({ configured: 2, synced: 1, failed: ['docs'] });
|
|
182
|
-
|
|
183
|
-
expect(
|
|
184
|
-
expect(
|
|
185
|
-
expect(
|
|
182
|
+
expect(updateOutro(result)).toMatch(/Finished with registry sync errors/);
|
|
183
|
+
expect(updateOutro(result)).toMatch(/1\/2/);
|
|
184
|
+
expect(updateOutro(result)).toMatch(/docs/);
|
|
185
|
+
expect(errSpy.mock.calls.flat().join('\n')).toMatch(/docs/);
|
|
186
186
|
});
|
|
187
187
|
});
|
|
188
188
|
describe('awm update — nunca se cuelga esperando a un humano que no está', () => {
|
|
@@ -758,21 +758,37 @@ describe('supervisor loop', () => {
|
|
|
758
758
|
// subagent kept an idle leader while the work ran in processes outside its
|
|
759
759
|
// process group, and the supervisor entered a false custody. Work in the
|
|
760
760
|
// descendant tree is activity: observe, never custody, while it moves.
|
|
761
|
+
// The tree must move deterministically between the two ticks. A `while :; do
|
|
762
|
+
// sleep 0.02; done` loop leaves instants with no `sleep` child, so under CPU
|
|
763
|
+
// load two samples 150 ms apart could read identical (~1% of pairs) and the
|
|
764
|
+
// supervisor entered a real custody: a flaky red on linux-arm64. Each worker
|
|
765
|
+
// child lives 30 s, and the test itself makes the tree grow by one between ticks.
|
|
761
766
|
itPosix('espera sana: lider ocioso + descendiente fuera del grupo trabajando => observa, no custodia (R4.2)', async () => {
|
|
762
767
|
initUnattendedFixture(repo);
|
|
763
768
|
(0, generations_1.beginGeneration)(repo, 'main');
|
|
764
|
-
const
|
|
765
|
-
const
|
|
769
|
+
const trigger = path_1.default.join(repo, 'grow-descendant');
|
|
770
|
+
const worker = [
|
|
771
|
+
"const { spawn } = require('child_process'); const fs = require('fs');",
|
|
772
|
+
"const grow = () => spawn('sleep', ['30'], { detached: true, stdio: 'ignore' }).unref();",
|
|
773
|
+
'grow();',
|
|
774
|
+
'const poll = setInterval(() => { if (fs.existsSync(process.argv[1])) { clearInterval(poll); grow(); } }, 10);',
|
|
775
|
+
'setTimeout(() => {}, 20000);',
|
|
776
|
+
].join(' ');
|
|
777
|
+
const { child, ref } = (0, process_1.spawnStructured)(['node', '-e', worker, trigger], process.cwd(), 'nWait');
|
|
766
778
|
try {
|
|
767
779
|
let s = (0, store_1.readJournal)(repo, 'main').state;
|
|
768
780
|
(0, generations_1.activeGeneration)(s).processRef = ref;
|
|
769
781
|
s.controllerHeartbeatAt = new Date(Date.now() - 3600000).toISOString(); // heartbeat vencido: bloqueado en la espera
|
|
770
782
|
(0, store_1.writeJournal)(repo, 'main', s);
|
|
771
|
-
|
|
783
|
+
const descendants = () => (0, process_1.activitySnapshot)(ref)?.descendants ?? '0:none';
|
|
784
|
+
await until(() => descendants().startsWith('1:'));
|
|
772
785
|
const cfg = { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, provider: 'codex', heartbeatTimeoutMs: 1, activityWindowMs: 50, tickMs: 20 };
|
|
773
786
|
const sup = new supervisor_1.Supervisor(repo, 'main', cfg, fakeSpawner, undefined, admitted);
|
|
774
787
|
await sup.tick();
|
|
775
|
-
|
|
788
|
+
const firstTick = Date.now();
|
|
789
|
+
fs_1.default.writeFileSync(trigger, ''); // el arbol crece: trabajo real fuera del grupo
|
|
790
|
+
await until(() => descendants().startsWith('2:'));
|
|
791
|
+
await new Promise((r) => setTimeout(r, Math.max(0, 150 - (Date.now() - firstTick)))); // mas que la ventana, pero el arbol se movio
|
|
776
792
|
expect(await sup.tick()).not.toBe('custody');
|
|
777
793
|
const after = (0, store_1.readJournal)(repo, 'main').state;
|
|
778
794
|
expect(after.cycle.status).toBe('IN_PROGRESS');
|
|
@@ -780,7 +796,7 @@ describe('supervisor loop', () => {
|
|
|
780
796
|
expect(child.killed).toBe(false);
|
|
781
797
|
}
|
|
782
798
|
finally {
|
|
783
|
-
//
|
|
799
|
+
// Each detached child leads its own group: kill that group, never a pattern match.
|
|
784
800
|
let shells = [];
|
|
785
801
|
try {
|
|
786
802
|
shells = (0, child_process_1.execFileSync)('pgrep', ['-P', String(ref.pid)], { encoding: 'utf8' }).split('\n').filter(Boolean).map(Number);
|
|
@@ -67,19 +67,25 @@ function emptyGitConfig(home) {
|
|
|
67
67
|
fs_1.default.writeFileSync(file, '');
|
|
68
68
|
return file;
|
|
69
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Every test git child: no signing, and no automatic maintenance after a commit. A newer git runs that maintenance detached in
|
|
72
|
+
* the background, where its lock file under `.git/objects` appears and vanishes while a test copies or removes the repository
|
|
73
|
+
* (`cloneRepo` failed with ENOENT on macOS with git 2.55).
|
|
74
|
+
*/
|
|
75
|
+
const GIT_TEST_CONFIG = ['-c', 'commit.gpgsign=false', '-c', 'maintenance.auto=false', '-c', 'gc.auto=0'];
|
|
70
76
|
/** The git runner of a repository: identity and config isolation in an explicit environment, optional explicit dates. */
|
|
71
77
|
function gitApi(root, home) {
|
|
72
78
|
const env = {
|
|
73
79
|
...process.env, HOME: home, GIT_CONFIG_GLOBAL: path_1.default.join(home, 'gitconfig'), GIT_CONFIG_NOSYSTEM: '1',
|
|
74
80
|
GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t',
|
|
75
81
|
};
|
|
76
|
-
const run = (extra, args) => (0, child_process_1.execFileSync)('git', [
|
|
82
|
+
const run = (extra, args) => (0, child_process_1.execFileSync)('git', [...GIT_TEST_CONFIG, ...args], { cwd: root, encoding: 'utf8', env: { ...env, ...extra } }).trim();
|
|
77
83
|
const dates = (date) => {
|
|
78
84
|
const stamp = `${Math.floor(Date.parse(date) / 1000)} +0000`;
|
|
79
85
|
return { GIT_AUTHOR_DATE: stamp, GIT_COMMITTER_DATE: stamp };
|
|
80
86
|
};
|
|
81
87
|
const git = (...args) => run({}, args);
|
|
82
|
-
const gitInput = (input, ...args) => (0, child_process_1.execFileSync)('git', [
|
|
88
|
+
const gitInput = (input, ...args) => (0, child_process_1.execFileSync)('git', [...GIT_TEST_CONFIG, ...args], { cwd: root, encoding: 'utf8', env, input }).trim();
|
|
83
89
|
const gitAt = (date, ...args) => run(dates(date), args);
|
|
84
90
|
const commit = (files, message, date) => {
|
|
85
91
|
for (const [rel, content] of Object.entries(files)) {
|
|
@@ -144,7 +150,13 @@ Object.defineProperty(exports, "posixOnly", { enumerable: true, get: function ()
|
|
|
144
150
|
*/
|
|
145
151
|
function cloneRepo(template) {
|
|
146
152
|
const root = fs_1.default.realpathSync.native(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-chg-')));
|
|
147
|
-
|
|
153
|
+
try {
|
|
154
|
+
fs_1.default.cpSync(template.root, root, { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
158
|
+
throw error;
|
|
159
|
+
}
|
|
148
160
|
const { git, gitInput, gitAt, commit } = gitApi(root, template.home);
|
|
149
161
|
return { root, home: template.home, git, gitInput, gitAt, commit, cleanup: () => fs_1.default.rmSync(root, { recursive: true, force: true }) };
|
|
150
162
|
}
|