@zalom/plastic 1.0.0-alpha.14 → 1.0.0-alpha.16
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 +18 -18
- package/README.md +13 -6
- package/agents/future-intent-researcher.md +1 -1
- package/agents/intent-curator.md +1 -1
- package/deprecations.yml +6 -5
- package/package.json +1 -1
- package/scripts/doctor.rb +37 -30
- package/scripts/hook-auto-arm +3 -3
- package/scripts/hook-continue +1 -1
- package/scripts/hook-gate-check +3 -3
- package/scripts/hook-session-start +1 -1
- package/scripts/install.rb +175 -119
- package/scripts/lib/bridge.rb +1 -1
- package/scripts/migrate-to-global +1 -1
- package/skills/_active-intent-gate.md +1 -1
- package/skills/auto/SKILL.md +6 -6
- package/skills/auto/evals/evals.json +17 -17
- package/skills/auto/references/agent-architecture.md +1 -1
- package/skills/brainstorming/SKILL.md +8 -8
- package/skills/brainstorming-grill-me/SKILL.md +5 -5
- package/skills/continuing/SKILL.md +4 -4
- package/skills/creating-intent/SKILL.md +1 -1
- package/skills/creating-project/SKILL.md +4 -4
- package/skills/doctor/SKILL.md +4 -4
- package/skills/evaluating-skills/SKILL.md +1 -1
- package/skills/executing-plan/SKILL.md +1 -1
- package/skills/install/SKILL.md +32 -8
- package/skills/intent-curator/SKILL.md +3 -3
- package/skills/linking-intents/SKILL.md +1 -1
- package/skills/managing-index/SKILL.md +1 -1
- package/skills/releasing/SKILL.md +3 -3
- package/skills/research/SKILL.md +2 -2
- package/skills/savepoint/SKILL.md +1 -1
- package/skills/uninstall/SKILL.md +39 -28
- package/skills/update/SKILL.md +2 -2
- package/skills/writing-instructions/SKILL.md +2 -2
- package/skills/writing-plans/SKILL.md +5 -5
- package/templates/agents.md +7 -7
package/scripts/install.rb
CHANGED
|
@@ -165,6 +165,7 @@ def distribute(mode)
|
|
|
165
165
|
"scripts/hook-future-intent-check" => "scripts/hook-future-intent-check",
|
|
166
166
|
"scripts/hook-gate-check" => "scripts/hook-gate-check",
|
|
167
167
|
"scripts/hook-code-gate" => "scripts/hook-code-gate",
|
|
168
|
+
"scripts/hook-auto-arm" => "scripts/hook-auto-arm",
|
|
168
169
|
"scripts/lib/bridge.rb" => "scripts/lib/bridge.rb",
|
|
169
170
|
"scripts/doctor.rb" => "scripts/doctor.rb",
|
|
170
171
|
}
|
|
@@ -257,6 +258,19 @@ end
|
|
|
257
258
|
|
|
258
259
|
# --- Agent adapters ---
|
|
259
260
|
|
|
261
|
+
def manifest_path_for(key, config)
|
|
262
|
+
case key
|
|
263
|
+
when "claude" then File.join(config[:dir], "plastic", "manifest.json")
|
|
264
|
+
else File.join(config[:dir], "plastic-manifest.json")
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def manifest_files(manifest_path)
|
|
269
|
+
return [] unless File.exist?(manifest_path)
|
|
270
|
+
data = JSON.parse(File.read(manifest_path)) rescue {}
|
|
271
|
+
(data["files"] || {}).keys
|
|
272
|
+
end
|
|
273
|
+
|
|
260
274
|
def install_for_agent(key, force)
|
|
261
275
|
config = agent_config(key)
|
|
262
276
|
return { agent: config[:name], success: false, reason: "Unknown agent" } unless config
|
|
@@ -265,20 +279,47 @@ def install_for_agent(key, force)
|
|
|
265
279
|
return { agent: config[:name], success: false, reason: "#{config[:dir]} not found \u{2014} #{config[:name]} not installed?" }
|
|
266
280
|
end
|
|
267
281
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
282
|
+
# Capture the prior manifest so we can prune files that no longer ship
|
|
283
|
+
# (renamed/removed skills) after a re-copy. This gives leftover-free updates.
|
|
284
|
+
old_files = manifest_files(manifest_path_for(key, config))
|
|
285
|
+
|
|
286
|
+
result = case key
|
|
287
|
+
when "claude" then install_claude(config, force)
|
|
288
|
+
when "codex" then install_codex(config, force)
|
|
289
|
+
when "hermes" then install_hermes(config, force)
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
new_files = manifest_files(manifest_path_for(key, config))
|
|
293
|
+
pruned = prune_removed_files(old_files - new_files)
|
|
294
|
+
result[:pruned] = pruned if pruned.positive?
|
|
295
|
+
result
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Delete tracked files present in the old manifest but absent from the new one,
|
|
299
|
+
# then remove any now-empty skill directories they lived in.
|
|
300
|
+
def prune_removed_files(stale_files)
|
|
301
|
+
removed = 0
|
|
302
|
+
dirs = []
|
|
303
|
+
stale_files.each do |f|
|
|
304
|
+
if File.exist?(f)
|
|
305
|
+
File.delete(f)
|
|
306
|
+
removed += 1
|
|
307
|
+
end
|
|
308
|
+
dirs << File.dirname(f)
|
|
272
309
|
end
|
|
310
|
+
dirs.uniq.sort_by { |d| -d.length }.each do |d|
|
|
311
|
+
FileUtils.rmdir(d) if File.directory?(d) && Dir.empty?(d)
|
|
312
|
+
end
|
|
313
|
+
removed
|
|
273
314
|
end
|
|
274
315
|
|
|
275
316
|
def install_claude(config, force)
|
|
276
317
|
hooks_dir = File.join(config[:dir], "hooks")
|
|
277
|
-
|
|
318
|
+
skills_root = File.join(config[:dir], "skills")
|
|
278
319
|
plastic_dir = File.join(config[:dir], "plastic")
|
|
279
320
|
|
|
280
321
|
FileUtils.mkdir_p(hooks_dir)
|
|
281
|
-
FileUtils.mkdir_p(
|
|
322
|
+
FileUtils.mkdir_p(skills_root)
|
|
282
323
|
FileUtils.mkdir_p(plastic_dir)
|
|
283
324
|
|
|
284
325
|
installed = []
|
|
@@ -298,19 +339,19 @@ def install_claude(config, force)
|
|
|
298
339
|
installed << dest
|
|
299
340
|
end
|
|
300
341
|
|
|
301
|
-
# Copy skills
|
|
342
|
+
# Copy skills as flat, hyphen-namespaced personal skills (plastic-<name>/)
|
|
302
343
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
303
|
-
installed +=
|
|
344
|
+
installed += install_skills_flat(skills_source, skills_root) if File.directory?(skills_source)
|
|
304
345
|
|
|
305
346
|
# Write VERSION
|
|
306
347
|
version_file = File.join(plastic_dir, "VERSION")
|
|
307
348
|
File.write(version_file, "#{VERSION}\n")
|
|
308
349
|
installed << version_file
|
|
309
350
|
|
|
310
|
-
#
|
|
311
|
-
|
|
351
|
+
# Remove any prior plugin/marketplace install so the layouts don't coexist
|
|
352
|
+
migrate_legacy_plugin(config[:dir])
|
|
312
353
|
|
|
313
|
-
# Merge hooks +
|
|
354
|
+
# Merge hooks + statusline into settings.json (no plugin registration)
|
|
314
355
|
settings_path = File.join(config[:dir], "settings.json")
|
|
315
356
|
merge_claude_hooks(settings_path)
|
|
316
357
|
|
|
@@ -322,99 +363,106 @@ def install_claude(config, force)
|
|
|
322
363
|
end
|
|
323
364
|
|
|
324
365
|
def install_codex(config, force)
|
|
325
|
-
skills_dir = File.join(config[:dir], "skills", "plastic")
|
|
326
|
-
FileUtils.mkdir_p(skills_dir)
|
|
327
|
-
|
|
328
366
|
installed = []
|
|
329
367
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
330
|
-
installed +=
|
|
331
|
-
|
|
332
|
-
manifest_path = File.join(config[:dir], "plastic-manifest.json")
|
|
333
|
-
write_manifest(installed, manifest_path)
|
|
368
|
+
installed += install_skills_flat(skills_source, File.join(config[:dir], "skills")) if File.directory?(skills_source)
|
|
334
369
|
|
|
370
|
+
write_manifest(installed, File.join(config[:dir], "plastic-manifest.json"))
|
|
335
371
|
{ agent: config[:name], success: true, files: installed.size }
|
|
336
372
|
end
|
|
337
373
|
|
|
338
374
|
def install_hermes(config, force)
|
|
339
|
-
skills_dir = File.join(config[:dir], "skills", "plastic")
|
|
340
|
-
FileUtils.mkdir_p(skills_dir)
|
|
341
|
-
|
|
342
375
|
installed = []
|
|
343
376
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
344
|
-
installed +=
|
|
345
|
-
|
|
346
|
-
manifest_path = File.join(config[:dir], "plastic-manifest.json")
|
|
347
|
-
write_manifest(installed, manifest_path)
|
|
377
|
+
installed += install_skills_flat(skills_source, File.join(config[:dir], "skills")) if File.directory?(skills_source)
|
|
348
378
|
|
|
379
|
+
write_manifest(installed, File.join(config[:dir], "plastic-manifest.json"))
|
|
349
380
|
{ agent: config[:name], success: true, files: installed.size }
|
|
350
381
|
end
|
|
351
382
|
|
|
352
|
-
#
|
|
383
|
+
# Copy each skills/<name>/ to <skills_root>/plastic-<name>/ (flat, namespaced by
|
|
384
|
+
# directory name — the only personal-skill namespacing Claude Code supports).
|
|
385
|
+
# The non-skill `_active-intent-gate.md` is relocated to ~/.plastic/ instead.
|
|
386
|
+
def install_skills_flat(skills_source, skills_root)
|
|
387
|
+
installed = []
|
|
388
|
+
FileUtils.mkdir_p(skills_root)
|
|
389
|
+
|
|
390
|
+
Dir.children(skills_source).reject { |e| e.start_with?(".") }.each do |entry|
|
|
391
|
+
src = File.join(skills_source, entry)
|
|
392
|
+
if File.directory?(src)
|
|
393
|
+
installed += copy_dir_recursive(src, File.join(skills_root, "plastic-#{entry}"))
|
|
394
|
+
elsif entry == "_active-intent-gate.md"
|
|
395
|
+
FileUtils.mkdir_p(PLASTIC_HOME)
|
|
396
|
+
dest = File.join(PLASTIC_HOME, entry)
|
|
397
|
+
FileUtils.cp(src, dest)
|
|
398
|
+
installed << dest
|
|
399
|
+
end
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
installed
|
|
403
|
+
end
|
|
404
|
+
|
|
405
|
+
# --- Legacy plugin migration ---
|
|
406
|
+
|
|
407
|
+
# Earlier versions registered Plastic as a local marketplace plugin
|
|
408
|
+
# (plastic@plastic) for skill discovery. The current model uses flat,
|
|
409
|
+
# hyphen-namespaced personal skills, so any prior plugin layout is removed here
|
|
410
|
+
# to stop the two coexisting. Returns a list of what was migrated.
|
|
411
|
+
def migrate_legacy_plugin(claude_dir)
|
|
412
|
+
removed = []
|
|
413
|
+
|
|
414
|
+
legacy_dirs = [
|
|
415
|
+
File.join(claude_dir, "plugins", "marketplaces", "plastic"),
|
|
416
|
+
File.join(claude_dir, "plugins", "cache", "plastic"),
|
|
417
|
+
File.join(claude_dir, "skills", "plastic"), # old nested skills layout
|
|
418
|
+
]
|
|
419
|
+
legacy_dirs.each do |d|
|
|
420
|
+
if File.directory?(d)
|
|
421
|
+
FileUtils.rm_rf(d)
|
|
422
|
+
removed << d
|
|
423
|
+
end
|
|
424
|
+
end
|
|
353
425
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
426
|
+
# settings.json: drop enabledPlugins + extraKnownMarketplaces entries
|
|
427
|
+
settings_path = File.join(claude_dir, "settings.json")
|
|
428
|
+
settings = read_json_safe(settings_path)
|
|
429
|
+
if settings
|
|
430
|
+
changed = false
|
|
431
|
+
if settings.dig("enabledPlugins", "plastic@plastic")
|
|
432
|
+
settings["enabledPlugins"].delete("plastic@plastic")
|
|
433
|
+
settings.delete("enabledPlugins") if settings["enabledPlugins"].empty?
|
|
434
|
+
changed = true
|
|
435
|
+
end
|
|
436
|
+
if settings.dig("extraKnownMarketplaces", "plastic")
|
|
437
|
+
settings["extraKnownMarketplaces"].delete("plastic")
|
|
438
|
+
settings.delete("extraKnownMarketplaces") if settings["extraKnownMarketplaces"].empty?
|
|
439
|
+
changed = true
|
|
440
|
+
end
|
|
441
|
+
if changed
|
|
442
|
+
write_json_atomic(settings_path, settings)
|
|
443
|
+
removed << "settings.json: plastic@plastic plugin registration"
|
|
444
|
+
end
|
|
445
|
+
end
|
|
357
446
|
|
|
358
|
-
|
|
447
|
+
# plugins/known_marketplaces.json: drop the plastic entry
|
|
448
|
+
known_path = File.join(claude_dir, "plugins", "known_marketplaces.json")
|
|
449
|
+
known = read_json_safe(known_path)
|
|
450
|
+
if known.is_a?(Hash) && known.key?("plastic")
|
|
451
|
+
known.delete("plastic")
|
|
452
|
+
write_json_atomic(known_path, known)
|
|
453
|
+
removed << "known_marketplaces.json: plastic"
|
|
454
|
+
end
|
|
359
455
|
|
|
360
|
-
|
|
361
|
-
"
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
"author" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" },
|
|
365
|
-
"homepage" => "https://github.com/zalom/plastic",
|
|
366
|
-
"repository" => "https://github.com/zalom/plastic",
|
|
367
|
-
"license" => "MIT",
|
|
368
|
-
"keywords" => %w[intent-driven state-management zettelkasten ai-agent]
|
|
369
|
-
}
|
|
370
|
-
write_json_atomic(File.join(plugin_meta_dir, "plugin.json"), plugin_json)
|
|
371
|
-
|
|
372
|
-
marketplace_json = {
|
|
373
|
-
"name" => "plastic",
|
|
374
|
-
"description" => "Marketplace for Plastic — intent-driven state management",
|
|
375
|
-
"owner" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" },
|
|
376
|
-
"plugins" => [{
|
|
377
|
-
"name" => "plastic",
|
|
378
|
-
"description" => "Intent-driven state management for AI coding sessions",
|
|
379
|
-
"version" => VERSION,
|
|
380
|
-
"source" => "./",
|
|
381
|
-
"author" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" }
|
|
382
|
-
}]
|
|
383
|
-
}
|
|
384
|
-
write_json_atomic(File.join(plugin_meta_dir, "marketplace.json"), marketplace_json)
|
|
456
|
+
unless removed.empty?
|
|
457
|
+
puts " \u{1f9f9} Migrated legacy plugin install:"
|
|
458
|
+
removed.each { |r| puts " - #{tilde(r)}" }
|
|
459
|
+
end
|
|
385
460
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
copy_dir_recursive(skills_source, skills_dest)
|
|
392
|
-
end
|
|
393
|
-
|
|
394
|
-
# Sync agents
|
|
395
|
-
agents_source = File.join(PACKAGE_ROOT, "agents")
|
|
396
|
-
agents_dest = File.join(marketplace_dir, "agents")
|
|
397
|
-
if File.directory?(agents_source)
|
|
398
|
-
FileUtils.rm_rf(agents_dest)
|
|
399
|
-
copy_dir_recursive(agents_source, agents_dest)
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
# Write .claude/settings.json (permissions)
|
|
403
|
-
claude_settings_dir = File.join(marketplace_dir, ".claude")
|
|
404
|
-
FileUtils.mkdir_p(claude_settings_dir)
|
|
405
|
-
write_json_atomic(File.join(claude_settings_dir, "settings.json"), {
|
|
406
|
-
"permissions" => {
|
|
407
|
-
"allow" => [
|
|
408
|
-
"Bash(ruby *)",
|
|
409
|
-
"Bash(ls *)",
|
|
410
|
-
"Bash(find *)",
|
|
411
|
-
"Bash(git *)",
|
|
412
|
-
"Bash(mkdir *)",
|
|
413
|
-
"Bash(chmod *)",
|
|
414
|
-
"mcp__serena__*"
|
|
415
|
-
]
|
|
416
|
-
}
|
|
417
|
-
})
|
|
461
|
+
removed
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def tilde(path)
|
|
465
|
+
path.sub(Dir.home, "~")
|
|
418
466
|
end
|
|
419
467
|
|
|
420
468
|
# --- settings.json merge (read-modify-write, never clobber) ---
|
|
@@ -485,13 +533,8 @@ def merge_claude_hooks(settings_path)
|
|
|
485
533
|
|
|
486
534
|
settings["statusLine"] = { "type" => "command", "command" => "#{hook_dir}/plastic-statusline" }
|
|
487
535
|
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
plugins["plastic@plastic"] = true
|
|
491
|
-
|
|
492
|
-
# Register the marketplace source
|
|
493
|
-
marketplaces = settings["extraKnownMarketplaces"] ||= {}
|
|
494
|
-
marketplaces["plastic"] ||= { "source" => { "source" => "github", "repo" => "zalom/plastic" } }
|
|
536
|
+
# No plugin/marketplace registration: skills are flat personal skills
|
|
537
|
+
# (plastic-<name>/) discovered directly from ~/.claude/skills.
|
|
495
538
|
|
|
496
539
|
write_json_atomic(settings_path, settings)
|
|
497
540
|
end
|
|
@@ -525,14 +568,25 @@ def handle_uninstall(agents)
|
|
|
525
568
|
next unless config
|
|
526
569
|
|
|
527
570
|
result = uninstall_agent(key, config)
|
|
528
|
-
|
|
529
|
-
puts " \u{2705} #{config[:name]}: uninstalled (#{result[:files]} files removed)"
|
|
530
|
-
else
|
|
571
|
+
unless result[:success]
|
|
531
572
|
puts " \u{26a0}\u{fe0f} #{config[:name]}: #{result[:reason]}"
|
|
573
|
+
next
|
|
532
574
|
end
|
|
575
|
+
|
|
576
|
+
puts " \u{2705} #{config[:name]}: uninstalled (#{result[:files]} files removed)"
|
|
577
|
+
result[:removed].each { |r| puts " removed: #{tilde(r)}" }
|
|
533
578
|
end
|
|
534
579
|
|
|
535
|
-
|
|
580
|
+
# What was deliberately left behind
|
|
581
|
+
puts "\n Left in place (not removed):"
|
|
582
|
+
puts " - #{tilde(PLASTIC_HOME)} (your intent store, history, and projects)"
|
|
583
|
+
puts " - any non-Plastic entries in settings.json"
|
|
584
|
+
|
|
585
|
+
puts "\n Verify removal:"
|
|
586
|
+
puts " ls ~/.claude/skills | grep '^plastic-' # → no output"
|
|
587
|
+
puts " ls ~/.claude/hooks | grep '^plastic-' # → no output"
|
|
588
|
+
puts " grep -c plastic ~/.claude/settings.json # → only hook refs gone"
|
|
589
|
+
puts "\n To also delete your intent store: rm -rf #{tilde(PLASTIC_HOME)}\n\n"
|
|
536
590
|
end
|
|
537
591
|
|
|
538
592
|
def uninstall_agent(key, config)
|
|
@@ -540,45 +594,47 @@ def uninstall_agent(key, config)
|
|
|
540
594
|
return { success: false, reason: "#{config[:dir]} not found" }
|
|
541
595
|
end
|
|
542
596
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
else File.join(config[:dir], "plastic-manifest.json")
|
|
546
|
-
end
|
|
547
|
-
|
|
548
|
-
files_removed = 0
|
|
597
|
+
removed = []
|
|
598
|
+
manifest_path = manifest_path_for(key, config)
|
|
549
599
|
|
|
550
600
|
if File.exist?(manifest_path)
|
|
551
601
|
manifest = JSON.parse(File.read(manifest_path)) rescue {}
|
|
552
602
|
(manifest["files"] || {}).each_key do |f|
|
|
553
603
|
if File.exist?(f)
|
|
554
604
|
File.delete(f)
|
|
555
|
-
|
|
605
|
+
removed << f
|
|
556
606
|
end
|
|
557
607
|
end
|
|
558
608
|
File.delete(manifest_path)
|
|
559
|
-
|
|
609
|
+
removed << manifest_path
|
|
560
610
|
end
|
|
561
611
|
|
|
562
|
-
#
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
612
|
+
# Remove flat plastic-<name>/ skill dirs (now-empty after manifest deletion,
|
|
613
|
+
# plus any the manifest missed) and the plastic state dir.
|
|
614
|
+
skills_root = File.join(config[:dir], "skills")
|
|
615
|
+
if File.directory?(skills_root)
|
|
616
|
+
Dir.children(skills_root).select { |e| e.start_with?("plastic-") }.each do |d|
|
|
617
|
+
full = File.join(skills_root, d)
|
|
618
|
+
FileUtils.rm_rf(full)
|
|
619
|
+
removed << full
|
|
620
|
+
end
|
|
621
|
+
end
|
|
572
622
|
|
|
573
|
-
|
|
623
|
+
[File.join(config[:dir], "plastic")].each do |d|
|
|
624
|
+
if File.directory?(d)
|
|
625
|
+
FileUtils.rm_rf(d)
|
|
626
|
+
removed << d
|
|
627
|
+
end
|
|
628
|
+
end
|
|
574
629
|
|
|
575
|
-
#
|
|
630
|
+
# Claude Code: clean hooks/statusline and any legacy plugin registration
|
|
576
631
|
if key == "claude"
|
|
577
632
|
settings_path = File.join(config[:dir], "settings.json")
|
|
578
633
|
remove_claude_hooks(settings_path) if File.exist?(settings_path)
|
|
634
|
+
removed.concat(migrate_legacy_plugin(config[:dir]))
|
|
579
635
|
end
|
|
580
636
|
|
|
581
|
-
{ success: true, files:
|
|
637
|
+
{ success: true, files: removed.size, removed: removed }
|
|
582
638
|
end
|
|
583
639
|
|
|
584
640
|
def remove_claude_hooks(settings_path)
|
package/scripts/lib/bridge.rb
CHANGED
|
@@ -211,7 +211,7 @@ module Bridge
|
|
|
211
211
|
|
|
212
212
|
id = intent_info["id"]
|
|
213
213
|
"intent #{id} has not reached How — write plan.md + checklist.md before " \
|
|
214
|
-
"editing project code. Run plastic
|
|
214
|
+
"editing project code. Run plastic-auto or plastic-writing-plans first. " \
|
|
215
215
|
"(blocked edit: #{file_abs})"
|
|
216
216
|
end
|
|
217
217
|
|
|
@@ -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
|
|
|
@@ -13,7 +13,7 @@ Before proceeding, resolve the active intent:
|
|
|
13
13
|
- Look under `## Active` for intent entries
|
|
14
14
|
- If exactly one active intent → use it
|
|
15
15
|
- If multiple active intents → ask user which one
|
|
16
|
-
- If no active intent → refuse: "No active intent. Create one first with /plastic
|
|
16
|
+
- If no active intent → refuse: "No active intent. Create one first with /plastic-creating-intent"
|
|
17
17
|
|
|
18
18
|
3. **Resolve paths:**
|
|
19
19
|
- Intent directory: `{store}/store/{id}--{slug}/`
|
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,7 +12,7 @@ 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
|
|
|
@@ -86,7 +86,7 @@ If the plan calls for creating a new project (the intent is an implementation in
|
|
|
86
86
|
1. Determine project path from `~/.plastic/config.yml` `project_roots` or from intent context
|
|
87
87
|
2. **Confirm path with user** — this is the ONE human interaction during auto delivery:
|
|
88
88
|
> "Creating project `<slug>` at `<path>`. Confirm path, or provide alternative."
|
|
89
|
-
3. Invoke `plastic
|
|
89
|
+
3. Invoke `plastic-creating-project` skill
|
|
90
90
|
4. The global intent is now Completed (creating-project handles this)
|
|
91
91
|
5. The tactical mirror in the project store becomes the active intent
|
|
92
92
|
6. Continue execution from the project directory using the tactical intent
|
|
@@ -94,7 +94,7 @@ If the plan calls for creating a new project (the intent is an implementation in
|
|
|
94
94
|
## Exec Phase
|
|
95
95
|
|
|
96
96
|
1. If `superpowers:subagent-driven-development` or `superpowers:executing-plans` is available, delegate execution to it
|
|
97
|
-
2. Otherwise invoke `plastic
|
|
97
|
+
2. Otherwise invoke `plastic-executing-plan`
|
|
98
98
|
3. Execute actions from checklist sequentially
|
|
99
99
|
4. Check off items in `checklist.md` as completed
|
|
100
100
|
5. Append observations to `## Insights` with `(autonomous)` marker
|
|
@@ -143,9 +143,9 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
143
143
|
- `fix_and_retry` — attempt to fix the failure, re-run verify (max 2 retries)
|
|
144
144
|
- `stop` — write `savepoint.md` with current state, notify user: "Verify failed — savepoint written.", **STOP**
|
|
145
145
|
- `manual` — notify user: "Verify failed: [summary]. Resolve manually."
|
|
146
|
-
5. If `release.on_green` has items, invoke `plastic
|
|
146
|
+
5. If `release.on_green` has items, invoke `plastic-releasing` to handle them (tag, changelog, publish, etc.). Do NOT duplicate release logic — delegate entirely.
|
|
147
147
|
5. Review `## Insights` for observations that should spawn future intents. If any:
|
|
148
|
-
- Create them (using `plastic
|
|
148
|
+
- Create them (using `plastic-creating-intent` conventions)
|
|
149
149
|
- Update `chain` in the current intent's frontmatter
|
|
150
150
|
6. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
151
151
|
7. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"skill_name": "plastic
|
|
2
|
+
"skill_name": "plastic-auto",
|
|
3
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
4
|
"results": {
|
|
5
5
|
"triggering": { "cases": 8, "passed": 8, "pass_at_1": 1.0, "run": "2026-06-10, one subagent per case" },
|
|
@@ -9,73 +9,73 @@
|
|
|
9
9
|
{
|
|
10
10
|
"id": 1, "scope": "triggering", "set": "train",
|
|
11
11
|
"prompt": "auto",
|
|
12
|
-
"expected_output": "Activates plastic
|
|
12
|
+
"expected_output": "Activates plastic-auto (the bare 'auto' keyword is the documented trigger).",
|
|
13
13
|
"files": [],
|
|
14
14
|
"assertions": [
|
|
15
|
-
{ "type": "code", "check": "router CHOICE == plastic
|
|
15
|
+
{ "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
|
|
16
16
|
]
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"id": 2, "scope": "triggering", "set": "train",
|
|
20
20
|
"prompt": "take it from here and deliver intent 27 end to end",
|
|
21
|
-
"expected_output": "Activates plastic
|
|
21
|
+
"expected_output": "Activates plastic-auto (autonomous delivery of an active intent).",
|
|
22
22
|
"files": [],
|
|
23
23
|
"assertions": [
|
|
24
|
-
{ "type": "code", "check": "router CHOICE == plastic
|
|
24
|
+
{ "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
|
|
25
25
|
]
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"id": 3, "scope": "triggering", "set": "validation",
|
|
29
29
|
"prompt": "go fully autonomous on the active intent, don't ask me questions",
|
|
30
|
-
"expected_output": "Activates plastic
|
|
30
|
+
"expected_output": "Activates plastic-auto.",
|
|
31
31
|
"files": [],
|
|
32
32
|
"assertions": [
|
|
33
|
-
{ "type": "code", "check": "router CHOICE == plastic
|
|
33
|
+
{ "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
|
|
34
34
|
]
|
|
35
35
|
},
|
|
36
36
|
{
|
|
37
37
|
"id": 4, "scope": "triggering", "set": "train",
|
|
38
38
|
"prompt": "deliver this intent for me",
|
|
39
|
-
"expected_output": "Activates plastic
|
|
39
|
+
"expected_output": "Activates plastic-auto.",
|
|
40
40
|
"files": [],
|
|
41
41
|
"assertions": [
|
|
42
|
-
{ "type": "code", "check": "router CHOICE == plastic
|
|
42
|
+
{ "type": "code", "check": "router CHOICE == plastic-auto", "observed": "plastic-auto", "result": "pass" }
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"id": 5, "scope": "triggering", "set": "train",
|
|
47
47
|
"prompt": "set up a hook to automatically format the file on every save",
|
|
48
|
-
"expected_output": "Does NOT activate plastic
|
|
48
|
+
"expected_output": "Does NOT activate plastic-auto. Near-miss: shares 'auto*' but is a settings/hooks task (update-config).",
|
|
49
49
|
"files": [],
|
|
50
50
|
"assertions": [
|
|
51
|
-
{ "type": "code", "check": "router CHOICE != plastic
|
|
51
|
+
{ "type": "code", "check": "router CHOICE != plastic-auto", "observed": "update-config", "result": "pass" }
|
|
52
52
|
]
|
|
53
53
|
},
|
|
54
54
|
{
|
|
55
55
|
"id": 6, "scope": "triggering", "set": "validation",
|
|
56
56
|
"prompt": "deliver the built package to the dist directory",
|
|
57
|
-
"expected_output": "Does NOT activate plastic
|
|
57
|
+
"expected_output": "Does NOT activate plastic-auto. Near-miss: shares 'deliver' but is a build/file task.",
|
|
58
58
|
"files": [],
|
|
59
59
|
"assertions": [
|
|
60
|
-
{ "type": "code", "check": "router CHOICE != plastic
|
|
60
|
+
{ "type": "code", "check": "router CHOICE != plastic-auto", "observed": "none", "result": "pass" }
|
|
61
61
|
]
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"id": 7, "scope": "triggering", "set": "train",
|
|
65
65
|
"prompt": "create a new intent for the dashboard idea",
|
|
66
|
-
"expected_output": "Does NOT activate plastic
|
|
66
|
+
"expected_output": "Does NOT activate plastic-auto; activates plastic-creating-intent.",
|
|
67
67
|
"files": [],
|
|
68
68
|
"assertions": [
|
|
69
|
-
{ "type": "code", "check": "router CHOICE != plastic
|
|
69
|
+
{ "type": "code", "check": "router CHOICE != plastic-auto", "observed": "plastic-creating-intent", "result": "pass" }
|
|
70
70
|
]
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"id": 8, "scope": "triggering", "set": "validation",
|
|
74
74
|
"prompt": "what's the status of my active intents?",
|
|
75
|
-
"expected_output": "Does NOT activate plastic
|
|
75
|
+
"expected_output": "Does NOT activate plastic-auto; this is a read/continuing/managing-index query.",
|
|
76
76
|
"files": [],
|
|
77
77
|
"assertions": [
|
|
78
|
-
{ "type": "code", "check": "router CHOICE != plastic
|
|
78
|
+
{ "type": "code", "check": "router CHOICE != plastic-auto", "observed": "plastic-managing-index", "result": "pass" }
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -37,7 +37,7 @@ Rules:
|
|
|
37
37
|
|
|
38
38
|
Human owns What and Why for human-initiated intents. Agent assists (research,
|
|
39
39
|
exploration) but human drives until handoff. When Why is complete — or human
|
|
40
|
-
triggers `plastic
|
|
40
|
+
triggers `plastic-auto` — the agent takes over How and Exec autonomously.
|
|
41
41
|
|
|
42
42
|
- **Safe-by-default:** Agent always prefers non-destructive routes (rename vs
|
|
43
43
|
delete, additive migrations, backups before changes). Destructive actions on
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: plastic
|
|
2
|
+
name: plastic-brainstorming
|
|
3
3
|
description: "Explore intent requirements and design before implementation. Produces spec.md in the active intent directory."
|
|
4
4
|
---
|
|
5
5
|
|
|
@@ -20,7 +20,7 @@ Do NOT invoke any implementation skill, write any code, scaffold any project, or
|
|
|
20
20
|
Before proceeding, resolve the active intent:
|
|
21
21
|
|
|
22
22
|
1. **Detect store:** Read `~/.plastic/projects.yml`, match CWD against registered project paths. If match → project store at `~/.plastic/projects/{slug}/store/`. If no match → global store at `~/.plastic/store/`.
|
|
23
|
-
2. **Find active intent:** Read `INDEX.md` from the detected store. Look under `## Active`. If exactly one → use it. If multiple → ask which. If none → refuse: "No active intent. Create one first with /plastic
|
|
23
|
+
2. **Find active intent:** Read `INDEX.md` from the detected store. Look under `## Active`. If exactly one → use it. If multiple → ask which. If none → refuse: "No active intent. Create one first with /plastic-creating-intent"
|
|
24
24
|
3. **Resolve intent directory:** `{store}/store/{id}--{slug}/`
|
|
25
25
|
|
|
26
26
|
All artifacts go to the intent directory. Never write to external paths.
|
|
@@ -40,7 +40,7 @@ You MUST create a task for each of these items and complete them in order:
|
|
|
40
40
|
5. **Write spec** — save to `{intent_dir}/spec.md` and commit to store repo
|
|
41
41
|
6. **Spec self-review** — placeholder scan, consistency, scope, ambiguity
|
|
42
42
|
7. **User reviews written spec** — ask user to review before proceeding
|
|
43
|
-
8. **Transition to planning** — invoke `plastic
|
|
43
|
+
8. **Transition to planning** — invoke `plastic-writing-plans`
|
|
44
44
|
|
|
45
45
|
## Process Flow
|
|
46
46
|
|
|
@@ -54,7 +54,7 @@ digraph brainstorming {
|
|
|
54
54
|
"Write spec" [shape=box];
|
|
55
55
|
"Spec self-review\n(fix inline)" [shape=box];
|
|
56
56
|
"User reviews spec?" [shape=diamond];
|
|
57
|
-
"Invoke plastic
|
|
57
|
+
"Invoke plastic-writing-plans" [shape=doublecircle];
|
|
58
58
|
|
|
59
59
|
"Explore project context" -> "Ask clarifying questions";
|
|
60
60
|
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
|
@@ -65,11 +65,11 @@ digraph brainstorming {
|
|
|
65
65
|
"Write spec" -> "Spec self-review\n(fix inline)";
|
|
66
66
|
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
|
67
67
|
"User reviews spec?" -> "Write spec" [label="changes requested"];
|
|
68
|
-
"User reviews spec?" -> "Invoke plastic
|
|
68
|
+
"User reviews spec?" -> "Invoke plastic-writing-plans" [label="approved"];
|
|
69
69
|
}
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
**The terminal state is invoking `plastic
|
|
72
|
+
**The terminal state is invoking `plastic-writing-plans`.** Do NOT invoke any other implementation skill. The ONLY skill you invoke after brainstorming is `plastic-writing-plans`.
|
|
73
73
|
|
|
74
74
|
## The Process
|
|
75
75
|
|
|
@@ -130,8 +130,8 @@ After the spec review loop passes, ask the user to review the written spec befor
|
|
|
130
130
|
Wait for the user's response. If they request changes, make them and re-run the spec review loop. Only proceed once the user approves.
|
|
131
131
|
|
|
132
132
|
**Implementation:**
|
|
133
|
-
- Invoke `plastic
|
|
134
|
-
- Do NOT invoke any other skill. `plastic
|
|
133
|
+
- Invoke `plastic-writing-plans` to create the implementation plan
|
|
134
|
+
- Do NOT invoke any other skill. `plastic-writing-plans` is the next step.
|
|
135
135
|
|
|
136
136
|
## Key Principles
|
|
137
137
|
|