@kontourai/flow-agents 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +110 -0
- package/.github/workflows/runtime-compat.yml +5 -2
- package/CHANGELOG.md +42 -0
- package/README.md +26 -5
- package/build/src/cli/console-learning-projection.js +19 -2
- package/build/src/cli/effective-backlog-settings.js +18 -2
- package/build/src/cli/fixture-retirement-audit.js +19 -2
- package/build/src/cli/init.js +19 -2
- package/build/src/cli/{flow-kit.js → kit.js} +122 -108
- package/build/src/cli/promote-workflow-artifact.js +19 -2
- package/build/src/cli/publish-change-helper.js +19 -2
- package/build/src/cli/pull-work-provider.js +19 -2
- package/build/src/cli/runtime-adapter.js +20 -2
- package/build/src/cli/usage-feedback.js +19 -2
- package/build/src/cli/utterance-check.js +19 -2
- package/build/src/cli/validate-hook-influence.js +19 -2
- package/build/src/cli/validate-source-tree.js +4 -4
- package/build/src/cli/veritas-governance.js +19 -2
- package/build/src/cli/workflow-artifact-cleanup-audit.js +19 -2
- package/build/src/cli.js +3 -3
- package/build/src/flow-kit/validate.js +58 -62
- package/build/src/runtime-adapters.js +55 -24
- package/build/src/tools/build-universal-bundles.js +83 -19
- package/build/src/tools/generate-context-map.js +68 -9
- package/build/src/tools/validate-package.js +19 -2
- package/build/src/tools/validate-source-tree.js +51 -3
- package/context/scripts/telemetry/console-presets.sh +1 -1
- package/docs/adr/0007-flow-skill-kit-tool-boundary.md +169 -0
- package/docs/adr/0007-skill-audit.md +112 -0
- package/docs/adr/0008-kit-operation-boundary.md +88 -0
- package/docs/context-map.md +18 -22
- package/docs/flow-kit-repository-contract.md +5 -5
- package/docs/getting-started.md +177 -0
- package/docs/index.md +19 -8
- package/docs/kit-authoring-guide.md +46 -10
- package/docs/knowledge-kit.md +2 -2
- package/docs/spec/runtime-hook-surface.md +1 -1
- package/docs/vision.md +1 -1
- package/docs/workflow-usage-guide.md +1 -1
- package/evals/ci/run-baseline.sh +55 -8
- package/evals/fixtures/builder-kit-workflow-state/happy-path.json +2 -2
- package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +2 -2
- package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +1 -1
- package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
- package/evals/integration/test_activate_npx_context.sh +2 -2
- package/evals/integration/test_bundle_install.sh +17 -12
- package/evals/integration/test_console_learning_projection.sh +1 -1
- package/evals/integration/test_flow_kit_install_git.sh +7 -7
- package/evals/integration/test_flow_kit_repository.sh +4 -4
- package/evals/integration/test_kit_conformance_levels.sh +1 -1
- package/evals/integration/test_local_flow_kit_install.sh +7 -7
- package/evals/integration/test_publish_change_helper.sh +1 -1
- package/evals/integration/test_pull_work_provider.sh +1 -1
- package/evals/integration/test_runtime_adapter_activation.sh +140 -19
- package/evals/lib/node.sh +2 -2
- package/evals/run.sh +2 -0
- package/evals/static/test_console_presets.sh +49 -0
- package/evals/static/test_workflow_skills.sh +15 -15
- package/integrations/strands/flow_agents_strands/steering.py +1 -1
- package/integrations/strands-ts/src/hooks.ts +1 -1
- package/kits/builder/kit.json +17 -0
- package/{skills → kits/builder/skills}/builder-shape/SKILL.md +4 -4
- package/{skills → kits/builder/skills}/idea-to-backlog/SKILL.md +1 -1
- package/kits/knowledge/kit.json +16 -9
- package/package.json +8 -5
- package/packaging/packs.json +1 -21
- package/scripts/README.md +1 -1
- package/scripts/kit.js +2 -0
- package/scripts/telemetry/console-presets.sh +1 -1
- package/skills/README.md +23 -0
- package/src/cli/console-learning-projection.ts +7 -1
- package/src/cli/effective-backlog-settings.ts +6 -1
- package/src/cli/fixture-retirement-audit.ts +7 -1
- package/src/cli/init.ts +7 -1
- package/src/cli/{flow-kit.ts → kit.ts} +124 -109
- package/src/cli/promote-workflow-artifact.ts +7 -1
- package/src/cli/publish-change-helper.ts +7 -1
- package/src/cli/pull-work-provider.ts +7 -1
- package/src/cli/runtime-adapter.ts +8 -1
- package/src/cli/usage-feedback.ts +7 -1
- package/src/cli/utterance-check.ts +7 -1
- package/src/cli/validate-hook-influence.ts +7 -1
- package/src/cli/validate-source-tree.ts +4 -4
- package/src/cli/veritas-governance.ts +7 -1
- package/src/cli/workflow-artifact-cleanup-audit.ts +7 -1
- package/src/cli.ts +3 -3
- package/src/flow-kit/validate.ts +63 -57
- package/src/runtime-adapters.ts +54 -26
- package/src/tools/build-universal-bundles.ts +67 -14
- package/src/tools/generate-context-map.ts +43 -7
- package/src/tools/validate-package.ts +7 -1
- package/src/tools/validate-source-tree.ts +34 -2
- 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
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { parseArgs, flagString } from "../lib/args.js";
|
|
4
5
|
import { isoNow, relPath } from "../lib/fs.js";
|
|
@@ -59,5 +60,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
59
60
|
console.log(`archived_artifact=${archive}`);
|
|
60
61
|
return 0;
|
|
61
62
|
}
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
64
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
65
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
66
|
+
const _selfRealPath = (() => { try {
|
|
67
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return fileURLToPath(import.meta.url);
|
|
71
|
+
} })();
|
|
72
|
+
const _argv1RealPath = (() => { try {
|
|
73
|
+
return fs.realpathSync(process.argv[1]);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return process.argv[1];
|
|
77
|
+
} })();
|
|
78
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
79
|
+
process.exitCode = main();
|
|
80
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { parseArgs, flagString } from "../lib/args.js";
|
|
4
5
|
import { readJson } from "../lib/fs.js";
|
|
@@ -150,5 +151,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
150
151
|
return 1;
|
|
151
152
|
}
|
|
152
153
|
}
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
155
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
156
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
157
|
+
const _selfRealPath = (() => { try {
|
|
158
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return fileURLToPath(import.meta.url);
|
|
162
|
+
} })();
|
|
163
|
+
const _argv1RealPath = (() => { try {
|
|
164
|
+
return fs.realpathSync(process.argv[1]);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
return process.argv[1];
|
|
168
|
+
} })();
|
|
169
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
170
|
+
process.exitCode = main();
|
|
171
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import { parseArgs, flagList, flagString } from "../lib/args.js";
|
|
3
4
|
const FLOW_ARTIFACT_PATTERN = /(?<path>\.flow-agents\/[^\s`'")]+)/g;
|
|
4
5
|
const GITHUB_ISSUE_PATTERN = /(?:(?<owner>[A-Za-z0-9_.-]+)\/(?<repo>[A-Za-z0-9_.-]+))?#(?<number>\d+)/g;
|
|
@@ -465,5 +466,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
465
466
|
console.log(JSON.stringify({ items }, null, 2));
|
|
466
467
|
return 0;
|
|
467
468
|
}
|
|
468
|
-
|
|
469
|
-
|
|
469
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
470
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
471
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
472
|
+
const _selfRealPath = (() => { try {
|
|
473
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
return fileURLToPath(import.meta.url);
|
|
477
|
+
} })();
|
|
478
|
+
const _argv1RealPath = (() => { try {
|
|
479
|
+
return fs.realpathSync(process.argv[1]);
|
|
480
|
+
}
|
|
481
|
+
catch {
|
|
482
|
+
return process.argv[1];
|
|
483
|
+
} })();
|
|
484
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
485
|
+
process.exitCode = main();
|
|
486
|
+
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
1
3
|
import * as path from "node:path";
|
|
2
4
|
import { parseArgs, flagString } from "../lib/args.js";
|
|
3
5
|
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
@@ -23,5 +25,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
23
25
|
console.log(JSON.stringify(result, null, 2));
|
|
24
26
|
return Array.isArray(result.errors) && result.errors.length ? 1 : 0;
|
|
25
27
|
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
29
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
30
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
31
|
+
const _selfRealPath = (() => { try {
|
|
32
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return fileURLToPath(import.meta.url);
|
|
36
|
+
} })();
|
|
37
|
+
const _argv1RealPath = (() => { try {
|
|
38
|
+
return fs.realpathSync(process.argv[1]);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return process.argv[1];
|
|
42
|
+
} })();
|
|
43
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
44
|
+
process.exitCode = main();
|
|
45
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as os from "node:os";
|
|
3
4
|
import * as path from "node:path";
|
|
4
5
|
import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
|
|
@@ -439,5 +440,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
439
440
|
return 1;
|
|
440
441
|
}
|
|
441
442
|
}
|
|
442
|
-
|
|
443
|
-
|
|
443
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
444
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
445
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
446
|
+
const _selfRealPath = (() => { try {
|
|
447
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
448
|
+
}
|
|
449
|
+
catch {
|
|
450
|
+
return fileURLToPath(import.meta.url);
|
|
451
|
+
} })();
|
|
452
|
+
const _argv1RealPath = (() => { try {
|
|
453
|
+
return fs.realpathSync(process.argv[1]);
|
|
454
|
+
}
|
|
455
|
+
catch {
|
|
456
|
+
return process.argv[1];
|
|
457
|
+
} })();
|
|
458
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
459
|
+
process.exitCode = main();
|
|
460
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
4
5
|
// ---------------------------------------------------------------------------
|
|
@@ -232,5 +233,21 @@ export async function main(argv = process.argv.slice(2)) {
|
|
|
232
233
|
}
|
|
233
234
|
return runCheck(rest);
|
|
234
235
|
}
|
|
235
|
-
|
|
236
|
-
|
|
236
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
237
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
238
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
239
|
+
const _selfRealPath = (() => { try {
|
|
240
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return fileURLToPath(import.meta.url);
|
|
244
|
+
} })();
|
|
245
|
+
const _argv1RealPath = (() => { try {
|
|
246
|
+
return fs.realpathSync(process.argv[1]);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return process.argv[1];
|
|
250
|
+
} })();
|
|
251
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
252
|
+
process.exitCode = await main();
|
|
253
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
const validTiers = new Set(["adapter", "design-target", "installed-command", "live-acceptance", "documented-runtime-gap"]);
|
|
3
4
|
const validRuntimes = new Set(["codex", "claude-code", "kiro-cli"]);
|
|
4
5
|
const validHooks = new Set(["workflow-steering", "stop-goal-fit"]);
|
|
@@ -148,5 +149,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
148
149
|
return 1;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
|
|
152
|
-
|
|
152
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
153
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
154
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
155
|
+
const _selfRealPath = (() => { try {
|
|
156
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return fileURLToPath(import.meta.url);
|
|
160
|
+
} })();
|
|
161
|
+
const _argv1RealPath = (() => { try {
|
|
162
|
+
return fs.realpathSync(process.argv[1]);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return process.argv[1];
|
|
166
|
+
} })();
|
|
167
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
168
|
+
process.exitCode = main();
|
|
169
|
+
}
|
|
@@ -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 =
|
|
47
|
+
main().then((code) => { process.exitCode = code; }).catch((err) => { console.error(err); process.exitCode = 1; });
|
|
48
48
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
4
5
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
@@ -258,5 +259,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
258
259
|
const options = parseEvidenceOptions(argv);
|
|
259
260
|
return options ? runEvidence(options) : 2;
|
|
260
261
|
}
|
|
261
|
-
|
|
262
|
-
|
|
262
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
263
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
264
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
265
|
+
const _selfRealPath = (() => { try {
|
|
266
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return fileURLToPath(import.meta.url);
|
|
270
|
+
} })();
|
|
271
|
+
const _argv1RealPath = (() => { try {
|
|
272
|
+
return fs.realpathSync(process.argv[1]);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return process.argv[1];
|
|
276
|
+
} })();
|
|
277
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
278
|
+
process.exitCode = main();
|
|
279
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { flagBool, flagString, parseArgs } from "../lib/args.js";
|
|
4
5
|
const ACTIVE_STATUSES = new Set([
|
|
@@ -268,5 +269,21 @@ export function main(argv = process.argv.slice(2)) {
|
|
|
268
269
|
printText(result);
|
|
269
270
|
return 0;
|
|
270
271
|
}
|
|
271
|
-
|
|
272
|
-
|
|
272
|
+
// Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
|
|
273
|
+
// Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
|
|
274
|
+
// entry-point guard fires correctly when the module is loaded directly as a script.
|
|
275
|
+
const _selfRealPath = (() => { try {
|
|
276
|
+
return fs.realpathSync(fileURLToPath(import.meta.url));
|
|
277
|
+
}
|
|
278
|
+
catch {
|
|
279
|
+
return fileURLToPath(import.meta.url);
|
|
280
|
+
} })();
|
|
281
|
+
const _argv1RealPath = (() => { try {
|
|
282
|
+
return fs.realpathSync(process.argv[1]);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return process.argv[1];
|
|
286
|
+
} })();
|
|
287
|
+
if (_selfRealPath === _argv1RealPath) {
|
|
288
|
+
process.exitCode = main();
|
|
289
|
+
}
|
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
|
|
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
|
-
["
|
|
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-
|
|
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
|
-
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
errors.push(
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
const
|
|
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
|
-
|
|
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;
|
|
@@ -118,31 +118,48 @@ export function readKitInventory(sourceRoot, dest) {
|
|
|
118
118
|
function safeSegment(value) {
|
|
119
119
|
return value.replace(/[^A-Za-z0-9_.-]+/g, "-").replace(/^[.-]+|[.-]+$/g, "") || "asset";
|
|
120
120
|
}
|
|
121
|
+
// Asset classes that are directly activated (copied to the runtime directory) by both adapters.
|
|
122
|
+
// flows: gate definitions read by the adapter's flow-routing layer.
|
|
123
|
+
// skills: agent guidance markdown copied to skills/<kit-id>/ for agent discovery.
|
|
124
|
+
// docs: documentation markdown copied to docs/<kit-id>/ for agent reference.
|
|
125
|
+
const ACTIVATED_ASSET_CLASSES = new Set(["flows", "skills", "docs"]);
|
|
121
126
|
export function activateCodexLocal(sourceRoot, dest) {
|
|
122
127
|
const inventory = readKitInventory(sourceRoot, dest);
|
|
123
128
|
const runtimeDir = path.join(dest, ".flow-agents", "runtime", "codex");
|
|
124
129
|
const generated = [];
|
|
125
130
|
const skipped = [];
|
|
126
131
|
for (const asset of inventory.assets) {
|
|
127
|
-
if (asset.asset_class
|
|
128
|
-
|
|
129
|
-
|
|
132
|
+
if (asset.asset_class === "flows") {
|
|
133
|
+
if (!asset.asset_id) {
|
|
134
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: null, reason: "flow asset is missing an id" });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
138
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
139
|
+
fs.copyFileSync(asset.source_path, output);
|
|
140
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
130
141
|
}
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
|
|
142
|
+
else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
|
|
143
|
+
// Copy skills and docs to runtime/<adapter>/<class>/<kit-id>/<filename> so the
|
|
144
|
+
// agent's guidance index (AGENTS.md) can reference them and they are co-located
|
|
145
|
+
// with flow definitions for the same kit.
|
|
146
|
+
const filename = path.basename(asset.source_path);
|
|
147
|
+
const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
|
|
148
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
149
|
+
fs.copyFileSync(asset.source_path, output);
|
|
150
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by codex-local" });
|
|
134
154
|
}
|
|
135
|
-
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
136
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
137
|
-
fs.copyFileSync(asset.source_path, output);
|
|
138
|
-
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
139
155
|
}
|
|
140
156
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
141
|
-
const
|
|
157
|
+
const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
|
|
158
|
+
const manifest = { schema_version: "1.0", adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
142
159
|
const manifestPath = path.join(runtimeDir, "activation.json");
|
|
143
160
|
writeJson(manifestPath, manifest);
|
|
144
161
|
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "codex-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
145
|
-
return { selected_adapter: "codex-local", supported_asset_classes:
|
|
162
|
+
return { selected_adapter: "codex-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
146
163
|
}
|
|
147
164
|
// Decision Q3 (Issue #32): Option (a) — new adapter id "strands-local" rather than
|
|
148
165
|
// loading kit flows inside FlowAgentsHooks. Rationale: activation is a workspace-prep
|
|
@@ -154,27 +171,41 @@ export function activateStrandsLocal(sourceRoot, dest) {
|
|
|
154
171
|
const inventory = readKitInventory(sourceRoot, dest);
|
|
155
172
|
// Runtime flows land at .flow-agents/runtime/strands/flows/<kit-id>/<asset-id>.flow.json
|
|
156
173
|
// so the Strands steering context can glob for *.flow.json under this path.
|
|
174
|
+
// Runtime skills land at .flow-agents/runtime/strands/skills/<kit-id>/<filename> and
|
|
175
|
+
// docs at .flow-agents/runtime/strands/docs/<kit-id>/<filename> for system-prompt injection.
|
|
157
176
|
const runtimeDir = path.join(dest, ".flow-agents", "runtime", "strands");
|
|
158
177
|
const generated = [];
|
|
159
178
|
const skipped = [];
|
|
160
179
|
for (const asset of inventory.assets) {
|
|
161
|
-
if (asset.asset_class
|
|
162
|
-
|
|
163
|
-
|
|
180
|
+
if (asset.asset_class === "flows") {
|
|
181
|
+
if (!asset.asset_id) {
|
|
182
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: null, reason: "flow asset is missing an id" });
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
186
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
187
|
+
fs.copyFileSync(asset.source_path, output);
|
|
188
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
164
189
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
190
|
+
else if (asset.asset_class === "skills" || asset.asset_class === "docs") {
|
|
191
|
+
// Mirror the codex-local layout: strands/<class>/<kit-id>/<filename>.
|
|
192
|
+
// The Strands system-prompt injection layer can glob for all *.md files under
|
|
193
|
+
// .flow-agents/runtime/strands/skills/ to include agent guidance in the context.
|
|
194
|
+
const filename = path.basename(asset.source_path);
|
|
195
|
+
const output = path.join(runtimeDir, asset.asset_class, safeSegment(asset.kit_id), filename);
|
|
196
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
197
|
+
fs.copyFileSync(asset.source_path, output);
|
|
198
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id ?? "", source_path: asset.source_path.split(path.sep).join("/") });
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is not activated by strands-local" });
|
|
168
202
|
}
|
|
169
|
-
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
170
|
-
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
171
|
-
fs.copyFileSync(asset.source_path, output);
|
|
172
|
-
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
173
203
|
}
|
|
174
204
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
175
|
-
const
|
|
205
|
+
const supportedClasses = Array.from(ACTIVATED_ASSET_CLASSES);
|
|
206
|
+
const manifest = { schema_version: "1.0", adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
176
207
|
const manifestPath = path.join(runtimeDir, "activation.json");
|
|
177
208
|
writeJson(manifestPath, manifest);
|
|
178
209
|
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "strands-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
179
|
-
return { selected_adapter: "strands-local", supported_asset_classes:
|
|
210
|
+
return { selected_adapter: "strands-local", supported_asset_classes: supportedClasses, generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
180
211
|
}
|