@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
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic auto brief` - an intent's spawn preamble (`spawn-preamble`, run
6
+ # through Legacy). `--role advisor` also prints the three shapes an advisor
7
+ # request takes, so a dispatching lead never has to look them up.
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class AutoBrief < IntentCommand
12
+ USAGE_LINE = "plastic auto brief ID [--role ROLE] [--json]"
13
+
14
+ SCRIPT = "spawn-preamble"
15
+ AFTER = "plastic auto report ID"
16
+ BECAUSE = "the report closes the turn the preamble opened"
17
+
18
+ ADVISOR_SHAPES =
19
+ "State the shape you need: a verdict plus the biggest risk for one bounded " \
20
+ "decision; a stepped plan plus a risk map for a plan or plan review; rival " \
21
+ "approaches and kill criteria for architecture, one-way doors, or deadlocks."
22
+
23
+ def call
24
+ super
25
+ @output.row("advisor request shapes", ADVISOR_SHAPES) if role == "advisor"
26
+ end
27
+
28
+ private
29
+
30
+ def role
31
+ options[:role]
32
+ end
33
+
34
+ def switches(parser)
35
+ parser.on("--role ROLE") { |v| @options[:role] = v }
36
+ end
37
+
38
+ def script_arguments
39
+ role ? [intent_dir, "--role", role] : [intent_dir]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "../command"
5
+ require_relative "../legacy"
6
+
7
+ # `plastic auto lock` - inspects, repairs, or releases an intent's delivery
8
+ # lock (`plastic-lock status|fix|release --intent-dir DIR`, run through
9
+ # Legacy). The verb comes first, then the id, since the verb is what the
10
+ # table's two-token match consumes as part of the command name.
11
+ #
12
+ # `status` and `fix` speak JSON, so this command renders them as a screen and
13
+ # leaves the document to `--json`. `release` already reports in prose. A lock
14
+ # another session owns exits 3, which travels as a refusal.
15
+ module Plastic
16
+ class CLI
17
+ module Commands
18
+ class AutoLock < Command
19
+ USAGE_LINE = "plastic auto lock status|fix|release ID [--json]"
20
+
21
+ VERBS = %w[status fix release].freeze
22
+ AFTER = {"status" => "none", "fix" => "plastic auto brief ID",
23
+ "release" => "plastic session summary"}.freeze
24
+ BECAUSE = {"status" => "inspection is complete; a held lock does not authorize repair",
25
+ "fix" => "a repaired lock is ready for the preamble again",
26
+ "release" => "the summary is what a freed lock leads to"}.freeze
27
+
28
+ def initialize(argv, runner: nil, **streams)
29
+ super(argv, **streams)
30
+ @runner = runner
31
+ end
32
+
33
+ def call
34
+ raise Usage, "VERB must be one of #{VERBS.join(", ")}" unless VERBS.include?(verb)
35
+
36
+ case verb
37
+ when "status" then screen(report("status"))
38
+ when "fix" then options[:json] ? run_verb : fix_screen(report("fix"))
39
+ else run_verb
40
+ end
41
+ @output.next_step(AFTER.fetch(verb).sub("ID", id.to_s), because: BECAUSE.fetch(verb))
42
+ end
43
+
44
+ private
45
+
46
+ def run_verb
47
+ status = legacy.run("plastic-lock", verb, "--intent-dir", intent_dir)
48
+ raise Failure, "plastic-lock exited #{status}" unless status.zero?
49
+ end
50
+
51
+ def report(name)
52
+ text, status = legacy.capture("plastic-lock", name, "--intent-dir", intent_dir)
53
+ raise Failure, "plastic-lock exited #{status}" unless status.zero?
54
+
55
+ JSON.parse(text)
56
+ rescue JSON::ParserError
57
+ raise Failure, "plastic-lock did not print a report"
58
+ end
59
+
60
+ def screen(report)
61
+ @output.row("intent", File.basename(report["intent_dir"].to_s))
62
+ @output.row("lock", lock_line(report))
63
+ @output.row("worktree", worktree_line(report["worktree"]))
64
+ @output.row("delivering", report["delivering"] ? "yes" : "no")
65
+ @output.row("claims", Array(report["claims"]).map(&:to_s))
66
+ end
67
+
68
+ def fix_screen(report)
69
+ lock = report["lock"] || {}
70
+ @output.row("lock", "#{report["status"]} by #{lock["owner_session"] || report["session"]}, " \
71
+ "#{lock["run_mode"]} mode")
72
+ @output.row("actions", Array(report["actions"]).map(&:to_s))
73
+ end
74
+
75
+ def lock_line(report)
76
+ lock = report["lock"]
77
+ return "corrupt, plastic auto lock fix #{id} rewrites it" if report["lock_corrupt"]
78
+ return "none" if lock.nil?
79
+
80
+ holder = lock.is_a?(Hash) ? (lock["owner_session"] || lock["session"]) : lock
81
+ "held by #{holder}#{", stale" unless report["lock_fresh"]}"
82
+ end
83
+
84
+ def worktree_line(worktree)
85
+ return "none" unless worktree.is_a?(Hash) && worktree["provisioned"]
86
+
87
+ [worktree["code"], worktree["code_branch"]].compact.join(" on ")
88
+ end
89
+
90
+ def verb
91
+ arguments[0]
92
+ end
93
+
94
+ def id
95
+ arguments[1]
96
+ end
97
+
98
+ def intent_dir
99
+ dir = scope.intent_dir(id)
100
+ raise Failure, "no intent #{id.inspect} in #{scope.slug}; plastic status lists the ones that exist" unless dir
101
+
102
+ dir
103
+ end
104
+
105
+ def legacy
106
+ @legacy ||= Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json])
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic auto report` - an intent's completion report (`agent-report`, run
6
+ # through Legacy), then the review-by-risk rules a lead checks it against.
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class AutoReport < IntentCommand
11
+ USAGE_LINE = "plastic auto report ID [--role ROLE] [--json]"
12
+
13
+ SCRIPT = "agent-report"
14
+ AFTER = "plastic auto lock release ID"
15
+ BECAUSE = "closing the lock is what a delivered or abandoned report leads to"
16
+
17
+ REVIEW_RULES = <<~TEXT.chomp
18
+ Review by risk, boot 3, only when a rule fires; otherwise the green suite is
19
+ the review. Dispatch the post-execution reviewer when any of these holds:
20
+ 1. the diff touches a path on the risk list (hooks, the lock, the arming
21
+ module, the installer, a release file).
22
+ 2. a failure-mode matrix row names a test not in the diff, or a test the
23
+ green run did not execute.
24
+ 3. the completion report carries a status other than delivered, or a
25
+ non-empty deviations or blockers field.
26
+ The reviewer returns a pass or fixes; at most one review-fix round, ever.
27
+ TEXT
28
+
29
+ def call
30
+ super
31
+ @output.row("review rules", REVIEW_RULES)
32
+ end
33
+
34
+ private
35
+
36
+ def role
37
+ options[:role]
38
+ end
39
+
40
+ def switches(parser)
41
+ parser.on("--role ROLE") { |v| @options[:role] = v }
42
+ end
43
+
44
+ def script_arguments
45
+ role ? [intent_dir, "--role", role] : [intent_dir]
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "intent_command"
5
+
6
+ # `plastic auto take` - arms the intent's delivery lock for this session
7
+ # (`plastic-lock arm --intent-dir DIR --mode auto`, run through Legacy).
8
+ # The lock records who took it: --harness, --agent, --model and --thread pass
9
+ # through, and a Claude Code session names its harness when none is given.
10
+ # plastic-lock answers in JSON, so this command renders it as a screen and
11
+ # leaves the document to `--json`. A lock another session owns exits 3.
12
+ module Plastic
13
+ class CLI
14
+ module Commands
15
+ class AutoTake < IntentCommand
16
+ USAGE_LINE = "plastic auto take ID [--allow-inline] [--harness NAME] [--agent NAME] " \
17
+ "[--model MODEL] [--thread ID] [--json]"
18
+
19
+ SCRIPT = "plastic-lock"
20
+ AFTER = "plastic auto brief ID"
21
+ BECAUSE = "the preamble is the live state a taken intent is read from next"
22
+ PROVENANCE = %i[harness agent model thread].freeze
23
+
24
+ def call
25
+ return super if options[:json]
26
+
27
+ text, status = legacy.capture(SCRIPT, *script_arguments)
28
+ raise Failure, "#{SCRIPT} exited #{status}" unless status.zero?
29
+
30
+ screen(JSON.parse(text))
31
+ command, reason = after_run
32
+ @output.next_step(command, because: reason)
33
+ rescue JSON::ParserError
34
+ raise Failure, "#{SCRIPT} did not print a report"
35
+ end
36
+
37
+ private
38
+
39
+ def switches(parser)
40
+ parser.on("--allow-inline") { @options[:allow_inline] = true }
41
+ PROVENANCE.each do |name|
42
+ parser.on("--#{name} VALUE") { |value| @options[name] = value }
43
+ end
44
+ end
45
+
46
+ def script_arguments
47
+ args = ["arm", "--intent-dir", intent_dir, "--mode", "auto"]
48
+ provenance.each { |name, value| args.push("--#{name}", value) }
49
+ args << "--allow-inline" if options[:allow_inline]
50
+ args
51
+ end
52
+
53
+ def provenance
54
+ given = PROVENANCE.to_h { |name| [name, options[name]] }
55
+ given[:harness] ||= "claude" if @env["CLAUDE_CODE_SESSION_ID"].to_s.strip != ""
56
+ given.compact
57
+ end
58
+
59
+ def screen(report)
60
+ worktree = report["worktree"] || {}
61
+ @output.row("intent", File.basename(report["intent_dir"].to_s))
62
+ @output.row("lock", "#{report["status"]} by #{report["session"]}, #{report["run_mode"]} mode")
63
+ @output.row("worktree", worktree["provisioned"] ? worktree["code"].to_s : "none")
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "version"
5
+ require_relative "../../backup"
6
+
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class Backup < Command
11
+ USAGE_LINE = "plastic backup [--list] [--json]"
12
+
13
+ def call
14
+ options[:list] ? list : write
15
+ rescue Plastic::Backup::Missing => e
16
+ @output.next_step("plastic sync", because: "a backup copies the three databases, and sync builds them")
17
+ raise Failure, "no #{e.message} at #{scope.plastic_home}; run plastic sync"
18
+ rescue Sqlite::Error => e
19
+ raise Failure, e.message
20
+ end
21
+
22
+ private
23
+
24
+ def switches(parser)
25
+ parser.on("--list") { options[:list] = true }
26
+ end
27
+
28
+ def write
29
+ version = Version.new([], out: nil, err: nil, env: @env).found.last
30
+ @output.row("archive", Plastic::Backup.call(scope.plastic_home, version: version))
31
+ @output.next_step("plastic backup --list", because: "to restore, unpack the archive into an empty ~/.plastic and run plastic checkout")
32
+ end
33
+
34
+ def list
35
+ archives = Plastic::Backup.list(scope.plastic_home)
36
+ archives.each { |path| @output.row(File.basename(path), "#{File.size(path)} bytes #{File.mtime(path).strftime("%Y-%m-%d %H:%M")}") }
37
+ @output.row("result", "no backup yet") if archives.empty?
38
+ @output.next_step("plastic backup", because: "copying backups/ off this machine is your choice; Plastic sends nothing")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../../store_sync"
5
+
6
+ module Plastic
7
+ class CLI
8
+ module Commands
9
+ class Checkout < Command
10
+ USAGE_LINE = "plastic checkout [--json]"
11
+
12
+ def call
13
+ home = scope.plastic_home
14
+ raise Failure, "no databases at #{home}; run plastic sync" unless File.exist?(ReferenceArchive.path(home))
15
+
16
+ restored = StoreSync.checkout(home)
17
+ @output.row("restored", restored.empty? ? "nothing was missing" : restored)
18
+ @output.next_step("plastic sync --dry-run", because: "checkout restores missing files only; a changed file is never overwritten")
19
+ rescue Sqlite::Error => e
20
+ raise Failure, e.message
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../frontier"
5
+ require_relative "../scope"
6
+
7
+ # `plastic continue` - where one project stands and what runs next: the store it
8
+ # uses, the intents it has open, its liveliest roadmap and that roadmap's
9
+ # frontier, then the step to take and the rule behind it.
10
+ #
11
+ # With no roadmap the first active intent answers instead, so a project that
12
+ # never grew a roadmap still gets a next step.
13
+ module Plastic
14
+ class CLI
15
+ module Commands
16
+ class Continue < Command
17
+ USAGE_LINE = "plastic continue [--project SLUG] [--json]"
18
+
19
+ def call
20
+ @output.row("project", scope.slug)
21
+ @output.row("root", scope.root)
22
+ @output.row("active", active)
23
+ @output.row("roadmap", roadmap)
24
+ command, because = decision
25
+ @output.next_step(command, because: because)
26
+ rescue RoadmapSavepoint::MissingGroupingHeading => e
27
+ raise Failure, e.message
28
+ end
29
+
30
+ private
31
+
32
+ def switches(parser)
33
+ parser.on("--project SLUG") { |slug| @options[:project] = slug }
34
+ end
35
+
36
+ def active
37
+ scope.active_entries.map { |id, title| "#{id} #{title}" }
38
+ end
39
+
40
+ def roadmap
41
+ return Frontier::NONE unless frontier.roadmap
42
+
43
+ "#{frontier.roadmap} frontier #{frontier.heading}"
44
+ end
45
+
46
+ def decision
47
+ return [frontier.next_step, frontier.because] if frontier.roadmap
48
+
49
+ id = scope.active_ids.first
50
+ return [plan_for(id), "#{id} is the first active intent in #{scope.slug}"] if id
51
+
52
+ ["plastic help", "#{scope.slug} has no active work and no roadmap"]
53
+ end
54
+
55
+ def plan_for(id)
56
+ dir = scope.intent_dir(id)
57
+ dir ? "plastic intent show #{id}" : "plastic status"
58
+ end
59
+
60
+ def frontier
61
+ @frontier ||= Frontier.new(scope)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require_relative "installer_verb"
5
+
6
+ # `plastic doctor` - diagnoses Plastic installation health. The work is still
7
+ # scripts/doctor.rb, run through Legacy; each finding prints its own repair.
8
+ # The script speaks JSON, so this command renders it as a screen and keeps the
9
+ # document itself behind `--json`, the way every other command reads. A report
10
+ # that is not `pass` exits non-zero, because findings are what doctor is for.
11
+ #
12
+ # InstallerVerb keeps the raw argv rather than running the option parser, so
13
+ # `--json` is taken off the line here instead of through `options`.
14
+ module Plastic
15
+ class CLI
16
+ module Commands
17
+ class Doctor < InstallerVerb
18
+ USAGE_LINE = "plastic doctor [--core] [--agent claude|codex|hermes] [--store WHICH] [--json]"
19
+ FLAGS = %w[--core --agent --store].freeze
20
+
21
+ SCRIPT = "doctor.rb"
22
+ AFTER = "none"
23
+ BECAUSE = "the findings are the whole answer, and each one names its own repair"
24
+
25
+ def call
26
+ as_document = !@argv.delete("--json").nil?
27
+ unknown = arguments.grep(/\A-/) - FLAGS
28
+ raise Usage, "#{unknown.first} is not a flag of this command" unless unknown.empty?
29
+
30
+ report = parse(legacy.capture(SCRIPT, *arguments).first)
31
+ as_document ? document(report) : screen(report)
32
+ @output.next_step(AFTER, because: BECAUSE)
33
+ flush
34
+ raise Failure, verdict(report) unless report["status"] == "pass"
35
+ end
36
+
37
+ def flush
38
+ @output.flush(json: false) unless @document_printed
39
+ end
40
+
41
+ private
42
+
43
+ def parse(report)
44
+ JSON.parse(report)
45
+ rescue JSON::ParserError
46
+ raise Failure, "#{SCRIPT} did not print a report"
47
+ end
48
+
49
+ def document(report)
50
+ @document_printed = true
51
+ @output.document(report)
52
+ end
53
+
54
+ def findings(report)
55
+ report["checks"].to_a.reject { |check| check["status"] == "pass" }
56
+ end
57
+
58
+ def verdict(report)
59
+ found = findings(report)
60
+ "doctor found #{found.size} #{(found.size == 1) ? "finding" : "findings"}, listed above"
61
+ end
62
+
63
+ def screen(report)
64
+ checks = report["checks"].to_a
65
+ found = findings(report)
66
+
67
+ @output.row("status", "#{report["status"]} #{checks.size - found.size} of #{checks.size} checks pass")
68
+ @output.row("version", report["version"])
69
+ @output.row("agent", report["agent"])
70
+ found.each { |finding| finding_rows(finding) }
71
+ end
72
+
73
+ def finding_rows(finding)
74
+ name = [finding["category"], finding["name"]].compact.join("/")
75
+ @output.row(finding["status"], "#{name} #{finding["message"]}")
76
+ @output.row("", "fix: #{finding["fix_hint"]}") if finding["fix_hint"]
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic feedback "TITLE"` - files a report with feedback-report, the body
7
+ # read from standard input and passed to the child process unchanged.
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class Feedback < Command
12
+ USAGE_LINE = 'plastic feedback "TITLE"'
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ raise Usage, "plastic feedback needs a title" if title.nil? || title.strip.empty?
21
+
22
+ status = legacy.run("feedback-report", "--title", title)
23
+ raise Failure, "feedback-report exited #{status}" unless status.zero?
24
+
25
+ @output.next_step("none", because: "the report is saved as a draft; open the printed URL to send it")
26
+ end
27
+
28
+ private
29
+
30
+ def title
31
+ arguments.first
32
+ end
33
+
34
+ def legacy
35
+ @legacy ||= Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json])
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../table"
5
+
6
+ # `plastic help` - the command list plus the help topics, straight out of the
7
+ # table and docs/help, or one command's usage line, or one topic's chapter. A
8
+ # command name always wins over a topic of the same name. The list loads no
9
+ # command file: that is what the table's summary column is for.
10
+ module Plastic
11
+ class CLI
12
+ module Commands
13
+ class Help < Command
14
+ USAGE_LINE = "plastic help [COMMAND|TOPIC] [--json]"
15
+
16
+ def call
17
+ name = arguments.join(" ")
18
+ return list if name.empty?
19
+ return one_command(name) if TABLE.key?(name)
20
+ return one_topic(name) if topic?(name)
21
+
22
+ raise Usage, "no command or help topic named #{name.inspect}"
23
+ end
24
+
25
+ private
26
+
27
+ def list
28
+ @output.row("usage", "plastic <command> [options]")
29
+ TABLE.sort.each { |name, (_file, _const, summary)| @output.row(name, summary) }
30
+ @output.row("topics", topic_names)
31
+ @output.next_step("plastic status", because: "it needs no argument and names the rest")
32
+ end
33
+
34
+ def one_command(name)
35
+ file, const, summary = TABLE.fetch(name)
36
+ require_relative "../#{file}"
37
+ @output.row("usage", Commands.const_get(const)::USAGE_LINE)
38
+ @output.row("summary", summary)
39
+ @output.next_step("none", because: "the usage line is the whole answer")
40
+ end
41
+
42
+ def one_topic(name)
43
+ @output.row(name, File.read(topic_path(name)))
44
+ @output.next_step("none", because: "the chapter is the whole answer")
45
+ end
46
+
47
+ def topic?(name)
48
+ name == File.basename(name) && File.file?(topic_path(name))
49
+ end
50
+
51
+ def topic_names
52
+ Dir.glob(File.join(help_dir, "*.md")).map { |path| File.basename(path, ".md") }.sort
53
+ end
54
+
55
+ def topic_path(name)
56
+ File.join(help_dir, "#{name}.md")
57
+ end
58
+
59
+ def help_dir
60
+ File.join(package_root, "docs", "help")
61
+ end
62
+
63
+ def package_root
64
+ @env["PLASTIC_PACKAGE_ROOT"] || File.expand_path("../../../..", __dir__)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "English"
4
+ require_relative "../command"
5
+ require_relative "../legacy"
6
+
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class Hook < Command
11
+ USAGE_LINE = "plastic hook EVENT"
12
+ EVENTS = %w[call-budget capture close record savepoint session-start stop].freeze
13
+
14
+ DEFAULT_RUNNER = lambda do |path|
15
+ system(path)
16
+ Legacy.exit_code($CHILD_STATUS)
17
+ end
18
+
19
+ def self.call(argv, runner: DEFAULT_RUNNER, **seams)
20
+ return super(argv, **seams) unless EVENTS.include?(argv.first)
21
+
22
+ root = Legacy.new(env: seams.fetch(:env, ENV)).package_root
23
+ runner.call(File.join(root, "hooks", argv.first))
24
+ end
25
+
26
+ def call
27
+ raise Usage, "EVENT is one of #{EVENTS.join(", ")}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../../search_index"
5
+
6
+ module Plastic
7
+ class CLI
8
+ module Commands
9
+ class Index < Command
10
+ USAGE_LINE = "plastic index [--json]"
11
+
12
+ def call
13
+ count = SearchIndex.build(scope.plastic_home)
14
+ @output.row("indexed", "#{count} files")
15
+ @output.row("database", SearchIndex.path(scope.plastic_home))
16
+ @output.next_step("plastic search TERMS", because: "the index is whole and new; it is rebuilt, never updated")
17
+ rescue Sqlite::Error => e
18
+ raise Failure, e.message
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "installer_verb"
4
+
5
+ # `plastic install` - wires Plastic into this machine's agents. The work is
6
+ # still scripts/install.rb, run as a child process through Legacy; this command
7
+ # is what replaces the Node shim the npm package used to carry.
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class Install < InstallerVerb
12
+ USAGE_LINE = "plastic install [--claude] [--codex] [--hermes] [--all] [--reinstall] [--force] [--advisor NAME] [--no-advisor] [--statusline] [--ledger-action NAME]"
13
+ FLAGS = %w[--claude --codex --hermes --all --reinstall --force --advisor --no-advisor --statusline --ledger-action].freeze
14
+
15
+ SCRIPT = "install.rb"
16
+ AFTER = "plastic version"
17
+ BECAUSE = "the install is done, and the version proves the command works"
18
+ end
19
+ end
20
+ end
21
+ end