agent-method 1.5.3 → 1.5.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +197 -57
  2. package/bin/wwa.js +35 -9
  3. package/docs/internal/doc-tokens.yaml +452 -0
  4. package/docs/internal/feature-registry.yaml +13 -1
  5. package/lib/cli/casestudy.js +691 -0
  6. package/lib/cli/check.js +71 -71
  7. package/lib/cli/close.js +446 -0
  8. package/lib/cli/completion.js +639 -0
  9. package/lib/cli/digest.js +66 -0
  10. package/lib/cli/docs.js +207 -0
  11. package/lib/cli/helpers.js +49 -2
  12. package/lib/cli/implement.js +159 -0
  13. package/lib/cli/init.js +25 -6
  14. package/lib/cli/plan.js +128 -0
  15. package/lib/cli/refine.js +202 -202
  16. package/lib/cli/review.js +68 -0
  17. package/lib/cli/scan.js +28 -28
  18. package/lib/cli/status.js +61 -61
  19. package/lib/cli/upgrade.js +150 -147
  20. package/lib/init.js +478 -296
  21. package/package.json +12 -4
  22. package/templates/README.md +73 -25
  23. package/templates/entry-points/.cursorrules +143 -14
  24. package/templates/entry-points/AGENT.md +143 -14
  25. package/templates/entry-points/CLAUDE.md +143 -14
  26. package/templates/extensions/analytical-system.md +1 -1
  27. package/templates/extensions/code-project.md +1 -1
  28. package/templates/extensions/data-exploration.md +1 -1
  29. package/templates/full/.context/BASE.md +33 -0
  30. package/templates/full/.context/METHODOLOGY.md +62 -5
  31. package/templates/full/.cursorrules +128 -18
  32. package/templates/full/AGENT.md +128 -18
  33. package/templates/full/CLAUDE.md +128 -18
  34. package/templates/full/Management/DIGEST.md +23 -0
  35. package/templates/full/Management/STATUS.md +46 -0
  36. package/templates/full/PROJECT.md +34 -0
  37. package/templates/full/Reviews/INDEX.md +41 -0
  38. package/templates/full/Reviews/backlog.md +52 -0
  39. package/templates/full/Reviews/plan.md +43 -0
  40. package/templates/full/Reviews/project.md +41 -0
  41. package/templates/full/Reviews/requirements.md +42 -0
  42. package/templates/full/Reviews/roadmap.md +41 -0
  43. package/templates/full/Reviews/state.md +56 -0
  44. package/templates/full/SESSION-LOG.md +29 -0
  45. package/templates/full/SUMMARY.md +7 -4
  46. package/templates/full/agentWorkflows/INDEX.md +42 -0
  47. package/templates/full/agentWorkflows/observations.md +65 -0
  48. package/templates/full/agentWorkflows/patterns.md +68 -0
  49. package/templates/full/agentWorkflows/sessions.md +92 -0
  50. package/templates/full/intro/README.md +39 -0
  51. package/templates/starter/.context/BASE.md +35 -0
  52. package/templates/starter/.context/METHODOLOGY.md +59 -5
  53. package/templates/starter/.cursorrules +135 -13
  54. package/templates/starter/AGENT.md +135 -13
  55. package/templates/starter/CLAUDE.md +135 -13
  56. package/templates/starter/Management/DIGEST.md +23 -0
  57. package/templates/starter/Management/STATUS.md +46 -0
  58. package/templates/starter/PROJECT.md +34 -0
  59. package/templates/starter/Reviews/INDEX.md +75 -0
  60. package/templates/starter/SESSION-LOG.md +29 -0
  61. package/templates/starter/SUMMARY.md +27 -0
  62. package/templates/starter/agentWorkflows/INDEX.md +61 -0
  63. package/templates/starter/intro/README.md +37 -0
  64. package/templates/full/docs/index.md +0 -46
@@ -0,0 +1,66 @@
1
+ /** wwa digest — show management digest and project health. */
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { resolve, join } from "node:path";
5
+
6
+ export function register(program) {
7
+ program
8
+ .command("digest [directory]")
9
+ .description("Show management digest from Management/DIGEST.md")
10
+ .option("--status", "Show Management/STATUS.md instead of digest")
11
+ .option("--json", "Output as JSON")
12
+ .action(async (directory, opts) => {
13
+ directory = directory || ".";
14
+ const d = resolve(directory);
15
+
16
+ const digestPath = join(d, "Management", "DIGEST.md");
17
+ const statusPath = join(d, "Management", "STATUS.md");
18
+
19
+ const target = opts.status ? statusPath : digestPath;
20
+ const label = opts.status ? "STATUS" : "DIGEST";
21
+
22
+ if (!existsSync(target)) {
23
+ // Try to synthesize from SUMMARY.md or STATE.md
24
+ const summaryPath = join(d, "SUMMARY.md");
25
+ const statePath = join(d, "STATE.md");
26
+
27
+ if (opts.json) {
28
+ console.log(
29
+ JSON.stringify(
30
+ {
31
+ error: `Management/${label}.md not found`,
32
+ hasSummary: existsSync(summaryPath),
33
+ hasState: existsSync(statePath),
34
+ },
35
+ null,
36
+ 2
37
+ )
38
+ );
39
+ return;
40
+ }
41
+
42
+ console.log(`\n Management/${label}.md not found.\n`);
43
+ if (existsSync(summaryPath) || existsSync(statePath)) {
44
+ console.log(" Available alternatives:");
45
+ if (existsSync(summaryPath))
46
+ console.log(" - SUMMARY.md (session audit trail)");
47
+ if (existsSync(statePath))
48
+ console.log(" - STATE.md (current position + decisions)");
49
+ }
50
+ console.log(
51
+ "\n To get management reports, use the starter or full template tier:" +
52
+ "\n wwa init <type>\n"
53
+ );
54
+ return;
55
+ }
56
+
57
+ const content = readFileSync(target, "utf-8");
58
+
59
+ if (opts.json) {
60
+ console.log(JSON.stringify({ file: target, content }, null, 2));
61
+ return;
62
+ }
63
+
64
+ console.log(content);
65
+ });
66
+ }
@@ -0,0 +1,207 @@
1
+ /** wwa docs — show docs coverage and staleness from DOCS-MAP.md. */
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { resolve, join } from "node:path";
5
+
6
+ export function register(program) {
7
+ program
8
+ .command("docs [directory]")
9
+ .description("Show docs coverage report from .context/DOCS-MAP.md")
10
+ .option("--json", "Output as JSON")
11
+ .action(async (directory, opts) => {
12
+ directory = directory || ".";
13
+ const d = resolve(directory);
14
+
15
+ const docsMapPath = join(d, ".context", "DOCS-MAP.md");
16
+
17
+ if (!existsSync(docsMapPath)) {
18
+ if (opts.json) {
19
+ console.log(JSON.stringify({ error: "No .context/DOCS-MAP.md found" }, null, 2));
20
+ return;
21
+ }
22
+ console.log("\n No .context/DOCS-MAP.md found.\n");
23
+ console.log(" This file maps project components to their documentation.");
24
+ console.log(" Create it by running: wwa init <type>");
25
+ console.log(" Or ask your agent to populate it during onboarding.\n");
26
+ return;
27
+ }
28
+
29
+ const content = readFileSync(docsMapPath, "utf-8");
30
+ const report = parseDocsMap(content, d);
31
+
32
+ if (opts.json) {
33
+ console.log(JSON.stringify(report, null, 2));
34
+ return;
35
+ }
36
+
37
+ printReport(report);
38
+ });
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // DOCS-MAP.md parsing
43
+ // ---------------------------------------------------------------------------
44
+
45
+ function parseDocsMap(content, projectDir) {
46
+ const inventory = [];
47
+ const mappings = [];
48
+ const scaffolding = [];
49
+
50
+ // Parse docs inventory table
51
+ const invSection = content.match(
52
+ /## Docs inventory\s*\n[\s\S]*?\n\|[^\n]+\n\|[-| :]+\n((?:\|[^\n]+\n)*)/
53
+ );
54
+ if (invSection) {
55
+ for (const row of invSection[1].trim().split("\n")) {
56
+ const cols = row.split("|").map((c) => c.trim()).filter((c) => c);
57
+ if (cols.length >= 3 && !cols[0].startsWith("<!--")) {
58
+ const path = cols[0];
59
+ const exists = existsSync(join(projectDir, path));
60
+ inventory.push({
61
+ path,
62
+ purpose: cols[1],
63
+ sources: cols[2],
64
+ status: cols[3] || (exists ? "active" : "missing"),
65
+ exists,
66
+ });
67
+ }
68
+ }
69
+ }
70
+
71
+ // Parse component-to-docs mapping table
72
+ const mapSection = content.match(
73
+ /## Component-to-docs mapping\s*\n[\s\S]*?\n\|[^\n]+\n\|[-| :]+\n((?:\|[^\n]+\n)*)/
74
+ );
75
+ if (mapSection) {
76
+ for (const row of mapSection[1].trim().split("\n")) {
77
+ const cols = row.split("|").map((c) => c.trim()).filter((c) => c);
78
+ if (cols.length >= 3 && !cols[0].startsWith("<!--")) {
79
+ mappings.push({
80
+ component: cols[0],
81
+ documentedIn: cols[1],
82
+ trigger: cols[2],
83
+ });
84
+ }
85
+ }
86
+ }
87
+
88
+ // Parse scaffolding rules table
89
+ const scaffSection = content.match(
90
+ /## Scaffolding rules\s*\n[\s\S]*?\n\|[^\n]+\n\|[-| :]+\n((?:\|[^\n]+\n)*)/
91
+ );
92
+ if (scaffSection) {
93
+ for (const row of scaffSection[1].trim().split("\n")) {
94
+ const cols = row.split("|").map((c) => c.trim()).filter((c) => c);
95
+ if (cols.length >= 3 && !cols[0].startsWith("<!--")) {
96
+ scaffolding.push({
97
+ condition: cols[0],
98
+ proposedDoc: cols[1],
99
+ seedFrom: cols[2],
100
+ });
101
+ }
102
+ }
103
+ }
104
+
105
+ // Check which docs actually exist on disk
106
+ const docsDir = join(projectDir, "docs");
107
+ const existingDocs = [];
108
+ const missingDocs = [];
109
+ const unmappedDocs = [];
110
+
111
+ for (const item of inventory) {
112
+ if (item.exists) {
113
+ existingDocs.push(item.path);
114
+ } else {
115
+ missingDocs.push(item.path);
116
+ }
117
+ }
118
+
119
+ // Find docs/ files that exist but aren't in the inventory
120
+ if (existsSync(docsDir)) {
121
+ const inventoryPaths = new Set(inventory.map((i) => i.path));
122
+ // Check common docs paths
123
+ const candidates = ["docs/index.md"];
124
+ for (const c of candidates) {
125
+ if (existsSync(join(projectDir, c)) && !inventoryPaths.has(c)) {
126
+ unmappedDocs.push(c);
127
+ }
128
+ }
129
+ }
130
+
131
+ // Count mapped vs unmapped components
132
+ const mappedComponents = mappings.length;
133
+ const docsWithMappings = new Set(mappings.map((m) => m.documentedIn));
134
+
135
+ return {
136
+ inventory,
137
+ mappings,
138
+ scaffolding,
139
+ summary: {
140
+ totalDocs: inventory.length,
141
+ existingDocs: existingDocs.length,
142
+ missingDocs: missingDocs.length,
143
+ unmappedDocs: unmappedDocs.length,
144
+ mappedComponents,
145
+ scaffoldingRules: scaffolding.length,
146
+ },
147
+ existingDocs,
148
+ missingDocs,
149
+ unmappedDocs,
150
+ };
151
+ }
152
+
153
+ // ---------------------------------------------------------------------------
154
+ // Report output
155
+ // ---------------------------------------------------------------------------
156
+
157
+ function printReport(report) {
158
+ const s = report.summary;
159
+
160
+ console.log("\n Docs Coverage Report\n");
161
+ console.log(` Docs inventory: ${s.totalDocs} files (${s.existingDocs} exist, ${s.missingDocs} missing)`);
162
+ console.log(` Component mappings: ${s.mappedComponents}`);
163
+ console.log(` Scaffolding rules: ${s.scaffoldingRules}`);
164
+
165
+ if (report.inventory.length > 0) {
166
+ console.log("\n Docs inventory:");
167
+ for (const item of report.inventory) {
168
+ const icon = item.exists ? "[x]" : "[ ]";
169
+ const status = item.status === "stale" ? " (stale)" : "";
170
+ console.log(` ${icon} ${item.path} — ${item.purpose}${status}`);
171
+ }
172
+ }
173
+
174
+ if (report.missingDocs.length > 0) {
175
+ console.log("\n Missing docs (in inventory but not on disk):");
176
+ for (const p of report.missingDocs) {
177
+ console.log(` - ${p}`);
178
+ }
179
+ }
180
+
181
+ if (report.unmappedDocs.length > 0) {
182
+ console.log("\n Unmapped docs (on disk but not in inventory):");
183
+ for (const p of report.unmappedDocs) {
184
+ console.log(` - ${p}`);
185
+ }
186
+ }
187
+
188
+ if (report.mappings.length > 0) {
189
+ console.log("\n Component mappings:");
190
+ for (const m of report.mappings) {
191
+ console.log(` ${m.component} → ${m.documentedIn}`);
192
+ }
193
+ } else {
194
+ console.log("\n No component-to-docs mappings defined yet.");
195
+ console.log(" Ask your agent to populate .context/DOCS-MAP.md during onboarding.");
196
+ }
197
+
198
+ if (report.scaffolding.length > 0) {
199
+ console.log("\n Scaffolding rules (auto-propose new docs):");
200
+ for (const r of report.scaffolding) {
201
+ console.log(` When: ${r.condition}`);
202
+ console.log(` Create: ${r.proposedDoc}`);
203
+ }
204
+ }
205
+
206
+ console.log("");
207
+ }
@@ -3,8 +3,8 @@
3
3
  * entry point discovery, output formatting.
4
4
  */
5
5
 
6
- import { readFileSync, existsSync } from "node:fs";
7
- import { resolve, join, dirname } from "node:path";
6
+ import { readFileSync, existsSync, writeFileSync, copyFileSync } from "node:fs";
7
+ import { resolve, join, dirname, extname } from "node:path";
8
8
  import { fileURLToPath } from "node:url";
9
9
 
10
10
  const __filename = fileURLToPath(import.meta.url);
@@ -113,6 +113,53 @@ export function basename_of(filepath) {
113
113
  return filepath.split(/[\\/]/).pop();
114
114
  }
115
115
 
116
+ // ---------------------------------------------------------------------------
117
+ // Write-safety invariant
118
+ // ---------------------------------------------------------------------------
119
+
120
+ /** Allowed file extensions for CLI write operations. */
121
+ const SAFE_EXTENSIONS = new Set([".md", ".yaml", ".yml"]);
122
+
123
+ /**
124
+ * Returns true if the file path has an allowed extension for writing.
125
+ * The CLI tool must NEVER modify user source code — only methodology
126
+ * files (.md) and agent-understanding files (.yaml/.yml) are writable.
127
+ */
128
+ export function isSafeToWrite(filePath) {
129
+ const ext = extname(filePath).toLowerCase();
130
+ return SAFE_EXTENSIONS.has(ext);
131
+ }
132
+
133
+ /**
134
+ * Write a file, but only if the extension is safe.
135
+ * Throws if the path would modify user source code.
136
+ */
137
+ export function safeWriteFile(filePath, content, encoding = "utf-8") {
138
+ if (!isSafeToWrite(filePath)) {
139
+ throw new Error(
140
+ `Safety invariant: refusing to write '${filePath}' — ` +
141
+ `only .md, .yaml, and .yml files are allowed. ` +
142
+ `The CLI tool never modifies user source code.`
143
+ );
144
+ }
145
+ writeFileSync(filePath, content, encoding);
146
+ }
147
+
148
+ /**
149
+ * Copy a file, but only if the destination extension is safe.
150
+ * Throws if the destination would modify user source code.
151
+ */
152
+ export function safeCopyFile(src, dst) {
153
+ if (!isSafeToWrite(dst)) {
154
+ throw new Error(
155
+ `Safety invariant: refusing to copy to '${dst}' — ` +
156
+ `only .md, .yaml, and .yml files are allowed. ` +
157
+ `The CLI tool never modifies user source code.`
158
+ );
159
+ }
160
+ copyFileSync(src, dst);
161
+ }
162
+
116
163
  // ---------------------------------------------------------------------------
117
164
  // Output formatting
118
165
  // ---------------------------------------------------------------------------
@@ -0,0 +1,159 @@
1
+ /** wwa implement — show implementation guidance for the current plan step. */
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { resolve, join } from "node:path";
5
+ import { findEntryPoint } from "./helpers.js";
6
+
7
+ export function register(program) {
8
+ program
9
+ .command("implement [directory]")
10
+ .description("Show implementation guidance for the current plan step")
11
+ .option("--json", "Output as JSON")
12
+ .action(async (directory, opts) => {
13
+ directory = directory || ".";
14
+ const d = resolve(directory);
15
+
16
+ const planPath = join(d, "PLAN.md");
17
+ if (!existsSync(planPath)) {
18
+ console.error(
19
+ "No PLAN.md found. Run 'wwa init' to set up methodology files."
20
+ );
21
+ process.exit(1);
22
+ }
23
+
24
+ const planContent = readFileSync(planPath, "utf-8");
25
+ const steps = parseSteps(planContent);
26
+ const nextStep = steps.find((s) => s.status !== "done");
27
+
28
+ // Read STATE.md for context
29
+ const statePath = join(d, "STATE.md");
30
+ let position = null;
31
+ if (existsSync(statePath)) {
32
+ const stateContent = readFileSync(statePath, "utf-8");
33
+ position = extractPosition(stateContent);
34
+ }
35
+
36
+ // Read entry point for scoping and cascade rules
37
+ const ep = findEntryPoint(directory);
38
+ let scopingRules = [];
39
+ let cascadeRules = [];
40
+ if (ep) {
41
+ const epContent = readFileSync(ep, "utf-8");
42
+ scopingRules = extractTableRows(epContent, "Scoping rules");
43
+ cascadeRules = extractTableRows(epContent, "Dependency cascade");
44
+ }
45
+
46
+ if (opts.json) {
47
+ console.log(
48
+ JSON.stringify(
49
+ { position, nextStep, totalSteps: steps.length, scopingRules, cascadeRules },
50
+ null,
51
+ 2
52
+ )
53
+ );
54
+ return;
55
+ }
56
+
57
+ if (!nextStep) {
58
+ console.log("\n All plan steps are complete.");
59
+ if (position) {
60
+ console.log(` Phase: ${position.phase}`);
61
+ console.log(` Status: ${position.status}`);
62
+ }
63
+ console.log(
64
+ "\n Next actions:" +
65
+ "\n - Verify all criteria pass: wwa plan" +
66
+ "\n - Close the phase: update STATE.md, ROADMAP.md, SUMMARY.md" +
67
+ "\n - Run session close: wwa close\n"
68
+ );
69
+ return;
70
+ }
71
+
72
+ const done = steps.filter((s) => s.status === "done").length;
73
+ console.log(`\n Implementation guidance`);
74
+ console.log(` Progress: ${done}/${steps.length} steps complete\n`);
75
+
76
+ console.log(` Next step: #${nextStep.num}`);
77
+ console.log(` Deliverable: ${nextStep.deliverable}`);
78
+ if (nextStep.read) console.log(` Read first: ${nextStep.read}`);
79
+ if (nextStep.produce) console.log(` Produce: ${nextStep.produce}`);
80
+
81
+ // Show relevant scoping rules
82
+ if (scopingRules.length > 0) {
83
+ console.log(`\n Relevant scoping rules (from entry point):`);
84
+ for (const rule of scopingRules.slice(0, 5)) {
85
+ console.log(` ${rule}`);
86
+ }
87
+ }
88
+
89
+ // Show cascade reminders
90
+ if (cascadeRules.length > 0) {
91
+ console.log(`\n Cascade rules to remember:`);
92
+ for (const rule of cascadeRules.slice(0, 5)) {
93
+ console.log(` ${rule}`);
94
+ }
95
+ }
96
+
97
+ console.log(
98
+ "\n After completing this step:" +
99
+ "\n - Record any decisions in STATE.md" +
100
+ "\n - Check cascade table for dependent updates" +
101
+ "\n - Mark step as 'done' in PLAN.md\n"
102
+ );
103
+ });
104
+ }
105
+
106
+ function parseSteps(content) {
107
+ const steps = [];
108
+ const stepPattern =
109
+ /\|\s*(\d+)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(.+?)\s*\|\s*(\w+)\s*\|/g;
110
+ let m;
111
+ while ((m = stepPattern.exec(content)) !== null) {
112
+ steps.push({
113
+ num: parseInt(m[1], 10),
114
+ deliverable: m[2].trim(),
115
+ read: m[3].trim(),
116
+ produce: m[4].trim(),
117
+ status: m[5].trim().toLowerCase(),
118
+ });
119
+ }
120
+ return steps;
121
+ }
122
+
123
+ function extractPosition(stateContent) {
124
+ const phase =
125
+ stateContent.match(/\*\*Phase\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
126
+ const status =
127
+ stateContent.match(/\*\*Status\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
128
+ return { phase, status };
129
+ }
130
+
131
+ function extractTableRows(content, sectionName) {
132
+ const rows = [];
133
+ const sectionIdx = content.indexOf(sectionName);
134
+ if (sectionIdx === -1) return rows;
135
+
136
+ const afterSection = content.slice(sectionIdx);
137
+ const nextHeading = afterSection.indexOf("\n## ", 1);
138
+ const block =
139
+ nextHeading > 0 ? afterSection.slice(0, nextHeading) : afterSection;
140
+
141
+ for (const line of block.split("\n")) {
142
+ if (
143
+ line.startsWith("|") &&
144
+ !line.startsWith("| Query") &&
145
+ !line.startsWith("| When") &&
146
+ !line.startsWith("|--") &&
147
+ !line.startsWith("|-")
148
+ ) {
149
+ const cols = line
150
+ .split("|")
151
+ .map((c) => c.trim())
152
+ .filter((c) => c);
153
+ if (cols.length >= 2) {
154
+ rows.push(`${cols[0]} → ${cols[1]}`);
155
+ }
156
+ }
157
+ }
158
+ return rows;
159
+ }
package/lib/cli/init.js CHANGED
@@ -15,6 +15,7 @@ export function register(program) {
15
15
  .option("--tier <tier>", "Template tier (starter/full)")
16
16
  .option("--runtime <runtime>", "Agent runtime (claude/cursor/all)")
17
17
  .option("--profile <profile>", "Integration profile (lite/standard/full)")
18
+ .option("--onboarding <mode>", "Onboarding mode (greenfield/brownfield/auto)")
18
19
  .option("--registry <path>", "Path to feature-registry.yaml")
19
20
  .option("--json", "Output as JSON")
20
21
  .option(
@@ -89,9 +90,9 @@ export function register(program) {
89
90
  name: "runtime",
90
91
  message: "Agent runtime:",
91
92
  choices: [
92
- { name: "Claude Code — creates CLAUDE.md", value: "claude" },
93
- { name: "Cursor — creates .cursorrules", value: "cursor" },
94
- { name: "Other / All keeps all entry points", value: "all" },
93
+ { name: "Claude Code — creates CLAUDE.md", value: "claude" },
94
+ { name: "Cursor / Composer — creates .cursorrules", value: "cursor" },
95
+ { name: "Other (Gemini, Codex, Cline, Aider, etc.) creates AGENT.md + all entry points", value: "all" },
95
96
  ],
96
97
  },
97
98
  ]);
@@ -120,20 +121,37 @@ export function register(program) {
120
121
  name: "profile",
121
122
  message: "Integration profile:",
122
123
  choices: [
123
- { name: "Standard (Sonnet) — recommended", value: "standard" },
124
- { name: "Lite (Haiku) — minimal rules, simple projects", value: "lite" },
125
- { name: "Full (Opus) — all rules inline, complex projects", value: "full" },
124
+ { name: "Standard (Sonnet, GPT-4, Gemini Pro, Codex) — recommended", value: "standard" },
125
+ { name: "Lite (Haiku, GPT-3.5, Gemini Flash) — minimal rules, simple projects", value: "lite" },
126
+ { name: "Full (Opus, o1, Gemini Ultra) — all rules inline, complex projects", value: "full" },
126
127
  ],
127
128
  },
128
129
  ]);
129
130
  answers.profile = profile;
130
131
  }
131
132
 
133
+ if (!opts.onboarding) {
134
+ const { onboarding } = await inquirer.prompt([
135
+ {
136
+ type: "list",
137
+ name: "onboarding",
138
+ message: "Project scenario:",
139
+ choices: [
140
+ { name: "Greenfield — new project, no existing code", value: "greenfield" },
141
+ { name: "Brownfield — existing codebase, add methodology", value: "brownfield" },
142
+ { name: "Agent decides — let the agent detect on first session", value: "auto" },
143
+ ],
144
+ },
145
+ ]);
146
+ answers.onboarding = onboarding;
147
+ }
148
+
132
149
  projectTypeArg = projectTypeArg || answers.type;
133
150
  directory = directory || answers.dir;
134
151
  opts.runtime = opts.runtime || answers.runtime;
135
152
  opts.tier = opts.tier || answers.tier;
136
153
  opts.profile = opts.profile || answers.profile;
154
+ opts.onboarding = opts.onboarding || answers.onboarding;
137
155
  }
138
156
 
139
157
  const projectType = resolveProjectType(projectTypeArg);
@@ -144,6 +162,7 @@ export function register(program) {
144
162
  tier: opts.tier || "starter",
145
163
  runtime: opts.runtime || "all",
146
164
  profile: opts.profile || "standard",
165
+ onboarding: opts.onboarding || "auto",
147
166
  registryPath: opts.registry,
148
167
  });
149
168
  });
@@ -0,0 +1,128 @@
1
+ /** wwa plan — display current plan status. */
2
+
3
+ import { readFileSync, existsSync } from "node:fs";
4
+ import { resolve, join } from "node:path";
5
+ import { findEntryPoint } from "./helpers.js";
6
+
7
+ export function register(program) {
8
+ program
9
+ .command("plan [directory]")
10
+ .description("Display current plan status from PLAN.md")
11
+ .option("--json", "Output as JSON")
12
+ .action(async (directory, opts) => {
13
+ directory = directory || ".";
14
+ const d = resolve(directory);
15
+
16
+ const planPath = join(d, "PLAN.md");
17
+ if (!existsSync(planPath)) {
18
+ console.error(
19
+ "No PLAN.md found in current directory.\n" +
20
+ "Run 'wwa init' to set up methodology files, or specify a directory."
21
+ );
22
+ process.exit(1);
23
+ }
24
+
25
+ const content = readFileSync(planPath, "utf-8");
26
+ const parsed = parsePlan(content);
27
+
28
+ // Also read STATE.md for current position
29
+ const statePath = join(d, "STATE.md");
30
+ let position = null;
31
+ if (existsSync(statePath)) {
32
+ const stateContent = readFileSync(statePath, "utf-8");
33
+ position = extractPosition(stateContent);
34
+ }
35
+
36
+ if (opts.json) {
37
+ console.log(JSON.stringify({ position, plan: parsed }, null, 2));
38
+ } else {
39
+ if (position) {
40
+ console.log(`\n Current position:`);
41
+ console.log(` Phase: ${position.phase}`);
42
+ console.log(` Status: ${position.status}`);
43
+ if (position.next) console.log(` Next: ${position.next}`);
44
+ }
45
+
46
+ console.log(`\n Plan: ${parsed.title}`);
47
+
48
+ if (parsed.objective) {
49
+ console.log(`\n Objective: ${parsed.objective}`);
50
+ }
51
+
52
+ if (parsed.steps.length > 0) {
53
+ const done = parsed.steps.filter((s) => s.status === "done").length;
54
+ const total = parsed.steps.length;
55
+ console.log(`\n Progress: ${done}/${total} steps complete`);
56
+ console.log("");
57
+
58
+ for (const step of parsed.steps) {
59
+ const icon = step.status === "done" ? "[x]" : "[ ]";
60
+ console.log(` ${icon} Step ${step.num}: ${step.deliverable}`);
61
+ }
62
+ }
63
+
64
+ if (parsed.verification.length > 0) {
65
+ const passed = parsed.verification.filter((v) => v.checked).length;
66
+ console.log(
67
+ `\n Verification: ${passed}/${parsed.verification.length} criteria met`
68
+ );
69
+ }
70
+
71
+ console.log("");
72
+ }
73
+ });
74
+ }
75
+
76
+ function parsePlan(content) {
77
+ const lines = content.split("\n");
78
+
79
+ // Extract title from first heading
80
+ const titleMatch = content.match(/^#\s+(.+)$/m);
81
+ const title = titleMatch ? titleMatch[1].trim() : "Unknown";
82
+
83
+ // Extract phase heading
84
+ const phaseMatch = content.match(/^##\s+(.+)$/m);
85
+ const phase = phaseMatch ? phaseMatch[1].trim() : title;
86
+
87
+ // Extract objective paragraph
88
+ let objective = null;
89
+ const objMatch = content.match(/###\s+Objective\s*\n\s*\n(.+?)(?:\n\n|\n###)/s);
90
+ if (objMatch) {
91
+ objective = objMatch[1].trim().split("\n")[0];
92
+ }
93
+
94
+ // Parse implementation table
95
+ const steps = [];
96
+ const stepPattern =
97
+ /\|\s*(\d+)\s*\|\s*(.+?)\s*\|\s*.+?\s*\|\s*.+?\s*\|\s*(\w+)\s*\|/g;
98
+ let m;
99
+ while ((m = stepPattern.exec(content)) !== null) {
100
+ steps.push({
101
+ num: parseInt(m[1], 10),
102
+ deliverable: m[2].trim(),
103
+ status: m[3].trim().toLowerCase(),
104
+ });
105
+ }
106
+
107
+ // Parse verification criteria
108
+ const verification = [];
109
+ const verPattern = /- \[([ x])\]\s*(.+)/g;
110
+ while ((m = verPattern.exec(content)) !== null) {
111
+ verification.push({
112
+ checked: m[1] === "x",
113
+ criterion: m[2].trim(),
114
+ });
115
+ }
116
+
117
+ return { title, phase, objective, steps, verification };
118
+ }
119
+
120
+ function extractPosition(stateContent) {
121
+ const phase =
122
+ stateContent.match(/\*\*Phase\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
123
+ const status =
124
+ stateContent.match(/\*\*Status\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
125
+ const next =
126
+ stateContent.match(/\*\*Next\*\*:\s*(.+)/)?.[1]?.trim() || null;
127
+ return { phase, status, next };
128
+ }