agent-method 1.5.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +343 -0
- package/bin/wwa.js +115 -0
- package/docs/internal/cli-commands.yaml +259 -0
- package/docs/internal/doc-tokens.yaml +1103 -0
- package/docs/internal/feature-registry.yaml +1643 -0
- package/lib/boundaries.js +247 -0
- package/lib/cli/add.js +170 -0
- package/lib/cli/casestudy.js +1000 -0
- package/lib/cli/check.js +323 -0
- package/lib/cli/close.js +838 -0
- package/lib/cli/completion.js +735 -0
- package/lib/cli/deps.js +234 -0
- package/lib/cli/digest.js +73 -0
- package/lib/cli/doc-review.js +486 -0
- package/lib/cli/docs.js +315 -0
- package/lib/cli/helpers.js +198 -0
- package/lib/cli/implement.js +169 -0
- package/lib/cli/init.js +280 -0
- package/lib/cli/pipeline.js +206 -0
- package/lib/cli/plan.js +140 -0
- package/lib/cli/record.js +98 -0
- package/lib/cli/refine.js +202 -0
- package/lib/cli/report-helpers.js +113 -0
- package/lib/cli/review.js +76 -0
- package/lib/cli/routable.js +109 -0
- package/lib/cli/route.js +101 -0
- package/lib/cli/scan.js +133 -0
- package/lib/cli/serve.js +23 -0
- package/lib/cli/status.js +65 -0
- package/lib/cli/update-docs.js +574 -0
- package/lib/cli/upgrade.js +222 -0
- package/lib/cli/watch.js +32 -0
- package/lib/dependencies.js +196 -0
- package/lib/init.js +692 -0
- package/lib/mcp-server.js +612 -0
- package/lib/pipeline.js +907 -0
- package/lib/registry.js +132 -0
- package/lib/watcher.js +165 -0
- package/package.json +54 -0
- package/templates/README.md +363 -0
- package/templates/entry-points/.cursorrules +90 -0
- package/templates/entry-points/AGENT.md +90 -0
- package/templates/entry-points/CLAUDE.md +88 -0
- package/templates/extensions/MANIFEST.md +110 -0
- package/templates/extensions/analytical-system.md +96 -0
- package/templates/extensions/code-project.md +77 -0
- package/templates/extensions/data-exploration.md +117 -0
- package/templates/full/.context/BASE.md +101 -0
- package/templates/full/.context/COMPOSITION.md +47 -0
- package/templates/full/.context/INDEX.yaml +56 -0
- package/templates/full/.context/METHODOLOGY.md +246 -0
- package/templates/full/.context/PROTOCOL.yaml +169 -0
- package/templates/full/.context/REGISTRY.md +75 -0
- package/templates/full/.cursorrules +90 -0
- package/templates/full/AGENT.md +90 -0
- package/templates/full/CLAUDE.md +90 -0
- package/templates/full/Management/DIGEST.md +23 -0
- package/templates/full/Management/STATUS.md +46 -0
- package/templates/full/PLAN.md +67 -0
- package/templates/full/PROJECT-PROFILE.md +61 -0
- package/templates/full/PROJECT.md +80 -0
- package/templates/full/REQUIREMENTS.md +30 -0
- package/templates/full/ROADMAP.md +39 -0
- package/templates/full/Reviews/INDEX.md +41 -0
- package/templates/full/Reviews/backlog.md +52 -0
- package/templates/full/Reviews/plan.md +43 -0
- package/templates/full/Reviews/project.md +41 -0
- package/templates/full/Reviews/requirements.md +42 -0
- package/templates/full/Reviews/roadmap.md +41 -0
- package/templates/full/Reviews/state.md +56 -0
- package/templates/full/SESSION-LOG.md +102 -0
- package/templates/full/STATE.md +42 -0
- package/templates/full/SUMMARY.md +27 -0
- package/templates/full/agentWorkflows/INDEX.md +42 -0
- package/templates/full/agentWorkflows/observations.md +65 -0
- package/templates/full/agentWorkflows/patterns.md +68 -0
- package/templates/full/agentWorkflows/sessions.md +92 -0
- package/templates/full/intro/README.md +39 -0
- package/templates/full/registry/feature-registry.yaml +25 -0
- package/templates/full/registry/features/catalog.yaml +743 -0
- package/templates/full/registry/features/protocol.yaml +121 -0
- package/templates/full/registry/features/routing.yaml +358 -0
- package/templates/full/registry/features/workflows.yaml +404 -0
- package/templates/full/todos/backlog.md +19 -0
- package/templates/starter/.context/BASE.md +66 -0
- package/templates/starter/.context/INDEX.yaml +51 -0
- package/templates/starter/.context/METHODOLOGY.md +228 -0
- package/templates/starter/.context/PROTOCOL.yaml +165 -0
- package/templates/starter/.cursorrules +90 -0
- package/templates/starter/AGENT.md +90 -0
- package/templates/starter/CLAUDE.md +90 -0
- package/templates/starter/Management/DIGEST.md +23 -0
- package/templates/starter/Management/STATUS.md +46 -0
- package/templates/starter/PLAN.md +67 -0
- package/templates/starter/PROJECT-PROFILE.md +44 -0
- package/templates/starter/PROJECT.md +80 -0
- package/templates/starter/ROADMAP.md +39 -0
- package/templates/starter/Reviews/INDEX.md +75 -0
- package/templates/starter/SESSION-LOG.md +102 -0
- package/templates/starter/STATE.md +42 -0
- package/templates/starter/SUMMARY.md +27 -0
- package/templates/starter/agentWorkflows/INDEX.md +61 -0
- package/templates/starter/intro/README.md +37 -0
- package/templates/starter/registry/feature-registry.yaml +25 -0
- package/templates/starter/registry/features/catalog.yaml +743 -0
- package/templates/starter/registry/features/protocol.yaml +121 -0
- package/templates/starter/registry/features/routing.yaml +358 -0
- package/templates/starter/registry/features/workflows.yaml +404 -0
package/lib/cli/init.js
ADDED
|
@@ -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
|
+
}
|
package/lib/cli/plan.js
ADDED
|
@@ -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
|
+
}
|