@kontourai/flow-agents 0.4.0 → 1.0.1

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 (58) hide show
  1. package/.github/workflows/kit-gates-demo.yml +171 -0
  2. package/CHANGELOG.md +43 -0
  3. package/CONTEXT.md +1 -1
  4. package/README.md +13 -2
  5. package/build/src/cli/flow-kit.js +175 -6
  6. package/build/src/cli/validate-source-tree.js +19 -2
  7. package/build/src/flow-kit/validate.js +98 -0
  8. package/build/src/runtime-adapters.js +1 -1
  9. package/build/src/tools/validate-source-tree.js +3 -2
  10. package/context/scripts/hooks/config-protection.js +217 -15
  11. package/docs/fixture-ownership.md +2 -1
  12. package/docs/index.md +9 -1
  13. package/docs/kit-authoring-guide.md +126 -0
  14. package/docs/knowledge-kit.md +69 -0
  15. package/docs/vision.md +22 -0
  16. package/evals/fixtures/kit-conformance-levels/k0-flows-only/flows/review.flow.json +26 -0
  17. package/evals/fixtures/kit-conformance-levels/k0-flows-only/kit.json +13 -0
  18. package/evals/fixtures/kit-conformance-levels/k1-agent-extension/docs/README.md +3 -0
  19. package/evals/fixtures/kit-conformance-levels/k1-agent-extension/flows/build.flow.json +26 -0
  20. package/evals/fixtures/kit-conformance-levels/k1-agent-extension/kit.json +20 -0
  21. package/evals/fixtures/kit-conformance-levels/k2-with-evals/docs/README.md +3 -0
  22. package/evals/fixtures/kit-conformance-levels/k2-with-evals/eval-suites/contract-suite/suite.test.js +1 -0
  23. package/evals/fixtures/kit-conformance-levels/k2-with-evals/flows/synthesize.flow.json +26 -0
  24. package/evals/fixtures/kit-conformance-levels/k2-with-evals/kit.json +27 -0
  25. package/evals/fixtures/kit-conformance-levels/third-party-extension/flows/review.flow.json +26 -0
  26. package/evals/fixtures/kit-conformance-levels/third-party-extension/kit.json +19 -0
  27. package/evals/integration/test_activate_npx_context.sh +134 -0
  28. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  29. package/evals/integration/test_flow_kit_install_git.sh +163 -0
  30. package/evals/integration/test_hook_category_behaviors.sh +51 -0
  31. package/evals/integration/test_kit_conformance_levels.sh +209 -0
  32. package/evals/run.sh +2 -0
  33. package/kits/catalog.json +6 -0
  34. package/kits/knowledge/adapters/default-store/index.js +2 -2
  35. package/kits/knowledge/adapters/flow-runner/entity-extractor.js +194 -0
  36. package/kits/knowledge/adapters/flow-runner/index.js +349 -0
  37. package/kits/knowledge/adapters/obsidian-store/README.md +141 -0
  38. package/kits/knowledge/adapters/obsidian-store/demo.js +181 -0
  39. package/kits/knowledge/adapters/obsidian-store/index.js +868 -0
  40. package/kits/knowledge/adapters/shared/codec.js +325 -0
  41. package/kits/knowledge/docs/store-contract.md +72 -0
  42. package/kits/knowledge/evals/entities/demo-acme.js +125 -0
  43. package/kits/knowledge/evals/entities/suite.test.js +722 -0
  44. package/kits/knowledge/kit.json +10 -0
  45. package/kits/release-evidence/fixtures/claims/README.md +14 -0
  46. package/kits/release-evidence/fixtures/claims/fail-rejected-release.trust.json +22 -0
  47. package/kits/release-evidence/fixtures/claims/pass-trusted-release.trust.json +22 -0
  48. package/kits/release-evidence/flows/release-evidence.flow.json +38 -0
  49. package/kits/release-evidence/kit.json +13 -0
  50. package/package.json +1 -1
  51. package/packaging/conformance/fixtures/config-protection--allow-no-verify-in-string.json +20 -0
  52. package/packaging/conformance/fixtures/config-protection--block-git-no-verify.json +23 -0
  53. package/scripts/hooks/config-protection.js +217 -15
  54. package/src/cli/flow-kit.ts +162 -5
  55. package/src/cli/validate-source-tree.ts +7 -1
  56. package/src/flow-kit/validate.ts +127 -0
  57. package/src/runtime-adapters.ts +1 -1
  58. package/src/tools/validate-source-tree.ts +3 -2
@@ -0,0 +1,171 @@
1
+ name: Kit Gates Demo (Agentless)
2
+
3
+ # Proves that Flow Kit gates can be evaluated over claim files in CI with no
4
+ # agent in the loop. The workflow runs:
5
+ # flow validate-definition — validates the Flow Definition in the kit
6
+ # flow validate-kit — validates kit.json container (requires
7
+ # @kontourai/flow >= 0.1.21; merged in source
8
+ # PR #67 but not yet in the published package
9
+ # as of 0.1.20 — step is continue-on-error)
10
+ # flow start — creates a Flow run
11
+ # flow attach-evidence --trust-artifact — attaches a claim file to the gate
12
+ # flow evaluate --exit-code — evaluates gate; exits 1 when gate does not pass
13
+ #
14
+ # Two jobs exercise one passing and one failing claim fixture:
15
+ # pass-case: trusted claim → evaluate exits 0 → job succeeds
16
+ # fail-case: rejected claim → evaluate exits 1 → workflow asserts the
17
+ # failure is detected (non-zero exit) rather than silently ignored
18
+ #
19
+ # Triggered on: workflow_dispatch or PR touching kits/release-evidence/** or
20
+ # this workflow file.
21
+
22
+ on:
23
+ workflow_dispatch:
24
+ pull_request:
25
+ paths:
26
+ - "kits/release-evidence/**"
27
+ - ".github/workflows/kit-gates-demo.yml"
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ pass-case:
34
+ name: Gate pass — trusted release.evidence claim
35
+ runs-on: ubuntu-latest
36
+ timeout-minutes: 10
37
+
38
+ steps:
39
+ - name: Checkout
40
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
41
+
42
+ - name: Set up Node.js
43
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
44
+ with:
45
+ node-version: "22"
46
+
47
+ - name: Install Flow CLI
48
+ # Mirrors the FLOW_CLI_ROOT install pattern used in ci.yml.
49
+ run: |
50
+ mkdir -p .flow-cli
51
+ cd .flow-cli
52
+ printf '{"name":"flow-cli-host","private":true}\n' > package.json
53
+ npm install --no-save @kontourai/flow
54
+ env:
55
+ FLOW_CLI_ROOT: ${{ github.workspace }}/.flow-cli/node_modules/@kontourai/flow
56
+
57
+ - name: Validate Flow Definition (available in published CLI)
58
+ run: |
59
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
60
+ validate-definition \
61
+ kits/release-evidence/flows/release-evidence.flow.json \
62
+ --json
63
+
64
+ - name: Validate kit container (requires @kontourai/flow >= next release after 0.1.20)
65
+ # validate-kit is merged in kontourai/flow source (PR #67) but not yet
66
+ # validate-kit ships in @kontourai/flow >= 0.2.0 (published 2026-06-12).
67
+ run: |
68
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
69
+ validate-kit kits/release-evidence
70
+
71
+ - name: Init Flow workspace and start run
72
+ run: |
73
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js init
74
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
75
+ start kits/release-evidence/flows/release-evidence.flow.json \
76
+ --run-id gate-pass-demo
77
+
78
+ - name: Attach trusted release.evidence claim
79
+ run: |
80
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
81
+ attach-evidence gate-pass-demo \
82
+ --gate release-evidence-gate \
83
+ --file kits/release-evidence/fixtures/claims/pass-trusted-release.trust.json \
84
+ --trust-artifact
85
+
86
+ - name: Evaluate gate — trusted claim must produce pass and exit 0
87
+ run: |
88
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
89
+ evaluate gate-pass-demo --exit-code
90
+
91
+ - name: Show run status
92
+ if: always()
93
+ run: |
94
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
95
+ status gate-pass-demo --format summary
96
+
97
+ fail-case:
98
+ name: Gate fail — rejected claim (failure MUST be detected)
99
+ runs-on: ubuntu-latest
100
+ timeout-minutes: 10
101
+
102
+ steps:
103
+ - name: Checkout
104
+ uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
105
+
106
+ - name: Set up Node.js
107
+ uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
108
+ with:
109
+ node-version: "22"
110
+
111
+ - name: Install Flow CLI
112
+ run: |
113
+ mkdir -p .flow-cli
114
+ cd .flow-cli
115
+ printf '{"name":"flow-cli-host","private":true}\n' > package.json
116
+ npm install --no-save @kontourai/flow
117
+ env:
118
+ FLOW_CLI_ROOT: ${{ github.workspace }}/.flow-cli/node_modules/@kontourai/flow
119
+
120
+ - name: Validate Flow Definition
121
+ run: |
122
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
123
+ validate-definition \
124
+ kits/release-evidence/flows/release-evidence.flow.json \
125
+ --json
126
+
127
+ - name: Validate kit container (requires @kontourai/flow >= next release after 0.1.20)
128
+ continue-on-error: true
129
+ run: |
130
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
131
+ validate-kit kits/release-evidence
132
+
133
+ - name: Init Flow workspace and start run
134
+ run: |
135
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js init
136
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
137
+ start kits/release-evidence/flows/release-evidence.flow.json \
138
+ --run-id gate-fail-demo
139
+
140
+ - name: Attach rejected release.evidence claim
141
+ run: |
142
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
143
+ attach-evidence gate-fail-demo \
144
+ --gate release-evidence-gate \
145
+ --file kits/release-evidence/fixtures/claims/fail-rejected-release.trust.json \
146
+ --trust-artifact
147
+
148
+ - name: Evaluate gate — rejected claim must produce non-pass and exit 1
149
+ # --exit-code makes the CLI exit 1 when the gate does not pass.
150
+ # continue-on-error: true captures the outcome without failing the job here.
151
+ id: evaluate_fail
152
+ continue-on-error: true
153
+ run: |
154
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
155
+ evaluate gate-fail-demo --exit-code
156
+
157
+ - name: Assert failure was detected (gate must NOT pass a rejected claim)
158
+ # If evaluate exited 0 (outcome == success), the gate incorrectly
159
+ # accepted a rejected claim — false negative in gate evaluation.
160
+ run: |
161
+ if [ "${{ steps.evaluate_fail.outcome }}" = "success" ]; then
162
+ echo "ERROR: gate passed a rejected claim — false negative in gate evaluation"
163
+ exit 1
164
+ fi
165
+ echo "PASS: gate correctly did not pass the rejected claim (outcome=${{ steps.evaluate_fail.outcome }})"
166
+
167
+ - name: Show run status
168
+ if: always()
169
+ run: |
170
+ node .flow-cli/node_modules/@kontourai/flow/dist/cli.js \
171
+ status gate-fail-demo --format summary
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.1](https://github.com/kontourai/flow-agents/compare/v1.0.0...v1.0.1) (2026-06-12)
4
+
5
+
6
+ ### Fixes
7
+
8
+ * resolve three kit-distribution blockers ([#55](https://github.com/kontourai/flow-agents/issues/55) [#56](https://github.com/kontourai/flow-agents/issues/56) [#57](https://github.com/kontourai/flow-agents/issues/57)) ([3350cb1](https://github.com/kontourai/flow-agents/commit/3350cb15f44bff92d8d9c57f447761d0e1a1b20c))
9
+ * resolve three kit-distribution blockers ([#55](https://github.com/kontourai/flow-agents/issues/55), [#56](https://github.com/kontourai/flow-agents/issues/56), [#57](https://github.com/kontourai/flow-agents/issues/57)) ([13bf732](https://github.com/kontourai/flow-agents/commit/13bf732ff365efa84423e9ea46042e501d202db8))
10
+
11
+ ## [1.0.0](https://github.com/kontourai/flow-agents/compare/v0.4.0...v1.0.0) (2026-06-12)
12
+
13
+
14
+ ### Features
15
+
16
+ * agentless Flow Kit gate evaluation proof (issue [#52](https://github.com/kontourai/flow-agents/issues/52) item 3) ([f7857ec](https://github.com/kontourai/flow-agents/commit/f7857ec8ba69d614b0c3c70548d724f7a97c164a))
17
+ * agentless Flow Kit gate evaluation proof (issue [#52](https://github.com/kontourai/flow-agents/issues/52) item 3) ([86c881f](https://github.com/kontourai/flow-agents/commit/86c881f579a08ab75787cd32e401f83b77952c39))
18
+ * config-protection blocks git hook-skip flags ([#41](https://github.com/kontourai/flow-agents/issues/41)) ([6d9e981](https://github.com/kontourai/flow-agents/commit/6d9e9810b3d4e60fe172ade340e61dbe4053d0c9))
19
+ * K-level conformance, degradation invariant, and consumer-target derivation ([#52](https://github.com/kontourai/flow-agents/issues/52) items 1+2) ([d5c332a](https://github.com/kontourai/flow-agents/commit/d5c332a3f4400eb29e9f8fd4e845ec34cf30ae0b))
20
+ * K-level conformance, degradation invariant, and consumer-target derivation (issue [#52](https://github.com/kontourai/flow-agents/issues/52) items 1+2) ([6ac62eb](https://github.com/kontourai/flow-agents/commit/6ac62eb4cec195baca3d039b398f29e45e5d62de))
21
+ * **knowledge-kit:** obsidian layout — insights at node root, sources nested; dimension frontmatter ([5d6489b](https://github.com/kontourai/flow-agents/commit/5d6489b6dc30eacc3a4d0c51487c1a7d3a004f00))
22
+ * **knowledge-kit:** Obsidian store adapter — file-is-the-record spike ([#43](https://github.com/kontourai/flow-agents/issues/43)) ([83d9ff4](https://github.com/kontourai/flow-agents/commit/83d9ff43c2e1d59ac8d3235ae7250fc43be47725))
23
+ * **knowledge-kit:** Obsidian store adapter spike — file-is-the-record RATIFIED ([#43](https://github.com/kontourai/flow-agents/issues/43)) ([467c8dc](https://github.com/kontourai/flow-agents/commit/467c8dc60180a5f6bf15b30a4f0e29e486803fb8))
24
+ * **knowledge-kit:** person entity cards with backlinks + gated resolution ([#48](https://github.com/kontourai/flow-agents/issues/48)) ([9456cef](https://github.com/kontourai/flow-agents/commit/9456cef3b55c639bd50a9aeaea675bef425ea0be))
25
+ * **knowledge-kit:** person entity cards with backlinks + gated resolution ([#48](https://github.com/kontourai/flow-agents/issues/48)) ([ac5ccb0](https://github.com/kontourai/flow-agents/commit/ac5ccb08e40bb6adadd35ff165a617be29e8d23a))
26
+
27
+
28
+ ### Fixes
29
+
30
+ * **entity-extractor:** parse trailing-period last-entry correctly ([76abd87](https://github.com/kontourai/flow-agents/commit/76abd87ce2e6bfa3650c3d20de7309d498a446f8))
31
+ * **entity-extractor:** parse trailing-period last-entry correctly ([#48](https://github.com/kontourai/flow-agents/issues/48)) ([ac64fc1](https://github.com/kontourai/flow-agents/commit/ac64fc1cb3151af9032948ac463337da3eeaf907))
32
+
33
+
34
+ ### Documentation
35
+
36
+ * elevate Flow Kits as the authorable-ecosystem pillar ([#45](https://github.com/kontourai/flow-agents/issues/45)) ([fa81820](https://github.com/kontourai/flow-agents/commit/fa8182089c4e3c404e3020c6d516be93353a897b))
37
+ * elevate Flow Kits as the authorable-ecosystem pillar ([#45](https://github.com/kontourai/flow-agents/issues/45)) ([7ffa44e](https://github.com/kontourai/flow-agents/commit/7ffa44ea8be799e709b41d4ac4220948e3819fb8))
38
+ * kit container/extension layering — container contract is Flow-owned ([#50](https://github.com/kontourai/flow-agents/issues/50)) ([33d6ec0](https://github.com/kontourai/flow-agents/commit/33d6ec0bf0fefd9095c19dc76b995ee7dd8079fb))
39
+ * kit container/extension layering — container contract is Flow-owned ([#50](https://github.com/kontourai/flow-agents/issues/50)) ([fd87366](https://github.com/kontourai/flow-agents/commit/fd873663d64e205e1a8c2b898b913a56d410591f))
40
+
41
+
42
+ ### Maintenance
43
+
44
+ * cut v1.0.0 ([5f88ac5](https://github.com/kontourai/flow-agents/commit/5f88ac51598fe3f13b16360572ff851b822013a2))
45
+
3
46
  ## [0.4.0](https://github.com/kontourai/flow-agents/compare/v0.3.0...v0.4.0) (2026-06-12)
4
47
 
5
48
 
package/CONTEXT.md CHANGED
@@ -75,7 +75,7 @@ A reusable procedure Flow Agents can invoke to carry out part of a work mode. Sk
75
75
 
76
76
  ### Flow Kit
77
77
 
78
- An installable, authorable bundle of Flow-backed workflows and optional supporting assets, such as Flow Definitions, docs, skills, adapters, provider contracts, and evals. Flow Kits are runtime-neutral: skills are one supported asset type, not a requirement.
78
+ Flow's distribution unit for portable workflow bundles. A Flow Kit has two layers: (1) the **container** (owned by Kontour Flow) — a `kit.json` manifest with `schema_version`, `id`, `name`, and a non-empty `flows` list, plus optional `description` and `product_name`; and (2) the **agent extension** (owned by Flow Agents) optional `skills`, `docs`, `adapters`, `evals`, and `assets` fields that make it a Flow Agents Kit. The container contract permits unknown top-level fields so consumers can extend it without breaking core validation. A kit with only core container fields is a valid Flow Kit; adding Flow Agents extension fields makes it a Flow Agents Kit.
79
79
  _Avoid_: Pack, plugin, marketplace package
80
80
 
81
81
  ### Flow Kit Repository
package/README.md CHANGED
@@ -71,6 +71,8 @@ L2 means all four policy classes with blocking; L1 means steering and stop-goal-
71
71
 
72
72
  ## Install
73
73
 
74
+ Requires Node.js 22 or newer.
75
+
74
76
  ```bash
75
77
  # guided install into your workspace (auto-detects runtime)
76
78
  npx @kontourai/flow-agents init --dest /path/to/workspace
@@ -111,9 +113,15 @@ The [Workflow Usage Guide](docs/workflow-usage-guide.md) has example prompts and
111
113
 
112
114
  ## Flow Kits
113
115
 
114
- A Flow Kit is a portable workflow bundle: a `kit.json` manifest, one or more Flow Definitions, and optional skills, docs, adapters, evals, and assets — all validated and installed as a unit. Kits are the extension model for Flow Agents: they let you package a workflow once and deploy it into any workspace through the same path as the built-in workflows.
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.
117
+
118
+ **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
+
120
+ **Knowledge Kit** — a Flow Kit for durable, gated knowledge storage. It ships a store contract with four record types (`raw`, `compiled`, `concept`, `snapshot`), five pipeline flows (`ingest`, `compile`, `synthesize`, `consolidate`, `retire`), and a mutation policy of propose→evidence-gate→apply/reject with supersede-not-delete. All mutations require provenance; nothing is silently overwritten or deleted. Ships with 198 tests.
115
121
 
116
- **Builder Kit** is the first Kontour-authored kit. It 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). Builder Kit is installed automatically by `npx @kontourai/flow-agents init`.
122
+ The output-shape story is the core reason kits matter. The Knowledge Kit store contract is representation-neutral: two adapters ship today. The **default adapter** stores records as flat markdown files with YAML frontmatter and a JSON graph index. The **Obsidian adapter** renders the same workflow into the shape a human already thinks in one canonical note per record, category→folder hierarchy, configurable frontmatter dimensions (e.g. territory/customer/initiative as filterable fields), living overview notes with sources nested below, and superseded records moved to an `archive/` folder rather than deleted. Same flows, same mutation gates, different rendering layer. (The Obsidian adapter is shipped; layout/dimensions refinements and person/entity card support are in development.)
123
+
124
+ The Knowledge Kit is also LIVE-proven: the default adapter passes the parameterized contract suite; keyless operation is validated via a Strands agent + local ollama acceptance harness; vector similarity clustering uses ollama embeddings (`nomic-embed-text`) with a pluggable detector interface.
117
125
 
118
126
  Install a local kit:
119
127
 
@@ -123,6 +131,9 @@ npx @kontourai/flow-agents flow-kit install-local path/to/my-kit --dest /path/to
123
131
 
124
132
  - [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.
125
133
  - [Flow Kit Repository Contract](docs/flow-kit-repository-contract.md) — the full validation rules, registry schema, and activation diagnostics.
134
+ - [Knowledge Kit docs](kits/knowledge/docs/README.md) — store contract, record types, mutation ops, similarity detectors, and the Obsidian adapter.
135
+
136
+ **Direction** (not shipped): domain kits that compose this substrate — a Sales Kit (territory/customer/initiative schema with side-effect adapters for CRM logging), a Research Kit (transcript capture→compile→recall). Distribution follows sequencing: authoring tooling and covetable reference kits first, then a registry, then a marketplace. No marketplace claims are shipped.
126
137
 
127
138
  ## Framework adapters
128
139
 
@@ -1,9 +1,12 @@
1
+ import * as child_process from "node:child_process";
1
2
  import * as crypto from "node:crypto";
2
3
  import * as fs from "node:fs";
4
+ import * as os from "node:os";
3
5
  import * as path from "node:path";
6
+ import { fileURLToPath } from "node:url";
4
7
  import { parseArgs, flagBool, flagString } from "../lib/args.js";
5
8
  import { assertPathContained, copyDir, isoNow, readJson, walkFiles, writeJson } from "../lib/fs.js";
6
- import { assertKitRepository } from "../flow-kit/validate.js";
9
+ import { assertKitRepository, deriveKitTargets } from "../flow-kit/validate.js";
7
10
  import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
8
11
  const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
9
12
  const REPOSITORIES_REL = path.join("kits", "local", "repositories");
@@ -27,6 +30,22 @@ function contentHash(root) {
27
30
  }
28
31
  return `sha256:${hash.digest("hex")}`;
29
32
  }
33
+ /** Content hash that excludes .git and other VCS/cache directories (for install-git clones). */
34
+ function kitContentHash(root) {
35
+ const EXCLUDE_DIRS = new Set([".git", "__pycache__", ".pytest_cache"]);
36
+ const hash = crypto.createHash("sha256");
37
+ for (const file of walkFiles(root)) {
38
+ const parts = path.relative(root, file).split(path.sep);
39
+ if (parts.some((p) => EXCLUDE_DIRS.has(p)))
40
+ continue;
41
+ const rel = parts.join("/");
42
+ hash.update(rel);
43
+ hash.update("\0");
44
+ hash.update(fs.readFileSync(file));
45
+ hash.update("\0");
46
+ }
47
+ return `sha256:${hash.digest("hex")}`;
48
+ }
30
49
  function installLocal(argv) {
31
50
  const args = parseArgs(argv);
32
51
  const source = path.resolve(args.positionals[0] ?? "");
@@ -36,13 +55,11 @@ function installLocal(argv) {
36
55
  manifest = assertKitRepository(source);
37
56
  }
38
57
  catch (error) {
39
- console.log("warning: Flow validation surface unavailable; local kit check uses the minimal Flow Definition fallback");
40
58
  console.log("Flow Kit repository validation failed:");
41
59
  for (const diagnostic of (error.diagnostics ?? [error.message]))
42
60
  console.log(` - ${diagnostic}`);
43
61
  return 1;
44
62
  }
45
- console.log("warning: Flow validation surface unavailable; local kit check uses the minimal Flow Definition fallback");
46
63
  const kitId = String(manifest.id);
47
64
  const hash = contentHash(source);
48
65
  const registry = loadRegistry(dest);
@@ -126,18 +143,170 @@ function activate(argv) {
126
143
  console.log(JSON.stringify(result, null, 2));
127
144
  return Array.isArray(result.errors) && result.errors.length ? 1 : 0;
128
145
  }
146
+ /**
147
+ * inspect <kit-dir> [--json]
148
+ *
149
+ * 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.
151
+ * Outputs stable JSON suitable for use by catalog tooling and CI.
152
+ *
153
+ * K-levels (issue #52):
154
+ * K0 valid core Flow Kit container — gates evaluable agentlessly by any Flow consumer.
155
+ * K1 K0 + Flow Agents extension assets present (skills/docs/adapters/evals/assets).
156
+ * K2 K1 + evals present (live evidence layer).
157
+ *
158
+ * Consumer targets derived from observable asset classes:
159
+ * flow always present at K0 (any Flow consumer: gates/definition-of-done)
160
+ * flow-agents present at K1+ (Flow Agents extension activated)
161
+ * <namespace> unknown top-level keys list verbatim as third-party consumer targets
162
+ */
163
+ function inspect(argv) {
164
+ const args = parseArgs(argv);
165
+ const kitDir = path.resolve(args.positionals[0] ?? ".");
166
+ const manifestPath = path.join(kitDir, "kit.json");
167
+ if (!fs.existsSync(manifestPath)) {
168
+ console.error(`inspect: kit.json not found at ${manifestPath}`);
169
+ return 1;
170
+ }
171
+ let manifest;
172
+ try {
173
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
174
+ }
175
+ catch (err) {
176
+ console.error(`inspect: invalid JSON in ${manifestPath}: ${err.message}`);
177
+ return 1;
178
+ }
179
+ const result = deriveKitTargets(manifest);
180
+ console.log(JSON.stringify(result, null, 2));
181
+ return result.conformance.k0 ? 0 : 1;
182
+ }
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
+ }
129
278
  export function main(argv = process.argv.slice(2)) {
130
279
  const [command, ...rest] = argv;
131
280
  if (command === "install-local")
132
281
  return installLocal(rest);
282
+ if (command === "install-git")
283
+ return installGit(rest);
133
284
  if (command === "list")
134
285
  return list(rest);
135
286
  if (command === "status")
136
287
  return status(rest);
137
288
  if (command === "activate")
138
289
  return activate(rest);
139
- console.error("usage: flow-kit <install-local|list|status|activate> ...");
290
+ if (command === "inspect")
291
+ return inspect(rest);
292
+ console.error("usage: flow-kit <install-local|install-git|list|status|activate|inspect> ...");
140
293
  return 2;
141
294
  }
142
- if (import.meta.url === `file://${process.argv[1]}`)
143
- process.exit(main());
295
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
296
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
297
+ // entry-point guard fires correctly when the module is loaded directly as a script.
298
+ const _selfRealPath = (() => { try {
299
+ return fs.realpathSync(fileURLToPath(import.meta.url));
300
+ }
301
+ catch {
302
+ return fileURLToPath(import.meta.url);
303
+ } })();
304
+ const _argv1RealPath = (() => { try {
305
+ return fs.realpathSync(process.argv[1]);
306
+ }
307
+ catch {
308
+ return process.argv[1];
309
+ } })();
310
+ if (_selfRealPath === _argv1RealPath) {
311
+ process.exitCode = main();
312
+ }
@@ -27,5 +27,22 @@ export function main(argv = process.argv.slice(2)) {
27
27
  console.log("Source tree validation passed");
28
28
  return 0;
29
29
  }
30
- if (import.meta.url === `file://${process.argv[1]}`)
31
- process.exit(main());
30
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
31
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS).
32
+ import * as _fsVST from "node:fs";
33
+ import { fileURLToPath as _ftpVST } from "node:url";
34
+ const _selfVST = (() => { try {
35
+ return _fsVST.realpathSync(_ftpVST(import.meta.url));
36
+ }
37
+ catch {
38
+ return _ftpVST(import.meta.url);
39
+ } })();
40
+ const _argv1VST = (() => { try {
41
+ return _fsVST.realpathSync(process.argv[1]);
42
+ }
43
+ catch {
44
+ return process.argv[1];
45
+ } })();
46
+ if (_selfVST === _argv1VST) {
47
+ process.exitCode = main();
48
+ }
@@ -2,6 +2,91 @@ import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import { readJson } from "../lib/fs.js";
4
4
  const ASSET_CLASSES = ["flows", "skills", "docs", "adapters", "evals", "assets"];
5
+ // Core container fields owned by kontourai/flow (flow-kit-container.schema.json).
6
+ // agent-extension fields are skills, docs, adapters, evals, assets.
7
+ const CORE_CONTAINER_FIELDS = new Set(["schema_version", "id", "name", "description", "product_name", "flows"]);
8
+ const AGENT_EXTENSION_CLASSES = new Set(["skills", "docs", "adapters", "evals", "assets"]);
9
+ /**
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).
13
+ *
14
+ * The degradation invariant: every Flow Agents Kit MUST remain a valid core
15
+ * Flow Kit container when agent-extension fields are ignored.
16
+ */
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;
47
+ }
48
+ /**
49
+ * Derives the consumer-target level (K0/K1/K2) and target audience list from
50
+ * observable asset classes in the kit manifest. Does not require file I/O.
51
+ *
52
+ * Derivation rules (from kontourai/flow-agents#52 and Brian's layering review):
53
+ * - K0: valid core container (schema_version, id, name, flows non-empty).
54
+ * - K1: K0 + any Flow Agents extension field present (skills/docs/adapters/evals/assets).
55
+ * - K2: K1 + evals present.
56
+ * - targets.flow: always present when K0 (any Flow consumer can evaluate gates).
57
+ * - targets.flow-agents: present when K1 (agent extension assets activate in >=1 harness).
58
+ * - third-party: any top-level keys that are not core fields and not Flow Agents extension classes.
59
+ */
60
+ export function deriveKitTargets(manifest) {
61
+ const kitId = typeof manifest.id === "string" ? manifest.id : "<unknown>";
62
+ const kitName = typeof manifest.name === "string" ? manifest.name : "<unknown>";
63
+ const coreErrors = validateCoreContainer(manifest, "kit.json");
64
+ const k0 = coreErrors.length === 0;
65
+ const hasAgentExtension = AGENT_EXTENSION_CLASSES.size > 0 &&
66
+ [...AGENT_EXTENSION_CLASSES].some((cls) => Array.isArray(manifest[cls]) && manifest[cls].length > 0);
67
+ const hasEvals = Array.isArray(manifest["evals"]) && manifest["evals"].length > 0;
68
+ const k1 = k0 && hasAgentExtension;
69
+ const k2 = k1 && hasEvals;
70
+ // Detect third-party extension namespaces: top-level keys that are neither
71
+ // core fields nor Flow Agents extension classes.
72
+ const thirdPartyExtensions = Object.keys(manifest)
73
+ .filter((key) => !CORE_CONTAINER_FIELDS.has(key) && !AGENT_EXTENSION_CLASSES.has(key))
74
+ .sort();
75
+ const targets = [];
76
+ if (k0)
77
+ targets.push("flow");
78
+ if (k1)
79
+ targets.push("flow-agents");
80
+ for (const ns of thirdPartyExtensions)
81
+ targets.push(ns);
82
+ return {
83
+ kit_id: kitId,
84
+ kit_name: kitName,
85
+ conformance: { k0, k1, k2 },
86
+ targets,
87
+ third_party_extensions: thirdPartyExtensions,
88
+ };
89
+ }
5
90
  export function validateKitRepository(kitDir) {
6
91
  const errors = [];
7
92
  const manifestPath = path.join(kitDir, "kit.json");
@@ -20,6 +105,19 @@ export function validateKitRepository(kitDir) {
20
105
  }
21
106
  if (typeof manifest.name !== "string" || !manifest.name.trim())
22
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
+ }
23
121
  for (const section of ASSET_CLASSES) {
24
122
  const entries = manifest[section];
25
123
  if (entries === undefined)
@@ -74,7 +74,7 @@ export function readKitInventory(sourceRoot, dest) {
74
74
  const assets = [];
75
75
  const catalogPath = path.join(sourceRoot, "kits", "catalog.json");
76
76
  if (!fs.existsSync(catalogPath))
77
- errors.push(`${catalogPath}: missing Kit Catalog`);
77
+ warnings.push(`${catalogPath}: built-in Kit Catalog not found; skipping built-in kits (this is normal when running outside a flow-agents checkout)`);
78
78
  else {
79
79
  const catalog = readJson(catalogPath);
80
80
  const kits = catalog.kits;