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
@@ -0,0 +1,280 @@
1
+ /** wwa init — set up a new project with methodology templates. */
2
+
3
+ import {
4
+ resolveProjectType,
5
+ getPipeline,
6
+ loadRegistryData,
7
+ outputData,
8
+ PROJECT_TYPE_ALIASES,
9
+ packageRoot,
10
+ safeCopyFile,
11
+ } from "./helpers.js";
12
+ import { existsSync, mkdirSync } from "node:fs";
13
+ import { join, dirname, resolve as pathResolve } from "node:path";
14
+
15
+ export function register(program) {
16
+ program
17
+ .command("init [project-type] [directory]")
18
+ .description("Set up a new project with methodology templates")
19
+ .option("--tier <tier>", "Template tier (starter/full)")
20
+ .option("--runtime <runtime>", "Agent runtime (claude/cursor/all)")
21
+ .option("--profile <profile>", "Integration profile (lite/standard/full)")
22
+ .option("--onboarding <mode>", "Onboarding mode (greenfield/brownfield/auto)")
23
+ .option("--registry <path>", "Path to feature-registry.yaml")
24
+ .option("--json", "Output as JSON")
25
+ .option("--validate-after", "Run wwa check after init")
26
+ .option("--auto-configure", "Use defaults for optional settings (boundaries enabled, standard paths)")
27
+ .option("--project-name <name>", "Project name for entry point")
28
+ .option("--description <text>", "Short project description for entry point")
29
+ .option(
30
+ "--describe",
31
+ "Only describe what the entry point should contain (no file creation)"
32
+ )
33
+ .action(async (projectTypeArg, directory, opts) => {
34
+ // If describe mode with a type, show entry point spec
35
+ if (opts.describe && projectTypeArg) {
36
+ const projectType = resolveProjectType(projectTypeArg);
37
+ const { generateEntryPoint } = await getPipeline();
38
+ const reg = await loadRegistryData(opts.registry);
39
+ const result = generateEntryPoint(projectType, opts.tier || "starter", reg);
40
+
41
+ let friendly = projectType;
42
+ for (const [alias, internal] of Object.entries(PROJECT_TYPE_ALIASES)) {
43
+ if (internal === projectType && alias !== projectType) {
44
+ friendly = `${alias} (${projectType})`;
45
+ break;
46
+ }
47
+ }
48
+
49
+ console.log(
50
+ `Entry point specification for: ${friendly} (${opts.tier || "starter"})`
51
+ );
52
+ outputData(result, opts.json);
53
+ return;
54
+ }
55
+
56
+ // Interactive mode — prompt for missing values
57
+ if (!projectTypeArg || !directory) {
58
+ const inquirer = (await import("inquirer")).default;
59
+
60
+ console.log("\n wwa — Set up AI-agent-assisted development\n");
61
+
62
+ const answers = {};
63
+
64
+ if (!projectTypeArg) {
65
+ const { type } = await inquirer.prompt([
66
+ {
67
+ type: "list",
68
+ name: "type",
69
+ message: "Project type:",
70
+ choices: [
71
+ { name: "Code — software project", value: "code" },
72
+ { name: "Data — data index/querying", value: "data" },
73
+ { name: "Analytical — prompts, chains, evaluation", value: "context" },
74
+ { name: "Mixed — multiple types combined", value: "mix" },
75
+ { name: "General — universal rules only", value: "general" },
76
+ ],
77
+ },
78
+ ]);
79
+ answers.type = type;
80
+ }
81
+
82
+ if (!directory) {
83
+ const { dir } = await inquirer.prompt([
84
+ {
85
+ type: "input",
86
+ name: "dir",
87
+ message: "Project directory:",
88
+ default: ".",
89
+ },
90
+ ]);
91
+ answers.dir = dir;
92
+ }
93
+
94
+ if (!opts.runtime) {
95
+ const { runtime } = await inquirer.prompt([
96
+ {
97
+ type: "list",
98
+ name: "runtime",
99
+ message: "Agent runtime:",
100
+ choices: [
101
+ { name: "Claude Code — creates CLAUDE.md", value: "claude" },
102
+ { name: "Cursor / Composer — creates .cursorrules", value: "cursor" },
103
+ { name: "Other (Gemini, Codex, Cline, Aider, etc.) — creates AGENT.md + all entry points", value: "all" },
104
+ ],
105
+ },
106
+ ]);
107
+ answers.runtime = runtime;
108
+ }
109
+
110
+ if (!opts.tier) {
111
+ const { tier } = await inquirer.prompt([
112
+ {
113
+ type: "list",
114
+ name: "tier",
115
+ message: "Template tier:",
116
+ choices: [
117
+ { name: "Starter (7 files) — recommended for most projects", value: "starter" },
118
+ { name: "Full (11+ files) — complex or long-running projects", value: "full" },
119
+ ],
120
+ },
121
+ ]);
122
+ answers.tier = tier;
123
+ }
124
+
125
+ if (!opts.profile) {
126
+ const { profile } = await inquirer.prompt([
127
+ {
128
+ type: "list",
129
+ name: "profile",
130
+ message: "Integration profile:",
131
+ choices: [
132
+ { name: "Standard (Sonnet, GPT-4, Gemini Pro, Codex) — recommended", value: "standard" },
133
+ { name: "Lite (Haiku, GPT-3.5, Gemini Flash) — minimal rules, simple projects", value: "lite" },
134
+ { name: "Full (Opus, o1, Gemini Ultra) — all rules inline, complex projects", value: "full" },
135
+ ],
136
+ },
137
+ ]);
138
+ answers.profile = profile;
139
+ }
140
+
141
+ if (!opts.onboarding) {
142
+ const { onboarding } = await inquirer.prompt([
143
+ {
144
+ type: "list",
145
+ name: "onboarding",
146
+ message: "Project scenario:",
147
+ choices: [
148
+ { name: "Greenfield — new project, no existing code", value: "greenfield" },
149
+ { name: "Brownfield — existing codebase, add methodology", value: "brownfield" },
150
+ { name: "Agent decides — let the agent detect on first session", value: "auto" },
151
+ ],
152
+ },
153
+ ]);
154
+ answers.onboarding = onboarding;
155
+ }
156
+
157
+ // Phase 7q: Optional project name and description (skip if --auto-configure or flags set)
158
+ if (!opts["project-name"] && !opts.autoConfigure) {
159
+ const { projectName } = await inquirer.prompt([
160
+ {
161
+ type: "input",
162
+ name: "projectName",
163
+ message: "Project name (for entry point):",
164
+ default: "My project",
165
+ },
166
+ ]);
167
+ answers.projectName = projectName;
168
+ }
169
+ if (!opts.description && !opts.autoConfigure) {
170
+ const { description } = await inquirer.prompt([
171
+ {
172
+ type: "input",
173
+ name: "description",
174
+ message: "Short project description (1 line):",
175
+ default: "AI-agent-assisted project using wwa methodology.",
176
+ },
177
+ ]);
178
+ answers.description = description;
179
+ }
180
+
181
+ projectTypeArg = projectTypeArg || answers.type;
182
+ directory = directory || answers.dir;
183
+ opts.runtime = opts.runtime || answers.runtime;
184
+ opts.tier = opts.tier || answers.tier;
185
+ opts.profile = opts.profile || answers.profile;
186
+ opts.onboarding = opts.onboarding || answers.onboarding;
187
+ opts.projectName = opts["project-name"] || answers.projectName;
188
+ opts.description = opts.description || answers.description;
189
+ }
190
+
191
+ const projectType = resolveProjectType(projectTypeArg);
192
+
193
+ // Phase 7r: Brownfield onboarding questionnaire (project purpose, audience, spec location)
194
+ // Always run when onboarding=brownfield and not auto-configured, even if other args were provided.
195
+ if (opts.onboarding === "brownfield" && !opts.autoConfigure && !opts.brownfieldQuestionnaire) {
196
+ const inquirer = (await import("inquirer")).default;
197
+ const brownfieldAnswers = await inquirer.prompt([
198
+ {
199
+ type: "list",
200
+ name: "projectPurpose",
201
+ message: "What is this project for?",
202
+ choices: [
203
+ { name: "Review / audit", value: "review" },
204
+ { name: "Production / ongoing development", value: "production" },
205
+ { name: "Case study / methodology validation", value: "case study" },
206
+ { name: "Other", value: "other" },
207
+ ],
208
+ },
209
+ {
210
+ type: "list",
211
+ name: "audience",
212
+ message: "Who is the primary audience?",
213
+ choices: [
214
+ { name: "Reviewers (read-only)", value: "reviewers" },
215
+ { name: "Maintainers (editing)", value: "maintainers" },
216
+ { name: "Both", value: "both" },
217
+ ],
218
+ },
219
+ {
220
+ type: "list",
221
+ name: "specLocation",
222
+ message: "Is the methodology spec local or remote?",
223
+ choices: [
224
+ { name: "Local (in this repo)", value: "local" },
225
+ { name: "Remote (external reference)", value: "remote" },
226
+ ],
227
+ },
228
+ ]);
229
+ opts.brownfieldQuestionnaire = brownfieldAnswers;
230
+ }
231
+
232
+ // Setup mode
233
+ const { initProject } = await import("../init.js");
234
+ await initProject(projectType, directory, {
235
+ tier: opts.tier || "starter",
236
+ runtime: opts.runtime || "all",
237
+ profile: opts.profile || "standard",
238
+ onboarding: opts.onboarding || "auto",
239
+ registryPath: opts.registry,
240
+ projectName: opts["project-name"] || opts.projectName,
241
+ description: opts.description,
242
+ autoConfigure: opts["auto-configure"],
243
+ brownfieldQuestionnaire: opts.brownfieldQuestionnaire,
244
+ });
245
+
246
+ // Ensure CLI command metadata is available in the new project.
247
+ // Only docs/internal/cli-commands.yaml is copied from the package;
248
+ // all other docs/internal files remain dev-only.
249
+ try {
250
+ const projectDir = pathResolve(directory);
251
+ const src = join(packageRoot, "docs", "internal", "cli-commands.yaml");
252
+ const dst = join(projectDir, "docs", "internal", "cli-commands.yaml");
253
+ if (existsSync(src) && !existsSync(dst)) {
254
+ mkdirSync(dirname(dst), { recursive: true });
255
+ safeCopyFile(src, dst);
256
+ }
257
+ } catch {
258
+ // Best-effort; init should not fail if CLI metadata copy fails.
259
+ }
260
+
261
+ // Phase 7q: Optional post-init validation
262
+ if (opts["validate-after"]) {
263
+ const { execSync } = await import("node:child_process");
264
+ const path = await import("node:path");
265
+ const { fileURLToPath } = await import("node:url");
266
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
267
+ const binPath = path.join(__dirname, "..", "..", "bin", "wwa.js");
268
+ const targetDir = path.resolve(directory);
269
+ console.log("\nRunning wwa check (--validate-after)...\n");
270
+ try {
271
+ execSync(`node "${binPath}" check "${targetDir}"`, {
272
+ stdio: "inherit",
273
+ cwd: targetDir,
274
+ });
275
+ } catch (err) {
276
+ console.warn("wwa check reported issues. Fix any validation failures above.");
277
+ }
278
+ }
279
+ });
280
+ }
@@ -0,0 +1,206 @@
1
+ /** wwa pipeline — advanced subcommands for debugging and testing. */
2
+
3
+ import { resolve, dirname, join } from "node:path";
4
+ import { existsSync, readdirSync } from "node:fs";
5
+ import {
6
+ resolveProjectType,
7
+ getPipeline,
8
+ getRegistry,
9
+ loadRegistryData,
10
+ outputData,
11
+ } from "./helpers.js";
12
+ import { printCliReport, writeLastRunSummary } from "./report-helpers.js";
13
+
14
+ export function register(program) {
15
+ const pipeline = program
16
+ .command("pipeline")
17
+ .description(
18
+ "Advanced pipeline commands for debugging and testing.\n" +
19
+ "Most users should use check, scan, route instead."
20
+ );
21
+
22
+ pipeline
23
+ .command("classify <query>")
24
+ .description("Classify a natural language query (S1)")
25
+ .option(
26
+ "-p, --project-type <type>",
27
+ "Project type: code, context, data, mix, general",
28
+ "general"
29
+ )
30
+ .option("--registry <path>", "Path to feature-registry.yaml")
31
+ .option("--json", "Output as JSON")
32
+ .action(async (query, opts) => {
33
+ const { classify } = await getPipeline();
34
+ const reg = await loadRegistryData(opts.registry);
35
+ const projectType = resolveProjectType(opts.projectType);
36
+ const result = classify(query, projectType, reg);
37
+ const queryType = result.query_type || result.classified_as || "—";
38
+ if (opts.json) {
39
+ console.log(`Query: "${query}"`);
40
+ outputData(result, true);
41
+ } else {
42
+ const summary = `Classified as query_type: ${queryType}.`;
43
+ const inputRef = `query="${query}", project type=${projectType}`;
44
+ printCliReport({ command: "pipeline classify", inputRef, summary });
45
+ console.log(" Detail — Query: \"" + query + "\"");
46
+ outputData(result, false);
47
+ writeLastRunSummary(".", { command: "pipeline classify", inputRef, summary });
48
+ }
49
+ });
50
+
51
+ pipeline
52
+ .command("select <query-type> <project-type>")
53
+ .description("Select workflow for a query type (S2)")
54
+ .option("--first-session", "Force WF-04 bootstrap workflow")
55
+ .option("--registry <path>", "Path to feature-registry.yaml")
56
+ .option("--json", "Output as JSON")
57
+ .action(async (queryType, projectType, opts) => {
58
+ const { selectWorkflow } = await getPipeline();
59
+ await loadRegistryData(opts.registry);
60
+ const result = selectWorkflow(
61
+ queryType,
62
+ projectType,
63
+ opts.firstSession || false
64
+ );
65
+ const wfId = result.workflow_id || result.workflow || "—";
66
+ if (opts.json) {
67
+ outputData(result, true);
68
+ } else {
69
+ const summary = `Workflow selected: ${wfId}.`;
70
+ const inputRef = `query-type=${queryType}, project-type=${projectType}`;
71
+ printCliReport({ command: "pipeline select", inputRef, summary });
72
+ console.log(" Detail");
73
+ outputData(result, false);
74
+ writeLastRunSummary(".", { command: "pipeline select", inputRef, summary });
75
+ }
76
+ });
77
+
78
+ pipeline
79
+ .command("resolve <workflow-id> <stage>")
80
+ .description("Resolve features for a workflow stage (S3)")
81
+ .option("--registry <path>", "Path to feature-registry.yaml")
82
+ .option("--json", "Output as JSON")
83
+ .action(async (workflowId, stage, opts) => {
84
+ const { resolveFeatures } = await getPipeline();
85
+ const reg = await loadRegistryData(opts.registry);
86
+ const result = resolveFeatures(workflowId, stage, reg);
87
+ const features = result.features || result.read_set || [];
88
+ const n = Array.isArray(features) ? features.length : 0;
89
+ if (opts.json) {
90
+ console.log(`Workflow: ${workflowId} | Stage: ${stage}`);
91
+ outputData(result, true);
92
+ } else {
93
+ const summary = `Features resolved for ${workflowId} @ ${stage}: ${n} item(s).`;
94
+ const inputRef = `workflow=${workflowId}, stage=${stage}`;
95
+ printCliReport({ command: "pipeline resolve", inputRef, summary });
96
+ console.log(" Detail — Workflow: " + workflowId + " | Stage: " + stage);
97
+ outputData(result, false);
98
+ writeLastRunSummary(".", { command: "pipeline resolve", inputRef, summary });
99
+ }
100
+ });
101
+
102
+ pipeline
103
+ .command("cascade <trigger>")
104
+ .description("Compute cascade chain from a trigger (S5)")
105
+ .option(
106
+ "--context <context>",
107
+ "Cascade context: code, context, data, mix, universal",
108
+ "universal"
109
+ )
110
+ .option("--registry <path>", "Path to feature-registry.yaml")
111
+ .option("--json", "Output as JSON")
112
+ .action(async (trigger, opts) => {
113
+ const { resolveCascade } = await getPipeline();
114
+ await loadRegistryData(opts.registry);
115
+ const context =
116
+ opts.context !== "universal"
117
+ ? resolveProjectType(opts.context)
118
+ : opts.context;
119
+ const result = resolveCascade([], trigger, context);
120
+ const chain = result.chain || result.targets || result.files || [];
121
+ const n = Array.isArray(chain) ? chain.length : 0;
122
+ if (opts.json) {
123
+ outputData(result, true);
124
+ } else {
125
+ const summary = `Cascade for trigger "${trigger}": ${n} step(s).`;
126
+ const inputRef = `trigger=${trigger}, context=${context}`;
127
+ printCliReport({ command: "pipeline cascade", inputRef, summary });
128
+ console.log(" Detail");
129
+ outputData(result, false);
130
+ writeLastRunSummary(".", { command: "pipeline cascade", inputRef, summary });
131
+ }
132
+ });
133
+
134
+ pipeline
135
+ .command("test [fixtures...]")
136
+ .description("Run YAML test fixtures against the pipeline")
137
+ .option("--registry <path>", "Path to feature-registry.yaml")
138
+ .action(async (fixtures, opts) => {
139
+ const { runFixture } = await getPipeline();
140
+ const { findRegistry } = await getRegistry();
141
+ const reg = await loadRegistryData(opts.registry);
142
+
143
+ let files;
144
+ if (fixtures && fixtures.length > 0) {
145
+ files = fixtures.map((f) => resolve(f));
146
+ } else {
147
+ let regPath;
148
+ try {
149
+ regPath = opts.registry ? resolve(opts.registry) : findRegistry();
150
+ } catch {
151
+ regPath = ".";
152
+ }
153
+ const fixtureDir = resolve(
154
+ dirname(regPath), "..", "..", "tests", "fixtures"
155
+ );
156
+ const altDir = resolve(
157
+ dirname(new URL(import.meta.url).pathname), "..", "..", "tests", "fixtures"
158
+ );
159
+ const searchDir = existsSync(fixtureDir) ? fixtureDir : altDir;
160
+
161
+ if (existsSync(searchDir)) {
162
+ files = readdirSync(searchDir)
163
+ .filter((f) => f.startsWith("pipeline-") && f.endsWith(".yaml"))
164
+ .sort()
165
+ .map((f) => join(searchDir, f));
166
+ } else {
167
+ files = [];
168
+ }
169
+ }
170
+
171
+ if (files.length === 0) {
172
+ console.error("No fixture files found.");
173
+ process.exit(1);
174
+ }
175
+
176
+ let totalPassed = 0;
177
+ let totalFailed = 0;
178
+ for (const fpath of files) {
179
+ if (!existsSync(fpath)) {
180
+ console.log(` Fixture not found: ${fpath}`);
181
+ continue;
182
+ }
183
+ const result = runFixture(fpath, reg);
184
+ const fileStatus = result.failed === 0 ? "PASS" : "FAIL";
185
+ console.log(
186
+ ` ${result.stage.padEnd(12)} ${result.passed}/${result.total} passed [${fileStatus}]`
187
+ );
188
+ totalPassed += result.passed;
189
+ totalFailed += result.failed;
190
+
191
+ for (const detail of result.details) {
192
+ if (detail.status === "FAIL") {
193
+ console.log(` FAIL ${detail.id}: ${detail.description}`);
194
+ if (detail.actual) {
195
+ console.log(` actual: ${JSON.stringify(detail.actual)}`);
196
+ }
197
+ }
198
+ }
199
+ }
200
+
201
+ console.log(
202
+ `\n Total: ${totalPassed} passed, ${totalFailed} failed, ${totalPassed + totalFailed} total`
203
+ );
204
+ process.exit(totalFailed > 0 ? 1 : 0);
205
+ });
206
+ }
@@ -0,0 +1,140 @@
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
+ import { printCliReport, writeLastRunSummary } from "./report-helpers.js";
7
+
8
+ export function register(program) {
9
+ program
10
+ .command("plan [directory]")
11
+ .description("Display current plan status from PLAN.md")
12
+ .option("--json", "Output as JSON")
13
+ .action(async (directory, opts) => {
14
+ directory = directory || ".";
15
+ const d = resolve(directory);
16
+
17
+ const planPath = join(d, "PLAN.md");
18
+ if (!existsSync(planPath)) {
19
+ console.error(
20
+ "No PLAN.md found in current directory.\n" +
21
+ "Run 'wwa init' to set up methodology files, or specify a directory."
22
+ );
23
+ process.exit(1);
24
+ }
25
+
26
+ const content = readFileSync(planPath, "utf-8");
27
+ const parsed = parsePlan(content);
28
+
29
+ // Also read STATE.md for current position
30
+ const statePath = join(d, "STATE.md");
31
+ let position = null;
32
+ if (existsSync(statePath)) {
33
+ const stateContent = readFileSync(statePath, "utf-8");
34
+ position = extractPosition(stateContent);
35
+ }
36
+
37
+ if (opts.json) {
38
+ console.log(JSON.stringify({ position, plan: parsed }, null, 2));
39
+ } else {
40
+ const done = parsed.steps.filter((s) => s.status === "done").length;
41
+ const total = parsed.steps.length;
42
+ const summary = [
43
+ position ? `Phase: ${position.phase}, status: ${position.status}.` : "No STATE.md position.",
44
+ position?.next ? `Next: ${position.next}.` : "",
45
+ `${done}/${total} steps complete. Plan: ${parsed.title}`,
46
+ ]
47
+ .filter(Boolean)
48
+ .join(" ");
49
+ const inputRef = `directory=${directory}`;
50
+ printCliReport({ command: "plan", inputRef, summary });
51
+ console.log(" Detail");
52
+ if (position) {
53
+ console.log(`\n Current position:`);
54
+ console.log(` Phase: ${position.phase}`);
55
+ console.log(` Status: ${position.status}`);
56
+ if (position.next) console.log(` Next: ${position.next}`);
57
+ }
58
+
59
+ console.log(`\n Plan: ${parsed.title}`);
60
+
61
+ if (parsed.objective) {
62
+ console.log(`\n Objective: ${parsed.objective}`);
63
+ }
64
+
65
+ if (parsed.steps.length > 0) {
66
+ console.log(`\n Progress: ${done}/${total} steps complete`);
67
+ console.log("");
68
+
69
+ for (const step of parsed.steps) {
70
+ const icon = step.status === "done" ? "[x]" : "[ ]";
71
+ console.log(` ${icon} Step ${step.num}: ${step.deliverable}`);
72
+ }
73
+ }
74
+
75
+ if (parsed.verification.length > 0) {
76
+ const passed = parsed.verification.filter((v) => v.checked).length;
77
+ console.log(
78
+ `\n Verification: ${passed}/${parsed.verification.length} criteria met`
79
+ );
80
+ }
81
+
82
+ console.log("");
83
+ writeLastRunSummary(d, { command: "plan", inputRef, summary });
84
+ }
85
+ });
86
+ }
87
+
88
+ function parsePlan(content) {
89
+ const lines = content.split("\n");
90
+
91
+ // Extract title from first heading
92
+ const titleMatch = content.match(/^#\s+(.+)$/m);
93
+ const title = titleMatch ? titleMatch[1].trim() : "Unknown";
94
+
95
+ // Extract phase heading
96
+ const phaseMatch = content.match(/^##\s+(.+)$/m);
97
+ const phase = phaseMatch ? phaseMatch[1].trim() : title;
98
+
99
+ // Extract objective paragraph
100
+ let objective = null;
101
+ const objMatch = content.match(/###\s+Objective\s*\n\s*\n(.+?)(?:\n\n|\n###)/s);
102
+ if (objMatch) {
103
+ objective = objMatch[1].trim().split("\n")[0];
104
+ }
105
+
106
+ // Parse implementation table
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
+ status: m[3].trim().toLowerCase(),
116
+ });
117
+ }
118
+
119
+ // Parse verification criteria
120
+ const verification = [];
121
+ const verPattern = /- \[([ x])\]\s*(.+)/g;
122
+ while ((m = verPattern.exec(content)) !== null) {
123
+ verification.push({
124
+ checked: m[1] === "x",
125
+ criterion: m[2].trim(),
126
+ });
127
+ }
128
+
129
+ return { title, phase, objective, steps, verification };
130
+ }
131
+
132
+ function extractPosition(stateContent) {
133
+ const phase =
134
+ stateContent.match(/\*\*Phase\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
135
+ const status =
136
+ stateContent.match(/\*\*Status\*\*:\s*(.+)/)?.[1]?.trim() || "Unknown";
137
+ const next =
138
+ stateContent.match(/\*\*Next\*\*:\s*(.+)/)?.[1]?.trim() || null;
139
+ return { phase, status, next };
140
+ }