coding-os 0.3.2__py3-none-any.whl

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 (1304) hide show
  1. adapters/claude/README.md +6 -0
  2. adapters/claude/_install_helpers/extract_stacks.py +43 -0
  3. adapters/claude/_install_helpers/update_mcp_json.py +75 -0
  4. adapters/claude/adapter.yaml +164 -0
  5. adapters/claude/hooks/README.md +40 -0
  6. adapters/claude/hooks/agent_memory_sync.py +131 -0
  7. adapters/claude/hooks/ensure-agent-memory-link.sh +36 -0
  8. adapters/claude/hooks/sync-agent-memory.sh +21 -0
  9. adapters/claude/install.sh +86 -0
  10. adapters/claude/sdk_dispatcher.py +871 -0
  11. adapters/claude/settings.local.template.json +31 -0
  12. adapters/claude/settings.template.json +808 -0
  13. adapters/claude/update_mcp_json.py +85 -0
  14. adapters/codex/adapter.yaml +254 -0
  15. adapters/codex/chat_provider.py +230 -0
  16. adapters/codex/commands/formula-f1.md +129 -0
  17. adapters/codex/commands/formula-f10.md +100 -0
  18. adapters/codex/commands/formula-f11.md +123 -0
  19. adapters/codex/commands/formula-f2.md +139 -0
  20. adapters/codex/commands/formula-f3.md +127 -0
  21. adapters/codex/commands/formula-f4.md +101 -0
  22. adapters/codex/commands/formula-f5.md +135 -0
  23. adapters/codex/commands/formula-f6.md +147 -0
  24. adapters/codex/commands/formula-f7.md +111 -0
  25. adapters/codex/commands/formula-f8.md +133 -0
  26. adapters/codex/commands/formula-f9.md +112 -0
  27. adapters/codex/enable_codex_hooks.py +94 -0
  28. adapters/codex/ensure_codex_mcp.py +124 -0
  29. adapters/codex/hooks/codex-merge-hook-output.py +72 -0
  30. adapters/codex/hooks/codex-normalize-edit.py +96 -0
  31. adapters/codex/hooks/codex-postedit-dispatch.sh +75 -0
  32. adapters/codex/hooks/codex-posttool-dispatch.sh +70 -0
  33. adapters/codex/hooks/codex-preedit-dispatch.sh +83 -0
  34. adapters/codex/hooks/codex-pretool-dispatch.sh +82 -0
  35. adapters/codex/hooks/codex-sessionend-dispatch.sh +20 -0
  36. adapters/codex/hooks/codex-sessionstart-dispatch.sh +68 -0
  37. adapters/codex/hooks/codex-stop-dispatch.sh +73 -0
  38. adapters/codex/hooks/codex-userpromptsubmit-dispatch.sh +74 -0
  39. adapters/codex/hooks.template.json +208 -0
  40. adapters/codex/install.sh +83 -0
  41. adapters/codex/sdk_dispatcher.py +449 -0
  42. board_os/__init__.py +39 -0
  43. board_os/_agent_runtime.py +256 -0
  44. board_os/config.py +421 -0
  45. board_os/git_coherence.py +107 -0
  46. board_os/hub_adapter_manifest.py +140 -0
  47. board_os/mcp_tools.py +3228 -0
  48. board_os/migration.py +166 -0
  49. board_os/parser.py +317 -0
  50. board_os/presence.py +156 -0
  51. board_os/sync.py +320 -0
  52. board_os/transition_gates.py +224 -0
  53. board_os/transition_gates_cli.py +272 -0
  54. board_os/transition_gates_validator.py +551 -0
  55. board_os/verify_suites.py +126 -0
  56. board_os/verify_suites_cli.py +328 -0
  57. board_os/workflow.py +967 -0
  58. cli/__init__.py +0 -0
  59. cli/_data_types.py +248 -0
  60. cli/_init_helpers.py +587 -0
  61. cli/_resources.py +100 -0
  62. cli/adapter_registry.py +239 -0
  63. cli/add_stack.py +315 -0
  64. cli/aggregator.py +438 -0
  65. cli/board_commands.py +1218 -0
  66. cli/brain_commands.py +255 -0
  67. cli/cognition.py +345 -0
  68. cli/config_composer.py +349 -0
  69. cli/core_version.py +41 -0
  70. cli/cron_commands.py +278 -0
  71. cli/db_reset.py +298 -0
  72. cli/doc_commands.py +111 -0
  73. cli/doctor.py +2953 -0
  74. cli/doctor_board.py +365 -0
  75. cli/doctor_extras.py +1121 -0
  76. cli/doctor_graph.py +608 -0
  77. cli/doctor_tokens.py +254 -0
  78. cli/graph_commands.py +1265 -0
  79. cli/hook_renderer.py +393 -0
  80. cli/hub_commands.py +580 -0
  81. cli/list_adapters.py +79 -0
  82. cli/list_stacks.py +105 -0
  83. cli/logs_commands.py +89 -0
  84. cli/main.py +3070 -0
  85. cli/materialize_file.py +65 -0
  86. cli/mcp_start.py +153 -0
  87. cli/module_commands.py +513 -0
  88. cli/pr_commands.py +2024 -0
  89. cli/preset_commands.py +126 -0
  90. cli/preset_registry.py +171 -0
  91. cli/project_overrides.py +119 -0
  92. cli/registry.py +365 -0
  93. cli/remove_stack.py +492 -0
  94. cli/renderer.py +620 -0
  95. cli/setup.py +464 -0
  96. cli/skill_commands.py +689 -0
  97. cli/skill_registry.py +235 -0
  98. cli/skills_list.py +332 -0
  99. cli/stack_lint.py +351 -0
  100. cli/stack_registry.py +688 -0
  101. cli/subsystems.py +335 -0
  102. cli/sync_all.py +310 -0
  103. cli/tail_command.py +410 -0
  104. cli/update.py +608 -0
  105. cli/verify_since_edit.py +439 -0
  106. coding_os-0.3.2.dist-info/METADATA +508 -0
  107. coding_os-0.3.2.dist-info/RECORD +1304 -0
  108. coding_os-0.3.2.dist-info/WHEEL +5 -0
  109. coding_os-0.3.2.dist-info/entry_points.txt +5 -0
  110. coding_os-0.3.2.dist-info/licenses/LICENSE +201 -0
  111. coding_os-0.3.2.dist-info/top_level.txt +10 -0
  112. core/__init__.py +0 -0
  113. core/board_os/__init__.py +39 -0
  114. core/board_os/_agent_runtime.py +256 -0
  115. core/board_os/config.py +421 -0
  116. core/board_os/git_coherence.py +107 -0
  117. core/board_os/hub_adapter_manifest.py +140 -0
  118. core/board_os/mcp_tools.py +3228 -0
  119. core/board_os/migration.py +166 -0
  120. core/board_os/parser.py +317 -0
  121. core/board_os/presence.py +156 -0
  122. core/board_os/sync.py +320 -0
  123. core/board_os/transition-gates.yaml +176 -0
  124. core/board_os/transition_gates.py +224 -0
  125. core/board_os/transition_gates_cli.py +272 -0
  126. core/board_os/transition_gates_validator.py +551 -0
  127. core/board_os/verify-suites.yaml +113 -0
  128. core/board_os/verify_suites.py +126 -0
  129. core/board_os/verify_suites_cli.py +328 -0
  130. core/board_os/workflow.py +967 -0
  131. core/commands/board.md +27 -0
  132. core/commands/classify.md +23 -0
  133. core/commands/compose.md +23 -0
  134. core/commands/daily.md +31 -0
  135. core/commands/diagnose.md +7 -0
  136. core/commands/memory-search.md +23 -0
  137. core/commands/new-project.md +33 -0
  138. core/commands/retro.md +38 -0
  139. core/commands/review.md +14 -0
  140. core/commands/task.md +17 -0
  141. core/commands/verify.md +34 -0
  142. core/docs/thinking_os-final-edition.md +1449 -0
  143. core/doctor-config.yaml +74 -0
  144. core/graph_os/__init__.py +29 -0
  145. core/graph_os/backend.py +233 -0
  146. core/graph_os/backends/__init__.py +13 -0
  147. core/graph_os/backends/sqlite_backend.py +1053 -0
  148. core/graph_os/bench/__init__.py +17 -0
  149. core/graph_os/bench/fixtures.py +56 -0
  150. core/graph_os/bench/harness.py +95 -0
  151. core/graph_os/bench/persian_precision.py +142 -0
  152. core/graph_os/bench/scale_500k.py +120 -0
  153. core/graph_os/bench/token_cost.py +170 -0
  154. core/graph_os/bench/viewer_fps.py +110 -0
  155. core/graph_os/communities.py +410 -0
  156. core/graph_os/enterprise.py +218 -0
  157. core/graph_os/entry_points.py +226 -0
  158. core/graph_os/extractors/__init__.py +25 -0
  159. core/graph_os/extractors/code_generic.py +914 -0
  160. core/graph_os/extractors/code_go.py +1422 -0
  161. core/graph_os/extractors/code_json.py +340 -0
  162. core/graph_os/extractors/code_php.py +979 -0
  163. core/graph_os/extractors/code_python.py +1454 -0
  164. core/graph_os/extractors/code_shell.py +538 -0
  165. core/graph_os/extractors/code_toml.py +302 -0
  166. core/graph_os/extractors/code_ts.py +1665 -0
  167. core/graph_os/extractors/code_yaml.py +394 -0
  168. core/graph_os/extractors/contracts.py +1592 -0
  169. core/graph_os/extractors/md_links.py +890 -0
  170. core/graph_os/extractors/task_deps.py +345 -0
  171. core/graph_os/groups/__init__.py +22 -0
  172. core/graph_os/groups/cross_repo.py +156 -0
  173. core/graph_os/groups/manifest.py +141 -0
  174. core/graph_os/ingest/__init__.py +19 -0
  175. core/graph_os/ingest/base.py +306 -0
  176. core/graph_os/ingest/github.py +112 -0
  177. core/graph_os/ingest/zip.py +95 -0
  178. core/graph_os/toolchain.py +393 -0
  179. core/graph_os/tools/__init__.py +9 -0
  180. core/graph_os/tools/graph.py +5573 -0
  181. core/graph_os/tools/reindex_dispatch.py +730 -0
  182. core/graph_os/tree_sitter_overlay.py +235 -0
  183. core/graph_os/types.py +252 -0
  184. core/graph_os/vec_index.py +277 -0
  185. core/graph_os/viewer/__init__.py +12 -0
  186. core/graph_os/viewer/exporter.py +93 -0
  187. core/graph_os/viewer/template.py +189 -0
  188. core/hooks/_helpers/_paths.py +40 -0
  189. core/hooks/_helpers/advance_role.py +72 -0
  190. core/hooks/_helpers/auto_compose.py +228 -0
  191. core/hooks/_helpers/auto_validate_lessons.py +55 -0
  192. core/hooks/_helpers/branch_guard_check.py +796 -0
  193. core/hooks/_helpers/check_commit_message.py +108 -0
  194. core/hooks/_helpers/check_dangerous_rm.py +80 -0
  195. core/hooks/_helpers/check_git_bypass.py +154 -0
  196. core/hooks/_helpers/check_git_destructive.py +77 -0
  197. core/hooks/_helpers/check_settings_write.py +97 -0
  198. core/hooks/_helpers/consume_override.py +51 -0
  199. core/hooks/_helpers/context_budget.py +77 -0
  200. core/hooks/_helpers/cos_say_json.py +103 -0
  201. core/hooks/_helpers/destructive_edit_check.py +163 -0
  202. core/hooks/_helpers/detect_status_transition.py +82 -0
  203. core/hooks/_helpers/digest_regen.py +56 -0
  204. core/hooks/_helpers/doc_sync_check.py +498 -0
  205. core/hooks/_helpers/drain_embedding_outbox.py +52 -0
  206. core/hooks/_helpers/extract_additional_context.py +51 -0
  207. core/hooks/_helpers/extract_commit_msg_arg.py +74 -0
  208. core/hooks/_helpers/git_command_parse.py +424 -0
  209. core/hooks/_helpers/git_settings_fields.py +47 -0
  210. core/hooks/_helpers/graph_context_match.py +37 -0
  211. core/hooks/_helpers/graph_marker_check.py +70 -0
  212. core/hooks/_helpers/jit_recall.py +56 -0
  213. core/hooks/_helpers/json_field.py +41 -0
  214. core/hooks/_helpers/narrative_signal.py +59 -0
  215. core/hooks/_helpers/observation_count.py +31 -0
  216. core/hooks/_helpers/pre_commit_batch.py +177 -0
  217. core/hooks/_helpers/pre_commit_fake_input.py +42 -0
  218. core/hooks/_helpers/presence_gc.py +102 -0
  219. core/hooks/_helpers/presence_write.py +167 -0
  220. core/hooks/_helpers/recover_indirect.py +35 -0
  221. core/hooks/_helpers/routing_evolution.py +104 -0
  222. core/hooks/_helpers/session_recap.py +72 -0
  223. core/hooks/_helpers/skill_primer.py +229 -0
  224. core/hooks/_helpers/task_sync.py +59 -0
  225. core/hooks/_helpers/tool_failure_capture.py +147 -0
  226. core/hooks/_helpers/trajectory_autosnap.py +278 -0
  227. core/hooks/_helpers/trajectory_startup.py +62 -0
  228. core/hooks/_helpers/turn_summary.py +82 -0
  229. core/hooks/_helpers/validate_task_frontmatter.py +98 -0
  230. core/hooks/_helpers/wip_limit_check.py +103 -0
  231. core/hooks/_helpers/wip_lines.py +53 -0
  232. core/hooks/_helpers/work_log_append.py +89 -0
  233. core/hooks/_helpers/wrap_dispatch_output.py +82 -0
  234. core/hooks/advance-role.sh +48 -0
  235. core/hooks/agent-presence.sh +179 -0
  236. core/hooks/auto-brain-decay.sh +184 -0
  237. core/hooks/auto-compose-roles.sh +83 -0
  238. core/hooks/auto-graph-reconcile-shell.sh +119 -0
  239. core/hooks/auto-regen-doc-index.sh +120 -0
  240. core/hooks/auto-reindex-docs.sh +130 -0
  241. core/hooks/auto-task-sync.sh +56 -0
  242. core/hooks/auto-trace-rotate.sh +88 -0
  243. core/hooks/block-bad-patterns.sh +212 -0
  244. core/hooks/block-dangerous-commands.sh +182 -0
  245. core/hooks/block-hardcoded-literals.sh +90 -0
  246. core/hooks/block-migration-conflict.sh +114 -0
  247. core/hooks/block-protected-files.sh +129 -0
  248. core/hooks/block-secrets.sh +185 -0
  249. core/hooks/block-shared-tree-edit.sh +75 -0
  250. core/hooks/block-uv-heredoc.sh +78 -0
  251. core/hooks/branch-guard.sh +122 -0
  252. core/hooks/capture-observation.sh +76 -0
  253. core/hooks/capture-tool-failure.sh +24 -0
  254. core/hooks/capture-work-log.sh +89 -0
  255. core/hooks/check-agents-md-refs.sh +75 -0
  256. core/hooks/check-agents-md-size.sh +49 -0
  257. core/hooks/check-capture-worked.sh +148 -0
  258. core/hooks/check-doc-size.sh +61 -0
  259. core/hooks/check-mcp-extras.sh +92 -0
  260. core/hooks/check-state.sh +87 -0
  261. core/hooks/classify-task-mode.sh +103 -0
  262. core/hooks/cos-env.sh +1301 -0
  263. core/hooks/drain-embedding-outbox.sh +24 -0
  264. core/hooks/enforce-anti-ambiguity.sh +74 -0
  265. core/hooks/enforce-commit-message.sh +73 -0
  266. core/hooks/enforce-doc-anchor.sh +224 -0
  267. core/hooks/enforce-doc-sync.sh +206 -0
  268. core/hooks/enforce-graph-context.sh +89 -0
  269. core/hooks/enforce-graph-first-read.sh +94 -0
  270. core/hooks/enforce-memory-check.sh +128 -0
  271. core/hooks/enforce-rename-plan.sh +78 -0
  272. core/hooks/enforce-scaffold-boundary.sh +68 -0
  273. core/hooks/enforce-skill.sh +125 -0
  274. core/hooks/enforce-task-body.sh +52 -0
  275. core/hooks/enforce-task-start.sh +81 -0
  276. core/hooks/enforce-task-transition.sh +75 -0
  277. core/hooks/enforce-template.sh +143 -0
  278. core/hooks/enforce-verify.sh +112 -0
  279. core/hooks/enforce-wip-limit.sh +43 -0
  280. core/hooks/enforce-zoom.sh +69 -0
  281. core/hooks/ensure-hub-up.sh +67 -0
  282. core/hooks/inject-mcp-caller-session.sh +70 -0
  283. core/hooks/jit-recall.sh +65 -0
  284. core/hooks/link-commit-to-task.sh +143 -0
  285. core/hooks/lint-task.sh +40 -0
  286. core/hooks/nudge-docs-first.sh +71 -0
  287. core/hooks/nudge-git-mode.sh +29 -0
  288. core/hooks/nudge-graph-os.sh +118 -0
  289. core/hooks/nudge-learn-narrative.sh +36 -0
  290. core/hooks/nudge-model-routing.sh +32 -0
  291. core/hooks/nudge-reentry.sh +101 -0
  292. core/hooks/nudge-reuse-first.sh +68 -0
  293. core/hooks/nudge-task-discovery.sh +81 -0
  294. core/hooks/nudge-thinking-os.sh +109 -0
  295. core/hooks/pr-reap.sh +23 -0
  296. core/hooks/reclaim-sweep.sh +58 -0
  297. core/hooks/record-verify-auto.sh +77 -0
  298. core/hooks/record-verify.sh +74 -0
  299. core/hooks/regen-reminder.sh +104 -0
  300. core/hooks/registry.yaml +1262 -0
  301. core/hooks/remind-daily.sh +27 -0
  302. core/hooks/remind-dogfood.sh +70 -0
  303. core/hooks/remind-learn-validate.sh +94 -0
  304. core/hooks/rules-primer.sh +50 -0
  305. core/hooks/search-enforce-inventory.sh +108 -0
  306. core/hooks/search-verify-remaining.sh +132 -0
  307. core/hooks/session-context.sh +729 -0
  308. core/hooks/session-end.sh +145 -0
  309. core/hooks/session-skill-primer.sh +43 -0
  310. core/hooks/snapshot-transcript.sh +56 -0
  311. core/hooks/sync-task-current.sh +85 -0
  312. core/hooks/test-first-reminder.sh +120 -0
  313. core/hooks/test-governor.sh +173 -0
  314. core/hooks/thinking_os-gate.sh +52 -0
  315. core/hooks/track-backtrack.sh +35 -0
  316. core/hooks/track-discovery.sh +121 -0
  317. core/hooks/track-skill.sh +53 -0
  318. core/hooks/validate-task-frontmatter.sh +49 -0
  319. core/hooks/verify-rename-callers.sh +119 -0
  320. core/hooks/warn-abandoned-task.sh +99 -0
  321. core/hooks/warn-destructive-edit.sh +64 -0
  322. core/hooks/warn-diff-size.sh +43 -0
  323. core/hooks/warn-graph-empty.sh +81 -0
  324. core/hooks/warn-mcp-down.sh +190 -0
  325. core/hooks/write-state.sh +55 -0
  326. core/logging_os/__init__.py +33 -0
  327. core/logging_os/api.py +127 -0
  328. core/logging_os/bridge.py +80 -0
  329. core/logging_os/config.py +172 -0
  330. core/logging_os/fingerprint.py +25 -0
  331. core/logging_os/redact.py +53 -0
  332. core/logging_os/render.py +83 -0
  333. core/logging_os/sinks.py +164 -0
  334. core/rules/anti-overengineering.md +44 -0
  335. core/rules/api-contract-discipline.md +41 -0
  336. core/rules/dimension-registry.md +155 -0
  337. core/rules/git-workflow.md +57 -0
  338. core/rules/memory.md +46 -0
  339. core/rules/model-routing.md +22 -0
  340. core/rules/skill-enforcement.md +75 -0
  341. core/rules/test-discipline.md +38 -0
  342. core/rules/thinking_os.md +48 -0
  343. core/rules/transparency-banner.md +37 -0
  344. core/runtime_paths.yaml +36 -0
  345. core/scaffold_manifest.json +14430 -0
  346. core/scheduled/__init__.py +0 -0
  347. core/scheduled/_activity.py +126 -0
  348. core/scheduled/_state.py +113 -0
  349. core/scheduled/config.py +86 -0
  350. core/scheduled/dep_reconcile.py +135 -0
  351. core/scheduled/error_sweep.py +137 -0
  352. core/scheduled/nightly.py +930 -0
  353. core/scheduled/responsive_extract.py +65 -0
  354. core/schemas/adapter.schema.json +269 -0
  355. core/schemas/preset.schema.json +50 -0
  356. core/schemas/skill.schema.json +81 -0
  357. core/schemas/stack.schema.json +404 -0
  358. core/scripts/_lib.sh +9 -0
  359. core/scripts/docs-lint.sh +228 -0
  360. core/scripts/docs-nav-fix.sh +133 -0
  361. core/scripts/docs-staleness-check.sh +154 -0
  362. core/scripts/install-adapter.sh +266 -0
  363. core/scripts/link-stack-skills.sh +52 -0
  364. core/scripts/log-latest.sh +106 -0
  365. core/scripts/log-search.sh +89 -0
  366. core/scripts/log-write.sh +134 -0
  367. core/scripts/ref-resolve.sh +71 -0
  368. core/skills/a11y/SKILL.md +305 -0
  369. core/skills/a11y/assets/a11y-checklist.md +137 -0
  370. core/skills/a11y/references/aria-and-focus.md +247 -0
  371. core/skills/a11y/references/rn-accessibility.md +343 -0
  372. core/skills/a11y/references/screen-reader-testing.md +190 -0
  373. core/skills/agent-memory/SKILL.md +191 -0
  374. core/skills/agent-memory/assets/memory-checklist.md +21 -0
  375. core/skills/agent-memory/references/memory-recipes.md +57 -0
  376. core/skills/api-design/SKILL.md +232 -0
  377. core/skills/api-design/assets/api-design-checklist.md +110 -0
  378. core/skills/api-design/references/error-envelope.md +381 -0
  379. core/skills/api-design/references/idempotency-pagination.md +312 -0
  380. core/skills/api-design/references/rest-contracts.md +426 -0
  381. core/skills/auth-patterns/SKILL.md +352 -0
  382. core/skills/auth-patterns/assets/auth-checklist.md +118 -0
  383. core/skills/auth-patterns/references/jwt-and-service-tokens.md +343 -0
  384. core/skills/auth-patterns/references/passkeys-2fa.md +289 -0
  385. core/skills/auth-patterns/references/sessions-vs-jwt.md +230 -0
  386. core/skills/auth-patterns/scripts/cookie-flag-check.py +146 -0
  387. core/skills/backend-fundamentals/SKILL.md +238 -0
  388. core/skills/backend-fundamentals/assets/backend-checklist.md +27 -0
  389. core/skills/backend-fundamentals/references/backend-patterns.md +56 -0
  390. core/skills/backend-fundamentals/scripts/check_layering.py +83 -0
  391. core/skills/clean-code/SKILL.md +642 -0
  392. core/skills/clean-code/scripts/audit-fail-closed.py +167 -0
  393. core/skills/codebase-explorer/SKILL.md +89 -0
  394. core/skills/codebase-explorer/assets/reading-checklist.md +24 -0
  395. core/skills/codebase-explorer/references/reading-strategies.md +52 -0
  396. core/skills/codebase-explorer/scripts/outline.py +99 -0
  397. core/skills/db-design/SKILL.md +327 -0
  398. core/skills/db-design/assets/migration-template.sql +49 -0
  399. core/skills/db-design/references/migration-discipline.md +290 -0
  400. core/skills/db-design/references/postgres-patterns.md +340 -0
  401. core/skills/db-design/scripts/migration-safety.sh +150 -0
  402. core/skills/deployment-cicd/SKILL.md +260 -0
  403. core/skills/deployment-cicd/assets/deploy-checklist.md +26 -0
  404. core/skills/deployment-cicd/references/pipeline-and-release.md +54 -0
  405. core/skills/deployment-cicd/scripts/lint_workflow.py +79 -0
  406. core/skills/docker/SKILL.md +114 -0
  407. core/skills/docker/assets/dockerfile-checklist.md +31 -0
  408. core/skills/docker/references/compose-patterns.md +66 -0
  409. core/skills/docker/references/dockerfile-optimization.md +64 -0
  410. core/skills/docker/scripts/lint_dockerfile.sh +48 -0
  411. core/skills/docker/versions.json +16 -0
  412. core/skills/end-to-end-testing/SKILL.md +101 -0
  413. core/skills/end-to-end-testing/assets/e2e-checklist.md +23 -0
  414. core/skills/end-to-end-testing/references/maestro.md +63 -0
  415. core/skills/end-to-end-testing/references/playwright.md +68 -0
  416. core/skills/end-to-end-testing/scripts/lint_e2e.py +88 -0
  417. core/skills/end-to-end-testing/versions.json +16 -0
  418. core/skills/frontend-design/SKILL.md +76 -0
  419. core/skills/frontend-design/assets/design-checklist.md +29 -0
  420. core/skills/frontend-design/references/design-principles.md +65 -0
  421. core/skills/frontend-design/scripts/check_contrast.py +89 -0
  422. core/skills/frontend-fundamentals/SKILL.md +213 -0
  423. core/skills/frontend-fundamentals/assets/frontend-checklist.md +25 -0
  424. core/skills/frontend-fundamentals/references/rendering-and-state.md +66 -0
  425. core/skills/frontend-fundamentals/scripts/check_frontend.py +86 -0
  426. core/skills/graph-explorer/SKILL.md +215 -0
  427. core/skills/graph-explorer/scripts/explain-impact.sh +64 -0
  428. core/skills/graphql/SKILL.md +187 -0
  429. core/skills/grpc-microservices/SKILL.md +174 -0
  430. core/skills/hexagonal-architecture/SKILL.md +199 -0
  431. core/skills/hexagonal-architecture/assets/folder-scaffold.md +233 -0
  432. core/skills/hexagonal-architecture/references/anti-patterns.md +129 -0
  433. core/skills/hexagonal-architecture/references/go-fiber-layout.md +429 -0
  434. core/skills/hexagonal-architecture/references/python-fastapi-layout.md +453 -0
  435. core/skills/hexagonal-architecture/references/react-native-layout.md +428 -0
  436. core/skills/i18n/SKILL.md +126 -0
  437. core/skills/incident-response/SKILL.md +225 -0
  438. core/skills/incident-response/assets/incident-checklist.md +29 -0
  439. core/skills/incident-response/references/severity-and-runbook.md +53 -0
  440. core/skills/incident-response/scripts/classify_severity.py +86 -0
  441. core/skills/linux-sysadmin/SKILL.md +115 -0
  442. core/skills/linux-sysadmin/assets/hardening-checklist.md +29 -0
  443. core/skills/linux-sysadmin/references/ssh-hardening.md +62 -0
  444. core/skills/linux-sysadmin/references/systemd-and-services.md +77 -0
  445. core/skills/linux-sysadmin/scripts/triage.sh +50 -0
  446. core/skills/linux-sysadmin/versions.json +17 -0
  447. core/skills/llm-patterns/SKILL.md +410 -0
  448. core/skills/llm-patterns/assets/llm-feature-checklist.md +26 -0
  449. core/skills/llm-patterns/references/rag-and-evals.md +59 -0
  450. core/skills/llm-patterns/scripts/estimate_tokens.py +75 -0
  451. core/skills/messaging-queues/SKILL.md +142 -0
  452. core/skills/mobile-fundamentals/SKILL.md +406 -0
  453. core/skills/mobile-fundamentals/assets/mobile-launch-checklist.md +130 -0
  454. core/skills/mobile-fundamentals/references/navigation-and-deep-links.md +337 -0
  455. core/skills/mobile-fundamentals/references/offline-sync.md +339 -0
  456. core/skills/node-backend/SKILL.md +114 -0
  457. core/skills/node-backend/assets/node-checklist.md +25 -0
  458. core/skills/node-backend/references/async-and-errors.md +67 -0
  459. core/skills/node-backend/references/event-loop.md +65 -0
  460. core/skills/node-backend/scripts/check_package.py +78 -0
  461. core/skills/node-backend/versions.json +17 -0
  462. core/skills/observability/SKILL.md +289 -0
  463. core/skills/observability/assets/observability-checklist.md +27 -0
  464. core/skills/observability/references/instrumentation.md +57 -0
  465. core/skills/observability/scripts/lint_logging.py +77 -0
  466. core/skills/payments/SKILL.md +102 -0
  467. core/skills/performance/SKILL.md +305 -0
  468. core/skills/performance/assets/perf-checklist.md +143 -0
  469. core/skills/performance/references/mobile-performance.md +249 -0
  470. core/skills/performance/references/web-vitals.md +209 -0
  471. core/skills/php/SKILL.md +116 -0
  472. core/skills/php/assets/php-checklist.md +26 -0
  473. core/skills/php/references/modern-php.md +64 -0
  474. core/skills/php/references/security.md +72 -0
  475. core/skills/php/scripts/scan_php_smells.py +99 -0
  476. core/skills/php/versions.json +9 -0
  477. core/skills/pr-mode-driver/SKILL.md +65 -0
  478. core/skills/realtime-websockets/SKILL.md +152 -0
  479. core/skills/redis/SKILL.md +105 -0
  480. core/skills/redis/assets/redis-checklist.md +27 -0
  481. core/skills/redis/references/operations.md +66 -0
  482. core/skills/redis/references/patterns.md +62 -0
  483. core/skills/redis/scripts/analyze_info.py +101 -0
  484. core/skills/redis/versions.json +9 -0
  485. core/skills/search/SKILL.md +91 -0
  486. core/skills/search/references/grep.md +76 -0
  487. core/skills/search/scripts/verify-count.sh +73 -0
  488. core/skills/search-infra/SKILL.md +109 -0
  489. core/skills/security-mobile/SKILL.md +394 -0
  490. core/skills/security-mobile/assets/mobile-security-checklist.md +117 -0
  491. core/skills/security-mobile/references/masvs-l1-checklist.md +127 -0
  492. core/skills/security-web/SKILL.md +217 -0
  493. core/skills/security-web/assets/security-web-checklist.md +167 -0
  494. core/skills/security-web/references/owasp-top-10.md +551 -0
  495. core/skills/security-web/references/supply-chain.md +179 -0
  496. core/skills/security-web/scripts/csp-check.sh +153 -0
  497. core/skills/shell-scripting/SKILL.md +128 -0
  498. core/skills/shell-scripting/assets/script-checklist.md +33 -0
  499. core/skills/shell-scripting/references/argument-parsing.md +84 -0
  500. core/skills/shell-scripting/references/bash-robustness.md +78 -0
  501. core/skills/shell-scripting/scripts/lint_script.sh +53 -0
  502. core/skills/shell-scripting/scripts/new_script.py +131 -0
  503. core/skills/shell-scripting/versions.json +23 -0
  504. core/skills/sql-authoring/SKILL.md +104 -0
  505. core/skills/sql-authoring/assets/query-review-checklist.md +26 -0
  506. core/skills/sql-authoring/references/query-patterns.md +89 -0
  507. core/skills/sql-authoring/references/reading-explain.md +52 -0
  508. core/skills/sql-authoring/scripts/analyze_plan.py +100 -0
  509. core/skills/sql-authoring/versions.json +17 -0
  510. core/skills/state-management/SKILL.md +428 -0
  511. core/skills/state-management/references/tanstack-query-recipes.md +301 -0
  512. core/skills/state-management/references/zustand-recipes.md +364 -0
  513. core/skills/supabase/SKILL.md +108 -0
  514. core/skills/supabase/assets/supabase-checklist.md +26 -0
  515. core/skills/supabase/references/realtime-and-storage.md +55 -0
  516. core/skills/supabase/references/rls-and-auth.md +65 -0
  517. core/skills/supabase/scripts/check_rls.py +88 -0
  518. core/skills/supabase/versions.json +9 -0
  519. core/skills/task-driver/SKILL.md +272 -0
  520. core/skills/task-driver/scripts/task-lint.sh +163 -0
  521. core/skills/technical-writing/SKILL.md +85 -0
  522. core/skills/technical-writing/assets/doc-checklist.md +29 -0
  523. core/skills/technical-writing/references/doc-anatomy.md +53 -0
  524. core/skills/technical-writing/references/writing-craft.md +59 -0
  525. core/skills/technical-writing/scripts/new_doc.py +81 -0
  526. core/skills/terraform-k8s/SKILL.md +133 -0
  527. core/skills/testing-strategy/SKILL.md +266 -0
  528. core/skills/testing-strategy/assets/test-review-checklist.md +25 -0
  529. core/skills/testing-strategy/references/test-types.md +52 -0
  530. core/skills/testing-strategy/scripts/coverage_gate.py +79 -0
  531. core/skills/thinking_os/SKILL.md +288 -0
  532. core/skills/thinking_os/scripts/classify.sh +122 -0
  533. core/skills/typescript/SKILL.md +110 -0
  534. core/skills/typescript/assets/typescript-checklist.md +24 -0
  535. core/skills/typescript/references/strictness.md +53 -0
  536. core/skills/typescript/references/type-system.md +86 -0
  537. core/skills/typescript/scripts/check_tsconfig.py +92 -0
  538. core/skills/typescript/versions.json +9 -0
  539. core/subsystems.yaml +202 -0
  540. core/thinking_os/__init__.py +1 -0
  541. core/thinking_os/_agent_markers.py +32 -0
  542. core/thinking_os/agents/README.md +71 -0
  543. core/thinking_os/agents/analyst.md +139 -0
  544. core/thinking_os/agents/architect.md +127 -0
  545. core/thinking_os/agents/debugger.md +111 -0
  546. core/thinking_os/agents/deployer.md +112 -0
  547. core/thinking_os/agents/distiller.md +28 -0
  548. core/thinking_os/agents/documenter.md +101 -0
  549. core/thinking_os/agents/implementer.md +135 -0
  550. core/thinking_os/agents/internal/session_observer.md +39 -0
  551. core/thinking_os/agents/observer.md +100 -0
  552. core/thinking_os/agents/onboarder.md +81 -0
  553. core/thinking_os/agents/refactorer.md +123 -0
  554. core/thinking_os/agents/repairer.md +47 -0
  555. core/thinking_os/agents/researcher.md +129 -0
  556. core/thinking_os/agents/reviewer.md +147 -0
  557. core/thinking_os/agents/security_auditor.md +133 -0
  558. core/thinking_os/background.py +405 -0
  559. core/thinking_os/bootstrap_outcomes.py +200 -0
  560. core/thinking_os/budget.py +302 -0
  561. core/thinking_os/capture.py +495 -0
  562. core/thinking_os/cognition.py +516 -0
  563. core/thinking_os/cognition_schemas.py +517 -0
  564. core/thinking_os/compress.py +192 -0
  565. core/thinking_os/concepts.py +233 -0
  566. core/thinking_os/dashboard.py +159 -0
  567. core/thinking_os/database.py +2883 -0
  568. core/thinking_os/decay.py +393 -0
  569. core/thinking_os/digest.py +295 -0
  570. core/thinking_os/dispatcher.py +192 -0
  571. core/thinking_os/dispatcher_helpers.py +48 -0
  572. core/thinking_os/dispatchers/__init__.py +3 -0
  573. core/thinking_os/dispatchers/default.py +47 -0
  574. core/thinking_os/distill.py +192 -0
  575. core/thinking_os/doc_indexer.py +905 -0
  576. core/thinking_os/embeddings.py +943 -0
  577. core/thinking_os/formula_composer.py +556 -0
  578. core/thinking_os/gate_marker.py +75 -0
  579. core/thinking_os/graph.py +296 -0
  580. core/thinking_os/graph_indexer.py +360 -0
  581. core/thinking_os/health_check.py +517 -0
  582. core/thinking_os/impact.py +119 -0
  583. core/thinking_os/memory_gc.py +356 -0
  584. core/thinking_os/migrator_embeddings.py +318 -0
  585. core/thinking_os/precision.py +194 -0
  586. core/thinking_os/presets/registry.yaml +127 -0
  587. core/thinking_os/record_outcome.py +399 -0
  588. core/thinking_os/repair.py +105 -0
  589. core/thinking_os/retrieval_quality.py +239 -0
  590. core/thinking_os/roles/analyst.yaml +83 -0
  591. core/thinking_os/roles/architect.yaml +88 -0
  592. core/thinking_os/roles/debugger.yaml +71 -0
  593. core/thinking_os/roles/deployer.yaml +71 -0
  594. core/thinking_os/roles/documenter.yaml +72 -0
  595. core/thinking_os/roles/implementer.yaml +83 -0
  596. core/thinking_os/roles/observer.yaml +70 -0
  597. core/thinking_os/roles/refactorer.yaml +71 -0
  598. core/thinking_os/roles/researcher.yaml +72 -0
  599. core/thinking_os/roles/reviewer.yaml +79 -0
  600. core/thinking_os/roles/security_auditor.yaml +83 -0
  601. core/thinking_os/roles_state.py +168 -0
  602. core/thinking_os/sanitizer.py +320 -0
  603. core/thinking_os/server.py +3160 -0
  604. core/thinking_os/session_enrich.py +272 -0
  605. core/thinking_os/session_observe_worker.py +111 -0
  606. core/thinking_os/session_startup.py +74 -0
  607. core/thinking_os/session_summary.py +245 -0
  608. core/thinking_os/situations/registry.yaml +99 -0
  609. core/thinking_os/task_analyzer.py +462 -0
  610. core/thinking_os/task_parser.py +342 -0
  611. core/thinking_os/task_sync.py +73 -0
  612. core/thinking_os/tools/__init__.py +6 -0
  613. core/thinking_os/tools/_shared.py +947 -0
  614. core/thinking_os/tools/cognition.py +1867 -0
  615. core/thinking_os/tools/docs.py +770 -0
  616. core/thinking_os/tools/learning.py +2078 -0
  617. core/thinking_os/tools/logs.py +79 -0
  618. core/thinking_os/tools/memory.py +840 -0
  619. core/thinking_os/tools/metrics.py +200 -0
  620. core/thinking_os/tools/retrieve.py +415 -0
  621. core/thinking_os/tools/routing.py +658 -0
  622. core/thinking_os/tools/tasks.py +449 -0
  623. core/thinking_os/tools/trajectory.py +181 -0
  624. core/thinking_os/tracing.py +235 -0
  625. core/web/__init__.py +5 -0
  626. core/web/_cache.py +118 -0
  627. core/web/_deps.py +56 -0
  628. core/web/_envelope.py +85 -0
  629. core/web/_project_context.py +140 -0
  630. core/web/chat_providers.py +108 -0
  631. core/web/init_jobs.py +216 -0
  632. core/web/routes/__init__.py +25 -0
  633. core/web/routes/_bounded_read.py +76 -0
  634. core/web/routes/board.py +1089 -0
  635. core/web/routes/cognition.py +1838 -0
  636. core/web/routes/config.py +635 -0
  637. core/web/routes/graph.py +513 -0
  638. core/web/routes/health.py +180 -0
  639. core/web/routes/hooks.py +288 -0
  640. core/web/routes/hub.py +1219 -0
  641. core/web/routes/logs.py +374 -0
  642. core/web/routes/metrics.py +43 -0
  643. core/web/routes/observability.py +400 -0
  644. core/web/routes/patterns.py +227 -0
  645. core/web/routes/presence.py +609 -0
  646. core/web/routes/roles.py +446 -0
  647. core/web/routes/scheduled.py +261 -0
  648. core/web/routes/search.py +238 -0
  649. core/web/routes/sessions.py +220 -0
  650. core/web/routes/settings.py +363 -0
  651. core/web/routes/stream.py +547 -0
  652. core/web/security.py +157 -0
  653. core/web/server.py +283 -0
  654. graph_os/__init__.py +29 -0
  655. graph_os/backend.py +233 -0
  656. graph_os/backends/__init__.py +13 -0
  657. graph_os/backends/sqlite_backend.py +1053 -0
  658. graph_os/communities.py +410 -0
  659. graph_os/enterprise.py +218 -0
  660. graph_os/entry_points.py +226 -0
  661. graph_os/extractors/__init__.py +25 -0
  662. graph_os/extractors/code_generic.py +914 -0
  663. graph_os/extractors/code_go.py +1422 -0
  664. graph_os/extractors/code_json.py +340 -0
  665. graph_os/extractors/code_php.py +979 -0
  666. graph_os/extractors/code_python.py +1454 -0
  667. graph_os/extractors/code_shell.py +538 -0
  668. graph_os/extractors/code_toml.py +302 -0
  669. graph_os/extractors/code_ts.py +1665 -0
  670. graph_os/extractors/code_yaml.py +394 -0
  671. graph_os/extractors/contracts.py +1592 -0
  672. graph_os/extractors/md_links.py +890 -0
  673. graph_os/extractors/task_deps.py +345 -0
  674. graph_os/groups/__init__.py +22 -0
  675. graph_os/groups/cross_repo.py +156 -0
  676. graph_os/groups/manifest.py +141 -0
  677. graph_os/ingest/__init__.py +19 -0
  678. graph_os/ingest/base.py +306 -0
  679. graph_os/ingest/github.py +112 -0
  680. graph_os/ingest/zip.py +95 -0
  681. graph_os/toolchain.py +393 -0
  682. graph_os/tools/__init__.py +9 -0
  683. graph_os/tools/graph.py +5573 -0
  684. graph_os/tools/reindex_dispatch.py +730 -0
  685. graph_os/tree_sitter_overlay.py +235 -0
  686. graph_os/types.py +252 -0
  687. graph_os/vec_index.py +277 -0
  688. graph_os/viewer/__init__.py +12 -0
  689. graph_os/viewer/exporter.py +93 -0
  690. graph_os/viewer/template.py +189 -0
  691. scheduled/__init__.py +0 -0
  692. scheduled/_activity.py +126 -0
  693. scheduled/_state.py +113 -0
  694. scheduled/config.py +86 -0
  695. scheduled/dep_reconcile.py +135 -0
  696. scheduled/error_sweep.py +137 -0
  697. scheduled/nightly.py +930 -0
  698. scheduled/responsive_extract.py +65 -0
  699. scripts/__init__.py +4 -0
  700. scripts/_commit_msg_body.sh +31 -0
  701. scripts/_post_commit_body.sh +49 -0
  702. scripts/_pre_commit_body.sh +121 -0
  703. scripts/_prepare_commit_msg_body.sh +53 -0
  704. scripts/audit_mcp_tools.py +693 -0
  705. scripts/bench_sdk_dispatcher.py +177 -0
  706. scripts/capture_golden.py +169 -0
  707. scripts/check_graph_phantoms.py +75 -0
  708. scripts/dev/audit_doc_links.py +359 -0
  709. scripts/dev/audit_scaffold_module_tags.py +107 -0
  710. scripts/dev/backfill_doc_headers.py +328 -0
  711. scripts/dev/backfill_nav_lines.py +119 -0
  712. scripts/dev/fix_nav_placement.py +106 -0
  713. scripts/dev/inspect_sdk_options.py +45 -0
  714. scripts/dev/migrate_check_ids.py +170 -0
  715. scripts/dev/strip_purpose_blocks.py +154 -0
  716. scripts/dump_openapi.py +66 -0
  717. scripts/e2e_dispatch_tool.py +195 -0
  718. scripts/generate_manifest.py +166 -0
  719. scripts/golden_sections.py +20 -0
  720. scripts/graph_demo.py +161 -0
  721. scripts/install-git-hooks.sh +47 -0
  722. scripts/migrate_embeddings_minilm_to_bge_m3.py +84 -0
  723. scripts/operational_eval.py +445 -0
  724. scripts/probe_agent_session_resolver.py +59 -0
  725. scripts/prune_deleted_path.py +127 -0
  726. scripts/refactor_agent_dual_mode.py +171 -0
  727. scripts/refresh_skill_versions.py +302 -0
  728. scripts/regen_doc_index.py +209 -0
  729. scripts/regen_doctor_schema.py +62 -0
  730. scripts/regen_rules.py +94 -0
  731. scripts/rename_formulas_to_semantic.py +241 -0
  732. scripts/smoke_db_connections.py +183 -0
  733. scripts/smoke_doc_header.py +72 -0
  734. scripts/smoke_graph_e2e.py +374 -0
  735. scripts/smoke_sdk_dispatch.py +84 -0
  736. scripts/smoke_uid_resolver.py +164 -0
  737. scripts/verify_dispatchers.py +244 -0
  738. scripts/verify_phase_c_e2e.py +436 -0
  739. templates/__init__.py +6 -0
  740. templates/_base/Makefile.base +353 -0
  741. templates/_base/base.yaml +59 -0
  742. templates/_base/coding-os.yaml.template +39 -0
  743. templates/_base/dimension-registry.template.md +68 -0
  744. templates/_base/domain-config.template.json +46 -0
  745. templates/_base/fragments/anatomy-map.md.tmpl +11 -0
  746. templates/_base/fragments/context-discipline.md.tmpl +3 -0
  747. templates/_base/fragments/core-loop.md.tmpl +52 -0
  748. templates/_base/fragments/engineering-routing.md.tmpl +3 -0
  749. templates/_base/fragments/header.md.tmpl +6 -0
  750. templates/_base/fragments/identity.md.tmpl +3 -0
  751. templates/_base/fragments/principles.md.tmpl +3 -0
  752. templates/_base/fragments/retrieval-routing.md.tmpl +23 -0
  753. templates/_base/fragments/session-handoff.md.tmpl +3 -0
  754. templates/_base/fragments/skills.md.tmpl +3 -0
  755. templates/_base/fragments/ssot-map.md.tmpl +3 -0
  756. templates/_base/fragments/stop-conditions.md.tmpl +3 -0
  757. templates/_base/fragments/subagent-dispatch.md.tmpl +3 -0
  758. templates/_base/fragments/task-authoring.md.tmpl +69 -0
  759. templates/_base/fragments/task-logging.md.tmpl +8 -0
  760. templates/_base/fragments/tool-routing.md.tmpl +9 -0
  761. templates/_base/fragments/verification-matrix.md.tmpl +12 -0
  762. templates/_base/lang/dart/analysis_options.yaml +7 -0
  763. templates/_base/lang/php/phpcs.xml.dist +10 -0
  764. templates/_base/lang/python/pyproject.toml +19 -0
  765. templates/_base/lang/rust/clippy.toml +5 -0
  766. templates/_base/lang/rust/rustfmt.toml +3 -0
  767. templates/_base/lang/typescript/eslint.config.js +26 -0
  768. templates/_base/lang/typescript/tsconfig.json +15 -0
  769. templates/_base/lang/typescript/vitest.config.ts +10 -0
  770. templates/_base/scaffold/changes.log +1 -0
  771. templates/_base/scaffold/docs/00-index.md +55 -0
  772. templates/_base/scaffold/docs/_meta/feature-dependency-tree.md +30 -0
  773. templates/_base/scaffold/docs/_meta/foundation-map.md +56 -0
  774. templates/_base/scaffold/docs/_meta/questions.md +8 -0
  775. templates/_base/scaffold/docs/_meta/roadmap.md +33 -0
  776. templates/_base/scaffold/docs/api-contracts/00-index.md +58 -0
  777. templates/_base/scaffold/docs/api-contracts/error-format.md +58 -0
  778. templates/_base/scaffold/docs/architecture/00-index.md +42 -0
  779. templates/_base/scaffold/docs/architecture/adr/00-index.md +39 -0
  780. templates/_base/scaffold/docs/engineering/00-index.md +9 -0
  781. templates/_base/scaffold/docs/governance/00-index.md +55 -0
  782. templates/_base/scaffold/docs/governance/_templates/doc-cheat-sheet.md +202 -0
  783. templates/_base/scaffold/docs/governance/_templates/playbook-template.md +81 -0
  784. templates/_base/scaffold/docs/governance/_templates/post-mortem-template.md +85 -0
  785. templates/_base/scaffold/docs/governance/_templates/runbook-template.md +88 -0
  786. templates/_base/scaffold/docs/governance/_templates/security-review-template.md +111 -0
  787. templates/_base/scaffold/docs/governance/_templates/task-detail.md +61 -0
  788. templates/_base/scaffold/docs/governance/agent-workflow.md +101 -0
  789. templates/_base/scaffold/docs/governance/anatomy-contract.md +150 -0
  790. templates/_base/scaffold/docs/governance/critical-rules.md +224 -0
  791. templates/_base/scaffold/docs/governance/decision-records.md +58 -0
  792. templates/_base/scaffold/docs/governance/docs-first-protocol.md +157 -0
  793. templates/_base/scaffold/docs/governance/docs-system.md +151 -0
  794. templates/_base/scaffold/docs/governance/gdpr-compliance.md +66 -0
  795. templates/_base/scaffold/docs/governance/mcp-tool-inventory.md +112 -0
  796. templates/_base/scaffold/docs/governance/risk-register.md +26 -0
  797. templates/_base/scaffold/docs/governance/scaffold-boundary-contract.md +161 -0
  798. templates/_base/scaffold/docs/governance/task-lifecycle.md +125 -0
  799. templates/_base/scaffold/docs/governance/wrapper-derivation.md +50 -0
  800. templates/_base/scaffold/docs/insights/00-index.md +17 -0
  801. templates/_base/scaffold/docs/ops/00-index.md +59 -0
  802. templates/_base/scaffold/docs/ops/runbooks/00-index.md +9 -0
  803. templates/_base/scaffold/docs/playbooks/00-index.md +12 -0
  804. templates/_base/scaffold/docs/playbooks/research-validation.md +29 -0
  805. templates/_base/scaffold/docs/playbooks/security-review.md +41 -0
  806. templates/_base/scaffold/docs/prd/00-index.md +43 -0
  807. templates/_base/scaffold/docs/prd/01-snapshot-vision.md +56 -0
  808. templates/_base/scaffold/docs/workflow/workflow-guide.md +138 -0
  809. templates/_base/scaffold/src/shared/README.md +23 -0
  810. templates/_base/skill-enforcement.template.md +14 -0
  811. templates/_base/task-detail.template.md +61 -0
  812. templates/_presets/ai-saas.yaml +9 -0
  813. templates/_presets/django-next.yaml +8 -0
  814. templates/_presets/dotnet-react.yaml +8 -0
  815. templates/_presets/flutter-baas.yaml +8 -0
  816. templates/_presets/go-react.yaml +8 -0
  817. templates/_presets/hexagonal-product.yaml +16 -0
  818. templates/_presets/jamstack.yaml +8 -0
  819. templates/_presets/laravel-vue.yaml +8 -0
  820. templates/_presets/mean.yaml +8 -0
  821. templates/_presets/mern.yaml +9 -0
  822. templates/_presets/nest-angular.yaml +8 -0
  823. templates/_presets/nextjs-fastapi.yaml +10 -0
  824. templates/_presets/nuxt-fullstack.yaml +9 -0
  825. templates/_presets/pern.yaml +9 -0
  826. templates/_presets/rails-react.yaml +8 -0
  827. templates/_presets/rn-api.yaml +8 -0
  828. templates/_presets/rust-svelte.yaml +8 -0
  829. templates/_presets/spring-react.yaml +8 -0
  830. templates/_presets/t3-style.yaml +9 -0
  831. templates/_presets/tall.yaml +8 -0
  832. templates/_presets/wordpress-cms.yaml +8 -0
  833. templates/angular/rules/frontend.md +19 -0
  834. templates/angular/scaffold/docs/engineering/accessibility.md +46 -0
  835. templates/angular/scaffold/docs/engineering/angular-rules.md +35 -0
  836. templates/angular/scaffold/docs/playbooks/angular-app.md +42 -0
  837. templates/angular/scaffold/src/frontend/angular.json +52 -0
  838. templates/angular/scaffold/src/frontend/package.json +28 -0
  839. templates/angular/scaffold/src/frontend/src/app/app.component.ts +19 -0
  840. templates/angular/scaffold/src/frontend/src/app/app.config.ts +22 -0
  841. templates/angular/scaffold/src/frontend/src/app/app.routes.ts +8 -0
  842. templates/angular/scaffold/src/frontend/src/app/core/global-error-handler.ts +12 -0
  843. templates/angular/scaffold/src/frontend/src/app/health/health.component.ts +14 -0
  844. templates/angular/scaffold/src/frontend/src/app/health/health.service.spec.ts +16 -0
  845. templates/angular/scaffold/src/frontend/src/app/health/health.service.ts +10 -0
  846. templates/angular/scaffold/src/frontend/src/index.html +11 -0
  847. templates/angular/scaffold/src/frontend/src/main.ts +9 -0
  848. templates/angular/scaffold/src/frontend/src/styles.css +14 -0
  849. templates/angular/scaffold/src/frontend/tsconfig.app.json +8 -0
  850. templates/angular/scaffold/src/frontend/tsconfig.json +27 -0
  851. templates/angular/scaffold/src/frontend/tsconfig.spec.json +8 -0
  852. templates/angular/scaffold-boundary.yaml +27 -0
  853. templates/angular/skills/angular/SKILL.md +79 -0
  854. templates/angular/skills/angular/references/anatomy.md +69 -0
  855. templates/angular/stack.yaml +75 -0
  856. templates/aspnet-core/rules/backend.md +20 -0
  857. templates/aspnet-core/scaffold/docs/engineering/aspnet-core-rules.md +36 -0
  858. templates/aspnet-core/scaffold/docs/playbooks/aspnet-core-service.md +40 -0
  859. templates/aspnet-core/scaffold/src/backend/Backend.csproj +11 -0
  860. templates/aspnet-core/scaffold/src/backend/Backend.sln +27 -0
  861. templates/aspnet-core/scaffold/src/backend/Common/ExceptionHandlingMiddleware.cs +35 -0
  862. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthEndpoints.cs +10 -0
  863. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthService.cs +9 -0
  864. templates/aspnet-core/scaffold/src/backend/Program.cs +22 -0
  865. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/Backend.Tests.csproj +22 -0
  866. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/HealthServiceTests.cs +15 -0
  867. templates/aspnet-core/scaffold-boundary.yaml +24 -0
  868. templates/aspnet-core/skills/aspnet-core/SKILL.md +80 -0
  869. templates/aspnet-core/skills/aspnet-core/references/anatomy.md +65 -0
  870. templates/aspnet-core/stack.yaml +68 -0
  871. templates/astro/rules/frontend.md +20 -0
  872. templates/astro/scaffold/docs/engineering/astro-rules.md +40 -0
  873. templates/astro/scaffold/docs/playbooks/astro-app.md +57 -0
  874. templates/astro/scaffold/docs/playbooks/content-seo.md +37 -0
  875. templates/astro/scaffold/src/frontend/astro.config.mjs +10 -0
  876. templates/astro/scaffold/src/frontend/package.json +23 -0
  877. templates/astro/scaffold/src/frontend/src/components/Greeting.astro +13 -0
  878. templates/astro/scaffold/src/frontend/src/content/posts/hello.md +10 -0
  879. templates/astro/scaffold/src/frontend/src/content.config.ts +19 -0
  880. templates/astro/scaffold/src/frontend/src/lib/problem.test.ts +39 -0
  881. templates/astro/scaffold/src/frontend/src/lib/problem.ts +30 -0
  882. templates/astro/scaffold/src/frontend/src/pages/api/health.ts +13 -0
  883. templates/astro/scaffold/src/frontend/src/pages/index.astro +21 -0
  884. templates/astro/scaffold/src/frontend/tsconfig.json +9 -0
  885. templates/astro/scaffold/src/frontend/vitest.config.ts +10 -0
  886. templates/astro/scaffold-boundary.yaml +28 -0
  887. templates/astro/skills/astro/SKILL.md +71 -0
  888. templates/astro/skills/astro/references/anatomy.md +66 -0
  889. templates/astro/stack.yaml +75 -0
  890. templates/csharp-plain/scaffold/src/backend/Backend.csproj +12 -0
  891. templates/csharp-plain/scaffold/src/backend/Program.cs +1 -0
  892. templates/csharp-plain/scaffold-boundary.yaml +23 -0
  893. templates/csharp-plain/stack.yaml +50 -0
  894. templates/django/rules/backend.md +18 -0
  895. templates/django/scaffold/docs/engineering/anti-ambiguity.md +74 -0
  896. templates/django/scaffold/docs/engineering/backend-rules.md +133 -0
  897. templates/django/scaffold/docs/engineering/glossary.md +51 -0
  898. templates/django/scaffold/docs/engineering/logging-standards.md +107 -0
  899. templates/django/scaffold/docs/engineering/naming-conventions.md +68 -0
  900. templates/django/scaffold/docs/engineering/secrets-rotation-runbook.md +142 -0
  901. templates/django/scaffold/docs/playbooks/backend-api.md +119 -0
  902. templates/django/scaffold/src/backend/config/__init__.py +0 -0
  903. templates/django/scaffold/src/backend/config/settings.py +31 -0
  904. templates/django/scaffold/src/backend/config/urls.py +11 -0
  905. templates/django/scaffold/src/backend/config/wsgi.py +6 -0
  906. templates/django/scaffold/src/backend/manage.py +14 -0
  907. templates/django/scaffold/src/backend/pyproject.toml +37 -0
  908. templates/django/scaffold/src/backend/tests/test_health.py +4 -0
  909. templates/django/scaffold-boundary.yaml +25 -0
  910. templates/django/skills/python-django/SKILL.md +450 -0
  911. templates/django/skills/python-django/references/anatomy.md +117 -0
  912. templates/django/skills/python-django/scripts/new_endpoint.py +89 -0
  913. templates/django/stack.yaml +73 -0
  914. templates/fastapi/rules/backend.md +18 -0
  915. templates/fastapi/scaffold/docs/engineering/fastapi-rules.md +37 -0
  916. templates/fastapi/scaffold/docs/playbooks/fastapi-service.md +30 -0
  917. templates/fastapi/scaffold/src/backend/app/__init__.py +0 -0
  918. templates/fastapi/scaffold/src/backend/app/main.py +8 -0
  919. templates/fastapi/scaffold/src/backend/pyproject.toml +39 -0
  920. templates/fastapi/scaffold/src/backend/tests/test_health.py +11 -0
  921. templates/fastapi/scaffold-boundary.yaml +25 -0
  922. templates/fastapi/skills/python-fastapi/SKILL.md +75 -0
  923. templates/fastapi/skills/python-fastapi/references/anatomy.md +117 -0
  924. templates/fastapi/skills/python-fastapi/scripts/new_endpoint.py +101 -0
  925. templates/fastapi/stack.yaml +57 -0
  926. templates/flutter/rules/mobile.md +26 -0
  927. templates/flutter/scaffold/docs/engineering/flutter-rules.md +35 -0
  928. templates/flutter/scaffold/docs/playbooks/flutter-app.md +45 -0
  929. templates/flutter/scaffold/src/mobile/lib/core/error_mapper.dart +17 -0
  930. templates/flutter/scaffold/src/mobile/lib/core/router.dart +13 -0
  931. templates/flutter/scaffold/src/mobile/lib/main.dart +22 -0
  932. templates/flutter/scaffold/src/mobile/lib/screens/health_screen.dart +32 -0
  933. templates/flutter/scaffold/src/mobile/lib/services/health_service.dart +11 -0
  934. templates/flutter/scaffold/src/mobile/lib/state/health_provider.dart +13 -0
  935. templates/flutter/scaffold/src/mobile/pubspec.yaml +22 -0
  936. templates/flutter/scaffold/src/mobile/test/health_provider_test.dart +90 -0
  937. templates/flutter/scaffold-boundary.yaml +28 -0
  938. templates/flutter/skills/flutter/SKILL.md +76 -0
  939. templates/flutter/skills/flutter/references/anatomy.md +64 -0
  940. templates/flutter/stack.yaml +70 -0
  941. templates/go/rules/backend.md +19 -0
  942. templates/go/scaffold/docs/engineering/go-rules.md +45 -0
  943. templates/go/scaffold/docs/playbooks/go-service.md +30 -0
  944. templates/go/scaffold/src/backend/cmd/api/main.go +22 -0
  945. templates/go/scaffold/src/backend/cmd/api/main_test.go +20 -0
  946. templates/go/scaffold/src/backend/go.mod +3 -0
  947. templates/go/scaffold-boundary.yaml +25 -0
  948. templates/go/skills/go-patterns/SKILL.md +68 -0
  949. templates/go/skills/go-patterns/assets/go-checklist.md +29 -0
  950. templates/go/skills/go-patterns/references/anatomy.md +115 -0
  951. templates/go/skills/go-patterns/references/go-2026-idioms.md +92 -0
  952. templates/go/skills/go-patterns/scripts/new_endpoint.py +117 -0
  953. templates/go/skills/go-patterns/versions.json +16 -0
  954. templates/go/stack.yaml +54 -0
  955. templates/go-fiber/rules/backend.md +20 -0
  956. templates/go-fiber/scaffold/docs/engineering/fiber-rules.md +97 -0
  957. templates/go-fiber/scaffold/docs/playbooks/fiber-service.md +149 -0
  958. templates/go-fiber/scaffold/src/backend/cmd/api/main.go +21 -0
  959. templates/go-fiber/scaffold/src/backend/cmd/api/main_test.go +17 -0
  960. templates/go-fiber/scaffold/src/backend/go.mod +23 -0
  961. templates/go-fiber/scaffold/src/backend/go.sum +49 -0
  962. templates/go-fiber/scaffold-boundary.yaml +26 -0
  963. templates/go-fiber/skills/go-fiber/SKILL.md +203 -0
  964. templates/go-fiber/skills/go-fiber/assets/fiber-checklist.md +26 -0
  965. templates/go-fiber/skills/go-fiber/references/anatomy.md +116 -0
  966. templates/go-fiber/skills/go-fiber/references/fiber-v3-patterns.md +89 -0
  967. templates/go-fiber/skills/go-fiber/scripts/new_endpoint.py +107 -0
  968. templates/go-fiber/skills/go-fiber/versions.json +16 -0
  969. templates/go-fiber/stack.yaml +59 -0
  970. templates/go-plain/scaffold/src/backend/go.mod +3 -0
  971. templates/go-plain/scaffold/src/backend/main.go +7 -0
  972. templates/go-plain/scaffold-boundary.yaml +22 -0
  973. templates/go-plain/stack.yaml +51 -0
  974. templates/java-plain/scaffold/src/backend/mvnw +302 -0
  975. templates/java-plain/scaffold/src/backend/pom.xml +41 -0
  976. templates/java-plain/scaffold/src/backend/src/main/java/com/example/app/Main.java +10 -0
  977. templates/java-plain/scaffold-boundary.yaml +23 -0
  978. templates/java-plain/stack.yaml +51 -0
  979. templates/laravel/rules/backend.md +20 -0
  980. templates/laravel/scaffold/docs/engineering/laravel-rules.md +28 -0
  981. templates/laravel/scaffold/docs/playbooks/laravel-service.md +28 -0
  982. templates/laravel/scaffold/src/backend/app/Exceptions/Handler.php +27 -0
  983. templates/laravel/scaffold/src/backend/app/Http/Controllers/HealthController.php +15 -0
  984. templates/laravel/scaffold/src/backend/app/Support/HealthStatus.php +14 -0
  985. templates/laravel/scaffold/src/backend/composer.json +24 -0
  986. templates/laravel/scaffold/src/backend/phpunit.xml +10 -0
  987. templates/laravel/scaffold/src/backend/public/index.php +8 -0
  988. templates/laravel/scaffold/src/backend/routes/api.php +7 -0
  989. templates/laravel/scaffold/src/backend/tests/Unit/HealthStatusTest.php +16 -0
  990. templates/laravel/scaffold-boundary.yaml +23 -0
  991. templates/laravel/skills/laravel/SKILL.md +56 -0
  992. templates/laravel/skills/laravel/references/anatomy.md +65 -0
  993. templates/laravel/stack.yaml +66 -0
  994. templates/meta/rules/graph-first.md +27 -0
  995. templates/meta/rules/hook-author.md +19 -0
  996. templates/meta/rules/mcp-tool-author.md +18 -0
  997. templates/meta/rules/meta-engineering.md +17 -0
  998. templates/meta/scaffold-boundary.yaml +55 -0
  999. templates/meta/skills/claude-sdk-integration/SKILL.md +163 -0
  1000. templates/meta/skills/claude-sdk-integration/assets/sdk-checklist.md +27 -0
  1001. templates/meta/skills/claude-sdk-integration/scripts/check_model_ids.py +97 -0
  1002. templates/meta/skills/graph-os-authoring/SKILL.md +278 -0
  1003. templates/meta/skills/graph-os-authoring/assets/graph-os-checklist.md +25 -0
  1004. templates/meta/skills/graph-os-authoring/scripts/new_extractor.py +76 -0
  1005. templates/meta/skills/hook-authoring/SKILL.md +292 -0
  1006. templates/meta/skills/hook-authoring/assets/hook-checklist.md +30 -0
  1007. templates/meta/skills/hook-authoring/scripts/new_hook.sh +75 -0
  1008. templates/meta/skills/mcp-tool-authoring/SKILL.md +301 -0
  1009. templates/meta/skills/mcp-tool-authoring/assets/mcp-tool-checklist.md +29 -0
  1010. templates/meta/skills/mcp-tool-authoring/scripts/new_tool.py +74 -0
  1011. templates/meta/skills/meta-engineering/SKILL.md +151 -0
  1012. templates/meta/skills/meta-engineering/assets/meta-edit-checklist.md +28 -0
  1013. templates/meta/skills/meta-engineering/scripts/which_layer.py +61 -0
  1014. templates/meta/skills/python-meta-server/SKILL.md +162 -0
  1015. templates/meta/skills/python-meta-server/assets/meta-server-checklist.md +28 -0
  1016. templates/meta/skills/python-meta-server/scripts/check_envelope.py +91 -0
  1017. templates/meta/skills/react-vite-hub/SKILL.md +140 -0
  1018. templates/meta/skills/react-vite-hub/assets/hub-ui-checklist.md +23 -0
  1019. templates/meta/skills/react-vite-hub/scripts/check_vite_env.py +73 -0
  1020. templates/meta/stack.yaml +119 -0
  1021. templates/nestjs/rules/backend.md +20 -0
  1022. templates/nestjs/scaffold/docs/engineering/nestjs-rules.md +33 -0
  1023. templates/nestjs/scaffold/docs/playbooks/nestjs-service.md +39 -0
  1024. templates/nestjs/scaffold/src/backend/nest-cli.json +5 -0
  1025. templates/nestjs/scaffold/src/backend/package.json +28 -0
  1026. templates/nestjs/scaffold/src/backend/src/app.module.ts +9 -0
  1027. templates/nestjs/scaffold/src/backend/src/common/all-exceptions.filter.ts +59 -0
  1028. templates/nestjs/scaffold/src/backend/src/health/health.controller.ts +14 -0
  1029. templates/nestjs/scaffold/src/backend/src/health/health.module.ts +10 -0
  1030. templates/nestjs/scaffold/src/backend/src/health/health.service.spec.ts +21 -0
  1031. templates/nestjs/scaffold/src/backend/src/health/health.service.ts +9 -0
  1032. templates/nestjs/scaffold/src/backend/src/main.ts +26 -0
  1033. templates/nestjs/scaffold/src/backend/tsconfig.json +16 -0
  1034. templates/nestjs/scaffold/src/backend/vitest.config.ts +9 -0
  1035. templates/nestjs/scaffold-boundary.yaml +25 -0
  1036. templates/nestjs/skills/nestjs/SKILL.md +67 -0
  1037. templates/nestjs/skills/nestjs/references/anatomy.md +65 -0
  1038. templates/nestjs/stack.yaml +68 -0
  1039. templates/nextjs/rules/frontend.md +18 -0
  1040. templates/nextjs/scaffold/docs/design/00-index.md +23 -0
  1041. templates/nextjs/scaffold/docs/design/colors-tokens.md +141 -0
  1042. templates/nextjs/scaffold/docs/design/components-patterns.md +159 -0
  1043. templates/nextjs/scaffold/docs/design/motion-accessibility.md +137 -0
  1044. templates/nextjs/scaffold/docs/design/typography-spacing.md +107 -0
  1045. templates/nextjs/scaffold/docs/engineering/accessibility-web.md +56 -0
  1046. templates/nextjs/scaffold/docs/engineering/copywriting-standard.md +102 -0
  1047. templates/nextjs/scaffold/docs/engineering/formatting-rules.md +89 -0
  1048. templates/nextjs/scaffold/docs/engineering/frontend-rendering-rules.md +80 -0
  1049. templates/nextjs/scaffold/docs/engineering/frontend-rules.md +183 -0
  1050. templates/nextjs/scaffold/docs/engineering/i18n-policy.md +99 -0
  1051. templates/nextjs/scaffold/docs/pages-content-spec/00-index.md +78 -0
  1052. templates/nextjs/scaffold/docs/playbooks/content-seo.md +55 -0
  1053. templates/nextjs/scaffold/docs/playbooks/docs-governance.md +51 -0
  1054. templates/nextjs/scaffold/docs/playbooks/frontend-ui.md +63 -0
  1055. templates/nextjs/scaffold/src/frontend/app/layout.tsx +14 -0
  1056. templates/nextjs/scaffold/src/frontend/app/page.tsx +3 -0
  1057. templates/nextjs/scaffold/src/frontend/eslint.config.js +17 -0
  1058. templates/nextjs/scaffold/src/frontend/lib/greeting.test.ts +9 -0
  1059. templates/nextjs/scaffold/src/frontend/lib/greeting.ts +3 -0
  1060. templates/nextjs/scaffold/src/frontend/package.json +28 -0
  1061. templates/nextjs/scaffold/src/frontend/tsconfig.json +18 -0
  1062. templates/nextjs/scaffold/src/frontend/vitest.config.ts +10 -0
  1063. templates/nextjs/scaffold-boundary.yaml +30 -0
  1064. templates/nextjs/skills/nextjs-react/SKILL.md +485 -0
  1065. templates/nextjs/skills/nextjs-react/references/anatomy.md +116 -0
  1066. templates/nextjs/skills/nextjs-react/scripts/new_component.py +72 -0
  1067. templates/nextjs/stack.yaml +81 -0
  1068. templates/node-express/rules/backend.md +20 -0
  1069. templates/node-express/scaffold/docs/engineering/express-rules.md +30 -0
  1070. templates/node-express/scaffold/docs/playbooks/express-service.md +35 -0
  1071. templates/node-express/scaffold/src/backend/package.json +24 -0
  1072. templates/node-express/scaffold/src/backend/src/index.ts +17 -0
  1073. templates/node-express/scaffold/src/backend/src/middleware/error-handler.ts +12 -0
  1074. templates/node-express/scaffold/src/backend/src/routes/health.test.ts +33 -0
  1075. templates/node-express/scaffold/src/backend/src/routes/health.ts +7 -0
  1076. templates/node-express/scaffold/src/backend/tsconfig.json +15 -0
  1077. templates/node-express/scaffold/src/backend/types/express-bootstrap.d.ts +21 -0
  1078. templates/node-express/scaffold-boundary.yaml +25 -0
  1079. templates/node-express/skills/node-express/SKILL.md +70 -0
  1080. templates/node-express/skills/node-express/references/anatomy.md +63 -0
  1081. templates/node-express/stack.yaml +63 -0
  1082. templates/python/scaffold/docs/engineering/python-rules.md +27 -0
  1083. templates/python/scaffold/docs/playbooks/python-library.md +35 -0
  1084. templates/python/stack.yaml +60 -0
  1085. templates/rails/rules/backend.md +10 -0
  1086. templates/rails/scaffold/docs/engineering/rails-rules.md +33 -0
  1087. templates/rails/scaffold/docs/playbooks/rails-service.md +42 -0
  1088. templates/rails/scaffold/src/backend/Gemfile +12 -0
  1089. templates/rails/scaffold/src/backend/app/controllers/application_controller.rb +26 -0
  1090. templates/rails/scaffold/src/backend/app/controllers/health_controller.rb +6 -0
  1091. templates/rails/scaffold/src/backend/app/models/health.rb +6 -0
  1092. templates/rails/scaffold/src/backend/config/application.rb +12 -0
  1093. templates/rails/scaffold/src/backend/config/boot.rb +3 -0
  1094. templates/rails/scaffold/src/backend/config/routes.rb +4 -0
  1095. templates/rails/scaffold/src/backend/config.ru +5 -0
  1096. templates/rails/scaffold/src/backend/spec/rails_helper.rb +18 -0
  1097. templates/rails/scaffold/src/backend/spec/requests/health_spec.rb +24 -0
  1098. templates/rails/scaffold-boundary.yaml +25 -0
  1099. templates/rails/skills/rails/SKILL.md +62 -0
  1100. templates/rails/skills/rails/references/anatomy.md +71 -0
  1101. templates/rails/stack.yaml +72 -0
  1102. templates/react-native/rules/mobile.md +26 -0
  1103. templates/react-native/scaffold/docs/engineering/accessibility-mobile.md +95 -0
  1104. templates/react-native/scaffold/docs/engineering/mobile-rules.md +56 -0
  1105. templates/react-native/scaffold/docs/engineering/offline-first.md +61 -0
  1106. templates/react-native/scaffold/docs/playbooks/mobile-app.md +49 -0
  1107. templates/react-native/scaffold/src/mobile/App.tsx +9 -0
  1108. templates/react-native/scaffold/src/mobile/eslint.config.js +17 -0
  1109. templates/react-native/scaffold/src/mobile/package.json +23 -0
  1110. templates/react-native/scaffold/src/mobile/src/greeting.test.ts +9 -0
  1111. templates/react-native/scaffold/src/mobile/src/greeting.ts +3 -0
  1112. templates/react-native/scaffold/src/mobile/tsconfig.json +17 -0
  1113. templates/react-native/scaffold/src/mobile/vitest.config.ts +10 -0
  1114. templates/react-native/scaffold-boundary.yaml +30 -0
  1115. templates/react-native/skills/react-native-mobile/SKILL.md +119 -0
  1116. templates/react-native/skills/react-native-mobile/assets/rn-mobile-checklist.md +28 -0
  1117. templates/react-native/skills/react-native-mobile/references/anatomy.md +140 -0
  1118. templates/react-native/skills/react-native-mobile/references/rn-2026-practices.md +54 -0
  1119. templates/react-native/skills/react-native-mobile/scripts/new_screen.py +73 -0
  1120. templates/react-native/skills/react-native-mobile/versions.json +16 -0
  1121. templates/react-native/skills/react-native-patterns/SKILL.md +512 -0
  1122. templates/react-native/skills/react-native-patterns/assets/rn-review-checklist.md +26 -0
  1123. templates/react-native/skills/react-native-patterns/references/anatomy.md +62 -0
  1124. templates/react-native/skills/react-native-patterns/references/list-performance.md +70 -0
  1125. templates/react-native/skills/react-native-patterns/scripts/scan_rn_perf.py +77 -0
  1126. templates/react-native/stack.yaml +70 -0
  1127. templates/ruby-plain/scaffold/src/backend/Gemfile +8 -0
  1128. templates/ruby-plain/scaffold/src/backend/main.rb +3 -0
  1129. templates/ruby-plain/scaffold-boundary.yaml +23 -0
  1130. templates/ruby-plain/stack.yaml +50 -0
  1131. templates/rust-axum/rules/backend.md +20 -0
  1132. templates/rust-axum/scaffold/docs/engineering/rust-axum-rules.md +35 -0
  1133. templates/rust-axum/scaffold/docs/playbooks/rust-axum-service.md +43 -0
  1134. templates/rust-axum/scaffold/src/backend/Cargo.toml +20 -0
  1135. templates/rust-axum/scaffold/src/backend/src/app.rs +12 -0
  1136. templates/rust-axum/scaffold/src/backend/src/error.rs +48 -0
  1137. templates/rust-axum/scaffold/src/backend/src/main.rs +24 -0
  1138. templates/rust-axum/scaffold/src/backend/src/routes/health.rs +37 -0
  1139. templates/rust-axum/scaffold/src/backend/src/routes/mod.rs +2 -0
  1140. templates/rust-axum/scaffold-boundary.yaml +25 -0
  1141. templates/rust-axum/skills/rust/SKILL.md +73 -0
  1142. templates/rust-axum/skills/rust/references/anatomy.md +63 -0
  1143. templates/rust-axum/stack.yaml +65 -0
  1144. templates/rust-plain/scaffold/src/backend/Cargo.toml +6 -0
  1145. templates/rust-plain/scaffold/src/backend/src/main.rs +3 -0
  1146. templates/rust-plain/scaffold-boundary.yaml +23 -0
  1147. templates/rust-plain/stack.yaml +51 -0
  1148. templates/spring-boot/rules/backend.md +20 -0
  1149. templates/spring-boot/scaffold/docs/engineering/spring-boot-rules.md +35 -0
  1150. templates/spring-boot/scaffold/docs/playbooks/spring-boot-service.md +45 -0
  1151. templates/spring-boot/scaffold/src/backend/mvnw +302 -0
  1152. templates/spring-boot/scaffold/src/backend/pom.xml +69 -0
  1153. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/Application.java +16 -0
  1154. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/common/GlobalExceptionHandler.java +31 -0
  1155. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthController.java +22 -0
  1156. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthService.java +12 -0
  1157. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthStatus.java +4 -0
  1158. templates/spring-boot/scaffold/src/backend/src/test/java/com/example/app/health/HealthServiceTest.java +15 -0
  1159. templates/spring-boot/scaffold-boundary.yaml +26 -0
  1160. templates/spring-boot/skills/spring-boot/SKILL.md +84 -0
  1161. templates/spring-boot/skills/spring-boot/references/anatomy.md +63 -0
  1162. templates/spring-boot/stack.yaml +65 -0
  1163. templates/svelte-sveltekit/rules/frontend.md +20 -0
  1164. templates/svelte-sveltekit/scaffold/docs/engineering/svelte-sveltekit-rules.md +37 -0
  1165. templates/svelte-sveltekit/scaffold/docs/playbooks/svelte-sveltekit-app.md +36 -0
  1166. templates/svelte-sveltekit/scaffold/src/frontend/package.json +22 -0
  1167. templates/svelte-sveltekit/scaffold/src/frontend/src/app.html +12 -0
  1168. templates/svelte-sveltekit/scaffold/src/frontend/src/hooks.server.ts +14 -0
  1169. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/components/Greeting.svelte +6 -0
  1170. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.test.ts +26 -0
  1171. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.ts +4 -0
  1172. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+layout.svelte +24 -0
  1173. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.svelte +9 -0
  1174. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.ts +7 -0
  1175. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/health/+server.ts +7 -0
  1176. templates/svelte-sveltekit/scaffold/src/frontend/svelte.config.js +10 -0
  1177. templates/svelte-sveltekit/scaffold/src/frontend/tsconfig.json +7 -0
  1178. templates/svelte-sveltekit/scaffold/src/frontend/vite.config.ts +7 -0
  1179. templates/svelte-sveltekit/scaffold/src/frontend/vitest.config.ts +11 -0
  1180. templates/svelte-sveltekit/scaffold-boundary.yaml +29 -0
  1181. templates/svelte-sveltekit/skills/svelte/SKILL.md +90 -0
  1182. templates/svelte-sveltekit/skills/svelte/references/anatomy.md +62 -0
  1183. templates/svelte-sveltekit/stack.yaml +70 -0
  1184. templates/typescript-plain/scaffold/src/index.ts +3 -0
  1185. templates/typescript-plain/scaffold/tsconfig.json +13 -0
  1186. templates/typescript-plain/scaffold-boundary.yaml +22 -0
  1187. templates/typescript-plain/stack.yaml +44 -0
  1188. templates/vue-nuxt/rules/frontend.md +19 -0
  1189. templates/vue-nuxt/scaffold/docs/engineering/nuxt-rules.md +30 -0
  1190. templates/vue-nuxt/scaffold/docs/playbooks/nuxt-app.md +29 -0
  1191. templates/vue-nuxt/scaffold/src/frontend/app.vue +3 -0
  1192. templates/vue-nuxt/scaffold/src/frontend/nuxt.config.ts +11 -0
  1193. templates/vue-nuxt/scaffold/src/frontend/package.json +20 -0
  1194. templates/vue-nuxt/scaffold/src/frontend/pages/index.test.ts +19 -0
  1195. templates/vue-nuxt/scaffold/src/frontend/pages/index.vue +11 -0
  1196. templates/vue-nuxt/scaffold/src/frontend/vitest.config.ts +12 -0
  1197. templates/vue-nuxt/scaffold-boundary.yaml +26 -0
  1198. templates/vue-nuxt/skills/vue-nuxt/SKILL.md +57 -0
  1199. templates/vue-nuxt/skills/vue-nuxt/references/anatomy.md +60 -0
  1200. templates/vue-nuxt/stack.yaml +61 -0
  1201. templates/wordpress/rules/backend.md +19 -0
  1202. templates/wordpress/scaffold/docs/engineering/wordpress-rules.md +28 -0
  1203. templates/wordpress/scaffold/docs/playbooks/wordpress-service.md +29 -0
  1204. templates/wordpress/scaffold/src/backend/composer.json +17 -0
  1205. templates/wordpress/scaffold/src/backend/phpcs.xml.dist +11 -0
  1206. templates/wordpress/scaffold/src/backend/phpunit.xml +10 -0
  1207. templates/wordpress/scaffold/src/backend/plugin/inc/health.php +8 -0
  1208. templates/wordpress/scaffold/src/backend/plugin/plugin.php +28 -0
  1209. templates/wordpress/scaffold/src/backend/tests/HealthStatusTest.php +15 -0
  1210. templates/wordpress/scaffold/src/backend/theme/functions.php +18 -0
  1211. templates/wordpress/scaffold/src/backend/theme/style.css +11 -0
  1212. templates/wordpress/scaffold-boundary.yaml +23 -0
  1213. templates/wordpress/skills/wordpress/SKILL.md +110 -0
  1214. templates/wordpress/skills/wordpress/assets/wp-checklist.md +28 -0
  1215. templates/wordpress/skills/wordpress/references/wp-development.md +75 -0
  1216. templates/wordpress/skills/wordpress/references/wp-security.md +68 -0
  1217. templates/wordpress/skills/wordpress/scripts/scan_wp_smells.py +91 -0
  1218. templates/wordpress/skills/wordpress/versions.json +16 -0
  1219. templates/wordpress/stack.yaml +60 -0
  1220. thinking_os/__init__.py +1 -0
  1221. thinking_os/_agent_markers.py +32 -0
  1222. thinking_os/background.py +405 -0
  1223. thinking_os/bootstrap_outcomes.py +200 -0
  1224. thinking_os/budget.py +302 -0
  1225. thinking_os/capture.py +495 -0
  1226. thinking_os/cognition.py +516 -0
  1227. thinking_os/cognition_schemas.py +517 -0
  1228. thinking_os/compress.py +192 -0
  1229. thinking_os/concepts.py +233 -0
  1230. thinking_os/dashboard.py +159 -0
  1231. thinking_os/database.py +2883 -0
  1232. thinking_os/decay.py +393 -0
  1233. thinking_os/digest.py +295 -0
  1234. thinking_os/dispatcher.py +192 -0
  1235. thinking_os/dispatcher_helpers.py +48 -0
  1236. thinking_os/dispatchers/__init__.py +3 -0
  1237. thinking_os/dispatchers/default.py +47 -0
  1238. thinking_os/distill.py +192 -0
  1239. thinking_os/doc_indexer.py +905 -0
  1240. thinking_os/embeddings.py +943 -0
  1241. thinking_os/formula_composer.py +556 -0
  1242. thinking_os/gate_marker.py +75 -0
  1243. thinking_os/graph.py +296 -0
  1244. thinking_os/graph_indexer.py +360 -0
  1245. thinking_os/health_check.py +517 -0
  1246. thinking_os/impact.py +119 -0
  1247. thinking_os/memory_gc.py +356 -0
  1248. thinking_os/migrator_embeddings.py +318 -0
  1249. thinking_os/precision.py +194 -0
  1250. thinking_os/record_outcome.py +399 -0
  1251. thinking_os/repair.py +105 -0
  1252. thinking_os/retrieval_quality.py +239 -0
  1253. thinking_os/roles_state.py +168 -0
  1254. thinking_os/sanitizer.py +320 -0
  1255. thinking_os/server.py +3160 -0
  1256. thinking_os/session_enrich.py +272 -0
  1257. thinking_os/session_observe_worker.py +111 -0
  1258. thinking_os/session_startup.py +74 -0
  1259. thinking_os/session_summary.py +245 -0
  1260. thinking_os/task_analyzer.py +462 -0
  1261. thinking_os/task_parser.py +342 -0
  1262. thinking_os/task_sync.py +73 -0
  1263. thinking_os/tools/__init__.py +6 -0
  1264. thinking_os/tools/_shared.py +947 -0
  1265. thinking_os/tools/cognition.py +1867 -0
  1266. thinking_os/tools/docs.py +770 -0
  1267. thinking_os/tools/learning.py +2078 -0
  1268. thinking_os/tools/logs.py +79 -0
  1269. thinking_os/tools/memory.py +840 -0
  1270. thinking_os/tools/metrics.py +200 -0
  1271. thinking_os/tools/retrieve.py +415 -0
  1272. thinking_os/tools/routing.py +658 -0
  1273. thinking_os/tools/tasks.py +449 -0
  1274. thinking_os/tools/trajectory.py +181 -0
  1275. thinking_os/tracing.py +235 -0
  1276. web/__init__.py +5 -0
  1277. web/_cache.py +118 -0
  1278. web/_deps.py +56 -0
  1279. web/_envelope.py +85 -0
  1280. web/_project_context.py +140 -0
  1281. web/chat_providers.py +108 -0
  1282. web/init_jobs.py +216 -0
  1283. web/routes/__init__.py +25 -0
  1284. web/routes/_bounded_read.py +76 -0
  1285. web/routes/board.py +1089 -0
  1286. web/routes/cognition.py +1838 -0
  1287. web/routes/config.py +635 -0
  1288. web/routes/graph.py +513 -0
  1289. web/routes/health.py +180 -0
  1290. web/routes/hooks.py +288 -0
  1291. web/routes/hub.py +1219 -0
  1292. web/routes/logs.py +374 -0
  1293. web/routes/metrics.py +43 -0
  1294. web/routes/observability.py +400 -0
  1295. web/routes/patterns.py +227 -0
  1296. web/routes/presence.py +609 -0
  1297. web/routes/roles.py +446 -0
  1298. web/routes/scheduled.py +261 -0
  1299. web/routes/search.py +238 -0
  1300. web/routes/sessions.py +220 -0
  1301. web/routes/settings.py +363 -0
  1302. web/routes/stream.py +547 -0
  1303. web/security.py +157 -0
  1304. web/server.py +283 -0
@@ -0,0 +1,1867 @@
1
+ """Coding OS — Formula-agent supervisor MCP tools."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import logging
8
+ import os
9
+ import re
10
+ import sqlite3
11
+ from datetime import datetime, timezone
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ from tools._shared import _gated_module, fail, ok, safe_tool
16
+
17
+ logger = logging.getLogger("coding_os.tools.cognition")
18
+
19
+ # Canonical corrective action per backtrack root cause — the SSOT shared by
20
+ # cos_backtrack_log (returned as the agent's next-step suggestion) and
21
+ # learn_extract's anatomy mining (the remedy paired with a recurring cause that
22
+ # carries no recorded remedy). Keys match the backtrack root_cause enum.
23
+ CANONICAL_REMEDIES: dict[str, str] = {
24
+ "wrong_model": "Use cos_route_model to select the right model before re-dispatching.",
25
+ "scope_too_large": "Decompose via cos_task_create and pick the smallest slice.",
26
+ "missing_context": "Run cos_doc_search or cos_search to load relevant context first.",
27
+ "tool_failure": "Run cos_health to verify permissions/env vars, then retry with explicit paths.",
28
+ "spec_ambiguity": "Log open questions via cos_discovery and resolve with user before implementing.",
29
+ "env_mismatch": "Run cos doctor to validate environment config, then restart the session.",
30
+ "other": "Re-classify the problem (Cynefin gate) and review the Anti-Paralysis advisory.",
31
+ }
32
+
33
+
34
+ # Lazy import of cognition — avoids circular at module load time
35
+ def _cog():
36
+ import cognition as _mod
37
+
38
+ return _mod
39
+
40
+
41
+ def _schemas():
42
+ import cognition_schemas as _mod
43
+
44
+ return _mod
45
+
46
+
47
+ # ---------------------------------------------------------------------------
48
+ # Internal helpers
49
+ # ---------------------------------------------------------------------------
50
+
51
+
52
+ def _now_iso() -> str:
53
+ return datetime.now(timezone.utc).isoformat(timespec="seconds")
54
+
55
+
56
+ _role_persistence_cache: dict[str, tuple[str | None, Any]] | None = None
57
+
58
+
59
+ def _resolve_role_persistence(role_id: str) -> tuple[str | None, Any]:
60
+ global _role_persistence_cache
61
+ if _role_persistence_cache is None:
62
+ _role_persistence_cache = {}
63
+ cog = _cog()
64
+ schemas_mod = _schemas()
65
+ # Primary source: ROLE_OUTPUT_CLASSES registry in cognition_schemas.
66
+ # Frontmatter `output_schema:` / `bundle_field:` override on a per-
67
+ # role basis (lets a deployment swap the Pydantic class without
68
+ # editing the registry).
69
+ for rid, cls in schemas_mod.ROLE_OUTPUT_CLASSES.items():
70
+ _role_persistence_cache[rid] = (rid, cls)
71
+
72
+ try:
73
+ registry = cog.load_agent_registry()
74
+ except Exception as exc:
75
+ logger.warning("agent registry load failed: %s", exc)
76
+ registry = {}
77
+ for rid, meta in registry.items():
78
+ if not isinstance(meta, dict):
79
+ continue
80
+ field = meta.get("bundle_field") or rid
81
+ schema_ref = meta.get("output_schema")
82
+ cls = _role_persistence_cache.get(rid, (None, None))[1]
83
+ if isinstance(schema_ref, str) and schema_ref.strip():
84
+ cls_name = schema_ref.split(".")[-1].strip()
85
+ if cls_name.isidentifier():
86
+ override = getattr(schemas_mod, cls_name, None)
87
+ if override is not None:
88
+ cls = override
89
+ _role_persistence_cache[rid] = (field, cls)
90
+ return _role_persistence_cache.get(role_id, (None, None))
91
+
92
+
93
+ def _all_bundle_fields() -> set[str]:
94
+ """Bundle field names from every registered role (data-driven)."""
95
+ cog = _cog()
96
+ try:
97
+ registry = cog.load_agent_registry()
98
+ except Exception:
99
+ return set()
100
+ out: set[str] = set()
101
+ for rid, meta in registry.items():
102
+ if isinstance(meta, dict):
103
+ out.add(str(meta.get("bundle_field") or rid))
104
+ return out
105
+
106
+
107
+ def _resolve_agent_dir() -> Path:
108
+ import os as _os
109
+
110
+ explicit = _os.environ.get("COS_AGENT_DIR")
111
+ if explicit:
112
+ d = Path(explicit)
113
+ d.mkdir(parents=True, exist_ok=True)
114
+ return d
115
+ agent = _os.environ.get("COS_AGENT") or "claude"
116
+ d = Path(".coding-os") / agent
117
+ d.mkdir(parents=True, exist_ok=True)
118
+ return d
119
+
120
+
121
+ def _bundle_path(session_id: str) -> Path:
122
+ agent_dir = _resolve_agent_dir()
123
+ return agent_dir / f"evidence_bundle_{session_id}.json"
124
+
125
+
126
+ def _load_bundle(session_id: str, task_marker: str, persona_id: str) -> Any:
127
+ schemas = _schemas()
128
+ path = _bundle_path(session_id)
129
+ if path.exists():
130
+ try:
131
+ data = json.loads(path.read_text())
132
+ return schemas.EvidenceBundle.model_validate(data)
133
+ except Exception as exc:
134
+ ts = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%S")
135
+ corrupt = path.with_suffix(f".corrupt-{ts}.json")
136
+ path.rename(corrupt)
137
+ logger.warning("Corrupted bundle quarantined to %s: %s", corrupt, exc)
138
+ return schemas.EvidenceBundle(task_marker=task_marker, persona_id=persona_id)
139
+
140
+
141
+ def _save_bundle(session_id: str, bundle: Any) -> None:
142
+ import fcntl
143
+
144
+ path = _bundle_path(session_id)
145
+ payload = bundle.model_dump_json(indent=2)
146
+ # flock-serialize concurrent writers so a parallel run can't interleave a
147
+ # half-written bundle. flush+fsync BEFORE the lock drops — close() releases
148
+ # the lock, and a buffered write would otherwise hit disk after LOCK_UN.
149
+ with path.open("a+") as fh:
150
+ fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
151
+ fh.seek(0)
152
+ fh.truncate()
153
+ fh.write(payload)
154
+ fh.flush()
155
+ os.fsync(fh.fileno())
156
+
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # cos_supervise
160
+ # ---------------------------------------------------------------------------
161
+
162
+
163
+ def register_cos_supervise(mcp, db_path): # db_path reserved for future warm-history ranking
164
+ @mcp.tool(
165
+ name="cos_supervise",
166
+ description=(
167
+ "Return the next action the main agent should take: dispatch a "
168
+ "formula-agent, backtrack, or signal done. Call repeatedly after "
169
+ "recording each formula output via cos_supervise_record_output. "
170
+ "Never spawns agents itself — only tells the main agent what to dispatch."
171
+ ),
172
+ )
173
+ @safe_tool
174
+ def cos_supervise(
175
+ session_id: str,
176
+ task_marker: str,
177
+ persona_id: str,
178
+ intensity: str = "standard",
179
+ situation_id: str = "",
180
+ phase: str = "ROUTING",
181
+ dispatched: str = "[]",
182
+ pending: str = "[]",
183
+ backtrack_count: int = 0,
184
+ ) -> str:
185
+ cog = _cog()
186
+ schemas = _schemas()
187
+
188
+ state = schemas.SupervisorState(
189
+ session_id=session_id,
190
+ task_marker=task_marker,
191
+ persona_id=persona_id,
192
+ intensity=intensity,
193
+ situation_id=situation_id or None,
194
+ phase=phase,
195
+ dispatched=json.loads(dispatched),
196
+ pending=json.loads(pending),
197
+ backtrack_count=backtrack_count,
198
+ )
199
+ bundle = _load_bundle(session_id, task_marker, persona_id)
200
+ action = cog.advance(state, bundle)
201
+
202
+ # emit trace event for every supervisor transition
203
+ try:
204
+ import tracing
205
+
206
+ event_kind = "supervise_action"
207
+ if action.action == "dispatch":
208
+ event_kind = "role_dispatch"
209
+ elif action.action == "dispatch_parallel":
210
+ event_kind = "parallel_dispatch"
211
+ elif action.action == "backtrack":
212
+ event_kind = "backtrack"
213
+ elif action.action == "done":
214
+ event_kind = "task_done"
215
+ tracing.emit(
216
+ session_id,
217
+ event_kind,
218
+ {
219
+ "action": action.action,
220
+ "formula": action.formula,
221
+ "formulas": action.formulas,
222
+ "reason": action.reason,
223
+ "phase": state.phase,
224
+ "dispatched_count": len(state.dispatched),
225
+ "backtrack_count": state.backtrack_count,
226
+ },
227
+ role=action.formula,
228
+ phase=state.phase,
229
+ )
230
+ except Exception as exc:
231
+ from core.logging_os import swallow_safe
232
+
233
+ swallow_safe("thinking_os.cognition", "supervise output trace emit failed", exc=exc)
234
+
235
+ # Mirror the dispatch-time model precedence (claude-sdk.md §7.3
236
+ # tiers 2-3) so the main agent can pick a model BEFORE the run tool.
237
+ model_hints: dict = {}
238
+ if action.action in ("dispatch", "dispatch_parallel") and action.formula:
239
+ role_meta = cog.load_agent_registry().get(action.formula) or {}
240
+ model_hints = {
241
+ "preset_hint": _preset_role_hint(session_id, action.formula, db_path),
242
+ "role_pref": role_meta.get("model_pref") or {},
243
+ }
244
+
245
+ return ok(
246
+ {
247
+ "action": action.action,
248
+ "formula": action.formula,
249
+ "formulas": action.formulas,
250
+ "agent_file": action.agent_file,
251
+ "reason": action.reason,
252
+ "advisory": action.advisory,
253
+ "model_hints": model_hints,
254
+ "state": {
255
+ "phase": state.phase,
256
+ "dispatched": state.dispatched,
257
+ "pending": state.pending,
258
+ "backtrack_count": state.backtrack_count,
259
+ },
260
+ },
261
+ meta={"layer": "routing", "source": "supervisor"},
262
+ )
263
+
264
+ return cos_supervise
265
+
266
+
267
+ # ---------------------------------------------------------------------------
268
+ # cos_supervise_record_output
269
+ # ---------------------------------------------------------------------------
270
+
271
+
272
+ def register_cos_supervise_record_output(mcp, db_path):
273
+ @mcp.tool(
274
+ name="cos_supervise_record_output",
275
+ description=(
276
+ "Append a formula-agent's output to the session EvidenceBundle "
277
+ "and record the dispatch in formula_dispatches. Call after each "
278
+ "formula-agent returns. status: ok|fail|timeout."
279
+ ),
280
+ )
281
+ @safe_tool
282
+ def cos_supervise_record_output(
283
+ session_id: str,
284
+ task_marker: str,
285
+ persona_id: str,
286
+ formula_id: str,
287
+ output_json: str,
288
+ status: str = "ok",
289
+ latency_ms: int = 0,
290
+ ) -> str:
291
+ # Validate formula_id before any use. A malformed value (e.g. an XML
292
+ # tool-call fragment leaking the arg boundary — `researcher</formula_id>...`)
293
+ # once landed verbatim in the typed column. Reject non-identifier values
294
+ # up front rather than persisting garbage.
295
+ if not formula_id or not re.fullmatch(r"[a-z0-9_]+", formula_id):
296
+ return fail("validation", "formula_id must match [a-z0-9_]+")
297
+
298
+ bundle = _load_bundle(session_id, task_marker, persona_id)
299
+
300
+ # Data-driven role → bundle-field + Pydantic class (frontmatter SSOT).
301
+ error_detail: str | None = None
302
+ field, cls = _resolve_role_persistence(formula_id)
303
+ if field and cls and status == "ok":
304
+ try:
305
+ parsed = cls.model_validate_json(output_json)
306
+ setattr(bundle, field, parsed)
307
+ except Exception as exc:
308
+ logger.warning("Failed to parse %s output: %s", formula_id, exc)
309
+ bundle.degraded_formulas.append(formula_id)
310
+ status = "fail"
311
+ error_detail = f"{formula_id} parse: {exc}"
312
+ elif status == "timeout":
313
+ bundle.degraded_formulas.append(formula_id)
314
+ error_detail = "timeout"
315
+
316
+ # Caller-supplied non-ok status (not a parse failure) carries its reason
317
+ # in output_json — keep a bounded slice so the failure stays diagnosable.
318
+ if status != "ok" and not error_detail:
319
+ error_detail = (output_json or "")[:1000]
320
+
321
+ _save_bundle(session_id, bundle)
322
+
323
+ output_hash = hashlib.sha256(output_json.encode()).hexdigest()[:16]
324
+ input_hash = hashlib.sha256(f"{session_id}:{formula_id}".encode()).hexdigest()[:16]
325
+
326
+ try:
327
+ with sqlite3.connect(db_path) as conn:
328
+ conn.execute(
329
+ "INSERT INTO formula_dispatches "
330
+ "(session_id, task_marker, persona_id, formula_id, input_hash, "
331
+ "output_hash, latency_ms, status, ts, error) "
332
+ "VALUES (?,?,?,?,?,?,?,?,?,?)",
333
+ (
334
+ session_id,
335
+ task_marker,
336
+ persona_id,
337
+ formula_id,
338
+ input_hash,
339
+ output_hash,
340
+ latency_ms,
341
+ status,
342
+ _now_iso(),
343
+ error_detail,
344
+ ),
345
+ )
346
+ except Exception as exc:
347
+ logger.debug("formula_dispatches insert failed: %s", exc)
348
+
349
+ filled = sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
350
+ # emit trace event so the flowchart replay knows a role completed
351
+ try:
352
+ import tracing
353
+
354
+ tracing.emit(
355
+ session_id,
356
+ "role_output_recorded",
357
+ {
358
+ "formula_id": formula_id,
359
+ "status": status,
360
+ "latency_ms": latency_ms,
361
+ "output_hash": output_hash,
362
+ "bundle_fields_filled": filled,
363
+ },
364
+ role=formula_id,
365
+ )
366
+ except Exception:
367
+ pass # tracing must never break caller
368
+ return ok(
369
+ {"formula_id": formula_id, "status": status, "bundle_fields_filled": filled},
370
+ meta={"layer": "routing"},
371
+ )
372
+
373
+ return cos_supervise_record_output
374
+
375
+
376
+ # ---------------------------------------------------------------------------
377
+ # cos_dispatch_formula
378
+ # ---------------------------------------------------------------------------
379
+
380
+
381
+ def register_cos_dispatch_formula(mcp, db_path):
382
+ @mcp.tool(
383
+ name="cos_dispatch_formula",
384
+ description=(
385
+ "Return the rendered agent prompt and input slice for a formula-agent. "
386
+ "The main agent uses this to construct the subagent dispatch. "
387
+ "Does NOT spawn the subagent — returns prompt text only."
388
+ ),
389
+ )
390
+ @safe_tool
391
+ def cos_dispatch_formula(
392
+ formula_id: str,
393
+ session_id: str,
394
+ task_marker: str,
395
+ persona_id: str,
396
+ intensity: str = "standard",
397
+ ) -> str:
398
+ cog = _cog()
399
+ agents = cog.load_agent_registry()
400
+ meta = agents.get(formula_id)
401
+ if not meta:
402
+ return fail("not_found", f"No agent file for formula {formula_id}")
403
+
404
+ agent_file = f"src/core/thinking_os/agents/{meta['_file']}"
405
+ agent_path = Path(__file__).resolve().parent.parent / "agents" / meta["_file"]
406
+ prompt_text = agent_path.read_text() if agent_path.exists() else ""
407
+
408
+ bundle = _load_bundle(session_id, task_marker, persona_id)
409
+ input_slice = cog.build_input_slice(formula_id, bundle)
410
+ input_slice["intensity_steps"] = cog._intensity_steps(formula_id, intensity)
411
+
412
+ return ok(
413
+ {
414
+ "formula_id": formula_id,
415
+ "agent_file": agent_file,
416
+ "prompt_text": prompt_text,
417
+ "input_slice": input_slice,
418
+ "timeout_s": meta.get("timeout_s", 90),
419
+ "model_pref": meta.get("model_pref", {}),
420
+ },
421
+ meta={"layer": "routing"},
422
+ )
423
+
424
+ return cos_dispatch_formula
425
+
426
+
427
+ # ---------------------------------------------------------------------------
428
+ # cos_ambiguity_check
429
+ # ---------------------------------------------------------------------------
430
+
431
+
432
+ def register_cos_ambiguity_check(mcp, db_path):
433
+ @mcp.tool(
434
+ name="cos_ambiguity_check",
435
+ description=(
436
+ "Run the 7-criteria Anti-Ambiguity gate over the session EvidenceBundle. "
437
+ "Returns violations (formula, criterion, detail). Empty list = gate passes. "
438
+ "Fires once at PLAN→EXECUTE; CLEAR 1 tasks skip this check."
439
+ ),
440
+ )
441
+ @safe_tool
442
+ def cos_ambiguity_check(
443
+ session_id: str,
444
+ task_marker: str,
445
+ persona_id: str,
446
+ ) -> str:
447
+ cog = _cog()
448
+ bundle = _load_bundle(session_id, task_marker, persona_id)
449
+ violations = cog.ambiguity_check(bundle)
450
+
451
+ # The enforce-anti-ambiguity gate reads this table as the CURRENT ambiguity
452
+ # state for the session, so each check supersedes the prior one: clear old
453
+ # rows first (a pass leaves none → the gate clears) then record this check's.
454
+ try:
455
+ with sqlite3.connect(db_path) as conn:
456
+ conn.execute("DELETE FROM ambiguity_violations WHERE session_id = ?", (session_id,))
457
+ for v in violations:
458
+ conn.execute(
459
+ "INSERT INTO ambiguity_violations "
460
+ "(session_id, formula_id, criterion, detail, ts) "
461
+ "VALUES (?, ?, ?, ?, ?)",
462
+ (
463
+ session_id,
464
+ v["formula"],
465
+ v["criterion"],
466
+ v.get("detail", ""),
467
+ _now_iso(),
468
+ ),
469
+ )
470
+ except Exception as exc:
471
+ logger.debug("ambiguity_violations write failed: %s", exc)
472
+
473
+ return ok(
474
+ {"violations": violations, "passed": len(violations) == 0},
475
+ meta={"layer": "routing"},
476
+ )
477
+
478
+ return cos_ambiguity_check
479
+
480
+
481
+ # ---------------------------------------------------------------------------
482
+ # cos_traceability
483
+ # ---------------------------------------------------------------------------
484
+
485
+
486
+ def register_cos_traceability(mcp, db_path):
487
+ @mcp.tool(
488
+ name="cos_traceability",
489
+ description=(
490
+ "Read-only audit: verify that tasks have doc anchors and that "
491
+ "recent formula dispatches have matching evidence in the bundle. "
492
+ "Idempotent and non-blocking. scope: task|project."
493
+ ),
494
+ )
495
+ @safe_tool
496
+ def cos_traceability(
497
+ session_id: str,
498
+ task_marker: str,
499
+ persona_id: str,
500
+ scope: str = "task",
501
+ ) -> str:
502
+ gaps = []
503
+ bundle = _load_bundle(session_id, task_marker, persona_id)
504
+
505
+ try:
506
+ with sqlite3.connect(db_path) as conn:
507
+ rows = conn.execute(
508
+ "SELECT formula_id, status FROM formula_dispatches "
509
+ "WHERE session_id=? ORDER BY ts",
510
+ (session_id,),
511
+ ).fetchall()
512
+ except Exception:
513
+ rows = []
514
+
515
+ dispatched_ids = {r[0] for r in rows if r[1] == "ok"}
516
+ # Data-driven traceability: every dispatched role with a bundle field
517
+ # is checked. Roles missing bundle_field in their frontmatter are skipped
518
+ # (correct behavior — non-persisting roles don't appear in the bundle).
519
+ for fid in dispatched_ids:
520
+ field, _cls = _resolve_role_persistence(fid)
521
+ if field and getattr(bundle, field, None) is None:
522
+ gaps.append({"formula": fid, "detail": "dispatched but no output in bundle"})
523
+
524
+ total = len(dispatched_ids)
525
+ score = 1.0 if total == 0 else (total - len(gaps)) / total
526
+
527
+ return ok(
528
+ {"gaps": gaps, "redundancies": [], "score": round(score, 2), "scope": scope},
529
+ meta={"layer": "routing"},
530
+ )
531
+
532
+ return cos_traceability
533
+
534
+
535
+ # ---------------------------------------------------------------------------
536
+ # cos_backtrack_log
537
+ # ---------------------------------------------------------------------------
538
+
539
+
540
+ def register_cos_backtrack_log(mcp, db_path):
541
+ @mcp.tool(
542
+ name="cos_backtrack_log",
543
+ description=(
544
+ "Record a backtrack event. Returns {count, advisory, suggested_action, "
545
+ "root_cause_summary}. advisory fires at ≥3/≥5 backtracks. "
546
+ "suggested_action gives a concrete next step when root_cause is supplied. "
547
+ "root_cause_summary shows per-cause counts for this session."
548
+ ),
549
+ )
550
+ @safe_tool
551
+ def cos_backtrack_log(
552
+ session_id: str,
553
+ from_formula: str,
554
+ to_formula: str,
555
+ reason: str,
556
+ task_marker: str = "",
557
+ persona_id: str = "",
558
+ hypothesis: str = "",
559
+ failure_signal: str = "",
560
+ root_cause: str = "",
561
+ corrective_action: str = "",
562
+ ) -> str:
563
+ _VALID_ROOT_CAUSES = set(CANONICAL_REMEDIES)
564
+
565
+ # Silently clear invalid root_cause to avoid polluting the enum
566
+ if root_cause and root_cause not in _VALID_ROOT_CAUSES:
567
+ root_cause = "other"
568
+
569
+ try:
570
+ with sqlite3.connect(db_path) as conn:
571
+ # Try inserting with anatomy columns (v25); fall back to base schema
572
+ try:
573
+ conn.execute(
574
+ "INSERT INTO backtrack_events "
575
+ "(session_id, from_formula, to_formula, reason, ts, "
576
+ " hypothesis, failure_signal, root_cause, corrective_action) "
577
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
578
+ (
579
+ session_id,
580
+ from_formula,
581
+ to_formula,
582
+ reason,
583
+ _now_iso(),
584
+ hypothesis or None,
585
+ failure_signal or None,
586
+ root_cause or None,
587
+ corrective_action or None,
588
+ ),
589
+ )
590
+ except sqlite3.OperationalError:
591
+ # v25 columns not yet applied — insert base fields only
592
+ conn.execute(
593
+ "INSERT INTO backtrack_events "
594
+ "(session_id, from_formula, to_formula, reason, ts) "
595
+ "VALUES (?, ?, ?, ?, ?)",
596
+ (session_id, from_formula, to_formula, reason, _now_iso()),
597
+ )
598
+ count = conn.execute(
599
+ "SELECT COUNT(*) FROM backtrack_events WHERE session_id=?",
600
+ (session_id,),
601
+ ).fetchone()[0]
602
+
603
+ # C1: root_cause_summary — per-cause backtrack counts this session
604
+ root_cause_summary: dict[str, int] = {}
605
+ try:
606
+ rows = conn.execute(
607
+ "SELECT root_cause, COUNT(*) AS cnt FROM backtrack_events "
608
+ "WHERE session_id=? AND root_cause IS NOT NULL "
609
+ "GROUP BY root_cause",
610
+ (session_id,),
611
+ ).fetchall()
612
+ root_cause_summary = {r[0]: r[1] for r in rows}
613
+ except sqlite3.OperationalError:
614
+ root_cause_summary = {} # pre-v25: root_cause column absent
615
+
616
+ except Exception as exc:
617
+ return fail("internal", f"backtrack_log failed: {exc}")
618
+
619
+ advisory = ""
620
+ if count >= 5:
621
+ advisory = (
622
+ f"Anti-Paralysis: {count} backtracks this session. "
623
+ "Consider narrowing task scope or raising intensity level."
624
+ )
625
+ elif count >= 3:
626
+ advisory = f"Anti-Paralysis: {count} backtracks. Review scope if pattern continues."
627
+
628
+ # C2: concrete next step for the supplied root_cause
629
+ suggested_action = CANONICAL_REMEDIES.get(root_cause, "") if root_cause else ""
630
+
631
+ # emit trace event for replay
632
+ try:
633
+ import tracing
634
+
635
+ tracing.emit(
636
+ session_id,
637
+ "backtrack",
638
+ {
639
+ "from": from_formula,
640
+ "to": to_formula,
641
+ "reason": reason,
642
+ "count": count,
643
+ },
644
+ role=from_formula,
645
+ )
646
+ if advisory:
647
+ tracing.emit(
648
+ session_id,
649
+ "anti_paralysis_warn",
650
+ {
651
+ "count": count,
652
+ "advisory": advisory,
653
+ },
654
+ )
655
+ except Exception as _exc:
656
+ logger.debug("backtrack tracing skipped: %s", _exc)
657
+
658
+ return ok(
659
+ {
660
+ "count": count,
661
+ "advisory": advisory,
662
+ "suggested_action": suggested_action,
663
+ "root_cause_summary": root_cause_summary,
664
+ },
665
+ meta={"layer": "routing"},
666
+ )
667
+
668
+ return cos_backtrack_log
669
+
670
+
671
+ # ---------------------------------------------------------------------------
672
+ # cos_discovery
673
+ # ---------------------------------------------------------------------------
674
+
675
+
676
+ def register_cos_discovery(mcp, db_path):
677
+ @mcp.tool(
678
+ name="cos_discovery",
679
+ description=(
680
+ "Capture a mid-work discovery. decision=backtrack_now triggers an "
681
+ "immediate backtrack recommendation. decision=record_for_later stores "
682
+ "the discovery for session summary review."
683
+ ),
684
+ )
685
+ @safe_tool
686
+ def cos_discovery(
687
+ session_id: str,
688
+ task_marker: str,
689
+ persona_id: str,
690
+ kind: str,
691
+ summary: str,
692
+ impact_assessment: str,
693
+ decision: str,
694
+ ) -> str:
695
+ if decision not in ("backtrack_now", "record_for_later"):
696
+ return fail("validation", "decision must be backtrack_now or record_for_later")
697
+
698
+ bundle = _load_bundle(session_id, task_marker, persona_id)
699
+ schemas = _schemas()
700
+ disc = schemas.Discovery(
701
+ kind=kind,
702
+ summary=summary,
703
+ impact_assessment=impact_assessment,
704
+ decision=decision,
705
+ ts=_now_iso(),
706
+ )
707
+ bundle.discoveries.append(disc)
708
+ _save_bundle(session_id, bundle)
709
+
710
+ # Also store as observation for session_summary to surface
711
+ try:
712
+ with sqlite3.connect(db_path) as conn:
713
+ conn.execute(
714
+ "INSERT INTO observations (session_id, kind, content, ts) VALUES (?, ?, ?, ?)",
715
+ (
716
+ session_id,
717
+ f"discovery:{kind}",
718
+ json.dumps(
719
+ {"summary": summary, "impact": impact_assessment, "decision": decision}
720
+ ),
721
+ _now_iso(),
722
+ ),
723
+ )
724
+ except Exception as exc:
725
+ logger.debug("observation insert failed: %s", exc)
726
+
727
+ return ok(
728
+ {"stored": True, "action_required": decision == "backtrack_now"},
729
+ meta={"layer": "routing"},
730
+ )
731
+
732
+ return cos_discovery
733
+
734
+
735
+ # ---------------------------------------------------------------------------
736
+ # cos_situation_detect
737
+ # ---------------------------------------------------------------------------
738
+
739
+
740
+ def register_cos_situation_detect(mcp, db_path):
741
+ @mcp.tool(
742
+ name="cos_situation_detect",
743
+ description=(
744
+ "Classify a set of signals into a situational dispatch chain id "
745
+ "(incident-response, onboarding, scope-change, external-integration, "
746
+ "design-review, existing-project-takeover) or null if none match. "
747
+ "The matched situation overrides persona primary_formulas."
748
+ ),
749
+ )
750
+ @safe_tool
751
+ def cos_situation_detect(signals: str = "[]") -> str:
752
+ cog = _cog()
753
+ signal_set = set(json.loads(signals))
754
+ situations = cog.load_situation_registry()
755
+
756
+ for sit_id, sit in situations.items():
757
+ triggers = set(sit.get("trigger_signals", []))
758
+ matched = signal_set & triggers
759
+ if matched:
760
+ return ok(
761
+ {"situation_id": sit_id, "matched_signals": list(matched)},
762
+ meta={"layer": "routing"},
763
+ )
764
+
765
+ return ok({"situation_id": None, "matched_signals": []}, meta={"layer": "routing"})
766
+
767
+ return cos_situation_detect
768
+
769
+
770
+ # ---------------------------------------------------------------------------
771
+ # cos_takeover
772
+ # ---------------------------------------------------------------------------
773
+
774
+
775
+ def register_cos_takeover(mcp, db_path):
776
+ @mcp.tool(
777
+ name="cos_takeover",
778
+ description=(
779
+ "Bootstrap an existing-project-takeover session: sets the situation "
780
+ "to existing-project-takeover, picks legacy-maintainer persona, "
781
+ "and returns the first dispatch action (Analyst in reverse mode). "
782
+ "Use when inheriting a legacy repo with no docs."
783
+ ),
784
+ )
785
+ @safe_tool
786
+ def cos_takeover(
787
+ session_id: str,
788
+ task_marker: str,
789
+ repo_description: str = "", # reserved for Researcher pre-seeding in future slice
790
+ ) -> str:
791
+ cog = _cog()
792
+ schemas = _schemas()
793
+
794
+ persona_id = "legacy-maintainer"
795
+ situation_id = "existing-project-takeover"
796
+
797
+ bundle = schemas.EvidenceBundle(
798
+ task_marker=task_marker,
799
+ persona_id=persona_id,
800
+ situation_id=situation_id,
801
+ intensity="full",
802
+ )
803
+ _save_bundle(session_id, bundle)
804
+
805
+ state = schemas.SupervisorState(
806
+ session_id=session_id,
807
+ task_marker=task_marker,
808
+ persona_id=persona_id,
809
+ intensity="full",
810
+ situation_id=situation_id,
811
+ phase="ROUTING",
812
+ )
813
+ first_action = cog.advance(state, bundle)
814
+
815
+ return ok(
816
+ {
817
+ "persona_id": persona_id,
818
+ "situation_id": situation_id,
819
+ "first_action": {
820
+ "action": first_action.action,
821
+ "formula": first_action.formula,
822
+ "agent_file": first_action.agent_file,
823
+ "reason": first_action.reason,
824
+ },
825
+ },
826
+ meta={"layer": "routing"},
827
+ )
828
+
829
+ return cos_takeover
830
+
831
+
832
+ # ---------------------------------------------------------------------------
833
+ # Registration entry point
834
+ # ---------------------------------------------------------------------------
835
+
836
+ # ---------------------------------------------------------------------------
837
+ # Role-based cognitive routing
838
+ # Spec: docs/phase-n-role-based-routing-plan.md §2.6
839
+ # ---------------------------------------------------------------------------
840
+
841
+
842
+ def register_cos_analyze_task(mcp, db_path):
843
+ @mcp.tool(
844
+ name="cos_analyze_task",
845
+ description=(
846
+ "Extract TaskSignals (domain, action, novelty, urgency, scope, "
847
+ "external_dependency, is_takeover, breaking_change, ...) from a "
848
+ "prompt + optional memory/graph context. Replaces persona "
849
+ "keyword matching. Under 500ms; cached per task_marker."
850
+ ),
851
+ )
852
+ @safe_tool
853
+ def cos_analyze_task(
854
+ prompt: str,
855
+ task_marker: str = "",
856
+ complexity: str = "COMPLICATED",
857
+ dimensions: int = 1,
858
+ project_dir: str = "",
859
+ session_id: str = "",
860
+ ) -> str:
861
+ import task_analyzer # lazy
862
+ import tracing
863
+
864
+ agent_dir = _resolve_agent_dir()
865
+ pd = Path(project_dir) if project_dir else Path.cwd()
866
+ sid = session_id or "anon"
867
+ tracing.emit(
868
+ sid,
869
+ "analyze_start",
870
+ {
871
+ "prompt_len": len(prompt),
872
+ "task_marker": task_marker,
873
+ "complexity": complexity,
874
+ "dimensions": dimensions,
875
+ },
876
+ )
877
+ signals = task_analyzer.analyze_task(
878
+ prompt=prompt,
879
+ task_marker=task_marker or None,
880
+ complexity=complexity,
881
+ dimensions=dimensions,
882
+ agent_dir=agent_dir,
883
+ project_dir=pd,
884
+ )
885
+ tracing.emit(
886
+ sid,
887
+ "analyze_done",
888
+ {
889
+ "action": signals.action,
890
+ "domain": signals.domain,
891
+ "urgency": signals.urgency,
892
+ "scope_size": signals.scope_size,
893
+ "external_dependency": signals.external_dependency,
894
+ "is_takeover": signals.is_takeover,
895
+ "breaking_change": signals.breaking_change,
896
+ "extraction_ms": signals.extraction_ms,
897
+ "source_errors": signals.source_errors,
898
+ },
899
+ )
900
+ return ok(
901
+ signals.model_dump(),
902
+ meta={"layer": "routing", "source": "task_analyzer"},
903
+ )
904
+
905
+ return cos_analyze_task
906
+
907
+
908
+ def register_cos_compose_chain(mcp, db_path):
909
+ @mcp.tool(
910
+ name="cos_compose_chain",
911
+ description=(
912
+ "Compose an ordered formula-role chain from TaskSignals. "
913
+ "Strategy: situation override > preset match > per-role scoring "
914
+ "composer > hard fallback. Returns ComposedChain with provenance "
915
+ "(preset_id, preset_version, effective_threshold, activations)."
916
+ ),
917
+ )
918
+ @safe_tool
919
+ def cos_compose_chain(
920
+ signals_json: str,
921
+ situation_id: str = "",
922
+ preset_min_score: int = -1,
923
+ session_id: str = "",
924
+ ) -> str:
925
+ import formula_composer # lazy
926
+
927
+ schemas = _schemas()
928
+ signals = schemas.TaskSignals.model_validate_json(signals_json)
929
+ chain = formula_composer.compose_chain(
930
+ signals=signals,
931
+ situation_id=situation_id or None,
932
+ preset_min_score=None if preset_min_score < 0 else preset_min_score,
933
+ )
934
+ sid = session_id or "anon"
935
+ # Emit branch + compose_done traces AND stamp the chain to the
936
+ # agent-scoped state files the Hub /api/roles panel reads. Both live in
937
+ # roles_state so this MCP path and the auto-compose hook
938
+ # (hooks/_helpers/auto_compose.py) never drift on what the panel reads
939
+ # — the drift that left the panel empty.
940
+ try:
941
+ import roles_state
942
+
943
+ roles_state.record_compose_traces(chain, sid)
944
+ roles_state.stamp_roles(chain.chain)
945
+ except Exception as exc: # fire-and-forget telemetry — a write/serialize
946
+ logger.debug("roles trace/state write failed: %s", exc) # error must never fail compose
947
+
948
+ # telemetry — persist the lead persona for the dispatch.
949
+ # Schema (migration v14): one row per compose_chain call.
950
+ try:
951
+ import sqlite3 as _sqlite
952
+
953
+ _conn = _sqlite.connect(db_path)
954
+ try:
955
+ lead_persona = chain.chain[0] if chain.chain else "unknown"
956
+ # Confidence proxy: preset_matched > situation_override >
957
+ # composer_fallback > hard_fallback (1.0 / 0.85 / 0.7 / 0.4).
958
+ conf_map = {"preset": 1.0, "situation": 0.85, "composer": 0.7, "hard": 0.4}
959
+ conf = conf_map.get(chain.source, 0.5)
960
+ # Intensity is per-role; use lead role's first step as the
961
+ # session-level signal (pragmatic — full breakdown lives in
962
+ # the trace event).
963
+ intensity = "default"
964
+ _conn.execute(
965
+ "INSERT INTO persona_selections "
966
+ "(session_id, task_marker, persona_id, confidence, reason, intensity) "
967
+ "VALUES (?, ?, ?, ?, ?, ?)",
968
+ (sid, chain.preset_id or "", lead_persona, conf, chain.source, intensity),
969
+ )
970
+ _conn.commit()
971
+ finally:
972
+ _conn.close()
973
+ except Exception as exc:
974
+ logger.debug("persona_selections insert failed: %s", exc)
975
+ return ok(
976
+ chain.model_dump(),
977
+ meta={"layer": "routing", "source": "formula_composer"},
978
+ )
979
+
980
+ return cos_compose_chain
981
+
982
+
983
+ def register_cos_role_info(mcp, db_path):
984
+ @mcp.tool(
985
+ name="cos_role_info",
986
+ description=(
987
+ "Return metadata for a formula-role (researcher..refactorer): prompt_prefix, "
988
+ "tools_budget, intensity_steps, backtrack_triggers, "
989
+ "criteria_required. Useful for the main agent before dispatch."
990
+ ),
991
+ )
992
+ @safe_tool
993
+ def cos_role_info(role_id: str) -> str:
994
+ import formula_composer # lazy
995
+
996
+ roles = formula_composer.load_roles()
997
+ role = roles.get(role_id)
998
+ if role is None:
999
+ return fail("not_found", f"unknown role_id: {role_id}")
1000
+ keep = {
1001
+ k: role.get(k)
1002
+ for k in (
1003
+ "id",
1004
+ "role_name",
1005
+ "formula_ref",
1006
+ "agent_file",
1007
+ "intensity_steps",
1008
+ "tools_budget",
1009
+ "max_tokens_in",
1010
+ "max_tokens_out",
1011
+ "timeout_s",
1012
+ "model_pref",
1013
+ "backtrack_triggers",
1014
+ "criteria_required",
1015
+ "prompt_prefix",
1016
+ "parallel_dispatch",
1017
+ )
1018
+ if k in role
1019
+ }
1020
+ return ok(keep, meta={"layer": "routing", "source": "role_registry"})
1021
+
1022
+ return cos_role_info
1023
+
1024
+
1025
+ # ---------------------------------------------------------------------------
1026
+ # cos_dispatch_formula_run — real-dispatch tool
1027
+ # ---------------------------------------------------------------------------
1028
+
1029
+
1030
+ def _persist_dispatch_output(
1031
+ *,
1032
+ session_id: str,
1033
+ task_marker: str,
1034
+ persona_id: str,
1035
+ formula_id: str,
1036
+ output_json: dict,
1037
+ status: str,
1038
+ latency_ms: int,
1039
+ db_path: str,
1040
+ raw_transcript: str | None = None,
1041
+ ) -> int:
1042
+ bundle = _load_bundle(session_id, task_marker, persona_id)
1043
+ # Data-driven role → bundle field + Pydantic class resolution.
1044
+ # Role frontmatter declares `output_schema: cognition.<X>Output`;
1045
+ # `bundle_field` defaults to role id but can be overridden in frontmatter.
1046
+ field, cls = _resolve_role_persistence(formula_id)
1047
+ validation_failed = False
1048
+ if field and cls and status == "ok":
1049
+ try:
1050
+ parsed = cls.model_validate(output_json)
1051
+ setattr(bundle, field, parsed)
1052
+ except Exception as exc:
1053
+ # T1.6 — Pydantic validation runs BEFORE persistence.
1054
+ # On failure: mark degraded, skip the formula_dispatches INSERT
1055
+ # entirely (row would carry untrusted output_hash). Bundle still
1056
+ # saved with degraded_formulas marker so the supervisor can
1057
+ # backtrack.
1058
+ logger.warning(
1059
+ "Failed to validate %s output against schema: %s",
1060
+ formula_id,
1061
+ exc,
1062
+ )
1063
+ bundle.degraded_formulas.append(formula_id)
1064
+ status = "fail"
1065
+ validation_failed = True
1066
+ elif status == "timeout":
1067
+ bundle.degraded_formulas.append(formula_id)
1068
+
1069
+ _save_bundle(session_id, bundle)
1070
+
1071
+ # T1.6: skip the dispatch row INSERT when schema validation failed.
1072
+ # Returning the bundle field count keeps the caller signature stable.
1073
+ if validation_failed:
1074
+ return sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
1075
+
1076
+ raw = json.dumps(output_json, sort_keys=True, default=str).encode()
1077
+ output_hash = hashlib.sha256(raw).hexdigest()[:16]
1078
+ input_hash = hashlib.sha256(f"{session_id}:{formula_id}".encode()).hexdigest()[:16]
1079
+ # pull telemetry the Claude dispatcher stamps
1080
+ # into output_json["_meta"] (cost_usd, usage, model_usage, tool_calls,
1081
+ # tool_failures) and persist into the v23 columns. JSON-encoded so
1082
+ # the schema migration stays append-only — readers parse on demand.
1083
+ meta = output_json.get("_meta") if isinstance(output_json, dict) else None
1084
+ meta = meta if isinstance(meta, dict) else {}
1085
+ cost_usd_raw = meta.get("total_cost_usd")
1086
+ if isinstance(cost_usd_raw, (int, float)):
1087
+ cost_usd_val: float | None = float(cost_usd_raw)
1088
+ else:
1089
+ cost_usd_val = None
1090
+
1091
+ def _jsonb(value: Any) -> str | None:
1092
+ if value is None:
1093
+ return None
1094
+ try:
1095
+ return json.dumps(value, default=str, sort_keys=True)
1096
+ except (TypeError, ValueError):
1097
+ return None
1098
+
1099
+ try:
1100
+ with sqlite3.connect(db_path) as conn:
1101
+ conn.execute(
1102
+ "INSERT INTO formula_dispatches "
1103
+ "(session_id, task_marker, persona_id, formula_id, input_hash, "
1104
+ "output_hash, latency_ms, status, ts, "
1105
+ "cost_usd, budget_usd, usage_jsonb, model_usage_jsonb, "
1106
+ "tool_calls_jsonb, tool_failures_jsonb, "
1107
+ "sub_session_id, model, checkpoints_jsonb, error, raw_transcript) "
1108
+ "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
1109
+ (
1110
+ session_id,
1111
+ task_marker,
1112
+ persona_id,
1113
+ formula_id,
1114
+ input_hash,
1115
+ output_hash,
1116
+ latency_ms,
1117
+ status,
1118
+ _now_iso(),
1119
+ cost_usd_val,
1120
+ meta.get("budget_usd"),
1121
+ _jsonb(meta.get("usage")),
1122
+ _jsonb(meta.get("model_usage")),
1123
+ _jsonb(meta.get("tool_calls")),
1124
+ _jsonb(meta.get("tool_failures")),
1125
+ meta.get("session_id"),
1126
+ meta.get("model"),
1127
+ _jsonb(meta.get("checkpoints")),
1128
+ str(meta.get("error"))[:1000] if meta.get("error") else None,
1129
+ raw_transcript[:50000] if raw_transcript else None,
1130
+ ),
1131
+ )
1132
+ except Exception as exc:
1133
+ logger.debug("formula_dispatches insert failed: %s", exc)
1134
+
1135
+ return sum(1 for f in _all_bundle_fields() if getattr(bundle, f, None) is not None)
1136
+
1137
+
1138
+ def _emit_dispatch_metrics_safe(
1139
+ *,
1140
+ db_path: str,
1141
+ formula_id: str,
1142
+ status: str,
1143
+ latency_ms: int,
1144
+ output_json: dict,
1145
+ ) -> None:
1146
+ import sqlite3 as _sqlite3
1147
+
1148
+ try:
1149
+ from tools.metrics import metric_record # type: ignore[import]
1150
+
1151
+ _outcome_map = {
1152
+ "ok": "success",
1153
+ "timeout": "blocked",
1154
+ "error": "partial",
1155
+ "skipped": "partial",
1156
+ }
1157
+ outcome = _outcome_map.get(status, "partial")
1158
+ meta = output_json.get("_meta") if isinstance(output_json, dict) else {}
1159
+ meta = meta if isinstance(meta, dict) else {}
1160
+ model_used = meta.get("model") or None
1161
+ with _sqlite3.connect(db_path) as conn:
1162
+ metric_record(
1163
+ conn,
1164
+ agent_type="dispatch",
1165
+ outcome=outcome,
1166
+ duration_ms=latency_ms,
1167
+ domain=formula_id,
1168
+ model=model_used,
1169
+ complexity="CLEAR",
1170
+ )
1171
+ except Exception as exc:
1172
+ logger.debug("dispatch metric emit failed: %s", exc)
1173
+
1174
+
1175
+ def _preset_role_hint(session_id: str, formula_id: str, db_path) -> dict:
1176
+ # Tier-2 lookup (claude-sdk.md §7.3): the session's composed preset is
1177
+ # read back from persona_selections (preset_id lives in its task_marker
1178
+ # column — see register_cos_compose_chain's INSERT), then that preset's
1179
+ # roles_adapter_hints[formula_id]. Fail-open: hints are advisory.
1180
+ if not db_path:
1181
+ return {}
1182
+ try:
1183
+ import sqlite3 as _sqlite3
1184
+
1185
+ conn = _sqlite3.connect(db_path)
1186
+ try:
1187
+ row = conn.execute(
1188
+ "SELECT task_marker FROM persona_selections WHERE session_id = ? "
1189
+ "ORDER BY rowid DESC LIMIT 1",
1190
+ (session_id,),
1191
+ ).fetchone()
1192
+ finally:
1193
+ conn.close()
1194
+ preset_id = row[0] if row and row[0] else ""
1195
+ if not preset_id:
1196
+ return {}
1197
+ import formula_composer
1198
+
1199
+ presets, _version = formula_composer.load_presets()
1200
+ preset = next((p for p in presets if p.get("id") == preset_id), {})
1201
+ hint = (preset.get("roles_adapter_hints") or {}).get(formula_id) or {}
1202
+ return hint if isinstance(hint, dict) else {}
1203
+ except Exception as exc:
1204
+ logger.debug("preset hint lookup failed: %s", exc)
1205
+ return {}
1206
+
1207
+
1208
+ def _empirical_model(complexity: str, db_path) -> str:
1209
+ # Tier-4: cos_route_model's recommendation, only when real outcome
1210
+ # history backs it (cold-start static defaults must not override the
1211
+ # SDK default for every dispatch).
1212
+ if not db_path or not complexity.strip():
1213
+ return ""
1214
+ try:
1215
+ import sqlite3 as _sqlite3
1216
+
1217
+ from tools.routing import route_model_bandit
1218
+
1219
+ conn = _sqlite3.connect(db_path)
1220
+ try:
1221
+ result = route_model_bandit(conn, complexity=complexity.strip().upper())
1222
+ finally:
1223
+ conn.close()
1224
+ if int(result.get("data_points") or 0) > 0:
1225
+ return str(result.get("recommended_model") or "")
1226
+ return ""
1227
+ except Exception as exc:
1228
+ logger.debug("empirical model lookup failed: %s", exc)
1229
+ return ""
1230
+
1231
+
1232
+ def _resolve_dispatch_model(
1233
+ formula_id: str,
1234
+ session_id: str,
1235
+ meta: dict,
1236
+ model: str,
1237
+ complexity: str,
1238
+ db_path,
1239
+ ) -> str:
1240
+ level = complexity.strip().lower()
1241
+ hint_pref = _preset_role_hint(session_id, formula_id, db_path).get("model_pref") or {}
1242
+ role_pref = meta.get("model_pref") or {}
1243
+ resolved = ""
1244
+ for candidate, source in (
1245
+ (model.strip(), "explicit"),
1246
+ (hint_pref.get(level, ""), "preset_hint"),
1247
+ (role_pref.get(level, ""), "role_pref"),
1248
+ ):
1249
+ if candidate:
1250
+ logger.info("dispatch model resolved for %s: %s via %s", formula_id, candidate, source)
1251
+ resolved = candidate
1252
+ break
1253
+ if not resolved:
1254
+ # Empirical is the expensive tier (sqlite connect + bandit query/sampling)
1255
+ # — consult it lazily, only when the static tiers all miss.
1256
+ empirical = _empirical_model(complexity, db_path)
1257
+ if empirical:
1258
+ logger.info("dispatch model resolved for %s: %s via empirical", formula_id, empirical)
1259
+ resolved = empirical
1260
+ # Cost-routed independent reviewer: a review role runs one tier cheaper than
1261
+ # the generator. Gated by COS_ROUTER_REVIEWER_CHEAPER (default off, unchanged).
1262
+ if resolved and os.environ.get("COS_ROUTER_REVIEWER_CHEAPER"):
1263
+ from tools.routing import _REVIEW_ROLES, reviewer_model
1264
+
1265
+ if formula_id in _REVIEW_ROLES:
1266
+ cheaper = reviewer_model(resolved)
1267
+ if cheaper and cheaper != resolved:
1268
+ logger.info("reviewer %s downgraded to cheaper tier %s", formula_id, cheaper)
1269
+ resolved = cheaper
1270
+ return resolved
1271
+
1272
+
1273
+ def _build_dispatch_request(
1274
+ formula_id: str,
1275
+ session_id: str,
1276
+ task_marker: str,
1277
+ persona_id: str,
1278
+ intensity: str,
1279
+ timeout_s: float | None,
1280
+ model: str = "",
1281
+ complexity: str = "",
1282
+ db_path=None,
1283
+ ):
1284
+ """Build a DispatchRequest from session state (shared by run-one and run-parallel)."""
1285
+ from thinking_os import dispatcher as _disp # lazy: avoid circular at import time
1286
+
1287
+ cog = _cog()
1288
+ agents = cog.load_agent_registry()
1289
+ meta = agents.get(formula_id) or {}
1290
+ agent_file_rel = f"src/core/thinking_os/agents/{meta.get('_file', f'{formula_id}.md')}"
1291
+ agent_path = Path(__file__).resolve().parent.parent / "agents" / meta.get("_file", "")
1292
+ prompt_text = agent_path.read_text() if agent_path.exists() else ""
1293
+
1294
+ bundle = _load_bundle(session_id, task_marker, persona_id)
1295
+ input_slice = cog.build_input_slice(formula_id, bundle)
1296
+ input_slice["intensity_steps"] = cog._intensity_steps(formula_id, intensity)
1297
+
1298
+ resolved_model = _resolve_dispatch_model(
1299
+ formula_id, session_id, meta, model, complexity, db_path
1300
+ )
1301
+
1302
+ return _disp.DispatchRequest(
1303
+ formula_id=formula_id,
1304
+ agent_file=str(agent_path if agent_path.exists() else agent_file_rel),
1305
+ prompt=prompt_text,
1306
+ input_slice=input_slice,
1307
+ persona_id=persona_id,
1308
+ intensity=intensity if intensity in ("light", "standard", "full") else "standard",
1309
+ timeout_s=float(timeout_s) if timeout_s else float(meta.get("timeout_s", 300)),
1310
+ session_id=session_id,
1311
+ long_context=bool(meta.get("long_context", False)),
1312
+ model=resolved_model or None,
1313
+ )
1314
+
1315
+
1316
+ def register_cos_dispatch_formula_run(mcp, db_path):
1317
+ @mcp.tool(
1318
+ name="cos_dispatch_formula_run",
1319
+ description=(
1320
+ "EXPLICIT, OPT-IN sub-agent spawn for one role. Costs ~5k tokens "
1321
+ "per call (system prompt + input slice + completion) and rebuilds "
1322
+ "context inside the sub-agent. PREFER lazy-loading: read "
1323
+ "src/core/thinking_os/agents/<role>.md inline and produce the output "
1324
+ "schema yourself — same accuracy, far fewer tokens, no context "
1325
+ "rebuild penalty. Use this tool only when (a) the role's work is "
1326
+ "long-running and would dominate the main loop, or (b) you "
1327
+ "explicitly want a separate session for parallelism. If no SDK is "
1328
+ "available, returns status='skipped' and the main agent should "
1329
+ "execute the role's procedure inline."
1330
+ ),
1331
+ )
1332
+ @safe_tool
1333
+ def cos_dispatch_formula_run(
1334
+ formula_id: str,
1335
+ session_id: str,
1336
+ task_marker: str,
1337
+ persona_id: str,
1338
+ intensity: str = "standard",
1339
+ timeout_s: float | None = None,
1340
+ model: str = "",
1341
+ complexity: str = "",
1342
+ ) -> str:
1343
+ import asyncio as _asyncio
1344
+
1345
+ from thinking_os import budget as _budget, dispatcher as _disp
1346
+
1347
+ gate = _budget.check(db_path)
1348
+ if not gate.allowed:
1349
+ return fail("budget", gate.reason)
1350
+ chain_gate = _budget.chain_check(db_path, task_marker)
1351
+ if not chain_gate.allowed:
1352
+ return fail("budget", chain_gate.reason)
1353
+
1354
+ try:
1355
+ req = _build_dispatch_request(
1356
+ formula_id,
1357
+ session_id,
1358
+ task_marker,
1359
+ persona_id,
1360
+ intensity,
1361
+ timeout_s,
1362
+ model,
1363
+ complexity,
1364
+ db_path,
1365
+ )
1366
+ except Exception as exc:
1367
+ return fail("validation", f"failed to build request: {exc}")
1368
+
1369
+ d = _disp.get_dispatcher(request=req)
1370
+
1371
+ # Trace event — visible in cos cognition trace replay so the
1372
+ # flowchart shows the actual sub-agent execution span.
1373
+ try:
1374
+ import tracing
1375
+
1376
+ tracing.emit(
1377
+ session_id,
1378
+ "dispatch_started",
1379
+ {
1380
+ "formula_id": formula_id,
1381
+ "dispatcher_name": getattr(d, "name", "unknown"),
1382
+ "intensity": intensity,
1383
+ "model": req.model,
1384
+ "long_context": req.long_context,
1385
+ },
1386
+ role=formula_id,
1387
+ phase="EXECUTE",
1388
+ )
1389
+ except Exception as exc:
1390
+ logger.debug("dispatch_started trace emit failed: %s", exc)
1391
+
1392
+ try:
1393
+ result = _asyncio.run(d.dispatch(req))
1394
+ except RuntimeError as exc:
1395
+ # Nested loop — fall back to a fresh thread-owned loop
1396
+ if "already running" in str(exc):
1397
+ import threading
1398
+
1399
+ box: dict = {}
1400
+
1401
+ def _runner():
1402
+ loop = _asyncio.new_event_loop()
1403
+ try:
1404
+ box["result"] = loop.run_until_complete(d.dispatch(req))
1405
+ finally:
1406
+ loop.close()
1407
+
1408
+ t = threading.Thread(target=_runner, daemon=True)
1409
+ t.start()
1410
+ t.join(timeout=req.timeout_s + 10)
1411
+ if "result" not in box:
1412
+ return fail("transient", "dispatcher thread did not return")
1413
+ result = box["result"]
1414
+ else:
1415
+ return fail("internal", f"asyncio.run failed: {exc}")
1416
+
1417
+ filled = 0
1418
+ if result.status in ("ok", "timeout") and result.output_json:
1419
+ filled = _persist_dispatch_output(
1420
+ session_id=session_id,
1421
+ task_marker=task_marker,
1422
+ persona_id=persona_id,
1423
+ formula_id=formula_id,
1424
+ output_json=result.output_json,
1425
+ status=result.status,
1426
+ latency_ms=result.latency_ms,
1427
+ db_path=db_path,
1428
+ raw_transcript=result.raw_transcript,
1429
+ )
1430
+
1431
+ # T2.5 + T8.4: emit dispatch cost and duration as coding-os metrics
1432
+ # so cos_metric_trend surfaces spend over time.
1433
+ _emit_dispatch_metrics_safe(
1434
+ db_path=db_path,
1435
+ formula_id=formula_id,
1436
+ status=result.status,
1437
+ latency_ms=result.latency_ms,
1438
+ output_json=result.output_json,
1439
+ )
1440
+
1441
+ # Trace event — pairs with dispatch_started above so the cognition
1442
+ # replay shows the full sub-agent execution span (not just supervisor
1443
+ # routing decision).
1444
+ try:
1445
+ import tracing
1446
+
1447
+ _meta = result.output_json.get("_meta") if isinstance(result.output_json, dict) else {}
1448
+ _meta = _meta if isinstance(_meta, dict) else {}
1449
+ tracing.emit(
1450
+ session_id,
1451
+ "dispatch_completed",
1452
+ {
1453
+ "formula_id": formula_id,
1454
+ "status": result.status,
1455
+ "latency_ms": result.latency_ms,
1456
+ "cost_usd": _meta.get("total_cost_usd"),
1457
+ "sub_session_id": _meta.get("session_id"),
1458
+ "model": _meta.get("model"),
1459
+ "bundle_fields_filled": filled,
1460
+ "error": result.error,
1461
+ },
1462
+ role=formula_id,
1463
+ phase="EXECUTE",
1464
+ )
1465
+ except Exception as exc:
1466
+ logger.debug("dispatch_completed trace emit failed: %s", exc)
1467
+
1468
+ return ok(
1469
+ {
1470
+ "status": result.status,
1471
+ "formula_id": result.formula_id,
1472
+ "dispatcher_name": result.dispatcher_name,
1473
+ "latency_ms": result.latency_ms,
1474
+ "output_json": result.output_json,
1475
+ "error": result.error,
1476
+ "bundle_fields_filled": filled,
1477
+ },
1478
+ meta={"layer": "dispatch"},
1479
+ )
1480
+
1481
+ return cos_dispatch_formula_run
1482
+
1483
+
1484
+ def register_cos_dispatch_parallel_run(mcp, db_path):
1485
+ @mcp.tool(
1486
+ name="cos_dispatch_parallel_run",
1487
+ description=(
1488
+ "Spawn multiple formula-agents concurrently via asyncio.gather. "
1489
+ "Use when the supervisor returns action='dispatch_parallel' "
1490
+ "(e.g. security_auditor layers). Each output is persisted to the bundle. "
1491
+ "Returns list of DispatchResults in input order."
1492
+ ),
1493
+ )
1494
+ @safe_tool
1495
+ def cos_dispatch_parallel_run(
1496
+ formula_ids: list[str],
1497
+ session_id: str,
1498
+ task_marker: str,
1499
+ persona_id: str,
1500
+ intensity: str = "standard",
1501
+ timeout_s: float | None = None,
1502
+ model: str = "",
1503
+ complexity: str = "",
1504
+ ) -> str:
1505
+ import asyncio as _asyncio
1506
+
1507
+ from thinking_os import budget as _budget, dispatcher as _disp
1508
+
1509
+ if not formula_ids:
1510
+ return fail("validation", "formula_ids must be non-empty")
1511
+
1512
+ # One check authorizes all N concurrent spawns, so it must gate on the
1513
+ # projected total — spent-only lets the fan-out overrun the cap.
1514
+ estimate = _budget.estimate_dispatch_cost(db_path, len(formula_ids))
1515
+ gate = _budget.check(db_path, additional_estimate_usd=estimate)
1516
+ if not gate.allowed:
1517
+ return fail("budget", gate.reason)
1518
+ chain_gate = _budget.chain_check(db_path, task_marker, additional_estimate_usd=estimate)
1519
+ if not chain_gate.allowed:
1520
+ return fail("budget", chain_gate.reason)
1521
+
1522
+ try:
1523
+ requests = [
1524
+ _build_dispatch_request(
1525
+ fid,
1526
+ session_id,
1527
+ task_marker,
1528
+ persona_id,
1529
+ intensity,
1530
+ timeout_s,
1531
+ model,
1532
+ complexity,
1533
+ db_path,
1534
+ )
1535
+ for fid in formula_ids
1536
+ ]
1537
+ except Exception as exc:
1538
+ return fail("validation", f"failed to build requests: {exc}")
1539
+
1540
+ d = _disp.get_dispatcher()
1541
+
1542
+ async def _gather_all():
1543
+ return await _asyncio.gather(
1544
+ *(d.dispatch(req) for req in requests),
1545
+ return_exceptions=True,
1546
+ )
1547
+
1548
+ import time as _time
1549
+
1550
+ t0 = _time.monotonic()
1551
+ try:
1552
+ gathered = _asyncio.run(_gather_all())
1553
+ except RuntimeError:
1554
+ # Nested-loop fallback: run in a dedicated thread with fresh loop
1555
+ import threading
1556
+
1557
+ box: dict = {}
1558
+
1559
+ def _runner():
1560
+ loop = _asyncio.new_event_loop()
1561
+ try:
1562
+ box["result"] = loop.run_until_complete(_gather_all())
1563
+ finally:
1564
+ loop.close()
1565
+
1566
+ t = threading.Thread(target=_runner, daemon=True)
1567
+ t.start()
1568
+ deadline = max(req.timeout_s for req in requests) + 10
1569
+ t.join(timeout=deadline)
1570
+ gathered = box.get("result", [])
1571
+ wall_ms = int((_time.monotonic() - t0) * 1000)
1572
+
1573
+ results = []
1574
+ ok_count = 0
1575
+ for req, outcome in zip(requests, gathered):
1576
+ if isinstance(outcome, Exception):
1577
+ results.append(
1578
+ {
1579
+ "status": "error",
1580
+ "formula_id": req.formula_id,
1581
+ "error": f"{type(outcome).__name__}: {outcome}",
1582
+ "dispatcher_name": d.name,
1583
+ "latency_ms": 0,
1584
+ "output_json": {},
1585
+ "bundle_fields_filled": 0,
1586
+ }
1587
+ )
1588
+ continue
1589
+ filled = 0
1590
+ if outcome.status == "ok" and outcome.output_json:
1591
+ filled = _persist_dispatch_output(
1592
+ session_id=session_id,
1593
+ task_marker=task_marker,
1594
+ persona_id=persona_id,
1595
+ formula_id=outcome.formula_id,
1596
+ output_json=outcome.output_json,
1597
+ status=outcome.status,
1598
+ latency_ms=outcome.latency_ms,
1599
+ db_path=db_path,
1600
+ raw_transcript=outcome.raw_transcript,
1601
+ )
1602
+ ok_count += 1
1603
+ results.append(
1604
+ {
1605
+ "status": outcome.status,
1606
+ "formula_id": outcome.formula_id,
1607
+ "dispatcher_name": outcome.dispatcher_name,
1608
+ "latency_ms": outcome.latency_ms,
1609
+ "output_json": outcome.output_json,
1610
+ "error": outcome.error,
1611
+ "bundle_fields_filled": filled,
1612
+ }
1613
+ )
1614
+
1615
+ return ok(
1616
+ {
1617
+ "results": results,
1618
+ "parallel_wall_ms": wall_ms,
1619
+ "ok_count": ok_count,
1620
+ "total": len(results),
1621
+ },
1622
+ meta={"layer": "dispatch"},
1623
+ )
1624
+
1625
+ return cos_dispatch_parallel_run
1626
+
1627
+
1628
+ def classify_prompt_heuristic(prompt: str) -> dict:
1629
+ """Deterministic Cynefin + dimensions heuristic — shared by the
1630
+ cos_classify_prompt tool and the hub chat auto-router (no LLM call)."""
1631
+ import re as _re
1632
+
1633
+ text = (prompt or "").strip().lower()
1634
+ if not text:
1635
+ return {
1636
+ "complexity": "CLEAR",
1637
+ "dimensions": 1,
1638
+ "signals": [],
1639
+ "hit_domains": [],
1640
+ "reasoning": "empty prompt",
1641
+ }
1642
+
1643
+ signals: list[str] = []
1644
+ complexity = "CLEAR"
1645
+
1646
+ chaotic_re = _re.compile(
1647
+ r"\b(p0|p1|outage|down|broken|crashed?|emergency|urgent|"
1648
+ r"fire|on[- ]call|paged|rollback (?:now|asap))\b"
1649
+ )
1650
+ if chaotic_re.search(text):
1651
+ complexity = "CHAOTIC"
1652
+ signals.append("incident-language")
1653
+
1654
+ complex_re = _re.compile(
1655
+ r"\b(best way|explore|experiment|optimi[sz]e|research|novel|"
1656
+ r"investigate|figure out|prototype|spike|trade[- ]off|benchmark)\b"
1657
+ )
1658
+ if complexity == "CLEAR" and complex_re.search(text):
1659
+ complexity = "COMPLEX"
1660
+ signals.append("exploratory-language")
1661
+
1662
+ complicated_re = _re.compile(
1663
+ r"\b(design|architect|integrate|refactor|implement|build|migrat\w*|"
1664
+ r"split|merge|extract|generali[sz]e|extend|orchestrat\w*|"
1665
+ r"normali[sz]e|denormali[sz]e)\b"
1666
+ )
1667
+ if complexity == "CLEAR" and complicated_re.search(text):
1668
+ complexity = "COMPLICATED"
1669
+ signals.append("design-language")
1670
+
1671
+ word_count = len(text.split())
1672
+ if complexity == "CLEAR" and word_count > 60:
1673
+ complexity = "COMPLICATED"
1674
+ signals.append(f"prompt-length={word_count}")
1675
+
1676
+ domain_patterns = {
1677
+ "backend": r"\b(api|backend|server|django|fastapi|fiber|endpoint|router|service)\b",
1678
+ "frontend": r"\b(frontend|react|next\.?js|nextjs|component|ui|client|page|jsx|tsx)\b",
1679
+ "mobile": r"\b(mobile|ios|android|react native|expo|swift|kotlin)\b",
1680
+ "ai": r"\b(llm|ai|prompt|embedding|rag|model|completion|token)\b",
1681
+ "security": r"\b(security|auth|permission|csrf|xss|sql injection|jwt|oauth|tls|encryption|secret)\b",
1682
+ "ops": r"\b(deploy|ci/cd|docker|kubernetes|k8s|infra|monitoring|alert|runbook|sre)\b",
1683
+ "docs": r"\b(doc|documentation|readme|spec|playbook|adr)\b",
1684
+ "db": r"\b(database|sql|sqlite|postgres|mysql|migration|schema|index|query)\b",
1685
+ "graph": r"\b(graph|neo4j|kuzu|node|edge|traversal)\b",
1686
+ "test": r"\b(test|testing|pytest|jest|coverage|fixture|mock)\b",
1687
+ }
1688
+ hit_domains: list[str] = []
1689
+ for name, pat in domain_patterns.items():
1690
+ if _re.search(pat, text):
1691
+ hit_domains.append(name)
1692
+ dimensions = max(1, len(hit_domains))
1693
+ if dimensions >= 5 and complexity == "CLEAR":
1694
+ complexity = "COMPLICATED"
1695
+ signals.append(f"multi-dimension={dimensions}")
1696
+
1697
+ trivial_re = _re.compile(r"^(fix typo|update doc(?:string)?|tweak (?:wording|comment))\b")
1698
+ if (
1699
+ trivial_re.search(text)
1700
+ and word_count < 15
1701
+ and len(hit_domains) <= 1
1702
+ and complexity != "CHAOTIC"
1703
+ ):
1704
+ complexity = "CLEAR"
1705
+ dimensions = 1
1706
+ signals = ["trivial-edit-shortcut"]
1707
+
1708
+ reasoning = (
1709
+ f"Cynefin: {complexity} ({', '.join(signals) or 'no escalating signals'}); "
1710
+ f"dims={dimensions} from domains: {', '.join(hit_domains) or 'none'}"
1711
+ )
1712
+ return {
1713
+ "complexity": complexity,
1714
+ "dimensions": dimensions,
1715
+ "signals": signals,
1716
+ "hit_domains": hit_domains,
1717
+ "reasoning": reasoning,
1718
+ }
1719
+
1720
+
1721
+ def register_cos_classify_prompt(mcp, db_path):
1722
+ """Register cos_classify_prompt — heuristic Cynefin + dimensions classifier.
1723
+
1724
+ Replaces the manual `bash write-state.sh .thinking_os-gate "COMPLICATED 3"`
1725
+ step. The agent calls this on the user's prompt and gets back a recorded
1726
+ gate without manually counting domains or evaluating Cynefin signals.
1727
+ """
1728
+
1729
+ @mcp.tool(
1730
+ name="cos_classify_prompt",
1731
+ description=(
1732
+ "Heuristic Cynefin + dimensions classifier. Reads a user prompt "
1733
+ "and returns {complexity, dimensions, reasoning, signals}. "
1734
+ "Optionally writes the gate marker so enforce-task-start.sh "
1735
+ "passes. Replaces the manual `write-state.sh .thinking_os-gate` "
1736
+ "step. Sub-second; deterministic; no LLM call."
1737
+ ),
1738
+ )
1739
+ @safe_tool
1740
+ def cos_classify_prompt(
1741
+ prompt: str,
1742
+ record: bool = True,
1743
+ agent_dir: str = "",
1744
+ ) -> str:
1745
+ import os as _os
1746
+
1747
+ if not (prompt or "").strip():
1748
+ return ok(
1749
+ {
1750
+ "complexity": "CLEAR",
1751
+ "dimensions": 1,
1752
+ "reasoning": "empty prompt",
1753
+ "signals": [],
1754
+ "recorded": False,
1755
+ },
1756
+ meta={"layer": "routing"},
1757
+ )
1758
+
1759
+ heuristic = classify_prompt_heuristic(prompt)
1760
+ complexity = heuristic["complexity"]
1761
+ dimensions = heuristic["dimensions"]
1762
+ signals = heuristic["signals"]
1763
+ hit_domains = heuristic["hit_domains"]
1764
+ reasoning = heuristic["reasoning"]
1765
+
1766
+ # Record the gate so enforce-task-start.sh passes — but ONLY when a
1767
+ # panel session is resolvable. The MCP server has no per-call panel
1768
+ # env, so this succeeds mainly when COS_PANEL_DIR is set (or agent_dir
1769
+ # is a real panel dir). It writes the SAME session-prefixed format the
1770
+ # strict panel reader (check-state.sh) requires; a bare
1771
+ # value or an agent-dir write would be silently rejected and leave a
1772
+ # misleading fossil — so we do neither, reporting recorded=false + a
1773
+ # shell hint instead. (Fixes the prior wrong-dir/wrong-format
1774
+ # /no-trace bug.)
1775
+ recorded = False
1776
+ record_hint = ""
1777
+ if record:
1778
+ panel_dir = _os.environ.get("COS_PANEL_DIR") or agent_dir
1779
+ sid = ""
1780
+ if panel_dir:
1781
+ sid_file = Path(panel_dir) / "session-id"
1782
+ if sid_file.exists():
1783
+ try:
1784
+ sid = sid_file.read_text(encoding="utf-8").strip().split(" ")[0]
1785
+ except OSError:
1786
+ sid = ""
1787
+ if panel_dir and sid:
1788
+ try:
1789
+ gate_path = Path(panel_dir) / ".thinking_os-gate"
1790
+ gate_path.parent.mkdir(parents=True, exist_ok=True)
1791
+ tmp_path = gate_path.with_name(".thinking_os-gate.tmp")
1792
+ tmp_path.write_text(f"{sid} {complexity} {dimensions}\n", encoding="utf-8")
1793
+ tmp_path.replace(gate_path)
1794
+ recorded = True
1795
+ try:
1796
+ import tracing
1797
+
1798
+ tracing.emit(
1799
+ sid,
1800
+ "classify",
1801
+ {"complexity": complexity, "dimensions": dimensions},
1802
+ )
1803
+ except Exception as exc:
1804
+ from core.logging_os import swallow_safe
1805
+
1806
+ swallow_safe("thinking_os.cognition", "classify trace emit failed", exc=exc)
1807
+ except OSError:
1808
+ recorded = False
1809
+ if not recorded:
1810
+ record_hint = (
1811
+ "gate not recorded (no panel session context from MCP); "
1812
+ "record it in your shell: write-state.sh .thinking_os-gate "
1813
+ f'"{complexity} {dimensions}"'
1814
+ )
1815
+
1816
+ # Discoverability nudge (TASK-509): a COMPLICATED+ gate needs the
1817
+ # cognition surface (role composition / formula dispatch, Rule 15), but a
1818
+ # lean profile may have disabled it. Surface the one-liner to re-enable
1819
+ # rather than letting the agent hit a module_disabled wall mid-plan.
1820
+ nudge = ""
1821
+ if (
1822
+ complexity in ("COMPLICATED", "COMPLEX")
1823
+ and _gated_module("cos_compose_chain") == "cognition"
1824
+ ):
1825
+ nudge = (
1826
+ f"{complexity} work but the cognition module is OFF — role "
1827
+ "composition / formula dispatch are unavailable. Enable it with "
1828
+ "`cos module enable cognition` (or scaffold with `--profile full`)."
1829
+ )
1830
+
1831
+ return ok(
1832
+ {
1833
+ "complexity": complexity,
1834
+ "dimensions": dimensions,
1835
+ "reasoning": reasoning,
1836
+ "signals": signals,
1837
+ "domains": hit_domains,
1838
+ "recorded": recorded,
1839
+ "record_hint": record_hint,
1840
+ "nudge": nudge,
1841
+ },
1842
+ meta={"layer": "routing"},
1843
+ )
1844
+
1845
+ return cos_classify_prompt
1846
+
1847
+
1848
+ def register_all(mcp, db_path: str) -> None:
1849
+ """Register all 15 cognition tools with the MCP server."""
1850
+ register_cos_supervise(mcp, db_path)
1851
+ register_cos_supervise_record_output(mcp, db_path)
1852
+ register_cos_dispatch_formula(mcp, db_path)
1853
+ register_cos_ambiguity_check(mcp, db_path)
1854
+ register_cos_traceability(mcp, db_path)
1855
+ register_cos_backtrack_log(mcp, db_path)
1856
+ register_cos_discovery(mcp, db_path)
1857
+ register_cos_situation_detect(mcp, db_path)
1858
+ register_cos_takeover(mcp, db_path)
1859
+ # Additions
1860
+ register_cos_analyze_task(mcp, db_path)
1861
+ register_cos_compose_chain(mcp, db_path)
1862
+ register_cos_role_info(mcp, db_path)
1863
+ # real dispatch (opt-in, costly)
1864
+ register_cos_dispatch_formula_run(mcp, db_path)
1865
+ register_cos_dispatch_parallel_run(mcp, db_path)
1866
+ # auto-Classify (eliminates manual gate recording)
1867
+ register_cos_classify_prompt(mcp, db_path)