@multileaf/ai-workflow 0.1.3 → 0.1.5

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/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project are documented in this file. The format foll
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.5] - 2026-09-12
8
+
9
+ ### Fixed
10
+
11
+ - Connect SDD capability recommendations through explicit prerequisites so custom selections include required earlier workflow stages.
12
+ - Recommend verification for supported project profiles and test runners, including test commands discovered in monorepo workspaces.
13
+ - Align brainstorming, specification, planning, and verification gates with their matching artifacts; remove references to a nonexistent review gate command.
14
+ - Clarify skill handoffs across brainstorming, requirements, design, planning, implementation, verification, and code review.
15
+ - Correct the project profile path referenced by the project initialization skill.
16
+
17
+ ## [0.1.4] - 2026-09-12
18
+
19
+ ### Added
20
+
21
+ - Guided `/ai-init` onboarding with model-cost guidance, parallel project analysis, monorepo-aware profiles, selectable resource blocks, and project-specific resource personalization.
22
+ - NestJS, Prisma, and Vite capability recommendations with matching skills, agents, and data-access guidance.
23
+
24
+ ### Changed
25
+
26
+ - Allow recommendation selection by individual resource, including an explicit select-all option.
27
+ - Expand project profiles with workspace-level technologies, quality tools, test runners, architecture, patterns, and evidence.
28
+
29
+ ### Documentation
30
+
31
+ - Specify onboarding flow, host capabilities, MCP recommendations, safety expectations, and acceptance criteria.
32
+
7
33
  ## [0.1.3] - 2026-09-12
8
34
 
9
35
  ### Fixed
@@ -50,7 +76,9 @@ All notable changes to this project are documented in this file. The format foll
50
76
 
51
77
  - Path confinement, symlink protection, explicit permission gates, secret redaction, strict parsers, atomic migrations, and immutable execution checkpoints.
52
78
 
53
- [Unreleased]: https://github.com/MultiLeaf/aiw/compare/v0.1.3...HEAD
79
+ [Unreleased]: https://github.com/MultiLeaf/aiw/compare/v0.1.5...HEAD
80
+ [0.1.5]: https://github.com/MultiLeaf/aiw/compare/v0.1.4...v0.1.5
81
+ [0.1.4]: https://github.com/MultiLeaf/aiw/compare/v0.1.3...v0.1.4
54
82
  [0.1.3]: https://github.com/MultiLeaf/aiw/compare/v0.1.2...v0.1.3
55
83
  [0.1.2]: https://github.com/MultiLeaf/aiw/compare/v0.1.1...v0.1.2
56
84
  [0.1.1]: https://github.com/MultiLeaf/aiw/releases/tag/v0.1.1
package/README.md CHANGED
@@ -122,49 +122,52 @@ flowchart TD
122
122
  D --> E[(.aiw/recommendations.yml)]
123
123
  E --> F{User confirms scope}
124
124
  F -->|Revise| B
125
- F -->|Proceed| G[Brainstorm]
126
-
127
- G --> H[(generated/specs/brainstorm.md)]
128
- H --> I{Brainstorm gate}
129
- I -->|Incomplete| G
130
- I -->|Complete| J[Write specification]
131
-
132
- J --> K[(generated/specs/specification.md)]
133
- K --> L{Specification gate}
134
- L -->|Incomplete| J
135
- L -->|Complete| M{Architecture decision needed?}
136
-
137
- M -->|Yes| N[Create ADR]
138
- N --> O[(.context/adrs/ADR-NNN-title.md)]
139
- O --> P[Update ADR index]
140
- P --> Q[(.context/adrs/INDEX.md)]
141
- M -->|No| R[Create implementation plan]
142
- Q --> R
143
-
144
- R --> S[(generated/plans/implementation-plan.md)]
145
- S --> T{Plan gate}
146
- T -->|Incomplete| R
147
- T -->|Ready| U[Implement tasks with TDD]
148
-
149
- U --> V[Write code and tests]
150
- V --> W[Run task validation commands]
151
- W --> X{Validation passes?}
152
- X -->|No| U
153
- X -->|Yes| Y[Record completion evidence]
154
-
155
- Y --> Z[(generated/artifacts and checkpoint evidence)]
156
- Z --> AA[Verify declared evidence]
157
- AA --> AB{Verification gate}
158
- AB -->|Missing evidence| U
159
- AB -->|Complete| AC[Build traceability graph]
160
-
161
- AC --> AD[Requirement → ADR → Task → Code → Test → Evidence]
162
- AD --> AE[(generated artifacts and trace output)]
163
- AE --> AF[Run repository self-validation]
164
- AF --> AG[(.aiw/checkpoints/self-validation-TICKET.yml)]
165
- AG --> AH{Review outcome}
166
- AH -->|Changes required| U
167
- AH -->|Accepted| AI[Commit and independent integration validation]
125
+ F -->|Proceed| G{Clarification needed?}
126
+
127
+ G -->|Yes| H[Brainstorm]
128
+ H --> I[(generated/specs/brainstorm.md)]
129
+ I --> J{Brainstorm gate}
130
+ J -->|Incomplete| H
131
+ J -->|Complete| K[Write specification]
132
+ G -->|No| K
133
+
134
+ K --> L[(generated/specs/specification.md)]
135
+ L --> M{Specification gate}
136
+ M -->|Incomplete| K
137
+ M -->|Complete| N{Architecture decision needed?}
138
+
139
+ N -->|Yes| O[Create ADR]
140
+ O --> P[(.context/adrs/ADR-NNN-title.md)]
141
+ P --> Q[Update ADR index]
142
+ Q --> R[(.context/adrs/INDEX.md)]
143
+ N -->|No| S[Create implementation plan]
144
+ R --> S
145
+
146
+ S --> T[(generated/plans/implementation-plan.md)]
147
+ T --> U{Plan gate}
148
+ U -->|Incomplete| S
149
+ U -->|Ready| V[Implement tasks with TDD]
150
+
151
+ V --> W[Write code and tests]
152
+ W --> X[Run task validation commands]
153
+ X --> Y{Validation passes?}
154
+ Y -->|No| V
155
+ Y -->|Yes| Z[Record completion evidence]
156
+
157
+ Z --> AA[(generated/artifacts and checkpoint evidence)]
158
+ AA --> AB[Write verification report]
159
+ AB --> AC{Verification gate}
160
+ AC -->|Missing evidence| V
161
+ AC -->|Complete| AD[Build traceability graph]
162
+
163
+ AD --> AE[Requirement → ADR → Task → Code → Test → Evidence]
164
+ AE --> AF[(generated artifacts and trace output)]
165
+ AF --> AG[Run code-review skill when installed]
166
+ AG --> AH{Review findings?}
167
+ AH -->|Changes required| V
168
+ AH -->|Clear| AI[Run repository self-validation]
169
+ AI --> AJ[(.aiw/checkpoints/self-validation-TICKET.yml)]
170
+ AJ --> AK[Commit and independent integration validation]
168
171
 
169
172
  B -. command history and evidence .-> LOGS[(.aiw/checkpoints/)]
170
173
  D -. command history and evidence .-> LOGS
@@ -180,7 +183,7 @@ npx @multileaf/ai-workflow scan
180
183
  npx @multileaf/ai-workflow recommend
181
184
 
182
185
  # In CI, provide comma-separated recommendation IDs detected for that project.
183
- npx @multileaf/ai-workflow recommend --select=typescript-quality,vitest-testing
186
+ npx @multileaf/ai-workflow recommend --select=typescript-quality,verification
184
187
 
185
188
  # Create structured product artifacts.
186
189
  npx @multileaf/ai-workflow brainstorm --title="Feature name"
@@ -188,13 +191,18 @@ npx @multileaf/ai-workflow spec --title="Feature requirements"
188
191
  npx @multileaf/ai-workflow adr --id=001 --title="Technical decision"
189
192
  npx @multileaf/ai-workflow plan --title="Implementation plan"
190
193
 
191
- # Fill the generated scaffold sections before running their quality gates.
194
+ # Optional, after creating a brainstorm artifact for an unclear request:
195
+ npx @multileaf/ai-workflow gate brainstorming
196
+ # After filling the specification and plan scaffolds shown above:
192
197
  npx @multileaf/ai-workflow gate specification
198
+ npx @multileaf/ai-workflow gate plan
199
+ # After writing .aiw/generated/reports/verification-report.md from the verification report template:
200
+ npx @multileaf/ai-workflow gate verification
193
201
  npx @multileaf/ai-workflow verify
194
202
  npx @multileaf/ai-workflow trace
195
203
  ```
196
204
 
197
- Scaffold commands write deterministic files at fixed paths and a repeated invocation replaces that scaffold. Complete and review each artifact before advancing. Requirements use stable identifiers and Given/When/Then acceptance criteria. Plans link tasks to requirements, tests, risks, dependencies, and expected evidence. `gate` performs textual completeness checks, `verify` validates the expected verification fields rather than running a test command, and `trace` records declared links between requirements, decisions, tasks, code, tests, and evidence.
205
+ Scaffold commands write deterministic files at fixed paths and a repeated invocation replaces that scaffold. Complete and review each artifact before advancing. Requirements use stable identifiers and Given/When/Then acceptance criteria. Plans link tasks to requirements, tests, risks, dependencies, and expected evidence. Each `gate` validates the artifact named by its stage. `verify` checks that the specification has acceptance criteria and the plan links requirements to validation; it does not execute project test commands. `trace` records declared links between requirements, decisions, tasks, code, tests, and evidence. Code review is performed with the `code-review` skill when installed; there is no separate review gate command.
198
206
 
199
207
  ## Project intelligence
200
208
 
package/dist/adapter.js CHANGED
@@ -40,22 +40,24 @@ This skill is the bootstrap for AI Workflow. The installer intentionally install
40
40
 
41
41
  ## First-run setup
42
42
 
43
- 1. Inspect the repository's existing AI instructions and project structure. Preserve user-authored files and do not expose secrets.
44
- 2. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw scan\`. Explain the detected facts and their evidence; ask the user to confirm uncertain inferences before they influence generated content.
45
- 3. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend\`, then read \`.aiw/recommendations.yml\` and \`.aiw/profile.yml\`. Explain why each suggested skill, rule, agent, hook, or template fits and identify external permissions. Ask which capabilities to install, and ask separately before granting an external permission such as \`network:external\`.
46
- 4. Record the user's choices with \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=id,id\`; use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=\` when all recommendations are declined. If at least one capability is selected, run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync\`; when the user separately approved external network access, add \`--allow=network:external\`. Sync installs only selected resources and fills supported project rules with detected stack and quality-tool details. Do not add unselected resources or overwrite existing files.
47
- 5. Review the installed resource list and the generated project-specific files with the user. Ask before changing project policy or tailoring any other skill/rule.
48
- 6. Introduce the selected workflow gates: brainstorming → specification → architecture decision when needed → implementation plan → implementation with tests → verification → traceability → review. Use only gates supported by the resources the user chose.
43
+ 1. Before scanning, recommend that the user switch to a cheaper model for this onboarding. Ask whether they switched or prefer to continue with the current model; never change model settings yourself. Explain that analysis is read-only and optional resources will not be installed without their selection.
44
+ 2. Use the host's native subagent mechanism to launch these read-only analyses in parallel: (a) technologies, root and nested manifests, workspaces, and monorepo boundaries; (b) architecture, modules, entry points, and recurring code patterns; (c) tests, quality commands, CI, and developer tools. Keep the main agent as orchestrator. Wait for all results, verify important claims against cited project files, reconcile disagreements, and consolidate one evidence-backed project profile. Do not inspect secrets, ignored files, dependency directories such as \`node_modules\`, or generated output; inspect declared dependencies in project manifests. If subagents are unavailable, perform the same analyses directly and disclose the fallback.
45
+ 3. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw scan\` and read \`.aiw/profile.yml\`. Treat this deterministic scan as another evidence source, not as a replacement for workspace-level analysis. Summarize each workspace separately; do not assume root dependencies or commands apply to every module. Ask the user to confirm uncertain or conflicting facts before using them as project policy. Add confirmed architecture and recurring patterns to the matching entries in the \`project_modules\` JSON array in \`.aiw/profile.yml\`, including file-path evidence; create a root-level entry when only the root project exists. Preserve existing detected fields and never store secrets or source-code excerpts there.
46
+ 4. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend\` and read \`.aiw/recommendations.yml\`. Combine catalog results with the consolidated project profile. Recommend matching skills, rules, agents, hooks, templates, and MCP servers. For every MCP, disclose its publisher/source, purpose, capabilities, permissions, transport, and credential needs; verify them against current official host and server documentation. Recommend only integrations supported by project evidence and never invent credentials.
47
+ 5. Present recommendations as concise tables grouped by category: skills, rules, agents, hooks, templates, and MCPs. Include one row per recommended item and columns for the item, a short project-specific reason for the recommendation, and relevant evidence. For MCP rows, also summarize purpose, source, permissions, transport, and credential needs (use a separate table if needed). Do not show a flat list without category headings or generic reasons such as “useful for this project.” After the tables, use the host's native selection widget to offer three choices: install all recommendations, customize, or install none. If the user customizes, show selectable category blocks and individual items, keeping each item's short reason and declared prerequisites visible. Read \`requires\` from \`.aiw/recommendations.yml\`; explain that choosing a stage auto-selects its prerequisite capabilities and their resources. If the host lacks a multi-select widget, use a numbered all/custom/none prompt and grouped checklists. MCP selection does not grant permission to execute the server or use external access; ask separately for those permissions.
48
+ 6. Record all recommendations with \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=all\`; for customized item-level choices, pass the selected resource references such as \`skills/verification,rules/tdd-policy,agents/test-engineer\`. Use \`npx --yes --package=@multileaf/ai-workflow -- aiw recommend --select=\` when none are chosen. If any AI Workflow resources are selected, run \`npx --yes --package=@multileaf/ai-workflow -- aiw sync\` without extra permissions first. It installs selected bundled resources and reports any external resource that is still pending approval. Only if the user separately approves external access, rerun with \`--allow=network:external\` to install that pending external resource. Install only selected resources and declared dependencies. Tailor instructions only where project evidence changes their usefulness (such as test commands, workspace boundaries, framework patterns, or module architecture). Preserve existing files, configuration, and secrets; report conflicts rather than overwriting them.
49
+ 7. For selected MCPs, add only the selected servers to the host's supported project configuration. Check current official host and server documentation for the configuration format, merge existing settings without replacing unrelated entries, and use environment-variable references instead of literal credentials. Do not run server install/start/authentication commands without separate approval. If the target requires user-level changes or safe configuration cannot be validated, give the user exact manual steps and report the MCP as pending rather than installed.
50
+ 8. Report installed resources by block, project-specific changes, skipped recommendations, MCP trust/authentication steps, and conflicts. Recommend restarting or reloading the coding-agent session so it discovers newly installed resources; follow known host behavior if it reloads changes automatically. Introduce only the gates supported by installed resources: brainstorming → specification → architecture decision when needed → implementation plan → implementation with tests → verification → traceability → review.
49
51
 
50
52
  If the user declines all recommendations, leave the project with only the bootstrap skill and scan state. Do not treat scanning or recommendation as consent to install.
51
53
 
52
54
  ## For each feature request after setup
53
55
 
54
- - Start with the brainstorming skill when scope, users, constraints, or assumptions are unclear.
55
- - Create and complete a specification with stable requirement IDs and Given/When/Then acceptance criteria; pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate specification\` before planning.
56
- - Record consequential architecture choices as ADRs. Create an implementation plan that links tasks to requirements, tests, risks, and validation commands; pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate plan\` before coding.
57
- - Follow the TDD policy and project quality rules. Use specialist agents for requirements, architecture, implementation, tests, security, documentation, and review when useful.
58
- - Run the declared checks, then \`npx --yes --package=@multileaf/ai-workflow -- aiw verify\`, \`npx --yes --package=@multileaf/ai-workflow -- aiw trace\`, and the review gate. Do not claim completion without evidence.
56
+ - Use the brainstorming skill when scope, users, constraints, or assumptions are unclear; create its artifact with \`npx --yes --package=@multileaf/ai-workflow -- aiw brainstorm\`, then validate it with \`npx --yes --package=@multileaf/ai-workflow -- aiw gate brainstorming\`.
57
+ - Create a specification with stable requirement IDs and Given/When/Then acceptance criteria; pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate specification\` before planning.
58
+ - For changes with architecture impact, use technical design, record consequential choices as ADRs with \`npx --yes --package=@multileaf/ai-workflow -- aiw adr\`, and link those decisions to the requirements. Then create an implementation plan with \`npx --yes --package=@multileaf/ai-workflow -- aiw plan\`, linking tasks to requirements, tests, risks, and validation commands; pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate plan\` before coding.
59
+ - When TDD resources are installed and the project has a test workflow, follow the TDD policy; otherwise use the project's existing validation methods. Follow installed project quality rules and use specialist agents only when they are installed and useful.
60
+ - Run the declared checks, write \`.aiw/generated/reports/verification-report.md\` using the verification-report template, and pass \`npx --yes --package=@multileaf/ai-workflow -- aiw gate verification\`. Then run \`npx --yes --package=@multileaf/ai-workflow -- aiw verify\` and \`npx --yes --package=@multileaf/ai-workflow -- aiw trace\`. Use the code-review skill when installed and record its findings; AI Workflow does not provide a separate review gate. Do not claim completion without evidence.
59
61
 
60
62
  ## Language policy
61
63
 
package/dist/generator.js CHANGED
@@ -2,16 +2,28 @@ import { resolveGeneratedPath } from "./drift.js";
2
2
  export function generateProjectResources(profile, selected, write) {
3
3
  const linterCommand = safeInlineValue(profile.quality.linter?.command);
4
4
  const formatterCommand = safeInlineValue(profile.quality.formatter?.command);
5
- const testCommand = safeInlineValue(profile.testing?.command);
6
- if (selected.includes("typescript-quality"))
5
+ const testCommands = [
6
+ ...(profile.testing?.command ? [{ scope: "project", command: profile.testing.command }] : []),
7
+ ...(profile.modules ?? []).flatMap((module) => module.testing?.command
8
+ ? [{ scope: `workspace ${module.path}`, command: module.testing.command }]
9
+ : []),
10
+ ].map(({ scope, command }) => ({ scope, command: safeInlineValue(command) }));
11
+ const uniqueTestCommands = [
12
+ ...new Map(testCommands
13
+ .filter(({ command }) => command)
14
+ .map(({ scope, command }) => [`${scope}:${command}`, { scope, command }])).values(),
15
+ ];
16
+ if (selected.includes("typescript-quality") || selected.includes("rules/project-quality"))
7
17
  write(resolveGeneratedPath("rules/project-quality.md"), `# Project Quality Rules\n\n- Use ${profile.runtime.languages.map((language) => (language === "typescript" ? "TypeScript" : language)).join(", ")} with strict typing.\n${linterCommand ? `- Run \`${linterCommand}\` before completion.\n` : ""}${formatterCommand ? `- Run \`${formatterCommand}\` before review.\n` : ""}- Keep all generated AI Workflow artifacts in English.\n`);
8
- if (selected.includes("tdd-development"))
9
- write(resolveGeneratedPath("rules/tdd-project-policy.md"), `# TDD Project Policy\n\n${testCommand ? `- Run \`${testCommand}\` for verification.\n` : ""}- Write a failing behavioral test before implementation.\n- Refactor only after the test is green.\n`);
10
- if (selected.includes("nextjs-development") || selected.includes("react-best-practices"))
18
+ if (selected.includes("tdd-development") || selected.includes("rules/tdd-project-policy"))
19
+ write(resolveGeneratedPath("rules/tdd-project-policy.md"), `# TDD Project Policy\n\n${uniqueTestCommands.map(({ scope, command }) => `- Run the ${scope} test command \`${command}\` for verification.`).join("\n")}${uniqueTestCommands.length ? "\n" : ""}- Write a failing behavioral test before implementation.\n- Refactor only after the test is green.\n`);
20
+ if (selected.includes("nextjs-development") ||
21
+ selected.includes("react-best-practices") ||
22
+ selected.includes("rules/architecture-policy"))
11
23
  write(resolveGeneratedPath("agents/project-context.md"), `# Project Context\n\nThis project uses ${profile.frameworks.map((framework) => (framework === "nextjs" ? "Next.js" : framework)).join(", ")}. Follow the detected project structure and existing framework conventions.\n`);
12
24
  if (selected.length > 0) {
13
25
  write(resolveGeneratedPath("context/project-profile.md"), `# Confirmed Project Profile\n\n- Languages: ${profile.runtime.languages.join(", ") || "none detected"}\n- Frameworks: ${profile.frameworks.join(", ") || "none detected"}\n- Package manager: ${profile.packageManager}\n- All generated content is in English.\n`);
14
- write(resolveGeneratedPath("hooks/quality-check.md"), `# Quality Check Hook\n\nRun the available project quality commands before completion.${linterCommand ? `\n\n- Linter: \`${linterCommand}\`` : ""}${formatterCommand ? `\n- Formatter: \`${formatterCommand}\`` : ""}${testCommand ? `\n- Tests: \`${testCommand}\`` : ""}\n`);
26
+ write(resolveGeneratedPath("hooks/quality-check.md"), `# Quality Check Hook\n\nRun the available project quality commands before completion.${linterCommand ? `\n\n- Linter: \`${linterCommand}\`` : ""}${formatterCommand ? `\n- Formatter: \`${formatterCommand}\`` : ""}${uniqueTestCommands.map(({ scope, command }) => `\n- ${scope} tests: \`${command}\``).join("")}\n`);
15
27
  }
16
28
  }
17
29
  function safeInlineValue(value) {
package/dist/profile.d.ts CHANGED
@@ -3,6 +3,24 @@ export type Tool = {
3
3
  name: string;
4
4
  command?: string;
5
5
  };
6
+ export type ProjectModule = {
7
+ path: string;
8
+ name?: string;
9
+ runtime: {
10
+ languages: string[];
11
+ };
12
+ frameworks: string[];
13
+ packageManager: string;
14
+ quality: {
15
+ linter?: Tool;
16
+ formatter?: Tool;
17
+ typecheck?: Tool;
18
+ };
19
+ testing?: Tool;
20
+ architecture?: string[];
21
+ patterns?: string[];
22
+ evidence?: string[];
23
+ };
6
24
  export type ProjectProfile = {
7
25
  runtime: {
8
26
  languages: string[];
@@ -17,6 +35,7 @@ export type ProjectProfile = {
17
35
  testing?: Tool;
18
36
  ci: string[];
19
37
  workspaces: string[];
38
+ modules?: ProjectModule[];
20
39
  facts: EvidenceFact[];
21
40
  };
22
41
  export declare function parseProjectProfile(content: string): ProjectProfile;
package/dist/profile.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { readFile } from "node:fs/promises";
2
- import { join } from "node:path";
2
+ import { dirname, join } from "node:path";
3
3
  import { scanProject } from "./scanner.js";
4
4
  import { buildFacts } from "./evidence.js";
5
5
  function scalar(content, key) {
@@ -36,6 +36,15 @@ export function parseProjectProfile(content) {
36
36
  const toolName = scalar(content, name);
37
37
  return toolName ? { name: toolName, command: scalar(content, command) } : undefined;
38
38
  };
39
+ let modules = [];
40
+ try {
41
+ const parsed = JSON.parse(scalar(content, "project_modules") ?? "[]");
42
+ if (Array.isArray(parsed))
43
+ modules = parseModules(parsed);
44
+ }
45
+ catch {
46
+ modules = [];
47
+ }
39
48
  return {
40
49
  runtime: { languages },
41
50
  frameworks,
@@ -48,12 +57,68 @@ export function parseProjectProfile(content) {
48
57
  testing: tool("testing", "testing_command"),
49
58
  ci: list(content, "ci"),
50
59
  workspaces: list(content, "workspaces"),
60
+ modules,
51
61
  facts: [],
52
62
  };
53
63
  }
64
+ function parseModules(values) {
65
+ return values.slice(0, 256).flatMap((value) => {
66
+ if (!value || typeof value !== "object" || Array.isArray(value))
67
+ return [];
68
+ const item = value;
69
+ if (typeof item.path !== "string" || !item.path.trim())
70
+ return [];
71
+ const runtime = item.runtime && typeof item.runtime === "object"
72
+ ? item.runtime
73
+ : {};
74
+ const quality = item.quality && typeof item.quality === "object"
75
+ ? item.quality
76
+ : {};
77
+ const tool = (candidate) => {
78
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate))
79
+ return undefined;
80
+ const details = candidate;
81
+ if (typeof details.name !== "string" || !details.name.trim())
82
+ return undefined;
83
+ return {
84
+ name: details.name.slice(0, 180),
85
+ command: typeof details.command === "string" ? details.command.slice(0, 180) : undefined,
86
+ };
87
+ };
88
+ return [
89
+ {
90
+ path: item.path.slice(0, 240),
91
+ name: typeof item.name === "string" ? item.name.slice(0, 180) : undefined,
92
+ runtime: { languages: stringArray(runtime.languages) },
93
+ frameworks: stringArray(item.frameworks),
94
+ packageManager: typeof item.packageManager === "string" ? item.packageManager.slice(0, 80) : "unknown",
95
+ quality: {
96
+ linter: tool(quality.linter),
97
+ formatter: tool(quality.formatter),
98
+ typecheck: tool(quality.typecheck),
99
+ },
100
+ testing: tool(item.testing),
101
+ architecture: stringArray(item.architecture),
102
+ patterns: stringArray(item.patterns),
103
+ evidence: stringArray(item.evidence),
104
+ },
105
+ ];
106
+ });
107
+ }
108
+ function stringArray(value) {
109
+ return Array.isArray(value)
110
+ ? value
111
+ .filter((item) => typeof item === "string")
112
+ .slice(0, 32)
113
+ .map((item) => item.slice(0, 240))
114
+ : [];
115
+ }
54
116
  async function packageManifest(root) {
117
+ return packageManifestFile(join(root, "package.json"));
118
+ }
119
+ async function packageManifestFile(path) {
55
120
  try {
56
- return JSON.parse(await readFile(join(root, "package.json"), "utf8"));
121
+ return JSON.parse(await readFile(path, "utf8"));
57
122
  }
58
123
  catch {
59
124
  return {};
@@ -69,17 +134,21 @@ export async function profileProject(root) {
69
134
  const scan = await scanProject(root);
70
135
  const manifest = await packageManifest(root);
71
136
  const files = scan.files;
137
+ const moduleManifests = files.filter((file) => /(^|\/)package\.json$/.test(file));
138
+ const packageDirectories = moduleManifests.map((file) => dirname(file));
139
+ const modules = await Promise.all(moduleManifests
140
+ .filter((file) => dirname(file) !== ".")
141
+ .map((file) => profileModule(root, file, files, scan.evidence, undefined, packageDirectories)));
142
+ const rootModule = await profileModule(root, "package.json", files, scan.evidence, manifest, packageDirectories);
72
143
  const languages = [
73
- ...new Set(scan.evidence
74
- .filter((item) => item.fact.startsWith("language."))
75
- .map((item) => item.fact.replace("language.", ""))),
144
+ ...new Set([
145
+ ...rootModule.runtime.languages,
146
+ ...modules.flatMap((item) => item.runtime.languages),
147
+ ]),
76
148
  ];
77
149
  const frameworks = [
78
- hasDependency(manifest, "next") ? "nextjs" : "",
79
- hasDependency(manifest, "react") ? "react" : "",
80
- hasDependency(manifest, "vue") ? "vue" : "",
81
- hasDependency(manifest, "express") ? "express" : "",
82
- ].filter(Boolean);
150
+ ...new Set([...rootModule.frameworks, ...modules.flatMap((item) => item.frameworks)]),
151
+ ];
83
152
  const packageManager = files.includes("pnpm-lock.yaml")
84
153
  ? "pnpm"
85
154
  : files.includes("yarn.lock")
@@ -116,8 +185,74 @@ export async function profileProject(root) {
116
185
  testing: testingName ? { name: testingName, command: commandFor(manifest, "test") } : undefined,
117
186
  ci: files.some((file) => file.startsWith(".github/workflows/")) ? ["github-actions"] : [],
118
187
  workspaces,
188
+ modules,
119
189
  facts: [],
120
190
  };
121
191
  profile.facts = buildFacts(profile, scan.evidence);
122
192
  return profile;
123
193
  }
194
+ async function profileModule(root, manifestPath, files, evidence, existingManifest, packageDirectories = []) {
195
+ const manifest = existingManifest ?? (await packageManifestFile(join(root, manifestPath)));
196
+ const modulePath = dirname(manifestPath) === "." ? "." : dirname(manifestPath).split("\\").join("/");
197
+ const prefix = modulePath === "." ? "" : `${modulePath}/`;
198
+ const childPrefixes = packageDirectories
199
+ .filter((directory) => directory !== modulePath && directory.startsWith(prefix))
200
+ .map((directory) => `${directory}/`);
201
+ const moduleFiles = files.filter((file) => file.startsWith(prefix) && !childPrefixes.some((childPrefix) => file.startsWith(childPrefix)));
202
+ const languages = [
203
+ ...new Set(evidence
204
+ .filter((item) => item.fact.startsWith("language.") && item.source.startsWith(prefix))
205
+ .map((item) => item.fact.slice("language.".length))),
206
+ ];
207
+ const frameworkSet = new Set();
208
+ const addDependency = (dependency, label) => {
209
+ if (hasDependency(manifest, dependency))
210
+ frameworkSet.add(label);
211
+ };
212
+ addDependency("next", "nextjs");
213
+ addDependency("react", "react");
214
+ addDependency("vue", "vue");
215
+ addDependency("express", "express");
216
+ addDependency("@nestjs/core", "nestjs");
217
+ addDependency("@prisma/client", "prisma");
218
+ addDependency("prisma", "prisma");
219
+ addDependency("vite", "vite");
220
+ addDependency("fastify", "fastify");
221
+ if (moduleFiles.some((file) => file.startsWith(`${prefix}prisma/`) && file.endsWith(".prisma")))
222
+ frameworkSet.add("prisma");
223
+ const testingName = hasDependency(manifest, "vitest")
224
+ ? "vitest"
225
+ : hasDependency(manifest, "jest")
226
+ ? "jest"
227
+ : hasDependency(manifest, "@playwright/test")
228
+ ? "playwright"
229
+ : hasDependency(manifest, "cypress")
230
+ ? "cypress"
231
+ : undefined;
232
+ const qualityTool = (dependency, config, script, name) => hasDependency(manifest, dependency) ||
233
+ moduleFiles.some((file) => file.startsWith(`${prefix}${config}`))
234
+ ? { name, command: commandFor(manifest, script) }
235
+ : undefined;
236
+ const packageManager = moduleFiles.some((file) => file === `${prefix}pnpm-lock.yaml`)
237
+ ? "pnpm"
238
+ : moduleFiles.some((file) => file === `${prefix}yarn.lock`)
239
+ ? "yarn"
240
+ : moduleFiles.some((file) => file === `${prefix}package-lock.json`)
241
+ ? "npm"
242
+ : "unknown";
243
+ return {
244
+ path: modulePath,
245
+ name: typeof manifest.name === "string" ? manifest.name : undefined,
246
+ runtime: { languages },
247
+ frameworks: [...frameworkSet],
248
+ packageManager,
249
+ quality: {
250
+ linter: qualityTool("eslint", "eslint.config", "lint", "eslint"),
251
+ formatter: qualityTool("prettier", "prettier", "format", "prettier"),
252
+ typecheck: commandFor(manifest, "typecheck")
253
+ ? { name: "typescript", command: commandFor(manifest, "typecheck") }
254
+ : undefined,
255
+ },
256
+ testing: testingName ? { name: testingName, command: commandFor(manifest, "test") } : undefined,
257
+ };
258
+ }
@@ -1,2 +1,2 @@
1
- export type QualityGateStage = "specification" | "plan" | "verification";
1
+ export type QualityGateStage = "brainstorming" | "specification" | "plan" | "verification";
2
2
  export declare function evaluateQualityGate(stage: QualityGateStage, content: string): string[];
@@ -11,7 +11,7 @@ function requireSections(content, artifact, headings) {
11
11
  });
12
12
  }
13
13
  export function evaluateQualityGate(stage, content) {
14
- if (stage === "specification")
14
+ if (stage === "brainstorming")
15
15
  return requireSections(content, "Brainstorm", [
16
16
  "Goal",
17
17
  "Users",
@@ -19,7 +19,7 @@ export function evaluateQualityGate(stage, content) {
19
19
  "Risks",
20
20
  "Non-goals",
21
21
  ]);
22
- if (stage === "plan") {
22
+ if (stage === "specification") {
23
23
  const issues = [];
24
24
  if (!/REQ-\d+/.test(content))
25
25
  issues.push("Specification must define a stable REQ-### ID.");
@@ -29,12 +29,21 @@ export function evaluateQualityGate(stage, content) {
29
29
  issues.push("Requirement acceptance criteria must use Given, When, and Then.");
30
30
  return issues;
31
31
  }
32
- const fields = ["Requirement", "Code", "Tests", "Validation", "Evidence"];
33
- const issues = !/TASK-\d+/.test(content)
34
- ? ["Implementation plan must define a TASK-### ID."]
35
- : [];
36
- for (const field of fields)
37
- if (!new RegExp(`^[ \\t]*-?[ \\t]*${field}:[ \\t]*\\S+`, "im").test(content))
38
- issues.push(`Implementation plan task must define ${field}.`);
39
- return issues;
32
+ if (stage === "plan") {
33
+ const fields = ["Requirement", "Code", "Tests", "Validation", "Evidence"];
34
+ const issues = !/TASK-\d+/.test(content)
35
+ ? ["Implementation plan must define a TASK-### ID."]
36
+ : [];
37
+ for (const field of fields)
38
+ if (!new RegExp(`^[ \\t]*-?[ \\t]*${field}:[ \\t]*\\S+`, "im").test(content))
39
+ issues.push(`Implementation plan task must define ${field}.`);
40
+ return issues;
41
+ }
42
+ return requireSections(content, "Verification Report", [
43
+ "Requirements Checked",
44
+ "Checks Passed",
45
+ "Missing Evidence",
46
+ "Residual Risks",
47
+ "Decision",
48
+ ]);
40
49
  }
@@ -13,6 +13,7 @@ export type CapabilityRecommendation = {
13
13
  permissions: string[];
14
14
  conflicts: string[];
15
15
  resources: RecommendedResource[];
16
+ requires?: string[];
16
17
  };
17
18
  export declare function recommendCapabilities(profile: ProjectProfile): CapabilityRecommendation[];
18
19
  export declare function resourcesForCapabilities(ids: string[]): RecommendedResource[];