worclaude 1.4.0 → 1.6.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 +3 -3
- package/package.json +1 -1
- package/src/commands/delete.js +234 -0
- package/src/commands/init.js +8 -0
- package/src/core/backup.js +10 -0
- package/src/core/remover.js +213 -0
- package/src/data/agents.js +2 -18
- package/src/index.js +6 -0
- package/templates/commands/commit-push-pr.md +27 -23
- package/templates/commands/conflict-resolver.md +40 -0
- package/templates/commands/end.md +15 -10
- package/templates/commands/start.md +8 -0
- package/templates/commands/sync.md +49 -0
- package/templates/core/claude-md.md +4 -1
- package/templates/skills/universal/context-management.md +2 -0
- package/templates/skills/universal/git-conventions.md +11 -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 [53 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 23 agents,
|
|
13
|
+
Worclaude scaffolds a complete Claude Code workflow into any project in seconds. It implements all [53 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 23 agents, 12 slash commands, 13 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
|
|
|
@@ -23,8 +23,8 @@ Worclaude scaffolds a complete Claude Code workflow into any project in seconds.
|
|
|
23
23
|
- 5 universal: plan-reviewer, code-simplifier, test-writer, build-validator, verify-app
|
|
24
24
|
- 18 optional across 6 categories: Backend, Frontend, DevOps, Quality, Documentation, Data/AI
|
|
25
25
|
|
|
26
|
-
**Slash Commands (
|
|
27
|
-
`/start` `/end` `/commit-push-pr` `/review-plan` `/techdebt` `/verify` `/compact-safe` `/status` `/update-claude-md` `/setup`
|
|
26
|
+
**Slash Commands (12)**
|
|
27
|
+
`/start` `/end` `/commit-push-pr` `/review-plan` `/techdebt` `/verify` `/compact-safe` `/status` `/update-claude-md` `/setup` `/sync` `/conflict-resolver`
|
|
28
28
|
|
|
29
29
|
**Skills (13)**
|
|
30
30
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { workflowMetaExists, readWorkflowMeta } from '../core/config.js';
|
|
5
|
+
import { createBackup } from '../core/backup.js';
|
|
6
|
+
import {
|
|
7
|
+
classifyClaudeFiles,
|
|
8
|
+
detectRootFiles,
|
|
9
|
+
removeTrackedFiles,
|
|
10
|
+
removeRootFiles,
|
|
11
|
+
cleanGitignore,
|
|
12
|
+
} from '../core/remover.js';
|
|
13
|
+
import * as display from '../utils/display.js';
|
|
14
|
+
|
|
15
|
+
export async function deleteCommand() {
|
|
16
|
+
const projectRoot = process.cwd();
|
|
17
|
+
|
|
18
|
+
// Pre-flight: ensure worclaude is installed
|
|
19
|
+
if (!(await workflowMetaExists(projectRoot))) {
|
|
20
|
+
display.error('No worclaude workflow found in this project.');
|
|
21
|
+
display.info('Run `worclaude init` to set up a workflow first.');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const meta = await readWorkflowMeta(projectRoot);
|
|
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.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
display.sectionHeader('DELETE WORKFLOW');
|
|
33
|
+
display.newline();
|
|
34
|
+
|
|
35
|
+
// Step 1: Mode selection
|
|
36
|
+
const { mode } = await inquirer.prompt([
|
|
37
|
+
{
|
|
38
|
+
type: 'list',
|
|
39
|
+
name: 'mode',
|
|
40
|
+
message: 'What would you like to do?',
|
|
41
|
+
choices: [
|
|
42
|
+
{ name: 'Remove workflow from this project', value: 'project' },
|
|
43
|
+
{ name: 'Remove workflow and uninstall worclaude globally', value: 'global' },
|
|
44
|
+
new inquirer.Separator(),
|
|
45
|
+
{ name: '← Cancel', value: 'cancel' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
if (mode === 'cancel') {
|
|
51
|
+
display.info('Delete cancelled.');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Step 2: Classify files
|
|
56
|
+
const classification = await classifyClaudeFiles(projectRoot, meta);
|
|
57
|
+
const rootFiles = await detectRootFiles(projectRoot);
|
|
58
|
+
|
|
59
|
+
// Step 3: Show preview
|
|
60
|
+
display.newline();
|
|
61
|
+
display.barLine('Workflow files in .claude/:');
|
|
62
|
+
if (classification.safeToDelete.length > 0) {
|
|
63
|
+
display.barLine(
|
|
64
|
+
` ${display.green('✓')} ${classification.safeToDelete.length} unmodified files (safe to remove)`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (classification.modified.length > 0) {
|
|
68
|
+
display.barLine(
|
|
69
|
+
` ${display.yellow('~')} ${classification.modified.length} files you've customized`
|
|
70
|
+
);
|
|
71
|
+
for (const key of classification.modified) {
|
|
72
|
+
display.barLine(` ${display.yellow('~')} .claude/${key}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (classification.userOwned.length > 0) {
|
|
76
|
+
display.barLine(
|
|
77
|
+
` ${display.blue('●')} ${classification.userOwned.length} user-added files (will NOT be touched)`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 4: Handle modified files
|
|
82
|
+
let filesToDelete = [...classification.safeToDelete];
|
|
83
|
+
|
|
84
|
+
if (classification.modified.length > 0) {
|
|
85
|
+
display.newline();
|
|
86
|
+
const { modifiedAction } = await inquirer.prompt([
|
|
87
|
+
{
|
|
88
|
+
type: 'list',
|
|
89
|
+
name: 'modifiedAction',
|
|
90
|
+
message: `${classification.modified.length} file(s) have been customized. What should we do?`,
|
|
91
|
+
choices: [
|
|
92
|
+
{ name: "Delete them too (they'll be in the backup)", value: 'delete' },
|
|
93
|
+
{ name: 'Keep them in .claude/', value: 'keep' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
|
|
98
|
+
if (modifiedAction === 'delete') {
|
|
99
|
+
filesToDelete.push(...classification.modified);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Step 5: Handle root-level files (settings.json + project root files)
|
|
104
|
+
let rootFilesToDelete = [];
|
|
105
|
+
|
|
106
|
+
if (rootFiles.length > 0) {
|
|
107
|
+
display.newline();
|
|
108
|
+
display.info('These files were created or modified by worclaude but may contain your work:');
|
|
109
|
+
for (const f of rootFiles) {
|
|
110
|
+
display.dim(` ${f.label}`);
|
|
111
|
+
}
|
|
112
|
+
display.newline();
|
|
113
|
+
|
|
114
|
+
const { rootAction } = await inquirer.prompt([
|
|
115
|
+
{
|
|
116
|
+
type: 'list',
|
|
117
|
+
name: 'rootAction',
|
|
118
|
+
message: 'What would you like to do with these files?',
|
|
119
|
+
choices: [
|
|
120
|
+
{ name: 'Keep all (recommended)', value: 'keep' },
|
|
121
|
+
{ name: 'Let me choose which to remove', value: 'choose' },
|
|
122
|
+
{ name: 'Remove all', value: 'remove' },
|
|
123
|
+
],
|
|
124
|
+
default: 0,
|
|
125
|
+
},
|
|
126
|
+
]);
|
|
127
|
+
|
|
128
|
+
if (rootAction === 'remove') {
|
|
129
|
+
rootFilesToDelete = rootFiles.map((f) => f.path);
|
|
130
|
+
} else if (rootAction === 'choose') {
|
|
131
|
+
const { selected } = await inquirer.prompt([
|
|
132
|
+
{
|
|
133
|
+
type: 'checkbox',
|
|
134
|
+
name: 'selected',
|
|
135
|
+
message: 'Select files to remove:',
|
|
136
|
+
choices: rootFiles.map((f) => ({ name: f.label, value: f.path })),
|
|
137
|
+
},
|
|
138
|
+
]);
|
|
139
|
+
rootFilesToDelete = selected;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Step 6: Final confirmation
|
|
144
|
+
const totalDeletions = filesToDelete.length + rootFilesToDelete.length;
|
|
145
|
+
|
|
146
|
+
if (totalDeletions === 0) {
|
|
147
|
+
display.newline();
|
|
148
|
+
display.info('No files selected for removal.');
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
display.newline();
|
|
153
|
+
display.warn(`This will permanently delete ${totalDeletions} file(s).`);
|
|
154
|
+
display.dim(' A backup will be created first.');
|
|
155
|
+
display.newline();
|
|
156
|
+
|
|
157
|
+
const { confirm } = await inquirer.prompt([
|
|
158
|
+
{
|
|
159
|
+
type: 'list',
|
|
160
|
+
name: 'confirm',
|
|
161
|
+
message: 'Confirm deletion?',
|
|
162
|
+
choices: [
|
|
163
|
+
{ name: 'Yes, delete', value: true },
|
|
164
|
+
{ name: 'No, cancel', value: false },
|
|
165
|
+
],
|
|
166
|
+
default: 1,
|
|
167
|
+
},
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
if (!confirm) {
|
|
171
|
+
display.info('Delete cancelled.');
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Step 7: Execute
|
|
176
|
+
const spinner = ora('Creating backup...').start();
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const backupDir = await createBackup(projectRoot);
|
|
180
|
+
spinner.text = 'Removing workflow files...';
|
|
181
|
+
|
|
182
|
+
// Remove .claude/ tracked files
|
|
183
|
+
const claudeRemoved = await removeTrackedFiles(projectRoot, filesToDelete);
|
|
184
|
+
|
|
185
|
+
// Remove root files
|
|
186
|
+
let rootRemoved = 0;
|
|
187
|
+
if (rootFilesToDelete.length > 0) {
|
|
188
|
+
rootRemoved = await removeRootFiles(projectRoot, rootFilesToDelete);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Clean .gitignore
|
|
192
|
+
const gitignoreCleaned = await cleanGitignore(projectRoot);
|
|
193
|
+
|
|
194
|
+
spinner.succeed('Workflow removed!');
|
|
195
|
+
|
|
196
|
+
// Step 8: Report
|
|
197
|
+
display.newline();
|
|
198
|
+
if (claudeRemoved > 0) {
|
|
199
|
+
display.success(`Removed ${claudeRemoved} workflow files from .claude/`);
|
|
200
|
+
}
|
|
201
|
+
if (rootRemoved > 0) {
|
|
202
|
+
display.success(`Removed ${rootRemoved} root-level file(s)`);
|
|
203
|
+
}
|
|
204
|
+
if (gitignoreCleaned) {
|
|
205
|
+
display.success('Cleaned up .gitignore');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const keptModified = classification.modified.filter((f) => !filesToDelete.includes(f));
|
|
209
|
+
if (keptModified.length > 0) {
|
|
210
|
+
display.info(`Kept ${keptModified.length} customized file(s) in .claude/`);
|
|
211
|
+
}
|
|
212
|
+
if (classification.userOwned.length > 0) {
|
|
213
|
+
display.info(`Kept ${classification.userOwned.length} user-added file(s) in .claude/`);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const keptRootFiles = rootFiles.filter((f) => !rootFilesToDelete.includes(f.path));
|
|
217
|
+
if (keptRootFiles.length > 0) {
|
|
218
|
+
display.info(`Kept: ${keptRootFiles.map((f) => f.label).join(', ')}`);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
display.newline();
|
|
222
|
+
display.dim(` Backup: ${path.basename(backupDir)}/`);
|
|
223
|
+
|
|
224
|
+
// Step 9: Global uninstall hint
|
|
225
|
+
if (mode === 'global') {
|
|
226
|
+
display.newline();
|
|
227
|
+
display.info('To uninstall worclaude CLI globally, run:');
|
|
228
|
+
display.dim(' npm uninstall -g worclaude');
|
|
229
|
+
}
|
|
230
|
+
} catch (err) {
|
|
231
|
+
spinner.fail('Delete failed.');
|
|
232
|
+
display.error(err.message);
|
|
233
|
+
}
|
|
234
|
+
}
|
package/src/commands/init.js
CHANGED
|
@@ -676,6 +676,14 @@ export async function initCommand() {
|
|
|
676
676
|
const version = await getPackageVersion();
|
|
677
677
|
display.banner(version);
|
|
678
678
|
|
|
679
|
+
// Windows guidance: hooks require Git Bash
|
|
680
|
+
if (process.platform === 'win32') {
|
|
681
|
+
display.info(
|
|
682
|
+
'Windows detected \u2014 hooks require Git for Windows (Git Bash). See: https://gitforwindows.org'
|
|
683
|
+
);
|
|
684
|
+
display.newline();
|
|
685
|
+
}
|
|
686
|
+
|
|
679
687
|
// Step 3: If existing project, show detection report and confirm
|
|
680
688
|
let existingScan = null;
|
|
681
689
|
let backupPath = null;
|
package/src/core/backup.js
CHANGED
|
@@ -37,6 +37,16 @@ export async function createBackup(projectRoot) {
|
|
|
37
37
|
await copyFile(mcpPath, path.join(backupDir, '.mcp.json'));
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const progressPath = path.join(projectRoot, 'docs', 'spec', 'PROGRESS.md');
|
|
41
|
+
if (await fileExists(progressPath)) {
|
|
42
|
+
await copyFile(progressPath, path.join(backupDir, 'docs', 'spec', 'PROGRESS.md'));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const specPath = path.join(projectRoot, 'docs', 'spec', 'SPEC.md');
|
|
46
|
+
if (await fileExists(specPath)) {
|
|
47
|
+
await copyFile(specPath, path.join(backupDir, 'docs', 'spec', 'SPEC.md'));
|
|
48
|
+
}
|
|
49
|
+
|
|
40
50
|
return backupDir;
|
|
41
51
|
}
|
|
42
52
|
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { hashFile } from '../utils/hash.js';
|
|
3
|
+
import {
|
|
4
|
+
fileExists,
|
|
5
|
+
dirExists,
|
|
6
|
+
readFile,
|
|
7
|
+
writeFile,
|
|
8
|
+
listFiles,
|
|
9
|
+
listFilesRecursive,
|
|
10
|
+
removeDirectory,
|
|
11
|
+
} from '../utils/file.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Classify .claude/ files into safe-to-delete, modified, missing, and user-owned.
|
|
15
|
+
* Uses only on-disk hash vs. stored hash (no template hash comparison).
|
|
16
|
+
*/
|
|
17
|
+
export async function classifyClaudeFiles(projectRoot, meta) {
|
|
18
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
19
|
+
const fileHashes = meta.fileHashes || {};
|
|
20
|
+
|
|
21
|
+
const safeToDelete = [];
|
|
22
|
+
const modified = [];
|
|
23
|
+
const missing = [];
|
|
24
|
+
const userOwned = [];
|
|
25
|
+
|
|
26
|
+
// Classify tracked files by comparing on-disk hash to stored hash
|
|
27
|
+
for (const [key, storedHash] of Object.entries(fileHashes)) {
|
|
28
|
+
const filePath = path.join(claudeDir, ...key.split('/'));
|
|
29
|
+
if (!(await fileExists(filePath))) {
|
|
30
|
+
missing.push(key);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const currentHash = await hashFile(filePath);
|
|
34
|
+
if (currentHash === storedHash) {
|
|
35
|
+
safeToDelete.push(key);
|
|
36
|
+
} else {
|
|
37
|
+
modified.push(key);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// workflow-meta.json is always worclaude's — safe to delete
|
|
42
|
+
if (await fileExists(path.join(claudeDir, 'workflow-meta.json'))) {
|
|
43
|
+
safeToDelete.push('workflow-meta.json');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Scan disk for files not in fileHashes
|
|
47
|
+
const allTrackedKeys = new Set([
|
|
48
|
+
...Object.keys(fileHashes),
|
|
49
|
+
'workflow-meta.json',
|
|
50
|
+
'settings.json',
|
|
51
|
+
]);
|
|
52
|
+
const allDiskFiles = await listFilesRecursive(claudeDir);
|
|
53
|
+
|
|
54
|
+
for (const fp of allDiskFiles) {
|
|
55
|
+
const relKey = path.relative(claudeDir, fp).split(path.sep).join('/');
|
|
56
|
+
if (allTrackedKeys.has(relKey)) continue;
|
|
57
|
+
|
|
58
|
+
// .workflow-ref.md files are upgrade artifacts — safe to delete
|
|
59
|
+
if (relKey.endsWith('.workflow-ref.md')) {
|
|
60
|
+
safeToDelete.push(relKey);
|
|
61
|
+
} else {
|
|
62
|
+
userOwned.push(relKey);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { safeToDelete, modified, missing, userOwned };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect root-level files that worclaude creates or modifies.
|
|
71
|
+
* settings.json is included here since it may contain user customizations.
|
|
72
|
+
*/
|
|
73
|
+
export async function detectRootFiles(projectRoot) {
|
|
74
|
+
const candidates = [
|
|
75
|
+
{
|
|
76
|
+
path: path.join('.claude', 'settings.json'),
|
|
77
|
+
label: '.claude/settings.json (permissions & hooks)',
|
|
78
|
+
},
|
|
79
|
+
{ path: 'CLAUDE.md', label: 'CLAUDE.md' },
|
|
80
|
+
{ path: '.mcp.json', label: '.mcp.json' },
|
|
81
|
+
{ path: path.join('docs', 'spec', 'PROGRESS.md'), label: 'docs/spec/PROGRESS.md' },
|
|
82
|
+
{ path: path.join('docs', 'spec', 'SPEC.md'), label: 'docs/spec/SPEC.md' },
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const found = [];
|
|
86
|
+
for (const c of candidates) {
|
|
87
|
+
if (await fileExists(path.join(projectRoot, c.path))) {
|
|
88
|
+
found.push(c);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// CLAUDE.md.workflow-suggestions is an upgrade artifact
|
|
93
|
+
const suggestionsPath = 'CLAUDE.md.workflow-suggestions';
|
|
94
|
+
if (await fileExists(path.join(projectRoot, suggestionsPath))) {
|
|
95
|
+
found.push({ path: suggestionsPath, label: suggestionsPath });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return found;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Delete specified files from .claude/ and clean up empty directories.
|
|
103
|
+
* Uses fs.remove (via removeDirectory) which handles both files and directories.
|
|
104
|
+
*/
|
|
105
|
+
export async function removeTrackedFiles(projectRoot, fileKeys) {
|
|
106
|
+
const claudeDir = path.join(projectRoot, '.claude');
|
|
107
|
+
let removedCount = 0;
|
|
108
|
+
|
|
109
|
+
for (const key of fileKeys) {
|
|
110
|
+
const filePath = path.join(claudeDir, ...key.split('/'));
|
|
111
|
+
if (await fileExists(filePath)) {
|
|
112
|
+
await removeDirectory(filePath);
|
|
113
|
+
removedCount++;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Clean up empty subdirectories
|
|
118
|
+
for (const subdir of ['agents', 'commands', 'skills']) {
|
|
119
|
+
const dirPath = path.join(claudeDir, subdir);
|
|
120
|
+
if (await dirExists(dirPath)) {
|
|
121
|
+
const remaining = await listFiles(dirPath);
|
|
122
|
+
if (remaining.length === 0) {
|
|
123
|
+
await removeDirectory(dirPath);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Remove .claude/ itself only if completely empty
|
|
129
|
+
if (await dirExists(claudeDir)) {
|
|
130
|
+
const remaining = await listFilesRecursive(claudeDir);
|
|
131
|
+
if (remaining.length === 0) {
|
|
132
|
+
await removeDirectory(claudeDir);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return removedCount;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Delete specified root-level files and clean up empty docs directories.
|
|
141
|
+
*/
|
|
142
|
+
export async function removeRootFiles(projectRoot, filePaths) {
|
|
143
|
+
let removedCount = 0;
|
|
144
|
+
|
|
145
|
+
for (const relPath of filePaths) {
|
|
146
|
+
const fullPath = path.join(projectRoot, relPath);
|
|
147
|
+
if (await fileExists(fullPath)) {
|
|
148
|
+
await removeDirectory(fullPath);
|
|
149
|
+
removedCount++;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Clean up empty docs/spec/ then docs/
|
|
154
|
+
const specDir = path.join(projectRoot, 'docs', 'spec');
|
|
155
|
+
if (await dirExists(specDir)) {
|
|
156
|
+
const remaining = await listFiles(specDir);
|
|
157
|
+
if (remaining.length === 0) {
|
|
158
|
+
await removeDirectory(specDir);
|
|
159
|
+
|
|
160
|
+
const docsDir = path.join(projectRoot, 'docs');
|
|
161
|
+
if (await dirExists(docsDir)) {
|
|
162
|
+
const docsRemaining = await listFilesRecursive(docsDir);
|
|
163
|
+
if (docsRemaining.length === 0) {
|
|
164
|
+
await removeDirectory(docsDir);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return removedCount;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Remove worclaude entries from .gitignore.
|
|
175
|
+
* Matches lines individually. Keeps .claude-backup-* / so backups stay git-ignored.
|
|
176
|
+
*/
|
|
177
|
+
export async function cleanGitignore(projectRoot) {
|
|
178
|
+
const gitignorePath = path.join(projectRoot, '.gitignore');
|
|
179
|
+
if (!(await fileExists(gitignorePath))) return false;
|
|
180
|
+
|
|
181
|
+
const content = await readFile(gitignorePath);
|
|
182
|
+
const lines = content.split(/\r?\n/);
|
|
183
|
+
|
|
184
|
+
const REMOVE_LINES = new Set(['# Worclaude (generated workflow files)', '.claude/']);
|
|
185
|
+
|
|
186
|
+
const filtered = lines.filter((line) => !REMOVE_LINES.has(line.trim()));
|
|
187
|
+
|
|
188
|
+
// Collapse consecutive blank lines (max 2 in a row)
|
|
189
|
+
const cleaned = [];
|
|
190
|
+
let blankCount = 0;
|
|
191
|
+
for (const line of filtered) {
|
|
192
|
+
if (line.trim() === '') {
|
|
193
|
+
blankCount++;
|
|
194
|
+
if (blankCount <= 2) cleaned.push(line);
|
|
195
|
+
} else {
|
|
196
|
+
blankCount = 0;
|
|
197
|
+
cleaned.push(line);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Trim trailing blank lines
|
|
202
|
+
while (cleaned.length > 0 && cleaned[cleaned.length - 1].trim() === '') {
|
|
203
|
+
cleaned.pop();
|
|
204
|
+
}
|
|
205
|
+
// Ensure file ends with newline if non-empty
|
|
206
|
+
const newContent = cleaned.length > 0 ? cleaned.join('\n') + '\n' : '';
|
|
207
|
+
|
|
208
|
+
if (newContent !== content) {
|
|
209
|
+
await writeFile(gitignorePath, newContent);
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
package/src/data/agents.js
CHANGED
|
@@ -168,6 +168,8 @@ export const COMMAND_FILES = [
|
|
|
168
168
|
'status',
|
|
169
169
|
'update-claude-md',
|
|
170
170
|
'setup',
|
|
171
|
+
'sync',
|
|
172
|
+
'conflict-resolver',
|
|
171
173
|
];
|
|
172
174
|
|
|
173
175
|
export const UNIVERSAL_SKILLS = [
|
|
@@ -217,24 +219,6 @@ export const TECH_STACKS = [
|
|
|
217
219
|
{ name: 'Other / None', value: 'other' },
|
|
218
220
|
];
|
|
219
221
|
|
|
220
|
-
export const FORMATTER_COMMANDS = {
|
|
221
|
-
python: 'ruff format . || true',
|
|
222
|
-
node: 'npx prettier --write . || true',
|
|
223
|
-
java: "google-java-format -i $(find . -name '*.java' 2>/dev/null) || true",
|
|
224
|
-
csharp: 'dotnet format || true',
|
|
225
|
-
cpp: "find . -name '*.c' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' | xargs clang-format -i || true",
|
|
226
|
-
go: 'gofmt -w . || true',
|
|
227
|
-
php: 'php-cs-fixer fix . || true',
|
|
228
|
-
ruby: 'rubocop -A || true',
|
|
229
|
-
kotlin: 'ktlint -F || true',
|
|
230
|
-
swift: 'swift-format format -r . -i || true',
|
|
231
|
-
rust: 'cargo fmt || true',
|
|
232
|
-
dart: 'dart format . || true',
|
|
233
|
-
scala: 'scalafmt || true',
|
|
234
|
-
elixir: 'mix format || true',
|
|
235
|
-
zig: 'zig fmt . || true',
|
|
236
|
-
};
|
|
237
|
-
|
|
238
222
|
export const PROJECT_TYPE_DESCRIPTIONS = {
|
|
239
223
|
'Full-stack web application': 'Frontend + backend in one repo',
|
|
240
224
|
'Backend / API': 'Server, REST/GraphQL, no frontend',
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { statusCommand } from './commands/status.js';
|
|
|
8
8
|
import { backupCommand } from './commands/backup.js';
|
|
9
9
|
import { restoreCommand } from './commands/restore.js';
|
|
10
10
|
import { diffCommand } from './commands/diff.js';
|
|
11
|
+
import { deleteCommand } from './commands/delete.js';
|
|
11
12
|
|
|
12
13
|
const program = new Command();
|
|
13
14
|
|
|
@@ -46,4 +47,9 @@ program
|
|
|
46
47
|
.description('Compare current setup against installed workflow version')
|
|
47
48
|
.action(diffCommand);
|
|
48
49
|
|
|
50
|
+
program
|
|
51
|
+
.command('delete')
|
|
52
|
+
.description('Remove worclaude workflow from project')
|
|
53
|
+
.action(deleteCommand);
|
|
54
|
+
|
|
49
55
|
program.parse();
|
|
@@ -1,23 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
1
|
+
Determine which branch you're on, then follow the appropriate flow.
|
|
2
|
+
|
|
3
|
+
## On a feature branch (feature/*, fix/*, chore/*, refactor/*)
|
|
4
|
+
|
|
5
|
+
Feature branches contain ONLY the task changes. Do NOT touch shared-state
|
|
6
|
+
files (see git-conventions.md for the canonical list).
|
|
7
|
+
|
|
8
|
+
1. Stage all changes: git add -A
|
|
9
|
+
2. Write a clear, conventional commit message
|
|
10
|
+
3. Push to the current branch
|
|
11
|
+
4. Create a PR targeting develop: gh pr create --base develop
|
|
12
|
+
5. Include in PR description: title, changes, testing done, reviewer notes
|
|
13
|
+
|
|
14
|
+
## On develop
|
|
15
|
+
|
|
16
|
+
Only used for release merges after /sync has been run.
|
|
17
|
+
|
|
18
|
+
1. Stage all changes: git add -A
|
|
19
|
+
2. Write a clear, conventional commit message
|
|
20
|
+
3. Push to develop
|
|
21
|
+
4. Create a PR targeting main: gh pr create --base main
|
|
22
|
+
|
|
23
|
+
## On any other branch
|
|
24
|
+
|
|
25
|
+
Ask the user which base branch to target before creating a PR.
|
|
26
|
+
|
|
27
|
+
Use gh pr create for PR creation.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
You are resolving merge conflicts. ONLY resolve conflicts — do not
|
|
2
|
+
update PROGRESS.md, SPEC.md, or bump versions. That is /sync's job.
|
|
3
|
+
|
|
4
|
+
## Step 1: Detect
|
|
5
|
+
|
|
6
|
+
Run: git status
|
|
7
|
+
Identify all files with merge conflicts (listed as "both modified").
|
|
8
|
+
If no conflicts found, report "No conflicts detected" and stop.
|
|
9
|
+
|
|
10
|
+
## Step 2: Understand
|
|
11
|
+
|
|
12
|
+
For each conflicted file:
|
|
13
|
+
- Read the file and find all <<<<<<< / ======= / >>>>>>> markers
|
|
14
|
+
- Understand what EACH side was trying to do
|
|
15
|
+
- Check git log for both branches to understand the intent
|
|
16
|
+
|
|
17
|
+
## Step 3: Resolve
|
|
18
|
+
|
|
19
|
+
For each conflict:
|
|
20
|
+
- Changes in DIFFERENT parts of the code → keep both
|
|
21
|
+
- Changes modify the SAME lines → combine intelligently based on intent
|
|
22
|
+
- Changes truly contradict → ask the user which to keep
|
|
23
|
+
- NEVER silently drop changes from either side
|
|
24
|
+
|
|
25
|
+
## Step 4: Verify clean
|
|
26
|
+
|
|
27
|
+
Search ALL tracked files for remaining conflict markers
|
|
28
|
+
(<<<<<<, =======, >>>>>>>). If any remain, resolve them.
|
|
29
|
+
|
|
30
|
+
## Step 5: Test
|
|
31
|
+
|
|
32
|
+
Run /verify (or the project's test and lint commands).
|
|
33
|
+
If anything fails, fix it.
|
|
34
|
+
|
|
35
|
+
## Step 6: Commit resolution only
|
|
36
|
+
|
|
37
|
+
- git add -A
|
|
38
|
+
- git commit -m "merge: resolve conflicts on develop"
|
|
39
|
+
|
|
40
|
+
Do NOT push. Do NOT create a PR. The user will run /sync next.
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
Use this
|
|
2
|
-
If the task is complete, use /commit-push-pr instead — it handles PROGRESS.md updates.
|
|
1
|
+
Use this ONLY when stopping work mid-task without committing.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
2.
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
Do NOT update PROGRESS.md — /sync handles that on develop after merging.
|
|
4
|
+
|
|
5
|
+
## Mid-task handoff
|
|
6
|
+
|
|
7
|
+
1. Create docs/handoffs/HANDOFF-{branch-name}-{date}.md
|
|
8
|
+
2. Include:
|
|
9
|
+
- What was being worked on
|
|
10
|
+
- What is done so far
|
|
11
|
+
- What is left to do
|
|
12
|
+
- Decisions or context the next session needs
|
|
13
|
+
- Files that were modified
|
|
14
|
+
3. git add -A
|
|
15
|
+
4. git commit -m "wip: handoff for [task description]"
|
|
16
|
+
5. git push
|
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
Read docs/spec/PROGRESS.md to understand current state.
|
|
2
|
+
Read .claude/skills/agent-routing.md for agent usage guidance.
|
|
3
|
+
|
|
4
|
+
Check for handoff files from previous sessions:
|
|
5
|
+
- Look in docs/handoffs/ for any HANDOFF*.md files
|
|
6
|
+
(both HANDOFF-{branch}-{date}.md and legacy HANDOFF_{date}.md)
|
|
7
|
+
- Prioritize files matching the current branch name
|
|
8
|
+
- If found, read them for context and report what was handed off
|
|
9
|
+
|
|
2
10
|
If an active implementation prompt exists, read it.
|
|
3
11
|
Report: what was last completed, what's next, any blockers.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Update shared-state files after merging feature PRs into develop.
|
|
2
|
+
Run this on the develop branch AFTER all PRs are merged and any
|
|
3
|
+
conflicts are resolved.
|
|
4
|
+
|
|
5
|
+
ONLY update shared-state files — do not resolve conflicts. That is
|
|
6
|
+
/conflict-resolver's job. If you detect unresolved conflicts, stop
|
|
7
|
+
and tell the user to run /conflict-resolver first.
|
|
8
|
+
|
|
9
|
+
## Pre-check
|
|
10
|
+
|
|
11
|
+
1. Confirm you are on the develop branch. If not, stop and warn.
|
|
12
|
+
2. Check for unresolved conflict markers in tracked files.
|
|
13
|
+
If found, stop: "Run /conflict-resolver first."
|
|
14
|
+
3. Read git log to understand what was merged since the last sync
|
|
15
|
+
or version tag. If nothing was merged, report "Nothing to sync"
|
|
16
|
+
and stop.
|
|
17
|
+
|
|
18
|
+
## Update PROGRESS.md
|
|
19
|
+
|
|
20
|
+
4. Update docs/spec/PROGRESS.md:
|
|
21
|
+
- Mark completed items based on merged work
|
|
22
|
+
- Update "Last Updated" date to today
|
|
23
|
+
- Update Stats section by checking actual project values
|
|
24
|
+
(run the test suite, count source files, etc.)
|
|
25
|
+
- Move "In Progress" items to "Completed" if their PRs merged
|
|
26
|
+
- Update "Next Steps" if needed
|
|
27
|
+
|
|
28
|
+
## Update SPEC.md
|
|
29
|
+
|
|
30
|
+
5. If any merged PRs added features or changed behavior, update
|
|
31
|
+
docs/spec/SPEC.md to reflect the current state.
|
|
32
|
+
If nothing changed spec-wise, leave it alone.
|
|
33
|
+
|
|
34
|
+
## Version bump
|
|
35
|
+
|
|
36
|
+
6. Determine version bump using the Versioning Policy in
|
|
37
|
+
git-conventions.md. If bump needed: update version in package.json.
|
|
38
|
+
|
|
39
|
+
## Verify
|
|
40
|
+
|
|
41
|
+
7. Run /verify to confirm tests and lint pass.
|
|
42
|
+
If anything fails, fix it before proceeding.
|
|
43
|
+
|
|
44
|
+
## Commit, push, and PR
|
|
45
|
+
|
|
46
|
+
8. git add -A
|
|
47
|
+
9. git commit -m "chore: sync progress, spec, and version to [new version]"
|
|
48
|
+
10. git push origin develop
|
|
49
|
+
11. gh pr create --base main with description of what was merged
|
|
@@ -29,7 +29,9 @@ See `.claude/skills/` — load only what's relevant:
|
|
|
29
29
|
## Session Protocol
|
|
30
30
|
**Start:** Read PROGRESS.md → Read `.claude/skills/agent-routing.md` → Read active implementation prompt if any.
|
|
31
31
|
**During:** One task at a time. Commit after each. Use subagents per routing guide.
|
|
32
|
-
**
|
|
32
|
+
**Feature branch:** /start → work → /verify → /commit-push-pr
|
|
33
|
+
**After merging PRs:** git checkout develop → git pull → /conflict-resolver (if needed) → /sync
|
|
34
|
+
**Mid-task stop:** /end (writes handoff file)
|
|
33
35
|
|
|
34
36
|
## Critical Rules
|
|
35
37
|
1. SPEC.md is source of truth. Do not invent features.
|
|
@@ -39,6 +41,7 @@ See `.claude/skills/` — load only what's relevant:
|
|
|
39
41
|
5. Self-healing: same mistake twice → update CLAUDE.md.
|
|
40
42
|
6. Use subagents to keep main context clean.
|
|
41
43
|
7. Mediocre fix → scrap it, implement elegantly.
|
|
44
|
+
8. Feature branches NEVER modify shared-state files. Those are updated only on develop via /sync after merging PRs. See git-conventions.md Shared-State Files for the canonical list.
|
|
42
45
|
|
|
43
46
|
## Gotchas
|
|
44
47
|
[Grows during development]
|
|
@@ -56,6 +56,8 @@ The workflow installs a PostCompact hook that runs:
|
|
|
56
56
|
cat CLAUDE.md && cat docs/spec/PROGRESS.md 2>/dev/null || true
|
|
57
57
|
```
|
|
58
58
|
|
|
59
|
+
> On Windows, this command runs in Git Bash (installed with [Git for Windows](https://gitforwindows.org)).
|
|
60
|
+
|
|
59
61
|
This ensures you never lose your bearings after compaction. The hook fires
|
|
60
62
|
automatically — you don't need to re-read these files manually.
|
|
61
63
|
|
|
@@ -97,6 +97,17 @@ When using `git worktree` for parallel work:
|
|
|
97
97
|
Agents that use worktree isolation (code-simplifier, test-writer, ci-fixer, etc.)
|
|
98
98
|
create and clean up their own worktrees automatically.
|
|
99
99
|
|
|
100
|
+
## Shared-State Files
|
|
101
|
+
|
|
102
|
+
These files are modified ONLY on the develop branch (via /sync), never on feature branches:
|
|
103
|
+
|
|
104
|
+
- `docs/spec/PROGRESS.md` — project progress tracker
|
|
105
|
+
- `docs/spec/SPEC.md` — feature specification
|
|
106
|
+
- `README.md` — project documentation
|
|
107
|
+
- `package.json` version field — release versioning
|
|
108
|
+
|
|
109
|
+
This prevents merge conflicts when running parallel feature branches.
|
|
110
|
+
|
|
100
111
|
## Versioning Policy
|
|
101
112
|
|
|
102
113
|
Follow [semver](https://semver.org/) when the project publishes releases:
|