@productbrain/cli 0.1.0-beta.85 → 0.1.0-beta.86
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/handshake.d.ts.map +1 -1
- package/dist/commands/handshake.js +85 -30
- package/dist/commands/handshake.js.map +1 -1
- package/dist/formatters/handshake.d.ts +12 -0
- package/dist/formatters/handshake.d.ts.map +1 -1
- package/dist/formatters/handshake.js +37 -0
- 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/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,304 @@
|
|
|
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
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { join } from 'path';
|
|
13
|
+
// vi.mock calls are hoisted — use vi.hoisted() for constants referenced inside factories.
|
|
14
|
+
const { vfs, MOCK_PB_DIR } = vi.hoisted(() => ({
|
|
15
|
+
vfs: {},
|
|
16
|
+
MOCK_PB_DIR: '/tmp/pb-test/.productbrain',
|
|
17
|
+
}));
|
|
18
|
+
vi.mock('fs', () => ({
|
|
19
|
+
existsSync: vi.fn((path) => path in vfs || Object.keys(vfs).some((k) => k.startsWith(path + '/'))),
|
|
20
|
+
readFileSync: vi.fn((path, _enc) => {
|
|
21
|
+
if (path in vfs)
|
|
22
|
+
return vfs[path];
|
|
23
|
+
throw Object.assign(new Error(`ENOENT: no such file '${path}'`), { code: 'ENOENT' });
|
|
24
|
+
}),
|
|
25
|
+
readdirSync: vi.fn((path) => {
|
|
26
|
+
const prefix = path.endsWith('/') ? path : path + '/';
|
|
27
|
+
return Object.keys(vfs)
|
|
28
|
+
.filter((k) => k.startsWith(prefix) && !k.slice(prefix.length).includes('/'))
|
|
29
|
+
.map((k) => k.slice(prefix.length));
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
import { readPersonalLayer, readPersonalSkillsLayer, shouldEmitToTarget, isPersistLocal } from '../generators/portable-knowledge.js';
|
|
33
|
+
const LOCAL_RULES_DIR = join(MOCK_PB_DIR, '.local', 'rules');
|
|
34
|
+
function makeRulePath(name) {
|
|
35
|
+
return join(LOCAL_RULES_DIR, `${name}.md`);
|
|
36
|
+
}
|
|
37
|
+
const MINIMAL_RULE_CONTENT = `---
|
|
38
|
+
name: my-rule
|
|
39
|
+
description: A test rule
|
|
40
|
+
autoApply: false
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
Rule body content.
|
|
44
|
+
`;
|
|
45
|
+
const RULE_WITH_FRONTMATTER = `---
|
|
46
|
+
name: personal-git-rule
|
|
47
|
+
description: Personal git workflow preferences
|
|
48
|
+
autoApply: true
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
Always sign commits.
|
|
52
|
+
`;
|
|
53
|
+
describe('readPersonalLayer()', () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
Object.keys(vfs).forEach((k) => delete vfs[k]);
|
|
56
|
+
vi.clearAllMocks();
|
|
57
|
+
});
|
|
58
|
+
it('returns [] when .productbrain/.local/rules/ directory is absent', () => {
|
|
59
|
+
// vfs is empty — no local rules directory
|
|
60
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
61
|
+
expect(result).toEqual([]);
|
|
62
|
+
});
|
|
63
|
+
it('returns [] when local rules directory exists but has no .md files', () => {
|
|
64
|
+
// Add a non-md file to make the directory "exist" in vfs
|
|
65
|
+
vfs[join(LOCAL_RULES_DIR, 'not-a-rule.txt')] = 'ignored';
|
|
66
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
67
|
+
expect(result).toEqual([]);
|
|
68
|
+
});
|
|
69
|
+
it('returns rules with persist: "local" from .productbrain/.local/rules/', () => {
|
|
70
|
+
vfs[makeRulePath('my-rule')] = MINIMAL_RULE_CONTENT;
|
|
71
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
72
|
+
expect(result).toHaveLength(1);
|
|
73
|
+
expect(result[0].persist).toBe('local');
|
|
74
|
+
});
|
|
75
|
+
it('sets persist: "local" on every returned rule regardless of file content', () => {
|
|
76
|
+
vfs[makeRulePath('rule-a')] = RULE_WITH_FRONTMATTER;
|
|
77
|
+
vfs[makeRulePath('rule-b')] = MINIMAL_RULE_CONTENT;
|
|
78
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
79
|
+
expect(result).toHaveLength(2);
|
|
80
|
+
for (const rule of result) {
|
|
81
|
+
expect(rule.persist).toBe('local');
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
it('parses frontmatter fields correctly from personal rules', () => {
|
|
85
|
+
vfs[makeRulePath('personal-git-rule')] = RULE_WITH_FRONTMATTER;
|
|
86
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
87
|
+
expect(result).toHaveLength(1);
|
|
88
|
+
const rule = result[0];
|
|
89
|
+
expect(rule.name).toBe('personal-git-rule');
|
|
90
|
+
expect(rule.description).toBe('Personal git workflow preferences');
|
|
91
|
+
expect(rule.autoApply).toBe(true);
|
|
92
|
+
expect(rule.body.trim()).toBe('Always sign commits.');
|
|
93
|
+
expect(rule.persist).toBe('local');
|
|
94
|
+
});
|
|
95
|
+
it('returns rules sorted by name', () => {
|
|
96
|
+
vfs[makeRulePath('zzz-rule')] = `---\nname: zzz-rule\ndescription: Z\nautoApply: false\n---\nbody`;
|
|
97
|
+
vfs[makeRulePath('aaa-rule')] = `---\nname: aaa-rule\ndescription: A\nautoApply: false\n---\nbody`;
|
|
98
|
+
const result = readPersonalLayer(MOCK_PB_DIR);
|
|
99
|
+
expect(result[0].name).toBe('aaa-rule');
|
|
100
|
+
expect(result[1].name).toBe('zzz-rule');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('name collision detection (personal overrides team)', () => {
|
|
104
|
+
it('personal rule name collides with team rule name — personal rule is present in merged array', () => {
|
|
105
|
+
// Simulate the merge logic from handshake.ts
|
|
106
|
+
const teamRules = [
|
|
107
|
+
{ name: 'shared-rule', description: 'Team version', autoApply: false, body: 'team body', sourcePath: '/team/shared-rule.md' },
|
|
108
|
+
];
|
|
109
|
+
const personalRules = [
|
|
110
|
+
{ name: 'shared-rule', description: 'Personal version', autoApply: true, body: 'personal body', sourcePath: '/local/shared-rule.md', persist: 'local' },
|
|
111
|
+
];
|
|
112
|
+
// Detect collision (same logic as handshake.ts)
|
|
113
|
+
const teamRuleNames = new Set(teamRules.map((r) => r.name));
|
|
114
|
+
const collisions = [];
|
|
115
|
+
for (const pr of personalRules) {
|
|
116
|
+
if (teamRuleNames.has(pr.name)) {
|
|
117
|
+
collisions.push(pr.name);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Collision was detected
|
|
121
|
+
expect(collisions).toContain('shared-rule');
|
|
122
|
+
// Merged array contains both (personal appended after team)
|
|
123
|
+
const merged = [...teamRules, ...personalRules];
|
|
124
|
+
expect(merged).toHaveLength(2);
|
|
125
|
+
const personalEntry = merged.find((r) => r.persist === 'local');
|
|
126
|
+
expect(personalEntry).toBeDefined();
|
|
127
|
+
expect(personalEntry?.name).toBe('shared-rule');
|
|
128
|
+
});
|
|
129
|
+
it('no collision when personal rule has a unique name', () => {
|
|
130
|
+
const teamRules = [
|
|
131
|
+
{ name: 'team-only-rule', description: 'Team', autoApply: false, body: 'body', sourcePath: '/team/rule.md' },
|
|
132
|
+
];
|
|
133
|
+
const personalRules = [
|
|
134
|
+
{ name: 'personal-only-rule', description: 'Personal', autoApply: false, body: 'body', sourcePath: '/local/rule.md', persist: 'local' },
|
|
135
|
+
];
|
|
136
|
+
const teamRuleNames = new Set(teamRules.map((r) => r.name));
|
|
137
|
+
const collisions = [];
|
|
138
|
+
for (const pr of personalRules) {
|
|
139
|
+
if (teamRuleNames.has(pr.name)) {
|
|
140
|
+
collisions.push(pr.name);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
expect(collisions).toHaveLength(0);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
describe('shouldEmitToTarget() with persist: "local"', () => {
|
|
147
|
+
const localRule = {
|
|
148
|
+
name: 'local-rule',
|
|
149
|
+
description: 'A local-only rule',
|
|
150
|
+
autoApply: false,
|
|
151
|
+
body: 'body',
|
|
152
|
+
sourcePath: '/local/rule.md',
|
|
153
|
+
persist: 'local',
|
|
154
|
+
};
|
|
155
|
+
const teamRule = {
|
|
156
|
+
name: 'team-rule',
|
|
157
|
+
description: 'A team rule',
|
|
158
|
+
autoApply: false,
|
|
159
|
+
body: 'body',
|
|
160
|
+
sourcePath: '/team/rule.md',
|
|
161
|
+
};
|
|
162
|
+
it('local rule: emits to "claude" target (local surface file)', () => {
|
|
163
|
+
expect(shouldEmitToTarget(localRule, 'claude')).toBe(true);
|
|
164
|
+
});
|
|
165
|
+
it('local rule: emits to "cursor" target (local surface file)', () => {
|
|
166
|
+
expect(shouldEmitToTarget(localRule, 'cursor')).toBe(true);
|
|
167
|
+
});
|
|
168
|
+
it('local rule: does NOT emit to "copilot" target (committed adapter)', () => {
|
|
169
|
+
expect(shouldEmitToTarget(localRule, 'copilot')).toBe(false);
|
|
170
|
+
});
|
|
171
|
+
it('local rule: does NOT emit to "codex" target (committed adapter)', () => {
|
|
172
|
+
expect(shouldEmitToTarget(localRule, 'codex')).toBe(false);
|
|
173
|
+
});
|
|
174
|
+
it('team rule (no persist): emits to all targets normally', () => {
|
|
175
|
+
expect(shouldEmitToTarget(teamRule, 'claude')).toBe(true);
|
|
176
|
+
expect(shouldEmitToTarget(teamRule, 'cursor')).toBe(true);
|
|
177
|
+
expect(shouldEmitToTarget(teamRule, 'copilot')).toBe(true);
|
|
178
|
+
expect(shouldEmitToTarget(teamRule, 'codex')).toBe(true);
|
|
179
|
+
});
|
|
180
|
+
it('local rule with explicit targets still blocked from committed adapter targets', () => {
|
|
181
|
+
const localRuleWithTargets = {
|
|
182
|
+
...localRule,
|
|
183
|
+
targets: ['claude', 'cursor', 'copilot'], // explicitly includes copilot
|
|
184
|
+
};
|
|
185
|
+
// persist: 'local' wins — copilot is committed adapter, must be blocked
|
|
186
|
+
expect(shouldEmitToTarget(localRuleWithTargets, 'copilot')).toBe(false);
|
|
187
|
+
expect(shouldEmitToTarget(localRuleWithTargets, 'claude')).toBe(true);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
describe('readPersonalSkillsLayer()', () => {
|
|
191
|
+
const LOCAL_SKILLS_DIR = join(MOCK_PB_DIR, '.local', 'skills');
|
|
192
|
+
function makeSkillPath(name) {
|
|
193
|
+
return join(LOCAL_SKILLS_DIR, `${name}.md`);
|
|
194
|
+
}
|
|
195
|
+
const MINIMAL_SKILL_CONTENT = `---
|
|
196
|
+
name: my-skill
|
|
197
|
+
description: A test skill
|
|
198
|
+
triggers:
|
|
199
|
+
- do the thing
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
Skill body content.
|
|
203
|
+
`;
|
|
204
|
+
beforeEach(() => {
|
|
205
|
+
Object.keys(vfs).forEach((k) => delete vfs[k]);
|
|
206
|
+
vi.clearAllMocks();
|
|
207
|
+
});
|
|
208
|
+
it('returns [] when .productbrain/.local/skills/ directory is absent', () => {
|
|
209
|
+
expect(readPersonalSkillsLayer(MOCK_PB_DIR)).toEqual([]);
|
|
210
|
+
});
|
|
211
|
+
it('returns [] when skills directory exists but has no .md files', () => {
|
|
212
|
+
vfs[join(LOCAL_SKILLS_DIR, 'not-a-skill.txt')] = 'ignored';
|
|
213
|
+
expect(readPersonalSkillsLayer(MOCK_PB_DIR)).toEqual([]);
|
|
214
|
+
});
|
|
215
|
+
it('returns skills with persist: "local" from .productbrain/.local/skills/', () => {
|
|
216
|
+
vfs[makeSkillPath('my-skill')] = MINIMAL_SKILL_CONTENT;
|
|
217
|
+
const result = readPersonalSkillsLayer(MOCK_PB_DIR);
|
|
218
|
+
expect(result).toHaveLength(1);
|
|
219
|
+
expect(result[0].persist).toBe('local');
|
|
220
|
+
});
|
|
221
|
+
it('parses frontmatter fields correctly from personal skills', () => {
|
|
222
|
+
vfs[makeSkillPath('my-skill')] = MINIMAL_SKILL_CONTENT;
|
|
223
|
+
const result = readPersonalSkillsLayer(MOCK_PB_DIR);
|
|
224
|
+
const skill = result[0];
|
|
225
|
+
expect(skill.name).toBe('my-skill');
|
|
226
|
+
expect(skill.description).toBe('A test skill');
|
|
227
|
+
expect(skill.triggers).toEqual(['do the thing']);
|
|
228
|
+
expect(skill.body.trim()).toBe('Skill body content.');
|
|
229
|
+
expect(skill.persist).toBe('local');
|
|
230
|
+
});
|
|
231
|
+
it('sets persist: "local" on every returned skill regardless of content', () => {
|
|
232
|
+
vfs[makeSkillPath('skill-a')] = MINIMAL_SKILL_CONTENT;
|
|
233
|
+
vfs[makeSkillPath('skill-b')] = `---\nname: skill-b\ndescription: B\n---\nbody`;
|
|
234
|
+
const result = readPersonalSkillsLayer(MOCK_PB_DIR);
|
|
235
|
+
expect(result).toHaveLength(2);
|
|
236
|
+
for (const s of result) {
|
|
237
|
+
expect(s.persist).toBe('local');
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
it('returns skills sorted by name', () => {
|
|
241
|
+
vfs[makeSkillPath('zzz-skill')] = `---\nname: zzz-skill\ndescription: Z\n---\nbody`;
|
|
242
|
+
vfs[makeSkillPath('aaa-skill')] = `---\nname: aaa-skill\ndescription: A\n---\nbody`;
|
|
243
|
+
const result = readPersonalSkillsLayer(MOCK_PB_DIR);
|
|
244
|
+
expect(result[0].name).toBe('aaa-skill');
|
|
245
|
+
expect(result[1].name).toBe('zzz-skill');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
describe('shouldEmitToTarget() with persist: "local" on CanonicalSkill', () => {
|
|
249
|
+
const localSkill = {
|
|
250
|
+
name: 'local-skill',
|
|
251
|
+
description: 'A local-only skill',
|
|
252
|
+
triggers: ['run it'],
|
|
253
|
+
body: 'body',
|
|
254
|
+
sourcePath: '/local/skill.md',
|
|
255
|
+
persist: 'local',
|
|
256
|
+
};
|
|
257
|
+
it('local skill: emits to "claude" target', () => {
|
|
258
|
+
expect(shouldEmitToTarget(localSkill, 'claude')).toBe(true);
|
|
259
|
+
});
|
|
260
|
+
it('local skill: emits to "cursor" target', () => {
|
|
261
|
+
expect(shouldEmitToTarget(localSkill, 'cursor')).toBe(true);
|
|
262
|
+
});
|
|
263
|
+
it('local skill: does NOT emit to "copilot" target (committed adapter)', () => {
|
|
264
|
+
expect(shouldEmitToTarget(localSkill, 'copilot')).toBe(false);
|
|
265
|
+
});
|
|
266
|
+
it('local skill: does NOT emit to "codex" target (committed adapter)', () => {
|
|
267
|
+
expect(shouldEmitToTarget(localSkill, 'codex')).toBe(false);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
270
|
+
describe('isPersistLocal()', () => {
|
|
271
|
+
it('returns true for a rule with persist: "local"', () => {
|
|
272
|
+
const rule = {
|
|
273
|
+
name: 'test',
|
|
274
|
+
description: '',
|
|
275
|
+
autoApply: false,
|
|
276
|
+
body: '',
|
|
277
|
+
sourcePath: '',
|
|
278
|
+
persist: 'local',
|
|
279
|
+
};
|
|
280
|
+
expect(isPersistLocal(rule)).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
it('returns false for a rule with persist: "team"', () => {
|
|
283
|
+
const rule = {
|
|
284
|
+
name: 'test',
|
|
285
|
+
description: '',
|
|
286
|
+
autoApply: false,
|
|
287
|
+
body: '',
|
|
288
|
+
sourcePath: '',
|
|
289
|
+
persist: 'team',
|
|
290
|
+
};
|
|
291
|
+
expect(isPersistLocal(rule)).toBe(false);
|
|
292
|
+
});
|
|
293
|
+
it('returns false for a rule with no persist field', () => {
|
|
294
|
+
const rule = {
|
|
295
|
+
name: 'test',
|
|
296
|
+
description: '',
|
|
297
|
+
autoApply: false,
|
|
298
|
+
body: '',
|
|
299
|
+
sourcePath: '',
|
|
300
|
+
};
|
|
301
|
+
expect(isPersistLocal(rule)).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
304
|
+
//# sourceMappingURL=personal-layer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"personal-layer.test.js","sourceRoot":"","sources":["../../src/__tests__/personal-layer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surfaces.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/surfaces.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Surface Capability Registry — unit tests.
|
|
3
|
+
* WP-310 S0 (FEAT-959): typed SURFACE_REGISTRY foundation.
|
|
4
|
+
* INS-740: Cursor and Copilot must have empty hookEvents to prevent double-fire.
|
|
5
|
+
*/
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
import { SURFACE_REGISTRY } from '../surfaces/registry.js';
|
|
8
|
+
describe('SURFACE_REGISTRY', () => {
|
|
9
|
+
it('contains all four surfaces', () => {
|
|
10
|
+
const surfaces = ['claude', 'cursor', 'copilot', 'codex'];
|
|
11
|
+
for (const surface of surfaces) {
|
|
12
|
+
expect(SURFACE_REGISTRY).toHaveProperty(surface);
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
it('claude.hookFilePath is .claude/settings.json', () => {
|
|
16
|
+
expect(SURFACE_REGISTRY.claude.hookFilePath).toBe('.claude/settings.json');
|
|
17
|
+
});
|
|
18
|
+
it('claude.hookEvents has exactly 3 events (SessionStart, Stop, PreCompact)', () => {
|
|
19
|
+
expect(SURFACE_REGISTRY.claude.hookEvents).toHaveLength(3);
|
|
20
|
+
expect(SURFACE_REGISTRY.claude.hookEvents).toContain('SessionStart');
|
|
21
|
+
expect(SURFACE_REGISTRY.claude.hookEvents).toContain('Stop');
|
|
22
|
+
expect(SURFACE_REGISTRY.claude.hookEvents).toContain('PreCompact');
|
|
23
|
+
});
|
|
24
|
+
it('cursor.hookEvents is empty (INS-740 double-fire safety)', () => {
|
|
25
|
+
expect(SURFACE_REGISTRY.cursor.hookEvents).toHaveLength(0);
|
|
26
|
+
});
|
|
27
|
+
it('copilot.hookEvents is empty (INS-740 double-fire safety)', () => {
|
|
28
|
+
expect(SURFACE_REGISTRY.copilot.hookEvents).toHaveLength(0);
|
|
29
|
+
});
|
|
30
|
+
it('codex.supportsAgentsMd is true', () => {
|
|
31
|
+
expect(SURFACE_REGISTRY.codex.supportsAgentsMd).toBe(true);
|
|
32
|
+
});
|
|
33
|
+
it('claude.supportsAgentsMd is false', () => {
|
|
34
|
+
expect(SURFACE_REGISTRY.claude.supportsAgentsMd).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
it('claude.personalConfigMechanism is settings_json', () => {
|
|
37
|
+
expect(SURFACE_REGISTRY.claude.personalConfigMechanism).toBe('settings_json');
|
|
38
|
+
});
|
|
39
|
+
it('cursor.rulesFormat is mdc', () => {
|
|
40
|
+
expect(SURFACE_REGISTRY.cursor.rulesFormat).toBe('mdc');
|
|
41
|
+
});
|
|
42
|
+
it('copilot.skillsFormat is none', () => {
|
|
43
|
+
expect(SURFACE_REGISTRY.copilot.skillsFormat).toBe('none');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=surfaces.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surfaces.test.js","sourceRoot":"","sources":["../../src/__tests__/surfaces.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,QAAQ,GAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QACzE,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,CAAC,gBAAgB,CAAC,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8CAA8C,EAAE,GAAG,EAAE;QACtD,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC7E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yEAAyE,EAAE,GAAG,EAAE;QACjF,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC3D,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;QACrE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAC7D,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yDAAyD,EAAE,GAAG,EAAE;QACjE,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0DAA0D,EAAE,GAAG,EAAE;QAClE,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,CAAC,gBAAgB,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iDAAiD,EAAE,GAAG,EAAE;QACzD,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,uBAAuB,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;IAChF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,CAAC,gBAAgB,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC1D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,GAAG,EAAE;QACtC,MAAM,CAAC,gBAAgB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/commands/handshake.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"handshake.d.ts","sourceRoot":"","sources":["../../src/commands/handshake.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqLH,wBAAsB,gBAAgB,CAAC,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA2HxG;AACD,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAgB,sCAAsC,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAO9E;AAsBD,wBAAsB,YAAY,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAubhF"}
|
|
@@ -8,16 +8,19 @@ import { homedir } from 'os';
|
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
import { getConfigOrGuide } from '../lib/config.js';
|
|
10
10
|
import { select as promptSelect } from '../lib/prompts.js';
|
|
11
|
+
import { composeHooksFromIntents, getHookStatusForSurface } from '../lib/hook-intents.js';
|
|
11
12
|
import { kernelCall } from '../lib/client.js';
|
|
12
13
|
import { detectRepo, extractWorkspaceProfile } from '../lib/repo-detect.js';
|
|
13
14
|
import { generateContextMd } from '../generators/context-md.js';
|
|
14
15
|
import { generateBriefingMd } from '../generators/briefing-md.js';
|
|
15
16
|
import { MARKER, generateAgentsMd, generateClaudeMd, generateCursorMdc, generateCopilotMd } from '../generators/adapters.js';
|
|
16
|
-
import { readCanonicalSkills, readCanonicalRules, generateCursorSkill, generateCursorRule, generateCodexSkill, generateCodexSkillIndex, generateClaudeRule, generateClaudeSkillRouter, shouldEmitToTarget, filterByLevel, validateCodexSkills, evaluateConditions, STAGE_TO_MAX_LEVEL, LEVEL_ORDER, } from '../generators/portable-knowledge.js';
|
|
17
|
+
import { readCanonicalSkills, readCanonicalRules, readPersonalLayer, readPersonalSkillsLayer, generateCursorSkill, generateCursorRule, generateCodexSkill, generateCodexSkillIndex, generateClaudeRule, generateClaudeSkillRouter, shouldEmitToTarget, filterByLevel, validateCodexSkills, evaluateConditions, STAGE_TO_MAX_LEVEL, LEVEL_ORDER, } from '../generators/portable-knowledge.js';
|
|
17
18
|
import { generateChainRules } from '../generators/chain-rules.js';
|
|
18
19
|
import { saveHandshakeState, loadPreviousState, diffHandshakeState, formatDiff, buildCurrentState, } from '../generators/handshake-diff.js';
|
|
19
20
|
import { resolveSurfaceProfile } from '../generators/surface-profiles.js';
|
|
20
21
|
import { formatHandshakeReport } from '../formatters/handshake.js';
|
|
22
|
+
import { readManifest, filterByAdoptionState } from '../generators/manifest.js';
|
|
23
|
+
import { loadMethodRegistry } from '../lib/method-registry.js';
|
|
21
24
|
import { CLIError, ErrorCode } from '../lib/errors.js';
|
|
22
25
|
const LEVELS = {
|
|
23
26
|
guide: {
|
|
@@ -50,9 +53,6 @@ const LEVEL_KEYS = ['guide', 'work', 'silent', 'full-trust'];
|
|
|
50
53
|
// Hook failure contract (TEN-712): all hook commands MUST end with '2>/dev/null || true'
|
|
51
54
|
// so Claude Code always starts even if pb is unavailable. Never remove this suffix.
|
|
52
55
|
const INIT_PERMISSION = 'Bash(pb:*)';
|
|
53
|
-
const INIT_SESSION_START_CMD = 'pb session start 2>/dev/null || true';
|
|
54
|
-
const INIT_SESSION_CLOSE_CMD = 'pb session close 2>/dev/null || true';
|
|
55
|
-
const INIT_PRECOMPACT_CMD = 'pb session id > /dev/null 2>&1 && echo \'{"systemMessage": "pb session active — capture decisions/tensions before compacting: pb capture \\"DEC: ...\\""}\' || true';
|
|
56
56
|
function readSettings(filePath) {
|
|
57
57
|
if (!existsSync(filePath))
|
|
58
58
|
return {};
|
|
@@ -64,9 +64,6 @@ function readSettings(filePath) {
|
|
|
64
64
|
return {};
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
function hasCommand(groups, cmd) {
|
|
68
|
-
return (groups ?? []).some((g) => g.hooks?.some((h) => h.command === cmd));
|
|
69
|
-
}
|
|
70
67
|
// Team write: hooks + Bash(pb:*) → .claude/settings.json (safe to commit)
|
|
71
68
|
function writeTeamSettings(cwd, dryRun) {
|
|
72
69
|
const claudeDir = join(cwd, '.claude');
|
|
@@ -82,26 +79,10 @@ function writeTeamSettings(cwd, dryRun) {
|
|
|
82
79
|
}
|
|
83
80
|
settings.permissions = permissions;
|
|
84
81
|
const hooks = (settings.hooks ?? {});
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
];
|
|
90
|
-
added.push('SessionStart → pb session start');
|
|
91
|
-
}
|
|
92
|
-
if (!hasCommand(hooks.Stop, INIT_SESSION_CLOSE_CMD)) {
|
|
93
|
-
hooks.Stop = [
|
|
94
|
-
...(hooks.Stop ?? []),
|
|
95
|
-
{ hooks: [{ type: 'command', command: INIT_SESSION_CLOSE_CMD, statusMessage: 'Closing pb session...' }] },
|
|
96
|
-
];
|
|
97
|
-
added.push('Stop → pb session close');
|
|
98
|
-
}
|
|
99
|
-
if (!hasCommand(hooks.PreCompact, INIT_PRECOMPACT_CMD)) {
|
|
100
|
-
hooks.PreCompact = [
|
|
101
|
-
...(hooks.PreCompact ?? []),
|
|
102
|
-
{ hooks: [{ type: 'command', command: INIT_PRECOMPACT_CMD }] },
|
|
103
|
-
];
|
|
104
|
-
added.push('PreCompact → decision capture reminder');
|
|
82
|
+
const hookAdditions = composeHooksFromIntents(['session-start', 'session-close', 'pre-compact'], hooks);
|
|
83
|
+
for (const addition of hookAdditions) {
|
|
84
|
+
hooks[addition.event] = [...(hooks[addition.event] ?? []), addition.entry];
|
|
85
|
+
added.push(addition.label);
|
|
105
86
|
}
|
|
106
87
|
settings.hooks = hooks;
|
|
107
88
|
if (!dryRun) {
|
|
@@ -194,6 +175,31 @@ export async function runHandshakeInit(options = {}) {
|
|
|
194
175
|
if (!dryRun)
|
|
195
176
|
console.log('Reload /hooks in Claude Code (or restart) to activate.');
|
|
196
177
|
console.log('Run `pb handshake --init --level <guide|work|silent|full-trust>` to change level.');
|
|
178
|
+
// Step 2b: Report multi-surface hook opt-in status (WP-310 E3b)
|
|
179
|
+
// Reads manifest.hooks.{cursor,copilot} and prints an informational note for
|
|
180
|
+
// each opted-in surface. Silence = no manifest or no hooks flags set.
|
|
181
|
+
// DEC-536: Claude-native default is already wired above; this block only fires
|
|
182
|
+
// when the user has explicitly opted in via manifest.
|
|
183
|
+
const pbDirForManifest = join(cwd, '.productbrain');
|
|
184
|
+
const initManifest = readManifest(pbDirForManifest);
|
|
185
|
+
const multiSurfaceOptIns = [];
|
|
186
|
+
if (initManifest?.hooks?.cursor)
|
|
187
|
+
multiSurfaceOptIns.push('cursor');
|
|
188
|
+
if (initManifest?.hooks?.copilot)
|
|
189
|
+
multiSurfaceOptIns.push('copilot');
|
|
190
|
+
if (multiSurfaceOptIns.length > 0) {
|
|
191
|
+
console.log('');
|
|
192
|
+
for (const surface of multiSurfaceOptIns) {
|
|
193
|
+
const status = getHookStatusForSurface(surface);
|
|
194
|
+
if (status.writable) {
|
|
195
|
+
// Future-proofing path: surface has hook events, would write files.
|
|
196
|
+
console.log(`ℹ ${surface} hooks: opted in — hooks will be written`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(`ℹ ${surface} hooks: opted in — no auto-hooks (${surface} has no session events; run \`pb session start\` manually)`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
197
203
|
// Step 3: Scaffold starter templates if .productbrain/rules/ is empty
|
|
198
204
|
const rulesDir = join(cwd, '.productbrain', 'rules');
|
|
199
205
|
const hasExistingRules = existsSync(rulesDir) && readdirSync(rulesDir).filter((f) => f.endsWith('.md')).length > 0;
|
|
@@ -382,12 +388,54 @@ export async function runHandshake(options = {}) {
|
|
|
382
388
|
log(diffText);
|
|
383
389
|
}
|
|
384
390
|
}
|
|
385
|
-
//
|
|
391
|
+
// 5b. Read personal layer (WP-310 E2) — machine-local rules/skills from .productbrain/.local/
|
|
392
|
+
// Returns [] when the directory is absent. All returned entries have persist: 'local'.
|
|
393
|
+
const personalRules = readPersonalLayer(pbDir);
|
|
394
|
+
if (personalRules.length > 0) {
|
|
395
|
+
// Name collision detection — warn when a personal rule overrides a team rule
|
|
396
|
+
const teamRuleNames = new Set(allRules.map((r) => r.name));
|
|
397
|
+
for (const pr of personalRules) {
|
|
398
|
+
if (teamRuleNames.has(pr.name)) {
|
|
399
|
+
logErr(`Personal rule "${pr.name}" overrides team rule (local takes precedence).`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
// Merge: team rules first, personal rules appended (personal takes precedence via name collision above)
|
|
403
|
+
allRules = [...allRules, ...personalRules];
|
|
404
|
+
}
|
|
405
|
+
const personalSkills = readPersonalSkillsLayer(pbDir);
|
|
406
|
+
if (personalSkills.length > 0) {
|
|
407
|
+
const teamSkillNames = new Set(allSkills.map((s) => s.name));
|
|
408
|
+
for (const ps of personalSkills) {
|
|
409
|
+
if (teamSkillNames.has(ps.name)) {
|
|
410
|
+
logErr(`Personal skill "${ps.name}" overrides team skill (local takes precedence).`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
allSkills.push(...personalSkills);
|
|
414
|
+
}
|
|
415
|
+
// 5c. Apply manifest-based adoption filter (WP-310 E1)
|
|
416
|
+
// readManifest returns null when manifest.yaml is absent → filterByAdoptionState is a no-op.
|
|
417
|
+
const manifest = readManifest(pbDir);
|
|
418
|
+
// 5d. Load method registry (WP-310 E4) — only when manifest is present.
|
|
419
|
+
let registrySource;
|
|
420
|
+
let registryStale;
|
|
421
|
+
if (manifest) {
|
|
422
|
+
const registryResult = await loadMethodRegistry(manifest.method_source, kernelCall).catch(() => null);
|
|
423
|
+
if (registryResult) {
|
|
424
|
+
registrySource = registryResult.source;
|
|
425
|
+
registryStale = registryResult.stale;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const adoptionFilteredSkills = filterByAdoptionState(allSkills, manifest);
|
|
429
|
+
const adoptionFilteredRules = filterByAdoptionState(allRules, manifest);
|
|
430
|
+
// Compute adoption counts for report (only meaningful when manifest is present)
|
|
431
|
+
const adoptedRulesCount = manifest ? adoptionFilteredRules.length : undefined;
|
|
432
|
+
const rejectedRulesCount = manifest ? allRules.length - adoptionFilteredRules.length : undefined;
|
|
433
|
+
// Apply level filtering with stage-gating (after adoption filter, before target filtering in write loop)
|
|
386
434
|
// Stage caps the effective level: blank→beginner, seed→intermediate, grounded+→expert.
|
|
387
435
|
// If stage caps below the requested level, log it so the user knows why items were dropped.
|
|
388
436
|
const profileStage = workspaceProfile?.stage;
|
|
389
|
-
const levelFilteredSkills = filterByLevel(
|
|
390
|
-
const levelFilteredRules = filterByLevel(
|
|
437
|
+
const levelFilteredSkills = filterByLevel(adoptionFilteredSkills, level, profileStage);
|
|
438
|
+
const levelFilteredRules = filterByLevel(adoptionFilteredRules, level, profileStage);
|
|
391
439
|
// Log when stage gating changes the effective level
|
|
392
440
|
if (profileStage) {
|
|
393
441
|
const stageCap = STAGE_TO_MAX_LEVEL[profileStage];
|
|
@@ -439,6 +487,7 @@ export async function runHandshake(options = {}) {
|
|
|
439
487
|
}
|
|
440
488
|
: undefined;
|
|
441
489
|
// Collect codex-targeted skills for AGENTS.md skill directory
|
|
490
|
+
// Exclude persist: 'local' rules — committed adapter files must never include local-only rules.
|
|
442
491
|
const agentsCodexSkills = canonicalSkills
|
|
443
492
|
.filter((s) => shouldEmitToTarget(s, 'codex'))
|
|
444
493
|
.map((s) => ({
|
|
@@ -608,6 +657,12 @@ export async function runHandshake(options = {}) {
|
|
|
608
657
|
codexWarnings: codexWarnings.length > 0 ? codexWarnings : undefined,
|
|
609
658
|
chainRulesStats: chainRulesStats ?? undefined,
|
|
610
659
|
chainGaps: chainGaps.length > 0 ? chainGaps : undefined,
|
|
660
|
+
adoptedCount: adoptedRulesCount,
|
|
661
|
+
rejectedCount: rejectedRulesCount,
|
|
662
|
+
personalRuleCount: personalRules.length > 0 ? personalRules.length : undefined,
|
|
663
|
+
personalSkillCount: personalSkills.length > 0 ? personalSkills.length : undefined,
|
|
664
|
+
registrySource,
|
|
665
|
+
registryStale,
|
|
611
666
|
};
|
|
612
667
|
if (!quiet) {
|
|
613
668
|
process.stdout.write('\n');
|