@ulysses-ai/create-workspace 0.14.0-beta.3 → 0.15.0-beta.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/package.json +1 -1
- package/template/.claude/agents/reviewer.md +1 -1
- package/template/.claude/hooks/pre-compact.mjs +1 -1
- package/template/.claude/hooks/repo-write-detection.mjs +2 -2
- package/template/.claude/hooks/session-start.mjs +10 -7
- package/template/.claude/hooks/subagent-start.mjs +3 -3
- package/template/.claude/recipes/migrate-from-notion.md +6 -6
- package/template/.claude/rules/coherent-revisions.md +2 -2
- package/template/.claude/rules/local-dev-environment.md.skip +2 -2
- package/template/.claude/rules/memory-guidance.md +20 -14
- package/template/.claude/rules/token-economics.md.skip +2 -2
- package/template/.claude/rules/work-item-tracking.md +1 -1
- package/template/.claude/rules/workspace-structure.md +36 -15
- package/template/.claude/scripts/build-workspace-context.mjs +365 -0
- package/template/.claude/scripts/build-workspace-context.test.mjs +633 -0
- package/template/.claude/scripts/capture-context.mjs +217 -0
- package/template/.claude/scripts/capture-context.test.mjs +383 -0
- package/template/.claude/scripts/generate-claude-local.mjs +104 -0
- package/template/.claude/scripts/generate-claude-local.test.mjs +184 -0
- package/template/.claude/scripts/migrate-open-work.mjs +1 -1
- package/template/.claude/scripts/migrate-to-workspace-context.mjs +520 -0
- package/template/.claude/scripts/migrate-to-workspace-context.test.mjs +325 -0
- package/template/.claude/scripts/sweep-references.mjs +177 -0
- package/template/.claude/scripts/sweep-references.test.mjs +184 -0
- package/template/.claude/skills/aside/SKILL.md +49 -44
- package/template/.claude/skills/braindump/SKILL.md +25 -19
- package/template/.claude/skills/build-docs-site/SKILL.md +1 -1
- package/template/.claude/skills/build-docs-site/checklists/framing.md +1 -1
- package/template/.claude/skills/complete-work/SKILL.md +3 -3
- package/template/.claude/skills/handoff/SKILL.md +31 -30
- package/template/.claude/skills/maintenance/SKILL.md +18 -18
- package/template/.claude/skills/pause-work/SKILL.md +1 -1
- package/template/.claude/skills/promote/SKILL.md +18 -8
- package/template/.claude/skills/release/SKILL.md +17 -13
- package/template/.claude/skills/start-work/SKILL.md +1 -1
- package/template/.claude/skills/workspace-init/SKILL.md +12 -12
- package/template/CLAUDE.md.tmpl +4 -3
- package/template/_gitignore +1 -0
- package/template/workspace.json.tmpl +2 -2
- package/template/.claude/scripts/build-shared-context-index.mjs +0 -212
- package/template/.claude/scripts/build-shared-context-index.test.mjs +0 -318
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Unit tests for generate-claude-local.mjs
|
|
3
|
+
// Run: node template/.claude/scripts/generate-claude-local.test.mjs
|
|
4
|
+
|
|
5
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync, existsSync } from 'node:fs';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import {
|
|
10
|
+
readWorkspaceUser,
|
|
11
|
+
renderClaudeLocal,
|
|
12
|
+
generateClaudeLocal,
|
|
13
|
+
} from './generate-claude-local.mjs';
|
|
14
|
+
|
|
15
|
+
let failed = 0;
|
|
16
|
+
let passed = 0;
|
|
17
|
+
|
|
18
|
+
function assert(cond, msg) {
|
|
19
|
+
if (cond) { passed++; } else { failed++; console.error(` FAIL: ${msg}`); }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function assertEq(actual, expected, msg) {
|
|
23
|
+
const a = JSON.stringify(actual);
|
|
24
|
+
const e = JSON.stringify(expected);
|
|
25
|
+
if (a === e) { passed++; } else {
|
|
26
|
+
failed++;
|
|
27
|
+
console.error(` FAIL: ${msg}\n expected: ${e}\n actual: ${a}`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function assertThrows(fn, matcher, msg) {
|
|
32
|
+
try {
|
|
33
|
+
fn();
|
|
34
|
+
failed++;
|
|
35
|
+
console.error(` FAIL: ${msg} (expected throw)`);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
if (matcher.test(e.message)) { passed++; }
|
|
38
|
+
else { failed++; console.error(` FAIL: ${msg}\n error: ${e.message}`); }
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setupRoot(settings) {
|
|
43
|
+
const root = mkdtempSync(join(tmpdir(), 'gcl-test-'));
|
|
44
|
+
mkdirSync(join(root, '.claude'), { recursive: true });
|
|
45
|
+
if (settings !== null) {
|
|
46
|
+
writeFileSync(
|
|
47
|
+
join(root, '.claude', 'settings.local.json'),
|
|
48
|
+
JSON.stringify(settings),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
return root;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function cleanup(root) {
|
|
55
|
+
rmSync(root, { recursive: true, force: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('# readWorkspaceUser');
|
|
59
|
+
|
|
60
|
+
{
|
|
61
|
+
const root = setupRoot({ workspace: { user: 'alice' } });
|
|
62
|
+
assertEq(readWorkspaceUser(root), 'alice', 'reads workspace.user');
|
|
63
|
+
cleanup(root);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
{
|
|
67
|
+
const root = mkdtempSync(join(tmpdir(), 'gcl-test-'));
|
|
68
|
+
assertThrows(
|
|
69
|
+
() => readWorkspaceUser(root),
|
|
70
|
+
/Missing.*settings\.local\.json/,
|
|
71
|
+
'missing settings file',
|
|
72
|
+
);
|
|
73
|
+
cleanup(root);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
{
|
|
77
|
+
const root = setupRoot({ workspace: {} });
|
|
78
|
+
assertThrows(
|
|
79
|
+
() => readWorkspaceUser(root),
|
|
80
|
+
/workspace\.user not set/,
|
|
81
|
+
'missing user field',
|
|
82
|
+
);
|
|
83
|
+
cleanup(root);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
{
|
|
87
|
+
const root = setupRoot({ workspace: { user: 'bad/name' } });
|
|
88
|
+
assertThrows(
|
|
89
|
+
() => readWorkspaceUser(root),
|
|
90
|
+
/must be alphanumeric/,
|
|
91
|
+
'rejects user with slash',
|
|
92
|
+
);
|
|
93
|
+
cleanup(root);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
{
|
|
97
|
+
const root = mkdtempSync(join(tmpdir(), 'gcl-test-'));
|
|
98
|
+
mkdirSync(join(root, '.claude'), { recursive: true });
|
|
99
|
+
writeFileSync(join(root, '.claude', 'settings.local.json'), 'not json');
|
|
100
|
+
assertThrows(
|
|
101
|
+
() => readWorkspaceUser(root),
|
|
102
|
+
/Could not parse/,
|
|
103
|
+
'invalid JSON',
|
|
104
|
+
);
|
|
105
|
+
cleanup(root);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('# renderClaudeLocal');
|
|
109
|
+
|
|
110
|
+
{
|
|
111
|
+
const out = renderClaudeLocal('alice');
|
|
112
|
+
assert(out.includes('## My Context'), 'has heading');
|
|
113
|
+
assert(out.includes('@workspace-context/team-member/alice/index.md'), 'has user-specific import');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log('# generateClaudeLocal');
|
|
117
|
+
|
|
118
|
+
{
|
|
119
|
+
const root = setupRoot({ workspace: { user: 'alice' } });
|
|
120
|
+
const result = generateClaudeLocal(root);
|
|
121
|
+
assertEq(result.status, 'written', 'first write reports written');
|
|
122
|
+
const content = readFileSync(result.path, 'utf-8');
|
|
123
|
+
assert(content.includes('alice/index.md'), 'file written with user import');
|
|
124
|
+
cleanup(root);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
{
|
|
128
|
+
// running twice with same content reports unchanged
|
|
129
|
+
const root = setupRoot({ workspace: { user: 'alice' } });
|
|
130
|
+
generateClaudeLocal(root);
|
|
131
|
+
const result = generateClaudeLocal(root);
|
|
132
|
+
assertEq(result.status, 'unchanged', 'idempotent on second run');
|
|
133
|
+
cleanup(root);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
{
|
|
137
|
+
// refuses to overwrite divergent content without --force
|
|
138
|
+
const root = setupRoot({ workspace: { user: 'alice' } });
|
|
139
|
+
writeFileSync(join(root, 'CLAUDE.local.md'), '## Custom\nUser had custom content here\n');
|
|
140
|
+
assertThrows(
|
|
141
|
+
() => generateClaudeLocal(root),
|
|
142
|
+
/already exists with different content/,
|
|
143
|
+
'refuses to overwrite custom content',
|
|
144
|
+
);
|
|
145
|
+
cleanup(root);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
// --force overwrites
|
|
150
|
+
const root = setupRoot({ workspace: { user: 'alice' } });
|
|
151
|
+
writeFileSync(join(root, 'CLAUDE.local.md'), '## Custom\n');
|
|
152
|
+
const result = generateClaudeLocal(root, { force: true });
|
|
153
|
+
assertEq(result.status, 'written', '--force writes');
|
|
154
|
+
const content = readFileSync(join(root, 'CLAUDE.local.md'), 'utf-8');
|
|
155
|
+
assert(content.includes('alice/index.md'), 'content overwritten');
|
|
156
|
+
cleanup(root);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
console.log('# CLI end-to-end');
|
|
160
|
+
|
|
161
|
+
{
|
|
162
|
+
const root = setupRoot({ workspace: { user: 'bob' } });
|
|
163
|
+
const scriptPath = new URL('./generate-claude-local.mjs', import.meta.url).pathname;
|
|
164
|
+
const result = spawnSync('node', [scriptPath, '--root', root], { encoding: 'utf-8' });
|
|
165
|
+
assertEq(result.status, 0, 'CLI exits 0');
|
|
166
|
+
const out = JSON.parse(result.stdout);
|
|
167
|
+
assertEq(out.status, 'written', 'CLI reports written');
|
|
168
|
+
assert(existsSync(join(root, 'CLAUDE.local.md')), 'file created');
|
|
169
|
+
cleanup(root);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
{
|
|
173
|
+
// CLI exits 1 on missing settings
|
|
174
|
+
const root = mkdtempSync(join(tmpdir(), 'gcl-test-'));
|
|
175
|
+
const scriptPath = new URL('./generate-claude-local.mjs', import.meta.url).pathname;
|
|
176
|
+
const result = spawnSync('node', [scriptPath, '--root', root], { encoding: 'utf-8' });
|
|
177
|
+
assertEq(result.status, 1, 'CLI exits 1 on missing settings');
|
|
178
|
+
assert(result.stderr.includes('generate-claude-local'), 'stderr labeled');
|
|
179
|
+
cleanup(root);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
console.log('');
|
|
183
|
+
console.log(`${passed} passed, ${failed} failed`);
|
|
184
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
@@ -67,7 +67,7 @@ for (const ticket of tickets) {
|
|
|
67
67
|
'',
|
|
68
68
|
'---',
|
|
69
69
|
'',
|
|
70
|
-
`Migrated from \`
|
|
70
|
+
`Migrated from \`workspace-context/open-work.md\` ticket #${ticket.id} (status at migration: \`${ticket.status}\`${ticket.branch ? `, branch: \`${ticket.branch}\`` : ''}).`,
|
|
71
71
|
].join('\n');
|
|
72
72
|
|
|
73
73
|
const issue = await tracker.createIssue({ title: ticket.title, body, labels, milestone: ticket.milestone });
|