farmwork 1.0.0 ā 1.0.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/README.md +116 -88
- package/bin/farmwork.js +0 -6
- package/package.json +8 -2
- package/src/doctor.js +57 -41
- package/src/index.js +0 -1
- package/src/init.js +429 -328
- package/src/status.js +108 -145
- package/src/terminal.js +649 -0
- package/src/add.js +0 -194
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
|
|
49
|
-
const
|
|
50
|
-
if (!fs.existsSync(
|
|
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(
|
|
53
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
54
54
|
const scoreMatch = content.match(/\*\*Score:\*\* (\d+\.?\d*)\/10/);
|
|
55
|
-
const statusMatch = content.match(/\*\*Status:\*\* (
|
|
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
|
-
|
|
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
|
-
|
|
76
|
-
|
|
88
|
+
farmTerm.error("Farmwork not initialized");
|
|
89
|
+
farmTerm.info("Run: farmwork init");
|
|
77
90
|
return;
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|