@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.
- package/PLASTIC.md +128 -473
- package/README.md +90 -58
- package/agents/future-intent-researcher.md +1 -1
- package/agents/intent-curator.md +1 -1
- package/bin/plastic.js +57 -0
- package/bin/test +28 -0
- package/deprecations.yml +7 -6
- package/hooks/auto-arm +5 -0
- package/hooks/bash-gate +3 -0
- package/hooks/check-update +12 -8
- package/hooks/code-gate +10 -0
- package/hooks/hooks.json +25 -4
- package/hooks/statusline +50 -10
- package/package.json +2 -2
- package/scripts/dashboard.rb +480 -0
- package/scripts/doctor.rb +973 -0
- package/scripts/hook-auto-arm +52 -0
- package/scripts/hook-bash-gate +53 -0
- package/scripts/hook-code-gate +39 -0
- package/scripts/hook-continue +15 -114
- package/scripts/hook-gate-check +19 -4
- package/scripts/hook-session-start +76 -31
- package/scripts/install.rb +91 -480
- package/scripts/lib/bridge.rb +255 -0
- package/scripts/lib/installer_core.rb +760 -0
- package/scripts/migrate-to-global +1 -1
- package/scripts/select-update-target +93 -0
- package/scripts/uninstall.rb +53 -0
- package/scripts/update.rb +142 -0
- package/scripts/versions.rb +141 -0
- package/skills/_active-intent-gate.md +26 -0
- package/skills/auto/SKILL.md +62 -9
- package/skills/auto/evals/evals.json +92 -0
- package/skills/auto/references/agent-architecture.md +60 -0
- package/skills/brainstorming/SKILL.md +143 -0
- package/skills/brainstorming-grill-me/SKILL.md +5 -5
- package/skills/continuing/SKILL.md +102 -77
- package/skills/continuing/evals/evals.json +136 -0
- package/skills/continuing/references/context-management.md +32 -0
- package/skills/creating-intent/SKILL.md +16 -1
- package/skills/creating-intent/references/lifecycle.md +74 -0
- package/skills/creating-intent/references/wikilinks.md +8 -0
- package/skills/creating-project/SKILL.md +8 -4
- package/skills/creating-project/references/hubs-projects.md +55 -0
- package/skills/dashboard/SKILL.md +92 -0
- package/skills/doctor/SKILL.md +116 -0
- package/skills/doctor/references/gates-stuck-detection.md +38 -0
- package/skills/doctor/report.md +96 -0
- package/skills/evaluating-skills/SKILL.md +140 -0
- package/skills/evaluating-skills/assets/eval-template.json +12 -0
- package/skills/evaluating-skills/evals/evals.json +75 -0
- package/skills/evaluating-skills/references/convention-checks.md +76 -0
- package/skills/evaluating-skills/references/eval-methodology.md +154 -0
- package/skills/executing-plan/SKILL.md +3 -3
- package/skills/install/SKILL.md +56 -8
- package/skills/intent-curator/SKILL.md +3 -3
- package/skills/linking-intents/SKILL.md +5 -1
- package/skills/linking-intents/references/zettelkasten.md +33 -0
- package/skills/managing-index/SKILL.md +5 -1
- package/skills/releasing/SKILL.md +119 -18
- package/skills/releasing/references/deprecations.md +44 -0
- package/skills/research/SKILL.md +114 -0
- package/skills/savepoint/SKILL.md +46 -37
- package/skills/savepoint/references/context-management.md +32 -0
- package/skills/uninstall/SKILL.md +39 -28
- package/skills/update/SKILL.md +41 -36
- package/skills/versions/SKILL.md +65 -0
- package/skills/writing-instructions/SKILL.md +159 -0
- package/skills/writing-instructions/references/agentskills-spec.md +135 -0
- package/skills/writing-plans/SKILL.md +183 -0
- package/templates/agents.md +16 -0
- package/templates/outcome.md +13 -0
- package/templates/project.yml +5 -0
- package/templates/savepoint.md +14 -13
- package/templates/spec.md +25 -0
- 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
|
|
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.
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: plastic
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
115
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
+
}
|