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.
Files changed (108) hide show
  1. package/README.md +343 -0
  2. package/bin/wwa.js +115 -0
  3. package/docs/internal/cli-commands.yaml +259 -0
  4. package/docs/internal/doc-tokens.yaml +1103 -0
  5. package/docs/internal/feature-registry.yaml +1643 -0
  6. package/lib/boundaries.js +247 -0
  7. package/lib/cli/add.js +170 -0
  8. package/lib/cli/casestudy.js +1000 -0
  9. package/lib/cli/check.js +323 -0
  10. package/lib/cli/close.js +838 -0
  11. package/lib/cli/completion.js +735 -0
  12. package/lib/cli/deps.js +234 -0
  13. package/lib/cli/digest.js +73 -0
  14. package/lib/cli/doc-review.js +486 -0
  15. package/lib/cli/docs.js +315 -0
  16. package/lib/cli/helpers.js +198 -0
  17. package/lib/cli/implement.js +169 -0
  18. package/lib/cli/init.js +280 -0
  19. package/lib/cli/pipeline.js +206 -0
  20. package/lib/cli/plan.js +140 -0
  21. package/lib/cli/record.js +98 -0
  22. package/lib/cli/refine.js +202 -0
  23. package/lib/cli/report-helpers.js +113 -0
  24. package/lib/cli/review.js +76 -0
  25. package/lib/cli/routable.js +109 -0
  26. package/lib/cli/route.js +101 -0
  27. package/lib/cli/scan.js +133 -0
  28. package/lib/cli/serve.js +23 -0
  29. package/lib/cli/status.js +65 -0
  30. package/lib/cli/update-docs.js +574 -0
  31. package/lib/cli/upgrade.js +222 -0
  32. package/lib/cli/watch.js +32 -0
  33. package/lib/dependencies.js +196 -0
  34. package/lib/init.js +692 -0
  35. package/lib/mcp-server.js +612 -0
  36. package/lib/pipeline.js +907 -0
  37. package/lib/registry.js +132 -0
  38. package/lib/watcher.js +165 -0
  39. package/package.json +54 -0
  40. package/templates/README.md +363 -0
  41. package/templates/entry-points/.cursorrules +90 -0
  42. package/templates/entry-points/AGENT.md +90 -0
  43. package/templates/entry-points/CLAUDE.md +88 -0
  44. package/templates/extensions/MANIFEST.md +110 -0
  45. package/templates/extensions/analytical-system.md +96 -0
  46. package/templates/extensions/code-project.md +77 -0
  47. package/templates/extensions/data-exploration.md +117 -0
  48. package/templates/full/.context/BASE.md +101 -0
  49. package/templates/full/.context/COMPOSITION.md +47 -0
  50. package/templates/full/.context/INDEX.yaml +56 -0
  51. package/templates/full/.context/METHODOLOGY.md +246 -0
  52. package/templates/full/.context/PROTOCOL.yaml +169 -0
  53. package/templates/full/.context/REGISTRY.md +75 -0
  54. package/templates/full/.cursorrules +90 -0
  55. package/templates/full/AGENT.md +90 -0
  56. package/templates/full/CLAUDE.md +90 -0
  57. package/templates/full/Management/DIGEST.md +23 -0
  58. package/templates/full/Management/STATUS.md +46 -0
  59. package/templates/full/PLAN.md +67 -0
  60. package/templates/full/PROJECT-PROFILE.md +61 -0
  61. package/templates/full/PROJECT.md +80 -0
  62. package/templates/full/REQUIREMENTS.md +30 -0
  63. package/templates/full/ROADMAP.md +39 -0
  64. package/templates/full/Reviews/INDEX.md +41 -0
  65. package/templates/full/Reviews/backlog.md +52 -0
  66. package/templates/full/Reviews/plan.md +43 -0
  67. package/templates/full/Reviews/project.md +41 -0
  68. package/templates/full/Reviews/requirements.md +42 -0
  69. package/templates/full/Reviews/roadmap.md +41 -0
  70. package/templates/full/Reviews/state.md +56 -0
  71. package/templates/full/SESSION-LOG.md +102 -0
  72. package/templates/full/STATE.md +42 -0
  73. package/templates/full/SUMMARY.md +27 -0
  74. package/templates/full/agentWorkflows/INDEX.md +42 -0
  75. package/templates/full/agentWorkflows/observations.md +65 -0
  76. package/templates/full/agentWorkflows/patterns.md +68 -0
  77. package/templates/full/agentWorkflows/sessions.md +92 -0
  78. package/templates/full/intro/README.md +39 -0
  79. package/templates/full/registry/feature-registry.yaml +25 -0
  80. package/templates/full/registry/features/catalog.yaml +743 -0
  81. package/templates/full/registry/features/protocol.yaml +121 -0
  82. package/templates/full/registry/features/routing.yaml +358 -0
  83. package/templates/full/registry/features/workflows.yaml +404 -0
  84. package/templates/full/todos/backlog.md +19 -0
  85. package/templates/starter/.context/BASE.md +66 -0
  86. package/templates/starter/.context/INDEX.yaml +51 -0
  87. package/templates/starter/.context/METHODOLOGY.md +228 -0
  88. package/templates/starter/.context/PROTOCOL.yaml +165 -0
  89. package/templates/starter/.cursorrules +90 -0
  90. package/templates/starter/AGENT.md +90 -0
  91. package/templates/starter/CLAUDE.md +90 -0
  92. package/templates/starter/Management/DIGEST.md +23 -0
  93. package/templates/starter/Management/STATUS.md +46 -0
  94. package/templates/starter/PLAN.md +67 -0
  95. package/templates/starter/PROJECT-PROFILE.md +44 -0
  96. package/templates/starter/PROJECT.md +80 -0
  97. package/templates/starter/ROADMAP.md +39 -0
  98. package/templates/starter/Reviews/INDEX.md +75 -0
  99. package/templates/starter/SESSION-LOG.md +102 -0
  100. package/templates/starter/STATE.md +42 -0
  101. package/templates/starter/SUMMARY.md +27 -0
  102. package/templates/starter/agentWorkflows/INDEX.md +61 -0
  103. package/templates/starter/intro/README.md +37 -0
  104. package/templates/starter/registry/feature-registry.yaml +25 -0
  105. package/templates/starter/registry/features/catalog.yaml +743 -0
  106. package/templates/starter/registry/features/protocol.yaml +121 -0
  107. package/templates/starter/registry/features/routing.yaml +358 -0
  108. package/templates/starter/registry/features/workflows.yaml +404 -0
package/lib/init.js ADDED
@@ -0,0 +1,692 @@
1
+ /**
2
+ * Interactive project setup — Node.js port of setup.sh.
3
+ *
4
+ * Creates a new methodology instance from templates with interactive
5
+ * prompts for project configuration.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, copyFileSync, writeFileSync } from "node:fs";
9
+ import { resolve, join, dirname, basename, relative } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { safeCopyFile, safeWriteFile } from "./cli/helpers.js";
12
+ import { buildDefaultNodes, buildDefaultEdges } from "./dependencies.js";
13
+
14
+ /** Entry point filenames that bypass the extension-based safety check. */
15
+ const ENTRY_POINT_NAMES = new Set(["CLAUDE.md", ".cursorrules", "AGENT.md"]);
16
+
17
+ /** Write file — allows entry points (e.g. .cursorrules) plus safe extensions. */
18
+ function writeInitFile(filePath, content, encoding = "utf-8") {
19
+ const name = basename(filePath);
20
+ if (ENTRY_POINT_NAMES.has(name)) {
21
+ writeFileSync(filePath, content, encoding);
22
+ } else {
23
+ safeWriteFile(filePath, content, encoding);
24
+ }
25
+ }
26
+
27
+ const __filename = fileURLToPath(import.meta.url);
28
+ const __dirname = dirname(__filename);
29
+
30
+ const EXTENSION_MAP = {
31
+ code: "code-project.md",
32
+ data: "data-exploration.md",
33
+ analytical: "analytical-system.md",
34
+ };
35
+
36
+ const FRIENDLY_NAMES = {
37
+ code: "Code project",
38
+ data: "Data exploration",
39
+ analytical: "Analytical/prompt system",
40
+ mixed: "Multi-type project",
41
+ general: "General project",
42
+ };
43
+
44
+ const RUNTIME_ENTRY_POINTS = {
45
+ claude: "CLAUDE.md",
46
+ cursor: ".cursorrules",
47
+ all: null, // keep all
48
+ };
49
+
50
+ /**
51
+ * Set up a new project with methodology templates.
52
+ */
53
+ export async function initProject(projectType, directory, opts = {}) {
54
+ const { tier = "starter", runtime = "all", profile = "standard", onboarding = "auto", registryPath } = opts;
55
+ const targetDir = resolve(directory);
56
+ const methodRoot = resolve(__dirname, "..");
57
+
58
+ const templateDir = join(methodRoot, "templates", tier);
59
+ if (!existsSync(templateDir)) {
60
+ console.error(`Template directory not found: ${templateDir}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
65
+ console.log(` Template: ${tier}`);
66
+ console.log(` Profile: ${profile}`);
67
+ console.log(` Runtime: ${runtime}`);
68
+ console.log(` Onboarding: ${onboarding}`);
69
+ console.log(` Target: ${targetDir}\n`);
70
+
71
+ // Determine brownfield mode from user selection or auto-detection
72
+ let isBrownfield;
73
+ if (onboarding === "brownfield") {
74
+ isBrownfield = true;
75
+ } else if (onboarding === "greenfield") {
76
+ isBrownfield = false;
77
+ } else {
78
+ // auto — detect from existing files
79
+ isBrownfield = detectBrownfield(targetDir);
80
+ }
81
+
82
+ if (isBrownfield) {
83
+ console.log(" Brownfield mode — will append to existing entry points, not overwrite.\n");
84
+ }
85
+
86
+ // Create target directory
87
+ mkdirSync(targetDir, { recursive: true });
88
+
89
+ // Copy template files
90
+ const copied = [];
91
+ const skipped = [];
92
+ copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield, runtime);
93
+
94
+ // Phase 7q: Ensure .context/ exists with PROTOCOL and DOCS-MAP (avoid post-init validation failures)
95
+ ensureContextDirectory(methodRoot, templateDir, targetDir, tier, copied);
96
+
97
+ // Apply extensions based on project type
98
+ const extensions = getExtensions(projectType);
99
+ for (const ext of extensions) {
100
+ const extFile = join(methodRoot, "templates", "extensions", ext);
101
+ if (existsSync(extFile)) {
102
+ mergeExtension(extFile, targetDir, isBrownfield);
103
+ copied.push(`extension: ${ext}`);
104
+ }
105
+ }
106
+
107
+ // Copy feature-registry.yaml to user project (populated, from package)
108
+ copyFeatureRegistry(methodRoot, targetDir, copied);
109
+
110
+ // Create project-specific doc-tokens.yaml with initial values
111
+ createProjectTokens(targetDir, projectType, tier, profile, onboarding, copied);
112
+
113
+ // Post-process PROTOCOL.yaml and INDEX.yaml (replace date placeholders)
114
+ postProcessYamlFiles(targetDir, projectType, tier);
115
+
116
+ // Replace placeholders in entry points (Phase 7q: project name and description)
117
+ replacePlaceholders(targetDir, projectType, tier, opts);
118
+
119
+ // Set integration profile in entry points
120
+ setProfile(targetDir, profile);
121
+
122
+ // Set onboarding scenario in PROJECT-PROFILE.md
123
+ setOnboardingScenario(targetDir, onboarding, isBrownfield, projectType);
124
+
125
+ // Phase 7r: Persist brownfield questionnaire to PROJECT-PROFILE.md when provided
126
+ if (opts.brownfieldQuestionnaire) {
127
+ persistBrownfieldQuestionnaire(targetDir, opts.brownfieldQuestionnaire);
128
+ }
129
+
130
+ // Remove unused entry points based on runtime choice
131
+ const removed = removeUnusedEntryPoints(targetDir, runtime, isBrownfield);
132
+
133
+ // Report
134
+ console.log(` Files created: ${copied.length}`);
135
+ for (const f of copied) console.log(` + ${f}`);
136
+ if (removed.length > 0) {
137
+ console.log(` Entry points removed: ${removed.length}`);
138
+ for (const f of removed) console.log(` - ${f}`);
139
+ }
140
+ if (skipped.length > 0) {
141
+ console.log(` Files skipped (already exist): ${skipped.length}`);
142
+ for (const f of skipped) console.log(` ~ ${f}`);
143
+ }
144
+
145
+ // Determine which entry point the user kept
146
+ const keptEntry = runtime === "claude" ? "CLAUDE.md"
147
+ : runtime === "cursor" ? ".cursorrules"
148
+ : "CLAUDE.md / .cursorrules / AGENT.md";
149
+
150
+ console.log(`\nNext steps:`);
151
+ let step = 1;
152
+ console.log(` ${step++}. cd ${relative(process.cwd(), targetDir) || "."}`);
153
+ if (runtime === "all") {
154
+ console.log(` ${step++}. Delete the two entry point files you don't use`);
155
+ console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
156
+ }
157
+ console.log(` ${step++}. Start a conversation with your agent — it reads ${keptEntry} automatically`);
158
+ if (isBrownfield) {
159
+ console.log(` The agent will scan your codebase and walk you through 5 onboarding stages`);
160
+ console.log(` (scan → clarify → map → organize → starting state)`);
161
+ } else {
162
+ console.log(` The agent will walk you through 4 onboarding stages`);
163
+ console.log(` (vision → architecture → goals → starting state)`);
164
+ }
165
+ console.log(`\nVerify: wwa check`);
166
+ }
167
+
168
+ function detectBrownfield(dir) {
169
+ if (!existsSync(dir)) return false;
170
+ const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
171
+ let count = 0;
172
+ for (const ind of indicators) {
173
+ if (existsSync(join(dir, ind))) count++;
174
+ }
175
+ return count >= 2;
176
+ }
177
+
178
+ /** Phase 7q: Ensure .context/ exists with PROTOCOL.yaml and DOCS-MAP.md so wwa check can pass. */
179
+ function ensureContextDirectory(methodRoot, templateDir, targetDir, tier, copied) {
180
+ const contextDir = join(targetDir, ".context");
181
+ mkdirSync(contextDir, { recursive: true });
182
+
183
+ const protocolPath = join(targetDir, ".context", "PROTOCOL.yaml");
184
+ if (!existsSync(protocolPath)) {
185
+ const srcProtocol = join(templateDir, ".context", "PROTOCOL.yaml");
186
+ if (existsSync(srcProtocol)) {
187
+ safeCopyFile(srcProtocol, protocolPath);
188
+ copied.push(".context/PROTOCOL.yaml (created)");
189
+ }
190
+ }
191
+
192
+ const docsMapPath = join(targetDir, ".context", "DOCS-MAP.md");
193
+ if (!existsSync(docsMapPath)) {
194
+ const docsMapContent = [
195
+ "# DOCS-MAP — component-to-docs mapping",
196
+ "",
197
+ "Populated during first-session onboarding. See METHODOLOGY.md for scaffolding rules.",
198
+ "",
199
+ "| Component | Docs path |",
200
+ "|-----------|-----------|",
201
+ "| (add during onboarding) | docs/ |",
202
+ "",
203
+ ].join("\n");
204
+ safeWriteFile(docsMapPath, docsMapContent, "utf-8");
205
+ copied.push(".context/DOCS-MAP.md (created)");
206
+ }
207
+ }
208
+
209
+ function copyTemplateDir(src, dst, copied, skipped, isBrownfield, runtime) {
210
+ if (!existsSync(src)) return;
211
+ mkdirSync(dst, { recursive: true });
212
+
213
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
214
+ const srcPath = join(src, entry.name);
215
+ const dstPath = join(dst, entry.name);
216
+
217
+ if (entry.isDirectory()) {
218
+ copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield, runtime);
219
+ } else {
220
+ // Entry points: special handling in brownfield
221
+ const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
222
+
223
+ // Skip entry points that won't be kept (avoids safety invariant issue with .cursorrules)
224
+ if (isEntryPoint && runtime !== "all" && !isBrownfield) {
225
+ const keep = RUNTIME_ENTRY_POINTS[runtime];
226
+ if (keep && entry.name !== keep) continue;
227
+ }
228
+
229
+ // Defer heavy infrastructure files in brownfield to reduce first-session load
230
+ const isDeferredInBrownfield = isBrownfield && entry.name === "REGISTRY.md";
231
+ if (isDeferredInBrownfield) {
232
+ skipped.push(`${relative(dst, dstPath)} (deferred — created on first file split)`);
233
+ continue;
234
+ }
235
+
236
+ if (existsSync(dstPath)) {
237
+ if (isEntryPoint && isBrownfield) {
238
+ // Append methodology sections to existing entry point
239
+ appendToEntryPoint(srcPath, dstPath);
240
+ copied.push(`${relative(dst, dstPath)} (merged)`);
241
+ } else {
242
+ skipped.push(relative(dst, dstPath));
243
+ }
244
+ } else if (isEntryPoint) {
245
+ // Entry points bypass safeCopyFile (e.g. .cursorrules has no .md extension)
246
+ copyFileSync(srcPath, dstPath);
247
+ copied.push(relative(dst, dstPath));
248
+ } else {
249
+ safeCopyFile(srcPath, dstPath);
250
+ copied.push(relative(dst, dstPath));
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ function appendToEntryPoint(srcPath, dstPath) {
257
+ const srcContent = readFileSync(srcPath, "utf-8");
258
+ let dstContent = readFileSync(dstPath, "utf-8");
259
+
260
+ // Extract sections from template that don't exist in target
261
+ const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
262
+ for (const section of sections) {
263
+ if (!dstContent.includes(section) && srcContent.includes(section)) {
264
+ const idx = srcContent.indexOf(section);
265
+ // Find the next section or end
266
+ let endIdx = srcContent.length;
267
+ for (const nextSection of sections) {
268
+ const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
269
+ if (nextIdx > idx && nextIdx < endIdx) {
270
+ endIdx = nextIdx;
271
+ }
272
+ }
273
+ const sectionContent = srcContent.slice(idx, endIdx).trim();
274
+ dstContent += "\n\n" + sectionContent;
275
+ }
276
+ }
277
+
278
+ writeInitFile(dstPath, dstContent, "utf-8");
279
+ }
280
+
281
+ function getExtensions(projectType) {
282
+ if (projectType === "mixed") {
283
+ return Object.values(EXTENSION_MAP);
284
+ }
285
+ const ext = EXTENSION_MAP[projectType];
286
+ return ext ? [ext] : [];
287
+ }
288
+
289
+ function mergeExtension(extFile, targetDir, isBrownfield) {
290
+ const content = readFileSync(extFile, "utf-8");
291
+
292
+ // Extract scoping rows and cascade rows from extension file
293
+ const scopingRows = [];
294
+ const cascadeRows = [];
295
+ let inScoping = false;
296
+ let inCascade = false;
297
+
298
+ for (const line of content.split("\n")) {
299
+ if (line.includes("## Scoping")) inScoping = true;
300
+ if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
301
+ inCascade = true;
302
+ if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
303
+ inScoping = false;
304
+ inCascade = false;
305
+ }
306
+
307
+ if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
308
+ scopingRows.push(line);
309
+ }
310
+ if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
311
+ cascadeRows.push(line);
312
+ }
313
+ }
314
+
315
+ // Inject rows into entry points
316
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
317
+ const epPath = join(targetDir, epName);
318
+ if (!existsSync(epPath)) continue;
319
+
320
+ let epContent = readFileSync(epPath, "utf-8");
321
+
322
+ // Inject scoping rows before the instruction comment or at end of table
323
+ if (scopingRows.length > 0) {
324
+ const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
325
+ if (epContent.includes(placeholder)) {
326
+ epContent = epContent.replace(
327
+ placeholder,
328
+ scopingRows.join("\n") + "\n" + placeholder
329
+ );
330
+ }
331
+ }
332
+
333
+ // Inject cascade rows
334
+ if (cascadeRows.length > 0) {
335
+ const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
336
+ if (epContent.includes(placeholder)) {
337
+ epContent = epContent.replace(
338
+ placeholder,
339
+ cascadeRows.join("\n") + "\n" + placeholder
340
+ );
341
+ }
342
+ }
343
+
344
+ writeInitFile(epPath, epContent, "utf-8");
345
+ }
346
+ }
347
+
348
+ function replacePlaceholders(targetDir, projectType, tier, opts = {}) {
349
+ const projectName = opts.projectName || `${FRIENDLY_NAMES[projectType] || projectType} project`;
350
+ const defaultDescriptions = {
351
+ code: "Software project with AI-agent-assisted development (wwa methodology).",
352
+ data: "Data exploration and indexing project with AI-agent-assisted development.",
353
+ analytical: "Analytical/prompt system with chains, evaluation, and composition (wwa methodology).",
354
+ context: "Analytical/prompt system with chains, evaluation, and composition (wwa methodology).",
355
+ mixed: "Multi-type project with AI-agent-assisted development.",
356
+ general: "Project using wwa methodology for agent-assisted development.",
357
+ };
358
+ const description = opts.description || defaultDescriptions[projectType] || "AI-agent-assisted project using wwa methodology.";
359
+
360
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
361
+ const epPath = join(targetDir, epName);
362
+ if (!existsSync(epPath)) continue;
363
+
364
+ let content = readFileSync(epPath, "utf-8");
365
+
366
+ // Replace project type placeholder
367
+ content = content.replace(/\{your-project-type\}/g, projectType);
368
+ content = content.replace(/\{your-domain-type\}/g, projectType);
369
+
370
+ // Set template tier
371
+ content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
372
+ // Phase 7q: Reduce placeholder text in entry point
373
+ content = content.replace(/\{Project Name\}/g, projectName);
374
+ content = content.replace(/\{Describe what your project does[^}]*\}/g, description);
375
+ content = content.replace(/\{Describe what your project does, its architecture, and key technologies\.\}/g, description);
376
+
377
+ writeInitFile(epPath, content, "utf-8");
378
+ }
379
+ }
380
+
381
+ function setProfile(targetDir, profile) {
382
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
383
+ const epPath = join(targetDir, epName);
384
+ if (!existsSync(epPath)) continue;
385
+
386
+ let content = readFileSync(epPath, "utf-8");
387
+ content = content.replace(/tier:\s*(lite|standard|full)/, `tier: ${profile}`);
388
+ writeInitFile(epPath, content, "utf-8");
389
+ }
390
+ }
391
+
392
+ function setOnboardingScenario(targetDir, onboarding, isBrownfield, projectType) {
393
+ const profilePath = join(targetDir, "PROJECT-PROFILE.md");
394
+ if (!existsSync(profilePath)) return;
395
+
396
+ let content = readFileSync(profilePath, "utf-8");
397
+
398
+ // Set lifecycle stage based on scenario
399
+ const stage = isBrownfield ? "discovery" : "design";
400
+ content = content.replace(/\*\*Stage\*\*:\s*\{stage\}/, `**Stage**: ${stage}`);
401
+ content = content.replace(/\*\*Maturity\*\*:\s*\{maturity\}/, `**Maturity**: new`);
402
+
403
+ const today = new Date().toISOString().slice(0, 10);
404
+ content = content.replace(
405
+ /\*\*Last transition\*\*:\s*\{date\}.*$/m,
406
+ `**Last transition**: ${today} (setup -> ${stage})`
407
+ );
408
+
409
+ // Set project type
410
+ content = content.replace(/\[{project-type}\]/, `[${projectType}]`);
411
+
412
+ // Set onboarding hint so the agent knows which first-session flow
413
+ const scenario = onboarding === "auto"
414
+ ? (isBrownfield ? "brownfield (auto-detected)" : "greenfield (auto-detected)")
415
+ : onboarding;
416
+
417
+ // Add onboarding scenario after lifecycle section
418
+ if (!content.includes("**Onboarding**")) {
419
+ content = content.replace(
420
+ /(\*\*Last transition\*\*:.*$)/m,
421
+ `$1\n- **Onboarding**: ${scenario}`
422
+ );
423
+ }
424
+
425
+ // Set extension stack
426
+ const extensions = getExtensions(projectType);
427
+ const extNames = extensions.map(e => e.replace(".md", ""));
428
+ content = content.replace(/\[{extensions-applied}\]/, `[${extNames.join(", ") || "none"}]`);
429
+
430
+ for (const ext of ["code-project", "data-exploration", "analytical-system"]) {
431
+ const isActive = extNames.includes(ext) ? "yes" : "no";
432
+ const source = extNames.includes(ext) ? "wwa init" : "—";
433
+ content = content.replace(
434
+ new RegExp(`\\| ${ext} \\| \\{yes/no\\} \\| \\{setup\\.sh / manual\\} \\|`),
435
+ `| ${ext} | ${isActive} | ${source} |`
436
+ );
437
+ }
438
+
439
+ safeWriteFile(profilePath, content, "utf-8");
440
+ }
441
+
442
+ /**
443
+ * Append Brownfield setup section to PROJECT-PROFILE.md (Phase 7r).
444
+ * @param {string} targetDir - Project root
445
+ * @param {{ projectPurpose: string, audience: string, specLocation: string }} q - Questionnaire answers
446
+ */
447
+ function persistBrownfieldQuestionnaire(targetDir, q) {
448
+ const profilePath = join(targetDir, "PROJECT-PROFILE.md");
449
+ if (!existsSync(profilePath) || !q) return;
450
+
451
+ const section = `
452
+
453
+ ## Brownfield setup
454
+
455
+ - **Project purpose**: ${q.projectPurpose}
456
+ - **Primary audience**: ${q.audience}
457
+ - **Methodology spec**: ${q.specLocation}
458
+ `;
459
+ const content = readFileSync(profilePath, "utf-8");
460
+ if (content.includes("## Brownfield setup")) return;
461
+ safeWriteFile(profilePath, content.trimEnd() + section + "\n", "utf-8");
462
+ }
463
+
464
+ function createProjectTokens(targetDir, projectType, tier, profile, onboarding, copied) {
465
+ const regDir = join(targetDir, "registry");
466
+ mkdirSync(regDir, { recursive: true });
467
+ const dst = join(regDir, "doc-tokens.yaml");
468
+ if (existsSync(dst)) return; // don't overwrite
469
+
470
+ const today = new Date().toISOString().slice(0, 10);
471
+
472
+ // Count files that were actually created
473
+ const mdFiles = [];
474
+ const countMdFiles = (dir) => {
475
+ if (!existsSync(dir)) return;
476
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
477
+ const p = join(dir, entry.name);
478
+ if (entry.isDirectory()) countMdFiles(p);
479
+ else if (entry.name.endsWith(".md") || entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) {
480
+ mdFiles.push(relative(targetDir, p).replace(/\\/g, "/"));
481
+ }
482
+ }
483
+ };
484
+ countMdFiles(targetDir);
485
+
486
+ const content = [
487
+ "# Project Token Registry",
488
+ "# Computed by wwa init. Updated by wwa close.",
489
+ "# Used by wwa casestudy for methodology reference data.",
490
+ "#",
491
+ "# Agent: read this before writing docs. Check values are current.",
492
+ "",
493
+ "tokens:",
494
+ "",
495
+ " # --- Project identity ---",
496
+ ` project_type: ${projectType}`,
497
+ ` template_tier: ${tier}`,
498
+ ` integration_profile: ${profile}`,
499
+ ` onboarding: ${onboarding}`,
500
+ ` created: "${today}"`,
501
+ ` last_close: "${today}"`,
502
+ "",
503
+ " # --- File counts (updated at close) ---",
504
+ ` methodology_file_count: ${mdFiles.length}`,
505
+ " docs_file_count: 0",
506
+ " context_file_count: 0",
507
+ "",
508
+ " # --- Session metrics (updated at close) ---",
509
+ " session_count: 0",
510
+ " decision_count: 0",
511
+ " total_features_referenced: 0",
512
+ "",
513
+ " # --- Scale indicators (updated at close) ---",
514
+ " files_near_threshold: 0 # files with 250+ lines",
515
+ " files_over_threshold: 0 # files with 300+ lines (need splitting)",
516
+ "",
517
+ " # --- Docs coverage (updated at close) ---",
518
+ " docs_inventory_count: 0",
519
+ " docs_on_disk: 0",
520
+ " docs_missing: 0",
521
+ " component_mappings: 0",
522
+ "",
523
+ "# --- Document dependency graph (built at init, refined during onboarding, updated at close) ---",
524
+ "doc_graph:",
525
+ ` last_scan: "${today}"`,
526
+ ` defaults_applied: ${projectType}`,
527
+ "",
528
+ " nodes:",
529
+ ];
530
+
531
+ // Seed nodes from methodology file inventory
532
+ const defaultNodes = buildDefaultNodes(tier);
533
+ for (const node of defaultNodes) {
534
+ content.push(` - {path: "${node.path}", category: "${node.category}", role: "${node.role}"}`);
535
+ }
536
+
537
+ content.push("", " edges:");
538
+
539
+ // Seed edges from project-type defaults
540
+ const defaultEdges = buildDefaultEdges(projectType);
541
+ for (const edge of defaultEdges) {
542
+ let line = ` - {from: "${edge.from}", to: "${edge.to}", type: "${edge.type}"`;
543
+ if (edge.trigger) line += `, trigger: "${edge.trigger}"`;
544
+ line += "}";
545
+ content.push(line);
546
+ }
547
+
548
+ content.push(
549
+ "",
550
+ "# --- Project-specific terminology (populated during brownfield onboarding) ---",
551
+ "project_names:",
552
+ " modules: []",
553
+ " apis: []",
554
+ " entities: []",
555
+ " conventions: []",
556
+ "",
557
+ );
558
+
559
+ const finalContent = content.join("\n");
560
+
561
+ safeWriteFile(dst, finalContent, "utf-8");
562
+ copied.push("registry/doc-tokens.yaml");
563
+ }
564
+
565
+ function postProcessYamlFiles(targetDir, projectType, tier) {
566
+ const today = new Date().toISOString().slice(0, 10);
567
+
568
+ // Post-process PROTOCOL.yaml — replace {date}, enable boundaries (Phase 7q), add analytical scoping if needed
569
+ const protocolPath = join(targetDir, ".context", "PROTOCOL.yaml");
570
+ if (existsSync(protocolPath)) {
571
+ let content = readFileSync(protocolPath, "utf-8");
572
+ content = content.replace(/"\{date\}"/g, `"${today}"`);
573
+ content = content.replace(/\{date\}/g, today);
574
+ // Phase 7q: Auto-enable boundaries so V-11 passes (paths match init-created files)
575
+ content = enableBoundariesInProtocol(content, tier);
576
+ // Phase 7q: Add project-type-specific scoping for analytical
577
+ if (projectType === "analytical" || projectType === "context") {
578
+ content = addAnalyticalScopingToProtocol(content);
579
+ }
580
+ safeWriteFile(protocolPath, content, "utf-8");
581
+ }
582
+
583
+ // Post-process INDEX.yaml — replace placeholders
584
+ const indexPath = join(targetDir, ".context", "INDEX.yaml");
585
+ if (existsSync(indexPath)) {
586
+ let content = readFileSync(indexPath, "utf-8");
587
+ content = content.replace(/"\{Project Name\}"/g, `"${projectType} project"`);
588
+ content = content.replace(/\{Project Name\}/g, `${projectType} project`);
589
+ content = content.replace(/type: general/,
590
+ `type: ${projectType === "context" ? "analytical" : projectType}`);
591
+ content = content.replace(/tier: (standard|full)/,
592
+ `tier: ${tier}`);
593
+ safeWriteFile(indexPath, content, "utf-8");
594
+ }
595
+ }
596
+
597
+ /** Phase 7q: Uncomment boundaries in PROTOCOL so validator can resolve registry paths. */
598
+ function enableBoundariesInProtocol(content, tier) {
599
+ const commentedBlock = /# === BOUNDARIES: path resolution[\s\S]*?# exclude: \[".context\/", "docs\/internal\/"\]\n?/;
600
+ const fullTier = tier === "full";
601
+ const boundariesYaml = [
602
+ "# === BOUNDARIES: path resolution, visibility, distribution (enabled by wwa init) ===",
603
+ "boundaries:",
604
+ " registries:",
605
+ ' tokens: "registry/doc-tokens.yaml"',
606
+ ' feature_registry: "registry/feature-registry.yaml"',
607
+ ' docs_map: ".context/DOCS-MAP.md"',
608
+ ' output: "docs/internal/"',
609
+ " visibility:",
610
+ fullTier
611
+ ? ' internal: [".context/", "registry/", "research/", "todos/", "docs/internal/"]'
612
+ : ' internal: [".context/", "research/", "todos/", "docs/internal/"]',
613
+ fullTier
614
+ ? ' release: ["docs/architecture/", "docs/workflow/", "docs/guides/", "templates/"]'
615
+ : ' release: ["docs/architecture/", "docs/workflow/", "docs/guides/", "templates/"]',
616
+ " scan:",
617
+ fullTier
618
+ ? ' include: ["docs/", ".context/", "registry/", "templates/"]'
619
+ : ' include: ["docs/", ".context/", "templates/"]',
620
+ ' exclude: ["node_modules/", ".git/"]',
621
+ " distribution:",
622
+ " git:",
623
+ ' exclude: ["STATE.md", "PLAN.md", ".context/", "research/", "todos/"]',
624
+ " npm:",
625
+ ' include: ["bin/", "lib/", "templates/"]',
626
+ ' exclude: [".context/", "docs/internal/"]',
627
+ "",
628
+ ].join("\n");
629
+ return content.replace(commentedBlock, boundariesYaml);
630
+ }
631
+
632
+ /** Phase 7q: Add analytical query types to PROTOCOL scoping (chain_work, evaluation, composition, domain_research). */
633
+ function addAnalyticalScopingToProtocol(content) {
634
+ const injectBefore = " # CUSTOMIZE: add project-specific query types below";
635
+ const analyticalBlock = [
636
+ " chain_work:",
637
+ " reads: [.context/BASE.md, .context/EXECUTION.md, pipeline config]",
638
+ " writes: [.context/EXECUTION.md, .context/BASE.md]",
639
+ " evaluation:",
640
+ " reads: [.context/BASE.md, .context/EVALUATION.md, scoring rubrics]",
641
+ " writes: [.context/EVALUATION.md]",
642
+ " composition:",
643
+ " reads: [.context/BASE.md, .context/COMPOSITION.md, existing templates]",
644
+ " writes: [.context/COMPOSITION.md]",
645
+ " domain_research:",
646
+ " reads: [.context/BASE.md, .context/DOMAIN.md]",
647
+ " writes: [.context/DOMAIN.md, STATE.md]",
648
+ " ",
649
+ injectBefore,
650
+ ].join("\n");
651
+ if (content.includes(injectBefore) && !content.includes("chain_work:")) {
652
+ content = content.replace(injectBefore, analyticalBlock);
653
+ }
654
+ return content;
655
+ }
656
+
657
+ function copyFeatureRegistry(methodRoot, targetDir, copied) {
658
+ // Split registry ships in templates — already copied by copyTemplateDir().
659
+ // Only need to handle fallback if registry/ wasn't in the template.
660
+ const splitIndex = join(targetDir, "registry", "feature-registry.yaml");
661
+ if (existsSync(splitIndex)) return; // already copied from template
662
+
663
+ // Fallback: copy monolithic file to registry/ (for older templates)
664
+ const src = join(methodRoot, "docs", "internal", "feature-registry.yaml");
665
+ if (!existsSync(src)) return;
666
+ const regDir = join(targetDir, "registry");
667
+ mkdirSync(regDir, { recursive: true });
668
+ const dst = join(regDir, "feature-registry.yaml");
669
+ if (existsSync(dst)) return;
670
+ safeCopyFile(src, dst);
671
+ copied.push("registry/feature-registry.yaml");
672
+ }
673
+
674
+ function removeUnusedEntryPoints(targetDir, runtime, isBrownfield) {
675
+ if (runtime === "all" || isBrownfield) return [];
676
+
677
+ const allEntryPoints = ["CLAUDE.md", ".cursorrules", "AGENT.md"];
678
+ const keep = RUNTIME_ENTRY_POINTS[runtime];
679
+ if (!keep) return [];
680
+
681
+ const removed = [];
682
+ for (const ep of allEntryPoints) {
683
+ if (ep !== keep) {
684
+ const epPath = join(targetDir, ep);
685
+ if (existsSync(epPath)) {
686
+ unlinkSync(epPath);
687
+ removed.push(ep);
688
+ }
689
+ }
690
+ }
691
+ return removed;
692
+ }