@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/dist/recommendations.js
CHANGED
|
@@ -38,10 +38,26 @@ const CATALOG = [
|
|
|
38
38
|
{ type: "agents", id: "technical-architect" },
|
|
39
39
|
{ type: "templates", id: "implementation-plan" },
|
|
40
40
|
]),
|
|
41
|
+
requires: ["requirements-specification"],
|
|
41
42
|
matches: (profile) => hasProjectSignals(profile)
|
|
42
43
|
? { evidence: projectEvidence(profile), confidence: 0.88 }
|
|
43
44
|
: undefined,
|
|
44
45
|
},
|
|
46
|
+
{
|
|
47
|
+
id: "technical-design",
|
|
48
|
+
provider: "multileaf",
|
|
49
|
+
rationale: "Architecture changes benefit from explicit alternatives, trade-offs, and decision records.",
|
|
50
|
+
permissions: [],
|
|
51
|
+
resources: local([
|
|
52
|
+
{ type: "skills", id: "technical-design" },
|
|
53
|
+
{ type: "agents", id: "technical-architect" },
|
|
54
|
+
{ type: "templates", id: "technical-design" },
|
|
55
|
+
{ type: "templates", id: "decision-record" },
|
|
56
|
+
]),
|
|
57
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
58
|
+
? { evidence: projectEvidence(profile), confidence: 0.82 }
|
|
59
|
+
: undefined,
|
|
60
|
+
},
|
|
45
61
|
{
|
|
46
62
|
id: "code-review",
|
|
47
63
|
provider: "multileaf",
|
|
@@ -51,6 +67,7 @@ const CATALOG = [
|
|
|
51
67
|
{ type: "skills", id: "code-review" },
|
|
52
68
|
{ type: "agents", id: "quality-reviewer" },
|
|
53
69
|
]),
|
|
70
|
+
requires: ["verification"],
|
|
54
71
|
matches: (profile) => profile.runtime.languages.length
|
|
55
72
|
? { evidence: projectEvidence(profile), confidence: 0.86 }
|
|
56
73
|
: undefined,
|
|
@@ -68,9 +85,25 @@ const CATALOG = [
|
|
|
68
85
|
{ type: "hooks", id: "post-implementation" },
|
|
69
86
|
{ type: "templates", id: "test-plan" },
|
|
70
87
|
]),
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
88
|
+
requires: ["verification"],
|
|
89
|
+
matches: (profile) => {
|
|
90
|
+
const runners = [
|
|
91
|
+
...(profile.testing ? [profile.testing] : []),
|
|
92
|
+
...(profile.modules ?? []).flatMap((module) => module.testing ? [{ ...module.testing, module: module.path }] : []),
|
|
93
|
+
];
|
|
94
|
+
return runners.length
|
|
95
|
+
? {
|
|
96
|
+
evidence: [
|
|
97
|
+
...new Set(runners.flatMap((runner) => [
|
|
98
|
+
...("module" in runner ? [`workspace:${runner.module}`] : []),
|
|
99
|
+
`testing:${runner.name}`,
|
|
100
|
+
...(runner.command ? [`test-command:${runner.command}`] : []),
|
|
101
|
+
])),
|
|
102
|
+
],
|
|
103
|
+
confidence: 1,
|
|
104
|
+
}
|
|
105
|
+
: undefined;
|
|
106
|
+
},
|
|
74
107
|
},
|
|
75
108
|
{
|
|
76
109
|
id: "typescript-quality",
|
|
@@ -86,18 +119,49 @@ const CATALOG = [
|
|
|
86
119
|
: undefined,
|
|
87
120
|
},
|
|
88
121
|
{
|
|
89
|
-
id: "
|
|
122
|
+
id: "nestjs-backend",
|
|
123
|
+
provider: "multileaf",
|
|
124
|
+
rationale: "NestJS is detected in this project; its module, dependency-injection, and test conventions can guide backend work.",
|
|
125
|
+
permissions: [],
|
|
126
|
+
resources: local([
|
|
127
|
+
{ type: "skills", id: "nestjs-development" },
|
|
128
|
+
{ type: "agents", id: "nestjs-engineer" },
|
|
129
|
+
{ type: "rules", id: "architecture-policy" },
|
|
130
|
+
{ type: "templates", id: "technical-design" },
|
|
131
|
+
]),
|
|
132
|
+
matches: (profile) => profile.frameworks.includes("nestjs")
|
|
133
|
+
? { evidence: projectEvidence(profile, "nestjs"), confidence: 1 }
|
|
134
|
+
: undefined,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: "prisma-data-access",
|
|
90
138
|
provider: "multileaf",
|
|
91
|
-
rationale: "
|
|
139
|
+
rationale: "Prisma is detected; schema and data-access changes need migration-aware guidance aligned to the owning workspace.",
|
|
140
|
+
permissions: [],
|
|
141
|
+
resources: local([
|
|
142
|
+
{ type: "skills", id: "prisma-data-access" },
|
|
143
|
+
{ type: "agents", id: "prisma-engineer" },
|
|
144
|
+
{ type: "rules", id: "data-access-policy" },
|
|
145
|
+
{ type: "rules", id: "security-policy" },
|
|
146
|
+
]),
|
|
147
|
+
matches: (profile) => profile.frameworks.includes("prisma")
|
|
148
|
+
? { evidence: projectEvidence(profile, "prisma"), confidence: 1 }
|
|
149
|
+
: undefined,
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: "verification",
|
|
153
|
+
provider: "multileaf",
|
|
154
|
+
rationale: "Verification guidance can use the project's detected test runner and quality commands.",
|
|
92
155
|
permissions: [],
|
|
93
156
|
resources: local([
|
|
94
157
|
{ type: "skills", id: "verification" },
|
|
95
|
-
{ type: "agents", id: "
|
|
158
|
+
{ type: "agents", id: "quality-reviewer" },
|
|
96
159
|
{ type: "templates", id: "test-plan" },
|
|
97
160
|
{ type: "templates", id: "verification-report" },
|
|
98
161
|
]),
|
|
99
|
-
|
|
100
|
-
|
|
162
|
+
requires: ["implementation-planning"],
|
|
163
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
164
|
+
? { evidence: projectEvidence(profile), confidence: profile.testing ? 1 : 0.84 }
|
|
101
165
|
: undefined,
|
|
102
166
|
},
|
|
103
167
|
{
|
|
@@ -130,6 +194,21 @@ const CATALOG = [
|
|
|
130
194
|
? { evidence: ["framework:nextjs"], confidence: 1 }
|
|
131
195
|
: undefined,
|
|
132
196
|
},
|
|
197
|
+
{
|
|
198
|
+
id: "vite-frontend",
|
|
199
|
+
provider: "multileaf",
|
|
200
|
+
rationale: "Vite is detected; frontend build, test, and module guidance should follow the owning app's framework and scripts.",
|
|
201
|
+
permissions: [],
|
|
202
|
+
resources: local([
|
|
203
|
+
{ type: "skills", id: "vite-frontend" },
|
|
204
|
+
{ type: "agents", id: "typescript-engineer" },
|
|
205
|
+
{ type: "rules", id: "quality-gates" },
|
|
206
|
+
{ type: "templates", id: "technical-design" },
|
|
207
|
+
]),
|
|
208
|
+
matches: (profile) => profile.frameworks.includes("vite")
|
|
209
|
+
? { evidence: projectEvidence(profile, "vite"), confidence: 1 }
|
|
210
|
+
: undefined,
|
|
211
|
+
},
|
|
133
212
|
{
|
|
134
213
|
id: "security-review",
|
|
135
214
|
provider: "multileaf",
|
|
@@ -163,15 +242,22 @@ export function recommendCapabilities(profile) {
|
|
|
163
242
|
});
|
|
164
243
|
}
|
|
165
244
|
export function resourcesForCapabilities(ids) {
|
|
166
|
-
const
|
|
167
|
-
const
|
|
245
|
+
const normalizedIds = ids.map((id) => (id === "vitest-testing" ? "verification" : id));
|
|
246
|
+
const selectedCapabilities = new Set(CATALOG.filter(({ id }) => normalizedIds.includes(id)).map(({ id }) => id));
|
|
247
|
+
const selectedResources = new Set(normalizedIds.filter((id) => id.includes("/")));
|
|
248
|
+
const resources = CATALOG.flatMap(({ id, resources }) => selectedCapabilities.has(id)
|
|
249
|
+
? resources
|
|
250
|
+
: resources.filter(({ type, id: resourceId }) => selectedResources.has(`${type}/${resourceId}`)));
|
|
168
251
|
return [
|
|
169
252
|
...new Map(resources.map((resource) => [`${resource.type}/${resource.id}`, resource])).values(),
|
|
170
253
|
];
|
|
171
254
|
}
|
|
172
255
|
export function serializeRecommendations(items, selected = []) {
|
|
173
256
|
return `schema: 1\nrecommendations:\n${items
|
|
174
|
-
.map((item) => ` - id: ${item.id}\n provider: ${item.provider}\n confidence: ${item.confidence}\n selected: ${selected.includes(item.id)}\n
|
|
257
|
+
.map((item) => ` - id: ${item.id}\n provider: ${item.provider}\n confidence: ${item.confidence}\n selected: ${selected.includes(item.id)}\n requires: [${(item.requires ?? []).join(", ")}]\n resources: [${item.resources.map(({ type, id }) => `${type}/${id}`).join(", ")}]\n selected_resources: [${item.resources
|
|
258
|
+
.filter(({ type, id }) => selected.includes(`${type}/${id}`))
|
|
259
|
+
.map(({ type, id }) => `${type}/${id}`)
|
|
260
|
+
.join(", ")}]\n evidence: [${item.evidence.map((evidence) => `"${evidence}"`).join(", ")}]\n rationale: ${item.rationale}`)
|
|
175
261
|
.join("\n")}\n`;
|
|
176
262
|
}
|
|
177
263
|
function hasProjectSignals(profile) {
|
|
@@ -182,7 +268,15 @@ function hasProjectSignals(profile) {
|
|
|
182
268
|
profile.quality.linter ||
|
|
183
269
|
profile.ci.length);
|
|
184
270
|
}
|
|
185
|
-
function projectEvidence(profile) {
|
|
271
|
+
function projectEvidence(profile, framework) {
|
|
272
|
+
const moduleEvidence = (profile.modules ?? [])
|
|
273
|
+
.filter((module) => !framework || module.frameworks.includes(framework))
|
|
274
|
+
.flatMap((module) => [
|
|
275
|
+
`workspace:${module.path}`,
|
|
276
|
+
...module.frameworks.map((name) => `framework:${name}`),
|
|
277
|
+
...(module.testing ? [`testing:${module.testing.name}`] : []),
|
|
278
|
+
...(module.testing?.command ? [`test-command:${module.testing.command}`] : []),
|
|
279
|
+
]);
|
|
186
280
|
return [
|
|
187
281
|
...profile.runtime.languages.map((language) => `language:${language}`),
|
|
188
282
|
...profile.frameworks.map((framework) => `framework:${framework}`),
|
|
@@ -190,5 +284,6 @@ function projectEvidence(profile) {
|
|
|
190
284
|
...(profile.testing ? [`testing:${profile.testing.name}`] : []),
|
|
191
285
|
...(profile.quality.linter ? [`linter:${profile.quality.linter.name}`] : []),
|
|
192
286
|
...(profile.ci.length ? profile.ci.map((system) => `ci:${system}`) : []),
|
|
287
|
+
...moduleEvidence,
|
|
193
288
|
];
|
|
194
289
|
}
|
package/dist/selection.js
CHANGED
|
@@ -1,11 +1,47 @@
|
|
|
1
1
|
export async function selectRecommendations(items, answer, selectedIds) {
|
|
2
|
+
const available = new Set(items.flatMap((item) => [
|
|
3
|
+
item.id,
|
|
4
|
+
...(item.resources ?? []).map(({ type, id }) => `${type}/${id}`),
|
|
5
|
+
]));
|
|
6
|
+
available.add("vitest-testing");
|
|
7
|
+
if (selectedIds?.includes("all"))
|
|
8
|
+
return items.map(({ id }) => id);
|
|
2
9
|
if (selectedIds)
|
|
3
|
-
return
|
|
10
|
+
return includeRequirements(items, selectedIds
|
|
11
|
+
.filter((id) => available.has(id))
|
|
12
|
+
.map((id) => (id === "vitest-testing" ? "verification" : id)));
|
|
4
13
|
const selected = [];
|
|
5
14
|
for (const item of items) {
|
|
6
15
|
const response = (await answer(`Install ${item.id}? [Y/n] `)).trim().toLowerCase();
|
|
7
16
|
if (response === "" || response === "y" || response === "yes")
|
|
8
17
|
selected.push(item.id);
|
|
9
18
|
}
|
|
10
|
-
return selected;
|
|
19
|
+
return includeRequirements(items, selected);
|
|
20
|
+
}
|
|
21
|
+
function includeRequirements(items, selectedIds) {
|
|
22
|
+
const selected = new Set(selectedIds);
|
|
23
|
+
const byId = new Map(items.map((item) => [item.id, item]));
|
|
24
|
+
const owners = new Map();
|
|
25
|
+
for (const item of items)
|
|
26
|
+
for (const resource of item.resources ?? [])
|
|
27
|
+
owners.set(`${resource.type}/${resource.id}`, item.id);
|
|
28
|
+
const visit = (capabilityId, visited) => {
|
|
29
|
+
if (visited.has(capabilityId))
|
|
30
|
+
return;
|
|
31
|
+
visited.add(capabilityId);
|
|
32
|
+
const capability = byId.get(capabilityId);
|
|
33
|
+
if (!capability)
|
|
34
|
+
return;
|
|
35
|
+
for (const requirement of capability.requires ?? []) {
|
|
36
|
+
if (!byId.has(requirement))
|
|
37
|
+
continue;
|
|
38
|
+
selected.add(requirement);
|
|
39
|
+
visit(requirement, visited);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const initial = [...selected];
|
|
43
|
+
const visited = new Set();
|
|
44
|
+
for (const id of initial)
|
|
45
|
+
visit(byId.has(id) ? id : (owners.get(id) ?? ""), visited);
|
|
46
|
+
return [...selected];
|
|
11
47
|
}
|
package/dist/workflow.js
CHANGED
|
@@ -290,7 +290,7 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
290
290
|
const evidence = profile.facts
|
|
291
291
|
.map((fact) => ` - key: ${fact.key}\n value: ${fact.value}\n state: ${fact.state}\n method: ${fact.method}\n confidence: ${fact.confidence}${fact.requiresConfirmation === undefined ? "" : `\n requires_confirmation: ${fact.requiresConfirmation}`}\n evidence: [${fact.evidence.map((item) => `"${item.source}"`).join(", ")}]`)
|
|
292
292
|
.join("\n");
|
|
293
|
-
fs.write(join(aiw, "profile.yml"), `schema: 1\nstatus: scanned\nfacts:\n${evidence}\nruntime:\n languages: [${profile.runtime.languages.join(", ")}]\nframeworks: [${profile.frameworks.join(", ")}]\npackage_manager: ${profile.packageManager}\nquality:\n linter: ${profile.quality.linter?.name ?? "unknown"}\n linter_command: ${profile.quality.linter?.command ?? "unknown"}\n formatter: ${profile.quality.formatter?.name ?? "unknown"}\n formatter_command: ${profile.quality.formatter?.command ?? "unknown"}\n typecheck: ${profile.quality.typecheck?.name ?? "unknown"}\n typecheck_command: ${profile.quality.typecheck?.command ?? "unknown"}\ntesting: ${profile.testing?.name ?? "unknown"}\ntesting_command: ${profile.testing?.command ?? "unknown"}\nci: [${profile.ci.join(", ")}]\nworkspaces: [${profile.workspaces.join(", ")}]\npolicies:\n artifact_language: en\n`);
|
|
293
|
+
fs.write(join(aiw, "profile.yml"), `schema: 1\nstatus: scanned\nfacts:\n${evidence}\nruntime:\n languages: [${profile.runtime.languages.join(", ")}]\nframeworks: [${profile.frameworks.join(", ")}]\npackage_manager: ${profile.packageManager}\nquality:\n linter: ${profile.quality.linter?.name ?? "unknown"}\n linter_command: ${profile.quality.linter?.command ?? "unknown"}\n formatter: ${profile.quality.formatter?.name ?? "unknown"}\n formatter_command: ${profile.quality.formatter?.command ?? "unknown"}\n typecheck: ${profile.quality.typecheck?.name ?? "unknown"}\n typecheck_command: ${profile.quality.typecheck?.command ?? "unknown"}\ntesting: ${profile.testing?.name ?? "unknown"}\ntesting_command: ${profile.testing?.command ?? "unknown"}\nci: [${profile.ci.join(", ")}]\nworkspaces: [${profile.workspaces.join(", ")}]\nproject_modules: ${JSON.stringify(profile.modules ?? [])}\npolicies:\n artifact_language: en\n`);
|
|
294
294
|
return { exitCode: 0, output: "Project profile generated." };
|
|
295
295
|
}
|
|
296
296
|
if (command === "target") {
|
|
@@ -749,6 +749,36 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
749
749
|
: await profileProject(root);
|
|
750
750
|
const selectedArg = args.find((arg) => arg.startsWith("--select="))?.split("=", 2)[1];
|
|
751
751
|
const selected = selectedArg?.split(",") ?? (command === "sync" ? readSelectedRecommendations(fs, aiw) : []);
|
|
752
|
+
if (command === "sync") {
|
|
753
|
+
const result = syncSelectedResources(fs, root, aiw, profile, selected);
|
|
754
|
+
if (result.error)
|
|
755
|
+
return { exitCode: 1, error: result.error };
|
|
756
|
+
const messages = [result.output ?? "Selected bundled resources synchronized."];
|
|
757
|
+
if (selected.includes("react-best-practices")) {
|
|
758
|
+
const approvedPermissions = parseApprovedPermissions(args);
|
|
759
|
+
if (!approvedPermissions.includes("network:external")) {
|
|
760
|
+
messages.push("Remote skill pending: network:external was not approved. Bundled resources were installed; rerun sync with --allow=network:external only if you approve external access.");
|
|
761
|
+
}
|
|
762
|
+
else {
|
|
763
|
+
try {
|
|
764
|
+
enforceConfiguredOrganizationPolicy(fs, aiw, {
|
|
765
|
+
provider: "vercel-skills",
|
|
766
|
+
source: "vercel-labs/agent-skills",
|
|
767
|
+
permissions: ["network:external"],
|
|
768
|
+
});
|
|
769
|
+
const target = parseManifest(fs.read(join(aiw, "manifest.yml"))).target.active;
|
|
770
|
+
await installVercelSkill(services.externalSkills ?? nodeCommandExecutor, "vercel-labs/agent-skills", "vercel-react-best-practices", target);
|
|
771
|
+
writeVercelSkillLock(fs, root, aiw, "vercel-labs/agent-skills", "vercel-react-best-practices");
|
|
772
|
+
messages.push("Remote React best-practices skill installed.");
|
|
773
|
+
}
|
|
774
|
+
catch (error) {
|
|
775
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
776
|
+
messages.push(`Remote skill pending: ${reason}`);
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return { exitCode: 0, output: messages.join(" ") };
|
|
781
|
+
}
|
|
752
782
|
if (selected.includes("react-best-practices")) {
|
|
753
783
|
const approvedPermissions = parseApprovedPermissions(args);
|
|
754
784
|
if (!approvedPermissions.includes("network:external"))
|
|
@@ -765,12 +795,6 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
765
795
|
await installVercelSkill(services.externalSkills ?? nodeCommandExecutor, "vercel-labs/agent-skills", "vercel-react-best-practices", target);
|
|
766
796
|
writeVercelSkillLock(fs, root, aiw, "vercel-labs/agent-skills", "vercel-react-best-practices");
|
|
767
797
|
}
|
|
768
|
-
if (command === "sync") {
|
|
769
|
-
const result = syncSelectedResources(fs, root, aiw, profile, selected);
|
|
770
|
-
return result.error
|
|
771
|
-
? { exitCode: 1, error: result.error }
|
|
772
|
-
: { exitCode: 0, output: result.output };
|
|
773
|
-
}
|
|
774
798
|
const conflicts = [];
|
|
775
799
|
generateProjectResources(profile, selected, (path, content) => {
|
|
776
800
|
const destination = join(aiw, path);
|
|
@@ -1019,12 +1043,16 @@ export async function runCommand(args, root, fs, services = {}) {
|
|
|
1019
1043
|
return { exitCode: 1, error: "Run `aiw install` first." };
|
|
1020
1044
|
const stage = args[1];
|
|
1021
1045
|
const requirements = {
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1046
|
+
brainstorming: "generated/specs/brainstorm.md",
|
|
1047
|
+
specification: "generated/specs/specification.md",
|
|
1048
|
+
plan: "generated/plans/implementation-plan.md",
|
|
1049
|
+
verification: "generated/reports/verification-report.md",
|
|
1025
1050
|
};
|
|
1026
|
-
if (!stage || !(stage
|
|
1027
|
-
return {
|
|
1051
|
+
if (!stage || !Object.hasOwn(requirements, stage))
|
|
1052
|
+
return {
|
|
1053
|
+
exitCode: 1,
|
|
1054
|
+
error: "Usage: aiw gate <brainstorming|specification|plan|verification>",
|
|
1055
|
+
};
|
|
1028
1056
|
const gateStage = stage;
|
|
1029
1057
|
const artifact = requirements[gateStage];
|
|
1030
1058
|
if (!fs.exists(join(aiw, artifact)))
|
|
@@ -1630,7 +1658,19 @@ function syncSelectedResources(fs, root, aiw, profile, selected) {
|
|
|
1630
1658
|
};
|
|
1631
1659
|
}
|
|
1632
1660
|
function personalizeBundledResource(type, id, content, profile) {
|
|
1633
|
-
|
|
1661
|
+
const contextual = (type === "rules" &&
|
|
1662
|
+
[
|
|
1663
|
+
"quality-gates",
|
|
1664
|
+
"tdd-policy",
|
|
1665
|
+
"dependency-policy",
|
|
1666
|
+
"architecture-policy",
|
|
1667
|
+
"data-access-policy",
|
|
1668
|
+
].includes(id)) ||
|
|
1669
|
+
(type === "skills" &&
|
|
1670
|
+
["nestjs-development", "prisma-data-access", "vite-frontend"].includes(id)) ||
|
|
1671
|
+
(type === "agents" &&
|
|
1672
|
+
["technical-architect", "typescript-engineer", "nestjs-engineer", "prisma-engineer"].includes(id));
|
|
1673
|
+
if (!contextual)
|
|
1634
1674
|
return content;
|
|
1635
1675
|
const signals = [
|
|
1636
1676
|
profile.runtime.languages.length ? `- Languages: ${profile.runtime.languages.join(", ")}` : "",
|
|
@@ -1647,6 +1687,24 @@ function personalizeBundledResource(type, id, content, profile) {
|
|
|
1647
1687
|
profile.quality.formatter?.command
|
|
1648
1688
|
? `- Format command: \`${safeProjectSignal(profile.quality.formatter.command)}\``
|
|
1649
1689
|
: "",
|
|
1690
|
+
...(profile.modules ?? []).map((module) => {
|
|
1691
|
+
const parts = [
|
|
1692
|
+
module.name
|
|
1693
|
+
? `${safeProjectSignal(module.name)} (${safeProjectSignal(module.path)})`
|
|
1694
|
+
: safeProjectSignal(module.path),
|
|
1695
|
+
module.frameworks.length ? `frameworks: ${module.frameworks.join(", ")}` : "",
|
|
1696
|
+
...(module.architecture ?? []).map((item) => `architecture: ${safeProjectSignal(item)}`),
|
|
1697
|
+
...(module.patterns ?? []).map((item) => `patterns: ${safeProjectSignal(item)}`),
|
|
1698
|
+
module.testing
|
|
1699
|
+
? `tests: ${module.testing.name}${module.testing.command ? ` (${safeProjectSignal(module.testing.command)})` : ""}`
|
|
1700
|
+
: "",
|
|
1701
|
+
module.quality.linter
|
|
1702
|
+
? `linter: ${module.quality.linter.name}${module.quality.linter.command ? ` (${safeProjectSignal(module.quality.linter.command)})` : ""}`
|
|
1703
|
+
: "",
|
|
1704
|
+
].filter(Boolean);
|
|
1705
|
+
const evidence = (module.evidence ?? []).slice(0, 8).map(safeProjectSignal);
|
|
1706
|
+
return `- Module ${parts.join("; ")}${evidence.length ? `; evidence: ${evidence.join(", ")}` : ""}`;
|
|
1707
|
+
}),
|
|
1650
1708
|
].filter(Boolean);
|
|
1651
1709
|
return signals.length
|
|
1652
1710
|
? `${content.trimEnd()}\n\n## Detected project context\n\n${signals.join("\n")}\n`
|
|
@@ -1666,11 +1724,20 @@ function readSelectedRecommendations(fs, aiw) {
|
|
|
1666
1724
|
if (!fs.exists(join(aiw, "recommendations.yml")))
|
|
1667
1725
|
return [];
|
|
1668
1726
|
const content = fs.read(join(aiw, "recommendations.yml"));
|
|
1669
|
-
|
|
1727
|
+
const selections = content
|
|
1670
1728
|
.split(/^\s{2}- id: /m)
|
|
1671
1729
|
.slice(1)
|
|
1672
|
-
.
|
|
1673
|
-
|
|
1730
|
+
.flatMap((block) => {
|
|
1731
|
+
const id = block.split("\n", 1)[0].trim();
|
|
1732
|
+
const selectedCapability = /^\s{4}selected: true$/m.test(block) ? [id] : [];
|
|
1733
|
+
const selectedResources = block
|
|
1734
|
+
.match(/^\s{4}selected_resources: \[([^\]]*)\]$/m)?.[1]
|
|
1735
|
+
?.split(",")
|
|
1736
|
+
.map((resource) => resource.trim())
|
|
1737
|
+
.filter(Boolean) ?? [];
|
|
1738
|
+
return [...selectedCapability, ...selectedResources];
|
|
1739
|
+
});
|
|
1740
|
+
return [...new Set(selections)];
|
|
1674
1741
|
}
|
|
1675
1742
|
function parseApprovedPermissions(args) {
|
|
1676
1743
|
return args
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# AI Init Project Onboarding Specification
|
|
2
|
+
|
|
3
|
+
## Goal
|
|
4
|
+
|
|
5
|
+
Make `/ai-init` a guided, evidence-backed onboarding flow. The initial `npx` install remains a small bootstrap; `/ai-init` analyzes the repository, recommends project-matched capabilities, gets an explicit selection, installs only that selection, and reports any restart needed to load it.
|
|
6
|
+
|
|
7
|
+
## User flow
|
|
8
|
+
|
|
9
|
+
1. **Model and consent.** Before repository analysis, recommend switching to a lower-cost model. Do not change model settings. Ask whether the user switched or wants to continue with the current model. Explain that repository analysis is read-only until resource selection.
|
|
10
|
+
2. **Parallel project analysis.** Use the host's native subagent mechanism to start independent read-only scans in parallel. Assign separate work to (a) technologies and package/workspace topology, (b) architecture, modules, and code patterns, and (c) tests, quality tools, and CI. The initiating agent is the orchestrator: it consolidates findings, resolves conflicts using repository evidence, and records paths for every material conclusion. If the host cannot start subagents, do the same scans directly and say so.
|
|
11
|
+
3. **Monorepo coverage.** Discover root and nested project manifests, workspace definitions, package boundaries, framework configs, tests, and module entry points. Analyze each workspace separately, then summarize shared tooling and cross-workspace relationships. Never infer that root dependencies apply to every workspace.
|
|
12
|
+
4. **Scan record.** Run the deterministic AI Workflow scan and combine it with the parallel analysis. Present technologies, architecture, patterns, modules, tests, quality commands, workspace topology, confidence, and source evidence. Do not include secret values. Ask for confirmation where conflicting or uncertain findings would affect installed guidance.
|
|
13
|
+
5. **Recommendations.** Match catalog capabilities to the consolidated project profile. Show concise tables grouped by resource category (skills, rules, agents, hooks, templates, and MCPs), with one row per item and a short, project-specific reason plus supporting evidence. Include resource types, required permissions, and project-specific configuration steps where relevant. Recommend an MCP only when repository evidence supports its use; disclose its publisher/source, capabilities, access, transport, and credential needs. Never invent credentials or silently enable an MCP.
|
|
14
|
+
6. **Selection.** After the recommendation tables, use the coding host's native selection widget when it supports one. First offer: install all recommendations, customize, or install none. For customization, show selectable blocks grouped by resource type, then the individual recommended items in each selected block; retain each item's concise reason and evidence in the selection details. Include MCPs as a separate block. Keep dependencies visible and select required dependencies together. If the host has no selection widget, use an equivalent numbered prompt and accept all/custom/none explicitly.
|
|
15
|
+
7. **Install and personalize.** Install only the confirmed selection. For “all”, install every recommendation that does not require an unapproved external permission. A missing external permission must not block independent bundled resources; report the external item as pending and give the user the exact retry command. For custom selection, install only selected items and their declared dependencies. Personalize resources only when project evidence materially changes their instructions (for example, test commands, workspace/module layout, framework patterns, or architectural boundaries). Preserve existing files and secrets; report conflicts rather than overwriting. MCP setup must use the selected host's supported project-level configuration, preserve existing configuration, use environment-variable references for secrets, and request separate approval for permissions or external execution.
|
|
16
|
+
8. **Completion.** Report installed resources by block, project-specific changes, skipped recommendations, conflicts, MCP authentication/trust steps, and validation performed. Recommend restarting/reloading the coding-agent session so newly installed resources are discovered. Do not claim a restart is required when the host loads changes immediately; distinguish host behavior where known.
|
|
17
|
+
|
|
18
|
+
## Safety and compatibility
|
|
19
|
+
|
|
20
|
+
- Bootstrap installation alone must not activate optional project resources.
|
|
21
|
+
- Analysis agents must be read-only and must not inspect ignored, generated, dependency, secret, or credential files.
|
|
22
|
+
- The orchestrator treats agent output as untrusted evidence, checks paths and key findings against the repository, and does not merge unsupported claims into project policy.
|
|
23
|
+
- Selection and external permissions are separate decisions. Selecting an MCP is not permission to execute it or to expose credentials.
|
|
24
|
+
- Resource writes use target adapters, ownership tracking, safe paths, atomic updates, and conflict reporting.
|
|
25
|
+
- Host-specific capabilities (subagents, selection widgets, MCP configuration, restart semantics) are detected or described accurately; provide a safe manual fallback where unavailable.
|
|
26
|
+
|
|
27
|
+
## Acceptance criteria
|
|
28
|
+
|
|
29
|
+
- **AIINIT-001:** A fresh install writes only the `ai-init` bootstrap and neutral AI Workflow state.
|
|
30
|
+
- **AIINIT-002:** The bootstrap asks about using a lower-cost model before analysis and never changes model settings itself.
|
|
31
|
+
- **AIINIT-003:** On supported hosts, three independent read-only analysis agents start in parallel and the orchestrator consolidates their evidence; unsupported hosts use a disclosed sequential fallback.
|
|
32
|
+
- **AIINIT-004:** Scans include separate summaries for every detected monorepo workspace and do not attribute root dependencies to all packages.
|
|
33
|
+
- **AIINIT-005:** Recommendations identify matching evidence, resource blocks, dependencies, MCP sources, and permissions.
|
|
34
|
+
- **AIINIT-012:** Recommendations are displayed in concise tables grouped by category, and each item has a project-specific reason and supporting evidence before selection.
|
|
35
|
+
- **AIINIT-006:** The selection UI offers install-all, custom, and none; custom selections can choose individual items by skills, rules, agents, hooks, templates, and MCPs.
|
|
36
|
+
- **AIINIT-007:** Installation activates only selected resources and required dependencies, personalizes only context-sensitive content, and preserves unowned or edited files.
|
|
37
|
+
- **AIINIT-008:** MCP setup is target-aware, merges safely with existing configuration, never stores literal secrets, and requires separate permission approval.
|
|
38
|
+
- **AIINIT-009:** Completion lists changes and recommends a session restart/reload to discover installed resources.
|
|
39
|
+
- **AIINIT-010:** CLI tests cover mixed-stack monorepos, all/custom/none selection, permission gating, MCP config merging, personalization, conflicts, and completion output.
|
|
40
|
+
- **AIINIT-011:** Custom selections can choose individual resource references by block, while `--select=all` selects every recommended capability and `--select=` selects none.
|
|
41
|
+
- **AIINIT-013:** A selected remote resource without approved network access does not block independent bundled resource sync; sync reports the remote item as pending and preserves unrelated project files.
|
|
42
|
+
|
|
43
|
+
## Delivery slices
|
|
44
|
+
|
|
45
|
+
1. Bootstrap orchestration, host-native selection, model-cost prompt, and completion/reload guidance.
|
|
46
|
+
2. Deterministic monorepo profiles, stack-specific capability recommendations, resource-level selection, and context-aware sync.
|
|
47
|
+
3. MCP catalog provenance, per-target project configuration/merge support, credential placeholders, ownership, and config validation.
|
package/docs/backlog.md
CHANGED
|
@@ -43,6 +43,7 @@ Priority levels: **P0** blocks the product or makes it unsafe; **P1** is require
|
|
|
43
43
|
- [x] **SDD-005** Implement verification skill. _Acceptance:_ missing evidence and untested requirements are reported.
|
|
44
44
|
- [x] **SDD-006** Implement requirement-to-evidence traceability graph. _Acceptance:_ requirement → decision → task → code → test → evidence is queryable.
|
|
45
45
|
- [x] **SDD-007** Add quality gates between workflow stages. _Acceptance:_ incomplete artifacts block progression with actionable feedback.
|
|
46
|
+
- [x] **SDD-008** Connect workflow recommendations and quality gates to the SDD sequence. _Acceptance:_ selected later stages include declared prerequisites; verification is recommended for supported project profiles and test runners; each gate checks the matching artifact; review instructions do not refer to a nonexistent CLI gate.
|
|
46
47
|
|
|
47
48
|
## Adapters and migration — P1/P2
|
|
48
49
|
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
- Selected resources must be customized using confirmed project facts.
|
|
25
25
|
- `/ai-init` must orchestrate scan → recommendations → user selection → selected-resource sync and summarize what it added.
|
|
26
26
|
- The bootstrap must invoke CLI operations through the package runner; it cannot assume `aiw` is installed globally or in the consuming project's dependencies.
|
|
27
|
+
- The complete onboarding behavior and acceptance criteria are defined in [`ai-init-workflow-spec.md`](./ai-init-workflow-spec.md).
|
|
27
28
|
|
|
28
29
|
### Development workflow
|
|
29
30
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# NestJS Engineer
|
|
2
|
+
|
|
3
|
+
Analyze the target NestJS module and its tests before proposing changes. Report relevant controllers, providers, dependency-injection relationships, DTO validation, and module boundaries with file evidence. Implement only within the assigned scope, preserve existing patterns, and return changed paths plus the exact workspace checks run. Do not access secrets or run migrations.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# Prisma Engineer
|
|
2
|
+
|
|
3
|
+
Inspect the active Prisma schema, generated-client usage, migrations, and data-access tests. Explain the impact of proposed schema/query changes and preserve the project's transaction and migration conventions. Never reset, migrate, or access a database without explicit approval. Return evidence paths, changed paths, and workspace-scoped validation results; do not include connection strings or credentials.
|
package/resources/package.yaml
CHANGED
|
@@ -28,6 +28,9 @@ resources:
|
|
|
28
28
|
- { id: documentation-maintenance, version: 0.1.0, path: skills/documentation-maintenance.md }
|
|
29
29
|
- { id: adapter-development, version: 0.1.0, path: skills/adapter-development.md }
|
|
30
30
|
- { id: skill-authoring, version: 0.1.0, path: skills/skill-authoring.md }
|
|
31
|
+
- { id: nestjs-development, version: 0.1.0, path: skills/nestjs-development.md }
|
|
32
|
+
- { id: prisma-data-access, version: 0.1.0, path: skills/prisma-data-access.md }
|
|
33
|
+
- { id: vite-frontend, version: 0.1.0, path: skills/vite-frontend.md }
|
|
31
34
|
rules:
|
|
32
35
|
- { id: language-policy, version: 0.1.0, path: rules/language-policy.md }
|
|
33
36
|
- { id: tdd-policy, version: 0.1.0, path: rules/tdd-policy.md }
|
|
@@ -39,6 +42,7 @@ resources:
|
|
|
39
42
|
- { id: documentation-policy, version: 0.1.0, path: rules/documentation-policy.md }
|
|
40
43
|
- { id: self-validation, version: 0.1.0, path: rules/self-validation.md }
|
|
41
44
|
- { id: completion-validation, version: 0.1.0, path: rules/completion-validation.md }
|
|
45
|
+
- { id: data-access-policy, version: 0.1.0, path: rules/data-access-policy.md }
|
|
42
46
|
agents:
|
|
43
47
|
- { id: requirements-analyst, version: 0.1.0, path: agents/requirements-analyst.md }
|
|
44
48
|
- { id: technical-architect, version: 0.1.0, path: agents/technical-architect.md }
|
|
@@ -48,6 +52,8 @@ resources:
|
|
|
48
52
|
- { id: security-reviewer, version: 0.1.0, path: agents/security-reviewer.md }
|
|
49
53
|
- { id: adapter-engineer, version: 0.1.0, path: agents/adapter-engineer.md }
|
|
50
54
|
- { id: documentation-writer, version: 0.1.0, path: agents/documentation-writer.md }
|
|
55
|
+
- { id: nestjs-engineer, version: 0.1.0, path: agents/nestjs-engineer.md }
|
|
56
|
+
- { id: prisma-engineer, version: 0.1.0, path: agents/prisma-engineer.md }
|
|
51
57
|
hooks:
|
|
52
58
|
- { id: pre-implementation, version: 0.1.0, path: hooks/pre-implementation.md }
|
|
53
59
|
- { id: post-implementation, version: 0.1.0, path: hooks/post-implementation.md }
|
|
@@ -66,8 +72,62 @@ resources:
|
|
|
66
72
|
- { id: security-review, version: 0.1.0, path: templates/security-review.md }
|
|
67
73
|
- { id: migration-report, version: 0.1.0, path: templates/migration-report.md }
|
|
68
74
|
provides:
|
|
69
|
-
skills:
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
skills:
|
|
76
|
+
[
|
|
77
|
+
project-init,
|
|
78
|
+
brainstorming,
|
|
79
|
+
requirements-specification,
|
|
80
|
+
technical-design,
|
|
81
|
+
implementation-planning,
|
|
82
|
+
tdd-development,
|
|
83
|
+
verification,
|
|
84
|
+
code-review,
|
|
85
|
+
security-review,
|
|
86
|
+
documentation-maintenance,
|
|
87
|
+
adapter-development,
|
|
88
|
+
skill-authoring,
|
|
89
|
+
nestjs-development,
|
|
90
|
+
prisma-data-access,
|
|
91
|
+
vite-frontend,
|
|
92
|
+
]
|
|
93
|
+
rules:
|
|
94
|
+
[
|
|
95
|
+
language-policy,
|
|
96
|
+
tdd-policy,
|
|
97
|
+
architecture-policy,
|
|
98
|
+
quality-gates,
|
|
99
|
+
security-policy,
|
|
100
|
+
dependency-policy,
|
|
101
|
+
generated-files,
|
|
102
|
+
documentation-policy,
|
|
103
|
+
self-validation,
|
|
104
|
+
completion-validation,
|
|
105
|
+
data-access-policy,
|
|
106
|
+
]
|
|
107
|
+
agents:
|
|
108
|
+
[
|
|
109
|
+
requirements-analyst,
|
|
110
|
+
technical-architect,
|
|
111
|
+
typescript-engineer,
|
|
112
|
+
test-engineer,
|
|
113
|
+
quality-reviewer,
|
|
114
|
+
security-reviewer,
|
|
115
|
+
adapter-engineer,
|
|
116
|
+
documentation-writer,
|
|
117
|
+
nestjs-engineer,
|
|
118
|
+
prisma-engineer,
|
|
119
|
+
]
|
|
72
120
|
hooks: [pre-implementation, post-implementation, pre-commit, post-change, pre-release]
|
|
73
|
-
templates:
|
|
121
|
+
templates:
|
|
122
|
+
[
|
|
123
|
+
brainstorm,
|
|
124
|
+
specification,
|
|
125
|
+
technical-design,
|
|
126
|
+
decision-record,
|
|
127
|
+
implementation-plan,
|
|
128
|
+
task,
|
|
129
|
+
test-plan,
|
|
130
|
+
verification-report,
|
|
131
|
+
security-review,
|
|
132
|
+
migration-report,
|
|
133
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Data Access Policy
|
|
2
|
+
|
|
3
|
+
- Follow the repository's existing data-access and transaction boundaries.
|
|
4
|
+
- Scope commands and tests to the workspace that owns the database schema or query.
|
|
5
|
+
- Review migration contents before applying them; never run destructive database commands without explicit approval.
|
|
6
|
+
- Keep credentials in the project's approved secret store or environment, never in source, logs, or generated AI instructions.
|
|
7
|
+
- Validate data-access changes with relevant tests and report any migration or compatibility impact.
|
|
@@ -7,3 +7,4 @@ description: Turn an early product idea into structured goals, assumptions, cons
|
|
|
7
7
|
|
|
8
8
|
Separate facts, hypotheses, decisions, and open questions. Identify users, desired outcomes, constraints, risks, and non-goals. Do not prematurely prescribe implementation. Produce a versioned brainstorm artifact in English.
|
|
9
9
|
|
|
10
|
+
When the request is already clear, skip brainstorming. Once the goal and constraints are clear, hand off to `requirements-specification` and link the specification to this brainstorm when one exists.
|
|
@@ -7,3 +7,4 @@ description: Review code for correctness, maintainability, design quality, and r
|
|
|
7
7
|
|
|
8
8
|
Review behavior, boundaries, cohesion, naming, types, error handling, duplication, complexity, tests, and documentation. Prioritize actionable findings with severity and evidence.
|
|
9
9
|
|
|
10
|
+
If findings require changes, send them back to implementation and require verification again. If there are no blocking findings, record the review result and complete traceability. This skill is a review checklist, not a CLI quality gate.
|
|
@@ -7,3 +7,4 @@ description: Break an approved design into independently verifiable implementati
|
|
|
7
7
|
|
|
8
8
|
Create small ordered tasks. Each task must reference requirements, affected modules, tests, commands, dependencies, and completion evidence.
|
|
9
9
|
|
|
10
|
+
After the plan passes its gate, implement in task order. Use `tdd-development` when it is installed and the project has a test workflow; otherwise use the repository's existing validation methods. Hand completed work to `verification`.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: nestjs-development
|
|
3
|
+
description: Implement and review NestJS services using the repository's module boundaries and conventions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# NestJS Development
|
|
7
|
+
|
|
8
|
+
Inspect the relevant NestJS module before editing. Follow the project's established controller, provider, dependency-injection, guard, pipe, interceptor, and DTO patterns; do not introduce a new architectural layer without evidence or approval. Keep transport concerns in controllers and business behavior in the existing service/domain boundaries. Validate external input with the project's configured validation approach. Add focused unit or integration tests using the test runner configured for this workspace, and run that workspace's declared quality commands.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: prisma-data-access
|
|
3
|
+
description: Change Prisma schemas and data access safely within the repository's database conventions.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Prisma Data Access
|
|
7
|
+
|
|
8
|
+
Read the active Prisma schema, client setup, existing migrations, and callers before changing data access. Preserve naming, relation, transaction, and repository patterns already used by the project. Do not apply migrations, reset databases, or run destructive commands without explicit approval. When schema changes are selected, explain migration impact, add or update tests around affected queries, and use the exact Prisma and test commands declared by the relevant workspace. Never copy connection strings or credentials into generated instructions.
|
|
@@ -5,5 +5,4 @@ description: Inspect a repository, build a project profile, and prepare AI Workf
|
|
|
5
5
|
|
|
6
6
|
# Project Initialization
|
|
7
7
|
|
|
8
|
-
Inspect structure and configuration safely. Detect languages, frameworks, package managers, scripts, tests, quality tools, CI, and conventions. Record evidence and confidence in `.
|
|
9
|
-
|
|
8
|
+
Inspect structure and configuration safely. Detect languages, frameworks, package managers, scripts, tests, quality tools, CI, and conventions. Record evidence and confidence in `.aiw/profile.yml`. Ask for confirmation before treating uncertain inferences as facts. Recommend capabilities with rationale, permissions, and conflicts.
|