@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
|
@@ -10,37 +10,48 @@
|
|
|
10
10
|
module AgentModels
|
|
11
11
|
# Claude Code aliases only (never pinned ids, never Fable). Keys are the agent
|
|
12
12
|
# file basenames without the `.md` extension.
|
|
13
|
+
# The three per-kind node agents (intent 340b, G7c, n2), dispatched by
|
|
14
|
+
# HarnessAdapter::AGENT_TYPE_BY_KIND. work and research resolve the
|
|
15
|
+
# executor tier, verify the advisor tier, mirroring RunnerPolicy's
|
|
16
|
+
# model_role split (327 D12): work and research run on plastic-executor's
|
|
17
|
+
# own tier, and verify on the lifecycle advisor tier, never the cheap tier.
|
|
13
18
|
TIER_DEFAULTS = {
|
|
14
19
|
"plastic-enforcer" => "opus",
|
|
15
|
-
"plastic-executor" => "sonnet"
|
|
20
|
+
"plastic-executor" => "sonnet",
|
|
21
|
+
"plastic-node-work" => "sonnet",
|
|
22
|
+
"plastic-node-verify" => "opus",
|
|
23
|
+
"plastic-node-research" => "sonnet"
|
|
16
24
|
}.freeze
|
|
17
25
|
|
|
18
|
-
# The two
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# 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
|
|
22
29
|
# hard-wired identity: agents.models.claude.<name> (or the legacy flat form)
|
|
23
30
|
# overrides either one through the same install-time frontmatter rewrite
|
|
24
31
|
# every agent override uses. Neither is a lifecycle-stage role: never
|
|
25
|
-
# dispatched by the auto pipeline, not part of TIER_DEFAULTS.
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# Codex reasoning
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
# reasoning tier -> the deepest generally-safe effort (high, not the model-dependent
|
|
38
|
-
# xhigh); sonnet the mid execution tier -> medium; haiku the lightest -> low. minimal is
|
|
39
|
-
# 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.
|
|
40
44
|
EFFORT_BY_ALIAS = {
|
|
41
|
-
"opus" => "
|
|
45
|
+
"opus" => "medium",
|
|
42
46
|
"sonnet" => "medium",
|
|
43
|
-
"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"
|
|
44
55
|
}.freeze
|
|
45
56
|
|
|
46
57
|
# Codex model id per tier alias (intent 186). Codex has NO vendor alias layer: every model id
|
|
@@ -49,17 +60,26 @@ module AgentModels
|
|
|
49
60
|
# centralizing every id in ONE map: Plastic owns the alias, so per-role identity costs a single
|
|
50
61
|
# line to refresh on a Codex deprecation plus a Plastic release, and no per-role file carries a
|
|
51
62
|
# raw id. opus (deepest reasoning tier) -> the flagship Sol; sonnet (execution tier) -> the
|
|
52
|
-
# balanced Terra; haiku (lightest) -> the fast/cheap Luna.
|
|
53
|
-
#
|
|
54
|
-
#
|
|
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.
|
|
55
66
|
CODEX_MODEL_BY_ALIAS = {
|
|
56
67
|
"opus" => "gpt-5.6-sol",
|
|
57
68
|
"sonnet" => "gpt-5.6-terra",
|
|
58
69
|
"haiku" => "gpt-5.6-luna"
|
|
59
70
|
}.freeze
|
|
60
71
|
|
|
72
|
+
CODEX_MODEL_BY_AGENT = {
|
|
73
|
+
"plastic-primary-advisor" => "gpt-6-astra",
|
|
74
|
+
"plastic-secondary-advisor" => "gpt-6-astra"
|
|
75
|
+
}.freeze
|
|
76
|
+
|
|
61
77
|
module_function
|
|
62
78
|
|
|
79
|
+
def shipped_effort_for(agent)
|
|
80
|
+
SHIPPED_EFFORT_DEFAULTS.fetch(agent.to_s, DEFAULT_EFFORT)
|
|
81
|
+
end
|
|
82
|
+
|
|
63
83
|
# Pull { basename => model } out of a loaded config hash's `agents.models`
|
|
64
84
|
# section, scoped to `harness` ("claude" or "codex"), tolerating a missing or
|
|
65
85
|
# malformed shape. `agents.models` can mix two shapes: legacy FLAT scalar
|
|
@@ -93,6 +113,17 @@ module AgentModels
|
|
|
93
113
|
models_section(global_config, harness: harness).merge(models_section(project_config, harness: harness))
|
|
94
114
|
end
|
|
95
115
|
|
|
116
|
+
def effort_override_map(project_config: {}, global_config: {}, harness: "claude")
|
|
117
|
+
efforts_section(global_config, harness).merge(efforts_section(project_config, harness))
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def efforts_section(config, harness)
|
|
121
|
+
agents = config.is_a?(Hash) ? config["agents"] : nil
|
|
122
|
+
efforts = agents.is_a?(Hash) ? agents["efforts"] : nil
|
|
123
|
+
section = efforts.is_a?(Hash) ? efforts[harness] : nil
|
|
124
|
+
section.is_a?(Hash) ? section : {}
|
|
125
|
+
end
|
|
126
|
+
|
|
96
127
|
# The model_reasoning_effort for a Plastic tier alias, or nil for any value that is not
|
|
97
128
|
# one of the three shipped aliases (the caller treats nil as a literal Codex model id).
|
|
98
129
|
def effort_for(value)
|
|
@@ -104,4 +135,11 @@ module AgentModels
|
|
|
104
135
|
def codex_model_for(value)
|
|
105
136
|
CODEX_MODEL_BY_ALIAS[value.to_s]
|
|
106
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
|
|
107
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"
|
|
@@ -10,13 +11,14 @@ require_relative "savepoint"
|
|
|
10
11
|
|
|
11
12
|
# Arm - how an auto team takes an intent and gives it back (intent 307).
|
|
12
13
|
#
|
|
13
|
-
#
|
|
14
|
-
# directory
|
|
15
|
-
# the code lands
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
14
|
+
# Two durable facts make a delivery: the delivery lock in the intent
|
|
15
|
+
# directory says who delivers, and the code worktree under the project repo
|
|
16
|
+
# says where the code lands. A conversation session's tmp directory under
|
|
17
|
+
# the global store's `.tmp/` says only that a session started; it names no
|
|
18
|
+
# intent. This module carries the
|
|
19
|
+
# arm, disarm, and repair operations (intent 307); the pure INDEX/project-config
|
|
20
|
+
# helpers that once sat alongside them on a shared-helpers module now live on
|
|
21
|
+
# IndexEntry and ProjectConfig (intent 344).
|
|
20
22
|
#
|
|
21
23
|
# Pure and dependency-injected: every path and clock is an argument, every git
|
|
22
24
|
# call goes through an injected runner, and the only environment read is the
|
|
@@ -32,7 +34,7 @@ module Arm
|
|
|
32
34
|
end
|
|
33
35
|
|
|
34
36
|
# Deterministic, session-less key derived from the store and the intent id,
|
|
35
|
-
# for headless runs that carry no session id at all
|
|
37
|
+
# for headless runs that carry no session id at all.
|
|
36
38
|
def derive_key(store, intent_id)
|
|
37
39
|
"auto-" + Digest::SHA256.hexdigest("#{store}/#{intent_id}")[0, 10]
|
|
38
40
|
end
|
|
@@ -62,12 +64,12 @@ module Arm
|
|
|
62
64
|
end
|
|
63
65
|
|
|
64
66
|
def global_store(home)
|
|
65
|
-
File.join(File.expand_path(home), ".plastic"
|
|
67
|
+
Plastic::StoreLayout.global_store(File.join(File.expand_path(home), ".plastic"))
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
# The minimal hash Worktree.provision, release, and finish consume: the
|
|
69
71
|
# intent block plus, when asked, the derived worktree block.
|
|
70
|
-
def
|
|
72
|
+
def delivery(intent_dir:, home: Dir.home, with_worktree: true)
|
|
71
73
|
dir = File.expand_path(intent_dir)
|
|
72
74
|
data = {
|
|
73
75
|
"intent" => { "id" => intent_id_for(dir), "dir" => File.basename(dir), "store" => store_for(dir) },
|
|
@@ -94,78 +96,68 @@ module Arm
|
|
|
94
96
|
}
|
|
95
97
|
end
|
|
96
98
|
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
def read_pointer(session, home:)
|
|
105
|
-
path = pointer_path(session, home: home)
|
|
106
|
-
File.exist?(path) ? File.read(path).strip : nil
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
def write_pointer(session, value, home:)
|
|
99
|
+
# Owner rule 2026-08-31: has this session already started a conversation?
|
|
100
|
+
# A conversation session's tmp directory is made at boot under the global
|
|
101
|
+
# store's `.tmp/`; a dispatched or headless session (a derived key) never
|
|
102
|
+
# gets one. Reads only; rescues to false (fail open: a broken store must
|
|
103
|
+
# never block a legitimate delivery).
|
|
104
|
+
def started_session?(session, home:)
|
|
110
105
|
store = global_store(home)
|
|
111
|
-
SessionLedger.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
File.write(path, "#{value}\n")
|
|
115
|
-
path
|
|
106
|
+
Dir.exist?(SessionLedger.session_tmp_dir(store, SessionLedger.short_session_id(nil, session)))
|
|
107
|
+
rescue StandardError
|
|
108
|
+
false
|
|
116
109
|
end
|
|
117
110
|
|
|
118
111
|
# --- arm ---------------------------------------------------------------------
|
|
119
112
|
|
|
120
113
|
# Take an intent for `session`: acquire delivery.lock (stamped with the run
|
|
121
|
-
# mode and the provenance the caller knows)
|
|
122
|
-
# (fail-open for a global-only or non-git intent)
|
|
123
|
-
#
|
|
124
|
-
#
|
|
125
|
-
#
|
|
114
|
+
# mode and the provenance the caller knows) and provision the code worktree
|
|
115
|
+
# (fail-open for a global-only or non-git intent). Returns
|
|
116
|
+
# `{status:, lock:, worktree:, session:}`. A held, stale, excluded, or
|
|
117
|
+
# corrupt lock returns that status with the lock data read and touches
|
|
118
|
+
# nothing.
|
|
126
119
|
def arm(intent_dir:, session:, mode: "auto", home: Dir.home, harness: nil,
|
|
127
120
|
agent: nil, model: nil, thread: nil, now: Time.now, runner: Worktree::ShellRunner.new,
|
|
128
|
-
host: Socket.gethostname)
|
|
121
|
+
host: Socket.gethostname, allow_inline: false)
|
|
129
122
|
raise ArgumentError, "mode must be auto or guided" unless %w[auto guided].include?(mode.to_s)
|
|
130
123
|
dir = File.expand_path(intent_dir)
|
|
131
124
|
key = resolve_session(session, store: store_for(dir), intent_id: intent_id_for(dir))
|
|
132
125
|
h = home_for(dir, home: home)
|
|
133
126
|
|
|
127
|
+
# Owner rule 2026-08-31: the main session never delivers an intent inline.
|
|
128
|
+
# A conversation session (its tmp directory already exists under the
|
|
129
|
+
# global store) arming an intent it does not already hold the lock for is
|
|
130
|
+
# inline delivery and is refused BEFORE any lock is taken. Re-arming the
|
|
131
|
+
# intent it already holds stays idempotent. A dispatched or headless
|
|
132
|
+
# session has no tmp directory and arms freely. --allow-inline is the
|
|
133
|
+
# explicit owner override.
|
|
134
|
+
if !allow_inline && started_session?(key, home: h) && !Lock.holds?(dir, session: key)
|
|
135
|
+
return { status: :inline_refused, lock: nil, worktree: nil, session: key }
|
|
136
|
+
end
|
|
137
|
+
|
|
134
138
|
status, lock = Lock.acquire(dir, session: key, host: host, now: now,
|
|
135
139
|
harness: harness, agent: agent, model: model,
|
|
136
140
|
thread: thread, run_mode: mode.to_s)
|
|
137
141
|
unless %i[acquired owned].include?(status)
|
|
138
|
-
return { status: status, lock: lock, worktree: nil, session: key
|
|
142
|
+
return { status: status, lock: lock, worktree: nil, session: key }
|
|
139
143
|
end
|
|
140
144
|
|
|
141
|
-
data =
|
|
145
|
+
data = delivery(intent_dir: dir, home: h, with_worktree: false)
|
|
142
146
|
begin
|
|
143
147
|
Worktree.provision(data, home: h, runner: runner)
|
|
144
148
|
rescue StandardError => e
|
|
145
149
|
warn "plastic: worktree provision raised, continuing unprovisioned: #{e.message}"
|
|
146
150
|
end
|
|
147
151
|
|
|
148
|
-
# The pointer is what the capture and record hooks read; they key on the
|
|
149
|
-
# harness's real session id, so a derived key gets no pointer (nothing would
|
|
150
|
-
# ever read it). A pointer failure warns and never undoes an acquired lock.
|
|
151
|
-
pointer = nil
|
|
152
|
-
unless key == derive_key(store_for(dir), intent_id_for(dir))
|
|
153
|
-
begin
|
|
154
|
-
pointer = write_pointer(key, intent_id_for(dir), home: h)
|
|
155
|
-
rescue StandardError => e
|
|
156
|
-
warn "plastic: session pointer not written (#{e.message}); the lock is held regardless"
|
|
157
|
-
end
|
|
158
|
-
end
|
|
159
152
|
{ status: status, lock: lock, worktree: worktree_block(intent_dir: dir, home: h),
|
|
160
|
-
session: key
|
|
153
|
+
session: key }
|
|
161
154
|
end
|
|
162
155
|
|
|
163
156
|
# --- disarm ------------------------------------------------------------------
|
|
164
157
|
|
|
165
|
-
# Give the intent back: remove the worktree (when `remove`)
|
|
166
|
-
# lock as its recorded owner (falling back to `session`)
|
|
167
|
-
#
|
|
168
|
-
# the lock release status (:released, :none, :not_owner, or :raised).
|
|
158
|
+
# Give the intent back: remove the worktree (when `remove`) and release the
|
|
159
|
+
# lock as its recorded owner (falling back to `session`). Returns the lock
|
|
160
|
+
# release status (:released, :none, :not_owner, or :raised).
|
|
169
161
|
def disarm(intent_dir:, session:, home: Dir.home, runner: Worktree::ShellRunner.new,
|
|
170
162
|
remove: true, now: Time.now)
|
|
171
163
|
dir = File.expand_path(intent_dir)
|
|
@@ -173,39 +165,24 @@ module Arm
|
|
|
173
165
|
key = resolve_session(session, store: store_for(dir), intent_id: intent_id_for(dir))
|
|
174
166
|
|
|
175
167
|
begin
|
|
176
|
-
Worktree.release(
|
|
168
|
+
Worktree.release(delivery(intent_dir: dir, home: h), home: h, runner: runner, remove: remove)
|
|
177
169
|
rescue StandardError => e
|
|
178
170
|
warn "plastic: worktree release raised, continuing: #{e.message}"
|
|
179
171
|
end
|
|
180
172
|
|
|
181
173
|
lock = Lock.read(dir)
|
|
182
174
|
owner = lock && !blank?(lock["owner_session"]) ? lock["owner_session"] : key
|
|
183
|
-
|
|
175
|
+
begin
|
|
184
176
|
Lock.release(dir, session: owner)
|
|
185
177
|
rescue StandardError => e
|
|
186
178
|
warn "plastic: delivery lock release raised for #{dir}, continuing: #{e.message}"
|
|
187
179
|
:raised
|
|
188
180
|
end
|
|
189
|
-
|
|
190
|
-
begin
|
|
191
|
-
reset_pointer(key, intent_id_for(dir), home: h, now: now)
|
|
192
|
-
rescue StandardError => e
|
|
193
|
-
warn "plastic: session pointer not reset (#{e.message})"
|
|
194
|
-
end
|
|
195
|
-
release_status
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
# Point the session back at the day ledger, but only when it names `intent_id`.
|
|
199
|
-
def reset_pointer(session, intent_id, home:, now: Time.now)
|
|
200
|
-
current = read_pointer(session, home: home)
|
|
201
|
-
return false unless current == intent_id.to_s
|
|
202
|
-
write_pointer(session, SessionLedger.day_id(now), home: home)
|
|
203
|
-
true
|
|
204
181
|
end
|
|
205
182
|
|
|
206
183
|
# --- repair ------------------------------------------------------------------
|
|
207
184
|
|
|
208
|
-
# One idempotent repair (the lock half of the
|
|
185
|
+
# One idempotent repair (the lock half of the repair path):
|
|
209
186
|
# remove a corrupt lock, back off from a fresh foreign lock (`held`), report
|
|
210
187
|
# a stale foreign lock (`stale`) for the explicit reclaim verb, keep and
|
|
211
188
|
# enrich an own lock, heartbeat a delegated one, acquire when none, and
|
|
@@ -259,7 +236,7 @@ module Arm
|
|
|
259
236
|
end
|
|
260
237
|
|
|
261
238
|
begin
|
|
262
|
-
Worktree.provision(
|
|
239
|
+
Worktree.provision(delivery(intent_dir: dir, home: h, with_worktree: false), home: h, runner: runner)
|
|
263
240
|
rescue StandardError => e
|
|
264
241
|
warn "plastic: worktree provision raised during repair, continuing unprovisioned: #{e.message}"
|
|
265
242
|
end
|
|
@@ -268,16 +245,4 @@ module Arm
|
|
|
268
245
|
|
|
269
246
|
{ "status" => "repaired", "actions" => actions, "lock" => lock_data, "session" => key }
|
|
270
247
|
end
|
|
271
|
-
|
|
272
|
-
# The intent directory this session's pointer names, searched in the given
|
|
273
|
-
# stores in order; nil when the pointer is absent, empty, or a day id.
|
|
274
|
-
def intent_dir_from_pointer(session, home:, stores:)
|
|
275
|
-
current = read_pointer(session, home: home)
|
|
276
|
-
return nil if blank?(current) || SessionLedger.valid_day_id?(current)
|
|
277
|
-
stores.each do |store|
|
|
278
|
-
match = Dir.glob(File.join(store, "#{current}--*")).find { |p| File.directory?(p) }
|
|
279
|
-
return match if match
|
|
280
|
-
end
|
|
281
|
-
nil
|
|
282
|
-
end
|
|
283
248
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# AtomicWrite (intent 334, n2, D19r): a sibling-temp-plus-rename write, the
|
|
5
|
+
# same shape as Lock#write (scripts/lib/lock.rb) - a content write is never
|
|
6
|
+
# an in-place truncate, so a crash mid-write can never leave an empty or
|
|
7
|
+
# half-written target on disk. The temp file is a SIBLING in the same
|
|
8
|
+
# directory as the target, never a system tmpdir, because File.rename can
|
|
9
|
+
# raise EXDEV when the temp and the target live on different filesystems.
|
|
10
|
+
# The temp name ends in .lock (post-execution review, non-blocking 5), the
|
|
11
|
+
# same property Lock#write_temp_path documents: an orphan left by a crash
|
|
12
|
+
# between the write and the rename is covered by the store's existing
|
|
13
|
+
# *.lock gitignore rule rather than swept into its git add -A auto-commit.
|
|
14
|
+
#
|
|
15
|
+
# The renamer is injectable so the interrupted-write case (a rename that
|
|
16
|
+
# raises) is testable with dependency injection, never eval or a global
|
|
17
|
+
# (D19r).
|
|
18
|
+
module AtomicWrite
|
|
19
|
+
module_function
|
|
20
|
+
|
|
21
|
+
def write(path, content, renamer: File.method(:rename))
|
|
22
|
+
dir = File.dirname(path)
|
|
23
|
+
temp = File.join(dir, ".#{File.basename(path)}.tmp.#{Process.pid}.#{Time.now.to_f}.#{rand(0xFFFFFF)}.lock")
|
|
24
|
+
File.write(temp, content)
|
|
25
|
+
renamer.call(temp, path)
|
|
26
|
+
true
|
|
27
|
+
rescue StandardError
|
|
28
|
+
File.delete(temp) if temp && File.exist?(temp)
|
|
29
|
+
raise
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
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
|
+
require_relative "worktree"
|
|
10
|
+
|
|
11
|
+
module Plastic
|
|
12
|
+
module Backup
|
|
13
|
+
DATABASES = [SearchIndex::NAME, WorkGraph::NAME, ReferenceArchive::NAME].freeze
|
|
14
|
+
ROOT_FILES = %w[config.yml projects.yml INDEX.md].freeze
|
|
15
|
+
Missing = Class.new(StandardError)
|
|
16
|
+
|
|
17
|
+
def self.dir(home)
|
|
18
|
+
File.join(home, "backups")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def self.list(home)
|
|
22
|
+
Dir.glob(File.join(dir(home), "plastic-backup--*.tar.gz")).sort
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.call(home, version:, now: Time.now.utc)
|
|
26
|
+
missing = DATABASES.reject { |name| File.exist?(File.join(home, name)) }
|
|
27
|
+
raise Missing, missing.join(", ") unless missing.empty?
|
|
28
|
+
|
|
29
|
+
Worktree.ensure_gitignored(home, "backups/")
|
|
30
|
+
Dir.mktmpdir("plastic-backup") do |work|
|
|
31
|
+
DATABASES.each { |name| Sqlite.call(File.join(home, name), "VACUUM INTO #{Sqlite.quote(File.join(work, name))};", readonly: true) }
|
|
32
|
+
FileUtils.cp(ROOT_FILES.map { |name| File.join(home, name) }.select { |path| File.exist?(path) }, work)
|
|
33
|
+
File.write(File.join(work, "manifest.json"), JSON.pretty_generate(manifest(work, version, now)))
|
|
34
|
+
pack(work, File.join(dir(home), "plastic-backup--#{now.strftime("%Y%m%dT%H%M%SZ")}.tar.gz"))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.manifest(work, version, now)
|
|
39
|
+
files = Dir.children(work).sort.map do |name|
|
|
40
|
+
path = File.join(work, name)
|
|
41
|
+
{"name" => name, "bytes" => File.size(path), "sha256" => Digest::SHA256.file(path).hexdigest}
|
|
42
|
+
end
|
|
43
|
+
{"version" => version, "at" => now.strftime("%Y-%m-%dT%H:%M:%SZ"), "files" => files}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.pack(work, archive)
|
|
47
|
+
require "rubygems"
|
|
48
|
+
require "rubygems/package"
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(archive))
|
|
50
|
+
draft = "#{archive}.tmp"
|
|
51
|
+
Zlib::GzipWriter.open(draft) do |gzip|
|
|
52
|
+
Gem::Package::TarWriter.new(gzip) do |tar|
|
|
53
|
+
Dir.children(work).sort.each do |name|
|
|
54
|
+
path = File.join(work, name)
|
|
55
|
+
tar.add_file_simple(name, 0o644, File.size(path)) { |entry| IO.copy_stream(path, entry) }
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
File.rename(draft, archive)
|
|
60
|
+
archive
|
|
61
|
+
ensure
|
|
62
|
+
FileUtils.rm_f(draft.to_s)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
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.validate_scope
|
|
22
|
+
command.call
|
|
23
|
+
command.flush
|
|
24
|
+
OK
|
|
25
|
+
rescue OptionParser::ParseError, Scope::UnknownProject, Usage => e
|
|
26
|
+
command.output.usage(e.message, self::USAGE_LINE)
|
|
27
|
+
USAGE
|
|
28
|
+
rescue Refusal => e
|
|
29
|
+
command.output.refused(e.message)
|
|
30
|
+
REFUSED
|
|
31
|
+
rescue Failure => e
|
|
32
|
+
command.output.failed(e.message)
|
|
33
|
+
FAILED
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
attr_reader :output
|
|
37
|
+
|
|
38
|
+
def initialize(argv, out:, err:, env: ENV, home: Dir.home, directory: Dir.pwd)
|
|
39
|
+
@argv = argv.dup
|
|
40
|
+
@env = env
|
|
41
|
+
@home = home
|
|
42
|
+
@directory = directory
|
|
43
|
+
@output = Output.new(out: out, err: err, json: @argv.include?("--json"))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def call
|
|
47
|
+
raise NoMethodError, "#{self.class} must define call"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def validate_scope
|
|
51
|
+
scope.slug if @argv.grep(/\A--project(?:=|$)/).any?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def flush
|
|
55
|
+
@output.project = scope.slug
|
|
56
|
+
@output.flush(json: options[:json])
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def options
|
|
62
|
+
return @options if @options
|
|
63
|
+
|
|
64
|
+
@options = {}
|
|
65
|
+
parser.parse!(@argv)
|
|
66
|
+
@options
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def arguments
|
|
70
|
+
options
|
|
71
|
+
@argv
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def parser
|
|
75
|
+
@parser ||= OptionParser.new do |o|
|
|
76
|
+
o.banner = self.class::USAGE_LINE
|
|
77
|
+
o.on("--json") { @options[:json] = true }
|
|
78
|
+
o.on("--project SLUG") { |slug| @options[:project] = slug }
|
|
79
|
+
switches(o)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def switches(_parser)
|
|
84
|
+
nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def scope
|
|
88
|
+
@scope ||= Scope.new(env: @env, home: @home, slug: options[:project], directory: @directory)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
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
|