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
core/hooks/cos-env.sh ADDED
@@ -0,0 +1,1301 @@
1
+ #!/usr/bin/env bash
2
+ # Coding OS — Shared environment for hooks.
3
+ # Source this file at the top of every hook: source "$(dirname "$0")/cos-env.sh"
4
+ #
5
+ # Provides:
6
+ # COS_STATE_DIR — shared state directory root (default: .coding-os)
7
+ # Holds agent-agnostic artifacts: coding-os.db, .hooks.log,
8
+ # .agent marker, .capture-errors.log, .dogfood-reminded,
9
+ # installed-manifest.json, domain-config.json.
10
+ # COS_AGENT — which agent runtime invoked this hook (claude|codex|unknown)
11
+ # COS_AGENT_DIR — agent-private state directory = $COS_STATE_DIR/$COS_AGENT
12
+ # Holds per-agent state: session-id, .task-current,
13
+ # .thinking_os-gate, .zoom-checkpoint, .doc-anchor,
14
+ # .memory-check, .active-skill.
15
+ # Two agents running against the same project write to
16
+ # different dirs and never collide.
17
+ # COS_SESSION_FILE — path to session-id file (inside COS_AGENT_DIR)
18
+ # COS_DB_PATH — path to the project's coding-os SQLite DB (shared, in COS_STATE_DIR)
19
+ # COS_HOOK_LOG — path to the append-only hook activity log (shared,
20
+ # every line carries agent=X session=Y task=Z so downstream
21
+ # tools can filter by agent without a separate file)
22
+ #
23
+ # And the helper:
24
+ # cos_log_hook HOOK_NAME [ACTION] [DETAIL]
25
+ # Appends a line to $COS_HOOK_LOG so the user (or `cos hooks-log`) can
26
+ # see live hook activity. Fail-open: never errors a hook even if the
27
+ # write fails.
28
+
29
+ # Resolve $COS_STATE_DIR. Precedence (applied ONLY while it is the bare default):
30
+ # 1. explicit non-default COS_STATE_DIR → verbatim
31
+ # 2. $CLAUDE_PROJECT_DIR → $CLAUDE_PROJECT_DIR/.coding-os
32
+ # 3. $COS_PROJECT_ROOT → $COS_PROJECT_ROOT/.coding-os
33
+ # 4. upward marker-walk from $PWD → <root>/.coding-os
34
+ # 5. nothing found → relative .coding-os (legacy)
35
+ # Why a walk: hooks often run with cwd != repo root (e.g. `cd src/backend &&
36
+ # go build`); a bare relative ".coding-os" then lazily creates a STRAY
37
+ # .coding-os/ at the subdir (the nested-.coding-os bug). CLAUDE_PROJECT_DIR is
38
+ # unset under the VSCode native extension, so steps 3-4 are the runtime-
39
+ # independent fix. SPEC: docs/engineering/state-files.md § Project-root resolution.
40
+ COS_STATE_DIR="${COS_STATE_DIR:-.coding-os}"
41
+
42
+ # Pure-Bash project-root finder (no python3 spawn — this runs on every hook).
43
+ # DRIFT WARNING: mirrors src/core/thinking_os/database.py::_find_project_root_from_cwd
44
+ # (its _ROOT_MARKERS + .coding-os/-co-location requirement); tests/test_hooks.py
45
+ # asserts the two stay identical. The extra $HOME hard-stop here prevents binding
46
+ # the global hub at $HOME/.coding-os. Echoes the resolved root, or empty when none
47
+ # is found at/below the $HOME boundary.
48
+ _cos_find_project_root() {
49
+ local dir home_real first_state="" marker found_marker parent
50
+ # Rule 5: resolve symlinks (macOS /tmp -> /private/tmp) before the $HOME compare.
51
+ dir="$(cd -P "${PWD}" 2>/dev/null && pwd -P)" || dir=""
52
+ # Only an absolute, resolvable cwd is walkable. A relative/stale $PWD (cos-env.sh
53
+ # is SOURCED, so it inherits the parent's $PWD) would otherwise make dirname
54
+ # collapse to '.' — a fixpoint that spins forever. Bail to the relative default.
55
+ if [[ "$dir" != /* ]]; then
56
+ printf ''
57
+ return 0
58
+ fi
59
+ home_real="$(cd -P "${HOME:-/dev/null}" 2>/dev/null && pwd -P)" || home_real="${HOME:-}"
60
+ while [[ -n "$dir" && "$dir" != "/" ]]; do
61
+ # Never inspect/accept $HOME or above — $HOME/.coding-os is the global hub.
62
+ if [[ -n "$home_real" && "$dir" == "$home_real" ]]; then
63
+ break
64
+ fi
65
+ if [[ -d "$dir/.coding-os" ]]; then
66
+ if [[ -z "$first_state" ]]; then
67
+ first_state="$dir"
68
+ fi
69
+ # Prefer a .coding-os/ co-located with a root marker (skips a stray nested
70
+ # .coding-os/). Marker set mirrors database.py::_ROOT_MARKERS.
71
+ found_marker=""
72
+ for marker in .git .coding-os.yaml pyproject.toml package.json go.mod AGENTS.md; do
73
+ if [[ -e "$dir/$marker" ]]; then
74
+ found_marker=1
75
+ break
76
+ fi
77
+ done
78
+ if [[ -n "$found_marker" ]]; then
79
+ printf '%s' "$dir"
80
+ return 0
81
+ fi
82
+ fi
83
+ parent="$(dirname "$dir")"
84
+ # dirname fixpoint ('/' or any path that cannot ascend) → stop, never spin.
85
+ if [[ "$parent" == "$dir" ]]; then
86
+ break
87
+ fi
88
+ dir="$parent"
89
+ done
90
+ # No marked root below the boundary → innermost bare .coding-os/ (never
91
+ # lazy-create at cwd), else empty (caller keeps the relative default).
92
+ printf '%s' "$first_state"
93
+ }
94
+
95
+ # Resolve the MAIN repo root for a command running inside a git worktree, or
96
+ # empty. A linked worktree's --git-common-dir points at <main>/.git, so its
97
+ # parent is the main checkout. Used only when a worktree command lacks the
98
+ # COS_PROJECT_ROOT fast-path (TASK-515 / pr-mode). Always returns 0 — this file
99
+ # is SOURCED under the caller's `set -euo pipefail`, so a non-zero return from a
100
+ # command-substitution assignment would kill the hook (mirrors
101
+ # _cos_find_project_root's echo-empty-and-return-0 contract).
102
+ # SPEC: docs/playbooks/pr-workflow.md § 3.
103
+ _cos_main_repo_from_worktree() {
104
+ command -v git >/dev/null 2>&1 || { printf ''; return 0; }
105
+ local common
106
+ common="$(git rev-parse --git-common-dir 2>/dev/null)" || { printf ''; return 0; }
107
+ [[ -z "$common" ]] && { printf ''; return 0; }
108
+ common="$(cd -P "$common" 2>/dev/null && pwd -P)" || { printf ''; return 0; }
109
+ case "$common" in
110
+ */.git) printf '%s' "${common%/.git}" ;;
111
+ *) printf '' ;;
112
+ esac
113
+ return 0
114
+ }
115
+
116
+ # Cheap worktree pre-check (NO git fork): a linked worktree's root carries `.git`
117
+ # as a FILE (a gitdir pointer), whereas a normal checkout has it as a directory.
118
+ # Walk up at most 40 levels from PWD looking for a `.git` file — returns 0 (found)
119
+ # so the git-fork probe below runs only for a plausible worktree, keeping normal
120
+ # trunk hooks fork-free. Bounded + dirname-fixpoint guarded against infinite walk.
121
+ _cos_has_dotgit_file() {
122
+ local dir="${PWD}" i=0
123
+ [[ "$dir" == /* ]] || return 1
124
+ while [[ -n "$dir" && "$dir" != "/" && $i -lt 40 ]]; do
125
+ [[ -f "$dir/.git" ]] && return 0
126
+ [[ -d "$dir/.git" ]] && return 1 # a real repo root — not a worktree
127
+ local parent; parent="$(dirname "$dir")"
128
+ [[ "$parent" == "$dir" ]] && break
129
+ dir="$parent"; i=$((i + 1))
130
+ done
131
+ return 1
132
+ }
133
+
134
+ # _cos_helpers_dir — physical path to _helpers/, resolved through this file's
135
+ # own symlink chain. Consumers (and the meta-repo's own .claude/hooks/) symlink
136
+ # cos-env.sh but NOT _helpers/, so a $(dirname)-relative path lands in a dir with
137
+ # no helpers and the python fallbacks silently no-op — a fail-OPEN gap. readlink
138
+ # to the real file's dir finds _helpers/. Defined here (before the worktree-
139
+ # routing + pr-mode-enablement blocks below call it) so it is already in scope.
140
+ _cos_helpers_dir() {
141
+ local src dir
142
+ src="${BASH_SOURCE[0]}"
143
+ while [ -L "$src" ]; do
144
+ dir="$(cd -P "$(dirname "$src")" && pwd)"
145
+ src="$(readlink "$src")"
146
+ [[ "$src" != /* ]] && src="${dir}/${src}"
147
+ done
148
+ printf '%s' "$(cd -P "$(dirname "$src")" && pwd)/_helpers"
149
+ }
150
+
151
+ case "${COS_STATE_DIR}" in
152
+ .coding-os | ./.coding-os)
153
+ if [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
154
+ COS_STATE_DIR="${CLAUDE_PROJECT_DIR}/.coding-os"
155
+ elif [[ -n "${COS_PROJECT_ROOT:-}" ]]; then
156
+ COS_STATE_DIR="${COS_PROJECT_ROOT}/.coding-os"
157
+ else
158
+ _cos_root="$(_cos_find_project_root)"
159
+ if [[ -n "$_cos_root" ]]; then
160
+ COS_STATE_DIR="${_cos_root}/.coding-os"
161
+ fi
162
+ unset _cos_root
163
+ fi
164
+ ;;
165
+ esac
166
+
167
+ # ---------------------------------------------------------------------------
168
+ # Worktree state routing + misroute guard (TASK-515 / pr-mode). A command
169
+ # inside a git worktree under ~/.coding-os/worktrees/ must resolve state to the
170
+ # MAIN repo, so every worktree of one repo shares its $COS_STATE_DIR (DB, board,
171
+ # presence, the test-governor .test-run.lock). Runs AFTER the case so it also
172
+ # corrects an INHERITED hub COS_STATE_DIR (e.g. a command spawned under
173
+ # `cos hub`, whose env carries COS_STATE_DIR=$HOME/.coding-os). Cheap raw-string
174
+ # gate first — only worktree cwds pay the resolution cost; the happy path
175
+ # (COS_PROJECT_ROOT exported by the cos pr dispatch) never spawns git.
176
+ # SPEC: docs/playbooks/pr-workflow.md § 3.
177
+ # ---------------------------------------------------------------------------
178
+ _cos_in_wt=""
179
+ case "${PWD}" in
180
+ *"/.coding-os/worktrees/"*) _cos_in_wt=1 ;;
181
+ esac
182
+ if [[ -z "$_cos_in_wt" && -n "${COS_WORKTREE_ROOT:-}" && "${PWD}" == "${COS_WORKTREE_ROOT}"/* ]]; then
183
+ _cos_in_wt=1
184
+ fi
185
+ # Catches a custom-location worktree the raw-string gates miss. Short-circuit ONLY
186
+ # on COS_PROJECT_ROOT (the authoritative fast-path) — the old CLAUDE_PROJECT_DIR
187
+ # arm gated this OFF for every Claude Code hook, exactly when the probe is needed,
188
+ # so a custom-location worktree misrouted state into itself (stray .coding-os in
189
+ # the agent's PR). Trunk fast-path preserved by a cheap `.git`-FILE pre-check: a
190
+ # linked worktree's root has `.git` as a file (gitdir pointer), a normal repo has
191
+ # it as a directory — only the former forks git below. (pr-workflow.md § 3, TASK-531.)
192
+ if [[ -z "$_cos_in_wt" && -z "${COS_PROJECT_ROOT:-}" ]] && _cos_has_dotgit_file; then
193
+ _cos_wt_main="$(_cos_main_repo_from_worktree)"
194
+ if [[ -n "$_cos_wt_main" ]]; then
195
+ _cos_wt_top="$(git rev-parse --show-toplevel 2>/dev/null)" || _cos_wt_top=""
196
+ if [[ -n "$_cos_wt_top" ]]; then
197
+ _cos_wt_top="$(cd -P "$_cos_wt_top" 2>/dev/null && pwd -P)" || _cos_wt_top=""
198
+ fi
199
+ if [[ -n "$_cos_wt_top" && "$_cos_wt_top" != "$_cos_wt_main" ]]; then
200
+ _cos_in_wt=1
201
+ fi
202
+ fi
203
+ unset _cos_wt_main _cos_wt_top
204
+ fi
205
+ if [[ -n "$_cos_in_wt" ]]; then
206
+ # In a worktree the project root is unambiguously the MAIN repo. COS_PROJECT_ROOT
207
+ # is authoritative and beats whatever the case produced (CLAUDE_PROJECT_DIR or
208
+ # the walk can point at the worktree itself or the global hub).
209
+ _cos_main=""
210
+ if [[ -n "${COS_PROJECT_ROOT:-}" ]]; then
211
+ _cos_main="${COS_PROJECT_ROOT}"
212
+ else
213
+ _cos_main="$(_cos_main_repo_from_worktree)"
214
+ fi
215
+ if [[ -n "$_cos_main" && -d "${_cos_main}/.coding-os" ]]; then
216
+ COS_STATE_DIR="${_cos_main}/.coding-os"
217
+ else
218
+ # main unresolvable: steer to a per-worktree QUARANTINE, never the global hub
219
+ # ($HOME/.coding-os) — binding there made every misrouted worktree share the
220
+ # hub's own state (DB/board/presence/locks) and collide with it and each other
221
+ # (TASK-582 regression of 1f8869b5). Also never a worktree-relative .coding-os —
222
+ # that stray gets committed into the agent's PR (TASK-531). The quarantine sits
223
+ # OFF the hub root and outside the checkout, keyed per worktree path; the
224
+ # misroute flag still fires so the operator sees the misconfig.
225
+ export COS_STATE_MISROUTE=1
226
+ printf 'cos-env: worktree state misroute — main repo unresolvable from %s; export COS_PROJECT_ROOT=<main-repo> for worktree commands (docs/playbooks/pr-workflow.md § 3).\n' "$PWD" >&2
227
+ _cos_home_real="$(cd -P "${HOME:-/dev/null}" 2>/dev/null && pwd -P)" || _cos_home_real="${HOME:-}"
228
+ if [[ -n "$_cos_home_real" ]]; then
229
+ _cos_wt_tag="$(printf '%s' "$PWD" | cksum 2>/dev/null | cut -d' ' -f1 2>/dev/null || true)"
230
+ COS_STATE_DIR="${_cos_home_real}/.coding-os-misrouted/${_cos_wt_tag:-orphan}"
231
+ unset _cos_wt_tag
232
+ fi
233
+ unset _cos_home_real
234
+ fi
235
+ unset _cos_main
236
+ fi
237
+ unset _cos_in_wt
238
+
239
+ # ---------------------------------------------------------------------------
240
+ # pr-mode enablement (TASK-518) — when this project's Hub sets
241
+ # git_settings.enabled=true in hub-settings.json, export COS_GIT_WORKFLOW=pr
242
+ # (+ branch policy) into EVERY hook's process env. That env is the only place
243
+ # branch-guard / block-shared-tree-edit / the cos pr executor can read the mode
244
+ # (the inline per-command override is broken). Default OFF = byte-identical to
245
+ # trunk. A cheap grep-gate keeps trunk projects (no git_settings key) at one
246
+ # grep per hook; an explicitly-exported COS_GIT_WORKFLOW always wins.
247
+ # SPEC: docs/playbooks/pr-workflow.md § 1.
248
+ # ---------------------------------------------------------------------------
249
+ _cos_git_warn_once() {
250
+ # Debounced stderr warning (≤1/hour per condition per state dir) so a persistent
251
+ # corrupt/divergent git_settings surfaces without spamming every hook. Fail-open.
252
+ local key="$1" msg="$2" marker now last
253
+ marker="${COS_STATE_DIR}/.git-settings-warn-${key}"
254
+ now="$(date +%s 2>/dev/null || echo 0)"
255
+ if [[ -f "$marker" ]]; then
256
+ last="$(cat "$marker" 2>/dev/null || echo 0)"
257
+ if [[ $((now - last)) -lt 3600 ]]; then
258
+ return 0
259
+ fi
260
+ fi
261
+ printf '%s\n' "$msg" >&2 2>/dev/null || true
262
+ printf '%s' "$now" >"$marker" 2>/dev/null || true
263
+ }
264
+ if [[ -f "${COS_STATE_DIR}/hub-settings.json" ]] \
265
+ && grep -q '"git_settings"' "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null; then
266
+ # jq fast-path, python3 fallback — a host WITHOUT jq must still honor an
267
+ # enabled project (the old `command -v jq` precondition silently downgraded
268
+ # it to trunk). Both emit one tab-separated line: enabled\tintegration\t
269
+ # protected(csv)\tautonomy. python3 is a hard dep of coding-os.
270
+ if command -v jq >/dev/null 2>&1; then
271
+ _cos_git_line="$(jq -r '[(.git_settings.enabled // false), (.git_settings.integration_branch // "main"), ((.git_settings.protected_branches // ["production"]) | join(",")), (.git_settings.autonomy_level // "draft")] | @tsv' "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null || true)"
272
+ elif command -v python3 >/dev/null 2>&1; then
273
+ _cos_git_line="$(python3 "$(_cos_helpers_dir)/git_settings_fields.py" "${COS_STATE_DIR}/hub-settings.json" 2>/dev/null || true)"
274
+ else
275
+ _cos_git_line=""
276
+ fi
277
+ _cos_git_enabled="$(printf '%s' "$_cos_git_line" | cut -f1)"
278
+ if [[ -z "${COS_GIT_WORKFLOW:-}" && "$_cos_git_enabled" == "true" ]]; then
279
+ # Split declare/assign so shellcheck SC2155 stays clean (return-value masking).
280
+ COS_GIT_INTEGRATION_BRANCH="$(printf '%s' "$_cos_git_line" | cut -f2)"
281
+ COS_GIT_PROTECTED_BRANCHES="$(printf '%s' "$_cos_git_line" | cut -f3)"
282
+ COS_GIT_AUTONOMY="$(printf '%s' "$_cos_git_line" | cut -f4)"
283
+ export COS_GIT_WORKFLOW="pr" COS_GIT_INTEGRATION_BRANCH COS_GIT_PROTECTED_BRANCHES COS_GIT_AUTONOMY
284
+ elif [[ -z "${COS_GIT_WORKFLOW:-}" && -z "$_cos_git_line" ]]; then
285
+ # grep matched the git_settings key but no parser could read it (corrupt/torn
286
+ # JSON, or neither jq nor python3). The file EXISTS and names git_settings, so
287
+ # the operator opted into pr-mode; a torn write must NOT silently re-enable
288
+ # trunk (where a direct push to main is legal). Fail CLOSED to pr-mode — the
289
+ # stricter posture — with safe-default policy, and warn once. (TASK-587b)
290
+ COS_GIT_INTEGRATION_BRANCH="${COS_GIT_INTEGRATION_BRANCH:-main}"
291
+ COS_GIT_PROTECTED_BRANCHES="${COS_GIT_PROTECTED_BRANCHES:-production}"
292
+ COS_GIT_AUTONOMY="${COS_GIT_AUTONOMY:-draft}"
293
+ export COS_GIT_WORKFLOW="pr" COS_GIT_INTEGRATION_BRANCH COS_GIT_PROTECTED_BRANCHES COS_GIT_AUTONOMY
294
+ _cos_git_warn_once "unreadable" \
295
+ "cos-env: git_settings present but hub-settings.json could not be parsed — failing CLOSED to pr-mode (stricter). Fix the file or install jq/python3 to restore the chosen mode. (docs/playbooks/pr-workflow.md § 1)"
296
+ elif [[ "${COS_GIT_WORKFLOW:-}" == "trunk" && "$_cos_git_enabled" == "true" ]]; then
297
+ # An inherited explicit COS_GIT_WORKFLOW=trunk wins over the file by design;
298
+ # surface the self-downgrade of the Hub policy instead of applying it silently.
299
+ _cos_git_warn_once "divergence" \
300
+ "cos-env: git_settings enables pr-mode but an inherited COS_GIT_WORKFLOW=trunk overrides it — running TRUNK. Unset COS_GIT_WORKFLOW to honor the Hub setting. (docs/playbooks/pr-workflow.md § 1)"
301
+ fi
302
+ unset _cos_git_line _cos_git_enabled
303
+ fi
304
+
305
+ # Default DB filename is `coding-os.db`. Legacy `thinking_os.db` is auto-renamed
306
+ # by src/core/thinking_os/database.py::migrate_legacy_db_filename() on first init_db()
307
+ # call after the upgrade — no shell-side migration needed.
308
+ COS_DB_PATH="${COS_DB_PATH:-${COS_STATE_DIR}/coding-os.db}"
309
+ COS_HOOK_LOG="${COS_HOOK_LOG:-${COS_STATE_DIR}/.hooks.log}"
310
+ # Block-only durable log. The main log above is flooded by high-volume 'fire'
311
+ # lines and capped, so rare 'block' events are evicted within hours — long
312
+ # before learn_extract mines them (the hook-block lesson signal was being lost).
313
+ # This log only ever receives 'block' lines, so it retains them across the
314
+ # extraction window. Mined by learning._mine_hook_block_lessons.
315
+ COS_HOOK_BLOCK_LOG="${COS_HOOK_BLOCK_LOG:-${COS_STATE_DIR}/.hook-blocks.log}"
316
+
317
+ # ---------------------------------------------------------------------------
318
+ # Per-project hook override (TASK-256) — a disabled NON-safety hook self-skips
319
+ # for THIS project. `$COS_STATE_DIR/disabled-hook-scripts` (one script basename
320
+ # per line) is the derived allowlist written by cli.project_overrides, which
321
+ # NEVER lists a safety-category hook — so a safety hook can never be disabled.
322
+ # Guarded + fail-open: when the file is absent (the common case) this costs a
323
+ # single stat; any error continues normally. Skip == `exit 0` (this file is
324
+ # SOURCED, so the exit ends the calling hook before its body runs). Set
325
+ # COS_SKIP_OVERRIDE_CHECK=1 to bypass (used by non-hook sourcers / tests).
326
+ # ---------------------------------------------------------------------------
327
+ if [[ -z "${COS_SKIP_OVERRIDE_CHECK:-}" && -f "${COS_STATE_DIR}/disabled-hook-scripts" ]]; then
328
+ _cos_self="$(basename "${BASH_SOURCE[1]:-${0:-}}" 2>/dev/null || echo "")"
329
+ if [[ -n "$_cos_self" ]] \
330
+ && grep -qxF -- "$_cos_self" "${COS_STATE_DIR}/disabled-hook-scripts" 2>/dev/null; then
331
+ unset _cos_self
332
+ exit 0
333
+ fi
334
+ unset _cos_self
335
+ fi
336
+
337
+ # Cap the log at 500 lines so `cos hooks-log` stays snappy and the file
338
+ # never blooms into a multi-MB artifact that would be tempting to open.
339
+ # Truncation runs when the file passes 2× the cap (=1000 lines) and keeps
340
+ # only the most recent $COS_HOOK_LOG_MAX_LINES.
341
+ COS_HOOK_LOG_MAX_LINES="${COS_HOOK_LOG_MAX_LINES:-500}"
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # Agent runtime detection — which runtime invoked this hook?
345
+ # Priority: explicit COS_AGENT env > .coding-os/.agent (install marker) >
346
+ # Codex > Claude Code > unknown.
347
+ #
348
+ # Must run BEFORE COS_AGENT_DIR / COS_SESSION_FILE are computed.
349
+ #
350
+ # DRIFT WARNING: src/cli/board_commands.py::_detect_agent_runtime mirrors this
351
+ # same priority in Python for CLI-originated task transitions. If you add
352
+ # a new marker here, add it there (and the matching test in
353
+ # tests/test_board_commands_agent_detect.py).
354
+ # ---------------------------------------------------------------------------
355
+ if [[ -z "${COS_AGENT:-}" ]]; then
356
+ COS_AGENT=""
357
+ # Prefer runtime-specific env markers over persisted .agent.
358
+ # .agent is a fallback when the host runtime doesn't expose identity.
359
+ #
360
+ # Claude Code (VSCode/Antigravity variants) does NOT export CLAUDECODE=1
361
+ # to hook processes — only CLAUDE_CODE_ENTRYPOINT and CLAUDE_AGENT_SDK_VERSION
362
+ # make it through. Treat those as authoritative claude signals, otherwise
363
+ # hooks fired by Claude Code mis-tag themselves via the .agent fallback.
364
+ if [[ -n "${CODEX_SESSION_ID:-}" ]] || [[ -n "${CODEX_AGENT_DIR:-}" ]] || [[ -n "${CODEX_HOME:-}" ]]; then
365
+ COS_AGENT="codex"
366
+ elif [[ -n "${CLAUDECODE:-}" ]] || [[ -n "${CLAUDE_CODE_SSE_PORT:-}" ]] \
367
+ || [[ -n "${CLAUDE_CODE_ENTRYPOINT:-}" ]] \
368
+ || [[ -n "${CLAUDE_AGENT_SDK_VERSION:-}" ]]; then
369
+ COS_AGENT="claude"
370
+ fi
371
+
372
+ if [[ -z "${COS_AGENT:-}" ]] && [[ -f "${COS_STATE_DIR}/.agent" ]]; then
373
+ COS_AGENT="$(head -c 32 "${COS_STATE_DIR}/.agent" 2>/dev/null | tr -d '[:space:]' || true)"
374
+ fi
375
+
376
+ # Last-resort Claude compatibility marker; only use this when no
377
+ # stronger signal existed.
378
+ if [[ -z "${COS_AGENT:-}" ]] && [[ -n "${CLAUDE_PROJECT_DIR:-}" ]]; then
379
+ COS_AGENT="claude"
380
+ fi
381
+
382
+ COS_AGENT="${COS_AGENT:-unknown}"
383
+ fi
384
+
385
+ # Agent-private state dir. Every per-agent state file lives here so two
386
+ # DIFFERENT agents (e.g. Claude + Codex) attached to the same project never
387
+ # trample each other's state.
388
+ COS_AGENT_DIR="${COS_AGENT_DIR:-${COS_STATE_DIR}/${COS_AGENT}}"
389
+
390
+ # ---------------------------------------------------------------------------
391
+ # Panel identity — keys cognitive state per-PANEL of the SAME agent.
392
+ #
393
+ # Two panels of the same Claude/Codex instance on the same project
394
+ # share $COS_AGENT_DIR but get distinct $COS_PANEL_DIR subdirs so cognitive
395
+ # state files (.thinking_os-gate, .task-current, .active-skill, .doc-anchor,
396
+ # .memory-check, .zoom-checkpoint, .active-formula, .learn-suggestions and
397
+ # the dedupe markers) never collide.
398
+ #
399
+ # Resolution priority (highest first):
400
+ # 1. $COS_PANEL_ID env — explicit caller override (test harness, manual).
401
+ # 2. Adapter runtime stdin `session_id` — set later by
402
+ # cos_panel_upgrade_from_payload(). cos-env.sh CANNOT consume stdin
403
+ # itself (would steal it from the hook), so this slot is filled by
404
+ # the hook after it reads stdin.
405
+ # 3. Adapter-runtime env vars — declared per adapter in
406
+ # src/adapters/<id>/adapter.yaml::runtime_session_marker. Listed
407
+ # below in fall-through order so any adapter that exports its
408
+ # session id natively (Claude `CLAUDE_SESSION_ID`, Codex
409
+ # `CODEX_SESSION_ID`) is picked up. A new
410
+ # adapter adds its var here + to its adapter.yaml when it ships.
411
+ # 4. PPID-derived stable token — last resort. The hook's parent process
412
+ # is the agent runtime process for THIS panel; PIDs differ across
413
+ # panels of the same agent, so a hash over (PPID, agent) is a
414
+ # panel-stable identifier in the absence of a richer signal.
415
+ # ---------------------------------------------------------------------------
416
+ _cos_resolve_panel_id() {
417
+ local id="" v val ppid_val
418
+ if [[ -n "${COS_PANEL_ID:-}" ]]; then
419
+ printf '%s' "$COS_PANEL_ID"
420
+ return
421
+ fi
422
+ # CLAUDE_CODE_SESSION_ID is the real var Claude Code exports to every hook
423
+ # subprocess (stable per session, distinct per tab). It MUST lead — without
424
+ # it the resolver fell through to the ppid fallback (#4), which is ephemeral
425
+ # per tool call on Claude (fresh subprocess each fire) and scattered one
426
+ # session's state across dozens of panels. Keep the bare CLAUDE_SESSION_ID
427
+ # after it as a forward-compat alias. Stays in sync with each adapter.yaml
428
+ # ::runtime_session_marker.env_vars (per-agent SSOT for the var name).
429
+ # Real adapter session vars only (claude/codex) — matches the two
430
+ # src/adapters/*/adapter.yaml::runtime_session_marker.env_vars. Speculative
431
+ # GEMINI_*/ANTHROPIC_* removed: a future
432
+ # adapter adds its var here + to its adapter.yaml when it actually ships.
433
+ for v in CLAUDE_CODE_SESSION_ID CLAUDE_SESSION_ID CODEX_SESSION_ID; do
434
+ val="$(printenv "$v" 2>/dev/null || true)"
435
+ if [[ -n "$val" ]]; then
436
+ id="$val"
437
+ break
438
+ fi
439
+ done
440
+ if [[ -z "$id" ]]; then
441
+ ppid_val="${PPID:-$$}"
442
+ if command -v shasum >/dev/null 2>&1; then
443
+ id="ppid-$(printf 'p%s-%s' "$ppid_val" "${COS_AGENT:-unknown}" | shasum -a 1 | cut -c1-8)"
444
+ else
445
+ id="ppid-${ppid_val}"
446
+ fi
447
+ fi
448
+ # FS-safe: keep [A-Za-z0-9._-] only, cap at 64 chars
449
+ printf '%s' "$id" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-64
450
+ }
451
+ COS_PANEL_ID="${COS_PANEL_ID:-$(_cos_resolve_panel_id)}"
452
+ COS_PANEL_DIR="${COS_PANEL_DIR:-${COS_AGENT_DIR}/panels/${COS_PANEL_ID}}"
453
+ COS_SESSION_FILE="${COS_PANEL_DIR}/session-id"
454
+
455
+ # Classify how the panel id was derived so SessionStart can surface a collision
456
+ # risk (cheap, no side effects — the loud warning lives in session-context.sh).
457
+ # A ppid-* prefix means NO runtime session-id var was exported and we fell back
458
+ # to a PPID hash; two panels sharing a PPID would then collide on one panel dir.
459
+ case "$COS_PANEL_ID" in
460
+ ppid-*) export COS_PANEL_ID_SOURCE="ppid" ;;
461
+ *) export COS_PANEL_ID_SOURCE="session" ;;
462
+ esac
463
+
464
+ # Per-panel state file allowlist (basenames). Single source of truth used by
465
+ # cos_state_path / write-state.sh / check-state.sh to route a write to the
466
+ # panel dir vs the shared per-agent dir. Adding a new per-panel marker:
467
+ # append its basename here; the writer/reader auto-route from then on.
468
+ #
469
+ # Explicitly NOT in this list (intentionally shared across panels of the
470
+ # same agent): .agent, .model, .swimlane, .last-verify*, .last-decay,
471
+ # .turn-activity.log, .overrides.json, .hooks.log, sessions/, traces/,
472
+ # locks/, heartbeat, coding-os.db. Rationale per file in
473
+ # docs/engineering/state-files.md.
474
+ # .task-mode and the discovery-debounce markers moved to per-panel — two
475
+ # panels of the same agent must not share banner verbosity or nudge debounce.
476
+ COS_PER_PANEL_FILES="${COS_PER_PANEL_FILES:-.thinking_os-gate .task-current .active-skill .doc-anchor .memory-check .zoom-checkpoint .active-formula .learn-suggestions .zoom-prompt-suggested .docs-first-nudged .roles-composed .roles .role .graph-call-seen .abandoned-task-warned .graph-empty-warning-shown .doc-anchor-override .memory-check-override .uv-heredoc-override .task-mode .last-discovery-reminder session-id}"
477
+
478
+ # Model signal for the routing / learning pipeline. Priority:
479
+ # 1. Caller already exported COS_AGENT_MODEL (test harness / explicit).
480
+ # 2. Agent runtime env — adapters expose their preferred model env so
481
+ # consumer projects with different adapters share one resolver.
482
+ # 3. $COS_AGENT_DIR/.model — latest hook-stdin model snapshot (written
483
+ # by session-context.sh when the agent sends `model` in the hook
484
+ # payload). This is the dynamic source of truth — Claude Code
485
+ # rotates models mid-session and the hook payload carries the
486
+ # current choice.
487
+ # Everything falls back to empty (NULL) — routing_weights tolerates it.
488
+ if [[ -z "${COS_AGENT_MODEL:-}" ]]; then
489
+ # printenv keeps this portable across bash / zsh (zsh lacks ${!var}).
490
+ for _cand in CLAUDE_CODE_MODEL CODEX_MODEL ANTHROPIC_MODEL \
491
+ OPENAI_MODEL; do
492
+ _val="$(printenv "$_cand" 2>/dev/null || true)"
493
+ if [[ -n "$_val" ]]; then
494
+ COS_AGENT_MODEL="$_val"
495
+ break
496
+ fi
497
+ done
498
+ unset _cand _val
499
+ fi
500
+ if [[ -z "${COS_AGENT_MODEL:-}" ]] && [[ -f "${COS_AGENT_DIR}/.model" ]]; then
501
+ COS_AGENT_MODEL="$(head -c 64 "${COS_AGENT_DIR}/.model" 2>/dev/null \
502
+ | tr -d '[:space:]' || true)"
503
+ fi
504
+ COS_AGENT_MODEL="${COS_AGENT_MODEL:-}"
505
+
506
+ # Hook latency SLI — stamp wall-clock entry time when the hook sources
507
+ # this file. cos_log_hook subtracts it to emit dt=<ms>, giving the hook layer
508
+ # a real per-invocation duration without a wrapper. $EPOCHREALTIME is bash 5+
509
+ # (seconds.microseconds, e.g. 1717545600.123456); we keep the raw string and
510
+ # do integer-µs math in cos_log_hook so there is no float/locale dependency.
511
+ COS_HOOK_T0="${COS_HOOK_T0:-${EPOCHREALTIME:-}}"
512
+
513
+ export COS_STATE_DIR COS_AGENT_DIR COS_PANEL_ID COS_PANEL_DIR COS_SESSION_FILE COS_DB_PATH COS_HOOK_LOG COS_HOOK_BLOCK_LOG COS_HOOK_LOG_MAX_LINES COS_AGENT COS_AGENT_MODEL COS_PER_PANEL_FILES COS_HOOK_T0
514
+
515
+ # Activity heartbeat — written on every hook invocation so GC can measure
516
+ # inactivity rather than session age. Per-panel so orphan GC can target
517
+ # dead panels' subdirs without disturbing live siblings.
518
+ mkdir -p "${COS_PANEL_DIR}" 2>/dev/null || true
519
+ date +%s > "${COS_PANEL_DIR}/heartbeat" 2>/dev/null || true
520
+
521
+ # ---------------------------------------------------------------------------
522
+ # Identity helpers — pure reads, cheap enough to call per hook invocation.
523
+ # ---------------------------------------------------------------------------
524
+ cos_current_session() {
525
+ # Echo current session id or 'none' if not available. Fail-open.
526
+ # Read STRICTLY from panel-private session-id (no AGENT_DIR fallback —
527
+ # cross-panel leak protection: a different panel's session-id parked
528
+ # at $COS_AGENT_DIR/session-id must not become "ours"). When no panel
529
+ # session-id file exists yet, fall back to $COS_PANEL_ID so write-
530
+ # state.sh can still stamp ownership.
531
+ local raw
532
+ if [[ -f "$COS_SESSION_FILE" ]]; then
533
+ raw="$(head -c 64 "$COS_SESSION_FILE" 2>/dev/null | tr -d '[:space:]' || true)"
534
+ if [[ -n "$raw" ]]; then
535
+ echo "$raw"
536
+ return
537
+ fi
538
+ fi
539
+ if [[ -n "${COS_PANEL_ID:-}" ]]; then
540
+ echo "$COS_PANEL_ID"
541
+ return
542
+ fi
543
+ echo "none"
544
+ }
545
+
546
+ cos_current_task() {
547
+ # Echo current task marker in agent-friendly form:
548
+ # 1. If the marker contains a TASK-### token, return just that (e.g.,
549
+ # "TASK-NNN") so the agent can jump straight to docs/tasks/TASK-NNN-*.md.
550
+ # 2. Else if the marker is a governance/docs-update/exploratory slug,
551
+ # return it truncated to 40 chars (keeps log lines readable).
552
+ # 3. Else return 'none'.
553
+ # File format: "<session_id> <task_name>" (single whitespace).
554
+ # STRICTLY panel-scoped — never read AGENT_DIR fossil (cross-panel leak).
555
+ local f="${COS_PANEL_DIR}/.task-current"
556
+ if [[ ! -f "$f" ]]; then
557
+ echo "none"
558
+ return
559
+ fi
560
+
561
+ local line raw
562
+ line="$(head -1 "$f" 2>/dev/null || true)"
563
+ # Second whitespace-separated token onward is the task name.
564
+ raw="$(echo "$line" | awk '{$1=""; sub(/^ /, ""); print}' 2>/dev/null || true)"
565
+ if [[ -z "$raw" ]]; then
566
+ echo "none"
567
+ return
568
+ fi
569
+
570
+ # Prefer explicit TASK-### anywhere in the marker — shortest agent-useful form.
571
+ local match
572
+ match="$(echo "$raw" | grep -oE 'TASK-([A-Z][A-Z0-9]*-)?[0-9]+' | head -1 || true)"
573
+ if [[ -n "$match" ]]; then
574
+ echo "$match"
575
+ return
576
+ fi
577
+
578
+ # Fallback: truncate long governance slugs so log lines stay readable.
579
+ if [[ ${#raw} -gt 40 ]]; then
580
+ echo "${raw:0:37}..."
581
+ else
582
+ echo "$raw"
583
+ fi
584
+ }
585
+
586
+ # ---------------------------------------------------------------------------
587
+ # Hook activity logging — makes hook execution visible to the user.
588
+ # Format: [ISO-8601] [hook_name] [action] agent=X session=Y task=Z detail...
589
+ # Keeps human-readable shape so `grep`/`awk`/`tail -f` all still work; new
590
+ # identity fields are appended in front of the free-form detail so downstream
591
+ # filters (cos hooks-log --agent X) never need a JSON parser.
592
+ # ---------------------------------------------------------------------------
593
+ # ---------------------------------------------------------------------------
594
+ # cos_record_activity <category> <detail> — append a per-turn activity entry
595
+ # ---------------------------------------------------------------------------
596
+ # Logs operator-visible side effects (memory capture, graph reindex, task
597
+ # sync, skill invoke, worklog) for the per-turn pulse. session-context.sh
598
+ # reads this file on the next UserPromptSubmit, summarizes, then truncates.
599
+ # Claude Code does NOT render PostToolUse stdout, so this aggregation is
600
+ # the only reliable way to surface PostToolUse activity in the chat UI.
601
+ # Fail-open: never aborts the parent hook on logging failure.
602
+ # ---------------------------------------------------------------------------
603
+ cos_record_activity() {
604
+ local category="${1:-}"
605
+ local detail="${2:-}"
606
+ [[ -z "$category" ]] && return 0
607
+ [[ -z "${COS_AGENT_DIR:-}" ]] && return 0
608
+ local log ts size
609
+ log="$COS_AGENT_DIR/.turn-activity.log"
610
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null || echo "")"
611
+ {
612
+ mkdir -p "$COS_AGENT_DIR" 2>/dev/null
613
+ printf '%s\t%s\t%s\n' "$category" "$detail" "$ts" >> "$log"
614
+ } 2>/dev/null || true
615
+ # Opportunistic truncation — keep last 500 lines once the file exceeds 50 KB.
616
+ size=$(stat -f%z "$log" 2>/dev/null || stat -c%s "$log" 2>/dev/null || echo 0)
617
+ if [[ "$size" -gt 50000 ]]; then
618
+ {
619
+ tail -n 500 "$log" > "${log}.tmp" && mv "${log}.tmp" "$log"
620
+ } 2>/dev/null || true
621
+ fi
622
+ return 0
623
+ }
624
+
625
+ # Echo a python interpreter that can import cos's installed extras (the `rag`
626
+ # group: sentence_transformers). Bare python3 usually lacks them; the `cos`
627
+ # entry point's shebang points at the venv that has them. Falls back to python3.
628
+ cos_resolve_python() {
629
+ local gc ac py
630
+ gc="$(command -v cos 2>/dev/null || true)"
631
+ if [[ -z "$gc" ]]; then command -v python3 2>/dev/null || true; return; fi
632
+ ac="$gc"
633
+ if [[ -L "$gc" ]]; then
634
+ ac="$(python3 -c 'import os,sys; print(os.path.realpath(sys.argv[1]))' "$gc" 2>/dev/null || echo "$gc")"
635
+ fi
636
+ py="$(awk 'NR==1{sub(/^#!/,""); sub(/[[:space:]].*$/,""); print; exit}' "$ac" 2>/dev/null || true)"
637
+ if [[ -z "$py" || ! -x "$py" ]]; then py="$(dirname "$ac")/python3"; fi
638
+ [[ -x "$py" ]] || py="$(command -v python3 2>/dev/null || true)"
639
+ printf '%s\n' "$py"
640
+ }
641
+
642
+ # Elapsed wall-time (ms) since this hook sourced cos-env.sh. Pure integer-µs
643
+ # math on the two $EPOCHREALTIME strings (sec.usec) — no float, no locale,
644
+ # no awk. Echoes a non-negative integer; empty when T0 / now is unavailable
645
+ # (older bash without $EPOCHREALTIME) so the dt= field is simply omitted.
646
+ cos_hook_elapsed_ms() {
647
+ local t0="${COS_HOOK_T0:-}" now="${EPOCHREALTIME:-}"
648
+ [[ -z "$t0" || -z "$now" ]] && return 0
649
+ # Split sec.usec; pad/truncate the fractional part to exactly 6 digits.
650
+ local t0_s="${t0%%.*}" t0_f="${t0#*.}" now_s="${now%%.*}" now_f="${now#*.}"
651
+ [[ "$t0_f" == "$t0" ]] && t0_f="0"
652
+ [[ "$now_f" == "$now" ]] && now_f="0"
653
+ t0_f="${t0_f}000000"; t0_f="${t0_f:0:6}"
654
+ now_f="${now_f}000000"; now_f="${now_f:0:6}"
655
+ # Strip leading zeros so bash does not read them as octal.
656
+ local t0_us=$((10#$t0_s * 1000000 + 10#$t0_f))
657
+ local now_us=$((10#$now_s * 1000000 + 10#$now_f))
658
+ local dms=$(((now_us - t0_us) / 1000))
659
+ ((dms < 0)) && dms=0
660
+ printf '%s' "$dms"
661
+ }
662
+
663
+ cos_log_hook() {
664
+ local hook_name="${1:-unknown}"
665
+ local action="${2:-fire}"
666
+ shift 2 2>/dev/null || true
667
+ local detail="$*"
668
+
669
+ local ts agent session task model_bit dt_bit
670
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
671
+ agent="${COS_AGENT:-unknown}"
672
+ session="$(cos_current_session)"
673
+ task="$(cos_current_task)"
674
+ model_bit=""
675
+ if [[ -n "${COS_HOOK_RUNTIME_MODEL:-}" ]]; then
676
+ model_bit=" model=${COS_HOOK_RUNTIME_MODEL}"
677
+ fi
678
+ dt_bit=""
679
+ local _dms
680
+ _dms="$(cos_hook_elapsed_ms 2>/dev/null || true)"
681
+ if [[ -n "$_dms" ]]; then
682
+ dt_bit=" dt=${_dms}ms"
683
+ fi
684
+
685
+ # Fail-open: never let a logging error abort the hook.
686
+ {
687
+ mkdir -p "$(dirname "$COS_HOOK_LOG")" 2>/dev/null
688
+ local log_line
689
+ if [[ -n "$detail" ]]; then
690
+ log_line="[${ts}] [${hook_name}] [${action}] agent=${agent} session=${session} task=${task}${model_bit}${dt_bit} ${detail}"
691
+ else
692
+ log_line="[${ts}] [${hook_name}] [${action}] agent=${agent} session=${session} task=${task}${model_bit}${dt_bit}"
693
+ fi
694
+ echo "$log_line" >> "$COS_HOOK_LOG"
695
+
696
+ # Mirror 'block' lines into the block-only durable log so the high-volume
697
+ # main log's cap can't evict them before learn_extract mines them.
698
+ if [[ "$action" == "block" && -n "${COS_HOOK_BLOCK_LOG:-}" ]]; then
699
+ echo "$log_line" >> "$COS_HOOK_BLOCK_LOG"
700
+ local blk_lines
701
+ blk_lines=$(wc -l < "$COS_HOOK_BLOCK_LOG" 2>/dev/null || echo 0)
702
+ if [[ "$blk_lines" -gt $((COS_HOOK_LOG_MAX_LINES * 2)) ]]; then
703
+ tail -n "$COS_HOOK_LOG_MAX_LINES" "$COS_HOOK_BLOCK_LOG" > "${COS_HOOK_BLOCK_LOG}.tmp" \
704
+ && mv "${COS_HOOK_BLOCK_LOG}.tmp" "$COS_HOOK_BLOCK_LOG"
705
+ fi
706
+ fi
707
+
708
+ # F8 (TASK-447): make a BLOCK durable in the SQLite log_events store the
709
+ # logging_os sink owns so cos_log_query / error_sweep surface it — not just
710
+ # the text logs above. Reuse the shared shell→DB writer (DB-only here).
711
+ if [[ "$action" == "block" ]] && command -v python3 >/dev/null 2>&1; then
712
+ local _db_helper
713
+ _db_helper="$(_cos_helpers_dir)/cos_say_json.py"
714
+ if [[ -f "$_db_helper" ]]; then
715
+ python3 "$_db_helper" "$ts" "ERROR" "hook.${hook_name}" "${detail:-blocked}" \
716
+ "action=block session=${session} task=${task}" >/dev/null 2>&1 || true
717
+ fi
718
+ fi
719
+
720
+ # Opportunistic truncation — keep only last N lines when file grows past 2x cap.
721
+ if [[ -f "$COS_HOOK_LOG" ]]; then
722
+ local lines
723
+ lines=$(wc -l < "$COS_HOOK_LOG" 2>/dev/null || echo 0)
724
+ if [[ "$lines" -gt $((COS_HOOK_LOG_MAX_LINES * 2)) ]]; then
725
+ tail -n "$COS_HOOK_LOG_MAX_LINES" "$COS_HOOK_LOG" > "${COS_HOOK_LOG}.tmp" \
726
+ && mv "${COS_HOOK_LOG}.tmp" "$COS_HOOK_LOG"
727
+ fi
728
+ fi
729
+ } 2>/dev/null || true
730
+ }
731
+
732
+
733
+ # ---------------------------------------------------------------------------
734
+ # cos_read_stdin_bounded — drain stdin with a hard timeout
735
+ #
736
+ # WHY
737
+ # Hooks read JSON from stdin (PreToolUse / PostToolUse / Stop / etc.).
738
+ # When invoked from a terminal (`bash hook.sh` for testing), stdin is
739
+ # a tty and `cat` would block forever waiting for the user. perl's
740
+ # alarm() gives us a portable stdin read with a hard ceiling — bash's
741
+ # `read -t` doesn't slurp multi-line JSON, and `timeout(1)` is missing
742
+ # on macOS by default. Also: when the agent runtime sends nothing, we
743
+ # want to fall through to defaults rather than hang the hook.
744
+ #
745
+ # USAGE
746
+ # INPUT="$(cos_read_stdin_bounded 2)" # 2-second ceiling
747
+ #
748
+ # CONTRACT
749
+ # - Returns whatever bytes arrived on stdin (possibly empty).
750
+ # - On timeout: prints nothing, returns 0 (fail-open).
751
+ # - When stdin is a tty: returns immediately with empty output.
752
+ # ---------------------------------------------------------------------------
753
+ cos_read_stdin_bounded() {
754
+ local timeout_s="${1:-2}"
755
+ if [[ -t 0 ]]; then
756
+ return 0
757
+ fi
758
+ perl -e '
759
+ my $timeout = shift // 2;
760
+ eval {
761
+ local $SIG{ALRM} = sub { die "cos_stdin_timeout\n" };
762
+ alarm $timeout;
763
+ local $/;
764
+ my $data = <STDIN>;
765
+ alarm 0;
766
+ print $data if defined $data;
767
+ };
768
+ exit 0;
769
+ ' "$timeout_s" 2>/dev/null || true
770
+ }
771
+
772
+
773
+ # ---------------------------------------------------------------------------
774
+ # cos_require_or_skip — fail-open when a required CLI binary is absent
775
+ #
776
+ # WHY
777
+ # Hooks hard-depend on `jq` and `python3`. Fresh-clone or minimal-CI
778
+ # runtimes may lack one. With `set -euo pipefail` a missing binary
779
+ # would kill the hook (exit ≠ 0 → BLOCK from the agent's POV). This
780
+ # helper logs a `skip reason=missing_dep` event and exits 0 so the
781
+ # hook degrades gracefully.
782
+ #
783
+ # USAGE (top of hook, after sourcing cos-env.sh)
784
+ # cos_require_or_skip jq block-secrets
785
+ #
786
+ # STRICT MODE
787
+ # COS_STRICT_DEPS=1 makes missing deps exit 2 (block) instead of 0.
788
+ # Opt-in for CI that demands a fully-set-up environment.
789
+ # ---------------------------------------------------------------------------
790
+ cos_require_or_skip() {
791
+ local bin="$1"
792
+ local hook_id="${2:-unknown-hook}"
793
+ if command -v "$bin" >/dev/null 2>&1; then
794
+ return 0
795
+ fi
796
+ cos_log_hook "$hook_id" "skip" "reason=missing_dep dep=$bin" 2>/dev/null || true
797
+ if [[ "${COS_STRICT_DEPS:-0}" == "1" ]]; then
798
+ echo "BLOCKED: hook $hook_id needs '$bin' on PATH (COS_STRICT_DEPS=1)" >&2
799
+ exit 2
800
+ fi
801
+ exit 0
802
+ }
803
+
804
+
805
+ # ---------------------------------------------------------------------------
806
+ # cos_require_parser <hook_id> — fail-CLOSED dep guard for harm gates.
807
+ #
808
+ # WHY
809
+ # An irreversible/integrity-harm gate (block-secrets, block-dangerous-
810
+ # commands, ...) must be able to read its decision input. The old
811
+ # `jq -r '...' || echo ""` returned empty when jq was missing → the gate
812
+ # exited 0 (allow) and silently disabled itself. observability-eye I8:
813
+ # a guard that cannot evaluate must DENY, not allow.
814
+ #
815
+ # CONTRACT
816
+ # Run at the TOP of a harm gate, OUTSIDE command-substitution (so the
817
+ # exit can actually block). Returns 0 when at least one JSON parser
818
+ # (jq OR python3) is on PATH; otherwise captures + exit 2 (block).
819
+ # python3 is a hard dep of coding-os, so the realistic degraded case
820
+ # (jq absent) still passes — cos_json_field falls back to python3.
821
+ #
822
+ # ESCAPE
823
+ # COS_ALLOW_MISSING_DEPS=1 lets a human bootstrap (install jq/python3)
824
+ # when both are absent.
825
+ # ---------------------------------------------------------------------------
826
+ cos_require_parser() {
827
+ local hook_id="${1:-unknown-hook}"
828
+ if command -v jq >/dev/null 2>&1 || command -v python3 >/dev/null 2>&1; then
829
+ return 0
830
+ fi
831
+ if [[ "${COS_ALLOW_MISSING_DEPS:-0}" == "1" ]]; then
832
+ cos_log_hook "$hook_id" "skip" "reason=no_parser_override" 2>/dev/null || true
833
+ return 0
834
+ fi
835
+ cos_say error "hook.${hook_id}" "no JSON parser (jq/python3) on PATH — gate fails closed" 2>/dev/null || true
836
+ cos_log_hook "$hook_id" "block" "rule=no-parser-fail-closed" 2>/dev/null || true
837
+ echo "BLOCKED: $hook_id needs jq or python3 to evaluate safety — neither found. Install one, or set COS_ALLOW_MISSING_DEPS=1 to bootstrap." >&2
838
+ exit 2
839
+ }
840
+
841
+
842
+ # ---------------------------------------------------------------------------
843
+ # cos_json_field <path...> — extract first non-empty string field from a hook
844
+ # JSON envelope read on stdin. jq fast-path, python3 fallback.
845
+ #
846
+ # Echoes the value (empty if the field is genuinely absent). Does NOT block
847
+ # on a missing parser — that is cos_require_parser's job, which must run
848
+ # outside command-substitution. Replaces the `jq -r '...' || echo ""` idiom
849
+ # whose empty-on-missing-jq result drove the harm-gate fail-open class.
850
+ #
851
+ # USAGE
852
+ # TOOL=$(printf '%s' "$INPUT" | cos_json_field tool_name)
853
+ # CONTENT=$(printf '%s' "$INPUT" | cos_json_field tool_input.new_string tool_input.content)
854
+ # ---------------------------------------------------------------------------
855
+ cos_json_field() {
856
+ local input filter="" p
857
+ input="$(cat)"
858
+ if command -v jq >/dev/null 2>&1; then
859
+ for p in "$@"; do
860
+ [[ -n "$filter" ]] && filter+=" // "
861
+ filter+=".${p}"
862
+ done
863
+ filter+=" // empty"
864
+ printf '%s' "$input" | jq -r "$filter" 2>/dev/null || true
865
+ return 0
866
+ fi
867
+ if command -v python3 >/dev/null 2>&1; then
868
+ printf '%s' "$input" \
869
+ | python3 "$(_cos_helpers_dir)/json_field.py" "$@" 2>/dev/null || true
870
+ return 0
871
+ fi
872
+ return 0
873
+ }
874
+
875
+
876
+ # ---------------------------------------------------------------------------
877
+ # cos_sanity_check — verify project state before a hook does real work
878
+ #
879
+ # WHY
880
+ # Hooks assume the coding-os layout: $COS_STATE_DIR exists, the project
881
+ # has docs/ and core/, etc. When a hook fires in an unconfigured project
882
+ # (fresh clone, mid-cos-init, accidentally-running-elsewhere) the bare
883
+ # jq/python invocations emit cryptic errors. This helper centralises the
884
+ # "is the world sane?" probe so individual hooks can fail-open cleanly.
885
+ #
886
+ # USAGE
887
+ # cos_sanity_check <hook_id> [check1 check2 ...]
888
+ # - returns 0 if all named checks pass.
889
+ # - on failure: logs "skip reason=sanity_<check>" and exits 0 (fail-open).
890
+ #
891
+ # CHECKS
892
+ # state_dir — $COS_STATE_DIR exists and is writable.
893
+ # agent_dir — $COS_AGENT_DIR exists.
894
+ # db — $COS_DB_PATH exists.
895
+ # tasks_dir — docs/tasks/ exists relative to project root.
896
+ # board_os — src/core/board_os/ exists.
897
+ # git — .git/ exists somewhere up the tree.
898
+ #
899
+ # Default check set when no args: state_dir.
900
+ # ---------------------------------------------------------------------------
901
+ # ---------------------------------------------------------------------------
902
+ # cos_one_shot_override — unified one-shot bypass for blocking hooks
903
+ #
904
+ # WHY
905
+ # Several hooks (block-hardcoded-literals, block-uv-heredoc,
906
+ # enforce-doc-anchor, enforce-memory-check, enforce-template) historically
907
+ # consumed their own bypass dotfile in different directories with
908
+ # different name prefixes. That made overrides:
909
+ # - hard to discover (no central list of what bypasses are active)
910
+ # - impossible to audit (no trail of who/when/why)
911
+ # - error-prone (touching the wrong path silently fails)
912
+ #
913
+ # UNIFIED MODEL
914
+ # Single JSON registry: $COS_STATE_DIR/.overrides.json
915
+ # {
916
+ # "doc-anchor": {"reason": "spike", "ts": 1714234567, "agent": "claude"},
917
+ # "memory-check": {...}
918
+ # }
919
+ # Audit trail (append-only): $COS_STATE_DIR/.overrides.audit.log
920
+ #
921
+ # CONTRACT
922
+ # cos_one_shot_override <key> → returns 0 if override found and consumed,
923
+ # 1 otherwise. On hit, appends to audit log
924
+ # and removes the entry from the registry
925
+ # (or deletes the legacy dotfile).
926
+ #
927
+ # BACK-COMPAT
928
+ # Legacy paths remain consulted so existing tooling and docs keep working:
929
+ # $COS_AGENT_DIR/.<key>-override
930
+ # $COS_STATE_DIR/.<key>-override (literals only, historic)
931
+ # When a legacy file is found, it is consumed identically. New writes
932
+ # should prefer the unified registry, but legacy is permanently supported.
933
+ #
934
+ # SETTING AN OVERRIDE
935
+ # echo '{"doc-anchor": {"reason": "spike-XYZ"}}' > $COS_STATE_DIR/.overrides.json
936
+ # (or simply: touch $COS_AGENT_DIR/.doc-anchor-override)
937
+ # ---------------------------------------------------------------------------
938
+ cos_one_shot_override() {
939
+ local key="${1:-}"
940
+ [[ -z "$key" ]] && return 1
941
+ local reg="$COS_STATE_DIR/.overrides.json"
942
+ local audit="$COS_STATE_DIR/.overrides.audit.log"
943
+ local legacy_agent="$COS_AGENT_DIR/.${key}-override"
944
+ local legacy_shared="$COS_STATE_DIR/.${key}-override"
945
+ local ts
946
+ ts="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
947
+
948
+ _audit() {
949
+ {
950
+ mkdir -p "$(dirname "$audit")" 2>/dev/null
951
+ echo "[${ts}] consumed key=${key} agent=${COS_AGENT:-unknown} source=$1" >> "$audit"
952
+ } 2>/dev/null || true
953
+ }
954
+
955
+ local consume_helper
956
+ consume_helper="$(_cos_helpers_dir)/consume_override.py"
957
+ if [[ -f "$reg" ]] && command -v python3 >/dev/null 2>&1 && [[ -f "$consume_helper" ]]; then
958
+ if python3 "$consume_helper" "$reg" "$key" >/dev/null 2>&1; then
959
+ _audit "registry"
960
+ return 0
961
+ fi
962
+ fi
963
+
964
+ # 2. Legacy per-agent dotfile.
965
+ if [[ -f "$legacy_agent" ]]; then
966
+ rm -f "$legacy_agent" 2>/dev/null || true
967
+ _audit "legacy_agent"
968
+ return 0
969
+ fi
970
+
971
+ # 3. Legacy shared dotfile (historic literals path).
972
+ if [[ -f "$legacy_shared" ]]; then
973
+ rm -f "$legacy_shared" 2>/dev/null || true
974
+ _audit "legacy_shared"
975
+ return 0
976
+ fi
977
+
978
+ return 1
979
+ }
980
+
981
+ # ---------------------------------------------------------------------------
982
+ # cos_say <level> <scope> <msg> [k=v ...] — shell parity for logging_os.
983
+ #
984
+ # Schema mirrors src/core/logging_os/ (docs/engineering/logging_os.md).
985
+ # Three renders auto-detected from env + isatty(stderr); fans out to stderr,
986
+ # $COS_LOG_FILE (always short text), and ${COS_LOG_FILE}.jsonl (always json).
987
+ # Fail-open: never aborts the parent hook on logging failure.
988
+ # ---------------------------------------------------------------------------
989
+ cos_say() {
990
+ local level scope msg
991
+ level="${1:-info}"
992
+ scope="${2:-shell.unknown}"
993
+ msg="${3:-}"
994
+ shift 3 2>/dev/null || true
995
+ level="$(echo "$level" | tr '[:lower:]' '[:upper:]')"
996
+ local level_value=20
997
+ case "$level" in
998
+ DEBUG) level_value=10 ;;
999
+ INFO) level_value=20 ;;
1000
+ OK) level_value=21 ;;
1001
+ WARN) level_value=30 ;;
1002
+ ERROR) level_value=40 ;;
1003
+ FATAL) level_value=50 ;;
1004
+ *) level="INFO"; level_value=20 ;;
1005
+ esac
1006
+
1007
+ local floor_name floor_value=20
1008
+ floor_name="$(echo "${COS_LOG_LEVEL:-info}" | tr '[:lower:]' '[:upper:]')"
1009
+ case "$floor_name" in
1010
+ DEBUG) floor_value=10 ;;
1011
+ INFO) floor_value=20 ;;
1012
+ OK) floor_value=21 ;;
1013
+ WARN) floor_value=30 ;;
1014
+ ERROR) floor_value=40 ;;
1015
+ FATAL) floor_value=50 ;;
1016
+ esac
1017
+ # Per-sink flooring (TASK-473): the console floor (COS_LOG_LEVEL) gates the
1018
+ # human sinks (stderr/text/jsonl); the durable floor (COS_LOG_DB_MIN_LEVEL,
1019
+ # re-applied inside cos_say_json.py) gates the log_events row independently.
1020
+ # Short-circuit only when the event clears NEITHER floor — flooring at the
1021
+ # console level alone here dropped a WARN before the durable store saw it.
1022
+ local db_floor_name db_floor_value=30
1023
+ db_floor_name="$(echo "${COS_LOG_DB_MIN_LEVEL:-WARN}" | tr '[:lower:]' '[:upper:]')"
1024
+ case "$db_floor_name" in
1025
+ DEBUG) db_floor_value=10 ;;
1026
+ INFO) db_floor_value=20 ;;
1027
+ OK) db_floor_value=21 ;;
1028
+ WARN) db_floor_value=30 ;;
1029
+ ERROR) db_floor_value=40 ;;
1030
+ FATAL) db_floor_value=50 ;;
1031
+ esac
1032
+ local min_floor="$floor_value"
1033
+ [[ "$db_floor_value" -lt "$min_floor" ]] && min_floor="$db_floor_value"
1034
+ if [[ "$level_value" -lt "$min_floor" ]]; then
1035
+ return 0
1036
+ fi
1037
+ local below_console=0
1038
+ [[ "$level_value" -lt "$floor_value" ]] && below_console=1
1039
+
1040
+ local kv=""
1041
+ if [[ $# -gt 0 ]]; then
1042
+ kv="$*"
1043
+ fi
1044
+
1045
+ local mode
1046
+ if [[ "${COS_LOG_JSON:-}" == "1" ]]; then
1047
+ mode="json"
1048
+ elif [[ "${COS_LOG_FORCE_PRETTY:-}" == "1" ]]; then
1049
+ mode="pretty"
1050
+ elif [[ -n "${NO_COLOR:-}" ]]; then
1051
+ mode="short"
1052
+ elif [[ -t 2 ]]; then
1053
+ mode="pretty"
1054
+ else
1055
+ mode="short"
1056
+ fi
1057
+
1058
+ local ts_iso ts_short
1059
+ ts_iso="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo "")"
1060
+ ts_short="$(date -u +%H:%M:%S 2>/dev/null || echo "")"
1061
+
1062
+ local emoji="" color="" reset=""
1063
+ case "$level" in
1064
+ DEBUG) emoji="🔍 "; color=$'\e[90m' ;;
1065
+ INFO) emoji="ℹ️ "; color=$'\e[36m' ;;
1066
+ OK) emoji="✅"; color=$'\e[32m' ;;
1067
+ WARN) emoji="⚠️ "; color=$'\e[33m' ;;
1068
+ ERROR) emoji="❌"; color=$'\e[31m' ;;
1069
+ FATAL) emoji="💀"; color=$'\e[1;31m' ;;
1070
+ esac
1071
+ reset=$'\e[0m'
1072
+
1073
+ local level_padded
1074
+ level_padded="$(printf '%-5s' "$level")"
1075
+ local short_line="${ts_short} ${level_padded} ${scope} ${msg}"
1076
+ [[ -n "$kv" ]] && short_line="${short_line} ${kv}"
1077
+
1078
+ local helper
1079
+ helper="$(_cos_helpers_dir)/cos_say_json.py"
1080
+ local json_line=""
1081
+ if command -v python3 >/dev/null 2>&1 && [[ -f "$helper" ]]; then
1082
+ json_line="$(python3 "$helper" "$ts_iso" "$level" "$scope" "$msg" "$kv" 2>/dev/null || true)"
1083
+ fi
1084
+
1085
+ local stderr_line
1086
+ case "$mode" in
1087
+ pretty)
1088
+ local pad
1089
+ pad="$(printf '%-20s' "$scope")"
1090
+ stderr_line="${emoji} ${ts_short} ${color}${level_padded}${reset} ${pad} ${msg}"
1091
+ [[ -n "$kv" ]] && stderr_line="${stderr_line} ${kv}"
1092
+ ;;
1093
+ json)
1094
+ stderr_line="${json_line:-$short_line}"
1095
+ ;;
1096
+ *)
1097
+ stderr_line="$short_line"
1098
+ ;;
1099
+ esac
1100
+
1101
+ # Human sinks respect the console floor; the durable log_events row was already
1102
+ # written above by cos_say_json.py (gated by COS_LOG_DB_MIN_LEVEL) (TASK-473).
1103
+ if [[ "$below_console" -eq 0 ]]; then
1104
+ printf '%s\n' "$stderr_line" >&2 2>/dev/null || true
1105
+ fi
1106
+
1107
+ local log_file="${COS_LOG_FILE:-${COS_STATE_DIR}/.cos.log}"
1108
+ if [[ "$below_console" -eq 0 ]]; then
1109
+ {
1110
+ mkdir -p "$(dirname "$log_file")" 2>/dev/null
1111
+ printf '%s\n' "$short_line" >> "$log_file"
1112
+ } 2>/dev/null || true
1113
+
1114
+ if [[ -n "$json_line" ]]; then
1115
+ {
1116
+ printf '%s\n' "$json_line" >> "${log_file}.jsonl"
1117
+ } 2>/dev/null || true
1118
+ fi
1119
+ fi
1120
+
1121
+ return 0
1122
+ }
1123
+
1124
+ cos_sanity_check() {
1125
+ local hook_id="${1:-unknown-hook}"
1126
+ shift 2>/dev/null || true
1127
+ local checks=("$@")
1128
+ if [[ ${#checks[@]} -eq 0 ]]; then
1129
+ checks=(state_dir)
1130
+ fi
1131
+
1132
+ local check fail
1133
+ for check in "${checks[@]}"; do
1134
+ fail=""
1135
+ case "$check" in
1136
+ state_dir)
1137
+ [[ -d "$COS_STATE_DIR" ]] || fail="state_dir_missing"
1138
+ ;;
1139
+ agent_dir)
1140
+ [[ -d "$COS_AGENT_DIR" ]] || fail="agent_dir_missing"
1141
+ ;;
1142
+ db)
1143
+ [[ -f "$COS_DB_PATH" ]] || fail="db_missing"
1144
+ ;;
1145
+ tasks_dir)
1146
+ [[ -d "${CLAUDE_PROJECT_DIR:-.}/docs/tasks" ]] \
1147
+ || [[ -d "./docs/tasks" ]] \
1148
+ || fail="tasks_dir_missing"
1149
+ ;;
1150
+ board_os)
1151
+ [[ -d "${CLAUDE_PROJECT_DIR:-.}/src/core/board_os" ]] \
1152
+ || [[ -d "${CLAUDE_PROJECT_DIR:-.}/core/board_os" ]] \
1153
+ || [[ -d "./src/core/board_os" ]] \
1154
+ || [[ -d "./core/board_os" ]] \
1155
+ || fail="board_os_missing"
1156
+ ;;
1157
+ git)
1158
+ local d
1159
+ d="$(pwd)"
1160
+ local found=0
1161
+ while [[ "$d" != "/" ]]; do
1162
+ [[ -d "$d/.git" ]] && { found=1; break; }
1163
+ d="$(dirname "$d")"
1164
+ done
1165
+ [[ "$found" -eq 1 ]] || fail="git_missing"
1166
+ ;;
1167
+ *)
1168
+ fail="unknown_check_${check}"
1169
+ ;;
1170
+ esac
1171
+ if [[ -n "$fail" ]]; then
1172
+ cos_log_hook "$hook_id" "skip" "reason=sanity_${fail}" 2>/dev/null || true
1173
+ exit 0
1174
+ fi
1175
+ done
1176
+ return 0
1177
+ }
1178
+
1179
+ # ---------------------------------------------------------------------------
1180
+ # cos_state_path <basename-or-path>
1181
+ #
1182
+ # Single-source-of-truth path resolver for state files. Centralises the
1183
+ # per-panel / per-agent routing decision so write-state.sh and check-state.sh
1184
+ # (and any future state-touching hook) never re-implement the allowlist
1185
+ # inline. Behavior:
1186
+ #
1187
+ # - bare basename ".thinking_os-gate" and basename is in
1188
+ # $COS_PER_PANEL_FILES → returns "$COS_PANEL_DIR/.thinking_os-gate"
1189
+ # - bare basename ".task-mode" (not in allowlist) → returns
1190
+ # "$COS_AGENT_DIR/.task-mode"
1191
+ # - path already containing a slash (absolute or relative): if its
1192
+ # basename is in the per-panel allowlist AND the parent dir resolves
1193
+ # to $COS_AGENT_DIR, redirect to $COS_PANEL_DIR. Otherwise return as
1194
+ # given (back-compat for callers that pass shared-dir paths like
1195
+ # "$COS_STATE_DIR/.capture-errors.log").
1196
+ # ---------------------------------------------------------------------------
1197
+ cos_state_path() {
1198
+ local arg="${1:?Usage: cos_state_path <basename-or-path>}"
1199
+ local base parent
1200
+ base="$(basename "$arg")"
1201
+ case " $COS_PER_PANEL_FILES " in
1202
+ *" $base "*)
1203
+ if [[ "$arg" == */* ]]; then
1204
+ parent="$(cd "$(dirname "$arg")" 2>/dev/null && pwd || dirname "$arg")"
1205
+ local agent_real
1206
+ agent_real="$(cd "$COS_AGENT_DIR" 2>/dev/null && pwd || echo "$COS_AGENT_DIR")"
1207
+ if [[ "$parent" == "$agent_real" ]]; then
1208
+ printf '%s/%s' "$COS_PANEL_DIR" "$base"
1209
+ return
1210
+ fi
1211
+ printf '%s' "$arg"
1212
+ return
1213
+ fi
1214
+ printf '%s/%s' "$COS_PANEL_DIR" "$base"
1215
+ ;;
1216
+ *)
1217
+ if [[ "$arg" == /* ]] || [[ "$arg" == */* ]]; then
1218
+ printf '%s' "$arg"
1219
+ else
1220
+ printf '%s/%s' "$COS_AGENT_DIR" "$arg"
1221
+ fi
1222
+ ;;
1223
+ esac
1224
+ }
1225
+
1226
+ # ---------------------------------------------------------------------------
1227
+ # cos_panel_upgrade_from_payload <json-payload>
1228
+ #
1229
+ # Hook helper. After a hook reads stdin via cos_read_stdin_bounded, call
1230
+ # this with the payload to upgrade $COS_PANEL_ID from the agent runtime's
1231
+ # stdin session_id field — strongest panel signal available. Idempotent;
1232
+ # no-op when the payload lacks session_id, jq is missing, or the id is
1233
+ # already current.
1234
+ #
1235
+ # Why a separate helper instead of consuming stdin in cos-env.sh: stdin is
1236
+ # one-shot. cos-env.sh runs at hook source-time, before the hook itself
1237
+ # reads stdin. Stealing stdin from cos-env would break every hook.
1238
+ # ---------------------------------------------------------------------------
1239
+ cos_panel_upgrade_from_payload() {
1240
+ local payload="${1:-}"
1241
+ [[ -z "$payload" ]] && return 0
1242
+ command -v jq >/dev/null 2>&1 || return 0
1243
+ local sid
1244
+ sid="$(printf '%s' "$payload" | jq -r '.session_id // .sessionId // empty' 2>/dev/null || true)"
1245
+ [[ -z "$sid" ]] && return 0
1246
+ sid="$(printf '%s' "$sid" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-64)"
1247
+ [[ -z "$sid" ]] && return 0
1248
+ if [[ "$COS_PANEL_ID" != "$sid" ]]; then
1249
+ COS_PANEL_ID="$sid"
1250
+ COS_PANEL_DIR="${COS_AGENT_DIR}/panels/${COS_PANEL_ID}"
1251
+ COS_SESSION_FILE="${COS_PANEL_DIR}/session-id"
1252
+ export COS_PANEL_ID COS_PANEL_DIR COS_SESSION_FILE
1253
+ mkdir -p "$COS_PANEL_DIR" 2>/dev/null || true
1254
+ fi
1255
+ # Initialize the panel session-id file when missing. Without this, every
1256
+ # reader that goes through $COS_SESSION_FILE (the SSOT for "who am I")
1257
+ # sees an empty value, and the per-session ownership check rejects every
1258
+ # state file as un-owned — the cascade that surfaces as banner ses=? ·
1259
+ # task=none · gate=unset on hooks that only have agent-level legacy
1260
+ # state. SessionStart:startup writes a `ses-<agent>-<ts>-<rand>` id; for
1261
+ # resume/compact/user-prompt-submit (where startup never fires for this
1262
+ # panel), we mirror the agent-level session-id when present, else seed
1263
+ # with the panel id (stable across the conversation).
1264
+ if [[ ! -s "$COS_SESSION_FILE" ]]; then
1265
+ local seed=""
1266
+ if [[ -s "${COS_AGENT_DIR}/session-id" ]]; then
1267
+ seed="$(tr -d '\n\r' < "${COS_AGENT_DIR}/session-id" 2>/dev/null || true)"
1268
+ fi
1269
+ [[ -z "$seed" ]] && seed="ses-${COS_AGENT}-${COS_PANEL_ID}"
1270
+ local _tmp="${COS_SESSION_FILE}.tmp.$$"
1271
+ printf '%s\n' "$seed" > "$_tmp" 2>/dev/null \
1272
+ && mv -f "$_tmp" "$COS_SESSION_FILE" 2>/dev/null \
1273
+ || rm -f "$_tmp" 2>/dev/null
1274
+ fi
1275
+ }
1276
+
1277
+ # ---------------------------------------------------------------------------
1278
+ # cos_task_bound_in_live_sibling <task-id>
1279
+ #
1280
+ # rc 0 when ANOTHER panel of this agent binds <task-id> via its .task-current
1281
+ # (last whitespace field — write-state.sh prefixes a session id) AND that
1282
+ # panel's heartbeat is fresh. A task actively driven in a sibling panel is
1283
+ # not stranded: nudging an idle panel about it invites a "rescue" park of
1284
+ # live work (the phantom NULL-reason in_progress→icebox reverts).
1285
+ # ---------------------------------------------------------------------------
1286
+ cos_task_bound_in_live_sibling() {
1287
+ local task="${1:-}" pd ptask hb now
1288
+ [[ -n "$task" && -d "${COS_AGENT_DIR:-}/panels" ]] || return 1
1289
+ now=$(date +%s)
1290
+ for pd in "${COS_AGENT_DIR}/panels"/*/; do
1291
+ [[ -d "$pd" ]] || continue
1292
+ [[ "${pd%/}" == "${COS_PANEL_DIR:-}" ]] && continue
1293
+ ptask="$(awk '{print $NF}' "${pd}.task-current" 2>/dev/null | head -1 || true)"
1294
+ [[ "$ptask" == "$task" ]] || continue
1295
+ hb=$(stat -c %Y "${pd}heartbeat" 2>/dev/null || stat -f %m "${pd}heartbeat" 2>/dev/null || echo 0)
1296
+ if [[ "$hb" -gt 0 && $((now - hb)) -lt "${COS_SIBLING_BIND_TTL:-3600}" ]]; then
1297
+ return 0
1298
+ fi
1299
+ done
1300
+ return 1
1301
+ }