@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.
Files changed (111) hide show
  1. package/.github/workflows/ci.yml +110 -0
  2. package/.github/workflows/runtime-compat.yml +5 -2
  3. package/CHANGELOG.md +42 -0
  4. package/README.md +26 -5
  5. package/build/src/cli/console-learning-projection.js +19 -2
  6. package/build/src/cli/effective-backlog-settings.js +18 -2
  7. package/build/src/cli/fixture-retirement-audit.js +19 -2
  8. package/build/src/cli/init.js +19 -2
  9. package/build/src/cli/{flow-kit.js → kit.js} +122 -108
  10. package/build/src/cli/promote-workflow-artifact.js +19 -2
  11. package/build/src/cli/publish-change-helper.js +19 -2
  12. package/build/src/cli/pull-work-provider.js +19 -2
  13. package/build/src/cli/runtime-adapter.js +20 -2
  14. package/build/src/cli/usage-feedback.js +19 -2
  15. package/build/src/cli/utterance-check.js +19 -2
  16. package/build/src/cli/validate-hook-influence.js +19 -2
  17. package/build/src/cli/validate-source-tree.js +4 -4
  18. package/build/src/cli/veritas-governance.js +19 -2
  19. package/build/src/cli/workflow-artifact-cleanup-audit.js +19 -2
  20. package/build/src/cli.js +3 -3
  21. package/build/src/flow-kit/validate.js +58 -62
  22. package/build/src/runtime-adapters.js +55 -24
  23. package/build/src/tools/build-universal-bundles.js +83 -19
  24. package/build/src/tools/generate-context-map.js +68 -9
  25. package/build/src/tools/validate-package.js +19 -2
  26. package/build/src/tools/validate-source-tree.js +51 -3
  27. package/context/scripts/telemetry/console-presets.sh +1 -1
  28. package/docs/adr/0007-flow-skill-kit-tool-boundary.md +169 -0
  29. package/docs/adr/0007-skill-audit.md +112 -0
  30. package/docs/adr/0008-kit-operation-boundary.md +88 -0
  31. package/docs/context-map.md +18 -22
  32. package/docs/flow-kit-repository-contract.md +5 -5
  33. package/docs/getting-started.md +177 -0
  34. package/docs/index.md +19 -8
  35. package/docs/kit-authoring-guide.md +46 -10
  36. package/docs/knowledge-kit.md +2 -2
  37. package/docs/spec/runtime-hook-surface.md +1 -1
  38. package/docs/vision.md +1 -1
  39. package/docs/workflow-usage-guide.md +1 -1
  40. package/evals/ci/run-baseline.sh +55 -8
  41. package/evals/fixtures/builder-kit-workflow-state/happy-path.json +2 -2
  42. package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +2 -2
  43. package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +1 -1
  44. package/evals/fixtures/pull-work-provider/github-issues.json +5 -5
  45. package/evals/integration/test_activate_npx_context.sh +2 -2
  46. package/evals/integration/test_bundle_install.sh +17 -12
  47. package/evals/integration/test_console_learning_projection.sh +1 -1
  48. package/evals/integration/test_flow_kit_install_git.sh +7 -7
  49. package/evals/integration/test_flow_kit_repository.sh +4 -4
  50. package/evals/integration/test_kit_conformance_levels.sh +1 -1
  51. package/evals/integration/test_local_flow_kit_install.sh +7 -7
  52. package/evals/integration/test_publish_change_helper.sh +1 -1
  53. package/evals/integration/test_pull_work_provider.sh +1 -1
  54. package/evals/integration/test_runtime_adapter_activation.sh +140 -19
  55. package/evals/lib/node.sh +2 -2
  56. package/evals/run.sh +2 -0
  57. package/evals/static/test_console_presets.sh +49 -0
  58. package/evals/static/test_workflow_skills.sh +15 -15
  59. package/integrations/strands/flow_agents_strands/steering.py +1 -1
  60. package/integrations/strands-ts/src/hooks.ts +1 -1
  61. package/kits/builder/kit.json +17 -0
  62. package/{skills → kits/builder/skills}/builder-shape/SKILL.md +4 -4
  63. package/{skills → kits/builder/skills}/idea-to-backlog/SKILL.md +1 -1
  64. package/kits/knowledge/kit.json +16 -9
  65. package/package.json +8 -5
  66. package/packaging/packs.json +1 -21
  67. package/scripts/README.md +1 -1
  68. package/scripts/kit.js +2 -0
  69. package/scripts/telemetry/console-presets.sh +1 -1
  70. package/skills/README.md +23 -0
  71. package/src/cli/console-learning-projection.ts +7 -1
  72. package/src/cli/effective-backlog-settings.ts +6 -1
  73. package/src/cli/fixture-retirement-audit.ts +7 -1
  74. package/src/cli/init.ts +7 -1
  75. package/src/cli/{flow-kit.ts → kit.ts} +124 -109
  76. package/src/cli/promote-workflow-artifact.ts +7 -1
  77. package/src/cli/publish-change-helper.ts +7 -1
  78. package/src/cli/pull-work-provider.ts +7 -1
  79. package/src/cli/runtime-adapter.ts +8 -1
  80. package/src/cli/usage-feedback.ts +7 -1
  81. package/src/cli/utterance-check.ts +7 -1
  82. package/src/cli/validate-hook-influence.ts +7 -1
  83. package/src/cli/validate-source-tree.ts +4 -4
  84. package/src/cli/veritas-governance.ts +7 -1
  85. package/src/cli/workflow-artifact-cleanup-audit.ts +7 -1
  86. package/src/cli.ts +3 -3
  87. package/src/flow-kit/validate.ts +63 -57
  88. package/src/runtime-adapters.ts +54 -26
  89. package/src/tools/build-universal-bundles.ts +67 -14
  90. package/src/tools/generate-context-map.ts +43 -7
  91. package/src/tools/validate-package.ts +7 -1
  92. package/src/tools/validate-source-tree.ts +34 -2
  93. package/scripts/flow-kit.js +0 -2
  94. package/skills/context-budget/SKILL.md +0 -40
  95. package/skills/explore/SKILL.md +0 -137
  96. package/skills/feedback-loop/SKILL.md +0 -87
  97. package/skills/frontend-design/SKILL.md +0 -80
  98. /package/{skills → kits/builder/skills}/deliver/SKILL.md +0 -0
  99. /package/{skills → kits/builder/skills}/design-probe/SKILL.md +0 -0
  100. /package/{skills → kits/builder/skills}/evidence-gate/SKILL.md +0 -0
  101. /package/{skills → kits/builder/skills}/execute-plan/SKILL.md +0 -0
  102. /package/{skills → kits/builder/skills}/fix-bug/SKILL.md +0 -0
  103. /package/{skills → kits/builder/skills}/learning-review/SKILL.md +0 -0
  104. /package/{skills → kits/builder/skills}/pickup-probe/SKILL.md +0 -0
  105. /package/{skills → kits/builder/skills}/plan-work/SKILL.md +0 -0
  106. /package/{skills → kits/builder/skills}/pull-work/SKILL.md +0 -0
  107. /package/{skills → kits/builder/skills}/release-readiness/SKILL.md +0 -0
  108. /package/{skills → kits/builder/skills}/review-work/SKILL.md +0 -0
  109. /package/{skills → kits/builder/skills}/tdd-workflow/SKILL.md +0 -0
  110. /package/{skills → kits/builder/skills}/verify-work/SKILL.md +0 -0
  111. /package/{skills → kits/knowledge/skills}/knowledge-capture/SKILL.md +0 -0
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import { fileURLToPath } from "node:url";
3
4
  import path from "node:path";
4
5
  import { loadJson, readText, root, walkFiles, writeText } from "./common.js";
5
6
  const dist = process.env.FLOW_AGENTS_DIST_DIR ? path.resolve(process.env.FLOW_AGENTS_DIST_DIR) : path.join(root, "dist");
@@ -8,6 +9,61 @@ const packs = loadJson(path.join(root, "packaging/packs.json"));
8
9
  const textExtensions = new Set([".css", ".html", ".js", ".json", ".md", ".sh", ".toml", ".txt", ".yaml", ".yml", ".ts"]);
9
10
  const dropDiagnostics = [];
10
11
  const printDiagnostics = !["0", "false", "no"].includes(String(process.env.FLOW_AGENTS_EXPORT_DIAGNOSTICS ?? "1").toLowerCase());
12
+ /**
13
+ * Collect all skill source paths across skills/ and kit-owned skills.
14
+ * Returns an array of {name, src} pairs where name is the install name
15
+ * (same as the directory name) and src is the absolute SKILL.md path.
16
+ * Kit-owned skills are discovered by reading kit.json `skills` arrays;
17
+ * each entry's `path` is resolved relative to the kit directory.
18
+ */
19
+ function collectAllSkills() {
20
+ const results = [];
21
+ const seen = new Set();
22
+ // 1. Top-level skills/ directory (tools pending reclassification).
23
+ const skillsDir = path.join(root, "skills");
24
+ if (fs.existsSync(skillsDir)) {
25
+ for (const skill of fs.readdirSync(skillsDir).sort()) {
26
+ const skillPath = path.join(skillsDir, skill, "SKILL.md");
27
+ if (fs.existsSync(skillPath) && !seen.has(skill)) {
28
+ seen.add(skill);
29
+ results.push({ name: skill, src: skillPath });
30
+ }
31
+ }
32
+ }
33
+ // 2. Kit-owned skills declared in kits/<kit>/kit.json `skills` arrays.
34
+ const kitsDir = path.join(root, "kits");
35
+ if (fs.existsSync(kitsDir)) {
36
+ for (const kitName of fs.readdirSync(kitsDir).sort()) {
37
+ const kitJson = path.join(kitsDir, kitName, "kit.json");
38
+ if (!fs.existsSync(kitJson))
39
+ continue;
40
+ let kitManifest;
41
+ try {
42
+ kitManifest = loadJson(kitJson);
43
+ }
44
+ catch {
45
+ continue;
46
+ }
47
+ const skills = Array.isArray(kitManifest["skills"]) ? kitManifest["skills"] : [];
48
+ for (const entry of skills) {
49
+ if (typeof entry !== "object" || entry === null)
50
+ continue;
51
+ const skillEntry = entry;
52
+ const relPath = typeof skillEntry["path"] === "string" ? skillEntry["path"] : null;
53
+ if (!relPath)
54
+ continue;
55
+ // Derive install name from the directory containing SKILL.md (one level up).
56
+ const absPath = path.resolve(path.join(kitsDir, kitName), relPath);
57
+ const skillName = path.basename(path.dirname(absPath));
58
+ if (fs.existsSync(absPath) && !seen.has(skillName)) {
59
+ seen.add(skillName);
60
+ results.push({ name: skillName, src: absPath });
61
+ }
62
+ }
63
+ }
64
+ }
65
+ return results.sort((a, b) => a.name.localeCompare(b.name));
66
+ }
11
67
  function resetDir(dir) {
12
68
  fs.rmSync(dir, { recursive: true, force: true });
13
69
  fs.mkdirSync(dir, { recursive: true });
@@ -326,10 +382,8 @@ function buildClaudeCode(agents) {
326
382
  writeText(path.join(bundle, manifest.claude_code.task_dir, ".gitkeep"), "");
327
383
  for (const spec of agents)
328
384
  writeText(path.join(bundle, ".claude/agents", `${spec.name}.md`), exportClaudeAgent(spec));
329
- for (const skill of fs.readdirSync(path.join(root, "skills"))) {
330
- const skillPath = path.join(root, "skills", skill, "SKILL.md");
331
- if (fs.existsSync(skillPath))
332
- writeText(path.join(bundle, ".claude/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "claude-code", "<bundle-root>"));
385
+ for (const { name, src } of collectAllSkills()) {
386
+ writeText(path.join(bundle, ".claude/skills", name, "SKILL.md"), sanitizeText(readText(src), "claude-code", "<bundle-root>"));
333
387
  }
334
388
  writeText(path.join(bundle, ".claude/settings.json"), exportClaudeSettings());
335
389
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Claude Code", agents, manifest.claude_code.task_dir));
@@ -351,10 +405,8 @@ function buildCodex(agents) {
351
405
  writeText(path.join(bundle, ".codex/hooks.json"), exportCodexHooks());
352
406
  for (const spec of targetAgents)
353
407
  writeText(path.join(bundle, ".codex/agents", `${spec.name}.toml`), exportCodexAgent(spec));
354
- for (const skill of fs.readdirSync(path.join(root, "skills"))) {
355
- const skillPath = path.join(root, "skills", skill, "SKILL.md");
356
- if (fs.existsSync(skillPath))
357
- writeText(path.join(bundle, ".codex/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "codex", "<bundle-root>"));
408
+ for (const { name, src } of collectAllSkills()) {
409
+ writeText(path.join(bundle, ".codex/skills", name, "SKILL.md"), sanitizeText(readText(src), "codex", "<bundle-root>"));
358
410
  }
359
411
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("Codex", targetAgents, manifest.codex.task_dir));
360
412
  writeText(path.join(bundle, "README.md"), exportTargetReadme("Codex", "bash install.sh /path/to/workspace"));
@@ -518,10 +570,8 @@ function buildOpencode(agents) {
518
570
  for (const spec of agents) {
519
571
  writeText(path.join(bundle, ".opencode/agents", `${spec.name}.md`), exportOpencodeAgent(spec));
520
572
  }
521
- for (const skill of fs.readdirSync(path.join(root, "skills"))) {
522
- const skillPath = path.join(root, "skills", skill, "SKILL.md");
523
- if (fs.existsSync(skillPath))
524
- writeText(path.join(bundle, ".opencode/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "opencode", "<bundle-root>"));
573
+ for (const { name, src } of collectAllSkills()) {
574
+ writeText(path.join(bundle, ".opencode/skills", name, "SKILL.md"), sanitizeText(readText(src), "opencode", "<bundle-root>"));
525
575
  }
526
576
  writeText(path.join(bundle, ".opencode/plugins/flow-agents.js"), exportOpencodePlugin());
527
577
  writeText(path.join(bundle, "opencode.json"), exportOpencodeConfig());
@@ -631,10 +681,8 @@ function buildPi(agents) {
631
681
  writeText(path.join(bundle, manifest.pi.task_dir, ".gitkeep"), "");
632
682
  // pi has no named-subagent registry; agents are left canonical/unexported.
633
683
  // Skills are exported to .pi/skills/ (direct .md files supported in that dir).
634
- for (const skill of fs.readdirSync(path.join(root, "skills"))) {
635
- const skillPath = path.join(root, "skills", skill, "SKILL.md");
636
- if (fs.existsSync(skillPath))
637
- writeText(path.join(bundle, ".pi/skills", skill, "SKILL.md"), sanitizeText(readText(skillPath), "pi", "<bundle-root>"));
684
+ for (const { name, src } of collectAllSkills()) {
685
+ writeText(path.join(bundle, ".pi/skills", name, "SKILL.md"), sanitizeText(readText(src), "pi", "<bundle-root>"));
638
686
  }
639
687
  writeText(path.join(bundle, ".pi/extensions/flow-agents.ts"), exportPiExtension());
640
688
  writeText(path.join(bundle, "AGENTS.md"), exportRootAgentsMd("pi", agents, manifest.pi.task_dir));
@@ -647,7 +695,7 @@ function buildCatalog(agents) {
647
695
  return {
648
696
  source_root: ".",
649
697
  agents: agents.slice().sort((a, b) => a.name.localeCompare(b.name)).map((spec) => spec.name),
650
- skills: fs.readdirSync(path.join(root, "skills")).filter((name) => fs.existsSync(path.join(root, "skills", name, "SKILL.md"))).sort(),
698
+ skills: collectAllSkills().map(({ name }) => name),
651
699
  powers: fs.readdirSync(path.join(root, "powers")).filter((name) => fs.existsSync(path.join(root, "powers", name, "mcp.json"))).sort(),
652
700
  packs: packs.packs ?? [],
653
701
  kits: fs.existsSync(kitsCatalog) ? loadJson(kitsCatalog).kits ?? [] : [],
@@ -678,5 +726,21 @@ export function main() {
678
726
  }
679
727
  return 0;
680
728
  }
681
- if (import.meta.url === `file://${process.argv[1]}`)
682
- process.exit(main());
729
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
730
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
731
+ // entry-point guard fires correctly when the module is loaded directly as a script.
732
+ const _selfRealPath = (() => { try {
733
+ return fs.realpathSync(fileURLToPath(import.meta.url));
734
+ }
735
+ catch {
736
+ return fileURLToPath(import.meta.url);
737
+ } })();
738
+ const _argv1RealPath = (() => { try {
739
+ return fs.realpathSync(process.argv[1]);
740
+ }
741
+ catch {
742
+ return process.argv[1];
743
+ } })();
744
+ if (_selfRealPath === _argv1RealPath) {
745
+ process.exitCode = main();
746
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import { fileURLToPath } from "node:url";
3
4
  import path from "node:path";
4
5
  import { exists, loadJson, markdownTable, oneLine, readText, rel, root, writeText } from "./common.js";
5
6
  const defaultOutput = path.join(root, "docs/context-map.md");
@@ -74,16 +75,58 @@ function repoShape(manifest) {
74
75
  rows.push([".flow-agents", "runtime", "Cross-session workflow artifacts and sidecars. Not committed by default."]);
75
76
  return rows;
76
77
  }
78
+ /** Collect all skill {name, absPath} pairs from skills/ and kit-owned skills. */
79
+ function allSkillPaths() {
80
+ const results = [];
81
+ const seen = new Set();
82
+ const skillsDir = path.join(root, "skills");
83
+ if (exists(skillsDir)) {
84
+ for (const name of fs.readdirSync(skillsDir).sort()) {
85
+ const absPath = path.join(skillsDir, name, "SKILL.md");
86
+ if (exists(absPath) && !seen.has(name)) {
87
+ seen.add(name);
88
+ results.push({ name, absPath });
89
+ }
90
+ }
91
+ }
92
+ const kitsDir = path.join(root, "kits");
93
+ if (exists(kitsDir)) {
94
+ for (const kitName of fs.readdirSync(kitsDir).sort()) {
95
+ const kitJson = path.join(kitsDir, kitName, "kit.json");
96
+ if (!exists(kitJson))
97
+ continue;
98
+ let kitManifest;
99
+ try {
100
+ kitManifest = loadJson(kitJson);
101
+ }
102
+ catch {
103
+ continue;
104
+ }
105
+ const skills = Array.isArray(kitManifest["skills"]) ? kitManifest["skills"] : [];
106
+ for (const entry of skills) {
107
+ if (typeof entry !== "object" || entry === null)
108
+ continue;
109
+ const skillEntry = entry;
110
+ const relPath = typeof skillEntry["path"] === "string" ? skillEntry["path"] : null;
111
+ if (!relPath)
112
+ continue;
113
+ const absPath = path.resolve(path.join(kitsDir, kitName), relPath);
114
+ const skillName = path.basename(path.dirname(absPath));
115
+ if (exists(absPath) && !seen.has(skillName)) {
116
+ seen.add(skillName);
117
+ results.push({ name: skillName, absPath });
118
+ }
119
+ }
120
+ }
121
+ }
122
+ return results.sort((a, b) => a.name.localeCompare(b.name));
123
+ }
77
124
  function listSkillRows() {
78
125
  const workflowRows = [];
79
126
  const supportRows = [];
80
- const skillsDir = path.join(root, "skills");
81
- for (const name of fs.readdirSync(skillsDir).sort()) {
82
- const skillPath = path.join(skillsDir, name, "SKILL.md");
83
- if (!exists(skillPath))
84
- continue;
85
- const meta = frontmatter(readText(skillPath));
86
- const row = [meta.name ?? name, rel(skillPath), oneLine(meta.description ?? "")];
127
+ for (const { name, absPath } of allSkillPaths()) {
128
+ const meta = frontmatter(readText(absPath));
129
+ const row = [meta.name ?? name, rel(absPath), oneLine(meta.description ?? "")];
87
130
  if (workflowSkills.has(row[0]))
88
131
  workflowRows.push(row);
89
132
  else
@@ -194,5 +237,21 @@ export function main(argv = process.argv.slice(2)) {
194
237
  console.log(`Wrote ${rel(output)}`);
195
238
  return 0;
196
239
  }
197
- if (import.meta.url === `file://${process.argv[1]}`)
198
- process.exit(main());
240
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
241
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
242
+ // entry-point guard fires correctly when the module is loaded directly as a script.
243
+ const _selfRealPath = (() => { try {
244
+ return fs.realpathSync(fileURLToPath(import.meta.url));
245
+ }
246
+ catch {
247
+ return fileURLToPath(import.meta.url);
248
+ } })();
249
+ const _argv1RealPath = (() => { try {
250
+ return fs.realpathSync(process.argv[1]);
251
+ }
252
+ catch {
253
+ return process.argv[1];
254
+ } })();
255
+ if (_selfRealPath === _argv1RealPath) {
256
+ process.exitCode = main();
257
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import { fileURLToPath } from "node:url";
3
4
  import path from "node:path";
4
5
  export function main(argv = process.argv.slice(2)) {
5
6
  const [prefixArg, localFlag] = argv;
@@ -60,5 +61,21 @@ export function main(argv = process.argv.slice(2)) {
60
61
  console.log(errors === 0 ? "Result: PASS" : `Result: FAIL (${errors} error(s))`);
61
62
  return errors === 0 ? 0 : 1;
62
63
  }
63
- if (import.meta.url === `file://${process.argv[1]}`)
64
- process.exit(main());
64
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
65
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
66
+ // entry-point guard fires correctly when the module is loaded directly as a script.
67
+ const _selfRealPath = (() => { try {
68
+ return fs.realpathSync(fileURLToPath(import.meta.url));
69
+ }
70
+ catch {
71
+ return fileURLToPath(import.meta.url);
72
+ } })();
73
+ const _argv1RealPath = (() => { try {
74
+ return fs.realpathSync(process.argv[1]);
75
+ }
76
+ catch {
77
+ return process.argv[1];
78
+ } })();
79
+ if (_selfRealPath === _argv1RealPath) {
80
+ process.exitCode = main();
81
+ }
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import fs from "node:fs";
3
+ import { fileURLToPath } from "node:url";
3
4
  import path from "node:path";
4
5
  import { spawnSync } from "node:child_process";
5
6
  import { loadJson, readText, rel, root, walkFiles } from "./common.js";
@@ -36,7 +37,7 @@ const publicScriptWrappers = new Map([
36
37
  ] }],
37
38
  ["scripts/filter-installed-packs.js", { target: "../build/src/tools/filter-installed-packs.js", significantLines: ['import("../build/src/tools/filter-installed-packs.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
38
39
  ["scripts/generate-context-map.js", { target: "../build/src/tools/generate-context-map.js", significantLines: ['import("../build/src/tools/generate-context-map.js").then(({ main }) => process.exit(main(process.argv.slice(2))));'] }],
39
- ["scripts/flow-kit.js", { target: "../build/src/cli/flow-kit.js", significantLines: ['import("../build/src/cli/flow-kit.js").then(({ main }) => process.exit(main()));'] }],
40
+ ["scripts/kit.js", { target: "../build/src/cli/kit.js", significantLines: ['import("../build/src/cli/kit.js").then(({ main }) => main().then((code) => process.exit(code)));'] }],
40
41
  ["scripts/pull-work-provider.js", { target: "../build/src/cli/pull-work-provider.js", significantLines: ['import("../build/src/cli/pull-work-provider.js").then(({ main }) => process.exit(main()));'] }],
41
42
  ["scripts/effective-backlog-settings.js", { target: "../build/src/cli/effective-backlog-settings.js", significantLines: ['import("../build/src/cli/effective-backlog-settings.js").then(({ main }) => process.exit(main()));'] }],
42
43
  ["scripts/publish-change-helper.js", { target: "../build/src/cli/publish-change-helper.js", significantLines: ['import("../build/src/cli/publish-change-helper.js").then(({ main }) => process.exit(main()));'] }],
@@ -390,6 +391,32 @@ function validateAgentPaths(reporter, manifest) {
390
391
  }
391
392
  }
392
393
  function validateLegacyRefs(reporter) {
394
+ // Collect all kit-owned asset relative paths so legacy-ref scanning can skip matches
395
+ // that are subpaths of kit-owned assets. E.g. legacyRefRe matches "skills/plan-work/SKILL.md"
396
+ // within "kits/builder/skills/plan-work/SKILL.md"; the kit declares and validates these.
397
+ const kitOwnedSubPaths = new Set();
398
+ const kitsDir = path.join(root, "kits");
399
+ if (fs.existsSync(kitsDir)) {
400
+ for (const kitName of fs.readdirSync(kitsDir)) {
401
+ const kitJson = path.join(kitsDir, kitName, "kit.json");
402
+ if (!fs.existsSync(kitJson))
403
+ continue;
404
+ try {
405
+ const kitManifest = loadJson(kitJson);
406
+ for (const section of ["skills", "docs", "adapters", "evals", "assets"]) {
407
+ const entries = Array.isArray(kitManifest[section]) ? kitManifest[section] : [];
408
+ for (const entry of entries) {
409
+ if (typeof entry !== "object" || entry === null)
410
+ continue;
411
+ const relPath = entry["path"];
412
+ if (typeof relPath === "string" && relPath)
413
+ kitOwnedSubPaths.add(relPath);
414
+ }
415
+ }
416
+ }
417
+ catch { /* skip invalid kit.json */ }
418
+ }
419
+ }
393
420
  for (const file of walkFiles(path.join(root, "evals")).sort()) {
394
421
  if (!textRefExtensions.has(path.extname(file)))
395
422
  continue;
@@ -403,6 +430,11 @@ function validateLegacyRefs(reporter) {
403
430
  continue;
404
431
  if (ref.split(/[\\/]/).includes("node_modules"))
405
432
  continue;
433
+ // Skip refs that are declared kit-owned asset paths or their parent directories
434
+ // (e.g. "skills/plan-work/SKILL.md" or "skills/plan-work" matched inside
435
+ // "kits/builder/skills/plan-work/SKILL.md" in eval files).
436
+ if (kitOwnedSubPaths.has(ref) || [...kitOwnedSubPaths].some((p) => p.startsWith(ref + "/")))
437
+ continue;
406
438
  const candidates = [path.join(root, ref), ...(ref.startsWith("evals/") ? [] : [path.join(root, "evals", ref)])];
407
439
  if (!candidates.some(fs.existsSync))
408
440
  reporter.fail(`${rel(file)}: references missing source path: ${ref}`);
@@ -624,5 +656,21 @@ export function main(argv = process.argv.slice(2)) {
624
656
  console.log("Source tree validation passed.");
625
657
  return 0;
626
658
  }
627
- if (import.meta.url === `file://${process.argv[1]}`)
628
- process.exit(main());
659
+ // Use process.exitCode (not process.exit) to allow stdout to be flushed before exit.
660
+ // Resolve real paths to handle symlinks (e.g. /tmp -> /private/tmp on macOS) so the
661
+ // entry-point guard fires correctly when the module is loaded directly as a script.
662
+ const _selfRealPath = (() => { try {
663
+ return fs.realpathSync(fileURLToPath(import.meta.url));
664
+ }
665
+ catch {
666
+ return fileURLToPath(import.meta.url);
667
+ } })();
668
+ const _argv1RealPath = (() => { try {
669
+ return fs.realpathSync(process.argv[1]);
670
+ }
671
+ catch {
672
+ return process.argv[1];
673
+ } })();
674
+ if (_selfRealPath === _argv1RealPath) {
675
+ process.exitCode = main();
676
+ }
@@ -6,7 +6,7 @@ flow_agents_local_kontour_console_url() {
6
6
  }
7
7
 
8
8
  flow_agents_kontour_cloud_console_url() {
9
- printf '%s\n' "${FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL:-https://console.kontourai.com}"
9
+ printf '%s\n' "${FLOW_AGENTS_KONTOUR_CLOUD_CONSOLE_URL:-https://console.kontourai.io}"
10
10
  }
11
11
 
12
12
  flow_agents_kontour_hosted_console_url() {
@@ -0,0 +1,169 @@
1
+ ---
2
+ title: "ADR 0007: Flow / Skill / Kit / Tool Boundary"
3
+ ---
4
+
5
+ # ADR 0007: Flow / Skill / Kit / Tool Boundary
6
+
7
+ **Date:** 2026-06-15
8
+ **Status:** Accepted
9
+
10
+ ---
11
+
12
+ ## Context
13
+
14
+ Flow Agents has accumulated ~26 skills. When kits were first introduced, the word "skill" was used loosely for anything the agent knew how to do — from kit-specific workflow procedures to general capabilities (search, browser, dependency scanning) to cross-cutting concerns (agentic engineering principles, context budgeting). That blurred three genuinely different things: the agent's *procedural methods for flow steps*, the *raw capabilities it wields*, and the *containers that package flows with their implementations*.
15
+
16
+ The boundary became sharper when the Flow Definition container moved to `kontourai/flow` (ADR 0001). Once Flow owns the process contract and Flow Agents owns the agent-facing implementation, a natural question emerges: where does each skill belong, and what is a skill in the first place?
17
+
18
+ A related accumulation was an informal "general capability skill tier" — skills like `agentic-engineering`, `search-first`, `github-cli`, and `eval-rebuild` that described how the agent uses runtime capabilities, not how it implements a specific flow step. That tier was never designed; it accreted as the skill list grew. It is not a peer design concept alongside Kit-skills; it is an artifact of undifferentiated naming.
19
+
20
+ This ADR records the conceptual model that was agreed in a design conversation with Brian Anderson on 2026-06-15 and makes it the authoritative reference for future skill placement, kit authoring, and orphan triage.
21
+
22
+ ---
23
+
24
+ ## Decision
25
+
26
+ ### The Four Definitions
27
+
28
+ #### 1. Flow Definition = the WHAT
29
+
30
+ A Flow Definition is a process contract: steps, gates, expected evidence, and definition-of-done. It is the container for *what* a workflow accomplishes, not *how* the agent does it.
31
+
32
+ - Flow Definitions are owned and validated by `kontourai/flow`.
33
+ - They are agentless-capable: a CI job or a human can satisfy a flow without an agent, as proven by the agentless gate-eval work (issues #52 and #60).
34
+ - Flow Agents *consumes* Flow Definitions; it does not own them (ADR 0001).
35
+
36
+ #### 2. Skill = the HOW = the *do* of a specific flow step
37
+
38
+ A skill is the agent's procedural method for accomplishing one step or gate of a flow. Brian's framing: *"skills are the do part of the flow definition."*
39
+
40
+ Consequences of this definition:
41
+
42
+ - There is exactly **one kind of skill** in the intended design.
43
+ - Every skill is bound to a flow step: it implements the agent's side of that step.
44
+ - Every skill belongs to the kit that owns that flow.
45
+ - A skill with no flow step behind it is not a skill in this model — it is either a tool, an orphan, or evidence of a missing flow.
46
+
47
+ The earlier informal "general capability skill tier" was an accreted artifact, not a designed concept. It is not a peer category to Kit-skills. Skills in that tier are reclassified as tools, orphans, or — where a missing flow is strongly implied — candidates for a future kit.
48
+
49
+ #### 3. Tool = a raw capability the agent wields
50
+
51
+ A tool is something the agent *uses* to do the work: run bash, read/write files, drive a browser, call `gh`, look up packages, search the web. Tools are:
52
+
53
+ - Provided by the runtime or harness, not tied to any flow.
54
+ - The agent's *hands*, not its *method*.
55
+ - Distinct from a skill even when the agent wraps a tool call in a named skill file — if the "skill" is just "here is how to invoke this capability," it is a tool description, not a flow-step method.
56
+
57
+ A skill that only describes how to use a tool (e.g., `github-cli`, `browser-test`, `eval-rebuild`) belongs in harness documentation or an agent system prompt, not in the `skills/` directory as a first-class skill.
58
+
59
+ #### 4. Kit = packages a flow with its skills
60
+
61
+ A kit:
62
+
63
+ - Owns one or more Flow Definitions.
64
+ - Provides the agent-side skills that implement those flows' steps.
65
+ - May include store adapters, evals, and docs.
66
+
67
+ A kit's skills are the agent-side implementation of that kit's flows, in a one-to-one relationship between skills and flow steps (or a small cluster of closely related steps).
68
+
69
+ ### Core Rule
70
+
71
+ > **A skill that has no flow step behind it is not a Kit-skill.** It is either:
72
+ > - (a) a **TOOL** mislabeled as a skill — it describes a raw capability and should live in harness docs or an agent system prompt, or
73
+ > - (b) an **ORPHAN** — it is procedural and agent-driven but the flow that would own its step has not been defined yet; it signals a missing or implicit flow, or
74
+ > - (c) **scope drift** — it does not belong in this repo's skill layer at all.
75
+
76
+ ### Cross-Kit Skill Sharing: DEFERRED
77
+
78
+ A question arose during this discussion: can a skill be shared across kits? For example, `knowledge-capture` is implemented as a Knowledge Kit skill (`knowledge.ingest:capture`) but is used as a support step inside Builder Kit flows (e.g., `learning-review` calls `knowledge-capture`).
79
+
80
+ The sharing model under active consideration is an npm-dependency model: Kit B declares a peer dependency on Kit A and invokes Kit A's skills as a consumer, without absorbing them into Kit B's skill list.
81
+
82
+ **This alternative is not adopted now.** The cross-kit sharing question is deferred. Until it is resolved:
83
+
84
+ - Each skill belongs to exactly one kit.
85
+ - When a skill is consumed across kits, the consumer kit calls the skill by reference; it does not duplicate or re-own it.
86
+ - The audit table in the companion document (`0007-skill-audit.md`) reflects the intended single-kit ownership for each skill.
87
+
88
+ ---
89
+
90
+ ## Consequences
91
+
92
+ ### Immediate Structural Clarity
93
+
94
+ - The `skills/` directory contains a mix of Kit-skills, tools, and orphans. The audit table in `docs/adr/0007-skill-audit.md` maps each skill to one of those three classifications.
95
+ - Skills that are tools (`agentic-engineering`, `browser-test`, `dependency-update`, `eval-rebuild`, `github-cli`, `search-first`) should eventually migrate to agent system prompts, harness context, or be removed from `skills/` entirely. No move is made in this ADR.
96
+ - Orphans (`context-budget`, `explore`, `feedback-loop`, `frontend-design`) each implied either a missing flow or a reclassification. All four were ruled on 2026-06-15 (see "Orphan Rulings — 2026-06-15" below); all are REMOVED, with preserved intents recorded.
97
+
98
+ ### Builder Kit Fix (Issue #62) Becomes Mechanical
99
+
100
+ With this model, the Builder Kit fix discussed in issue #62 is straightforward: every skill in `skills/` that maps to `builder.build` or `builder.shape` steps belongs in the Builder Kit. The audit table provides the exact step-to-skill mapping. The fix is to move those skills into `kits/builder/` — but that move is explicitly out of scope for this ADR. This ADR provides the analysis that makes the move mechanical.
101
+
102
+ ### Tools Are Flow-Agnostic
103
+
104
+ Runtime-provided tools (`gh`, Playwright, bash, file read/write, package registries) are not skills. They do not belong to flows. The agent wields them as hands while executing flow steps. Packaging them as skills creates false parallelism with Kit-skills and inflates the skill list.
105
+
106
+ ### Orphan Triage Protocol
107
+
108
+ For each orphan, one of these outcomes is required:
109
+
110
+ 1. **Define the missing flow.** If the orphan describes a coherent procedural method that deserves a kit, define the flow, create the kit, and move the skill.
111
+ 2. **Reclassify as a tool.** If the orphan is really a description of how to use a raw capability, remove it from `skills/` and fold its content into agent context or harness docs.
112
+ 3. **Accept scope drift.** If the orphan does not belong in this repo's skill layer, remove it.
113
+
114
+ No orphan should remain permanently in `skills/` without a documented triage outcome.
115
+
116
+ ### Orphan Rulings — 2026-06-15
117
+
118
+ Brian ruled on all four orphans on 2026-06-15. All four are **REMOVED** for now; preserved intents are recorded below so the rationale is not lost.
119
+
120
+ | Orphan Skill | Ruling | Preserved Intent |
121
+ | --- | --- | --- |
122
+ | `explore` | **REMOVED** — reclassified as a tool: parallel codebase-reading capability. | The original aim was multi-angle codebase capture (dependencies, security, runnability/testability, business logic, patterns). This preserved intent is the seed of a possible future `codebase-onboarding` flow if that capability is ever wanted as a first-class kit flow. |
123
+ | `feedback-loop` | **REMOVED** — subsumed. Its "did the change work locally" concern is now handled by `verify-work` plus the flow route-back capability. | No separate preserved intent; the concern is covered. |
124
+ | `context-budget` | **REMOVED** — agent self-maintenance; relates conceptually to `learning-review`. Not a flow-step skill. | Conceptually adjacent to `learning-review`; could inform a future self-maintenance flow, but is not a flow-step skill under the current model. |
125
+ | `frontend-design` | **REMOVED** for now. | "Plan-work but for UI" — the seed of a possible future UI/Frontend Kit with design + visual-verify steps. Revisit if a UI kit is ever built. |
126
+
127
+ These rulings do not change this ADR's status. The ADR remains **Proposed** pending Brian's separate confirmation of the whole document.
128
+
129
+ ### Knowledge Kit Boundary
130
+
131
+ `knowledge-capture` is the one skill in `skills/` that belongs to the Knowledge Kit rather than the Builder Kit. Its canonical flow is `knowledge.ingest:capture`. Its presence in the top-level `skills/` directory (rather than in `kits/knowledge/`) is itself a consequence of the pre-ADR undifferentiated structure. Future kit authoring should place kit skills inside their kit directory.
132
+
133
+ ### Audit Table as Evidence
134
+
135
+ The companion document `docs/adr/0007-skill-audit.md` is the authoritative mapping of every skill in `skills/` to this model. It provides the evidence base for Builder Kit issue #62 and any future kit migrations.
136
+
137
+ ---
138
+
139
+ ## Alternatives Considered
140
+
141
+ ### Keep the "General Capability Skill Tier" as a Peer Category
142
+
143
+ Rejected. The general capability tier was never designed — it accreted. Treating it as a first-class category alongside Kit-skills would institutionalize an accident. The model is simpler with exactly one kind of skill.
144
+
145
+ ### Allow Skills to Be Defined Without Flow Binding
146
+
147
+ Rejected. The whole value of the model is that a skill's purpose, ownership, and location are derivable from its flow step. A skill with no flow binding is undefined in the model — it either needs a flow or needs to be reclassified.
148
+
149
+ ### Cross-Kit Skill Sharing via npm Dependency (DEFERRED)
150
+
151
+ Not rejected but not adopted now. The npm-dependency model for cross-kit sharing is the most likely future path if skills need to be consumed across kits. It is deferred until a concrete cross-kit case requires it. See "Cross-Kit Skill Sharing: DEFERRED" above.
152
+
153
+ ### Move All Tool-like Skills to Agent System Prompts Immediately
154
+
155
+ Deferred. The tool-like skills in `skills/` have runtime users. Moving them without updating agent specs and evals would break existing behavior. This ADR records the intended direction; the migrations should be sequenced through backlog issues.
156
+
157
+ ---
158
+
159
+ ## References
160
+
161
+ - [ADR 0001: Flow Agents Consumes Flow For Workflow Enforcement](./0001-flow-agents-consumes-flow.md) — establishes that Flow owns Flow Definitions and Flow Agents consumes them.
162
+ - [ADR 0002: Flow Kits as Extension Unit](./0002-flow-kits-as-extension-unit.md) — establishes the kit as the packaging unit.
163
+ - [ADR 0003: Flow Agents Coordinates Kits and Adapters](./0003-flow-agents-coordinates-kits-and-adapters.md) — establishes how Flow Agents relates to kits.
164
+ - [docs/adr/0007-skill-audit.md](./0007-skill-audit.md) — companion skill audit table.
165
+ - `kits/builder/flows/build.flow.json` — Builder Kit build flow step IDs.
166
+ - `kits/builder/flows/shape.flow.json` — Builder Kit shape flow step IDs.
167
+ - `kits/knowledge/flows/ingest.flow.json` — Knowledge Kit ingest flow step IDs.
168
+ - GitHub issue #62 — Builder Kit skill placement fix (becomes mechanical given this model).
169
+ - GitHub issues #52 and #60 — agentless gate-eval work proving Flow Definitions are agentless-capable.