agent-method 1.5.3 → 1.5.6

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 (64) hide show
  1. package/README.md +197 -57
  2. package/bin/wwa.js +35 -9
  3. package/docs/internal/doc-tokens.yaml +452 -0
  4. package/docs/internal/feature-registry.yaml +13 -1
  5. package/lib/cli/casestudy.js +691 -0
  6. package/lib/cli/check.js +71 -71
  7. package/lib/cli/close.js +446 -0
  8. package/lib/cli/completion.js +639 -0
  9. package/lib/cli/digest.js +66 -0
  10. package/lib/cli/docs.js +207 -0
  11. package/lib/cli/helpers.js +49 -2
  12. package/lib/cli/implement.js +159 -0
  13. package/lib/cli/init.js +25 -6
  14. package/lib/cli/plan.js +128 -0
  15. package/lib/cli/refine.js +202 -202
  16. package/lib/cli/review.js +68 -0
  17. package/lib/cli/scan.js +28 -28
  18. package/lib/cli/status.js +61 -61
  19. package/lib/cli/upgrade.js +150 -147
  20. package/lib/init.js +478 -296
  21. package/package.json +12 -4
  22. package/templates/README.md +73 -25
  23. package/templates/entry-points/.cursorrules +143 -14
  24. package/templates/entry-points/AGENT.md +143 -14
  25. package/templates/entry-points/CLAUDE.md +143 -14
  26. package/templates/extensions/analytical-system.md +1 -1
  27. package/templates/extensions/code-project.md +1 -1
  28. package/templates/extensions/data-exploration.md +1 -1
  29. package/templates/full/.context/BASE.md +33 -0
  30. package/templates/full/.context/METHODOLOGY.md +62 -5
  31. package/templates/full/.cursorrules +128 -18
  32. package/templates/full/AGENT.md +128 -18
  33. package/templates/full/CLAUDE.md +128 -18
  34. package/templates/full/Management/DIGEST.md +23 -0
  35. package/templates/full/Management/STATUS.md +46 -0
  36. package/templates/full/PROJECT.md +34 -0
  37. package/templates/full/Reviews/INDEX.md +41 -0
  38. package/templates/full/Reviews/backlog.md +52 -0
  39. package/templates/full/Reviews/plan.md +43 -0
  40. package/templates/full/Reviews/project.md +41 -0
  41. package/templates/full/Reviews/requirements.md +42 -0
  42. package/templates/full/Reviews/roadmap.md +41 -0
  43. package/templates/full/Reviews/state.md +56 -0
  44. package/templates/full/SESSION-LOG.md +29 -0
  45. package/templates/full/SUMMARY.md +7 -4
  46. package/templates/full/agentWorkflows/INDEX.md +42 -0
  47. package/templates/full/agentWorkflows/observations.md +65 -0
  48. package/templates/full/agentWorkflows/patterns.md +68 -0
  49. package/templates/full/agentWorkflows/sessions.md +92 -0
  50. package/templates/full/intro/README.md +39 -0
  51. package/templates/starter/.context/BASE.md +35 -0
  52. package/templates/starter/.context/METHODOLOGY.md +59 -5
  53. package/templates/starter/.cursorrules +135 -13
  54. package/templates/starter/AGENT.md +135 -13
  55. package/templates/starter/CLAUDE.md +135 -13
  56. package/templates/starter/Management/DIGEST.md +23 -0
  57. package/templates/starter/Management/STATUS.md +46 -0
  58. package/templates/starter/PROJECT.md +34 -0
  59. package/templates/starter/Reviews/INDEX.md +75 -0
  60. package/templates/starter/SESSION-LOG.md +29 -0
  61. package/templates/starter/SUMMARY.md +27 -0
  62. package/templates/starter/agentWorkflows/INDEX.md +61 -0
  63. package/templates/starter/intro/README.md +37 -0
  64. package/templates/full/docs/index.md +0 -46
@@ -0,0 +1,691 @@
1
+ /** wwa casestudy — extract a structured case study from project methodology files. */
2
+
3
+ import { readFileSync, existsSync, mkdirSync } from "node:fs";
4
+ import { resolve, join, basename, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ import {
7
+ findEntryPoint,
8
+ findSessionLog,
9
+ safeWriteFile,
10
+ readMethodVersion,
11
+ packageRoot,
12
+ } from "./helpers.js";
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = dirname(__filename);
16
+
17
+ export function register(program) {
18
+ program
19
+ .command("casestudy [directory]")
20
+ .description(
21
+ "Extract a structured case study from SESSION-LOG.md and project files"
22
+ )
23
+ .option("-o, --output <path>", "Output file path")
24
+ .option("--name <name>", "Project name override")
25
+ .option("--json", "Output as JSON")
26
+ .action(async (directory, opts) => {
27
+ directory = directory || ".";
28
+ const d = resolve(directory);
29
+
30
+ // Gather all project data
31
+ const data = await gatherProjectData(d, opts.name);
32
+
33
+ if (data.errors.length > 0 && data.sessions.length === 0) {
34
+ console.error("Cannot generate case study:");
35
+ for (const e of data.errors) console.error(` - ${e}`);
36
+ process.exit(1);
37
+ }
38
+
39
+ if (opts.json) {
40
+ const output = opts.output;
41
+ const result = JSON.stringify(data, null, 2);
42
+ if (output) {
43
+ safeWriteFile(output, result);
44
+ console.log(`Case study data written to ${output}`);
45
+ } else {
46
+ console.log(result);
47
+ }
48
+ return;
49
+ }
50
+
51
+ const caseStudy = generateCaseStudy(data);
52
+
53
+ const outputPath =
54
+ opts.output || join(d, "case-studies", `${slugify(data.projectName)}.md`);
55
+
56
+ // Ensure output directory exists
57
+ const outputDir = resolve(outputPath, "..");
58
+ if (!existsSync(outputDir)) {
59
+ mkdirSync(outputDir, { recursive: true });
60
+ }
61
+
62
+ safeWriteFile(outputPath, caseStudy);
63
+ console.log(
64
+ `Case study written to ${outputPath} (${data.sessions.length} sessions)`
65
+ );
66
+
67
+ if (data.warnings.length > 0) {
68
+ console.log("\nWarnings:");
69
+ for (const w of data.warnings) console.log(` - ${w}`);
70
+ }
71
+ });
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // Data gathering
76
+ // ---------------------------------------------------------------------------
77
+
78
+ async function gatherProjectData(dir, nameOverride) {
79
+ const data = {
80
+ projectName: nameOverride || "Unknown Project",
81
+ projectType: "unknown",
82
+ lifecycleStart: "unknown",
83
+ lifecycleEnd: "unknown",
84
+ templateTier: "unknown",
85
+ extensions: "none",
86
+ entryPointFile: null,
87
+ integrationProfile: "unknown",
88
+ modelTier: "unknown",
89
+ methodVersion: null,
90
+ projectDescription: "",
91
+ studyGoals: "",
92
+ sessions: [],
93
+ decisions: [],
94
+ workflows: new Set(),
95
+ features: new Set(),
96
+ frictionPoints: [],
97
+ findings: [],
98
+ agentWorkflows: null,
99
+ tokenRegistry: null,
100
+ nameMappings: null,
101
+ docsMap: null,
102
+ errors: [],
103
+ warnings: [],
104
+ };
105
+
106
+ // --- Entry point ---
107
+ const entryPoint = findEntryPoint(dir);
108
+ if (entryPoint) {
109
+ data.entryPointFile = basename(entryPoint);
110
+ data.methodVersion = readMethodVersion(entryPoint);
111
+ const epContent = readFileSync(entryPoint, "utf-8");
112
+ const tierMatch = epContent.match(/tier:\s*(\S+)/);
113
+ if (tierMatch) data.templateTier = tierMatch[1];
114
+ const modeMatch = epContent.match(/mode:\s*(\S+)/);
115
+ if (modeMatch) data.integrationProfile = modeMatch[1];
116
+ } else {
117
+ data.warnings.push("No entry point found (CLAUDE.md / .cursorrules / AGENT.md)");
118
+ }
119
+
120
+ // --- PROJECT.md ---
121
+ const projectPath = join(dir, "PROJECT.md");
122
+ if (existsSync(projectPath)) {
123
+ const content = readFileSync(projectPath, "utf-8");
124
+ const titleMatch = content.match(/^#\s+(.+)/m);
125
+ if (titleMatch && !nameOverride) {
126
+ data.projectName = titleMatch[1].trim();
127
+ }
128
+ // Extract description (first paragraph after the title)
129
+ const descMatch = content.match(/^#[^\n]+\n+(.+?)(?:\n\n|\n##)/s);
130
+ if (descMatch) {
131
+ data.projectDescription = descMatch[1].trim();
132
+ }
133
+ } else {
134
+ data.warnings.push("No PROJECT.md found — project name and description may be incomplete");
135
+ }
136
+
137
+ // --- PROJECT-PROFILE.md ---
138
+ const profilePath = join(dir, "PROJECT-PROFILE.md");
139
+ if (existsSync(profilePath)) {
140
+ const content = readFileSync(profilePath, "utf-8");
141
+ const typeMatch = content.match(/project.?type[:\s|]+(\S+)/i);
142
+ if (typeMatch) data.projectType = typeMatch[1].toLowerCase();
143
+ const stageMatch = content.match(/lifecycle.?stage[:\s|]+(\S+)/i);
144
+ if (stageMatch) data.lifecycleStart = stageMatch[1].toLowerCase();
145
+ const extMatch = content.match(/extension[s]?[:\s|]+([^\n|]+)/i);
146
+ if (extMatch) data.extensions = extMatch[1].trim();
147
+ }
148
+
149
+ // --- STATE.md (decisions) ---
150
+ const statePath = join(dir, "STATE.md");
151
+ if (existsSync(statePath)) {
152
+ const content = readFileSync(statePath, "utf-8");
153
+ // Extract decisions from table rows
154
+ const decisionSection = content.match(
155
+ /##\s*Decisions?\s*\n([\s\S]*?)(?=\n##|\n$|$)/i
156
+ );
157
+ if (decisionSection) {
158
+ const rows = decisionSection[1].match(/\|[^|\n]+\|[^|\n]+\|/g) || [];
159
+ for (const row of rows) {
160
+ const cols = row
161
+ .split("|")
162
+ .map((c) => c.trim())
163
+ .filter((c) => c && !c.match(/^[-:]+$/));
164
+ if (cols.length >= 2 && !cols[0].toLowerCase().includes("date")) {
165
+ data.decisions.push({ date: cols[0], decision: cols.slice(1).join(" — ") });
166
+ }
167
+ }
168
+ }
169
+ }
170
+
171
+ // --- SESSION-LOG.md ---
172
+ const sessionLog = findSessionLog(dir);
173
+ if (sessionLog) {
174
+ const content = readFileSync(sessionLog, "utf-8");
175
+ data.sessions = parseSessionEntries(content);
176
+
177
+ // Extract project context header
178
+ const ctxMatch = content.match(
179
+ /## Project context\s*\n\s*\|[^\n]+\n\s*\|[-| ]+\n((?:\|[^\n]+\n)*)/
180
+ );
181
+ if (ctxMatch) {
182
+ for (const row of ctxMatch[1].trim().split("\n")) {
183
+ const cols = row
184
+ .split("|")
185
+ .map((c) => c.trim())
186
+ .filter((c) => c);
187
+ if (cols.length >= 2) {
188
+ const key = cols[0].toLowerCase();
189
+ if (key.includes("project name") && !nameOverride) {
190
+ data.projectName = cols[1];
191
+ }
192
+ if (key.includes("project type")) data.projectType = cols[1].toLowerCase();
193
+ if (key.includes("profile")) data.integrationProfile = cols[1];
194
+ if (key.includes("extension")) data.extensions = cols[1];
195
+ if (key.includes("model")) data.modelTier = cols[1];
196
+ }
197
+ }
198
+ }
199
+
200
+ // Aggregate workflows and features
201
+ for (const s of data.sessions) {
202
+ if (s.workflow) data.workflows.add(s.workflow);
203
+ if (s.features) {
204
+ for (const f of s.features.split(",").map((x) => x.trim())) {
205
+ if (f) data.features.add(f);
206
+ }
207
+ }
208
+ if (s.friction && s.friction.toLowerCase() !== "none") {
209
+ data.frictionPoints.push({ session: s.session, date: s.date, text: s.friction });
210
+ }
211
+ if (s.finding && s.finding.toLowerCase() !== "none") {
212
+ data.findings.push({ session: s.session, date: s.date, text: s.finding });
213
+ }
214
+ }
215
+ } else {
216
+ data.errors.push("No SESSION-LOG.md found — session data is required for case study extraction");
217
+ }
218
+
219
+ // --- agentWorkflows/INDEX.md ---
220
+ const wfPath = join(dir, "agentWorkflows", "INDEX.md");
221
+ if (existsSync(wfPath)) {
222
+ data.agentWorkflows = readFileSync(wfPath, "utf-8");
223
+ }
224
+
225
+ // --- SUMMARY.md ---
226
+ const summaryPath = join(dir, "SUMMARY.md");
227
+ if (existsSync(summaryPath)) {
228
+ const content = readFileSync(summaryPath, "utf-8");
229
+ // Count summary entries
230
+ const entryCount = (content.match(/^##\s+/gm) || []).length;
231
+ if (entryCount > 0) {
232
+ data.warnings.push(
233
+ `SUMMARY.md has ${entryCount} entries — cross-reference with session log for completeness`
234
+ );
235
+ }
236
+ }
237
+
238
+ // --- Token registry ---
239
+ // Priority: project .context/doc-tokens.yaml > package docs/internal/doc-tokens.yaml
240
+ const projectTokensPath = join(dir, ".context", "doc-tokens.yaml");
241
+ const packageTokensPath = join(packageRoot, "docs", "internal", "doc-tokens.yaml");
242
+ try {
243
+ const yaml = (await import("js-yaml")).default;
244
+ // Load project-specific tokens (populated by wwa close)
245
+ if (existsSync(projectTokensPath)) {
246
+ const raw = readFileSync(projectTokensPath, "utf-8");
247
+ const parsed = yaml.load(raw);
248
+ if (parsed && parsed.tokens) data.tokenRegistry = parsed.tokens;
249
+ }
250
+ // Load methodology name mappings from package (always available)
251
+ if (existsSync(packageTokensPath)) {
252
+ const raw = readFileSync(packageTokensPath, "utf-8");
253
+ const parsed = yaml.load(raw);
254
+ if (parsed && parsed.names) data.nameMappings = parsed.names;
255
+ // If no project tokens, fall back to package tokens
256
+ if (!data.tokenRegistry && parsed && parsed.tokens) data.tokenRegistry = parsed.tokens;
257
+ }
258
+ } catch (_) {
259
+ data.warnings.push("Could not parse doc-tokens.yaml — token registry omitted");
260
+ }
261
+
262
+ // --- DOCS-MAP.md (from project) ---
263
+ const docsMapPath = join(dir, ".context", "DOCS-MAP.md");
264
+ if (existsSync(docsMapPath)) {
265
+ const content = readFileSync(docsMapPath, "utf-8");
266
+ data.docsMap = parseDocsMapForCaseStudy(content);
267
+ }
268
+
269
+ // Convert sets to arrays for serialization
270
+ data.workflows = [...data.workflows];
271
+ data.features = [...data.features];
272
+ data.lifecycleEnd = data.lifecycleStart; // default; override if detectable
273
+
274
+ return data;
275
+ }
276
+
277
+ // ---------------------------------------------------------------------------
278
+ // Session log parsing (aligned with refine.js format)
279
+ // ---------------------------------------------------------------------------
280
+
281
+ function parseSessionEntries(content) {
282
+ const entries = [];
283
+ const entryPattern =
284
+ /###\s+S(\d+)\s*(?:\u2014|--)\s*(\S+)\s*(?:\u2014|--)\s*(.+?)$(.*?)(?=###\s+S\d+|$)/gms;
285
+ let m;
286
+ while ((m = entryPattern.exec(content)) !== null) {
287
+ const [, num, date, title, body] = m;
288
+ const entry = {
289
+ session: parseInt(num, 10),
290
+ date,
291
+ title: title.trim(),
292
+ };
293
+
294
+ for (const line of body.split("\n")) {
295
+ const trimmed = line.trim();
296
+ if (!trimmed || trimmed.startsWith("#")) continue;
297
+ // Parse pipe-delimited key:value pairs
298
+ const segments = trimmed.split("|").map((s) => s.trim());
299
+ for (const seg of segments) {
300
+ if (seg.includes(":")) {
301
+ const idx = seg.indexOf(":");
302
+ const key = seg.slice(0, idx).trim().toLowerCase();
303
+ const val = seg.slice(idx + 1).trim();
304
+ if (key && val) {
305
+ entry[key] = val;
306
+ }
307
+ }
308
+ }
309
+ }
310
+ entries.push(entry);
311
+ }
312
+ return entries;
313
+ }
314
+
315
+ // ---------------------------------------------------------------------------
316
+ // Case study generation
317
+ // ---------------------------------------------------------------------------
318
+
319
+ function generateCaseStudy(data) {
320
+ const lines = [];
321
+
322
+ // Section 1: Overview
323
+ lines.push(
324
+ `# Case Study: ${data.projectName}`,
325
+ "",
326
+ "## Overview",
327
+ "",
328
+ "| Field | Value |",
329
+ "|-------|-------|",
330
+ `| Project name | ${data.projectName} |`,
331
+ `| Project type | ${data.projectType} |`,
332
+ `| Lifecycle stage at start | ${data.lifecycleStart} |`,
333
+ `| Lifecycle stage at end | ${data.lifecycleEnd} |`,
334
+ `| Template tier | ${data.templateTier} |`,
335
+ `| Extensions applied | ${data.extensions} |`,
336
+ `| Entry point | ${data.entryPointFile || "unknown"} |`,
337
+ `| Integration profile | ${data.integrationProfile} |`,
338
+ `| Model tier | ${data.modelTier} |`,
339
+ `| Sessions documented | ${data.sessions.length} |`,
340
+ `| Method version | ${data.methodVersion || "unknown"} |`,
341
+ ""
342
+ );
343
+
344
+ if (data.projectDescription) {
345
+ lines.push("## Project description", "", data.projectDescription, "");
346
+ }
347
+
348
+ lines.push(
349
+ "## Study goals",
350
+ "",
351
+ "<!-- Fill in: what this case study specifically aims to validate -->",
352
+ ""
353
+ );
354
+
355
+ // Section 2: Setup
356
+ lines.push(
357
+ "---",
358
+ "",
359
+ "## Setup",
360
+ "",
361
+ "### Bootstrap experience",
362
+ "",
363
+ "| Step | Time | Friction | Notes |",
364
+ "|------|------|----------|-------|"
365
+ );
366
+
367
+ if (data.sessions.length > 0) {
368
+ const first = data.sessions[0];
369
+ lines.push(
370
+ `| First session (S${first.session}) | ${first.time || "unknown"} | ${first.friction || "none"} | ${first.title} |`
371
+ );
372
+ }
373
+
374
+ lines.push("");
375
+
376
+ // Section 3: Feature activation
377
+ lines.push(
378
+ "---",
379
+ "",
380
+ "## Feature activation",
381
+ "",
382
+ "### Features activated",
383
+ "",
384
+ "| Feature | Sessions | Evidence |",
385
+ "|---------|:--------:|----------|"
386
+ );
387
+
388
+ if (data.features.length > 0) {
389
+ for (const f of data.features.sort()) {
390
+ // Find which sessions activated this feature
391
+ const activeSessions = data.sessions
392
+ .filter((s) => s.features && s.features.includes(f))
393
+ .map((s) => `S${s.session}`)
394
+ .join(", ");
395
+ lines.push(`| ${f} | ${activeSessions} | Observed in session log |`);
396
+ }
397
+ } else {
398
+ lines.push("| (no features recorded) | -- | -- |");
399
+ }
400
+
401
+ lines.push("");
402
+
403
+ lines.push("### Workflow usage", "", "| Workflow | Sessions | Completed? |", "|---------|:--------:|:----------:|");
404
+ if (data.workflows.length > 0) {
405
+ for (const wf of data.workflows.sort()) {
406
+ const wfSessions = data.sessions
407
+ .filter((s) => s.workflow === wf)
408
+ .map((s) => `S${s.session}`)
409
+ .join(", ");
410
+ lines.push(`| ${wf} | ${wfSessions} | yes |`);
411
+ }
412
+ } else {
413
+ lines.push("| (no workflows recorded) | -- | -- |");
414
+ }
415
+ lines.push("");
416
+
417
+ // Section 4: Session log
418
+ lines.push("---", "", "## Session log", "");
419
+
420
+ for (const s of data.sessions) {
421
+ lines.push(
422
+ `### Session ${s.session}: ${s.title}`,
423
+ "",
424
+ "| Field | Value |",
425
+ "|-------|-------|",
426
+ `| Date | ${s.date} |`,
427
+ `| Model | ${s.model || "unknown"} |`,
428
+ `| Profile | ${s.profile || "unknown"} |`,
429
+ `| Effort | ${s.effort || "unknown"} |`,
430
+ `| Ambiguity | ${s.ambiguity || "unknown"} |`,
431
+ `| Context | ${s.context || "unknown"} |`,
432
+ `| Tokens | ${s.tokens || "unknown"} |`,
433
+ `| Time | ${s.time || "unknown"} |`,
434
+ `| Workflow | ${s.workflow || "unknown"} |`,
435
+ `| Features | ${s.features || "none"} |`,
436
+ `| Cascades | ${s.cascades || "unknown"} |`,
437
+ `| Decisions | ${s.decisions || "0"} |`,
438
+ `| Response | ${s.response || "unknown"} |`,
439
+ `| Friction | ${s.friction || "none"} |`,
440
+ `| Finding | ${s.finding || "none"} |`
441
+ );
442
+
443
+ // Include refinement delta fields if present
444
+ if (s.revisions || s.magnitude) {
445
+ lines.push(
446
+ `| Revisions | ${s.revisions || "0"} |`,
447
+ `| Magnitude | ${s.magnitude || "none"} |`,
448
+ `| Delta | ${s.delta || "n/a"} |`,
449
+ `| Survival | ${s.survival || "n/a"} |`
450
+ );
451
+ }
452
+
453
+ lines.push("");
454
+ }
455
+
456
+ // Section 5: Scoring rubric
457
+ lines.push(
458
+ "---",
459
+ "",
460
+ "## Methodology effectiveness scoring",
461
+ "",
462
+ "Rate each dimension 1-5 with specific evidence.",
463
+ "",
464
+ "| Dimension | Score | Evidence | Notes |",
465
+ "|-----------|:-----:|----------|-------|"
466
+ );
467
+
468
+ const dimensions = [
469
+ "Context quality",
470
+ "Decision preservation",
471
+ "Scope discipline",
472
+ "Cascade coverage",
473
+ "Audit completeness",
474
+ "Bootstrap speed",
475
+ "Lifecycle fit",
476
+ "Model adequacy",
477
+ ];
478
+
479
+ for (const dim of dimensions) {
480
+ lines.push(`| ${dim} | -- | | |`);
481
+ }
482
+ lines.push("| **Overall** | -- | | |", "");
483
+
484
+ // Section 6: Findings
485
+ lines.push("---", "", "## Findings", "", "### What worked", "");
486
+
487
+ if (data.findings.length > 0) {
488
+ lines.push("| # | Finding | Session | Evidence |", "|---|---------|---------|----------|");
489
+ let wn = 1;
490
+ for (const f of data.findings) {
491
+ lines.push(`| W${wn} | ${f.text} | S${f.session} (${f.date}) | Session log |`);
492
+ wn++;
493
+ }
494
+ } else {
495
+ lines.push("<!-- Fill in findings from session review -->");
496
+ }
497
+
498
+ lines.push("", "### What needs improvement", "");
499
+
500
+ if (data.frictionPoints.length > 0) {
501
+ lines.push(
502
+ "| # | Finding | Session | Severity | Suggested change |",
503
+ "|---|---------|---------|----------|-----------------|"
504
+ );
505
+ let fn = 1;
506
+ for (const f of data.frictionPoints) {
507
+ lines.push(
508
+ `| I${fn} | ${f.text} | S${f.session} (${f.date}) | -- | <!-- suggest fix --> |`
509
+ );
510
+ fn++;
511
+ }
512
+ } else {
513
+ lines.push("<!-- Fill in improvement findings from session review -->");
514
+ }
515
+
516
+ lines.push("", "### What's missing", "", "<!-- Fill in gaps discovered during the study -->", "");
517
+
518
+ // Section 7: Refinement recommendations
519
+ lines.push(
520
+ "---",
521
+ "",
522
+ "## Refinement recommendations",
523
+ "",
524
+ "| # | Finding ref | Target | Change type | Specific change | Validation |",
525
+ "|---|------------|--------|-------------|-----------------|------------|"
526
+ );
527
+
528
+ if (data.frictionPoints.length > 0) {
529
+ let rn = 1;
530
+ for (let i = 0; i < data.frictionPoints.length; i++) {
531
+ lines.push(
532
+ `| R${rn} | I${i + 1} | <!-- target --> | <!-- type --> | <!-- change --> | <!-- validation --> |`
533
+ );
534
+ rn++;
535
+ }
536
+ } else {
537
+ lines.push("| R1 | -- | -- | -- | -- | -- |");
538
+ }
539
+
540
+ lines.push("");
541
+
542
+ // Agent workflow annotations
543
+ if (data.agentWorkflows) {
544
+ lines.push(
545
+ "---",
546
+ "",
547
+ "## Agent workflow annotations",
548
+ "",
549
+ "Extracted from agentWorkflows/INDEX.md:",
550
+ "",
551
+ data.agentWorkflows,
552
+ ""
553
+ );
554
+ }
555
+
556
+ // Statistics summary
557
+ lines.push(
558
+ "---",
559
+ "",
560
+ "## Statistics",
561
+ "",
562
+ `- **Total sessions**: ${data.sessions.length}`,
563
+ `- **Workflows used**: ${data.workflows.join(", ") || "none recorded"}`,
564
+ `- **Features observed**: ${data.features.length}`,
565
+ `- **Decisions recorded**: ${data.decisions.length}`,
566
+ `- **Friction points**: ${data.frictionPoints.length}`,
567
+ `- **Findings**: ${data.findings.length}`,
568
+ ""
569
+ );
570
+
571
+ // Methodology reference data
572
+ if (data.tokenRegistry || data.nameMappings || data.docsMap) {
573
+ lines.push("---", "", "## Methodology reference data", "");
574
+ }
575
+
576
+ if (data.tokenRegistry) {
577
+ lines.push("### Token registry snapshot", "");
578
+ lines.push("| Token | Value |", "|-------|-------|");
579
+ const keySubset = [
580
+ "registry_version", "feature_count", "domain_count", "directive_count",
581
+ "workflow_count", "query_pattern_count", "starter_file_count", "full_file_count",
582
+ "max_file_lines", "cli_command", "npm_package", "pip_package",
583
+ "developer_cmd_count", "pipeline_cmd_count", "total_cmd_count",
584
+ ];
585
+ for (const key of keySubset) {
586
+ if (data.tokenRegistry[key] !== undefined) {
587
+ lines.push(`| ${key} | ${data.tokenRegistry[key]} |`);
588
+ }
589
+ }
590
+ lines.push("");
591
+ }
592
+
593
+ if (data.nameMappings) {
594
+ lines.push("### Name mappings", "");
595
+
596
+ if (data.nameMappings.project_types) {
597
+ lines.push("**Project types**", "", "| Canonical | CLI alias |", "|-----------|-----------|");
598
+ for (const t of data.nameMappings.project_types) {
599
+ lines.push(`| ${t.canonical} | ${t.cli_alias || "—"} |`);
600
+ }
601
+ lines.push("");
602
+ }
603
+
604
+ if (data.nameMappings.guided_workflows) {
605
+ lines.push("**Workflows**", "", "| ID | Name | Stages |", "|----|------|--------|");
606
+ for (const wf of data.nameMappings.guided_workflows) {
607
+ lines.push(`| ${wf.id} | ${wf.display} | ${wf.context || ""} |`);
608
+ }
609
+ lines.push("");
610
+ }
611
+
612
+ if (data.nameMappings.feature_domains) {
613
+ lines.push("**Feature domains**", "", "| ID | Domain | Range |", "|----|--------|-------|");
614
+ for (const d of data.nameMappings.feature_domains) {
615
+ lines.push(`| ${d.id} | ${d.canonical} | ${d.feature_range} |`);
616
+ }
617
+ lines.push("");
618
+ }
619
+
620
+ if (data.nameMappings.protocol_directives) {
621
+ lines.push("**Protocol directives**", "", "| ID | Name | Rule |", "|----|------|------|");
622
+ for (const p of data.nameMappings.protocol_directives) {
623
+ lines.push(`| ${p.id} | ${p.canonical} | ${p.short} |`);
624
+ }
625
+ lines.push("");
626
+ }
627
+ }
628
+
629
+ if (data.docsMap) {
630
+ lines.push("### Docs dependency map", "");
631
+ if (data.docsMap.inventory.length > 0) {
632
+ lines.push("**Docs inventory**", "", "| Path | Purpose | Status |", "|------|---------|--------|");
633
+ for (const item of data.docsMap.inventory) {
634
+ lines.push(`| ${item.path} | ${item.purpose} | ${item.status} |`);
635
+ }
636
+ lines.push("");
637
+ }
638
+ if (data.docsMap.mappings.length > 0) {
639
+ lines.push("**Component mappings**", "", "| Component | Documented in | Trigger |", "|-----------|--------------|---------|");
640
+ for (const m of data.docsMap.mappings) {
641
+ lines.push(`| ${m.component} | ${m.documentedIn} | ${m.trigger} |`);
642
+ }
643
+ lines.push("");
644
+ }
645
+ lines.push(`**Scaffolding rules**: ${data.docsMap.scaffolding.length} active`, "");
646
+ }
647
+
648
+ lines.push("---", "", "*Generated by `wwa casestudy` from project methodology files.*", "");
649
+
650
+ return lines.join("\n");
651
+ }
652
+
653
+ // ---------------------------------------------------------------------------
654
+ // Utilities
655
+ // ---------------------------------------------------------------------------
656
+
657
+ function parseDocsMapForCaseStudy(content) {
658
+ const result = { inventory: [], mappings: [], scaffolding: [] };
659
+
660
+ const parseTable = (sectionName) => {
661
+ const re = new RegExp(
662
+ `## ${sectionName}[ \\t]*\\n\\n?\\|[^\\n]+\\n\\|[-| :]+\\n((?:\\|[^\\n]+\\n)*)`,
663
+ );
664
+ const m = content.match(re);
665
+ if (!m) return [];
666
+ return m[1].trim().split("\n").map((row) => {
667
+ const cols = row.split("|").map((c) => c.trim()).filter((c) => c && !c.startsWith("<!--"));
668
+ return cols;
669
+ }).filter((cols) => cols.length >= 2);
670
+ };
671
+
672
+ for (const cols of parseTable("Docs inventory")) {
673
+ result.inventory.push({ path: cols[0], purpose: cols[1], sources: cols[2] || "", status: cols[3] || "" });
674
+ }
675
+ for (const cols of parseTable("Component-to-docs mapping")) {
676
+ result.mappings.push({ component: cols[0], documentedIn: cols[1], trigger: cols[2] || "" });
677
+ }
678
+ for (const cols of parseTable("Scaffolding rules")) {
679
+ result.scaffolding.push({ condition: cols[0], proposedDoc: cols[1], seedFrom: cols[2] || "" });
680
+ }
681
+
682
+ return result;
683
+ }
684
+
685
+ function slugify(name) {
686
+ return name
687
+ .toLowerCase()
688
+ .replace(/[^a-z0-9]+/g, "-")
689
+ .replace(/^-|-$/g, "")
690
+ || "project";
691
+ }