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/web/routes/hub.py ADDED
@@ -0,0 +1,1219 @@
1
+ """core.web.routes.hub — global Hub registry endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ import os
8
+ import re
9
+ import shutil
10
+ import subprocess
11
+ import sys
12
+ import uuid
13
+ from pathlib import Path
14
+ from typing import Optional
15
+
16
+ from fastapi import APIRouter, Body, Query
17
+ from fastapi.responses import JSONResponse
18
+
19
+ _REPO_ROOT = Path(__file__).resolve().parent.parent.parent.parent
20
+ if str(_REPO_ROOT) not in sys.path:
21
+ sys.path.insert(0, str(_REPO_ROOT))
22
+
23
+ logger = logging.getLogger("coding_os.web.hub")
24
+ router = APIRouter(prefix="/api/hub", tags=["hub"])
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Shared helpers
29
+ # ---------------------------------------------------------------------------
30
+
31
+
32
+ def _looks_like_cos_project(path: Path) -> bool:
33
+ """Quick heuristic: directory exists and has .coding-os/ inside."""
34
+ try:
35
+ return path.is_dir() and (path / ".coding-os").is_dir()
36
+ except OSError:
37
+ return False
38
+
39
+
40
+ def _is_hub_state_dir(coding_os: Path) -> bool:
41
+ """True when this .coding-os/ is the GLOBAL hub state dir, not a project's.
42
+
43
+ The global ~/.coding-os/ carries the hub registry + pid; a project's
44
+ .coding-os/ never does (hub-architecture.md § address spaces). Lets the
45
+ home/global dir be rejected as a phantom "runtime-cwd" project.
46
+ """
47
+ try:
48
+ return (coding_os / "registry.json").is_file() or (coding_os / "hub.pid").is_file()
49
+ except OSError:
50
+ return False
51
+
52
+
53
+ def _is_meta_repo(path: Path) -> bool:
54
+ """True when `path` is the coding-os meta-repo checkout itself.
55
+
56
+ Recognises the dogfood case: the meta-repo lives at
57
+ `<somewhere>/coding-os/` AND ships its own `.coding-os/`
58
+ (dogfood — Principle P5). It must never be flagged as "nested
59
+ inside another coding-os project" just because a higher-up
60
+ ancestor (e.g. `~/.coding-os/` scratch dir) happens to have a
61
+ `.coding-os/`.
62
+ """
63
+ try:
64
+ return (
65
+ (path / "src" / "cli" / "main.py").is_file()
66
+ and (path / "src" / "core" / "thinking_os" / "server.py").is_file()
67
+ and (path / "pyproject.toml").is_file()
68
+ )
69
+ except OSError:
70
+ return False
71
+
72
+
73
+ def _is_registered_project(path: Path) -> bool:
74
+ """True iff `path` (resolved) is recorded in the cli registry.
75
+
76
+ The nested-project check should only fire when the enclosing
77
+ ancestor is an *actual registered project*. A stray
78
+ `~/.coding-os/` (left over from a test run, or the user's
79
+ scratch dir) is not a project — blocking on it has rejected
80
+ legitimate contributor checkouts (issue reported 2026-05-23).
81
+ """
82
+ try:
83
+ from cli.registry import load_registry # type: ignore
84
+
85
+ reg = load_registry()
86
+ except Exception:
87
+ return False
88
+ target = str(path.resolve())
89
+ for p in reg.projects:
90
+ try:
91
+ if str(Path(p.path).resolve()) == target:
92
+ return True
93
+ except (OSError, RuntimeError):
94
+ continue
95
+ return False
96
+
97
+
98
+ def _ancestor_with_coding_os(path: Path) -> Path | None:
99
+ """Walk parents looking for an enclosing **registered** coding-os
100
+ project root.
101
+
102
+ Returns the first ancestor (strictly above `path`) that has a
103
+ `.coding-os/` directory AND is recorded in the cli registry —
104
+ a true nesting violation. An ancestor with just `.coding-os/`
105
+ on disk but not in the registry is treated as noise (scratch,
106
+ leftover) and ignored.
107
+
108
+ Also skips the check entirely when `path` is the meta-repo
109
+ itself (dogfood — see `_is_meta_repo`).
110
+ """
111
+ try:
112
+ resolved = path.resolve()
113
+ except (OSError, RuntimeError):
114
+ return None
115
+ if _is_meta_repo(resolved):
116
+ return None
117
+ for parent in resolved.parents:
118
+ try:
119
+ if (parent / ".coding-os").is_dir() and _is_registered_project(parent):
120
+ return parent
121
+ except OSError:
122
+ continue
123
+ return None
124
+
125
+
126
+ def _resolve_slug_from_registry(cwd: Path) -> str:
127
+ """Match cli.registry._derive_slug so UI and API agree on spelling."""
128
+ try:
129
+ from cli.registry import _derive_slug # type: ignore
130
+
131
+ return _derive_slug(cwd)
132
+ except Exception as exc:
133
+ logger.debug("cli.registry._derive_slug unavailable: %s", exc)
134
+ return cwd.name.lower().strip() or "project"
135
+
136
+
137
+ def _derive_runtime_entry() -> dict | None:
138
+ """Return an in-memory entry for the cwd project when it's a cos repo."""
139
+ cwd = Path(os.environ.get("COS_PROJECT_ROOT") or os.getcwd()).resolve()
140
+ # $HOME hosts the GLOBAL hub state at ~/.coding-os/ — never a project.
141
+ try:
142
+ if cwd == Path.home().resolve():
143
+ return None
144
+ except (OSError, RuntimeError):
145
+ pass
146
+ if not _looks_like_cos_project(cwd):
147
+ return None
148
+ # A .coding-os/ carrying the hub registry/pid is the global state dir,
149
+ # not a project root (e.g. the Hub booted from a non-home COS_HOME).
150
+ if _is_hub_state_dir(cwd / ".coding-os"):
151
+ return None
152
+ # .coding-os/ only exists at the project root. A nested .coding-os/
153
+ # inside another project (e.g. src/core/web/ui/.coding-os/ left over
154
+ # from a test run) must NEVER surface as a separate project entry —
155
+ # we'd hijack the Hub's "default" slot with a stray dir.
156
+ if _ancestor_with_coding_os(cwd) is not None:
157
+ return None
158
+ return {
159
+ "slug": _resolve_slug_from_registry(cwd),
160
+ "path": str(cwd),
161
+ "created_at": "",
162
+ "source": "runtime-cwd",
163
+ }
164
+
165
+
166
+ def _err(category: str, message: str, *, status: int = 400) -> JSONResponse:
167
+ """Shared error-envelope shape matching the rest of /api/*."""
168
+ return JSONResponse(
169
+ status_code=status,
170
+ content={
171
+ "error": {
172
+ "category": category,
173
+ "retryable": False,
174
+ "message": message,
175
+ },
176
+ },
177
+ )
178
+
179
+
180
+ def _validate_project_path(raw: str) -> tuple[Path | None, JSONResponse | None]:
181
+ """Sanitize an incoming project-path string for registry mutations."""
182
+ if not isinstance(raw, str) or not raw.strip():
183
+ return None, _err("validation", "path is required and must be a non-empty string")
184
+ try:
185
+ path = Path(raw).expanduser().resolve()
186
+ except (OSError, RuntimeError) as exc:
187
+ return None, _err("validation", f"invalid path: {exc}")
188
+ if not path.is_dir():
189
+ return None, _err("not_found", f"path is not a directory: {path}", status=404)
190
+ if not _looks_like_cos_project(path):
191
+ return None, _err(
192
+ "validation",
193
+ f"{path} has no .coding-os/ — run `cos init` there first",
194
+ )
195
+ ancestor = _ancestor_with_coding_os(path)
196
+ if ancestor is not None:
197
+ return None, _err(
198
+ "validation",
199
+ (
200
+ f"{path} sits inside {ancestor} which is already a coding-os "
201
+ "project — .coding-os/ must only exist at the project root. "
202
+ "Remove the nested .coding-os/ directory."
203
+ ),
204
+ )
205
+ return path, None
206
+
207
+
208
+ # ---------------------------------------------------------------------------
209
+ # GET /api/hub/projects
210
+ # ---------------------------------------------------------------------------
211
+
212
+
213
+ @router.get("/projects")
214
+ def hub_projects() -> dict:
215
+ """List every registered coding-os project whose directory still exists."""
216
+ projects: list[dict] = []
217
+ seen_paths: set[str] = set()
218
+ try:
219
+ from cli.registry import load_registry # type: ignore
220
+
221
+ reg = load_registry()
222
+ except Exception as exc:
223
+ logger.debug("load_registry failed: %s", exc)
224
+ reg = None
225
+
226
+ if reg is not None:
227
+ for p in reg.projects:
228
+ path = Path(p.path)
229
+ if not _looks_like_cos_project(path):
230
+ continue
231
+ resolved = str(path.resolve())
232
+ if resolved in seen_paths:
233
+ continue
234
+ seen_paths.add(resolved)
235
+ projects.append(
236
+ {
237
+ "slug": p.slug,
238
+ "path": p.path,
239
+ "created_at": p.created_at,
240
+ "source": "registry",
241
+ }
242
+ )
243
+
244
+ runtime = _derive_runtime_entry()
245
+ if runtime is not None:
246
+ resolved = str(Path(runtime["path"]).resolve())
247
+ if resolved not in seen_paths:
248
+ projects.insert(0, runtime)
249
+
250
+ return {"projects": projects, "count": len(projects)}
251
+
252
+
253
+ @router.get("/agents")
254
+ def hub_agents() -> dict:
255
+ """Cross-project live-agent roster — one group per registered project (TASK-437)."""
256
+ from web.routes.presence import cross_project_agents # type: ignore
257
+
258
+ groups = cross_project_agents()
259
+ return {"projects": groups, "count": sum(len(g["agents"]) for g in groups)}
260
+
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # POST /api/hub/registry/add
264
+ # ---------------------------------------------------------------------------
265
+
266
+
267
+ @router.post("/registry/add")
268
+ def hub_registry_add(
269
+ path: str = Body(..., embed=True),
270
+ slug: str | None = Body(None, embed=True),
271
+ ):
272
+ """Register an existing `.coding-os/` directory with the Hub."""
273
+ resolved, err = _validate_project_path(path)
274
+ if err is not None:
275
+ return err
276
+ if slug is not None and not isinstance(slug, str):
277
+ return _err("validation", "slug must be a string when provided")
278
+
279
+ try:
280
+ from cli.registry import add_project # type: ignore
281
+ except Exception as exc:
282
+ return _err(
283
+ "unavailable",
284
+ f"cli.registry unavailable: {exc}",
285
+ status=503,
286
+ )
287
+
288
+ try:
289
+ entry = add_project(resolved, slug=(slug or "").strip() or None)
290
+ except Exception as exc:
291
+ return _err("validation", str(exc))
292
+
293
+ return {
294
+ "data": {
295
+ "slug": entry.slug,
296
+ "path": entry.path,
297
+ "created_at": entry.created_at,
298
+ },
299
+ "meta": {"layer": "hub", "source": "hub.registry_add"},
300
+ }
301
+
302
+
303
+ # ---------------------------------------------------------------------------
304
+ # GET /api/hub/stacks + POST /api/hub/registry/init (create-from-UI, TASK-249)
305
+ # ---------------------------------------------------------------------------
306
+
307
+ _PROJECT_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9._-]{0,63}$")
308
+
309
+
310
+ @router.get("/stacks")
311
+ def hub_stacks() -> dict:
312
+ """List installable stack templates (data-driven from src/templates/*/stack.yaml)."""
313
+ try:
314
+ from cli.list_stacks import TEMPLATES_DIR # type: ignore
315
+ from cli.stack_registry import load_stack_registry # type: ignore
316
+
317
+ reg = load_stack_registry(TEMPLATES_DIR)
318
+ except Exception as exc:
319
+ return _err("unavailable", f"stack registry unavailable: {exc}", status=503)
320
+ stacks = [
321
+ {"id": s.id, "label": s.label, "category": s.category, "language": s.language}
322
+ for s in sorted(reg.values(), key=lambda p: p.id)
323
+ if s.id != "_base"
324
+ ]
325
+ return {
326
+ "data": {"stacks": stacks, "count": len(stacks)},
327
+ "meta": {"layer": "hub", "source": "hub.stacks"},
328
+ }
329
+
330
+
331
+ @router.get("/adapters")
332
+ def hub_adapters() -> dict:
333
+ """List installable agent adapters (data-driven from src/adapters/*/adapter.yaml)."""
334
+ try:
335
+ from cli._resources import adapters_dir # type: ignore
336
+ from cli.adapter_registry import load_adapter_registry # type: ignore
337
+
338
+ reg = load_adapter_registry(adapters_dir())
339
+ except Exception as exc:
340
+ return _err("unavailable", f"adapter registry unavailable: {exc}", status=503)
341
+ adapters = [{"id": a.id, "label": a.label} for a in sorted(reg.values(), key=lambda a: a.id)]
342
+ return {
343
+ "data": {"adapters": adapters, "count": len(adapters)},
344
+ "meta": {"layer": "hub", "source": "hub.adapters"},
345
+ }
346
+
347
+
348
+ @router.get("/modules")
349
+ def hub_modules() -> dict:
350
+ """List subsystem modules (data-driven from src/core/subsystems.yaml) for the Composer."""
351
+ try:
352
+ from cli.subsystems import ( # type: ignore
353
+ load_profiles,
354
+ load_subsystems,
355
+ resolve_profile,
356
+ )
357
+
358
+ registry = load_subsystems()
359
+ _, default_profile = load_profiles()
360
+ default_disabled = resolve_profile(default_profile)
361
+ except Exception as exc:
362
+ return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503)
363
+ modules = [
364
+ {
365
+ "id": m.id,
366
+ "label": m.label,
367
+ "kernel": m.kernel,
368
+ "depends_on": list(m.depends_on),
369
+ }
370
+ for m in registry.values()
371
+ if not m.hidden
372
+ ]
373
+ visible = {m["id"] for m in modules}
374
+ return {
375
+ "data": {
376
+ "modules": modules,
377
+ "count": len(modules),
378
+ "default_profile": default_profile,
379
+ "default_disabled": [m for m in default_disabled if m in visible],
380
+ },
381
+ "meta": {"layer": "hub", "source": "hub.modules"},
382
+ }
383
+
384
+
385
+ @router.get("/presets")
386
+ def hub_presets() -> dict:
387
+ """List project presets (data-driven from src/templates/_presets/*.yaml)."""
388
+ try:
389
+ from cli.list_stacks import TEMPLATES_DIR # type: ignore
390
+ from cli.preset_registry import load_preset_registry # type: ignore
391
+ from cli.stack_registry import load_stack_registry # type: ignore
392
+
393
+ known = set(load_stack_registry(TEMPLATES_DIR).keys())
394
+ registry = load_preset_registry(TEMPLATES_DIR, known_stacks=known)
395
+ except Exception as exc:
396
+ return _err("unavailable", f"preset registry unavailable: {exc}", status=503)
397
+ templates_root = TEMPLATES_DIR.resolve()
398
+ presets = [
399
+ {
400
+ "id": p.id,
401
+ "label": p.label,
402
+ "description": p.description,
403
+ "stacks": list(p.stacks),
404
+ "provenance": (
405
+ "core" if p.source_path.resolve().is_relative_to(templates_root) else "user"
406
+ ),
407
+ }
408
+ for p in sorted(registry.values(), key=lambda p: p.id)
409
+ ]
410
+ return {
411
+ "data": {"presets": presets, "count": len(presets)},
412
+ "meta": {"layer": "hub", "source": "hub.presets"},
413
+ }
414
+
415
+
416
+ @router.get("/skills")
417
+ def hub_skills() -> dict:
418
+ """Global core+stack skill catalog with provenance + validation status."""
419
+ try:
420
+ from cli.skills_list import collect_skill_catalog # type: ignore
421
+
422
+ catalog = collect_skill_catalog()
423
+ except Exception as exc:
424
+ return _err("unavailable", f"skill catalog unavailable: {exc}", status=503)
425
+ return {"data": catalog, "meta": {"layer": "hub", "source": "hub.skills"}}
426
+
427
+
428
+ @router.get("/stacks/{stack_id}/skills")
429
+ def hub_stack_skills(stack_id: str) -> dict:
430
+ """Required/recommended/optional skill groups for one stack (onboarding preview)."""
431
+ try:
432
+ from cli.skills_list import collect_stack_skill_groups # type: ignore
433
+
434
+ payload = collect_stack_skill_groups(stack_id)
435
+ except KeyError:
436
+ return _err("not_found", f"stack '{stack_id}' not found", status=404)
437
+ except Exception as exc:
438
+ return _err("unavailable", f"skill groups unavailable: {exc}", status=503)
439
+ return {"data": payload, "meta": {"layer": "hub", "source": "hub.stack_skills"}}
440
+
441
+
442
+ def _cos_init_command() -> list[str]:
443
+ """Resolve how to invoke `cos` — the installed bin, else `python -m cli.main`."""
444
+ found = shutil.which("cos")
445
+ return [found] if found else [sys.executable, "-m", "cli.main"]
446
+
447
+
448
+ def _resolve_agents(agent: str, agents: list[str] | None) -> list[str]:
449
+ """Merge the single `agent` (back-compat) + `agents` list into a deduped,
450
+ order-preserving list; defaults to ['claude'] when both are empty.
451
+
452
+ A project may legitimately host several adapters (.claude/ + .codex/) —
453
+ the CLI already supports `--agent claude,codex` (main.py::_parse_agents)."""
454
+ merged: list[str] = []
455
+ for candidate in [*(agents or []), agent]:
456
+ token = (candidate or "").strip()
457
+ if token and token not in merged:
458
+ merged.append(token)
459
+ return merged or ["claude"]
460
+
461
+
462
+ def _validate_init_inputs(
463
+ name: str,
464
+ parent_dir: str,
465
+ stacks: list[str],
466
+ preset: str,
467
+ agents: list[str],
468
+ extra_skills: list[str] | None = None,
469
+ disabled_modules: list[str] | None = None,
470
+ ) -> tuple[JSONResponse | dict | None, dict]:
471
+ """Shared dry-run validation for validate-init AND registry/init (SSOT).
472
+
473
+ Returns (error_response, info). info carries name/auto_named/parent/
474
+ target/templates once every check passes. No filesystem writes."""
475
+ info: dict = {}
476
+ name = (name or "").strip()
477
+ if name:
478
+ if not _PROJECT_NAME_RE.match(name):
479
+ return (
480
+ _err(
481
+ "validation",
482
+ "name must match ^[a-z0-9][a-z0-9._-]{0,63}$ (lowercase, no spaces)",
483
+ ),
484
+ {},
485
+ )
486
+ info["name"], info["auto_named"] = name, False
487
+ else:
488
+ # "don't know yet" — temp slug, renameable later via registry rename.
489
+ info["name"], info["auto_named"] = f"proj-{uuid.uuid4().hex[:6]}", True
490
+
491
+ if not isinstance(parent_dir, str) or not parent_dir.strip():
492
+ return _err("validation", "parent_dir is required"), {}
493
+ try:
494
+ parent = Path(parent_dir).expanduser().resolve()
495
+ except (OSError, RuntimeError) as exc:
496
+ return _err("validation", f"invalid parent_dir: {exc}"), {}
497
+ if not parent.is_dir():
498
+ return _err("not_found", f"parent_dir is not a directory: {parent}", status=404), {}
499
+ if not os.access(parent, os.W_OK):
500
+ return _err("validation", f"parent_dir is not writable: {parent}"), {}
501
+ if _is_meta_repo(parent):
502
+ return _err("validation", "cannot scaffold inside the coding-os meta-repo checkout"), {}
503
+ target = parent / info["name"]
504
+ if target.exists():
505
+ return _err("conflict", f"{target} already exists", status=409), {}
506
+ nested = _ancestor_with_coding_os(parent)
507
+ if nested is not None:
508
+ return (
509
+ _err(
510
+ "validation",
511
+ f"{parent} is inside the registered project {nested} — pick a parent outside it",
512
+ ),
513
+ {},
514
+ )
515
+
516
+ try:
517
+ from cli.list_stacks import TEMPLATES_DIR # type: ignore
518
+ from cli.stack_registry import load_stack_registry # type: ignore
519
+
520
+ stack_reg = load_stack_registry(TEMPLATES_DIR)
521
+ except Exception as exc:
522
+ return _err("unavailable", f"stack registry unavailable: {exc}", status=503), {}
523
+ stacks = [s for s in (stacks or []) if s]
524
+ unknown = [s for s in stacks if s not in stack_reg]
525
+ if unknown:
526
+ return _err("validation", f"unknown stack(s): {unknown}"), {}
527
+ if preset:
528
+ if stacks:
529
+ return _err("validation", "preset and stacks are mutually exclusive"), {}
530
+ from cli.preset_registry import load_preset_registry # type: ignore
531
+
532
+ presets = load_preset_registry(TEMPLATES_DIR, known_stacks=set(stack_reg.keys()))
533
+ if preset not in presets:
534
+ return _err("not_found", f"preset '{preset}' not found", status=404), {}
535
+ info["templates"] = list(presets[preset].stacks)
536
+ else:
537
+ info["templates"] = stacks
538
+
539
+ try:
540
+ from cli._resources import adapters_dir # type: ignore
541
+ from cli.adapter_registry import load_adapter_registry # type: ignore
542
+
543
+ adapter_reg = load_adapter_registry(adapters_dir())
544
+ except Exception as exc:
545
+ return _err("unavailable", f"adapter registry unavailable: {exc}", status=503), {}
546
+ if not agents:
547
+ return _err("validation", "at least one agent is required"), {}
548
+ unknown_agents = [a for a in agents if a not in adapter_reg]
549
+ if unknown_agents:
550
+ return (
551
+ _err(
552
+ "validation",
553
+ f"unknown agent(s) {unknown_agents} — available: {sorted(adapter_reg)}",
554
+ ),
555
+ {},
556
+ )
557
+ info["agents"] = list(agents)
558
+
559
+ # Module toggles (TASK-421): validate against the subsystem registry —
560
+ # kernel is non-disableable, and the set is closed over dependents before
561
+ # it reaches init: the scaffold REFUSES (never cascades) an unclosed one,
562
+ # so an unclosed set would return ok for a project contradicting the request.
563
+ disabled_modules = [m for m in (disabled_modules or []) if m]
564
+ if disabled_modules:
565
+ try:
566
+ from cli.subsystems import load_subsystems # type: ignore
567
+
568
+ registry_modules = load_subsystems()
569
+ except Exception as exc:
570
+ return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503), {}
571
+ unknown_mods = [m for m in disabled_modules if m not in registry_modules]
572
+ if unknown_mods:
573
+ return _err("validation", f"unknown module(s): {unknown_mods}"), {}
574
+ kernel_mods = [m for m in disabled_modules if registry_modules[m].kernel]
575
+ if kernel_mods:
576
+ return _err(
577
+ "validation", f"module(s) {kernel_mods} are kernel and cannot be disabled"
578
+ ), {}
579
+ # Same closure the CLI applies, so an identical payload produces an
580
+ # identical project through either entrypoint.
581
+ try:
582
+ from cli.subsystems import close_over_dependents # type: ignore
583
+
584
+ disabled_modules = close_over_dependents(disabled_modules, registry_modules)
585
+ except Exception as exc:
586
+ return _err("unavailable", f"subsystem registry unavailable: {exc}", status=503), {}
587
+ info["disabled_modules"] = disabled_modules
588
+
589
+ # Argv allowlist (TASK-363): every value that reaches the subprocess argv
590
+ # is validated against a registry — skills included.
591
+ if extra_skills:
592
+ try:
593
+ from cli.skill_registry import load_skill_registry # type: ignore
594
+ from cli.skills_list import CORE_SKILLS_DIR # type: ignore
595
+
596
+ known_skills = set(load_skill_registry(CORE_SKILLS_DIR).skills.keys())
597
+ except Exception as exc:
598
+ return _err("unavailable", f"skill registry unavailable: {exc}", status=503), {}
599
+ unknown_skills = [s for s in extra_skills if s not in known_skills]
600
+ if unknown_skills:
601
+ return _err("validation", f"unknown skill(s): {unknown_skills}"), {}
602
+
603
+ info["parent"], info["target"] = parent, target
604
+ return None, info
605
+
606
+
607
+ @router.post("/registry/validate-init")
608
+ def hub_registry_validate_init(
609
+ name: str = Body("", embed=True),
610
+ parent_dir: str = Body(..., embed=True),
611
+ stacks: list[str] = Body(default_factory=list, embed=True),
612
+ preset: str = Body("", embed=True),
613
+ agent: str = Body("", embed=True),
614
+ agents: list[str] = Body(default_factory=list, embed=True),
615
+ extra_skills: list[str] = Body(default_factory=list, embed=True),
616
+ disabled_modules: list[str] = Body(default_factory=list, embed=True),
617
+ ):
618
+ """Dry-run validation + merged-config preview for the onboarding wizard (TASK-358)."""
619
+ resolved_agents = _resolve_agents(agent, agents)
620
+ error, info = _validate_init_inputs(
621
+ name,
622
+ parent_dir,
623
+ stacks,
624
+ preset,
625
+ resolved_agents,
626
+ extra_skills=extra_skills,
627
+ disabled_modules=disabled_modules,
628
+ )
629
+ if error is not None:
630
+ return error
631
+ swimlanes: list[str] = []
632
+ conflicts: list[str] = []
633
+ try:
634
+ from cli.config_composer import preview_coding_os_configs # type: ignore
635
+ from cli.list_stacks import TEMPLATES_DIR # type: ignore
636
+
637
+ merged, conflicts = preview_coding_os_configs(
638
+ info["templates"], templates_dir=TEMPLATES_DIR
639
+ )
640
+ scrumban = merged.get("scrumban-config.yaml") or {}
641
+ swimlanes = [
642
+ lane.get("id") for lane in scrumban.get("swimlanes") or [] if isinstance(lane, dict)
643
+ ]
644
+ except Exception as exc:
645
+ logger.debug("dry-config preview failed: %s", exc)
646
+ return {
647
+ "data": {
648
+ "valid": True,
649
+ "name": info["name"],
650
+ "auto_named": info["auto_named"],
651
+ "target": str(info["target"]),
652
+ "templates": info["templates"],
653
+ "agents": info["agents"],
654
+ "disabled_modules": info["disabled_modules"],
655
+ "swimlanes": swimlanes,
656
+ "conflicts": conflicts,
657
+ },
658
+ "meta": {"layer": "hub", "source": "hub.registry_validate_init"},
659
+ }
660
+
661
+
662
+ def _default_profile_reenables(disabled_modules: list[str]) -> list[str]:
663
+ # Modules the chips kept ON that the default profile would disable —
664
+ # emitted as --enable-module so the payload stays authoritative without
665
+ # pinning a profile. Hidden modules follow the profile: the chips never
666
+ # show them, so the caller expressed no intent about them.
667
+ try:
668
+ from cli.subsystems import load_profiles, load_subsystems, resolve_profile # type: ignore
669
+
670
+ modules = load_subsystems()
671
+ _, default_name = load_profiles()
672
+ default_disabled = resolve_profile(default_name)
673
+ except Exception as exc:
674
+ logger.debug("profile lookup failed: %s", exc)
675
+ return []
676
+ off = set(disabled_modules)
677
+ return sorted(
678
+ module_id
679
+ for module_id in default_disabled
680
+ if module_id not in off and module_id in modules and not modules[module_id].hidden
681
+ )
682
+
683
+
684
+ def _build_cos_init_cmd(
685
+ name: str,
686
+ parent_dir: str,
687
+ stacks: list[str],
688
+ agents: list[str],
689
+ preset: str = "",
690
+ description: str = "",
691
+ extra_skills: list[str] | None = None,
692
+ disabled_modules: list[str] | None = None,
693
+ ) -> list[str]:
694
+ """One argv builder for sync AND job-based init (parity stays in the CLI)."""
695
+ cmd = _cos_init_command() + [
696
+ "init",
697
+ "--name",
698
+ name,
699
+ "--project-dir",
700
+ parent_dir,
701
+ "--agent",
702
+ ",".join(agents),
703
+ "--yes",
704
+ "--no-index",
705
+ # Skip the heavy doc-RAG embedding but still build the knowledge graph
706
+ # (AST walk, no model) so the new project's Graph tab is never empty.
707
+ "--graph-index",
708
+ "--format",
709
+ "json",
710
+ ]
711
+ if disabled_modules:
712
+ # init UNIONS the profile's disabled set with --disable-module, so the
713
+ # chips a user left ON must ride as --enable-module or the default
714
+ # profile silently turns them off. With no explicit set, stay silent so
715
+ # a bare API create matches a hand-typed `cos init`.
716
+ for module_id in _default_profile_reenables(disabled_modules):
717
+ cmd += ["--enable-module", module_id]
718
+ if preset:
719
+ cmd += ["--preset", preset]
720
+ for stack in stacks:
721
+ cmd += ["--template", stack]
722
+ if description.strip():
723
+ cmd += ["--summary", description.strip()]
724
+ if extra_skills:
725
+ cmd += ["--skills", ",".join(extra_skills)]
726
+ for module_id in disabled_modules or []:
727
+ cmd += ["--disable-module", module_id]
728
+ return cmd
729
+
730
+
731
+ def _run_cos_init(
732
+ name: str,
733
+ parent_dir: str,
734
+ stacks: list[str],
735
+ agents: list[str],
736
+ preset: str = "",
737
+ description: str = "",
738
+ extra_skills: list[str] | None = None,
739
+ disabled_modules: list[str] | None = None,
740
+ timeout: int = 300,
741
+ ):
742
+ """Run `cos init` in a subprocess → (ok, payload, error).
743
+
744
+ Default timeout has headroom over the in-init graph-build cap
745
+ (COS_INIT_GRAPH_TIMEOUT, default 180s) so a slow graph build degrades to an
746
+ empty graph inside init rather than the create subprocess being killed.
747
+
748
+ Module-level so a test can monkeypatch it without a real scaffold.
749
+ Description/extra-skills ride the CLI flags (--summary/--skills) so the
750
+ wizard and a hand-typed `cos init` produce byte-identical projects."""
751
+ cmd = _build_cos_init_cmd(
752
+ name,
753
+ parent_dir,
754
+ stacks,
755
+ agents,
756
+ preset=preset,
757
+ description=description,
758
+ extra_skills=extra_skills,
759
+ disabled_modules=disabled_modules,
760
+ )
761
+ try:
762
+ proc = subprocess.run( # noqa: S603 — fixed argv list, never shell=True
763
+ cmd, capture_output=True, text=True, timeout=timeout, cwd=parent_dir
764
+ )
765
+ except subprocess.TimeoutExpired:
766
+ return False, None, f"init timed out after {timeout}s"
767
+ except OSError as exc:
768
+ return False, None, f"could not launch cos init: {exc}"
769
+ if proc.returncode != 0:
770
+ return False, None, (proc.stderr or proc.stdout or "init failed").strip()[-400:]
771
+ return True, _parse_init_payload((proc.stdout or "").splitlines()), ""
772
+
773
+
774
+ def _parse_init_payload(stdout_lines: list[str]) -> dict:
775
+ # init pretty-prints the summary, and the job runner merges stderr progress
776
+ # into the same pipe, so the object can be preceded AND followed by noise.
777
+ # raw_decode stops at the end of the first complete value.
778
+ text = "\n".join(stdout_lines)
779
+ decoder = json.JSONDecoder()
780
+ start = text.find("{")
781
+ while start != -1:
782
+ try:
783
+ parsed, _ = decoder.raw_decode(text[start:])
784
+ except json.JSONDecodeError:
785
+ start = text.find("{", start + 1)
786
+ continue
787
+ if isinstance(parsed, dict):
788
+ return parsed
789
+ start = text.find("{", start + 1)
790
+ return {}
791
+
792
+
793
+ @router.post("/registry/init")
794
+ def hub_registry_init(
795
+ name: str = Body("", embed=True),
796
+ parent_dir: str = Body(..., embed=True),
797
+ stack: str = Body("", embed=True),
798
+ stacks: list[str] = Body(default_factory=list, embed=True),
799
+ preset: str = Body("", embed=True),
800
+ agent: str = Body("", embed=True),
801
+ agents: list[str] = Body(default_factory=list, embed=True),
802
+ description: str = Body("", embed=True),
803
+ extra_skills: list[str] = Body(default_factory=list, embed=True),
804
+ disabled_modules: list[str] = Body(default_factory=list, embed=True),
805
+ background: bool = Body(False, embed=True),
806
+ ):
807
+ """Scaffold a NEW project via `cos init` and register it (security-gated, TASK-249/358/362)."""
808
+ all_stacks = [s for s in ((stacks or []) + ([stack] if stack else [])) if s]
809
+ resolved_agents = _resolve_agents(agent, agents)
810
+ error, info = _validate_init_inputs(
811
+ name,
812
+ parent_dir,
813
+ all_stacks,
814
+ preset,
815
+ resolved_agents,
816
+ extra_skills=extra_skills,
817
+ disabled_modules=disabled_modules,
818
+ )
819
+ if error is not None:
820
+ return error
821
+ target: Path = info["target"]
822
+ if background:
823
+ # Job-based create (TASK-362): returns a job_id immediately; phases +
824
+ # log stream over GET /api/hub/init-jobs/{id}/events.
825
+ from web import init_jobs # type: ignore
826
+
827
+ cmd = _build_cos_init_cmd(
828
+ info["name"],
829
+ str(info["parent"]),
830
+ all_stacks,
831
+ resolved_agents,
832
+ preset=preset,
833
+ description=description or "",
834
+ extra_skills=extra_skills or [],
835
+ disabled_modules=info["disabled_modules"],
836
+ )
837
+ job = init_jobs.start_job(cmd, target, str(info["parent"]), _parse_init_payload)
838
+ return {
839
+ "data": {
840
+ "job_id": job.job_id,
841
+ "name": info["name"],
842
+ "auto_named": info["auto_named"],
843
+ "target": str(target),
844
+ },
845
+ "meta": {"layer": "hub", "source": "hub.registry_init_job"},
846
+ }
847
+ ok, payload, err = _run_cos_init(
848
+ info["name"],
849
+ str(info["parent"]),
850
+ all_stacks,
851
+ resolved_agents,
852
+ preset=preset,
853
+ description=description or "",
854
+ extra_skills=extra_skills or [],
855
+ disabled_modules=info["disabled_modules"],
856
+ )
857
+ if not ok:
858
+ # A failed init must leave nothing — remove the partial scaffold.
859
+ if target.exists():
860
+ shutil.rmtree(target, ignore_errors=True)
861
+ return _err("internal", f"init failed: {err}", status=500)
862
+ slug = (payload or {}).get("slug") or _resolve_slug_from_registry(target)
863
+ return {
864
+ "data": {
865
+ "slug": slug,
866
+ "path": str(target),
867
+ "stack": (all_stacks[0] if all_stacks else None),
868
+ "stacks": all_stacks,
869
+ "agents": resolved_agents,
870
+ "disabled_modules": info["disabled_modules"],
871
+ "preset": preset or None,
872
+ "auto_named": info["auto_named"],
873
+ },
874
+ "meta": {"layer": "hub", "source": "hub.registry_init"},
875
+ }
876
+
877
+
878
+ @router.get("/init-jobs/{job_id}")
879
+ def hub_init_job_snapshot(job_id: str):
880
+ """Current phase + status + log tail for a tracked init job (TASK-362)."""
881
+ from web import init_jobs # type: ignore
882
+
883
+ job = init_jobs.get_job(job_id)
884
+ if job is None:
885
+ return _err("not_found", f"no init job {job_id!r}", status=404)
886
+ return {"data": job.snapshot(), "meta": {"layer": "hub", "source": "hub.init_job"}}
887
+
888
+
889
+ @router.post("/init-jobs/{job_id}/cancel")
890
+ def hub_init_job_cancel(job_id: str):
891
+ """Cancel a running init job; the partial scaffold is cleaned up (TASK-362)."""
892
+ from web import init_jobs # type: ignore
893
+
894
+ job = init_jobs.cancel_job(job_id)
895
+ if job is None:
896
+ return _err("not_found", f"no init job {job_id!r}", status=404)
897
+ return {
898
+ "data": {"job_id": job.job_id, "status": job.snapshot()["status"]},
899
+ "meta": {"layer": "hub", "source": "hub.init_job_cancel"},
900
+ }
901
+
902
+
903
+ @router.get("/init-jobs/{job_id}/events")
904
+ async def hub_init_job_events(job_id: str):
905
+ """SSE: buffered log replay then live phase/log events until terminal.
906
+
907
+ Reconnect-safe — a browser refresh re-attaches with the same job_id and
908
+ replays the buffered log before following (TASK-362)."""
909
+ import asyncio
910
+
911
+ from fastapi.responses import StreamingResponse
912
+
913
+ from web import init_jobs # type: ignore
914
+
915
+ job = init_jobs.get_job(job_id)
916
+ if job is None:
917
+ return _err("not_found", f"no init job {job_id!r}", status=404)
918
+
919
+ def _frame(event: str, payload: dict) -> bytes:
920
+ return f"event: {event}\ndata: {json.dumps(payload)}\n\n".encode("utf-8")
921
+
922
+ async def _gen():
923
+ offset = 0
924
+ last_phase = None
925
+ while True:
926
+ snap = job.snapshot(log_tail=0)
927
+ lines, offset = await asyncio.to_thread(job.log_slice, offset)
928
+ for line in lines:
929
+ yield _frame("log", {"line": line})
930
+ if snap["phase"] != last_phase:
931
+ last_phase = snap["phase"]
932
+ yield _frame("phase", {"phase": last_phase, "phases": snap["phases"]})
933
+ if snap["status"] != "running":
934
+ yield _frame(
935
+ snap["status"],
936
+ {
937
+ "status": snap["status"],
938
+ "error": snap["error"],
939
+ "result": snap["result"],
940
+ "cleanup": snap["cleanup"],
941
+ },
942
+ )
943
+ return
944
+ await asyncio.sleep(0.25)
945
+
946
+ return StreamingResponse(
947
+ _gen(),
948
+ media_type="text/event-stream",
949
+ headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
950
+ )
951
+
952
+
953
+ @router.patch("/registry/{slug}")
954
+ def hub_registry_rename(slug: str, new_slug: str = Body(..., embed=True)):
955
+ """Rename a project's slug (temp-slug → real name; path untouched)."""
956
+ if not _PROJECT_NAME_RE.match((new_slug or "").strip()):
957
+ return _err("validation", "new_slug must match ^[a-z0-9][a-z0-9._-]{0,63}$")
958
+ try:
959
+ from cli.registry import rename_project # type: ignore
960
+
961
+ renamed = rename_project(slug, new_slug.strip())
962
+ except Exception as exc:
963
+ return _err("conflict", str(exc), status=409)
964
+ if renamed is None:
965
+ return _err("not_found", f"no project with slug {slug!r}", status=404)
966
+ return {
967
+ "data": {"slug": renamed.slug, "path": renamed.path},
968
+ "meta": {"layer": "hub", "source": "hub.registry_rename"},
969
+ }
970
+
971
+
972
+ # ---------------------------------------------------------------------------
973
+ # DELETE /api/hub/registry/{slug}
974
+ # ---------------------------------------------------------------------------
975
+
976
+
977
+ @router.delete("/registry/{slug}")
978
+ def hub_registry_remove(slug: str):
979
+ """Unregister a project by slug. Does NOT touch the project on disk."""
980
+ if not slug or not slug.strip():
981
+ return _err("validation", "slug is required")
982
+ try:
983
+ from cli.registry import remove_project # type: ignore
984
+ except Exception as exc:
985
+ return _err("unavailable", f"cli.registry unavailable: {exc}", status=503)
986
+
987
+ try:
988
+ removed = remove_project(slug.strip())
989
+ except Exception as exc:
990
+ return _err("internal", str(exc), status=500)
991
+
992
+ if removed is None:
993
+ return _err("not_found", f"no project with slug {slug!r}", status=404)
994
+
995
+ return {
996
+ "data": {"slug": removed.slug, "path": removed.path},
997
+ "meta": {"layer": "hub", "source": "hub.registry_remove"},
998
+ }
999
+
1000
+
1001
+ # ---------------------------------------------------------------------------
1002
+ # POST /api/hub/registry/scan
1003
+ # ---------------------------------------------------------------------------
1004
+
1005
+ # Hard caps so a malicious/misconfigured scan can't lock the process.
1006
+ _SCAN_MAX_DEPTH = 6
1007
+ _SCAN_MAX_VISITED_DIRS = 5000
1008
+
1009
+
1010
+ @router.post("/registry/scan")
1011
+ def hub_registry_scan(
1012
+ root: str = Body(..., embed=True),
1013
+ max_depth: int = Body(_SCAN_MAX_DEPTH, embed=True),
1014
+ limit: int = Body(50, embed=True),
1015
+ ):
1016
+ """Walk a filesystem root and return every `.coding-os/` project found."""
1017
+ if not isinstance(root, str) or not root.strip():
1018
+ return _err("validation", "root is required")
1019
+ try:
1020
+ root_path = Path(root).expanduser().resolve()
1021
+ except (OSError, RuntimeError) as exc:
1022
+ return _err("validation", f"invalid root: {exc}")
1023
+ if not root_path.is_dir():
1024
+ return _err("not_found", f"root is not a directory: {root_path}", status=404)
1025
+
1026
+ max_depth = max(1, min(_SCAN_MAX_DEPTH, int(max_depth) if max_depth else _SCAN_MAX_DEPTH))
1027
+ limit = max(1, min(500, int(limit) if limit else 50))
1028
+
1029
+ _SKIP_DIR_NAMES = {
1030
+ ".git",
1031
+ ".hg",
1032
+ ".svn",
1033
+ "node_modules",
1034
+ ".venv",
1035
+ "venv",
1036
+ "__pycache__",
1037
+ ".pytest_cache",
1038
+ ".mypy_cache",
1039
+ ".ruff_cache",
1040
+ ".tox",
1041
+ ".nox",
1042
+ ".coding-os", # never recurse INTO a cos state dir
1043
+ "dist",
1044
+ "build",
1045
+ ".next",
1046
+ ".turbo",
1047
+ "Library",
1048
+ "Trash",
1049
+ ".Trash",
1050
+ }
1051
+
1052
+ # Snapshot registered paths for the "already_registered" annotation.
1053
+ registered_paths: set[str] = set()
1054
+ try:
1055
+ from cli.registry import load_registry # type: ignore
1056
+
1057
+ for p in load_registry().projects:
1058
+ try:
1059
+ registered_paths.add(str(Path(p.path).resolve()))
1060
+ except (OSError, RuntimeError):
1061
+ continue
1062
+ except Exception as exc:
1063
+ logger.debug("scan: could not snapshot registry: %s", exc)
1064
+
1065
+ hits: list[dict] = []
1066
+ visited = 0
1067
+ hit_limit_reached = False
1068
+ depth_limit_reached = False
1069
+
1070
+ # BFS so shallow hits surface first (a consumer usually cares about
1071
+ # "~/code/my-app" before "~/code/my-app/backend/vendor/old/...").
1072
+ from collections import deque
1073
+
1074
+ queue: deque[tuple[Path, int]] = deque([(root_path, 0)])
1075
+ while queue:
1076
+ if len(hits) >= limit:
1077
+ hit_limit_reached = True
1078
+ break
1079
+ if visited >= _SCAN_MAX_VISITED_DIRS:
1080
+ break
1081
+ current, depth = queue.popleft()
1082
+ visited += 1
1083
+ if not current.is_dir():
1084
+ continue
1085
+ if _looks_like_cos_project(current):
1086
+ resolved = str(current.resolve())
1087
+ hits.append(
1088
+ {
1089
+ "path": resolved,
1090
+ "slug": _resolve_slug_from_registry(current),
1091
+ "already_registered": resolved in registered_paths,
1092
+ }
1093
+ )
1094
+ # Don't recurse into a cos project; nested cos repos are
1095
+ # extremely rare and the skip keeps scans snappy.
1096
+ continue
1097
+ if depth >= max_depth:
1098
+ depth_limit_reached = True
1099
+ continue
1100
+ try:
1101
+ children = list(current.iterdir())
1102
+ except (OSError, PermissionError):
1103
+ continue
1104
+ for child in children:
1105
+ if not child.is_dir():
1106
+ continue
1107
+ if child.name in _SKIP_DIR_NAMES:
1108
+ continue
1109
+ if child.name.startswith("."):
1110
+ # Don't descend into dotfiles directories (browser caches,
1111
+ # editor state); .coding-os is explicitly skipped above.
1112
+ continue
1113
+ queue.append((child, depth + 1))
1114
+
1115
+ return {
1116
+ "data": {
1117
+ "root": str(root_path),
1118
+ "hits": hits,
1119
+ "count": len(hits),
1120
+ "visited_dirs": visited,
1121
+ "hit_limit_reached": hit_limit_reached,
1122
+ "depth_limit_reached": depth_limit_reached,
1123
+ },
1124
+ "meta": {
1125
+ "layer": "hub",
1126
+ "source": "hub.registry_scan",
1127
+ "max_depth": max_depth,
1128
+ "limit": limit,
1129
+ },
1130
+ }
1131
+
1132
+
1133
+ # ---------------------------------------------------------------------------
1134
+ # POST /api/hub/registry/gc
1135
+ # ---------------------------------------------------------------------------
1136
+
1137
+
1138
+ @router.post("/registry/gc")
1139
+ def hub_registry_gc(
1140
+ dry_run: bool = Body(False, embed=True),
1141
+ ):
1142
+ """Remove registry entries whose directory no longer exists."""
1143
+ try:
1144
+ from cli.registry import Registry, load_registry, save_registry # type: ignore
1145
+ except Exception as exc:
1146
+ return _err("unavailable", f"cli.registry unavailable: {exc}", status=503)
1147
+
1148
+ try:
1149
+ reg = load_registry()
1150
+ except Exception as exc:
1151
+ return _err("internal", f"load_registry failed: {exc}", status=500)
1152
+
1153
+ kept: list[dict] = []
1154
+ removed: list[dict] = []
1155
+ for entry in reg.projects:
1156
+ path = Path(entry.path)
1157
+ alive = _looks_like_cos_project(path)
1158
+ item = {"slug": entry.slug, "path": entry.path, "created_at": entry.created_at}
1159
+ (kept if alive else removed).append(item)
1160
+
1161
+ if not dry_run and removed:
1162
+ reg.projects = [p for p in reg.projects if _looks_like_cos_project(Path(p.path))]
1163
+ try:
1164
+ save_registry(reg)
1165
+ except Exception as exc:
1166
+ return _err("internal", f"save_registry failed: {exc}", status=500)
1167
+
1168
+ return {
1169
+ "data": {
1170
+ "kept": kept,
1171
+ "removed": removed,
1172
+ "dry_run": bool(dry_run),
1173
+ "kept_count": len(kept),
1174
+ "removed_count": len(removed),
1175
+ },
1176
+ "meta": {"layer": "hub", "source": "hub.registry_gc"},
1177
+ }
1178
+
1179
+
1180
+ # ---------------------------------------------------------------------------
1181
+ # GET /api/hub/suggest-roots — surface likely scan roots for the UI
1182
+ # ---------------------------------------------------------------------------
1183
+
1184
+
1185
+ @router.get("/suggest-roots")
1186
+ def hub_suggest_roots(depth: int = Query(0)):
1187
+ """Return sensible default scan roots for the UI's import wizard."""
1188
+ _ = depth # reserved — currently unused; keeps the route parameter list stable
1189
+ candidates: list[Path] = [
1190
+ Path(os.environ.get("COS_PROJECT_ROOT") or os.getcwd()).resolve(),
1191
+ Path.home() / "code",
1192
+ Path.home() / "Projects",
1193
+ Path.home() / "Developer",
1194
+ Path.home(),
1195
+ ]
1196
+ seen: set[str] = set()
1197
+ suggestions: list[str] = []
1198
+ for c in candidates:
1199
+ try:
1200
+ resolved = str(c.resolve())
1201
+ except (OSError, RuntimeError):
1202
+ continue
1203
+ if resolved in seen:
1204
+ continue
1205
+ if not c.is_dir():
1206
+ continue
1207
+ seen.add(resolved)
1208
+ suggestions.append(resolved)
1209
+ # The Composer scaffolds INTO the picked root, so it needs the subset that
1210
+ # `_validate_init_inputs` would accept; import/scan happily target the rest.
1211
+ scaffoldable = [
1212
+ s
1213
+ for s in suggestions
1214
+ if not _is_meta_repo(Path(s)) and _ancestor_with_coding_os(Path(s)) is None
1215
+ ]
1216
+ return {
1217
+ "data": {"suggestions": suggestions, "scaffoldable": scaffoldable},
1218
+ "meta": {"layer": "hub", "source": "hub.suggest_roots"},
1219
+ }