agentic-workflow-manager 2.1.0 → 3.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.
- package/dist/src/index.js +130 -17
- package/dist/src/release/core.js +83 -0
- package/dist/src/release/index.js +94 -0
- package/dist/src/release/orchestrator.js +116 -0
- package/dist/src/ui/picker-view.js +77 -0
- package/dist/src/ui/picker.js +194 -0
- package/dist/src/ui/text.js +75 -0
- package/dist/src/ui/tty.js +15 -0
- package/dist/src/utils/registry-view.js +35 -21
- package/dist/tests/release/core.test.js +106 -0
- package/dist/tests/release/orchestrator.test.js +188 -0
- package/dist/tests/ui/picker-reducer.test.js +92 -0
- package/dist/tests/ui/picker-shell.test.js +59 -0
- package/dist/tests/ui/picker-view.test.js +57 -0
- package/dist/tests/ui/text.test.js +55 -0
- package/dist/tests/ui/tty.test.js +39 -0
- package/dist/tests/utils/registry-view.test.js +39 -36
- package/package.json +3 -1
- package/dist/tests/core/registry-versioned-sync.test.js +0 -113
- package/dist/tests/core/registry.test.js +0 -51
- package/dist/tests/registry/b3-ledger-wiring.test.js +0 -74
- package/dist/tests/registry/catalog-consistency.test.js +0 -47
- package/dist/tests/registry/design-skills.test.js +0 -146
- package/dist/tests/registry/prose-agnostic.test.js +0 -18
- package/dist/tests/registry/sensor-packs.test.js +0 -45
- package/dist/tests/registry/skill-versions.test.js +0 -26
- package/dist/tests/registry/using-awm.test.js +0 -39
|
@@ -1,113 +0,0 @@
|
|
|
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/core/registry-versioned-sync.test.ts
|
|
7
|
-
const fs_1 = __importDefault(require("fs"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const os_1 = __importDefault(require("os"));
|
|
10
|
-
const child_process_1 = require("child_process");
|
|
11
|
-
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' });
|
|
12
|
-
function makeTaggedRepo(base, name, versions) {
|
|
13
|
-
const dir = path_1.default.join(base, name);
|
|
14
|
-
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
15
|
-
GIT(dir, 'init -q -b main');
|
|
16
|
-
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), 'init');
|
|
17
|
-
GIT(dir, 'add -A');
|
|
18
|
-
GIT(dir, 'commit -qm init');
|
|
19
|
-
for (const v of versions) {
|
|
20
|
-
fs_1.default.writeFileSync(path_1.default.join(dir, 'VERSION'), v);
|
|
21
|
-
GIT(dir, 'add -A');
|
|
22
|
-
GIT(dir, `commit -qm ${v}`);
|
|
23
|
-
GIT(dir, `tag v${v}`);
|
|
24
|
-
}
|
|
25
|
-
return dir;
|
|
26
|
-
}
|
|
27
|
-
function addRelease(source, version) {
|
|
28
|
-
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), version);
|
|
29
|
-
GIT(source, 'add -A');
|
|
30
|
-
GIT(source, `commit -qm ${version}`);
|
|
31
|
-
GIT(source, `tag v${version}`);
|
|
32
|
-
}
|
|
33
|
-
describe('syncRegistry versionado (fixtures git locales)', () => {
|
|
34
|
-
let tmpHome;
|
|
35
|
-
let tmpWork;
|
|
36
|
-
let originalHome;
|
|
37
|
-
let originalAwmHome;
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regver-home-'));
|
|
40
|
-
tmpWork = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-regver-work-'));
|
|
41
|
-
originalHome = process.env.HOME;
|
|
42
|
-
originalAwmHome = process.env.AWM_HOME;
|
|
43
|
-
process.env.HOME = tmpHome;
|
|
44
|
-
process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
|
|
45
|
-
jest.resetModules();
|
|
46
|
-
});
|
|
47
|
-
afterEach(() => {
|
|
48
|
-
fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
|
|
49
|
-
fs_1.default.rmSync(tmpWork, { recursive: true, force: true });
|
|
50
|
-
if (originalHome === undefined)
|
|
51
|
-
delete process.env.HOME;
|
|
52
|
-
else
|
|
53
|
-
process.env.HOME = originalHome;
|
|
54
|
-
if (originalAwmHome === undefined)
|
|
55
|
-
delete process.env.AWM_HOME;
|
|
56
|
-
else
|
|
57
|
-
process.env.AWM_HOME = originalAwmHome;
|
|
58
|
-
});
|
|
59
|
-
const registryVersionFile = () => path_1.default.join(process.env.AWM_HOME, 'cli-source/VERSION');
|
|
60
|
-
it('clone fresco queda checkouteado en el último tag (no en HEAD)', async () => {
|
|
61
|
-
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
62
|
-
// commit post-tag: HEAD del remote va más allá del último release
|
|
63
|
-
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), 'unreleased');
|
|
64
|
-
GIT(source, 'add -A');
|
|
65
|
-
GIT(source, 'commit -qm unreleased');
|
|
66
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
67
|
-
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
68
|
-
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
69
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.0.0');
|
|
70
|
-
});
|
|
71
|
-
it('clone existente transiciona al tag nuevo tras un release en el remote', async () => {
|
|
72
|
-
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
73
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
74
|
-
await syncRegistry(source, { channel: 'stable' });
|
|
75
|
-
addRelease(source, '1.1.0');
|
|
76
|
-
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
77
|
-
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.1.0', version: '1.1.0' });
|
|
78
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.1.0');
|
|
79
|
-
});
|
|
80
|
-
it('rollback: pin a un tag anterior vuelve el contenido a esa versión', async () => {
|
|
81
|
-
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0', '1.1.0']);
|
|
82
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
83
|
-
await syncRegistry(source, { channel: 'stable' });
|
|
84
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.1.0');
|
|
85
|
-
const resolved = await syncRegistry(source, { pin: '1.0.0', channel: 'stable' });
|
|
86
|
-
expect(resolved).toEqual({ kind: 'tag', ref: 'v1.0.0', version: '1.0.0' });
|
|
87
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('1.0.0');
|
|
88
|
-
});
|
|
89
|
-
it('canal dev sigue HEAD del branch y recibe commits nuevos', async () => {
|
|
90
|
-
const source = makeTaggedRepo(tmpWork, 'src', ['1.0.0']);
|
|
91
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
92
|
-
const first = await syncRegistry(source, { channel: 'dev' });
|
|
93
|
-
expect(first).toEqual({ kind: 'head', ref: 'main' });
|
|
94
|
-
fs_1.default.writeFileSync(path_1.default.join(source, 'VERSION'), 'head-2');
|
|
95
|
-
GIT(source, 'add -A');
|
|
96
|
-
GIT(source, 'commit -qm head-2');
|
|
97
|
-
await syncRegistry(source, { channel: 'dev' });
|
|
98
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('head-2');
|
|
99
|
-
});
|
|
100
|
-
it('repo sin tags en canal stable → head-fallback y sigue HEAD', async () => {
|
|
101
|
-
const source = makeTaggedRepo(tmpWork, 'src', []);
|
|
102
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
103
|
-
const resolved = await syncRegistry(source, { channel: 'stable' });
|
|
104
|
-
expect(resolved).toEqual({ kind: 'head-fallback', ref: 'main' });
|
|
105
|
-
expect(fs_1.default.readFileSync(registryVersionFile(), 'utf-8')).toBe('init');
|
|
106
|
-
});
|
|
107
|
-
it('sin opts (callers legacy) → comportamiento stable por default', async () => {
|
|
108
|
-
const source = makeTaggedRepo(tmpWork, 'src', ['2.0.0']);
|
|
109
|
-
const { syncRegistry } = require('../../src/core/registry');
|
|
110
|
-
const resolved = await syncRegistry(source);
|
|
111
|
-
expect(resolved).toEqual({ kind: 'tag', ref: 'v2.0.0', version: '2.0.0' });
|
|
112
|
-
});
|
|
113
|
-
});
|
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const child_process_1 = require("child_process");
|
|
4
|
-
jest.mock('child_process');
|
|
5
|
-
const mockSpawnSync = child_process_1.spawnSync;
|
|
6
|
-
describe('buildCli', () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
jest.clearAllMocks();
|
|
9
|
-
});
|
|
10
|
-
it('returns success when npm run build exits 0', () => {
|
|
11
|
-
mockSpawnSync.mockReturnValue({ status: 0, stderr: Buffer.from(''), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
12
|
-
const { buildCli } = require('../../src/core/registry');
|
|
13
|
-
const result = buildCli('/fake/cli');
|
|
14
|
-
expect(result).toEqual({ success: true });
|
|
15
|
-
expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['run', 'build'], expect.objectContaining({ cwd: '/fake/cli', shell: true }));
|
|
16
|
-
});
|
|
17
|
-
it('returns failure with error message when build exits non-zero', () => {
|
|
18
|
-
mockSpawnSync.mockReturnValue({ status: 1, stderr: Buffer.from('tsc error: Type mismatch'), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
19
|
-
const { buildCli } = require('../../src/core/registry');
|
|
20
|
-
const result = buildCli('/fake/cli');
|
|
21
|
-
expect(result.success).toBe(false);
|
|
22
|
-
expect(result.error).toContain('tsc error');
|
|
23
|
-
});
|
|
24
|
-
it('returns failure with stdout message when tsc writes errors to stdout', () => {
|
|
25
|
-
mockSpawnSync.mockReturnValue({ status: 2, stderr: Buffer.from(''), stdout: Buffer.from('error TS2322: Type mismatch'), pid: 1, output: [], signal: null });
|
|
26
|
-
const { buildCli } = require('../../src/core/registry');
|
|
27
|
-
const result = buildCli('/fake/cli');
|
|
28
|
-
expect(result.success).toBe(false);
|
|
29
|
-
expect(result.error).toContain('TS2322');
|
|
30
|
-
});
|
|
31
|
-
it('returns failure when spawnSync throws unexpectedly', () => {
|
|
32
|
-
mockSpawnSync.mockImplementation(() => { throw new Error('unexpected error'); });
|
|
33
|
-
const { buildCli } = require('../../src/core/registry');
|
|
34
|
-
const result = buildCli('/fake/cli');
|
|
35
|
-
expect(result.success).toBe(false);
|
|
36
|
-
expect(result.error).toBe('unexpected error');
|
|
37
|
-
});
|
|
38
|
-
it('returns failure when npm is not found (shell returns status 127)', () => {
|
|
39
|
-
mockSpawnSync.mockReturnValue({ status: 127, stderr: Buffer.from('/bin/sh: npm: not found'), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
40
|
-
const { buildCli } = require('../../src/core/registry');
|
|
41
|
-
const result = buildCli('/fake/cli');
|
|
42
|
-
expect(result.success).toBe(false);
|
|
43
|
-
expect(result.error).toContain('not found');
|
|
44
|
-
});
|
|
45
|
-
it('uses REGISTRY_DIR/cli as default cwd', () => {
|
|
46
|
-
mockSpawnSync.mockReturnValue({ status: 0, stderr: Buffer.from(''), stdout: Buffer.from(''), pid: 1, output: [], signal: null });
|
|
47
|
-
const { buildCli, REGISTRY_DIR } = require('../../src/core/registry');
|
|
48
|
-
buildCli();
|
|
49
|
-
expect(mockSpawnSync).toHaveBeenCalledWith('npm', ['run', 'build'], expect.objectContaining({ cwd: `${REGISTRY_DIR}/cli` }));
|
|
50
|
-
});
|
|
51
|
-
});
|
|
@@ -1,74 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,146 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,18 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1,39 +0,0 @@
|
|
|
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
|
-
});
|