joycraft 0.5.21 → 0.6.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.
@@ -0,0 +1,414 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ getPackageVersion,
4
+ hashContent,
5
+ readVersion,
6
+ writeVersion
7
+ } from "./chunk-4ZI7B4IW.js";
8
+ import {
9
+ CODEX_SKILLS,
10
+ SKILLS,
11
+ TEMPLATES
12
+ } from "./chunk-QDRX3WM6.js";
13
+
14
+ // src/upgrade.ts
15
+ import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, rmSync as rmSync2 } from "fs";
16
+ import { join as join2, dirname, resolve } from "path";
17
+ import { createInterface } from "readline";
18
+
19
+ // src/migration.ts
20
+ import {
21
+ cpSync,
22
+ existsSync,
23
+ mkdirSync,
24
+ readdirSync,
25
+ renameSync,
26
+ rmSync,
27
+ statSync
28
+ } from "fs";
29
+ import { join } from "path";
30
+ var DATE_PREFIX_RE = /^\d{4}-\d{2}-\d{2}-(.+)$/;
31
+ function deriveSlug(filename) {
32
+ return filename.replace(/\.md$/, "");
33
+ }
34
+ function slugWithoutDate(slug) {
35
+ const m = slug.match(DATE_PREFIX_RE);
36
+ return m ? m[1] : null;
37
+ }
38
+ function listMdFiles(dir) {
39
+ if (!existsSync(dir)) return [];
40
+ try {
41
+ return readdirSync(dir).filter((f) => f.endsWith(".md"));
42
+ } catch {
43
+ return [];
44
+ }
45
+ }
46
+ function listSubdirs(dir) {
47
+ if (!existsSync(dir)) return [];
48
+ try {
49
+ return readdirSync(dir).filter((name) => {
50
+ try {
51
+ return statSync(join(dir, name)).isDirectory();
52
+ } catch {
53
+ return false;
54
+ }
55
+ });
56
+ } catch {
57
+ return [];
58
+ }
59
+ }
60
+ function planMigration(projectDir) {
61
+ const briefsDir = join(projectDir, "docs", "briefs");
62
+ const researchDir = join(projectDir, "docs", "research");
63
+ const designsDir = join(projectDir, "docs", "designs");
64
+ const specsDir = join(projectDir, "docs", "specs");
65
+ const moves = [];
66
+ const skipped = [];
67
+ const slugSet = /* @__PURE__ */ new Set();
68
+ for (const file of listMdFiles(briefsDir)) {
69
+ const slug = deriveSlug(file);
70
+ slugSet.add(slug);
71
+ const move = {
72
+ from: join(briefsDir, file),
73
+ to: join(projectDir, "docs", "features", slug, "brief.md"),
74
+ kind: "brief"
75
+ };
76
+ if (existsSync(move.to)) skipped.push(move);
77
+ else moves.push(move);
78
+ }
79
+ for (const file of listMdFiles(researchDir)) {
80
+ const slug = deriveSlug(file);
81
+ slugSet.add(slug);
82
+ const move = {
83
+ from: join(researchDir, file),
84
+ to: join(projectDir, "docs", "features", slug, "research.md"),
85
+ kind: "research"
86
+ };
87
+ if (existsSync(move.to)) skipped.push(move);
88
+ else moves.push(move);
89
+ }
90
+ for (const file of listMdFiles(designsDir)) {
91
+ const slug = deriveSlug(file);
92
+ slugSet.add(slug);
93
+ const move = {
94
+ from: join(designsDir, file),
95
+ to: join(projectDir, "docs", "features", slug, "design.md"),
96
+ kind: "design"
97
+ };
98
+ if (existsSync(move.to)) skipped.push(move);
99
+ else moves.push(move);
100
+ }
101
+ const briefSlugs = Array.from(slugSet);
102
+ const orphanSpecsDirs = [];
103
+ for (const subdir of listSubdirs(specsDir)) {
104
+ let matchedSlug = null;
105
+ if (briefSlugs.includes(subdir)) {
106
+ matchedSlug = subdir;
107
+ } else {
108
+ for (const slug of briefSlugs) {
109
+ if (slugWithoutDate(slug) === subdir) {
110
+ matchedSlug = slug;
111
+ break;
112
+ }
113
+ }
114
+ }
115
+ if (matchedSlug) {
116
+ const move = {
117
+ from: join(specsDir, subdir),
118
+ to: join(projectDir, "docs", "features", matchedSlug, "specs"),
119
+ kind: "specs-dir"
120
+ };
121
+ if (existsSync(move.to)) skipped.push(move);
122
+ else moves.push(move);
123
+ } else {
124
+ orphanSpecsDirs.push(subdir);
125
+ }
126
+ }
127
+ const plan = {
128
+ moves,
129
+ slugs: Array.from(slugSet),
130
+ orphans: { specsDirs: orphanSpecsDirs }
131
+ };
132
+ if (skipped.length > 0) plan.skipped = skipped;
133
+ return plan;
134
+ }
135
+ function moveFsItem(from, to) {
136
+ mkdirSync(join(to, ".."), { recursive: true });
137
+ try {
138
+ renameSync(from, to);
139
+ } catch (err) {
140
+ const code = err.code;
141
+ if (code === "EXDEV") {
142
+ cpSync(from, to, { recursive: true });
143
+ rmSync(from, { recursive: true, force: true });
144
+ } else {
145
+ throw err;
146
+ }
147
+ }
148
+ }
149
+ function applyMigration(plan) {
150
+ let applied = 0;
151
+ let skipped = plan.skipped?.length ?? 0;
152
+ const errors = [];
153
+ for (const move of plan.moves) {
154
+ if (existsSync(move.to)) {
155
+ skipped++;
156
+ continue;
157
+ }
158
+ if (!existsSync(move.from)) {
159
+ errors.push({ move, error: `Source missing: ${move.from}` });
160
+ continue;
161
+ }
162
+ try {
163
+ moveFsItem(move.from, move.to);
164
+ applied++;
165
+ } catch (err) {
166
+ errors.push({ move, error: err.message });
167
+ }
168
+ }
169
+ return { applied, skipped, errors };
170
+ }
171
+
172
+ // src/upgrade.ts
173
+ function getManagedFiles() {
174
+ const files = {};
175
+ for (const [name, content] of Object.entries(SKILLS)) {
176
+ const skillName = name.replace(/\.md$/, "");
177
+ files[join2(".claude", "skills", skillName, "SKILL.md")] = content;
178
+ }
179
+ for (const [name, content] of Object.entries(TEMPLATES)) {
180
+ files[join2("docs", "templates", name)] = content;
181
+ }
182
+ for (const [name, content] of Object.entries(CODEX_SKILLS)) {
183
+ const skillName = name.replace(/\.md$/, "");
184
+ files[join2(".agents", "skills", skillName, "SKILL.md")] = content;
185
+ }
186
+ return files;
187
+ }
188
+ var DEPRECATED_SKILL_DIRS = [
189
+ // Pre-rebrand names
190
+ "joysmith",
191
+ // pre-rebrand main skill
192
+ "joysmith-assess",
193
+ // merged into joycraft-tune
194
+ "joysmith-upgrade",
195
+ // merged into joycraft-tune
196
+ // Pre-namespace names (bare names without joycraft- prefix)
197
+ "tune",
198
+ // now /joycraft-tune
199
+ "tune-assess",
200
+ // merged into joycraft-tune
201
+ "tune-upgrade",
202
+ // merged into joycraft-tune
203
+ "joy",
204
+ // merged into joycraft-tune
205
+ "interview",
206
+ // now /joycraft-interview
207
+ "new-feature",
208
+ // now /joycraft-new-feature
209
+ "decompose",
210
+ // now /joycraft-decompose
211
+ "session-end"
212
+ // now /joycraft-session-end
213
+ ];
214
+ var DEPRECATED_SKILL_FILES = [
215
+ "tune.md",
216
+ "joy.md",
217
+ "joysmith.md",
218
+ "joysmith-assess.md",
219
+ "joysmith-upgrade.md",
220
+ "tune-assess.md",
221
+ "tune-upgrade.md",
222
+ "interview.md",
223
+ "new-feature.md",
224
+ "decompose.md",
225
+ "session-end.md"
226
+ ];
227
+ function cleanupDeprecatedSkills(targetDir) {
228
+ const skillsDir = join2(targetDir, ".claude", "skills");
229
+ if (!existsSync2(skillsDir)) return 0;
230
+ let removed = 0;
231
+ for (const name of DEPRECATED_SKILL_DIRS) {
232
+ const dir = join2(skillsDir, name);
233
+ if (existsSync2(dir)) {
234
+ rmSync2(dir, { recursive: true, force: true });
235
+ removed++;
236
+ }
237
+ }
238
+ for (const name of DEPRECATED_SKILL_FILES) {
239
+ const file = join2(skillsDir, name);
240
+ if (existsSync2(file)) {
241
+ rmSync2(file);
242
+ removed++;
243
+ }
244
+ }
245
+ return removed;
246
+ }
247
+ function countLines(content) {
248
+ return content.split("\n").length;
249
+ }
250
+ async function askUser(question) {
251
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
252
+ return new Promise((resolve2) => {
253
+ rl.question(`${question} [y/N] `, (answer) => {
254
+ rl.close();
255
+ resolve2(answer.trim().toLowerCase() === "y");
256
+ });
257
+ });
258
+ }
259
+ function printMigrationSummary(plan, projectDir) {
260
+ console.log("");
261
+ console.log("Joycraft is migrating your docs/ to the new per-feature layout:");
262
+ console.log("");
263
+ const bySlug = /* @__PURE__ */ new Map();
264
+ for (const move of plan.moves) {
265
+ const rel = move.to.startsWith(projectDir) ? move.to.slice(projectDir.length + 1) : move.to;
266
+ const parts = rel.split(/[\\/]/);
267
+ const slug = parts.length >= 3 ? parts[2] : "(root)";
268
+ if (!bySlug.has(slug)) bySlug.set(slug, []);
269
+ bySlug.get(slug).push(move);
270
+ }
271
+ for (const [slug, moves] of bySlug) {
272
+ console.log(` ${slug}/`);
273
+ for (const move of moves) {
274
+ const fromRel = move.from.startsWith(projectDir) ? move.from.slice(projectDir.length + 1) : move.from;
275
+ const toRel = move.to.startsWith(projectDir) ? move.to.slice(projectDir.length + 1) : move.to;
276
+ console.log(` ${fromRel} \u2192 ${toRel}`);
277
+ }
278
+ }
279
+ if (plan.orphans.specsDirs.length > 0) {
280
+ console.log("");
281
+ console.log(" Left in place \u2014 area-level specs (e.g., bugfix areas):");
282
+ for (const orphan of plan.orphans.specsDirs) {
283
+ console.log(` docs/specs/${orphan}/`);
284
+ }
285
+ }
286
+ console.log("");
287
+ }
288
+ function printMigrationBanner() {
289
+ console.log("");
290
+ console.log('Migration complete. See the README section "Migration: Flat \u2192 Per-Feature Layout"');
291
+ console.log("for context on what changed and why. If your project is a git repo, run");
292
+ console.log("`git status` to inspect the moves before committing.");
293
+ console.log("");
294
+ }
295
+ function runForcedMigration(projectDir) {
296
+ const plan = planMigration(projectDir);
297
+ if (plan.moves.length === 0 && plan.orphans.specsDirs.length === 0) {
298
+ return;
299
+ }
300
+ printMigrationSummary(plan, projectDir);
301
+ const result = applyMigration(plan);
302
+ const attempted = result.applied + result.errors.length;
303
+ if (attempted > 0 && result.errors.length / attempted > 0.5) {
304
+ console.error("Migration failed for more than half of attempted moves. Aborting upgrade.");
305
+ for (const e of result.errors) {
306
+ console.error(` ${e.move.from} \u2192 ${e.move.to}: ${e.error}`);
307
+ }
308
+ throw new Error("Migration aborted: too many failures");
309
+ }
310
+ if (result.errors.length > 0) {
311
+ console.log("Some moves had errors but upgrade will continue:");
312
+ for (const e of result.errors) {
313
+ console.log(` warn: ${e.move.from} \u2192 ${e.move.to}: ${e.error}`);
314
+ }
315
+ }
316
+ printMigrationBanner();
317
+ }
318
+ async function upgrade(dir, opts) {
319
+ const targetDir = resolve(dir);
320
+ const versionInfo = readVersion(targetDir);
321
+ const hasSkill = existsSync2(join2(targetDir, ".claude", "skills", "joycraft-tune", "SKILL.md")) || existsSync2(join2(targetDir, ".claude", "skills", "tune", "SKILL.md")) || existsSync2(join2(targetDir, ".claude", "skills", "joy", "SKILL.md")) || existsSync2(join2(targetDir, ".claude", "skills", "joysmith", "SKILL.md"));
322
+ if (!versionInfo && !hasSkill) {
323
+ console.log("This project has not been initialized with Joycraft.");
324
+ console.log("Run `npx joycraft init` first.");
325
+ return;
326
+ }
327
+ const deprecatedRemoved = cleanupDeprecatedSkills(targetDir);
328
+ if (deprecatedRemoved > 0) {
329
+ console.log(`Removed ${deprecatedRemoved} deprecated skill(s) from previous Joycraft versions.`);
330
+ }
331
+ runForcedMigration(targetDir);
332
+ const pkgVersion = getPackageVersion();
333
+ const managedFiles = getManagedFiles();
334
+ const installedHashes = versionInfo?.files ?? {};
335
+ const changes = [];
336
+ let upToDate = 0;
337
+ for (const [relPath, newContent] of Object.entries(managedFiles)) {
338
+ const absPath = join2(targetDir, relPath);
339
+ const newHash = hashContent(newContent);
340
+ if (!existsSync2(absPath)) {
341
+ changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "new" });
342
+ continue;
343
+ }
344
+ const currentContent = readFileSync(absPath, "utf-8");
345
+ const currentHash = hashContent(currentContent);
346
+ if (currentHash === newHash) {
347
+ upToDate++;
348
+ continue;
349
+ }
350
+ const originalHash = installedHashes[relPath];
351
+ if (originalHash && currentHash === originalHash) {
352
+ changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "updated" });
353
+ } else {
354
+ changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: "customized" });
355
+ }
356
+ }
357
+ if (changes.length === 0) {
358
+ console.log("Already up to date.");
359
+ return;
360
+ }
361
+ let updated = 0;
362
+ let skipped = 0;
363
+ let added = 0;
364
+ for (const change of changes) {
365
+ if (change.kind === "new") {
366
+ mkdirSync2(dirname(change.absolutePath), { recursive: true });
367
+ writeFileSync(change.absolutePath, change.newContent, "utf-8");
368
+ added++;
369
+ console.log(` + ${change.relativePath}`);
370
+ } else if (change.kind === "updated") {
371
+ writeFileSync(change.absolutePath, change.newContent, "utf-8");
372
+ updated++;
373
+ } else if (change.kind === "customized") {
374
+ const currentContent = readFileSync(change.absolutePath, "utf-8");
375
+ const currentLines = countLines(currentContent);
376
+ const newLines = countLines(change.newContent);
377
+ const diff = newLines - currentLines;
378
+ const diffLabel = diff > 0 ? `+${diff} lines` : diff < 0 ? `${diff} lines` : "same length";
379
+ const label = `Customized: ${change.relativePath} (local: ${currentLines} lines, latest: ${newLines} lines, ${diffLabel})`;
380
+ if (opts.yes) {
381
+ writeFileSync(change.absolutePath, change.newContent, "utf-8");
382
+ updated++;
383
+ } else {
384
+ const accept = await askUser(`${label} \u2014 overwrite with latest?`);
385
+ if (accept) {
386
+ writeFileSync(change.absolutePath, change.newContent, "utf-8");
387
+ updated++;
388
+ } else {
389
+ skipped++;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ const newHashes = {};
395
+ for (const [relPath, content] of Object.entries(managedFiles)) {
396
+ const absPath = join2(targetDir, relPath);
397
+ if (existsSync2(absPath)) {
398
+ const current = readFileSync(absPath, "utf-8");
399
+ newHashes[relPath] = hashContent(current);
400
+ }
401
+ }
402
+ writeVersion(targetDir, pkgVersion, newHashes);
403
+ const parts = [];
404
+ if (updated > 0) parts.push(`Updated ${updated}`);
405
+ if (skipped > 0) parts.push(`skipped ${skipped} (customized)`);
406
+ if (added > 0) parts.push(`added ${added} new`);
407
+ if (upToDate > 0) parts.push(`${upToDate} already up to date`);
408
+ console.log(`
409
+ Upgrade complete: ${parts.join(", ")}.`);
410
+ }
411
+ export {
412
+ upgrade
413
+ };
414
+ //# sourceMappingURL=upgrade-ENNS6HSP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/upgrade.ts","../src/migration.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { readVersion, writeVersion, hashContent } from './version.js';\nimport { SKILLS, TEMPLATES, CODEX_SKILLS } from './bundled-files.js';\nimport { getPackageVersion } from './package-version.js';\nimport { planMigration, applyMigration, type MigrationPlan } from './migration.js';\n\nexport interface UpgradeOptions {\n yes: boolean;\n}\n\ninterface FileChange {\n relativePath: string;\n absolutePath: string;\n newContent: string;\n kind: 'new' | 'updated' | 'customized';\n}\n\nfunction getManagedFiles(): Record<string, string> {\n const files: Record<string, string> = {};\n for (const [name, content] of Object.entries(SKILLS)) {\n const skillName = name.replace(/\\.md$/, '');\n files[join('.claude', 'skills', skillName, 'SKILL.md')] = content;\n }\n for (const [name, content] of Object.entries(TEMPLATES)) {\n files[join('docs', 'templates', name)] = content;\n }\n for (const [name, content] of Object.entries(CODEX_SKILLS)) {\n const skillName = name.replace(/\\.md$/, '');\n files[join('.agents', 'skills', skillName, 'SKILL.md')] = content;\n }\n return files;\n}\n\n// Deprecated skill names from previous versions of Joycraft.\n// These get removed during upgrade to prevent stale slash commands.\nconst DEPRECATED_SKILL_DIRS = [\n // Pre-rebrand names\n 'joysmith', // pre-rebrand main skill\n 'joysmith-assess', // merged into joycraft-tune\n 'joysmith-upgrade', // merged into joycraft-tune\n // Pre-namespace names (bare names without joycraft- prefix)\n 'tune', // now /joycraft-tune\n 'tune-assess', // merged into joycraft-tune\n 'tune-upgrade', // merged into joycraft-tune\n 'joy', // merged into joycraft-tune\n 'interview', // now /joycraft-interview\n 'new-feature', // now /joycraft-new-feature\n 'decompose', // now /joycraft-decompose\n 'session-end', // now /joycraft-session-end\n];\n\n// Flat .md files from the pre-directory skill format\nconst DEPRECATED_SKILL_FILES = [\n 'tune.md',\n 'joy.md',\n 'joysmith.md',\n 'joysmith-assess.md',\n 'joysmith-upgrade.md',\n 'tune-assess.md',\n 'tune-upgrade.md',\n 'interview.md',\n 'new-feature.md',\n 'decompose.md',\n 'session-end.md',\n];\n\nfunction cleanupDeprecatedSkills(targetDir: string): number {\n const skillsDir = join(targetDir, '.claude', 'skills');\n if (!existsSync(skillsDir)) return 0;\n\n let removed = 0;\n\n // Remove deprecated directories\n for (const name of DEPRECATED_SKILL_DIRS) {\n const dir = join(skillsDir, name);\n if (existsSync(dir)) {\n rmSync(dir, { recursive: true, force: true });\n removed++;\n }\n }\n\n // Remove flat .md files from pre-directory format\n for (const name of DEPRECATED_SKILL_FILES) {\n const file = join(skillsDir, name);\n if (existsSync(file)) {\n rmSync(file);\n removed++;\n }\n }\n\n return removed;\n}\n\nfunction countLines(content: string): number {\n return content.split('\\n').length;\n}\n\nasync function askUser(question: string): Promise<boolean> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolve) => {\n rl.question(`${question} [y/N] `, (answer) => {\n rl.close();\n resolve(answer.trim().toLowerCase() === 'y');\n });\n });\n}\n\nfunction printMigrationSummary(plan: MigrationPlan, projectDir: string): void {\n console.log('');\n console.log('Joycraft is migrating your docs/ to the new per-feature layout:');\n console.log('');\n\n // Group moves by feature folder for readability\n const bySlug = new Map<string, typeof plan.moves>();\n for (const move of plan.moves) {\n const rel = move.to.startsWith(projectDir) ? move.to.slice(projectDir.length + 1) : move.to;\n const parts = rel.split(/[\\\\/]/);\n // docs/features/<slug>/...\n const slug = parts.length >= 3 ? parts[2] : '(root)';\n if (!bySlug.has(slug)) bySlug.set(slug, []);\n bySlug.get(slug)!.push(move);\n }\n for (const [slug, moves] of bySlug) {\n console.log(` ${slug}/`);\n for (const move of moves) {\n const fromRel = move.from.startsWith(projectDir) ? move.from.slice(projectDir.length + 1) : move.from;\n const toRel = move.to.startsWith(projectDir) ? move.to.slice(projectDir.length + 1) : move.to;\n console.log(` ${fromRel} → ${toRel}`);\n }\n }\n\n if (plan.orphans.specsDirs.length > 0) {\n console.log('');\n console.log(' Left in place — area-level specs (e.g., bugfix areas):');\n for (const orphan of plan.orphans.specsDirs) {\n console.log(` docs/specs/${orphan}/`);\n }\n }\n console.log('');\n}\n\nfunction printMigrationBanner(): void {\n console.log('');\n console.log('Migration complete. See the README section \"Migration: Flat → Per-Feature Layout\"');\n console.log('for context on what changed and why. If your project is a git repo, run');\n console.log('`git status` to inspect the moves before committing.');\n console.log('');\n}\n\nfunction runForcedMigration(projectDir: string): void {\n const plan = planMigration(projectDir);\n if (plan.moves.length === 0 && plan.orphans.specsDirs.length === 0) {\n return; // No flat layout — silent no-op.\n }\n\n printMigrationSummary(plan, projectDir);\n const result = applyMigration(plan);\n\n // Abort threshold: if more than 50% of attempted moves failed, bail loudly.\n const attempted = result.applied + result.errors.length;\n if (attempted > 0 && result.errors.length / attempted > 0.5) {\n console.error('Migration failed for more than half of attempted moves. Aborting upgrade.');\n for (const e of result.errors) {\n console.error(` ${e.move.from} → ${e.move.to}: ${e.error}`);\n }\n throw new Error('Migration aborted: too many failures');\n }\n\n if (result.errors.length > 0) {\n console.log('Some moves had errors but upgrade will continue:');\n for (const e of result.errors) {\n console.log(` warn: ${e.move.from} → ${e.move.to}: ${e.error}`);\n }\n }\n\n printMigrationBanner();\n}\n\nexport async function upgrade(dir: string, opts: UpgradeOptions): Promise<void> {\n const targetDir = resolve(dir);\n\n // Check if project was initialized\n const versionInfo = readVersion(targetDir);\n const hasSkill = existsSync(join(targetDir, '.claude', 'skills', 'joycraft-tune', 'SKILL.md'))\n || existsSync(join(targetDir, '.claude', 'skills', 'tune', 'SKILL.md'))\n || existsSync(join(targetDir, '.claude', 'skills', 'joy', 'SKILL.md'))\n || existsSync(join(targetDir, '.claude', 'skills', 'joysmith', 'SKILL.md'));\n\n if (!versionInfo && !hasSkill) {\n console.log('This project has not been initialized with Joycraft.');\n console.log('Run `npx joycraft init` first.');\n return;\n }\n\n // Clean up deprecated skill directories/files from older versions\n const deprecatedRemoved = cleanupDeprecatedSkills(targetDir);\n if (deprecatedRemoved > 0) {\n console.log(`Removed ${deprecatedRemoved} deprecated skill(s) from previous Joycraft versions.`);\n }\n\n // Forced migration: flat docs/{briefs,research,designs,specs/<feature>}/\n // → docs/features/<slug>/{brief,research,design,specs/}/\n // Runs before the managed-file diff loop so any new managed files end up\n // correctly placed in an already-migrated tree.\n runForcedMigration(targetDir);\n\n // Get current package version\n const pkgVersion = getPackageVersion();\n\n // If version matches exactly, check if any file content actually changed\n const managedFiles = getManagedFiles();\n const installedHashes = versionInfo?.files ?? {};\n\n const changes: FileChange[] = [];\n let upToDate = 0;\n\n for (const [relPath, newContent] of Object.entries(managedFiles)) {\n const absPath = join(targetDir, relPath);\n const newHash = hashContent(newContent);\n\n if (!existsSync(absPath)) {\n // File doesn't exist locally — new file\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'new' });\n continue;\n }\n\n const currentContent = readFileSync(absPath, 'utf-8');\n const currentHash = hashContent(currentContent);\n\n if (currentHash === newHash) {\n // Already matches the latest version\n upToDate++;\n continue;\n }\n\n const originalHash = installedHashes[relPath];\n\n if (originalHash && currentHash === originalHash) {\n // User hasn't modified the file — safe to auto-update\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'updated' });\n } else {\n // User has customized this file (or no original hash recorded)\n changes.push({ relativePath: relPath, absolutePath: absPath, newContent, kind: 'customized' });\n }\n }\n\n if (changes.length === 0) {\n console.log('Already up to date.');\n return;\n }\n\n // Process changes\n let updated = 0;\n let skipped = 0;\n let added = 0;\n\n for (const change of changes) {\n if (change.kind === 'new') {\n // New Joycraft files are always auto-added — no prompt needed\n mkdirSync(dirname(change.absolutePath), { recursive: true });\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n added++;\n console.log(` + ${change.relativePath}`);\n } else if (change.kind === 'updated') {\n // Safe to auto-update — user hasn't touched the file\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else if (change.kind === 'customized') {\n const currentContent = readFileSync(change.absolutePath, 'utf-8');\n const currentLines = countLines(currentContent);\n const newLines = countLines(change.newContent);\n const diff = newLines - currentLines;\n const diffLabel = diff > 0 ? `+${diff} lines` : diff < 0 ? `${diff} lines` : 'same length';\n const label = `Customized: ${change.relativePath} (local: ${currentLines} lines, latest: ${newLines} lines, ${diffLabel})`;\n\n if (opts.yes) {\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else {\n const accept = await askUser(`${label} — overwrite with latest?`);\n if (accept) {\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n updated++;\n } else {\n skipped++;\n }\n }\n }\n }\n\n // Write new version file with updated hashes\n const newHashes: Record<string, string> = {};\n for (const [relPath, content] of Object.entries(managedFiles)) {\n const absPath = join(targetDir, relPath);\n if (existsSync(absPath)) {\n const current = readFileSync(absPath, 'utf-8');\n newHashes[relPath] = hashContent(current);\n }\n }\n writeVersion(targetDir, pkgVersion, newHashes);\n\n // Print summary\n const parts: string[] = [];\n if (updated > 0) parts.push(`Updated ${updated}`);\n if (skipped > 0) parts.push(`skipped ${skipped} (customized)`);\n if (added > 0) parts.push(`added ${added} new`);\n if (upToDate > 0) parts.push(`${upToDate} already up to date`);\n console.log(`\\nUpgrade complete: ${parts.join(', ')}.`);\n}\n\n","// Migration module — moves flat docs/{briefs,research,designs,specs/<feature>} layouts\n// into per-feature folders at docs/features/<slug>/{brief,research,design,specs/}.\n// Plan-then-apply split so callers can render a summary before mutating the filesystem.\n\nimport {\n cpSync,\n existsSync,\n mkdirSync,\n readdirSync,\n renameSync,\n rmSync,\n statSync,\n} from 'node:fs';\nimport { join } from 'node:path';\n\nexport type MoveKind = 'brief' | 'research' | 'design' | 'specs-dir';\n\nexport interface Move {\n from: string;\n to: string;\n kind: MoveKind;\n}\n\nexport interface MigrationPlan {\n moves: Move[];\n slugs: string[];\n orphans: { specsDirs: string[] };\n skipped?: Move[];\n}\n\nexport interface MigrationResult {\n applied: number;\n skipped: number;\n errors: Array<{ move: Move; error: string }>;\n}\n\nconst DATE_PREFIX_RE = /^\\d{4}-\\d{2}-\\d{2}-(.+)$/;\n\nfunction deriveSlug(filename: string): string {\n return filename.replace(/\\.md$/, '');\n}\n\nfunction slugWithoutDate(slug: string): string | null {\n const m = slug.match(DATE_PREFIX_RE);\n return m ? m[1] : null;\n}\n\nfunction listMdFiles(dir: string): string[] {\n if (!existsSync(dir)) return [];\n try {\n return readdirSync(dir).filter(f => f.endsWith('.md'));\n } catch {\n return [];\n }\n}\n\nfunction listSubdirs(dir: string): string[] {\n if (!existsSync(dir)) return [];\n try {\n return readdirSync(dir).filter(name => {\n try {\n return statSync(join(dir, name)).isDirectory();\n } catch {\n return false;\n }\n });\n } catch {\n return [];\n }\n}\n\nexport function planMigration(projectDir: string): MigrationPlan {\n const briefsDir = join(projectDir, 'docs', 'briefs');\n const researchDir = join(projectDir, 'docs', 'research');\n const designsDir = join(projectDir, 'docs', 'designs');\n const specsDir = join(projectDir, 'docs', 'specs');\n\n const moves: Move[] = [];\n const skipped: Move[] = [];\n const slugSet = new Set<string>();\n\n // Briefs\n for (const file of listMdFiles(briefsDir)) {\n const slug = deriveSlug(file);\n slugSet.add(slug);\n const move: Move = {\n from: join(briefsDir, file),\n to: join(projectDir, 'docs', 'features', slug, 'brief.md'),\n kind: 'brief',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\n }\n\n // Research\n for (const file of listMdFiles(researchDir)) {\n const slug = deriveSlug(file);\n slugSet.add(slug);\n const move: Move = {\n from: join(researchDir, file),\n to: join(projectDir, 'docs', 'features', slug, 'research.md'),\n kind: 'research',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\n }\n\n // Designs\n for (const file of listMdFiles(designsDir)) {\n const slug = deriveSlug(file);\n slugSet.add(slug);\n const move: Move = {\n from: join(designsDir, file),\n to: join(projectDir, 'docs', 'features', slug, 'design.md'),\n kind: 'design',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\n }\n\n // Spec dirs — match by exact slug or date-stripped slug\n const briefSlugs = Array.from(slugSet);\n const orphanSpecsDirs: string[] = [];\n\n for (const subdir of listSubdirs(specsDir)) {\n let matchedSlug: string | null = null;\n\n if (briefSlugs.includes(subdir)) {\n matchedSlug = subdir;\n } else {\n // Look for a brief slug whose date-stripped form matches this subdir\n for (const slug of briefSlugs) {\n if (slugWithoutDate(slug) === subdir) {\n matchedSlug = slug;\n break;\n }\n }\n }\n\n if (matchedSlug) {\n const move: Move = {\n from: join(specsDir, subdir),\n to: join(projectDir, 'docs', 'features', matchedSlug, 'specs'),\n kind: 'specs-dir',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\n } else {\n orphanSpecsDirs.push(subdir);\n }\n }\n\n const plan: MigrationPlan = {\n moves,\n slugs: Array.from(slugSet),\n orphans: { specsDirs: orphanSpecsDirs },\n };\n if (skipped.length > 0) plan.skipped = skipped;\n return plan;\n}\n\nfunction moveFsItem(from: string, to: string): void {\n mkdirSync(join(to, '..'), { recursive: true });\n try {\n renameSync(from, to);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'EXDEV') {\n cpSync(from, to, { recursive: true });\n rmSync(from, { recursive: true, force: true });\n } else {\n throw err;\n }\n }\n}\n\nexport function applyMigration(plan: MigrationPlan): MigrationResult {\n let applied = 0;\n let skipped = (plan.skipped?.length) ?? 0;\n const errors: Array<{ move: Move; error: string }> = [];\n\n for (const move of plan.moves) {\n if (existsSync(move.to)) {\n skipped++;\n continue;\n }\n if (!existsSync(move.from)) {\n errors.push({ move, error: `Source missing: ${move.from}` });\n continue;\n }\n try {\n moveFsItem(move.from, move.to);\n applied++;\n } catch (err) {\n errors.push({ move, error: (err as Error).message });\n }\n }\n\n return { applied, skipped, errors };\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","rmSync","join","join","existsSync","rmSync","resolve","mkdirSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joycraft",
3
- "version": "0.5.21",
3
+ "version": "0.6.0",
4
4
  "description": "CLI + Claude Code plugin that scaffolds and upgrades AI development harnesses",
5
5
  "type": "module",
6
6
  "bin": {