@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.
Files changed (75) hide show
  1. package/.github/workflows/runtime-compat.yml +5 -2
  2. package/CHANGELOG.md +26 -0
  3. package/README.md +26 -5
  4. package/build/src/cli/{flow-kit.js → kit.js} +122 -108
  5. package/build/src/cli/validate-source-tree.js +4 -4
  6. package/build/src/cli.js +3 -3
  7. package/build/src/flow-kit/validate.js +58 -62
  8. package/build/src/tools/build-universal-bundles.js +64 -17
  9. package/build/src/tools/generate-context-map.js +49 -7
  10. package/build/src/tools/validate-source-tree.js +32 -1
  11. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +169 -0
  12. package/docs/adr/0007-skill-audit.md +112 -0
  13. package/docs/adr/0008-kit-operation-boundary.md +88 -0
  14. package/docs/context-map.md +18 -22
  15. package/docs/flow-kit-repository-contract.md +5 -5
  16. package/docs/getting-started.md +177 -0
  17. package/docs/index.md +19 -8
  18. package/docs/kit-authoring-guide.md +26 -7
  19. package/docs/knowledge-kit.md +2 -2
  20. package/docs/spec/runtime-hook-surface.md +1 -1
  21. package/docs/vision.md +1 -1
  22. package/docs/workflow-usage-guide.md +1 -1
  23. package/evals/fixtures/builder-kit-workflow-state/happy-path.json +2 -2
  24. package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +2 -2
  25. package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +1 -1
  26. package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
  27. package/evals/integration/test_activate_npx_context.sh +2 -2
  28. package/evals/integration/test_bundle_install.sh +17 -12
  29. package/evals/integration/test_console_learning_projection.sh +1 -1
  30. package/evals/integration/test_flow_kit_install_git.sh +7 -7
  31. package/evals/integration/test_flow_kit_repository.sh +4 -4
  32. package/evals/integration/test_kit_conformance_levels.sh +1 -1
  33. package/evals/integration/test_local_flow_kit_install.sh +7 -7
  34. package/evals/integration/test_publish_change_helper.sh +1 -1
  35. package/evals/integration/test_pull_work_provider.sh +1 -1
  36. package/evals/integration/test_runtime_adapter_activation.sh +3 -3
  37. package/evals/lib/node.sh +2 -2
  38. package/evals/static/test_workflow_skills.sh +15 -15
  39. package/integrations/strands/flow_agents_strands/steering.py +1 -1
  40. package/integrations/strands-ts/src/hooks.ts +1 -1
  41. package/kits/builder/kit.json +17 -0
  42. package/{skills → kits/builder/skills}/builder-shape/SKILL.md +4 -4
  43. package/{skills → kits/builder/skills}/idea-to-backlog/SKILL.md +1 -1
  44. package/kits/knowledge/kit.json +16 -9
  45. package/package.json +8 -5
  46. package/packaging/packs.json +1 -21
  47. package/scripts/README.md +1 -1
  48. package/scripts/kit.js +2 -0
  49. package/skills/README.md +23 -0
  50. package/src/cli/{flow-kit.ts → kit.ts} +124 -109
  51. package/src/cli/validate-source-tree.ts +4 -4
  52. package/src/cli.ts +3 -3
  53. package/src/flow-kit/validate.ts +63 -57
  54. package/src/tools/build-universal-bundles.ts +60 -13
  55. package/src/tools/generate-context-map.ts +36 -6
  56. package/src/tools/validate-source-tree.ts +27 -1
  57. package/scripts/flow-kit.js +0 -2
  58. package/skills/context-budget/SKILL.md +0 -40
  59. package/skills/explore/SKILL.md +0 -137
  60. package/skills/feedback-loop/SKILL.md +0 -87
  61. package/skills/frontend-design/SKILL.md +0 -80
  62. /package/{skills → kits/builder/skills}/deliver/SKILL.md +0 -0
  63. /package/{skills → kits/builder/skills}/design-probe/SKILL.md +0 -0
  64. /package/{skills → kits/builder/skills}/evidence-gate/SKILL.md +0 -0
  65. /package/{skills → kits/builder/skills}/execute-plan/SKILL.md +0 -0
  66. /package/{skills → kits/builder/skills}/fix-bug/SKILL.md +0 -0
  67. /package/{skills → kits/builder/skills}/learning-review/SKILL.md +0 -0
  68. /package/{skills → kits/builder/skills}/pickup-probe/SKILL.md +0 -0
  69. /package/{skills → kits/builder/skills}/plan-work/SKILL.md +0 -0
  70. /package/{skills → kits/builder/skills}/pull-work/SKILL.md +0 -0
  71. /package/{skills → kits/builder/skills}/release-readiness/SKILL.md +0 -0
  72. /package/{skills → kits/builder/skills}/review-work/SKILL.md +0 -0
  73. /package/{skills → kits/builder/skills}/tdd-workflow/SKILL.md +0 -0
  74. /package/{skills → kits/builder/skills}/verify-work/SKILL.md +0 -0
  75. /package/{skills → kits/knowledge/skills}/knowledge-capture/SKILL.md +0 -0
@@ -40,7 +40,7 @@ jobs:
40
40
  uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
41
41
 
42
42
  - name: Set up Node.js
43
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
43
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
44
44
  with:
45
45
  node-version: 24
46
46
 
@@ -49,6 +49,9 @@ jobs:
49
49
  ${{ matrix.install }}
50
50
  ${{ matrix.version }}
51
51
 
52
+ - name: Install dependencies
53
+ run: npm ci
54
+
52
55
  - name: Build bundles
53
56
  run: npm run build:bundles
54
57
 
@@ -67,7 +70,7 @@ jobs:
67
70
  uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
68
71
 
69
72
  - name: Set up Node.js
70
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
73
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
71
74
  with:
72
75
  node-version: 24
73
76
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0](https://github.com/kontourai/flow-agents/compare/v1.1.0...v1.2.0) (2026-06-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * **#62:** move Builder Kit skills into kits/builder, add Knowledge Kit skill, remove orphans ([3822e07](https://github.com/kontourai/flow-agents/commit/3822e075e9cd488f46124179ebf9a8459825b9c6))
9
+ * **#62:** move Builder Kit skills into kits/builder, Knowledge Kit skill, remove orphans ([31f63ca](https://github.com/kontourai/flow-agents/commit/31f63ca18019d51438accd3b5f1e03cb5f2873f2))
10
+ * delegate container validation to @kontourai/flow; rename flow-kit → flow-agents kit ([d39e909](https://github.com/kontourai/flow-agents/commit/d39e9090dad220a8159d2148d5a1effb2460ac9f))
11
+ * delegate container validation to @kontourai/flow; rename flow-kit → flow-agents kit (ADR 0008) ([4343e84](https://github.com/kontourai/flow-agents/commit/4343e845a992858c9441258bedbbf3c7302a8532))
12
+
13
+
14
+ ### Fixes
15
+
16
+ * **ci:** install repo deps before building bundles in runtime-compat canary ([#76](https://github.com/kontourai/flow-agents/issues/76)) ([f8947aa](https://github.com/kontourai/flow-agents/commit/f8947aab5723ba9325372ea4054458ce21875bee))
17
+ * lazy-load @kontourai/flow in validate.ts so list/status/activate work without it ([99beebb](https://github.com/kontourai/flow-agents/commit/99beebb58f02dba374b35ae5e3df229cb39ea8d0))
18
+
19
+
20
+ ### Documentation
21
+
22
+ * add ADR 0007 flow/skill/kit/tool boundary + skill audit ([20b5c7b](https://github.com/kontourai/flow-agents/commit/20b5c7b272e7ad7985640e70b6be71733cec9995))
23
+ * add Builder Kit quick-start guide and update index/README Quick Start ([2e89bf0](https://github.com/kontourai/flow-agents/commit/2e89bf08968a6f45a26ceddcddf5a66bf77d3f44))
24
+ * ADR 0007 flow/skill/kit/tool boundary + skill audit ([a1dde52](https://github.com/kontourai/flow-agents/commit/a1dde52eb3b051a0eab5712395f0266c7428ae0f))
25
+ * Builder Kit quick-start guide (zero to gated build flow) ([83237f7](https://github.com/kontourai/flow-agents/commit/83237f77812917d49c86547db87986d6dbfdbfd9))
26
+ * fold orphan rulings into ADR 0007, add ADR 0008 kit-operation boundary ([d547edc](https://github.com/kontourai/flow-agents/commit/d547edc954ea9d9a12039003d41401802f994097))
27
+ * mark ADRs 0007 + 0008 Accepted (decisions reached in 2026-06-15 design conversation) ([3eb7636](https://github.com/kontourai/flow-agents/commit/3eb7636c1c4f866fd119195936ec856425573dda))
28
+
3
29
  ## [1.1.0](https://github.com/kontourai/flow-agents/compare/v1.0.1...v1.1.0) (2026-06-15)
4
30
 
5
31
 
package/README.md CHANGED
@@ -99,21 +99,42 @@ bash install.sh /path/to/workspace --telemetry-sink local-kontour-console
99
99
 
100
100
  ## Use it
101
101
 
102
- After installing, ask the agent for the workflow you want — in plain language:
102
+ After installing, ask the agent for the workflow you want — in plain language.
103
+
104
+ ### Builder Kit quick start
105
+
106
+ The Builder Kit installs automatically and gives your agent two gated flows: `builder.shape` turns a raw idea into slices and executable work items; `builder.build` takes a selected work item through design probe, planning, execution, verification, PR readiness, merge readiness, and learning.
107
+
108
+ Shape an idea:
109
+
110
+ ```text
111
+ Use Builder Kit shape. I want to add a progress indicator to the CLI output
112
+ so users can see what step the installer is on. Shape this into an executable
113
+ work item and stop at the backlog gate.
114
+ ```
115
+
116
+ Build it:
103
117
 
104
118
  ```text
105
- Use Builder Kit shape for this feature idea and create executable GitHub issues.
119
+ Use deliver for the issue you just filed. Pull it, probe the design, plan it,
120
+ implement it, verify it, and stop if any evidence is missing.
106
121
  ```
107
122
 
123
+ Each step has an evidence gate. The agent either presents the expected evidence and advances, or blocks and explains what is missing — it does not produce a confident summary and proceed on partial work. Session state is written to `.flow-agents/<slug>/` and survives context loss or compaction.
124
+
125
+ For a full walkthrough — what each gate checks, what you observe, and how to invoke individual skills — read the [Builder Kit Quick Start](docs/getting-started.md).
126
+
127
+ For bugs:
128
+
108
129
  ```text
109
- Use deliver for this issue. Plan it, execute it, verify it, and stop if evidence is missing.
130
+ Use fix-bug. Reproduce the problem, diagnose root cause, implement the fix, and verify the regression path.
110
131
  ```
111
132
 
112
133
  The [Workflow Usage Guide](docs/workflow-usage-guide.md) has example prompts and expected behavior for every stage — `pull-work`, `plan-work`, `execute-plan`, `review-work`, `verify-work`, `fix-bug`, `release-readiness`, and more. The [Agent System Guidebook](docs/agent-system-guidebook.md) is the plain-language map of how the pieces fit.
113
134
 
114
135
  ## Flow Kits
115
136
 
116
- A Flow Kit bundles a workflow AND its opinionated output shape into a single validated unit: a `kit.json` manifest (schema version 1.0), one or more Flow Definitions, and optional skills, docs, adapters, evals, and assets. Authoring a kit means deciding not just _what_ an agent does but _how the result is rendered_ — the same pipeline produces different representations depending on which store adapter is active. Kits are the extension model for Flow Agents: validated by the `flow-kit` CLI, installed through a single command, and activatable into any workspace that runs Flow Agents.
137
+ A Flow Kit bundles a workflow AND its opinionated output shape into a single validated unit: a `kit.json` manifest (schema version 1.0), one or more Flow Definitions, and optional skills, docs, adapters, evals, and assets. Authoring a kit means deciding not just _what_ an agent does but _how the result is rendered_ — the same pipeline produces different representations depending on which store adapter is active. Kits are the extension model for Flow Agents: validated and installed through the `flow-agents kit` CLI, and activatable into any workspace that runs Flow Agents.
117
138
 
118
139
  **Builder Kit** — ships with `builder.shape` (shape a problem into slices and fileable work items) and `builder.build` (pull ready work through design probing, planning, execution, verification, PR readiness, merge readiness, and learning). Installed automatically by `npx @kontourai/flow-agents init`.
119
140
 
@@ -126,7 +147,7 @@ The Knowledge Kit is also LIVE-proven: the default adapter passes the parameteri
126
147
  Install a local kit:
127
148
 
128
149
  ```bash
129
- npx @kontourai/flow-agents flow-kit install-local path/to/my-kit --dest /path/to/workspace
150
+ npx @kontourai/flow-agents kit install path/to/my-kit --dest /path/to/workspace
130
151
  ```
131
152
 
132
153
  - [Kit Authoring Guide](docs/kit-authoring-guide.md) — build your own kit from scratch: directory layout, `kit.json`, a flow file, validation, install, and activation.
@@ -30,7 +30,7 @@ function contentHash(root) {
30
30
  }
31
31
  return `sha256:${hash.digest("hex")}`;
32
32
  }
33
- /** Content hash that excludes .git and other VCS/cache directories (for install-git clones). */
33
+ /** Content hash that excludes .git and other VCS/cache directories (for install git clones). */
34
34
  function kitContentHash(root) {
35
35
  const EXCLUDE_DIRS = new Set([".git", "__pycache__", ".pytest_cache"]);
36
36
  const hash = crypto.createHash("sha256");
@@ -46,13 +46,37 @@ function kitContentHash(root) {
46
46
  }
47
47
  return `sha256:${hash.digest("hex")}`;
48
48
  }
49
- function installLocal(argv) {
49
+ /**
50
+ * install <source> [--dest <path>] [--force] [--update] [--ref <branch|tag|sha>]
51
+ *
52
+ * Installs a Flow Kit from a local path or a git URL.
53
+ *
54
+ * - Local path: validates then copies the kit into the destination registry.
55
+ * - Git URL (http://, https://, git+, ssh://, file://): shallow-clones the repository,
56
+ * validates the kit container with @kontourai/flow, then delegates to the install path.
57
+ * Supports an optional #ref fragment in the URL or a separate --ref flag.
58
+ */
59
+ async function install(argv) {
60
+ const args = parseArgs(argv);
61
+ const source = args.positionals[0] ?? "";
62
+ if (!source) {
63
+ console.error("install: missing <source> argument");
64
+ console.error("usage: flow-agents kit install <path-or-git-url> [--dest <path>] [--ref <ref>] [--force] [--update]");
65
+ return 2;
66
+ }
67
+ // Detect git URL: starts with http(s)://, git+, ssh://, file://, or ends with .git
68
+ const isGitUrl = /^(https?:\/\/|git\+|ssh:\/\/|file:\/\/)/.test(source) || source.endsWith(".git");
69
+ if (isGitUrl) {
70
+ return await installGitSource(source, argv);
71
+ }
72
+ return await installLocalSource(path.resolve(source), argv);
73
+ }
74
+ async function installLocalSource(source, argv) {
50
75
  const args = parseArgs(argv);
51
- const source = path.resolve(args.positionals[0] ?? "");
52
76
  const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
53
77
  let manifest;
54
78
  try {
55
- manifest = assertKitRepository(source);
79
+ manifest = await assertKitRepository(source);
56
80
  }
57
81
  catch (error) {
58
82
  console.log("Flow Kit repository validation failed:");
@@ -91,6 +115,86 @@ function installLocal(argv) {
91
115
  console.log(`${existing ? "updated" : "installed"} local kit '${kitId}' at ${target}`);
92
116
  return 0;
93
117
  }
118
+ async function installGitSource(rawUrl, argv) {
119
+ const args = parseArgs(argv);
120
+ // Parse ref: #fragment in URL takes precedence over --ref flag.
121
+ let repoUrl = rawUrl;
122
+ let ref = null;
123
+ const hashIdx = rawUrl.indexOf("#");
124
+ if (hashIdx !== -1) {
125
+ repoUrl = rawUrl.slice(0, hashIdx);
126
+ ref = rawUrl.slice(hashIdx + 1) || null;
127
+ }
128
+ if (!ref)
129
+ ref = flagString(args.flags, "ref") ?? null;
130
+ const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
131
+ const force = flagBool(args.flags, "force") ?? false;
132
+ const update = flagBool(args.flags, "update") ?? false;
133
+ // Shallow-clone into a temporary directory.
134
+ const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "flow-kit-git-"));
135
+ try {
136
+ const cloneArgs = ["clone", "--depth", "1"];
137
+ if (ref)
138
+ cloneArgs.push("--branch", ref);
139
+ cloneArgs.push("--", repoUrl, tmpBase);
140
+ try {
141
+ child_process.execFileSync("git", cloneArgs, { stdio: ["ignore", "pipe", "pipe"] });
142
+ }
143
+ catch (err) {
144
+ const msg = err instanceof Error && err.stderr
145
+ ? err.stderr.toString().trim()
146
+ : String(err);
147
+ console.error(`install: git clone failed: ${msg}`);
148
+ return 1;
149
+ }
150
+ // Validate the cloned kit using the same logic as install local.
151
+ let manifest;
152
+ try {
153
+ manifest = await assertKitRepository(tmpBase);
154
+ }
155
+ catch (error) {
156
+ console.log("Flow Kit repository validation failed:");
157
+ for (const diagnostic of (error.diagnostics ?? [error.message])) {
158
+ console.log(` - ${diagnostic}`);
159
+ }
160
+ return 1;
161
+ }
162
+ // Delegate to the shared install logic (copy + registry update).
163
+ const kitId = String(manifest.id);
164
+ const hash = kitContentHash(tmpBase);
165
+ const registry = loadRegistry(dest);
166
+ const existing = registry.kits.find((entry) => entry.id === kitId);
167
+ const target = installedPath(dest, kitId);
168
+ assertPathContained(dest, target);
169
+ const sourceText = repoUrl + (ref ? `#${ref}` : "");
170
+ if (existing && existing.source !== sourceText && !update) {
171
+ console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
172
+ return 2;
173
+ }
174
+ if (existing && existing.source === sourceText && existing.hash === hash && fs.existsSync(target) && !force) {
175
+ console.log(`kit '${kitId}' is already installed from ${sourceText}`);
176
+ return 0;
177
+ }
178
+ copyDir(tmpBase, target);
179
+ const entry = {
180
+ id: kitId,
181
+ source: sourceText,
182
+ hash,
183
+ installed_at: existing && existing.source === sourceText && !update ? existing.installed_at : isoNow(),
184
+ installed_path: target,
185
+ state: "installed",
186
+ };
187
+ if (typeof manifest.version === "string" && manifest.version)
188
+ entry.version = manifest.version;
189
+ registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
190
+ writeJson(registryPath(dest), registry);
191
+ console.log(`${existing ? "updated" : "installed"} git kit '${kitId}' from ${sourceText} at ${target}`);
192
+ return 0;
193
+ }
194
+ finally {
195
+ fs.rmSync(tmpBase, { recursive: true, force: true });
196
+ }
197
+ }
94
198
  function list(argv) {
95
199
  const args = parseArgs(argv);
96
200
  const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
@@ -147,7 +251,8 @@ function activate(argv) {
147
251
  * inspect <kit-dir> [--json]
148
252
  *
149
253
  * Derives conformance level (K0/K1/K2) and consumer targets from a kit's
150
- * observable asset classes. Exits 1 if the kit fails core container validation.
254
+ * observable asset classes. Delegates core container validation to @kontourai/flow.
255
+ * Exits 1 if the kit fails core container validation.
151
256
  * Outputs stable JSON suitable for use by catalog tooling and CI.
152
257
  *
153
258
  * K-levels (issue #52):
@@ -160,7 +265,7 @@ function activate(argv) {
160
265
  * flow-agents present at K1+ (Flow Agents extension activated)
161
266
  * <namespace> unknown top-level keys list verbatim as third-party consumer targets
162
267
  */
163
- function inspect(argv) {
268
+ async function inspect(argv) {
164
269
  const args = parseArgs(argv);
165
270
  const kitDir = path.resolve(args.positionals[0] ?? ".");
166
271
  const manifestPath = path.join(kitDir, "kit.json");
@@ -176,111 +281,20 @@ function inspect(argv) {
176
281
  console.error(`inspect: invalid JSON in ${manifestPath}: ${err.message}`);
177
282
  return 1;
178
283
  }
179
- const result = deriveKitTargets(manifest);
284
+ // Pass the real kitDir so @kontourai/flow can validate flow file existence for K0.
285
+ const result = await deriveKitTargets(manifest, kitDir);
180
286
  console.log(JSON.stringify(result, null, 2));
181
287
  return result.conformance.k0 ? 0 : 1;
182
288
  }
183
- /**
184
- * install-git <repo-url>[#ref] [--ref <branch|tag|sha>] [--dest <path>] [--force] [--update]
185
- *
186
- * Shallow-clones a remote git repository to a temporary directory, validates the kit
187
- * container with the same logic used by install-local, then delegates to the existing
188
- * install path. Supports an optional #ref fragment in the URL or a separate --ref flag.
189
- *
190
- * Implements kontourai/flow-agents#56 (git-ref install surface).
191
- */
192
- function installGit(argv) {
193
- const args = parseArgs(argv);
194
- const rawUrl = args.positionals[0] ?? "";
195
- if (!rawUrl) {
196
- console.error("install-git: missing <repo-url> argument");
197
- console.error("usage: flow-kit install-git <repo-url>[#ref] [--ref <branch|tag|sha>] [--dest <path>]");
198
- return 2;
199
- }
200
- // Parse ref: #fragment in URL takes precedence over --ref flag.
201
- let repoUrl = rawUrl;
202
- let ref = null;
203
- const hashIdx = rawUrl.indexOf("#");
204
- if (hashIdx !== -1) {
205
- repoUrl = rawUrl.slice(0, hashIdx);
206
- ref = rawUrl.slice(hashIdx + 1) || null;
207
- }
208
- if (!ref)
209
- ref = flagString(args.flags, "ref") ?? null;
210
- const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
211
- const force = flagBool(args.flags, "force") ?? false;
212
- const update = flagBool(args.flags, "update") ?? false;
213
- // Shallow-clone into a temporary directory.
214
- const tmpBase = fs.mkdtempSync(path.join(os.tmpdir(), "flow-kit-git-"));
215
- try {
216
- const cloneArgs = ["clone", "--depth", "1"];
217
- if (ref)
218
- cloneArgs.push("--branch", ref);
219
- cloneArgs.push("--", repoUrl, tmpBase);
220
- try {
221
- child_process.execFileSync("git", cloneArgs, { stdio: ["ignore", "pipe", "pipe"] });
222
- }
223
- catch (err) {
224
- const msg = err instanceof Error && err.stderr
225
- ? err.stderr.toString().trim()
226
- : String(err);
227
- console.error(`install-git: git clone failed: ${msg}`);
228
- return 1;
229
- }
230
- // Validate the cloned kit using the same logic as install-local.
231
- let manifest;
232
- try {
233
- manifest = assertKitRepository(tmpBase);
234
- }
235
- catch (error) {
236
- console.log("Flow Kit repository validation failed:");
237
- for (const diagnostic of (error.diagnostics ?? [error.message])) {
238
- console.log(` - ${diagnostic}`);
239
- }
240
- return 1;
241
- }
242
- // Delegate to the shared install logic (copy + registry update).
243
- const kitId = String(manifest.id);
244
- const hash = kitContentHash(tmpBase);
245
- const registry = loadRegistry(dest);
246
- const existing = registry.kits.find((entry) => entry.id === kitId);
247
- const target = installedPath(dest, kitId);
248
- assertPathContained(dest, target);
249
- const sourceText = repoUrl + (ref ? `#${ref}` : "");
250
- if (existing && existing.source !== sourceText && !update) {
251
- console.log(`conflict: kit '${kitId}' is already installed from ${existing.source}; rerun with --update to replace it`);
252
- return 2;
253
- }
254
- if (existing && existing.source === sourceText && existing.hash === hash && fs.existsSync(target) && !force) {
255
- console.log(`kit '${kitId}' is already installed from ${sourceText}`);
256
- return 0;
257
- }
258
- copyDir(tmpBase, target);
259
- const entry = {
260
- id: kitId,
261
- source: sourceText,
262
- hash,
263
- installed_at: existing && existing.source === sourceText && !update ? existing.installed_at : isoNow(),
264
- installed_path: target,
265
- state: "installed",
266
- };
267
- if (typeof manifest.version === "string" && manifest.version)
268
- entry.version = manifest.version;
269
- registry.kits = existing ? registry.kits.map((item) => item.id === kitId ? entry : item) : [...registry.kits, entry];
270
- writeJson(registryPath(dest), registry);
271
- console.log(`${existing ? "updated" : "installed"} git kit '${kitId}' from ${sourceText} at ${target}`);
272
- return 0;
273
- }
274
- finally {
275
- fs.rmSync(tmpBase, { recursive: true, force: true });
276
- }
277
- }
278
- export function main(argv = process.argv.slice(2)) {
289
+ export async function main(argv = process.argv.slice(2)) {
279
290
  const [command, ...rest] = argv;
291
+ if (command === "install")
292
+ return await install(rest);
293
+ // Legacy sub-subcommands forwarded for backward compatibility within the kit subcommand.
280
294
  if (command === "install-local")
281
- return installLocal(rest);
295
+ return await installLocalSource(path.resolve(rest[0] ?? ""), rest);
282
296
  if (command === "install-git")
283
- return installGit(rest);
297
+ return await installGitSource(rest[0] ?? "", rest);
284
298
  if (command === "list")
285
299
  return list(rest);
286
300
  if (command === "status")
@@ -288,8 +302,8 @@ export function main(argv = process.argv.slice(2)) {
288
302
  if (command === "activate")
289
303
  return activate(rest);
290
304
  if (command === "inspect")
291
- return inspect(rest);
292
- console.error("usage: flow-kit <install-local|install-git|list|status|activate|inspect> ...");
305
+ return await inspect(rest);
306
+ console.error("usage: flow-agents kit <install|activate|inspect|list|status> ...");
293
307
  return 2;
294
308
  }
295
309
  // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
@@ -308,5 +322,5 @@ catch {
308
322
  return process.argv[1];
309
323
  } })();
310
324
  if (_selfRealPath === _argv1RealPath) {
311
- process.exitCode = main();
325
+ main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; });
312
326
  }
@@ -1,11 +1,11 @@
1
1
  import * as path from "node:path";
2
2
  import { parseArgs } from "../lib/args.js";
3
3
  import { validateKitRepository } from "../flow-kit/validate.js";
4
- export function main(argv = process.argv.slice(2)) {
4
+ export async function main(argv = process.argv.slice(2)) {
5
5
  const args = parseArgs(argv);
6
6
  const kit = args.flags.kit;
7
7
  if (typeof kit === "string") {
8
- const errors = validateKitRepository(path.resolve(kit));
8
+ const errors = await validateKitRepository(path.resolve(kit));
9
9
  if (errors.length) {
10
10
  console.log("Flow Kit repository validation failed:");
11
11
  for (const error of errors)
@@ -17,7 +17,7 @@ export function main(argv = process.argv.slice(2)) {
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)
@@ -44,5 +44,5 @@ catch {
44
44
  return process.argv[1];
45
45
  } })();
46
46
  if (_selfVST === _argv1VST) {
47
- process.exitCode = main();
47
+ main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; });
48
48
  }
package/build/src/cli.js 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 flowKit } from "./cli/flow-kit.js";
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";
@@ -27,7 +27,7 @@ const availableCommands = new Map([
27
27
  ["effective-backlog-settings", effectiveBacklogSettings],
28
28
  ["filter-installed-packs", filterInstalledPacks],
29
29
  ["fixture-retirement-audit", fixtureRetirementAudit],
30
- ["flow-kit", flowKit],
30
+ ["kit", kit],
31
31
  ["init", init],
32
32
  ["promote-workflow-artifact", promoteWorkflowArtifact],
33
33
  ["publish-change", publishChange],
@@ -49,7 +49,7 @@ const aliases = new Map([
49
49
  ["flow-agents-effective-backlog-settings", "effective-backlog-settings"],
50
50
  ["flow-agents-filter-installed-packs", "filter-installed-packs"],
51
51
  ["flow-agents-fixture-retirement-audit", "fixture-retirement-audit"],
52
- ["flow-agents-flow-kit", "flow-kit"],
52
+ ["flow-agents-kit", "kit"],
53
53
  ["flow-agents-promote-workflow-artifact", "promote-workflow-artifact"],
54
54
  ["flow-agents-publish-change", "publish-change"],
55
55
  ["flow-agents-pull-work-provider", "pull-work-provider"],
@@ -1,49 +1,52 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { readJson } from "../lib/fs.js";
4
- const ASSET_CLASSES = ["flows", "skills", "docs", "adapters", "evals", "assets"];
4
+ // Extension-only asset classes: validated by Flow Agents. Flows are validated by @kontourai/flow.
5
+ const EXTENSION_ASSET_CLASSES = ["skills", "docs", "adapters", "evals", "assets"];
5
6
  // Core container fields owned by kontourai/flow (flow-kit-container.schema.json).
6
7
  // agent-extension fields are skills, docs, adapters, evals, assets.
7
8
  const CORE_CONTAINER_FIELDS = new Set(["schema_version", "id", "name", "description", "product_name", "flows"]);
8
9
  const AGENT_EXTENSION_CLASSES = new Set(["skills", "docs", "adapters", "evals", "assets"]);
10
+ let _validateKitContainerCache = null;
11
+ async function loadValidateKitContainer() {
12
+ if (_validateKitContainerCache)
13
+ return _validateKitContainerCache;
14
+ let mod;
15
+ try {
16
+ mod = await import("@kontourai/flow");
17
+ }
18
+ catch (err) {
19
+ throw new Error("container validation requires @kontourai/flow; run from an npm-installed flow-agents workspace " +
20
+ `or use 'flow kit validate' (original error: ${err.message})`);
21
+ }
22
+ if (typeof mod.validateKitContainer !== "function") {
23
+ throw new Error("@kontourai/flow did not export validateKitContainer");
24
+ }
25
+ _validateKitContainerCache = mod.validateKitContainer;
26
+ return _validateKitContainerCache;
27
+ }
9
28
  /**
10
- * Validates that the manifest satisfies the core Flow Kit container contract
11
- * (as specified by kontourai/flow PR #67) with all agent-extension fields stripped.
12
- * Returns a list of violation messages (empty = valid).
29
+ * Delegates core Flow Kit container validation to @kontourai/flow's validateKitContainer.
30
+ * The container contract lives once, in Flow. Returns a list of violation messages (empty = valid).
13
31
  *
14
32
  * The degradation invariant: every Flow Agents Kit MUST remain a valid core
15
33
  * Flow Kit container when agent-extension fields are ignored.
34
+ *
35
+ * Loads @kontourai/flow lazily (on first call) so that runtime ops (list/status/activate)
36
+ * that never invoke validation can run in standalone installed bundles where
37
+ * @kontourai/flow is not present.
38
+ *
39
+ * @param kitDir Real kit directory path for file-existence checks on flows[].path entries.
40
+ * Pass the actual kit directory when available; pass "" for structural-only checks.
16
41
  */
17
- export function validateCoreContainer(manifest, label) {
18
- const errors = [];
19
- if (manifest.schema_version !== "1.0") {
20
- errors.push(`${label}: .schema_version must be "1.0"`);
21
- }
22
- if (typeof manifest.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(manifest.id)) {
23
- errors.push(`${label}: .id must be a stable kebab-case string`);
24
- }
25
- if (typeof manifest.name !== "string" || !manifest.name.trim()) {
26
- errors.push(`${label}: .name must be a non-empty string`);
27
- }
28
- if (!Array.isArray(manifest.flows) || manifest.flows.length === 0) {
29
- errors.push(`${label}: .flows must be a non-empty list`);
30
- }
31
- else {
32
- manifest.flows.forEach((entry, index) => {
33
- if (typeof entry !== "object" || entry === null) {
34
- errors.push(`${label}: flows[${index}] must be an object`);
35
- return;
36
- }
37
- const flow = entry;
38
- if (typeof flow.id !== "string" || !flow.id) {
39
- errors.push(`${label}: flows[${index}].id must be a string`);
40
- }
41
- if (typeof flow.path !== "string" || !flow.path) {
42
- errors.push(`${label}: flows[${index}].path must be a string`);
43
- }
44
- });
45
- }
46
- return errors;
42
+ async function delegateCoreContainerValidation(kitDir, manifest) {
43
+ const validateKitContainer = await loadValidateKitContainer();
44
+ const result = validateKitContainer(kitDir, manifest);
45
+ if (result.valid)
46
+ return [];
47
+ return result.diagnostics
48
+ .filter((d) => d.severity === "error")
49
+ .map((d) => `${d.path}: ${d.message}`);
47
50
  }
48
51
  /**
49
52
  * Derives the consumer-target level (K0/K1/K2) and target audience list from
@@ -56,11 +59,16 @@ export function validateCoreContainer(manifest, label) {
56
59
  * - targets.flow: always present when K0 (any Flow consumer can evaluate gates).
57
60
  * - targets.flow-agents: present when K1 (agent extension assets activate in >=1 harness).
58
61
  * - third-party: any top-level keys that are not core fields and not Flow Agents extension classes.
62
+ *
63
+ * @param manifest The kit.json manifest object.
64
+ * @param kitDir Kit directory for flow file-existence checks. Defaults to "" (structural-only).
65
+ * Pass the real kit directory from `inspect` to get authoritative K0 validation.
59
66
  */
60
- export function deriveKitTargets(manifest) {
67
+ export async function deriveKitTargets(manifest, kitDir = "") {
61
68
  const kitId = typeof manifest.id === "string" ? manifest.id : "<unknown>";
62
69
  const kitName = typeof manifest.name === "string" ? manifest.name : "<unknown>";
63
- const coreErrors = validateCoreContainer(manifest, "kit.json");
70
+ // Delegate core container validation to @kontourai/flow.
71
+ const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
64
72
  const k0 = coreErrors.length === 0;
65
73
  const hasAgentExtension = AGENT_EXTENSION_CLASSES.size > 0 &&
66
74
  [...AGENT_EXTENSION_CLASSES].some((cls) => Array.isArray(manifest[cls]) && manifest[cls].length > 0);
@@ -87,7 +95,7 @@ export function deriveKitTargets(manifest) {
87
95
  third_party_extensions: thirdPartyExtensions,
88
96
  };
89
97
  }
90
- export function validateKitRepository(kitDir) {
98
+ export async function validateKitRepository(kitDir) {
91
99
  const errors = [];
92
100
  const manifestPath = path.join(kitDir, "kit.json");
93
101
  let manifest;
@@ -98,27 +106,16 @@ export function validateKitRepository(kitDir) {
98
106
  errors.push(`${manifestPath}: invalid JSON: ${error.message}`);
99
107
  return errors;
100
108
  }
101
- if (manifest.schema_version !== "1.0")
102
- errors.push(`${manifestPath}: .schema_version must be "1.0"`);
103
- if (typeof manifest.id !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(manifest.id)) {
104
- errors.push(`${manifestPath}: .id must be a stable kebab-case string`);
105
- }
106
- if (typeof manifest.name !== "string" || !manifest.name.trim())
107
- errors.push(`${manifestPath}: .name must be a non-empty string`);
108
- // Degradation invariant: every Flow Agents Kit must remain a valid core Flow Kit container
109
- // when agent-extension fields are stripped. Strip extensions and re-validate core contract.
110
- const coreManifest = {};
111
- for (const key of Object.keys(manifest)) {
112
- if (CORE_CONTAINER_FIELDS.has(key))
113
- coreManifest[key] = manifest[key];
114
- }
115
- const coreErrors = validateCoreContainer(coreManifest, manifestPath);
116
- for (const err of coreErrors) {
117
- // Deduplicate: only add if not already covered by top-level checks above.
118
- if (!errors.some((existing) => existing === err))
119
- errors.push(err);
120
- }
121
- for (const section of ASSET_CLASSES) {
109
+ // Delegate core container validation (schema_version, id, name, flows including file
110
+ // existence) to @kontourai/flow — the container contract lives once, in Flow.
111
+ // This enforces the degradation invariant: a Flow Agents Kit must remain a valid
112
+ // core Flow Kit container when extension fields are stripped.
113
+ const coreErrors = await delegateCoreContainerValidation(kitDir, manifest);
114
+ for (const err of coreErrors)
115
+ errors.push(err);
116
+ // Flow Agents extension validation: skills, docs, adapters, evals, assets.
117
+ // Flows are validated above by @kontourai/flow; only extension classes are checked here.
118
+ for (const section of EXTENSION_ASSET_CLASSES) {
122
119
  const entries = manifest[section];
123
120
  if (entries === undefined)
124
121
  continue;
@@ -155,15 +152,14 @@ export function validateKitRepository(kitDir) {
155
152
  return;
156
153
  }
157
154
  if (!fs.existsSync(resolved)) {
158
- const noun = section === "flows" ? "Flow Definition" : "asset";
159
- errors.push(`${manifestPath}: ${section}[${index}].path points at missing ${noun}: ${rel}`);
155
+ errors.push(`${manifestPath}: ${section}[${index}].path points at missing asset: ${rel}`);
160
156
  }
161
157
  });
162
158
  }
163
159
  return errors;
164
160
  }
165
- export function assertKitRepository(kitDir) {
166
- const errors = validateKitRepository(kitDir);
161
+ export async function assertKitRepository(kitDir) {
162
+ const errors = await validateKitRepository(kitDir);
167
163
  if (errors.length) {
168
164
  const error = new Error("Flow Kit repository validation failed");
169
165
  error.diagnostics = errors;