joycraft 0.6.9 → 0.6.10
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/README.md +25 -0
- package/dist/{chunk-JVRMYMBC.js → chunk-P45XNSGK.js} +17 -1
- package/dist/chunk-P45XNSGK.js.map +1 -0
- package/dist/{chunk-63OWWRAJ.js → chunk-PUYGEBLA.js} +19 -4
- package/dist/chunk-PUYGEBLA.js.map +1 -0
- package/dist/cli.js +11 -6
- package/dist/cli.js.map +1 -1
- package/dist/{init-35PB3SOQ.js → init-PS57M5XM.js} +63 -16
- package/dist/init-PS57M5XM.js.map +1 -0
- package/dist/{init-autofix-3QKXF5UX.js → init-autofix-MQL74RF7.js} +2 -2
- package/dist/{upgrade-G7PYJXEE.js → upgrade-7E5ZGYHP.js} +10 -5
- package/dist/upgrade-7E5ZGYHP.js.map +1 -0
- package/dist/{version-HEQ42K47.js → version-TG3D7QLM.js} +6 -2
- package/package.json +1 -1
- package/dist/chunk-63OWWRAJ.js.map +0 -1
- package/dist/chunk-JVRMYMBC.js.map +0 -1
- package/dist/init-35PB3SOQ.js.map +0 -1
- package/dist/upgrade-G7PYJXEE.js.map +0 -1
- /package/dist/{init-autofix-3QKXF5UX.js.map → init-autofix-MQL74RF7.js.map} +0 -0
- /package/dist/{version-HEQ42K47.js.map → version-TG3D7QLM.js.map} +0 -0
package/README.md
CHANGED
|
@@ -78,6 +78,31 @@ Joycraft auto-detects your tech stack and creates:
|
|
|
78
78
|
- **Context documents** in `docs/context/`: production map, dangerous assumptions, decision log, institutional knowledge, and troubleshooting guide
|
|
79
79
|
- **Templates** including atomic spec, feature brief, implementation plan, boundary framework, and workflow templates for scenario generation and autofix loops
|
|
80
80
|
|
|
81
|
+
### Git tracking: shared vs private
|
|
82
|
+
|
|
83
|
+
By default Joycraft assumes you want to **commit** the harness so your whole team
|
|
84
|
+
gets the same skills and workflow. Some teams prefer to keep the harness local
|
|
85
|
+
and track only the docs. Choose a profile at init time:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npx joycraft init --gitignore=shared # default — commit .claude/, .agents/, .pi/
|
|
89
|
+
npx joycraft init --gitignore=private # gitignore them; track only CLAUDE.md, AGENTS.md, docs/
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Run interactively without the flag and `init` will ask. The choice is saved, so
|
|
93
|
+
`npx joycraft upgrade` re-applies it automatically. `.gitignore` edits are
|
|
94
|
+
append-only — Joycraft never rewrites or removes your existing lines.
|
|
95
|
+
|
|
96
|
+
| Profile | Tracked in git | Gitignored |
|
|
97
|
+
|---------|----------------|------------|
|
|
98
|
+
| `shared` (default) | `CLAUDE.md`, `AGENTS.md`, `docs/`, `.claude/skills/`, `.agents/`, `.pi/` | hidden upgrade state only |
|
|
99
|
+
| `private` | `CLAUDE.md`, `AGENTS.md`, `docs/` | `.claude/`, `.agents/`, `.pi/` |
|
|
100
|
+
|
|
101
|
+
> Switching an existing project to `private` only updates `.gitignore`. If
|
|
102
|
+
> harness files were already committed, untrack them with
|
|
103
|
+
> `git rm -r --cached .claude .agents .pi` (Joycraft prints this reminder and
|
|
104
|
+
> never runs git for you).
|
|
105
|
+
|
|
81
106
|
### Supported Stacks
|
|
82
107
|
|
|
83
108
|
Node.js (npm/pnpm/yarn/bun), Python (poetry/pip/uv), Rust, Go, Swift, and generic (Makefile/Dockerfile).
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
STATE_PATH
|
|
4
|
+
} from "./chunk-PUYGEBLA.js";
|
|
2
5
|
|
|
3
6
|
// src/gitignore.ts
|
|
4
7
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
5
8
|
import { join } from "path";
|
|
9
|
+
var PRIVATE_PROFILE_IGNORES = [".claude/", ".agents/", ".pi/"];
|
|
6
10
|
function ensureGitignoreEntry(targetDir, line) {
|
|
7
11
|
const gitignorePath = join(targetDir, ".gitignore");
|
|
8
12
|
if (!existsSync(gitignorePath)) {
|
|
@@ -16,6 +20,17 @@ function ensureGitignoreEntry(targetDir, line) {
|
|
|
16
20
|
writeFileSync(gitignorePath, current + sep + line + "\n", "utf-8");
|
|
17
21
|
return true;
|
|
18
22
|
}
|
|
23
|
+
function applyGitignoreProfile(targetDir, profile) {
|
|
24
|
+
const added = [];
|
|
25
|
+
if (profile === "private") {
|
|
26
|
+
for (const entry of PRIVATE_PROFILE_IGNORES) {
|
|
27
|
+
if (ensureGitignoreEntry(targetDir, entry)) added.push(entry);
|
|
28
|
+
}
|
|
29
|
+
return added;
|
|
30
|
+
}
|
|
31
|
+
if (ensureGitignoreEntry(targetDir, STATE_PATH)) added.push(STATE_PATH);
|
|
32
|
+
return added;
|
|
33
|
+
}
|
|
19
34
|
|
|
20
35
|
// src/package-version.ts
|
|
21
36
|
import { readFileSync as readFileSync2 } from "fs";
|
|
@@ -34,6 +49,7 @@ function getPackageVersion() {
|
|
|
34
49
|
|
|
35
50
|
export {
|
|
36
51
|
ensureGitignoreEntry,
|
|
52
|
+
applyGitignoreProfile,
|
|
37
53
|
getPackageVersion
|
|
38
54
|
};
|
|
39
|
-
//# sourceMappingURL=chunk-
|
|
55
|
+
//# sourceMappingURL=chunk-P45XNSGK.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/gitignore.ts","../src/package-version.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { STATE_PATH, type GitignoreProfile } from './version.js';\n\n/**\n * The harness directories the `private` profile gitignores. Tracking only\n * CLAUDE.md, AGENTS.md, and docs/ means everything under these three dirs\n * stays local. `.claude/` already covers the hidden state file.\n */\nexport const PRIVATE_PROFILE_IGNORES = ['.claude/', '.agents/', '.pi/'];\n\n/**\n * Append-only, create-if-absent, idempotent .gitignore writer.\n *\n * Mirrors the \"append over modify when touching user files\" principle: it never\n * rewrites, reorders, or removes existing lines — it only adds `line` if no\n * existing line matches it exactly (after trimming). Returns true if it wrote.\n *\n * Used by both `init` (to gitignore the relocated state file) and `upgrade`'s\n * legacy-migration step (so migrated projects also stop committing the state).\n */\nexport function ensureGitignoreEntry(targetDir: string, line: string): boolean {\n const gitignorePath = join(targetDir, '.gitignore');\n\n if (!existsSync(gitignorePath)) {\n writeFileSync(gitignorePath, line + '\\n', 'utf-8');\n return true;\n }\n\n const current = readFileSync(gitignorePath, 'utf-8');\n const already = current.split('\\n').some((l) => l.trim() === line.trim());\n if (already) return false;\n\n // Append on its own line, tolerating a file that may or may not end in \\n.\n const sep = current.length > 0 && !current.endsWith('\\n') ? '\\n' : '';\n writeFileSync(gitignorePath, current + sep + line + '\\n', 'utf-8');\n return true;\n}\n\n/**\n * Apply a gitignore profile's entries to the project's .gitignore.\n *\n * - `shared` — ignore only the hidden upgrade-state file (current default).\n * - `private` — ignore the whole .claude/, .agents/, .pi/ trees. Since\n * .claude/ already covers the state file, the per-line writer skips the\n * redundant state entry to avoid a dead line.\n *\n * Append-only and idempotent (each line goes through ensureGitignoreEntry), so\n * re-running init/upgrade never duplicates entries. Returns the list of lines\n * actually added this call (empty when everything was already present).\n */\nexport function applyGitignoreProfile(targetDir: string, profile: GitignoreProfile): string[] {\n const added: string[] = [];\n\n if (profile === 'private') {\n for (const entry of PRIVATE_PROFILE_IGNORES) {\n if (ensureGitignoreEntry(targetDir, entry)) added.push(entry);\n }\n return added;\n }\n\n // `shared`: only the hidden state file, matching long-standing behavior.\n if (ensureGitignoreEntry(targetDir, STATE_PATH)) added.push(STATE_PATH);\n return added;\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport function getPackageVersion(): string {\n const pkgPath = join(__dirname, '..', 'package.json');\n const raw = readFileSync(pkgPath, 'utf-8');\n const pkg = JSON.parse(raw) as { version?: unknown };\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n throw new Error(`Joycraft package.json at ${pkgPath} is missing a version field`);\n }\n return pkg.version;\n}\n"],"mappings":";;;;;;AAAA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,YAAY;AAQd,IAAM,0BAA0B,CAAC,YAAY,YAAY,MAAM;AAY/D,SAAS,qBAAqB,WAAmB,MAAuB;AAC7E,QAAM,gBAAgB,KAAK,WAAW,YAAY;AAElD,MAAI,CAAC,WAAW,aAAa,GAAG;AAC9B,kBAAc,eAAe,OAAO,MAAM,OAAO;AACjD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,aAAa,eAAe,OAAO;AACnD,QAAM,UAAU,QAAQ,MAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AACxE,MAAI,QAAS,QAAO;AAGpB,QAAM,MAAM,QAAQ,SAAS,KAAK,CAAC,QAAQ,SAAS,IAAI,IAAI,OAAO;AACnE,gBAAc,eAAe,UAAU,MAAM,OAAO,MAAM,OAAO;AACjE,SAAO;AACT;AAcO,SAAS,sBAAsB,WAAmB,SAAqC;AAC5F,QAAM,QAAkB,CAAC;AAEzB,MAAI,YAAY,WAAW;AACzB,eAAW,SAAS,yBAAyB;AAC3C,UAAI,qBAAqB,WAAW,KAAK,EAAG,OAAM,KAAK,KAAK;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAGA,MAAI,qBAAqB,WAAW,UAAU,EAAG,OAAM,KAAK,UAAU;AACtE,SAAO;AACT;;;AChEA,SAAS,gBAAAA,qBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,QAAAC,aAAY;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEjD,SAAS,oBAA4B;AAC1C,QAAM,UAAUA,MAAK,WAAW,MAAM,cAAc;AACpD,QAAM,MAAMD,cAAa,SAAS,OAAO;AACzC,QAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,4BAA4B,OAAO,6BAA6B;AAAA,EAClF;AACA,SAAO,IAAI;AACb;","names":["readFileSync","join"]}
|
|
@@ -7,6 +7,10 @@ import { createHash } from "crypto";
|
|
|
7
7
|
var STATE_PATH = join(".claude", ".joycraft", "state.json");
|
|
8
8
|
var LEGACY_VERSION_FILE = ".joycraft-version";
|
|
9
9
|
var HASH_LENGTH = 16;
|
|
10
|
+
var DEFAULT_GITIGNORE_PROFILE = "shared";
|
|
11
|
+
function parseGitignoreProfile(value) {
|
|
12
|
+
return value === "shared" || value === "private" ? value : null;
|
|
13
|
+
}
|
|
10
14
|
function hashContent(content) {
|
|
11
15
|
return createHash("sha256").update(content).digest("hex");
|
|
12
16
|
}
|
|
@@ -20,20 +24,29 @@ function readVersion(dir) {
|
|
|
20
24
|
const raw = readFileSync(filePath, "utf-8");
|
|
21
25
|
const parsed = JSON.parse(raw);
|
|
22
26
|
if (typeof parsed.version === "string" && typeof parsed.files === "object") {
|
|
23
|
-
|
|
27
|
+
const profile = parseGitignoreProfile(parsed.gitignoreProfile);
|
|
28
|
+
return {
|
|
29
|
+
version: parsed.version,
|
|
30
|
+
files: parsed.files,
|
|
31
|
+
...profile ? { gitignoreProfile: profile } : {}
|
|
32
|
+
};
|
|
24
33
|
}
|
|
25
34
|
return null;
|
|
26
35
|
} catch {
|
|
27
36
|
return null;
|
|
28
37
|
}
|
|
29
38
|
}
|
|
30
|
-
function writeVersion(dir, version, files) {
|
|
39
|
+
function writeVersion(dir, version, files, gitignoreProfile) {
|
|
31
40
|
const filePath = join(dir, STATE_PATH);
|
|
32
41
|
const truncated = {};
|
|
33
42
|
for (const [path, hash] of Object.entries(files)) {
|
|
34
43
|
truncated[path] = truncateHash(hash);
|
|
35
44
|
}
|
|
36
|
-
const data = {
|
|
45
|
+
const data = {
|
|
46
|
+
version,
|
|
47
|
+
files: truncated,
|
|
48
|
+
...gitignoreProfile ? { gitignoreProfile } : {}
|
|
49
|
+
};
|
|
37
50
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
38
51
|
writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
39
52
|
}
|
|
@@ -49,10 +62,12 @@ function getLevel(dir) {
|
|
|
49
62
|
export {
|
|
50
63
|
STATE_PATH,
|
|
51
64
|
LEGACY_VERSION_FILE,
|
|
65
|
+
DEFAULT_GITIGNORE_PROFILE,
|
|
66
|
+
parseGitignoreProfile,
|
|
52
67
|
hashContent,
|
|
53
68
|
truncateHash,
|
|
54
69
|
readVersion,
|
|
55
70
|
writeVersion,
|
|
56
71
|
getLevel
|
|
57
72
|
};
|
|
58
|
-
//# sourceMappingURL=chunk-
|
|
73
|
+
//# sourceMappingURL=chunk-PUYGEBLA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\n\n/**\n * Project-relative path to Joycraft's upgrade-state file.\n *\n * Hidden inside `.claude/` — the dir `init` always creates for every harness —\n * directly analogous to npm's own hidden lockfile at\n * `node_modules/.package-lock.json`. Never at the repo root, never committed\n * (init/upgrade gitignore it). The old root location was `.joycraft-version`\n * (see LEGACY_VERSION_FILE); `upgrade` migrates it on first run.\n */\nexport const STATE_PATH = join('.claude', '.joycraft', 'state.json');\n\n/** The pre-relocation root path. Kept only so `upgrade` can migrate it. */\nexport const LEGACY_VERSION_FILE = '.joycraft-version';\n\n/**\n * Length we truncate stored hashes to. Full SHA-256 is 64 hex chars; 16 hex\n * (64 bits) is ample to detect customization across ~100 managed files and\n * shrinks the state ~4×. Compare truncated-on-both-sides (see upgrade.ts).\n */\nconst HASH_LENGTH = 16;\n\n/**\n * How much of the Joycraft harness is tracked in git.\n * - `shared` — commit skills/agents/pi so teammates get the workflow (default).\n * - `private` — gitignore .claude/, .agents/, .pi/; track only CLAUDE.md,\n * AGENTS.md, and docs/.\n */\nexport type GitignoreProfile = 'shared' | 'private';\n\nexport const DEFAULT_GITIGNORE_PROFILE: GitignoreProfile = 'shared';\n\n/** Narrow an arbitrary value to a GitignoreProfile, or null if unrecognized. */\nexport function parseGitignoreProfile(value: unknown): GitignoreProfile | null {\n return value === 'shared' || value === 'private' ? value : null;\n}\n\nexport interface VersionInfo {\n version: string;\n files: Record<string, string>;\n /**\n * The gitignore profile chosen at init/upgrade. Absent on state written by\n * Joycraft versions before this field existed — treat absent as `shared`.\n */\n gitignoreProfile?: GitignoreProfile;\n}\n\nexport function hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\n/** Truncate a (full) content hash to the stored length. Idempotent for already-short input. */\nexport function truncateHash(hash: string): string {\n return hash.slice(0, HASH_LENGTH);\n}\n\nexport function readVersion(dir: string): VersionInfo | null {\n const filePath = join(dir, STATE_PATH);\n if (!existsSync(filePath)) return null;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (typeof parsed.version === 'string' && typeof parsed.files === 'object') {\n // Sanitize the profile: ignore unknown/legacy values rather than\n // returning them (absent or bogus → undefined, callers default to shared).\n const profile = parseGitignoreProfile(parsed.gitignoreProfile);\n return {\n version: parsed.version,\n files: parsed.files,\n ...(profile ? { gitignoreProfile: profile } : {}),\n };\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function writeVersion(\n dir: string,\n version: string,\n files: Record<string, string>,\n gitignoreProfile?: GitignoreProfile\n): void {\n const filePath = join(dir, STATE_PATH);\n // Store truncated hashes — single source of truth for the on-disk shape.\n const truncated: Record<string, string> = {};\n for (const [path, hash] of Object.entries(files)) {\n truncated[path] = truncateHash(hash);\n }\n const data: VersionInfo = {\n version,\n files: truncated,\n ...(gitignoreProfile ? { gitignoreProfile } : {}),\n };\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(data, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Detect the current Joycraft harness level for a project directory.\n * Returns 5 if Level 5 artifacts (autofix workflow + External Validation) are present, 4 otherwise.\n */\nexport function getLevel(dir: string): number {\n const hasAutofix = existsSync(join(dir, '.github', 'workflows', 'autofix.yml'));\n if (!hasAutofix) return 4;\n const claudeMdPath = join(dir, 'CLAUDE.md');\n if (!existsSync(claudeMdPath)) return 4;\n const content = readFileSync(claudeMdPath, 'utf-8');\n return content.includes('## External Validation') ? 5 : 4;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAC9B,SAAS,kBAAkB;AAWpB,IAAM,aAAa,KAAK,WAAW,aAAa,YAAY;AAG5D,IAAM,sBAAsB;AAOnC,IAAM,cAAc;AAUb,IAAM,4BAA8C;AAGpD,SAAS,sBAAsB,OAAyC;AAC7E,SAAO,UAAU,YAAY,UAAU,YAAY,QAAQ;AAC7D;AAYO,SAAS,YAAY,SAAyB;AACnD,SAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1D;AAGO,SAAS,aAAa,MAAsB;AACjD,SAAO,KAAK,MAAM,GAAG,WAAW;AAClC;AAEO,SAAS,YAAY,KAAiC;AAC3D,QAAM,WAAW,KAAK,KAAK,UAAU;AACrC,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAClC,MAAI;AACF,UAAM,MAAM,aAAa,UAAU,OAAO;AAC1C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,OAAO,YAAY,YAAY,OAAO,OAAO,UAAU,UAAU;AAG1E,YAAM,UAAU,sBAAsB,OAAO,gBAAgB;AAC7D,aAAO;AAAA,QACL,SAAS,OAAO;AAAA,QAChB,OAAO,OAAO;AAAA,QACd,GAAI,UAAU,EAAE,kBAAkB,QAAQ,IAAI,CAAC;AAAA,MACjD;AAAA,IACF;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aACd,KACA,SACA,OACA,kBACM;AACN,QAAM,WAAW,KAAK,KAAK,UAAU;AAErC,QAAM,YAAoC,CAAC;AAC3C,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,cAAU,IAAI,IAAI,aAAa,IAAI;AAAA,EACrC;AACA,QAAM,OAAoB;AAAA,IACxB;AAAA,IACA,OAAO;AAAA,IACP,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;AAAA,EACjD;AACA,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,OAAO;AACvE;AAMO,SAAS,SAAS,KAAqB;AAC5C,QAAM,aAAa,WAAW,KAAK,KAAK,WAAW,aAAa,aAAa,CAAC;AAC9E,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,eAAe,KAAK,KAAK,WAAW;AAC1C,MAAI,CAAC,WAAW,YAAY,EAAG,QAAO;AACtC,QAAM,UAAU,aAAa,cAAc,OAAO;AAClD,SAAO,QAAQ,SAAS,wBAAwB,IAAI,IAAI;AAC1D;","names":[]}
|
package/dist/cli.js
CHANGED
|
@@ -9,23 +9,28 @@ var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
var pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
10
10
|
var program = new Command();
|
|
11
11
|
program.name("joycraft").description("Scaffold and upgrade AI development harnesses").version(pkg.version, "-v, --version");
|
|
12
|
-
program.command("init").description("Scaffold the Joycraft harness into the current project").argument("[dir]", "Target directory", ".").option("--force", "Overwrite existing files").action(async (dir, opts) => {
|
|
13
|
-
const { init } = await import("./init-
|
|
14
|
-
|
|
12
|
+
program.command("init").description("Scaffold the Joycraft harness into the current project").argument("[dir]", "Target directory", ".").option("--force", "Overwrite existing files").option("--gitignore <profile>", "Gitignore profile: 'shared' (commit skills) or 'private' (gitignore .claude/.agents/.pi)").action(async (dir, opts) => {
|
|
13
|
+
const { init } = await import("./init-PS57M5XM.js");
|
|
14
|
+
try {
|
|
15
|
+
await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });
|
|
16
|
+
} catch (err) {
|
|
17
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
15
20
|
});
|
|
16
21
|
program.command("upgrade").description("Upgrade installed Joycraft templates and skills to latest").argument("[dir]", "Target directory", ".").option("--yes", "Auto-accept all updates").action(async (dir, opts) => {
|
|
17
|
-
const { upgrade } = await import("./upgrade-
|
|
22
|
+
const { upgrade } = await import("./upgrade-7E5ZGYHP.js");
|
|
18
23
|
await upgrade(dir, { yes: opts.yes ?? false });
|
|
19
24
|
});
|
|
20
25
|
program.command("init-autofix").description("Set up the Level 5 auto-fix loop with holdout scenarios").argument("[dir]", "Target directory", ".").option("--scenarios-repo <name>", "Name for scenarios repo").option("--app-id <id>", "GitHub App ID for Joycraft Autofix").option("--force", "Overwrite existing workflow files").option("--dry-run", "Show what would be created without creating it").action(async (dir, opts) => {
|
|
21
|
-
const { initAutofix } = await import("./init-autofix-
|
|
26
|
+
const { initAutofix } = await import("./init-autofix-MQL74RF7.js");
|
|
22
27
|
await initAutofix(dir, opts);
|
|
23
28
|
});
|
|
24
29
|
program.command("check-version").description("Check if a newer version of Joycraft is available").action(async () => {
|
|
25
30
|
try {
|
|
26
31
|
const { readFileSync: readFileSync2, existsSync } = await import("fs");
|
|
27
32
|
const { join: join2 } = await import("path");
|
|
28
|
-
const { STATE_PATH, LEGACY_VERSION_FILE } = await import("./version-
|
|
33
|
+
const { STATE_PATH, LEGACY_VERSION_FILE } = await import("./version-TG3D7QLM.js");
|
|
29
34
|
const statePath = existsSync(join2(process.cwd(), STATE_PATH)) ? join2(process.cwd(), STATE_PATH) : join2(process.cwd(), LEGACY_VERSION_FILE);
|
|
30
35
|
const data = JSON.parse(readFileSync2(statePath, "utf-8"));
|
|
31
36
|
const res = await fetch("https://registry.npmjs.org/joycraft/latest", { signal: AbortSignal.timeout(3e3) });
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\n\nconst program = new Command();\n\nprogram\n .name('joycraft')\n .description('Scaffold and upgrade AI development harnesses')\n .version(pkg.version, '-v, --version');\n\nprogram\n .command('init')\n .description('Scaffold the Joycraft harness into the current project')\n .argument('[dir]', 'Target directory', '.')\n .option('--force', 'Overwrite existing files')\n .action(async (dir: string, opts: { force?: boolean }) => {\n const { init } = await import('./init.js');\n await init(dir, { force: opts.force ?? false });\n });\n\nprogram\n .command('upgrade')\n .description('Upgrade installed Joycraft templates and skills to latest')\n .argument('[dir]', 'Target directory', '.')\n .option('--yes', 'Auto-accept all updates')\n .action(async (dir: string, opts: { yes?: boolean }) => {\n const { upgrade } = await import('./upgrade.js');\n await upgrade(dir, { yes: opts.yes ?? false });\n });\n\nprogram\n .command('init-autofix')\n .description('Set up the Level 5 auto-fix loop with holdout scenarios')\n .argument('[dir]', 'Target directory', '.')\n .option('--scenarios-repo <name>', 'Name for scenarios repo')\n .option('--app-id <id>', 'GitHub App ID for Joycraft Autofix')\n .option('--force', 'Overwrite existing workflow files')\n .option('--dry-run', 'Show what would be created without creating it')\n .action(async (dir: string, opts: { scenariosRepo?: string; appId?: string; force?: boolean; dryRun?: boolean }) => {\n const { initAutofix } = await import('./init-autofix.js');\n await initAutofix(dir, opts);\n });\n\nprogram\n .command('check-version')\n .description('Check if a newer version of Joycraft is available')\n .action(async () => {\n try {\n const { readFileSync, existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const { STATE_PATH, LEGACY_VERSION_FILE } = await import('./version.js');\n // Prefer the hidden state; fall back to the legacy root file for projects\n // that have not been upgraded (which relocates it) yet.\n const statePath = existsSync(join(process.cwd(), STATE_PATH))\n ? join(process.cwd(), STATE_PATH)\n : join(process.cwd(), LEGACY_VERSION_FILE);\n const data = JSON.parse(readFileSync(statePath, 'utf-8'));\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (data.version !== latest) {\n console.log(`Joycraft ${latest} available (you have ${data.version}). Run: npm install -g joycraft`);\n }\n }\n } catch {\n // Silent — don't block session start\n }\n });\n\n// Start update check immediately so it runs in parallel with the command\nconst updateCheckPromise = (async (): Promise<string | null> => {\n try {\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (latest !== pkg.version) {\n return `\\nJoycraft ${latest} available (you have ${pkg.version}). Run: npm install -g joycraft`;\n }\n }\n } catch {\n // Silent — don't block or error on network issues\n }\n return null;\n})();\n\n// Print update nudge after every command\nprogram.hook('postAction', async () => {\n const message = await updateCheckPromise;\n if (message) {\n console.log(message);\n }\n});\n\n// Show help when no arguments provided\nif (process.argv.length <= 2) {\n program.outputHelp();\n process.exit(0);\n}\n\nprogram.parse();\n"],"mappings":";;;AAAA,SAAS,eAAe;AACxB,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,YAAY;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,MAAM,KAAK,MAAM,aAAa,KAAK,WAAW,MAAM,cAAc,GAAG,OAAO,CAAC;AAEnF,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,UAAU,EACf,YAAY,+CAA+C,EAC3D,QAAQ,IAAI,SAAS,eAAe;AAEvC,QACG,QAAQ,MAAM,EACd,YAAY,wDAAwD,EACpE,SAAS,SAAS,oBAAoB,GAAG,EACzC,OAAO,WAAW,0BAA0B,EAC5C,OAAO,OAAO,KAAa,
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { Command } from 'commander';\nimport { readFileSync } from 'fs';\nimport { fileURLToPath } from 'url';\nimport { dirname, join } from 'path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\n\nconst program = new Command();\n\nprogram\n .name('joycraft')\n .description('Scaffold and upgrade AI development harnesses')\n .version(pkg.version, '-v, --version');\n\nprogram\n .command('init')\n .description('Scaffold the Joycraft harness into the current project')\n .argument('[dir]', 'Target directory', '.')\n .option('--force', 'Overwrite existing files')\n .option('--gitignore <profile>', \"Gitignore profile: 'shared' (commit skills) or 'private' (gitignore .claude/.agents/.pi)\")\n .action(async (dir: string, opts: { force?: boolean; gitignore?: string }) => {\n const { init } = await import('./init.js');\n try {\n await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n\nprogram\n .command('upgrade')\n .description('Upgrade installed Joycraft templates and skills to latest')\n .argument('[dir]', 'Target directory', '.')\n .option('--yes', 'Auto-accept all updates')\n .action(async (dir: string, opts: { yes?: boolean }) => {\n const { upgrade } = await import('./upgrade.js');\n await upgrade(dir, { yes: opts.yes ?? false });\n });\n\nprogram\n .command('init-autofix')\n .description('Set up the Level 5 auto-fix loop with holdout scenarios')\n .argument('[dir]', 'Target directory', '.')\n .option('--scenarios-repo <name>', 'Name for scenarios repo')\n .option('--app-id <id>', 'GitHub App ID for Joycraft Autofix')\n .option('--force', 'Overwrite existing workflow files')\n .option('--dry-run', 'Show what would be created without creating it')\n .action(async (dir: string, opts: { scenariosRepo?: string; appId?: string; force?: boolean; dryRun?: boolean }) => {\n const { initAutofix } = await import('./init-autofix.js');\n await initAutofix(dir, opts);\n });\n\nprogram\n .command('check-version')\n .description('Check if a newer version of Joycraft is available')\n .action(async () => {\n try {\n const { readFileSync, existsSync } = await import('node:fs');\n const { join } = await import('node:path');\n const { STATE_PATH, LEGACY_VERSION_FILE } = await import('./version.js');\n // Prefer the hidden state; fall back to the legacy root file for projects\n // that have not been upgraded (which relocates it) yet.\n const statePath = existsSync(join(process.cwd(), STATE_PATH))\n ? join(process.cwd(), STATE_PATH)\n : join(process.cwd(), LEGACY_VERSION_FILE);\n const data = JSON.parse(readFileSync(statePath, 'utf-8'));\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (data.version !== latest) {\n console.log(`Joycraft ${latest} available (you have ${data.version}). Run: npm install -g joycraft`);\n }\n }\n } catch {\n // Silent — don't block session start\n }\n });\n\n// Start update check immediately so it runs in parallel with the command\nconst updateCheckPromise = (async (): Promise<string | null> => {\n try {\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', {\n signal: AbortSignal.timeout(3000)\n });\n if (res.ok) {\n const latest = ((await res.json()) as { version: string }).version;\n if (latest !== pkg.version) {\n return `\\nJoycraft ${latest} available (you have ${pkg.version}). Run: npm install -g joycraft`;\n }\n }\n } catch {\n // Silent — don't block or error on network issues\n }\n return null;\n})();\n\n// Print update nudge after every command\nprogram.hook('postAction', async () => {\n const message = await updateCheckPromise;\n if (message) {\n console.log(message);\n }\n});\n\n// Show help when no arguments provided\nif (process.argv.length <= 2) {\n program.outputHelp();\n process.exit(0);\n}\n\nprogram.parse();\n"],"mappings":"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","names":["readFileSync","join"]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
applyGitignoreProfile,
|
|
4
4
|
getPackageVersion
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-P45XNSGK.js";
|
|
6
6
|
import {
|
|
7
7
|
CODEX_SKILLS,
|
|
8
8
|
PI_AGENTS,
|
|
@@ -13,10 +13,13 @@ import {
|
|
|
13
13
|
TEMPLATES
|
|
14
14
|
} from "./chunk-W6AHAE7X.js";
|
|
15
15
|
import {
|
|
16
|
+
DEFAULT_GITIGNORE_PROFILE,
|
|
16
17
|
STATE_PATH,
|
|
17
18
|
hashContent,
|
|
19
|
+
parseGitignoreProfile,
|
|
20
|
+
readVersion,
|
|
18
21
|
writeVersion
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-PUYGEBLA.js";
|
|
20
23
|
|
|
21
24
|
// src/init.ts
|
|
22
25
|
import { mkdirSync as mkdirSync2, existsSync as existsSync4, writeFileSync as writeFileSync2, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync, chmodSync } from "fs";
|
|
@@ -696,6 +699,37 @@ function installSafeguardHooks(targetDir, customPatterns = [], force = false, sk
|
|
|
696
699
|
}
|
|
697
700
|
|
|
698
701
|
// src/init.ts
|
|
702
|
+
import { createInterface } from "readline";
|
|
703
|
+
async function promptGitignoreProfile() {
|
|
704
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
705
|
+
console.log("\nHow should Joycraft files be tracked in git?");
|
|
706
|
+
console.log(" shared \u2014 commit skills so your team gets the same workflow (default)");
|
|
707
|
+
console.log(" private \u2014 gitignore .claude/, .agents/, .pi/; track only CLAUDE.md, AGENTS.md, docs/");
|
|
708
|
+
return new Promise((resolve2) => {
|
|
709
|
+
rl.question("Profile [shared/private] (shared): ", (answer) => {
|
|
710
|
+
rl.close();
|
|
711
|
+
const parsed = parseGitignoreProfile(answer.trim().toLowerCase());
|
|
712
|
+
resolve2(parsed ?? DEFAULT_GITIGNORE_PROFILE);
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
async function resolveGitignoreProfile(targetDir, opts) {
|
|
717
|
+
if (opts.gitignore !== void 0) {
|
|
718
|
+
const parsed = parseGitignoreProfile(opts.gitignore.trim().toLowerCase());
|
|
719
|
+
if (!parsed) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`Unknown gitignore profile '${opts.gitignore}'. Use 'shared' or 'private'.`
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
return parsed;
|
|
725
|
+
}
|
|
726
|
+
const persisted = readVersion(targetDir)?.gitignoreProfile;
|
|
727
|
+
if (persisted) return persisted;
|
|
728
|
+
if (process.stdin.isTTY) {
|
|
729
|
+
return promptGitignoreProfile();
|
|
730
|
+
}
|
|
731
|
+
return DEFAULT_GITIGNORE_PROFILE;
|
|
732
|
+
}
|
|
699
733
|
function ensureDir(dir) {
|
|
700
734
|
if (!existsSync4(dir)) {
|
|
701
735
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -714,6 +748,7 @@ async function init(dir, opts) {
|
|
|
714
748
|
const result = { created: [], skipped: [], modified: [], warnings: [] };
|
|
715
749
|
const stack = await detectStack(targetDir);
|
|
716
750
|
const isPi = existsSync4(join4(targetDir, ".pi"));
|
|
751
|
+
const gitignoreProfile = await resolveGitignoreProfile(targetDir, opts);
|
|
717
752
|
ensureDir(join4(targetDir, "docs", "context"));
|
|
718
753
|
const skillsDir = join4(targetDir, ".claude", "skills");
|
|
719
754
|
let existingSkills = [];
|
|
@@ -833,8 +868,8 @@ async function init(dir, opts) {
|
|
|
833
868
|
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
834
869
|
fileHashes[join4(".pi", "agents", name)] = hashContent(content);
|
|
835
870
|
}
|
|
836
|
-
writeVersion(targetDir, getPackageVersion(), fileHashes);
|
|
837
|
-
|
|
871
|
+
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile);
|
|
872
|
+
applyGitignoreProfile(targetDir, gitignoreProfile);
|
|
838
873
|
const hooksDir = join4(targetDir, ".claude", "hooks");
|
|
839
874
|
ensureDir(hooksDir);
|
|
840
875
|
const hookScript = `// Joycraft version check \u2014 runs on Claude Code session start
|
|
@@ -911,18 +946,20 @@ try {
|
|
|
911
946
|
const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);
|
|
912
947
|
result.created.push(...hookResult.created);
|
|
913
948
|
result.skipped.push(...hookResult.skipped);
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
949
|
+
if (gitignoreProfile === "shared") {
|
|
950
|
+
const gitignorePath = join4(targetDir, ".gitignore");
|
|
951
|
+
if (existsSync4(gitignorePath)) {
|
|
952
|
+
const gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
953
|
+
if (/^\.claude\/?$/m.test(gitignore) || /^\.claude\/\*$/m.test(gitignore)) {
|
|
954
|
+
result.warnings.push(
|
|
955
|
+
".claude/ is in your .gitignore \u2014 teammates won't get Joycraft skills.\n Add this line to .gitignore to fix: !.claude/skills/"
|
|
956
|
+
);
|
|
957
|
+
}
|
|
921
958
|
}
|
|
922
959
|
}
|
|
923
|
-
printSummary(result, stack, existingSkills, isPi);
|
|
960
|
+
printSummary(result, stack, existingSkills, isPi, gitignoreProfile);
|
|
924
961
|
}
|
|
925
|
-
function printSummary(result, stack, existingSkills = [], isPi = false) {
|
|
962
|
+
function printSummary(result, stack, existingSkills = [], isPi = false, gitignoreProfile = DEFAULT_GITIGNORE_PROFILE) {
|
|
926
963
|
console.log("\nJoycraft initialized!\n");
|
|
927
964
|
if (stack.language !== "unknown") {
|
|
928
965
|
const fw = stack.framework ? ` + ${stack.framework}` : "";
|
|
@@ -933,6 +970,11 @@ function printSummary(result, stack, existingSkills = [], isPi = false) {
|
|
|
933
970
|
if (isPi) {
|
|
934
971
|
console.log(" Detected agent: Pi");
|
|
935
972
|
}
|
|
973
|
+
if (gitignoreProfile === "private") {
|
|
974
|
+
console.log(" Gitignore profile: private (.claude/, .agents/, .pi/ are gitignored \u2014 only CLAUDE.md, AGENTS.md, docs/ are tracked)");
|
|
975
|
+
} else {
|
|
976
|
+
console.log(" Gitignore profile: shared (skills and docs are tracked for your team)");
|
|
977
|
+
}
|
|
936
978
|
if (result.created.length > 0) {
|
|
937
979
|
console.log(`
|
|
938
980
|
Created ${result.created.length} file(s):`);
|
|
@@ -974,7 +1016,12 @@ function printSummary(result, stack, existingSkills = [], isPi = false) {
|
|
|
974
1016
|
}
|
|
975
1017
|
console.log(" 2. Try /joycraft-new-feature to start building with the spec-driven workflow");
|
|
976
1018
|
console.log(" (feature artifacts are written to docs/features/<slug>/ as you go)");
|
|
977
|
-
|
|
1019
|
+
if (gitignoreProfile === "private") {
|
|
1020
|
+
console.log(" 3. Commit CLAUDE.md, AGENTS.md, and docs/ \u2014 .claude/, .agents/, .pi/ stay local (gitignored)");
|
|
1021
|
+
console.log(" (if any harness files were already committed, run: git rm -r --cached .claude .agents .pi)");
|
|
1022
|
+
} else {
|
|
1023
|
+
console.log(" 3. Commit .claude/skills/ and docs/ so your team gets the same workflow");
|
|
1024
|
+
}
|
|
978
1025
|
if (!isPi) {
|
|
979
1026
|
console.log(" Pi: Skills installed to .pi/skills/. Use /skill:joycraft-* to invoke.");
|
|
980
1027
|
}
|
|
@@ -983,4 +1030,4 @@ function printSummary(result, stack, existingSkills = [], isPi = false) {
|
|
|
983
1030
|
export {
|
|
984
1031
|
init
|
|
985
1032
|
};
|
|
986
|
-
//# sourceMappingURL=init-
|
|
1033
|
+
//# sourceMappingURL=init-PS57M5XM.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init.ts","../src/detect.ts","../src/improve-claude-md.ts","../src/agents-md.ts","../src/permissions.ts","../src/safeguard.ts"],"sourcesContent":["import { mkdirSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync, chmodSync } from 'node:fs';\nimport { join, basename, resolve, dirname } from 'node:path';\nimport { detectStack } from './detect.js';\nimport { generateCLAUDEMd } from './improve-claude-md.js';\nimport { generateAgentsMd } from './agents-md.js';\nimport { generatePermissions } from './permissions.js';\nimport { installSafeguardHooks } from './safeguard.js';\nimport { SKILLS, TEMPLATES, CODEX_SKILLS, PI_SKILLS, PI_SCRIPTS, PI_EXTENSIONS, PI_AGENTS } from './bundled-files.js';\nimport { createInterface } from 'node:readline';\nimport {\n writeVersion,\n readVersion,\n hashContent,\n STATE_PATH,\n parseGitignoreProfile,\n DEFAULT_GITIGNORE_PROFILE,\n type GitignoreProfile,\n} from './version.js';\nimport { applyGitignoreProfile } from './gitignore.js';\nimport { getPackageVersion } from './package-version.js';\n\nexport interface InitOptions {\n force: boolean;\n /** Raw --gitignore value from the CLI, if provided. Validated in init(). */\n gitignore?: string;\n}\n\n/**\n * Prompt the user to choose a gitignore profile. Only called interactively\n * (TTY, no flag, no persisted choice). Mirrors upgrade.ts's askUser readline use.\n */\nasync function promptGitignoreProfile(): Promise<GitignoreProfile> {\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n console.log('\\nHow should Joycraft files be tracked in git?');\n console.log(' shared — commit skills so your team gets the same workflow (default)');\n console.log(' private — gitignore .claude/, .agents/, .pi/; track only CLAUDE.md, AGENTS.md, docs/');\n return new Promise((resolve) => {\n rl.question('Profile [shared/private] (shared): ', (answer) => {\n rl.close();\n const parsed = parseGitignoreProfile(answer.trim().toLowerCase());\n resolve(parsed ?? DEFAULT_GITIGNORE_PROFILE);\n });\n });\n}\n\n/**\n * Resolve the gitignore profile by precedence:\n * 1. --gitignore flag (validated; throws on unknown value)\n * 2. profile persisted in state.json (re-init keeps prior choice)\n * 3. interactive prompt (TTY only)\n * 4. default `shared`\n */\nasync function resolveGitignoreProfile(\n targetDir: string,\n opts: InitOptions\n): Promise<GitignoreProfile> {\n if (opts.gitignore !== undefined) {\n const parsed = parseGitignoreProfile(opts.gitignore.trim().toLowerCase());\n if (!parsed) {\n throw new Error(\n `Unknown gitignore profile '${opts.gitignore}'. Use 'shared' or 'private'.`\n );\n }\n return parsed;\n }\n\n const persisted = readVersion(targetDir)?.gitignoreProfile;\n if (persisted) return persisted;\n\n if (process.stdin.isTTY) {\n return promptGitignoreProfile();\n }\n\n return DEFAULT_GITIGNORE_PROFILE;\n}\n\ninterface InitResult {\n created: string[];\n skipped: string[];\n modified: string[];\n warnings: string[];\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction writeFile(path: string, content: string, force: boolean, result: InitResult): void {\n if (existsSync(path) && !force) {\n result.skipped.push(path);\n return;\n }\n writeFileSync(path, content, 'utf-8');\n result.created.push(path);\n}\n\nexport async function init(dir: string, opts: InitOptions): Promise<void> {\n const targetDir = resolve(dir);\n const result: InitResult = { created: [], skipped: [], modified: [], warnings: [] };\n\n // Detect stack\n const stack = await detectStack(targetDir);\n\n // Pi detection — check if project uses Pi coding agent\n const isPi = existsSync(join(targetDir, '.pi'));\n\n // Resolve the gitignore profile up front (flag → persisted → prompt → default)\n // so it governs both the .gitignore writes and the \"teammates won't get skills\"\n // warning below.\n const gitignoreProfile = await resolveGitignoreProfile(targetDir, opts);\n\n // 1. Create the only Joycraft-managed docs/ subdirectory: context/.\n // All other folders (briefs/specs/discoveries/decisions/contracts/features/backlog/...) are\n // lazy-created by the skills that write to them. Solo-first: no preemptive ceremony.\n ensureDir(join(targetDir, 'docs', 'context'));\n\n // 1b. Scan for existing non-Joycraft skills before copying ours\n const skillsDir = join(targetDir, '.claude', 'skills');\n let existingSkills: string[] = [];\n if (existsSync(skillsDir)) {\n existingSkills = readdirSync(skillsDir)\n .filter(name => {\n if (name.startsWith('joycraft-')) return false;\n if (name.startsWith('.')) return false;\n const fullPath = join(skillsDir, name);\n try {\n return statSync(fullPath).isDirectory();\n } catch {\n return false;\n }\n });\n }\n\n // 2. Copy skill files to .claude/skills/<name>/SKILL.md\n for (const [filename, content] of Object.entries(SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(skillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2b. Copy Codex skill files to .agents/skills/<name>/SKILL.md\n const codexSkillsDir = join(targetDir, '.agents', 'skills');\n let existingCodexSkills: string[] = [];\n if (existsSync(codexSkillsDir)) {\n existingCodexSkills = readdirSync(codexSkillsDir)\n .filter(name => {\n if (name.startsWith('joycraft-')) return false;\n if (name.startsWith('.')) return false;\n const fullPath = join(codexSkillsDir, name);\n try {\n return statSync(fullPath).isDirectory();\n } catch {\n return false;\n }\n });\n }\n for (const [filename, content] of Object.entries(CODEX_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(codexSkillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2c. Copy Pi skill files to .pi/skills/<name>/SKILL.md\n const piSkillsDir = join(targetDir, '.pi', 'skills');\n for (const [filename, content] of Object.entries(PI_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(piSkillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2d. Install Pi pipeline runtime scripts to .pi/scripts/joycraft/\n const piScriptsDir = join(targetDir, '.pi', 'scripts', 'joycraft');\n ensureDir(piScriptsDir);\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n const scriptPath = join(piScriptsDir, name);\n writeFile(scriptPath, content, opts.force, result);\n if (name !== 'README.md') {\n try { chmodSync(scriptPath, 0o755); } catch { /* non-fatal */ }\n }\n }\n\n // 2e. Install Pi extension to .pi/extensions/\n const piExtDir = join(targetDir, '.pi', 'extensions');\n ensureDir(piExtDir);\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n writeFile(join(piExtDir, name), content, opts.force, result);\n }\n\n // 2f. Install Pi subagent definitions to .pi/agents/\n const piAgentsDir = join(targetDir, '.pi', 'agents');\n ensureDir(piAgentsDir);\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n writeFile(join(piAgentsDir, name), content, opts.force, result);\n }\n\n // 3. Copy template files to docs/templates/\n const templatesDir = join(targetDir, 'docs', 'templates');\n ensureDir(templatesDir);\n for (const [filename, content] of Object.entries(TEMPLATES)) {\n ensureDir(dirname(join(templatesDir, filename)));\n writeFile(join(templatesDir, filename), content, opts.force, result);\n }\n\n // 4. Handle CLAUDE.md — only create if missing, never modify existing (unless --force)\n const claudeMdPath = join(targetDir, 'CLAUDE.md');\n if (existsSync(claudeMdPath) && !opts.force) {\n result.skipped.push(claudeMdPath);\n } else {\n const projectName = basename(targetDir);\n const content = generateCLAUDEMd(projectName, stack, existingSkills);\n writeFileSync(claudeMdPath, content, 'utf-8');\n result.created.push(claudeMdPath);\n }\n\n // 5. Handle AGENTS.md — only create if missing, never modify existing (unless --force)\n const agentsMdPath = join(targetDir, 'AGENTS.md');\n if (existsSync(agentsMdPath) && !opts.force) {\n result.skipped.push(agentsMdPath);\n } else {\n const projectName = basename(targetDir);\n const content = generateAgentsMd(projectName, stack);\n writeFileSync(agentsMdPath, content, 'utf-8');\n result.created.push(agentsMdPath);\n }\n\n // 6. Write the hidden state (.claude/.joycraft/state.json) with hashes of all managed files\n const fileHashes: Record<string, string> = {};\n for (const [filename, content] of Object.entries(SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.claude', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(CODEX_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.agents', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(TEMPLATES)) {\n fileHashes[join('docs', 'templates', filename)] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(PI_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.pi', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n fileHashes[join('.pi', 'scripts', 'joycraft', name)] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n fileHashes[join('.pi', 'extensions', name)] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n fileHashes[join('.pi', 'agents', name)] = hashContent(content);\n }\n writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile);\n\n // 6b. Apply the chosen gitignore profile.\n // - shared: ignore only the hidden upgrade-state file (npm-lockfile-style;\n // tool-managed state that should never land in commits).\n // - private: ignore .claude/, .agents/, .pi/ — track only CLAUDE.md,\n // AGENTS.md, docs/.\n // Append-only + create-if-absent + idempotent (never clobbers existing entries).\n applyGitignoreProfile(targetDir, gitignoreProfile);\n\n // 7. Install version check hook\n const hooksDir = join(targetDir, '.claude', 'hooks');\n ensureDir(hooksDir);\n const hookScript = `// Joycraft version check — runs on Claude Code session start\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\ntry {\n const data = JSON.parse(readFileSync(join(process.cwd(), '${STATE_PATH.split(/[\\\\/]/).join(\"', '\")}'), 'utf-8'));\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const latest = (await res.json()).version;\n if (data.version !== latest) console.log('Joycraft ' + latest + ' available (you have ' + data.version + '). Run: npm install -g joycraft');\n }\n} catch {}\n`;\n writeFile(join(hooksDir, 'joycraft-version-check.mjs'), hookScript, opts.force, result);\n\n // Update .claude/settings.json with SessionStart hook\n const settingsPath = join(targetDir, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n let settingsMalformed = false;\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n settingsMalformed = true;\n result.warnings.push(\n 'settings.json exists but is malformed — skipping settings merge to protect your config.\\n' +\n ' Fix the JSON in .claude/settings.json and re-run init.'\n );\n }\n }\n if (!settingsMalformed) {\n if (!settings.hooks) settings.hooks = {};\n const hooksConfig = settings.hooks as Record<string, unknown>;\n if (!hooksConfig.SessionStart) hooksConfig.SessionStart = [];\n const sessionStartHooks = hooksConfig.SessionStart as Array<Record<string, unknown>>;\n const hasJoycraftHook = sessionStartHooks.some(h => {\n const innerHooks = h.hooks as Array<Record<string, unknown>> | undefined;\n return innerHooks?.some(ih => typeof ih.command === 'string' && ih.command.includes('joycraft'));\n });\n if (!hasJoycraftHook) {\n sessionStartHooks.push({\n matcher: '',\n hooks: [{\n type: 'command',\n command: 'node .claude/hooks/joycraft-version-check.mjs',\n }],\n });\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n result.created.push(settingsPath);\n }\n\n // 8. Generate and merge permission rules into settings.json\n const permissions = generatePermissions(stack);\n // Re-read settings in case it was just created by hook step\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n result.warnings.push(\n 'settings.json became unreadable after hook merge — skipping permissions merge.\\n' +\n ' Fix the JSON in .claude/settings.json and re-run init.'\n );\n settingsMalformed = true;\n }\n }\n if (!settingsMalformed) {\n if (!settings.permissions) settings.permissions = {};\n const perms = settings.permissions as Record<string, string[]>;\n if (!perms.allow) perms.allow = [];\n if (!perms.deny) perms.deny = [];\n for (const rule of permissions.allow) {\n if (!perms.allow.includes(rule)) perms.allow.push(rule);\n }\n for (const rule of permissions.deny) {\n if (!perms.deny.includes(rule)) perms.deny.push(rule);\n }\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n }\n }\n\n // 9. Install safeguard hooks (PreToolUse deny-pattern blocking)\n const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);\n result.created.push(...hookResult.created);\n result.skipped.push(...hookResult.skipped);\n\n // 10. Check .gitignore for .claude/ exclusion.\n // Only a concern under the `shared` profile, where the intent is to commit\n // skills so teammates get them. Under `private`, ignoring .claude/ is the\n // user's deliberate choice — surfacing a warning there would be wrong.\n if (gitignoreProfile === 'shared') {\n const gitignorePath = join(targetDir, '.gitignore');\n if (existsSync(gitignorePath)) {\n const gitignore = readFileSync(gitignorePath, 'utf-8');\n if (/^\\.claude\\/?$/m.test(gitignore) || /^\\.claude\\/\\*$/m.test(gitignore)) {\n result.warnings.push(\n '.claude/ is in your .gitignore — teammates won\\'t get Joycraft skills.\\n' +\n ' Add this line to .gitignore to fix: !.claude/skills/'\n );\n }\n }\n }\n\n // 11. Print summary\n printSummary(result, stack, existingSkills, isPi, gitignoreProfile);\n}\n\nfunction printSummary(result: InitResult, stack: import('./detect.js').StackInfo, existingSkills: string[] = [], isPi: boolean = false, gitignoreProfile: GitignoreProfile = DEFAULT_GITIGNORE_PROFILE): void {\n console.log('\\nJoycraft initialized!\\n');\n\n if (stack.language !== 'unknown') {\n const fw = stack.framework ? ` + ${stack.framework}` : '';\n console.log(` Detected stack: ${stack.language}${fw} (${stack.packageManager})`);\n } else {\n console.log(' Detected stack: unknown (no recognized manifest found)');\n }\n\n if (isPi) {\n console.log(' Detected agent: Pi');\n }\n\n if (gitignoreProfile === 'private') {\n console.log(' Gitignore profile: private (.claude/, .agents/, .pi/ are gitignored — only CLAUDE.md, AGENTS.md, docs/ are tracked)');\n } else {\n console.log(' Gitignore profile: shared (skills and docs are tracked for your team)');\n }\n\n if (result.created.length > 0) {\n console.log(`\\n Created ${result.created.length} file(s):`);\n for (const f of result.created) {\n console.log(` + ${f}`);\n }\n }\n\n if (result.modified.length > 0) {\n console.log(`\\n Modified ${result.modified.length} file(s):`);\n for (const f of result.modified) {\n console.log(` ~ ${f}`);\n }\n }\n\n if (result.skipped.length > 0) {\n console.log(`\\n Skipped ${result.skipped.length} file(s) (already exist, use --force to overwrite):`);\n for (const f of result.skipped) {\n console.log(` - ${f}`);\n }\n }\n\n if (result.warnings.length > 0) {\n console.log('\\n Warnings:');\n for (const w of result.warnings) {\n console.log(` ⚠ ${w}`);\n }\n }\n\n if (existingSkills.length > 0) {\n console.log(`\\n Found existing skills: ${existingSkills.join(', ')}. These are preserved — Joycraft is additive.`);\n }\n\n const hasExistingClaude = result.skipped.some(f => f.endsWith('CLAUDE.md'));\n\n console.log('\\n Next steps:');\n console.log(' 1. Run Claude Code and try /joycraft-setup — the first-run door that sets up and assesses your project');\n if (hasExistingClaude) {\n console.log(' (it routes to /joycraft-tune to assess and improve your existing CLAUDE.md)');\n } else {\n console.log(' (then review and customize the generated CLAUDE.md for your project)');\n }\n console.log(' 2. Try /joycraft-new-feature to start building with the spec-driven workflow');\n console.log(' (feature artifacts are written to docs/features/<slug>/ as you go)');\n if (gitignoreProfile === 'private') {\n console.log(' 3. Commit CLAUDE.md, AGENTS.md, and docs/ — .claude/, .agents/, .pi/ stay local (gitignored)');\n console.log(' (if any harness files were already committed, run: git rm -r --cached .claude .agents .pi)');\n } else {\n console.log(' 3. Commit .claude/skills/ and docs/ so your team gets the same workflow');\n }\n if (!isPi) {\n console.log(' Pi: Skills installed to .pi/skills/. Use /skill:joycraft-* to invoke.');\n }\n console.log('');\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface TestingStrategy {\n backbone: 'playwright' | 'maestro' | 'api' | 'native';\n testFormat: string;\n layers: ('ui' | 'api' | 'logic' | 'static')[];\n}\n\nexport interface StackInfo {\n language: string;\n packageManager: string;\n commands: {\n build?: string;\n test?: string;\n lint?: string;\n typecheck?: string;\n deploy?: string;\n };\n framework?: string;\n testingStrategy?: TestingStrategy;\n}\n\nconst WEB_FRAMEWORKS = new Set(['Next.js', 'Nuxt', 'Remix', 'React', 'Vue', 'Svelte']);\nconst API_FRAMEWORKS = new Set(['Express', 'Fastify', 'FastAPI', 'Django', 'Flask', 'Gin', 'Fiber', 'Echo', 'Actix', 'Axum', 'Rocket']);\n\nfunction buildTestingStrategy(backbone: TestingStrategy['backbone']): TestingStrategy {\n switch (backbone) {\n case 'playwright':\n return { backbone, testFormat: '.spec.ts', layers: ['ui', 'api', 'logic', 'static'] };\n case 'maestro':\n return { backbone, testFormat: '.yaml', layers: ['ui', 'api', 'logic', 'static'] };\n case 'api':\n return { backbone, testFormat: '.test.ts', layers: ['api', 'logic', 'static'] };\n case 'native':\n return { backbone, testFormat: '.test.ts', layers: ['logic', 'static'] };\n }\n}\n\nfunction readFile(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\nfunction detectNodeFramework(pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }): string | undefined {\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (allDeps['next']) return 'Next.js';\n if (allDeps['nuxt']) return 'Nuxt';\n if (allDeps['@remix-run/node'] || allDeps['@remix-run/react']) return 'Remix';\n if (allDeps['express']) return 'Express';\n if (allDeps['fastify']) return 'Fastify';\n if (allDeps['react']) return 'React';\n if (allDeps['vue']) return 'Vue';\n if (allDeps['svelte']) return 'Svelte';\n return undefined;\n}\n\nfunction detectNodeTestFramework(pkg: { devDependencies?: Record<string, string>; dependencies?: Record<string, string> }): string | undefined {\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (allDeps['vitest']) return 'vitest';\n if (allDeps['jest']) return 'jest';\n if (allDeps['mocha']) return 'mocha';\n return undefined;\n}\n\nfunction detectNodePackageManager(dir: string): string {\n if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(dir, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(dir, 'bun.lockb')) || existsSync(join(dir, 'bun.lock'))) return 'bun';\n return 'npm';\n}\n\nfunction detectNode(dir: string): StackInfo | null {\n const raw = readFile(join(dir, 'package.json'));\n if (raw === null) return null;\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return null;\n }\n\n const pm = detectNodePackageManager(dir);\n const run = pm === 'npm' ? 'npm run' : pm;\n const scripts = (pkg.scripts ?? {}) as Record<string, string>;\n const framework = detectNodeFramework(pkg as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> });\n const testFramework = detectNodeTestFramework(pkg as { devDependencies?: Record<string, string>; dependencies?: Record<string, string> });\n\n const commands: StackInfo['commands'] = {};\n if (scripts.build) commands.build = `${run} build`;\n else commands.build = `${run} build`;\n if (scripts.test) commands.test = `${run} test`;\n else if (testFramework) commands.test = `${run} test`;\n else commands.test = `${pm === 'npm' ? 'npm' : pm} test`;\n if (scripts.lint) commands.lint = `${run} lint`;\n if (scripts.typecheck) commands.typecheck = `${run} typecheck`;\n else if ((pkg.devDependencies as Record<string, string> | undefined)?.['typescript']) {\n commands.typecheck = 'tsc --noEmit';\n }\n\n const allDeps = { ...(pkg.dependencies as Record<string, string> | undefined), ...(pkg.devDependencies as Record<string, string> | undefined) };\n let testingStrategy: TestingStrategy;\n if (allDeps['react-native']) {\n testingStrategy = buildTestingStrategy('maestro');\n } else if (framework && WEB_FRAMEWORKS.has(framework)) {\n testingStrategy = buildTestingStrategy('playwright');\n } else if (framework && API_FRAMEWORKS.has(framework)) {\n testingStrategy = buildTestingStrategy('api');\n } else {\n testingStrategy = buildTestingStrategy('native');\n }\n\n return {\n language: 'node',\n packageManager: pm,\n commands,\n framework,\n testingStrategy,\n };\n}\n\nfunction detectPythonFramework(content: string): string | undefined {\n if (/fastapi/i.test(content)) return 'FastAPI';\n if (/django/i.test(content)) return 'Django';\n if (/flask/i.test(content)) return 'Flask';\n return undefined;\n}\n\nfunction detectPython(dir: string): StackInfo | null {\n const pyproject = readFile(join(dir, 'pyproject.toml'));\n if (pyproject !== null) {\n const isPoetry = /\\[tool\\.poetry\\]/.test(pyproject);\n const isUv = existsSync(join(dir, 'uv.lock'));\n\n let pm: string;\n let run: string;\n if (isUv) {\n pm = 'uv';\n run = 'uv run';\n } else if (isPoetry) {\n pm = 'poetry';\n run = 'poetry run';\n } else {\n pm = 'pip';\n run = 'python -m';\n }\n\n const framework = detectPythonFramework(pyproject);\n const hasPytest = /pytest/i.test(pyproject);\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'python',\n packageManager: pm,\n commands: {\n build: `${pm === 'poetry' ? 'poetry' : pm} build`,\n test: hasPytest ? `${run} pytest` : `${run} pytest`,\n lint: `${run} ruff check .`,\n },\n framework,\n testingStrategy,\n };\n }\n\n const requirements = readFile(join(dir, 'requirements.txt'));\n if (requirements !== null) {\n const framework = detectPythonFramework(requirements);\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'python',\n packageManager: 'pip',\n commands: {\n build: 'pip install -e .',\n test: 'python -m pytest',\n lint: 'python -m ruff check .',\n },\n framework,\n testingStrategy,\n };\n }\n\n return null;\n}\n\nfunction detectRust(dir: string): StackInfo | null {\n const cargo = readFile(join(dir, 'Cargo.toml'));\n if (cargo === null) return null;\n\n let framework: string | undefined;\n if (/actix-web/.test(cargo)) framework = 'Actix';\n else if (/axum/.test(cargo)) framework = 'Axum';\n else if (/rocket/.test(cargo)) framework = 'Rocket';\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'rust',\n packageManager: 'cargo',\n commands: {\n build: 'cargo build',\n test: 'cargo test',\n lint: 'cargo clippy',\n },\n framework,\n testingStrategy,\n };\n}\n\nfunction detectGo(dir: string): StackInfo | null {\n const gomod = readFile(join(dir, 'go.mod'));\n if (gomod === null) return null;\n\n let framework: string | undefined;\n if (/github\\.com\\/gin-gonic\\/gin/.test(gomod)) framework = 'Gin';\n else if (/github\\.com\\/gofiber\\/fiber/.test(gomod)) framework = 'Fiber';\n else if (/github\\.com\\/labstack\\/echo/.test(gomod)) framework = 'Echo';\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'go',\n packageManager: 'go',\n commands: {\n build: 'go build ./...',\n test: 'go test ./...',\n lint: 'golangci-lint run',\n },\n framework,\n testingStrategy,\n };\n}\n\nfunction detectSwift(dir: string): StackInfo | null {\n const pkg = readFile(join(dir, 'Package.swift'));\n if (pkg === null) return null;\n\n return {\n language: 'swift',\n packageManager: 'swift',\n commands: {\n build: 'swift build',\n test: 'swift test',\n },\n testingStrategy: buildTestingStrategy('native'),\n };\n}\n\nfunction detectFlutter(dir: string): StackInfo | null {\n const pubspec = readFile(join(dir, 'pubspec.yaml'));\n if (pubspec === null) return null;\n if (!/flutter/i.test(pubspec)) return null;\n\n return {\n language: 'dart',\n packageManager: 'flutter',\n commands: {\n build: 'flutter build',\n test: 'flutter test',\n lint: 'flutter analyze',\n },\n framework: 'Flutter',\n testingStrategy: buildTestingStrategy('maestro'),\n };\n}\n\nfunction detectXcode(dir: string): StackInfo | null {\n // Check for .xcodeproj or .xcworkspace directories\n try {\n const entries = readdirSync(dir);\n const hasXcode = entries.some(e => e.endsWith('.xcodeproj') || e.endsWith('.xcworkspace'));\n if (!hasXcode) return null;\n } catch {\n return null;\n }\n\n return {\n language: 'swift',\n packageManager: 'xcode',\n commands: {\n build: 'xcodebuild build',\n test: 'xcodebuild test',\n },\n testingStrategy: buildTestingStrategy('maestro'),\n };\n}\n\nfunction detectMakefile(dir: string): StackInfo | null {\n const makefile = readFile(join(dir, 'Makefile'));\n if (makefile === null) return null;\n\n const commands: StackInfo['commands'] = {};\n commands.build = 'make build';\n if (/^test:/m.test(makefile)) commands.test = 'make test';\n if (/^lint:/m.test(makefile)) commands.lint = 'make lint';\n\n return {\n language: 'unknown',\n packageManager: 'make',\n commands,\n };\n}\n\nfunction detectDockerfile(dir: string): StackInfo | null {\n if (!existsSync(join(dir, 'Dockerfile'))) return null;\n\n return {\n language: 'unknown',\n packageManager: 'docker',\n commands: {\n build: 'docker build .',\n },\n };\n}\n\nexport async function detectStack(dir: string): Promise<StackInfo> {\n const detectors = [\n detectNode,\n detectPython,\n detectRust,\n detectGo,\n detectFlutter,\n detectSwift,\n detectXcode,\n detectMakefile,\n detectDockerfile,\n ];\n\n for (const detect of detectors) {\n const result = detect(dir);\n if (result) return result;\n }\n\n return { language: 'unknown', packageManager: '', commands: {} };\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { StackInfo } from './detect.js';\n\nexport interface ImproveOptions {\n projectDir?: string;\n}\n\ninterface Section {\n header: string;\n content: string;\n}\n\nfunction parseSections(markdown: string): Section[] {\n const lines = markdown.split('\\n');\n const sections: Section[] = [];\n let currentHeader = '';\n let currentLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n currentHeader = line;\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n // Push the last section\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n\n return sections;\n}\n\nfunction hasSection(sections: Section[], pattern: RegExp): boolean {\n return sections.some(s => pattern.test(s.header));\n}\n\nfunction generateCommandsBlock(stack: StackInfo): string {\n const lines: string[] = ['```bash'];\n if (stack.commands.build) lines.push(`# Build\\n${stack.commands.build}`);\n if (stack.commands.test) lines.push(`# Test\\n${stack.commands.test}`);\n if (stack.commands.lint) lines.push(`# Lint\\n${stack.commands.lint}`);\n if (stack.commands.typecheck) lines.push(`# Type check\\n${stack.commands.typecheck}`);\n if (stack.commands.deploy) lines.push(`# Deploy\\n${stack.commands.deploy}`);\n lines.push('```');\n return lines.join('\\n');\n}\n\nexport function generateBoundariesSection(): string {\n return `## Behavioral Boundaries\n\n### ALWAYS\n- Run tests and type-check before committing\n- Run tests before implementing new features -- confirm they fail first, then implement until they pass\n- Use \\`verb: concise message\\` format for commits\n- Commit after completing each discrete task (atomic commits)\n- Stage specific files by name (not \\`git add -A\\` or \\`git add .\\`)\n- Read \\`docs/context/\\` before making infrastructure or config changes\n- Follow existing code patterns and style\n\n### ASK FIRST\n- Pushing to remote\n- Creating or merging pull requests\n- Adding new dependencies\n- Modifying database schema or data models\n- Changing authentication or authorization flows\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master without approval\n- Commit .env files, secrets, or credentials\n- Use --no-verify to skip hooks\n- Amend commits that have been pushed\n- Skip type-checking or linting\n- Commit code that doesn't build`;\n}\n\nfunction generateWorkflowSection(stack: StackInfo): string {\n return `## Development Workflow\n\n${generateCommandsBlock(stack)}\n\n**Default execution mode:** batch\n\n_How \\`/joycraft-implement\\` wraps up after each spec. \\`joycraft-decompose\\` reads this line (absent ⇒ \\`batch\\`) and recommends a per-spec mode you approve. Modes: \\`batch\\` (implement a cluster, wrap once at the end), \\`checkpoint\\` (commit + status bump after each spec), \\`isolated\\` (fresh context per spec — on Pi, the \\`joycraft-implement-loop\\` driver). Change the value above to set your project default._`;\n}\n\nfunction generateArchitectureSection(): string {\n return `## Architecture\n\n_TODO: Add a brief description of your project's architecture and key directories._`;\n}\n\nfunction generateKeyFilesSection(): string {\n return `## Key Files\n\n| File | Purpose |\n|------|---------|\n| _TODO_ | _Add key files and their purposes_ |`;\n}\n\nfunction generateGotchasSection(): string {\n return `## Common Gotchas\n\n_TODO: Add any gotchas, quirks, or non-obvious behaviors that developers should know about._`;\n}\n\nfunction generateGettingStartedSection(): string {\n return `## Getting Started with Joycraft\n\nThis project uses [Joycraft](https://github.com/maksutovic/joycraft) for AI development workflow. Available skills:\n\n| Skill | Purpose |\n|-------|---------|\n| \\`/joycraft-setup\\` | Start here — the first-run door; sets up and assesses your project |\n| \\`/joycraft-tune\\` | Assess your harness, apply upgrades, see path to Level 5 |\n| \\`/joycraft-new-feature\\` | Interview -> Feature Brief -> Atomic Specs |\n| \\`/joycraft-interview\\` | Lightweight brainstorm — yap about ideas, get a structured summary |\n| \\`/joycraft-decompose\\` | Break a brief into small, testable specs |\n| \\`/joycraft-session-end\\` | Capture discoveries, verify, commit |\n| \\`/joycraft-implement-level5\\` | Set up Level 5 — autofix loop, holdout scenarios, scenario evolution |\n\nRun \\`/joycraft-tune\\` to see where your project stands and what to improve next.`;\n}\n\nexport function generateContextMapSection(): string {\n return `## Context Map\n\nKeep this file lean — link out, don't inline. Long-form reference docs live in \\`docs/context/reference/\\`; this table points to what to read on demand.\n\n| Document | Read it when… |\n|----------|---------------|`;\n}\n\nfunction generateExternalValidationSection(): string {\n return `## External Validation\n\nThis project uses holdout scenario tests in a separate private repo.\n\n### NEVER\n- Access, read, or reference the scenarios repo\n- Mention scenario test names or contents\n- Modify the scenarios dispatch workflow to leak test information\n\nThe scenarios repo is deliberately invisible to you. This is the holdout guarantee — like a validation set in ML.`;\n}\n\nfunction generateAreasSection(): string {\n return `## Areas\n\nThis project organizes some work by area. When working on a specific area, read its README first; check for area-specific boundaries.\n\n- For each area: see \\`docs/areas/<area-name>/README.md\\`\n- Area-level boundaries (when present): \\`docs/areas/<area-name>/boundaries.md\\``;\n}\n\nfunction projectHasAreas(opts?: ImproveOptions): boolean {\n if (!opts?.projectDir) return false;\n return existsSync(join(opts.projectDir, 'docs', 'areas'));\n}\n\nfunction stripAreasSection(content: string): string {\n // Remove an existing \"## Areas\" section (header + body up to next \"## \" header or EOF).\n return content.replace(/\\n##\\s+Areas\\b[\\s\\S]*?(?=\\n##\\s|\\n*$)/, '').trimEnd() + '\\n';\n}\n\nfunction generateProjectToolsSection(existingSkills: string[]): string {\n const MAX_LISTED = 10;\n let skillList: string;\n if (existingSkills.length <= MAX_LISTED) {\n skillList = existingSkills.join(', ');\n } else {\n skillList = existingSkills.slice(0, MAX_LISTED).join(', ') +\n `, and ${existingSkills.length - MAX_LISTED} more — see .claude/skills/`;\n }\n return `## Project Tools\n\nThis project has additional tools beyond Joycraft. Always check \\`.claude/skills/\\` for available skills: ${skillList}`;\n}\n\nexport function improveCLAUDEMd(\n existing: string,\n stack: StackInfo,\n existingSkills: string[] = [],\n opts?: ImproveOptions,\n): string {\n // Areas pointer: idempotent in both directions.\n // Always strip an existing \"## Areas\" section first so we re-evaluate cleanly.\n let working = stripAreasSection(existing);\n const sections = parseSections(working);\n const additions: string[] = [];\n\n if (!hasSection(sections, /behavioral\\s*boundar/i)) {\n additions.push(generateBoundariesSection());\n }\n\n if (!hasSection(sections, /development\\s*workflow/i) && !hasSection(sections, /workflow/i)) {\n additions.push(generateWorkflowSection(stack));\n }\n\n if (!hasSection(sections, /architecture/i)) {\n additions.push(generateArchitectureSection());\n }\n\n if (!hasSection(sections, /key\\s*files/i)) {\n additions.push(generateKeyFilesSection());\n }\n\n if (!hasSection(sections, /common\\s*gotchas/i) && !hasSection(sections, /gotchas/i)) {\n additions.push(generateGotchasSection());\n }\n\n if (!hasSection(sections, /getting\\s*started.*joycraft/i) && !hasSection(sections, /joycraft.*skills/i)) {\n additions.push(generateGettingStartedSection());\n }\n\n if (!hasSection(sections, /context\\s*map/i)) {\n additions.push(generateContextMapSection());\n }\n\n if (!hasSection(sections, /external\\s*validation/i)) {\n additions.push(generateExternalValidationSection());\n }\n\n if (existingSkills.length > 0 && !hasSection(sections, /project\\s*tools/i)) {\n additions.push(generateProjectToolsSection(existingSkills));\n }\n\n if (projectHasAreas(opts)) {\n additions.push(generateAreasSection());\n }\n\n if (additions.length === 0) {\n return working === existing ? existing : working;\n }\n\n const trimmed = working.trimEnd();\n return trimmed + '\\n\\n' + additions.join('\\n\\n') + '\\n';\n}\n\nexport function generateCLAUDEMd(\n projectName: string,\n stack: StackInfo,\n existingSkills: string[] = [],\n opts?: ImproveOptions,\n): string {\n const frameworkNote = stack.framework ? ` (${stack.framework})` : '';\n const langLabel = stack.language === 'unknown' ? '' : ` | **Stack:** ${stack.language}${frameworkNote}`;\n\n const lines: string[] = [\n `# ${projectName}`,\n '',\n `**Component:** _TODO: describe what this project is_${langLabel}`,\n '',\n '---',\n '',\n generateBoundariesSection(),\n '',\n generateWorkflowSection(stack),\n '',\n generateArchitectureSection(),\n '',\n generateKeyFilesSection(),\n '',\n generateGotchasSection(),\n '',\n generateContextMapSection(),\n '',\n generateGettingStartedSection(),\n '',\n ];\n\n if (existingSkills.length > 0) {\n lines.push(generateProjectToolsSection(existingSkills), '');\n }\n\n if (projectHasAreas(opts)) {\n lines.push(generateAreasSection(), '');\n }\n\n return lines.join('\\n');\n}\n","import type { StackInfo } from './detect.js';\nimport { generateBoundariesSection } from './improve-claude-md.js';\n\ninterface Section {\n header: string;\n content: string;\n}\n\nfunction parseSections(markdown: string): Section[] {\n const lines = markdown.split('\\n');\n const sections: Section[] = [];\n let currentHeader = '';\n let currentLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n currentHeader = line;\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n\n return sections;\n}\n\nfunction hasSection(sections: Section[], pattern: RegExp): boolean {\n return sections.some(s => pattern.test(s.header));\n}\n\nfunction generateCommandsBlock(stack: StackInfo): string {\n const lines: string[] = ['```bash'];\n if (stack.commands.build) lines.push(stack.commands.build);\n if (stack.commands.test) lines.push(stack.commands.test);\n if (stack.commands.lint) lines.push(stack.commands.lint);\n if (stack.commands.typecheck) lines.push(stack.commands.typecheck);\n if (stack.commands.deploy) lines.push(stack.commands.deploy);\n lines.push('```');\n return lines.join('\\n');\n}\n\nfunction generateExternalApiSafetySection(): string {\n return '### External API Safety\\n- Read official docs and type definitions before writing code against a third-party SDK\\n- Add third-party SDKs as devDependencies so typecheck runs against real types, not stubs\\n- Critical integration paths should have a smoke test that validates against the real runtime';\n}\n\nfunction generateDevelopmentSection(stack: StackInfo): string {\n return `## Development\\n\\n${generateCommandsBlock(stack)}`;\n}\n\nfunction generateArchitectureSection(): string {\n return `## Architecture\\n\\n_TODO: Add a compact directory tree and one-paragraph summary._`;\n}\n\nfunction generateKeyFilesSection(): string {\n return `## Key Files\\n\\n| File | Purpose |\\n|------|---------|\\n| _TODO_ | _Add key files_ |`;\n}\n\nexport function generateAgentsMd(projectName: string, stack: StackInfo): string {\n const frameworkNote = stack.framework ? ` (${stack.framework})` : '';\n const langLabel = stack.language === 'unknown' ? '' : ` | **Stack:** ${stack.language}${frameworkNote}`;\n\n const lines: string[] = [\n `# ${projectName}`,\n '',\n `**Component:** _TODO: describe what this project is_${langLabel}`,\n '',\n '> Auto-generated by Joycraft.',\n '',\n '---',\n '',\n generateBoundariesSection(),\n generateExternalApiSafetySection(),\n '',\n generateArchitectureSection(),\n '',\n generateKeyFilesSection(),\n '',\n generateDevelopmentSection(stack),\n '',\n ];\n\n return lines.join('\\n');\n}\n\nexport function improveAgentsMd(existing: string, stack: StackInfo): string {\n const sections = parseSections(existing);\n const additions: string[] = [];\n\n if (!hasSection(sections, /behavioral\\s*boundar/i)) {\n additions.push(generateBoundariesSection());\n }\n\n if (!/external\\s*api\\s*safety/i.test(existing)) {\n additions.push('### External API Safety\\n- Read official docs and type definitions before writing code against a third-party SDK\\n- Add third-party SDKs as devDependencies so typecheck runs against real types, not stubs\\n- Critical integration paths should have a smoke test that validates against the real runtime');\n }\n\n if (!hasSection(sections, /architecture/i)) {\n additions.push(generateArchitectureSection());\n }\n\n if (!hasSection(sections, /key\\s*files/i)) {\n additions.push(generateKeyFilesSection());\n }\n\n if (!hasSection(sections, /development/i)) {\n additions.push(generateDevelopmentSection(stack));\n }\n\n if (additions.length === 0) {\n return existing;\n }\n\n const trimmed = existing.trimEnd();\n return trimmed + '\\n\\n' + additions.join('\\n\\n') + '\\n';\n}\n","import type { StackInfo } from './detect.js';\n\nexport interface PermissionRules {\n allow: string[];\n deny: string[];\n}\n\nexport function generatePermissions(stack: StackInfo): PermissionRules {\n // Default deny rules for ALL projects\n const deny: string[] = [\n 'Bash(rm -rf *)',\n 'Bash(git push --force *)',\n 'Bash(git push -f *)',\n 'Bash(git reset --hard *)',\n 'Edit(//.env*)',\n 'Edit(//*.pem)',\n 'Edit(//*.key)',\n 'Edit(//.git/**)',\n ];\n\n // Default allow rules\n const allow: string[] = [\n 'Bash(git status)',\n 'Bash(git diff *)',\n 'Bash(git log *)',\n 'Bash(git add *)',\n 'Bash(git commit *)',\n 'Bash(git checkout *)',\n 'Bash(git branch *)',\n ];\n\n // Stack-specific rules\n if (stack.language === 'node') {\n allow.push(`Bash(${stack.packageManager} *)`);\n if (stack.packageManager !== 'npm') deny.push('Bash(npm install *)');\n if (stack.packageManager !== 'yarn') deny.push('Bash(yarn add *)');\n if (stack.packageManager !== 'pnpm') deny.push('Bash(pnpm add *)');\n if (stack.packageManager !== 'bun') deny.push('Bash(bun add *)');\n if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);\n if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);\n if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);\n if (stack.commands.typecheck) allow.push(`Bash(${stack.commands.typecheck})`);\n }\n\n if (stack.language === 'python') {\n allow.push(`Bash(${stack.packageManager} *)`);\n if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);\n if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);\n if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);\n }\n\n if (stack.language === 'rust') {\n allow.push('Bash(cargo *)');\n }\n\n if (stack.language === 'go') {\n allow.push('Bash(go *)');\n }\n\n if (stack.language === 'swift') {\n allow.push('Bash(swift *)');\n allow.push('Bash(xcodebuild *)');\n }\n\n return { allow, deny };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface SafeguardConfig {\n denyPatterns: string[];\n}\n\n/**\n * Generate default deny patterns from common dangerous operations.\n */\nexport function getDefaultDenyPatterns(): string[] {\n return [\n 'rm\\\\s+-rf\\\\s+/', // rm -rf with absolute path\n 'rm\\\\s+-rf\\\\s+\\\\.', // rm -rf . or ./\n 'git\\\\s+push\\\\s+--force', // force push\n 'git\\\\s+push\\\\s+-f\\\\b', // force push shorthand\n 'git\\\\s+reset\\\\s+--hard', // hard reset\n 'DROP\\\\s+TABLE', // SQL drop\n 'DROP\\\\s+DATABASE', // SQL drop\n 'TRUNCATE', // SQL truncate\n 'chmod\\\\s+777', // wide-open permissions\n 'curl.*\\\\|.*sh', // pipe curl to shell\n 'wget.*\\\\|.*sh', // pipe wget to shell\n ];\n}\n\n/**\n * Generate the hook script that blocks dangerous Bash commands.\n */\nexport function generateHookScript(): string {\n return `#!/bin/bash\n# Joycraft Safeguard — PreToolUse hook\n# Blocks dangerous Bash commands. Exit 2 = block the action.\n# Edit deny-patterns.txt to customize what's blocked.\n\nTOOL_NAME=\"$1\"\n# Read the full tool input from stdin\nINPUT=$(cat)\n\n# Only check Bash commands\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then\n exit 0\nfi\n\n# Extract the command from the JSON input\nCOMMAND=$(echo \"$INPUT\" | grep -o '\"command\":\"[^\"]*\"' | head -1 | sed 's/\"command\":\"//;s/\"$//')\n\nif [ -z \"$COMMAND\" ]; then\n exit 0\nfi\n\nPATTERNS_FILE=\"$(dirname \"$0\")/deny-patterns.txt\"\n\nif [ ! -f \"$PATTERNS_FILE\" ]; then\n exit 0\nfi\n\nwhile IFS= read -r pattern || [ -n \"$pattern\" ]; do\n # Skip empty lines and comments\n [ -z \"$pattern\" ] && continue\n [[ \"$pattern\" == \\\\#* ]] && continue\n\n if echo \"$COMMAND\" | grep -qEi \"$pattern\"; then\n echo \"Blocked by Joycraft Safeguard: command matches deny pattern '$pattern'\"\n echo \"Edit .claude/hooks/joycraft/deny-patterns.txt to modify blocked patterns.\"\n exit 2\n fi\ndone < \"$PATTERNS_FILE\"\n\nexit 0\n`;\n}\n\n/**\n * Generate deny-patterns.txt content from default patterns + custom patterns.\n */\nexport function generateDenyPatternsFile(customPatterns: string[] = []): string {\n const lines = [\n '# Joycraft Safeguard — Deny Patterns',\n '# One regex pattern per line. Lines starting with # are comments.',\n '# Commands matching any pattern will be blocked (exit 2).',\n '# Edit this file to customize what\\'s blocked.',\n '',\n '# Destructive file operations',\n 'rm\\\\s+-rf\\\\s+/',\n 'rm\\\\s+-rf\\\\s+\\\\.',\n '',\n '# Dangerous git operations',\n 'git\\\\s+push\\\\s+--force',\n 'git\\\\s+push\\\\s+-f\\\\b',\n 'git\\\\s+reset\\\\s+--hard',\n '',\n '# SQL destruction',\n 'DROP\\\\s+TABLE',\n 'DROP\\\\s+DATABASE',\n 'TRUNCATE',\n '',\n '# System security',\n 'chmod\\\\s+777',\n 'curl.*\\\\|.*sh',\n 'wget.*\\\\|.*sh',\n ];\n\n if (customPatterns.length > 0) {\n lines.push('');\n lines.push('# Project-specific patterns (from risk interview)');\n for (const p of customPatterns) {\n lines.push(p);\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Install safeguard hooks into a project.\n */\nexport function installSafeguardHooks(\n targetDir: string,\n customPatterns: string[] = [],\n force: boolean = false,\n skipSettingsMerge: boolean = false\n): { created: string[]; skipped: string[] } {\n const result = { created: [] as string[], skipped: [] as string[] };\n const hooksDir = join(targetDir, '.claude', 'hooks', 'joycraft');\n\n if (!existsSync(hooksDir)) {\n mkdirSync(hooksDir, { recursive: true });\n }\n\n // Write hook script\n const hookPath = join(hooksDir, 'block-dangerous.sh');\n if (!existsSync(hookPath) || force) {\n writeFileSync(hookPath, generateHookScript(), { mode: 0o755 });\n result.created.push(hookPath);\n } else {\n result.skipped.push(hookPath);\n }\n\n // Write deny patterns\n const patternsPath = join(hooksDir, 'deny-patterns.txt');\n if (!existsSync(patternsPath) || force) {\n writeFileSync(patternsPath, generateDenyPatternsFile(customPatterns));\n result.created.push(patternsPath);\n } else {\n result.skipped.push(patternsPath);\n }\n\n // Register hook in settings.json (skip if caller detected malformed JSON)\n if (!skipSettingsMerge) {\n const settingsPath = join(targetDir, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n // Don't overwrite — caller should handle the warning\n return result;\n }\n }\n\n if (!settings.hooks) settings.hooks = {};\n const hooks = settings.hooks as Record<string, unknown[]>;\n if (!hooks.PreToolUse) hooks.PreToolUse = [];\n\n const preToolUse = hooks.PreToolUse as Array<Record<string, unknown>>;\n const hasJoycraftHook = preToolUse.some(h => {\n const innerHooks = h.hooks as Array<Record<string, unknown>> | undefined;\n return innerHooks?.some(ih => typeof ih.command === 'string' && ih.command.includes('joycraft'));\n });\n\n if (!hasJoycraftHook) {\n preToolUse.push({\n matcher: 'Bash',\n hooks: [{\n type: 'command',\n command: '.claude/hooks/joycraft/block-dangerous.sh',\n }],\n });\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":["mkdirSync","existsSync","writeFileSync","readFileSync","readdirSync","join","existsSync","join","existsSync","join","generateCommandsBlock","generateArchitectureSection","generateKeyFilesSection","existsSync","readFileSync","join","join","existsSync","readFileSync","resolve","existsSync","mkdirSync","writeFileSync","join","readdirSync","readFileSync"]}
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
LEGACY_VERSION_FILE,
|
|
7
7
|
STATE_PATH
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-PUYGEBLA.js";
|
|
9
9
|
|
|
10
10
|
// src/init-autofix.ts
|
|
11
11
|
import { mkdirSync, existsSync, writeFileSync } from "fs";
|
|
@@ -116,4 +116,4 @@ function printSummary(result, dryRun, scenariosRepo) {
|
|
|
116
116
|
export {
|
|
117
117
|
initAutofix
|
|
118
118
|
};
|
|
119
|
-
//# sourceMappingURL=init-autofix-
|
|
119
|
+
//# sourceMappingURL=init-autofix-MQL74RF7.js.map
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
applyGitignoreProfile,
|
|
3
4
|
ensureGitignoreEntry,
|
|
4
5
|
getPackageVersion
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-P45XNSGK.js";
|
|
6
7
|
import {
|
|
7
8
|
CODEX_SKILLS,
|
|
8
9
|
PI_AGENTS,
|
|
@@ -13,13 +14,14 @@ import {
|
|
|
13
14
|
TEMPLATES
|
|
14
15
|
} from "./chunk-W6AHAE7X.js";
|
|
15
16
|
import {
|
|
17
|
+
DEFAULT_GITIGNORE_PROFILE,
|
|
16
18
|
LEGACY_VERSION_FILE,
|
|
17
19
|
STATE_PATH,
|
|
18
20
|
hashContent,
|
|
19
21
|
readVersion,
|
|
20
22
|
truncateHash,
|
|
21
23
|
writeVersion
|
|
22
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-PUYGEBLA.js";
|
|
23
25
|
|
|
24
26
|
// src/upgrade.ts
|
|
25
27
|
import { existsSync as existsSync2, readFileSync, writeFileSync, mkdirSync as mkdirSync2, rmSync as rmSync2, chmodSync } from "fs";
|
|
@@ -423,7 +425,10 @@ async function upgrade(dir, opts) {
|
|
|
423
425
|
runForcedMigration(targetDir);
|
|
424
426
|
const pkgVersion = getPackageVersion();
|
|
425
427
|
const managedFiles = getManagedFiles();
|
|
426
|
-
const
|
|
428
|
+
const installed = readVersion(targetDir);
|
|
429
|
+
const installedHashes = installed?.files ?? {};
|
|
430
|
+
const gitignoreProfile = installed?.gitignoreProfile ?? DEFAULT_GITIGNORE_PROFILE;
|
|
431
|
+
applyGitignoreProfile(targetDir, gitignoreProfile);
|
|
427
432
|
const changes = [];
|
|
428
433
|
let upToDate = 0;
|
|
429
434
|
for (const [relPath, newContent] of Object.entries(managedFiles)) {
|
|
@@ -494,7 +499,7 @@ async function upgrade(dir, opts) {
|
|
|
494
499
|
newHashes[relPath] = hashContent(current);
|
|
495
500
|
}
|
|
496
501
|
}
|
|
497
|
-
writeVersion(targetDir, pkgVersion, newHashes);
|
|
502
|
+
writeVersion(targetDir, pkgVersion, newHashes, gitignoreProfile);
|
|
498
503
|
const parts = [];
|
|
499
504
|
if (updated > 0) parts.push(`Updated ${updated}`);
|
|
500
505
|
if (skipped > 0) parts.push(`skipped ${skipped} (customized)`);
|
|
@@ -506,4 +511,4 @@ Upgrade complete: ${parts.join(", ")}.`);
|
|
|
506
511
|
export {
|
|
507
512
|
upgrade
|
|
508
513
|
};
|
|
509
|
-
//# sourceMappingURL=upgrade-
|
|
514
|
+
//# sourceMappingURL=upgrade-7E5ZGYHP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/upgrade.ts","../src/migration.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, chmodSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { readVersion, writeVersion, hashContent, truncateHash, STATE_PATH, LEGACY_VERSION_FILE, DEFAULT_GITIGNORE_PROFILE } from './version.js';\nimport { ensureGitignoreEntry, applyGitignoreProfile } from './gitignore.js';\nimport { SKILLS, TEMPLATES, CODEX_SKILLS, PI_SKILLS, PI_SCRIPTS, PI_EXTENSIONS, PI_AGENTS } from './bundled-files.js';\nimport { getPackageVersion } from './package-version.js';\nimport { planMigration, applyMigration, type MigrationPlan } from './migration.js';\n\nfunction isStaleVersion(current: string, latest: string): boolean {\n const currentParts = current.split('.').map(Number);\n const latestParts = latest.split('.').map(Number);\n const len = Math.max(currentParts.length, latestParts.length);\n for (let i = 0; i < len; i++) {\n const c = currentParts[i] ?? 0;\n const l = latestParts[i] ?? 0;\n if (c < l) return true;\n if (c > l) return false;\n }\n return false;\n}\n\nasync function checkCliVersion(): Promise<{ stale: boolean; latest?: string }> {\n try {\n const pkgVersion = getPackageVersion();\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', {\n signal: AbortSignal.timeout(3000)\n });\n if (!res.ok) return { stale: false };\n const data = (await res.json()) as { version: string };\n if (isStaleVersion(pkgVersion, data.version)) {\n return { stale: true, latest: data.version };\n }\n } catch {\n // Silent fallback on network errors or missing version\n }\n return { stale: false };\n}\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 for (const [name, content] of Object.entries(PI_SKILLS)) {\n const skillName = name.replace(/\\.md$/, '');\n files[join('.pi', 'skills', skillName, 'SKILL.md')] = content;\n }\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n files[join('.pi', 'scripts', 'joycraft', name)] = content;\n }\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n files[join('.pi', 'extensions', name)] = content;\n }\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n files[join('.pi', 'agents', 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 // 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\n/**\n * Self-heal projects inited by an older Joycraft that wrote state to the repo\n * root (`.joycraft-version`). Reads the legacy file, re-writes it to the hidden\n * `.claude/.joycraft/state.json` location, deletes the root file, and ensures\n * the gitignore entry. No-op when no legacy root file exists.\n *\n * Runs BEFORE the managed-file diff so the recorded-original hashes are\n * available at the new location for the same run's 3-way comparison. The hidden\n * state's own write truncates the (possibly full-length legacy) hashes, so the\n * comparison stays consistent — see the truncateHash() call in the diff loop.\n */\nfunction migrateLegacyVersionFile(targetDir: string): boolean {\n const legacyPath = join(targetDir, LEGACY_VERSION_FILE);\n if (!existsSync(legacyPath)) return false;\n\n let parsed: { version?: unknown; files?: unknown };\n try {\n parsed = JSON.parse(readFileSync(legacyPath, 'utf-8'));\n } catch {\n // Corrupt legacy file: treat as no usable baseline. Remove it so the root\n // stops being polluted; upgrade then proceeds with no recorded-original\n // (every changed file becomes \"customized\" — safe, never silently wrong).\n rmSync(legacyPath, { force: true });\n ensureGitignoreEntry(targetDir, STATE_PATH);\n return true;\n }\n\n const version = typeof parsed.version === 'string' ? parsed.version : getPackageVersion();\n const files =\n parsed.files && typeof parsed.files === 'object'\n ? (parsed.files as Record<string, string>)\n : {};\n\n // writeVersion targets the new hidden path and truncates the hashes.\n writeVersion(targetDir, version, files);\n rmSync(legacyPath, { force: true });\n ensureGitignoreEntry(targetDir, STATE_PATH);\n return true;\n}\n\nfunction countLines(content: string): number {\n return content.split('\\n').length;\n}\n\nfunction ensureScriptExecutable(absolutePath: string): void {\n // Joycraft shell scripts in .pi/scripts/joycraft/ must be executable.\n // README.md is the only file in that directory that should stay 644.\n if (absolutePath.includes('.pi/scripts/joycraft/') && !absolutePath.endsWith('README.md')) {\n try {\n chmodSync(absolutePath, 0o755);\n } catch {\n // non-fatal — permissions may be restricted\n }\n }\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 const relTo = (p: string) => (p.startsWith(projectDir) ? p.slice(projectDir.length + 1) : p);\n\n // Group per-feature moves by feature folder for readability.\n // Bugfix-area moves are listed separately below.\n const featureMoves = plan.moves.filter(m => m.kind !== 'bugfix-dir');\n const bugfixMoves = plan.moves.filter(m => m.kind === 'bugfix-dir');\n\n const bySlug = new Map<string, typeof plan.moves>();\n for (const move of featureMoves) {\n const parts = relTo(move.to).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 console.log(` ${relTo(move.from)} → ${relTo(move.to)}`);\n }\n }\n\n if (bugfixMoves.length > 0) {\n console.log('');\n console.log(' Migrating bugfix areas:');\n for (const move of bugfixMoves) {\n console.log(` ${relTo(move.from)} → ${relTo(move.to)}`);\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 // Guard: if the CLI itself is out of date, warn and bail before comparing\n // project files against stale bundled content.\n const cliCheck = await checkCliVersion();\n if (cliCheck.stale) {\n const pkgVersion = getPackageVersion();\n console.log(`Joycraft CLI is out of date (you have ${pkgVersion}, latest is ${cliCheck.latest}).`);\n console.log('Update with: npm install -g joycraft');\n console.log('Then re-run: npx joycraft upgrade');\n return;\n }\n\n // Check if project was initialized. A project is \"initialized\" if it has the\n // hidden state, OR a known skill, OR a legacy root state file (pre-relocation).\n const hasLegacyState = existsSync(join(targetDir, LEGACY_VERSION_FILE));\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 (!readVersion(targetDir) && !hasLegacyState && !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 // Relocate any legacy repo-root .joycraft-version → hidden state, BEFORE the\n // diff loop so the migrated recorded-original is used on this same run. No-op\n // when no legacy root file exists.\n migrateLegacyVersionFile(targetDir);\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 // Re-read state AFTER migration so a just-migrated project's recorded-original\n // hashes (now at the hidden path) feed the comparison below.\n const managedFiles = getManagedFiles();\n const installed = readVersion(targetDir);\n const installedHashes = installed?.files ?? {};\n\n // Carry the project's gitignore profile forward across upgrades. Projects\n // inited before this field existed have no recorded profile → default shared,\n // which re-applies the long-standing state-file-only ignore (idempotent).\n const gitignoreProfile = installed?.gitignoreProfile ?? DEFAULT_GITIGNORE_PROFILE;\n applyGitignoreProfile(targetDir, gitignoreProfile);\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 // installedHashes are stored truncated; truncate the fresh hash to match.\n const originalHash = installedHashes[relPath];\n\n if (originalHash && truncateHash(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 ensureScriptExecutable(change.absolutePath);\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 ensureScriptExecutable(change.absolutePath);\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 ensureScriptExecutable(change.absolutePath);\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, gitignoreProfile);\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' | 'bugfix-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 // No matching brief slug → this is an area-level (bugfix) spec dir.\n // Reclassify it as a forced move into docs/bugfixes/<area>/.\n orphanSpecsDirs.push(subdir);\n const move: Move = {\n from: join(specsDir, subdir),\n to: join(projectDir, 'docs', 'bugfixes', subdir),\n kind: 'bugfix-dir',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\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","pkgVersion","mkdirSync"]}
|
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
DEFAULT_GITIGNORE_PROFILE,
|
|
3
4
|
LEGACY_VERSION_FILE,
|
|
4
5
|
STATE_PATH,
|
|
5
6
|
getLevel,
|
|
6
7
|
hashContent,
|
|
8
|
+
parseGitignoreProfile,
|
|
7
9
|
readVersion,
|
|
8
10
|
truncateHash,
|
|
9
11
|
writeVersion
|
|
10
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-PUYGEBLA.js";
|
|
11
13
|
export {
|
|
14
|
+
DEFAULT_GITIGNORE_PROFILE,
|
|
12
15
|
LEGACY_VERSION_FILE,
|
|
13
16
|
STATE_PATH,
|
|
14
17
|
getLevel,
|
|
15
18
|
hashContent,
|
|
19
|
+
parseGitignoreProfile,
|
|
16
20
|
readVersion,
|
|
17
21
|
truncateHash,
|
|
18
22
|
writeVersion
|
|
19
23
|
};
|
|
20
|
-
//# sourceMappingURL=version-
|
|
24
|
+
//# sourceMappingURL=version-TG3D7QLM.js.map
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/version.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHash } from 'node:crypto';\n\n/**\n * Project-relative path to Joycraft's upgrade-state file.\n *\n * Hidden inside `.claude/` — the dir `init` always creates for every harness —\n * directly analogous to npm's own hidden lockfile at\n * `node_modules/.package-lock.json`. Never at the repo root, never committed\n * (init/upgrade gitignore it). The old root location was `.joycraft-version`\n * (see LEGACY_VERSION_FILE); `upgrade` migrates it on first run.\n */\nexport const STATE_PATH = join('.claude', '.joycraft', 'state.json');\n\n/** The pre-relocation root path. Kept only so `upgrade` can migrate it. */\nexport const LEGACY_VERSION_FILE = '.joycraft-version';\n\n/**\n * Length we truncate stored hashes to. Full SHA-256 is 64 hex chars; 16 hex\n * (64 bits) is ample to detect customization across ~100 managed files and\n * shrinks the state ~4×. Compare truncated-on-both-sides (see upgrade.ts).\n */\nconst HASH_LENGTH = 16;\n\nexport interface VersionInfo {\n version: string;\n files: Record<string, string>;\n}\n\nexport function hashContent(content: string): string {\n return createHash('sha256').update(content).digest('hex');\n}\n\n/** Truncate a (full) content hash to the stored length. Idempotent for already-short input. */\nexport function truncateHash(hash: string): string {\n return hash.slice(0, HASH_LENGTH);\n}\n\nexport function readVersion(dir: string): VersionInfo | null {\n const filePath = join(dir, STATE_PATH);\n if (!existsSync(filePath)) return null;\n try {\n const raw = readFileSync(filePath, 'utf-8');\n const parsed = JSON.parse(raw);\n if (typeof parsed.version === 'string' && typeof parsed.files === 'object') {\n return parsed as VersionInfo;\n }\n return null;\n } catch {\n return null;\n }\n}\n\nexport function writeVersion(dir: string, version: string, files: Record<string, string>): void {\n const filePath = join(dir, STATE_PATH);\n // Store truncated hashes — single source of truth for the on-disk shape.\n const truncated: Record<string, string> = {};\n for (const [path, hash] of Object.entries(files)) {\n truncated[path] = truncateHash(hash);\n }\n const data: VersionInfo = { version, files: truncated };\n mkdirSync(dirname(filePath), { recursive: true });\n writeFileSync(filePath, JSON.stringify(data, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Detect the current Joycraft harness level for a project directory.\n * Returns 5 if Level 5 artifacts (autofix workflow + External Validation) are present, 4 otherwise.\n */\nexport function getLevel(dir: string): number {\n const hasAutofix = existsSync(join(dir, '.github', 'workflows', 'autofix.yml'));\n if (!hasAutofix) return 4;\n const claudeMdPath = join(dir, 'CLAUDE.md');\n if (!existsSync(claudeMdPath)) return 4;\n const content = readFileSync(claudeMdPath, 'utf-8');\n return content.includes('## External Validation') ? 5 : 4;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,eAAe,YAAY,iBAAiB;AACnE,SAAS,MAAM,eAAe;AAC9B,SAAS,kBAAkB;AAWpB,IAAM,aAAa,KAAK,WAAW,aAAa,YAAY;AAG5D,IAAM,sBAAsB;AAOnC,IAAM,cAAc;AAOb,SAAS,YAAY,SAAyB;AACnD,SAAO,WAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1D;AAGO,SAAS,aAAa,MAAsB;AACjD,SAAO,KAAK,MAAM,GAAG,WAAW;AAClC;AAEO,SAAS,YAAY,KAAiC;AAC3D,QAAM,WAAW,KAAK,KAAK,UAAU;AACrC,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAClC,MAAI;AACF,UAAM,MAAM,aAAa,UAAU,OAAO;AAC1C,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,OAAO,OAAO,YAAY,YAAY,OAAO,OAAO,UAAU,UAAU;AAC1E,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,aAAa,KAAa,SAAiB,OAAqC;AAC9F,QAAM,WAAW,KAAK,KAAK,UAAU;AAErC,QAAM,YAAoC,CAAC;AAC3C,aAAW,CAAC,MAAM,IAAI,KAAK,OAAO,QAAQ,KAAK,GAAG;AAChD,cAAU,IAAI,IAAI,aAAa,IAAI;AAAA,EACrC;AACA,QAAM,OAAoB,EAAE,SAAS,OAAO,UAAU;AACtD,YAAU,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAChD,gBAAc,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,OAAO;AACvE;AAMO,SAAS,SAAS,KAAqB;AAC5C,QAAM,aAAa,WAAW,KAAK,KAAK,WAAW,aAAa,aAAa,CAAC;AAC9E,MAAI,CAAC,WAAY,QAAO;AACxB,QAAM,eAAe,KAAK,KAAK,WAAW;AAC1C,MAAI,CAAC,WAAW,YAAY,EAAG,QAAO;AACtC,QAAM,UAAU,aAAa,cAAc,OAAO;AAClD,SAAO,QAAQ,SAAS,wBAAwB,IAAI,IAAI;AAC1D;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/gitignore.ts","../src/package-version.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Append-only, create-if-absent, idempotent .gitignore writer.\n *\n * Mirrors the \"append over modify when touching user files\" principle: it never\n * rewrites, reorders, or removes existing lines — it only adds `line` if no\n * existing line matches it exactly (after trimming). Returns true if it wrote.\n *\n * Used by both `init` (to gitignore the relocated state file) and `upgrade`'s\n * legacy-migration step (so migrated projects also stop committing the state).\n */\nexport function ensureGitignoreEntry(targetDir: string, line: string): boolean {\n const gitignorePath = join(targetDir, '.gitignore');\n\n if (!existsSync(gitignorePath)) {\n writeFileSync(gitignorePath, line + '\\n', 'utf-8');\n return true;\n }\n\n const current = readFileSync(gitignorePath, 'utf-8');\n const already = current.split('\\n').some((l) => l.trim() === line.trim());\n if (already) return false;\n\n // Append on its own line, tolerating a file that may or may not end in \\n.\n const sep = current.length > 0 && !current.endsWith('\\n') ? '\\n' : '';\n writeFileSync(gitignorePath, current + sep + line + '\\n', 'utf-8');\n return true;\n}\n","import { readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nexport function getPackageVersion(): string {\n const pkgPath = join(__dirname, '..', 'package.json');\n const raw = readFileSync(pkgPath, 'utf-8');\n const pkg = JSON.parse(raw) as { version?: unknown };\n if (typeof pkg.version !== 'string' || pkg.version.length === 0) {\n throw new Error(`Joycraft package.json at ${pkgPath} is missing a version field`);\n }\n return pkg.version;\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,YAAY;AAYd,SAAS,qBAAqB,WAAmB,MAAuB;AAC7E,QAAM,gBAAgB,KAAK,WAAW,YAAY;AAElD,MAAI,CAAC,WAAW,aAAa,GAAG;AAC9B,kBAAc,eAAe,OAAO,MAAM,OAAO;AACjD,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,aAAa,eAAe,OAAO;AACnD,QAAM,UAAU,QAAQ,MAAM,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,MAAM,KAAK,KAAK,CAAC;AACxE,MAAI,QAAS,QAAO;AAGpB,QAAM,MAAM,QAAQ,SAAS,KAAK,CAAC,QAAQ,SAAS,IAAI,IAAI,OAAO;AACnE,gBAAc,eAAe,UAAU,MAAM,OAAO,MAAM,OAAO;AACjE,SAAO;AACT;;;AC7BA,SAAS,gBAAAA,qBAAoB;AAC7B,SAAS,qBAAqB;AAC9B,SAAS,SAAS,QAAAC,aAAY;AAE9B,IAAM,YAAY,QAAQ,cAAc,YAAY,GAAG,CAAC;AAEjD,SAAS,oBAA4B;AAC1C,QAAM,UAAUA,MAAK,WAAW,MAAM,cAAc;AACpD,QAAM,MAAMD,cAAa,SAAS,OAAO;AACzC,QAAM,MAAM,KAAK,MAAM,GAAG;AAC1B,MAAI,OAAO,IAAI,YAAY,YAAY,IAAI,QAAQ,WAAW,GAAG;AAC/D,UAAM,IAAI,MAAM,4BAA4B,OAAO,6BAA6B;AAAA,EAClF;AACA,SAAO,IAAI;AACb;","names":["readFileSync","join"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/init.ts","../src/detect.ts","../src/improve-claude-md.ts","../src/agents-md.ts","../src/permissions.ts","../src/safeguard.ts"],"sourcesContent":["import { mkdirSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync, chmodSync } from 'node:fs';\nimport { join, basename, resolve, dirname } from 'node:path';\nimport { detectStack } from './detect.js';\nimport { generateCLAUDEMd } from './improve-claude-md.js';\nimport { generateAgentsMd } from './agents-md.js';\nimport { generatePermissions } from './permissions.js';\nimport { installSafeguardHooks } from './safeguard.js';\nimport { SKILLS, TEMPLATES, CODEX_SKILLS, PI_SKILLS, PI_SCRIPTS, PI_EXTENSIONS, PI_AGENTS } from './bundled-files.js';\nimport { writeVersion, hashContent, STATE_PATH } from './version.js';\nimport { ensureGitignoreEntry } from './gitignore.js';\nimport { getPackageVersion } from './package-version.js';\n\nexport interface InitOptions {\n force: boolean;\n}\n\ninterface InitResult {\n created: string[];\n skipped: string[];\n modified: string[];\n warnings: string[];\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\nfunction writeFile(path: string, content: string, force: boolean, result: InitResult): void {\n if (existsSync(path) && !force) {\n result.skipped.push(path);\n return;\n }\n writeFileSync(path, content, 'utf-8');\n result.created.push(path);\n}\n\nexport async function init(dir: string, opts: InitOptions): Promise<void> {\n const targetDir = resolve(dir);\n const result: InitResult = { created: [], skipped: [], modified: [], warnings: [] };\n\n // Detect stack\n const stack = await detectStack(targetDir);\n\n // Pi detection — check if project uses Pi coding agent\n const isPi = existsSync(join(targetDir, '.pi'));\n\n // 1. Create the only Joycraft-managed docs/ subdirectory: context/.\n // All other folders (briefs/specs/discoveries/decisions/contracts/features/backlog/...) are\n // lazy-created by the skills that write to them. Solo-first: no preemptive ceremony.\n ensureDir(join(targetDir, 'docs', 'context'));\n\n // 1b. Scan for existing non-Joycraft skills before copying ours\n const skillsDir = join(targetDir, '.claude', 'skills');\n let existingSkills: string[] = [];\n if (existsSync(skillsDir)) {\n existingSkills = readdirSync(skillsDir)\n .filter(name => {\n if (name.startsWith('joycraft-')) return false;\n if (name.startsWith('.')) return false;\n const fullPath = join(skillsDir, name);\n try {\n return statSync(fullPath).isDirectory();\n } catch {\n return false;\n }\n });\n }\n\n // 2. Copy skill files to .claude/skills/<name>/SKILL.md\n for (const [filename, content] of Object.entries(SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(skillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2b. Copy Codex skill files to .agents/skills/<name>/SKILL.md\n const codexSkillsDir = join(targetDir, '.agents', 'skills');\n let existingCodexSkills: string[] = [];\n if (existsSync(codexSkillsDir)) {\n existingCodexSkills = readdirSync(codexSkillsDir)\n .filter(name => {\n if (name.startsWith('joycraft-')) return false;\n if (name.startsWith('.')) return false;\n const fullPath = join(codexSkillsDir, name);\n try {\n return statSync(fullPath).isDirectory();\n } catch {\n return false;\n }\n });\n }\n for (const [filename, content] of Object.entries(CODEX_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(codexSkillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2c. Copy Pi skill files to .pi/skills/<name>/SKILL.md\n const piSkillsDir = join(targetDir, '.pi', 'skills');\n for (const [filename, content] of Object.entries(PI_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n const skillDir = join(piSkillsDir, skillName);\n ensureDir(skillDir);\n writeFile(join(skillDir, 'SKILL.md'), content, opts.force, result);\n }\n\n // 2d. Install Pi pipeline runtime scripts to .pi/scripts/joycraft/\n const piScriptsDir = join(targetDir, '.pi', 'scripts', 'joycraft');\n ensureDir(piScriptsDir);\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n const scriptPath = join(piScriptsDir, name);\n writeFile(scriptPath, content, opts.force, result);\n if (name !== 'README.md') {\n try { chmodSync(scriptPath, 0o755); } catch { /* non-fatal */ }\n }\n }\n\n // 2e. Install Pi extension to .pi/extensions/\n const piExtDir = join(targetDir, '.pi', 'extensions');\n ensureDir(piExtDir);\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n writeFile(join(piExtDir, name), content, opts.force, result);\n }\n\n // 2f. Install Pi subagent definitions to .pi/agents/\n const piAgentsDir = join(targetDir, '.pi', 'agents');\n ensureDir(piAgentsDir);\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n writeFile(join(piAgentsDir, name), content, opts.force, result);\n }\n\n // 3. Copy template files to docs/templates/\n const templatesDir = join(targetDir, 'docs', 'templates');\n ensureDir(templatesDir);\n for (const [filename, content] of Object.entries(TEMPLATES)) {\n ensureDir(dirname(join(templatesDir, filename)));\n writeFile(join(templatesDir, filename), content, opts.force, result);\n }\n\n // 4. Handle CLAUDE.md — only create if missing, never modify existing (unless --force)\n const claudeMdPath = join(targetDir, 'CLAUDE.md');\n if (existsSync(claudeMdPath) && !opts.force) {\n result.skipped.push(claudeMdPath);\n } else {\n const projectName = basename(targetDir);\n const content = generateCLAUDEMd(projectName, stack, existingSkills);\n writeFileSync(claudeMdPath, content, 'utf-8');\n result.created.push(claudeMdPath);\n }\n\n // 5. Handle AGENTS.md — only create if missing, never modify existing (unless --force)\n const agentsMdPath = join(targetDir, 'AGENTS.md');\n if (existsSync(agentsMdPath) && !opts.force) {\n result.skipped.push(agentsMdPath);\n } else {\n const projectName = basename(targetDir);\n const content = generateAgentsMd(projectName, stack);\n writeFileSync(agentsMdPath, content, 'utf-8');\n result.created.push(agentsMdPath);\n }\n\n // 6. Write the hidden state (.claude/.joycraft/state.json) with hashes of all managed files\n const fileHashes: Record<string, string> = {};\n for (const [filename, content] of Object.entries(SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.claude', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(CODEX_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.agents', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(TEMPLATES)) {\n fileHashes[join('docs', 'templates', filename)] = hashContent(content);\n }\n for (const [filename, content] of Object.entries(PI_SKILLS)) {\n const skillName = filename.replace(/\\.md$/, '');\n fileHashes[join('.pi', 'skills', skillName, 'SKILL.md')] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n fileHashes[join('.pi', 'scripts', 'joycraft', name)] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n fileHashes[join('.pi', 'extensions', name)] = hashContent(content);\n }\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n fileHashes[join('.pi', 'agents', name)] = hashContent(content);\n }\n writeVersion(targetDir, getPackageVersion(), fileHashes);\n\n // 6b. Gitignore the hidden state file. Like npm's node_modules/.package-lock.json,\n // it is tool-managed state that should never land in the user's commits.\n // Append-only + create-if-absent + idempotent (never clobbers existing entries).\n ensureGitignoreEntry(targetDir, STATE_PATH);\n\n // 7. Install version check hook\n const hooksDir = join(targetDir, '.claude', 'hooks');\n ensureDir(hooksDir);\n const hookScript = `// Joycraft version check — runs on Claude Code session start\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\ntry {\n const data = JSON.parse(readFileSync(join(process.cwd(), '${STATE_PATH.split(/[\\\\/]/).join(\"', '\")}'), 'utf-8'));\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const latest = (await res.json()).version;\n if (data.version !== latest) console.log('Joycraft ' + latest + ' available (you have ' + data.version + '). Run: npm install -g joycraft');\n }\n} catch {}\n`;\n writeFile(join(hooksDir, 'joycraft-version-check.mjs'), hookScript, opts.force, result);\n\n // Update .claude/settings.json with SessionStart hook\n const settingsPath = join(targetDir, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n let settingsMalformed = false;\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n settingsMalformed = true;\n result.warnings.push(\n 'settings.json exists but is malformed — skipping settings merge to protect your config.\\n' +\n ' Fix the JSON in .claude/settings.json and re-run init.'\n );\n }\n }\n if (!settingsMalformed) {\n if (!settings.hooks) settings.hooks = {};\n const hooksConfig = settings.hooks as Record<string, unknown>;\n if (!hooksConfig.SessionStart) hooksConfig.SessionStart = [];\n const sessionStartHooks = hooksConfig.SessionStart as Array<Record<string, unknown>>;\n const hasJoycraftHook = sessionStartHooks.some(h => {\n const innerHooks = h.hooks as Array<Record<string, unknown>> | undefined;\n return innerHooks?.some(ih => typeof ih.command === 'string' && ih.command.includes('joycraft'));\n });\n if (!hasJoycraftHook) {\n sessionStartHooks.push({\n matcher: '',\n hooks: [{\n type: 'command',\n command: 'node .claude/hooks/joycraft-version-check.mjs',\n }],\n });\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n result.created.push(settingsPath);\n }\n\n // 8. Generate and merge permission rules into settings.json\n const permissions = generatePermissions(stack);\n // Re-read settings in case it was just created by hook step\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n result.warnings.push(\n 'settings.json became unreadable after hook merge — skipping permissions merge.\\n' +\n ' Fix the JSON in .claude/settings.json and re-run init.'\n );\n settingsMalformed = true;\n }\n }\n if (!settingsMalformed) {\n if (!settings.permissions) settings.permissions = {};\n const perms = settings.permissions as Record<string, string[]>;\n if (!perms.allow) perms.allow = [];\n if (!perms.deny) perms.deny = [];\n for (const rule of permissions.allow) {\n if (!perms.allow.includes(rule)) perms.allow.push(rule);\n }\n for (const rule of permissions.deny) {\n if (!perms.deny.includes(rule)) perms.deny.push(rule);\n }\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n }\n }\n\n // 9. Install safeguard hooks (PreToolUse deny-pattern blocking)\n const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);\n result.created.push(...hookResult.created);\n result.skipped.push(...hookResult.skipped);\n\n // 10. Check .gitignore for .claude/ exclusion\n const gitignorePath = join(targetDir, '.gitignore');\n if (existsSync(gitignorePath)) {\n const gitignore = readFileSync(gitignorePath, 'utf-8');\n if (/^\\.claude\\/?$/m.test(gitignore) || /^\\.claude\\/\\*$/m.test(gitignore)) {\n result.warnings.push(\n '.claude/ is in your .gitignore — teammates won\\'t get Joycraft skills.\\n' +\n ' Add this line to .gitignore to fix: !.claude/skills/'\n );\n }\n }\n\n // 11. Print summary\n printSummary(result, stack, existingSkills, isPi);\n}\n\nfunction printSummary(result: InitResult, stack: import('./detect.js').StackInfo, existingSkills: string[] = [], isPi: boolean = false): void {\n console.log('\\nJoycraft initialized!\\n');\n\n if (stack.language !== 'unknown') {\n const fw = stack.framework ? ` + ${stack.framework}` : '';\n console.log(` Detected stack: ${stack.language}${fw} (${stack.packageManager})`);\n } else {\n console.log(' Detected stack: unknown (no recognized manifest found)');\n }\n\n if (isPi) {\n console.log(' Detected agent: Pi');\n }\n\n if (result.created.length > 0) {\n console.log(`\\n Created ${result.created.length} file(s):`);\n for (const f of result.created) {\n console.log(` + ${f}`);\n }\n }\n\n if (result.modified.length > 0) {\n console.log(`\\n Modified ${result.modified.length} file(s):`);\n for (const f of result.modified) {\n console.log(` ~ ${f}`);\n }\n }\n\n if (result.skipped.length > 0) {\n console.log(`\\n Skipped ${result.skipped.length} file(s) (already exist, use --force to overwrite):`);\n for (const f of result.skipped) {\n console.log(` - ${f}`);\n }\n }\n\n if (result.warnings.length > 0) {\n console.log('\\n Warnings:');\n for (const w of result.warnings) {\n console.log(` ⚠ ${w}`);\n }\n }\n\n if (existingSkills.length > 0) {\n console.log(`\\n Found existing skills: ${existingSkills.join(', ')}. These are preserved — Joycraft is additive.`);\n }\n\n const hasExistingClaude = result.skipped.some(f => f.endsWith('CLAUDE.md'));\n\n console.log('\\n Next steps:');\n console.log(' 1. Run Claude Code and try /joycraft-setup — the first-run door that sets up and assesses your project');\n if (hasExistingClaude) {\n console.log(' (it routes to /joycraft-tune to assess and improve your existing CLAUDE.md)');\n } else {\n console.log(' (then review and customize the generated CLAUDE.md for your project)');\n }\n console.log(' 2. Try /joycraft-new-feature to start building with the spec-driven workflow');\n console.log(' (feature artifacts are written to docs/features/<slug>/ as you go)');\n console.log(' 3. Commit .claude/skills/ and docs/ so your team gets the same workflow');\n if (!isPi) {\n console.log(' Pi: Skills installed to .pi/skills/. Use /skill:joycraft-* to invoke.');\n }\n console.log('');\n}\n","import { readFileSync, existsSync, readdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface TestingStrategy {\n backbone: 'playwright' | 'maestro' | 'api' | 'native';\n testFormat: string;\n layers: ('ui' | 'api' | 'logic' | 'static')[];\n}\n\nexport interface StackInfo {\n language: string;\n packageManager: string;\n commands: {\n build?: string;\n test?: string;\n lint?: string;\n typecheck?: string;\n deploy?: string;\n };\n framework?: string;\n testingStrategy?: TestingStrategy;\n}\n\nconst WEB_FRAMEWORKS = new Set(['Next.js', 'Nuxt', 'Remix', 'React', 'Vue', 'Svelte']);\nconst API_FRAMEWORKS = new Set(['Express', 'Fastify', 'FastAPI', 'Django', 'Flask', 'Gin', 'Fiber', 'Echo', 'Actix', 'Axum', 'Rocket']);\n\nfunction buildTestingStrategy(backbone: TestingStrategy['backbone']): TestingStrategy {\n switch (backbone) {\n case 'playwright':\n return { backbone, testFormat: '.spec.ts', layers: ['ui', 'api', 'logic', 'static'] };\n case 'maestro':\n return { backbone, testFormat: '.yaml', layers: ['ui', 'api', 'logic', 'static'] };\n case 'api':\n return { backbone, testFormat: '.test.ts', layers: ['api', 'logic', 'static'] };\n case 'native':\n return { backbone, testFormat: '.test.ts', layers: ['logic', 'static'] };\n }\n}\n\nfunction readFile(path: string): string | null {\n try {\n return readFileSync(path, 'utf-8');\n } catch {\n return null;\n }\n}\n\nfunction detectNodeFramework(pkg: { dependencies?: Record<string, string>; devDependencies?: Record<string, string> }): string | undefined {\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (allDeps['next']) return 'Next.js';\n if (allDeps['nuxt']) return 'Nuxt';\n if (allDeps['@remix-run/node'] || allDeps['@remix-run/react']) return 'Remix';\n if (allDeps['express']) return 'Express';\n if (allDeps['fastify']) return 'Fastify';\n if (allDeps['react']) return 'React';\n if (allDeps['vue']) return 'Vue';\n if (allDeps['svelte']) return 'Svelte';\n return undefined;\n}\n\nfunction detectNodeTestFramework(pkg: { devDependencies?: Record<string, string>; dependencies?: Record<string, string> }): string | undefined {\n const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (allDeps['vitest']) return 'vitest';\n if (allDeps['jest']) return 'jest';\n if (allDeps['mocha']) return 'mocha';\n return undefined;\n}\n\nfunction detectNodePackageManager(dir: string): string {\n if (existsSync(join(dir, 'pnpm-lock.yaml'))) return 'pnpm';\n if (existsSync(join(dir, 'yarn.lock'))) return 'yarn';\n if (existsSync(join(dir, 'bun.lockb')) || existsSync(join(dir, 'bun.lock'))) return 'bun';\n return 'npm';\n}\n\nfunction detectNode(dir: string): StackInfo | null {\n const raw = readFile(join(dir, 'package.json'));\n if (raw === null) return null;\n\n let pkg: Record<string, unknown>;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return null;\n }\n\n const pm = detectNodePackageManager(dir);\n const run = pm === 'npm' ? 'npm run' : pm;\n const scripts = (pkg.scripts ?? {}) as Record<string, string>;\n const framework = detectNodeFramework(pkg as { dependencies?: Record<string, string>; devDependencies?: Record<string, string> });\n const testFramework = detectNodeTestFramework(pkg as { devDependencies?: Record<string, string>; dependencies?: Record<string, string> });\n\n const commands: StackInfo['commands'] = {};\n if (scripts.build) commands.build = `${run} build`;\n else commands.build = `${run} build`;\n if (scripts.test) commands.test = `${run} test`;\n else if (testFramework) commands.test = `${run} test`;\n else commands.test = `${pm === 'npm' ? 'npm' : pm} test`;\n if (scripts.lint) commands.lint = `${run} lint`;\n if (scripts.typecheck) commands.typecheck = `${run} typecheck`;\n else if ((pkg.devDependencies as Record<string, string> | undefined)?.['typescript']) {\n commands.typecheck = 'tsc --noEmit';\n }\n\n const allDeps = { ...(pkg.dependencies as Record<string, string> | undefined), ...(pkg.devDependencies as Record<string, string> | undefined) };\n let testingStrategy: TestingStrategy;\n if (allDeps['react-native']) {\n testingStrategy = buildTestingStrategy('maestro');\n } else if (framework && WEB_FRAMEWORKS.has(framework)) {\n testingStrategy = buildTestingStrategy('playwright');\n } else if (framework && API_FRAMEWORKS.has(framework)) {\n testingStrategy = buildTestingStrategy('api');\n } else {\n testingStrategy = buildTestingStrategy('native');\n }\n\n return {\n language: 'node',\n packageManager: pm,\n commands,\n framework,\n testingStrategy,\n };\n}\n\nfunction detectPythonFramework(content: string): string | undefined {\n if (/fastapi/i.test(content)) return 'FastAPI';\n if (/django/i.test(content)) return 'Django';\n if (/flask/i.test(content)) return 'Flask';\n return undefined;\n}\n\nfunction detectPython(dir: string): StackInfo | null {\n const pyproject = readFile(join(dir, 'pyproject.toml'));\n if (pyproject !== null) {\n const isPoetry = /\\[tool\\.poetry\\]/.test(pyproject);\n const isUv = existsSync(join(dir, 'uv.lock'));\n\n let pm: string;\n let run: string;\n if (isUv) {\n pm = 'uv';\n run = 'uv run';\n } else if (isPoetry) {\n pm = 'poetry';\n run = 'poetry run';\n } else {\n pm = 'pip';\n run = 'python -m';\n }\n\n const framework = detectPythonFramework(pyproject);\n const hasPytest = /pytest/i.test(pyproject);\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'python',\n packageManager: pm,\n commands: {\n build: `${pm === 'poetry' ? 'poetry' : pm} build`,\n test: hasPytest ? `${run} pytest` : `${run} pytest`,\n lint: `${run} ruff check .`,\n },\n framework,\n testingStrategy,\n };\n }\n\n const requirements = readFile(join(dir, 'requirements.txt'));\n if (requirements !== null) {\n const framework = detectPythonFramework(requirements);\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'python',\n packageManager: 'pip',\n commands: {\n build: 'pip install -e .',\n test: 'python -m pytest',\n lint: 'python -m ruff check .',\n },\n framework,\n testingStrategy,\n };\n }\n\n return null;\n}\n\nfunction detectRust(dir: string): StackInfo | null {\n const cargo = readFile(join(dir, 'Cargo.toml'));\n if (cargo === null) return null;\n\n let framework: string | undefined;\n if (/actix-web/.test(cargo)) framework = 'Actix';\n else if (/axum/.test(cargo)) framework = 'Axum';\n else if (/rocket/.test(cargo)) framework = 'Rocket';\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'rust',\n packageManager: 'cargo',\n commands: {\n build: 'cargo build',\n test: 'cargo test',\n lint: 'cargo clippy',\n },\n framework,\n testingStrategy,\n };\n}\n\nfunction detectGo(dir: string): StackInfo | null {\n const gomod = readFile(join(dir, 'go.mod'));\n if (gomod === null) return null;\n\n let framework: string | undefined;\n if (/github\\.com\\/gin-gonic\\/gin/.test(gomod)) framework = 'Gin';\n else if (/github\\.com\\/gofiber\\/fiber/.test(gomod)) framework = 'Fiber';\n else if (/github\\.com\\/labstack\\/echo/.test(gomod)) framework = 'Echo';\n\n const testingStrategy = framework && API_FRAMEWORKS.has(framework)\n ? buildTestingStrategy('api')\n : buildTestingStrategy('native');\n\n return {\n language: 'go',\n packageManager: 'go',\n commands: {\n build: 'go build ./...',\n test: 'go test ./...',\n lint: 'golangci-lint run',\n },\n framework,\n testingStrategy,\n };\n}\n\nfunction detectSwift(dir: string): StackInfo | null {\n const pkg = readFile(join(dir, 'Package.swift'));\n if (pkg === null) return null;\n\n return {\n language: 'swift',\n packageManager: 'swift',\n commands: {\n build: 'swift build',\n test: 'swift test',\n },\n testingStrategy: buildTestingStrategy('native'),\n };\n}\n\nfunction detectFlutter(dir: string): StackInfo | null {\n const pubspec = readFile(join(dir, 'pubspec.yaml'));\n if (pubspec === null) return null;\n if (!/flutter/i.test(pubspec)) return null;\n\n return {\n language: 'dart',\n packageManager: 'flutter',\n commands: {\n build: 'flutter build',\n test: 'flutter test',\n lint: 'flutter analyze',\n },\n framework: 'Flutter',\n testingStrategy: buildTestingStrategy('maestro'),\n };\n}\n\nfunction detectXcode(dir: string): StackInfo | null {\n // Check for .xcodeproj or .xcworkspace directories\n try {\n const entries = readdirSync(dir);\n const hasXcode = entries.some(e => e.endsWith('.xcodeproj') || e.endsWith('.xcworkspace'));\n if (!hasXcode) return null;\n } catch {\n return null;\n }\n\n return {\n language: 'swift',\n packageManager: 'xcode',\n commands: {\n build: 'xcodebuild build',\n test: 'xcodebuild test',\n },\n testingStrategy: buildTestingStrategy('maestro'),\n };\n}\n\nfunction detectMakefile(dir: string): StackInfo | null {\n const makefile = readFile(join(dir, 'Makefile'));\n if (makefile === null) return null;\n\n const commands: StackInfo['commands'] = {};\n commands.build = 'make build';\n if (/^test:/m.test(makefile)) commands.test = 'make test';\n if (/^lint:/m.test(makefile)) commands.lint = 'make lint';\n\n return {\n language: 'unknown',\n packageManager: 'make',\n commands,\n };\n}\n\nfunction detectDockerfile(dir: string): StackInfo | null {\n if (!existsSync(join(dir, 'Dockerfile'))) return null;\n\n return {\n language: 'unknown',\n packageManager: 'docker',\n commands: {\n build: 'docker build .',\n },\n };\n}\n\nexport async function detectStack(dir: string): Promise<StackInfo> {\n const detectors = [\n detectNode,\n detectPython,\n detectRust,\n detectGo,\n detectFlutter,\n detectSwift,\n detectXcode,\n detectMakefile,\n detectDockerfile,\n ];\n\n for (const detect of detectors) {\n const result = detect(dir);\n if (result) return result;\n }\n\n return { language: 'unknown', packageManager: '', commands: {} };\n}\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { StackInfo } from './detect.js';\n\nexport interface ImproveOptions {\n projectDir?: string;\n}\n\ninterface Section {\n header: string;\n content: string;\n}\n\nfunction parseSections(markdown: string): Section[] {\n const lines = markdown.split('\\n');\n const sections: Section[] = [];\n let currentHeader = '';\n let currentLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n currentHeader = line;\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n // Push the last section\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n\n return sections;\n}\n\nfunction hasSection(sections: Section[], pattern: RegExp): boolean {\n return sections.some(s => pattern.test(s.header));\n}\n\nfunction generateCommandsBlock(stack: StackInfo): string {\n const lines: string[] = ['```bash'];\n if (stack.commands.build) lines.push(`# Build\\n${stack.commands.build}`);\n if (stack.commands.test) lines.push(`# Test\\n${stack.commands.test}`);\n if (stack.commands.lint) lines.push(`# Lint\\n${stack.commands.lint}`);\n if (stack.commands.typecheck) lines.push(`# Type check\\n${stack.commands.typecheck}`);\n if (stack.commands.deploy) lines.push(`# Deploy\\n${stack.commands.deploy}`);\n lines.push('```');\n return lines.join('\\n');\n}\n\nexport function generateBoundariesSection(): string {\n return `## Behavioral Boundaries\n\n### ALWAYS\n- Run tests and type-check before committing\n- Run tests before implementing new features -- confirm they fail first, then implement until they pass\n- Use \\`verb: concise message\\` format for commits\n- Commit after completing each discrete task (atomic commits)\n- Stage specific files by name (not \\`git add -A\\` or \\`git add .\\`)\n- Read \\`docs/context/\\` before making infrastructure or config changes\n- Follow existing code patterns and style\n\n### ASK FIRST\n- Pushing to remote\n- Creating or merging pull requests\n- Adding new dependencies\n- Modifying database schema or data models\n- Changing authentication or authorization flows\n- Any destructive git operation (force-push, reset --hard, branch deletion)\n\n### NEVER\n- Push directly to main/master without approval\n- Commit .env files, secrets, or credentials\n- Use --no-verify to skip hooks\n- Amend commits that have been pushed\n- Skip type-checking or linting\n- Commit code that doesn't build`;\n}\n\nfunction generateWorkflowSection(stack: StackInfo): string {\n return `## Development Workflow\n\n${generateCommandsBlock(stack)}\n\n**Default execution mode:** batch\n\n_How \\`/joycraft-implement\\` wraps up after each spec. \\`joycraft-decompose\\` reads this line (absent ⇒ \\`batch\\`) and recommends a per-spec mode you approve. Modes: \\`batch\\` (implement a cluster, wrap once at the end), \\`checkpoint\\` (commit + status bump after each spec), \\`isolated\\` (fresh context per spec — on Pi, the \\`joycraft-implement-loop\\` driver). Change the value above to set your project default._`;\n}\n\nfunction generateArchitectureSection(): string {\n return `## Architecture\n\n_TODO: Add a brief description of your project's architecture and key directories._`;\n}\n\nfunction generateKeyFilesSection(): string {\n return `## Key Files\n\n| File | Purpose |\n|------|---------|\n| _TODO_ | _Add key files and their purposes_ |`;\n}\n\nfunction generateGotchasSection(): string {\n return `## Common Gotchas\n\n_TODO: Add any gotchas, quirks, or non-obvious behaviors that developers should know about._`;\n}\n\nfunction generateGettingStartedSection(): string {\n return `## Getting Started with Joycraft\n\nThis project uses [Joycraft](https://github.com/maksutovic/joycraft) for AI development workflow. Available skills:\n\n| Skill | Purpose |\n|-------|---------|\n| \\`/joycraft-setup\\` | Start here — the first-run door; sets up and assesses your project |\n| \\`/joycraft-tune\\` | Assess your harness, apply upgrades, see path to Level 5 |\n| \\`/joycraft-new-feature\\` | Interview -> Feature Brief -> Atomic Specs |\n| \\`/joycraft-interview\\` | Lightweight brainstorm — yap about ideas, get a structured summary |\n| \\`/joycraft-decompose\\` | Break a brief into small, testable specs |\n| \\`/joycraft-session-end\\` | Capture discoveries, verify, commit |\n| \\`/joycraft-implement-level5\\` | Set up Level 5 — autofix loop, holdout scenarios, scenario evolution |\n\nRun \\`/joycraft-tune\\` to see where your project stands and what to improve next.`;\n}\n\nexport function generateContextMapSection(): string {\n return `## Context Map\n\nKeep this file lean — link out, don't inline. Long-form reference docs live in \\`docs/context/reference/\\`; this table points to what to read on demand.\n\n| Document | Read it when… |\n|----------|---------------|`;\n}\n\nfunction generateExternalValidationSection(): string {\n return `## External Validation\n\nThis project uses holdout scenario tests in a separate private repo.\n\n### NEVER\n- Access, read, or reference the scenarios repo\n- Mention scenario test names or contents\n- Modify the scenarios dispatch workflow to leak test information\n\nThe scenarios repo is deliberately invisible to you. This is the holdout guarantee — like a validation set in ML.`;\n}\n\nfunction generateAreasSection(): string {\n return `## Areas\n\nThis project organizes some work by area. When working on a specific area, read its README first; check for area-specific boundaries.\n\n- For each area: see \\`docs/areas/<area-name>/README.md\\`\n- Area-level boundaries (when present): \\`docs/areas/<area-name>/boundaries.md\\``;\n}\n\nfunction projectHasAreas(opts?: ImproveOptions): boolean {\n if (!opts?.projectDir) return false;\n return existsSync(join(opts.projectDir, 'docs', 'areas'));\n}\n\nfunction stripAreasSection(content: string): string {\n // Remove an existing \"## Areas\" section (header + body up to next \"## \" header or EOF).\n return content.replace(/\\n##\\s+Areas\\b[\\s\\S]*?(?=\\n##\\s|\\n*$)/, '').trimEnd() + '\\n';\n}\n\nfunction generateProjectToolsSection(existingSkills: string[]): string {\n const MAX_LISTED = 10;\n let skillList: string;\n if (existingSkills.length <= MAX_LISTED) {\n skillList = existingSkills.join(', ');\n } else {\n skillList = existingSkills.slice(0, MAX_LISTED).join(', ') +\n `, and ${existingSkills.length - MAX_LISTED} more — see .claude/skills/`;\n }\n return `## Project Tools\n\nThis project has additional tools beyond Joycraft. Always check \\`.claude/skills/\\` for available skills: ${skillList}`;\n}\n\nexport function improveCLAUDEMd(\n existing: string,\n stack: StackInfo,\n existingSkills: string[] = [],\n opts?: ImproveOptions,\n): string {\n // Areas pointer: idempotent in both directions.\n // Always strip an existing \"## Areas\" section first so we re-evaluate cleanly.\n let working = stripAreasSection(existing);\n const sections = parseSections(working);\n const additions: string[] = [];\n\n if (!hasSection(sections, /behavioral\\s*boundar/i)) {\n additions.push(generateBoundariesSection());\n }\n\n if (!hasSection(sections, /development\\s*workflow/i) && !hasSection(sections, /workflow/i)) {\n additions.push(generateWorkflowSection(stack));\n }\n\n if (!hasSection(sections, /architecture/i)) {\n additions.push(generateArchitectureSection());\n }\n\n if (!hasSection(sections, /key\\s*files/i)) {\n additions.push(generateKeyFilesSection());\n }\n\n if (!hasSection(sections, /common\\s*gotchas/i) && !hasSection(sections, /gotchas/i)) {\n additions.push(generateGotchasSection());\n }\n\n if (!hasSection(sections, /getting\\s*started.*joycraft/i) && !hasSection(sections, /joycraft.*skills/i)) {\n additions.push(generateGettingStartedSection());\n }\n\n if (!hasSection(sections, /context\\s*map/i)) {\n additions.push(generateContextMapSection());\n }\n\n if (!hasSection(sections, /external\\s*validation/i)) {\n additions.push(generateExternalValidationSection());\n }\n\n if (existingSkills.length > 0 && !hasSection(sections, /project\\s*tools/i)) {\n additions.push(generateProjectToolsSection(existingSkills));\n }\n\n if (projectHasAreas(opts)) {\n additions.push(generateAreasSection());\n }\n\n if (additions.length === 0) {\n return working === existing ? existing : working;\n }\n\n const trimmed = working.trimEnd();\n return trimmed + '\\n\\n' + additions.join('\\n\\n') + '\\n';\n}\n\nexport function generateCLAUDEMd(\n projectName: string,\n stack: StackInfo,\n existingSkills: string[] = [],\n opts?: ImproveOptions,\n): string {\n const frameworkNote = stack.framework ? ` (${stack.framework})` : '';\n const langLabel = stack.language === 'unknown' ? '' : ` | **Stack:** ${stack.language}${frameworkNote}`;\n\n const lines: string[] = [\n `# ${projectName}`,\n '',\n `**Component:** _TODO: describe what this project is_${langLabel}`,\n '',\n '---',\n '',\n generateBoundariesSection(),\n '',\n generateWorkflowSection(stack),\n '',\n generateArchitectureSection(),\n '',\n generateKeyFilesSection(),\n '',\n generateGotchasSection(),\n '',\n generateContextMapSection(),\n '',\n generateGettingStartedSection(),\n '',\n ];\n\n if (existingSkills.length > 0) {\n lines.push(generateProjectToolsSection(existingSkills), '');\n }\n\n if (projectHasAreas(opts)) {\n lines.push(generateAreasSection(), '');\n }\n\n return lines.join('\\n');\n}\n","import type { StackInfo } from './detect.js';\nimport { generateBoundariesSection } from './improve-claude-md.js';\n\ninterface Section {\n header: string;\n content: string;\n}\n\nfunction parseSections(markdown: string): Section[] {\n const lines = markdown.split('\\n');\n const sections: Section[] = [];\n let currentHeader = '';\n let currentLines: string[] = [];\n\n for (const line of lines) {\n if (line.startsWith('## ')) {\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n currentHeader = line;\n currentLines = [];\n } else {\n currentLines.push(line);\n }\n }\n\n if (currentHeader || currentLines.length > 0) {\n sections.push({ header: currentHeader, content: currentLines.join('\\n') });\n }\n\n return sections;\n}\n\nfunction hasSection(sections: Section[], pattern: RegExp): boolean {\n return sections.some(s => pattern.test(s.header));\n}\n\nfunction generateCommandsBlock(stack: StackInfo): string {\n const lines: string[] = ['```bash'];\n if (stack.commands.build) lines.push(stack.commands.build);\n if (stack.commands.test) lines.push(stack.commands.test);\n if (stack.commands.lint) lines.push(stack.commands.lint);\n if (stack.commands.typecheck) lines.push(stack.commands.typecheck);\n if (stack.commands.deploy) lines.push(stack.commands.deploy);\n lines.push('```');\n return lines.join('\\n');\n}\n\nfunction generateExternalApiSafetySection(): string {\n return '### External API Safety\\n- Read official docs and type definitions before writing code against a third-party SDK\\n- Add third-party SDKs as devDependencies so typecheck runs against real types, not stubs\\n- Critical integration paths should have a smoke test that validates against the real runtime';\n}\n\nfunction generateDevelopmentSection(stack: StackInfo): string {\n return `## Development\\n\\n${generateCommandsBlock(stack)}`;\n}\n\nfunction generateArchitectureSection(): string {\n return `## Architecture\\n\\n_TODO: Add a compact directory tree and one-paragraph summary._`;\n}\n\nfunction generateKeyFilesSection(): string {\n return `## Key Files\\n\\n| File | Purpose |\\n|------|---------|\\n| _TODO_ | _Add key files_ |`;\n}\n\nexport function generateAgentsMd(projectName: string, stack: StackInfo): string {\n const frameworkNote = stack.framework ? ` (${stack.framework})` : '';\n const langLabel = stack.language === 'unknown' ? '' : ` | **Stack:** ${stack.language}${frameworkNote}`;\n\n const lines: string[] = [\n `# ${projectName}`,\n '',\n `**Component:** _TODO: describe what this project is_${langLabel}`,\n '',\n '> Auto-generated by Joycraft.',\n '',\n '---',\n '',\n generateBoundariesSection(),\n generateExternalApiSafetySection(),\n '',\n generateArchitectureSection(),\n '',\n generateKeyFilesSection(),\n '',\n generateDevelopmentSection(stack),\n '',\n ];\n\n return lines.join('\\n');\n}\n\nexport function improveAgentsMd(existing: string, stack: StackInfo): string {\n const sections = parseSections(existing);\n const additions: string[] = [];\n\n if (!hasSection(sections, /behavioral\\s*boundar/i)) {\n additions.push(generateBoundariesSection());\n }\n\n if (!/external\\s*api\\s*safety/i.test(existing)) {\n additions.push('### External API Safety\\n- Read official docs and type definitions before writing code against a third-party SDK\\n- Add third-party SDKs as devDependencies so typecheck runs against real types, not stubs\\n- Critical integration paths should have a smoke test that validates against the real runtime');\n }\n\n if (!hasSection(sections, /architecture/i)) {\n additions.push(generateArchitectureSection());\n }\n\n if (!hasSection(sections, /key\\s*files/i)) {\n additions.push(generateKeyFilesSection());\n }\n\n if (!hasSection(sections, /development/i)) {\n additions.push(generateDevelopmentSection(stack));\n }\n\n if (additions.length === 0) {\n return existing;\n }\n\n const trimmed = existing.trimEnd();\n return trimmed + '\\n\\n' + additions.join('\\n\\n') + '\\n';\n}\n","import type { StackInfo } from './detect.js';\n\nexport interface PermissionRules {\n allow: string[];\n deny: string[];\n}\n\nexport function generatePermissions(stack: StackInfo): PermissionRules {\n // Default deny rules for ALL projects\n const deny: string[] = [\n 'Bash(rm -rf *)',\n 'Bash(git push --force *)',\n 'Bash(git push -f *)',\n 'Bash(git reset --hard *)',\n 'Edit(//.env*)',\n 'Edit(//*.pem)',\n 'Edit(//*.key)',\n 'Edit(//.git/**)',\n ];\n\n // Default allow rules\n const allow: string[] = [\n 'Bash(git status)',\n 'Bash(git diff *)',\n 'Bash(git log *)',\n 'Bash(git add *)',\n 'Bash(git commit *)',\n 'Bash(git checkout *)',\n 'Bash(git branch *)',\n ];\n\n // Stack-specific rules\n if (stack.language === 'node') {\n allow.push(`Bash(${stack.packageManager} *)`);\n if (stack.packageManager !== 'npm') deny.push('Bash(npm install *)');\n if (stack.packageManager !== 'yarn') deny.push('Bash(yarn add *)');\n if (stack.packageManager !== 'pnpm') deny.push('Bash(pnpm add *)');\n if (stack.packageManager !== 'bun') deny.push('Bash(bun add *)');\n if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);\n if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);\n if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);\n if (stack.commands.typecheck) allow.push(`Bash(${stack.commands.typecheck})`);\n }\n\n if (stack.language === 'python') {\n allow.push(`Bash(${stack.packageManager} *)`);\n if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);\n if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);\n if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);\n }\n\n if (stack.language === 'rust') {\n allow.push('Bash(cargo *)');\n }\n\n if (stack.language === 'go') {\n allow.push('Bash(go *)');\n }\n\n if (stack.language === 'swift') {\n allow.push('Bash(swift *)');\n allow.push('Bash(xcodebuild *)');\n }\n\n return { allow, deny };\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';\nimport { join } from 'node:path';\n\nexport interface SafeguardConfig {\n denyPatterns: string[];\n}\n\n/**\n * Generate default deny patterns from common dangerous operations.\n */\nexport function getDefaultDenyPatterns(): string[] {\n return [\n 'rm\\\\s+-rf\\\\s+/', // rm -rf with absolute path\n 'rm\\\\s+-rf\\\\s+\\\\.', // rm -rf . or ./\n 'git\\\\s+push\\\\s+--force', // force push\n 'git\\\\s+push\\\\s+-f\\\\b', // force push shorthand\n 'git\\\\s+reset\\\\s+--hard', // hard reset\n 'DROP\\\\s+TABLE', // SQL drop\n 'DROP\\\\s+DATABASE', // SQL drop\n 'TRUNCATE', // SQL truncate\n 'chmod\\\\s+777', // wide-open permissions\n 'curl.*\\\\|.*sh', // pipe curl to shell\n 'wget.*\\\\|.*sh', // pipe wget to shell\n ];\n}\n\n/**\n * Generate the hook script that blocks dangerous Bash commands.\n */\nexport function generateHookScript(): string {\n return `#!/bin/bash\n# Joycraft Safeguard — PreToolUse hook\n# Blocks dangerous Bash commands. Exit 2 = block the action.\n# Edit deny-patterns.txt to customize what's blocked.\n\nTOOL_NAME=\"$1\"\n# Read the full tool input from stdin\nINPUT=$(cat)\n\n# Only check Bash commands\nif [ \"$TOOL_NAME\" != \"Bash\" ]; then\n exit 0\nfi\n\n# Extract the command from the JSON input\nCOMMAND=$(echo \"$INPUT\" | grep -o '\"command\":\"[^\"]*\"' | head -1 | sed 's/\"command\":\"//;s/\"$//')\n\nif [ -z \"$COMMAND\" ]; then\n exit 0\nfi\n\nPATTERNS_FILE=\"$(dirname \"$0\")/deny-patterns.txt\"\n\nif [ ! -f \"$PATTERNS_FILE\" ]; then\n exit 0\nfi\n\nwhile IFS= read -r pattern || [ -n \"$pattern\" ]; do\n # Skip empty lines and comments\n [ -z \"$pattern\" ] && continue\n [[ \"$pattern\" == \\\\#* ]] && continue\n\n if echo \"$COMMAND\" | grep -qEi \"$pattern\"; then\n echo \"Blocked by Joycraft Safeguard: command matches deny pattern '$pattern'\"\n echo \"Edit .claude/hooks/joycraft/deny-patterns.txt to modify blocked patterns.\"\n exit 2\n fi\ndone < \"$PATTERNS_FILE\"\n\nexit 0\n`;\n}\n\n/**\n * Generate deny-patterns.txt content from default patterns + custom patterns.\n */\nexport function generateDenyPatternsFile(customPatterns: string[] = []): string {\n const lines = [\n '# Joycraft Safeguard — Deny Patterns',\n '# One regex pattern per line. Lines starting with # are comments.',\n '# Commands matching any pattern will be blocked (exit 2).',\n '# Edit this file to customize what\\'s blocked.',\n '',\n '# Destructive file operations',\n 'rm\\\\s+-rf\\\\s+/',\n 'rm\\\\s+-rf\\\\s+\\\\.',\n '',\n '# Dangerous git operations',\n 'git\\\\s+push\\\\s+--force',\n 'git\\\\s+push\\\\s+-f\\\\b',\n 'git\\\\s+reset\\\\s+--hard',\n '',\n '# SQL destruction',\n 'DROP\\\\s+TABLE',\n 'DROP\\\\s+DATABASE',\n 'TRUNCATE',\n '',\n '# System security',\n 'chmod\\\\s+777',\n 'curl.*\\\\|.*sh',\n 'wget.*\\\\|.*sh',\n ];\n\n if (customPatterns.length > 0) {\n lines.push('');\n lines.push('# Project-specific patterns (from risk interview)');\n for (const p of customPatterns) {\n lines.push(p);\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\n/**\n * Install safeguard hooks into a project.\n */\nexport function installSafeguardHooks(\n targetDir: string,\n customPatterns: string[] = [],\n force: boolean = false,\n skipSettingsMerge: boolean = false\n): { created: string[]; skipped: string[] } {\n const result = { created: [] as string[], skipped: [] as string[] };\n const hooksDir = join(targetDir, '.claude', 'hooks', 'joycraft');\n\n if (!existsSync(hooksDir)) {\n mkdirSync(hooksDir, { recursive: true });\n }\n\n // Write hook script\n const hookPath = join(hooksDir, 'block-dangerous.sh');\n if (!existsSync(hookPath) || force) {\n writeFileSync(hookPath, generateHookScript(), { mode: 0o755 });\n result.created.push(hookPath);\n } else {\n result.skipped.push(hookPath);\n }\n\n // Write deny patterns\n const patternsPath = join(hooksDir, 'deny-patterns.txt');\n if (!existsSync(patternsPath) || force) {\n writeFileSync(patternsPath, generateDenyPatternsFile(customPatterns));\n result.created.push(patternsPath);\n } else {\n result.skipped.push(patternsPath);\n }\n\n // Register hook in settings.json (skip if caller detected malformed JSON)\n if (!skipSettingsMerge) {\n const settingsPath = join(targetDir, '.claude', 'settings.json');\n let settings: Record<string, unknown> = {};\n if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n // Don't overwrite — caller should handle the warning\n return result;\n }\n }\n\n if (!settings.hooks) settings.hooks = {};\n const hooks = settings.hooks as Record<string, unknown[]>;\n if (!hooks.PreToolUse) hooks.PreToolUse = [];\n\n const preToolUse = hooks.PreToolUse as Array<Record<string, unknown>>;\n const hasJoycraftHook = preToolUse.some(h => {\n const innerHooks = h.hooks as Array<Record<string, unknown>> | undefined;\n return innerHooks?.some(ih => typeof ih.command === 'string' && ih.command.includes('joycraft'));\n });\n\n if (!hasJoycraftHook) {\n preToolUse.push({\n matcher: 'Bash',\n hooks: [{\n type: 'command',\n command: '.claude/hooks/joycraft/block-dangerous.sh',\n }],\n });\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n }\n }\n\n return result;\n}\n"],"mappings":"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","names":["mkdirSync","existsSync","writeFileSync","readFileSync","readdirSync","join","existsSync","join","existsSync","join","generateCommandsBlock","generateArchitectureSection","generateKeyFilesSection","existsSync","readFileSync","join","join","existsSync","readFileSync","existsSync","mkdirSync","writeFileSync","join","readdirSync","readFileSync"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/upgrade.ts","../src/migration.ts"],"sourcesContent":["import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, chmodSync } from 'node:fs';\nimport { join, dirname, resolve } from 'node:path';\nimport { createInterface } from 'node:readline';\nimport { readVersion, writeVersion, hashContent, truncateHash, STATE_PATH, LEGACY_VERSION_FILE } from './version.js';\nimport { ensureGitignoreEntry } from './gitignore.js';\nimport { SKILLS, TEMPLATES, CODEX_SKILLS, PI_SKILLS, PI_SCRIPTS, PI_EXTENSIONS, PI_AGENTS } from './bundled-files.js';\nimport { getPackageVersion } from './package-version.js';\nimport { planMigration, applyMigration, type MigrationPlan } from './migration.js';\n\nfunction isStaleVersion(current: string, latest: string): boolean {\n const currentParts = current.split('.').map(Number);\n const latestParts = latest.split('.').map(Number);\n const len = Math.max(currentParts.length, latestParts.length);\n for (let i = 0; i < len; i++) {\n const c = currentParts[i] ?? 0;\n const l = latestParts[i] ?? 0;\n if (c < l) return true;\n if (c > l) return false;\n }\n return false;\n}\n\nasync function checkCliVersion(): Promise<{ stale: boolean; latest?: string }> {\n try {\n const pkgVersion = getPackageVersion();\n const res = await fetch('https://registry.npmjs.org/joycraft/latest', {\n signal: AbortSignal.timeout(3000)\n });\n if (!res.ok) return { stale: false };\n const data = (await res.json()) as { version: string };\n if (isStaleVersion(pkgVersion, data.version)) {\n return { stale: true, latest: data.version };\n }\n } catch {\n // Silent fallback on network errors or missing version\n }\n return { stale: false };\n}\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 for (const [name, content] of Object.entries(PI_SKILLS)) {\n const skillName = name.replace(/\\.md$/, '');\n files[join('.pi', 'skills', skillName, 'SKILL.md')] = content;\n }\n for (const [name, content] of Object.entries(PI_SCRIPTS)) {\n files[join('.pi', 'scripts', 'joycraft', name)] = content;\n }\n for (const [name, content] of Object.entries(PI_EXTENSIONS)) {\n files[join('.pi', 'extensions', name)] = content;\n }\n for (const [name, content] of Object.entries(PI_AGENTS)) {\n files[join('.pi', 'agents', 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 // 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\n/**\n * Self-heal projects inited by an older Joycraft that wrote state to the repo\n * root (`.joycraft-version`). Reads the legacy file, re-writes it to the hidden\n * `.claude/.joycraft/state.json` location, deletes the root file, and ensures\n * the gitignore entry. No-op when no legacy root file exists.\n *\n * Runs BEFORE the managed-file diff so the recorded-original hashes are\n * available at the new location for the same run's 3-way comparison. The hidden\n * state's own write truncates the (possibly full-length legacy) hashes, so the\n * comparison stays consistent — see the truncateHash() call in the diff loop.\n */\nfunction migrateLegacyVersionFile(targetDir: string): boolean {\n const legacyPath = join(targetDir, LEGACY_VERSION_FILE);\n if (!existsSync(legacyPath)) return false;\n\n let parsed: { version?: unknown; files?: unknown };\n try {\n parsed = JSON.parse(readFileSync(legacyPath, 'utf-8'));\n } catch {\n // Corrupt legacy file: treat as no usable baseline. Remove it so the root\n // stops being polluted; upgrade then proceeds with no recorded-original\n // (every changed file becomes \"customized\" — safe, never silently wrong).\n rmSync(legacyPath, { force: true });\n ensureGitignoreEntry(targetDir, STATE_PATH);\n return true;\n }\n\n const version = typeof parsed.version === 'string' ? parsed.version : getPackageVersion();\n const files =\n parsed.files && typeof parsed.files === 'object'\n ? (parsed.files as Record<string, string>)\n : {};\n\n // writeVersion targets the new hidden path and truncates the hashes.\n writeVersion(targetDir, version, files);\n rmSync(legacyPath, { force: true });\n ensureGitignoreEntry(targetDir, STATE_PATH);\n return true;\n}\n\nfunction countLines(content: string): number {\n return content.split('\\n').length;\n}\n\nfunction ensureScriptExecutable(absolutePath: string): void {\n // Joycraft shell scripts in .pi/scripts/joycraft/ must be executable.\n // README.md is the only file in that directory that should stay 644.\n if (absolutePath.includes('.pi/scripts/joycraft/') && !absolutePath.endsWith('README.md')) {\n try {\n chmodSync(absolutePath, 0o755);\n } catch {\n // non-fatal — permissions may be restricted\n }\n }\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 const relTo = (p: string) => (p.startsWith(projectDir) ? p.slice(projectDir.length + 1) : p);\n\n // Group per-feature moves by feature folder for readability.\n // Bugfix-area moves are listed separately below.\n const featureMoves = plan.moves.filter(m => m.kind !== 'bugfix-dir');\n const bugfixMoves = plan.moves.filter(m => m.kind === 'bugfix-dir');\n\n const bySlug = new Map<string, typeof plan.moves>();\n for (const move of featureMoves) {\n const parts = relTo(move.to).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 console.log(` ${relTo(move.from)} → ${relTo(move.to)}`);\n }\n }\n\n if (bugfixMoves.length > 0) {\n console.log('');\n console.log(' Migrating bugfix areas:');\n for (const move of bugfixMoves) {\n console.log(` ${relTo(move.from)} → ${relTo(move.to)}`);\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 // Guard: if the CLI itself is out of date, warn and bail before comparing\n // project files against stale bundled content.\n const cliCheck = await checkCliVersion();\n if (cliCheck.stale) {\n const pkgVersion = getPackageVersion();\n console.log(`Joycraft CLI is out of date (you have ${pkgVersion}, latest is ${cliCheck.latest}).`);\n console.log('Update with: npm install -g joycraft');\n console.log('Then re-run: npx joycraft upgrade');\n return;\n }\n\n // Check if project was initialized. A project is \"initialized\" if it has the\n // hidden state, OR a known skill, OR a legacy root state file (pre-relocation).\n const hasLegacyState = existsSync(join(targetDir, LEGACY_VERSION_FILE));\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 (!readVersion(targetDir) && !hasLegacyState && !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 // Relocate any legacy repo-root .joycraft-version → hidden state, BEFORE the\n // diff loop so the migrated recorded-original is used on this same run. No-op\n // when no legacy root file exists.\n migrateLegacyVersionFile(targetDir);\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 // Re-read state AFTER migration so a just-migrated project's recorded-original\n // hashes (now at the hidden path) feed the comparison below.\n const managedFiles = getManagedFiles();\n const installedHashes = readVersion(targetDir)?.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 // installedHashes are stored truncated; truncate the fresh hash to match.\n const originalHash = installedHashes[relPath];\n\n if (originalHash && truncateHash(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 ensureScriptExecutable(change.absolutePath);\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 ensureScriptExecutable(change.absolutePath);\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 ensureScriptExecutable(change.absolutePath);\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' | 'bugfix-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 // No matching brief slug → this is an area-level (bugfix) spec dir.\n // Reclassify it as a forced move into docs/bugfixes/<area>/.\n orphanSpecsDirs.push(subdir);\n const move: Move = {\n from: join(specsDir, subdir),\n to: join(projectDir, 'docs', 'bugfixes', subdir),\n kind: 'bugfix-dir',\n };\n if (existsSync(move.to)) skipped.push(move);\n else moves.push(move);\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","pkgVersion","mkdirSync"]}
|
|
File without changes
|
|
File without changes
|