agentic-workflow-manager 3.0.1 → 3.2.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/dist/src/commands/add.js +84 -0
- package/dist/src/commands/agent.js +95 -0
- package/dist/src/commands/backup.js +46 -0
- package/dist/src/commands/doctor.js +80 -4
- package/dist/src/commands/export.js +48 -0
- package/dist/src/commands/hooks/claude.js +192 -0
- package/dist/src/commands/hooks/codex.js +191 -0
- package/dist/src/commands/hooks/index.js +23 -8
- package/dist/src/commands/hooks/install.js +11 -107
- package/dist/src/commands/hooks/resync.js +28 -18
- package/dist/src/commands/hooks/shared.js +94 -0
- package/dist/src/commands/hooks/status.js +13 -64
- package/dist/src/commands/hooks/uninstall.js +11 -47
- package/dist/src/commands/init.js +90 -28
- package/dist/src/commands/registry/index.js +1 -1
- package/dist/src/commands/sync.js +113 -0
- package/dist/src/commands/update.js +107 -0
- package/dist/src/core/agent-targets.js +50 -0
- package/dist/src/core/artifact-state.js +65 -0
- package/dist/src/core/atomic-file.js +72 -0
- package/dist/src/core/bundle-install.js +60 -38
- package/dist/src/core/bundles.js +21 -0
- package/dist/src/core/context/managed-block.js +190 -0
- package/dist/src/core/context/orchestrator.js +3 -1
- package/dist/src/core/context/project-constitution-inject.js +1 -1
- package/dist/src/core/context/regenerate.js +3 -3
- package/dist/src/core/context/strategies/codex-agents.js +100 -0
- package/dist/src/core/diagnostics/checks.js +16 -0
- package/dist/src/core/diagnostics/context.js +37 -8
- package/dist/src/core/diagnostics/provider-checks.js +219 -0
- package/dist/src/core/discovery.js +9 -3
- package/dist/src/core/executor.js +30 -9
- package/dist/src/core/export/index.js +51 -0
- package/dist/src/core/export/pack.js +58 -0
- package/dist/src/core/export/resolve.js +77 -0
- package/dist/src/core/export/transform.js +57 -0
- package/dist/src/core/export/types.js +2 -0
- package/dist/src/core/init/mutation-targets.js +139 -0
- package/dist/src/core/init/provider-facts.js +164 -0
- package/dist/src/core/init/steps.js +65 -9
- package/dist/src/core/install-planner.js +206 -0
- package/dist/src/core/install-transaction.js +405 -0
- package/dist/src/core/profile.js +1 -1
- package/dist/src/core/provider-artifacts.js +54 -0
- package/dist/src/core/provider-version.js +36 -0
- package/dist/src/core/reconciliation.js +61 -0
- package/dist/src/core/registries.js +10 -0
- package/dist/src/core/renderers/canonical-agent.js +27 -0
- package/dist/src/core/renderers/codex-agent.js +60 -0
- package/dist/src/core/skill-integrity.js +4 -3
- package/dist/src/index.js +99 -289
- package/dist/src/providers/index.js +143 -39
- package/dist/src/ui/provider-preflight.js +26 -0
- package/dist/src/utils/config.js +101 -9
- package/dist/tests/commands/agent.test.js +103 -0
- package/dist/tests/commands/backup.test.js +109 -0
- package/dist/tests/commands/doctor.test.js +65 -4
- package/dist/tests/commands/export.test.js +64 -0
- package/dist/tests/commands/hooks/codex.test.js +230 -0
- package/dist/tests/commands/hooks/install.test.js +20 -1
- package/dist/tests/commands/hooks/resync.test.js +53 -6
- package/dist/tests/commands/hooks/status.test.js +10 -0
- package/dist/tests/commands/hooks/uninstall.test.js +12 -0
- package/dist/tests/commands/init.test.js +195 -8
- package/dist/tests/commands/multi-agent-targeting.test.js +231 -0
- package/dist/tests/core/agent-targets.test.js +45 -0
- package/dist/tests/core/artifact-state.test.js +144 -0
- package/dist/tests/core/atomic-file.test.js +96 -0
- package/dist/tests/core/bundle-install.test.js +208 -17
- package/dist/tests/core/bundles.test.js +12 -0
- package/dist/tests/core/context/managed-block.test.js +90 -0
- package/dist/tests/core/context/orchestrator.test.js +48 -3
- package/dist/tests/core/context/regenerate.test.js +1 -1
- package/dist/tests/core/context/strategies/codex-agents.test.js +185 -0
- package/dist/tests/core/context/strategies/config-instructions.test.js +1 -1
- package/dist/tests/core/diagnostics/checks.test.js +19 -0
- package/dist/tests/core/diagnostics/context.test.js +74 -0
- package/dist/tests/core/diagnostics/provider-checks.test.js +252 -0
- package/dist/tests/core/executor.test.js +20 -0
- package/dist/tests/core/export/engine.test.js +104 -0
- package/dist/tests/core/export/pack.test.js +122 -0
- package/dist/tests/core/export/resolve.test.js +78 -0
- package/dist/tests/core/export/transform.test.js +74 -0
- package/dist/tests/core/init/mutation-targets.test.js +235 -0
- package/dist/tests/core/init/orchestrator.test.js +1 -1
- package/dist/tests/core/init/provider-facts.test.js +129 -0
- package/dist/tests/core/init/steps.test.js +106 -2
- package/dist/tests/core/install-planner.test.js +224 -0
- package/dist/tests/core/install-transaction.test.js +257 -0
- package/dist/tests/core/provider-artifacts.test.js +64 -0
- package/dist/tests/core/provider-version.test.js +53 -0
- package/dist/tests/core/reconciliation.test.js +165 -0
- package/dist/tests/core/registries-sync.test.js +4 -4
- package/dist/tests/core/renderers/canonical-agent.test.js +58 -0
- package/dist/tests/core/renderers/codex-agent.test.js +128 -0
- package/dist/tests/core/versioning.test.js +1 -1
- package/dist/tests/integration/codex-provider-isolated.test.js +206 -0
- package/dist/tests/providers/hooks-config.test.js +43 -25
- package/dist/tests/providers/index.test.js +144 -39
- package/dist/tests/structural/codex-agent-escaping-completeness.test.js +77 -0
- package/dist/tests/ui/provider-preflight.test.js +25 -0
- package/dist/tests/utils/config.test.js +178 -18
- package/package.json +1 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const codex_agent_1 = require("../../../src/core/renderers/codex-agent");
|
|
4
|
+
const canonical = `---
|
|
5
|
+
name: development-process
|
|
6
|
+
description: Orchestrates the development lifecycle
|
|
7
|
+
mode: primary
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Development Process
|
|
11
|
+
|
|
12
|
+
Invoke the \`development-process\` skill before implementation.
|
|
13
|
+
`;
|
|
14
|
+
it('renders deterministic native Codex TOML', () => {
|
|
15
|
+
expect((0, codex_agent_1.renderCodexAgent)(canonical)).toBe(`name = "development-process"
|
|
16
|
+
description = "Orchestrates the development lifecycle"
|
|
17
|
+
developer_instructions = """
|
|
18
|
+
# Development Process
|
|
19
|
+
|
|
20
|
+
Invoke the \`development-process\` skill before implementation.
|
|
21
|
+
"""
|
|
22
|
+
`); // verifies R8, R9
|
|
23
|
+
});
|
|
24
|
+
it.each([
|
|
25
|
+
['---\nname: Bad Name\ndescription: x\n---\nbody', 'invalid agent name'],
|
|
26
|
+
['---\nname: ok\ndescription:\n---\nbody', 'non-empty description'],
|
|
27
|
+
['---\nname: ok\ndescription: x\n---\n', 'non-empty instruction body'],
|
|
28
|
+
['name: ok', 'frontmatter'],
|
|
29
|
+
])('rejects invalid canonical agents before rendering', (source, message) => {
|
|
30
|
+
expect(() => (0, codex_agent_1.renderCodexAgent)(source)).toThrow(message); // verifies R17
|
|
31
|
+
});
|
|
32
|
+
it('escapes an embedded backslash and runs of double quotes of varying length in the instructions body', () => {
|
|
33
|
+
const instructionsBody = [
|
|
34
|
+
'Backslash: \\',
|
|
35
|
+
'One quote: "',
|
|
36
|
+
'Three quotes: """',
|
|
37
|
+
'Four quotes: """"',
|
|
38
|
+
'Five quotes: """""',
|
|
39
|
+
'Seven quotes: """""""',
|
|
40
|
+
'Eight quotes: """"""""',
|
|
41
|
+
].join('\n');
|
|
42
|
+
const source = `---
|
|
43
|
+
name: quote-agent
|
|
44
|
+
description: Handles many quotes
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
${instructionsBody}
|
|
48
|
+
`;
|
|
49
|
+
expect((0, codex_agent_1.renderCodexAgent)(source)).toBe(`name = "quote-agent"
|
|
50
|
+
description = "Handles many quotes"
|
|
51
|
+
developer_instructions = """
|
|
52
|
+
Backslash: \\\\
|
|
53
|
+
One quote: \\"
|
|
54
|
+
Three quotes: \\"\\"\\"
|
|
55
|
+
Four quotes: \\"\\"\\"\\"
|
|
56
|
+
Five quotes: \\"\\"\\"\\"\\"
|
|
57
|
+
Seven quotes: \\"\\"\\"\\"\\"\\"\\"
|
|
58
|
+
Eight quotes: \\"\\"\\"\\"\\"\\"\\"\\"
|
|
59
|
+
"""
|
|
60
|
+
`); // verifies R8, R9 (guards against greedy triple-quote-run escaping producing invalid TOML)
|
|
61
|
+
});
|
|
62
|
+
it('escapes U+007F (DEL) in single-line TOML string fields', () => {
|
|
63
|
+
const source = `---
|
|
64
|
+
name: ok
|
|
65
|
+
description: hasdel
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
Body text.
|
|
69
|
+
`;
|
|
70
|
+
expect((0, codex_agent_1.renderCodexAgent)(source)).toBe(`name = "ok"
|
|
71
|
+
description = "has\\u007fdel"
|
|
72
|
+
developer_instructions = """
|
|
73
|
+
Body text.
|
|
74
|
+
"""
|
|
75
|
+
`); // verifies R8, R9
|
|
76
|
+
});
|
|
77
|
+
it('escapes U+007F (DEL) embedded in the multiline instructions body', () => {
|
|
78
|
+
const del = String.fromCharCode(0x7f);
|
|
79
|
+
const source = `---
|
|
80
|
+
name: ok
|
|
81
|
+
description: fine
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
Body${del}text.
|
|
85
|
+
`;
|
|
86
|
+
expect((0, codex_agent_1.renderCodexAgent)(source)).toBe(`name = "ok"
|
|
87
|
+
description = "fine"
|
|
88
|
+
developer_instructions = """
|
|
89
|
+
Body\\u007ftext.
|
|
90
|
+
"""
|
|
91
|
+
`); // verifies R8, R9
|
|
92
|
+
});
|
|
93
|
+
it('escapes other C0 control characters (e.g. NUL, U+000B) embedded in the multiline instructions body while leaving tab and newline raw', () => {
|
|
94
|
+
const nul = String.fromCharCode(0x00);
|
|
95
|
+
const vt = String.fromCharCode(0x0b);
|
|
96
|
+
const unitSep = String.fromCharCode(0x1f);
|
|
97
|
+
const source = `---
|
|
98
|
+
name: ok
|
|
99
|
+
description: fine
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
Body${nul}with${vt}control${unitSep}chars\tand\na newline.
|
|
103
|
+
`;
|
|
104
|
+
expect((0, codex_agent_1.renderCodexAgent)(source)).toBe(`name = "ok"
|
|
105
|
+
description = "fine"
|
|
106
|
+
developer_instructions = """
|
|
107
|
+
Body\\u0000with\\u000bcontrol\\u001fchars\tand
|
|
108
|
+
a newline.
|
|
109
|
+
"""
|
|
110
|
+
`); // verifies R8, R9
|
|
111
|
+
});
|
|
112
|
+
it('escapes other C0 control characters (e.g. NUL, U+000B) in single-line TOML string fields', () => {
|
|
113
|
+
const nul = String.fromCharCode(0x00);
|
|
114
|
+
const vt = String.fromCharCode(0x0b);
|
|
115
|
+
const source = `---
|
|
116
|
+
name: ok
|
|
117
|
+
description: has${nul}nul${vt}vt
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
Body text.
|
|
121
|
+
`;
|
|
122
|
+
expect((0, codex_agent_1.renderCodexAgent)(source)).toBe(`name = "ok"
|
|
123
|
+
description = "has\\u0000nul\\u000bvt"
|
|
124
|
+
developer_instructions = """
|
|
125
|
+
Body text.
|
|
126
|
+
"""
|
|
127
|
+
`); // verifies R8, R9
|
|
128
|
+
});
|
|
@@ -152,7 +152,7 @@ describe('versioning core', () => {
|
|
|
152
152
|
it('lee channel dev y pin por nombre desde preferences', () => {
|
|
153
153
|
const awmDir = path_1.default.join(tmpHome, '.awm');
|
|
154
154
|
fs_1.default.mkdirSync(awmDir, { recursive: true });
|
|
155
|
-
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude', installMethod: 'symlink', defaultScope: 'local', channel: 'dev', pins: { base: '1.2.0', equipo: '0.3.0' } }));
|
|
155
|
+
fs_1.default.writeFileSync(path_1.default.join(awmDir, 'preferences.json'), JSON.stringify({ defaultAgent: 'claude-code', installMethod: 'symlink', defaultScope: 'local', channel: 'dev', pins: { base: '1.2.0', equipo: '0.3.0' } }));
|
|
156
156
|
const { machineVersionOpts } = require('../../src/core/versioning');
|
|
157
157
|
expect(machineVersionOpts('base')).toEqual({ pin: '1.2.0', channel: 'dev' });
|
|
158
158
|
expect(machineVersionOpts('equipo')).toEqual({ pin: '0.3.0', channel: 'dev' });
|
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
// cli/tests/integration/codex-provider-isolated.test.ts
|
|
7
|
+
//
|
|
8
|
+
// Task 9, Step 5 — a genuinely-real, isolated-home E2E: unlike the rest of the
|
|
9
|
+
// suite (which stubs installBundle/installHook/etc. to observe call shape),
|
|
10
|
+
// this test lets `awm init --agent codex` run its REAL pipeline end-to-end
|
|
11
|
+
// (real symlinks, real Codex TOML rendering, real preferences writes) against
|
|
12
|
+
// a hand-seeded registry fixture, then asserts on the actual resulting
|
|
13
|
+
// filesystem — never against the real `~/.awm` (CLAUDE.md's "never touch
|
|
14
|
+
// ~/.awm" rule; HOME/AWM_HOME are isolated tmpdirs for the whole test, same
|
|
15
|
+
// pattern as tests/commands/hooks/install.test.ts).
|
|
16
|
+
//
|
|
17
|
+
// Matches the plan's illustrative Task 9 Step 5 snippet: OpenCode is enabled
|
|
18
|
+
// (and really initialized) alongside Claude Code BEFORE the real
|
|
19
|
+
// `awm init --agent codex` run, exactly the "OpenCode already enabled, then
|
|
20
|
+
// Codex joins" scenario this whole plan exists to support. This used to trip
|
|
21
|
+
// install-planner.ts's `assertCompleteSharedGroup` (R14, Task 5) — OpenCode
|
|
22
|
+
// and Codex share the exact same physical skills directory (~/.agents/skills,
|
|
23
|
+
// providers/index.ts) — because `core/init/steps.ts`'s `stepDevCore`/
|
|
24
|
+
// `stepAmbient` passed a `[agent]` singleton as `selectedAgents`, which R14
|
|
25
|
+
// refuses whenever a co-owner of the shared target is independently enabled.
|
|
26
|
+
// That was a confirmed BLOCKER (found in post-implementation QA): it made
|
|
27
|
+
// `awm init --agent codex` structurally fail whenever OpenCode was already
|
|
28
|
+
// enabled, and vice versa. Fixed by having `stepDevCore`/`stepAmbient`
|
|
29
|
+
// compute the complete shared-skill-target group among currently-enabled
|
|
30
|
+
// agents (`install-planner.ts`'s `agentsSharingSkillTarget`, used via
|
|
31
|
+
// `steps.ts`'s `sharedInstallAgents`) instead of a singleton — see
|
|
32
|
+
// tests/core/init/steps.test.ts for the unit-level coverage of that
|
|
33
|
+
// computation. This test proves the real end-to-end flow now succeeds.
|
|
34
|
+
//
|
|
35
|
+
// While building this fixture, this test also caught and fixed a real,
|
|
36
|
+
// previously-untested bug: `runInit` called `assertClaudeBaselinePreserved`
|
|
37
|
+
// unconditionally, even when the run's OWN target was claude-code — meaning
|
|
38
|
+
// any genuinely successful `awm init` (no --agent, the CLI's default/most
|
|
39
|
+
// common path) with real registry content would falsely throw "Claude Code
|
|
40
|
+
// baseline changed during a non-Claude init" and exit 2. See
|
|
41
|
+
// src/commands/init.ts's `beforeClaudeFacts` guard.
|
|
42
|
+
const fs_1 = __importDefault(require("fs"));
|
|
43
|
+
const os_1 = __importDefault(require("os"));
|
|
44
|
+
const path_1 = __importDefault(require("path"));
|
|
45
|
+
describe('codex provider — isolated home E2E (Task 9)', () => {
|
|
46
|
+
let tmpHome;
|
|
47
|
+
let tmpWork;
|
|
48
|
+
let originalHome;
|
|
49
|
+
let originalAwmHome;
|
|
50
|
+
let writeSpy;
|
|
51
|
+
function stdout() {
|
|
52
|
+
return writeSpy.mock.calls.map((c) => c[0]).join('');
|
|
53
|
+
}
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-e2e-home-'));
|
|
56
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-e2e-work-'));
|
|
57
|
+
originalHome = process.env.HOME;
|
|
58
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
59
|
+
process.env.HOME = tmpHome;
|
|
60
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
61
|
+
jest.resetModules();
|
|
62
|
+
// Every runInit/runDoctor call in this suite writes a full text report to
|
|
63
|
+
// stdout — mock it so the real test suite's own output stays readable, and
|
|
64
|
+
// recover it via `stdout()` where a test needs to inspect the JSON payload.
|
|
65
|
+
writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
|
|
66
|
+
});
|
|
67
|
+
afterEach(() => {
|
|
68
|
+
writeSpy.mockRestore();
|
|
69
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
70
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
71
|
+
if (originalHome === undefined)
|
|
72
|
+
delete process.env.HOME;
|
|
73
|
+
else
|
|
74
|
+
process.env.HOME = originalHome;
|
|
75
|
+
if (originalAwmHome === undefined)
|
|
76
|
+
delete process.env.AWM_HOME;
|
|
77
|
+
else
|
|
78
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
79
|
+
});
|
|
80
|
+
/** Hand-seeds a registry content root with everything a real `awm init` needs:
|
|
81
|
+
* hooks (Claude + Codex), the bootstrap skill, one baseline bundle providing
|
|
82
|
+
* both a skill and a Codex-native agent artifact. No git repo involved — the
|
|
83
|
+
* registry cache is content-root-shaped, exactly what a real clone produces. */
|
|
84
|
+
function seedPublicRegistryFixture(root) {
|
|
85
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'hooks'), { recursive: true });
|
|
86
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'hooks/session-start'), '#!/bin/sh\necho "{}"\n', { mode: 0o755 });
|
|
87
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'hooks/run-hook.cmd'), '#!/bin/sh\nexec sh "$1"\n', { mode: 0o755 });
|
|
88
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'hooks/codex-session-start'), '#!/bin/sh\necho "{}"\n', { mode: 0o755 });
|
|
89
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills/using-awm'), { recursive: true });
|
|
90
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills/using-awm/SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
|
|
91
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'skills/development-process'), { recursive: true });
|
|
92
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'skills/development-process/SKILL.md'), '---\nname: development-process\n---\nOrchestrates the dev lifecycle.');
|
|
93
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'agents'), { recursive: true });
|
|
94
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'agents/development-process.md'), '---\nname: development-process\ndescription: Orchestrates the dev lifecycle.\n---\nFollow the AWM development process end to end.');
|
|
95
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'catalog.json'), JSON.stringify({
|
|
96
|
+
version: 1,
|
|
97
|
+
bundles: [{ name: 'dev-core', source: 'bundles/dev-core', version: '1.0.0', scope: 'baseline', visibility: 'public' }],
|
|
98
|
+
}));
|
|
99
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'bundles/dev-core'), { recursive: true });
|
|
100
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'bundles/dev-core/bundle.json'), JSON.stringify({
|
|
101
|
+
name: 'dev-core', description: '', version: '1.0.0', scope: 'baseline', visibility: 'public',
|
|
102
|
+
dependsOn: [], skills: ['development-process'], workflows: [], agents: ['development-process'],
|
|
103
|
+
}));
|
|
104
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
|
|
105
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/registries.json'), JSON.stringify([{ name: 'baseline', remote: 'https://example.invalid/baseline.git' }], null, 2));
|
|
106
|
+
}
|
|
107
|
+
function writePrefs(prefs) {
|
|
108
|
+
const { savePreferences } = require('../../src/utils/config');
|
|
109
|
+
savePreferences(prefs);
|
|
110
|
+
}
|
|
111
|
+
function prefsWith(enabledAgents) {
|
|
112
|
+
return { defaultAgent: enabledAgents[0], enabledAgents, installMethod: 'symlink', defaultScope: 'local' };
|
|
113
|
+
}
|
|
114
|
+
function readPrefs() {
|
|
115
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf8'));
|
|
116
|
+
}
|
|
117
|
+
/** Cheap recursive content snapshot for "did this subtree change at all" assertions
|
|
118
|
+
* (same shape as tests/commands/init.test.ts's snapshotTree). */
|
|
119
|
+
function snapshotTree(dir) {
|
|
120
|
+
if (!fs_1.default.existsSync(dir))
|
|
121
|
+
return null;
|
|
122
|
+
const walk = (p) => {
|
|
123
|
+
const st = fs_1.default.lstatSync(p);
|
|
124
|
+
if (st.isSymbolicLink())
|
|
125
|
+
return { type: 'symlink', target: fs_1.default.readlinkSync(p) };
|
|
126
|
+
if (st.isDirectory()) {
|
|
127
|
+
const entries = fs_1.default.readdirSync(p).sort();
|
|
128
|
+
return { type: 'dir', entries: Object.fromEntries(entries.map((e) => [e, walk(path_1.default.join(p, e))])) };
|
|
129
|
+
}
|
|
130
|
+
return { type: 'file', content: fs_1.default.readFileSync(p, 'utf8') };
|
|
131
|
+
};
|
|
132
|
+
return walk(dir);
|
|
133
|
+
}
|
|
134
|
+
it('initializes Codex beside a real Claude Code + OpenCode install without touching Claude files', async () => {
|
|
135
|
+
expect(process.env.HOME).toBe(tmpHome);
|
|
136
|
+
expect(process.env.AWM_HOME).toBe(path_1.default.join(tmpHome, '.awm'));
|
|
137
|
+
seedPublicRegistryFixture(path_1.default.join(tmpHome, '.awm/registries/baseline'));
|
|
138
|
+
const { runInit } = require('../../src/commands/init');
|
|
139
|
+
// Real, full, unstubbed init for claude-code — installs the real hook,
|
|
140
|
+
// real global skill symlink and real global agent symlink.
|
|
141
|
+
const claudeCode = await runInit({ cwd: tmpWork, yes: true });
|
|
142
|
+
expect(claudeCode).toBeLessThanOrEqual(1);
|
|
143
|
+
expect(fs_1.default.lstatSync(path_1.default.join(tmpHome, '.claude/skills/development-process')).isSymbolicLink()).toBe(true);
|
|
144
|
+
expect(readPrefs().enabledAgents).toEqual(['claude-code']);
|
|
145
|
+
// Real, full, unstubbed init for opencode — enables it alongside Claude
|
|
146
|
+
// Code and materializes its real ~/.agents/skills symlink, the exact
|
|
147
|
+
// physical directory Codex shares (providers/index.ts). This is the
|
|
148
|
+
// scenario the plan's own Task 9 Step 5 snippet illustrates ("OpenCode
|
|
149
|
+
// already enabled, then Codex joins") and the one the BLOCKER fixed in
|
|
150
|
+
// core/init/steps.ts (sharedInstallAgents) makes work end-to-end.
|
|
151
|
+
const opencode = await runInit({ cwd: tmpWork, yes: true, agent: 'opencode' });
|
|
152
|
+
expect(opencode).toBeLessThanOrEqual(1);
|
|
153
|
+
expect(readPrefs().enabledAgents).toEqual(['claude-code', 'opencode']);
|
|
154
|
+
const opencodeSkillLinkBefore = fs_1.default.realpathSync(path_1.default.join(tmpHome, '.agents/skills/development-process'));
|
|
155
|
+
const claudeBefore = snapshotTree(path_1.default.join(tmpHome, '.claude'));
|
|
156
|
+
// Real, full, unstubbed init for codex — real Codex hook, real shared
|
|
157
|
+
// skill symlink (co-owned with OpenCode), real Codex-native TOML agent
|
|
158
|
+
// render. Before the fix, this call threw: "Shared skill target cannot
|
|
159
|
+
// diverge; select the complete shared target group: opencode,codex".
|
|
160
|
+
const codex = await runInit({
|
|
161
|
+
cwd: tmpWork,
|
|
162
|
+
yes: true,
|
|
163
|
+
agent: 'codex',
|
|
164
|
+
// Real `codex --version` execution is unreliable inside this sandboxed
|
|
165
|
+
// test runner (subprocess exec of freshly-written fixture binaries is
|
|
166
|
+
// blocked even with PATH correctly set — verified directly; see task
|
|
167
|
+
// notes). Every other real-pipeline test in the suite (init.test.ts's
|
|
168
|
+
// `codexInitOptions`) uses this exact seam for the same reason — R2's
|
|
169
|
+
// gate itself is unit-tested separately in provider-version.test.ts.
|
|
170
|
+
assertProviderSupported: () => ({ provider: 'codex', version: '0.150.0' }),
|
|
171
|
+
});
|
|
172
|
+
expect(codex).toBeLessThanOrEqual(1);
|
|
173
|
+
expect(readPrefs().enabledAgents).toEqual(['claude-code', 'opencode', 'codex']);
|
|
174
|
+
// R1/R7: Codex's global skill dir (~/.agents/skills, shared by design with
|
|
175
|
+
// OpenCode — providers/index.ts) got a real symlink into the registry —
|
|
176
|
+
// the SAME physical link OpenCode's own init already produced, now
|
|
177
|
+
// co-owned by both agents (R15/R15.1) rather than fought over.
|
|
178
|
+
const skillLink = path_1.default.join(tmpHome, '.agents/skills/development-process');
|
|
179
|
+
expect(fs_1.default.lstatSync(skillLink).isSymbolicLink()).toBe(true);
|
|
180
|
+
expect(fs_1.default.realpathSync(skillLink)).toContain(path_1.default.join('.awm/registries/baseline/skills/development-process'));
|
|
181
|
+
// OpenCode's own skill link is untouched — same physical target, same source.
|
|
182
|
+
expect(fs_1.default.realpathSync(skillLink)).toBe(opencodeSkillLinkBefore);
|
|
183
|
+
// R8: the canonical agent got rendered into Codex's native .toml shape.
|
|
184
|
+
const tomlPath = path_1.default.join(tmpHome, '.codex/agents/development-process.toml');
|
|
185
|
+
const toml = fs_1.default.readFileSync(tomlPath, 'utf8');
|
|
186
|
+
expect(toml).toContain('developer_instructions = """');
|
|
187
|
+
expect(toml).toContain('name = "development-process"');
|
|
188
|
+
// R18: the Codex hook got installed for real.
|
|
189
|
+
const hooksJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.codex/hooks.json'), 'utf8'));
|
|
190
|
+
expect(hooksJson.hooks.SessionStart).toHaveLength(1);
|
|
191
|
+
// R19/R19.1/R23: nothing under Claude's baseline moved during the Codex run.
|
|
192
|
+
expect(snapshotTree(path_1.default.join(tmpHome, '.claude'))).toEqual(claudeBefore);
|
|
193
|
+
});
|
|
194
|
+
it('doctor reports the isolated Codex install as supported/healthy, not against the real ~/.awm', () => {
|
|
195
|
+
seedPublicRegistryFixture(path_1.default.join(tmpHome, '.awm/registries/baseline'));
|
|
196
|
+
writePrefs(prefsWith(['claude-code', 'codex']));
|
|
197
|
+
const { runDoctor } = require('../../src/commands/doctor');
|
|
198
|
+
// R20: doctor resolves a single explicit provider without needing every
|
|
199
|
+
// enabled agent to be independently initialized first.
|
|
200
|
+
runDoctor({ cwd: tmpWork, json: true, agent: 'codex' });
|
|
201
|
+
const report = JSON.parse(stdout());
|
|
202
|
+
expect(report.providers.map((p) => p.id)).toEqual(['codex']);
|
|
203
|
+
const codexReport = report.providers[0];
|
|
204
|
+
expect(codexReport.checks.find((c) => c.id === 'binary.version').state).toBe('missing');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -1,25 +1,50 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
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"));
|
|
3
9
|
const providers_1 = require("../../src/providers");
|
|
4
10
|
describe('Hook configuration in providers', () => {
|
|
11
|
+
const originalHome = process.env.HOME;
|
|
12
|
+
const originalAwmHome = process.env.AWM_HOME;
|
|
13
|
+
let tmpHome;
|
|
14
|
+
let tmpWork;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-hook-home-'));
|
|
17
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-hook-work-'));
|
|
18
|
+
process.env.HOME = tmpHome;
|
|
19
|
+
process.env.AWM_HOME = path_1.default.join(tmpWork, 'awm');
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (originalHome === undefined)
|
|
23
|
+
delete process.env.HOME;
|
|
24
|
+
else
|
|
25
|
+
process.env.HOME = originalHome;
|
|
26
|
+
if (originalAwmHome === undefined)
|
|
27
|
+
delete process.env.AWM_HOME;
|
|
28
|
+
else
|
|
29
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
30
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
31
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
32
|
+
});
|
|
5
33
|
it('claude-code provider defines a HookConfig', () => {
|
|
6
|
-
const cc = providers_1.
|
|
34
|
+
const cc = (0, providers_1.providerFor)('claude-code');
|
|
7
35
|
expect(cc.hooks).toBeDefined();
|
|
8
36
|
expect(cc.hooks?.type).toBe('cc-settings-merge');
|
|
9
37
|
expect(cc.hooks?.eventName).toBe('SessionStart');
|
|
10
38
|
expect(cc.hooks?.matcher).toBe('startup|clear|compact');
|
|
11
39
|
});
|
|
12
|
-
it('
|
|
13
|
-
const cc = providers_1.
|
|
14
|
-
expect(cc.hooks?.settingsPath).
|
|
40
|
+
it('preserves the legacy Claude Code AWM_HOME hooks directory', () => {
|
|
41
|
+
const cc = (0, providers_1.providerFor)('claude-code');
|
|
42
|
+
expect(cc.hooks?.settingsPath).toBe(path_1.default.join(tmpHome, '.claude/settings.json'));
|
|
43
|
+
expect(cc.hooks?.scriptsDir).toBe(path_1.default.join(process.env.AWM_HOME, 'hooks'));
|
|
15
44
|
});
|
|
16
|
-
it('
|
|
17
|
-
|
|
18
|
-
expect(
|
|
19
|
-
});
|
|
20
|
-
it('antigravity and opencode have no hooks (single-harness scope)', () => {
|
|
21
|
-
expect(providers_1.PROVIDERS['antigravity'].hooks).toBeUndefined();
|
|
22
|
-
expect(providers_1.PROVIDERS['opencode'].hooks).toBeUndefined();
|
|
45
|
+
it('antigravity and opencode have no hooks', () => {
|
|
46
|
+
expect((0, providers_1.providerFor)('antigravity').hooks).toBeUndefined();
|
|
47
|
+
expect((0, providers_1.providerFor)('opencode').hooks).toBeUndefined();
|
|
23
48
|
});
|
|
24
49
|
it('getHookConfig returns config for supported target', () => {
|
|
25
50
|
const config = (0, providers_1.getHookConfig)('claude-code');
|
|
@@ -27,19 +52,12 @@ describe('Hook configuration in providers', () => {
|
|
|
27
52
|
expect(config?.type).toBe('cc-settings-merge');
|
|
28
53
|
});
|
|
29
54
|
it('getHookConfig returns undefined for unsupported target', () => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
jest.resetModules();
|
|
38
|
-
const { PROVIDERS: P } = require('../../src/providers');
|
|
39
|
-
expect(P['claude-code'].hooks.scriptsDir).toBe('/tmp/awm-test/hooks');
|
|
40
|
-
if (originalEnv === undefined)
|
|
41
|
-
delete process.env.AWM_HOME;
|
|
42
|
-
else
|
|
43
|
-
process.env.AWM_HOME = originalEnv;
|
|
55
|
+
expect((0, providers_1.getHookConfig)('antigravity')).toBeUndefined();
|
|
56
|
+
});
|
|
57
|
+
it('respects AWM_HOME changes at call time', () => {
|
|
58
|
+
const nextAwmHome = path_1.default.join(tmpWork, 'next-awm');
|
|
59
|
+
process.env.AWM_HOME = nextAwmHome;
|
|
60
|
+
expect((0, providers_1.getHookConfig)('claude-code')?.scriptsDir)
|
|
61
|
+
.toBe(path_1.default.join(nextAwmHome, 'hooks'));
|
|
44
62
|
});
|
|
45
63
|
});
|
|
@@ -3,56 +3,161 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
const providers_1 = require("../../src/providers");
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
7
|
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const providers_1 = require("../../src/providers");
|
|
9
10
|
describe('Providers Routing', () => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
const originalHome = process.env.HOME;
|
|
12
|
+
const originalAwmHome = process.env.AWM_HOME;
|
|
13
|
+
let tmpHome;
|
|
14
|
+
let tmpWork;
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-provider-home-'));
|
|
17
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-provider-work-'));
|
|
18
|
+
process.env.HOME = tmpHome;
|
|
19
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm-test');
|
|
14
20
|
});
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
if (originalHome === undefined)
|
|
23
|
+
delete process.env.HOME;
|
|
24
|
+
else
|
|
25
|
+
process.env.HOME = originalHome;
|
|
26
|
+
if (originalAwmHome === undefined)
|
|
27
|
+
delete process.env.AWM_HOME;
|
|
28
|
+
else
|
|
29
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
30
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
31
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
18
32
|
});
|
|
19
|
-
it('
|
|
20
|
-
const
|
|
21
|
-
|
|
33
|
+
it('resolves Codex paths from the current HOME at call time', () => {
|
|
34
|
+
const first = fs_1.default.mkdtempSync(path_1.default.join(tmpWork, 'awm-home-a-'));
|
|
35
|
+
const second = fs_1.default.mkdtempSync(path_1.default.join(tmpWork, 'awm-home-b-'));
|
|
36
|
+
process.env.HOME = first;
|
|
37
|
+
expect((0, providers_1.getTargetPath)('skill', 'codex', 'global'))
|
|
38
|
+
.toBe(path_1.default.join(first, '.agents/skills'));
|
|
39
|
+
process.env.HOME = second;
|
|
40
|
+
expect((0, providers_1.getTargetPath)('agent', 'codex', 'global'))
|
|
41
|
+
.toBe(path_1.default.join(second, '.codex/agents'));
|
|
22
42
|
});
|
|
23
|
-
it('
|
|
24
|
-
|
|
43
|
+
it('returns a fresh provider graph using current HOME and AWM_HOME', () => {
|
|
44
|
+
const first = (0, providers_1.providers)();
|
|
45
|
+
const nextHome = path_1.default.join(tmpWork, 'next-home');
|
|
46
|
+
const nextAwmHome = path_1.default.join(tmpWork, 'next-awm');
|
|
47
|
+
process.env.HOME = nextHome;
|
|
48
|
+
process.env.AWM_HOME = nextAwmHome;
|
|
49
|
+
const second = (0, providers_1.providers)();
|
|
50
|
+
expect(second).not.toBe(first);
|
|
51
|
+
expect(second.codex.skill.global).toBe(path_1.default.join(nextHome, '.agents/skills'));
|
|
52
|
+
expect(second.codex.hooks?.scriptsDir).toBe(path_1.default.join(nextAwmHome, 'hooks/codex'));
|
|
53
|
+
expect(first.codex.skill.global).toBe(path_1.default.join(tmpHome, '.agents/skills'));
|
|
25
54
|
});
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
55
|
+
it('declares the complete Codex capability metadata', () => {
|
|
56
|
+
expect((0, providers_1.providerFor)('codex')).toEqual({
|
|
57
|
+
label: 'Codex',
|
|
58
|
+
minimumVersion: '0.145.0',
|
|
59
|
+
versionCommand: { command: 'codex', args: ['--version'] },
|
|
60
|
+
skill: {
|
|
61
|
+
global: path_1.default.join(tmpHome, '.agents/skills'),
|
|
62
|
+
local: '.agents/skills',
|
|
63
|
+
renderer: 'link',
|
|
64
|
+
},
|
|
65
|
+
workflow: null,
|
|
66
|
+
agent: {
|
|
67
|
+
global: path_1.default.join(tmpHome, '.codex/agents'),
|
|
68
|
+
local: '.codex/agents',
|
|
69
|
+
renderer: 'codex-agent-toml',
|
|
70
|
+
},
|
|
71
|
+
hooks: {
|
|
72
|
+
type: 'codex-hooks-json',
|
|
73
|
+
settingsPath: path_1.default.join(tmpHome, '.codex/hooks.json'),
|
|
74
|
+
scriptsDir: path_1.default.join(process.env.AWM_HOME, 'hooks/codex'),
|
|
75
|
+
matcher: 'startup|resume|clear|compact',
|
|
76
|
+
eventName: 'SessionStart',
|
|
77
|
+
},
|
|
78
|
+
injection: {
|
|
79
|
+
type: 'managed-agents-md',
|
|
80
|
+
globalPath: path_1.default.join(tmpHome, '.codex/AGENTS.md'),
|
|
81
|
+
localFile: 'AGENTS.md',
|
|
82
|
+
},
|
|
83
|
+
});
|
|
30
84
|
});
|
|
31
|
-
it('
|
|
32
|
-
const
|
|
33
|
-
expect(
|
|
85
|
+
it('keeps Claude Code, OpenCode, and Antigravity contracts unchanged', () => {
|
|
86
|
+
const graph = (0, providers_1.providers)();
|
|
87
|
+
expect(graph.antigravity).toEqual({
|
|
88
|
+
label: 'Antigravity',
|
|
89
|
+
skill: {
|
|
90
|
+
global: path_1.default.join(tmpHome, '.gemini/antigravity/skills'),
|
|
91
|
+
local: '.agent/skills',
|
|
92
|
+
renderer: 'link',
|
|
93
|
+
},
|
|
94
|
+
workflow: {
|
|
95
|
+
global: path_1.default.join(tmpHome, '.gemini/antigravity/global_workflows'),
|
|
96
|
+
local: '.agent/workflows',
|
|
97
|
+
renderer: 'link',
|
|
98
|
+
},
|
|
99
|
+
agent: null,
|
|
100
|
+
});
|
|
101
|
+
expect(graph.opencode).toEqual({
|
|
102
|
+
label: 'OpenCode',
|
|
103
|
+
skill: {
|
|
104
|
+
global: path_1.default.join(tmpHome, '.agents/skills'),
|
|
105
|
+
local: '.agents/skills',
|
|
106
|
+
renderer: 'link',
|
|
107
|
+
},
|
|
108
|
+
workflow: null,
|
|
109
|
+
agent: {
|
|
110
|
+
global: path_1.default.join(tmpHome, '.config/opencode/agents'),
|
|
111
|
+
local: '.agents/profiles',
|
|
112
|
+
renderer: 'link',
|
|
113
|
+
},
|
|
114
|
+
injection: {
|
|
115
|
+
type: 'config-instructions',
|
|
116
|
+
configPath: path_1.default.join(tmpHome, '.config/opencode/opencode.json'),
|
|
117
|
+
field: 'instructions',
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
expect(graph['claude-code']).toEqual({
|
|
121
|
+
label: 'Claude Code',
|
|
122
|
+
skill: {
|
|
123
|
+
global: path_1.default.join(tmpHome, '.claude/skills'),
|
|
124
|
+
local: '.claude/skills',
|
|
125
|
+
renderer: 'link',
|
|
126
|
+
},
|
|
127
|
+
workflow: null,
|
|
128
|
+
agent: {
|
|
129
|
+
global: path_1.default.join(tmpHome, '.claude/agents'),
|
|
130
|
+
local: '.claude/agents',
|
|
131
|
+
renderer: 'link',
|
|
132
|
+
},
|
|
133
|
+
hooks: {
|
|
134
|
+
type: 'cc-settings-merge',
|
|
135
|
+
settingsPath: path_1.default.join(tmpHome, '.claude/settings.json'),
|
|
136
|
+
scriptsDir: path_1.default.join(process.env.AWM_HOME, 'hooks'),
|
|
137
|
+
matcher: 'startup|clear|compact',
|
|
138
|
+
eventName: 'SessionStart',
|
|
139
|
+
},
|
|
140
|
+
injection: { type: 'cc-settings-merge' },
|
|
141
|
+
});
|
|
34
142
|
});
|
|
35
|
-
it('
|
|
36
|
-
|
|
37
|
-
|
|
143
|
+
it('keeps Claude Code and OpenCode destinations unchanged', () => {
|
|
144
|
+
expect((0, providers_1.getTargetPath)('skill', 'claude-code', 'global'))
|
|
145
|
+
.toBe(path_1.default.join(process.env.HOME, '.claude/skills'));
|
|
146
|
+
expect((0, providers_1.getTargetPath)('skill', 'opencode', 'global'))
|
|
147
|
+
.toBe(path_1.default.join(process.env.HOME, '.agents/skills'));
|
|
38
148
|
});
|
|
39
|
-
it('
|
|
40
|
-
|
|
41
|
-
expect(
|
|
149
|
+
it('uses AGENT_TARGETS as the single iterable target catalog', () => {
|
|
150
|
+
expect(providers_1.AGENT_TARGETS).toEqual(['antigravity', 'opencode', 'claude-code', 'codex']);
|
|
151
|
+
expect(Object.keys((0, providers_1.providers)())).toEqual([...providers_1.AGENT_TARGETS]);
|
|
42
152
|
});
|
|
43
|
-
it('throws on
|
|
153
|
+
it('throws on unsupported artifacts', () => {
|
|
154
|
+
expect(() => (0, providers_1.getTargetPath)('workflow', 'opencode', 'global')).toThrow('not supported');
|
|
44
155
|
expect(() => (0, providers_1.getTargetPath)('workflow', 'claude-code', 'global')).toThrow('not supported');
|
|
156
|
+
expect(() => (0, providers_1.getTargetPath)('agent', 'antigravity', 'local')).toThrow('not supported');
|
|
45
157
|
});
|
|
46
|
-
it('
|
|
47
|
-
expect(() => (0, providers_1.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
it('exports PROVIDERS with all three targets', () => {
|
|
51
|
-
expect(Object.keys(providers_1.PROVIDERS)).toEqual(expect.arrayContaining(['antigravity', 'opencode', 'claude-code']));
|
|
52
|
-
});
|
|
53
|
-
it('marks unsupported artifact types as null', () => {
|
|
54
|
-
expect(providers_1.PROVIDERS['antigravity'].agent).toBeNull();
|
|
55
|
-
expect(providers_1.PROVIDERS['opencode'].workflow).toBeNull();
|
|
56
|
-
expect(providers_1.PROVIDERS['claude-code'].workflow).toBeNull();
|
|
158
|
+
it('fails loudly for invalid runtime provider input', () => {
|
|
159
|
+
expect(() => (0, providers_1.providerFor)('unknown-agent')).toThrow('Unknown agent target');
|
|
160
|
+
expect(() => (0, providers_1.getTargetPath)('skill', 'unknown-agent', 'global'))
|
|
161
|
+
.toThrow('Unknown agent target');
|
|
57
162
|
});
|
|
58
163
|
});
|