@zalom/plastic 1.0.0-beta.9 → 1.0.0
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-reference.md +133 -0
- package/PLASTIC.md +398 -82
- package/README.md +99 -82
- package/agents/plastic-brainstorming.md +12 -9
- package/agents/plastic-enforcer.md +52 -12
- package/agents/plastic-executor.md +10 -10
- package/agents/plastic-future-intent-researcher.md +4 -8
- package/agents/plastic-intent-curator.md +12 -14
- package/agents/plastic-intent-discovery.md +35 -0
- package/agents/plastic-planner.md +15 -10
- package/agents/plastic-spec-specialist.md +15 -10
- package/bin/plastic.js +7 -3
- package/hooks/code-gate +10 -1
- package/hooks/hooks.json +28 -3
- package/hooks/lock-gate +21 -0
- package/hooks/retrieval-gate +10 -0
- package/hooks/savepoint-pre +10 -0
- package/hooks/statusline +48 -6
- package/package.json +2 -1
- package/scripts/agent-report +21 -0
- package/scripts/dashboard.rb +98 -23
- package/scripts/doctor.rb +232 -5
- package/scripts/hook-auto-arm +1 -1
- package/scripts/hook-bash-gate +22 -4
- package/scripts/hook-code-gate +11 -6
- package/scripts/hook-continue +17 -0
- package/scripts/hook-create-gate +45 -9
- package/scripts/hook-gate-check +24 -23
- package/scripts/hook-lock-gate +83 -0
- package/scripts/hook-retrieval-gate +148 -0
- package/scripts/hook-savepoint-pre +32 -0
- package/scripts/hook-session-start +1 -1
- package/scripts/insight-append +51 -0
- package/scripts/install.rb +44 -7
- package/scripts/lib/agent_models.rb +43 -0
- package/scripts/lib/bridge.rb +924 -84
- package/scripts/lib/dashboard_banner.rb +42 -0
- package/scripts/lib/hook_registry.rb +95 -0
- package/scripts/lib/insights.rb +86 -0
- package/scripts/lib/installer_core.rb +100 -60
- package/scripts/lib/link_suggestions.rb +319 -0
- package/scripts/lib/lock.rb +375 -0
- package/scripts/lib/power_tools.rb +22 -21
- package/scripts/lib/preflight.rb +79 -0
- package/scripts/lib/qmd_sync.rb +15 -0
- package/scripts/lib/retrieval_gate.rb +211 -0
- package/scripts/lib/worktree.rb +384 -0
- package/scripts/link-suggest +213 -0
- package/scripts/new-intent +9 -1
- package/scripts/plastic-lock +164 -0
- package/scripts/read-config +4 -0
- package/scripts/spawn-preamble +11 -3
- package/scripts/update.rb +16 -7
- package/skills/auto/SKILL.md +126 -19
- package/skills/auto/references/agent-architecture.md +7 -4
- package/skills/auto/references/agent-report-contract.md +36 -1
- package/skills/auto/references/end-tail.md +56 -0
- package/skills/auto/references/human-report-contract.md +55 -0
- package/skills/auto/references/tiers.md +77 -0
- package/skills/brainstorming/SKILL.md +7 -34
- package/skills/brainstorming/references/design-principles.md +49 -0
- package/skills/continuing/SKILL.md +26 -7
- package/skills/creating-intent/SKILL.md +13 -28
- package/skills/creating-project/SKILL.md +11 -74
- package/skills/creating-project/references/project-scaffolding.md +97 -0
- package/skills/creating-skills/SKILL.md +65 -0
- package/skills/creating-skills/evals/evals.json +108 -0
- package/skills/creating-skills/references/agents.md +168 -0
- package/skills/creating-skills/references/evals.md +41 -0
- package/skills/creating-skills/references/hooks.md +248 -0
- package/skills/creating-skills/references/progressive-disclosure.md +176 -0
- package/skills/creating-skills/references/scripts.md +166 -0
- package/skills/creating-skills/references/skills.md +165 -0
- package/skills/creating-skills/scripts/scaffold.rb +313 -0
- package/skills/dashboard/SKILL.md +13 -11
- package/skills/dashboard/references/classification.md +22 -0
- package/skills/doctor/SKILL.md +10 -7
- package/skills/executing-plan/SKILL.md +4 -4
- package/skills/humanizer/SKILL.md +39 -0
- package/skills/humanizer/evals/evals.json +70 -0
- package/skills/humanizer/references/always-on-snippet.md +9 -0
- package/skills/humanizer/references/examples.md +48 -0
- package/skills/install/SKILL.md +75 -84
- package/skills/intent-curator/SKILL.md +5 -1
- package/skills/intent-discovery/SKILL.md +47 -0
- package/skills/intent-starting/SKILL.md +130 -0
- package/skills/intent-starting/evals/evals.json +117 -0
- package/skills/intent-starting/references/boarding-matrix.md +35 -0
- package/skills/linking-intents/SKILL.md +48 -12
- package/skills/lock/SKILL.md +41 -0
- package/skills/managing-index/SKILL.md +6 -0
- package/skills/releasing/SKILL.md +44 -29
- package/skills/releasing/references/promotion-and-tagging.md +60 -0
- package/skills/roadmap/SKILL.md +51 -0
- package/skills/roadmap/references/file-format.md +91 -0
- package/skills/roadmap/references/operations.md +78 -0
- package/skills/uninstall/SKILL.md +29 -11
- package/skills/update/SKILL.md +34 -23
- package/skills/versions/SKILL.md +27 -12
- package/skills/writing-plans/SKILL.md +10 -88
- package/skills/writing-plans/references/plan-format.md +102 -0
- package/templates/config.yml +8 -0
- package/templates/outcome.md +3 -0
- package/templates/revisions.md +58 -0
- package/templates/roadmap.md +30 -0
- package/skills/writing-instructions/SKILL.md +0 -159
- package/skills/writing-instructions/references/agentskills-spec.md +0 -135
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# PreToolUse retrieval gate (intent 84, Lever 2; operation-based redesign 89a;
|
|
6
|
+
# ADVISORY since intent 108, D8). Reads the tool call (JSON on stdin: tool_name +
|
|
7
|
+
# tool_input), computes capabilities (QMD detect + freshness), and delegates the
|
|
8
|
+
# decision to RetrievalGate. It NEVER blocks: reads and searches always run
|
|
9
|
+
# (exit 0). A store content search that QMD could serve better gets a QMD hint
|
|
10
|
+
# via PreToolUse additionalContext on stdout. Hard gates guard writes, locks,
|
|
11
|
+
# and structure; never reads.
|
|
12
|
+
# Fail-open: any parse error, timeout, or unexpected exception exits 0. On the
|
|
13
|
+
# STALE QMD path RetrievalGate fires QmdSync.reindex_async (NEVER synchronous).
|
|
14
|
+
# Binds subagents (PreToolUse hooks apply to subagent tool calls too).
|
|
15
|
+
#
|
|
16
|
+
# Only CONTENT SEARCH over store markdown gets the hint; reads and structural
|
|
17
|
+
# ops stay silent. Code navigation is a soft prompt recommendation
|
|
18
|
+
# (UserPromptSubmit power-tools), not handled here.
|
|
19
|
+
#
|
|
20
|
+
# Scope: only the agent's own Bash/Read/Grep/Glob calls. Ruby `File.read` inside
|
|
21
|
+
# scripts is invisible to a PreToolUse hook and is out of scope (no exemptions).
|
|
22
|
+
#
|
|
23
|
+
# ARGV[0] is plastic_home (passed by the launcher), like hook-qmd-search.
|
|
24
|
+
|
|
25
|
+
require "json"
|
|
26
|
+
require "timeout"
|
|
27
|
+
require_relative "lib/retrieval_gate"
|
|
28
|
+
require_relative "lib/qmd_sync"
|
|
29
|
+
|
|
30
|
+
module RetrievalGateHook
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Pure-ish core: capabilities and reindex are injected so this is unit-testable
|
|
34
|
+
# with no real qmd. Returns [exit_code, stderr_string, stdout_json].
|
|
35
|
+
# stdin: raw PreToolUse JSON
|
|
36
|
+
# capabilities: { qmd:, qmd_fresh: }
|
|
37
|
+
# reindex: callable fired on the STALE path
|
|
38
|
+
def run(stdin:, plastic_home:, cwd:, capabilities:, reindex: -> {})
|
|
39
|
+
payload = parse(stdin)
|
|
40
|
+
return [0, nil, nil] unless payload
|
|
41
|
+
|
|
42
|
+
tool_name = payload["tool_name"].to_s
|
|
43
|
+
tool_input = payload["tool_input"]
|
|
44
|
+
tool_input = {} unless tool_input.is_a?(Hash)
|
|
45
|
+
|
|
46
|
+
bypassed = false
|
|
47
|
+
reason = RetrievalGate.decision(
|
|
48
|
+
tool_name: tool_name, tool_input: tool_input,
|
|
49
|
+
plastic_home: plastic_home, cwd: cwd,
|
|
50
|
+
capabilities: capabilities, reindex: reindex
|
|
51
|
+
) { |_sig| bypassed = true }
|
|
52
|
+
|
|
53
|
+
if reason
|
|
54
|
+
# Advisory (intent 108, D8): reads and searches are never denied. The
|
|
55
|
+
# would-block reason becomes a hint the agent sees alongside the result.
|
|
56
|
+
hint = JSON.generate(
|
|
57
|
+
"hookSpecificOutput" => {
|
|
58
|
+
"hookEventName" => "PreToolUse",
|
|
59
|
+
"additionalContext" =>
|
|
60
|
+
"PLASTIC advisory: #{reason} (this search ran; the hint is not a block)"
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
[0, nil, hint]
|
|
64
|
+
else
|
|
65
|
+
# The `# qmd-ok` bypass token is still accepted (bypassed is set), but
|
|
66
|
+
# with nothing to bypass it no longer announces itself.
|
|
67
|
+
[0, nil, nil]
|
|
68
|
+
end
|
|
69
|
+
rescue StandardError
|
|
70
|
+
[0, nil, nil] # fail-open
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def parse(raw)
|
|
74
|
+
data = JSON.parse(raw.to_s)
|
|
75
|
+
data.is_a?(Hash) ? data : nil
|
|
76
|
+
rescue StandardError
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Detect real capabilities for the live executable. Probes are injected so the
|
|
81
|
+
# three-tier QMD failure model is unit-testable:
|
|
82
|
+
# - absent : QMD not on PATH -> allow, no warn.
|
|
83
|
+
# - broken : QMD present but freshness probe -> tier-b: WARN once, then treat
|
|
84
|
+
# raises/times out as absent this turn (allow).
|
|
85
|
+
# - fresh : probe returns truthy -> gate is live.
|
|
86
|
+
# A slow `qmd status` cannot stall a tool call: a 2s Timeout bounds the probe.
|
|
87
|
+
def detect_capabilities(cwd:,
|
|
88
|
+
detect: -> { QmdSync.detect },
|
|
89
|
+
fresh: -> { QmdSync.fresh? },
|
|
90
|
+
warn: ->(m) { $stderr.puts(m) })
|
|
91
|
+
qmd = detect.call
|
|
92
|
+
qmd_fresh = false
|
|
93
|
+
if qmd
|
|
94
|
+
begin
|
|
95
|
+
qmd_fresh = Timeout.timeout(2) { fresh.call }
|
|
96
|
+
rescue StandardError
|
|
97
|
+
# Tier-b: QMD is present but its freshness probe broke/stalled. Distinct
|
|
98
|
+
# from QMD being absent — warn so a degraded QMD is visible, then fail open
|
|
99
|
+
# (allow this turn, no reindex).
|
|
100
|
+
warn.call("PLASTIC GATE — QMD is present but its freshness probe failed; " \
|
|
101
|
+
"allowing this turn without routing search to QMD (check qmd).")
|
|
102
|
+
qmd = false
|
|
103
|
+
qmd_fresh = false
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
{ qmd: qmd, qmd_fresh: qmd_fresh }
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Best-effort reindex callable for the STALE path. Resolves the collection from
|
|
110
|
+
# cwd (project + global) and fires the async reindexer for each; never raises.
|
|
111
|
+
def reindex_for(cwd:, plastic_home:)
|
|
112
|
+
lambda do
|
|
113
|
+
begin
|
|
114
|
+
cols = QmdSync.collections_for_cwd(cwd, plastic_home: plastic_home)
|
|
115
|
+
cols.each { |c| QmdSync.reindex_async(collection: c) }
|
|
116
|
+
rescue StandardError
|
|
117
|
+
# non-fatal; the read is already allowed this turn
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# --- executable entrypoint ---
|
|
124
|
+
if $PROGRAM_NAME == __FILE__
|
|
125
|
+
raw = begin
|
|
126
|
+
$stdin.read
|
|
127
|
+
rescue StandardError
|
|
128
|
+
""
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
plastic_home = (ARGV[0] && !ARGV[0].empty?) ? ARGV[0] : File.expand_path("~/.plastic")
|
|
132
|
+
cwd = Dir.pwd
|
|
133
|
+
|
|
134
|
+
code, err, out = begin
|
|
135
|
+
caps = RetrievalGateHook.detect_capabilities(cwd: cwd)
|
|
136
|
+
RetrievalGateHook.run(
|
|
137
|
+
stdin: raw, plastic_home: plastic_home, cwd: cwd,
|
|
138
|
+
capabilities: caps,
|
|
139
|
+
reindex: RetrievalGateHook.reindex_for(cwd: cwd, plastic_home: plastic_home)
|
|
140
|
+
)
|
|
141
|
+
rescue StandardError
|
|
142
|
+
[0, nil, nil] # fail-open at the outermost boundary too
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
$stderr.puts(err) if err && !err.empty?
|
|
146
|
+
print out if out && !out.empty?
|
|
147
|
+
exit code
|
|
148
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
# Usage: hook-savepoint-pre <file_path>
|
|
5
|
+
#
|
|
6
|
+
# PreToolUse savepoint trigger (intent 81). When a stage-opening lifecycle file
|
|
7
|
+
# (spec.md => Why, plan.md => How) is ABOUT to be written into an intent dir,
|
|
8
|
+
# append the pre-stage `started` ledger line so the ledger records "this stage
|
|
9
|
+
# was entered" before its artifact lands.
|
|
10
|
+
#
|
|
11
|
+
# Like the PostToolUse decoupled savepoint write (intent 52), it is derived from
|
|
12
|
+
# the file path alone: no bridge, no session, fires headless. It NEVER blocks a
|
|
13
|
+
# write: any non-match or failure exits 0. The append is idempotent and only
|
|
14
|
+
# fires while the stage is genuinely starting (the artifact is not yet a real,
|
|
15
|
+
# non-placeholder file), so re-edits add nothing.
|
|
16
|
+
|
|
17
|
+
require_relative "lib/bridge"
|
|
18
|
+
|
|
19
|
+
file_path = ARGV[0]
|
|
20
|
+
exit 0 if file_path.nil? || file_path.empty?
|
|
21
|
+
|
|
22
|
+
file_path_abs = File.expand_path(file_path)
|
|
23
|
+
intent_dir = Bridge.intent_dir_for(file_path_abs)
|
|
24
|
+
exit 0 unless intent_dir
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
Bridge.append_started_savepoint(intent_dir, file_path_abs)
|
|
28
|
+
rescue StandardError
|
|
29
|
+
# best-effort; the ledger is rebuildable and the post line still lands
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
exit 0
|
|
@@ -48,7 +48,7 @@ bridge_data = nil
|
|
|
48
48
|
if active.length == 1 && active.first =~ /store\/([\w-]+)\//
|
|
49
49
|
dir_name = $1
|
|
50
50
|
intent_dir = "#{store_root}/store/#{dir_name}"
|
|
51
|
-
session = ENV["
|
|
51
|
+
session = ENV["CLAUDE_CODE_SESSION_ID"] || Process.pid.to_s
|
|
52
52
|
intent_id = dir_name.split("--").first
|
|
53
53
|
intent_name = active.first[/\[([^\]]+)\]/, 1] || "unknown"
|
|
54
54
|
bridge_data = Bridge.derive(session, intent_id: intent_id, intent_dir: intent_dir, store: store_root, name: intent_name)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# insight-append - the blessed write path for an intent's `## Insights` section
|
|
6
|
+
# (intent 82). A thin CLI wrapper over Insights.append_insight: it formats the
|
|
7
|
+
# `{utc-iso8601} · {stage} · {author}` prefix, validates it, and appends one
|
|
8
|
+
# entry at the bottom of the section (creating the section or file if absent).
|
|
9
|
+
#
|
|
10
|
+
# Sibling to scripts/spawn-preamble and scripts/agent-report. The library's
|
|
11
|
+
# `now:` seam is the test seam; the CLI uses the default Time.now, which is fine
|
|
12
|
+
# because determinism is covered at the library level (test/insights_test.rb).
|
|
13
|
+
#
|
|
14
|
+
# Usage:
|
|
15
|
+
# insight-append <intent_dir> <text> --stage S --author A
|
|
16
|
+
#
|
|
17
|
+
# Exit codes: 0 (entry appended), 2 (usage).
|
|
18
|
+
|
|
19
|
+
require_relative "lib/insights"
|
|
20
|
+
|
|
21
|
+
def parse_args(argv)
|
|
22
|
+
stage = nil
|
|
23
|
+
author = nil
|
|
24
|
+
positional = []
|
|
25
|
+
i = 0
|
|
26
|
+
while i < argv.length
|
|
27
|
+
case argv[i]
|
|
28
|
+
when "--stage"
|
|
29
|
+
stage = argv[i + 1]
|
|
30
|
+
i += 2
|
|
31
|
+
when "--author"
|
|
32
|
+
author = argv[i + 1]
|
|
33
|
+
i += 2
|
|
34
|
+
else
|
|
35
|
+
positional << argv[i]
|
|
36
|
+
i += 1
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
[positional[0], positional[1], stage, author]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
intent_dir, text, stage, author = parse_args(ARGV)
|
|
43
|
+
|
|
44
|
+
if [intent_dir, text, stage, author].any? { |v| v.nil? || v.to_s.empty? }
|
|
45
|
+
warn "usage: insight-append <intent_dir> <text> --stage S --author A"
|
|
46
|
+
exit 2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
entry = Insights.append_insight(File.expand_path(intent_dir), text,
|
|
50
|
+
stage: stage, author: author)
|
|
51
|
+
puts "appended: #{entry}"
|
package/scripts/install.rb
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
# so install.rb is the single file-syncer; the ledger action is contextual.
|
|
15
15
|
|
|
16
16
|
require_relative "lib/installer_core"
|
|
17
|
+
require_relative "lib/preflight"
|
|
17
18
|
|
|
18
19
|
class Install < InstallerCore
|
|
19
20
|
def cli(argv = ARGV)
|
|
@@ -22,6 +23,9 @@ class Install < InstallerCore
|
|
|
22
23
|
return 0
|
|
23
24
|
end
|
|
24
25
|
|
|
26
|
+
gate = preflight_gate
|
|
27
|
+
return gate unless gate.zero?
|
|
28
|
+
|
|
25
29
|
force = argv.include?("--force")
|
|
26
30
|
reinstall = argv.include?("--reinstall")
|
|
27
31
|
ledger_action = flag_value(argv, "--ledger-action")
|
|
@@ -42,19 +46,19 @@ class Install < InstallerCore
|
|
|
42
46
|
return 1
|
|
43
47
|
end
|
|
44
48
|
|
|
45
|
-
run(selected: selected, force: force, reinstall: reinstall, ledger_action: ledger_action)
|
|
49
|
+
run(selected: selected, force: force, reinstall: reinstall, ledger_action: ledger_action, argv: argv)
|
|
46
50
|
0
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
# Hermetic entrypoint (no prompting / no exit). Returns the per-agent results array.
|
|
50
|
-
def run(selected:, force: false, reinstall: false, ledger_action: nil)
|
|
54
|
+
def run(selected:, force: false, reinstall: false, ledger_action: nil, argv: ARGV, input: $stdin)
|
|
51
55
|
fresh = !installed?
|
|
52
56
|
mode = fresh ? :install : :update # :update here means "re-sync, skip bootstrap"
|
|
53
57
|
|
|
54
58
|
distribute(mode)
|
|
55
59
|
bootstrap if fresh
|
|
56
60
|
|
|
57
|
-
results = selected.map { |key| install_for_agent(key, force) }
|
|
61
|
+
results = selected.map { |key| install_for_agent(key, force, argv: argv, input: input, reinstall: reinstall) }
|
|
58
62
|
|
|
59
63
|
action = ledger_action || (fresh ? "install" : "reinstall")
|
|
60
64
|
ledger_append(version, action)
|
|
@@ -72,8 +76,37 @@ class Install < InstallerCore
|
|
|
72
76
|
File.exist?(path) ? File.read(path).strip : nil
|
|
73
77
|
end
|
|
74
78
|
|
|
79
|
+
# Injectable pre-flight gate: real probes as default args, printing to an
|
|
80
|
+
# injectable `out:` IO so this is hermetically testable via StringIO. Returns
|
|
81
|
+
# 1 (stop the install) when Ruby is missing/too-old, else 0.
|
|
82
|
+
def preflight_gate(ruby_version: RUBY_VERSION, node_version: node_probe, git_present: git_probe,
|
|
83
|
+
mise_present: mise_probe, out: $stderr)
|
|
84
|
+
result = Preflight.check(ruby_version: ruby_version, node_version: node_version,
|
|
85
|
+
git_present: git_present, mise_present: mise_present)
|
|
86
|
+
result[:messages].each { |message| out.puts(message) }
|
|
87
|
+
result[:fatal] ? 1 : 0
|
|
88
|
+
end
|
|
89
|
+
|
|
75
90
|
private
|
|
76
91
|
|
|
92
|
+
def node_probe
|
|
93
|
+
`node --version`.strip
|
|
94
|
+
rescue StandardError
|
|
95
|
+
""
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def git_probe
|
|
99
|
+
!`command -v git`.strip.empty?
|
|
100
|
+
rescue StandardError
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def mise_probe
|
|
105
|
+
!`command -v mise`.strip.empty?
|
|
106
|
+
rescue StandardError
|
|
107
|
+
false
|
|
108
|
+
end
|
|
109
|
+
|
|
77
110
|
def flag_value(argv, name)
|
|
78
111
|
i = argv.index(name)
|
|
79
112
|
return nil unless i && argv[i + 1]
|
|
@@ -96,7 +129,8 @@ class Install < InstallerCore
|
|
|
96
129
|
verb = mode == :reinstall ? "re-synced" : "installed"
|
|
97
130
|
puts "\n\u{2705} Plastic v#{version} #{verb}."
|
|
98
131
|
puts " Registered for: #{installed.map { |r| r[:agent] }.join(", ")}"
|
|
99
|
-
puts " Run /clear (or restart your agent) to pick up new conventions
|
|
132
|
+
puts " Run /clear (or restart your agent) to pick up new conventions."
|
|
133
|
+
puts " Next: read docs/guides/your-first-intent-in-10-minutes.md\n\n"
|
|
100
134
|
end
|
|
101
135
|
|
|
102
136
|
def show_help
|
|
@@ -119,9 +153,12 @@ class Install < InstallerCore
|
|
|
119
153
|
--alpha Alpha channel
|
|
120
154
|
|
|
121
155
|
Other options:
|
|
122
|
-
--reinstall
|
|
123
|
-
--force
|
|
124
|
-
|
|
156
|
+
--reinstall Re-sync core files for the installed version (repair). Store untouched.
|
|
157
|
+
--force Overwrite existing files without prompting
|
|
158
|
+
--statusline VALUE keep or plastic. If an existing statusline is found, this
|
|
159
|
+
skips the interactive prompt. Interactive sessions ask by
|
|
160
|
+
default; non-interactive sessions default to keep.
|
|
161
|
+
-h, --help Show this help
|
|
125
162
|
|
|
126
163
|
Notes:
|
|
127
164
|
Install is one-shot. If Plastic is already installed, use `update` to upgrade or
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Shared model-tier resolution for Plastic subagents (intent 116).
|
|
4
|
+
#
|
|
5
|
+
# TIER_DEFAULTS mirrors the shipped `agents/*.md` frontmatter so `read-config`
|
|
6
|
+
# can answer `agents.models.<basename>` with the built-in default. The installer
|
|
7
|
+
# does NOT use TIER_DEFAULTS: it applies only genuine config overrides via
|
|
8
|
+
# `override_map`, so shipped frontmatter with no override passes through
|
|
9
|
+
# unchanged.
|
|
10
|
+
module AgentModels
|
|
11
|
+
# Claude Code aliases only (never pinned ids, never Fable). Keys are the agent
|
|
12
|
+
# file basenames without the `.md` extension.
|
|
13
|
+
TIER_DEFAULTS = {
|
|
14
|
+
"plastic-enforcer" => "opus",
|
|
15
|
+
"plastic-brainstorming" => "opus",
|
|
16
|
+
"plastic-planner" => "opus",
|
|
17
|
+
"plastic-spec-specialist" => "sonnet",
|
|
18
|
+
"plastic-executor" => "sonnet",
|
|
19
|
+
"plastic-intent-curator" => "sonnet",
|
|
20
|
+
"plastic-future-intent-researcher" => "sonnet",
|
|
21
|
+
"plastic-intent-discovery" => "sonnet"
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
module_function
|
|
25
|
+
|
|
26
|
+
# Pull the `agents.models` sub-hash out of a loaded config hash, tolerating a
|
|
27
|
+
# missing or malformed shape. Returns a plain { basename => model } hash.
|
|
28
|
+
def models_section(config)
|
|
29
|
+
return {} unless config.is_a?(Hash)
|
|
30
|
+
agents = config["agents"]
|
|
31
|
+
return {} unless agents.is_a?(Hash)
|
|
32
|
+
section = agents["models"]
|
|
33
|
+
section.is_a?(Hash) ? section : {}
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Override map for the installer: global overrides overlaid by project
|
|
37
|
+
# overrides (project wins). Defaults are intentionally excluded. Unknown agent
|
|
38
|
+
# keys are carried through as-is; install_agents simply never matches them to a
|
|
39
|
+
# copied file, so they are ignored without raising.
|
|
40
|
+
def override_map(project_config: {}, global_config: {})
|
|
41
|
+
models_section(global_config).merge(models_section(project_config))
|
|
42
|
+
end
|
|
43
|
+
end
|