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
thinking_os/server.py ADDED
@@ -0,0 +1,3160 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Coding OS — Thinking OS MCP Server (stdio transport).
4
+
5
+ Agent-agnostic self-learning system for AI coding agents.
6
+ Tools live in the modules under tools/; board_os and graph_os tools are
7
+ mounted onto the same FastMCP server when their packages are importable.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import logging
14
+ import os
15
+ import sys
16
+ from pathlib import Path
17
+
18
+ from database import get_db_stats, get_pooled_conn, init_db, project_root
19
+ from mcp.server.fastmcp import FastMCP
20
+ from tools._shared import apply_module_tool_gating, fail, ok, safe_tool
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Logging — central via core.logging_os; .mcp.log retained as MCP-specific sink.
24
+ # ---------------------------------------------------------------------------
25
+ from core.logging_os import setup as _logging_os_setup
26
+
27
+ _logging_os_setup(level="info")
28
+ logger = logging.getLogger("thinking_os")
29
+
30
+ _LOG_FORMAT = "%(asctime)s [%(name)s] %(levelname)s: %(message)s"
31
+ try:
32
+ _state_dir = Path(os.environ.get("COS_STATE_DIR") or ".coding-os")
33
+ _state_dir.mkdir(parents=True, exist_ok=True)
34
+ _file_handler = logging.FileHandler(_state_dir / ".mcp.log", mode="a", encoding="utf-8")
35
+ _file_handler.setFormatter(logging.Formatter(_LOG_FORMAT))
36
+ logging.getLogger().addHandler(_file_handler)
37
+ except OSError as _exc:
38
+ logger.debug("mcp log file mirror unavailable: %s", _exc)
39
+
40
+ # ---------------------------------------------------------------------------
41
+ # MCP Server
42
+ # ---------------------------------------------------------------------------
43
+ mcp = FastMCP("coding_os_mcp")
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Database bootstrap
47
+ # ---------------------------------------------------------------------------
48
+ _db_conn = init_db()
49
+
50
+ # Opt-in continuous indexer. No-op unless COS_BACKGROUND_INDEX=1.
51
+ # Wrapped in try/except so a broken indexer never blocks MCP startup.
52
+ try:
53
+ from background import maybe_start_indexer
54
+
55
+ _bg_status = maybe_start_indexer()
56
+ if _bg_status.get("started"):
57
+ logger.info("background indexer started: %s", _bg_status.get("reason"))
58
+ except Exception as exc:
59
+ logger.warning("background indexer bootstrap failed: %s", exc)
60
+
61
+
62
+ # ---------------------------------------------------------------------------
63
+ # Health check tool
64
+ # ---------------------------------------------------------------------------
65
+ @mcp.tool(
66
+ name="cos_health",
67
+ annotations={
68
+ "title": "Thinking OS Health Check",
69
+ "readOnlyHint": True,
70
+ "destructiveHint": False,
71
+ "idempotentHint": True,
72
+ "openWorldHint": False,
73
+ },
74
+ )
75
+ @safe_tool
76
+ def thinking_os_health() -> str:
77
+ """Return database health stats: row counts per table, schema version, DB size, FTS5 availability, embeddings status.
78
+
79
+ Use this tool to verify the thinking_os database is operational and
80
+ to get a quick summary of stored data volume.
81
+
82
+ Returns:
83
+ str: JSON object with keys: tables (row counts), schema_version,
84
+ fts5_available, db_size_bytes, rag (embeddings + doc_chunks status).
85
+ """
86
+ stats = get_db_stats(_db_conn)
87
+
88
+ # Surface RAG availability so the agent can decide whether
89
+ # semantic search is wired up before issuing cos_doc_search.
90
+ embeddings_available = False
91
+ active_model = "unknown"
92
+ try:
93
+ from embeddings import active_model_name, is_available
94
+
95
+ embeddings_available = is_available()
96
+ active_model = active_model_name()
97
+ except ImportError as exc:
98
+ logger.debug("Embeddings module unavailable for health check: %s", exc)
99
+
100
+ stats["rag"] = {
101
+ "embeddings_available": embeddings_available,
102
+ "embedding_model": active_model,
103
+ "embeddings_count": stats["tables"].get("embeddings") or 0,
104
+ "document_chunks_count": stats["tables"].get("document_chunks") or 0,
105
+ }
106
+
107
+ # Task store status — lets the agent detect whether
108
+ # `cos_task_*` queries will return data before making the call.
109
+ stats["task_store"] = {
110
+ "tasks_count": stats["tables"].get("tasks") or 0,
111
+ }
112
+
113
+ # Background indexer status — surfaced even when the loop
114
+ # is disabled so `cos doctor` can warn about misconfigured state.
115
+ try:
116
+ from background import get_indexer, is_enabled
117
+
118
+ stats["background_indexer"] = (
119
+ get_indexer().status()
120
+ if is_enabled()
121
+ else {
122
+ "enabled": False,
123
+ "running": False,
124
+ "reason": "COS_BACKGROUND_INDEX not set",
125
+ }
126
+ )
127
+ except ImportError as exc: # pragma: no cover — defensive
128
+ logger.debug("background module unavailable: %s", exc)
129
+ stats["background_indexer"] = {
130
+ "enabled": False,
131
+ "running": False,
132
+ "reason": f"import_error: {exc}",
133
+ }
134
+
135
+ # Constitution slice durability (TASK-497): the values layer is surfaced at
136
+ # every SessionStart directly from docs/governance/constitution.md — not from
137
+ # decaying agent memory — so it is non-decaying by construction. A missing file
138
+ # or absent SLICE markers would silently drop the slice; assert it here so
139
+ # `cos doctor` / health flags the regression like a dangling symlink. Fail-open.
140
+ try:
141
+ const_path = project_root() / "docs" / "governance" / "constitution.md"
142
+ present = const_path.is_file()
143
+ slice_markers_ok = False
144
+ if present:
145
+ const_text = const_path.read_text(encoding="utf-8", errors="ignore")
146
+ start = const_text.find("<!-- SLICE:START -->")
147
+ end = const_text.find("<!-- SLICE:END -->")
148
+ # Require non-empty CONTENT between the markers, not just their
149
+ # presence: an empty slice silently delivers no values while a
150
+ # markers-only check would report healthy (TASK-510).
151
+ slice_markers_ok = (
152
+ start != -1
153
+ and end > start
154
+ and const_text[start + len("<!-- SLICE:START -->") : end].strip() != ""
155
+ )
156
+ stats["constitution"] = {
157
+ "present": present,
158
+ "slice_markers_ok": slice_markers_ok,
159
+ "non_decaying": True,
160
+ "repair": None
161
+ if (present and slice_markers_ok)
162
+ else "restore docs/governance/constitution.md with a non-empty <!-- SLICE:START/END --> block so SessionStart can surface the values slice (TASK-491)",
163
+ }
164
+ except Exception as exc: # pragma: no cover — defensive, never fail the health call
165
+ logger.debug("constitution health check failed: %s", exc)
166
+ stats["constitution"] = {
167
+ "present": False,
168
+ "slice_markers_ok": False,
169
+ "non_decaying": True,
170
+ "repair": f"check_error: {exc}",
171
+ }
172
+
173
+ return ok(stats, meta={"layer": "health"})
174
+
175
+
176
+ # ---------------------------------------------------------------------------
177
+ # Import tool modules
178
+ # ---------------------------------------------------------------------------
179
+ from graph import query_related
180
+ from tools.docs import doc_search, list_doc_headers, parse_doc_header
181
+ from tools.learning import (
182
+ learn_extract,
183
+ learn_narrative,
184
+ learn_suggest,
185
+ learn_validate,
186
+ )
187
+ from tools.logs import log_query
188
+ from tools.memory import memory_details, memory_promote, memory_search, memory_timeline
189
+ from tools.metrics import metric_query, metric_record, metric_trend
190
+ from tools.retrieve import (
191
+ cite_retrievals,
192
+ learn_from_retrievals,
193
+ log_retrieval,
194
+ log_router_decision,
195
+ )
196
+ from tools.routing import failure_pattern_query, route_model_bandit, route_skill
197
+ from tools.tasks import task_by_filter, task_dependencies, task_dependents, task_search
198
+ from tools.trajectory import trajectory_read, trajectory_snapshot
199
+
200
+ # ---------------------------------------------------------------------------
201
+ # Agent-session resolver — fix for AGENT STREAM "H" label
202
+ # ---------------------------------------------------------------------------
203
+
204
+
205
+ def _detect_agent_session_default() -> str | None:
206
+ """Best-effort fallback for MCP tools that accept `agent_session`."""
207
+ import os as _os
208
+ from pathlib import Path as _P
209
+
210
+ explicit = (_os.environ.get("COS_AGENT_SESSION_ID") or "").strip()
211
+ if explicit:
212
+ return explicit
213
+
214
+ def _first_line(p: "_P") -> str:
215
+ try:
216
+ return p.read_text(encoding="utf-8", errors="ignore").strip() if p.is_file() else ""
217
+ except OSError:
218
+ return ""
219
+
220
+ # Priority 0 — the calling panel's own session-id, when a panel dir is
221
+ # in the environment (hook-driven CLI calls). Most accurate signal.
222
+ panel_dir_env = _os.environ.get("COS_PANEL_DIR")
223
+ if panel_dir_env:
224
+ sid = _first_line(_P(panel_dir_env) / "session-id")
225
+ if sid:
226
+ return sid
227
+
228
+ # Priority 1 — the agent-level ".active-session" pointer that
229
+ # session-context.sh refreshes every prompt. The long-lived MCP server
230
+ # has no $COS_PANEL_DIR, so this is the freshest signal it can read;
231
+ # the flat "session-id" file is a stale fossil kept only as a last
232
+ # resort (see docs/engineering/state-files.md).
233
+ agent_dir_env = _os.environ.get("COS_AGENT_DIR")
234
+ if agent_dir_env:
235
+ for _fname in (".active-session", "session-id"):
236
+ sid = _first_line(_P(agent_dir_env) / _fname)
237
+ if sid:
238
+ return sid
239
+
240
+ # Priority 2 — vendor env markers. Data-driven from
241
+ # adapters/<id>/adapter.yaml::runtime_env_markers (rule #11 — no
242
+ # hardcoded vendor lists in core).
243
+ agent: str | None = None
244
+ if _os.environ.get("COS_AGENT"):
245
+ agent = _os.environ["COS_AGENT"].strip().lower() or None
246
+ else:
247
+ try:
248
+ from board_os._agent_runtime import detect_agent as _detect_agent
249
+
250
+ detected = _detect_agent(None)
251
+ # detect_agent returns "agent" or "human" when nothing matches;
252
+ # only treat real adapter ids as a positive identification.
253
+ if detected and detected not in ("human", "agent"):
254
+ agent = detected
255
+ except Exception:
256
+ agent = None
257
+ # Fallback heuristic — CLAUDE_PROJECT_DIR is a weak signal, so it
258
+ # only fires when no stronger signal matched.
259
+ if agent is None and _os.environ.get("CLAUDE_PROJECT_DIR"):
260
+ agent = "claude"
261
+
262
+ if agent is None:
263
+ return None
264
+
265
+ state_dir = _os.environ.get("COS_STATE_DIR", ".coding-os")
266
+ sid_path = _P(state_dir) / agent / "session-id"
267
+ try:
268
+ if sid_path.is_file():
269
+ raw = sid_path.read_text(encoding="utf-8", errors="ignore").strip()
270
+ if raw:
271
+ return raw
272
+ except OSError:
273
+ pass
274
+
275
+ # Last resort — synthesize a per-process id so the column at least
276
+ # carries the agent prefix instead of NULL. The hub's
277
+ # `agentForSession()` substring-matches on "claude" / "codex",
278
+ # so this is enough to render the correct badge.
279
+ return f"ses-{agent}-mcp-{_os.getpid()}"
280
+
281
+
282
+ # ---------------------------------------------------------------------------
283
+ # Metrics tools
284
+ # ---------------------------------------------------------------------------
285
+ @mcp.tool(
286
+ name="cos_metric_record",
287
+ annotations={
288
+ "title": "Record Agent Metric",
289
+ "readOnlyHint": False,
290
+ "destructiveHint": False,
291
+ "idempotentHint": False,
292
+ "openWorldHint": False,
293
+ },
294
+ )
295
+ @safe_tool
296
+ def cos_metric_record(
297
+ agent_type: str,
298
+ outcome: str,
299
+ task_id: str = "",
300
+ model: str = "",
301
+ duration_ms: int = 0,
302
+ domain: str = "",
303
+ complexity: str = "",
304
+ ) -> str:
305
+ """Record a single agent performance metric after task completion.
306
+
307
+ Args:
308
+ agent_type: Type of agent (e.g. "general", "planner", "code-reviewer").
309
+ outcome: Result — one of: success, rework, partial, blocked.
310
+ task_id: Task identifier (e.g. "TASK-143"). Optional.
311
+ model: Model used (e.g. "sonnet", "opus"). Optional.
312
+ duration_ms: Duration in milliseconds. Optional.
313
+ domain: Task domain (e.g. "BACKEND", "FRONTEND", "INFRA"). Optional.
314
+ complexity: Cynefin classification (e.g. "CLEAR", "COMPLICATED"). Optional.
315
+
316
+ Returns:
317
+ str: JSON with inserted row id and status.
318
+ """
319
+ result = metric_record(
320
+ _db_conn,
321
+ task_id=task_id or None,
322
+ agent_type=agent_type,
323
+ model=model or None,
324
+ duration_ms=duration_ms or None,
325
+ outcome=outcome,
326
+ domain=domain or None,
327
+ complexity=complexity or None,
328
+ )
329
+ return ok(result, meta={"layer": "metrics"})
330
+
331
+
332
+ @mcp.tool(
333
+ name="cos_metric_query",
334
+ annotations={
335
+ "title": "Query Agent Metrics",
336
+ "readOnlyHint": True,
337
+ "destructiveHint": False,
338
+ "idempotentHint": True,
339
+ "openWorldHint": False,
340
+ },
341
+ )
342
+ @safe_tool
343
+ def cos_metric_query(
344
+ domain: str = "",
345
+ model: str = "",
346
+ outcome: str = "",
347
+ agent_type: str = "",
348
+ date_from: str = "",
349
+ date_to: str = "",
350
+ limit: int = 20,
351
+ ) -> str:
352
+ """Query agent metrics with optional filters.
353
+
354
+ Args:
355
+ domain: Filter by domain (e.g. "BACKEND"). Optional.
356
+ model: Filter by model (e.g. "sonnet"). Optional.
357
+ outcome: Filter by outcome (e.g. "rework"). Optional.
358
+ agent_type: Filter by agent type. Optional.
359
+ date_from: Start date (ISO format, e.g. "2026-03-01"). Optional.
360
+ date_to: End date (ISO format, e.g. "2026-03-25"). Optional.
361
+ limit: Max rows (1-100, default 20).
362
+
363
+ Returns:
364
+ str: JSON with total count and matching rows.
365
+ """
366
+ result = metric_query(
367
+ _db_conn,
368
+ domain=domain or None,
369
+ model=model or None,
370
+ outcome=outcome or None,
371
+ agent_type=agent_type or None,
372
+ date_from=date_from or None,
373
+ date_to=date_to or None,
374
+ limit=limit,
375
+ )
376
+ return ok(
377
+ result,
378
+ meta={
379
+ "layer": "metrics",
380
+ "filters_applied": {
381
+ "domain": domain or None,
382
+ "model": model or None,
383
+ "outcome": outcome or None,
384
+ "agent_type": agent_type or None,
385
+ },
386
+ },
387
+ )
388
+
389
+
390
+ @mcp.tool(
391
+ name="cos_metric_trend",
392
+ annotations={
393
+ "title": "Agent Metric Trends",
394
+ "readOnlyHint": True,
395
+ "destructiveHint": False,
396
+ "idempotentHint": True,
397
+ "openWorldHint": False,
398
+ },
399
+ )
400
+ @safe_tool
401
+ def cos_metric_trend(
402
+ metric: str = "success_rate",
403
+ window_days: int = 30,
404
+ group_by: str = "domain",
405
+ ) -> str:
406
+ """Get aggregated trend data for agent metrics.
407
+
408
+ Args:
409
+ metric: One of: success_rate, rework_rate, count.
410
+ window_days: Lookback window in days (1-365, default 30).
411
+ group_by: Grouping dimension: domain, model, agent_type, complexity.
412
+
413
+ Returns:
414
+ str: JSON with trends array containing period, counts, and rate.
415
+ """
416
+ result = metric_trend(
417
+ _db_conn,
418
+ metric=metric,
419
+ window_days=window_days,
420
+ group_by=group_by,
421
+ )
422
+ return ok(result, meta={"layer": "metrics"})
423
+
424
+
425
+ @mcp.tool(
426
+ name="cos_log_query",
427
+ annotations={
428
+ "title": "Query Durable Error / Log Store",
429
+ "readOnlyHint": True,
430
+ "destructiveHint": False,
431
+ "idempotentHint": True,
432
+ "openWorldHint": False,
433
+ },
434
+ )
435
+ @safe_tool
436
+ def cos_log_query(
437
+ level: str = "",
438
+ scope: str = "",
439
+ since: str = "",
440
+ search: str = "",
441
+ session_id: str = "",
442
+ trace_id: str = "",
443
+ fingerprint: str = "",
444
+ limit: int = 50,
445
+ ) -> str:
446
+ """Query the durable log_events store (WARN+), most-recent first — the agent's "what is broken now"."""
447
+ result = log_query(
448
+ _db_conn,
449
+ level=level or None,
450
+ scope=scope or None,
451
+ since=since or None,
452
+ search=search or None,
453
+ session_id=session_id or None,
454
+ trace_id=trace_id or None,
455
+ fingerprint=fingerprint or None,
456
+ limit=limit,
457
+ )
458
+ return ok(result, meta={"layer": "logs", "source": "cos_log_query"})
459
+
460
+
461
+ # ---------------------------------------------------------------------------
462
+ # Memory tools
463
+ # ---------------------------------------------------------------------------
464
+ @mcp.tool(
465
+ name="cos_observation_record",
466
+ annotations={
467
+ "title": "Record Observation (manual capture)",
468
+ "readOnlyHint": False,
469
+ "destructiveHint": False,
470
+ "idempotentHint": True,
471
+ "openWorldHint": False,
472
+ },
473
+ )
474
+ @safe_tool
475
+ def cos_observation_record(
476
+ file_path: str,
477
+ tool_name: str = "Edit",
478
+ ) -> str:
479
+ """Record an observation explicitly."""
480
+ from capture import capture_observation
481
+
482
+ tool_name = (tool_name or "Edit").strip()
483
+ if tool_name not in {"Write", "Edit", "MultiEdit"}:
484
+ return fail("validation", f"tool_name must be Write|Edit|MultiEdit, got {tool_name!r}")
485
+ if not file_path:
486
+ return fail("validation", "file_path is required")
487
+ payload = {"tool_name": tool_name, "tool_input": {"file_path": file_path}}
488
+ result = capture_observation(payload)
489
+ return ok(result, meta={"layer": "memory", "source": "cos_observation_record"})
490
+
491
+
492
+ @mcp.tool(
493
+ name="cos_search",
494
+ annotations={
495
+ "title": "Search Thinking OS Memory",
496
+ "readOnlyHint": False, # writes retrieval telemetry only — raw search does NOT bump access_count/confidence
497
+ "destructiveHint": False,
498
+ "idempotentHint": False,
499
+ "openWorldHint": False,
500
+ },
501
+ )
502
+ @safe_tool(name="cos_search")
503
+ def thinking_os_search(
504
+ query: str,
505
+ limit: int = 5,
506
+ memory_type: str = "",
507
+ min_confidence: float = 0.3,
508
+ since_days: int = 0,
509
+ ) -> str:
510
+ """Search observations and learned patterns with 5-signal ranking.
511
+
512
+ Use during Orient step to find relevant past experience. Read-only over
513
+ memory rows (writes retrieval telemetry only; reinforcement happens on
514
+ cos_details, not here — TASK-109).
515
+
516
+ Stage-1 metadata pre-filter:
517
+ - `min_confidence` drops decayed/low-trust patterns BEFORE ranking.
518
+ Stale low-signal patterns can otherwise crowd out fresh hits.
519
+ Default 0.3 skips decayed/unvalidated noise (fresh patterns start at
520
+ 0.5, so they still pass); pass 0.0 to include everything.
521
+ - `since_days` caps row age. 0 = no cap (default) — age is opt-in so a
522
+ valuable old decision is never silently hidden from default recall.
523
+
524
+ Args:
525
+ query: Search text (e.g. "backend rework", "django migration").
526
+ limit: Max results (1-20, default 5).
527
+ memory_type: Filter by type (pattern/workflow/error/decision/discovery). Optional.
528
+ min_confidence: Drop learned_patterns with confidence below this
529
+ value (0.0-1.0). Default 0.3 (skips decayed noise). 0.0 = no filter.
530
+ since_days: Drop rows older than now-`since_days`. 0 = no cap.
531
+ Common: 90 (one quarter) for "recent" queries.
532
+
533
+ Returns:
534
+ str: JSON with results list [{id, title, confidence, impact_score, memory_type, source_table}].
535
+ """
536
+ from database import has_fts5_table
537
+
538
+ result = memory_search(
539
+ _db_conn,
540
+ query=query,
541
+ limit=limit,
542
+ memory_type=memory_type or None,
543
+ use_fts5=has_fts5_table(_db_conn),
544
+ min_confidence=float(min_confidence),
545
+ since_days=int(since_days) if since_days and since_days > 0 else None,
546
+ )
547
+ # Log each returned row for the outcome-feedback loop.
548
+ rows = (result.get("results") or []) if isinstance(result, dict) else []
549
+ rids = log_retrieval(_db_conn, layer="memory", query=query, rows=rows)
550
+ if isinstance(result, dict):
551
+ result["retrieval_ids"] = rids
552
+ # Router-level telemetry.
553
+ log_router_decision(_db_conn, query=query, chosen_layer="memory", bytes_returned=len(str(rows)))
554
+ # A real Orient memory query — record the marker enforce-memory-check reads,
555
+ # so the honest path is automatic and the marker means an actual search ran.
556
+ _record_memory_check_safe(query)
557
+ return ok(
558
+ result,
559
+ meta={
560
+ "layer": "memory",
561
+ "query": query,
562
+ "source": result.get("source") if isinstance(result, dict) else None,
563
+ },
564
+ )
565
+
566
+
567
+ @mcp.tool(
568
+ name="cos_timeline",
569
+ annotations={
570
+ "title": "Thinking OS Timeline",
571
+ "readOnlyHint": True,
572
+ "destructiveHint": False,
573
+ "idempotentHint": True,
574
+ "openWorldHint": False,
575
+ },
576
+ )
577
+ @safe_tool(name="cos_timeline")
578
+ def thinking_os_timeline(
579
+ days: int = 30,
580
+ domain: str = "",
581
+ limit: int = 20,
582
+ ) -> str:
583
+ """Get recent task outcomes and observations timeline.
584
+
585
+ Args:
586
+ days: Lookback window (1-365, default 30).
587
+ domain: Filter by domain (e.g. "BACKEND"). Optional.
588
+ limit: Max entries (1-50, default 20).
589
+
590
+ Returns:
591
+ str: JSON with timeline entries [{id, title, date, outcome, type}].
592
+ """
593
+ result = memory_timeline(
594
+ _db_conn,
595
+ days=days,
596
+ domain=domain or None,
597
+ limit=limit,
598
+ )
599
+ return ok(
600
+ result,
601
+ meta={"layer": "memory", "filters_applied": {"domain": domain or None, "days": days}},
602
+ )
603
+
604
+
605
+ @mcp.tool(
606
+ name="cos_details",
607
+ annotations={
608
+ "title": "Thinking OS Details",
609
+ "readOnlyHint": False, # updates access_count
610
+ "destructiveHint": False,
611
+ "idempotentHint": False,
612
+ "openWorldHint": False,
613
+ },
614
+ )
615
+ @safe_tool(name="cos_details")
616
+ def thinking_os_details(
617
+ pattern_id: int,
618
+ source: str = "learned_patterns",
619
+ ) -> str:
620
+ """Get full details of a pattern, observation, or task outcome.
621
+
622
+ Args:
623
+ pattern_id: Row ID (or task_id string for task_outcomes).
624
+ source: Table name — observations, learned_patterns, or task_outcomes.
625
+
626
+ Returns:
627
+ str: JSON with full record.
628
+ """
629
+ result = memory_details(
630
+ _db_conn,
631
+ pattern_id=pattern_id,
632
+ source=source,
633
+ )
634
+ return ok(result, meta={"layer": "memory"})
635
+
636
+
637
+ @mcp.tool(
638
+ name="cos_promote",
639
+ annotations={
640
+ "title": "Promote Pattern to Rule",
641
+ "readOnlyHint": False,
642
+ "destructiveHint": False,
643
+ "idempotentHint": True,
644
+ "openWorldHint": False,
645
+ },
646
+ )
647
+ @safe_tool
648
+ def thinking_os_promote_tool(
649
+ pattern_id: int,
650
+ target: str = "feedback",
651
+ ) -> str:
652
+ """Promote a validated pattern to a rule or feedback memory file.
653
+
654
+ Requires confidence >= 0.3. Creates file content but does NOT write to disk
655
+ (caller writes the returned content to the appropriate location).
656
+
657
+ Args:
658
+ pattern_id: ID in learned_patterns table.
659
+ target: Output type — "feedback" or "rule".
660
+
661
+ Returns:
662
+ str: JSON with status, filename, and file content to write.
663
+ """
664
+ result = memory_promote(
665
+ _db_conn,
666
+ pattern_id=pattern_id,
667
+ target=target,
668
+ memory_dir="", # caller handles file writing
669
+ )
670
+ return ok(result, meta={"layer": "memory"})
671
+
672
+
673
+ # ---------------------------------------------------------------------------
674
+ # Learning tools
675
+ # ---------------------------------------------------------------------------
676
+ def _panel_or_agent_dir() -> str | None:
677
+ # The per-session state dir a marker belongs in, most-specific first:
678
+ # COS_PANEL_DIR (the reminder + session-context reset both target it), then
679
+ # COS_AGENT_DIR, then <state>/<agent> (COS_AGENT env or the .agent marker).
680
+ import os as _os
681
+ from pathlib import Path as _P
682
+
683
+ target_dir = _os.environ.get("COS_PANEL_DIR") or _os.environ.get("COS_AGENT_DIR")
684
+ if target_dir:
685
+ return target_dir
686
+ state_dir = _P(_os.environ.get("COS_STATE_DIR", ".coding-os"))
687
+ agent = _os.environ.get("COS_AGENT", "")
688
+ if not agent:
689
+ marker = state_dir / ".agent"
690
+ if marker.exists():
691
+ agent = marker.read_text(encoding="utf-8").strip()
692
+ return str(state_dir / agent) if agent else None
693
+
694
+
695
+ def _persist_learn_suggestions_safe(result: dict) -> None:
696
+ """Append surfaced pattern ids to the panel-dir .learn-suggestions."""
697
+ try:
698
+ from pathlib import Path as _P
699
+
700
+ # Panel dir first: the same file auto_compose.py writes, the task-done
701
+ # reminder reads, and session-context.sh resets. The old COS_AGENT_DIR
702
+ # target was a file nothing read and nothing pruned.
703
+ target_dir = _panel_or_agent_dir()
704
+ if not target_dir:
705
+ return
706
+ suggestions = (result or {}).get("suggestions") or []
707
+ if not suggestions:
708
+ return
709
+ target = _P(target_dir) / ".learn-suggestions"
710
+ target.parent.mkdir(parents=True, exist_ok=True)
711
+ lines: list[str] = []
712
+ for s in suggestions:
713
+ if not isinstance(s, dict):
714
+ continue
715
+ pid = s.get("id")
716
+ txt = (s.get("pattern") or "").replace("\t", " ").replace("\n", " ")
717
+ if pid is None:
718
+ continue
719
+ lines.append(f"{pid}\t{txt}")
720
+ if lines:
721
+ with target.open("a", encoding="utf-8") as f:
722
+ f.write("\n".join(lines) + "\n")
723
+ except Exception as exc:
724
+ logger.debug("_persist_learn_suggestions_safe swallowed: %s", exc)
725
+
726
+
727
+ def _record_memory_check_safe(query: str) -> None:
728
+ """Mark the Orient memory-check satisfied by a REAL cos_search, so
729
+ enforce-memory-check reflects an actual query, not a self-attested claim."""
730
+ try:
731
+ from pathlib import Path as _P
732
+
733
+ target_dir = _panel_or_agent_dir()
734
+ if not target_dir:
735
+ return
736
+ marker = _P(target_dir) / ".memory-check"
737
+ marker.parent.mkdir(parents=True, exist_ok=True)
738
+ marker.write_text(f"cos_search:{(query or '')[:120]}\n", encoding="utf-8")
739
+ except Exception as exc:
740
+ logger.debug("_record_memory_check_safe swallowed: %s", exc)
741
+
742
+
743
+ @mcp.tool(
744
+ name="cos_learn_extract",
745
+ annotations={
746
+ "title": "Extract Learned Patterns",
747
+ "readOnlyHint": False,
748
+ "destructiveHint": False,
749
+ "idempotentHint": True,
750
+ "openWorldHint": False,
751
+ },
752
+ )
753
+ @safe_tool
754
+ def cos_learn_extract(min_occurrences: int = 3) -> str:
755
+ """Scan task outcomes to discover recurring patterns.
756
+
757
+ Detects domain_rework, skill_correlation, and complexity_mismatch patterns.
758
+ Inserts new patterns into learned_patterns with calculated confidence.
759
+
760
+ Args:
761
+ min_occurrences: Minimum occurrences to consider a pattern (default 3).
762
+
763
+ Returns:
764
+ str: JSON with extracted patterns list and analysis stats.
765
+ """
766
+ result = learn_extract(_db_conn, min_occurrences=min_occurrences)
767
+ return ok(result, meta={"layer": "learning"})
768
+
769
+
770
+ @mcp.tool(
771
+ name="cos_learn_suggest",
772
+ annotations={
773
+ "title": "Suggest Learned Patterns",
774
+ "readOnlyHint": True,
775
+ "destructiveHint": False,
776
+ "idempotentHint": True,
777
+ "openWorldHint": False,
778
+ },
779
+ )
780
+ @safe_tool
781
+ def cos_learn_suggest(
782
+ domain: str = "",
783
+ complexity: str = "",
784
+ task_type: str = "",
785
+ limit: int = 5,
786
+ ) -> str:
787
+ """Return relevant patterns for the current task context.
788
+
789
+ Includes spaced repetition: fading patterns (0.2-0.4 confidence) that
790
+ were once validated get priority for re-validation.
791
+
792
+ Args:
793
+ domain: Task domain (e.g. "BACKEND"). Optional.
794
+ complexity: Cynefin classification. Optional.
795
+ task_type: Type of task (e.g. "feat"). Optional.
796
+ limit: Max suggestions (1-20, default 5).
797
+
798
+ Returns:
799
+ str: JSON with suggestions list [{id, pattern, confidence, reason}].
800
+ """
801
+ result = learn_suggest(
802
+ _db_conn,
803
+ domain=domain or None,
804
+ complexity=complexity or None,
805
+ task_type=task_type or None,
806
+ limit=limit,
807
+ )
808
+ # Persist the suggestion set so remind-learn-validate.sh
809
+ # can prompt the agent to close the loop after task-done. One line
810
+ # per pattern, format "id<TAB>text" — the hook prints a slice.
811
+ _persist_learn_suggestions_safe(result)
812
+ return ok(
813
+ result,
814
+ meta={
815
+ "layer": "learning",
816
+ "filters_applied": {
817
+ "domain": domain or None,
818
+ "complexity": complexity or None,
819
+ "task_type": task_type or None,
820
+ },
821
+ },
822
+ )
823
+
824
+
825
+ @mcp.tool(
826
+ name="cos_learn_validate",
827
+ annotations={
828
+ "title": "Validate Learned Pattern",
829
+ "readOnlyHint": False,
830
+ "destructiveHint": False,
831
+ "idempotentHint": False,
832
+ "openWorldHint": False,
833
+ },
834
+ )
835
+ @safe_tool
836
+ def cos_learn_validate(pattern_id: int, was_helpful: bool = True) -> str:
837
+ """Record whether a suggested pattern was helpful.
838
+
839
+ Updates confidence using brain-inspired formulas:
840
+ - Helpful: LTP with diminishing returns + temporal proximity bonus
841
+ - Not helpful: LTD proportional penalty
842
+
843
+ Args:
844
+ pattern_id: ID in learned_patterns table.
845
+ was_helpful: Whether the pattern was useful (default True).
846
+
847
+ Returns:
848
+ str: JSON with old/new confidence and validation status.
849
+ """
850
+ result = learn_validate(_db_conn, pattern_id=pattern_id, was_helpful=was_helpful)
851
+ return ok(result, meta={"layer": "learning"})
852
+
853
+
854
+ @mcp.tool(
855
+ name="cos_learn_narrative",
856
+ annotations={
857
+ "title": "Record Breakthrough Narrative",
858
+ "readOnlyHint": False,
859
+ "destructiveHint": False,
860
+ "idempotentHint": False,
861
+ "openWorldHint": False,
862
+ },
863
+ )
864
+ @safe_tool
865
+ def cos_learn_narrative(
866
+ task_id: str,
867
+ what_failed: str = "",
868
+ what_worked: str = "",
869
+ key_insight: str = "",
870
+ ) -> str:
871
+ """Record what was learned from a difficult task (breakthrough narrative).
872
+
873
+ Call this after a rework→success breakthrough to capture:
874
+ - What approaches failed and why
875
+ - What finally worked
876
+ - The reusable key insight
877
+
878
+ Creates a high-impact learned pattern for future suggestions.
879
+
880
+ Args:
881
+ task_id: Task identifier (e.g. "TASK-100").
882
+ what_failed: Approaches that didn't work.
883
+ what_worked: The solution that resolved the issue.
884
+ key_insight: Reusable lesson learned (required).
885
+
886
+ Returns:
887
+ str: JSON with status, history_id, pattern_id.
888
+ """
889
+ result = learn_narrative(
890
+ _db_conn,
891
+ task_id=task_id,
892
+ what_failed=what_failed,
893
+ what_worked=what_worked,
894
+ key_insight=key_insight,
895
+ )
896
+ return ok(result, meta={"layer": "learning"})
897
+
898
+
899
+ # ---------------------------------------------------------------------------
900
+ # Graph tools (v4 brain features)
901
+ # ---------------------------------------------------------------------------
902
+ # W7.10 / R4-14: legacy cos_graph stub removed entirely. Use
903
+ # cos_graph_resolve(q) → cos_graph_context(uid) / cos_graph_impact(uid)
904
+ # / cos_graph_references(uid) instead.
905
+
906
+
907
+ # ---------------------------------------------------------------------------
908
+ # Routing tools
909
+ # ---------------------------------------------------------------------------
910
+ @mcp.tool(
911
+ name="cos_route_model",
912
+ annotations={
913
+ "title": "Route Model Recommendation",
914
+ "readOnlyHint": True,
915
+ "destructiveHint": False,
916
+ "idempotentHint": True,
917
+ "openWorldHint": False,
918
+ },
919
+ )
920
+ @safe_tool
921
+ def cos_route_model(
922
+ complexity: str,
923
+ dimensions: int = 1,
924
+ domain: str = "",
925
+ ) -> str:
926
+ """Recommend optimal model based on historical outcome data.
927
+
928
+ Cold start (<10 outcomes): returns static default from performance.md.
929
+ Warm: queries success rates per model for the given complexity+domain.
930
+
931
+ Args:
932
+ complexity: Cynefin classification (CLEAR/COMPLICATED/COMPLEX/CHAOTIC).
933
+ dimensions: Number of problem dimensions (default 1).
934
+ domain: Task domain (e.g. "BACKEND"). Optional.
935
+
936
+ Returns:
937
+ str: JSON with recommended_model, confidence, reason, fallback_model.
938
+ """
939
+ result = route_model_bandit(
940
+ _db_conn,
941
+ complexity=complexity,
942
+ dimensions=dimensions,
943
+ domain=domain or None,
944
+ )
945
+ return ok(result, meta={"layer": "routing"})
946
+
947
+
948
+ @mcp.tool(
949
+ name="cos_route_skill",
950
+ annotations={
951
+ "title": "Route Skill Recommendation",
952
+ "readOnlyHint": True,
953
+ "destructiveHint": False,
954
+ "idempotentHint": True,
955
+ "openWorldHint": False,
956
+ },
957
+ )
958
+ @safe_tool
959
+ def cos_route_skill(
960
+ domain: str,
961
+ task_type: str = "",
962
+ complexity: str = "",
963
+ ) -> str:
964
+ """Recommend skills based on historical outcome data.
965
+
966
+ Cold start: returns static defaults from skill-enforcement.md.
967
+ Warm: augments with historically successful skills.
968
+
969
+ Args:
970
+ domain: Task domain (e.g. "BACKEND", "FRONTEND").
971
+ task_type: Type of task (e.g. "feat", "fix"). Optional.
972
+ complexity: Cynefin classification. Optional.
973
+
974
+ Returns:
975
+ str: JSON with skills list [{name, confidence, reason}].
976
+ """
977
+ result = route_skill(
978
+ _db_conn,
979
+ domain=domain,
980
+ task_type=task_type or None,
981
+ complexity=complexity or None,
982
+ )
983
+ return ok(result, meta={"layer": "routing"})
984
+
985
+
986
+ # ---------------------------------------------------------------------------
987
+ # Project Trajectory + Failure Archaeology + Routing Drift
988
+ # ---------------------------------------------------------------------------
989
+
990
+
991
+ @mcp.tool(
992
+ name="cos_trajectory_snapshot",
993
+ annotations={
994
+ "title": "Project Trajectory Snapshot (Write)",
995
+ "readOnlyHint": False,
996
+ "destructiveHint": False,
997
+ "idempotentHint": False,
998
+ "openWorldHint": False,
999
+ },
1000
+ )
1001
+ @safe_tool
1002
+ def cos_trajectory_snapshot(
1003
+ session_id: str,
1004
+ phase: str = "",
1005
+ current_focus: str = "",
1006
+ architectural_decisions: str = "[]",
1007
+ anti_patterns_discovered: str = "[]",
1008
+ open_questions: str = "[]",
1009
+ next_logical_step: str = "",
1010
+ confidence: float = 0.7,
1011
+ ) -> str:
1012
+ """Persist a project trajectory snapshot for the current session.
1013
+
1014
+ Records WHERE the project is heading (phase, focus, architectural decisions,
1015
+ anti-patterns discovered, open questions) so future sessions have strategic
1016
+ context beyond task history. Each call creates a new row linked to the
1017
+ previous snapshot via supersedes_id.
1018
+
1019
+ Args:
1020
+ session_id: Current session identifier.
1021
+ phase: Current development phase (e.g. "v2 hardening").
1022
+ current_focus: What the team is focused on right now.
1023
+ architectural_decisions: JSON array of {decision, rationale} objects.
1024
+ anti_patterns_discovered: JSON array of {pattern, context} objects.
1025
+ open_questions: JSON array of {question, priority} objects or plain strings.
1026
+ next_logical_step: Single-sentence description of what comes next.
1027
+ confidence: Confidence in this trajectory assessment (0.0-1.0).
1028
+
1029
+ Returns:
1030
+ JSON with {status, id, supersedes_id}.
1031
+ """
1032
+ import json as _json
1033
+
1034
+ try:
1035
+ ad = _json.loads(architectural_decisions or "[]")
1036
+ apd = _json.loads(anti_patterns_discovered or "[]")
1037
+ oq = _json.loads(open_questions or "[]")
1038
+ except _json.JSONDecodeError as exc:
1039
+ return fail("validation", f"JSON parse error in list field: {exc}")
1040
+
1041
+ result = trajectory_snapshot(
1042
+ _db_conn,
1043
+ session_id=session_id,
1044
+ phase=phase,
1045
+ current_focus=current_focus,
1046
+ architectural_decisions=ad,
1047
+ anti_patterns_discovered=apd,
1048
+ open_questions=oq,
1049
+ next_logical_step=next_logical_step,
1050
+ confidence=confidence,
1051
+ )
1052
+ return ok(result, meta={"layer": "trajectory"})
1053
+
1054
+
1055
+ @mcp.tool(
1056
+ name="cos_trajectory_read",
1057
+ annotations={
1058
+ "title": "Project Trajectory Read",
1059
+ "readOnlyHint": True,
1060
+ "destructiveHint": False,
1061
+ "idempotentHint": True,
1062
+ "openWorldHint": False,
1063
+ },
1064
+ )
1065
+ @safe_tool
1066
+ def cos_trajectory_read(limit: int = 1) -> str:
1067
+ """Return the most recent project trajectory snapshot(s).
1068
+
1069
+ Use at session start to understand WHERE the project is heading before
1070
+ looking at the task board. Returns phase, current focus, architectural
1071
+ decisions made, anti-patterns discovered, and open questions.
1072
+
1073
+ Args:
1074
+ limit: Number of recent snapshots to return (1-20, default 1).
1075
+
1076
+ Returns:
1077
+ JSON with {snapshots: [...], count: int}.
1078
+ """
1079
+ result = trajectory_read(_db_conn, limit=limit)
1080
+ return ok(result, meta={"layer": "trajectory"})
1081
+
1082
+
1083
+ @mcp.tool(
1084
+ name="cos_failure_pattern_query",
1085
+ annotations={
1086
+ "title": "Failure Pattern Query",
1087
+ "readOnlyHint": True,
1088
+ "destructiveHint": False,
1089
+ "idempotentHint": True,
1090
+ "openWorldHint": False,
1091
+ },
1092
+ )
1093
+ @safe_tool
1094
+ def cos_failure_pattern_query(
1095
+ root_cause: str = "",
1096
+ domain: str = "",
1097
+ limit: int = 10,
1098
+ ) -> str:
1099
+ """Aggregate structured failure anatomy from backtrack_events.
1100
+
1101
+ Returns which root_cause categories recur most frequently, with examples.
1102
+ Use before planning to avoid known failure modes. Requires migration v25
1103
+ (structured backtrack anatomy columns).
1104
+
1105
+ root_cause filter values: wrong_model | scope_too_large | missing_context |
1106
+ tool_failure | spec_ambiguity | env_mismatch | other
1107
+
1108
+ Args:
1109
+ root_cause: Optional filter to a specific root cause category.
1110
+ domain: Reserved for future per-domain filtering.
1111
+ limit: Max pattern groups to return (1-50, default 10).
1112
+
1113
+ Returns:
1114
+ JSON with {patterns: [{root_cause, count, examples}],
1115
+ total_structured, total_backtrack}.
1116
+ """
1117
+ result = failure_pattern_query(
1118
+ _db_conn,
1119
+ root_cause=root_cause or None,
1120
+ domain=domain or None,
1121
+ limit=limit,
1122
+ )
1123
+ return ok(result, meta={"layer": "routing"})
1124
+
1125
+
1126
+ # ---------------------------------------------------------------------------
1127
+ # Document RAG search
1128
+ # ---------------------------------------------------------------------------
1129
+ @mcp.tool(
1130
+ name="cos_doc_search",
1131
+ annotations={
1132
+ "title": "Search Project Documentation",
1133
+ "readOnlyHint": True,
1134
+ "destructiveHint": False,
1135
+ "idempotentHint": True,
1136
+ "openWorldHint": False,
1137
+ },
1138
+ )
1139
+ @safe_tool
1140
+ def cos_doc_search(
1141
+ query: str,
1142
+ source_types: str = "",
1143
+ limit: int = 5,
1144
+ mode: str = "auto",
1145
+ domain: str = "",
1146
+ layer: str = "",
1147
+ since_iso: str = "",
1148
+ include_inactive: bool = False,
1149
+ auto_context: bool = True,
1150
+ ) -> str:
1151
+ """Semantic + lexical search over project documentation chunks.
1152
+
1153
+ Stage-1 metadata pre-filter (since migration v22):
1154
+ `domain`, `layer`, `since_iso`, and `include_inactive` narrow the
1155
+ chunk universe BEFORE vector / FTS ranking. Vector search finds
1156
+ meaning; metadata enforces reality (correct era, correct domain,
1157
+ not superseded). Combine with `source_types` for cheap, indexed
1158
+ pre-filtering.
1159
+
1160
+ Args:
1161
+ query: Natural language search query (e.g. "commission rate calculation").
1162
+ source_types: Optional comma-separated filter — restrict to specific
1163
+ source types (e.g. "prd,architecture,adr"). Empty = all types.
1164
+ limit: Maximum results (1-50, default 5).
1165
+ mode: "auto" (default) | "semantic" | "lexical".
1166
+ domain: Frontmatter `domain:` filter (BACKEND, FRONTEND, OPS,
1167
+ DOCS, …). Empty = any. Indexed.
1168
+ layer: Frontmatter `layer:` filter (adr, playbook, spec, policy,
1169
+ reference, runbook, postmortem, task). Empty = any. Indexed.
1170
+ since_iso: Lower bound on frontmatter `updated:` (YYYY-MM-DD).
1171
+ Use when the agent asks about "recent" or "current" state and
1172
+ a stale older doc would be the wrong answer. Empty = any age.
1173
+ include_inactive: When False (default), hide chunks marked
1174
+ is_active=0 because the source doc was deleted or superseded.
1175
+ Set True for decision-history retrieval that must surface
1176
+ superseded specs.
1177
+ auto_context: When True (default), soft-default `domain` from the
1178
+ active task's swimlane ($COS_AGENT_DIR/.swimlane). Explicit
1179
+ `domain` argument always wins. Set False to disable.
1180
+
1181
+ Response meta carries `filter_hints` — heuristic suggestions
1182
+ extracted from the query (date phrasing, domain keywords, layer
1183
+ cues). Suggestions are NEVER auto-applied; the agent decides
1184
+ whether to re-query with them. Mental model: Filter → Search →
1185
+ Summarize. Vector finds meaning, metadata enforces correctness.
1186
+
1187
+ Returns:
1188
+ str: JSON envelope with results list and count. Each result
1189
+ carries source_path, source_type, heading_path, content,
1190
+ score, priority, mtime, chunk_index, retrieval_source.
1191
+ """
1192
+ types = [t.strip() for t in source_types.split(",") if t.strip()] or None
1193
+ mode_clean = mode if mode in ("auto", "semantic", "lexical") else "auto"
1194
+ domain_clean = domain.strip() or None
1195
+ layer_clean = layer.strip() or None
1196
+ since_clean = since_iso.strip() or None
1197
+
1198
+ results, search_meta = doc_search(
1199
+ _db_conn,
1200
+ query=query,
1201
+ source_types=types,
1202
+ limit=limit,
1203
+ mode=mode_clean,
1204
+ domain=domain_clean,
1205
+ layer=layer_clean,
1206
+ since_iso=since_clean,
1207
+ include_inactive=include_inactive,
1208
+ auto_context=auto_context,
1209
+ return_meta=True,
1210
+ )
1211
+ # Derive retrieval source from result rows for diagnostic meta.
1212
+ if results:
1213
+ sources_used = sorted(
1214
+ {r.get("retrieval_source") for r in results if r.get("retrieval_source")}
1215
+ )
1216
+ source_label = "+".join(sources_used) if sources_used else mode_clean
1217
+ else:
1218
+ source_label = "empty"
1219
+ # Outcome-feedback loop logging.
1220
+ rids = log_retrieval(_db_conn, layer="docs", query=query, rows=results)
1221
+ # Router-level telemetry.
1222
+ log_router_decision(
1223
+ _db_conn, query=query, chosen_layer="docs", bytes_returned=len(str(results))
1224
+ )
1225
+ # D7-F4: when the rag embedding extra is unavailable, retrieval
1226
+ # silently degrades to FTS-only — surface that as retrieval_mode so the
1227
+ # beginner persona is warned, not misled. An explicit lexical request keeps
1228
+ # its own mode (intentional, not a degradation).
1229
+ from embeddings import is_available as _emb_available
1230
+
1231
+ retrieval_mode = mode_clean if _emb_available() else "lexical-only"
1232
+ return ok(
1233
+ {"results": results, "count": len(results), "retrieval_ids": rids},
1234
+ meta={
1235
+ "layer": "docs",
1236
+ "query": query,
1237
+ "mode": mode_clean,
1238
+ "retrieval_mode": retrieval_mode,
1239
+ "source": source_label,
1240
+ "filters_applied": search_meta.get("applied", {}),
1241
+ "filter_hints": search_meta.get("filter_hints", {}),
1242
+ },
1243
+ )
1244
+
1245
+
1246
+ # ---------------------------------------------------------------------------
1247
+ # Doc header tools: header-only lazy load
1248
+ # ---------------------------------------------------------------------------
1249
+ @mcp.tool(
1250
+ name="cos_doc_header",
1251
+ annotations={
1252
+ "title": "Read Doc Header (frontmatter + opening block)",
1253
+ "readOnlyHint": True,
1254
+ "destructiveHint": False,
1255
+ "idempotentHint": True,
1256
+ "openWorldHint": False,
1257
+ },
1258
+ )
1259
+ @safe_tool
1260
+ def cos_doc_header(path: str) -> str:
1261
+ """Return a single doc's header without reading the body."""
1262
+ candidate = (path or "").strip()
1263
+ if not candidate:
1264
+ return fail("validation", "path is required")
1265
+ root_dir = project_root().resolve()
1266
+ target = Path(candidate)
1267
+ if not target.is_absolute():
1268
+ target = (root_dir / target).resolve()
1269
+ else:
1270
+ try:
1271
+ target = target.resolve()
1272
+ except OSError as exc:
1273
+ return fail("validation", f"cannot resolve path: {exc}")
1274
+ # Path-traversal guard. The MCP server is trusted today,
1275
+ # but a future external client must never read files outside the
1276
+ # project root via this tool.
1277
+ try:
1278
+ target.relative_to(root_dir)
1279
+ except ValueError:
1280
+ return fail(
1281
+ "permission",
1282
+ f"path escapes project root: {candidate}",
1283
+ )
1284
+ if not target.exists():
1285
+ return fail("not_found", f"no such file: {candidate}")
1286
+ header = parse_doc_header(target)
1287
+ if header is None:
1288
+ return fail("validation", f"cannot parse doc header: {candidate}")
1289
+ return ok(
1290
+ header,
1291
+ meta={"layer": "docs", "source": "filesystem", "query": candidate},
1292
+ )
1293
+
1294
+
1295
+ @mcp.tool(
1296
+ name="cos_doc_headers_by",
1297
+ annotations={
1298
+ "title": "List Doc Headers by Frontmatter Filter",
1299
+ "readOnlyHint": True,
1300
+ "destructiveHint": False,
1301
+ "idempotentHint": True,
1302
+ "openWorldHint": False,
1303
+ },
1304
+ )
1305
+ @safe_tool
1306
+ def cos_doc_headers_by(
1307
+ domain: str = "",
1308
+ layer: str = "",
1309
+ ssot: str = "",
1310
+ since_iso: str = "",
1311
+ root: str = "docs",
1312
+ limit: int = 50,
1313
+ ) -> str:
1314
+ """Bulk header-only scan filtered by frontmatter."""
1315
+ cap = max(1, min(int(limit) if limit else 50, 200))
1316
+ root_dir = project_root().resolve()
1317
+ root_path = Path(root) if root else Path("docs")
1318
+ if not root_path.is_absolute():
1319
+ root_path = (root_dir / root_path).resolve()
1320
+ else:
1321
+ try:
1322
+ root_path = root_path.resolve()
1323
+ except OSError as exc:
1324
+ return fail("validation", f"cannot resolve root: {exc}")
1325
+ # Path-traversal guard — root must stay inside project.
1326
+ try:
1327
+ root_path.relative_to(root_dir)
1328
+ except ValueError:
1329
+ return fail("permission", f"root escapes project root: {root}")
1330
+ if not root_path.exists():
1331
+ return fail("not_found", f"no such root: {root}")
1332
+ rows = list_doc_headers(
1333
+ root_path,
1334
+ domain=domain or None,
1335
+ layer=layer or None,
1336
+ ssot=ssot or None,
1337
+ since_iso=since_iso or None,
1338
+ limit=cap,
1339
+ )
1340
+ return ok(
1341
+ {"results": rows, "count": len(rows)},
1342
+ meta={
1343
+ "layer": "docs",
1344
+ "source": "filesystem",
1345
+ "filters_applied": {
1346
+ k: v
1347
+ for k, v in {
1348
+ "domain": domain,
1349
+ "layer": layer,
1350
+ "ssot": ssot,
1351
+ "since_iso": since_iso,
1352
+ "root": str(root_path),
1353
+ }.items()
1354
+ if v
1355
+ },
1356
+ },
1357
+ )
1358
+
1359
+
1360
+ # ---------------------------------------------------------------------------
1361
+ # Task store tools
1362
+ # ---------------------------------------------------------------------------
1363
+ @mcp.tool(
1364
+ name="cos_task_search",
1365
+ annotations={
1366
+ "title": "Search Tasks (Semantic + Filter)",
1367
+ "readOnlyHint": True,
1368
+ "destructiveHint": False,
1369
+ "idempotentHint": True,
1370
+ "openWorldHint": False,
1371
+ },
1372
+ )
1373
+ @safe_tool
1374
+ def cos_task_search(
1375
+ query: str,
1376
+ status: str = "",
1377
+ domain: str = "",
1378
+ limit: int = 10,
1379
+ ) -> str:
1380
+ """Semantic search over the task store with optional status/domain filters.
1381
+
1382
+ Use this when you need to find tasks related to a concept — even when
1383
+ exact keywords don't match. Falls back to LIKE on title + goal when
1384
+ embeddings are unavailable.
1385
+
1386
+ Args:
1387
+ query: Natural language query (e.g. "payment splitting multi vendor").
1388
+ status: Optional status filter — one of open/wip/done/blocked. Empty = all.
1389
+ domain: Optional domain filter (BACKEND/FRONTEND/DOCS/INFRA/...). Empty = all.
1390
+ limit: Maximum results (1-100, default 10).
1391
+
1392
+ Returns:
1393
+ JSON with results and count. Each result: task_id, title, domain,
1394
+ status, file_path, goal_text, dependencies, score.
1395
+ """
1396
+ results = task_search(
1397
+ _db_conn,
1398
+ query=query,
1399
+ status=status or None,
1400
+ domain=domain or None,
1401
+ limit=limit,
1402
+ )
1403
+ rids = log_retrieval(_db_conn, layer="tasks", query=query, rows=results)
1404
+ log_router_decision(
1405
+ _db_conn, query=query, chosen_layer="tasks", bytes_returned=len(str(results))
1406
+ )
1407
+ return ok(
1408
+ {"results": results, "count": len(results), "retrieval_ids": rids},
1409
+ meta={
1410
+ "layer": "tasks",
1411
+ "query": query,
1412
+ "filters_applied": {"status": status or None, "domain": domain or None},
1413
+ },
1414
+ )
1415
+
1416
+
1417
+ @mcp.tool(
1418
+ name="cos_task_dependencies",
1419
+ annotations={
1420
+ "title": "Task Dependencies (Upstream)",
1421
+ "readOnlyHint": True,
1422
+ "destructiveHint": False,
1423
+ "idempotentHint": True,
1424
+ "openWorldHint": False,
1425
+ },
1426
+ )
1427
+ @safe_tool
1428
+ def cos_task_dependencies(task_id: str) -> str:
1429
+ """Return the tasks that `task_id` directly depends on.
1430
+
1431
+ Use before starting a task to verify prerequisites are done. Returns
1432
+ only direct (first-level) dependencies — use repeated calls for
1433
+ transitive traversal.
1434
+
1435
+ Args:
1436
+ task_id: Task identifier (e.g. "TASK-199").
1437
+
1438
+ Returns:
1439
+ JSON with task_id, dependencies list, and count.
1440
+ """
1441
+ results = task_dependencies(_db_conn, task_id)
1442
+ return ok(
1443
+ {"task_id": task_id, "dependencies": results, "count": len(results)},
1444
+ meta={"layer": "tasks"},
1445
+ )
1446
+
1447
+
1448
+ @mcp.tool(
1449
+ name="cos_task_dependents",
1450
+ annotations={
1451
+ "title": "Task Dependents (Downstream)",
1452
+ "readOnlyHint": True,
1453
+ "destructiveHint": False,
1454
+ "idempotentHint": True,
1455
+ "openWorldHint": False,
1456
+ },
1457
+ )
1458
+ @safe_tool
1459
+ def cos_task_dependents(task_id: str) -> str:
1460
+ """Return the tasks that declare `task_id` as a dependency.
1461
+
1462
+ Use for impact analysis: "If I change TASK-195, what downstream tasks
1463
+ need to be re-verified?" Returns only direct dependents — non-transitive.
1464
+
1465
+ Args:
1466
+ task_id: Task identifier (e.g. "TASK-195").
1467
+
1468
+ Returns:
1469
+ JSON with task_id, dependents list, and count.
1470
+ """
1471
+ results = task_dependents(_db_conn, task_id)
1472
+ return ok(
1473
+ {"task_id": task_id, "dependents": results, "count": len(results)}, meta={"layer": "tasks"}
1474
+ )
1475
+
1476
+
1477
+ @mcp.tool(
1478
+ name="cos_task_by_filter",
1479
+ annotations={
1480
+ "title": "List Tasks by Filter",
1481
+ "readOnlyHint": True,
1482
+ "destructiveHint": False,
1483
+ "idempotentHint": True,
1484
+ "openWorldHint": False,
1485
+ },
1486
+ )
1487
+ @safe_tool
1488
+ def cos_task_by_filter(
1489
+ status: str = "",
1490
+ domain: str = "",
1491
+ limit: int = 20,
1492
+ ) -> str:
1493
+ """List tasks matching an optional status and/or domain filter.
1494
+
1495
+ No semantic query — pure structured filter. Use when you need "all
1496
+ open backend tasks" or "all blocked tasks" without a specific concept.
1497
+
1498
+ Args:
1499
+ status: Filter by status (open/wip/done/blocked). Empty = all.
1500
+ domain: Filter by domain (BACKEND/FRONTEND/DOCS/...). Empty = all.
1501
+ limit: Maximum results (1-100, default 20).
1502
+
1503
+ Returns:
1504
+ JSON with results list (sorted by task_id ASC) and count.
1505
+ """
1506
+ results = task_by_filter(
1507
+ _db_conn,
1508
+ status=status or None,
1509
+ domain=domain or None,
1510
+ limit=limit,
1511
+ )
1512
+ return ok(
1513
+ {"results": results, "count": len(results)},
1514
+ meta={
1515
+ "layer": "tasks",
1516
+ "filters_applied": {"status": status or None, "domain": domain or None},
1517
+ },
1518
+ )
1519
+
1520
+
1521
+ # ---------------------------------------------------------------------------
1522
+ # Board-OS MCP tools — Scrumban task board
1523
+ # ---------------------------------------------------------------------------
1524
+ # Imported from core/board_os/mcp_tools.py. Each tool here is a thin
1525
+ # @mcp.tool-decorated wrapper that injects the server's shared _db_conn.
1526
+
1527
+ try:
1528
+ # `from board_os...` requires the project root (parent of `core/`)
1529
+ # on sys.path, since `core/` is a namespace package without __init__.py.
1530
+ _PROJECT_ROOT = Path(__file__).resolve().parents[2]
1531
+ if str(_PROJECT_ROOT) not in sys.path:
1532
+ sys.path.insert(0, str(_PROJECT_ROOT))
1533
+ from board_os import mcp_tools as _board_mcp # type: ignore
1534
+
1535
+ _BOARD_OS_AVAILABLE = True
1536
+ except ImportError as _exc:
1537
+ logger.warning("board_os MCP tools unavailable: %s", _exc)
1538
+ _BOARD_OS_AVAILABLE = False
1539
+
1540
+
1541
+ if _BOARD_OS_AVAILABLE:
1542
+
1543
+ @mcp.tool(
1544
+ name="cos_task_create",
1545
+ annotations={
1546
+ "title": "Create New Scrumban Task",
1547
+ "readOnlyHint": False,
1548
+ "destructiveHint": False,
1549
+ "idempotentHint": False,
1550
+ "openWorldHint": False,
1551
+ },
1552
+ )
1553
+ def cos_task_create(
1554
+ title: str,
1555
+ swimlane: str,
1556
+ kind: str,
1557
+ priority: str = "P2",
1558
+ appetite: str = "1d",
1559
+ epic: str = "",
1560
+ labels: list[str] | None = None,
1561
+ outcome: str = "",
1562
+ acceptance: str = "",
1563
+ repro: str = "",
1564
+ read_first: list[str] | None = None,
1565
+ depends_on: list[str] | None = None,
1566
+ status: str = "icebox",
1567
+ ready: bool = False,
1568
+ agent_session: str = "",
1569
+ ) -> str:
1570
+ """Create a new Scrumban task file + sync to DB.
1571
+
1572
+ Prefer this over hand-writing YAML. Validates swimlane against
1573
+ scrumban-config.yaml and kind against the 8-value enum. Pass
1574
+ ready=True to mark the task pullable in one shot; for bug-kind
1575
+ tasks pass acceptance= (G/W/T lines) and repro= so the create
1576
+ satisfies its own DoR in one call.
1577
+ """
1578
+ resolved_session = agent_session or _detect_agent_session_default() or None
1579
+ return _board_mcp.cos_task_create(
1580
+ get_pooled_conn(),
1581
+ title=title,
1582
+ swimlane=swimlane,
1583
+ kind=kind,
1584
+ priority=priority,
1585
+ appetite=appetite,
1586
+ epic=epic or None,
1587
+ labels=labels or [],
1588
+ outcome=outcome or None,
1589
+ acceptance=acceptance or None,
1590
+ repro=repro or None,
1591
+ read_first=read_first or [],
1592
+ depends_on=depends_on or [],
1593
+ status=status,
1594
+ ready=ready,
1595
+ agent_session=resolved_session,
1596
+ )
1597
+
1598
+ @mcp.tool(
1599
+ name="cos_task_board",
1600
+ annotations={
1601
+ "title": "Scrumban Board State",
1602
+ "readOnlyHint": True,
1603
+ "destructiveHint": False,
1604
+ "idempotentHint": True,
1605
+ "openWorldHint": False,
1606
+ },
1607
+ )
1608
+ def cos_task_board(
1609
+ swimlane: str = "",
1610
+ kind: str = "",
1611
+ epic: str = "",
1612
+ status_filter: list[str] | None = None,
1613
+ include_archive: bool = False,
1614
+ limit: int = 50,
1615
+ page_size: int = 50,
1616
+ cursor: str = "",
1617
+ ) -> str:
1618
+ """Return the board state grouped by (swimlane, status) with WIP info. Complete/archive columns are keyset-paginated (pass cursor + status_filter to load more)."""
1619
+ return _board_mcp.cos_task_board(
1620
+ get_pooled_conn(),
1621
+ swimlane=swimlane or None,
1622
+ kind=kind or None,
1623
+ epic=epic or None,
1624
+ status_filter=status_filter,
1625
+ include_archive=include_archive,
1626
+ limit=limit,
1627
+ page_size=page_size,
1628
+ cursor=cursor or None,
1629
+ )
1630
+
1631
+ @mcp.tool(
1632
+ name="cos_task_show",
1633
+ annotations={
1634
+ "title": "Show Single Task (frontmatter + body)",
1635
+ "readOnlyHint": True,
1636
+ "destructiveHint": False,
1637
+ "idempotentHint": True,
1638
+ "openWorldHint": False,
1639
+ },
1640
+ )
1641
+ def cos_task_show(task_id: str, include_body: bool = True) -> str:
1642
+ """Show a single task's frontmatter fields and full markdown body — in-session alternative to raw ls/grep/Read on docs/tasks."""
1643
+ return _board_mcp.cos_task_show(
1644
+ get_pooled_conn(),
1645
+ task_id=task_id,
1646
+ include_body=include_body,
1647
+ )
1648
+
1649
+ @mcp.tool(
1650
+ name="cos_task_history",
1651
+ annotations={
1652
+ "title": "Task History (create + transitions + edits + commits)",
1653
+ "readOnlyHint": True,
1654
+ "destructiveHint": False,
1655
+ "idempotentHint": True,
1656
+ "openWorldHint": False,
1657
+ },
1658
+ )
1659
+ def cos_task_history(task_id: str, include_commits: bool = True, limit: int = 200) -> str:
1660
+ """Full actor-attributed task history — creation, status transitions, field edits, and git commits."""
1661
+ return _board_mcp.cos_task_history(
1662
+ get_pooled_conn(),
1663
+ task_id=task_id,
1664
+ include_commits=include_commits,
1665
+ limit=limit,
1666
+ )
1667
+
1668
+ @mcp.tool(
1669
+ name="cos_task_edit",
1670
+ annotations={
1671
+ "title": "Edit Task Fields / Body (actor-attributed)",
1672
+ "readOnlyHint": False,
1673
+ "destructiveHint": False,
1674
+ "idempotentHint": False,
1675
+ "openWorldHint": False,
1676
+ },
1677
+ )
1678
+ def cos_task_edit(
1679
+ task_id: str,
1680
+ title: str = "",
1681
+ priority: str = "",
1682
+ swimlane: str = "",
1683
+ appetite: str = "",
1684
+ epic: str = "",
1685
+ labels_csv: str = "",
1686
+ body: str = "",
1687
+ actor_type: str = "agent",
1688
+ actor_id: str = "",
1689
+ source: str = "mcp",
1690
+ ) -> str:
1691
+ """Edit a task's frontmatter fields and/or body; each change is recorded to the actor-attributed edit history."""
1692
+ return _board_mcp.cos_task_edit(
1693
+ get_pooled_conn(),
1694
+ task_id=task_id,
1695
+ title=title or None,
1696
+ priority=priority or None,
1697
+ swimlane=swimlane or None,
1698
+ appetite=appetite or None,
1699
+ epic=epic or None,
1700
+ labels=[s.strip() for s in labels_csv.split(",") if s.strip()] if labels_csv else None,
1701
+ body=body or None,
1702
+ actor_type=actor_type,
1703
+ actor_id=actor_id or None,
1704
+ source=source,
1705
+ )
1706
+
1707
+ @mcp.tool(
1708
+ name="cos_task_link",
1709
+ annotations={
1710
+ "title": "Link a Task to a Forge Issue/PR (external_ref)",
1711
+ "readOnlyHint": False,
1712
+ "destructiveHint": False,
1713
+ "idempotentHint": True,
1714
+ "openWorldHint": False,
1715
+ },
1716
+ )
1717
+ def cos_task_link(task_id: str, ref: str) -> str:
1718
+ """Set a task's optional external_ref (e.g. github#42) — forge auto-detected; metadata only, never the id."""
1719
+ return _board_mcp.cos_task_link(get_pooled_conn(), task_id=task_id, ref=ref)
1720
+
1721
+ @mcp.tool(
1722
+ name="cos_presence_query",
1723
+ annotations={
1724
+ "title": "Live Agent Presence (sessions + states)",
1725
+ "readOnlyHint": True,
1726
+ "destructiveHint": False,
1727
+ "idempotentHint": True,
1728
+ "openWorldHint": False,
1729
+ },
1730
+ )
1731
+ def cos_presence_query(agent: str = "") -> str:
1732
+ """Return per-agent presence state and live-session inventory.
1733
+
1734
+ Reads `.coding-os/<agent>/sessions/*.json` (the same files
1735
+ agent-presence.sh writes) and applies the SSOT rules in
1736
+ `board_os.presence`. When `agent` is empty, every adapter
1737
+ registered in adapters/<id>/adapter.yaml is reported.
1738
+
1739
+ Used by `cos daily`, CI gates, and the live-agents board UI to
1740
+ verify zombie sessions are gone after deploy.
1741
+ """
1742
+ try:
1743
+ from board_os.hub_adapter_manifest import list_agent_manifest_rows
1744
+ from board_os.presence import (
1745
+ agent_state as _agent_state_q,
1746
+ session_inventory as _session_inventory_q,
1747
+ )
1748
+ except ImportError as exc:
1749
+ return fail(
1750
+ "unavailable",
1751
+ f"board_os presence module not importable: {exc}",
1752
+ retryable=False,
1753
+ )
1754
+
1755
+ # Resolve the project root the same way the web routes do so
1756
+ # multi-project servers inspect the right .coding-os/ tree.
1757
+ try:
1758
+ from web._project_context import current_project_root # type: ignore
1759
+
1760
+ root = current_project_root()
1761
+ except Exception as exc:
1762
+ return fail(
1763
+ "unavailable",
1764
+ f"cannot resolve project root: {exc}",
1765
+ retryable=False,
1766
+ )
1767
+
1768
+ agents = (
1769
+ [agent.strip()]
1770
+ if agent.strip()
1771
+ else [str(r.get("id") or "") for r in list_agent_manifest_rows() if r.get("id")]
1772
+ )
1773
+ states: dict[str, str] = {}
1774
+ sessions: list[dict] = []
1775
+ for aid in agents:
1776
+ if not aid:
1777
+ continue
1778
+ d = root / ".coding-os" / aid / "sessions"
1779
+ states[aid] = _agent_state_q(d)
1780
+ sessions.extend(_session_inventory_q(aid, d))
1781
+ return ok(
1782
+ {
1783
+ "agent_states": states,
1784
+ "session_states": sessions,
1785
+ "session_counts": {
1786
+ aid: sum(1 for s in sessions if s["agent"] == aid) for aid in agents
1787
+ },
1788
+ "scope": "per_project",
1789
+ "root": str(root),
1790
+ }
1791
+ )
1792
+
1793
+ @mcp.tool(
1794
+ name="cos_task_move",
1795
+ annotations={
1796
+ "title": "Move Task to New Status",
1797
+ "readOnlyHint": False,
1798
+ "destructiveHint": False,
1799
+ "idempotentHint": False,
1800
+ "openWorldHint": False,
1801
+ },
1802
+ )
1803
+ def cos_task_move(
1804
+ task_id: str,
1805
+ to: str,
1806
+ reason: str = "",
1807
+ bypass_wip: bool = False,
1808
+ agent_session: str = "",
1809
+ ) -> str:
1810
+ """Transition a task through the Scrumban state machine."""
1811
+ resolved_session = agent_session or _detect_agent_session_default() or None
1812
+ return _board_mcp.cos_task_move(
1813
+ get_pooled_conn(),
1814
+ task_id=task_id,
1815
+ to=to,
1816
+ reason=reason or "mcp:cos_task_move (no reason given)",
1817
+ bypass_wip=bypass_wip,
1818
+ agent_session=resolved_session,
1819
+ )
1820
+
1821
+ @mcp.tool(
1822
+ name="cos_task_reposition",
1823
+ annotations={
1824
+ "title": "Reposition Task (status and/or swimlane)",
1825
+ "readOnlyHint": False,
1826
+ "destructiveHint": False,
1827
+ "idempotentHint": False,
1828
+ "openWorldHint": False,
1829
+ },
1830
+ )
1831
+ def cos_task_reposition(
1832
+ task_id: str,
1833
+ swimlane: str = "",
1834
+ to: str = "",
1835
+ reason: str = "",
1836
+ bypass_wip: bool = False,
1837
+ agent_session: str = "",
1838
+ ) -> str:
1839
+ """Update Scrumban status and/or swimlane (MD frontmatter + sync)."""
1840
+ resolved_session = agent_session or _detect_agent_session_default() or None
1841
+ return _board_mcp.cos_task_reposition(
1842
+ get_pooled_conn(),
1843
+ task_id=task_id,
1844
+ swimlane=swimlane or None,
1845
+ to=to or None,
1846
+ reason=reason or None,
1847
+ bypass_wip=bypass_wip,
1848
+ agent_session=resolved_session,
1849
+ )
1850
+
1851
+ @mcp.tool(
1852
+ name="cos_task_ready",
1853
+ annotations={
1854
+ "title": "Mark Task Ready (toggle pull-gate label)",
1855
+ "readOnlyHint": False,
1856
+ "destructiveHint": False,
1857
+ "idempotentHint": True,
1858
+ "openWorldHint": False,
1859
+ },
1860
+ )
1861
+ def cos_task_ready(
1862
+ task_id: str,
1863
+ ready: bool = True,
1864
+ agent_session: str = "",
1865
+ ) -> str:
1866
+ """Add or remove the 'ready' label that gates icebox→in_progress."""
1867
+ resolved_session = agent_session or _detect_agent_session_default() or None
1868
+ return _board_mcp.cos_task_ready(
1869
+ get_pooled_conn(),
1870
+ task_id=task_id,
1871
+ ready=ready,
1872
+ agent_session=resolved_session,
1873
+ )
1874
+
1875
+ @mcp.tool(
1876
+ name="cos_task_reclaim",
1877
+ annotations={
1878
+ "title": "Reclaim Zombie in_progress Tasks",
1879
+ "readOnlyHint": False,
1880
+ "destructiveHint": False,
1881
+ "idempotentHint": True,
1882
+ "openWorldHint": False,
1883
+ },
1884
+ )
1885
+ def cos_task_reclaim(
1886
+ idle_hours: int = 0,
1887
+ dry_run: bool = False,
1888
+ agent_session: str = "",
1889
+ ) -> str:
1890
+ """Reclaim zombie in_progress tasks (idle + owner session inactive) to icebox+ready."""
1891
+ resolved_session = agent_session or _detect_agent_session_default() or None
1892
+ return _board_mcp.cos_task_reclaim(
1893
+ get_pooled_conn(),
1894
+ idle_hours=idle_hours or None,
1895
+ dry_run=dry_run,
1896
+ agent_session=resolved_session,
1897
+ )
1898
+
1899
+ @mcp.tool(
1900
+ name="cos_task_reconcile",
1901
+ annotations={
1902
+ "title": "Reconcile Stranded Tasks (review-first)",
1903
+ "readOnlyHint": True,
1904
+ "destructiveHint": False,
1905
+ "idempotentHint": True,
1906
+ "openWorldHint": False,
1907
+ },
1908
+ )
1909
+ def cos_task_reconcile(include_active: bool = False) -> str:
1910
+ """Triage stranded in_progress/testing tasks with completion evidence + a review recommendation (read-only)."""
1911
+ return _board_mcp.cos_task_reconcile(get_pooled_conn(), include_active=include_active)
1912
+
1913
+ @mcp.tool(
1914
+ name="cos_task_pick",
1915
+ annotations={
1916
+ "title": "Pick Next Task to Work On",
1917
+ "readOnlyHint": True,
1918
+ "destructiveHint": False,
1919
+ "idempotentHint": True,
1920
+ "openWorldHint": False,
1921
+ },
1922
+ )
1923
+ def cos_task_pick(
1924
+ swimlane: str = "",
1925
+ priority_min: str = "P2",
1926
+ max_candidates: int = 5,
1927
+ ) -> str:
1928
+ """Return top candidate tasks to start next, ranked by priority."""
1929
+ return _board_mcp.cos_task_pick(
1930
+ get_pooled_conn(),
1931
+ swimlane=swimlane or None,
1932
+ priority_min=priority_min,
1933
+ max_candidates=max_candidates,
1934
+ )
1935
+
1936
+ @mcp.tool(
1937
+ name="cos_task_claim_next",
1938
+ annotations={
1939
+ "title": "Atomically Claim Next Runnable Task",
1940
+ "readOnlyHint": False,
1941
+ "destructiveHint": False,
1942
+ "idempotentHint": False,
1943
+ "openWorldHint": False,
1944
+ },
1945
+ )
1946
+ def cos_task_claim_next(
1947
+ swimlane: str = "",
1948
+ priority_min: str = "P2",
1949
+ agent_session: str = "",
1950
+ ) -> str:
1951
+ """Atomically select+claim the top runnable task for this session (or claimed=null)."""
1952
+ return _board_mcp.cos_task_claim_next(
1953
+ get_pooled_conn(),
1954
+ swimlane=swimlane or None,
1955
+ priority_min=priority_min,
1956
+ agent_session=agent_session or None,
1957
+ )
1958
+
1959
+ @mcp.tool(
1960
+ name="cos_task_daily",
1961
+ annotations={
1962
+ "title": "Daily Standup Summary",
1963
+ "readOnlyHint": True,
1964
+ "destructiveHint": False,
1965
+ "idempotentHint": True,
1966
+ "openWorldHint": False,
1967
+ },
1968
+ )
1969
+ def cos_task_daily(since: str = "24h", agent_session: str = "") -> str:
1970
+ """Produce the daily standup summary."""
1971
+ return _board_mcp.cos_task_daily(
1972
+ get_pooled_conn(),
1973
+ since=since,
1974
+ agent_session=agent_session or None,
1975
+ )
1976
+
1977
+ @mcp.tool(
1978
+ name="cos_task_retro",
1979
+ annotations={
1980
+ "title": "Weekly Retrospective",
1981
+ "readOnlyHint": True,
1982
+ "destructiveHint": False,
1983
+ "idempotentHint": True,
1984
+ "openWorldHint": False,
1985
+ },
1986
+ )
1987
+ def cos_task_retro(since: str = "7d") -> str:
1988
+ """Weekly retro metrics (cycle time, throughput, emergency count)."""
1989
+ return _board_mcp.cos_task_retro(get_pooled_conn(), since=since)
1990
+
1991
+ @mcp.tool(
1992
+ name="cos_task_wip_check",
1993
+ annotations={
1994
+ "title": "WIP Cap Health Check",
1995
+ "readOnlyHint": True,
1996
+ "destructiveHint": False,
1997
+ "idempotentHint": True,
1998
+ "openWorldHint": False,
1999
+ },
2000
+ )
2001
+ def cos_task_wip_check() -> str:
2002
+ """Lightweight check of current WIP counts vs. configured caps."""
2003
+ return _board_mcp.cos_task_wip_check(get_pooled_conn())
2004
+
2005
+ @mcp.tool(
2006
+ name="cos_work_log_append",
2007
+ annotations={
2008
+ "title": "Append Line to Task Work Log",
2009
+ "readOnlyHint": False,
2010
+ "destructiveHint": False,
2011
+ "idempotentHint": False,
2012
+ "openWorldHint": False,
2013
+ },
2014
+ )
2015
+ def cos_work_log_append(
2016
+ task_id: str,
2017
+ summary: str,
2018
+ agent_session: str = "",
2019
+ source: str = "manual",
2020
+ ) -> str:
2021
+ """Append one Work Log line to a task. Critical for Codex sessions."""
2022
+ resolved_session = agent_session or _detect_agent_session_default() or None
2023
+ return _board_mcp.cos_work_log_append(
2024
+ get_pooled_conn(),
2025
+ task_id=task_id,
2026
+ summary=summary,
2027
+ agent_session=resolved_session,
2028
+ source=source,
2029
+ )
2030
+
2031
+
2032
+ # ---------------------------------------------------------------------------
2033
+ # Retrieval feedback
2034
+ # ---------------------------------------------------------------------------
2035
+ @mcp.tool(
2036
+ name="cos_retrieval_cite",
2037
+ annotations={
2038
+ "title": "Cite Retrievals the Agent Used",
2039
+ "readOnlyHint": False,
2040
+ "destructiveHint": False,
2041
+ "idempotentHint": True,
2042
+ "openWorldHint": False,
2043
+ },
2044
+ )
2045
+ @safe_tool
2046
+ def cos_retrieval_cite(retrieval_ids: str) -> str:
2047
+ """Mark retrieval rows as actively cited by the agent.
2048
+
2049
+ Call this after using one or more chunks/patterns/tasks in a meaningful
2050
+ way (read them carefully, applied them). Cited retrievals get ~4× the
2051
+ weight when priority-learning runs, so the signal is only useful if it
2052
+ reflects actual use — do NOT cite passive retrievals.
2053
+
2054
+ Args:
2055
+ retrieval_ids: Comma-separated list of retrieval ids (int), returned
2056
+ as `retrieval_ids` in prior cos_search / cos_doc_search /
2057
+ cos_task_search responses. e.g. "12,17,24".
2058
+
2059
+ Returns:
2060
+ JSON with `{updated, unknown}` — updated count + list of ids that
2061
+ did not exist.
2062
+ """
2063
+ try:
2064
+ ids = [int(x) for x in retrieval_ids.split(",") if x.strip()]
2065
+ except ValueError:
2066
+ raise ValueError("retrieval_ids must be comma-separated integers")
2067
+ result = cite_retrievals(_db_conn, ids)
2068
+ return ok(result, meta={"layer": "learning"})
2069
+
2070
+
2071
+ @mcp.tool(
2072
+ name="cos_retrieval_learn",
2073
+ annotations={
2074
+ "title": "Priority Learning from Retrieval Outcomes",
2075
+ "readOnlyHint": False,
2076
+ "destructiveHint": False,
2077
+ "idempotentHint": False,
2078
+ "openWorldHint": False,
2079
+ },
2080
+ )
2081
+ @safe_tool
2082
+ def cos_retrieval_learn(lookback_days: int = 7, dry_run: bool = False) -> str:
2083
+ """Adjust document_chunks.priority based on recent retrieval outcomes.
2084
+
2085
+ Walks retrievals with a known outcome in the lookback window and:
2086
+ - chunk cited in a success task → priority += 0.02
2087
+ - chunk cited in a rework/blocked task → priority −= 0.01
2088
+ - passive retrievals ±0.005 (weaker signal)
2089
+
2090
+ Clamped to [0.1, 0.9]. Intended to run nightly via cron or after a
2091
+ batch of task-done events.
2092
+
2093
+ Args:
2094
+ lookback_days: How many days of retrievals to consider (default 7).
2095
+ dry_run: When True, compute changes without writing.
2096
+
2097
+ Returns:
2098
+ `{adjusted, gained, lost, changes[], status}` envelope.
2099
+ """
2100
+ result = learn_from_retrievals(
2101
+ _db_conn, lookback_days=int(lookback_days), dry_run=bool(dry_run)
2102
+ )
2103
+ return ok(result, meta={"layer": "learning"})
2104
+
2105
+
2106
+ # ---------------------------------------------------------------------------
2107
+ # Agent digest
2108
+ # ---------------------------------------------------------------------------
2109
+ @mcp.tool(
2110
+ name="cos_digest_regenerate",
2111
+ annotations={
2112
+ "title": "Regenerate Agent Digest",
2113
+ "readOnlyHint": False,
2114
+ "destructiveHint": False,
2115
+ "idempotentHint": True,
2116
+ "openWorldHint": False,
2117
+ },
2118
+ )
2119
+ @safe_tool
2120
+ def cos_digest_regenerate(project_root: str = "") -> str:
2121
+ """Refresh `.coding-os/digest.md` from current memory state.
2122
+
2123
+ The digest is a ≤ 2.4 KB rolling snapshot of the agent's identity:
2124
+ active beliefs, fading patterns, recent breakthroughs, preferences.
2125
+ Session-startup reads this file to give the agent a coherent
2126
+ memory anchor before any retrieval fires.
2127
+
2128
+ Args:
2129
+ project_root: Override project root. Empty (default) uses cwd.
2130
+
2131
+ Returns:
2132
+ `{path, size_chars, truncated, status}` envelope.
2133
+ """
2134
+ import os
2135
+ from pathlib import Path
2136
+
2137
+ from digest import regenerate
2138
+
2139
+ root = Path(project_root) if project_root else Path(os.environ.get("COS_PROJECT_ROOT", "."))
2140
+ result = regenerate(_db_conn, project_root=root)
2141
+ return ok(result, meta={"layer": "learning"})
2142
+
2143
+
2144
+ # ---------------------------------------------------------------------------
2145
+ # Retrieval quality / enrichment gate
2146
+ # ---------------------------------------------------------------------------
2147
+ @mcp.tool(
2148
+ name="cos_retrieval_quality",
2149
+ annotations={
2150
+ "title": "Retrieval Precision Summary",
2151
+ "readOnlyHint": True,
2152
+ "destructiveHint": False,
2153
+ "idempotentHint": True,
2154
+ "openWorldHint": False,
2155
+ },
2156
+ )
2157
+ @safe_tool
2158
+ def cos_retrieval_quality(lookback_days: int = 14, layer: str = "") -> str:
2159
+ """Report mean retrieval precision over the lookback window.
2160
+
2161
+ Precision is derived from (was_cited, outcome) pairs on the
2162
+ retrievals table, so it's honest: a retrieval that was cited and
2163
+ led to success counts as 1.0; a cited retrieval that led to rework
2164
+ counts as 0.0. Used to decide whether contextual enrichment is worth
2165
+ the LLM cost.
2166
+
2167
+ Args:
2168
+ lookback_days: Window in days (default 14).
2169
+ layer: Optional layer filter ("memory"|"docs"|"tasks").
2170
+
2171
+ Returns:
2172
+ `{mean_precision, samples, below_gate, gate, layer, status}`.
2173
+ """
2174
+ from retrieval_quality import backfill_quality_from_outcomes, precision_summary
2175
+
2176
+ # Idempotent: ensure quality rows are up to date before summarising
2177
+ backfill_quality_from_outcomes(_db_conn, lookback_days=int(lookback_days))
2178
+ result = precision_summary(
2179
+ _db_conn,
2180
+ lookback_days=int(lookback_days),
2181
+ layer=layer or None,
2182
+ )
2183
+ return ok(result, meta={"layer": "metrics"})
2184
+
2185
+
2186
+ @mcp.tool(
2187
+ name="cos_retrieval_enrichment_check",
2188
+ annotations={
2189
+ "title": "Contextual Enrichment Recommendation",
2190
+ "readOnlyHint": True,
2191
+ "destructiveHint": False,
2192
+ "idempotentHint": True,
2193
+ "openWorldHint": False,
2194
+ },
2195
+ )
2196
+ @safe_tool
2197
+ def cos_retrieval_enrichment_check(lookback_days: int = 14) -> str:
2198
+ """Recommend whether to enable contextual retrieval enrichment.
2199
+
2200
+ The underlying LLM enrichment path is intentionally a stub — this tool
2201
+ exists so the *decision* is metric-driven and auditable before anyone
2202
+ pays the Haiku bill.
2203
+
2204
+ Args:
2205
+ lookback_days: Window of retrieval quality data (default 14).
2206
+
2207
+ Returns:
2208
+ `{recommend: bool, reason, cost_warning?, summary}`.
2209
+ """
2210
+ from retrieval_quality import backfill_quality_from_outcomes, should_enable_enrichment
2211
+
2212
+ backfill_quality_from_outcomes(_db_conn, lookback_days=int(lookback_days))
2213
+ result = should_enable_enrichment(_db_conn, lookback_days=int(lookback_days))
2214
+ return ok(result, meta={"layer": "metrics"})
2215
+
2216
+
2217
+ # ---------------------------------------------------------------------------
2218
+ # 9 formula-agent supervisor tools.
2219
+ # 3 role-based routing tools (cos_analyze_task, cos_compose_chain,
2220
+ # cos_role_info).
2221
+ # 2 dispatch tools.
2222
+ # ---------------------------------------------------------------------------
2223
+ try:
2224
+ from database import DEFAULT_DB_PATH as _DEFAULT_DB_PATH
2225
+ from tools.cognition import register_all as _register_cognition_tools
2226
+
2227
+ _register_cognition_tools(mcp, str(_DEFAULT_DB_PATH))
2228
+ logger.info("Cognition tools registered")
2229
+ except Exception as _cog_exc: # pragma: no cover
2230
+ logger.warning("cognition tools unavailable: %s", _cog_exc)
2231
+
2232
+
2233
+ # ---------------------------------------------------------------------------
2234
+ # cos_graph_* MCP tools (knowledge-graph layer).
2235
+ #
2236
+ # The implementations live in `core/graph_os/tools/graph.py`; the wrappers
2237
+ # here expose them via FastMCP with MCP-friendly parameter types (comma-
2238
+ # separated strings instead of Sequence[str], etc.). Every wrapper stays
2239
+ # envelope-compliant because the underlying functions already route
2240
+ # through ok()/fail().
2241
+ # ---------------------------------------------------------------------------
2242
+ try:
2243
+ from graph_os.tools import (
2244
+ graph as _graph_tools,
2245
+ )
2246
+
2247
+ _GRAPH_TOOLS_AVAILABLE = True
2248
+ except ImportError as _graph_import_exc: # pragma: no cover — defensive
2249
+ logger.warning("graph_os tools unavailable: %s", _graph_import_exc)
2250
+ _graph_tools = None # type: ignore[assignment]
2251
+ _GRAPH_TOOLS_AVAILABLE = False
2252
+
2253
+
2254
+ def _csv(value: str) -> list[str] | None:
2255
+ """Parse a comma-separated CLI-style string into a clean list or None."""
2256
+ if not value:
2257
+ return None
2258
+ parts = [p.strip() for p in value.split(",") if p.strip()]
2259
+ return parts or None
2260
+
2261
+
2262
+ def _graph_unavailable() -> str:
2263
+ """Envelope the agent sees when graph_os tools can't be imported.
2264
+
2265
+ B20: MCP tool returns must be JSON-encoded strings. ``fail()`` from
2266
+ ``tools._shared`` already returns ``json.dumps(...)`` so this
2267
+ function always returns a ``str``. The explicit ``json.dumps`` wrapper
2268
+ below makes the contract unambiguous should the import path change.
2269
+ """
2270
+ import json as _json
2271
+
2272
+ return _json.dumps(
2273
+ {
2274
+ "ok": False,
2275
+ "error": {
2276
+ "category": "unavailable",
2277
+ "retryable": False,
2278
+ "message": "graph_os package not importable; install graph_os extra",
2279
+ },
2280
+ }
2281
+ )
2282
+
2283
+
2284
+ if _GRAPH_TOOLS_AVAILABLE:
2285
+
2286
+ @mcp.tool(
2287
+ name="cos_graph_query",
2288
+ annotations={
2289
+ "title": "Graph Symbol Lookup (known name/path/uid)",
2290
+ "readOnlyHint": True,
2291
+ "destructiveHint": False,
2292
+ "idempotentHint": True,
2293
+ "openWorldHint": False,
2294
+ },
2295
+ )
2296
+ @safe_tool
2297
+ def cos_graph_query_tool(
2298
+ q: str,
2299
+ kinds: str = "",
2300
+ limit: int = 10,
2301
+ max_hops: int = 2,
2302
+ confidence_min: float = 0.3,
2303
+ include_spine: bool = False,
2304
+ ) -> str:
2305
+ """Look up a symbol by a KNOWN short term, path, or uid (lexical + graph expansion). For a natural-language DESCRIPTION of code whose name you don't know, use cos_graph_search instead.
2306
+
2307
+ TIP: prefer SHORT terms ("sdk_dispatcher", "ClaudeSDKDispatcher.dispatch") or
2308
+ a literal path / uid. Long natural-language queries return weaker matches
2309
+ because the index is built from labels + docstrings, not free text.
2310
+
2311
+ UID scheme (also accepted as `q`):
2312
+ code:file:<path> · code:function:<path>::<name> · code:class:<path>::<name>
2313
+ code:method:<path>::<class>.<name> · code:module:<dotted>
2314
+ doc:file:<path> · doc:heading:<path>#<slug>:<level> · folder:<path>
2315
+
2316
+ When the query looks like a path or uid and the lexical pass
2317
+ returns nothing, the tool falls back to a direct uid lookup so
2318
+ the agent gets a single-item hit instead of empty results.
2319
+
2320
+ Args:
2321
+ q: Short term, path, or uid (non-empty). NL queries work but degrade.
2322
+ kinds: Comma-separated filter of node kinds (e.g. "function,class,method"). Empty = all.
2323
+ limit: Max results (default 10).
2324
+ max_hops: Walk expansion depth (default 2).
2325
+ confidence_min: Edge confidence floor (default 0.3).
2326
+ include_spine: S3 — attach the CONTAINS-ancestor chain to each result for breadcrumbs.
2327
+
2328
+ Returns:
2329
+ JSON envelope with `results` array. See docs/engineering/graph_os-queries.md.
2330
+ """
2331
+ return _graph_tools.cos_graph_query(
2332
+ q,
2333
+ kinds=_csv(kinds),
2334
+ limit=int(limit),
2335
+ max_hops=int(max_hops),
2336
+ confidence_min=float(confidence_min),
2337
+ include_spine=bool(include_spine),
2338
+ )
2339
+
2340
+ @mcp.tool(
2341
+ name="cos_graph_context",
2342
+ annotations={
2343
+ "title": "Graph Neighbourhood",
2344
+ "readOnlyHint": True,
2345
+ "destructiveHint": False,
2346
+ "idempotentHint": True,
2347
+ "openWorldHint": False,
2348
+ },
2349
+ )
2350
+ @safe_tool
2351
+ def cos_graph_context_tool(
2352
+ uid_or_name: str,
2353
+ direction: str = "both",
2354
+ depth: int = 1,
2355
+ include_content: bool = False,
2356
+ include_evidence: bool = False,
2357
+ include_spine: bool = False,
2358
+ ) -> str:
2359
+ """Return callers + callees + siblings + referenced docs around a symbol.
2360
+
2361
+ Args:
2362
+ uid_or_name: Node uid or fuzzy label. Uid scheme:
2363
+ ``code:file:<path>`` | ``code:function:<path>::<name>`` |
2364
+ ``code:class:<path>::<name>`` | ``code:module:<dotted>`` |
2365
+ ``doc:file:<path>`` | ``doc:heading:<path>#<slug>:<level>`` |
2366
+ ``folder:<path>``. Raw repo paths (``core/foo.py``) are
2367
+ auto-resolved to ``code:file:`` / ``doc:file:`` / ``folder:``;
2368
+ if all variants miss, a fuzzy label match is tried. Run
2369
+ ``cos_graph_query`` first to discover candidates.
2370
+ direction: "in" | "out" | "both".
2371
+ depth: BFS depth (default 1).
2372
+ include_content: When True, each returned node gains a ``content``
2373
+ field with source text read from ``file_path:start_line..end_line``
2374
+ (capped at 2000 chars, with ``truncated: bool``). Silently skipped
2375
+ when the file is missing or the node has no file_path. (B21)
2376
+ include_evidence: JOIN evidence rows (costs ~2× tokens).
2377
+ include_spine: S3 — pulls the CONTAINS-ancestor chain (file → folder → …)
2378
+ so the UI can render breadcrumbs.
2379
+ """
2380
+ return _graph_tools.cos_graph_context(
2381
+ uid_or_name,
2382
+ direction=str(direction),
2383
+ depth=int(depth),
2384
+ include_content=bool(include_content),
2385
+ include_evidence=bool(include_evidence),
2386
+ include_spine=bool(include_spine),
2387
+ )
2388
+
2389
+ @mcp.tool(
2390
+ name="cos_graph_impact",
2391
+ annotations={
2392
+ "title": "Graph Blast-Radius",
2393
+ "readOnlyHint": True,
2394
+ "destructiveHint": False,
2395
+ "idempotentHint": True,
2396
+ "openWorldHint": False,
2397
+ },
2398
+ )
2399
+ @safe_tool
2400
+ def cos_graph_impact_tool(
2401
+ uid: str,
2402
+ direction: str = "downstream",
2403
+ depth: int = 3,
2404
+ confidence_min: float = 0.3,
2405
+ visit_limit: int = 500,
2406
+ ) -> str:
2407
+ """Group affected nodes by risk tier (will_break / should_review / context).
2408
+
2409
+ Args:
2410
+ uid: Fully-qualified node uid. Scheme: ``code:file:<path>`` |
2411
+ ``code:function:<path>::<name>`` | ``code:class:<path>::<name>`` |
2412
+ ``code:module:<dotted>`` | ``doc:file:<path>`` | ``folder:<path>``.
2413
+ Raw repo paths (``core/foo.py``) are auto-resolved to
2414
+ ``code:file:`` / ``doc:file:`` / ``folder:``. If unsure, run
2415
+ ``cos_graph_query`` first to discover the right uid.
2416
+ direction: "downstream" (callers — break if `uid` changes) |
2417
+ "upstream" (deps `uid` calls/imports) | "both".
2418
+ depth: BFS hop limit (default 3).
2419
+ confidence_min: Drop edges below this score (default 0.3, matching the function + HTTP route).
2420
+ visit_limit: BFS node-visit cap (1..50000, default 500). Raise when meta.walk_truncated is true.
2421
+ """
2422
+ return _graph_tools.cos_graph_impact(
2423
+ uid,
2424
+ direction=str(direction),
2425
+ depth=int(depth),
2426
+ confidence_min=float(confidence_min),
2427
+ visit_limit=int(visit_limit),
2428
+ )
2429
+
2430
+ @mcp.tool(
2431
+ name="cos_graph_detect_changes",
2432
+ annotations={
2433
+ "title": "Graph Pre-Commit Self-Review",
2434
+ "readOnlyHint": True,
2435
+ "destructiveHint": False,
2436
+ "idempotentHint": True,
2437
+ "openWorldHint": False,
2438
+ },
2439
+ )
2440
+ @safe_tool
2441
+ def cos_graph_detect_changes_tool(
2442
+ files: str = "",
2443
+ scope: str = "working",
2444
+ analyze_downstream: bool = True,
2445
+ ) -> str:
2446
+ """Map changed files to affected symbols + downstream tasks + risk level.
2447
+
2448
+ Args:
2449
+ files: Comma-separated file paths (empty → echo empty envelope).
2450
+ scope: Label only; "working" | "staged" | "HEAD~1..HEAD".
2451
+ analyze_downstream: Walk transitive blast radius.
2452
+ """
2453
+ return _graph_tools.cos_graph_detect_changes(
2454
+ scope=str(scope),
2455
+ files=_csv(files),
2456
+ analyze_downstream=bool(analyze_downstream),
2457
+ )
2458
+
2459
+ @mcp.tool(
2460
+ name="cos_graph_trace",
2461
+ annotations={
2462
+ "title": "Graph Execution Trace",
2463
+ "readOnlyHint": True,
2464
+ "destructiveHint": False,
2465
+ "idempotentHint": True,
2466
+ "openWorldHint": False,
2467
+ },
2468
+ )
2469
+ @safe_tool
2470
+ def cos_graph_trace_tool(
2471
+ entry_uid: str,
2472
+ terminals: str = "return,exception",
2473
+ max_steps: int = 50,
2474
+ include_external: bool = False,
2475
+ ) -> str:
2476
+ """Forward execution walk from `entry_uid` until terminals.
2477
+
2478
+ Args:
2479
+ entry_uid: Function/method uid to start from, e.g.
2480
+ ``code:function:core/foo.py::bar``. Raw paths or names are
2481
+ auto-resolved (file → ``code:file:`` then entry-point heuristic).
2482
+ Run ``cos_graph_query`` first if unsure.
2483
+ terminals: Comma-separated edge labels that stop the walk.
2484
+ max_steps: Hard cap on emitted steps.
2485
+ """
2486
+ return _graph_tools.cos_graph_trace(
2487
+ entry_uid,
2488
+ terminals=tuple(_csv(terminals) or ("return", "exception")),
2489
+ max_steps=int(max_steps),
2490
+ include_external=bool(include_external),
2491
+ )
2492
+
2493
+ @mcp.tool(
2494
+ name="cos_graph_similar",
2495
+ annotations={
2496
+ "title": "Graph Semantic Similarity",
2497
+ "readOnlyHint": True,
2498
+ "destructiveHint": False,
2499
+ "idempotentHint": True,
2500
+ "openWorldHint": False,
2501
+ },
2502
+ )
2503
+ @safe_tool
2504
+ def cos_graph_similar_tool(
2505
+ uid: str,
2506
+ top_k: int = 5,
2507
+ confidence_min: float = 0.5,
2508
+ ) -> str:
2509
+ """Return the top-K nodes most similar to `uid` (difflib baseline).
2510
+
2511
+ Args:
2512
+ uid: Fully-qualified node uid (see ``cos_graph_impact`` for
2513
+ scheme). Raw repo paths are auto-resolved to
2514
+ ``code:file:`` / ``doc:file:`` / ``folder:``.
2515
+ top_k: Number of similar nodes to return.
2516
+ confidence_min: Minimum similarity score (0.0–1.0).
2517
+ """
2518
+ return _graph_tools.cos_graph_similar(
2519
+ uid,
2520
+ top_k=int(top_k),
2521
+ confidence_min=float(confidence_min),
2522
+ )
2523
+
2524
+ @mcp.tool(
2525
+ name="cos_graph_search",
2526
+ annotations={
2527
+ "title": "Graph Semantic Search (by description)",
2528
+ "readOnlyHint": True,
2529
+ "destructiveHint": False,
2530
+ "idempotentHint": True,
2531
+ "openWorldHint": False,
2532
+ },
2533
+ )
2534
+ @safe_tool
2535
+ def cos_graph_search_tool(
2536
+ query: str,
2537
+ top_k: int = 10,
2538
+ ) -> str:
2539
+ """Find code symbols from a NATURAL-LANGUAGE description (semantic + lexical + centrality). For a KNOWN name / path / uid, use cos_graph_query instead.
2540
+
2541
+ Args:
2542
+ query: Natural-language or code-ish query (e.g. "validate jwt token").
2543
+ top_k: Number of results to return (1–50).
2544
+ """
2545
+ return _graph_tools.cos_graph_search(query, top_k=int(top_k))
2546
+
2547
+ @mcp.tool(
2548
+ name="cos_graph_references",
2549
+ annotations={
2550
+ "title": "Graph Inbound References",
2551
+ "readOnlyHint": True,
2552
+ "destructiveHint": False,
2553
+ "idempotentHint": True,
2554
+ "openWorldHint": False,
2555
+ },
2556
+ )
2557
+ @safe_tool
2558
+ def cos_graph_references_tool(
2559
+ uid: str,
2560
+ kinds: str = "",
2561
+ limit: int = 100,
2562
+ ) -> str:
2563
+ """List inbound edges — "who references this?".
2564
+
2565
+ Args:
2566
+ uid: Fully-qualified node uid. Scheme: ``code:file:<path>`` |
2567
+ ``code:function:<path>::<name>`` | ``code:class:<path>::<name>`` |
2568
+ ``code:module:<dotted>`` | ``doc:file:<path>`` | ``folder:<path>``.
2569
+ Raw repo paths are auto-resolved.
2570
+ kinds: Comma-separated edge types. Empty string (default)
2571
+ picks edge types automatically per node-kind — class
2572
+ nodes get ``constructs+has_param_type+is_decorated_by+inherits_from``,
2573
+ function/method get ``calls+accesses_field+imports``, files
2574
+ get ``imports+links_to+references_doc+contains``. R4-02.
2575
+ limit: Max edges returned (default 100).
2576
+ """
2577
+ parsed = tuple(_csv(kinds) or ())
2578
+ return _graph_tools.cos_graph_references(
2579
+ uid,
2580
+ kinds=parsed if parsed else None,
2581
+ limit=int(limit),
2582
+ )
2583
+
2584
+ @mcp.tool(
2585
+ name="cos_graph_path",
2586
+ annotations={
2587
+ "title": "Graph Shortest Path",
2588
+ "readOnlyHint": True,
2589
+ "destructiveHint": False,
2590
+ "idempotentHint": True,
2591
+ "openWorldHint": False,
2592
+ },
2593
+ )
2594
+ @safe_tool
2595
+ def cos_graph_path_tool(
2596
+ source_uid: str,
2597
+ target_uid: str,
2598
+ max_hops: int = 5,
2599
+ ) -> str:
2600
+ """Shortest path between two nodes (either direction).
2601
+
2602
+ Args:
2603
+ source_uid: Origin uid (auto-resolves raw paths; see
2604
+ ``cos_graph_impact`` for the scheme).
2605
+ target_uid: Destination uid (same rules as ``source_uid``).
2606
+ max_hops: BFS depth limit (default 5).
2607
+ """
2608
+ return _graph_tools.cos_graph_path(
2609
+ source_uid,
2610
+ target_uid,
2611
+ max_hops=int(max_hops),
2612
+ )
2613
+
2614
+ @mcp.tool(
2615
+ name="cos_graph_export",
2616
+ annotations={
2617
+ "title": "Graph Subgraph Export",
2618
+ "readOnlyHint": True,
2619
+ "destructiveHint": False,
2620
+ "idempotentHint": True,
2621
+ "openWorldHint": False,
2622
+ },
2623
+ )
2624
+ @safe_tool
2625
+ def cos_graph_export_tool(
2626
+ format: str = "json",
2627
+ root_uid: str = "",
2628
+ edge_types: str = "",
2629
+ max_nodes: int = 500,
2630
+ include_spine: bool = False,
2631
+ mode: str = "auto",
2632
+ exclude_kinds: str = "__default__",
2633
+ ) -> str:
2634
+ """Export a subgraph as json | mermaid | dot.
2635
+
2636
+ Args:
2637
+ format: Output format (``json`` / ``mermaid`` / ``dot``).
2638
+ root_uid: Optional seed; empty walks the edge table.
2639
+ edge_types: Comma-separated edge filter (empty = all).
2640
+ max_nodes: Hard cap on node count.
2641
+ include_spine: S3 — also include the CONTAINS ancestor chain.
2642
+ mode: TASK-141 view-mode blend when no root is pinned —
2643
+ ``auto`` (semantic + contains, default), ``containment``,
2644
+ ``dependencies``, or ``processes``.
2645
+ exclude_kinds: Comma-separated noise kinds to drop. Sentinel
2646
+ ``__default__`` (default) applies the built-in noise list;
2647
+ empty string disables filtering.
2648
+ """
2649
+ if exclude_kinds == "__default__":
2650
+ ek = None
2651
+ elif exclude_kinds == "":
2652
+ ek = []
2653
+ else:
2654
+ ek = list(_csv(exclude_kinds) or ())
2655
+ return _graph_tools.cos_graph_export(
2656
+ format=str(format),
2657
+ root_uid=root_uid or None,
2658
+ edge_types=_csv(edge_types),
2659
+ max_nodes=int(max_nodes),
2660
+ include_spine=bool(include_spine),
2661
+ mode=str(mode),
2662
+ exclude_kinds=ek,
2663
+ )
2664
+
2665
+ @mcp.tool(
2666
+ name="cos_graph_rename_plan",
2667
+ annotations={
2668
+ "title": "Graph Rename Plan",
2669
+ "readOnlyHint": True,
2670
+ "destructiveHint": False,
2671
+ "idempotentHint": True,
2672
+ "openWorldHint": False,
2673
+ },
2674
+ )
2675
+ @safe_tool
2676
+ def cos_graph_rename_plan_tool(
2677
+ uid: str,
2678
+ new_name: str,
2679
+ check_strings: bool = True,
2680
+ ) -> str:
2681
+ """Plan a rename — call-sites, docs, tests, strings, risk.
2682
+
2683
+ Args:
2684
+ uid: Symbol to rename. Scheme: ``code:function:<path>::<name>`` |
2685
+ ``code:class:<path>::<name>`` | ``code:module:<dotted>``.
2686
+ Raw paths are auto-resolved when applicable.
2687
+ new_name: Replacement symbol name.
2688
+ check_strings: Also scan string literals for the old name.
2689
+ """
2690
+ return _graph_tools.cos_graph_rename_plan(
2691
+ uid,
2692
+ new_name,
2693
+ check_strings=bool(check_strings),
2694
+ )
2695
+
2696
+ @mcp.tool(
2697
+ name="cos_graph_contracts",
2698
+ annotations={
2699
+ "title": "Graph API Contracts",
2700
+ "readOnlyHint": True,
2701
+ "destructiveHint": False,
2702
+ "idempotentHint": True,
2703
+ "openWorldHint": False,
2704
+ },
2705
+ )
2706
+ @safe_tool
2707
+ def cos_graph_contracts_tool(
2708
+ scope: str = "all",
2709
+ kinds: str = "http,mcp,grpc,event,websocket",
2710
+ include_test_sources: bool = False,
2711
+ ) -> str:
2712
+ """Enumerate every handler declared in the graph (HTTP / MCP / gRPC / events / WS)."""
2713
+ return _graph_tools.cos_graph_contracts(
2714
+ scope=str(scope),
2715
+ kinds=tuple(_csv(kinds) or ("http", "mcp", "grpc", "event", "websocket")),
2716
+ include_test_sources=bool(include_test_sources),
2717
+ )
2718
+
2719
+ @mcp.tool(
2720
+ name="cos_graph_entrypoints",
2721
+ annotations={
2722
+ "title": "Graph Entry Points (Scored)",
2723
+ "readOnlyHint": True,
2724
+ "destructiveHint": False,
2725
+ "idempotentHint": True,
2726
+ "openWorldHint": False,
2727
+ },
2728
+ )
2729
+ @safe_tool
2730
+ def cos_graph_entrypoints_tool(
2731
+ top: int = 20,
2732
+ kind: str = "",
2733
+ min_score: float = 0.05,
2734
+ diversify: bool = True,
2735
+ ) -> str:
2736
+ """Top-N scored entry points (main / cli / http / cron / test) — TASK-081."""
2737
+ return _graph_tools.cos_graph_entrypoints(
2738
+ top=int(top),
2739
+ kind=(kind or None),
2740
+ min_score=float(min_score),
2741
+ diversify=bool(diversify),
2742
+ )
2743
+
2744
+ @mcp.tool(
2745
+ name="cos_graph_communities",
2746
+ annotations={
2747
+ "title": "Graph Communities / Processes (Louvain)",
2748
+ "readOnlyHint": True,
2749
+ "destructiveHint": False,
2750
+ "idempotentHint": True,
2751
+ "openWorldHint": False,
2752
+ },
2753
+ )
2754
+ @safe_tool
2755
+ def cos_graph_communities_tool(
2756
+ top: int = 50,
2757
+ min_size: int = 2,
2758
+ max_members: int = 10,
2759
+ ) -> str:
2760
+ """Louvain process clusters — response key is `processes` (not `communities`)."""
2761
+ return _graph_tools.cos_graph_communities(
2762
+ top=int(top),
2763
+ min_size=int(min_size),
2764
+ max_members=int(max_members),
2765
+ )
2766
+
2767
+ @mcp.tool(
2768
+ name="cos_graph_resolve",
2769
+ annotations={
2770
+ "title": "Graph UID Resolver (NL → canonical uid)",
2771
+ "readOnlyHint": True,
2772
+ "destructiveHint": False,
2773
+ "idempotentHint": True,
2774
+ "openWorldHint": False,
2775
+ },
2776
+ )
2777
+ @safe_tool
2778
+ def cos_graph_resolve_tool(
2779
+ q: str,
2780
+ kinds: str = "",
2781
+ top: int = 10,
2782
+ ) -> str:
2783
+ """Resolve a natural-language label, path, or partial uid to canonical uids.
2784
+
2785
+ Use this BEFORE other cos_graph_* tools when you don't know the exact uid.
2786
+ Tries: direct uid → path/qualname → FTS5 full-text → LIKE fallback.
2787
+
2788
+ UID scheme:
2789
+ code:file:<path> · code:function:<path>::<name> · code:class:<path>::<name>
2790
+ code:method:<path>::<class>.<name> · code:module:<dotted>
2791
+ doc:file:<path> · doc:heading:<path>#<slug>:<level> · folder:<path>
2792
+
2793
+ Args:
2794
+ q: Natural language ("the dispatcher function"), label ("ClaudeSDKDispatcher"),
2795
+ path ("adapters/claude/sdk_dispatcher.py"), or qualname ("Class.method").
2796
+ kinds: Comma-separated kind filter (e.g. "function,method,class"). Empty = all.
2797
+ top: Max results (default 10).
2798
+
2799
+ Returns:
2800
+ JSON envelope with `results` (ranked list of {uid, kind, label, …}) and
2801
+ `strategy` (which resolution path matched).
2802
+ """
2803
+ return _graph_tools.cos_graph_resolve(
2804
+ q,
2805
+ kinds=_csv(kinds) or None,
2806
+ top=int(top),
2807
+ )
2808
+
2809
+ @mcp.tool(
2810
+ name="cos_graph_centrality",
2811
+ annotations={
2812
+ "title": "Graph Centrality (degree / betweenness)",
2813
+ "readOnlyHint": True,
2814
+ "destructiveHint": False,
2815
+ "idempotentHint": True,
2816
+ "openWorldHint": False,
2817
+ },
2818
+ )
2819
+ @safe_tool
2820
+ def cos_graph_centrality_tool(
2821
+ metric: str = "degree",
2822
+ top: int = 20,
2823
+ kind: str = "",
2824
+ ) -> str:
2825
+ """Hub detection — surface high-degree (or high-betweenness) nodes.
2826
+
2827
+ Use to identify chokepoints / refactor priorities / nodes that demand
2828
+ extra review.
2829
+
2830
+ Args:
2831
+ metric: "degree" (cheap, default) or "betweenness" (expensive).
2832
+ top: Max nodes returned (default 20).
2833
+ kind: Optional kind filter (e.g. "function", "class"). Empty = all.
2834
+
2835
+ Returns:
2836
+ JSON envelope with `nodes` ranked by centrality score.
2837
+ """
2838
+ return _graph_tools.cos_graph_centrality(
2839
+ metric=metric,
2840
+ top=int(top),
2841
+ kind=kind or None,
2842
+ )
2843
+
2844
+ @mcp.tool(
2845
+ name="cos_graph_ranking",
2846
+ annotations={
2847
+ "title": "Graph PageRank (importance / personalised)",
2848
+ "readOnlyHint": True,
2849
+ "destructiveHint": False,
2850
+ "idempotentHint": True,
2851
+ "openWorldHint": False,
2852
+ },
2853
+ )
2854
+ @safe_tool
2855
+ def cos_graph_ranking_tool(
2856
+ query: str = "",
2857
+ top: int = 20,
2858
+ kind: str = "",
2859
+ damping: float = 0.85,
2860
+ iterations: int = 30,
2861
+ ) -> str:
2862
+ """PageRank — node importance, optionally personalised by query.
2863
+
2864
+ Use for: knowledge condensation (top-N canonical concepts),
2865
+ query-personalised search ranking, documentation sourcing.
2866
+
2867
+ Args:
2868
+ query: Optional personalisation query ("auth", "graph backend").
2869
+ Empty = global PageRank.
2870
+ top: Max nodes returned (default 20).
2871
+ kind: Optional kind filter. Empty = all.
2872
+ damping: PageRank damping factor (default 0.85).
2873
+ iterations: Power-iteration count (default 30).
2874
+
2875
+ Returns:
2876
+ JSON envelope with `nodes` ranked by PageRank score.
2877
+ """
2878
+ return _graph_tools.cos_graph_ranking(
2879
+ query=query or None,
2880
+ top=int(top),
2881
+ kind=kind or None,
2882
+ damping=float(damping),
2883
+ iterations=int(iterations),
2884
+ )
2885
+
2886
+ @mcp.tool(
2887
+ name="cos_graph_cycles",
2888
+ annotations={
2889
+ "title": "Graph Circular Dependencies (SCC)",
2890
+ "readOnlyHint": True,
2891
+ "destructiveHint": False,
2892
+ "idempotentHint": True,
2893
+ "openWorldHint": False,
2894
+ },
2895
+ )
2896
+ @safe_tool
2897
+ def cos_graph_cycles_tool(
2898
+ scope: str = "imports",
2899
+ top: int = 20,
2900
+ min_size: int = 2,
2901
+ ) -> str:
2902
+ """Detect circular dependencies as strongly-connected components.
2903
+
2904
+ Args:
2905
+ scope: "imports" (module-level circular deps, the design smell) or
2906
+ "calls" (function cycles incl. legitimate mutual recursion).
2907
+ top: Max cycles returned (default 20).
2908
+ min_size: Minimum SCC size to report (default 2).
2909
+
2910
+ Returns:
2911
+ JSON envelope with `cycles` (each {size, members}) + total_count.
2912
+ """
2913
+ return _graph_tools.cos_graph_cycles(
2914
+ scope=str(scope),
2915
+ top=int(top),
2916
+ min_size=int(min_size),
2917
+ )
2918
+
2919
+ @mcp.tool(
2920
+ name="cos_graph_dead_code",
2921
+ annotations={
2922
+ "title": "Graph Dead-Code Candidates",
2923
+ "readOnlyHint": True,
2924
+ "destructiveHint": False,
2925
+ "idempotentHint": True,
2926
+ "openWorldHint": False,
2927
+ },
2928
+ )
2929
+ @safe_tool
2930
+ def cos_graph_dead_code_tool(
2931
+ kind: str = "",
2932
+ top: int = 50,
2933
+ include_tests: bool = False,
2934
+ ) -> str:
2935
+ """List in-repo symbols with zero non-test inbound references (dead-code candidates).
2936
+
2937
+ Surfaces functions / methods / classes that nothing (outside tests)
2938
+ calls, constructs, subclasses, or type-references — the inverse of
2939
+ centrality. Candidates only: dynamic-dispatch / CLI-registered /
2940
+ externally-called symbols may appear; verify with cos_graph_references
2941
+ before deleting.
2942
+
2943
+ Args:
2944
+ kind: Optional filter — function | method | class. Empty = all three.
2945
+ top: Max candidates returned (default 50, max 500).
2946
+ include_tests: Count test-sourced edges + include test files (default False).
2947
+
2948
+ Returns:
2949
+ JSON envelope with `dead` (list) + `total_count`.
2950
+ """
2951
+ return _graph_tools.cos_graph_dead_code(
2952
+ kind=kind or "",
2953
+ top=int(top),
2954
+ include_tests=bool(include_tests),
2955
+ )
2956
+
2957
+ @mcp.tool(
2958
+ name="cos_graph_test_gap",
2959
+ annotations={
2960
+ "title": "Graph Test-Gap (untested symbols)",
2961
+ "readOnlyHint": True,
2962
+ "destructiveHint": False,
2963
+ "idempotentHint": True,
2964
+ "openWorldHint": False,
2965
+ },
2966
+ )
2967
+ @safe_tool
2968
+ def cos_graph_test_gap_tool(
2969
+ kind: str = "",
2970
+ top: int = 50,
2971
+ ) -> str:
2972
+ """List prod function/method/class with zero inbound edge from any test (untested symbols).
2973
+
2974
+ Candidates only: indirect exercise (CLI / fixtures / dynamic dispatch)
2975
+ may not appear as a graph edge. Shell excluded (no call-graph).
2976
+
2977
+ Args:
2978
+ kind: Optional filter — function | method | class. Empty = all three.
2979
+ top: Max returned (default 50, max 500).
2980
+
2981
+ Returns:
2982
+ JSON envelope with `untested` (list) + total_count.
2983
+ """
2984
+ return _graph_tools.cos_graph_test_gap(kind=kind or "", top=int(top))
2985
+
2986
+ @mcp.tool(
2987
+ name="cos_graph_diff",
2988
+ annotations={
2989
+ "title": "Graph Diff (git revision blast-radius)",
2990
+ "readOnlyHint": True,
2991
+ "destructiveHint": False,
2992
+ "idempotentHint": True,
2993
+ "openWorldHint": False,
2994
+ },
2995
+ )
2996
+ @safe_tool
2997
+ def cos_graph_diff_tool(
2998
+ base: str = "HEAD~1",
2999
+ head: str = "HEAD",
3000
+ analyze_downstream: bool = True,
3001
+ ) -> str:
3002
+ """Graph blast-radius of a git revision range (base..head).
3003
+
3004
+ Resolves changed files via `git diff --name-only base..head`, then maps
3005
+ them to affected symbols + downstream consumers + risk (PR/review view).
3006
+
3007
+ Args:
3008
+ base: Base git revision (default HEAD~1).
3009
+ head: Head git revision (default HEAD).
3010
+ analyze_downstream: Walk transitive consumers (default True).
3011
+
3012
+ Returns:
3013
+ JSON envelope with range, files, symbols, downstream_consumers, risk_level.
3014
+ """
3015
+ return _graph_tools.cos_graph_diff(
3016
+ base=str(base),
3017
+ head=str(head),
3018
+ analyze_downstream=bool(analyze_downstream),
3019
+ )
3020
+
3021
+ @mcp.tool(
3022
+ name="cos_graph_doctor",
3023
+ annotations={
3024
+ "title": "Graph Health Doctor",
3025
+ "readOnlyHint": True,
3026
+ "destructiveHint": False,
3027
+ "idempotentHint": True,
3028
+ "openWorldHint": False,
3029
+ },
3030
+ )
3031
+ @safe_tool
3032
+ def cos_graph_doctor_tool(
3033
+ fix: bool = False,
3034
+ ) -> str:
3035
+ """Graph health snapshot — orphans, dangling edges, duplicates, backend status.
3036
+
3037
+ Call when graph queries return nothing or `meta.backend_fallback=true`.
3038
+
3039
+ Args:
3040
+ fix: If True, attempt safe repairs (delete dangling edges). Default False
3041
+ — use the report-only mode to see what would change first.
3042
+
3043
+ Returns:
3044
+ JSON envelope with `healthy` boolean, `issues` list, `stats` dict.
3045
+ """
3046
+ return _graph_tools.cos_graph_doctor(
3047
+ fix=bool(fix),
3048
+ )
3049
+
3050
+ else:
3051
+ # Deterministic unavailable responses so agents still see a valid envelope.
3052
+ for _name in (
3053
+ "cos_graph_query",
3054
+ "cos_graph_resolve",
3055
+ "cos_graph_context",
3056
+ "cos_graph_impact",
3057
+ "cos_graph_detect_changes",
3058
+ "cos_graph_trace",
3059
+ "cos_graph_similar",
3060
+ "cos_graph_search",
3061
+ "cos_graph_references",
3062
+ "cos_graph_path",
3063
+ "cos_graph_export",
3064
+ "cos_graph_rename_plan",
3065
+ "cos_graph_contracts",
3066
+ "cos_graph_entrypoints",
3067
+ "cos_graph_communities",
3068
+ "cos_graph_centrality",
3069
+ "cos_graph_ranking",
3070
+ "cos_graph_cycles",
3071
+ "cos_graph_dead_code",
3072
+ "cos_graph_test_gap",
3073
+ "cos_graph_diff",
3074
+ "cos_graph_doctor",
3075
+ ):
3076
+
3077
+ def _make_stub(tool_name: str):
3078
+ @mcp.tool(
3079
+ name=tool_name,
3080
+ annotations={"title": f"{tool_name} (unavailable)", "readOnlyHint": True},
3081
+ )
3082
+ @safe_tool
3083
+ def _stub(*_args: object, **_kwargs: object) -> str:
3084
+ return _graph_unavailable()
3085
+
3086
+ return _stub
3087
+
3088
+ _make_stub(_name)
3089
+
3090
+
3091
+ # ---------------------------------------------------------------------------
3092
+ # Entry point
3093
+ # ---------------------------------------------------------------------------
3094
+ def _run_self_test() -> bool:
3095
+ """Quick self-test: verify DB is reachable and health tool works.
3096
+
3097
+ Walks the MCP envelope (docs/engineering/mcp-error-envelope.md) — asserts
3098
+ `ok: true` then drills into `data` for the actual health stats.
3099
+ """
3100
+ logger.info("Running self-test...")
3101
+ envelope = json.loads(thinking_os_health())
3102
+
3103
+ if not envelope.get("ok"):
3104
+ logger.error("FAIL: health returned error envelope: %s", envelope.get("error"))
3105
+ return False
3106
+
3107
+ data = envelope["data"]
3108
+ checks_passed = True
3109
+
3110
+ if "schema_version" not in data:
3111
+ logger.error("FAIL: schema_version missing from health response")
3112
+ checks_passed = False
3113
+ elif data["schema_version"] < 1:
3114
+ logger.error("FAIL: schema_version is %d, expected >= 1", data["schema_version"])
3115
+ checks_passed = False
3116
+
3117
+ if "tables" not in data:
3118
+ logger.error("FAIL: tables missing from health response")
3119
+ checks_passed = False
3120
+ else:
3121
+ expected_tables = [
3122
+ "task_outcomes",
3123
+ "agent_metrics",
3124
+ "learned_patterns",
3125
+ "observations",
3126
+ "session_summaries",
3127
+ ]
3128
+ for table in expected_tables:
3129
+ if table not in data["tables"]:
3130
+ logger.error("FAIL: table '%s' missing from stats", table)
3131
+ checks_passed = False
3132
+ elif data["tables"][table] is None:
3133
+ logger.error("FAIL: table '%s' does not exist in DB", table)
3134
+ checks_passed = False
3135
+
3136
+ if checks_passed:
3137
+ logger.info("PASS: all self-test checks passed")
3138
+ logger.info("Stats: %s", json.dumps(data, indent=2))
3139
+ return checks_passed
3140
+
3141
+
3142
+ def main() -> None:
3143
+ """Entry point — handles --test flag or starts MCP stdio server."""
3144
+ if "--test" in sys.argv:
3145
+ success = _run_self_test()
3146
+ sys.exit(0 if success else 1)
3147
+ else:
3148
+ logger.info("Starting thinking_os MCP server (stdio)...")
3149
+ gating = apply_module_tool_gating(mcp)
3150
+ if gating["removed"]:
3151
+ logger.info(
3152
+ "Module gating: removed %d tool(s) for disabled module(s) %s",
3153
+ len(gating["removed"]),
3154
+ gating["disabled_modules"],
3155
+ )
3156
+ mcp.run(transport="stdio")
3157
+
3158
+
3159
+ if __name__ == "__main__":
3160
+ main()