agentic-workflow-manager 9.14.1 → 9.15.1
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/context-budget/budget.js +50 -4
- package/dist/src/commands/doctor.js +4 -1
- package/dist/src/commands/hooks/cursor.js +359 -0
- package/dist/src/commands/hooks/install.js +3 -0
- package/dist/src/commands/hooks/resync.js +10 -0
- package/dist/src/commands/hooks/status.js +3 -0
- package/dist/src/commands/hooks/uninstall.js +3 -0
- package/dist/src/commands/update.js +4 -0
- package/dist/src/commands/watch/index.js +10 -0
- package/dist/src/core/admission/index.js +8 -3
- package/dist/src/core/context/strategies/codex-agents.js +6 -2
- package/dist/src/core/cursor-budgets.js +8 -0
- package/dist/src/core/dashboard/sanitize.js +2 -2
- package/dist/src/core/diagnostics/provider-checks.js +11 -2
- package/dist/src/core/init/migrate-cursor-mdc.js +72 -0
- package/dist/src/core/init/steps.js +4 -0
- package/dist/src/core/journal/durable-custody.js +19 -0
- package/dist/src/providers/index.js +18 -4
- package/dist/tests/commands/add.test.js +5 -7
- package/dist/tests/commands/context-budget/budget.test.js +68 -0
- package/dist/tests/commands/doctor.test.js +2 -2
- package/dist/tests/commands/hooks/cursor.test.js +257 -0
- package/dist/tests/commands/hooks/resync.test.js +8 -0
- package/dist/tests/commands/init.test.js +23 -22
- package/dist/tests/commands/plan/index.test.js +56 -2
- package/dist/tests/core/admission/index.test.js +19 -0
- package/dist/tests/core/bundle-install.test.js +8 -14
- package/dist/tests/core/context/strategies/codex-agents.test.js +31 -12
- package/dist/tests/core/diagnostics/provider-tier.test.js +46 -96
- package/dist/tests/core/diagnostics/rendered-artifact-visibility.test.js +6 -4
- package/dist/tests/core/init/migrate-cursor-mdc.test.js +74 -0
- package/dist/tests/core/install-planner.test.js +16 -12
- package/dist/tests/core/journal/durable-custody.test.js +44 -0
- package/dist/tests/integration/cursor-subset-gate.test.js +123 -0
- package/dist/tests/providers/index.test.js +34 -21
- package/dist/tests/structural/provider-paths-honor-config-home.test.js +3 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/secure_fs.node +0 -0
- package/prebuilds/darwin-x64/secure_fs.node +0 -0
- package/prebuilds/win32-arm64/secure_fs.node +0 -0
- package/prebuilds/win32-x64/secure_fs.node +0 -0
|
@@ -83,6 +83,10 @@ describe('install-planner', () => {
|
|
|
83
83
|
const group = (0, install_planner_1.agentsSharingSkillTarget)('opencode', ['opencode', 'codex', 'copilot'], 'global', tmpWork);
|
|
84
84
|
expect(group.sort()).toEqual(['codex', 'opencode']);
|
|
85
85
|
});
|
|
86
|
+
it('includes cursor in the shared ~/.agents/skills group with codex and opencode (Plan B R7)', () => {
|
|
87
|
+
const group = (0, install_planner_1.agentsSharingSkillTarget)('cursor', ['cursor', 'codex', 'opencode', 'claude-code'], 'global', tmpWork);
|
|
88
|
+
expect(group.sort()).toEqual(['codex', 'cursor', 'opencode']);
|
|
89
|
+
});
|
|
86
90
|
});
|
|
87
91
|
describe('planInstall', () => {
|
|
88
92
|
it('deduplicates the OpenCode/Codex physical skill write and reports both owners', () => {
|
|
@@ -220,7 +224,7 @@ describe('install-planner', () => {
|
|
|
220
224
|
})).toThrow(/physical target already claimed by a different source/);
|
|
221
225
|
});
|
|
222
226
|
describe('renderer-driven filename computation (Task 4.3)', () => {
|
|
223
|
-
it('
|
|
227
|
+
it('installs Cursor skills via link under .agents/skills (shared group, Plan B R1/R2)', () => {
|
|
224
228
|
const plan = (0, install_planner_1.planInstall)({
|
|
225
229
|
artifacts: [skillArtifact('development-process')],
|
|
226
230
|
selectedAgents: ['cursor'],
|
|
@@ -229,8 +233,8 @@ describe('install-planner', () => {
|
|
|
229
233
|
projectRoot: tmpWork,
|
|
230
234
|
method: 'symlink',
|
|
231
235
|
});
|
|
232
|
-
expect(plan.operations[0].targetPath).toBe(path_1.default.join(tmpWork, '.
|
|
233
|
-
expect(plan.operations[0].renderer).toBe('
|
|
236
|
+
expect(plan.operations[0].targetPath).toBe(path_1.default.join(tmpWork, '.agents', 'skills', 'development-process'));
|
|
237
|
+
expect(plan.operations[0].renderer).toBe('link');
|
|
234
238
|
});
|
|
235
239
|
it('renders the Copilot skill target with a .instructions.md extension (not .md.instructions.md)', () => {
|
|
236
240
|
const plan = (0, install_planner_1.planInstall)({
|
|
@@ -261,13 +265,13 @@ describe('install-planner', () => {
|
|
|
261
265
|
});
|
|
262
266
|
expect(path_1.default.basename(plan.operations[0].targetPath)).toBe('development-process.instructions.md');
|
|
263
267
|
});
|
|
264
|
-
it('does not truncate
|
|
268
|
+
it('does not truncate a Cursor link installName with an embedded, non-extension dot', () => {
|
|
265
269
|
// Regression: physicalTarget used to derive the base name via
|
|
266
270
|
// path.parse(...).name, which strips everything after the LAST
|
|
267
271
|
// dot — not just a genuine trailing .md extension. A skill
|
|
268
272
|
// literally named `v1.2-migration` would silently truncate to
|
|
269
273
|
// `v1`, dropping `2-migration` and risking a collision with any
|
|
270
|
-
// other skill named `v1`.
|
|
274
|
+
// other skill named `v1`. Cursor now uses link under .agents/skills.
|
|
271
275
|
const dottedName = {
|
|
272
276
|
name: 'v1.2-migration',
|
|
273
277
|
installName: 'v1.2-migration',
|
|
@@ -282,10 +286,10 @@ describe('install-planner', () => {
|
|
|
282
286
|
projectRoot: tmpWork,
|
|
283
287
|
method: 'symlink',
|
|
284
288
|
});
|
|
285
|
-
expect(path_1.default.basename(plan.operations[0].targetPath)).toBe('v1.2-migration
|
|
286
|
-
expect(path_1.default.basename(plan.operations[0].targetPath)).not.toBe('v1
|
|
289
|
+
expect(path_1.default.basename(plan.operations[0].targetPath)).toBe('v1.2-migration');
|
|
290
|
+
expect(path_1.default.basename(plan.operations[0].targetPath)).not.toBe('v1');
|
|
287
291
|
});
|
|
288
|
-
it('still strips a real trailing .md extension for
|
|
292
|
+
it('still strips a real trailing .md extension for Copilot (not using-awm.md.instructions.md)', () => {
|
|
289
293
|
const withMdExtension = {
|
|
290
294
|
name: 'using-awm',
|
|
291
295
|
installName: 'using-awm.md',
|
|
@@ -294,14 +298,14 @@ describe('install-planner', () => {
|
|
|
294
298
|
};
|
|
295
299
|
const plan = (0, install_planner_1.planInstall)({
|
|
296
300
|
artifacts: [withMdExtension],
|
|
297
|
-
selectedAgents: ['
|
|
298
|
-
enabledAgents: ['
|
|
301
|
+
selectedAgents: ['copilot'],
|
|
302
|
+
enabledAgents: ['copilot'],
|
|
299
303
|
scope: 'local',
|
|
300
304
|
projectRoot: tmpWork,
|
|
301
305
|
method: 'symlink',
|
|
302
306
|
});
|
|
303
|
-
expect(path_1.default.basename(plan.operations[0].targetPath)).toBe('using-awm.
|
|
304
|
-
expect(path_1.default.basename(plan.operations[0].targetPath)).not.toBe('using-awm.md.
|
|
307
|
+
expect(path_1.default.basename(plan.operations[0].targetPath)).toBe('using-awm.instructions.md');
|
|
308
|
+
expect(path_1.default.basename(plan.operations[0].targetPath)).not.toBe('using-awm.md.instructions.md');
|
|
305
309
|
});
|
|
306
310
|
});
|
|
307
311
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const commander_1 = require("commander");
|
|
4
|
+
const durable_custody_1 = require("../../../src/core/journal/durable-custody");
|
|
5
|
+
const watch_1 = require("../../../src/commands/watch");
|
|
6
|
+
describe('durable custody suspension', () => {
|
|
7
|
+
const original = process.env.AWM_ALLOW_DURABLE_CUSTODY;
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
if (original === undefined)
|
|
10
|
+
delete process.env.AWM_ALLOW_DURABLE_CUSTODY;
|
|
11
|
+
else
|
|
12
|
+
process.env.AWM_ALLOW_DURABLE_CUSTODY = original;
|
|
13
|
+
});
|
|
14
|
+
it('is suspended for operators (env unset) and only re-enabled by the Jest harness', () => {
|
|
15
|
+
delete process.env.AWM_ALLOW_DURABLE_CUSTODY;
|
|
16
|
+
expect((0, durable_custody_1.isDurableCustodySuspended)()).toBe(true);
|
|
17
|
+
process.env.AWM_ALLOW_DURABLE_CUSTODY = '0';
|
|
18
|
+
expect((0, durable_custody_1.isDurableCustodySuspended)()).toBe(true);
|
|
19
|
+
process.env.AWM_ALLOW_DURABLE_CUSTODY = '1';
|
|
20
|
+
expect((0, durable_custody_1.isDurableCustodySuspended)()).toBe(false);
|
|
21
|
+
});
|
|
22
|
+
it('refuses every awm watch entry point while suspended', async () => {
|
|
23
|
+
delete process.env.AWM_ALLOW_DURABLE_CUSTODY;
|
|
24
|
+
const program = new commander_1.Command();
|
|
25
|
+
(0, watch_1.registerWatchCommand)(program);
|
|
26
|
+
const err = [];
|
|
27
|
+
const exit = jest.spyOn(process, 'exit').mockImplementation(((code) => {
|
|
28
|
+
throw Object.assign(new Error(`exit:${code ?? 0}`), { code });
|
|
29
|
+
}));
|
|
30
|
+
program.configureOutput({ writeErr: (s) => { err.push(String(s)); } });
|
|
31
|
+
// stderr write in the refusal helper bypasses configureOutput; capture it too.
|
|
32
|
+
const stderr = jest.spyOn(process.stderr, 'write').mockImplementation(((chunk) => {
|
|
33
|
+
err.push(String(chunk));
|
|
34
|
+
return true;
|
|
35
|
+
}));
|
|
36
|
+
await expect(program.parseAsync(['node', 'awm', 'watch', 'journal-status'])).rejects.toThrow(/exit:1/);
|
|
37
|
+
expect(err.join('')).toContain(durable_custody_1.DURABLE_CUSTODY_SUSPENDED_MESSAGE);
|
|
38
|
+
err.length = 0;
|
|
39
|
+
await expect(program.parseAsync(['node', 'awm', 'watch', '--init', '--plan', 'docs/plan.md'])).rejects.toThrow(/exit:1/);
|
|
40
|
+
expect(err.join('')).toContain(durable_custody_1.DURABLE_CUSTODY_SUSPENDED_MESSAGE);
|
|
41
|
+
exit.mockRestore();
|
|
42
|
+
stderr.mockRestore();
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -0,0 +1,123 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Plan B R3.1 — seven non-empty subsets of {claude-code, codex, cursor}.
|
|
8
|
+
* Isolated HOME/AWM_HOME; no real ~/.claude or ~/.codex required for {cursor}.
|
|
9
|
+
*/
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const TRIO = ['claude-code', 'codex', 'cursor'];
|
|
14
|
+
function subsetsOf(agents) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (let mask = 1; mask < (1 << agents.length); mask++) {
|
|
17
|
+
out.push(agents.filter((_, i) => (mask & (1 << i)) !== 0));
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
describe('Cursor subset gate (Plan B R3.1)', () => {
|
|
22
|
+
let tmpHome;
|
|
23
|
+
let tmpRegistry;
|
|
24
|
+
let tmpWork;
|
|
25
|
+
let originalHome;
|
|
26
|
+
let originalAwmHome;
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-subset-home-'));
|
|
29
|
+
tmpRegistry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-subset-reg-'));
|
|
30
|
+
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-subset-work-'));
|
|
31
|
+
originalHome = process.env.HOME;
|
|
32
|
+
originalAwmHome = process.env.AWM_HOME;
|
|
33
|
+
process.env.HOME = tmpHome;
|
|
34
|
+
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
35
|
+
jest.resetModules();
|
|
36
|
+
const skill = path_1.default.join(tmpRegistry, 'skills/using-awm');
|
|
37
|
+
fs_1.default.mkdirSync(skill, { recursive: true });
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(skill, 'SKILL.md'), '---\nname: using-awm\ndescription: guide\n---\nMUST invoke skills.\n');
|
|
39
|
+
const hooks = path_1.default.join(tmpRegistry, 'hooks');
|
|
40
|
+
fs_1.default.mkdirSync(hooks, { recursive: true });
|
|
41
|
+
for (const name of ['cursor-session-start', 'cursor-pre-compact', 'cursor-post-tool-use', 'codex-session-start', 'session-start', 'run-hook.cmd']) {
|
|
42
|
+
fs_1.default.writeFileSync(path_1.default.join(hooks, name), '#!/usr/bin/env node\nconsole.log("{}")\n', { mode: 0o755 });
|
|
43
|
+
}
|
|
44
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm'), { recursive: true });
|
|
45
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), JSON.stringify({
|
|
46
|
+
defaultAgent: 'cursor',
|
|
47
|
+
enabledAgents: TRIO,
|
|
48
|
+
installMethod: 'symlink',
|
|
49
|
+
defaultScope: 'local',
|
|
50
|
+
}));
|
|
51
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpHome, '.awm/registries.json'), JSON.stringify([
|
|
52
|
+
{ name: 'baseline', remote: 'https://example.invalid/baseline.git' },
|
|
53
|
+
]));
|
|
54
|
+
// Point baseline content root at our temp registry via junction/symlink of registries/baseline
|
|
55
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpHome, '.awm/registries'), { recursive: true });
|
|
56
|
+
fs_1.default.symlinkSync(tmpRegistry, path_1.default.join(tmpHome, '.awm/registries/baseline'), process.platform === 'win32' ? 'junction' : 'dir');
|
|
57
|
+
});
|
|
58
|
+
afterEach(() => {
|
|
59
|
+
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
60
|
+
fs_1.default.rmSync(tmpRegistry, { recursive: true, force: true });
|
|
61
|
+
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
62
|
+
if (originalHome === undefined)
|
|
63
|
+
delete process.env.HOME;
|
|
64
|
+
else
|
|
65
|
+
process.env.HOME = originalHome;
|
|
66
|
+
if (originalAwmHome === undefined)
|
|
67
|
+
delete process.env.AWM_HOME;
|
|
68
|
+
else
|
|
69
|
+
process.env.AWM_HOME = originalAwmHome;
|
|
70
|
+
});
|
|
71
|
+
it.each(subsetsOf(TRIO).map((s) => [s.join('+'), s]))('subset %s installs a single using-awm skill delivery and Cursor hooks when included', (_label, subset) => {
|
|
72
|
+
const { planInstall } = require('../../src/core/install-planner');
|
|
73
|
+
const { applyInstallPlan } = require('../../src/core/install-transaction');
|
|
74
|
+
const { installHook } = require('../../src/commands/hooks/install');
|
|
75
|
+
const { providerFor } = require('../../src/providers');
|
|
76
|
+
const skillSource = path_1.default.join(tmpRegistry, 'skills/using-awm');
|
|
77
|
+
const plan = planInstall({
|
|
78
|
+
artifacts: [{
|
|
79
|
+
name: 'using-awm',
|
|
80
|
+
installName: 'using-awm',
|
|
81
|
+
type: 'skill',
|
|
82
|
+
sourcePath: skillSource,
|
|
83
|
+
}],
|
|
84
|
+
selectedAgents: subset,
|
|
85
|
+
enabledAgents: subset,
|
|
86
|
+
scope: 'global',
|
|
87
|
+
projectRoot: tmpWork,
|
|
88
|
+
method: 'symlink',
|
|
89
|
+
});
|
|
90
|
+
applyInstallPlan(plan);
|
|
91
|
+
// Shared agents skills root (Cursor/Codex/OpenCode) — at most one using-awm.
|
|
92
|
+
// Claude may also have ~/.claude/skills/using-awm; that is a separate channel.
|
|
93
|
+
const sharedRoot = path_1.default.join(tmpHome, '.agents/skills');
|
|
94
|
+
const sharedSkill = path_1.default.join(sharedRoot, 'using-awm');
|
|
95
|
+
const wantsShared = subset.some((agent) => {
|
|
96
|
+
const global = providerFor(agent).skill.global;
|
|
97
|
+
return global === sharedRoot;
|
|
98
|
+
});
|
|
99
|
+
if (wantsShared) {
|
|
100
|
+
expect(fs_1.default.existsSync(sharedSkill)).toBe(true);
|
|
101
|
+
}
|
|
102
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.cursor/rules/using-awm.mdc'))).toBe(false);
|
|
103
|
+
if (subset.includes('cursor')) {
|
|
104
|
+
// {cursor} alone must not require ~/.claude or ~/.codex
|
|
105
|
+
if (subset.length === 1) {
|
|
106
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.claude'))).toBe(false);
|
|
107
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.codex'))).toBe(false);
|
|
108
|
+
}
|
|
109
|
+
installHook({
|
|
110
|
+
agent: 'cursor',
|
|
111
|
+
registryRoot: tmpRegistry,
|
|
112
|
+
installMethod: 'copy',
|
|
113
|
+
baselineRegistryVersion: '4.9.0',
|
|
114
|
+
});
|
|
115
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/hooks/cursor/session-start'))).toBe(true);
|
|
116
|
+
const hooksJson = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.cursor/hooks.json'), 'utf8'));
|
|
117
|
+
expect(hooksJson.version).toBe(1);
|
|
118
|
+
expect(hooksJson.hooks.sessionStart.some((e) => e.command.endsWith(`${path_1.default.sep}session-start`))).toBe(true);
|
|
119
|
+
expect(hooksJson.hooks.preCompact.some((e) => e.command.endsWith(`${path_1.default.sep}pre-compact`))).toBe(true);
|
|
120
|
+
expect(hooksJson.hooks.postToolUse.some((e) => e.command.endsWith(`${path_1.default.sep}post-tool-use`))).toBe(true);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -170,10 +170,25 @@ describe('Providers Routing', () => {
|
|
|
170
170
|
expect((0, providers_1.isAgentTarget)('cursor')).toBe(true);
|
|
171
171
|
expect((0, providers_1.isAgentTarget)('copilot')).toBe(true);
|
|
172
172
|
});
|
|
173
|
-
it('
|
|
174
|
-
expect((0, providers_1.
|
|
173
|
+
it('joins the shared agents skills dirs with link renderer (Plan B R1/R2)', () => {
|
|
174
|
+
expect((0, providers_1.providerFor)('cursor').skill).toEqual({
|
|
175
|
+
global: path_1.default.join(process.env.HOME, '.agents/skills'),
|
|
176
|
+
local: '.agents/skills',
|
|
177
|
+
renderer: 'link',
|
|
178
|
+
});
|
|
179
|
+
expect((0, providers_1.getTargetPath)('skill', 'cursor', 'local')).toBe('.agents/skills');
|
|
175
180
|
expect((0, providers_1.getTargetPath)('skill', 'cursor', 'global'))
|
|
176
|
-
.toBe(path_1.default.join(process.env.HOME, '.
|
|
181
|
+
.toBe(path_1.default.join(process.env.HOME, '.agents/skills'));
|
|
182
|
+
});
|
|
183
|
+
it('installs canonical Cursor agents under .cursor/agents with link (Plan B R6/R9)', () => {
|
|
184
|
+
expect((0, providers_1.providerFor)('cursor').agent).toEqual({
|
|
185
|
+
global: path_1.default.join(process.env.HOME, '.cursor/agents'),
|
|
186
|
+
local: '.cursor/agents',
|
|
187
|
+
renderer: 'link',
|
|
188
|
+
});
|
|
189
|
+
expect((0, providers_1.getTargetPath)('agent', 'cursor', 'local')).toBe('.cursor/agents');
|
|
190
|
+
expect((0, providers_1.getTargetPath)('agent', 'cursor', 'global'))
|
|
191
|
+
.toBe(path_1.default.join(process.env.HOME, '.cursor/agents'));
|
|
177
192
|
});
|
|
178
193
|
it('resolves the Copilot local skill path', () => {
|
|
179
194
|
expect((0, providers_1.getTargetPath)('skill', 'copilot', 'local')).toBe('.github/instructions');
|
|
@@ -181,31 +196,29 @@ describe('Providers Routing', () => {
|
|
|
181
196
|
it('throws a specific, non-generic reason when Copilot global skills are requested', () => {
|
|
182
197
|
expect(() => (0, providers_1.getTargetPath)('skill', 'copilot', 'global')).toThrow('skill global scope is not supported by Copilot: GitHub Copilot has no user-level skill discovery mechanism — skills must be installed per-project.');
|
|
183
198
|
});
|
|
184
|
-
it('keeps workflow
|
|
199
|
+
it('keeps workflow unsupported for cursor and agent unsupported for copilot', () => {
|
|
185
200
|
expect(() => (0, providers_1.getTargetPath)('workflow', 'cursor', 'local')).toThrow('workflows are not supported by Cursor.');
|
|
186
201
|
expect(() => (0, providers_1.getTargetPath)('agent', 'copilot', 'local')).toThrow('agents are not supported by Copilot.');
|
|
187
202
|
});
|
|
188
|
-
it('declares
|
|
189
|
-
expect((0, providers_1.providerFor)('cursor').hooks).
|
|
203
|
+
it('declares cursor-hooks-json for cursor and none for copilot', () => {
|
|
204
|
+
expect((0, providers_1.providerFor)('cursor').hooks).toEqual({
|
|
205
|
+
type: 'cursor-hooks-json',
|
|
206
|
+
settingsPath: path_1.default.join(tmpHome, '.cursor/hooks.json'),
|
|
207
|
+
scriptsDir: path_1.default.join(process.env.AWM_HOME, 'hooks/cursor'),
|
|
208
|
+
matcher: '',
|
|
209
|
+
eventName: '',
|
|
210
|
+
});
|
|
190
211
|
expect((0, providers_1.providerFor)('copilot').hooks).toBeUndefined();
|
|
191
212
|
});
|
|
192
|
-
it('assigns
|
|
193
|
-
expect((0, providers_1.providerFor)('cursor').skill.renderer).toBe('
|
|
213
|
+
it('assigns Copilot instructions renderer; Cursor skills use link (cursor-mdc kept for migration)', () => {
|
|
214
|
+
expect((0, providers_1.providerFor)('cursor').skill.renderer).toBe('link');
|
|
194
215
|
expect((0, providers_1.providerFor)('copilot').skill.renderer).toBe('copilot-instructions');
|
|
195
216
|
});
|
|
196
|
-
it('assertLinkRenderer
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
// legacy interactive `awm add`) can only symlink/copy verbatim; they never
|
|
202
|
-
// render. A raw SKILL.md copy at `.cursor/rules/<name>` or
|
|
203
|
-
// `.github/instructions/<name>` has no `.mdc`/`.instructions.md` extension
|
|
204
|
-
// and no frontmatter (`alwaysApply`/`applyTo`) — neither Cursor nor Copilot
|
|
205
|
-
// would ever read it. This must keep throwing, same as codex-agent-toml
|
|
206
|
-
// always has, directing users to commands/add.ts's real render pipeline
|
|
207
|
-
// instead (which never calls assertLinkRenderer at all).
|
|
208
|
-
expect(() => (0, providers_1.assertLinkRenderer)('skill', 'cursor')).toThrow(/not implemented yet/);
|
|
217
|
+
it('assertLinkRenderer accepts Cursor link skills and still refuses Copilot', () => {
|
|
218
|
+
expect(() => (0, providers_1.assertLinkRenderer)('skill', 'cursor')).not.toThrow();
|
|
219
|
+
expect((0, providers_1.assertLinkRenderer)('skill', 'cursor')?.renderer).toBe('link');
|
|
220
|
+
// Copilot still renders via the add pipeline only — a raw SKILL.md under
|
|
221
|
+
// `.github/instructions/` lacks `.instructions.md` + applyTo frontmatter.
|
|
209
222
|
expect(() => (0, providers_1.assertLinkRenderer)('skill', 'copilot')).toThrow(/not implemented yet/);
|
|
210
223
|
});
|
|
211
224
|
});
|
|
@@ -86,11 +86,12 @@ describe('a provider that declares a config-home override honors it everywhere',
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
it('shared, cross-agent conventions are NOT moved by one agent’s override', () => {
|
|
89
|
-
// `~/.agents/skills` lo comparten Codex y
|
|
90
|
-
// agente. Moverlo con CODEX_HOME desconectaria a OpenCode de sus propias skills.
|
|
89
|
+
// `~/.agents/skills` lo comparten Codex, OpenCode y Cursor: es del ecosistema, no de un
|
|
90
|
+
// agente. Moverlo con CODEX_HOME desconectaria a OpenCode/Cursor de sus propias skills.
|
|
91
91
|
const moved = providersWith({ HOME, CODEX_HOME: ELSEWHERE });
|
|
92
92
|
expect(moved.codex.skill.global).toBe(path_1.default.join(HOME, '.agents/skills'));
|
|
93
93
|
expect(moved.opencode.skill.global).toBe(path_1.default.join(HOME, '.agents/skills'));
|
|
94
|
+
expect(moved.cursor.skill.global).toBe(path_1.default.join(HOME, '.agents/skills'));
|
|
94
95
|
});
|
|
95
96
|
it('every agent declares a config home, so a new provider cannot skip the question', () => {
|
|
96
97
|
const table = providersWith({ HOME });
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|