joycraft 0.6.15 → 0.6.16
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 +31 -13
- package/dist/{chunk-YE4LWG2O.js → chunk-G6WSFZQG.js} +2 -2
- package/dist/chunk-G6WSFZQG.js.map +1 -0
- package/dist/{chunk-XOMQIK4U.js → chunk-UEG5IO6Q.js} +6 -184
- package/dist/chunk-UEG5IO6Q.js.map +1 -0
- package/dist/cli.js +4 -4
- package/dist/{init-LXSMLAY5.js → init-WPKDBQDN.js} +141 -55
- package/dist/init-WPKDBQDN.js.map +1 -0
- package/dist/{init-autofix-OZW5ITFI.js → init-autofix-ESN27L3W.js} +5 -3
- package/dist/{init-autofix-OZW5ITFI.js.map → init-autofix-ESN27L3W.js.map} +1 -1
- package/dist/{upgrade-P3JZS7NM.js → upgrade-LKX25GTT.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-XOMQIK4U.js.map +0 -1
- package/dist/chunk-YE4LWG2O.js.map +0 -1
- package/dist/init-LXSMLAY5.js.map +0 -1
- /package/dist/{upgrade-P3JZS7NM.js.map → upgrade-LKX25GTT.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PRIVATE_DIRS_DISPLAY
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-G6WSFZQG.js";
|
|
5
5
|
import "./chunk-34IWIKXS.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
@@ -15,7 +15,7 @@ var GITIGNORE_OPTION_DESC = `Gitignore profile: 'shared' (commit skills) or 'pri
|
|
|
15
15
|
var program = new Command();
|
|
16
16
|
program.name("joycraft").description("Scaffold and upgrade AI development harnesses").version(pkg.version, "-v, --version");
|
|
17
17
|
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_OPTION_DESC).action(async (dir, opts) => {
|
|
18
|
-
const { init } = await import("./init-
|
|
18
|
+
const { init } = await import("./init-WPKDBQDN.js");
|
|
19
19
|
try {
|
|
20
20
|
await init(dir, { force: opts.force ?? false, gitignore: opts.gitignore });
|
|
21
21
|
} catch (err) {
|
|
@@ -24,7 +24,7 @@ program.command("init").description("Scaffold the Joycraft harness into the curr
|
|
|
24
24
|
}
|
|
25
25
|
});
|
|
26
26
|
program.command("upgrade").description("Upgrade installed Joycraft templates and skills to latest").argument("[dir]", "Target directory", ".").option("--yes", "Auto-accept all updates").option("--gitignore <profile>", GITIGNORE_OPTION_DESC).action(async (dir, opts) => {
|
|
27
|
-
const { upgrade } = await import("./upgrade-
|
|
27
|
+
const { upgrade } = await import("./upgrade-LKX25GTT.js");
|
|
28
28
|
try {
|
|
29
29
|
await upgrade(dir, { yes: opts.yes ?? false, gitignore: opts.gitignore });
|
|
30
30
|
} catch (err) {
|
|
@@ -33,7 +33,7 @@ program.command("upgrade").description("Upgrade installed Joycraft templates and
|
|
|
33
33
|
}
|
|
34
34
|
});
|
|
35
35
|
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) => {
|
|
36
|
-
const { initAutofix } = await import("./init-autofix-
|
|
36
|
+
const { initAutofix } = await import("./init-autofix-ESN27L3W.js");
|
|
37
37
|
await initAutofix(dir, opts);
|
|
38
38
|
});
|
|
39
39
|
program.command("check-version").description("Check if a newer version of Joycraft is available").action(async () => {
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
PRIVATE_UNTRACK_COMMAND,
|
|
8
8
|
applyGitignoreProfile,
|
|
9
9
|
resolveGitignoreProfile
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-G6WSFZQG.js";
|
|
11
11
|
import {
|
|
12
12
|
CODEX_SKILLS,
|
|
13
13
|
PI_AGENTS,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
PI_SKILLS,
|
|
17
17
|
SKILLS,
|
|
18
18
|
TEMPLATES
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-UEG5IO6Q.js";
|
|
20
20
|
import {
|
|
21
21
|
DEFAULT_GITIGNORE_PROFILE,
|
|
22
22
|
STATE_PATH,
|
|
@@ -27,8 +27,8 @@ import {
|
|
|
27
27
|
} from "./chunk-34IWIKXS.js";
|
|
28
28
|
|
|
29
29
|
// src/init.ts
|
|
30
|
-
import { mkdirSync as mkdirSync2, existsSync as
|
|
31
|
-
import { join as
|
|
30
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync, chmodSync } from "fs";
|
|
31
|
+
import { join as join5, basename, resolve, dirname } from "path";
|
|
32
32
|
|
|
33
33
|
// src/detect.ts
|
|
34
34
|
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
@@ -713,25 +713,100 @@ function installSafeguardHooks(targetDir, customPatterns = [], force = false, sk
|
|
|
713
713
|
return result;
|
|
714
714
|
}
|
|
715
715
|
|
|
716
|
+
// src/tsconfig.ts
|
|
717
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
718
|
+
import { join as join4 } from "path";
|
|
719
|
+
var PI_EXCLUDE = ".pi";
|
|
720
|
+
function stripJsonComments(text) {
|
|
721
|
+
return text.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
722
|
+
}
|
|
723
|
+
function alreadyExcludesPi(rawText) {
|
|
724
|
+
try {
|
|
725
|
+
const parsed = JSON.parse(stripJsonComments(rawText));
|
|
726
|
+
if (!Array.isArray(parsed.exclude)) return false;
|
|
727
|
+
return parsed.exclude.some(
|
|
728
|
+
(e) => typeof e === "string" && (e === PI_EXCLUDE || e === "./.pi" || e === ".pi/**")
|
|
729
|
+
);
|
|
730
|
+
} catch {
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
function insertPiExclude(rawText) {
|
|
735
|
+
const excludeArrayRe = /("exclude"\s*:\s*\[)(\s*)/;
|
|
736
|
+
if (excludeArrayRe.test(rawText)) {
|
|
737
|
+
return rawText.replace(excludeArrayRe, (_m, open, ws) => {
|
|
738
|
+
const sep = ws.includes("\n") ? ws : " ";
|
|
739
|
+
return `${open}${sep}"${PI_EXCLUDE}",${ws.includes("\n") ? "" : " "}`;
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
const includeArrayRe = /("include"\s*:\s*\[[^\]]*\])/;
|
|
743
|
+
if (includeArrayRe.test(rawText)) {
|
|
744
|
+
return rawText.replace(includeArrayRe, (m) => `${m},
|
|
745
|
+
"exclude": ["${PI_EXCLUDE}"]`);
|
|
746
|
+
}
|
|
747
|
+
const lastBrace = rawText.lastIndexOf("}");
|
|
748
|
+
if (lastBrace > 0 && rawText.slice(0, lastBrace).includes(":")) {
|
|
749
|
+
const before = rawText.slice(0, lastBrace).replace(/\s*$/, "");
|
|
750
|
+
const after = rawText.slice(lastBrace);
|
|
751
|
+
const needsComma = !before.endsWith(",") && !before.endsWith("{");
|
|
752
|
+
return `${before}${needsComma ? "," : ""}
|
|
753
|
+
"exclude": ["${PI_EXCLUDE}"]
|
|
754
|
+
${after}`;
|
|
755
|
+
}
|
|
756
|
+
return null;
|
|
757
|
+
}
|
|
758
|
+
function ensurePiExcludedFromTsconfig(targetDir) {
|
|
759
|
+
const path = join4(targetDir, "tsconfig.json");
|
|
760
|
+
if (!existsSync4(path)) return { status: "no-tsconfig" };
|
|
761
|
+
let rawText;
|
|
762
|
+
try {
|
|
763
|
+
rawText = readFileSync3(path, "utf-8");
|
|
764
|
+
} catch {
|
|
765
|
+
return { status: "skipped", reason: "tsconfig.json could not be read" };
|
|
766
|
+
}
|
|
767
|
+
if (alreadyExcludesPi(rawText)) {
|
|
768
|
+
return { status: "already-present", path };
|
|
769
|
+
}
|
|
770
|
+
const updated = insertPiExclude(rawText);
|
|
771
|
+
if (updated === null || updated === rawText) {
|
|
772
|
+
return {
|
|
773
|
+
status: "skipped",
|
|
774
|
+
reason: 'could not safely edit tsconfig.json \u2014 add ".pi" to its "exclude" array manually so the Pi extension stays out of your TypeScript build'
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
if (!alreadyExcludesPi(updated)) {
|
|
778
|
+
return {
|
|
779
|
+
status: "skipped",
|
|
780
|
+
reason: 'tsconfig.json edit could not be verified \u2014 add ".pi" to its "exclude" array manually'
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
try {
|
|
784
|
+
writeFileSync2(path, updated, "utf-8");
|
|
785
|
+
} catch {
|
|
786
|
+
return { status: "skipped", reason: "tsconfig.json could not be written" };
|
|
787
|
+
}
|
|
788
|
+
return { status: "added", path };
|
|
789
|
+
}
|
|
790
|
+
|
|
716
791
|
// src/init.ts
|
|
717
792
|
function ensureDir(dir) {
|
|
718
|
-
if (!
|
|
793
|
+
if (!existsSync5(dir)) {
|
|
719
794
|
mkdirSync2(dir, { recursive: true });
|
|
720
795
|
}
|
|
721
796
|
}
|
|
722
797
|
function writeFile(path, content, force, result) {
|
|
723
|
-
if (
|
|
798
|
+
if (existsSync5(path) && !force) {
|
|
724
799
|
result.skipped.push(path);
|
|
725
800
|
return;
|
|
726
801
|
}
|
|
727
|
-
|
|
802
|
+
writeFileSync3(path, content, "utf-8");
|
|
728
803
|
result.created.push(path);
|
|
729
804
|
}
|
|
730
805
|
async function init(dir, opts) {
|
|
731
806
|
const targetDir = resolve(dir);
|
|
732
807
|
const result = { created: [], skipped: [], modified: [], warnings: [] };
|
|
733
808
|
const stack = await detectStack(targetDir);
|
|
734
|
-
const isPi =
|
|
809
|
+
const isPi = existsSync5(join5(targetDir, ".pi"));
|
|
735
810
|
const harnesses = await resolveHarnesses(process.stdin.isTTY === true);
|
|
736
811
|
const wants = (h) => harnesses.includes(h);
|
|
737
812
|
if (harnesses.length === 0) {
|
|
@@ -746,14 +821,14 @@ async function init(dir, opts) {
|
|
|
746
821
|
interactive: process.stdin.isTTY === true,
|
|
747
822
|
promptIntro: "\nHow should Joycraft files be tracked in git?"
|
|
748
823
|
});
|
|
749
|
-
ensureDir(
|
|
750
|
-
const skillsDir =
|
|
824
|
+
ensureDir(join5(targetDir, "docs", "context"));
|
|
825
|
+
const skillsDir = join5(targetDir, ".claude", "skills");
|
|
751
826
|
let existingSkills = [];
|
|
752
|
-
if (wants("claude") &&
|
|
827
|
+
if (wants("claude") && existsSync5(skillsDir)) {
|
|
753
828
|
existingSkills = readdirSync2(skillsDir).filter((name) => {
|
|
754
829
|
if (name.startsWith("joycraft-")) return false;
|
|
755
830
|
if (name.startsWith(".")) return false;
|
|
756
|
-
const fullPath =
|
|
831
|
+
const fullPath = join5(skillsDir, name);
|
|
757
832
|
try {
|
|
758
833
|
return statSync(fullPath).isDirectory();
|
|
759
834
|
} catch {
|
|
@@ -764,32 +839,32 @@ async function init(dir, opts) {
|
|
|
764
839
|
if (wants("claude")) {
|
|
765
840
|
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
766
841
|
const skillName = filename.replace(/\.md$/, "");
|
|
767
|
-
const skillDir =
|
|
842
|
+
const skillDir = join5(skillsDir, skillName);
|
|
768
843
|
ensureDir(skillDir);
|
|
769
|
-
writeFile(
|
|
844
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
770
845
|
}
|
|
771
846
|
}
|
|
772
847
|
if (wants("codex")) {
|
|
773
|
-
const codexSkillsDir =
|
|
848
|
+
const codexSkillsDir = join5(targetDir, ".agents", "skills");
|
|
774
849
|
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
775
850
|
const skillName = filename.replace(/\.md$/, "");
|
|
776
|
-
const skillDir =
|
|
851
|
+
const skillDir = join5(codexSkillsDir, skillName);
|
|
777
852
|
ensureDir(skillDir);
|
|
778
|
-
writeFile(
|
|
853
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
779
854
|
}
|
|
780
855
|
}
|
|
781
856
|
if (wants("pi")) {
|
|
782
|
-
const piSkillsDir =
|
|
857
|
+
const piSkillsDir = join5(targetDir, ".pi", "skills");
|
|
783
858
|
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
784
859
|
const skillName = filename.replace(/\.md$/, "");
|
|
785
|
-
const skillDir =
|
|
860
|
+
const skillDir = join5(piSkillsDir, skillName);
|
|
786
861
|
ensureDir(skillDir);
|
|
787
|
-
writeFile(
|
|
862
|
+
writeFile(join5(skillDir, "SKILL.md"), content, opts.force, result);
|
|
788
863
|
}
|
|
789
|
-
const piScriptsDir =
|
|
864
|
+
const piScriptsDir = join5(targetDir, ".pi", "scripts", "joycraft");
|
|
790
865
|
ensureDir(piScriptsDir);
|
|
791
866
|
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
792
|
-
const scriptPath =
|
|
867
|
+
const scriptPath = join5(piScriptsDir, name);
|
|
793
868
|
writeFile(scriptPath, content, opts.force, result);
|
|
794
869
|
if (name !== "README.md") {
|
|
795
870
|
try {
|
|
@@ -798,78 +873,89 @@ async function init(dir, opts) {
|
|
|
798
873
|
}
|
|
799
874
|
}
|
|
800
875
|
}
|
|
801
|
-
const piExtDir =
|
|
876
|
+
const piExtDir = join5(targetDir, ".pi", "extensions");
|
|
802
877
|
ensureDir(piExtDir);
|
|
803
878
|
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
804
|
-
writeFile(
|
|
879
|
+
writeFile(join5(piExtDir, name), content, opts.force, result);
|
|
805
880
|
}
|
|
806
|
-
const piAgentsDir =
|
|
881
|
+
const piAgentsDir = join5(targetDir, ".pi", "agents");
|
|
807
882
|
ensureDir(piAgentsDir);
|
|
808
883
|
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
809
|
-
writeFile(
|
|
884
|
+
writeFile(join5(piAgentsDir, name), content, opts.force, result);
|
|
810
885
|
}
|
|
811
886
|
}
|
|
812
|
-
const templatesDir =
|
|
887
|
+
const templatesDir = join5(targetDir, "docs", "templates");
|
|
813
888
|
ensureDir(templatesDir);
|
|
814
889
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
815
|
-
ensureDir(dirname(
|
|
816
|
-
writeFile(
|
|
890
|
+
ensureDir(dirname(join5(templatesDir, filename)));
|
|
891
|
+
writeFile(join5(templatesDir, filename), content, opts.force, result);
|
|
817
892
|
}
|
|
818
|
-
const claudeMdPath =
|
|
819
|
-
if (
|
|
893
|
+
const claudeMdPath = join5(targetDir, "CLAUDE.md");
|
|
894
|
+
if (existsSync5(claudeMdPath) && !opts.force) {
|
|
820
895
|
result.skipped.push(claudeMdPath);
|
|
821
896
|
} else {
|
|
822
897
|
const projectName = basename(targetDir);
|
|
823
898
|
const content = generateCLAUDEMd(projectName, stack, existingSkills, {
|
|
824
899
|
privateProfile: gitignoreProfile === "private"
|
|
825
900
|
});
|
|
826
|
-
|
|
901
|
+
writeFileSync3(claudeMdPath, content, "utf-8");
|
|
827
902
|
result.created.push(claudeMdPath);
|
|
828
903
|
}
|
|
829
|
-
const agentsMdPath =
|
|
830
|
-
if (
|
|
904
|
+
const agentsMdPath = join5(targetDir, "AGENTS.md");
|
|
905
|
+
if (existsSync5(agentsMdPath) && !opts.force) {
|
|
831
906
|
result.skipped.push(agentsMdPath);
|
|
832
907
|
} else {
|
|
833
908
|
const projectName = basename(targetDir);
|
|
834
909
|
const content = generateAgentsMd(projectName, stack, gitignoreProfile === "private");
|
|
835
|
-
|
|
910
|
+
writeFileSync3(agentsMdPath, content, "utf-8");
|
|
836
911
|
result.created.push(agentsMdPath);
|
|
837
912
|
}
|
|
838
913
|
const fileHashes = {};
|
|
839
914
|
if (wants("claude")) {
|
|
840
915
|
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
841
916
|
const skillName = filename.replace(/\.md$/, "");
|
|
842
|
-
fileHashes[
|
|
917
|
+
fileHashes[join5(".claude", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
843
918
|
}
|
|
844
919
|
}
|
|
845
920
|
if (wants("codex")) {
|
|
846
921
|
for (const [filename, content] of Object.entries(CODEX_SKILLS)) {
|
|
847
922
|
const skillName = filename.replace(/\.md$/, "");
|
|
848
|
-
fileHashes[
|
|
923
|
+
fileHashes[join5(".agents", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
849
924
|
}
|
|
850
925
|
}
|
|
851
926
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
852
|
-
fileHashes[
|
|
927
|
+
fileHashes[join5("docs", "templates", filename)] = hashContent(content);
|
|
853
928
|
}
|
|
854
929
|
if (wants("pi")) {
|
|
855
930
|
for (const [filename, content] of Object.entries(PI_SKILLS)) {
|
|
856
931
|
const skillName = filename.replace(/\.md$/, "");
|
|
857
|
-
fileHashes[
|
|
932
|
+
fileHashes[join5(".pi", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
858
933
|
}
|
|
859
934
|
for (const [name, content] of Object.entries(PI_SCRIPTS)) {
|
|
860
|
-
fileHashes[
|
|
935
|
+
fileHashes[join5(".pi", "scripts", "joycraft", name)] = hashContent(content);
|
|
861
936
|
}
|
|
862
937
|
for (const [name, content] of Object.entries(PI_EXTENSIONS)) {
|
|
863
|
-
fileHashes[
|
|
938
|
+
fileHashes[join5(".pi", "extensions", name)] = hashContent(content);
|
|
864
939
|
}
|
|
865
940
|
for (const [name, content] of Object.entries(PI_AGENTS)) {
|
|
866
|
-
fileHashes[
|
|
941
|
+
fileHashes[join5(".pi", "agents", name)] = hashContent(content);
|
|
867
942
|
}
|
|
868
943
|
}
|
|
869
944
|
writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile, harnesses);
|
|
870
945
|
applyGitignoreProfile(targetDir, gitignoreProfile);
|
|
946
|
+
if (wants("pi")) {
|
|
947
|
+
const outcome = ensurePiExcludedFromTsconfig(targetDir);
|
|
948
|
+
if (outcome.status === "added") {
|
|
949
|
+
result.modified.push(outcome.path);
|
|
950
|
+
result.warnings.push(
|
|
951
|
+
'Added ".pi" to tsconfig.json "exclude" so the Pi extension stays out of your TypeScript build.'
|
|
952
|
+
);
|
|
953
|
+
} else if (outcome.status === "skipped") {
|
|
954
|
+
result.warnings.push(outcome.reason);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
871
957
|
if (wants("claude")) {
|
|
872
|
-
const hooksDir =
|
|
958
|
+
const hooksDir = join5(targetDir, ".claude", "hooks");
|
|
873
959
|
ensureDir(hooksDir);
|
|
874
960
|
const hookScript = `// Joycraft version check \u2014 runs on Claude Code session start
|
|
875
961
|
import { readFileSync } from 'node:fs';
|
|
@@ -883,13 +969,13 @@ try {
|
|
|
883
969
|
}
|
|
884
970
|
} catch {}
|
|
885
971
|
`;
|
|
886
|
-
writeFile(
|
|
887
|
-
const settingsPath =
|
|
972
|
+
writeFile(join5(hooksDir, "joycraft-version-check.mjs"), hookScript, opts.force, result);
|
|
973
|
+
const settingsPath = join5(targetDir, ".claude", "settings.json");
|
|
888
974
|
let settings = {};
|
|
889
975
|
let settingsMalformed = false;
|
|
890
|
-
if (
|
|
976
|
+
if (existsSync5(settingsPath)) {
|
|
891
977
|
try {
|
|
892
|
-
settings = JSON.parse(
|
|
978
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
893
979
|
} catch {
|
|
894
980
|
settingsMalformed = true;
|
|
895
981
|
result.warnings.push(
|
|
@@ -922,13 +1008,13 @@ try {
|
|
|
922
1008
|
});
|
|
923
1009
|
}
|
|
924
1010
|
if (!hasJoycraftHook || envMissing) {
|
|
925
|
-
|
|
1011
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
926
1012
|
if (!result.created.includes(settingsPath)) result.created.push(settingsPath);
|
|
927
1013
|
}
|
|
928
1014
|
const permissions = generatePermissions(stack);
|
|
929
|
-
if (
|
|
1015
|
+
if (existsSync5(settingsPath)) {
|
|
930
1016
|
try {
|
|
931
|
-
settings = JSON.parse(
|
|
1017
|
+
settings = JSON.parse(readFileSync4(settingsPath, "utf-8"));
|
|
932
1018
|
} catch {
|
|
933
1019
|
result.warnings.push(
|
|
934
1020
|
"settings.json became unreadable after hook merge \u2014 skipping permissions merge.\n Fix the JSON in .claude/settings.json and re-run init."
|
|
@@ -947,7 +1033,7 @@ try {
|
|
|
947
1033
|
for (const rule of permissions.deny) {
|
|
948
1034
|
if (!perms.deny.includes(rule)) perms.deny.push(rule);
|
|
949
1035
|
}
|
|
950
|
-
|
|
1036
|
+
writeFileSync3(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
951
1037
|
}
|
|
952
1038
|
}
|
|
953
1039
|
const hookResult = installSafeguardHooks(targetDir, [], opts.force, settingsMalformed);
|
|
@@ -955,9 +1041,9 @@ try {
|
|
|
955
1041
|
result.skipped.push(...hookResult.skipped);
|
|
956
1042
|
}
|
|
957
1043
|
if (gitignoreProfile === "shared" && wants("claude")) {
|
|
958
|
-
const gitignorePath =
|
|
959
|
-
if (
|
|
960
|
-
const gitignore =
|
|
1044
|
+
const gitignorePath = join5(targetDir, ".gitignore");
|
|
1045
|
+
if (existsSync5(gitignorePath)) {
|
|
1046
|
+
const gitignore = readFileSync4(gitignorePath, "utf-8");
|
|
961
1047
|
if (/^\.claude\/?$/m.test(gitignore) || /^\.claude\/\*$/m.test(gitignore)) {
|
|
962
1048
|
result.warnings.push(
|
|
963
1049
|
".claude/ is in your .gitignore \u2014 teammates won't get Joycraft skills.\n Add this line to .gitignore to fix: !.claude/skills/"
|
|
@@ -1041,4 +1127,4 @@ function printSummary(result, stack, existingSkills = [], isPi = false, gitignor
|
|
|
1041
1127
|
export {
|
|
1042
1128
|
init
|
|
1043
1129
|
};
|
|
1044
|
-
//# sourceMappingURL=init-
|
|
1130
|
+
//# sourceMappingURL=init-WPKDBQDN.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","../src/tsconfig.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 {\n writeVersion,\n readVersion,\n hashContent,\n STATE_PATH,\n DEFAULT_GITIGNORE_PROFILE,\n type GitignoreProfile,\n} from './version.js';\nimport {\n applyGitignoreProfile,\n resolveGitignoreProfile,\n PRIVATE_DIRS_DISPLAY,\n PRIVATE_UNTRACK_COMMAND,\n} from './gitignore.js';\nimport { getPackageVersion } from './package-version.js';\nimport { resolveHarnesses, type Harness } from './harness.js';\nimport { ensurePiExcludedFromTsconfig } from './tsconfig.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\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 which harnesses to install (interactive multi-select; non-interactive\n // installs all three). Done before any scaffolding so a \"none\" selection is a\n // clean no-op — and before the gitignore prompt so we don't ask a second\n // question when there's nothing to track.\n const harnesses = await resolveHarnesses(process.stdin.isTTY === true);\n const wants = (h: Harness): boolean => harnesses.includes(h);\n if (harnesses.length === 0) {\n console.log(\n '\\nNo harness selected — Joycraft will not install any skills.\\n' +\n 'Please run init again and select at least one harness (claude, codex, pi).'\n );\n return;\n }\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. Unlike upgrade, init persists even the non-interactive\n // default: init creates the state fresh, and `shared` is the documented\n // default for a first run.\n const { profile: gitignoreProfile } = await resolveGitignoreProfile({\n flag: opts.gitignore,\n persisted: readVersion(targetDir)?.gitignoreProfile,\n interactive: process.stdin.isTTY === true,\n promptIntro: '\\nHow should Joycraft files be tracked in git?',\n });\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 (claude only).\n const skillsDir = join(targetDir, '.claude', 'skills');\n let existingSkills: string[] = [];\n if (wants('claude') && 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 if (wants('claude')) {\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\n // 2b. Copy Codex skill files to .agents/skills/<name>/SKILL.md\n if (wants('codex')) {\n const codexSkillsDir = join(targetDir, '.agents', 'skills');\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\n // 2c–2f. Install the Pi harness: skills, runtime scripts, extension, subagents.\n if (wants('pi')) {\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 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 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 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\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 privateProfile: gitignoreProfile === 'private',\n });\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, gitignoreProfile === 'private');\n writeFileSync(agentsMdPath, content, 'utf-8');\n result.created.push(agentsMdPath);\n }\n\n // 6. Write the hidden state (docs/.joycraft/state.json) with hashes of the\n // files we actually installed. Only hash a harness's files when that harness\n // was selected — otherwise upgrade would later see the unwritten files as\n // missing/drifted. Templates are harness-agnostic and always installed.\n const fileHashes: Record<string, string> = {};\n if (wants('claude')) {\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 }\n if (wants('codex')) {\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 }\n for (const [filename, content] of Object.entries(TEMPLATES)) {\n fileHashes[join('docs', 'templates', filename)] = hashContent(content);\n }\n if (wants('pi')) {\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 }\n writeVersion(targetDir, getPackageVersion(), fileHashes, gitignoreProfile, harnesses);\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 // 6c. Pi only: keep the installed `.pi/extensions/*.ts` runtime out of the\n // user's TypeScript program. It imports a Pi-only package the project doesn't\n // depend on, so a default `**/*.ts` toolchain (e.g. create-next-app) would\n // fail `tsc`/build on it. Surgically add `.pi` to tsconfig exclude, idempotent,\n // and report transparently (never silently rewrites — see tsconfig.ts).\n if (wants('pi')) {\n const outcome = ensurePiExcludedFromTsconfig(targetDir);\n if (outcome.status === 'added') {\n result.modified.push(outcome.path);\n result.warnings.push(\n 'Added \".pi\" to tsconfig.json \"exclude\" so the Pi extension stays out of your TypeScript build.'\n );\n } else if (outcome.status === 'skipped') {\n result.warnings.push(outcome.reason);\n }\n }\n\n // Steps 7–9 configure the Claude Code harness (.claude/hooks + settings.json).\n // Skip entirely when claude isn't a selected harness — a codex/pi-only install\n // must not create a .claude/ tree.\n if (wants('claude')) {\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\n // Enable Claude Code agent teams (experimental) so skills like\n // /joycraft-research can fan out to subagents without the user having to\n // hand-edit settings.json. Idempotent: only set when absent — never clobber\n // an explicit user value.\n if (!settings.env) settings.env = {};\n const env = settings.env as Record<string, unknown>;\n const envMissing = !('CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS' in env);\n if (envMissing) {\n env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS = '1';\n }\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 }\n if (!hasJoycraftHook || envMissing) {\n writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n', 'utf-8');\n if (!result.created.includes(settingsPath)) 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 } // end if (wants('claude'))\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' && wants('claude')) {\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, harnesses);\n}\n\nfunction printSummary(result: InitResult, stack: import('./detect.js').StackInfo, existingSkills: string[] = [], isPi: boolean = false, gitignoreProfile: GitignoreProfile = DEFAULT_GITIGNORE_PROFILE, harnesses: Harness[] = []): void {\n console.log('\\nJoycraft initialized!\\n');\n\n if (harnesses.length > 0) {\n console.log(` Installed harnesses: ${harnesses.join(', ')}`);\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 (${PRIVATE_DIRS_DISPLAY} 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/ — ${PRIVATE_DIRS_DISPLAY} stay local (gitignored)`);\n console.log(` (if any harness files were already committed, run: ${PRIVATE_UNTRACK_COMMAND})`);\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 * When true, the project gitignores the harness dirs (`private` profile), so\n * teammates who clone won't get the skill files. A discreet setup note is\n * emitted telling them to run `npx joycraft init` to regenerate them locally.\n */\n privateProfile?: boolean;\n}\n\n/**\n * Stable phrase used to detect (and avoid duplicating) the private-mode setup\n * note across re-runs. Kept terse so it lives quietly in the Getting Started\n * footer rather than spending a heading.\n */\nexport const PRIVATE_SETUP_NOTE_MARKER = 'After cloning, run';\n\nexport function generatePrivateSetupNote(): string {\n return `> **Private setup:** The harness dirs (\\`.claude/\\`, \\`.agents/\\`, \\`.pi/\\`) are gitignored in this repo, so they aren't committed. ${PRIVATE_SETUP_NOTE_MARKER} \\`npx joycraft init\\` to regenerate the skill files locally.`;\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 // Private-mode setup note: independent of the Getting Started check above so\n // it gets added on a re-run even when Getting Started already exists. Matched\n // on its stable phrase, not a heading, so it's idempotent across upgrades.\n if (opts?.privateProfile && !existing.includes(PRIVATE_SETUP_NOTE_MARKER)) {\n additions.push(generatePrivateSetupNote());\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 (opts?.privateProfile) {\n lines.push(generatePrivateSetupNote(), '');\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 {\n generateBoundariesSection,\n generatePrivateSetupNote,\n PRIVATE_SETUP_NOTE_MARKER,\n} 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, privateProfile = false): 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 if (privateProfile) {\n lines.push(generatePrivateSetupNote(), '');\n }\n\n return lines.join('\\n');\n}\n\nexport function improveAgentsMd(existing: string, stack: StackInfo, privateProfile = false): 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 (privateProfile && !existing.includes(PRIVATE_SETUP_NOTE_MARKER)) {\n additions.push(generatePrivateSetupNote());\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","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * Ensure the project's tsconfig.json excludes `.pi/` from the TypeScript program.\n *\n * Why: when Pi is a selected harness, `init` installs `.pi/extensions/joycraft-pipeline.ts`\n * — real Pi runtime code that imports `@earendil-works/pi-coding-agent`, a package\n * the user's project does NOT (and shouldn't) depend on. A default TS toolchain\n * whose `include` globs `**/*.ts` (e.g. create-next-app) would pull that file into\n * the program and fail `tsc`/`next build` on the missing import. The extension must\n * keep its `.ts` extension (Pi loads it as code), so the fix is to keep it out of\n * the user's program: add `.pi` to `exclude`.\n *\n * Transparency + safety contract (mirrors the \"append over rewrite, surface the\n * change\" principle):\n * - Edits surgically by text insertion, preserving the user's comments and\n * formatting — never round-trips through JSON.stringify (which would strip\n * comments from a JSONC tsconfig).\n * - Idempotent: a tsconfig already excluding `.pi` is left untouched.\n * - Bails (returns a 'skipped' outcome with a reason) rather than risk writing\n * malformed JSON when the file shape is too unusual to edit confidently. The\n * caller surfaces the reason so the user can add the line by hand.\n */\n\nexport type TsconfigExcludeOutcome =\n | { status: 'added'; path: string }\n | { status: 'already-present'; path: string }\n | { status: 'no-tsconfig' }\n | { status: 'skipped'; reason: string };\n\nconst PI_EXCLUDE = '.pi';\n\n/** Strip // and /* */ comments for analysis only (never for the written output). */\nfunction stripJsonComments(text: string): string {\n // Remove block comments, then line comments. Good enough to test for an\n // existing `.pi` exclude entry; we never write this stripped form back.\n return text\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/(^|[^:])\\/\\/.*$/gm, '$1');\n}\n\n/** True when the parsed (comment-stripped) config already excludes `.pi`. */\nfunction alreadyExcludesPi(rawText: string): boolean {\n try {\n const parsed = JSON.parse(stripJsonComments(rawText)) as { exclude?: unknown };\n if (!Array.isArray(parsed.exclude)) return false;\n return parsed.exclude.some(\n (e) => typeof e === 'string' && (e === PI_EXCLUDE || e === './.pi' || e === '.pi/**'),\n );\n } catch {\n // Unparseable even after comment-stripping → treat as \"not present\" and let\n // the editor below decide whether it can safely insert.\n return false;\n }\n}\n\n/**\n * Insert `.pi` into an existing top-level `\"exclude\": [ ... ]` array, or add a\n * fresh `exclude` key, by surgical text edit. Returns the new text, or null if\n * the file shape is too ambiguous to edit safely (caller then skips + warns).\n */\nfunction insertPiExclude(rawText: string): string | null {\n // Case A: an existing top-level `exclude` array. Match `\"exclude\"` followed by\n // `:` and the opening `[`, then inject `.pi` as the first element. Anchored to\n // the start of a line (after optional whitespace) so we don't match a nested\n // key named \"exclude\" inside compilerOptions.\n const excludeArrayRe = /(\"exclude\"\\s*:\\s*\\[)(\\s*)/;\n if (excludeArrayRe.test(rawText)) {\n return rawText.replace(excludeArrayRe, (_m, open: string, ws: string) => {\n // Preserve whatever whitespace followed the bracket so formatting survives.\n const sep = ws.includes('\\n') ? ws : ' ';\n return `${open}${sep}\"${PI_EXCLUDE}\",${ws.includes('\\n') ? '' : ' '}`;\n });\n }\n\n // Case B: no `exclude` key. Add one right after the top-level `include` array\n // if present (keeps the two siblings together), else before the final closing\n // brace. Only attempt when there's exactly one top-level object.\n const includeArrayRe = /(\"include\"\\s*:\\s*\\[[^\\]]*\\])/;\n if (includeArrayRe.test(rawText)) {\n return rawText.replace(includeArrayRe, (m) => `${m},\\n \"exclude\": [\"${PI_EXCLUDE}\"]`);\n }\n\n // Case C: fall back to inserting before the last `}` in the file. Guard: there\n // must be a closing brace, and the content before it must end in something we\n // can comma-append to. To stay safe, only do this when the object is non-empty\n // (contains at least one `:`), otherwise bail.\n const lastBrace = rawText.lastIndexOf('}');\n if (lastBrace > 0 && rawText.slice(0, lastBrace).includes(':')) {\n const before = rawText.slice(0, lastBrace).replace(/\\s*$/, '');\n const after = rawText.slice(lastBrace);\n const needsComma = !before.endsWith(',') && !before.endsWith('{');\n return `${before}${needsComma ? ',' : ''}\\n \"exclude\": [\"${PI_EXCLUDE}\"]\\n${after}`;\n }\n\n return null;\n}\n\n/**\n * Add `.pi` to tsconfig.json's `exclude` so a Pi-selected install doesn't break\n * the user's `tsc`/build. No-op when there's no tsconfig, or it already excludes\n * `.pi`. See the module doc for the safety contract.\n */\nexport function ensurePiExcludedFromTsconfig(targetDir: string): TsconfigExcludeOutcome {\n const path = join(targetDir, 'tsconfig.json');\n if (!existsSync(path)) return { status: 'no-tsconfig' };\n\n let rawText: string;\n try {\n rawText = readFileSync(path, 'utf-8');\n } catch {\n return { status: 'skipped', reason: 'tsconfig.json could not be read' };\n }\n\n if (alreadyExcludesPi(rawText)) {\n return { status: 'already-present', path };\n }\n\n const updated = insertPiExclude(rawText);\n if (updated === null || updated === rawText) {\n return {\n status: 'skipped',\n reason:\n 'could not safely edit tsconfig.json — add \".pi\" to its \"exclude\" array manually so the Pi extension stays out of your TypeScript build',\n };\n }\n\n // Sanity gate: the edited text must still parse (after comment-stripping) and\n // must now actually exclude .pi. If either fails, do NOT write — bail and warn.\n if (!alreadyExcludesPi(updated)) {\n return {\n status: 'skipped',\n reason:\n 'tsconfig.json edit could not be verified — add \".pi\" to its \"exclude\" array manually',\n };\n }\n\n try {\n writeFileSync(path, updated, 'utf-8');\n } catch {\n return { status: 'skipped', reason: 'tsconfig.json could not be written' };\n }\n return { status: 'added', path };\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","readFileSync","writeFileSync","join","existsSync","mkdirSync","writeFileSync","join","readdirSync","readFileSync"]}
|