@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,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "shellwords"
5
+
6
+ # Plastic::CLI::Output (intent 363) - one place where a command's answer is
7
+ # printed. A result is rows of label and value; every command ends with a
8
+ # `next:` line and the `because:` line that gives the rule behind it, which is
9
+ # where the step order the skills used to carry now lives. `--json` prints the
10
+ # same three things with stable keys.
11
+ #
12
+ # Results go to the output stream and diagnostics to the error stream, so a
13
+ # caller can pipe one without the other. Nothing here colours its output.
14
+ module Plastic
15
+ class CLI
16
+ class Output
17
+ LABEL_GAP = 2
18
+
19
+ attr_writer :project
20
+
21
+ def initialize(out:, err:, json: false)
22
+ @json = json
23
+ @raw = []
24
+ @out = out
25
+ @err = err
26
+ @rows = []
27
+ @next_step = nil
28
+ @because = nil
29
+ @flushed = false
30
+ end
31
+
32
+ def row(label, value)
33
+ @rows << [label, value]
34
+ self
35
+ end
36
+
37
+ def next_step(command, because:)
38
+ @next_step = command
39
+ @because = because
40
+ self
41
+ end
42
+
43
+ def flush(json: false)
44
+ return self if @flushed
45
+
46
+ @flushed = true
47
+ json ? print_json : print_text
48
+ self
49
+ end
50
+
51
+ def document(value)
52
+ @out.puts JSON.pretty_generate(value)
53
+ @flushed = true
54
+ self
55
+ end
56
+
57
+ def raw(text)
58
+ @json ? @raw << text : @out.puts(text)
59
+ self
60
+ end
61
+
62
+ def usage(message, banner)
63
+ @err.puts "plastic: #{message}"
64
+ @err.puts banner
65
+ error_document(message, "usage")
66
+ self
67
+ end
68
+
69
+ def refused(message)
70
+ @err.puts "plastic: refused, #{message}"
71
+ @err.puts "This step belongs to the owner. Stop and ask; do not retry with a flag."
72
+ error_document(message, "refused")
73
+ self
74
+ end
75
+
76
+ def failed(message)
77
+ @err.puts "plastic: #{message}"
78
+ error_document(message, "failed")
79
+ self
80
+ end
81
+
82
+ private
83
+
84
+ def error_document(message, kind)
85
+ return unless @json
86
+
87
+ row("error", {"kind" => kind, "message" => message})
88
+ next_step("none", because: message)
89
+ flush(json: true)
90
+ end
91
+
92
+ def result
93
+ data = @rows.to_h
94
+ data["output"] = @raw unless @raw.empty?
95
+ data
96
+ end
97
+
98
+ def scoped_next
99
+ return @next_step unless @project && @next_step&.match?(/\Aplastic (?:intent|auto|roadmap|continue|next|search)\b/)
100
+ return @next_step if @next_step.include?("--project")
101
+
102
+ "#{@next_step} --project #{Shellwords.escape(@project)}"
103
+ end
104
+
105
+ def print_json
106
+ @out.puts JSON.pretty_generate("result" => result, "next" => scoped_next,
107
+ "because" => @because)
108
+ end
109
+
110
+ def print_text
111
+ printed = print_rows
112
+ return unless @next_step
113
+
114
+ @out.puts if printed
115
+ @out.puts "next: #{scoped_next}"
116
+ @out.puts "because: #{@because}"
117
+ end
118
+
119
+ def print_rows
120
+ width = label_width
121
+ printed = false
122
+ @rows.each do |label, value|
123
+ Array(value).each_with_index do |item, index|
124
+ @out.puts "#{(index.zero? ? label : "").ljust(width)}#{item}"
125
+ printed = true
126
+ end
127
+ end
128
+ printed
129
+ end
130
+
131
+ def label_width
132
+ widths = @rows.reject { |_label, value| Array(value).empty? }.map { |label, _value| label.length }
133
+ widths.empty? ? 0 : widths.max + LABEL_GAP
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../store_layout"
4
+ require_relative "../index_entry"
5
+ require_relative "../store_discovery"
6
+
7
+ # Plastic::CLI::Scope (intent 363) - which store a command works on. Six skills
8
+ # used to restate this rule in prose; it is stated here once.
9
+ #
10
+ # A named project wins. With no name, the project whose repository holds the
11
+ # working directory wins, longest path first, so a repository nested inside
12
+ # another resolves to the inner one. With neither, the global store answers.
13
+ #
14
+ # Every path comes from the injected environment and home directory, so a test
15
+ # points at a temporary store and never at the real ~/.plastic.
16
+ module Plastic
17
+ class CLI
18
+ class Scope
19
+ UnknownProject = Class.new(StandardError)
20
+
21
+ GLOBAL = "global"
22
+
23
+ def initialize(env:, home:, slug: nil, directory: Dir.pwd)
24
+ @env = env
25
+ @home = home
26
+ @requested = slug
27
+ @directory = canonical_path(File.expand_path(directory))
28
+ end
29
+
30
+ def plastic_home
31
+ @plastic_home ||= @env["PLASTIC_HOME"] || File.join(@home, ".plastic")
32
+ end
33
+
34
+ def stores
35
+ @stores ||= StoreDiscovery.discover(plastic_home)[:stores]
36
+ end
37
+
38
+ def slug
39
+ @slug ||= resolve_slug
40
+ end
41
+
42
+ def root
43
+ @root ||= found&.fetch(:root) || plastic_home
44
+ end
45
+
46
+ def store
47
+ File.join(root, "store")
48
+ end
49
+
50
+ def index_path
51
+ File.join(root, "INDEX.md")
52
+ end
53
+
54
+ def roadmaps_dir
55
+ File.join(root, "roadmaps")
56
+ end
57
+
58
+ def active_entries
59
+ return [] unless File.exist?(index_path)
60
+
61
+ section(File.read(index_path)).filter_map do |line|
62
+ match = IndexEntry.match(line)
63
+ match && [match[1], match[2]]
64
+ end
65
+ end
66
+
67
+ def active_ids
68
+ active_entries.map(&:first)
69
+ end
70
+
71
+ def intent_dir(id)
72
+ Dir.glob(File.join(store, "#{id}--*")).min
73
+ end
74
+
75
+ def directory_project
76
+ @directory_project = project_for_directory unless defined?(@directory_project)
77
+ @directory_project
78
+ end
79
+
80
+ def known_slugs
81
+ stores.map { |store| store[:slug] }.sort
82
+ end
83
+
84
+ private
85
+
86
+ def found
87
+ stores.find { |store| store[:slug] == slug }
88
+ end
89
+
90
+ def resolve_slug
91
+ return requested_slug if @requested
92
+
93
+ directory_project || GLOBAL
94
+ end
95
+
96
+ def requested_slug
97
+ return @requested if known_slugs.include?(@requested)
98
+
99
+ raise UnknownProject,
100
+ "no project named #{@requested.inspect}; this machine has #{known_slugs.join(", ")}"
101
+ end
102
+
103
+ def project_for_directory
104
+ (repositories + stores.map { |store| [store[:slug], store[:root]] })
105
+ .select { |_slug, path| inside?(path) }
106
+ .max_by { |_slug, path| path.length }
107
+ &.first
108
+ end
109
+
110
+ def inside?(path)
111
+ path = canonical_path(File.expand_path(path))
112
+ @directory == path || @directory.start_with?("#{path}#{File::SEPARATOR}")
113
+ end
114
+
115
+ def canonical_path(path)
116
+ return File.realpath(path) if File.exist?(path)
117
+
118
+ File.join(canonical_path(File.dirname(path)), File.basename(path))
119
+ end
120
+
121
+ def repositories
122
+ StoreProvisioning.load_projects(plastic_home).filter_map do |slug, info|
123
+ path = info.is_a?(Hash) ? info["path"].to_s : ""
124
+ [slug, path] unless path.empty?
125
+ end
126
+ end
127
+
128
+ def section(text)
129
+ body = text.match(/^##\s+Active\s*$(.*?)(?=^##\s|\z)/m)
130
+ body ? body[1].lines.map(&:strip) : []
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Plastic::CLI::TABLE (intent 363) - the whole command line in one frozen hash:
4
+ # a command name mapped to the file that holds it, the class inside
5
+ # Plastic::CLI::Commands, and the one line `plastic help` prints beside it.
6
+ #
7
+ # Adding a command is one row here plus one file. `plastic help` reads this
8
+ # hash and loads nothing, so listing the commands costs no command file.
9
+ # test/cli/table_test.rb walks every row, so a half-wired command cannot ship.
10
+ module Plastic
11
+ class CLI
12
+ TABLE = {
13
+ "auto brief" => ["commands/auto_brief", "AutoBrief", "Print an intent's spawn preamble, and the advisor shapes for --role advisor"],
14
+ "auto lock" => ["commands/auto_lock", "AutoLock", "Inspect, repair, or release an intent's delivery lock"],
15
+ "auto report" => ["commands/auto_report", "AutoReport", "Print an intent's completion report, then the review-by-risk rules"],
16
+ "auto take" => ["commands/auto_take", "AutoTake", "Arm an intent's delivery lock for this session"],
17
+ "auto" => ["commands/auto", "Auto", "List the auto subcommands"],
18
+ "backup" => ["commands/backup", "Backup", "Write one archive of the three databases and the config"],
19
+ "checkout" => ["commands/checkout", "Checkout", "Restore missing store files from the databases"],
20
+ "continue" => ["commands/continue", "Continue", "Show where one project stands and what runs next"],
21
+ "doctor" => ["commands/doctor", "Doctor", "Diagnose Plastic installation health"],
22
+ "feedback" => ["commands/feedback", "Feedback", "File a feedback report, body on standard input"],
23
+ "help" => ["commands/help", "Help", "List the commands and help topics, or show one usage"],
24
+ "hook" => ["commands/hook", "Hook", "Run one harness hook event through its launcher"],
25
+ "index" => ["commands/index", "Index", "Rebuild the search index from every markdown file in the stores"],
26
+ "install" => ["commands/install", "Install", "Install Plastic into this machine's agents"],
27
+ "intent" => ["commands/intent", "Intent", "List the intent subcommands"],
28
+ "intent answer" => ["commands/intent_answer", "IntentAnswer", "Answer an intent's needs_decision node"],
29
+ "intent end" => ["commands/intent_end", "IntentEnd", "Close an intent as delivered or abandoned"],
30
+ "intent new" => ["commands/intent_new", "IntentNew", "Create a new intent"],
31
+ "intent note" => ["commands/intent_note", "IntentNote", "Append a savepoint note to an intent"],
32
+ "intent rule" => ["commands/intent_rule", "IntentRule", "Record a ruling in an intent's Insights"],
33
+ "intent show" => ["commands/intent_show", "IntentShow", "Print an intent's state screen"],
34
+ "intent spec" => ["commands/intent_spec", "IntentSpec", "Print an intent's state screen, then the speccing rules"],
35
+ "intent step" => ["commands/intent_step", "IntentStep", "Run the next ready step of an intent's graph"],
36
+ "intent verify" => ["commands/intent_verify", "IntentVerify", "Run an intent's merge-gate checks"],
37
+ "migrate" => ["commands/migrate", "Migrate", "List the migrate subcommands"],
38
+ "migrate stores" => ["commands/migrate_stores", "MigrateStores", "Move the stores under stores/, behind a full copy of the home"],
39
+ "next" => ["commands/next", "Next", "Print the next action in one line"],
40
+ "project" => ["commands/project", "Project", "List the project subcommands"],
41
+ "project links" => ["commands/project_links", "ProjectLinks", "Project every store's Links sections from frontmatter"],
42
+ "project list" => ["commands/project_list", "ProjectList", "List every store this machine holds"],
43
+ "project new" => ["commands/project_new", "ProjectNew", "Register, provision and validate a new project"],
44
+ "query" => ["commands/query", "Query", "Run one read-only SQL statement on the index"],
45
+ "render" => ["commands/render", "Render", "Print one markdown file as an HTML page"],
46
+ "roadmap" => ["commands/roadmap", "Roadmap", "List the roadmap subcommands"],
47
+ "roadmap check" => ["commands/roadmap_check", "RoadmapCheck", "Check a roadmap's graph for cycles and dangling ids"],
48
+ "roadmap log" => ["commands/roadmap_log", "RoadmapLog", "Append a savepoint line to a roadmap's ledger"],
49
+ "roadmap migrate" => ["commands/roadmap_migrate", "RoadmapMigrate", "Write a roadmap's Graph section from its batches"],
50
+ "roadmap next" => ["commands/roadmap_next", "RoadmapNext", "Name the roadmap most worth continuing"],
51
+ "roadmap show" => ["commands/roadmap_show", "RoadmapShow", "Print a roadmap's state screen"],
52
+ "rollback" => ["commands/rollback", "Rollback", "Move to a Plastic version this machine has run before"],
53
+ "search" => ["commands/search", "Search", "Find text in the stores, ranked, with one excerpt each"],
54
+ "session commit" => ["commands/session_commit", "SessionCommit", "Commit one verified checklist item"],
55
+ "session handoff" => ["commands/session_handoff", "SessionHandoff", "Write this session's hand-off into the day ledger"],
56
+ "session summary" => ["commands/session_summary", "SessionSummary", "Print the day ledger's open items and recent activity"],
57
+ "session" => ["commands/session", "Session", "List the session subcommands"],
58
+ "status" => ["commands/status", "Status", "Show active work in every store"],
59
+ "sync" => ["commands/sync", "Sync", "Bring the store files and the three databases level"],
60
+ "uninstall" => ["commands/uninstall", "Uninstall", "Remove Plastic from this machine's agents"],
61
+ "update" => ["commands/update", "Update", "Move Plastic to the next version on its channel"],
62
+ "version" => ["commands/version", "Version", "Print the installed Plastic version"]
63
+ }.freeze
64
+ end
65
+ end
@@ -0,0 +1,94 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "did_you_mean"
5
+ require_relative "cli/table"
6
+ require_relative "cli/command"
7
+ require_relative "cli/output"
8
+
9
+ # Plastic::CLI (intent 363) - the dispatcher behind bin/plastic. It reads the
10
+ # command table, matches the longest command name the arguments begin with,
11
+ # requires that one file, and calls it. Nothing else is loaded, which is what
12
+ # keeps a call at about 20 milliseconds with RubyGems off.
13
+ #
14
+ # No arguments, or --help, print the command list. --version prints the
15
+ # version. A command's own --help prints that command's usage line, read from
16
+ # the class without building it. An unknown name exits 2 and names the closest
17
+ # command, so a typo never becomes a stack trace.
18
+ module Plastic
19
+ class CLI
20
+ FLAGS = {"--version" => "version", "-v" => "version",
21
+ "--help" => "help", "-h" => "help"}.freeze
22
+
23
+ def self.call(...)
24
+ new(...).call
25
+ end
26
+
27
+ def initialize(argv, out: $stdout, err: $stderr, env: ENV, home: Dir.home,
28
+ directory: Dir.pwd, table: TABLE)
29
+ @argv = argv.dup
30
+ @out = out
31
+ @err = err
32
+ @env = env
33
+ @home = home
34
+ @directory = directory
35
+ @table = table
36
+ end
37
+
38
+ def call
39
+ matched = match(expanded)
40
+ return unknown(expanded.first) unless matched
41
+
42
+ name, rest = matched
43
+ return usage(name, rest) if help?(rest)
44
+
45
+ command(name).call(rest, out: @out, err: @err, env: @env, home: @home,
46
+ directory: @directory)
47
+ end
48
+
49
+ def match(argv)
50
+ [2, 1].each do |width|
51
+ name = argv.first(width).join(" ")
52
+ return [name, argv.drop(width)] if @table.key?(name)
53
+ end
54
+ nil
55
+ end
56
+
57
+ private
58
+
59
+ def expanded
60
+ return ["help"] if @argv.empty?
61
+
62
+ [FLAGS.fetch(@argv.first, @argv.first)] + @argv.drop(1)
63
+ end
64
+
65
+ def help?(rest)
66
+ rest.any? { |argument| FLAGS[argument] == "help" }
67
+ end
68
+
69
+ def usage(name, rest)
70
+ Output.new(out: @out, err: @err)
71
+ .row("usage", command(name)::USAGE_LINE)
72
+ .flush(json: rest.include?("--json"))
73
+ Command::OK
74
+ end
75
+
76
+ def command(name)
77
+ file, const, = @table.fetch(name)
78
+ require_relative "cli/#{file}"
79
+ Commands.const_get(const)
80
+ end
81
+
82
+ def unknown(name)
83
+ suggestions = closest(name)
84
+ @err.puts "plastic: no command named #{name.inspect}"
85
+ @err.puts "Closest: #{suggestions.join(", ")}" unless suggestions.empty?
86
+ @err.puts "Run `plastic help` for the command list."
87
+ Command::USAGE
88
+ end
89
+
90
+ def closest(name)
91
+ DidYouMean::SpellChecker.new(dictionary: @table.keys).correct(name)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,198 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "yaml"
5
+ require "timeout"
6
+ require "tmpdir"
7
+ require_relative "runner_policy"
8
+
9
+ # CodexAdapter (intent 340b, G7c, n6): the argv `codex exec` needs for one
10
+ # node's kind, and the mechanics of running it once - a bounded subprocess
11
+ # whose stdin carries the node input, whose stdout and `--output-last-message`
12
+ # file are the only two places a return can come from, and whose timeout
13
+ # kills the whole process group rather than one pid.
14
+ #
15
+ # Pure where it can be: sandbox_mode, git_dir_for_worktree, add_dir_args and
16
+ # build_argv touch nothing but the filesystem read they are explicitly given
17
+ # (the worktree's own `.git` file), never a git call and never a guess at a
18
+ # repository root. #execute is the one place this module actually spawns a
19
+ # child process; scripts/node-run is the only caller.
20
+ module CodexAdapter
21
+ module_function
22
+
23
+ # Verify and research get NO write access at all (spec D9): read-only, no
24
+ # `--add-dir`. Everything else - work, and any kind this table has never
25
+ # heard of - gets the widest treatment, matching RunnerPolicy's own
26
+ # unknown-kind-falls-back-to-work rule (matrix row 6.10).
27
+ READ_ONLY_KINDS = %w[verify research].freeze
28
+
29
+ def sandbox_mode(kind)
30
+ READ_ONLY_KINDS.include?(kind.to_s) ? "read-only" : "workspace-write"
31
+ end
32
+
33
+ # The repository's OWN `.git` directory, derived from `worktree`'s `.git`
34
+ # FILE (a git worktree never carries a real `.git` directory of its own -
35
+ # spec) rather than any guess at how many path segments separate a
36
+ # worktree from its repo (matrix row 6.6). `.git` reads
37
+ # `gitdir: <repo>/.git/worktrees/<name>`; two directories up from that
38
+ # target is the repository's real `.git`. Returns nil when `worktree` is
39
+ # not a git worktree at all (no `.git` file, or `.git` is a directory,
40
+ # meaning `worktree` is a repository's own primary checkout) - callers
41
+ # treat that as "no `--add-dir` to add", never as a reason to guess.
42
+ def git_dir_for_worktree(worktree)
43
+ gitfile = File.join(worktree.to_s, ".git")
44
+ return nil unless File.file?(gitfile)
45
+
46
+ content = File.read(gitfile)
47
+ m = content.match(/\Agitdir:\s*(.+?)\s*\z/m)
48
+ return nil unless m
49
+
50
+ worktrees_entry = File.expand_path(m[1], worktree.to_s)
51
+ File.dirname(File.dirname(worktrees_entry))
52
+ end
53
+
54
+ # [] for a read-only kind (matrix row 6.4): `-C` already makes the
55
+ # worktree writable, and `--add-dir` on top of `--sandbox read-only` would
56
+ # contradict the sandbox mode itself. Exactly one `--add-dir`, the
57
+ # repository's own `.git`, for every other kind (matrix row 6.5) - nil
58
+ # when the worktree's own gitdir pointer cannot be resolved, so a caller
59
+ # handed a plain (non-worktree) directory never gets a bogus argument.
60
+ def add_dir_args(kind:, worktree:)
61
+ return [] if READ_ONLY_KINDS.include?(kind.to_s)
62
+
63
+ git_dir = git_dir_for_worktree(worktree)
64
+ git_dir ? ["--add-dir", git_dir] : []
65
+ end
66
+
67
+ # The whole `codex exec` argv for one node (matrix rows 6.1-6.9): `-C` at
68
+ # the worktree given (row 6.8), the sandbox mode and `--add-dir` this
69
+ # kind's row calls for and nothing else (row 6.7 - never the intent
70
+ # worktree, never `~/.plastic`), `--output-last-message` at the path the
71
+ # caller names, and a bare `-` so the prompt is read from stdin (row 6.9) -
72
+ # the node input itself never rides in this array.
73
+ def build_argv(kind:, worktree:, output_last_message:, model: nil, effort: nil)
74
+ argv = [
75
+ "codex", "exec",
76
+ "-C", worktree.to_s,
77
+ "--sandbox", sandbox_mode(kind),
78
+ *add_dir_args(kind: kind, worktree: worktree),
79
+ ]
80
+ argv += ["--model", model.to_s] unless model.to_s.strip.empty?
81
+ argv += ["--config", %(model_reasoning_effort="#{effort}")] unless effort.to_s.strip.empty?
82
+ argv + ["--output-last-message", output_last_message.to_s, "-"]
83
+ end
84
+
85
+ # matrix row 6.23: the wall-clock bound comes from the KIND's own lease
86
+ # length (RunnerPolicy.lease_minutes), never one shared constant - a long
87
+ # `work` build must not be capped as tightly as a quick `verify` pass.
88
+ def timeout_seconds(kind)
89
+ RunnerPolicy.lease_minutes(kind) * 60
90
+ end
91
+
92
+ # The return text (matrix rows 6.18/6.19): `output_last_message_path`'s
93
+ # own content when it exists and is non-blank (row 6.18 - stdout is never
94
+ # even inspected on this path), else the last YAML document found in
95
+ # `stdout` (row 6.19), else nil (no return anywhere - row 6.20 is the
96
+ # caller's problem, not this method's).
97
+ def read_return(output_last_message_path:, stdout:)
98
+ from_file = file_present_content(output_last_message_path)
99
+ return from_file unless from_file.nil?
100
+
101
+ stdout_fallback(stdout)
102
+ end
103
+
104
+ # The LAST YAML document in `text` that parses to a mapping, or nil when
105
+ # none does (row 6.20's other half). Documents are separated the ordinary
106
+ # YAML way, a line holding only `---`; a stream with no such separator at
107
+ # all is treated as one candidate document. Every candidate is tried from
108
+ # the end, so three documents (one row names explicitly) still resolve to
109
+ # the last one, and prose or a fragment that never parses to a mapping is
110
+ # skipped rather than returned as the "document".
111
+ def stdout_fallback(text)
112
+ return nil if text.to_s.strip.empty?
113
+
114
+ segments = text.to_s.split(/^---[ \t]*$/m).map(&:strip).reject(&:empty?)
115
+ segments = [text.to_s.strip] if segments.empty?
116
+
117
+ segments.reverse_each do |segment|
118
+ return segment if safe_yaml(segment).is_a?(Hash)
119
+ end
120
+ nil
121
+ end
122
+
123
+ # Runs `argv` once, feeding `stdin_data` on stdin through a scratch file
124
+ # (never a pipe - the same "never deadlock the read side" reasoning
125
+ # HookReplay.run_bounded already carries), bounded to `timeout_seconds`.
126
+ # `argv`'s own child becomes its own process group (`pgroup: true`) so a
127
+ # timeout can kill every child `codex exec` spawned along the way, not
128
+ # just the pid this call started (matrix row 6.24) - `codex exec` itself
129
+ # spawns shells for the commands it runs, and those would otherwise
130
+ # outlive a plain `Process.kill` on the parent alone, holding the
131
+ # worktree.
132
+ #
133
+ # Returns {exit_code:, timed_out:, stdout:, message:}. `message` is
134
+ # #read_return's result, computed only when the call did not time out - a
135
+ # timed-out child's partial output is never trusted (matrix row 6.22).
136
+ def execute(argv, stdin_data:, timeout_seconds:, output_last_message_path:)
137
+ Dir.mktmpdir("plastic-node-run-") do |scratch|
138
+ in_path = File.join(scratch, "stdin")
139
+ out_path = File.join(scratch, "stdout")
140
+ err_path = File.join(scratch, "stderr")
141
+ File.binwrite(in_path, stdin_data.to_s)
142
+
143
+ pid = Process.spawn(*argv.map(&:to_s), in: in_path, out: out_path, err: err_path, pgroup: true)
144
+ timed_out = false
145
+ exit_code =
146
+ begin
147
+ Timeout.timeout(timeout_seconds) do
148
+ Process.wait(pid)
149
+ $?.exitstatus
150
+ end
151
+ rescue Timeout::Error
152
+ timed_out = true
153
+ kill_group(pid)
154
+ nil
155
+ end
156
+
157
+ stdout = File.exist?(out_path) ? File.read(out_path) : ""
158
+ message = timed_out ? nil : read_return(output_last_message_path: output_last_message_path, stdout: stdout)
159
+
160
+ { exit_code: exit_code, timed_out: timed_out, stdout: stdout, message: message }
161
+ end
162
+ end
163
+
164
+ # --- internals ---------------------------------------------------------------
165
+
166
+ def file_present_content(path)
167
+ return nil if path.nil? || !File.exist?(path)
168
+
169
+ content = File.read(path)
170
+ content.strip.empty? ? nil : content
171
+ end
172
+ private_class_method :file_present_content
173
+
174
+ def safe_yaml(text)
175
+ YAML.safe_load(text, aliases: false, permitted_classes: [])
176
+ rescue Psych::Exception, ArgumentError
177
+ nil
178
+ end
179
+ private_class_method :safe_yaml
180
+
181
+ # Kills the whole process GROUP `pid` leads, not just `pid` itself (matrix
182
+ # row 6.24): a negative pid signals the group. Rescued and reaped exactly
183
+ # like HookReplay.kill_and_reap - a process that exited in the race
184
+ # between the timeout firing and this call raises Errno::ESRCH on the kill
185
+ # or Errno::ECHILD on the wait, neither of which should ever propagate.
186
+ def kill_group(pid)
187
+ Process.kill("KILL", -pid)
188
+ rescue StandardError
189
+ nil
190
+ ensure
191
+ begin
192
+ Process.wait(pid)
193
+ rescue StandardError
194
+ nil
195
+ end
196
+ end
197
+ private_class_method :kill_group
198
+ end
@@ -19,23 +19,31 @@ require "digest"
19
19
  #
20
20
  # Library only: no CLI, no ENV, no I/O.
21
21
  module CompactInstructions
22
- # 35 and 50 percent of a 1M window.
23
- OFFER_TOKENS = 350_000
24
- INSIST_TOKENS = 500_000
22
+ # 15 and 25 percent of a 1M window (intent 355, n5, D7).
23
+ OFFER_TOKENS = 150_000
24
+ INSIST_TOKENS = 250_000
25
25
 
26
26
  # Static on purpose. A body rendered from the user's config would change its hash
27
27
  # every time they edited config.yml, and doctor would then report a correct install
28
28
  # as stale, so the block states the shipped numbers and names the two keys as the
29
29
  # override instead. It names the hand-off in words and never by path, so it reads
30
30
  # correctly whether or not the hand-off writer is installed.
31
+ # The `@` line is a Claude Code import: the harness reads the named file into the
32
+ # session. It is the whole of ruling D43, that PLASTIC.md is the only instruction text
33
+ # Plastic carries. It names the installed copy under the Plastic home, because a bare
34
+ # `@PLASTIC.md` would resolve against ~/.claude, where no such file exists.
31
35
  BODY = <<~MD.freeze
36
+ Plastic runs from one command, `plastic`. Its instructions are one page, imported here:
37
+
38
+ @~/.plastic/PLASTIC.md
39
+
32
40
  Plastic watches this session's context. When the harness reports how much of the
33
41
  window is used:
34
42
 
35
- - At 350,000 tokens, offer to compact. Say that the hand-off in today's day ledger
43
+ - At 150,000 tokens, offer to compact. Say that the hand-off in today's day ledger
36
44
  is written and current, and take no for an answer: a task that is nearly done
37
45
  does not need the interruption.
38
- - At 500,000 tokens, insist. Take no new work, write the hand-off in today's day
46
+ - At 250,000 tokens, insist. Take no new work, write the hand-off in today's day
39
47
  ledger, and compact before continuing.
40
48
  - After a compaction, say continue. The day summary at boot and the hand-off carry
41
49
  the state; do not rebuild it by re-reading files.