@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 +29 -1
- package/README.md +54 -46
- package/dist/adapter.js +13 -11
- package/dist/generator.js +18 -6
- package/dist/profile.d.ts +19 -0
- package/dist/profile.js +145 -10
- package/dist/quality-gates.d.ts +1 -1
- package/dist/quality-gates.js +19 -10
- package/dist/recommendations.d.ts +1 -0
- package/dist/recommendations.js +107 -12
- package/dist/selection.js +38 -2
- package/dist/workflow.js +83 -16
- package/docs/ai-init-workflow-spec.md +47 -0
- package/docs/backlog.md +1 -0
- package/docs/product-requirements.md +1 -0
- package/package.json +1 -1
- package/resources/agents/nestjs-engineer.md +3 -0
- package/resources/agents/prisma-engineer.md +3 -0
- package/resources/package.yaml +64 -4
- package/resources/rules/data-access-policy.md +7 -0
- package/resources/skills/brainstorming.md +1 -0
- package/resources/skills/code-review.md +1 -0
- package/resources/skills/implementation-planning.md +1 -0
- package/resources/skills/nestjs-development.md +8 -0
- package/resources/skills/prisma-data-access.md +8 -0
- package/resources/skills/project-init.md +1 -2
- package/resources/skills/requirements-specification.md +1 -0
- package/resources/skills/tdd-development.md +1 -0
- package/resources/skills/technical-design.md +1 -0
- package/resources/skills/verification.md +1 -0
- package/resources/skills/vite-frontend.md +8 -0
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.
|
|
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
|
|
126
|
-
|
|
127
|
-
G
|
|
128
|
-
H --> I
|
|
129
|
-
I
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
L
|
|
136
|
-
|
|
137
|
-
M -->|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
Q --> R
|
|
143
|
-
|
|
144
|
-
R --> S
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
T
|
|
148
|
-
|
|
149
|
-
U
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
X
|
|
153
|
-
X
|
|
154
|
-
|
|
155
|
-
Y
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
AB
|
|
159
|
-
AB
|
|
160
|
-
|
|
161
|
-
AC
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
AH -->|
|
|
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,
|
|
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
|
-
#
|
|
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`
|
|
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.
|
|
44
|
-
2.
|
|
45
|
-
3. Run \`npx --yes --package=@multileaf/ai-workflow -- aiw
|
|
46
|
-
4.
|
|
47
|
-
5.
|
|
48
|
-
6.
|
|
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
|
-
-
|
|
55
|
-
- Create
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
- Run the declared checks,
|
|
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
|
|
6
|
-
|
|
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${
|
|
10
|
-
if (selected.includes("nextjs-development") ||
|
|
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}\`` : ""}${
|
|
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(
|
|
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(
|
|
74
|
-
.
|
|
75
|
-
.
|
|
144
|
+
...new Set([
|
|
145
|
+
...rootModule.runtime.languages,
|
|
146
|
+
...modules.flatMap((item) => item.runtime.languages),
|
|
147
|
+
]),
|
|
76
148
|
];
|
|
77
149
|
const frameworks = [
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
}
|
package/dist/quality-gates.d.ts
CHANGED
|
@@ -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[];
|
package/dist/quality-gates.js
CHANGED
|
@@ -11,7 +11,7 @@ function requireSections(content, artifact, headings) {
|
|
|
11
11
|
});
|
|
12
12
|
}
|
|
13
13
|
export function evaluateQualityGate(stage, content) {
|
|
14
|
-
if (stage === "
|
|
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 === "
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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[];
|