@kontourai/flow-agents 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (418) hide show
  1. package/.githooks/pre-push +11 -0
  2. package/.github/workflows/ci.yml +210 -0
  3. package/.github/workflows/docs-pages.yml +52 -0
  4. package/.github/workflows/publish-npm.yml +104 -0
  5. package/AGENTS.md +26 -0
  6. package/CHANGELOG.md +66 -0
  7. package/CODE_OF_CONDUCT.md +25 -0
  8. package/CONTEXT.md +300 -0
  9. package/CONTRIBUTING.md +44 -0
  10. package/LICENSE +201 -0
  11. package/README.md +129 -0
  12. package/SECURITY.md +33 -0
  13. package/agent-cards/dev.json +19 -0
  14. package/agents/dev.json +127 -0
  15. package/agents/tool-code-reviewer.json +61 -0
  16. package/agents/tool-dependencies-updater.json +118 -0
  17. package/agents/tool-explore-config.json +92 -0
  18. package/agents/tool-explore-deps.json +92 -0
  19. package/agents/tool-explore-entry.json +92 -0
  20. package/agents/tool-explore-patterns.json +92 -0
  21. package/agents/tool-explore-structure.json +92 -0
  22. package/agents/tool-explore-tests.json +92 -0
  23. package/agents/tool-planner.json +57 -0
  24. package/agents/tool-playwright.json +145 -0
  25. package/agents/tool-security-reviewer.json +56 -0
  26. package/agents/tool-verifier.json +61 -0
  27. package/agents/tool-worker.json +58 -0
  28. package/build/src/cli/console-learning-projection.js +123 -0
  29. package/build/src/cli/docs-preview.js +39 -0
  30. package/build/src/cli/effective-backlog-settings.js +102 -0
  31. package/build/src/cli/export-bookmarks.js +38 -0
  32. package/build/src/cli/fixture-retirement-audit.js +140 -0
  33. package/build/src/cli/flow-kit.js +138 -0
  34. package/build/src/cli/import-bookmarks.js +50 -0
  35. package/build/src/cli/init.js +239 -0
  36. package/build/src/cli/instinct-cli.js +93 -0
  37. package/build/src/cli/promote-workflow-artifact.js +63 -0
  38. package/build/src/cli/publish-change-helper.js +154 -0
  39. package/build/src/cli/pull-work-provider.js +469 -0
  40. package/build/src/cli/runtime-adapter.js +23 -0
  41. package/build/src/cli/telemetry-doctor.js +221 -0
  42. package/build/src/cli/usage-feedback.js +443 -0
  43. package/build/src/cli/validate-hook-influence.js +152 -0
  44. package/build/src/cli/validate-source-tree.js +31 -0
  45. package/build/src/cli/validate-workflow-artifacts.js +486 -0
  46. package/build/src/cli/veritas-governance.js +262 -0
  47. package/build/src/cli/workflow-artifact-cleanup-audit.js +272 -0
  48. package/build/src/cli/workflow-sidecar.js +816 -0
  49. package/build/src/cli.js +89 -0
  50. package/build/src/flow-kit/validate.js +75 -0
  51. package/build/src/lib/args.js +45 -0
  52. package/build/src/lib/fs.js +62 -0
  53. package/build/src/lib/workflow-learning-projection.js +334 -0
  54. package/build/src/runtime-adapters.js +146 -0
  55. package/build/src/tools/build-universal-bundles.js +397 -0
  56. package/build/src/tools/common.js +56 -0
  57. package/build/src/tools/filter-installed-packs.js +132 -0
  58. package/build/src/tools/generate-context-map.js +198 -0
  59. package/build/src/tools/validate-package.js +64 -0
  60. package/build/src/tools/validate-source-tree.js +622 -0
  61. package/console.telemetry.json +176 -0
  62. package/context/base-rules.md +17 -0
  63. package/context/code-review-standards.md +62 -0
  64. package/context/coding-standards.md +42 -0
  65. package/context/common/orchestrators.md +12 -0
  66. package/context/common/subagents.md +28 -0
  67. package/context/contracts/artifact-contract.md +182 -0
  68. package/context/contracts/builder-kit-workflow-state-contract.md +319 -0
  69. package/context/contracts/delivery-contract.md +69 -0
  70. package/context/contracts/execution-contract.md +53 -0
  71. package/context/contracts/governance-adapter-contract.md +67 -0
  72. package/context/contracts/planning-contract.md +85 -0
  73. package/context/contracts/review-contract.md +104 -0
  74. package/context/contracts/sandbox-policy.md +52 -0
  75. package/context/contracts/verification-contract.md +134 -0
  76. package/context/contracts/work-item-contract.md +215 -0
  77. package/context/deferred/demo-mode.md +33 -0
  78. package/context/deferred/languages/go.md +31 -0
  79. package/context/deferred/languages/python.md +31 -0
  80. package/context/deferred/languages/typescript.md +34 -0
  81. package/context/deferred/parallelization.md +35 -0
  82. package/context/deferred/worktree-isolation.md +24 -0
  83. package/context/development-workflow.md +50 -0
  84. package/context/scripts/context-budget/budget-scan.sh +166 -0
  85. package/context/scripts/detect-tools.sh +3 -0
  86. package/context/scripts/discover-agents.sh +28 -0
  87. package/context/scripts/git-status.sh +49 -0
  88. package/context/scripts/hooks/config-protection.js +79 -0
  89. package/context/scripts/hooks/desktop-notify.sh +39 -0
  90. package/context/scripts/hooks/governance-audit.sh +135 -0
  91. package/context/scripts/hooks/lib/audit-transport.sh +40 -0
  92. package/context/scripts/hooks/lib/hook-flags.js +49 -0
  93. package/context/scripts/hooks/lib/patterns.sh +57 -0
  94. package/context/scripts/hooks/lib/resolve-formatter.js +80 -0
  95. package/context/scripts/hooks/post-edit-accumulator.js +66 -0
  96. package/context/scripts/hooks/pre-commit-quality.js +194 -0
  97. package/context/scripts/hooks/quality-gate.js +93 -0
  98. package/context/scripts/hooks/report-only-guard.js +21 -0
  99. package/context/scripts/hooks/run-hook.js +136 -0
  100. package/context/scripts/hooks/stop-format-typecheck.js +141 -0
  101. package/context/scripts/hooks/stop-goal-fit.js +337 -0
  102. package/context/scripts/hooks/workflow-steering.js +250 -0
  103. package/context/scripts/telemetry/console-presets.sh +14 -0
  104. package/context/scripts/telemetry/install-console-config.sh +214 -0
  105. package/context/scripts/telemetry/lib/config.sh +85 -0
  106. package/context/scripts/telemetry/lib/enrich.sh +115 -0
  107. package/context/scripts/telemetry/lib/redact.sh +22 -0
  108. package/context/scripts/telemetry/lib/session.sh +63 -0
  109. package/context/scripts/telemetry/lib/transport.sh +183 -0
  110. package/context/scripts/telemetry/lib/usage.sh +29 -0
  111. package/context/scripts/telemetry/sync-agents.sh +173 -0
  112. package/context/scripts/telemetry/telemetry.conf +23 -0
  113. package/context/scripts/telemetry/telemetry.sh +387 -0
  114. package/context/scripts/validate-package.sh +89 -0
  115. package/context/settings/backlog-provider-settings.json +54 -0
  116. package/context/templates/core/identity.md +26 -0
  117. package/context/templates/core/user.md +15 -0
  118. package/docs/_config.yml +15 -0
  119. package/docs/_layouts/default.html +87 -0
  120. package/docs/adr/0001-flow-agents-consumes-flow.md +77 -0
  121. package/docs/adr/0002-flow-kits-as-extension-unit.md +13 -0
  122. package/docs/adr/0003-flow-agents-coordinates-kits-and-adapters.md +13 -0
  123. package/docs/adr/0004-gates-expect-surface-claims.md +15 -0
  124. package/docs/adr/0005-kubernetes-inspired-resource-contracts.md +48 -0
  125. package/docs/adr/0006-typescript-first-source-policy.md +98 -0
  126. package/docs/agent-system-guidebook.md +391 -0
  127. package/docs/agent-usage-feedback-loop.md +351 -0
  128. package/docs/assets/favicon.svg +13 -0
  129. package/docs/assets/og-image.png +0 -0
  130. package/docs/assets/site.css +774 -0
  131. package/docs/assets/site.js +139 -0
  132. package/docs/configurable-workflow-routing.md +174 -0
  133. package/docs/context-map.md +145 -0
  134. package/docs/developer-architecture.md +145 -0
  135. package/docs/developer-hook-setup.md +61 -0
  136. package/docs/fixture-ownership.md +44 -0
  137. package/docs/flow-kit-repository-contract.md +180 -0
  138. package/docs/index.md +129 -0
  139. package/docs/kontour-resource-contract.md +358 -0
  140. package/docs/migrations.md +64 -0
  141. package/docs/north-star.md +322 -0
  142. package/docs/operating-layers.md +110 -0
  143. package/docs/repository-structure.md +132 -0
  144. package/docs/sandbox-policy.md +56 -0
  145. package/docs/skills-map.md +203 -0
  146. package/docs/standards-register.md +96 -0
  147. package/docs/veritas-integration.md +165 -0
  148. package/docs/work-item-adapters.md +72 -0
  149. package/docs/workflow-artifact-lifecycle.md +141 -0
  150. package/docs/workflow-eval-strategy.md +295 -0
  151. package/docs/workflow-shared-contracts.md +51 -0
  152. package/docs/workflow-usage-guide.md +443 -0
  153. package/evals/ARCHITECTURE.md +143 -0
  154. package/evals/CONVENTIONS.md +58 -0
  155. package/evals/README.md +128 -0
  156. package/evals/acceptance/run.sh +29 -0
  157. package/evals/acceptance/test_claude_harness.sh +242 -0
  158. package/evals/acceptance/test_codex_harness.sh +108 -0
  159. package/evals/acceptance/test_kiro_harness.sh +128 -0
  160. package/evals/cases/dev/404.html +97 -0
  161. package/evals/cases/dev/code-review.yaml +44 -0
  162. package/evals/cases/dev/dashboard.html +300 -0
  163. package/evals/cases/dev/deliver.yaml +66 -0
  164. package/evals/cases/dev/dependency-update.yaml +16 -0
  165. package/evals/cases/dev/explore.yaml +20 -0
  166. package/evals/cases/dev/index.html +370 -0
  167. package/evals/cases/dev/package-lock.json +28 -0
  168. package/evals/cases/dev/package.json +16 -0
  169. package/evals/cases/dev/plan-work.yaml +20 -0
  170. package/evals/cases/dev/promptfooconfig.yaml +666 -0
  171. package/evals/cases/dev/search-first.yaml +20 -0
  172. package/evals/cases/dev/tdd-workflow.yaml +48 -0
  173. package/evals/cases/dev/verify-work.yaml +44 -0
  174. package/evals/cases/dev/workflow.yaml +34 -0
  175. package/evals/ci/run-baseline.sh +283 -0
  176. package/evals/fixtures/backlog-provider-settings/global-default.json +44 -0
  177. package/evals/fixtures/backlog-provider-settings/project-override.json +53 -0
  178. package/evals/fixtures/builder-kit-workflow-state/baseline-freshness-resolution-hint.json +139 -0
  179. package/evals/fixtures/builder-kit-workflow-state/direct-primitive-stop.json +59 -0
  180. package/evals/fixtures/builder-kit-workflow-state/empty-board-route-shape.json +55 -0
  181. package/evals/fixtures/builder-kit-workflow-state/happy-path.json +71 -0
  182. package/evals/fixtures/builder-kit-workflow-state/mid-work-resume.json +80 -0
  183. package/evals/fixtures/builder-kit-workflow-state/missing-prestep-recovery.json +65 -0
  184. package/evals/fixtures/builder-kit-workflow-state/product-build-chaining.json +60 -0
  185. package/evals/fixtures/builder-kit-workflow-state/stale-continuation-requires-new-probe.json +57 -0
  186. package/evals/fixtures/console-learning-projection/artifacts/console-learning-correction/learning.json +50 -0
  187. package/evals/fixtures/console-learning-projection/artifacts/console-learning-open-route/learning.json +41 -0
  188. package/evals/fixtures/flow-kit-repository/invalid-absolute-path/kit.json +8 -0
  189. package/evals/fixtures/flow-kit-repository/invalid-asset-section/flows/review.flow.json +6 -0
  190. package/evals/fixtures/flow-kit-repository/invalid-asset-section/kit.json +11 -0
  191. package/evals/fixtures/flow-kit-repository/invalid-duplicate-flow/flows/review.flow.json +6 -0
  192. package/evals/fixtures/flow-kit-repository/invalid-duplicate-flow/kit.json +9 -0
  193. package/evals/fixtures/flow-kit-repository/invalid-id/flows/review.flow.json +6 -0
  194. package/evals/fixtures/flow-kit-repository/invalid-id/kit.json +8 -0
  195. package/evals/fixtures/flow-kit-repository/invalid-malformed-json/kit.json +8 -0
  196. package/evals/fixtures/flow-kit-repository/invalid-missing-flow/kit.json +8 -0
  197. package/evals/fixtures/flow-kit-repository/invalid-missing-id/flows/review.flow.json +6 -0
  198. package/evals/fixtures/flow-kit-repository/invalid-missing-id/kit.json +7 -0
  199. package/evals/fixtures/flow-kit-repository/invalid-missing-schema-version/flows/review.flow.json +6 -0
  200. package/evals/fixtures/flow-kit-repository/invalid-missing-schema-version/kit.json +7 -0
  201. package/evals/fixtures/flow-kit-repository/invalid-name/flows/review.flow.json +6 -0
  202. package/evals/fixtures/flow-kit-repository/invalid-name/kit.json +8 -0
  203. package/evals/fixtures/flow-kit-repository/invalid-schema-version/flows/review.flow.json +6 -0
  204. package/evals/fixtures/flow-kit-repository/invalid-schema-version/kit.json +8 -0
  205. package/evals/fixtures/flow-kit-repository/invalid-traversal/kit.json +8 -0
  206. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/adapters/example.json +3 -0
  207. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/assets/example.txt +1 -0
  208. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/docs/README.md +3 -0
  209. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/flows/runtime.flow.json +26 -0
  210. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/kit-evals/example.json +3 -0
  211. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/kit-skills/mixed/SKILL.md +3 -0
  212. package/evals/fixtures/flow-kit-repository/mixed-runtime-kit/kit.json +44 -0
  213. package/evals/fixtures/flow-kit-repository/valid-local-kit/docs/README.md +3 -0
  214. package/evals/fixtures/flow-kit-repository/valid-local-kit/flows/review.flow.json +26 -0
  215. package/evals/fixtures/flow-kit-repository/valid-local-kit/kit.json +20 -0
  216. package/evals/fixtures/hook-influence/cases.json +336 -0
  217. package/evals/fixtures/pull-work-provider/github-issues.json +170 -0
  218. package/evals/fixtures/pull-work-wip-shepherding/global-wip-informs.json +43 -0
  219. package/evals/fixtures/pull-work-wip-shepherding/personal-wip-blocks.json +42 -0
  220. package/evals/fixtures/surface-trust/accepted-claim-trust-report.json +31 -0
  221. package/evals/fixtures/surface-trust/artifact-absent.json +19 -0
  222. package/evals/fixtures/surface-trust/integrity-mismatch-trust-report.json +32 -0
  223. package/evals/fixtures/surface-trust/missing-authority-trust-report.json +27 -0
  224. package/evals/fixtures/surface-trust/provider-absent.json +19 -0
  225. package/evals/fixtures/surface-trust/rejected-claim-trust-report.json +30 -0
  226. package/evals/fixtures/surface-trust/stale-claim-trust-snapshot.json +31 -0
  227. package/evals/fixtures/usage-feedback/sample-full.jsonl +11 -0
  228. package/evals/fixtures/usage-feedback/sample-outcomes.jsonl +1 -0
  229. package/evals/fixtures/veritas-governance-adapter/fake-veritas-pass.sh +18 -0
  230. package/evals/fixtures/veritas-governance-adapter/fake-veritas-secret-fail.sh +10 -0
  231. package/evals/fixtures/veritas-governance-adapter/fake-veritas-unconfigured.sh +4 -0
  232. package/evals/integration/test_bundle_install.sh +541 -0
  233. package/evals/integration/test_console_learning_projection.sh +192 -0
  234. package/evals/integration/test_context_map.sh +65 -0
  235. package/evals/integration/test_effective_backlog_settings.sh +58 -0
  236. package/evals/integration/test_fixture_retirement_audit.sh +58 -0
  237. package/evals/integration/test_flow_agents_statusline.sh +93 -0
  238. package/evals/integration/test_flow_kit_repository.sh +90 -0
  239. package/evals/integration/test_goal_fit_hook.sh +482 -0
  240. package/evals/integration/test_hook_category_behaviors.sh +190 -0
  241. package/evals/integration/test_hook_influence_cases.sh +69 -0
  242. package/evals/integration/test_local_flow_kit_install.sh +145 -0
  243. package/evals/integration/test_publish_change_helper.sh +176 -0
  244. package/evals/integration/test_pull_work_provider.sh +140 -0
  245. package/evals/integration/test_runtime_adapter_activation.sh +106 -0
  246. package/evals/integration/test_telemetry.sh +485 -0
  247. package/evals/integration/test_telemetry_doctor.sh +193 -0
  248. package/evals/integration/test_usage_feedback_dashboard.sh +169 -0
  249. package/evals/integration/test_usage_feedback_global.sh +117 -0
  250. package/evals/integration/test_usage_feedback_import.sh +227 -0
  251. package/evals/integration/test_usage_feedback_outcomes.sh +165 -0
  252. package/evals/integration/test_usage_feedback_report.sh +263 -0
  253. package/evals/integration/test_veritas_governance_adapter.sh +235 -0
  254. package/evals/integration/test_workflow_artifact_cleanup_audit.sh +287 -0
  255. package/evals/integration/test_workflow_artifacts.sh +1247 -0
  256. package/evals/integration/test_workflow_sidecar_writer.sh +2112 -0
  257. package/evals/integration/test_workflow_steering_hook.sh +337 -0
  258. package/evals/lib/assertions/delegated-to.js +40 -0
  259. package/evals/lib/assertions/max-tool-calls.js +15 -0
  260. package/evals/lib/assertions/no-write-tools.js +27 -0
  261. package/evals/lib/assertions/pass-at-k.js +39 -0
  262. package/evals/lib/assertions/telemetry-utils.js +105 -0
  263. package/evals/lib/assertions/tool-called.js +39 -0
  264. package/evals/lib/assertions/verify-after-fix.js +61 -0
  265. package/evals/lib/claude-judge.sh +40 -0
  266. package/evals/lib/claude-provider.sh +74 -0
  267. package/evals/lib/codex-judge.sh +39 -0
  268. package/evals/lib/codex-provider.sh +81 -0
  269. package/evals/lib/eval-dev.sh +5 -0
  270. package/evals/lib/eval-judge.sh +22 -0
  271. package/evals/lib/eval-provider.sh +26 -0
  272. package/evals/lib/eval-report.sh +73 -0
  273. package/evals/lib/kiro-dev.sh +4 -0
  274. package/evals/lib/kiro-judge.sh +17 -0
  275. package/evals/lib/kiro-provider.sh +62 -0
  276. package/evals/lib/node.sh +111 -0
  277. package/evals/promptfooconfig.yaml +70 -0
  278. package/evals/run.sh +309 -0
  279. package/evals/static/test_evidence_refs.sh +141 -0
  280. package/evals/static/test_package.sh +407 -0
  281. package/evals/static/test_repo_hooks.sh +68 -0
  282. package/evals/static/test_universal_bundles.sh +274 -0
  283. package/evals/static/test_workflow_skills.sh +1207 -0
  284. package/install.sh +64 -0
  285. package/integrations/veritas/flow-agents.adapter.json +138 -0
  286. package/integrations/veritas/flow-agents.authority-settings.json +26 -0
  287. package/integrations/veritas/flow-agents.repo-standards.json +82 -0
  288. package/kits/builder/flows/build.flow.json +218 -0
  289. package/kits/builder/flows/shape.flow.json +127 -0
  290. package/kits/builder/kit.json +19 -0
  291. package/kits/catalog.json +11 -0
  292. package/package.json +130 -0
  293. package/packaging/README.md +60 -0
  294. package/packaging/manifest.json +173 -0
  295. package/packaging/packs.json +69 -0
  296. package/powers/dependency-checker/POWER.md +20 -0
  297. package/powers/dependency-checker/mcp.json +20 -0
  298. package/powers/playwright/POWER.md +25 -0
  299. package/powers/playwright/mcp.json +12 -0
  300. package/prompts/code-audit.md +123 -0
  301. package/prompts/kcommit.md +88 -0
  302. package/schemas/backlog-provider-settings.schema.json +138 -0
  303. package/schemas/workflow-acceptance.schema.json +216 -0
  304. package/schemas/workflow-critique.schema.json +113 -0
  305. package/schemas/workflow-evidence.schema.json +357 -0
  306. package/schemas/workflow-handoff.schema.json +52 -0
  307. package/schemas/workflow-learning.schema.json +223 -0
  308. package/schemas/workflow-release.schema.json +172 -0
  309. package/schemas/workflow-state.schema.json +80 -0
  310. package/scripts/README.md +111 -0
  311. package/scripts/build-universal-bundles.js +3 -0
  312. package/scripts/check-content-boundary.cjs +99 -0
  313. package/scripts/context-budget/budget-scan.sh +166 -0
  314. package/scripts/detect-tools.sh +3 -0
  315. package/scripts/discover-agents.sh +28 -0
  316. package/scripts/effective-backlog-settings.js +2 -0
  317. package/scripts/filter-installed-packs.js +2 -0
  318. package/scripts/flow-kit.js +2 -0
  319. package/scripts/generate-context-map.js +2 -0
  320. package/scripts/git-status.sh +49 -0
  321. package/scripts/hooks/claude-hook-adapter.js +174 -0
  322. package/scripts/hooks/claude-telemetry-hook.js +115 -0
  323. package/scripts/hooks/codex-hook-adapter.js +176 -0
  324. package/scripts/hooks/codex-telemetry-hook.js +95 -0
  325. package/scripts/hooks/config-protection.js +79 -0
  326. package/scripts/hooks/desktop-notify.sh +39 -0
  327. package/scripts/hooks/governance-audit.sh +135 -0
  328. package/scripts/hooks/lib/audit-transport.sh +40 -0
  329. package/scripts/hooks/lib/hook-flags.js +49 -0
  330. package/scripts/hooks/lib/patterns.sh +57 -0
  331. package/scripts/hooks/lib/resolve-formatter.js +80 -0
  332. package/scripts/hooks/post-edit-accumulator.js +66 -0
  333. package/scripts/hooks/pre-commit-quality.js +194 -0
  334. package/scripts/hooks/quality-gate.js +93 -0
  335. package/scripts/hooks/report-only-guard.js +21 -0
  336. package/scripts/hooks/run-hook.js +136 -0
  337. package/scripts/hooks/stop-format-typecheck.js +141 -0
  338. package/scripts/hooks/stop-goal-fit.js +337 -0
  339. package/scripts/hooks/workflow-steering.js +250 -0
  340. package/scripts/install-codex-home.sh +106 -0
  341. package/scripts/package.json +3 -0
  342. package/scripts/promote-workflow-artifact.js +2 -0
  343. package/scripts/publish-change-helper.js +2 -0
  344. package/scripts/pull-work-provider.js +2 -0
  345. package/scripts/setup-repo-hooks.sh +8 -0
  346. package/scripts/statusline/flow-agents-statusline.js +157 -0
  347. package/scripts/telemetry/console-presets.sh +14 -0
  348. package/scripts/telemetry/install-console-config.sh +214 -0
  349. package/scripts/telemetry/lib/config.sh +85 -0
  350. package/scripts/telemetry/lib/enrich.sh +115 -0
  351. package/scripts/telemetry/lib/redact.sh +22 -0
  352. package/scripts/telemetry/lib/session.sh +63 -0
  353. package/scripts/telemetry/lib/transport.sh +183 -0
  354. package/scripts/telemetry/lib/usage.sh +29 -0
  355. package/scripts/telemetry/sync-agents.sh +173 -0
  356. package/scripts/telemetry/telemetry.conf +23 -0
  357. package/scripts/telemetry/telemetry.sh +387 -0
  358. package/scripts/usage-feedback.js +2 -0
  359. package/scripts/validate-hook-influence-cases.js +2 -0
  360. package/scripts/validate-package.sh +89 -0
  361. package/scripts/validate-source-tree.js +9 -0
  362. package/skills/agentic-engineering/SKILL.md +62 -0
  363. package/skills/browser-test/SKILL.md +51 -0
  364. package/skills/builder-shape/SKILL.md +76 -0
  365. package/skills/context-budget/SKILL.md +40 -0
  366. package/skills/deliver/SKILL.md +241 -0
  367. package/skills/dependency-update/SKILL.md +68 -0
  368. package/skills/design-probe/SKILL.md +107 -0
  369. package/skills/eval-rebuild/SKILL.md +39 -0
  370. package/skills/evidence-gate/SKILL.md +186 -0
  371. package/skills/execute-plan/SKILL.md +110 -0
  372. package/skills/explore/SKILL.md +137 -0
  373. package/skills/feedback-loop/SKILL.md +87 -0
  374. package/skills/fix-bug/SKILL.md +133 -0
  375. package/skills/frontend-design/SKILL.md +80 -0
  376. package/skills/github-cli/SKILL.md +63 -0
  377. package/skills/idea-to-backlog/SKILL.md +267 -0
  378. package/skills/knowledge-capture/SKILL.md +55 -0
  379. package/skills/learning-review/SKILL.md +115 -0
  380. package/skills/pickup-probe/SKILL.md +114 -0
  381. package/skills/plan-work/SKILL.md +176 -0
  382. package/skills/pull-work/SKILL.md +309 -0
  383. package/skills/release-readiness/SKILL.md +121 -0
  384. package/skills/review-work/SKILL.md +161 -0
  385. package/skills/search-first/SKILL.md +66 -0
  386. package/skills/tdd-workflow/SKILL.md +140 -0
  387. package/skills/verify-work/SKILL.md +109 -0
  388. package/src/cli/console-learning-projection.ts +140 -0
  389. package/src/cli/effective-backlog-settings.ts +99 -0
  390. package/src/cli/fixture-retirement-audit.ts +154 -0
  391. package/src/cli/flow-kit.ts +139 -0
  392. package/src/cli/init.ts +248 -0
  393. package/src/cli/promote-workflow-artifact.ts +64 -0
  394. package/src/cli/publish-change-helper.ts +143 -0
  395. package/src/cli/pull-work-provider.ts +481 -0
  396. package/src/cli/runtime-adapter.ts +24 -0
  397. package/src/cli/telemetry-doctor.ts +243 -0
  398. package/src/cli/usage-feedback.ts +418 -0
  399. package/src/cli/validate-hook-influence.ts +119 -0
  400. package/src/cli/validate-source-tree.ts +30 -0
  401. package/src/cli/validate-workflow-artifacts.ts +411 -0
  402. package/src/cli/veritas-governance.ts +322 -0
  403. package/src/cli/workflow-artifact-cleanup-audit.ts +281 -0
  404. package/src/cli/workflow-sidecar.ts +676 -0
  405. package/src/cli.ts +95 -0
  406. package/src/flow-kit/validate.ts +74 -0
  407. package/src/lib/args.ts +43 -0
  408. package/src/lib/fs.ts +62 -0
  409. package/src/lib/workflow-learning-projection.ts +491 -0
  410. package/src/runtime-adapters.ts +154 -0
  411. package/src/tools/build-universal-bundles.ts +366 -0
  412. package/src/tools/common.ts +61 -0
  413. package/src/tools/filter-installed-packs.ts +129 -0
  414. package/src/tools/generate-context-map.ts +199 -0
  415. package/src/tools/validate-package.ts +57 -0
  416. package/src/tools/validate-source-tree.ts +488 -0
  417. package/tsconfig.json +19 -0
  418. package/veritas.claims.json +6 -0
@@ -0,0 +1,243 @@
1
+ import * as fs from "node:fs";
2
+ import * as http from "node:http";
3
+ import * as https from "node:https";
4
+ import * as path from "node:path";
5
+ import { parseArgs, flagBool, flagString } from "../lib/args.js";
6
+
7
+ type Config = Record<string, string>;
8
+
9
+ type Reachability = {
10
+ checked: boolean;
11
+ ok: boolean | null;
12
+ statusCode?: number;
13
+ error?: string;
14
+ };
15
+
16
+ type DoctorReport = {
17
+ ok: boolean;
18
+ destination: string;
19
+ telemetry: {
20
+ configFile: string;
21
+ configExists: boolean;
22
+ enabled: boolean;
23
+ dataDir: string;
24
+ sessionDir: string;
25
+ channels: Array<{ name: string; logFile?: string; endpointUrl?: string; active: boolean }>;
26
+ activeSinks: string[];
27
+ };
28
+ console: {
29
+ sink: "local-only" | "console";
30
+ url?: string;
31
+ endpointUrl?: string;
32
+ endpointAllowed: boolean;
33
+ tokenConfigured: boolean;
34
+ tenantConfigured: boolean;
35
+ reachability: Reachability;
36
+ };
37
+ warnings: string[];
38
+ };
39
+
40
+ const defaultChannels = "full";
41
+ const sensitiveQueryKeys = new Set(["token", "api_key", "apikey", "key", "secret", "password", "auth", "authorization", "access_token"]);
42
+
43
+ function usage(): void {
44
+ console.error(`usage: flow-agents telemetry-doctor [options]
45
+
46
+ Options:
47
+ --dest PATH Installed Flow Agents root. Defaults to current directory.
48
+ --json Emit machine-readable JSON.
49
+ --headless Do not prompt; suitable for CI.
50
+ --timeout-ms N Console reachability timeout. Defaults to 2000.
51
+ --allow-network Allow reachability checks to non-local HTTPS Console hosts.
52
+ `);
53
+ }
54
+
55
+ function readConfig(file: string): Config {
56
+ if (!fs.existsSync(file)) return {};
57
+ const config: Config = {};
58
+ for (const line of fs.readFileSync(file, "utf8").split(/\r?\n/)) {
59
+ const trimmed = line.trim();
60
+ if (!trimmed || trimmed.startsWith("#")) continue;
61
+ const eq = trimmed.indexOf("=");
62
+ if (eq === -1) continue;
63
+ const key = trimmed.slice(0, eq).trim();
64
+ const value = trimmed.slice(eq + 1).trim();
65
+ if (key) config[key] = value;
66
+ }
67
+ return config;
68
+ }
69
+
70
+ function configValue(config: Config, envName: string, key: string, fallback = ""): string {
71
+ return process.env[envName] ?? config[key] ?? fallback;
72
+ }
73
+
74
+ function channelEnvName(channel: string, key: string): string {
75
+ return `TELEMETRY_CHANNEL_${channel.toUpperCase()}_${key.toUpperCase()}`;
76
+ }
77
+
78
+ function channelConfigValue(config: Config, channel: string, key: string, fallback = ""): string {
79
+ const envName = channelEnvName(channel, key);
80
+ return process.env[envName] ?? config[`channel.${channel}.${key}`] ?? fallback;
81
+ }
82
+
83
+ function telemetryDataDir(dest: string): string {
84
+ const configured = process.env.TELEMETRY_DATA_DIR;
85
+ return configured ? path.resolve(dest, configured) : path.resolve(dest, "..", ".telemetry");
86
+ }
87
+
88
+ function deriveConsoleEndpoint(consoleUrl: string, explicitEndpoint: string): string {
89
+ if (explicitEndpoint) return explicitEndpoint;
90
+ if (!consoleUrl) return "";
91
+ const base = consoleUrl.replace(/\/+$/, "");
92
+ if (base.endsWith("/api/telemetry/records")) return base;
93
+ if (base.endsWith("/api/telemetry")) return `${base}/records`;
94
+ return `${base}/api/telemetry/records`;
95
+ }
96
+
97
+ function endpointAllowed(endpointUrl: string, allowNetwork = false): boolean {
98
+ if (!endpointUrl || endpointUrl.includes("\n") || endpointUrl.includes("\r") || endpointUrl.includes('"')) return false;
99
+ const url = parseUrl(endpointUrl);
100
+ if (!url) return false;
101
+ if (url.username || url.password) return false;
102
+ if (url.protocol === "https:") return allowNetwork || isLocalHostname(url.hostname);
103
+ return url.protocol === "http:" && isLocalHostname(url.hostname);
104
+ }
105
+
106
+ function activeSinks(enabled: boolean, channels: DoctorReport["telemetry"]["channels"], consoleEndpoint: string, allowed: boolean): string[] {
107
+ const sinks: string[] = [];
108
+ if (enabled && channels.some((channel) => channel.active && channel.logFile)) sinks.push("local-files");
109
+ if (enabled && channels.some((channel) => channel.active && channel.endpointUrl)) sinks.push("channel-endpoint");
110
+ if (enabled && consoleEndpoint && allowed) sinks.push("console");
111
+ return sinks;
112
+ }
113
+
114
+ function checkConsoleReachability(endpointUrl: string, timeoutMs: number, allowNetwork: boolean): Promise<Reachability> {
115
+ if (!endpointUrl) return Promise.resolve({ checked: false, ok: null });
116
+ if (!endpointAllowed(endpointUrl, allowNetwork)) return Promise.resolve({ checked: false, ok: null, error: "endpoint is not allowed" });
117
+ const url = parseUrl(endpointUrl);
118
+ if (!url) return Promise.resolve({ checked: false, ok: null, error: "endpoint URL is malformed" });
119
+ return new Promise((resolve) => {
120
+ let settled = false;
121
+ const client = url.protocol === "https:" ? https : http;
122
+ const req = client.request(url, { method: "HEAD", timeout: timeoutMs }, (res) => {
123
+ settled = true;
124
+ res.resume();
125
+ resolve({ checked: true, ok: Boolean(res.statusCode && res.statusCode < 500), statusCode: res.statusCode });
126
+ });
127
+ req.on("timeout", () => {
128
+ if (settled) return;
129
+ settled = true;
130
+ req.destroy();
131
+ resolve({ checked: true, ok: false, error: `timeout after ${timeoutMs}ms` });
132
+ });
133
+ req.on("error", (error) => {
134
+ if (settled) return;
135
+ settled = true;
136
+ resolve({ checked: true, ok: false, error: error.message });
137
+ });
138
+ req.end();
139
+ });
140
+ }
141
+
142
+ function parseUrl(value: string): URL | null {
143
+ try {
144
+ return new URL(value);
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ function isLocalHostname(hostname: string): boolean {
151
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost");
152
+ }
153
+
154
+ function safeReportUrl(value: string): string | undefined {
155
+ if (!value) return undefined;
156
+ const parsed = parseUrl(value);
157
+ if (!parsed) return "[malformed-url]";
158
+ parsed.username = "";
159
+ parsed.password = "";
160
+ for (const key of Array.from(parsed.searchParams.keys())) {
161
+ if (sensitiveQueryKeys.has(key.toLowerCase())) parsed.searchParams.set(key, "[redacted]");
162
+ }
163
+ return parsed.toString();
164
+ }
165
+
166
+ async function buildReport(argv: string[]): Promise<DoctorReport> {
167
+ const args = parseArgs(argv);
168
+ const allowNetwork = flagBool(args.flags, "allow-network");
169
+ const dest = path.resolve(flagString(args.flags, "dest", process.cwd()) ?? process.cwd());
170
+ const telemetryDir = path.join(dest, "scripts", "telemetry");
171
+ const configFile = path.join(telemetryDir, "telemetry.conf");
172
+ const config = readConfig(configFile);
173
+ const enabled = configValue(config, "TELEMETRY_ENABLED", "enabled", "true") !== "false";
174
+ const dataDir = telemetryDataDir(dest);
175
+ const sessionDir = path.resolve(configValue(config, "TELEMETRY_SESSION_DIR", "telemetry_session_dir", path.join(dataDir, "sessions")));
176
+ const channels = configValue(config, "TELEMETRY_CHANNELS", "channels", defaultChannels).split(",").map((channel) => channel.trim()).filter(Boolean);
177
+ const channelReports = channels.map((name) => {
178
+ const defaultLog = name === "full" ? path.join(dataDir, "full.jsonl") : name === "analytics" ? path.join(dataDir, "analytics.jsonl") : "";
179
+ const logFile = channelConfigValue(config, name, "log_file", defaultLog);
180
+ const endpointUrl = channelConfigValue(config, name, "endpoint_url");
181
+ return { name, logFile: logFile ? path.resolve(dest, logFile) : undefined, endpointUrl: safeReportUrl(endpointUrl), active: enabled };
182
+ });
183
+ const consoleUrl = process.env.CONSOLE_TELEMETRY_URL ?? process.env.CONSOLE_URL ?? config.console_telemetry_url ?? config.console_url ?? "";
184
+ const explicitEndpoint = process.env.CONSOLE_TELEMETRY_ENDPOINT_URL ?? config.console_telemetry_endpoint_url ?? "";
185
+ const endpointUrl = deriveConsoleEndpoint(consoleUrl, explicitEndpoint);
186
+ const allowed = endpointAllowed(endpointUrl, allowNetwork);
187
+ const timeoutMs = Number.parseInt(flagString(args.flags, "timeout-ms", "2000") ?? "2000", 10);
188
+ const reachability = await checkConsoleReachability(endpointUrl, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 2000, allowNetwork);
189
+ const warnings = reportWarnings(configFile, endpointUrl, allowed, allowNetwork);
190
+ return {
191
+ ok: enabled && (!endpointUrl || (allowed && reachability.ok !== false)),
192
+ destination: dest,
193
+ telemetry: {
194
+ configFile,
195
+ configExists: fs.existsSync(configFile),
196
+ enabled,
197
+ dataDir,
198
+ sessionDir,
199
+ channels: channelReports,
200
+ activeSinks: activeSinks(enabled, channelReports, endpointUrl, allowed),
201
+ },
202
+ console: {
203
+ sink: endpointUrl ? "console" : "local-only",
204
+ url: safeReportUrl(consoleUrl),
205
+ endpointUrl: safeReportUrl(endpointUrl),
206
+ endpointAllowed: allowed,
207
+ tokenConfigured: Boolean(process.env.CONSOLE_TELEMETRY_TOKEN ?? process.env.CONSOLE_AUTH_TOKEN ?? config.console_telemetry_token),
208
+ tenantConfigured: Boolean(process.env.CONSOLE_TENANT_ID ?? config.console_tenant_id),
209
+ reachability,
210
+ },
211
+ warnings,
212
+ };
213
+ }
214
+
215
+ function reportWarnings(configFile: string, endpointUrl: string, allowed: boolean, allowNetwork: boolean): string[] {
216
+ const warnings: string[] = [];
217
+ if (!fs.existsSync(configFile)) warnings.push("telemetry.conf was not found under destination scripts/telemetry");
218
+ if (endpointUrl && !allowed) warnings.push(allowNetwork ? "Console endpoint is malformed or contains credentials" : "Console endpoint is not allowed without --allow-network; local http(s) endpoints are allowed by default");
219
+ return warnings;
220
+ }
221
+
222
+ function printText(report: DoctorReport): void {
223
+ console.log(`Telemetry doctor for ${report.destination}`);
224
+ console.log(`Config: ${report.telemetry.configFile} (${report.telemetry.configExists ? "found" : "missing"})`);
225
+ console.log(`Telemetry enabled: ${report.telemetry.enabled}`);
226
+ console.log(`Local telemetry dir: ${report.telemetry.dataDir}`);
227
+ console.log(`Active sinks: ${report.telemetry.activeSinks.length ? report.telemetry.activeSinks.join(", ") : "none"}`);
228
+ console.log(`Console endpoint: ${report.console.endpointUrl || "not configured"}`);
229
+ console.log(`Console reachability: ${report.console.reachability.checked ? (report.console.reachability.ok ? "ok" : `failed (${report.console.reachability.error ?? report.console.reachability.statusCode ?? "unknown"})`) : "not checked"}`);
230
+ for (const warning of report.warnings) console.log(`Warning: ${warning}`);
231
+ }
232
+
233
+ export async function main(argv: string[] = process.argv.slice(2)): Promise<number> {
234
+ const args = parseArgs(argv);
235
+ if (flagBool(args.flags, "help") || flagBool(args.flags, "h")) {
236
+ usage();
237
+ return 0;
238
+ }
239
+ const report = await buildReport(argv);
240
+ if (flagBool(args.flags, "json")) console.log(JSON.stringify(report, null, 2));
241
+ else printText(report);
242
+ return report.ok ? 0 : 1;
243
+ }
@@ -0,0 +1,418 @@
1
+ import * as fs from "node:fs";
2
+ import * as os from "node:os";
3
+ import * as path from "node:path";
4
+ import { parseArgs, flagBool, flagList, flagString } from "../lib/args.js";
5
+
6
+ const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
7
+
8
+ function telemetryDir(flags: Record<string, string | boolean | string[]>): string {
9
+ return path.resolve(flagString(flags, "telemetry-dir", process.env.TELEMETRY_DATA_DIR ?? ".telemetry") ?? ".telemetry");
10
+ }
11
+
12
+ function ensureSafeDir(dir: string): void {
13
+ let current = path.resolve(dir);
14
+ const missing: string[] = [];
15
+ while (!fs.existsSync(current)) {
16
+ missing.push(current);
17
+ current = path.dirname(current);
18
+ }
19
+ if (fs.lstatSync(current).isSymbolicLink()) throw new Error(`unsafe telemetry path contains symlink: ${current}`);
20
+ const rel = path.relative(current, dir).split(path.sep).filter(Boolean);
21
+ let cursor = current;
22
+ for (const part of rel) {
23
+ cursor = path.join(cursor, part);
24
+ if (fs.existsSync(cursor) && fs.lstatSync(cursor).isSymbolicLink()) throw new Error(`unsafe telemetry path contains symlink: ${cursor}`);
25
+ }
26
+ for (const item of missing.reverse()) fs.mkdirSync(item);
27
+ }
28
+
29
+ function appendJsonl(file: string, record: unknown): void {
30
+ try {
31
+ if (fs.lstatSync(file).isSymbolicLink()) throw new Error(`refusing to write symlinked file: ${file}`);
32
+ } catch (error) {
33
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
34
+ }
35
+ fs.appendFileSync(file, `${JSON.stringify(record)}\n`, "utf8");
36
+ }
37
+
38
+ function readJsonl(file: string): Record<string, unknown>[] {
39
+ if (!fs.existsSync(file)) return [];
40
+ return fs.readFileSync(file, "utf8").split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
41
+ }
42
+
43
+ function writeJsonlUpsert(file: string, rows: Record<string, unknown>[], key: string): void {
44
+ const existing = new Map(readJsonl(file).map((row) => [String(row[key]), row]));
45
+ for (const row of rows) existing.set(String(row[key]), row);
46
+ fs.writeFileSync(file, Array.from(existing.values()).map((row) => JSON.stringify(row)).join("\n") + (existing.size ? "\n" : ""), "utf8");
47
+ }
48
+
49
+ function recordOutcome(argv: string[]): number {
50
+ const { flags } = parseArgs(argv);
51
+ const sessionId = flagString(flags, "session-id");
52
+ const result = flagString(flags, "result");
53
+ if (!sessionId) throw new Error("--session-id is required");
54
+ if (!result || !VALID_RESULTS.has(result)) throw new Error("--result must be success, partial, failure, or not_verified");
55
+ const dir = telemetryDir(flags);
56
+ ensureSafeDir(dir);
57
+ const record = {
58
+ schema_version: "1",
59
+ outcome_id: `${sessionId}:${flagString(flags, "task-slug", "outcome")}`,
60
+ recorded_at: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
61
+ session_id: sessionId,
62
+ runtime_session_id: flagString(flags, "runtime-session-id"),
63
+ runtime: flagString(flags, "runtime", "codex"),
64
+ repo: flagString(flags, "repo"),
65
+ agent: flagString(flags, "agent"),
66
+ profile_id: flagString(flags, "profile-id"),
67
+ prompt_id: flagString(flags, "prompt-id"),
68
+ prompt_variant: flagString(flags, "prompt-variant"),
69
+ skill_ids: flagList(flags, "skill-id"),
70
+ skill_variant: flagString(flags, "skill-variant"),
71
+ task_type: flagString(flags, "task-type"),
72
+ task_slug: flagString(flags, "task-slug"),
73
+ result,
74
+ quality_score: flagString(flags, "quality-score") ? Number(flagString(flags, "quality-score")) : null,
75
+ human_minutes_saved: flagString(flags, "human-minutes-saved") ? Number(flagString(flags, "human-minutes-saved")) : null,
76
+ rework_required: flagBool(flags, "rework-required"),
77
+ notes: flagString(flags, "notes"),
78
+ evidence: flagList(flags, "evidence"),
79
+ };
80
+ appendJsonl(path.join(dir, "outcomes.jsonl"), record);
81
+ return 0;
82
+ }
83
+
84
+ function normalize(input: Record<string, unknown>[], runtime: string, flags: Record<string, string | boolean | string[]>, fallbackSource = "flow-agents"): Record<string, unknown>[] {
85
+ const groups = new Map<string, Record<string, unknown>[]>();
86
+ for (const event of input) groups.set(String(event.session_id ?? "unknown"), [...(groups.get(String(event.session_id ?? "unknown")) ?? []), event]);
87
+ return Array.from(groups, ([sessionId, events]) => ({
88
+ schema_version: "1",
89
+ session_id: sessionId,
90
+ source_id: flagString(flags, "source-id") ?? String(events.find((event) => event.repo)?.repo ?? fallbackSource),
91
+ runtime,
92
+ repo: flagString(flags, "repo") ?? String(events.find((event) => event.repo)?.repo ?? ""),
93
+ repo_root: flagString(flags, "repo-root"),
94
+ agent: flagString(flags, "agent") ?? String((events.find((event) => event.agent) as { agent?: { name?: string } } | undefined)?.agent?.name ?? "dev"),
95
+ profile_id: flagString(flags, "profile-id") ?? String(events.find((event) => event.profile_id)?.profile_id ?? ""),
96
+ prompt_id: flagString(flags, "prompt-id") ?? String(events.find((event) => event.prompt_id)?.prompt_id ?? ""),
97
+ prompt_variant: flagString(flags, "prompt-variant") ?? String(events.find((event) => event.prompt_variant)?.prompt_variant ?? ""),
98
+ skill_ids: flagList(flags, "skill-id").length ? flagList(flags, "skill-id") : ((events.find((event) => Array.isArray(event.skill_ids))?.skill_ids as string[] | undefined) ?? []),
99
+ skill_variant: flagString(flags, "skill-variant") ?? String(events.find((event) => event.skill_variant)?.skill_variant ?? ""),
100
+ started_at: events[0]?.timestamp,
101
+ ended_at: events[events.length - 1]?.timestamp,
102
+ turns: events.filter((event) => String(event.event_type ?? "").includes("turn.user")).length,
103
+ tool_invocations: events.filter((event) => String(event.event_type ?? "") === "tool.invoke").length,
104
+ delegations: events.filter((event) => String(event.event_type ?? "").includes("delegate")).length,
105
+ permission_requests: events.filter((event) => String(event.event_type ?? "").includes("permission")).length,
106
+ }));
107
+ }
108
+
109
+ function importTelemetry(argv: string[], defaultRuntime?: string): number {
110
+ const { flags } = parseArgs(argv);
111
+ const runtime = defaultRuntime ?? flagString(flags, "runtime", "codex") ?? "codex";
112
+ const input = flagString(flags, "input-full-jsonl") ?? path.join(flagString(flags, "input-telemetry-dir") ?? "", "full.jsonl");
113
+ if (!input || !fs.existsSync(input)) throw new Error(`input telemetry file does not exist: ${input}`);
114
+ const dir = telemetryDir(flags);
115
+ ensureSafeDir(dir);
116
+ const inputDir = flagString(flags, "input-telemetry-dir");
117
+ const fallbackSource = inputDir ? path.basename(path.resolve(inputDir)) : "flow-agents";
118
+ writeJsonlUpsert(path.join(dir, "normalized-sessions.jsonl"), normalize(readJsonl(input), runtime, flags, fallbackSource), "session_id");
119
+ return 0;
120
+ }
121
+
122
+ function artifactOutcomes(artifactPath: string, flags: Record<string, string | boolean | string[]>): Record<string, unknown>[] {
123
+ const files: string[] = [];
124
+ const stat = fs.statSync(artifactPath);
125
+ if (stat.isDirectory()) {
126
+ for (const name of fs.readdirSync(artifactPath, { recursive: true }) as string[]) if (name.endsWith(".md")) files.push(path.join(artifactPath, name));
127
+ } else files.push(artifactPath);
128
+ return files.flatMap((file) => {
129
+ const text = fs.readFileSync(file, "utf8");
130
+ const status = text.match(/^status:\s*(.+)$/m)?.[1].trim() ?? "";
131
+ const type = text.match(/^type:\s*(.+)$/m)?.[1].trim() ?? "";
132
+ const terminal = ["delivered", "accepted", "done"].includes(status);
133
+ if (!terminal && !flagBool(flags, "include-open")) return [];
134
+ const title = text.split(/\r?\n/).find((line) => line.startsWith("# "))?.slice(2).trim() ?? path.basename(file, ".md");
135
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
136
+ return [{ schema_version: "1", outcome_id: `artifact:${file}`, recorded_at: new Date().toISOString(), session_id: slug, runtime: flagString(flags, "runtime", "codex"), repo: flagString(flags, "repo"), profile_id: flagString(flags, "profile-id"), prompt_id: flagString(flags, "prompt-id"), skill_ids: flagList(flags, "skill-id"), task_type: type || "deliver", task_slug: slug, result: terminal ? "success" : status === "failed" ? "failure" : "not_verified", quality_score: null, human_minutes_saved: null, rework_required: false, evidence: [file] }];
137
+ });
138
+ }
139
+
140
+ function syncArtifacts(argv: string[]): number {
141
+ const { flags } = parseArgs(argv);
142
+ const dir = telemetryDir(flags);
143
+ ensureSafeDir(dir);
144
+ const artifacts = flagList(flags, "artifact-dir");
145
+ const records = (artifacts.length ? artifacts : [".flow-agents"]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
146
+ writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
147
+ if (!flagBool(flags, "quiet")) console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
148
+ return 0;
149
+ }
150
+
151
+ function rows(dir: string): { sessions: Record<string, unknown>[]; outcomes: Record<string, unknown>[] } {
152
+ const full = fs.existsSync(path.join(dir, "full.jsonl")) ? path.join(dir, "full.jsonl") : path.join(dir, "sample-full.jsonl");
153
+ const outcomes = fs.existsSync(path.join(dir, "outcomes.jsonl")) ? path.join(dir, "outcomes.jsonl") : path.join(dir, "sample-outcomes.jsonl");
154
+ return {
155
+ sessions: [
156
+ ...readJsonl(path.join(dir, "normalized-sessions.jsonl")),
157
+ ...readJsonl(path.join(dir, "sessions.jsonl")),
158
+ ...normalize(readJsonl(full), "codex", {}, path.basename(path.resolve(dir))),
159
+ ],
160
+ outcomes: readJsonl(outcomes),
161
+ };
162
+ }
163
+
164
+ function reportData(dirs: string[], groupBy?: string): Record<string, unknown> {
165
+ const sessions = dirs.flatMap((dir) => rows(dir).sessions);
166
+ const outcomes = dirs.flatMap((dir) => rows(dir).outcomes);
167
+ const outcomesBySession = new Map<string, Record<string, unknown>[]>();
168
+ for (const outcome of outcomes) {
169
+ const key = String(outcome.session_id ?? "unknown");
170
+ outcomesBySession.set(key, [...(outcomesBySession.get(key) ?? []), outcome]);
171
+ }
172
+ const success = outcomes.filter((outcome) => outcome.result === "success").length;
173
+ const groups = new Map<string, { sessions: number; outcomes: number; success: number; tools: number[]; rework: number }>();
174
+ const groupValue = (session: Record<string, unknown>, sessionOutcomes: Record<string, unknown>[]): string => {
175
+ if (!groupBy) return "all";
176
+ if (groupBy === "source") return String(session.source_id ?? "unknown");
177
+ if (groupBy === "skill_id") return Array.isArray(session.skill_ids) && session.skill_ids.length ? session.skill_ids.join(",") : "unknown";
178
+ if (groupBy === "task_type") return String(sessionOutcomes.find((outcome) => outcome.task_type)?.task_type ?? "unknown");
179
+ return String(session[groupBy] ?? "unknown");
180
+ };
181
+ for (const session of sessions) {
182
+ const sessionOutcomes = outcomesBySession.get(String(session.session_id ?? "unknown")) ?? [];
183
+ const key = cleanLabel(groupValue(session, sessionOutcomes));
184
+ const entry = groups.get(key) ?? { sessions: 0, outcomes: 0, success: 0, tools: [], rework: 0 };
185
+ entry.sessions += 1;
186
+ entry.outcomes += sessionOutcomes.length;
187
+ entry.success += sessionOutcomes.filter((outcome) => outcome.result === "success").length;
188
+ if (session.tool_invocations !== undefined && session.tool_invocations !== null) entry.tools.push(Number(session.tool_invocations));
189
+ entry.rework += sessionOutcomes.filter((outcome) => outcome.rework_required).length;
190
+ groups.set(key, entry);
191
+ }
192
+ const sessionIdsWithOutcomes = new Set(outcomes.map((outcome) => outcome.session_id));
193
+ const avgTools = sessions.length ? sessions.reduce((total, session) => total + Number(session.tool_invocations ?? 0), 0) / sessions.length : null;
194
+ const reworkCount = outcomes.filter((outcome) => outcome.rework_required).length;
195
+ return {
196
+ summary: {
197
+ sessions: sessions.length,
198
+ sessions_with_outcomes: sessionIdsWithOutcomes.size,
199
+ outcomes: outcomes.length,
200
+ success_rate: outcomes.length ? success / outcomes.length : null,
201
+ avg_tool_invocations: avgTools,
202
+ rework_rate: outcomes.length ? reworkCount / outcomes.length : null,
203
+ },
204
+ sources: Array.from(new Set(sessions.map((session) => String(session.source_id ?? "unknown")))).sort(),
205
+ groups: Array.from(groups, ([key, entry]) => ({
206
+ key,
207
+ group: key,
208
+ name: key,
209
+ sessions: entry.sessions,
210
+ outcomes: entry.outcomes,
211
+ success_rate: entry.outcomes ? entry.success / entry.outcomes : null,
212
+ avg_tool_invocations: entry.tools.length ? entry.tools.reduce((a, b) => a + b, 0) / entry.tools.length : null,
213
+ rework_rate: entry.outcomes ? entry.rework / entry.outcomes : null,
214
+ })).sort((a, b) => String(a.key).localeCompare(String(b.key))),
215
+ };
216
+ }
217
+
218
+ function reportPath(output: string | undefined, dir: string, force: boolean): string | undefined {
219
+ if (!output) return undefined;
220
+ const reports = path.join(dir, "reports");
221
+ if (fs.existsSync(reports) && fs.lstatSync(reports).isSymbolicLink()) throw new Error("reports directory is symlinked");
222
+ let relativeOutput = output.replace(new RegExp(`^${path.basename(dir)}/reports/`), "").replace(/^reports\//, "");
223
+ let target = path.isAbsolute(output) ? output : path.join(reports, relativeOutput);
224
+ target = path.resolve(target);
225
+ if (!target.startsWith(path.resolve(reports) + path.sep)) throw new Error("report output must be inside telemetry reports directory");
226
+ try {
227
+ if (fs.lstatSync(target).isSymbolicLink()) throw new Error("report output is symlinked");
228
+ } catch (error) {
229
+ if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
230
+ }
231
+ if (fs.existsSync(target) && !force) throw new Error("report output exists; rerun with --force");
232
+ fs.mkdirSync(path.dirname(target), { recursive: true });
233
+ return target;
234
+ }
235
+
236
+ function html(content: string): string {
237
+ return `<!doctype html><html><body><h1>Usage Dashboard</h1><h2>What Needs Attention</h2><h2>Measurement state</h2><h2>Data Coverage</h2><h2>Outcome Mix</h2><h2>Missing Label Drilldown</h2><pre>${escapeHtml(content)}</pre></body></html>`;
238
+ }
239
+
240
+ function cleanLabel(value: unknown): string {
241
+ return String(value ?? "unknown").replace(/[\r\n\t\x00-\x1f]+/g, " ").split(/\s+/).filter(Boolean).join(" ") || "unknown";
242
+ }
243
+
244
+ function escapeHtml(value: unknown): string {
245
+ return String(value ?? "")
246
+ .replace(/&/g, "&amp;")
247
+ .replace(/</g, "&lt;")
248
+ .replace(/>/g, "&gt;")
249
+ .replace(/"/g, "&quot;")
250
+ .replace(/'/g, "&#39;");
251
+ }
252
+
253
+ function markdownCell(value: unknown): string {
254
+ return cleanLabel(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
255
+ }
256
+
257
+ function fmtRate(value: unknown): string {
258
+ return typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "n/a";
259
+ }
260
+
261
+ function fmtNum(value: unknown): string {
262
+ return typeof value === "number" ? String(Number.isInteger(value) ? value : Number(value.toFixed(2))) : "n/a";
263
+ }
264
+
265
+ function markdownReport(data: Record<string, unknown>, groupBy?: string): string {
266
+ const summary = data.summary as Record<string, unknown>;
267
+ const groups = data.groups as Record<string, unknown>[];
268
+ const lines = [
269
+ "# Agent Usage Feedback Report",
270
+ "",
271
+ "## Summary",
272
+ "",
273
+ `- Sessions: ${summary.sessions}`,
274
+ `- Sessions with outcomes: ${summary.sessions_with_outcomes}`,
275
+ `- Success rate: ${fmtRate(summary.success_rate)}`,
276
+ `- Avg tool invocations: ${fmtNum(summary.avg_tool_invocations)}`,
277
+ `- Rework rate: ${fmtRate(summary.rework_rate)}`,
278
+ ];
279
+ if (groupBy) {
280
+ lines.push("", `## Groups by ${groupBy}`, "", "| Group | Sessions | Outcomes | Success rate | Avg tool invocations | Rework rate |", "| --- | ---: | ---: | ---: | ---: | ---: |");
281
+ for (const group of groups) {
282
+ lines.push(`| ${markdownCell(group.key)} | ${group.sessions} | ${group.outcomes} | ${fmtRate(group.success_rate)} | ${fmtNum(group.avg_tool_invocations)} | ${fmtRate(group.rework_rate)} |`);
283
+ }
284
+ }
285
+ return `${lines.join("\n")}\n`;
286
+ }
287
+
288
+ function report(argv: string[]): number {
289
+ const { flags } = parseArgs(argv);
290
+ const dirs = flagList(flags, "telemetry-dir").map((dir) => path.resolve(dir));
291
+ dirs.forEach(ensureSafeDir);
292
+ const data = reportData(dirs, flagString(flags, "group-by"));
293
+ const format = flagString(flags, "format", "markdown");
294
+ const out = reportPath(flagString(flags, "output"), dirs[0], flagBool(flags, "force"));
295
+ let body: string;
296
+ if (format === "json") body = JSON.stringify(data, null, 2);
297
+ else if (format === "html") body = html(JSON.stringify(data));
298
+ else body = markdownReport(data, flagString(flags, "group-by"));
299
+ if (out) fs.writeFileSync(out, body, "utf8");
300
+ else console.log(body);
301
+ return 0;
302
+ }
303
+
304
+ function dashboard(argv: string[]): number {
305
+ const { flags } = parseArgs(argv);
306
+ syncArtifacts(argv);
307
+ const dir = telemetryDir(flags);
308
+ const out = reportPath(flagString(flags, "output", "dashboard.html"), dir, flagBool(flags, "force"))!;
309
+ const outcomes = readJsonl(path.join(dir, "outcomes.jsonl"));
310
+ fs.writeFileSync(out, html(outcomes.map((o) => String(o.task_slug)).join("\n")), "utf8");
311
+ if (!flagBool(flags, "quiet")) console.log(`dashboard written to ${out}`);
312
+ return 0;
313
+ }
314
+
315
+ function registerProject(argv: string[]): number {
316
+ const { flags } = parseArgs(argv);
317
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
318
+ ensureSafeDir(globalDir);
319
+ const repoRoot = path.resolve(flagString(flags, "repo-root", ".") ?? ".");
320
+ const name = flagString(flags, "name", path.basename(repoRoot)) ?? path.basename(repoRoot);
321
+ const record = { name, repo_root: repoRoot, artifact_dir: path.join(repoRoot, ".flow-agents"), input_telemetry_dir: path.join(repoRoot, ".telemetry"), runtime: flagString(flags, "runtime", "codex"), repo: flagString(flags, "repo", name), agent: flagString(flags, "agent"), profile_id: flagString(flags, "profile-id"), prompt_id: flagString(flags, "prompt-id"), prompt_variant: flagString(flags, "prompt-variant"), skill_ids: flagList(flags, "skill-id"), skill_variant: flagString(flags, "skill-variant") };
322
+ const registryFile = path.join(globalDir, "projects.json");
323
+ const existing = fs.existsSync(registryFile) ? JSON.parse(fs.readFileSync(registryFile, "utf8")) : { projects: [] };
324
+ const projects = Array.isArray(existing) ? existing : Array.isArray(existing.projects) ? existing.projects : [];
325
+ const index = projects.findIndex((project: Record<string, unknown>) => project.name === name || project.repo_root === repoRoot);
326
+ if (index >= 0) projects[index] = { ...projects[index], ...record };
327
+ else projects.push(record);
328
+ fs.writeFileSync(registryFile, `${JSON.stringify({ projects }, null, 2)}\n`, "utf8");
329
+ console.log(`registered ${record.name} in ${path.join(globalDir, "projects.json")}`);
330
+ return 0;
331
+ }
332
+
333
+ function loadProjects(globalDir: string): Record<string, unknown>[] {
334
+ const file = path.join(globalDir, "projects.json");
335
+ if (!fs.existsSync(file)) return [];
336
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
337
+ return Array.isArray(data) ? data : Array.isArray(data.projects) ? data.projects : [];
338
+ }
339
+
340
+ function syncProject(project: Record<string, unknown>, globalDir: string): void {
341
+ const name = String(project.name ?? project.repo ?? "project").replace(/[^a-zA-Z0-9_.-]+/g, "-") || "project";
342
+ const store = path.join(globalDir, "projects", name);
343
+ ensureSafeDir(store);
344
+ const artifactDir = String(project.artifact_dir ?? path.join(String(project.repo_root), ".flow-agents"));
345
+ const flags: Record<string, string | boolean | string[]> = {
346
+ "repo": String(project.repo ?? name),
347
+ "runtime": String(project.runtime ?? "codex"),
348
+ "agent": project.agent ? String(project.agent) : "",
349
+ "profile-id": project.profile_id ? String(project.profile_id) : "",
350
+ "prompt-id": project.prompt_id ? String(project.prompt_id) : "",
351
+ "prompt-variant": project.prompt_variant ? String(project.prompt_variant) : "",
352
+ "skill-id": Array.isArray(project.skill_ids) ? project.skill_ids.map(String) : [],
353
+ "skill-variant": project.skill_variant ? String(project.skill_variant) : "",
354
+ "include-open": true,
355
+ };
356
+ const outcomes = fs.existsSync(artifactDir) ? artifactOutcomes(artifactDir, flags) : [];
357
+ writeJsonlUpsert(path.join(store, "outcomes.jsonl"), outcomes, "outcome_id");
358
+ }
359
+
360
+ function discoverProjects(root: string): Record<string, unknown>[] {
361
+ if (!fs.existsSync(root)) return [];
362
+ const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
363
+ return candidates.filter((candidate) => fs.existsSync(path.join(candidate, ".flow-agents"))).map((repoRoot) => {
364
+ const name = path.basename(repoRoot);
365
+ return { name, repo: name, repo_root: repoRoot, artifact_dir: path.join(repoRoot, ".flow-agents"), input_telemetry_dir: path.join(repoRoot, ".telemetry"), runtime: "codex", skill_ids: [] };
366
+ });
367
+ }
368
+
369
+ function syncProjects(argv: string[]): number {
370
+ const { flags } = parseArgs(argv);
371
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
372
+ ensureSafeDir(globalDir);
373
+ if (flagString(flags, "repo-root")) registerProject(argv);
374
+ for (const project of loadProjects(globalDir)) syncProject(project, globalDir);
375
+ return 0;
376
+ }
377
+
378
+ function globalDashboard(argv: string[]): number {
379
+ const { flags } = parseArgs(argv);
380
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
381
+ ensureSafeDir(globalDir);
382
+ const discovered = flagList(flags, "discover").flatMap(discoverProjects);
383
+ if (discovered.length) {
384
+ const existing = loadProjects(globalDir);
385
+ const merged = [...existing];
386
+ for (const project of discovered) if (!merged.some((item) => item.name === project.name || item.repo_root === project.repo_root)) merged.push(project);
387
+ fs.writeFileSync(path.join(globalDir, "projects.json"), `${JSON.stringify({ projects: merged }, null, 2)}\n`, "utf8");
388
+ }
389
+ for (const project of loadProjects(globalDir)) syncProject(project, globalDir);
390
+ const dirs = fs.existsSync(path.join(globalDir, "projects")) ? fs.readdirSync(path.join(globalDir, "projects")).map((name) => path.join(globalDir, "projects", name)) : [];
391
+ const data = reportData(dirs, "repo");
392
+ const out = reportPath(flagString(flags, "output", "global-dashboard.html"), globalDir, flagBool(flags, "force"))!;
393
+ const projectNames = loadProjects(globalDir).map((project) => String(project.name ?? project.repo ?? "")).filter(Boolean).join("\n");
394
+ fs.writeFileSync(out, html(`${markdownReport(data, "repo")}\n${projectNames}`), "utf8");
395
+ return 0;
396
+ }
397
+
398
+ export function main(argv = process.argv.slice(2)): number {
399
+ try {
400
+ const [command, ...rest] = argv;
401
+ if (command === "record-outcome") return recordOutcome(rest);
402
+ if (command === "import-codex") return importTelemetry(rest, "codex");
403
+ if (command === "import-telemetry") return importTelemetry(rest);
404
+ if (command === "sync-artifacts") return syncArtifacts(rest);
405
+ if (command === "report") return report(rest);
406
+ if (command === "dashboard") return dashboard(rest);
407
+ if (command === "register-project") return registerProject(rest);
408
+ if (command === "sync-projects") return syncProjects(rest);
409
+ if (command === "global-dashboard") return globalDashboard(rest);
410
+ console.error("usage-feedback command required");
411
+ return 2;
412
+ } catch (error) {
413
+ console.error((error as Error).message);
414
+ return 1;
415
+ }
416
+ }
417
+
418
+ if (import.meta.url === `file://${process.argv[1]}`) process.exit(main());