wtf-p 0.3.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.
Files changed (183) hide show
  1. package/README.md +146 -208
  2. package/bin/commands/doctor.js +88 -2
  3. package/bin/commands/install-logic.js +127 -36
  4. package/bin/commands/status.js +22 -4
  5. package/bin/commands/update.js +5 -4
  6. package/bin/install.js +66 -26
  7. package/bin/lib/analyze-impact.js +105 -0
  8. package/bin/lib/bib-format.js +161 -0
  9. package/bin/lib/bib-index.js +104 -0
  10. package/bin/lib/checkpoint.js +183 -0
  11. package/bin/lib/citation-fetcher.js +299 -0
  12. package/bin/lib/citation-ranker.js +133 -0
  13. package/bin/lib/context-primer.js +214 -0
  14. package/bin/lib/manifest.js +80 -2
  15. package/bin/lib/scholar-lookup.js +188 -0
  16. package/bin/lib/semantic-scholar.js +184 -0
  17. package/bin/lib/utils.js +22 -1
  18. package/bin/uninstall.js +270 -111
  19. package/core/write-the-f-paper/references/agent-model-matrix.md +62 -0
  20. package/core/write-the-f-paper/references/checkpoints.md +160 -9
  21. package/core/write-the-f-paper/references/context-fidelity.md +153 -0
  22. package/core/write-the-f-paper/references/deviation-rules.md +150 -0
  23. package/core/write-the-f-paper/references/git-integration.md +80 -0
  24. package/core/write-the-f-paper/references/orchestrator-pattern.md +167 -0
  25. package/core/write-the-f-paper/references/planning-config.md +135 -0
  26. package/core/write-the-f-paper/references/ui-brand.md +186 -0
  27. package/core/write-the-f-paper/templates/UAT.md +80 -0
  28. package/core/write-the-f-paper/templates/base-prefs.yaml +42 -0
  29. package/core/write-the-f-paper/templates/config.json +31 -5
  30. package/core/write-the-f-paper/workflows/create-outline.wcn.md +1 -1
  31. package/core/write-the-f-paper/workflows/execute-outline.md +163 -0
  32. package/core/write-the-f-paper/workflows/execute-outline.wcn.md +73 -0
  33. package/core/write-the-f-paper/workflows/execute-section.wcn.md +141 -169
  34. package/core/write-the-f-paper/workflows/plan-section.wcn.md +96 -103
  35. package/core/write-the-f-paper/workflows/research-gap.md +27 -3
  36. package/core/write-the-f-paper/workflows/research-gap.wcn.md +18 -1
  37. package/core/write-the-f-paper/workflows/verify-work.md +251 -0
  38. package/core/write-the-f-paper/workflows/verify-work.wcn.md +74 -0
  39. package/package.json +19 -7
  40. package/vendors/claude/.claude-plugin/plugin.json +2 -2
  41. package/vendors/claude/agents/wtfp/argument-verifier.md +189 -0
  42. package/vendors/claude/agents/wtfp/citation-expert.md +45 -0
  43. package/vendors/claude/agents/wtfp/citation-formatter.md +42 -0
  44. package/vendors/claude/agents/wtfp/coherence-checker.md +167 -0
  45. package/vendors/claude/agents/wtfp/outliner.md +259 -0
  46. package/vendors/claude/agents/wtfp/plan-checker.md +249 -0
  47. package/vendors/claude/agents/wtfp/prose-polisher.md +166 -0
  48. package/vendors/claude/agents/wtfp/research-synthesizer.md +215 -0
  49. package/vendors/claude/agents/wtfp/section-planner.md +364 -0
  50. package/vendors/claude/agents/wtfp/section-reviewer.md +182 -0
  51. package/vendors/claude/agents/wtfp/section-writer.md +312 -0
  52. package/vendors/claude/commands/wtfp/add-todo.md +108 -0
  53. package/vendors/claude/commands/wtfp/analyze-bib.md +60 -249
  54. package/vendors/claude/commands/wtfp/audit-milestone.md +286 -0
  55. package/vendors/claude/commands/wtfp/check-refs.md +60 -131
  56. package/vendors/claude/commands/wtfp/check-todos.md +152 -0
  57. package/vendors/claude/commands/wtfp/checkpoint.md +105 -0
  58. package/vendors/claude/commands/wtfp/contribute.md +1 -1
  59. package/vendors/claude/commands/wtfp/create-outline.md +109 -195
  60. package/vendors/claude/commands/wtfp/create-poster.md +1 -1
  61. package/vendors/claude/commands/wtfp/create-slides.md +1 -1
  62. package/vendors/claude/commands/wtfp/discuss-section.md +1 -1
  63. package/vendors/claude/commands/wtfp/execute-outline.md +257 -0
  64. package/vendors/claude/commands/wtfp/export-latex.md +1 -1
  65. package/vendors/claude/commands/wtfp/help.md +174 -280
  66. package/vendors/claude/commands/wtfp/insert-section.md +1 -1
  67. package/vendors/claude/commands/wtfp/list-assumptions.md +1 -1
  68. package/vendors/claude/commands/wtfp/map-project.md +35 -124
  69. package/vendors/claude/commands/wtfp/new-paper.md +88 -362
  70. package/vendors/claude/commands/wtfp/pause-writing.md +1 -1
  71. package/vendors/claude/commands/wtfp/plan-milestone-gaps.md +255 -0
  72. package/vendors/claude/commands/wtfp/plan-revision.md +1 -1
  73. package/vendors/claude/commands/wtfp/plan-section.md +165 -190
  74. package/vendors/claude/commands/wtfp/polish-prose.md +66 -118
  75. package/vendors/claude/commands/wtfp/progress.md +82 -254
  76. package/vendors/claude/commands/wtfp/quick.md +107 -0
  77. package/vendors/claude/commands/wtfp/remove-section.md +1 -1
  78. package/vendors/claude/commands/wtfp/report-bug.md +1 -1
  79. package/vendors/claude/commands/wtfp/request-feature.md +1 -1
  80. package/vendors/claude/commands/wtfp/research-gap.md +138 -151
  81. package/vendors/claude/commands/wtfp/resume-writing.md +1 -1
  82. package/vendors/claude/commands/wtfp/review-section.md +55 -333
  83. package/vendors/claude/commands/wtfp/settings.md +197 -0
  84. package/vendors/claude/commands/wtfp/submit-milestone.md +248 -99
  85. package/vendors/claude/commands/wtfp/update.md +112 -0
  86. package/vendors/claude/commands/wtfp/verify-work.md +254 -0
  87. package/vendors/claude/commands/wtfp/write-section.md +144 -180
  88. package/vendors/claude/mcp/research-server/package.json +13 -0
  89. package/vendors/claude/mcp/research-server/src/index.js +133 -0
  90. package/vendors/gemini/agents/wtfp/argument-verifier.md +184 -0
  91. package/vendors/gemini/agents/wtfp/citation-expert.md +41 -0
  92. package/vendors/gemini/agents/wtfp/citation-formatter.md +38 -0
  93. package/vendors/gemini/agents/wtfp/coherence-checker.md +162 -0
  94. package/vendors/gemini/agents/wtfp/outliner.md +252 -0
  95. package/vendors/gemini/agents/wtfp/plan-checker.md +244 -0
  96. package/vendors/gemini/agents/wtfp/prose-polisher.md +158 -0
  97. package/vendors/gemini/agents/wtfp/research-synthesizer.md +205 -0
  98. package/vendors/gemini/agents/wtfp/section-planner.md +355 -0
  99. package/vendors/gemini/agents/wtfp/section-reviewer.md +175 -0
  100. package/vendors/gemini/agents/wtfp/section-writer.md +304 -0
  101. package/vendors/gemini/commands/wtfp/add-todo.toml +102 -0
  102. package/vendors/gemini/commands/wtfp/analyze-bib.toml +126 -0
  103. package/vendors/gemini/commands/wtfp/audit-milestone.toml +278 -0
  104. package/vendors/gemini/commands/wtfp/check-refs.toml +123 -0
  105. package/vendors/gemini/commands/wtfp/check-todos.toml +145 -0
  106. package/vendors/gemini/commands/wtfp/checkpoint.toml +96 -0
  107. package/vendors/gemini/commands/wtfp/contribute.toml +269 -0
  108. package/vendors/gemini/commands/wtfp/create-outline.toml +180 -0
  109. package/vendors/gemini/commands/wtfp/create-poster.toml +36 -0
  110. package/vendors/gemini/commands/wtfp/create-slides.toml +35 -0
  111. package/vendors/gemini/commands/wtfp/discuss-section.toml +62 -0
  112. package/vendors/gemini/commands/wtfp/execute-outline.toml +247 -0
  113. package/vendors/gemini/commands/wtfp/export-latex.toml +209 -0
  114. package/vendors/gemini/commands/wtfp/help.toml +184 -0
  115. package/vendors/gemini/commands/wtfp/insert-section.toml +123 -0
  116. package/vendors/gemini/commands/wtfp/list-assumptions.toml +104 -0
  117. package/vendors/gemini/commands/wtfp/map-project.toml +99 -0
  118. package/vendors/gemini/commands/wtfp/new-paper.toml +160 -0
  119. package/vendors/gemini/commands/wtfp/pause-writing.toml +140 -0
  120. package/vendors/gemini/commands/wtfp/plan-milestone-gaps.toml +249 -0
  121. package/vendors/gemini/commands/wtfp/plan-revision.toml +171 -0
  122. package/vendors/gemini/commands/wtfp/plan-section.toml +212 -0
  123. package/vendors/gemini/commands/wtfp/polish-prose.toml +114 -0
  124. package/vendors/gemini/commands/wtfp/progress.toml +145 -0
  125. package/vendors/gemini/commands/wtfp/quick.toml +96 -0
  126. package/vendors/gemini/commands/wtfp/remove-section.toml +140 -0
  127. package/vendors/gemini/commands/wtfp/report-bug.toml +126 -0
  128. package/vendors/gemini/commands/wtfp/request-feature.toml +155 -0
  129. package/vendors/gemini/commands/wtfp/research-gap.toml +214 -0
  130. package/vendors/gemini/commands/wtfp/resume-writing.toml +136 -0
  131. package/vendors/gemini/commands/wtfp/review-section.toml +121 -0
  132. package/vendors/gemini/commands/wtfp/settings.toml +191 -0
  133. package/vendors/gemini/commands/wtfp/submit-milestone.toml +351 -0
  134. package/vendors/gemini/commands/wtfp/update.toml +107 -0
  135. package/vendors/gemini/commands/wtfp/verify-work.toml +245 -0
  136. package/vendors/gemini/commands/wtfp/write-section.toml +228 -0
  137. package/vendors/opencode/agents/wtfp/argument-verifier.md +184 -0
  138. package/vendors/opencode/agents/wtfp/citation-expert.md +41 -0
  139. package/vendors/opencode/agents/wtfp/citation-formatter.md +38 -0
  140. package/vendors/opencode/agents/wtfp/coherence-checker.md +162 -0
  141. package/vendors/opencode/agents/wtfp/outliner.md +252 -0
  142. package/vendors/opencode/agents/wtfp/plan-checker.md +244 -0
  143. package/vendors/opencode/agents/wtfp/prose-polisher.md +158 -0
  144. package/vendors/opencode/agents/wtfp/research-synthesizer.md +205 -0
  145. package/vendors/opencode/agents/wtfp/section-planner.md +355 -0
  146. package/vendors/opencode/agents/wtfp/section-reviewer.md +175 -0
  147. package/vendors/opencode/agents/wtfp/section-writer.md +304 -0
  148. package/vendors/opencode/commands/wtfp/add-todo.md +103 -0
  149. package/vendors/opencode/commands/wtfp/analyze-bib.md +127 -0
  150. package/vendors/opencode/commands/wtfp/audit-milestone.md +279 -0
  151. package/vendors/opencode/commands/wtfp/check-refs.md +124 -0
  152. package/vendors/opencode/commands/wtfp/check-todos.md +146 -0
  153. package/vendors/opencode/commands/wtfp/checkpoint.md +97 -0
  154. package/vendors/opencode/commands/wtfp/contribute.md +266 -0
  155. package/vendors/opencode/commands/wtfp/create-outline.md +181 -0
  156. package/vendors/opencode/commands/wtfp/create-poster.md +37 -0
  157. package/vendors/opencode/commands/wtfp/create-slides.md +36 -0
  158. package/vendors/opencode/commands/wtfp/discuss-section.md +63 -0
  159. package/vendors/opencode/commands/wtfp/execute-outline.md +248 -0
  160. package/vendors/opencode/commands/wtfp/export-latex.md +210 -0
  161. package/vendors/opencode/commands/wtfp/help.md +185 -0
  162. package/vendors/opencode/commands/wtfp/insert-section.md +124 -0
  163. package/vendors/opencode/commands/wtfp/list-assumptions.md +105 -0
  164. package/vendors/opencode/commands/wtfp/map-project.md +100 -0
  165. package/vendors/opencode/commands/wtfp/new-paper.md +161 -0
  166. package/vendors/opencode/commands/wtfp/pause-writing.md +141 -0
  167. package/vendors/opencode/commands/wtfp/plan-milestone-gaps.md +250 -0
  168. package/vendors/opencode/commands/wtfp/plan-revision.md +174 -0
  169. package/vendors/opencode/commands/wtfp/plan-section.md +214 -0
  170. package/vendors/opencode/commands/wtfp/polish-prose.md +115 -0
  171. package/vendors/opencode/commands/wtfp/progress.md +146 -0
  172. package/vendors/opencode/commands/wtfp/quick.md +97 -0
  173. package/vendors/opencode/commands/wtfp/remove-section.md +141 -0
  174. package/vendors/opencode/commands/wtfp/report-bug.md +127 -0
  175. package/vendors/opencode/commands/wtfp/request-feature.md +156 -0
  176. package/vendors/opencode/commands/wtfp/research-gap.md +215 -0
  177. package/vendors/opencode/commands/wtfp/resume-writing.md +137 -0
  178. package/vendors/opencode/commands/wtfp/review-section.md +123 -0
  179. package/vendors/opencode/commands/wtfp/settings.md +192 -0
  180. package/vendors/opencode/commands/wtfp/submit-milestone.md +352 -0
  181. package/vendors/opencode/commands/wtfp/update.md +108 -0
  182. package/vendors/opencode/commands/wtfp/verify-work.md +246 -0
  183. package/vendors/opencode/commands/wtfp/write-section.md +228 -0
@@ -0,0 +1,161 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * WTF-P BibTeX Formatter
5
+ * Standardizes BibTeX entries to the project's strict template.
6
+ * Handles missing fields and assigns provenance metadata.
7
+ */
8
+
9
+ // --- Parsing Logic ---
10
+
11
+ function parseField(text, field) {
12
+ // Regex to find field="value" or field={value}
13
+ const regex = new RegExp(`\\b${field}\\s*=\\s*[{"']([\\s\\S]*?)[}"']\\s*[,}]`, 'i');
14
+ const match = text.match(regex);
15
+ return match ? match[1].trim() : null;
16
+ }
17
+
18
+ function parseEntryType(text) {
19
+ const match = text.match(/@(\w+)\s*{/);
20
+ return match ? match[1].toLowerCase() : 'misc';
21
+ }
22
+
23
+ function parseKey(text) {
24
+ const match = text.match(/@\w+\s*{\s*([^,]+),/);
25
+ return match ? match[1].trim() : 'unknown_key';
26
+ }
27
+
28
+ function parse(rawEntry) {
29
+ const entryType = parseEntryType(rawEntry);
30
+ const key = parseKey(rawEntry);
31
+
32
+ return {
33
+ key,
34
+ entryType,
35
+ author: parseField(rawEntry, 'author'),
36
+ title: parseField(rawEntry, 'title'),
37
+ booktitle: parseField(rawEntry, 'booktitle') || parseField(rawEntry, 'journal'),
38
+ year: parseField(rawEntry, 'year'),
39
+ month: parseField(rawEntry, 'month'),
40
+ abstract: parseField(rawEntry, 'abstract'),
41
+ publisher: parseField(rawEntry, 'publisher'),
42
+ volume: parseField(rawEntry, 'volume'),
43
+ number: parseField(rawEntry, 'number'),
44
+ pages: parseField(rawEntry, 'pages'),
45
+ doi: parseField(rawEntry, 'doi'),
46
+ url: parseField(rawEntry, 'url'),
47
+ keywords: parseField(rawEntry, 'keywords'),
48
+ abbr: parseField(rawEntry, 'abbr'),
49
+ bibtex_show: parseField(rawEntry, 'bibtex_show'),
50
+ selected: parseField(rawEntry, 'selected'),
51
+ projects: parseField(rawEntry, 'projects')
52
+ };
53
+ }
54
+
55
+ // --- Formatting Logic ---
56
+
57
+ function format(data, provenance = {}) {
58
+ // Determine Status
59
+ let status = provenance.wtfp_status || 'official';
60
+ const missingFields = [];
61
+
62
+ if (!data.doi) {
63
+ status = 'incomplete';
64
+ missingFields.push('doi');
65
+ }
66
+ if (!data.author || data.author === '{MISSING_AUTHOR}') {
67
+ status = 'incomplete';
68
+ missingFields.push('author');
69
+ }
70
+ if (!data.booktitle || data.booktitle === '{MISSING_VENUE}') {
71
+ missingFields.push('venue');
72
+ }
73
+ if (!data.abstract) {
74
+ if (status !== 'incomplete') status = 'partial';
75
+ missingFields.push('abstract');
76
+ }
77
+
78
+ // Use provided provenance or defaults
79
+ const wtfp_source = provenance.wtfp_source || '';
80
+ const wtfp_citations = provenance.wtfp_citations || '';
81
+ const wtfp_velocity = provenance.wtfp_velocity || '';
82
+ const wtfp_s2_id = provenance.wtfp_s2_id || '';
83
+ const wtfp_scholar_id = provenance.wtfp_scholar_id || '';
84
+ const wtfp_fetched = provenance.wtfp_fetched || new Date().toISOString().split('T')[0];
85
+
86
+ const entryType = data.entryType === 'inproceedings' ? 'conference' : (data.entryType === 'article' ? 'journal' : data.entryType || 'misc');
87
+
88
+ // Clean fields
89
+ const clean = (val, fallback = "") => val || fallback;
90
+
91
+ return `@${entryType}{${data.key || 'unknown'},
92
+ abbr="${clean(data.abbr)}",
93
+ entry_type="${entryType}",
94
+ author="${clean(data.author, "{MISSING_AUTHOR}")}",
95
+ abstract="${clean(data.abstract)}",
96
+ booktitle="${clean(data.booktitle, "{MISSING_VENUE}")}",
97
+ title="${clean(data.title, "{MISSING_TITLE}")}",
98
+ year="${clean(data.year, "{????}")}",
99
+ month="${clean(data.month)}",
100
+ publisher="${clean(data.publisher)}",
101
+ volume="${clean(data.volume)}",
102
+ number="${clean(data.number)}",
103
+ pages="${clean(data.pages)}",
104
+ keywords="${clean(data.keywords)}",
105
+ doi="${clean(data.doi)}",
106
+ url="${clean(data.url, data.doi ? `https://doi.org/${data.doi}` : "")}",
107
+ html="${clean(data.html, data.doi ? `https://doi.org/${data.doi}` : "")}",
108
+ pdf="${clean(data.pdf, "paper.pdf")}",
109
+ google_scholar_id="${clean(data.google_scholar_id)}",
110
+ additional_info="${clean(data.additional_info)}",
111
+ bibtex_show="${clean(data.bibtex_show, "true")}",
112
+ selected="${clean(data.selected, "false")}",
113
+ projects="${clean(data.projects)}",
114
+ wtfp_status="${status}",
115
+ wtfp_source="${wtfp_source}",
116
+ wtfp_citations="${wtfp_citations}",
117
+ wtfp_velocity="${wtfp_velocity}",
118
+ wtfp_s2_id="${wtfp_s2_id}",
119
+ wtfp_scholar_id="${wtfp_scholar_id}",
120
+ wtfp_fetched="${wtfp_fetched}",
121
+ wtfp_missing="${missingFields.join(',')}"
122
+ }`;
123
+ }
124
+
125
+ // --- CLI Handling ---
126
+
127
+ if (require.main === module) {
128
+ const MODE = process.argv[2];
129
+ const INPUT = process.argv[3];
130
+ const KEY_ARG = process.argv[4];
131
+
132
+ if (!MODE) {
133
+ console.error('Usage: node bib-format.js <raw_string|--file> <content|filepath> [key]');
134
+ process.exit(1);
135
+ }
136
+
137
+ let rawEntry = '';
138
+
139
+ if (MODE === '--file') {
140
+ if (!fs.existsSync(INPUT)) {
141
+ console.error(`File not found: ${INPUT}`);
142
+ process.exit(1);
143
+ }
144
+ const content = fs.readFileSync(INPUT, 'utf8');
145
+ const regex = new RegExp(`@\w+\s*{\s*${KEY_ARG}\s*,[\s\S]*?\n}`, 'm');
146
+ const match = content.match(regex);
147
+ if (match) {
148
+ rawEntry = match[0];
149
+ } else {
150
+ console.error(`Key ${KEY_ARG} not found in ${INPUT}`);
151
+ process.exit(1);
152
+ }
153
+ } else {
154
+ rawEntry = INPUT;
155
+ }
156
+
157
+ const parsed = parse(rawEntry);
158
+ console.log(format(parsed));
159
+ }
160
+
161
+ module.exports = { parse, format };
@@ -0,0 +1,104 @@
1
+ const fs = require('fs');
2
+
3
+ /**
4
+ * WTF-P Bibliography Indexer
5
+ * Indexes and retrieves BibTeX entries without loading the entire file into context.
6
+ */
7
+
8
+ const ENTRY_REGEX = /@(\w+)\s*\{\s*([^,]+),([^@]*)\}/g;
9
+
10
+ function parseEntries(text) {
11
+ const entries = [];
12
+ let match;
13
+ // Reset regex index just in case
14
+ ENTRY_REGEX.lastIndex = 0;
15
+
16
+ while ((match = ENTRY_REGEX.exec(text)) !== null) {
17
+ const fullText = match[0];
18
+ const type = match[1];
19
+ const key = match[2].trim();
20
+ const body = match[3];
21
+
22
+ // Extract title specifically for indexing
23
+ const titleMatch = body.match(/title\s*=\s*[{"'](.+?)[}"']/i);
24
+ const title = titleMatch ? titleMatch[1] : 'Unknown Title';
25
+
26
+ // Extract year
27
+ const yearMatch = body.match(/year\s*=\s*[{"']?(\d+)[}"']?/i);
28
+ const year = yearMatch ? yearMatch[1] : '????';
29
+
30
+ entries.push({ key, type, title, year, fullText: `@${type}{${key},${body}}` });
31
+ }
32
+ return entries;
33
+ }
34
+
35
+ function index(content) {
36
+ const entries = parseEntries(content);
37
+ return JSON.stringify(entries.map(e => ({ key: e.key, title: e.title, year: e.year })), null, 2);
38
+ }
39
+
40
+ function getEntry(content, key) {
41
+ const entries = parseEntries(content);
42
+ const entry = entries.find(e => e.key === key);
43
+ return entry ? entry.fullText : null;
44
+ }
45
+
46
+ function search(content, query) {
47
+ const entries = parseEntries(content);
48
+ const q = query.toLowerCase();
49
+ const results = entries.filter(e =>
50
+ e.title.toLowerCase().includes(q) ||
51
+ e.key.toLowerCase().includes(q) ||
52
+ e.fullText.toLowerCase().includes(q)
53
+ );
54
+ return JSON.stringify(results.map(e => ({ key: e.key, title: e.title, year: e.year })), null, 2);
55
+ }
56
+
57
+ // --- CLI Handling ---
58
+
59
+ if (require.main === module) {
60
+ const COMMAND = process.argv[2];
61
+ const BIB_FILE = process.argv[3];
62
+ const ARG = process.argv[4];
63
+
64
+ if (!COMMAND || !BIB_FILE) {
65
+ console.error('Usage: node bib-index.js <command> <bib_file> [arg]');
66
+ process.exit(1);
67
+ }
68
+
69
+ if (!fs.existsSync(BIB_FILE)) {
70
+ console.error(`Error: Bibliography file not found: ${BIB_FILE}`);
71
+ process.exit(1);
72
+ }
73
+
74
+ const content = fs.readFileSync(BIB_FILE, 'utf8');
75
+
76
+ if (COMMAND === 'index') {
77
+ console.log(index(content));
78
+ }
79
+ else if (COMMAND === 'get') {
80
+ if (!ARG) {
81
+ console.error('Error: Missing citation key');
82
+ process.exit(1);
83
+ }
84
+ const result = getEntry(content, ARG);
85
+ if (result) console.log(result);
86
+ else {
87
+ console.error(`Error: Entry '${ARG}' not found.`);
88
+ process.exit(1);
89
+ }
90
+ }
91
+ else if (COMMAND === 'search') {
92
+ if (!ARG) {
93
+ console.error('Error: Missing search query');
94
+ process.exit(1);
95
+ }
96
+ console.log(search(content, ARG));
97
+ }
98
+ else {
99
+ console.error(`Unknown command: ${COMMAND}`);
100
+ process.exit(1);
101
+ }
102
+ }
103
+
104
+ module.exports = { index, getEntry, search, parseEntries };
@@ -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
+ };