@scemoon/cdh 1.0.8 → 1.0.9-beta.2

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 (786) hide show
  1. package/ai-dlc-skill/SKILL.md +106 -0
  2. package/ai-dlc-skill/architecture/project-structure.md +78 -0
  3. package/ai-dlc-skill/brownfield/README.md +36 -0
  4. package/ai-dlc-skill/brownfield/entry.md +45 -0
  5. package/ai-dlc-skill/brownfield/generate-context.sh +64 -0
  6. package/ai-dlc-skill/brownfield/scripts/deps.sh +27 -0
  7. package/ai-dlc-skill/brownfield/scripts/discover.sh +34 -0
  8. package/ai-dlc-skill/brownfield/scripts/extract-api.sh +45 -0
  9. package/ai-dlc-skill/components/backend.md +47 -0
  10. package/ai-dlc-skill/components/desktop.md +63 -0
  11. package/ai-dlc-skill/components/mya.md +219 -0
  12. package/ai-dlc-skill/components/native.md +73 -0
  13. package/ai-dlc-skill/components/tta.md +235 -0
  14. package/ai-dlc-skill/components/web.md +77 -0
  15. package/ai-dlc-skill/components/wxa.md +219 -0
  16. package/ai-dlc-skill/contracts/README.md +25 -0
  17. package/ai-dlc-skill/core/adaptive-flow.md +107 -0
  18. package/ai-dlc-skill/core/security.md +29 -0
  19. package/ai-dlc-skill/core/task-registry.md +153 -0
  20. package/ai-dlc-skill/cross-tool/README.md +20 -0
  21. package/ai-dlc-skill/cross-tool/cline/export.sh +38 -0
  22. package/ai-dlc-skill/cross-tool/copilot/export.sh +41 -0
  23. package/ai-dlc-skill/cross-tool/cursor/export.sh +53 -0
  24. package/ai-dlc-skill/cross-tool/onecode/export.sh +27 -0
  25. package/ai-dlc-skill/cross-tool/opencode/export.sh +38 -0
  26. package/ai-dlc-skill/phases/debug/entry.md +48 -0
  27. package/ai-dlc-skill/phases/debug/lifecycle.md +284 -0
  28. package/ai-dlc-skill/phases/deliver/entry.md +35 -0
  29. package/ai-dlc-skill/phases/deliver/lifecycle.md +185 -0
  30. package/ai-dlc-skill/phases/deliver/prompt.md +54 -0
  31. package/ai-dlc-skill/phases/deliver/rules.md +61 -0
  32. package/ai-dlc-skill/phases/plan/design_system/accessibility.md +268 -0
  33. package/ai-dlc-skill/phases/plan/design_system/atomic_design.md +341 -0
  34. package/ai-dlc-skill/phases/plan/design_system/component_spec.md +398 -0
  35. package/ai-dlc-skill/phases/plan/design_system/design_tokens.md +190 -0
  36. package/ai-dlc-skill/phases/plan/design_system/iconography.md +299 -0
  37. package/ai-dlc-skill/phases/plan/design_system/platform_ui/desktop.md +318 -0
  38. package/ai-dlc-skill/phases/plan/design_system/platform_ui/mya.md +354 -0
  39. package/ai-dlc-skill/phases/plan/design_system/platform_ui/native.md +328 -0
  40. package/ai-dlc-skill/phases/plan/design_system/platform_ui/tta.md +394 -0
  41. package/ai-dlc-skill/phases/plan/design_system/platform_ui/web.md +287 -0
  42. package/ai-dlc-skill/phases/plan/design_system/platform_ui/wxa.md +374 -0
  43. package/ai-dlc-skill/phases/plan/design_system/theme_system.md +318 -0
  44. package/ai-dlc-skill/phases/plan/entry.md +33 -0
  45. package/ai-dlc-skill/phases/plan/lifecycle.md +260 -0
  46. package/ai-dlc-skill/phases/plan/prompt.md +75 -0
  47. package/ai-dlc-skill/phases/plan/rules.md +60 -0
  48. package/ai-dlc-skill/phases/understand/entry.md +36 -0
  49. package/ai-dlc-skill/phases/understand/lifecycle.md +184 -0
  50. package/ai-dlc-skill/phases/understand/prompt.md +58 -0
  51. package/ai-dlc-skill/phases/understand/rules.md +49 -0
  52. package/ai-dlc-skill/phases/verify/config/base_url.md +65 -0
  53. package/ai-dlc-skill/phases/verify/entry.md +59 -0
  54. package/ai-dlc-skill/phases/verify/gates/browser_e2e.md +96 -0
  55. package/ai-dlc-skill/phases/verify/lifecycle.md +217 -0
  56. package/ai-dlc-skill/phases/verify/prompt.md +73 -0
  57. package/ai-dlc-skill/phases/verify/rules.md +91 -0
  58. package/ai-dlc-skill/phases/verify/runners/playwright_local.md +103 -0
  59. package/ai-dlc-skill/practices/bdd.md +119 -0
  60. package/ai-dlc-skill/practices/sdd.md +84 -0
  61. package/ai-dlc-skill/practices/tdd.md +128 -0
  62. package/ai-dlc-skill/providers/README.md +114 -0
  63. package/ai-dlc-skill/providers/aliyun/SKILL.md +204 -0
  64. package/ai-dlc-skill/providers/aliyun/api/rest-api.md +214 -0
  65. package/ai-dlc-skill/providers/aliyun/api/sdk.md +307 -0
  66. package/ai-dlc-skill/providers/aliyun/best-practices/database-design.md +363 -0
  67. package/ai-dlc-skill/providers/aliyun/best-practices/function-design.md +424 -0
  68. package/ai-dlc-skill/providers/aliyun/best-practices/troubleshooting.md +258 -0
  69. package/ai-dlc-skill/providers/aliyun/cli/database.md +263 -0
  70. package/ai-dlc-skill/providers/aliyun/cli/functions.md +263 -0
  71. package/ai-dlc-skill/providers/aliyun/cli/hosting.md +226 -0
  72. package/ai-dlc-skill/providers/aliyun/cli/setup.md +235 -0
  73. package/ai-dlc-skill/providers/aliyun/cli/storage.md +315 -0
  74. package/ai-dlc-skill/providers/aliyun/deployment.yaml +66 -0
  75. package/ai-dlc-skill/providers/aliyun/integration/ci-cd.md +288 -0
  76. package/ai-dlc-skill/providers/aliyun/preview.yaml +84 -0
  77. package/ai-dlc-skill/providers/aliyun/provider.yaml +160 -0
  78. package/ai-dlc-skill/providers/aliyun/security/authentication.md +254 -0
  79. package/ai-dlc-skill/providers/aliyun/services/cdn-hosting.md +220 -0
  80. package/ai-dlc-skill/providers/aliyun/services/database.md +319 -0
  81. package/ai-dlc-skill/providers/aliyun/services/function-compute.md +273 -0
  82. package/ai-dlc-skill/providers/aliyun/services/overview.md +163 -0
  83. package/ai-dlc-skill/providers/aliyun/services/serverless-app-engine.md +266 -0
  84. package/ai-dlc-skill/providers/aliyun/services/storage.md +269 -0
  85. package/ai-dlc-skill/providers/tcb/SKILL.md +208 -0
  86. package/ai-dlc-skill/providers/tcb/api/rest-api.md +338 -0
  87. package/ai-dlc-skill/providers/tcb/api/sdk.md +288 -0
  88. package/ai-dlc-skill/providers/tcb/best-practices/database-design.md +452 -0
  89. package/ai-dlc-skill/providers/tcb/best-practices/function-design.md +502 -0
  90. package/ai-dlc-skill/providers/tcb/best-practices/troubleshooting.md +462 -0
  91. package/ai-dlc-skill/providers/tcb/cli/cls.md +133 -0
  92. package/ai-dlc-skill/providers/tcb/cli/database.md +230 -0
  93. package/ai-dlc-skill/providers/tcb/cli/env.md +207 -0
  94. package/ai-dlc-skill/providers/tcb/cli/functions.md +276 -0
  95. package/ai-dlc-skill/providers/tcb/cli/hosting.md +219 -0
  96. package/ai-dlc-skill/providers/tcb/cli/setup.md +218 -0
  97. package/ai-dlc-skill/providers/tcb/cli/storage.md +233 -0
  98. package/ai-dlc-skill/providers/tcb/deployment.yaml +111 -0
  99. package/ai-dlc-skill/providers/tcb/integration/ci-cd.md +309 -0
  100. package/ai-dlc-skill/providers/tcb/integration/mcp-server.md +371 -0
  101. package/ai-dlc-skill/providers/tcb/preview.yaml +83 -0
  102. package/ai-dlc-skill/providers/tcb/provider.yaml +172 -0
  103. package/ai-dlc-skill/providers/tcb/security/authentication.md +241 -0
  104. package/ai-dlc-skill/providers/tcb/services/cloud-database.md +299 -0
  105. package/ai-dlc-skill/providers/tcb/services/cloud-functions.md +265 -0
  106. package/ai-dlc-skill/providers/tcb/services/cloud-hosting.md +264 -0
  107. package/ai-dlc-skill/providers/tcb/services/cloud-run.md +285 -0
  108. package/ai-dlc-skill/providers/tcb/services/cloud-storage.md +245 -0
  109. package/ai-dlc-skill/providers/tcb/services/overview.md +174 -0
  110. package/ai-dlc-skill/pybdd/README.md +41 -0
  111. package/ai-dlc-skill/pybdd/conftest.py +17 -0
  112. package/ai-dlc-skill/pybdd/example/auth/login.feature +32 -0
  113. package/ai-dlc-skill/pybdd/pytest.ini +5 -0
  114. package/ai-dlc-skill/pybdd/steps/.gitkeep +1 -0
  115. package/ai-dlc-skill/pybdd/steps/test_example_steps.py +65 -0
  116. package/ai-dlc-skill/requirements.md +140 -0
  117. package/ai-dlc-skill/skill.yaml +135 -0
  118. package/ai-dlc-skill/templates/brownfield/context.md +78 -0
  119. package/ai-dlc-skill/templates/integration/CHANGELOG.md +41 -0
  120. package/ai-dlc-skill/templates/integration/asyncapi.yaml +43 -0
  121. package/ai-dlc-skill/templates/integration/contract-diff.md +63 -0
  122. package/ai-dlc-skill/templates/integration/contract-spec.md +49 -0
  123. package/ai-dlc-skill/templates/integration/openapi.yaml +18 -0
  124. package/ai-dlc-skill/templates/integration/runtime-contract.yaml +36 -0
  125. package/ai-dlc-skill/templates/plan/design.md +49 -0
  126. package/ai-dlc-skill/templates/plan/task-list.md +32 -0
  127. package/ai-dlc-skill/templates/project/README.md +126 -0
  128. package/ai-dlc-skill/templates/project/template.md +119 -0
  129. package/ai-dlc-skill/templates/understand/feature.feature +30 -0
  130. package/ai-dlc-skill/templates/understand/intent.md +29 -0
  131. package/ai-dlc-skill/templates/understand/spec-delta.md +47 -0
  132. package/ai-dlc-skill/templates/verify/test-unit.py +27 -0
  133. package/ai-dlc-skill/walkthrough/collect.sh +50 -0
  134. package/ai-dlc-skill/walkthrough/template.md +54 -0
  135. package/cdh/__init__.py +0 -0
  136. package/cdh/__main__.py +4 -0
  137. package/cdh/__pycache__/__init__.cpython-314.pyc +0 -0
  138. package/cdh/__pycache__/cli.cpython-314.pyc +0 -0
  139. package/cdh/__pycache__/cli_logging.cpython-314.pyc +0 -0
  140. package/cdh/__pycache__/config.cpython-314.pyc +0 -0
  141. package/cdh/__pycache__/project_loader.cpython-314.pyc +0 -0
  142. package/cdh/__pycache__/scaffold.cpython-314.pyc +0 -0
  143. package/cdh/__pycache__/session_store.cpython-314.pyc +0 -0
  144. package/cdh/cdh_session_aggregator.py +258 -0
  145. package/cdh/cli.py +3000 -0
  146. package/cdh/cli_logging.py +45 -0
  147. package/cdh/config.py +28 -0
  148. package/cdh/event_loop/__init__.py +7 -0
  149. package/cdh/event_loop/bus.py +123 -0
  150. package/cdh/event_loop/events.py +25 -0
  151. package/cdh/event_loop/runner.py +28 -0
  152. package/cdh/event_loop/scheduler.py +158 -0
  153. package/cdh/generators/__init__.py +35 -0
  154. package/cdh/generators/_runtime.py +176 -0
  155. package/cdh/generators/ada/MANIFEST.toml +14 -0
  156. package/cdh/generators/ada/template.ads.tmpl +7 -0
  157. package/cdh/generators/bash/MANIFEST.toml +14 -0
  158. package/cdh/generators/bash/template.sh.tmpl +16 -0
  159. package/cdh/generators/builtin_index.json +590 -0
  160. package/cdh/generators/c/MANIFEST.toml +14 -0
  161. package/cdh/generators/c/template.h.tmpl +20 -0
  162. package/cdh/generators/cli.py +379 -0
  163. package/cdh/generators/clojure/MANIFEST.toml +14 -0
  164. package/cdh/generators/clojure/template.clj.tmpl +13 -0
  165. package/cdh/generators/cobol/MANIFEST.toml +14 -0
  166. package/cdh/generators/cobol/template.cbl.tmpl +14 -0
  167. package/cdh/generators/cpp/MANIFEST.toml +14 -0
  168. package/cdh/generators/cpp/template.hpp.tmpl +32 -0
  169. package/cdh/generators/crystal/MANIFEST.toml +14 -0
  170. package/cdh/generators/crystal/template.cr.tmpl +11 -0
  171. package/cdh/generators/csharp/MANIFEST.toml +23 -0
  172. package/cdh/generators/csharp/template.cs.tmpl +14 -0
  173. package/cdh/generators/cython/MANIFEST.toml +14 -0
  174. package/cdh/generators/cython/template.pyx.tmpl +17 -0
  175. package/cdh/generators/d/MANIFEST.toml +14 -0
  176. package/cdh/generators/d/template.d.tmpl +24 -0
  177. package/cdh/generators/dart/MANIFEST.toml +17 -0
  178. package/cdh/generators/dart/template.dart.tmpl +27 -0
  179. package/cdh/generators/elixir/MANIFEST.toml +17 -0
  180. package/cdh/generators/elixir/template.ex.tmpl +27 -0
  181. package/cdh/generators/elm/MANIFEST.toml +14 -0
  182. package/cdh/generators/elm/template.elm.tmpl +20 -0
  183. package/cdh/generators/erlang/MANIFEST.toml +14 -0
  184. package/cdh/generators/erlang/template.erl.tmpl +18 -0
  185. package/cdh/generators/fortran/MANIFEST.toml +14 -0
  186. package/cdh/generators/fortran/template.f90.tmpl +24 -0
  187. package/cdh/generators/fsharp/MANIFEST.toml +14 -0
  188. package/cdh/generators/fsharp/template.fs.tmpl +17 -0
  189. package/cdh/generators/go/MANIFEST.toml +18 -0
  190. package/cdh/generators/go/imports.go.tmpl +3 -0
  191. package/cdh/generators/go/template.go.tmpl +9 -0
  192. package/cdh/generators/graphql-schema/MANIFEST.toml +18 -0
  193. package/cdh/generators/graphql-schema/template.graphqls.tmpl +12 -0
  194. package/cdh/generators/groovy/MANIFEST.toml +14 -0
  195. package/cdh/generators/groovy/template.groovy.tmpl +28 -0
  196. package/cdh/generators/hack/MANIFEST.toml +14 -0
  197. package/cdh/generators/hack/template.hh.tmpl +24 -0
  198. package/cdh/generators/haskell/MANIFEST.toml +14 -0
  199. package/cdh/generators/haskell/template.hs.tmpl +20 -0
  200. package/cdh/generators/haxe/MANIFEST.toml +14 -0
  201. package/cdh/generators/haxe/template.hx.tmpl +20 -0
  202. package/cdh/generators/idris/MANIFEST.toml +14 -0
  203. package/cdh/generators/idris/template.idr.tmpl +22 -0
  204. package/cdh/generators/java/MANIFEST.toml +18 -0
  205. package/cdh/generators/java/template.java.tmpl +13 -0
  206. package/cdh/generators/julia/MANIFEST.toml +14 -0
  207. package/cdh/generators/julia/template.jl.tmpl +27 -0
  208. package/cdh/generators/kotlin/MANIFEST.toml +18 -0
  209. package/cdh/generators/kotlin/template.kt.tmpl +12 -0
  210. package/cdh/generators/lua/MANIFEST.toml +14 -0
  211. package/cdh/generators/lua/template.lua.tmpl +27 -0
  212. package/cdh/generators/matlab/MANIFEST.toml +14 -0
  213. package/cdh/generators/matlab/template.m.tmpl +26 -0
  214. package/cdh/generators/nim/MANIFEST.toml +14 -0
  215. package/cdh/generators/nim/template.nim.tmpl +12 -0
  216. package/cdh/generators/nix/MANIFEST.toml +14 -0
  217. package/cdh/generators/nix/template.nix.tmpl +8 -0
  218. package/cdh/generators/objective-c/MANIFEST.toml +14 -0
  219. package/cdh/generators/objective-c/template.m.tmpl +31 -0
  220. package/cdh/generators/ocaml/MANIFEST.toml +14 -0
  221. package/cdh/generators/ocaml/template.ml.tmpl +13 -0
  222. package/cdh/generators/pascal/MANIFEST.toml +14 -0
  223. package/cdh/generators/pascal/template.pas.tmpl +5 -0
  224. package/cdh/generators/perl/MANIFEST.toml +14 -0
  225. package/cdh/generators/perl/template.pm.tmpl +27 -0
  226. package/cdh/generators/php/MANIFEST.toml +17 -0
  227. package/cdh/generators/php/template.php.tmpl +31 -0
  228. package/cdh/generators/powershell/MANIFEST.toml +14 -0
  229. package/cdh/generators/powershell/template.ps1.tmpl +20 -0
  230. package/cdh/generators/protobuf/MANIFEST.toml +18 -0
  231. package/cdh/generators/protobuf/template.proto.tmpl +13 -0
  232. package/cdh/generators/python/MANIFEST.toml +18 -0
  233. package/cdh/generators/python/template.py.tmpl +12 -0
  234. package/cdh/generators/r/MANIFEST.toml +14 -0
  235. package/cdh/generators/r/template.R.tmpl +29 -0
  236. package/cdh/generators/racket/MANIFEST.toml +14 -0
  237. package/cdh/generators/racket/template.rkt.tmpl +15 -0
  238. package/cdh/generators/registry.py +211 -0
  239. package/cdh/generators/ruby/MANIFEST.toml +17 -0
  240. package/cdh/generators/ruby/template.rb.tmpl +27 -0
  241. package/cdh/generators/rust/MANIFEST.toml +18 -0
  242. package/cdh/generators/rust/template.rs.tmpl +16 -0
  243. package/cdh/generators/scala/MANIFEST.toml +20 -0
  244. package/cdh/generators/scala/template.scala.tmpl +13 -0
  245. package/cdh/generators/scheme/MANIFEST.toml +14 -0
  246. package/cdh/generators/scheme/template.scm.tmpl +16 -0
  247. package/cdh/generators/solidity/MANIFEST.toml +14 -0
  248. package/cdh/generators/solidity/template.sol.tmpl +24 -0
  249. package/cdh/generators/swift/MANIFEST.toml +18 -0
  250. package/cdh/generators/swift/template.swift.tmpl +10 -0
  251. package/cdh/generators/typescript/MANIFEST.toml +18 -0
  252. package/cdh/generators/typescript/template.ts.tmpl +7 -0
  253. package/cdh/generators/version.py +214 -0
  254. package/cdh/generators/zig/MANIFEST.toml +14 -0
  255. package/cdh/generators/zig/template.zig.tmpl +20 -0
  256. package/cdh/logging.py +314 -0
  257. package/cdh/optimizer/__init__.py +7 -0
  258. package/cdh/optimizer/loop.py +201 -0
  259. package/cdh/optimizer/mutation.py +85 -0
  260. package/cdh/optimizer/reward.py +51 -0
  261. package/cdh/optimizer/tracker.py +118 -0
  262. package/cdh/project_loader.py +1022 -0
  263. package/cdh/rules/patterns.yaml +132 -0
  264. package/cdh/rules/spectral.yaml +301 -0
  265. package/cdh/scaffold.py +1436 -0
  266. package/cdh/session_store.py +221 -0
  267. package/cdh/state_schema.py +127 -0
  268. package/cdh/tools/__init__.py +57 -0
  269. package/cdh/tools/cls_search.py +406 -0
  270. package/cdh/tools/contract_diff.py +644 -0
  271. package/cdh/tools/deploy_stack.py +590 -0
  272. package/cdh/tools/generate_shared.py +300 -0
  273. package/cdh/tools/iac_gen.py +224 -0
  274. package/cdh/tools/otel_export.py +125 -0
  275. package/cdh/tools/pattern_detect.py +451 -0
  276. package/cdh/tools/retrofit_labels.py +505 -0
  277. package/cdh/trace/__init__.py +101 -0
  278. package/cdh/trace/__pycache__/__init__.cpython-314.pyc +0 -0
  279. package/cdh/trace/dashboard/__init__.py +61 -0
  280. package/cdh/trace/dashboard/api.py +1032 -0
  281. package/cdh/trace/dashboard/app.html +1321 -0
  282. package/cdh/trace/dashboard/server.py +310 -0
  283. package/cdh/trace/otel_exporter.py +462 -0
  284. package/cdh/trace/otel_tracer.py +281 -0
  285. package/cdh/tui.py +1084 -0
  286. package/cdh/validators/__init__.py +16 -0
  287. package/cdh/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  288. package/cdh/validators/__pycache__/bdd.cpython-314.pyc +0 -0
  289. package/cdh/validators/__pycache__/dag.cpython-314.pyc +0 -0
  290. package/cdh/validators/__pycache__/ears.cpython-314.pyc +0 -0
  291. package/cdh/validators/__pycache__/fr_namespace.cpython-314.pyc +0 -0
  292. package/cdh/validators/bdd.py +144 -0
  293. package/cdh/validators/dag.py +179 -0
  294. package/cdh/validators/ears.py +452 -0
  295. package/cdh/validators/fr_namespace.py +143 -0
  296. package/cdh/validators/spectral.py +475 -0
  297. package/cdh/verification/__init__.py +3 -0
  298. package/cdh/verification/aggregation.py +53 -0
  299. package/cdh/verification/gates/__init__.py +5 -0
  300. package/cdh/verification/gates/base.py +18 -0
  301. package/cdh/verification/gates/lint_gate.py +37 -0
  302. package/cdh/verification/gates/test_gate.py +41 -0
  303. package/cdh/verification/loop.py +93 -0
  304. package/cdh/verification/policy.py +24 -0
  305. package/onecode/__init__.py +1 -0
  306. package/onecode/__main__.py +4 -0
  307. package/onecode/__pycache__/__init__.cpython-314.pyc +0 -0
  308. package/onecode/__pycache__/cli.cpython-314.pyc +0 -0
  309. package/onecode/__pycache__/commands.cpython-314.pyc +0 -0
  310. package/onecode/__pycache__/config.cpython-314.pyc +0 -0
  311. package/onecode/__pycache__/migrate.cpython-314.pyc +0 -0
  312. package/onecode/agent/__init__.py +0 -0
  313. package/onecode/agent/__pycache__/__init__.cpython-314.pyc +0 -0
  314. package/onecode/agent/__pycache__/cdh_loader.cpython-314.pyc +0 -0
  315. package/onecode/agent/__pycache__/checkpoint.cpython-314.pyc +0 -0
  316. package/onecode/agent/__pycache__/context.cpython-314.pyc +0 -0
  317. package/onecode/agent/__pycache__/engine.cpython-314.pyc +0 -0
  318. package/onecode/agent/__pycache__/hooks.cpython-314.pyc +0 -0
  319. package/onecode/agent/__pycache__/onecode_agent_acp.cpython-314.pyc +0 -0
  320. package/onecode/agent/__pycache__/permissions.cpython-314.pyc +0 -0
  321. package/onecode/agent/__pycache__/permissions_store.cpython-314.pyc +0 -0
  322. package/onecode/agent/__pycache__/project_doc.cpython-314.pyc +0 -0
  323. package/onecode/agent/__pycache__/session.cpython-314.pyc +0 -0
  324. package/onecode/agent/__pycache__/turn_record.cpython-314.pyc +0 -0
  325. package/onecode/agent/agents/__init__.py +29 -0
  326. package/onecode/agent/agents/__pycache__/__init__.cpython-314.pyc +0 -0
  327. package/onecode/agent/agents/__pycache__/types.cpython-314.pyc +0 -0
  328. package/onecode/agent/agents/prompts/build-agent.md +50 -0
  329. package/onecode/agent/agents/prompts/compaction-instructions.md +12 -0
  330. package/onecode/agent/agents/prompts/explore-agent.md +42 -0
  331. package/onecode/agent/agents/prompts/plan-agent.md +44 -0
  332. package/onecode/agent/agents/prompts/plan-gate-hard.md +4 -0
  333. package/onecode/agent/agents/prompts/plan-gate-soft.md +3 -0
  334. package/onecode/agent/agents/prompts/solo-agent.md +57 -0
  335. package/onecode/agent/agents/prompts/subagent-constraints.md +10 -0
  336. package/onecode/agent/agents/prompts/summary-instructions.md +7 -0
  337. package/onecode/agent/agents/prompts/title-instructions.md +3 -0
  338. package/onecode/agent/agents/prompts/tool-descriptions.md +129 -0
  339. package/onecode/agent/agents/types.py +786 -0
  340. package/onecode/agent/attachments.py +112 -0
  341. package/onecode/agent/cdh_loader.py +3 -0
  342. package/onecode/agent/checkpoint.py +285 -0
  343. package/onecode/agent/context.py +465 -0
  344. package/onecode/agent/engine.py +3547 -0
  345. package/onecode/agent/event_bridge.py +75 -0
  346. package/onecode/agent/hooks.py +86 -0
  347. package/onecode/agent/onecode_agent_acp.py +2993 -0
  348. package/onecode/agent/permissions.py +150 -0
  349. package/onecode/agent/permissions_store.py +73 -0
  350. package/onecode/agent/project_doc.py +19 -0
  351. package/onecode/agent/question_detect.py +230 -0
  352. package/onecode/agent/session.py +3 -0
  353. package/onecode/agent/snapshot.py +144 -0
  354. package/onecode/agent/tools/__init__.py +17 -0
  355. package/onecode/agent/tools/__pycache__/__init__.cpython-314.pyc +0 -0
  356. package/onecode/agent/tools/__pycache__/agent_tools.cpython-314.pyc +0 -0
  357. package/onecode/agent/tools/__pycache__/apply_patch_tool.cpython-314.pyc +0 -0
  358. package/onecode/agent/tools/__pycache__/bash_tool.cpython-314.pyc +0 -0
  359. package/onecode/agent/tools/__pycache__/communication_tools.cpython-314.pyc +0 -0
  360. package/onecode/agent/tools/__pycache__/config_tool.cpython-314.pyc +0 -0
  361. package/onecode/agent/tools/__pycache__/cron_tools.cpython-314.pyc +0 -0
  362. package/onecode/agent/tools/__pycache__/errors.cpython-314.pyc +0 -0
  363. package/onecode/agent/tools/__pycache__/file_ops.cpython-314.pyc +0 -0
  364. package/onecode/agent/tools/__pycache__/file_tools.cpython-314.pyc +0 -0
  365. package/onecode/agent/tools/__pycache__/git_tools.cpython-314.pyc +0 -0
  366. package/onecode/agent/tools/__pycache__/lsp_tools.cpython-314.pyc +0 -0
  367. package/onecode/agent/tools/__pycache__/mcp_tools.cpython-314.pyc +0 -0
  368. package/onecode/agent/tools/__pycache__/permission_handler.cpython-314.pyc +0 -0
  369. package/onecode/agent/tools/__pycache__/permissions.cpython-314.pyc +0 -0
  370. package/onecode/agent/tools/__pycache__/protocol.cpython-314.pyc +0 -0
  371. package/onecode/agent/tools/__pycache__/registry.cpython-314.pyc +0 -0
  372. package/onecode/agent/tools/__pycache__/sandbox.cpython-314.pyc +0 -0
  373. package/onecode/agent/tools/__pycache__/schema_validation.cpython-314.pyc +0 -0
  374. package/onecode/agent/tools/__pycache__/skill_tools.cpython-314.pyc +0 -0
  375. package/onecode/agent/tools/__pycache__/todo_tools.cpython-314.pyc +0 -0
  376. package/onecode/agent/tools/__pycache__/web_tools.cpython-314.pyc +0 -0
  377. package/onecode/agent/tools/agent_tools.py +128 -0
  378. package/onecode/agent/tools/apply_patch_tool.py +138 -0
  379. package/onecode/agent/tools/bash_tool.py +121 -0
  380. package/onecode/agent/tools/communication_tools.py +154 -0
  381. package/onecode/agent/tools/config_tool.py +130 -0
  382. package/onecode/agent/tools/cron_tools.py +178 -0
  383. package/onecode/agent/tools/errors.py +17 -0
  384. package/onecode/agent/tools/file_ops.py +221 -0
  385. package/onecode/agent/tools/file_tools.py +225 -0
  386. package/onecode/agent/tools/git_tools.py +88 -0
  387. package/onecode/agent/tools/lsp_tools.py +404 -0
  388. package/onecode/agent/tools/mcp_tools.py +211 -0
  389. package/onecode/agent/tools/permission_handler.py +46 -0
  390. package/onecode/agent/tools/permissions.py +77 -0
  391. package/onecode/agent/tools/protocol.py +20 -0
  392. package/onecode/agent/tools/registry.py +210 -0
  393. package/onecode/agent/tools/sandbox.py +460 -0
  394. package/onecode/agent/tools/schema_validation.py +100 -0
  395. package/onecode/agent/tools/skill_tools.py +52 -0
  396. package/onecode/agent/tools/todo_tools.py +208 -0
  397. package/onecode/agent/tools/web_tools.py +530 -0
  398. package/onecode/agent/turn_record.py +28 -0
  399. package/onecode/builtin_skills/agent-browser/SKILL.md +212 -0
  400. package/onecode/builtin_skills/agent-browser/skill.yaml +8 -0
  401. package/onecode/builtin_skills/cloudbase/SKILL.md +162 -0
  402. package/onecode/builtin_skills/cloudbase/skill.yaml +7 -0
  403. package/onecode/builtin_skills/git/SKILL.md +64 -0
  404. package/onecode/builtin_skills/git/skill.yaml +7 -0
  405. package/onecode/builtin_skills/shell/SKILL.md +58 -0
  406. package/onecode/builtin_skills/shell/skill.yaml +8 -0
  407. package/onecode/builtin_skills/skill-creator/SKILL.md +106 -0
  408. package/onecode/builtin_skills/skill-creator/skill.yaml +8 -0
  409. package/onecode/cli.py +1325 -0
  410. package/onecode/codebase/__init__.py +69 -0
  411. package/onecode/codebase/__pycache__/__init__.cpython-314.pyc +0 -0
  412. package/onecode/codebase/__pycache__/chunker.cpython-314.pyc +0 -0
  413. package/onecode/codebase/__pycache__/indexer.cpython-314.pyc +0 -0
  414. package/onecode/codebase/__pycache__/retriever.cpython-314.pyc +0 -0
  415. package/onecode/codebase/__pycache__/storage.cpython-314.pyc +0 -0
  416. package/onecode/codebase/__pycache__/tools.cpython-314.pyc +0 -0
  417. package/onecode/codebase/chunker.py +125 -0
  418. package/onecode/codebase/indexer.py +154 -0
  419. package/onecode/codebase/retriever.py +240 -0
  420. package/onecode/codebase/storage.py +153 -0
  421. package/onecode/codebase/tools.py +59 -0
  422. package/onecode/commands.py +673 -0
  423. package/onecode/config.py +539 -0
  424. package/onecode/config_screen.py +486 -0
  425. package/onecode/cron/__init__.py +3 -0
  426. package/onecode/lsp/__init__.py +3 -0
  427. package/onecode/mcp/__init__.py +11 -0
  428. package/onecode/mcp/__pycache__/__init__.cpython-314.pyc +0 -0
  429. package/onecode/mcp/__pycache__/client.cpython-314.pyc +0 -0
  430. package/onecode/mcp/__pycache__/cloudbase.cpython-314.pyc +0 -0
  431. package/onecode/mcp/__pycache__/config.cpython-314.pyc +0 -0
  432. package/onecode/mcp/__pycache__/manager.cpython-314.pyc +0 -0
  433. package/onecode/mcp/__pycache__/oauth.cpython-314.pyc +0 -0
  434. package/onecode/mcp/client.py +305 -0
  435. package/onecode/mcp/cloudbase.py +188 -0
  436. package/onecode/mcp/config.py +363 -0
  437. package/onecode/mcp/manager.py +603 -0
  438. package/onecode/mcp/oauth.py +199 -0
  439. package/onecode/memory/__init__.py +106 -0
  440. package/onecode/memory/__pycache__/__init__.cpython-314.pyc +0 -0
  441. package/onecode/memory/__pycache__/backend.cpython-314.pyc +0 -0
  442. package/onecode/memory/__pycache__/pyramid.cpython-314.pyc +0 -0
  443. package/onecode/memory/__pycache__/recall.cpython-314.pyc +0 -0
  444. package/onecode/memory/backend.py +179 -0
  445. package/onecode/memory/pyramid.py +145 -0
  446. package/onecode/memory/recall.py +322 -0
  447. package/onecode/migrate.py +122 -0
  448. package/onecode/models/__init__.py +4 -0
  449. package/onecode/models/__pycache__/__init__.cpython-314.pyc +0 -0
  450. package/onecode/models/__pycache__/errors.cpython-314.pyc +0 -0
  451. package/onecode/models/__pycache__/messages.cpython-314.pyc +0 -0
  452. package/onecode/models/__pycache__/ollama_manager.cpython-314.pyc +0 -0
  453. package/onecode/models/__pycache__/provider.cpython-314.pyc +0 -0
  454. package/onecode/models/__pycache__/registry.cpython-314.pyc +0 -0
  455. package/onecode/models/errors.py +104 -0
  456. package/onecode/models/messages.py +750 -0
  457. package/onecode/models/ollama_manager.py +167 -0
  458. package/onecode/models/provider.py +730 -0
  459. package/onecode/models/providers/__init__.py +17 -0
  460. package/onecode/models/providers/__pycache__/__init__.cpython-314.pyc +0 -0
  461. package/onecode/models/providers/__pycache__/anthropic_provider.cpython-314.pyc +0 -0
  462. package/onecode/models/providers/__pycache__/deepseek_provider.cpython-314.pyc +0 -0
  463. package/onecode/models/providers/__pycache__/glm_provider.cpython-314.pyc +0 -0
  464. package/onecode/models/providers/__pycache__/minimax_provider.cpython-314.pyc +0 -0
  465. package/onecode/models/providers/__pycache__/minimaxi_provider.cpython-314.pyc +0 -0
  466. package/onecode/models/providers/__pycache__/ollama_provider.cpython-314.pyc +0 -0
  467. package/onecode/models/providers/__pycache__/openai_provider.cpython-314.pyc +0 -0
  468. package/onecode/models/providers/anthropic_provider.py +286 -0
  469. package/onecode/models/providers/deepseek_provider.py +221 -0
  470. package/onecode/models/providers/glm_provider.py +219 -0
  471. package/onecode/models/providers/minimax_provider.py +182 -0
  472. package/onecode/models/providers/minimaxi_provider.py +426 -0
  473. package/onecode/models/providers/ollama_provider.py +183 -0
  474. package/onecode/models/providers/openai_provider.py +234 -0
  475. package/onecode/models/registry.py +349 -0
  476. package/onecode/server/__init__.py +3 -0
  477. package/onecode/server/app.py +251 -0
  478. package/onecode/skills/__init__.py +11 -0
  479. package/onecode/skills/__pycache__/__init__.cpython-314.pyc +0 -0
  480. package/onecode/skills/__pycache__/argument_substitution.cpython-314.pyc +0 -0
  481. package/onecode/skills/__pycache__/bootstrap.cpython-314.pyc +0 -0
  482. package/onecode/skills/__pycache__/create.cpython-314.pyc +0 -0
  483. package/onecode/skills/__pycache__/frontmatter.cpython-314.pyc +0 -0
  484. package/onecode/skills/__pycache__/loader.cpython-314.pyc +0 -0
  485. package/onecode/skills/__pycache__/model.cpython-314.pyc +0 -0
  486. package/onecode/skills/argument_substitution.py +38 -0
  487. package/onecode/skills/bootstrap.py +37 -0
  488. package/onecode/skills/create.py +56 -0
  489. package/onecode/skills/frontmatter.py +33 -0
  490. package/onecode/skills/loader.py +215 -0
  491. package/onecode/skills/manager.py +75 -0
  492. package/onecode/skills/model.py +43 -0
  493. package/onecode/storage/__init__.py +0 -0
  494. package/onecode/storage/activity.py +63 -0
  495. package/onecode/storage/project.py +54 -0
  496. package/onecode/storage/session.py +112 -0
  497. package/onecode/tasks/__init__.py +26 -0
  498. package/onecode/tasks/manager.py +232 -0
  499. package/onecode/tasks/models.py +240 -0
  500. package/onecode/trace/__init__.py +0 -0
  501. package/onecode/trace/tracer.py +173 -0
  502. package/onecode/utils/__init__.py +0 -0
  503. package/onecode/utils/helpers.py +20 -0
  504. package/onecode/verification/__init__.py +5 -0
  505. package/onecode/verification/__pycache__/__init__.cpython-314.pyc +0 -0
  506. package/onecode/verification/__pycache__/aggregation.cpython-314.pyc +0 -0
  507. package/onecode/verification/__pycache__/loop.cpython-314.pyc +0 -0
  508. package/onecode/verification/__pycache__/policy.cpython-314.pyc +0 -0
  509. package/onecode/verification/aggregation.py +53 -0
  510. package/onecode/verification/gates/__init__.py +6 -0
  511. package/onecode/verification/gates/__pycache__/__init__.cpython-314.pyc +0 -0
  512. package/onecode/verification/gates/__pycache__/base.cpython-314.pyc +0 -0
  513. package/onecode/verification/gates/__pycache__/lint_gate.cpython-314.pyc +0 -0
  514. package/onecode/verification/gates/__pycache__/test_gate.cpython-314.pyc +0 -0
  515. package/onecode/verification/gates/__pycache__/type_gate.cpython-314.pyc +0 -0
  516. package/onecode/verification/gates/base.py +20 -0
  517. package/onecode/verification/gates/lint_gate.py +45 -0
  518. package/onecode/verification/gates/test_gate.py +62 -0
  519. package/onecode/verification/gates/type_gate.py +46 -0
  520. package/onecode/verification/loop.py +95 -0
  521. package/onecode/verification/policy.py +29 -0
  522. package/package.json +2 -2
  523. package/pyproject.toml +84 -0
  524. package/tui/__init__.py +46 -0
  525. package/tui/__main__.py +4 -0
  526. package/tui/__pycache__/__init__.cpython-314.pyc +0 -0
  527. package/tui/__pycache__/_loop.cpython-314.pyc +0 -0
  528. package/tui/__pycache__/_path_fuzzy_search.cpython-314.pyc +0 -0
  529. package/tui/__pycache__/_path_match.cpython-314.pyc +0 -0
  530. package/tui/__pycache__/agent.cpython-314.pyc +0 -0
  531. package/tui/__pycache__/agent_schema.cpython-314.pyc +0 -0
  532. package/tui/__pycache__/agents.cpython-314.pyc +0 -0
  533. package/tui/__pycache__/answer.cpython-314.pyc +0 -0
  534. package/tui/__pycache__/app.cpython-314.pyc +0 -0
  535. package/tui/__pycache__/atomic.cpython-314.pyc +0 -0
  536. package/tui/__pycache__/complete.cpython-314.pyc +0 -0
  537. package/tui/__pycache__/constants.cpython-314.pyc +0 -0
  538. package/tui/__pycache__/conversation_markdown.cpython-314.pyc +0 -0
  539. package/tui/__pycache__/db.cpython-314.pyc +0 -0
  540. package/tui/__pycache__/dec.cpython-314.pyc +0 -0
  541. package/tui/__pycache__/directory.cpython-314.pyc +0 -0
  542. package/tui/__pycache__/directory_watcher.cpython-314.pyc +0 -0
  543. package/tui/__pycache__/format_path.cpython-314.pyc +0 -0
  544. package/tui/__pycache__/fuzzy.cpython-314.pyc +0 -0
  545. package/tui/__pycache__/fuzzy_index.cpython-314.pyc +0 -0
  546. package/tui/__pycache__/history.cpython-314.pyc +0 -0
  547. package/tui/__pycache__/jsonrpc.cpython-314.pyc +0 -0
  548. package/tui/__pycache__/menus.cpython-314.pyc +0 -0
  549. package/tui/__pycache__/message_log.cpython-314.pyc +0 -0
  550. package/tui/__pycache__/messages.cpython-314.pyc +0 -0
  551. package/tui/__pycache__/path_complete.cpython-314.pyc +0 -0
  552. package/tui/__pycache__/path_filter.cpython-314.pyc +0 -0
  553. package/tui/__pycache__/paths.cpython-314.pyc +0 -0
  554. package/tui/__pycache__/pill.cpython-314.pyc +0 -0
  555. package/tui/__pycache__/platform_commands.cpython-314.pyc +0 -0
  556. package/tui/__pycache__/protocol.cpython-314.pyc +0 -0
  557. package/tui/__pycache__/session_tracker.cpython-314.pyc +0 -0
  558. package/tui/__pycache__/settings.cpython-314.pyc +0 -0
  559. package/tui/__pycache__/settings_schema.cpython-314.pyc +0 -0
  560. package/tui/__pycache__/shell.cpython-314.pyc +0 -0
  561. package/tui/__pycache__/shell_read.cpython-314.pyc +0 -0
  562. package/tui/__pycache__/slash_command.cpython-314.pyc +0 -0
  563. package/tui/__pycache__/version.cpython-314.pyc +0 -0
  564. package/tui/_loop.py +86 -0
  565. package/tui/_path_fuzzy_search.py +48 -0
  566. package/tui/_path_match.py +141 -0
  567. package/tui/about.py +95 -0
  568. package/tui/acp/__pycache__/agent.cpython-314.pyc +0 -0
  569. package/tui/acp/__pycache__/api.cpython-314.pyc +0 -0
  570. package/tui/acp/__pycache__/encode_tool_call_id.cpython-314.pyc +0 -0
  571. package/tui/acp/__pycache__/event_tap.cpython-314.pyc +0 -0
  572. package/tui/acp/__pycache__/messages.cpython-314.pyc +0 -0
  573. package/tui/acp/__pycache__/prompt.cpython-314.pyc +0 -0
  574. package/tui/acp/__pycache__/protocol.cpython-314.pyc +0 -0
  575. package/tui/acp/agent.py +1212 -0
  576. package/tui/acp/api.py +79 -0
  577. package/tui/acp/encode_tool_call_id.py +12 -0
  578. package/tui/acp/event_tap.py +107 -0
  579. package/tui/acp/messages.py +222 -0
  580. package/tui/acp/prompt.py +53 -0
  581. package/tui/acp/protocol.py +551 -0
  582. package/tui/agent.py +70 -0
  583. package/tui/agent_schema.py +76 -0
  584. package/tui/agents.py +46 -0
  585. package/tui/ansi/__init__.py +1 -0
  586. package/tui/ansi/__pycache__/__init__.cpython-314.pyc +0 -0
  587. package/tui/ansi/__pycache__/_ansi.cpython-314.pyc +0 -0
  588. package/tui/ansi/__pycache__/_ansi_colors.cpython-314.pyc +0 -0
  589. package/tui/ansi/__pycache__/_control_codes.cpython-314.pyc +0 -0
  590. package/tui/ansi/__pycache__/_keys.cpython-314.pyc +0 -0
  591. package/tui/ansi/__pycache__/_sgr_styles.cpython-314.pyc +0 -0
  592. package/tui/ansi/__pycache__/_stream_parser.cpython-314.pyc +0 -0
  593. package/tui/ansi/_ansi.py +1689 -0
  594. package/tui/ansi/_ansi_colors.py +264 -0
  595. package/tui/ansi/_control_codes.py +37 -0
  596. package/tui/ansi/_keys.py +251 -0
  597. package/tui/ansi/_sgr_styles.py +64 -0
  598. package/tui/ansi/_stream_parser.py +418 -0
  599. package/tui/answer.py +12 -0
  600. package/tui/app.py +1195 -0
  601. package/tui/atomic.py +37 -0
  602. package/tui/cli.py +208 -0
  603. package/tui/code_analyze.py +27 -0
  604. package/tui/complete.py +38 -0
  605. package/tui/constants.py +61 -0
  606. package/tui/conversation_markdown.py +30 -0
  607. package/tui/danger.py +372 -0
  608. package/tui/data/agents/ampcode.com.toml +52 -0
  609. package/tui/data/agents/augmentcode.com.toml +41 -0
  610. package/tui/data/agents/cdh.cloud-dev-harness.toml +23 -0
  611. package/tui/data/agents/claude.com.toml +41 -0
  612. package/tui/data/agents/copilot.github.com.toml +29 -0
  613. package/tui/data/agents/crowai.dev.toml +36 -0
  614. package/tui/data/agents/cursor.com.toml +38 -0
  615. package/tui/data/agents/deepagents.langchain.com +34 -0
  616. package/tui/data/agents/docker.com.toml +60 -0
  617. package/tui/data/agents/geminicli.com.toml +28 -0
  618. package/tui/data/agents/goose.ai.toml +52 -0
  619. package/tui/data/agents/inference.huggingface.co.toml +41 -0
  620. package/tui/data/agents/kimi.com.toml +35 -0
  621. package/tui/data/agents/kiro.dev.toml +43 -0
  622. package/tui/data/agents/onecode.dev.toml +22 -0
  623. package/tui/data/agents/openai.com.toml +53 -0
  624. package/tui/data/agents/openclaw.ai.toml +37 -0
  625. package/tui/data/agents/opencode.ai.toml +39 -0
  626. package/tui/data/agents/openhands.dev.toml +46 -0
  627. package/tui/data/agents/stakpak.dev.toml +62 -0
  628. package/tui/data/agents/vibe.mistral.ai.toml +31 -0
  629. package/tui/data/agents/vtcode.dev.toml +63 -0
  630. package/tui/data/images/frog.png +0 -0
  631. package/tui/data/sounds/question.wav +0 -0
  632. package/tui/data/sounds/turn-over.wav +0 -0
  633. package/tui/db.py +213 -0
  634. package/tui/dec.py +332 -0
  635. package/tui/diff_utils.py +131 -0
  636. package/tui/directory.py +234 -0
  637. package/tui/directory_suggester.py +93 -0
  638. package/tui/directory_watcher.py +231 -0
  639. package/tui/format_path.py +25 -0
  640. package/tui/fuzzy.py +140 -0
  641. package/tui/fuzzy_index.py +275 -0
  642. package/tui/gist.py +33 -0
  643. package/tui/history.py +138 -0
  644. package/tui/jsonrpc.py +599 -0
  645. package/tui/menus.py +14 -0
  646. package/tui/message_log.py +69 -0
  647. package/tui/messages.py +144 -0
  648. package/tui/option_content.py +51 -0
  649. package/tui/os.py +0 -0
  650. package/tui/path_complete.py +217 -0
  651. package/tui/path_filter.py +124 -0
  652. package/tui/paths.py +87 -0
  653. package/tui/pill.py +23 -0
  654. package/tui/platform_commands.py +52 -0
  655. package/tui/prompt/__pycache__/extract.cpython-314.pyc +0 -0
  656. package/tui/prompt/__pycache__/resource.cpython-314.pyc +0 -0
  657. package/tui/prompt/extract.py +19 -0
  658. package/tui/prompt/resource.py +68 -0
  659. package/tui/protocol.py +28 -0
  660. package/tui/screens/__init__.py +0 -0
  661. package/tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
  662. package/tui/screens/__pycache__/diff_screen.cpython-314.pyc +0 -0
  663. package/tui/screens/__pycache__/main.cpython-314.pyc +0 -0
  664. package/tui/screens/__pycache__/sessions.cpython-314.pyc +0 -0
  665. package/tui/screens/action_modal.py +94 -0
  666. package/tui/screens/agent_modal.py +175 -0
  667. package/tui/screens/command_edit_modal.py +74 -0
  668. package/tui/screens/diff_explorer_screen.py +419 -0
  669. package/tui/screens/diff_screen.py +84 -0
  670. package/tui/screens/ide_screen.py +461 -0
  671. package/tui/screens/main.py +397 -0
  672. package/tui/screens/main.tcss +662 -0
  673. package/tui/screens/permissions.py +404 -0
  674. package/tui/screens/permissions.tcss +72 -0
  675. package/tui/screens/projects.tcss +84 -0
  676. package/tui/screens/projects_app.py +341 -0
  677. package/tui/screens/projects_screen.py +277 -0
  678. package/tui/screens/session_resume_modal.py +172 -0
  679. package/tui/screens/session_resume_modal.tcss +35 -0
  680. package/tui/screens/sessions.py +171 -0
  681. package/tui/screens/sessions.tcss +119 -0
  682. package/tui/screens/settings.py +293 -0
  683. package/tui/screens/settings.tcss +113 -0
  684. package/tui/screens/store.py +502 -0
  685. package/tui/screens/store.tcss +274 -0
  686. package/tui/screens/tool_content_screen.py +59 -0
  687. package/tui/screens/trace.py +317 -0
  688. package/tui/session_tracker.py +123 -0
  689. package/tui/settings.py +353 -0
  690. package/tui/settings_schema.py +467 -0
  691. package/tui/shell.py +304 -0
  692. package/tui/shell_read.py +42 -0
  693. package/tui/slash_command.py +34 -0
  694. package/tui/tui.tcss +246 -0
  695. package/tui/twitter.py +38 -0
  696. package/tui/version.py +80 -0
  697. package/tui/visuals/__init__.py +0 -0
  698. package/tui/visuals/__pycache__/__init__.cpython-314.pyc +0 -0
  699. package/tui/visuals/__pycache__/columns.cpython-314.pyc +0 -0
  700. package/tui/visuals/columns.py +272 -0
  701. package/tui/widgets/__init__.py +0 -0
  702. package/tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
  703. package/tui/widgets/__pycache__/agent_response.cpython-314.pyc +0 -0
  704. package/tui/widgets/__pycache__/agent_thought.cpython-314.pyc +0 -0
  705. package/tui/widgets/__pycache__/aidlc.cpython-314.pyc +0 -0
  706. package/tui/widgets/__pycache__/ask_user.cpython-314.pyc +0 -0
  707. package/tui/widgets/__pycache__/condensed_path.cpython-314.pyc +0 -0
  708. package/tui/widgets/__pycache__/context_stats.cpython-314.pyc +0 -0
  709. package/tui/widgets/__pycache__/conversation.cpython-314.pyc +0 -0
  710. package/tui/widgets/__pycache__/diff_view.cpython-314.pyc +0 -0
  711. package/tui/widgets/__pycache__/flash.cpython-314.pyc +0 -0
  712. package/tui/widgets/__pycache__/future_text.cpython-314.pyc +0 -0
  713. package/tui/widgets/__pycache__/grid_select.cpython-314.pyc +0 -0
  714. package/tui/widgets/__pycache__/highlighted_textarea.cpython-314.pyc +0 -0
  715. package/tui/widgets/__pycache__/load_more.cpython-314.pyc +0 -0
  716. package/tui/widgets/__pycache__/menu.cpython-314.pyc +0 -0
  717. package/tui/widgets/__pycache__/modified_files.cpython-314.pyc +0 -0
  718. package/tui/widgets/__pycache__/non_selectable_label.cpython-314.pyc +0 -0
  719. package/tui/widgets/__pycache__/note.cpython-314.pyc +0 -0
  720. package/tui/widgets/__pycache__/path_search.cpython-314.pyc +0 -0
  721. package/tui/widgets/__pycache__/pending_prompts.cpython-314.pyc +0 -0
  722. package/tui/widgets/__pycache__/plan.cpython-314.pyc +0 -0
  723. package/tui/widgets/__pycache__/project_directory_tree.cpython-314.pyc +0 -0
  724. package/tui/widgets/__pycache__/prompt.cpython-314.pyc +0 -0
  725. package/tui/widgets/__pycache__/question.cpython-314.pyc +0 -0
  726. package/tui/widgets/__pycache__/session_grid_select.cpython-314.pyc +0 -0
  727. package/tui/widgets/__pycache__/session_summary.cpython-314.pyc +0 -0
  728. package/tui/widgets/__pycache__/session_tabs.cpython-314.pyc +0 -0
  729. package/tui/widgets/__pycache__/shell_terminal.cpython-314.pyc +0 -0
  730. package/tui/widgets/__pycache__/side_bar.cpython-314.pyc +0 -0
  731. package/tui/widgets/__pycache__/slash_complete.cpython-314.pyc +0 -0
  732. package/tui/widgets/__pycache__/strike_text.cpython-314.pyc +0 -0
  733. package/tui/widgets/__pycache__/terminal.cpython-314.pyc +0 -0
  734. package/tui/widgets/__pycache__/throbber.cpython-314.pyc +0 -0
  735. package/tui/widgets/__pycache__/tool_call.cpython-314.pyc +0 -0
  736. package/tui/widgets/__pycache__/user_input.cpython-314.pyc +0 -0
  737. package/tui/widgets/acp_content.py +70 -0
  738. package/tui/widgets/agent_response.py +76 -0
  739. package/tui/widgets/agent_thought.py +167 -0
  740. package/tui/widgets/aidlc.py +334 -0
  741. package/tui/widgets/ask_user.py +604 -0
  742. package/tui/widgets/command_pane.py +223 -0
  743. package/tui/widgets/condensed_path.py +115 -0
  744. package/tui/widgets/context_stats.py +66 -0
  745. package/tui/widgets/conversation.py +3235 -0
  746. package/tui/widgets/danger_warning.py +65 -0
  747. package/tui/widgets/diff_view.py +69 -0
  748. package/tui/widgets/directory_input.py +8 -0
  749. package/tui/widgets/edit_field.py +206 -0
  750. package/tui/widgets/flash.py +81 -0
  751. package/tui/widgets/future_text.py +126 -0
  752. package/tui/widgets/grid_select.py +234 -0
  753. package/tui/widgets/highlighted_textarea.py +183 -0
  754. package/tui/widgets/load_more.py +24 -0
  755. package/tui/widgets/mandelbrot.py +294 -0
  756. package/tui/widgets/markdown_note.py +13 -0
  757. package/tui/widgets/menu.py +147 -0
  758. package/tui/widgets/model_status.py +230 -0
  759. package/tui/widgets/modified_files.py +315 -0
  760. package/tui/widgets/non_selectable_label.py +5 -0
  761. package/tui/widgets/note.py +18 -0
  762. package/tui/widgets/path_search.py +492 -0
  763. package/tui/widgets/pending_prompts.py +36 -0
  764. package/tui/widgets/plan.py +285 -0
  765. package/tui/widgets/project_directory_tree.py +102 -0
  766. package/tui/widgets/project_grid_select.py +68 -0
  767. package/tui/widgets/project_summary.py +33 -0
  768. package/tui/widgets/prompt.py +935 -0
  769. package/tui/widgets/question.py +360 -0
  770. package/tui/widgets/session_grid_select.py +230 -0
  771. package/tui/widgets/session_summary.py +115 -0
  772. package/tui/widgets/session_tabs.py +194 -0
  773. package/tui/widgets/shell_result.py +35 -0
  774. package/tui/widgets/shell_terminal.py +40 -0
  775. package/tui/widgets/side_bar.py +124 -0
  776. package/tui/widgets/slash_complete.py +235 -0
  777. package/tui/widgets/strike_text.py +70 -0
  778. package/tui/widgets/subagent.py +287 -0
  779. package/tui/widgets/subagent_screen.py +715 -0
  780. package/tui/widgets/terminal.py +576 -0
  781. package/tui/widgets/terminal_tool.py +360 -0
  782. package/tui/widgets/throbber.py +100 -0
  783. package/tui/widgets/tool_call.py +432 -0
  784. package/tui/widgets/user_input.py +23 -0
  785. package/tui/widgets/version.py +5 -0
  786. package/tui/widgets/welcome.py +31 -0
@@ -0,0 +1,1022 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import os
6
+ import tempfile
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ import yaml
11
+
12
+
13
+ AIDLC_VERSION = "4.0.0"
14
+
15
+
16
+ def _walk_up_parents(workspace_root: Path):
17
+ current = workspace_root.resolve()
18
+ yield current
19
+ try:
20
+ git_root = next(
21
+ (
22
+ p
23
+ for p in current.parents
24
+ if (p / ".git").exists() or (p / ".hg").exists()
25
+ ),
26
+ None,
27
+ )
28
+ if git_root:
29
+ yield git_root
30
+ except StopIteration:
31
+ pass
32
+
33
+
34
+ class CdhProjectLoader:
35
+ CDH_DIRNAME = ".cdh"
36
+ STATE_FILENAME = "state.json"
37
+ LAST_SESSION_FILENAME = "last_session.json"
38
+ TODOS_FILENAME = "todos.json"
39
+ PERMISSIONS_FILENAME = "permissions.json"
40
+ VALIDATE_HISTORY_FILENAME = "validate_history.json"
41
+ METRICS_FILENAME = "metrics.json"
42
+
43
+ STATE_VERSION = 2
44
+ SCHEMA_VERSION = "1.0"
45
+ _PHASE_SEQUENCE = ["init", "understand", "plan", "verify", "deliver"]
46
+
47
+ # ── discovery ──────────────────────────────────────────────
48
+
49
+ @staticmethod
50
+ def find_cdh_dir(workspace_root: Path) -> Optional[Path]:
51
+ for parent in _walk_up_parents(workspace_root):
52
+ candidate = parent / CdhProjectLoader.CDH_DIRNAME
53
+ if candidate.is_dir():
54
+ return candidate
55
+ return None
56
+
57
+ @staticmethod
58
+ def find_cdh_dir_for_todos(workspace_root: Path) -> Optional[Path]:
59
+ candidate = workspace_root.resolve() / CdhProjectLoader.CDH_DIRNAME
60
+ return candidate if candidate.is_dir() else None
61
+
62
+ # ── file readers ───────────────────────────────────────────
63
+
64
+ @staticmethod
65
+ def load_project_config(cdh_dir: Path) -> dict:
66
+ yaml_path = cdh_dir / "config.yaml"
67
+ if yaml_path.exists():
68
+ try:
69
+ return yaml.safe_load(yaml_path.read_text(encoding="utf-8")) or {}
70
+ except Exception:
71
+ pass
72
+ json_path = cdh_dir / "config.json"
73
+ if json_path.exists():
74
+ try:
75
+ return json.loads(json_path.read_text(encoding="utf-8"))
76
+ except Exception:
77
+ pass
78
+ return {}
79
+
80
+ @staticmethod
81
+ def load_project_state(cdh_dir: Path) -> dict:
82
+ state_path = cdh_dir / CdhProjectLoader.STATE_FILENAME
83
+ if state_path.exists():
84
+ try:
85
+ state = json.loads(state_path.read_text(encoding="utf-8"))
86
+ except Exception:
87
+ state = {}
88
+ else:
89
+ state = {}
90
+ return CdhProjectLoader._migrate_state(state)
91
+
92
+ @staticmethod
93
+ def save_state(cdh_dir: Path, state_data: dict) -> None:
94
+ path = cdh_dir / CdhProjectLoader.STATE_FILENAME
95
+ migrated = CdhProjectLoader._migrate_state(state_data)
96
+ tmp = path.with_suffix(".tmp")
97
+ tmp.write_text(
98
+ json.dumps(migrated, ensure_ascii=False, indent=2),
99
+ encoding="utf-8",
100
+ )
101
+ tmp.replace(path)
102
+
103
+ @staticmethod
104
+ def save_state_atomic(cdh_dir: Path, state_data: dict, timeout: float = 5.0) -> bool:
105
+ """Atomically update state with file locking.
106
+
107
+ Returns True if successful, False if lock could not be acquired.
108
+ """
109
+ import time
110
+ path = cdh_dir / CdhProjectLoader.STATE_FILENAME
111
+ lock_path = path.with_suffix(".lock")
112
+ start = time.time()
113
+ while lock_path.exists():
114
+ if time.time() - start > timeout:
115
+ return False
116
+ time.sleep(0.05)
117
+ try:
118
+ lock_path.touch()
119
+ migrated = CdhProjectLoader._migrate_state(state_data)
120
+ tmp = path.with_suffix(".tmp")
121
+ tmp.write_text(
122
+ json.dumps(migrated, ensure_ascii=False, indent=2),
123
+ encoding="utf-8",
124
+ )
125
+ tmp.replace(path)
126
+ return True
127
+ finally:
128
+ if lock_path.exists():
129
+ lock_path.unlink()
130
+
131
+ @staticmethod
132
+ def validate_state_schema(cdh_dir: Path) -> tuple[bool, list[str]]:
133
+ from cdh.state_schema import validate_state
134
+
135
+ state_path = cdh_dir / CdhProjectLoader.STATE_FILENAME
136
+ if not state_path.exists():
137
+ return False, [f"state file not found: {state_path}"]
138
+ try:
139
+ state = json.loads(state_path.read_text(encoding="utf-8"))
140
+ except (OSError, json.JSONDecodeError) as exc:
141
+ return False, [f"unable to read state.json: {exc}"]
142
+ return validate_state(state)
143
+
144
+
145
+ @staticmethod
146
+ def _atomic_write(path: Path, data: str) -> None:
147
+ tmp = path.with_suffix(".tmp")
148
+ tmp.write_text(data, encoding="utf-8")
149
+ tmp.replace(path)
150
+
151
+ # ── validate history / metrics persistence ────────────────
152
+
153
+ @staticmethod
154
+ def append_validate_history(cdh_dir: Path, entry: dict) -> None:
155
+ path = cdh_dir / CdhProjectLoader.VALIDATE_HISTORY_FILENAME
156
+ history = CdhProjectLoader.get_validate_history(cdh_dir)
157
+ history.append(entry)
158
+ CdhProjectLoader._atomic_write(
159
+ path,
160
+ json.dumps(history, ensure_ascii=False, indent=2),
161
+ )
162
+
163
+ @staticmethod
164
+ def get_validate_history(cdh_dir: Path) -> list:
165
+ path = cdh_dir / CdhProjectLoader.VALIDATE_HISTORY_FILENAME
166
+ if path.exists():
167
+ try:
168
+ data = json.loads(path.read_text(encoding="utf-8"))
169
+ return data if isinstance(data, list) else []
170
+ except Exception:
171
+ return []
172
+ return []
173
+
174
+ @staticmethod
175
+ def record_metrics(cdh_dir: Path, run_entry: dict) -> dict:
176
+ """Record an aggregated metrics entry derived from a validate run.
177
+
178
+ ``run_entry`` must contain: ``checks_run`` (list[str]), ``passed`` (bool),
179
+ ``duration_ms`` (int), ``failed_checks`` (list[str]).
180
+ Returns the updated metrics dict.
181
+ """
182
+ path = cdh_dir / CdhProjectLoader.METRICS_FILENAME
183
+ metrics = CdhProjectLoader.get_metrics(cdh_dir)
184
+
185
+ metrics["total_runs"] = metrics.get("total_runs", 0) + 1
186
+ metrics["last_run_timestamp"] = run_entry.get("timestamp", "")
187
+
188
+ per_check = metrics.setdefault("per_check", {})
189
+ for check in run_entry.get("checks_run", []):
190
+ slot = per_check.setdefault(
191
+ check,
192
+ {
193
+ "runs": 0,
194
+ "passes": 0,
195
+ "fails": 0,
196
+ "total_duration_ms": 0,
197
+ "first_failure_at": None,
198
+ },
199
+ )
200
+ slot["runs"] += 1
201
+ if run_entry.get("passed"):
202
+ slot["passes"] += 1
203
+ else:
204
+ slot["fails"] += 1
205
+ if slot["first_failure_at"] is None:
206
+ slot["first_failure_at"] = run_entry.get("timestamp", "")
207
+
208
+ avg_duration = run_entry.get("duration_ms", 0)
209
+ runs_so_far = metrics["total_runs"]
210
+ if runs_so_far > 0:
211
+ metrics["average_duration_ms"] = round(
212
+ (metrics.get("average_duration_ms", 0) * (runs_so_far - 1) + avg_duration)
213
+ / runs_so_far
214
+ )
215
+
216
+ mttd = metrics.setdefault("mttd_per_check", {})
217
+ for failed in run_entry.get("failed_checks", []):
218
+ slot = per_check.get(failed, {})
219
+ if slot.get("first_failure_at") and slot.get("fails", 0) > 0:
220
+ # MTTD proxy: average ms-to-first-failure across runs so far.
221
+ # We accumulate total ms at first failure for this check.
222
+ mttd.setdefault(failed, {"accumulated_ms": 0, "samples": 0})
223
+ mttd[failed]["accumulated_ms"] += avg_duration
224
+ mttd[failed]["samples"] += 1
225
+
226
+ for check, slot_mttd in mttd.items():
227
+ samples = slot_mttd.get("samples", 0)
228
+ if samples > 0:
229
+ slot_mttd["mttd_ms"] = round(
230
+ slot_mttd["accumulated_ms"] / samples
231
+ )
232
+
233
+ CdhProjectLoader._atomic_write(
234
+ path,
235
+ json.dumps(metrics, ensure_ascii=False, indent=2),
236
+ )
237
+ return metrics
238
+
239
+ @staticmethod
240
+ def get_metrics(cdh_dir: Path) -> dict:
241
+ path = cdh_dir / CdhProjectLoader.METRICS_FILENAME
242
+ if path.exists():
243
+ try:
244
+ data = json.loads(path.read_text(encoding="utf-8"))
245
+ return data if isinstance(data, dict) else {}
246
+ except Exception:
247
+ return {}
248
+ return {}
249
+
250
+ @staticmethod
251
+ def save_last_session(cdh_dir: Path, session_data: dict) -> None:
252
+ path = cdh_dir / CdhProjectLoader.LAST_SESSION_FILENAME
253
+ CdhProjectLoader._atomic_write(
254
+ path,
255
+ json.dumps(session_data, ensure_ascii=False, indent=2),
256
+ )
257
+
258
+ @staticmethod
259
+ def load_last_session(cdh_dir: Path) -> dict:
260
+ path = cdh_dir / CdhProjectLoader.LAST_SESSION_FILENAME
261
+ if path.exists():
262
+ try:
263
+ return json.loads(path.read_text(encoding="utf-8"))
264
+ except Exception:
265
+ pass
266
+ return {}
267
+
268
+ # ── todo persistence ───────────────────────────────────────
269
+
270
+ @staticmethod
271
+ def save_todos(cdh_dir: Path, todos_data: dict) -> None:
272
+ path = cdh_dir / CdhProjectLoader.TODOS_FILENAME
273
+ CdhProjectLoader._atomic_write(
274
+ path,
275
+ json.dumps(todos_data, ensure_ascii=False, indent=2),
276
+ )
277
+
278
+ @staticmethod
279
+ def load_todos(cdh_dir: Path) -> dict:
280
+ path = cdh_dir / CdhProjectLoader.TODOS_FILENAME
281
+ if path.exists():
282
+ try:
283
+ return json.loads(path.read_text(encoding="utf-8"))
284
+ except Exception:
285
+ pass
286
+ return {}
287
+
288
+ # ── permission persistence ─────────────────────────────────
289
+
290
+ @staticmethod
291
+ def save_permissions(cdh_dir: Path, perm_data: dict) -> None:
292
+ path = cdh_dir / CdhProjectLoader.PERMISSIONS_FILENAME
293
+ CdhProjectLoader._atomic_write(
294
+ path,
295
+ json.dumps(perm_data, ensure_ascii=False, indent=2),
296
+ )
297
+
298
+ @staticmethod
299
+ def load_permissions(cdh_dir: Path) -> dict:
300
+ path = cdh_dir / CdhProjectLoader.PERMISSIONS_FILENAME
301
+ if path.exists():
302
+ try:
303
+ return json.loads(path.read_text(encoding="utf-8"))
304
+ except Exception:
305
+ pass
306
+ return {}
307
+
308
+ # ── public API ─────────────────────────────────────────────
309
+
310
+ @staticmethod
311
+ def load_for_workspace(workspace_root: Path) -> str:
312
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
313
+ if cdh_dir is None:
314
+ return ""
315
+
316
+ config = CdhProjectLoader.load_project_config(cdh_dir)
317
+ state = CdhProjectLoader.load_project_state(cdh_dir)
318
+
319
+ parts = ["## Project State (.cdh)"]
320
+ name = config.get("name", cdh_dir.parent.name)
321
+ parts.append(f"- Name: {name}")
322
+ parts.append(f"- Path: {cdh_dir.parent}")
323
+ phase = state.get("current_phase", config.get("phase", ""))
324
+ if phase:
325
+ parts.append(f"- Phase: {phase}")
326
+ platform = config.get("platform", "")
327
+ if platform:
328
+ parts.append(f"- Platform: {platform}")
329
+ if config:
330
+ parts.append(f"- Config: {json.dumps(config, ensure_ascii=False)}")
331
+ if state:
332
+ parts.append(f"- State: {json.dumps(state, ensure_ascii=False)}")
333
+
334
+ parts.append(
335
+ "\n**Important**: Work directly in the project root directory shown above. "
336
+ "Do NOT create a subdirectory named after the project — all files go "
337
+ "directly under the project root."
338
+ )
339
+
340
+ return "\n".join(parts)
341
+
342
+ @staticmethod
343
+ def is_aidlc_project(workspace_root: Path) -> bool:
344
+ """Check if workspace is an AI-DLC project."""
345
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
346
+ if cdh_dir is None:
347
+ return False
348
+ aidlc_yaml = cdh_dir.parent / "aidlc" / "project.yaml"
349
+ return aidlc_yaml.exists()
350
+
351
+ @staticmethod
352
+ def _get_agents_version(workspace_root: Path) -> str:
353
+ """Extract AI-DLC version from AGENTS.md header comment."""
354
+ agents_md = workspace_root / "AGENTS.md"
355
+ if not agents_md.exists():
356
+ return ""
357
+ try:
358
+ content = agents_md.read_text(encoding="utf-8")
359
+ import re
360
+ m = re.search(r"version\s+([\d.]+)", content)
361
+ return m.group(1) if m else ""
362
+ except Exception:
363
+ return ""
364
+
365
+ @staticmethod
366
+ def _get_skill_version(workspace_root: Path) -> str:
367
+ """Get current AI-DLC skill version from skill.yaml."""
368
+ skill_yaml_paths = [
369
+ workspace_root / "ai-dlc-skill" / "skill.yaml",
370
+ Path(__file__).resolve().parents[1] / "ai-dlc-skill" / "skill.yaml",
371
+ ]
372
+ for skill_yaml in skill_yaml_paths:
373
+ if skill_yaml.exists():
374
+ try:
375
+ import yaml as _yaml
376
+ data = _yaml.safe_load(skill_yaml.read_text(encoding="utf-8"))
377
+ return str(data.get("version", "") if data else "")
378
+ except Exception:
379
+ pass
380
+ return ""
381
+
382
+ @staticmethod
383
+ def _detect_phase_from_files(project_root: Path) -> dict | None:
384
+ """Detect suggested phase based on recent file changes.
385
+
386
+ Returns dict with 'phase', 'label', 'reason' if detection matches,
387
+ otherwise None.
388
+ """
389
+ import time
390
+
391
+ def _match_recent(base: Path, pattern: str) -> bool:
392
+ """Check if any file matching pattern was modified in last 24h."""
393
+ if "/*/" in pattern:
394
+ prefix, suffix = pattern.split("/*/", 1)
395
+ suffix = suffix.rstrip("/") if suffix else ""
396
+ base_path = project_root / prefix
397
+ if not base_path.is_dir():
398
+ return False
399
+ for parent in base_path.iterdir():
400
+ if parent.is_dir():
401
+ if suffix:
402
+ for f in parent.glob(suffix + "/*"):
403
+ if f.is_file() and f.stat().st_mtime > cutoff:
404
+ return True
405
+ else:
406
+ if parent.stat().st_mtime > cutoff:
407
+ return True
408
+ elif pattern.endswith("/"):
409
+ path = project_root / pattern.rstrip("/")
410
+ if path.is_dir():
411
+ for f in path.rglob("*"):
412
+ if f.is_file() and f.stat().st_mtime > cutoff:
413
+ return True
414
+ else:
415
+ path = project_root / pattern
416
+ if path.is_file() and path.stat().st_mtime > cutoff:
417
+ return True
418
+ return False
419
+
420
+ # File patterns that indicate each phase (first match wins)
421
+ phase_indicators = [
422
+ ("understand", "Understand", [
423
+ ("aidlc/requirements.md", "New requirements document"),
424
+ ]),
425
+ ("plan", "Plan", [
426
+ ("aidlc/openspec/changes/", "Spec delta ready for planning"),
427
+ ("aidlc/design/", "Design documents"),
428
+ ]),
429
+ ("verify", "Verify", [
430
+ ("apps/*/features/", "New BDD feature files"),
431
+ ("aidlc/contracts/", "Contract changes need verification"),
432
+ ]),
433
+ ]
434
+
435
+ cutoff = time.time() - 86400 # files modified in last 24h
436
+
437
+ for phase, label, patterns in phase_indicators:
438
+ for pattern, reason in patterns:
439
+ if _match_recent(project_root, pattern):
440
+ return {
441
+ "phase": phase,
442
+ "label": label,
443
+ "reason": f"Detected recent changes in {pattern}",
444
+ }
445
+
446
+ return None
447
+
448
+ @staticmethod
449
+ def analyze_user_intent(user_input: str, workspace_root: Path) -> dict | None:
450
+ """Analyze user input and suggest AI-DLC complexity level and phases.
451
+
452
+ Returns dict with:
453
+ - level: L1-L5 complexity level
454
+ - phases: list of recommended phases
455
+ - reason: explanation of why these phases are recommended
456
+ - confidence: 0.0-1.0 confidence score
457
+ """
458
+ import re
459
+
460
+ user_lower = user_input.lower()
461
+ # Use substring matching for keyword detection
462
+ user_input_lower = user_input.lower()
463
+
464
+ level = None
465
+ phases = []
466
+ reasons = []
467
+ confidence = 0.5
468
+
469
+ # Component detection from FR prefixes
470
+ fr_prefixes = re.findall(r'\b(NATIVE|DESKTOP|WEB|BE|WXA|MYA|TTA|INT)-FR-\d+', user_input, re.IGNORECASE)
471
+ if fr_prefixes:
472
+ components_affected = len(set(fr_prefixes))
473
+ if components_affected >= 3:
474
+ level = "L4"
475
+ phases = ["understand", "plan", "verify", "deliver"]
476
+ reasons.append(f"涉及 {components_affected} 个组件的跨栈变更")
477
+ confidence = 0.9
478
+ elif components_affected == 2:
479
+ level = "L3"
480
+ phases = ["understand", "plan", "verify"]
481
+ reasons.append(f"涉及 {components_affected} 个组件的集成变更")
482
+ confidence = 0.85
483
+ else:
484
+ level = "L2"
485
+ phases = ["understand", "verify"]
486
+ reasons.append("单组件功能变更")
487
+ confidence = 0.8
488
+
489
+ # Keyword-based complexity detection
490
+ bug_keywords = ["bug", "fix", "修复", "错误", "defect", "patch", "修"]
491
+ feature_keywords = ["feature", "新功能", "新增", "add", "implement", "功能", "实现"]
492
+ deploy_keywords = ["deploy", "部署", "release", "发布", "上线", "production"]
493
+ refactor_keywords = ["refactor", "重构", "migration", "迁移", "架构"]
494
+
495
+ if level is None:
496
+ if any(k in user_input_lower for k in bug_keywords):
497
+ level = "L1"
498
+ phases = ["verify"]
499
+ reasons.append("Bug fix")
500
+ confidence = 0.7
501
+ elif any(k in user_input_lower for k in deploy_keywords):
502
+ level = "L4"
503
+ phases = ["understand", "plan", "verify", "deliver"]
504
+ reasons.append("包含部署的生产变更")
505
+ confidence = 0.85
506
+ elif any(k in user_input_lower for k in refactor_keywords):
507
+ level = "L5"
508
+ phases = ["plan", "verify"]
509
+ reasons.append("架构重构")
510
+ confidence = 0.75
511
+ elif any(k in user_input_lower for k in feature_keywords):
512
+ # Check if multi-component
513
+ component_dirs = ["native", "desktop", "web", "backend", "wxa", "mya", "tta"]
514
+ mentioned_components = [c for c in component_dirs if c in user_input_lower]
515
+ if len(mentioned_components) >= 2:
516
+ level = "L3"
517
+ phases = ["understand", "plan", "verify"]
518
+ reasons.append(f"多组件功能: {', '.join(mentioned_components)}")
519
+ else:
520
+ level = "L2"
521
+ phases = ["understand", "verify"]
522
+ reasons.append("单组件功能")
523
+ confidence = 0.7
524
+ else:
525
+ level = "L2"
526
+ phases = ["understand", "verify"]
527
+ reasons.append("一般开发任务")
528
+ confidence = 0.6
529
+
530
+ # Contract detection
531
+ if "contract" in user_lower or "api" in user_lower or "接口" in user_input:
532
+ if level in ("L2", "L3"):
533
+ level = "L3"
534
+ if "understand" not in phases:
535
+ phases.insert(0, "understand")
536
+ if "plan" not in phases:
537
+ phases.insert(1, "plan")
538
+ reasons.append("涉及接口合约变更")
539
+
540
+ return {
541
+ "level": level,
542
+ "phases": phases,
543
+ "reasons": reasons,
544
+ "confidence": confidence,
545
+ "suggestion": f"[{level}] {' → '.join(phases)}",
546
+ }
547
+
548
+ @staticmethod
549
+ def load_aidlc_nudge(workspace_root: Path, user_intent: str = "") -> str:
550
+ """Generate AI-DLC contextual nudge based on project state.
551
+
552
+ Returns an empty string if not an AI-DLC project or no nudge needed.
553
+ """
554
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
555
+ if cdh_dir is None:
556
+ return ""
557
+
558
+ project_root = cdh_dir.parent
559
+ aidlc_yaml = project_root / "aidlc" / "project.yaml"
560
+ if not aidlc_yaml.exists():
561
+ return ""
562
+
563
+ state = CdhProjectLoader.load_project_state(cdh_dir)
564
+ current_phase = state.get("current_phase", "")
565
+ completed_phases = state.get("completed_phases", [])
566
+ task_registry = state.get("task_registry", [])
567
+
568
+ pending_tasks = [
569
+ t for t in task_registry
570
+ if isinstance(t, dict) and t.get("status") not in ("completed", "skipped")
571
+ ]
572
+
573
+ parts = ["<!-- AIDLC_NUDGE -->"]
574
+
575
+ # Version sync check
576
+ agents_ver = CdhProjectLoader._get_agents_version(project_root)
577
+ skill_ver = CdhProjectLoader._get_skill_version(project_root)
578
+ if agents_ver and skill_ver and agents_ver != skill_ver:
579
+ parts.append(f"\n## AI-DLC: Version Mismatch ⚠️\n")
580
+ parts.append(f"- Project AGENTS.md: **{agents_ver}**\n")
581
+ parts.append(f"- Latest skill: **{skill_ver}**\n")
582
+ parts.append(f"\nRun `cdh aidlc sync` to update AGENTS.md.\n")
583
+
584
+ # File-based phase suggestion
585
+ phase_suggestion = CdhProjectLoader._detect_phase_from_files(project_root)
586
+
587
+ if not current_phase and not task_registry:
588
+ if phase_suggestion:
589
+ parts.append(f"\n## AI-DLC: Detected Work → {phase_suggestion['label']}\n")
590
+ parts.append(f"{phase_suggestion['reason']}\n")
591
+ parts.append(f"\nSay 'start {phase_suggestion['phase']}' to begin.\n")
592
+ else:
593
+ parts.append("\n## AI-DLC: Ready to Start\n")
594
+ parts.append("This is an AI-DLC project. To begin a new development cycle:\n")
595
+ parts.append("- Describe your **intent** and I'll assess complexity (L1-L5)\n")
596
+ parts.append("- Or use `/aidlc phase understand` to start with the Understand phase\n")
597
+ parts.append("\n**Triggers**: mention `ai-dlc`, `lifecycle`, `BDD`, or `INT-FR` to activate\n")
598
+ return "".join(parts)
599
+
600
+ phase_labels = {
601
+ "init": "Initialized",
602
+ "understand": "Understand",
603
+ "plan": "Plan",
604
+ "verify": "Verify",
605
+ "deliver": "Deliver",
606
+ }
607
+
608
+ if pending_tasks:
609
+ task_intents = [t.get("intent", "Unknown")[:50] for t in pending_tasks[:3]]
610
+ parts.append(f"\n## AI-DLC: {len(pending_tasks)} Pending Task(s)\n")
611
+ for intent in task_intents:
612
+ parts.append(f"- {intent}...\n")
613
+ if current_phase:
614
+ label = phase_labels.get(current_phase, current_phase)
615
+ parts.append(f"\n**Current phase**: {label}\n")
616
+ parts.append("\nTo continue, describe what you want to work on.\n")
617
+ return "".join(parts)
618
+
619
+ if current_phase and current_phase not in completed_phases:
620
+ label = phase_labels.get(current_phase, current_phase)
621
+ parts.append(f"\n## AI-DLC: In Progress\n")
622
+ parts.append(f"**Current phase**: {label}\n")
623
+ if completed_phases:
624
+ completed_labels = [phase_labels.get(p, p) for p in completed_phases]
625
+ parts.append(f"**Completed**: {', '.join(completed_labels)}\n")
626
+ parts.append("\nTo advance, describe what you want to work on or say 'continue'.\n")
627
+ return "".join(parts)
628
+
629
+ # File-based phase suggestion (Level 2)
630
+ phase_suggestion = CdhProjectLoader._detect_phase_from_files(project_root)
631
+ if phase_suggestion:
632
+ parts.append(f"\n## AI-DLC: Detected Work → {phase_suggestion['label']}\n")
633
+ parts.append(f"{phase_suggestion['reason']}\n")
634
+ parts.append(f"\nSay 'start {phase_suggestion['phase']}' to begin.\n")
635
+
636
+ # Proactive intent analysis (Level 1) - only if user provided intent
637
+ if user_intent and len(user_intent) > 10:
638
+ intent_analysis = CdhProjectLoader.analyze_user_intent(user_intent, project_root)
639
+ if intent_analysis and intent_analysis.get("confidence", 0) >= 0.6:
640
+ parts.append(f"\n## AI-DLC: Intent Analysis\n")
641
+ parts.append(f"**Suggested**: `{intent_analysis['suggestion']}`\n")
642
+ for reason in intent_analysis.get("reasons", []):
643
+ parts.append(f"- {reason}\n")
644
+ parts.append(f"\nConfidence: {intent_analysis['confidence']:.0%}\n")
645
+ parts.append(f"\nSay 'start {intent_analysis['phases'][0]}' to begin.\n")
646
+
647
+ return ""
648
+
649
+ # ── intent matching & task registry ────────────────────────
650
+
651
+ @staticmethod
652
+ def _compute_intent_hash(intent: str) -> str:
653
+ """Compute a deterministic hash for intent matching."""
654
+ import hashlib
655
+ normalized = " ".join(intent.lower().split())
656
+ return hashlib.sha256(normalized.encode()).hexdigest()[:16]
657
+
658
+ @staticmethod
659
+ def match_intent(task_registry: list, new_intent: str) -> dict | None:
660
+ """Match new intent against existing tasks.
661
+
662
+ Returns dict with 'task', 'match_type' (exact/fuzzy/partial), 'pending_phases'
663
+ or None if no match.
664
+ """
665
+ if not task_registry:
666
+ return None
667
+
668
+ new_hash = CdhProjectLoader._compute_intent_hash(new_intent)
669
+
670
+ for task in task_registry:
671
+ if not isinstance(task, dict):
672
+ continue
673
+
674
+ existing_intent = task.get("intent", "")
675
+ existing_hash = CdhProjectLoader._compute_intent_hash(existing_intent)
676
+
677
+ if existing_hash == new_hash:
678
+ pending = [
679
+ p["name"] for p in task.get("phases", [])
680
+ if p.get("status") not in ("completed", "skipped")
681
+ ]
682
+ return {
683
+ "task": task,
684
+ "match_type": "exact",
685
+ "pending_phases": pending,
686
+ "task_id": task.get("id", ""),
687
+ }
688
+
689
+ new_words = set(new_intent.lower().split())
690
+ for task in task_registry:
691
+ if not isinstance(task, dict):
692
+ continue
693
+ existing_words = set(task.get("intent", "").lower().split())
694
+ overlap = new_words & existing_words
695
+ if len(overlap) >= 3 and len(overlap) / max(len(new_words), len(existing_words)) > 0.5:
696
+ pending = [
697
+ p["name"] for p in task.get("phases", [])
698
+ if p.get("status") not in ("completed", "skipped")
699
+ ]
700
+ return {
701
+ "task": task,
702
+ "match_type": "fuzzy",
703
+ "pending_phases": pending,
704
+ "task_id": task.get("id", ""),
705
+ }
706
+
707
+ return None
708
+
709
+ @staticmethod
710
+ def create_task(workspace_root: Path, intent: str, level: str, phases: list) -> dict | None:
711
+ """Create a new task in the registry."""
712
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
713
+ if cdh_dir is None:
714
+ return None
715
+
716
+ state = CdhProjectLoader.load_project_state(cdh_dir)
717
+ task_id = f"task-{CdhProjectLoader._compute_intent_hash(intent)}"
718
+
719
+ existing = CdhProjectLoader.match_intent(state.get("task_registry", []), intent)
720
+ if existing and existing.get("pending_phases") == []:
721
+ return None
722
+
723
+ new_task = {
724
+ "id": task_id,
725
+ "intent": intent[:200],
726
+ "level": level,
727
+ "status": "running",
728
+ "phases": [{"name": p, "status": "pending"} for p in phases],
729
+ }
730
+
731
+ state.setdefault("task_registry", []).append(new_task)
732
+ state["current_phase"] = phases[0] if phases else ""
733
+ CdhProjectLoader.save_state(cdh_dir, state)
734
+
735
+ return new_task
736
+
737
+ @staticmethod
738
+ def get_task_fingerprint(workspace_root: Path) -> str:
739
+ """Get fingerprint for current task state."""
740
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
741
+ if cdh_dir is None:
742
+ return ""
743
+ state = CdhProjectLoader.load_project_state(cdh_dir)
744
+ active_tasks = [
745
+ t for t in state.get("task_registry", [])
746
+ if isinstance(t, dict) and t.get("status") == "running"
747
+ ]
748
+ if not active_tasks:
749
+ return ""
750
+ return active_tasks[0].get("id", "")
751
+
752
+ # ── phase state management ─────────────────────────────────
753
+
754
+ @staticmethod
755
+ def get_phase_prerequisites(phase: str) -> list:
756
+ """Get prerequisite phases that must be completed before entering this phase."""
757
+ prereqs = {
758
+ "understand": [],
759
+ "plan": ["understand"],
760
+ "verify": ["understand"],
761
+ "deliver": ["plan", "verify"],
762
+ }
763
+ return prereqs.get(phase, [])
764
+
765
+ @staticmethod
766
+ def can_enter_phase(workspace_root: Path, phase: str) -> tuple[bool, str]:
767
+ """Check if it's valid to enter a phase (prerequisites met).
768
+
769
+ Returns (can_enter, reason).
770
+ """
771
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
772
+ if cdh_dir is None:
773
+ return False, "No .cdh directory found"
774
+
775
+ state = CdhProjectLoader.load_project_state(cdh_dir)
776
+ completed = set(state.get("completed_phases", []))
777
+ prereqs = CdhProjectLoader.get_phase_prerequisites(phase)
778
+
779
+ missing = [p for p in prereqs if p not in completed]
780
+ if missing:
781
+ return False, f"Prerequisites not met: {', '.join(missing)} must be completed first"
782
+
783
+ return True, ""
784
+
785
+ @staticmethod
786
+ def advance_phase(workspace_root: Path, phase: str) -> bool:
787
+ """Advance to the next AI-DLC phase. Only single-step forward allowed.
788
+
789
+ To reset: call with phase="init".
790
+ """
791
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
792
+ if cdh_dir is None:
793
+ return False
794
+ state = CdhProjectLoader.load_project_state(cdh_dir)
795
+ prev = state.get("current_phase", "")
796
+
797
+ if phase == prev:
798
+ return True
799
+
800
+ if phase == "init":
801
+ state["current_phase"] = "init"
802
+ state["completed_phases"] = []
803
+ state["gate_results"] = {}
804
+ CdhProjectLoader.save_state(cdh_dir, state)
805
+ return True
806
+
807
+ seq = CdhProjectLoader._PHASE_SEQUENCE
808
+ try:
809
+ prev_idx = seq.index(prev) if prev else -1
810
+ target_idx = seq.index(phase)
811
+ except ValueError:
812
+ return False
813
+
814
+ # Allow any forward jump (AI-DLC adaptive flow may skip phases)
815
+ if target_idx <= prev_idx:
816
+ return False
817
+
818
+ if prev and prev != "init" and prev in seq:
819
+ completed = state.get("completed_phases", [])
820
+ if prev not in completed:
821
+ completed.append(prev)
822
+ state["completed_phases"] = completed
823
+
824
+ state["current_phase"] = phase
825
+ CdhProjectLoader.save_state(cdh_dir, state)
826
+ return True
827
+
828
+ @staticmethod
829
+ def record_gate_result(
830
+ workspace_root: Path, gate_name: str, status: str, summary: str = ""
831
+ ) -> bool:
832
+ """Record a quality gate result."""
833
+ cdh_dir = CdhProjectLoader.find_cdh_dir(workspace_root)
834
+ if cdh_dir is None:
835
+ return False
836
+ state = CdhProjectLoader.load_project_state(cdh_dir)
837
+ state.setdefault("gate_results", {})[gate_name] = {
838
+ "status": status,
839
+ "summary": summary,
840
+ }
841
+ CdhProjectLoader.save_state(cdh_dir, state)
842
+ return True
843
+
844
+ # ── state migration & fingerprinting ───────────────────────
845
+
846
+ @staticmethod
847
+ def _migrate_state(state: dict) -> dict:
848
+ """Migrate legacy state dicts to current schema (idempotent)."""
849
+ if not isinstance(state, dict):
850
+ state = {}
851
+ if "state_version" not in state:
852
+ state["state_version"] = CdhProjectLoader.STATE_VERSION
853
+ if "fingerprint" not in state:
854
+ state["fingerprint"] = ""
855
+ if "task_registry" not in state:
856
+ state["task_registry"] = []
857
+ if "schema_version" not in state:
858
+ state["schema_version"] = CdhProjectLoader.SCHEMA_VERSION
859
+ return state
860
+
861
+ @staticmethod
862
+ def compute_task_fingerprint(intent: str, project_root: Path) -> str:
863
+ """Return SHA256 hex of (intent, project.yaml contents)[:24]."""
864
+ project_yaml = project_root / "project.yaml"
865
+ yaml_contents = ""
866
+ if project_yaml.exists():
867
+ try:
868
+ yaml_contents = project_yaml.read_text(encoding="utf-8")
869
+ except Exception:
870
+ yaml_contents = ""
871
+ payload = f"{intent}\n{yaml_contents}".encode("utf-8")
872
+ return hashlib.sha256(payload).hexdigest()[:24]
873
+
874
+ @staticmethod
875
+ def find_existing_task(cdh_dir: Path, fingerprint: str) -> Optional[dict]:
876
+ """Search task_registry for entry matching fingerprint."""
877
+ state = CdhProjectLoader.load_project_state(cdh_dir)
878
+ for entry in state.get("task_registry", []):
879
+ if isinstance(entry, dict) and entry.get("fingerprint") == fingerprint:
880
+ return entry
881
+ return None
882
+
883
+ @staticmethod
884
+ def register_task(cdh_dir: Path, intent: str, status: str) -> bool:
885
+ """Register or update a task in task_registry. Returns False on no-op."""
886
+ project_root = cdh_dir.parent
887
+ fingerprint = CdhProjectLoader.compute_task_fingerprint(intent, project_root)
888
+ state = CdhProjectLoader.load_project_state(cdh_dir)
889
+ registry = state.setdefault("task_registry", [])
890
+
891
+ for entry in registry:
892
+ if isinstance(entry, dict) and entry.get("fingerprint") == fingerprint:
893
+ if entry.get("status") == status:
894
+ return False
895
+ entry["status"] = status
896
+ state["fingerprint"] = fingerprint
897
+ CdhProjectLoader.save_state(cdh_dir, state)
898
+ return True
899
+
900
+ registry.append(
901
+ {
902
+ "fingerprint": fingerprint,
903
+ "intent": intent,
904
+ "status": status,
905
+ }
906
+ )
907
+ state["fingerprint"] = fingerprint
908
+ CdhProjectLoader.save_state(cdh_dir, state)
909
+ return True
910
+
911
+ # ── scaffolding ────────────────────────────────────────────
912
+
913
+ @staticmethod
914
+ def init_project(
915
+ workspace_root: Path,
916
+ name: str,
917
+ platform: str = "",
918
+ phase: str = "init",
919
+ ) -> Path:
920
+ workspace_root = workspace_root.resolve()
921
+ existing = CdhProjectLoader.find_cdh_dir(workspace_root)
922
+ if existing is not None:
923
+ if existing.parent != workspace_root:
924
+ import sys as _sys
925
+ print(
926
+ f"⚠ {workspace_root} is inside an existing project at "
927
+ f"{existing.parent}. Auto-correcting to use that project.",
928
+ file=_sys.stderr,
929
+ )
930
+ return existing
931
+ cdh_dir = existing
932
+ else:
933
+ cdh_dir = workspace_root / CdhProjectLoader.CDH_DIRNAME
934
+ cdh_dir.mkdir(parents=True, exist_ok=True)
935
+
936
+ config = {"name": name}
937
+ if platform:
938
+ config["platform"] = platform
939
+ if phase:
940
+ config["phase"] = phase
941
+ config_path = cdh_dir / "config.yaml"
942
+ config_path.write_text(yaml.dump(config, default_flow_style=False), encoding="utf-8")
943
+
944
+ state = {"current_phase": phase, "completed_phases": [], "gate_results": {}}
945
+ CdhProjectLoader.save_state(cdh_dir, state)
946
+
947
+ return cdh_dir
948
+
949
+ # ── incremental validation cache ──────────────────────────────
950
+
951
+ CACHE_FILENAME = "validate_cache.json"
952
+
953
+ @staticmethod
954
+ def load_validate_cache(cdh_dir: Path) -> dict:
955
+ """Load incremental validation cache."""
956
+ path = cdh_dir / CdhProjectLoader.CACHE_FILENAME
957
+ if path.exists():
958
+ try:
959
+ return json.loads(path.read_text(encoding="utf-8"))
960
+ except Exception:
961
+ pass
962
+ return {"version": 1, "entries": {}}
963
+
964
+ @staticmethod
965
+ def save_validate_cache(cdh_dir: Path, cache_data: dict) -> None:
966
+ """Save incremental validation cache atomically."""
967
+ path = cdh_dir / CdhProjectLoader.CACHE_FILENAME
968
+ CdhProjectLoader._atomic_write(
969
+ path,
970
+ json.dumps(cache_data, ensure_ascii=False, indent=2),
971
+ )
972
+
973
+ @staticmethod
974
+ def get_file_hash(file_path: Path) -> str:
975
+ """Get SHA256 hash of file content."""
976
+ import hashlib
977
+ try:
978
+ return hashlib.sha256(file_path.read_bytes()).hexdigest()[:16]
979
+ except Exception:
980
+ return ""
981
+
982
+ @staticmethod
983
+ def should_skip_validation(cdh_dir: Path, file_path: Path, check_name: str) -> bool:
984
+ """Check if validation can be skipped for a file based on cache."""
985
+ cache = CdhProjectLoader.load_validate_cache(cdh_dir)
986
+ entries = cache.get("entries", {})
987
+ file_key = str(file_path.relative_to(cdh_dir.parent))
988
+ file_hash = CdhProjectLoader.get_file_hash(file_path)
989
+ if not file_hash:
990
+ return False
991
+ entry = entries.get(file_key)
992
+ if not entry:
993
+ return False
994
+ return entry.get("hash") == file_hash and check_name in entry.get("passed_checks", [])
995
+
996
+ @staticmethod
997
+ def record_validation_pass(cdh_dir: Path, file_path: Path, check_name: str) -> None:
998
+ """Record that a file passed a specific validation check."""
999
+ cache = CdhProjectLoader.load_validate_cache(cdh_dir)
1000
+ entries = cache.setdefault("entries", {})
1001
+ file_key = str(file_path.relative_to(cdh_dir.parent))
1002
+ file_hash = CdhProjectLoader.get_file_hash(file_path)
1003
+ if not file_hash:
1004
+ return
1005
+ entry = entries.setdefault(file_key, {"hash": file_hash, "passed_checks": []})
1006
+ if entry["hash"] != file_hash:
1007
+ entry["hash"] = file_hash
1008
+ entry["passed_checks"] = []
1009
+ if check_name not in entry["passed_checks"]:
1010
+ entry["passed_checks"].append(check_name)
1011
+ CdhProjectLoader.save_validate_cache(cdh_dir, cache)
1012
+
1013
+ @staticmethod
1014
+ def invalidate_validation_cache(cdh_dir: Path, file_path: Path | None = None) -> None:
1015
+ """Invalidate cache for a specific file or all files."""
1016
+ if file_path is None:
1017
+ cache = {"version": 1, "entries": {}}
1018
+ else:
1019
+ cache = CdhProjectLoader.load_validate_cache(cdh_dir)
1020
+ file_key = str(file_path.relative_to(cdh_dir.parent))
1021
+ cache.get("entries", {}).pop(file_key, None)
1022
+ CdhProjectLoader.save_validate_cache(cdh_dir, cache)