@blxzer/cursor-trellis 0.4.2 → 0.4.3

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 (315) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +23 -14
  3. package/README.zh-CN.md +14 -10
  4. package/bin/smart-search.js +10 -2
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +36 -15
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/commands/init.d.ts +1 -1
  9. package/dist/commands/init.d.ts.map +1 -1
  10. package/dist/commands/init.js +42 -153
  11. package/dist/commands/init.js.map +1 -1
  12. package/dist/commands/migrate.d.ts +10 -0
  13. package/dist/commands/migrate.d.ts.map +1 -0
  14. package/dist/commands/migrate.js +38 -0
  15. package/dist/commands/migrate.js.map +1 -0
  16. package/dist/commands/update.d.ts +5 -0
  17. package/dist/commands/update.d.ts.map +1 -1
  18. package/dist/commands/update.js +137 -40
  19. package/dist/commands/update.js.map +1 -1
  20. package/dist/configurators/cursor.d.ts +2 -1
  21. package/dist/configurators/cursor.d.ts.map +1 -1
  22. package/dist/configurators/cursor.js +4 -2
  23. package/dist/configurators/cursor.js.map +1 -1
  24. package/dist/configurators/index.js +4 -4
  25. package/dist/configurators/index.js.map +1 -1
  26. package/dist/configurators/shared.d.ts +8 -0
  27. package/dist/configurators/shared.d.ts.map +1 -1
  28. package/dist/configurators/shared.js +27 -1
  29. package/dist/configurators/shared.js.map +1 -1
  30. package/dist/configurators/workflow.d.ts +0 -9
  31. package/dist/configurators/workflow.d.ts.map +1 -1
  32. package/dist/configurators/workflow.js +2 -4
  33. package/dist/configurators/workflow.js.map +1 -1
  34. package/dist/constants/paths.d.ts +9 -0
  35. package/dist/constants/paths.d.ts.map +1 -1
  36. package/dist/constants/paths.js +13 -0
  37. package/dist/constants/paths.js.map +1 -1
  38. package/dist/migrations/manifests/0.3.0.json +1 -1
  39. package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/skills-and-commands.md +2 -4
  40. package/dist/templates/common/bundled-skills/cstl-skill-creator/SKILL.md +10 -7
  41. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/authoring-rules.md +93 -19
  42. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/general-authoring.md +189 -0
  43. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/review-checklist.md +10 -5
  44. package/dist/templates/common/commands/continue.md +5 -4
  45. package/dist/templates/common/index.d.ts +20 -0
  46. package/dist/templates/common/index.d.ts.map +1 -1
  47. package/dist/templates/common/index.js +21 -2
  48. package/dist/templates/common/index.js.map +1 -1
  49. package/dist/templates/common/optional-skills/chrome-cdp/SKILL.md +179 -0
  50. package/dist/templates/common/optional-skills/chrome-cdp/examples/fetch-hook-api-capture.md +149 -0
  51. package/dist/templates/common/optional-skills/chrome-cdp/scripts/cdp.mjs +903 -0
  52. package/dist/templates/cursor/agents/cstl-research.md +1 -1
  53. package/dist/templates/cursor/fixtures/expected-rules.d.ts +1 -1
  54. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  55. package/dist/templates/cursor/fixtures/expected-rules.js +7 -24
  56. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  57. package/dist/templates/cursor/hooks.json +4 -0
  58. package/dist/templates/cursor/index.d.ts +1 -1
  59. package/dist/templates/cursor/index.js +1 -1
  60. package/dist/templates/cursor/rules/cstl-bootstrap.mdc +17 -0
  61. package/dist/templates/markdown/agents.md +15 -10
  62. package/dist/templates/markdown/framework/cursor-native-modes-guide.md.txt +12 -9
  63. package/dist/templates/markdown/framework/cursor-subagent-policy.md.txt +62 -126
  64. package/dist/templates/markdown/framework/dogfood-only-surfaces.md.txt +4 -6
  65. package/dist/templates/markdown/framework/execution-strategy.md.txt +2 -0
  66. package/dist/templates/markdown/framework/index.md.txt +4 -0
  67. package/dist/templates/markdown/framework/internal-skills-cursor-reachability.md.txt +4 -3
  68. package/dist/templates/markdown/framework/middleware-protocol.md.txt +83 -0
  69. package/dist/templates/markdown/framework/parallel-first-execution.md.txt +78 -0
  70. package/dist/templates/markdown/framework/release-boundary.md.txt +30 -0
  71. package/dist/templates/markdown/framework/retrieval-daily-guide.md.txt +1 -1
  72. package/dist/templates/markdown/framework/upgrade.md.txt +15 -0
  73. package/dist/templates/markdown/index.d.ts +4 -0
  74. package/dist/templates/markdown/index.d.ts.map +1 -1
  75. package/dist/templates/markdown/index.js +17 -0
  76. package/dist/templates/markdown/index.js.map +1 -1
  77. package/dist/templates/markdown/spec/guides/index.md.txt +0 -6
  78. package/dist/templates/shared-hooks/event-bridge.py +60 -0
  79. package/dist/templates/shared-hooks/index.d.ts +1 -1
  80. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  81. package/dist/templates/shared-hooks/index.js +1 -0
  82. package/dist/templates/shared-hooks/index.js.map +1 -1
  83. package/dist/templates/shared-hooks/session-start.py +18 -1
  84. package/dist/templates/trellis/CONTEXT.md +63 -3
  85. package/dist/templates/trellis/gitignore.txt +39 -39
  86. package/dist/templates/trellis/index.d.ts +12 -0
  87. package/dist/templates/trellis/index.d.ts.map +1 -1
  88. package/dist/templates/trellis/index.js +73 -15
  89. package/dist/templates/trellis/index.js.map +1 -1
  90. package/dist/templates/trellis/pool/README.md +12 -3
  91. package/dist/templates/trellis/pool/plan.md +24 -5
  92. package/dist/templates/trellis/scripts/common/adapter_middleware.py +158 -0
  93. package/dist/templates/trellis/scripts/common/artifact_locale.py +12 -3
  94. package/dist/templates/trellis/scripts/common/cursor_retrieval_env.py +5 -2
  95. package/dist/templates/trellis/scripts/common/full_quality.py +237 -0
  96. package/dist/templates/trellis/scripts/common/git.py +6 -0
  97. package/dist/templates/trellis/scripts/common/git_context.py +17 -5
  98. package/dist/templates/trellis/scripts/common/kernel_command.py +367 -0
  99. package/dist/templates/trellis/scripts/common/lite_context.py +280 -0
  100. package/dist/templates/trellis/scripts/common/ondemand_topology.py +225 -0
  101. package/dist/templates/trellis/scripts/common/parent_orchestration.py +1 -1
  102. package/dist/templates/trellis/scripts/common/pool_store.py +16 -4
  103. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +2 -2
  104. package/dist/templates/trellis/scripts/common/session_context.py +28 -2
  105. package/dist/templates/trellis/scripts/common/task_gates.py +73 -12
  106. package/dist/templates/trellis/scripts/common/task_map.py +42 -9
  107. package/dist/templates/trellis/scripts/common/task_store.py +286 -56
  108. package/dist/templates/trellis/scripts/common/test_adapter_middleware.py +56 -0
  109. package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +9 -0
  110. package/dist/templates/trellis/scripts/common/test_full_quality.py +197 -0
  111. package/dist/templates/trellis/scripts/common/test_kernel_command.py +326 -0
  112. package/dist/templates/trellis/scripts/common/test_lite_path.py +190 -0
  113. package/dist/templates/trellis/scripts/common/test_ondemand_topology.py +122 -0
  114. package/dist/templates/trellis/scripts/common/test_pool_store.py +17 -0
  115. package/dist/templates/trellis/scripts/common/test_task_store_kernel_patch.py +163 -0
  116. package/dist/templates/trellis/scripts/common/workflow_phase.py +3 -0
  117. package/dist/templates/trellis/scripts/get_context.py +1 -0
  118. package/dist/templates/trellis/scripts/pool.py +5 -0
  119. package/dist/templates/trellis/scripts/task.py +83 -7
  120. package/dist/templates/trellis/workflow.md +100 -18
  121. package/dist/utils/cursor-retrieval-env.d.ts +2 -1
  122. package/dist/utils/cursor-retrieval-env.d.ts.map +1 -1
  123. package/dist/utils/cursor-retrieval-env.js +3 -2
  124. package/dist/utils/cursor-retrieval-env.js.map +1 -1
  125. package/dist/utils/cursor2plus-residue-cleanup.d.ts +38 -0
  126. package/dist/utils/cursor2plus-residue-cleanup.d.ts.map +1 -0
  127. package/dist/utils/cursor2plus-residue-cleanup.js +145 -0
  128. package/dist/utils/cursor2plus-residue-cleanup.js.map +1 -0
  129. package/dist/utils/manifest-prune.d.ts.map +1 -1
  130. package/dist/utils/manifest-prune.js +7 -2
  131. package/dist/utils/manifest-prune.js.map +1 -1
  132. package/dist/utils/p36-upgrade.d.ts +62 -0
  133. package/dist/utils/p36-upgrade.d.ts.map +1 -0
  134. package/dist/utils/p36-upgrade.js +160 -0
  135. package/dist/utils/p36-upgrade.js.map +1 -0
  136. package/dist/utils/project-capabilities.d.ts +1 -12
  137. package/dist/utils/project-capabilities.d.ts.map +1 -1
  138. package/dist/utils/project-capabilities.js +3 -100
  139. package/dist/utils/project-capabilities.js.map +1 -1
  140. package/dist/utils/readiness.d.ts +10 -0
  141. package/dist/utils/readiness.d.ts.map +1 -1
  142. package/dist/utils/readiness.js +58 -26
  143. package/dist/utils/readiness.js.map +1 -1
  144. package/dist/utils/retrieval-agent-instructions.js +1 -1
  145. package/dist/utils/retrieval-agent-instructions.js.map +1 -1
  146. package/dist/utils/template-hash.d.ts.map +1 -1
  147. package/dist/utils/template-hash.js +11 -1
  148. package/dist/utils/template-hash.js.map +1 -1
  149. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  150. package/dist/utils/uninstall-scrubbers.js +52 -14
  151. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  152. package/dist/utils/update-rollout-report.d.ts +14 -0
  153. package/dist/utils/update-rollout-report.d.ts.map +1 -1
  154. package/dist/utils/update-rollout-report.js +1 -0
  155. package/dist/utils/update-rollout-report.js.map +1 -1
  156. package/dist/utils/workflow-ownership.d.ts +1 -1
  157. package/dist/utils/workflow-ownership.js +4 -4
  158. package/dist/utils/workflow-ownership.js.map +1 -1
  159. package/package.json +5 -4
  160. package/scripts/postinstall.js +6 -7
  161. package/dist/commands/campaign/canvas-render.d.ts +0 -68
  162. package/dist/commands/campaign/canvas-render.d.ts.map +0 -1
  163. package/dist/commands/campaign/canvas-render.js +0 -414
  164. package/dist/commands/campaign/canvas-render.js.map +0 -1
  165. package/dist/commands/campaign/compose.d.ts +0 -15
  166. package/dist/commands/campaign/compose.d.ts.map +0 -1
  167. package/dist/commands/campaign/compose.js +0 -110
  168. package/dist/commands/campaign/compose.js.map +0 -1
  169. package/dist/commands/campaign/index.d.ts +0 -13
  170. package/dist/commands/campaign/index.d.ts.map +0 -1
  171. package/dist/commands/campaign/index.js +0 -134
  172. package/dist/commands/campaign/index.js.map +0 -1
  173. package/dist/commands/campaign/kind-map.d.ts +0 -6
  174. package/dist/commands/campaign/kind-map.d.ts.map +0 -1
  175. package/dist/commands/campaign/kind-map.js +0 -21
  176. package/dist/commands/campaign/kind-map.js.map +0 -1
  177. package/dist/commands/campaign/mcp-server.d.ts +0 -6
  178. package/dist/commands/campaign/mcp-server.d.ts.map +0 -1
  179. package/dist/commands/campaign/mcp-server.js +0 -167
  180. package/dist/commands/campaign/mcp-server.js.map +0 -1
  181. package/dist/commands/campaign/render.d.ts +0 -5
  182. package/dist/commands/campaign/render.d.ts.map +0 -1
  183. package/dist/commands/campaign/render.js +0 -103
  184. package/dist/commands/campaign/render.js.map +0 -1
  185. package/dist/commands/campaign/trellis-load.d.ts +0 -13
  186. package/dist/commands/campaign/trellis-load.d.ts.map +0 -1
  187. package/dist/commands/campaign/trellis-load.js +0 -122
  188. package/dist/commands/campaign/trellis-load.js.map +0 -1
  189. package/dist/commands/campaign/types.d.ts +0 -61
  190. package/dist/commands/campaign/types.d.ts.map +0 -1
  191. package/dist/commands/campaign/types.js +0 -3
  192. package/dist/commands/campaign/types.js.map +0 -1
  193. package/dist/commands/goal/index.d.ts +0 -3
  194. package/dist/commands/goal/index.d.ts.map +0 -1
  195. package/dist/commands/goal/index.js +0 -159
  196. package/dist/commands/goal/index.js.map +0 -1
  197. package/dist/commands/goal/review.d.ts +0 -6
  198. package/dist/commands/goal/review.d.ts.map +0 -1
  199. package/dist/commands/goal/review.js +0 -28
  200. package/dist/commands/goal/review.js.map +0 -1
  201. package/dist/commands/rpc/client.d.ts +0 -51
  202. package/dist/commands/rpc/client.d.ts.map +0 -1
  203. package/dist/commands/rpc/client.js +0 -147
  204. package/dist/commands/rpc/client.js.map +0 -1
  205. package/dist/commands/rpc/index.d.ts +0 -10
  206. package/dist/commands/rpc/index.d.ts.map +0 -1
  207. package/dist/commands/rpc/index.js +0 -157
  208. package/dist/commands/rpc/index.js.map +0 -1
  209. package/dist/commands/rpc/serve.d.ts +0 -23
  210. package/dist/commands/rpc/serve.d.ts.map +0 -1
  211. package/dist/commands/rpc/serve.js +0 -60
  212. package/dist/commands/rpc/serve.js.map +0 -1
  213. package/dist/commands/rpc/smoke.d.ts +0 -16
  214. package/dist/commands/rpc/smoke.d.ts.map +0 -1
  215. package/dist/commands/rpc/smoke.js +0 -92
  216. package/dist/commands/rpc/smoke.js.map +0 -1
  217. package/dist/commands/sdk/index.d.ts +0 -9
  218. package/dist/commands/sdk/index.d.ts.map +0 -1
  219. package/dist/commands/sdk/index.js +0 -58
  220. package/dist/commands/sdk/index.js.map +0 -1
  221. package/dist/commands/sdk/run.d.ts +0 -42
  222. package/dist/commands/sdk/run.d.ts.map +0 -1
  223. package/dist/commands/sdk/run.js +0 -220
  224. package/dist/commands/sdk/run.js.map +0 -1
  225. package/dist/commands/sdk/status.d.ts +0 -14
  226. package/dist/commands/sdk/status.d.ts.map +0 -1
  227. package/dist/commands/sdk/status.js +0 -69
  228. package/dist/commands/sdk/status.js.map +0 -1
  229. package/dist/configurators/cursor2plus-local.d.ts +0 -6
  230. package/dist/configurators/cursor2plus-local.d.ts.map +0 -1
  231. package/dist/configurators/cursor2plus-local.js +0 -21
  232. package/dist/configurators/cursor2plus-local.js.map +0 -1
  233. package/dist/goal/action-packet.d.ts +0 -89
  234. package/dist/goal/action-packet.d.ts.map +0 -1
  235. package/dist/goal/action-packet.js +0 -113
  236. package/dist/goal/action-packet.js.map +0 -1
  237. package/dist/goal/audit.d.ts +0 -14
  238. package/dist/goal/audit.d.ts.map +0 -1
  239. package/dist/goal/audit.js +0 -12
  240. package/dist/goal/audit.js.map +0 -1
  241. package/dist/goal/constants.d.ts +0 -26
  242. package/dist/goal/constants.d.ts.map +0 -1
  243. package/dist/goal/constants.js +0 -24
  244. package/dist/goal/constants.js.map +0 -1
  245. package/dist/goal/paths.d.ts +0 -8
  246. package/dist/goal/paths.d.ts.map +0 -1
  247. package/dist/goal/paths.js +0 -36
  248. package/dist/goal/paths.js.map +0 -1
  249. package/dist/goal/preflight.d.ts +0 -21
  250. package/dist/goal/preflight.d.ts.map +0 -1
  251. package/dist/goal/preflight.js +0 -97
  252. package/dist/goal/preflight.js.map +0 -1
  253. package/dist/goal/review-seam.d.ts +0 -44
  254. package/dist/goal/review-seam.d.ts.map +0 -1
  255. package/dist/goal/review-seam.js +0 -153
  256. package/dist/goal/review-seam.js.map +0 -1
  257. package/dist/goal/reviewer-rules.d.ts +0 -13
  258. package/dist/goal/reviewer-rules.d.ts.map +0 -1
  259. package/dist/goal/reviewer-rules.js +0 -153
  260. package/dist/goal/reviewer-rules.js.map +0 -1
  261. package/dist/goal/reviewer.d.ts +0 -9
  262. package/dist/goal/reviewer.d.ts.map +0 -1
  263. package/dist/goal/reviewer.js +0 -54
  264. package/dist/goal/reviewer.js.map +0 -1
  265. package/dist/goal/runtime.d.ts +0 -32
  266. package/dist/goal/runtime.d.ts.map +0 -1
  267. package/dist/goal/runtime.js +0 -242
  268. package/dist/goal/runtime.js.map +0 -1
  269. package/dist/goal/sdk-client.d.ts +0 -10
  270. package/dist/goal/sdk-client.d.ts.map +0 -1
  271. package/dist/goal/sdk-client.js +0 -34
  272. package/dist/goal/sdk-client.js.map +0 -1
  273. package/dist/goal/state.d.ts +0 -40
  274. package/dist/goal/state.d.ts.map +0 -1
  275. package/dist/goal/state.js +0 -37
  276. package/dist/goal/state.js.map +0 -1
  277. package/dist/goal/task-triggers.d.ts +0 -15
  278. package/dist/goal/task-triggers.d.ts.map +0 -1
  279. package/dist/goal/task-triggers.js +0 -23
  280. package/dist/goal/task-triggers.js.map +0 -1
  281. package/dist/goal/walls.d.ts +0 -12
  282. package/dist/goal/walls.d.ts.map +0 -1
  283. package/dist/goal/walls.js +0 -65
  284. package/dist/goal/walls.js.map +0 -1
  285. package/dist/goal/worker-prompt.d.ts +0 -13
  286. package/dist/goal/worker-prompt.d.ts.map +0 -1
  287. package/dist/goal/worker-prompt.js +0 -41
  288. package/dist/goal/worker-prompt.js.map +0 -1
  289. package/dist/goal/worker.d.ts +0 -42
  290. package/dist/goal/worker.d.ts.map +0 -1
  291. package/dist/goal/worker.js +0 -180
  292. package/dist/goal/worker.js.map +0 -1
  293. package/dist/templates/common/bundled-skills/cstl-cursor2plus-setup/SKILL.md +0 -63
  294. package/dist/templates/cursor/commands/cursor2plus-setup.md +0 -82
  295. package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +0 -32
  296. package/dist/templates/cursor/rules/cstl-session-rename.mdc +0 -31
  297. package/dist/templates/cursor/rules/cstl-subagent-dispatch.mdc +0 -34
  298. package/dist/templates/cursor/rules/cstl-triage.mdc +0 -36
  299. package/dist/templates/cursor/rules/retrieval-routing.mdc +0 -78
  300. package/dist/templates/trellis/local/README.md +0 -85
  301. package/dist/templates/trellis/local/config.local.json.example +0 -6
  302. package/dist/templates/trellis/local/index.d.ts +0 -8
  303. package/dist/templates/trellis/local/index.d.ts.map +0 -1
  304. package/dist/templates/trellis/local/index.js +0 -15
  305. package/dist/templates/trellis/local/index.js.map +0 -1
  306. package/dist/templates/trellis/local/patch_wpelc8.py +0 -421
  307. package/dist/templates/trellis/local/smoke.py +0 -94
  308. package/dist/templates/trellis/local/subagent-models.json.example +0 -4
  309. package/dist/templates/trellis/local/trellis-task-models.json.example +0 -10
  310. package/dist/templates/trellis/local/trellis-task-models.json5.example +0 -45
  311. package/dist/templates/trellis/local/trellis_task_models_config.py +0 -200
  312. package/dist/utils/cursor-sdk-gate.d.ts +0 -10
  313. package/dist/utils/cursor-sdk-gate.d.ts.map +0 -1
  314. package/dist/utils/cursor-sdk-gate.js +0 -32
  315. package/dist/utils/cursor-sdk-gate.js.map +0 -1
@@ -0,0 +1,237 @@
1
+ """Stage 4 Full Quality helpers (controls, AC ledger, graded Independent Check).
2
+
3
+ Python projection of the Kernel Full Quality contract. Kernel remains the
4
+ writer; these helpers validate extras before archive and prefer persisted
5
+ required_controls over inferring Rigor from files.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import re
12
+ from pathlib import Path
13
+ from typing import Any
14
+
15
+ FULL_QUALITY_SOURCE = "full-quality-contract"
16
+ FULL_QUALITY_SCHEMA_VERSION = 1
17
+ FULL_BASELINE_CONTROLS = (
18
+ "definition",
19
+ "execution-contract",
20
+ "verification-plan",
21
+ "evidence",
22
+ "independent-check",
23
+ )
24
+ DEFAULT_CONTROL_SURFACES = {
25
+ "definition": "prd.md",
26
+ "execution-contract": "implement.md",
27
+ "verification-plan": "implement.md",
28
+ "evidence": "verify.md",
29
+ "design": "design.md",
30
+ }
31
+ DESIGN_RISK_SIGNALS = {
32
+ "architecture",
33
+ "public-contract",
34
+ "schema",
35
+ "security",
36
+ "secret",
37
+ "framework-semantics",
38
+ "runtime-semantics",
39
+ }
40
+ PLACEHOLDER_VALUE_RE = re.compile(
41
+ r"(?i)^(TBD|TODO|待定|待补充|N/?A|NA|NONE|-|\.\.\.)$"
42
+ )
43
+ AC_ITEM_RE = re.compile(r"(?im)^\s*-\s*\[[ xX]\]\s+(.+)$")
44
+
45
+
46
+ class FullQualityError(RuntimeError):
47
+ """Full Quality contract rejected a start/close request."""
48
+
49
+
50
+ def quality_fingerprint(payload: str) -> str:
51
+ digest = hashlib.sha256(payload.encode("utf-8")).hexdigest()
52
+ return f"sha256:{digest}"
53
+
54
+
55
+ def resolve_required_controls(
56
+ *,
57
+ rigor: str = "lite",
58
+ verification_profile: str | None = None,
59
+ risk_signals: list[str] | None = None,
60
+ policy_requires_design: bool = False,
61
+ explicit_controls: list[str] | None = None,
62
+ surfaces: dict[str, str] | None = None,
63
+ ) -> dict[str, Any]:
64
+ normalized = "full" if rigor == "full" else "lite"
65
+ signals = [str(item).strip().lower() for item in (risk_signals or []) if item]
66
+ design_required = normalized == "full" and (
67
+ policy_requires_design
68
+ or verification_profile == "architecture"
69
+ or any(signal in DESIGN_RISK_SIGNALS for signal in signals)
70
+ )
71
+ if explicit_controls:
72
+ controls = _unique(explicit_controls)
73
+ elif normalized == "full":
74
+ controls = list(FULL_BASELINE_CONTROLS)
75
+ if design_required:
76
+ controls.append("design")
77
+ else:
78
+ controls = ["definition", "evidence"]
79
+ if design_required and "design" not in controls:
80
+ controls.append("design")
81
+ return {
82
+ "schema_version": FULL_QUALITY_SCHEMA_VERSION,
83
+ "source": FULL_QUALITY_SOURCE,
84
+ "rigor": normalized,
85
+ "controls": controls,
86
+ "surfaces": {**DEFAULT_CONTROL_SURFACES, **(surfaces or {})},
87
+ "resolved_from": {
88
+ "verification_profile": verification_profile,
89
+ "risk_signals": signals,
90
+ "policy_requires_design": policy_requires_design,
91
+ },
92
+ }
93
+
94
+
95
+ def rigor_from_required_controls(task_data: dict | None) -> str | None:
96
+ extras = task_data or {}
97
+ bundle = extras.get("required_controls")
98
+ if not isinstance(bundle, dict):
99
+ return None
100
+ rigor = bundle.get("rigor")
101
+ if rigor in ("lite", "full"):
102
+ return rigor
103
+ return None
104
+
105
+
106
+ def parse_acceptance_items(prd_text: str) -> list[dict[str, str]]:
107
+ items: list[dict[str, str]] = []
108
+ index = 0
109
+ for match in AC_ITEM_RE.finditer(prd_text):
110
+ statement = match.group(1).strip()
111
+ if not statement or PLACEHOLDER_VALUE_RE.match(statement):
112
+ continue
113
+ index += 1
114
+ items.append({"id": f"AC-{index}", "statement": statement})
115
+ return items
116
+
117
+
118
+ def evaluate_independent_check(
119
+ *,
120
+ mode: str,
121
+ independent_worker_available: bool,
122
+ evidence: str,
123
+ code_fingerprint: str,
124
+ attempted_writes: bool = False,
125
+ ) -> dict[str, Any]:
126
+ if attempted_writes:
127
+ raise FullQualityError("Independent Check is read-only; repairs return to Execute")
128
+ if mode not in ("self-review", "true-independent"):
129
+ raise FullQualityError("Independent Check mode must be self-review or true-independent")
130
+ if mode == "true-independent" and not independent_worker_available:
131
+ return {
132
+ "schema_version": FULL_QUALITY_SCHEMA_VERSION,
133
+ "mode": "true-independent",
134
+ "readonly": True,
135
+ "result": "BLOCKED",
136
+ "evidence": evidence.strip(),
137
+ "independent_worker": False,
138
+ "code_fingerprint": code_fingerprint,
139
+ }
140
+ if not evidence.strip() or PLACEHOLDER_VALUE_RE.match(evidence.strip()):
141
+ raise FullQualityError(
142
+ "Independent Check PASS/FAIL requires non-empty evidence (no fake-green)"
143
+ )
144
+ return {
145
+ "schema_version": FULL_QUALITY_SCHEMA_VERSION,
146
+ "mode": mode,
147
+ "readonly": True,
148
+ "result": "PASS",
149
+ "evidence": evidence.strip(),
150
+ "independent_worker": independent_worker_available,
151
+ "code_fingerprint": code_fingerprint,
152
+ }
153
+
154
+
155
+ def full_quality_archive_errors(task_dir: Path, task_data: dict | None) -> list[str]:
156
+ """Return archive errors for persisted Full required_controls only.
157
+
158
+ Lite and legacy file-heuristic Full are unchanged: this does not force
159
+ Independent Check onto Lite.
160
+ """
161
+ data = task_data or {}
162
+ bundle = data.get("required_controls")
163
+ if not isinstance(bundle, dict) or bundle.get("rigor") != "full":
164
+ return []
165
+ errors: list[str] = []
166
+ controls = bundle.get("controls")
167
+ if not isinstance(controls, list):
168
+ controls = list(FULL_BASELINE_CONTROLS)
169
+ surfaces = bundle.get("surfaces") if isinstance(bundle.get("surfaces"), dict) else {}
170
+ seen: set[str] = set()
171
+ for control in controls:
172
+ if control in ("independent-check",):
173
+ continue
174
+ relative = surfaces.get(control) or DEFAULT_CONTROL_SURFACES.get(control)
175
+ if not relative or relative in seen:
176
+ continue
177
+ seen.add(relative)
178
+ path = task_dir / relative
179
+ if not path.is_file() or not path.read_text(encoding="utf-8").strip():
180
+ errors.append(f"Full required control {control} missing surface {relative}")
181
+
182
+ ledger = data.get("ac_evidence_ledger")
183
+ if not isinstance(ledger, dict):
184
+ errors.append("Full Close requires a machine-readable AC → Evidence ledger")
185
+ else:
186
+ definition = surfaces.get("definition") or DEFAULT_CONTROL_SURFACES["definition"]
187
+ prd_path = task_dir / definition
188
+ prd_text = prd_path.read_text(encoding="utf-8") if prd_path.is_file() else ""
189
+ acceptance = parse_acceptance_items(prd_text)
190
+ mapped = {
191
+ item.get("ac_id"): item
192
+ for item in ledger.get("items", [])
193
+ if isinstance(item, dict)
194
+ }
195
+ if not acceptance:
196
+ errors.append(f"{definition} has no Acceptance Criteria items to map")
197
+ for item in acceptance:
198
+ mapping = mapped.get(item["id"])
199
+ ref = mapping.get("evidence_ref") if isinstance(mapping, dict) else ""
200
+ if not isinstance(ref, str) or not ref.strip() or PLACEHOLDER_VALUE_RE.match(ref.strip()):
201
+ errors.append(f"AC → Evidence ledger missing mapping for {item['id']}")
202
+ current_source = quality_fingerprint(prd_text)
203
+ if ledger.get("source_fingerprint") and ledger.get("source_fingerprint") != current_source:
204
+ errors.append("AC → Evidence ledger is stale after Definition/code change")
205
+
206
+ check = data.get("independent_check")
207
+ if not isinstance(check, dict):
208
+ errors.append("Full Close requires a graded Independent Check verdict")
209
+ else:
210
+ if check.get("readonly") is False:
211
+ errors.append("Independent Check is read-only; repairs return to Execute")
212
+ mode = check.get("mode") or check.get("assurance")
213
+ if mode == "true-independent" and check.get("independent_worker") is not True:
214
+ errors.append("true-independent Check cannot be satisfied without an independent worker")
215
+ if check.get("result") != "PASS":
216
+ errors.append("Full Close requires Independent Check result PASS")
217
+ evidence = check.get("evidence")
218
+ if not isinstance(evidence, str) or not evidence.strip():
219
+ errors.append("Independent Check PASS requires non-empty evidence (no fake-green)")
220
+ ledger = data.get("ac_evidence_ledger")
221
+ if (
222
+ isinstance(ledger, dict)
223
+ and isinstance(ledger.get("tested_code_fingerprint"), str)
224
+ and isinstance(check.get("code_fingerprint"), str)
225
+ and check.get("code_fingerprint") != ledger.get("tested_code_fingerprint")
226
+ ):
227
+ errors.append("Independent Check verdict is stale after code change")
228
+ return errors
229
+
230
+
231
+ def _unique(values: list[str]) -> list[str]:
232
+ out: list[str] = []
233
+ for value in values:
234
+ item = value.strip()
235
+ if item and item not in out:
236
+ out.append(item)
237
+ return out
@@ -29,3 +29,9 @@ def run_git(args: list[str], cwd: Path | None = None) -> tuple[int, str, str]:
29
29
  return result.returncode, result.stdout, result.stderr
30
30
  except Exception as e:
31
31
  return 1, "", str(e)
32
+
33
+
34
+ def is_git_worktree(path: Path) -> bool:
35
+ """Return True when path is inside a Git worktree. VCS is On-demand."""
36
+ rc, out, _ = run_git(["rev-parse", "--is-inside-work-tree"], cwd=path)
37
+ return rc == 0 and out.strip().lower() == "true"
@@ -28,8 +28,7 @@ from .packages_context import (
28
28
  get_context_packages_json,
29
29
  )
30
30
  from .paths import get_repo_root
31
- from .project_file_stats import resolve_project_file_count_arg
32
- from .retrieval_pack_context import output_retrieval_pack_json, read_evidence_input
31
+ from .lite_context import LiteContextPackError, get_lite_context_json
33
32
  from .trellis_config import read_trellis_config
34
33
  from .workflow_phase import (
35
34
  filter_platform,
@@ -60,13 +59,14 @@ def main() -> None:
60
59
  parser.add_argument(
61
60
  "--mode",
62
61
  "-m",
63
- choices=["default", "record", "packages", "phase", "retrieval-pack"],
62
+ choices=["default", "record", "packages", "phase", "retrieval-pack", "lite"],
64
63
  default="default",
65
64
  help=(
66
65
  "Output mode: default (full context), record (for record-session), "
67
66
  "packages (package info only), phase (workflow step extraction), "
68
67
  "retrieval-pack (score and pack already-collected retrieval evidence; "
69
- "use after research collection, not on every SessionStart)"
68
+ "use after research collection, not on every SessionStart), "
69
+ "lite (Personal Lite phase pack with budget; no Parent/VCS/retrieval-extended)"
70
70
  ),
71
71
  )
72
72
  parser.add_argument(
@@ -138,12 +138,18 @@ def main() -> None:
138
138
  print(content, end="")
139
139
  elif args.mode == "retrieval-pack":
140
140
  try:
141
+ from .project_file_stats import resolve_project_file_count_arg
142
+ from .retrieval_pack_context import (
143
+ output_retrieval_pack_json,
144
+ read_evidence_input,
145
+ )
146
+
141
147
  evidence = read_evidence_input(args.input)
142
148
  project_file_count = resolve_project_file_count_arg(
143
149
  args.project_file_count,
144
150
  repo_root=get_repo_root(),
145
151
  )
146
- except (OSError, json.JSONDecodeError, ValueError) as error:
152
+ except (OSError, json.JSONDecodeError, ValueError, ImportError) as error:
147
153
  parser.exit(1, f"retrieval pack error: {error}\n")
148
154
  output_retrieval_pack_json(
149
155
  evidence_input=evidence,
@@ -153,6 +159,12 @@ def main() -> None:
153
159
  pretty=args.json,
154
160
  project_file_count=project_file_count,
155
161
  )
162
+ elif args.mode == "lite":
163
+ try:
164
+ pack = get_lite_context_json()
165
+ except LiteContextPackError as error:
166
+ parser.exit(1, f"lite context pack error: {error}\n")
167
+ print(json.dumps(pack, indent=2 if args.json else None, ensure_ascii=False))
156
168
  else:
157
169
  if args.json:
158
170
  output_json()
@@ -0,0 +1,367 @@
1
+ """JSON CLI bridge to the TypeScript Kernel (P30 Stage 2 strangler).
2
+
3
+ Python create / start-execution / record-gate / archive / patch submit a
4
+ Kernel Command. `task.json` status, meta, and gate extras are a same-command
5
+ projection, not a second core-state writer.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ from pathlib import Path
16
+
17
+ from .log import Colors, colored
18
+
19
+ TASK_RECORD_FIELDS = (
20
+ "id",
21
+ "name",
22
+ "title",
23
+ "description",
24
+ "status",
25
+ "dev_type",
26
+ "scope",
27
+ "package",
28
+ "priority",
29
+ "creator",
30
+ "assignee",
31
+ "createdAt",
32
+ "completedAt",
33
+ "branch",
34
+ "base_branch",
35
+ "worktree_path",
36
+ "commit",
37
+ "pr_url",
38
+ "subtasks",
39
+ "children",
40
+ "parent",
41
+ "relatedFiles",
42
+ "notes",
43
+ "meta",
44
+ )
45
+
46
+ _NULLABLE_STRING = {
47
+ "dev_type",
48
+ "scope",
49
+ "package",
50
+ "completedAt",
51
+ "branch",
52
+ "base_branch",
53
+ "worktree_path",
54
+ "commit",
55
+ "pr_url",
56
+ "parent",
57
+ }
58
+ _STRING_ARRAYS = {"subtasks", "children", "relatedFiles"}
59
+
60
+
61
+ class KernelCliNotFound(RuntimeError):
62
+ """`cstl kernel --json` (or TRELLIS_KERNEL_CLI) is not available."""
63
+
64
+
65
+ class KernelCommandError(RuntimeError):
66
+ """Kernel JSON CLI returned ok:false or could not be parsed."""
67
+
68
+ def __init__(
69
+ self,
70
+ message: str,
71
+ *,
72
+ result: dict | None = None,
73
+ returncode: int | None = None,
74
+ ) -> None:
75
+ super().__init__(message)
76
+ self.result = result
77
+ self.returncode = returncode
78
+
79
+
80
+ def to_kernel_record(task_data: dict) -> dict:
81
+ """Project a Python task dict onto the canonical 24-field Kernel record."""
82
+ record: dict = {}
83
+ for field in TASK_RECORD_FIELDS:
84
+ if field in task_data:
85
+ record[field] = task_data[field]
86
+ continue
87
+ if field in _NULLABLE_STRING:
88
+ record[field] = None
89
+ elif field in _STRING_ARRAYS:
90
+ record[field] = []
91
+ elif field == "meta":
92
+ record[field] = {}
93
+ elif field == "priority":
94
+ record[field] = "P2"
95
+ elif field == "status":
96
+ record[field] = "planning"
97
+ elif field in ("description", "notes", "id", "name", "title", "creator", "assignee", "createdAt"):
98
+ record[field] = ""
99
+ else:
100
+ record[field] = None
101
+ if not isinstance(record["meta"], dict):
102
+ record["meta"] = {}
103
+ else:
104
+ record["meta"] = {
105
+ key: value
106
+ for key, value in record["meta"].items()
107
+ if key not in {"classification", "task_kind", "task_type", "mode"}
108
+ }
109
+ for array_field in _STRING_ARRAYS:
110
+ value = record[array_field]
111
+ if not isinstance(value, list):
112
+ record[array_field] = []
113
+ return record
114
+
115
+
116
+ def kernel_cli_argv() -> list[str]:
117
+ override = os.environ.get("TRELLIS_KERNEL_CLI", "").strip()
118
+ if override:
119
+ if sys.platform == "win32":
120
+ import shlex
121
+
122
+ return shlex.split(override, posix=False)
123
+ import shlex
124
+
125
+ return shlex.split(override)
126
+ cstl = shutil.which("cstl")
127
+ if cstl:
128
+ return [cstl, "kernel", "--json"]
129
+ raise KernelCliNotFound(
130
+ "Kernel CLI not found. Set TRELLIS_KERNEL_CLI or put `cstl` on PATH. "
131
+ "Stage 2 create/start-execution/record-gate/archive/patch require `cstl kernel --json`."
132
+ )
133
+
134
+
135
+ def run_kernel_command(payload: dict, *, cwd: Path | None = None) -> dict:
136
+ """Send one Kernel Command on stdin and parse one JSON object from stdout."""
137
+ argv = kernel_cli_argv()
138
+ encoded = json.dumps(payload, ensure_ascii=False)
139
+ try:
140
+ proc = subprocess.run(
141
+ argv,
142
+ input=encoded,
143
+ capture_output=True,
144
+ text=True,
145
+ encoding="utf-8",
146
+ cwd=str(cwd) if cwd is not None else None,
147
+ check=False,
148
+ )
149
+ except OSError as err:
150
+ raise KernelCommandError(f"failed to invoke Kernel CLI {argv!r}: {err}") from err
151
+
152
+ raw = (proc.stdout or "").strip()
153
+ parsed: dict | None = None
154
+ if raw:
155
+ try:
156
+ parsed = json.loads(raw)
157
+ except json.JSONDecodeError:
158
+ last_line = raw.splitlines()[-1] if raw.splitlines() else raw
159
+ try:
160
+ parsed = json.loads(last_line)
161
+ except json.JSONDecodeError:
162
+ parsed = None
163
+
164
+ if not isinstance(parsed, dict):
165
+ err_tail = (proc.stderr or "").strip()[-800:]
166
+ raise KernelCommandError(
167
+ f"Kernel CLI did not return JSON (exit {proc.returncode}). {err_tail}",
168
+ returncode=proc.returncode,
169
+ )
170
+
171
+ if not parsed.get("ok"):
172
+ error = parsed.get("error") if isinstance(parsed.get("error"), dict) else {}
173
+ code = error.get("code", "UNKNOWN")
174
+ message = error.get("message", "kernel command failed")
175
+ raise KernelCommandError(
176
+ f"Kernel {payload.get('op', 'command')} failed ({code}): {message}",
177
+ result=parsed,
178
+ returncode=proc.returncode,
179
+ )
180
+ return parsed
181
+
182
+
183
+ def kernel_read(task_dir: Path) -> dict:
184
+ return run_kernel_command({"op": "read", "taskDir": str(task_dir)})
185
+
186
+
187
+ def kernel_expected_revision(task_dir: Path) -> int:
188
+ result = kernel_read(task_dir)
189
+ kernel = result.get("kernel")
190
+ if not isinstance(kernel, dict):
191
+ raise KernelCommandError("Kernel read did not include kernel.revision", result=result)
192
+ revision = kernel.get("revision")
193
+ if not isinstance(revision, int) or revision < 0:
194
+ raise KernelCommandError(
195
+ f"Kernel revision is invalid: {revision!r}",
196
+ result=result,
197
+ )
198
+ return revision
199
+
200
+
201
+ def kernel_create(
202
+ task_dir: Path,
203
+ record: dict,
204
+ *,
205
+ actor: str,
206
+ idempotency_key: str,
207
+ evidence: str | None = None,
208
+ extras: dict | None = None,
209
+ ) -> dict:
210
+ payload: dict = {
211
+ "op": "create",
212
+ "taskDir": str(task_dir),
213
+ "actor": actor,
214
+ "idempotencyKey": idempotency_key,
215
+ "record": to_kernel_record(record),
216
+ }
217
+ if extras:
218
+ payload["extras"] = extras
219
+ if evidence:
220
+ payload["evidence"] = evidence
221
+ return run_kernel_command(payload)
222
+
223
+
224
+ def kernel_start(
225
+ task_dir: Path,
226
+ record: dict,
227
+ extras: dict,
228
+ *,
229
+ expected_revision: int,
230
+ actor: str,
231
+ idempotency_key: str,
232
+ evidence: str | None = None,
233
+ ) -> dict:
234
+ payload: dict = {
235
+ "op": "start",
236
+ "taskDir": str(task_dir),
237
+ "expectedRevision": expected_revision,
238
+ "actor": actor,
239
+ "idempotencyKey": idempotency_key,
240
+ "record": to_kernel_record(record),
241
+ "extras": extras,
242
+ }
243
+ if evidence:
244
+ payload["evidence"] = evidence
245
+ return run_kernel_command(payload)
246
+
247
+
248
+ def kernel_record_gate(
249
+ task_dir: Path,
250
+ *,
251
+ expected_revision: int,
252
+ actor: str,
253
+ idempotency_key: str,
254
+ transition: str,
255
+ gate: str,
256
+ record: dict,
257
+ extras: dict,
258
+ evidence: str | None = None,
259
+ ) -> dict:
260
+ payload: dict = {
261
+ "op": "record-gate",
262
+ "taskDir": str(task_dir),
263
+ "expectedRevision": expected_revision,
264
+ "actor": actor,
265
+ "idempotencyKey": idempotency_key,
266
+ "transition": transition,
267
+ "gate": gate,
268
+ "record": record,
269
+ "extras": extras,
270
+ }
271
+ if evidence:
272
+ payload["evidence"] = evidence
273
+ return run_kernel_command(payload)
274
+
275
+
276
+ KERNEL_PROJECTION_EXTRA_KEYS = (
277
+ "quality_gate_results",
278
+ "execution_approval",
279
+ "required_controls",
280
+ "ac_evidence_ledger",
281
+ "independent_check",
282
+ "topology",
283
+ "dependency_graph",
284
+ "ondemand_modules",
285
+ "depends_on",
286
+ "dependency_satisfied",
287
+ "decompose_proposal",
288
+ "profile_health",
289
+ "event_bridge",
290
+ "middleware_providers",
291
+ "capability_router",
292
+ "required_capabilities",
293
+ )
294
+
295
+
296
+ def kernel_projection_extras(task_data: dict) -> dict:
297
+ """Non-canonical task.json fields owned by the same Kernel Command."""
298
+ extras: dict = {}
299
+ for key in KERNEL_PROJECTION_EXTRA_KEYS:
300
+ if key in task_data:
301
+ extras[key] = task_data[key]
302
+ return extras
303
+
304
+
305
+ def kernel_archive(
306
+ task_dir: Path,
307
+ record: dict,
308
+ extras: dict,
309
+ *,
310
+ expected_revision: int,
311
+ actor: str,
312
+ idempotency_key: str,
313
+ evidence: str | None = None,
314
+ ) -> dict:
315
+ payload: dict = {
316
+ "op": "archive",
317
+ "taskDir": str(task_dir),
318
+ "expectedRevision": expected_revision,
319
+ "actor": actor,
320
+ "idempotencyKey": idempotency_key,
321
+ "record": to_kernel_record(record),
322
+ "extras": extras,
323
+ }
324
+ if evidence:
325
+ payload["evidence"] = evidence
326
+ return run_kernel_command(payload)
327
+
328
+
329
+ def kernel_patch(
330
+ task_dir: Path,
331
+ record: dict | None = None,
332
+ extras: dict | None = None,
333
+ *,
334
+ expected_revision: int,
335
+ actor: str,
336
+ idempotency_key: str,
337
+ evidence: str | None = None,
338
+ ) -> dict:
339
+ payload: dict = {
340
+ "op": "patch",
341
+ "taskDir": str(task_dir),
342
+ "expectedRevision": expected_revision,
343
+ "actor": actor,
344
+ "idempotencyKey": idempotency_key,
345
+ }
346
+ if record is not None:
347
+ payload["record"] = to_kernel_record(record)
348
+ if extras is not None:
349
+ payload["extras"] = extras
350
+ if evidence:
351
+ payload["evidence"] = evidence
352
+ return run_kernel_command(payload)
353
+
354
+
355
+ def print_kernel_error(err: Exception) -> None:
356
+ print(colored(f"Error: {err}", Colors.RED), file=sys.stderr)
357
+ if isinstance(err, KernelCommandError) and isinstance(err.result, dict):
358
+ half = err.result.get("halfConversion")
359
+ if isinstance(half, dict) and half.get("kernelPersisted"):
360
+ print(
361
+ colored(
362
+ "Kernel.json was committed but the task.json projection did not finish "
363
+ "(half-conversion). Re-run the same command to recover; do not treat this as success.",
364
+ Colors.YELLOW,
365
+ ),
366
+ file=sys.stderr,
367
+ )