joycraft 0.3.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +445 -27
- package/dist/chunk-2S7KP7FU.js +36 -0
- package/dist/chunk-2S7KP7FU.js.map +1 -0
- package/dist/chunk-HHW4Q2UC.js +2297 -0
- package/dist/chunk-HHW4Q2UC.js.map +1 -0
- package/dist/cli.js +6 -2
- package/dist/cli.js.map +1 -1
- package/dist/{init-R33WYEFZ.js → init-DHVJEWGX.js} +249 -43
- package/dist/init-DHVJEWGX.js.map +1 -0
- package/dist/init-autofix-OVHXYVLB.js +118 -0
- package/dist/init-autofix-OVHXYVLB.js.map +1 -0
- package/dist/{upgrade-ZE6K64XX.js → upgrade-QEODYEDE.js} +54 -6
- package/dist/upgrade-QEODYEDE.js.map +1 -0
- package/package.json +11 -2
- package/dist/chunk-LLJVCCB2.js +0 -1129
- package/dist/chunk-LLJVCCB2.js.map +0 -1
- package/dist/init-R33WYEFZ.js.map +0 -1
- package/dist/upgrade-ZE6K64XX.js.map +0 -1
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
SKILLS,
|
|
4
|
-
TEMPLATES,
|
|
5
3
|
hashContent,
|
|
6
4
|
writeVersion
|
|
7
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-2S7KP7FU.js";
|
|
6
|
+
import {
|
|
7
|
+
SKILLS,
|
|
8
|
+
TEMPLATES
|
|
9
|
+
} from "./chunk-HHW4Q2UC.js";
|
|
8
10
|
|
|
9
11
|
// src/init.ts
|
|
10
|
-
import { mkdirSync, existsSync as
|
|
11
|
-
import { join as
|
|
12
|
+
import { mkdirSync as mkdirSync2, existsSync as existsSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
|
|
13
|
+
import { join as join3, basename, resolve, dirname } from "path";
|
|
12
14
|
|
|
13
15
|
// src/detect.ts
|
|
14
16
|
import { readFileSync, existsSync } from "fs";
|
|
@@ -241,6 +243,7 @@ function generateBoundariesSection() {
|
|
|
241
243
|
- Use \`verb: concise message\` format for commits
|
|
242
244
|
- Commit after completing each discrete task (atomic commits)
|
|
243
245
|
- Stage specific files by name (not \`git add -A\` or \`git add .\`)
|
|
246
|
+
- Read \`docs/context/\` before making infrastructure or config changes
|
|
244
247
|
- Follow existing code patterns and style
|
|
245
248
|
|
|
246
249
|
### ASK FIRST
|
|
@@ -288,13 +291,14 @@ This project uses [Joycraft](https://github.com/maksutovic/joycraft) for AI deve
|
|
|
288
291
|
|
|
289
292
|
| Skill | Purpose |
|
|
290
293
|
|-------|---------|
|
|
291
|
-
| \`/tune\` | Assess your harness, apply upgrades, see path to Level 5 |
|
|
292
|
-
| \`/new-feature\` | Interview -> Feature Brief -> Atomic Specs |
|
|
293
|
-
| \`/interview\` | Lightweight brainstorm \u2014 yap about ideas, get a structured summary |
|
|
294
|
-
| \`/decompose\` | Break a brief into small, testable specs |
|
|
295
|
-
| \`/session-end\` | Capture discoveries, verify, commit |
|
|
294
|
+
| \`/joycraft-tune\` | Assess your harness, apply upgrades, see path to Level 5 |
|
|
295
|
+
| \`/joycraft-new-feature\` | Interview -> Feature Brief -> Atomic Specs |
|
|
296
|
+
| \`/joycraft-interview\` | Lightweight brainstorm \u2014 yap about ideas, get a structured summary |
|
|
297
|
+
| \`/joycraft-decompose\` | Break a brief into small, testable specs |
|
|
298
|
+
| \`/joycraft-session-end\` | Capture discoveries, verify, commit |
|
|
299
|
+
| \`/joycraft-implement-level5\` | Set up Level 5 \u2014 autofix loop, holdout scenarios, scenario evolution |
|
|
296
300
|
|
|
297
|
-
Run \`/tune\` to see where your project stands and what to improve next.`;
|
|
301
|
+
Run \`/joycraft-tune\` to see where your project stands and what to improve next.`;
|
|
298
302
|
}
|
|
299
303
|
function generateCLAUDEMd(projectName, stack) {
|
|
300
304
|
const frameworkNote = stack.framework ? ` (${stack.framework})` : "";
|
|
@@ -391,69 +395,250 @@ function generateAgentsMd(projectName, stack) {
|
|
|
391
395
|
return lines.join("\n");
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
// src/permissions.ts
|
|
399
|
+
function generatePermissions(stack) {
|
|
400
|
+
const deny = [
|
|
401
|
+
"Bash(rm -rf *)",
|
|
402
|
+
"Bash(git push --force *)",
|
|
403
|
+
"Bash(git push -f *)",
|
|
404
|
+
"Bash(git reset --hard *)",
|
|
405
|
+
"Edit(//.env*)",
|
|
406
|
+
"Edit(//*.pem)",
|
|
407
|
+
"Edit(//*.key)",
|
|
408
|
+
"Edit(//.git/**)"
|
|
409
|
+
];
|
|
410
|
+
const allow = [
|
|
411
|
+
"Bash(git status)",
|
|
412
|
+
"Bash(git diff *)",
|
|
413
|
+
"Bash(git log *)",
|
|
414
|
+
"Bash(git add *)",
|
|
415
|
+
"Bash(git commit *)",
|
|
416
|
+
"Bash(git checkout *)",
|
|
417
|
+
"Bash(git branch *)"
|
|
418
|
+
];
|
|
419
|
+
if (stack.language === "node") {
|
|
420
|
+
allow.push(`Bash(${stack.packageManager} *)`);
|
|
421
|
+
if (stack.packageManager !== "npm") deny.push("Bash(npm install *)");
|
|
422
|
+
if (stack.packageManager !== "yarn") deny.push("Bash(yarn add *)");
|
|
423
|
+
if (stack.packageManager !== "pnpm") deny.push("Bash(pnpm add *)");
|
|
424
|
+
if (stack.packageManager !== "bun") deny.push("Bash(bun add *)");
|
|
425
|
+
if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);
|
|
426
|
+
if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);
|
|
427
|
+
if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);
|
|
428
|
+
if (stack.commands.typecheck) allow.push(`Bash(${stack.commands.typecheck})`);
|
|
429
|
+
}
|
|
430
|
+
if (stack.language === "python") {
|
|
431
|
+
allow.push(`Bash(${stack.packageManager} *)`);
|
|
432
|
+
if (stack.commands.test) allow.push(`Bash(${stack.commands.test})`);
|
|
433
|
+
if (stack.commands.lint) allow.push(`Bash(${stack.commands.lint})`);
|
|
434
|
+
if (stack.commands.build) allow.push(`Bash(${stack.commands.build})`);
|
|
435
|
+
}
|
|
436
|
+
if (stack.language === "rust") {
|
|
437
|
+
allow.push("Bash(cargo *)");
|
|
438
|
+
}
|
|
439
|
+
if (stack.language === "go") {
|
|
440
|
+
allow.push("Bash(go *)");
|
|
441
|
+
}
|
|
442
|
+
if (stack.language === "swift") {
|
|
443
|
+
allow.push("Bash(swift *)");
|
|
444
|
+
allow.push("Bash(xcodebuild *)");
|
|
445
|
+
}
|
|
446
|
+
return { allow, deny };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// src/safeguard.ts
|
|
450
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync, mkdirSync } from "fs";
|
|
451
|
+
import { join as join2 } from "path";
|
|
452
|
+
function generateHookScript() {
|
|
453
|
+
return `#!/bin/bash
|
|
454
|
+
# Joycraft Safeguard \u2014 PreToolUse hook
|
|
455
|
+
# Blocks dangerous Bash commands. Exit 2 = block the action.
|
|
456
|
+
# Edit deny-patterns.txt to customize what's blocked.
|
|
457
|
+
|
|
458
|
+
TOOL_NAME="$1"
|
|
459
|
+
# Read the full tool input from stdin
|
|
460
|
+
INPUT=$(cat)
|
|
461
|
+
|
|
462
|
+
# Only check Bash commands
|
|
463
|
+
if [ "$TOOL_NAME" != "Bash" ]; then
|
|
464
|
+
exit 0
|
|
465
|
+
fi
|
|
466
|
+
|
|
467
|
+
# Extract the command from the JSON input
|
|
468
|
+
COMMAND=$(echo "$INPUT" | grep -o '"command":"[^"]*"' | head -1 | sed 's/"command":"//;s/"$//')
|
|
469
|
+
|
|
470
|
+
if [ -z "$COMMAND" ]; then
|
|
471
|
+
exit 0
|
|
472
|
+
fi
|
|
473
|
+
|
|
474
|
+
PATTERNS_FILE="$(dirname "$0")/deny-patterns.txt"
|
|
475
|
+
|
|
476
|
+
if [ ! -f "$PATTERNS_FILE" ]; then
|
|
477
|
+
exit 0
|
|
478
|
+
fi
|
|
479
|
+
|
|
480
|
+
while IFS= read -r pattern || [ -n "$pattern" ]; do
|
|
481
|
+
# Skip empty lines and comments
|
|
482
|
+
[ -z "$pattern" ] && continue
|
|
483
|
+
[[ "$pattern" == \\#* ]] && continue
|
|
484
|
+
|
|
485
|
+
if echo "$COMMAND" | grep -qEi "$pattern"; then
|
|
486
|
+
echo "Blocked by Joycraft Safeguard: command matches deny pattern '$pattern'"
|
|
487
|
+
echo "Edit .claude/hooks/joycraft/deny-patterns.txt to modify blocked patterns."
|
|
488
|
+
exit 2
|
|
489
|
+
fi
|
|
490
|
+
done < "$PATTERNS_FILE"
|
|
491
|
+
|
|
492
|
+
exit 0
|
|
493
|
+
`;
|
|
494
|
+
}
|
|
495
|
+
function generateDenyPatternsFile(customPatterns = []) {
|
|
496
|
+
const lines = [
|
|
497
|
+
"# Joycraft Safeguard \u2014 Deny Patterns",
|
|
498
|
+
"# One regex pattern per line. Lines starting with # are comments.",
|
|
499
|
+
"# Commands matching any pattern will be blocked (exit 2).",
|
|
500
|
+
"# Edit this file to customize what's blocked.",
|
|
501
|
+
"",
|
|
502
|
+
"# Destructive file operations",
|
|
503
|
+
"rm\\s+-rf\\s+/",
|
|
504
|
+
"rm\\s+-rf\\s+\\.",
|
|
505
|
+
"",
|
|
506
|
+
"# Dangerous git operations",
|
|
507
|
+
"git\\s+push\\s+--force",
|
|
508
|
+
"git\\s+push\\s+-f\\b",
|
|
509
|
+
"git\\s+reset\\s+--hard",
|
|
510
|
+
"",
|
|
511
|
+
"# SQL destruction",
|
|
512
|
+
"DROP\\s+TABLE",
|
|
513
|
+
"DROP\\s+DATABASE",
|
|
514
|
+
"TRUNCATE",
|
|
515
|
+
"",
|
|
516
|
+
"# System security",
|
|
517
|
+
"chmod\\s+777",
|
|
518
|
+
"curl.*\\|.*sh",
|
|
519
|
+
"wget.*\\|.*sh"
|
|
520
|
+
];
|
|
521
|
+
if (customPatterns.length > 0) {
|
|
522
|
+
lines.push("");
|
|
523
|
+
lines.push("# Project-specific patterns (from risk interview)");
|
|
524
|
+
for (const p of customPatterns) {
|
|
525
|
+
lines.push(p);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return lines.join("\n") + "\n";
|
|
529
|
+
}
|
|
530
|
+
function installSafeguardHooks(targetDir, customPatterns = [], force = false) {
|
|
531
|
+
const result = { created: [], skipped: [] };
|
|
532
|
+
const hooksDir = join2(targetDir, ".claude", "hooks", "joycraft");
|
|
533
|
+
if (!existsSync2(hooksDir)) {
|
|
534
|
+
mkdirSync(hooksDir, { recursive: true });
|
|
535
|
+
}
|
|
536
|
+
const hookPath = join2(hooksDir, "block-dangerous.sh");
|
|
537
|
+
if (!existsSync2(hookPath) || force) {
|
|
538
|
+
writeFileSync(hookPath, generateHookScript(), { mode: 493 });
|
|
539
|
+
result.created.push(hookPath);
|
|
540
|
+
} else {
|
|
541
|
+
result.skipped.push(hookPath);
|
|
542
|
+
}
|
|
543
|
+
const patternsPath = join2(hooksDir, "deny-patterns.txt");
|
|
544
|
+
if (!existsSync2(patternsPath) || force) {
|
|
545
|
+
writeFileSync(patternsPath, generateDenyPatternsFile(customPatterns));
|
|
546
|
+
result.created.push(patternsPath);
|
|
547
|
+
} else {
|
|
548
|
+
result.skipped.push(patternsPath);
|
|
549
|
+
}
|
|
550
|
+
const settingsPath = join2(targetDir, ".claude", "settings.json");
|
|
551
|
+
let settings = {};
|
|
552
|
+
if (existsSync2(settingsPath)) {
|
|
553
|
+
try {
|
|
554
|
+
settings = JSON.parse(readFileSync2(settingsPath, "utf-8"));
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (!settings.hooks) settings.hooks = {};
|
|
559
|
+
const hooks = settings.hooks;
|
|
560
|
+
if (!hooks.PreToolUse) hooks.PreToolUse = [];
|
|
561
|
+
const preToolUse = hooks.PreToolUse;
|
|
562
|
+
const hasJoycraftHook = preToolUse.some((h) => {
|
|
563
|
+
const innerHooks = h.hooks;
|
|
564
|
+
return innerHooks?.some((ih) => typeof ih.command === "string" && ih.command.includes("joycraft"));
|
|
565
|
+
});
|
|
566
|
+
if (!hasJoycraftHook) {
|
|
567
|
+
preToolUse.push({
|
|
568
|
+
matcher: "Bash",
|
|
569
|
+
hooks: [{
|
|
570
|
+
type: "command",
|
|
571
|
+
command: ".claude/hooks/joycraft/block-dangerous.sh"
|
|
572
|
+
}]
|
|
573
|
+
});
|
|
574
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
575
|
+
}
|
|
576
|
+
return result;
|
|
577
|
+
}
|
|
578
|
+
|
|
394
579
|
// src/init.ts
|
|
395
580
|
function ensureDir(dir) {
|
|
396
|
-
if (!
|
|
397
|
-
|
|
581
|
+
if (!existsSync3(dir)) {
|
|
582
|
+
mkdirSync2(dir, { recursive: true });
|
|
398
583
|
}
|
|
399
584
|
}
|
|
400
585
|
function writeFile(path, content, force, result) {
|
|
401
|
-
if (
|
|
586
|
+
if (existsSync3(path) && !force) {
|
|
402
587
|
result.skipped.push(path);
|
|
403
588
|
return;
|
|
404
589
|
}
|
|
405
|
-
|
|
590
|
+
writeFileSync2(path, content, "utf-8");
|
|
406
591
|
result.created.push(path);
|
|
407
592
|
}
|
|
408
593
|
async function init(dir, opts) {
|
|
409
594
|
const targetDir = resolve(dir);
|
|
410
595
|
const result = { created: [], skipped: [], modified: [], warnings: [] };
|
|
411
596
|
const stack = await detectStack(targetDir);
|
|
412
|
-
const docsDirs = ["briefs", "specs", "discoveries", "contracts", "decisions"];
|
|
597
|
+
const docsDirs = ["briefs", "specs", "discoveries", "contracts", "decisions", "context"];
|
|
413
598
|
for (const sub of docsDirs) {
|
|
414
|
-
ensureDir(
|
|
599
|
+
ensureDir(join3(targetDir, "docs", sub));
|
|
415
600
|
}
|
|
416
|
-
const skillsDir =
|
|
601
|
+
const skillsDir = join3(targetDir, ".claude", "skills");
|
|
417
602
|
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
418
603
|
const skillName = filename.replace(/\.md$/, "");
|
|
419
|
-
const skillDir =
|
|
604
|
+
const skillDir = join3(skillsDir, skillName);
|
|
420
605
|
ensureDir(skillDir);
|
|
421
|
-
writeFile(
|
|
606
|
+
writeFile(join3(skillDir, "SKILL.md"), content, opts.force, result);
|
|
422
607
|
}
|
|
423
|
-
const templatesDir =
|
|
608
|
+
const templatesDir = join3(targetDir, "docs", "templates");
|
|
424
609
|
ensureDir(templatesDir);
|
|
425
610
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
426
|
-
ensureDir(dirname(
|
|
427
|
-
writeFile(
|
|
611
|
+
ensureDir(dirname(join3(templatesDir, filename)));
|
|
612
|
+
writeFile(join3(templatesDir, filename), content, opts.force, result);
|
|
428
613
|
}
|
|
429
|
-
const claudeMdPath =
|
|
430
|
-
if (
|
|
614
|
+
const claudeMdPath = join3(targetDir, "CLAUDE.md");
|
|
615
|
+
if (existsSync3(claudeMdPath) && !opts.force) {
|
|
431
616
|
result.skipped.push(claudeMdPath);
|
|
432
617
|
} else {
|
|
433
618
|
const projectName = basename(targetDir);
|
|
434
619
|
const content = generateCLAUDEMd(projectName, stack);
|
|
435
|
-
|
|
620
|
+
writeFileSync2(claudeMdPath, content, "utf-8");
|
|
436
621
|
result.created.push(claudeMdPath);
|
|
437
622
|
}
|
|
438
|
-
const agentsMdPath =
|
|
439
|
-
if (
|
|
623
|
+
const agentsMdPath = join3(targetDir, "AGENTS.md");
|
|
624
|
+
if (existsSync3(agentsMdPath) && !opts.force) {
|
|
440
625
|
result.skipped.push(agentsMdPath);
|
|
441
626
|
} else {
|
|
442
627
|
const projectName = basename(targetDir);
|
|
443
628
|
const content = generateAgentsMd(projectName, stack);
|
|
444
|
-
|
|
629
|
+
writeFileSync2(agentsMdPath, content, "utf-8");
|
|
445
630
|
result.created.push(agentsMdPath);
|
|
446
631
|
}
|
|
447
632
|
const fileHashes = {};
|
|
448
633
|
for (const [filename, content] of Object.entries(SKILLS)) {
|
|
449
634
|
const skillName = filename.replace(/\.md$/, "");
|
|
450
|
-
fileHashes[
|
|
635
|
+
fileHashes[join3(".claude", "skills", skillName, "SKILL.md")] = hashContent(content);
|
|
451
636
|
}
|
|
452
637
|
for (const [filename, content] of Object.entries(TEMPLATES)) {
|
|
453
|
-
fileHashes[
|
|
638
|
+
fileHashes[join3("docs", "templates", filename)] = hashContent(content);
|
|
454
639
|
}
|
|
455
640
|
writeVersion(targetDir, "0.1.0", fileHashes);
|
|
456
|
-
const hooksDir =
|
|
641
|
+
const hooksDir = join3(targetDir, ".claude", "hooks");
|
|
457
642
|
ensureDir(hooksDir);
|
|
458
643
|
const hookScript = `// Joycraft version check \u2014 runs on Claude Code session start
|
|
459
644
|
import { readFileSync } from 'node:fs';
|
|
@@ -467,12 +652,12 @@ try {
|
|
|
467
652
|
}
|
|
468
653
|
} catch {}
|
|
469
654
|
`;
|
|
470
|
-
writeFile(
|
|
471
|
-
const settingsPath =
|
|
655
|
+
writeFile(join3(hooksDir, "joycraft-version-check.mjs"), hookScript, opts.force, result);
|
|
656
|
+
const settingsPath = join3(targetDir, ".claude", "settings.json");
|
|
472
657
|
let settings = {};
|
|
473
|
-
if (
|
|
658
|
+
if (existsSync3(settingsPath)) {
|
|
474
659
|
try {
|
|
475
|
-
settings = JSON.parse(
|
|
660
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
476
661
|
} catch {
|
|
477
662
|
}
|
|
478
663
|
}
|
|
@@ -492,12 +677,33 @@ try {
|
|
|
492
677
|
command: "node .claude/hooks/joycraft-version-check.mjs"
|
|
493
678
|
}]
|
|
494
679
|
});
|
|
495
|
-
|
|
680
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
496
681
|
result.created.push(settingsPath);
|
|
497
682
|
}
|
|
498
|
-
const
|
|
499
|
-
if (
|
|
500
|
-
|
|
683
|
+
const permissions = generatePermissions(stack);
|
|
684
|
+
if (existsSync3(settingsPath)) {
|
|
685
|
+
try {
|
|
686
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (!settings.permissions) settings.permissions = {};
|
|
691
|
+
const perms = settings.permissions;
|
|
692
|
+
if (!perms.allow) perms.allow = [];
|
|
693
|
+
if (!perms.deny) perms.deny = [];
|
|
694
|
+
for (const rule of permissions.allow) {
|
|
695
|
+
if (!perms.allow.includes(rule)) perms.allow.push(rule);
|
|
696
|
+
}
|
|
697
|
+
for (const rule of permissions.deny) {
|
|
698
|
+
if (!perms.deny.includes(rule)) perms.deny.push(rule);
|
|
699
|
+
}
|
|
700
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
701
|
+
const hookResult = installSafeguardHooks(targetDir, [], opts.force);
|
|
702
|
+
result.created.push(...hookResult.created);
|
|
703
|
+
result.skipped.push(...hookResult.skipped);
|
|
704
|
+
const gitignorePath = join3(targetDir, ".gitignore");
|
|
705
|
+
if (existsSync3(gitignorePath)) {
|
|
706
|
+
const gitignore = readFileSync3(gitignorePath, "utf-8");
|
|
501
707
|
if (/^\.claude\/?$/m.test(gitignore) || /^\.claude\/\*$/m.test(gitignore)) {
|
|
502
708
|
result.warnings.push(
|
|
503
709
|
".claude/ is in your .gitignore \u2014 teammates won't get Joycraft skills.\n Add this line to .gitignore to fix: !.claude/skills/"
|
|
@@ -544,15 +750,15 @@ function printSummary(result, stack) {
|
|
|
544
750
|
const hasExistingClaude = result.skipped.some((f) => f.endsWith("CLAUDE.md"));
|
|
545
751
|
console.log("\n Next steps:");
|
|
546
752
|
if (hasExistingClaude) {
|
|
547
|
-
console.log(" 1. Run Claude Code and try /tune to assess and improve your existing CLAUDE.md");
|
|
753
|
+
console.log(" 1. Run Claude Code and try /joycraft-tune to assess and improve your existing CLAUDE.md");
|
|
548
754
|
} else {
|
|
549
755
|
console.log(" 1. Review and customize the generated CLAUDE.md for your project");
|
|
550
756
|
}
|
|
551
|
-
console.log(" 2. Try /new-feature to start building with the spec-driven workflow");
|
|
757
|
+
console.log(" 2. Try /joycraft-new-feature to start building with the spec-driven workflow");
|
|
552
758
|
console.log(" 3. Commit .claude/skills/ and docs/ so your team gets the same workflow");
|
|
553
759
|
console.log("");
|
|
554
760
|
}
|
|
555
761
|
export {
|
|
556
762
|
init
|
|
557
763
|
};
|
|
558
|
-
//# sourceMappingURL=init-
|
|
764
|
+
//# sourceMappingURL=init-DHVJEWGX.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 } 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 } from './bundled-files.js';\nimport { writeVersion, hashContent } from './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 // 1. Create docs/ subdirectories\n const docsDirs = ['briefs', 'specs', 'discoveries', 'contracts', 'decisions', 'context'];\n for (const sub of docsDirs) {\n ensureDir(join(targetDir, 'docs', sub));\n }\n\n // 2. Copy skill files to .claude/skills/<name>/SKILL.md\n const skillsDir = join(targetDir, '.claude', 'skills');\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 // 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);\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 .joycraft-version 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(TEMPLATES)) {\n fileHashes[join('docs', 'templates', filename)] = hashContent(content);\n }\n writeVersion(targetDir, '0.1.0', fileHashes);\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(), '.joycraft-version'), '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: npx joycraft upgrade');\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 if (existsSync(settingsPath)) {\n try {\n settings = JSON.parse(readFileSync(settingsPath, 'utf-8'));\n } catch {\n // If settings.json is malformed, start fresh\n }\n }\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 // keep existing settings object\n }\n }\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 // 9. Install safeguard hooks (PreToolUse deny-pattern blocking)\n const hookResult = installSafeguardHooks(targetDir, [], opts.force);\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);\n}\n\nfunction printSummary(result: InitResult, stack: import('./detect.js').StackInfo): 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 (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 const hasExistingClaude = result.skipped.some(f => f.endsWith('CLAUDE.md'));\n\n console.log('\\n Next steps:');\n if (hasExistingClaude) {\n console.log(' 1. Run Claude Code and try /joycraft-tune to assess and improve your existing CLAUDE.md');\n } else {\n console.log(' 1. 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(' 3. Commit .claude/skills/ and docs/ so your team gets the same workflow');\n console.log('');\n}\n","import { readFileSync, existsSync } from 'node:fs';\nimport { join } from 'node:path';\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}\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 return {\n language: 'node',\n packageManager: pm,\n commands,\n framework,\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 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 };\n }\n\n const requirements = readFile(join(dir, 'requirements.txt'));\n if (requirements !== null) {\n const framework = detectPythonFramework(requirements);\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 };\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 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 };\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 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 };\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 };\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 detectSwift,\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 type { StackInfo } from './detect.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 // 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\nfunction generateBoundariesSection(): string {\n return `## Behavioral Boundaries\n\n### ALWAYS\n- Run tests and type-check before committing\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\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-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\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\nexport function improveCLAUDEMd(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 (!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, /external\\s*validation/i)) {\n additions.push(generateExternalValidationSection());\n }\n\n if (additions.length === 0) {\n return existing;\n }\n\n // Append missing sections\n const trimmed = existing.trimEnd();\n return trimmed + '\\n\\n' + additions.join('\\n\\n') + '\\n';\n}\n\nexport function generateCLAUDEMd(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 '---',\n '',\n generateBoundariesSection(),\n '',\n generateWorkflowSection(stack),\n '',\n generateArchitectureSection(),\n '',\n generateKeyFilesSection(),\n '',\n generateGotchasSection(),\n '',\n generateGettingStartedSection(),\n '',\n ];\n\n return lines.join('\\n');\n}\n","import type { StackInfo } from './detect.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 generateBoundariesSection(): string {\n return `## Behavioral Boundaries\n\n### ALWAYS\n- Run tests and type-check before committing\n- Follow existing code patterns and style\n\n### ASK FIRST\n- Adding new dependencies\n- Changing auth or data models\n- Any destructive operation\n\n### NEVER\n- Push to main without approval\n- Skip tests or type-checking\n- Hardcode secrets or credentials`;\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. See CLAUDE.md for detailed instructions.',\n '',\n '---',\n '',\n generateBoundariesSection(),\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 (!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): { 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\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 }\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 return result;\n}\n"],"mappings":"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","names":["mkdirSync","existsSync","writeFileSync","readFileSync","join","generateCommandsBlock","generateBoundariesSection","generateArchitectureSection","generateKeyFilesSection","existsSync","readFileSync","join","join","existsSync","readFileSync","existsSync","mkdirSync","writeFileSync","join","readFileSync"]}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
TEMPLATES
|
|
4
|
+
} from "./chunk-HHW4Q2UC.js";
|
|
5
|
+
|
|
6
|
+
// src/init-autofix.ts
|
|
7
|
+
import { mkdirSync, existsSync, writeFileSync } from "fs";
|
|
8
|
+
import { join, resolve, basename, dirname } from "path";
|
|
9
|
+
function ensureDir(dir) {
|
|
10
|
+
if (!existsSync(dir)) {
|
|
11
|
+
mkdirSync(dir, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function replacePlaceholders(content, vars) {
|
|
15
|
+
let result = content;
|
|
16
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
17
|
+
result = result.replaceAll("$" + key, value);
|
|
18
|
+
}
|
|
19
|
+
return result;
|
|
20
|
+
}
|
|
21
|
+
async function initAutofix(dir, opts) {
|
|
22
|
+
const targetDir = resolve(dir);
|
|
23
|
+
if (!existsSync(join(targetDir, ".joycraft-version"))) {
|
|
24
|
+
throw new Error("Project is not initialized. Run `npx joycraft init` first.");
|
|
25
|
+
}
|
|
26
|
+
const force = opts.force ?? false;
|
|
27
|
+
const dryRun = opts.dryRun ?? false;
|
|
28
|
+
const scenariosRepo = opts.scenariosRepo ?? `${basename(targetDir)}-scenarios`;
|
|
29
|
+
const appId = opts.appId;
|
|
30
|
+
const vars = {
|
|
31
|
+
SCENARIOS_REPO: scenariosRepo
|
|
32
|
+
};
|
|
33
|
+
if (appId !== void 0) {
|
|
34
|
+
vars["JOYCRAFT_APP_ID"] = appId;
|
|
35
|
+
}
|
|
36
|
+
const result = { created: [], skipped: [] };
|
|
37
|
+
const workflowsDir = join(targetDir, ".github", "workflows");
|
|
38
|
+
for (const [key, rawContent] of Object.entries(TEMPLATES)) {
|
|
39
|
+
if (!key.startsWith("workflows/")) continue;
|
|
40
|
+
const filename = key.slice("workflows/".length);
|
|
41
|
+
const destPath = join(workflowsDir, filename);
|
|
42
|
+
const content = replacePlaceholders(rawContent, vars);
|
|
43
|
+
if (dryRun) {
|
|
44
|
+
result.created.push(destPath);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (existsSync(destPath) && !force) {
|
|
48
|
+
result.skipped.push(destPath);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
ensureDir(dirname(destPath));
|
|
52
|
+
writeFileSync(destPath, content, "utf-8");
|
|
53
|
+
result.created.push(destPath);
|
|
54
|
+
}
|
|
55
|
+
const scenariosTemplateDir = join(targetDir, "docs", "templates", "scenarios");
|
|
56
|
+
for (const [key, rawContent] of Object.entries(TEMPLATES)) {
|
|
57
|
+
if (!key.startsWith("scenarios/")) continue;
|
|
58
|
+
const relativePath = key.slice("scenarios/".length);
|
|
59
|
+
const destPath = join(scenariosTemplateDir, relativePath);
|
|
60
|
+
const content = replacePlaceholders(rawContent, vars);
|
|
61
|
+
if (dryRun) {
|
|
62
|
+
result.created.push(destPath);
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (existsSync(destPath) && !force) {
|
|
66
|
+
result.skipped.push(destPath);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
ensureDir(dirname(destPath));
|
|
70
|
+
writeFileSync(destPath, content, "utf-8");
|
|
71
|
+
result.created.push(destPath);
|
|
72
|
+
}
|
|
73
|
+
printSummary(result, dryRun, scenariosRepo);
|
|
74
|
+
}
|
|
75
|
+
function printSummary(result, dryRun, scenariosRepo) {
|
|
76
|
+
if (dryRun) {
|
|
77
|
+
console.log("\nDry run \u2014 these files would be created:\n");
|
|
78
|
+
for (const f of result.created) {
|
|
79
|
+
console.log(` + ${f}`);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log("\nJoycraft Autofix initialized!\n");
|
|
84
|
+
if (result.created.length > 0) {
|
|
85
|
+
console.log(` Created ${result.created.length} file(s):`);
|
|
86
|
+
for (const f of result.created) {
|
|
87
|
+
console.log(` + ${f}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (result.skipped.length > 0) {
|
|
91
|
+
console.log(`
|
|
92
|
+
Skipped ${result.skipped.length} file(s) (already exist, use --force to overwrite):`);
|
|
93
|
+
for (const f of result.skipped) {
|
|
94
|
+
console.log(` - ${f}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
console.log("\n Setup checklist:\n");
|
|
98
|
+
console.log(" 1. Create a GitHub App with these permissions:");
|
|
99
|
+
console.log(" - Contents: Read & Write");
|
|
100
|
+
console.log(" - Actions: Read & Write");
|
|
101
|
+
console.log(" - Pull requests: Read & Write");
|
|
102
|
+
console.log("");
|
|
103
|
+
console.log(" 2. Add these secrets to your main repo (GitHub Settings > Secrets):");
|
|
104
|
+
console.log(" JOYCRAFT_APP_PRIVATE_KEY \u2014 private key PEM from your GitHub App");
|
|
105
|
+
console.log(" ANTHROPIC_API_KEY \u2014 your Anthropic API key");
|
|
106
|
+
console.log("");
|
|
107
|
+
console.log(" 3. Create the scenarios repo (private):");
|
|
108
|
+
console.log(` ${scenariosRepo}`);
|
|
109
|
+
console.log(" Copy docs/templates/scenarios/ into it as the repo root.");
|
|
110
|
+
console.log("");
|
|
111
|
+
console.log(" 4. Update autofix.yml with your GitHub App ID if not already set.");
|
|
112
|
+
console.log("");
|
|
113
|
+
console.log(" Run `npx joycraft init-autofix --help` for options.\n");
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
initAutofix
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=init-autofix-OVHXYVLB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/init-autofix.ts"],"sourcesContent":["import { mkdirSync, existsSync, writeFileSync } from 'node:fs';\nimport { join, resolve, basename, dirname } from 'node:path';\nimport { TEMPLATES } from './bundled-files.js';\n\nexport interface InitAutofixOptions {\n scenariosRepo?: string;\n appId?: string;\n force?: boolean;\n dryRun?: boolean;\n}\n\ninterface AutofixResult {\n created: string[];\n skipped: string[];\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\n/**\n * Replace literal placeholders in content.\n * Replaces $KEY but intentionally leaves ${{ ... }} GitHub Actions expressions untouched —\n * the simple string replace of \"$KEY\" won't match \"${{\" since \"${{\" != \"$\" + \"KEY\".\n */\nfunction replacePlaceholders(content: string, vars: Record<string, string>): string {\n let result = content;\n for (const [key, value] of Object.entries(vars)) {\n // Only replace literal $KEY, not ${{ expressions }}\n // Use a regex that matches $KEY but not ${{\n result = result.replaceAll('$' + key, value);\n }\n return result;\n}\n\nexport async function initAutofix(dir: string, opts: InitAutofixOptions): Promise<void> {\n const targetDir = resolve(dir);\n\n // Check project is initialized\n if (!existsSync(join(targetDir, '.joycraft-version'))) {\n throw new Error('Project is not initialized. Run `npx joycraft init` first.');\n }\n\n const force = opts.force ?? false;\n const dryRun = opts.dryRun ?? false;\n\n // Determine placeholder values\n const scenariosRepo = opts.scenariosRepo ?? `${basename(targetDir)}-scenarios`;\n const appId = opts.appId; // may be undefined — leave $JOYCRAFT_APP_ID as-is if so\n\n const vars: Record<string, string> = {\n SCENARIOS_REPO: scenariosRepo,\n };\n if (appId !== undefined) {\n vars['JOYCRAFT_APP_ID'] = appId;\n }\n\n const result: AutofixResult = { created: [], skipped: [] };\n\n // Install workflow templates to .github/workflows/\n const workflowsDir = join(targetDir, '.github', 'workflows');\n for (const [key, rawContent] of Object.entries(TEMPLATES)) {\n if (!key.startsWith('workflows/')) continue;\n const filename = key.slice('workflows/'.length); // e.g. \"autofix.yml\"\n const destPath = join(workflowsDir, filename);\n const content = replacePlaceholders(rawContent, vars);\n\n if (dryRun) {\n result.created.push(destPath);\n continue;\n }\n\n if (existsSync(destPath) && !force) {\n result.skipped.push(destPath);\n continue;\n }\n\n ensureDir(dirname(destPath));\n writeFileSync(destPath, content, 'utf-8');\n result.created.push(destPath);\n }\n\n // Install scenario templates to docs/templates/scenarios/\n const scenariosTemplateDir = join(targetDir, 'docs', 'templates', 'scenarios');\n for (const [key, rawContent] of Object.entries(TEMPLATES)) {\n if (!key.startsWith('scenarios/')) continue;\n const relativePath = key.slice('scenarios/'.length); // e.g. \"README.md\" or \"workflows/run.yml\"\n const destPath = join(scenariosTemplateDir, relativePath);\n const content = replacePlaceholders(rawContent, vars);\n\n if (dryRun) {\n result.created.push(destPath);\n continue;\n }\n\n if (existsSync(destPath) && !force) {\n result.skipped.push(destPath);\n continue;\n }\n\n ensureDir(dirname(destPath));\n writeFileSync(destPath, content, 'utf-8');\n result.created.push(destPath);\n }\n\n printSummary(result, dryRun, scenariosRepo);\n}\n\nfunction printSummary(result: AutofixResult, dryRun: boolean, scenariosRepo: string): void {\n if (dryRun) {\n console.log('\\nDry run — these files would be created:\\n');\n for (const f of result.created) {\n console.log(` + ${f}`);\n }\n return;\n }\n\n console.log('\\nJoycraft Autofix initialized!\\n');\n\n if (result.created.length > 0) {\n console.log(` Created ${result.created.length} file(s):`);\n for (const f of result.created) {\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 console.log('\\n Setup checklist:\\n');\n console.log(' 1. Create a GitHub App with these permissions:');\n console.log(' - Contents: Read & Write');\n console.log(' - Actions: Read & Write');\n console.log(' - Pull requests: Read & Write');\n console.log('');\n console.log(' 2. Add these secrets to your main repo (GitHub Settings > Secrets):');\n console.log(' JOYCRAFT_APP_PRIVATE_KEY — private key PEM from your GitHub App');\n console.log(' ANTHROPIC_API_KEY — your Anthropic API key');\n console.log('');\n console.log(' 3. Create the scenarios repo (private):');\n console.log(` ${scenariosRepo}`);\n console.log(' Copy docs/templates/scenarios/ into it as the repo root.');\n console.log('');\n console.log(' 4. Update autofix.yml with your GitHub App ID if not already set.');\n console.log('');\n console.log(' Run `npx joycraft init-autofix --help` for options.\\n');\n}\n"],"mappings":"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","names":[]}
|