worclaude 1.3.9 → 1.5.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/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 +0 -18
- package/src/index.js +6 -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 +24 -1
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('\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
|
@@ -217,24 +217,6 @@ export const TECH_STACKS = [
|
|
|
217
217
|
{ name: 'Other / None', value: 'other' },
|
|
218
218
|
];
|
|
219
219
|
|
|
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
220
|
export const PROJECT_TYPE_DESCRIPTIONS = {
|
|
239
221
|
'Full-stack web application': 'Frontend + backend in one repo',
|
|
240
222
|
'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();
|
|
@@ -15,11 +15,14 @@
|
|
|
15
15
|
## Skills (read on demand, not upfront)
|
|
16
16
|
See `.claude/skills/` — load only what's relevant:
|
|
17
17
|
- context-management.md — Session lifecycle
|
|
18
|
-
-
|
|
18
|
+
- claude-md-maintenance.md — CLAUDE.md self-healing
|
|
19
|
+
- git-conventions.md — Commits, branches, versioning
|
|
19
20
|
- planning-with-files.md — Implementation planning
|
|
21
|
+
- prompt-engineering.md — Prompting patterns and quality
|
|
20
22
|
- review-and-handoff.md — Session endings
|
|
21
23
|
- verification.md — How to verify work
|
|
22
24
|
- testing.md — Test philosophy and patterns
|
|
25
|
+
- subagent-usage.md — When and how to use subagents
|
|
23
26
|
- agent-routing.md — When and how to use each installed agent (READ EVERY SESSION)
|
|
24
27
|
{project_specific_skills}
|
|
25
28
|
|
|
@@ -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
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: "Branch naming, commit message format, PR workflow, worktree conventions"
|
|
2
|
+
description: "Branch naming, commit message format, PR workflow, worktree conventions, versioning policy"
|
|
3
3
|
---
|
|
4
4
|
|
|
5
5
|
# Git Conventions
|
|
@@ -97,6 +97,29 @@ 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
|
+
## Versioning Policy
|
|
101
|
+
|
|
102
|
+
Follow [semver](https://semver.org/) when the project publishes releases:
|
|
103
|
+
|
|
104
|
+
| What changed | Bump | Example |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| Bug fix, patch to existing behavior | **patch** | Fixed edge case in date parser |
|
|
107
|
+
| New feature, command, or API surface | **minor** | Added CSV export option |
|
|
108
|
+
| Breaking change to public API or CLI | **major** | Renamed config key, removed flag |
|
|
109
|
+
| Only docs, CI, tests, internal refactor | **no bump** | Updated README, added test |
|
|
110
|
+
|
|
111
|
+
**Publish from the primary branch (usually `main`),** not from feature or development branches. What is published must always match what is on the release branch.
|
|
112
|
+
|
|
113
|
+
**When to bump:** Include the version change in the same PR as the work — no separate "bump version" commits after the fact.
|
|
114
|
+
|
|
115
|
+
**How to publish:**
|
|
116
|
+
1. Merge the release PR into `main`
|
|
117
|
+
2. Pull locally: `git checkout main && git pull`
|
|
118
|
+
3. Publish using your ecosystem's tool (`npm publish`, `cargo publish`, `twine upload`, etc.)
|
|
119
|
+
4. Sync develop: `git checkout develop && git merge main && git push origin develop`
|
|
120
|
+
|
|
121
|
+
**Rule of thumb:** If the change affects what users see, install, or depend on, it needs a version bump. If it only affects the project's internal development workflow, it does not.
|
|
122
|
+
|
|
100
123
|
## Gotchas
|
|
101
124
|
|
|
102
125
|
- Never force-push to main/master. Force-push to feature branches only when you
|