agentic-workflow-manager 8.2.0 → 8.3.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/hooks/claude.js +47 -31
- package/dist/src/commands/hooks/index.js +1 -1
- package/dist/src/commands/hooks/shared.js +1 -1
- package/dist/src/commands/registry/add.js +6 -1
- package/dist/src/commands/registry/index.js +3 -0
- package/dist/src/commands/sensors/compatibility/live.js +1 -0
- package/dist/src/commands/sensors/compatibility/probe.js +15 -3
- package/dist/src/commands/sensors/status.js +6 -2
- package/dist/src/core/context/orchestrator.js +15 -2
- package/dist/src/core/context/provider.js +29 -1
- package/dist/src/core/context/regenerate.js +29 -0
- package/dist/src/core/orchestrators.js +142 -0
- package/dist/tests/commands/hooks/install-symlink-fallback.test.js +13 -4
- package/dist/tests/commands/hooks/install.test.js +121 -3
- package/dist/tests/commands/hooks/resync.test.js +40 -2
- package/dist/tests/commands/registry/add.test.js +104 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
- package/dist/tests/commands/sensors/status.test.js +58 -0
- package/dist/tests/core/context/orchestrator.test.js +86 -0
- package/dist/tests/core/context/provider.test.js +137 -0
- package/dist/tests/core/context/regenerate.test.js +56 -0
- package/dist/tests/core/orchestrators.test.js +236 -0
- package/package.json +2 -2
|
@@ -51,7 +51,10 @@ describe('installHook (happy path + merge)', () => {
|
|
|
51
51
|
const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
|
|
52
52
|
expect(fs_1.default.existsSync(path_1.default.join(scriptsDir, 'session-start'))).toBe(true);
|
|
53
53
|
expect(fs_1.default.existsSync(path_1.default.join(scriptsDir, 'run-hook.cmd'))).toBe(true);
|
|
54
|
-
|
|
54
|
+
// Task 6: using-awm.md is now a materialized file (buildContext's output), not a
|
|
55
|
+
// symlink to the raw SKILL.md — so declared orchestrators actually reach Claude Code.
|
|
56
|
+
expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(false);
|
|
57
|
+
expect(fs_1.default.readFileSync(path_1.default.join(scriptsDir, 'using-awm.md'), 'utf-8')).toContain('MUST invoke skills.');
|
|
55
58
|
const settings = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.claude/settings.json'), 'utf-8'));
|
|
56
59
|
expect(settings.hooks.SessionStart).toHaveLength(1);
|
|
57
60
|
expect(settings.hooks.SessionStart[0].matcher).toBe('startup|clear|compact');
|
|
@@ -135,11 +138,126 @@ describe('installHook (happy path + merge)', () => {
|
|
|
135
138
|
// Did not create settings.json
|
|
136
139
|
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.claude/settings.json'))).toBe(false);
|
|
137
140
|
});
|
|
138
|
-
|
|
141
|
+
// Task 6: using-awm.md is materialized (buildContext's composed output), never a
|
|
142
|
+
// symlink, regardless of installMethod — superseding the pre-Task-6 "UX choice" of
|
|
143
|
+
// always symlinking this one file even under installMethod 'copy'.
|
|
144
|
+
it('materializes using-awm.md (never a symlink) even when installMethod is copy', () => {
|
|
139
145
|
const { installHook } = require('../../../src/commands/hooks/install');
|
|
140
146
|
installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'copy' });
|
|
141
147
|
const skillPath = path_1.default.join(tmpHome, '.awm/hooks/using-awm.md');
|
|
142
|
-
expect(fs_1.default.lstatSync(skillPath).isSymbolicLink()).toBe(
|
|
148
|
+
expect(fs_1.default.lstatSync(skillPath).isSymbolicLink()).toBe(false);
|
|
149
|
+
expect(fs_1.default.readFileSync(skillPath, 'utf-8')).toContain('MUST invoke skills.');
|
|
150
|
+
});
|
|
151
|
+
// Non-regression net (Task 5, Step 1): fixes the hook's observable
|
|
152
|
+
// contract before Task 6 replaces the symlink with a materialized
|
|
153
|
+
// file write in claude.ts. Verifies R6.1.
|
|
154
|
+
it('el hook queda apuntando a un archivo legible con el contenido de using-awm', () => {
|
|
155
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
156
|
+
installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' });
|
|
157
|
+
const skillDest = path_1.default.join(tmpHome, '.awm/hooks/using-awm.md');
|
|
158
|
+
expect(fs_1.default.existsSync(skillDest)).toBe(true);
|
|
159
|
+
const content = fs_1.default.readFileSync(skillDest, 'utf-8');
|
|
160
|
+
expect(content).toContain('MUST invoke skills.');
|
|
161
|
+
});
|
|
162
|
+
// Task 6, Step 1: closes the bypass — everything buildContext composes (declared
|
|
163
|
+
// orchestrators, Tasks 1-4) must actually reach Claude Code's using-awm.md, not just
|
|
164
|
+
// the raw SKILL.md. Verifies R1.1.
|
|
165
|
+
//
|
|
166
|
+
// Investigation note: the plan's literal sample writes `awm-registry.json` straight
|
|
167
|
+
// into `tmpRegistry` and expects `collectAndWarn()` to pick it up via `listRegistries()`
|
|
168
|
+
// — but `listRegistries()` reads registries.json under AWM_HOME, and `tmpRegistry` here
|
|
169
|
+
// is a bare mkdtemp dir, never registered there via `awm registry add`. Writing the
|
|
170
|
+
// manifest into an unregistered dir would leave `declared` empty and this test green
|
|
171
|
+
// for the wrong reason (or red for the wrong reason, pre-fix). Instead this test
|
|
172
|
+
// registers a REAL listed registry via `writeRegistriesConfig` + `registryContentRoot`
|
|
173
|
+
// (the same pattern `tests/core/context/orchestrator.test.ts` already uses for this
|
|
174
|
+
// exact situation) and installs FROM that registry, so `options.registryRoot` and the
|
|
175
|
+
// one entry `listRegistries()` returns are the same directory — exercising the real
|
|
176
|
+
// collection path end to end.
|
|
177
|
+
it('el hook recibe los orquestadores declarados, no el SKILL.md crudo', () => {
|
|
178
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
179
|
+
const { writeRegistriesConfig, registryContentRoot } = require('../../../src/core/registries');
|
|
180
|
+
writeRegistriesConfig([{ name: 'declaring-test', remote: 'unused' }]);
|
|
181
|
+
const registryRoot = registryContentRoot('declaring-test');
|
|
182
|
+
const regHooks = path_1.default.join(registryRoot, 'hooks');
|
|
183
|
+
const regSkill = path_1.default.join(registryRoot, 'skills/using-awm');
|
|
184
|
+
fs_1.default.mkdirSync(regHooks, { recursive: true });
|
|
185
|
+
fs_1.default.mkdirSync(regSkill, { recursive: true });
|
|
186
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), '#!/usr/bin/env bash\necho "{}"', { mode: 0o755 });
|
|
187
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
|
|
188
|
+
fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
|
|
189
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'awm-registry.json'), JSON.stringify({ orchestrator: { name: 'mi-proceso', appliesWhen: 'al arrancar', terminatesTo: 'development-process' } }));
|
|
190
|
+
installHook({ agent: 'claude-code', registryRoot, installMethod: 'symlink' });
|
|
191
|
+
const content = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm', 'hooks', 'using-awm.md'), 'utf-8');
|
|
192
|
+
expect(content).toContain('mi-proceso'); // la composicion LLEGA a Claude Code
|
|
193
|
+
expect(content).toContain('MUST invoke skills.'); // y el skill sigue entero
|
|
194
|
+
});
|
|
195
|
+
// Finding 2 (code quality review, Task 6): regression-locks what the reviewer verified
|
|
196
|
+
// by hand — a pre-Task-6 install left using-awm.md as a REAL symlink to the registry's
|
|
197
|
+
// raw SKILL.md. writeMaterializedSkill() must fs.unlinkSync() that symlink (removing
|
|
198
|
+
// only the directory entry) before writing the materialized file, never dereference it
|
|
199
|
+
// and clobber the registry's own SKILL.md.
|
|
200
|
+
it('migrates a pre-Task-6 symlinked using-awm.md to a materialized file without touching the registry SKILL.md', () => {
|
|
201
|
+
const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
|
|
202
|
+
fs_1.default.mkdirSync(scriptsDir, { recursive: true });
|
|
203
|
+
const skillDest = path_1.default.join(scriptsDir, 'using-awm.md');
|
|
204
|
+
const registrySkillPath = path_1.default.join(tmpRegistry, 'skills/using-awm/SKILL.md');
|
|
205
|
+
const originalSkillContent = fs_1.default.readFileSync(registrySkillPath, 'utf-8');
|
|
206
|
+
fs_1.default.symlinkSync(registrySkillPath, skillDest, 'file');
|
|
207
|
+
expect(fs_1.default.lstatSync(skillDest).isSymbolicLink()).toBe(true);
|
|
208
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
209
|
+
installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' });
|
|
210
|
+
expect(fs_1.default.lstatSync(skillDest).isSymbolicLink()).toBe(false);
|
|
211
|
+
expect(fs_1.default.readFileSync(skillDest, 'utf-8')).toContain('MUST invoke skills.');
|
|
212
|
+
// The registry's own SKILL.md must be untouched — proves unlinkSync removed only
|
|
213
|
+
// the directory entry and never dereferenced/deleted the symlink's target.
|
|
214
|
+
expect(fs_1.default.existsSync(registrySkillPath)).toBe(true);
|
|
215
|
+
expect(fs_1.default.readFileSync(registrySkillPath, 'utf-8')).toBe(originalSkillContent);
|
|
216
|
+
});
|
|
217
|
+
// Finding 2 (post-implementation-qa, Release 2): R5.1's fail-safe guarantee (a broken
|
|
218
|
+
// declaration in one registry never blocks context construction for the others) was
|
|
219
|
+
// already regression-tested for the generic InjectionOrchestrator/opencode path
|
|
220
|
+
// (tests/core/context/orchestrator.test.ts, 'installContext still succeeds when a
|
|
221
|
+
// DIFFERENT installed registry has a broken declaration') but not through
|
|
222
|
+
// installClaudeHook/resyncClaudeHookFiles — Task 6's own highest-risk change. Mirrors
|
|
223
|
+
// that test's two-registries setup (one valid orchestrator declaration, one broken
|
|
224
|
+
// JSON) via writeRegistriesConfig/registryContentRoot, same as 'el hook recibe los
|
|
225
|
+
// orquestadores declarados...' above, but installs from the VALID registry and asserts
|
|
226
|
+
// the broken sibling never surfaces as a thrown error.
|
|
227
|
+
it('installHook succeeds and materializes the valid registry\'s orchestrator when a sibling registry has a broken declaration', () => {
|
|
228
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
229
|
+
const { writeRegistriesConfig, registryContentRoot } = require('../../../src/core/registries');
|
|
230
|
+
writeRegistriesConfig([
|
|
231
|
+
{ name: 'broken-sibling', remote: 'unused' },
|
|
232
|
+
{ name: 'valid-declaring', remote: 'unused' },
|
|
233
|
+
]);
|
|
234
|
+
// Broken sibling: unparsable awm-registry.json. No hooks/skill content needed —
|
|
235
|
+
// it is never the registryRoot passed to installHook, only a sibling
|
|
236
|
+
// collectAndWarn() walks while gathering declared orchestrators.
|
|
237
|
+
const brokenRoot = registryContentRoot('broken-sibling');
|
|
238
|
+
fs_1.default.mkdirSync(brokenRoot, { recursive: true });
|
|
239
|
+
fs_1.default.writeFileSync(path_1.default.join(brokenRoot, 'awm-registry.json'), '{ not json');
|
|
240
|
+
// Valid registry: the one actually installed from.
|
|
241
|
+
const registryRoot = registryContentRoot('valid-declaring');
|
|
242
|
+
const regHooks = path_1.default.join(registryRoot, 'hooks');
|
|
243
|
+
const regSkill = path_1.default.join(registryRoot, 'skills/using-awm');
|
|
244
|
+
fs_1.default.mkdirSync(regHooks, { recursive: true });
|
|
245
|
+
fs_1.default.mkdirSync(regSkill, { recursive: true });
|
|
246
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), '#!/usr/bin/env bash\necho "{}"', { mode: 0o755 });
|
|
247
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
|
|
248
|
+
fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
|
|
249
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'awm-registry.json'), JSON.stringify({ orchestrator: { name: 'proceso-valido', appliesWhen: 'al arrancar', terminatesTo: 'development-process' } }));
|
|
250
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
251
|
+
let result;
|
|
252
|
+
expect(() => {
|
|
253
|
+
result = installHook({ agent: 'claude-code', registryRoot, installMethod: 'symlink' });
|
|
254
|
+
}).not.toThrow();
|
|
255
|
+
expect(result.status).toBe('installed');
|
|
256
|
+
const content = fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm', 'hooks', 'using-awm.md'), 'utf-8');
|
|
257
|
+
expect(content).toContain('proceso-valido'); // valid registry's declared orchestrator reached Claude Code
|
|
258
|
+
expect(content).toContain('MUST invoke skills.'); // and the skill body is intact
|
|
259
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('warning:')); // broken sibling warned, not thrown
|
|
260
|
+
warnSpy.mockRestore();
|
|
143
261
|
});
|
|
144
262
|
it('throws for unsupported agent target', () => {
|
|
145
263
|
const { installHook } = require('../../../src/commands/hooks/install');
|
|
@@ -70,7 +70,10 @@ describe('resyncInstalledHooks', () => {
|
|
|
70
70
|
expect(synced).toContain('NEW VERSION');
|
|
71
71
|
expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink()).toBe(false);
|
|
72
72
|
expect(() => fs_1.default.accessSync(path_1.default.join(scriptsDir, 'session-start'), fs_1.default.constants.X_OK)).not.toThrow();
|
|
73
|
-
|
|
73
|
+
// Task 6: using-awm.md is materialized (buildContext's output), not a symlink to the
|
|
74
|
+
// raw SKILL.md — resync must not regress the install path back to a raw symlink.
|
|
75
|
+
expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(false);
|
|
76
|
+
expect(fs_1.default.readFileSync(path_1.default.join(scriptsDir, 'using-awm.md'), 'utf-8')).toContain('MUST invoke skills.');
|
|
74
77
|
});
|
|
75
78
|
it('does NOT touch anything when the hook was never installed (no settings entry)', () => {
|
|
76
79
|
writeRegistry('#!/usr/bin/env bash\necho "NEW VERSION"');
|
|
@@ -82,6 +85,39 @@ describe('resyncInstalledHooks', () => {
|
|
|
82
85
|
]);
|
|
83
86
|
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/hooks/session-start'))).toBe(false);
|
|
84
87
|
});
|
|
88
|
+
// Task 6, Step 5 regression guard: the exact staleness the plan calls "el hallazgo mas
|
|
89
|
+
// importante" — if resyncClaudeHookFiles ever regressed back to symlinking the raw
|
|
90
|
+
// SKILL.md (instead of writing buildContext's materialized output, same as
|
|
91
|
+
// installClaudeHook), `awm update` would silently strip declared orchestrators back
|
|
92
|
+
// out of Claude Code's context on the very next resync after a correct install.
|
|
93
|
+
it('re-materializes using-awm.md with declared orchestrators on resync, not a raw-SKILL.md symlink', () => {
|
|
94
|
+
const { writeRegistriesConfig, registryContentRoot } = require('../../../src/core/registries');
|
|
95
|
+
writeRegistriesConfig([{ name: 'declaring-resync-test', remote: 'unused' }]);
|
|
96
|
+
const registryRoot = registryContentRoot('declaring-resync-test');
|
|
97
|
+
const regHooks = path_1.default.join(registryRoot, 'hooks');
|
|
98
|
+
const regSkill = path_1.default.join(registryRoot, 'skills/using-awm');
|
|
99
|
+
fs_1.default.mkdirSync(regHooks, { recursive: true });
|
|
100
|
+
fs_1.default.mkdirSync(regSkill, { recursive: true });
|
|
101
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), '#!/usr/bin/env bash\necho "{}"', { mode: 0o755 });
|
|
102
|
+
fs_1.default.writeFileSync(path_1.default.join(regHooks, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
|
|
103
|
+
fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
|
|
104
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'awm-registry.json'), JSON.stringify({ orchestrator: { name: 'mi-proceso', appliesWhen: 'al arrancar', terminatesTo: 'development-process' } }));
|
|
105
|
+
const { installHook } = require('../../../src/commands/hooks/install');
|
|
106
|
+
installHook({ agent: 'claude-code', registryRoot, installMethod: 'symlink' });
|
|
107
|
+
const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
|
|
108
|
+
const skillDest = path_1.default.join(scriptsDir, 'using-awm.md');
|
|
109
|
+
expect(fs_1.default.readFileSync(skillDest, 'utf-8')).toContain('mi-proceso');
|
|
110
|
+
const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
|
|
111
|
+
const results = resyncInstalledHooks(registryRoot);
|
|
112
|
+
expect(results).toEqual([
|
|
113
|
+
{ agent: 'claude-code', action: 'resynced' },
|
|
114
|
+
{ agent: 'codex', action: 'not-installed' },
|
|
115
|
+
]);
|
|
116
|
+
expect(fs_1.default.lstatSync(skillDest).isSymbolicLink()).toBe(false);
|
|
117
|
+
const content = fs_1.default.readFileSync(skillDest, 'utf-8');
|
|
118
|
+
expect(content).toContain('mi-proceso');
|
|
119
|
+
expect(content).toContain('MUST invoke skills.');
|
|
120
|
+
});
|
|
85
121
|
it('preserves symlink install method', () => {
|
|
86
122
|
writeRegistry('#!/usr/bin/env bash\necho "V2"');
|
|
87
123
|
const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
|
|
@@ -96,7 +132,9 @@ describe('resyncInstalledHooks', () => {
|
|
|
96
132
|
{ agent: 'codex', action: 'not-installed' },
|
|
97
133
|
]);
|
|
98
134
|
expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink()).toBe(true);
|
|
99
|
-
|
|
135
|
+
// Task 6: using-awm.md is materialized regardless of the scripts' install method —
|
|
136
|
+
// it was never governed by `method` in the first place, before or after this change.
|
|
137
|
+
expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(false);
|
|
100
138
|
});
|
|
101
139
|
it('skips with registry-missing when the registry has no hooks dir', () => {
|
|
102
140
|
const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
|
|
@@ -8,6 +8,20 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const os_1 = __importDefault(require("os"));
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
|
+
// @clack/prompts ships as ESM; mock it so Jest (CommonJS mode) can load
|
|
12
|
+
// `commands/registry/index.ts` (same pattern as hooks/router.test.ts and
|
|
13
|
+
// commands/update.test.ts). Only the CLI-wiring describe block below exercises
|
|
14
|
+
// this — the rest of this file talks to `addRegistry` directly and never
|
|
15
|
+
// touches @clack/prompts.
|
|
16
|
+
jest.mock('@clack/prompts', () => ({
|
|
17
|
+
intro: jest.fn(),
|
|
18
|
+
outro: jest.fn(),
|
|
19
|
+
confirm: jest.fn(),
|
|
20
|
+
isCancel: jest.fn(() => false),
|
|
21
|
+
spinner: jest.fn(() => ({ start: jest.fn(), stop: jest.fn() })),
|
|
22
|
+
multiselect: jest.fn(),
|
|
23
|
+
select: jest.fn(),
|
|
24
|
+
}));
|
|
11
25
|
const GIT = (cwd, cmd) => (0, child_process_1.execSync)(`git -c user.email=t@t.t -c user.name=t ${cmd}`, { cwd, stdio: 'pipe' });
|
|
12
26
|
function makeSourceRepo(base, opts) {
|
|
13
27
|
const dir = path_1.default.join(base, `src-${opts.skill ?? 'empty'}`);
|
|
@@ -212,6 +226,33 @@ describe('addRegistry', () => {
|
|
|
212
226
|
const { readRegistriesConfig } = require('../../../src/core/registries');
|
|
213
227
|
expect(readRegistriesConfig().map((r) => r.name)).toEqual(['personal']);
|
|
214
228
|
});
|
|
229
|
+
it('reporta una declaracion de orquestador invalida sin abortar la instalacion', async () => {
|
|
230
|
+
// Registry local con layout valido y declaracion rota
|
|
231
|
+
const src = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-'));
|
|
232
|
+
fs_1.default.mkdirSync(path_1.default.join(src, 'skills/mi-proceso'), { recursive: true });
|
|
233
|
+
fs_1.default.writeFileSync(path_1.default.join(src, 'skills/mi-proceso/SKILL.md'), '---\nname: mi-proceso\n---\nx');
|
|
234
|
+
fs_1.default.writeFileSync(path_1.default.join(src, 'awm-registry.json'), JSON.stringify({ orchestrator: { name: 'roto' } }));
|
|
235
|
+
GIT(src, 'init -q');
|
|
236
|
+
GIT(src, 'add -A');
|
|
237
|
+
GIT(src, 'commit -qm init');
|
|
238
|
+
const { addRegistry } = require('../../../src/commands/registry/add');
|
|
239
|
+
const result = await addRegistry(src, 'roto-reg');
|
|
240
|
+
expect(result.ok).toBe(true); // la instalacion NO se aborta por esto
|
|
241
|
+
expect(result.ok && result.orchestratorDiagnostics).toBeDefined();
|
|
242
|
+
expect(result.ok && result.orchestratorDiagnostics.join('\n')).toMatch(/appliesWhen/);
|
|
243
|
+
});
|
|
244
|
+
it('un registry sin declaracion se instala sin diagnosticos', async () => {
|
|
245
|
+
const src = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-'));
|
|
246
|
+
fs_1.default.mkdirSync(path_1.default.join(src, 'skills/otro'), { recursive: true });
|
|
247
|
+
fs_1.default.writeFileSync(path_1.default.join(src, 'skills/otro/SKILL.md'), '---\nname: otro\n---\nx');
|
|
248
|
+
GIT(src, 'init -q');
|
|
249
|
+
GIT(src, 'add -A');
|
|
250
|
+
GIT(src, 'commit -qm init');
|
|
251
|
+
const { addRegistry } = require('../../../src/commands/registry/add');
|
|
252
|
+
const result = await addRegistry(src, 'sin-decl');
|
|
253
|
+
expect(result.ok).toBe(true);
|
|
254
|
+
expect(result.ok && (result.orchestratorDiagnostics ?? [])).toEqual([]);
|
|
255
|
+
});
|
|
215
256
|
});
|
|
216
257
|
describe('registry add + bundle install (post-add flow)', () => {
|
|
217
258
|
let tmpHome;
|
|
@@ -315,3 +356,66 @@ describe('registry add + bundle install (post-add flow)', () => {
|
|
|
315
356
|
expect(fs_1.default.existsSync(skillLink)).toBe(false);
|
|
316
357
|
});
|
|
317
358
|
});
|
|
359
|
+
describe('registry add — CLI wiring prints orchestrator diagnostics', () => {
|
|
360
|
+
let tmpHome;
|
|
361
|
+
let tmpWork;
|
|
362
|
+
let originalHome;
|
|
363
|
+
let originalAwmHome;
|
|
364
|
+
beforeEach(() => {
|
|
365
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regadd-cli-home-'));
|
|
366
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regadd-cli-work-'));
|
|
367
|
+
originalHome = process.env.HOME;
|
|
368
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
369
|
+
process.env.HOME = tmpHome;
|
|
370
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
371
|
+
jest.resetModules();
|
|
372
|
+
});
|
|
373
|
+
afterEach(() => {
|
|
374
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
375
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
376
|
+
if (originalHome === undefined)
|
|
377
|
+
delete process.env.HOME;
|
|
378
|
+
else
|
|
379
|
+
process.env.HOME = originalHome;
|
|
380
|
+
if (originalAwmHome === undefined)
|
|
381
|
+
delete process.env.AWM_HOME;
|
|
382
|
+
else
|
|
383
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
384
|
+
});
|
|
385
|
+
// Task 2 added a `console.warn` loop after a successful `awm registry add` that
|
|
386
|
+
// prints each orchestrator diagnostic (`result.orchestratorDiagnostics`) returned
|
|
387
|
+
// by `addRegistry`. `addRegistry` itself is already covered above ("reporta una
|
|
388
|
+
// declaracion de orquestador invalida sin abortar la instalacion"), but nothing
|
|
389
|
+
// proved the CLI command actually surfaces those diagnostics to the user — this
|
|
390
|
+
// drives the real commander-wired `registry add` action end-to-end (same pattern
|
|
391
|
+
// as tests/commands/sensors/index.test.ts's `program.parseAsync`).
|
|
392
|
+
it('prints each orchestrator diagnostic via console.warn after a successful add', async () => {
|
|
393
|
+
const src = fs_1.default.mkdtempSync(path_1.default.join(tmpWork, 'awm-cli-src-'));
|
|
394
|
+
fs_1.default.mkdirSync(path_1.default.join(src, 'skills/mi-proceso'), { recursive: true });
|
|
395
|
+
fs_1.default.writeFileSync(path_1.default.join(src, 'skills/mi-proceso/SKILL.md'), '---\nname: mi-proceso\n---\nx');
|
|
396
|
+
// Broken orchestrator declaration: missing appliesWhen/terminatesTo.
|
|
397
|
+
fs_1.default.writeFileSync(path_1.default.join(src, 'awm-registry.json'), JSON.stringify({ orchestrator: { name: 'roto' } }));
|
|
398
|
+
GIT(src, 'init -q');
|
|
399
|
+
GIT(src, 'add -A');
|
|
400
|
+
GIT(src, 'commit -qm init');
|
|
401
|
+
const { Command } = require('commander');
|
|
402
|
+
const { registerRegistryCommand } = require('../../../src/commands/registry/index');
|
|
403
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
404
|
+
const logSpy = jest.spyOn(console, 'log').mockImplementation(() => undefined);
|
|
405
|
+
try {
|
|
406
|
+
const program = new Command();
|
|
407
|
+
registerRegistryCommand(program);
|
|
408
|
+
await program.parseAsync(['node', 'awm', 'registry', 'add', src, '--name', 'cli-roto', '--no-install']);
|
|
409
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
410
|
+
const warned = warnSpy.mock.calls.map((c) => String(c[0])).join('\n');
|
|
411
|
+
expect(warned).toMatch(/appliesWhen/);
|
|
412
|
+
expect(warned).toMatch(/terminatesTo/);
|
|
413
|
+
const { readRegistriesConfig } = require('../../../src/core/registries');
|
|
414
|
+
expect(readRegistriesConfig().map((r) => r.name)).toEqual(['cli-roto']);
|
|
415
|
+
}
|
|
416
|
+
finally {
|
|
417
|
+
warnSpy.mockRestore();
|
|
418
|
+
logSpy.mockRestore();
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
});
|
|
@@ -53,4 +53,31 @@ describe('runCompatibilityProbe', () => {
|
|
|
53
53
|
await (0, probe_1.runCompatibilityProbe)({ kind: 'version' }, { ...evidence, toolExecutable, toolResolution: 'python-environment' }, fakeExecutor);
|
|
54
54
|
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: toolExecutable, resolution: 'python-environment', args: ['--version'] }), expect.any(Object));
|
|
55
55
|
});
|
|
56
|
+
// A pack asset is never named the tool's own default (e.g. `eslint.config.awm.mjs`,
|
|
57
|
+
// not `eslint.config.js`) so the real command always pins it via `--config`. A probe
|
|
58
|
+
// that omits the same flag never finds a config to load and always reports
|
|
59
|
+
// not-matched — a false negative on every AWM-configured project. The probe must
|
|
60
|
+
// mirror the variant's own `--config` argument, not guess a bare invocation.
|
|
61
|
+
it('carries the variant\'s --config argument into an eslint-print-config probe', async () => {
|
|
62
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'eslint-print-config' }, {
|
|
63
|
+
...evidence, variantArgs: ['.', '--config', 'eslint.config.awm.mjs', '--cache', '--format', 'json'],
|
|
64
|
+
}, fakeExecutor);
|
|
65
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ args: ['--config', 'eslint.config.awm.mjs', '--print-config', 'eslint.config.js'] }), expect.any(Object));
|
|
66
|
+
});
|
|
67
|
+
it('carries the variant\'s --config argument into a typescript-show-config probe', async () => {
|
|
68
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'typescript-show-config' }, {
|
|
69
|
+
...evidence, variantArgs: ['--config', 'tsconfig.awm.json', '--noEmit'],
|
|
70
|
+
}, fakeExecutor);
|
|
71
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ args: ['--config', 'tsconfig.awm.json', '--showConfig'] }), expect.any(Object));
|
|
72
|
+
});
|
|
73
|
+
it('carries the variant\'s --config argument into a semgrep-validate probe', async () => {
|
|
74
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'semgrep-validate' }, {
|
|
75
|
+
...evidence, variantArgs: ['--config', '.semgrep.awm.yml', '--json', '.'],
|
|
76
|
+
}, fakeExecutor);
|
|
77
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ args: ['--config', '.semgrep.awm.yml', '--validate'] }), expect.any(Object));
|
|
78
|
+
});
|
|
79
|
+
it('omits --config from the probe when the variant command declares none', async () => {
|
|
80
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'eslint-print-config' }, { ...evidence, variantArgs: ['.', '--cache'] }, fakeExecutor);
|
|
81
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ args: ['--print-config', 'eslint.config.js'] }), expect.any(Object));
|
|
82
|
+
});
|
|
56
83
|
});
|
|
@@ -182,6 +182,64 @@ describe('computeSensorStatus', () => {
|
|
|
182
182
|
expect(result.pack).toBe('python');
|
|
183
183
|
expect(result.checks).toEqual({});
|
|
184
184
|
});
|
|
185
|
+
it('resolves v2 compatibility and structured-command assets against packageRoot in a monorepo', async () => {
|
|
186
|
+
// Monorepo support (mirrors run.ts/init.ts): the manifest lives at the repo
|
|
187
|
+
// root, but package.json/node_modules/the config asset all live under the
|
|
188
|
+
// declared packageRoot subdirectory. Without threading packageRoot through,
|
|
189
|
+
// detection finds no package.json at the manifest's own directory and every
|
|
190
|
+
// sensor reads back "not-applicable" — a false compatibility drift on every
|
|
191
|
+
// check, even immediately after a correct `awm sensors init --package-root`.
|
|
192
|
+
const previousHome = process.env.AWM_HOME;
|
|
193
|
+
const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-home-'));
|
|
194
|
+
try {
|
|
195
|
+
process.env.AWM_HOME = home;
|
|
196
|
+
const registry = path_1.default.join(home, 'registries', 'baseline');
|
|
197
|
+
fs_1.default.mkdirSync(path_1.default.join(registry, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
198
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'https://example.test/baseline.git' }]));
|
|
199
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'sensor-packs', 'js-ts', 'pack.json'), JSON.stringify({
|
|
200
|
+
schemaVersion: 2, name: 'js-ts', description: 'test', detects: ['package.json'],
|
|
201
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
|
|
202
|
+
sensors: { lint: {
|
|
203
|
+
applicability: { allFiles: ['package.json'] },
|
|
204
|
+
variants: [{
|
|
205
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
206
|
+
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
|
|
207
|
+
assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'package-script-present' },
|
|
208
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--config', 'eslint.config.awm.mjs'] },
|
|
209
|
+
}],
|
|
210
|
+
} },
|
|
211
|
+
}));
|
|
212
|
+
const packageDir = path_1.default.join(tmpDir, 'cli');
|
|
213
|
+
fs_1.default.mkdirSync(packageDir, { recursive: true });
|
|
214
|
+
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' }, scripts: { lint: 'eslint .' } }));
|
|
215
|
+
fs_1.default.mkdirSync(path_1.default.join(packageDir, 'node_modules', 'eslint'), { recursive: true });
|
|
216
|
+
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.4.1' }));
|
|
217
|
+
fs_1.default.mkdirSync(path_1.default.join(packageDir, 'node_modules', '.bin'), { recursive: true });
|
|
218
|
+
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'node_modules', '.bin', 'eslint'), '');
|
|
219
|
+
fs_1.default.writeFileSync(path_1.default.join(packageDir, 'eslint.config.awm.mjs'), 'export default []');
|
|
220
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
221
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
222
|
+
schemaVersion: 2, pack: 'js-ts', packageRoot: 'cli', registryRoot: registry,
|
|
223
|
+
sensors: { lint: {
|
|
224
|
+
enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--config', 'eslint.config.awm.mjs'] },
|
|
225
|
+
assets: ['eslint.config.awm.mjs'],
|
|
226
|
+
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.4.1', runtimeVersion: process.versions.node, certifiedRange: '>=10.0.0 <11.0.0', evidence: [] },
|
|
227
|
+
} },
|
|
228
|
+
}));
|
|
229
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
230
|
+
expect(result.checks.lint).toMatchObject({ ok: true });
|
|
231
|
+
expect(result.overall).toBe('READY');
|
|
232
|
+
expect(runCommand).not.toHaveBeenCalled();
|
|
233
|
+
expect(runStructuredCommand).not.toHaveBeenCalled();
|
|
234
|
+
}
|
|
235
|
+
finally {
|
|
236
|
+
if (previousHome === undefined)
|
|
237
|
+
delete process.env.AWM_HOME;
|
|
238
|
+
else
|
|
239
|
+
process.env.AWM_HOME = previousHome;
|
|
240
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
185
243
|
it('marks disabled sensors as ok', async () => {
|
|
186
244
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
187
245
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
@@ -17,6 +17,26 @@ jest.mock('../../../src/commands/hooks/status', () => ({
|
|
|
17
17
|
const install_1 = require("../../../src/commands/hooks/install");
|
|
18
18
|
const uninstall_1 = require("../../../src/commands/hooks/uninstall");
|
|
19
19
|
const status_1 = require("../../../src/commands/hooks/status");
|
|
20
|
+
const registries_1 = require("../../../src/core/registries");
|
|
21
|
+
// inputFor/statusInputFor now call listRegistries() (collectDeclaredOrchestrators) on every
|
|
22
|
+
// install/status. Without isolating AWM_HOME here, that reads the REAL ~/.awm/registries.json
|
|
23
|
+
// of whatever machine runs the suite — exactly what CLAUDE.md's testing rule forbids ("ningun
|
|
24
|
+
// test puede tocar el ~/.awm real. Todos usan tmpdirs aislados"). An empty isolated AWM_HOME
|
|
25
|
+
// makes listRegistries() return [] deterministically, matching pre-change behavior everywhere.
|
|
26
|
+
let isolatedAwmHome;
|
|
27
|
+
let originalAwmHomeEnv;
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
isolatedAwmHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-orch-isolated-home-'));
|
|
30
|
+
originalAwmHomeEnv = process.env.AWM_HOME;
|
|
31
|
+
process.env.AWM_HOME = isolatedAwmHome;
|
|
32
|
+
});
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
if (originalAwmHomeEnv === undefined)
|
|
35
|
+
delete process.env.AWM_HOME;
|
|
36
|
+
else
|
|
37
|
+
process.env.AWM_HOME = originalAwmHomeEnv;
|
|
38
|
+
fs_1.default.rmSync(isolatedAwmHome, { recursive: true, force: true });
|
|
39
|
+
});
|
|
20
40
|
function tmpRegistry() {
|
|
21
41
|
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-orch-'));
|
|
22
42
|
const dir = path_1.default.join(root, 'skills/using-awm');
|
|
@@ -221,3 +241,69 @@ describe('InjectionOrchestrator (local scope materializes under the project, not
|
|
|
221
241
|
expect(orch.contextStatus(op)).toBe('injected');
|
|
222
242
|
});
|
|
223
243
|
});
|
|
244
|
+
describe('InjectionOrchestrator (declared orchestrators from an installed registry reach status too)', () => {
|
|
245
|
+
// Regression guard for R5.1's wiring: installContext (inputFor) and contextStatus
|
|
246
|
+
// (statusInputFor) must collect the SAME declared-orchestrator set. If only inputFor
|
|
247
|
+
// did, the materialized file's hash would include the declared orchestrator while
|
|
248
|
+
// statusInputFor's "expected" hash would not — contextStatus would report 'stale'
|
|
249
|
+
// forever, even immediately after a correct install.
|
|
250
|
+
it('contextStatus reports injected (not stale) right after installing with a declared orchestrator', () => {
|
|
251
|
+
// The global beforeEach already points AWM_HOME at an isolated, empty home —
|
|
252
|
+
// register one real registry in it so listRegistries()/collectDeclaredOrchestrators
|
|
253
|
+
// actually has something to find.
|
|
254
|
+
(0, registries_1.writeRegistriesConfig)([{ name: 'declaring', remote: 'unused' }]);
|
|
255
|
+
const registryRoot = (0, registries_1.registryContentRoot)('declaring');
|
|
256
|
+
fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'skills/using-awm'), { recursive: true });
|
|
257
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'skills/using-awm/SKILL.md'), '---\nversion: "1.0.0"\n---\nBODY');
|
|
258
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'awm-registry.json'), JSON.stringify({
|
|
259
|
+
orchestrator: { name: 'mi-proceso', appliesWhen: 'al arrancar', terminatesTo: 'development-process' },
|
|
260
|
+
}));
|
|
261
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-oc-declared-'));
|
|
262
|
+
const configPath = path_1.default.join(dir, 'opencode.json');
|
|
263
|
+
const absPath = path_1.default.join(dir, 'awm-context.md');
|
|
264
|
+
const orch = new orchestrator_1.InjectionOrchestrator({
|
|
265
|
+
providerOverride: {
|
|
266
|
+
label: 'OpenCode', configHome: { envVar: null, dir: '.test', resolved: '/tmp/test-config-home' }, skill: { global: '', local: '', renderer: 'link' }, workflow: null, agent: null,
|
|
267
|
+
injection: { type: 'config-instructions', configPath, field: 'instructions' },
|
|
268
|
+
},
|
|
269
|
+
contextPathOverride: absPath,
|
|
270
|
+
});
|
|
271
|
+
const args = { agent: 'opencode', scope: 'global', registryRoot, installMethod: 'symlink', profileExtensions: [] };
|
|
272
|
+
orch.installContext(args);
|
|
273
|
+
expect(fs_1.default.readFileSync(absPath, 'utf-8')).toContain('mi-proceso');
|
|
274
|
+
expect(orch.contextStatus(args)).toBe('injected');
|
|
275
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
276
|
+
});
|
|
277
|
+
it('installContext still succeeds when a DIFFERENT installed registry has a broken declaration', () => {
|
|
278
|
+
// Two registries installed: "broken" has an unparsable awm-registry.json, "clean"
|
|
279
|
+
// is the one actually being operated on. The broken one must not block install.
|
|
280
|
+
(0, registries_1.writeRegistriesConfig)([
|
|
281
|
+
{ name: 'broken', remote: 'unused' },
|
|
282
|
+
{ name: 'clean', remote: 'unused' },
|
|
283
|
+
]);
|
|
284
|
+
const brokenRoot = (0, registries_1.registryContentRoot)('broken');
|
|
285
|
+
fs_1.default.mkdirSync(brokenRoot, { recursive: true });
|
|
286
|
+
fs_1.default.writeFileSync(path_1.default.join(brokenRoot, 'awm-registry.json'), '{ not json');
|
|
287
|
+
const registryRoot = (0, registries_1.registryContentRoot)('clean');
|
|
288
|
+
fs_1.default.mkdirSync(path_1.default.join(registryRoot, 'skills/using-awm'), { recursive: true });
|
|
289
|
+
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'skills/using-awm/SKILL.md'), '---\nversion: "1.0.0"\n---\nBODY');
|
|
290
|
+
const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-oc-brokensibling-'));
|
|
291
|
+
const configPath = path_1.default.join(dir, 'opencode.json');
|
|
292
|
+
const absPath = path_1.default.join(dir, 'awm-context.md');
|
|
293
|
+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { });
|
|
294
|
+
const orch = new orchestrator_1.InjectionOrchestrator({
|
|
295
|
+
providerOverride: {
|
|
296
|
+
label: 'OpenCode', configHome: { envVar: null, dir: '.test', resolved: '/tmp/test-config-home' }, skill: { global: '', local: '', renderer: 'link' }, workflow: null, agent: null,
|
|
297
|
+
injection: { type: 'config-instructions', configPath, field: 'instructions' },
|
|
298
|
+
},
|
|
299
|
+
contextPathOverride: absPath,
|
|
300
|
+
});
|
|
301
|
+
const args = { agent: 'opencode', scope: 'global', registryRoot, installMethod: 'symlink', profileExtensions: [] };
|
|
302
|
+
expect(() => orch.installContext(args)).not.toThrow();
|
|
303
|
+
expect(fs_1.default.readFileSync(absPath, 'utf-8')).toContain('BODY');
|
|
304
|
+
expect(orch.contextStatus(args)).toBe('injected');
|
|
305
|
+
expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('warning:'));
|
|
306
|
+
warnSpy.mockRestore();
|
|
307
|
+
fs_1.default.rmSync(dir, { recursive: true, force: true });
|
|
308
|
+
});
|
|
309
|
+
});
|