agent-method 1.5.3 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +197 -57
  2. package/bin/wwa.js +35 -9
  3. package/docs/internal/doc-tokens.yaml +452 -0
  4. package/docs/internal/feature-registry.yaml +13 -1
  5. package/lib/cli/casestudy.js +691 -0
  6. package/lib/cli/check.js +71 -71
  7. package/lib/cli/close.js +446 -0
  8. package/lib/cli/completion.js +639 -0
  9. package/lib/cli/digest.js +66 -0
  10. package/lib/cli/docs.js +207 -0
  11. package/lib/cli/helpers.js +49 -2
  12. package/lib/cli/implement.js +159 -0
  13. package/lib/cli/init.js +25 -6
  14. package/lib/cli/plan.js +128 -0
  15. package/lib/cli/refine.js +202 -202
  16. package/lib/cli/review.js +68 -0
  17. package/lib/cli/scan.js +28 -28
  18. package/lib/cli/status.js +61 -61
  19. package/lib/cli/upgrade.js +150 -147
  20. package/lib/init.js +478 -296
  21. package/package.json +12 -4
  22. package/templates/README.md +73 -25
  23. package/templates/entry-points/.cursorrules +143 -14
  24. package/templates/entry-points/AGENT.md +143 -14
  25. package/templates/entry-points/CLAUDE.md +143 -14
  26. package/templates/extensions/analytical-system.md +1 -1
  27. package/templates/extensions/code-project.md +1 -1
  28. package/templates/extensions/data-exploration.md +1 -1
  29. package/templates/full/.context/BASE.md +33 -0
  30. package/templates/full/.context/METHODOLOGY.md +62 -5
  31. package/templates/full/.cursorrules +128 -18
  32. package/templates/full/AGENT.md +128 -18
  33. package/templates/full/CLAUDE.md +128 -18
  34. package/templates/full/Management/DIGEST.md +23 -0
  35. package/templates/full/Management/STATUS.md +46 -0
  36. package/templates/full/PROJECT.md +34 -0
  37. package/templates/full/Reviews/INDEX.md +41 -0
  38. package/templates/full/Reviews/backlog.md +52 -0
  39. package/templates/full/Reviews/plan.md +43 -0
  40. package/templates/full/Reviews/project.md +41 -0
  41. package/templates/full/Reviews/requirements.md +42 -0
  42. package/templates/full/Reviews/roadmap.md +41 -0
  43. package/templates/full/Reviews/state.md +56 -0
  44. package/templates/full/SESSION-LOG.md +29 -0
  45. package/templates/full/SUMMARY.md +7 -4
  46. package/templates/full/agentWorkflows/INDEX.md +42 -0
  47. package/templates/full/agentWorkflows/observations.md +65 -0
  48. package/templates/full/agentWorkflows/patterns.md +68 -0
  49. package/templates/full/agentWorkflows/sessions.md +92 -0
  50. package/templates/full/intro/README.md +39 -0
  51. package/templates/starter/.context/BASE.md +35 -0
  52. package/templates/starter/.context/METHODOLOGY.md +59 -5
  53. package/templates/starter/.cursorrules +135 -13
  54. package/templates/starter/AGENT.md +135 -13
  55. package/templates/starter/CLAUDE.md +135 -13
  56. package/templates/starter/Management/DIGEST.md +23 -0
  57. package/templates/starter/Management/STATUS.md +46 -0
  58. package/templates/starter/PROJECT.md +34 -0
  59. package/templates/starter/Reviews/INDEX.md +75 -0
  60. package/templates/starter/SESSION-LOG.md +29 -0
  61. package/templates/starter/SUMMARY.md +27 -0
  62. package/templates/starter/agentWorkflows/INDEX.md +61 -0
  63. package/templates/starter/intro/README.md +37 -0
  64. package/templates/full/docs/index.md +0 -46
package/lib/init.js CHANGED
@@ -1,296 +1,478 @@
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, copyFileSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from "node:fs";
9
- import { resolve, join, dirname, basename, relative } from "node:path";
10
- import { fileURLToPath } from "node:url";
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = dirname(__filename);
14
-
15
- const EXTENSION_MAP = {
16
- code: "code-project.md",
17
- data: "data-exploration.md",
18
- analytical: "analytical-system.md",
19
- };
20
-
21
- const FRIENDLY_NAMES = {
22
- code: "Code project",
23
- data: "Data exploration",
24
- analytical: "Analytical/prompt system",
25
- mixed: "Multi-type project",
26
- general: "General project",
27
- };
28
-
29
- const RUNTIME_ENTRY_POINTS = {
30
- claude: "CLAUDE.md",
31
- cursor: ".cursorrules",
32
- all: null, // keep all
33
- };
34
-
35
- /**
36
- * Set up a new project with methodology templates.
37
- */
38
- export async function initProject(projectType, directory, opts = {}) {
39
- const { tier = "starter", runtime = "all", profile = "standard", registryPath } = opts;
40
- const targetDir = resolve(directory);
41
- const methodRoot = resolve(__dirname, "..");
42
-
43
- const templateDir = join(methodRoot, "templates", tier);
44
- if (!existsSync(templateDir)) {
45
- console.error(`Template directory not found: ${templateDir}`);
46
- process.exit(1);
47
- }
48
-
49
- console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
50
- console.log(` Template: ${tier}`);
51
- console.log(` Profile: ${profile}`);
52
- console.log(` Runtime: ${runtime}`);
53
- console.log(` Target: ${targetDir}\n`);
54
-
55
- // Detect brownfield (existing project with methodology files)
56
- const isBrownfield = detectBrownfield(targetDir);
57
- if (isBrownfield) {
58
- console.log(" Detected existing methodology files (brownfield mode)");
59
- console.log(" Will append to existing entry points, not overwrite.\n");
60
- }
61
-
62
- // Create target directory
63
- mkdirSync(targetDir, { recursive: true });
64
-
65
- // Copy template files
66
- const copied = [];
67
- const skipped = [];
68
- copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield);
69
-
70
- // Apply extensions based on project type
71
- const extensions = getExtensions(projectType);
72
- for (const ext of extensions) {
73
- const extFile = join(methodRoot, "templates", "extensions", ext);
74
- if (existsSync(extFile)) {
75
- mergeExtension(extFile, targetDir, isBrownfield);
76
- copied.push(`extension: ${ext}`);
77
- }
78
- }
79
-
80
- // Replace placeholders in entry points
81
- replacePlaceholders(targetDir, projectType, tier);
82
-
83
- // Set integration profile in entry points
84
- setProfile(targetDir, profile);
85
-
86
- // Remove unused entry points based on runtime choice
87
- const removed = removeUnusedEntryPoints(targetDir, runtime, isBrownfield);
88
-
89
- // Report
90
- console.log(` Files created: ${copied.length}`);
91
- for (const f of copied) console.log(` + ${f}`);
92
- if (removed.length > 0) {
93
- console.log(` Entry points removed: ${removed.length}`);
94
- for (const f of removed) console.log(` - ${f}`);
95
- }
96
- if (skipped.length > 0) {
97
- console.log(` Files skipped (already exist): ${skipped.length}`);
98
- for (const f of skipped) console.log(` ~ ${f}`);
99
- }
100
-
101
- // Determine which entry point the user kept
102
- const keptEntry = runtime === "claude" ? "CLAUDE.md"
103
- : runtime === "cursor" ? ".cursorrules"
104
- : "CLAUDE.md / .cursorrules / AGENT.md";
105
-
106
- console.log(`\nNext steps:`);
107
- console.log(` 1. cd ${relative(process.cwd(), targetDir) || "."}`);
108
- console.log(` 2. Fill in PROJECT.md with your project vision`);
109
- if (runtime === "all") {
110
- console.log(` 3. Delete the two entry point files you don't use`);
111
- console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
112
- }
113
- console.log(` ${runtime === "all" ? "4" : "3"}. Start a conversation — the agent reads ${keptEntry} automatically`);
114
- console.log(`\nVerify: npx wwa check`);
115
- }
116
-
117
- function detectBrownfield(dir) {
118
- if (!existsSync(dir)) return false;
119
- const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
120
- let count = 0;
121
- for (const ind of indicators) {
122
- if (existsSync(join(dir, ind))) count++;
123
- }
124
- return count >= 2;
125
- }
126
-
127
- function copyTemplateDir(src, dst, copied, skipped, isBrownfield) {
128
- if (!existsSync(src)) return;
129
- mkdirSync(dst, { recursive: true });
130
-
131
- for (const entry of readdirSync(src, { withFileTypes: true })) {
132
- const srcPath = join(src, entry.name);
133
- const dstPath = join(dst, entry.name);
134
-
135
- if (entry.isDirectory()) {
136
- copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield);
137
- } else {
138
- // Entry points: special handling in brownfield
139
- const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
140
-
141
- if (existsSync(dstPath)) {
142
- if (isEntryPoint && isBrownfield) {
143
- // Append methodology sections to existing entry point
144
- appendToEntryPoint(srcPath, dstPath);
145
- copied.push(`${relative(dst, dstPath)} (merged)`);
146
- } else {
147
- skipped.push(relative(dst, dstPath));
148
- }
149
- } else {
150
- copyFileSync(srcPath, dstPath);
151
- copied.push(relative(dst, dstPath));
152
- }
153
- }
154
- }
155
- }
156
-
157
- function appendToEntryPoint(srcPath, dstPath) {
158
- const srcContent = readFileSync(srcPath, "utf-8");
159
- let dstContent = readFileSync(dstPath, "utf-8");
160
-
161
- // Extract sections from template that don't exist in target
162
- const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
163
- for (const section of sections) {
164
- if (!dstContent.includes(section) && srcContent.includes(section)) {
165
- const idx = srcContent.indexOf(section);
166
- // Find the next section or end
167
- let endIdx = srcContent.length;
168
- for (const nextSection of sections) {
169
- const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
170
- if (nextIdx > idx && nextIdx < endIdx) {
171
- endIdx = nextIdx;
172
- }
173
- }
174
- const sectionContent = srcContent.slice(idx, endIdx).trim();
175
- dstContent += "\n\n" + sectionContent;
176
- }
177
- }
178
-
179
- writeFileSync(dstPath, dstContent, "utf-8");
180
- }
181
-
182
- function getExtensions(projectType) {
183
- if (projectType === "mixed") {
184
- return Object.values(EXTENSION_MAP);
185
- }
186
- const ext = EXTENSION_MAP[projectType];
187
- return ext ? [ext] : [];
188
- }
189
-
190
- function mergeExtension(extFile, targetDir, isBrownfield) {
191
- const content = readFileSync(extFile, "utf-8");
192
-
193
- // Extract scoping rows and cascade rows from extension file
194
- const scopingRows = [];
195
- const cascadeRows = [];
196
- let inScoping = false;
197
- let inCascade = false;
198
-
199
- for (const line of content.split("\n")) {
200
- if (line.includes("## Scoping")) inScoping = true;
201
- if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
202
- inCascade = true;
203
- if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
204
- inScoping = false;
205
- inCascade = false;
206
- }
207
-
208
- if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
209
- scopingRows.push(line);
210
- }
211
- if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
212
- cascadeRows.push(line);
213
- }
214
- }
215
-
216
- // Inject rows into entry points
217
- for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
218
- const epPath = join(targetDir, epName);
219
- if (!existsSync(epPath)) continue;
220
-
221
- let epContent = readFileSync(epPath, "utf-8");
222
-
223
- // Inject scoping rows before the instruction comment or at end of table
224
- if (scopingRows.length > 0) {
225
- const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
226
- if (epContent.includes(placeholder)) {
227
- epContent = epContent.replace(
228
- placeholder,
229
- scopingRows.join("\n") + "\n" + placeholder
230
- );
231
- }
232
- }
233
-
234
- // Inject cascade rows
235
- if (cascadeRows.length > 0) {
236
- const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
237
- if (epContent.includes(placeholder)) {
238
- epContent = epContent.replace(
239
- placeholder,
240
- cascadeRows.join("\n") + "\n" + placeholder
241
- );
242
- }
243
- }
244
-
245
- writeFileSync(epPath, epContent, "utf-8");
246
- }
247
- }
248
-
249
- function replacePlaceholders(targetDir, projectType, tier) {
250
- for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
251
- const epPath = join(targetDir, epName);
252
- if (!existsSync(epPath)) continue;
253
-
254
- let content = readFileSync(epPath, "utf-8");
255
-
256
- // Replace project type placeholder
257
- content = content.replace(/\{your-project-type\}/g, projectType);
258
- content = content.replace(/\{your-domain-type\}/g, projectType);
259
-
260
- // Set template tier
261
- content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
262
-
263
- writeFileSync(epPath, content, "utf-8");
264
- }
265
- }
266
-
267
- function setProfile(targetDir, profile) {
268
- for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
269
- const epPath = join(targetDir, epName);
270
- if (!existsSync(epPath)) continue;
271
-
272
- let content = readFileSync(epPath, "utf-8");
273
- content = content.replace(/tier:\s*(lite|standard|full)/, `tier: ${profile}`);
274
- writeFileSync(epPath, content, "utf-8");
275
- }
276
- }
277
-
278
- function removeUnusedEntryPoints(targetDir, runtime, isBrownfield) {
279
- if (runtime === "all" || isBrownfield) return [];
280
-
281
- const allEntryPoints = ["CLAUDE.md", ".cursorrules", "AGENT.md"];
282
- const keep = RUNTIME_ENTRY_POINTS[runtime];
283
- if (!keep) return [];
284
-
285
- const removed = [];
286
- for (const ep of allEntryPoints) {
287
- if (ep !== keep) {
288
- const epPath = join(targetDir, ep);
289
- if (existsSync(epPath)) {
290
- unlinkSync(epPath);
291
- removed.push(ep);
292
- }
293
- }
294
- }
295
- return removed;
296
- }
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
+
13
+ /** Entry point filenames that bypass the extension-based safety check. */
14
+ const ENTRY_POINT_NAMES = new Set(["CLAUDE.md", ".cursorrules", "AGENT.md"]);
15
+
16
+ /** Write file — allows entry points (e.g. .cursorrules) plus safe extensions. */
17
+ function writeInitFile(filePath, content, encoding = "utf-8") {
18
+ const name = basename(filePath);
19
+ if (ENTRY_POINT_NAMES.has(name)) {
20
+ writeFileSync(filePath, content, encoding);
21
+ } else {
22
+ safeWriteFile(filePath, content, encoding);
23
+ }
24
+ }
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = dirname(__filename);
28
+
29
+ const EXTENSION_MAP = {
30
+ code: "code-project.md",
31
+ data: "data-exploration.md",
32
+ analytical: "analytical-system.md",
33
+ };
34
+
35
+ const FRIENDLY_NAMES = {
36
+ code: "Code project",
37
+ data: "Data exploration",
38
+ analytical: "Analytical/prompt system",
39
+ mixed: "Multi-type project",
40
+ general: "General project",
41
+ };
42
+
43
+ const RUNTIME_ENTRY_POINTS = {
44
+ claude: "CLAUDE.md",
45
+ cursor: ".cursorrules",
46
+ all: null, // keep all
47
+ };
48
+
49
+ /**
50
+ * Set up a new project with methodology templates.
51
+ */
52
+ export async function initProject(projectType, directory, opts = {}) {
53
+ const { tier = "starter", runtime = "all", profile = "standard", onboarding = "auto", registryPath } = opts;
54
+ const targetDir = resolve(directory);
55
+ const methodRoot = resolve(__dirname, "..");
56
+
57
+ const templateDir = join(methodRoot, "templates", tier);
58
+ if (!existsSync(templateDir)) {
59
+ console.error(`Template directory not found: ${templateDir}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ console.log(`\nSetting up ${FRIENDLY_NAMES[projectType] || projectType} project`);
64
+ console.log(` Template: ${tier}`);
65
+ console.log(` Profile: ${profile}`);
66
+ console.log(` Runtime: ${runtime}`);
67
+ console.log(` Onboarding: ${onboarding}`);
68
+ console.log(` Target: ${targetDir}\n`);
69
+
70
+ // Determine brownfield mode from user selection or auto-detection
71
+ let isBrownfield;
72
+ if (onboarding === "brownfield") {
73
+ isBrownfield = true;
74
+ } else if (onboarding === "greenfield") {
75
+ isBrownfield = false;
76
+ } else {
77
+ // auto — detect from existing files
78
+ isBrownfield = detectBrownfield(targetDir);
79
+ }
80
+
81
+ if (isBrownfield) {
82
+ console.log(" Brownfield mode — will append to existing entry points, not overwrite.\n");
83
+ }
84
+
85
+ // Create target directory
86
+ mkdirSync(targetDir, { recursive: true });
87
+
88
+ // Copy template files
89
+ const copied = [];
90
+ const skipped = [];
91
+ copyTemplateDir(templateDir, targetDir, copied, skipped, isBrownfield, runtime);
92
+
93
+ // Apply extensions based on project type
94
+ const extensions = getExtensions(projectType);
95
+ for (const ext of extensions) {
96
+ const extFile = join(methodRoot, "templates", "extensions", ext);
97
+ if (existsSync(extFile)) {
98
+ mergeExtension(extFile, targetDir, isBrownfield);
99
+ copied.push(`extension: ${ext}`);
100
+ }
101
+ }
102
+
103
+ // Copy feature-registry.yaml to user project (populated, from package)
104
+ copyFeatureRegistry(methodRoot, targetDir, copied);
105
+
106
+ // Create project-specific doc-tokens.yaml with initial values
107
+ createProjectTokens(targetDir, projectType, tier, profile, onboarding, copied);
108
+
109
+ // Replace placeholders in entry points
110
+ replacePlaceholders(targetDir, projectType, tier);
111
+
112
+ // Set integration profile in entry points
113
+ setProfile(targetDir, profile);
114
+
115
+ // Set onboarding scenario in PROJECT-PROFILE.md
116
+ setOnboardingScenario(targetDir, onboarding, isBrownfield, projectType);
117
+
118
+ // Remove unused entry points based on runtime choice
119
+ const removed = removeUnusedEntryPoints(targetDir, runtime, isBrownfield);
120
+
121
+ // Report
122
+ console.log(` Files created: ${copied.length}`);
123
+ for (const f of copied) console.log(` + ${f}`);
124
+ if (removed.length > 0) {
125
+ console.log(` Entry points removed: ${removed.length}`);
126
+ for (const f of removed) console.log(` - ${f}`);
127
+ }
128
+ if (skipped.length > 0) {
129
+ console.log(` Files skipped (already exist): ${skipped.length}`);
130
+ for (const f of skipped) console.log(` ~ ${f}`);
131
+ }
132
+
133
+ // Determine which entry point the user kept
134
+ const keptEntry = runtime === "claude" ? "CLAUDE.md"
135
+ : runtime === "cursor" ? ".cursorrules"
136
+ : "CLAUDE.md / .cursorrules / AGENT.md";
137
+
138
+ console.log(`\nNext steps:`);
139
+ let step = 1;
140
+ console.log(` ${step++}. cd ${relative(process.cwd(), targetDir) || "."}`);
141
+ if (runtime === "all") {
142
+ console.log(` ${step++}. Delete the two entry point files you don't use`);
143
+ console.log(` (keep CLAUDE.md, .cursorrules, or AGENT.md)`);
144
+ }
145
+ console.log(` ${step++}. Start a conversation with your agent — it reads ${keptEntry} automatically`);
146
+ if (isBrownfield) {
147
+ console.log(` The agent will scan your codebase and walk you through 5 onboarding stages`);
148
+ console.log(` (scan → clarify → map → organize → starting state)`);
149
+ } else {
150
+ console.log(` The agent will walk you through 4 onboarding stages`);
151
+ console.log(` (vision → architecture → goals → starting state)`);
152
+ }
153
+ console.log(`\nVerify: wwa check`);
154
+ }
155
+
156
+ function detectBrownfield(dir) {
157
+ if (!existsSync(dir)) return false;
158
+ const indicators = ["CLAUDE.md", ".cursorrules", "AGENT.md", "STATE.md", ".context"];
159
+ let count = 0;
160
+ for (const ind of indicators) {
161
+ if (existsSync(join(dir, ind))) count++;
162
+ }
163
+ return count >= 2;
164
+ }
165
+
166
+ function copyTemplateDir(src, dst, copied, skipped, isBrownfield, runtime) {
167
+ if (!existsSync(src)) return;
168
+ mkdirSync(dst, { recursive: true });
169
+
170
+ for (const entry of readdirSync(src, { withFileTypes: true })) {
171
+ const srcPath = join(src, entry.name);
172
+ const dstPath = join(dst, entry.name);
173
+
174
+ if (entry.isDirectory()) {
175
+ copyTemplateDir(srcPath, dstPath, copied, skipped, isBrownfield, runtime);
176
+ } else {
177
+ // Entry points: special handling in brownfield
178
+ const isEntryPoint = ["CLAUDE.md", ".cursorrules", "AGENT.md"].includes(entry.name);
179
+
180
+ // Skip entry points that won't be kept (avoids safety invariant issue with .cursorrules)
181
+ if (isEntryPoint && runtime !== "all" && !isBrownfield) {
182
+ const keep = RUNTIME_ENTRY_POINTS[runtime];
183
+ if (keep && entry.name !== keep) continue;
184
+ }
185
+
186
+ // Defer heavy infrastructure files in brownfield to reduce first-session load
187
+ const isDeferredInBrownfield = isBrownfield && entry.name === "REGISTRY.md";
188
+ if (isDeferredInBrownfield) {
189
+ skipped.push(`${relative(dst, dstPath)} (deferred — created on first file split)`);
190
+ continue;
191
+ }
192
+
193
+ if (existsSync(dstPath)) {
194
+ if (isEntryPoint && isBrownfield) {
195
+ // Append methodology sections to existing entry point
196
+ appendToEntryPoint(srcPath, dstPath);
197
+ copied.push(`${relative(dst, dstPath)} (merged)`);
198
+ } else {
199
+ skipped.push(relative(dst, dstPath));
200
+ }
201
+ } else if (isEntryPoint) {
202
+ // Entry points bypass safeCopyFile (e.g. .cursorrules has no .md extension)
203
+ copyFileSync(srcPath, dstPath);
204
+ copied.push(relative(dst, dstPath));
205
+ } else {
206
+ safeCopyFile(srcPath, dstPath);
207
+ copied.push(relative(dst, dstPath));
208
+ }
209
+ }
210
+ }
211
+ }
212
+
213
+ function appendToEntryPoint(srcPath, dstPath) {
214
+ const srcContent = readFileSync(srcPath, "utf-8");
215
+ let dstContent = readFileSync(dstPath, "utf-8");
216
+
217
+ // Extract sections from template that don't exist in target
218
+ const sections = ["## Scoping rules", "## Dependency cascade", "## Conventions", "## Do not"];
219
+ for (const section of sections) {
220
+ if (!dstContent.includes(section) && srcContent.includes(section)) {
221
+ const idx = srcContent.indexOf(section);
222
+ // Find the next section or end
223
+ let endIdx = srcContent.length;
224
+ for (const nextSection of sections) {
225
+ const nextIdx = srcContent.indexOf(nextSection, idx + section.length);
226
+ if (nextIdx > idx && nextIdx < endIdx) {
227
+ endIdx = nextIdx;
228
+ }
229
+ }
230
+ const sectionContent = srcContent.slice(idx, endIdx).trim();
231
+ dstContent += "\n\n" + sectionContent;
232
+ }
233
+ }
234
+
235
+ writeInitFile(dstPath, dstContent, "utf-8");
236
+ }
237
+
238
+ function getExtensions(projectType) {
239
+ if (projectType === "mixed") {
240
+ return Object.values(EXTENSION_MAP);
241
+ }
242
+ const ext = EXTENSION_MAP[projectType];
243
+ return ext ? [ext] : [];
244
+ }
245
+
246
+ function mergeExtension(extFile, targetDir, isBrownfield) {
247
+ const content = readFileSync(extFile, "utf-8");
248
+
249
+ // Extract scoping rows and cascade rows from extension file
250
+ const scopingRows = [];
251
+ const cascadeRows = [];
252
+ let inScoping = false;
253
+ let inCascade = false;
254
+
255
+ for (const line of content.split("\n")) {
256
+ if (line.includes("## Scoping")) inScoping = true;
257
+ if (line.includes("## Cascade") || line.includes("## Dependency cascade"))
258
+ inCascade = true;
259
+ if (line.startsWith("## ") && !line.includes("Scoping") && !line.includes("Cascade")) {
260
+ inScoping = false;
261
+ inCascade = false;
262
+ }
263
+
264
+ if (inScoping && line.startsWith("|") && !line.startsWith("| Query") && !line.startsWith("|--")) {
265
+ scopingRows.push(line);
266
+ }
267
+ if (inCascade && line.startsWith("|") && !line.startsWith("| When") && !line.startsWith("|--")) {
268
+ cascadeRows.push(line);
269
+ }
270
+ }
271
+
272
+ // Inject rows into entry points
273
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
274
+ const epPath = join(targetDir, epName);
275
+ if (!existsSync(epPath)) continue;
276
+
277
+ let epContent = readFileSync(epPath, "utf-8");
278
+
279
+ // Inject scoping rows before the instruction comment or at end of table
280
+ if (scopingRows.length > 0) {
281
+ const placeholder = "<!-- INSTRUCTION: Add project-specific scoping";
282
+ if (epContent.includes(placeholder)) {
283
+ epContent = epContent.replace(
284
+ placeholder,
285
+ scopingRows.join("\n") + "\n" + placeholder
286
+ );
287
+ }
288
+ }
289
+
290
+ // Inject cascade rows
291
+ if (cascadeRows.length > 0) {
292
+ const placeholder = "<!-- INSTRUCTION: Add project-specific cascade";
293
+ if (epContent.includes(placeholder)) {
294
+ epContent = epContent.replace(
295
+ placeholder,
296
+ cascadeRows.join("\n") + "\n" + placeholder
297
+ );
298
+ }
299
+ }
300
+
301
+ writeInitFile(epPath, epContent, "utf-8");
302
+ }
303
+ }
304
+
305
+ function replacePlaceholders(targetDir, projectType, tier) {
306
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
307
+ const epPath = join(targetDir, epName);
308
+ if (!existsSync(epPath)) continue;
309
+
310
+ let content = readFileSync(epPath, "utf-8");
311
+
312
+ // Replace project type placeholder
313
+ content = content.replace(/\{your-project-type\}/g, projectType);
314
+ content = content.replace(/\{your-domain-type\}/g, projectType);
315
+
316
+ // Set template tier
317
+ content = content.replace(/template_tier:\s*\S+/, `template_tier: ${tier}`);
318
+
319
+ writeInitFile(epPath, content, "utf-8");
320
+ }
321
+ }
322
+
323
+ function setProfile(targetDir, profile) {
324
+ for (const epName of ["CLAUDE.md", ".cursorrules", "AGENT.md"]) {
325
+ const epPath = join(targetDir, epName);
326
+ if (!existsSync(epPath)) continue;
327
+
328
+ let content = readFileSync(epPath, "utf-8");
329
+ content = content.replace(/tier:\s*(lite|standard|full)/, `tier: ${profile}`);
330
+ writeInitFile(epPath, content, "utf-8");
331
+ }
332
+ }
333
+
334
+ function setOnboardingScenario(targetDir, onboarding, isBrownfield, projectType) {
335
+ const profilePath = join(targetDir, "PROJECT-PROFILE.md");
336
+ if (!existsSync(profilePath)) return;
337
+
338
+ let content = readFileSync(profilePath, "utf-8");
339
+
340
+ // Set lifecycle stage based on scenario
341
+ const stage = isBrownfield ? "discovery" : "design";
342
+ content = content.replace(/\*\*Stage\*\*:\s*\{stage\}/, `**Stage**: ${stage}`);
343
+ content = content.replace(/\*\*Maturity\*\*:\s*\{maturity\}/, `**Maturity**: new`);
344
+
345
+ const today = new Date().toISOString().slice(0, 10);
346
+ content = content.replace(
347
+ /\*\*Last transition\*\*:\s*\{date\}.*$/m,
348
+ `**Last transition**: ${today} (setup -> ${stage})`
349
+ );
350
+
351
+ // Set project type
352
+ content = content.replace(/\[{project-type}\]/, `[${projectType}]`);
353
+
354
+ // Set onboarding hint so the agent knows which first-session flow
355
+ const scenario = onboarding === "auto"
356
+ ? (isBrownfield ? "brownfield (auto-detected)" : "greenfield (auto-detected)")
357
+ : onboarding;
358
+
359
+ // Add onboarding scenario after lifecycle section
360
+ if (!content.includes("**Onboarding**")) {
361
+ content = content.replace(
362
+ /(\*\*Last transition\*\*:.*$)/m,
363
+ `$1\n- **Onboarding**: ${scenario}`
364
+ );
365
+ }
366
+
367
+ // Set extension stack
368
+ const extensions = getExtensions(projectType);
369
+ const extNames = extensions.map(e => e.replace(".md", ""));
370
+ content = content.replace(/\[{extensions-applied}\]/, `[${extNames.join(", ") || "none"}]`);
371
+
372
+ for (const ext of ["code-project", "data-exploration", "analytical-system"]) {
373
+ const isActive = extNames.includes(ext) ? "yes" : "no";
374
+ const source = extNames.includes(ext) ? "wwa init" : "—";
375
+ content = content.replace(
376
+ new RegExp(`\\| ${ext} \\| \\{yes/no\\} \\| \\{setup\\.sh / manual\\} \\|`),
377
+ `| ${ext} | ${isActive} | ${source} |`
378
+ );
379
+ }
380
+
381
+ safeWriteFile(profilePath, content, "utf-8");
382
+ }
383
+
384
+ function createProjectTokens(targetDir, projectType, tier, profile, onboarding, copied) {
385
+ const ctxDir = join(targetDir, ".context");
386
+ mkdirSync(ctxDir, { recursive: true });
387
+ const dst = join(ctxDir, "doc-tokens.yaml");
388
+ if (existsSync(dst)) return; // don't overwrite
389
+
390
+ const today = new Date().toISOString().slice(0, 10);
391
+
392
+ // Count files that were actually created
393
+ const mdFiles = [];
394
+ const countMdFiles = (dir) => {
395
+ if (!existsSync(dir)) return;
396
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
397
+ const p = join(dir, entry.name);
398
+ if (entry.isDirectory()) countMdFiles(p);
399
+ else if (entry.name.endsWith(".md") || entry.name.endsWith(".yaml") || entry.name.endsWith(".yml")) {
400
+ mdFiles.push(relative(targetDir, p).replace(/\\/g, "/"));
401
+ }
402
+ }
403
+ };
404
+ countMdFiles(targetDir);
405
+
406
+ const content = [
407
+ "# Project Token Registry",
408
+ "# Computed by wwa init. Updated by wwa close.",
409
+ "# Used by wwa casestudy for methodology reference data.",
410
+ "#",
411
+ "# Agent: read this before writing docs. Check values are current.",
412
+ "",
413
+ "tokens:",
414
+ "",
415
+ " # --- Project identity ---",
416
+ ` project_type: ${projectType}`,
417
+ ` template_tier: ${tier}`,
418
+ ` integration_profile: ${profile}`,
419
+ ` onboarding: ${onboarding}`,
420
+ ` created: "${today}"`,
421
+ ` last_close: "${today}"`,
422
+ "",
423
+ " # --- File counts (updated at close) ---",
424
+ ` methodology_file_count: ${mdFiles.length}`,
425
+ " docs_file_count: 0",
426
+ " context_file_count: 0",
427
+ "",
428
+ " # --- Session metrics (updated at close) ---",
429
+ " session_count: 0",
430
+ " decision_count: 0",
431
+ " total_features_referenced: 0",
432
+ "",
433
+ " # --- Scale indicators (updated at close) ---",
434
+ " files_near_threshold: 0 # files with 250+ lines",
435
+ " files_over_threshold: 0 # files with 300+ lines (need splitting)",
436
+ "",
437
+ " # --- Docs coverage (updated at close) ---",
438
+ " docs_inventory_count: 0",
439
+ " docs_on_disk: 0",
440
+ " docs_missing: 0",
441
+ " component_mappings: 0",
442
+ "",
443
+ ].join("\n");
444
+
445
+ safeWriteFile(dst, content, "utf-8");
446
+ copied.push(".context/doc-tokens.yaml");
447
+ }
448
+
449
+ function copyFeatureRegistry(methodRoot, targetDir, copied) {
450
+ const src = join(methodRoot, "docs", "internal", "feature-registry.yaml");
451
+ if (!existsSync(src)) return;
452
+ const ctxDir = join(targetDir, ".context");
453
+ mkdirSync(ctxDir, { recursive: true });
454
+ const dst = join(ctxDir, "feature-registry.yaml");
455
+ if (existsSync(dst)) return; // don't overwrite existing
456
+ safeCopyFile(src, dst);
457
+ copied.push(".context/feature-registry.yaml");
458
+ }
459
+
460
+ function removeUnusedEntryPoints(targetDir, runtime, isBrownfield) {
461
+ if (runtime === "all" || isBrownfield) return [];
462
+
463
+ const allEntryPoints = ["CLAUDE.md", ".cursorrules", "AGENT.md"];
464
+ const keep = RUNTIME_ENTRY_POINTS[runtime];
465
+ if (!keep) return [];
466
+
467
+ const removed = [];
468
+ for (const ep of allEntryPoints) {
469
+ if (ep !== keep) {
470
+ const epPath = join(targetDir, ep);
471
+ if (existsSync(epPath)) {
472
+ unlinkSync(epPath);
473
+ removed.push(ep);
474
+ }
475
+ }
476
+ }
477
+ return removed;
478
+ }