@zalom/plastic 2.0.0-alpha.27 → 2.0.0-alpha.28

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 (207) hide show
  1. package/PLASTIC.md +13 -139
  2. package/README.md +337 -133
  3. package/agents/plastic-enforcer.md +9 -10
  4. package/agents/plastic-executor.md +1 -1
  5. package/bin/crap +4 -0
  6. package/bin/lib/context_budget.rb +35 -1
  7. package/bin/lib/skill_census.rb +839 -0
  8. package/bin/plastic +6 -0
  9. package/bin/plastic-skill-census +114 -0
  10. package/bin/verify-change +345 -0
  11. package/deprecations.yml +1 -1
  12. package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +4 -7
  13. package/{skills/auto/references → docs/help}/agent-architecture.md +7 -7
  14. package/{skills/conventions/references → docs/help}/completion-and-done.md +1 -1
  15. package/{skills/auto/references → docs/help}/human-report-contract.md +17 -19
  16. package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
  17. package/{skills/tutorial/references → docs/help}/track-1-guided.md +8 -8
  18. package/{skills/tutorial/references → docs/help}/track-2-auto.md +5 -5
  19. package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
  20. package/package.json +3 -2
  21. package/scripts/append-ledger +2 -1
  22. package/scripts/dashboard.rb +10 -9
  23. package/scripts/day-summary +2 -1
  24. package/scripts/doctor.rb +48 -42
  25. package/scripts/end-intent +2 -8
  26. package/scripts/file-session-intent +2 -1
  27. package/scripts/hook-capture +4 -3
  28. package/scripts/hook-close +2 -1
  29. package/scripts/hook-record +3 -2
  30. package/scripts/hook-savepoint +3 -2
  31. package/scripts/hook-session-start +5 -4
  32. package/scripts/hook-stop +2 -1
  33. package/scripts/insight-append +1 -2
  34. package/scripts/install.rb +3 -1
  35. package/scripts/lib/active_delivery.rb +1 -1
  36. package/scripts/lib/arm.rb +2 -1
  37. package/scripts/lib/backup.rb +63 -0
  38. package/scripts/lib/cli/command.rb +85 -0
  39. package/scripts/lib/cli/commands/auto.rb +18 -0
  40. package/scripts/lib/cli/commands/auto_brief.rb +44 -0
  41. package/scripts/lib/cli/commands/auto_lock.rb +60 -0
  42. package/scripts/lib/cli/commands/auto_report.rb +50 -0
  43. package/scripts/lib/cli/commands/auto_take.rb +25 -0
  44. package/scripts/lib/cli/commands/backup.rb +43 -0
  45. package/scripts/lib/cli/commands/checkout.rb +25 -0
  46. package/scripts/lib/cli/commands/continue.rb +66 -0
  47. package/scripts/lib/cli/commands/doctor.rb +20 -0
  48. package/scripts/lib/cli/commands/feedback.rb +40 -0
  49. package/scripts/lib/cli/commands/help.rb +69 -0
  50. package/scripts/lib/cli/commands/hook.rb +32 -0
  51. package/scripts/lib/cli/commands/index.rb +23 -0
  52. package/scripts/lib/cli/commands/install.rb +21 -0
  53. package/scripts/lib/cli/commands/installer_verb.rb +37 -0
  54. package/scripts/lib/cli/commands/intent.rb +19 -0
  55. package/scripts/lib/cli/commands/intent_answer.rb +37 -0
  56. package/scripts/lib/cli/commands/intent_command.rb +53 -0
  57. package/scripts/lib/cli/commands/intent_end.rb +61 -0
  58. package/scripts/lib/cli/commands/intent_new.rb +62 -0
  59. package/scripts/lib/cli/commands/intent_note.rb +43 -0
  60. package/scripts/lib/cli/commands/intent_rule.rb +36 -0
  61. package/scripts/lib/cli/commands/intent_show.rb +25 -0
  62. package/scripts/lib/cli/commands/intent_spec.rb +44 -0
  63. package/scripts/lib/cli/commands/intent_step.rb +43 -0
  64. package/scripts/lib/cli/commands/intent_verify.rb +26 -0
  65. package/scripts/lib/cli/commands/migrate.rb +16 -0
  66. package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
  67. package/scripts/lib/cli/commands/next.rb +49 -0
  68. package/scripts/lib/cli/commands/project.rb +19 -0
  69. package/scripts/lib/cli/commands/project_links.rb +42 -0
  70. package/scripts/lib/cli/commands/project_list.rb +20 -0
  71. package/scripts/lib/cli/commands/project_new.rb +72 -0
  72. package/scripts/lib/cli/commands/query.rb +31 -0
  73. package/scripts/lib/cli/commands/render.rb +25 -0
  74. package/scripts/lib/cli/commands/roadmap.rb +19 -0
  75. package/scripts/lib/cli/commands/roadmap_check.rb +44 -0
  76. package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
  77. package/scripts/lib/cli/commands/roadmap_next.rb +34 -0
  78. package/scripts/lib/cli/commands/roadmap_show.rb +45 -0
  79. package/scripts/lib/cli/commands/rollback.rb +20 -0
  80. package/scripts/lib/cli/commands/search.rb +60 -0
  81. package/scripts/lib/cli/commands/session.rb +18 -0
  82. package/scripts/lib/cli/commands/session_commit.rb +42 -0
  83. package/scripts/lib/cli/commands/session_handoff.rb +34 -0
  84. package/scripts/lib/cli/commands/session_summary.rb +35 -0
  85. package/scripts/lib/cli/commands/status.rb +68 -0
  86. package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
  87. package/scripts/lib/cli/commands/sync.rb +46 -0
  88. package/scripts/lib/cli/commands/uninstall.rb +20 -0
  89. package/scripts/lib/cli/commands/update.rb +20 -0
  90. package/scripts/lib/cli/commands/version.rb +53 -0
  91. package/scripts/lib/cli/frontier.rb +84 -0
  92. package/scripts/lib/cli/legacy.rb +50 -0
  93. package/scripts/lib/cli/output.rb +102 -0
  94. package/scripts/lib/cli/scope.rb +127 -0
  95. package/scripts/lib/cli/table.rb +64 -0
  96. package/scripts/lib/cli.rb +94 -0
  97. package/scripts/lib/compact_instructions.rb +8 -0
  98. package/scripts/lib/day_summary.rb +4 -3
  99. package/scripts/lib/doctor_core.rb +7 -32
  100. package/scripts/lib/doctor_session_ledger.rb +2 -1
  101. package/scripts/lib/feedback_report.rb +1 -1
  102. package/scripts/lib/graph_measure_models.rb +3 -1
  103. package/scripts/lib/index_entry.rb +9 -0
  104. package/scripts/lib/installer_core.rb +73 -19
  105. package/scripts/lib/intent_screen.rb +3 -3
  106. package/scripts/lib/lock.rb +2 -2
  107. package/scripts/lib/node_input.rb +3 -2
  108. package/scripts/lib/preflight.rb +2 -4
  109. package/scripts/lib/project_config.rb +2 -1
  110. package/scripts/lib/project_validator.rb +3 -2
  111. package/scripts/lib/qmd_sync.rb +8 -7
  112. package/scripts/lib/reference_archive.rb +45 -0
  113. package/scripts/lib/release_guard.rb +2 -0
  114. package/scripts/lib/report_screen.rb +4 -3
  115. package/scripts/lib/rlm/corpus.rb +13 -0
  116. package/scripts/lib/rlm/probe.rb +29 -0
  117. package/scripts/lib/rlm/query.rb +22 -0
  118. package/scripts/lib/roadmap_queue.rb +2 -2
  119. package/scripts/lib/roadmap_savepoint.rb +1 -1
  120. package/scripts/lib/runner_absorb.rb +3 -2
  121. package/scripts/lib/search_index.rb +55 -0
  122. package/scripts/lib/session_git.rb +4 -3
  123. package/scripts/lib/sqlite.rb +22 -0
  124. package/scripts/lib/store_discovery.rb +7 -6
  125. package/scripts/lib/store_layout.rb +54 -0
  126. package/scripts/lib/store_provisioning.rb +2 -1
  127. package/scripts/lib/store_sync.rb +85 -0
  128. package/scripts/lib/stores_move.rb +93 -0
  129. package/scripts/lib/verify_intent.rb +2 -7
  130. package/scripts/lib/version_number.rb +48 -0
  131. package/scripts/lib/work_graph.rb +59 -0
  132. package/scripts/lib/worktree.rb +3 -8
  133. package/scripts/lib/worktree_sweep.rb +3 -2
  134. package/scripts/link-suggest +2 -1
  135. package/scripts/migrate-to-global +1 -1
  136. package/scripts/new-intent +3 -12
  137. package/scripts/plastic-lock +3 -2
  138. package/scripts/promote-session-item +3 -2
  139. package/scripts/release-check +10 -5
  140. package/scripts/report-screen +1 -1
  141. package/scripts/session-commit +2 -1
  142. package/scripts/spawn-preamble +2 -2
  143. package/scripts/update.rb +25 -4
  144. package/scripts/write-handoff +2 -1
  145. package/templates/agents.md +6 -6
  146. package/templates/render.css +10 -0
  147. package/bin/plastic.js +0 -70
  148. package/skills/agent-advisor/SKILL.md +0 -84
  149. package/skills/auto/SKILL.md +0 -297
  150. package/skills/auto/evals/evals.json +0 -255
  151. package/skills/auto/references/end-tail.md +0 -64
  152. package/skills/conventions/SKILL.md +0 -29
  153. package/skills/dashboard/SKILL.md +0 -180
  154. package/skills/dashboard/evals/evals.json +0 -38
  155. package/skills/dashboard/references/classification.md +0 -22
  156. package/skills/dashboard/templates/dashboard-global.md +0 -20
  157. package/skills/dashboard/templates/dashboard-project.md +0 -19
  158. package/skills/direct/SKILL.md +0 -66
  159. package/skills/direct/references/request-signals.md +0 -59
  160. package/skills/doctor/SKILL.md +0 -305
  161. package/skills/doctor/report.md +0 -102
  162. package/skills/feedback/SKILL.md +0 -98
  163. package/skills/feedback/references/transport-and-privacy.md +0 -65
  164. package/skills/feedback/report.md +0 -36
  165. package/skills/install/SKILL.md +0 -215
  166. package/skills/intent-continuing/SKILL.md +0 -156
  167. package/skills/intent-continuing/references/board-fill.md +0 -52
  168. package/skills/intent-continuing/references/boarding-matrix.md +0 -35
  169. package/skills/intent-continuing/references/context-management.md +0 -28
  170. package/skills/intent-continuing/references/liveness-ranking.md +0 -57
  171. package/skills/intent-creating/SKILL.md +0 -89
  172. package/skills/intent-creating/evals/evals.json +0 -72
  173. package/skills/intent-creating/references/lifecycle.md +0 -81
  174. package/skills/intent-creating/references/wikilinks.md +0 -8
  175. package/skills/intent-ending/SKILL.md +0 -182
  176. package/skills/intent-ending/evals/evals.json +0 -74
  177. package/skills/intent-executing/SKILL.md +0 -87
  178. package/skills/intent-executing/evals/evals.json +0 -66
  179. package/skills/intent-executing/implementer-prompt.md +0 -47
  180. package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
  181. package/skills/intent-speccing/SKILL.md +0 -136
  182. package/skills/intent-speccing/evals/evals.json +0 -126
  183. package/skills/intent-speccing/references/design-principles.md +0 -44
  184. package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
  185. package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
  186. package/skills/project-creating/SKILL.md +0 -162
  187. package/skills/project-creating/references/hubs-projects.md +0 -55
  188. package/skills/project-creating/references/project-scaffolding.md +0 -97
  189. package/skills/releasing/SKILL.md +0 -376
  190. package/skills/releasing/references/deprecations.md +0 -60
  191. package/skills/releasing/references/promotion-and-tagging.md +0 -70
  192. package/skills/releasing/references/release-lines.md +0 -105
  193. package/skills/roadmap/SKILL.md +0 -90
  194. package/skills/roadmap/references/file-format.md +0 -134
  195. package/skills/roadmap/references/operations.md +0 -112
  196. package/skills/rollback/SKILL.md +0 -91
  197. package/skills/tutorial/SKILL.md +0 -66
  198. package/skills/tutorial/evals/evals.json +0 -186
  199. package/skills/uninstall/SKILL.md +0 -75
  200. package/skills/update/SKILL.md +0 -126
  201. /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
  202. /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
  203. /package/{skills/conventions/references → docs/help}/knowledge-graph.md +0 -0
  204. /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
  205. /package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +0 -0
  206. /package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +0 -0
  207. /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
@@ -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,49 @@
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
+ @output.row("next work", frontier.summary)
18
+ detail if options[:why]
19
+ @output.next_step(frontier.next_step, because: frontier.because)
20
+ rescue RoadmapSavepoint::MissingGroupingHeading => e
21
+ raise Failure, e.message
22
+ end
23
+
24
+ private
25
+
26
+ def switches(parser)
27
+ parser.on("--why") { @options[:why] = true }
28
+ parser.on("--project SLUG") { |slug| @options[:project] = slug }
29
+ end
30
+
31
+ def detail
32
+ @output.row("roadmap", frontier.roadmap || Frontier::NONE)
33
+ @output.row("frontier", frontier.heading || Frontier::NONE)
34
+ @output.row("dispatchable", listed(frontier.dispatchable_ids))
35
+ @output.row("in flight", listed(frontier.in_flight_ids))
36
+ @output.row("blocked", listed(frontier.blocked_ids))
37
+ end
38
+
39
+ def listed(ids)
40
+ ids.empty? ? Frontier::NONE : ids.join(", ")
41
+ end
42
+
43
+ def frontier
44
+ @frontier ||= Frontier.new(scope)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic project links` - projects every store's `## Links` sections from
7
+ # frontmatter (`project-links`, run through Legacy).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class ProjectLinks < Command
12
+ USAGE_LINE = "plastic project links [--dry-run] [--json]"
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ status = legacy.run("project-links", "--plastic-home", scope.plastic_home, *dry_run_flag)
21
+ raise Failure, "project-links exited #{status}" unless status.zero?
22
+
23
+ @output.next_step("plastic status", because: "the Links sections just rebuilt across every store")
24
+ end
25
+
26
+ private
27
+
28
+ def switches(parser)
29
+ parser.on("--dry-run") { @options[:dry_run] = true }
30
+ end
31
+
32
+ def dry_run_flag
33
+ options[:dry_run] ? ["--dry-run"] : []
34
+ end
35
+
36
+ def legacy
37
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+
5
+ # `plastic project list` - every store this machine holds, global and
6
+ # project, straight from Scope#stores, in this process.
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class ProjectList < Command
11
+ USAGE_LINE = "plastic project list [--json]"
12
+
13
+ def call
14
+ scope.stores.each { |store| @output.row(store[:slug], store[:root]) }
15
+ @output.next_step("plastic status", because: "status shows what is active in each store this just listed")
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+ require "date"
5
+ require_relative "../command"
6
+ require_relative "../legacy"
7
+ require_relative "../../store_provisioning"
8
+
9
+ # `plastic project new` - registers a project in projects.yml, then provisions
10
+ # and validates its store (`provision-project-store`, `validate-project`, run
11
+ # through Legacy). The project directory must already exist; this command
12
+ # never makes one, and a slug already registered is a Failure.
13
+ module Plastic
14
+ class CLI
15
+ module Commands
16
+ class ProjectNew < Command
17
+ USAGE_LINE = "plastic project new SLUG --path PATH [--parent ID] [--json]"
18
+
19
+ def initialize(argv, runner: nil, **streams)
20
+ super(argv, **streams)
21
+ @runner = runner
22
+ end
23
+
24
+ def call
25
+ raise Usage, "SLUG is required" if slug.to_s.empty?
26
+ raise Usage, "--path is required" if options[:path].to_s.empty?
27
+ raise Failure, "#{options[:path]} does not exist" unless Dir.exist?(options[:path])
28
+ raise Failure, "#{options[:path]} has no AGENTS.md; write one first" unless File.file?(File.join(options[:path], "AGENTS.md"))
29
+ raise Failure, "#{slug} is already registered" if StoreProvisioning.registered?(slug, scope.plastic_home)
30
+
31
+ register
32
+ run("provision-project-store", slug, "--home", scope.plastic_home)
33
+ run("validate-project", slug, "--home", scope.plastic_home)
34
+ @output.next_step("plastic project links", because: "the new store's Links section needs projecting before anything cites it")
35
+ end
36
+
37
+ private
38
+
39
+ def slug
40
+ arguments.first
41
+ end
42
+
43
+ def switches(parser)
44
+ parser.on("--path PATH") { |v| @options[:path] = v }
45
+ parser.on("--parent ID") { |v| @options[:parent] = v }
46
+ end
47
+
48
+ def projects_yml
49
+ File.join(scope.plastic_home, "projects.yml")
50
+ end
51
+
52
+ def register
53
+ data = File.exist?(projects_yml) ? (YAML.safe_load_file(projects_yml) || {}) : {}
54
+ data["projects"] ||= {}
55
+ entry = {"path" => File.expand_path(options[:path]), "registered" => Date.today.to_s, "status" => "active"}
56
+ entry["parent"] = options[:parent] if options[:parent]
57
+ data["projects"][slug] = entry
58
+ File.write(projects_yml, YAML.dump(data))
59
+ end
60
+
61
+ def run(script, *args)
62
+ status = legacy.run(script, *args)
63
+ raise Failure, "#{script} exited #{status}" unless status.zero?
64
+ end
65
+
66
+ def legacy
67
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,31 @@
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 Query < Command
10
+ USAGE_LINE = "plastic query SQL [--json]"
11
+
12
+ def call
13
+ raise Usage, "SQL is missing" if arguments.empty?
14
+ raise Failure, "no index at #{database}; run plastic index" unless File.file?(database)
15
+
16
+ rows = Sqlite.call(database, arguments.join(" "), readonly: true)
17
+ rows.each_with_index { |row, index| @output.row((index + 1).to_s, row.map { |key, value| "#{key}=#{value}" }.join(" ")) }
18
+ @output.next_step("none", because: "the rows are the whole answer; the database is opened read-only")
19
+ rescue Sqlite::Error => e
20
+ raise Failure, e.message
21
+ end
22
+
23
+ private
24
+
25
+ def database
26
+ SearchIndex.path(scope.plastic_home)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+
5
+ module Plastic
6
+ class CLI
7
+ module Commands
8
+ class Render < Command
9
+ USAGE_LINE = "plastic render FILE"
10
+ STYLE = File.expand_path("../../../../templates/render.css", __dir__)
11
+
12
+ def call
13
+ file = arguments.first or raise Usage, "FILE is the markdown file to render"
14
+ raise Failure, "no file at #{file}" unless File.file?(file)
15
+
16
+ require "rubygems"
17
+ require "rdoc"
18
+ body = RDoc::Markdown.parse(File.read(file, encoding: "UTF-8")).accept(RDoc::Markup::ToHtml.new)
19
+ @output.raw("<!doctype html>\n<meta charset=\"utf-8\">\n<title>#{File.basename(file)}</title>\n" \
20
+ "<style>\n#{File.read(STYLE)}</style>\n#{body}")
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subcommand_list"
4
+
5
+ # `plastic roadmap` - the list of `plastic roadmap ...` 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 Roadmap < SubcommandList
12
+ USAGE_LINE = "plastic roadmap SUBCOMMAND [options]"
13
+ PREFIX = "roadmap"
14
+ NEXT = "plastic roadmap next"
15
+ BECAUSE = "next names the roadmap most worth continuing, with no slug to guess"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic roadmap check` - the roadmap graph's batches, ready set and any
7
+ # cycle or dangling id (`roadmap-graph check`, run through Legacy).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class RoadmapCheck < Command
12
+ USAGE_LINE = "plastic roadmap check SLUG [--json]"
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ raise Usage, "SLUG is required" if slug.to_s.empty?
21
+
22
+ status = legacy.run("roadmap-graph", "check", path)
23
+ raise Failure, "roadmap-graph exited #{status}" unless status.zero?
24
+
25
+ @output.next_step("plastic roadmap show #{slug}", because: "the graph just checked out clean")
26
+ end
27
+
28
+ private
29
+
30
+ def slug
31
+ arguments.first
32
+ end
33
+
34
+ def path
35
+ File.join(scope.roadmaps_dir, "#{slug}.md")
36
+ end
37
+
38
+ def legacy
39
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic roadmap log` - appends a savepoint line to a roadmap's ledger
7
+ # (`roadmap-savepoint append`, run through Legacy).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class RoadmapLog < Command
12
+ USAGE_LINE = 'plastic roadmap log SLUG EVENT "TEXT" [--json]'
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ raise Usage, "SLUG is required" if slug.to_s.empty?
21
+ raise Usage, "EVENT is required" if event.to_s.empty?
22
+ raise Usage, "TEXT is required" if text.to_s.empty?
23
+
24
+ status = legacy.run("roadmap-savepoint", "append", "--roadmap", path, "--event", event, "--detail", text)
25
+ raise Failure, "roadmap-savepoint exited #{status}" unless status.zero?
26
+
27
+ @output.next_step("plastic roadmap show #{slug}", because: "the log line just landed on the state screen")
28
+ end
29
+
30
+ private
31
+
32
+ def slug
33
+ arguments[0]
34
+ end
35
+
36
+ def event
37
+ arguments[1]
38
+ end
39
+
40
+ def text
41
+ arguments[2]
42
+ end
43
+
44
+ def path
45
+ File.join(scope.roadmaps_dir, "#{slug}.md")
46
+ end
47
+
48
+ def legacy
49
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic roadmap next` - the roadmap most worth continuing right now
7
+ # (`roadmap-next --which`, run through Legacy).
8
+ module Plastic
9
+ class CLI
10
+ module Commands
11
+ class RoadmapNext < Command
12
+ USAGE_LINE = "plastic roadmap next [--json]"
13
+
14
+ def initialize(argv, runner: nil, **streams)
15
+ super(argv, **streams)
16
+ @runner = runner
17
+ end
18
+
19
+ def call
20
+ status = legacy.run("roadmap-next", "--roadmaps-dir", scope.roadmaps_dir, "--which")
21
+ raise Failure, "roadmap-next exited #{status}" unless status.zero?
22
+
23
+ @output.next_step("plastic roadmap show SLUG", because: "the winner it just named is the slug that command wants")
24
+ end
25
+
26
+ private
27
+
28
+ def legacy
29
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic roadmap show` - a roadmap's state screen (`report-screen roadmap
7
+ # FILE state`, run through Legacy). SLUG is required: `plastic roadmap next`
8
+ # is the command that picks one when the caller has none in mind.
9
+ module Plastic
10
+ class CLI
11
+ module Commands
12
+ class RoadmapShow < Command
13
+ USAGE_LINE = "plastic roadmap show SLUG [--json]"
14
+
15
+ def initialize(argv, runner: nil, **streams)
16
+ super(argv, **streams)
17
+ @runner = runner
18
+ end
19
+
20
+ def call
21
+ raise Usage, "SLUG is required" if slug.to_s.empty?
22
+
23
+ status = legacy.run("report-screen", "roadmap", path, "state")
24
+ raise Failure, "report-screen exited #{status}" unless status.zero?
25
+
26
+ @output.next_step("plastic roadmap log #{slug} EVENT \"TEXT\"", because: "a savepoint is how this state screen keeps moving")
27
+ end
28
+
29
+ private
30
+
31
+ def slug
32
+ arguments.first
33
+ end
34
+
35
+ def path
36
+ File.join(scope.roadmaps_dir, "#{slug}.md")
37
+ end
38
+
39
+ def legacy
40
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "installer_verb"
4
+
5
+ # `plastic rollback` - steps back to a version this machine has already run. The
6
+ # work is still scripts/rollback.rb, run through Legacy.
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class Rollback < InstallerVerb
11
+ USAGE_LINE = "plastic rollback [--version VERSION] [--downgrade] [--upgrade] [--reinstall]"
12
+ FLAGS = %w[--version --downgrade --upgrade --reinstall].freeze
13
+
14
+ SCRIPT = "rollback.rb"
15
+ AFTER = "plastic version"
16
+ BECAUSE = "the version says which build is installed"
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../../search_index"
5
+ require_relative "../../rlm/query"
6
+
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class Search < Command
11
+ USAGE_LINE = "plastic search TERMS [--ask] [--project SLUG] [--limit N] [--json]"
12
+ DEFAULT_LIMIT = 10
13
+
14
+ def call
15
+ raise Usage, "TERMS is missing" if arguments.empty?
16
+ raise Failure, "no index at #{database}; run plastic index" unless File.file?(database)
17
+
18
+ rows = options[:ask] ? RLM::Query.new(RLM::Probe.new(corpus, limit: limit)).call(arguments.join(" ")) : found(arguments, limit)
19
+ rows.each { |row| @output.row(row["path"], row["excerpt"].gsub(/\s+/, " ").strip) }
20
+ @output.row("result", "no match") if rows.empty?
21
+ @output.next_step("plastic index", because: "the index was built #{File.mtime(database).strftime("%Y-%m-%d %H:%M")}; rebuild it when the stores have changed")
22
+ rescue Sqlite::Error => e
23
+ raise Failure, e.message
24
+ end
25
+
26
+ private
27
+
28
+ def switches(parser)
29
+ parser.on("--ask") { @options[:ask] = true }
30
+ parser.on("--project SLUG") { |slug| @options[:project] = slug }
31
+ parser.on("--limit N", Integer) { |limit| @options[:limit] = limit }
32
+ end
33
+
34
+ def limit
35
+ options[:limit] || DEFAULT_LIMIT
36
+ end
37
+
38
+ def found(terms, count)
39
+ SearchIndex.search(scope.plastic_home, terms, limit: count, prefix: prefix)
40
+ end
41
+
42
+ def corpus
43
+ RLM::Corpus.new(method(:found))
44
+ end
45
+
46
+ def database
47
+ SearchIndex.path(scope.plastic_home)
48
+ end
49
+
50
+ def prefix
51
+ return "" unless options[:project]
52
+
53
+ home = scope.plastic_home
54
+ root = (scope.slug == Scope::GLOBAL) ? StoreLayout.global_store(home) : StoreLayout.project_root(home, scope.slug)
55
+ "#{root.delete_prefix("#{home}/")}/"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "subcommand_list"
4
+
5
+ # `plastic session` - the list of `plastic session ...` subcommands, straight
6
+ # out of the table.
7
+ module Plastic
8
+ class CLI
9
+ module Commands
10
+ class Session < SubcommandList
11
+ USAGE_LINE = "plastic session SUBCOMMAND [options]"
12
+ PREFIX = "session"
13
+ NEXT = 'plastic session commit "SUMMARY"'
14
+ BECAUSE = "a commit is the smallest unit every other session subcommand builds on"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../command"
4
+ require_relative "../legacy"
5
+
6
+ # `plastic session commit` - one commit for a verified checklist item, on the
7
+ # directory this command was invoked from (`session-commit --cwd DIR
8
+ # --summary SUMMARY`, run through Legacy; fail-open by contract, so a Failure
9
+ # here means the script's own usage error, not a git failure).
10
+ module Plastic
11
+ class CLI
12
+ module Commands
13
+ class SessionCommit < Command
14
+ USAGE_LINE = 'plastic session commit "SUMMARY" [--json]'
15
+
16
+ def initialize(argv, runner: nil, **streams)
17
+ super(argv, **streams)
18
+ @runner = runner
19
+ end
20
+
21
+ def call
22
+ raise Usage, "SUMMARY is required" if summary.to_s.empty?
23
+
24
+ status = legacy.run("session-commit", "--cwd", @directory, "--summary", summary)
25
+ raise Failure, "session-commit exited #{status}" unless status.zero?
26
+
27
+ @output.next_step("plastic session handoff", because: "a handoff after a commit keeps the day ledger current")
28
+ end
29
+
30
+ private
31
+
32
+ def summary
33
+ arguments.first
34
+ end
35
+
36
+ def legacy
37
+ @legacy ||= Legacy.new(env: @env, runner: @runner)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end