wtf-p 0.1.0 → 0.3.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 (110) hide show
  1. package/README.md +215 -230
  2. package/bin/commands/doctor.js +100 -0
  3. package/bin/commands/install-logic.js +235 -0
  4. package/bin/commands/list.js +88 -0
  5. package/bin/commands/status.js +85 -0
  6. package/bin/commands/update.js +50 -0
  7. package/bin/install.js +157 -309
  8. package/bin/lib/manifest.js +41 -0
  9. package/bin/lib/utils.js +338 -0
  10. package/bin/lib/wcn-compiler.js +226 -0
  11. package/bin/uninstall.js +202 -206
  12. package/core/write-the-f-paper/references/github-contrib.md +216 -0
  13. package/core/write-the-f-paper/templates/posters/basic.md +34 -0
  14. package/core/write-the-f-paper/templates/slides/basic.md +31 -0
  15. package/package.json +14 -4
  16. package/vendors/claude/.claude-plugin/plugin.json +12 -0
  17. package/vendors/claude/commands/wtfp/contribute.md +278 -0
  18. package/vendors/claude/commands/wtfp/create-poster.md +42 -0
  19. package/vendors/claude/commands/wtfp/create-slides.md +41 -0
  20. package/{commands → vendors/claude/commands}/wtfp/help.md +28 -0
  21. package/vendors/claude/commands/wtfp/report-bug.md +131 -0
  22. package/vendors/claude/commands/wtfp/request-feature.md +160 -0
  23. package/vendors/claude/skills/wtfp/echarts/SKILL.md +47 -0
  24. package/vendors/claude/skills/wtfp/marp/SKILL.md +49 -0
  25. /package/{write-the-f-paper → core/write-the-f-paper}/references/checkpoints.md +0 -0
  26. /package/{write-the-f-paper → core/write-the-f-paper}/references/continuation-format.md +0 -0
  27. /package/{write-the-f-paper → core/write-the-f-paper}/references/git-integration.md +0 -0
  28. /package/{write-the-f-paper → core/write-the-f-paper}/references/imrad-structure.md +0 -0
  29. /package/{write-the-f-paper → core/write-the-f-paper}/references/length-estimation.md +0 -0
  30. /package/{write-the-f-paper → core/write-the-f-paper}/references/plan-format.md +0 -0
  31. /package/{write-the-f-paper → core/write-the-f-paper}/references/principles.md +0 -0
  32. /package/{write-the-f-paper → core/write-the-f-paper}/references/questioning.md +0 -0
  33. /package/{write-the-f-paper → core/write-the-f-paper}/references/research-pitfalls.md +0 -0
  34. /package/{write-the-f-paper → core/write-the-f-paper}/templates/config.json +0 -0
  35. /package/{write-the-f-paper → core/write-the-f-paper}/templates/context.md +0 -0
  36. /package/{write-the-f-paper → core/write-the-f-paper}/templates/continue-here.md +0 -0
  37. /package/{write-the-f-paper → core/write-the-f-paper}/templates/data-inventory.md +0 -0
  38. /package/{write-the-f-paper → core/write-the-f-paper}/templates/discovery.md +0 -0
  39. /package/{write-the-f-paper → core/write-the-f-paper}/templates/issues.md +0 -0
  40. /package/{write-the-f-paper → core/write-the-f-paper}/templates/manuscript.md +0 -0
  41. /package/{write-the-f-paper → core/write-the-f-paper}/templates/narrative-arc.md +0 -0
  42. /package/{write-the-f-paper → core/write-the-f-paper}/templates/outline.md +0 -0
  43. /package/{write-the-f-paper → core/write-the-f-paper}/templates/phase-prompt.md +0 -0
  44. /package/{write-the-f-paper → core/write-the-f-paper}/templates/prior-drafts.md +0 -0
  45. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/argument-structure.md +0 -0
  46. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/bibliography.md +0 -0
  47. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/gaps.md +0 -0
  48. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/imrad.md +0 -0
  49. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/peer-review.md +0 -0
  50. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/style-guide.md +0 -0
  51. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project-context/tools.md +0 -0
  52. /package/{write-the-f-paper → core/write-the-f-paper}/templates/project.md +0 -0
  53. /package/{write-the-f-paper → core/write-the-f-paper}/templates/research.md +0 -0
  54. /package/{write-the-f-paper → core/write-the-f-paper}/templates/review-comments.md +0 -0
  55. /package/{write-the-f-paper → core/write-the-f-paper}/templates/revision-issues.md +0 -0
  56. /package/{write-the-f-paper → core/write-the-f-paper}/templates/roadmap.md +0 -0
  57. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-archive.md +0 -0
  58. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section-context.md +0 -0
  59. /package/{write-the-f-paper → core/write-the-f-paper}/templates/section.md +0 -0
  60. /package/{write-the-f-paper → core/write-the-f-paper}/templates/state.md +0 -0
  61. /package/{write-the-f-paper → core/write-the-f-paper}/templates/summary.md +0 -0
  62. /package/{write-the-f-paper → core/write-the-f-paper}/venues/acm-cs.yaml +0 -0
  63. /package/{write-the-f-paper → core/write-the-f-paper}/venues/arxiv-ml.yaml +0 -0
  64. /package/{write-the-f-paper → core/write-the-f-paper}/venues/ieee-cs.yaml +0 -0
  65. /package/{write-the-f-paper → core/write-the-f-paper}/venues/nature.yaml +0 -0
  66. /package/{write-the-f-paper → core/write-the-f-paper}/venues/thesis-chapter.yaml +0 -0
  67. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.md +0 -0
  68. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/create-outline.wcn.md +0 -0
  69. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.md +0 -0
  70. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/discuss-section.wcn.md +0 -0
  71. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.md +0 -0
  72. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/execute-section.wcn.md +0 -0
  73. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.md +0 -0
  74. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/list-assumptions.wcn.md +0 -0
  75. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.md +0 -0
  76. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/lit-review-phase.wcn.md +0 -0
  77. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.md +0 -0
  78. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/map-project.wcn.md +0 -0
  79. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.md +0 -0
  80. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/plan-section.wcn.md +0 -0
  81. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.md +0 -0
  82. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/research-gap.wcn.md +0 -0
  83. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.md +0 -0
  84. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/resume-paper.wcn.md +0 -0
  85. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.md +0 -0
  86. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/review-section.wcn.md +0 -0
  87. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.md +0 -0
  88. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/submit-draft.wcn.md +0 -0
  89. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.md +0 -0
  90. /package/{write-the-f-paper → core/write-the-f-paper}/workflows/transition.wcn.md +0 -0
  91. /package/{commands → vendors/claude/commands}/wtfp/analyze-bib.md +0 -0
  92. /package/{commands → vendors/claude/commands}/wtfp/check-refs.md +0 -0
  93. /package/{commands → vendors/claude/commands}/wtfp/create-outline.md +0 -0
  94. /package/{commands → vendors/claude/commands}/wtfp/discuss-section.md +0 -0
  95. /package/{commands → vendors/claude/commands}/wtfp/export-latex.md +0 -0
  96. /package/{commands → vendors/claude/commands}/wtfp/insert-section.md +0 -0
  97. /package/{commands → vendors/claude/commands}/wtfp/list-assumptions.md +0 -0
  98. /package/{commands → vendors/claude/commands}/wtfp/map-project.md +0 -0
  99. /package/{commands → vendors/claude/commands}/wtfp/new-paper.md +0 -0
  100. /package/{commands → vendors/claude/commands}/wtfp/pause-writing.md +0 -0
  101. /package/{commands → vendors/claude/commands}/wtfp/plan-revision.md +0 -0
  102. /package/{commands → vendors/claude/commands}/wtfp/plan-section.md +0 -0
  103. /package/{commands → vendors/claude/commands}/wtfp/polish-prose.md +0 -0
  104. /package/{commands → vendors/claude/commands}/wtfp/progress.md +0 -0
  105. /package/{commands → vendors/claude/commands}/wtfp/remove-section.md +0 -0
  106. /package/{commands → vendors/claude/commands}/wtfp/research-gap.md +0 -0
  107. /package/{commands → vendors/claude/commands}/wtfp/resume-writing.md +0 -0
  108. /package/{commands → vendors/claude/commands}/wtfp/review-section.md +0 -0
  109. /package/{commands → vendors/claude/commands}/wtfp/submit-milestone.md +0 -0
  110. /package/{commands → vendors/claude/commands}/wtfp/write-section.md +0 -0
@@ -0,0 +1,338 @@
1
+ /**
2
+ * Shared utilities for WTF-P CLI
3
+ */
4
+
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+ const os = require('os');
8
+ const readline = require('readline');
9
+
10
+ // Version tracking file name
11
+ const VERSION_FILE = '.wtfp-version';
12
+
13
+ // ============ Path Utilities ============
14
+
15
+ /**
16
+ * Expand ~ to home directory
17
+ */
18
+ function expandTilde(filePath) {
19
+ if (filePath && filePath.startsWith('~/')) {
20
+ return path.join(os.homedir(), filePath.slice(2));
21
+ }
22
+ return filePath;
23
+ }
24
+
25
+ /**
26
+ * Normalize path: expand tilde, resolve to absolute, resolve symlinks
27
+ */
28
+ function normalizePath(inputPath) {
29
+ if (!inputPath) return inputPath;
30
+
31
+ // Expand tilde
32
+ let normalized = expandTilde(inputPath);
33
+
34
+ // Resolve to absolute path
35
+ normalized = path.resolve(normalized);
36
+
37
+ // Resolve symlinks if path exists
38
+ if (fs.existsSync(normalized)) {
39
+ try {
40
+ normalized = fs.realpathSync(normalized);
41
+ } catch {
42
+ // Keep as-is if realpath fails (permissions, etc.)
43
+ }
44
+ }
45
+
46
+ return normalized;
47
+ }
48
+
49
+ /**
50
+ * Validate path for safety
51
+ */
52
+ function isValidPath(inputPath) {
53
+ if (!inputPath || typeof inputPath !== 'string') return false;
54
+
55
+ // Check for null bytes (security issue)
56
+ if (inputPath.includes('\0')) return false;
57
+
58
+ // Check path length (Windows ~260, but we use 1024 for safety)
59
+ if (inputPath.length > 1024) return false;
60
+
61
+ return true;
62
+ }
63
+
64
+ /**
65
+ * Get the configuration directory for a specific vendor
66
+ */
67
+ function getVendorDir(vendor, explicitConfigDir, isGlobal = true) {
68
+ if (!isGlobal) {
69
+ return path.join(process.cwd(), `.${vendor}`);
70
+ }
71
+
72
+ const envVar = `${vendor.toUpperCase()}_CONFIG_DIR`;
73
+ const configDir = normalizePath(explicitConfigDir) ||
74
+ normalizePath(process.env[envVar]);
75
+
76
+ return configDir || path.join(os.homedir(), `.${vendor}`);
77
+ }
78
+
79
+ /**
80
+ * Get the Claude config directory (respects CLAUDE_CONFIG_DIR env var)
81
+ */
82
+ function getClaudeDir(explicitConfigDir, isGlobal = true) {
83
+ return getVendorDir('claude', explicitConfigDir, isGlobal);
84
+ }
85
+
86
+ /**
87
+ * Get human-readable label for a path (with ~ for homedir)
88
+ */
89
+ function getPathLabel(fullPath, isGlobal = true) {
90
+ if (isGlobal) {
91
+ return fullPath.replace(os.homedir(), '~');
92
+ }
93
+ return fullPath.replace(process.cwd(), '.');
94
+ }
95
+
96
+ // ============ Version Tracking ============
97
+
98
+ /**
99
+ * Read installed WTF-P version from .wtfp-version file
100
+ */
101
+ function readInstalledVersion(claudeDir) {
102
+ const versionFile = path.join(claudeDir, VERSION_FILE);
103
+ if (!fs.existsSync(versionFile)) {
104
+ return null;
105
+ }
106
+ try {
107
+ const content = fs.readFileSync(versionFile, 'utf8').trim();
108
+ const data = JSON.parse(content);
109
+ return data;
110
+ } catch {
111
+ // Corrupt file or old format
112
+ return { version: 'unknown', corrupt: true };
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Write version tracking file
118
+ */
119
+ function writeVersionFile(claudeDir, version, installedFiles) {
120
+ if (!fs.existsSync(claudeDir)) {
121
+ fs.mkdirSync(claudeDir, { recursive: true });
122
+ }
123
+ const versionFile = path.join(claudeDir, VERSION_FILE);
124
+ const data = {
125
+ version,
126
+ installedAt: new Date().toISOString(),
127
+ files: installedFiles.length,
128
+ manifest: installedFiles.map(f => ({
129
+ path: f.dest.replace(claudeDir, '.'),
130
+ checksum: simpleChecksum(f.dest)
131
+ }))
132
+ };
133
+ fs.writeFileSync(versionFile, JSON.stringify(data, null, 2));
134
+ }
135
+
136
+ /**
137
+ * Simple checksum for file integrity (not cryptographic)
138
+ */
139
+ function simpleChecksum(filePath) {
140
+ if (!fs.existsSync(filePath)) return null;
141
+ try {
142
+ const content = fs.readFileSync(filePath, 'utf8');
143
+ let hash = 0;
144
+ for (let i = 0; i < content.length; i++) {
145
+ const char = content.charCodeAt(i);
146
+ hash = ((hash << 5) - hash) + char;
147
+ hash = hash & hash; // Convert to 32bit integer
148
+ }
149
+ return hash.toString(16);
150
+ } catch {
151
+ return null;
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Detect installation state
157
+ */
158
+ function detectInstallation(vendorDir) {
159
+ const result = {
160
+ hasCommands: false,
161
+ hasWorkflows: false,
162
+ hasSkills: false,
163
+ version: null,
164
+ partial: false,
165
+ corrupt: false,
166
+ commandFiles: [],
167
+ workflowFiles: [],
168
+ skillFiles: []
169
+ };
170
+
171
+ const commandsDir = path.join(vendorDir, 'commands', 'wtfp');
172
+ const workflowsDir = path.join(vendorDir, 'write-the-f-paper');
173
+ const skillsDir = path.join(vendorDir, 'skills', 'wtfp');
174
+
175
+ result.hasCommands = fs.existsSync(commandsDir);
176
+ result.hasWorkflows = fs.existsSync(workflowsDir);
177
+ result.hasSkills = fs.existsSync(skillsDir);
178
+
179
+ if (result.hasCommands) {
180
+ result.commandFiles = collectFiles(commandsDir);
181
+ }
182
+ if (result.hasWorkflows) {
183
+ result.workflowFiles = collectFiles(workflowsDir);
184
+ }
185
+ if (result.hasSkills) {
186
+ result.skillFiles = collectFiles(skillsDir);
187
+ }
188
+
189
+ // Read version info
190
+ const versionData = readInstalledVersion(vendorDir);
191
+ if (versionData) {
192
+ result.version = versionData.version;
193
+ result.corrupt = versionData.corrupt || false;
194
+
195
+ // Check for partial install
196
+ if (!versionData.corrupt) {
197
+ const expectedHasCommands = versionData.manifest?.some(f => f.path.includes('commands/wtfp'));
198
+ const expectedHasWorkflows = versionData.manifest?.some(f => f.path.includes('write-the-f-paper'));
199
+ const expectedHasSkills = versionData.manifest?.some(f => f.path.includes('skills/wtfp'));
200
+
201
+ if ((expectedHasCommands && !result.hasCommands) ||
202
+ (expectedHasWorkflows && !result.hasWorkflows) ||
203
+ (expectedHasSkills && !result.hasSkills)) {
204
+ result.partial = true;
205
+ }
206
+ }
207
+ } else if (result.hasCommands || result.hasWorkflows || result.hasSkills) {
208
+ // Files exist but no version file - legacy install
209
+ result.version = 'legacy';
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ /**
216
+ * Collect all files recursively in a directory
217
+ */
218
+ function collectFiles(dir, files = []) {
219
+ if (!fs.existsSync(dir)) return files;
220
+
221
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
222
+ for (const entry of entries) {
223
+ const fullPath = path.join(dir, entry.name);
224
+ if (entry.isDirectory()) {
225
+ collectFiles(fullPath, files);
226
+ } else {
227
+ files.push(fullPath);
228
+ }
229
+ }
230
+ return files;
231
+ }
232
+
233
+ // ============ Output Utilities ============
234
+
235
+ /**
236
+ * Create color functions (respects --no-color)
237
+ */
238
+ function createColors(useColors = true) {
239
+ if (!useColors) {
240
+ return {
241
+ cyan: s => s,
242
+ green: s => s,
243
+ yellow: s => s,
244
+ red: s => s,
245
+ magenta: s => s,
246
+ dim: s => s,
247
+ reset: ''
248
+ };
249
+ }
250
+
251
+ return {
252
+ cyan: s => `\x1b[36m${s}\x1b[0m`,
253
+ green: s => `\x1b[32m${s}\x1b[0m`,
254
+ yellow: s => `\x1b[33m${s}\x1b[0m`,
255
+ red: s => `\x1b[31m${s}\x1b[0m`,
256
+ magenta: s => `\x1b[35m${s}\x1b[0m`,
257
+ dim: s => `\x1b[2m${s}\x1b[0m`,
258
+ reset: '\x1b[0m'
259
+ };
260
+ }
261
+
262
+ /**
263
+ * Create output helpers (respects --quiet/--verbose)
264
+ */
265
+ function createOutput(options = {}) {
266
+ const { quiet = false, verbose = false, useColors = true } = options;
267
+ const c = createColors(useColors);
268
+
269
+ return {
270
+ colors: c,
271
+ log: (...args) => !quiet && console.log(...args),
272
+ verbose: (...args) => verbose && !quiet && console.log(c.dim(...args)),
273
+ error: (...args) => console.error(c.red('Error:'), ...args),
274
+ warn: (...args) => !quiet && console.log(c.yellow('Warning:'), ...args),
275
+ success: (...args) => !quiet && console.log(c.green('✓'), ...args),
276
+ info: (...args) => !quiet && console.log(c.cyan('ℹ'), ...args)
277
+ };
278
+ }
279
+
280
+ // ============ Prompt Utilities ============
281
+
282
+ /**
283
+ * Create readline interface
284
+ */
285
+ function createRL() {
286
+ return readline.createInterface({
287
+ input: process.stdin,
288
+ output: process.stdout
289
+ });
290
+ }
291
+
292
+ /**
293
+ * Prompt user with a question
294
+ */
295
+ function prompt(rl, question) {
296
+ return new Promise(resolve => {
297
+ rl.question(question, answer => resolve(answer.trim().toLowerCase()));
298
+ });
299
+ }
300
+
301
+ /**
302
+ * Generate backup path with timestamp
303
+ */
304
+ function getBackupPath(filePath) {
305
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
306
+ const dir = path.dirname(filePath);
307
+ const ext = path.extname(filePath);
308
+ const base = path.basename(filePath, ext);
309
+ return path.join(dir, `${base}.backup-${timestamp}${ext}`);
310
+ }
311
+
312
+ // ============ Exports ============
313
+
314
+ module.exports = {
315
+ // Path utilities
316
+ expandTilde,
317
+ normalizePath,
318
+ isValidPath,
319
+ getClaudeDir,
320
+ getPathLabel,
321
+
322
+ // Version tracking
323
+ VERSION_FILE,
324
+ readInstalledVersion,
325
+ writeVersionFile,
326
+ simpleChecksum,
327
+ detectInstallation,
328
+ collectFiles,
329
+
330
+ // Output utilities
331
+ createColors,
332
+ createOutput,
333
+
334
+ // Prompt utilities
335
+ createRL,
336
+ prompt,
337
+ getBackupPath
338
+ };
@@ -0,0 +1,226 @@
1
+ /**
2
+ * WCN Compiler (Workflow Compression Notation)
3
+ * A robust, state-machine based compiler for converting Verbose Markdown to Compressed WCN.
4
+ */
5
+
6
+ const FLUFF_PATTERNS = [
7
+ /This ensures[^.]*\./g,
8
+ /This is important[^.]*\./g,
9
+ /Note that[^.]*\./g,
10
+ /Keep in mind[^.]*\./g,
11
+ /Remember that[^.]*\./g,
12
+ /As mentioned[^.]*\./g,
13
+ /It is important to[^.]*\./g,
14
+ ];
15
+
16
+ class WCNCompiler {
17
+ constructor() {
18
+ this.lines = [];
19
+ this.output = [];
20
+ this.state = 'ROOT'; // ROOT, STEP, BASH
21
+ this.context = {};
22
+ }
23
+
24
+ compile(markdown) {
25
+ // 1. Pre-process: Remove fluff and normalize
26
+ let cleanMd = markdown;
27
+ FLUFF_PATTERNS.forEach(p => cleanMd = cleanMd.replace(p, ''));
28
+
29
+ this.lines = cleanMd.split('\n');
30
+ this.output = [];
31
+ this.state = 'ROOT';
32
+
33
+ // 2. Parse Line by Line
34
+ for (let i = 0; i < this.lines.length; i++) {
35
+ const line = this.lines[i].trim();
36
+ this.processLine(line, i);
37
+ }
38
+
39
+ // 3. Post-process: Clean up excessive newlines
40
+ return this.output.join('\n').replace(/\n{3,}/g, '\n\n').trim();
41
+ }
42
+
43
+ processLine(line, index) {
44
+ // Handle State Transitions
45
+ if (this.state === 'ROOT') {
46
+ if (line.startsWith('<step')) {
47
+ this.enterStep(line);
48
+ } else if (line.startsWith('<context>')) {
49
+ this.enterContext();
50
+ } else if (line.startsWith('**If') && line.endsWith(':**')) {
51
+ // Handle standalone conditionals (outside steps, rare but possible)
52
+ this.parseConditional(line, index);
53
+ } else if (line.startsWith('**Route')) {
54
+ this.parseRoute(line, index);
55
+ } else if (!line.startsWith('</')) {
56
+ // Pass through other content, but minimal
57
+ // We generally skip prose outside steps in WCN unless it's structural
58
+ if (line.startsWith('#') || line.startsWith('---')) {
59
+ this.output.push(line);
60
+ }
61
+ }
62
+ } else if (this.state === 'STEP') {
63
+ if (line.startsWith('</step>')) {
64
+ this.exitStep();
65
+ } else if (line.startsWith('```bash')) {
66
+ this.state = 'BASH';
67
+ } else if (line.startsWith('**If') && line.endsWith(':**')) {
68
+ this.parseConditional(line, index);
69
+ } else if (line.toLowerCase().startsWith('parse and internalize:')) {
70
+ this.parseParseBlock(index);
71
+ } else if (line.trim().startsWith('- ') || line.trim().startsWith('* ')) {
72
+ // Capture bullet points as instructions
73
+ this.output.push(line.trim());
74
+ } else if (line.trim().startsWith('**') && !line.includes('If')) {
75
+ // Capture bold instructions
76
+ this.output.push(line.trim());
77
+ }
78
+ } else if (this.state === 'BASH') {
79
+ if (line.startsWith('```')) {
80
+ this.state = 'STEP';
81
+ } else {
82
+ // Capture bash command (first line usually sufficient for WCN)
83
+ if (line && !line.startsWith('#')) {
84
+ this.output.push(`RUN: ${line}`);
85
+ // Skip rest of bash block for WCN brevity?
86
+ // Current logic captures all commands. Let's capture all.
87
+ }
88
+ }
89
+ }
90
+ }
91
+
92
+ enterStep(line) {
93
+ this.state = 'STEP';
94
+ // Parse attributes: name="foo" priority="bar"
95
+ const nameMatch = line.match(/name="([^"]+)"/);
96
+ const priorityMatch = line.match(/priority="([^"]+)"/);
97
+
98
+ const name = nameMatch ? nameMatch[1] : 'unknown';
99
+ const pAttr = priorityMatch ? ` p=${priorityMatch[1] === 'first' ? '1' : priorityMatch[1]}` : '';
100
+
101
+ this.output.push(`[step:${name}${pAttr}]`);
102
+ }
103
+
104
+ exitStep() {
105
+ this.output.push('[/step]');
106
+ this.state = 'ROOT';
107
+ }
108
+
109
+ enterContext() {
110
+ // Context is usually multiline, handled by looking ahead or buffering
111
+ // For simplicity, we'll scan ahead here since we have full lines access
112
+ // But sticking to line-by-line:
113
+ // Actually, Regex is better for Context blocks as they are simple <context>...</context>
114
+ // Let's defer this to a specialized Regex pass *before* line processing?
115
+ // Or just look ahead now.
116
+ // Let's skip for now and rely on a pre-pass replacement for Context, similar to current tool.
117
+ // Why? Because Context is global metadata, usually at top.
118
+ }
119
+
120
+ parseConditional(line, index) {
121
+ // **If mode="yolo":**
122
+ const condition = line.replace(/\*\*If\s+/, '').replace(/:\*\*/, '').trim().replace(/["']/g, '');
123
+
124
+ // Look ahead for the action
125
+ // Usually the next line or the line after
126
+ let action = '';
127
+ for (let i = 1; i < 5; i++) {
128
+ const next = this.lines[index + i];
129
+ if (next && next.trim()) {
130
+ action = next.trim();
131
+ break;
132
+ }
133
+ }
134
+
135
+ if (action) {
136
+ // Simplify action text
137
+ if (action.length > 50) action = action.substring(0, 47) + '...';
138
+ this.output.push(`IF ${condition} → ${action}`);
139
+ }
140
+ }
141
+
142
+ parseParseBlock(index) {
143
+ // Parse and internalize:
144
+ // - Item 1
145
+ // - Item 2
146
+ const fields = [];
147
+ for (let i = 1; i < 10; i++) {
148
+ const next = this.lines[index + i];
149
+ if (!next || (!next.trim().startsWith('-') && !next.trim().startsWith('*'))) break;
150
+
151
+ const field = next.replace(/^[-*]\s+/, '').trim().split(' ')[0].toLowerCase().replace(/\s+/g, '_');
152
+ fields.push(field);
153
+ }
154
+
155
+ if (fields.length > 0) {
156
+ this.output.push(`PARSE: ${fields.join(', ')}`);
157
+ }
158
+ }
159
+
160
+ parseRoute(line, index) {
161
+ // **Route A: Title**
162
+ const match = line.match(/\*\*Route\s+([A-Z]):\s*([^*]+)\*\*/);
163
+ if (!match) return;
164
+
165
+ // We need to buffer the route table
166
+ // This is hard to do line-by-line without buffering.
167
+ // Let's assume we handle Routes via Regex pre-pass like the original,
168
+ // OR we detect the start of a route block and switch state.
169
+ // Given the complexity, let's stick to the Regex for Routes/Modes/Context
170
+ // and use State Machine for the detailed STEP logic which is where the fragility usually is.
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Hybrid Compiler:
176
+ * 1. Uses Regex for high-level blocks (Context, Routes, Modes)
177
+ * 2. Uses State Machine for Steps (the most complex part)
178
+ */
179
+ function compile(markdown) {
180
+ let result = markdown;
181
+
182
+ // 1. Remove fluff
183
+ FLUFF_PATTERNS.forEach(p => result = result.replace(p, ''));
184
+
185
+ // 2. Compress Context (Regex is fine here)
186
+ result = result.replace(/<context>\s*([\s\S]*?)<\/context>/g, (match, content) => {
187
+ const files = content.match(/@[^\s\n]+/g) || [];
188
+ if (files.length === 0) return '';
189
+ const simplified = files.map(f => f.replace('@.planning/', ''));
190
+ return `@context{${simplified.join(',')}}`;
191
+ });
192
+
193
+ // 3. Compress Routes (Regex is fine here)
194
+ const routes = [];
195
+ result = result.replace(/\*\*Route\s+([A-Z]):\s*([^*]+)\*\*\s*([\s\S]*?)(?=\*\*Route|<step|$)/g, (match, id, title, content) => {
196
+ // Extract next step logic
197
+ const nextMatch = content.match(/\`\/wtfp:([^`]+)\`/);
198
+ const next = nextMatch ? `/wtfp:${nextMatch[1]}` : 'continue';
199
+ routes.push(` ${title.trim().toLowerCase().replace(/\s+/g, '_')} → "${title.trim()}" → ${next}`);
200
+ return '';
201
+ });
202
+ if (routes.length > 0) {
203
+ result = result + '\n\nROUTE{condition → output → next}:\n' + routes.join('\n');
204
+ }
205
+
206
+ // 4. Compress Modes (Regex is fine here)
207
+ const modes = [];
208
+ result = result.replace(/\*\*If\s+mode="([^"]+)":\*\*\s*\n((?:[-*]\s+[^\n]+\n?)+)/g, (match, mode, items) => {
209
+ const itemList = items.split('\n').filter(l => l.trim()).map(l => l.replace(/^[-*]\s+/, '').trim());
210
+ modes.push(` ${mode} | ${itemList[0] || ''}`);
211
+ return '';
212
+ });
213
+ if (modes.length > 0) {
214
+ result = 'MODE{name,action}:\n' + modes.join('\n') + '\n\n' + result;
215
+ }
216
+
217
+ // 5. Compress Steps using State Machine (The Core Logic)
218
+ const compiler = new WCNCompiler();
219
+ // We only run the compiler on the text that remains (which should be mostly steps)
220
+ // But wait, the compiler expects full text to find steps.
221
+ // Let's pass the text to the compiler, but the compiler only outputs Step blocks and structural elements.
222
+
223
+ return compiler.compile(result);
224
+ }
225
+
226
+ module.exports = { compile, WCNCompiler };