coding-os 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1304) hide show
  1. adapters/claude/README.md +6 -0
  2. adapters/claude/_install_helpers/extract_stacks.py +43 -0
  3. adapters/claude/_install_helpers/update_mcp_json.py +75 -0
  4. adapters/claude/adapter.yaml +164 -0
  5. adapters/claude/hooks/README.md +40 -0
  6. adapters/claude/hooks/agent_memory_sync.py +131 -0
  7. adapters/claude/hooks/ensure-agent-memory-link.sh +36 -0
  8. adapters/claude/hooks/sync-agent-memory.sh +21 -0
  9. adapters/claude/install.sh +86 -0
  10. adapters/claude/sdk_dispatcher.py +871 -0
  11. adapters/claude/settings.local.template.json +31 -0
  12. adapters/claude/settings.template.json +808 -0
  13. adapters/claude/update_mcp_json.py +85 -0
  14. adapters/codex/adapter.yaml +254 -0
  15. adapters/codex/chat_provider.py +230 -0
  16. adapters/codex/commands/formula-f1.md +129 -0
  17. adapters/codex/commands/formula-f10.md +100 -0
  18. adapters/codex/commands/formula-f11.md +123 -0
  19. adapters/codex/commands/formula-f2.md +139 -0
  20. adapters/codex/commands/formula-f3.md +127 -0
  21. adapters/codex/commands/formula-f4.md +101 -0
  22. adapters/codex/commands/formula-f5.md +135 -0
  23. adapters/codex/commands/formula-f6.md +147 -0
  24. adapters/codex/commands/formula-f7.md +111 -0
  25. adapters/codex/commands/formula-f8.md +133 -0
  26. adapters/codex/commands/formula-f9.md +112 -0
  27. adapters/codex/enable_codex_hooks.py +94 -0
  28. adapters/codex/ensure_codex_mcp.py +124 -0
  29. adapters/codex/hooks/codex-merge-hook-output.py +72 -0
  30. adapters/codex/hooks/codex-normalize-edit.py +96 -0
  31. adapters/codex/hooks/codex-postedit-dispatch.sh +75 -0
  32. adapters/codex/hooks/codex-posttool-dispatch.sh +70 -0
  33. adapters/codex/hooks/codex-preedit-dispatch.sh +83 -0
  34. adapters/codex/hooks/codex-pretool-dispatch.sh +82 -0
  35. adapters/codex/hooks/codex-sessionend-dispatch.sh +20 -0
  36. adapters/codex/hooks/codex-sessionstart-dispatch.sh +68 -0
  37. adapters/codex/hooks/codex-stop-dispatch.sh +73 -0
  38. adapters/codex/hooks/codex-userpromptsubmit-dispatch.sh +74 -0
  39. adapters/codex/hooks.template.json +208 -0
  40. adapters/codex/install.sh +83 -0
  41. adapters/codex/sdk_dispatcher.py +449 -0
  42. board_os/__init__.py +39 -0
  43. board_os/_agent_runtime.py +256 -0
  44. board_os/config.py +421 -0
  45. board_os/git_coherence.py +107 -0
  46. board_os/hub_adapter_manifest.py +140 -0
  47. board_os/mcp_tools.py +3228 -0
  48. board_os/migration.py +166 -0
  49. board_os/parser.py +317 -0
  50. board_os/presence.py +156 -0
  51. board_os/sync.py +320 -0
  52. board_os/transition_gates.py +224 -0
  53. board_os/transition_gates_cli.py +272 -0
  54. board_os/transition_gates_validator.py +551 -0
  55. board_os/verify_suites.py +126 -0
  56. board_os/verify_suites_cli.py +328 -0
  57. board_os/workflow.py +967 -0
  58. cli/__init__.py +0 -0
  59. cli/_data_types.py +248 -0
  60. cli/_init_helpers.py +587 -0
  61. cli/_resources.py +100 -0
  62. cli/adapter_registry.py +239 -0
  63. cli/add_stack.py +315 -0
  64. cli/aggregator.py +438 -0
  65. cli/board_commands.py +1218 -0
  66. cli/brain_commands.py +255 -0
  67. cli/cognition.py +345 -0
  68. cli/config_composer.py +349 -0
  69. cli/core_version.py +41 -0
  70. cli/cron_commands.py +278 -0
  71. cli/db_reset.py +298 -0
  72. cli/doc_commands.py +111 -0
  73. cli/doctor.py +2953 -0
  74. cli/doctor_board.py +365 -0
  75. cli/doctor_extras.py +1121 -0
  76. cli/doctor_graph.py +608 -0
  77. cli/doctor_tokens.py +254 -0
  78. cli/graph_commands.py +1265 -0
  79. cli/hook_renderer.py +393 -0
  80. cli/hub_commands.py +580 -0
  81. cli/list_adapters.py +79 -0
  82. cli/list_stacks.py +105 -0
  83. cli/logs_commands.py +89 -0
  84. cli/main.py +3070 -0
  85. cli/materialize_file.py +65 -0
  86. cli/mcp_start.py +153 -0
  87. cli/module_commands.py +513 -0
  88. cli/pr_commands.py +2024 -0
  89. cli/preset_commands.py +126 -0
  90. cli/preset_registry.py +171 -0
  91. cli/project_overrides.py +119 -0
  92. cli/registry.py +365 -0
  93. cli/remove_stack.py +492 -0
  94. cli/renderer.py +620 -0
  95. cli/setup.py +464 -0
  96. cli/skill_commands.py +689 -0
  97. cli/skill_registry.py +235 -0
  98. cli/skills_list.py +332 -0
  99. cli/stack_lint.py +351 -0
  100. cli/stack_registry.py +688 -0
  101. cli/subsystems.py +335 -0
  102. cli/sync_all.py +310 -0
  103. cli/tail_command.py +410 -0
  104. cli/update.py +608 -0
  105. cli/verify_since_edit.py +439 -0
  106. coding_os-0.3.2.dist-info/METADATA +508 -0
  107. coding_os-0.3.2.dist-info/RECORD +1304 -0
  108. coding_os-0.3.2.dist-info/WHEEL +5 -0
  109. coding_os-0.3.2.dist-info/entry_points.txt +5 -0
  110. coding_os-0.3.2.dist-info/licenses/LICENSE +201 -0
  111. coding_os-0.3.2.dist-info/top_level.txt +10 -0
  112. core/__init__.py +0 -0
  113. core/board_os/__init__.py +39 -0
  114. core/board_os/_agent_runtime.py +256 -0
  115. core/board_os/config.py +421 -0
  116. core/board_os/git_coherence.py +107 -0
  117. core/board_os/hub_adapter_manifest.py +140 -0
  118. core/board_os/mcp_tools.py +3228 -0
  119. core/board_os/migration.py +166 -0
  120. core/board_os/parser.py +317 -0
  121. core/board_os/presence.py +156 -0
  122. core/board_os/sync.py +320 -0
  123. core/board_os/transition-gates.yaml +176 -0
  124. core/board_os/transition_gates.py +224 -0
  125. core/board_os/transition_gates_cli.py +272 -0
  126. core/board_os/transition_gates_validator.py +551 -0
  127. core/board_os/verify-suites.yaml +113 -0
  128. core/board_os/verify_suites.py +126 -0
  129. core/board_os/verify_suites_cli.py +328 -0
  130. core/board_os/workflow.py +967 -0
  131. core/commands/board.md +27 -0
  132. core/commands/classify.md +23 -0
  133. core/commands/compose.md +23 -0
  134. core/commands/daily.md +31 -0
  135. core/commands/diagnose.md +7 -0
  136. core/commands/memory-search.md +23 -0
  137. core/commands/new-project.md +33 -0
  138. core/commands/retro.md +38 -0
  139. core/commands/review.md +14 -0
  140. core/commands/task.md +17 -0
  141. core/commands/verify.md +34 -0
  142. core/docs/thinking_os-final-edition.md +1449 -0
  143. core/doctor-config.yaml +74 -0
  144. core/graph_os/__init__.py +29 -0
  145. core/graph_os/backend.py +233 -0
  146. core/graph_os/backends/__init__.py +13 -0
  147. core/graph_os/backends/sqlite_backend.py +1053 -0
  148. core/graph_os/bench/__init__.py +17 -0
  149. core/graph_os/bench/fixtures.py +56 -0
  150. core/graph_os/bench/harness.py +95 -0
  151. core/graph_os/bench/persian_precision.py +142 -0
  152. core/graph_os/bench/scale_500k.py +120 -0
  153. core/graph_os/bench/token_cost.py +170 -0
  154. core/graph_os/bench/viewer_fps.py +110 -0
  155. core/graph_os/communities.py +410 -0
  156. core/graph_os/enterprise.py +218 -0
  157. core/graph_os/entry_points.py +226 -0
  158. core/graph_os/extractors/__init__.py +25 -0
  159. core/graph_os/extractors/code_generic.py +914 -0
  160. core/graph_os/extractors/code_go.py +1422 -0
  161. core/graph_os/extractors/code_json.py +340 -0
  162. core/graph_os/extractors/code_php.py +979 -0
  163. core/graph_os/extractors/code_python.py +1454 -0
  164. core/graph_os/extractors/code_shell.py +538 -0
  165. core/graph_os/extractors/code_toml.py +302 -0
  166. core/graph_os/extractors/code_ts.py +1665 -0
  167. core/graph_os/extractors/code_yaml.py +394 -0
  168. core/graph_os/extractors/contracts.py +1592 -0
  169. core/graph_os/extractors/md_links.py +890 -0
  170. core/graph_os/extractors/task_deps.py +345 -0
  171. core/graph_os/groups/__init__.py +22 -0
  172. core/graph_os/groups/cross_repo.py +156 -0
  173. core/graph_os/groups/manifest.py +141 -0
  174. core/graph_os/ingest/__init__.py +19 -0
  175. core/graph_os/ingest/base.py +306 -0
  176. core/graph_os/ingest/github.py +112 -0
  177. core/graph_os/ingest/zip.py +95 -0
  178. core/graph_os/toolchain.py +393 -0
  179. core/graph_os/tools/__init__.py +9 -0
  180. core/graph_os/tools/graph.py +5573 -0
  181. core/graph_os/tools/reindex_dispatch.py +730 -0
  182. core/graph_os/tree_sitter_overlay.py +235 -0
  183. core/graph_os/types.py +252 -0
  184. core/graph_os/vec_index.py +277 -0
  185. core/graph_os/viewer/__init__.py +12 -0
  186. core/graph_os/viewer/exporter.py +93 -0
  187. core/graph_os/viewer/template.py +189 -0
  188. core/hooks/_helpers/_paths.py +40 -0
  189. core/hooks/_helpers/advance_role.py +72 -0
  190. core/hooks/_helpers/auto_compose.py +228 -0
  191. core/hooks/_helpers/auto_validate_lessons.py +55 -0
  192. core/hooks/_helpers/branch_guard_check.py +796 -0
  193. core/hooks/_helpers/check_commit_message.py +108 -0
  194. core/hooks/_helpers/check_dangerous_rm.py +80 -0
  195. core/hooks/_helpers/check_git_bypass.py +154 -0
  196. core/hooks/_helpers/check_git_destructive.py +77 -0
  197. core/hooks/_helpers/check_settings_write.py +97 -0
  198. core/hooks/_helpers/consume_override.py +51 -0
  199. core/hooks/_helpers/context_budget.py +77 -0
  200. core/hooks/_helpers/cos_say_json.py +103 -0
  201. core/hooks/_helpers/destructive_edit_check.py +163 -0
  202. core/hooks/_helpers/detect_status_transition.py +82 -0
  203. core/hooks/_helpers/digest_regen.py +56 -0
  204. core/hooks/_helpers/doc_sync_check.py +498 -0
  205. core/hooks/_helpers/drain_embedding_outbox.py +52 -0
  206. core/hooks/_helpers/extract_additional_context.py +51 -0
  207. core/hooks/_helpers/extract_commit_msg_arg.py +74 -0
  208. core/hooks/_helpers/git_command_parse.py +424 -0
  209. core/hooks/_helpers/git_settings_fields.py +47 -0
  210. core/hooks/_helpers/graph_context_match.py +37 -0
  211. core/hooks/_helpers/graph_marker_check.py +70 -0
  212. core/hooks/_helpers/jit_recall.py +56 -0
  213. core/hooks/_helpers/json_field.py +41 -0
  214. core/hooks/_helpers/narrative_signal.py +59 -0
  215. core/hooks/_helpers/observation_count.py +31 -0
  216. core/hooks/_helpers/pre_commit_batch.py +177 -0
  217. core/hooks/_helpers/pre_commit_fake_input.py +42 -0
  218. core/hooks/_helpers/presence_gc.py +102 -0
  219. core/hooks/_helpers/presence_write.py +167 -0
  220. core/hooks/_helpers/recover_indirect.py +35 -0
  221. core/hooks/_helpers/routing_evolution.py +104 -0
  222. core/hooks/_helpers/session_recap.py +72 -0
  223. core/hooks/_helpers/skill_primer.py +229 -0
  224. core/hooks/_helpers/task_sync.py +59 -0
  225. core/hooks/_helpers/tool_failure_capture.py +147 -0
  226. core/hooks/_helpers/trajectory_autosnap.py +278 -0
  227. core/hooks/_helpers/trajectory_startup.py +62 -0
  228. core/hooks/_helpers/turn_summary.py +82 -0
  229. core/hooks/_helpers/validate_task_frontmatter.py +98 -0
  230. core/hooks/_helpers/wip_limit_check.py +103 -0
  231. core/hooks/_helpers/wip_lines.py +53 -0
  232. core/hooks/_helpers/work_log_append.py +89 -0
  233. core/hooks/_helpers/wrap_dispatch_output.py +82 -0
  234. core/hooks/advance-role.sh +48 -0
  235. core/hooks/agent-presence.sh +179 -0
  236. core/hooks/auto-brain-decay.sh +184 -0
  237. core/hooks/auto-compose-roles.sh +83 -0
  238. core/hooks/auto-graph-reconcile-shell.sh +119 -0
  239. core/hooks/auto-regen-doc-index.sh +120 -0
  240. core/hooks/auto-reindex-docs.sh +130 -0
  241. core/hooks/auto-task-sync.sh +56 -0
  242. core/hooks/auto-trace-rotate.sh +88 -0
  243. core/hooks/block-bad-patterns.sh +212 -0
  244. core/hooks/block-dangerous-commands.sh +182 -0
  245. core/hooks/block-hardcoded-literals.sh +90 -0
  246. core/hooks/block-migration-conflict.sh +114 -0
  247. core/hooks/block-protected-files.sh +129 -0
  248. core/hooks/block-secrets.sh +185 -0
  249. core/hooks/block-shared-tree-edit.sh +75 -0
  250. core/hooks/block-uv-heredoc.sh +78 -0
  251. core/hooks/branch-guard.sh +122 -0
  252. core/hooks/capture-observation.sh +76 -0
  253. core/hooks/capture-tool-failure.sh +24 -0
  254. core/hooks/capture-work-log.sh +89 -0
  255. core/hooks/check-agents-md-refs.sh +75 -0
  256. core/hooks/check-agents-md-size.sh +49 -0
  257. core/hooks/check-capture-worked.sh +148 -0
  258. core/hooks/check-doc-size.sh +61 -0
  259. core/hooks/check-mcp-extras.sh +92 -0
  260. core/hooks/check-state.sh +87 -0
  261. core/hooks/classify-task-mode.sh +103 -0
  262. core/hooks/cos-env.sh +1301 -0
  263. core/hooks/drain-embedding-outbox.sh +24 -0
  264. core/hooks/enforce-anti-ambiguity.sh +74 -0
  265. core/hooks/enforce-commit-message.sh +73 -0
  266. core/hooks/enforce-doc-anchor.sh +224 -0
  267. core/hooks/enforce-doc-sync.sh +206 -0
  268. core/hooks/enforce-graph-context.sh +89 -0
  269. core/hooks/enforce-graph-first-read.sh +94 -0
  270. core/hooks/enforce-memory-check.sh +128 -0
  271. core/hooks/enforce-rename-plan.sh +78 -0
  272. core/hooks/enforce-scaffold-boundary.sh +68 -0
  273. core/hooks/enforce-skill.sh +125 -0
  274. core/hooks/enforce-task-body.sh +52 -0
  275. core/hooks/enforce-task-start.sh +81 -0
  276. core/hooks/enforce-task-transition.sh +75 -0
  277. core/hooks/enforce-template.sh +143 -0
  278. core/hooks/enforce-verify.sh +112 -0
  279. core/hooks/enforce-wip-limit.sh +43 -0
  280. core/hooks/enforce-zoom.sh +69 -0
  281. core/hooks/ensure-hub-up.sh +67 -0
  282. core/hooks/inject-mcp-caller-session.sh +70 -0
  283. core/hooks/jit-recall.sh +65 -0
  284. core/hooks/link-commit-to-task.sh +143 -0
  285. core/hooks/lint-task.sh +40 -0
  286. core/hooks/nudge-docs-first.sh +71 -0
  287. core/hooks/nudge-git-mode.sh +29 -0
  288. core/hooks/nudge-graph-os.sh +118 -0
  289. core/hooks/nudge-learn-narrative.sh +36 -0
  290. core/hooks/nudge-model-routing.sh +32 -0
  291. core/hooks/nudge-reentry.sh +101 -0
  292. core/hooks/nudge-reuse-first.sh +68 -0
  293. core/hooks/nudge-task-discovery.sh +81 -0
  294. core/hooks/nudge-thinking-os.sh +109 -0
  295. core/hooks/pr-reap.sh +23 -0
  296. core/hooks/reclaim-sweep.sh +58 -0
  297. core/hooks/record-verify-auto.sh +77 -0
  298. core/hooks/record-verify.sh +74 -0
  299. core/hooks/regen-reminder.sh +104 -0
  300. core/hooks/registry.yaml +1262 -0
  301. core/hooks/remind-daily.sh +27 -0
  302. core/hooks/remind-dogfood.sh +70 -0
  303. core/hooks/remind-learn-validate.sh +94 -0
  304. core/hooks/rules-primer.sh +50 -0
  305. core/hooks/search-enforce-inventory.sh +108 -0
  306. core/hooks/search-verify-remaining.sh +132 -0
  307. core/hooks/session-context.sh +729 -0
  308. core/hooks/session-end.sh +145 -0
  309. core/hooks/session-skill-primer.sh +43 -0
  310. core/hooks/snapshot-transcript.sh +56 -0
  311. core/hooks/sync-task-current.sh +85 -0
  312. core/hooks/test-first-reminder.sh +120 -0
  313. core/hooks/test-governor.sh +173 -0
  314. core/hooks/thinking_os-gate.sh +52 -0
  315. core/hooks/track-backtrack.sh +35 -0
  316. core/hooks/track-discovery.sh +121 -0
  317. core/hooks/track-skill.sh +53 -0
  318. core/hooks/validate-task-frontmatter.sh +49 -0
  319. core/hooks/verify-rename-callers.sh +119 -0
  320. core/hooks/warn-abandoned-task.sh +99 -0
  321. core/hooks/warn-destructive-edit.sh +64 -0
  322. core/hooks/warn-diff-size.sh +43 -0
  323. core/hooks/warn-graph-empty.sh +81 -0
  324. core/hooks/warn-mcp-down.sh +190 -0
  325. core/hooks/write-state.sh +55 -0
  326. core/logging_os/__init__.py +33 -0
  327. core/logging_os/api.py +127 -0
  328. core/logging_os/bridge.py +80 -0
  329. core/logging_os/config.py +172 -0
  330. core/logging_os/fingerprint.py +25 -0
  331. core/logging_os/redact.py +53 -0
  332. core/logging_os/render.py +83 -0
  333. core/logging_os/sinks.py +164 -0
  334. core/rules/anti-overengineering.md +44 -0
  335. core/rules/api-contract-discipline.md +41 -0
  336. core/rules/dimension-registry.md +155 -0
  337. core/rules/git-workflow.md +57 -0
  338. core/rules/memory.md +46 -0
  339. core/rules/model-routing.md +22 -0
  340. core/rules/skill-enforcement.md +75 -0
  341. core/rules/test-discipline.md +38 -0
  342. core/rules/thinking_os.md +48 -0
  343. core/rules/transparency-banner.md +37 -0
  344. core/runtime_paths.yaml +36 -0
  345. core/scaffold_manifest.json +14430 -0
  346. core/scheduled/__init__.py +0 -0
  347. core/scheduled/_activity.py +126 -0
  348. core/scheduled/_state.py +113 -0
  349. core/scheduled/config.py +86 -0
  350. core/scheduled/dep_reconcile.py +135 -0
  351. core/scheduled/error_sweep.py +137 -0
  352. core/scheduled/nightly.py +930 -0
  353. core/scheduled/responsive_extract.py +65 -0
  354. core/schemas/adapter.schema.json +269 -0
  355. core/schemas/preset.schema.json +50 -0
  356. core/schemas/skill.schema.json +81 -0
  357. core/schemas/stack.schema.json +404 -0
  358. core/scripts/_lib.sh +9 -0
  359. core/scripts/docs-lint.sh +228 -0
  360. core/scripts/docs-nav-fix.sh +133 -0
  361. core/scripts/docs-staleness-check.sh +154 -0
  362. core/scripts/install-adapter.sh +266 -0
  363. core/scripts/link-stack-skills.sh +52 -0
  364. core/scripts/log-latest.sh +106 -0
  365. core/scripts/log-search.sh +89 -0
  366. core/scripts/log-write.sh +134 -0
  367. core/scripts/ref-resolve.sh +71 -0
  368. core/skills/a11y/SKILL.md +305 -0
  369. core/skills/a11y/assets/a11y-checklist.md +137 -0
  370. core/skills/a11y/references/aria-and-focus.md +247 -0
  371. core/skills/a11y/references/rn-accessibility.md +343 -0
  372. core/skills/a11y/references/screen-reader-testing.md +190 -0
  373. core/skills/agent-memory/SKILL.md +191 -0
  374. core/skills/agent-memory/assets/memory-checklist.md +21 -0
  375. core/skills/agent-memory/references/memory-recipes.md +57 -0
  376. core/skills/api-design/SKILL.md +232 -0
  377. core/skills/api-design/assets/api-design-checklist.md +110 -0
  378. core/skills/api-design/references/error-envelope.md +381 -0
  379. core/skills/api-design/references/idempotency-pagination.md +312 -0
  380. core/skills/api-design/references/rest-contracts.md +426 -0
  381. core/skills/auth-patterns/SKILL.md +352 -0
  382. core/skills/auth-patterns/assets/auth-checklist.md +118 -0
  383. core/skills/auth-patterns/references/jwt-and-service-tokens.md +343 -0
  384. core/skills/auth-patterns/references/passkeys-2fa.md +289 -0
  385. core/skills/auth-patterns/references/sessions-vs-jwt.md +230 -0
  386. core/skills/auth-patterns/scripts/cookie-flag-check.py +146 -0
  387. core/skills/backend-fundamentals/SKILL.md +238 -0
  388. core/skills/backend-fundamentals/assets/backend-checklist.md +27 -0
  389. core/skills/backend-fundamentals/references/backend-patterns.md +56 -0
  390. core/skills/backend-fundamentals/scripts/check_layering.py +83 -0
  391. core/skills/clean-code/SKILL.md +642 -0
  392. core/skills/clean-code/scripts/audit-fail-closed.py +167 -0
  393. core/skills/codebase-explorer/SKILL.md +89 -0
  394. core/skills/codebase-explorer/assets/reading-checklist.md +24 -0
  395. core/skills/codebase-explorer/references/reading-strategies.md +52 -0
  396. core/skills/codebase-explorer/scripts/outline.py +99 -0
  397. core/skills/db-design/SKILL.md +327 -0
  398. core/skills/db-design/assets/migration-template.sql +49 -0
  399. core/skills/db-design/references/migration-discipline.md +290 -0
  400. core/skills/db-design/references/postgres-patterns.md +340 -0
  401. core/skills/db-design/scripts/migration-safety.sh +150 -0
  402. core/skills/deployment-cicd/SKILL.md +260 -0
  403. core/skills/deployment-cicd/assets/deploy-checklist.md +26 -0
  404. core/skills/deployment-cicd/references/pipeline-and-release.md +54 -0
  405. core/skills/deployment-cicd/scripts/lint_workflow.py +79 -0
  406. core/skills/docker/SKILL.md +114 -0
  407. core/skills/docker/assets/dockerfile-checklist.md +31 -0
  408. core/skills/docker/references/compose-patterns.md +66 -0
  409. core/skills/docker/references/dockerfile-optimization.md +64 -0
  410. core/skills/docker/scripts/lint_dockerfile.sh +48 -0
  411. core/skills/docker/versions.json +16 -0
  412. core/skills/end-to-end-testing/SKILL.md +101 -0
  413. core/skills/end-to-end-testing/assets/e2e-checklist.md +23 -0
  414. core/skills/end-to-end-testing/references/maestro.md +63 -0
  415. core/skills/end-to-end-testing/references/playwright.md +68 -0
  416. core/skills/end-to-end-testing/scripts/lint_e2e.py +88 -0
  417. core/skills/end-to-end-testing/versions.json +16 -0
  418. core/skills/frontend-design/SKILL.md +76 -0
  419. core/skills/frontend-design/assets/design-checklist.md +29 -0
  420. core/skills/frontend-design/references/design-principles.md +65 -0
  421. core/skills/frontend-design/scripts/check_contrast.py +89 -0
  422. core/skills/frontend-fundamentals/SKILL.md +213 -0
  423. core/skills/frontend-fundamentals/assets/frontend-checklist.md +25 -0
  424. core/skills/frontend-fundamentals/references/rendering-and-state.md +66 -0
  425. core/skills/frontend-fundamentals/scripts/check_frontend.py +86 -0
  426. core/skills/graph-explorer/SKILL.md +215 -0
  427. core/skills/graph-explorer/scripts/explain-impact.sh +64 -0
  428. core/skills/graphql/SKILL.md +187 -0
  429. core/skills/grpc-microservices/SKILL.md +174 -0
  430. core/skills/hexagonal-architecture/SKILL.md +199 -0
  431. core/skills/hexagonal-architecture/assets/folder-scaffold.md +233 -0
  432. core/skills/hexagonal-architecture/references/anti-patterns.md +129 -0
  433. core/skills/hexagonal-architecture/references/go-fiber-layout.md +429 -0
  434. core/skills/hexagonal-architecture/references/python-fastapi-layout.md +453 -0
  435. core/skills/hexagonal-architecture/references/react-native-layout.md +428 -0
  436. core/skills/i18n/SKILL.md +126 -0
  437. core/skills/incident-response/SKILL.md +225 -0
  438. core/skills/incident-response/assets/incident-checklist.md +29 -0
  439. core/skills/incident-response/references/severity-and-runbook.md +53 -0
  440. core/skills/incident-response/scripts/classify_severity.py +86 -0
  441. core/skills/linux-sysadmin/SKILL.md +115 -0
  442. core/skills/linux-sysadmin/assets/hardening-checklist.md +29 -0
  443. core/skills/linux-sysadmin/references/ssh-hardening.md +62 -0
  444. core/skills/linux-sysadmin/references/systemd-and-services.md +77 -0
  445. core/skills/linux-sysadmin/scripts/triage.sh +50 -0
  446. core/skills/linux-sysadmin/versions.json +17 -0
  447. core/skills/llm-patterns/SKILL.md +410 -0
  448. core/skills/llm-patterns/assets/llm-feature-checklist.md +26 -0
  449. core/skills/llm-patterns/references/rag-and-evals.md +59 -0
  450. core/skills/llm-patterns/scripts/estimate_tokens.py +75 -0
  451. core/skills/messaging-queues/SKILL.md +142 -0
  452. core/skills/mobile-fundamentals/SKILL.md +406 -0
  453. core/skills/mobile-fundamentals/assets/mobile-launch-checklist.md +130 -0
  454. core/skills/mobile-fundamentals/references/navigation-and-deep-links.md +337 -0
  455. core/skills/mobile-fundamentals/references/offline-sync.md +339 -0
  456. core/skills/node-backend/SKILL.md +114 -0
  457. core/skills/node-backend/assets/node-checklist.md +25 -0
  458. core/skills/node-backend/references/async-and-errors.md +67 -0
  459. core/skills/node-backend/references/event-loop.md +65 -0
  460. core/skills/node-backend/scripts/check_package.py +78 -0
  461. core/skills/node-backend/versions.json +17 -0
  462. core/skills/observability/SKILL.md +289 -0
  463. core/skills/observability/assets/observability-checklist.md +27 -0
  464. core/skills/observability/references/instrumentation.md +57 -0
  465. core/skills/observability/scripts/lint_logging.py +77 -0
  466. core/skills/payments/SKILL.md +102 -0
  467. core/skills/performance/SKILL.md +305 -0
  468. core/skills/performance/assets/perf-checklist.md +143 -0
  469. core/skills/performance/references/mobile-performance.md +249 -0
  470. core/skills/performance/references/web-vitals.md +209 -0
  471. core/skills/php/SKILL.md +116 -0
  472. core/skills/php/assets/php-checklist.md +26 -0
  473. core/skills/php/references/modern-php.md +64 -0
  474. core/skills/php/references/security.md +72 -0
  475. core/skills/php/scripts/scan_php_smells.py +99 -0
  476. core/skills/php/versions.json +9 -0
  477. core/skills/pr-mode-driver/SKILL.md +65 -0
  478. core/skills/realtime-websockets/SKILL.md +152 -0
  479. core/skills/redis/SKILL.md +105 -0
  480. core/skills/redis/assets/redis-checklist.md +27 -0
  481. core/skills/redis/references/operations.md +66 -0
  482. core/skills/redis/references/patterns.md +62 -0
  483. core/skills/redis/scripts/analyze_info.py +101 -0
  484. core/skills/redis/versions.json +9 -0
  485. core/skills/search/SKILL.md +91 -0
  486. core/skills/search/references/grep.md +76 -0
  487. core/skills/search/scripts/verify-count.sh +73 -0
  488. core/skills/search-infra/SKILL.md +109 -0
  489. core/skills/security-mobile/SKILL.md +394 -0
  490. core/skills/security-mobile/assets/mobile-security-checklist.md +117 -0
  491. core/skills/security-mobile/references/masvs-l1-checklist.md +127 -0
  492. core/skills/security-web/SKILL.md +217 -0
  493. core/skills/security-web/assets/security-web-checklist.md +167 -0
  494. core/skills/security-web/references/owasp-top-10.md +551 -0
  495. core/skills/security-web/references/supply-chain.md +179 -0
  496. core/skills/security-web/scripts/csp-check.sh +153 -0
  497. core/skills/shell-scripting/SKILL.md +128 -0
  498. core/skills/shell-scripting/assets/script-checklist.md +33 -0
  499. core/skills/shell-scripting/references/argument-parsing.md +84 -0
  500. core/skills/shell-scripting/references/bash-robustness.md +78 -0
  501. core/skills/shell-scripting/scripts/lint_script.sh +53 -0
  502. core/skills/shell-scripting/scripts/new_script.py +131 -0
  503. core/skills/shell-scripting/versions.json +23 -0
  504. core/skills/sql-authoring/SKILL.md +104 -0
  505. core/skills/sql-authoring/assets/query-review-checklist.md +26 -0
  506. core/skills/sql-authoring/references/query-patterns.md +89 -0
  507. core/skills/sql-authoring/references/reading-explain.md +52 -0
  508. core/skills/sql-authoring/scripts/analyze_plan.py +100 -0
  509. core/skills/sql-authoring/versions.json +17 -0
  510. core/skills/state-management/SKILL.md +428 -0
  511. core/skills/state-management/references/tanstack-query-recipes.md +301 -0
  512. core/skills/state-management/references/zustand-recipes.md +364 -0
  513. core/skills/supabase/SKILL.md +108 -0
  514. core/skills/supabase/assets/supabase-checklist.md +26 -0
  515. core/skills/supabase/references/realtime-and-storage.md +55 -0
  516. core/skills/supabase/references/rls-and-auth.md +65 -0
  517. core/skills/supabase/scripts/check_rls.py +88 -0
  518. core/skills/supabase/versions.json +9 -0
  519. core/skills/task-driver/SKILL.md +272 -0
  520. core/skills/task-driver/scripts/task-lint.sh +163 -0
  521. core/skills/technical-writing/SKILL.md +85 -0
  522. core/skills/technical-writing/assets/doc-checklist.md +29 -0
  523. core/skills/technical-writing/references/doc-anatomy.md +53 -0
  524. core/skills/technical-writing/references/writing-craft.md +59 -0
  525. core/skills/technical-writing/scripts/new_doc.py +81 -0
  526. core/skills/terraform-k8s/SKILL.md +133 -0
  527. core/skills/testing-strategy/SKILL.md +266 -0
  528. core/skills/testing-strategy/assets/test-review-checklist.md +25 -0
  529. core/skills/testing-strategy/references/test-types.md +52 -0
  530. core/skills/testing-strategy/scripts/coverage_gate.py +79 -0
  531. core/skills/thinking_os/SKILL.md +288 -0
  532. core/skills/thinking_os/scripts/classify.sh +122 -0
  533. core/skills/typescript/SKILL.md +110 -0
  534. core/skills/typescript/assets/typescript-checklist.md +24 -0
  535. core/skills/typescript/references/strictness.md +53 -0
  536. core/skills/typescript/references/type-system.md +86 -0
  537. core/skills/typescript/scripts/check_tsconfig.py +92 -0
  538. core/skills/typescript/versions.json +9 -0
  539. core/subsystems.yaml +202 -0
  540. core/thinking_os/__init__.py +1 -0
  541. core/thinking_os/_agent_markers.py +32 -0
  542. core/thinking_os/agents/README.md +71 -0
  543. core/thinking_os/agents/analyst.md +139 -0
  544. core/thinking_os/agents/architect.md +127 -0
  545. core/thinking_os/agents/debugger.md +111 -0
  546. core/thinking_os/agents/deployer.md +112 -0
  547. core/thinking_os/agents/distiller.md +28 -0
  548. core/thinking_os/agents/documenter.md +101 -0
  549. core/thinking_os/agents/implementer.md +135 -0
  550. core/thinking_os/agents/internal/session_observer.md +39 -0
  551. core/thinking_os/agents/observer.md +100 -0
  552. core/thinking_os/agents/onboarder.md +81 -0
  553. core/thinking_os/agents/refactorer.md +123 -0
  554. core/thinking_os/agents/repairer.md +47 -0
  555. core/thinking_os/agents/researcher.md +129 -0
  556. core/thinking_os/agents/reviewer.md +147 -0
  557. core/thinking_os/agents/security_auditor.md +133 -0
  558. core/thinking_os/background.py +405 -0
  559. core/thinking_os/bootstrap_outcomes.py +200 -0
  560. core/thinking_os/budget.py +302 -0
  561. core/thinking_os/capture.py +495 -0
  562. core/thinking_os/cognition.py +516 -0
  563. core/thinking_os/cognition_schemas.py +517 -0
  564. core/thinking_os/compress.py +192 -0
  565. core/thinking_os/concepts.py +233 -0
  566. core/thinking_os/dashboard.py +159 -0
  567. core/thinking_os/database.py +2883 -0
  568. core/thinking_os/decay.py +393 -0
  569. core/thinking_os/digest.py +295 -0
  570. core/thinking_os/dispatcher.py +192 -0
  571. core/thinking_os/dispatcher_helpers.py +48 -0
  572. core/thinking_os/dispatchers/__init__.py +3 -0
  573. core/thinking_os/dispatchers/default.py +47 -0
  574. core/thinking_os/distill.py +192 -0
  575. core/thinking_os/doc_indexer.py +905 -0
  576. core/thinking_os/embeddings.py +943 -0
  577. core/thinking_os/formula_composer.py +556 -0
  578. core/thinking_os/gate_marker.py +75 -0
  579. core/thinking_os/graph.py +296 -0
  580. core/thinking_os/graph_indexer.py +360 -0
  581. core/thinking_os/health_check.py +517 -0
  582. core/thinking_os/impact.py +119 -0
  583. core/thinking_os/memory_gc.py +356 -0
  584. core/thinking_os/migrator_embeddings.py +318 -0
  585. core/thinking_os/precision.py +194 -0
  586. core/thinking_os/presets/registry.yaml +127 -0
  587. core/thinking_os/record_outcome.py +399 -0
  588. core/thinking_os/repair.py +105 -0
  589. core/thinking_os/retrieval_quality.py +239 -0
  590. core/thinking_os/roles/analyst.yaml +83 -0
  591. core/thinking_os/roles/architect.yaml +88 -0
  592. core/thinking_os/roles/debugger.yaml +71 -0
  593. core/thinking_os/roles/deployer.yaml +71 -0
  594. core/thinking_os/roles/documenter.yaml +72 -0
  595. core/thinking_os/roles/implementer.yaml +83 -0
  596. core/thinking_os/roles/observer.yaml +70 -0
  597. core/thinking_os/roles/refactorer.yaml +71 -0
  598. core/thinking_os/roles/researcher.yaml +72 -0
  599. core/thinking_os/roles/reviewer.yaml +79 -0
  600. core/thinking_os/roles/security_auditor.yaml +83 -0
  601. core/thinking_os/roles_state.py +168 -0
  602. core/thinking_os/sanitizer.py +320 -0
  603. core/thinking_os/server.py +3160 -0
  604. core/thinking_os/session_enrich.py +272 -0
  605. core/thinking_os/session_observe_worker.py +111 -0
  606. core/thinking_os/session_startup.py +74 -0
  607. core/thinking_os/session_summary.py +245 -0
  608. core/thinking_os/situations/registry.yaml +99 -0
  609. core/thinking_os/task_analyzer.py +462 -0
  610. core/thinking_os/task_parser.py +342 -0
  611. core/thinking_os/task_sync.py +73 -0
  612. core/thinking_os/tools/__init__.py +6 -0
  613. core/thinking_os/tools/_shared.py +947 -0
  614. core/thinking_os/tools/cognition.py +1867 -0
  615. core/thinking_os/tools/docs.py +770 -0
  616. core/thinking_os/tools/learning.py +2078 -0
  617. core/thinking_os/tools/logs.py +79 -0
  618. core/thinking_os/tools/memory.py +840 -0
  619. core/thinking_os/tools/metrics.py +200 -0
  620. core/thinking_os/tools/retrieve.py +415 -0
  621. core/thinking_os/tools/routing.py +658 -0
  622. core/thinking_os/tools/tasks.py +449 -0
  623. core/thinking_os/tools/trajectory.py +181 -0
  624. core/thinking_os/tracing.py +235 -0
  625. core/web/__init__.py +5 -0
  626. core/web/_cache.py +118 -0
  627. core/web/_deps.py +56 -0
  628. core/web/_envelope.py +85 -0
  629. core/web/_project_context.py +140 -0
  630. core/web/chat_providers.py +108 -0
  631. core/web/init_jobs.py +216 -0
  632. core/web/routes/__init__.py +25 -0
  633. core/web/routes/_bounded_read.py +76 -0
  634. core/web/routes/board.py +1089 -0
  635. core/web/routes/cognition.py +1838 -0
  636. core/web/routes/config.py +635 -0
  637. core/web/routes/graph.py +513 -0
  638. core/web/routes/health.py +180 -0
  639. core/web/routes/hooks.py +288 -0
  640. core/web/routes/hub.py +1219 -0
  641. core/web/routes/logs.py +374 -0
  642. core/web/routes/metrics.py +43 -0
  643. core/web/routes/observability.py +400 -0
  644. core/web/routes/patterns.py +227 -0
  645. core/web/routes/presence.py +609 -0
  646. core/web/routes/roles.py +446 -0
  647. core/web/routes/scheduled.py +261 -0
  648. core/web/routes/search.py +238 -0
  649. core/web/routes/sessions.py +220 -0
  650. core/web/routes/settings.py +363 -0
  651. core/web/routes/stream.py +547 -0
  652. core/web/security.py +157 -0
  653. core/web/server.py +283 -0
  654. graph_os/__init__.py +29 -0
  655. graph_os/backend.py +233 -0
  656. graph_os/backends/__init__.py +13 -0
  657. graph_os/backends/sqlite_backend.py +1053 -0
  658. graph_os/communities.py +410 -0
  659. graph_os/enterprise.py +218 -0
  660. graph_os/entry_points.py +226 -0
  661. graph_os/extractors/__init__.py +25 -0
  662. graph_os/extractors/code_generic.py +914 -0
  663. graph_os/extractors/code_go.py +1422 -0
  664. graph_os/extractors/code_json.py +340 -0
  665. graph_os/extractors/code_php.py +979 -0
  666. graph_os/extractors/code_python.py +1454 -0
  667. graph_os/extractors/code_shell.py +538 -0
  668. graph_os/extractors/code_toml.py +302 -0
  669. graph_os/extractors/code_ts.py +1665 -0
  670. graph_os/extractors/code_yaml.py +394 -0
  671. graph_os/extractors/contracts.py +1592 -0
  672. graph_os/extractors/md_links.py +890 -0
  673. graph_os/extractors/task_deps.py +345 -0
  674. graph_os/groups/__init__.py +22 -0
  675. graph_os/groups/cross_repo.py +156 -0
  676. graph_os/groups/manifest.py +141 -0
  677. graph_os/ingest/__init__.py +19 -0
  678. graph_os/ingest/base.py +306 -0
  679. graph_os/ingest/github.py +112 -0
  680. graph_os/ingest/zip.py +95 -0
  681. graph_os/toolchain.py +393 -0
  682. graph_os/tools/__init__.py +9 -0
  683. graph_os/tools/graph.py +5573 -0
  684. graph_os/tools/reindex_dispatch.py +730 -0
  685. graph_os/tree_sitter_overlay.py +235 -0
  686. graph_os/types.py +252 -0
  687. graph_os/vec_index.py +277 -0
  688. graph_os/viewer/__init__.py +12 -0
  689. graph_os/viewer/exporter.py +93 -0
  690. graph_os/viewer/template.py +189 -0
  691. scheduled/__init__.py +0 -0
  692. scheduled/_activity.py +126 -0
  693. scheduled/_state.py +113 -0
  694. scheduled/config.py +86 -0
  695. scheduled/dep_reconcile.py +135 -0
  696. scheduled/error_sweep.py +137 -0
  697. scheduled/nightly.py +930 -0
  698. scheduled/responsive_extract.py +65 -0
  699. scripts/__init__.py +4 -0
  700. scripts/_commit_msg_body.sh +31 -0
  701. scripts/_post_commit_body.sh +49 -0
  702. scripts/_pre_commit_body.sh +121 -0
  703. scripts/_prepare_commit_msg_body.sh +53 -0
  704. scripts/audit_mcp_tools.py +693 -0
  705. scripts/bench_sdk_dispatcher.py +177 -0
  706. scripts/capture_golden.py +169 -0
  707. scripts/check_graph_phantoms.py +75 -0
  708. scripts/dev/audit_doc_links.py +359 -0
  709. scripts/dev/audit_scaffold_module_tags.py +107 -0
  710. scripts/dev/backfill_doc_headers.py +328 -0
  711. scripts/dev/backfill_nav_lines.py +119 -0
  712. scripts/dev/fix_nav_placement.py +106 -0
  713. scripts/dev/inspect_sdk_options.py +45 -0
  714. scripts/dev/migrate_check_ids.py +170 -0
  715. scripts/dev/strip_purpose_blocks.py +154 -0
  716. scripts/dump_openapi.py +66 -0
  717. scripts/e2e_dispatch_tool.py +195 -0
  718. scripts/generate_manifest.py +166 -0
  719. scripts/golden_sections.py +20 -0
  720. scripts/graph_demo.py +161 -0
  721. scripts/install-git-hooks.sh +47 -0
  722. scripts/migrate_embeddings_minilm_to_bge_m3.py +84 -0
  723. scripts/operational_eval.py +445 -0
  724. scripts/probe_agent_session_resolver.py +59 -0
  725. scripts/prune_deleted_path.py +127 -0
  726. scripts/refactor_agent_dual_mode.py +171 -0
  727. scripts/refresh_skill_versions.py +302 -0
  728. scripts/regen_doc_index.py +209 -0
  729. scripts/regen_doctor_schema.py +62 -0
  730. scripts/regen_rules.py +94 -0
  731. scripts/rename_formulas_to_semantic.py +241 -0
  732. scripts/smoke_db_connections.py +183 -0
  733. scripts/smoke_doc_header.py +72 -0
  734. scripts/smoke_graph_e2e.py +374 -0
  735. scripts/smoke_sdk_dispatch.py +84 -0
  736. scripts/smoke_uid_resolver.py +164 -0
  737. scripts/verify_dispatchers.py +244 -0
  738. scripts/verify_phase_c_e2e.py +436 -0
  739. templates/__init__.py +6 -0
  740. templates/_base/Makefile.base +353 -0
  741. templates/_base/base.yaml +59 -0
  742. templates/_base/coding-os.yaml.template +39 -0
  743. templates/_base/dimension-registry.template.md +68 -0
  744. templates/_base/domain-config.template.json +46 -0
  745. templates/_base/fragments/anatomy-map.md.tmpl +11 -0
  746. templates/_base/fragments/context-discipline.md.tmpl +3 -0
  747. templates/_base/fragments/core-loop.md.tmpl +52 -0
  748. templates/_base/fragments/engineering-routing.md.tmpl +3 -0
  749. templates/_base/fragments/header.md.tmpl +6 -0
  750. templates/_base/fragments/identity.md.tmpl +3 -0
  751. templates/_base/fragments/principles.md.tmpl +3 -0
  752. templates/_base/fragments/retrieval-routing.md.tmpl +23 -0
  753. templates/_base/fragments/session-handoff.md.tmpl +3 -0
  754. templates/_base/fragments/skills.md.tmpl +3 -0
  755. templates/_base/fragments/ssot-map.md.tmpl +3 -0
  756. templates/_base/fragments/stop-conditions.md.tmpl +3 -0
  757. templates/_base/fragments/subagent-dispatch.md.tmpl +3 -0
  758. templates/_base/fragments/task-authoring.md.tmpl +69 -0
  759. templates/_base/fragments/task-logging.md.tmpl +8 -0
  760. templates/_base/fragments/tool-routing.md.tmpl +9 -0
  761. templates/_base/fragments/verification-matrix.md.tmpl +12 -0
  762. templates/_base/lang/dart/analysis_options.yaml +7 -0
  763. templates/_base/lang/php/phpcs.xml.dist +10 -0
  764. templates/_base/lang/python/pyproject.toml +19 -0
  765. templates/_base/lang/rust/clippy.toml +5 -0
  766. templates/_base/lang/rust/rustfmt.toml +3 -0
  767. templates/_base/lang/typescript/eslint.config.js +26 -0
  768. templates/_base/lang/typescript/tsconfig.json +15 -0
  769. templates/_base/lang/typescript/vitest.config.ts +10 -0
  770. templates/_base/scaffold/changes.log +1 -0
  771. templates/_base/scaffold/docs/00-index.md +55 -0
  772. templates/_base/scaffold/docs/_meta/feature-dependency-tree.md +30 -0
  773. templates/_base/scaffold/docs/_meta/foundation-map.md +56 -0
  774. templates/_base/scaffold/docs/_meta/questions.md +8 -0
  775. templates/_base/scaffold/docs/_meta/roadmap.md +33 -0
  776. templates/_base/scaffold/docs/api-contracts/00-index.md +58 -0
  777. templates/_base/scaffold/docs/api-contracts/error-format.md +58 -0
  778. templates/_base/scaffold/docs/architecture/00-index.md +42 -0
  779. templates/_base/scaffold/docs/architecture/adr/00-index.md +39 -0
  780. templates/_base/scaffold/docs/engineering/00-index.md +9 -0
  781. templates/_base/scaffold/docs/governance/00-index.md +55 -0
  782. templates/_base/scaffold/docs/governance/_templates/doc-cheat-sheet.md +202 -0
  783. templates/_base/scaffold/docs/governance/_templates/playbook-template.md +81 -0
  784. templates/_base/scaffold/docs/governance/_templates/post-mortem-template.md +85 -0
  785. templates/_base/scaffold/docs/governance/_templates/runbook-template.md +88 -0
  786. templates/_base/scaffold/docs/governance/_templates/security-review-template.md +111 -0
  787. templates/_base/scaffold/docs/governance/_templates/task-detail.md +61 -0
  788. templates/_base/scaffold/docs/governance/agent-workflow.md +101 -0
  789. templates/_base/scaffold/docs/governance/anatomy-contract.md +150 -0
  790. templates/_base/scaffold/docs/governance/critical-rules.md +224 -0
  791. templates/_base/scaffold/docs/governance/decision-records.md +58 -0
  792. templates/_base/scaffold/docs/governance/docs-first-protocol.md +157 -0
  793. templates/_base/scaffold/docs/governance/docs-system.md +151 -0
  794. templates/_base/scaffold/docs/governance/gdpr-compliance.md +66 -0
  795. templates/_base/scaffold/docs/governance/mcp-tool-inventory.md +112 -0
  796. templates/_base/scaffold/docs/governance/risk-register.md +26 -0
  797. templates/_base/scaffold/docs/governance/scaffold-boundary-contract.md +161 -0
  798. templates/_base/scaffold/docs/governance/task-lifecycle.md +125 -0
  799. templates/_base/scaffold/docs/governance/wrapper-derivation.md +50 -0
  800. templates/_base/scaffold/docs/insights/00-index.md +17 -0
  801. templates/_base/scaffold/docs/ops/00-index.md +59 -0
  802. templates/_base/scaffold/docs/ops/runbooks/00-index.md +9 -0
  803. templates/_base/scaffold/docs/playbooks/00-index.md +12 -0
  804. templates/_base/scaffold/docs/playbooks/research-validation.md +29 -0
  805. templates/_base/scaffold/docs/playbooks/security-review.md +41 -0
  806. templates/_base/scaffold/docs/prd/00-index.md +43 -0
  807. templates/_base/scaffold/docs/prd/01-snapshot-vision.md +56 -0
  808. templates/_base/scaffold/docs/workflow/workflow-guide.md +138 -0
  809. templates/_base/scaffold/src/shared/README.md +23 -0
  810. templates/_base/skill-enforcement.template.md +14 -0
  811. templates/_base/task-detail.template.md +61 -0
  812. templates/_presets/ai-saas.yaml +9 -0
  813. templates/_presets/django-next.yaml +8 -0
  814. templates/_presets/dotnet-react.yaml +8 -0
  815. templates/_presets/flutter-baas.yaml +8 -0
  816. templates/_presets/go-react.yaml +8 -0
  817. templates/_presets/hexagonal-product.yaml +16 -0
  818. templates/_presets/jamstack.yaml +8 -0
  819. templates/_presets/laravel-vue.yaml +8 -0
  820. templates/_presets/mean.yaml +8 -0
  821. templates/_presets/mern.yaml +9 -0
  822. templates/_presets/nest-angular.yaml +8 -0
  823. templates/_presets/nextjs-fastapi.yaml +10 -0
  824. templates/_presets/nuxt-fullstack.yaml +9 -0
  825. templates/_presets/pern.yaml +9 -0
  826. templates/_presets/rails-react.yaml +8 -0
  827. templates/_presets/rn-api.yaml +8 -0
  828. templates/_presets/rust-svelte.yaml +8 -0
  829. templates/_presets/spring-react.yaml +8 -0
  830. templates/_presets/t3-style.yaml +9 -0
  831. templates/_presets/tall.yaml +8 -0
  832. templates/_presets/wordpress-cms.yaml +8 -0
  833. templates/angular/rules/frontend.md +19 -0
  834. templates/angular/scaffold/docs/engineering/accessibility.md +46 -0
  835. templates/angular/scaffold/docs/engineering/angular-rules.md +35 -0
  836. templates/angular/scaffold/docs/playbooks/angular-app.md +42 -0
  837. templates/angular/scaffold/src/frontend/angular.json +52 -0
  838. templates/angular/scaffold/src/frontend/package.json +28 -0
  839. templates/angular/scaffold/src/frontend/src/app/app.component.ts +19 -0
  840. templates/angular/scaffold/src/frontend/src/app/app.config.ts +22 -0
  841. templates/angular/scaffold/src/frontend/src/app/app.routes.ts +8 -0
  842. templates/angular/scaffold/src/frontend/src/app/core/global-error-handler.ts +12 -0
  843. templates/angular/scaffold/src/frontend/src/app/health/health.component.ts +14 -0
  844. templates/angular/scaffold/src/frontend/src/app/health/health.service.spec.ts +16 -0
  845. templates/angular/scaffold/src/frontend/src/app/health/health.service.ts +10 -0
  846. templates/angular/scaffold/src/frontend/src/index.html +11 -0
  847. templates/angular/scaffold/src/frontend/src/main.ts +9 -0
  848. templates/angular/scaffold/src/frontend/src/styles.css +14 -0
  849. templates/angular/scaffold/src/frontend/tsconfig.app.json +8 -0
  850. templates/angular/scaffold/src/frontend/tsconfig.json +27 -0
  851. templates/angular/scaffold/src/frontend/tsconfig.spec.json +8 -0
  852. templates/angular/scaffold-boundary.yaml +27 -0
  853. templates/angular/skills/angular/SKILL.md +79 -0
  854. templates/angular/skills/angular/references/anatomy.md +69 -0
  855. templates/angular/stack.yaml +75 -0
  856. templates/aspnet-core/rules/backend.md +20 -0
  857. templates/aspnet-core/scaffold/docs/engineering/aspnet-core-rules.md +36 -0
  858. templates/aspnet-core/scaffold/docs/playbooks/aspnet-core-service.md +40 -0
  859. templates/aspnet-core/scaffold/src/backend/Backend.csproj +11 -0
  860. templates/aspnet-core/scaffold/src/backend/Backend.sln +27 -0
  861. templates/aspnet-core/scaffold/src/backend/Common/ExceptionHandlingMiddleware.cs +35 -0
  862. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthEndpoints.cs +10 -0
  863. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthService.cs +9 -0
  864. templates/aspnet-core/scaffold/src/backend/Program.cs +22 -0
  865. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/Backend.Tests.csproj +22 -0
  866. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/HealthServiceTests.cs +15 -0
  867. templates/aspnet-core/scaffold-boundary.yaml +24 -0
  868. templates/aspnet-core/skills/aspnet-core/SKILL.md +80 -0
  869. templates/aspnet-core/skills/aspnet-core/references/anatomy.md +65 -0
  870. templates/aspnet-core/stack.yaml +68 -0
  871. templates/astro/rules/frontend.md +20 -0
  872. templates/astro/scaffold/docs/engineering/astro-rules.md +40 -0
  873. templates/astro/scaffold/docs/playbooks/astro-app.md +57 -0
  874. templates/astro/scaffold/docs/playbooks/content-seo.md +37 -0
  875. templates/astro/scaffold/src/frontend/astro.config.mjs +10 -0
  876. templates/astro/scaffold/src/frontend/package.json +23 -0
  877. templates/astro/scaffold/src/frontend/src/components/Greeting.astro +13 -0
  878. templates/astro/scaffold/src/frontend/src/content/posts/hello.md +10 -0
  879. templates/astro/scaffold/src/frontend/src/content.config.ts +19 -0
  880. templates/astro/scaffold/src/frontend/src/lib/problem.test.ts +39 -0
  881. templates/astro/scaffold/src/frontend/src/lib/problem.ts +30 -0
  882. templates/astro/scaffold/src/frontend/src/pages/api/health.ts +13 -0
  883. templates/astro/scaffold/src/frontend/src/pages/index.astro +21 -0
  884. templates/astro/scaffold/src/frontend/tsconfig.json +9 -0
  885. templates/astro/scaffold/src/frontend/vitest.config.ts +10 -0
  886. templates/astro/scaffold-boundary.yaml +28 -0
  887. templates/astro/skills/astro/SKILL.md +71 -0
  888. templates/astro/skills/astro/references/anatomy.md +66 -0
  889. templates/astro/stack.yaml +75 -0
  890. templates/csharp-plain/scaffold/src/backend/Backend.csproj +12 -0
  891. templates/csharp-plain/scaffold/src/backend/Program.cs +1 -0
  892. templates/csharp-plain/scaffold-boundary.yaml +23 -0
  893. templates/csharp-plain/stack.yaml +50 -0
  894. templates/django/rules/backend.md +18 -0
  895. templates/django/scaffold/docs/engineering/anti-ambiguity.md +74 -0
  896. templates/django/scaffold/docs/engineering/backend-rules.md +133 -0
  897. templates/django/scaffold/docs/engineering/glossary.md +51 -0
  898. templates/django/scaffold/docs/engineering/logging-standards.md +107 -0
  899. templates/django/scaffold/docs/engineering/naming-conventions.md +68 -0
  900. templates/django/scaffold/docs/engineering/secrets-rotation-runbook.md +142 -0
  901. templates/django/scaffold/docs/playbooks/backend-api.md +119 -0
  902. templates/django/scaffold/src/backend/config/__init__.py +0 -0
  903. templates/django/scaffold/src/backend/config/settings.py +31 -0
  904. templates/django/scaffold/src/backend/config/urls.py +11 -0
  905. templates/django/scaffold/src/backend/config/wsgi.py +6 -0
  906. templates/django/scaffold/src/backend/manage.py +14 -0
  907. templates/django/scaffold/src/backend/pyproject.toml +37 -0
  908. templates/django/scaffold/src/backend/tests/test_health.py +4 -0
  909. templates/django/scaffold-boundary.yaml +25 -0
  910. templates/django/skills/python-django/SKILL.md +450 -0
  911. templates/django/skills/python-django/references/anatomy.md +117 -0
  912. templates/django/skills/python-django/scripts/new_endpoint.py +89 -0
  913. templates/django/stack.yaml +73 -0
  914. templates/fastapi/rules/backend.md +18 -0
  915. templates/fastapi/scaffold/docs/engineering/fastapi-rules.md +37 -0
  916. templates/fastapi/scaffold/docs/playbooks/fastapi-service.md +30 -0
  917. templates/fastapi/scaffold/src/backend/app/__init__.py +0 -0
  918. templates/fastapi/scaffold/src/backend/app/main.py +8 -0
  919. templates/fastapi/scaffold/src/backend/pyproject.toml +39 -0
  920. templates/fastapi/scaffold/src/backend/tests/test_health.py +11 -0
  921. templates/fastapi/scaffold-boundary.yaml +25 -0
  922. templates/fastapi/skills/python-fastapi/SKILL.md +75 -0
  923. templates/fastapi/skills/python-fastapi/references/anatomy.md +117 -0
  924. templates/fastapi/skills/python-fastapi/scripts/new_endpoint.py +101 -0
  925. templates/fastapi/stack.yaml +57 -0
  926. templates/flutter/rules/mobile.md +26 -0
  927. templates/flutter/scaffold/docs/engineering/flutter-rules.md +35 -0
  928. templates/flutter/scaffold/docs/playbooks/flutter-app.md +45 -0
  929. templates/flutter/scaffold/src/mobile/lib/core/error_mapper.dart +17 -0
  930. templates/flutter/scaffold/src/mobile/lib/core/router.dart +13 -0
  931. templates/flutter/scaffold/src/mobile/lib/main.dart +22 -0
  932. templates/flutter/scaffold/src/mobile/lib/screens/health_screen.dart +32 -0
  933. templates/flutter/scaffold/src/mobile/lib/services/health_service.dart +11 -0
  934. templates/flutter/scaffold/src/mobile/lib/state/health_provider.dart +13 -0
  935. templates/flutter/scaffold/src/mobile/pubspec.yaml +22 -0
  936. templates/flutter/scaffold/src/mobile/test/health_provider_test.dart +90 -0
  937. templates/flutter/scaffold-boundary.yaml +28 -0
  938. templates/flutter/skills/flutter/SKILL.md +76 -0
  939. templates/flutter/skills/flutter/references/anatomy.md +64 -0
  940. templates/flutter/stack.yaml +70 -0
  941. templates/go/rules/backend.md +19 -0
  942. templates/go/scaffold/docs/engineering/go-rules.md +45 -0
  943. templates/go/scaffold/docs/playbooks/go-service.md +30 -0
  944. templates/go/scaffold/src/backend/cmd/api/main.go +22 -0
  945. templates/go/scaffold/src/backend/cmd/api/main_test.go +20 -0
  946. templates/go/scaffold/src/backend/go.mod +3 -0
  947. templates/go/scaffold-boundary.yaml +25 -0
  948. templates/go/skills/go-patterns/SKILL.md +68 -0
  949. templates/go/skills/go-patterns/assets/go-checklist.md +29 -0
  950. templates/go/skills/go-patterns/references/anatomy.md +115 -0
  951. templates/go/skills/go-patterns/references/go-2026-idioms.md +92 -0
  952. templates/go/skills/go-patterns/scripts/new_endpoint.py +117 -0
  953. templates/go/skills/go-patterns/versions.json +16 -0
  954. templates/go/stack.yaml +54 -0
  955. templates/go-fiber/rules/backend.md +20 -0
  956. templates/go-fiber/scaffold/docs/engineering/fiber-rules.md +97 -0
  957. templates/go-fiber/scaffold/docs/playbooks/fiber-service.md +149 -0
  958. templates/go-fiber/scaffold/src/backend/cmd/api/main.go +21 -0
  959. templates/go-fiber/scaffold/src/backend/cmd/api/main_test.go +17 -0
  960. templates/go-fiber/scaffold/src/backend/go.mod +23 -0
  961. templates/go-fiber/scaffold/src/backend/go.sum +49 -0
  962. templates/go-fiber/scaffold-boundary.yaml +26 -0
  963. templates/go-fiber/skills/go-fiber/SKILL.md +203 -0
  964. templates/go-fiber/skills/go-fiber/assets/fiber-checklist.md +26 -0
  965. templates/go-fiber/skills/go-fiber/references/anatomy.md +116 -0
  966. templates/go-fiber/skills/go-fiber/references/fiber-v3-patterns.md +89 -0
  967. templates/go-fiber/skills/go-fiber/scripts/new_endpoint.py +107 -0
  968. templates/go-fiber/skills/go-fiber/versions.json +16 -0
  969. templates/go-fiber/stack.yaml +59 -0
  970. templates/go-plain/scaffold/src/backend/go.mod +3 -0
  971. templates/go-plain/scaffold/src/backend/main.go +7 -0
  972. templates/go-plain/scaffold-boundary.yaml +22 -0
  973. templates/go-plain/stack.yaml +51 -0
  974. templates/java-plain/scaffold/src/backend/mvnw +302 -0
  975. templates/java-plain/scaffold/src/backend/pom.xml +41 -0
  976. templates/java-plain/scaffold/src/backend/src/main/java/com/example/app/Main.java +10 -0
  977. templates/java-plain/scaffold-boundary.yaml +23 -0
  978. templates/java-plain/stack.yaml +51 -0
  979. templates/laravel/rules/backend.md +20 -0
  980. templates/laravel/scaffold/docs/engineering/laravel-rules.md +28 -0
  981. templates/laravel/scaffold/docs/playbooks/laravel-service.md +28 -0
  982. templates/laravel/scaffold/src/backend/app/Exceptions/Handler.php +27 -0
  983. templates/laravel/scaffold/src/backend/app/Http/Controllers/HealthController.php +15 -0
  984. templates/laravel/scaffold/src/backend/app/Support/HealthStatus.php +14 -0
  985. templates/laravel/scaffold/src/backend/composer.json +24 -0
  986. templates/laravel/scaffold/src/backend/phpunit.xml +10 -0
  987. templates/laravel/scaffold/src/backend/public/index.php +8 -0
  988. templates/laravel/scaffold/src/backend/routes/api.php +7 -0
  989. templates/laravel/scaffold/src/backend/tests/Unit/HealthStatusTest.php +16 -0
  990. templates/laravel/scaffold-boundary.yaml +23 -0
  991. templates/laravel/skills/laravel/SKILL.md +56 -0
  992. templates/laravel/skills/laravel/references/anatomy.md +65 -0
  993. templates/laravel/stack.yaml +66 -0
  994. templates/meta/rules/graph-first.md +27 -0
  995. templates/meta/rules/hook-author.md +19 -0
  996. templates/meta/rules/mcp-tool-author.md +18 -0
  997. templates/meta/rules/meta-engineering.md +17 -0
  998. templates/meta/scaffold-boundary.yaml +55 -0
  999. templates/meta/skills/claude-sdk-integration/SKILL.md +163 -0
  1000. templates/meta/skills/claude-sdk-integration/assets/sdk-checklist.md +27 -0
  1001. templates/meta/skills/claude-sdk-integration/scripts/check_model_ids.py +97 -0
  1002. templates/meta/skills/graph-os-authoring/SKILL.md +278 -0
  1003. templates/meta/skills/graph-os-authoring/assets/graph-os-checklist.md +25 -0
  1004. templates/meta/skills/graph-os-authoring/scripts/new_extractor.py +76 -0
  1005. templates/meta/skills/hook-authoring/SKILL.md +292 -0
  1006. templates/meta/skills/hook-authoring/assets/hook-checklist.md +30 -0
  1007. templates/meta/skills/hook-authoring/scripts/new_hook.sh +75 -0
  1008. templates/meta/skills/mcp-tool-authoring/SKILL.md +301 -0
  1009. templates/meta/skills/mcp-tool-authoring/assets/mcp-tool-checklist.md +29 -0
  1010. templates/meta/skills/mcp-tool-authoring/scripts/new_tool.py +74 -0
  1011. templates/meta/skills/meta-engineering/SKILL.md +151 -0
  1012. templates/meta/skills/meta-engineering/assets/meta-edit-checklist.md +28 -0
  1013. templates/meta/skills/meta-engineering/scripts/which_layer.py +61 -0
  1014. templates/meta/skills/python-meta-server/SKILL.md +162 -0
  1015. templates/meta/skills/python-meta-server/assets/meta-server-checklist.md +28 -0
  1016. templates/meta/skills/python-meta-server/scripts/check_envelope.py +91 -0
  1017. templates/meta/skills/react-vite-hub/SKILL.md +140 -0
  1018. templates/meta/skills/react-vite-hub/assets/hub-ui-checklist.md +23 -0
  1019. templates/meta/skills/react-vite-hub/scripts/check_vite_env.py +73 -0
  1020. templates/meta/stack.yaml +119 -0
  1021. templates/nestjs/rules/backend.md +20 -0
  1022. templates/nestjs/scaffold/docs/engineering/nestjs-rules.md +33 -0
  1023. templates/nestjs/scaffold/docs/playbooks/nestjs-service.md +39 -0
  1024. templates/nestjs/scaffold/src/backend/nest-cli.json +5 -0
  1025. templates/nestjs/scaffold/src/backend/package.json +28 -0
  1026. templates/nestjs/scaffold/src/backend/src/app.module.ts +9 -0
  1027. templates/nestjs/scaffold/src/backend/src/common/all-exceptions.filter.ts +59 -0
  1028. templates/nestjs/scaffold/src/backend/src/health/health.controller.ts +14 -0
  1029. templates/nestjs/scaffold/src/backend/src/health/health.module.ts +10 -0
  1030. templates/nestjs/scaffold/src/backend/src/health/health.service.spec.ts +21 -0
  1031. templates/nestjs/scaffold/src/backend/src/health/health.service.ts +9 -0
  1032. templates/nestjs/scaffold/src/backend/src/main.ts +26 -0
  1033. templates/nestjs/scaffold/src/backend/tsconfig.json +16 -0
  1034. templates/nestjs/scaffold/src/backend/vitest.config.ts +9 -0
  1035. templates/nestjs/scaffold-boundary.yaml +25 -0
  1036. templates/nestjs/skills/nestjs/SKILL.md +67 -0
  1037. templates/nestjs/skills/nestjs/references/anatomy.md +65 -0
  1038. templates/nestjs/stack.yaml +68 -0
  1039. templates/nextjs/rules/frontend.md +18 -0
  1040. templates/nextjs/scaffold/docs/design/00-index.md +23 -0
  1041. templates/nextjs/scaffold/docs/design/colors-tokens.md +141 -0
  1042. templates/nextjs/scaffold/docs/design/components-patterns.md +159 -0
  1043. templates/nextjs/scaffold/docs/design/motion-accessibility.md +137 -0
  1044. templates/nextjs/scaffold/docs/design/typography-spacing.md +107 -0
  1045. templates/nextjs/scaffold/docs/engineering/accessibility-web.md +56 -0
  1046. templates/nextjs/scaffold/docs/engineering/copywriting-standard.md +102 -0
  1047. templates/nextjs/scaffold/docs/engineering/formatting-rules.md +89 -0
  1048. templates/nextjs/scaffold/docs/engineering/frontend-rendering-rules.md +80 -0
  1049. templates/nextjs/scaffold/docs/engineering/frontend-rules.md +183 -0
  1050. templates/nextjs/scaffold/docs/engineering/i18n-policy.md +99 -0
  1051. templates/nextjs/scaffold/docs/pages-content-spec/00-index.md +78 -0
  1052. templates/nextjs/scaffold/docs/playbooks/content-seo.md +55 -0
  1053. templates/nextjs/scaffold/docs/playbooks/docs-governance.md +51 -0
  1054. templates/nextjs/scaffold/docs/playbooks/frontend-ui.md +63 -0
  1055. templates/nextjs/scaffold/src/frontend/app/layout.tsx +14 -0
  1056. templates/nextjs/scaffold/src/frontend/app/page.tsx +3 -0
  1057. templates/nextjs/scaffold/src/frontend/eslint.config.js +17 -0
  1058. templates/nextjs/scaffold/src/frontend/lib/greeting.test.ts +9 -0
  1059. templates/nextjs/scaffold/src/frontend/lib/greeting.ts +3 -0
  1060. templates/nextjs/scaffold/src/frontend/package.json +28 -0
  1061. templates/nextjs/scaffold/src/frontend/tsconfig.json +18 -0
  1062. templates/nextjs/scaffold/src/frontend/vitest.config.ts +10 -0
  1063. templates/nextjs/scaffold-boundary.yaml +30 -0
  1064. templates/nextjs/skills/nextjs-react/SKILL.md +485 -0
  1065. templates/nextjs/skills/nextjs-react/references/anatomy.md +116 -0
  1066. templates/nextjs/skills/nextjs-react/scripts/new_component.py +72 -0
  1067. templates/nextjs/stack.yaml +81 -0
  1068. templates/node-express/rules/backend.md +20 -0
  1069. templates/node-express/scaffold/docs/engineering/express-rules.md +30 -0
  1070. templates/node-express/scaffold/docs/playbooks/express-service.md +35 -0
  1071. templates/node-express/scaffold/src/backend/package.json +24 -0
  1072. templates/node-express/scaffold/src/backend/src/index.ts +17 -0
  1073. templates/node-express/scaffold/src/backend/src/middleware/error-handler.ts +12 -0
  1074. templates/node-express/scaffold/src/backend/src/routes/health.test.ts +33 -0
  1075. templates/node-express/scaffold/src/backend/src/routes/health.ts +7 -0
  1076. templates/node-express/scaffold/src/backend/tsconfig.json +15 -0
  1077. templates/node-express/scaffold/src/backend/types/express-bootstrap.d.ts +21 -0
  1078. templates/node-express/scaffold-boundary.yaml +25 -0
  1079. templates/node-express/skills/node-express/SKILL.md +70 -0
  1080. templates/node-express/skills/node-express/references/anatomy.md +63 -0
  1081. templates/node-express/stack.yaml +63 -0
  1082. templates/python/scaffold/docs/engineering/python-rules.md +27 -0
  1083. templates/python/scaffold/docs/playbooks/python-library.md +35 -0
  1084. templates/python/stack.yaml +60 -0
  1085. templates/rails/rules/backend.md +10 -0
  1086. templates/rails/scaffold/docs/engineering/rails-rules.md +33 -0
  1087. templates/rails/scaffold/docs/playbooks/rails-service.md +42 -0
  1088. templates/rails/scaffold/src/backend/Gemfile +12 -0
  1089. templates/rails/scaffold/src/backend/app/controllers/application_controller.rb +26 -0
  1090. templates/rails/scaffold/src/backend/app/controllers/health_controller.rb +6 -0
  1091. templates/rails/scaffold/src/backend/app/models/health.rb +6 -0
  1092. templates/rails/scaffold/src/backend/config/application.rb +12 -0
  1093. templates/rails/scaffold/src/backend/config/boot.rb +3 -0
  1094. templates/rails/scaffold/src/backend/config/routes.rb +4 -0
  1095. templates/rails/scaffold/src/backend/config.ru +5 -0
  1096. templates/rails/scaffold/src/backend/spec/rails_helper.rb +18 -0
  1097. templates/rails/scaffold/src/backend/spec/requests/health_spec.rb +24 -0
  1098. templates/rails/scaffold-boundary.yaml +25 -0
  1099. templates/rails/skills/rails/SKILL.md +62 -0
  1100. templates/rails/skills/rails/references/anatomy.md +71 -0
  1101. templates/rails/stack.yaml +72 -0
  1102. templates/react-native/rules/mobile.md +26 -0
  1103. templates/react-native/scaffold/docs/engineering/accessibility-mobile.md +95 -0
  1104. templates/react-native/scaffold/docs/engineering/mobile-rules.md +56 -0
  1105. templates/react-native/scaffold/docs/engineering/offline-first.md +61 -0
  1106. templates/react-native/scaffold/docs/playbooks/mobile-app.md +49 -0
  1107. templates/react-native/scaffold/src/mobile/App.tsx +9 -0
  1108. templates/react-native/scaffold/src/mobile/eslint.config.js +17 -0
  1109. templates/react-native/scaffold/src/mobile/package.json +23 -0
  1110. templates/react-native/scaffold/src/mobile/src/greeting.test.ts +9 -0
  1111. templates/react-native/scaffold/src/mobile/src/greeting.ts +3 -0
  1112. templates/react-native/scaffold/src/mobile/tsconfig.json +17 -0
  1113. templates/react-native/scaffold/src/mobile/vitest.config.ts +10 -0
  1114. templates/react-native/scaffold-boundary.yaml +30 -0
  1115. templates/react-native/skills/react-native-mobile/SKILL.md +119 -0
  1116. templates/react-native/skills/react-native-mobile/assets/rn-mobile-checklist.md +28 -0
  1117. templates/react-native/skills/react-native-mobile/references/anatomy.md +140 -0
  1118. templates/react-native/skills/react-native-mobile/references/rn-2026-practices.md +54 -0
  1119. templates/react-native/skills/react-native-mobile/scripts/new_screen.py +73 -0
  1120. templates/react-native/skills/react-native-mobile/versions.json +16 -0
  1121. templates/react-native/skills/react-native-patterns/SKILL.md +512 -0
  1122. templates/react-native/skills/react-native-patterns/assets/rn-review-checklist.md +26 -0
  1123. templates/react-native/skills/react-native-patterns/references/anatomy.md +62 -0
  1124. templates/react-native/skills/react-native-patterns/references/list-performance.md +70 -0
  1125. templates/react-native/skills/react-native-patterns/scripts/scan_rn_perf.py +77 -0
  1126. templates/react-native/stack.yaml +70 -0
  1127. templates/ruby-plain/scaffold/src/backend/Gemfile +8 -0
  1128. templates/ruby-plain/scaffold/src/backend/main.rb +3 -0
  1129. templates/ruby-plain/scaffold-boundary.yaml +23 -0
  1130. templates/ruby-plain/stack.yaml +50 -0
  1131. templates/rust-axum/rules/backend.md +20 -0
  1132. templates/rust-axum/scaffold/docs/engineering/rust-axum-rules.md +35 -0
  1133. templates/rust-axum/scaffold/docs/playbooks/rust-axum-service.md +43 -0
  1134. templates/rust-axum/scaffold/src/backend/Cargo.toml +20 -0
  1135. templates/rust-axum/scaffold/src/backend/src/app.rs +12 -0
  1136. templates/rust-axum/scaffold/src/backend/src/error.rs +48 -0
  1137. templates/rust-axum/scaffold/src/backend/src/main.rs +24 -0
  1138. templates/rust-axum/scaffold/src/backend/src/routes/health.rs +37 -0
  1139. templates/rust-axum/scaffold/src/backend/src/routes/mod.rs +2 -0
  1140. templates/rust-axum/scaffold-boundary.yaml +25 -0
  1141. templates/rust-axum/skills/rust/SKILL.md +73 -0
  1142. templates/rust-axum/skills/rust/references/anatomy.md +63 -0
  1143. templates/rust-axum/stack.yaml +65 -0
  1144. templates/rust-plain/scaffold/src/backend/Cargo.toml +6 -0
  1145. templates/rust-plain/scaffold/src/backend/src/main.rs +3 -0
  1146. templates/rust-plain/scaffold-boundary.yaml +23 -0
  1147. templates/rust-plain/stack.yaml +51 -0
  1148. templates/spring-boot/rules/backend.md +20 -0
  1149. templates/spring-boot/scaffold/docs/engineering/spring-boot-rules.md +35 -0
  1150. templates/spring-boot/scaffold/docs/playbooks/spring-boot-service.md +45 -0
  1151. templates/spring-boot/scaffold/src/backend/mvnw +302 -0
  1152. templates/spring-boot/scaffold/src/backend/pom.xml +69 -0
  1153. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/Application.java +16 -0
  1154. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/common/GlobalExceptionHandler.java +31 -0
  1155. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthController.java +22 -0
  1156. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthService.java +12 -0
  1157. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthStatus.java +4 -0
  1158. templates/spring-boot/scaffold/src/backend/src/test/java/com/example/app/health/HealthServiceTest.java +15 -0
  1159. templates/spring-boot/scaffold-boundary.yaml +26 -0
  1160. templates/spring-boot/skills/spring-boot/SKILL.md +84 -0
  1161. templates/spring-boot/skills/spring-boot/references/anatomy.md +63 -0
  1162. templates/spring-boot/stack.yaml +65 -0
  1163. templates/svelte-sveltekit/rules/frontend.md +20 -0
  1164. templates/svelte-sveltekit/scaffold/docs/engineering/svelte-sveltekit-rules.md +37 -0
  1165. templates/svelte-sveltekit/scaffold/docs/playbooks/svelte-sveltekit-app.md +36 -0
  1166. templates/svelte-sveltekit/scaffold/src/frontend/package.json +22 -0
  1167. templates/svelte-sveltekit/scaffold/src/frontend/src/app.html +12 -0
  1168. templates/svelte-sveltekit/scaffold/src/frontend/src/hooks.server.ts +14 -0
  1169. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/components/Greeting.svelte +6 -0
  1170. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.test.ts +26 -0
  1171. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.ts +4 -0
  1172. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+layout.svelte +24 -0
  1173. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.svelte +9 -0
  1174. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.ts +7 -0
  1175. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/health/+server.ts +7 -0
  1176. templates/svelte-sveltekit/scaffold/src/frontend/svelte.config.js +10 -0
  1177. templates/svelte-sveltekit/scaffold/src/frontend/tsconfig.json +7 -0
  1178. templates/svelte-sveltekit/scaffold/src/frontend/vite.config.ts +7 -0
  1179. templates/svelte-sveltekit/scaffold/src/frontend/vitest.config.ts +11 -0
  1180. templates/svelte-sveltekit/scaffold-boundary.yaml +29 -0
  1181. templates/svelte-sveltekit/skills/svelte/SKILL.md +90 -0
  1182. templates/svelte-sveltekit/skills/svelte/references/anatomy.md +62 -0
  1183. templates/svelte-sveltekit/stack.yaml +70 -0
  1184. templates/typescript-plain/scaffold/src/index.ts +3 -0
  1185. templates/typescript-plain/scaffold/tsconfig.json +13 -0
  1186. templates/typescript-plain/scaffold-boundary.yaml +22 -0
  1187. templates/typescript-plain/stack.yaml +44 -0
  1188. templates/vue-nuxt/rules/frontend.md +19 -0
  1189. templates/vue-nuxt/scaffold/docs/engineering/nuxt-rules.md +30 -0
  1190. templates/vue-nuxt/scaffold/docs/playbooks/nuxt-app.md +29 -0
  1191. templates/vue-nuxt/scaffold/src/frontend/app.vue +3 -0
  1192. templates/vue-nuxt/scaffold/src/frontend/nuxt.config.ts +11 -0
  1193. templates/vue-nuxt/scaffold/src/frontend/package.json +20 -0
  1194. templates/vue-nuxt/scaffold/src/frontend/pages/index.test.ts +19 -0
  1195. templates/vue-nuxt/scaffold/src/frontend/pages/index.vue +11 -0
  1196. templates/vue-nuxt/scaffold/src/frontend/vitest.config.ts +12 -0
  1197. templates/vue-nuxt/scaffold-boundary.yaml +26 -0
  1198. templates/vue-nuxt/skills/vue-nuxt/SKILL.md +57 -0
  1199. templates/vue-nuxt/skills/vue-nuxt/references/anatomy.md +60 -0
  1200. templates/vue-nuxt/stack.yaml +61 -0
  1201. templates/wordpress/rules/backend.md +19 -0
  1202. templates/wordpress/scaffold/docs/engineering/wordpress-rules.md +28 -0
  1203. templates/wordpress/scaffold/docs/playbooks/wordpress-service.md +29 -0
  1204. templates/wordpress/scaffold/src/backend/composer.json +17 -0
  1205. templates/wordpress/scaffold/src/backend/phpcs.xml.dist +11 -0
  1206. templates/wordpress/scaffold/src/backend/phpunit.xml +10 -0
  1207. templates/wordpress/scaffold/src/backend/plugin/inc/health.php +8 -0
  1208. templates/wordpress/scaffold/src/backend/plugin/plugin.php +28 -0
  1209. templates/wordpress/scaffold/src/backend/tests/HealthStatusTest.php +15 -0
  1210. templates/wordpress/scaffold/src/backend/theme/functions.php +18 -0
  1211. templates/wordpress/scaffold/src/backend/theme/style.css +11 -0
  1212. templates/wordpress/scaffold-boundary.yaml +23 -0
  1213. templates/wordpress/skills/wordpress/SKILL.md +110 -0
  1214. templates/wordpress/skills/wordpress/assets/wp-checklist.md +28 -0
  1215. templates/wordpress/skills/wordpress/references/wp-development.md +75 -0
  1216. templates/wordpress/skills/wordpress/references/wp-security.md +68 -0
  1217. templates/wordpress/skills/wordpress/scripts/scan_wp_smells.py +91 -0
  1218. templates/wordpress/skills/wordpress/versions.json +16 -0
  1219. templates/wordpress/stack.yaml +60 -0
  1220. thinking_os/__init__.py +1 -0
  1221. thinking_os/_agent_markers.py +32 -0
  1222. thinking_os/background.py +405 -0
  1223. thinking_os/bootstrap_outcomes.py +200 -0
  1224. thinking_os/budget.py +302 -0
  1225. thinking_os/capture.py +495 -0
  1226. thinking_os/cognition.py +516 -0
  1227. thinking_os/cognition_schemas.py +517 -0
  1228. thinking_os/compress.py +192 -0
  1229. thinking_os/concepts.py +233 -0
  1230. thinking_os/dashboard.py +159 -0
  1231. thinking_os/database.py +2883 -0
  1232. thinking_os/decay.py +393 -0
  1233. thinking_os/digest.py +295 -0
  1234. thinking_os/dispatcher.py +192 -0
  1235. thinking_os/dispatcher_helpers.py +48 -0
  1236. thinking_os/dispatchers/__init__.py +3 -0
  1237. thinking_os/dispatchers/default.py +47 -0
  1238. thinking_os/distill.py +192 -0
  1239. thinking_os/doc_indexer.py +905 -0
  1240. thinking_os/embeddings.py +943 -0
  1241. thinking_os/formula_composer.py +556 -0
  1242. thinking_os/gate_marker.py +75 -0
  1243. thinking_os/graph.py +296 -0
  1244. thinking_os/graph_indexer.py +360 -0
  1245. thinking_os/health_check.py +517 -0
  1246. thinking_os/impact.py +119 -0
  1247. thinking_os/memory_gc.py +356 -0
  1248. thinking_os/migrator_embeddings.py +318 -0
  1249. thinking_os/precision.py +194 -0
  1250. thinking_os/record_outcome.py +399 -0
  1251. thinking_os/repair.py +105 -0
  1252. thinking_os/retrieval_quality.py +239 -0
  1253. thinking_os/roles_state.py +168 -0
  1254. thinking_os/sanitizer.py +320 -0
  1255. thinking_os/server.py +3160 -0
  1256. thinking_os/session_enrich.py +272 -0
  1257. thinking_os/session_observe_worker.py +111 -0
  1258. thinking_os/session_startup.py +74 -0
  1259. thinking_os/session_summary.py +245 -0
  1260. thinking_os/task_analyzer.py +462 -0
  1261. thinking_os/task_parser.py +342 -0
  1262. thinking_os/task_sync.py +73 -0
  1263. thinking_os/tools/__init__.py +6 -0
  1264. thinking_os/tools/_shared.py +947 -0
  1265. thinking_os/tools/cognition.py +1867 -0
  1266. thinking_os/tools/docs.py +770 -0
  1267. thinking_os/tools/learning.py +2078 -0
  1268. thinking_os/tools/logs.py +79 -0
  1269. thinking_os/tools/memory.py +840 -0
  1270. thinking_os/tools/metrics.py +200 -0
  1271. thinking_os/tools/retrieve.py +415 -0
  1272. thinking_os/tools/routing.py +658 -0
  1273. thinking_os/tools/tasks.py +449 -0
  1274. thinking_os/tools/trajectory.py +181 -0
  1275. thinking_os/tracing.py +235 -0
  1276. web/__init__.py +5 -0
  1277. web/_cache.py +118 -0
  1278. web/_deps.py +56 -0
  1279. web/_envelope.py +85 -0
  1280. web/_project_context.py +140 -0
  1281. web/chat_providers.py +108 -0
  1282. web/init_jobs.py +216 -0
  1283. web/routes/__init__.py +25 -0
  1284. web/routes/_bounded_read.py +76 -0
  1285. web/routes/board.py +1089 -0
  1286. web/routes/cognition.py +1838 -0
  1287. web/routes/config.py +635 -0
  1288. web/routes/graph.py +513 -0
  1289. web/routes/health.py +180 -0
  1290. web/routes/hooks.py +288 -0
  1291. web/routes/hub.py +1219 -0
  1292. web/routes/logs.py +374 -0
  1293. web/routes/metrics.py +43 -0
  1294. web/routes/observability.py +400 -0
  1295. web/routes/patterns.py +227 -0
  1296. web/routes/presence.py +609 -0
  1297. web/routes/roles.py +446 -0
  1298. web/routes/scheduled.py +261 -0
  1299. web/routes/search.py +238 -0
  1300. web/routes/sessions.py +220 -0
  1301. web/routes/settings.py +363 -0
  1302. web/routes/stream.py +547 -0
  1303. web/security.py +157 -0
  1304. web/server.py +283 -0
@@ -0,0 +1,947 @@
1
+ """
2
+ Coding OS — Shared MCP tool envelope helpers.
3
+
4
+ Canonical response contract: docs/engineering/mcp-error-envelope.md.
5
+
6
+ Every `cos_*` tool registered in `server.py` MUST route its return value
7
+ through `ok()` / `fail()` so consuming agents can distinguish success from
8
+ failure, categorize errors, and decide whether to retry.
9
+
10
+ Uniform `meta` block:
11
+ Every success payload carries `data.meta` with at minimum:
12
+ - layer ("memory"|"docs"|"tasks"|"metrics"|"routing"|
13
+ "graph"|"health"|"learning")
14
+ - tokens_estimated (script-aware token estimate of the serialized response)
15
+ - truncated (bool — set True when token budget forced a cut)
16
+
17
+ Callers supply `meta={"layer": ..., "source": ..., "query": ...}` via
18
+ the `ok(data, meta=...)` kwarg. `tokens_estimated` and `truncated` are
19
+ computed by this helper — callers must not set them manually.
20
+
21
+ Token-budget enforcement:
22
+ Serialized responses above TOKEN_BUDGET_CHARS are trimmed. Strategy:
23
+ shrink `data.results` from the tail until the payload fits. Meta
24
+ records both the original and kept sizes so the agent knows it asked
25
+ for too much and can page with a smaller limit.
26
+ """
27
+
28
+ from __future__ import annotations
29
+
30
+ import json
31
+ import logging
32
+ import os
33
+ import sqlite3
34
+ import threading
35
+ from collections.abc import Callable
36
+ from functools import wraps
37
+ from typing import Any, Literal
38
+
39
+ logger = logging.getLogger("coding_os.tools._shared")
40
+
41
+ ErrorCategory = Literal[
42
+ "transient",
43
+ "validation",
44
+ "permission",
45
+ "not_found",
46
+ "unavailable",
47
+ "internal",
48
+ "module_disabled",
49
+ ]
50
+
51
+ _RETRYABLE_BY_DEFAULT: frozenset[str] = frozenset({"transient", "unavailable"})
52
+
53
+ # 32 KB ≈ 8 000 tokens. Sized to fit 4× the largest typical cos_doc_search
54
+ # response (≈2k tokens) so normal traffic never hits the budget, while
55
+ # catastrophic payloads (e.g. 1000-row metric queries) get trimmed.
56
+ TOKEN_BUDGET_CHARS = 32_000
57
+
58
+ # OOM-safety ceiling for graph-export-shaped responses ({nodes, edges}).
59
+ # Rationale: structural snapshots aren't textual prose — they describe
60
+ # a whole subgraph. The agent-context budget (32 KB) is the wrong cap;
61
+ # the caller already constrained volume via max_nodes/max_hops (G35
62
+ # hard-caps max_nodes at 2000). UI consumers (/api/graph/export) need
63
+ # the full tree to render the CONTAINS spine. Set ceiling at ~5 MB —
64
+ # any browser fetches that comfortably, MCP transports tolerate it, and
65
+ # the full repo tree (1094 nodes + 1444 edges ≈ 1 MB with indent=2)
66
+ # never trips it under normal operation. Above the ceiling we fall
67
+ # back to coherent-subgraph trim (top-K nodes by degree, edges between
68
+ # kept nodes) so a pathological agent request never returns zero edges
69
+ # or an incoherent slice.
70
+ GRAPH_SUBGRAPH_BUDGET_CHARS = 5_000_000
71
+
72
+ # Layer names — enumerated so tests can pin the contract and agents can
73
+ # filter (Three-Layer Retrieval in CLAUDE.md).
74
+ VALID_LAYERS: frozenset[str] = frozenset(
75
+ {
76
+ "memory",
77
+ "docs",
78
+ "tasks",
79
+ "metrics",
80
+ "routing",
81
+ "graph",
82
+ "health",
83
+ "learning",
84
+ }
85
+ )
86
+
87
+
88
+ # ---------------------------------------------------------------------------
89
+ # Success envelope
90
+ # ---------------------------------------------------------------------------
91
+
92
+
93
+ def ok(data: Any, *, meta: dict | None = None, apply_budget: bool = True) -> str:
94
+ """Wrap a successful tool result in the canonical envelope."""
95
+ if not isinstance(data, dict):
96
+ return json.dumps({"ok": True, "data": data}, indent=2, default=str)
97
+
98
+ # Merge caller-supplied meta on top of any meta already in data.
99
+ # Diagnostic keys (tokens_estimated, truncated, truncated_results_*) are
100
+ # strictly computed here — caller-supplied values are stripped so agents
101
+ # cannot spoof "this response wasn't trimmed" in logs/audit.
102
+ existing_meta = dict(data.get("meta") or {})
103
+ if meta:
104
+ existing_meta.update(meta)
105
+ # Strip diagnostic keys the trimmer owns — caller meta can't spoof
106
+ # them. `tokens_estimated` + `truncated` always; every `truncated_*`
107
+ # key (added by _trim_list_key + _trim_edges_by_type) reserved too.
108
+ for _diag in ("tokens_estimated", "truncated"):
109
+ existing_meta.pop(_diag, None)
110
+ for _k in [k for k in existing_meta if k.startswith("truncated_")]:
111
+ existing_meta.pop(_k, None)
112
+
113
+ # Strip `meta` from the data dict so we can re-attach with diagnostics
114
+ body = {k: v for k, v in data.items() if k != "meta"}
115
+
116
+ # First serialization to estimate tokens (meta is the in-progress dict
117
+ # referenced below; JSON reflects current state each dump).
118
+ serialized = json.dumps(
119
+ {"ok": True, "data": {**body, "meta": existing_meta}},
120
+ indent=2,
121
+ default=str,
122
+ )
123
+ existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
124
+ existing_meta["truncated"] = False
125
+
126
+ # Graph-export-shaped responses ({nodes:[...], edges:[...]}) describe a
127
+ # whole subgraph. Two-tier budget:
128
+ # 1. Under GRAPH_SUBGRAPH_BUDGET_CHARS (≈500 KB): pass through
129
+ # untouched — UI Hub needs full tree to render CONTAINS spine.
130
+ # 2. Over the larger ceiling: coherent-subgraph trim (top-K nodes
131
+ # by degree + edges between kept nodes). Never zero out edges
132
+ # and never return an incoherent slice.
133
+ # Agent consumers can use max_nodes/max_hops to stay under the agent
134
+ # context window; the envelope is the safety net, not the primary cap.
135
+ _is_graph_subgraph = (
136
+ isinstance(body.get("nodes"), list)
137
+ and isinstance(body.get("edges"), list)
138
+ and bool(body.get("nodes"))
139
+ and bool(body.get("edges"))
140
+ )
141
+ # Enforce token budget. `truncated` only flips when the body was
142
+ # actually shrunk — a no-op (shape not matched) leaves the flag
143
+ # False so agents trust the signal. Web/browser callers pass
144
+ # apply_budget=False: a browser is not token-limited, so the 32KB
145
+ # agent-context cap (and its envelope_unshrinkable fall-through when a
146
+ # payload's shape isn't in the trim ladder) must not apply to it — that
147
+ # cap is an agent-context concept, not a wire limit. Mirrors the
148
+ # apply_budget param cos_task_board already exposes (the board's browser
149
+ # path threads it straight through here).
150
+ if not apply_budget:
151
+ pass
152
+ elif _is_graph_subgraph:
153
+ if len(serialized) > GRAPH_SUBGRAPH_BUDGET_CHARS:
154
+ body, existing_meta, did_trim = _trim_coherent_subgraph(
155
+ body, existing_meta, budget_chars=GRAPH_SUBGRAPH_BUDGET_CHARS
156
+ )
157
+ if did_trim:
158
+ existing_meta["truncated"] = True
159
+ serialized = json.dumps(
160
+ {"ok": True, "data": {**body, "meta": existing_meta}},
161
+ indent=2,
162
+ default=str,
163
+ )
164
+ existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
165
+ elif _budget_size(serialized) > TOKEN_BUDGET_CHARS:
166
+ body, existing_meta, did_trim = _apply_token_budget(body, existing_meta)
167
+ if did_trim:
168
+ existing_meta["truncated"] = True
169
+ # Audit fix: a list-dropping token trim makes the
170
+ # answer incomplete, so the coverage flag must agree. Tools
171
+ # that publish `result_truncated` (contracts/references/...)
172
+ # had it stay False while items were silently dropped, so an
173
+ # agent reading only result_truncated assumed completeness.
174
+ if "result_truncated" in existing_meta:
175
+ existing_meta["result_truncated"] = True
176
+ serialized = json.dumps(
177
+ {"ok": True, "data": {**body, "meta": existing_meta}},
178
+ indent=2,
179
+ default=str,
180
+ )
181
+ existing_meta["tokens_estimated"] = _estimate_tokens(serialized)
182
+
183
+ payload = {"ok": True, "data": {**body, "meta": existing_meta}}
184
+ return json.dumps(payload, indent=2, default=str)
185
+
186
+
187
+ # keys that hold list payloads across cos_* tools. Trimmer
188
+ # walks them in order — biggest payload first when there's a choice.
189
+ # `results` stays first for legacy callers; context/references/impact
190
+ # emit the rest. `edges_by_type` is a dict-of-lists handled separately.
191
+ _TRIMMABLE_LIST_KEYS: tuple[str, ...] = (
192
+ "results",
193
+ "neighbours",
194
+ "references",
195
+ # Export-shaped responses — trim `edges` BEFORE `nodes`.
196
+ # Caller asked for nodes; edges are the high-volume tail.
197
+ "edges",
198
+ "nodes",
199
+ "processes",
200
+ "call_sites",
201
+ "import_sites",
202
+ "doc_references",
203
+ "test_references",
204
+ "string_literals",
205
+ "external_targets",
206
+ "branches",
207
+ "steps",
208
+ # W6.2: cos_graph_contracts top-level buckets.
209
+ "http_routes",
210
+ "mcp_tools",
211
+ "grpc_endpoints",
212
+ "event_handlers",
213
+ "websocket",
214
+ # Common list payloads in other tools.
215
+ "nodes_top",
216
+ "samples",
217
+ # cos_graph_detect_changes bulk arrays.
218
+ "symbols",
219
+ "downstream_consumers",
220
+ "downstream_tasks",
221
+ # S11 (TASK-259): wide-payload tool list keys that were OUTSIDE the ladder,
222
+ # so an oversized agent call produced envelope_unshrinkable — the same bug
223
+ # class as the board 186KB ERROR, generalized. Adding a new wide tool? Add
224
+ # its list key here (and to test_envelope.py::TestTrimLadderCoverage).
225
+ "rows", # cos_log_query, cos_metric_query
226
+ "entries", # cos_timeline
227
+ "cycles", # cos_graph_cycles
228
+ "untested", # cos_graph_test_gap
229
+ "dead", # cos_graph_dead_code
230
+ )
231
+
232
+ # W6.2: dict-of-lists buckets (parent_key → {sub_key: [items]}). Trimmer
233
+ # halves the biggest sub-list until envelope fits, like _trim_edges_by_type.
234
+ # Covers cos_graph_impact.tiers + cos_graph_contracts.* bucketed outputs.
235
+ _TRIMMABLE_NESTED_BUCKETS: tuple[str, ...] = (
236
+ "tiers",
237
+ "http_routes_by_method",
238
+ "mcp_tools_by_module",
239
+ "event_handlers_by_source",
240
+ "contracts",
241
+ )
242
+
243
+ # W6.2: list[dict] containers whose members carry a nested list to trim.
244
+ # (parent_key, member_list_key) — e.g. ("processes", "members") for
245
+ # cos_graph_communities so we shrink members per-process before dropping
246
+ # whole processes.
247
+ _TRIMMABLE_NESTED_MEMBERS: tuple[tuple[str, str], ...] = (("processes", "members"),)
248
+
249
+
250
+ def _estimate_tokens(text: str) -> int:
251
+ # Heuristic, NOT a tokenizer. chars/4 holds for ASCII but undercounts non-Latin
252
+ # ~2-3x — BPE emits more tokens per char for CJK / Arabic / Cyrillic, so the old
253
+ # chars/4 let oversized non-Latin payloads slip under the budget with
254
+ # truncated=False (the coverage signal the graph-first contract trusts). Model:
255
+ # ASCII ~4 chars/token; each non-ASCII char weighted ~1 token — heavier than
256
+ # chars/4, which closes most of the gap. Not exact: dense CJK can still exceed
257
+ # 1 tok/char (mild residual undercount) and Arabic/Cyrillic run lighter (mild
258
+ # over-trim). The goal is removing the silent undercount, not matching a tokenizer.
259
+ ascii_chars = len(text.encode("ascii", "ignore"))
260
+ non_ascii = len(text) - ascii_chars
261
+ return max(1, int(ascii_chars / 4 + non_ascii))
262
+
263
+
264
+ def _budget_size(text: str) -> int:
265
+ # Token-normalised size for the char-denominated budgets: identical to len()
266
+ # for ASCII (zero behaviour change), inflated for non-Latin so the trimmers
267
+ # shrink to the real token budget instead of a char proxy.
268
+ return max(len(text), _estimate_tokens(text) * 4)
269
+
270
+
271
+ def _probe_size(body: dict, meta: dict) -> int:
272
+ return _budget_size(
273
+ json.dumps(
274
+ {"ok": True, "data": {**body, "meta": meta}},
275
+ indent=2,
276
+ default=str,
277
+ )
278
+ )
279
+
280
+
281
+ def _trim_list_key(body: dict, meta: dict, key: str) -> tuple[dict, dict, bool]:
282
+ # Binary-search shrink the list at body[key] until envelope ≤ budget.
283
+ # Returns (body, meta, fits). O(log N) probes.
284
+ items = body.get(key)
285
+ if not isinstance(items, list) or not items:
286
+ return body, meta, False
287
+ original_n = len(items)
288
+ lo, hi = 0, original_n
289
+ best_keep = 0
290
+ while lo < hi:
291
+ mid = (lo + hi + 1) // 2
292
+ trial = {**body, key: items[:mid]}
293
+ if _probe_size(trial, meta) <= TOKEN_BUDGET_CHARS:
294
+ best_keep = mid
295
+ lo = mid
296
+ else:
297
+ hi = mid - 1
298
+ if best_keep < original_n:
299
+ meta[f"truncated_{key}_from"] = original_n
300
+ meta[f"truncated_{key}_to"] = best_keep
301
+ # N5: a sibling `count` that mirrored the pre-trim list length would now
302
+ # lie (count=100 while the array holds 60). Reconcile it through every
303
+ # shrink step — but ONLY when it provably tracked THIS list (== original
304
+ # length), never a count describing a different key.
305
+ count_tracks_key = body.get("count") == original_n
306
+ body = {**body, key: items[:best_keep]}
307
+ if count_tracks_key:
308
+ body["count"] = best_keep
309
+ # The binary search probed fit with `meta` BEFORE these two marker
310
+ # keys existed; committing them grows the envelope by ~50 bytes and
311
+ # can push a borderline best_keep back over budget. Re-check the
312
+ # committed body and shrink one element further until it truly fits,
313
+ # so the ladder never returns a marginally-over body that then falls
314
+ # through and mauls load-bearing scalars (scope/risk_level).
315
+ while best_keep > 0 and _probe_size(body, meta) > TOKEN_BUDGET_CHARS:
316
+ best_keep -= 1
317
+ meta[f"truncated_{key}_to"] = best_keep
318
+ body = {**body, key: items[:best_keep]}
319
+ if count_tracks_key:
320
+ body["count"] = best_keep
321
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
322
+
323
+
324
+ def _trim_coherent_subgraph(
325
+ body: dict, meta: dict, *, budget_chars: int = TOKEN_BUDGET_CHARS
326
+ ) -> tuple[dict, dict, bool]:
327
+ # Drop nodes proportionally (top-K by degree + edges between kept nodes),
328
+ # not edges-first, so the subgraph stays connected — the Hub UI must get a
329
+ # renderable tree, never 0 edges/0 nodes.
330
+ nodes = body.get("nodes")
331
+ edges = body.get("edges")
332
+ if not isinstance(nodes, list) or not isinstance(edges, list):
333
+ return body, meta, _probe_size(body, meta) <= budget_chars
334
+ if not nodes or not edges:
335
+ return body, meta, _probe_size(body, meta) <= budget_chars
336
+
337
+ # Degree map: edges that reference each uid.
338
+ degree: dict[str, int] = {}
339
+ for e in edges:
340
+ if isinstance(e, dict):
341
+ src = e.get("source_uid") or e.get("src_uid") or e.get("source")
342
+ dst = e.get("target_uid") or e.get("dst_uid") or e.get("target")
343
+ if src:
344
+ degree[src] = degree.get(src, 0) + 1
345
+ if dst:
346
+ degree[dst] = degree.get(dst, 0) + 1
347
+
348
+ def _uid_of(n):
349
+ return n.get("uid") if isinstance(n, dict) else None
350
+
351
+ # Sort nodes once: highest degree first, stable order for ties.
352
+ nodes_sorted = sorted(
353
+ nodes,
354
+ key=lambda n: (-degree.get(_uid_of(n) or "", 0), nodes.index(n)),
355
+ )
356
+ original_n = len(nodes_sorted)
357
+ original_e = len(edges)
358
+
359
+ def _probe(k_nodes: int) -> tuple[list, list, int]:
360
+ kept_nodes = nodes_sorted[:k_nodes]
361
+ kept_uids = {_uid_of(n) for n in kept_nodes if _uid_of(n)}
362
+ kept_edges = [
363
+ e
364
+ for e in edges
365
+ if isinstance(e, dict)
366
+ and (
367
+ (e.get("source_uid") or e.get("src_uid") or e.get("source")) in kept_uids
368
+ and (e.get("target_uid") or e.get("dst_uid") or e.get("target")) in kept_uids
369
+ )
370
+ ]
371
+ trial = {**body, "nodes": kept_nodes, "edges": kept_edges}
372
+ return kept_nodes, kept_edges, _probe_size(trial, meta)
373
+
374
+ lo, hi = 0, original_n
375
+ best_k = 0
376
+ while lo < hi:
377
+ mid = (lo + hi + 1) // 2
378
+ _, _, size = _probe(mid)
379
+ if size <= budget_chars:
380
+ best_k = mid
381
+ lo = mid
382
+ else:
383
+ hi = mid - 1
384
+
385
+ kept_nodes, kept_edges, _ = _probe(best_k)
386
+ did_trim = best_k < original_n or len(kept_edges) < original_e
387
+ if did_trim:
388
+ meta["truncated_subgraph"] = True
389
+ meta["truncated_nodes_from"] = original_n
390
+ meta["truncated_nodes_to"] = best_k
391
+ meta["truncated_edges_from"] = original_e
392
+ meta["truncated_edges_to"] = len(kept_edges)
393
+ body = {**body, "nodes": kept_nodes, "edges": kept_edges}
394
+ return body, meta, did_trim
395
+
396
+
397
+ def _trim_edges_by_type(body: dict, meta: dict) -> tuple[dict, dict, bool]:
398
+ # edges_by_type is dict-of-lists; greedily halve biggest bucket until fits.
399
+ edges_by_type = body.get("edges_by_type")
400
+ if not isinstance(edges_by_type, dict) or not edges_by_type:
401
+ return body, meta, False
402
+ # F#6: separate list buckets (trimmable) from non-list values (preserved).
403
+ list_buckets = {k: list(v) for k, v in edges_by_type.items() if isinstance(v, list)}
404
+ non_list = {k: v for k, v in edges_by_type.items() if not isinstance(v, list)}
405
+ if not list_buckets:
406
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
407
+ trim_record: dict[str, dict[str, int]] = {}
408
+ while (
409
+ _probe_size({**body, "edges_by_type": {**non_list, **list_buckets}}, meta)
410
+ > TOKEN_BUDGET_CHARS
411
+ ):
412
+ biggest = max(list_buckets, key=lambda k: len(list_buckets[k]), default=None)
413
+ if biggest is None or not list_buckets[biggest]:
414
+ break
415
+ before = trim_record.get(biggest, {}).get("from", len(edges_by_type[biggest]))
416
+ new_len = max(0, len(list_buckets[biggest]) // 2)
417
+ trim_record[biggest] = {"from": before, "to": new_len}
418
+ list_buckets[biggest] = list_buckets[biggest][:new_len]
419
+ if all(len(v) == 0 for v in list_buckets.values()):
420
+ break
421
+ body = {**body, "edges_by_type": {**non_list, **list_buckets}}
422
+ if trim_record:
423
+ meta["truncated_edges_by_type"] = trim_record
424
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
425
+
426
+
427
+ def _trim_nested_buckets(body: dict, meta: dict, parent_key: str) -> tuple[dict, dict, bool]:
428
+ # W6.2: parent_key holds a dict mapping sub_key → list[items]
429
+ # (impact.tiers, contracts.http_routes_by_method, etc). Same halve-
430
+ # biggest strategy as _trim_edges_by_type.
431
+ parent = body.get(parent_key)
432
+ if not isinstance(parent, dict) or not parent:
433
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
434
+ list_buckets = {k: list(v) for k, v in parent.items() if isinstance(v, list)}
435
+ non_list = {k: v for k, v in parent.items() if not isinstance(v, list)}
436
+ if not list_buckets:
437
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
438
+ trim_record: dict[str, dict[str, int]] = {}
439
+ original_lens = {k: len(v) for k, v in list_buckets.items()}
440
+ while (
441
+ _probe_size({**body, parent_key: {**non_list, **list_buckets}}, meta) > TOKEN_BUDGET_CHARS
442
+ ):
443
+ biggest = max(list_buckets, key=lambda k: len(list_buckets[k]), default=None)
444
+ if biggest is None or not list_buckets[biggest]:
445
+ break
446
+ new_len = max(0, len(list_buckets[biggest]) // 2)
447
+ trim_record[biggest] = {"from": original_lens[biggest], "to": new_len}
448
+ list_buckets[biggest] = list_buckets[biggest][:new_len]
449
+ if all(len(v) == 0 for v in list_buckets.values()):
450
+ break
451
+ body = {**body, parent_key: {**non_list, **list_buckets}}
452
+ if trim_record:
453
+ meta[f"truncated_{parent_key}"] = trim_record
454
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
455
+
456
+
457
+ def _trim_nested_member_lists(
458
+ body: dict, meta: dict, parent_key: str, member_key: str, *, floor: int = 3
459
+ ) -> tuple[dict, dict, bool]:
460
+ # W6.2: shrink list-of-dict members[*]. e.g. processes[*].members.
461
+ # Halve member-lists per-entry until envelope fits or floor reached;
462
+ # then drop whole entries from the tail.
463
+ items = body.get(parent_key)
464
+ if not isinstance(items, list) or not items:
465
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
466
+ members_orig = [len(it.get(member_key) or []) if isinstance(it, dict) else 0 for it in items]
467
+ new_items = [dict(it) if isinstance(it, dict) else it for it in items]
468
+ record: dict[str, int] = {}
469
+ iter_guard = 0
470
+ # iter_guard caps the halving loop. Each iteration halves the
471
+ # biggest member list above `floor`, so at most ceil(log2(max_len))
472
+ # passes per entry × N entries. 64 covers up to ~2^64 starting size
473
+ # while keeping the loop bounded against pathological inputs.
474
+ _ITER_GUARD_MAX = 64
475
+ while (
476
+ _probe_size({**body, parent_key: new_items}, meta) > TOKEN_BUDGET_CHARS
477
+ and iter_guard < _ITER_GUARD_MAX
478
+ ):
479
+ iter_guard += 1
480
+ # find entry with biggest member list above floor
481
+ candidate_idx = -1
482
+ biggest = floor
483
+ for i, it in enumerate(new_items):
484
+ if not isinstance(it, dict):
485
+ continue
486
+ n = len(it.get(member_key) or [])
487
+ if n > biggest:
488
+ biggest = n
489
+ candidate_idx = i
490
+ if candidate_idx == -1:
491
+ break
492
+ entry = new_items[candidate_idx]
493
+ mlist = entry.get(member_key) or []
494
+ new_len = max(floor, len(mlist) // 2)
495
+ entry[member_key] = mlist[:new_len]
496
+ record[f"entry_{candidate_idx}_{member_key}"] = new_len
497
+ # If still over budget, drop tail entries.
498
+ while (
499
+ _probe_size({**body, parent_key: new_items}, meta) > TOKEN_BUDGET_CHARS
500
+ and len(new_items) > 1
501
+ ):
502
+ new_items.pop()
503
+ record["entries_dropped_tail"] = record.get("entries_dropped_tail", 0) + 1
504
+ body = {**body, parent_key: new_items}
505
+ if record:
506
+ meta[f"truncated_{parent_key}_{member_key}"] = record
507
+ meta[f"{parent_key}_original_member_counts"] = members_orig
508
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
509
+
510
+
511
+ # Scalars shorter than this are load-bearing (scope, risk_level, status,
512
+ # …) and can never recover meaningful budget — truncating a 4-char "high"
513
+ # is pure signal loss. The F#5 safety-net skips them and only shortens
514
+ # genuinely large scalars (signatures, generated text, big blobs).
515
+ _SCALAR_TRIM_FLOOR_CHARS = 200
516
+
517
+
518
+ def _trim_huge_string_fields(body: dict, meta: dict) -> tuple[dict, dict, bool]:
519
+ # F#5 safety net: after list trims, if envelope still over budget the
520
+ # culprit is a huge top-level scalar (large signature, generated text).
521
+ # Truncate biggest scalars first until envelope fits. Never touch
522
+ # `node` (caller's root identity). W6.2: ONLY touch str fields —
523
+ # numeric scalars (int/float/bool) preserve typed contract (e.g.
524
+ # impacted_count must stay int). Strings get a cut-to-length prefix
525
+ # instead of a sentinel so partial content remains useful.
526
+ if _probe_size(body, meta) <= TOKEN_BUDGET_CHARS:
527
+ return body, meta, True
528
+ sizes: list[tuple[int, str]] = []
529
+ for k, v in body.items():
530
+ if k == "node" or isinstance(v, (list, dict)):
531
+ continue
532
+ if not isinstance(v, str):
533
+ # W6.2 (T4/F7): never stringify int/bool/float scalars.
534
+ continue
535
+ if len(v) < _SCALAR_TRIM_FLOOR_CHARS:
536
+ # Tiny load-bearing scalar (scope/risk_level/status) — truncating
537
+ # it can't recover budget and destroys signal. Leave intact.
538
+ continue
539
+ sizes.append((len(v), k))
540
+ if not sizes:
541
+ return body, meta, False
542
+ sizes.sort(reverse=True)
543
+ truncated_fields: list[str] = []
544
+ new_body = dict(body)
545
+ for _size, key in sizes:
546
+ if _probe_size(new_body, meta) <= TOKEN_BUDGET_CHARS:
547
+ break
548
+ original = new_body[key]
549
+ # Aggressive halving until fits or string drops below 80 chars.
550
+ cut = max(80, len(original) // 2)
551
+ while cut > 0 and _probe_size(new_body, meta) > TOKEN_BUDGET_CHARS:
552
+ new_body[key] = original[:cut] + "…[truncated]"
553
+ cut //= 2
554
+ truncated_fields.append(key)
555
+ if truncated_fields:
556
+ meta["truncated_string_fields"] = truncated_fields
557
+ return new_body, meta, _probe_size(new_body, meta) <= TOKEN_BUDGET_CHARS
558
+
559
+
560
+ def _trim_lists_balanced(body: dict, meta: dict) -> tuple[dict, dict, bool]:
561
+ # F1: balanced multi-bucket trim. When >=2 trimmable list keys are
562
+ # present, shrink the LARGEST remaining list repeatedly (never below 1
563
+ # item) until the envelope fits — so no bucket is silently zeroed while
564
+ # a sibling bucket keeps items. Root cause of cos_graph_contracts(
565
+ # kinds=http,mcp) returning http_routes=[] while mcp_tools kept 70:
566
+ # the sequential ladder drained http_routes (earlier key) to 0 because
567
+ # mcp_tools (later key) alone exceeded the budget. Uses the same
568
+ # truncated_<key>_from/to markers as the per-key ladder so coverage
569
+ # flags stay consistent. No-op (and identical to the old path) when
570
+ # fewer than two list buckets are present.
571
+ present = [
572
+ k for k in _TRIMMABLE_LIST_KEYS if isinstance(body.get(k), list) and len(body[k]) > 0
573
+ ]
574
+ if len(present) < 2:
575
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
576
+ originals = {k: len(body[k]) for k in present}
577
+ while _probe_size(body, meta) > TOKEN_BUDGET_CHARS:
578
+ biggest = max(present, key=lambda x: len(body[x]))
579
+ cur_n = len(body[biggest])
580
+ if cur_n <= 1:
581
+ break # every bucket down to its last survivor — fall through
582
+ new_n = max(1, int(cur_n * 0.85))
583
+ if new_n >= cur_n:
584
+ new_n = cur_n - 1
585
+ body = {**body, biggest: body[biggest][:new_n]}
586
+ for k in present:
587
+ kept = len(body[k])
588
+ if kept < originals[k]:
589
+ meta[f"truncated_{k}_from"] = originals[k]
590
+ meta[f"truncated_{k}_to"] = kept
591
+ return body, meta, _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
592
+
593
+
594
+ def _apply_token_budget(body: dict, meta: dict) -> tuple[dict, dict, bool]:
595
+ # Trim every list-shaped field + edges_by_type dict-of-lists + (F#5
596
+ # final safety net) huge non-list scalars. did_trim=True when any
597
+ # field got cut. Contract: post-trim body MUST fit budget — if even
598
+ # scalar truncation fails (extreme pathological case), surface via
599
+ # meta.envelope_unshrinkable=True so caller can log/alert.
600
+ # Graph-subgraph shape ({nodes,edges}) is handled by ok() ahead of
601
+ # this call via _trim_coherent_subgraph, so the per-key trim ladder
602
+ # below never zeroes out edges.
603
+ did_any = False
604
+ fits = _probe_size(body, meta) <= TOKEN_BUDGET_CHARS
605
+ # W6.2: shrink nested members (processes[*].members) BEFORE dropping
606
+ # whole entries — preserves community/process count signal.
607
+ for parent_key, member_key in _TRIMMABLE_NESTED_MEMBERS:
608
+ if fits:
609
+ break
610
+ body, meta, fits_after = _trim_nested_member_lists(body, meta, parent_key, member_key)
611
+ if f"truncated_{parent_key}_{member_key}" in meta:
612
+ did_any = True
613
+ fits = fits or fits_after
614
+ # W6.2: shrink dict-of-list buckets (impact.tiers, contracts.*) BEFORE
615
+ # toplevel list trims so we preserve top-level metadata (impacted_count,
616
+ # http_routes count etc).
617
+ for parent_key in _TRIMMABLE_NESTED_BUCKETS:
618
+ if fits:
619
+ break
620
+ body, meta, fits_after = _trim_nested_buckets(body, meta, parent_key)
621
+ if f"truncated_{parent_key}" in meta:
622
+ did_any = True
623
+ fits = fits or fits_after
624
+ # F1: balanced multi-bucket trim BEFORE the sequential per-key ladder,
625
+ # so an earlier bucket isn't zeroed while a larger later bucket keeps
626
+ # items. No-op for single-bucket payloads (the ladder below handles
627
+ # those unchanged).
628
+ if not fits:
629
+ _markers_before = sum(1 for k in meta if k.startswith("truncated_") and k.endswith("_to"))
630
+ body, meta, fits = _trim_lists_balanced(body, meta)
631
+ _markers_after = sum(1 for k in meta if k.startswith("truncated_") and k.endswith("_to"))
632
+ if _markers_after > _markers_before:
633
+ did_any = True
634
+ for key in _TRIMMABLE_LIST_KEYS:
635
+ if fits:
636
+ break
637
+ body, meta, fits_after = _trim_list_key(body, meta, key)
638
+ if f"truncated_{key}_to" in meta:
639
+ did_any = True
640
+ fits = fits or fits_after
641
+ if not fits:
642
+ body, meta, fits_after = _trim_edges_by_type(body, meta)
643
+ if "truncated_edges_by_type" in meta:
644
+ did_any = True
645
+ fits = fits or fits_after
646
+ if not fits:
647
+ body, meta, fits_after = _trim_huge_string_fields(body, meta)
648
+ if "truncated_string_fields" in meta:
649
+ did_any = True
650
+ fits = fits or fits_after
651
+ if not fits:
652
+ meta["envelope_unshrinkable"] = True
653
+ # Debug-level here; the @safe_tool layer re-logs this at ERROR WITH the
654
+ # tool name so the eye gets an actionable, deduplicable row
655
+ # instead of an anonymous one. Tools not wrapped by safe_tool are rare.
656
+ logger.debug(
657
+ "envelope %d chars > budget %d after all trims",
658
+ _probe_size(body, meta),
659
+ TOKEN_BUDGET_CHARS,
660
+ )
661
+ did_any = True
662
+ return body, meta, did_any
663
+
664
+
665
+ # ---------------------------------------------------------------------------
666
+ # Error envelope
667
+ # ---------------------------------------------------------------------------
668
+
669
+
670
+ def fail(
671
+ category: ErrorCategory,
672
+ message: str,
673
+ *,
674
+ retryable: bool | None = None,
675
+ ) -> str:
676
+ """Wrap an error in the canonical envelope.
677
+
678
+ If `retryable` is not given it is inferred from the category:
679
+ `transient` and `unavailable` default to True; everything else to False.
680
+ Override when a specific call has better information.
681
+ """
682
+ if retryable is None:
683
+ retryable = category in _RETRYABLE_BY_DEFAULT
684
+ return json.dumps(
685
+ {
686
+ "ok": False,
687
+ "error": {
688
+ "category": category,
689
+ "retryable": retryable,
690
+ "message": message,
691
+ },
692
+ },
693
+ indent=2,
694
+ default=str,
695
+ )
696
+
697
+
698
+ # ---------------------------------------------------------------------------
699
+ # G36: SSOT validation helpers — uniform shape across all cos_* tools.
700
+ # Return None when valid, a fail-envelope string when invalid. Caller
701
+ # pattern is `err = validate_X(...); if err: return err`.
702
+ # ---------------------------------------------------------------------------
703
+
704
+
705
+ def validate_enum(value: Any, allowed: tuple[str, ...], field: str) -> str | None:
706
+ # None when valid; fail envelope when value not in allowed.
707
+ if value not in allowed:
708
+ return fail(
709
+ "validation",
710
+ f"{field} must be one of {allowed} (got {value!r})",
711
+ )
712
+ return None
713
+
714
+
715
+ def validate_positive_int(value: Any, field: str) -> str | None:
716
+ # Reject non-int / <=0; otherwise None.
717
+ if not isinstance(value, int) or value <= 0:
718
+ return fail("validation", f"{field} must be a positive int (got {value!r})")
719
+ return None
720
+
721
+
722
+ def validate_non_empty_str(value: Any, field: str) -> str | None:
723
+ if not isinstance(value, str) or not value.strip():
724
+ return fail("validation", f"{field} must be a non-empty string")
725
+ return None
726
+
727
+
728
+ def validate_min_chars(value: Any, field: str, *, min_chars: int = 2) -> str | None:
729
+ # W7.1 / R4-09: parity between cos_graph_query (already enforces 2)
730
+ # and cos_graph_resolve (silently accepted single-char fuzzy).
731
+ if not isinstance(value, str):
732
+ return fail("validation", f"{field} must be a string (got {type(value).__name__})")
733
+ if len(value.strip()) < min_chars:
734
+ return fail("validation", f"{field} must be at least {min_chars} chars (got {value!r})")
735
+ return None
736
+
737
+
738
+ def validate_confidence(value: Any, field: str) -> str | None:
739
+ # W7.1 / R4-19/R4-26: confidence scores live in [0.0, 1.0]; the
740
+ # cos_graph_impact / cos_graph_query tools silently accepted 999
741
+ # and filtered everything.
742
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
743
+ return fail("validation", f"{field} must be a number (got {type(value).__name__})")
744
+ if value < 0.0 or value > 1.0:
745
+ return fail("validation", f"{field} must be in [0.0, 1.0] (got {value})")
746
+ return None
747
+
748
+
749
+ def clamp_int(value: int, *, min_v: int, max_v: int) -> tuple[int, bool]:
750
+ # Return (clamped, was_clamped). Caller surfaces was_clamped in meta.
751
+ clamped = max(min_v, min(value, max_v))
752
+ return clamped, clamped != value
753
+
754
+
755
+ def _sqlite_db_identity(args: tuple[Any, ...]) -> str:
756
+ # Multi-process forensics (mcp-error-envelope.md § Internal-error
757
+ # forensics): every server process appends to the same .mcp.log, so a
758
+ # sqlite failure must name the DB file its connection was attached to.
759
+ conn = args[0] if args and isinstance(args[0], sqlite3.Connection) else None
760
+ if conn is None:
761
+ return ""
762
+ try:
763
+ rows = conn.execute("PRAGMA database_list").fetchall()
764
+ return " db=" + ";".join(str(row[2]) for row in rows)
765
+ except sqlite3.Error:
766
+ return " db=<database_list unavailable>"
767
+
768
+
769
+ def _log_tool_failure(tool_name: str, exc: Exception, args: tuple[Any, ...]) -> None:
770
+ db_identity = _sqlite_db_identity(args) if isinstance(exc, sqlite3.Error) else ""
771
+ logger.exception(
772
+ "tool %s raised %s [pid=%d thread=%s]%s",
773
+ tool_name,
774
+ type(exc).__name__,
775
+ os.getpid(),
776
+ threading.current_thread().name,
777
+ db_identity,
778
+ )
779
+
780
+
781
+ # ---------------------------------------------------------------------------
782
+ # Module gating (TASK-354) — tools owned by a disabled subsystem module fail
783
+ # with category=module_disabled instead of behaving as if the tool vanished.
784
+ # Reads src/core/subsystems.yaml (tool entries may end in '*' for a prefix
785
+ # family) + $COS_STATE_DIR/subsystems-state.json. Fail-open: any read error
786
+ # means no gating.
787
+
788
+ _MODULE_GATE_CACHE: dict[str, Any] = {"map": None}
789
+
790
+
791
+ def _tool_module_map() -> list[tuple[str, str]]:
792
+ if _MODULE_GATE_CACHE["map"] is not None:
793
+ return _MODULE_GATE_CACHE["map"]
794
+ pairs: list[tuple[str, str]] = []
795
+ try:
796
+ from pathlib import Path
797
+
798
+ import yaml
799
+
800
+ manifest = Path(__file__).resolve().parents[2] / "subsystems.yaml"
801
+ data = yaml.safe_load(manifest.read_text(encoding="utf-8")) or {}
802
+ for module in data.get("modules") or []:
803
+ if module.get("kernel"):
804
+ continue
805
+ for tool in module.get("tools") or []:
806
+ pairs.append((str(tool), str(module["id"])))
807
+ except Exception as exc:
808
+ logger.debug("module gate map unavailable: %s", exc)
809
+ _MODULE_GATE_CACHE["map"] = pairs
810
+ return pairs
811
+
812
+
813
+ def _disabled_modules() -> set[str]:
814
+ try:
815
+ from pathlib import Path
816
+
817
+ from thinking_os.database import get_active_project_root, resolve_db_path
818
+
819
+ # A bound Hub request scope must win over the ambient $COS_STATE_DIR
820
+ # (the launch project's), else every /api/p/<slug>/* call is gated by
821
+ # the launch project's enabled-modules set instead of the slug's.
822
+ if get_active_project_root() is not None:
823
+ state_dir = resolve_db_path().parent
824
+ else:
825
+ state_dir = Path(os.environ.get("COS_STATE_DIR") or ".coding-os")
826
+ state_file = state_dir / "subsystems-state.json"
827
+ if not state_file.is_file():
828
+ return set()
829
+ data = json.loads(state_file.read_text(encoding="utf-8"))
830
+ return {str(x) for x in data.get("disabled") or []}
831
+ except Exception as exc:
832
+ logger.debug("subsystems state unreadable (%s) — gating off", exc)
833
+ return set()
834
+
835
+
836
+ def _gated_module(tool_name: str) -> str | None:
837
+ disabled = _disabled_modules()
838
+ if not disabled:
839
+ return None
840
+ for pattern, module_id in _tool_module_map():
841
+ if module_id not in disabled:
842
+ continue
843
+ if pattern.endswith("*"):
844
+ if tool_name.startswith(pattern[:-1]):
845
+ return module_id
846
+ elif tool_name == pattern:
847
+ return module_id
848
+ return None
849
+
850
+
851
+ # Startup-time surface removal (TASK-476): a disabled module's tools must
852
+ # VANISH from the served list_tools, not merely fail when called — else the
853
+ # agent still sees them and hallucinates calls to a dead tool. The per-call
854
+ # safe_tool gate above stays as defense-in-depth (a client holding a cached
855
+ # tool list, or a module toggled mid-session). Fail-open: any error leaves the
856
+ # full surface intact rather than serving a half-surface.
857
+ def apply_module_tool_gating(mcp: Any) -> dict:
858
+ """Remove disabled-module-owned tools from the live MCP tool surface."""
859
+ disabled = _disabled_modules()
860
+ if not disabled:
861
+ return {"removed": [], "disabled_modules": []}
862
+ removed: list[str] = []
863
+ try:
864
+ for name in [tool.name for tool in mcp._tool_manager.list_tools()]:
865
+ if _gated_module(name):
866
+ mcp.remove_tool(name)
867
+ removed.append(name)
868
+ except Exception as exc:
869
+ logger.debug("module tool-surface gating skipped (%s) — full surface served", exc)
870
+ return {"removed": sorted(removed), "disabled_modules": sorted(disabled)}
871
+
872
+
873
+ def safe_tool(
874
+ fn: Callable[..., str] | None = None,
875
+ *,
876
+ name: str | None = None,
877
+ ) -> Callable[..., str] | Callable[[Callable[..., str]], Callable[..., str]]:
878
+ """Decorator: convert unhandled exceptions inside a tool to `fail()`.
879
+
880
+ Mapping is conservative — specific Python exception types map to the
881
+ closest envelope category; anything unrecognized becomes `internal`.
882
+ Place this INSIDE `@mcp.tool(...)` so MCP still sees a string return:
883
+
884
+ @mcp.tool(name="cos_example", ...)
885
+ @safe_tool
886
+ def cos_example(...) -> str:
887
+ ...
888
+
889
+ Pass `name=` when the Python function name differs from the registered MCP
890
+ tool name (e.g. `thinking_os_search` registered as `cos_search`) so the
891
+ subsystems module gate keys on the name `subsystems.yaml` lists, not the
892
+ internal function name. Without it the gate silently never fires for that
893
+ tool.
894
+ """
895
+
896
+ def decorate(target: Callable[..., str]) -> Callable[..., str]:
897
+ gate_name = name or target.__name__
898
+
899
+ @wraps(target)
900
+ def wrapper(*args: Any, **kwargs: Any) -> str:
901
+ gated_by = _gated_module(gate_name)
902
+ if gated_by:
903
+ return fail(
904
+ "module_disabled",
905
+ f"tool '{gate_name}' belongs to the disabled '{gated_by}' module — "
906
+ f"enable it with `cos module enable {gated_by}`",
907
+ retryable=False,
908
+ )
909
+ try:
910
+ result = target(*args, **kwargs)
911
+ except PermissionError as exc:
912
+ _log_tool_failure(gate_name, exc, args)
913
+ return fail("permission", str(exc) or "permission denied", retryable=False)
914
+ except FileNotFoundError as exc:
915
+ _log_tool_failure(gate_name, exc, args)
916
+ return fail("not_found", str(exc) or "resource not found", retryable=False)
917
+ except (TimeoutError, ConnectionError) as exc:
918
+ _log_tool_failure(gate_name, exc, args)
919
+ return fail("transient", str(exc) or type(exc).__name__, retryable=True)
920
+ except ValueError as exc:
921
+ _log_tool_failure(gate_name, exc, args)
922
+ return fail("validation", str(exc) or "invalid input", retryable=False)
923
+ except ImportError as exc:
924
+ _log_tool_failure(gate_name, exc, args)
925
+ return fail(
926
+ "unavailable", str(exc) or "optional dependency missing", retryable=True
927
+ )
928
+ except Exception as exc:
929
+ _log_tool_failure(gate_name, exc, args)
930
+ return fail("internal", f"{type(exc).__name__}: {exc}", retryable=False)
931
+
932
+ # Name the offending tool when ok() flagged the envelope unshrinkable.
933
+ # ok() logs the size from a context without the tool name, leaving the
934
+ # eye with an unactionable "something is 265KB" error.
935
+ if isinstance(result, str) and "envelope_unshrinkable" in result:
936
+ logger.error(
937
+ "tool %s returned an unshrinkable envelope (%d chars > %d budget)",
938
+ gate_name,
939
+ len(result),
940
+ TOKEN_BUDGET_CHARS,
941
+ )
942
+ return result
943
+
944
+ return wrapper
945
+
946
+ # Bare `@safe_tool` → fn is the target. `@safe_tool(name=...)` → fn is None.
947
+ return decorate(fn) if fn is not None else decorate