@blxzer/cursor-trellis 0.4.1 → 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 (337) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +25 -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 +144 -41
  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 +12 -45
  33. package/dist/configurators/workflow.js.map +1 -1
  34. package/dist/constants/paths.d.ts +13 -0
  35. package/dist/constants/paths.d.ts.map +1 -1
  36. package/dist/constants/paths.js +17 -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-check/SKILL.md +1 -1
  40. package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/hooks-and-settings.md +2 -2
  41. package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/overview.md +1 -1
  42. package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/skills-and-commands.md +2 -4
  43. package/dist/templates/common/bundled-skills/cstl-skill-creator/SKILL.md +10 -7
  44. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/authoring-rules.md +93 -19
  45. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/general-authoring.md +189 -0
  46. package/dist/templates/common/bundled-skills/cstl-skill-creator/references/review-checklist.md +10 -5
  47. package/dist/templates/common/commands/continue.md +6 -5
  48. package/dist/templates/common/index.d.ts +20 -0
  49. package/dist/templates/common/index.d.ts.map +1 -1
  50. package/dist/templates/common/index.js +21 -2
  51. package/dist/templates/common/index.js.map +1 -1
  52. package/dist/templates/common/optional-skills/chrome-cdp/SKILL.md +179 -0
  53. package/dist/templates/common/optional-skills/chrome-cdp/examples/fetch-hook-api-capture.md +149 -0
  54. package/dist/templates/common/optional-skills/chrome-cdp/scripts/cdp.mjs +903 -0
  55. package/dist/templates/common/skills/brainstorm.md +2 -2
  56. package/dist/templates/cursor/agents/cstl-check.md +2 -2
  57. package/dist/templates/cursor/agents/cstl-implement.md +1 -1
  58. package/dist/templates/cursor/agents/cstl-research.md +2 -2
  59. package/dist/templates/cursor/fixtures/expected-rules.d.ts +1 -1
  60. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  61. package/dist/templates/cursor/fixtures/expected-rules.js +7 -24
  62. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  63. package/dist/templates/cursor/hooks.json +4 -0
  64. package/dist/templates/cursor/index.d.ts +1 -1
  65. package/dist/templates/cursor/index.js +1 -1
  66. package/dist/templates/cursor/rules/cstl-bootstrap.mdc +17 -0
  67. package/dist/templates/markdown/agents.md +36 -3
  68. package/dist/templates/markdown/{spec/guides → framework}/cursor-native-modes-guide.md.txt +13 -10
  69. package/dist/templates/markdown/{spec/guides → framework}/cursor-subagent-policy.md.txt +63 -127
  70. package/dist/templates/markdown/framework/dogfood-only-surfaces.md.txt +29 -0
  71. package/dist/templates/markdown/{spec/guides → framework}/execution-strategy.md.txt +2 -0
  72. package/dist/templates/markdown/framework/index.md.txt +31 -0
  73. package/dist/templates/markdown/framework/internal-skills-cursor-reachability.md.txt +45 -0
  74. package/dist/templates/markdown/framework/middleware-protocol.md.txt +83 -0
  75. package/dist/templates/markdown/framework/parallel-first-execution.md.txt +78 -0
  76. package/dist/templates/markdown/framework/prd-grill-frontier.md.txt +83 -0
  77. package/dist/templates/markdown/framework/release-boundary.md.txt +30 -0
  78. package/dist/templates/markdown/{spec/guides → framework}/retrieval-daily-guide.md.txt +1 -1
  79. package/dist/templates/markdown/framework/upgrade.md.txt +15 -0
  80. package/dist/templates/markdown/{spec/guides → framework}/verification-strength-guide.md.txt +2 -2
  81. package/dist/templates/markdown/index.d.ts +26 -10
  82. package/dist/templates/markdown/index.d.ts.map +1 -1
  83. package/dist/templates/markdown/index.js +87 -11
  84. package/dist/templates/markdown/index.js.map +1 -1
  85. package/dist/templates/markdown/prompts/run-semantic-slice-12.md.txt +1 -1
  86. package/dist/templates/markdown/spec/guides/index.md.txt +5 -19
  87. package/dist/templates/markdown/spec/guides/prototype-guide.md.txt +10 -10
  88. package/dist/templates/markdown/spec/guides/test-discipline-guide.md.txt +1 -1
  89. package/dist/templates/shared-hooks/event-bridge.py +60 -0
  90. package/dist/templates/shared-hooks/index.d.ts +1 -1
  91. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  92. package/dist/templates/shared-hooks/index.js +1 -0
  93. package/dist/templates/shared-hooks/index.js.map +1 -1
  94. package/dist/templates/shared-hooks/session-start.py +19 -2
  95. package/dist/templates/trellis/CONTEXT.md +63 -3
  96. package/dist/templates/trellis/config.yaml +1 -1
  97. package/dist/templates/trellis/gitignore.txt +39 -39
  98. package/dist/templates/trellis/index.d.ts +12 -0
  99. package/dist/templates/trellis/index.d.ts.map +1 -1
  100. package/dist/templates/trellis/index.js +73 -15
  101. package/dist/templates/trellis/index.js.map +1 -1
  102. package/dist/templates/trellis/pool/README.md +12 -3
  103. package/dist/templates/trellis/pool/plan.md +24 -5
  104. package/dist/templates/trellis/scripts/common/adapter_middleware.py +158 -0
  105. package/dist/templates/trellis/scripts/common/artifact_locale.py +12 -3
  106. package/dist/templates/trellis/scripts/common/cursor_retrieval_env.py +5 -2
  107. package/dist/templates/trellis/scripts/common/full_quality.py +237 -0
  108. package/dist/templates/trellis/scripts/common/git.py +6 -0
  109. package/dist/templates/trellis/scripts/common/git_context.py +17 -5
  110. package/dist/templates/trellis/scripts/common/kernel_command.py +367 -0
  111. package/dist/templates/trellis/scripts/common/lite_context.py +280 -0
  112. package/dist/templates/trellis/scripts/common/ondemand_topology.py +225 -0
  113. package/dist/templates/trellis/scripts/common/parent_orchestration.py +1 -1
  114. package/dist/templates/trellis/scripts/common/pool_store.py +16 -4
  115. package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +2 -2
  116. package/dist/templates/trellis/scripts/common/session_context.py +28 -2
  117. package/dist/templates/trellis/scripts/common/task_gates.py +73 -12
  118. package/dist/templates/trellis/scripts/common/task_map.py +42 -9
  119. package/dist/templates/trellis/scripts/common/task_store.py +293 -61
  120. package/dist/templates/trellis/scripts/common/test_adapter_middleware.py +56 -0
  121. package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +9 -0
  122. package/dist/templates/trellis/scripts/common/test_full_quality.py +197 -0
  123. package/dist/templates/trellis/scripts/common/test_kernel_command.py +326 -0
  124. package/dist/templates/trellis/scripts/common/test_lite_path.py +190 -0
  125. package/dist/templates/trellis/scripts/common/test_observable_defaults.py +10 -4
  126. package/dist/templates/trellis/scripts/common/test_ondemand_topology.py +122 -0
  127. package/dist/templates/trellis/scripts/common/test_pool_store.py +17 -0
  128. package/dist/templates/trellis/scripts/common/test_task_store_kernel_patch.py +163 -0
  129. package/dist/templates/trellis/scripts/common/workflow_phase.py +3 -0
  130. package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +4 -5
  131. package/dist/templates/trellis/scripts/get_context.py +1 -0
  132. package/dist/templates/trellis/scripts/pool.py +5 -0
  133. package/dist/templates/trellis/scripts/task.py +83 -7
  134. package/dist/templates/trellis/workflow.md +109 -23
  135. package/dist/utils/cursor-retrieval-env.d.ts +2 -1
  136. package/dist/utils/cursor-retrieval-env.d.ts.map +1 -1
  137. package/dist/utils/cursor-retrieval-env.js +3 -2
  138. package/dist/utils/cursor-retrieval-env.js.map +1 -1
  139. package/dist/utils/cursor2plus-residue-cleanup.d.ts +38 -0
  140. package/dist/utils/cursor2plus-residue-cleanup.d.ts.map +1 -0
  141. package/dist/utils/cursor2plus-residue-cleanup.js +145 -0
  142. package/dist/utils/cursor2plus-residue-cleanup.js.map +1 -0
  143. package/dist/utils/manifest-prune.d.ts.map +1 -1
  144. package/dist/utils/manifest-prune.js +7 -2
  145. package/dist/utils/manifest-prune.js.map +1 -1
  146. package/dist/utils/p36-upgrade.d.ts +62 -0
  147. package/dist/utils/p36-upgrade.d.ts.map +1 -0
  148. package/dist/utils/p36-upgrade.js +160 -0
  149. package/dist/utils/p36-upgrade.js.map +1 -0
  150. package/dist/utils/project-capabilities.d.ts +1 -12
  151. package/dist/utils/project-capabilities.d.ts.map +1 -1
  152. package/dist/utils/project-capabilities.js +3 -100
  153. package/dist/utils/project-capabilities.js.map +1 -1
  154. package/dist/utils/readiness.d.ts +10 -0
  155. package/dist/utils/readiness.d.ts.map +1 -1
  156. package/dist/utils/readiness.js +58 -26
  157. package/dist/utils/readiness.js.map +1 -1
  158. package/dist/utils/retrieval-agent-instructions.js +1 -1
  159. package/dist/utils/retrieval-agent-instructions.js.map +1 -1
  160. package/dist/utils/retrieval-execution-telemetry.js +1 -1
  161. package/dist/utils/retrieval-execution-telemetry.js.map +1 -1
  162. package/dist/utils/template-hash.d.ts.map +1 -1
  163. package/dist/utils/template-hash.js +11 -1
  164. package/dist/utils/template-hash.js.map +1 -1
  165. package/dist/utils/uninstall-scrubbers.d.ts.map +1 -1
  166. package/dist/utils/uninstall-scrubbers.js +52 -14
  167. package/dist/utils/uninstall-scrubbers.js.map +1 -1
  168. package/dist/utils/update-rollout-report.d.ts +14 -0
  169. package/dist/utils/update-rollout-report.d.ts.map +1 -1
  170. package/dist/utils/update-rollout-report.js +1 -0
  171. package/dist/utils/update-rollout-report.js.map +1 -1
  172. package/dist/utils/workflow-ownership.d.ts +1 -1
  173. package/dist/utils/workflow-ownership.js +4 -4
  174. package/dist/utils/workflow-ownership.js.map +1 -1
  175. package/package.json +5 -4
  176. package/scripts/postinstall.js +6 -7
  177. package/dist/commands/campaign/canvas-render.d.ts +0 -68
  178. package/dist/commands/campaign/canvas-render.d.ts.map +0 -1
  179. package/dist/commands/campaign/canvas-render.js +0 -414
  180. package/dist/commands/campaign/canvas-render.js.map +0 -1
  181. package/dist/commands/campaign/compose.d.ts +0 -15
  182. package/dist/commands/campaign/compose.d.ts.map +0 -1
  183. package/dist/commands/campaign/compose.js +0 -110
  184. package/dist/commands/campaign/compose.js.map +0 -1
  185. package/dist/commands/campaign/index.d.ts +0 -13
  186. package/dist/commands/campaign/index.d.ts.map +0 -1
  187. package/dist/commands/campaign/index.js +0 -134
  188. package/dist/commands/campaign/index.js.map +0 -1
  189. package/dist/commands/campaign/kind-map.d.ts +0 -6
  190. package/dist/commands/campaign/kind-map.d.ts.map +0 -1
  191. package/dist/commands/campaign/kind-map.js +0 -21
  192. package/dist/commands/campaign/kind-map.js.map +0 -1
  193. package/dist/commands/campaign/mcp-server.d.ts +0 -6
  194. package/dist/commands/campaign/mcp-server.d.ts.map +0 -1
  195. package/dist/commands/campaign/mcp-server.js +0 -167
  196. package/dist/commands/campaign/mcp-server.js.map +0 -1
  197. package/dist/commands/campaign/render.d.ts +0 -5
  198. package/dist/commands/campaign/render.d.ts.map +0 -1
  199. package/dist/commands/campaign/render.js +0 -103
  200. package/dist/commands/campaign/render.js.map +0 -1
  201. package/dist/commands/campaign/trellis-load.d.ts +0 -13
  202. package/dist/commands/campaign/trellis-load.d.ts.map +0 -1
  203. package/dist/commands/campaign/trellis-load.js +0 -122
  204. package/dist/commands/campaign/trellis-load.js.map +0 -1
  205. package/dist/commands/campaign/types.d.ts +0 -61
  206. package/dist/commands/campaign/types.d.ts.map +0 -1
  207. package/dist/commands/campaign/types.js +0 -3
  208. package/dist/commands/campaign/types.js.map +0 -1
  209. package/dist/commands/goal/index.d.ts +0 -3
  210. package/dist/commands/goal/index.d.ts.map +0 -1
  211. package/dist/commands/goal/index.js +0 -159
  212. package/dist/commands/goal/index.js.map +0 -1
  213. package/dist/commands/goal/review.d.ts +0 -6
  214. package/dist/commands/goal/review.d.ts.map +0 -1
  215. package/dist/commands/goal/review.js +0 -28
  216. package/dist/commands/goal/review.js.map +0 -1
  217. package/dist/commands/rpc/client.d.ts +0 -51
  218. package/dist/commands/rpc/client.d.ts.map +0 -1
  219. package/dist/commands/rpc/client.js +0 -147
  220. package/dist/commands/rpc/client.js.map +0 -1
  221. package/dist/commands/rpc/index.d.ts +0 -10
  222. package/dist/commands/rpc/index.d.ts.map +0 -1
  223. package/dist/commands/rpc/index.js +0 -157
  224. package/dist/commands/rpc/index.js.map +0 -1
  225. package/dist/commands/rpc/serve.d.ts +0 -23
  226. package/dist/commands/rpc/serve.d.ts.map +0 -1
  227. package/dist/commands/rpc/serve.js +0 -60
  228. package/dist/commands/rpc/serve.js.map +0 -1
  229. package/dist/commands/rpc/smoke.d.ts +0 -16
  230. package/dist/commands/rpc/smoke.d.ts.map +0 -1
  231. package/dist/commands/rpc/smoke.js +0 -92
  232. package/dist/commands/rpc/smoke.js.map +0 -1
  233. package/dist/commands/sdk/index.d.ts +0 -9
  234. package/dist/commands/sdk/index.d.ts.map +0 -1
  235. package/dist/commands/sdk/index.js +0 -58
  236. package/dist/commands/sdk/index.js.map +0 -1
  237. package/dist/commands/sdk/run.d.ts +0 -42
  238. package/dist/commands/sdk/run.d.ts.map +0 -1
  239. package/dist/commands/sdk/run.js +0 -220
  240. package/dist/commands/sdk/run.js.map +0 -1
  241. package/dist/commands/sdk/status.d.ts +0 -14
  242. package/dist/commands/sdk/status.d.ts.map +0 -1
  243. package/dist/commands/sdk/status.js +0 -69
  244. package/dist/commands/sdk/status.js.map +0 -1
  245. package/dist/configurators/cursor2plus-local.d.ts +0 -6
  246. package/dist/configurators/cursor2plus-local.d.ts.map +0 -1
  247. package/dist/configurators/cursor2plus-local.js +0 -21
  248. package/dist/configurators/cursor2plus-local.js.map +0 -1
  249. package/dist/goal/action-packet.d.ts +0 -89
  250. package/dist/goal/action-packet.d.ts.map +0 -1
  251. package/dist/goal/action-packet.js +0 -113
  252. package/dist/goal/action-packet.js.map +0 -1
  253. package/dist/goal/audit.d.ts +0 -14
  254. package/dist/goal/audit.d.ts.map +0 -1
  255. package/dist/goal/audit.js +0 -12
  256. package/dist/goal/audit.js.map +0 -1
  257. package/dist/goal/constants.d.ts +0 -26
  258. package/dist/goal/constants.d.ts.map +0 -1
  259. package/dist/goal/constants.js +0 -24
  260. package/dist/goal/constants.js.map +0 -1
  261. package/dist/goal/paths.d.ts +0 -8
  262. package/dist/goal/paths.d.ts.map +0 -1
  263. package/dist/goal/paths.js +0 -36
  264. package/dist/goal/paths.js.map +0 -1
  265. package/dist/goal/preflight.d.ts +0 -21
  266. package/dist/goal/preflight.d.ts.map +0 -1
  267. package/dist/goal/preflight.js +0 -97
  268. package/dist/goal/preflight.js.map +0 -1
  269. package/dist/goal/review-seam.d.ts +0 -44
  270. package/dist/goal/review-seam.d.ts.map +0 -1
  271. package/dist/goal/review-seam.js +0 -153
  272. package/dist/goal/review-seam.js.map +0 -1
  273. package/dist/goal/reviewer-rules.d.ts +0 -13
  274. package/dist/goal/reviewer-rules.d.ts.map +0 -1
  275. package/dist/goal/reviewer-rules.js +0 -153
  276. package/dist/goal/reviewer-rules.js.map +0 -1
  277. package/dist/goal/reviewer.d.ts +0 -9
  278. package/dist/goal/reviewer.d.ts.map +0 -1
  279. package/dist/goal/reviewer.js +0 -54
  280. package/dist/goal/reviewer.js.map +0 -1
  281. package/dist/goal/runtime.d.ts +0 -32
  282. package/dist/goal/runtime.d.ts.map +0 -1
  283. package/dist/goal/runtime.js +0 -242
  284. package/dist/goal/runtime.js.map +0 -1
  285. package/dist/goal/sdk-client.d.ts +0 -10
  286. package/dist/goal/sdk-client.d.ts.map +0 -1
  287. package/dist/goal/sdk-client.js +0 -34
  288. package/dist/goal/sdk-client.js.map +0 -1
  289. package/dist/goal/state.d.ts +0 -40
  290. package/dist/goal/state.d.ts.map +0 -1
  291. package/dist/goal/state.js +0 -37
  292. package/dist/goal/state.js.map +0 -1
  293. package/dist/goal/task-triggers.d.ts +0 -15
  294. package/dist/goal/task-triggers.d.ts.map +0 -1
  295. package/dist/goal/task-triggers.js +0 -23
  296. package/dist/goal/task-triggers.js.map +0 -1
  297. package/dist/goal/walls.d.ts +0 -12
  298. package/dist/goal/walls.d.ts.map +0 -1
  299. package/dist/goal/walls.js +0 -65
  300. package/dist/goal/walls.js.map +0 -1
  301. package/dist/goal/worker-prompt.d.ts +0 -13
  302. package/dist/goal/worker-prompt.d.ts.map +0 -1
  303. package/dist/goal/worker-prompt.js +0 -41
  304. package/dist/goal/worker-prompt.js.map +0 -1
  305. package/dist/goal/worker.d.ts +0 -42
  306. package/dist/goal/worker.d.ts.map +0 -1
  307. package/dist/goal/worker.js +0 -180
  308. package/dist/goal/worker.js.map +0 -1
  309. package/dist/templates/common/bundled-skills/cstl-cursor2plus-setup/SKILL.md +0 -63
  310. package/dist/templates/cursor/commands/cursor2plus-setup.md +0 -82
  311. package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +0 -32
  312. package/dist/templates/cursor/rules/cstl-session-rename.mdc +0 -31
  313. package/dist/templates/cursor/rules/cstl-subagent-dispatch.mdc +0 -34
  314. package/dist/templates/cursor/rules/cstl-triage.mdc +0 -36
  315. package/dist/templates/cursor/rules/retrieval-routing.mdc +0 -78
  316. package/dist/templates/markdown/spec/guides/cursor-trellis-release-coexistence-guide.md.txt +0 -166
  317. package/dist/templates/markdown/spec/guides/goal-release-regression-runbook.md.txt +0 -132
  318. package/dist/templates/trellis/local/README.md +0 -85
  319. package/dist/templates/trellis/local/config.local.json.example +0 -6
  320. package/dist/templates/trellis/local/index.d.ts +0 -8
  321. package/dist/templates/trellis/local/index.d.ts.map +0 -1
  322. package/dist/templates/trellis/local/index.js +0 -15
  323. package/dist/templates/trellis/local/index.js.map +0 -1
  324. package/dist/templates/trellis/local/patch_wpelc8.py +0 -421
  325. package/dist/templates/trellis/local/smoke.py +0 -94
  326. package/dist/templates/trellis/local/subagent-models.json.example +0 -4
  327. package/dist/templates/trellis/local/trellis-task-models.json.example +0 -10
  328. package/dist/templates/trellis/local/trellis-task-models.json5.example +0 -45
  329. package/dist/templates/trellis/local/trellis_task_models_config.py +0 -200
  330. package/dist/utils/cursor-sdk-gate.d.ts +0 -10
  331. package/dist/utils/cursor-sdk-gate.d.ts.map +0 -1
  332. package/dist/utils/cursor-sdk-gate.js +0 -32
  333. package/dist/utils/cursor-sdk-gate.js.map +0 -1
  334. /package/dist/templates/markdown/{spec/guides → framework}/artifact-locale-guide.md.txt +0 -0
  335. /package/dist/templates/markdown/{spec/guides → framework}/cursor-context-injection-guide.md.txt +0 -0
  336. /package/dist/templates/markdown/{spec/guides → framework}/cursor-semantic-compliance.md.txt +0 -0
  337. /package/dist/templates/markdown/{spec/guides → framework}/injection-budget-guide.md.txt +0 -0
@@ -0,0 +1,225 @@
1
+ """Stage 5 Topology + typed graph + On-demand (Python projection).
2
+
3
+ Kernel extras remain authoritative. This module renders and validates the
4
+ same contract; it does not schedule workers or write a second graph.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any
10
+
11
+ STAGE5_SOURCE = "stage5-ondemand-topology"
12
+ STAGE5_SCHEMA_VERSION = 1
13
+
14
+ STAGE5_ONDEMAND_MODULES = (
15
+ "candidate-pool",
16
+ "define-extended",
17
+ "worker-orchestration",
18
+ "parent-child",
19
+ "debug-recovery",
20
+ "integration-handoff",
21
+ "session-transfer",
22
+ "spec-learning",
23
+ "vcs-integration",
24
+ "personal-memory",
25
+ "retention-storage",
26
+ )
27
+
28
+ ONDEMAND_OWNERS = {
29
+ "integration-handoff": "parent-child",
30
+ "session-transfer": "session-transfer",
31
+ }
32
+
33
+ LITE_DORMANT_ONDEMAND = (
34
+ "parent-child",
35
+ "vcs-integration",
36
+ "personal-memory",
37
+ "retention-storage",
38
+ )
39
+
40
+
41
+ class TopologyError(RuntimeError):
42
+ """Topology / On-demand contract rejected a request."""
43
+
44
+
45
+ def capture_candidate(summary: str) -> dict[str, Any]:
46
+ return {
47
+ "kind": "candidate",
48
+ "summary": (summary or "").strip(),
49
+ "task_created": False,
50
+ }
51
+
52
+
53
+ def propose_decompose(parent_id: str, slices: list[str]) -> dict[str, Any]:
54
+ return {
55
+ "parent_id": parent_id,
56
+ "slices": _unique(slices),
57
+ "confirmed": False,
58
+ "children_created": [],
59
+ }
60
+
61
+
62
+ def confirm_decompose(proposal: dict[str, Any]) -> dict[str, Any]:
63
+ slices = _unique(proposal.get("slices") or [])
64
+ return {
65
+ **proposal,
66
+ "confirmed": True,
67
+ "children_created": slices,
68
+ }
69
+
70
+
71
+ def apply_retention(outcome: str) -> str:
72
+ return outcome
73
+
74
+
75
+ def default_topology(
76
+ parent: str | None = None,
77
+ children: list[str] | None = None,
78
+ ) -> dict[str, Any]:
79
+ """Seed Topology. ``parent_id`` + empty children stays ``single``."""
80
+ kids = _unique(children or [])
81
+ parent_id = (parent or "").strip() or None
82
+ return {
83
+ "schema_version": STAGE5_SCHEMA_VERSION,
84
+ "kind": "parent-child" if kids else "single",
85
+ "parent_id": parent_id,
86
+ "children": kids,
87
+ }
88
+
89
+
90
+ def assign_parent(topology: dict[str, Any], parent_id: str) -> dict[str, Any]:
91
+ current = topology.get("parent_id")
92
+ next_parent = (parent_id or "").strip()
93
+ if not next_parent:
94
+ raise TopologyError("parent_id must be a non-empty string")
95
+ if current and current != next_parent:
96
+ raise TopologyError(
97
+ f"single-parent tree: second parent rejected ({current} vs {next_parent})"
98
+ )
99
+ children = list(topology.get("children") or [])
100
+ return {
101
+ "schema_version": STAGE5_SCHEMA_VERSION,
102
+ "kind": "parent-child" if children else "single",
103
+ "parent_id": next_parent,
104
+ "children": children,
105
+ }
106
+
107
+
108
+ def assert_integration_authority(actor_role: str) -> None:
109
+ if actor_role == "parent":
110
+ return
111
+ raise TopologyError(
112
+ "Integration authority stays with Parent; Child cannot integrate-child"
113
+ )
114
+
115
+
116
+ def expand_depends_on_to_requires(
117
+ graph: dict[str, Any],
118
+ from_id: str,
119
+ depends_on: list[str],
120
+ ) -> dict[str, Any]:
121
+ edges = list(graph.get("edges") or [])
122
+ for dep in _unique(depends_on):
123
+ if any(
124
+ edge.get("from") == from_id
125
+ and edge.get("to") == dep
126
+ and edge.get("type") == "requires"
127
+ for edge in edges
128
+ if isinstance(edge, dict)
129
+ ):
130
+ continue
131
+ edges.append({"from": from_id, "to": dep, "type": "requires"})
132
+ return {"schema_version": STAGE5_SCHEMA_VERSION, "edges": edges}
133
+
134
+
135
+ def unmet_requires(
136
+ graph: dict[str, Any],
137
+ satisfied: list[str],
138
+ from_id: str | None = None,
139
+ ) -> list[str]:
140
+ done = set(satisfied)
141
+ missing: list[str] = []
142
+ for edge in graph.get("edges") or []:
143
+ if not isinstance(edge, dict) or edge.get("type") != "requires":
144
+ continue
145
+ if from_id and edge.get("from") != from_id:
146
+ continue
147
+ target = edge.get("to")
148
+ if isinstance(target, str) and target not in done and target not in missing:
149
+ missing.append(target)
150
+ return missing
151
+
152
+
153
+ def project_task_map_graph(
154
+ topology: dict[str, Any],
155
+ graph: dict[str, Any],
156
+ ) -> dict[str, Any]:
157
+ children = []
158
+ for child_id in topology.get("children") or []:
159
+ if not isinstance(child_id, str):
160
+ continue
161
+ depends = [
162
+ edge.get("to")
163
+ for edge in graph.get("edges") or []
164
+ if isinstance(edge, dict)
165
+ and edge.get("type") == "requires"
166
+ and edge.get("from") == child_id
167
+ ]
168
+ children.append({"id": child_id, "depends_on": depends})
169
+ return {
170
+ "graph_authority": "kernel-extras",
171
+ "topology_kind": topology.get("kind") or "single",
172
+ "parent_id": topology.get("parent_id"),
173
+ "children": children,
174
+ }
175
+
176
+
177
+ def apply_kernel_graph_projection(data: dict[str, Any], extras: dict[str, Any]) -> None:
178
+ """Stamp Parent task-map frontmatter from Kernel extras. Projection only."""
179
+ topology = extras.get("topology")
180
+ graph = extras.get("dependency_graph")
181
+ if not isinstance(topology, dict) and not isinstance(graph, dict):
182
+ return
183
+ projection = project_task_map_graph(
184
+ topology if isinstance(topology, dict) else {},
185
+ graph if isinstance(graph, dict) else {"edges": []},
186
+ )
187
+ data["graph_authority"] = "kernel-extras"
188
+ data["topology_kind"] = projection["topology_kind"]
189
+ if projection.get("parent_id"):
190
+ data["parent_id"] = projection["parent_id"]
191
+ existing = {
192
+ child.get("id"): child
193
+ for child in data.get("children") or []
194
+ if isinstance(child, dict)
195
+ }
196
+ children = data.setdefault("children", [])
197
+ if not isinstance(children, list):
198
+ data["children"] = []
199
+ children = data["children"]
200
+ for child in projection["children"]:
201
+ current = existing.get(child["id"])
202
+ if current is None:
203
+ current = {"id": child["id"], "state": "open", "depends_on": []}
204
+ children.append(current)
205
+ existing[child["id"]] = current
206
+ current["depends_on"] = child["depends_on"]
207
+
208
+
209
+ def resident_on_demand_modules(extras: dict[str, Any]) -> list[str]:
210
+ modules = extras.get("ondemand_modules")
211
+ if not isinstance(modules, dict):
212
+ return []
213
+ active = modules.get("active") or []
214
+ return [name for name in active if isinstance(name, str)]
215
+
216
+
217
+ def _unique(values: list[Any]) -> list[str]:
218
+ out: list[str] = []
219
+ for value in values:
220
+ if not isinstance(value, str):
221
+ continue
222
+ item = value.strip()
223
+ if item and item not in out:
224
+ out.append(item)
225
+ return out
@@ -271,7 +271,7 @@ def build_child_prompt(
271
271
  f"Selected task: {child_rel}",
272
272
  "",
273
273
  "Delivery mode: subagent (Cursor)",
274
- "- **Default:** Parent session dispatches **Task** with `subagent_type=cstl-implement` and this prompt as the task description (writable sub-agent). Model under Cursor++ BYOK comes from `.cstl/local/cursor2plus/` routing (see `cursor-subagent-policy.md` Method 2.5/2.6).",
274
+ "- **Default:** Parent session dispatches **Task** with `subagent_type=cstl-implement` and this prompt as the task description (writable sub-agent). Native Cursor `model:` frontmatter still applies. Cursor++ / cstl-byok are not Native install surfaces.",
275
275
  "- **Exception:** If parent `child-prompts.md` or the user names this child for a **separate writable Agent chat**, open a new Agent session, pick the model manually, paste this prompt — do not use Task from Parent.",
276
276
  "- Parent retains `review-child` / `integrate-child`. Child must not nest further `cstl-research` / `cstl-implement` / `cstl-check` Task dispatches.",
277
277
  f"- Parent orchestration: `execution_topology={topo}`, `merge_limit={merge_lim}`.",
@@ -12,7 +12,7 @@ Frontmatter writes splice a re-serialized header between the raw `---`
12
12
  fences so the body is preserved byte-for-byte (including the blank line
13
13
  after the closing fence and the final newline), and key order stays stable:
14
14
  known keys first (id, title, status, type, locale, created, approved,
15
- linked_tasks), unknown keys keep their original relative order.
15
+ priority, linked_tasks), unknown keys keep their original relative order.
16
16
  """
17
17
 
18
18
  from __future__ import annotations
@@ -22,7 +22,8 @@ from dataclasses import dataclass, field
22
22
  from pathlib import Path
23
23
 
24
24
  from .artifact_search import parse_frontmatter, split_frontmatter
25
- from .io import read_json, write_json
25
+ from .io import read_json
26
+ from .kernel_command import kernel_expected_revision, kernel_patch, kernel_projection_extras
26
27
  from .paths import get_repo_root, get_tasks_dir
27
28
 
28
29
  POOL_STATUSES = frozenset({"inbox", "review", "accepted", "rejected", "rework"})
@@ -39,6 +40,7 @@ KNOWN_FRONTMATTER_KEY_ORDER = (
39
40
  "locale",
40
41
  "created",
41
42
  "approved",
43
+ "priority",
42
44
  LINKED_TASKS_KEY,
43
45
  )
44
46
 
@@ -328,7 +330,7 @@ def _iter_task_dirs(tasks_dir: Path):
328
330
 
329
331
 
330
332
  def _task_meta_with_pool_items(task_dir: Path, item_id: str, *, remove: bool) -> bool:
331
- """Add/remove item_id in task.json meta.pool_items. True if file changed."""
333
+ """Add/remove item_id in task.json meta.pool_items via Kernel patch."""
332
334
  data = read_json(task_dir / "task.json")
333
335
  if not isinstance(data, dict):
334
336
  return False
@@ -346,7 +348,17 @@ def _task_meta_with_pool_items(task_dir: Path, item_id: str, *, remove: bool) ->
346
348
  pool_items.append(item_id)
347
349
  meta[POOL_ITEMS_KEY] = pool_items
348
350
  data[META_KEY] = meta
349
- return bool(write_json(task_dir / "task.json", data))
351
+ action = "unlink" if remove else "link"
352
+ kernel_patch(
353
+ task_dir,
354
+ data,
355
+ kernel_projection_extras(data),
356
+ expected_revision=kernel_expected_revision(task_dir),
357
+ actor="pool.py link" if not remove else "pool.py unlink",
358
+ idempotency_key=f"patch:pool-{action}:{task_dir.name}:{item_id}",
359
+ evidence="pool_store._task_meta_with_pool_items",
360
+ )
361
+ return True
350
362
 
351
363
 
352
364
  def link_item_task(
@@ -312,11 +312,11 @@ def render_agent_instructions(
312
312
  label = (
313
313
  "**语义合规(cursorEnv 未知,保守 BYOK):**"
314
314
  if cursor_env == ENV_UNKNOWN
315
- else "**语义合规(Cursor++ BYOK):**"
315
+ else "**语义合规(BYOK env):**"
316
316
  )
317
317
  if locale != "zh":
318
318
  lines.append(
319
- "**Semantic compliance (Cursor++ BYOK):**"
319
+ "**Semantic compliance (BYOK env):**"
320
320
  if cursor_env != ENV_UNKNOWN
321
321
  else "**Semantic compliance (cursorEnv unknown, conservative BYOK):**"
322
322
  )
@@ -22,7 +22,6 @@ from pathlib import Path
22
22
  from .active_task import resolve_context_key
23
23
  from .artifact_locale import artifact_locale_summary, resolve_artifact_locale
24
24
  from .config import get_git_packages, get_journal_snippet_enabled
25
- from .session_memory import get_latest_journal_summary
26
25
  from .git import run_git
27
26
  from .packages_context import get_packages_section
28
27
  from .tasks import iter_active_tasks, load_task, get_all_statuses, children_progress
@@ -48,7 +47,30 @@ from .paths import (
48
47
  # =============================================================================
49
48
 
50
49
  _PACKAGE_NAME = "@blxzer/cursor-trellis"
51
- _PYTHON_CMD = "python"
50
+
51
+
52
+ def _resolve_python_command() -> str:
53
+ """Pick the platform-appropriate python command.
54
+
55
+ Mirrors test resolvePython() (win32 prefers python, POSIX prefers
56
+ python3) so rendered guidance matches what integration tests assert.
57
+ """
58
+ candidates = ["python", "python3"] if os.name == "nt" else ["python3", "python"]
59
+ for exe in candidates:
60
+ try:
61
+ subprocess.run(
62
+ [exe, "--version"],
63
+ capture_output=True,
64
+ timeout=2,
65
+ check=True,
66
+ )
67
+ return exe
68
+ except (OSError, subprocess.SubprocessError):
69
+ continue
70
+ return "python"
71
+
72
+
73
+ _PYTHON_CMD = _resolve_python_command()
52
74
  _UPDATE_CHECK_TIMEOUT_SECONDS = 1.0
53
75
  _VERSION_RE = re.compile(
54
76
  r"^\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?\s*$"
@@ -842,6 +864,10 @@ def _resolve_journal_snippet(
842
864
  ) -> str:
843
865
  if not journal_file or not developer:
844
866
  return ""
867
+ try:
868
+ from .session_memory import get_latest_journal_summary
869
+ except ImportError:
870
+ return ""
845
871
  return get_latest_journal_summary(journal_file, repo_root, developer)
846
872
 
847
873
 
@@ -26,6 +26,7 @@ from .task_map import (
26
26
  validate_child_archive_state,
27
27
  validate_parent_children_complete,
28
28
  )
29
+ from .full_quality import full_quality_archive_errors, rigor_from_required_controls
29
30
 
30
31
  SCHEMA_VERSION = 1
31
32
  BASELINE_GATE = "baseline-check"
@@ -182,13 +183,45 @@ def normalize_result(result: str) -> str:
182
183
  return result.strip().upper()
183
184
 
184
185
 
186
+ def wave_c_stop_read_confirmed(start: Path | None = None) -> bool:
187
+ """Return True after ``cstl update`` wrote the Wave C confirm flag.
188
+
189
+ Unconfirmed projects keep leftover dual-read. Missing ``.cstl`` is
190
+ unconfirmed (unit fixtures without a repo root stay dual-read).
191
+ """
192
+ from .paths import DIR_WORKFLOW, get_repo_root
193
+
194
+ root = get_repo_root(start)
195
+ path = root / DIR_WORKFLOW / ".p36-wave-c.json"
196
+ if not path.is_file():
197
+ return False
198
+ try:
199
+ parsed = json.loads(path.read_text(encoding="utf-8"))
200
+ except (OSError, json.JSONDecodeError):
201
+ return False
202
+ return isinstance(parsed, dict) and parsed.get("confirmed") is True
203
+
204
+
185
205
  def task_closeout_profile(task_dir: Path, task_data: dict | None = None) -> str:
186
- """Return lite, full, or parent closeout profile."""
206
+ """Return lite, full, or parent closeout profile.
207
+
208
+ New Tasks use persisted ``required_controls`` / Topology only. Missing
209
+ contract is explicit Lite. File existence and ``children[]`` are not
210
+ used to infer Rigor or Parent. Leftover kind/mode/classification are
211
+ dual-read only until Wave C is confirmed.
212
+ """
187
213
  data = task_data or {}
188
- child_names = data.get("children")
189
- if isinstance(child_names, list) and any(isinstance(name, str) for name in child_names):
214
+ topology = data.get("topology") if isinstance(data.get("topology"), dict) else {}
215
+ if topology.get("kind") == "parent-child":
190
216
  return "parent"
191
217
 
218
+ persisted = rigor_from_required_controls(data)
219
+ if persisted in ("lite", "full"):
220
+ return persisted
221
+
222
+ if wave_c_stop_read_confirmed(task_dir):
223
+ return "lite"
224
+
192
225
  meta = data.get("meta") if isinstance(data.get("meta"), dict) else {}
193
226
  candidates = [
194
227
  data.get("task_kind"),
@@ -200,8 +233,6 @@ def task_closeout_profile(task_dir: Path, task_data: dict | None = None) -> str:
200
233
  meta.get("classification"),
201
234
  meta.get("mode"),
202
235
  ]
203
-
204
- explicit_lite = False
205
236
  for value in candidates:
206
237
  if not isinstance(value, str):
207
238
  continue
@@ -211,13 +242,8 @@ def task_closeout_profile(task_dir: Path, task_data: dict | None = None) -> str:
211
242
  if "full" in normalized:
212
243
  return "full"
213
244
  if "lite" in normalized:
214
- explicit_lite = True
215
-
216
- if explicit_lite:
217
- return "lite"
245
+ return "lite"
218
246
 
219
- if (task_dir / "design.md").is_file() and (task_dir / "implement.md").is_file():
220
- return "full"
221
247
  return "lite"
222
248
 
223
249
 
@@ -478,7 +504,12 @@ def make_baseline_record(
478
504
 
479
505
 
480
506
  def write_gate_record(task_data: dict, transition: str, gate: str, record: dict) -> None:
481
- """Write a gate record into task_data in-place."""
507
+ """Write a gate record into task_data in-place.
508
+
509
+ This mutates memory only. Stage 2 persists `quality_gate_results` as a
510
+ Kernel Command projection (same command as `record-gate` / start / archive).
511
+ Do not treat this helper as an independent `task.json` writer.
512
+ """
482
513
  qgr = task_data.get("quality_gate_results")
483
514
  if not isinstance(qgr, dict):
484
515
  qgr = {}
@@ -499,6 +530,35 @@ def write_gate_record(task_data: dict, transition: str, gate: str, record: dict)
499
530
  transition_records[gate] = record
500
531
 
501
532
 
533
+ KERNEL_PROJECTION_EXTRA_KEYS = (
534
+ "quality_gate_results",
535
+ "execution_approval",
536
+ "required_controls",
537
+ "ac_evidence_ledger",
538
+ "independent_check",
539
+ "topology",
540
+ "dependency_graph",
541
+ "ondemand_modules",
542
+ "depends_on",
543
+ "dependency_satisfied",
544
+ "decompose_proposal",
545
+ "profile_health",
546
+ "event_bridge",
547
+ "middleware_providers",
548
+ "capability_router",
549
+ "required_capabilities",
550
+ )
551
+
552
+
553
+ def collect_kernel_projection_extras(task_data: dict) -> dict:
554
+ """Non-canonical task.json fields owned by the same Kernel Command."""
555
+ extras: dict = {}
556
+ for key in KERNEL_PROJECTION_EXTRA_KEYS:
557
+ if key in task_data:
558
+ extras[key] = task_data[key]
559
+ return extras
560
+
561
+
502
562
  def build_reviewer_gate_record(
503
563
  task_dir: Path,
504
564
  task_data: dict,
@@ -998,6 +1058,7 @@ def validate_archive(
998
1058
 
999
1059
  errors.extend(_required_file_errors(task_dir, ["verify.md"]))
1000
1060
  errors.extend(_verify_evidence_errors(task_dir, task_data))
1061
+ errors.extend(full_quality_archive_errors(task_dir, task_data))
1001
1062
 
1002
1063
  parent_name = task_data.get("parent")
1003
1064
  if isinstance(parent_name, str) and parent_name:
@@ -2,8 +2,10 @@
2
2
  """
3
3
  Parent/Child task-map helpers.
4
4
 
5
- task-map.md is the Parent Supervisor's orchestration authority. This module
6
- parses and writes only the small YAML frontmatter subset generated here.
5
+ task-map.md is a projection of the Kernel Topology / Dependency Graph plus
6
+ Parent orchestration fields (child worker state, event log). Graph authority
7
+ is Kernel extras (`topology`, `dependency_graph`); this module may render,
8
+ not invent, typed edges.
7
9
  """
8
10
 
9
11
  from __future__ import annotations
@@ -75,7 +77,7 @@ def ensure_task_map(
75
77
  data = {
76
78
  "parent_id": parent_data.get("id") or parent_dir.name,
77
79
  "contract_epoch": 1,
78
- "execution_topology": "serial",
80
+ "execution_topology": "parallel",
79
81
  "merge_limit": 1,
80
82
  "children": [],
81
83
  "stages": [],
@@ -85,7 +87,7 @@ def ensure_task_map(
85
87
 
86
88
  data.setdefault("parent_id", parent_data.get("id") or parent_dir.name)
87
89
  data.setdefault("contract_epoch", 1)
88
- data.setdefault("execution_topology", "serial")
90
+ data.setdefault("execution_topology", "parallel")
89
91
  data.setdefault("merge_limit", 1)
90
92
  data.setdefault("children", [])
91
93
  data.setdefault("stages", [])
@@ -102,6 +104,16 @@ def ensure_task_map(
102
104
  data["children"].append(child)
103
105
  existing[child_name] = child
104
106
 
107
+ extras = {
108
+ key: parent_data[key]
109
+ for key in ("topology", "dependency_graph")
110
+ if key in parent_data
111
+ }
112
+ if extras:
113
+ from .ondemand_topology import apply_kernel_graph_projection
114
+
115
+ apply_kernel_graph_projection(data, extras)
116
+
105
117
  write_task_map(parent_dir, data, body, event)
106
118
  return data
107
119
 
@@ -492,12 +504,21 @@ def _default_body() -> str:
492
504
  return (
493
505
  "# Task Map\n\n"
494
506
  "## Orchestration notes\n\n"
495
- "- `execution_topology: parallel` children with empty `depends_on` may run concurrently; "
496
- "Parent integrates serially up to `merge_limit`.\n"
507
+ "- **parallel-first:** Prefer Cursor Multitask / Build in Parallel / Agent `Task` / native "
508
+ "worktree. Trellis does not schedule workers or replace Multitask.\n"
509
+ "- `execution_topology: parallel` — default when ≥2 children have empty `depends_on`. "
510
+ "Parent `integrate-child`, HITL, `start-execution --approved`, and reviewer gates stay "
511
+ "serial (`merge_limit`).\n"
512
+ "- `serial_reason` — required when topology is `serial`. One of: shared write-set / "
513
+ "HITL or reviewer gate / `depends_on` unmet / user asked serial / conflict surface "
514
+ "cannot isolate.\n"
515
+ "- `stages:` (or `parallel_groups`) — concurrently runnable child id groups.\n"
516
+ "- `merge_points` — where serial join happens (usually `integrate-child` + gates). "
517
+ "Default `merge_limit: 1`.\n"
518
+ "- `touches` (or `conflict_surface`) — write-sets that can collide; declare before dispatch.\n"
497
519
  "- Child-reported states: `open` → `working` → `blocked` | `review`.\n"
498
520
  "- Parent-controlled states: `review` → `changes` | `accepted` → `integrating` → "
499
521
  "`integrated` | `cancelled`.\n"
500
- "- Declare `touches` per child before dispatch to reduce merge conflicts.\n"
501
522
  "- `isolation: git-worktree` — run `prepare-child-worktree` from the **git package root** "
502
523
  "(not a non-git harness root).\n\n"
503
524
  "## Event Log\n\n"
@@ -594,10 +615,22 @@ def _format_frontmatter(data: dict) -> str:
594
615
  lines = [
595
616
  f"parent_id: {_format_value(data.get('parent_id'))}",
596
617
  f"contract_epoch: {_format_value(data.get('contract_epoch', 1))}",
597
- f"execution_topology: {_format_value(data.get('execution_topology', 'serial'))}",
618
+ f"execution_topology: {_format_value(data.get('execution_topology', 'parallel'))}",
598
619
  f"merge_limit: {_format_value(data.get('merge_limit', 1))}",
599
- "children:",
600
620
  ]
621
+ if data.get("serial_reason"):
622
+ lines.append(f"serial_reason: {_format_value(data.get('serial_reason'))}")
623
+ if data.get("parallel_groups"):
624
+ lines.append(f"parallel_groups: {_format_value(data.get('parallel_groups'))}")
625
+ if data.get("merge_points"):
626
+ lines.append(f"merge_points: {_format_value(data.get('merge_points'))}")
627
+ if data.get("conflict_surface"):
628
+ lines.append(f"conflict_surface: {_format_value(data.get('conflict_surface'))}")
629
+ if data.get("graph_authority"):
630
+ lines.append(f"graph_authority: {_format_value(data.get('graph_authority'))}")
631
+ if data.get("topology_kind"):
632
+ lines.append(f"topology_kind: {_format_value(data.get('topology_kind'))}")
633
+ lines.append("children:")
601
634
  children = data.get("children", [])
602
635
  if isinstance(children, list):
603
636
  for child in children: