agentic-workflow-manager 2.0.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 +62 -0
- package/dist/src/commands/doctor.js +77 -0
- package/dist/src/commands/hooks/index.js +118 -0
- package/dist/src/commands/hooks/install.js +113 -0
- package/dist/src/commands/hooks/resync.js +50 -0
- package/dist/src/commands/hooks/status.js +74 -0
- package/dist/src/commands/hooks/uninstall.js +59 -0
- package/dist/src/commands/init.js +181 -0
- package/dist/src/commands/ledger/index.js +73 -0
- package/dist/src/commands/pin.js +75 -0
- package/dist/src/commands/registry/add.js +62 -0
- package/dist/src/commands/registry/index.js +166 -0
- package/dist/src/commands/registry/install-bundles.js +36 -0
- package/dist/src/commands/registry/remove.js +18 -0
- package/dist/src/commands/registry/status.js +31 -0
- package/dist/src/commands/sensors/baseline.js +93 -0
- package/dist/src/commands/sensors/formatters/eslint.js +29 -0
- package/dist/src/commands/sensors/formatters/generic.js +8 -0
- package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
- package/dist/src/commands/sensors/formatters/test.js +12 -0
- package/dist/src/commands/sensors/formatters/tsc.js +23 -0
- package/dist/src/commands/sensors/index.js +94 -0
- package/dist/src/commands/sensors/init.js +112 -0
- package/dist/src/commands/sensors/install.js +78 -0
- package/dist/src/commands/sensors/run.js +217 -0
- package/dist/src/commands/sensors/status.js +85 -0
- package/dist/src/commands/sensors/types.js +2 -0
- package/dist/src/core/bundle-install.js +116 -0
- package/dist/src/core/bundles.js +122 -0
- package/dist/src/core/cli-version.js +30 -0
- package/dist/src/core/context/materializer.js +30 -0
- package/dist/src/core/context/orchestrator.js +75 -0
- package/dist/src/core/context/project-constitution-inject.js +47 -0
- package/dist/src/core/context/provider.js +29 -0
- package/dist/src/core/context/regenerate.js +61 -0
- package/dist/src/core/context/strategies/config-instructions.js +73 -0
- package/dist/src/core/context/strategies/hook-merge.js +23 -0
- package/dist/src/core/context/strategies/strategy.js +2 -0
- package/dist/src/core/context/types.js +2 -0
- package/dist/src/core/diagnostics/checks.js +141 -0
- package/dist/src/core/diagnostics/context.js +181 -0
- package/dist/src/core/diagnostics/types.js +2 -0
- package/dist/src/core/discovery.js +135 -0
- package/dist/src/core/executor.js +41 -0
- package/dist/src/core/init/detector.js +67 -0
- package/dist/src/core/init/orchestrator.js +54 -0
- package/dist/src/core/init/steps.js +279 -0
- package/dist/src/core/init/types.js +2 -0
- package/dist/src/core/ledger/store.js +73 -0
- package/dist/src/core/ledger/types.js +2 -0
- package/dist/src/core/miro.js +314 -0
- package/dist/src/core/profile-pins.js +36 -0
- package/dist/src/core/profile.js +146 -0
- package/dist/src/core/registries.js +205 -0
- package/dist/src/core/registry.js +28 -0
- package/dist/src/core/skill-integrity.js +97 -0
- package/dist/src/core/story-map-parser.js +83 -0
- package/dist/src/core/update-check-worker.js +10 -0
- package/dist/src/core/update-check.js +106 -0
- package/dist/src/core/versioning.js +112 -0
- package/dist/src/index.js +689 -0
- package/dist/src/providers/index.js +63 -0
- package/dist/src/utils/config.js +35 -0
- package/dist/src/utils/grouping.js +51 -0
- package/dist/src/utils/registry-view.js +154 -0
- package/dist/tests/commands/doctor.test.js +98 -0
- package/dist/tests/commands/hooks/install.test.js +149 -0
- package/dist/tests/commands/hooks/resync.test.js +135 -0
- package/dist/tests/commands/hooks/router.test.js +26 -0
- package/dist/tests/commands/hooks/status.test.js +88 -0
- package/dist/tests/commands/hooks/uninstall.test.js +104 -0
- package/dist/tests/commands/init.test.js +87 -0
- package/dist/tests/commands/ledger/index.test.js +64 -0
- package/dist/tests/commands/pin.test.js +72 -0
- package/dist/tests/commands/registry/add.test.js +223 -0
- package/dist/tests/commands/registry/install-bundles.test.js +87 -0
- package/dist/tests/commands/registry/remove.test.js +49 -0
- package/dist/tests/commands/registry/status.test.js +43 -0
- package/dist/tests/commands/sensors/baseline.test.js +106 -0
- package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
- package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
- package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
- package/dist/tests/commands/sensors/index.test.js +18 -0
- package/dist/tests/commands/sensors/init.test.js +137 -0
- package/dist/tests/commands/sensors/install.test.js +60 -0
- package/dist/tests/commands/sensors/router.test.js +23 -0
- package/dist/tests/commands/sensors/run.test.js +353 -0
- package/dist/tests/commands/sensors/status.test.js +98 -0
- package/dist/tests/core/base-remote.test.js +70 -0
- package/dist/tests/core/bundle-install.test.js +198 -0
- package/dist/tests/core/bundles-multiroot.test.js +68 -0
- package/dist/tests/core/bundles-overrides.test.js +49 -0
- package/dist/tests/core/bundles.test.js +96 -0
- package/dist/tests/core/cli-version.test.js +17 -0
- package/dist/tests/core/context/materializer.test.js +46 -0
- package/dist/tests/core/context/orchestrator.test.js +127 -0
- package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
- package/dist/tests/core/context/provider.test.js +45 -0
- package/dist/tests/core/context/regenerate.test.js +106 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
- package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
- package/dist/tests/core/context/types.test.js +15 -0
- package/dist/tests/core/diagnostics/checks.test.js +208 -0
- package/dist/tests/core/diagnostics/context.test.js +170 -0
- package/dist/tests/core/discovery-multiroot.test.js +63 -0
- package/dist/tests/core/discovery-overrides.test.js +105 -0
- package/dist/tests/core/discovery.test.js +113 -0
- package/dist/tests/core/executor.test.js +44 -0
- package/dist/tests/core/init/detector.test.js +56 -0
- package/dist/tests/core/init/orchestrator.test.js +122 -0
- package/dist/tests/core/init/steps.test.js +363 -0
- package/dist/tests/core/ledger/gitignore.test.js +13 -0
- package/dist/tests/core/ledger/store.test.js +122 -0
- package/dist/tests/core/miro-layout.test.js +150 -0
- package/dist/tests/core/profile-pins.test.js +131 -0
- package/dist/tests/core/profile-registries.test.js +59 -0
- package/dist/tests/core/profile.test.js +110 -0
- package/dist/tests/core/registries-capability.test.js +52 -0
- package/dist/tests/core/registries-seed.test.js +66 -0
- package/dist/tests/core/registries-sync.test.js +205 -0
- package/dist/tests/core/registries.test.js +91 -0
- package/dist/tests/core/registry-manifest.test.js +95 -0
- package/dist/tests/core/registry-versioned-sync.test.js +113 -0
- package/dist/tests/core/registry.test.js +51 -0
- package/dist/tests/core/skill-integrity.test.js +126 -0
- package/dist/tests/core/story-map-parser.test.js +136 -0
- package/dist/tests/core/sync-gates.test.js +97 -0
- package/dist/tests/core/update-check.test.js +106 -0
- package/dist/tests/core/versioning.test.js +169 -0
- package/dist/tests/integration/pack-e2e.test.js +50 -0
- package/dist/tests/providers/hooks-config.test.js +45 -0
- package/dist/tests/providers/index.test.js +58 -0
- package/dist/tests/providers/injection-config.test.js +25 -0
- package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
- package/dist/tests/registry/catalog-consistency.test.js +47 -0
- package/dist/tests/registry/design-skills.test.js +146 -0
- package/dist/tests/registry/prose-agnostic.test.js +18 -0
- package/dist/tests/registry/sensor-packs.test.js +45 -0
- package/dist/tests/registry/skill-versions.test.js +26 -0
- package/dist/tests/registry/using-awm.test.js +39 -0
- package/dist/tests/utils/config.test.js +31 -0
- package/dist/tests/utils/grouping.test.js +43 -0
- package/dist/tests/utils/registry-view-overrides.test.js +41 -0
- package/dist/tests/utils/registry-view.test.js +156 -0
- package/package.json +49 -0
|
@@ -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
|
+
// tests/core/executor.test.ts
|
|
7
|
+
const executor_1 = require("../../src/core/executor");
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
describe('Executor Engine', () => {
|
|
11
|
+
const sourceDir = path_1.default.join(__dirname, 'mock_source');
|
|
12
|
+
const targetDir = path_1.default.join(__dirname, 'mock_target');
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
fs_1.default.mkdirSync(sourceDir, { recursive: true });
|
|
15
|
+
fs_1.default.writeFileSync(path_1.default.join(sourceDir, 'test.txt'), 'hello');
|
|
16
|
+
fs_1.default.mkdirSync(targetDir, { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
afterEach(() => {
|
|
19
|
+
fs_1.default.rmSync(sourceDir, { recursive: true, force: true });
|
|
20
|
+
fs_1.default.rmSync(targetDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
it('creates a symlink successfully', () => {
|
|
23
|
+
const dest = path_1.default.join(targetDir, 'my-skill');
|
|
24
|
+
(0, executor_1.installArtifact)(sourceDir, dest, 'symlink');
|
|
25
|
+
expect(fs_1.default.lstatSync(dest).isSymbolicLink()).toBe(true);
|
|
26
|
+
});
|
|
27
|
+
it('copies the directory successfully', () => {
|
|
28
|
+
const dest = path_1.default.join(targetDir, 'my-copied-skill');
|
|
29
|
+
(0, executor_1.installArtifact)(sourceDir, dest, 'copy');
|
|
30
|
+
expect(fs_1.default.lstatSync(dest).isDirectory()).toBe(true);
|
|
31
|
+
expect(fs_1.default.existsSync(path_1.default.join(dest, 'test.txt'))).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('removes an installed artifact', () => {
|
|
34
|
+
const dest = path_1.default.join(targetDir, 'to-remove');
|
|
35
|
+
(0, executor_1.installArtifact)(sourceDir, dest, 'symlink');
|
|
36
|
+
expect(fs_1.default.existsSync(dest)).toBe(true);
|
|
37
|
+
(0, executor_1.removeArtifact)(dest);
|
|
38
|
+
expect(fs_1.default.existsSync(dest)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
it('throws when removing a non-existent artifact', () => {
|
|
41
|
+
const dest = path_1.default.join(targetDir, 'does-not-exist');
|
|
42
|
+
expect(() => (0, executor_1.removeArtifact)(dest)).toThrow('Artifact not found');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
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 detector_1 = require("../../../src/core/init/detector");
|
|
10
|
+
function tmpRepo() {
|
|
11
|
+
return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-detector-'));
|
|
12
|
+
}
|
|
13
|
+
describe('detectExtensions', () => {
|
|
14
|
+
let root;
|
|
15
|
+
beforeEach(() => { root = tmpRepo(); });
|
|
16
|
+
afterEach(() => { fs_1.default.rmSync(root, { recursive: true, force: true }); });
|
|
17
|
+
it('proposes frontend when package.json has a frontend dep', () => {
|
|
18
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ dependencies: { next: '14.0.0' } }));
|
|
19
|
+
const r = (0, detector_1.detectExtensions)(root);
|
|
20
|
+
expect(r.proposed).toContain('frontend');
|
|
21
|
+
expect(r.signals.some((s) => s.includes('next'))).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
it('does NOT propose docs for a lone README', () => {
|
|
24
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'docs'));
|
|
25
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'docs', 'README.md'), '# readme');
|
|
26
|
+
expect((0, detector_1.detectExtensions)(root).proposed).not.toContain('docs');
|
|
27
|
+
});
|
|
28
|
+
it('proposes docs when a docs config is present', () => {
|
|
29
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'mkdocs.yml'), 'site_name: x');
|
|
30
|
+
expect((0, detector_1.detectExtensions)(root).proposed).toContain('docs');
|
|
31
|
+
});
|
|
32
|
+
it('proposes docs when docs/ has 2+ markdown files', () => {
|
|
33
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'docs'));
|
|
34
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'docs', 'a.md'), '# a');
|
|
35
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'docs', 'b.md'), '# b');
|
|
36
|
+
expect((0, detector_1.detectExtensions)(root).proposed).toContain('docs');
|
|
37
|
+
});
|
|
38
|
+
it('proposes nothing for a backend-only repo', () => {
|
|
39
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ dependencies: { express: '4.0.0' } }));
|
|
40
|
+
const r = (0, detector_1.detectExtensions)(root);
|
|
41
|
+
expect(r.proposed).toEqual([]);
|
|
42
|
+
expect(r.deferred).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
it('defers infra signals (no bundle yet)', () => {
|
|
45
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'Dockerfile'), 'FROM node');
|
|
46
|
+
const r = (0, detector_1.detectExtensions)(root);
|
|
47
|
+
expect(r.proposed).toEqual([]);
|
|
48
|
+
expect(r.deferred.some((d) => d.includes('infra'))).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
it('proposes both for a combined repo (Next + real docs)', () => {
|
|
51
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ devDependencies: { astro: '4.0.0' } }));
|
|
52
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'docusaurus.config.js'), 'module.exports = {}');
|
|
53
|
+
const r = (0, detector_1.detectExtensions)(root);
|
|
54
|
+
expect(r.proposed).toEqual(expect.arrayContaining(['frontend', 'docs']));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
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
|
+
// Siembra un registry baseline mínimo en <contentRoot> (= ~/.awm/registries/baseline).
|
|
10
|
+
// El content root ES la raíz del registry — no hay subdir "registry/" en la nueva arquitectura.
|
|
11
|
+
function seedRegistry(contentRoot) {
|
|
12
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'skills', 'brainstorming'), { recursive: true });
|
|
13
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'skills', 'brainstorming', 'SKILL.md'), '# brainstorming');
|
|
14
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'skills', 'using-awm'), { recursive: true });
|
|
15
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'skills', 'using-awm', 'SKILL.md'), '# using-awm');
|
|
16
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'hooks'), { recursive: true });
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'hooks', 'session-start'), '#!/bin/sh\n');
|
|
18
|
+
fs_1.default.chmodSync(path_1.default.join(contentRoot, 'hooks', 'session-start'), 0o755);
|
|
19
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'hooks', 'run-hook.cmd'), '#!/bin/sh\n');
|
|
20
|
+
fs_1.default.chmodSync(path_1.default.join(contentRoot, 'hooks', 'run-hook.cmd'), 0o755);
|
|
21
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
22
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'sensor-packs', 'js-ts', 'pack.json'), JSON.stringify({ sensors: { lint: { defaultCmd: 'eslint {{SOURCE_DIRS}}', fast: true } } }));
|
|
23
|
+
// catalog + bundle dev (baseline)
|
|
24
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'catalog.json'), JSON.stringify({
|
|
25
|
+
version: 1, bundles: [{ name: 'dev', source: './bundles/dev', version: '1.0.0', scope: 'baseline' }],
|
|
26
|
+
}));
|
|
27
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, 'bundles', 'dev'), { recursive: true });
|
|
28
|
+
fs_1.default.writeFileSync(path_1.default.join(contentRoot, 'bundles', 'dev', 'bundle.json'), JSON.stringify({
|
|
29
|
+
name: 'dev', version: '1.0.0', scope: 'baseline', dependsOn: [],
|
|
30
|
+
skills: [{ name: 'brainstorming' }, { name: 'using-awm' }], workflows: [], agents: [],
|
|
31
|
+
}));
|
|
32
|
+
// Seed .git in the content root so gatherMachine.registryCache.present is true.
|
|
33
|
+
fs_1.default.mkdirSync(path_1.default.join(contentRoot, '.git'), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
describe('runInitSteps — orchestrator', () => {
|
|
36
|
+
let tmpHome;
|
|
37
|
+
let originalHome;
|
|
38
|
+
let originalAwmHome;
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-orch-'));
|
|
41
|
+
originalHome = process.env.HOME;
|
|
42
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
43
|
+
process.env.HOME = tmpHome;
|
|
44
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
45
|
+
jest.resetModules();
|
|
46
|
+
});
|
|
47
|
+
afterEach(() => {
|
|
48
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
49
|
+
if (originalHome === undefined)
|
|
50
|
+
delete process.env.HOME;
|
|
51
|
+
else
|
|
52
|
+
process.env.HOME = originalHome;
|
|
53
|
+
if (originalAwmHome === undefined)
|
|
54
|
+
delete process.env.AWM_HOME;
|
|
55
|
+
else
|
|
56
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
57
|
+
});
|
|
58
|
+
function buildDeps(cwd) {
|
|
59
|
+
const { gatherContext } = require('../../../src/core/diagnostics/context');
|
|
60
|
+
const { discoverAllBundles } = require('../../../src/core/bundles');
|
|
61
|
+
const { contentRoots, REGISTRIES_DIR } = require('../../../src/core/registries');
|
|
62
|
+
const { defaultActions } = require('../../../src/core/init/steps');
|
|
63
|
+
// Seed content at ~/.awm/registries/baseline (content root IS the registry root).
|
|
64
|
+
const contentRoot = path_1.default.join(process.env.AWM_HOME, 'registries', 'baseline');
|
|
65
|
+
fs_1.default.mkdirSync(contentRoot, { recursive: true });
|
|
66
|
+
seedRegistry(contentRoot);
|
|
67
|
+
// Register the baseline registry in registries.json so contentRoots() picks it up.
|
|
68
|
+
const registriesJson = path_1.default.join(process.env.AWM_HOME, 'registries.json');
|
|
69
|
+
fs_1.default.writeFileSync(registriesJson, JSON.stringify([
|
|
70
|
+
{ name: 'baseline', remote: 'https://example.com/baseline.git' },
|
|
71
|
+
], null, 2));
|
|
72
|
+
const roots = contentRoots();
|
|
73
|
+
const bundles = discoverAllBundles(roots);
|
|
74
|
+
const contentDir = roots[0] ?? '';
|
|
75
|
+
const ctx = gatherContext({ cwd, bundles });
|
|
76
|
+
return {
|
|
77
|
+
cwd, ctx, bundles, agent: 'claude-code', installMethod: 'symlink',
|
|
78
|
+
registryRoot: contentRoot, contentDir, sensorPacksRoot: contentRoot,
|
|
79
|
+
confirmExtensions: async (p) => p,
|
|
80
|
+
// syncCache es no-op: el cache ya está sembrado en disco
|
|
81
|
+
actions: { ...defaultActions, syncCache: async () => { } },
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
it('machine-only on a bare cwd installs baseline + hook, reaches healthy machine', async () => {
|
|
85
|
+
const { runInitSteps } = require('../../../src/core/init/orchestrator');
|
|
86
|
+
const bareCwd = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bare-'));
|
|
87
|
+
try {
|
|
88
|
+
const deps = buildDeps(bareCwd); // bareCwd no es repo → project null
|
|
89
|
+
deps.ctx.project = null;
|
|
90
|
+
const out = await runInitSteps(deps);
|
|
91
|
+
expect(out.applied).toBeGreaterThan(0);
|
|
92
|
+
expect(out.steps.some((s) => s.id === 'machine.devCore' && s.action === 'applied')).toBe(true);
|
|
93
|
+
expect(out.after.results.find((r) => r.id === 'machine.devCore').status).toBe('ok');
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
fs_1.default.rmSync(bareCwd, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('project repo: applies activation/sensors, flags constitution+context as pending', async () => {
|
|
100
|
+
const root = path_1.default.join(tmpHome, 'repo');
|
|
101
|
+
fs_1.default.mkdirSync(path_1.default.join(root, '.awm'), { recursive: true });
|
|
102
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ dependencies: { next: '14.0.0' } }));
|
|
103
|
+
const deps = buildDeps(root);
|
|
104
|
+
const { runInitSteps } = require('../../../src/core/init/orchestrator');
|
|
105
|
+
const out = await runInitSteps(deps);
|
|
106
|
+
expect(out.steps.some((s) => s.id === 'project.sensors' && s.action === 'applied')).toBe(true);
|
|
107
|
+
expect(out.steps.find((s) => s.id === 'project.constitution').action).toBe('pending');
|
|
108
|
+
expect(out.steps.find((s) => s.id === 'project.context').action).toBe('pending');
|
|
109
|
+
});
|
|
110
|
+
it('is idempotent: a second run applies nothing and yields an identical after-report', async () => {
|
|
111
|
+
const root = path_1.default.join(tmpHome, 'repo2');
|
|
112
|
+
fs_1.default.mkdirSync(path_1.default.join(root, '.awm'), { recursive: true });
|
|
113
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ dependencies: { next: '14.0.0' } }));
|
|
114
|
+
const { runInitSteps } = require('../../../src/core/init/orchestrator');
|
|
115
|
+
const out1 = await runInitSteps(buildDeps(root));
|
|
116
|
+
expect(out1.applied).toBeGreaterThan(0);
|
|
117
|
+
// segundo run: re-gather refleja el estado ya materializado
|
|
118
|
+
const out2 = await runInitSteps(buildDeps(root));
|
|
119
|
+
expect(out2.applied).toBe(0);
|
|
120
|
+
expect(out2.after).toEqual(out1.after);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,363 @@
|
|
|
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 steps_1 = require("../../../src/core/init/steps");
|
|
10
|
+
const providers_1 = require("../../../src/providers");
|
|
11
|
+
function bundle(name, scope, skills) {
|
|
12
|
+
return {
|
|
13
|
+
name, description: '', version: '1.0.0', scope, visibility: 'public',
|
|
14
|
+
dependsOn: [], skills: skills.map((s) => ({ name: s, onSignal: false })),
|
|
15
|
+
workflows: [], agents: [],
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function machine() {
|
|
19
|
+
return {
|
|
20
|
+
registryCache: { present: true, gitState: 'clean' },
|
|
21
|
+
hook: { present: true, degraded: false },
|
|
22
|
+
devCore: { present: true, brokenLinks: [] },
|
|
23
|
+
ambient: { wanted: [], installed: [] },
|
|
24
|
+
contextInjection: [],
|
|
25
|
+
globalSkills: { valid: [], repairable: [], dead: [] },
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function project(over = {}) {
|
|
29
|
+
return {
|
|
30
|
+
root: '/repo',
|
|
31
|
+
profile: { present: true, extensions: [] },
|
|
32
|
+
activeBundles: { expected: [], linked: [], broken: [] },
|
|
33
|
+
sensors: { present: true },
|
|
34
|
+
constitution: { present: true },
|
|
35
|
+
context: { present: true, file: 'CLAUDE.md' },
|
|
36
|
+
...over,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function spies() {
|
|
40
|
+
return {
|
|
41
|
+
syncCache: jest.fn(async () => { }),
|
|
42
|
+
installHook: jest.fn(() => ({ status: 'installed' })),
|
|
43
|
+
installBundle: jest.fn(() => ({ installed: ['a'], skipped: [] })),
|
|
44
|
+
syncProfile: jest.fn(() => ({ installed: ['a'], skipped: [], extensions: ['frontend'] })),
|
|
45
|
+
initSensors: jest.fn(() => ({ detection: { pack: 'js-ts' } })),
|
|
46
|
+
addExtension: jest.fn(),
|
|
47
|
+
ensureProfile: jest.fn(),
|
|
48
|
+
gatherProject: jest.fn((_cwd, _bundles) => null),
|
|
49
|
+
contextStatus: jest.fn(() => 'absent'),
|
|
50
|
+
installContext: jest.fn(),
|
|
51
|
+
repairGlobalSkills: jest.fn(() => ({ relinked: [], pruned: [], failed: [] })),
|
|
52
|
+
injectProjectConstitution: jest.fn(() => 'injected'),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function deps(ctx, actions, over = {}) {
|
|
56
|
+
return {
|
|
57
|
+
cwd: '/repo', ctx, bundles: [bundle('dev', 'baseline', ['brainstorming'])],
|
|
58
|
+
agent: 'claude-code', installMethod: 'symlink',
|
|
59
|
+
registryRoot: '/cache', contentDir: '/cache/registry', sensorPacksRoot: '/cache/registry',
|
|
60
|
+
confirmExtensions: async (p) => p, actions, ...over,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
describe('stepCache', () => {
|
|
64
|
+
it('skips when cli present and not behind', async () => {
|
|
65
|
+
const a = spies();
|
|
66
|
+
const r = await (0, steps_1.stepCache)(deps({ machine: machine(), project: null }, a));
|
|
67
|
+
expect(r.action).toBe('skipped');
|
|
68
|
+
expect(a.syncCache).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
it('syncs when registry cache absent', async () => {
|
|
71
|
+
const a = spies();
|
|
72
|
+
const m = machine();
|
|
73
|
+
m.registryCache = { present: false };
|
|
74
|
+
const r = await (0, steps_1.stepCache)(deps({ machine: m, project: null }, a));
|
|
75
|
+
expect(r.action).toBe('applied');
|
|
76
|
+
expect(a.syncCache).toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
it('syncs when registry cache behind', async () => {
|
|
79
|
+
const a = spies();
|
|
80
|
+
const m = machine();
|
|
81
|
+
m.registryCache = { present: true, gitState: 'behind' };
|
|
82
|
+
expect((await (0, steps_1.stepCache)(deps({ machine: m, project: null }, a))).action).toBe('applied');
|
|
83
|
+
});
|
|
84
|
+
it('reports failed when syncCache throws (does not throw)', async () => {
|
|
85
|
+
const a = spies();
|
|
86
|
+
a.syncCache = jest.fn(async () => { throw new Error('net down'); });
|
|
87
|
+
const m = machine();
|
|
88
|
+
m.registryCache = { present: false };
|
|
89
|
+
const r = await (0, steps_1.stepCache)(deps({ machine: m, project: null }, a));
|
|
90
|
+
expect(r.action).toBe('failed');
|
|
91
|
+
expect(r.error).toContain('net down');
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
describe('stepHook / stepDevCore / stepAmbient', () => {
|
|
95
|
+
it('hook skips when present and healthy', () => {
|
|
96
|
+
const a = spies();
|
|
97
|
+
expect((0, steps_1.stepHook)(deps({ machine: machine(), project: null }, a)).action).toBe('skipped');
|
|
98
|
+
expect(a.installHook).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
it('hook installs when absent', () => {
|
|
101
|
+
const a = spies();
|
|
102
|
+
const m = machine();
|
|
103
|
+
m.hook = { present: false };
|
|
104
|
+
expect((0, steps_1.stepHook)(deps({ machine: m, project: null }, a)).action).toBe('applied');
|
|
105
|
+
expect(a.installHook).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
it('hook reinstalls when present but degraded', () => {
|
|
108
|
+
const a = spies();
|
|
109
|
+
const m = machine();
|
|
110
|
+
m.hook = { present: true, degraded: true };
|
|
111
|
+
expect((0, steps_1.stepHook)(deps({ machine: m, project: null }, a)).action).toBe('applied');
|
|
112
|
+
expect(a.installHook).toHaveBeenCalled();
|
|
113
|
+
});
|
|
114
|
+
it('devCore installs baseline when links broken', () => {
|
|
115
|
+
const a = spies();
|
|
116
|
+
const m = machine();
|
|
117
|
+
m.devCore = { present: true, brokenLinks: ['brainstorming'] };
|
|
118
|
+
expect((0, steps_1.stepDevCore)(deps({ machine: m, project: null }, a)).action).toBe('applied');
|
|
119
|
+
expect(a.installBundle).toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
it('devCore installs when not present at all', () => {
|
|
122
|
+
const a = spies();
|
|
123
|
+
const m = machine();
|
|
124
|
+
m.devCore = { present: false, brokenLinks: [] };
|
|
125
|
+
expect((0, steps_1.stepDevCore)(deps({ machine: m, project: null }, a)).action).toBe('applied');
|
|
126
|
+
expect(a.installBundle).toHaveBeenCalled();
|
|
127
|
+
});
|
|
128
|
+
it('ambient installs only missing wanted', () => {
|
|
129
|
+
const a = spies();
|
|
130
|
+
const m = machine();
|
|
131
|
+
m.ambient = { wanted: ['personal-notion', 'docs'], installed: ['docs'] };
|
|
132
|
+
const r = (0, steps_1.stepAmbient)(deps({ machine: m, project: null }, a));
|
|
133
|
+
expect(r.action).toBe('applied');
|
|
134
|
+
expect(a.installBundle).toHaveBeenCalledTimes(1);
|
|
135
|
+
});
|
|
136
|
+
it('ambient skips when nothing wanted', () => {
|
|
137
|
+
const a = spies();
|
|
138
|
+
expect((0, steps_1.stepAmbient)(deps({ machine: machine(), project: null }, a)).action).toBe('skipped');
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
describe('stepProfile', () => {
|
|
142
|
+
it('adds confirmed extension when detector finds a match', async () => {
|
|
143
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-step-profile-'));
|
|
144
|
+
try {
|
|
145
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ dependencies: { next: '14.0.0' } }));
|
|
146
|
+
const a = spies();
|
|
147
|
+
const ctx = {
|
|
148
|
+
machine: machine(),
|
|
149
|
+
project: project({ root, profile: { present: true, extensions: [] } }),
|
|
150
|
+
};
|
|
151
|
+
const r = await (0, steps_1.stepProfile)(deps(ctx, a, { confirmExtensions: async (p) => p }));
|
|
152
|
+
expect(r.action).toBe('applied');
|
|
153
|
+
expect(a.addExtension).toHaveBeenCalledWith(root, 'frontend');
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
it('skips when confirm returns empty', async () => {
|
|
160
|
+
const a = spies();
|
|
161
|
+
const ctx = { machine: machine(), project: project() };
|
|
162
|
+
const r = await (0, steps_1.stepProfile)(deps(ctx, a, { confirmExtensions: async () => [] }));
|
|
163
|
+
expect(a.addExtension).not.toHaveBeenCalled();
|
|
164
|
+
expect(r.action).toBe('skipped');
|
|
165
|
+
});
|
|
166
|
+
it('does not invoke confirmExtensions when no new extensions are proposed (#1 guard)', async () => {
|
|
167
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-ext-'));
|
|
168
|
+
try {
|
|
169
|
+
const a = spies();
|
|
170
|
+
const confirm = jest.fn(async (p) => p);
|
|
171
|
+
const ctx = {
|
|
172
|
+
machine: machine(),
|
|
173
|
+
project: project({ root, profile: { present: true, extensions: [] } }),
|
|
174
|
+
};
|
|
175
|
+
const r = await (0, steps_1.stepProfile)(deps(ctx, a, { confirmExtensions: confirm }));
|
|
176
|
+
expect(confirm).not.toHaveBeenCalled();
|
|
177
|
+
expect(r.action).toBe('skipped');
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
it('bootstraps an empty profile when none exists and no extensions are proposed', async () => {
|
|
184
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-bootstrap-profile-'));
|
|
185
|
+
try {
|
|
186
|
+
const a = spies();
|
|
187
|
+
const ctx = {
|
|
188
|
+
machine: machine(),
|
|
189
|
+
project: project({ root, profile: { present: false, extensions: [] } }),
|
|
190
|
+
};
|
|
191
|
+
const r = await (0, steps_1.stepProfile)(deps(ctx, a, { confirmExtensions: async (p) => p }));
|
|
192
|
+
expect(r.action).toBe('applied');
|
|
193
|
+
expect(a.ensureProfile).toHaveBeenCalledWith(root);
|
|
194
|
+
expect(a.addExtension).not.toHaveBeenCalled();
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
it('does not bootstrap when the profile already exists', async () => {
|
|
201
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-existing-profile-'));
|
|
202
|
+
try {
|
|
203
|
+
const a = spies();
|
|
204
|
+
const ctx = {
|
|
205
|
+
machine: machine(),
|
|
206
|
+
project: project({ root, profile: { present: true, extensions: [] } }),
|
|
207
|
+
};
|
|
208
|
+
const r = await (0, steps_1.stepProfile)(deps(ctx, a));
|
|
209
|
+
expect(r.action).toBe('skipped');
|
|
210
|
+
expect(a.ensureProfile).not.toHaveBeenCalled();
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
describe('stepActivation', () => {
|
|
218
|
+
it('skips when expected all linked and none broken', () => {
|
|
219
|
+
const a = spies();
|
|
220
|
+
a.gatherProject = jest.fn((_cwd, _bundles) => project({ activeBundles: { expected: ['x'], linked: ['x'], broken: [] } }));
|
|
221
|
+
const r = (0, steps_1.stepActivation)(deps({ machine: machine(), project: project() }, a));
|
|
222
|
+
expect(r.action).toBe('skipped');
|
|
223
|
+
expect(a.syncProfile).not.toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
it('syncs when links missing', () => {
|
|
226
|
+
const a = spies();
|
|
227
|
+
a.gatherProject = jest.fn((_cwd, _bundles) => project({ activeBundles: { expected: ['x', 'y'], linked: ['x'], broken: [] } }));
|
|
228
|
+
const r = (0, steps_1.stepActivation)(deps({ machine: machine(), project: project() }, a));
|
|
229
|
+
expect(r.action).toBe('applied');
|
|
230
|
+
expect(a.syncProfile).toHaveBeenCalled();
|
|
231
|
+
});
|
|
232
|
+
it('passes target agent to gatherProject (#4 — project local skills scoped to agent)', () => {
|
|
233
|
+
const a = spies();
|
|
234
|
+
const m = machine();
|
|
235
|
+
m.devCore = { present: true, brokenLinks: [] };
|
|
236
|
+
const ctx = { machine: m, project: project() };
|
|
237
|
+
(0, steps_1.stepActivation)(deps(ctx, a, { agent: 'opencode' }));
|
|
238
|
+
expect(a.gatherProject).toHaveBeenCalledWith(expect.any(String), expect.any(Array), 'opencode');
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
describe('stepSensors', () => {
|
|
242
|
+
it('skips when sensors present', () => {
|
|
243
|
+
const a = spies();
|
|
244
|
+
expect((0, steps_1.stepSensors)(deps({ machine: machine(), project: project() }, a)).action).toBe('skipped');
|
|
245
|
+
});
|
|
246
|
+
it('inits sensors when absent', () => {
|
|
247
|
+
const a = spies();
|
|
248
|
+
const r = (0, steps_1.stepSensors)(deps({ machine: machine(), project: project({ sensors: { present: false } }) }, a));
|
|
249
|
+
expect(r.action).toBe('applied');
|
|
250
|
+
expect(a.initSensors).toHaveBeenCalledWith({ cwd: '/repo', registryRoot: '/cache/registry', configure: true }); // sensorPacksRoot='/cache/registry' from deps()
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('stepConstitution / stepContext (frontera agente)', () => {
|
|
254
|
+
it('constitution pending + names the skill, never writes', () => {
|
|
255
|
+
const a = spies();
|
|
256
|
+
const r = (0, steps_1.stepConstitution)(deps({ machine: machine(), project: project({ constitution: { present: false } }) }, a));
|
|
257
|
+
expect(r.action).toBe('pending');
|
|
258
|
+
expect(r.detail).toContain('project-constitution');
|
|
259
|
+
});
|
|
260
|
+
it('context pending names project-context-init', () => {
|
|
261
|
+
const a = spies();
|
|
262
|
+
const r = (0, steps_1.stepContext)(deps({ machine: machine(), project: project({ context: { present: false } }) }, a));
|
|
263
|
+
expect(r.action).toBe('pending');
|
|
264
|
+
expect(r.detail).toContain('project-context-init');
|
|
265
|
+
});
|
|
266
|
+
it('both skip when present', () => {
|
|
267
|
+
const a = spies();
|
|
268
|
+
const ctx = { machine: machine(), project: project() };
|
|
269
|
+
expect((0, steps_1.stepConstitution)(deps(ctx, a)).action).toBe('skipped');
|
|
270
|
+
expect((0, steps_1.stepContext)(deps(ctx, a)).action).toBe('skipped');
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
describe('stepGlobalSkillsRepair', () => {
|
|
274
|
+
it('skips when nothing is broken', () => {
|
|
275
|
+
const a = spies();
|
|
276
|
+
const m = machine(); // globalSkills.repairable=[], dead=[] by default
|
|
277
|
+
const r = (0, steps_1.stepGlobalSkillsRepair)(deps({ machine: m, project: null }, a));
|
|
278
|
+
expect(r.action).toBe('skipped');
|
|
279
|
+
expect(a.repairGlobalSkills).not.toHaveBeenCalled();
|
|
280
|
+
});
|
|
281
|
+
it('applies repair when there are broken links', () => {
|
|
282
|
+
const a = spies();
|
|
283
|
+
a.repairGlobalSkills = jest.fn(() => ({ relinked: ['b'], pruned: ['c'], failed: [] }));
|
|
284
|
+
const m = machine();
|
|
285
|
+
m.globalSkills = { valid: ['a'], repairable: ['b'], dead: ['c'] };
|
|
286
|
+
const r = (0, steps_1.stepGlobalSkillsRepair)(deps({ machine: m, project: null }, a));
|
|
287
|
+
expect(r.action).toBe('applied');
|
|
288
|
+
expect(a.repairGlobalSkills).toHaveBeenCalledTimes(1);
|
|
289
|
+
expect(r.detail).toContain('re-linked 1');
|
|
290
|
+
expect(r.detail).toContain('pruned 1');
|
|
291
|
+
});
|
|
292
|
+
it('repairs the target agent skills dir, not Claude (#4)', () => {
|
|
293
|
+
const a = spies();
|
|
294
|
+
a.repairGlobalSkills = jest.fn(() => ({ relinked: ['b'], pruned: [], failed: [] }));
|
|
295
|
+
const m = machine();
|
|
296
|
+
m.globalSkills = { valid: [], repairable: ['b'], dead: [] };
|
|
297
|
+
const r = (0, steps_1.stepGlobalSkillsRepair)(deps({ machine: m, project: null }, a, { agent: 'opencode' }));
|
|
298
|
+
expect(r.action).toBe('applied');
|
|
299
|
+
expect(a.repairGlobalSkills).toHaveBeenCalledWith(providers_1.PROVIDERS['opencode'].skill.global, expect.any(Array));
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
describe('stepConstitutionInjection (#6)', () => {
|
|
303
|
+
it('injects for a config-instructions agent when CONSTITUTION.md is present', () => {
|
|
304
|
+
const a = spies();
|
|
305
|
+
const r = (0, steps_1.stepConstitutionInjection)(deps({ machine: machine(), project: project({ constitution: { present: true } }) }, a, { agent: 'opencode' }));
|
|
306
|
+
expect(r.action).toBe('applied');
|
|
307
|
+
expect(a.injectProjectConstitution).toHaveBeenCalledWith({ projectRoot: '/repo', agent: 'opencode' });
|
|
308
|
+
});
|
|
309
|
+
it('skips for Claude (delivered by the hook), never touching the action', () => {
|
|
310
|
+
const a = spies();
|
|
311
|
+
const r = (0, steps_1.stepConstitutionInjection)(deps({ machine: machine(), project: project({ constitution: { present: true } }) }, a, { agent: 'claude-code' }));
|
|
312
|
+
expect(r.action).toBe('skipped');
|
|
313
|
+
expect(a.injectProjectConstitution).not.toHaveBeenCalled();
|
|
314
|
+
});
|
|
315
|
+
it('skips when CONSTITUTION.md is absent', () => {
|
|
316
|
+
const a = spies();
|
|
317
|
+
const r = (0, steps_1.stepConstitutionInjection)(deps({ machine: machine(), project: project({ constitution: { present: false } }) }, a, { agent: 'opencode' }));
|
|
318
|
+
expect(r.action).toBe('skipped');
|
|
319
|
+
expect(a.injectProjectConstitution).not.toHaveBeenCalled();
|
|
320
|
+
});
|
|
321
|
+
it('maps already → skipped when CONSTITUTION.md was already in opencode.json', () => {
|
|
322
|
+
const a = spies();
|
|
323
|
+
a.injectProjectConstitution.mockReturnValue('already');
|
|
324
|
+
const r = (0, steps_1.stepConstitutionInjection)(deps({ machine: machine(), project: project({ constitution: { present: true } }) }, a, { agent: 'opencode' }));
|
|
325
|
+
expect(r.action).toBe('skipped');
|
|
326
|
+
expect(r.detail).toBe('already');
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
describe('stepContextInjection', () => {
|
|
330
|
+
it('skips claude-code (covered by stepHook)', () => {
|
|
331
|
+
const a = spies();
|
|
332
|
+
const r = (0, steps_1.stepContextInjection)(deps({ machine: machine(), project: null }, a, { agent: 'claude-code' }));
|
|
333
|
+
expect(r.action).toBe('skipped');
|
|
334
|
+
expect(a.installContext).not.toHaveBeenCalled();
|
|
335
|
+
});
|
|
336
|
+
it('skips an agent without an injection mechanism', () => {
|
|
337
|
+
const a = spies();
|
|
338
|
+
const r = (0, steps_1.stepContextInjection)(deps({ machine: machine(), project: null }, a, { agent: 'antigravity' }));
|
|
339
|
+
expect(r.action).toBe('skipped');
|
|
340
|
+
expect(a.installContext).not.toHaveBeenCalled();
|
|
341
|
+
});
|
|
342
|
+
it('skips opencode when already injected', () => {
|
|
343
|
+
const a = spies();
|
|
344
|
+
a.contextStatus = jest.fn(() => 'injected');
|
|
345
|
+
const r = (0, steps_1.stepContextInjection)(deps({ machine: machine(), project: null }, a, { agent: 'opencode' }));
|
|
346
|
+
expect(r.action).toBe('skipped');
|
|
347
|
+
expect(a.installContext).not.toHaveBeenCalled();
|
|
348
|
+
});
|
|
349
|
+
it('installs context for opencode when absent', () => {
|
|
350
|
+
const a = spies();
|
|
351
|
+
a.contextStatus = jest.fn(() => 'absent');
|
|
352
|
+
const r = (0, steps_1.stepContextInjection)(deps({ machine: machine(), project: null }, a, { agent: 'opencode' }));
|
|
353
|
+
expect(r.action).toBe('applied');
|
|
354
|
+
expect(a.installContext).toHaveBeenCalledWith(expect.objectContaining({ agent: 'opencode', scope: 'global' }));
|
|
355
|
+
});
|
|
356
|
+
it('installs context for opencode when stale', () => {
|
|
357
|
+
const a = spies();
|
|
358
|
+
a.contextStatus = jest.fn(() => 'stale');
|
|
359
|
+
const r = (0, steps_1.stepContextInjection)(deps({ machine: machine(), project: null }, a, { agent: 'opencode' }));
|
|
360
|
+
expect(r.action).toBe('applied');
|
|
361
|
+
expect(a.installContext).toHaveBeenCalled();
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
test('.awm (and therefore .awm/ledger) is gitignored — raw ledger never committed', () => {
|
|
9
|
+
const gitignore = fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../.gitignore'), 'utf-8');
|
|
10
|
+
const lines = gitignore.split('\n').map(l => l.trim());
|
|
11
|
+
const coversAwm = lines.includes('.awm') || lines.includes('.awm/') || lines.includes('.awm/ledger') || lines.includes('.awm/ledger/');
|
|
12
|
+
expect(coversAwm).toBe(true);
|
|
13
|
+
});
|