agentic-workflow-manager 9.14.1 → 9.15.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.
Files changed (36) hide show
  1. package/dist/src/commands/context-budget/budget.js +50 -4
  2. package/dist/src/commands/doctor.js +4 -1
  3. package/dist/src/commands/hooks/cursor.js +359 -0
  4. package/dist/src/commands/hooks/install.js +3 -0
  5. package/dist/src/commands/hooks/resync.js +10 -0
  6. package/dist/src/commands/hooks/status.js +3 -0
  7. package/dist/src/commands/hooks/uninstall.js +3 -0
  8. package/dist/src/commands/update.js +4 -0
  9. package/dist/src/core/context/strategies/codex-agents.js +6 -2
  10. package/dist/src/core/cursor-budgets.js +8 -0
  11. package/dist/src/core/dashboard/sanitize.js +2 -2
  12. package/dist/src/core/diagnostics/provider-checks.js +11 -2
  13. package/dist/src/core/init/migrate-cursor-mdc.js +72 -0
  14. package/dist/src/core/init/steps.js +4 -0
  15. package/dist/src/providers/index.js +18 -4
  16. package/dist/tests/commands/add.test.js +5 -7
  17. package/dist/tests/commands/context-budget/budget.test.js +68 -0
  18. package/dist/tests/commands/doctor.test.js +2 -2
  19. package/dist/tests/commands/hooks/cursor.test.js +257 -0
  20. package/dist/tests/commands/hooks/resync.test.js +8 -0
  21. package/dist/tests/commands/init.test.js +23 -22
  22. package/dist/tests/commands/plan/index.test.js +56 -2
  23. package/dist/tests/core/bundle-install.test.js +8 -14
  24. package/dist/tests/core/context/strategies/codex-agents.test.js +31 -12
  25. package/dist/tests/core/diagnostics/provider-tier.test.js +46 -96
  26. package/dist/tests/core/diagnostics/rendered-artifact-visibility.test.js +6 -4
  27. package/dist/tests/core/init/migrate-cursor-mdc.test.js +74 -0
  28. package/dist/tests/core/install-planner.test.js +16 -12
  29. package/dist/tests/integration/cursor-subset-gate.test.js +123 -0
  30. package/dist/tests/providers/index.test.js +34 -21
  31. package/dist/tests/structural/provider-paths-honor-config-home.test.js +3 -2
  32. package/package.json +1 -1
  33. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  34. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  35. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  36. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -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('resolves Cursor skill paths for both scopes', () => {
174
- expect((0, providers_1.getTargetPath)('skill', 'cursor', 'local')).toBe('.cursor/rules');
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, '.cursor/rules'));
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/agent unsupported (null) for both, via the existing generic message', () => {
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 no hooks config for cursor or copilot', () => {
189
- expect((0, providers_1.providerFor)('cursor').hooks).toBeUndefined();
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 the Cursor .mdc and Copilot instructions renderers to their skill artifact config (Task 4.3)', () => {
193
- expect((0, providers_1.providerFor)('cursor').skill.renderer).toBe('cursor-mdc');
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 still refuses the Cursor/Copilot skill renderers (Task 4.3 code-quality-review fix)', () => {
197
- // Regression: an earlier version of this task widened assertLinkRenderer to
198
- // allow these two through, on the theory that a raw unrendered copy is "at
199
- // least a plausible degraded install" — wrong. assertLinkRenderer's only
200
- // callers (core/provider-artifacts.ts's legacy preflight, src/index.ts's
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 OpenCode: es del ecosistema, no de un
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-workflow-manager",
3
- "version": "9.14.1",
3
+ "version": "9.15.0",
4
4
  "main": "dist/src/index.js",
5
5
  "bin": {
6
6
  "awm": "./dist/src/index.js"
Binary file
Binary file