agentic-workflow-manager 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +62 -0
  2. package/dist/src/commands/doctor.js +77 -0
  3. package/dist/src/commands/hooks/index.js +118 -0
  4. package/dist/src/commands/hooks/install.js +113 -0
  5. package/dist/src/commands/hooks/resync.js +50 -0
  6. package/dist/src/commands/hooks/status.js +74 -0
  7. package/dist/src/commands/hooks/uninstall.js +59 -0
  8. package/dist/src/commands/init.js +181 -0
  9. package/dist/src/commands/ledger/index.js +73 -0
  10. package/dist/src/commands/pin.js +75 -0
  11. package/dist/src/commands/registry/add.js +62 -0
  12. package/dist/src/commands/registry/index.js +166 -0
  13. package/dist/src/commands/registry/install-bundles.js +36 -0
  14. package/dist/src/commands/registry/remove.js +18 -0
  15. package/dist/src/commands/registry/status.js +31 -0
  16. package/dist/src/commands/sensors/baseline.js +93 -0
  17. package/dist/src/commands/sensors/formatters/eslint.js +29 -0
  18. package/dist/src/commands/sensors/formatters/generic.js +8 -0
  19. package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
  20. package/dist/src/commands/sensors/formatters/test.js +12 -0
  21. package/dist/src/commands/sensors/formatters/tsc.js +23 -0
  22. package/dist/src/commands/sensors/index.js +94 -0
  23. package/dist/src/commands/sensors/init.js +112 -0
  24. package/dist/src/commands/sensors/install.js +78 -0
  25. package/dist/src/commands/sensors/run.js +217 -0
  26. package/dist/src/commands/sensors/status.js +85 -0
  27. package/dist/src/commands/sensors/types.js +2 -0
  28. package/dist/src/core/bundle-install.js +116 -0
  29. package/dist/src/core/bundles.js +122 -0
  30. package/dist/src/core/cli-version.js +30 -0
  31. package/dist/src/core/context/materializer.js +30 -0
  32. package/dist/src/core/context/orchestrator.js +75 -0
  33. package/dist/src/core/context/project-constitution-inject.js +47 -0
  34. package/dist/src/core/context/provider.js +29 -0
  35. package/dist/src/core/context/regenerate.js +61 -0
  36. package/dist/src/core/context/strategies/config-instructions.js +73 -0
  37. package/dist/src/core/context/strategies/hook-merge.js +23 -0
  38. package/dist/src/core/context/strategies/strategy.js +2 -0
  39. package/dist/src/core/context/types.js +2 -0
  40. package/dist/src/core/diagnostics/checks.js +141 -0
  41. package/dist/src/core/diagnostics/context.js +181 -0
  42. package/dist/src/core/diagnostics/types.js +2 -0
  43. package/dist/src/core/discovery.js +135 -0
  44. package/dist/src/core/executor.js +41 -0
  45. package/dist/src/core/init/detector.js +67 -0
  46. package/dist/src/core/init/orchestrator.js +54 -0
  47. package/dist/src/core/init/steps.js +279 -0
  48. package/dist/src/core/init/types.js +2 -0
  49. package/dist/src/core/ledger/store.js +73 -0
  50. package/dist/src/core/ledger/types.js +2 -0
  51. package/dist/src/core/miro.js +314 -0
  52. package/dist/src/core/profile-pins.js +36 -0
  53. package/dist/src/core/profile.js +146 -0
  54. package/dist/src/core/registries.js +205 -0
  55. package/dist/src/core/registry.js +28 -0
  56. package/dist/src/core/skill-integrity.js +97 -0
  57. package/dist/src/core/story-map-parser.js +83 -0
  58. package/dist/src/core/update-check-worker.js +10 -0
  59. package/dist/src/core/update-check.js +106 -0
  60. package/dist/src/core/versioning.js +112 -0
  61. package/dist/src/index.js +689 -0
  62. package/dist/src/providers/index.js +63 -0
  63. package/dist/src/utils/config.js +35 -0
  64. package/dist/src/utils/grouping.js +51 -0
  65. package/dist/src/utils/registry-view.js +154 -0
  66. package/dist/tests/commands/doctor.test.js +98 -0
  67. package/dist/tests/commands/hooks/install.test.js +149 -0
  68. package/dist/tests/commands/hooks/resync.test.js +135 -0
  69. package/dist/tests/commands/hooks/router.test.js +26 -0
  70. package/dist/tests/commands/hooks/status.test.js +88 -0
  71. package/dist/tests/commands/hooks/uninstall.test.js +104 -0
  72. package/dist/tests/commands/init.test.js +87 -0
  73. package/dist/tests/commands/ledger/index.test.js +64 -0
  74. package/dist/tests/commands/pin.test.js +72 -0
  75. package/dist/tests/commands/registry/add.test.js +223 -0
  76. package/dist/tests/commands/registry/install-bundles.test.js +87 -0
  77. package/dist/tests/commands/registry/remove.test.js +49 -0
  78. package/dist/tests/commands/registry/status.test.js +43 -0
  79. package/dist/tests/commands/sensors/baseline.test.js +106 -0
  80. package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
  81. package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
  82. package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
  83. package/dist/tests/commands/sensors/index.test.js +18 -0
  84. package/dist/tests/commands/sensors/init.test.js +137 -0
  85. package/dist/tests/commands/sensors/install.test.js +60 -0
  86. package/dist/tests/commands/sensors/router.test.js +23 -0
  87. package/dist/tests/commands/sensors/run.test.js +353 -0
  88. package/dist/tests/commands/sensors/status.test.js +98 -0
  89. package/dist/tests/core/base-remote.test.js +70 -0
  90. package/dist/tests/core/bundle-install.test.js +198 -0
  91. package/dist/tests/core/bundles-multiroot.test.js +68 -0
  92. package/dist/tests/core/bundles-overrides.test.js +49 -0
  93. package/dist/tests/core/bundles.test.js +96 -0
  94. package/dist/tests/core/cli-version.test.js +17 -0
  95. package/dist/tests/core/context/materializer.test.js +46 -0
  96. package/dist/tests/core/context/orchestrator.test.js +127 -0
  97. package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
  98. package/dist/tests/core/context/provider.test.js +45 -0
  99. package/dist/tests/core/context/regenerate.test.js +106 -0
  100. package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
  101. package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
  102. package/dist/tests/core/context/types.test.js +15 -0
  103. package/dist/tests/core/diagnostics/checks.test.js +208 -0
  104. package/dist/tests/core/diagnostics/context.test.js +170 -0
  105. package/dist/tests/core/discovery-multiroot.test.js +63 -0
  106. package/dist/tests/core/discovery-overrides.test.js +105 -0
  107. package/dist/tests/core/discovery.test.js +113 -0
  108. package/dist/tests/core/executor.test.js +44 -0
  109. package/dist/tests/core/init/detector.test.js +56 -0
  110. package/dist/tests/core/init/orchestrator.test.js +122 -0
  111. package/dist/tests/core/init/steps.test.js +363 -0
  112. package/dist/tests/core/ledger/gitignore.test.js +13 -0
  113. package/dist/tests/core/ledger/store.test.js +122 -0
  114. package/dist/tests/core/miro-layout.test.js +150 -0
  115. package/dist/tests/core/profile-pins.test.js +131 -0
  116. package/dist/tests/core/profile-registries.test.js +59 -0
  117. package/dist/tests/core/profile.test.js +110 -0
  118. package/dist/tests/core/registries-capability.test.js +52 -0
  119. package/dist/tests/core/registries-seed.test.js +66 -0
  120. package/dist/tests/core/registries-sync.test.js +205 -0
  121. package/dist/tests/core/registries.test.js +91 -0
  122. package/dist/tests/core/registry-manifest.test.js +95 -0
  123. package/dist/tests/core/registry-versioned-sync.test.js +113 -0
  124. package/dist/tests/core/registry.test.js +51 -0
  125. package/dist/tests/core/skill-integrity.test.js +126 -0
  126. package/dist/tests/core/story-map-parser.test.js +136 -0
  127. package/dist/tests/core/sync-gates.test.js +97 -0
  128. package/dist/tests/core/update-check.test.js +106 -0
  129. package/dist/tests/core/versioning.test.js +169 -0
  130. package/dist/tests/integration/pack-e2e.test.js +50 -0
  131. package/dist/tests/providers/hooks-config.test.js +45 -0
  132. package/dist/tests/providers/index.test.js +58 -0
  133. package/dist/tests/providers/injection-config.test.js +25 -0
  134. package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
  135. package/dist/tests/registry/catalog-consistency.test.js +47 -0
  136. package/dist/tests/registry/design-skills.test.js +146 -0
  137. package/dist/tests/registry/prose-agnostic.test.js +18 -0
  138. package/dist/tests/registry/sensor-packs.test.js +45 -0
  139. package/dist/tests/registry/skill-versions.test.js +26 -0
  140. package/dist/tests/registry/using-awm.test.js +39 -0
  141. package/dist/tests/utils/config.test.js +31 -0
  142. package/dist/tests/utils/grouping.test.js +43 -0
  143. package/dist/tests/utils/registry-view-overrides.test.js +41 -0
  144. package/dist/tests/utils/registry-view.test.js +156 -0
  145. package/package.json +49 -0
@@ -0,0 +1,50 @@
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/pack-e2e.test.ts
7
+ //
8
+ // Verifica el criterio del roadmap sin publicar: el tarball de npm pack corre
9
+ // `awm update` end-to-end contra un registry fixture, sin el monorepo.
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ const os_1 = __importDefault(require("os"));
13
+ const child_process_1 = require("child_process");
14
+ const GIT = (cwd, cmd) => (0, child_process_1.execSync)(`git -c user.email=t@t.t -c user.name=t -c tag.gpgSign=false ${cmd}`, { cwd, stdio: 'pipe' });
15
+ jest.setTimeout(180_000);
16
+ it('el tarball empaquetado corre awm update sin el monorepo', () => {
17
+ const tmp = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-'));
18
+ const cliDir = path_1.default.resolve(__dirname, '../..');
19
+ try {
20
+ (0, child_process_1.execSync)(`npm pack --pack-destination "${tmp}"`, { cwd: cliDir, stdio: 'pipe' });
21
+ const tarball = fs_1.default.readdirSync(tmp).find((f) => f.endsWith('.tgz'));
22
+ (0, child_process_1.execSync)(`tar -xzf "${tarball}"`, { cwd: tmp });
23
+ const pkgDir = path_1.default.join(tmp, 'package');
24
+ // files whitelist: dist viaja, src no
25
+ expect(fs_1.default.existsSync(path_1.default.join(pkgDir, 'dist/src/index.js'))).toBe(true);
26
+ expect(fs_1.default.existsSync(path_1.default.join(pkgDir, 'src'))).toBe(false);
27
+ // deps sin red: el node_modules del repo sirve al binario extraído
28
+ fs_1.default.symlinkSync(path_1.default.join(cliDir, 'node_modules'), path_1.default.join(pkgDir, 'node_modules'));
29
+ // registry fixture con un skill y tag v1.0.0
30
+ const source = path_1.default.join(tmp, 'src-reg');
31
+ fs_1.default.mkdirSync(path_1.default.join(source, 'skills/alpha'), { recursive: true });
32
+ fs_1.default.writeFileSync(path_1.default.join(source, 'skills/alpha/SKILL.md'), '---\nname: alpha\ndescription: t\n---\n');
33
+ GIT(source, 'init -q');
34
+ GIT(source, 'add -A');
35
+ GIT(source, 'commit -qm init');
36
+ GIT(source, 'tag v1.0.0');
37
+ const home = path_1.default.join(tmp, 'home');
38
+ const awmHome = path_1.default.join(home, '.awm');
39
+ fs_1.default.mkdirSync(awmHome, { recursive: true });
40
+ fs_1.default.writeFileSync(path_1.default.join(awmHome, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: source }]));
41
+ (0, child_process_1.execSync)(`node "${path_1.default.join(pkgDir, 'dist/src/index.js')}" update`, {
42
+ env: { ...process.env, HOME: home, AWM_HOME: awmHome, AWM_NO_UPDATE_CHECK: '1' },
43
+ stdio: 'pipe',
44
+ });
45
+ expect(fs_1.default.existsSync(path_1.default.join(awmHome, 'registries/baseline/skills/alpha/SKILL.md'))).toBe(true);
46
+ }
47
+ finally {
48
+ fs_1.default.rmSync(tmp, { recursive: true, force: true });
49
+ }
50
+ });
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const providers_1 = require("../../src/providers");
4
+ describe('Hook configuration in providers', () => {
5
+ it('claude-code provider defines a HookConfig', () => {
6
+ const cc = providers_1.PROVIDERS['claude-code'];
7
+ expect(cc.hooks).toBeDefined();
8
+ expect(cc.hooks?.type).toBe('cc-settings-merge');
9
+ expect(cc.hooks?.eventName).toBe('SessionStart');
10
+ expect(cc.hooks?.matcher).toBe('startup|clear|compact');
11
+ });
12
+ it('claude-code settingsPath resolves to ~/.claude/settings.json', () => {
13
+ const cc = providers_1.PROVIDERS['claude-code'];
14
+ expect(cc.hooks?.settingsPath).toMatch(/\.claude\/settings\.json$/);
15
+ });
16
+ it('claude-code scriptsDir resolves to ~/.awm/hooks/', () => {
17
+ const cc = providers_1.PROVIDERS['claude-code'];
18
+ expect(cc.hooks?.scriptsDir).toMatch(/\.awm\/hooks$/);
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();
23
+ });
24
+ it('getHookConfig returns config for supported target', () => {
25
+ const config = (0, providers_1.getHookConfig)('claude-code');
26
+ expect(config).toBeDefined();
27
+ expect(config?.type).toBe('cc-settings-merge');
28
+ });
29
+ it('getHookConfig returns undefined for unsupported target', () => {
30
+ const config = (0, providers_1.getHookConfig)('antigravity');
31
+ expect(config).toBeUndefined();
32
+ });
33
+ it('respects AWM_HOME env var override for scriptsDir', () => {
34
+ const originalEnv = process.env.AWM_HOME;
35
+ process.env.AWM_HOME = '/tmp/awm-test';
36
+ // Re-import to pick up env change
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;
44
+ });
45
+ });
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // tests/providers/index.test.ts
7
+ const providers_1 = require("../../src/providers");
8
+ const os_1 = __importDefault(require("os"));
9
+ describe('Providers Routing', () => {
10
+ // ── Existing Antigravity tests (preserved) ──
11
+ it('routes antigravity global skills correctly', () => {
12
+ const result = (0, providers_1.getTargetPath)('skill', 'antigravity', 'global');
13
+ expect(result).toBe(`${os_1.default.homedir()}/.gemini/antigravity/skills`);
14
+ });
15
+ it('routes opencode local skills correctly', () => {
16
+ const result = (0, providers_1.getTargetPath)('skill', 'opencode', 'local');
17
+ expect(result).toBe('.agents/skills');
18
+ });
19
+ it('routes antigravity global workflows correctly', () => {
20
+ const result = (0, providers_1.getTargetPath)('workflow', 'antigravity', 'global');
21
+ expect(result).toBe(`${os_1.default.homedir()}/.gemini/antigravity/global_workflows`);
22
+ });
23
+ it('throws on opencode workflow', () => {
24
+ expect(() => (0, providers_1.getTargetPath)('workflow', 'opencode', 'global')).toThrow('not supported');
25
+ });
26
+ // ── New Claude Code tests ──
27
+ it('routes claude-code global skills correctly', () => {
28
+ const result = (0, providers_1.getTargetPath)('skill', 'claude-code', 'global');
29
+ expect(result).toBe(`${os_1.default.homedir()}/.claude/skills`);
30
+ });
31
+ it('routes claude-code local skills correctly', () => {
32
+ const result = (0, providers_1.getTargetPath)('skill', 'claude-code', 'local');
33
+ expect(result).toBe('.claude/skills');
34
+ });
35
+ it('routes claude-code global agents correctly', () => {
36
+ const result = (0, providers_1.getTargetPath)('agent', 'claude-code', 'global');
37
+ expect(result).toBe(`${os_1.default.homedir()}/.claude/agents`);
38
+ });
39
+ it('routes claude-code local agents correctly', () => {
40
+ const result = (0, providers_1.getTargetPath)('agent', 'claude-code', 'local');
41
+ expect(result).toBe('.claude/agents');
42
+ });
43
+ it('throws on claude-code workflow', () => {
44
+ expect(() => (0, providers_1.getTargetPath)('workflow', 'claude-code', 'global')).toThrow('not supported');
45
+ });
46
+ it('throws on unknown agent target', () => {
47
+ expect(() => (0, providers_1.getTargetPath)('skill', 'unknown-agent', 'global')).toThrow('Unknown agent target');
48
+ });
49
+ // ── PROVIDERS map structure tests ──
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();
57
+ });
58
+ });
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const os_1 = __importDefault(require("os"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const providers_1 = require("../../src/providers");
9
+ describe('getInjection', () => {
10
+ it('returns cc-settings-merge for claude-code', () => {
11
+ const inj = (0, providers_1.getInjection)('claude-code');
12
+ expect(inj?.type).toBe('cc-settings-merge');
13
+ });
14
+ it('returns config-instructions for opencode pointing at the global opencode.json', () => {
15
+ const inj = (0, providers_1.getInjection)('opencode');
16
+ expect(inj).toEqual({
17
+ type: 'config-instructions',
18
+ configPath: path_1.default.join(os_1.default.homedir(), '.config/opencode/opencode.json'),
19
+ field: 'instructions',
20
+ });
21
+ });
22
+ it('returns undefined for antigravity (no injection mechanism wired yet)', () => {
23
+ expect((0, providers_1.getInjection)('antigravity')).toBeUndefined();
24
+ });
25
+ });
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const REG = path_1.default.join(__dirname, '../../../registry/skills');
9
+ const read = (p) => fs_1.default.readFileSync(path_1.default.join(REG, p), 'utf-8');
10
+ describe('B-3 harness-retro is ledger-driven', () => {
11
+ const skill = read('harness-retro/SKILL.md');
12
+ test('reads the ledger via awm ledger list + recurring', () => {
13
+ expect(skill).toMatch(/awm ledger list/);
14
+ expect(skill).toMatch(/awm ledger recurring/);
15
+ });
16
+ test('archives the ledger when done', () => {
17
+ expect(skill).toMatch(/awm ledger archive/);
18
+ });
19
+ test('no longer relies on the human "where did this fail before?" memory step', () => {
20
+ expect(skill).not.toMatch(/Where did this pattern fail before\?/);
21
+ });
22
+ test('cures into AGENTS.md (agnostic) for agent-style lessons + wins, not CLAUDE.md', () => {
23
+ expect(skill).toMatch(/AGENTS\.md/);
24
+ });
25
+ test('writes the awm-retro-complete marker', () => {
26
+ expect(skill).toMatch(/awm-retro-complete/);
27
+ });
28
+ test('treats recurrence as a signal, not a hard >=2 gate (interactive decision)', () => {
29
+ expect(skill).toMatch(/se(ñ|n)al|signal/i);
30
+ });
31
+ });
32
+ describe('B-3 capture wiring — phases append to the ledger', () => {
33
+ test('SDD spec reviewer emits findings AND wins to the ledger', () => {
34
+ const p = read('subagent-driven-development/spec-reviewer-prompt.md');
35
+ expect(p).toMatch(/awm ledger add/);
36
+ expect(p).toMatch(/--polarity finding/);
37
+ expect(p).toMatch(/--polarity win/);
38
+ });
39
+ test('SDD code-quality reviewer emits findings AND wins to the ledger', () => {
40
+ const p = read('subagent-driven-development/code-quality-reviewer-prompt.md');
41
+ expect(p).toMatch(/awm ledger add/);
42
+ expect(p).toMatch(/--polarity win/);
43
+ });
44
+ test('post-qa deep-review emits findings AND wins to the ledger', () => {
45
+ const p = read('post-implementation-qa/deep-review-prompt.md');
46
+ expect(p).toMatch(/awm ledger add/);
47
+ expect(p).toMatch(/--polarity win/);
48
+ });
49
+ test('verification-before-completion logs recurring sensor failures', () => {
50
+ const p = read('verification-before-completion/SKILL.md');
51
+ expect(p).toMatch(/awm ledger add/);
52
+ });
53
+ test('systematic-debugging logs the confirmed root cause', () => {
54
+ const p = read('systematic-debugging/SKILL.md');
55
+ expect(p).toMatch(/awm ledger add/);
56
+ });
57
+ });
58
+ describe('B-3 development-process routes harness-retro as a terminal phase', () => {
59
+ const skill = read('development-process/SKILL.md');
60
+ test('harness-retro appears as a pipeline phase between QA and finishing', () => {
61
+ const qaIdx = skill.indexOf('post-implementation-qa');
62
+ const retroIdx = skill.indexOf('harness-retro');
63
+ const finishIdx = skill.indexOf('finishing-a-development-branch');
64
+ expect(retroIdx).toBeGreaterThan(-1);
65
+ expect(qaIdx).toBeLessThan(retroIdx);
66
+ expect(retroIdx).toBeLessThan(finishIdx);
67
+ // Also assert the routing edge exists (not just node declarations)
68
+ expect(skill).toMatch(/post-implementation-qa.*harness-retro|harness-retro.*post-implementation-qa/s);
69
+ expect(skill).toMatch(/harness-retro.*finishing-a-development-branch|finishing-a-development-branch.*harness-retro/s);
70
+ });
71
+ test('routing keys on the awm-retro-complete marker', () => {
72
+ expect(skill).toMatch(/awm-retro-complete/);
73
+ });
74
+ });
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const CONTENT = path_1.default.join(__dirname, '../../../registry');
9
+ function readJson(p) { return JSON.parse(fs_1.default.readFileSync(p, 'utf-8')); }
10
+ describe('catalog/bundle consistency', () => {
11
+ const catalog = readJson(path_1.default.join(CONTENT, 'catalog.json'));
12
+ it('declares exactly the 4 bundles', () => {
13
+ expect(catalog.bundles.map((b) => b.name).sort())
14
+ .toEqual(['authoring', 'dev', 'docs', 'frontend']);
15
+ });
16
+ it('every catalog entry has a matching bundle.json whose mirrored fields agree', () => {
17
+ for (const entry of catalog.bundles) {
18
+ const manifest = readJson(path_1.default.join(CONTENT, entry.source, 'bundle.json'));
19
+ expect(manifest.name).toBe(entry.name);
20
+ expect(manifest.scope).toBe(entry.scope);
21
+ expect(manifest.version).toBe(entry.version);
22
+ expect(manifest.visibility ?? 'public').toBe(entry.visibility ?? 'public');
23
+ }
24
+ });
25
+ it('every referenced skill exists in registry/skills', () => {
26
+ for (const entry of catalog.bundles) {
27
+ const manifest = readJson(path_1.default.join(CONTENT, entry.source, 'bundle.json'));
28
+ for (const s of manifest.skills) {
29
+ const name = typeof s === 'string' ? s : s.name;
30
+ expect(fs_1.default.existsSync(path_1.default.join(CONTENT, 'skills', name, 'SKILL.md'))).toBe(true);
31
+ }
32
+ }
33
+ });
34
+ it('bundle skills partition the 41 skills with no overlap', () => {
35
+ const all = [];
36
+ for (const entry of catalog.bundles) {
37
+ const manifest = readJson(path_1.default.join(CONTENT, entry.source, 'bundle.json'));
38
+ for (const s of manifest.skills)
39
+ all.push(typeof s === 'string' ? s : s.name);
40
+ }
41
+ expect(all.length).toBe(41);
42
+ expect(new Set(all).size).toBe(41);
43
+ });
44
+ it('processes.json has been removed', () => {
45
+ expect(fs_1.default.existsSync(path_1.default.join(CONTENT, 'processes.json'))).toBe(false);
46
+ });
47
+ });
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const REGISTRY = path_1.default.join(__dirname, '..', '..', '..', 'registry');
9
+ const SKILLS = path_1.default.join(REGISTRY, 'skills');
10
+ const LOCK_FILE = path_1.default.join(__dirname, '..', '..', '..', 'skills-lock.json');
11
+ function frontmatter(skill) {
12
+ const content = fs_1.default.readFileSync(path_1.default.join(SKILLS, skill, 'SKILL.md'), 'utf-8');
13
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
14
+ expect(match).not.toBeNull();
15
+ return match[1];
16
+ }
17
+ describe('frontend-craft skill', () => {
18
+ it('exists with valid frontmatter', () => {
19
+ const fm = frontmatter('frontend-craft');
20
+ expect(fm).toMatch(/^name:\s*frontend-craft\s*$/m);
21
+ expect(fm).toMatch(/^description:\s*.+$/m);
22
+ });
23
+ it('bundles emil and taste as internal references', () => {
24
+ const ref = path_1.default.join(SKILLS, 'frontend-craft', 'reference');
25
+ expect(fs_1.default.existsSync(path_1.default.join(ref, 'emil-design-eng.md'))).toBe(true);
26
+ expect(fs_1.default.existsSync(path_1.default.join(ref, 'design-taste-frontend.md'))).toBe(true);
27
+ });
28
+ it('SKILL.md points to its reference files', () => {
29
+ const content = fs_1.default.readFileSync(path_1.default.join(SKILLS, 'frontend-craft', 'SKILL.md'), 'utf-8');
30
+ expect(content).toMatch(/reference\/emil-design-eng\.md/);
31
+ expect(content).toMatch(/reference\/design-taste-frontend\.md/);
32
+ });
33
+ });
34
+ describe('impeccable skill (non-live scope)', () => {
35
+ const base = path_1.default.join(SKILLS, 'impeccable');
36
+ it('exists with valid frontmatter', () => {
37
+ expect(fs_1.default.existsSync(path_1.default.join(base, 'SKILL.md'))).toBe(true);
38
+ });
39
+ it('has no literal .agents/skills/impeccable paths in markdown', () => {
40
+ const mdFiles = [
41
+ path_1.default.join(base, 'SKILL.md'),
42
+ ...fs_1.default.readdirSync(path_1.default.join(base, 'reference')).map((f) => path_1.default.join(base, 'reference', f)),
43
+ ];
44
+ for (const f of mdFiles) {
45
+ const content = fs_1.default.readFileSync(f, 'utf-8');
46
+ expect(content).not.toMatch(/\.agents\/skills\/impeccable/);
47
+ }
48
+ });
49
+ it('dropped the live/Codex layer', () => {
50
+ expect(fs_1.default.existsSync(path_1.default.join(base, 'agents'))).toBe(false);
51
+ expect(fs_1.default.existsSync(path_1.default.join(base, 'reference', 'live.md'))).toBe(false);
52
+ expect(fs_1.default.existsSync(path_1.default.join(base, 'reference', 'codex.md'))).toBe(false);
53
+ const liveScripts = fs_1.default.readdirSync(path_1.default.join(base, 'scripts')).filter((f) => /^live-/.test(f) || f === 'modern-screenshot.umd.js');
54
+ expect(liveScripts).toEqual([]);
55
+ });
56
+ it('kept the static detector and non-live support scripts', () => {
57
+ const scripts = path_1.default.join(base, 'scripts');
58
+ for (const keep of ['detect.mjs', 'context.mjs', 'critique-storage.mjs', 'impeccable-paths.mjs']) {
59
+ expect(fs_1.default.existsSync(path_1.default.join(scripts, keep))).toBe(true);
60
+ }
61
+ expect(fs_1.default.existsSync(path_1.default.join(scripts, 'detector'))).toBe(true);
62
+ });
63
+ it('removed the live row from the commands table', () => {
64
+ const content = fs_1.default.readFileSync(path_1.default.join(base, 'SKILL.md'), 'utf-8');
65
+ expect(content).not.toMatch(/\|\s*`live`\s*\|/);
66
+ });
67
+ });
68
+ describe('google stitch skills', () => {
69
+ for (const s of ['extract-design-md', 'code-to-design', 'react-components']) {
70
+ it(`${s} exists with SKILL.md`, () => {
71
+ expect(fs_1.default.existsSync(path_1.default.join(SKILLS, s, 'SKILL.md'))).toBe(true);
72
+ });
73
+ }
74
+ });
75
+ describe('catalog/bundles (replaces processes.json)', () => {
76
+ const catalog = JSON.parse(fs_1.default.readFileSync(path_1.default.join(REGISTRY, 'catalog.json'), 'utf-8'));
77
+ function readBundle(source) {
78
+ return JSON.parse(fs_1.default.readFileSync(path_1.default.join(REGISTRY, source, 'bundle.json'), 'utf-8'));
79
+ }
80
+ function skillNames(bundle) {
81
+ return bundle.skills.map((s) => (typeof s === 'string' ? s : s.name));
82
+ }
83
+ it('frontend bundle includes the heavy design skills', () => {
84
+ const entry = catalog.bundles.find((b) => b.name === 'frontend');
85
+ expect(entry).toBeDefined();
86
+ const bundle = readBundle(entry.source);
87
+ const names = skillNames(bundle);
88
+ for (const s of ['impeccable', 'ui-design', 'extract-design-md', 'code-to-design', 'react-components']) {
89
+ expect(names).toContain(s);
90
+ }
91
+ });
92
+ it('frontend bundle includes frontend-craft', () => {
93
+ const entry = catalog.bundles.find((b) => b.name === 'frontend');
94
+ expect(entry).toBeDefined();
95
+ const bundle = readBundle(entry.source);
96
+ expect(skillNames(bundle)).toContain('frontend-craft');
97
+ });
98
+ it('every skill referenced by any bundle exists on disk', () => {
99
+ for (const entry of catalog.bundles) {
100
+ const bundle = readBundle(entry.source);
101
+ for (const s of skillNames(bundle)) {
102
+ expect(fs_1.default.existsSync(path_1.default.join(SKILLS, s, 'SKILL.md'))).toBe(true);
103
+ }
104
+ }
105
+ });
106
+ });
107
+ describe('skills-lock.json', () => {
108
+ const lock = JSON.parse(fs_1.default.readFileSync(LOCK_FILE, 'utf-8'));
109
+ it('records provenance for the new external skills', () => {
110
+ for (const s of ['emil-design-eng', 'design-taste-frontend', 'impeccable', 'extract-design-md', 'code-to-design', 'react-components']) {
111
+ expect(lock.skills[s]).toBeDefined();
112
+ expect(lock.skills[s].source).toMatch(/.+\/.+/);
113
+ expect(lock.skills[s].sourceType).toBe('github');
114
+ }
115
+ });
116
+ });
117
+ describe('post-implementation-qa skill', () => {
118
+ const base = path_1.default.join(SKILLS, 'post-implementation-qa');
119
+ it('exists with valid frontmatter', () => {
120
+ const content = fs_1.default.readFileSync(path_1.default.join(base, 'SKILL.md'), 'utf-8');
121
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
122
+ expect(match).not.toBeNull();
123
+ const fm = match[1];
124
+ expect(fm).toMatch(/^name:\s*post-implementation-qa\s*$/m);
125
+ expect(fm).toMatch(/^description:\s*.+$/m);
126
+ });
127
+ it('includes deep-review-prompt.md', () => {
128
+ expect(fs_1.default.existsSync(path_1.default.join(base, 'deep-review-prompt.md'))).toBe(true);
129
+ });
130
+ it('SKILL.md contains the awm-qa-complete marker instruction', () => {
131
+ const content = fs_1.default.readFileSync(path_1.default.join(base, 'SKILL.md'), 'utf-8');
132
+ expect(content).toMatch(/awm-qa-complete/);
133
+ });
134
+ });
135
+ describe('development-process QA integration', () => {
136
+ it('SKILL.md references post-implementation-qa at least 6 times', () => {
137
+ const content = fs_1.default.readFileSync(path_1.default.join(SKILLS, 'development-process', 'SKILL.md'), 'utf-8');
138
+ const matches = content.match(/post-implementation-qa/g) || [];
139
+ expect(matches.length).toBeGreaterThanOrEqual(6);
140
+ });
141
+ it('SKILL.md references awm-qa-complete at least 2 times', () => {
142
+ const content = fs_1.default.readFileSync(path_1.default.join(SKILLS, 'development-process', 'SKILL.md'), 'utf-8');
143
+ const matches = content.match(/awm-qa-complete/g) || [];
144
+ expect(matches.length).toBeGreaterThanOrEqual(2);
145
+ });
146
+ });
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const REPO_ROOT = path_1.default.resolve(__dirname, '../../..');
9
+ describe('skill prose stays agent-agnostic (#5)', () => {
10
+ const files = ['writing-skills/SKILL.md', 'project-constitution/SKILL.md'];
11
+ for (const f of files) {
12
+ it(`${f} does not push the model to the ~/.claude/skills path`, () => {
13
+ const txt = fs_1.default.readFileSync(path_1.default.join(REPO_ROOT, 'registry/skills', f), 'utf-8');
14
+ expect(txt).not.toMatch(/~\/\.claude\/skills/);
15
+ expect(txt).not.toMatch(/\.claude\/settings\.json/);
16
+ });
17
+ }
18
+ });
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const REGISTRY_ROOT = path_1.default.join(__dirname, '..', '..', '..', 'registry');
9
+ const PACKS_DIR = path_1.default.join(REGISTRY_ROOT, 'sensor-packs');
10
+ describe('sensor-packs registry', () => {
11
+ it('sensor-packs directory exists in registry', () => {
12
+ expect(fs_1.default.existsSync(PACKS_DIR)).toBe(true);
13
+ });
14
+ for (const packName of ['js-ts', 'generic']) {
15
+ describe(`pack: ${packName}`, () => {
16
+ const packDir = path_1.default.join(PACKS_DIR, packName);
17
+ it('directory exists', () => {
18
+ expect(fs_1.default.existsSync(packDir)).toBe(true);
19
+ });
20
+ it('has valid pack.json', () => {
21
+ const packJson = path_1.default.join(packDir, 'pack.json');
22
+ expect(fs_1.default.existsSync(packJson)).toBe(true);
23
+ const parsed = JSON.parse(fs_1.default.readFileSync(packJson, 'utf-8'));
24
+ expect(parsed.name).toBe(packName);
25
+ expect(typeof parsed.description).toBe('string');
26
+ expect(typeof parsed.sensors).toBe('object');
27
+ });
28
+ it('pack.json name matches directory name', () => {
29
+ const parsed = JSON.parse(fs_1.default.readFileSync(path_1.default.join(packDir, 'pack.json'), 'utf-8'));
30
+ expect(parsed.name).toBe(packName);
31
+ });
32
+ });
33
+ }
34
+ it('js-ts pack has required sensor config files', () => {
35
+ const jstsDir = path_1.default.join(PACKS_DIR, 'js-ts');
36
+ expect(fs_1.default.existsSync(path_1.default.join(jstsDir, 'tsconfig.awm.json'))).toBe(true);
37
+ expect(fs_1.default.existsSync(path_1.default.join(jstsDir, 'eslint.config.awm.mjs'))).toBe(true);
38
+ expect(fs_1.default.existsSync(path_1.default.join(jstsDir, '.semgrep.awm.yml'))).toBe(true);
39
+ });
40
+ it('tsconfig.awm.json extends ./tsconfig.json', () => {
41
+ const tsconfig = JSON.parse(fs_1.default.readFileSync(path_1.default.join(PACKS_DIR, 'js-ts', 'tsconfig.awm.json'), 'utf-8'));
42
+ expect(tsconfig.extends).toBe('./tsconfig.json');
43
+ expect(tsconfig.compilerOptions?.strict).toBe(true);
44
+ });
45
+ });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const SKILLS_DIR = path_1.default.join(__dirname, '../../../registry/skills');
9
+ function frontmatter(file) {
10
+ const raw = fs_1.default.readFileSync(file, 'utf-8');
11
+ const m = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/);
12
+ return m ? m[1] : '';
13
+ }
14
+ describe('skill frontmatter version', () => {
15
+ const dirs = fs_1.default.readdirSync(SKILLS_DIR, { withFileTypes: true })
16
+ .filter((e) => e.isDirectory())
17
+ .filter((e) => fs_1.default.existsSync(path_1.default.join(SKILLS_DIR, e.name, 'SKILL.md')))
18
+ .map((e) => e.name);
19
+ it('finds the 41 skills', () => {
20
+ expect(dirs.length).toBe(41);
21
+ });
22
+ it.each(dirs)('skill "%s" declares a semver version', (name) => {
23
+ const fm = frontmatter(path_1.default.join(SKILLS_DIR, name, 'SKILL.md'));
24
+ expect(fm).toMatch(/^version:\s*["']?\d+\.\d+\.\d+["']?\s*$/m);
25
+ });
26
+ });
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ describe('using-awm skill', () => {
9
+ const skillPath = path_1.default.join(__dirname, '../../../registry/skills/using-awm/SKILL.md');
10
+ it('exists at the expected path', () => {
11
+ expect(fs_1.default.existsSync(skillPath)).toBe(true);
12
+ });
13
+ it('has a valid frontmatter with required fields', () => {
14
+ const content = fs_1.default.readFileSync(skillPath, 'utf-8');
15
+ const match = content.match(/^---\n([\s\S]*?)\n---/);
16
+ expect(match).not.toBeNull();
17
+ const frontmatter = match[1];
18
+ expect(frontmatter).toMatch(/^name:\s*using-awm\s*$/m);
19
+ expect(frontmatter).toMatch(/^description:\s*.+$/m);
20
+ });
21
+ it('does NOT contain a model: field (aligned with canon)', () => {
22
+ const content = fs_1.default.readFileSync(skillPath, 'utf-8');
23
+ expect(content).not.toMatch(/^model:\s*/m);
24
+ });
25
+ it('uses tiered triggering (no blanket 1% mandate)', () => {
26
+ const content = fs_1.default.readFileSync(skillPath, 'utf-8');
27
+ expect(content).not.toMatch(/1%/);
28
+ expect(content).toMatch(/always|siempre/i);
29
+ expect(content).toMatch(/signal|señal/i);
30
+ });
31
+ it('contains SUBAGENT-STOP block (prevents recursion)', () => {
32
+ const content = fs_1.default.readFileSync(skillPath, 'utf-8');
33
+ expect(content).toMatch(/<SUBAGENT-STOP>/);
34
+ });
35
+ it('points to development-process as default orchestrator', () => {
36
+ const content = fs_1.default.readFileSync(skillPath, 'utf-8');
37
+ expect(content).toMatch(/development-process/);
38
+ });
39
+ });
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ // tests/utils/config.test.ts
7
+ const config_1 = require("../../src/utils/config");
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const os_1 = __importDefault(require("os"));
11
+ describe('Preferences Manager', () => {
12
+ let tmpDir;
13
+ beforeEach(() => {
14
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-prefs-'));
15
+ process.env.AWM_HOME = tmpDir;
16
+ });
17
+ afterEach(() => {
18
+ delete process.env.AWM_HOME;
19
+ fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
20
+ });
21
+ it('creates default preferences if none exist', () => {
22
+ const prefs = (0, config_1.getPreferences)();
23
+ expect(prefs.defaultAgent).toBe('antigravity');
24
+ });
25
+ it('saves and loads preferences correctly', () => {
26
+ (0, config_1.savePreferences)({ defaultAgent: 'opencode', installMethod: 'copy', defaultScope: 'local' });
27
+ const prefs = (0, config_1.getPreferences)();
28
+ expect(prefs.defaultAgent).toBe('opencode');
29
+ expect(prefs.installMethod).toBe('copy');
30
+ });
31
+ });