farmwork 1.0.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 +180 -0
- package/bin/farmwork.js +44 -0
- package/package.json +40 -0
- package/src/add.js +194 -0
- package/src/doctor.js +244 -0
- package/src/index.js +4 -0
- package/src/init.js +1175 -0
- package/src/status.js +247 -0
package/README.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Farmwork
|
|
2
|
+
|
|
3
|
+
> Agentic development harness for AI-assisted projects
|
|
4
|
+
|
|
5
|
+
Farmwork is a framework that transforms AI coding assistants from reactive tools into proactive development partners. The name is a play on "framework" - because building software should feel like tending a well-organized farm.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g farmwork
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or run directly with npx:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx farmwork init
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Initialize in your project
|
|
23
|
+
cd your-project
|
|
24
|
+
farmwork init
|
|
25
|
+
|
|
26
|
+
# Check your setup
|
|
27
|
+
farmwork doctor
|
|
28
|
+
|
|
29
|
+
# View framework status
|
|
30
|
+
farmwork status
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Commands
|
|
34
|
+
|
|
35
|
+
### `farmwork init`
|
|
36
|
+
|
|
37
|
+
Initialize the Farmwork framework in your current directory. Runs an interactive setup wizard to configure your project for any tech stack.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
farmwork init # Interactive setup wizard
|
|
41
|
+
farmwork init -f # Force overwrite existing files
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Options:**
|
|
45
|
+
- `-f, --force` - Overwrite existing files
|
|
46
|
+
|
|
47
|
+
**Storybook Support:**
|
|
48
|
+
If you enable Storybook (for React/Vue projects), the wizard will also ask for:
|
|
49
|
+
- Storybook URL (e.g., storybook.yoursite.com)
|
|
50
|
+
- Netlify Auth Token (for deployment)
|
|
51
|
+
- Netlify Site ID
|
|
52
|
+
- Password protection preference (recommended)
|
|
53
|
+
|
|
54
|
+
**Creates:**
|
|
55
|
+
- `CLAUDE.md` - Main instructions and phrase commands
|
|
56
|
+
- `.claude/` - Claude Code configuration directory
|
|
57
|
+
- `settings.json` - Project settings
|
|
58
|
+
- `agents/` - Specialized subagents
|
|
59
|
+
- `commands/` - User-invocable skills
|
|
60
|
+
- `_AUDIT/` - Living audit documents
|
|
61
|
+
- `FARMHOUSE.md` - Framework command center
|
|
62
|
+
- `_PLANS/` - Implementation plans directory
|
|
63
|
+
- `justfile` - Navigation and task commands
|
|
64
|
+
|
|
65
|
+
### `farmwork add <type> <name>`
|
|
66
|
+
|
|
67
|
+
Add a new component to your Farmwork setup.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
farmwork add agent code-reviewer # Add a new agent
|
|
71
|
+
farmwork add command deploy # Add a new command
|
|
72
|
+
farmwork add audit performance # Add a new audit document
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Types:**
|
|
76
|
+
- `agent` - Creates `.claude/agents/<name>.md`
|
|
77
|
+
- `command` - Creates `.claude/commands/<name>.md`
|
|
78
|
+
- `audit` - Creates `_AUDIT/<NAME>.md`
|
|
79
|
+
|
|
80
|
+
### `farmwork status`
|
|
81
|
+
|
|
82
|
+
Display Farmwork status and metrics.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
farmwork status
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Shows:**
|
|
89
|
+
- Component counts (agents, commands, audits, plans)
|
|
90
|
+
- Issue tracking status (if beads is configured)
|
|
91
|
+
- FARMHOUSE score and open items
|
|
92
|
+
- Configuration file status
|
|
93
|
+
- Project metrics (tests, stories)
|
|
94
|
+
|
|
95
|
+
### `farmwork doctor`
|
|
96
|
+
|
|
97
|
+
Check your Farmwork setup and diagnose issues.
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
farmwork doctor
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Checks:**
|
|
104
|
+
- Core files (CLAUDE.md, .claude/, settings)
|
|
105
|
+
- Agents and commands configuration
|
|
106
|
+
- Audit system (_AUDIT/, FARMHOUSE.md, _PLANS/)
|
|
107
|
+
- Navigation (justfile, just command)
|
|
108
|
+
- Issue tracking (beads)
|
|
109
|
+
- Security (.gitignore settings)
|
|
110
|
+
|
|
111
|
+
## The Farmwork Method
|
|
112
|
+
|
|
113
|
+
### Core Concepts
|
|
114
|
+
|
|
115
|
+
1. **FARMHOUSE.md** - Central command for tracking framework metrics
|
|
116
|
+
2. **Phrase Commands** - Natural language triggers for workflows
|
|
117
|
+
3. **Agents** - Specialized AI subagents for specific tasks
|
|
118
|
+
4. **Commands** - User-invocable skills (triggered with `/command`)
|
|
119
|
+
5. **Issue Tracking** - Using beads (`bd`) for full visibility
|
|
120
|
+
6. **Living Audits** - Documents that track ongoing concerns
|
|
121
|
+
|
|
122
|
+
### Phrase Commands
|
|
123
|
+
|
|
124
|
+
**Farmwork Phrases** (Development Workflow):
|
|
125
|
+
- `till the land` - Audit systems, update metrics
|
|
126
|
+
- `inspect the farm` - Full inspection (code review, performance, security, quality)
|
|
127
|
+
- `go to market` - i18n translation check
|
|
128
|
+
- `harvest crops` - Full push workflow
|
|
129
|
+
|
|
130
|
+
**Plan Phrases**:
|
|
131
|
+
- `make a plan for...` - Create implementation plan
|
|
132
|
+
- `let's implement...` - Execute plan with issue tracking
|
|
133
|
+
|
|
134
|
+
### Recommended Workflow
|
|
135
|
+
|
|
136
|
+
1. **Start Session**: Run `till the land` to audit current state
|
|
137
|
+
2. **Plan Work**: Use `make a plan for...` for new features
|
|
138
|
+
3. **Implement**: Use `let's implement...` to execute with tracking
|
|
139
|
+
4. **Quality Check**: Run `inspect the farm`
|
|
140
|
+
5. **Ship**: Run `harvest crops` to push changes
|
|
141
|
+
|
|
142
|
+
## Directory Structure
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
your-project/
|
|
146
|
+
āāā CLAUDE.md # Main instructions & phrase commands
|
|
147
|
+
āāā .claude/ # Claude Code configuration
|
|
148
|
+
ā āāā settings.json # Project settings
|
|
149
|
+
ā āāā agents/ # Specialized subagents
|
|
150
|
+
ā ā āāā code-reviewer.md
|
|
151
|
+
ā ā āāā security-auditor.md
|
|
152
|
+
ā ā āāā ...
|
|
153
|
+
ā āāā commands/ # User-invocable skills
|
|
154
|
+
ā āāā push.md
|
|
155
|
+
ā āāā ...
|
|
156
|
+
āāā _AUDIT/ # Living audit documents
|
|
157
|
+
ā āāā FARMHOUSE.md # Framework command center
|
|
158
|
+
ā āāā SECURITY.md
|
|
159
|
+
ā āāā PERFORMANCE.md
|
|
160
|
+
ā āāā ...
|
|
161
|
+
āāā _PLANS/ # Implementation plans
|
|
162
|
+
ā āāā FEATURE_NAME.md
|
|
163
|
+
āāā .beads/ # Issue tracking (optional)
|
|
164
|
+
āāā justfile # Navigation commands
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Requirements
|
|
168
|
+
|
|
169
|
+
- Node.js 18+
|
|
170
|
+
- [just](https://github.com/casey/just) (recommended for navigation)
|
|
171
|
+
- [beads](https://github.com/steveyegge/beads) (optional, for issue tracking)
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT
|
|
176
|
+
|
|
177
|
+
## Links
|
|
178
|
+
|
|
179
|
+
- [Farmwork Documentation](https://farmwork.wynter.ai)
|
|
180
|
+
- [GitHub Repository](https://github.com/wynterjones/farmwork)
|
package/bin/farmwork.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import { init } from '../src/init.js';
|
|
5
|
+
import { add } from '../src/add.js';
|
|
6
|
+
import { status } from '../src/status.js';
|
|
7
|
+
import { doctor } from '../src/doctor.js';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
const VERSION = '1.0.0';
|
|
11
|
+
|
|
12
|
+
console.log(chalk.green(`
|
|
13
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
14
|
+
ā š¾ Farmwork CLI v${VERSION} ā
|
|
15
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
16
|
+
`));
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('farmwork')
|
|
20
|
+
.description('Farmwork - Agentic development harness for AI-assisted projects')
|
|
21
|
+
.version(VERSION);
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('init')
|
|
25
|
+
.description('Initialize Farmwork in current directory')
|
|
26
|
+
.option('-f, --force', 'Overwrite existing files')
|
|
27
|
+
.action(init);
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('add <type> <name>')
|
|
31
|
+
.description('Add a component (agent, command, audit)')
|
|
32
|
+
.action(add);
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command('status')
|
|
36
|
+
.description('Show Farmwork status and metrics')
|
|
37
|
+
.action(status);
|
|
38
|
+
|
|
39
|
+
program
|
|
40
|
+
.command('doctor')
|
|
41
|
+
.description('Check Farmwork setup and diagnose issues')
|
|
42
|
+
.action(doctor);
|
|
43
|
+
|
|
44
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "farmwork",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Farmwork - Agentic development harness for AI-assisted projects",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"farmwork": "./bin/farmwork.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"lint": "eslint src/"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"cli",
|
|
16
|
+
"ai",
|
|
17
|
+
"claude",
|
|
18
|
+
"development",
|
|
19
|
+
"workflow",
|
|
20
|
+
"automation",
|
|
21
|
+
"agentic"
|
|
22
|
+
],
|
|
23
|
+
"author": "Wynter Jones",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"chalk": "^5.3.0",
|
|
27
|
+
"commander": "^12.1.0",
|
|
28
|
+
"inquirer": "^9.2.12",
|
|
29
|
+
"ora": "^8.0.1",
|
|
30
|
+
"fs-extra": "^11.2.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/wynterjones/farmwork"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://farmwork.wynter.ai"
|
|
40
|
+
}
|
package/src/add.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import ora from "ora";
|
|
5
|
+
|
|
6
|
+
const TEMPLATES = {
|
|
7
|
+
agent: (name, description) => `# ${name} Agent
|
|
8
|
+
|
|
9
|
+
> ${description}
|
|
10
|
+
|
|
11
|
+
## Purpose
|
|
12
|
+
|
|
13
|
+
[Describe what this agent does and when to use it]
|
|
14
|
+
|
|
15
|
+
## Triggers
|
|
16
|
+
|
|
17
|
+
This agent is launched via the Task tool with \`subagent_type="${name}"\`.
|
|
18
|
+
|
|
19
|
+
## Workflow
|
|
20
|
+
|
|
21
|
+
1. [First step]
|
|
22
|
+
2. [Second step]
|
|
23
|
+
3. [Third step]
|
|
24
|
+
|
|
25
|
+
## Output
|
|
26
|
+
|
|
27
|
+
[Describe what the agent returns or produces]
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
\`\`\`
|
|
32
|
+
# Example usage
|
|
33
|
+
Task tool with subagent_type="${name}"
|
|
34
|
+
Prompt: "[Example prompt]"
|
|
35
|
+
\`\`\`
|
|
36
|
+
`,
|
|
37
|
+
|
|
38
|
+
command: (
|
|
39
|
+
name,
|
|
40
|
+
description,
|
|
41
|
+
) => `# ${name.charAt(0).toUpperCase() + name.slice(1)} Command
|
|
42
|
+
|
|
43
|
+
${description}
|
|
44
|
+
|
|
45
|
+
## Workflow
|
|
46
|
+
|
|
47
|
+
Execute these steps in order. **Stop immediately if any step fails.**
|
|
48
|
+
|
|
49
|
+
### Step 1: [First Step]
|
|
50
|
+
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
# Command to run
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
### Step 2: [Second Step]
|
|
56
|
+
|
|
57
|
+
\`\`\`bash
|
|
58
|
+
# Command to run
|
|
59
|
+
\`\`\`
|
|
60
|
+
|
|
61
|
+
### Step 3: Report Results
|
|
62
|
+
|
|
63
|
+
Show a summary of what was accomplished.
|
|
64
|
+
`,
|
|
65
|
+
|
|
66
|
+
audit: (name) => `# ${name.charAt(0).toUpperCase() + name.slice(1)} Audit
|
|
67
|
+
|
|
68
|
+
> [One-line description of this audit area]
|
|
69
|
+
|
|
70
|
+
**Last Updated:** ${new Date().toISOString().split("T")[0]}
|
|
71
|
+
**Score:** 0.0/10
|
|
72
|
+
**Status:** 0 open items
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## How to get 10/10
|
|
77
|
+
|
|
78
|
+
[One paragraph explaining what perfect looks like for this area]
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Constraints
|
|
83
|
+
|
|
84
|
+
| Constraint | Reason | Impact |
|
|
85
|
+
|------------|--------|--------|
|
|
86
|
+
| [Constraint 1] | [Why] | [What happens] |
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Open Items
|
|
91
|
+
|
|
92
|
+
_None currently_
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## [Area-Specific Section]
|
|
97
|
+
|
|
98
|
+
[Content specific to this audit area]
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Audit History
|
|
103
|
+
|
|
104
|
+
| Date | Changes |
|
|
105
|
+
|------|---------|
|
|
106
|
+
| ${new Date().toISOString().split("T")[0]} | Initial audit document |
|
|
107
|
+
`,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export async function add(type, name, options) {
|
|
111
|
+
const cwd = process.cwd();
|
|
112
|
+
const spinner = ora();
|
|
113
|
+
|
|
114
|
+
const validTypes = ["agent", "command", "audit"];
|
|
115
|
+
if (!validTypes.includes(type)) {
|
|
116
|
+
console.log(chalk.red(`\nā Invalid type: ${type}`));
|
|
117
|
+
console.log(chalk.gray(` Valid types: ${validTypes.join(", ")}`));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!name) {
|
|
122
|
+
console.log(chalk.red("\nā Name is required"));
|
|
123
|
+
console.log(chalk.gray(` Usage: produce add ${type} <name>`));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const claudeDir = path.join(cwd, ".claude");
|
|
128
|
+
if (!fs.existsSync(claudeDir)) {
|
|
129
|
+
console.log(chalk.red("\nā Farmwork not initialized"));
|
|
130
|
+
console.log(chalk.gray(" Run: produce init"));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
spinner.start(`Adding ${type}: ${name}`);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
let filePath;
|
|
138
|
+
let content;
|
|
139
|
+
let description = options?.description || `[Description for ${name}]`;
|
|
140
|
+
|
|
141
|
+
switch (type) {
|
|
142
|
+
case "agent":
|
|
143
|
+
filePath = path.join(claudeDir, "agents", `${name}.md`);
|
|
144
|
+
content = TEMPLATES.agent(name, description);
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case "command":
|
|
148
|
+
filePath = path.join(claudeDir, "commands", `${name}.md`);
|
|
149
|
+
content = TEMPLATES.command(name, description);
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
case "audit":
|
|
153
|
+
const auditDir = path.join(cwd, "_AUDIT");
|
|
154
|
+
if (!fs.existsSync(auditDir)) {
|
|
155
|
+
fs.mkdirSync(auditDir, { recursive: true });
|
|
156
|
+
}
|
|
157
|
+
filePath = path.join(auditDir, `${name.toUpperCase()}.md`);
|
|
158
|
+
content = TEMPLATES.audit(name);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (fs.existsSync(filePath)) {
|
|
163
|
+
spinner.fail(`${type} already exists: ${name}`);
|
|
164
|
+
console.log(chalk.gray(` File: ${filePath}`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const dir = path.dirname(filePath);
|
|
169
|
+
if (!fs.existsSync(dir)) {
|
|
170
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fs.writeFileSync(filePath, content);
|
|
174
|
+
spinner.succeed(`Added ${type}: ${name}`);
|
|
175
|
+
|
|
176
|
+
console.log(chalk.gray(`\n File: ${filePath}`));
|
|
177
|
+
console.log(chalk.cyan(`\n Edit the file to customize the ${type}.`));
|
|
178
|
+
|
|
179
|
+
if (type === "agent") {
|
|
180
|
+
console.log(
|
|
181
|
+
chalk.gray(`\n Launch with: Task tool, subagent_type="${name}"`),
|
|
182
|
+
);
|
|
183
|
+
} else if (type === "command") {
|
|
184
|
+
console.log(chalk.gray(`\n Invoke with: /${name}`));
|
|
185
|
+
} else if (type === "audit") {
|
|
186
|
+
console.log(
|
|
187
|
+
chalk.gray(`\n Update FARMHOUSE.md to reference this audit.`),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
spinner.fail(`Failed to add ${type}`);
|
|
192
|
+
console.log(chalk.red(` ${error.message}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
package/src/doctor.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import fs from "fs-extra";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
|
|
6
|
+
function checkExists(filePath, description) {
|
|
7
|
+
const exists = fs.existsSync(filePath);
|
|
8
|
+
return {
|
|
9
|
+
passed: exists,
|
|
10
|
+
message: description,
|
|
11
|
+
details: exists ? null : `Missing: ${filePath}`,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function checkCommand(command, description) {
|
|
16
|
+
try {
|
|
17
|
+
execSync(`which ${command}`, { encoding: "utf8", stdio: "pipe" });
|
|
18
|
+
return { passed: true, message: description, details: null };
|
|
19
|
+
} catch {
|
|
20
|
+
return {
|
|
21
|
+
passed: false,
|
|
22
|
+
message: description,
|
|
23
|
+
details: `Command not found: ${command}`,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function checkDirectoryNotEmpty(dir, description) {
|
|
29
|
+
if (!fs.existsSync(dir)) {
|
|
30
|
+
return {
|
|
31
|
+
passed: false,
|
|
32
|
+
message: description,
|
|
33
|
+
details: `Directory missing: ${dir}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const files = fs.readdirSync(dir);
|
|
37
|
+
const hasContent = files.length > 0;
|
|
38
|
+
return {
|
|
39
|
+
passed: hasContent,
|
|
40
|
+
message: description,
|
|
41
|
+
details: hasContent ? null : `Directory empty: ${dir}`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function checkClaudeMdSections(claudeMdPath) {
|
|
46
|
+
if (!fs.existsSync(claudeMdPath)) {
|
|
47
|
+
return {
|
|
48
|
+
passed: false,
|
|
49
|
+
message: "CLAUDE.md has required sections",
|
|
50
|
+
details: "File missing",
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const content = fs.readFileSync(claudeMdPath, "utf8");
|
|
55
|
+
const requiredSections = [
|
|
56
|
+
"Phrase Commands",
|
|
57
|
+
"Issue Tracking",
|
|
58
|
+
"Claude Code Commands",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const missing = requiredSections.filter(
|
|
62
|
+
(section) => !content.includes(section),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
passed: missing.length === 0,
|
|
67
|
+
message: "CLAUDE.md has required sections",
|
|
68
|
+
details:
|
|
69
|
+
missing.length > 0 ? `Missing sections: ${missing.join(", ")}` : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function checkFarmhouseFormat(farmhousePath) {
|
|
74
|
+
if (!fs.existsSync(farmhousePath)) {
|
|
75
|
+
return {
|
|
76
|
+
passed: false,
|
|
77
|
+
message: "FARMHOUSE.md follows format",
|
|
78
|
+
details: "File missing",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const content = fs.readFileSync(farmhousePath, "utf8");
|
|
83
|
+
const requiredFields = ["**Last Updated:**", "**Score:**", "**Status:**"];
|
|
84
|
+
|
|
85
|
+
const missing = requiredFields.filter((field) => !content.includes(field));
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
passed: missing.length === 0,
|
|
89
|
+
message: "FARMHOUSE.md follows format",
|
|
90
|
+
details:
|
|
91
|
+
missing.length > 0 ? `Missing fields: ${missing.join(", ")}` : null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function checkGitignore(cwd) {
|
|
96
|
+
const gitignorePath = path.join(cwd, ".gitignore");
|
|
97
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
98
|
+
return {
|
|
99
|
+
passed: false,
|
|
100
|
+
message: ".gitignore includes local settings",
|
|
101
|
+
details: "File missing",
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const content = fs.readFileSync(gitignorePath, "utf8");
|
|
106
|
+
const hasLocalSettings = content.includes("settings.local.json");
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
passed: hasLocalSettings,
|
|
110
|
+
message: ".gitignore includes local settings",
|
|
111
|
+
details: hasLocalSettings ? null : "Add: .claude/settings.local.json",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export async function doctor() {
|
|
116
|
+
const cwd = process.cwd();
|
|
117
|
+
|
|
118
|
+
console.log(chalk.cyan("\n𩺠Farmwork Doctor\n"));
|
|
119
|
+
console.log(chalk.gray("Checking your Farmwork setup...\n"));
|
|
120
|
+
|
|
121
|
+
const checks = [];
|
|
122
|
+
|
|
123
|
+
checks.push({ category: "Core Files", items: [] });
|
|
124
|
+
checks[0].items.push(
|
|
125
|
+
checkExists(path.join(cwd, "CLAUDE.md"), "CLAUDE.md exists"),
|
|
126
|
+
);
|
|
127
|
+
checks[0].items.push(
|
|
128
|
+
checkExists(path.join(cwd, ".claude"), ".claude/ directory exists"),
|
|
129
|
+
);
|
|
130
|
+
checks[0].items.push(
|
|
131
|
+
checkExists(
|
|
132
|
+
path.join(cwd, ".claude", "settings.json"),
|
|
133
|
+
"settings.json exists",
|
|
134
|
+
),
|
|
135
|
+
);
|
|
136
|
+
checks[0].items.push(checkClaudeMdSections(path.join(cwd, "CLAUDE.md")));
|
|
137
|
+
|
|
138
|
+
checks.push({ category: "Agents & Commands", items: [] });
|
|
139
|
+
checks[1].items.push(
|
|
140
|
+
checkDirectoryNotEmpty(
|
|
141
|
+
path.join(cwd, ".claude", "agents"),
|
|
142
|
+
"Has agents defined",
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
checks[1].items.push(
|
|
146
|
+
checkDirectoryNotEmpty(
|
|
147
|
+
path.join(cwd, ".claude", "commands"),
|
|
148
|
+
"Has commands defined",
|
|
149
|
+
),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
checks.push({ category: "Audit System", items: [] });
|
|
153
|
+
checks[2].items.push(
|
|
154
|
+
checkExists(path.join(cwd, "_AUDIT"), "_AUDIT/ directory exists"),
|
|
155
|
+
);
|
|
156
|
+
checks[2].items.push(
|
|
157
|
+
checkExists(
|
|
158
|
+
path.join(cwd, "_AUDIT", "FARMHOUSE.md"),
|
|
159
|
+
"FARMHOUSE.md exists",
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
checks[2].items.push(
|
|
163
|
+
checkFarmhouseFormat(path.join(cwd, "_AUDIT", "FARMHOUSE.md")),
|
|
164
|
+
);
|
|
165
|
+
checks[2].items.push(
|
|
166
|
+
checkExists(path.join(cwd, "_PLANS"), "_PLANS/ directory exists"),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
checks.push({ category: "Navigation", items: [] });
|
|
170
|
+
checks[3].items.push(
|
|
171
|
+
checkExists(path.join(cwd, "justfile"), "justfile exists"),
|
|
172
|
+
);
|
|
173
|
+
checks[3].items.push(checkCommand("just", "just command available"));
|
|
174
|
+
|
|
175
|
+
checks.push({ category: "Issue Tracking", items: [] });
|
|
176
|
+
checks[4].items.push(
|
|
177
|
+
checkExists(path.join(cwd, ".beads"), ".beads/ directory exists"),
|
|
178
|
+
);
|
|
179
|
+
checks[4].items.push(checkCommand("bd", "bd (beads) command available"));
|
|
180
|
+
|
|
181
|
+
checks.push({ category: "Security", items: [] });
|
|
182
|
+
checks[5].items.push(checkGitignore(cwd));
|
|
183
|
+
|
|
184
|
+
let totalPassed = 0;
|
|
185
|
+
let totalFailed = 0;
|
|
186
|
+
let totalWarnings = 0;
|
|
187
|
+
|
|
188
|
+
for (const category of checks) {
|
|
189
|
+
console.log(chalk.bold(`${category.category}`));
|
|
190
|
+
|
|
191
|
+
for (const check of category.items) {
|
|
192
|
+
if (check.passed) {
|
|
193
|
+
console.log(chalk.green(` š± ${check.message}`));
|
|
194
|
+
totalPassed++;
|
|
195
|
+
} else {
|
|
196
|
+
if (check.message.includes("beads") || check.message.includes("bd ")) {
|
|
197
|
+
console.log(chalk.yellow(` š ${check.message}`));
|
|
198
|
+
if (check.details) console.log(chalk.gray(` ${check.details}`));
|
|
199
|
+
totalWarnings++;
|
|
200
|
+
} else {
|
|
201
|
+
console.log(chalk.red(` š ${check.message}`));
|
|
202
|
+
if (check.details) console.log(chalk.gray(` ${check.details}`));
|
|
203
|
+
totalFailed++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
console.log();
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
console.log(chalk.bold("Summary"));
|
|
211
|
+
console.log(chalk.green(` š± ${totalPassed} passed`));
|
|
212
|
+
if (totalWarnings > 0) {
|
|
213
|
+
console.log(chalk.yellow(` š ${totalWarnings} warnings (optional)`));
|
|
214
|
+
}
|
|
215
|
+
if (totalFailed > 0) {
|
|
216
|
+
console.log(chalk.red(` š ${totalFailed} failed`));
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const health =
|
|
220
|
+
totalFailed === 0
|
|
221
|
+
? totalWarnings === 0
|
|
222
|
+
? "Excellent"
|
|
223
|
+
: "Good"
|
|
224
|
+
: totalFailed <= 2
|
|
225
|
+
? "Needs Attention"
|
|
226
|
+
: "Critical";
|
|
227
|
+
|
|
228
|
+
const healthColor =
|
|
229
|
+
health === "Excellent"
|
|
230
|
+
? chalk.green
|
|
231
|
+
: health === "Good"
|
|
232
|
+
? chalk.cyan
|
|
233
|
+
: health === "Needs Attention"
|
|
234
|
+
? chalk.yellow
|
|
235
|
+
: chalk.red;
|
|
236
|
+
|
|
237
|
+
console.log(`\n${chalk.bold("Health:")} ${healthColor(health)}`);
|
|
238
|
+
|
|
239
|
+
if (totalFailed > 0) {
|
|
240
|
+
console.log(chalk.gray("\nRun `farmwork init` to fix missing components."));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
console.log();
|
|
244
|
+
}
|
package/src/index.js
ADDED