coding-os 0.3.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1304) hide show
  1. adapters/claude/README.md +6 -0
  2. adapters/claude/_install_helpers/extract_stacks.py +43 -0
  3. adapters/claude/_install_helpers/update_mcp_json.py +75 -0
  4. adapters/claude/adapter.yaml +164 -0
  5. adapters/claude/hooks/README.md +40 -0
  6. adapters/claude/hooks/agent_memory_sync.py +131 -0
  7. adapters/claude/hooks/ensure-agent-memory-link.sh +36 -0
  8. adapters/claude/hooks/sync-agent-memory.sh +21 -0
  9. adapters/claude/install.sh +86 -0
  10. adapters/claude/sdk_dispatcher.py +871 -0
  11. adapters/claude/settings.local.template.json +31 -0
  12. adapters/claude/settings.template.json +808 -0
  13. adapters/claude/update_mcp_json.py +85 -0
  14. adapters/codex/adapter.yaml +254 -0
  15. adapters/codex/chat_provider.py +230 -0
  16. adapters/codex/commands/formula-f1.md +129 -0
  17. adapters/codex/commands/formula-f10.md +100 -0
  18. adapters/codex/commands/formula-f11.md +123 -0
  19. adapters/codex/commands/formula-f2.md +139 -0
  20. adapters/codex/commands/formula-f3.md +127 -0
  21. adapters/codex/commands/formula-f4.md +101 -0
  22. adapters/codex/commands/formula-f5.md +135 -0
  23. adapters/codex/commands/formula-f6.md +147 -0
  24. adapters/codex/commands/formula-f7.md +111 -0
  25. adapters/codex/commands/formula-f8.md +133 -0
  26. adapters/codex/commands/formula-f9.md +112 -0
  27. adapters/codex/enable_codex_hooks.py +94 -0
  28. adapters/codex/ensure_codex_mcp.py +124 -0
  29. adapters/codex/hooks/codex-merge-hook-output.py +72 -0
  30. adapters/codex/hooks/codex-normalize-edit.py +96 -0
  31. adapters/codex/hooks/codex-postedit-dispatch.sh +75 -0
  32. adapters/codex/hooks/codex-posttool-dispatch.sh +70 -0
  33. adapters/codex/hooks/codex-preedit-dispatch.sh +83 -0
  34. adapters/codex/hooks/codex-pretool-dispatch.sh +82 -0
  35. adapters/codex/hooks/codex-sessionend-dispatch.sh +20 -0
  36. adapters/codex/hooks/codex-sessionstart-dispatch.sh +68 -0
  37. adapters/codex/hooks/codex-stop-dispatch.sh +73 -0
  38. adapters/codex/hooks/codex-userpromptsubmit-dispatch.sh +74 -0
  39. adapters/codex/hooks.template.json +208 -0
  40. adapters/codex/install.sh +83 -0
  41. adapters/codex/sdk_dispatcher.py +449 -0
  42. board_os/__init__.py +39 -0
  43. board_os/_agent_runtime.py +256 -0
  44. board_os/config.py +421 -0
  45. board_os/git_coherence.py +107 -0
  46. board_os/hub_adapter_manifest.py +140 -0
  47. board_os/mcp_tools.py +3228 -0
  48. board_os/migration.py +166 -0
  49. board_os/parser.py +317 -0
  50. board_os/presence.py +156 -0
  51. board_os/sync.py +320 -0
  52. board_os/transition_gates.py +224 -0
  53. board_os/transition_gates_cli.py +272 -0
  54. board_os/transition_gates_validator.py +551 -0
  55. board_os/verify_suites.py +126 -0
  56. board_os/verify_suites_cli.py +328 -0
  57. board_os/workflow.py +967 -0
  58. cli/__init__.py +0 -0
  59. cli/_data_types.py +248 -0
  60. cli/_init_helpers.py +587 -0
  61. cli/_resources.py +100 -0
  62. cli/adapter_registry.py +239 -0
  63. cli/add_stack.py +315 -0
  64. cli/aggregator.py +438 -0
  65. cli/board_commands.py +1218 -0
  66. cli/brain_commands.py +255 -0
  67. cli/cognition.py +345 -0
  68. cli/config_composer.py +349 -0
  69. cli/core_version.py +41 -0
  70. cli/cron_commands.py +278 -0
  71. cli/db_reset.py +298 -0
  72. cli/doc_commands.py +111 -0
  73. cli/doctor.py +2953 -0
  74. cli/doctor_board.py +365 -0
  75. cli/doctor_extras.py +1121 -0
  76. cli/doctor_graph.py +608 -0
  77. cli/doctor_tokens.py +254 -0
  78. cli/graph_commands.py +1265 -0
  79. cli/hook_renderer.py +393 -0
  80. cli/hub_commands.py +580 -0
  81. cli/list_adapters.py +79 -0
  82. cli/list_stacks.py +105 -0
  83. cli/logs_commands.py +89 -0
  84. cli/main.py +3070 -0
  85. cli/materialize_file.py +65 -0
  86. cli/mcp_start.py +153 -0
  87. cli/module_commands.py +513 -0
  88. cli/pr_commands.py +2024 -0
  89. cli/preset_commands.py +126 -0
  90. cli/preset_registry.py +171 -0
  91. cli/project_overrides.py +119 -0
  92. cli/registry.py +365 -0
  93. cli/remove_stack.py +492 -0
  94. cli/renderer.py +620 -0
  95. cli/setup.py +464 -0
  96. cli/skill_commands.py +689 -0
  97. cli/skill_registry.py +235 -0
  98. cli/skills_list.py +332 -0
  99. cli/stack_lint.py +351 -0
  100. cli/stack_registry.py +688 -0
  101. cli/subsystems.py +335 -0
  102. cli/sync_all.py +310 -0
  103. cli/tail_command.py +410 -0
  104. cli/update.py +608 -0
  105. cli/verify_since_edit.py +439 -0
  106. coding_os-0.3.2.dist-info/METADATA +508 -0
  107. coding_os-0.3.2.dist-info/RECORD +1304 -0
  108. coding_os-0.3.2.dist-info/WHEEL +5 -0
  109. coding_os-0.3.2.dist-info/entry_points.txt +5 -0
  110. coding_os-0.3.2.dist-info/licenses/LICENSE +201 -0
  111. coding_os-0.3.2.dist-info/top_level.txt +10 -0
  112. core/__init__.py +0 -0
  113. core/board_os/__init__.py +39 -0
  114. core/board_os/_agent_runtime.py +256 -0
  115. core/board_os/config.py +421 -0
  116. core/board_os/git_coherence.py +107 -0
  117. core/board_os/hub_adapter_manifest.py +140 -0
  118. core/board_os/mcp_tools.py +3228 -0
  119. core/board_os/migration.py +166 -0
  120. core/board_os/parser.py +317 -0
  121. core/board_os/presence.py +156 -0
  122. core/board_os/sync.py +320 -0
  123. core/board_os/transition-gates.yaml +176 -0
  124. core/board_os/transition_gates.py +224 -0
  125. core/board_os/transition_gates_cli.py +272 -0
  126. core/board_os/transition_gates_validator.py +551 -0
  127. core/board_os/verify-suites.yaml +113 -0
  128. core/board_os/verify_suites.py +126 -0
  129. core/board_os/verify_suites_cli.py +328 -0
  130. core/board_os/workflow.py +967 -0
  131. core/commands/board.md +27 -0
  132. core/commands/classify.md +23 -0
  133. core/commands/compose.md +23 -0
  134. core/commands/daily.md +31 -0
  135. core/commands/diagnose.md +7 -0
  136. core/commands/memory-search.md +23 -0
  137. core/commands/new-project.md +33 -0
  138. core/commands/retro.md +38 -0
  139. core/commands/review.md +14 -0
  140. core/commands/task.md +17 -0
  141. core/commands/verify.md +34 -0
  142. core/docs/thinking_os-final-edition.md +1449 -0
  143. core/doctor-config.yaml +74 -0
  144. core/graph_os/__init__.py +29 -0
  145. core/graph_os/backend.py +233 -0
  146. core/graph_os/backends/__init__.py +13 -0
  147. core/graph_os/backends/sqlite_backend.py +1053 -0
  148. core/graph_os/bench/__init__.py +17 -0
  149. core/graph_os/bench/fixtures.py +56 -0
  150. core/graph_os/bench/harness.py +95 -0
  151. core/graph_os/bench/persian_precision.py +142 -0
  152. core/graph_os/bench/scale_500k.py +120 -0
  153. core/graph_os/bench/token_cost.py +170 -0
  154. core/graph_os/bench/viewer_fps.py +110 -0
  155. core/graph_os/communities.py +410 -0
  156. core/graph_os/enterprise.py +218 -0
  157. core/graph_os/entry_points.py +226 -0
  158. core/graph_os/extractors/__init__.py +25 -0
  159. core/graph_os/extractors/code_generic.py +914 -0
  160. core/graph_os/extractors/code_go.py +1422 -0
  161. core/graph_os/extractors/code_json.py +340 -0
  162. core/graph_os/extractors/code_php.py +979 -0
  163. core/graph_os/extractors/code_python.py +1454 -0
  164. core/graph_os/extractors/code_shell.py +538 -0
  165. core/graph_os/extractors/code_toml.py +302 -0
  166. core/graph_os/extractors/code_ts.py +1665 -0
  167. core/graph_os/extractors/code_yaml.py +394 -0
  168. core/graph_os/extractors/contracts.py +1592 -0
  169. core/graph_os/extractors/md_links.py +890 -0
  170. core/graph_os/extractors/task_deps.py +345 -0
  171. core/graph_os/groups/__init__.py +22 -0
  172. core/graph_os/groups/cross_repo.py +156 -0
  173. core/graph_os/groups/manifest.py +141 -0
  174. core/graph_os/ingest/__init__.py +19 -0
  175. core/graph_os/ingest/base.py +306 -0
  176. core/graph_os/ingest/github.py +112 -0
  177. core/graph_os/ingest/zip.py +95 -0
  178. core/graph_os/toolchain.py +393 -0
  179. core/graph_os/tools/__init__.py +9 -0
  180. core/graph_os/tools/graph.py +5573 -0
  181. core/graph_os/tools/reindex_dispatch.py +730 -0
  182. core/graph_os/tree_sitter_overlay.py +235 -0
  183. core/graph_os/types.py +252 -0
  184. core/graph_os/vec_index.py +277 -0
  185. core/graph_os/viewer/__init__.py +12 -0
  186. core/graph_os/viewer/exporter.py +93 -0
  187. core/graph_os/viewer/template.py +189 -0
  188. core/hooks/_helpers/_paths.py +40 -0
  189. core/hooks/_helpers/advance_role.py +72 -0
  190. core/hooks/_helpers/auto_compose.py +228 -0
  191. core/hooks/_helpers/auto_validate_lessons.py +55 -0
  192. core/hooks/_helpers/branch_guard_check.py +796 -0
  193. core/hooks/_helpers/check_commit_message.py +108 -0
  194. core/hooks/_helpers/check_dangerous_rm.py +80 -0
  195. core/hooks/_helpers/check_git_bypass.py +154 -0
  196. core/hooks/_helpers/check_git_destructive.py +77 -0
  197. core/hooks/_helpers/check_settings_write.py +97 -0
  198. core/hooks/_helpers/consume_override.py +51 -0
  199. core/hooks/_helpers/context_budget.py +77 -0
  200. core/hooks/_helpers/cos_say_json.py +103 -0
  201. core/hooks/_helpers/destructive_edit_check.py +163 -0
  202. core/hooks/_helpers/detect_status_transition.py +82 -0
  203. core/hooks/_helpers/digest_regen.py +56 -0
  204. core/hooks/_helpers/doc_sync_check.py +498 -0
  205. core/hooks/_helpers/drain_embedding_outbox.py +52 -0
  206. core/hooks/_helpers/extract_additional_context.py +51 -0
  207. core/hooks/_helpers/extract_commit_msg_arg.py +74 -0
  208. core/hooks/_helpers/git_command_parse.py +424 -0
  209. core/hooks/_helpers/git_settings_fields.py +47 -0
  210. core/hooks/_helpers/graph_context_match.py +37 -0
  211. core/hooks/_helpers/graph_marker_check.py +70 -0
  212. core/hooks/_helpers/jit_recall.py +56 -0
  213. core/hooks/_helpers/json_field.py +41 -0
  214. core/hooks/_helpers/narrative_signal.py +59 -0
  215. core/hooks/_helpers/observation_count.py +31 -0
  216. core/hooks/_helpers/pre_commit_batch.py +177 -0
  217. core/hooks/_helpers/pre_commit_fake_input.py +42 -0
  218. core/hooks/_helpers/presence_gc.py +102 -0
  219. core/hooks/_helpers/presence_write.py +167 -0
  220. core/hooks/_helpers/recover_indirect.py +35 -0
  221. core/hooks/_helpers/routing_evolution.py +104 -0
  222. core/hooks/_helpers/session_recap.py +72 -0
  223. core/hooks/_helpers/skill_primer.py +229 -0
  224. core/hooks/_helpers/task_sync.py +59 -0
  225. core/hooks/_helpers/tool_failure_capture.py +147 -0
  226. core/hooks/_helpers/trajectory_autosnap.py +278 -0
  227. core/hooks/_helpers/trajectory_startup.py +62 -0
  228. core/hooks/_helpers/turn_summary.py +82 -0
  229. core/hooks/_helpers/validate_task_frontmatter.py +98 -0
  230. core/hooks/_helpers/wip_limit_check.py +103 -0
  231. core/hooks/_helpers/wip_lines.py +53 -0
  232. core/hooks/_helpers/work_log_append.py +89 -0
  233. core/hooks/_helpers/wrap_dispatch_output.py +82 -0
  234. core/hooks/advance-role.sh +48 -0
  235. core/hooks/agent-presence.sh +179 -0
  236. core/hooks/auto-brain-decay.sh +184 -0
  237. core/hooks/auto-compose-roles.sh +83 -0
  238. core/hooks/auto-graph-reconcile-shell.sh +119 -0
  239. core/hooks/auto-regen-doc-index.sh +120 -0
  240. core/hooks/auto-reindex-docs.sh +130 -0
  241. core/hooks/auto-task-sync.sh +56 -0
  242. core/hooks/auto-trace-rotate.sh +88 -0
  243. core/hooks/block-bad-patterns.sh +212 -0
  244. core/hooks/block-dangerous-commands.sh +182 -0
  245. core/hooks/block-hardcoded-literals.sh +90 -0
  246. core/hooks/block-migration-conflict.sh +114 -0
  247. core/hooks/block-protected-files.sh +129 -0
  248. core/hooks/block-secrets.sh +185 -0
  249. core/hooks/block-shared-tree-edit.sh +75 -0
  250. core/hooks/block-uv-heredoc.sh +78 -0
  251. core/hooks/branch-guard.sh +122 -0
  252. core/hooks/capture-observation.sh +76 -0
  253. core/hooks/capture-tool-failure.sh +24 -0
  254. core/hooks/capture-work-log.sh +89 -0
  255. core/hooks/check-agents-md-refs.sh +75 -0
  256. core/hooks/check-agents-md-size.sh +49 -0
  257. core/hooks/check-capture-worked.sh +148 -0
  258. core/hooks/check-doc-size.sh +61 -0
  259. core/hooks/check-mcp-extras.sh +92 -0
  260. core/hooks/check-state.sh +87 -0
  261. core/hooks/classify-task-mode.sh +103 -0
  262. core/hooks/cos-env.sh +1301 -0
  263. core/hooks/drain-embedding-outbox.sh +24 -0
  264. core/hooks/enforce-anti-ambiguity.sh +74 -0
  265. core/hooks/enforce-commit-message.sh +73 -0
  266. core/hooks/enforce-doc-anchor.sh +224 -0
  267. core/hooks/enforce-doc-sync.sh +206 -0
  268. core/hooks/enforce-graph-context.sh +89 -0
  269. core/hooks/enforce-graph-first-read.sh +94 -0
  270. core/hooks/enforce-memory-check.sh +128 -0
  271. core/hooks/enforce-rename-plan.sh +78 -0
  272. core/hooks/enforce-scaffold-boundary.sh +68 -0
  273. core/hooks/enforce-skill.sh +125 -0
  274. core/hooks/enforce-task-body.sh +52 -0
  275. core/hooks/enforce-task-start.sh +81 -0
  276. core/hooks/enforce-task-transition.sh +75 -0
  277. core/hooks/enforce-template.sh +143 -0
  278. core/hooks/enforce-verify.sh +112 -0
  279. core/hooks/enforce-wip-limit.sh +43 -0
  280. core/hooks/enforce-zoom.sh +69 -0
  281. core/hooks/ensure-hub-up.sh +67 -0
  282. core/hooks/inject-mcp-caller-session.sh +70 -0
  283. core/hooks/jit-recall.sh +65 -0
  284. core/hooks/link-commit-to-task.sh +143 -0
  285. core/hooks/lint-task.sh +40 -0
  286. core/hooks/nudge-docs-first.sh +71 -0
  287. core/hooks/nudge-git-mode.sh +29 -0
  288. core/hooks/nudge-graph-os.sh +118 -0
  289. core/hooks/nudge-learn-narrative.sh +36 -0
  290. core/hooks/nudge-model-routing.sh +32 -0
  291. core/hooks/nudge-reentry.sh +101 -0
  292. core/hooks/nudge-reuse-first.sh +68 -0
  293. core/hooks/nudge-task-discovery.sh +81 -0
  294. core/hooks/nudge-thinking-os.sh +109 -0
  295. core/hooks/pr-reap.sh +23 -0
  296. core/hooks/reclaim-sweep.sh +58 -0
  297. core/hooks/record-verify-auto.sh +77 -0
  298. core/hooks/record-verify.sh +74 -0
  299. core/hooks/regen-reminder.sh +104 -0
  300. core/hooks/registry.yaml +1262 -0
  301. core/hooks/remind-daily.sh +27 -0
  302. core/hooks/remind-dogfood.sh +70 -0
  303. core/hooks/remind-learn-validate.sh +94 -0
  304. core/hooks/rules-primer.sh +50 -0
  305. core/hooks/search-enforce-inventory.sh +108 -0
  306. core/hooks/search-verify-remaining.sh +132 -0
  307. core/hooks/session-context.sh +729 -0
  308. core/hooks/session-end.sh +145 -0
  309. core/hooks/session-skill-primer.sh +43 -0
  310. core/hooks/snapshot-transcript.sh +56 -0
  311. core/hooks/sync-task-current.sh +85 -0
  312. core/hooks/test-first-reminder.sh +120 -0
  313. core/hooks/test-governor.sh +173 -0
  314. core/hooks/thinking_os-gate.sh +52 -0
  315. core/hooks/track-backtrack.sh +35 -0
  316. core/hooks/track-discovery.sh +121 -0
  317. core/hooks/track-skill.sh +53 -0
  318. core/hooks/validate-task-frontmatter.sh +49 -0
  319. core/hooks/verify-rename-callers.sh +119 -0
  320. core/hooks/warn-abandoned-task.sh +99 -0
  321. core/hooks/warn-destructive-edit.sh +64 -0
  322. core/hooks/warn-diff-size.sh +43 -0
  323. core/hooks/warn-graph-empty.sh +81 -0
  324. core/hooks/warn-mcp-down.sh +190 -0
  325. core/hooks/write-state.sh +55 -0
  326. core/logging_os/__init__.py +33 -0
  327. core/logging_os/api.py +127 -0
  328. core/logging_os/bridge.py +80 -0
  329. core/logging_os/config.py +172 -0
  330. core/logging_os/fingerprint.py +25 -0
  331. core/logging_os/redact.py +53 -0
  332. core/logging_os/render.py +83 -0
  333. core/logging_os/sinks.py +164 -0
  334. core/rules/anti-overengineering.md +44 -0
  335. core/rules/api-contract-discipline.md +41 -0
  336. core/rules/dimension-registry.md +155 -0
  337. core/rules/git-workflow.md +57 -0
  338. core/rules/memory.md +46 -0
  339. core/rules/model-routing.md +22 -0
  340. core/rules/skill-enforcement.md +75 -0
  341. core/rules/test-discipline.md +38 -0
  342. core/rules/thinking_os.md +48 -0
  343. core/rules/transparency-banner.md +37 -0
  344. core/runtime_paths.yaml +36 -0
  345. core/scaffold_manifest.json +14430 -0
  346. core/scheduled/__init__.py +0 -0
  347. core/scheduled/_activity.py +126 -0
  348. core/scheduled/_state.py +113 -0
  349. core/scheduled/config.py +86 -0
  350. core/scheduled/dep_reconcile.py +135 -0
  351. core/scheduled/error_sweep.py +137 -0
  352. core/scheduled/nightly.py +930 -0
  353. core/scheduled/responsive_extract.py +65 -0
  354. core/schemas/adapter.schema.json +269 -0
  355. core/schemas/preset.schema.json +50 -0
  356. core/schemas/skill.schema.json +81 -0
  357. core/schemas/stack.schema.json +404 -0
  358. core/scripts/_lib.sh +9 -0
  359. core/scripts/docs-lint.sh +228 -0
  360. core/scripts/docs-nav-fix.sh +133 -0
  361. core/scripts/docs-staleness-check.sh +154 -0
  362. core/scripts/install-adapter.sh +266 -0
  363. core/scripts/link-stack-skills.sh +52 -0
  364. core/scripts/log-latest.sh +106 -0
  365. core/scripts/log-search.sh +89 -0
  366. core/scripts/log-write.sh +134 -0
  367. core/scripts/ref-resolve.sh +71 -0
  368. core/skills/a11y/SKILL.md +305 -0
  369. core/skills/a11y/assets/a11y-checklist.md +137 -0
  370. core/skills/a11y/references/aria-and-focus.md +247 -0
  371. core/skills/a11y/references/rn-accessibility.md +343 -0
  372. core/skills/a11y/references/screen-reader-testing.md +190 -0
  373. core/skills/agent-memory/SKILL.md +191 -0
  374. core/skills/agent-memory/assets/memory-checklist.md +21 -0
  375. core/skills/agent-memory/references/memory-recipes.md +57 -0
  376. core/skills/api-design/SKILL.md +232 -0
  377. core/skills/api-design/assets/api-design-checklist.md +110 -0
  378. core/skills/api-design/references/error-envelope.md +381 -0
  379. core/skills/api-design/references/idempotency-pagination.md +312 -0
  380. core/skills/api-design/references/rest-contracts.md +426 -0
  381. core/skills/auth-patterns/SKILL.md +352 -0
  382. core/skills/auth-patterns/assets/auth-checklist.md +118 -0
  383. core/skills/auth-patterns/references/jwt-and-service-tokens.md +343 -0
  384. core/skills/auth-patterns/references/passkeys-2fa.md +289 -0
  385. core/skills/auth-patterns/references/sessions-vs-jwt.md +230 -0
  386. core/skills/auth-patterns/scripts/cookie-flag-check.py +146 -0
  387. core/skills/backend-fundamentals/SKILL.md +238 -0
  388. core/skills/backend-fundamentals/assets/backend-checklist.md +27 -0
  389. core/skills/backend-fundamentals/references/backend-patterns.md +56 -0
  390. core/skills/backend-fundamentals/scripts/check_layering.py +83 -0
  391. core/skills/clean-code/SKILL.md +642 -0
  392. core/skills/clean-code/scripts/audit-fail-closed.py +167 -0
  393. core/skills/codebase-explorer/SKILL.md +89 -0
  394. core/skills/codebase-explorer/assets/reading-checklist.md +24 -0
  395. core/skills/codebase-explorer/references/reading-strategies.md +52 -0
  396. core/skills/codebase-explorer/scripts/outline.py +99 -0
  397. core/skills/db-design/SKILL.md +327 -0
  398. core/skills/db-design/assets/migration-template.sql +49 -0
  399. core/skills/db-design/references/migration-discipline.md +290 -0
  400. core/skills/db-design/references/postgres-patterns.md +340 -0
  401. core/skills/db-design/scripts/migration-safety.sh +150 -0
  402. core/skills/deployment-cicd/SKILL.md +260 -0
  403. core/skills/deployment-cicd/assets/deploy-checklist.md +26 -0
  404. core/skills/deployment-cicd/references/pipeline-and-release.md +54 -0
  405. core/skills/deployment-cicd/scripts/lint_workflow.py +79 -0
  406. core/skills/docker/SKILL.md +114 -0
  407. core/skills/docker/assets/dockerfile-checklist.md +31 -0
  408. core/skills/docker/references/compose-patterns.md +66 -0
  409. core/skills/docker/references/dockerfile-optimization.md +64 -0
  410. core/skills/docker/scripts/lint_dockerfile.sh +48 -0
  411. core/skills/docker/versions.json +16 -0
  412. core/skills/end-to-end-testing/SKILL.md +101 -0
  413. core/skills/end-to-end-testing/assets/e2e-checklist.md +23 -0
  414. core/skills/end-to-end-testing/references/maestro.md +63 -0
  415. core/skills/end-to-end-testing/references/playwright.md +68 -0
  416. core/skills/end-to-end-testing/scripts/lint_e2e.py +88 -0
  417. core/skills/end-to-end-testing/versions.json +16 -0
  418. core/skills/frontend-design/SKILL.md +76 -0
  419. core/skills/frontend-design/assets/design-checklist.md +29 -0
  420. core/skills/frontend-design/references/design-principles.md +65 -0
  421. core/skills/frontend-design/scripts/check_contrast.py +89 -0
  422. core/skills/frontend-fundamentals/SKILL.md +213 -0
  423. core/skills/frontend-fundamentals/assets/frontend-checklist.md +25 -0
  424. core/skills/frontend-fundamentals/references/rendering-and-state.md +66 -0
  425. core/skills/frontend-fundamentals/scripts/check_frontend.py +86 -0
  426. core/skills/graph-explorer/SKILL.md +215 -0
  427. core/skills/graph-explorer/scripts/explain-impact.sh +64 -0
  428. core/skills/graphql/SKILL.md +187 -0
  429. core/skills/grpc-microservices/SKILL.md +174 -0
  430. core/skills/hexagonal-architecture/SKILL.md +199 -0
  431. core/skills/hexagonal-architecture/assets/folder-scaffold.md +233 -0
  432. core/skills/hexagonal-architecture/references/anti-patterns.md +129 -0
  433. core/skills/hexagonal-architecture/references/go-fiber-layout.md +429 -0
  434. core/skills/hexagonal-architecture/references/python-fastapi-layout.md +453 -0
  435. core/skills/hexagonal-architecture/references/react-native-layout.md +428 -0
  436. core/skills/i18n/SKILL.md +126 -0
  437. core/skills/incident-response/SKILL.md +225 -0
  438. core/skills/incident-response/assets/incident-checklist.md +29 -0
  439. core/skills/incident-response/references/severity-and-runbook.md +53 -0
  440. core/skills/incident-response/scripts/classify_severity.py +86 -0
  441. core/skills/linux-sysadmin/SKILL.md +115 -0
  442. core/skills/linux-sysadmin/assets/hardening-checklist.md +29 -0
  443. core/skills/linux-sysadmin/references/ssh-hardening.md +62 -0
  444. core/skills/linux-sysadmin/references/systemd-and-services.md +77 -0
  445. core/skills/linux-sysadmin/scripts/triage.sh +50 -0
  446. core/skills/linux-sysadmin/versions.json +17 -0
  447. core/skills/llm-patterns/SKILL.md +410 -0
  448. core/skills/llm-patterns/assets/llm-feature-checklist.md +26 -0
  449. core/skills/llm-patterns/references/rag-and-evals.md +59 -0
  450. core/skills/llm-patterns/scripts/estimate_tokens.py +75 -0
  451. core/skills/messaging-queues/SKILL.md +142 -0
  452. core/skills/mobile-fundamentals/SKILL.md +406 -0
  453. core/skills/mobile-fundamentals/assets/mobile-launch-checklist.md +130 -0
  454. core/skills/mobile-fundamentals/references/navigation-and-deep-links.md +337 -0
  455. core/skills/mobile-fundamentals/references/offline-sync.md +339 -0
  456. core/skills/node-backend/SKILL.md +114 -0
  457. core/skills/node-backend/assets/node-checklist.md +25 -0
  458. core/skills/node-backend/references/async-and-errors.md +67 -0
  459. core/skills/node-backend/references/event-loop.md +65 -0
  460. core/skills/node-backend/scripts/check_package.py +78 -0
  461. core/skills/node-backend/versions.json +17 -0
  462. core/skills/observability/SKILL.md +289 -0
  463. core/skills/observability/assets/observability-checklist.md +27 -0
  464. core/skills/observability/references/instrumentation.md +57 -0
  465. core/skills/observability/scripts/lint_logging.py +77 -0
  466. core/skills/payments/SKILL.md +102 -0
  467. core/skills/performance/SKILL.md +305 -0
  468. core/skills/performance/assets/perf-checklist.md +143 -0
  469. core/skills/performance/references/mobile-performance.md +249 -0
  470. core/skills/performance/references/web-vitals.md +209 -0
  471. core/skills/php/SKILL.md +116 -0
  472. core/skills/php/assets/php-checklist.md +26 -0
  473. core/skills/php/references/modern-php.md +64 -0
  474. core/skills/php/references/security.md +72 -0
  475. core/skills/php/scripts/scan_php_smells.py +99 -0
  476. core/skills/php/versions.json +9 -0
  477. core/skills/pr-mode-driver/SKILL.md +65 -0
  478. core/skills/realtime-websockets/SKILL.md +152 -0
  479. core/skills/redis/SKILL.md +105 -0
  480. core/skills/redis/assets/redis-checklist.md +27 -0
  481. core/skills/redis/references/operations.md +66 -0
  482. core/skills/redis/references/patterns.md +62 -0
  483. core/skills/redis/scripts/analyze_info.py +101 -0
  484. core/skills/redis/versions.json +9 -0
  485. core/skills/search/SKILL.md +91 -0
  486. core/skills/search/references/grep.md +76 -0
  487. core/skills/search/scripts/verify-count.sh +73 -0
  488. core/skills/search-infra/SKILL.md +109 -0
  489. core/skills/security-mobile/SKILL.md +394 -0
  490. core/skills/security-mobile/assets/mobile-security-checklist.md +117 -0
  491. core/skills/security-mobile/references/masvs-l1-checklist.md +127 -0
  492. core/skills/security-web/SKILL.md +217 -0
  493. core/skills/security-web/assets/security-web-checklist.md +167 -0
  494. core/skills/security-web/references/owasp-top-10.md +551 -0
  495. core/skills/security-web/references/supply-chain.md +179 -0
  496. core/skills/security-web/scripts/csp-check.sh +153 -0
  497. core/skills/shell-scripting/SKILL.md +128 -0
  498. core/skills/shell-scripting/assets/script-checklist.md +33 -0
  499. core/skills/shell-scripting/references/argument-parsing.md +84 -0
  500. core/skills/shell-scripting/references/bash-robustness.md +78 -0
  501. core/skills/shell-scripting/scripts/lint_script.sh +53 -0
  502. core/skills/shell-scripting/scripts/new_script.py +131 -0
  503. core/skills/shell-scripting/versions.json +23 -0
  504. core/skills/sql-authoring/SKILL.md +104 -0
  505. core/skills/sql-authoring/assets/query-review-checklist.md +26 -0
  506. core/skills/sql-authoring/references/query-patterns.md +89 -0
  507. core/skills/sql-authoring/references/reading-explain.md +52 -0
  508. core/skills/sql-authoring/scripts/analyze_plan.py +100 -0
  509. core/skills/sql-authoring/versions.json +17 -0
  510. core/skills/state-management/SKILL.md +428 -0
  511. core/skills/state-management/references/tanstack-query-recipes.md +301 -0
  512. core/skills/state-management/references/zustand-recipes.md +364 -0
  513. core/skills/supabase/SKILL.md +108 -0
  514. core/skills/supabase/assets/supabase-checklist.md +26 -0
  515. core/skills/supabase/references/realtime-and-storage.md +55 -0
  516. core/skills/supabase/references/rls-and-auth.md +65 -0
  517. core/skills/supabase/scripts/check_rls.py +88 -0
  518. core/skills/supabase/versions.json +9 -0
  519. core/skills/task-driver/SKILL.md +272 -0
  520. core/skills/task-driver/scripts/task-lint.sh +163 -0
  521. core/skills/technical-writing/SKILL.md +85 -0
  522. core/skills/technical-writing/assets/doc-checklist.md +29 -0
  523. core/skills/technical-writing/references/doc-anatomy.md +53 -0
  524. core/skills/technical-writing/references/writing-craft.md +59 -0
  525. core/skills/technical-writing/scripts/new_doc.py +81 -0
  526. core/skills/terraform-k8s/SKILL.md +133 -0
  527. core/skills/testing-strategy/SKILL.md +266 -0
  528. core/skills/testing-strategy/assets/test-review-checklist.md +25 -0
  529. core/skills/testing-strategy/references/test-types.md +52 -0
  530. core/skills/testing-strategy/scripts/coverage_gate.py +79 -0
  531. core/skills/thinking_os/SKILL.md +288 -0
  532. core/skills/thinking_os/scripts/classify.sh +122 -0
  533. core/skills/typescript/SKILL.md +110 -0
  534. core/skills/typescript/assets/typescript-checklist.md +24 -0
  535. core/skills/typescript/references/strictness.md +53 -0
  536. core/skills/typescript/references/type-system.md +86 -0
  537. core/skills/typescript/scripts/check_tsconfig.py +92 -0
  538. core/skills/typescript/versions.json +9 -0
  539. core/subsystems.yaml +202 -0
  540. core/thinking_os/__init__.py +1 -0
  541. core/thinking_os/_agent_markers.py +32 -0
  542. core/thinking_os/agents/README.md +71 -0
  543. core/thinking_os/agents/analyst.md +139 -0
  544. core/thinking_os/agents/architect.md +127 -0
  545. core/thinking_os/agents/debugger.md +111 -0
  546. core/thinking_os/agents/deployer.md +112 -0
  547. core/thinking_os/agents/distiller.md +28 -0
  548. core/thinking_os/agents/documenter.md +101 -0
  549. core/thinking_os/agents/implementer.md +135 -0
  550. core/thinking_os/agents/internal/session_observer.md +39 -0
  551. core/thinking_os/agents/observer.md +100 -0
  552. core/thinking_os/agents/onboarder.md +81 -0
  553. core/thinking_os/agents/refactorer.md +123 -0
  554. core/thinking_os/agents/repairer.md +47 -0
  555. core/thinking_os/agents/researcher.md +129 -0
  556. core/thinking_os/agents/reviewer.md +147 -0
  557. core/thinking_os/agents/security_auditor.md +133 -0
  558. core/thinking_os/background.py +405 -0
  559. core/thinking_os/bootstrap_outcomes.py +200 -0
  560. core/thinking_os/budget.py +302 -0
  561. core/thinking_os/capture.py +495 -0
  562. core/thinking_os/cognition.py +516 -0
  563. core/thinking_os/cognition_schemas.py +517 -0
  564. core/thinking_os/compress.py +192 -0
  565. core/thinking_os/concepts.py +233 -0
  566. core/thinking_os/dashboard.py +159 -0
  567. core/thinking_os/database.py +2883 -0
  568. core/thinking_os/decay.py +393 -0
  569. core/thinking_os/digest.py +295 -0
  570. core/thinking_os/dispatcher.py +192 -0
  571. core/thinking_os/dispatcher_helpers.py +48 -0
  572. core/thinking_os/dispatchers/__init__.py +3 -0
  573. core/thinking_os/dispatchers/default.py +47 -0
  574. core/thinking_os/distill.py +192 -0
  575. core/thinking_os/doc_indexer.py +905 -0
  576. core/thinking_os/embeddings.py +943 -0
  577. core/thinking_os/formula_composer.py +556 -0
  578. core/thinking_os/gate_marker.py +75 -0
  579. core/thinking_os/graph.py +296 -0
  580. core/thinking_os/graph_indexer.py +360 -0
  581. core/thinking_os/health_check.py +517 -0
  582. core/thinking_os/impact.py +119 -0
  583. core/thinking_os/memory_gc.py +356 -0
  584. core/thinking_os/migrator_embeddings.py +318 -0
  585. core/thinking_os/precision.py +194 -0
  586. core/thinking_os/presets/registry.yaml +127 -0
  587. core/thinking_os/record_outcome.py +399 -0
  588. core/thinking_os/repair.py +105 -0
  589. core/thinking_os/retrieval_quality.py +239 -0
  590. core/thinking_os/roles/analyst.yaml +83 -0
  591. core/thinking_os/roles/architect.yaml +88 -0
  592. core/thinking_os/roles/debugger.yaml +71 -0
  593. core/thinking_os/roles/deployer.yaml +71 -0
  594. core/thinking_os/roles/documenter.yaml +72 -0
  595. core/thinking_os/roles/implementer.yaml +83 -0
  596. core/thinking_os/roles/observer.yaml +70 -0
  597. core/thinking_os/roles/refactorer.yaml +71 -0
  598. core/thinking_os/roles/researcher.yaml +72 -0
  599. core/thinking_os/roles/reviewer.yaml +79 -0
  600. core/thinking_os/roles/security_auditor.yaml +83 -0
  601. core/thinking_os/roles_state.py +168 -0
  602. core/thinking_os/sanitizer.py +320 -0
  603. core/thinking_os/server.py +3160 -0
  604. core/thinking_os/session_enrich.py +272 -0
  605. core/thinking_os/session_observe_worker.py +111 -0
  606. core/thinking_os/session_startup.py +74 -0
  607. core/thinking_os/session_summary.py +245 -0
  608. core/thinking_os/situations/registry.yaml +99 -0
  609. core/thinking_os/task_analyzer.py +462 -0
  610. core/thinking_os/task_parser.py +342 -0
  611. core/thinking_os/task_sync.py +73 -0
  612. core/thinking_os/tools/__init__.py +6 -0
  613. core/thinking_os/tools/_shared.py +947 -0
  614. core/thinking_os/tools/cognition.py +1867 -0
  615. core/thinking_os/tools/docs.py +770 -0
  616. core/thinking_os/tools/learning.py +2078 -0
  617. core/thinking_os/tools/logs.py +79 -0
  618. core/thinking_os/tools/memory.py +840 -0
  619. core/thinking_os/tools/metrics.py +200 -0
  620. core/thinking_os/tools/retrieve.py +415 -0
  621. core/thinking_os/tools/routing.py +658 -0
  622. core/thinking_os/tools/tasks.py +449 -0
  623. core/thinking_os/tools/trajectory.py +181 -0
  624. core/thinking_os/tracing.py +235 -0
  625. core/web/__init__.py +5 -0
  626. core/web/_cache.py +118 -0
  627. core/web/_deps.py +56 -0
  628. core/web/_envelope.py +85 -0
  629. core/web/_project_context.py +140 -0
  630. core/web/chat_providers.py +108 -0
  631. core/web/init_jobs.py +216 -0
  632. core/web/routes/__init__.py +25 -0
  633. core/web/routes/_bounded_read.py +76 -0
  634. core/web/routes/board.py +1089 -0
  635. core/web/routes/cognition.py +1838 -0
  636. core/web/routes/config.py +635 -0
  637. core/web/routes/graph.py +513 -0
  638. core/web/routes/health.py +180 -0
  639. core/web/routes/hooks.py +288 -0
  640. core/web/routes/hub.py +1219 -0
  641. core/web/routes/logs.py +374 -0
  642. core/web/routes/metrics.py +43 -0
  643. core/web/routes/observability.py +400 -0
  644. core/web/routes/patterns.py +227 -0
  645. core/web/routes/presence.py +609 -0
  646. core/web/routes/roles.py +446 -0
  647. core/web/routes/scheduled.py +261 -0
  648. core/web/routes/search.py +238 -0
  649. core/web/routes/sessions.py +220 -0
  650. core/web/routes/settings.py +363 -0
  651. core/web/routes/stream.py +547 -0
  652. core/web/security.py +157 -0
  653. core/web/server.py +283 -0
  654. graph_os/__init__.py +29 -0
  655. graph_os/backend.py +233 -0
  656. graph_os/backends/__init__.py +13 -0
  657. graph_os/backends/sqlite_backend.py +1053 -0
  658. graph_os/communities.py +410 -0
  659. graph_os/enterprise.py +218 -0
  660. graph_os/entry_points.py +226 -0
  661. graph_os/extractors/__init__.py +25 -0
  662. graph_os/extractors/code_generic.py +914 -0
  663. graph_os/extractors/code_go.py +1422 -0
  664. graph_os/extractors/code_json.py +340 -0
  665. graph_os/extractors/code_php.py +979 -0
  666. graph_os/extractors/code_python.py +1454 -0
  667. graph_os/extractors/code_shell.py +538 -0
  668. graph_os/extractors/code_toml.py +302 -0
  669. graph_os/extractors/code_ts.py +1665 -0
  670. graph_os/extractors/code_yaml.py +394 -0
  671. graph_os/extractors/contracts.py +1592 -0
  672. graph_os/extractors/md_links.py +890 -0
  673. graph_os/extractors/task_deps.py +345 -0
  674. graph_os/groups/__init__.py +22 -0
  675. graph_os/groups/cross_repo.py +156 -0
  676. graph_os/groups/manifest.py +141 -0
  677. graph_os/ingest/__init__.py +19 -0
  678. graph_os/ingest/base.py +306 -0
  679. graph_os/ingest/github.py +112 -0
  680. graph_os/ingest/zip.py +95 -0
  681. graph_os/toolchain.py +393 -0
  682. graph_os/tools/__init__.py +9 -0
  683. graph_os/tools/graph.py +5573 -0
  684. graph_os/tools/reindex_dispatch.py +730 -0
  685. graph_os/tree_sitter_overlay.py +235 -0
  686. graph_os/types.py +252 -0
  687. graph_os/vec_index.py +277 -0
  688. graph_os/viewer/__init__.py +12 -0
  689. graph_os/viewer/exporter.py +93 -0
  690. graph_os/viewer/template.py +189 -0
  691. scheduled/__init__.py +0 -0
  692. scheduled/_activity.py +126 -0
  693. scheduled/_state.py +113 -0
  694. scheduled/config.py +86 -0
  695. scheduled/dep_reconcile.py +135 -0
  696. scheduled/error_sweep.py +137 -0
  697. scheduled/nightly.py +930 -0
  698. scheduled/responsive_extract.py +65 -0
  699. scripts/__init__.py +4 -0
  700. scripts/_commit_msg_body.sh +31 -0
  701. scripts/_post_commit_body.sh +49 -0
  702. scripts/_pre_commit_body.sh +121 -0
  703. scripts/_prepare_commit_msg_body.sh +53 -0
  704. scripts/audit_mcp_tools.py +693 -0
  705. scripts/bench_sdk_dispatcher.py +177 -0
  706. scripts/capture_golden.py +169 -0
  707. scripts/check_graph_phantoms.py +75 -0
  708. scripts/dev/audit_doc_links.py +359 -0
  709. scripts/dev/audit_scaffold_module_tags.py +107 -0
  710. scripts/dev/backfill_doc_headers.py +328 -0
  711. scripts/dev/backfill_nav_lines.py +119 -0
  712. scripts/dev/fix_nav_placement.py +106 -0
  713. scripts/dev/inspect_sdk_options.py +45 -0
  714. scripts/dev/migrate_check_ids.py +170 -0
  715. scripts/dev/strip_purpose_blocks.py +154 -0
  716. scripts/dump_openapi.py +66 -0
  717. scripts/e2e_dispatch_tool.py +195 -0
  718. scripts/generate_manifest.py +166 -0
  719. scripts/golden_sections.py +20 -0
  720. scripts/graph_demo.py +161 -0
  721. scripts/install-git-hooks.sh +47 -0
  722. scripts/migrate_embeddings_minilm_to_bge_m3.py +84 -0
  723. scripts/operational_eval.py +445 -0
  724. scripts/probe_agent_session_resolver.py +59 -0
  725. scripts/prune_deleted_path.py +127 -0
  726. scripts/refactor_agent_dual_mode.py +171 -0
  727. scripts/refresh_skill_versions.py +302 -0
  728. scripts/regen_doc_index.py +209 -0
  729. scripts/regen_doctor_schema.py +62 -0
  730. scripts/regen_rules.py +94 -0
  731. scripts/rename_formulas_to_semantic.py +241 -0
  732. scripts/smoke_db_connections.py +183 -0
  733. scripts/smoke_doc_header.py +72 -0
  734. scripts/smoke_graph_e2e.py +374 -0
  735. scripts/smoke_sdk_dispatch.py +84 -0
  736. scripts/smoke_uid_resolver.py +164 -0
  737. scripts/verify_dispatchers.py +244 -0
  738. scripts/verify_phase_c_e2e.py +436 -0
  739. templates/__init__.py +6 -0
  740. templates/_base/Makefile.base +353 -0
  741. templates/_base/base.yaml +59 -0
  742. templates/_base/coding-os.yaml.template +39 -0
  743. templates/_base/dimension-registry.template.md +68 -0
  744. templates/_base/domain-config.template.json +46 -0
  745. templates/_base/fragments/anatomy-map.md.tmpl +11 -0
  746. templates/_base/fragments/context-discipline.md.tmpl +3 -0
  747. templates/_base/fragments/core-loop.md.tmpl +52 -0
  748. templates/_base/fragments/engineering-routing.md.tmpl +3 -0
  749. templates/_base/fragments/header.md.tmpl +6 -0
  750. templates/_base/fragments/identity.md.tmpl +3 -0
  751. templates/_base/fragments/principles.md.tmpl +3 -0
  752. templates/_base/fragments/retrieval-routing.md.tmpl +23 -0
  753. templates/_base/fragments/session-handoff.md.tmpl +3 -0
  754. templates/_base/fragments/skills.md.tmpl +3 -0
  755. templates/_base/fragments/ssot-map.md.tmpl +3 -0
  756. templates/_base/fragments/stop-conditions.md.tmpl +3 -0
  757. templates/_base/fragments/subagent-dispatch.md.tmpl +3 -0
  758. templates/_base/fragments/task-authoring.md.tmpl +69 -0
  759. templates/_base/fragments/task-logging.md.tmpl +8 -0
  760. templates/_base/fragments/tool-routing.md.tmpl +9 -0
  761. templates/_base/fragments/verification-matrix.md.tmpl +12 -0
  762. templates/_base/lang/dart/analysis_options.yaml +7 -0
  763. templates/_base/lang/php/phpcs.xml.dist +10 -0
  764. templates/_base/lang/python/pyproject.toml +19 -0
  765. templates/_base/lang/rust/clippy.toml +5 -0
  766. templates/_base/lang/rust/rustfmt.toml +3 -0
  767. templates/_base/lang/typescript/eslint.config.js +26 -0
  768. templates/_base/lang/typescript/tsconfig.json +15 -0
  769. templates/_base/lang/typescript/vitest.config.ts +10 -0
  770. templates/_base/scaffold/changes.log +1 -0
  771. templates/_base/scaffold/docs/00-index.md +55 -0
  772. templates/_base/scaffold/docs/_meta/feature-dependency-tree.md +30 -0
  773. templates/_base/scaffold/docs/_meta/foundation-map.md +56 -0
  774. templates/_base/scaffold/docs/_meta/questions.md +8 -0
  775. templates/_base/scaffold/docs/_meta/roadmap.md +33 -0
  776. templates/_base/scaffold/docs/api-contracts/00-index.md +58 -0
  777. templates/_base/scaffold/docs/api-contracts/error-format.md +58 -0
  778. templates/_base/scaffold/docs/architecture/00-index.md +42 -0
  779. templates/_base/scaffold/docs/architecture/adr/00-index.md +39 -0
  780. templates/_base/scaffold/docs/engineering/00-index.md +9 -0
  781. templates/_base/scaffold/docs/governance/00-index.md +55 -0
  782. templates/_base/scaffold/docs/governance/_templates/doc-cheat-sheet.md +202 -0
  783. templates/_base/scaffold/docs/governance/_templates/playbook-template.md +81 -0
  784. templates/_base/scaffold/docs/governance/_templates/post-mortem-template.md +85 -0
  785. templates/_base/scaffold/docs/governance/_templates/runbook-template.md +88 -0
  786. templates/_base/scaffold/docs/governance/_templates/security-review-template.md +111 -0
  787. templates/_base/scaffold/docs/governance/_templates/task-detail.md +61 -0
  788. templates/_base/scaffold/docs/governance/agent-workflow.md +101 -0
  789. templates/_base/scaffold/docs/governance/anatomy-contract.md +150 -0
  790. templates/_base/scaffold/docs/governance/critical-rules.md +224 -0
  791. templates/_base/scaffold/docs/governance/decision-records.md +58 -0
  792. templates/_base/scaffold/docs/governance/docs-first-protocol.md +157 -0
  793. templates/_base/scaffold/docs/governance/docs-system.md +151 -0
  794. templates/_base/scaffold/docs/governance/gdpr-compliance.md +66 -0
  795. templates/_base/scaffold/docs/governance/mcp-tool-inventory.md +112 -0
  796. templates/_base/scaffold/docs/governance/risk-register.md +26 -0
  797. templates/_base/scaffold/docs/governance/scaffold-boundary-contract.md +161 -0
  798. templates/_base/scaffold/docs/governance/task-lifecycle.md +125 -0
  799. templates/_base/scaffold/docs/governance/wrapper-derivation.md +50 -0
  800. templates/_base/scaffold/docs/insights/00-index.md +17 -0
  801. templates/_base/scaffold/docs/ops/00-index.md +59 -0
  802. templates/_base/scaffold/docs/ops/runbooks/00-index.md +9 -0
  803. templates/_base/scaffold/docs/playbooks/00-index.md +12 -0
  804. templates/_base/scaffold/docs/playbooks/research-validation.md +29 -0
  805. templates/_base/scaffold/docs/playbooks/security-review.md +41 -0
  806. templates/_base/scaffold/docs/prd/00-index.md +43 -0
  807. templates/_base/scaffold/docs/prd/01-snapshot-vision.md +56 -0
  808. templates/_base/scaffold/docs/workflow/workflow-guide.md +138 -0
  809. templates/_base/scaffold/src/shared/README.md +23 -0
  810. templates/_base/skill-enforcement.template.md +14 -0
  811. templates/_base/task-detail.template.md +61 -0
  812. templates/_presets/ai-saas.yaml +9 -0
  813. templates/_presets/django-next.yaml +8 -0
  814. templates/_presets/dotnet-react.yaml +8 -0
  815. templates/_presets/flutter-baas.yaml +8 -0
  816. templates/_presets/go-react.yaml +8 -0
  817. templates/_presets/hexagonal-product.yaml +16 -0
  818. templates/_presets/jamstack.yaml +8 -0
  819. templates/_presets/laravel-vue.yaml +8 -0
  820. templates/_presets/mean.yaml +8 -0
  821. templates/_presets/mern.yaml +9 -0
  822. templates/_presets/nest-angular.yaml +8 -0
  823. templates/_presets/nextjs-fastapi.yaml +10 -0
  824. templates/_presets/nuxt-fullstack.yaml +9 -0
  825. templates/_presets/pern.yaml +9 -0
  826. templates/_presets/rails-react.yaml +8 -0
  827. templates/_presets/rn-api.yaml +8 -0
  828. templates/_presets/rust-svelte.yaml +8 -0
  829. templates/_presets/spring-react.yaml +8 -0
  830. templates/_presets/t3-style.yaml +9 -0
  831. templates/_presets/tall.yaml +8 -0
  832. templates/_presets/wordpress-cms.yaml +8 -0
  833. templates/angular/rules/frontend.md +19 -0
  834. templates/angular/scaffold/docs/engineering/accessibility.md +46 -0
  835. templates/angular/scaffold/docs/engineering/angular-rules.md +35 -0
  836. templates/angular/scaffold/docs/playbooks/angular-app.md +42 -0
  837. templates/angular/scaffold/src/frontend/angular.json +52 -0
  838. templates/angular/scaffold/src/frontend/package.json +28 -0
  839. templates/angular/scaffold/src/frontend/src/app/app.component.ts +19 -0
  840. templates/angular/scaffold/src/frontend/src/app/app.config.ts +22 -0
  841. templates/angular/scaffold/src/frontend/src/app/app.routes.ts +8 -0
  842. templates/angular/scaffold/src/frontend/src/app/core/global-error-handler.ts +12 -0
  843. templates/angular/scaffold/src/frontend/src/app/health/health.component.ts +14 -0
  844. templates/angular/scaffold/src/frontend/src/app/health/health.service.spec.ts +16 -0
  845. templates/angular/scaffold/src/frontend/src/app/health/health.service.ts +10 -0
  846. templates/angular/scaffold/src/frontend/src/index.html +11 -0
  847. templates/angular/scaffold/src/frontend/src/main.ts +9 -0
  848. templates/angular/scaffold/src/frontend/src/styles.css +14 -0
  849. templates/angular/scaffold/src/frontend/tsconfig.app.json +8 -0
  850. templates/angular/scaffold/src/frontend/tsconfig.json +27 -0
  851. templates/angular/scaffold/src/frontend/tsconfig.spec.json +8 -0
  852. templates/angular/scaffold-boundary.yaml +27 -0
  853. templates/angular/skills/angular/SKILL.md +79 -0
  854. templates/angular/skills/angular/references/anatomy.md +69 -0
  855. templates/angular/stack.yaml +75 -0
  856. templates/aspnet-core/rules/backend.md +20 -0
  857. templates/aspnet-core/scaffold/docs/engineering/aspnet-core-rules.md +36 -0
  858. templates/aspnet-core/scaffold/docs/playbooks/aspnet-core-service.md +40 -0
  859. templates/aspnet-core/scaffold/src/backend/Backend.csproj +11 -0
  860. templates/aspnet-core/scaffold/src/backend/Backend.sln +27 -0
  861. templates/aspnet-core/scaffold/src/backend/Common/ExceptionHandlingMiddleware.cs +35 -0
  862. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthEndpoints.cs +10 -0
  863. templates/aspnet-core/scaffold/src/backend/Features/Health/HealthService.cs +9 -0
  864. templates/aspnet-core/scaffold/src/backend/Program.cs +22 -0
  865. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/Backend.Tests.csproj +22 -0
  866. templates/aspnet-core/scaffold/src/backend/tests/Backend.Tests/HealthServiceTests.cs +15 -0
  867. templates/aspnet-core/scaffold-boundary.yaml +24 -0
  868. templates/aspnet-core/skills/aspnet-core/SKILL.md +80 -0
  869. templates/aspnet-core/skills/aspnet-core/references/anatomy.md +65 -0
  870. templates/aspnet-core/stack.yaml +68 -0
  871. templates/astro/rules/frontend.md +20 -0
  872. templates/astro/scaffold/docs/engineering/astro-rules.md +40 -0
  873. templates/astro/scaffold/docs/playbooks/astro-app.md +57 -0
  874. templates/astro/scaffold/docs/playbooks/content-seo.md +37 -0
  875. templates/astro/scaffold/src/frontend/astro.config.mjs +10 -0
  876. templates/astro/scaffold/src/frontend/package.json +23 -0
  877. templates/astro/scaffold/src/frontend/src/components/Greeting.astro +13 -0
  878. templates/astro/scaffold/src/frontend/src/content/posts/hello.md +10 -0
  879. templates/astro/scaffold/src/frontend/src/content.config.ts +19 -0
  880. templates/astro/scaffold/src/frontend/src/lib/problem.test.ts +39 -0
  881. templates/astro/scaffold/src/frontend/src/lib/problem.ts +30 -0
  882. templates/astro/scaffold/src/frontend/src/pages/api/health.ts +13 -0
  883. templates/astro/scaffold/src/frontend/src/pages/index.astro +21 -0
  884. templates/astro/scaffold/src/frontend/tsconfig.json +9 -0
  885. templates/astro/scaffold/src/frontend/vitest.config.ts +10 -0
  886. templates/astro/scaffold-boundary.yaml +28 -0
  887. templates/astro/skills/astro/SKILL.md +71 -0
  888. templates/astro/skills/astro/references/anatomy.md +66 -0
  889. templates/astro/stack.yaml +75 -0
  890. templates/csharp-plain/scaffold/src/backend/Backend.csproj +12 -0
  891. templates/csharp-plain/scaffold/src/backend/Program.cs +1 -0
  892. templates/csharp-plain/scaffold-boundary.yaml +23 -0
  893. templates/csharp-plain/stack.yaml +50 -0
  894. templates/django/rules/backend.md +18 -0
  895. templates/django/scaffold/docs/engineering/anti-ambiguity.md +74 -0
  896. templates/django/scaffold/docs/engineering/backend-rules.md +133 -0
  897. templates/django/scaffold/docs/engineering/glossary.md +51 -0
  898. templates/django/scaffold/docs/engineering/logging-standards.md +107 -0
  899. templates/django/scaffold/docs/engineering/naming-conventions.md +68 -0
  900. templates/django/scaffold/docs/engineering/secrets-rotation-runbook.md +142 -0
  901. templates/django/scaffold/docs/playbooks/backend-api.md +119 -0
  902. templates/django/scaffold/src/backend/config/__init__.py +0 -0
  903. templates/django/scaffold/src/backend/config/settings.py +31 -0
  904. templates/django/scaffold/src/backend/config/urls.py +11 -0
  905. templates/django/scaffold/src/backend/config/wsgi.py +6 -0
  906. templates/django/scaffold/src/backend/manage.py +14 -0
  907. templates/django/scaffold/src/backend/pyproject.toml +37 -0
  908. templates/django/scaffold/src/backend/tests/test_health.py +4 -0
  909. templates/django/scaffold-boundary.yaml +25 -0
  910. templates/django/skills/python-django/SKILL.md +450 -0
  911. templates/django/skills/python-django/references/anatomy.md +117 -0
  912. templates/django/skills/python-django/scripts/new_endpoint.py +89 -0
  913. templates/django/stack.yaml +73 -0
  914. templates/fastapi/rules/backend.md +18 -0
  915. templates/fastapi/scaffold/docs/engineering/fastapi-rules.md +37 -0
  916. templates/fastapi/scaffold/docs/playbooks/fastapi-service.md +30 -0
  917. templates/fastapi/scaffold/src/backend/app/__init__.py +0 -0
  918. templates/fastapi/scaffold/src/backend/app/main.py +8 -0
  919. templates/fastapi/scaffold/src/backend/pyproject.toml +39 -0
  920. templates/fastapi/scaffold/src/backend/tests/test_health.py +11 -0
  921. templates/fastapi/scaffold-boundary.yaml +25 -0
  922. templates/fastapi/skills/python-fastapi/SKILL.md +75 -0
  923. templates/fastapi/skills/python-fastapi/references/anatomy.md +117 -0
  924. templates/fastapi/skills/python-fastapi/scripts/new_endpoint.py +101 -0
  925. templates/fastapi/stack.yaml +57 -0
  926. templates/flutter/rules/mobile.md +26 -0
  927. templates/flutter/scaffold/docs/engineering/flutter-rules.md +35 -0
  928. templates/flutter/scaffold/docs/playbooks/flutter-app.md +45 -0
  929. templates/flutter/scaffold/src/mobile/lib/core/error_mapper.dart +17 -0
  930. templates/flutter/scaffold/src/mobile/lib/core/router.dart +13 -0
  931. templates/flutter/scaffold/src/mobile/lib/main.dart +22 -0
  932. templates/flutter/scaffold/src/mobile/lib/screens/health_screen.dart +32 -0
  933. templates/flutter/scaffold/src/mobile/lib/services/health_service.dart +11 -0
  934. templates/flutter/scaffold/src/mobile/lib/state/health_provider.dart +13 -0
  935. templates/flutter/scaffold/src/mobile/pubspec.yaml +22 -0
  936. templates/flutter/scaffold/src/mobile/test/health_provider_test.dart +90 -0
  937. templates/flutter/scaffold-boundary.yaml +28 -0
  938. templates/flutter/skills/flutter/SKILL.md +76 -0
  939. templates/flutter/skills/flutter/references/anatomy.md +64 -0
  940. templates/flutter/stack.yaml +70 -0
  941. templates/go/rules/backend.md +19 -0
  942. templates/go/scaffold/docs/engineering/go-rules.md +45 -0
  943. templates/go/scaffold/docs/playbooks/go-service.md +30 -0
  944. templates/go/scaffold/src/backend/cmd/api/main.go +22 -0
  945. templates/go/scaffold/src/backend/cmd/api/main_test.go +20 -0
  946. templates/go/scaffold/src/backend/go.mod +3 -0
  947. templates/go/scaffold-boundary.yaml +25 -0
  948. templates/go/skills/go-patterns/SKILL.md +68 -0
  949. templates/go/skills/go-patterns/assets/go-checklist.md +29 -0
  950. templates/go/skills/go-patterns/references/anatomy.md +115 -0
  951. templates/go/skills/go-patterns/references/go-2026-idioms.md +92 -0
  952. templates/go/skills/go-patterns/scripts/new_endpoint.py +117 -0
  953. templates/go/skills/go-patterns/versions.json +16 -0
  954. templates/go/stack.yaml +54 -0
  955. templates/go-fiber/rules/backend.md +20 -0
  956. templates/go-fiber/scaffold/docs/engineering/fiber-rules.md +97 -0
  957. templates/go-fiber/scaffold/docs/playbooks/fiber-service.md +149 -0
  958. templates/go-fiber/scaffold/src/backend/cmd/api/main.go +21 -0
  959. templates/go-fiber/scaffold/src/backend/cmd/api/main_test.go +17 -0
  960. templates/go-fiber/scaffold/src/backend/go.mod +23 -0
  961. templates/go-fiber/scaffold/src/backend/go.sum +49 -0
  962. templates/go-fiber/scaffold-boundary.yaml +26 -0
  963. templates/go-fiber/skills/go-fiber/SKILL.md +203 -0
  964. templates/go-fiber/skills/go-fiber/assets/fiber-checklist.md +26 -0
  965. templates/go-fiber/skills/go-fiber/references/anatomy.md +116 -0
  966. templates/go-fiber/skills/go-fiber/references/fiber-v3-patterns.md +89 -0
  967. templates/go-fiber/skills/go-fiber/scripts/new_endpoint.py +107 -0
  968. templates/go-fiber/skills/go-fiber/versions.json +16 -0
  969. templates/go-fiber/stack.yaml +59 -0
  970. templates/go-plain/scaffold/src/backend/go.mod +3 -0
  971. templates/go-plain/scaffold/src/backend/main.go +7 -0
  972. templates/go-plain/scaffold-boundary.yaml +22 -0
  973. templates/go-plain/stack.yaml +51 -0
  974. templates/java-plain/scaffold/src/backend/mvnw +302 -0
  975. templates/java-plain/scaffold/src/backend/pom.xml +41 -0
  976. templates/java-plain/scaffold/src/backend/src/main/java/com/example/app/Main.java +10 -0
  977. templates/java-plain/scaffold-boundary.yaml +23 -0
  978. templates/java-plain/stack.yaml +51 -0
  979. templates/laravel/rules/backend.md +20 -0
  980. templates/laravel/scaffold/docs/engineering/laravel-rules.md +28 -0
  981. templates/laravel/scaffold/docs/playbooks/laravel-service.md +28 -0
  982. templates/laravel/scaffold/src/backend/app/Exceptions/Handler.php +27 -0
  983. templates/laravel/scaffold/src/backend/app/Http/Controllers/HealthController.php +15 -0
  984. templates/laravel/scaffold/src/backend/app/Support/HealthStatus.php +14 -0
  985. templates/laravel/scaffold/src/backend/composer.json +24 -0
  986. templates/laravel/scaffold/src/backend/phpunit.xml +10 -0
  987. templates/laravel/scaffold/src/backend/public/index.php +8 -0
  988. templates/laravel/scaffold/src/backend/routes/api.php +7 -0
  989. templates/laravel/scaffold/src/backend/tests/Unit/HealthStatusTest.php +16 -0
  990. templates/laravel/scaffold-boundary.yaml +23 -0
  991. templates/laravel/skills/laravel/SKILL.md +56 -0
  992. templates/laravel/skills/laravel/references/anatomy.md +65 -0
  993. templates/laravel/stack.yaml +66 -0
  994. templates/meta/rules/graph-first.md +27 -0
  995. templates/meta/rules/hook-author.md +19 -0
  996. templates/meta/rules/mcp-tool-author.md +18 -0
  997. templates/meta/rules/meta-engineering.md +17 -0
  998. templates/meta/scaffold-boundary.yaml +55 -0
  999. templates/meta/skills/claude-sdk-integration/SKILL.md +163 -0
  1000. templates/meta/skills/claude-sdk-integration/assets/sdk-checklist.md +27 -0
  1001. templates/meta/skills/claude-sdk-integration/scripts/check_model_ids.py +97 -0
  1002. templates/meta/skills/graph-os-authoring/SKILL.md +278 -0
  1003. templates/meta/skills/graph-os-authoring/assets/graph-os-checklist.md +25 -0
  1004. templates/meta/skills/graph-os-authoring/scripts/new_extractor.py +76 -0
  1005. templates/meta/skills/hook-authoring/SKILL.md +292 -0
  1006. templates/meta/skills/hook-authoring/assets/hook-checklist.md +30 -0
  1007. templates/meta/skills/hook-authoring/scripts/new_hook.sh +75 -0
  1008. templates/meta/skills/mcp-tool-authoring/SKILL.md +301 -0
  1009. templates/meta/skills/mcp-tool-authoring/assets/mcp-tool-checklist.md +29 -0
  1010. templates/meta/skills/mcp-tool-authoring/scripts/new_tool.py +74 -0
  1011. templates/meta/skills/meta-engineering/SKILL.md +151 -0
  1012. templates/meta/skills/meta-engineering/assets/meta-edit-checklist.md +28 -0
  1013. templates/meta/skills/meta-engineering/scripts/which_layer.py +61 -0
  1014. templates/meta/skills/python-meta-server/SKILL.md +162 -0
  1015. templates/meta/skills/python-meta-server/assets/meta-server-checklist.md +28 -0
  1016. templates/meta/skills/python-meta-server/scripts/check_envelope.py +91 -0
  1017. templates/meta/skills/react-vite-hub/SKILL.md +140 -0
  1018. templates/meta/skills/react-vite-hub/assets/hub-ui-checklist.md +23 -0
  1019. templates/meta/skills/react-vite-hub/scripts/check_vite_env.py +73 -0
  1020. templates/meta/stack.yaml +119 -0
  1021. templates/nestjs/rules/backend.md +20 -0
  1022. templates/nestjs/scaffold/docs/engineering/nestjs-rules.md +33 -0
  1023. templates/nestjs/scaffold/docs/playbooks/nestjs-service.md +39 -0
  1024. templates/nestjs/scaffold/src/backend/nest-cli.json +5 -0
  1025. templates/nestjs/scaffold/src/backend/package.json +28 -0
  1026. templates/nestjs/scaffold/src/backend/src/app.module.ts +9 -0
  1027. templates/nestjs/scaffold/src/backend/src/common/all-exceptions.filter.ts +59 -0
  1028. templates/nestjs/scaffold/src/backend/src/health/health.controller.ts +14 -0
  1029. templates/nestjs/scaffold/src/backend/src/health/health.module.ts +10 -0
  1030. templates/nestjs/scaffold/src/backend/src/health/health.service.spec.ts +21 -0
  1031. templates/nestjs/scaffold/src/backend/src/health/health.service.ts +9 -0
  1032. templates/nestjs/scaffold/src/backend/src/main.ts +26 -0
  1033. templates/nestjs/scaffold/src/backend/tsconfig.json +16 -0
  1034. templates/nestjs/scaffold/src/backend/vitest.config.ts +9 -0
  1035. templates/nestjs/scaffold-boundary.yaml +25 -0
  1036. templates/nestjs/skills/nestjs/SKILL.md +67 -0
  1037. templates/nestjs/skills/nestjs/references/anatomy.md +65 -0
  1038. templates/nestjs/stack.yaml +68 -0
  1039. templates/nextjs/rules/frontend.md +18 -0
  1040. templates/nextjs/scaffold/docs/design/00-index.md +23 -0
  1041. templates/nextjs/scaffold/docs/design/colors-tokens.md +141 -0
  1042. templates/nextjs/scaffold/docs/design/components-patterns.md +159 -0
  1043. templates/nextjs/scaffold/docs/design/motion-accessibility.md +137 -0
  1044. templates/nextjs/scaffold/docs/design/typography-spacing.md +107 -0
  1045. templates/nextjs/scaffold/docs/engineering/accessibility-web.md +56 -0
  1046. templates/nextjs/scaffold/docs/engineering/copywriting-standard.md +102 -0
  1047. templates/nextjs/scaffold/docs/engineering/formatting-rules.md +89 -0
  1048. templates/nextjs/scaffold/docs/engineering/frontend-rendering-rules.md +80 -0
  1049. templates/nextjs/scaffold/docs/engineering/frontend-rules.md +183 -0
  1050. templates/nextjs/scaffold/docs/engineering/i18n-policy.md +99 -0
  1051. templates/nextjs/scaffold/docs/pages-content-spec/00-index.md +78 -0
  1052. templates/nextjs/scaffold/docs/playbooks/content-seo.md +55 -0
  1053. templates/nextjs/scaffold/docs/playbooks/docs-governance.md +51 -0
  1054. templates/nextjs/scaffold/docs/playbooks/frontend-ui.md +63 -0
  1055. templates/nextjs/scaffold/src/frontend/app/layout.tsx +14 -0
  1056. templates/nextjs/scaffold/src/frontend/app/page.tsx +3 -0
  1057. templates/nextjs/scaffold/src/frontend/eslint.config.js +17 -0
  1058. templates/nextjs/scaffold/src/frontend/lib/greeting.test.ts +9 -0
  1059. templates/nextjs/scaffold/src/frontend/lib/greeting.ts +3 -0
  1060. templates/nextjs/scaffold/src/frontend/package.json +28 -0
  1061. templates/nextjs/scaffold/src/frontend/tsconfig.json +18 -0
  1062. templates/nextjs/scaffold/src/frontend/vitest.config.ts +10 -0
  1063. templates/nextjs/scaffold-boundary.yaml +30 -0
  1064. templates/nextjs/skills/nextjs-react/SKILL.md +485 -0
  1065. templates/nextjs/skills/nextjs-react/references/anatomy.md +116 -0
  1066. templates/nextjs/skills/nextjs-react/scripts/new_component.py +72 -0
  1067. templates/nextjs/stack.yaml +81 -0
  1068. templates/node-express/rules/backend.md +20 -0
  1069. templates/node-express/scaffold/docs/engineering/express-rules.md +30 -0
  1070. templates/node-express/scaffold/docs/playbooks/express-service.md +35 -0
  1071. templates/node-express/scaffold/src/backend/package.json +24 -0
  1072. templates/node-express/scaffold/src/backend/src/index.ts +17 -0
  1073. templates/node-express/scaffold/src/backend/src/middleware/error-handler.ts +12 -0
  1074. templates/node-express/scaffold/src/backend/src/routes/health.test.ts +33 -0
  1075. templates/node-express/scaffold/src/backend/src/routes/health.ts +7 -0
  1076. templates/node-express/scaffold/src/backend/tsconfig.json +15 -0
  1077. templates/node-express/scaffold/src/backend/types/express-bootstrap.d.ts +21 -0
  1078. templates/node-express/scaffold-boundary.yaml +25 -0
  1079. templates/node-express/skills/node-express/SKILL.md +70 -0
  1080. templates/node-express/skills/node-express/references/anatomy.md +63 -0
  1081. templates/node-express/stack.yaml +63 -0
  1082. templates/python/scaffold/docs/engineering/python-rules.md +27 -0
  1083. templates/python/scaffold/docs/playbooks/python-library.md +35 -0
  1084. templates/python/stack.yaml +60 -0
  1085. templates/rails/rules/backend.md +10 -0
  1086. templates/rails/scaffold/docs/engineering/rails-rules.md +33 -0
  1087. templates/rails/scaffold/docs/playbooks/rails-service.md +42 -0
  1088. templates/rails/scaffold/src/backend/Gemfile +12 -0
  1089. templates/rails/scaffold/src/backend/app/controllers/application_controller.rb +26 -0
  1090. templates/rails/scaffold/src/backend/app/controllers/health_controller.rb +6 -0
  1091. templates/rails/scaffold/src/backend/app/models/health.rb +6 -0
  1092. templates/rails/scaffold/src/backend/config/application.rb +12 -0
  1093. templates/rails/scaffold/src/backend/config/boot.rb +3 -0
  1094. templates/rails/scaffold/src/backend/config/routes.rb +4 -0
  1095. templates/rails/scaffold/src/backend/config.ru +5 -0
  1096. templates/rails/scaffold/src/backend/spec/rails_helper.rb +18 -0
  1097. templates/rails/scaffold/src/backend/spec/requests/health_spec.rb +24 -0
  1098. templates/rails/scaffold-boundary.yaml +25 -0
  1099. templates/rails/skills/rails/SKILL.md +62 -0
  1100. templates/rails/skills/rails/references/anatomy.md +71 -0
  1101. templates/rails/stack.yaml +72 -0
  1102. templates/react-native/rules/mobile.md +26 -0
  1103. templates/react-native/scaffold/docs/engineering/accessibility-mobile.md +95 -0
  1104. templates/react-native/scaffold/docs/engineering/mobile-rules.md +56 -0
  1105. templates/react-native/scaffold/docs/engineering/offline-first.md +61 -0
  1106. templates/react-native/scaffold/docs/playbooks/mobile-app.md +49 -0
  1107. templates/react-native/scaffold/src/mobile/App.tsx +9 -0
  1108. templates/react-native/scaffold/src/mobile/eslint.config.js +17 -0
  1109. templates/react-native/scaffold/src/mobile/package.json +23 -0
  1110. templates/react-native/scaffold/src/mobile/src/greeting.test.ts +9 -0
  1111. templates/react-native/scaffold/src/mobile/src/greeting.ts +3 -0
  1112. templates/react-native/scaffold/src/mobile/tsconfig.json +17 -0
  1113. templates/react-native/scaffold/src/mobile/vitest.config.ts +10 -0
  1114. templates/react-native/scaffold-boundary.yaml +30 -0
  1115. templates/react-native/skills/react-native-mobile/SKILL.md +119 -0
  1116. templates/react-native/skills/react-native-mobile/assets/rn-mobile-checklist.md +28 -0
  1117. templates/react-native/skills/react-native-mobile/references/anatomy.md +140 -0
  1118. templates/react-native/skills/react-native-mobile/references/rn-2026-practices.md +54 -0
  1119. templates/react-native/skills/react-native-mobile/scripts/new_screen.py +73 -0
  1120. templates/react-native/skills/react-native-mobile/versions.json +16 -0
  1121. templates/react-native/skills/react-native-patterns/SKILL.md +512 -0
  1122. templates/react-native/skills/react-native-patterns/assets/rn-review-checklist.md +26 -0
  1123. templates/react-native/skills/react-native-patterns/references/anatomy.md +62 -0
  1124. templates/react-native/skills/react-native-patterns/references/list-performance.md +70 -0
  1125. templates/react-native/skills/react-native-patterns/scripts/scan_rn_perf.py +77 -0
  1126. templates/react-native/stack.yaml +70 -0
  1127. templates/ruby-plain/scaffold/src/backend/Gemfile +8 -0
  1128. templates/ruby-plain/scaffold/src/backend/main.rb +3 -0
  1129. templates/ruby-plain/scaffold-boundary.yaml +23 -0
  1130. templates/ruby-plain/stack.yaml +50 -0
  1131. templates/rust-axum/rules/backend.md +20 -0
  1132. templates/rust-axum/scaffold/docs/engineering/rust-axum-rules.md +35 -0
  1133. templates/rust-axum/scaffold/docs/playbooks/rust-axum-service.md +43 -0
  1134. templates/rust-axum/scaffold/src/backend/Cargo.toml +20 -0
  1135. templates/rust-axum/scaffold/src/backend/src/app.rs +12 -0
  1136. templates/rust-axum/scaffold/src/backend/src/error.rs +48 -0
  1137. templates/rust-axum/scaffold/src/backend/src/main.rs +24 -0
  1138. templates/rust-axum/scaffold/src/backend/src/routes/health.rs +37 -0
  1139. templates/rust-axum/scaffold/src/backend/src/routes/mod.rs +2 -0
  1140. templates/rust-axum/scaffold-boundary.yaml +25 -0
  1141. templates/rust-axum/skills/rust/SKILL.md +73 -0
  1142. templates/rust-axum/skills/rust/references/anatomy.md +63 -0
  1143. templates/rust-axum/stack.yaml +65 -0
  1144. templates/rust-plain/scaffold/src/backend/Cargo.toml +6 -0
  1145. templates/rust-plain/scaffold/src/backend/src/main.rs +3 -0
  1146. templates/rust-plain/scaffold-boundary.yaml +23 -0
  1147. templates/rust-plain/stack.yaml +51 -0
  1148. templates/spring-boot/rules/backend.md +20 -0
  1149. templates/spring-boot/scaffold/docs/engineering/spring-boot-rules.md +35 -0
  1150. templates/spring-boot/scaffold/docs/playbooks/spring-boot-service.md +45 -0
  1151. templates/spring-boot/scaffold/src/backend/mvnw +302 -0
  1152. templates/spring-boot/scaffold/src/backend/pom.xml +69 -0
  1153. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/Application.java +16 -0
  1154. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/common/GlobalExceptionHandler.java +31 -0
  1155. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthController.java +22 -0
  1156. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthService.java +12 -0
  1157. templates/spring-boot/scaffold/src/backend/src/main/java/com/example/app/health/HealthStatus.java +4 -0
  1158. templates/spring-boot/scaffold/src/backend/src/test/java/com/example/app/health/HealthServiceTest.java +15 -0
  1159. templates/spring-boot/scaffold-boundary.yaml +26 -0
  1160. templates/spring-boot/skills/spring-boot/SKILL.md +84 -0
  1161. templates/spring-boot/skills/spring-boot/references/anatomy.md +63 -0
  1162. templates/spring-boot/stack.yaml +65 -0
  1163. templates/svelte-sveltekit/rules/frontend.md +20 -0
  1164. templates/svelte-sveltekit/scaffold/docs/engineering/svelte-sveltekit-rules.md +37 -0
  1165. templates/svelte-sveltekit/scaffold/docs/playbooks/svelte-sveltekit-app.md +36 -0
  1166. templates/svelte-sveltekit/scaffold/src/frontend/package.json +22 -0
  1167. templates/svelte-sveltekit/scaffold/src/frontend/src/app.html +12 -0
  1168. templates/svelte-sveltekit/scaffold/src/frontend/src/hooks.server.ts +14 -0
  1169. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/components/Greeting.svelte +6 -0
  1170. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.test.ts +26 -0
  1171. templates/svelte-sveltekit/scaffold/src/frontend/src/lib/stores/count.ts +4 -0
  1172. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+layout.svelte +24 -0
  1173. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.svelte +9 -0
  1174. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/+page.ts +7 -0
  1175. templates/svelte-sveltekit/scaffold/src/frontend/src/routes/health/+server.ts +7 -0
  1176. templates/svelte-sveltekit/scaffold/src/frontend/svelte.config.js +10 -0
  1177. templates/svelte-sveltekit/scaffold/src/frontend/tsconfig.json +7 -0
  1178. templates/svelte-sveltekit/scaffold/src/frontend/vite.config.ts +7 -0
  1179. templates/svelte-sveltekit/scaffold/src/frontend/vitest.config.ts +11 -0
  1180. templates/svelte-sveltekit/scaffold-boundary.yaml +29 -0
  1181. templates/svelte-sveltekit/skills/svelte/SKILL.md +90 -0
  1182. templates/svelte-sveltekit/skills/svelte/references/anatomy.md +62 -0
  1183. templates/svelte-sveltekit/stack.yaml +70 -0
  1184. templates/typescript-plain/scaffold/src/index.ts +3 -0
  1185. templates/typescript-plain/scaffold/tsconfig.json +13 -0
  1186. templates/typescript-plain/scaffold-boundary.yaml +22 -0
  1187. templates/typescript-plain/stack.yaml +44 -0
  1188. templates/vue-nuxt/rules/frontend.md +19 -0
  1189. templates/vue-nuxt/scaffold/docs/engineering/nuxt-rules.md +30 -0
  1190. templates/vue-nuxt/scaffold/docs/playbooks/nuxt-app.md +29 -0
  1191. templates/vue-nuxt/scaffold/src/frontend/app.vue +3 -0
  1192. templates/vue-nuxt/scaffold/src/frontend/nuxt.config.ts +11 -0
  1193. templates/vue-nuxt/scaffold/src/frontend/package.json +20 -0
  1194. templates/vue-nuxt/scaffold/src/frontend/pages/index.test.ts +19 -0
  1195. templates/vue-nuxt/scaffold/src/frontend/pages/index.vue +11 -0
  1196. templates/vue-nuxt/scaffold/src/frontend/vitest.config.ts +12 -0
  1197. templates/vue-nuxt/scaffold-boundary.yaml +26 -0
  1198. templates/vue-nuxt/skills/vue-nuxt/SKILL.md +57 -0
  1199. templates/vue-nuxt/skills/vue-nuxt/references/anatomy.md +60 -0
  1200. templates/vue-nuxt/stack.yaml +61 -0
  1201. templates/wordpress/rules/backend.md +19 -0
  1202. templates/wordpress/scaffold/docs/engineering/wordpress-rules.md +28 -0
  1203. templates/wordpress/scaffold/docs/playbooks/wordpress-service.md +29 -0
  1204. templates/wordpress/scaffold/src/backend/composer.json +17 -0
  1205. templates/wordpress/scaffold/src/backend/phpcs.xml.dist +11 -0
  1206. templates/wordpress/scaffold/src/backend/phpunit.xml +10 -0
  1207. templates/wordpress/scaffold/src/backend/plugin/inc/health.php +8 -0
  1208. templates/wordpress/scaffold/src/backend/plugin/plugin.php +28 -0
  1209. templates/wordpress/scaffold/src/backend/tests/HealthStatusTest.php +15 -0
  1210. templates/wordpress/scaffold/src/backend/theme/functions.php +18 -0
  1211. templates/wordpress/scaffold/src/backend/theme/style.css +11 -0
  1212. templates/wordpress/scaffold-boundary.yaml +23 -0
  1213. templates/wordpress/skills/wordpress/SKILL.md +110 -0
  1214. templates/wordpress/skills/wordpress/assets/wp-checklist.md +28 -0
  1215. templates/wordpress/skills/wordpress/references/wp-development.md +75 -0
  1216. templates/wordpress/skills/wordpress/references/wp-security.md +68 -0
  1217. templates/wordpress/skills/wordpress/scripts/scan_wp_smells.py +91 -0
  1218. templates/wordpress/skills/wordpress/versions.json +16 -0
  1219. templates/wordpress/stack.yaml +60 -0
  1220. thinking_os/__init__.py +1 -0
  1221. thinking_os/_agent_markers.py +32 -0
  1222. thinking_os/background.py +405 -0
  1223. thinking_os/bootstrap_outcomes.py +200 -0
  1224. thinking_os/budget.py +302 -0
  1225. thinking_os/capture.py +495 -0
  1226. thinking_os/cognition.py +516 -0
  1227. thinking_os/cognition_schemas.py +517 -0
  1228. thinking_os/compress.py +192 -0
  1229. thinking_os/concepts.py +233 -0
  1230. thinking_os/dashboard.py +159 -0
  1231. thinking_os/database.py +2883 -0
  1232. thinking_os/decay.py +393 -0
  1233. thinking_os/digest.py +295 -0
  1234. thinking_os/dispatcher.py +192 -0
  1235. thinking_os/dispatcher_helpers.py +48 -0
  1236. thinking_os/dispatchers/__init__.py +3 -0
  1237. thinking_os/dispatchers/default.py +47 -0
  1238. thinking_os/distill.py +192 -0
  1239. thinking_os/doc_indexer.py +905 -0
  1240. thinking_os/embeddings.py +943 -0
  1241. thinking_os/formula_composer.py +556 -0
  1242. thinking_os/gate_marker.py +75 -0
  1243. thinking_os/graph.py +296 -0
  1244. thinking_os/graph_indexer.py +360 -0
  1245. thinking_os/health_check.py +517 -0
  1246. thinking_os/impact.py +119 -0
  1247. thinking_os/memory_gc.py +356 -0
  1248. thinking_os/migrator_embeddings.py +318 -0
  1249. thinking_os/precision.py +194 -0
  1250. thinking_os/record_outcome.py +399 -0
  1251. thinking_os/repair.py +105 -0
  1252. thinking_os/retrieval_quality.py +239 -0
  1253. thinking_os/roles_state.py +168 -0
  1254. thinking_os/sanitizer.py +320 -0
  1255. thinking_os/server.py +3160 -0
  1256. thinking_os/session_enrich.py +272 -0
  1257. thinking_os/session_observe_worker.py +111 -0
  1258. thinking_os/session_startup.py +74 -0
  1259. thinking_os/session_summary.py +245 -0
  1260. thinking_os/task_analyzer.py +462 -0
  1261. thinking_os/task_parser.py +342 -0
  1262. thinking_os/task_sync.py +73 -0
  1263. thinking_os/tools/__init__.py +6 -0
  1264. thinking_os/tools/_shared.py +947 -0
  1265. thinking_os/tools/cognition.py +1867 -0
  1266. thinking_os/tools/docs.py +770 -0
  1267. thinking_os/tools/learning.py +2078 -0
  1268. thinking_os/tools/logs.py +79 -0
  1269. thinking_os/tools/memory.py +840 -0
  1270. thinking_os/tools/metrics.py +200 -0
  1271. thinking_os/tools/retrieve.py +415 -0
  1272. thinking_os/tools/routing.py +658 -0
  1273. thinking_os/tools/tasks.py +449 -0
  1274. thinking_os/tools/trajectory.py +181 -0
  1275. thinking_os/tracing.py +235 -0
  1276. web/__init__.py +5 -0
  1277. web/_cache.py +118 -0
  1278. web/_deps.py +56 -0
  1279. web/_envelope.py +85 -0
  1280. web/_project_context.py +140 -0
  1281. web/chat_providers.py +108 -0
  1282. web/init_jobs.py +216 -0
  1283. web/routes/__init__.py +25 -0
  1284. web/routes/_bounded_read.py +76 -0
  1285. web/routes/board.py +1089 -0
  1286. web/routes/cognition.py +1838 -0
  1287. web/routes/config.py +635 -0
  1288. web/routes/graph.py +513 -0
  1289. web/routes/health.py +180 -0
  1290. web/routes/hooks.py +288 -0
  1291. web/routes/hub.py +1219 -0
  1292. web/routes/logs.py +374 -0
  1293. web/routes/metrics.py +43 -0
  1294. web/routes/observability.py +400 -0
  1295. web/routes/patterns.py +227 -0
  1296. web/routes/presence.py +609 -0
  1297. web/routes/roles.py +446 -0
  1298. web/routes/scheduled.py +261 -0
  1299. web/routes/search.py +238 -0
  1300. web/routes/sessions.py +220 -0
  1301. web/routes/settings.py +363 -0
  1302. web/routes/stream.py +547 -0
  1303. web/security.py +157 -0
  1304. web/server.py +283 -0
@@ -0,0 +1,1053 @@
1
+ """graph_os — SQLite fallback backend.
2
+
3
+ DEPENDS: sqlite3 stdlib; core/thinking_os/database.py for init_db when
4
+ path-based; core/graph_os/types.py for the value types.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import logging
11
+ import os
12
+ import sqlite3
13
+ import threading
14
+ import time
15
+ from collections.abc import Iterable, Sequence
16
+ from pathlib import Path
17
+ from typing import Any
18
+
19
+ from ..types import EvidenceSignal, GraphEdge, GraphNode, normalize_kind
20
+
21
+ logger = logging.getLogger("graph_os.backends.sqlite")
22
+
23
+
24
+ def _import_db_module() -> Any:
25
+ """Return the thinking_os database module.
26
+
27
+ Prefers the installed package path (`thinking_os.database`); falls back
28
+ to the bare ``database`` name for environments where
29
+ core/thinking_os/ is on sys.path directly (e.g. direct script
30
+ invocation without editable install).
31
+ """
32
+ try:
33
+ from thinking_os import database as _db # type: ignore
34
+
35
+ return _db
36
+ except ImportError:
37
+ import database as _db # type: ignore
38
+
39
+ return _db
40
+
41
+
42
+ class SqliteBackend:
43
+ """SQLite-backed graph store (thinking_os DB, migration v12).
44
+
45
+ DEPENDS: migration v12 (graph_nodes, graph_edges_v12,
46
+ graph_evidence_v12 tables + optional FTS5 virtual
47
+ table).
48
+ """
49
+
50
+ backend_id: str = "sqlite"
51
+
52
+ def __init__(
53
+ self,
54
+ *,
55
+ conn: sqlite3.Connection | None = None,
56
+ db_path: str | None = None,
57
+ ) -> None:
58
+ self._owns_conn = False
59
+ # B1: serialise writes across threads so sqlite3.Connection (which
60
+ # is only cursor-safe, not write-safe under concurrent INSERT) does
61
+ # not raise ProgrammingError. Reads fall under the same lock to
62
+ # cover BEGIN/COMMIT boundaries in ``upsert_edge``.
63
+ self._write_lock = threading.RLock()
64
+ # P6: per-thread read connections. WAL allows multiple concurrent
65
+ # readers; a single shared sqlite3.Connection serialises them
66
+ # behind the GIL + connection mutex. Opening one connection per
67
+ # thread (lazy) unblocks true parallel reads in Hub UI + parallel
68
+ # MCP dispatch. Track all opened conns so close() can drain them.
69
+ self._db_path: str | None = None
70
+ self._read_conn_pool = threading.local()
71
+ self._all_read_conns: list[sqlite3.Connection] = []
72
+ self._read_conn_lock = threading.Lock()
73
+ if conn is not None:
74
+ self._conn = conn
75
+ else:
76
+ try:
77
+ from thinking_os.database import resolve_db_path # type: ignore
78
+
79
+ resolved = db_path or str(resolve_db_path())
80
+ except ImportError:
81
+ # Standalone — fall back to env var + repo-relative default.
82
+ resolved = db_path or os.environ.get("COS_DB_PATH", ".coding-os/coding-os.db")
83
+ Path(resolved).parent.mkdir(parents=True, exist_ok=True)
84
+ db = _import_db_module()
85
+ # B1: own-connection path opens with check_same_thread=False so
86
+ # multiple MCP threads can share this backend instance. WAL +
87
+ # busy_timeout give us concurrent readers + a writer without
88
+ # SQLITE_BUSY spam.
89
+ self._db_path = resolved
90
+ self._conn = sqlite3.connect(resolved, timeout=10, check_same_thread=False)
91
+ self._conn.row_factory = sqlite3.Row
92
+ self._conn.execute("PRAGMA journal_mode = WAL")
93
+ self._conn.execute("PRAGMA synchronous = NORMAL")
94
+ self._conn.execute("PRAGMA foreign_keys = ON")
95
+ # 30s ceiling tolerates ten concurrent dispatcher workers
96
+ # reindexing different files at the same time without the
97
+ # busy_timeout firing.
98
+ self._conn.execute("PRAGMA busy_timeout = 30000")
99
+ # G16: apply the full pragma SSOT (cache_size, mmap_size,
100
+ # temp_store, wal_autocheckpoint) so standalone-conn bench
101
+ # harnesses (bench/scale_500k.py, bench/viewer_fps.py) +
102
+ # reindex_dispatch.py see the same p99 SLA the pooled path
103
+ # does. Fail-open if helper unavailable (older DB module).
104
+ _apply_pragmas = getattr(db, "_apply_pragmas", None)
105
+ if callable(_apply_pragmas):
106
+ try:
107
+ _apply_pragmas(self._conn)
108
+ except Exception as exc: # pragma: no cover — diagnostic
109
+ logger.debug("standalone PRAGMA application skipped: %s", exc)
110
+ db.run_migrations(self._conn)
111
+ self._owns_conn = True
112
+ self._conn.execute("PRAGMA foreign_keys = ON")
113
+ self._verify_schema()
114
+
115
+ def _verify_schema(self) -> None:
116
+ db = _import_db_module()
117
+ missing: list[str] = []
118
+ if not db.has_graph_nodes_table(self._conn):
119
+ missing.append("graph_nodes")
120
+ if not db.has_graph_edges_table(self._conn):
121
+ missing.append("graph_edges_v12")
122
+ if not db.has_graph_evidence_table(self._conn):
123
+ missing.append("graph_evidence_v12")
124
+ if missing:
125
+ raise RuntimeError(
126
+ "SqliteBackend: required tables missing "
127
+ f"({', '.join(missing)}). Run migrations (init_db) first."
128
+ )
129
+
130
+ # -- Lifecycle ---------------------------------------------------------
131
+
132
+ def close(self) -> None:
133
+ if self._owns_conn:
134
+ with self._write_lock:
135
+ try:
136
+ self._conn.close()
137
+ except sqlite3.Error as exc:
138
+ logger.debug("sqlite close suppressed: %s", exc)
139
+ # P6: drain per-thread read connections too.
140
+ with self._read_conn_lock:
141
+ for conn in self._all_read_conns:
142
+ try:
143
+ conn.close()
144
+ except sqlite3.Error as exc:
145
+ logger.debug("sqlite read-conn close suppressed: %s", exc)
146
+ self._all_read_conns.clear()
147
+
148
+ def _get_read_conn(self) -> sqlite3.Connection:
149
+ """P6: return this thread's read-only sqlite3.Connection.
150
+
151
+ Lazy-open per thread under WAL so concurrent get_node /
152
+ count_edges / list_edges calls don't serialise behind the
153
+ primary connection's GIL+mutex. Falls back to the shared
154
+ connection when ``_db_path`` is unset (caller-provided conn
155
+ via constructor) — tests + caller-pooled scenarios keep
156
+ working unchanged.
157
+ """
158
+ if self._db_path is None:
159
+ return self._conn
160
+ conn = getattr(self._read_conn_pool, "conn", None)
161
+ if conn is not None:
162
+ return conn
163
+ conn = sqlite3.connect(self._db_path, timeout=10, check_same_thread=False)
164
+ conn.row_factory = sqlite3.Row
165
+ # Read connections need WAL so they see committed writes
166
+ # immediately. Foreign keys + query_only enforce read-only
167
+ # semantics (defence-in-depth). Cache stays modest (~2MB
168
+ # default) so 16 threads don't multiply the 64MB primary cache.
169
+ try:
170
+ conn.execute("PRAGMA journal_mode = WAL")
171
+ conn.execute("PRAGMA query_only = ON")
172
+ conn.execute("PRAGMA foreign_keys = ON")
173
+ conn.execute("PRAGMA busy_timeout = 30000")
174
+ except sqlite3.Error as exc:
175
+ logger.debug("read-conn pragma skipped: %s", exc)
176
+ self._read_conn_pool.conn = conn
177
+ with self._read_conn_lock:
178
+ self._all_read_conns.append(conn)
179
+ return conn
180
+
181
+ # -- Write path --------------------------------------------------------
182
+
183
+ def upsert_node(self, node: GraphNode) -> int:
184
+ """Insert a node or update it in place; return the primary key.
185
+
186
+ DEPENDS: migration v12 schema.
187
+ """
188
+ now = int(time.time())
189
+ metadata_json = json.dumps(dict(node.metadata), sort_keys=True)
190
+ # Canonicalise kind at the storage boundary (S3 NodeKind). Falls
191
+ # back to the raw string if normalize_kind doesn't recognise the
192
+ # form so a stray label can't kill an entire reindex run.
193
+ try:
194
+ kind_value = normalize_kind(node.kind).value
195
+ except ValueError:
196
+ kind_value = node.kind
197
+ with self._write_lock:
198
+ row = self._conn.execute(
199
+ "SELECT id, doc_blob, signature, metadata_json, file_path, lang, "
200
+ "content_hash, start_line, end_line FROM graph_nodes WHERE uid = ?",
201
+ (node.uid,),
202
+ ).fetchone()
203
+ if row is None:
204
+ cursor = self._conn.execute(
205
+ """
206
+ INSERT INTO graph_nodes
207
+ (kind, label, uid, file_path, start_line, end_line,
208
+ signature, lang, doc_blob, ast_hash, content_hash,
209
+ metadata_json, created_at, updated_at)
210
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
211
+ """,
212
+ (
213
+ kind_value,
214
+ node.label,
215
+ node.uid,
216
+ node.file_path,
217
+ node.start_line,
218
+ node.end_line,
219
+ node.signature,
220
+ node.lang,
221
+ node.doc_blob,
222
+ node.ast_hash,
223
+ node.content_hash,
224
+ metadata_json,
225
+ now,
226
+ now,
227
+ ),
228
+ )
229
+ self._conn.commit()
230
+ return int(cursor.lastrowid)
231
+
232
+ node_id = int(row[0])
233
+ existing_doc_blob = row[1]
234
+ existing_signature = row[2]
235
+ existing_meta_json = row[3]
236
+ existing_file_path = row[4]
237
+ existing_lang = row[5]
238
+ existing_content_hash = row[6]
239
+ existing_start_line = row[7]
240
+ existing_end_line = row[8]
241
+ incoming_is_stub = bool(node.metadata.get("stub")) if node.metadata else False
242
+ existing_meta = json.loads(existing_meta_json or "{}")
243
+ existing_is_stub = bool(existing_meta.get("stub"))
244
+
245
+ doc_blob_to_write = node.doc_blob
246
+ sig_to_write = node.signature
247
+ meta_to_write = metadata_json
248
+ if incoming_is_stub and not existing_is_stub:
249
+ doc_blob_to_write = existing_doc_blob
250
+ sig_to_write = existing_signature
251
+ meta_to_write = existing_meta_json or metadata_json
252
+ else:
253
+ if existing_doc_blob and not node.doc_blob:
254
+ doc_blob_to_write = existing_doc_blob
255
+ if existing_signature and not node.signature:
256
+ sig_to_write = existing_signature
257
+
258
+ # Preserve existing non-null path/lang/hash; a path-less stub upsert must not clobber them.
259
+ file_path_to_write = (
260
+ node.file_path if node.file_path is not None else existing_file_path
261
+ )
262
+ lang_to_write = node.lang if node.lang is not None else existing_lang
263
+ content_hash_to_write = (
264
+ node.content_hash if node.content_hash is not None else existing_content_hash
265
+ )
266
+ start_line_to_write = (
267
+ node.start_line if node.start_line is not None else existing_start_line
268
+ )
269
+ end_line_to_write = node.end_line if node.end_line is not None else existing_end_line
270
+
271
+ self._conn.execute(
272
+ """
273
+ UPDATE graph_nodes SET
274
+ kind=?, label=?, file_path=?, start_line=?, end_line=?,
275
+ signature=?, lang=?, doc_blob=?, ast_hash=?, content_hash=?,
276
+ metadata_json=?, updated_at=?
277
+ WHERE id=?
278
+ """,
279
+ (
280
+ kind_value,
281
+ node.label,
282
+ file_path_to_write,
283
+ start_line_to_write,
284
+ end_line_to_write,
285
+ sig_to_write,
286
+ lang_to_write,
287
+ doc_blob_to_write,
288
+ node.ast_hash,
289
+ content_hash_to_write,
290
+ meta_to_write,
291
+ now,
292
+ node_id,
293
+ ),
294
+ )
295
+ self._conn.commit()
296
+ return node_id
297
+
298
+ def upsert_edge(self, edge: GraphEdge) -> int:
299
+ """Insert or update an edge; replace evidence atomically.
300
+
301
+ DEPENDS: upsert_node must have been called for both endpoints.
302
+
303
+ F12 / Audit #3: extractors occasionally emit self-loops
304
+ (source_uid == target_uid) when an AST visitor mis-resolves
305
+ recursion or nested attribute access. They poison call-graph
306
+ analytics. Drop them at the backend write boundary so a single
307
+ fix covers every extractor.
308
+ """
309
+ if edge.source_uid == edge.target_uid:
310
+ logger.debug(
311
+ "self-loop dropped at upsert_edge: uid=%s type=%s", edge.source_uid, edge.edge_type
312
+ )
313
+ return -1
314
+ now = int(time.time())
315
+ with self._write_lock:
316
+ source_id = self._node_id_for_uid(edge.source_uid)
317
+ target_id = self._node_id_for_uid(edge.target_uid)
318
+ cursor = self._conn.cursor()
319
+ try:
320
+ cursor.execute("BEGIN")
321
+ if edge.edge_type == "contains":
322
+ # Structural folder-spine edge — every extractor that
323
+ # touches a file re-emits the folder→file spine. Dedup on
324
+ # (source,target,type) IGNORING extractor; otherwise a file
325
+ # processed by N extractors yields N identical rows that
326
+ # inflate degree centrality (COUNT(e.id), not DISTINCT). W6.10.
327
+ row = cursor.execute(
328
+ """
329
+ SELECT id FROM graph_edges_v12
330
+ WHERE source_id=? AND target_id=? AND edge_type=?
331
+ """,
332
+ (source_id, target_id, edge.edge_type),
333
+ ).fetchone()
334
+ else:
335
+ row = cursor.execute(
336
+ """
337
+ SELECT id FROM graph_edges_v12
338
+ WHERE source_id=? AND target_id=? AND edge_type=? AND extractor=?
339
+ """,
340
+ (source_id, target_id, edge.edge_type, edge.extractor),
341
+ ).fetchone()
342
+ if row is None:
343
+ cursor.execute(
344
+ """
345
+ INSERT INTO graph_edges_v12
346
+ (source_id, target_id, edge_type, confidence,
347
+ extractor, source_span, created_at, updated_at)
348
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
349
+ """,
350
+ (
351
+ source_id,
352
+ target_id,
353
+ edge.edge_type,
354
+ float(edge.confidence),
355
+ edge.extractor,
356
+ edge.source_span,
357
+ now,
358
+ now,
359
+ ),
360
+ )
361
+ edge_id = int(cursor.lastrowid)
362
+ else:
363
+ edge_id = int(row[0])
364
+ cursor.execute(
365
+ """
366
+ UPDATE graph_edges_v12 SET
367
+ confidence=?, source_span=?, updated_at=?
368
+ WHERE id=?
369
+ """,
370
+ (
371
+ float(edge.confidence),
372
+ edge.source_span,
373
+ now,
374
+ edge_id,
375
+ ),
376
+ )
377
+ cursor.execute("DELETE FROM graph_evidence_v12 WHERE edge_id=?", (edge_id,))
378
+
379
+ for signal in edge.evidence:
380
+ cursor.execute(
381
+ """
382
+ INSERT INTO graph_evidence_v12
383
+ (edge_id, signal_name, weight, note, created_at)
384
+ VALUES (?, ?, ?, ?, ?)
385
+ """,
386
+ (
387
+ edge_id,
388
+ signal.signal_name,
389
+ float(signal.weight),
390
+ signal.note,
391
+ now,
392
+ ),
393
+ )
394
+ self._conn.commit()
395
+ return edge_id
396
+ except Exception:
397
+ self._conn.rollback()
398
+ raise
399
+
400
+ def bulk_upsert(
401
+ self, nodes: Iterable[GraphNode], edges: Iterable[GraphEdge]
402
+ ) -> tuple[int, int]:
403
+ """Insert many nodes then many edges; return counts written.
404
+
405
+ Two-pass so edges never reference an unknown uid.
406
+ """
407
+ node_count = 0
408
+ for node in nodes:
409
+ self.upsert_node(node)
410
+ node_count += 1
411
+ edge_count = 0
412
+ for edge in edges:
413
+ if self.upsert_edge(edge) >= 0:
414
+ edge_count += 1
415
+ return node_count, edge_count
416
+
417
+ def delete_node(self, uid: str) -> bool:
418
+ """Remove a node; FK CASCADE removes edges + evidence."""
419
+ with self._write_lock:
420
+ cursor = self._conn.execute("DELETE FROM graph_nodes WHERE uid=?", (uid,))
421
+ self._conn.commit()
422
+ return cursor.rowcount > 0
423
+
424
+ def delete_nodes_for_file(
425
+ self, file_path: str, *, extractors: Sequence[str] | None = None
426
+ ) -> int:
427
+ """Prune nodes belonging to a single source file before reindex.
428
+
429
+ HARD delete by design — the graph mirrors HEAD-of-tree, so a symbol
430
+ that left the file is gone, not historical (git is the record). This
431
+ runs on every per-file reindex (prune-before-reindex), so the graph
432
+ keeps no deletion ledger of its own — that would be pure churn.
433
+ Node prune here is routine and logged at debug. See graph-os-authoring.
434
+ """
435
+ with self._write_lock:
436
+ if not extractors:
437
+ cursor = self._conn.execute(
438
+ "DELETE FROM graph_nodes WHERE file_path=?", (file_path,)
439
+ )
440
+ else:
441
+ placeholders = " OR ".join(["metadata_json LIKE ?"] * len(extractors))
442
+ params: list[Any] = [file_path]
443
+ for ex in extractors:
444
+ params.append(f'%"extractor": "{ex}"%')
445
+ cursor = self._conn.execute(
446
+ f"DELETE FROM graph_nodes WHERE file_path=? AND ({placeholders})",
447
+ params,
448
+ )
449
+ self._conn.commit()
450
+ deleted = int(cursor.rowcount or 0)
451
+ if deleted:
452
+ logger.debug("hard-deleted %d node(s) for %s", deleted, file_path)
453
+ return deleted
454
+
455
+ def link_external_stubs(self, *, file_path: str | None = None) -> int:
456
+ with self._write_lock:
457
+ if file_path:
458
+ stub_rows = self._conn.execute(
459
+ """
460
+ SELECT DISTINCT stub.id, stub.uid
461
+ FROM graph_edges_v12 e
462
+ JOIN graph_nodes stub ON stub.id = e.target_id
463
+ JOIN graph_nodes src ON src.id = e.source_id
464
+ WHERE src.file_path = ?
465
+ AND stub.uid LIKE 'code:external:%'
466
+ AND stub.uid NOT LIKE 'code:external:unresolved:%'
467
+ """,
468
+ (file_path,),
469
+ ).fetchall()
470
+ else:
471
+ stub_rows = self._conn.execute(
472
+ """
473
+ SELECT id, uid FROM graph_nodes
474
+ WHERE uid LIKE 'code:external:%'
475
+ AND uid NOT LIKE 'code:external:unresolved:%'
476
+ """
477
+ ).fetchall()
478
+
479
+ stubs_by_label: dict[str, list[tuple[int, str, str]]] = {}
480
+ for stub_id, stub_uid in stub_rows:
481
+ rest = stub_uid[len("code:external:") :]
482
+ module, _, name = rest.rpartition(":")
483
+ if not module or not name:
484
+ continue
485
+ stubs_by_label.setdefault(name, []).append((int(stub_id), module, stub_uid))
486
+
487
+ if not stubs_by_label:
488
+ return 0
489
+
490
+ labels = list(stubs_by_label.keys())
491
+ placeholders = ",".join(["?"] * len(labels))
492
+ real_rows = self._conn.execute(
493
+ f"""
494
+ SELECT id, label, file_path, kind FROM graph_nodes
495
+ WHERE kind IN ('function','method','class','variable','interface')
496
+ AND label IN ({placeholders})
497
+ AND file_path IS NOT NULL
498
+ """,
499
+ tuple(labels),
500
+ ).fetchall()
501
+ real_by_label: dict[str, list[tuple[int, str, str]]] = {}
502
+ for real_id, real_label, real_file, real_kind in real_rows:
503
+ real_by_label.setdefault(real_label, []).append(
504
+ (int(real_id), real_file, str(real_kind))
505
+ )
506
+
507
+ rewrites = 0
508
+ for label, candidate_stubs in stubs_by_label.items():
509
+ real_candidates = real_by_label.get(label, [])
510
+ if not real_candidates:
511
+ continue
512
+ for stub_id, module, _stub_uid in candidate_stubs:
513
+ module_suffix = module.replace(".", "/")
514
+ # collect ALL real files whose path matches the
515
+ # stub's module, then resolve ONLY when exactly one does.
516
+ # First-match-break used to pick an arbitrary candidate
517
+ # when several `label`s exist in different modules (e.g. 3
518
+ # `fail` functions) — a false-edge risk amplified once the
519
+ # global cross-file pass runs. Ambiguous (>1) ⇒ skip, never
520
+ # guess.
521
+ matches = [
522
+ (real_id, real_kind)
523
+ for real_id, real_file, real_kind in real_candidates
524
+ if (
525
+ real_file == f"{module_suffix}.py"
526
+ or real_file.endswith(f"/{module_suffix}.py")
527
+ or real_file == f"{module_suffix}/__init__.py"
528
+ or real_file.endswith(f"/{module_suffix}/__init__.py")
529
+ )
530
+ ]
531
+ if len(matches) != 1:
532
+ continue
533
+ matched_real_id, matched_real_kind = matches[0]
534
+ # N2: when stub resolves to a real CLASS node, promote
535
+ # any inbound `calls` edges (constructor-shaped) to
536
+ # `constructs`. The original extract-time gate
537
+ # (is_constructor_like + target.startswith('code:class:'))
538
+ # missed these because the stub uid was `code:external:*`
539
+ # at the time edges were emitted.
540
+ # rewrite stub→real edges with OR IGNORE. A bare
541
+ # UPDATE aborts the WHOLE linker pass with an IntegrityError
542
+ # when the rewrite would duplicate an existing edge — e.g. a
543
+ # caller reaches the same real symbol via two module
544
+ # spellings (`tools._shared:fail` + `pkg.tools._shared:fail`)
545
+ # so the second rewrite collides on UNIQUE(source,target,
546
+ # edge_type,extractor). OR IGNORE skips the (duplicate)
547
+ # colliding rows instead of aborting; the real edge from the
548
+ # first rewrite already exists, so the un-rewritten leftover
549
+ # row is a redundant duplicate pointing at the stub. We do
550
+ # NOT delete it here (a blanket DELETE on target_id risked
551
+ # removing rows OR IGNORE skipped for non-duplicate reasons);
552
+ # the stub simply retains it and surfaces as an info-level
553
+ # `orphaned_external_unresolved` in doctor. Net: every
554
+ # distinct caller reaches the real node and the pass never
555
+ # aborts mid-loop.
556
+ if matched_real_kind == "class":
557
+ self._conn.execute(
558
+ "UPDATE OR IGNORE graph_edges_v12 SET target_id = ?, "
559
+ "edge_type = CASE WHEN edge_type='calls' "
560
+ "THEN 'constructs' ELSE edge_type END "
561
+ "WHERE target_id = ?",
562
+ (matched_real_id, stub_id),
563
+ )
564
+ else:
565
+ self._conn.execute(
566
+ "UPDATE OR IGNORE graph_edges_v12 "
567
+ "SET target_id = ? WHERE target_id = ?",
568
+ (matched_real_id, stub_id),
569
+ )
570
+ rewrites += 1
571
+ self._conn.commit()
572
+ return rewrites
573
+
574
+ def edges_among(
575
+ self,
576
+ uids: Sequence[str],
577
+ *,
578
+ edge_types: Sequence[str] | None = None,
579
+ exclude_edge_types: Sequence[str] = ("contains",),
580
+ limit: int = 100_000,
581
+ ) -> list[GraphEdge]:
582
+ """Edges whose BOTH endpoints are inside ``uids`` — the semantic
583
+ overlay of a subtree-scoped export (TASK-406). Chunked source-side
584
+ IN query + in-memory target filter."""
585
+ if not uids:
586
+ return []
587
+ chunk = 500
588
+ id_to_uid: dict[int, str] = {}
589
+ uid_list = list(dict.fromkeys(uids))
590
+ for start in range(0, len(uid_list), chunk):
591
+ batch = uid_list[start : start + chunk]
592
+ for row_id, row_uid in self._conn.execute(
593
+ f"SELECT id, uid FROM graph_nodes WHERE uid IN ({','.join('?' * len(batch))})",
594
+ batch,
595
+ ).fetchall():
596
+ id_to_uid[int(row_id)] = str(row_uid)
597
+ member_ids = list(id_to_uid)
598
+ member_set = set(member_ids)
599
+ excluded_types = set(exclude_edge_types or ())
600
+ wanted_types = set(edge_types) if edge_types else None
601
+ edges: list[GraphEdge] = []
602
+ for start in range(0, len(member_ids), chunk):
603
+ batch = member_ids[start : start + chunk]
604
+ rows = self._conn.execute(
605
+ "SELECT source_id, target_id, edge_type, extractor, confidence "
606
+ f"FROM graph_edges_v12 WHERE source_id IN ({','.join('?' * len(batch))})",
607
+ batch,
608
+ ).fetchall()
609
+ for src, tgt, edge_type, extractor, confidence in rows:
610
+ if int(tgt) not in member_set:
611
+ continue
612
+ if edge_type in excluded_types:
613
+ continue
614
+ if wanted_types is not None and edge_type not in wanted_types:
615
+ continue
616
+ edges.append(
617
+ GraphEdge(
618
+ source_uid=id_to_uid[int(src)],
619
+ target_uid=id_to_uid[int(tgt)],
620
+ edge_type=str(edge_type),
621
+ extractor=str(extractor or ""),
622
+ confidence=float(confidence or 0.0),
623
+ )
624
+ )
625
+ if len(edges) >= limit:
626
+ return edges
627
+ return edges
628
+
629
+ def contains_ancestors_bulk(
630
+ self, uids: Sequence[str]
631
+ ) -> tuple[list[GraphNode], list[GraphEdge]]:
632
+ """Ancestor closure over ``contains`` for a uid SET — one query per
633
+ tree level instead of one walk per node (TASK-403: the per-node
634
+ loop cost ~12 s on a 30k-uid spine export).
635
+ """
636
+ if not uids:
637
+ return [], []
638
+ chunk = 500
639
+ id_to_uid: dict[int, str] = {}
640
+
641
+ def _map_ids(ids: Iterable[int]) -> None:
642
+ pending = [i for i in ids if i not in id_to_uid]
643
+ for start in range(0, len(pending), chunk):
644
+ batch = pending[start : start + chunk]
645
+ for row_id, row_uid in self._conn.execute(
646
+ f"SELECT id, uid FROM graph_nodes WHERE id IN ({','.join('?' * len(batch))})",
647
+ batch,
648
+ ).fetchall():
649
+ id_to_uid[int(row_id)] = str(row_uid)
650
+
651
+ frontier: set[int] = set()
652
+ uid_list = list(dict.fromkeys(uids))
653
+ for start in range(0, len(uid_list), chunk):
654
+ batch = uid_list[start : start + chunk]
655
+ for row_id, row_uid in self._conn.execute(
656
+ f"SELECT id, uid FROM graph_nodes WHERE uid IN ({','.join('?' * len(batch))})",
657
+ batch,
658
+ ).fetchall():
659
+ id_to_uid[int(row_id)] = str(row_uid)
660
+ frontier.add(int(row_id))
661
+
662
+ seen: set[int] = set(frontier)
663
+ new_ids: set[int] = set()
664
+ edge_rows: list[tuple[int, int, float]] = []
665
+ for _ in range(32): # spine depth ceiling — real trees are ≤ ~12
666
+ if not frontier:
667
+ break
668
+ level_parents: set[int] = set()
669
+ frontier_list = list(frontier)
670
+ for start in range(0, len(frontier_list), chunk):
671
+ batch = frontier_list[start : start + chunk]
672
+ for src, tgt, conf in self._conn.execute(
673
+ "SELECT source_id, target_id, confidence FROM graph_edges_v12 "
674
+ f"WHERE edge_type='contains' AND target_id IN ({','.join('?' * len(batch))})",
675
+ batch,
676
+ ).fetchall():
677
+ edge_rows.append((int(src), int(tgt), float(conf or 1.0)))
678
+ if int(src) not in seen:
679
+ level_parents.add(int(src))
680
+ seen |= level_parents
681
+ new_ids |= level_parents
682
+ frontier = level_parents
683
+
684
+ ancestor_nodes: list[GraphNode] = []
685
+ if new_ids:
686
+ _map_ids(new_ids)
687
+ hydrated = self.get_nodes_bulk([id_to_uid[i] for i in new_ids if i in id_to_uid])
688
+ ancestor_nodes = list(hydrated.values())
689
+ spine_edges = [
690
+ GraphEdge(
691
+ source_uid=id_to_uid[src],
692
+ target_uid=id_to_uid[tgt],
693
+ edge_type="contains",
694
+ extractor="spine_closure",
695
+ confidence=conf,
696
+ )
697
+ for src, tgt, conf in edge_rows
698
+ if src in id_to_uid and tgt in id_to_uid
699
+ ]
700
+ return ancestor_nodes, spine_edges
701
+
702
+ def link_import_bindings(self, *, file_path: str | None = None) -> int:
703
+ """Bind ``import_`` nodes to the symbol they import (TASK-402).
704
+
705
+ code_python emits one ``code:import:<file>::<name>`` node per
706
+ imported name (metadata carries ``imported`` + ``source_module``)
707
+ but no edge to the symbol itself, so every ``from M import name``
708
+ caller was invisible to references/impact (init_db probe: 16 of
709
+ ~106 caller files reachable). Same exactly-one resolution contract
710
+ as link_external_stubs: ambiguous or unresolved → skip, never guess.
711
+ """
712
+ with self._write_lock:
713
+ scope = " AND file_path = ?" if file_path else ""
714
+ params: tuple[Any, ...] = (file_path,) if file_path else ()
715
+ rows = self._conn.execute(
716
+ f"SELECT id, metadata_json FROM graph_nodes WHERE kind = 'import_'{scope}",
717
+ params,
718
+ ).fetchall()
719
+ wanted: dict[str, list[tuple[int, str]]] = {}
720
+ for node_id, metadata_json in rows:
721
+ try:
722
+ metadata = json.loads(metadata_json or "{}")
723
+ except ValueError:
724
+ continue
725
+ name = metadata.get("imported")
726
+ module = metadata.get("source_module")
727
+ if not name or not module or metadata.get("wildcard"):
728
+ continue
729
+ wanted.setdefault(str(name), []).append((int(node_id), str(module)))
730
+ if not wanted:
731
+ return 0
732
+ placeholders = ",".join("?" * len(wanted))
733
+ real_rows = self._conn.execute(
734
+ f"""
735
+ SELECT id, label, file_path FROM graph_nodes
736
+ WHERE kind IN ('function','class','variable','interface')
737
+ AND label IN ({placeholders})
738
+ AND file_path IS NOT NULL
739
+ """,
740
+ tuple(wanted),
741
+ ).fetchall()
742
+ real_by_label: dict[str, list[tuple[int, str]]] = {}
743
+ for real_id, real_label, real_file in real_rows:
744
+ real_by_label.setdefault(str(real_label), []).append((int(real_id), real_file))
745
+ now = int(time.time())
746
+ linked = 0
747
+ for name, importers in wanted.items():
748
+ candidates = real_by_label.get(name, [])
749
+ if not candidates:
750
+ continue
751
+ for import_id, module in importers:
752
+ module_suffix = module.replace(".", "/")
753
+ matches = {
754
+ real_id
755
+ for real_id, real_file in candidates
756
+ if (
757
+ real_file == f"{module_suffix}.py"
758
+ or real_file.endswith(f"/{module_suffix}.py")
759
+ or real_file == f"{module_suffix}/__init__.py"
760
+ or real_file.endswith(f"/{module_suffix}/__init__.py")
761
+ )
762
+ }
763
+ if len(matches) != 1:
764
+ continue
765
+ cursor = self._conn.execute(
766
+ """
767
+ INSERT OR IGNORE INTO graph_edges_v12
768
+ (source_id, target_id, edge_type, confidence,
769
+ extractor, source_span, created_at, updated_at)
770
+ VALUES (?, ?, 'imports', 0.85, 'import_linker@v1', NULL, ?, ?)
771
+ """,
772
+ (import_id, next(iter(matches)), now, now),
773
+ )
774
+ linked += int(cursor.rowcount or 0)
775
+ self._conn.commit()
776
+ return linked
777
+
778
+ def link_php_handlers(self) -> int:
779
+ """Resolve Laravel controller-handler stubs to real method nodes.
780
+
781
+ Contracts emits a route→`code:external:phproute:Ctrl.method` stub
782
+ because the controller lives in another file. After the global walk
783
+ every method node exists, so bind each stub to the unique
784
+ `code:method:…::Ctrl.method` node (skip when 0 or >1 match — never
785
+ guess). Mirrors `link_external_stubs` but for the PHP class-method
786
+ uid shape, which the Python-`.py` matcher there does not handle.
787
+ """
788
+ with self._write_lock:
789
+ stub_rows = self._conn.execute(
790
+ "SELECT id, uid FROM graph_nodes WHERE uid LIKE 'code:external:phproute:%'"
791
+ ).fetchall()
792
+ rewrites = 0
793
+ for stub_id, stub_uid in stub_rows:
794
+ key = stub_uid[len("code:external:phproute:") :] # Ctrl.method
795
+ matches = self._conn.execute(
796
+ "SELECT id FROM graph_nodes WHERE kind='method' AND uid LIKE ?",
797
+ (f"%::{key}",),
798
+ ).fetchall()
799
+ if len(matches) != 1:
800
+ continue
801
+ self._conn.execute(
802
+ "UPDATE OR IGNORE graph_edges_v12 SET target_id = ? WHERE target_id = ?",
803
+ (int(matches[0][0]), int(stub_id)),
804
+ )
805
+ rewrites += 1
806
+ self._conn.commit()
807
+ return rewrites
808
+
809
+ # -- Read path ---------------------------------------------------------
810
+
811
+ def get_node(self, uid: str) -> GraphNode | None:
812
+ # G18: pure-SELECT, no write_lock — WAL gives concurrent
813
+ # readers; the lock here was serialising reads behind any
814
+ # in-flight write for no correctness benefit.
815
+ # P6: thread-local read connection so parallel get_node calls
816
+ # don't serialise behind the primary conn's mutex+GIL.
817
+ row = (
818
+ self._get_read_conn()
819
+ .execute(
820
+ """
821
+ SELECT kind, label, uid, file_path, start_line, end_line,
822
+ signature, lang, doc_blob, ast_hash, content_hash,
823
+ metadata_json
824
+ FROM graph_nodes WHERE uid=?
825
+ """,
826
+ (uid,),
827
+ )
828
+ .fetchone()
829
+ )
830
+ if row is None:
831
+ return None
832
+ return self._row_to_node(row)
833
+
834
+ def get_nodes_bulk(self, uids: Sequence[str]) -> dict[str, GraphNode]:
835
+ """B6: batch variant of get_node — one SELECT per call, not N."""
836
+ if not uids:
837
+ return {}
838
+ uniq = list(dict.fromkeys(uids))
839
+ result: dict[str, GraphNode] = {}
840
+ # SQLite parameter limit is 999; chunk to stay safely under.
841
+ chunk = 500
842
+ # G18: pure-SELECT, no write_lock. P6: thread-local read conn.
843
+ read_conn = self._get_read_conn()
844
+ for start in range(0, len(uniq), chunk):
845
+ group = uniq[start : start + chunk]
846
+ placeholders = ",".join("?" for _ in group)
847
+ rows = read_conn.execute(
848
+ f"""
849
+ SELECT kind, label, uid, file_path, start_line, end_line,
850
+ signature, lang, doc_blob, ast_hash, content_hash,
851
+ metadata_json
852
+ FROM graph_nodes WHERE uid IN ({placeholders})
853
+ """,
854
+ tuple(group),
855
+ ).fetchall()
856
+ for row in rows:
857
+ node = self._row_to_node(row)
858
+ result[node.uid] = node
859
+ return result
860
+
861
+ def count_nodes(self, kind: str | None = None) -> int:
862
+ # G18: pure-SELECT, no write_lock. P6: thread-local read conn.
863
+ read_conn = self._get_read_conn()
864
+ if kind is None:
865
+ row = read_conn.execute("SELECT COUNT(*) FROM graph_nodes").fetchone()
866
+ else:
867
+ # Accept legacy or canonical form; storage is canonical.
868
+ try:
869
+ kind_q = normalize_kind(kind).value
870
+ except ValueError:
871
+ kind_q = kind
872
+ row = read_conn.execute(
873
+ "SELECT COUNT(*) FROM graph_nodes WHERE kind=?", (kind_q,)
874
+ ).fetchone()
875
+ return int(row[0])
876
+
877
+ def count_edges(self, edge_type: str | None = None) -> int:
878
+ # G17: count DISTINCT logical edges (source, target, edge_type).
879
+ # `list_edges` dedupes via highest-confidence subquery — match
880
+ # that semantic here. G18: no write_lock. P6: thread-local conn.
881
+ read_conn = self._get_read_conn()
882
+ if edge_type is None:
883
+ row = read_conn.execute(
884
+ "SELECT COUNT(*) FROM (SELECT DISTINCT source_id, target_id, edge_type FROM graph_edges_v12)"
885
+ ).fetchone()
886
+ else:
887
+ row = read_conn.execute(
888
+ "SELECT COUNT(*) FROM (SELECT DISTINCT source_id, target_id, edge_type FROM graph_edges_v12 WHERE edge_type=?)",
889
+ (edge_type,),
890
+ ).fetchone()
891
+ return int(row[0])
892
+
893
+ def list_edges(
894
+ self,
895
+ *,
896
+ source_uid: str | None = None,
897
+ target_uid: str | None = None,
898
+ edge_types: Sequence[str] | None = None,
899
+ confidence_min: float = 0.0,
900
+ include_evidence: bool = False,
901
+ limit: int = 100,
902
+ ) -> list[GraphEdge]:
903
+ """List edges matching filters, ordered by confidence DESC, id ASC.
904
+
905
+ Ordering on id ASC as a tie-break makes results deterministic
906
+ — required for parity testing (Section 12.6 of the plan).
907
+ """
908
+ where_parts = ["e.confidence >= ?"]
909
+ params: list[Any] = [float(confidence_min)]
910
+
911
+ if source_uid is not None:
912
+ where_parts.append("e.source_id = (SELECT id FROM graph_nodes WHERE uid=?)")
913
+ params.append(source_uid)
914
+ if target_uid is not None:
915
+ where_parts.append("e.target_id = (SELECT id FROM graph_nodes WHERE uid=?)")
916
+ params.append(target_uid)
917
+ if edge_types:
918
+ placeholders = ",".join("?" for _ in edge_types)
919
+ where_parts.append(f"e.edge_type IN ({placeholders})")
920
+ params.extend(edge_types)
921
+
922
+ where_sql = " AND ".join(where_parts)
923
+ # Dedupe across extractors: when multiple extractors emit the
924
+ # same logical edge (source, target, edge_type), keep the
925
+ # highest-confidence row (ties broken by lowest id) so the API
926
+ # surface presents one row per relationship instead of N copies.
927
+ query = f"""
928
+ SELECT e.id, ns.uid, nt.uid, e.edge_type, e.extractor,
929
+ e.confidence, e.source_span
930
+ FROM graph_edges_v12 e
931
+ JOIN graph_nodes ns ON ns.id = e.source_id
932
+ JOIN graph_nodes nt ON nt.id = e.target_id
933
+ WHERE {where_sql}
934
+ AND e.id = (
935
+ SELECT id FROM graph_edges_v12 ee
936
+ WHERE ee.source_id = e.source_id
937
+ AND ee.target_id = e.target_id
938
+ AND ee.edge_type = e.edge_type
939
+ ORDER BY ee.confidence DESC, ee.id ASC
940
+ LIMIT 1
941
+ )
942
+ ORDER BY e.confidence DESC, e.id ASC
943
+ LIMIT ?
944
+ """
945
+ params.append(int(limit))
946
+ # G18: pure-SELECT, no write_lock. P6: thread-local read conn.
947
+ read_conn = self._get_read_conn()
948
+ rows = read_conn.execute(query, params).fetchall()
949
+
950
+ evidence_by_edge: dict[int, list[sqlite3.Row]] = {}
951
+ if include_evidence and rows:
952
+ ev_rows = read_conn.execute(
953
+ """
954
+ SELECT edge_id, signal_name, weight, note
955
+ FROM graph_evidence_v12
956
+ WHERE edge_id IN (%s)
957
+ ORDER BY id ASC
958
+ """
959
+ % ",".join(str(int(r[0])) for r in rows)
960
+ ).fetchall()
961
+ for ev in ev_rows:
962
+ evidence_by_edge.setdefault(int(ev[0]), []).append(ev)
963
+
964
+ edges: list[GraphEdge] = []
965
+ for edge_row in rows:
966
+ evidence_tuple: tuple[EvidenceSignal, ...] = ()
967
+ if include_evidence:
968
+ ev_rows = evidence_by_edge.get(int(edge_row[0]), [])
969
+ evidence_tuple = tuple(
970
+ EvidenceSignal(signal_name=r[1], weight=float(r[2]), note=r[3]) for r in ev_rows
971
+ )
972
+ edges.append(
973
+ GraphEdge(
974
+ source_uid=edge_row[1],
975
+ target_uid=edge_row[2],
976
+ edge_type=edge_row[3],
977
+ extractor=edge_row[4],
978
+ confidence=float(edge_row[5]),
979
+ source_span=edge_row[6],
980
+ evidence=evidence_tuple,
981
+ )
982
+ )
983
+ return edges
984
+
985
+ def sample_nodes(self, kind: str | None, limit: int) -> list[GraphNode]:
986
+ """B13: return up to `limit` nodes, optionally filtered by kind."""
987
+ # G18: pure-SELECT, no write_lock. P6: thread-local read conn.
988
+ read_conn = self._get_read_conn()
989
+ if kind is None:
990
+ rows = read_conn.execute(
991
+ """
992
+ SELECT kind, label, uid, file_path, start_line, end_line,
993
+ signature, lang, doc_blob, ast_hash, content_hash,
994
+ metadata_json
995
+ FROM graph_nodes
996
+ ORDER BY id ASC
997
+ LIMIT ?
998
+ """,
999
+ (int(limit),),
1000
+ ).fetchall()
1001
+ else:
1002
+ try:
1003
+ kind_q = normalize_kind(kind).value
1004
+ except ValueError:
1005
+ kind_q = kind
1006
+ rows = read_conn.execute(
1007
+ """
1008
+ SELECT kind, label, uid, file_path, start_line, end_line,
1009
+ signature, lang, doc_blob, ast_hash, content_hash,
1010
+ metadata_json
1011
+ FROM graph_nodes
1012
+ WHERE kind = ?
1013
+ ORDER BY id ASC
1014
+ LIMIT ?
1015
+ """,
1016
+ (kind_q, int(limit)),
1017
+ ).fetchall()
1018
+ return [self._row_to_node(row) for row in rows]
1019
+
1020
+ # -- Internal helpers --------------------------------------------------
1021
+
1022
+ def _node_id_for_uid(self, uid: str) -> int:
1023
+ row = self._conn.execute("SELECT id FROM graph_nodes WHERE uid=?", (uid,)).fetchone()
1024
+ if row is None:
1025
+ raise ValueError(f"unknown uid {uid!r}: upsert the node before emitting edges")
1026
+ return int(row[0])
1027
+
1028
+ @staticmethod
1029
+ def _row_to_node(row: Sequence[Any]) -> GraphNode:
1030
+ metadata = {}
1031
+ metadata_json = row[11]
1032
+ if metadata_json:
1033
+ try:
1034
+ metadata = json.loads(metadata_json)
1035
+ except json.JSONDecodeError as exc:
1036
+ logger.debug("metadata JSON decode failed for uid=%s: %s", row[2], exc)
1037
+ return GraphNode(
1038
+ kind=row[0],
1039
+ label=row[1],
1040
+ uid=row[2],
1041
+ file_path=row[3],
1042
+ start_line=row[4],
1043
+ end_line=row[5],
1044
+ signature=row[6],
1045
+ lang=row[7],
1046
+ doc_blob=row[8],
1047
+ ast_hash=row[9],
1048
+ content_hash=row[10],
1049
+ metadata=metadata,
1050
+ )
1051
+
1052
+
1053
+ __all__ = ["SqliteBackend"]