agentic-workflow-manager 9.7.0 → 9.8.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.
- package/README.md +5 -1
- package/dist/src/commands/doctor.js +4 -2
- package/dist/src/commands/export.js +3 -0
- package/dist/src/commands/init.js +10 -2
- package/dist/src/commands/job/exec-wrapper.js +53 -3
- package/dist/src/commands/plan/index.js +124 -6
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +7 -5
- package/dist/src/commands/registry/install-bundles.js +4 -4
- package/dist/src/commands/registry/status.js +4 -4
- package/dist/src/commands/sensors/bootstrap.js +36 -5
- package/dist/src/commands/sensors/compatibility/contract.js +19 -4
- package/dist/src/commands/sensors/compatibility/discovery.js +23 -3
- package/dist/src/commands/sensors/compatibility/live.js +18 -1
- package/dist/src/commands/sensors/compatibility/probe.js +8 -2
- package/dist/src/commands/sensors/compatibility/resolve.js +5 -2
- package/dist/src/commands/sensors/coverage/evidence.js +19 -10
- package/dist/src/commands/sensors/coverage/index.js +11 -2
- package/dist/src/commands/sensors/coverage/resolve.js +10 -0
- package/dist/src/commands/sensors/exec.js +16 -1
- package/dist/src/commands/sensors/migrate.js +62 -2
- package/dist/src/commands/sensors/prepare.js +2 -0
- package/dist/src/commands/sensors/result.js +2 -0
- package/dist/src/commands/sensors/run.js +64 -1
- package/dist/src/commands/sensors/status.js +6 -4
- package/dist/src/commands/sync.js +2 -1
- package/dist/src/commands/update.js +3 -1
- package/dist/src/commands/watch/archive-unused.js +138 -0
- package/dist/src/commands/watch/generations.js +32 -9
- package/dist/src/commands/watch/index.js +108 -2
- package/dist/src/commands/watch/init.js +149 -18
- package/dist/src/commands/watch/supervisor.js +167 -13
- package/dist/src/commands/watch/teardown-driver.js +13 -13
- package/dist/src/commands/watch/track-state.js +59 -0
- package/dist/src/commands/watch/tracks.js +38 -102
- package/dist/src/core/admission/index.js +181 -0
- package/dist/src/core/admission/registry-contracts.js +290 -0
- package/dist/src/core/bundle-install.js +2 -2
- package/dist/src/core/bundles.js +92 -19
- package/dist/src/core/diagnostics/context.js +1 -1
- package/dist/src/core/export/index.js +1 -1
- package/dist/src/core/export/resolve.js +2 -2
- package/dist/src/core/journal/fingerprint.js +35 -1
- package/dist/src/core/journal/paths.js +11 -0
- package/dist/src/core/journal/store.js +146 -4
- package/dist/src/core/journal/types.js +35 -1
- package/dist/src/core/migration/historical-provenance.js +18 -0
- package/dist/src/core/migration/index.js +322 -0
- package/dist/src/core/plan/identity.js +75 -0
- package/dist/src/core/plan/validate.js +178 -64
- package/dist/src/core/reconciliation.js +1 -1
- package/dist/src/index.js +46 -39
- package/dist/src/utils/grouping.js +1 -1
- package/dist/src/utils/registry-view.js +1 -1
- package/dist/tests/commands/bundle-diagnostics.test.js +145 -0
- package/dist/tests/commands/export.test.js +28 -2
- package/dist/tests/commands/job/exec-wrapper.test.js +16 -0
- package/dist/tests/commands/job/gate-reconcile.test.js +24 -10
- package/dist/tests/commands/multi-agent-targeting.test.js +1 -1
- package/dist/tests/commands/pin.test.js +23 -23
- package/dist/tests/commands/plan/index.test.js +630 -14
- package/dist/tests/commands/sensors/bootstrap.test.js +57 -4
- package/dist/tests/commands/sensors/compatibility/contract.test.js +21 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +19 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +14 -1
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +17 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +51 -27
- package/dist/tests/commands/sensors/coverage/resolve.test.js +51 -0
- package/dist/tests/commands/sensors/exec.test.js +112 -0
- package/dist/tests/commands/sensors/run-changed.test.js +11 -0
- package/dist/tests/commands/sensors/run.test.js +15 -2
- package/dist/tests/commands/sensors/status.test.js +3 -3
- package/dist/tests/commands/watch/e2e-crash.test.js +126 -11
- package/dist/tests/commands/watch/generations.test.js +113 -4
- package/dist/tests/commands/watch/plan-lifecycle.test.js +263 -0
- package/dist/tests/commands/watch/supervisor-loop.test.js +247 -49
- package/dist/tests/commands/watch/track-finalize.test.js +20 -3
- package/dist/tests/commands/watch/track-freeze.test.js +43 -30
- package/dist/tests/commands/watch/track-state-dependency.test.js +15 -0
- package/dist/tests/commands/watch/watch-init.test.js +244 -0
- package/dist/tests/core/admission/index.test.js +87 -0
- package/dist/tests/core/bundle-install.test.js +36 -0
- package/dist/tests/core/bundles-multiroot.test.js +24 -2
- package/dist/tests/core/bundles-overrides.test.js +10 -7
- package/dist/tests/core/bundles.test.js +75 -6
- package/dist/tests/core/diagnostics/context.test.js +2 -2
- package/dist/tests/core/export/engine.test.js +1 -1
- package/dist/tests/core/export/resolve.test.js +1 -1
- package/dist/tests/core/init/mutation-targets.test.js +1 -1
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/steps.test.js +1 -1
- package/dist/tests/core/journal/fingerprint.test.js +22 -0
- package/dist/tests/core/journal/store.test.js +53 -0
- package/dist/tests/core/journal/types.test.js +7 -0
- package/dist/tests/core/migration/facts.test.js +289 -0
- package/dist/tests/core/migration/historical-provenance.test.js +25 -0
- package/dist/tests/core/plan/validate.test.js +320 -6
- package/dist/tests/core/reconciliation.test.js +1 -1
- package/dist/tests/core/sync-profile-atomicity.test.js +1 -1
- package/dist/tests/integration/pack-e2e.test.js +4 -1
- package/dist/tests/integration/preflight-json-pipe.e2e.test.js +7 -3
- package/dist/tests/integration/sensor-bootstrap.e2e.test.js +37 -0
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +12 -10
- package/dist/tests/providers/injection-config.test.js +14 -6
- package/dist/tests/structural/bundle-skill-reference-contract.test.js +56 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +29 -24
- package/dist/tests/structural/native-release-artifacts.test.js +16 -0
- package/dist/tests/utils/grouping.test.js +2 -2
- package/dist/tests/utils/registry-view.test.js +7 -7
- package/package.json +2 -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
|
@@ -0,0 +1,263 @@
|
|
|
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 child_process_1 = require("child_process");
|
|
9
|
+
const init_1 = require("../../../src/commands/watch/init");
|
|
10
|
+
const store_1 = require("../../../src/core/journal/store");
|
|
11
|
+
const paths_1 = require("../../../src/core/journal/paths");
|
|
12
|
+
const validate_1 = require("../../../src/core/plan/validate");
|
|
13
|
+
const git_fixture_1 = require("../../helpers/git-fixture");
|
|
14
|
+
const generations_1 = require("../../../src/commands/watch/generations");
|
|
15
|
+
const requests_1 = require("../../../src/core/journal/requests");
|
|
16
|
+
const request_1 = require("../../../src/commands/job/request");
|
|
17
|
+
const fingerprint_1 = require("../../../src/core/journal/fingerprint");
|
|
18
|
+
const supervisor_1 = require("../../../src/commands/watch/supervisor");
|
|
19
|
+
const exec_wrapper_1 = require("../../../src/commands/job/exec-wrapper");
|
|
20
|
+
const lock_1 = require("../../../src/commands/watch/lock");
|
|
21
|
+
const archive_unused_1 = require("../../../src/commands/watch/archive-unused");
|
|
22
|
+
jest.setTimeout(30000);
|
|
23
|
+
function valid(repo) {
|
|
24
|
+
const report = (0, validate_1.validatePlanFile)('docs/plan.md', repo);
|
|
25
|
+
if (report.state !== 'valid')
|
|
26
|
+
throw new Error(JSON.stringify(report));
|
|
27
|
+
return report;
|
|
28
|
+
}
|
|
29
|
+
function cli(repo, ...args) {
|
|
30
|
+
return (0, child_process_1.spawnSync)(process.execPath, [path_1.default.resolve(__dirname, '../../../dist/src/index.js'), ...args], {
|
|
31
|
+
cwd: repo, encoding: 'utf8', env: { ...process.env, HOME: repo, AWM_HOME: path_1.default.join(repo, 'awm-home'), AWM_NO_UPDATE_CHECK: '1' },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
describe('compact lifecycle proof and unused bootstrap archival', () => {
|
|
35
|
+
let repo;
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
repo = (0, git_fixture_1.initRepo)();
|
|
38
|
+
(0, git_fixture_1.commitFile)(repo, 'source.md', '## Canonical source\nfixture source\n');
|
|
39
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, 'docs'));
|
|
40
|
+
fs_1.default.mkdirSync(path_1.default.join(repo, '.awm'));
|
|
41
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, '.awm/sensors.json'), '{}');
|
|
42
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'package.json'), JSON.stringify({ scripts: { test: 'node -e "process.exit(0)"' } }));
|
|
43
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), `${fs_1.default.readFileSync(path_1.default.join(__dirname, '../../core/plan/fixtures/compact-slices-v1/valid.md'), 'utf8')}\n- [ ] Reviewed task.\n`);
|
|
44
|
+
(0, init_1.initWatch)(repo, 'main', { path: 'docs/plan.md', report: valid(repo) });
|
|
45
|
+
});
|
|
46
|
+
afterEach(() => { fs_1.default.rmSync(repo, { recursive: true, force: true }); });
|
|
47
|
+
test('durable journal retains hash commitments only, never the validated plan source body', () => {
|
|
48
|
+
const contents = (directory) => fs_1.default.readdirSync(directory, { withFileTypes: true }).map(entry => entry.isDirectory() ? contents(path_1.default.join(directory, entry.name)) : fs_1.default.readFileSync(path_1.default.join(directory, entry.name), 'utf8')).join('\n');
|
|
49
|
+
expect(contents((0, paths_1.journalDir)(repo, 'main'))).not.toContain('Reviewed task.');
|
|
50
|
+
expect((0, store_1.readJournal)(repo, 'main').state.planBinding).toMatchObject({ executionIdentitySchema: 'awm-plan-execution/v1', executionDigest: expect.stringMatching(/^[a-f0-9]{64}$/) });
|
|
51
|
+
});
|
|
52
|
+
test.each(['malformed', 'oversized', 'deep'])('CLI init rejects %s verifier inputs before mutating gitignore or journal', (kind) => {
|
|
53
|
+
const fresh = (0, git_fixture_1.initRepo)();
|
|
54
|
+
try {
|
|
55
|
+
(0, git_fixture_1.commitFile)(fresh, 'source.md', '## Canonical source\nfixture source\n');
|
|
56
|
+
fs_1.default.mkdirSync(path_1.default.join(fresh, 'docs'));
|
|
57
|
+
fs_1.default.copyFileSync(path_1.default.join(repo, 'docs/plan.md'), path_1.default.join(fresh, 'docs/plan.md'));
|
|
58
|
+
fs_1.default.writeFileSync(path_1.default.join(fresh, '.gitignore'), 'user-owned-ignore\n');
|
|
59
|
+
if (kind === 'malformed')
|
|
60
|
+
fs_1.default.writeFileSync(path_1.default.join(fresh, 'package.json'), '{invalid package');
|
|
61
|
+
if (kind === 'oversized')
|
|
62
|
+
fs_1.default.writeFileSync(path_1.default.join(fresh, 'package.json'), JSON.stringify({ padding: 'x'.repeat(256 * 1024) }));
|
|
63
|
+
if (kind === 'deep') {
|
|
64
|
+
let cursor = fresh;
|
|
65
|
+
for (let i = 0; i < 65; i++) {
|
|
66
|
+
cursor = path_1.default.join(cursor, 'p');
|
|
67
|
+
fs_1.default.mkdirSync(cursor);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const before = fs_1.default.readFileSync(path_1.default.join(fresh, '.gitignore'), 'utf8');
|
|
71
|
+
const rejected = cli(fresh, 'watch', '--init', '--plan', 'docs/plan.md');
|
|
72
|
+
expect(rejected.status).not.toBe(0);
|
|
73
|
+
expect(rejected.stderr).toMatch(/verifier scan/);
|
|
74
|
+
expect(fs_1.default.readFileSync(path_1.default.join(fresh, '.gitignore'), 'utf8')).toBe(before);
|
|
75
|
+
expect(fs_1.default.existsSync((0, paths_1.statePath)(fresh, 'main'))).toBe(false);
|
|
76
|
+
expect(JSON.parse(cli(fresh, 'watch', 'journal-status', '--json').stdout).state).toBe('missing');
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
fs_1.default.rmSync(fresh, { recursive: true, force: true });
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
test('CLI init rejects an existing linked journal segment before mutating gitignore', () => {
|
|
83
|
+
const fresh = (0, git_fixture_1.initRepo)();
|
|
84
|
+
try {
|
|
85
|
+
(0, git_fixture_1.commitFile)(fresh, 'source.md', '## Canonical source\nfixture source\n');
|
|
86
|
+
fs_1.default.mkdirSync(path_1.default.join(fresh, 'docs'));
|
|
87
|
+
fs_1.default.copyFileSync(path_1.default.join(repo, 'docs/plan.md'), path_1.default.join(fresh, 'docs/plan.md'));
|
|
88
|
+
fs_1.default.writeFileSync(path_1.default.join(fresh, '.gitignore'), 'user-owned-ignore\n');
|
|
89
|
+
fs_1.default.mkdirSync((0, paths_1.journalDir)(fresh, 'main'), { recursive: true });
|
|
90
|
+
fs_1.default.symlinkSync(path_1.default.join(fresh, 'docs'), (0, paths_1.requestsDir)(fresh, 'main'), 'junction');
|
|
91
|
+
const before = fs_1.default.readFileSync(path_1.default.join(fresh, '.gitignore'), 'utf8');
|
|
92
|
+
const rejected = cli(fresh, 'watch', '--init', '--plan', 'docs/plan.md');
|
|
93
|
+
expect(rejected.status).not.toBe(0);
|
|
94
|
+
expect(rejected.stderr).toMatch(/symlink/);
|
|
95
|
+
expect(fs_1.default.readFileSync(path_1.default.join(fresh, '.gitignore'), 'utf8')).toBe(before);
|
|
96
|
+
expect(fs_1.default.existsSync((0, paths_1.statePath)(fresh, 'main'))).toBe(false);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
fs_1.default.rmSync(fresh, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
test('CLI reconciles proved progress while IN_PROGRESS without fabricating completion', () => {
|
|
103
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), fs_1.default.readFileSync(path_1.default.join(repo, 'docs/plan.md'), 'utf8')
|
|
104
|
+
.replace('- [ ] Reviewed task.', '- [x] Reviewed task.')
|
|
105
|
+
.replace('#### Evidence', '<!-- awm-qa-complete: 2026-09-16 -->\n#### Evidence'));
|
|
106
|
+
const result = cli(repo, 'watch', 'rebind', '--plan', 'docs/plan.md');
|
|
107
|
+
expect(result.stderr).toBe('');
|
|
108
|
+
expect(result.status).toBe(0);
|
|
109
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
110
|
+
expect(state.cycle.status).toBe('IN_PROGRESS');
|
|
111
|
+
expect(state.cycle.completedAt).toBeUndefined();
|
|
112
|
+
expect(state.planBinding.digest).toBe(valid(repo).planDigest);
|
|
113
|
+
expect(state.planBindingHistory).toHaveLength(1);
|
|
114
|
+
expect(state.tasks).toEqual([]);
|
|
115
|
+
expect(state.verdicts).toEqual([]);
|
|
116
|
+
});
|
|
117
|
+
test.each([
|
|
118
|
+
['source-scoped evidence', ['source.md'], 'complete'],
|
|
119
|
+
['plan-inclusive evidence', [], 'custody'],
|
|
120
|
+
])('real execution after CLI rebind: %s (%s) reaches %s without a COMPLETE fixture', async (_label, paths, expectedOutcome) => {
|
|
121
|
+
const generation = (0, generations_1.beginGeneration)(repo, 'main');
|
|
122
|
+
const supervisor = new supervisor_1.Supervisor(repo, 'main', { ...supervisor_1.DEFAULT_SUPERVISOR_CONFIG, tickMs: 10 }, (job, nonce, logsRoot, repoRoot) => {
|
|
123
|
+
// The external controller is the test itself; actual verification
|
|
124
|
+
// jobs still execute through the real wrapper and durable results.
|
|
125
|
+
if (job.argv[0] === 'codex')
|
|
126
|
+
return;
|
|
127
|
+
void (0, exec_wrapper_1.runExecWrapper)({ logsRoot, jobId: job.id, nonce, argv: job.argv, cwd: job.cwd, repoRoot });
|
|
128
|
+
}, undefined, async () => ({ state: 'admitted', planState: 'valid', executionMode: 'desatendido', journal: 'current', currentness: 'current', sensors: 'pass', diagnostics: [] }));
|
|
129
|
+
const emit = (id, payload) => (0, requests_1.emitRequest)(repo, 'main', { kind: 'register-entity', generationToken: generation.token, idempotencyKey: id, payload });
|
|
130
|
+
emit('task', { entity: 'task', taskId: 'T1', title: 'Actual verification', verificationPlan: [{ id: 'tests', kind: 'test' }, { id: 'sensors', kind: 'sensors' }], reviewObligations: [{ id: 'spec', kind: 'spec' }, { id: 'quality', kind: 'quality' }] });
|
|
131
|
+
emit('cycle', { entity: 'cycle-plan', items: [{ id: 'qa', kind: 'qa' }, { id: 'interlock', kind: 'interlock' }] });
|
|
132
|
+
for (const id of ['tests', 'sensors'])
|
|
133
|
+
(0, request_1.requestJob)(repo, 'main', generation.token, [process.execPath, '-e', 'process.exit(0)'], paths, '.', { satisfies: id });
|
|
134
|
+
for (const obligationId of ['spec', 'quality']) {
|
|
135
|
+
const argv = ['awm-review', obligationId];
|
|
136
|
+
(0, requests_1.emitRequest)(repo, 'main', { kind: 'verdict', generationToken: generation.token, idempotencyKey: obligationId, payload: {
|
|
137
|
+
verdictId: obligationId, obligationId, result: 'pass', detail: 'Real controller observation',
|
|
138
|
+
fingerprint: (0, fingerprint_1.computeFingerprint)(repo, argv, ['source.md'], '.').fingerprint, argv, paths: ['source.md'], cwd: '.',
|
|
139
|
+
} });
|
|
140
|
+
}
|
|
141
|
+
for (let i = 0; i < 100; i++) {
|
|
142
|
+
expect(await supervisor.tick()).toBe('continue');
|
|
143
|
+
if (Object.values((0, store_1.readJournal)(repo, 'main').state.jobs).some(job => job.verdict === 'pass'))
|
|
144
|
+
break;
|
|
145
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
146
|
+
}
|
|
147
|
+
expect(Object.values((0, store_1.readJournal)(repo, 'main').state.jobs).every(job => job.executionState === 'exited' && job.verdict === 'pass')).toBe(true);
|
|
148
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), fs_1.default.readFileSync(path_1.default.join(repo, 'docs/plan.md'), 'utf8')
|
|
149
|
+
.replace('- [ ] Reviewed task.', '- [x] Reviewed task.')
|
|
150
|
+
.replace('#### Evidence', '<!-- awm-qa-complete: 2026-09-16 -->\n<!-- awm-docs-complete: 2026-09-16 -->\n<!-- awm-retro-complete: 2026-09-16 -->\n#### Evidence'));
|
|
151
|
+
const rebound = cli(repo, 'watch', 'rebind', '--plan', 'docs/plan.md');
|
|
152
|
+
expect(rebound.stderr).toBe('');
|
|
153
|
+
expect(rebound.status).toBe(0);
|
|
154
|
+
if (expectedOutcome === 'custody')
|
|
155
|
+
expect(rebound.stdout).toMatch(/stale-fingerprint.*job request/);
|
|
156
|
+
for (const id of ['qa', 'interlock'])
|
|
157
|
+
(0, request_1.requestJob)(repo, 'main', generation.token, [process.execPath, '-e', 'process.exit(0)'], ['source.md'], '.', { satisfies: id });
|
|
158
|
+
emit('done', { entity: 'task-status', taskId: 'T1', status: 'done' });
|
|
159
|
+
let outcome = 'continue';
|
|
160
|
+
for (let i = 0; i < 100 && outcome === 'continue'; i++) {
|
|
161
|
+
outcome = await supervisor.tick();
|
|
162
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
163
|
+
}
|
|
164
|
+
expect(outcome).toBe(expectedOutcome);
|
|
165
|
+
const final = (0, store_1.readJournal)(repo, 'main').state;
|
|
166
|
+
expect(final.cycle.status).toBe(expectedOutcome === 'complete' ? 'COMPLETE' : 'BLOCKED');
|
|
167
|
+
if (expectedOutcome === 'complete')
|
|
168
|
+
expect(final.cycle.completedAt).toEqual(expect.any(String));
|
|
169
|
+
else {
|
|
170
|
+
expect(final.cycle.completedAt).toBeUndefined();
|
|
171
|
+
expect(final.cycle.blockedReason).toMatch(/stale-fingerprint.*job request/);
|
|
172
|
+
}
|
|
173
|
+
expect(final.tasks[0].status).toBe('done');
|
|
174
|
+
expect(final.verdicts).toHaveLength(2);
|
|
175
|
+
expect(Object.values(final.jobs).every(job => job.executionState === 'exited' && job.verdict === 'pass')).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
test('public current-branch status distinguishes missing, corrupt and unused without exporting bodies', () => {
|
|
178
|
+
const present = cli(repo, 'watch', 'journal-status', '--json');
|
|
179
|
+
expect(present.status).toBe(0);
|
|
180
|
+
expect(JSON.parse(present.stdout)).toMatchObject({ state: 'present', cycleState: 'IN_PROGRESS', bootstrapUnused: true });
|
|
181
|
+
expect(present.stdout).not.toContain('Reviewed task.');
|
|
182
|
+
const state = (0, paths_1.statePath)(repo, 'main');
|
|
183
|
+
const before = fs_1.default.readFileSync(state, 'utf8');
|
|
184
|
+
fs_1.default.writeFileSync(state, '{corrupt');
|
|
185
|
+
expect(JSON.parse(cli(repo, 'watch', 'journal-status', '--json').stdout)).toEqual({ state: 'corrupt', bootstrapUnused: false });
|
|
186
|
+
fs_1.default.writeFileSync(state, before);
|
|
187
|
+
expect(cli(repo, 'watch', 'archive-unused', '--plan', 'docs/plan.md').status).toBe(0);
|
|
188
|
+
expect(JSON.parse(cli(repo, 'watch', 'journal-status', '--json').stdout)).toEqual({ state: 'missing', bootstrapUnused: false });
|
|
189
|
+
});
|
|
190
|
+
test.each([
|
|
191
|
+
['requirements', (text) => text.replace(/RF-1\.3/g, 'RF-1.4')],
|
|
192
|
+
['argv', (text) => text.replace('"test"', '"build"')],
|
|
193
|
+
['source fact', (text) => text.replace('intentionally small and stable', 'changed fact')],
|
|
194
|
+
['prose', (text) => text.replace('only surfaces', 'changed surfaces')],
|
|
195
|
+
['mode', (text) => text.replace('desatendido', 'interactivo')],
|
|
196
|
+
])('rejects %s change and preserves the entire prior journal', (_label, edit) => {
|
|
197
|
+
fs_1.default.writeFileSync(path_1.default.join(repo, 'docs/plan.md'), edit(fs_1.default.readFileSync(path_1.default.join(repo, 'docs/plan.md'), 'utf8')));
|
|
198
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
199
|
+
expect(() => (0, init_1.rebindWatchPlan)(repo, 'main', { path: 'docs/plan.md', report: valid(repo) })).toThrow();
|
|
200
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
201
|
+
});
|
|
202
|
+
test('archives unused bootstrap recoverably via CLI without COMPLETE or cycle evidence', () => {
|
|
203
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
204
|
+
const result = cli(repo, 'watch', 'archive-unused', '--plan', 'docs/plan.md');
|
|
205
|
+
expect(result.stderr).toBe('');
|
|
206
|
+
expect(result.status).toBe(0);
|
|
207
|
+
const archived = JSON.parse(result.stdout).archived;
|
|
208
|
+
expect(fs_1.default.readFileSync(path_1.default.join(repo, archived, 'state.json'), 'utf8')).toBe(before);
|
|
209
|
+
expect(JSON.parse(before).cycle.status).toBe('IN_PROGRESS');
|
|
210
|
+
expect(fs_1.default.existsSync((0, paths_1.statePath)(repo, 'main'))).toBe(false);
|
|
211
|
+
expect(fs_1.default.existsSync(path_1.default.join(repo, '.awm/evidence'))).toBe(false);
|
|
212
|
+
expect(fs_1.default.existsSync((0, paths_1.supervisorLockPath)(repo))).toBe(false);
|
|
213
|
+
});
|
|
214
|
+
test('archive refuses one durable task and leaves source journal intact', () => {
|
|
215
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
216
|
+
state.tasks.push({ id: 'T1', title: 'Actual work', status: 'pending', attempts: 0, verificationPlan: [], reviewObligations: [] });
|
|
217
|
+
(0, store_1.writeJournal)(repo, 'main', state);
|
|
218
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
219
|
+
const result = cli(repo, 'watch', 'archive-unused', '--plan', 'docs/plan.md');
|
|
220
|
+
expect(result.status).not.toBe(0);
|
|
221
|
+
expect(result.stderr).toMatch(/unused|bootstrap|utilizado/i);
|
|
222
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
223
|
+
});
|
|
224
|
+
test('archive refuses a pending request or a symlink artifact', () => {
|
|
225
|
+
fs_1.default.writeFileSync(path_1.default.join((0, paths_1.requestsDir)(repo, 'main'), 'pending.json'), '{}');
|
|
226
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
227
|
+
const result = cli(repo, 'watch', 'archive-unused', '--plan', 'docs/plan.md');
|
|
228
|
+
expect(result.status).not.toBe(0);
|
|
229
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
230
|
+
fs_1.default.rmSync(path_1.default.join((0, paths_1.requestsDir)(repo, 'main'), 'pending.json'));
|
|
231
|
+
fs_1.default.symlinkSync(path_1.default.join(repo, 'source.md'), path_1.default.join((0, paths_1.journalDir)(repo, 'main'), 'outside'));
|
|
232
|
+
const linked = cli(repo, 'watch', 'archive-unused', '--plan', 'docs/plan.md');
|
|
233
|
+
expect(linked.status).not.toBe(0);
|
|
234
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
235
|
+
});
|
|
236
|
+
test('archive obeys the real supervisor lock and rejects unknown optional runtime state', () => {
|
|
237
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
238
|
+
const lock = (0, lock_1.acquireLock)(repo);
|
|
239
|
+
try {
|
|
240
|
+
expect(() => (0, archive_unused_1.archiveUnusedWatch)(repo, 'main', 'docs/plan.md')).toThrow(/supervisor activo/i);
|
|
241
|
+
}
|
|
242
|
+
finally {
|
|
243
|
+
(0, lock_1.releaseLock)(repo, lock);
|
|
244
|
+
}
|
|
245
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
246
|
+
const state = (0, store_1.readJournal)(repo, 'main').state;
|
|
247
|
+
state.controllerHeartbeatAt = new Date().toISOString();
|
|
248
|
+
(0, store_1.writeJournal)(repo, 'main', state);
|
|
249
|
+
expect(() => (0, archive_unused_1.archiveUnusedWatch)(repo, 'main', 'docs/plan.md')).toThrow(/no reconocido|unused/i);
|
|
250
|
+
});
|
|
251
|
+
test('a rename failure preserves active journal and releases its lock', () => {
|
|
252
|
+
const before = (0, store_1.readJournal)(repo, 'main').raw;
|
|
253
|
+
const rename = jest.spyOn(fs_1.default, 'renameSync').mockImplementation(() => { throw new Error('injected rename failure'); });
|
|
254
|
+
try {
|
|
255
|
+
expect(() => (0, archive_unused_1.archiveUnusedWatch)(repo, 'main', 'docs/plan.md')).toThrow('injected rename failure');
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
rename.mockRestore();
|
|
259
|
+
}
|
|
260
|
+
expect((0, store_1.readJournal)(repo, 'main').raw).toBe(before);
|
|
261
|
+
expect(fs_1.default.existsSync((0, paths_1.supervisorLockPath)(repo))).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
});
|