joycraft 0.4.0 → 0.5.1

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.
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- SKILLS,
4
- TEMPLATES,
5
3
  hashContent,
6
4
  readVersion,
7
5
  writeVersion
8
- } from "./chunk-FNGCEYUY.js";
6
+ } from "./chunk-2S7KP7FU.js";
7
+ import {
8
+ SKILLS,
9
+ TEMPLATES
10
+ } from "./chunk-HHW4Q2UC.js";
9
11
 
10
12
  // src/upgrade.ts
11
- import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync } from "fs";
12
14
  import { join, dirname, resolve } from "path";
13
15
  import { createInterface } from "readline";
14
16
  function getManagedFiles() {
@@ -22,6 +24,48 @@ function getManagedFiles() {
22
24
  }
23
25
  return files;
24
26
  }
27
+ var DEPRECATED_SKILL_DIRS = [
28
+ "tune",
29
+ // pre-namespace (was /tune, now /joycraft-tune)
30
+ "joy",
31
+ // merged into joycraft-tune
32
+ "joysmith",
33
+ // pre-rebrand
34
+ "joysmith-assess",
35
+ // merged into joycraft-tune
36
+ "tune-assess",
37
+ // merged into joycraft-tune
38
+ "tune-upgrade"
39
+ // merged into joycraft-tune
40
+ ];
41
+ var DEPRECATED_SKILL_FILES = [
42
+ "tune.md",
43
+ "joy.md",
44
+ "joysmith.md",
45
+ "joysmith-assess.md",
46
+ "tune-assess.md",
47
+ "tune-upgrade.md"
48
+ ];
49
+ function cleanupDeprecatedSkills(targetDir) {
50
+ const skillsDir = join(targetDir, ".claude", "skills");
51
+ if (!existsSync(skillsDir)) return 0;
52
+ let removed = 0;
53
+ for (const name of DEPRECATED_SKILL_DIRS) {
54
+ const dir = join(skillsDir, name);
55
+ if (existsSync(dir)) {
56
+ rmSync(dir, { recursive: true, force: true });
57
+ removed++;
58
+ }
59
+ }
60
+ for (const name of DEPRECATED_SKILL_FILES) {
61
+ const file = join(skillsDir, name);
62
+ if (existsSync(file)) {
63
+ rmSync(file);
64
+ removed++;
65
+ }
66
+ }
67
+ return removed;
68
+ }
25
69
  function countLines(content) {
26
70
  return content.split("\n").length;
27
71
  }
@@ -43,6 +87,10 @@ async function upgrade(dir, opts) {
43
87
  console.log("Run `npx joycraft init` first.");
44
88
  return;
45
89
  }
90
+ const deprecatedRemoved = cleanupDeprecatedSkills(targetDir);
91
+ if (deprecatedRemoved > 0) {
92
+ console.log(`Removed ${deprecatedRemoved} deprecated skill(s) from previous Joycraft versions.`);
93
+ }
46
94
  const pkgVersion = getPackageVersion();
47
95
  const managedFiles = getManagedFiles();
48
96
  const installedHashes = versionInfo?.files ?? {};
@@ -138,4 +186,4 @@ function getPackageVersion() {
138
186
  export {
139
187
  upgrade
140
188
  };
141
- //# sourceMappingURL=upgrade-NOHZWQMO.js.map
189
+ //# sourceMappingURL=upgrade-QEODYEDE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/upgrade.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 } from './bundled-files.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 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 'tune', // pre-namespace (was /tune, now /joycraft-tune)\n 'joy', // merged into joycraft-tune\n 'joysmith', // pre-rebrand\n 'joysmith-assess', // merged into joycraft-tune\n 'tune-assess', // merged into joycraft-tune\n 'tune-upgrade', // merged into joycraft-tune\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 'tune-assess.md',\n 'tune-upgrade.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\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 // 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 const label = `New file: ${change.relativePath}`;\n const accept = opts.yes || await askUser(`${label} — add it?`);\n if (accept) {\n mkdirSync(dirname(change.absolutePath), { recursive: true });\n writeFileSync(change.absolutePath, change.newContent, 'utf-8');\n added++;\n if (!opts.yes) console.log(` + ${change.relativePath}`);\n } else {\n skipped++;\n }\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\nfunction getPackageVersion(): string {\n try {\n // In bundled CLI, __dirname won't help — use a hardcoded fallback\n // The version is set in package.json and read at build time\n return '0.1.0';\n } catch {\n return '0.0.0';\n }\n}\n"],"mappings":"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","names":["resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "joycraft",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "CLI + Claude Code plugin that scaffolds and upgrades AI development harnesses",
5
5
  "type": "module",
6
6
  "bin": {
@@ -24,8 +24,17 @@
24
24
  "harness",
25
25
  "scaffolding"
26
26
  ],
27
- "author": "",
27
+ "author": "Maximilian Maksutovic",
28
28
  "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/maksutovic/joycraft"
32
+ },
33
+ "homepage": "https://github.com/maksutovic/joycraft#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/maksutovic/joycraft/issues"
36
+ },
37
+ "packageManager": "pnpm@10.19.0",
29
38
  "dependencies": {
30
39
  "commander": "^13.1.0"
31
40
  },