farmwork 1.0.0 → 1.0.2

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/src/status.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "fs-extra";
2
2
  import path from "path";
3
- import chalk from "chalk";
4
3
  import { execSync } from "child_process";
4
+ import { farmTerm, emojis } from "./terminal.js";
5
5
 
6
6
  function countFiles(dir, pattern) {
7
7
  try {
@@ -45,21 +45,21 @@ function getBeadsStatus(cwd) {
45
45
  }
46
46
  }
47
47
 
48
- function readFarmhouse(cwd) {
49
- const farmhousePath = path.join(cwd, "_AUDIT", "FARMHOUSE.md");
50
- if (!fs.existsSync(farmhousePath)) return null;
48
+ function readAuditFile(cwd, filename) {
49
+ const filePath = path.join(cwd, "_AUDIT", filename);
50
+ if (!fs.existsSync(filePath)) return null;
51
51
 
52
52
  try {
53
- const content = fs.readFileSync(farmhousePath, "utf8");
53
+ const content = fs.readFileSync(filePath, "utf8");
54
54
  const scoreMatch = content.match(/\*\*Score:\*\* (\d+\.?\d*)\/10/);
55
- const statusMatch = content.match(/\*\*Status:\*\* (\d+) open items/);
55
+ const statusMatch = content.match(/\*\*Status:\*\* (.+)/);
56
56
  const lastUpdatedMatch = content.match(
57
57
  /\*\*Last Updated:\*\* (\d{4}-\d{2}-\d{2})/,
58
58
  );
59
59
 
60
60
  return {
61
61
  score: scoreMatch ? parseFloat(scoreMatch[1]) : null,
62
- openItems: statusMatch ? parseInt(statusMatch[1]) : null,
62
+ status: statusMatch ? statusMatch[1].trim() : null,
63
63
  lastUpdated: lastUpdatedMatch ? lastUpdatedMatch[1] : null,
64
64
  };
65
65
  } catch {
@@ -67,17 +67,33 @@ function readFarmhouse(cwd) {
67
67
  }
68
68
  }
69
69
 
70
+ function countJustfileRecipes(cwd) {
71
+ const justfilePath = path.join(cwd, "justfile");
72
+ if (!fs.existsSync(justfilePath)) return 0;
73
+
74
+ try {
75
+ const content = fs.readFileSync(justfilePath, "utf8");
76
+ const recipes = content.match(/^[a-zA-Z_][a-zA-Z0-9_-]*\s*:/gm);
77
+ return recipes ? recipes.length : 0;
78
+ } catch {
79
+ return 0;
80
+ }
81
+ }
82
+
70
83
  export async function status() {
71
84
  const cwd = process.cwd();
72
85
 
73
86
  const claudeDir = path.join(cwd, ".claude");
74
87
  if (!fs.existsSync(claudeDir)) {
75
- console.log(chalk.red("\nāŒ Farmwork not initialized"));
76
- console.log(chalk.gray(" Run: farmwork init"));
88
+ farmTerm.error("Farmwork not initialized");
89
+ farmTerm.info("Run: farmwork init");
77
90
  return;
78
91
  }
79
92
 
80
- console.log(chalk.cyan("\nšŸ“Š Farmwork Status\n"));
93
+ // Header with logo
94
+ farmTerm.logo();
95
+ farmTerm.header("FARMWORK STATUS", "primary");
96
+ await farmTerm.analyzing("Scanning project", 800);
81
97
 
82
98
  const agentsDir = path.join(claudeDir, "agents");
83
99
  const commandsDir = path.join(claudeDir, "commands");
@@ -89,159 +105,106 @@ export async function status() {
89
105
  const commands = countMarkdownFiles(commandsDir);
90
106
  const audits = countMarkdownFiles(auditDir);
91
107
  const plans = countMarkdownFiles(plansDir);
108
+ const recipes = countJustfileRecipes(cwd);
92
109
 
93
- console.log(chalk.white("ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
94
- console.log(
95
- chalk.white("│") +
96
- chalk.bold(" Component Counts ") +
97
- chalk.white("│"),
98
- );
99
- console.log(chalk.white("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤"));
100
- console.log(
101
- chalk.white("│") +
102
- ` Agents (.claude/agents/) ${chalk.green(String(agents).padStart(8))} ` +
103
- chalk.white("│"),
104
- );
105
- console.log(
106
- chalk.white("│") +
107
- ` Commands (.claude/commands/) ${chalk.green(String(commands).padStart(8))} ` +
108
- chalk.white("│"),
109
- );
110
- console.log(
111
- chalk.white("│") +
112
- ` Audit Docs (_AUDIT/) ${chalk.green(String(audits).padStart(8))} ` +
113
- chalk.white("│"),
114
- );
115
- console.log(
116
- chalk.white("│") +
117
- ` Plans (_PLANS/) ${chalk.yellow(String(plans).padStart(8))} ` +
118
- chalk.white("│"),
119
- );
120
- console.log(chalk.white("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"));
110
+ // Component Counts Section
111
+ farmTerm.section("Component Counts", emojis.corn);
112
+ farmTerm.metric("Agents", agents, emojis.horse);
113
+ farmTerm.metric("Commands", commands, emojis.bee);
114
+ farmTerm.metric("Justfile Recipes", recipes, emojis.sheep);
115
+ farmTerm.metric("Audit Docs", audits, emojis.wheat);
116
+ farmTerm.metric("Plans", plans, emojis.sunflower);
121
117
 
118
+ // Issue Tracking Section
122
119
  if (fs.existsSync(beadsDir)) {
123
120
  const beads = getBeadsStatus(cwd);
124
- console.log(chalk.white("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
125
- console.log(
126
- chalk.white("│") +
127
- chalk.bold(" Issue Tracking (beads) ") +
128
- chalk.white("│"),
129
- );
130
- console.log(chalk.white("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤"));
131
- console.log(
132
- chalk.white("│") +
133
- ` Open Issues ${chalk.yellow(String(beads.open).padStart(8))} ` +
134
- chalk.white("│"),
135
- );
136
- console.log(
137
- chalk.white("│") +
138
- ` Closed Issues ${chalk.green(String(beads.closed).padStart(8))} ` +
139
- chalk.white("│"),
140
- );
141
- console.log(chalk.white("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"));
121
+ farmTerm.section("Issue Tracking", emojis.pig);
122
+ farmTerm.metric("Open Issues", beads.open, emojis.apple);
123
+ farmTerm.metric("Closed Issues", beads.closed, emojis.lettuce);
124
+
125
+ if (beads.open + beads.closed > 0) {
126
+ const completionRate = Math.round((beads.closed / (beads.open + beads.closed)) * 100);
127
+ farmTerm.nl();
128
+ farmTerm.score("Completion", completionRate, 100);
129
+ }
142
130
  }
143
131
 
144
- const farmhouse = readFarmhouse(cwd);
145
- if (farmhouse) {
146
- console.log(chalk.white("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
147
- console.log(
148
- chalk.white("│") +
149
- chalk.bold(" FARMHOUSE Status ") +
150
- chalk.white("│"),
151
- );
152
- console.log(chalk.white("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤"));
153
- if (farmhouse.score !== null) {
154
- const scoreColor =
155
- farmhouse.score >= 8
156
- ? chalk.green
157
- : farmhouse.score >= 5
158
- ? chalk.yellow
159
- : chalk.red;
160
- console.log(
161
- chalk.white("│") +
162
- ` Score ${scoreColor(String(farmhouse.score + "/10").padStart(8))} ` +
163
- chalk.white("│"),
164
- );
165
- }
166
- if (farmhouse.openItems !== null) {
167
- const itemsColor = farmhouse.openItems === 0 ? chalk.green : chalk.yellow;
168
- console.log(
169
- chalk.white("│") +
170
- ` Open Items ${itemsColor(String(farmhouse.openItems).padStart(8))} ` +
171
- chalk.white("│"),
172
- );
173
- }
174
- if (farmhouse.lastUpdated) {
175
- console.log(
176
- chalk.white("│") +
177
- ` Last Updated ${chalk.gray(farmhouse.lastUpdated)} ` +
178
- chalk.white("│"),
179
- );
132
+ // Audit Scores Section
133
+ const auditFiles = [
134
+ { name: "FARMHOUSE.md", label: "Farmhouse" },
135
+ { name: "CODE_QUALITY.md", label: "Code Quality" },
136
+ { name: "SECURITY.md", label: "Security" },
137
+ { name: "PERFORMANCE.md", label: "Performance" },
138
+ { name: "TESTS.md", label: "Tests" },
139
+ ];
140
+
141
+ const auditData = auditFiles
142
+ .map((f) => ({ ...f, data: readAuditFile(cwd, f.name) }))
143
+ .filter((f) => f.data !== null && f.data.score !== null);
144
+
145
+ if (auditData.length > 0) {
146
+ farmTerm.section("Audit Scores", emojis.owl);
147
+
148
+ for (const audit of auditData) {
149
+ farmTerm.score(audit.label, audit.data.score, 10);
180
150
  }
181
- console.log(chalk.white("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"));
151
+
152
+ // Calculate average score
153
+ const avgScore = auditData.reduce((sum, a) => sum + a.data.score, 0) / auditData.length;
154
+ farmTerm.nl();
155
+ farmTerm.divider();
156
+ farmTerm.score("Average Score", avgScore.toFixed(1), 10);
182
157
  }
183
158
 
159
+ // Configuration Files Section
184
160
  const claudeMd = path.join(cwd, "CLAUDE.md");
185
161
  const justfile = path.join(cwd, "justfile");
186
- const settingsJson = path.join(claudeDir, "settings.json");
187
-
188
- console.log(chalk.white("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
189
- console.log(
190
- chalk.white("│") +
191
- chalk.bold(" Configuration Files ") +
192
- chalk.white("│"),
193
- );
194
- console.log(chalk.white("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤"));
195
- console.log(
196
- chalk.white("│") +
197
- ` CLAUDE.md ${fs.existsSync(claudeMd) ? chalk.green(" 🌱 exists") : chalk.red(" šŸ‚ missing")} ` +
198
- chalk.white("│"),
199
- );
200
- console.log(
201
- chalk.white("│") +
202
- ` justfile ${fs.existsSync(justfile) ? chalk.green(" 🌱 exists") : chalk.red(" šŸ‚ missing")} ` +
203
- chalk.white("│"),
204
- );
205
- console.log(
206
- chalk.white("│") +
207
- ` settings.json ${fs.existsSync(settingsJson) ? chalk.green(" 🌱 exists") : chalk.red(" šŸ‚ missing")} ` +
208
- chalk.white("│"),
209
- );
210
- console.log(
211
- chalk.white("│") +
212
- ` .beads/ ${fs.existsSync(beadsDir) ? chalk.green(" 🌱 exists") : chalk.gray(" not configured")} ` +
213
- chalk.white("│"),
214
- );
215
- console.log(chalk.white("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"));
216
-
217
- const testFiles =
218
- countFiles(cwd, "*.test.ts") + countFiles(cwd, "*.test.tsx");
162
+
163
+ farmTerm.section("Configuration Status", emojis.seedling);
164
+
165
+ const configItems = [
166
+ { label: "CLAUDE.md", exists: fs.existsSync(claudeMd) },
167
+ { label: "justfile", exists: fs.existsSync(justfile) },
168
+ { label: ".claude/agents/", exists: fs.existsSync(agentsDir) && agents > 0 },
169
+ { label: ".claude/commands/", exists: fs.existsSync(commandsDir) && commands > 0 },
170
+ { label: ".beads/", exists: fs.existsSync(beadsDir), optional: true },
171
+ ];
172
+
173
+ for (const item of configItems) {
174
+ if (item.exists) {
175
+ farmTerm.status(item.label, "pass");
176
+ } else if (item.optional) {
177
+ farmTerm.status(item.label, "info", "(optional)");
178
+ } else {
179
+ farmTerm.status(item.label, "fail", "(missing)");
180
+ }
181
+ }
182
+
183
+ // Project Metrics Section
184
+ const testFiles = countFiles(cwd, "*.test.ts") + countFiles(cwd, "*.test.tsx");
219
185
  const storyFiles = countFiles(cwd, "*.stories.tsx");
220
186
 
221
187
  if (testFiles > 0 || storyFiles > 0) {
222
- console.log(chalk.white("\nā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”"));
223
- console.log(
224
- chalk.white("│") +
225
- chalk.bold(" Project Metrics ") +
226
- chalk.white("│"),
227
- );
228
- console.log(chalk.white("ā”œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”¤"));
188
+ farmTerm.section("Project Metrics", emojis.sunflower);
229
189
  if (testFiles > 0) {
230
- console.log(
231
- chalk.white("│") +
232
- ` Test Files ${chalk.green(String(testFiles).padStart(8))} ` +
233
- chalk.white("│"),
234
- );
190
+ farmTerm.metric("Test Files", testFiles, emojis.potato);
235
191
  }
236
192
  if (storyFiles > 0) {
237
- console.log(
238
- chalk.white("│") +
239
- ` Storybook Stories ${chalk.green(String(storyFiles).padStart(8))} ` +
240
- chalk.white("│"),
241
- );
193
+ farmTerm.metric("Storybook Stories", storyFiles, emojis.cow);
242
194
  }
243
- console.log(chalk.white("ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜"));
244
195
  }
245
196
 
246
- console.log(chalk.gray("\nRun `farmwork doctor` to check for issues.\n"));
197
+ // ASCII Art and Phrases
198
+ farmTerm.nl();
199
+ farmTerm.printTractor();
200
+
201
+ farmTerm.phrases([
202
+ { phrase: "till the land", description: "Audit systems, update FARMHOUSE.md", emoji: emojis.seedling },
203
+ { phrase: "inspect the farm", description: "Full code review & quality audit", emoji: emojis.owl },
204
+ { phrase: "go to market", description: "Scan & translate missing i18n", emoji: emojis.basket },
205
+ { phrase: "harvest crops", description: "Lint, test, build, commit, push", emoji: emojis.tractor },
206
+ { phrase: "open the farm", description: "Full audit cycle", emoji: emojis.barn },
207
+ ]);
208
+
209
+ farmTerm.gray(" Run `farmwork doctor` to check for issues.\n\n");
247
210
  }