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,967 @@
1
+ """board_os workflow engine — state machine + WIP enforcement.
2
+
3
+ One module, one SSOT for:
4
+ - Valid status transitions (8-state machine, plan §6.4)
5
+ - WIP cap enforcement (plan §3 P-L-3, config-driven)
6
+ - Atomic frontmatter writes (temp file + rename)
7
+ - task_status_history auditing
8
+ - Optimistic concurrency (plan §17, R-L-29 DFS cycle check)
9
+
10
+ Public API:
11
+ transition(conn, task_id, to_status, *, reason, agent_session,
12
+ expected_from=None, bypass_wip=False) -> TransitionResult
13
+ check_wip(conn, config) -> WipState
14
+ validate_dependencies_no_cycle(conn, task_id, new_deps) -> list[str]
15
+ patch_task_frontmatter_scalars(path, updates) -> None
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import logging
22
+ import os
23
+ import sqlite3
24
+ import tempfile
25
+ import time
26
+ from dataclasses import dataclass, field
27
+ from pathlib import Path
28
+
29
+ import yaml
30
+
31
+ from board_os.config import READY_LABEL, STATUS_ENUM, ScrumbanConfig
32
+
33
+ logger = logging.getLogger("coding_os.board_os.workflow")
34
+
35
+
36
+ # Valid transition edges: {from_status: {to_statuses}}
37
+ # "ready" was a dedicated column in earlier versions — it has been folded
38
+ # into an `icebox + "ready" label` combination so the board has one less
39
+ # queue and "ready" becomes something the agent tags rather than a
40
+ # destination to drag tasks into. Any legacy task still carrying
41
+ # status='ready' is migrated to 'icebox' via _migrate_v19_drop_ready_status.
42
+ # archive is *soft-terminal*: the only way out is back to icebox or complete,
43
+ # which is how a user recovers from an accidental archive. Any other target
44
+ # requires an explicit --force flag (workflow.transition(..., force=True)) so
45
+ # mis-clicks still surface an error, but a human can always self-correct.
46
+ _VALID_TRANSITIONS: dict[str, set[str]] = {
47
+ "icebox": {"in_progress", "emergency", "archive"},
48
+ "emergency": {"in_progress", "icebox"},
49
+ "in_progress": {"testing", "blocked", "icebox", "emergency", "complete"},
50
+ "testing": {"complete", "in_progress", "blocked"},
51
+ "complete": {"archive"},
52
+ "blocked": {"in_progress", "emergency", "icebox"},
53
+ "archive": {"icebox", "complete"}, # un-archive paths (see note above)
54
+ }
55
+
56
+ # Statuses that count toward the WIP cap for a given column.
57
+ _WIP_COLUMN_MAP: dict[str, str] = {
58
+ "in_progress": "in_progress",
59
+ "testing": "testing",
60
+ "emergency": "emergency",
61
+ }
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class TransitionResult:
66
+ ok: bool
67
+ task_id: str
68
+ previous_status: str | None
69
+ new_status: str
70
+ warnings: tuple[str, ...] = field(default_factory=tuple)
71
+ wip_state: dict[str, int] = field(default_factory=dict)
72
+ error: str | None = None
73
+ error_category: str | None = None
74
+
75
+
76
+ @dataclass(frozen=True)
77
+ class WipState:
78
+ """Current WIP counts vs. configured caps per column."""
79
+
80
+ counts: dict[str, int]
81
+ caps: dict[str, int]
82
+ violations: tuple[str, ...]
83
+
84
+ def violates(self, column: str) -> bool:
85
+ cap = self.caps.get(column)
86
+ return cap is not None and self.counts.get(column, 0) >= cap
87
+
88
+
89
+ class TransitionError(ValueError):
90
+ """Raised on invalid transitions. Carries suggested paths."""
91
+
92
+ def __init__(
93
+ self,
94
+ message: str,
95
+ *,
96
+ task_id: str,
97
+ from_status: str,
98
+ to_status: str,
99
+ suggested: list[str] | None = None,
100
+ ) -> None:
101
+ self.task_id = task_id
102
+ self.from_status = from_status
103
+ self.to_status = to_status
104
+ self.suggested = suggested or []
105
+ super().__init__(message)
106
+
107
+
108
+ # ---------- Public API ----------
109
+
110
+
111
+ def _is_shared_pid_session(session: str | None) -> bool:
112
+ # resolve_agent_session's last-resort synthetic is ses-<agent>-pid<PID>.
113
+ # For the long-lived MCP server that PID is shared by ALL panels, so a
114
+ # per-session cap keyed on it is NOT panel-isolated.
115
+ if not session or not session.startswith("ses-"):
116
+ return False
117
+ idx = session.rfind("-pid")
118
+ return idx != -1 and session[idx + len("-pid") :].isdigit()
119
+
120
+
121
+ def check_wip(
122
+ conn: sqlite3.Connection,
123
+ config: ScrumbanConfig,
124
+ *,
125
+ agent_session: str | None = None,
126
+ ) -> WipState:
127
+ # in_progress is a per-worker focus cap: when per_session_wip is on
128
+ # and a session is known, count only that session's in_progress
129
+ # tasks so concurrent sessions don't block each other on a global
130
+ # cap. testing / emergency stay board-global (queue / SEV limits).
131
+ per_session = bool(config.workflow_policy.per_session_wip and agent_session)
132
+ if per_session and _is_shared_pid_session(agent_session):
133
+ # Attribution fell back to the shared MCP-server PID synthetic — surface
134
+ # it rather than silently applying an in_progress cap that is shared
135
+ # across sibling panels instead of being per-panel.
136
+ logger.warning(
137
+ "per-session WIP cap degraded: agent_session %r is a shared "
138
+ "ses-<agent>-pid<PID> synthetic (panel attribution unresolved); "
139
+ "the in_progress cap is shared across sibling panels, not per-panel.",
140
+ agent_session,
141
+ )
142
+ counts: dict[str, int] = {}
143
+ for status in _WIP_COLUMN_MAP.values():
144
+ if per_session and status == "in_progress":
145
+ row = conn.execute(
146
+ "SELECT COUNT(*) FROM tasks WHERE status = ? AND agent_session = ?",
147
+ (status, agent_session),
148
+ ).fetchone()
149
+ else:
150
+ row = conn.execute("SELECT COUNT(*) FROM tasks WHERE status = ?", (status,)).fetchone()
151
+ counts[status] = int(row[0]) if row else 0
152
+ caps = {
153
+ "in_progress": config.wip_limits.in_progress,
154
+ "testing": config.wip_limits.testing,
155
+ "emergency": config.wip_limits.emergency,
156
+ }
157
+ violations = tuple(col for col in caps if counts.get(col, 0) > caps[col])
158
+ return WipState(counts=counts, caps=caps, violations=violations)
159
+
160
+
161
+ def _has_task_dependencies_table(conn: sqlite3.Connection) -> bool:
162
+ return (
163
+ conn.execute(
164
+ "SELECT 1 FROM sqlite_master WHERE type='table' AND name='task_dependencies'"
165
+ ).fetchone()
166
+ is not None
167
+ )
168
+
169
+
170
+ def incomplete_dependencies(conn: sqlite3.Connection, task_id: str) -> list[str]:
171
+ """Return the depends_on ids of `task_id` whose status is not 'complete'.
172
+
173
+ Reuses the same junction-then-JSON-column resolution as
174
+ tools.tasks.task_dependencies: on a v35+ DB it joins the indexed
175
+ task_dependencies junction; otherwise it reads the JSON `dependencies`
176
+ column. A dep id that has no matching tasks row (never synced) counts as
177
+ incomplete so a dangling prerequisite can't silently unblock a pull.
178
+ """
179
+ if _has_task_dependencies_table(conn):
180
+ try:
181
+ rows = conn.execute(
182
+ "SELECT d.depends_on, t.status "
183
+ "FROM task_dependencies d "
184
+ "LEFT JOIN tasks t ON t.task_id = d.depends_on "
185
+ "WHERE d.task_id = ? ORDER BY d.depends_on ASC",
186
+ (task_id,),
187
+ ).fetchall()
188
+ return [str(dep) for dep, status in rows if status != "complete"]
189
+ except sqlite3.OperationalError as exc:
190
+ logger.debug("incomplete_dependencies junction failed, JSON fallback: %s", exc)
191
+
192
+ row = conn.execute("SELECT dependencies FROM tasks WHERE task_id = ?", (task_id,)).fetchone()
193
+ if row is None or not row[0]:
194
+ return []
195
+ try:
196
+ dep_ids = [str(d) for d in json.loads(row[0])]
197
+ except (json.JSONDecodeError, TypeError):
198
+ return []
199
+ if not dep_ids:
200
+ return []
201
+ placeholders = ",".join("?" * len(dep_ids))
202
+ status_by_dep = {
203
+ str(r[0]): str(r[1])
204
+ for r in conn.execute(
205
+ f"SELECT task_id, status FROM tasks WHERE task_id IN ({placeholders})",
206
+ dep_ids,
207
+ ).fetchall()
208
+ }
209
+ return [dep for dep in dep_ids if status_by_dep.get(dep) != "complete"]
210
+
211
+
212
+ def dependents_of(conn: sqlite3.Connection, task_id: str) -> list[str]:
213
+ """Return the ids of tasks that declare `task_id` in their depends_on.
214
+
215
+ The reverse of incomplete_dependencies. Drives the completion cascade:
216
+ when a prerequisite completes, its dependents are the only candidates that
217
+ could newly become runnable. Uses the indexed task_dependencies(depends_on)
218
+ junction on a v35+ DB; otherwise scans the JSON `dependencies` column.
219
+ """
220
+ if _has_task_dependencies_table(conn):
221
+ try:
222
+ rows = conn.execute(
223
+ "SELECT task_id FROM task_dependencies WHERE depends_on = ? ORDER BY task_id ASC",
224
+ (task_id,),
225
+ ).fetchall()
226
+ return [str(r[0]) for r in rows]
227
+ except sqlite3.OperationalError as exc:
228
+ logger.debug("dependents_of junction failed, JSON fallback: %s", exc)
229
+
230
+ rows = conn.execute(
231
+ "SELECT task_id, dependencies FROM tasks "
232
+ "WHERE dependencies IS NOT NULL AND dependencies != ''"
233
+ ).fetchall()
234
+ found: list[str] = []
235
+ for dependent_id, deps_raw in rows:
236
+ try:
237
+ deps = [str(d) for d in json.loads(deps_raw)]
238
+ except (json.JSONDecodeError, TypeError):
239
+ continue
240
+ if task_id in deps:
241
+ found.append(str(dependent_id))
242
+ return sorted(found)
243
+
244
+
245
+ def validate_dependencies_no_cycle(
246
+ conn: sqlite3.Connection, task_id: str, new_deps: list[str]
247
+ ) -> list[str]:
248
+ """Detect cycles a proposed task_id -> new_deps edge set would create. R-L-29.
249
+
250
+ On a v35 DB this runs a targeted recursive CTE over the task_dependencies
251
+ junction — for each proposed dep it asks whether that dep can already reach
252
+ task_id, traversing only the reachable subgraph instead of loading every
253
+ task row (TASK-229). Falls back to the full-scan DFS on a pre-v35 DB.
254
+ Returns a list of cycle paths (empty if no cycle).
255
+ """
256
+ if not new_deps:
257
+ return []
258
+ if not _has_task_dependencies_table(conn):
259
+ return _validate_dependencies_no_cycle_fallback(conn, task_id, new_deps)
260
+
261
+ cycles: list[str] = []
262
+ if task_id in new_deps:
263
+ cycles.append(f"{task_id} → {task_id}") # trivial self-cycle
264
+ for dep in new_deps:
265
+ if dep == task_id:
266
+ continue
267
+ # Can `dep` already reach task_id (excluding task_id's own edges, which
268
+ # this edit replaces)? If so, task_id -> dep closes a cycle. depth guard
269
+ # terminates on any pre-existing cycle in the data.
270
+ # UNION (not UNION ALL) dedups on tid, so a dense DAG with many distinct
271
+ # paths to the same node is bounded to O(reachable nodes) instead of
272
+ # enumerating every path — and the dedup makes any pre-existing data
273
+ # cycle terminate without needing a depth guard.
274
+ row = conn.execute(
275
+ """
276
+ WITH RECURSIVE reachable(tid) AS (
277
+ SELECT ?
278
+ UNION
279
+ SELECT td.depends_on
280
+ FROM task_dependencies td
281
+ JOIN reachable r ON td.task_id = r.tid
282
+ WHERE td.task_id != ?
283
+ )
284
+ SELECT 1 FROM reachable WHERE tid = ? LIMIT 1
285
+ """,
286
+ (dep, task_id, task_id),
287
+ ).fetchone()
288
+ if row:
289
+ cycles.append(f"{task_id} → {dep} → … → {task_id}")
290
+ return cycles
291
+
292
+
293
+ def _validate_dependencies_no_cycle_fallback(
294
+ conn: sqlite3.Connection, task_id: str, new_deps: list[str]
295
+ ) -> list[str]:
296
+ """Pre-v35 fallback: DFS over the full dependency graph (loads all rows)."""
297
+ deps_by_task: dict[str, list[str]] = {}
298
+ for row in conn.execute("SELECT task_id, dependencies FROM tasks"):
299
+ if row[0] == task_id:
300
+ continue
301
+ raw = row[1] or ""
302
+ # Dependencies may be stored as JSON (new style) or as a
303
+ # newline/comma-separated string (legacy). Handle both.
304
+ parsed_deps: list[str] = []
305
+ if isinstance(raw, str) and raw.strip():
306
+ text = raw.strip()
307
+ if text.startswith("["):
308
+ try:
309
+ parsed_deps = [str(d) for d in json.loads(text)]
310
+ except json.JSONDecodeError:
311
+ parsed_deps = []
312
+ else:
313
+ import re as _re
314
+
315
+ parsed_deps = _re.findall(r"TASK-(?:[A-Z][A-Z0-9]*-)?\d+", text)
316
+ deps_by_task[row[0]] = parsed_deps
317
+ deps_by_task[task_id] = list(new_deps)
318
+
319
+ cycles: list[str] = []
320
+ visited: set[str] = set()
321
+ stack: list[str] = []
322
+
323
+ def dfs(node: str) -> None:
324
+ if node in stack:
325
+ cycle = stack[stack.index(node) :] + [node]
326
+ cycles.append(" → ".join(cycle))
327
+ return
328
+ if node in visited:
329
+ return
330
+ visited.add(node)
331
+ stack.append(node)
332
+ for dep in deps_by_task.get(node, []):
333
+ dfs(dep)
334
+ stack.pop()
335
+
336
+ dfs(task_id)
337
+ return cycles
338
+
339
+
340
+ def transition(
341
+ conn: sqlite3.Connection,
342
+ task_id: str,
343
+ to_status: str,
344
+ *,
345
+ reason: str | None = None,
346
+ agent_session: str | None = None,
347
+ expected_from: str | None = None,
348
+ bypass_wip: bool = False,
349
+ bypass_gates: bool = False,
350
+ force: bool = False,
351
+ config: ScrumbanConfig | None = None,
352
+ file_path: Path | None = None,
353
+ ) -> TransitionResult:
354
+ if force:
355
+ bypass_wip = True
356
+ bypass_gates = True
357
+ # Every history row must carry an honest reason — NULL-reason moves are
358
+ # unauditable (the phantom in_progress→icebox reverts). Entry doors tag
359
+ # their own source; this catches any caller that passed nothing.
360
+ if not reason:
361
+ reason = "(unattributed — caller passed no reason)"
362
+ if to_status not in STATUS_ENUM:
363
+ return TransitionResult(
364
+ ok=False,
365
+ task_id=task_id,
366
+ previous_status=None,
367
+ new_status=to_status,
368
+ error=f"unknown status {to_status!r}; must be one of {sorted(STATUS_ENUM)}",
369
+ error_category="validation",
370
+ )
371
+
372
+ row = conn.execute(
373
+ "SELECT status, file_path, agent_session, labels_json FROM tasks WHERE task_id = ?",
374
+ (task_id,),
375
+ ).fetchone()
376
+ if row is None:
377
+ return TransitionResult(
378
+ ok=False,
379
+ task_id=task_id,
380
+ previous_status=None,
381
+ new_status=to_status,
382
+ error=f"task {task_id} not found",
383
+ error_category="not_found",
384
+ )
385
+
386
+ current_status = str(row[0])
387
+ current_file_path = row[1]
388
+ current_labels = _parse_labels(row[3] if len(row) > 3 else None)
389
+
390
+ # Optimistic concurrency — R-L-29.
391
+ if expected_from and current_status != expected_from:
392
+ return TransitionResult(
393
+ ok=False,
394
+ task_id=task_id,
395
+ previous_status=current_status,
396
+ new_status=to_status,
397
+ error=(
398
+ f"status changed under us: expected {expected_from!r}, current {current_status!r}"
399
+ ),
400
+ error_category="transient",
401
+ )
402
+
403
+ if to_status == current_status:
404
+ return TransitionResult(
405
+ ok=True,
406
+ task_id=task_id,
407
+ previous_status=current_status,
408
+ new_status=to_status,
409
+ warnings=("no-op (already in that status)",),
410
+ )
411
+
412
+ valid_next = _VALID_TRANSITIONS.get(current_status, set())
413
+ forced_warning: str | None = None
414
+ if to_status not in valid_next:
415
+ if not force:
416
+ return TransitionResult(
417
+ ok=False,
418
+ task_id=task_id,
419
+ previous_status=current_status,
420
+ new_status=to_status,
421
+ error=(
422
+ f"invalid transition {current_status!r} → {to_status!r}; "
423
+ f"valid: {sorted(valid_next) or ['(terminal)']}. "
424
+ "Pass force=True (or `cos task-move --force`) to override."
425
+ ),
426
+ error_category="validation",
427
+ )
428
+ forced_warning = (
429
+ f"forced-transition {current_status!r} → {to_status!r} "
430
+ f"(state machine disallows this; recorded in history)"
431
+ )
432
+
433
+ # ── Workflow-policy gates (config-driven, state machine) ──
434
+ # Both default-on; a consumer relaxes them via scrumban-config.yaml
435
+ # `workflow_policy:`. Policy runs only when a config is supplied
436
+ # (the live MCP/CLI path) and gates aren't explicitly bypassed —
437
+ # DB-only test/migration calls (config=None) are unaffected.
438
+ policy = config.workflow_policy if config is not None else None
439
+
440
+ # Ready hard-gate: a task must be deliberately marked `ready` before
441
+ # it can be pulled from the backlog. icebox→in_progress is the only
442
+ # pull edge; emergency→in_progress (the fast lane) stays exempt.
443
+ if (
444
+ policy is not None
445
+ and policy.require_ready_label
446
+ and not bypass_gates
447
+ and current_status == "icebox"
448
+ and to_status == "in_progress"
449
+ and READY_LABEL not in current_labels
450
+ ):
451
+ return TransitionResult(
452
+ ok=False,
453
+ task_id=task_id,
454
+ previous_status=current_status,
455
+ new_status=to_status,
456
+ error=(
457
+ f"task not ready: add the '{READY_LABEL}' label before pulling "
458
+ f"it into in_progress — `cos task-ready {task_id}` "
459
+ f"(or escalate via emergency). Override: force=True."
460
+ ),
461
+ error_category="validation",
462
+ )
463
+
464
+ # Dependency gate: a task whose prerequisites are not yet complete cannot
465
+ # be pulled. Same pull edge as the ready gate (icebox→in_progress);
466
+ # emergency→in_progress (the fast lane) stays exempt so a fire never waits
467
+ # on backlog. Category `transient` — the codebase's retryable-by-default
468
+ # category (the canonical retryable "conflict" in the MCP envelope; a bare
469
+ # `conflict` string is non-retryable here) — so the agent re-issues the
470
+ # pull unchanged once the upstream task completes.
471
+ if (
472
+ policy is not None
473
+ and policy.require_deps_complete
474
+ and not bypass_gates
475
+ and current_status == "icebox"
476
+ and to_status == "in_progress"
477
+ ):
478
+ pending = incomplete_dependencies(conn, task_id)
479
+ if pending:
480
+ return TransitionResult(
481
+ ok=False,
482
+ task_id=task_id,
483
+ previous_status=current_status,
484
+ new_status=to_status,
485
+ error=(
486
+ "blocked: prerequisites not complete: "
487
+ + ", ".join(pending)
488
+ + " — finish them or pass force=True"
489
+ ),
490
+ error_category="transient",
491
+ )
492
+
493
+ # Testing-before-complete gate: in_progress→complete must route
494
+ # through `testing` so the verification choreography runs. The edge
495
+ # stays legal in the state machine (testing→complete and forced
496
+ # paths work); policy just blocks the shortcut.
497
+ skip_testing_warning: str | None = None
498
+ if current_status == "in_progress" and to_status == "complete":
499
+ if policy is not None and policy.block_in_progress_to_complete and not bypass_gates:
500
+ return TransitionResult(
501
+ ok=False,
502
+ task_id=task_id,
503
+ previous_status=current_status,
504
+ new_status=to_status,
505
+ error=(
506
+ "must pass through testing: move in_progress→testing, run "
507
+ f"the verification matrix, then testing→complete — "
508
+ f"`cos task-move {task_id} --to testing`. Override: force=True."
509
+ ),
510
+ error_category="validation",
511
+ )
512
+ # config=None path (tests/migrations): keep the soft warning.
513
+ skip_testing_warning = (
514
+ "convention: in_progress→complete skipped 'testing' — "
515
+ "Core Loop expects move-to-testing → run verification matrix → "
516
+ "task-done. Legal but bypasses the gate; record verification in "
517
+ "the work log if intentional."
518
+ )
519
+
520
+ # WIP enforcement, status re-verification, and the row UPDATE all run
521
+ # together inside the atomic BEGIN IMMEDIATE critical section below
522
+ # — the count→write gap and the read→write gap are both inside
523
+ # one write lock, so no concurrent transition can slip between them.
524
+ wip_state: dict[str, int] = {}
525
+
526
+ # ── Transition gates (DoR / DoD) ────────────────────
527
+ # Validate the task body against the kind's rules. file_path=None
528
+ # (DB-only mode used by tests/migrations) skips the body gate.
529
+ target_file_for_gate = file_path or (Path(current_file_path) if current_file_path else None)
530
+ gate_warnings: list[str] = []
531
+ gate_override_reason: str | None = None
532
+ gate_override_actor: str | None = None
533
+ if (
534
+ not bypass_gates
535
+ and target_file_for_gate is not None
536
+ and to_status in {"in_progress", "complete"}
537
+ ):
538
+ try:
539
+ from board_os.transition_gates import (
540
+ GatesConfigError,
541
+ load_gates_config,
542
+ )
543
+ from board_os.transition_gates_validator import (
544
+ validate_transition as _gate_validate,
545
+ )
546
+
547
+ if target_file_for_gate.exists():
548
+ body_text = target_file_for_gate.read_text(encoding="utf-8")
549
+ kind = _extract_kind_from_frontmatter(body_text) or "feature"
550
+ # DoD inputs: read the .last-verify.json freshness signal
551
+ # via the same helper the CLI uses (avoids drift).
552
+ from board_os.transition_gates_cli import (
553
+ _has_work_log_entries as _wl,
554
+ _verify_state as _vs,
555
+ )
556
+
557
+ has_recent, age = _vs()
558
+ has_work_log = _wl(body_text)
559
+
560
+ gates_config = load_gates_config()
561
+ gate_result = _gate_validate(
562
+ task_id=task_id,
563
+ kind=kind,
564
+ body=body_text,
565
+ new_status=to_status,
566
+ config=gates_config,
567
+ has_recent_verify=has_recent,
568
+ verify_age_seconds=age,
569
+ has_work_log=has_work_log,
570
+ override_reason=os.environ.get("COS_OVERRIDE_REASON"),
571
+ override_actor=os.environ.get("COS_AGENT") or agent_session,
572
+ # Task files live at <root>/docs/tasks/<file>; the repo root is
573
+ # three parents up. Passing it enables the Read First dead-link
574
+ # check (WARN) — pure validator tests omit it and skip the stat.
575
+ project_root=str(target_file_for_gate.resolve().parent.parent.parent),
576
+ )
577
+
578
+ if gate_result.blocked:
579
+ return TransitionResult(
580
+ ok=False,
581
+ task_id=task_id,
582
+ previous_status=current_status,
583
+ new_status=to_status,
584
+ error=(
585
+ "transition gate failed: "
586
+ + "; ".join(f"[{m.code}] {m.message}" for m in gate_result.messages)
587
+ ),
588
+ error_category="validation",
589
+ wip_state=wip_state,
590
+ )
591
+
592
+ # PASS or WARN — collect override metadata for audit.
593
+ for m in gate_result.messages:
594
+ gate_warnings.append(f"[{m.code}] {m.message}")
595
+ if any("[OVERRIDDEN]" in m.message for m in gate_result.messages):
596
+ gate_override_reason = os.environ.get("COS_OVERRIDE_REASON")
597
+ gate_override_actor = os.environ.get("COS_AGENT") or agent_session
598
+ except GatesConfigError as exc:
599
+ # Bad config — surface to retro reviewers but don't crash live work.
600
+ gate_warnings.append(f"transition-gates config error (gate skipped): {exc}")
601
+ except Exception as exc:
602
+ gate_warnings.append(f"transition-gates internal error (skipped): {exc}")
603
+
604
+ # ── Atomic critical section ──────────────────────────────
605
+ # All validation + file reads above ran lock-free. BEGIN IMMEDIATE now
606
+ # takes the write lock up front so the status re-check, WIP count, row
607
+ # UPDATE, MD write, and history INSERT are isolated from any concurrent
608
+ # transition. A peer that moved the row during the lock-free gate I/O is
609
+ # caught by the re-SELECT and the CAS rowcount.
610
+ target_file = file_path or (Path(current_file_path) if current_file_path else None)
611
+ warnings: list[str] = []
612
+ if forced_warning is not None:
613
+ warnings.append(forced_warning)
614
+ if skip_testing_warning is not None:
615
+ warnings.append(skip_testing_warning)
616
+ warnings.extend(gate_warnings)
617
+ now_epoch = int(time.time())
618
+
619
+ try:
620
+ conn.execute("BEGIN IMMEDIATE")
621
+ except sqlite3.OperationalError as exc:
622
+ return TransitionResult(
623
+ ok=False,
624
+ task_id=task_id,
625
+ previous_status=current_status,
626
+ new_status=to_status,
627
+ error=f"could not acquire board write lock (busy): {exc}",
628
+ error_category="transient",
629
+ )
630
+
631
+ md_backup: str | None = None
632
+ try:
633
+ # Re-verify status under the lock — gate validation did file I/O,
634
+ # widening the read→write window; a peer may have moved it since.
635
+ locked_row = conn.execute(
636
+ "SELECT status FROM tasks WHERE task_id = ?", (task_id,)
637
+ ).fetchone()
638
+ locked_status = str(locked_row[0]) if locked_row else None
639
+ if locked_status != current_status:
640
+ conn.rollback()
641
+ return TransitionResult(
642
+ ok=False,
643
+ task_id=task_id,
644
+ previous_status=locked_status,
645
+ new_status=to_status,
646
+ error=(
647
+ f"status changed under us: expected {current_status!r}, "
648
+ f"current {locked_status!r} — re-read and retry"
649
+ ),
650
+ error_category="transient",
651
+ )
652
+
653
+ # WIP cap, now race-free (count + UPDATE share the write lock).
654
+ if config is not None and not bypass_wip:
655
+ target_col = _WIP_COLUMN_MAP.get(to_status)
656
+ if target_col:
657
+ state = check_wip(conn, config, agent_session=agent_session)
658
+ wip_state = dict(state.counts)
659
+ cap = state.caps.get(target_col)
660
+ if (
661
+ cap is not None
662
+ and state.counts.get(target_col, 0) >= cap
663
+ and os.environ.get("COS_WIP_OVERRIDE") != "1"
664
+ ):
665
+ conn.rollback()
666
+ return TransitionResult(
667
+ ok=False,
668
+ task_id=task_id,
669
+ previous_status=current_status,
670
+ new_status=to_status,
671
+ error=(
672
+ f"WIP cap reached for {target_col}: "
673
+ f"{state.counts.get(target_col)}/{cap}. "
674
+ f"Complete another task first or set "
675
+ f"COS_WIP_OVERRIDE=1 to force."
676
+ ),
677
+ error_category="validation",
678
+ wip_state=wip_state,
679
+ )
680
+
681
+ # CAS UPDATE — the AND status=? guard is the last-line defense: a
682
+ # rowcount other than 1 means the expected pre-state vanished.
683
+ cur = conn.execute(
684
+ "UPDATE tasks SET status = ?, agent_session = ?, "
685
+ "started_at = CASE WHEN ? = 'in_progress' AND started_at IS NULL "
686
+ " THEN ? ELSE started_at END, "
687
+ "completed_at = CASE WHEN ? = 'complete' THEN ? "
688
+ " WHEN ? IN ('ready','in_progress','testing','emergency') "
689
+ " THEN NULL ELSE completed_at END "
690
+ "WHERE task_id = ? AND status = ?",
691
+ (
692
+ to_status,
693
+ agent_session,
694
+ to_status,
695
+ now_epoch,
696
+ to_status,
697
+ now_epoch,
698
+ to_status,
699
+ task_id,
700
+ current_status,
701
+ ),
702
+ )
703
+ if cur.rowcount != 1:
704
+ conn.rollback()
705
+ return TransitionResult(
706
+ ok=False,
707
+ task_id=task_id,
708
+ previous_status=current_status,
709
+ new_status=to_status,
710
+ error="status changed under us (CAS miss) — re-read and retry",
711
+ error_category="transient",
712
+ )
713
+
714
+ # MD frontmatter write — inside the txn. The writer itself is atomic
715
+ # (tmp + os.replace), so its OWN failure leaves the original intact.
716
+ # We additionally snapshot the file first: if a LATER step (history
717
+ # INSERT / commit) raises, the outer except restores it — so a
718
+ # rolled-back transition changes neither the DB nor the file, instead
719
+ # of leaving the file's status ahead of the DB it just reverted.
720
+ if target_file is not None:
721
+ try:
722
+ md_backup = target_file.read_text(encoding="utf-8")
723
+ except OSError:
724
+ md_backup = None
725
+ try:
726
+ _write_status_to_frontmatter(target_file, to_status, agent_session=agent_session)
727
+ except FileNotFoundError:
728
+ warnings.append(f"MD file missing: {target_file} — DB-only transition")
729
+ except Exception as exc: # pragma: no cover
730
+ conn.rollback()
731
+ return TransitionResult(
732
+ ok=False,
733
+ task_id=task_id,
734
+ previous_status=current_status,
735
+ new_status=to_status,
736
+ error=f"MD write failed: {exc}",
737
+ error_category="unavailable",
738
+ )
739
+ else:
740
+ warnings.append("no file_path — DB-only transition")
741
+
742
+ # task_status_history gained override_reason/override_actor in
743
+ # migration v20. Detect column presence so this code
744
+ # works on a DB before v20 has run yet (test fixtures, fresh init).
745
+ has_override_cols = bool(
746
+ conn.execute(
747
+ "SELECT 1 FROM pragma_table_info('task_status_history') "
748
+ "WHERE name = 'override_reason' LIMIT 1"
749
+ ).fetchone()
750
+ )
751
+ if has_override_cols:
752
+ conn.execute(
753
+ "INSERT INTO task_status_history "
754
+ "(task_id, old_status, new_status, agent_session, reason, "
755
+ " transitioned_at, override_reason, override_actor) "
756
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
757
+ (
758
+ task_id,
759
+ current_status,
760
+ to_status,
761
+ agent_session,
762
+ reason,
763
+ now_epoch,
764
+ gate_override_reason,
765
+ gate_override_actor,
766
+ ),
767
+ )
768
+ else:
769
+ conn.execute(
770
+ "INSERT INTO task_status_history "
771
+ "(task_id, old_status, new_status, agent_session, reason, transitioned_at) "
772
+ "VALUES (?, ?, ?, ?, ?, ?)",
773
+ (task_id, current_status, to_status, agent_session, reason, now_epoch),
774
+ )
775
+ conn.commit()
776
+ except Exception:
777
+ conn.rollback()
778
+ # Undo a successful MD write whose transaction then failed, so the
779
+ # file never reports a status the DB rolled back (Finding A).
780
+ if md_backup is not None and target_file is not None:
781
+ try:
782
+ target_file.write_text(md_backup, encoding="utf-8")
783
+ except OSError as restore_exc:
784
+ logger.debug("MD restore after rollback failed: %s", restore_exc)
785
+ raise
786
+
787
+ return TransitionResult(
788
+ ok=True,
789
+ task_id=task_id,
790
+ previous_status=current_status,
791
+ new_status=to_status,
792
+ warnings=tuple(warnings),
793
+ wip_state=wip_state,
794
+ )
795
+
796
+
797
+ # ---------- Helpers ----------
798
+
799
+
800
+ def _parse_labels(raw: object) -> set[str]:
801
+ """Normalize a task's labels_json column into a set of label strings.
802
+
803
+ Accepts the JSON-array string the DB stores, a real list, or None.
804
+ """
805
+ if not raw:
806
+ return set()
807
+ if isinstance(raw, (list, tuple)):
808
+ return {str(x) for x in raw}
809
+ if isinstance(raw, str):
810
+ text = raw.strip()
811
+ if not text:
812
+ return set()
813
+ try:
814
+ parsed = json.loads(text)
815
+ except json.JSONDecodeError:
816
+ return {t.strip() for t in text.split(",") if t.strip()}
817
+ if isinstance(parsed, list):
818
+ return {str(x) for x in parsed}
819
+ return set()
820
+
821
+
822
+ def _extract_kind_from_frontmatter(content: str) -> str | None:
823
+ """Pull `kind:` from YAML frontmatter without dragging in PyYAML.
824
+
825
+ Frontmatter lives between two `---` lines at file head. Returns the
826
+ raw value as written; defaults to None when absent or malformed.
827
+ """
828
+ if not content.startswith("---"):
829
+ return None
830
+ end = content.find("\n---", 3)
831
+ if end < 0:
832
+ return None
833
+ head = content[3:end]
834
+ for line in head.splitlines():
835
+ line = line.strip()
836
+ if line.startswith("kind:"):
837
+ return line.split(":", 1)[1].strip().strip('"').strip("'") or None
838
+ return None
839
+
840
+
841
+ def _patch_fm_field(fm_text: str, key: str, value: str) -> str:
842
+ """Replace a scalar field in raw YAML text without touching comments or key order.
843
+
844
+ If the key already exists, its value is updated in-place.
845
+ If it is absent, the key is appended on a new line (handles tasks
846
+ created before a field was added to the template).
847
+ """
848
+ import re as _re
849
+
850
+ pattern = _re.compile(rf"^({_re.escape(key)}:)[ \t]*.*$", _re.MULTILINE)
851
+ if pattern.search(fm_text):
852
+ return pattern.sub(rf"\1 {value}", fm_text, count=1)
853
+ return fm_text + f"\n{key}: {value}"
854
+
855
+
856
+ def _write_status_to_frontmatter(
857
+ path: Path,
858
+ new_status: str,
859
+ *,
860
+ agent_session: str | None,
861
+ ) -> None:
862
+ """Atomically update status (+ started/completed timestamps) in frontmatter.
863
+
864
+ Uses targeted regex field-patching instead of YAML round-trip so that
865
+ inline comments (e.g. ``# always start here``) are preserved verbatim.
866
+ """
867
+ if not path.exists():
868
+ raise FileNotFoundError(str(path))
869
+
870
+ content = path.read_text(encoding="utf-8")
871
+ import re as _re
872
+
873
+ fm_re = _re.compile(r"^---\s*\n(.*?)\n---\s*\n", _re.DOTALL)
874
+ m = fm_re.match(content)
875
+ if not m:
876
+ raise ValueError(f"{path}: no frontmatter to update")
877
+
878
+ fm_raw = m.group(1)
879
+
880
+ # Validate YAML is parseable before touching anything.
881
+ try:
882
+ fm_parsed = yaml.safe_load(fm_raw) or {}
883
+ except yaml.YAMLError as exc:
884
+ raise ValueError(f"{path}: frontmatter YAML broken: {exc}") from exc
885
+ if not isinstance(fm_parsed, dict):
886
+ raise ValueError(f"{path}: frontmatter is not a mapping")
887
+
888
+ today = time.strftime("%Y-%m-%d")
889
+ fm_raw = _patch_fm_field(fm_raw, "status", new_status)
890
+ if agent_session is not None:
891
+ fm_raw = _patch_fm_field(fm_raw, "agent_session", agent_session)
892
+ if new_status == "in_progress" and not fm_parsed.get("started"):
893
+ fm_raw = _patch_fm_field(fm_raw, "started", today)
894
+ if new_status == "complete" and not fm_parsed.get("completed"):
895
+ fm_raw = _patch_fm_field(fm_raw, "completed", today)
896
+
897
+ new_content = f"---\n{fm_raw}\n---\n" + content[m.end() :]
898
+
899
+ tmp_fd, tmp_path = tempfile.mkstemp(
900
+ dir=str(path.parent),
901
+ prefix=".task-",
902
+ suffix=".tmp",
903
+ )
904
+ try:
905
+ with os.fdopen(tmp_fd, "w", encoding="utf-8") as fh:
906
+ fh.write(new_content)
907
+ os.replace(tmp_path, path)
908
+ except Exception:
909
+ try:
910
+ os.unlink(tmp_path)
911
+ except OSError:
912
+ pass
913
+ raise
914
+
915
+
916
+ def _format_yaml_scalar_token(value: str) -> str:
917
+ """Format a scalar for YAML frontmatter (unquoted id vs JSON-quoted string)."""
918
+ import re as _re
919
+
920
+ if _re.match(r"^[a-z0-9][a-z0-9-]*$", value, _re.I):
921
+ return value
922
+ return json.dumps(value)
923
+
924
+
925
+ def patch_task_frontmatter_scalars(path: Path, updates: dict[str, str]) -> None:
926
+ if not updates:
927
+ return
928
+ if not path.exists():
929
+ raise FileNotFoundError(str(path))
930
+
931
+ content = path.read_text(encoding="utf-8")
932
+ import re as _re
933
+
934
+ fm_re = _re.compile(r"^---\s*\n(.*?)\n---\s*\n", _re.DOTALL)
935
+ m = fm_re.match(content)
936
+ if not m:
937
+ raise ValueError(f"{path}: no frontmatter to update")
938
+
939
+ fm_raw = m.group(1)
940
+ try:
941
+ fm_parsed = yaml.safe_load(fm_raw) or {}
942
+ except yaml.YAMLError as exc:
943
+ raise ValueError(f"{path}: frontmatter YAML broken: {exc}") from exc
944
+ if not isinstance(fm_parsed, dict):
945
+ raise ValueError(f"{path}: frontmatter is not a mapping")
946
+
947
+ for key, raw_val in updates.items():
948
+ token = _format_yaml_scalar_token(raw_val)
949
+ fm_raw = _patch_fm_field(fm_raw, key, token)
950
+
951
+ new_content = f"---\n{fm_raw}\n---\n" + content[m.end() :]
952
+
953
+ tmp_fd, tmp_path = tempfile.mkstemp(
954
+ dir=str(path.parent),
955
+ prefix=".task-",
956
+ suffix=".tmp",
957
+ )
958
+ try:
959
+ with os.fdopen(tmp_fd, "w", encoding="utf-8") as fh:
960
+ fh.write(new_content)
961
+ os.replace(tmp_path, path)
962
+ except Exception:
963
+ try:
964
+ os.unlink(tmp_path)
965
+ except OSError:
966
+ pass
967
+ raise