@zalom/plastic 2.0.1 → 2.0.2
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/agents/plastic-enforcer.md +1 -1
- package/bin/plastic +2 -0
- package/docs/help/completion-and-done.md +12 -1
- package/docs/help/roadmaps.md +3 -2
- package/package.json +1 -1
- package/scripts/end-intent +225 -26
- package/scripts/lib/arm.rb +28 -10
- package/scripts/lib/cli/commands/auto_lock.rb +17 -5
- package/scripts/lib/cli/commands/auto_take.rb +38 -1
- package/scripts/lib/cli/commands/doctor.rb +2 -2
- package/scripts/lib/cli/commands/intent_command.rb +6 -1
- package/scripts/lib/cli/commands/intent_end.rb +15 -2
- package/scripts/lib/cli/commands/intent_step.rb +8 -0
- package/scripts/lib/cli/commands/project_links.rb +5 -1
- package/scripts/lib/cli/commands/project_new.rb +24 -1
- package/scripts/lib/cli/commands/render.rb +3 -1
- package/scripts/lib/cli/commands/roadmap_next.rb +10 -3
- package/scripts/lib/cli/commands/roadmap_show.rb +21 -1
- package/scripts/lib/cli/commands/session_commit.rb +1 -1
- package/scripts/lib/cli/commands/sync.rb +10 -1
- package/scripts/lib/hook_replay.rb +51 -45
- package/scripts/lib/installer_core.rb +12 -0
- package/scripts/lib/node_input.rb +13 -5
- package/scripts/lib/roadmap_queue.rb +12 -4
- package/scripts/lib/runner_dispatch.rb +8 -7
- package/scripts/lib/session_git.rb +42 -1
- package/scripts/lib/untouched_scaffold.rb +51 -0
- package/scripts/plastic-lock +24 -28
- package/scripts/project-links +6 -21
- package/scripts/roadmap-graph +10 -4
- package/scripts/rollback.rb +5 -1
- package/scripts/update.rb +7 -1
package/scripts/plastic-lock
CHANGED
|
@@ -15,8 +15,9 @@
|
|
|
15
15
|
# arm take the intent for this session (intent 307): acquire
|
|
16
16
|
# delivery.lock stamped with --mode (default auto) and the
|
|
17
17
|
# provenance flags, provision the code worktree, and point the
|
|
18
|
-
# session at the intent; exit
|
|
19
|
-
# corrupt
|
|
18
|
+
# session at the intent; exit 3 on a held, stale, or excluded
|
|
19
|
+
# lock (another owner's), exit 1 on a corrupt one, each with the
|
|
20
|
+
# public command that resolves it
|
|
20
21
|
# who compact, read-only durable owner/activity view
|
|
21
22
|
# status report the lock file, its freshness, the derived worktree,
|
|
22
23
|
# whether this session is the one delivering the intent, and
|
|
@@ -36,7 +37,8 @@
|
|
|
36
37
|
# configured project roots): exactly one fresh lock this session owns or
|
|
37
38
|
# delegates for. Session resolution no longer reads any per-session state
|
|
38
39
|
# file; that path was retired in 2.0 (intent 307) and 344 G11.
|
|
39
|
-
# Exit 0 on success/report; exit
|
|
40
|
+
# Exit 0 on success/report; exit 3 when another session owns the lock (arm,
|
|
41
|
+
# fix, release, reclaim); exit 1 when the verb is otherwise blocked.
|
|
40
42
|
|
|
41
43
|
require_relative "lib/store_layout"
|
|
42
44
|
require "json"
|
|
@@ -82,7 +84,6 @@ usage! if opts[:mode] && !%w[auto guided].include?(opts[:mode])
|
|
|
82
84
|
session = opts[:session]
|
|
83
85
|
session = ENV["CLAUDE_CODE_SESSION_ID"] if session.nil? || session.strip.empty?
|
|
84
86
|
harness = opts[:harness]
|
|
85
|
-
hint_harness = harness || "claude"
|
|
86
87
|
# The plastic home every hook uses (PLASTIC_HOME, else ~/.plastic); Arm takes the
|
|
87
88
|
# OS home above it, the same convention Worktree resolves projects.yml from.
|
|
88
89
|
plastic_home_env = ENV["PLASTIC_HOME"].to_s.strip
|
|
@@ -149,8 +150,11 @@ intent_id = Arm.intent_id_for(dir)
|
|
|
149
150
|
store = File.dirname(dir)
|
|
150
151
|
key = Arm.resolve_session(session, store: store, intent_id: intent_id)
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
153
|
+
# Exit 3 is Plastic's refusal: the lock belongs to another session, and only
|
|
154
|
+
# its owner (or the person who owns the work) may resolve it.
|
|
155
|
+
def refuse(message)
|
|
156
|
+
warn "plastic-lock: #{message}"
|
|
157
|
+
exit 3
|
|
154
158
|
end
|
|
155
159
|
|
|
156
160
|
case verb
|
|
@@ -167,19 +171,16 @@ when "arm"
|
|
|
167
171
|
"worktree" => wt
|
|
168
172
|
)
|
|
169
173
|
when :held
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
exit 1
|
|
174
|
+
refuse "delivery lock for intent #{intent_id} is held by session " \
|
|
175
|
+
"#{result[:lock] && result[:lock]['owner_session']}; plastic auto lock status #{intent_id} shows it"
|
|
173
176
|
when :stale
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
exit 1
|
|
177
|
+
refuse "delivery lock for intent #{intent_id} is stale (owner " \
|
|
178
|
+
"#{result[:lock] && result[:lock]['owner_session']}); #{Arm.stale_hint(intent_id)}"
|
|
177
179
|
when :excluded
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
exit 1
|
|
180
|
+
refuse "a #{result[:lock] && result[:lock]['type']} lock is active on intent " \
|
|
181
|
+
"#{intent_id}; plastic auto lock status #{intent_id} shows it"
|
|
181
182
|
when :corrupt
|
|
182
|
-
warn "plastic-lock: delivery.lock for intent #{intent_id} is unreadable;
|
|
183
|
+
warn "plastic-lock: delivery.lock for intent #{intent_id} is unreadable; plastic auto lock fix #{intent_id} rewrites it"
|
|
183
184
|
exit 1
|
|
184
185
|
when :inline_refused
|
|
185
186
|
warn "plastic-lock: refusing to arm intent #{intent_id} from a conversation session " \
|
|
@@ -207,32 +208,27 @@ when "status"
|
|
|
207
208
|
when "fix"
|
|
208
209
|
report = Arm.repair(intent_dir: dir, session: key, home: home, harness: harness,
|
|
209
210
|
agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
210
|
-
run_mode: opts[:mode]
|
|
211
|
+
run_mode: opts[:mode])
|
|
211
212
|
puts JSON.pretty_generate(report)
|
|
212
213
|
unless report["status"] == "repaired"
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
exit 1
|
|
214
|
+
refuse "#{report['status']} by #{report['owner']}" \
|
|
215
|
+
"#{report['hint'] ? " (#{report['hint']})" : ''}"
|
|
216
216
|
end
|
|
217
217
|
when "release"
|
|
218
218
|
result = Lock.release(dir, session: key)
|
|
219
|
-
if result == :not_owner
|
|
220
|
-
warn "plastic-lock: not the owner; run plastic-lock status"
|
|
221
|
-
exit 1
|
|
222
|
-
end
|
|
219
|
+
refuse "not the owner; plastic auto lock status #{intent_id} shows who is" if result == :not_owner
|
|
223
220
|
puts "released (#{result})"
|
|
224
221
|
when "reclaim"
|
|
225
222
|
status, lock_data = Lock.takeover(dir, session: key, harness: harness,
|
|
226
223
|
agent: opts[:agent], model: opts[:model],
|
|
227
224
|
thread: opts[:thread], run_mode: opts[:mode])
|
|
228
225
|
if status == :fresh
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
exit 1
|
|
226
|
+
refuse "lock is FRESH and held by #{lock_data['owner_session']}; " \
|
|
227
|
+
"back off (no silent reclaim)"
|
|
232
228
|
end
|
|
233
229
|
report = Arm.repair(intent_dir: dir, session: key, home: home, harness: harness,
|
|
234
230
|
agent: opts[:agent], model: opts[:model], thread: opts[:thread],
|
|
235
|
-
run_mode: opts[:mode]
|
|
231
|
+
run_mode: opts[:mode])
|
|
236
232
|
puts JSON.pretty_generate(report)
|
|
237
233
|
when "delegate"
|
|
238
234
|
usage! if opts[:delegate].nil?
|
package/scripts/project-links
CHANGED
|
@@ -48,10 +48,6 @@ require_relative "lib/revisions_writer"
|
|
|
48
48
|
class ProjectLinks
|
|
49
49
|
DEFAULT_HOME = File.join(Dir.home, ".plastic")
|
|
50
50
|
|
|
51
|
-
# The 72 intent dir audit destination (relative to plastic_home).
|
|
52
|
-
DEFAULT_AUDIT_REL =
|
|
53
|
-
"projects/plastic/store/72--links-graph-projection/resources/audit--links-projection.md"
|
|
54
|
-
|
|
55
51
|
def initialize(plastic_home: DEFAULT_HOME, dry_run: false, audit_path: nil,
|
|
56
52
|
drop_unbacked_links: false, intent: nil, store: nil)
|
|
57
53
|
@plastic_home = plastic_home
|
|
@@ -60,20 +56,7 @@ class ProjectLinks
|
|
|
60
56
|
@intent = intent && intent.to_s
|
|
61
57
|
@store = store
|
|
62
58
|
|
|
63
|
-
|
|
64
|
-
# the plan). With no explicit --audit-path, a dry run writes a `.dry-run.md`
|
|
65
|
-
# sibling, leaving the canonical real-run audit untouched. An explicit
|
|
66
|
-
# --audit-path is always honored verbatim (tests inject it). Mirrors
|
|
67
|
-
# RebuildGraph's discipline exactly.
|
|
68
|
-
canonical = File.join(plastic_home, DEFAULT_AUDIT_REL)
|
|
69
|
-
@audit_path =
|
|
70
|
-
if audit_path
|
|
71
|
-
audit_path
|
|
72
|
-
elsif dry_run
|
|
73
|
-
canonical.sub(/\.md\z/, ".dry-run.md")
|
|
74
|
-
else
|
|
75
|
-
canonical
|
|
76
|
-
end
|
|
59
|
+
@audit_path = audit_path || File.join(Plastic::StoreLayout.global_root(plastic_home), "resources", "audit--links-projection.md")
|
|
77
60
|
end
|
|
78
61
|
|
|
79
62
|
attr_reader :plastic_home, :dry_run, :audit_path, :drop_unbacked_links, :intent, :store
|
|
@@ -330,9 +313,11 @@ class ProjectLinks
|
|
|
330
313
|
"(ambiguous: multiple ## Links headings)"
|
|
331
314
|
end
|
|
332
315
|
|
|
333
|
-
#
|
|
316
|
+
# Return the audit for previews; save it only when applying changes.
|
|
334
317
|
def emit_audit(store_list, results)
|
|
335
318
|
text = render_audit(store_list, results)
|
|
319
|
+
return text if dry_run
|
|
320
|
+
|
|
336
321
|
FileUtils.mkdir_p(File.dirname(audit_path))
|
|
337
322
|
File.write(audit_path, text)
|
|
338
323
|
text
|
|
@@ -348,7 +333,7 @@ class ProjectLinks
|
|
|
348
333
|
|
|
349
334
|
def render_audit(store_list, results)
|
|
350
335
|
lines = []
|
|
351
|
-
lines << "# Audit: store-wide
|
|
336
|
+
lines << "# Audit: store-wide Links projection"
|
|
352
337
|
lines << ""
|
|
353
338
|
lines << "Generated: #{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")}#{dry_run ? " (DRY RUN)" : ""}"
|
|
354
339
|
lines << "Scoped to intent #{intent} only (--intent)." if intent
|
|
@@ -482,6 +467,6 @@ if $PROGRAM_NAME == __FILE__
|
|
|
482
467
|
"regenerated #{totals[:regenerated]}, added #{totals[:added]}, " \
|
|
483
468
|
"unchanged #{totals[:unchanged]}, failed #{totals[:failed]} " \
|
|
484
469
|
"across #{results.size} store(s)."
|
|
485
|
-
puts "Audit: #{tool.audit_path}"
|
|
470
|
+
puts(dry ? "Dry run: no files written." : "Audit: #{tool.audit_path}")
|
|
486
471
|
exit 1 if tool.any_failed?(results)
|
|
487
472
|
end
|
package/scripts/roadmap-graph
CHANGED
|
@@ -62,6 +62,7 @@ module RoadmapGraphCli
|
|
|
62
62
|
end
|
|
63
63
|
if result[:cycle]
|
|
64
64
|
warn "roadmap-graph check: cyclic graph: #{result[:cycle].join(' > ')}"
|
|
65
|
+
warn_dangling(result[:dangling])
|
|
65
66
|
return 1
|
|
66
67
|
end
|
|
67
68
|
if result[:reason]
|
|
@@ -73,14 +74,19 @@ module RoadmapGraphCli
|
|
|
73
74
|
puts "Ready: #{result[:ready].join(', ')}"
|
|
74
75
|
puts "Dead ends: #{result[:dead_ends].join(', ')}" unless result[:dead_ends].empty?
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
warn "roadmap-graph check: graph names #{result[:dangling].map(&:inspect).join(', ')}, no batch entry"
|
|
78
|
-
return 1
|
|
79
|
-
end
|
|
77
|
+
return 1 if warn_dangling(result[:dangling])
|
|
80
78
|
|
|
81
79
|
0
|
|
82
80
|
end
|
|
83
81
|
|
|
82
|
+
# Names every graph id no batch lists; true when there was one.
|
|
83
|
+
def warn_dangling(dangling)
|
|
84
|
+
return false if dangling.to_a.empty?
|
|
85
|
+
|
|
86
|
+
warn "roadmap-graph check: graph names #{dangling.map(&:inspect).join(', ')}, no batch entry"
|
|
87
|
+
true
|
|
88
|
+
end
|
|
89
|
+
|
|
84
90
|
def render(path, dry_run)
|
|
85
91
|
result = RoadmapRender.write(path, dry_run: dry_run)
|
|
86
92
|
unless result[:ok]
|
package/scripts/rollback.rb
CHANGED
|
@@ -89,12 +89,16 @@ class Rollback < InstallerCore
|
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def switch_to(target, current)
|
|
92
|
+
return 3 if refuse_incompatible_store_layout(target)
|
|
93
|
+
|
|
92
94
|
action = action_for(target, current)
|
|
93
95
|
puts "#{action == "downgrade" ? "\u{23ea}" : "\u{23e9}"} #{current} \u{2192} #{target} (#{action})"
|
|
94
96
|
prepare_switch(target, current)
|
|
95
97
|
cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", action, *harness_flags]
|
|
96
98
|
puts " $ #{cmd.join(" ")}"
|
|
97
|
-
system(*cmd)
|
|
99
|
+
return 1 unless system({"PLASTIC_PACKAGE_ROOT" => nil}, *cmd)
|
|
100
|
+
|
|
101
|
+
installed_version == target ? 0 : 1
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
# Before handing off to an older package (a downgrade), strip Plastic's own current hook
|
package/scripts/update.rb
CHANGED
|
@@ -62,6 +62,8 @@ class Update < InstallerCore
|
|
|
62
62
|
warn "No published version on the #{requested} channel."
|
|
63
63
|
return 1
|
|
64
64
|
when :ok
|
|
65
|
+
return 3 if refuse_incompatible_store_layout(res[:target])
|
|
66
|
+
|
|
65
67
|
if res[:kind] == :cross_bleeding && !confirm_bleeding(iv, res[:target], argv)
|
|
66
68
|
puts "Aborted."
|
|
67
69
|
return 1
|
|
@@ -235,10 +237,14 @@ class Update < InstallerCore
|
|
|
235
237
|
# the target version's install verb, recording the ledger action as `update`. On
|
|
236
238
|
# success, commits the re-synced core files and clears the update-check cache
|
|
237
239
|
# (former update skill, lines 124-125).
|
|
238
|
-
def perform_switch(target, agent_flags, switch_runner: ->(cmd) { system(*cmd) })
|
|
240
|
+
def perform_switch(target, agent_flags, switch_runner: ->(cmd) { system({"PLASTIC_PACKAGE_ROOT" => nil}, *cmd) })
|
|
239
241
|
cmd = ["npx", "#{PKG}@#{target}", "install", "--reinstall", "--ledger-action", "update", *agent_flags]
|
|
240
242
|
puts " $ #{cmd.join(" ")}"
|
|
241
243
|
return 1 unless switch_runner.call(cmd)
|
|
244
|
+
unless installed_version == target
|
|
245
|
+
warn "Update did not install #{target}; installed version is #{installed_version.inspect}."
|
|
246
|
+
return 1
|
|
247
|
+
end
|
|
242
248
|
|
|
243
249
|
commit_core_files(target)
|
|
244
250
|
clear_update_check_cache
|