@zalom/plastic 2.0.0-alpha.8 → 2.0.1

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 (322) hide show
  1. package/PLASTIC.md +13 -136
  2. package/README.md +357 -133
  3. package/agents/plastic-enforcer.md +20 -15
  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 +6 -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/{skills/conventions/references → docs/help}/completion-and-done.md +15 -16
  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 +23 -13
  26. package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +1 -1
  27. package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
  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 +62 -1
  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 +238 -22
  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 +19 -5
  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 +48 -83
  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 +99 -0
  67. package/scripts/lib/cli/commands/auto_report.rb +50 -0
  68. package/scripts/lib/cli/commands/auto_take.rb +31 -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 +53 -0
  82. package/scripts/lib/cli/commands/intent_end.rb +62 -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 +71 -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 +42 -0
  95. package/scripts/lib/cli/commands/project_list.rb +20 -0
  96. package/scripts/lib/cli/commands/project_new.rb +72 -0
  97. package/scripts/lib/cli/commands/query.rb +31 -0
  98. package/scripts/lib/cli/commands/render.rb +33 -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 +70 -0
  104. package/scripts/lib/cli/commands/roadmap_show.rb +45 -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 +46 -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 +30 -16
  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 +229 -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 +532 -71
  152. package/scripts/lib/intent_screen.rb +9 -6
  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 +377 -173
  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 +913 -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 +1460 -74
  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 +158 -8
  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 +525 -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 +555 -0
  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 +25 -18
  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/verify_intent.rb +36 -8
  212. package/scripts/lib/version_number.rb +48 -0
  213. package/scripts/lib/work_graph.rb +59 -0
  214. package/scripts/lib/work_graph_validator.rb +201 -0
  215. package/scripts/lib/worktree.rb +27 -32
  216. package/scripts/lib/worktree_sweep.rb +6 -5
  217. package/scripts/link-suggest +2 -1
  218. package/scripts/meter-watch +57 -0
  219. package/scripts/migrate-to-global +1 -1
  220. package/scripts/new-intent +4 -13
  221. package/scripts/node-input +92 -0
  222. package/scripts/node-run +225 -0
  223. package/scripts/node-transition +291 -0
  224. package/scripts/outcome-report +74 -0
  225. package/scripts/plastic-lock +42 -33
  226. package/scripts/promote-session-item +3 -2
  227. package/scripts/read-config +53 -9
  228. package/scripts/ready-set +126 -0
  229. package/scripts/release-check +123 -0
  230. package/scripts/report-screen +192 -30
  231. package/scripts/roadmap-graph +119 -0
  232. package/scripts/roadmap-savepoint +7 -0
  233. package/scripts/runner +581 -0
  234. package/scripts/savepoint-note +11 -9
  235. package/scripts/session-commit +2 -1
  236. package/scripts/session-usage +56 -0
  237. package/scripts/skill-lint +115 -6
  238. package/scripts/spawn-preamble +2 -2
  239. package/scripts/update.rb +25 -4
  240. package/scripts/validate-work-graph +39 -0
  241. package/scripts/verify-intent +3 -2
  242. package/scripts/write-handoff +2 -1
  243. package/templates/agents.md +7 -7
  244. package/templates/config.yml +16 -9
  245. package/templates/dashboard-screen.md +22 -0
  246. package/templates/display-fixture.md +21 -0
  247. package/templates/graph.md +16 -0
  248. package/templates/index.md +1 -1
  249. package/templates/intent-screen.md +1 -1
  250. package/templates/node-decision.md +11 -0
  251. package/templates/node-research.md +13 -0
  252. package/templates/node-verify.md +13 -0
  253. package/templates/node-work.md +22 -0
  254. package/templates/outcome.md +15 -2
  255. package/templates/project.yml +1 -1
  256. package/templates/render.css +10 -0
  257. package/templates/report-plan.md +15 -0
  258. package/templates/report-roadmap-delivered.md +10 -0
  259. package/templates/report-roadmap-plan.md +9 -0
  260. package/templates/report-roadmap-state.md +9 -0
  261. package/templates/report-state.md +1 -1
  262. package/templates/roadmap.md +13 -0
  263. package/bin/plastic.js +0 -70
  264. package/scripts/lib/bridge.rb +0 -116
  265. package/skills/agent-advisor/SKILL.md +0 -92
  266. package/skills/auto/SKILL.md +0 -295
  267. package/skills/auto/evals/evals.json +0 -255
  268. package/skills/auto/references/end-tail.md +0 -66
  269. package/skills/auto/references/human-report-contract.md +0 -78
  270. package/skills/conventions/SKILL.md +0 -29
  271. package/skills/dashboard/SKILL.md +0 -169
  272. package/skills/dashboard/evals/evals.json +0 -38
  273. package/skills/dashboard/references/classification.md +0 -22
  274. package/skills/dashboard/templates/dashboard-global.md +0 -20
  275. package/skills/dashboard/templates/dashboard-project.md +0 -19
  276. package/skills/direct/SKILL.md +0 -66
  277. package/skills/direct/references/request-signals.md +0 -59
  278. package/skills/doctor/SKILL.md +0 -299
  279. package/skills/doctor/report.md +0 -102
  280. package/skills/feedback/SKILL.md +0 -98
  281. package/skills/feedback/references/transport-and-privacy.md +0 -65
  282. package/skills/feedback/report.md +0 -36
  283. package/skills/install/SKILL.md +0 -217
  284. package/skills/intent-continuing/SKILL.md +0 -154
  285. package/skills/intent-continuing/references/board-fill.md +0 -43
  286. package/skills/intent-continuing/references/boarding-matrix.md +0 -34
  287. package/skills/intent-continuing/references/context-management.md +0 -28
  288. package/skills/intent-continuing/references/liveness-ranking.md +0 -57
  289. package/skills/intent-creating/SKILL.md +0 -164
  290. package/skills/intent-creating/evals/evals.json +0 -72
  291. package/skills/intent-creating/references/lifecycle.md +0 -81
  292. package/skills/intent-creating/references/wikilinks.md +0 -8
  293. package/skills/intent-ending/SKILL.md +0 -173
  294. package/skills/intent-ending/evals/evals.json +0 -74
  295. package/skills/intent-executing/SKILL.md +0 -170
  296. package/skills/intent-executing/evals/evals.json +0 -66
  297. package/skills/intent-executing/implementer-prompt.md +0 -42
  298. package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
  299. package/skills/intent-speccing/SKILL.md +0 -130
  300. package/skills/intent-speccing/evals/evals.json +0 -126
  301. package/skills/intent-speccing/references/design-principles.md +0 -44
  302. package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
  303. package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
  304. package/skills/project-creating/SKILL.md +0 -162
  305. package/skills/project-creating/references/hubs-projects.md +0 -55
  306. package/skills/project-creating/references/project-scaffolding.md +0 -97
  307. package/skills/releasing/SKILL.md +0 -337
  308. package/skills/releasing/references/deprecations.md +0 -60
  309. package/skills/releasing/references/promotion-and-tagging.md +0 -66
  310. package/skills/releasing/references/release-lines.md +0 -105
  311. package/skills/roadmap/SKILL.md +0 -64
  312. package/skills/roadmap/references/file-format.md +0 -124
  313. package/skills/roadmap/references/operations.md +0 -112
  314. package/skills/rollback/SKILL.md +0 -91
  315. package/skills/tutorial/SKILL.md +0 -65
  316. package/skills/tutorial/evals/evals.json +0 -186
  317. package/skills/uninstall/SKILL.md +0 -75
  318. package/skills/update/SKILL.md +0 -126
  319. /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
  320. /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
  321. /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
  322. /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
@@ -0,0 +1,99 @@
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` speaks JSON, so this command renders it as a screen and leaves the
13
+ # document to `--json`. `fix` and `release` already report in prose.
14
+ module Plastic
15
+ class CLI
16
+ module Commands
17
+ class AutoLock < Command
18
+ USAGE_LINE = "plastic auto lock status|fix|release ID [--json]"
19
+
20
+ VERBS = %w[status fix release].freeze
21
+ AFTER = {"status" => "none", "fix" => "plastic auto brief ID",
22
+ "release" => "plastic session summary"}.freeze
23
+ BECAUSE = {"status" => "inspection is complete; a held lock does not authorize repair",
24
+ "fix" => "a repaired lock is ready for the preamble again",
25
+ "release" => "the summary is what a freed lock leads to"}.freeze
26
+
27
+ def initialize(argv, runner: nil, **streams)
28
+ super(argv, **streams)
29
+ @runner = runner
30
+ end
31
+
32
+ def call
33
+ raise Usage, "VERB must be one of #{VERBS.join(", ")}" unless VERBS.include?(verb)
34
+
35
+ (verb == "status") ? screen(report) : run_verb
36
+ @output.next_step(AFTER.fetch(verb).sub("ID", id.to_s), because: BECAUSE.fetch(verb))
37
+ end
38
+
39
+ private
40
+
41
+ def run_verb
42
+ status = legacy.run("plastic-lock", verb, "--intent-dir", intent_dir)
43
+ raise Failure, "plastic-lock exited #{status}" unless status.zero?
44
+ end
45
+
46
+ def report
47
+ text, status = legacy.capture("plastic-lock", "status", "--intent-dir", intent_dir)
48
+ raise Failure, "plastic-lock exited #{status}" unless status.zero?
49
+
50
+ JSON.parse(text)
51
+ rescue JSON::ParserError
52
+ raise Failure, "plastic-lock did not print a report"
53
+ end
54
+
55
+ def screen(report)
56
+ @output.row("intent", File.basename(report["intent_dir"].to_s))
57
+ @output.row("lock", lock_line(report))
58
+ @output.row("worktree", worktree_line(report["worktree"]))
59
+ @output.row("delivering", report["delivering"] ? "yes" : "no")
60
+ @output.row("claims", Array(report["claims"]).map(&:to_s))
61
+ end
62
+
63
+ def lock_line(report)
64
+ lock = report["lock"]
65
+ return "corrupt, plastic auto lock fix #{id} rewrites it" if report["lock_corrupt"]
66
+ return "none" if lock.nil?
67
+
68
+ holder = lock.is_a?(Hash) ? (lock["owner_session"] || lock["session"]) : lock
69
+ "held by #{holder}#{", stale" unless report["lock_fresh"]}"
70
+ end
71
+
72
+ def worktree_line(worktree)
73
+ return "none" unless worktree.is_a?(Hash) && worktree["provisioned"]
74
+
75
+ [worktree["code"], worktree["code_branch"]].compact.join(" on ")
76
+ end
77
+
78
+ def verb
79
+ arguments[0]
80
+ end
81
+
82
+ def id
83
+ arguments[1]
84
+ end
85
+
86
+ def intent_dir
87
+ dir = scope.intent_dir(id)
88
+ raise Failure, "no intent #{id.inspect} in #{scope.slug}; plastic status lists the ones that exist" unless dir
89
+
90
+ dir
91
+ end
92
+
93
+ def legacy
94
+ @legacy ||= Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json])
95
+ end
96
+ end
97
+ end
98
+ end
99
+ 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,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic auto take` - arms the intent's delivery lock for this session
6
+ # (`plastic-lock arm --intent-dir DIR --mode auto`, run through Legacy).
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class AutoTake < IntentCommand
11
+ USAGE_LINE = "plastic auto take ID [--allow-inline] [--json]"
12
+
13
+ SCRIPT = "plastic-lock"
14
+ AFTER = "plastic auto brief ID"
15
+ BECAUSE = "the preamble is the live state a taken intent is read from next"
16
+
17
+ private
18
+
19
+ def switches(parser)
20
+ parser.on("--allow-inline") { @options[:allow_inline] = true }
21
+ end
22
+
23
+ def script_arguments
24
+ args = ["arm", "--intent-dir", intent_dir, "--mode", "auto"]
25
+ args << "--allow-inline" if options[:allow_inline]
26
+ args
27
+ end
28
+ end
29
+ end
30
+ end
31
+ 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] [--store WHICH] [--json]"
19
+ FLAGS = %w[--core --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
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ module Plastic
7
+ class CLI
8
+ module Commands
9
+ class InstallerVerb < Command
10
+ def initialize(argv, runner: nil, **streams)
11
+ super(argv, **streams)
12
+ @runner = runner
13
+ end
14
+
15
+ def call
16
+ unknown = arguments.grep(/\A-/) - self.class::FLAGS
17
+ raise Usage, "#{unknown.first} is not a flag of this command" unless unknown.empty?
18
+
19
+ status = legacy.run(self.class::SCRIPT, *arguments)
20
+ raise Failure, "#{self.class::SCRIPT} exited #{status}" unless status.zero?
21
+
22
+ @output.next_step(self.class::AFTER, because: self.class::BECAUSE)
23
+ end
24
+
25
+ private
26
+
27
+ def options
28
+ @options ||= {}
29
+ end
30
+
31
+ def legacy
32
+ @legacy ||= Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json])
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subcommand_list"
4
+
5
+ # `plastic intent` - the list of `plastic intent ...` subcommands, straight out
6
+ # of the table; a word that names none of them is a usage error naming the
7
+ # same list, so a typo never falls through to a script.
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class Intent < SubcommandList
12
+ USAGE_LINE = "plastic intent SUBCOMMAND [options]"
13
+ PREFIX = "intent"
14
+ NEXT = "plastic intent show ID"
15
+ BECAUSE = "an id is the one thing every other subcommand needs"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent answer` - closes a `needs_decision` node with the owner's
6
+ # ruling (`runner answer`, run through Legacy).
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class IntentAnswer < IntentCommand
11
+ USAGE_LINE = 'plastic intent answer ID --node NODE --decision "TEXT" [--json]'
12
+
13
+ SCRIPT = "runner"
14
+ AFTER = "plastic intent step ID"
15
+ BECAUSE = "step resumes the graph past the node this decision just closed"
16
+
17
+ def call
18
+ raise Usage, "--node is required" if options[:node].to_s.empty?
19
+ raise Usage, "--decision is required" if options[:decision].to_s.empty?
20
+
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def switches(parser)
27
+ parser.on("--node ID") { |v| @options[:node] = v }
28
+ parser.on("--decision TEXT") { |v| @options[:decision] = v }
29
+ end
30
+
31
+ def script_arguments
32
+ ["answer", intent_dir, "--node", options[:node], "--answer", options[:decision]]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end