@fernado03/zoo-flow 0.9.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -81
- package/bin/zoo-flow.js +444 -134
- package/package.json +3 -3
- package/templates/claude-code/.claude/commands/caveman.md +26 -0
- package/templates/claude-code/.claude/commands/commit-and-document.md +27 -0
- package/templates/claude-code/.claude/commands/diagnose.md +27 -0
- package/templates/claude-code/.claude/commands/feature.md +46 -0
- package/templates/claude-code/.claude/commands/fix.md +45 -0
- package/templates/claude-code/.claude/commands/grill-me.md +27 -0
- package/templates/claude-code/.claude/commands/grill-with-docs.md +27 -0
- package/templates/claude-code/.claude/commands/handoff.md +27 -0
- package/templates/claude-code/.claude/commands/improve-codebase-architecture.md +27 -0
- package/templates/claude-code/.claude/commands/prototype.md +27 -0
- package/templates/claude-code/.claude/commands/refactor.md +46 -0
- package/templates/claude-code/.claude/commands/review.md +27 -0
- package/templates/claude-code/.claude/commands/scaffold-context.md +27 -0
- package/templates/claude-code/.claude/commands/setup-matt-pocock-skills.md +27 -0
- package/templates/claude-code/.claude/commands/tdd.md +27 -0
- package/templates/claude-code/.claude/commands/teach.md +27 -0
- package/templates/claude-code/.claude/commands/to-issues.md +27 -0
- package/templates/claude-code/.claude/commands/to-prd.md +27 -0
- package/templates/claude-code/.claude/commands/triage.md +27 -0
- package/templates/claude-code/.claude/commands/tweak.md +27 -0
- package/templates/claude-code/.claude/commands/update-docs.md +27 -0
- package/templates/claude-code/.claude/commands/verify.md +27 -0
- package/templates/claude-code/.claude/commands/write-a-skill.md +27 -0
- package/templates/claude-code/.claude/commands/zoom-out.md +27 -0
- package/templates/claude-code/.claude/skills/engineering/commit-and-document/SKILL.md +37 -0
- package/templates/claude-code/.claude/skills/engineering/diagnose/SKILL.md +102 -0
- package/templates/claude-code/.claude/skills/engineering/explore/SKILL.md +61 -0
- package/templates/claude-code/.claude/skills/engineering/feature/SKILL.md +66 -0
- package/templates/claude-code/.claude/skills/engineering/fix/SKILL.md +59 -0
- package/templates/claude-code/.claude/skills/engineering/grill-with-docs/SKILL.md +35 -0
- package/templates/claude-code/.claude/skills/engineering/improve-codebase-architecture/SKILL.md +39 -0
- package/templates/claude-code/.claude/skills/engineering/prototype/SKILL.md +34 -0
- package/templates/claude-code/.claude/skills/engineering/refactor/SKILL.md +59 -0
- package/templates/claude-code/.claude/skills/engineering/review/SKILL.md +117 -0
- package/templates/claude-code/.claude/skills/engineering/scaffold-context/SKILL.md +44 -0
- package/templates/claude-code/.claude/skills/engineering/setup-matt-pocock-skills/SKILL.md +48 -0
- package/templates/claude-code/.claude/skills/engineering/tdd/SKILL.md +81 -0
- package/templates/claude-code/.claude/skills/engineering/to-issues/SKILL.md +37 -0
- package/templates/claude-code/.claude/skills/engineering/to-prd/SKILL.md +39 -0
- package/templates/claude-code/.claude/skills/engineering/triage/SKILL.md +36 -0
- package/templates/claude-code/.claude/skills/engineering/tweak/SKILL.md +37 -0
- package/templates/claude-code/.claude/skills/engineering/update-docs/SKILL.md +33 -0
- package/templates/claude-code/.claude/skills/engineering/verify/SKILL.md +38 -0
- package/templates/claude-code/.claude/skills/engineering/zoom-out/SKILL.md +34 -0
- package/templates/claude-code/.claude/skills/productivity/caveman/SKILL.md +24 -0
- package/templates/claude-code/.claude/skills/productivity/grill-me/SKILL.md +21 -0
- package/templates/claude-code/.claude/skills/productivity/handoff/SKILL.md +20 -0
- package/templates/claude-code/.claude/skills/productivity/teach/SKILL.md +116 -0
- package/templates/claude-code/.claude/skills/productivity/write-a-skill/SKILL.md +59 -0
- package/templates/claude-code/.zoo-flow/CONTEXT.md +8 -0
- package/templates/claude-code/.zoo-flow/START_HERE.md +28 -0
- package/templates/claude-code/.zoo-flow/docs/adr/0001-record-architecture-decisions.md +22 -0
- package/templates/claude-code/.zoo-flow/evals/no-regression-checklist.md +26 -0
- package/templates/claude-code/.zoo-flow/evals/routing-cases.jsonl +18 -0
- package/templates/claude-code/.zoo-flow/evals/routing-cases.md +211 -0
- package/templates/claude-code/.zoo-flow/project-profile.json +24 -0
- package/templates/claude-code/CLAUDE.md +237 -0
package/bin/zoo-flow.js
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
9
10
|
const packageRoot = path.resolve(__dirname, "..");
|
|
10
11
|
const templateRoot = path.join(packageRoot, "templates", "full");
|
|
12
|
+
const claudeCodeTemplateRoot = path.join(packageRoot, "templates", "claude-code");
|
|
11
13
|
|
|
12
14
|
const COMMAND_POLICY = {
|
|
13
15
|
routed: {
|
|
@@ -55,30 +57,74 @@ const BUILT_IN_MODE_NAMES = new Set([
|
|
|
55
57
|
"orchestrator"
|
|
56
58
|
]);
|
|
57
59
|
|
|
60
|
+
const CLAUDE_CODE_GITIGNORE_MARKER = "# Zoo Flow — Claude Code config (never committed)";
|
|
61
|
+
const CLAUDE_CODE_GITIGNORE_ENTRIES = [".claude/", ".zoo-flow/"];
|
|
62
|
+
|
|
63
|
+
function detectPlatform(projectRoot) {
|
|
64
|
+
if (pathExists(path.join(projectRoot, "CLAUDE.md"))) return "claude-code";
|
|
65
|
+
if (pathExists(path.join(projectRoot, ".roomodes"))) return "zoo-code";
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function getTemplateRoot(platform) {
|
|
70
|
+
return platform === "claude-code" ? claudeCodeTemplateRoot : templateRoot;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function promptPlatform() {
|
|
74
|
+
const rl = readline.createInterface({
|
|
75
|
+
input: process.stdin,
|
|
76
|
+
output: process.stdout
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return new Promise((resolve) => {
|
|
80
|
+
rl.question(
|
|
81
|
+
"\nZoo Flow — Workflow control plane for AI coding assistants\n\nInstall for which platform?\n 1. Claude Code\n 2. Zoo Code\n\nEnter number (1 or 2): ",
|
|
82
|
+
(answer) => {
|
|
83
|
+
rl.close();
|
|
84
|
+
const choice = answer.trim();
|
|
85
|
+
if (choice === "1") resolve("claude-code");
|
|
86
|
+
else if (choice === "2") resolve("zoo-code");
|
|
87
|
+
else {
|
|
88
|
+
console.log("\nInvalid choice. Please run again and enter 1 or 2.\n");
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
58
96
|
const command = process.argv[2];
|
|
59
97
|
|
|
60
98
|
const HELP = `
|
|
61
|
-
Zoo Flow
|
|
99
|
+
Zoo Flow — Workflow control plane for AI coding assistants
|
|
62
100
|
|
|
63
101
|
Usage:
|
|
64
102
|
npx @fernado03/zoo-flow@latest init
|
|
103
|
+
npx @fernado03/zoo-flow@latest init --platform=claude-code
|
|
104
|
+
npx @fernado03/zoo-flow@latest init --platform=zoo-code
|
|
65
105
|
npx @fernado03/zoo-flow@latest init --force
|
|
66
106
|
npx @fernado03/zoo-flow@latest update
|
|
67
107
|
npx @fernado03/zoo-flow@latest update --dry-run
|
|
68
108
|
npx @fernado03/zoo-flow@latest update --force
|
|
69
109
|
npx @fernado03/zoo-flow@latest doctor
|
|
70
110
|
npx @fernado03/zoo-flow@latest doctor --template-only
|
|
111
|
+
npx @fernado03/zoo-flow@latest doctor --template-only --platform=claude-code
|
|
71
112
|
|
|
72
113
|
Commands:
|
|
73
|
-
init Install Zoo Flow into the current project
|
|
114
|
+
init Install Zoo Flow into the current project (prompts for platform)
|
|
74
115
|
update Back up current config and copy the latest template
|
|
75
116
|
doctor Validate Zoo Flow in the current project
|
|
76
117
|
doctor --template-only Validate this package's bundled template
|
|
77
118
|
|
|
78
119
|
Options:
|
|
79
|
-
--
|
|
120
|
+
--platform=<platform> Install for 'claude-code' or 'zoo-code' (skip prompt)
|
|
121
|
+
--force Overwrite existing config after backup
|
|
80
122
|
--dry-run Print what update would do without changing files
|
|
81
123
|
--template-only Validate this package's template instead of current project
|
|
124
|
+
|
|
125
|
+
Supported platforms:
|
|
126
|
+
Claude Code Installs CLAUDE.md, .claude/commands/, .claude/skills/
|
|
127
|
+
Zoo Code Installs .roomodes, .roo/commands/, .roo/skills/, .roo/rules/
|
|
82
128
|
`;
|
|
83
129
|
|
|
84
130
|
function exitWithError(message) {
|
|
@@ -147,6 +193,22 @@ function appendZooFlowToGitignore(projectRoot) {
|
|
|
147
193
|
return true;
|
|
148
194
|
}
|
|
149
195
|
|
|
196
|
+
function appendClaudeCodeToGitignore(projectRoot) {
|
|
197
|
+
const gi = path.join(projectRoot, ".gitignore");
|
|
198
|
+
|
|
199
|
+
if (pathExists(gi)) {
|
|
200
|
+
const content = fs.readFileSync(gi, "utf8");
|
|
201
|
+
if (content.includes(CLAUDE_CODE_GITIGNORE_MARKER)) return false;
|
|
202
|
+
const addition = `\n${CLAUDE_CODE_GITIGNORE_MARKER}\n${CLAUDE_CODE_GITIGNORE_ENTRIES.join("\n")}\n`;
|
|
203
|
+
fs.appendFileSync(gi, addition);
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const fresh = `${CLAUDE_CODE_GITIGNORE_MARKER}\n${CLAUDE_CODE_GITIGNORE_ENTRIES.join("\n")}\n`;
|
|
208
|
+
fs.writeFileSync(gi, fresh);
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
150
212
|
function migrateRootContextDocs(projectRoot) {
|
|
151
213
|
const moved = [];
|
|
152
214
|
const targetFlowDir = path.join(projectRoot, ".zoo-flow");
|
|
@@ -543,25 +605,44 @@ function validateTemplate(rootDir) {
|
|
|
543
605
|
return failures;
|
|
544
606
|
}
|
|
545
607
|
|
|
546
|
-
function install() {
|
|
608
|
+
async function install() {
|
|
547
609
|
const args = new Set(process.argv.slice(3));
|
|
548
610
|
const force = args.has("--force");
|
|
549
611
|
const projectRoot = process.cwd();
|
|
550
612
|
|
|
551
|
-
|
|
552
|
-
|
|
613
|
+
// Check if platform was specified via --platform flag
|
|
614
|
+
let platform = null;
|
|
615
|
+
for (const arg of process.argv.slice(3)) {
|
|
616
|
+
if (arg.startsWith("--platform=")) {
|
|
617
|
+
platform = arg.substring(11);
|
|
618
|
+
if (platform !== "claude-code" && platform !== "zoo-code") {
|
|
619
|
+
exitWithError(`Invalid platform: ${platform}. Must be 'claude-code' or 'zoo-code'.`);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
}
|
|
553
623
|
|
|
554
|
-
|
|
555
|
-
|
|
624
|
+
// If no platform specified, prompt
|
|
625
|
+
if (!platform) {
|
|
626
|
+
platform = await promptPlatform();
|
|
556
627
|
}
|
|
557
628
|
|
|
558
|
-
const
|
|
559
|
-
const targetRoo = path.join(projectRoot, ".roo");
|
|
560
|
-
const hasExistingConfig = pathExists(targetRoomodes) || pathExists(targetRoo);
|
|
629
|
+
const root = getTemplateRoot(platform);
|
|
561
630
|
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
631
|
+
if (platform === "zoo-code") {
|
|
632
|
+
const sourceRoomodes = path.join(root, ".roomodes");
|
|
633
|
+
const sourceRoo = path.join(root, ".roo");
|
|
634
|
+
|
|
635
|
+
if (!pathExists(sourceRoomodes) || !pathExists(sourceRoo)) {
|
|
636
|
+
exitWithError("Bundled template is missing templates/full/.roomodes or templates/full/.roo/");
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const targetRoomodes = path.join(projectRoot, ".roomodes");
|
|
640
|
+
const targetRoo = path.join(projectRoot, ".roo");
|
|
641
|
+
const hasExistingConfig = pathExists(targetRoomodes) || pathExists(targetRoo);
|
|
642
|
+
|
|
643
|
+
if (hasExistingConfig && !force) {
|
|
644
|
+
console.log(`
|
|
645
|
+
Zoo Flow found existing Zoo Code config in this project.
|
|
565
646
|
|
|
566
647
|
Existing:
|
|
567
648
|
${pathExists(targetRoomodes) ? " - .roomodes\n" : ""}${pathExists(targetRoo) ? " - .roo/\n" : ""}
|
|
@@ -569,38 +650,38 @@ Run again with --force to back up and overwrite:
|
|
|
569
650
|
|
|
570
651
|
npx @fernado03/zoo-flow@latest init --force
|
|
571
652
|
`);
|
|
572
|
-
|
|
573
|
-
|
|
653
|
+
process.exit(0);
|
|
654
|
+
}
|
|
574
655
|
|
|
575
|
-
|
|
576
|
-
|
|
656
|
+
const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
|
|
657
|
+
let didBackup = false;
|
|
577
658
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
659
|
+
if (hasExistingConfig) {
|
|
660
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
661
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".roomodes") || didBackup;
|
|
662
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".roo") || didBackup;
|
|
582
663
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
664
|
+
removeRecursive(targetRoomodes);
|
|
665
|
+
removeRecursive(targetRoo);
|
|
666
|
+
}
|
|
586
667
|
|
|
587
|
-
|
|
588
|
-
|
|
668
|
+
copyRecursive(sourceRoomodes, targetRoomodes);
|
|
669
|
+
copyRecursive(sourceRoo, targetRoo);
|
|
589
670
|
|
|
590
|
-
|
|
591
|
-
|
|
671
|
+
const didAddGitignore = appendZooFlowToGitignore(projectRoot);
|
|
672
|
+
const didAddZooFlow = copyZooFlowIfMissing(projectRoot);
|
|
592
673
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
674
|
+
const copiedLines = [" - .roomodes", " - .roo/"];
|
|
675
|
+
if (didAddZooFlow) {
|
|
676
|
+
copiedLines.push(" - .zoo-flow/CONTEXT.md");
|
|
677
|
+
copiedLines.push(" - .zoo-flow/docs/adr/0001-record-architecture-decisions.md");
|
|
678
|
+
}
|
|
679
|
+
if (didAddGitignore) {
|
|
680
|
+
copiedLines.push(" - appended .roo/, .roomodes, .zoo-flow/ to .gitignore");
|
|
681
|
+
}
|
|
601
682
|
|
|
602
|
-
|
|
603
|
-
Zoo Flow installed.
|
|
683
|
+
console.log(`
|
|
684
|
+
Zoo Flow installed for Zoo Code.
|
|
604
685
|
|
|
605
686
|
Copied:
|
|
606
687
|
${copiedLines.join("\n")}
|
|
@@ -615,93 +696,256 @@ Next:
|
|
|
615
696
|
|
|
616
697
|
When workflow choices appear, type the number manually, e.g. 1.
|
|
617
698
|
`);
|
|
699
|
+
} else {
|
|
700
|
+
// Claude Code installation
|
|
701
|
+
const sourceClaudeMd = path.join(root, "CLAUDE.md");
|
|
702
|
+
const sourceClaude = path.join(root, ".claude");
|
|
703
|
+
const sourceZooFlow = path.join(root, ".zoo-flow");
|
|
704
|
+
|
|
705
|
+
if (!pathExists(sourceClaudeMd) || !pathExists(sourceClaude)) {
|
|
706
|
+
exitWithError("Bundled template is missing templates/claude-code/CLAUDE.md or templates/claude-code/.claude/");
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const targetClaudeMd = path.join(projectRoot, "CLAUDE.md");
|
|
710
|
+
const targetClaude = path.join(projectRoot, ".claude");
|
|
711
|
+
const targetZooFlow = path.join(projectRoot, ".zoo-flow");
|
|
712
|
+
const hasExistingConfig = pathExists(targetClaudeMd) || pathExists(targetClaude);
|
|
713
|
+
|
|
714
|
+
if (hasExistingConfig && !force) {
|
|
715
|
+
console.log(`
|
|
716
|
+
Zoo Flow found existing Claude Code config in this project.
|
|
717
|
+
|
|
718
|
+
Existing:
|
|
719
|
+
${pathExists(targetClaudeMd) ? " - CLAUDE.md\n" : ""}${pathExists(targetClaude) ? " - .claude/\n" : ""}
|
|
720
|
+
Run again with --force to back up and overwrite:
|
|
721
|
+
|
|
722
|
+
npx @fernado03/zoo-flow@latest init --force
|
|
723
|
+
`);
|
|
724
|
+
process.exit(0);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
|
|
728
|
+
let didBackup = false;
|
|
729
|
+
|
|
730
|
+
if (hasExistingConfig) {
|
|
731
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
732
|
+
didBackup = backupIfExists(projectRoot, backupDir, "CLAUDE.md") || didBackup;
|
|
733
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".claude") || didBackup;
|
|
734
|
+
|
|
735
|
+
removeRecursive(targetClaudeMd);
|
|
736
|
+
removeRecursive(targetClaude);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
copyRecursive(sourceClaudeMd, targetClaudeMd);
|
|
740
|
+
copyRecursive(sourceClaude, targetClaude);
|
|
741
|
+
|
|
742
|
+
// Copy .zoo-flow if missing
|
|
743
|
+
let didAddZooFlow = false;
|
|
744
|
+
if (!pathExists(targetZooFlow) && pathExists(sourceZooFlow)) {
|
|
745
|
+
copyRecursive(sourceZooFlow, targetZooFlow);
|
|
746
|
+
didAddZooFlow = true;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Append to .gitignore
|
|
750
|
+
const didAddGitignore = appendClaudeCodeToGitignore(projectRoot);
|
|
751
|
+
|
|
752
|
+
const copiedLines = [" - CLAUDE.md", " - .claude/"];
|
|
753
|
+
if (didAddZooFlow) {
|
|
754
|
+
copiedLines.push(" - .zoo-flow/CONTEXT.md");
|
|
755
|
+
copiedLines.push(" - .zoo-flow/docs/adr/0001-record-architecture-decisions.md");
|
|
756
|
+
}
|
|
757
|
+
if (didAddGitignore) {
|
|
758
|
+
copiedLines.push(" - appended .claude/, .zoo-flow/ to .gitignore");
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
console.log(`
|
|
762
|
+
Zoo Flow installed for Claude Code.
|
|
763
|
+
|
|
764
|
+
Copied:
|
|
765
|
+
${copiedLines.join("\n")}
|
|
766
|
+
${didBackup ? `\nBackup:\n ${backupDir}\n` : ""}
|
|
767
|
+
Next:
|
|
768
|
+
1. Open .zoo-flow/START_HERE.md (first-run guide)
|
|
769
|
+
2. Restart Claude Code (or open in a new session)
|
|
770
|
+
3. Try a small request, e.g.:
|
|
771
|
+
change a harmless comment in README
|
|
772
|
+
4. Or use a slash command like /tweak or /fix
|
|
773
|
+
|
|
774
|
+
When workflow choices appear, type the number manually, e.g. 1.
|
|
775
|
+
`);
|
|
776
|
+
}
|
|
618
777
|
}
|
|
619
778
|
|
|
620
779
|
function doctor() {
|
|
621
780
|
const args = new Set(process.argv.slice(3));
|
|
622
781
|
const templateOnly = args.has("--template-only");
|
|
623
|
-
const rootToCheck = templateOnly ? templateRoot : process.cwd();
|
|
624
|
-
const failures = validateTemplate(rootToCheck);
|
|
625
782
|
const optionalInfo = [];
|
|
626
783
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const giContent = fs.readFileSync(gi, "utf8");
|
|
635
|
-
if (!giContent.includes(ZOO_FLOW_GITIGNORE_MARKER)) {
|
|
636
|
-
optionalInfo.push("info: .gitignore does not contain Zoo Flow entries (.roomodes, .roo/, .zoo-flow/, docs/agents/, AGENTS.md)");
|
|
784
|
+
// Platform detection for --template-only
|
|
785
|
+
let platform = null;
|
|
786
|
+
for (const arg of process.argv.slice(3)) {
|
|
787
|
+
if (arg.startsWith("--platform=")) {
|
|
788
|
+
platform = arg.substring(11);
|
|
789
|
+
if (platform !== "claude-code" && platform !== "zoo-code") {
|
|
790
|
+
exitWithError(`Invalid platform: ${platform}. Must be 'claude-code' or 'zoo-code'.`);
|
|
637
791
|
}
|
|
638
|
-
} else {
|
|
639
|
-
optionalInfo.push("info: .gitignore missing — run `npx @fernado03/zoo-flow@latest init` to create it");
|
|
640
792
|
}
|
|
793
|
+
}
|
|
641
794
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
if (!pathExists(startHere)) {
|
|
645
|
-
optionalInfo.push("info: .zoo-flow/START_HERE.md missing — run `npx @fernado03/zoo-flow@latest init` to install starter docs");
|
|
646
|
-
}
|
|
795
|
+
let rootToCheck;
|
|
796
|
+
let detectedPlatform;
|
|
647
797
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
798
|
+
if (templateOnly) {
|
|
799
|
+
detectedPlatform = platform || "zoo-code";
|
|
800
|
+
rootToCheck = getTemplateRoot(detectedPlatform);
|
|
801
|
+
} else {
|
|
802
|
+
const projectRoot = process.cwd();
|
|
803
|
+
detectedPlatform = detectPlatform(projectRoot);
|
|
653
804
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
if (!pathExists(profile)) {
|
|
657
|
-
optionalInfo.push("info: .zoo-flow/project-profile.json missing — run `/setup-matt-pocock-skills` to configure");
|
|
805
|
+
if (!detectedPlatform) {
|
|
806
|
+
exitWithError("No Zoo Flow installation found. Run `npx @fernado03/zoo-flow@latest init` first.");
|
|
658
807
|
}
|
|
659
808
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
809
|
+
rootToCheck = projectRoot;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (detectedPlatform === "zoo-code") {
|
|
813
|
+
// Zoo Code validation (existing)
|
|
814
|
+
const failures = validateTemplate(rootToCheck);
|
|
815
|
+
|
|
816
|
+
if (!templateOnly) {
|
|
817
|
+
const projectRoot = process.cwd();
|
|
818
|
+
|
|
819
|
+
const gi = path.join(projectRoot, ".gitignore");
|
|
820
|
+
if (pathExists(gi)) {
|
|
821
|
+
const giContent = fs.readFileSync(gi, "utf8");
|
|
822
|
+
if (!giContent.includes(ZOO_FLOW_GITIGNORE_MARKER)) {
|
|
823
|
+
optionalInfo.push("info: .gitignore does not contain Zoo Flow entries (.roomodes, .roo/, .zoo-flow/, docs/agents/, AGENTS.md)");
|
|
824
|
+
}
|
|
825
|
+
} else {
|
|
826
|
+
optionalInfo.push("info: .gitignore missing — run `npx @fernado03/zoo-flow@latest init` to create it");
|
|
827
|
+
}
|
|
665
828
|
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
829
|
+
const startHere = path.join(projectRoot, ".zoo-flow", "START_HERE.md");
|
|
830
|
+
if (!pathExists(startHere)) {
|
|
831
|
+
optionalInfo.push("info: .zoo-flow/START_HERE.md missing — run `npx @fernado03/zoo-flow@latest init` to install starter docs");
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
const contextMd = path.join(projectRoot, ".zoo-flow", "CONTEXT.md");
|
|
835
|
+
if (!pathExists(contextMd)) {
|
|
836
|
+
optionalInfo.push("info: .zoo-flow/CONTEXT.md missing — run `/scaffold-context` to create project context");
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
const profile = path.join(projectRoot, ".zoo-flow", "project-profile.json");
|
|
840
|
+
if (!pathExists(profile)) {
|
|
841
|
+
optionalInfo.push("info: .zoo-flow/project-profile.json missing — run `/setup-matt-pocock-skills` to configure");
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
const lessons = path.join(projectRoot, ".zoo-flow", "LESSONS.md");
|
|
845
|
+
if (!pathExists(lessons)) {
|
|
846
|
+
optionalInfo.push("info: .zoo-flow/LESSONS.md missing — optional, created on demand");
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const roomodesPath = path.join(projectRoot, ".roomodes");
|
|
850
|
+
if (pathExists(roomodesPath)) {
|
|
851
|
+
try {
|
|
852
|
+
const roomodes = readJson(roomodesPath);
|
|
853
|
+
const slugs = (roomodes.customModes || []).map((m) => m.slug);
|
|
854
|
+
const required = ["custom-orchestrator", "system-architect", "code-tweaker"];
|
|
855
|
+
for (const slug of required) {
|
|
856
|
+
if (!slugs.includes(slug)) {
|
|
857
|
+
failures.push(`.roomodes missing required mode slug: ${slug}`);
|
|
858
|
+
}
|
|
676
859
|
}
|
|
860
|
+
} catch (error) {
|
|
861
|
+
failures.push(`.roomodes parse error: ${error.message}`);
|
|
677
862
|
}
|
|
678
|
-
}
|
|
679
|
-
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
if (!pathExists(path.join(projectRoot, ".roo", "commands"))) {
|
|
866
|
+
failures.push(".roo/commands/ missing — run `npx @fernado03/zoo-flow@latest update`");
|
|
867
|
+
}
|
|
868
|
+
if (!pathExists(path.join(projectRoot, ".roo", "rules"))) {
|
|
869
|
+
failures.push(".roo/rules/ missing — run `npx @fernado03/zoo-flow@latest update`");
|
|
680
870
|
}
|
|
681
871
|
}
|
|
682
872
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
873
|
+
if (failures.length > 0) {
|
|
874
|
+
console.error("\nZoo Flow doctor found problems (Zoo Code):\n");
|
|
875
|
+
for (const failure of failures) {
|
|
876
|
+
console.error(`- ${failure}`);
|
|
877
|
+
}
|
|
878
|
+
if (!templateOnly) {
|
|
879
|
+
console.error("\nRun `npx @fernado03/zoo-flow@latest update --dry-run` to preview replacing local Zoo Flow config.");
|
|
880
|
+
}
|
|
881
|
+
console.error("");
|
|
882
|
+
process.exit(1);
|
|
883
|
+
}
|
|
884
|
+
} else {
|
|
885
|
+
// Claude Code validation
|
|
886
|
+
const failures = [];
|
|
887
|
+
const projectRoot = templateOnly ? rootToCheck : rootToCheck;
|
|
888
|
+
|
|
889
|
+
if (!pathExists(path.join(projectRoot, "CLAUDE.md"))) {
|
|
890
|
+
failures.push("Missing CLAUDE.md");
|
|
686
891
|
}
|
|
687
|
-
if (!pathExists(path.join(projectRoot, ".
|
|
688
|
-
failures.push(".
|
|
892
|
+
if (!pathExists(path.join(projectRoot, ".claude", "commands"))) {
|
|
893
|
+
failures.push("Missing .claude/commands/");
|
|
894
|
+
}
|
|
895
|
+
if (!pathExists(path.join(projectRoot, ".claude", "skills"))) {
|
|
896
|
+
failures.push("Missing .claude/skills/");
|
|
689
897
|
}
|
|
690
|
-
}
|
|
691
898
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
899
|
+
// Validate command files exist and reference valid skill paths
|
|
900
|
+
const commandsDir = path.join(projectRoot, ".claude", "commands");
|
|
901
|
+
const skillRefRegex = /`?\.claude\/skills\/[^\s`]+SKILL\.md`?/g;
|
|
902
|
+
if (pathExists(commandsDir)) {
|
|
903
|
+
for (const entry of fs.readdirSync(commandsDir)) {
|
|
904
|
+
if (!entry.endsWith(".md")) continue;
|
|
905
|
+
const commandFile = path.join(commandsDir, entry);
|
|
906
|
+
const text = fs.readFileSync(commandFile, "utf8");
|
|
907
|
+
const matches = text.matchAll(skillRefRegex);
|
|
908
|
+
for (const match of matches) {
|
|
909
|
+
const skillPath = match[0].replace(/`/g, "");
|
|
910
|
+
const skillAbsolute = path.join(projectRoot, skillPath);
|
|
911
|
+
if (!pathExists(skillAbsolute)) {
|
|
912
|
+
failures.push(`Command .claude/commands/${entry} references missing skill: ${skillPath}`);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
}
|
|
696
916
|
}
|
|
917
|
+
|
|
697
918
|
if (!templateOnly) {
|
|
698
|
-
|
|
919
|
+
const gi = path.join(projectRoot, ".gitignore");
|
|
920
|
+
if (pathExists(gi)) {
|
|
921
|
+
const giContent = fs.readFileSync(gi, "utf8");
|
|
922
|
+
if (!giContent.includes(CLAUDE_CODE_GITIGNORE_MARKER)) {
|
|
923
|
+
optionalInfo.push("info: .gitignore does not contain Zoo Flow Claude Code entries (.claude/, .zoo-flow/)");
|
|
924
|
+
}
|
|
925
|
+
} else {
|
|
926
|
+
optionalInfo.push("info: .gitignore missing — run `npx @fernado03/zoo-flow@latest init` to create it");
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const startHere = path.join(projectRoot, ".zoo-flow", "START_HERE.md");
|
|
930
|
+
if (!pathExists(startHere)) {
|
|
931
|
+
optionalInfo.push("info: .zoo-flow/START_HERE.md missing — run `npx @fernado03/zoo-flow@latest init` to install starter docs");
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
if (failures.length > 0) {
|
|
936
|
+
console.error("\nZoo Flow doctor found problems (Claude Code):\n");
|
|
937
|
+
for (const failure of failures) {
|
|
938
|
+
console.error(`- ${failure}`);
|
|
939
|
+
}
|
|
940
|
+
if (!templateOnly) {
|
|
941
|
+
console.error("\nRun `npx @fernado03/zoo-flow@latest update --dry-run` to preview replacing local Zoo Flow config.");
|
|
942
|
+
}
|
|
943
|
+
console.error("");
|
|
944
|
+
process.exit(1);
|
|
699
945
|
}
|
|
700
|
-
console.error("");
|
|
701
|
-
process.exit(1);
|
|
702
946
|
}
|
|
703
947
|
|
|
704
|
-
const targetName = templateOnly ?
|
|
948
|
+
const targetName = templateOnly ? `bundled template (${detectedPlatform})` : `current project (${detectedPlatform})`;
|
|
705
949
|
console.log(`Zoo Flow doctor passed for ${targetName}: ${rootToCheck}`);
|
|
706
950
|
|
|
707
951
|
if (optionalInfo.length > 0) {
|
|
@@ -718,19 +962,9 @@ function update() {
|
|
|
718
962
|
const dryRun = args.has("--dry-run");
|
|
719
963
|
const projectRoot = process.cwd();
|
|
720
964
|
|
|
721
|
-
const
|
|
722
|
-
const sourceRoo = path.join(templateRoot, ".roo");
|
|
723
|
-
|
|
724
|
-
if (!pathExists(sourceRoomodes) || !pathExists(sourceRoo)) {
|
|
725
|
-
exitWithError("Bundled template is missing templates/full/.roomodes or templates/full/.roo/");
|
|
726
|
-
}
|
|
965
|
+
const platform = detectPlatform(projectRoot);
|
|
727
966
|
|
|
728
|
-
|
|
729
|
-
const targetRoo = path.join(projectRoot, ".roo");
|
|
730
|
-
const hasRoomodes = pathExists(targetRoomodes);
|
|
731
|
-
const hasRoo = pathExists(targetRoo);
|
|
732
|
-
|
|
733
|
-
if (!hasRoomodes && !hasRoo) {
|
|
967
|
+
if (!platform) {
|
|
734
968
|
console.log(`
|
|
735
969
|
Zoo Flow is not installed in this project yet.
|
|
736
970
|
|
|
@@ -740,15 +974,29 @@ Run:
|
|
|
740
974
|
process.exit(0);
|
|
741
975
|
}
|
|
742
976
|
|
|
743
|
-
if (
|
|
744
|
-
const
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
(pathExists(rootContext) ? ["CONTEXT.md"] : [])
|
|
748
|
-
.concat(pathExists(rootAdr) ? ["docs/adr/"] : []);
|
|
977
|
+
if (platform === "zoo-code") {
|
|
978
|
+
const root = getTemplateRoot("zoo-code");
|
|
979
|
+
const sourceRoomodes = path.join(root, ".roomodes");
|
|
980
|
+
const sourceRoo = path.join(root, ".roo");
|
|
749
981
|
|
|
750
|
-
|
|
751
|
-
|
|
982
|
+
if (!pathExists(sourceRoomodes) || !pathExists(sourceRoo)) {
|
|
983
|
+
exitWithError("Bundled template is missing templates/full/.roomodes or templates/full/.roo/");
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
const targetRoomodes = path.join(projectRoot, ".roomodes");
|
|
987
|
+
const targetRoo = path.join(projectRoot, ".roo");
|
|
988
|
+
const hasRoomodes = pathExists(targetRoomodes);
|
|
989
|
+
const hasRoo = pathExists(targetRoo);
|
|
990
|
+
|
|
991
|
+
if (dryRun) {
|
|
992
|
+
const rootContext = path.join(projectRoot, "CONTEXT.md");
|
|
993
|
+
const rootAdr = path.join(projectRoot, "docs", "adr");
|
|
994
|
+
const wouldMigrate =
|
|
995
|
+
(pathExists(rootContext) ? ["CONTEXT.md"] : [])
|
|
996
|
+
.concat(pathExists(rootAdr) ? ["docs/adr/"] : []);
|
|
997
|
+
|
|
998
|
+
console.log(`
|
|
999
|
+
Zoo Flow update dry run (Zoo Code).
|
|
752
1000
|
|
|
753
1001
|
Would back up:
|
|
754
1002
|
${hasRoomodes ? " - .roomodes\n" : ""}${hasRoo ? " - .roo/\n" : ""}
|
|
@@ -761,27 +1009,27 @@ Would append .roo/, .roomodes, .zoo-flow/ to .gitignore (idempotent).
|
|
|
761
1009
|
Run this to update:
|
|
762
1010
|
npx @fernado03/zoo-flow@latest update
|
|
763
1011
|
`);
|
|
764
|
-
|
|
765
|
-
|
|
1012
|
+
process.exit(0);
|
|
1013
|
+
}
|
|
766
1014
|
|
|
767
|
-
|
|
768
|
-
|
|
1015
|
+
const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
|
|
1016
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
769
1017
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
1018
|
+
let didBackup = false;
|
|
1019
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".roomodes") || didBackup;
|
|
1020
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".roo") || didBackup;
|
|
773
1021
|
|
|
774
|
-
|
|
775
|
-
|
|
1022
|
+
removeRecursive(targetRoomodes);
|
|
1023
|
+
removeRecursive(targetRoo);
|
|
776
1024
|
|
|
777
|
-
|
|
778
|
-
|
|
1025
|
+
copyRecursive(sourceRoomodes, targetRoomodes);
|
|
1026
|
+
copyRecursive(sourceRoo, targetRoo);
|
|
779
1027
|
|
|
780
|
-
|
|
781
|
-
|
|
1028
|
+
const moved = migrateRootContextDocs(projectRoot);
|
|
1029
|
+
appendZooFlowToGitignore(projectRoot);
|
|
782
1030
|
|
|
783
|
-
|
|
784
|
-
Zoo Flow updated.
|
|
1031
|
+
console.log(`
|
|
1032
|
+
Zoo Flow updated (Zoo Code).
|
|
785
1033
|
|
|
786
1034
|
Replaced:
|
|
787
1035
|
- .roomodes
|
|
@@ -792,6 +1040,65 @@ ${didBackup ? `Backup:\n ${backupDir}\n\nTo restore the previous config:\n rm
|
|
|
792
1040
|
2. Open Zoo Code
|
|
793
1041
|
3. Confirm the three custom modes still appear
|
|
794
1042
|
`);
|
|
1043
|
+
} else {
|
|
1044
|
+
// Claude Code update
|
|
1045
|
+
const root = getTemplateRoot("claude-code");
|
|
1046
|
+
const sourceClaudeMd = path.join(root, "CLAUDE.md");
|
|
1047
|
+
const sourceClaude = path.join(root, ".claude");
|
|
1048
|
+
|
|
1049
|
+
if (!pathExists(sourceClaudeMd) || !pathExists(sourceClaude)) {
|
|
1050
|
+
exitWithError("Bundled template is missing templates/claude-code/CLAUDE.md or templates/claude-code/.claude/");
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
const targetClaudeMd = path.join(projectRoot, "CLAUDE.md");
|
|
1054
|
+
const targetClaude = path.join(projectRoot, ".claude");
|
|
1055
|
+
const hasClaudeMd = pathExists(targetClaudeMd);
|
|
1056
|
+
const hasClaude = pathExists(targetClaude);
|
|
1057
|
+
|
|
1058
|
+
if (dryRun) {
|
|
1059
|
+
console.log(`
|
|
1060
|
+
Zoo Flow update dry run (Claude Code).
|
|
1061
|
+
|
|
1062
|
+
Would back up:
|
|
1063
|
+
${hasClaudeMd ? " - CLAUDE.md\n" : ""}${hasClaude ? " - .claude/\n" : ""}
|
|
1064
|
+
Would replace with latest template:
|
|
1065
|
+
- CLAUDE.md
|
|
1066
|
+
- .claude/
|
|
1067
|
+
|
|
1068
|
+
Would append .claude/, .zoo-flow/ to .gitignore (idempotent).
|
|
1069
|
+
|
|
1070
|
+
Run this to update:
|
|
1071
|
+
npx @fernado03/zoo-flow@latest update
|
|
1072
|
+
`);
|
|
1073
|
+
process.exit(0);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
|
|
1077
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
1078
|
+
|
|
1079
|
+
let didBackup = false;
|
|
1080
|
+
didBackup = backupIfExists(projectRoot, backupDir, "CLAUDE.md") || didBackup;
|
|
1081
|
+
didBackup = backupIfExists(projectRoot, backupDir, ".claude") || didBackup;
|
|
1082
|
+
|
|
1083
|
+
removeRecursive(targetClaudeMd);
|
|
1084
|
+
removeRecursive(targetClaude);
|
|
1085
|
+
|
|
1086
|
+
copyRecursive(sourceClaudeMd, targetClaudeMd);
|
|
1087
|
+
copyRecursive(sourceClaude, targetClaude);
|
|
1088
|
+
|
|
1089
|
+
appendClaudeCodeToGitignore(projectRoot);
|
|
1090
|
+
|
|
1091
|
+
console.log(`
|
|
1092
|
+
Zoo Flow updated (Claude Code).
|
|
1093
|
+
|
|
1094
|
+
Replaced:
|
|
1095
|
+
- CLAUDE.md
|
|
1096
|
+
- .claude/
|
|
1097
|
+
${didBackup ? `Backup:\n ${backupDir}\n\nTo restore the previous config:\n rm -rf CLAUDE.md .claude\n cp -R ${backupDir}/. .\n` : ""}Next:
|
|
1098
|
+
1. Restart Claude Code (or open in a new session)
|
|
1099
|
+
2. Confirm slash commands are available
|
|
1100
|
+
`);
|
|
1101
|
+
}
|
|
795
1102
|
}
|
|
796
1103
|
|
|
797
1104
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -800,7 +1107,10 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
800
1107
|
}
|
|
801
1108
|
|
|
802
1109
|
if (command === "init") {
|
|
803
|
-
install()
|
|
1110
|
+
install().catch((error) => {
|
|
1111
|
+
console.error(`\nError: ${error.message}\n`);
|
|
1112
|
+
process.exit(1);
|
|
1113
|
+
});
|
|
804
1114
|
} else if (command === "update") {
|
|
805
1115
|
update();
|
|
806
1116
|
} else if (command === "doctor") {
|