@productbrain/cli 0.1.0-beta.85 → 0.1.0-beta.88
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/__tests__/hook-intents.test.d.ts +2 -0
- package/dist/__tests__/hook-intents.test.d.ts.map +1 -0
- package/dist/__tests__/hook-intents.test.js +184 -0
- package/dist/__tests__/hook-intents.test.js.map +1 -0
- package/dist/__tests__/manifest.test.d.ts +6 -0
- package/dist/__tests__/manifest.test.d.ts.map +1 -0
- package/dist/__tests__/manifest.test.js +138 -0
- package/dist/__tests__/manifest.test.js.map +1 -0
- package/dist/__tests__/method-registry.integration.test.d.ts +6 -0
- package/dist/__tests__/method-registry.integration.test.d.ts.map +1 -0
- package/dist/__tests__/method-registry.integration.test.js +18 -0
- package/dist/__tests__/method-registry.integration.test.js.map +1 -0
- package/dist/__tests__/method-registry.test.d.ts +14 -0
- package/dist/__tests__/method-registry.test.d.ts.map +1 -0
- package/dist/__tests__/method-registry.test.js +134 -0
- package/dist/__tests__/method-registry.test.js.map +1 -0
- package/dist/__tests__/personal-layer.test.d.ts +12 -0
- package/dist/__tests__/personal-layer.test.d.ts.map +1 -0
- package/dist/__tests__/personal-layer.test.js +304 -0
- package/dist/__tests__/personal-layer.test.js.map +1 -0
- package/dist/__tests__/surfaces.test.d.ts +2 -0
- package/dist/__tests__/surfaces.test.d.ts.map +1 -0
- package/dist/__tests__/surfaces.test.js +46 -0
- package/dist/__tests__/surfaces.test.js.map +1 -0
- package/dist/commands/admin/seed.d.ts.map +1 -1
- package/dist/commands/admin/seed.js +6 -0
- package/dist/commands/admin/seed.js.map +1 -1
- package/dist/commands/connect-screens.js +1 -1
- package/dist/commands/connect-screens.js.map +1 -1
- package/dist/commands/handshake.d.ts +2 -0
- package/dist/commands/handshake.d.ts.map +1 -1
- package/dist/commands/handshake.js +117 -32
- package/dist/commands/handshake.js.map +1 -1
- package/dist/commands/method.d.ts +99 -0
- package/dist/commands/method.d.ts.map +1 -0
- package/dist/commands/method.js +781 -0
- package/dist/commands/method.js.map +1 -0
- package/dist/commands/session.js +5 -5
- package/dist/commands/session.js.map +1 -1
- package/dist/formatters/handshake.d.ts +20 -0
- package/dist/formatters/handshake.d.ts.map +1 -1
- package/dist/formatters/handshake.js +86 -2
- package/dist/formatters/handshake.js.map +1 -1
- package/dist/generators/manifest.d.ts +37 -0
- package/dist/generators/manifest.d.ts.map +1 -0
- package/dist/generators/manifest.js +166 -0
- package/dist/generators/manifest.js.map +1 -0
- package/dist/generators/portable-knowledge.d.ts +22 -0
- package/dist/generators/portable-knowledge.d.ts.map +1 -1
- package/dist/generators/portable-knowledge.js +109 -0
- package/dist/generators/portable-knowledge.js.map +1 -1
- package/dist/index.js +45 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/hook-intents.d.ts +51 -0
- package/dist/lib/hook-intents.d.ts.map +1 -0
- package/dist/lib/hook-intents.js +85 -0
- package/dist/lib/hook-intents.js.map +1 -0
- package/dist/lib/method-registry.d.ts +32 -0
- package/dist/lib/method-registry.d.ts.map +1 -0
- package/dist/lib/method-registry.js +53 -0
- package/dist/lib/method-registry.js.map +1 -0
- package/dist/surfaces/registry.d.ts +20 -0
- package/dist/surfaces/registry.d.ts.map +1 -0
- package/dist/surfaces/registry.js +42 -0
- package/dist/surfaces/registry.js.map +1 -0
- package/package.json +1 -1
- package/templates/method-registry.json +16 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-intents.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hook-intents.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hook-intents.ts — unit tests.
|
|
3
|
+
* WP-310 E3a: intent model extracts hook composition from writeTeamSettings().
|
|
4
|
+
* WP-310 E3b / S4: getHookStatusForSurface multi-surface hook status.
|
|
5
|
+
* TEN-712: hook commands must fail silently (|| true suffix).
|
|
6
|
+
*/
|
|
7
|
+
import { describe, expect, it } from 'vitest';
|
|
8
|
+
import { HOOK_INTENT_CONFIGS, composeHooksFromIntents, getHookStatusForSurface, } from '../lib/hook-intents.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// HOOK_INTENT_CONFIGS — static shape checks
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
describe('HOOK_INTENT_CONFIGS', () => {
|
|
13
|
+
it('has all three intents defined', () => {
|
|
14
|
+
const intents = ['session-start', 'session-close', 'pre-compact'];
|
|
15
|
+
for (const intent of intents) {
|
|
16
|
+
expect(HOOK_INTENT_CONFIGS[intent]).toBeDefined();
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
it('maps session-start to SessionStart hook event', () => {
|
|
20
|
+
expect(HOOK_INTENT_CONFIGS['session-start'].hookEvent).toBe('SessionStart');
|
|
21
|
+
});
|
|
22
|
+
it('maps session-close to Stop hook event', () => {
|
|
23
|
+
expect(HOOK_INTENT_CONFIGS['session-close'].hookEvent).toBe('Stop');
|
|
24
|
+
});
|
|
25
|
+
it('maps pre-compact to PreCompact hook event', () => {
|
|
26
|
+
expect(HOOK_INTENT_CONFIGS['pre-compact'].hookEvent).toBe('PreCompact');
|
|
27
|
+
});
|
|
28
|
+
it('session-start command matches TEN-712 silent-failure pattern', () => {
|
|
29
|
+
const cmd = HOOK_INTENT_CONFIGS['session-start'].command;
|
|
30
|
+
expect(cmd).toBe('pb session start 2>/dev/null || true');
|
|
31
|
+
});
|
|
32
|
+
it('session-close command matches TEN-712 silent-failure pattern', () => {
|
|
33
|
+
const cmd = HOOK_INTENT_CONFIGS['session-close'].command;
|
|
34
|
+
expect(cmd).toBe('pb session close 2>/dev/null || true');
|
|
35
|
+
});
|
|
36
|
+
it('pre-compact command ends with || true (TEN-712)', () => {
|
|
37
|
+
const cmd = HOOK_INTENT_CONFIGS['pre-compact'].command;
|
|
38
|
+
expect(cmd.endsWith('|| true')).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
it('session-start has statusMessage', () => {
|
|
41
|
+
expect(HOOK_INTENT_CONFIGS['session-start'].statusMessage).toBe('Opening pb session...');
|
|
42
|
+
});
|
|
43
|
+
it('session-close has statusMessage', () => {
|
|
44
|
+
expect(HOOK_INTENT_CONFIGS['session-close'].statusMessage).toBe('Closing pb session...');
|
|
45
|
+
});
|
|
46
|
+
it('pre-compact has no statusMessage', () => {
|
|
47
|
+
expect(HOOK_INTENT_CONFIGS['pre-compact'].statusMessage).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
// composeHooksFromIntents — idempotency and addition logic
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
describe('composeHooksFromIntents', () => {
|
|
54
|
+
it('returns all 3 additions when existingHooks is empty', () => {
|
|
55
|
+
const additions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], {});
|
|
56
|
+
expect(additions).toHaveLength(3);
|
|
57
|
+
const events = additions.map((a) => a.event);
|
|
58
|
+
expect(events).toContain('SessionStart');
|
|
59
|
+
expect(events).toContain('Stop');
|
|
60
|
+
expect(events).toContain('PreCompact');
|
|
61
|
+
});
|
|
62
|
+
it('returns only 2 additions when session-start is already present', () => {
|
|
63
|
+
const sessionStartCmd = HOOK_INTENT_CONFIGS['session-start'].command;
|
|
64
|
+
const existingHooks = {
|
|
65
|
+
SessionStart: [
|
|
66
|
+
{ hooks: [{ type: 'command', command: sessionStartCmd, statusMessage: 'Opening pb session...' }] },
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
const additions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], existingHooks);
|
|
70
|
+
expect(additions).toHaveLength(2);
|
|
71
|
+
const events = additions.map((a) => a.event);
|
|
72
|
+
expect(events).not.toContain('SessionStart');
|
|
73
|
+
expect(events).toContain('Stop');
|
|
74
|
+
expect(events).toContain('PreCompact');
|
|
75
|
+
});
|
|
76
|
+
it('returns empty array when all 3 hooks are already present', () => {
|
|
77
|
+
const existingHooks = {
|
|
78
|
+
SessionStart: [
|
|
79
|
+
{ hooks: [{ type: 'command', command: HOOK_INTENT_CONFIGS['session-start'].command }] },
|
|
80
|
+
],
|
|
81
|
+
Stop: [
|
|
82
|
+
{ hooks: [{ type: 'command', command: HOOK_INTENT_CONFIGS['session-close'].command }] },
|
|
83
|
+
],
|
|
84
|
+
PreCompact: [
|
|
85
|
+
{ hooks: [{ type: 'command', command: HOOK_INTENT_CONFIGS['pre-compact'].command }] },
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
const additions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], existingHooks);
|
|
89
|
+
expect(additions).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
it('each addition entry has a single hook with type=command', () => {
|
|
92
|
+
const additions = composeHooksFromIntents(['session-start'], {});
|
|
93
|
+
expect(additions).toHaveLength(1);
|
|
94
|
+
const [addition] = additions;
|
|
95
|
+
expect(addition.entry.hooks).toHaveLength(1);
|
|
96
|
+
expect(addition.entry.hooks[0].type).toBe('command');
|
|
97
|
+
});
|
|
98
|
+
it('session-start addition includes statusMessage', () => {
|
|
99
|
+
const additions = composeHooksFromIntents(['session-start'], {});
|
|
100
|
+
const [addition] = additions;
|
|
101
|
+
expect(addition.entry.hooks[0].statusMessage).toBe('Opening pb session...');
|
|
102
|
+
});
|
|
103
|
+
it('pre-compact addition does NOT include statusMessage', () => {
|
|
104
|
+
const additions = composeHooksFromIntents(['pre-compact'], {});
|
|
105
|
+
const [addition] = additions;
|
|
106
|
+
expect(addition.entry.hooks[0].statusMessage).toBeUndefined();
|
|
107
|
+
});
|
|
108
|
+
it('addition label for session-start matches expected string', () => {
|
|
109
|
+
const additions = composeHooksFromIntents(['session-start'], {});
|
|
110
|
+
expect(additions[0].label).toBe('SessionStart → pb session start');
|
|
111
|
+
});
|
|
112
|
+
it('addition label for session-close matches expected string', () => {
|
|
113
|
+
const additions = composeHooksFromIntents(['session-close'], {});
|
|
114
|
+
expect(additions[0].label).toBe('Stop → pb session close');
|
|
115
|
+
});
|
|
116
|
+
it('addition label for pre-compact matches expected string', () => {
|
|
117
|
+
const additions = composeHooksFromIntents(['pre-compact'], {});
|
|
118
|
+
expect(additions[0].label).toBe('PreCompact → decision capture reminder');
|
|
119
|
+
});
|
|
120
|
+
it('returns empty array when no intents are requested', () => {
|
|
121
|
+
const additions = composeHooksFromIntents([], {});
|
|
122
|
+
expect(additions).toHaveLength(0);
|
|
123
|
+
});
|
|
124
|
+
it('command in addition matches HOOK_INTENT_CONFIGS command', () => {
|
|
125
|
+
const additions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], {});
|
|
126
|
+
const sessionStart = additions.find((a) => a.event === 'SessionStart');
|
|
127
|
+
const sessionClose = additions.find((a) => a.event === 'Stop');
|
|
128
|
+
const preCompact = additions.find((a) => a.event === 'PreCompact');
|
|
129
|
+
expect(sessionStart?.entry.hooks[0].command).toBe(HOOK_INTENT_CONFIGS['session-start'].command);
|
|
130
|
+
expect(sessionClose?.entry.hooks[0].command).toBe(HOOK_INTENT_CONFIGS['session-close'].command);
|
|
131
|
+
expect(preCompact?.entry.hooks[0].command).toBe(HOOK_INTENT_CONFIGS['pre-compact'].command);
|
|
132
|
+
});
|
|
133
|
+
it('detects existing hook by command string match (hasCommand logic)', () => {
|
|
134
|
+
// Same event, different command → should not suppress
|
|
135
|
+
const existingHooks = {
|
|
136
|
+
SessionStart: [
|
|
137
|
+
{ hooks: [{ type: 'command', command: 'some-other-command' }] },
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
const additions = composeHooksFromIntents(['session-start'], existingHooks);
|
|
141
|
+
expect(additions).toHaveLength(1);
|
|
142
|
+
expect(additions[0].event).toBe('SessionStart');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// getHookStatusForSurface — WP-310 E3b / S4 multi-surface hook status
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
describe('getHookStatusForSurface', () => {
|
|
149
|
+
it('claude → writable: true (has hook events)', () => {
|
|
150
|
+
const result = getHookStatusForSurface('claude');
|
|
151
|
+
expect(result.writable).toBe(true);
|
|
152
|
+
expect(result.reason).toBe('native hook events supported');
|
|
153
|
+
});
|
|
154
|
+
it('cursor → writable: false (INS-740: no session hook events)', () => {
|
|
155
|
+
const result = getHookStatusForSurface('cursor');
|
|
156
|
+
expect(result.writable).toBe(false);
|
|
157
|
+
});
|
|
158
|
+
it('cursor reason contains "manually" (user guidance)', () => {
|
|
159
|
+
const result = getHookStatusForSurface('cursor');
|
|
160
|
+
expect(result.reason).toContain('manually');
|
|
161
|
+
});
|
|
162
|
+
it('copilot → writable: false (INS-740: no session hook events)', () => {
|
|
163
|
+
const result = getHookStatusForSurface('copilot');
|
|
164
|
+
expect(result.writable).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
it('copilot reason contains surface name', () => {
|
|
167
|
+
const result = getHookStatusForSurface('copilot');
|
|
168
|
+
expect(result.reason).toContain('copilot');
|
|
169
|
+
});
|
|
170
|
+
it('codex → writable: false (no hook events)', () => {
|
|
171
|
+
const result = getHookStatusForSurface('codex');
|
|
172
|
+
expect(result.writable).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
it('codex reason contains surface name', () => {
|
|
175
|
+
const result = getHookStatusForSurface('codex');
|
|
176
|
+
expect(result.reason).toContain('codex');
|
|
177
|
+
});
|
|
178
|
+
it('non-writable reason contains "manually" for cursor', () => {
|
|
179
|
+
// Explicit check that the user guidance message points to manual invocation
|
|
180
|
+
const result = getHookStatusForSurface('cursor');
|
|
181
|
+
expect(result.reason).toMatch(/pb session start/);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
//# sourceMappingURL=hook-intents.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-intents.test.js","sourceRoot":"","sources":["../../src/__tests__/hook-intents.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/manifest.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* manifest.ts — unit tests for readManifest() and filterByAdoptionState().
|
|
3
|
+
* WP-310 E1: personal workspace layer — manifest reader + adoption state filter.
|
|
4
|
+
*/
|
|
5
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
// vi.mock calls are hoisted — use vi.hoisted() for constants referenced inside factories.
|
|
8
|
+
const { vfs, MOCK_PB_DIR } = vi.hoisted(() => ({
|
|
9
|
+
vfs: {},
|
|
10
|
+
MOCK_PB_DIR: '/tmp/pb-test/.productbrain',
|
|
11
|
+
}));
|
|
12
|
+
vi.mock('fs', () => ({
|
|
13
|
+
existsSync: vi.fn((path) => path in vfs),
|
|
14
|
+
readFileSync: vi.fn((path, _enc) => {
|
|
15
|
+
if (path in vfs)
|
|
16
|
+
return vfs[path];
|
|
17
|
+
throw Object.assign(new Error(`ENOENT: no such file '${path}'`), { code: 'ENOENT' });
|
|
18
|
+
}),
|
|
19
|
+
}));
|
|
20
|
+
import { readManifest, filterByAdoptionState } from '../generators/manifest.js';
|
|
21
|
+
const MANIFEST_PATH = join(MOCK_PB_DIR, 'manifest.yaml');
|
|
22
|
+
describe('readManifest()', () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
Object.keys(vfs).forEach((k) => delete vfs[k]);
|
|
25
|
+
vi.clearAllMocks();
|
|
26
|
+
});
|
|
27
|
+
it('returns null when manifest.yaml is absent', () => {
|
|
28
|
+
// vfs is empty — no manifest.yaml
|
|
29
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
30
|
+
expect(result).toBeNull();
|
|
31
|
+
});
|
|
32
|
+
it('returns null when manifest.yaml has invalid YAML (throws during parse)', async () => {
|
|
33
|
+
// A file that causes our parser to surface an error: we simulate by making readFileSync throw
|
|
34
|
+
const fs = await import('fs');
|
|
35
|
+
vi.mocked(fs.readFileSync).mockImplementationOnce(() => {
|
|
36
|
+
throw new Error('read error');
|
|
37
|
+
});
|
|
38
|
+
// existsSync must return true for the manifest path
|
|
39
|
+
vi.mocked(fs.existsSync).mockImplementation((p) => String(p) === MANIFEST_PATH);
|
|
40
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
41
|
+
expect(result).toBeNull();
|
|
42
|
+
});
|
|
43
|
+
it('returns a parsed Manifest when manifest.yaml is valid', () => {
|
|
44
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nadopted:\n - rule-a\n - rule-b\nrejected:\n - rule-z\n`;
|
|
45
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
46
|
+
expect(result).not.toBeNull();
|
|
47
|
+
expect(result.version).toBe('0.1');
|
|
48
|
+
expect(result.adopted).toEqual(['rule-a', 'rule-b']);
|
|
49
|
+
expect(result.rejected).toEqual(['rule-z']);
|
|
50
|
+
});
|
|
51
|
+
it('returns a Manifest with no adopted/rejected when those keys are absent', () => {
|
|
52
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\n`;
|
|
53
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
54
|
+
expect(result).not.toBeNull();
|
|
55
|
+
expect(result.adopted).toBeUndefined();
|
|
56
|
+
expect(result.rejected).toBeUndefined();
|
|
57
|
+
});
|
|
58
|
+
it('returns null when adopted is a scalar string instead of an array', () => {
|
|
59
|
+
// adopted: rule-a (no dash) parses as a string, not array — must be rejected
|
|
60
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nadopted: rule-a\n`;
|
|
61
|
+
expect(readManifest(MOCK_PB_DIR)).toBeNull();
|
|
62
|
+
});
|
|
63
|
+
it('returns null when rejected is a scalar string instead of an array', () => {
|
|
64
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nrejected: rule-z\n`;
|
|
65
|
+
expect(readManifest(MOCK_PB_DIR)).toBeNull();
|
|
66
|
+
});
|
|
67
|
+
it('returns null when version is missing', () => {
|
|
68
|
+
vfs[MANIFEST_PATH] = `adopted:\n - rule-a\n`;
|
|
69
|
+
expect(readManifest(MOCK_PB_DIR)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
it('returns null when method_source has an invalid value', () => {
|
|
72
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nmethod_source: chn\n`;
|
|
73
|
+
expect(readManifest(MOCK_PB_DIR)).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
it('accepts method_source: chain', () => {
|
|
76
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nmethod_source: chain\n`;
|
|
77
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
78
|
+
expect(result).not.toBeNull();
|
|
79
|
+
expect(result.method_source).toBe('chain');
|
|
80
|
+
});
|
|
81
|
+
it('accepts method_source: bundled', () => {
|
|
82
|
+
vfs[MANIFEST_PATH] = `version: "0.1"\nmethod_source: bundled\n`;
|
|
83
|
+
const result = readManifest(MOCK_PB_DIR);
|
|
84
|
+
expect(result).not.toBeNull();
|
|
85
|
+
expect(result.method_source).toBe('bundled');
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
describe('filterByAdoptionState()', () => {
|
|
89
|
+
const rules = [
|
|
90
|
+
{ name: 'rule-a', description: 'A' },
|
|
91
|
+
{ name: 'rule-b', description: 'B' },
|
|
92
|
+
{ name: 'rule-c', description: 'C' },
|
|
93
|
+
];
|
|
94
|
+
it('returns all items unchanged when manifest is null (no-op)', () => {
|
|
95
|
+
const result = filterByAdoptionState(rules, null);
|
|
96
|
+
expect(result).toEqual(rules);
|
|
97
|
+
expect(result).toHaveLength(3);
|
|
98
|
+
});
|
|
99
|
+
it('returns only adopted items when adopted list is non-empty', () => {
|
|
100
|
+
const manifest = { version: '0.1', adopted: ['rule-a'] };
|
|
101
|
+
const result = filterByAdoptionState(rules, manifest);
|
|
102
|
+
expect(result).toHaveLength(1);
|
|
103
|
+
expect(result[0].name).toBe('rule-a');
|
|
104
|
+
});
|
|
105
|
+
it('excludes rejected items and includes all others when only rejected is set', () => {
|
|
106
|
+
const manifest = { version: '0.1', rejected: ['rule-b'] };
|
|
107
|
+
const result = filterByAdoptionState(rules, manifest);
|
|
108
|
+
expect(result).toHaveLength(2);
|
|
109
|
+
expect(result.map((r) => r.name)).toEqual(['rule-a', 'rule-c']);
|
|
110
|
+
});
|
|
111
|
+
it('rejected wins over adopted — item in both lists is excluded', () => {
|
|
112
|
+
// rejected is applied first as denylist, then adopted allowlist
|
|
113
|
+
// rule-a is in both → rejected first → excluded
|
|
114
|
+
const manifest = { version: '0.1', adopted: ['rule-a'], rejected: ['rule-a'] };
|
|
115
|
+
const result = filterByAdoptionState(rules, manifest);
|
|
116
|
+
expect(result).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
it('empty adopted array passes all non-rejected items through (allowlist inactive)', () => {
|
|
119
|
+
const manifest = { version: '0.1', adopted: [] };
|
|
120
|
+
const result = filterByAdoptionState(rules, manifest);
|
|
121
|
+
expect(result).toHaveLength(3);
|
|
122
|
+
expect(result).toEqual(rules);
|
|
123
|
+
});
|
|
124
|
+
it('empty rejected array leaves all items in place', () => {
|
|
125
|
+
const manifest = { version: '0.1', rejected: [] };
|
|
126
|
+
const result = filterByAdoptionState(rules, manifest);
|
|
127
|
+
expect(result).toHaveLength(3);
|
|
128
|
+
expect(result).toEqual(rules);
|
|
129
|
+
});
|
|
130
|
+
it('works with generic item shapes that have a name property', () => {
|
|
131
|
+
const items = [{ name: 'rule-a', extra: 42 }, { name: 'rule-b', extra: 99 }];
|
|
132
|
+
const manifest = { version: '0.1', adopted: ['rule-a'] };
|
|
133
|
+
const result = filterByAdoptionState(items, manifest);
|
|
134
|
+
expect(result).toHaveLength(1);
|
|
135
|
+
expect(result[0]).toEqual({ name: 'rule-a', extra: 42 });
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=manifest.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.test.js","sourceRoot":"","sources":["../../src/__tests__/manifest.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-registry.integration.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/method-registry.integration.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Integration test: loadMethodRegistry reads the actual templates/method-registry.json
|
|
3
|
+
* from the package root. No fs mocking — exercises the real file path resolution.
|
|
4
|
+
*/
|
|
5
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
6
|
+
import { loadMethodRegistry } from '../lib/method-registry.js';
|
|
7
|
+
describe('method-registry (integration — real fs)', () => {
|
|
8
|
+
it('loads actual bundled registry from package-root templates/ using real fs', async () => {
|
|
9
|
+
const chainFetch = vi.fn().mockRejectedValue(new Error('offline'));
|
|
10
|
+
const result = await loadMethodRegistry('bundled', chainFetch);
|
|
11
|
+
expect(result.source).toBe('bundled');
|
|
12
|
+
expect(result.registry.version).toBe('1.0');
|
|
13
|
+
expect(result.registry.methods.length).toBeGreaterThan(0);
|
|
14
|
+
expect(typeof result.registry.methods[0].name).toBe('string');
|
|
15
|
+
expect(typeof result.registry.generatedAt).toBe('string');
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
//# sourceMappingURL=method-registry.integration.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-registry.integration.test.js","sourceRoot":"","sources":["../../src/__tests__/method-registry.integration.test.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,QAAQ,CAAC,yCAAyC,EAAE,GAAG,EAAE;IACvD,EAAE,CAAC,0EAA0E,EAAE,KAAK,IAAI,EAAE;QACxF,MAAM,UAAU,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;QACnE,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC1D,MAAM,CAAC,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9D,MAAM,CAAC,OAAO,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC5D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* method-registry.test.ts — unit tests for WP-310 E4: method registry loader.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. loadMethodRegistry('bundled', ...) → source: 'bundled', returns registry with methods array
|
|
6
|
+
* 2. loadMethodRegistry('chain', chainFetch) where chainFetch resolves → source: 'chain'
|
|
7
|
+
* 3. loadMethodRegistry('chain', chainFetch) where chainFetch rejects → source: 'bundled' (fallback)
|
|
8
|
+
* 4. loadMethodRegistry(undefined, chainFetch) where chainFetch rejects → source: 'bundled' (fallback)
|
|
9
|
+
* 5. Stale detection: registry with old generatedAt → stale: true
|
|
10
|
+
* 6. Fresh registry: stale: false
|
|
11
|
+
* 7. Bundled JSON has all required fields (version, generatedAt, methods array, each method has name/usage/description)
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=method-registry.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/method-registry.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* method-registry.test.ts — unit tests for WP-310 E4: method registry loader.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. loadMethodRegistry('bundled', ...) → source: 'bundled', returns registry with methods array
|
|
6
|
+
* 2. loadMethodRegistry('chain', chainFetch) where chainFetch resolves → source: 'chain'
|
|
7
|
+
* 3. loadMethodRegistry('chain', chainFetch) where chainFetch rejects → source: 'bundled' (fallback)
|
|
8
|
+
* 4. loadMethodRegistry(undefined, chainFetch) where chainFetch rejects → source: 'bundled' (fallback)
|
|
9
|
+
* 5. Stale detection: registry with old generatedAt → stale: true
|
|
10
|
+
* 6. Fresh registry: stale: false
|
|
11
|
+
* 7. Bundled JSON has all required fields (version, generatedAt, methods array, each method has name/usage/description)
|
|
12
|
+
*/
|
|
13
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
14
|
+
// Stable bundled registry fixture used across tests
|
|
15
|
+
const BUNDLED_REGISTRY = {
|
|
16
|
+
version: '1.0',
|
|
17
|
+
generatedAt: new Date(Date.now() - 1 * 24 * 60 * 60 * 1000).toISOString(), // 1 day ago — fresh
|
|
18
|
+
methods: [
|
|
19
|
+
{ name: 'session start', usage: 'pb session start', description: 'Open a tracked write session.' },
|
|
20
|
+
{ name: 'capture', usage: 'pb capture "TYPE: text"', description: 'Capture a draft entry.' },
|
|
21
|
+
],
|
|
22
|
+
};
|
|
23
|
+
const STALE_BUNDLED_REGISTRY = {
|
|
24
|
+
...BUNDLED_REGISTRY,
|
|
25
|
+
generatedAt: new Date(Date.now() - 8 * 24 * 60 * 60 * 1000).toISOString(), // 8 days ago — stale
|
|
26
|
+
};
|
|
27
|
+
const CHAIN_REGISTRY = {
|
|
28
|
+
version: '1.1',
|
|
29
|
+
generatedAt: new Date().toISOString(),
|
|
30
|
+
methods: [
|
|
31
|
+
{ name: 'orient', usage: 'pb orient --task "<task>"', description: 'Get task-relevant governance.' },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
// Mock fs before importing the module under test
|
|
35
|
+
vi.mock('fs', () => ({
|
|
36
|
+
readFileSync: vi.fn(() => JSON.stringify(BUNDLED_REGISTRY)),
|
|
37
|
+
}));
|
|
38
|
+
import { loadMethodRegistry } from '../lib/method-registry.js';
|
|
39
|
+
import { readFileSync } from 'fs';
|
|
40
|
+
const mockReadFileSync = vi.mocked(readFileSync);
|
|
41
|
+
beforeEach(() => {
|
|
42
|
+
vi.clearAllMocks();
|
|
43
|
+
// Default: readFileSync returns fresh bundled registry
|
|
44
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(BUNDLED_REGISTRY));
|
|
45
|
+
});
|
|
46
|
+
describe('loadMethodRegistry()', () => {
|
|
47
|
+
describe("source: 'bundled'", () => {
|
|
48
|
+
it("returns source: 'bundled' and the registry when method_source is 'bundled'", async () => {
|
|
49
|
+
const chainFetch = vi.fn();
|
|
50
|
+
const result = await loadMethodRegistry('bundled', chainFetch);
|
|
51
|
+
expect(result.source).toBe('bundled');
|
|
52
|
+
expect(result.registry).toEqual(BUNDLED_REGISTRY);
|
|
53
|
+
expect(chainFetch).not.toHaveBeenCalled();
|
|
54
|
+
});
|
|
55
|
+
it("does not call chainFetch when method_source is 'bundled'", async () => {
|
|
56
|
+
const chainFetch = vi.fn().mockResolvedValue(CHAIN_REGISTRY);
|
|
57
|
+
const result = await loadMethodRegistry('bundled', chainFetch);
|
|
58
|
+
expect(chainFetch).not.toHaveBeenCalled();
|
|
59
|
+
expect(result.source).toBe('bundled');
|
|
60
|
+
});
|
|
61
|
+
it('returns stale: false when bundled registry generatedAt is recent', async () => {
|
|
62
|
+
const result = await loadMethodRegistry('bundled', vi.fn());
|
|
63
|
+
expect(result.stale).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
it('returns stale: true when bundled registry generatedAt is older than 7 days', async () => {
|
|
66
|
+
mockReadFileSync.mockReturnValue(JSON.stringify(STALE_BUNDLED_REGISTRY));
|
|
67
|
+
const result = await loadMethodRegistry('bundled', vi.fn());
|
|
68
|
+
expect(result.stale).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe("source: 'chain'", () => {
|
|
72
|
+
it("returns source: 'chain' when chainFetch resolves with valid registry shape", async () => {
|
|
73
|
+
const chainFetch = vi.fn().mockResolvedValue(CHAIN_REGISTRY);
|
|
74
|
+
const result = await loadMethodRegistry('chain', chainFetch);
|
|
75
|
+
expect(result.source).toBe('chain');
|
|
76
|
+
expect(result.registry).toEqual(CHAIN_REGISTRY);
|
|
77
|
+
expect(result.stale).toBe(false);
|
|
78
|
+
});
|
|
79
|
+
it("calls chainFetch with 'chain.getMethodRegistry' and empty args", async () => {
|
|
80
|
+
const chainFetch = vi.fn().mockResolvedValue(CHAIN_REGISTRY);
|
|
81
|
+
await loadMethodRegistry('chain', chainFetch);
|
|
82
|
+
expect(chainFetch).toHaveBeenCalledWith('chain.getMethodRegistry', {});
|
|
83
|
+
});
|
|
84
|
+
it("falls back to 'bundled' when chainFetch rejects", async () => {
|
|
85
|
+
const chainFetch = vi.fn().mockRejectedValue(new Error('Chain unreachable'));
|
|
86
|
+
const result = await loadMethodRegistry('chain', chainFetch);
|
|
87
|
+
expect(result.source).toBe('bundled');
|
|
88
|
+
expect(result.registry).toEqual(BUNDLED_REGISTRY);
|
|
89
|
+
});
|
|
90
|
+
it("falls back to 'bundled' when chainFetch resolves with unexpected shape (no methods field)", async () => {
|
|
91
|
+
const chainFetch = vi.fn().mockResolvedValue({ unexpected: true });
|
|
92
|
+
const result = await loadMethodRegistry('chain', chainFetch);
|
|
93
|
+
expect(result.source).toBe('bundled');
|
|
94
|
+
});
|
|
95
|
+
it("falls back to 'bundled' when chainFetch resolves with null", async () => {
|
|
96
|
+
const chainFetch = vi.fn().mockResolvedValue(null);
|
|
97
|
+
const result = await loadMethodRegistry('chain', chainFetch);
|
|
98
|
+
expect(result.source).toBe('bundled');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('method_source: undefined', () => {
|
|
102
|
+
it("falls back to 'bundled' when method_source is undefined and chainFetch rejects", async () => {
|
|
103
|
+
const chainFetch = vi.fn().mockRejectedValue(new Error('offline'));
|
|
104
|
+
const result = await loadMethodRegistry(undefined, chainFetch);
|
|
105
|
+
expect(result.source).toBe('bundled');
|
|
106
|
+
expect(result.registry).toEqual(BUNDLED_REGISTRY);
|
|
107
|
+
});
|
|
108
|
+
it("tries Chain first when method_source is undefined", async () => {
|
|
109
|
+
const chainFetch = vi.fn().mockResolvedValue(CHAIN_REGISTRY);
|
|
110
|
+
const result = await loadMethodRegistry(undefined, chainFetch);
|
|
111
|
+
expect(chainFetch).toHaveBeenCalledWith('chain.getMethodRegistry', {});
|
|
112
|
+
expect(result.source).toBe('chain');
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe('bundled JSON structure', () => {
|
|
116
|
+
it('bundled registry fixture has all required top-level fields', () => {
|
|
117
|
+
expect(BUNDLED_REGISTRY).toHaveProperty('version');
|
|
118
|
+
expect(BUNDLED_REGISTRY).toHaveProperty('generatedAt');
|
|
119
|
+
expect(BUNDLED_REGISTRY).toHaveProperty('methods');
|
|
120
|
+
expect(Array.isArray(BUNDLED_REGISTRY.methods)).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
it('each method in bundled registry has name, usage, and description', () => {
|
|
123
|
+
for (const method of BUNDLED_REGISTRY.methods) {
|
|
124
|
+
expect(method).toHaveProperty('name');
|
|
125
|
+
expect(method).toHaveProperty('usage');
|
|
126
|
+
expect(method).toHaveProperty('description');
|
|
127
|
+
expect(typeof method.name).toBe('string');
|
|
128
|
+
expect(typeof method.usage).toBe('string');
|
|
129
|
+
expect(typeof method.description).toBe('string');
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
//# sourceMappingURL=method-registry.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"method-registry.test.js","sourceRoot":"","sources":["../../src/__tests__/method-registry.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* personal-layer.test.ts — unit tests for WP-310 E2: personal workspace layer.
|
|
3
|
+
*
|
|
4
|
+
* Tests:
|
|
5
|
+
* 1. readPersonalLayer() returns [] when .productbrain/.local/rules/ is absent
|
|
6
|
+
* 2. Rules from readPersonalLayer() have persist: 'local'
|
|
7
|
+
* 3. Name collision detection fires when personal rule name matches team rule name
|
|
8
|
+
* 4. shouldEmitToTarget with persist: 'local' — local rules excluded from committed adapter targets
|
|
9
|
+
* 5. isPersistLocal helper
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=personal-layer.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personal-layer.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/personal-layer.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|