agent-method 1.5.12
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 +343 -0
- package/bin/wwa.js +115 -0
- package/docs/internal/cli-commands.yaml +259 -0
- package/docs/internal/doc-tokens.yaml +1103 -0
- package/docs/internal/feature-registry.yaml +1643 -0
- package/lib/boundaries.js +247 -0
- package/lib/cli/add.js +170 -0
- package/lib/cli/casestudy.js +1000 -0
- package/lib/cli/check.js +323 -0
- package/lib/cli/close.js +838 -0
- package/lib/cli/completion.js +735 -0
- package/lib/cli/deps.js +234 -0
- package/lib/cli/digest.js +73 -0
- package/lib/cli/doc-review.js +486 -0
- package/lib/cli/docs.js +315 -0
- package/lib/cli/helpers.js +198 -0
- package/lib/cli/implement.js +169 -0
- package/lib/cli/init.js +280 -0
- package/lib/cli/pipeline.js +206 -0
- package/lib/cli/plan.js +140 -0
- package/lib/cli/record.js +98 -0
- package/lib/cli/refine.js +202 -0
- package/lib/cli/report-helpers.js +113 -0
- package/lib/cli/review.js +76 -0
- package/lib/cli/routable.js +109 -0
- package/lib/cli/route.js +101 -0
- package/lib/cli/scan.js +133 -0
- package/lib/cli/serve.js +23 -0
- package/lib/cli/status.js +65 -0
- package/lib/cli/update-docs.js +574 -0
- package/lib/cli/upgrade.js +222 -0
- package/lib/cli/watch.js +32 -0
- package/lib/dependencies.js +196 -0
- package/lib/init.js +692 -0
- package/lib/mcp-server.js +612 -0
- package/lib/pipeline.js +907 -0
- package/lib/registry.js +132 -0
- package/lib/watcher.js +165 -0
- package/package.json +54 -0
- package/templates/README.md +363 -0
- package/templates/entry-points/.cursorrules +90 -0
- package/templates/entry-points/AGENT.md +90 -0
- package/templates/entry-points/CLAUDE.md +88 -0
- package/templates/extensions/MANIFEST.md +110 -0
- package/templates/extensions/analytical-system.md +96 -0
- package/templates/extensions/code-project.md +77 -0
- package/templates/extensions/data-exploration.md +117 -0
- package/templates/full/.context/BASE.md +101 -0
- package/templates/full/.context/COMPOSITION.md +47 -0
- package/templates/full/.context/INDEX.yaml +56 -0
- package/templates/full/.context/METHODOLOGY.md +246 -0
- package/templates/full/.context/PROTOCOL.yaml +169 -0
- package/templates/full/.context/REGISTRY.md +75 -0
- package/templates/full/.cursorrules +90 -0
- package/templates/full/AGENT.md +90 -0
- package/templates/full/CLAUDE.md +90 -0
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PLAN.md +67 -0
- package/templates/full/PROJECT-PROFILE.md +61 -0
- package/templates/full/PROJECT.md +80 -0
- package/templates/full/REQUIREMENTS.md +30 -0
- package/templates/full/ROADMAP.md +39 -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 +102 -0
- package/templates/full/STATE.md +42 -0
- package/templates/full/SUMMARY.md +27 -0
- 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/full/registry/feature-registry.yaml +25 -0
- package/templates/full/registry/features/catalog.yaml +743 -0
- package/templates/full/registry/features/protocol.yaml +121 -0
- package/templates/full/registry/features/routing.yaml +358 -0
- package/templates/full/registry/features/workflows.yaml +404 -0
- package/templates/full/todos/backlog.md +19 -0
- package/templates/starter/.context/BASE.md +66 -0
- package/templates/starter/.context/INDEX.yaml +51 -0
- package/templates/starter/.context/METHODOLOGY.md +228 -0
- package/templates/starter/.context/PROTOCOL.yaml +165 -0
- package/templates/starter/.cursorrules +90 -0
- package/templates/starter/AGENT.md +90 -0
- package/templates/starter/CLAUDE.md +90 -0
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PLAN.md +67 -0
- package/templates/starter/PROJECT-PROFILE.md +44 -0
- package/templates/starter/PROJECT.md +80 -0
- package/templates/starter/ROADMAP.md +39 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +102 -0
- package/templates/starter/STATE.md +42 -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/starter/registry/feature-registry.yaml +25 -0
- package/templates/starter/registry/features/catalog.yaml +743 -0
- package/templates/starter/registry/features/protocol.yaml +121 -0
- package/templates/starter/registry/features/routing.yaml +358 -0
- package/templates/starter/registry/features/workflows.yaml +404 -0
|
@@ -0,0 +1,1000 @@
|
|
|
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
|
+
import { resolveTokensPath, resolveDocsMapPath } from "../boundaries.js";
|
|
14
|
+
|
|
15
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
16
|
+
const __dirname = dirname(__filename);
|
|
17
|
+
|
|
18
|
+
export function register(program) {
|
|
19
|
+
program
|
|
20
|
+
.command("casestudy [directory]")
|
|
21
|
+
.description(
|
|
22
|
+
"Extract a structured case study from SESSION-LOG.md and project files"
|
|
23
|
+
)
|
|
24
|
+
.option("-o, --output <path>", "Output file path")
|
|
25
|
+
.option("--name <name>", "Project name override")
|
|
26
|
+
.option("--json", "Output as JSON")
|
|
27
|
+
.option(
|
|
28
|
+
"--yaml-only",
|
|
29
|
+
"Write a .yaml case study (no markdown file; all aspects in YAML)"
|
|
30
|
+
)
|
|
31
|
+
.option("--internal-registry", "Include docs/internal/doc-registry.yaml data in output")
|
|
32
|
+
.action(async (directory, opts) => {
|
|
33
|
+
directory = directory || ".";
|
|
34
|
+
const d = resolve(directory);
|
|
35
|
+
|
|
36
|
+
// Gather all project data
|
|
37
|
+
const data = await gatherProjectData(d, opts.name);
|
|
38
|
+
|
|
39
|
+
// Load internal registry if requested
|
|
40
|
+
if (opts.internalRegistry) {
|
|
41
|
+
const { loadInternalRegistry } = await import("./doc-review.js");
|
|
42
|
+
const registry = await loadInternalRegistry(d);
|
|
43
|
+
if (registry) {
|
|
44
|
+
data.internalRegistry = registry;
|
|
45
|
+
} else {
|
|
46
|
+
data.warnings.push(
|
|
47
|
+
"No docs/internal/doc-registry.yaml found — run `wwa doc-review` to generate it"
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (data.errors.length > 0 && data.sessions.length === 0) {
|
|
53
|
+
console.error("Cannot generate case study:");
|
|
54
|
+
for (const e of data.errors) console.error(` - ${e}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (opts.json) {
|
|
59
|
+
const output = opts.output;
|
|
60
|
+
const result = JSON.stringify(data, null, 2);
|
|
61
|
+
if (output) {
|
|
62
|
+
safeWriteFile(output, result);
|
|
63
|
+
console.log(`Case study data written to ${output}`);
|
|
64
|
+
} else {
|
|
65
|
+
console.log(result);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// YAML-only mode: write a single .yaml artifact with all structured data.
|
|
71
|
+
if (opts.yamlOnly) {
|
|
72
|
+
const baseDir = join(d, "case-studies");
|
|
73
|
+
const baseSlug = slugify(data.projectName);
|
|
74
|
+
let yamlOutputPath = opts.output || join(baseDir, `${baseSlug}.yaml`);
|
|
75
|
+
|
|
76
|
+
// Avoid overwriting existing case studies when using the default path.
|
|
77
|
+
if (!opts.output) {
|
|
78
|
+
let counter = 1;
|
|
79
|
+
while (existsSync(yamlOutputPath)) {
|
|
80
|
+
yamlOutputPath = join(baseDir, `${baseSlug}-${counter}.yaml`);
|
|
81
|
+
counter += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const yamlDir = resolve(yamlOutputPath, "..");
|
|
86
|
+
if (!existsSync(yamlDir)) {
|
|
87
|
+
mkdirSync(yamlDir, { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const ok = await writeCaseStudyYaml(yamlOutputPath, data);
|
|
91
|
+
if (ok) {
|
|
92
|
+
console.log(
|
|
93
|
+
`Case study YAML written to ${yamlOutputPath} (${data.sessions.length} sessions)`
|
|
94
|
+
);
|
|
95
|
+
} else {
|
|
96
|
+
console.error("Failed to write YAML case study.");
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
// Default: write markdown case study plus companion YAML.
|
|
101
|
+
const baseDir = join(d, "case-studies");
|
|
102
|
+
const baseSlug = slugify(data.projectName);
|
|
103
|
+
let markdownPath = opts.output || join(baseDir, `${baseSlug}.md`);
|
|
104
|
+
|
|
105
|
+
// Avoid overwriting existing case studies when using the default path.
|
|
106
|
+
if (!opts.output) {
|
|
107
|
+
let counter = 1;
|
|
108
|
+
while (existsSync(markdownPath)) {
|
|
109
|
+
markdownPath = join(baseDir, `${baseSlug}-${counter}.md`);
|
|
110
|
+
counter += 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const outputDir = resolve(markdownPath, "..");
|
|
115
|
+
if (!existsSync(outputDir)) {
|
|
116
|
+
mkdirSync(outputDir, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const caseStudy = generateCaseStudy(data);
|
|
120
|
+
safeWriteFile(markdownPath, caseStudy);
|
|
121
|
+
console.log(
|
|
122
|
+
`Case study written to ${markdownPath} (${data.sessions.length} sessions)`
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const yamlPath = markdownPath.replace(/\.md$/, ".yaml");
|
|
126
|
+
const yamlResult = await writeCaseStudyYaml(yamlPath, data);
|
|
127
|
+
if (yamlResult) {
|
|
128
|
+
console.log(`Case study YAML written to ${yamlPath}`);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (data.warnings.length > 0) {
|
|
133
|
+
console.log("\nWarnings:");
|
|
134
|
+
for (const w of data.warnings) console.log(` - ${w}`);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Data gathering
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
async function gatherProjectData(dir, nameOverride) {
|
|
144
|
+
const data = {
|
|
145
|
+
projectName: nameOverride || "Unknown Project",
|
|
146
|
+
projectType: "unknown",
|
|
147
|
+
lifecycleStart: "unknown",
|
|
148
|
+
lifecycleEnd: "unknown",
|
|
149
|
+
templateTier: "unknown",
|
|
150
|
+
extensions: "none",
|
|
151
|
+
entryPointFile: null,
|
|
152
|
+
integrationProfile: "unknown",
|
|
153
|
+
modelTier: "unknown",
|
|
154
|
+
methodVersion: null,
|
|
155
|
+
projectDescription: "",
|
|
156
|
+
studyGoals: "",
|
|
157
|
+
sessions: [],
|
|
158
|
+
decisions: [],
|
|
159
|
+
workflows: new Set(),
|
|
160
|
+
features: new Set(),
|
|
161
|
+
frictionPoints: [],
|
|
162
|
+
findings: [],
|
|
163
|
+
agentWorkflows: null,
|
|
164
|
+
tokenRegistry: null,
|
|
165
|
+
nameMappings: null,
|
|
166
|
+
docsMap: null,
|
|
167
|
+
errors: [],
|
|
168
|
+
warnings: [],
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// --- Entry point ---
|
|
172
|
+
const entryPoint = findEntryPoint(dir);
|
|
173
|
+
if (entryPoint) {
|
|
174
|
+
data.entryPointFile = basename(entryPoint);
|
|
175
|
+
data.methodVersion = readMethodVersion(entryPoint);
|
|
176
|
+
const epContent = readFileSync(entryPoint, "utf-8");
|
|
177
|
+
const tierMatch = epContent.match(/tier:\s*(\S+)/);
|
|
178
|
+
if (tierMatch) data.templateTier = tierMatch[1];
|
|
179
|
+
const modeMatch = epContent.match(/mode:\s*(\S+)/);
|
|
180
|
+
if (modeMatch) data.integrationProfile = modeMatch[1];
|
|
181
|
+
} else {
|
|
182
|
+
data.warnings.push("No entry point found (CLAUDE.md / .cursorrules / AGENT.md)");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// --- PROJECT.md ---
|
|
186
|
+
const projectPath = join(dir, "PROJECT.md");
|
|
187
|
+
if (existsSync(projectPath)) {
|
|
188
|
+
const content = readFileSync(projectPath, "utf-8");
|
|
189
|
+
const titleMatch = content.match(/^#\s+(.+)/m);
|
|
190
|
+
if (titleMatch && !nameOverride) {
|
|
191
|
+
data.projectName = titleMatch[1].trim();
|
|
192
|
+
}
|
|
193
|
+
// Extract description (first paragraph after the title)
|
|
194
|
+
const descMatch = content.match(/^#[^\n]+\n+(.+?)(?:\n\n|\n##)/s);
|
|
195
|
+
if (descMatch) {
|
|
196
|
+
data.projectDescription = descMatch[1].trim();
|
|
197
|
+
}
|
|
198
|
+
} else {
|
|
199
|
+
data.warnings.push("No PROJECT.md found — project name and description may be incomplete");
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// --- PROJECT-PROFILE.md ---
|
|
203
|
+
const profilePath = join(dir, "PROJECT-PROFILE.md");
|
|
204
|
+
if (existsSync(profilePath)) {
|
|
205
|
+
const content = readFileSync(profilePath, "utf-8");
|
|
206
|
+
const typeMatch = content.match(/project.?type[:\s|]+(\S+)/i);
|
|
207
|
+
if (typeMatch) data.projectType = typeMatch[1].toLowerCase();
|
|
208
|
+
const stageMatch = content.match(/lifecycle.?stage[:\s|]+(\S+)/i);
|
|
209
|
+
if (stageMatch) data.lifecycleStart = stageMatch[1].toLowerCase();
|
|
210
|
+
const extMatch = content.match(/extension[s]?[:\s|]+([^\n|]+)/i);
|
|
211
|
+
if (extMatch) data.extensions = extMatch[1].trim();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// --- STATE.md (decisions) ---
|
|
215
|
+
const statePath = join(dir, "STATE.md");
|
|
216
|
+
if (existsSync(statePath)) {
|
|
217
|
+
const content = readFileSync(statePath, "utf-8");
|
|
218
|
+
// Extract decisions from table rows
|
|
219
|
+
const decisionSection = content.match(
|
|
220
|
+
/##\s*Decisions?\s*\n([\s\S]*?)(?=\n##|\n$|$)/i
|
|
221
|
+
);
|
|
222
|
+
if (decisionSection) {
|
|
223
|
+
const rows = decisionSection[1].match(/\|[^|\n]+\|[^|\n]+\|/g) || [];
|
|
224
|
+
for (const row of rows) {
|
|
225
|
+
const cols = row
|
|
226
|
+
.split("|")
|
|
227
|
+
.map((c) => c.trim())
|
|
228
|
+
.filter((c) => c && !c.match(/^[-:]+$/));
|
|
229
|
+
if (cols.length >= 2 && !cols[0].toLowerCase().includes("date")) {
|
|
230
|
+
data.decisions.push({ date: cols[0], decision: cols.slice(1).join(" — ") });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- SESSION-LOG.md ---
|
|
237
|
+
const sessionLog = findSessionLog(dir);
|
|
238
|
+
if (sessionLog) {
|
|
239
|
+
const content = readFileSync(sessionLog, "utf-8");
|
|
240
|
+
data.sessions = parseSessionEntries(content);
|
|
241
|
+
|
|
242
|
+
// Extract project context header
|
|
243
|
+
const ctxMatch = content.match(
|
|
244
|
+
/## Project context\s*\n\s*\|[^\n]+\n\s*\|[-| ]+\n((?:\|[^\n]+\n)*)/
|
|
245
|
+
);
|
|
246
|
+
if (ctxMatch) {
|
|
247
|
+
for (const row of ctxMatch[1].trim().split("\n")) {
|
|
248
|
+
const cols = row
|
|
249
|
+
.split("|")
|
|
250
|
+
.map((c) => c.trim())
|
|
251
|
+
.filter((c) => c);
|
|
252
|
+
if (cols.length >= 2) {
|
|
253
|
+
const key = cols[0].toLowerCase();
|
|
254
|
+
if (key.includes("project name") && !nameOverride) {
|
|
255
|
+
data.projectName = cols[1];
|
|
256
|
+
}
|
|
257
|
+
if (key.includes("project type")) data.projectType = cols[1].toLowerCase();
|
|
258
|
+
if (key.includes("profile")) data.integrationProfile = cols[1];
|
|
259
|
+
if (key.includes("extension")) data.extensions = cols[1];
|
|
260
|
+
if (key.includes("model")) data.modelTier = cols[1];
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Aggregate workflows and features
|
|
266
|
+
for (const s of data.sessions) {
|
|
267
|
+
if (s.workflow) data.workflows.add(s.workflow);
|
|
268
|
+
if (s.features) {
|
|
269
|
+
for (const f of s.features.split(",").map((x) => x.trim())) {
|
|
270
|
+
if (f) data.features.add(f);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
if (s.friction && s.friction.toLowerCase() !== "none") {
|
|
274
|
+
data.frictionPoints.push({ session: s.session, date: s.date, text: s.friction });
|
|
275
|
+
}
|
|
276
|
+
if (s.finding && s.finding.toLowerCase() !== "none") {
|
|
277
|
+
data.findings.push({ session: s.session, date: s.date, text: s.finding });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
data.errors.push("No SESSION-LOG.md found — session data is required for case study extraction");
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- agentWorkflows/INDEX.md ---
|
|
285
|
+
const wfPath = join(dir, "agentWorkflows", "INDEX.md");
|
|
286
|
+
if (existsSync(wfPath)) {
|
|
287
|
+
data.agentWorkflows = readFileSync(wfPath, "utf-8");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// --- SUMMARY.md ---
|
|
291
|
+
const summaryPath = join(dir, "SUMMARY.md");
|
|
292
|
+
if (existsSync(summaryPath)) {
|
|
293
|
+
const content = readFileSync(summaryPath, "utf-8");
|
|
294
|
+
// Count summary entries
|
|
295
|
+
const entryCount = (content.match(/^##\s+/gm) || []).length;
|
|
296
|
+
if (entryCount > 0) {
|
|
297
|
+
data.warnings.push(
|
|
298
|
+
`SUMMARY.md has ${entryCount} entries — cross-reference with session log for completeness`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// --- Token registry ---
|
|
304
|
+
// Priority: project tokens (configured path) > package docs/internal/doc-tokens.yaml
|
|
305
|
+
const projectTokensPath = await resolveTokensPath(dir);
|
|
306
|
+
const packageTokensPath = join(packageRoot, "docs", "internal", "doc-tokens.yaml");
|
|
307
|
+
try {
|
|
308
|
+
const yaml = (await import("js-yaml")).default;
|
|
309
|
+
// Load project-specific tokens (populated by wwa close)
|
|
310
|
+
if (existsSync(projectTokensPath)) {
|
|
311
|
+
const raw = readFileSync(projectTokensPath, "utf-8");
|
|
312
|
+
const parsed = yaml.load(raw);
|
|
313
|
+
if (parsed && parsed.tokens) data.tokenRegistry = parsed.tokens;
|
|
314
|
+
}
|
|
315
|
+
// Load methodology name mappings from package (always available)
|
|
316
|
+
if (existsSync(packageTokensPath)) {
|
|
317
|
+
const raw = readFileSync(packageTokensPath, "utf-8");
|
|
318
|
+
const parsed = yaml.load(raw);
|
|
319
|
+
if (parsed && parsed.names) data.nameMappings = parsed.names;
|
|
320
|
+
// If no project tokens, fall back to package tokens
|
|
321
|
+
if (!data.tokenRegistry && parsed && parsed.tokens) data.tokenRegistry = parsed.tokens;
|
|
322
|
+
}
|
|
323
|
+
} catch (_) {
|
|
324
|
+
data.warnings.push("Could not parse doc-tokens.yaml — token registry omitted");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// --- DOCS-MAP.md (from project) ---
|
|
328
|
+
const docsMapPath = await resolveDocsMapPath(dir);
|
|
329
|
+
if (existsSync(docsMapPath)) {
|
|
330
|
+
const content = readFileSync(docsMapPath, "utf-8");
|
|
331
|
+
data.docsMap = parseDocsMapForCaseStudy(content);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Convert sets to arrays for serialization
|
|
335
|
+
data.workflows = [...data.workflows];
|
|
336
|
+
data.features = [...data.features];
|
|
337
|
+
data.lifecycleEnd = data.lifecycleStart; // default; override if detectable
|
|
338
|
+
|
|
339
|
+
return data;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ---------------------------------------------------------------------------
|
|
343
|
+
// Session log parsing (aligned with SESSION-LOG.md Entry format — Phase 7r §5.6)
|
|
344
|
+
// ---------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
/** Map SESSION-LOG.md field labels to canonical keys used by case study output. */
|
|
347
|
+
const SESSION_LOG_KEY_MAP = {
|
|
348
|
+
"llm tokens": "tokens",
|
|
349
|
+
"wall time": "time",
|
|
350
|
+
"tool calls": "tool_calls",
|
|
351
|
+
"delta notes": "delta_notes",
|
|
352
|
+
};
|
|
353
|
+
|
|
354
|
+
/** Known session entry keys (Entry format); ignore parsed key:value from checklist/bullets. */
|
|
355
|
+
const SESSION_ENTRY_KEYS = new Set([
|
|
356
|
+
"model", "profile", "workflow", "effort", "ambiguity", "context", "tokens", "tool_calls", "time",
|
|
357
|
+
"queries", "features", "cascades", "decisions", "response", "revisions", "magnitude", "delta",
|
|
358
|
+
"survival", "delta_notes", "friction", "finding",
|
|
359
|
+
]);
|
|
360
|
+
|
|
361
|
+
function parseSessionEntries(content) {
|
|
362
|
+
const entries = [];
|
|
363
|
+
// Normalize line endings and allow em dash (—) or " - " between session number, date, and title
|
|
364
|
+
const normalized = content.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
365
|
+
// Body: from the line after the title until the next "### S" session header or end of string.
|
|
366
|
+
// Use (?![\s\S]) for end-of-string so we don't stop at newlines (.(?!.) matches before \n).
|
|
367
|
+
const entryPattern =
|
|
368
|
+
/###\s+S(\d+)\s*(?:\u2014|-\s*-?)\s*(\S+)\s*(?:\u2014|-\s*-?)\s*(.+?)\n([\s\S]*?)(?=###\s+S\d+\s|(?![\s\S]))/gm;
|
|
369
|
+
let m;
|
|
370
|
+
while ((m = entryPattern.exec(normalized)) !== null) {
|
|
371
|
+
const [, num, date, title, body] = m;
|
|
372
|
+
const entry = {
|
|
373
|
+
session: parseInt(num, 10),
|
|
374
|
+
date,
|
|
375
|
+
title: title.trim(),
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
for (const line of body.split("\n")) {
|
|
379
|
+
const trimmed = line.trim();
|
|
380
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
381
|
+
// Parse pipe-delimited key: value pairs (SESSION-LOG Entry format)
|
|
382
|
+
const segments = trimmed.split("|").map((s) => s.trim());
|
|
383
|
+
for (const seg of segments) {
|
|
384
|
+
if (seg.includes(":")) {
|
|
385
|
+
const idx = seg.indexOf(":");
|
|
386
|
+
const rawKey = seg.slice(0, idx).trim().toLowerCase();
|
|
387
|
+
const val = seg.slice(idx + 1).trim();
|
|
388
|
+
if (!rawKey || !val) continue;
|
|
389
|
+
const key = SESSION_LOG_KEY_MAP[rawKey] ?? rawKey;
|
|
390
|
+
if (SESSION_ENTRY_KEYS.has(key)) entry[key] = val;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
entries.push(entry);
|
|
395
|
+
}
|
|
396
|
+
return entries;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
// Case study generation
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
|
|
403
|
+
function generateCaseStudy(data) {
|
|
404
|
+
const lines = [];
|
|
405
|
+
|
|
406
|
+
// Section 1: Overview
|
|
407
|
+
lines.push(
|
|
408
|
+
`# Case Study: ${data.projectName}`,
|
|
409
|
+
"",
|
|
410
|
+
"## Overview",
|
|
411
|
+
"",
|
|
412
|
+
"| Field | Value |",
|
|
413
|
+
"|-------|-------|",
|
|
414
|
+
`| Project name | ${data.projectName} |`,
|
|
415
|
+
`| Project type | ${data.projectType} |`,
|
|
416
|
+
`| Lifecycle stage at start | ${data.lifecycleStart} |`,
|
|
417
|
+
`| Lifecycle stage at end | ${data.lifecycleEnd} |`,
|
|
418
|
+
`| Template tier | ${data.templateTier} |`,
|
|
419
|
+
`| Extensions applied | ${data.extensions} |`,
|
|
420
|
+
`| Entry point | ${data.entryPointFile || "unknown"} |`,
|
|
421
|
+
`| Integration profile | ${data.integrationProfile} |`,
|
|
422
|
+
`| Model tier | ${data.modelTier} |`,
|
|
423
|
+
`| Sessions documented | ${data.sessions.length} |`,
|
|
424
|
+
`| Method version | ${data.methodVersion || "unknown"} |`,
|
|
425
|
+
""
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (data.projectDescription) {
|
|
429
|
+
lines.push("## Project description", "", data.projectDescription, "");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
lines.push(
|
|
433
|
+
"## Study goals",
|
|
434
|
+
"",
|
|
435
|
+
"<!-- Fill in: what this case study specifically aims to validate -->",
|
|
436
|
+
""
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
// Section 2: Setup
|
|
440
|
+
lines.push(
|
|
441
|
+
"---",
|
|
442
|
+
"",
|
|
443
|
+
"## Setup",
|
|
444
|
+
"",
|
|
445
|
+
"### Bootstrap experience",
|
|
446
|
+
"",
|
|
447
|
+
"| Step | Time | Friction | Notes |",
|
|
448
|
+
"|------|------|----------|-------|"
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
if (data.sessions.length > 0) {
|
|
452
|
+
const first = data.sessions[0];
|
|
453
|
+
lines.push(
|
|
454
|
+
`| First session (S${first.session}) | ${first.time || "unknown"} | ${first.friction || "none"} | ${first.title} |`
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
lines.push("");
|
|
459
|
+
|
|
460
|
+
// Section 3: Feature activation
|
|
461
|
+
lines.push(
|
|
462
|
+
"---",
|
|
463
|
+
"",
|
|
464
|
+
"## Feature activation",
|
|
465
|
+
"",
|
|
466
|
+
"### Features activated",
|
|
467
|
+
"",
|
|
468
|
+
"| Feature | Sessions | Evidence |",
|
|
469
|
+
"|---------|:--------:|----------|"
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
if (data.features.length > 0) {
|
|
473
|
+
for (const f of data.features.sort()) {
|
|
474
|
+
// Find which sessions activated this feature
|
|
475
|
+
const activeSessions = data.sessions
|
|
476
|
+
.filter((s) => s.features && s.features.includes(f))
|
|
477
|
+
.map((s) => `S${s.session}`)
|
|
478
|
+
.join(", ");
|
|
479
|
+
lines.push(`| ${f} | ${activeSessions} | Observed in session log |`);
|
|
480
|
+
}
|
|
481
|
+
} else {
|
|
482
|
+
lines.push("| (no features recorded) | -- | -- |");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
lines.push("");
|
|
486
|
+
|
|
487
|
+
lines.push("### Workflow usage", "", "| Workflow | Sessions | Completed? |", "|---------|:--------:|:----------:|");
|
|
488
|
+
if (data.workflows.length > 0) {
|
|
489
|
+
for (const wf of data.workflows.sort()) {
|
|
490
|
+
const wfSessions = data.sessions
|
|
491
|
+
.filter((s) => s.workflow === wf)
|
|
492
|
+
.map((s) => `S${s.session}`)
|
|
493
|
+
.join(", ");
|
|
494
|
+
lines.push(`| ${wf} | ${wfSessions} | yes |`);
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
lines.push("| (no workflows recorded) | -- | -- |");
|
|
498
|
+
}
|
|
499
|
+
lines.push("");
|
|
500
|
+
|
|
501
|
+
// Section 4: Session log
|
|
502
|
+
lines.push("---", "", "## Session log", "");
|
|
503
|
+
|
|
504
|
+
for (const s of data.sessions) {
|
|
505
|
+
lines.push(
|
|
506
|
+
`### Session ${s.session}: ${s.title}`,
|
|
507
|
+
"",
|
|
508
|
+
"| Field | Value |",
|
|
509
|
+
"|-------|-------|",
|
|
510
|
+
`| Date | ${s.date} |`,
|
|
511
|
+
`| Model | ${s.model || "unknown"} |`,
|
|
512
|
+
`| Profile | ${s.profile || "unknown"} |`,
|
|
513
|
+
`| Effort | ${s.effort || "unknown"} |`,
|
|
514
|
+
`| Ambiguity | ${s.ambiguity || "unknown"} |`,
|
|
515
|
+
`| Context | ${s.context || "unknown"} |`,
|
|
516
|
+
`| Tokens | ${s.tokens || "unknown"} |`,
|
|
517
|
+
`| Time | ${s.time || "unknown"} |`,
|
|
518
|
+
`| Tool calls | ${s.tool_calls ?? "—"} |`,
|
|
519
|
+
`| Workflow | ${s.workflow || "unknown"} |`,
|
|
520
|
+
`| Features | ${s.features || "none"} |`,
|
|
521
|
+
`| Cascades | ${s.cascades || "unknown"} |`,
|
|
522
|
+
`| Decisions | ${s.decisions || "0"} |`,
|
|
523
|
+
`| Response | ${s.response || "unknown"} |`,
|
|
524
|
+
`| Revisions | ${s.revisions ?? "—"} |`,
|
|
525
|
+
`| Magnitude | ${s.magnitude ?? "—"} |`,
|
|
526
|
+
`| Delta | ${s.delta ?? "n/a"} |`,
|
|
527
|
+
`| Survival | ${s.survival ?? "—"} |`,
|
|
528
|
+
`| Delta notes | ${s.delta_notes ?? "n/a"} |`,
|
|
529
|
+
`| Friction | ${s.friction || "none"} |`,
|
|
530
|
+
`| Finding | ${s.finding || "none"} |`
|
|
531
|
+
);
|
|
532
|
+
|
|
533
|
+
lines.push("");
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// Section 5: Scoring rubric
|
|
537
|
+
lines.push(
|
|
538
|
+
"---",
|
|
539
|
+
"",
|
|
540
|
+
"## Methodology effectiveness scoring",
|
|
541
|
+
"",
|
|
542
|
+
"Rate each dimension 1-5 with specific evidence.",
|
|
543
|
+
"",
|
|
544
|
+
"| Dimension | Score | Evidence | Notes |",
|
|
545
|
+
"|-----------|:-----:|----------|-------|"
|
|
546
|
+
);
|
|
547
|
+
|
|
548
|
+
const dimensions = [
|
|
549
|
+
"Context quality",
|
|
550
|
+
"Decision preservation",
|
|
551
|
+
"Scope discipline",
|
|
552
|
+
"Cascade coverage",
|
|
553
|
+
"Audit completeness",
|
|
554
|
+
"Bootstrap speed",
|
|
555
|
+
"Lifecycle fit",
|
|
556
|
+
"Model adequacy",
|
|
557
|
+
];
|
|
558
|
+
|
|
559
|
+
for (const dim of dimensions) {
|
|
560
|
+
lines.push(`| ${dim} | -- | | |`);
|
|
561
|
+
}
|
|
562
|
+
lines.push("| **Overall** | -- | | |", "");
|
|
563
|
+
|
|
564
|
+
// Section 6: Findings
|
|
565
|
+
lines.push("---", "", "## Findings", "", "### What worked", "");
|
|
566
|
+
|
|
567
|
+
if (data.findings.length > 0) {
|
|
568
|
+
lines.push("| # | Finding | Session | Evidence |", "|---|---------|---------|----------|");
|
|
569
|
+
let wn = 1;
|
|
570
|
+
for (const f of data.findings) {
|
|
571
|
+
lines.push(`| W${wn} | ${f.text} | S${f.session} (${f.date}) | Session log |`);
|
|
572
|
+
wn++;
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
lines.push("<!-- Fill in findings from session review -->");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
lines.push("", "### What needs improvement", "");
|
|
579
|
+
|
|
580
|
+
if (data.frictionPoints.length > 0) {
|
|
581
|
+
lines.push(
|
|
582
|
+
"| # | Finding | Session | Severity | Suggested change |",
|
|
583
|
+
"|---|---------|---------|----------|-----------------|"
|
|
584
|
+
);
|
|
585
|
+
let fn = 1;
|
|
586
|
+
for (const f of data.frictionPoints) {
|
|
587
|
+
lines.push(
|
|
588
|
+
`| I${fn} | ${f.text} | S${f.session} (${f.date}) | -- | <!-- suggest fix --> |`
|
|
589
|
+
);
|
|
590
|
+
fn++;
|
|
591
|
+
}
|
|
592
|
+
} else {
|
|
593
|
+
lines.push("<!-- Fill in improvement findings from session review -->");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
lines.push("", "### What's missing", "", "<!-- Fill in gaps discovered during the study -->", "");
|
|
597
|
+
|
|
598
|
+
// Section 7: Refinement recommendations
|
|
599
|
+
lines.push(
|
|
600
|
+
"---",
|
|
601
|
+
"",
|
|
602
|
+
"## Refinement recommendations",
|
|
603
|
+
"",
|
|
604
|
+
"| # | Finding ref | Target | Change type | Specific change | Validation |",
|
|
605
|
+
"|---|------------|--------|-------------|-----------------|------------|"
|
|
606
|
+
);
|
|
607
|
+
|
|
608
|
+
if (data.frictionPoints.length > 0) {
|
|
609
|
+
let rn = 1;
|
|
610
|
+
for (let i = 0; i < data.frictionPoints.length; i++) {
|
|
611
|
+
lines.push(
|
|
612
|
+
`| R${rn} | I${i + 1} | <!-- target --> | <!-- type --> | <!-- change --> | <!-- validation --> |`
|
|
613
|
+
);
|
|
614
|
+
rn++;
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
lines.push("| R1 | -- | -- | -- | -- | -- |");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
lines.push("");
|
|
621
|
+
|
|
622
|
+
// Agent workflow annotations
|
|
623
|
+
if (data.agentWorkflows) {
|
|
624
|
+
lines.push(
|
|
625
|
+
"---",
|
|
626
|
+
"",
|
|
627
|
+
"## Agent workflow annotations",
|
|
628
|
+
"",
|
|
629
|
+
"Extracted from agentWorkflows/INDEX.md:",
|
|
630
|
+
"",
|
|
631
|
+
data.agentWorkflows,
|
|
632
|
+
""
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Statistics summary
|
|
637
|
+
lines.push(
|
|
638
|
+
"---",
|
|
639
|
+
"",
|
|
640
|
+
"## Statistics",
|
|
641
|
+
"",
|
|
642
|
+
`- **Total sessions**: ${data.sessions.length}`,
|
|
643
|
+
`- **Workflows used**: ${data.workflows.join(", ") || "none recorded"}`,
|
|
644
|
+
`- **Features observed**: ${data.features.length}`,
|
|
645
|
+
`- **Decisions recorded**: ${data.decisions.length}`,
|
|
646
|
+
`- **Friction points**: ${data.frictionPoints.length}`,
|
|
647
|
+
`- **Findings**: ${data.findings.length}`,
|
|
648
|
+
""
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
// Methodology reference data
|
|
652
|
+
if (data.tokenRegistry || data.nameMappings || data.docsMap) {
|
|
653
|
+
lines.push("---", "", "## Methodology reference data", "");
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
if (data.tokenRegistry) {
|
|
657
|
+
lines.push("### Token registry snapshot", "");
|
|
658
|
+
lines.push("| Token | Value |", "|-------|-------|");
|
|
659
|
+
const keySubset = [
|
|
660
|
+
"registry_version", "feature_count", "domain_count", "directive_count",
|
|
661
|
+
"workflow_count", "query_pattern_count", "starter_file_count", "full_file_count",
|
|
662
|
+
"max_file_lines", "cli_command", "npm_package", "pip_package",
|
|
663
|
+
"developer_cmd_count", "pipeline_cmd_count", "total_cmd_count",
|
|
664
|
+
];
|
|
665
|
+
for (const key of keySubset) {
|
|
666
|
+
if (data.tokenRegistry[key] !== undefined) {
|
|
667
|
+
lines.push(`| ${key} | ${data.tokenRegistry[key]} |`);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
lines.push("");
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (data.nameMappings) {
|
|
674
|
+
lines.push("### Name mappings", "");
|
|
675
|
+
|
|
676
|
+
if (data.nameMappings.project_types) {
|
|
677
|
+
lines.push("**Project types**", "", "| Canonical | CLI alias |", "|-----------|-----------|");
|
|
678
|
+
for (const t of data.nameMappings.project_types) {
|
|
679
|
+
lines.push(`| ${t.canonical} | ${t.cli_alias || "—"} |`);
|
|
680
|
+
}
|
|
681
|
+
lines.push("");
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (data.nameMappings.guided_workflows) {
|
|
685
|
+
lines.push("**Workflows**", "", "| ID | Name | Stages |", "|----|------|--------|");
|
|
686
|
+
for (const wf of data.nameMappings.guided_workflows) {
|
|
687
|
+
lines.push(`| ${wf.id} | ${wf.display} | ${wf.context || ""} |`);
|
|
688
|
+
}
|
|
689
|
+
lines.push("");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (data.nameMappings.feature_domains) {
|
|
693
|
+
lines.push("**Feature domains**", "", "| ID | Domain | Range |", "|----|--------|-------|");
|
|
694
|
+
for (const d of data.nameMappings.feature_domains) {
|
|
695
|
+
lines.push(`| ${d.id} | ${d.canonical} | ${d.feature_range} |`);
|
|
696
|
+
}
|
|
697
|
+
lines.push("");
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (data.nameMappings.protocol_directives) {
|
|
701
|
+
lines.push("**Protocol directives**", "", "| ID | Name | Rule |", "|----|------|------|");
|
|
702
|
+
for (const p of data.nameMappings.protocol_directives) {
|
|
703
|
+
lines.push(`| ${p.id} | ${p.canonical} | ${p.short} |`);
|
|
704
|
+
}
|
|
705
|
+
lines.push("");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
if (data.docsMap) {
|
|
710
|
+
lines.push("### Docs dependency map", "");
|
|
711
|
+
if (data.docsMap.inventory.length > 0) {
|
|
712
|
+
lines.push("**Docs inventory**", "", "| Path | Purpose | Status |", "|------|---------|--------|");
|
|
713
|
+
for (const item of data.docsMap.inventory) {
|
|
714
|
+
lines.push(`| ${item.path} | ${item.purpose} | ${item.status} |`);
|
|
715
|
+
}
|
|
716
|
+
lines.push("");
|
|
717
|
+
}
|
|
718
|
+
if (data.docsMap.mappings.length > 0) {
|
|
719
|
+
lines.push("**Component mappings**", "", "| Component | Documented in | Trigger |", "|-----------|--------------|---------|");
|
|
720
|
+
for (const m of data.docsMap.mappings) {
|
|
721
|
+
lines.push(`| ${m.component} | ${m.documentedIn} | ${m.trigger} |`);
|
|
722
|
+
}
|
|
723
|
+
lines.push("");
|
|
724
|
+
}
|
|
725
|
+
lines.push(`**Scaffolding rules**: ${data.docsMap.scaffolding.length} active`, "");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Internal document registry (when --internal-registry is used)
|
|
729
|
+
if (data.internalRegistry) {
|
|
730
|
+
lines.push("---", "", "## Internal document registry", "");
|
|
731
|
+
const reg = data.internalRegistry;
|
|
732
|
+
|
|
733
|
+
if (reg.health) {
|
|
734
|
+
lines.push(
|
|
735
|
+
"### Registry health",
|
|
736
|
+
"",
|
|
737
|
+
"| Metric | Value |",
|
|
738
|
+
"|--------|-------|",
|
|
739
|
+
`| Total nodes | ${reg.health.totalDocs ?? reg.health.total_docs ?? "—"} |`,
|
|
740
|
+
`| Nodes on disk | ${reg.health.docsOnDisk ?? reg.health.docs_on_disk ?? "—"} |`,
|
|
741
|
+
`| Orphan nodes | ${reg.health.orphanNodes ?? reg.health.orphan_nodes ?? "—"} |`,
|
|
742
|
+
`| Dependency coverage | ${reg.health.dependencyCoverage ?? reg.health.dependency_coverage ?? "—"} |`,
|
|
743
|
+
""
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
if (reg.dependency_graph?.read_order?.length > 0) {
|
|
748
|
+
lines.push("### Dependency read order", "");
|
|
749
|
+
for (let i = 0; i < reg.dependency_graph.read_order.length; i++) {
|
|
750
|
+
lines.push(`${i + 1}. \`${reg.dependency_graph.read_order[i]}\``);
|
|
751
|
+
}
|
|
752
|
+
lines.push("");
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
if (reg.terminology) {
|
|
756
|
+
const t = reg.terminology;
|
|
757
|
+
const termCount =
|
|
758
|
+
(t.modules?.length || 0) +
|
|
759
|
+
(t.apis?.length || 0) +
|
|
760
|
+
(t.entities?.length || 0) +
|
|
761
|
+
(t.conventions?.length || 0);
|
|
762
|
+
lines.push(`### Terminology snapshot`, "", `${termCount} term(s) captured across modules, APIs, entities, and conventions.`, "");
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
// Synthesized analysis — agent-level understanding from session metrics
|
|
767
|
+
lines.push("---", "", "## Synthesized analysis", "");
|
|
768
|
+
lines.push(generateSynthesizedAnalysis(data), "");
|
|
769
|
+
|
|
770
|
+
lines.push("---", "", "*Generated by `wwa casestudy` from project methodology files.*", "");
|
|
771
|
+
|
|
772
|
+
return lines.join("\n");
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// ---------------------------------------------------------------------------
|
|
776
|
+
// Utilities
|
|
777
|
+
// ---------------------------------------------------------------------------
|
|
778
|
+
|
|
779
|
+
function generateSynthesizedAnalysis(data) {
|
|
780
|
+
const lines = [];
|
|
781
|
+
|
|
782
|
+
// Effort distribution
|
|
783
|
+
const effortDist = {};
|
|
784
|
+
const ambiguityDist = {};
|
|
785
|
+
const responseDist = {};
|
|
786
|
+
let totalCascades = 0;
|
|
787
|
+
let highEffortCount = 0;
|
|
788
|
+
|
|
789
|
+
for (const s of data.sessions) {
|
|
790
|
+
if (s.effort) effortDist[s.effort] = (effortDist[s.effort] || 0) + 1;
|
|
791
|
+
if (s.ambiguity) ambiguityDist[s.ambiguity] = (ambiguityDist[s.ambiguity] || 0) + 1;
|
|
792
|
+
if (s.response) responseDist[s.response] = (responseDist[s.response] || 0) + 1;
|
|
793
|
+
if (s.cascades) totalCascades += parseInt(s.cascades, 10) || 0;
|
|
794
|
+
if (s.effort === "high") highEffortCount++;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
lines.push("### Session profile");
|
|
798
|
+
lines.push("");
|
|
799
|
+
lines.push(`- **${data.sessions.length} sessions** across ${data.workflows.length} workflow type(s)`);
|
|
800
|
+
|
|
801
|
+
const effortSummary = Object.entries(effortDist).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
802
|
+
if (effortSummary) lines.push(`- Effort distribution: ${effortSummary}`);
|
|
803
|
+
|
|
804
|
+
const ambiguitySummary = Object.entries(ambiguityDist).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
805
|
+
if (ambiguitySummary) lines.push(`- Ambiguity distribution: ${ambiguitySummary}`);
|
|
806
|
+
|
|
807
|
+
const responseSummary = Object.entries(responseDist).map(([k, v]) => `${k}: ${v}`).join(", ");
|
|
808
|
+
if (responseSummary) lines.push(`- Response outcomes: ${responseSummary}`);
|
|
809
|
+
|
|
810
|
+
lines.push(`- Total cascades triggered: ${totalCascades}`);
|
|
811
|
+
lines.push(`- High-effort sessions: ${highEffortCount}/${data.sessions.length}`);
|
|
812
|
+
lines.push("");
|
|
813
|
+
|
|
814
|
+
// Feature coverage
|
|
815
|
+
lines.push("### Feature coverage");
|
|
816
|
+
lines.push("");
|
|
817
|
+
if (data.features.length > 0) {
|
|
818
|
+
lines.push(`${data.features.length} features activated: ${data.features.join(", ")}`);
|
|
819
|
+
} else {
|
|
820
|
+
lines.push("No features explicitly tracked in session log.");
|
|
821
|
+
}
|
|
822
|
+
lines.push("");
|
|
823
|
+
|
|
824
|
+
// Methodology health
|
|
825
|
+
lines.push("### Methodology health signals");
|
|
826
|
+
lines.push("");
|
|
827
|
+
|
|
828
|
+
const acceptedCount = responseDist["accepted"] || 0;
|
|
829
|
+
const totalResponses = Object.values(responseDist).reduce((a, b) => a + b, 0);
|
|
830
|
+
if (totalResponses > 0) {
|
|
831
|
+
const acceptRate = Math.round((acceptedCount / totalResponses) * 100);
|
|
832
|
+
lines.push(`- Acceptance rate: ${acceptRate}% (${acceptedCount}/${totalResponses} sessions)`);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (data.frictionPoints.length > 0) {
|
|
836
|
+
lines.push(`- Friction points: ${data.frictionPoints.length} recorded`);
|
|
837
|
+
const frictionSessions = data.frictionPoints.map(f => `S${f.session}`).join(", ");
|
|
838
|
+
lines.push(` - Sessions with friction: ${frictionSessions}`);
|
|
839
|
+
} else {
|
|
840
|
+
lines.push("- No friction points recorded — either smooth workflow or under-reporting");
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (data.findings.length > 0) {
|
|
844
|
+
lines.push(`- Findings: ${data.findings.length} methodology-relevant observations`);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
lines.push(`- Decisions logged: ${data.decisions.length}`);
|
|
848
|
+
lines.push("");
|
|
849
|
+
|
|
850
|
+
// Lifecycle assessment
|
|
851
|
+
lines.push("### Lifecycle assessment");
|
|
852
|
+
lines.push("");
|
|
853
|
+
lines.push(`Project type \`${data.projectType}\` at lifecycle stage \`${data.lifecycleStart}\` using \`${data.templateTier}\` tier.`);
|
|
854
|
+
|
|
855
|
+
if (data.templateTier === "standard" && highEffortCount > 5) {
|
|
856
|
+
lines.push("Consider upgrading to **full** tier — high-effort session count suggests complex project needing REQUIREMENTS.md and REGISTRY.md.");
|
|
857
|
+
}
|
|
858
|
+
if (data.templateTier === "full" && data.sessions.length > 0 && highEffortCount === 0) {
|
|
859
|
+
lines.push("Full tier may be over-provisioned — no high-effort sessions recorded. Standard tier may suffice.");
|
|
860
|
+
}
|
|
861
|
+
lines.push("");
|
|
862
|
+
|
|
863
|
+
return lines.join("\n");
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function parseDocsMapForCaseStudy(content) {
|
|
867
|
+
const result = { inventory: [], mappings: [], scaffolding: [] };
|
|
868
|
+
|
|
869
|
+
const parseTable = (sectionName) => {
|
|
870
|
+
const re = new RegExp(
|
|
871
|
+
`## ${sectionName}[ \\t]*\\n\\n?\\|[^\\n]+\\n\\|[-| :]+\\n((?:\\|[^\\n]+\\n)*)`,
|
|
872
|
+
);
|
|
873
|
+
const m = content.match(re);
|
|
874
|
+
if (!m) return [];
|
|
875
|
+
return m[1].trim().split("\n").map((row) => {
|
|
876
|
+
const cols = row.split("|").map((c) => c.trim()).filter((c) => c && !c.startsWith("<!--"));
|
|
877
|
+
return cols;
|
|
878
|
+
}).filter((cols) => cols.length >= 2);
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
for (const cols of parseTable("Docs inventory")) {
|
|
882
|
+
result.inventory.push({ path: cols[0], purpose: cols[1], sources: cols[2] || "", status: cols[3] || "" });
|
|
883
|
+
}
|
|
884
|
+
for (const cols of parseTable("Component-to-docs mapping")) {
|
|
885
|
+
result.mappings.push({ component: cols[0], documentedIn: cols[1], trigger: cols[2] || "" });
|
|
886
|
+
}
|
|
887
|
+
for (const cols of parseTable("Scaffolding rules")) {
|
|
888
|
+
result.scaffolding.push({ condition: cols[0], proposedDoc: cols[1], seedFrom: cols[2] || "" });
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return result;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
async function writeCaseStudyYaml(yamlPath, data) {
|
|
895
|
+
try {
|
|
896
|
+
const yaml = (await import("js-yaml")).default;
|
|
897
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
898
|
+
|
|
899
|
+
// Compute session metrics summary
|
|
900
|
+
const effortDist = {};
|
|
901
|
+
const ambiguityDist = {};
|
|
902
|
+
const responseDist = {};
|
|
903
|
+
let totalCascades = 0;
|
|
904
|
+
let totalDecisionsSessions = 0;
|
|
905
|
+
|
|
906
|
+
for (const s of data.sessions) {
|
|
907
|
+
if (s.effort) effortDist[s.effort] = (effortDist[s.effort] || 0) + 1;
|
|
908
|
+
if (s.ambiguity) ambiguityDist[s.ambiguity] = (ambiguityDist[s.ambiguity] || 0) + 1;
|
|
909
|
+
if (s.response) responseDist[s.response] = (responseDist[s.response] || 0) + 1;
|
|
910
|
+
if (s.cascades) totalCascades += parseInt(s.cascades, 10) || 0;
|
|
911
|
+
if (s.decisions) totalDecisionsSessions += parseInt(s.decisions, 10) || 0;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
const structured = {
|
|
915
|
+
generated: today,
|
|
916
|
+
generator: "wwa casestudy",
|
|
917
|
+
overview: {
|
|
918
|
+
project_name: data.projectName,
|
|
919
|
+
project_type: data.projectType,
|
|
920
|
+
lifecycle_start: data.lifecycleStart,
|
|
921
|
+
lifecycle_end: data.lifecycleEnd,
|
|
922
|
+
template_tier: data.templateTier,
|
|
923
|
+
extensions: data.extensions,
|
|
924
|
+
entry_point: data.entryPointFile,
|
|
925
|
+
integration_profile: data.integrationProfile,
|
|
926
|
+
model_tier: data.modelTier,
|
|
927
|
+
method_version: data.methodVersion,
|
|
928
|
+
},
|
|
929
|
+
session_metrics: {
|
|
930
|
+
total_sessions: data.sessions.length,
|
|
931
|
+
effort_distribution: effortDist,
|
|
932
|
+
ambiguity_distribution: ambiguityDist,
|
|
933
|
+
response_distribution: responseDist,
|
|
934
|
+
total_cascades: totalCascades,
|
|
935
|
+
total_decisions_in_sessions: totalDecisionsSessions,
|
|
936
|
+
workflows_used: data.workflows,
|
|
937
|
+
features_activated: data.features,
|
|
938
|
+
},
|
|
939
|
+
sessions: data.sessions.map(s => ({
|
|
940
|
+
session: s.session,
|
|
941
|
+
date: s.date,
|
|
942
|
+
title: s.title,
|
|
943
|
+
model: s.model || null,
|
|
944
|
+
profile: s.profile || null,
|
|
945
|
+
effort: s.effort || null,
|
|
946
|
+
ambiguity: s.ambiguity || null,
|
|
947
|
+
context: s.context || null,
|
|
948
|
+
tokens: s.tokens || null,
|
|
949
|
+
tool_calls: s.tool_calls || null,
|
|
950
|
+
time: s.time || null,
|
|
951
|
+
workflow: s.workflow || null,
|
|
952
|
+
features: s.features || null,
|
|
953
|
+
cascades: s.cascades || null,
|
|
954
|
+
decisions: s.decisions || null,
|
|
955
|
+
response: s.response || null,
|
|
956
|
+
revisions: s.revisions || null,
|
|
957
|
+
magnitude: s.magnitude || null,
|
|
958
|
+
delta: s.delta || null,
|
|
959
|
+
survival: s.survival || null,
|
|
960
|
+
delta_notes: s.delta_notes || null,
|
|
961
|
+
friction: s.friction || null,
|
|
962
|
+
finding: s.finding || null,
|
|
963
|
+
})),
|
|
964
|
+
decisions: data.decisions,
|
|
965
|
+
friction_points: data.frictionPoints,
|
|
966
|
+
findings: data.findings,
|
|
967
|
+
token_registry: data.tokenRegistry,
|
|
968
|
+
name_mappings: data.nameMappings ? {
|
|
969
|
+
project_types: data.nameMappings.project_types || null,
|
|
970
|
+
guided_workflows: data.nameMappings.guided_workflows || null,
|
|
971
|
+
feature_domains: data.nameMappings.feature_domains || null,
|
|
972
|
+
protocol_directives: data.nameMappings.protocol_directives || null,
|
|
973
|
+
} : null,
|
|
974
|
+
docs_map: data.docsMap,
|
|
975
|
+
internal_registry: data.internalRegistry || null,
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
const yamlOutput = yaml.dump(structured, { lineWidth: 120, noRefs: true, quotingType: '"' });
|
|
979
|
+
const header = [
|
|
980
|
+
"# Case Study — structured session metrics and methodology data",
|
|
981
|
+
`# Project: ${data.projectName}`,
|
|
982
|
+
`# Generated: ${today} by wwa casestudy`,
|
|
983
|
+
"# Companion to: " + yamlPath.replace(/\.yaml$/, ".md"),
|
|
984
|
+
"",
|
|
985
|
+
].join("\n");
|
|
986
|
+
|
|
987
|
+
safeWriteFile(yamlPath, header + yamlOutput, "utf-8");
|
|
988
|
+
return true;
|
|
989
|
+
} catch (_) {
|
|
990
|
+
return false;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
function slugify(name) {
|
|
995
|
+
return name
|
|
996
|
+
.toLowerCase()
|
|
997
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
998
|
+
.replace(/^-|-$/g, "")
|
|
999
|
+
|| "project";
|
|
1000
|
+
}
|