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,1454 @@
1
+ """graph_os — Python extractor (I.4).
2
+
3
+ DEPENDS: Python's stdlib `ast`; no tree-sitter / LSP.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import ast
9
+ import hashlib
10
+ import logging
11
+ import re
12
+ from dataclasses import dataclass
13
+ from pathlib import PurePosixPath
14
+
15
+ from ..types import EvidenceSignal, GraphEdge, GraphNode
16
+ from .md_links import (
17
+ ExtractionResult,
18
+ ParseError,
19
+ _normalize_path,
20
+ _promote_stubs,
21
+ emit_contains_spine,
22
+ )
23
+
24
+ logger = logging.getLogger("graph_os.extractors.code_python")
25
+
26
+ EXTRACTOR_ID = "code_python@v1"
27
+ # separate ID for the tree-sitter-primary import path so
28
+ # provenance_for() can distinguish ast-emitted edges from
29
+ # tree-sitter-emitted ones.
30
+ EXTRACTOR_ID_TS_IMPORTS = "code_python_ts@v1"
31
+
32
+
33
+ def _tree_sitter_imports_active() -> bool:
34
+ """True when imports should be parsed via tree-sitter.
35
+
36
+ Activation conditions:
37
+ - tree-sitter-python grammar is loadable, AND
38
+ - COS_EXTRACTOR_PREFERENCE is "tree-sitter" (default `auto`
39
+ keeps the legacy ast path so existing graphs don't double-emit
40
+ edges with two different extractor tags during rollout).
41
+ """
42
+ import os as _os
43
+
44
+ pref = (_os.environ.get("COS_EXTRACTOR_PREFERENCE") or "auto").lower()
45
+ if pref != "tree-sitter":
46
+ return False
47
+ try:
48
+ from ..tree_sitter_overlay import _load_language
49
+
50
+ return _load_language("python") is not None
51
+ except Exception:
52
+ return False
53
+
54
+
55
+ def _imports_via_tree_sitter(content: str) -> list[_ImportDecl] | None:
56
+ """Parse Python imports via tree-sitter.
57
+
58
+ Notes:
59
+ - Aliases are honoured: `from pkg.sub import Foo as F` keeps
60
+ `imported="Foo"` and `local_name="F"`.
61
+ - Wildcards (`from pkg import *`) emit a single `_ImportDecl`
62
+ with `is_wildcard=True`.
63
+ - Relative imports (`from . import X` / `from ..pkg import Y`)
64
+ prepend the dot count to source_module to match ast semantics.
65
+ """
66
+ try:
67
+ from ..tree_sitter_overlay import node_text, parse
68
+ except ImportError:
69
+ return None
70
+
71
+ parsed = parse("python", content)
72
+ if parsed is None:
73
+ return None
74
+
75
+ out: list[_ImportDecl] = []
76
+ content_bytes = content.encode("utf-8")
77
+
78
+ def _walk(node):
79
+ # Pre-order walk; we only act on import_statement /
80
+ # import_from_statement nodes — siblings recurse via children.
81
+ kind = getattr(node, "type", None)
82
+ if kind == "import_statement":
83
+ _emit_import_statement(node, content_bytes, out)
84
+ return
85
+ if kind == "import_from_statement":
86
+ _emit_import_from(node, content_bytes, out)
87
+ return
88
+ for child in node.children:
89
+ _walk(child)
90
+
91
+ _walk(parsed.root)
92
+ out.sort(key=lambda d: d.line)
93
+ return out
94
+
95
+
96
+ def _emit_import_statement(node, content_bytes: bytes, out: list[_ImportDecl]) -> None:
97
+ """Handle `import X` / `import X as Y` / `import X, Y`."""
98
+ # The tree-sitter Python grammar exposes each imported alias as a
99
+ # `dotted_name` (or `aliased_import` when `as` is present) child.
100
+ line = (node.start_point[0] if hasattr(node, "start_point") else 0) + 1
101
+ for child in node.children:
102
+ ctype = getattr(child, "type", None)
103
+ if ctype == "dotted_name":
104
+ name = _node_text(child, content_bytes)
105
+ out.append(
106
+ _ImportDecl(
107
+ source_module=None,
108
+ imported=name,
109
+ local_name=name.split(".")[0],
110
+ line=line,
111
+ )
112
+ )
113
+ elif ctype == "aliased_import":
114
+ name_node = child.child_by_field_name("name")
115
+ alias_node = child.child_by_field_name("alias")
116
+ if name_node is None or alias_node is None:
117
+ continue
118
+ name = _node_text(name_node, content_bytes)
119
+ alias = _node_text(alias_node, content_bytes)
120
+ out.append(
121
+ _ImportDecl(
122
+ source_module=None,
123
+ imported=name,
124
+ local_name=alias,
125
+ line=line,
126
+ )
127
+ )
128
+
129
+
130
+ def _emit_import_from(node, content_bytes: bytes, out: list[_ImportDecl]) -> None:
131
+ """Handle `from X import Y as Z`, `from . import X`, `from X import *`."""
132
+ line = (node.start_point[0] if hasattr(node, "start_point") else 0) + 1
133
+ module_node = node.child_by_field_name("module_name")
134
+ name_nodes = list(node.children_by_field_name("name") or [])
135
+
136
+ # Tree-sitter's grammar represents `from . import x` with a
137
+ # `relative_import` or a series of `import_prefix` children for
138
+ # the dots; rebuild the leading-dot count from the source text.
139
+ module_text = ""
140
+ if module_node is not None:
141
+ module_text = _node_text(module_node, content_bytes)
142
+ else:
143
+ for child in node.children:
144
+ if getattr(child, "type", None) == "relative_import":
145
+ module_text = _node_text(child, content_bytes)
146
+ break
147
+
148
+ # Wildcard import: the body is a `*` token, no aliased_import children.
149
+ is_wildcard = any(
150
+ getattr(c, "type", None) == "wildcard_import"
151
+ or (getattr(c, "type", None) == "*" and getattr(c, "is_named", True) is False)
152
+ for c in node.children
153
+ )
154
+ if is_wildcard:
155
+ out.append(
156
+ _ImportDecl(
157
+ source_module=module_text,
158
+ imported="*",
159
+ local_name="*",
160
+ line=line,
161
+ is_wildcard=True,
162
+ )
163
+ )
164
+ return
165
+
166
+ if not name_nodes:
167
+ # Older grammars expose the imported names as `dotted_name` /
168
+ # `aliased_import` children of the `import_from_statement`.
169
+ name_nodes = [
170
+ c
171
+ for c in node.children
172
+ if getattr(c, "type", None) in ("dotted_name", "aliased_import")
173
+ ]
174
+
175
+ for name_node in name_nodes:
176
+ ntype = getattr(name_node, "type", None)
177
+ if ntype == "aliased_import":
178
+ inner_name = name_node.child_by_field_name("name")
179
+ inner_alias = name_node.child_by_field_name("alias")
180
+ if inner_name is None or inner_alias is None:
181
+ continue
182
+ imported = _node_text(inner_name, content_bytes)
183
+ local = _node_text(inner_alias, content_bytes)
184
+ elif ntype == "dotted_name":
185
+ imported = _node_text(name_node, content_bytes)
186
+ local = imported
187
+ else:
188
+ continue
189
+ out.append(
190
+ _ImportDecl(
191
+ source_module=module_text,
192
+ imported=imported,
193
+ local_name=local,
194
+ line=line,
195
+ )
196
+ )
197
+
198
+
199
+ def _node_text(node, content_bytes: bytes) -> str:
200
+ try:
201
+ return content_bytes[node.start_byte : node.end_byte].decode("utf-8", errors="replace")
202
+ except Exception:
203
+ return ""
204
+
205
+
206
+ # ---------------------------------------------------------------------------
207
+ # tree-sitter primary path for class heritage + decorators
208
+ # ---------------------------------------------------------------------------
209
+
210
+
211
+ def _tree_sitter_heritage_active() -> bool:
212
+ """True when class heritage and decorator edges should be parsed via
213
+ tree-sitter. Same activation rule as `_tree_sitter_imports_active`
214
+ so a single ``--extractor=tree-sitter`` flag flips both paths in
215
+ lock-step (TASK-119 + TASK-120)."""
216
+ return _tree_sitter_imports_active()
217
+
218
+
219
+ def _heritage_via_tree_sitter(
220
+ path: str,
221
+ content: str,
222
+ ) -> tuple[list[tuple[str, str]], list[tuple[str, str]]] | None:
223
+ """Walk the tree-sitter Python AST and return ``(inherits, decorators)``.
224
+
225
+ Notes:
226
+ - Qualnames mirror the ast visitor (nested classes nest, nested
227
+ functions still emit at module level for the ast path → we
228
+ match that to keep edge counts identical).
229
+ - Decorators are captured for both functions and classes.
230
+ - Self / parent decorator chains preserve their dotted form
231
+ (`a.b.c`) via tree-sitter's `attribute` node textual extent.
232
+ """
233
+ try:
234
+ from ..tree_sitter_overlay import parse
235
+ except ImportError:
236
+ return None
237
+
238
+ parsed = parse("python", content)
239
+ if parsed is None:
240
+ return None
241
+
242
+ content_bytes = content.encode("utf-8")
243
+ inherits: list[tuple[str, str]] = []
244
+ decorators_edges: list[tuple[str, str]] = []
245
+
246
+ def _walk(node, qual_stack: list[str], scope_uid: str | None) -> None:
247
+ ntype = getattr(node, "type", None)
248
+ if ntype == "class_definition":
249
+ name_node = node.child_by_field_name("name")
250
+ if name_node is None:
251
+ return
252
+ class_name = _node_text(name_node, content_bytes)
253
+ new_qual = qual_stack + [class_name]
254
+ class_qualname = ".".join(new_qual)
255
+ class_uid_ = class_uid(path, class_qualname)
256
+
257
+ # Bases — `superclasses` field carries an `argument_list`.
258
+ superclasses = node.child_by_field_name("superclasses")
259
+ if superclasses is not None:
260
+ for child in superclasses.children:
261
+ if getattr(child, "type", None) in (
262
+ "identifier",
263
+ "attribute",
264
+ ):
265
+ base_name = _node_text(child, content_bytes)
266
+ if base_name:
267
+ inherits.append((class_uid_, base_name))
268
+
269
+ # Decorators — sibling `decorator` nodes that precede this
270
+ # class_definition inside a `decorated_definition` parent.
271
+ for dec_name in _decorator_names(node, content_bytes):
272
+ decorators_edges.append((class_uid_, dec_name))
273
+
274
+ body = node.child_by_field_name("body")
275
+ if body is not None:
276
+ for child in body.children:
277
+ _walk(child, new_qual, class_uid_)
278
+ return
279
+
280
+ if ntype in ("function_definition", "async_function_definition"):
281
+ name_node = node.child_by_field_name("name")
282
+ if name_node is None:
283
+ return
284
+ fn_name = _node_text(name_node, content_bytes)
285
+ new_qual = qual_stack + [fn_name]
286
+ qualname = ".".join(new_qual)
287
+ in_class = scope_uid is not None and scope_uid.startswith("code:class:")
288
+ uid = (
289
+ method_uid(path, qualname)
290
+ if in_class
291
+ else function_uid(
292
+ path,
293
+ qualname,
294
+ )
295
+ )
296
+
297
+ for dec_name in _decorator_names(node, content_bytes):
298
+ decorators_edges.append((uid, dec_name))
299
+
300
+ body = node.child_by_field_name("body")
301
+ if body is not None:
302
+ for child in body.children:
303
+ _walk(child, new_qual, uid)
304
+ return
305
+
306
+ if ntype == "decorated_definition":
307
+ # Recurse into the wrapped definition; the `_decorator_names`
308
+ # helper handles the decorators attached to that definition.
309
+ for child in node.children:
310
+ _walk(child, qual_stack, scope_uid)
311
+ return
312
+
313
+ # Plain pass-through for module-level / unrelated nodes.
314
+ for child in node.children:
315
+ _walk(child, qual_stack, scope_uid)
316
+
317
+ _walk(parsed.root, [], None)
318
+ return inherits, decorators_edges
319
+
320
+
321
+ def _decorator_names(definition_node, content_bytes: bytes) -> list[str]:
322
+ """Return dotted names for every decorator attached to
323
+ ``definition_node`` (a class_definition or function_definition).
324
+
325
+ The grammar nests the decorator(s) and the definition under a
326
+ ``decorated_definition`` parent. We walk parent siblings in
327
+ document order so chained decorators preserve their original
328
+ sequence — matching the ast visitor's iteration of
329
+ ``node.decorator_list``.
330
+ """
331
+ parent = getattr(definition_node, "parent", None)
332
+ if parent is None or getattr(parent, "type", None) != "decorated_definition":
333
+ return []
334
+ out: list[str] = []
335
+ for child in parent.children:
336
+ if child is definition_node:
337
+ break
338
+ if getattr(child, "type", None) == "decorator":
339
+ # decorator → expression child carrying the dotted name.
340
+ for inner in child.children:
341
+ itype = getattr(inner, "type", None)
342
+ if itype in ("identifier", "attribute", "call"):
343
+ text = _node_text(inner, content_bytes).strip()
344
+ if not text:
345
+ continue
346
+ # `@decorator(args)` → strip the `(...)` to keep the
347
+ # dotted name only, matching `_dotted_name(ast.Call)`.
348
+ if "(" in text:
349
+ text = text.split("(", 1)[0].strip()
350
+ if text:
351
+ out.append(text)
352
+ break
353
+ return out
354
+
355
+
356
+ # ---------------------------------------------------------------------------
357
+ # uid helpers
358
+ # ---------------------------------------------------------------------------
359
+
360
+
361
+ def file_uid(path: str) -> str:
362
+ return f"code:file:{_normalize_path(path)}"
363
+
364
+
365
+ def module_uid(module_name: str) -> str:
366
+ return f"code:module:{module_name}"
367
+
368
+
369
+ def class_uid(path: str, qualname: str) -> str:
370
+ return f"code:class:{_normalize_path(path)}::{qualname}"
371
+
372
+
373
+ def function_uid(path: str, qualname: str) -> str:
374
+ return f"code:function:{_normalize_path(path)}::{qualname}"
375
+
376
+
377
+ def method_uid(path: str, qualname: str) -> str:
378
+ return f"code:method:{_normalize_path(path)}::{qualname}"
379
+
380
+
381
+ # ---------------------------------------------------------------------------
382
+ # Visitor state
383
+ # ---------------------------------------------------------------------------
384
+
385
+
386
+ @dataclass
387
+ class _SymbolDecl:
388
+ """One declaration discovered during the AST walk."""
389
+
390
+ uid: str
391
+ kind: str
392
+ name: str
393
+ qualname: str
394
+ line: int
395
+ end_line: int | None
396
+ signature: str
397
+ docstring: str | None
398
+ decorators: tuple[str, ...]
399
+ parent_uid: str | None
400
+ is_method: bool = False
401
+
402
+
403
+ @dataclass
404
+ class _CallSite:
405
+ caller_uid: str
406
+ callee_name: str # last segment of the dotted expression
407
+ full_expr: str # dotted form for later chain resolution
408
+ line: int
409
+ is_constructor_like: bool # `Foo()` where Foo looks capitalised
410
+ is_await: bool = False # E5: `await X()` — emits `awaits` edge
411
+ dispatched_uids: tuple[str, ...] = () # E6: known-function uids passed as args
412
+ enclosing_class_uid: str | None = None # GE: class scope for self./cls. method resolution
413
+
414
+
415
+ @dataclass
416
+ class _ImportDecl:
417
+ """A single `import X` / `from X import Y` statement."""
418
+
419
+ source_module: str | None # `X` in `from X import Y`
420
+ imported: str # `Y` (or `X` for plain `import X`)
421
+ local_name: str # alias if present, else imported
422
+ line: int
423
+ is_wildcard: bool = False
424
+
425
+
426
+ # ---------------------------------------------------------------------------
427
+ # Main entry
428
+ # ---------------------------------------------------------------------------
429
+
430
+
431
+ def extract(path: str, content: str) -> ExtractionResult:
432
+ """Parse a `.py` file into nodes + edges."""
433
+ result = ExtractionResult()
434
+ normalised = _normalize_path(path)
435
+ content_hash = hashlib.sha256(content.encode("utf-8")).hexdigest()[:16]
436
+ file_header = _module_docstring(content)
437
+
438
+ file_node = GraphNode(
439
+ uid=file_uid(path),
440
+ kind="code:file",
441
+ label=PurePosixPath(normalised).name,
442
+ file_path=normalised,
443
+ lang="py",
444
+ doc_blob=file_header,
445
+ content_hash=content_hash,
446
+ metadata={"extractor": EXTRACTOR_ID},
447
+ )
448
+ result.nodes.append(file_node)
449
+
450
+ try:
451
+ tree = ast.parse(content, filename=normalised)
452
+ except SyntaxError as exc:
453
+ result.parse_errors.append(
454
+ ParseError(
455
+ kind="syntax_error",
456
+ detail=f"{exc.msg} at line {exc.lineno}",
457
+ line=exc.lineno,
458
+ )
459
+ )
460
+ emit_contains_spine(
461
+ file_path=path,
462
+ file_uid_=file_node.uid,
463
+ result=result,
464
+ extractor_id=EXTRACTOR_ID,
465
+ )
466
+ _promote_stubs(result)
467
+ return result
468
+ except Exception as exc:
469
+ result.parse_errors.append(ParseError(kind="fatal", detail=str(exc)))
470
+ emit_contains_spine(
471
+ file_path=path,
472
+ file_uid_=file_node.uid,
473
+ result=result,
474
+ extractor_id=EXTRACTOR_ID,
475
+ )
476
+ _promote_stubs(result)
477
+ return result
478
+
479
+ mod_name = _module_name_for_path(normalised)
480
+ mod_node = GraphNode(
481
+ uid=module_uid(mod_name),
482
+ kind="code:module",
483
+ label=mod_name,
484
+ file_path=normalised,
485
+ lang="py",
486
+ doc_blob=ast.get_docstring(tree) or file_header,
487
+ metadata={"extractor": EXTRACTOR_ID},
488
+ )
489
+ result.nodes.append(mod_node)
490
+ result.edges.append(
491
+ GraphEdge(
492
+ source_uid=file_node.uid,
493
+ target_uid=mod_node.uid,
494
+ edge_type="contains",
495
+ extractor=EXTRACTOR_ID,
496
+ confidence=1.0,
497
+ )
498
+ )
499
+
500
+ visitor = _PythonVisitor(path=normalised, module_name=mod_name, content=content)
501
+ visitor.visit(tree)
502
+
503
+ # Module-level call statements (e.g. ``_db_conn = init_db()`` at
504
+ # server.py:51) are not captured during ``visitor.visit`` because the
505
+ # visitor only walks call-sites inside ``visit_FunctionDef`` /
506
+ # ``visit_AsyncFunctionDef``. After the visit completes, the scope
507
+ # stack is back at module scope, so walking top-level non-decl
508
+ # statements attributes their calls correctly to the module uid.
509
+ # FunctionDef / ClassDef are skipped because their bodies were
510
+ # already walked. Import / ImportFrom were registered by
511
+ # ``visit_Import`` / ``visit_ImportFrom`` during generic_visit.
512
+ for stmt in tree.body:
513
+ if isinstance(
514
+ stmt,
515
+ (
516
+ ast.FunctionDef,
517
+ ast.AsyncFunctionDef,
518
+ ast.ClassDef,
519
+ ast.Import,
520
+ ast.ImportFrom,
521
+ ),
522
+ ):
523
+ continue
524
+ visitor._walk_calls(stmt)
525
+
526
+ # tree-sitter primary path for imports, opt-in via the
527
+ # `--extractor=tree-sitter` flag. When active and the
528
+ # grammar parse succeeds, replace the ast-derived import list with
529
+ # the tree-sitter one and tag the emitted edges with
530
+ # `code_python_ts@v1` so `provenance_for(...)` returns
531
+ # `"tree-sitter"`. When inactive (default) the legacy ast path
532
+ # runs unchanged — zero regression risk for existing graphs.
533
+ import_extractor_id = EXTRACTOR_ID
534
+ if _tree_sitter_imports_active():
535
+ ts_imports = _imports_via_tree_sitter(content)
536
+ if ts_imports is not None:
537
+ visitor.imports = ts_imports
538
+ visitor.imported_local_names = {
539
+ d.local_name: d for d in ts_imports if d.local_name != "*"
540
+ }
541
+ import_extractor_id = EXTRACTOR_ID_TS_IMPORTS
542
+
543
+ # tree-sitter primary path for class heritage + decorators.
544
+ # Same activation gate as imports — flips both paths in lock-step.
545
+ heritage_extractor_id = EXTRACTOR_ID
546
+ if _tree_sitter_heritage_active():
547
+ ts_heritage = _heritage_via_tree_sitter(normalised, content)
548
+ if ts_heritage is not None:
549
+ ts_inherits, ts_decorators = ts_heritage
550
+ visitor.inherits = ts_inherits
551
+ # G1/G28: tree-sitter overlay misses module-level decorators
552
+ # for some files (board_os/mcp_tools.py: 0 of 16 @safe_tool
553
+ # captured). MERGE rather than overwrite — keep the AST's
554
+ # decorators when tree-sitter's set is a strict subset.
555
+ ast_dec_set = set(visitor.decorators_edges)
556
+ ts_dec_set = set(ts_decorators)
557
+ if ast_dec_set - ts_dec_set:
558
+ # AST sees more — union and prefer.
559
+ visitor.decorators_edges = list(ast_dec_set | ts_dec_set)
560
+ else:
561
+ visitor.decorators_edges = ts_decorators
562
+ heritage_extractor_id = EXTRACTOR_ID_TS_IMPORTS
563
+
564
+ # Emit decls + containment.
565
+ for decl in visitor.decls:
566
+ result.nodes.append(
567
+ GraphNode(
568
+ uid=decl.uid,
569
+ kind=decl.kind,
570
+ label=decl.name,
571
+ file_path=normalised,
572
+ start_line=decl.line,
573
+ end_line=decl.end_line,
574
+ signature=decl.signature,
575
+ lang="py",
576
+ doc_blob=decl.docstring,
577
+ ast_hash=_hash_decl(decl),
578
+ metadata={
579
+ "qualname": decl.qualname,
580
+ "decorators": list(decl.decorators),
581
+ "is_method": decl.is_method,
582
+ "extractor": EXTRACTOR_ID,
583
+ },
584
+ )
585
+ )
586
+ parent = decl.parent_uid or mod_node.uid
587
+ result.edges.append(
588
+ GraphEdge(
589
+ source_uid=parent,
590
+ target_uid=decl.uid,
591
+ edge_type="contains",
592
+ extractor=EXTRACTOR_ID,
593
+ confidence=1.0,
594
+ source_span=f"{normalised}:{decl.line}",
595
+ )
596
+ )
597
+
598
+ # Inheritance.
599
+ inherit_signal = (
600
+ "tree_sitter_base_class"
601
+ if heritage_extractor_id == EXTRACTOR_ID_TS_IMPORTS
602
+ else "ast_base_class"
603
+ )
604
+ for subclass_uid, base_name in visitor.inherits:
605
+ result.edges.append(
606
+ GraphEdge(
607
+ source_uid=subclass_uid,
608
+ target_uid=_resolve_symbol(base_name, path=normalised, visitor=visitor),
609
+ edge_type="inherits_from",
610
+ extractor=heritage_extractor_id,
611
+ confidence=_inherit_confidence(base_name, visitor),
612
+ source_span=f"{normalised}",
613
+ evidence=(EvidenceSignal(inherit_signal, 0.9),),
614
+ )
615
+ )
616
+
617
+ # type annotations — has_param_type / returns_type / field_of_type.
618
+ for fn_uid, type_name in visitor.param_types:
619
+ result.edges.append(
620
+ GraphEdge(
621
+ source_uid=fn_uid,
622
+ target_uid=_resolve_symbol(type_name, path=normalised, visitor=visitor),
623
+ edge_type="has_param_type",
624
+ extractor=EXTRACTOR_ID,
625
+ confidence=_annotation_confidence(type_name, visitor),
626
+ source_span=normalised,
627
+ evidence=(EvidenceSignal("ast_annotation", 0.9),),
628
+ )
629
+ )
630
+ for fn_uid, type_name in visitor.return_types:
631
+ result.edges.append(
632
+ GraphEdge(
633
+ source_uid=fn_uid,
634
+ target_uid=_resolve_symbol(type_name, path=normalised, visitor=visitor),
635
+ edge_type="returns_type",
636
+ extractor=EXTRACTOR_ID,
637
+ confidence=_annotation_confidence(type_name, visitor),
638
+ source_span=normalised,
639
+ evidence=(EvidenceSignal("ast_annotation", 0.9),),
640
+ )
641
+ )
642
+ for field_stub, type_name in visitor.field_types:
643
+ result.edges.append(
644
+ GraphEdge(
645
+ source_uid=field_stub,
646
+ target_uid=_resolve_symbol(type_name, path=normalised, visitor=visitor),
647
+ edge_type="field_of_type",
648
+ extractor=EXTRACTOR_ID,
649
+ confidence=_annotation_confidence(type_name, visitor),
650
+ source_span=normalised,
651
+ evidence=(EvidenceSignal("ast_annotation", 0.9),),
652
+ )
653
+ )
654
+
655
+ # Decorators — is_decorated_by.
656
+ decorator_signal = (
657
+ "tree_sitter_decorator"
658
+ if heritage_extractor_id == EXTRACTOR_ID_TS_IMPORTS
659
+ else "ast_decorator"
660
+ )
661
+ for decorated_uid, dec_name in visitor.decorators_edges:
662
+ result.edges.append(
663
+ GraphEdge(
664
+ source_uid=decorated_uid,
665
+ target_uid=_resolve_symbol(dec_name, path=normalised, visitor=visitor),
666
+ edge_type="is_decorated_by",
667
+ extractor=heritage_extractor_id,
668
+ confidence=_decorator_confidence(dec_name, visitor),
669
+ evidence=(EvidenceSignal(decorator_signal, 0.9),),
670
+ )
671
+ )
672
+
673
+ # Imports.
674
+ for imp in visitor.imports:
675
+ # E2 fix: drop {imp.line} from UID so blank-line insertion above
676
+ # an import doesn't spawn a duplicate node. Line is still carried
677
+ # in start_line.
678
+ imp_uid = f"code:import:{normalised}::{imp.local_name}"
679
+ result.nodes.append(
680
+ GraphNode(
681
+ uid=imp_uid,
682
+ kind="code:import",
683
+ label=f"import {imp.local_name}",
684
+ file_path=normalised,
685
+ start_line=imp.line,
686
+ lang="py",
687
+ metadata={
688
+ "source_module": imp.source_module,
689
+ "imported": imp.imported,
690
+ "wildcard": imp.is_wildcard,
691
+ "extractor": import_extractor_id,
692
+ },
693
+ )
694
+ )
695
+ result.edges.append(
696
+ GraphEdge(
697
+ source_uid=mod_node.uid,
698
+ target_uid=imp_uid,
699
+ edge_type="contains",
700
+ extractor=import_extractor_id,
701
+ confidence=1.0,
702
+ )
703
+ )
704
+ target_mod = imp.source_module or imp.imported
705
+ signal_name = (
706
+ "tree_sitter_import" if import_extractor_id == EXTRACTOR_ID_TS_IMPORTS else "ast_import"
707
+ )
708
+ result.edges.append(
709
+ GraphEdge(
710
+ source_uid=mod_node.uid,
711
+ target_uid=module_uid(target_mod),
712
+ edge_type="imports",
713
+ extractor=import_extractor_id,
714
+ confidence=0.9,
715
+ source_span=f"{normalised}:{imp.line}",
716
+ evidence=(EvidenceSignal(signal_name, 0.9),),
717
+ )
718
+ )
719
+ # R3: wildcard `from .X import *` is also a re-export from the
720
+ # current module's surface — emit an explicit re_exports edge so
721
+ # consumers see what this module redistributes.
722
+ if imp.is_wildcard:
723
+ result.edges.append(
724
+ GraphEdge(
725
+ source_uid=mod_node.uid,
726
+ target_uid=module_uid(target_mod),
727
+ edge_type="re_exports",
728
+ extractor=import_extractor_id,
729
+ confidence=0.9,
730
+ source_span=f"{normalised}:{imp.line}",
731
+ evidence=(EvidenceSignal("wildcard_import", 0.9),),
732
+ )
733
+ )
734
+
735
+ # Calls. These are the hardest — the pure-Python baseline uses the
736
+ # 3-step subset of the 7-step lookup (same-scope, enclosing-scope,
737
+ # explicit-import). Unresolved references get confidence 0.3 and an
738
+ # `unresolved_call` evidence signal so the LSP overlay can lift
739
+ # them to 0.95 later without double-writing.
740
+ for call in visitor.calls:
741
+ confidence, evidence, resolved_uid = _resolve_call(call, visitor=visitor, path=normalised)
742
+ if resolved_uid is None:
743
+ continue
744
+ # E5: `await X()` — emit `awaits` instead of `calls`.
745
+ # E11: name-only `Foo()` heuristic over-tags `Path()` / `Counter()`
746
+ # as `constructs`. Promote to `constructs` only when resolved
747
+ # target is a real `code:class:*` node; demote otherwise.
748
+ if call.is_await:
749
+ edge_type = "awaits"
750
+ elif call.is_constructor_like and resolved_uid.startswith("code:class:"):
751
+ edge_type = "constructs"
752
+ else:
753
+ edge_type = "calls"
754
+ result.edges.append(
755
+ GraphEdge(
756
+ source_uid=call.caller_uid,
757
+ target_uid=resolved_uid,
758
+ edge_type=edge_type,
759
+ extractor=EXTRACTOR_ID,
760
+ confidence=confidence,
761
+ source_span=f"{normalised}:{call.line}",
762
+ evidence=evidence,
763
+ )
764
+ )
765
+ # E6: dispatches — when a call arg is a known function uid the
766
+ # caller is dispatching that fn (registry.register(fn) etc.).
767
+ # Emit secondary `dispatches` edges; confidence 0.8 (heuristic
768
+ # but only fires on local resolved symbols).
769
+ for dispatched_uid in call.dispatched_uids:
770
+ result.edges.append(
771
+ GraphEdge(
772
+ source_uid=call.caller_uid,
773
+ target_uid=dispatched_uid,
774
+ edge_type="dispatches",
775
+ extractor=EXTRACTOR_ID,
776
+ confidence=0.8,
777
+ source_span=f"{normalised}:{call.line}",
778
+ evidence=(EvidenceSignal("callable_arg", 0.8),),
779
+ )
780
+ )
781
+
782
+ # S3: Folder→...→File CONTAINS spine (idempotent via uid).
783
+ emit_contains_spine(
784
+ file_path=path,
785
+ file_uid_=file_node.uid,
786
+ result=result,
787
+ extractor_id=EXTRACTOR_ID,
788
+ )
789
+
790
+ # S3: File→Class / File→Function / Class→Method ``contains`` edges.
791
+ # The AST visitor already wires Module→decl and Class→Method; we
792
+ # add File→Class, File→Function, and File→Method(top-level) so the
793
+ # tree-view has a direct spine that bypasses the module node. These
794
+ # are idempotent thanks to the backend's (source,target,edge_type,
795
+ # extractor) uniqueness constraint.
796
+ file_uid_str = file_node.uid
797
+ for decl in visitor.decls:
798
+ if (decl.kind == "code:class" and decl.parent_uid is None) or (
799
+ decl.kind == "code:function" and decl.parent_uid is None
800
+ ):
801
+ result.edges.append(
802
+ GraphEdge(
803
+ source_uid=file_uid_str,
804
+ target_uid=decl.uid,
805
+ edge_type="contains",
806
+ extractor=EXTRACTOR_ID,
807
+ confidence=1.0,
808
+ )
809
+ )
810
+
811
+ _promote_stubs(result)
812
+ return result
813
+
814
+
815
+ # ---------------------------------------------------------------------------
816
+ # AST visitor
817
+ # ---------------------------------------------------------------------------
818
+
819
+
820
+ class _PythonVisitor(ast.NodeVisitor):
821
+ """Walk an AST once, collecting decls + imports + calls."""
822
+
823
+ def __init__(self, *, path: str, module_name: str, content: str) -> None:
824
+ self.path = path
825
+ self.module_name = module_name
826
+ self.content = content
827
+ self.decls: list[_SymbolDecl] = []
828
+ self.imports: list[_ImportDecl] = []
829
+ self.inherits: list[tuple[str, str]] = []
830
+ self.decorators_edges: list[tuple[str, str]] = []
831
+ self.calls: list[_CallSite] = []
832
+ # type-annotation edges discovered during the AST walk.
833
+ # `param_types` : (function_uid, type_name)
834
+ # `return_types` : (function_uid, type_name)
835
+ # `field_types` : (field_uid, type_name) — field_uid is
836
+ # the per-class field stub `<class_uid>.<name>`.
837
+ self.param_types: list[tuple[str, str]] = []
838
+ self.return_types: list[tuple[str, str]] = []
839
+ self.field_types: list[tuple[str, str]] = []
840
+ # Scope stack: the uid each new call-site counts as living inside.
841
+ self._scope_uid_stack: list[str] = [module_uid(module_name)]
842
+ # Qualname stack: dotted path for nested classes / functions.
843
+ self._qualname_stack: list[str] = []
844
+ # Name -> uid map for same-scope lookup (step 1 of 7-step lookup).
845
+ self.symbols_by_name: dict[str, str] = {}
846
+ # GE: per-class method map so `self.method()` resolves to THIS class's
847
+ # method, not the last same-named method in the file (bare-name collision).
848
+ self.methods_by_class: dict[str, dict[str, str]] = {}
849
+ self.imported_local_names: dict[str, _ImportDecl] = {}
850
+
851
+ # -- import handling ---------------------------------------------------
852
+
853
+ def visit_Import(self, node: ast.Import) -> None:
854
+ for alias in node.names:
855
+ decl = _ImportDecl(
856
+ source_module=None,
857
+ imported=alias.name,
858
+ local_name=alias.asname or alias.name.split(".")[0],
859
+ line=node.lineno,
860
+ )
861
+ self.imports.append(decl)
862
+ self.imported_local_names[decl.local_name] = decl
863
+
864
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
865
+ module = ("." * (node.level or 0)) + (node.module or "")
866
+ for alias in node.names:
867
+ if alias.name == "*":
868
+ self.imports.append(
869
+ _ImportDecl(
870
+ source_module=module,
871
+ imported="*",
872
+ local_name="*",
873
+ line=node.lineno,
874
+ is_wildcard=True,
875
+ )
876
+ )
877
+ continue
878
+ decl = _ImportDecl(
879
+ source_module=module,
880
+ imported=alias.name,
881
+ local_name=alias.asname or alias.name,
882
+ line=node.lineno,
883
+ )
884
+ self.imports.append(decl)
885
+ self.imported_local_names[decl.local_name] = decl
886
+
887
+ # -- class / function / method ----------------------------------------
888
+
889
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
890
+ qualname = self._push_qual(node.name)
891
+ uid = class_uid(self.path, qualname)
892
+ parent_uid = self._scope_uid_stack[-1]
893
+ decl = _SymbolDecl(
894
+ uid=uid,
895
+ kind="code:class",
896
+ name=node.name,
897
+ qualname=qualname,
898
+ line=node.lineno,
899
+ end_line=getattr(node, "end_lineno", None),
900
+ signature=_class_signature(node),
901
+ docstring=ast.get_docstring(node),
902
+ decorators=tuple(_dotted_name(d) for d in node.decorator_list),
903
+ parent_uid=parent_uid if parent_uid != module_uid(self.module_name) else None,
904
+ )
905
+ self.decls.append(decl)
906
+ self.symbols_by_name[node.name] = uid
907
+
908
+ for base in node.bases:
909
+ self.inherits.append((uid, _dotted_name(base)))
910
+ for dec in node.decorator_list:
911
+ self.decorators_edges.append((uid, _dotted_name(dec)))
912
+
913
+ # scan class body for `name: T` and `name: T = default`.
914
+ # Each annotated field becomes a real `code:variable` decl so it
915
+ # appears in the contains tree (parent class → field) instead of
916
+ # surfacing as an orphan stub. The stub UID still anchors the
917
+ # `field_of_type` edge that points at the annotation type.
918
+ for stmt in node.body:
919
+ if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name):
920
+ ann_name = _dotted_name(stmt.annotation)
921
+ field_name = stmt.target.id
922
+ field_stub = f"code:variable:{self.path}::{qualname}.{field_name}"
923
+ if ann_name:
924
+ self.field_types.append((field_stub, ann_name))
925
+ self.decls.append(
926
+ _SymbolDecl(
927
+ uid=field_stub,
928
+ kind="code:variable",
929
+ name=field_name,
930
+ qualname=f"{qualname}.{field_name}",
931
+ line=stmt.lineno,
932
+ end_line=getattr(stmt, "end_lineno", None),
933
+ signature=ann_name or "",
934
+ docstring=None,
935
+ decorators=tuple(),
936
+ parent_uid=uid,
937
+ )
938
+ )
939
+
940
+ self._scope_uid_stack.append(uid)
941
+ try:
942
+ self.generic_visit(node)
943
+ finally:
944
+ self._scope_uid_stack.pop()
945
+ self._pop_qual()
946
+
947
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
948
+ self._visit_function(node, is_async=False)
949
+
950
+ def visit_AsyncFunctionDef(self, node: ast.AsyncFunctionDef) -> None:
951
+ self._visit_function(node, is_async=True)
952
+
953
+ def _visit_function(self, node: ast.AST, *, is_async: bool) -> None:
954
+ name = node.name # type: ignore[attr-defined]
955
+ qualname = self._push_qual(name)
956
+ in_class = self._scope_uid_stack[-1].startswith("code:class:")
957
+ if in_class:
958
+ uid = method_uid(self.path, qualname)
959
+ kind = "code:method"
960
+ else:
961
+ uid = function_uid(self.path, qualname)
962
+ kind = "code:function"
963
+ parent_uid = self._scope_uid_stack[-1]
964
+ decl = _SymbolDecl(
965
+ uid=uid,
966
+ kind=kind,
967
+ name=name,
968
+ qualname=qualname,
969
+ line=node.lineno, # type: ignore[attr-defined]
970
+ end_line=getattr(node, "end_lineno", None),
971
+ signature=_function_signature(node, is_async=is_async),
972
+ docstring=ast.get_docstring(node), # type: ignore[arg-type]
973
+ decorators=tuple(_dotted_name(d) for d in node.decorator_list), # type: ignore[attr-defined]
974
+ parent_uid=parent_uid if parent_uid != module_uid(self.module_name) else None,
975
+ is_method=in_class,
976
+ )
977
+ self.decls.append(decl)
978
+ self.symbols_by_name[name] = uid
979
+ if in_class:
980
+ self.methods_by_class.setdefault(parent_uid, {})[name] = uid
981
+
982
+ for dec in node.decorator_list: # type: ignore[attr-defined]
983
+ self.decorators_edges.append((uid, _dotted_name(dec)))
984
+
985
+ # collect param + return type annotations.
986
+ for ann_name in _function_param_annotations(node):
987
+ self.param_types.append((uid, ann_name))
988
+ ret_ann = _function_return_annotation(node)
989
+ if ret_ann:
990
+ self.return_types.append((uid, ret_ann))
991
+
992
+ self._scope_uid_stack.append(uid)
993
+ try:
994
+ # Walk the body for two things: nested decls (visit them so we
995
+ # emit code:function / code:method nodes with full qualnames)
996
+ # AND Call nodes (emit call edges scoped to this function).
997
+ for child in node.body: # type: ignore[attr-defined]
998
+ if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
999
+ self.visit(child)
1000
+ else:
1001
+ self._walk_calls(child)
1002
+ finally:
1003
+ self._scope_uid_stack.pop()
1004
+ self._pop_qual()
1005
+
1006
+ def _walk_calls(self, node: ast.AST) -> None:
1007
+ # E5/E6: track parent ast.Await so we can emit `awaits` instead
1008
+ # of `calls`. ast.walk loses parent info → walk with our own
1009
+ # stack that records the immediate parent type.
1010
+ stack: list[tuple[ast.AST, ast.AST | None]] = [(node, None)]
1011
+ while stack:
1012
+ sub, parent = stack.pop()
1013
+ # Function-local / nested-block imports must register so
1014
+ # call-site resolution can rewrite a bare `init_db()` call
1015
+ # into `code:external:database:init_db` which
1016
+ # `link_external_stubs` then promotes to the canonical uid.
1017
+ if isinstance(sub, ast.Import):
1018
+ self.visit_Import(sub)
1019
+ elif isinstance(sub, ast.ImportFrom):
1020
+ self.visit_ImportFrom(sub)
1021
+ elif isinstance(sub, ast.Call):
1022
+ # R2: skip when target is method-access on a literal
1023
+ # (`{'a': 'b'}.get(...)` → bogus unresolved identifier).
1024
+ func = sub.func
1025
+ if isinstance(func, ast.Attribute) and isinstance(
1026
+ func.value,
1027
+ (ast.Dict, ast.Set, ast.List, ast.Tuple, ast.JoinedStr),
1028
+ ):
1029
+ # Still descend into args
1030
+ for child in ast.iter_child_nodes(sub):
1031
+ stack.append((child, sub))
1032
+ continue
1033
+ target = _dotted_name(func)
1034
+ if not target:
1035
+ for child in ast.iter_child_nodes(sub):
1036
+ stack.append((child, sub))
1037
+ continue
1038
+ last_segment = target.split(".")[-1]
1039
+ is_ctor = last_segment[:1].isupper()
1040
+ # E6: collect any args that resolve to known function uids
1041
+ # in this file's symbols_by_name — these are dispatched fns.
1042
+ dispatched: list[str] = []
1043
+ for arg in sub.args:
1044
+ if isinstance(arg, ast.Name) and arg.id in self.symbols_by_name:
1045
+ resolved = self.symbols_by_name[arg.id]
1046
+ if resolved.startswith(("code:function:", "code:method:")):
1047
+ dispatched.append(resolved)
1048
+ encl_class = next(
1049
+ (u for u in reversed(self._scope_uid_stack) if u.startswith("code:class:")),
1050
+ None,
1051
+ )
1052
+ self.calls.append(
1053
+ _CallSite(
1054
+ caller_uid=self._scope_uid_stack[-1],
1055
+ callee_name=last_segment,
1056
+ full_expr=target,
1057
+ line=sub.lineno,
1058
+ is_constructor_like=is_ctor,
1059
+ is_await=isinstance(parent, ast.Await),
1060
+ dispatched_uids=tuple(dispatched),
1061
+ enclosing_class_uid=encl_class,
1062
+ )
1063
+ )
1064
+ for child in ast.iter_child_nodes(sub):
1065
+ stack.append((child, sub))
1066
+ continue
1067
+ for child in ast.iter_child_nodes(sub):
1068
+ stack.append((child, sub))
1069
+
1070
+ # -- qualname stack helpers --------------------------------------------
1071
+
1072
+ def _push_qual(self, name: str) -> str:
1073
+ self._qualname_stack.append(name)
1074
+ return ".".join(self._qualname_stack)
1075
+
1076
+ def _pop_qual(self) -> None:
1077
+ self._qualname_stack.pop()
1078
+
1079
+
1080
+ # ---------------------------------------------------------------------------
1081
+ # Resolution helpers
1082
+ # ---------------------------------------------------------------------------
1083
+
1084
+
1085
+ def _absolute_module_for(source_module: str | None, *, path: str) -> str:
1086
+ if not source_module:
1087
+ return ""
1088
+ if not source_module.startswith("."):
1089
+ return source_module
1090
+ file_module = _module_name_for_path(path)
1091
+ file_parts = file_module.split(".") if file_module != "__root__" else []
1092
+ leading = len(source_module) - len(source_module.lstrip("."))
1093
+ tail = source_module.lstrip(".")
1094
+ base = file_parts[:-leading] if leading <= len(file_parts) else []
1095
+ if tail:
1096
+ base = base + tail.split(".")
1097
+ return ".".join(base)
1098
+
1099
+
1100
+ def _resolve_symbol(name: str, *, path: str, visitor: _PythonVisitor) -> str:
1101
+ root = name.split(".")[0]
1102
+ if root in visitor.symbols_by_name:
1103
+ return visitor.symbols_by_name[root]
1104
+ imp = visitor.imported_local_names.get(root)
1105
+ if imp is not None:
1106
+ target_mod = _absolute_module_for(imp.source_module, path=path) or imp.imported
1107
+ return f"code:external:{target_mod}:{imp.imported}"
1108
+ return f"code:external:unresolved:{name}"
1109
+
1110
+
1111
+ # Dotted-name shape an unresolved-call stub may carry — anything else is an
1112
+ # over-captured expression, not an identifier (TASK-405).
1113
+ _IDENTIFIER_EXPR_RE = re.compile(r"[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*")
1114
+
1115
+
1116
+ def _resolve_call(
1117
+ call: _CallSite,
1118
+ *,
1119
+ visitor: _PythonVisitor,
1120
+ path: str,
1121
+ ) -> tuple[float, tuple[EvidenceSignal, ...], str | None]:
1122
+ """Return (confidence, evidence, resolved_uid) for a call-site.
1123
+
1124
+ E4 calibration (audit: was 0.3-0.5 ceiling; 58.7% of calls at 0.3):
1125
+ same_scope (real fn in this file) → 1.0 (AST-certain)
1126
+ explicit_import (resolved to mod) → 0.9 (origin known)
1127
+ unresolved → 0.3 (best-effort stub)
1128
+ """
1129
+ signals: list[EvidenceSignal] = []
1130
+ confidence = 0.0
1131
+
1132
+ # GE: `self.method()` / `cls.method()` → resolve to the ENCLOSING class's
1133
+ # method, not the bare-name match (which picks the last same-named method
1134
+ # in the file — a wrong-target collision). Falls through for inherited /
1135
+ # attribute access not defined on this class.
1136
+ if call.enclosing_class_uid and (
1137
+ call.full_expr.startswith("self.") or call.full_expr.startswith("cls.")
1138
+ ):
1139
+ own_methods = visitor.methods_by_class.get(call.enclosing_class_uid, {})
1140
+ if call.callee_name in own_methods:
1141
+ return (0.95, (EvidenceSignal("self_method", 0.95),), own_methods[call.callee_name])
1142
+
1143
+ if call.callee_name in visitor.symbols_by_name:
1144
+ signals.append(EvidenceSignal("same_scope", 1.0))
1145
+ confidence = 1.0
1146
+ resolved = visitor.symbols_by_name[call.callee_name]
1147
+ elif call.callee_name in visitor.imported_local_names:
1148
+ imp = visitor.imported_local_names[call.callee_name]
1149
+ signals.append(EvidenceSignal("explicit_import", 0.9, note=imp.source_module))
1150
+ confidence = 0.9
1151
+ target_mod = _absolute_module_for(imp.source_module, path=path) or imp.imported
1152
+ resolved = f"code:external:{target_mod}:{imp.imported}"
1153
+ elif "." in call.full_expr and call.full_expr.split(".")[0] in visitor.imported_local_names:
1154
+ root = call.full_expr.split(".")[0]
1155
+ imp = visitor.imported_local_names[root]
1156
+ signals.append(EvidenceSignal("explicit_import", 0.9, note=imp.source_module))
1157
+ confidence = 0.9
1158
+ tail = ".".join(call.full_expr.split(".")[1:])
1159
+ # The alias is a MODULE: `from pkg import mod as g` → module pkg.mod;
1160
+ # `import pkg.mod as g` → module imp.imported. Resolving the attribute
1161
+ # (`g.func`) to <module>:func lets link_external_stubs bind it to the
1162
+ # real function instead of dropping the call on the bare package —
1163
+ # this is what made rename/references miss `g.func()` sites.
1164
+ abs_source = _absolute_module_for(imp.source_module, path=path)
1165
+ root_module = f"{abs_source}.{imp.imported}" if abs_source else imp.imported
1166
+ resolved = f"code:external:{root_module}:{tail}"
1167
+ else:
1168
+ # An "identifier" stub must be identifier-shaped (dotted names only).
1169
+ # Complex receivers (`(a or b / 'x').resolve`) used to mint
1170
+ # expression-shaped stubs — 956 junk rows that nothing can ever
1171
+ # link (TASK-405). Skip the edge entirely; the LSP overlay can
1172
+ # still resolve such sites later.
1173
+ if not _IDENTIFIER_EXPR_RE.fullmatch(call.full_expr or ""):
1174
+ return (0.0, tuple(signals), None)
1175
+ signals.append(EvidenceSignal("unresolved_call", 0.3))
1176
+ confidence = 0.3
1177
+ resolved = f"code:external:unresolved:{call.full_expr}"
1178
+
1179
+ return (round(min(confidence, 1.0), 4), tuple(signals), resolved)
1180
+
1181
+
1182
+ def _inherit_confidence(base_name: str, visitor: _PythonVisitor) -> float:
1183
+ if base_name.split(".")[0] in visitor.symbols_by_name:
1184
+ return 0.95
1185
+ if base_name.split(".")[0] in visitor.imported_local_names:
1186
+ return 0.8
1187
+ return 0.5
1188
+
1189
+
1190
+ def _decorator_confidence(name: str, visitor: _PythonVisitor) -> float:
1191
+ if name.split(".")[0] in visitor.symbols_by_name:
1192
+ return 0.9
1193
+ if name.split(".")[0] in visitor.imported_local_names:
1194
+ return 0.85
1195
+ return 0.6
1196
+
1197
+
1198
+ def _annotation_confidence(type_name: str, visitor: _PythonVisitor) -> float:
1199
+ """Confidence for has_param_type / returns_type / field_of_type edges.
1200
+
1201
+ Mirrors `_inherit_confidence` but slightly stricter: same-scope
1202
+ binding wins (0.95 — directly observed declaration), explicit
1203
+ import next (0.85 — origin is known but not the symbol body),
1204
+ builtin / unresolved fall back to 0.3 so consumers know the edge
1205
+ is best-effort.
1206
+ """
1207
+ head = type_name.split(".")[0]
1208
+ if head in visitor.symbols_by_name:
1209
+ return 0.95
1210
+ if head in visitor.imported_local_names:
1211
+ return 0.85
1212
+ if head in _BUILTIN_TYPES:
1213
+ # Edges to bare builtins are still useful (UI heat-maps), but
1214
+ # they should not win against any user-resolved type.
1215
+ return 0.7
1216
+ return 0.3
1217
+
1218
+
1219
+ _BUILTIN_TYPES: frozenset[str] = frozenset(
1220
+ {
1221
+ "str",
1222
+ "int",
1223
+ "float",
1224
+ "bool",
1225
+ "bytes",
1226
+ "list",
1227
+ "dict",
1228
+ "tuple",
1229
+ "set",
1230
+ "frozenset",
1231
+ "None",
1232
+ "Any",
1233
+ "object",
1234
+ "type",
1235
+ "complex",
1236
+ "range",
1237
+ "memoryview",
1238
+ "bytearray",
1239
+ "Path",
1240
+ "datetime",
1241
+ "date",
1242
+ "time",
1243
+ "timedelta",
1244
+ "Decimal",
1245
+ }
1246
+ )
1247
+
1248
+
1249
+ # ---------------------------------------------------------------------------
1250
+ # Misc helpers
1251
+ # ---------------------------------------------------------------------------
1252
+
1253
+
1254
+ def _module_docstring(content: str) -> str | None:
1255
+ try:
1256
+ tree = ast.parse(content)
1257
+ except SyntaxError:
1258
+ return None
1259
+ return ast.get_docstring(tree)
1260
+
1261
+
1262
+ def _module_name_for_path(path: str) -> str:
1263
+ """Derive a dotted module name from a file path.
1264
+
1265
+ Resolution order:
1266
+ 1. Active ToolchainContext (TASK-082): when pyproject.toml /
1267
+ setup.cfg declare a non-standard package root (e.g.
1268
+ ``[tool.poetry.packages] include="myapp" from="packages"``),
1269
+ honour it so `packages/myapp/auth.py` → `myapp.auth`.
1270
+ 2. Hard-coded ``src/`` / ``core/`` strip — keeps coding-os and
1271
+ most src-layout projects working without a config file.
1272
+ 3. Fall through: full POSIX path with `.py` and `__init__` removed.
1273
+ """
1274
+ parts = [p for p in _normalize_path(path).split("/") if p]
1275
+ if parts and parts[-1].endswith(".py"):
1276
+ parts[-1] = parts[-1][: -len(".py")]
1277
+ if parts and parts[-1] == "__init__":
1278
+ parts.pop()
1279
+
1280
+ # 1. Toolchain-driven package root.
1281
+ rebased = _toolchain_python_module_parts(parts)
1282
+ if rebased is not None:
1283
+ return ".".join(rebased) or "__root__"
1284
+
1285
+ # 2. Default repo-root shims.
1286
+ if parts and parts[0] in {"src", "core"}:
1287
+ parts = parts[1:]
1288
+ return ".".join(parts) or "__root__"
1289
+
1290
+
1291
+ def _toolchain_python_module_parts(parts: list[str]) -> list[str] | None:
1292
+ """Try to rebase a file's path-parts under a known Python package
1293
+ root from the active ToolchainContext. Returns the rebased parts
1294
+ (e.g. ``["myapp", "auth"]``) or None when no package root matches.
1295
+ """
1296
+ try:
1297
+ from ..toolchain import get_active
1298
+ except ImportError:
1299
+ return None
1300
+ ctx = get_active()
1301
+ if ctx is None:
1302
+ return None
1303
+ if not ctx.python_packages:
1304
+ return None
1305
+ flat = "/".join(parts)
1306
+ # Match longest root first so nested packages win over shallow ones.
1307
+ for pkg_name, pkg_root in sorted(ctx.python_packages.items(), key=lambda kv: -len(kv[1])):
1308
+ rel_root = pkg_root.strip("/").replace("\\", "/")
1309
+ if not rel_root:
1310
+ continue
1311
+ prefix = f"{rel_root}/"
1312
+ if flat.startswith(prefix):
1313
+ tail = flat[len(prefix) :]
1314
+ tail_parts = [p for p in tail.split("/") if p]
1315
+ return [pkg_name, *tail_parts]
1316
+ if flat == rel_root:
1317
+ return [pkg_name]
1318
+ return None
1319
+
1320
+
1321
+ def _hash_decl(decl: _SymbolDecl) -> str:
1322
+ key = f"{decl.kind}|{decl.uid}|{decl.signature}|{decl.decorators}"
1323
+ return hashlib.sha1(key.encode("utf-8")).hexdigest()[:12]
1324
+
1325
+
1326
+ def _class_signature(node: ast.ClassDef) -> str:
1327
+ bases = ", ".join(_dotted_name(b) for b in node.bases)
1328
+ return f"class {node.name}({bases})" if bases else f"class {node.name}"
1329
+
1330
+
1331
+ def _function_param_annotations(node: ast.AST) -> list[str]:
1332
+ """Return the annotation names for every annotated parameter of
1333
+ ``node`` (a FunctionDef / AsyncFunctionDef).
1334
+
1335
+ Skips `self` and `cls` because their annotation is uninteresting
1336
+ (it would always be the surrounding class), and skips parameters
1337
+ with no annotation. Union-style annotations expand to one entry
1338
+ per branch so each receiver type emits its own edge.
1339
+ """
1340
+ args = getattr(node, "args", None)
1341
+ if args is None:
1342
+ return []
1343
+ out: list[str] = []
1344
+ pos_args = list(getattr(args, "posonlyargs", []) or []) + list(getattr(args, "args", []) or [])
1345
+ for idx, arg in enumerate(pos_args):
1346
+ if idx == 0 and arg.arg in ("self", "cls"):
1347
+ continue
1348
+ if arg.annotation is None:
1349
+ continue
1350
+ out.extend(_expand_annotation(arg.annotation))
1351
+ for arg in getattr(args, "kwonlyargs", []) or []:
1352
+ if arg.annotation is None:
1353
+ continue
1354
+ out.extend(_expand_annotation(arg.annotation))
1355
+ if getattr(args, "vararg", None) is not None and args.vararg.annotation is not None:
1356
+ out.extend(_expand_annotation(args.vararg.annotation))
1357
+ if getattr(args, "kwarg", None) is not None and args.kwarg.annotation is not None:
1358
+ out.extend(_expand_annotation(args.kwarg.annotation))
1359
+ return out
1360
+
1361
+
1362
+ def _function_return_annotation(node: ast.AST) -> str | None:
1363
+ ret = getattr(node, "returns", None)
1364
+ if ret is None:
1365
+ return None
1366
+ flat = _expand_annotation(ret)
1367
+ return flat[0] if flat else None
1368
+
1369
+
1370
+ def _expand_annotation(node: ast.AST) -> list[str]:
1371
+ """Return one or more dotted names for an annotation node.
1372
+
1373
+ `Foo` → ["Foo"]
1374
+ `pkg.Foo` → ["pkg.Foo"]
1375
+ `Optional[Foo]` → ["Foo"] (None branch dropped)
1376
+ `Union[Foo, Bar]` → ["Foo", "Bar"]
1377
+ `Foo | Bar` → ["Foo", "Bar"]
1378
+ `list[Foo]` → ["Foo"] (container stripped)
1379
+ `Literal["x"]` → [] (value-only types skipped)
1380
+
1381
+ Anything we can't recognise yields an empty list — caller will then
1382
+ skip the edge entirely rather than emit a stub with bad confidence.
1383
+ """
1384
+ if isinstance(node, ast.Name):
1385
+ return [node.id]
1386
+ if isinstance(node, ast.Attribute):
1387
+ dotted = _dotted_name(node)
1388
+ return [dotted] if dotted else []
1389
+ if isinstance(node, ast.Constant):
1390
+ # `None` annotation → drop (often appears via Optional[...]).
1391
+ if node.value is None:
1392
+ return []
1393
+ # Forward reference as string literal: `"Foo"`.
1394
+ if isinstance(node.value, str):
1395
+ return [node.value]
1396
+ return []
1397
+ if isinstance(node, ast.BinOp) and isinstance(node.op, ast.BitOr):
1398
+ # PEP 604 unions: `Foo | Bar | None`.
1399
+ return _expand_annotation(node.left) + _expand_annotation(node.right)
1400
+ if isinstance(node, ast.Subscript):
1401
+ # Container subscripts: `list[Foo]`, `Optional[Foo]`, `Union[Foo, Bar]`,
1402
+ # `dict[str, Foo]`. Walk inside and recurse.
1403
+ value_name = (
1404
+ _dotted_name(node.value) if isinstance(node.value, (ast.Name, ast.Attribute)) else ""
1405
+ )
1406
+ slice_node = getattr(node, "slice", None)
1407
+ if slice_node is None:
1408
+ return []
1409
+ # Generic strip — unwrap the container, recurse on the slice.
1410
+ if isinstance(slice_node, ast.Tuple):
1411
+ return [t for elt in slice_node.elts for t in _expand_annotation(elt)]
1412
+ # Special case for `Literal[...]` — types-by-value, not by-class.
1413
+ if value_name == "Literal":
1414
+ return []
1415
+ return _expand_annotation(slice_node)
1416
+ if isinstance(node, ast.Tuple):
1417
+ return [t for elt in node.elts for t in _expand_annotation(elt)]
1418
+ return []
1419
+
1420
+
1421
+ def _function_signature(node: ast.AST, *, is_async: bool) -> str:
1422
+ try:
1423
+ args = ast.unparse(node.args) # type: ignore[attr-defined]
1424
+ except (AttributeError, TypeError):
1425
+ args = ""
1426
+ prefix = "async def" if is_async else "def"
1427
+ return f"{prefix} {node.name}({args})" # type: ignore[attr-defined]
1428
+
1429
+
1430
+ def _dotted_name(node: ast.AST) -> str:
1431
+ """Best-effort string form of an expression node (Name / Attribute / Call)."""
1432
+ if isinstance(node, ast.Name):
1433
+ return node.id
1434
+ if isinstance(node, ast.Attribute):
1435
+ return f"{_dotted_name(node.value)}.{node.attr}"
1436
+ if isinstance(node, ast.Call):
1437
+ return _dotted_name(node.func)
1438
+ if isinstance(node, ast.Subscript):
1439
+ return _dotted_name(node.value)
1440
+ try:
1441
+ return ast.unparse(node)
1442
+ except Exception:
1443
+ return "<expr>"
1444
+
1445
+
1446
+ __all__ = [
1447
+ "EXTRACTOR_ID",
1448
+ "class_uid",
1449
+ "extract",
1450
+ "file_uid",
1451
+ "function_uid",
1452
+ "method_uid",
1453
+ "module_uid",
1454
+ ]