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
|
@@ -8,9 +8,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
function writeBundleRoot(root, bundleName, skillName) {
|
|
11
|
+
const canonicalSkillName = typeof skillName === 'string' ? skillName : skillName.name;
|
|
11
12
|
fs_1.default.mkdirSync(path_1.default.join(root, 'bundles', bundleName), { recursive: true });
|
|
12
|
-
fs_1.default.mkdirSync(path_1.default.join(root, 'skills',
|
|
13
|
-
fs_1.default.writeFileSync(path_1.default.join(root, 'skills',
|
|
13
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills', canonicalSkillName), { recursive: true });
|
|
14
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills', canonicalSkillName, 'SKILL.md'), `---\nname: ${canonicalSkillName}\ndescription: d\n---\n`);
|
|
14
15
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles', bundleName, 'bundle.json'), JSON.stringify({ name: bundleName, version: '1.0.0', scope: 'ambient', skills: [skillName] }));
|
|
15
16
|
fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
|
|
16
17
|
version: 1,
|
|
@@ -38,6 +39,27 @@ describe('bundles multi-root', () => {
|
|
|
38
39
|
expect(all.map((b) => b.name).sort()).toEqual(['dev-x', 'personal-x']);
|
|
39
40
|
expect(all.find((b) => b.name === 'personal-x').contentRoot).toBe(rootB);
|
|
40
41
|
});
|
|
42
|
+
it('composes one legacy migration diagnostic for each affected manifest', () => {
|
|
43
|
+
writeBundleRoot(rootA, 'dev-x', { name: 'sx', onSignal: true });
|
|
44
|
+
writeBundleRoot(rootB, 'personal-x', { name: 'px', onSignal: false });
|
|
45
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
46
|
+
const result = inspectAllBundles([rootA, rootB]);
|
|
47
|
+
expect(result.bundles.map((b) => b.skills)).toEqual([['sx'], ['px']]);
|
|
48
|
+
expect(result.diagnostics).toHaveLength(2);
|
|
49
|
+
expect(new Set(result.diagnostics).size).toBe(2);
|
|
50
|
+
});
|
|
51
|
+
it('keeps diagnostics distinct when manifest identities share a truncated prefix', () => {
|
|
52
|
+
const sharedPrefix = path_1.default.join(tmp, ...Array.from({ length: 30 }, () => 'shared-prefix'));
|
|
53
|
+
const longRootA = path_1.default.join(sharedPrefix, 'registry-a');
|
|
54
|
+
const longRootB = path_1.default.join(sharedPrefix, 'registry-b');
|
|
55
|
+
writeBundleRoot(longRootA, 'legacy-a', { name: 'sa', onSignal: true });
|
|
56
|
+
writeBundleRoot(longRootB, 'legacy-b', { name: 'sb', onSignal: false });
|
|
57
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
58
|
+
const result = inspectAllBundles([longRootA, longRootB]);
|
|
59
|
+
expect(result.diagnostics).toHaveLength(2);
|
|
60
|
+
expect(result.diagnostics.every((diagnostic) => diagnostic.length <= 512)).toBe(true);
|
|
61
|
+
expect(result.diagnostics[0]).not.toBe(result.diagnostics[1]);
|
|
62
|
+
});
|
|
41
63
|
it('discoverAllBundles throws naming both sources on bundle name collision', () => {
|
|
42
64
|
writeBundleRoot(rootA, 'dup', 's1');
|
|
43
65
|
writeBundleRoot(rootB, 'dup', 's2');
|
|
@@ -7,9 +7,10 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
function writeBundleRoot(root, bundleName, skillName) {
|
|
10
|
+
const canonicalSkillName = typeof skillName === 'string' ? skillName : skillName.name;
|
|
10
11
|
fs_1.default.mkdirSync(path_1.default.join(root, 'bundles', bundleName), { recursive: true });
|
|
11
|
-
fs_1.default.mkdirSync(path_1.default.join(root, 'skills',
|
|
12
|
-
fs_1.default.writeFileSync(path_1.default.join(root, 'skills',
|
|
12
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills', canonicalSkillName), { recursive: true });
|
|
13
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills', canonicalSkillName, 'SKILL.md'), `---\nname: ${canonicalSkillName}\ndescription: d\n---\n`);
|
|
13
14
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles', bundleName, 'bundle.json'), JSON.stringify({ name: bundleName, version: '1.0.0', scope: 'ambient', skills: [skillName] }));
|
|
14
15
|
fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
|
|
15
16
|
version: 1,
|
|
@@ -33,11 +34,13 @@ describe('bundle override resolution', () => {
|
|
|
33
34
|
writeBundleRoot(rootA, 'pack', 's1');
|
|
34
35
|
writeBundleRoot(rootB, 'pack', 's2');
|
|
35
36
|
fs_1.default.writeFileSync(path_1.default.join(rootB, 'awm-registry.json'), JSON.stringify({ overrides: ['pack'] }));
|
|
36
|
-
const {
|
|
37
|
-
const
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
40
|
-
expect(
|
|
37
|
+
const { inspectAllBundles } = require('../../src/core/bundles');
|
|
38
|
+
const result = inspectAllBundles([rootA, rootB]);
|
|
39
|
+
expect(result.bundles).toHaveLength(1);
|
|
40
|
+
expect(result.bundles[0].contentRoot).toBe(rootB);
|
|
41
|
+
expect(result.bundles[0].overrode).toBe(rootA);
|
|
42
|
+
expect(result.bundles[0].skills).toEqual(['s2']);
|
|
43
|
+
expect(result.diagnostics).toHaveLength(0);
|
|
41
44
|
});
|
|
42
45
|
it('undeclared collision still throws naming both sources', () => {
|
|
43
46
|
writeBundleRoot(rootA, 'dup', 's1');
|
|
@@ -21,7 +21,7 @@ function makeFixture() {
|
|
|
21
21
|
}));
|
|
22
22
|
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles', 'dev', 'bundle.json'), JSON.stringify({
|
|
23
23
|
name: 'dev', version: '1.0.0', description: 'Dev core', scope: 'baseline', dependsOn: [],
|
|
24
|
-
skills: ['brainstorming',
|
|
24
|
+
skills: ['brainstorming', 'architecture-advisor'],
|
|
25
25
|
workflows: ['development-process'], agents: ['development-process'],
|
|
26
26
|
}));
|
|
27
27
|
fs_1.default.writeFileSync(path_1.default.join(content, 'bundles', 'frontend', 'bundle.json'), JSON.stringify({
|
|
@@ -47,17 +47,86 @@ describe('readCatalog', () => {
|
|
|
47
47
|
});
|
|
48
48
|
});
|
|
49
49
|
describe('discoverBundles', () => {
|
|
50
|
-
it('loads each bundle
|
|
50
|
+
it('loads each bundle with canonical skill names', () => {
|
|
51
51
|
const content = makeFixture();
|
|
52
52
|
const bundles = (0, bundles_1.discoverBundles)(content);
|
|
53
53
|
const dev = bundles.find((b) => b.name === 'dev');
|
|
54
|
-
expect(dev.skills).toEqual([
|
|
55
|
-
{ name: 'brainstorming', onSignal: false },
|
|
56
|
-
{ name: 'architecture-advisor', onSignal: true },
|
|
57
|
-
]);
|
|
54
|
+
expect(dev.skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
58
55
|
expect(dev.scope).toBe('baseline');
|
|
59
56
|
expect(dev.dependsOn).toEqual([]);
|
|
60
57
|
});
|
|
58
|
+
it('canonicalizes legacy skill objects and reports one bounded migration diagnostic per manifest', () => {
|
|
59
|
+
const content = makeFixture();
|
|
60
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
61
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify({
|
|
62
|
+
name: 'dev', version: '1.0.0', scope: 'baseline',
|
|
63
|
+
skills: [
|
|
64
|
+
{ name: 'brainstorming', onSignal: true },
|
|
65
|
+
{ name: 'architecture-advisor', onSignal: false },
|
|
66
|
+
],
|
|
67
|
+
}));
|
|
68
|
+
const { inspectBundles } = require('../../src/core/bundles');
|
|
69
|
+
const result = inspectBundles(content);
|
|
70
|
+
expect(result.bundles[0].skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
71
|
+
expect(result.diagnostics).toHaveLength(1);
|
|
72
|
+
expect(result.diagnostics[0]).toContain('bundle.json');
|
|
73
|
+
expect(result.diagnostics[0]).toContain('skills: ["skill-name"]');
|
|
74
|
+
expect(result.diagnostics[0]).toMatch(/v10/i);
|
|
75
|
+
expect(result.diagnostics[0]).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
|
|
76
|
+
expect(result.diagnostics[0]).not.toContain('{"name"');
|
|
77
|
+
expect(result.diagnostics[0].length).toBeLessThanOrEqual(512);
|
|
78
|
+
});
|
|
79
|
+
it('forwards one deduplicated legacy diagnostic to an injected reporter', () => {
|
|
80
|
+
const content = makeFixture();
|
|
81
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
82
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify({
|
|
83
|
+
name: 'dev', version: '1.0.0', scope: 'baseline',
|
|
84
|
+
skills: [{ name: 'brainstorming', onSignal: true }, { name: 'architecture-advisor', onSignal: false }],
|
|
85
|
+
}));
|
|
86
|
+
const diagnostics = [];
|
|
87
|
+
const bundles = (0, bundles_1.discoverBundles)(content, (diagnostic) => diagnostics.push(diagnostic));
|
|
88
|
+
expect(bundles[0].skills).toEqual(['brainstorming', 'architecture-advisor']);
|
|
89
|
+
expect(diagnostics).toHaveLength(1);
|
|
90
|
+
});
|
|
91
|
+
it('creates a command-scoped warning reporter that suppresses repeated diagnostics', () => {
|
|
92
|
+
const warnings = [];
|
|
93
|
+
const report = (0, bundles_1.createBundleDiagnosticReporter)((warning) => warnings.push(warning));
|
|
94
|
+
report('legacy warning');
|
|
95
|
+
report('legacy warning');
|
|
96
|
+
expect(warnings).toEqual(['warning: legacy warning']);
|
|
97
|
+
});
|
|
98
|
+
it('sanitizes, bounds, and then deduplicates caller-provided diagnostics', () => {
|
|
99
|
+
const warnings = [];
|
|
100
|
+
const report = (0, bundles_1.createBundleDiagnosticReporter)((warning) => warnings.push(warning));
|
|
101
|
+
const payload = `legacy\x1b\n${'x'.repeat(600)}`;
|
|
102
|
+
report(payload);
|
|
103
|
+
report(`legacy ${'x'.repeat(600)}`);
|
|
104
|
+
expect(warnings).toHaveLength(1);
|
|
105
|
+
expect(warnings[0]).toMatch(/^warning: legacy /);
|
|
106
|
+
expect(warnings[0]).not.toMatch(/[\x00-\x1f\x7f-\x9f]/);
|
|
107
|
+
expect(warnings[0].length).toBeLessThanOrEqual('warning: '.length + 512);
|
|
108
|
+
});
|
|
109
|
+
it.each([
|
|
110
|
+
['missing skills', undefined, /skills/],
|
|
111
|
+
['a non-array skills value', 'brainstorming', /skills/],
|
|
112
|
+
['an empty string skill', [''], /skills\[0\]/],
|
|
113
|
+
['an array skill', [['brainstorming']], /skills\[0\]/],
|
|
114
|
+
['a null skill', [null], /skills\[0\]/],
|
|
115
|
+
['an object missing name', [{ onSignal: true }], /skills\[0\]/],
|
|
116
|
+
['an object with an empty name', [{ name: '', onSignal: true }], /skills\[0\]/],
|
|
117
|
+
['an object with a non-string name', [{ name: 1, onSignal: true }], /skills\[0\]/],
|
|
118
|
+
['an object with a non-boolean onSignal', [{ name: 'brainstorming', onSignal: 'true' }], /skills\[0\]/],
|
|
119
|
+
['an object with an unknown key', [{ name: 'brainstorming', extra: true }], /skills\[0\]/],
|
|
120
|
+
])('rejects %s with a safe manifest identity', (_case, skills, location) => {
|
|
121
|
+
const content = makeFixture();
|
|
122
|
+
const manifest = path_1.default.join(content, 'bundles', 'dev', 'bundle.json');
|
|
123
|
+
const bundle = { name: 'dev', version: '1.0.0', scope: 'baseline' };
|
|
124
|
+
if (skills !== undefined)
|
|
125
|
+
bundle.skills = skills;
|
|
126
|
+
fs_1.default.writeFileSync(manifest, JSON.stringify(bundle));
|
|
127
|
+
expect(() => (0, bundles_1.discoverBundles)(content)).toThrow(/bundle\.json/);
|
|
128
|
+
expect(() => (0, bundles_1.discoverBundles)(content)).toThrow(location);
|
|
129
|
+
});
|
|
61
130
|
it('returns [] when catalog is missing', () => {
|
|
62
131
|
const empty = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-'));
|
|
63
132
|
expect((0, bundles_1.discoverBundles)(empty)).toEqual([]);
|
|
@@ -9,7 +9,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
9
9
|
function bundle(name, scope, skills) {
|
|
10
10
|
return {
|
|
11
11
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
12
|
-
dependsOn: [], skills
|
|
12
|
+
dependsOn: [], skills,
|
|
13
13
|
workflows: [], agents: [],
|
|
14
14
|
};
|
|
15
15
|
}
|
|
@@ -86,7 +86,7 @@ describe('gatherContext', () => {
|
|
|
86
86
|
it('machine: devCore surfaces a missing per-agent artifact even when the shared skill link is already present', () => {
|
|
87
87
|
const bundleWithAgent = () => ({
|
|
88
88
|
name: 'dev-core', description: '', version: '1.0.0', scope: 'baseline', visibility: 'public',
|
|
89
|
-
dependsOn: [], skills: [
|
|
89
|
+
dependsOn: [], skills: ['development-process'],
|
|
90
90
|
workflows: [], agents: ['development-process'],
|
|
91
91
|
});
|
|
92
92
|
// Simulate OpenCode having already linked the shared ~/.agents/skills dir.
|
|
@@ -21,7 +21,7 @@ function makeRoot() {
|
|
|
21
21
|
}));
|
|
22
22
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev/bundle.json'), JSON.stringify({
|
|
23
23
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
24
|
-
skills: ['proc-skill',
|
|
24
|
+
skills: ['proc-skill', 'mermaid', 'ported'],
|
|
25
25
|
workflows: [], agents: [],
|
|
26
26
|
}));
|
|
27
27
|
const mk = (name, fm) => {
|
|
@@ -17,7 +17,7 @@ function makeRoot() {
|
|
|
17
17
|
}));
|
|
18
18
|
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev/bundle.json'), JSON.stringify({
|
|
19
19
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
20
|
-
skills: ['proc-skill',
|
|
20
|
+
skills: ['proc-skill', 'mermaid', 'ported'],
|
|
21
21
|
workflows: [], agents: [],
|
|
22
22
|
}));
|
|
23
23
|
const mk = (name, fm) => {
|
|
@@ -23,7 +23,7 @@ const tmp_1 = require("../../support/tmp");
|
|
|
23
23
|
function bundle(name, scope, skills) {
|
|
24
24
|
return {
|
|
25
25
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
26
|
-
dependsOn: [], skills
|
|
26
|
+
dependsOn: [], skills,
|
|
27
27
|
workflows: [], agents: [],
|
|
28
28
|
};
|
|
29
29
|
}
|
|
@@ -31,7 +31,7 @@ function seedRegistry(contentRoot) {
|
|
|
31
31
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'bundles', 'dev'), { recursive: true });
|
|
32
32
|
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'bundles', 'dev', 'bundle.json'), JSON.stringify({
|
|
33
33
|
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
34
|
-
skills: [
|
|
34
|
+
skills: ['brainstorming', 'using-awm'], workflows: [], agents: [],
|
|
35
35
|
}));
|
|
36
36
|
// Seed .git in the content root so gatherMachine.registryCache.present is true.
|
|
37
37
|
fs_1.default.mkdirSync(path_1.default.join(contentRoot, '.git'), { recursive: true });
|
|
@@ -12,7 +12,7 @@ const context_1 = require("../../../src/core/diagnostics/context");
|
|
|
12
12
|
function bundle(name, scope, skills) {
|
|
13
13
|
return {
|
|
14
14
|
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
15
|
-
dependsOn: [], skills
|
|
15
|
+
dependsOn: [], skills,
|
|
16
16
|
workflows: [], agents: [],
|
|
17
17
|
};
|
|
18
18
|
}
|
|
@@ -8,6 +8,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
10
|
const fingerprint_1 = require("../../../src/core/journal/fingerprint");
|
|
11
|
+
const types_1 = require("../../../src/core/journal/types");
|
|
11
12
|
function git(cwd, ...args) {
|
|
12
13
|
(0, child_process_1.execFileSync)('git', ['-c', 'user.email=t@t.t', '-c', 'user.name=t', '-c', 'commit.gpgsign=false', ...args], { cwd });
|
|
13
14
|
}
|
|
@@ -96,6 +97,27 @@ describe('computeFingerprint', () => {
|
|
|
96
97
|
expect(() => (0, fingerprint_1.computeFingerprint)(repo, ['npm', 'test'], [], '.')).not.toThrow();
|
|
97
98
|
});
|
|
98
99
|
});
|
|
100
|
+
describe('reconcileUnattendedRecovery', () => {
|
|
101
|
+
test.each([
|
|
102
|
+
['journal', { journal: null, journalCorrupt: true }, 'repair-journal'],
|
|
103
|
+
['binding', { plan: { path: 'other.md' } }, 'rebind-plan'],
|
|
104
|
+
['git', { git: 'changed' }, 'reconcile-git'],
|
|
105
|
+
['verdicts', { verdicts: 'missing' }, 'repair-verdicts'],
|
|
106
|
+
['tests', { tests: 'fail' }, 'run-tests'],
|
|
107
|
+
['sensors', { sensors: 'missing' }, 'run-sensors'],
|
|
108
|
+
['ready', {}, 'select-work'],
|
|
109
|
+
])('covers recovery action %s', (_name, overrides, nextAction) => {
|
|
110
|
+
const journal = { ...(0, types_1.emptyState)('main'), schema: 2, planBinding: { path: 'docs/plan.md', digest: 'a'.repeat(64), schema: 'compact-slices/v1', executionMode: 'desatendido', boundAt: '2026-09-15T00:00:00.000Z' } };
|
|
111
|
+
const input = { journal, journalCorrupt: false, plan: journal.planBinding, git: 'current', activeJobIds: [], tests: 'pass', sensors: 'pass', verdicts: 'current', ...overrides };
|
|
112
|
+
expect((0, fingerprint_1.reconcileUnattendedRecovery)(input).nextAction).toBe(nextAction);
|
|
113
|
+
});
|
|
114
|
+
test('reuses active obligations before later verdict repair', () => {
|
|
115
|
+
const journal = { ...(0, types_1.emptyState)('main'), schema: 2, planBinding: { path: 'docs/plan.md', digest: 'a'.repeat(64), schema: 'compact-slices/v1', executionMode: 'desatendido', boundAt: '2026-09-15T00:00:00.000Z' } };
|
|
116
|
+
const input = { journal, journalCorrupt: false, plan: journal.planBinding, git: 'current', activeJobIds: ['job-b', 'job-a', 'job-a'], tests: 'pass', sensors: 'pass', verdicts: 'current' };
|
|
117
|
+
expect((0, fingerprint_1.reconcileUnattendedRecovery)(input)).toEqual({ state: 'ready', nextAction: 'reconcile-active-jobs', activeJobIds: ['job-a', 'job-b'], diagnostics: [] });
|
|
118
|
+
expect((0, fingerprint_1.reconcileUnattendedRecovery)({ ...input, verdicts: 'missing' })).toEqual({ state: 'ready', nextAction: 'reconcile-active-jobs', activeJobIds: ['job-a', 'job-b'], diagnostics: [] });
|
|
119
|
+
});
|
|
120
|
+
});
|
|
99
121
|
/** Defense-in-depth (post-implementation-qa, follow-up a Task 20): el `git()`
|
|
100
122
|
* interno de este archivo backea `computeFingerprint`, invocado en CADA tick
|
|
101
123
|
* del supervisor via `FingerprintNow`/`computeGate` (job/gate.ts). Sin stdio
|
|
@@ -27,6 +27,11 @@ describe('journal store', () => {
|
|
|
27
27
|
expect(r.corrupt).toBe(false);
|
|
28
28
|
expect(r.state.revision).toBe(0);
|
|
29
29
|
});
|
|
30
|
+
test('initJournal es idempotente cuando el arbol de journal ya existe', () => {
|
|
31
|
+
(0, store_1.initJournal)(repo, 'rama');
|
|
32
|
+
expect(() => (0, store_1.initJournal)(repo, 'rama')).not.toThrow();
|
|
33
|
+
expect((0, store_1.readJournal)(repo, 'rama')).toMatchObject({ corrupt: false, state: { revision: 0 } });
|
|
34
|
+
});
|
|
30
35
|
test('writeJournal incrementa revision y rechaza revision vieja (R1.2)', () => {
|
|
31
36
|
(0, store_1.initJournal)(repo, 'rama');
|
|
32
37
|
const s1 = (0, store_1.readJournal)(repo, 'rama').state;
|
|
@@ -51,6 +56,46 @@ describe('journal store', () => {
|
|
|
51
56
|
fs_1.default.writeFileSync((0, paths_1.statePath)(repo, 'rama'), '{roto'); // sintaxis invalida
|
|
52
57
|
expect((0, store_1.readJournal)(repo, 'rama').corrupt).toBe(true);
|
|
53
58
|
});
|
|
59
|
+
test('rechaza un state.json symlink sin leer el destino externo', () => {
|
|
60
|
+
(0, store_1.initJournal)(repo, 'rama');
|
|
61
|
+
const outside = path_1.default.join(repo, 'outside.json');
|
|
62
|
+
fs_1.default.writeFileSync(outside, fs_1.default.readFileSync((0, paths_1.statePath)(repo, 'rama')));
|
|
63
|
+
fs_1.default.rmSync((0, paths_1.statePath)(repo, 'rama'));
|
|
64
|
+
fs_1.default.symlinkSync(outside, (0, paths_1.statePath)(repo, 'rama'));
|
|
65
|
+
expect((0, store_1.readJournal)(repo, 'rama')).toMatchObject({ state: null, corrupt: true });
|
|
66
|
+
});
|
|
67
|
+
test('initJournal rechaza un padre .awm symlink sin crear journal fuera del repo', () => {
|
|
68
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-store-outside-'));
|
|
69
|
+
try {
|
|
70
|
+
fs_1.default.symlinkSync(outside, path_1.default.join(repo, '.awm'));
|
|
71
|
+
expect(() => (0, store_1.initJournal)(repo, 'rama')).toThrow(/symlink/i);
|
|
72
|
+
expect(fs_1.default.existsSync(path_1.default.join(outside, 'journal'))).toBe(false);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
test.each([
|
|
79
|
+
['.awm', (root) => path_1.default.join(root, '.awm')],
|
|
80
|
+
['journal', (root) => path_1.default.join(root, '.awm', 'journal')],
|
|
81
|
+
['branch', (root) => (0, paths_1.journalDir)(root, 'rama')],
|
|
82
|
+
['requests', (root) => (0, paths_1.requestsDir)(root, 'rama')],
|
|
83
|
+
['acks', (root) => (0, paths_1.acksDir)(root, 'rama')],
|
|
84
|
+
['logs', (root) => (0, paths_1.logsDir)(root, 'rama')],
|
|
85
|
+
['export', (root) => (0, paths_1.exportDir)(root, 'rama')],
|
|
86
|
+
])('initJournal rechaza el segmento symlink %s', (_name, segment) => {
|
|
87
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-store-outside-'));
|
|
88
|
+
const target = segment(repo);
|
|
89
|
+
try {
|
|
90
|
+
fs_1.default.mkdirSync(path_1.default.dirname(target), { recursive: true });
|
|
91
|
+
fs_1.default.symlinkSync(outside, target);
|
|
92
|
+
expect(() => (0, store_1.initJournal)(repo, 'rama')).toThrow(/symlink/i);
|
|
93
|
+
expect(fs_1.default.readdirSync(outside)).toEqual([]);
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
54
99
|
test('normaliza campos aditivos de snapshots schema 1 sin certificar evidencia legacy', () => {
|
|
55
100
|
(0, store_1.initJournal)(repo, 'rama');
|
|
56
101
|
const legacy = (0, store_1.readJournal)(repo, 'rama').state;
|
|
@@ -74,6 +119,14 @@ describe('journal store', () => {
|
|
|
74
119
|
expect(JSON.parse(lines[0]).kind).toBe('generation-launched');
|
|
75
120
|
expect(typeof JSON.parse(lines[0]).at).toBe('string');
|
|
76
121
|
});
|
|
122
|
+
test('appendEvent no sigue un events.jsonl symlink externo', () => {
|
|
123
|
+
(0, store_1.initJournal)(repo, 'rama');
|
|
124
|
+
const outside = path_1.default.join(repo, 'outside-events.jsonl');
|
|
125
|
+
fs_1.default.writeFileSync(outside, 'outside\n');
|
|
126
|
+
fs_1.default.symlinkSync(outside, (0, paths_1.eventsPath)(repo, 'rama'));
|
|
127
|
+
(0, store_1.appendEvent)(repo, 'rama', { kind: 'must-not-escape' });
|
|
128
|
+
expect(fs_1.default.readFileSync(outside, 'utf8')).toBe('outside\n');
|
|
129
|
+
});
|
|
77
130
|
test('writeJournal rechaza un estado propuesto con forma invalida, nunca lo persiste (R1.6)', () => {
|
|
78
131
|
(0, store_1.initJournal)(repo, 'rama');
|
|
79
132
|
const s = (0, store_1.readJournal)(repo, 'rama').state;
|
|
@@ -22,6 +22,13 @@ describe('journal types', () => {
|
|
|
22
22
|
expect(s.requiredVerifiers).toEqual([]);
|
|
23
23
|
expect(s.dispatches).toEqual([]);
|
|
24
24
|
});
|
|
25
|
+
test('binding metadata rejects persisted plan bodies and unversioned execution commitments', () => {
|
|
26
|
+
const state = { ...(0, types_1.emptyState)('main'), schema: 2, planBinding: { path: 'docs/plan.md', digest: 'a'.repeat(64), schema: 'compact-slices/v1', executionMode: 'desatendido', boundAt: '2026-09-16' } };
|
|
27
|
+
expect((0, types_1.isWellFormedState)(state)).toBe(true);
|
|
28
|
+
expect((0, types_1.isWellFormedState)({ ...state, planBinding: { ...state.planBinding, snapshot: 'private source body' } })).toBe(false);
|
|
29
|
+
expect((0, types_1.isWellFormedState)({ ...state, planBinding: { ...state.planBinding, executionDigest: 'b'.repeat(64) } })).toBe(false);
|
|
30
|
+
expect((0, types_1.isWellFormedState)({ ...state, planBinding: { ...state.planBinding, executionDigest: 'b'.repeat(64), executionIdentitySchema: 'awm-plan-execution/v1' } })).toBe(true);
|
|
31
|
+
});
|
|
25
32
|
test('isWellFormedState rechaza no-objetos y shapes rotos (R1.6)', () => {
|
|
26
33
|
expect((0, types_1.isWellFormedState)(null)).toBe(false);
|
|
27
34
|
expect((0, types_1.isWellFormedState)(42)).toBe(false);
|