@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.
Files changed (60) hide show
  1. package/README.md +113 -81
  2. package/bin/zoo-flow.js +444 -134
  3. package/package.json +3 -3
  4. package/templates/claude-code/.claude/commands/caveman.md +26 -0
  5. package/templates/claude-code/.claude/commands/commit-and-document.md +27 -0
  6. package/templates/claude-code/.claude/commands/diagnose.md +27 -0
  7. package/templates/claude-code/.claude/commands/feature.md +46 -0
  8. package/templates/claude-code/.claude/commands/fix.md +45 -0
  9. package/templates/claude-code/.claude/commands/grill-me.md +27 -0
  10. package/templates/claude-code/.claude/commands/grill-with-docs.md +27 -0
  11. package/templates/claude-code/.claude/commands/handoff.md +27 -0
  12. package/templates/claude-code/.claude/commands/improve-codebase-architecture.md +27 -0
  13. package/templates/claude-code/.claude/commands/prototype.md +27 -0
  14. package/templates/claude-code/.claude/commands/refactor.md +46 -0
  15. package/templates/claude-code/.claude/commands/review.md +27 -0
  16. package/templates/claude-code/.claude/commands/scaffold-context.md +27 -0
  17. package/templates/claude-code/.claude/commands/setup-matt-pocock-skills.md +27 -0
  18. package/templates/claude-code/.claude/commands/tdd.md +27 -0
  19. package/templates/claude-code/.claude/commands/teach.md +27 -0
  20. package/templates/claude-code/.claude/commands/to-issues.md +27 -0
  21. package/templates/claude-code/.claude/commands/to-prd.md +27 -0
  22. package/templates/claude-code/.claude/commands/triage.md +27 -0
  23. package/templates/claude-code/.claude/commands/tweak.md +27 -0
  24. package/templates/claude-code/.claude/commands/update-docs.md +27 -0
  25. package/templates/claude-code/.claude/commands/verify.md +27 -0
  26. package/templates/claude-code/.claude/commands/write-a-skill.md +27 -0
  27. package/templates/claude-code/.claude/commands/zoom-out.md +27 -0
  28. package/templates/claude-code/.claude/skills/engineering/commit-and-document/SKILL.md +37 -0
  29. package/templates/claude-code/.claude/skills/engineering/diagnose/SKILL.md +102 -0
  30. package/templates/claude-code/.claude/skills/engineering/explore/SKILL.md +61 -0
  31. package/templates/claude-code/.claude/skills/engineering/feature/SKILL.md +66 -0
  32. package/templates/claude-code/.claude/skills/engineering/fix/SKILL.md +59 -0
  33. package/templates/claude-code/.claude/skills/engineering/grill-with-docs/SKILL.md +35 -0
  34. package/templates/claude-code/.claude/skills/engineering/improve-codebase-architecture/SKILL.md +39 -0
  35. package/templates/claude-code/.claude/skills/engineering/prototype/SKILL.md +34 -0
  36. package/templates/claude-code/.claude/skills/engineering/refactor/SKILL.md +59 -0
  37. package/templates/claude-code/.claude/skills/engineering/review/SKILL.md +117 -0
  38. package/templates/claude-code/.claude/skills/engineering/scaffold-context/SKILL.md +44 -0
  39. package/templates/claude-code/.claude/skills/engineering/setup-matt-pocock-skills/SKILL.md +48 -0
  40. package/templates/claude-code/.claude/skills/engineering/tdd/SKILL.md +81 -0
  41. package/templates/claude-code/.claude/skills/engineering/to-issues/SKILL.md +37 -0
  42. package/templates/claude-code/.claude/skills/engineering/to-prd/SKILL.md +39 -0
  43. package/templates/claude-code/.claude/skills/engineering/triage/SKILL.md +36 -0
  44. package/templates/claude-code/.claude/skills/engineering/tweak/SKILL.md +37 -0
  45. package/templates/claude-code/.claude/skills/engineering/update-docs/SKILL.md +33 -0
  46. package/templates/claude-code/.claude/skills/engineering/verify/SKILL.md +38 -0
  47. package/templates/claude-code/.claude/skills/engineering/zoom-out/SKILL.md +34 -0
  48. package/templates/claude-code/.claude/skills/productivity/caveman/SKILL.md +24 -0
  49. package/templates/claude-code/.claude/skills/productivity/grill-me/SKILL.md +21 -0
  50. package/templates/claude-code/.claude/skills/productivity/handoff/SKILL.md +20 -0
  51. package/templates/claude-code/.claude/skills/productivity/teach/SKILL.md +116 -0
  52. package/templates/claude-code/.claude/skills/productivity/write-a-skill/SKILL.md +59 -0
  53. package/templates/claude-code/.zoo-flow/CONTEXT.md +8 -0
  54. package/templates/claude-code/.zoo-flow/START_HERE.md +28 -0
  55. package/templates/claude-code/.zoo-flow/docs/adr/0001-record-architecture-decisions.md +22 -0
  56. package/templates/claude-code/.zoo-flow/evals/no-regression-checklist.md +26 -0
  57. package/templates/claude-code/.zoo-flow/evals/routing-cases.jsonl +18 -0
  58. package/templates/claude-code/.zoo-flow/evals/routing-cases.md +211 -0
  59. package/templates/claude-code/.zoo-flow/project-profile.json +24 -0
  60. 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
- --force Overwrite existing .roomodes and .roo after backup
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
- const sourceRoomodes = path.join(templateRoot, ".roomodes");
552
- const sourceRoo = path.join(templateRoot, ".roo");
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
- if (!pathExists(sourceRoomodes) || !pathExists(sourceRoo)) {
555
- exitWithError("Bundled template is missing templates/full/.roomodes or templates/full/.roo/");
624
+ // If no platform specified, prompt
625
+ if (!platform) {
626
+ platform = await promptPlatform();
556
627
  }
557
628
 
558
- const targetRoomodes = path.join(projectRoot, ".roomodes");
559
- const targetRoo = path.join(projectRoot, ".roo");
560
- const hasExistingConfig = pathExists(targetRoomodes) || pathExists(targetRoo);
629
+ const root = getTemplateRoot(platform);
561
630
 
562
- if (hasExistingConfig && !force) {
563
- console.log(`
564
- Zoo Flow found existing config in this project.
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
- process.exit(0);
573
- }
653
+ process.exit(0);
654
+ }
574
655
 
575
- const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
576
- let didBackup = false;
656
+ const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
657
+ let didBackup = false;
577
658
 
578
- if (hasExistingConfig) {
579
- fs.mkdirSync(backupDir, { recursive: true });
580
- didBackup = backupIfExists(projectRoot, backupDir, ".roomodes") || didBackup;
581
- didBackup = backupIfExists(projectRoot, backupDir, ".roo") || didBackup;
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
- removeRecursive(targetRoomodes);
584
- removeRecursive(targetRoo);
585
- }
664
+ removeRecursive(targetRoomodes);
665
+ removeRecursive(targetRoo);
666
+ }
586
667
 
587
- copyRecursive(sourceRoomodes, targetRoomodes);
588
- copyRecursive(sourceRoo, targetRoo);
668
+ copyRecursive(sourceRoomodes, targetRoomodes);
669
+ copyRecursive(sourceRoo, targetRoo);
589
670
 
590
- const didAddGitignore = appendZooFlowToGitignore(projectRoot);
591
- const didAddZooFlow = copyZooFlowIfMissing(projectRoot);
671
+ const didAddGitignore = appendZooFlowToGitignore(projectRoot);
672
+ const didAddZooFlow = copyZooFlowIfMissing(projectRoot);
592
673
 
593
- const copiedLines = [" - .roomodes", " - .roo/"];
594
- if (didAddZooFlow) {
595
- copiedLines.push(" - .zoo-flow/CONTEXT.md");
596
- copiedLines.push(" - .zoo-flow/docs/adr/0001-record-architecture-decisions.md");
597
- }
598
- if (didAddGitignore) {
599
- copiedLines.push(" - appended .roo/, .roomodes, .zoo-flow/ to .gitignore");
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
- console.log(`
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
- if (!templateOnly) {
628
- // Installed-project checks
629
- const projectRoot = process.cwd();
630
-
631
- // .gitignore check
632
- const gi = path.join(projectRoot, ".gitignore");
633
- if (pathExists(gi)) {
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
- // START_HERE.md check
643
- const startHere = path.join(projectRoot, ".zoo-flow", "START_HERE.md");
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
- // CONTEXT.md check
649
- const contextMd = path.join(projectRoot, ".zoo-flow", "CONTEXT.md");
650
- if (!pathExists(contextMd)) {
651
- optionalInfo.push("info: .zoo-flow/CONTEXT.md missing — run `/scaffold-context` to create project context");
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
- // Project-profile check
655
- const profile = path.join(projectRoot, ".zoo-flow", "project-profile.json");
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
- // LESSONS check
661
- const lessons = path.join(projectRoot, ".zoo-flow", "LESSONS.md");
662
- if (!pathExists(lessons)) {
663
- optionalInfo.push("info: .zoo-flow/LESSONS.md missing — optional, created on demand");
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
- // Verify three modes exist in .roomodes
667
- const roomodesPath = path.join(projectRoot, ".roomodes");
668
- if (pathExists(roomodesPath)) {
669
- try {
670
- const roomodes = readJson(roomodesPath);
671
- const slugs = (roomodes.customModes || []).map((m) => m.slug);
672
- const required = ["custom-orchestrator", "system-architect", "code-tweaker"];
673
- for (const slug of required) {
674
- if (!slugs.includes(slug)) {
675
- failures.push(`.roomodes missing required mode slug: ${slug}`);
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
- } catch (error) {
679
- failures.push(`.roomodes parse error: ${error.message}`);
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
- // .roo/commands and .roo/rules exist
684
- if (!pathExists(path.join(projectRoot, ".roo", "commands"))) {
685
- failures.push(".roo/commands/ missing run `npx @fernado03/zoo-flow@latest update`");
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, ".roo", "rules"))) {
688
- failures.push(".roo/rules/ missing — run `npx @fernado03/zoo-flow@latest update`");
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
- if (failures.length > 0) {
693
- console.error("\nZoo Flow doctor found problems:\n");
694
- for (const failure of failures) {
695
- console.error(`- ${failure}`);
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
- console.error("\nRun `npx @fernado03/zoo-flow@latest update --dry-run` to preview replacing local Zoo Flow config.");
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 ? "bundled template" : "current project";
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 sourceRoomodes = path.join(templateRoot, ".roomodes");
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
- const targetRoomodes = path.join(projectRoot, ".roomodes");
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 (dryRun) {
744
- const rootContext = path.join(projectRoot, "CONTEXT.md");
745
- const rootAdr = path.join(projectRoot, "docs", "adr");
746
- const wouldMigrate =
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
- console.log(`
751
- Zoo Flow update dry run.
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
- process.exit(0);
765
- }
1012
+ process.exit(0);
1013
+ }
766
1014
 
767
- const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
768
- fs.mkdirSync(backupDir, { recursive: true });
1015
+ const backupDir = path.join(projectRoot, ".zoo-flow-backup", makeTimestamp());
1016
+ fs.mkdirSync(backupDir, { recursive: true });
769
1017
 
770
- let didBackup = false;
771
- didBackup = backupIfExists(projectRoot, backupDir, ".roomodes") || didBackup;
772
- didBackup = backupIfExists(projectRoot, backupDir, ".roo") || didBackup;
1018
+ let didBackup = false;
1019
+ didBackup = backupIfExists(projectRoot, backupDir, ".roomodes") || didBackup;
1020
+ didBackup = backupIfExists(projectRoot, backupDir, ".roo") || didBackup;
773
1021
 
774
- removeRecursive(targetRoomodes);
775
- removeRecursive(targetRoo);
1022
+ removeRecursive(targetRoomodes);
1023
+ removeRecursive(targetRoo);
776
1024
 
777
- copyRecursive(sourceRoomodes, targetRoomodes);
778
- copyRecursive(sourceRoo, targetRoo);
1025
+ copyRecursive(sourceRoomodes, targetRoomodes);
1026
+ copyRecursive(sourceRoo, targetRoo);
779
1027
 
780
- const moved = migrateRootContextDocs(projectRoot);
781
- appendZooFlowToGitignore(projectRoot);
1028
+ const moved = migrateRootContextDocs(projectRoot);
1029
+ appendZooFlowToGitignore(projectRoot);
782
1030
 
783
- console.log(`
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") {