@zalom/plastic 2.0.0-alpha.8 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLASTIC.md +13 -136
- package/README.md +357 -133
- package/agents/plastic-enforcer.md +20 -15
- package/agents/plastic-executor.md +15 -4
- package/agents/plastic-node-research.md +30 -0
- package/agents/plastic-node-verify.md +28 -0
- package/agents/plastic-node-work.md +33 -0
- package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +7 -9
- package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
- package/assets/plastic-logo.svg +1 -0
- package/bin/crap +4 -0
- package/bin/lib/context_budget.rb +43 -6
- package/bin/lib/skill_census.rb +839 -0
- package/bin/plastic +6 -0
- package/bin/plastic-skill-census +114 -0
- package/bin/test +24 -4
- package/bin/verify-change +345 -0
- package/config_asks.yml +4 -4
- package/deprecations.yml +1 -1
- package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +19 -24
- package/{skills/auto/references → docs/help}/agent-architecture.md +16 -14
- package/{skills/conventions/references → docs/help}/completion-and-done.md +15 -16
- package/docs/help/human-report-contract.md +152 -0
- package/{skills/conventions/references → docs/help}/knowledge-graph.md +9 -0
- package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +23 -13
- package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +1 -1
- package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
- package/{skills/tutorial/references → docs/help}/track-1-guided.md +28 -47
- package/{skills/tutorial/references → docs/help}/track-2-auto.md +8 -8
- package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
- package/hooks/call-budget +4 -0
- package/hooks/hooks.json +24 -0
- package/hooks/message-display +62 -1
- package/hooks/session-start +5 -1
- package/hooks/statusline +32 -27
- package/hooks/stop +5 -0
- package/package.json +5 -3
- package/scripts/append-ledger +2 -1
- package/scripts/dashboard.rb +267 -16
- package/scripts/day-summary +2 -1
- package/scripts/doctor.rb +680 -39
- package/scripts/end-intent +238 -22
- package/scripts/exec-worktree +5 -5
- package/scripts/file-session-intent +2 -1
- package/scripts/graph-measure +249 -0
- package/scripts/hook-call-budget +222 -0
- package/scripts/hook-capture +24 -123
- package/scripts/hook-close +2 -1
- package/scripts/hook-message-display +19 -5
- package/scripts/hook-record +24 -16
- package/scripts/hook-savepoint +27 -3
- package/scripts/hook-session-start +359 -321
- package/scripts/hook-stop +58 -0
- package/scripts/index-projection +74 -0
- package/scripts/insight-append +17 -4
- package/scripts/install.rb +9 -7
- package/scripts/lib/action_graph_shim.rb +279 -0
- package/scripts/lib/active_delivery.rb +105 -0
- package/scripts/lib/agent_models.rb +63 -25
- package/scripts/lib/arm.rb +48 -83
- package/scripts/lib/atomic_write.rb +31 -0
- package/scripts/lib/backup.rb +65 -0
- package/scripts/lib/cli/command.rb +92 -0
- package/scripts/lib/cli/commands/auto.rb +18 -0
- package/scripts/lib/cli/commands/auto_brief.rb +44 -0
- package/scripts/lib/cli/commands/auto_lock.rb +99 -0
- package/scripts/lib/cli/commands/auto_report.rb +50 -0
- package/scripts/lib/cli/commands/auto_take.rb +31 -0
- package/scripts/lib/cli/commands/backup.rb +43 -0
- package/scripts/lib/cli/commands/checkout.rb +25 -0
- package/scripts/lib/cli/commands/continue.rb +66 -0
- package/scripts/lib/cli/commands/doctor.rb +81 -0
- package/scripts/lib/cli/commands/feedback.rb +40 -0
- package/scripts/lib/cli/commands/help.rb +69 -0
- package/scripts/lib/cli/commands/hook.rb +32 -0
- package/scripts/lib/cli/commands/index.rb +23 -0
- package/scripts/lib/cli/commands/install.rb +21 -0
- package/scripts/lib/cli/commands/installer_verb.rb +37 -0
- package/scripts/lib/cli/commands/intent.rb +19 -0
- package/scripts/lib/cli/commands/intent_answer.rb +37 -0
- package/scripts/lib/cli/commands/intent_command.rb +53 -0
- package/scripts/lib/cli/commands/intent_end.rb +62 -0
- package/scripts/lib/cli/commands/intent_new.rb +62 -0
- package/scripts/lib/cli/commands/intent_note.rb +43 -0
- package/scripts/lib/cli/commands/intent_rule.rb +36 -0
- package/scripts/lib/cli/commands/intent_show.rb +32 -0
- package/scripts/lib/cli/commands/intent_spec.rb +44 -0
- package/scripts/lib/cli/commands/intent_step.rb +71 -0
- package/scripts/lib/cli/commands/intent_verify.rb +26 -0
- package/scripts/lib/cli/commands/migrate.rb +16 -0
- package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
- package/scripts/lib/cli/commands/next.rb +54 -0
- package/scripts/lib/cli/commands/project.rb +19 -0
- package/scripts/lib/cli/commands/project_links.rb +42 -0
- package/scripts/lib/cli/commands/project_list.rb +20 -0
- package/scripts/lib/cli/commands/project_new.rb +72 -0
- package/scripts/lib/cli/commands/query.rb +31 -0
- package/scripts/lib/cli/commands/render.rb +33 -0
- package/scripts/lib/cli/commands/roadmap.rb +19 -0
- package/scripts/lib/cli/commands/roadmap_check.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_migrate.rb +53 -0
- package/scripts/lib/cli/commands/roadmap_next.rb +70 -0
- package/scripts/lib/cli/commands/roadmap_show.rb +45 -0
- package/scripts/lib/cli/commands/rollback.rb +20 -0
- package/scripts/lib/cli/commands/search.rb +60 -0
- package/scripts/lib/cli/commands/session.rb +18 -0
- package/scripts/lib/cli/commands/session_commit.rb +42 -0
- package/scripts/lib/cli/commands/session_handoff.rb +34 -0
- package/scripts/lib/cli/commands/session_summary.rb +35 -0
- package/scripts/lib/cli/commands/status.rb +68 -0
- package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
- package/scripts/lib/cli/commands/sync.rb +46 -0
- package/scripts/lib/cli/commands/uninstall.rb +20 -0
- package/scripts/lib/cli/commands/update.rb +20 -0
- package/scripts/lib/cli/commands/version.rb +53 -0
- package/scripts/lib/cli/frontier.rb +86 -0
- package/scripts/lib/cli/intent_progress.rb +36 -0
- package/scripts/lib/cli/legacy.rb +80 -0
- package/scripts/lib/cli/output.rb +137 -0
- package/scripts/lib/cli/scope.rb +134 -0
- package/scripts/lib/cli/table.rb +65 -0
- package/scripts/lib/cli.rb +94 -0
- package/scripts/lib/codex_adapter.rb +198 -0
- package/scripts/lib/compact_instructions.rb +13 -5
- package/scripts/lib/core_integrity.rb +71 -0
- package/scripts/lib/dashboard_screen.rb +40 -0
- package/scripts/lib/data_boundary.rb +132 -0
- package/scripts/lib/day_summary.rb +30 -16
- package/scripts/lib/doctor_core.rb +114 -38
- package/scripts/lib/doctor_session_ledger.rb +5 -53
- package/scripts/lib/engine_permissions.rb +88 -0
- package/scripts/lib/exec_worktree.rb +24 -21
- package/scripts/lib/feedback_report.rb +1 -1
- package/scripts/lib/graph_edges.rb +137 -0
- package/scripts/lib/graph_file.rb +246 -0
- package/scripts/lib/graph_measure.rb +645 -0
- package/scripts/lib/graph_measure_budget.rb +409 -0
- package/scripts/lib/graph_measure_cohorts.rb +487 -0
- package/scripts/lib/graph_measure_models.rb +413 -0
- package/scripts/lib/graph_measure_report.rb +532 -0
- package/scripts/lib/graph_tree.rb +98 -0
- package/scripts/lib/guarded_append.rb +155 -0
- package/scripts/lib/handoff.rb +40 -13
- package/scripts/lib/harness_adapter.rb +184 -0
- package/scripts/lib/hook_registry.rb +27 -3
- package/scripts/lib/hook_replay.rb +229 -0
- package/scripts/lib/index_entry.rb +62 -0
- package/scripts/lib/index_projection.rb +201 -0
- package/scripts/lib/insights.rb +1 -1
- package/scripts/lib/installer_core.rb +532 -71
- package/scripts/lib/intent_screen.rb +9 -6
- package/scripts/lib/intent_screen_ansi.rb +73 -12
- package/scripts/lib/intent_validator.rb +2 -2
- package/scripts/lib/lock.rb +10 -11
- package/scripts/lib/message_display.rb +377 -173
- package/scripts/lib/meter_watch.rb +185 -0
- package/scripts/lib/node_file.rb +234 -0
- package/scripts/lib/node_ids.rb +99 -0
- package/scripts/lib/node_input.rb +913 -0
- package/scripts/lib/node_input_compatibility.rb +62 -0
- package/scripts/lib/node_ledger.rb +386 -0
- package/scripts/lib/node_progress.rb +153 -0
- package/scripts/lib/node_return.rb +204 -0
- package/scripts/lib/node_worktree.rb +337 -0
- package/scripts/lib/outcome_report.rb +440 -0
- package/scripts/lib/preflight.rb +4 -6
- package/scripts/lib/project_config.rb +46 -0
- package/scripts/lib/project_validator.rb +3 -2
- package/scripts/lib/qmd_sync.rb +8 -7
- package/scripts/lib/ready_set.rb +462 -0
- package/scripts/lib/reference_archive.rb +45 -0
- package/scripts/lib/release_guard.rb +18 -0
- package/scripts/lib/report_screen.rb +1460 -74
- package/scripts/lib/rlm/corpus.rb +13 -0
- package/scripts/lib/rlm/probe.rb +29 -0
- package/scripts/lib/rlm/query.rb +22 -0
- package/scripts/lib/roadmap_graph.rb +210 -0
- package/scripts/lib/roadmap_migration.rb +95 -0
- package/scripts/lib/roadmap_queue.rb +158 -8
- package/scripts/lib/roadmap_render.rb +150 -0
- package/scripts/lib/roadmap_savepoint.rb +64 -14
- package/scripts/lib/runner_absorb.rb +703 -0
- package/scripts/lib/runner_answer.rb +206 -0
- package/scripts/lib/runner_core.rb +194 -0
- package/scripts/lib/runner_dispatch.rb +525 -0
- package/scripts/lib/runner_policy.rb +191 -0
- package/scripts/lib/runner_proposals.rb +275 -0
- package/scripts/lib/runner_rewind.rb +201 -0
- package/scripts/lib/runner_sweep.rb +231 -0
- package/scripts/lib/runner_until_empty.rb +252 -0
- package/scripts/lib/runner_watch.rb +389 -0
- package/scripts/lib/savepoint.rb +141 -19
- package/scripts/lib/scaffold_intent.rb +6 -3
- package/scripts/lib/screen_paint.rb +555 -0
- package/scripts/lib/screens/dashboard.rb +20 -0
- package/scripts/lib/screens/plan.rb +18 -0
- package/scripts/lib/screens/roadmap.rb +15 -0
- package/scripts/lib/search_index.rb +55 -0
- package/scripts/lib/session_close.rb +30 -28
- package/scripts/lib/session_git.rb +25 -18
- package/scripts/lib/session_ledger.rb +48 -4
- package/scripts/lib/session_usage.rb +190 -0
- package/scripts/lib/sqlite.rb +22 -0
- package/scripts/lib/stop_gate.rb +95 -0
- package/scripts/lib/store_discovery.rb +7 -6
- package/scripts/lib/store_layout.rb +54 -0
- package/scripts/lib/store_provisioning.rb +2 -1
- package/scripts/lib/store_sync.rb +85 -0
- package/scripts/lib/stores_move.rb +93 -0
- package/scripts/lib/verify_intent.rb +36 -8
- package/scripts/lib/version_number.rb +48 -0
- package/scripts/lib/work_graph.rb +59 -0
- package/scripts/lib/work_graph_validator.rb +201 -0
- package/scripts/lib/worktree.rb +27 -32
- package/scripts/lib/worktree_sweep.rb +6 -5
- package/scripts/link-suggest +2 -1
- package/scripts/meter-watch +57 -0
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +4 -13
- package/scripts/node-input +92 -0
- package/scripts/node-run +225 -0
- package/scripts/node-transition +291 -0
- package/scripts/outcome-report +74 -0
- package/scripts/plastic-lock +42 -33
- package/scripts/promote-session-item +3 -2
- package/scripts/read-config +53 -9
- package/scripts/ready-set +126 -0
- package/scripts/release-check +123 -0
- package/scripts/report-screen +192 -30
- package/scripts/roadmap-graph +119 -0
- package/scripts/roadmap-savepoint +7 -0
- package/scripts/runner +581 -0
- package/scripts/savepoint-note +11 -9
- package/scripts/session-commit +2 -1
- package/scripts/session-usage +56 -0
- package/scripts/skill-lint +115 -6
- package/scripts/spawn-preamble +2 -2
- package/scripts/update.rb +25 -4
- package/scripts/validate-work-graph +39 -0
- package/scripts/verify-intent +3 -2
- package/scripts/write-handoff +2 -1
- package/templates/agents.md +7 -7
- package/templates/config.yml +16 -9
- package/templates/dashboard-screen.md +22 -0
- package/templates/display-fixture.md +21 -0
- package/templates/graph.md +16 -0
- package/templates/index.md +1 -1
- package/templates/intent-screen.md +1 -1
- package/templates/node-decision.md +11 -0
- package/templates/node-research.md +13 -0
- package/templates/node-verify.md +13 -0
- package/templates/node-work.md +22 -0
- package/templates/outcome.md +15 -2
- package/templates/project.yml +1 -1
- package/templates/render.css +10 -0
- package/templates/report-plan.md +15 -0
- package/templates/report-roadmap-delivered.md +10 -0
- package/templates/report-roadmap-plan.md +9 -0
- package/templates/report-roadmap-state.md +9 -0
- package/templates/report-state.md +1 -1
- package/templates/roadmap.md +13 -0
- package/bin/plastic.js +0 -70
- package/scripts/lib/bridge.rb +0 -116
- package/skills/agent-advisor/SKILL.md +0 -92
- package/skills/auto/SKILL.md +0 -295
- package/skills/auto/evals/evals.json +0 -255
- package/skills/auto/references/end-tail.md +0 -66
- package/skills/auto/references/human-report-contract.md +0 -78
- package/skills/conventions/SKILL.md +0 -29
- package/skills/dashboard/SKILL.md +0 -169
- package/skills/dashboard/evals/evals.json +0 -38
- package/skills/dashboard/references/classification.md +0 -22
- package/skills/dashboard/templates/dashboard-global.md +0 -20
- package/skills/dashboard/templates/dashboard-project.md +0 -19
- package/skills/direct/SKILL.md +0 -66
- package/skills/direct/references/request-signals.md +0 -59
- package/skills/doctor/SKILL.md +0 -299
- package/skills/doctor/report.md +0 -102
- package/skills/feedback/SKILL.md +0 -98
- package/skills/feedback/references/transport-and-privacy.md +0 -65
- package/skills/feedback/report.md +0 -36
- package/skills/install/SKILL.md +0 -217
- package/skills/intent-continuing/SKILL.md +0 -154
- package/skills/intent-continuing/references/board-fill.md +0 -43
- package/skills/intent-continuing/references/boarding-matrix.md +0 -34
- package/skills/intent-continuing/references/context-management.md +0 -28
- package/skills/intent-continuing/references/liveness-ranking.md +0 -57
- package/skills/intent-creating/SKILL.md +0 -164
- package/skills/intent-creating/evals/evals.json +0 -72
- package/skills/intent-creating/references/lifecycle.md +0 -81
- package/skills/intent-creating/references/wikilinks.md +0 -8
- package/skills/intent-ending/SKILL.md +0 -173
- package/skills/intent-ending/evals/evals.json +0 -74
- package/skills/intent-executing/SKILL.md +0 -170
- package/skills/intent-executing/evals/evals.json +0 -66
- package/skills/intent-executing/implementer-prompt.md +0 -42
- package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
- package/skills/intent-speccing/SKILL.md +0 -130
- package/skills/intent-speccing/evals/evals.json +0 -126
- package/skills/intent-speccing/references/design-principles.md +0 -44
- package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
- package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
- package/skills/project-creating/SKILL.md +0 -162
- package/skills/project-creating/references/hubs-projects.md +0 -55
- package/skills/project-creating/references/project-scaffolding.md +0 -97
- package/skills/releasing/SKILL.md +0 -337
- package/skills/releasing/references/deprecations.md +0 -60
- package/skills/releasing/references/promotion-and-tagging.md +0 -66
- package/skills/releasing/references/release-lines.md +0 -105
- package/skills/roadmap/SKILL.md +0 -64
- package/skills/roadmap/references/file-format.md +0 -124
- package/skills/roadmap/references/operations.md +0 -112
- package/skills/rollback/SKILL.md +0 -91
- package/skills/tutorial/SKILL.md +0 -65
- package/skills/tutorial/evals/evals.json +0 -186
- package/skills/uninstall/SKILL.md +0 -75
- package/skills/update/SKILL.md +0 -126
- /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
- /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
- /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
- /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
|
|
5
|
+
# `plastic status` - every store this machine holds, the intents each one has
|
|
6
|
+
# open, and the one store worth continuing.
|
|
7
|
+
#
|
|
8
|
+
# The store to continue is chosen by a fixed rule, so two runs never disagree:
|
|
9
|
+
# the store whose repository holds the working directory, else the store with
|
|
10
|
+
# the most active intents, and on a tie the first in discovery order, which puts
|
|
11
|
+
# the global store ahead of the projects and the projects in name order.
|
|
12
|
+
module Plastic
|
|
13
|
+
class CLI
|
|
14
|
+
module Commands
|
|
15
|
+
class Status < Command
|
|
16
|
+
USAGE_LINE = "plastic status [--json]"
|
|
17
|
+
|
|
18
|
+
def call
|
|
19
|
+
counted.each { |slug, ids| @output.row(slug, summary(ids)) }
|
|
20
|
+
command, because = decision
|
|
21
|
+
@output.next_step(command, because: because)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def counted
|
|
27
|
+
@counted ||= scope.stores.to_h do |store|
|
|
28
|
+
slug = store[:slug]
|
|
29
|
+
[slug, Scope.new(env: @env, home: @home, slug: slug, directory: @directory).active_ids]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def summary(ids)
|
|
34
|
+
return "0 active" if ids.empty?
|
|
35
|
+
|
|
36
|
+
"#{ids.length} active #{ids.join(", ")}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def decision
|
|
40
|
+
here = scope.directory_project
|
|
41
|
+
return [continue_command(here), "the working directory is inside #{here}"] if busy?(here)
|
|
42
|
+
|
|
43
|
+
chosen = busiest
|
|
44
|
+
return ["plastic help", "no store has active work"] unless chosen
|
|
45
|
+
|
|
46
|
+
[continue_command(chosen), "#{chosen} holds the most active work, #{intents(chosen)}"]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def busy?(slug)
|
|
50
|
+
slug && !counted.fetch(slug, []).empty?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def busiest
|
|
54
|
+
counted.reject { |_slug, ids| ids.empty? }.max_by { |_slug, ids| ids.length }&.first
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def intents(slug)
|
|
58
|
+
count = counted.fetch(slug).length
|
|
59
|
+
(count == 1) ? "1 intent" : "#{count} intents"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def continue_command(slug)
|
|
63
|
+
(slug == Scope::GLOBAL) ? "plastic continue" : "plastic continue --project #{slug}"
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../table"
|
|
5
|
+
|
|
6
|
+
# Plastic::CLI::Commands::SubcommandList (intent 372, family 3) - the shared
|
|
7
|
+
# base every bare group command (`plastic intent`, `plastic project`,
|
|
8
|
+
# `plastic roadmap`) runs on: list the table rows under its prefix, or a
|
|
9
|
+
# usage error naming that same list for a word that matches none of them.
|
|
10
|
+
module Plastic
|
|
11
|
+
class CLI
|
|
12
|
+
module Commands
|
|
13
|
+
class SubcommandList < Command
|
|
14
|
+
def call
|
|
15
|
+
unless arguments.empty?
|
|
16
|
+
raise Usage, "no subcommand named #{arguments.first.inspect}. Try: #{subcommand_names.join(", ")}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
@output.row("usage", self.class::USAGE_LINE)
|
|
20
|
+
subcommands.each { |name, (_file, _const, summary)| @output.row(name, summary) }
|
|
21
|
+
@output.next_step(self.class::NEXT, because: self.class::BECAUSE)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def subcommands
|
|
27
|
+
TABLE.select { |name, _| name.start_with?("#{self.class::PREFIX} ") }.sort.to_h
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def subcommand_names
|
|
31
|
+
subcommands.keys
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../../store_sync"
|
|
5
|
+
require_relative "../../work_graph"
|
|
6
|
+
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Sync < Command
|
|
11
|
+
USAGE_LINE = "plastic sync [--dry-run] [--json]"
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
home = scope.plastic_home
|
|
15
|
+
plan = File.exist?(SearchIndex.path(home)) ? StoreSync.plan(home) : [["build", SearchIndex::NAME]]
|
|
16
|
+
refuse(home, plan.filter_map { |action, path| path if action == "conflict" })
|
|
17
|
+
plan.group_by(&:first).each { |action, pairs| @output.row(action, pairs.map(&:last)) }
|
|
18
|
+
@output.row("result", "nothing to do") if plan.empty?
|
|
19
|
+
apply(home, plan) unless options[:dry_run]
|
|
20
|
+
@output.next_step("plastic search TERMS", because: "a changed file is read into its row, a changed row is written out, and both changed is yours to settle")
|
|
21
|
+
rescue Sqlite::Error => e
|
|
22
|
+
raise Failure, e.message
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def switches(parser)
|
|
28
|
+
parser.on("--dry-run") { options[:dry_run] = true }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def refuse(home, conflicts)
|
|
32
|
+
return if conflicts.empty?
|
|
33
|
+
|
|
34
|
+
diffs = conflicts.map { |path| "#{path}\n#{StoreSync.diff(home, path)}" }
|
|
35
|
+
raise Refusal, "the file and the row both changed; nothing was written\n#{diffs.join("\n")}"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def apply(home, plan)
|
|
39
|
+
(plan == [["build", SearchIndex::NAME]]) ? SearchIndex.build(home) : StoreSync.apply(home, plan)
|
|
40
|
+
WorkGraph.build(home)
|
|
41
|
+
ReferenceArchive.build(home)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "installer_verb"
|
|
4
|
+
|
|
5
|
+
# `plastic uninstall` - removes Plastic from this machine's agents and leaves
|
|
6
|
+
# the store alone. The work is still scripts/uninstall.rb, run through Legacy.
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Uninstall < InstallerVerb
|
|
11
|
+
USAGE_LINE = "plastic uninstall [--claude] [--codex] [--hermes] [--all]"
|
|
12
|
+
FLAGS = %w[--claude --codex --hermes --all].freeze
|
|
13
|
+
|
|
14
|
+
SCRIPT = "uninstall.rb"
|
|
15
|
+
AFTER = "none"
|
|
16
|
+
BECAUSE = "Plastic is removed from this machine's agents"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "installer_verb"
|
|
4
|
+
|
|
5
|
+
# `plastic update` - moves this machine to the next version on its channel. The
|
|
6
|
+
# work is still scripts/update.rb, run through Legacy.
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Update < InstallerVerb
|
|
11
|
+
USAGE_LINE = "plastic update [--claude] [--codex] [--hermes] [--all] [--latest] [--beta] [--alpha] [--yes] [--reinstall] [--full-doctor]"
|
|
12
|
+
FLAGS = %w[--claude --codex --hermes --all --latest --beta --alpha --yes --reinstall --full-doctor].freeze
|
|
13
|
+
|
|
14
|
+
SCRIPT = "update.rb"
|
|
15
|
+
AFTER = "plastic version"
|
|
16
|
+
BECAUSE = "the update is done, and restarting the session picks up the new conventions"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require_relative "../command"
|
|
5
|
+
|
|
6
|
+
# `plastic version` - the version of the package this command came from, and the
|
|
7
|
+
# file it was read from. The VERSION file wins, because an installed copy
|
|
8
|
+
# carries one; the package manifest answers in a checkout, which has none.
|
|
9
|
+
module Plastic
|
|
10
|
+
class CLI
|
|
11
|
+
module Commands
|
|
12
|
+
class Version < Command
|
|
13
|
+
USAGE_LINE = "plastic version [--json]"
|
|
14
|
+
|
|
15
|
+
SOURCES = ["VERSION", "package.json"].freeze
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
path, number = found
|
|
19
|
+
raise Failure, "no VERSION file and no package.json under #{package_root}" unless number
|
|
20
|
+
|
|
21
|
+
@output.row("version", number)
|
|
22
|
+
@output.row("source", path)
|
|
23
|
+
@output.next_step("plastic status", because: "the command line works, so read the work next")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def found
|
|
27
|
+
SOURCES.each do |name|
|
|
28
|
+
path = File.join(package_root, name)
|
|
29
|
+
number = read(path)
|
|
30
|
+
return [path, number] if number
|
|
31
|
+
end
|
|
32
|
+
[nil, nil]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def read(path)
|
|
38
|
+
return nil unless File.file?(path)
|
|
39
|
+
return File.read(path).strip unless path.end_with?(".json")
|
|
40
|
+
|
|
41
|
+
number = JSON.parse(File.read(path))["version"]
|
|
42
|
+
number&.to_s&.strip
|
|
43
|
+
rescue JSON::ParserError
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def package_root
|
|
48
|
+
@package_root ||= @env["PLASTIC_PACKAGE_ROOT"] || File.expand_path("../../../..", __dir__)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../roadmap_queue"
|
|
4
|
+
|
|
5
|
+
# Plastic::CLI::Frontier (intent 363) - the one reader of "what comes next" in a
|
|
6
|
+
# scope. `plastic next` prints it as a line and `plastic continue` prints it
|
|
7
|
+
# inside a project block, so the rule that picks the work is stated once rather
|
|
8
|
+
# than twice.
|
|
9
|
+
#
|
|
10
|
+
# It reads RoadmapQueue's queue mode: the liveliest roadmap, its frontier batch,
|
|
11
|
+
# the entries that can be dispatched, the entries in flight and the blocked
|
|
12
|
+
# ones. With no roadmap at all it answers the "none" state rather than raising,
|
|
13
|
+
# and a roadmap with no grouping heading raises through to the command, which
|
|
14
|
+
# fails loudly instead of parsing it as zero entries.
|
|
15
|
+
module Plastic
|
|
16
|
+
class CLI
|
|
17
|
+
class Frontier
|
|
18
|
+
NONE = "none"
|
|
19
|
+
|
|
20
|
+
def initialize(scope)
|
|
21
|
+
@scope = scope
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def state
|
|
25
|
+
payload["state"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def roadmap
|
|
29
|
+
payload["roadmap"]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def heading
|
|
33
|
+
payload["frontier_wave"]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def dispatchable_ids
|
|
37
|
+
ids(payload["dispatchable_queue"])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def in_flight_ids
|
|
41
|
+
ids(payload["in_flight"])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def blocked_ids
|
|
45
|
+
ids(payload["blocked"])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def id
|
|
49
|
+
(dispatchable_ids + in_flight_ids).first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def summary
|
|
53
|
+
return NONE unless id
|
|
54
|
+
|
|
55
|
+
"#{id} in #{heading}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def because
|
|
59
|
+
return "#{@scope.slug} has no roadmap" if state == NONE
|
|
60
|
+
return "#{roadmap} has blocked work: #{blocked_ids.join(", ")}" if id.nil? && !blocked_ids.empty?
|
|
61
|
+
return "#{roadmap} has an invalid graph: #{heading}" if state == "error"
|
|
62
|
+
return "every entry on #{roadmap} is delivered" if id.nil?
|
|
63
|
+
|
|
64
|
+
"#{id} is first on the frontier of #{roadmap}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def next_step
|
|
68
|
+
return "none" unless id
|
|
69
|
+
|
|
70
|
+
plan = @scope.intent_dir(id)
|
|
71
|
+
plan ? "plastic intent show #{id}" : "plastic status"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def payload
|
|
77
|
+
@payload ||= RoadmapQueue.new(roadmaps_dir: @scope.roadmaps_dir,
|
|
78
|
+
index_path: @scope.index_path).queue
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def ids(entries)
|
|
82
|
+
Array(entries).map { |entry| entry["id"] }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../intent_screen"
|
|
4
|
+
|
|
5
|
+
module Plastic
|
|
6
|
+
class CLI
|
|
7
|
+
class IntentProgress
|
|
8
|
+
def initialize(scope, id)
|
|
9
|
+
@scope = scope
|
|
10
|
+
@id = id
|
|
11
|
+
@dir = scope.intent_dir(id)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def decision
|
|
15
|
+
status, = IntentScreen.index_fields(@scope.root, @id)
|
|
16
|
+
return ["none", "the intent is #{status.downcase}"] if %w[Completed Abandoned].include?(status)
|
|
17
|
+
return ["plastic intent step #{@id}", "the graph is ready for its next step"] if File.file?(File.join(@dir, "graph.md"))
|
|
18
|
+
return ["plastic intent spec #{@id}", "write spec.md with the accepted scope"] unless prepared?("spec.md")
|
|
19
|
+
return ["plastic intent spec #{@id}", "write plan.md and checklist.md before execution"] unless prepared?("plan.md") && IntentScreen.items_present?(@dir)
|
|
20
|
+
|
|
21
|
+
items = IntentScreen.checklist_items(@dir)
|
|
22
|
+
return ["plastic intent spec #{@id}", "the checklist needs concrete work items"] if items.empty?
|
|
23
|
+
return ["plastic intent verify #{@id}", "all checklist items are complete"] if items.all? { |item| item[:done] }
|
|
24
|
+
|
|
25
|
+
["plastic intent step #{@id}", "the checklist has unfinished work"]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def prepared?(name)
|
|
31
|
+
path = File.join(@dir, name)
|
|
32
|
+
File.file?(path) && !File.read(path).strip.empty? && !File.read(path).include?(IntentScreen::PLACEHOLDER_SENTINEL)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "English"
|
|
4
|
+
require "open3"
|
|
5
|
+
require "rbconfig"
|
|
6
|
+
require_relative "command"
|
|
7
|
+
|
|
8
|
+
# Plastic::CLI::Legacy (intent 363) - runs a script that has not moved into a
|
|
9
|
+
# module yet as a child process, and passes its output straight through. It is
|
|
10
|
+
# scaffolding: when the last family is migrated it is deleted, and the commands
|
|
11
|
+
# call their modules in the same process instead.
|
|
12
|
+
#
|
|
13
|
+
# A child that exits 3 refused, and the refusal travels as a Refusal so the
|
|
14
|
+
# agent is told to stop and ask rather than to retry with a flag. Every other
|
|
15
|
+
# status is handed back to the caller unchanged.
|
|
16
|
+
module Plastic
|
|
17
|
+
class CLI
|
|
18
|
+
class Legacy
|
|
19
|
+
# A runner answers with the child's exit status. Asked to capture, it
|
|
20
|
+
# answers with the child's stdout and that status, for a command that
|
|
21
|
+
# renders the script's JSON as a screen. stderr is never captured, so a
|
|
22
|
+
# warning the script writes there still reaches the terminal.
|
|
23
|
+
DEFAULT_RUNNER = lambda do |path, arguments, capture: false|
|
|
24
|
+
unless capture
|
|
25
|
+
system(RbConfig.ruby, path, *arguments)
|
|
26
|
+
next exit_code($CHILD_STATUS)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
out, process = Open3.capture2(RbConfig.ruby, path, *arguments)
|
|
30
|
+
[out, exit_code(process)]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
NON_OWNER_EXIT_THREE = %w[verify-intent runner].freeze
|
|
34
|
+
|
|
35
|
+
def self.exit_code(status)
|
|
36
|
+
return Command::OK if status.success?
|
|
37
|
+
|
|
38
|
+
status.exitstatus || Command::FAILED
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def initialize(env:, runner: nil, output: nil, json: false)
|
|
42
|
+
@output = output
|
|
43
|
+
@json = json
|
|
44
|
+
@env = env
|
|
45
|
+
@runner = runner || DEFAULT_RUNNER
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def package_root
|
|
49
|
+
@package_root ||= @env["PLASTIC_PACKAGE_ROOT"] || File.expand_path("../../..", __dir__)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def script_path(script)
|
|
53
|
+
File.join(package_root, "scripts", script)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def run(script, *arguments)
|
|
57
|
+
if @json
|
|
58
|
+
text, status = @runner.call(script_path(script), arguments, capture: true)
|
|
59
|
+
@output.raw(text) unless text.empty?
|
|
60
|
+
else
|
|
61
|
+
status = @runner.call(script_path(script), arguments)
|
|
62
|
+
end
|
|
63
|
+
raise Command::Refusal, "#{script} needs the owner" if owner_refusal?(script, status)
|
|
64
|
+
|
|
65
|
+
status
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def owner_refusal?(script, status)
|
|
69
|
+
status == Command::REFUSED && !NON_OWNER_EXIT_THREE.include?(script)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def capture(script, *arguments)
|
|
73
|
+
out, status = @runner.call(script_path(script), arguments, capture: true)
|
|
74
|
+
raise Command::Refusal, "#{script} needs the owner" if owner_refusal?(script, status)
|
|
75
|
+
|
|
76
|
+
[out, status]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -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
|