@zalom/plastic 1.0.0-alpha.2 → 1.0.0-alpha.21

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.
Files changed (76) hide show
  1. package/PLASTIC.md +128 -473
  2. package/README.md +90 -58
  3. package/agents/future-intent-researcher.md +1 -1
  4. package/agents/intent-curator.md +1 -1
  5. package/bin/plastic.js +57 -0
  6. package/bin/test +28 -0
  7. package/deprecations.yml +7 -6
  8. package/hooks/auto-arm +5 -0
  9. package/hooks/bash-gate +3 -0
  10. package/hooks/check-update +12 -8
  11. package/hooks/code-gate +10 -0
  12. package/hooks/hooks.json +25 -4
  13. package/hooks/statusline +50 -10
  14. package/package.json +2 -2
  15. package/scripts/dashboard.rb +480 -0
  16. package/scripts/doctor.rb +973 -0
  17. package/scripts/hook-auto-arm +52 -0
  18. package/scripts/hook-bash-gate +53 -0
  19. package/scripts/hook-code-gate +39 -0
  20. package/scripts/hook-continue +15 -114
  21. package/scripts/hook-gate-check +19 -4
  22. package/scripts/hook-session-start +76 -31
  23. package/scripts/install.rb +91 -480
  24. package/scripts/lib/bridge.rb +255 -0
  25. package/scripts/lib/installer_core.rb +760 -0
  26. package/scripts/migrate-to-global +1 -1
  27. package/scripts/select-update-target +93 -0
  28. package/scripts/uninstall.rb +53 -0
  29. package/scripts/update.rb +142 -0
  30. package/scripts/versions.rb +141 -0
  31. package/skills/_active-intent-gate.md +26 -0
  32. package/skills/auto/SKILL.md +62 -9
  33. package/skills/auto/evals/evals.json +92 -0
  34. package/skills/auto/references/agent-architecture.md +60 -0
  35. package/skills/brainstorming/SKILL.md +143 -0
  36. package/skills/brainstorming-grill-me/SKILL.md +5 -5
  37. package/skills/continuing/SKILL.md +102 -77
  38. package/skills/continuing/evals/evals.json +136 -0
  39. package/skills/continuing/references/context-management.md +32 -0
  40. package/skills/creating-intent/SKILL.md +16 -1
  41. package/skills/creating-intent/references/lifecycle.md +74 -0
  42. package/skills/creating-intent/references/wikilinks.md +8 -0
  43. package/skills/creating-project/SKILL.md +8 -4
  44. package/skills/creating-project/references/hubs-projects.md +55 -0
  45. package/skills/dashboard/SKILL.md +92 -0
  46. package/skills/doctor/SKILL.md +116 -0
  47. package/skills/doctor/references/gates-stuck-detection.md +38 -0
  48. package/skills/doctor/report.md +96 -0
  49. package/skills/evaluating-skills/SKILL.md +140 -0
  50. package/skills/evaluating-skills/assets/eval-template.json +12 -0
  51. package/skills/evaluating-skills/evals/evals.json +75 -0
  52. package/skills/evaluating-skills/references/convention-checks.md +76 -0
  53. package/skills/evaluating-skills/references/eval-methodology.md +154 -0
  54. package/skills/executing-plan/SKILL.md +3 -3
  55. package/skills/install/SKILL.md +56 -8
  56. package/skills/intent-curator/SKILL.md +3 -3
  57. package/skills/linking-intents/SKILL.md +5 -1
  58. package/skills/linking-intents/references/zettelkasten.md +33 -0
  59. package/skills/managing-index/SKILL.md +5 -1
  60. package/skills/releasing/SKILL.md +119 -18
  61. package/skills/releasing/references/deprecations.md +44 -0
  62. package/skills/research/SKILL.md +114 -0
  63. package/skills/savepoint/SKILL.md +46 -37
  64. package/skills/savepoint/references/context-management.md +32 -0
  65. package/skills/uninstall/SKILL.md +39 -28
  66. package/skills/update/SKILL.md +41 -36
  67. package/skills/versions/SKILL.md +65 -0
  68. package/skills/writing-instructions/SKILL.md +159 -0
  69. package/skills/writing-instructions/references/agentskills-spec.md +135 -0
  70. package/skills/writing-plans/SKILL.md +183 -0
  71. package/templates/agents.md +16 -0
  72. package/templates/outcome.md +13 -0
  73. package/templates/project.yml +5 -0
  74. package/templates/savepoint.md +14 -13
  75. package/templates/spec.md +25 -0
  76. package/bin/install.js +0 -29
@@ -17,7 +17,7 @@ GLOBAL_ROOT="$HOME/.plastic"
17
17
  LOCAL_PLASTIC="$PROJECT_PATH/.plastic"
18
18
 
19
19
  if [ ! -d "$GLOBAL_ROOT" ]; then
20
- echo "Error: Global Plastic not installed. Run /plastic:install first." >&2
20
+ echo "Error: Global Plastic not installed. Run /plastic-install first." >&2
21
21
  exit 1
22
22
  fi
23
23
 
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+ # plastic-script-version: 1.0.0
3
+ # Channel-aware update target selector.
4
+ #
5
+ # Usage: npm view @zalom/plastic dist-tags --json | select-update-target <current-version>
6
+ #
7
+ # Prints the best update target to stdout, or nothing if already up to date.
8
+ #
9
+ # Rule: target = the highest version, among the dist-tags reachable from the
10
+ # installed channel, that is strictly greater than the installed version (semver).
11
+ # alpha installed -> reachable: alpha, beta, latest
12
+ # beta installed -> reachable: beta, latest (never downgrade to alpha)
13
+ # stable installed -> reachable: latest (stable only)
14
+ # A prerelease (1.1.0-alpha.1) is LOWER than its release (1.1.0); the `latest`
15
+ # placeholder (0.0.1) is naturally excluded as lower than any 1.0.0-alpha.N.
16
+
17
+ require "json"
18
+
19
+ # Parse "MAJOR.MINOR.PATCH[-prerelease]" into a comparable structure.
20
+ # Returns nil for anything that isn't a clean semver string.
21
+ def parse(version)
22
+ m = /\A(\d+)\.(\d+)\.(\d+)(?:-(.+))?\z/.match(version.to_s.strip)
23
+ return nil unless m
24
+ { maj: m[1].to_i, min: m[2].to_i, pat: m[3].to_i,
25
+ pre: m[4] ? m[4].split(".") : nil }
26
+ end
27
+
28
+ # Semver precedence (§11). Returns -1, 0, or 1.
29
+ def compare(a, b)
30
+ %i[maj min pat].each do |k|
31
+ return a[k] <=> b[k] unless a[k] == b[k]
32
+ end
33
+ # A version with no prerelease is greater than one with a prerelease.
34
+ return 0 if a[:pre].nil? && b[:pre].nil?
35
+ return 1 if a[:pre].nil?
36
+ return -1 if b[:pre].nil?
37
+ [a[:pre].length, b[:pre].length].max.times do |i|
38
+ x = a[:pre][i]
39
+ y = b[:pre][i]
40
+ return -1 if x.nil?
41
+ return 1 if y.nil?
42
+ xn = x.match?(/\A\d+\z/)
43
+ yn = y.match?(/\A\d+\z/)
44
+ if xn && yn
45
+ return x.to_i <=> y.to_i unless x.to_i == y.to_i
46
+ elsif xn
47
+ return -1 # numeric identifiers rank lower than alphanumeric
48
+ elsif yn
49
+ return 1
50
+ elsif x != y
51
+ return x <=> y
52
+ end
53
+ end
54
+ 0
55
+ end
56
+
57
+ def channel(version)
58
+ return "alpha" if version.include?("-alpha")
59
+ return "beta" if version.include?("-beta")
60
+ "stable"
61
+ end
62
+
63
+ REACH = {
64
+ "alpha" => %w[alpha beta latest],
65
+ "beta" => %w[beta latest],
66
+ "stable" => %w[latest],
67
+ }.freeze
68
+
69
+ current = ARGV[0]
70
+ cur = parse(current)
71
+ exit 0 unless cur
72
+
73
+ begin
74
+ tags = JSON.parse($stdin.read)
75
+ rescue JSON::ParserError, StandardError
76
+ exit 0
77
+ end
78
+ exit 0 unless tags.is_a?(Hash)
79
+
80
+ best = nil
81
+ best_parsed = nil
82
+ REACH[channel(current)].each do |tag|
83
+ raw = tags[tag]
84
+ next unless raw
85
+ parsed = parse(raw)
86
+ next unless parsed
87
+ if compare(parsed, cur) > 0 && (best_parsed.nil? || compare(parsed, best_parsed) > 0)
88
+ best = raw
89
+ best_parsed = parsed
90
+ end
91
+ end
92
+
93
+ print best if best
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # Plastic — `uninstall` verb. Runs via `npx @zalom/plastic uninstall` (bin/plastic.js) or directly.
6
+ # Usage: ruby scripts/uninstall.rb [--claude|--codex|--hermes|--all] [--help]
7
+ #
8
+ # Manifest-driven removal of installed files + legacy-plugin migration. Leaves ~/.plastic/
9
+ # (intent store, config, and the versions.json ledger) in place — the ledger is permanent.
10
+
11
+ require_relative "lib/installer_core"
12
+
13
+ class Uninstall < InstallerCore
14
+ def cli(argv = ARGV)
15
+ if argv.include?("--help") || argv.include?("-h")
16
+ show_help
17
+ return 0
18
+ end
19
+
20
+ selected = agent_keys_from(argv)
21
+ selected = ["claude"] if selected.empty?
22
+
23
+ puts "\n\u{1f9e0} Plastic v#{version} — uninstall\n\n"
24
+ handle_uninstall(selected)
25
+ 0
26
+ end
27
+
28
+ private
29
+
30
+ def show_help
31
+ puts <<~HELP
32
+
33
+ plastic uninstall — remove Plastic from an agent
34
+
35
+ Usage:
36
+ npx @zalom/plastic uninstall [options]
37
+
38
+ Agent options:
39
+ --claude Uninstall from Claude Code (default)
40
+ --codex Uninstall from Codex CLI
41
+ --hermes Uninstall from Hermes
42
+ --all Uninstall from all supported agents
43
+
44
+ Other options:
45
+ -h, --help Show this help
46
+
47
+ Leaves your intent store, config, and version ledger (~/.plastic/) in place.
48
+
49
+ HELP
50
+ end
51
+ end
52
+
53
+ exit(Uninstall.new(package_root: ENV["PLASTIC_PACKAGE_ROOT"] || File.expand_path("..", __dir__)).cli(ARGV)) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,142 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # Plastic — `update` verb. Runs via `npx @zalom/plastic update` (bin/plastic.js) or directly.
6
+ # Usage: ruby scripts/update.rb [--beta|--latest|--alpha] [--claude|--codex|--hermes|--all] [--help]
7
+ #
8
+ # Forward-only version transitions, sourced from npm dist-tags:
9
+ # - no flag = next version on the CURRENT channel (derived from VERSION)
10
+ # - --beta/--latest/--alpha = switch channel. Toward stability (alpha->beta->latest) is
11
+ # frictionless; toward bleeding edge requires confirmation.
12
+ # "Already up to date" is a clean no-op. Performs the switch by delegating the file-sync to
13
+ # `install --reinstall --ledger-action update` for the chosen version via npx.
14
+
15
+ require_relative "lib/installer_core"
16
+
17
+ class Update < InstallerCore
18
+ PKG = "@zalom/plastic"
19
+
20
+ def cli(argv = ARGV)
21
+ if argv.include?("--help") || argv.include?("-h")
22
+ show_help
23
+ return 0
24
+ end
25
+
26
+ iv = installed_version
27
+ unless iv
28
+ warn "Plastic is not installed. Run: npx #{PKG} install --claude"
29
+ return 1
30
+ end
31
+
32
+ requested = requested_channel(argv)
33
+ tags = fetch_dist_tags
34
+ unless tags
35
+ warn "Could not query npm dist-tags. Are you online?"
36
+ return 1
37
+ end
38
+
39
+ res = compute_target(installed_version: iv, dist_tags: tags, requested_channel: requested)
40
+
41
+ case res[:status]
42
+ when :up_to_date
43
+ puts "\u{2705} Plastic v#{iv} is already up to date on the #{channel_for(iv)} channel."
44
+ return 0
45
+ when :unknown_channel
46
+ warn "No published version on the #{requested} channel."
47
+ return 1
48
+ when :ok
49
+ if res[:kind] == :cross_bleeding && !confirm_bleeding(iv, res[:target])
50
+ puts "Aborted."
51
+ return 1
52
+ end
53
+ puts "\u{2b06}\u{fe0f} Updating Plastic #{iv} \u{2192} #{res[:target]}"
54
+ perform_switch(res[:target], agent_args(argv))
55
+ end
56
+ end
57
+
58
+ # Pure decision logic (hermetically testable). Returns a status hash.
59
+ def compute_target(installed_version:, dist_tags:, requested_channel: nil)
60
+ installed_ch = channel_for(installed_version)
61
+ target_ch = requested_channel || installed_ch
62
+ cand = dist_tags[target_ch]
63
+ return { status: :unknown_channel } unless cand
64
+
65
+ if target_ch == installed_ch
66
+ return { status: :up_to_date } unless semver_gt?(cand, installed_version)
67
+ { status: :ok, target: cand, kind: :in_channel }
68
+ else
69
+ bleeding = stability_rank(target_ch) < stability_rank(installed_ch)
70
+ { status: :ok, target: cand, kind: bleeding ? :cross_bleeding : :cross_stable }
71
+ end
72
+ end
73
+
74
+ def installed_version
75
+ path = File.join(plastic_home, "VERSION")
76
+ File.exist?(path) ? File.read(path).strip : nil
77
+ end
78
+
79
+ private
80
+
81
+ def requested_channel(argv)
82
+ return "alpha" if argv.include?("--alpha")
83
+ return "beta" if argv.include?("--beta")
84
+ return "latest" if argv.include?("--latest")
85
+ nil
86
+ end
87
+
88
+ # Agent flags to pass through to the delegated install (default --claude).
89
+ def agent_args(argv)
90
+ flags = agents.map { |a| a[:flag] }.select { |f| argv.include?(f) }
91
+ flags << "--all" if argv.include?("--all")
92
+ flags.empty? ? ["--claude"] : flags
93
+ end
94
+
95
+ def fetch_dist_tags
96
+ raw = `npm view #{PKG} dist-tags --json 2>/dev/null`
97
+ return nil if raw.nil? || raw.strip.empty?
98
+ JSON.parse(raw)
99
+ rescue JSON::ParserError, StandardError
100
+ nil
101
+ end
102
+
103
+ def confirm_bleeding(current, target)
104
+ return false unless $stdin.tty?
105
+ print "Switch from #{current} to the less-stable #{target}? [y/N]: "
106
+ ($stdin.gets&.strip || "").downcase.start_with?("y")
107
+ end
108
+
109
+ # Thin npx-exec glue (not unit-tested; the decision above is). Delegates the file-sync to
110
+ # the target version's install verb, recording the ledger action as `update`.
111
+ def perform_switch(target, agent_flags)
112
+ cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", "update", *agent_flags]
113
+ puts " $ #{cmd.join(" ")}"
114
+ system(*cmd) ? 0 : 1
115
+ end
116
+
117
+ def show_help
118
+ puts <<~HELP
119
+
120
+ plastic update — upgrade Plastic to a newer version
121
+
122
+ Usage:
123
+ npx @zalom/plastic update [options]
124
+
125
+ Channel options (default: stay on the current channel):
126
+ --latest Switch to / advance the stable channel
127
+ --beta Switch to / advance the beta channel
128
+ --alpha Switch to / advance the alpha channel (bleeding edge — confirmed)
129
+
130
+ Agent options (default: --claude):
131
+ --claude --codex --hermes --all
132
+
133
+ Behaviour:
134
+ No flag advances to the next version on your current channel. Switching toward a
135
+ more stable channel is frictionless; switching toward bleeding edge is confirmed.
136
+ Use `versions` to roll back to a previously-installed version.
137
+
138
+ HELP
139
+ end
140
+ end
141
+
142
+ exit(Update.new(package_root: ENV["PLASTIC_PACKAGE_ROOT"] || File.expand_path("..", __dir__)).cli(ARGV)) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # Plastic — `versions` verb. Runs via `npx @zalom/plastic versions` (bin/plastic.js) or directly.
6
+ # Usage: ruby scripts/versions.rb [--downgrade [--version <v>] | --upgrade] [--help]
7
+ #
8
+ # Local time-machine over the append-only versions.json ledger. Navigates ONLY versions you
9
+ # have actually run (the ledger), never npm's full list — fetching brand-new versions is
10
+ # `update`'s job. Lets you roll back to a known-good build after a bad release.
11
+ # - no flag print the ledger table (and offer to continue a rollback)
12
+ # - --downgrade step back one version in your history
13
+ # - --downgrade --version V jump to a specific previously-run version
14
+ # - --upgrade step forward one version (after a rollback)
15
+
16
+ require_relative "lib/installer_core"
17
+
18
+ class Versions < InstallerCore
19
+ PKG = "@zalom/plastic"
20
+
21
+ def cli(argv = ARGV)
22
+ if argv.include?("--help") || argv.include?("-h")
23
+ show_help
24
+ return 0
25
+ end
26
+
27
+ ledger = ledger_read
28
+ if ledger.empty?
29
+ puts "No version history yet (versions.json is empty). Install or update first."
30
+ return 0
31
+ end
32
+
33
+ timeline = version_timeline(ledger)
34
+ current = installed_version || timeline.last
35
+
36
+ if argv.include?("--downgrade")
37
+ explicit = flag_value(argv, "--version")
38
+ target = explicit || step_target(timeline, current, :back)
39
+ return no_target("No earlier version in your history.") unless target
40
+ unless timeline.include?(target)
41
+ warn "#{target} is not in your version history. Choose one of: #{timeline.join(", ")}"
42
+ return 1
43
+ end
44
+ return switch_to(target, current)
45
+ elsif argv.include?("--upgrade")
46
+ target = step_target(timeline, current, :forward)
47
+ return no_target("No later version in your history. Use `update` for new releases.") unless target
48
+ return switch_to(target, current)
49
+ end
50
+
51
+ # No flag: show the table, and detect an in-progress rollback.
52
+ print_table(ledger, current)
53
+ if ledger.last && ledger.last["action"] == "downgrade"
54
+ prev = step_target(timeline, current, :back)
55
+ if prev && $stdin.tty?
56
+ print "\nYou recently rolled back. Go back further to #{prev}? [y/N]: "
57
+ return switch_to(prev, current) if ($stdin.gets&.strip || "").downcase.start_with?("y")
58
+ end
59
+ end
60
+ 0
61
+ end
62
+
63
+ # Unique versions in first-seen (chronological) order — the user's personal timeline.
64
+ def version_timeline(ledger)
65
+ ledger.map { |e| e["version"] }.compact.uniq
66
+ end
67
+
68
+ # Neighbour of `current` in the timeline. direction :back (older) or :forward (newer).
69
+ def step_target(timeline, current, direction)
70
+ i = timeline.index(current)
71
+ return nil if i.nil?
72
+ j = direction == :back ? i - 1 : i + 1
73
+ return nil if j < 0 || j >= timeline.length
74
+ timeline[j]
75
+ end
76
+
77
+ # Direction-derived ledger action for moving current -> target.
78
+ def action_for(target, current)
79
+ cmp = semver_compare(target, current)
80
+ cmp == -1 ? "downgrade" : "update"
81
+ end
82
+
83
+ def installed_version
84
+ path = File.join(plastic_home, "VERSION")
85
+ File.exist?(path) ? File.read(path).strip : nil
86
+ end
87
+
88
+ private
89
+
90
+ def flag_value(argv, name)
91
+ i = argv.index(name)
92
+ return nil unless i && argv[i + 1]
93
+ argv[i + 1]
94
+ end
95
+
96
+ def no_target(msg)
97
+ puts msg
98
+ 0
99
+ end
100
+
101
+ def switch_to(target, current)
102
+ action = action_for(target, current)
103
+ puts "#{action == "downgrade" ? "\u{23ea}" : "\u{23e9}"} #{current} \u{2192} #{target} (#{action})"
104
+ cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", action, "--claude"]
105
+ puts " $ #{cmd.join(" ")}"
106
+ system(*cmd) ? 0 : 1
107
+ end
108
+
109
+ def print_table(ledger, current)
110
+ puts "\nPlastic version history (versions.json — append-only):\n\n"
111
+ puts " %-22s %-10s %s" % ["version", "action", "at"]
112
+ ledger.each do |e|
113
+ marker = e["version"] == current ? "\u{2192} " : " "
114
+ puts " #{marker}%-22s %-10s %s" % [e["version"], e["action"], e["at"]]
115
+ end
116
+ puts "\n \u{2192} = currently installed (v#{current})\n\n"
117
+ end
118
+
119
+ def show_help
120
+ puts <<~HELP
121
+
122
+ plastic versions — manage and roll back your local Plastic versions
123
+
124
+ Usage:
125
+ npx @zalom/plastic versions [options]
126
+
127
+ Options:
128
+ (none) Show the version-history table
129
+ --downgrade Roll back one version in your history
130
+ --downgrade --version V Roll back to a specific previously-run version
131
+ --upgrade Step forward one version in your history
132
+ -h, --help Show this help
133
+
134
+ Navigates only versions you have already run (the append-only ledger). To move to a
135
+ brand-new release, use `update`.
136
+
137
+ HELP
138
+ end
139
+ end
140
+
141
+ exit(Versions.new(package_root: ENV["PLASTIC_PACKAGE_ROOT"] || File.expand_path("..", __dir__)).cli(ARGV)) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,26 @@
1
+ ## Active Intent Gate
2
+
3
+ Before proceeding, resolve the active intent:
4
+
5
+ 1. **Detect store:**
6
+ - Read `~/.plastic/projects.yml`
7
+ - Match CWD against registered project paths
8
+ - If match → project store at `~/.plastic/projects/{slug}/store/`
9
+ - If no match → global store at `~/.plastic/store/`
10
+
11
+ 2. **Find active intent:**
12
+ - Read `INDEX.md` from the detected store
13
+ - Look under `## Active` for intent entries
14
+ - If exactly one active intent → use it
15
+ - If multiple active intents → ask user which one
16
+ - If no active intent → refuse: "No active intent. Create one first with /plastic-creating-intent"
17
+
18
+ 3. **Resolve paths:**
19
+ - Intent directory: `{store}/store/{id}--{slug}/`
20
+ - Spec: `{intent_dir}/spec.md`
21
+ - Plan: `{intent_dir}/plan.md`
22
+ - Checklist: `{intent_dir}/checklist.md`
23
+ - Resources: `{intent_dir}/resources/`
24
+ - Outcome: `{intent_dir}/outcome.md`
25
+
26
+ All lifecycle artifacts MUST be written to the intent directory. Never write to `docs/superpowers/specs/`, `docs/superpowers/plans/`, or any other external path.
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: plastic:auto
2
+ name: plastic-auto
3
3
  description: >-
4
4
  Autonomous intent delivery — agent takes over How and Exec. Use when user says
5
5
  "auto", "take it from here", "deliver this", or when brainstorming-grill-me concludes
@@ -12,10 +12,41 @@ Announce: "Taking over intent [ID] — [name] for autonomous delivery."
12
12
 
13
13
  ## Precondition
14
14
 
15
- An active intent MUST exist in INDEX.md. If none exists, refuse: "No active intent found. Create one first with /plastic:creating-intent."
15
+ An active intent MUST exist in INDEX.md. If none exists, refuse: "No active intent found. Create one first with /plastic-creating-intent."
16
16
 
17
17
  If multiple active intents exist, ask the user which one to deliver (this is the only question auto asks).
18
18
 
19
+ **Picking work when no intent is specified.** If the user says "auto" without naming an
20
+ intent and none is active, consult the dashboard's machine-readable queue to choose the
21
+ next dispatchable intent:
22
+
23
+ ```bash
24
+ ruby ~/.plastic/scripts/dashboard.rb all --json
25
+ ```
26
+
27
+ Work `dispatchable_queue` in `rank` order (these are `defer`/`research` dispositions —
28
+ safe to deliver autonomously). Leave `human_only` and `next_big_thing` for the user — those
29
+ are `drive`/`triage` items the human should lead. See the `plastic-dashboard` skill.
30
+
31
+ ## Arm the Lifecycle Gate (do this FIRST)
32
+
33
+ Immediately after selecting the intent — before any other work — arm auto mode. This
34
+ writes the session bridge that makes the code-edit gate live, so project code cannot be
35
+ edited before the plan exists (the gate applies to YOU, the orchestrator):
36
+
37
+ ```bash
38
+ ruby -r ~/.plastic/scripts/lib/bridge -e \
39
+ 'Bridge.arm_auto(ENV["CLAUDE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
40
+ ```
41
+
42
+ Replace `<ID>`, `<STORE>` (e.g. `~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
43
+ `<dir>` (the `ID--slug` directory), and `<name>`. If `CLAUDE_SESSION_ID` is unset, arming
44
+ is best-effort — proceed regardless; the gate simply won't engage.
45
+
46
+ **Hard rule for the rest of this run:** do NOT edit project code (anything outside the
47
+ intent directory / `~/.plastic/`) until `plan.md` AND `checklist.md` exist for the intent.
48
+ Honor the cycle: What → Why (spec.md) → How (plan.md + actions/ + checklist.md) → Exec.
49
+
19
50
  ## Flags
20
51
 
21
52
  - `--skip-permissions` — bypass hard stops on destructive actions on existing projects. Full trust mode. Default: off.
@@ -67,7 +98,7 @@ If the plan calls for creating a new project (the intent is an implementation in
67
98
  1. Determine project path from `~/.plastic/config.yml` `project_roots` or from intent context
68
99
  2. **Confirm path with user** — this is the ONE human interaction during auto delivery:
69
100
  > "Creating project `<slug>` at `<path>`. Confirm path, or provide alternative."
70
- 3. Invoke `plastic:creating-project` skill
101
+ 3. Invoke `plastic-creating-project` skill
71
102
  4. The global intent is now Completed (creating-project handles this)
72
103
  5. The tactical mirror in the project store becomes the active intent
73
104
  6. Continue execution from the project directory using the tactical intent
@@ -75,7 +106,7 @@ If the plan calls for creating a new project (the intent is an implementation in
75
106
  ## Exec Phase
76
107
 
77
108
  1. If `superpowers:subagent-driven-development` or `superpowers:executing-plans` is available, delegate execution to it
78
- 2. Otherwise invoke `plastic:executing-plan`
109
+ 2. Otherwise invoke `plastic-executing-plan`
79
110
  3. Execute actions from checklist sequentially
80
111
  4. Check off items in `checklist.md` as completed
81
112
  5. Append observations to `## Insights` with `(autonomous)` marker
@@ -111,12 +142,30 @@ During initial project creation, all decisions are non-destructive by definition
111
142
  1. Verify all checklist items are checked
112
143
  2. Write `outcome.md` with detailed results
113
144
  3. Write `## Outcome` summary in the intent file (1-2 sentences)
114
- 4. Review `## Insights` for observations that should spawn future intents. If any:
115
- - Create them (using `plastic:creating-intent` conventions)
145
+ 4. **Release (if configured)**
146
+ 1. Detect project match CWD against paths in `~/.plastic/projects.yml` to find the project slug. If no match, skip to step 5 (default commit-only behavior).
147
+ 2. Read `~/.plastic/projects/{slug}/project.yml`. If the file doesn't exist or has no `release` key, skip to step 5.
148
+ 3. Based on `release.on_complete`:
149
+ - `commit` — git add + commit (same as default, proceed to step 5)
150
+ - `commit_and_push` — git add + commit + push
151
+ - `manual` — skip auto-commit, notify user: "Release configured as manual — commit when ready."
152
+ 4. If `release.verify` is set, run the verify command (e.g. `bundle exec rake test`):
153
+ - **Exit 0 (green):** proceed to sub-step 5
154
+ - **Non-zero (red):** check `release.on_red`:
155
+ - `fix_and_retry` — attempt to fix the failure, re-run verify (max 2 retries)
156
+ - `stop` — write `savepoint.md` with current state, notify user: "Verify failed — savepoint written.", **STOP**
157
+ - `manual` — notify user: "Verify failed: [summary]. Resolve manually."
158
+ 5. If `release.on_green` has items, invoke `plastic-releasing` to handle them (tag, changelog, publish, etc.). Do NOT duplicate release logic — delegate entirely.
159
+ 5. Review `## Insights` for observations that should spawn future intents. If any:
160
+ - Create them (using `plastic-creating-intent` conventions)
116
161
  - Update `chain` in the current intent's frontmatter
117
- 5. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
118
- 6. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
119
- 7. Notify user: "Intent [ID] [name] delivered. [1-2 sentence summary]. See outcome.md for details."
162
+ 6. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
163
+ 7. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
164
+ 8. Disarm the lifecycle gate (auto delivery is finished):
165
+ ```bash
166
+ ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_SESSION_ID"])'
167
+ ```
168
+ 9. Notify user: "Intent [ID] — [name] delivered. [1-2 sentence summary]. See outcome.md for details."
120
169
 
121
170
  ## Error Handling
122
171
 
@@ -125,3 +174,7 @@ If the agent gets stuck (can't resolve a gap, dependency is missing, tests fail
125
174
  2. Write `savepoint.md` with current state
126
175
  3. Notify user: "Blocked on intent [ID] — [name]: [description]. Savepoint written."
127
176
  4. **STOP.** Do not attempt workarounds that could leave the project in a broken state.
177
+
178
+ ## References
179
+
180
+ - Read `references/agent-architecture.md` for the full agent hierarchy (Main Orchestrator, Project Orchestrators, coordination loop) when dispatching agents or understanding autonomous delivery scope
@@ -0,0 +1,92 @@
1
+ {
2
+ "skill_name": "plastic-auto",
3
+ "notes": "Intent 27. Scopes: description triggering (1-8) and behavior/output quality (9). Assertions written after observing Step-3 runs (one clean subagent router per case).",
4
+ "results": {
5
+ "triggering": { "cases": 8, "passed": 8, "pass_at_1": 1.0, "run": "2026-06-10, one subagent per case" },
6
+ "behavior": { "cases": 1, "passed": 1, "evidence": "dogfood: intent 27 itself delivered via auto produced spec->plan->checklist before any code edit; code-gate unit test proves pre-How project-code edits are blocked" }
7
+ },
8
+ "evals": [
9
+ {
10
+ "id": 1, "scope": "triggering", "set": "train",
11
+ "prompt": "auto",
12
+ "expected_output": "Activates plastic-auto (the bare 'auto' keyword is the documented trigger).",
13
+ "files": [],
14
+ "assertions": [
15
+ { "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
16
+ ]
17
+ },
18
+ {
19
+ "id": 2, "scope": "triggering", "set": "train",
20
+ "prompt": "take it from here and deliver intent 27 end to end",
21
+ "expected_output": "Activates plastic-auto (autonomous delivery of an active intent).",
22
+ "files": [],
23
+ "assertions": [
24
+ { "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
25
+ ]
26
+ },
27
+ {
28
+ "id": 3, "scope": "triggering", "set": "validation",
29
+ "prompt": "go fully autonomous on the active intent, don't ask me questions",
30
+ "expected_output": "Activates plastic-auto.",
31
+ "files": [],
32
+ "assertions": [
33
+ { "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
34
+ ]
35
+ },
36
+ {
37
+ "id": 4, "scope": "triggering", "set": "train",
38
+ "prompt": "deliver this intent for me",
39
+ "expected_output": "Activates plastic-auto.",
40
+ "files": [],
41
+ "assertions": [
42
+ { "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
43
+ ]
44
+ },
45
+ {
46
+ "id": 5, "scope": "triggering", "set": "train",
47
+ "prompt": "set up a hook to automatically format the file on every save",
48
+ "expected_output": "Does NOT activate plastic-auto. Near-miss: shares 'auto*' but is a settings/hooks task (update-config).",
49
+ "files": [],
50
+ "assertions": [
51
+ { "type": "code", "check": "router CHOICE != plastic-auto", "observed": "update-config", "result": "pass" }
52
+ ]
53
+ },
54
+ {
55
+ "id": 6, "scope": "triggering", "set": "validation",
56
+ "prompt": "deliver the built package to the dist directory",
57
+ "expected_output": "Does NOT activate plastic-auto. Near-miss: shares 'deliver' but is a build/file task.",
58
+ "files": [],
59
+ "assertions": [
60
+ { "type": "code", "check": "router CHOICE != plastic-auto", "observed": "none", "result": "pass" }
61
+ ]
62
+ },
63
+ {
64
+ "id": 7, "scope": "triggering", "set": "train",
65
+ "prompt": "create a new intent for the dashboard idea",
66
+ "expected_output": "Does NOT activate plastic-auto; activates plastic-creating-intent.",
67
+ "files": [],
68
+ "assertions": [
69
+ { "type": "code", "check": "router CHOICE != plastic-auto", "observed": "plastic-creating-intent", "result": "pass" }
70
+ ]
71
+ },
72
+ {
73
+ "id": 8, "scope": "triggering", "set": "validation",
74
+ "prompt": "what's the status of my active intents?",
75
+ "expected_output": "Does NOT activate plastic-auto; this is a read/continuing/managing-index query.",
76
+ "files": [],
77
+ "assertions": [
78
+ { "type": "code", "check": "router CHOICE != plastic-auto", "observed": "plastic-managing-index", "result": "pass" }
79
+ ]
80
+ },
81
+ {
82
+ "id": 9, "scope": "behavior", "set": "train",
83
+ "prompt": "Active intent X exists with only a '## Intent' section. Deliver it in auto mode.",
84
+ "expected_output": "Arms the lifecycle gate first, then produces spec.md (Why), then plan.md + actions/ + checklist.md (How), and edits NO project code before plan.md + checklist.md exist. Disarms on completion.",
85
+ "files": [],
86
+ "assertions": [
87
+ { "type": "human", "check": "spec.md written before plan.md before any project-code edit", "observed": "dogfood run of intent 27 followed this order", "result": "pass" },
88
+ { "type": "code", "check": "code-gate blocks project-code Edit/Write while pre-How (test/code_gate_test.rb)", "observed": "test green", "result": "pass" }
89
+ ]
90
+ }
91
+ ]
92
+ }