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.
- package/README.md +197 -57
- package/bin/wwa.js +35 -9
- package/docs/internal/doc-tokens.yaml +452 -0
- package/docs/internal/feature-registry.yaml +13 -1
- package/lib/cli/casestudy.js +691 -0
- package/lib/cli/check.js +71 -71
- package/lib/cli/close.js +446 -0
- package/lib/cli/completion.js +639 -0
- package/lib/cli/digest.js +66 -0
- package/lib/cli/docs.js +207 -0
- package/lib/cli/helpers.js +49 -2
- package/lib/cli/implement.js +159 -0
- package/lib/cli/init.js +25 -6
- package/lib/cli/plan.js +128 -0
- package/lib/cli/refine.js +202 -202
- package/lib/cli/review.js +68 -0
- package/lib/cli/scan.js +28 -28
- package/lib/cli/status.js +61 -61
- package/lib/cli/upgrade.js +150 -147
- package/lib/init.js +478 -296
- package/package.json +12 -4
- package/templates/README.md +73 -25
- package/templates/entry-points/.cursorrules +143 -14
- package/templates/entry-points/AGENT.md +143 -14
- package/templates/entry-points/CLAUDE.md +143 -14
- package/templates/extensions/analytical-system.md +1 -1
- package/templates/extensions/code-project.md +1 -1
- package/templates/extensions/data-exploration.md +1 -1
- package/templates/full/.context/BASE.md +33 -0
- package/templates/full/.context/METHODOLOGY.md +62 -5
- package/templates/full/.cursorrules +128 -18
- package/templates/full/AGENT.md +128 -18
- package/templates/full/CLAUDE.md +128 -18
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PROJECT.md +34 -0
- package/templates/full/Reviews/INDEX.md +41 -0
- package/templates/full/Reviews/backlog.md +52 -0
- package/templates/full/Reviews/plan.md +43 -0
- package/templates/full/Reviews/project.md +41 -0
- package/templates/full/Reviews/requirements.md +42 -0
- package/templates/full/Reviews/roadmap.md +41 -0
- package/templates/full/Reviews/state.md +56 -0
- package/templates/full/SESSION-LOG.md +29 -0
- package/templates/full/SUMMARY.md +7 -4
- package/templates/full/agentWorkflows/INDEX.md +42 -0
- package/templates/full/agentWorkflows/observations.md +65 -0
- package/templates/full/agentWorkflows/patterns.md +68 -0
- package/templates/full/agentWorkflows/sessions.md +92 -0
- package/templates/full/intro/README.md +39 -0
- package/templates/starter/.context/BASE.md +35 -0
- package/templates/starter/.context/METHODOLOGY.md +59 -5
- package/templates/starter/.cursorrules +135 -13
- package/templates/starter/AGENT.md +135 -13
- package/templates/starter/CLAUDE.md +135 -13
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PROJECT.md +34 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +29 -0
- package/templates/starter/SUMMARY.md +27 -0
- package/templates/starter/agentWorkflows/INDEX.md +61 -0
- package/templates/starter/intro/README.md +37 -0
- 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
|
+
}
|