@zalom/plastic 1.0.0-beta.34 → 1.0.0-beta.35

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 CHANGED
@@ -213,12 +213,14 @@ run the main session on the best available thinking model (Fable, Opus, or whate
213
213
  them). This is advisory only: it changes no behavior and blocks nothing if ignored, and it
214
214
  concerns the human's main session, never a dispatched subagent.
215
215
 
216
- **`plastic-intent-discovery`.** The What-stage agent. It fires at intent activation, before the
217
- delivery lock is armed and Why begins: it reads the intent's `chain`/`sources` frontmatter,
218
- runs QMD-first discovery over completed predecessor work and related parked or future intents,
219
- and deposits findings to `resources/discovery--<slug>.md` in the intent directory ONLY. It
220
- never writes the intent file, `spec.md`, or any other lifecycle deliverable; the Why-stage
221
- `plastic-brainstorming` agent reads its deposit and enriches `## Context`.
216
+ **`plastic-intent-discovery`.** The What-stage agent. It fires at intent activation, after the
217
+ delivery lock is armed and before Why begins, running under that lock as the owner session (it
218
+ does not acquire the lock itself and is not blocked by it): it reads the intent's
219
+ `chain`/`sources` frontmatter, runs QMD-first discovery over completed predecessor work and
220
+ related parked or future intents, and deposits findings to `resources/discovery--<slug>.md` in
221
+ the intent directory ONLY. It never writes the intent file, `spec.md`, or any other lifecycle
222
+ deliverable; the Why-stage `plastic-brainstorming` agent reads its deposit and enriches
223
+ `## Context`.
222
224
 
223
225
  `savepoint.md` — a deterministic, append-only ledger of cycle-step milestones (one line per
224
226
  lifecycle boundary, newest at the bottom), written automatically by the gate hook. It is
@@ -7,13 +7,14 @@ description: |
7
7
  <example>Context: An intent is being moved from Future to Active.
8
8
  user: "Board this intent and gather what we already know"
9
9
  assistant: "I'll use the intent-discovery agent to run QMD discovery and deposit findings to resources/"
10
- <commentary>What-stage discovery runs at activation, before the lock and Why.</commentary></example>
10
+ <commentary>What-stage discovery runs at activation, after the lock is armed, under it.</commentary></example>
11
11
  model: sonnet
12
12
  ---
13
13
 
14
14
  You are the Plastic Intent Discovery agent. You own the What stage: at intent
15
- activation, before the lock is armed and Why begins, you gather the context
16
- that already exists and deposit it for the Why stage to consume.
15
+ activation, after the lock is armed and before Why begins, under the lock as
16
+ the owner session, you gather the context that already exists and deposit it
17
+ for the Why stage to consume.
17
18
 
18
19
  ## Responsibilities
19
20
  1. **Read the intent's links.** Load the activating intent file's `chain` and
@@ -32,6 +33,8 @@ that already exists and deposit it for the Why stage to consume.
32
33
  ## Constraints
33
34
  - Read-only with respect to the intent: your single output is
34
35
  `resources/discovery--<slug>.md`.
35
- - Never take the delivery lock; you run before it is armed.
36
+ - You do not ACQUIRE the delivery lock; you run under the lock the
37
+ orchestrator armed (owner session, inherited session id) and are not
38
+ blocked by it.
36
39
  - End with a structured completion report per the spawn preamble's report
37
40
  contract.
package/bin/plastic.js CHANGED
@@ -50,8 +50,12 @@ try {
50
50
  })
51
51
  } catch (err) {
52
52
  if (err.status) process.exit(err.status)
53
- console.error('Error: Ruby is required to run Plastic.')
54
- console.error(' macOS: Ruby is pre-installed')
55
- console.error(' Linux: sudo apt install ruby / dnf install ruby')
53
+ // Ruby cannot run to print its own message when it is missing, so this
54
+ // mirrors scripts/lib/preflight.rb's FATAL block word for word.
55
+ console.error('Plastic needs Ruby 3.0.0 or newer to run its scripts (found not found).')
56
+ console.error('Install a pinned Ruby with mise:')
57
+ console.error(' curl https://mise.run | sh # only if mise is not installed yet')
58
+ console.error(' mise use --global ruby@3.3')
59
+ console.error('Then re-run the Plastic installer.')
56
60
  process.exit(1)
57
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "1.0.0-beta.34",
3
+ "version": "1.0.0-beta.35",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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]
@@ -119,9 +152,12 @@ class Install < InstallerCore
119
152
  --alpha Alpha channel
120
153
 
121
154
  Other options:
122
- --reinstall Re-sync core files for the installed version (repair). Store untouched.
123
- --force Overwrite existing files without prompting
124
- -h, --help Show this help
155
+ --reinstall Re-sync core files for the installed version (repair). Store untouched.
156
+ --force Overwrite existing files without prompting
157
+ --statusline VALUE keep or plastic. If an existing statusline is found, this
158
+ skips the interactive prompt. Interactive sessions ask by
159
+ default; non-interactive sessions default to keep.
160
+ -h, --help Show this help
125
161
 
126
162
  Notes:
127
163
  Install is one-shot. If Plastic is already installed, use `update` to upgrade or
@@ -1173,7 +1173,11 @@ module Bridge
1173
1173
  c = line[i]
1174
1174
  case state
1175
1175
  when :single
1176
- out << " "
1176
+ if c == "'" || c == "<" || c == ">"
1177
+ out << " "
1178
+ else
1179
+ out << c
1180
+ end
1177
1181
  state = :normal if c == "'"
1178
1182
  i += 1
1179
1183
  when :double
@@ -1181,7 +1185,11 @@ module Bridge
1181
1185
  out << " "
1182
1186
  i += 2
1183
1187
  else
1184
- out << " "
1188
+ if c == '"' || c == "<" || c == ">"
1189
+ out << " "
1190
+ else
1191
+ out << c
1192
+ end
1185
1193
  state = :normal if c == '"'
1186
1194
  i += 1
1187
1195
  end
@@ -1191,7 +1199,7 @@ module Bridge
1191
1199
  elsif c == '"'
1192
1200
  out << " "; state = :double; i += 1
1193
1201
  elsif c == "<" && line[i + 1] == "<"
1194
- m = line[i..].match(/\A<<(-?)\s*("|')?([A-Za-z_][A-Za-z0-9_]*)\2?/)
1202
+ m = line[i..].match(/\A<<(-?)\s*("|')?([A-Za-z0-9_][A-Za-z0-9_]*)\2?/)
1195
1203
  if m
1196
1204
  openers << { word: m[3], dash: m[1] == "-" }
1197
1205
  out << (" " * m[0].length)
@@ -161,6 +161,37 @@ class InstallerCore
161
161
  nums.select { |n| n >= 1 && n <= agents.size }.map { |n| agents[n - 1][:key] }
162
162
  end
163
163
 
164
+ # Resolve whether install should keep the user's existing statusline or switch it
165
+ # to Plastic's. Pure function of (settings file, argv, input, reinstall): no writes,
166
+ # so it stays fully unit-testable apart from merge_claude_hooks.
167
+ def statusline_choice(settings_path, argv: [], input: $stdin, reinstall: false)
168
+ existing_command = read_json_safe(settings_path)&.dig("statusLine", "command").to_s
169
+ return :plastic if existing_command.empty?
170
+ return :plastic if existing_command.include?("plastic-")
171
+
172
+ idx = argv.index("--statusline")
173
+ flag = idx && argv[idx + 1]
174
+ return flag.to_sym if %w[keep plastic].include?(flag)
175
+
176
+ return :keep if reinstall
177
+ return prompt_statusline(input: input) if input.tty?
178
+
179
+ :keep
180
+ end
181
+
182
+ def prompt_statusline(input: $stdin)
183
+ return :keep unless input.tty?
184
+
185
+ puts "An existing statusline was found in your settings.\n\n"
186
+ puts " 1. Keep my statusline (Plastic will not change it)"
187
+ puts " 2. Switch to Plastic's statusline"
188
+ puts
189
+ print "Select (1 or 2, Enter to keep): "
190
+ answer = input.gets&.strip
191
+
192
+ answer == "2" ? :plastic : :keep
193
+ end
194
+
164
195
  # --- Distribution phase ---
165
196
 
166
197
  def distribute(mode)
@@ -249,6 +280,7 @@ class InstallerCore
249
280
  "scripts/lib/store_provisioning.rb" => "scripts/lib/store_provisioning.rb",
250
281
  "scripts/provision-project-store" => "scripts/provision-project-store",
251
282
  "scripts/lib/installer_core.rb" => "scripts/lib/installer_core.rb",
283
+ "scripts/lib/preflight.rb" => "scripts/lib/preflight.rb",
252
284
  "scripts/install.rb" => "scripts/install.rb",
253
285
  "scripts/update.rb" => "scripts/update.rb",
254
286
  "scripts/uninstall.rb" => "scripts/uninstall.rb",
@@ -321,7 +353,7 @@ class InstallerCore
321
353
  (data["files"] || {}).keys
322
354
  end
323
355
 
324
- def install_for_agent(key, force)
356
+ def install_for_agent(key, force, argv: [], input: $stdin, reinstall: false)
325
357
  config = agent_config(key)
326
358
  return { agent: config[:name], success: false, reason: "Unknown agent" } unless config
327
359
 
@@ -334,7 +366,7 @@ class InstallerCore
334
366
  old_files = manifest_files(manifest_path_for(key, config))
335
367
 
336
368
  result = case key
337
- when "claude" then install_claude(config, force)
369
+ when "claude" then install_claude(config, force, argv: argv, input: input, reinstall: reinstall)
338
370
  when "codex" then install_codex(config, force)
339
371
  when "hermes" then install_hermes(config, force)
340
372
  end
@@ -363,7 +395,7 @@ class InstallerCore
363
395
  removed
364
396
  end
365
397
 
366
- def install_claude(config, force)
398
+ def install_claude(config, force, argv: [], input: $stdin, reinstall: false)
367
399
  hooks_dir = File.join(config[:dir], "hooks")
368
400
  skills_root = File.join(config[:dir], "skills")
369
401
  plastic_dir = File.join(config[:dir], "plastic")
@@ -406,7 +438,8 @@ class InstallerCore
406
438
 
407
439
  # Merge hooks + statusline into settings.json (no plugin registration)
408
440
  settings_path = File.join(config[:dir], "settings.json")
409
- merge_claude_hooks(settings_path)
441
+ choice = statusline_choice(settings_path, argv: argv, input: input, reinstall: reinstall)
442
+ merge_claude_hooks(settings_path, choice: choice)
410
443
 
411
444
  # Write manifest
412
445
  manifest_path = File.join(plastic_dir, "manifest.json")
@@ -572,7 +605,7 @@ class InstallerCore
572
605
 
573
606
  # --- settings.json merge (read-modify-write, never clobber) ---
574
607
 
575
- def merge_claude_hooks(settings_path)
608
+ def merge_claude_hooks(settings_path, choice: :plastic)
576
609
  settings = read_json_safe(settings_path) || {}
577
610
  return if settings.nil?
578
611
 
@@ -614,7 +647,7 @@ class InstallerCore
614
647
  File.write(File.join(cache_dir, "original-statusline.json"), JSON.pretty_generate(existing_status))
615
648
  end
616
649
 
617
- settings["statusLine"] = { "type" => "command", "command" => "#{hook_dir}/plastic-statusline" }
650
+ settings["statusLine"] = { "type" => "command", "command" => "#{hook_dir}/plastic-statusline" } if choice == :plastic
618
651
 
619
652
  # No plugin/marketplace registration: skills are flat personal skills
620
653
  # (plastic-<name>/) discovered directly from ~/.claude/skills.
@@ -0,0 +1,79 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ require "rubygems"
5
+
6
+ # Pure, dependency-injected pre-flight checks for Plastic's runtime dependencies
7
+ # (intent 38). Takes injected probes (ruby version, node version, git presence,
8
+ # mise presence) and returns a plain decision: ok / fatal plus branded messages.
9
+ #
10
+ # No I/O, no shelling out, no ENV reads here. Callers (scripts/install.rb,
11
+ # bin/plastic.js) own the impure probing and the printing, so this module stays
12
+ # hermetically testable. Voice matches boot_banner.rb (understated, "Plastic ..."
13
+ # prefix); no em-dash, no en-dash in any message.
14
+ module Preflight
15
+ module_function
16
+
17
+ RUBY_FLOOR = "3.0.0"
18
+ NODE_FLOOR = 18
19
+ RUBY_PIN = "3.3"
20
+
21
+ def check(ruby_version:, node_version:, git_present:, mise_present:)
22
+ messages = []
23
+
24
+ ruby_message = ruby_issue(ruby_version, mise_present)
25
+ fatal = !ruby_message.nil?
26
+ messages << ruby_message if ruby_message
27
+
28
+ node_message = node_issue(node_version)
29
+ messages << node_message if node_message
30
+
31
+ git_message = git_issue(git_present)
32
+ messages << git_message if git_message
33
+
34
+ { ok: messages.empty?, fatal: fatal, messages: messages }
35
+ end
36
+
37
+ def ruby_issue(ruby_version, mise_present)
38
+ parsed = safe_version(ruby_version)
39
+ return nil if parsed && parsed >= safe_version(RUBY_FLOOR)
40
+
41
+ lines = []
42
+ lines << "Plastic needs Ruby #{RUBY_FLOOR} or newer to run its scripts (found #{found(ruby_version)})."
43
+ lines << "Install a pinned Ruby with mise:"
44
+ lines << " curl https://mise.run | sh # only if mise is not installed yet" unless mise_present
45
+ lines << " mise use --global ruby@#{RUBY_PIN}"
46
+ lines << "Then re-run the Plastic installer."
47
+ lines.join("\n")
48
+ end
49
+
50
+ def node_issue(node_version)
51
+ parsed = safe_version(strip_leading_v(node_version))
52
+ return nil if parsed && parsed >= safe_version(NODE_FLOOR.to_s)
53
+
54
+ "Plastic works best on Node #{NODE_FLOOR} or newer (found #{found(node_version)}). " \
55
+ "Pin it with mise: mise use --global node@25"
56
+ end
57
+
58
+ def git_issue(git_present)
59
+ return nil if git_present
60
+
61
+ "Plastic uses git for its store and worktrees (git was not found). " \
62
+ "Install git, e.g. macOS: xcode-select --install"
63
+ end
64
+
65
+ def safe_version(str)
66
+ Gem::Version.new(str.to_s)
67
+ rescue ArgumentError
68
+ nil
69
+ end
70
+
71
+ def strip_leading_v(str)
72
+ str.to_s.strip.sub(/\Av/, "")
73
+ end
74
+
75
+ def found(value)
76
+ text = value.to_s.strip
77
+ text.empty? ? "not found" : text
78
+ end
79
+ end
@@ -3,7 +3,7 @@ name: plastic-doctor
3
3
  description: Use when diagnosing Plastic installation health, after updates, or when something seems broken. Runs checks and reports findings with fix options.
4
4
  ---
5
5
 
6
- # Doctor Plastic Health Check
6
+ # Doctor: Plastic Health Check
7
7
 
8
8
  ## Scopes
9
9
 
@@ -67,9 +67,9 @@ Parse the JSON output from stdout. The script is read-only and never modifies
67
67
  files. Errors go to stderr.
68
68
 
69
69
  Exit codes indicate check results, not script failure:
70
- - `0` all checks passed
71
- - `1` warnings found
72
- - `2` failures found
70
+ - `0`: all checks passed
71
+ - `1`: warnings found
72
+ - `2`: failures found
73
73
 
74
74
  All three exit codes mean the script ran successfully. Do not treat non-zero
75
75
  as an error.
@@ -118,7 +118,7 @@ Use the `fix_hint` value to determine the correct action:
118
118
  | "Remove stale references from INDEX.md" | Edit INDEX.md to remove ghost references |
119
119
  | "Inject the missing required frontmatter field(s)" | Edit the intent's `{ID}--{slug}.md` frontmatter to add the missing key (e.g. `chain: []`) without touching other keys |
120
120
  | "Run: provision-project-store {slug}" | Run `provision-project-store <slug>` (or invoke the `plastic-add-project-store` skill) to create the missing store |
121
- | "Re-run installer" | Run `npx @zalom/plastic@latest --agent` |
121
+ | "Re-run installer" | Run `npx -y @zalom/plastic@<channel> install --agent <agent>` (channel: -alpha->@alpha, -beta->@beta, else @latest) |
122
122
  | "Dispatch plastic-intent-curator ... revisions.md ..." | Invoke the `plastic-intent-curator` (or the agent) to relocate the flagged section or ref into the intent's `revisions.md` via move-and-record (one dated, `[rule: <tag>]`-tagged entry per item), per PLASTIC.md > Structural maintenance and revisions.md. For a missing required section, restore or reproject it instead. |
123
123
 
124
124
  For fixes the agent cannot handle automatically, explain what the user needs
@@ -144,7 +144,7 @@ Show the updated results.
144
144
  When invoked from `plastic-update` (not directly by the user):
145
145
 
146
146
  1. Run the diagnostic script as in Step 1.
147
- 2. If all checks pass: show a single line **"Health check: all clear."**
147
+ 2. If all checks pass, show a single line: **"Health check: all clear."**
148
148
  3. If issues are found: show the full report (Steps 3-6).
149
149
 
150
150
  This keeps the update flow clean when nothing is wrong.
@@ -1,24 +1,31 @@
1
1
  ---
2
2
  name: plastic-install
3
- description: Use when initializing Plastic globally (~/.plastic/) or locally in a project, or to re-install/repair a broken installation. Accepts channel flags (--alpha, --beta, --latest) to select release channel. Default is --latest (stable). Global install is recommended creates the global intent store as a git-backed repository. Local install creates .plastic/ in the current project for testing.
3
+ description: Use when initializing Plastic globally (~/.plastic/) or locally in a project, or to re-install/repair a broken installation. Accepts channel flags (--alpha, --beta, --latest) to select release channel. First install defaults to --beta; reinstalls match the already-installed channel. Global install is recommended: it creates the global intent store as a git-backed repository. Local install creates .plastic/ in the current project for testing.
4
4
  ---
5
5
 
6
6
  # Install Plastic
7
7
 
8
- > **Recommended path:** for a first install, run `npx @zalom/plastic@latest install --claude`
9
- > in your shell (or `bunx @zalom/plastic@latest --claude` if you use Bun). This skill
8
+ > **Recommended path:** for a first install, run `npx -y @zalom/plastic@beta install --claude`
9
+ > in your shell (or `bunx -y @zalom/plastic@beta install --claude` if you use Bun). This skill
10
10
  > exists to **re-install or repair** an existing setup from inside the agent, and to
11
11
  > drive interactive global configuration. Whenever this skill performs an install or
12
12
  > re-install, it **runs `/plastic-doctor` afterward** and reports the result.
13
13
 
14
+ ## Channel rule
15
+
16
+ If Plastic is installed, derive `<channel>` from `~/.plastic/VERSION`: a version containing
17
+ `-alpha` means `@alpha`, `-beta` means `@beta`, otherwise `@latest`. If not installed
18
+ (first install), default to `@beta`. The user can always override with
19
+ `--alpha` / `--beta` / `--latest`.
20
+
14
21
  ## Re-install / repair
15
22
 
16
23
  If Plastic is already installed but something is broken (skills missing, hooks not
17
- firing, leftover legacy plugin), re-run the installer it is idempotent, prunes
24
+ firing, leftover legacy plugin), re-run the installer, it is idempotent, prunes
18
25
  files that no longer ship, and removes any legacy plugin/marketplace layout:
19
26
 
20
27
  ```bash
21
- npx @zalom/plastic@latest install --reinstall --claude # or @beta / @alpha to match your channel
28
+ npx -y @zalom/plastic@<channel> install --reinstall --claude
22
29
  ```
23
30
 
24
31
  Then **run `/plastic-doctor`** and report what it found.
@@ -27,22 +34,22 @@ Then **run `/plastic-doctor`** and report what it found.
27
34
 
28
35
  | Flag | Behavior |
29
36
  |------|----------|
30
- | `--latest` | Install from stable channel (default) |
31
- | `--beta` | Install from beta channel |
32
- | `--alpha` | Install from alpha channel |
37
+ | `--latest` | Install from the stable channel |
38
+ | `--beta` | Install from the beta channel (default on a first install) |
39
+ | `--alpha` | Install from the alpha channel |
33
40
 
34
41
  When invoked from within Claude Code (re-install or channel switch), the skill
35
42
  runs the appropriate npx command:
36
43
 
37
44
  ```bash
38
- # Stable (default)
39
- npx @zalom/plastic install --claude
45
+ # Stable
46
+ npx -y @zalom/plastic@latest install --claude
40
47
 
41
- # Beta
42
- npx @zalom/plastic@beta install --claude
48
+ # Beta (default on a first install)
49
+ npx -y @zalom/plastic@beta install --claude
43
50
 
44
51
  # Alpha
45
- npx @zalom/plastic@alpha install --claude
52
+ npx -y @zalom/plastic@alpha install --claude
46
53
  ```
47
54
 
48
55
  The installed version and channel are recorded in `~/.plastic/VERSION`.
@@ -55,105 +62,80 @@ Run `/plastic-install` with no arguments.
55
62
 
56
63
  #### Procedure
57
64
 
58
- **Step 1: Check for existing installation**
59
-
60
- Check if `~/.plastic/INDEX.md` exists.
61
- - If yes: announce "Plastic is already installed at ~/.plastic/. Run `/plastic-update` to sync core files."
62
- - If no: proceed with fresh install.
65
+ **Step 1: Run the installer**
63
66
 
64
- **Step 2: Create ~/.plastic/ as a git repo**
67
+ Check if `~/.plastic/VERSION` exists.
68
+ - If yes: announce "Plastic is already installed at ~/.plastic/. Run `/plastic-update` to
69
+ sync core files, or use the re-install command above to repair in place."
70
+ - If no: run the fresh install command (default `@beta`, or the channel the user named):
65
71
 
66
72
  ```bash
67
- mkdir -p ~/.plastic/store ~/.plastic/projects
73
+ npx -y @zalom/plastic@beta install --claude
68
74
  ```
69
75
 
70
- Copy templates from the plugin:
71
- - `config.yml` from `${CLAUDE_PLUGIN_ROOT}/templates/config.yml`
72
- - `projects.yml` from `${CLAUDE_PLUGIN_ROOT}/templates/projects.yml`
73
- - `INDEX.md` from `${CLAUDE_PLUGIN_ROOT}/templates/index.md`
74
- - `PLASTIC.md` from `${CLAUDE_PLUGIN_ROOT}/PLASTIC.md`
76
+ This single command, via `install.rb` (`bootstrap` + `distribute`), creates `store/`,
77
+ `projects/`, `config.yml`, `projects.yml`, `INDEX.md`, and `AGENTS.md` under `~/.plastic/`,
78
+ and copies the utility scripts (`folgezettel-id`, `read-config`, and the rest of
79
+ `scripts/`). This skill does none of that itself; it wraps the command with the
80
+ interactive steps the CLI does not yet own, plus reporting and a doctor pass.
75
81
 
76
- Create `AGENTS.md` (user-editable, not overwritten on updates):
77
- ```markdown
78
- # Plastic — Agent Instructions
82
+ **Statusline**
79
83
 
80
- Read `PLASTIC.md` in this directory. It contains all Plastic conventions.
81
- Follow it exactly. Never modify it it is overwritten on plugin updates.
84
+ On install, if an existing statusline is already configured, Plastic asks whether to
85
+ keep it or switch to Plastic's (interactive sessions only). The choice is honored via
86
+ `--statusline keep` or `--statusline plastic`, which skips the prompt. Non-interactive
87
+ sessions (no tty) default to keeping the user's line: nothing is silently overwritten.
88
+ A fresh system with no statusline configured gets Plastic's line with no prompt.
82
89
 
83
- This file (`AGENTS.md`) is where project-specific rules live. Users and agents
84
- may add content below.
85
-
86
- ---
87
- ```
90
+ **Step 2: Initialize git (retained)**
88
91
 
89
- Add `.gitkeep` to `store/` and `projects/`.
92
+ Only if `~/.plastic/.git` is absent (a fresh bootstrap does not init git):
90
93
 
91
- Initialize git:
92
94
  ```bash
93
95
  cd ~/.plastic && git init && git add . && git commit -m "chore: initialize Plastic global intent store"
94
96
  ```
95
97
 
96
- **Step 2b: Copy utility scripts**
98
+ Retained here because the CLI does not git-init the store yet (follow-up).
97
99
 
98
- ```bash
99
- mkdir -p ~/.plastic/scripts
100
- cp "${CLAUDE_PLUGIN_ROOT}/scripts/folgezettel-id" ~/.plastic/scripts/folgezettel-id
101
- cp "${CLAUDE_PLUGIN_ROOT}/scripts/read-config" ~/.plastic/scripts/read-config
102
- chmod +x ~/.plastic/scripts/folgezettel-id ~/.plastic/scripts/read-config
103
- ```
104
-
105
- This ensures project agents can generate hashes via `~/.plastic/scripts/folgezettel-id` without depending on a specific agent's plugin cache path.
106
-
107
- **Step 2c: Detect agent type and set preferences**
100
+ **Step 3: Personalize config (retained)**
108
101
 
109
102
  Detect which agent is running:
110
- - If `CLAUDE_CODE` env var is set or we're running inside Claude Code `agent.type: claude-code`
111
- - If `HERMES_HOME` env var is set `agent.type: hermes`
112
- - Otherwise ask the user: "Which AI agent are you using? (claude-code / hermes / other)"
103
+ - If `CLAUDE_CODE` env var is set or we're running inside Claude Code -> `agent.type: claude-code`
104
+ - If `HERMES_HOME` env var is set -> `agent.type: hermes`
105
+ - Otherwise -> ask the user: "Which AI agent are you using? (claude-code / hermes / other)"
113
106
 
114
107
  Ask the user:
115
- > "Enable Agent Teams? (experimental parallel project work with teammates)"
116
- > - Yes set `parallel_mode: agent-teams`
117
- > - No set `parallel_mode: linear` (subagents only)
118
-
119
- Update `~/.plastic/config.yml` with detected/chosen values using `read-config --migrate` first to ensure v3 schema, then write the agent-specific values.
120
-
121
- Auto-commit the config change.
122
-
123
- **Step 2d: Configure GitHub and push preferences**
108
+ > "Enable Agent Teams? (experimental: parallel project work with teammates)"
109
+ > - Yes -> set `parallel_mode: agent-teams`
110
+ > - No -> set `parallel_mode: linear` (subagents only)
124
111
 
125
112
  Inform the user:
126
- > "Plastic agents can create GitHub repositories for new projects.
127
- > By default, all agent-created repos are **private**. Your global
128
- > intent store (~/.plastic/) is never pushed it stays local-only."
113
+ > "Plastic agents can create GitHub repositories for new projects. By default, all
114
+ > agent-created repos are **private**. Your global intent store (~/.plastic/) is never
115
+ > pushed, it stays local-only."
129
116
 
130
117
  Ask the user:
131
118
  > "Default visibility for agent-created repos?"
132
- > - Private (recommended) set `github.default_visibility: private`
133
- > - Public set `github.default_visibility: public`
134
-
119
+ > - Private (recommended) -> set `github.default_visibility: private`
120
+ > - Public -> set `github.default_visibility: public`
121
+ >
135
122
  > "Allow agents to push to GitHub without asking?"
136
- > - No (recommended) set `github.auto_push: false`
137
- > - Yes set `github.auto_push: true`
138
-
139
- Update `config.yml` with chosen values. Auto-commit.
140
-
141
- **Step 3: Configure project roots**
142
-
143
- Ask the user:
123
+ > - No (recommended) -> set `github.auto_push: false`
124
+ > - Yes -> set `github.auto_push: true`
125
+ >
144
126
  > "Where do you keep your projects? Default: ~/.plastic/projects/"
145
127
  > "Add additional roots? (e.g., ~/apps/personal/, ~/apps/companies/)"
146
128
 
147
- Update `config.yml` with any additional roots.
148
-
149
- Auto-commit the config change.
129
+ Write each answer via `read-config --migrate` first (ensures the v3 schema), then the
130
+ chosen values; auto-commit each change. Retained here because the CLI writes only
131
+ hardcoded defaults, so these interactive choices stay in the skill.
150
132
 
151
133
  **Step 4: Verify with doctor**
152
134
 
153
135
  Run `/plastic-doctor` and report the result. Resolve any fixable findings before
154
136
  announcing success.
155
137
 
156
- **Step 5: Register stores with QMD (optional)**
138
+ **Step 5: Register stores with QMD (retained)**
157
139
 
158
140
  QMD is an optional search layer. If it is installed, register the Plastic stores so
159
141
  they are searchable:
@@ -165,19 +147,27 @@ ruby ~/.plastic/scripts/qmd-sync detect && ruby ~/.plastic/scripts/qmd-sync regi
165
147
  `qmd-sync` no-ops cleanly when QMD is absent, so this is safe to run unconditionally.
166
148
  It registers `plastic-global` and every project store from `projects.yml`, then indexes
167
149
  them. Report what was registered, or that QMD was not detected and the step was skipped.
150
+ Retained here because the CLI does not register at install time (follow-up).
151
+
152
+ **Step 6: Report + announce**
168
153
 
169
- **Step 6: Announce**
154
+ ```
155
+ Plastic install (<channel>)
156
+ Command: npx -y @zalom/plastic@<channel> install --claude <flags>
157
+ Version: none -> <installed>
158
+ Doctor: <summary or "all clear">
159
+ ```
170
160
 
171
- > "Plastic installed globally at ~/.plastic/. Health check: [doctor summary].
172
- > Create your first intent with `/plastic-creating-intent`."
161
+ Then: "Create your first intent with `/plastic-creating-intent`."
173
162
 
174
163
  ### Local Install (testing/legacy)
175
164
 
176
- Run `/plastic-install --local`.
165
+ Run `/plastic-install --local`. `install.rb` has no `--local` verb, so this mode is
166
+ genuinely skill-owned.
177
167
 
178
168
  #### Procedure
179
169
 
180
- **Step 1:** Check if `.plastic/` exists in CWD if so, warn and exit.
170
+ **Step 1:** Check if `.plastic/` exists in CWD, if so, warn and exit.
181
171
 
182
172
  **Step 2:** Create `.plastic/` in CWD:
183
173
  - `config.yml` from templates
@@ -192,4 +182,5 @@ Run `/plastic-install --local`.
192
182
 
193
183
  **Step 4:** Commit in project: `git add .plastic/ && git commit -m "chore: initialize Plastic local store"`
194
184
 
195
- **Step 5:** Announce: "Plastic initialized locally. This is a testing/legacy mode. Consider `/plastic-install` for global mode."
185
+ **Step 5:** Announce: "Plastic initialized locally. This is a testing/legacy mode. Consider
186
+ `/plastic-install` for global mode."
@@ -4,22 +4,21 @@ description: >-
4
4
  What-stage context deposit at intent activation: run QMD discovery over the
5
5
  intent's chain/sources and related parked intents, and write findings to
6
6
  resources/discovery--<slug>.md for the Why stage to consume. Use when an intent
7
- is activated (moved from Future to Active), before the lock is armed and Why
8
- begins. Never writes the intent file itself.
7
+ is activated (moved from Future to Active), after the lock is armed, under it,
8
+ and before Why begins. Never writes the intent file itself.
9
9
  ---
10
10
 
11
11
  # Intent Discovery — What-stage context deposit
12
12
 
13
13
  Announce: "Discovering context for intent [ID] — [name]."
14
14
 
15
- Runs once, at intent activation, before the lock and Why. It gathers what is
15
+ Runs once, at intent activation, after the lock is armed and before Why. It gathers what is
16
16
  already known so Why does not start cold, and deposits it as a resource the
17
17
  Why-stage brainstorming agent reads.
18
18
 
19
19
  ## When it fires
20
- Inside `plastic-intent-starting`, at the moment the intent moves from
21
- `## Future` to `## Active` (before the bridge is armed). Dispatched as the
22
- `plastic-intent-discovery` background agent.
20
+ Inside `plastic-intent-starting`, right after the bridge is armed, under the
21
+ lock. Dispatched as the `plastic-intent-discovery` background agent.
23
22
 
24
23
  ## What it does
25
24
  1. **Read the intent's links.** Load the activating intent file's `chain` and
@@ -42,5 +41,7 @@ executor, Done: intent-curator).
42
41
 
43
42
  ## Boundaries
44
43
  - Single output: `resources/discovery--<slug>.md`.
45
- - Never takes the delivery lock (it runs before the lock is armed).
44
+ - Does not ACQUIRE the delivery lock itself; it runs under the lock the
45
+ orchestrator armed, as the owner session (inherited session id), and is not
46
+ blocked by it.
46
47
  - Advisory input to Why, not a gate.
@@ -34,14 +34,6 @@ enforces it: without a held lock, mutating writes to this active intent's dir ar
34
34
  1. **Ensure the intent is in INDEX `## Active`.** If it sits in `## Future`, activate it
35
35
  (move it to `## Active`, auto-commit) before arming. Creation precedes activation, so a
36
36
  brand-new What intent is activated here, then locked.
37
- 1a. **Dispatch What-stage discovery (before the lock).** Right after activation and before
38
- arming the bridge, dispatch the `plastic-intent-discovery` agent (see the
39
- `plastic-intent-discovery` skill). Resolve its model explicitly and pass it at dispatch
40
- time (belt-and-braces): `read-config agents.models.plastic-intent-discovery --project
41
- <repo>`. The agent runs QMD discovery over the intent's `chain`/`sources` and deposits
42
- findings to `resources/discovery--<slug>.md` only; it never writes the intent file, so the
43
- lock-owner-only rule is untouched. This is advisory context for Why, not a gate: if
44
- discovery yields nothing, proceed to the lock normally.
45
37
  2. **Self-heal the lock state first.** Run:
46
38
  `ruby ~/.plastic/scripts/plastic-lock fix --intent-dir <STORE>/<dir>`
47
39
  This is the one repair function (same one /plastic-lock exposes): it removes
@@ -61,6 +53,17 @@ enforces it: without a held lock, mutating writes to this active intent's dir ar
61
53
  ```
62
54
  Replace `<ID>`, `<STORE>` (`~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
63
55
  `<dir>` (the `ID--slug` directory), and `<name>`.
56
+ 4. **Dispatch What-stage discovery (under the lock).** Right after arming, when the intent
57
+ was just activated in step 1 (on a resume that already has
58
+ `resources/discovery--<slug>.md`, skip: discovery runs once per intent, at activation
59
+ only), dispatch the `plastic-intent-discovery` agent (see the `plastic-intent-discovery`
60
+ skill), now that this session owns the lock, deposit authorized as the owner session. Resolve its
61
+ model explicitly and pass it at dispatch time (belt-and-braces): `read-config
62
+ agents.models.plastic-intent-discovery --project <repo>`. The agent runs QMD discovery
63
+ over the intent's `chain`/`sources` and deposits findings to
64
+ `resources/discovery--<slug>.md` only; it never writes the intent file, so the
65
+ lock-owner-only rule is untouched. This is advisory context for Why, not a gate: if
66
+ discovery yields nothing, proceed to Why normally.
64
67
 
65
68
  **Session id resolution (verbatim from `plastic-auto`).** The first argument is the session
66
69
  id the bridge is keyed by: pass the hook stdin `session_id` when you have it, otherwise
@@ -9,15 +9,21 @@ The installer tracks every file it writes in a manifest, so uninstall is exact a
9
9
  leaves no orphans. Prefer running it through the CLI; this skill wraps the same
10
10
  underlying uninstaller and adds reporting + verification.
11
11
 
12
+ ## Channel rule
13
+
14
+ If Plastic is installed, derive `<channel>` from `~/.plastic/VERSION`: a version containing
15
+ `-alpha` means `@alpha`, `-beta` means `@beta`, otherwise `@latest`. If not installed,
16
+ default to `@beta`. The user can always override with `--alpha` / `--beta` / `--latest`.
17
+
12
18
  ## Procedure
13
19
 
14
20
  ### Step 1: Run the uninstaller
15
21
 
16
22
  ```bash
17
- npx @zalom/plastic@latest uninstall --claude
23
+ npx -y @zalom/plastic@<channel> uninstall --claude
18
24
  ```
19
25
 
20
- (Use `--codex` / `--hermes` / `--all` to target other agents. `bunx` works too.)
26
+ (Use `--codex` / `--hermes` / `--all` to target other agents. `bunx -y @zalom/plastic@<channel> uninstall --claude` works too.)
21
27
 
22
28
  This removes, for the targeted agent:
23
29
  - all `~/.claude/skills/plastic-*/` skills
@@ -29,8 +35,8 @@ This removes, for the targeted agent:
29
35
 
30
36
  ### Step 2: Report removed vs left
31
37
 
32
- Relay the uninstaller's output to the user what was **removed** and what was
33
- **left in place**:
38
+ Relay the uninstaller's output to the user: what was **removed** and what was
39
+ **left in place**.
34
40
  - **Left:** `~/.plastic/` (intent store, history, projects) and any non-Plastic
35
41
  settings.json entries.
36
42
 
@@ -39,21 +45,33 @@ Relay the uninstaller's output to the user — what was **removed** and what was
39
45
  Tell the user to confirm:
40
46
 
41
47
  ```bash
42
- ls ~/.claude/skills | grep '^plastic-' # no output
43
- ls ~/.claude/hooks | grep '^plastic-' # no output
44
- grep -n plastic ~/.claude/settings.json # no plastic hook/plugin refs
48
+ ls ~/.claude/skills | grep '^plastic-' # -> no output
49
+ ls ~/.claude/hooks | grep '^plastic-' # -> no output
50
+ grep -n plastic ~/.claude/settings.json # -> no plastic hook/plugin refs
51
+ ```
52
+
53
+ ### Step 4: Report + offer the data decision
54
+
55
+ Emit the reporting block, using the Step 3 checks for the verification line:
56
+
57
+ ```
58
+ Plastic uninstall (<channel>)
59
+ Command: npx -y @zalom/plastic@<channel> uninstall --claude <flags>
60
+ Version: removed
61
+ Verification: <Step 3 results, or "clean">
45
62
  ```
46
63
 
47
- ### Step 4: Offer the data decision
64
+ Then:
48
65
 
49
66
  ```
50
67
  Plastic is uninstalled from [agent].
51
68
  Your intent store at ~/.plastic/ is untouched.
52
69
 
53
70
  Delete it too?
54
- a) Keep everything (recommended) re-install anytime with npx
55
- b) Delete everything now removes ~/.plastic/ entirely (irreversible)
71
+ a) Keep everything (recommended): re-install anytime with npx
72
+ b) Delete everything now: removes ~/.plastic/ entirely (irreversible)
56
73
  ```
57
74
 
58
- - **Keep:** "Your data is at ~/.plastic/. Re-install anytime with `npx @zalom/plastic@latest install --claude`."
75
+ - **Keep:** "Your data is at ~/.plastic/. Re-install anytime with
76
+ `npx -y @zalom/plastic@beta install --claude` (or your channel)."
59
77
  - **Delete:** run `rm -rf ~/.plastic/` and confirm.
@@ -13,58 +13,69 @@ description: Use when updating Plastic. Runs the `update` verb, which reads the
13
13
  ## What it does
14
14
 
15
15
  `update` is a single deterministic command. It reads `~/.plastic/VERSION`, derives the
16
- channel from the version string (`-alpha`/`-beta`/none stable), queries `npm` dist-tags,
16
+ channel from the version string (`-alpha`/`-beta`/none -> stable), queries `npm` dist-tags,
17
17
  and advances to the **next version on the current channel**. "Already up to date" is a
18
- clean no-op. You do not compute the target yourself the script does.
18
+ clean no-op. You do not compute the target yourself, the script does.
19
+
20
+ ## Channel rule
21
+
22
+ If Plastic is installed, derive `<channel>` from `~/.plastic/VERSION`: a version containing
23
+ `-alpha` means `@alpha`, `-beta` means `@beta`, otherwise `@latest`. If not installed
24
+ (first install), default to `@beta`. The user can always override with
25
+ `--alpha` / `--beta` / `--latest`.
19
26
 
20
27
  ## Flags
21
28
 
22
29
  | Flag | Behaviour |
23
30
  |------|-----------|
24
31
  | (none) | Advance to the next version on the **current** channel |
25
- | `--latest` | Switch to / advance the **stable** channel (toward stability frictionless) |
32
+ | `--latest` | Switch to / advance the **stable** channel (toward stability, frictionless) |
26
33
  | `--beta` | Switch to / advance the **beta** channel |
27
- | `--alpha` | Switch to / advance the **alpha** channel (bleeding edge confirmed if moving down in stability) |
34
+ | `--alpha` | Switch to / advance the **alpha** channel (bleeding edge, confirmed if moving down in stability) |
28
35
 
29
36
  Switching toward a more stable channel is frictionless; switching toward bleeding edge is
30
37
  confirmed. To roll **back** to a previously-installed version, use `plastic-versions`.
31
38
 
32
39
  ## Prerequisites
33
40
 
34
- Plastic must be installed (`~/.plastic/VERSION` present). If not, run
35
- `npx @zalom/plastic install --claude` first.
41
+ Plastic must be installed (`~/.plastic/VERSION` present). If not, run `plastic-install`
42
+ first (or `npx -y @zalom/plastic@beta install --claude` directly).
36
43
 
37
44
  ## Procedure
38
45
 
39
46
  ### Step 1: Run the update
40
47
 
41
48
  ```bash
42
- npx @zalom/plastic update # next version on the current channel
43
- # or: npx @zalom/plastic update --beta / --latest / --alpha
49
+ npx -y @zalom/plastic@<channel> update --claude
50
+ # channel switch: append --beta / --latest / --alpha
51
+ # other agents: append --codex / --hermes / --all
44
52
  ```
45
53
 
46
- Append the agent flag(s) if not just Claude (`--codex`, `--hermes`, `--all`).
47
- The command prints the transition (`vX vY`) or "already up to date", and records the
48
- move in the append-only `~/.plastic/versions.json` ledger.
54
+ `bunx -y @zalom/plastic@<channel> update --claude` works as a fallback if `npx` is
55
+ unavailable. The command prints the transition (`vX -> vY`) or "already up to date", runs
56
+ a post-update doctor summary, and records the move in the append-only
57
+ `~/.plastic/versions.json` ledger.
49
58
 
50
- ### Step 2: Announce key changes
59
+ ### Step 2: Relay the result, announce convention changes
51
60
 
52
- After it completes, read `~/.plastic/PLASTIC.md` and announce convention changes that
53
- affect the current session:
61
+ Relay what `update` printed, do not recompute the version transition or the doctor
62
+ summary:
54
63
 
55
64
  ```
56
- Plastic updated to vX.Y.Z (channel).
57
-
58
- Key changes:
59
- - [notable convention changes, if any]
60
-
61
- Recommendation: run /clear for a clean session with all new conventions loaded.
65
+ Plastic update (<channel>)
66
+ Command: npx -y @zalom/plastic@<channel> update --claude <flags>
67
+ Version: <before> -> <after>
68
+ Doctor: <relayed summary, or "all clear">
62
69
  ```
63
70
 
64
- ### Step 3: Health check
71
+ Then read `~/.plastic/PLASTIC.md` and announce convention changes that affect the
72
+ current session, and recommend `/clear` for a clean session with all new conventions
73
+ loaded.
74
+
75
+ ### Step 3: Health check only on a relayed failure
65
76
 
66
- Invoke `plastic-doctor`. If all checks pass: **"Health check: all clear."** Otherwise show
67
- the report and offer to fix.
77
+ If the relayed doctor summary shows a failure, invoke `plastic-doctor` for the full
78
+ report and offer to fix. If it already reads clean, do not re-run doctor.
68
79
 
69
80
  ### Step 4: Commit + clear update cache
70
81
 
@@ -3,7 +3,7 @@ name: plastic-versions
3
3
  description: Use when the user wants to see their Plastic version history or roll back to a previously-installed version after a bad release. Manages the local, append-only versions.json ledger and steps between versions the user has actually run. For moving to a brand-new release, use plastic-update instead.
4
4
  ---
5
5
 
6
- # Plastic Versions local version time-machine
6
+ # Plastic Versions: local version time-machine
7
7
 
8
8
  ## When to Use
9
9
  - "show plastic versions", "version history", "what versions have I run"
@@ -11,11 +11,18 @@ description: Use when the user wants to see their Plastic version history or rol
11
11
  - A new version broke something and the user wants their last known-good build
12
12
 
13
13
  For upgrading to a **new** release, use `plastic-update`. This skill only navigates
14
- versions you have **already installed** the ones recorded in the ledger.
14
+ versions you have **already installed**, the ones recorded in the ledger.
15
+
16
+ ## Channel rule
17
+
18
+ `versions` restores whichever build you pick from the ledger, it does not take a channel
19
+ flag for the target. The pinned `<channel>` below is only the npx invocation itself:
20
+ derive it from `~/.plastic/VERSION` the same way as the other lifecycle skills,
21
+ `-alpha` -> `@alpha`, `-beta` -> `@beta`, otherwise `@latest`.
15
22
 
16
23
  ## The ledger
17
24
 
18
- `~/.plastic/versions.json` is an **append-only JSONL** ledger one line per version
25
+ `~/.plastic/versions.json` is an **append-only JSONL** ledger, one line per version
19
26
  change, never modified or deleted:
20
27
 
21
28
  ```json
@@ -23,15 +30,16 @@ change, never modified or deleted:
23
30
  {"version":"1.0.0-alpha.18","action":"update","at":"..."}
24
31
  ```
25
32
 
26
- `action` `install | reinstall | update | downgrade` (derived from version direction; the
27
- channel is derived from the version string, never stored). It is a troubleshooting record.
33
+ `action` is one of `install`, `reinstall`, `update`, `downgrade` (derived from version
34
+ direction; the channel is derived from the version string, never stored). It is a
35
+ troubleshooting record.
28
36
 
29
37
  ## Procedure
30
38
 
31
39
  ### Show history
32
40
 
33
41
  ```bash
34
- npx @zalom/plastic versions
42
+ npx -y @zalom/plastic@<channel> versions
35
43
  ```
36
44
 
37
45
  Prints the table with the currently-installed version marked. If the most recent action was
@@ -40,8 +48,8 @@ a `downgrade`, it asks whether to keep rolling back.
40
48
  ### Roll back
41
49
 
42
50
  ```bash
43
- npx @zalom/plastic versions --downgrade # one step back
44
- npx @zalom/plastic versions --downgrade --version 1.0.0-alpha.15 # to a specific run
51
+ npx -y @zalom/plastic@<channel> versions --downgrade # one step back
52
+ npx -y @zalom/plastic@<channel> versions --downgrade --version 1.0.0-alpha.15 # to a specific run
45
53
  ```
46
54
 
47
55
  Rollback targets are restricted to versions in the ledger (only builds you have actually
@@ -51,15 +59,22 @@ re-fetched from npm and re-synced; your intent store, config, and the ledger are
51
59
  ### Step forward (after a rollback)
52
60
 
53
61
  ```bash
54
- npx @zalom/plastic versions --upgrade # one step forward in your history
62
+ npx -y @zalom/plastic@<channel> versions --upgrade # one step forward in your history
55
63
  ```
56
64
 
57
65
  ### After any change
58
66
 
59
- Run `plastic-doctor` to confirm health, and suggest `/clear` so the session picks up the
60
- swapped conventions.
67
+ Run `plastic-doctor` to confirm health, then emit the reporting block and suggest
68
+ `/clear` so the session picks up the swapped conventions:
69
+
70
+ ```
71
+ Plastic versions (<channel>)
72
+ Command: npx -y @zalom/plastic@<channel> versions <flags>
73
+ Version: <before> -> <after>
74
+ Doctor: <summary or "all clear">
75
+ ```
61
76
 
62
77
  ## Notes
63
- - The ledger is **never** edited or pruned it is the audit trail.
78
+ - The ledger is **never** edited or pruned, it is the audit trail.
64
79
  - Downgrades cannot un-migrate a store-format change; if a warning appears, surface it to
65
80
  the user rather than forcing the rollback.