@zalom/plastic 2.0.0-alpha.26 → 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.
- package/PLASTIC.md +13 -139
- package/README.md +337 -133
- package/agents/plastic-enforcer.md +12 -12
- package/agents/plastic-executor.md +2 -1
- package/agents/plastic-node-research.md +4 -2
- package/agents/plastic-node-verify.md +1 -0
- package/agents/plastic-node-work.md +1 -0
- package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +6 -8
- package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
- package/bin/crap +4 -0
- package/bin/lib/context_budget.rb +35 -1
- package/bin/lib/skill_census.rb +839 -0
- package/bin/plastic +6 -0
- package/bin/plastic-skill-census +114 -0
- 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 +7 -7
- package/{skills/conventions/references → docs/help}/completion-and-done.md +1 -1
- package/{skills/auto/references → docs/help}/human-report-contract.md +17 -19
- package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
- package/{skills/tutorial/references → docs/help}/track-1-guided.md +8 -8
- package/{skills/tutorial/references → docs/help}/track-2-auto.md +5 -5
- package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
- package/package.json +3 -2
- package/scripts/append-ledger +2 -1
- package/scripts/dashboard.rb +10 -9
- package/scripts/day-summary +2 -1
- package/scripts/doctor.rb +57 -49
- package/scripts/end-intent +2 -8
- package/scripts/file-session-intent +2 -1
- package/scripts/hook-capture +4 -3
- package/scripts/hook-close +2 -1
- package/scripts/hook-record +3 -2
- package/scripts/hook-savepoint +3 -2
- package/scripts/hook-session-start +5 -4
- package/scripts/hook-stop +2 -1
- package/scripts/insight-append +1 -2
- package/scripts/install.rb +7 -5
- package/scripts/lib/action_graph_shim.rb +3 -1
- package/scripts/lib/active_delivery.rb +1 -1
- package/scripts/lib/agent_models.rb +44 -26
- package/scripts/lib/arm.rb +2 -1
- package/scripts/lib/backup.rb +63 -0
- package/scripts/lib/cli/command.rb +85 -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 +60 -0
- package/scripts/lib/cli/commands/auto_report.rb +50 -0
- package/scripts/lib/cli/commands/auto_take.rb +25 -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 +20 -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 +61 -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 +25 -0
- package/scripts/lib/cli/commands/intent_spec.rb +44 -0
- package/scripts/lib/cli/commands/intent_step.rb +43 -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 +49 -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 +25 -0
- package/scripts/lib/cli/commands/roadmap.rb +19 -0
- package/scripts/lib/cli/commands/roadmap_check.rb +44 -0
- package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
- package/scripts/lib/cli/commands/roadmap_next.rb +34 -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 +84 -0
- package/scripts/lib/cli/legacy.rb +50 -0
- package/scripts/lib/cli/output.rb +102 -0
- package/scripts/lib/cli/scope.rb +127 -0
- package/scripts/lib/cli/table.rb +64 -0
- package/scripts/lib/cli.rb +94 -0
- package/scripts/lib/codex_adapter.rb +5 -4
- package/scripts/lib/compact_instructions.rb +8 -0
- package/scripts/lib/day_summary.rb +4 -3
- package/scripts/lib/doctor_core.rb +7 -32
- package/scripts/lib/doctor_session_ledger.rb +2 -1
- package/scripts/lib/feedback_report.rb +1 -1
- package/scripts/lib/graph_measure_models.rb +4 -2
- package/scripts/lib/index_entry.rb +9 -0
- package/scripts/lib/installer_core.rb +175 -56
- package/scripts/lib/intent_screen.rb +3 -3
- package/scripts/lib/lock.rb +2 -2
- package/scripts/lib/node_file.rb +22 -2
- package/scripts/lib/node_input.rb +11 -6
- package/scripts/lib/node_return.rb +8 -3
- package/scripts/lib/preflight.rb +2 -4
- package/scripts/lib/project_config.rb +2 -1
- package/scripts/lib/project_validator.rb +3 -2
- package/scripts/lib/qmd_sync.rb +8 -7
- package/scripts/lib/reference_archive.rb +45 -0
- package/scripts/lib/release_guard.rb +2 -0
- package/scripts/lib/report_screen.rb +4 -3
- 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_queue.rb +2 -2
- package/scripts/lib/roadmap_savepoint.rb +1 -1
- package/scripts/lib/runner_absorb.rb +59 -2
- package/scripts/lib/runner_dispatch.rb +12 -8
- package/scripts/lib/runner_policy.rb +28 -10
- package/scripts/lib/search_index.rb +55 -0
- package/scripts/lib/session_git.rb +4 -3
- package/scripts/lib/sqlite.rb +22 -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 +2 -7
- package/scripts/lib/version_number.rb +48 -0
- package/scripts/lib/work_graph.rb +59 -0
- package/scripts/lib/worktree.rb +3 -8
- package/scripts/lib/worktree_sweep.rb +3 -2
- package/scripts/link-suggest +2 -1
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +3 -12
- package/scripts/node-run +2 -1
- package/scripts/plastic-lock +3 -2
- package/scripts/promote-session-item +3 -2
- package/scripts/read-config +44 -6
- package/scripts/release-check +10 -5
- package/scripts/report-screen +1 -1
- package/scripts/session-commit +2 -1
- package/scripts/spawn-preamble +2 -2
- package/scripts/update.rb +25 -4
- package/scripts/write-handoff +2 -1
- package/templates/agents.md +6 -6
- package/templates/config.yml +13 -6
- package/templates/node-research.md +4 -2
- package/templates/render.css +10 -0
- package/bin/plastic.js +0 -70
- package/skills/agent-advisor/SKILL.md +0 -92
- package/skills/auto/SKILL.md +0 -297
- package/skills/auto/evals/evals.json +0 -255
- package/skills/auto/references/end-tail.md +0 -64
- package/skills/conventions/SKILL.md +0 -29
- package/skills/dashboard/SKILL.md +0 -180
- 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 -305
- 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 -156
- package/skills/intent-continuing/references/board-fill.md +0 -52
- package/skills/intent-continuing/references/boarding-matrix.md +0 -35
- 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 -89
- 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 -182
- package/skills/intent-ending/evals/evals.json +0 -74
- package/skills/intent-executing/SKILL.md +0 -87
- package/skills/intent-executing/evals/evals.json +0 -66
- package/skills/intent-executing/implementer-prompt.md +0 -47
- package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
- package/skills/intent-speccing/SKILL.md +0 -136
- 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 -376
- package/skills/releasing/references/deprecations.md +0 -60
- package/skills/releasing/references/promotion-and-tagging.md +0 -70
- package/skills/releasing/references/release-lines.md +0 -105
- package/skills/roadmap/SKILL.md +0 -90
- package/skills/roadmap/references/file-format.md +0 -134
- package/skills/roadmap/references/operations.md +0 -112
- package/skills/rollback/SKILL.md +0 -91
- package/skills/tutorial/SKILL.md +0 -66
- 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}/knowledge-graph.md +0 -0
- /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
- /package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +0 -0
- /package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +0 -0
- /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
|
@@ -14,8 +14,7 @@ module AgentModels
|
|
|
14
14
|
# HarnessAdapter::AGENT_TYPE_BY_KIND. work and research resolve the
|
|
15
15
|
# executor tier, verify the advisor tier, mirroring RunnerPolicy's
|
|
16
16
|
# model_role split (327 D12): work and research run on plastic-executor's
|
|
17
|
-
# own tier, verify on the
|
|
18
|
-
# (plastic-faux-advisor) ships, never the cheap tier.
|
|
17
|
+
# own tier, and verify on the lifecycle advisor tier, never the cheap tier.
|
|
19
18
|
TIER_DEFAULTS = {
|
|
20
19
|
"plastic-enforcer" => "opus",
|
|
21
20
|
"plastic-executor" => "sonnet",
|
|
@@ -24,32 +23,35 @@ module AgentModels
|
|
|
24
23
|
"plastic-node-research" => "sonnet"
|
|
25
24
|
}.freeze
|
|
26
25
|
|
|
27
|
-
# The two
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
# ships `model: opus`). Both are shipped DEFAULTS in frontmatter, never a
|
|
26
|
+
# The two consultation agents are Primary Advisor and Secondary Advisor.
|
|
27
|
+
# Both ship Fable; Primary uses medium effort and Secondary uses high effort.
|
|
28
|
+
# Both are shipped defaults in frontmatter, never a
|
|
31
29
|
# hard-wired identity: agents.models.claude.<name> (or the legacy flat form)
|
|
32
30
|
# overrides either one through the same install-time frontmatter rewrite
|
|
33
31
|
# every agent override uses. Neither is a lifecycle-stage role: never
|
|
34
|
-
# dispatched by the auto pipeline, not part of TIER_DEFAULTS.
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Codex reasoning
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# reasoning tier -> the deepest generally-safe effort (high, not the model-dependent
|
|
47
|
-
# xhigh); sonnet the mid execution tier -> medium; haiku the lightest -> low. minimal is
|
|
48
|
-
# unused.
|
|
32
|
+
# dispatched by the auto pipeline, not part of TIER_DEFAULTS. Codex installs
|
|
33
|
+
# generate both advisors with Astra and preserve each role's effort.
|
|
34
|
+
CONSULTATION_AGENTS = %w[plastic-primary-advisor plastic-secondary-advisor].freeze
|
|
35
|
+
|
|
36
|
+
SHIPPED_MODEL_DEFAULTS = TIER_DEFAULTS.merge(
|
|
37
|
+
"plastic-primary-advisor" => "fable",
|
|
38
|
+
"plastic-secondary-advisor" => "fable"
|
|
39
|
+
).freeze
|
|
40
|
+
|
|
41
|
+
# Codex reasoning effort per tier alias. Model choice and reasoning effort are independent:
|
|
42
|
+
# aliases select the recommended OpenAI model, while every Plastic role starts at medium.
|
|
43
|
+
# A valid harness-scoped user override can still choose another effort for one agent.
|
|
49
44
|
EFFORT_BY_ALIAS = {
|
|
50
|
-
"opus" => "
|
|
45
|
+
"opus" => "medium",
|
|
51
46
|
"sonnet" => "medium",
|
|
52
|
-
"haiku" => "
|
|
47
|
+
"haiku" => "medium"
|
|
48
|
+
}.freeze
|
|
49
|
+
|
|
50
|
+
DEFAULT_EFFORT = "medium"
|
|
51
|
+
|
|
52
|
+
SHIPPED_EFFORT_DEFAULTS = {
|
|
53
|
+
"plastic-primary-advisor" => "medium",
|
|
54
|
+
"plastic-secondary-advisor" => "high"
|
|
53
55
|
}.freeze
|
|
54
56
|
|
|
55
57
|
# Codex model id per tier alias (intent 186). Codex has NO vendor alias layer: every model id
|
|
@@ -58,17 +60,26 @@ module AgentModels
|
|
|
58
60
|
# centralizing every id in ONE map: Plastic owns the alias, so per-role identity costs a single
|
|
59
61
|
# line to refresh on a Codex deprecation plus a Plastic release, and no per-role file carries a
|
|
60
62
|
# raw id. opus (deepest reasoning tier) -> the flagship Sol; sonnet (execution tier) -> the
|
|
61
|
-
# balanced Terra; haiku (lightest) -> the fast/cheap Luna.
|
|
62
|
-
#
|
|
63
|
-
#
|
|
63
|
+
# balanced Terra; haiku (lightest) -> the fast/cheap Luna. This is a shipped
|
|
64
|
+
# DEFAULT, fully overridable via agents.models.codex.<name>. Model strength does not change
|
|
65
|
+
# effort: all three aliases use medium unless agents.efforts.codex.<name> overrides it.
|
|
64
66
|
CODEX_MODEL_BY_ALIAS = {
|
|
65
67
|
"opus" => "gpt-5.6-sol",
|
|
66
68
|
"sonnet" => "gpt-5.6-terra",
|
|
67
69
|
"haiku" => "gpt-5.6-luna"
|
|
68
70
|
}.freeze
|
|
69
71
|
|
|
72
|
+
CODEX_MODEL_BY_AGENT = {
|
|
73
|
+
"plastic-primary-advisor" => "gpt-6-astra",
|
|
74
|
+
"plastic-secondary-advisor" => "gpt-6-astra"
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
70
77
|
module_function
|
|
71
78
|
|
|
79
|
+
def shipped_effort_for(agent)
|
|
80
|
+
SHIPPED_EFFORT_DEFAULTS.fetch(agent.to_s, DEFAULT_EFFORT)
|
|
81
|
+
end
|
|
82
|
+
|
|
72
83
|
# Pull { basename => model } out of a loaded config hash's `agents.models`
|
|
73
84
|
# section, scoped to `harness` ("claude" or "codex"), tolerating a missing or
|
|
74
85
|
# malformed shape. `agents.models` can mix two shapes: legacy FLAT scalar
|
|
@@ -124,4 +135,11 @@ module AgentModels
|
|
|
124
135
|
def codex_model_for(value)
|
|
125
136
|
CODEX_MODEL_BY_ALIAS[value.to_s]
|
|
126
137
|
end
|
|
138
|
+
|
|
139
|
+
def shipped_model_for(agent, harness: "claude")
|
|
140
|
+
value = SHIPPED_MODEL_DEFAULTS[agent.to_s]
|
|
141
|
+
return value unless harness.to_s == "codex"
|
|
142
|
+
|
|
143
|
+
CODEX_MODEL_BY_AGENT[agent.to_s] || codex_model_for(value) || value
|
|
144
|
+
end
|
|
127
145
|
end
|
package/scripts/lib/arm.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
+
require_relative "store_layout"
|
|
4
5
|
require "digest"
|
|
5
6
|
require "fileutils"
|
|
6
7
|
require_relative "lock"
|
|
@@ -63,7 +64,7 @@ module Arm
|
|
|
63
64
|
end
|
|
64
65
|
|
|
65
66
|
def global_store(home)
|
|
66
|
-
File.join(File.expand_path(home), ".plastic"
|
|
67
|
+
Plastic::StoreLayout.global_store(File.join(File.expand_path(home), ".plastic"))
|
|
67
68
|
end
|
|
68
69
|
|
|
69
70
|
# The minimal hash Worktree.provision, release, and finish consume: the
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "json"
|
|
6
|
+
require "tmpdir"
|
|
7
|
+
require "zlib"
|
|
8
|
+
require_relative "store_sync"
|
|
9
|
+
|
|
10
|
+
module Plastic
|
|
11
|
+
module Backup
|
|
12
|
+
DATABASES = [SearchIndex::NAME, WorkGraph::NAME, ReferenceArchive::NAME].freeze
|
|
13
|
+
ROOT_FILES = %w[config.yml projects.yml INDEX.md].freeze
|
|
14
|
+
Missing = Class.new(StandardError)
|
|
15
|
+
|
|
16
|
+
def self.dir(home)
|
|
17
|
+
File.join(home, "backups")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.list(home)
|
|
21
|
+
Dir.glob(File.join(dir(home), "plastic-backup--*.tar.gz")).sort
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.call(home, version:, now: Time.now.utc)
|
|
25
|
+
missing = DATABASES.reject { |name| File.exist?(File.join(home, name)) }
|
|
26
|
+
raise Missing, missing.join(", ") unless missing.empty?
|
|
27
|
+
|
|
28
|
+
Dir.mktmpdir("plastic-backup") do |work|
|
|
29
|
+
DATABASES.each { |name| Sqlite.call(File.join(home, name), "VACUUM INTO #{Sqlite.quote(File.join(work, name))};", readonly: true) }
|
|
30
|
+
FileUtils.cp(ROOT_FILES.map { |name| File.join(home, name) }.select { |path| File.exist?(path) }, work)
|
|
31
|
+
File.write(File.join(work, "manifest.json"), JSON.pretty_generate(manifest(work, version, now)))
|
|
32
|
+
pack(work, File.join(dir(home), "plastic-backup--#{now.strftime("%Y%m%dT%H%M%SZ")}.tar.gz"))
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.manifest(work, version, now)
|
|
37
|
+
files = Dir.children(work).sort.map do |name|
|
|
38
|
+
path = File.join(work, name)
|
|
39
|
+
{"name" => name, "bytes" => File.size(path), "sha256" => Digest::SHA256.file(path).hexdigest}
|
|
40
|
+
end
|
|
41
|
+
{"version" => version, "at" => now.strftime("%Y-%m-%dT%H:%M:%SZ"), "files" => files}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.pack(work, archive)
|
|
45
|
+
require "rubygems"
|
|
46
|
+
require "rubygems/package"
|
|
47
|
+
FileUtils.mkdir_p(File.dirname(archive))
|
|
48
|
+
draft = "#{archive}.tmp"
|
|
49
|
+
Zlib::GzipWriter.open(draft) do |gzip|
|
|
50
|
+
Gem::Package::TarWriter.new(gzip) do |tar|
|
|
51
|
+
Dir.children(work).sort.each do |name|
|
|
52
|
+
path = File.join(work, name)
|
|
53
|
+
tar.add_file_simple(name, 0o644, File.size(path)) { |entry| IO.copy_stream(path, entry) }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
File.rename(draft, archive)
|
|
58
|
+
archive
|
|
59
|
+
ensure
|
|
60
|
+
FileUtils.rm_f(draft.to_s)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
require_relative "output"
|
|
5
|
+
require_relative "scope"
|
|
6
|
+
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
class Command
|
|
10
|
+
OK = 0
|
|
11
|
+
FAILED = 1
|
|
12
|
+
USAGE = 2
|
|
13
|
+
REFUSED = 3
|
|
14
|
+
|
|
15
|
+
Usage = Class.new(StandardError)
|
|
16
|
+
Refusal = Class.new(StandardError)
|
|
17
|
+
Failure = Class.new(StandardError)
|
|
18
|
+
|
|
19
|
+
def self.call(...)
|
|
20
|
+
command = new(...)
|
|
21
|
+
command.call
|
|
22
|
+
command.flush
|
|
23
|
+
OK
|
|
24
|
+
rescue OptionParser::ParseError, Scope::UnknownProject, Usage => e
|
|
25
|
+
command.output.usage(e.message, self::USAGE_LINE)
|
|
26
|
+
USAGE
|
|
27
|
+
rescue Refusal => e
|
|
28
|
+
command.output.refused(e.message)
|
|
29
|
+
REFUSED
|
|
30
|
+
rescue Failure => e
|
|
31
|
+
command.output.failed(e.message)
|
|
32
|
+
FAILED
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
attr_reader :output
|
|
36
|
+
|
|
37
|
+
def initialize(argv, out:, err:, env: ENV, home: Dir.home, directory: Dir.pwd)
|
|
38
|
+
@argv = argv.dup
|
|
39
|
+
@env = env
|
|
40
|
+
@home = home
|
|
41
|
+
@directory = directory
|
|
42
|
+
@output = Output.new(out: out, err: err)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def call
|
|
46
|
+
raise NoMethodError, "#{self.class} must define call"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def flush
|
|
50
|
+
@output.flush(json: options[:json])
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def options
|
|
56
|
+
return @options if @options
|
|
57
|
+
|
|
58
|
+
@options = {}
|
|
59
|
+
parser.parse!(@argv)
|
|
60
|
+
@options
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def arguments
|
|
64
|
+
options
|
|
65
|
+
@argv
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def parser
|
|
69
|
+
@parser ||= OptionParser.new do |o|
|
|
70
|
+
o.banner = self.class::USAGE_LINE
|
|
71
|
+
o.on("--json") { @options[:json] = true }
|
|
72
|
+
switches(o)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def switches(_parser)
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def scope
|
|
81
|
+
@scope ||= Scope.new(env: @env, home: @home, slug: options[:project], directory: @directory)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "subcommand_list"
|
|
4
|
+
|
|
5
|
+
# `plastic auto` - the list of `plastic auto ...` subcommands, straight out of
|
|
6
|
+
# the table.
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Auto < SubcommandList
|
|
11
|
+
USAGE_LINE = "plastic auto SUBCOMMAND [options]"
|
|
12
|
+
PREFIX = "auto"
|
|
13
|
+
NEXT = "plastic auto take ID"
|
|
14
|
+
BECAUSE = "taking the intent is what every other auto subcommand needs first"
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "intent_command"
|
|
4
|
+
|
|
5
|
+
# `plastic auto brief` - an intent's spawn preamble (`spawn-preamble`, run
|
|
6
|
+
# through Legacy). `--role advisor` also prints the three shapes an advisor
|
|
7
|
+
# request takes, so a dispatching lead never has to look them up.
|
|
8
|
+
module Plastic
|
|
9
|
+
class CLI
|
|
10
|
+
module Commands
|
|
11
|
+
class AutoBrief < IntentCommand
|
|
12
|
+
USAGE_LINE = "plastic auto brief ID [--role ROLE] [--json]"
|
|
13
|
+
|
|
14
|
+
SCRIPT = "spawn-preamble"
|
|
15
|
+
AFTER = "plastic auto report ID"
|
|
16
|
+
BECAUSE = "the report closes the turn the preamble opened"
|
|
17
|
+
|
|
18
|
+
ADVISOR_SHAPES =
|
|
19
|
+
"State the shape you need: a verdict plus the biggest risk for one bounded " \
|
|
20
|
+
"decision; a stepped plan plus a risk map for a plan or plan review; rival " \
|
|
21
|
+
"approaches and kill criteria for architecture, one-way doors, or deadlocks."
|
|
22
|
+
|
|
23
|
+
def call
|
|
24
|
+
super
|
|
25
|
+
@output.row("advisor request shapes", ADVISOR_SHAPES) if role == "advisor"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def role
|
|
31
|
+
options[:role]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def switches(parser)
|
|
35
|
+
parser.on("--role ROLE") { |v| @options[:role] = v }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def script_arguments
|
|
39
|
+
role ? [intent_dir, "--role", role] : [intent_dir]
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../legacy"
|
|
5
|
+
|
|
6
|
+
# `plastic auto lock` - inspects, repairs, or releases an intent's delivery
|
|
7
|
+
# lock (`plastic-lock status|fix|release --intent-dir DIR`, run through
|
|
8
|
+
# Legacy). The verb comes first, then the id, since the verb is what the
|
|
9
|
+
# table's two-token match consumes as part of the command name.
|
|
10
|
+
module Plastic
|
|
11
|
+
class CLI
|
|
12
|
+
module Commands
|
|
13
|
+
class AutoLock < Command
|
|
14
|
+
USAGE_LINE = "plastic auto lock status|fix|release ID [--json]"
|
|
15
|
+
|
|
16
|
+
VERBS = %w[status fix release].freeze
|
|
17
|
+
AFTER = {"status" => "plastic auto lock fix ID", "fix" => "plastic auto brief ID",
|
|
18
|
+
"release" => "plastic session summary"}.freeze
|
|
19
|
+
BECAUSE = {"status" => "a stale or held lock is repaired by fix, not by re-arming",
|
|
20
|
+
"fix" => "a repaired lock is ready for the preamble again",
|
|
21
|
+
"release" => "the summary is what a freed lock leads to"}.freeze
|
|
22
|
+
|
|
23
|
+
def initialize(argv, runner: nil, **streams)
|
|
24
|
+
super(argv, **streams)
|
|
25
|
+
@runner = runner
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call
|
|
29
|
+
raise Usage, "VERB must be one of #{VERBS.join(", ")}" unless VERBS.include?(verb)
|
|
30
|
+
|
|
31
|
+
status = legacy.run("plastic-lock", verb, "--intent-dir", intent_dir)
|
|
32
|
+
raise Failure, "plastic-lock exited #{status}" unless status.zero?
|
|
33
|
+
|
|
34
|
+
@output.next_step(AFTER.fetch(verb).sub("ID", id.to_s), because: BECAUSE.fetch(verb))
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def verb
|
|
40
|
+
arguments[0]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def id
|
|
44
|
+
arguments[1]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def intent_dir
|
|
48
|
+
dir = scope.intent_dir(id)
|
|
49
|
+
raise Failure, "no intent #{id.inspect} in #{scope.slug}; plastic status lists the ones that exist" unless dir
|
|
50
|
+
|
|
51
|
+
dir
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def legacy
|
|
55
|
+
@legacy ||= Legacy.new(env: @env, runner: @runner)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "intent_command"
|
|
4
|
+
|
|
5
|
+
# `plastic auto report` - an intent's completion report (`agent-report`, run
|
|
6
|
+
# through Legacy), then the review-by-risk rules a lead checks it against.
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class AutoReport < IntentCommand
|
|
11
|
+
USAGE_LINE = "plastic auto report ID [--role ROLE] [--json]"
|
|
12
|
+
|
|
13
|
+
SCRIPT = "agent-report"
|
|
14
|
+
AFTER = "plastic auto lock release ID"
|
|
15
|
+
BECAUSE = "closing the lock is what a delivered or abandoned report leads to"
|
|
16
|
+
|
|
17
|
+
REVIEW_RULES = <<~TEXT.chomp
|
|
18
|
+
Review by risk, boot 3, only when a rule fires; otherwise the green suite is
|
|
19
|
+
the review. Dispatch the post-execution reviewer when any of these holds:
|
|
20
|
+
1. the diff touches a path on the risk list (hooks, the lock, the arming
|
|
21
|
+
module, the installer, a release file).
|
|
22
|
+
2. a failure-mode matrix row names a test not in the diff, or a test the
|
|
23
|
+
green run did not execute.
|
|
24
|
+
3. the completion report carries a status other than delivered, or a
|
|
25
|
+
non-empty deviations or blockers field.
|
|
26
|
+
The reviewer returns a pass or fixes; at most one review-fix round, ever.
|
|
27
|
+
TEXT
|
|
28
|
+
|
|
29
|
+
def call
|
|
30
|
+
super
|
|
31
|
+
@output.row("review rules", REVIEW_RULES)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def role
|
|
37
|
+
options[:role]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def switches(parser)
|
|
41
|
+
parser.on("--role ROLE") { |v| @options[:role] = v }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def script_arguments
|
|
45
|
+
role ? [intent_dir, "--role", role] : [intent_dir]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "intent_command"
|
|
4
|
+
|
|
5
|
+
# `plastic auto take` - arms the intent's delivery lock for this session
|
|
6
|
+
# (`plastic-lock arm --intent-dir DIR --mode auto`, run through Legacy).
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class AutoTake < IntentCommand
|
|
11
|
+
USAGE_LINE = "plastic auto take ID [--json]"
|
|
12
|
+
|
|
13
|
+
SCRIPT = "plastic-lock"
|
|
14
|
+
AFTER = "plastic auto brief ID"
|
|
15
|
+
BECAUSE = "the preamble is the live state a taken intent is read from next"
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
def script_arguments
|
|
20
|
+
["arm", "--intent-dir", intent_dir, "--mode", "auto"]
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "version"
|
|
5
|
+
require_relative "../../backup"
|
|
6
|
+
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Backup < Command
|
|
11
|
+
USAGE_LINE = "plastic backup [--list] [--json]"
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
options[:list] ? list : write
|
|
15
|
+
rescue Plastic::Backup::Missing => e
|
|
16
|
+
@output.next_step("plastic sync", because: "a backup copies the three databases, and sync builds them")
|
|
17
|
+
raise Failure, "no #{e.message} at #{scope.plastic_home}; run plastic sync"
|
|
18
|
+
rescue Sqlite::Error => e
|
|
19
|
+
raise Failure, e.message
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def switches(parser)
|
|
25
|
+
parser.on("--list") { options[:list] = true }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write
|
|
29
|
+
version = Version.new([], out: nil, err: nil, env: @env).found.last
|
|
30
|
+
@output.row("archive", Plastic::Backup.call(scope.plastic_home, version: version))
|
|
31
|
+
@output.next_step("plastic backup --list", because: "to restore, unpack the archive into an empty ~/.plastic and run plastic checkout")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def list
|
|
35
|
+
archives = Plastic::Backup.list(scope.plastic_home)
|
|
36
|
+
archives.each { |path| @output.row(File.basename(path), "#{File.size(path)} bytes #{File.mtime(path).strftime("%Y-%m-%d %H:%M")}") }
|
|
37
|
+
@output.row("result", "no backup yet") if archives.empty?
|
|
38
|
+
@output.next_step("plastic backup", because: "copying backups/ off this machine is your choice; Plastic sends nothing")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../../store_sync"
|
|
5
|
+
|
|
6
|
+
module Plastic
|
|
7
|
+
class CLI
|
|
8
|
+
module Commands
|
|
9
|
+
class Checkout < Command
|
|
10
|
+
USAGE_LINE = "plastic checkout [--json]"
|
|
11
|
+
|
|
12
|
+
def call
|
|
13
|
+
home = scope.plastic_home
|
|
14
|
+
raise Failure, "no databases at #{home}; run plastic sync" unless File.exist?(ReferenceArchive.path(home))
|
|
15
|
+
|
|
16
|
+
restored = StoreSync.checkout(home)
|
|
17
|
+
@output.row("restored", restored.empty? ? "nothing was missing" : restored)
|
|
18
|
+
@output.next_step("plastic sync --dry-run", because: "checkout restores missing files only; a changed file is never overwritten")
|
|
19
|
+
rescue Sqlite::Error => e
|
|
20
|
+
raise Failure, e.message
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../frontier"
|
|
5
|
+
require_relative "../scope"
|
|
6
|
+
|
|
7
|
+
# `plastic continue` - where one project stands and what runs next: the store it
|
|
8
|
+
# uses, the intents it has open, its liveliest roadmap and that roadmap's
|
|
9
|
+
# frontier, then the step to take and the rule behind it.
|
|
10
|
+
#
|
|
11
|
+
# With no roadmap the first active intent answers instead, so a project that
|
|
12
|
+
# never grew a roadmap still gets a next step.
|
|
13
|
+
module Plastic
|
|
14
|
+
class CLI
|
|
15
|
+
module Commands
|
|
16
|
+
class Continue < Command
|
|
17
|
+
USAGE_LINE = "plastic continue [--project SLUG] [--json]"
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
@output.row("project", scope.slug)
|
|
21
|
+
@output.row("root", scope.root)
|
|
22
|
+
@output.row("active", active)
|
|
23
|
+
@output.row("roadmap", roadmap)
|
|
24
|
+
command, because = decision
|
|
25
|
+
@output.next_step(command, because: because)
|
|
26
|
+
rescue RoadmapSavepoint::MissingGroupingHeading => e
|
|
27
|
+
raise Failure, e.message
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def switches(parser)
|
|
33
|
+
parser.on("--project SLUG") { |slug| @options[:project] = slug }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def active
|
|
37
|
+
scope.active_entries.map { |id, title| "#{id} #{title}" }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def roadmap
|
|
41
|
+
return Frontier::NONE unless frontier.roadmap
|
|
42
|
+
|
|
43
|
+
"#{frontier.roadmap} frontier #{frontier.heading}"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def decision
|
|
47
|
+
return [frontier.next_step, frontier.because] if frontier.id
|
|
48
|
+
|
|
49
|
+
id = scope.active_ids.first
|
|
50
|
+
return [plan_for(id), "#{id} is the first active intent in #{scope.slug}"] if id
|
|
51
|
+
|
|
52
|
+
["plastic help", "#{scope.slug} has no active work and no roadmap"]
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def plan_for(id)
|
|
56
|
+
dir = scope.intent_dir(id)
|
|
57
|
+
dir ? "read #{File.join(dir, "plan.md")}" : "plastic status"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def frontier
|
|
61
|
+
@frontier ||= Frontier.new(scope)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "installer_verb"
|
|
4
|
+
|
|
5
|
+
# `plastic doctor` - diagnoses Plastic installation health. The work is still
|
|
6
|
+
# scripts/doctor.rb, run through Legacy; each finding prints its own repair.
|
|
7
|
+
module Plastic
|
|
8
|
+
class CLI
|
|
9
|
+
module Commands
|
|
10
|
+
class Doctor < InstallerVerb
|
|
11
|
+
USAGE_LINE = "plastic doctor [--core] [--store WHICH]"
|
|
12
|
+
FLAGS = %w[--core --store].freeze
|
|
13
|
+
|
|
14
|
+
SCRIPT = "doctor.rb"
|
|
15
|
+
AFTER = "none"
|
|
16
|
+
BECAUSE = "the findings are the whole answer, and each one names its own repair"
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../command"
|
|
4
|
+
require_relative "../legacy"
|
|
5
|
+
|
|
6
|
+
# `plastic feedback "TITLE"` - files a report with feedback-report, the body
|
|
7
|
+
# read from standard input and passed to the child process unchanged.
|
|
8
|
+
module Plastic
|
|
9
|
+
class CLI
|
|
10
|
+
module Commands
|
|
11
|
+
class Feedback < Command
|
|
12
|
+
USAGE_LINE = 'plastic feedback "TITLE"'
|
|
13
|
+
|
|
14
|
+
def initialize(argv, runner: nil, **streams)
|
|
15
|
+
super(argv, **streams)
|
|
16
|
+
@runner = runner
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
raise Usage, "plastic feedback needs a title" if title.nil? || title.strip.empty?
|
|
21
|
+
|
|
22
|
+
status = legacy.run("feedback-report", "--title", title)
|
|
23
|
+
raise Failure, "feedback-report exited #{status}" unless status.zero?
|
|
24
|
+
|
|
25
|
+
@output.next_step("none", because: "the report is saved as a draft; open the printed URL to send it")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def title
|
|
31
|
+
arguments.first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def legacy
|
|
35
|
+
@legacy ||= Legacy.new(env: @env, runner: @runner)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|