@zalom/plastic 2.0.0-alpha.9 → 2.0.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 (326) hide show
  1. package/PLASTIC.md +13 -136
  2. package/README.md +357 -133
  3. package/agents/plastic-enforcer.md +21 -16
  4. package/agents/plastic-executor.md +15 -4
  5. package/agents/plastic-node-research.md +30 -0
  6. package/agents/plastic-node-verify.md +28 -0
  7. package/agents/plastic-node-work.md +33 -0
  8. package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +7 -9
  9. package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
  10. package/assets/plastic-logo.svg +1 -0
  11. package/bin/crap +4 -0
  12. package/bin/lib/context_budget.rb +43 -6
  13. package/bin/lib/skill_census.rb +839 -0
  14. package/bin/plastic +8 -0
  15. package/bin/plastic-skill-census +114 -0
  16. package/bin/test +24 -4
  17. package/bin/verify-change +345 -0
  18. package/config_asks.yml +4 -4
  19. package/deprecations.yml +1 -1
  20. package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +19 -24
  21. package/{skills/auto/references → docs/help}/agent-architecture.md +16 -14
  22. package/docs/help/completion-and-done.md +53 -0
  23. package/docs/help/human-report-contract.md +152 -0
  24. package/{skills/conventions/references → docs/help}/knowledge-graph.md +9 -0
  25. package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +11 -13
  26. package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +1 -1
  27. package/{skills/conventions/references → docs/help}/roadmaps.md +5 -4
  28. package/{skills/tutorial/references → docs/help}/track-1-guided.md +28 -47
  29. package/{skills/tutorial/references → docs/help}/track-2-auto.md +8 -8
  30. package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
  31. package/hooks/call-budget +4 -0
  32. package/hooks/hooks.json +24 -0
  33. package/hooks/message-display +55 -2
  34. package/hooks/session-start +5 -1
  35. package/hooks/statusline +32 -27
  36. package/hooks/stop +5 -0
  37. package/package.json +5 -3
  38. package/scripts/append-ledger +2 -1
  39. package/scripts/dashboard.rb +267 -16
  40. package/scripts/day-summary +2 -1
  41. package/scripts/doctor.rb +680 -39
  42. package/scripts/end-intent +378 -56
  43. package/scripts/exec-worktree +5 -5
  44. package/scripts/file-session-intent +2 -1
  45. package/scripts/graph-measure +249 -0
  46. package/scripts/hook-call-budget +222 -0
  47. package/scripts/hook-capture +24 -123
  48. package/scripts/hook-close +2 -1
  49. package/scripts/hook-message-display +7 -0
  50. package/scripts/hook-record +24 -16
  51. package/scripts/hook-savepoint +27 -3
  52. package/scripts/hook-session-start +359 -321
  53. package/scripts/hook-stop +58 -0
  54. package/scripts/index-projection +74 -0
  55. package/scripts/insight-append +17 -4
  56. package/scripts/install.rb +9 -7
  57. package/scripts/lib/action_graph_shim.rb +279 -0
  58. package/scripts/lib/active_delivery.rb +105 -0
  59. package/scripts/lib/agent_models.rb +63 -25
  60. package/scripts/lib/arm.rb +74 -91
  61. package/scripts/lib/atomic_write.rb +31 -0
  62. package/scripts/lib/backup.rb +65 -0
  63. package/scripts/lib/cli/command.rb +92 -0
  64. package/scripts/lib/cli/commands/auto.rb +18 -0
  65. package/scripts/lib/cli/commands/auto_brief.rb +44 -0
  66. package/scripts/lib/cli/commands/auto_lock.rb +111 -0
  67. package/scripts/lib/cli/commands/auto_report.rb +50 -0
  68. package/scripts/lib/cli/commands/auto_take.rb +68 -0
  69. package/scripts/lib/cli/commands/backup.rb +43 -0
  70. package/scripts/lib/cli/commands/checkout.rb +25 -0
  71. package/scripts/lib/cli/commands/continue.rb +66 -0
  72. package/scripts/lib/cli/commands/doctor.rb +81 -0
  73. package/scripts/lib/cli/commands/feedback.rb +40 -0
  74. package/scripts/lib/cli/commands/help.rb +69 -0
  75. package/scripts/lib/cli/commands/hook.rb +32 -0
  76. package/scripts/lib/cli/commands/index.rb +23 -0
  77. package/scripts/lib/cli/commands/install.rb +21 -0
  78. package/scripts/lib/cli/commands/installer_verb.rb +37 -0
  79. package/scripts/lib/cli/commands/intent.rb +19 -0
  80. package/scripts/lib/cli/commands/intent_answer.rb +37 -0
  81. package/scripts/lib/cli/commands/intent_command.rb +58 -0
  82. package/scripts/lib/cli/commands/intent_end.rb +75 -0
  83. package/scripts/lib/cli/commands/intent_new.rb +62 -0
  84. package/scripts/lib/cli/commands/intent_note.rb +43 -0
  85. package/scripts/lib/cli/commands/intent_rule.rb +36 -0
  86. package/scripts/lib/cli/commands/intent_show.rb +32 -0
  87. package/scripts/lib/cli/commands/intent_spec.rb +44 -0
  88. package/scripts/lib/cli/commands/intent_step.rb +79 -0
  89. package/scripts/lib/cli/commands/intent_verify.rb +26 -0
  90. package/scripts/lib/cli/commands/migrate.rb +16 -0
  91. package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
  92. package/scripts/lib/cli/commands/next.rb +54 -0
  93. package/scripts/lib/cli/commands/project.rb +19 -0
  94. package/scripts/lib/cli/commands/project_links.rb +46 -0
  95. package/scripts/lib/cli/commands/project_list.rb +20 -0
  96. package/scripts/lib/cli/commands/project_new.rb +95 -0
  97. package/scripts/lib/cli/commands/query.rb +31 -0
  98. package/scripts/lib/cli/commands/render.rb +35 -0
  99. package/scripts/lib/cli/commands/roadmap.rb +19 -0
  100. package/scripts/lib/cli/commands/roadmap_check.rb +54 -0
  101. package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
  102. package/scripts/lib/cli/commands/roadmap_migrate.rb +53 -0
  103. package/scripts/lib/cli/commands/roadmap_next.rb +77 -0
  104. package/scripts/lib/cli/commands/roadmap_show.rb +65 -0
  105. package/scripts/lib/cli/commands/rollback.rb +20 -0
  106. package/scripts/lib/cli/commands/search.rb +60 -0
  107. package/scripts/lib/cli/commands/session.rb +18 -0
  108. package/scripts/lib/cli/commands/session_commit.rb +42 -0
  109. package/scripts/lib/cli/commands/session_handoff.rb +34 -0
  110. package/scripts/lib/cli/commands/session_summary.rb +35 -0
  111. package/scripts/lib/cli/commands/status.rb +68 -0
  112. package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
  113. package/scripts/lib/cli/commands/sync.rb +55 -0
  114. package/scripts/lib/cli/commands/uninstall.rb +20 -0
  115. package/scripts/lib/cli/commands/update.rb +20 -0
  116. package/scripts/lib/cli/commands/version.rb +53 -0
  117. package/scripts/lib/cli/frontier.rb +86 -0
  118. package/scripts/lib/cli/intent_progress.rb +36 -0
  119. package/scripts/lib/cli/legacy.rb +80 -0
  120. package/scripts/lib/cli/output.rb +137 -0
  121. package/scripts/lib/cli/scope.rb +134 -0
  122. package/scripts/lib/cli/table.rb +65 -0
  123. package/scripts/lib/cli.rb +94 -0
  124. package/scripts/lib/codex_adapter.rb +198 -0
  125. package/scripts/lib/compact_instructions.rb +13 -5
  126. package/scripts/lib/core_integrity.rb +71 -0
  127. package/scripts/lib/dashboard_screen.rb +40 -0
  128. package/scripts/lib/data_boundary.rb +132 -0
  129. package/scripts/lib/day_summary.rb +23 -14
  130. package/scripts/lib/doctor_core.rb +114 -38
  131. package/scripts/lib/doctor_session_ledger.rb +5 -53
  132. package/scripts/lib/engine_permissions.rb +88 -0
  133. package/scripts/lib/exec_worktree.rb +24 -21
  134. package/scripts/lib/feedback_report.rb +1 -1
  135. package/scripts/lib/graph_edges.rb +137 -0
  136. package/scripts/lib/graph_file.rb +246 -0
  137. package/scripts/lib/graph_measure.rb +645 -0
  138. package/scripts/lib/graph_measure_budget.rb +409 -0
  139. package/scripts/lib/graph_measure_cohorts.rb +487 -0
  140. package/scripts/lib/graph_measure_models.rb +413 -0
  141. package/scripts/lib/graph_measure_report.rb +532 -0
  142. package/scripts/lib/graph_tree.rb +98 -0
  143. package/scripts/lib/guarded_append.rb +155 -0
  144. package/scripts/lib/handoff.rb +40 -13
  145. package/scripts/lib/harness_adapter.rb +184 -0
  146. package/scripts/lib/hook_registry.rb +27 -3
  147. package/scripts/lib/hook_replay.rb +235 -0
  148. package/scripts/lib/index_entry.rb +62 -0
  149. package/scripts/lib/index_projection.rb +201 -0
  150. package/scripts/lib/insights.rb +1 -1
  151. package/scripts/lib/installer_core.rb +543 -71
  152. package/scripts/lib/intent_screen.rb +4 -4
  153. package/scripts/lib/intent_screen_ansi.rb +73 -12
  154. package/scripts/lib/intent_validator.rb +2 -2
  155. package/scripts/lib/lock.rb +10 -11
  156. package/scripts/lib/message_display.rb +350 -54
  157. package/scripts/lib/meter_watch.rb +185 -0
  158. package/scripts/lib/node_file.rb +234 -0
  159. package/scripts/lib/node_ids.rb +99 -0
  160. package/scripts/lib/node_input.rb +921 -0
  161. package/scripts/lib/node_input_compatibility.rb +62 -0
  162. package/scripts/lib/node_ledger.rb +386 -0
  163. package/scripts/lib/node_progress.rb +153 -0
  164. package/scripts/lib/node_return.rb +204 -0
  165. package/scripts/lib/node_worktree.rb +337 -0
  166. package/scripts/lib/outcome_report.rb +440 -0
  167. package/scripts/lib/preflight.rb +4 -6
  168. package/scripts/lib/project_config.rb +46 -0
  169. package/scripts/lib/project_validator.rb +3 -2
  170. package/scripts/lib/qmd_sync.rb +8 -7
  171. package/scripts/lib/ready_set.rb +462 -0
  172. package/scripts/lib/reference_archive.rb +45 -0
  173. package/scripts/lib/release_guard.rb +18 -0
  174. package/scripts/lib/report_screen.rb +1371 -47
  175. package/scripts/lib/rlm/corpus.rb +13 -0
  176. package/scripts/lib/rlm/probe.rb +29 -0
  177. package/scripts/lib/rlm/query.rb +22 -0
  178. package/scripts/lib/roadmap_graph.rb +210 -0
  179. package/scripts/lib/roadmap_migration.rb +95 -0
  180. package/scripts/lib/roadmap_queue.rb +170 -12
  181. package/scripts/lib/roadmap_render.rb +150 -0
  182. package/scripts/lib/roadmap_savepoint.rb +64 -14
  183. package/scripts/lib/runner_absorb.rb +703 -0
  184. package/scripts/lib/runner_answer.rb +206 -0
  185. package/scripts/lib/runner_core.rb +194 -0
  186. package/scripts/lib/runner_dispatch.rb +526 -0
  187. package/scripts/lib/runner_policy.rb +191 -0
  188. package/scripts/lib/runner_proposals.rb +275 -0
  189. package/scripts/lib/runner_rewind.rb +201 -0
  190. package/scripts/lib/runner_sweep.rb +231 -0
  191. package/scripts/lib/runner_until_empty.rb +252 -0
  192. package/scripts/lib/runner_watch.rb +389 -0
  193. package/scripts/lib/savepoint.rb +141 -19
  194. package/scripts/lib/scaffold_intent.rb +6 -3
  195. package/scripts/lib/screen_paint.rb +365 -28
  196. package/scripts/lib/screens/dashboard.rb +20 -0
  197. package/scripts/lib/screens/plan.rb +18 -0
  198. package/scripts/lib/screens/roadmap.rb +15 -0
  199. package/scripts/lib/search_index.rb +55 -0
  200. package/scripts/lib/session_close.rb +30 -28
  201. package/scripts/lib/session_git.rb +67 -19
  202. package/scripts/lib/session_ledger.rb +48 -4
  203. package/scripts/lib/session_usage.rb +190 -0
  204. package/scripts/lib/sqlite.rb +22 -0
  205. package/scripts/lib/stop_gate.rb +95 -0
  206. package/scripts/lib/store_discovery.rb +7 -6
  207. package/scripts/lib/store_layout.rb +54 -0
  208. package/scripts/lib/store_provisioning.rb +2 -1
  209. package/scripts/lib/store_sync.rb +85 -0
  210. package/scripts/lib/stores_move.rb +93 -0
  211. package/scripts/lib/untouched_scaffold.rb +51 -0
  212. package/scripts/lib/verify_intent.rb +36 -8
  213. package/scripts/lib/version_number.rb +48 -0
  214. package/scripts/lib/work_graph.rb +59 -0
  215. package/scripts/lib/work_graph_validator.rb +201 -0
  216. package/scripts/lib/worktree.rb +27 -32
  217. package/scripts/lib/worktree_sweep.rb +6 -5
  218. package/scripts/link-suggest +2 -1
  219. package/scripts/meter-watch +57 -0
  220. package/scripts/migrate-to-global +1 -1
  221. package/scripts/new-intent +4 -13
  222. package/scripts/node-input +92 -0
  223. package/scripts/node-run +225 -0
  224. package/scripts/node-transition +291 -0
  225. package/scripts/outcome-report +74 -0
  226. package/scripts/plastic-lock +65 -60
  227. package/scripts/project-links +6 -21
  228. package/scripts/promote-session-item +3 -2
  229. package/scripts/read-config +53 -9
  230. package/scripts/ready-set +126 -0
  231. package/scripts/release-check +123 -0
  232. package/scripts/report-screen +177 -16
  233. package/scripts/roadmap-graph +125 -0
  234. package/scripts/roadmap-savepoint +7 -0
  235. package/scripts/rollback.rb +5 -1
  236. package/scripts/runner +581 -0
  237. package/scripts/savepoint-note +11 -9
  238. package/scripts/session-commit +2 -1
  239. package/scripts/session-usage +56 -0
  240. package/scripts/skill-lint +115 -6
  241. package/scripts/spawn-preamble +2 -2
  242. package/scripts/update.rb +31 -4
  243. package/scripts/validate-work-graph +39 -0
  244. package/scripts/verify-intent +3 -2
  245. package/scripts/write-handoff +2 -1
  246. package/templates/agents.md +7 -7
  247. package/templates/config.yml +16 -9
  248. package/templates/dashboard-screen.md +22 -0
  249. package/templates/display-fixture.md +21 -0
  250. package/templates/graph.md +16 -0
  251. package/templates/index.md +1 -1
  252. package/templates/intent-screen.md +1 -1
  253. package/templates/node-decision.md +11 -0
  254. package/templates/node-research.md +13 -0
  255. package/templates/node-verify.md +13 -0
  256. package/templates/node-work.md +22 -0
  257. package/templates/outcome.md +8 -3
  258. package/templates/project.yml +1 -1
  259. package/templates/render.css +10 -0
  260. package/templates/report-plan.md +15 -0
  261. package/templates/report-roadmap-delivered.md +10 -0
  262. package/templates/report-roadmap-plan.md +9 -0
  263. package/templates/report-roadmap-state.md +9 -0
  264. package/templates/report-state.md +1 -1
  265. package/templates/roadmap.md +13 -0
  266. package/bin/plastic.js +0 -70
  267. package/scripts/lib/bridge.rb +0 -116
  268. package/skills/agent-advisor/SKILL.md +0 -92
  269. package/skills/auto/SKILL.md +0 -296
  270. package/skills/auto/evals/evals.json +0 -255
  271. package/skills/auto/references/end-tail.md +0 -66
  272. package/skills/auto/references/human-report-contract.md +0 -78
  273. package/skills/conventions/SKILL.md +0 -29
  274. package/skills/conventions/references/completion-and-done.md +0 -43
  275. package/skills/dashboard/SKILL.md +0 -169
  276. package/skills/dashboard/evals/evals.json +0 -38
  277. package/skills/dashboard/references/classification.md +0 -22
  278. package/skills/dashboard/templates/dashboard-global.md +0 -20
  279. package/skills/dashboard/templates/dashboard-project.md +0 -19
  280. package/skills/direct/SKILL.md +0 -66
  281. package/skills/direct/references/request-signals.md +0 -59
  282. package/skills/doctor/SKILL.md +0 -299
  283. package/skills/doctor/report.md +0 -102
  284. package/skills/feedback/SKILL.md +0 -98
  285. package/skills/feedback/references/transport-and-privacy.md +0 -65
  286. package/skills/feedback/report.md +0 -36
  287. package/skills/install/SKILL.md +0 -217
  288. package/skills/intent-continuing/SKILL.md +0 -154
  289. package/skills/intent-continuing/references/board-fill.md +0 -43
  290. package/skills/intent-continuing/references/boarding-matrix.md +0 -34
  291. package/skills/intent-continuing/references/context-management.md +0 -28
  292. package/skills/intent-continuing/references/liveness-ranking.md +0 -57
  293. package/skills/intent-creating/SKILL.md +0 -164
  294. package/skills/intent-creating/evals/evals.json +0 -72
  295. package/skills/intent-creating/references/lifecycle.md +0 -81
  296. package/skills/intent-creating/references/wikilinks.md +0 -8
  297. package/skills/intent-ending/SKILL.md +0 -176
  298. package/skills/intent-ending/evals/evals.json +0 -74
  299. package/skills/intent-executing/SKILL.md +0 -170
  300. package/skills/intent-executing/evals/evals.json +0 -66
  301. package/skills/intent-executing/implementer-prompt.md +0 -42
  302. package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
  303. package/skills/intent-speccing/SKILL.md +0 -130
  304. package/skills/intent-speccing/evals/evals.json +0 -126
  305. package/skills/intent-speccing/references/design-principles.md +0 -44
  306. package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
  307. package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
  308. package/skills/project-creating/SKILL.md +0 -162
  309. package/skills/project-creating/references/hubs-projects.md +0 -55
  310. package/skills/project-creating/references/project-scaffolding.md +0 -97
  311. package/skills/releasing/SKILL.md +0 -337
  312. package/skills/releasing/references/deprecations.md +0 -60
  313. package/skills/releasing/references/promotion-and-tagging.md +0 -66
  314. package/skills/releasing/references/release-lines.md +0 -105
  315. package/skills/roadmap/SKILL.md +0 -64
  316. package/skills/roadmap/references/file-format.md +0 -124
  317. package/skills/roadmap/references/operations.md +0 -112
  318. package/skills/rollback/SKILL.md +0 -91
  319. package/skills/tutorial/SKILL.md +0 -65
  320. package/skills/tutorial/evals/evals.json +0 -186
  321. package/skills/uninstall/SKILL.md +0 -75
  322. package/skills/update/SKILL.md +0 -126
  323. /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
  324. /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
  325. /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
  326. /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
@@ -1,6 +1,7 @@
1
1
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "store_layout"
4
5
  require "json"
5
6
  require "yaml"
6
7
  require "socket"
@@ -108,16 +109,16 @@ module Worktree
108
109
 
109
110
  # --- provisioning ----------------------------------------------------------
110
111
 
111
- # Resolve the slug from the bridge's intent.store, create code + store
112
+ # Resolve the slug from the delivery's intent.store, create code + store
112
113
  # worktrees (idempotent: reuse an existing worktree path, do not error), write
113
- # the `worktree` block plus `provisioned: true` onto bridge_data, return it.
114
+ # the `worktree` block plus `provisioned: true` onto delivery, return it.
114
115
  #
115
116
  # Fails open with a stderr log when the repo is non-git or unresolvable:
116
117
  # sets `provisioned: false` and leaves `code: null`. All git ops use
117
118
  # `git -C <resolved path>` -- never cwd (decision D6).
118
- def provision(bridge_data, home: Dir.home, runner: ShellRunner.new)
119
- return bridge_data unless bridge_data.is_a?(Hash)
120
- intent = bridge_data["intent"] || {}
119
+ def provision(delivery, home: Dir.home, runner: ShellRunner.new)
120
+ return delivery unless delivery.is_a?(Hash)
121
+ intent = delivery["intent"] || {}
121
122
  intent_id = intent["id"].to_s
122
123
  store = intent["store"].to_s
123
124
  intent_slug = slug_from_dir(intent["dir"]) || slug_from_dir(store)
@@ -173,15 +174,15 @@ module Worktree
173
174
  # replaced by the fresh unprovisioned block; the guard must not fail harder
174
175
  # than the bug it guards.
175
176
  if code_ok
176
- bridge_data["worktree"] = block
177
+ delivery["worktree"] = block
177
178
  else
178
- existing = bridge_data["worktree"]
179
+ existing = delivery["worktree"]
179
180
  keep = existing.is_a?(Hash) && !blank?(existing["code"]) &&
180
181
  Dir.exist?(existing["code"].to_s)
181
- bridge_data["worktree"] = block unless keep
182
+ delivery["worktree"] = block unless keep
182
183
  end
183
184
 
184
- bridge_data
185
+ delivery
185
186
  end
186
187
 
187
188
  # Remove the worktree (then `git worktree prune`), clear the worktree block.
@@ -189,21 +190,21 @@ module Worktree
189
190
  # policy on top via `finish`; this is the plain remove. Pass `remove: false` to
190
191
  # clear the block WITHOUT touching git (so `finish` can merge first, then call
191
192
  # release to drop the worktree once the code branch is integrated).
192
- def release(bridge_data, home: Dir.home, runner: ShellRunner.new, remove: true)
193
- return bridge_data unless bridge_data.is_a?(Hash)
194
- block = bridge_data["worktree"]
195
- return bridge_data unless block.is_a?(Hash)
193
+ def release(delivery, home: Dir.home, runner: ShellRunner.new, remove: true)
194
+ return delivery unless delivery.is_a?(Hash)
195
+ block = delivery["worktree"]
196
+ return delivery unless block.is_a?(Hash)
196
197
 
197
198
  if remove
198
- slug = slug_for_store(bridge_data.dig("intent", "store").to_s, home: home)
199
+ slug = slug_for_store(delivery.dig("intent", "store").to_s, home: home)
199
200
  repo = repo_for(slug, home: home)
200
201
 
201
202
  remove_worktree(runner, repo: repo, worktree: block["code"]) if repo && block["code"]
202
203
  prune(runner, repo: repo) if repo
203
204
  end
204
205
 
205
- bridge_data.delete("worktree")
206
- bridge_data
206
+ delivery.delete("worktree")
207
+ delivery
207
208
  end
208
209
 
209
210
  # --- cleanup policy (merge-vs-remove) -------------------------------------
@@ -222,19 +223,19 @@ module Worktree
222
223
  # Fail-open and idempotent throughout: a missing block, missing branch, or any
223
224
  # git failure never raises and never blocks teardown. All git ops use
224
225
  # `git -C <path>`, never cwd (decision D6). No-op when nothing was provisioned.
225
- def finish(bridge_data, home: Dir.home, runner: ShellRunner.new, merge: false)
226
- return bridge_data unless bridge_data.is_a?(Hash)
227
- block = bridge_data["worktree"]
228
- return bridge_data unless block.is_a?(Hash)
226
+ def finish(delivery, home: Dir.home, runner: ShellRunner.new, merge: false)
227
+ return delivery unless delivery.is_a?(Hash)
228
+ block = delivery["worktree"]
229
+ return delivery unless block.is_a?(Hash)
229
230
 
230
231
  if merge
231
- slug = slug_for_store(bridge_data.dig("intent", "store").to_s, home: home)
232
+ slug = slug_for_store(delivery.dig("intent", "store").to_s, home: home)
232
233
  repo = repo_for(slug, home: home)
233
234
  branch = block["code_branch"]
234
235
  merge_branch(runner, repo: repo, branch: branch) if repo && !blank?(branch)
235
236
  end
236
237
 
237
- release(bridge_data, home: home, runner: runner, remove: true)
238
+ release(delivery, home: home, runner: runner, remove: true)
238
239
  end
239
240
 
240
241
  # Merge `branch` into the repo's default branch from the main checkout. The
@@ -298,8 +299,8 @@ module Worktree
298
299
  # --- lock ------------------------------------------------------------------
299
300
 
300
301
  # True iff ANOTHER session's delivery.lock is FRESH on this intent's dir
301
- # (intent 108, D2): the durable lock file decides; /tmp bridges are not
302
- # consulted and no pid is probed. current_session being the owner or a
302
+ # (intent 108, D2): the durable lock file decides alone, and no pid is
303
+ # probed. current_session being the owner or a
303
304
  # delegate does not count as "other". A stale lock does not hold (explicit
304
305
  # takeover reclaims it).
305
306
  def lock_held_by_other?(intent_id:, store:, current_session:, home: Dir.home,
@@ -377,14 +378,8 @@ module Worktree
377
378
  # (no project repo). Mirrors qmd_sync's slug_for_store fallback.
378
379
  def slug_for_store(store_dir, home: Dir.home)
379
380
  return nil if blank?(store_dir)
380
- plastic_home = File.expand_path(File.join(home, ".plastic"))
381
- store_dir = File.expand_path(store_dir)
382
- return nil if store_dir == File.join(plastic_home, "store")
383
-
384
- parts = store_dir.split(File::SEPARATOR)
385
- idx = parts.rindex("projects")
386
- return parts[idx + 1] if idx && parts[idx + 1] && parts[idx + 2] == "store"
387
- nil
381
+ slug = Plastic::StoreLayout.locate(store_dir).last
382
+ (slug == Plastic::StoreLayout::GLOBAL) ? nil : slug
388
383
  end
389
384
 
390
385
  # Best-effort slug for the worktree dir-name from an intent dir/store path:
@@ -1,8 +1,9 @@
1
1
  # encoding: UTF-8
2
2
  # frozen_string_literal: true
3
3
 
4
+ require_relative "store_layout"
4
5
  require_relative "worktree"
5
- require_relative "bridge"
6
+ require_relative "index_entry"
6
7
 
7
8
  # WorktreeSweep -- the one-time orphan sweep for store worktrees retired by
8
9
  # intent 178 (D2). Pure candidate classification plus a thin apply step, both
@@ -55,14 +56,14 @@ module WorktreeSweep
55
56
  # Matching on the full name, not just the numeric id, sidesteps id
56
57
  # collisions across projects (ids are only unique WITHIN one store).
57
58
  def resolve_intent_dir(plastic_home, name)
58
- candidates = [File.join(plastic_home, "store", name)] +
59
- Dir.glob(File.join(plastic_home, "projects", "*", "store", name))
59
+ candidates = [File.join(Plastic::StoreLayout.global_store(plastic_home), name)] +
60
+ Dir.glob(File.join(Plastic::StoreLayout.projects_root(plastic_home), "*", "store", name))
60
61
  candidates.find { |d| Dir.exist?(d) }
61
62
  end
62
63
 
63
64
  # The INDEX.md section heading (e.g. "Active", "Completed") that lists this
64
65
  # intent dir, or nil if no INDEX.md entry links to it. Reuses
65
- # Bridge.index_entry_match so this never drifts from the shared parser.
66
+ # IndexEntry.match so this never drifts from the shared parser.
66
67
  def index_status(intent_dir, name)
67
68
  store = File.dirname(intent_dir)
68
69
  index = File.join(File.dirname(store), "INDEX.md")
@@ -75,7 +76,7 @@ module WorktreeSweep
75
76
  section = stripped.sub(/\A##\s*/, "").strip
76
77
  next
77
78
  end
78
- m = Bridge.index_entry_match(stripped)
79
+ m = IndexEntry.match(stripped)
79
80
  next unless m
80
81
  return section if m[3].to_s.include?(name)
81
82
  end
@@ -26,6 +26,7 @@
26
26
  # nothing. --edge is explicit and required (sources is decided by origin, never
27
27
  # inferred); --rating is required when --edge is chain.
28
28
 
29
+ require_relative "lib/store_layout"
29
30
  require_relative "lib/link_suggestions"
30
31
 
31
32
  module LinkSuggestCLI
@@ -69,7 +70,7 @@ module LinkSuggestCLI
69
70
  i += 1
70
71
  end
71
72
  end
72
- opts[:store_dir] ||= File.join(opts[:home], "store")
73
+ opts[:store_dir] ||= Plastic::StoreLayout.global_store(opts[:home])
73
74
  opts
74
75
  end
75
76
 
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # meter-watch (intent 355, n5, D6): reads the rate-limit cache under --home
6
+ # and writes ~/.plastic/.cache/meter-state.json with the state (ok, reduce,
7
+ # stop, resume, stale, unavailable), the two raw percentages, the reset
8
+ # time, and when it was checked. A session watches that one file instead of
9
+ # every session parsing the cache and re-deriving the thresholds.
10
+ #
11
+ # Usage:
12
+ # meter-watch [--home DIR]
13
+ # meter-watch --install-timer [--home DIR]
14
+ #
15
+ # --install-timer writes a LaunchAgent plist under --home (default
16
+ # ~/.plastic when --home is not given) that runs this tick every 20
17
+ # minutes. It never calls launchctl; load it yourself with the command it
18
+ # prints. The Plastic installer never calls --install-timer on its own.
19
+ #
20
+ # Exit codes: 0 reported (unavailable included); 2 usage error.
21
+
22
+ require "json"
23
+ require_relative "lib/meter_watch"
24
+
25
+ def usage_abort(message)
26
+ warn "meter-watch: #{message}"
27
+ warn "Usage: meter-watch [--home DIR] [--install-timer]"
28
+ exit 2
29
+ end
30
+
31
+ home = File.join(Dir.home, ".plastic")
32
+ install_timer = false
33
+
34
+ argv = ARGV.dup
35
+ until argv.empty?
36
+ case (token = argv.shift)
37
+ when "--home"
38
+ usage_abort("--home requires a value") if argv.empty?
39
+ home = argv.shift
40
+ when "--install-timer"
41
+ install_timer = true
42
+ else
43
+ usage_abort("unknown argument #{token.inspect}")
44
+ end
45
+ end
46
+
47
+ home = File.expand_path(home)
48
+
49
+ if install_timer
50
+ plist_path = MeterWatch.install_timer(home: home, script_path: File.expand_path(__FILE__))
51
+ puts "Installed LaunchAgent at #{plist_path}"
52
+ puts "Run `launchctl load #{plist_path}` to activate it."
53
+ exit 0
54
+ end
55
+
56
+ state = MeterWatch.new(home: home).tick
57
+ puts JSON.generate(state)
@@ -17,7 +17,7 @@ GLOBAL_ROOT="$HOME/.plastic"
17
17
  LOCAL_PLASTIC="$PROJECT_PATH/.plastic"
18
18
 
19
19
  if [ ! -d "$GLOBAL_ROOT" ]; then
20
- echo "Error: Global Plastic not installed. Run /plastic-install first." >&2
20
+ echo "Error: Global Plastic not installed. Run 'plastic install' first." >&2
21
21
  exit 1
22
22
  fi
23
23
 
@@ -35,6 +35,7 @@
35
35
  # exactly one line, `created <dir>` or `joined <dir>`; exit 0 either way,
36
36
  # exit 1 on bad input or failed self-validation.
37
37
 
38
+ require_relative "lib/store_layout"
38
39
  require "fileutils"
39
40
  require "date"
40
41
  require_relative "lib/savepoint"
@@ -167,17 +168,7 @@ end
167
168
  # [nil, "global"] only if the layout is unrecognized, in which case Links
168
169
  # projection falls back to single-store resolution rooted at this store.
169
170
  def store_context(store)
170
- store = File.expand_path(store)
171
- parent = File.dirname(store) # `<home>` or `<home>/projects/<slug>`
172
- if File.basename(store) == "store" && File.basename(File.dirname(parent)) == "projects"
173
- slug = File.basename(parent)
174
- home = File.dirname(File.dirname(parent)) # strip projects/<slug>
175
- [home, "project:#{slug}"]
176
- elsif File.basename(store) == "store"
177
- [parent, "global"] # `<home>/store`
178
- else
179
- [parent, "global"]
180
- end
171
+ Plastic::StoreLayout.home_and_scope(store)
181
172
  end
182
173
 
183
174
  # The in-scope stores under `plastic_home`, each { key:, store: }. Delegates to
@@ -283,7 +274,7 @@ def run_tmp(opts)
283
274
  abort "new-intent: --sources is not valid with --tmp" unless opts[:sources].empty?
284
275
  abort "new-intent: --tags is not valid with --tmp" unless opts[:tags].empty?
285
276
 
286
- store = opts[:store] ? expand(opts[:store]) : expand(File.join(ENV.fetch("PLASTIC_HOME", "~/.plastic"), "store"))
277
+ store = opts[:store] ? expand(opts[:store]) : Plastic::StoreLayout.global_store(expand(ENV.fetch("PLASTIC_HOME", "~/.plastic")))
287
278
  templates = opts[:templates] ? expand(opts[:templates]) : default_templates_dir
288
279
  abort "new-intent: templates dir not found: #{templates}" unless Dir.exist?(templates)
289
280
 
@@ -362,7 +353,7 @@ def main(argv)
362
353
  # 4a. I1 reciprocity: write the child's id into EACH source intent's frontmatter
363
354
  # `chain` (the formative-reciprocity backlink), for BOTH the `--parent` and the
364
355
  # `--sources` path. `sources` is the redundant-explicit set from step 3 (it already
365
- # folds in `--parent`). Collect the touched source files so their `## Links` can be
356
+ # includes `--parent`). Collect the touched source files so their `## Links` can be
366
357
  # re-projected once the chain edges are on disk.
367
358
  source_files = []
368
359
  sources.each do |src_id|
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ require_relative "lib/node_input"
6
+ require_relative "lib/savepoint"
7
+
8
+ # node-input - the CLI over NodeInput (intent 338, G5). Builds one node's
9
+ # whole input from disk and writes it to attempts/<node>--a<N>.input (or
10
+ # --out), printing a parsable summary and the exact node-transition running
11
+ # command intent 340's runner should record.
12
+ #
13
+ # Usage:
14
+ # node-input <intent_dir> --node <id> [--budget N] [--hop-tokens N]
15
+ # [--holder H] [--expires E] [--model M] [--attempt N]
16
+ # [--out PATH] [--force]
17
+ #
18
+ # Exit codes (spec D17, shared with node-transition's family):
19
+ # 0 - the node input was written (or the identical bytes already existed)
20
+ # 2 - usage: not an intent directory, missing --node, or an unknown node
21
+ # 3 - an unreadable, cyclic or unparsable graph, node file, or record
22
+ # 4 - overflow past the third cut; nothing was written
23
+ # 5 - an existing attempt file whose bytes differ; nothing was written
24
+ module NodeInputCLI
25
+ module_function
26
+
27
+ def usage
28
+ warn "Usage: node-input <intent_dir> --node <id> [--budget N] [--hop-tokens N] " \
29
+ "[--holder H] [--expires E] [--model M] [--attempt N] [--out PATH] [--force]"
30
+ end
31
+
32
+ def intent_directory?(dir)
33
+ dir && File.directory?(dir) && File.exist?(Savepoint.intent_file(dir))
34
+ end
35
+
36
+ def opt(args, name)
37
+ (i = args.index(name)) && args[i + 1]
38
+ end
39
+
40
+ def main(argv)
41
+ args = argv.dup
42
+ intent_dir_arg = args.shift
43
+
44
+ unless intent_directory?(intent_dir_arg && File.expand_path(intent_dir_arg))
45
+ warn "node-input: #{intent_dir_arg.inspect} is not an intent directory"
46
+ usage
47
+ exit 2
48
+ end
49
+ intent_dir = File.expand_path(intent_dir_arg)
50
+
51
+ node = opt(args, "--node")
52
+ if node.to_s.strip.empty?
53
+ warn "node-input: --node is required"
54
+ usage
55
+ exit 2
56
+ end
57
+
58
+ budget = opt(args, "--budget")
59
+ hop_tokens = opt(args, "--hop-tokens")
60
+ attempt = opt(args, "--attempt")
61
+ out = opt(args, "--out")
62
+ holder = opt(args, "--holder")
63
+ expires = opt(args, "--expires")
64
+ model = opt(args, "--model")
65
+ force = args.include?("--force")
66
+
67
+ result = NodeInput.build(
68
+ intent_dir: intent_dir,
69
+ node: node,
70
+ budget_tokens: budget ? budget.to_i : NodeInput::DEFAULT_BUDGET_TOKENS,
71
+ hop_tokens: hop_tokens ? hop_tokens.to_i : NodeInput::DEFAULT_HOP_TOKENS,
72
+ holder: holder,
73
+ expires: expires,
74
+ model: model,
75
+ attempt: attempt ? attempt.to_i : nil,
76
+ out: out,
77
+ force: force,
78
+ )
79
+
80
+ if result[:ok]
81
+ puts NodeInput.summary_line(result)
82
+ puts result[:running_command]
83
+ exit 0
84
+ end
85
+
86
+ Array(result[:errors]).each { |e| warn "node-input: #{e}" }
87
+ puts result[:needs_decision_command] if result[:needs_decision_command]
88
+ exit result[:exit_code]
89
+ end
90
+ end
91
+
92
+ NodeInputCLI.main(ARGV) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ require "digest"
6
+ require "time"
7
+ require "yaml"
8
+ require "fileutils"
9
+ require_relative "lib/savepoint"
10
+ require_relative "lib/node_ledger"
11
+ require_relative "lib/node_input"
12
+ require_relative "lib/node_worktree"
13
+ require_relative "lib/node_file"
14
+ require_relative "lib/ready_set"
15
+ require_relative "lib/runner_core"
16
+ require_relative "lib/codex_adapter"
17
+
18
+ # node-run - the Codex leg of one node's whole attempt (intent 340b, G7c, n6).
19
+ #
20
+ # On Claude Code a subagent dispatch IS the executor; on Codex the executor
21
+ # is a subprocess, so this script owns the timing of a whole node: it reads
22
+ # the node's live `running` line, resolves the node input that line names,
23
+ # builds the `codex exec` argv for the node's kind (CodexAdapter), runs it
24
+ # in the node's worktree with the node input on stdin, reads the executor's
25
+ # final message, and writes it to the attempt's own return path.
26
+ #
27
+ # It writes NO ledger transition, on any path - success, refusal or error
28
+ # alike. That is the whole point: exactly one thing writes `done` (the
29
+ # runner's own six-check absorb gate), and a second writer here would throw
30
+ # that guarantee away. The caller absorbs with `runner step --return
31
+ # ID=PATH` through the same six checks every other return goes through.
32
+ #
33
+ # Usage:
34
+ # node-run <intent_dir> --node <id> [--session SID] [--now ISO8601]
35
+ # [--timeout-seconds N]
36
+ #
37
+ # Exit codes: 0 - a return file was written (whatever its content actually
38
+ # says; that judgment belongs to the absorb gate, not this script). Every
39
+ # other code is a REFUSAL: nothing is written, node-run never even reached
40
+ # `codex exec`.
41
+ # 2 - usage: not an intent directory, or missing --node
42
+ # 3 - the node has no live `running` line
43
+ # 4 - the running line is held by another session
44
+ # 5 - the node input the running line names is missing from disk
45
+ # 6 - the node input on disk does not hash to the running line's input=
46
+ # 7 - no worktree could be resolved to run in
47
+ module NodeRunCLI
48
+ module_function
49
+
50
+ def usage
51
+ warn "Usage: node-run <intent_dir> --node <id> [--session SID] [--now ISO8601] " \
52
+ "[--timeout-seconds N]"
53
+ end
54
+
55
+ def opt(args, name)
56
+ (i = args.index(name)) && args[i + 1]
57
+ end
58
+
59
+ def intent_directory?(dir)
60
+ dir && File.directory?(dir) && File.exist?(Savepoint.intent_file(dir))
61
+ end
62
+
63
+ # <intent_dir>/attempts/<node>--a<N>.return.yml (spec): beside the node input,
64
+ # one per attempt, the same treatment input files already get - never
65
+ # reused across attempts, so a second attempt's return can never be
66
+ # mistaken for the first's.
67
+ def return_path(intent_dir, node, attempt)
68
+ File.join(intent_dir, "attempts", "#{node}--a#{attempt}.return.yml")
69
+ end
70
+
71
+ # The node's own declared kind, read straight off its node file - "work"
72
+ # when the file cannot be found or parsed at all (mirroring RunnerPolicy's
73
+ # own unknown-kind fallback), but the LITERAL string it declares
74
+ # otherwise, even an invalid one (matrix row 6.10): CodexAdapter's own
75
+ # kind table only distinguishes "read-only" (verify, research) from
76
+ # everything else, so an unrecognized kind still gets the widest, safest
77
+ # treatment rather than no treatment at all.
78
+ def resolve_kind(intent_dir, node)
79
+ path = ReadySet.find_node_path(intent_dir, node)
80
+ return "work" unless path
81
+
82
+ parsed = NodeFile.parse(path)
83
+ kind = parsed[:kind].to_s.strip
84
+ kind.empty? ? "work" : kind
85
+ end
86
+
87
+ # The directory `codex exec -C` runs in: a `work` node's own worktree when
88
+ # one is actually provisioned on disk, else the shared intent worktree -
89
+ # verify and research nodes never get a worktree of their own (327 D6,
90
+ # D29), so the only place they could ever leave a diff is the intent
91
+ # worktree itself, exactly where NodeWorktree.changed_paths already looks
92
+ # for one.
93
+ def node_target_dir(context, node:, kind:)
94
+ if NodeWorktree::WORKTREE_KINDS.include?(kind.to_s)
95
+ p = NodeWorktree.paths(context, node: node)
96
+ return p["path"] if p["path"] && Dir.exist?(p["path"])
97
+ end
98
+ context.worktree
99
+ end
100
+
101
+ # A synthetic, deliberately non-YAML-mapping line the absorb gate reads as
102
+ # unparsable (spec Approach: "a return file the gate reads as
103
+ # unparsable"), covering every subprocess failure the same way: a nonzero
104
+ # exit, a timeout, an empty message file, and a message with no YAML
105
+ # document either way. Carries no colon-shaped `key: value` text on
106
+ # purpose, so it can never accidentally parse as a YAML mapping that
107
+ # happens to satisfy NodeReturn's own schema.
108
+ def unparsable_body(node, result)
109
+ reason =
110
+ if result[:timed_out]
111
+ "node-run - codex exec for #{node} timed out with no usable return"
112
+ elsif !result[:exit_code].nil? && result[:exit_code] != 0
113
+ "node-run - codex exec for #{node} exited #{result[:exit_code]} with no usable return"
114
+ else
115
+ "node-run - codex exec for #{node} produced no parsable YAML document on either " \
116
+ "--output-last-message or stdout"
117
+ end
118
+ "#{reason}\n"
119
+ end
120
+
121
+ def main(argv)
122
+ args = argv.dup
123
+ intent_dir_arg = args.shift
124
+
125
+ unless intent_directory?(intent_dir_arg && File.expand_path(intent_dir_arg))
126
+ warn "node-run: #{intent_dir_arg.inspect} is not an intent directory"
127
+ usage
128
+ exit 2
129
+ end
130
+ intent_dir = File.expand_path(intent_dir_arg)
131
+
132
+ node = opt(args, "--node")
133
+ if node.to_s.strip.empty?
134
+ warn "node-run: --node is required"
135
+ usage
136
+ exit 2
137
+ end
138
+
139
+ session_flag = opt(args, "--session")
140
+ now_flag = opt(args, "--now")
141
+ timeout_flag = opt(args, "--timeout-seconds")
142
+
143
+ begin
144
+ now_flag ? Time.iso8601(now_flag) : Time.now
145
+ rescue ArgumentError
146
+ warn "node-run: --now must be ISO 8601"
147
+ exit 2
148
+ end
149
+
150
+ context = RunnerCore.context(intent_dir: intent_dir, session: session_flag,
151
+ env: ENV["CLAUDE_CODE_SESSION_ID"])
152
+
153
+ savepoint_path = File.join(intent_dir, "savepoint.md")
154
+ current_status = NodeLedger.status_for(savepoint_path, node)
155
+ running_entry = NodeLedger.last_running(savepoint_path, node)
156
+ unless current_status == "running" && running_entry
157
+ warn "node-run: #{node} has no live running line (state is #{current_status})"
158
+ exit 3
159
+ end
160
+
161
+ fields = running_entry[:fields] || {}
162
+ holder = fields["holder"]
163
+ if context.session.nil? || context.session.to_s != holder.to_s
164
+ warn "node-run: #{node}'s running line is held by #{holder.inspect}, not this session"
165
+ exit 4
166
+ end
167
+
168
+ kind = resolve_kind(intent_dir, node)
169
+
170
+ # matrix row 6.11: the attempt number comes from NodeInput's own
171
+ # counter, never a hand count. `lease_flag_given: false` because the
172
+ # running line this attempt is acting on ALREADY exists on disk (it is
173
+ # what we just read above) - counting it a second time would double it
174
+ # (the same count, plus one, that produced it in the first place when
175
+ # the node input was originally built).
176
+ attempt = NodeInput.compute_attempt_number(intent_dir: intent_dir, node: node, lease_flag_given: false)
177
+ input_path = NodeInput.input_path(intent_dir: intent_dir, node: node, attempt: attempt)
178
+
179
+ unless File.exist?(input_path)
180
+ warn "node-run: #{node}'s node input is missing at #{input_path}"
181
+ exit 5
182
+ end
183
+
184
+ input_bytes = File.binread(input_path)
185
+ actual_sha = Digest::SHA256.hexdigest(input_bytes)[0, 12]
186
+ expected_sha = fields["input"]
187
+ unless expected_sha && actual_sha == expected_sha
188
+ warn "node-run: #{node}'s node input at #{input_path} does not hash to input=#{expected_sha.inspect} " \
189
+ "(computed #{actual_sha})"
190
+ exit 6
191
+ end
192
+
193
+ worktree = node_target_dir(context, node: node, kind: kind)
194
+ if worktree.nil? || !Dir.exist?(worktree)
195
+ warn "node-run: #{node} has no resolvable worktree to run in"
196
+ exit 7
197
+ end
198
+
199
+ out_path = return_path(intent_dir, node, attempt)
200
+ FileUtils.mkdir_p(File.dirname(out_path))
201
+ message_path = "#{out_path}.msg"
202
+
203
+ timeout_seconds = timeout_flag ? timeout_flag.to_i : CodexAdapter.timeout_seconds(kind)
204
+ codex_argv = CodexAdapter.build_argv(kind: kind, worktree: worktree, output_last_message: message_path,
205
+ model: fields["model"], effort: fields["effort"])
206
+
207
+ result = CodexAdapter.execute(codex_argv, stdin_data: input_bytes, timeout_seconds: timeout_seconds,
208
+ output_last_message_path: message_path)
209
+
210
+ # matrix rows 6.20-6.22: a nonzero exit or a timeout is authoritative -
211
+ # any message text the child managed to leave behind is never trusted
212
+ # once either has happened. Only a clean exit with no usable message at
213
+ # all falls to "no parsable YAML document".
214
+ failed = result[:timed_out] || (!result[:exit_code].nil? && result[:exit_code] != 0)
215
+ body = failed ? unparsable_body(node, result) : (result[:message] || unparsable_body(node, result))
216
+
217
+ File.write(out_path, body)
218
+ File.delete(message_path) if File.exist?(message_path)
219
+
220
+ puts out_path
221
+ exit 0
222
+ end
223
+ end
224
+
225
+ NodeRunCLI.main(ARGV) if $PROGRAM_NAME == __FILE__