@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,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
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # Plastic::CLI::Commands::IntentCommand (intent 372, family 2) - the shared
7
+ # base every `plastic intent ...` subcommand runs on: it turns the positional
8
+ # id into an intent directory through Scope, runs the one script the
9
+ # subclass names, and prints the `next:`/`because:` trailer the plan calls
10
+ # for. A subcommand that needs more than that (extra usage checks, printed
11
+ # guidance, a different script-exit contract) overrides `call`.
12
+ module Plastic
13
+ class CLI
14
+ module Commands
15
+ class IntentCommand < Command
16
+ def initialize(argv, runner: nil, **streams)
17
+ super(argv, **streams)
18
+ @runner = runner
19
+ end
20
+
21
+ def call
22
+ status = legacy.run(self.class::SCRIPT, *script_arguments)
23
+ raise Failure, "#{self.class::SCRIPT} exited #{status}" unless status.zero?
24
+
25
+ command, reason = after_run
26
+ @output.next_step(command, because: reason)
27
+ end
28
+
29
+ private
30
+
31
+ def after_run
32
+ [self.class::AFTER.sub("ID", id.to_s), self.class::BECAUSE]
33
+ end
34
+
35
+ def id
36
+ arguments.first
37
+ end
38
+
39
+ def intent_dir
40
+ return @intent_dir if defined?(@intent_dir)
41
+
42
+ @intent_dir = scope.intent_dir(id)
43
+ return @intent_dir if @intent_dir
44
+
45
+ raise Failure, "no intent #{id.inspect} in #{scope.slug}; plastic status lists the ones that exist"
46
+ end
47
+
48
+ def legacy
49
+ @legacy ||= Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json])
50
+ end
51
+
52
+ def script_arguments
53
+ raise NoMethodError, "#{self.class} must define script_arguments"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent end` - closes an intent as delivered or abandoned
6
+ # (`end-intent`, run through Legacy). end-intent's own exit code 4 is a
7
+ # pre-flight refusal (a live foreign session holds the delivery lock), not
8
+ # the exit 3 Legacy's generic contract turns into a Refusal on its own, so
9
+ # this command maps it by hand rather than through Legacy#run.
10
+ module Plastic
11
+ class CLI
12
+ module Commands
13
+ class IntentEnd < IntentCommand
14
+ USAGE_LINE = 'plastic intent end ID --delivered|--abandoned --summary "TEXT" [--note "TEXT"] [--dry-run] [--json]'
15
+
16
+ REFUSAL_STATUS = 4
17
+
18
+ # end-intent's own refusals the agent can act on: the reason is theirs to
19
+ # fix, so they stay failures (exit 1), but each is named rather than
20
+ # surfaced as a bare exit code.
21
+ REFUSED = {
22
+ 7 => "end-intent refused a hollow delivered close: write outcome.md's ## Delivered rows to match the action headings",
23
+ 8 => "end-intent refused to deliver an untouched scaffold: do the work first, or close it with --abandoned",
24
+ 9 => "end-intent refused a delivered close whose code is not merged: run the git merge it names in the repo, then run the close again"
25
+ }.freeze
26
+
27
+ SUMMARY_GUIDANCE = [
28
+ "a good summary is written for the reader deciding whether to merge, release, or accept",
29
+ "delivered: what shipped, impact and risk first, in plain language, never the checklist",
30
+ "abandoned: why, and the trail if part of the work was dropped mid-flight"
31
+ ].freeze
32
+
33
+ def call
34
+ raise Usage, "choose only one of --delivered or --abandoned" if options[:delivered] && options[:abandoned]
35
+ raise Usage, "one of --delivered or --abandoned is required" unless disposition
36
+ raise Usage, SUMMARY_GUIDANCE.join("\n") if options[:summary].to_s.empty?
37
+
38
+ status = legacy.run("end-intent", *end_intent_arguments)
39
+ raise Refusal, "end-intent needs the owner: the delivery lock is held" if status == REFUSAL_STATUS
40
+ raise Failure, REFUSED.fetch(status) { "end-intent exited #{status}" } unless status.zero?
41
+
42
+ if options[:dry_run]
43
+ @output.next_step("none", because: "the dry run wrote nothing and found nothing that would refuse the close")
44
+ else
45
+ @output.next_step("plastic status", because: "the intent moved out of Active")
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def disposition
52
+ return "delivered" if options[:delivered]
53
+ "abandoned" if options[:abandoned]
54
+ end
55
+
56
+ def switches(parser)
57
+ parser.on("--delivered") { @options[:delivered] = true }
58
+ parser.on("--abandoned") { @options[:abandoned] = true }
59
+ parser.on("--summary TEXT") { |v| @options[:summary] = v }
60
+ parser.on("--note TEXT") { |v| @options[:note] = v }
61
+ parser.on("--dry-run") { @options[:dry_run] = true }
62
+ end
63
+
64
+ def end_intent_arguments
65
+ intent_dir
66
+ args = ["--store", scope.store, "--id", id, "--disposition", disposition,
67
+ "--outcome-summary", options[:summary]]
68
+ args += ["--index-note", options[:note]] if options[:note]
69
+ args << "--dry-run" if options[:dry_run]
70
+ args
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+ require_relative "../../index_entry"
6
+
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class IntentNew < Command
11
+ USAGE_LINE = 'plastic intent new "LINE" [--slug SLUG] [--parent ID] [--sources IDS] [--tags TAGS] [--json]'
12
+ PASSED_THROUGH = %i[parent sources tags].freeze
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ raise Usage, "the one-line intent statement is required" if line.to_s.empty?
21
+
22
+ status = Legacy.new(env: @env, runner: @runner, output: @output, json: options[:json]).run("new-intent", *new_intent_arguments)
23
+ raise Failure, "new-intent exited #{status}" unless status.zero?
24
+
25
+ IndexEntry.add_active(scope.index_path, dir_name: dir_name, title: line)
26
+ @output.next_step("plastic intent spec #{id}", because: "a new intent has no specification yet")
27
+ end
28
+
29
+ private
30
+
31
+ def line
32
+ arguments.first
33
+ end
34
+
35
+ def slug
36
+ options[:slug] || line.downcase.scan(/[a-z0-9]+/).first(5).join("-")
37
+ end
38
+
39
+ def dir_name
40
+ newest = Dir.glob(File.join(scope.store, "*--#{slug}")).max_by { |path| File.mtime(path) }
41
+ raise Failure, "new-intent made no directory for #{slug}" unless newest
42
+
43
+ File.basename(newest)
44
+ end
45
+
46
+ def id
47
+ dir_name.split("--").first
48
+ end
49
+
50
+ def switches(parser)
51
+ parser.on("--slug SLUG") { |v| @options[:slug] = v }
52
+ PASSED_THROUGH.each { |key| parser.on("--#{key} VALUE") { |v| @options[key] = v } }
53
+ end
54
+
55
+ def new_intent_arguments
56
+ given = PASSED_THROUGH.select { |key| options[key] }
57
+ ["--store", scope.store, "--intent", line, "--slug", slug] + given.flat_map { |key| ["--#{key}", options[key]] }
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent note` - appends a savepoint line to an intent
6
+ # (`savepoint-note`, run through Legacy). `--kind` defaults to Report, the
7
+ # most general of the three savepoint kinds, for a note that names no kind
8
+ # of its own.
9
+ module Plastic
10
+ class CLI
11
+ module Commands
12
+ class IntentNote < IntentCommand
13
+ USAGE_LINE = 'plastic intent note ID "TEXT" [--kind Review|Commit|Report] [--json]'
14
+
15
+ SCRIPT = "savepoint-note"
16
+ AFTER = "plastic intent show ID"
17
+ BECAUSE = "the state screen carries the savepoint trail this just wrote"
18
+
19
+ DEFAULT_KIND = "Report"
20
+
21
+ def call
22
+ raise Usage, "the note text is required" if text.to_s.empty?
23
+
24
+ super
25
+ end
26
+
27
+ private
28
+
29
+ def text
30
+ arguments[1]
31
+ end
32
+
33
+ def switches(parser)
34
+ parser.on("--kind KIND") { |v| @options[:kind] = v }
35
+ end
36
+
37
+ def script_arguments
38
+ [intent_dir, "--kind", options[:kind] || DEFAULT_KIND, "--text", text]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent rule` - records one ruling in an intent's `## Insights`
6
+ # (`insight-append`, run through Legacy, stamped `--stage Why --author human`
7
+ # since a rule is always the owner's).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class IntentRule < IntentCommand
12
+ USAGE_LINE = 'plastic intent rule ID "TEXT" [--json]'
13
+
14
+ SCRIPT = "insight-append"
15
+ AFTER = "plastic intent show ID"
16
+ BECAUSE = "the state screen reads Insights straight off the ledger this just wrote"
17
+
18
+ def call
19
+ raise Usage, "the ruling text is required" if text.to_s.empty?
20
+
21
+ super
22
+ end
23
+
24
+ private
25
+
26
+ def text
27
+ arguments[1]
28
+ end
29
+
30
+ def script_arguments
31
+ [intent_dir, text, "--stage", "Why", "--author", "human"]
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+ require_relative "../intent_progress"
5
+
6
+ # `plastic intent show` - an intent's state screen (`report-screen state`, run
7
+ # through Legacy).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class IntentShow < IntentCommand
12
+ USAGE_LINE = "plastic intent show ID [--json]"
13
+
14
+ SCRIPT = "report-screen"
15
+ AFTER = "plastic intent step ID"
16
+ BECAUSE = "the state screen is where the next step comes from"
17
+
18
+ def call
19
+ super
20
+ command, reason = IntentProgress.new(scope, id).decision
21
+ @output.next_step(command, because: reason)
22
+ end
23
+
24
+ private
25
+
26
+ def script_arguments
27
+ ["state", intent_dir]
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent spec` - an intent's state screen (`report-screen state`,
6
+ # same as `plastic intent show`), then the five rules the speccing
7
+ # conversation itself runs on, so the reply carries both without a second
8
+ # call.
9
+ module Plastic
10
+ class CLI
11
+ module Commands
12
+ class IntentSpec < IntentCommand
13
+ USAGE_LINE = "plastic intent spec ID [--json]"
14
+
15
+ SCRIPT = "report-screen"
16
+ AFTER = 'plastic intent rule ID "TEXT"'
17
+ BECAUSE = "a ruling is recorded the moment it lands, never batched"
18
+
19
+ RULES = [
20
+ "1. context first: read the project state and the intent's Context and Insights",
21
+ "2. assess scope: several independent subsystems become separate thinking conversations",
22
+ "3. one question per message, in prose; look at the code before asking how something works",
23
+ "4. propose two or three approaches, trade-offs first, leading with the recommendation",
24
+ "5. present the design in sections scaled to complexity; get a ruling after each section",
25
+ "6. record every ruling the instant it lands, never batched:",
26
+ ' plastic intent rule ID "<ruling text>"',
27
+ "7. a later ruling that conflicts with an earlier one gets a new insight naming it superseded",
28
+ "8. both rulings stay on record; the later one wins"
29
+ ].freeze
30
+
31
+ def call
32
+ super
33
+ @output.row("rules", RULES)
34
+ end
35
+
36
+ private
37
+
38
+ def script_arguments
39
+ ["state", intent_dir]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+ require_relative "../intent_progress"
5
+ require_relative "../../runner_core"
6
+
7
+ # `plastic intent step` - runs the graph's next ready step (`runner step`, run
8
+ # through Legacy). Direct intents report their next checklist item. Graph
9
+ # execution requires ownership, resolved by the same rules as the runner.
10
+ module Plastic
11
+ class CLI
12
+ module Commands
13
+ class IntentStep < IntentCommand
14
+ USAGE_LINE = "plastic intent step ID [--return NODE=PATH] [--harness NAME] [--allow-core-drift] [--json]"
15
+
16
+ SCRIPT = "runner"
17
+ AFTER = "plastic intent step ID"
18
+ BECAUSE = "call step again after each dispatched node returns"
19
+ COMPLETE = "every node in the graph has finished"
20
+
21
+ def call
22
+ intent_dir
23
+ command, reason = IntentProgress.new(scope, id).decision
24
+ return @output.next_step(command, because: reason) if command == "none"
25
+
26
+ if graph?
27
+ return unless graph_owner?
28
+
29
+ super
30
+ elsif command == "plastic intent step #{id}"
31
+ item = IntentScreen.checklist_items(intent_dir).find { |entry| !entry[:done] }
32
+ @output.row("work", item[:text])
33
+ @output.row("checklist", File.join(intent_dir, "checklist.md"))
34
+ @output.next_step("none", because: "perform this checklist item, record its verification, then run plastic intent show #{id} --project #{scope.slug}")
35
+ else
36
+ @output.next_step(command, because: reason)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def switches(parser)
43
+ parser.on("--return NODE=PATH") { |pair| (@options[:returns] ||= []) << pair }
44
+ parser.on("--harness NAME") { |name| @options[:harness] = name }
45
+ parser.on("--allow-core-drift") { @options[:allow_core_drift] = true }
46
+ end
47
+
48
+ def graph_owner?
49
+ session = RunnerCore.resolve_owning_session(intent_dir, explicit: nil,
50
+ env_session: @env["CLAUDE_CODE_SESSION_ID"], store: scope.store, intent_id: id)
51
+ return true if session
52
+ raise Refusal, "intent #{id} is held by another session; inspect with plastic auto lock status #{id} --project #{scope.slug}" if Lock.read(intent_dir)
53
+ raise Refusal, "return not accepted: take intent #{id} with plastic auto take #{id} --project #{scope.slug} before submitting it" unless Array(options[:returns]).empty?
54
+
55
+ @output.next_step("plastic auto take #{id}", because: "graph execution requires this session to take the delivery lock")
56
+ false
57
+ end
58
+
59
+ def after_run
60
+ return super unless RunnerCore.complete?(RunnerCore.context(intent_dir: intent_dir, home: File.dirname(scope.plastic_home)))
61
+
62
+ ["plastic intent verify #{id}", COMPLETE]
63
+ end
64
+
65
+ def graph?
66
+ File.exist?(File.join(intent_dir, "graph.md"))
67
+ end
68
+
69
+ def script_arguments
70
+ args = ["step", intent_dir]
71
+ Array(options[:returns]).each { |pair| args.concat(["--return", pair]) }
72
+ args.concat(["--harness", options[:harness]]) if options[:harness]
73
+ args << "--allow-core-drift" if options[:allow_core_drift]
74
+ args
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "intent_command"
4
+
5
+ # `plastic intent verify` - the merge-gate checks one intent needs before a
6
+ # close is trustworthy (`verify-intent`, run through Legacy).
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class IntentVerify < IntentCommand
11
+ USAGE_LINE = "plastic intent verify ID [--json]"
12
+
13
+ SCRIPT = "verify-intent"
14
+ AFTER = 'plastic intent end ID --delivered --summary "TEXT"'
15
+ BECAUSE = "a clean verify is what makes the close trustworthy"
16
+
17
+ private
18
+
19
+ def script_arguments
20
+ intent_dir
21
+ ["--store", scope.store, "--id", id]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subcommand_list"
4
+
5
+ module Plastic
6
+ class CLI
7
+ module Commands
8
+ class Migrate < SubcommandList
9
+ USAGE_LINE = "plastic migrate SUBCOMMAND [options]"
10
+ PREFIX = "migrate"
11
+ NEXT = "plastic migrate stores --dry-run"
12
+ BECAUSE = "a dry run prints every move and rewrite and changes nothing"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../../stores_move"
5
+
6
+ module Plastic
7
+ class CLI
8
+ module Commands
9
+ class MigrateStores < Command
10
+ USAGE_LINE = "plastic migrate stores [--dry-run] [--json]"
11
+
12
+ def call
13
+ move = StoresMove.new(scope.plastic_home, qmd_index: File.join(@home, ".config", "qmd", "index.yml"), dry_run: options[:dry_run])
14
+ move.call.each { |verb, text| @output.row(verb, text) }
15
+ return @output.next_step("plastic migrate stores", because: "the dry run changed nothing") if options[:dry_run]
16
+
17
+ @output.row("remove", "rm -rf #{move.copy}")
18
+ @output.next_step("plastic doctor", because: "it checks the moved stores, and the copy stays until you remove it")
19
+ rescue StoresMove::Refused => e
20
+ raise Refusal, e.message
21
+ end
22
+
23
+ private
24
+
25
+ def switches(parser)
26
+ parser.on("--dry-run") { options[:dry_run] = true }
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../frontier"
5
+ require_relative "../scope"
6
+
7
+ # `plastic next` - one line saying what to pick up, then the step to take and
8
+ # the rule that chose it. `--why` adds the roadmap, its frontier batch, and
9
+ # what is dispatchable, in flight and blocked.
10
+ module Plastic
11
+ class CLI
12
+ module Commands
13
+ class Next < Command
14
+ USAGE_LINE = "plastic next [--why] [--project SLUG] [--json]"
15
+
16
+ def call
17
+ active_id = scope.active_ids.first unless frontier.roadmap
18
+ @output.row("next work", active_id ? "#{active_id} in #{scope.slug}" : frontier.summary)
19
+ detail if options[:why]
20
+ if active_id
21
+ @output.next_step("plastic intent show #{active_id}", because: "#{active_id} is the first active intent in #{scope.slug}")
22
+ else
23
+ @output.next_step(frontier.next_step, because: frontier.because)
24
+ end
25
+ rescue RoadmapSavepoint::MissingGroupingHeading => e
26
+ raise Failure, e.message
27
+ end
28
+
29
+ private
30
+
31
+ def switches(parser)
32
+ parser.on("--why") { @options[:why] = true }
33
+ parser.on("--project SLUG") { |slug| @options[:project] = slug }
34
+ end
35
+
36
+ def detail
37
+ @output.row("roadmap", frontier.roadmap || Frontier::NONE)
38
+ @output.row("frontier", frontier.heading || Frontier::NONE)
39
+ @output.row("dispatchable", listed(frontier.dispatchable_ids))
40
+ @output.row("in flight", listed(frontier.in_flight_ids))
41
+ @output.row("blocked", listed(frontier.blocked_ids))
42
+ end
43
+
44
+ def listed(ids)
45
+ ids.empty? ? Frontier::NONE : ids.join(", ")
46
+ end
47
+
48
+ def frontier
49
+ @frontier ||= Frontier.new(scope)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subcommand_list"
4
+
5
+ # `plastic project` - the list of `plastic project ...` subcommands, straight
6
+ # out of the table; a word that names none of them is a usage error naming
7
+ # the same list, so a typo never falls through to a script.
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class Project < SubcommandList
12
+ USAGE_LINE = "plastic project SUBCOMMAND [options]"
13
+ PREFIX = "project"
14
+ NEXT = "plastic project list"
15
+ BECAUSE = "the stores it lists are what every other subcommand names a slug from"
16
+ end
17
+ end
18
+ end
19
+ end