@kontourai/flow-agents 1.1.0 → 1.2.0
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/.github/workflows/runtime-compat.yml +5 -2
- package/CHANGELOG.md +26 -0
- package/README.md +26 -5
- package/build/src/cli/{flow-kit.js → kit.js} +122 -108
- package/build/src/cli/validate-source-tree.js +4 -4
- package/build/src/cli.js +3 -3
- package/build/src/flow-kit/validate.js +58 -62
- package/build/src/tools/build-universal-bundles.js +64 -17
- package/build/src/tools/generate-context-map.js +49 -7
- package/build/src/tools/validate-source-tree.js +32 -1
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +169 -0
- package/docs/adr/0007-skill-audit.md +112 -0
- package/docs/adr/0008-kit-operation-boundary.md +88 -0
- package/docs/context-map.md +18 -22
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +177 -0
- package/docs/index.md +19 -8
- package/docs/kit-authoring-guide.md +26 -7
- package/docs/knowledge-kit.md +2 -2
- package/docs/spec/runtime-hook-surface.md +1 -1
- package/docs/vision.md +1 -1
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/fixtures/builder-kit-workflow-state/happy-path.json +2 -2
- package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +2 -2
- package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +1 -1
- package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
- package/evals/integration/test_activate_npx_context.sh +2 -2
- package/evals/integration/test_bundle_install.sh +17 -12
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_flow_kit_install_git.sh +7 -7
- package/evals/integration/test_flow_kit_repository.sh +4 -4
- package/evals/integration/test_kit_conformance_levels.sh +1 -1
- package/evals/integration/test_local_flow_kit_install.sh +7 -7
- package/evals/integration/test_publish_change_helper.sh +1 -1
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_runtime_adapter_activation.sh +3 -3
- package/evals/lib/node.sh +2 -2
- package/evals/static/test_workflow_skills.sh +15 -15
- package/integrations/strands/flow_agents_strands/steering.py +1 -1
- package/integrations/strands-ts/src/hooks.ts +1 -1
- package/kits/builder/kit.json +17 -0
- package/{skills → kits/builder/skills}/builder-shape/SKILL.md +4 -4
- package/{skills → kits/builder/skills}/idea-to-backlog/SKILL.md +1 -1
- package/kits/knowledge/kit.json +16 -9
- package/package.json +8 -5
- package/packaging/packs.json +1 -21
- package/scripts/README.md +1 -1
- package/scripts/kit.js +2 -0
- package/skills/README.md +23 -0
- package/src/cli/{flow-kit.ts → kit.ts} +124 -109
- package/src/cli/validate-source-tree.ts +4 -4
- package/src/cli.ts +3 -3
- package/src/flow-kit/validate.ts +63 -57
- package/src/tools/build-universal-bundles.ts +60 -13
- package/src/tools/generate-context-map.ts +36 -6
- package/src/tools/validate-source-tree.ts +27 -1
- package/scripts/flow-kit.js +0 -2
- package/skills/context-budget/SKILL.md +0 -40
- package/skills/explore/SKILL.md +0 -137
- package/skills/feedback-loop/SKILL.md +0 -87
- package/skills/frontend-design/SKILL.md +0 -80
- /package/{skills → kits/builder/skills}/deliver/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/design-probe/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/evidence-gate/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/execute-plan/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/fix-bug/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/learning-review/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/pickup-probe/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/plan-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/pull-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/release-readiness/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/review-work/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/tdd-workflow/SKILL.md +0 -0
- /package/{skills → kits/builder/skills}/verify-work/SKILL.md +0 -0
- /package/{skills → kits/knowledge/skills}/knowledge-capture/SKILL.md +0 -0
package/packaging/packs.json
CHANGED
|
@@ -7,13 +7,6 @@
|
|
|
7
7
|
"description": "Small default surface for reliable coding and workflow execution.",
|
|
8
8
|
"skills": [
|
|
9
9
|
"search-first",
|
|
10
|
-
"plan-work",
|
|
11
|
-
"execute-plan",
|
|
12
|
-
"review-work",
|
|
13
|
-
"verify-work",
|
|
14
|
-
"evidence-gate",
|
|
15
|
-
"feedback-loop",
|
|
16
|
-
"knowledge-capture",
|
|
17
10
|
"browser-test"
|
|
18
11
|
],
|
|
19
12
|
"agents": [
|
|
@@ -32,23 +25,10 @@
|
|
|
32
25
|
"default": false,
|
|
33
26
|
"description": "Development workflow depth for backlog, release, dependency, GitHub, TDD, and frontend work.",
|
|
34
27
|
"skills": [
|
|
35
|
-
"builder-shape",
|
|
36
|
-
"idea-to-backlog",
|
|
37
|
-
"pull-work",
|
|
38
|
-
"design-probe",
|
|
39
|
-
"pickup-probe",
|
|
40
|
-
"deliver",
|
|
41
|
-
"fix-bug",
|
|
42
|
-
"tdd-workflow",
|
|
43
|
-
"release-readiness",
|
|
44
|
-
"learning-review",
|
|
45
28
|
"dependency-update",
|
|
46
29
|
"eval-rebuild",
|
|
47
|
-
"explore",
|
|
48
30
|
"github-cli",
|
|
49
|
-
"
|
|
50
|
-
"agentic-engineering",
|
|
51
|
-
"context-budget"
|
|
31
|
+
"agentic-engineering"
|
|
52
32
|
],
|
|
53
33
|
"agents": [
|
|
54
34
|
"dev",
|
package/scripts/README.md
CHANGED
|
@@ -11,7 +11,7 @@ These files are stable launchers for TypeScript code compiled under `build/src/`
|
|
|
11
11
|
| `build-universal-bundles.js` | `build/src/tools/build-universal-bundles.js` |
|
|
12
12
|
| `filter-installed-packs.js` | `build/src/tools/filter-installed-packs.js` |
|
|
13
13
|
| `generate-context-map.js` | `build/src/tools/generate-context-map.js` |
|
|
14
|
-
| `
|
|
14
|
+
| `kit.js` | `build/src/cli/kit.js` |
|
|
15
15
|
| `pull-work-provider.js` | `build/src/cli/pull-work-provider.js` |
|
|
16
16
|
| `effective-backlog-settings.js` | `build/src/cli/effective-backlog-settings.js` |
|
|
17
17
|
| `publish-change-helper.js` | `build/src/cli/publish-change-helper.js` |
|
package/scripts/kit.js
ADDED
package/skills/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# skills/
|
|
2
|
+
|
|
3
|
+
This directory contains standalone skills that are **not yet claimed by a specific kit**.
|
|
4
|
+
|
|
5
|
+
## Status: Pending Tool Reclassification
|
|
6
|
+
|
|
7
|
+
The 6 items here are currently classified as TOOLS pending formal reclassification in a follow-up ADR pass (tracking: #62 follow-up, ADR 0007).
|
|
8
|
+
|
|
9
|
+
| Skill | Notes |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| agentic-engineering | Pending reclassification as a tool or context doc |
|
|
12
|
+
| browser-test | Pending reclassification — closely tied to Playwright power |
|
|
13
|
+
| dependency-update | Pending reclassification — tooling-oriented |
|
|
14
|
+
| eval-rebuild | Pending reclassification — project-specific build hook |
|
|
15
|
+
| github-cli | Pending reclassification — CLI tool wrapper |
|
|
16
|
+
| search-first | Pending reclassification — research heuristic |
|
|
17
|
+
|
|
18
|
+
## Kit-owned skills
|
|
19
|
+
|
|
20
|
+
Builder Kit skills now live in `kits/builder/skills/`.
|
|
21
|
+
Knowledge Kit skills now live in `kits/knowledge/skills/`.
|
|
22
|
+
|
|
23
|
+
See `docs/adr/0007-skill-audit.md` for the full audit table and disposition rationale.
|
|
@@ -34,7 +34,7 @@ function contentHash(root: string): string {
|
|
|
34
34
|
return `sha256:${hash.digest("hex")}`;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
/** Content hash that excludes .git and other VCS/cache directories (for install
|
|
37
|
+
/** Content hash that excludes .git and other VCS/cache directories (for install git clones). */
|
|
38
38
|
function kitContentHash(root: string): string {
|
|
39
39
|
const EXCLUDE_DIRS = new Set([".git", "__pycache__", ".pytest_cache"]);
|
|
40
40
|
const hash = crypto.createHash("sha256");
|
|
@@ -50,13 +50,40 @@ function kitContentHash(root: string): string {
|
|
|
50
50
|
return `sha256:${hash.digest("hex")}`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* install <source> [--dest <path>] [--force] [--update] [--ref <branch|tag|sha>]
|
|
55
|
+
*
|
|
56
|
+
* Installs a Flow Kit from a local path or a git URL.
|
|
57
|
+
*
|
|
58
|
+
* - Local path: validates then copies the kit into the destination registry.
|
|
59
|
+
* - Git URL (http://, https://, git+, ssh://, file://): shallow-clones the repository,
|
|
60
|
+
* validates the kit container with @kontourai/flow, then delegates to the install path.
|
|
61
|
+
* Supports an optional #ref fragment in the URL or a separate --ref flag.
|
|
62
|
+
*/
|
|
63
|
+
async function install(argv: string[]): Promise<number> {
|
|
64
|
+
const args = parseArgs(argv);
|
|
65
|
+
const source = args.positionals[0] ?? "";
|
|
66
|
+
if (!source) {
|
|
67
|
+
console.error("install: missing <source> argument");
|
|
68
|
+
console.error("usage: flow-agents kit install <path-or-git-url> [--dest <path>] [--ref <ref>] [--force] [--update]");
|
|
69
|
+
return 2;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Detect git URL: starts with http(s)://, git+, ssh://, file://, or ends with .git
|
|
73
|
+
const isGitUrl = /^(https?:\/\/|git\+|ssh:\/\/|file:\/\/)/.test(source) || source.endsWith(".git");
|
|
74
|
+
|
|
75
|
+
if (isGitUrl) {
|
|
76
|
+
return await installGitSource(source, argv);
|
|
77
|
+
}
|
|
78
|
+
return await installLocalSource(path.resolve(source), argv);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function installLocalSource(source: string, argv: string[]): Promise<number> {
|
|
54
82
|
const args = parseArgs(argv);
|
|
55
|
-
const source = path.resolve(args.positionals[0] ?? "");
|
|
56
83
|
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
57
84
|
let manifest: Record<string, unknown>;
|
|
58
85
|
try {
|
|
59
|
-
manifest = assertKitRepository(source);
|
|
86
|
+
manifest = await assertKitRepository(source);
|
|
60
87
|
} catch (error) {
|
|
61
88
|
console.log("Flow Kit repository validation failed:");
|
|
62
89
|
for (const diagnostic of ((error as Error & { diagnostics?: string[] }).diagnostics ?? [(error as Error).message])) console.log(` - ${diagnostic}`);
|
|
@@ -93,6 +120,86 @@ function installLocal(argv: string[]): number {
|
|
|
93
120
|
return 0;
|
|
94
121
|
}
|
|
95
122
|
|
|
123
|
+
async function installGitSource(rawUrl: string, argv: string[]): Promise<number> {
|
|
124
|
+
const args = parseArgs(argv);
|
|
125
|
+
|
|
126
|
+
// Parse ref: #fragment in URL takes precedence over --ref flag.
|
|
127
|
+
let repoUrl = rawUrl;
|
|
128
|
+
let ref: string | null = null;
|
|
129
|
+
const hashIdx = rawUrl.indexOf("#");
|
|
130
|
+
if (hashIdx !== -1) {
|
|
131
|
+
repoUrl = rawUrl.slice(0, hashIdx);
|
|
132
|
+
ref = rawUrl.slice(hashIdx + 1) || null;
|
|
133
|
+
}
|
|
134
|
+
if (!ref) ref = flagString(args.flags, "ref") ?? null;
|
|
135
|
+
|
|
136
|
+
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
137
|
+
const force = flagBool(args.flags, "force") ?? false;
|
|
138
|
+
const update = flagBool(args.flags, "update") ?? false;
|
|
139
|
+
|
|
140
|
+
// Shallow-clone into a temporary directory.
|
|
141
|
+
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "flow-kit-git-"));
|
|
142
|
+
try {
|
|
143
|
+
const cloneArgs = ["clone", "--depth", "1"];
|
|
144
|
+
if (ref) cloneArgs.push("--branch", ref);
|
|
145
|
+
cloneArgs.push("--", repoUrl, tmpBase);
|
|
146
|
+
try {
|
|
147
|
+
child_process.execFileSync("git", cloneArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const msg = err instanceof Error && (err as NodeJS.ErrnoException & { stderr?: Buffer }).stderr
|
|
150
|
+
? ((err as NodeJS.ErrnoException & { stderr?: Buffer }).stderr as Buffer).toString().trim()
|
|
151
|
+
: String(err);
|
|
152
|
+
console.error(`install: git clone failed: ${msg}`);
|
|
153
|
+
return 1;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validate the cloned kit using the same logic as install local.
|
|
157
|
+
let manifest: Record<string, unknown>;
|
|
158
|
+
try {
|
|
159
|
+
manifest = await assertKitRepository(tmpBase);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.log("Flow Kit repository validation failed:");
|
|
162
|
+
for (const diagnostic of ((error as Error & { diagnostics?: string[] }).diagnostics ?? [(error as Error).message])) {
|
|
163
|
+
console.log(` - ${diagnostic}`);
|
|
164
|
+
}
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Delegate to the shared install logic (copy + registry update).
|
|
169
|
+
const kitId = String(manifest.id);
|
|
170
|
+
const hash = kitContentHash(tmpBase);
|
|
171
|
+
const registry = loadRegistry(dest);
|
|
172
|
+
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
173
|
+
const target = installedPath(dest, kitId);
|
|
174
|
+
assertPathContained(dest, target);
|
|
175
|
+
const sourceText = repoUrl + (ref ? `#${ref}` : "");
|
|
176
|
+
if (existing && existing.source !== sourceText && !update) {
|
|
177
|
+
console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
|
|
178
|
+
return 2;
|
|
179
|
+
}
|
|
180
|
+
if (existing && existing.source === sourceText && existing.hash === hash && fs.existsSync(target) && !force) {
|
|
181
|
+
console.log(`kit '${kitId}' is already installed from ${sourceText}`);
|
|
182
|
+
return 0;
|
|
183
|
+
}
|
|
184
|
+
copyDir(tmpBase, target);
|
|
185
|
+
const entry: Record<string, unknown> = {
|
|
186
|
+
id: kitId,
|
|
187
|
+
source: sourceText,
|
|
188
|
+
hash,
|
|
189
|
+
installed_at: existing && existing.source === sourceText && !update ? existing.installed_at : isoNow(),
|
|
190
|
+
installed_path: target,
|
|
191
|
+
state: "installed",
|
|
192
|
+
};
|
|
193
|
+
if (typeof manifest.version === "string" && manifest.version) entry.version = manifest.version;
|
|
194
|
+
registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
|
|
195
|
+
writeJson(registryPath(dest), registry);
|
|
196
|
+
console.log(`${existing ? "updated" : "installed"} git kit '${kitId}' from ${sourceText} at ${target}`);
|
|
197
|
+
return 0;
|
|
198
|
+
} finally {
|
|
199
|
+
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
96
203
|
function list(argv: string[]): number {
|
|
97
204
|
const args = parseArgs(argv);
|
|
98
205
|
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
@@ -153,7 +260,8 @@ function activate(argv: string[]): number {
|
|
|
153
260
|
* inspect <kit-dir> [--json]
|
|
154
261
|
*
|
|
155
262
|
* Derives conformance level (K0/K1/K2) and consumer targets from a kit's
|
|
156
|
-
* observable asset classes.
|
|
263
|
+
* observable asset classes. Delegates core container validation to @kontourai/flow.
|
|
264
|
+
* Exits 1 if the kit fails core container validation.
|
|
157
265
|
* Outputs stable JSON suitable for use by catalog tooling and CI.
|
|
158
266
|
*
|
|
159
267
|
* K-levels (issue #52):
|
|
@@ -166,7 +274,7 @@ function activate(argv: string[]): number {
|
|
|
166
274
|
* flow-agents present at K1+ (Flow Agents extension activated)
|
|
167
275
|
* <namespace> unknown top-level keys list verbatim as third-party consumer targets
|
|
168
276
|
*/
|
|
169
|
-
function inspect(argv: string[]): number {
|
|
277
|
+
async function inspect(argv: string[]): Promise<number> {
|
|
170
278
|
const args = parseArgs(argv);
|
|
171
279
|
const kitDir = path.resolve(args.positionals[0] ?? ".");
|
|
172
280
|
const manifestPath = path.join(kitDir, "kit.json");
|
|
@@ -181,116 +289,23 @@ function inspect(argv: string[]): number {
|
|
|
181
289
|
console.error(`inspect: invalid JSON in ${manifestPath}: ${(err as Error).message}`);
|
|
182
290
|
return 1;
|
|
183
291
|
}
|
|
184
|
-
|
|
292
|
+
// Pass the real kitDir so @kontourai/flow can validate flow file existence for K0.
|
|
293
|
+
const result = await deriveKitTargets(manifest, kitDir);
|
|
185
294
|
console.log(JSON.stringify(result, null, 2));
|
|
186
295
|
return result.conformance.k0 ? 0 : 1;
|
|
187
296
|
}
|
|
188
297
|
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* install-git <repo-url>[#ref] [--ref <branch|tag|sha>] [--dest <path>] [--force] [--update]
|
|
192
|
-
*
|
|
193
|
-
* Shallow-clones a remote git repository to a temporary directory, validates the kit
|
|
194
|
-
* container with the same logic used by install-local, then delegates to the existing
|
|
195
|
-
* install path. Supports an optional #ref fragment in the URL or a separate --ref flag.
|
|
196
|
-
*
|
|
197
|
-
* Implements kontourai/flow-agents#56 (git-ref install surface).
|
|
198
|
-
*/
|
|
199
|
-
function installGit(argv: string[]): number {
|
|
200
|
-
const args = parseArgs(argv);
|
|
201
|
-
const rawUrl = args.positionals[0] ?? "";
|
|
202
|
-
if (!rawUrl) {
|
|
203
|
-
console.error("install-git: missing <repo-url> argument");
|
|
204
|
-
console.error("usage: flow-kit install-git <repo-url>[#ref] [--ref <branch|tag|sha>] [--dest <path>]");
|
|
205
|
-
return 2;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Parse ref: #fragment in URL takes precedence over --ref flag.
|
|
209
|
-
let repoUrl = rawUrl;
|
|
210
|
-
let ref: string | null = null;
|
|
211
|
-
const hashIdx = rawUrl.indexOf("#");
|
|
212
|
-
if (hashIdx !== -1) {
|
|
213
|
-
repoUrl = rawUrl.slice(0, hashIdx);
|
|
214
|
-
ref = rawUrl.slice(hashIdx + 1) || null;
|
|
215
|
-
}
|
|
216
|
-
if (!ref) ref = flagString(args.flags, "ref") ?? null;
|
|
217
|
-
|
|
218
|
-
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
219
|
-
const force = flagBool(args.flags, "force") ?? false;
|
|
220
|
-
const update = flagBool(args.flags, "update") ?? false;
|
|
221
|
-
|
|
222
|
-
// Shallow-clone into a temporary directory.
|
|
223
|
-
const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "flow-kit-git-"));
|
|
224
|
-
try {
|
|
225
|
-
const cloneArgs = ["clone", "--depth", "1"];
|
|
226
|
-
if (ref) cloneArgs.push("--branch", ref);
|
|
227
|
-
cloneArgs.push("--", repoUrl, tmpBase);
|
|
228
|
-
try {
|
|
229
|
-
child_process.execFileSync("git", cloneArgs, { stdio: ["ignore", "pipe", "pipe"] });
|
|
230
|
-
} catch (err) {
|
|
231
|
-
const msg = err instanceof Error && (err as NodeJS.ErrnoException & { stderr?: Buffer }).stderr
|
|
232
|
-
? ((err as NodeJS.ErrnoException & { stderr?: Buffer }).stderr as Buffer).toString().trim()
|
|
233
|
-
: String(err);
|
|
234
|
-
console.error(`install-git: git clone failed: ${msg}`);
|
|
235
|
-
return 1;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Validate the cloned kit using the same logic as install-local.
|
|
239
|
-
let manifest: Record<string, unknown>;
|
|
240
|
-
try {
|
|
241
|
-
manifest = assertKitRepository(tmpBase);
|
|
242
|
-
} catch (error) {
|
|
243
|
-
console.log("Flow Kit repository validation failed:");
|
|
244
|
-
for (const diagnostic of ((error as Error & { diagnostics?: string[] }).diagnostics ?? [(error as Error).message])) {
|
|
245
|
-
console.log(` - ${diagnostic}`);
|
|
246
|
-
}
|
|
247
|
-
return 1;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// Delegate to the shared install logic (copy + registry update).
|
|
251
|
-
const kitId = String(manifest.id);
|
|
252
|
-
const hash = kitContentHash(tmpBase);
|
|
253
|
-
const registry = loadRegistry(dest);
|
|
254
|
-
const existing = registry.kits.find((entry) => entry.id === kitId);
|
|
255
|
-
const target = installedPath(dest, kitId);
|
|
256
|
-
assertPathContained(dest, target);
|
|
257
|
-
const sourceText = repoUrl + (ref ? `#${ref}` : "");
|
|
258
|
-
if (existing && existing.source !== sourceText && !update) {
|
|
259
|
-
console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
|
|
260
|
-
return 2;
|
|
261
|
-
}
|
|
262
|
-
if (existing && existing.source === sourceText && existing.hash === hash && fs.existsSync(target) && !force) {
|
|
263
|
-
console.log(`kit '${kitId}' is already installed from ${sourceText}`);
|
|
264
|
-
return 0;
|
|
265
|
-
}
|
|
266
|
-
copyDir(tmpBase, target);
|
|
267
|
-
const entry: Record<string, unknown> = {
|
|
268
|
-
id: kitId,
|
|
269
|
-
source: sourceText,
|
|
270
|
-
hash,
|
|
271
|
-
installed_at: existing && existing.source === sourceText && !update ? existing.installed_at : isoNow(),
|
|
272
|
-
installed_path: target,
|
|
273
|
-
state: "installed",
|
|
274
|
-
};
|
|
275
|
-
if (typeof manifest.version === "string" && manifest.version) entry.version = manifest.version;
|
|
276
|
-
registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
|
|
277
|
-
writeJson(registryPath(dest), registry);
|
|
278
|
-
console.log(`${existing ? "updated" : "installed"} git kit '${kitId}' from ${sourceText} at ${target}`);
|
|
279
|
-
return 0;
|
|
280
|
-
} finally {
|
|
281
|
-
fs.rmSync(tmpBase, { recursive: true, force: true });
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function main(argv = process.argv.slice(2)): number {
|
|
298
|
+
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
286
299
|
const [command, ...rest] = argv;
|
|
287
|
-
if (command === "install
|
|
288
|
-
|
|
300
|
+
if (command === "install") return await install(rest);
|
|
301
|
+
// Legacy sub-subcommands forwarded for backward compatibility within the kit subcommand.
|
|
302
|
+
if (command === "install-local") return await installLocalSource(path.resolve(rest[0] ?? ""), rest);
|
|
303
|
+
if (command === "install-git") return await installGitSource(rest[0] ?? "", rest);
|
|
289
304
|
if (command === "list") return list(rest);
|
|
290
305
|
if (command === "status") return status(rest);
|
|
291
306
|
if (command === "activate") return activate(rest);
|
|
292
|
-
if (command === "inspect") return inspect(rest);
|
|
293
|
-
console.error("usage: flow-kit <install
|
|
307
|
+
if (command === "inspect") return await inspect(rest);
|
|
308
|
+
console.error("usage: flow-agents kit <install|activate|inspect|list|status> ...");
|
|
294
309
|
return 2;
|
|
295
310
|
}
|
|
296
311
|
|
|
@@ -299,4 +314,4 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
299
314
|
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
300
315
|
const _selfRealPath = (() => { try { return fs.realpathSync(fileURLToPath(import.meta.url)); } catch { return fileURLToPath(import.meta.url); } })();
|
|
301
316
|
const _argv1RealPath = (() => { try { return fs.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
302
|
-
if (_selfRealPath === _argv1RealPath) { process.exitCode =
|
|
317
|
+
if (_selfRealPath === _argv1RealPath) { main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; }); }
|
|
@@ -2,11 +2,11 @@ import * as path from "node:path";
|
|
|
2
2
|
import { parseArgs } from "../lib/args.js";
|
|
3
3
|
import { validateKitRepository } from "../flow-kit/validate.js";
|
|
4
4
|
|
|
5
|
-
export function main(argv = process.argv.slice(2)): number {
|
|
5
|
+
export async function main(argv = process.argv.slice(2)): Promise<number> {
|
|
6
6
|
const args = parseArgs(argv);
|
|
7
7
|
const kit = args.flags.kit;
|
|
8
8
|
if (typeof kit === "string") {
|
|
9
|
-
const errors = validateKitRepository(path.resolve(kit));
|
|
9
|
+
const errors = await validateKitRepository(path.resolve(kit));
|
|
10
10
|
if (errors.length) {
|
|
11
11
|
console.log("Flow Kit repository validation failed:");
|
|
12
12
|
for (const error of errors) console.log(` - ${error}`);
|
|
@@ -17,7 +17,7 @@ export function main(argv = process.argv.slice(2)): number {
|
|
|
17
17
|
}
|
|
18
18
|
const root = path.resolve(".");
|
|
19
19
|
const builder = path.join(root, "kits", "builder");
|
|
20
|
-
const errors = validateKitRepository(builder);
|
|
20
|
+
const errors = await validateKitRepository(builder);
|
|
21
21
|
if (errors.length) {
|
|
22
22
|
console.log("Source tree validation failed:");
|
|
23
23
|
for (const error of errors) console.log(` - ${error}`);
|
|
@@ -33,4 +33,4 @@ import * as _fsVST from "node:fs";
|
|
|
33
33
|
import { fileURLToPath as _ftpVST } from "node:url";
|
|
34
34
|
const _selfVST = (() => { try { return _fsVST.realpathSync(_ftpVST(import.meta.url)); } catch { return _ftpVST(import.meta.url); } })();
|
|
35
35
|
const _argv1VST = (() => { try { return _fsVST.realpathSync(process.argv[1]); } catch { return process.argv[1]; } })();
|
|
36
|
-
if (_selfVST === _argv1VST) { process.exitCode =
|
|
36
|
+
if (_selfVST === _argv1VST) { main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; }); }
|
package/src/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { basename } from "node:path";
|
|
3
3
|
import { main as effectiveBacklogSettings } from "./cli/effective-backlog-settings.js";
|
|
4
4
|
import { main as consoleLearningProjection } from "./cli/console-learning-projection.js";
|
|
5
|
-
import { main as
|
|
5
|
+
import { main as kit } from "./cli/kit.js";
|
|
6
6
|
import { main as fixtureRetirementAudit } from "./cli/fixture-retirement-audit.js";
|
|
7
7
|
import { main as init } from "./cli/init.js";
|
|
8
8
|
import { main as promoteWorkflowArtifact } from "./cli/promote-workflow-artifact.js";
|
|
@@ -28,7 +28,7 @@ const availableCommands = new Map<string, (argv: string[]) => number | Promise<n
|
|
|
28
28
|
["effective-backlog-settings", effectiveBacklogSettings],
|
|
29
29
|
["filter-installed-packs", filterInstalledPacks],
|
|
30
30
|
["fixture-retirement-audit", fixtureRetirementAudit],
|
|
31
|
-
["
|
|
31
|
+
["kit", kit],
|
|
32
32
|
["init", init],
|
|
33
33
|
["promote-workflow-artifact", promoteWorkflowArtifact],
|
|
34
34
|
["publish-change", publishChange],
|
|
@@ -51,7 +51,7 @@ const aliases = new Map<string, string>([
|
|
|
51
51
|
["flow-agents-effective-backlog-settings", "effective-backlog-settings"],
|
|
52
52
|
["flow-agents-filter-installed-packs", "filter-installed-packs"],
|
|
53
53
|
["flow-agents-fixture-retirement-audit", "fixture-retirement-audit"],
|
|
54
|
-
["flow-agents-
|
|
54
|
+
["flow-agents-kit", "kit"],
|
|
55
55
|
["flow-agents-promote-workflow-artifact", "promote-workflow-artifact"],
|
|
56
56
|
["flow-agents-publish-change", "publish-change"],
|
|
57
57
|
["flow-agents-pull-work-provider", "pull-work-provider"],
|
package/src/flow-kit/validate.ts
CHANGED
|
@@ -2,7 +2,8 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { readJson } from "../lib/fs.js";
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Extension-only asset classes: validated by Flow Agents. Flows are validated by @kontourai/flow.
|
|
6
|
+
const EXTENSION_ASSET_CLASSES = ["skills", "docs", "adapters", "evals", "assets"] as const;
|
|
6
7
|
|
|
7
8
|
// Core container fields owned by kontourai/flow (flow-kit-container.schema.json).
|
|
8
9
|
// agent-extension fields are skills, docs, adapters, evals, assets.
|
|
@@ -33,43 +34,52 @@ export interface KitTargetsResult {
|
|
|
33
34
|
third_party_extensions: string[];
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// Lazy-loaded cache for validateKitContainer from @kontourai/flow.
|
|
38
|
+
// list/status/activate are runtime ops that never call validation and must NOT load
|
|
39
|
+
// @kontourai/flow (it is unresolvable in a standalone installed bundle).
|
|
40
|
+
// Only validate/inspect (authoring ops) trigger this load.
|
|
41
|
+
type ValidateKitContainerFn = (kitDir: string, manifest: Record<string, unknown>) => { valid: boolean; diagnostics: { severity: string; path: string; message: string }[] };
|
|
42
|
+
let _validateKitContainerCache: ValidateKitContainerFn | null = null;
|
|
43
|
+
|
|
44
|
+
async function loadValidateKitContainer(): Promise<ValidateKitContainerFn> {
|
|
45
|
+
if (_validateKitContainerCache) return _validateKitContainerCache;
|
|
46
|
+
let mod: { validateKitContainer?: unknown };
|
|
47
|
+
try {
|
|
48
|
+
mod = await import("@kontourai/flow") as { validateKitContainer?: unknown };
|
|
49
|
+
} catch (err) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
"container validation requires @kontourai/flow; run from an npm-installed flow-agents workspace " +
|
|
52
|
+
`or use 'flow kit validate' (original error: ${(err as Error).message})`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
if (typeof mod.validateKitContainer !== "function") {
|
|
56
|
+
throw new Error("@kontourai/flow did not export validateKitContainer");
|
|
57
|
+
}
|
|
58
|
+
_validateKitContainerCache = mod.validateKitContainer as ValidateKitContainerFn;
|
|
59
|
+
return _validateKitContainerCache;
|
|
60
|
+
}
|
|
61
|
+
|
|
36
62
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* Returns a list of violation messages (empty = valid).
|
|
63
|
+
* Delegates core Flow Kit container validation to @kontourai/flow's validateKitContainer.
|
|
64
|
+
* The container contract lives once, in Flow. Returns a list of violation messages (empty = valid).
|
|
40
65
|
*
|
|
41
66
|
* The degradation invariant: every Flow Agents Kit MUST remain a valid core
|
|
42
67
|
* Flow Kit container when agent-extension fields are ignored.
|
|
68
|
+
*
|
|
69
|
+
* Loads @kontourai/flow lazily (on first call) so that runtime ops (list/status/activate)
|
|
70
|
+
* that never invoke validation can run in standalone installed bundles where
|
|
71
|
+
* @kontourai/flow is not present.
|
|
72
|
+
*
|
|
73
|
+
* @param kitDir Real kit directory path for file-existence checks on flows[].path entries.
|
|
74
|
+
* Pass the actual kit directory when available; pass "" for structural-only checks.
|
|
43
75
|
*/
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
if (typeof manifest.name !== "string" || !manifest.name.trim()) {
|
|
53
|
-
errors.push(`${label}: .name must be a non-empty string`);
|
|
54
|
-
}
|
|
55
|
-
if (!Array.isArray(manifest.flows) || manifest.flows.length === 0) {
|
|
56
|
-
errors.push(`${label}: .flows must be a non-empty list`);
|
|
57
|
-
} else {
|
|
58
|
-
manifest.flows.forEach((entry: unknown, index: number) => {
|
|
59
|
-
if (typeof entry !== "object" || entry === null) {
|
|
60
|
-
errors.push(`${label}: flows[${index}] must be an object`);
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
const flow = entry as Record<string, unknown>;
|
|
64
|
-
if (typeof flow.id !== "string" || !flow.id) {
|
|
65
|
-
errors.push(`${label}: flows[${index}].id must be a string`);
|
|
66
|
-
}
|
|
67
|
-
if (typeof flow.path !== "string" || !flow.path) {
|
|
68
|
-
errors.push(`${label}: flows[${index}].path must be a string`);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
return errors;
|
|
76
|
+
async function delegateCoreContainerValidation(kitDir: string, manifest: Record<string, unknown>): Promise<string[]> {
|
|
77
|
+
const validateKitContainer = await loadValidateKitContainer();
|
|
78
|
+
const result = validateKitContainer(kitDir, manifest);
|
|
79
|
+
if (result.valid) return [];
|
|
80
|
+
return result.diagnostics
|
|
81
|
+
.filter((d) => d.severity === "error")
|
|
82
|
+
.map((d) => `${d.path}: ${d.message}`);
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
/**
|
|
@@ -83,12 +93,17 @@ export function validateCoreContainer(manifest: Record<string, unknown>, label:
|
|
|
83
93
|
* - targets.flow: always present when K0 (any Flow consumer can evaluate gates).
|
|
84
94
|
* - targets.flow-agents: present when K1 (agent extension assets activate in >=1 harness).
|
|
85
95
|
* - third-party: any top-level keys that are not core fields and not Flow Agents extension classes.
|
|
96
|
+
*
|
|
97
|
+
* @param manifest The kit.json manifest object.
|
|
98
|
+
* @param kitDir Kit directory for flow file-existence checks. Defaults to "" (structural-only).
|
|
99
|
+
* Pass the real kit directory from `inspect` to get authoritative K0 validation.
|
|
86
100
|
*/
|
|
87
|
-
export function deriveKitTargets(manifest: Record<string, unknown
|
|
101
|
+
export async function deriveKitTargets(manifest: Record<string, unknown>, kitDir = ""): Promise<KitTargetsResult> {
|
|
88
102
|
const kitId = typeof manifest.id === "string" ? manifest.id : "<unknown>";
|
|
89
103
|
const kitName = typeof manifest.name === "string" ? manifest.name : "<unknown>";
|
|
90
104
|
|
|
91
|
-
|
|
105
|
+
// Delegate core container validation to @kontourai/flow.
|
|
106
|
+
const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
|
|
92
107
|
const k0 = coreErrors.length === 0;
|
|
93
108
|
|
|
94
109
|
const hasAgentExtension = AGENT_EXTENSION_CLASSES.size > 0 &&
|
|
@@ -119,7 +134,7 @@ export function deriveKitTargets(manifest: Record<string, unknown>): KitTargetsR
|
|
|
119
134
|
};
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
export function validateKitRepository(kitDir: string): string[] {
|
|
137
|
+
export async function validateKitRepository(kitDir: string): Promise<string[]> {
|
|
123
138
|
const errors: string[] = [];
|
|
124
139
|
const manifestPath = path.join(kitDir, "kit.json");
|
|
125
140
|
let manifest: Record<string, unknown>;
|
|
@@ -129,25 +144,17 @@ export function validateKitRepository(kitDir: string): string[] {
|
|
|
129
144
|
errors.push(`${manifestPath}: invalid JSON: ${(error as Error).message}`);
|
|
130
145
|
return errors;
|
|
131
146
|
}
|
|
132
|
-
if (manifest.schema_version !== "1.0") errors.push(`${manifestPath}: .schema_version must be "1.0"`);
|
|
133
|
-
if (typeof manifest.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(manifest.id)) {
|
|
134
|
-
errors.push(`${manifestPath}: .id must be a stable kebab-case string`);
|
|
135
|
-
}
|
|
136
|
-
if (typeof manifest.name !== "string" || !manifest.name.trim()) errors.push(`${manifestPath}: .name must be a non-empty string`);
|
|
137
147
|
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const coreErrors = validateCoreContainer(coreManifest, manifestPath);
|
|
145
|
-
for (const err of coreErrors) {
|
|
146
|
-
// Deduplicate: only add if not already covered by top-level checks above.
|
|
147
|
-
if (!errors.some((existing) => existing === err)) errors.push(err);
|
|
148
|
-
}
|
|
148
|
+
// Delegate core container validation (schema_version, id, name, flows including file
|
|
149
|
+
// existence) to @kontourai/flow — the container contract lives once, in Flow.
|
|
150
|
+
// This enforces the degradation invariant: a Flow Agents Kit must remain a valid
|
|
151
|
+
// core Flow Kit container when extension fields are stripped.
|
|
152
|
+
const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
|
|
153
|
+
for (const err of coreErrors) errors.push(err);
|
|
149
154
|
|
|
150
|
-
|
|
155
|
+
// Flow Agents extension validation: skills, docs, adapters, evals, assets.
|
|
156
|
+
// Flows are validated above by @kontourai/flow; only extension classes are checked here.
|
|
157
|
+
for (const section of EXTENSION_ASSET_CLASSES) {
|
|
151
158
|
const entries = manifest[section];
|
|
152
159
|
if (entries === undefined) continue;
|
|
153
160
|
if (!Array.isArray(entries)) {
|
|
@@ -182,16 +189,15 @@ export function validateKitRepository(kitDir: string): string[] {
|
|
|
182
189
|
return;
|
|
183
190
|
}
|
|
184
191
|
if (!fs.existsSync(resolved)) {
|
|
185
|
-
|
|
186
|
-
errors.push(`${manifestPath}: ${section}[${index}].path points at missing ${noun}: ${rel}`);
|
|
192
|
+
errors.push(`${manifestPath}: ${section}[${index}].path points at missing asset: ${rel}`);
|
|
187
193
|
}
|
|
188
194
|
});
|
|
189
195
|
}
|
|
190
196
|
return errors;
|
|
191
197
|
}
|
|
192
198
|
|
|
193
|
-
export function assertKitRepository(kitDir: string): Record<string, unknown
|
|
194
|
-
const errors = validateKitRepository(kitDir);
|
|
199
|
+
export async function assertKitRepository(kitDir: string): Promise<Record<string, unknown>> {
|
|
200
|
+
const errors = await validateKitRepository(kitDir);
|
|
195
201
|
if (errors.length) {
|
|
196
202
|
const error = new Error("Flow Kit repository validation failed") as Error & { diagnostics?: string[] };
|
|
197
203
|
error.diagnostics = errors;
|