wtf-p 0.4.0 → 0.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/README.md +146 -226
- package/bin/commands/doctor.js +88 -2
- package/bin/commands/install-logic.js +121 -26
- package/bin/commands/status.js +22 -4
- package/bin/commands/update.js +5 -4
- package/bin/install.js +66 -26
- package/bin/lib/checkpoint.js +183 -0
- package/bin/lib/context-primer.js +214 -0
- package/bin/lib/manifest.js +62 -2
- package/bin/lib/utils.js +22 -1
- package/bin/uninstall.js +270 -111
- package/core/write-the-f-paper/references/agent-model-matrix.md +62 -0
- package/core/write-the-f-paper/references/checkpoints.md +160 -9
- package/core/write-the-f-paper/references/context-fidelity.md +153 -0
- package/core/write-the-f-paper/references/deviation-rules.md +150 -0
- package/core/write-the-f-paper/references/git-integration.md +80 -0
- package/core/write-the-f-paper/references/orchestrator-pattern.md +167 -0
- package/core/write-the-f-paper/references/planning-config.md +135 -0
- package/core/write-the-f-paper/references/ui-brand.md +186 -0
- package/core/write-the-f-paper/templates/UAT.md +80 -0
- package/core/write-the-f-paper/templates/base-prefs.yaml +42 -0
- package/core/write-the-f-paper/templates/config.json +31 -5
- package/core/write-the-f-paper/workflows/create-outline.wcn.md +1 -1
- package/core/write-the-f-paper/workflows/execute-outline.md +163 -0
- package/core/write-the-f-paper/workflows/execute-outline.wcn.md +73 -0
- package/core/write-the-f-paper/workflows/execute-section.wcn.md +141 -169
- package/core/write-the-f-paper/workflows/plan-section.wcn.md +96 -103
- package/core/write-the-f-paper/workflows/research-gap.md +27 -3
- package/core/write-the-f-paper/workflows/research-gap.wcn.md +18 -1
- package/core/write-the-f-paper/workflows/verify-work.md +251 -0
- package/core/write-the-f-paper/workflows/verify-work.wcn.md +74 -0
- package/package.json +19 -7
- package/vendors/claude/.claude-plugin/plugin.json +2 -2
- package/vendors/claude/agents/wtfp/argument-verifier.md +189 -0
- package/vendors/claude/agents/wtfp/citation-expert.md +1 -1
- package/vendors/claude/agents/wtfp/citation-formatter.md +1 -1
- package/vendors/claude/agents/wtfp/coherence-checker.md +167 -0
- package/vendors/claude/agents/wtfp/outliner.md +259 -0
- package/vendors/claude/agents/wtfp/plan-checker.md +249 -0
- package/vendors/claude/agents/wtfp/prose-polisher.md +166 -0
- package/vendors/claude/agents/wtfp/research-synthesizer.md +215 -0
- package/vendors/claude/agents/wtfp/section-planner.md +364 -0
- package/vendors/claude/agents/wtfp/section-reviewer.md +182 -0
- package/vendors/claude/agents/wtfp/section-writer.md +312 -0
- package/vendors/claude/commands/wtfp/add-todo.md +108 -0
- package/vendors/claude/commands/wtfp/analyze-bib.md +58 -236
- package/vendors/claude/commands/wtfp/audit-milestone.md +286 -0
- package/vendors/claude/commands/wtfp/check-refs.md +60 -133
- package/vendors/claude/commands/wtfp/check-todos.md +152 -0
- package/vendors/claude/commands/wtfp/checkpoint.md +105 -0
- package/vendors/claude/commands/wtfp/contribute.md +1 -1
- package/vendors/claude/commands/wtfp/create-outline.md +109 -195
- package/vendors/claude/commands/wtfp/create-poster.md +1 -1
- package/vendors/claude/commands/wtfp/create-slides.md +1 -1
- package/vendors/claude/commands/wtfp/discuss-section.md +1 -1
- package/vendors/claude/commands/wtfp/execute-outline.md +257 -0
- package/vendors/claude/commands/wtfp/export-latex.md +1 -1
- package/vendors/claude/commands/wtfp/help.md +174 -280
- package/vendors/claude/commands/wtfp/insert-section.md +1 -1
- package/vendors/claude/commands/wtfp/list-assumptions.md +1 -1
- package/vendors/claude/commands/wtfp/map-project.md +35 -124
- package/vendors/claude/commands/wtfp/new-paper.md +85 -320
- package/vendors/claude/commands/wtfp/pause-writing.md +1 -1
- package/vendors/claude/commands/wtfp/plan-milestone-gaps.md +255 -0
- package/vendors/claude/commands/wtfp/plan-revision.md +1 -1
- package/vendors/claude/commands/wtfp/plan-section.md +165 -190
- package/vendors/claude/commands/wtfp/polish-prose.md +66 -118
- package/vendors/claude/commands/wtfp/progress.md +82 -254
- package/vendors/claude/commands/wtfp/quick.md +107 -0
- package/vendors/claude/commands/wtfp/remove-section.md +1 -1
- package/vendors/claude/commands/wtfp/report-bug.md +1 -1
- package/vendors/claude/commands/wtfp/request-feature.md +1 -1
- package/vendors/claude/commands/wtfp/research-gap.md +138 -150
- package/vendors/claude/commands/wtfp/resume-writing.md +1 -1
- package/vendors/claude/commands/wtfp/review-section.md +55 -333
- package/vendors/claude/commands/wtfp/settings.md +197 -0
- package/vendors/claude/commands/wtfp/submit-milestone.md +248 -99
- package/vendors/claude/commands/wtfp/update.md +112 -0
- package/vendors/claude/commands/wtfp/verify-work.md +254 -0
- package/vendors/claude/commands/wtfp/write-section.md +144 -180
- package/vendors/gemini/agents/wtfp/argument-verifier.md +184 -0
- package/vendors/gemini/agents/wtfp/citation-expert.md +41 -0
- package/vendors/gemini/agents/wtfp/citation-formatter.md +38 -0
- package/vendors/gemini/agents/wtfp/coherence-checker.md +162 -0
- package/vendors/gemini/agents/wtfp/outliner.md +252 -0
- package/vendors/gemini/agents/wtfp/plan-checker.md +244 -0
- package/vendors/gemini/agents/wtfp/prose-polisher.md +158 -0
- package/vendors/gemini/agents/wtfp/research-synthesizer.md +205 -0
- package/vendors/gemini/agents/wtfp/section-planner.md +355 -0
- package/vendors/gemini/agents/wtfp/section-reviewer.md +175 -0
- package/vendors/gemini/agents/wtfp/section-writer.md +304 -0
- package/vendors/gemini/commands/wtfp/add-todo.toml +102 -0
- package/vendors/gemini/commands/wtfp/analyze-bib.toml +126 -0
- package/vendors/gemini/commands/wtfp/audit-milestone.toml +278 -0
- package/vendors/gemini/commands/wtfp/check-refs.toml +123 -0
- package/vendors/gemini/commands/wtfp/check-todos.toml +145 -0
- package/vendors/gemini/commands/wtfp/checkpoint.toml +96 -0
- package/vendors/gemini/commands/wtfp/contribute.toml +269 -0
- package/vendors/gemini/commands/wtfp/create-outline.toml +180 -0
- package/vendors/gemini/commands/wtfp/create-poster.toml +36 -0
- package/vendors/gemini/commands/wtfp/create-slides.toml +35 -0
- package/vendors/gemini/commands/wtfp/discuss-section.toml +62 -0
- package/vendors/gemini/commands/wtfp/execute-outline.toml +247 -0
- package/vendors/gemini/commands/wtfp/export-latex.toml +209 -0
- package/vendors/gemini/commands/wtfp/help.toml +184 -0
- package/vendors/gemini/commands/wtfp/insert-section.toml +123 -0
- package/vendors/gemini/commands/wtfp/list-assumptions.toml +104 -0
- package/vendors/gemini/commands/wtfp/map-project.toml +99 -0
- package/vendors/gemini/commands/wtfp/new-paper.toml +160 -0
- package/vendors/gemini/commands/wtfp/pause-writing.toml +140 -0
- package/vendors/gemini/commands/wtfp/plan-milestone-gaps.toml +249 -0
- package/vendors/gemini/commands/wtfp/plan-revision.toml +171 -0
- package/vendors/gemini/commands/wtfp/plan-section.toml +212 -0
- package/vendors/gemini/commands/wtfp/polish-prose.toml +114 -0
- package/vendors/gemini/commands/wtfp/progress.toml +145 -0
- package/vendors/gemini/commands/wtfp/quick.toml +96 -0
- package/vendors/gemini/commands/wtfp/remove-section.toml +140 -0
- package/vendors/gemini/commands/wtfp/report-bug.toml +126 -0
- package/vendors/gemini/commands/wtfp/request-feature.toml +155 -0
- package/vendors/gemini/commands/wtfp/research-gap.toml +214 -0
- package/vendors/gemini/commands/wtfp/resume-writing.toml +136 -0
- package/vendors/gemini/commands/wtfp/review-section.toml +121 -0
- package/vendors/gemini/commands/wtfp/settings.toml +191 -0
- package/vendors/gemini/commands/wtfp/submit-milestone.toml +351 -0
- package/vendors/gemini/commands/wtfp/update.toml +107 -0
- package/vendors/gemini/commands/wtfp/verify-work.toml +245 -0
- package/vendors/gemini/commands/wtfp/write-section.toml +228 -0
- package/vendors/opencode/agents/wtfp/argument-verifier.md +184 -0
- package/vendors/opencode/agents/wtfp/citation-expert.md +41 -0
- package/vendors/opencode/agents/wtfp/citation-formatter.md +38 -0
- package/vendors/opencode/agents/wtfp/coherence-checker.md +162 -0
- package/vendors/opencode/agents/wtfp/outliner.md +252 -0
- package/vendors/opencode/agents/wtfp/plan-checker.md +244 -0
- package/vendors/opencode/agents/wtfp/prose-polisher.md +158 -0
- package/vendors/opencode/agents/wtfp/research-synthesizer.md +205 -0
- package/vendors/opencode/agents/wtfp/section-planner.md +355 -0
- package/vendors/opencode/agents/wtfp/section-reviewer.md +175 -0
- package/vendors/opencode/agents/wtfp/section-writer.md +304 -0
- package/vendors/opencode/commands/wtfp/add-todo.md +103 -0
- package/vendors/opencode/commands/wtfp/analyze-bib.md +127 -0
- package/vendors/opencode/commands/wtfp/audit-milestone.md +279 -0
- package/vendors/opencode/commands/wtfp/check-refs.md +124 -0
- package/vendors/opencode/commands/wtfp/check-todos.md +146 -0
- package/vendors/opencode/commands/wtfp/checkpoint.md +97 -0
- package/vendors/opencode/commands/wtfp/contribute.md +266 -0
- package/vendors/opencode/commands/wtfp/create-outline.md +181 -0
- package/vendors/opencode/commands/wtfp/create-poster.md +37 -0
- package/vendors/opencode/commands/wtfp/create-slides.md +36 -0
- package/vendors/opencode/commands/wtfp/discuss-section.md +63 -0
- package/vendors/opencode/commands/wtfp/execute-outline.md +248 -0
- package/vendors/opencode/commands/wtfp/export-latex.md +210 -0
- package/vendors/opencode/commands/wtfp/help.md +185 -0
- package/vendors/opencode/commands/wtfp/insert-section.md +124 -0
- package/vendors/opencode/commands/wtfp/list-assumptions.md +105 -0
- package/vendors/opencode/commands/wtfp/map-project.md +100 -0
- package/vendors/opencode/commands/wtfp/new-paper.md +161 -0
- package/vendors/opencode/commands/wtfp/pause-writing.md +141 -0
- package/vendors/opencode/commands/wtfp/plan-milestone-gaps.md +250 -0
- package/vendors/opencode/commands/wtfp/plan-revision.md +174 -0
- package/vendors/opencode/commands/wtfp/plan-section.md +214 -0
- package/vendors/opencode/commands/wtfp/polish-prose.md +115 -0
- package/vendors/opencode/commands/wtfp/progress.md +146 -0
- package/vendors/opencode/commands/wtfp/quick.md +97 -0
- package/vendors/opencode/commands/wtfp/remove-section.md +141 -0
- package/vendors/opencode/commands/wtfp/report-bug.md +127 -0
- package/vendors/opencode/commands/wtfp/request-feature.md +156 -0
- package/vendors/opencode/commands/wtfp/research-gap.md +215 -0
- package/vendors/opencode/commands/wtfp/resume-writing.md +137 -0
- package/vendors/opencode/commands/wtfp/review-section.md +123 -0
- package/vendors/opencode/commands/wtfp/settings.md +192 -0
- package/vendors/opencode/commands/wtfp/submit-milestone.md +352 -0
- package/vendors/opencode/commands/wtfp/update.md +108 -0
- package/vendors/opencode/commands/wtfp/verify-work.md +246 -0
- package/vendors/opencode/commands/wtfp/write-section.md +228 -0
- package/vendors/claude/agents/wtfp/citation-retriever.md +0 -31
|
@@ -2,16 +2,43 @@ const fs = require('fs');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const MANIFEST = require('../lib/manifest');
|
|
5
|
-
const {
|
|
6
|
-
isValidPath,
|
|
7
|
-
|
|
8
|
-
getPathLabel,
|
|
9
|
-
getBackupPath,
|
|
10
|
-
writeVersionFile,
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
const {
|
|
6
|
+
isValidPath,
|
|
7
|
+
expandTilde,
|
|
8
|
+
getPathLabel,
|
|
9
|
+
getBackupPath,
|
|
10
|
+
writeVersionFile,
|
|
11
|
+
detectInstallation,
|
|
12
|
+
createRL,
|
|
13
|
+
prompt
|
|
13
14
|
} = require('../lib/utils');
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Get vendor-specific config directory
|
|
18
|
+
*/
|
|
19
|
+
function getVendorDir(runtime, explicitConfigDir) {
|
|
20
|
+
// Handle 'claude-local' special case
|
|
21
|
+
if (runtime === 'claude-local') {
|
|
22
|
+
return path.join(process.cwd(), '.claude');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const vendorConfig = MANIFEST[runtime];
|
|
26
|
+
if (!vendorConfig) {
|
|
27
|
+
throw new Error(`Unknown runtime: ${runtime}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (explicitConfigDir) {
|
|
31
|
+
return expandTilde(explicitConfigDir);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const envDir = process.env[vendorConfig.configDirEnv];
|
|
35
|
+
if (envDir) {
|
|
36
|
+
return expandTilde(envDir);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return path.join(os.homedir(), vendorConfig.defaultDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
15
42
|
/**
|
|
16
43
|
* Process file content with path replacement
|
|
17
44
|
*/
|
|
@@ -68,15 +95,15 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
|
|
|
68
95
|
|
|
69
96
|
if (existingFiles.length > 0 && !hasForce && !hasBackupAll && isInteractive) {
|
|
70
97
|
const relPath = getPathLabel(targetDir, true);
|
|
71
|
-
out.log(` ${c.yellow(`Found ${existingFiles.length} existing
|
|
98
|
+
out.log(` ${c.yellow(`Found ${existingFiles.length} existing file(s) in ${relPath}`)}
|
|
72
99
|
`);
|
|
73
100
|
|
|
74
101
|
if (showExplanations) {
|
|
75
102
|
out.log(` ${c.dim('For each file, choose:')}`);
|
|
76
|
-
out.log(` ${c.dim('[o]verwrite
|
|
77
|
-
out.log(` ${c.dim('[s]kip
|
|
78
|
-
out.log(` ${c.dim('[b]ackup
|
|
79
|
-
out.log(` ${c.dim('[a]ll
|
|
103
|
+
out.log(` ${c.dim('[o]verwrite Replace with new version')}`);
|
|
104
|
+
out.log(` ${c.dim('[s]kip Keep your existing file')}`);
|
|
105
|
+
out.log(` ${c.dim('[b]ackup Save existing, then install new')}`);
|
|
106
|
+
out.log(` ${c.dim('[a]ll Apply same choice to all remaining')}\n`);
|
|
80
107
|
}
|
|
81
108
|
}
|
|
82
109
|
|
|
@@ -132,6 +159,18 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
|
|
|
132
159
|
stats.backed++;
|
|
133
160
|
}
|
|
134
161
|
|
|
162
|
+
// Safety: never overwrite a non-WTF-P plugin.json
|
|
163
|
+
if (file.name === 'plugin.json' && file.componentId === 'plugin' && exists) {
|
|
164
|
+
try {
|
|
165
|
+
const existing = JSON.parse(fs.readFileSync(file.dest, 'utf8'));
|
|
166
|
+
if (existing.name && existing.name !== 'wtf-p' && existing.name !== 'write-the-f-paper') {
|
|
167
|
+
out.verbose(` ${c.yellow('!')} ${c.dim(relDest)} belongs to another plugin — skipped`);
|
|
168
|
+
stats.skipped++;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
} catch { /* corrupt JSON — safe to overwrite */ }
|
|
172
|
+
}
|
|
173
|
+
|
|
135
174
|
const content = processContent(file.src, pathPrefix);
|
|
136
175
|
if (content !== null) {
|
|
137
176
|
fs.writeFileSync(file.dest, content);
|
|
@@ -153,17 +192,27 @@ async function installWithConflictResolution(files, pathPrefix, targetDir, optio
|
|
|
153
192
|
|
|
154
193
|
/**
|
|
155
194
|
* Main install logic
|
|
195
|
+
* @param {string} runtime - 'claude' | 'gemini' | 'claude-local'
|
|
196
|
+
* @param {boolean} isUpdate - Whether this is an update operation
|
|
197
|
+
* @param {object} options - CLI options
|
|
198
|
+
* @param {object} pkg - Package.json contents
|
|
156
199
|
*/
|
|
157
|
-
async function install(
|
|
200
|
+
async function install(runtime, isUpdate, options, pkg) {
|
|
158
201
|
const { out, explicitConfigDir, hasQuiet, onlyInstall, showExplanations } = options;
|
|
159
202
|
const c = out.colors;
|
|
160
203
|
|
|
161
|
-
//
|
|
162
|
-
const
|
|
163
|
-
|
|
204
|
+
// Handle 'claude-local' by mapping to 'claude' vendor config
|
|
205
|
+
const vendorKey = runtime === 'claude-local' ? 'claude' : runtime;
|
|
206
|
+
const vendorConfig = MANIFEST[vendorKey];
|
|
207
|
+
|
|
208
|
+
if (!vendorConfig) {
|
|
209
|
+
out.error(`Unknown runtime: ${runtime}`);
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
|
|
164
213
|
// Resolve Target Directory using the Vendor Strategy
|
|
165
|
-
|
|
166
|
-
const
|
|
214
|
+
const targetDir = getVendorDir(runtime, explicitConfigDir);
|
|
215
|
+
const isGlobal = runtime !== 'claude-local';
|
|
167
216
|
const locationLabel = getPathLabel(targetDir, isGlobal);
|
|
168
217
|
|
|
169
218
|
// Validate path
|
|
@@ -174,7 +223,53 @@ async function install(isGlobal, isUpdate, options, pkg) {
|
|
|
174
223
|
|
|
175
224
|
const pathPrefix = isGlobal
|
|
176
225
|
? (explicitConfigDir ? `${targetDir}/` : `~/${vendorConfig.defaultDir}/`)
|
|
177
|
-
:
|
|
226
|
+
: `./.claude/`;
|
|
227
|
+
|
|
228
|
+
// ---- Cross-installation conflict detection ----
|
|
229
|
+
// If installing locally, warn if a global install already exists (and vice versa).
|
|
230
|
+
// Claude Code loads commands from BOTH ~/.claude/ and ./.claude/, causing duplicates.
|
|
231
|
+
if (vendorKey === 'claude' && !explicitConfigDir) {
|
|
232
|
+
const globalDir = path.join(os.homedir(), '.claude');
|
|
233
|
+
const localDir = path.join(process.cwd(), '.claude');
|
|
234
|
+
|
|
235
|
+
if (runtime === 'claude-local') {
|
|
236
|
+
// Installing local — check if global exists
|
|
237
|
+
const globalInstall = detectInstallation(globalDir);
|
|
238
|
+
if (globalInstall.hasCommands) {
|
|
239
|
+
out.log(` ${c.yellow('⚠ WTF-P is already installed globally')} ${c.dim('(' + getPathLabel(globalDir, true) + ')')}`);
|
|
240
|
+
out.log(` Installing locally too will cause every command to appear twice.`);
|
|
241
|
+
out.log('');
|
|
242
|
+
out.log(` ${c.cyan('Options:')}`);
|
|
243
|
+
out.log(` • Use your global install as-is (recommended)`);
|
|
244
|
+
out.log(` • Remove global first: ${c.dim('npx wtf-p uninstall --global')}`);
|
|
245
|
+
out.log(` • Continue anyway (commands will be duplicated)`);
|
|
246
|
+
out.log('');
|
|
247
|
+
|
|
248
|
+
if (options.isInteractive && !options.hasForce) {
|
|
249
|
+
const rl = createRL();
|
|
250
|
+
const answer = await prompt(rl, ` Continue with local install? [y/N]: `);
|
|
251
|
+
rl.close();
|
|
252
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
253
|
+
out.log(`\n ${c.yellow('Aborted.')} Using global install.\n`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
out.log('');
|
|
257
|
+
} else if (!options.hasForce) {
|
|
258
|
+
out.log(` ${c.yellow('Aborted.')} Use --force to install anyway.\n`);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} else if (runtime === 'claude') {
|
|
263
|
+
// Installing global — check if local exists in cwd
|
|
264
|
+
const localInstall = detectInstallation(localDir);
|
|
265
|
+
if (localInstall.hasCommands && globalDir !== localDir) {
|
|
266
|
+
out.log(` ${c.yellow('⚠ WTF-P is also installed locally')} ${c.dim('(' + getPathLabel(localDir, false) + ')')}`);
|
|
267
|
+
out.log(` Commands may appear twice in this directory.`);
|
|
268
|
+
out.log(` To fix: ${c.dim('npx wtf-p uninstall --local')}`);
|
|
269
|
+
out.log('');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
178
273
|
|
|
179
274
|
if (!hasQuiet) {
|
|
180
275
|
out.log(` Installing to ${c.cyan(locationLabel)}
|
|
@@ -214,16 +309,16 @@ async function install(isGlobal, isUpdate, options, pkg) {
|
|
|
214
309
|
// Summary
|
|
215
310
|
if (!hasQuiet) {
|
|
216
311
|
out.log(`
|
|
217
|
-
${c.green('Done!')} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
|
|
312
|
+
${c.green('Done!')} ${c.dim(`[${vendorConfig.name}]`)} Installed: ${stats.installed}, Skipped: ${stats.skipped}, Backed up: ${stats.backed}
|
|
218
313
|
|
|
219
|
-
Run ${c.cyan('/wtfp:help')} in
|
|
314
|
+
Run ${c.cyan('/wtfp:help')} in ${vendorConfig.name} to get started.
|
|
220
315
|
`);
|
|
221
316
|
|
|
222
317
|
if (showExplanations && !isUpdate) {
|
|
223
|
-
out.log(` ${c.yellow('
|
|
224
|
-
out.log(` ${c.cyan('/wtfp:new-paper')}
|
|
225
|
-
out.log(` ${c.cyan('/wtfp:progress')}
|
|
226
|
-
out.log(` ${c.cyan('/wtfp:help')}
|
|
318
|
+
out.log(` ${c.yellow('Get started:')}`);
|
|
319
|
+
out.log(` ${c.cyan('/wtfp:new-paper')} Define your paper's vision and structure`);
|
|
320
|
+
out.log(` ${c.cyan('/wtfp:progress')} See where you are and what's next`);
|
|
321
|
+
out.log(` ${c.cyan('/wtfp:help')} Browse all available commands\n`);
|
|
227
322
|
}
|
|
228
323
|
}
|
|
229
324
|
}
|
package/bin/commands/status.js
CHANGED
|
@@ -57,6 +57,7 @@ async function showStatus(options, pkg) {
|
|
|
57
57
|
if (loc.hasCommands) components.push(`commands (${loc.commandFiles.length} files)`);
|
|
58
58
|
if (loc.hasWorkflows) components.push(`workflows (${loc.workflowFiles.length} files)`);
|
|
59
59
|
if (loc.hasSkills) components.push(`skills (${loc.skillFiles.length} files)`);
|
|
60
|
+
if (loc.hasAgents) components.push(`agents (${loc.agentFiles.length} files)`);
|
|
60
61
|
out.log(` Installed: ${components.join(', ')}`);
|
|
61
62
|
|
|
62
63
|
// Warnings
|
|
@@ -70,12 +71,29 @@ async function showStatus(options, pkg) {
|
|
|
70
71
|
out.log('');
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
// Check for
|
|
74
|
+
// Check for dual-install conflict (global + local both present)
|
|
75
|
+
const bothInstalled = locations.filter(l =>
|
|
76
|
+
l.hasCommands || l.hasWorkflows || l.hasSkills
|
|
77
|
+
);
|
|
78
|
+
if (bothInstalled.length > 1) {
|
|
79
|
+
out.log(` ${c.red('⚠ Duplicate installation detected!')}`);
|
|
80
|
+
out.log(` WTF-P is installed in both locations. This causes every command to appear twice.`);
|
|
81
|
+
out.log(` Remove one to fix:`);
|
|
82
|
+
out.log(` ${c.cyan('npx wtf-p uninstall --local')} Remove local copy`);
|
|
83
|
+
out.log(` ${c.cyan('npx wtf-p uninstall --global')} Remove global copy\n`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check for updates (only suggest if registry version is actually newer)
|
|
74
87
|
try {
|
|
75
88
|
const latest = execSync('npm view wtf-p version 2>/dev/null', { encoding: 'utf8' }).trim();
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
const isNewer = (a, b) => {
|
|
90
|
+
const pa = a.split('.').map(Number), pb = b.split('.').map(Number);
|
|
91
|
+
for (let i = 0; i < 3; i++) { if ((pa[i] || 0) > (pb[i] || 0)) return true; if ((pa[i] || 0) < (pb[i] || 0)) return false; }
|
|
92
|
+
return false;
|
|
93
|
+
};
|
|
94
|
+
if (latest && isNewer(latest, pkg.version)) {
|
|
95
|
+
out.log(` ${c.yellow('Update available:')} ${pkg.version} -> ${c.green(latest)}`);
|
|
96
|
+
out.log(` Run: ${c.cyan('npx wtf-p update')}\n`);
|
|
79
97
|
}
|
|
80
98
|
} catch {
|
|
81
99
|
// Ignore
|
package/bin/commands/update.js
CHANGED
|
@@ -25,8 +25,8 @@ async function runUpdate(options, pkg, installFunc) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
if (!targetDir) {
|
|
28
|
-
out.log(` ${c.yellow('No
|
|
29
|
-
out.log(`
|
|
28
|
+
out.log(` ${c.yellow('No WTF-P installation found.')}`);
|
|
29
|
+
out.log(` Install with: ${c.cyan('npx wtf-p')}\n`);
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -43,8 +43,9 @@ async function runUpdate(options, pkg, installFunc) {
|
|
|
43
43
|
|
|
44
44
|
out.log(`\n ${c.cyan('Updating...')}\n`);
|
|
45
45
|
|
|
46
|
-
// Run install with backup
|
|
47
|
-
|
|
46
|
+
// Run install with backup (convert isGlobal to runtime string)
|
|
47
|
+
const runtime = isGlobal ? 'claude' : 'claude-local';
|
|
48
|
+
await installFunc(runtime, true, options, pkg);
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
module.exports = runUpdate;
|
package/bin/install.js
CHANGED
|
@@ -22,7 +22,7 @@ const showList = require('./commands/list');
|
|
|
22
22
|
const args = process.argv.slice(2);
|
|
23
23
|
|
|
24
24
|
// Detect subcommand
|
|
25
|
-
const subcommands = ['status', 'doctor', 'update'];
|
|
25
|
+
const subcommands = ['status', 'doctor', 'update', 'uninstall'];
|
|
26
26
|
const subcommand = args.find(arg => !arg.startsWith('-') && subcommands.includes(arg));
|
|
27
27
|
const subcommandIndex = args.indexOf(subcommand);
|
|
28
28
|
if (subcommandIndex !== -1) {
|
|
@@ -32,6 +32,9 @@ if (subcommandIndex !== -1) {
|
|
|
32
32
|
// Parse flags
|
|
33
33
|
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
34
34
|
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
35
|
+
const hasGemini = args.includes('--gemini');
|
|
36
|
+
const hasOpenCode = args.includes('--opencode');
|
|
37
|
+
const hasAll = args.includes('--all');
|
|
35
38
|
const hasForce = args.includes('--force') || args.includes('-f');
|
|
36
39
|
const hasBackupAll = args.includes('--backup-all') || args.includes('-b');
|
|
37
40
|
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
@@ -90,7 +93,7 @@ ${out.colors.magenta('╚███╔███╔╝ ██║ ██║
|
|
|
90
93
|
${out.colors.magenta(' ╚══╝╚══╝ ╚═╝ ╚═╝ ╚═╝')}
|
|
91
94
|
|
|
92
95
|
${out.colors.cyan('Write The F***ing Paper')} ${out.colors.dim(`v${pkg.version}`)}
|
|
93
|
-
|
|
96
|
+
Academic writing commands for your AI coding assistant.
|
|
94
97
|
`;
|
|
95
98
|
|
|
96
99
|
// ============ Help Text ============
|
|
@@ -101,16 +104,20 @@ function showHelp() {
|
|
|
101
104
|
console.log(` ${c.yellow('Usage:')} npx wtf-p [command] [options]
|
|
102
105
|
|
|
103
106
|
${c.yellow('Commands:')}
|
|
104
|
-
${c.cyan('status')} Show
|
|
105
|
-
${c.cyan('doctor')}
|
|
106
|
-
${c.cyan('update')} Update
|
|
107
|
+
${c.cyan('status')} Show installation status
|
|
108
|
+
${c.cyan('doctor')} Check for installation problems
|
|
109
|
+
${c.cyan('update')} Update to latest version
|
|
110
|
+
${c.cyan('uninstall')} Remove WTF-P
|
|
107
111
|
|
|
108
112
|
${c.yellow('Install Options:')}
|
|
109
|
-
${c.cyan('-g, --global')} Install
|
|
110
|
-
${c.cyan('-l, --local')} Install
|
|
111
|
-
${c.cyan('
|
|
112
|
-
${c.cyan('
|
|
113
|
-
${c.cyan('
|
|
113
|
+
${c.cyan('-g, --global')} Install to your home directory (recommended)
|
|
114
|
+
${c.cyan('-l, --local')} Install to current project only
|
|
115
|
+
${c.cyan('--gemini')} Install for Gemini CLI
|
|
116
|
+
${c.cyan('--opencode')} Install for OpenCode
|
|
117
|
+
${c.cyan('--all')} Install for all supported tools
|
|
118
|
+
${c.cyan('-c, --config-dir <path>')} Install to a custom directory
|
|
119
|
+
${c.cyan('-f, --force')} Overwrite existing files without asking
|
|
120
|
+
${c.cyan('-b, --backup-all')} Backup existing files before overwriting
|
|
114
121
|
${c.cyan('--only=<type>')} Install only: commands, workflows, or all
|
|
115
122
|
|
|
116
123
|
${c.yellow('Output Options:')}
|
|
@@ -121,18 +128,18 @@ function showHelp() {
|
|
|
121
128
|
${c.cyan('--verbose')} Show detailed progress
|
|
122
129
|
|
|
123
130
|
${c.yellow('Other:')}
|
|
124
|
-
${c.cyan('-v, --version')} Show version
|
|
125
|
-
${c.cyan('-h, --help')} Show this help
|
|
131
|
+
${c.cyan('-v, --version')} Show version
|
|
132
|
+
${c.cyan('-h, --help')} Show this help
|
|
126
133
|
|
|
127
134
|
${c.yellow('Examples:')}
|
|
128
|
-
${c.dim('#
|
|
135
|
+
${c.dim('# Install with interactive prompts')}
|
|
129
136
|
npx wtf-p
|
|
130
137
|
|
|
131
|
-
${c.dim('# Quick global install')}
|
|
138
|
+
${c.dim('# Quick global install (no prompts)')}
|
|
132
139
|
npx wtf-p --global --advanced
|
|
133
140
|
|
|
134
|
-
${c.yellow('After
|
|
135
|
-
|
|
141
|
+
${c.yellow('After installing:')}
|
|
142
|
+
Open your AI assistant and run ${c.cyan('/wtfp:help')} to see all commands.
|
|
136
143
|
`);
|
|
137
144
|
}
|
|
138
145
|
|
|
@@ -174,28 +181,61 @@ async function main() {
|
|
|
174
181
|
return;
|
|
175
182
|
}
|
|
176
183
|
|
|
184
|
+
if (subcommand === 'uninstall') {
|
|
185
|
+
// Delegate to uninstall script with remaining args
|
|
186
|
+
const { execFileSync } = require('child_process');
|
|
187
|
+
const uninstallArgs = process.argv.slice(2).filter(a => a !== 'uninstall');
|
|
188
|
+
try {
|
|
189
|
+
execFileSync(process.execPath, [path.join(__dirname, 'uninstall.js'), ...uninstallArgs], { stdio: 'inherit' });
|
|
190
|
+
} catch (e) {
|
|
191
|
+
process.exit(e.status || 1);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
177
196
|
// Default: Install
|
|
178
197
|
if (!hasQuiet) console.log(banner);
|
|
179
198
|
|
|
180
|
-
if (
|
|
181
|
-
|
|
199
|
+
if (hasAll) {
|
|
200
|
+
// Install for all supported runtimes
|
|
201
|
+
await install('claude', false, options, pkg);
|
|
202
|
+
await install('gemini', false, options, pkg);
|
|
203
|
+
await install('opencode', false, options, pkg);
|
|
204
|
+
} else if (hasGemini) {
|
|
205
|
+
await install('gemini', false, options, pkg);
|
|
206
|
+
} else if (hasOpenCode) {
|
|
207
|
+
await install('opencode', false, options, pkg);
|
|
208
|
+
} else if (hasGlobal) {
|
|
209
|
+
await install('claude', false, options, pkg);
|
|
182
210
|
} else if (hasLocal) {
|
|
183
|
-
await install(
|
|
211
|
+
await install('claude-local', false, options, pkg);
|
|
184
212
|
} else if (options.isInteractive) {
|
|
185
213
|
const rl = createRL();
|
|
186
|
-
out.log(` ${out.colors.yellow('
|
|
214
|
+
out.log(` ${out.colors.yellow('Which tool do you use for AI-assisted coding?')}
|
|
187
215
|
`);
|
|
188
|
-
out.log(` ${out.colors.cyan('1)')}
|
|
189
|
-
out.log(` ${out.colors.cyan('2)')}
|
|
216
|
+
out.log(` ${out.colors.cyan('1)')} Claude Code ${out.colors.dim('(~/.claude)')}`);
|
|
217
|
+
out.log(` ${out.colors.cyan('2)')} Gemini CLI ${out.colors.dim('(~/.config/gemini)')}`);
|
|
218
|
+
out.log(` ${out.colors.cyan('3)')} OpenCode ${out.colors.dim('(~/.opencode)')}`);
|
|
219
|
+
out.log(` ${out.colors.cyan('4)')} All of the above
|
|
190
220
|
`);
|
|
191
|
-
|
|
221
|
+
|
|
192
222
|
const answer = await prompt(rl, ` Choice ${out.colors.dim('[1]')}: `);
|
|
193
223
|
rl.close();
|
|
194
|
-
|
|
224
|
+
|
|
195
225
|
const choice = answer || '1';
|
|
196
|
-
|
|
226
|
+
if (choice === '4') {
|
|
227
|
+
await install('claude', false, options, pkg);
|
|
228
|
+
await install('gemini', false, options, pkg);
|
|
229
|
+
await install('opencode', false, options, pkg);
|
|
230
|
+
} else if (choice === '3') {
|
|
231
|
+
await install('opencode', false, options, pkg);
|
|
232
|
+
} else if (choice === '2') {
|
|
233
|
+
await install('gemini', false, options, pkg);
|
|
234
|
+
} else {
|
|
235
|
+
await install('claude', false, options, pkg);
|
|
236
|
+
}
|
|
197
237
|
} else {
|
|
198
|
-
await install(
|
|
238
|
+
await install('claude', false, options, pkg);
|
|
199
239
|
}
|
|
200
240
|
}
|
|
201
241
|
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checkpoint - Save and restore paper state bundles
|
|
3
|
+
*
|
|
4
|
+
* Captures complete .planning/ state as a git-tagged checkpoint
|
|
5
|
+
* that can be restored in a new session for full context recovery.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { execSync } = require('child_process');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create a checkpoint of current paper state
|
|
14
|
+
* @param {Object} options
|
|
15
|
+
* @param {string} options.planningDir - Path to .planning/
|
|
16
|
+
* @param {string} options.label - Checkpoint label (e.g., "pre-discussion", "draft-1")
|
|
17
|
+
* @param {string} [options.note] - Optional note about checkpoint context
|
|
18
|
+
* @returns {Object} - { tag, files, timestamp }
|
|
19
|
+
*/
|
|
20
|
+
function createCheckpoint({ planningDir, label, note }) {
|
|
21
|
+
if (!fs.existsSync(planningDir)) {
|
|
22
|
+
throw new Error('.planning/ directory not found');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
26
|
+
const tag = `wtfp-checkpoint/${label}-${timestamp}`;
|
|
27
|
+
|
|
28
|
+
// Collect all state files
|
|
29
|
+
const files = collectFiles(planningDir);
|
|
30
|
+
|
|
31
|
+
// Stage all planning files
|
|
32
|
+
try {
|
|
33
|
+
execSync(`git add ${planningDir}`, { stdio: 'pipe' });
|
|
34
|
+
} catch (e) {
|
|
35
|
+
// May fail if no changes — that's OK
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check if there are staged changes
|
|
39
|
+
const status = execSync('git status --porcelain', { encoding: 'utf8' });
|
|
40
|
+
const hasChanges = status.trim().length > 0;
|
|
41
|
+
|
|
42
|
+
if (hasChanges) {
|
|
43
|
+
const commitMsg = `checkpoint: ${label}${note ? `\n\n${note}` : ''}`;
|
|
44
|
+
execSync(`git commit -m "${commitMsg.replace(/"/g, '\\"')}"`, { stdio: 'pipe' });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Create annotated tag
|
|
48
|
+
const tagMsg = [
|
|
49
|
+
`WTF-P Checkpoint: ${label}`,
|
|
50
|
+
`Timestamp: ${timestamp}`,
|
|
51
|
+
`Files: ${files.length}`,
|
|
52
|
+
note ? `Note: ${note}` : '',
|
|
53
|
+
].filter(Boolean).join('\n');
|
|
54
|
+
|
|
55
|
+
execSync(`git tag -a "${tag}" -m "${tagMsg.replace(/"/g, '\\"')}"`, { stdio: 'pipe' });
|
|
56
|
+
|
|
57
|
+
return { tag, files: files.length, timestamp };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Restore a checkpoint by tag name
|
|
62
|
+
* @param {string} tag - Git tag to restore from
|
|
63
|
+
* @returns {Object} - { restored, files }
|
|
64
|
+
*/
|
|
65
|
+
function restoreCheckpoint(tag) {
|
|
66
|
+
// Verify tag exists
|
|
67
|
+
try {
|
|
68
|
+
execSync(`git tag -l "${tag}"`, { encoding: 'utf8', stdio: 'pipe' });
|
|
69
|
+
} catch (e) {
|
|
70
|
+
throw new Error(`Checkpoint tag not found: ${tag}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Restore .planning/ from the tagged commit
|
|
74
|
+
try {
|
|
75
|
+
execSync(`git checkout "${tag}" -- .planning/`, { stdio: 'pipe' });
|
|
76
|
+
return { restored: true, tag };
|
|
77
|
+
} catch (e) {
|
|
78
|
+
throw new Error(`Failed to restore checkpoint: ${e.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* List available checkpoints
|
|
84
|
+
* @returns {Array} - List of { tag, date, label, message }
|
|
85
|
+
*/
|
|
86
|
+
function listCheckpoints() {
|
|
87
|
+
try {
|
|
88
|
+
const tags = execSync('git tag -l "wtfp-checkpoint/*" --sort=-creatordate', {
|
|
89
|
+
encoding: 'utf8',
|
|
90
|
+
stdio: 'pipe',
|
|
91
|
+
}).trim();
|
|
92
|
+
|
|
93
|
+
if (!tags) return [];
|
|
94
|
+
|
|
95
|
+
return tags.split('\n').map(tag => {
|
|
96
|
+
let message = '';
|
|
97
|
+
try {
|
|
98
|
+
message = execSync(`git tag -l "${tag}" -n1`, {
|
|
99
|
+
encoding: 'utf8',
|
|
100
|
+
stdio: 'pipe',
|
|
101
|
+
}).trim();
|
|
102
|
+
} catch (e) { /* ignore */ }
|
|
103
|
+
|
|
104
|
+
const parts = tag.replace('wtfp-checkpoint/', '').split('-');
|
|
105
|
+
const label = parts.slice(0, -6).join('-'); // Remove timestamp portion
|
|
106
|
+
|
|
107
|
+
return { tag, label, message };
|
|
108
|
+
});
|
|
109
|
+
} catch (e) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Generate a handoff document for session continuity
|
|
116
|
+
* @param {string} planningDir - Path to .planning/
|
|
117
|
+
* @returns {string} - Markdown content for HANDOFF.md
|
|
118
|
+
*/
|
|
119
|
+
function generateHandoff(planningDir) {
|
|
120
|
+
const statePath = path.join(planningDir, 'STATE.md');
|
|
121
|
+
const configPath = path.join(planningDir, 'config.json');
|
|
122
|
+
|
|
123
|
+
const state = fs.existsSync(statePath) ? fs.readFileSync(statePath, 'utf8') : '';
|
|
124
|
+
let config = {};
|
|
125
|
+
try {
|
|
126
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
127
|
+
} catch (e) { /* ignore */ }
|
|
128
|
+
|
|
129
|
+
// Extract position from STATE.md
|
|
130
|
+
const positionMatch = state.match(/Section:\s*(\d+)\s*of\s*(\d+)/i);
|
|
131
|
+
const wordsMatch = state.match(/Words?:\s*(\d+)\s*\/\s*(\d+)/i);
|
|
132
|
+
|
|
133
|
+
const sections = [];
|
|
134
|
+
sections.push('# Session Handoff');
|
|
135
|
+
sections.push(`\nGenerated: ${new Date().toISOString()}`);
|
|
136
|
+
sections.push(`\n## Position`);
|
|
137
|
+
|
|
138
|
+
if (positionMatch) {
|
|
139
|
+
sections.push(`- Section: ${positionMatch[1]} of ${positionMatch[2]}`);
|
|
140
|
+
}
|
|
141
|
+
if (wordsMatch) {
|
|
142
|
+
sections.push(`- Words: ${wordsMatch[1]} / ${wordsMatch[2]}`);
|
|
143
|
+
}
|
|
144
|
+
if (config.model_profile) {
|
|
145
|
+
sections.push(`- Profile: ${config.model_profile}`);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
sections.push(`\n## Resume With`);
|
|
149
|
+
sections.push('```');
|
|
150
|
+
sections.push('/wtfp:resume-writing');
|
|
151
|
+
sections.push('```');
|
|
152
|
+
|
|
153
|
+
return sections.join('\n');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// --- Helpers ---
|
|
157
|
+
|
|
158
|
+
function collectFiles(dir) {
|
|
159
|
+
const files = [];
|
|
160
|
+
|
|
161
|
+
function walk(d) {
|
|
162
|
+
if (!fs.existsSync(d)) return;
|
|
163
|
+
const entries = fs.readdirSync(d, { withFileTypes: true });
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
const full = path.join(d, entry.name);
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
walk(full);
|
|
168
|
+
} else {
|
|
169
|
+
files.push(full);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
walk(dir);
|
|
175
|
+
return files;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = {
|
|
179
|
+
createCheckpoint,
|
|
180
|
+
restoreCheckpoint,
|
|
181
|
+
listCheckpoints,
|
|
182
|
+
generateHandoff,
|
|
183
|
+
};
|