@multileaf/ai-workflow 0.1.0 → 0.1.2
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 +316 -13
- package/dist/adapter.d.ts +1 -0
- package/dist/adapter.js +37 -1
- package/dist/bundled-resources.d.ts +9 -0
- package/dist/bundled-resources.js +29 -0
- package/dist/constants.js +1 -0
- package/dist/files.d.ts +10 -0
- package/dist/files.js +216 -7
- package/dist/generator.js +13 -3
- package/dist/interpreter.js +47 -6
- package/dist/lockfile.d.ts +1 -1
- package/dist/lockfile.js +6 -2
- package/dist/migration.d.ts +21 -2
- package/dist/migration.js +162 -21
- package/dist/ownership.d.ts +14 -0
- package/dist/ownership.js +83 -0
- package/dist/package-audit.d.ts +1 -0
- package/dist/package-audit.js +7 -3
- package/dist/package-contract.d.ts +1 -0
- package/dist/package-contract.js +136 -74
- package/dist/package-integrity.d.ts +5 -1
- package/dist/package-integrity.js +56 -1
- package/dist/providers.d.ts +2 -0
- package/dist/providers.js +27 -5
- package/dist/recommendations.d.ts +7 -0
- package/dist/recommendations.js +141 -10
- package/dist/scanner.js +28 -26
- package/dist/types.d.ts +2 -0
- package/dist/workflow.js +729 -147
- package/docs/adapter-model.md +3 -1
- package/docs/backlog.md +2 -2
- package/docs/package-model.md +5 -3
- package/docs/product-requirements.md +2 -0
- package/docs/project-intelligence.md +3 -1
- package/docs/release-backlog.md +1 -1
- package/docs/security.md +4 -0
- package/package.json +9 -5
- package/resources/package.yaml +37 -0
package/dist/recommendations.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
|
+
const local = (resources) => resources;
|
|
1
2
|
const CATALOG = [
|
|
3
|
+
{
|
|
4
|
+
id: "brainstorming",
|
|
5
|
+
provider: "multileaf",
|
|
6
|
+
rationale: "A project-specific brainstorming flow helps clarify goals and constraints before implementation.",
|
|
7
|
+
permissions: [],
|
|
8
|
+
resources: local([
|
|
9
|
+
{ type: "skills", id: "brainstorming" },
|
|
10
|
+
{ type: "rules", id: "language-policy" },
|
|
11
|
+
{ type: "templates", id: "brainstorm" },
|
|
12
|
+
]),
|
|
13
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
14
|
+
? { evidence: projectEvidence(profile), confidence: 0.92 }
|
|
15
|
+
: undefined,
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: "requirements-specification",
|
|
19
|
+
provider: "multileaf",
|
|
20
|
+
rationale: "The project can use explicit requirements and acceptance criteria before changes are planned.",
|
|
21
|
+
permissions: [],
|
|
22
|
+
resources: local([
|
|
23
|
+
{ type: "skills", id: "requirements-specification" },
|
|
24
|
+
{ type: "agents", id: "requirements-analyst" },
|
|
25
|
+
{ type: "templates", id: "specification" },
|
|
26
|
+
]),
|
|
27
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
28
|
+
? { evidence: projectEvidence(profile), confidence: 0.9 }
|
|
29
|
+
: undefined,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "implementation-planning",
|
|
33
|
+
provider: "multileaf",
|
|
34
|
+
rationale: "A traceable implementation plan can connect project requirements, tests, risks, and validation.",
|
|
35
|
+
permissions: [],
|
|
36
|
+
resources: local([
|
|
37
|
+
{ type: "skills", id: "implementation-planning" },
|
|
38
|
+
{ type: "agents", id: "technical-architect" },
|
|
39
|
+
{ type: "templates", id: "implementation-plan" },
|
|
40
|
+
]),
|
|
41
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
42
|
+
? { evidence: projectEvidence(profile), confidence: 0.88 }
|
|
43
|
+
: undefined,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "code-review",
|
|
47
|
+
provider: "multileaf",
|
|
48
|
+
rationale: "A consistent review checklist helps validate changes against this repository's conventions.",
|
|
49
|
+
permissions: [],
|
|
50
|
+
resources: local([
|
|
51
|
+
{ type: "skills", id: "code-review" },
|
|
52
|
+
{ type: "agents", id: "quality-reviewer" },
|
|
53
|
+
]),
|
|
54
|
+
matches: (profile) => profile.runtime.languages.length
|
|
55
|
+
? { evidence: projectEvidence(profile), confidence: 0.86 }
|
|
56
|
+
: undefined,
|
|
57
|
+
},
|
|
2
58
|
{
|
|
3
59
|
id: "tdd-development",
|
|
4
60
|
provider: "multileaf",
|
|
5
|
-
rationale: "The project uses automated tests and
|
|
61
|
+
rationale: "The project uses automated tests and can apply test-first development with its existing runner.",
|
|
62
|
+
permissions: [],
|
|
63
|
+
resources: local([
|
|
64
|
+
{ type: "skills", id: "tdd-development" },
|
|
65
|
+
{ type: "rules", id: "tdd-policy" },
|
|
66
|
+
{ type: "agents", id: "test-engineer" },
|
|
67
|
+
{ type: "hooks", id: "pre-implementation" },
|
|
68
|
+
{ type: "hooks", id: "post-implementation" },
|
|
69
|
+
{ type: "templates", id: "test-plan" },
|
|
70
|
+
]),
|
|
6
71
|
matches: (profile) => profile.testing
|
|
7
72
|
? { evidence: [`testing:${profile.testing.name}`], confidence: 1 }
|
|
8
73
|
: undefined,
|
|
@@ -10,7 +75,12 @@ const CATALOG = [
|
|
|
10
75
|
{
|
|
11
76
|
id: "typescript-quality",
|
|
12
77
|
provider: "multileaf",
|
|
13
|
-
rationale: "TypeScript is
|
|
78
|
+
rationale: "TypeScript is detected; add its specialist and tailor quality guidance to the repository's tools.",
|
|
79
|
+
permissions: [],
|
|
80
|
+
resources: local([
|
|
81
|
+
{ type: "agents", id: "typescript-engineer" },
|
|
82
|
+
{ type: "rules", id: "quality-gates" },
|
|
83
|
+
]),
|
|
14
84
|
matches: (profile) => profile.runtime.languages.includes("typescript")
|
|
15
85
|
? { evidence: ["language:typescript"], confidence: 1 }
|
|
16
86
|
: undefined,
|
|
@@ -18,7 +88,14 @@ const CATALOG = [
|
|
|
18
88
|
{
|
|
19
89
|
id: "vitest-testing",
|
|
20
90
|
provider: "multileaf",
|
|
21
|
-
rationale: "Vitest is configured and can be
|
|
91
|
+
rationale: "Vitest is configured and can be used by the verification skill and test specialist.",
|
|
92
|
+
permissions: [],
|
|
93
|
+
resources: local([
|
|
94
|
+
{ type: "skills", id: "verification" },
|
|
95
|
+
{ type: "agents", id: "test-engineer" },
|
|
96
|
+
{ type: "templates", id: "test-plan" },
|
|
97
|
+
{ type: "templates", id: "verification-report" },
|
|
98
|
+
]),
|
|
22
99
|
matches: (profile) => profile.testing?.name === "vitest"
|
|
23
100
|
? { evidence: ["testing:vitest"], confidence: 1 }
|
|
24
101
|
: undefined,
|
|
@@ -26,7 +103,14 @@ const CATALOG = [
|
|
|
26
103
|
{
|
|
27
104
|
id: "react-best-practices",
|
|
28
105
|
provider: "vercel-skills",
|
|
29
|
-
rationale: "React is detected; framework
|
|
106
|
+
rationale: "React is detected; framework guidance can improve component design and performance.",
|
|
107
|
+
permissions: ["network:external"],
|
|
108
|
+
resources: local([
|
|
109
|
+
{ type: "skills", id: "technical-design" },
|
|
110
|
+
{ type: "agents", id: "technical-architect" },
|
|
111
|
+
{ type: "rules", id: "architecture-policy" },
|
|
112
|
+
{ type: "templates", id: "technical-design" },
|
|
113
|
+
]),
|
|
30
114
|
matches: (profile) => profile.frameworks.includes("react")
|
|
31
115
|
? { evidence: ["framework:react"], confidence: 1 }
|
|
32
116
|
: undefined,
|
|
@@ -34,11 +118,34 @@ const CATALOG = [
|
|
|
34
118
|
{
|
|
35
119
|
id: "nextjs-development",
|
|
36
120
|
provider: "multileaf",
|
|
37
|
-
rationale: "Next.js is detected;
|
|
121
|
+
rationale: "Next.js is detected; architecture guidance can follow the project's framework conventions.",
|
|
122
|
+
permissions: [],
|
|
123
|
+
resources: local([
|
|
124
|
+
{ type: "skills", id: "technical-design" },
|
|
125
|
+
{ type: "agents", id: "technical-architect" },
|
|
126
|
+
{ type: "rules", id: "architecture-policy" },
|
|
127
|
+
{ type: "templates", id: "technical-design" },
|
|
128
|
+
]),
|
|
38
129
|
matches: (profile) => profile.frameworks.includes("nextjs")
|
|
39
130
|
? { evidence: ["framework:nextjs"], confidence: 1 }
|
|
40
131
|
: undefined,
|
|
41
132
|
},
|
|
133
|
+
{
|
|
134
|
+
id: "security-review",
|
|
135
|
+
provider: "multileaf",
|
|
136
|
+
rationale: "The detected stack and package tooling benefit from project-aware security and dependency review.",
|
|
137
|
+
permissions: [],
|
|
138
|
+
resources: local([
|
|
139
|
+
{ type: "skills", id: "security-review" },
|
|
140
|
+
{ type: "rules", id: "security-policy" },
|
|
141
|
+
{ type: "rules", id: "dependency-policy" },
|
|
142
|
+
{ type: "agents", id: "security-reviewer" },
|
|
143
|
+
{ type: "templates", id: "security-review" },
|
|
144
|
+
]),
|
|
145
|
+
matches: (profile) => hasProjectSignals(profile)
|
|
146
|
+
? { evidence: projectEvidence(profile), confidence: 0.83 }
|
|
147
|
+
: undefined,
|
|
148
|
+
},
|
|
42
149
|
];
|
|
43
150
|
export function recommendCapabilities(profile) {
|
|
44
151
|
return CATALOG.flatMap((capability) => {
|
|
@@ -46,18 +153,42 @@ export function recommendCapabilities(profile) {
|
|
|
46
153
|
return match
|
|
47
154
|
? [
|
|
48
155
|
{
|
|
49
|
-
|
|
50
|
-
provider: capability.provider,
|
|
156
|
+
...capability,
|
|
51
157
|
confidence: match.confidence,
|
|
52
|
-
rationale: capability.rationale,
|
|
53
158
|
evidence: match.evidence,
|
|
54
|
-
permissions: capability.permissions ?? [],
|
|
55
159
|
conflicts: [],
|
|
56
160
|
},
|
|
57
161
|
]
|
|
58
162
|
: [];
|
|
59
163
|
});
|
|
60
164
|
}
|
|
165
|
+
export function resourcesForCapabilities(ids) {
|
|
166
|
+
const selected = new Set(ids);
|
|
167
|
+
const resources = CATALOG.filter(({ id }) => selected.has(id)).flatMap(({ resources }) => resources);
|
|
168
|
+
return [
|
|
169
|
+
...new Map(resources.map((resource) => [`${resource.type}/${resource.id}`, resource])).values(),
|
|
170
|
+
];
|
|
171
|
+
}
|
|
61
172
|
export function serializeRecommendations(items, selected = []) {
|
|
62
|
-
return `schema: 1\nrecommendations:\n${items
|
|
173
|
+
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 resources: [${item.resources.map(({ type, id }) => `${type}/${id}`).join(", ")}]\n evidence: [${item.evidence.map((evidence) => `"${evidence}"`).join(", ")}]\n rationale: ${item.rationale}`)
|
|
175
|
+
.join("\n")}\n`;
|
|
176
|
+
}
|
|
177
|
+
function hasProjectSignals(profile) {
|
|
178
|
+
return Boolean(profile.runtime.languages.length ||
|
|
179
|
+
profile.frameworks.length ||
|
|
180
|
+
profile.packageManager !== "unknown" ||
|
|
181
|
+
profile.testing ||
|
|
182
|
+
profile.quality.linter ||
|
|
183
|
+
profile.ci.length);
|
|
184
|
+
}
|
|
185
|
+
function projectEvidence(profile) {
|
|
186
|
+
return [
|
|
187
|
+
...profile.runtime.languages.map((language) => `language:${language}`),
|
|
188
|
+
...profile.frameworks.map((framework) => `framework:${framework}`),
|
|
189
|
+
...(profile.packageManager === "unknown" ? [] : [`package-manager:${profile.packageManager}`]),
|
|
190
|
+
...(profile.testing ? [`testing:${profile.testing.name}`] : []),
|
|
191
|
+
...(profile.quality.linter ? [`linter:${profile.quality.linter.name}`] : []),
|
|
192
|
+
...(profile.ci.length ? profile.ci.map((system) => `ci:${system}`) : []),
|
|
193
|
+
];
|
|
63
194
|
}
|
package/dist/scanner.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ignore from "ignore";
|
|
1
2
|
import { lstat, readFile, readdir } from "node:fs/promises";
|
|
2
3
|
import { basename, relative, resolve } from "node:path";
|
|
3
4
|
const DEFAULT_IGNORES = new Set([
|
|
@@ -11,48 +12,49 @@ const DEFAULT_IGNORES = new Set([
|
|
|
11
12
|
"coverage",
|
|
12
13
|
]);
|
|
13
14
|
const SENSITIVE_NAMES = /^(\.env(?:\..*)?|.*\.(pem|key|p12|pfx|secret))$/i;
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return
|
|
15
|
+
function isIgnoredByProjectRules(absolute, isDirectory, scopes) {
|
|
16
|
+
let ignored = false;
|
|
17
|
+
for (const scope of scopes) {
|
|
18
|
+
const scopedPath = relative(scope.root, absolute).split("\\").join("/");
|
|
19
|
+
if (!scopedPath || scopedPath === ".." || scopedPath.startsWith("../"))
|
|
20
|
+
continue;
|
|
21
|
+
const result = scope.matcher.test(isDirectory ? `${scopedPath}/` : scopedPath);
|
|
22
|
+
if (result.ignored)
|
|
23
|
+
ignored = true;
|
|
24
|
+
else if (result.unignored)
|
|
25
|
+
ignored = false;
|
|
26
|
+
}
|
|
27
|
+
return ignored;
|
|
27
28
|
}
|
|
28
|
-
async function
|
|
29
|
+
async function addIgnoreFile(directory, scopes) {
|
|
29
30
|
try {
|
|
30
|
-
const content = await readFile(resolve(
|
|
31
|
-
return content
|
|
32
|
-
.split(/\r?\n/)
|
|
33
|
-
.map((line) => line.trim())
|
|
34
|
-
.filter((line) => line && !line.startsWith("#"));
|
|
31
|
+
const content = await readFile(resolve(directory, ".gitignore"), "utf8");
|
|
32
|
+
return [...scopes, { root: directory, matcher: ignore().add(content) }];
|
|
35
33
|
}
|
|
36
|
-
catch {
|
|
37
|
-
|
|
34
|
+
catch (error) {
|
|
35
|
+
if (error.code === "ENOENT")
|
|
36
|
+
return scopes;
|
|
37
|
+
throw error;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
async function collect(root, current,
|
|
40
|
+
async function collect(root, current, inheritedScopes, files) {
|
|
41
|
+
const scopes = await addIgnoreFile(current, inheritedScopes);
|
|
41
42
|
const entries = await readdir(current, { withFileTypes: true });
|
|
42
43
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
43
44
|
const absolute = resolve(current, entry.name);
|
|
44
45
|
const relativePath = relative(root, absolute).split("\\").join("/");
|
|
46
|
+
const isDirectory = entry.isDirectory();
|
|
45
47
|
if (DEFAULT_IGNORES.has(entry.name) ||
|
|
46
48
|
SENSITIVE_NAMES.test(entry.name) ||
|
|
47
|
-
|
|
49
|
+
isIgnoredByProjectRules(absolute, isDirectory, scopes))
|
|
48
50
|
continue;
|
|
49
51
|
if (entry.isSymbolicLink()) {
|
|
50
52
|
const link = await lstat(absolute);
|
|
51
53
|
if (link.isSymbolicLink())
|
|
52
54
|
continue;
|
|
53
55
|
}
|
|
54
|
-
if (
|
|
55
|
-
await collect(root, absolute,
|
|
56
|
+
if (isDirectory)
|
|
57
|
+
await collect(root, absolute, scopes, files);
|
|
56
58
|
else if (entry.isFile())
|
|
57
59
|
files.push(relativePath);
|
|
58
60
|
}
|
|
@@ -78,6 +80,6 @@ function detectEvidence(files) {
|
|
|
78
80
|
export async function scanProject(root) {
|
|
79
81
|
const projectRoot = resolve(root);
|
|
80
82
|
const files = [];
|
|
81
|
-
await collect(projectRoot, projectRoot,
|
|
83
|
+
await collect(projectRoot, projectRoot, [], files);
|
|
82
84
|
return { files, evidence: detectEvidence(files) };
|
|
83
85
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,12 +11,14 @@ export type FileSystem = {
|
|
|
11
11
|
read(path: string): string;
|
|
12
12
|
readBytes?(path: string): Uint8Array;
|
|
13
13
|
write(path: string, content: string): void;
|
|
14
|
+
mkdirExclusive?(path: string): boolean;
|
|
14
15
|
createExclusive?(path: string, content: string): boolean;
|
|
15
16
|
writeBytes?(path: string, content: Uint8Array): void;
|
|
16
17
|
mkdir(path: string): void;
|
|
17
18
|
list?(path: string): string[];
|
|
18
19
|
listFiles?(path: string): string[];
|
|
19
20
|
remove?(path: string): void;
|
|
21
|
+
removeDirectory?(path: string): void;
|
|
20
22
|
pathType?(path: string): PathType;
|
|
21
23
|
realpath?(path: string): string;
|
|
22
24
|
};
|