@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,221 @@
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
+ const defaultChannels = "full";
7
+ const sensitiveQueryKeys = new Set(["token", "api_key", "apikey", "key", "secret", "password", "auth", "authorization", "access_token"]);
8
+ function usage() {
9
+ console.error(`usage: flow-agents telemetry-doctor [options]
10
+
11
+ Options:
12
+ --dest PATH Installed Flow Agents root. Defaults to current directory.
13
+ --json Emit machine-readable JSON.
14
+ --headless Do not prompt; suitable for CI.
15
+ --timeout-ms N Console reachability timeout. Defaults to 2000.
16
+ --allow-network Allow reachability checks to non-local HTTPS Console hosts.
17
+ `);
18
+ }
19
+ function readConfig(file) {
20
+ if (!fs.existsSync(file))
21
+ return {};
22
+ const config = {};
23
+ for (const line of fs.readFileSync(file, "utf8").split(/\r?\n/)) {
24
+ const trimmed = line.trim();
25
+ if (!trimmed || trimmed.startsWith("#"))
26
+ continue;
27
+ const eq = trimmed.indexOf("=");
28
+ if (eq === -1)
29
+ continue;
30
+ const key = trimmed.slice(0, eq).trim();
31
+ const value = trimmed.slice(eq + 1).trim();
32
+ if (key)
33
+ config[key] = value;
34
+ }
35
+ return config;
36
+ }
37
+ function configValue(config, envName, key, fallback = "") {
38
+ return process.env[envName] ?? config[key] ?? fallback;
39
+ }
40
+ function channelEnvName(channel, key) {
41
+ return `TELEMETRY_CHANNEL_${channel.toUpperCase()}_${key.toUpperCase()}`;
42
+ }
43
+ function channelConfigValue(config, channel, key, fallback = "") {
44
+ const envName = channelEnvName(channel, key);
45
+ return process.env[envName] ?? config[`channel.${channel}.${key}`] ?? fallback;
46
+ }
47
+ function telemetryDataDir(dest) {
48
+ const configured = process.env.TELEMETRY_DATA_DIR;
49
+ return configured ? path.resolve(dest, configured) : path.resolve(dest, "..", ".telemetry");
50
+ }
51
+ function deriveConsoleEndpoint(consoleUrl, explicitEndpoint) {
52
+ if (explicitEndpoint)
53
+ return explicitEndpoint;
54
+ if (!consoleUrl)
55
+ return "";
56
+ const base = consoleUrl.replace(/\/+$/, "");
57
+ if (base.endsWith("/api/telemetry/records"))
58
+ return base;
59
+ if (base.endsWith("/api/telemetry"))
60
+ return `${base}/records`;
61
+ return `${base}/api/telemetry/records`;
62
+ }
63
+ function endpointAllowed(endpointUrl, allowNetwork = false) {
64
+ if (!endpointUrl || endpointUrl.includes("\n") || endpointUrl.includes("\r") || endpointUrl.includes('"'))
65
+ return false;
66
+ const url = parseUrl(endpointUrl);
67
+ if (!url)
68
+ return false;
69
+ if (url.username || url.password)
70
+ return false;
71
+ if (url.protocol === "https:")
72
+ return allowNetwork || isLocalHostname(url.hostname);
73
+ return url.protocol === "http:" && isLocalHostname(url.hostname);
74
+ }
75
+ function activeSinks(enabled, channels, consoleEndpoint, allowed) {
76
+ const sinks = [];
77
+ if (enabled && channels.some((channel) => channel.active && channel.logFile))
78
+ sinks.push("local-files");
79
+ if (enabled && channels.some((channel) => channel.active && channel.endpointUrl))
80
+ sinks.push("channel-endpoint");
81
+ if (enabled && consoleEndpoint && allowed)
82
+ sinks.push("console");
83
+ return sinks;
84
+ }
85
+ function checkConsoleReachability(endpointUrl, timeoutMs, allowNetwork) {
86
+ if (!endpointUrl)
87
+ return Promise.resolve({ checked: false, ok: null });
88
+ if (!endpointAllowed(endpointUrl, allowNetwork))
89
+ return Promise.resolve({ checked: false, ok: null, error: "endpoint is not allowed" });
90
+ const url = parseUrl(endpointUrl);
91
+ if (!url)
92
+ return Promise.resolve({ checked: false, ok: null, error: "endpoint URL is malformed" });
93
+ return new Promise((resolve) => {
94
+ let settled = false;
95
+ const client = url.protocol === "https:" ? https : http;
96
+ const req = client.request(url, { method: "HEAD", timeout: timeoutMs }, (res) => {
97
+ settled = true;
98
+ res.resume();
99
+ resolve({ checked: true, ok: Boolean(res.statusCode && res.statusCode < 500), statusCode: res.statusCode });
100
+ });
101
+ req.on("timeout", () => {
102
+ if (settled)
103
+ return;
104
+ settled = true;
105
+ req.destroy();
106
+ resolve({ checked: true, ok: false, error: `timeout after ${timeoutMs}ms` });
107
+ });
108
+ req.on("error", (error) => {
109
+ if (settled)
110
+ return;
111
+ settled = true;
112
+ resolve({ checked: true, ok: false, error: error.message });
113
+ });
114
+ req.end();
115
+ });
116
+ }
117
+ function parseUrl(value) {
118
+ try {
119
+ return new URL(value);
120
+ }
121
+ catch {
122
+ return null;
123
+ }
124
+ }
125
+ function isLocalHostname(hostname) {
126
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname.endsWith(".localhost");
127
+ }
128
+ function safeReportUrl(value) {
129
+ if (!value)
130
+ return undefined;
131
+ const parsed = parseUrl(value);
132
+ if (!parsed)
133
+ return "[malformed-url]";
134
+ parsed.username = "";
135
+ parsed.password = "";
136
+ for (const key of Array.from(parsed.searchParams.keys())) {
137
+ if (sensitiveQueryKeys.has(key.toLowerCase()))
138
+ parsed.searchParams.set(key, "[redacted]");
139
+ }
140
+ return parsed.toString();
141
+ }
142
+ async function buildReport(argv) {
143
+ const args = parseArgs(argv);
144
+ const allowNetwork = flagBool(args.flags, "allow-network");
145
+ const dest = path.resolve(flagString(args.flags, "dest", process.cwd()) ?? process.cwd());
146
+ const telemetryDir = path.join(dest, "scripts", "telemetry");
147
+ const configFile = path.join(telemetryDir, "telemetry.conf");
148
+ const config = readConfig(configFile);
149
+ const enabled = configValue(config, "TELEMETRY_ENABLED", "enabled", "true") !== "false";
150
+ const dataDir = telemetryDataDir(dest);
151
+ const sessionDir = path.resolve(configValue(config, "TELEMETRY_SESSION_DIR", "telemetry_session_dir", path.join(dataDir, "sessions")));
152
+ const channels = configValue(config, "TELEMETRY_CHANNELS", "channels", defaultChannels).split(",").map((channel) => channel.trim()).filter(Boolean);
153
+ const channelReports = channels.map((name) => {
154
+ const defaultLog = name === "full" ? path.join(dataDir, "full.jsonl") : name === "analytics" ? path.join(dataDir, "analytics.jsonl") : "";
155
+ const logFile = channelConfigValue(config, name, "log_file", defaultLog);
156
+ const endpointUrl = channelConfigValue(config, name, "endpoint_url");
157
+ return { name, logFile: logFile ? path.resolve(dest, logFile) : undefined, endpointUrl: safeReportUrl(endpointUrl), active: enabled };
158
+ });
159
+ const consoleUrl = process.env.CONSOLE_TELEMETRY_URL ?? process.env.CONSOLE_URL ?? config.console_telemetry_url ?? config.console_url ?? "";
160
+ const explicitEndpoint = process.env.CONSOLE_TELEMETRY_ENDPOINT_URL ?? config.console_telemetry_endpoint_url ?? "";
161
+ const endpointUrl = deriveConsoleEndpoint(consoleUrl, explicitEndpoint);
162
+ const allowed = endpointAllowed(endpointUrl, allowNetwork);
163
+ const timeoutMs = Number.parseInt(flagString(args.flags, "timeout-ms", "2000") ?? "2000", 10);
164
+ const reachability = await checkConsoleReachability(endpointUrl, Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : 2000, allowNetwork);
165
+ const warnings = reportWarnings(configFile, endpointUrl, allowed, allowNetwork);
166
+ return {
167
+ ok: enabled && (!endpointUrl || (allowed && reachability.ok !== false)),
168
+ destination: dest,
169
+ telemetry: {
170
+ configFile,
171
+ configExists: fs.existsSync(configFile),
172
+ enabled,
173
+ dataDir,
174
+ sessionDir,
175
+ channels: channelReports,
176
+ activeSinks: activeSinks(enabled, channelReports, endpointUrl, allowed),
177
+ },
178
+ console: {
179
+ sink: endpointUrl ? "console" : "local-only",
180
+ url: safeReportUrl(consoleUrl),
181
+ endpointUrl: safeReportUrl(endpointUrl),
182
+ endpointAllowed: allowed,
183
+ tokenConfigured: Boolean(process.env.CONSOLE_TELEMETRY_TOKEN ?? process.env.CONSOLE_AUTH_TOKEN ?? config.console_telemetry_token),
184
+ tenantConfigured: Boolean(process.env.CONSOLE_TENANT_ID ?? config.console_tenant_id),
185
+ reachability,
186
+ },
187
+ warnings,
188
+ };
189
+ }
190
+ function reportWarnings(configFile, endpointUrl, allowed, allowNetwork) {
191
+ const warnings = [];
192
+ if (!fs.existsSync(configFile))
193
+ warnings.push("telemetry.conf was not found under destination scripts/telemetry");
194
+ if (endpointUrl && !allowed)
195
+ 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");
196
+ return warnings;
197
+ }
198
+ function printText(report) {
199
+ console.log(`Telemetry doctor for ${report.destination}`);
200
+ console.log(`Config: ${report.telemetry.configFile} (${report.telemetry.configExists ? "found" : "missing"})`);
201
+ console.log(`Telemetry enabled: ${report.telemetry.enabled}`);
202
+ console.log(`Local telemetry dir: ${report.telemetry.dataDir}`);
203
+ console.log(`Active sinks: ${report.telemetry.activeSinks.length ? report.telemetry.activeSinks.join(", ") : "none"}`);
204
+ console.log(`Console endpoint: ${report.console.endpointUrl || "not configured"}`);
205
+ 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"}`);
206
+ for (const warning of report.warnings)
207
+ console.log(`Warning: ${warning}`);
208
+ }
209
+ export async function main(argv = process.argv.slice(2)) {
210
+ const args = parseArgs(argv);
211
+ if (flagBool(args.flags, "help") || flagBool(args.flags, "h")) {
212
+ usage();
213
+ return 0;
214
+ }
215
+ const report = await buildReport(argv);
216
+ if (flagBool(args.flags, "json"))
217
+ console.log(JSON.stringify(report, null, 2));
218
+ else
219
+ printText(report);
220
+ return report.ok ? 0 : 1;
221
+ }
@@ -0,0 +1,443 @@
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
+ const VALID_RESULTS = new Set(["success", "partial", "failure", "not_verified"]);
6
+ function telemetryDir(flags) {
7
+ return path.resolve(flagString(flags, "telemetry-dir", process.env.TELEMETRY_DATA_DIR ?? ".telemetry") ?? ".telemetry");
8
+ }
9
+ function ensureSafeDir(dir) {
10
+ let current = path.resolve(dir);
11
+ const missing = [];
12
+ while (!fs.existsSync(current)) {
13
+ missing.push(current);
14
+ current = path.dirname(current);
15
+ }
16
+ if (fs.lstatSync(current).isSymbolicLink())
17
+ throw new Error(`unsafe telemetry path contains symlink: ${current}`);
18
+ const rel = path.relative(current, dir).split(path.sep).filter(Boolean);
19
+ let cursor = current;
20
+ for (const part of rel) {
21
+ cursor = path.join(cursor, part);
22
+ if (fs.existsSync(cursor) && fs.lstatSync(cursor).isSymbolicLink())
23
+ throw new Error(`unsafe telemetry path contains symlink: ${cursor}`);
24
+ }
25
+ for (const item of missing.reverse())
26
+ fs.mkdirSync(item);
27
+ }
28
+ function appendJsonl(file, record) {
29
+ try {
30
+ if (fs.lstatSync(file).isSymbolicLink())
31
+ throw new Error(`refusing to write symlinked file: ${file}`);
32
+ }
33
+ catch (error) {
34
+ if (error.code !== "ENOENT")
35
+ throw error;
36
+ }
37
+ fs.appendFileSync(file, `${JSON.stringify(record)}\n`, "utf8");
38
+ }
39
+ function readJsonl(file) {
40
+ if (!fs.existsSync(file))
41
+ return [];
42
+ return fs.readFileSync(file, "utf8").split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
43
+ }
44
+ function writeJsonlUpsert(file, rows, key) {
45
+ const existing = new Map(readJsonl(file).map((row) => [String(row[key]), row]));
46
+ for (const row of rows)
47
+ existing.set(String(row[key]), row);
48
+ fs.writeFileSync(file, Array.from(existing.values()).map((row) => JSON.stringify(row)).join("\n") + (existing.size ? "\n" : ""), "utf8");
49
+ }
50
+ function recordOutcome(argv) {
51
+ const { flags } = parseArgs(argv);
52
+ const sessionId = flagString(flags, "session-id");
53
+ const result = flagString(flags, "result");
54
+ if (!sessionId)
55
+ throw new Error("--session-id is required");
56
+ if (!result || !VALID_RESULTS.has(result))
57
+ throw new Error("--result must be success, partial, failure, or not_verified");
58
+ const dir = telemetryDir(flags);
59
+ ensureSafeDir(dir);
60
+ const record = {
61
+ schema_version: "1",
62
+ outcome_id: `${sessionId}:${flagString(flags, "task-slug", "outcome")}`,
63
+ recorded_at: new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
64
+ session_id: sessionId,
65
+ runtime_session_id: flagString(flags, "runtime-session-id"),
66
+ runtime: flagString(flags, "runtime", "codex"),
67
+ repo: flagString(flags, "repo"),
68
+ agent: flagString(flags, "agent"),
69
+ profile_id: flagString(flags, "profile-id"),
70
+ prompt_id: flagString(flags, "prompt-id"),
71
+ prompt_variant: flagString(flags, "prompt-variant"),
72
+ skill_ids: flagList(flags, "skill-id"),
73
+ skill_variant: flagString(flags, "skill-variant"),
74
+ task_type: flagString(flags, "task-type"),
75
+ task_slug: flagString(flags, "task-slug"),
76
+ result,
77
+ quality_score: flagString(flags, "quality-score") ? Number(flagString(flags, "quality-score")) : null,
78
+ human_minutes_saved: flagString(flags, "human-minutes-saved") ? Number(flagString(flags, "human-minutes-saved")) : null,
79
+ rework_required: flagBool(flags, "rework-required"),
80
+ notes: flagString(flags, "notes"),
81
+ evidence: flagList(flags, "evidence"),
82
+ };
83
+ appendJsonl(path.join(dir, "outcomes.jsonl"), record);
84
+ return 0;
85
+ }
86
+ function normalize(input, runtime, flags, fallbackSource = "flow-agents") {
87
+ const groups = new Map();
88
+ for (const event of input)
89
+ groups.set(String(event.session_id ?? "unknown"), [...(groups.get(String(event.session_id ?? "unknown")) ?? []), event]);
90
+ return Array.from(groups, ([sessionId, events]) => ({
91
+ schema_version: "1",
92
+ session_id: sessionId,
93
+ source_id: flagString(flags, "source-id") ?? String(events.find((event) => event.repo)?.repo ?? fallbackSource),
94
+ runtime,
95
+ repo: flagString(flags, "repo") ?? String(events.find((event) => event.repo)?.repo ?? ""),
96
+ repo_root: flagString(flags, "repo-root"),
97
+ agent: flagString(flags, "agent") ?? String(events.find((event) => event.agent)?.agent?.name ?? "dev"),
98
+ profile_id: flagString(flags, "profile-id") ?? String(events.find((event) => event.profile_id)?.profile_id ?? ""),
99
+ prompt_id: flagString(flags, "prompt-id") ?? String(events.find((event) => event.prompt_id)?.prompt_id ?? ""),
100
+ prompt_variant: flagString(flags, "prompt-variant") ?? String(events.find((event) => event.prompt_variant)?.prompt_variant ?? ""),
101
+ skill_ids: flagList(flags, "skill-id").length ? flagList(flags, "skill-id") : (events.find((event) => Array.isArray(event.skill_ids))?.skill_ids ?? []),
102
+ skill_variant: flagString(flags, "skill-variant") ?? String(events.find((event) => event.skill_variant)?.skill_variant ?? ""),
103
+ started_at: events[0]?.timestamp,
104
+ ended_at: events[events.length - 1]?.timestamp,
105
+ turns: events.filter((event) => String(event.event_type ?? "").includes("turn.user")).length,
106
+ tool_invocations: events.filter((event) => String(event.event_type ?? "") === "tool.invoke").length,
107
+ delegations: events.filter((event) => String(event.event_type ?? "").includes("delegate")).length,
108
+ permission_requests: events.filter((event) => String(event.event_type ?? "").includes("permission")).length,
109
+ }));
110
+ }
111
+ function importTelemetry(argv, defaultRuntime) {
112
+ const { flags } = parseArgs(argv);
113
+ const runtime = defaultRuntime ?? flagString(flags, "runtime", "codex") ?? "codex";
114
+ const input = flagString(flags, "input-full-jsonl") ?? path.join(flagString(flags, "input-telemetry-dir") ?? "", "full.jsonl");
115
+ if (!input || !fs.existsSync(input))
116
+ throw new Error(`input telemetry file does not exist: ${input}`);
117
+ const dir = telemetryDir(flags);
118
+ ensureSafeDir(dir);
119
+ const inputDir = flagString(flags, "input-telemetry-dir");
120
+ const fallbackSource = inputDir ? path.basename(path.resolve(inputDir)) : "flow-agents";
121
+ writeJsonlUpsert(path.join(dir, "normalized-sessions.jsonl"), normalize(readJsonl(input), runtime, flags, fallbackSource), "session_id");
122
+ return 0;
123
+ }
124
+ function artifactOutcomes(artifactPath, flags) {
125
+ const files = [];
126
+ const stat = fs.statSync(artifactPath);
127
+ if (stat.isDirectory()) {
128
+ for (const name of fs.readdirSync(artifactPath, { recursive: true }))
129
+ if (name.endsWith(".md"))
130
+ files.push(path.join(artifactPath, name));
131
+ }
132
+ else
133
+ files.push(artifactPath);
134
+ return files.flatMap((file) => {
135
+ const text = fs.readFileSync(file, "utf8");
136
+ const status = text.match(/^status:\s*(.+)$/m)?.[1].trim() ?? "";
137
+ const type = text.match(/^type:\s*(.+)$/m)?.[1].trim() ?? "";
138
+ const terminal = ["delivered", "accepted", "done"].includes(status);
139
+ if (!terminal && !flagBool(flags, "include-open"))
140
+ return [];
141
+ const title = text.split(/\r?\n/).find((line) => line.startsWith("# "))?.slice(2).trim() ?? path.basename(file, ".md");
142
+ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
143
+ 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] }];
144
+ });
145
+ }
146
+ function syncArtifacts(argv) {
147
+ const { flags } = parseArgs(argv);
148
+ const dir = telemetryDir(flags);
149
+ ensureSafeDir(dir);
150
+ const artifacts = flagList(flags, "artifact-dir");
151
+ const records = (artifacts.length ? artifacts : [".flow-agents"]).flatMap((item) => fs.existsSync(item) ? artifactOutcomes(item, flags) : []);
152
+ writeJsonlUpsert(path.join(dir, "outcomes.jsonl"), records, "outcome_id");
153
+ if (!flagBool(flags, "quiet"))
154
+ console.log(`synced ${records.length} artifact outcome(s) to ${path.join(dir, "outcomes.jsonl")}`);
155
+ return 0;
156
+ }
157
+ function rows(dir) {
158
+ const full = fs.existsSync(path.join(dir, "full.jsonl")) ? path.join(dir, "full.jsonl") : path.join(dir, "sample-full.jsonl");
159
+ const outcomes = fs.existsSync(path.join(dir, "outcomes.jsonl")) ? path.join(dir, "outcomes.jsonl") : path.join(dir, "sample-outcomes.jsonl");
160
+ return {
161
+ sessions: [
162
+ ...readJsonl(path.join(dir, "normalized-sessions.jsonl")),
163
+ ...readJsonl(path.join(dir, "sessions.jsonl")),
164
+ ...normalize(readJsonl(full), "codex", {}, path.basename(path.resolve(dir))),
165
+ ],
166
+ outcomes: readJsonl(outcomes),
167
+ };
168
+ }
169
+ function reportData(dirs, groupBy) {
170
+ const sessions = dirs.flatMap((dir) => rows(dir).sessions);
171
+ const outcomes = dirs.flatMap((dir) => rows(dir).outcomes);
172
+ const outcomesBySession = new Map();
173
+ for (const outcome of outcomes) {
174
+ const key = String(outcome.session_id ?? "unknown");
175
+ outcomesBySession.set(key, [...(outcomesBySession.get(key) ?? []), outcome]);
176
+ }
177
+ const success = outcomes.filter((outcome) => outcome.result === "success").length;
178
+ const groups = new Map();
179
+ const groupValue = (session, sessionOutcomes) => {
180
+ if (!groupBy)
181
+ return "all";
182
+ if (groupBy === "source")
183
+ return String(session.source_id ?? "unknown");
184
+ if (groupBy === "skill_id")
185
+ return Array.isArray(session.skill_ids) && session.skill_ids.length ? session.skill_ids.join(",") : "unknown";
186
+ if (groupBy === "task_type")
187
+ return String(sessionOutcomes.find((outcome) => outcome.task_type)?.task_type ?? "unknown");
188
+ return String(session[groupBy] ?? "unknown");
189
+ };
190
+ for (const session of sessions) {
191
+ const sessionOutcomes = outcomesBySession.get(String(session.session_id ?? "unknown")) ?? [];
192
+ const key = cleanLabel(groupValue(session, sessionOutcomes));
193
+ const entry = groups.get(key) ?? { sessions: 0, outcomes: 0, success: 0, tools: [], rework: 0 };
194
+ entry.sessions += 1;
195
+ entry.outcomes += sessionOutcomes.length;
196
+ entry.success += sessionOutcomes.filter((outcome) => outcome.result === "success").length;
197
+ if (session.tool_invocations !== undefined && session.tool_invocations !== null)
198
+ entry.tools.push(Number(session.tool_invocations));
199
+ entry.rework += sessionOutcomes.filter((outcome) => outcome.rework_required).length;
200
+ groups.set(key, entry);
201
+ }
202
+ const sessionIdsWithOutcomes = new Set(outcomes.map((outcome) => outcome.session_id));
203
+ const avgTools = sessions.length ? sessions.reduce((total, session) => total + Number(session.tool_invocations ?? 0), 0) / sessions.length : null;
204
+ const reworkCount = outcomes.filter((outcome) => outcome.rework_required).length;
205
+ return {
206
+ summary: {
207
+ sessions: sessions.length,
208
+ sessions_with_outcomes: sessionIdsWithOutcomes.size,
209
+ outcomes: outcomes.length,
210
+ success_rate: outcomes.length ? success / outcomes.length : null,
211
+ avg_tool_invocations: avgTools,
212
+ rework_rate: outcomes.length ? reworkCount / outcomes.length : null,
213
+ },
214
+ sources: Array.from(new Set(sessions.map((session) => String(session.source_id ?? "unknown")))).sort(),
215
+ groups: Array.from(groups, ([key, entry]) => ({
216
+ key,
217
+ group: key,
218
+ name: key,
219
+ sessions: entry.sessions,
220
+ outcomes: entry.outcomes,
221
+ success_rate: entry.outcomes ? entry.success / entry.outcomes : null,
222
+ avg_tool_invocations: entry.tools.length ? entry.tools.reduce((a, b) => a + b, 0) / entry.tools.length : null,
223
+ rework_rate: entry.outcomes ? entry.rework / entry.outcomes : null,
224
+ })).sort((a, b) => String(a.key).localeCompare(String(b.key))),
225
+ };
226
+ }
227
+ function reportPath(output, dir, force) {
228
+ if (!output)
229
+ return undefined;
230
+ const reports = path.join(dir, "reports");
231
+ if (fs.existsSync(reports) && fs.lstatSync(reports).isSymbolicLink())
232
+ throw new Error("reports directory is symlinked");
233
+ let relativeOutput = output.replace(new RegExp(`^${path.basename(dir)}/reports/`), "").replace(/^reports\//, "");
234
+ let target = path.isAbsolute(output) ? output : path.join(reports, relativeOutput);
235
+ target = path.resolve(target);
236
+ if (!target.startsWith(path.resolve(reports) + path.sep))
237
+ throw new Error("report output must be inside telemetry reports directory");
238
+ try {
239
+ if (fs.lstatSync(target).isSymbolicLink())
240
+ throw new Error("report output is symlinked");
241
+ }
242
+ catch (error) {
243
+ if (error.code !== "ENOENT")
244
+ throw error;
245
+ }
246
+ if (fs.existsSync(target) && !force)
247
+ throw new Error("report output exists; rerun with --force");
248
+ fs.mkdirSync(path.dirname(target), { recursive: true });
249
+ return target;
250
+ }
251
+ function html(content) {
252
+ 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>`;
253
+ }
254
+ function cleanLabel(value) {
255
+ return String(value ?? "unknown").replace(/[\r\n\t\x00-\x1f]+/g, " ").split(/\s+/).filter(Boolean).join(" ") || "unknown";
256
+ }
257
+ function escapeHtml(value) {
258
+ return String(value ?? "")
259
+ .replace(/&/g, "&amp;")
260
+ .replace(/</g, "&lt;")
261
+ .replace(/>/g, "&gt;")
262
+ .replace(/"/g, "&quot;")
263
+ .replace(/'/g, "&#39;");
264
+ }
265
+ function markdownCell(value) {
266
+ return cleanLabel(value).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
267
+ }
268
+ function fmtRate(value) {
269
+ return typeof value === "number" ? `${(value * 100).toFixed(1)}%` : "n/a";
270
+ }
271
+ function fmtNum(value) {
272
+ return typeof value === "number" ? String(Number.isInteger(value) ? value : Number(value.toFixed(2))) : "n/a";
273
+ }
274
+ function markdownReport(data, groupBy) {
275
+ const summary = data.summary;
276
+ const groups = data.groups;
277
+ const lines = [
278
+ "# Agent Usage Feedback Report",
279
+ "",
280
+ "## Summary",
281
+ "",
282
+ `- Sessions: ${summary.sessions}`,
283
+ `- Sessions with outcomes: ${summary.sessions_with_outcomes}`,
284
+ `- Success rate: ${fmtRate(summary.success_rate)}`,
285
+ `- Avg tool invocations: ${fmtNum(summary.avg_tool_invocations)}`,
286
+ `- Rework rate: ${fmtRate(summary.rework_rate)}`,
287
+ ];
288
+ if (groupBy) {
289
+ lines.push("", `## Groups by ${groupBy}`, "", "| Group | Sessions | Outcomes | Success rate | Avg tool invocations | Rework rate |", "| --- | ---: | ---: | ---: | ---: | ---: |");
290
+ for (const group of groups) {
291
+ lines.push(`| ${markdownCell(group.key)} | ${group.sessions} | ${group.outcomes} | ${fmtRate(group.success_rate)} | ${fmtNum(group.avg_tool_invocations)} | ${fmtRate(group.rework_rate)} |`);
292
+ }
293
+ }
294
+ return `${lines.join("\n")}\n`;
295
+ }
296
+ function report(argv) {
297
+ const { flags } = parseArgs(argv);
298
+ const dirs = flagList(flags, "telemetry-dir").map((dir) => path.resolve(dir));
299
+ dirs.forEach(ensureSafeDir);
300
+ const data = reportData(dirs, flagString(flags, "group-by"));
301
+ const format = flagString(flags, "format", "markdown");
302
+ const out = reportPath(flagString(flags, "output"), dirs[0], flagBool(flags, "force"));
303
+ let body;
304
+ if (format === "json")
305
+ body = JSON.stringify(data, null, 2);
306
+ else if (format === "html")
307
+ body = html(JSON.stringify(data));
308
+ else
309
+ body = markdownReport(data, flagString(flags, "group-by"));
310
+ if (out)
311
+ fs.writeFileSync(out, body, "utf8");
312
+ else
313
+ console.log(body);
314
+ return 0;
315
+ }
316
+ function dashboard(argv) {
317
+ const { flags } = parseArgs(argv);
318
+ syncArtifacts(argv);
319
+ const dir = telemetryDir(flags);
320
+ const out = reportPath(flagString(flags, "output", "dashboard.html"), dir, flagBool(flags, "force"));
321
+ const outcomes = readJsonl(path.join(dir, "outcomes.jsonl"));
322
+ fs.writeFileSync(out, html(outcomes.map((o) => String(o.task_slug)).join("\n")), "utf8");
323
+ if (!flagBool(flags, "quiet"))
324
+ console.log(`dashboard written to ${out}`);
325
+ return 0;
326
+ }
327
+ function registerProject(argv) {
328
+ const { flags } = parseArgs(argv);
329
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
330
+ ensureSafeDir(globalDir);
331
+ const repoRoot = path.resolve(flagString(flags, "repo-root", ".") ?? ".");
332
+ const name = flagString(flags, "name", path.basename(repoRoot)) ?? path.basename(repoRoot);
333
+ 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") };
334
+ const registryFile = path.join(globalDir, "projects.json");
335
+ const existing = fs.existsSync(registryFile) ? JSON.parse(fs.readFileSync(registryFile, "utf8")) : { projects: [] };
336
+ const projects = Array.isArray(existing) ? existing : Array.isArray(existing.projects) ? existing.projects : [];
337
+ const index = projects.findIndex((project) => project.name === name || project.repo_root === repoRoot);
338
+ if (index >= 0)
339
+ projects[index] = { ...projects[index], ...record };
340
+ else
341
+ projects.push(record);
342
+ fs.writeFileSync(registryFile, `${JSON.stringify({ projects }, null, 2)}\n`, "utf8");
343
+ console.log(`registered ${record.name} in ${path.join(globalDir, "projects.json")}`);
344
+ return 0;
345
+ }
346
+ function loadProjects(globalDir) {
347
+ const file = path.join(globalDir, "projects.json");
348
+ if (!fs.existsSync(file))
349
+ return [];
350
+ const data = JSON.parse(fs.readFileSync(file, "utf8"));
351
+ return Array.isArray(data) ? data : Array.isArray(data.projects) ? data.projects : [];
352
+ }
353
+ function syncProject(project, globalDir) {
354
+ const name = String(project.name ?? project.repo ?? "project").replace(/[^a-zA-Z0-9_.-]+/g, "-") || "project";
355
+ const store = path.join(globalDir, "projects", name);
356
+ ensureSafeDir(store);
357
+ const artifactDir = String(project.artifact_dir ?? path.join(String(project.repo_root), ".flow-agents"));
358
+ const flags = {
359
+ "repo": String(project.repo ?? name),
360
+ "runtime": String(project.runtime ?? "codex"),
361
+ "agent": project.agent ? String(project.agent) : "",
362
+ "profile-id": project.profile_id ? String(project.profile_id) : "",
363
+ "prompt-id": project.prompt_id ? String(project.prompt_id) : "",
364
+ "prompt-variant": project.prompt_variant ? String(project.prompt_variant) : "",
365
+ "skill-id": Array.isArray(project.skill_ids) ? project.skill_ids.map(String) : [],
366
+ "skill-variant": project.skill_variant ? String(project.skill_variant) : "",
367
+ "include-open": true,
368
+ };
369
+ const outcomes = fs.existsSync(artifactDir) ? artifactOutcomes(artifactDir, flags) : [];
370
+ writeJsonlUpsert(path.join(store, "outcomes.jsonl"), outcomes, "outcome_id");
371
+ }
372
+ function discoverProjects(root) {
373
+ if (!fs.existsSync(root))
374
+ return [];
375
+ const candidates = [root, ...fs.readdirSync(root).map((name) => path.join(root, name))];
376
+ return candidates.filter((candidate) => fs.existsSync(path.join(candidate, ".flow-agents"))).map((repoRoot) => {
377
+ const name = path.basename(repoRoot);
378
+ 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: [] };
379
+ });
380
+ }
381
+ function syncProjects(argv) {
382
+ const { flags } = parseArgs(argv);
383
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
384
+ ensureSafeDir(globalDir);
385
+ if (flagString(flags, "repo-root"))
386
+ registerProject(argv);
387
+ for (const project of loadProjects(globalDir))
388
+ syncProject(project, globalDir);
389
+ return 0;
390
+ }
391
+ function globalDashboard(argv) {
392
+ const { flags } = parseArgs(argv);
393
+ const globalDir = path.resolve(flagString(flags, "global-dir", path.join(os.homedir(), ".local", "share", "flow-agents", "usage-feedback")) ?? "");
394
+ ensureSafeDir(globalDir);
395
+ const discovered = flagList(flags, "discover").flatMap(discoverProjects);
396
+ if (discovered.length) {
397
+ const existing = loadProjects(globalDir);
398
+ const merged = [...existing];
399
+ for (const project of discovered)
400
+ if (!merged.some((item) => item.name === project.name || item.repo_root === project.repo_root))
401
+ merged.push(project);
402
+ fs.writeFileSync(path.join(globalDir, "projects.json"), `${JSON.stringify({ projects: merged }, null, 2)}\n`, "utf8");
403
+ }
404
+ for (const project of loadProjects(globalDir))
405
+ syncProject(project, globalDir);
406
+ const dirs = fs.existsSync(path.join(globalDir, "projects")) ? fs.readdirSync(path.join(globalDir, "projects")).map((name) => path.join(globalDir, "projects", name)) : [];
407
+ const data = reportData(dirs, "repo");
408
+ const out = reportPath(flagString(flags, "output", "global-dashboard.html"), globalDir, flagBool(flags, "force"));
409
+ const projectNames = loadProjects(globalDir).map((project) => String(project.name ?? project.repo ?? "")).filter(Boolean).join("\n");
410
+ fs.writeFileSync(out, html(`${markdownReport(data, "repo")}\n${projectNames}`), "utf8");
411
+ return 0;
412
+ }
413
+ export function main(argv = process.argv.slice(2)) {
414
+ try {
415
+ const [command, ...rest] = argv;
416
+ if (command === "record-outcome")
417
+ return recordOutcome(rest);
418
+ if (command === "import-codex")
419
+ return importTelemetry(rest, "codex");
420
+ if (command === "import-telemetry")
421
+ return importTelemetry(rest);
422
+ if (command === "sync-artifacts")
423
+ return syncArtifacts(rest);
424
+ if (command === "report")
425
+ return report(rest);
426
+ if (command === "dashboard")
427
+ return dashboard(rest);
428
+ if (command === "register-project")
429
+ return registerProject(rest);
430
+ if (command === "sync-projects")
431
+ return syncProjects(rest);
432
+ if (command === "global-dashboard")
433
+ return globalDashboard(rest);
434
+ console.error("usage-feedback command required");
435
+ return 2;
436
+ }
437
+ catch (error) {
438
+ console.error(error.message);
439
+ return 1;
440
+ }
441
+ }
442
+ if (import.meta.url === `file://${process.argv[1]}`)
443
+ process.exit(main());