@kontourai/flow-agents 1.1.0 → 1.3.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/ci.yml +6 -1
- package/.github/workflows/kit-gates-demo.yml +6 -2
- package/.github/workflows/runtime-compat.yml +5 -2
- package/CHANGELOG.md +51 -0
- package/CONTRIBUTING.md +30 -0
- package/README.md +26 -5
- package/agents/dev.json +1 -1
- package/agents/tool-planner.json +1 -1
- 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/workflow-sidecar.js +70 -5
- package/build/src/cli.js +3 -3
- package/build/src/flow-kit/validate.js +89 -62
- package/build/src/tools/build-universal-bundles.js +78 -17
- package/build/src/tools/generate-context-map.js +49 -7
- package/build/src/tools/validate-source-tree.js +32 -1
- package/console.telemetry.json +1 -1
- package/docs/adr/0004-gates-expect-surface-claims.md +7 -7
- 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 +125 -13
- package/docs/knowledge-kit.md +2 -2
- package/docs/operating-layers.md +2 -2
- package/docs/spec/runtime-hook-surface.md +1 -1
- package/docs/veritas-integration.md +4 -4
- package/docs/vision.md +1 -1
- package/docs/workflow-eval-strategy.md +2 -2
- package/docs/workflow-usage-guide.md +2 -2
- package/evals/acceptance/test_opencode_harness.sh +18 -10
- package/evals/acceptance/test_pi_harness.sh +10 -6
- package/evals/ci/run-baseline.sh +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/flow-kit-repository/mixed-runtime-kit/flows/runtime.flow.json +4 -4
- package/evals/fixtures/flow-kit-repository/valid-local-kit/flows/review.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k0-flows-only/flows/review.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k1-agent-extension/flows/build.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/k2-with-evals/flows/synthesize.flow.json +4 -4
- package/evals/fixtures/kit-conformance-levels/third-party-extension/flows/review.flow.json +4 -4
- package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
- package/evals/fixtures/surface-trust/accepted-claim-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/artifact-absent.json +2 -2
- package/evals/fixtures/surface-trust/integrity-mismatch-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/missing-authority-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/provider-absent.json +2 -2
- package/evals/fixtures/surface-trust/rejected-claim-trust-report.json +2 -2
- package/evals/fixtures/surface-trust/stale-claim-trust-snapshot.json +2 -2
- 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 +2 -2
- 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_goal_fit_hook.sh +144 -0
- package/evals/integration/test_kit_conformance_levels.sh +56 -2
- 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/integration/test_workflow_sidecar_writer.sh +9 -9
- package/evals/lib/node.sh +2 -2
- package/evals/static/test_package.sh +3 -3
- package/evals/static/test_workflow_skills.sh +19 -19
- package/integrations/strands/flow_agents_strands/steering.py +1 -1
- package/integrations/strands-ts/src/hooks.ts +1 -1
- package/kits/builder/flows/build.flow.json +48 -48
- package/kits/builder/flows/shape.flow.json +36 -36
- 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/adapters/obsidian-store/index.js +137 -26
- package/kits/knowledge/evals/contract-suite/suite.test.js +90 -0
- package/kits/knowledge/flows/compile.flow.json +12 -12
- package/kits/knowledge/flows/consolidate.flow.json +16 -16
- package/kits/knowledge/flows/ingest.flow.json +12 -12
- package/kits/knowledge/flows/retire.flow.json +16 -16
- package/kits/knowledge/flows/store-contract.flow.json +12 -12
- package/kits/knowledge/flows/synthesize.flow.json +16 -16
- package/kits/knowledge/kit.json +16 -9
- package/kits/release-evidence/flows/release-evidence.flow.json +3 -3
- package/package.json +11 -5
- package/packaging/packs.json +1 -21
- package/schemas/workflow-evidence.schema.json +2 -1
- package/scripts/README.md +1 -1
- package/scripts/hooks/stop-goal-fit.js +66 -18
- 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/workflow-sidecar.ts +62 -4
- package/src/cli.ts +3 -3
- package/src/flow-kit/validate.ts +118 -58
- package/src/tools/build-universal-bundles.ts +74 -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
|
@@ -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; }); }
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
5
6
|
|
|
6
7
|
type AnyObj = Record<string, any>;
|
|
7
8
|
|
|
@@ -24,6 +25,46 @@ function appendJsonl(file: string, payload: AnyObj): void {
|
|
|
24
25
|
function die(message: string): never { throw new Error(message); }
|
|
25
26
|
function slugify(value: string, fallback: string): string { return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || fallback; }
|
|
26
27
|
|
|
28
|
+
// Optional Hachure trust-bundle validation. No-ops gracefully when hachure is not installed.
|
|
29
|
+
// Install hachure (^0.4.0) as an optional dependency to enable schema validation.
|
|
30
|
+
function tryLoadHachureValidator(): ((bundle: unknown) => { valid: boolean; errors: string[] }) | null {
|
|
31
|
+
try {
|
|
32
|
+
const _require = createRequire(import.meta.url);
|
|
33
|
+
const hachureDir = path.dirname(_require.resolve("hachure"));
|
|
34
|
+
const schemasDir = path.join(hachureDir, "schemas");
|
|
35
|
+
const Ajv = _require("ajv/dist/2020");
|
|
36
|
+
const schemas: Record<string, any> = {};
|
|
37
|
+
for (const file of fs.readdirSync(schemasDir)) {
|
|
38
|
+
if (!file.endsWith(".schema.json")) continue;
|
|
39
|
+
schemas[file] = JSON.parse(fs.readFileSync(path.join(schemasDir, file), "utf8"));
|
|
40
|
+
}
|
|
41
|
+
const ajv = new Ajv({ strict: false, allErrors: true });
|
|
42
|
+
for (const [filename, schema] of Object.entries(schemas)) {
|
|
43
|
+
if (filename === "trust-bundle.schema.json") continue;
|
|
44
|
+
ajv.addSchema(schema, filename);
|
|
45
|
+
}
|
|
46
|
+
const trustBundleSchema = schemas["trust-bundle.schema.json"];
|
|
47
|
+
if (!trustBundleSchema) return null;
|
|
48
|
+
const validate = ajv.compile(trustBundleSchema);
|
|
49
|
+
return (bundle: unknown) => {
|
|
50
|
+
const valid = validate(bundle);
|
|
51
|
+
if (valid) return { valid: true, errors: [] };
|
|
52
|
+
const errors = ((validate as any).errors ?? []).map((err: any) => {
|
|
53
|
+
const loc = err.instancePath || err.schemaPath || "";
|
|
54
|
+
return `${loc} ${err.message ?? "invalid"}`.trim();
|
|
55
|
+
});
|
|
56
|
+
return { valid: false, errors };
|
|
57
|
+
};
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
let _hachureValidator: ReturnType<typeof tryLoadHachureValidator> | undefined;
|
|
63
|
+
function getHachureValidator(): ReturnType<typeof tryLoadHachureValidator> {
|
|
64
|
+
if (_hachureValidator === undefined) _hachureValidator = tryLoadHachureValidator();
|
|
65
|
+
return _hachureValidator;
|
|
66
|
+
}
|
|
67
|
+
|
|
27
68
|
function safeRepoIdentifier(value: string): string {
|
|
28
69
|
const trimmed = value.trim().replace(/\.git$/, "");
|
|
29
70
|
if (!trimmed || trimmed.length > 120) return "";
|
|
@@ -372,11 +413,27 @@ function normalizeCheck(raw: AnyObj): AnyObj {
|
|
|
372
413
|
}
|
|
373
414
|
function normalizeSurfaceRefs(refs: any): AnyObj[] {
|
|
374
415
|
if (!Array.isArray(refs)) die("surface_trust_refs must be an array");
|
|
416
|
+
const hachureValidate = getHachureValidator();
|
|
375
417
|
return refs.map((ref) => {
|
|
376
418
|
const keys = JSON.stringify(ref).match(/"([^"]+)":/g) ?? [];
|
|
377
419
|
for (const key of keys.map((k) => k.slice(1, -2))) if (key.toLowerCase().includes("veritas")) die(`unsupported field in Surface trust ref: ${key}`);
|
|
378
420
|
const out = { ...ref };
|
|
379
|
-
|
|
421
|
+
// trust.bundle is the canonical Hachure-aligned artifact kind; TrustReport/Trust Snapshot are legacy aliases
|
|
422
|
+
if (!["trust.bundle", "TrustReport", "Trust Snapshot"].includes(out.artifact_kind)) die("artifact_kind must be one of: trust.bundle, TrustReport, Trust Snapshot");
|
|
423
|
+
// When hachure is installed, validate the referenced trust artifact if it is a local file
|
|
424
|
+
if (hachureValidate && out.artifact_ref && typeof out.artifact_ref === "string" && fs.existsSync(out.artifact_ref)) {
|
|
425
|
+
try {
|
|
426
|
+
const bundle = JSON.parse(fs.readFileSync(out.artifact_ref, "utf8"));
|
|
427
|
+
const result = hachureValidate(bundle);
|
|
428
|
+
if (!result.valid) {
|
|
429
|
+
const errorSummary = result.errors.slice(0, 3).join("; ");
|
|
430
|
+
die(`trust.bundle artifact at ${out.artifact_ref} failed Hachure schema validation: ${errorSummary}`);
|
|
431
|
+
}
|
|
432
|
+
} catch (err) {
|
|
433
|
+
if (err instanceof Error && err.message.includes("failed Hachure schema validation")) throw err;
|
|
434
|
+
// File read or parse errors are not re-thrown: the artifact_ref validation path is advisory
|
|
435
|
+
}
|
|
436
|
+
}
|
|
380
437
|
const status = deriveSurfaceStatus(out);
|
|
381
438
|
if (out.status === "pass" && status !== "pass") die("surface_trust_refs contradicts Surface trust facts");
|
|
382
439
|
return out;
|
|
@@ -394,15 +451,16 @@ function surfaceCheckFromArtifact(file: string, index: number): AnyObj {
|
|
|
394
451
|
const lower = JSON.stringify(raw).toLowerCase();
|
|
395
452
|
let ref: AnyObj;
|
|
396
453
|
if (lower.includes("provider") && lower.includes("absent")) {
|
|
397
|
-
ref = { artifact_kind: "
|
|
454
|
+
ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: "provider.unavailable", claim_type: "builder.trust.bundle", claim_status: "unknown", subject: "builder-kit", freshness: { status: "unknown", summary: "No trust provider is configured" }, authority: { producer: "unknown", summary: "No trust provider is configured" }, integrity: { status: "unknown", summary: "Unknown" }, status: "not_verified", summary: "No trust provider is configured" };
|
|
398
455
|
} else if (lower.includes("artifact") && lower.includes("absent")) {
|
|
399
|
-
ref = { artifact_kind: "
|
|
456
|
+
ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: "artifact.unavailable", claim_type: "builder.trust.bundle", claim_status: "unknown", subject: "builder-kit", freshness: { status: "unknown", summary: "Artifact not readable" }, authority: { producer: "unknown", summary: "Artifact not readable" }, integrity: { status: "unknown", summary: "Artifact not readable" }, status: "not_verified", summary: "artifact not readable" };
|
|
400
457
|
} else {
|
|
401
458
|
const claimStatus = lower.includes("rejected") ? "rejected" : "accepted";
|
|
402
459
|
const freshness = lower.includes("stale") ? "stale" : "fresh";
|
|
403
460
|
const producer = lower.includes("missing-authority") ? "unknown" : "surface-local";
|
|
404
461
|
const integrity = lower.includes("mismatch") ? "mismatch" : "matched";
|
|
405
|
-
|
|
462
|
+
// Use trust.bundle as the canonical Hachure-aligned artifact_kind for all trust-backed evidence refs
|
|
463
|
+
ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: "builder.trust.bundle", claim_type: "builder.trust.bundle", claim_status: claimStatus, subject: "builder-kit", freshness: { status: freshness, summary: freshness === "fresh" ? "fresh" : "not currently verifiable" }, authority: { producer, summary: producer === "unknown" ? "missing authority" : "Local Surface trust producer." }, integrity: { status: integrity, summary: integrity === "matched" ? "matched" : "integrity mismatch" } };
|
|
406
464
|
ref.status = deriveSurfaceStatus(ref);
|
|
407
465
|
ref.summary = ref.status === "pass" ? "accepted" : ref.status === "not_verified" ? "not currently verifiable" : (claimStatus === "rejected" ? "rejected" : producer === "unknown" ? "missing authority" : "integrity mismatch");
|
|
408
466
|
}
|
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"],
|