worclaude 1.9.0 → 2.1.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 +42 -3
- package/package.json +1 -1
- package/src/commands/backup.js +5 -3
- package/src/commands/delete.js +6 -9
- package/src/commands/diff.js +4 -5
- package/src/commands/doctor.js +265 -3
- package/src/commands/init.js +162 -106
- package/src/commands/status.js +4 -5
- package/src/commands/upgrade.js +52 -10
- package/src/core/config.js +11 -0
- package/src/core/detector.js +2 -1
- package/src/core/file-categorizer.js +4 -4
- package/src/core/merger.js +16 -14
- package/src/core/migration.js +144 -0
- package/src/core/remover.js +3 -1
- package/src/core/scaffolder.js +7 -1
- package/src/data/agents.js +2 -0
- package/src/utils/display.js +0 -9
- package/src/utils/file.js +21 -0
- package/templates/agents/optional/backend/api-designer.md +6 -0
- package/templates/agents/optional/backend/auth-auditor.md +2 -0
- package/templates/agents/optional/backend/database-analyst.md +7 -0
- package/templates/agents/optional/data/data-pipeline-reviewer.md +7 -0
- package/templates/agents/optional/data/ml-experiment-tracker.md +7 -0
- package/templates/agents/optional/data/prompt-engineer.md +2 -0
- package/templates/agents/optional/devops/ci-fixer.md +2 -0
- package/templates/agents/optional/devops/dependency-manager.md +7 -0
- package/templates/agents/optional/devops/deploy-validator.md +7 -0
- package/templates/agents/optional/devops/docker-helper.md +2 -0
- package/templates/agents/optional/docs/changelog-generator.md +7 -0
- package/templates/agents/optional/docs/doc-writer.md +3 -0
- package/templates/agents/optional/frontend/style-enforcer.md +2 -0
- package/templates/agents/optional/frontend/ui-reviewer.md +7 -0
- package/templates/agents/optional/quality/bug-fixer.md +2 -0
- package/templates/agents/optional/quality/build-fixer.md +2 -0
- package/templates/agents/optional/quality/e2e-runner.md +3 -0
- package/templates/agents/optional/quality/performance-auditor.md +8 -0
- package/templates/agents/optional/quality/refactorer.md +2 -0
- package/templates/agents/optional/quality/security-reviewer.md +12 -0
- package/templates/agents/universal/build-validator.md +3 -0
- package/templates/agents/universal/code-simplifier.md +2 -0
- package/templates/agents/universal/plan-reviewer.md +9 -0
- package/templates/agents/universal/test-writer.md +6 -1
- package/templates/agents/universal/verify-app.md +46 -0
- package/templates/commands/build-fix.md +4 -0
- package/templates/commands/commit-push-pr.md +11 -0
- package/templates/commands/compact-safe.md +4 -0
- package/templates/commands/conflict-resolver.md +4 -0
- package/templates/commands/end.md +13 -0
- package/templates/commands/refactor-clean.md +4 -0
- package/templates/commands/review-changes.md +4 -0
- package/templates/commands/review-plan.md +4 -0
- package/templates/commands/setup.md +7 -3
- package/templates/commands/start.md +5 -1
- package/templates/commands/status.md +4 -0
- package/templates/commands/sync.md +4 -0
- package/templates/commands/techdebt.md +4 -0
- package/templates/commands/test-coverage.md +4 -0
- package/templates/commands/update-claude-md.md +4 -0
- package/templates/commands/verify.md +4 -0
- package/templates/core/claude-md.md +13 -12
- package/templates/core/memory-md.md +61 -0
- package/templates/skills/templates/backend-conventions.md +6 -0
- package/templates/skills/templates/frontend-design-system.md +10 -0
- package/templates/skills/templates/project-patterns.md +4 -0
- package/templates/skills/universal/claude-md-maintenance.md +2 -0
- package/templates/skills/universal/context-management.md +2 -0
- package/templates/skills/universal/coordinator-mode.md +78 -0
- package/templates/skills/universal/git-conventions.md +2 -0
- package/templates/skills/universal/planning-with-files.md +2 -0
- package/templates/skills/universal/prompt-engineering.md +2 -0
- package/templates/skills/universal/review-and-handoff.md +2 -0
- package/templates/skills/universal/security-checklist.md +8 -0
- package/templates/skills/universal/subagent-usage.md +2 -0
- package/templates/skills/universal/testing.md +8 -0
- package/templates/skills/universal/verification.md +7 -0
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
[Full Documentation](https://sefaertunc.github.io/Worclaude/) · [Interactive Demo](https://sefaertunc.github.io/Worclaude/demo/) · [npm](https://www.npmjs.com/package/worclaude)
|
|
12
12
|
|
|
13
|
-
Worclaude scaffolds a complete Claude Code workflow into any project in seconds. It implements all [tips by Boris Cherny](https://www.howborisusesclaudecode.com/) — the creator of Claude Code at Anthropic — as a reusable, upgradable scaffold. One `init` command gives you 25 agents, 16 slash commands,
|
|
13
|
+
Worclaude scaffolds a complete Claude Code workflow into any project in seconds. It implements all [tips by Boris Cherny](https://www.howborisusesclaudecode.com/) — the creator of Claude Code at Anthropic — as a reusable, upgradable scaffold. One `init` command gives you 25 agents, 16 slash commands, 15 skills, hooks, permissions, and a CLAUDE.md template tuned for your tech stack. Whether you're starting fresh or adding structure to an existing project, Worclaude handles the setup so you can focus on building.
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
@@ -26,9 +26,9 @@ Worclaude scaffolds a complete Claude Code workflow into any project in seconds.
|
|
|
26
26
|
**Slash Commands (16)**
|
|
27
27
|
`/start` `/end` `/commit-push-pr` `/review-plan` `/techdebt` `/verify` `/compact-safe` `/status` `/update-claude-md` `/setup` `/sync` `/conflict-resolver` `/review-changes` `/build-fix` `/refactor-clean` `/test-coverage`
|
|
28
28
|
|
|
29
|
-
**Skills (
|
|
29
|
+
**Skills (15)**
|
|
30
30
|
|
|
31
|
-
-
|
|
31
|
+
- 11 universal knowledge files (testing, git conventions, context management, security, coordinator mode, and more)
|
|
32
32
|
- 3 project-specific templates filled in by `/setup`
|
|
33
33
|
- 1 generated agent routing guide (dynamically built from your agent selection)
|
|
34
34
|
|
|
@@ -48,6 +48,45 @@ Worclaude scaffolds a complete Claude Code workflow into any project in seconds.
|
|
|
48
48
|
|
|
49
49
|
---
|
|
50
50
|
|
|
51
|
+
## What's New in v2.0.0
|
|
52
|
+
|
|
53
|
+
v2.0.0 is a major integration release. Skills and agents now register with Claude Code's runtime systems — they show up in `/skills` and `/agents`, not just as files on disk.
|
|
54
|
+
|
|
55
|
+
**Claude Code Runtime Integration**
|
|
56
|
+
|
|
57
|
+
- Skills use directory format (`skill-name/SKILL.md`) and register with `/skills`
|
|
58
|
+
- Agents include `description` frontmatter and register with `/agents`
|
|
59
|
+
- Run `worclaude doctor` to verify everything is wired up correctly
|
|
60
|
+
|
|
61
|
+
**Conditional Skills**
|
|
62
|
+
|
|
63
|
+
- 6 skills activate only when relevant files are touched (testing, security, frontend, backend, verification, project-patterns)
|
|
64
|
+
- 8 skills stay always-loaded for cross-cutting guidance
|
|
65
|
+
- Saves context window budget by keeping domain-specific guidance out of unrelated work
|
|
66
|
+
|
|
67
|
+
**Agent Enhancements**
|
|
68
|
+
|
|
69
|
+
- Read-only agents blocked from file modifications via `disallowedTools`
|
|
70
|
+
- Background execution for long-running agents (verify-app, build-validator, e2e-runner)
|
|
71
|
+
- Turn limits (`maxTurns`) prevent runaway token consumption
|
|
72
|
+
- Persistent memory for agents that learn across sessions (test-writer, security-reviewer, doc-writer)
|
|
73
|
+
|
|
74
|
+
**New Content**
|
|
75
|
+
|
|
76
|
+
- Coordinator-mode skill for multi-agent orchestration patterns
|
|
77
|
+
- Optional MEMORY.md template for Claude Code's memory system
|
|
78
|
+
- Enhanced verify-app agent with structured verdicts and adversarial probe requirements
|
|
79
|
+
- Per-tech-stack permission presets (16 languages)
|
|
80
|
+
|
|
81
|
+
**Upgrade Migration**
|
|
82
|
+
|
|
83
|
+
- `worclaude upgrade` auto-migrates v1.x projects: flat skills → directory format, agents get required frontmatter
|
|
84
|
+
- `worclaude doctor` detects old formats and missing fields
|
|
85
|
+
|
|
86
|
+
See the [Claude Code Integration guide](https://sefaertunc.github.io/Worclaude/guide/claude-code-integration) for technical details.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
51
90
|
## Quick Start
|
|
52
91
|
|
|
53
92
|
```bash
|
package/package.json
CHANGED
package/src/commands/backup.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import { createBackup } from '../core/backup.js';
|
|
4
|
-
import { fileExists, dirExists, listFiles } from '../utils/file.js';
|
|
4
|
+
import { fileExists, dirExists, listFiles, listSkillDirs } from '../utils/file.js';
|
|
5
5
|
import * as display from '../utils/display.js';
|
|
6
6
|
|
|
7
7
|
export async function backupCommand() {
|
|
@@ -33,11 +33,13 @@ export async function backupCommand() {
|
|
|
33
33
|
if (await dirExists(claudeBackup)) {
|
|
34
34
|
const agents = await listFiles(path.join(claudeBackup, 'agents'));
|
|
35
35
|
const commands = await listFiles(path.join(claudeBackup, 'commands'));
|
|
36
|
-
const
|
|
36
|
+
const skillFiles = await listFiles(path.join(claudeBackup, 'skills'));
|
|
37
|
+
const skillDirs = await listSkillDirs(path.join(claudeBackup, 'skills'));
|
|
38
|
+
const skillCount = skillDirs.length + skillFiles.length;
|
|
37
39
|
const parts = [];
|
|
38
40
|
if (agents.length > 0) parts.push(`${agents.length} agents`);
|
|
39
41
|
if (commands.length > 0) parts.push(`${commands.length} commands`);
|
|
40
|
-
if (
|
|
42
|
+
if (skillCount > 0) parts.push(`${skillCount} skills`);
|
|
41
43
|
contents.push(`.claude/ (${parts.join(', ')})`);
|
|
42
44
|
}
|
|
43
45
|
|
package/src/commands/delete.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import inquirer from 'inquirer';
|
|
3
3
|
import ora from 'ora';
|
|
4
|
-
import {
|
|
4
|
+
import { requireWorkflowMeta } from '../core/config.js';
|
|
5
5
|
import { createBackup } from '../core/backup.js';
|
|
6
6
|
import {
|
|
7
7
|
classifyClaudeFiles,
|
|
@@ -16,16 +16,13 @@ export async function deleteCommand() {
|
|
|
16
16
|
const projectRoot = process.cwd();
|
|
17
17
|
|
|
18
18
|
// Pre-flight: ensure worclaude is installed
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
display.info('Run `worclaude init` to set up
|
|
19
|
+
const { meta, error } = await requireWorkflowMeta(projectRoot);
|
|
20
|
+
if (error === 'not-installed') {
|
|
21
|
+
display.info('Workflow is not installed. Run `worclaude init` to set up.');
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (!meta) {
|
|
27
|
-
display.error('workflow-meta.json is corrupted or unreadable.');
|
|
28
|
-
display.info('You may need to manually remove the .claude/ directory.');
|
|
24
|
+
if (error === 'corrupted') {
|
|
25
|
+
display.error('workflow-meta.json is corrupted. Run `worclaude init` to reinstall.');
|
|
29
26
|
return;
|
|
30
27
|
}
|
|
31
28
|
|
package/src/commands/diff.js
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { requireWorkflowMeta, getPackageVersion } from '../core/config.js';
|
|
2
2
|
import { categorizeFiles } from '../core/file-categorizer.js';
|
|
3
3
|
import * as display from '../utils/display.js';
|
|
4
4
|
|
|
5
5
|
export async function diffCommand() {
|
|
6
6
|
const projectRoot = process.cwd();
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
const { meta, error } = await requireWorkflowMeta(projectRoot);
|
|
9
|
+
if (error === 'not-installed') {
|
|
9
10
|
display.info('Workflow is not installed. Run `worclaude init` to set up.');
|
|
10
11
|
return;
|
|
11
12
|
}
|
|
12
|
-
|
|
13
|
-
const meta = await readWorkflowMeta(projectRoot);
|
|
14
|
-
if (!meta) {
|
|
13
|
+
if (error === 'corrupted') {
|
|
15
14
|
display.error('workflow-meta.json is corrupted. Run `worclaude init` to reinstall.');
|
|
16
15
|
return;
|
|
17
16
|
}
|
package/src/commands/doctor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
1
2
|
import path from 'node:path';
|
|
2
3
|
import { readWorkflowMeta, workflowMetaExists, getPackageVersion } from '../core/config.js';
|
|
3
4
|
import { hashFile } from '../utils/hash.js';
|
|
@@ -72,6 +73,48 @@ async function checkClaudeMd(projectRoot) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
async function checkClaudeMdSize(projectRoot) {
|
|
77
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
78
|
+
if (!(await fileExists(claudeMdPath))) {
|
|
79
|
+
return []; // Already covered by existing checkClaudeMd
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const content = await readFile(claudeMdPath);
|
|
83
|
+
const charCount = content.length;
|
|
84
|
+
const WARN_THRESHOLD = 30000;
|
|
85
|
+
const FAIL_THRESHOLD = 38000;
|
|
86
|
+
const HARD_LIMIT = 40000;
|
|
87
|
+
|
|
88
|
+
if (charCount > FAIL_THRESHOLD) {
|
|
89
|
+
return [
|
|
90
|
+
result(
|
|
91
|
+
FAIL,
|
|
92
|
+
`CLAUDE.md size: ${charCount.toLocaleString()} chars`,
|
|
93
|
+
`Exceeds recommended limit (${FAIL_THRESHOLD.toLocaleString()}/${HARD_LIMIT.toLocaleString()}). Claude Code caps at ${HARD_LIMIT.toLocaleString()} chars. Move domain-specific content to conditional skills with paths frontmatter.`
|
|
94
|
+
),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
if (charCount > WARN_THRESHOLD) {
|
|
98
|
+
return [
|
|
99
|
+
result(
|
|
100
|
+
WARN,
|
|
101
|
+
`CLAUDE.md size: ${charCount.toLocaleString()} chars`,
|
|
102
|
+
`Approaching limit (${WARN_THRESHOLD.toLocaleString()}/${HARD_LIMIT.toLocaleString()}). Consider moving content to skills.`
|
|
103
|
+
),
|
|
104
|
+
];
|
|
105
|
+
}
|
|
106
|
+
return [
|
|
107
|
+
result(
|
|
108
|
+
PASS,
|
|
109
|
+
`CLAUDE.md size: ${charCount.toLocaleString()} chars (limit: ${HARD_LIMIT.toLocaleString()})`,
|
|
110
|
+
null
|
|
111
|
+
),
|
|
112
|
+
];
|
|
113
|
+
} catch {
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
75
118
|
async function checkSettingsJson(projectRoot) {
|
|
76
119
|
const settingsPath = path.join(projectRoot, '.claude', 'settings.json');
|
|
77
120
|
if (!(await fileExists(settingsPath))) {
|
|
@@ -134,6 +177,60 @@ async function checkAgents(projectRoot, meta) {
|
|
|
134
177
|
return results;
|
|
135
178
|
}
|
|
136
179
|
|
|
180
|
+
async function readAgentFrontmatters(projectRoot) {
|
|
181
|
+
const agentsDir = path.join(projectRoot, '.claude', 'agents');
|
|
182
|
+
try {
|
|
183
|
+
const entries = await fs.readdir(agentsDir, { withFileTypes: true });
|
|
184
|
+
const mdFiles = entries.filter((e) => e.isFile() && e.name.endsWith('.md'));
|
|
185
|
+
const agents = [];
|
|
186
|
+
for (const file of mdFiles) {
|
|
187
|
+
const content = await readFile(path.join(agentsDir, file.name));
|
|
188
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
189
|
+
agents.push({ name: file.name, frontmatter: match ? match[1] : null });
|
|
190
|
+
}
|
|
191
|
+
return agents;
|
|
192
|
+
} catch {
|
|
193
|
+
return [];
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function checkAgentDescription(projectRoot) {
|
|
198
|
+
const agents = await readAgentFrontmatters(projectRoot);
|
|
199
|
+
if (agents.length === 0) return [];
|
|
200
|
+
|
|
201
|
+
const results = [];
|
|
202
|
+
for (const { name, frontmatter } of agents) {
|
|
203
|
+
if (!frontmatter) {
|
|
204
|
+
results.push(
|
|
205
|
+
result(FAIL, `agents/${name}`, 'No YAML frontmatter — agent is invisible to Claude Code')
|
|
206
|
+
);
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const hasName = /^name:\s*.+/m.test(frontmatter);
|
|
211
|
+
const hasDescription = /^description:\s*.+/m.test(frontmatter);
|
|
212
|
+
|
|
213
|
+
if (!hasName) {
|
|
214
|
+
results.push(result(FAIL, `agents/${name}`, 'Missing required "name" field in frontmatter'));
|
|
215
|
+
} else if (!hasDescription) {
|
|
216
|
+
results.push(
|
|
217
|
+
result(
|
|
218
|
+
FAIL,
|
|
219
|
+
`agents/${name}`,
|
|
220
|
+
'Missing required "description" field — agent is invisible to Claude Code\'s /agents and routing'
|
|
221
|
+
)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (results.length === 0) {
|
|
227
|
+
results.push(
|
|
228
|
+
result(PASS, `agents/ frontmatter (${agents.length} agents have required fields)`, null)
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
return results;
|
|
232
|
+
}
|
|
233
|
+
|
|
137
234
|
async function checkCommands(projectRoot) {
|
|
138
235
|
const commandsDir = path.join(projectRoot, '.claude', 'commands');
|
|
139
236
|
const missing = [];
|
|
@@ -157,7 +254,7 @@ async function checkSkills(projectRoot) {
|
|
|
157
254
|
const allExpected = [...UNIVERSAL_SKILLS, ...TEMPLATE_SKILLS, 'agent-routing'];
|
|
158
255
|
|
|
159
256
|
for (const skill of allExpected) {
|
|
160
|
-
const skillPath = path.join(skillsDir,
|
|
257
|
+
const skillPath = path.join(skillsDir, skill, 'SKILL.md');
|
|
161
258
|
if (!(await fileExists(skillPath))) {
|
|
162
259
|
missing.push(skill);
|
|
163
260
|
}
|
|
@@ -166,7 +263,49 @@ async function checkSkills(projectRoot) {
|
|
|
166
263
|
if (missing.length === 0) {
|
|
167
264
|
return [result(PASS, `skills/ (${allExpected.length} expected, all present)`, null)];
|
|
168
265
|
}
|
|
169
|
-
return missing.map((s) => result(WARN, `skills/${s}.md`, 'Missing skill'));
|
|
266
|
+
return missing.map((s) => result(WARN, `skills/${s}/SKILL.md`, 'Missing skill'));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function checkSkillFormat(projectRoot) {
|
|
270
|
+
const skillsDir = path.join(projectRoot, '.claude', 'skills');
|
|
271
|
+
const results = [];
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true });
|
|
275
|
+
const flatMdFiles = entries
|
|
276
|
+
.filter((e) => e.isFile() && e.name.endsWith('.md'))
|
|
277
|
+
.map((e) => e.name);
|
|
278
|
+
|
|
279
|
+
if (flatMdFiles.length > 0) {
|
|
280
|
+
results.push(
|
|
281
|
+
result(
|
|
282
|
+
FAIL,
|
|
283
|
+
`skills/ has ${flatMdFiles.length} flat .md file(s)`,
|
|
284
|
+
`Flat .md files in .claude/skills/ are invisible to Claude Code. Expected format: skill-name/SKILL.md. Run \`worclaude upgrade\` to migrate. Files: ${flatMdFiles.join(', ')}`
|
|
285
|
+
)
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Also check directory-format skills exist
|
|
290
|
+
const skillDirs = entries.filter((e) => e.isDirectory());
|
|
291
|
+
let validDirSkills = 0;
|
|
292
|
+
for (const dir of skillDirs) {
|
|
293
|
+
const skillMd = path.join(skillsDir, dir.name, 'SKILL.md');
|
|
294
|
+
if (await fileExists(skillMd)) {
|
|
295
|
+
validDirSkills++;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (validDirSkills > 0 && flatMdFiles.length === 0) {
|
|
300
|
+
results.push(
|
|
301
|
+
result(PASS, `skills/ format (${validDirSkills} directory-format skills)`, null)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
} catch {
|
|
305
|
+
// skills/ doesn't exist — covered by existing component checks
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return results;
|
|
170
309
|
}
|
|
171
310
|
|
|
172
311
|
async function checkHashIntegrity(projectRoot, meta) {
|
|
@@ -244,6 +383,124 @@ async function checkDocSpecs(projectRoot) {
|
|
|
244
383
|
return results;
|
|
245
384
|
}
|
|
246
385
|
|
|
386
|
+
const AGENT_OPTIONAL_FIELDS = [
|
|
387
|
+
'model',
|
|
388
|
+
'isolation',
|
|
389
|
+
'maxTurns',
|
|
390
|
+
'disallowedTools',
|
|
391
|
+
'background',
|
|
392
|
+
'memory',
|
|
393
|
+
'skills',
|
|
394
|
+
'initialPrompt',
|
|
395
|
+
'criticalSystemReminder',
|
|
396
|
+
'omitClaudeMd',
|
|
397
|
+
];
|
|
398
|
+
|
|
399
|
+
async function checkAgentCompleteness(projectRoot) {
|
|
400
|
+
const agents = await readAgentFrontmatters(projectRoot);
|
|
401
|
+
const withFrontmatter = agents.filter((a) => a.frontmatter);
|
|
402
|
+
if (withFrontmatter.length === 0) return [];
|
|
403
|
+
|
|
404
|
+
let totalFields = 0;
|
|
405
|
+
const suggestions = [];
|
|
406
|
+
|
|
407
|
+
for (const { name, frontmatter } of withFrontmatter) {
|
|
408
|
+
let fieldCount = 0;
|
|
409
|
+
for (const field of AGENT_OPTIONAL_FIELDS) {
|
|
410
|
+
if (new RegExp(`^${field}:`, 'm').test(frontmatter)) fieldCount++;
|
|
411
|
+
}
|
|
412
|
+
totalFields += fieldCount;
|
|
413
|
+
|
|
414
|
+
const hasDisallowed = /^disallowedTools:/m.test(frontmatter);
|
|
415
|
+
const hasReminder = /^criticalSystemReminder:/m.test(frontmatter);
|
|
416
|
+
if (hasDisallowed && !hasReminder) {
|
|
417
|
+
suggestions.push(`${name}: read-only agent could benefit from criticalSystemReminder`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const totalPossible = withFrontmatter.length * AGENT_OPTIONAL_FIELDS.length;
|
|
422
|
+
const pct = Math.round((totalFields / totalPossible) * 100);
|
|
423
|
+
const results = [];
|
|
424
|
+
|
|
425
|
+
if (pct >= 50) {
|
|
426
|
+
results.push(
|
|
427
|
+
result(
|
|
428
|
+
PASS,
|
|
429
|
+
`Agent enrichment: ${pct}% optional fields used across ${withFrontmatter.length} agents`,
|
|
430
|
+
null
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
} else {
|
|
434
|
+
results.push(
|
|
435
|
+
result(
|
|
436
|
+
WARN,
|
|
437
|
+
`Agent enrichment: ${pct}% optional fields used across ${withFrontmatter.length} agents`,
|
|
438
|
+
'Run `worclaude upgrade` to add model, maxTurns, skills, and other metadata'
|
|
439
|
+
)
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
for (const s of suggestions) {
|
|
444
|
+
results.push(result(WARN, s, null));
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
return results;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async function checkClaudeMdSections(projectRoot) {
|
|
451
|
+
const claudeMdPath = path.join(projectRoot, 'CLAUDE.md');
|
|
452
|
+
const SECTION_THRESHOLD = 20000; // Only analyze sections if file > 20KB
|
|
453
|
+
const results = [];
|
|
454
|
+
|
|
455
|
+
try {
|
|
456
|
+
const content = await readFile(claudeMdPath);
|
|
457
|
+
if (content.length < SECTION_THRESHOLD) return results;
|
|
458
|
+
|
|
459
|
+
// Split into ## sections
|
|
460
|
+
const sections = [];
|
|
461
|
+
const lines = content.split('\n');
|
|
462
|
+
let currentHeading = '(top-level)';
|
|
463
|
+
let currentLines = [];
|
|
464
|
+
|
|
465
|
+
for (const line of lines) {
|
|
466
|
+
const headingMatch = line.match(/^##\s+(.+)/);
|
|
467
|
+
if (headingMatch) {
|
|
468
|
+
if (currentLines.length > 0) {
|
|
469
|
+
sections.push({ heading: currentHeading, size: currentLines.join('\n').length });
|
|
470
|
+
}
|
|
471
|
+
currentHeading = headingMatch[1];
|
|
472
|
+
currentLines = [];
|
|
473
|
+
} else {
|
|
474
|
+
currentLines.push(line);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
if (currentLines.length > 0) {
|
|
478
|
+
sections.push({ heading: currentHeading, size: currentLines.join('\n').length });
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Sort by size, suggest extracting the top 3 sections > 2KB
|
|
482
|
+
const large = sections.filter((s) => s.size > 2000).sort((a, b) => b.size - a.size);
|
|
483
|
+
|
|
484
|
+
if (large.length > 0) {
|
|
485
|
+
const top = large.slice(0, 3);
|
|
486
|
+
const sectionList = top
|
|
487
|
+
.map((s) => `"${s.heading}" (${(s.size / 1024).toFixed(1)}KB)`)
|
|
488
|
+
.join(', ');
|
|
489
|
+
results.push(
|
|
490
|
+
result(
|
|
491
|
+
WARN,
|
|
492
|
+
`CLAUDE.md has large sections: ${sectionList}`,
|
|
493
|
+
'Consider extracting to conditional skills with paths frontmatter to save context budget'
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
} catch {
|
|
498
|
+
// Already covered by checkClaudeMd
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return results;
|
|
502
|
+
}
|
|
503
|
+
|
|
247
504
|
async function checkPendingReviewFiles(projectRoot) {
|
|
248
505
|
const pending = [];
|
|
249
506
|
try {
|
|
@@ -287,6 +544,8 @@ export async function doctorCommand() {
|
|
|
287
544
|
const meta = await readWorkflowMeta(projectRoot);
|
|
288
545
|
|
|
289
546
|
printResult(await checkClaudeMd(projectRoot));
|
|
547
|
+
for (const r of await checkClaudeMdSize(projectRoot)) printResult(r);
|
|
548
|
+
for (const r of await checkClaudeMdSections(projectRoot)) printResult(r);
|
|
290
549
|
printResult(await checkSettingsJson(projectRoot));
|
|
291
550
|
printResult(await checkSessions(projectRoot));
|
|
292
551
|
display.newline();
|
|
@@ -294,8 +553,11 @@ export async function doctorCommand() {
|
|
|
294
553
|
// Components
|
|
295
554
|
display.barLine(display.white('Components'));
|
|
296
555
|
for (const r of await checkAgents(projectRoot, meta)) printResult(r);
|
|
556
|
+
for (const r of await checkAgentDescription(projectRoot)) printResult(r);
|
|
297
557
|
for (const r of await checkCommands(projectRoot)) printResult(r);
|
|
298
558
|
for (const r of await checkSkills(projectRoot)) printResult(r);
|
|
559
|
+
for (const r of await checkSkillFormat(projectRoot)) printResult(r);
|
|
560
|
+
for (const r of await checkAgentCompleteness(projectRoot)) printResult(r);
|
|
299
561
|
display.newline();
|
|
300
562
|
|
|
301
563
|
// Docs
|
|
@@ -311,7 +573,7 @@ export async function doctorCommand() {
|
|
|
311
573
|
|
|
312
574
|
// Summary
|
|
313
575
|
if (metaResult.status === FAIL) {
|
|
314
|
-
display.
|
|
576
|
+
display.info('Workflow is not installed. Run `worclaude init` to set up.');
|
|
315
577
|
} else {
|
|
316
578
|
display.success('Doctor complete. Review any warnings above.');
|
|
317
579
|
}
|