@zalom/plastic 1.0.0-alpha.15 → 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 +174 -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
|
@@ -258,6 +258,19 @@ end
|
|
|
258
258
|
|
|
259
259
|
# --- Agent adapters ---
|
|
260
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
|
+
|
|
261
274
|
def install_for_agent(key, force)
|
|
262
275
|
config = agent_config(key)
|
|
263
276
|
return { agent: config[:name], success: false, reason: "Unknown agent" } unless config
|
|
@@ -266,20 +279,47 @@ def install_for_agent(key, force)
|
|
|
266
279
|
return { agent: config[:name], success: false, reason: "#{config[:dir]} not found \u{2014} #{config[:name]} not installed?" }
|
|
267
280
|
end
|
|
268
281
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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)
|
|
273
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
|
|
274
314
|
end
|
|
275
315
|
|
|
276
316
|
def install_claude(config, force)
|
|
277
317
|
hooks_dir = File.join(config[:dir], "hooks")
|
|
278
|
-
|
|
318
|
+
skills_root = File.join(config[:dir], "skills")
|
|
279
319
|
plastic_dir = File.join(config[:dir], "plastic")
|
|
280
320
|
|
|
281
321
|
FileUtils.mkdir_p(hooks_dir)
|
|
282
|
-
FileUtils.mkdir_p(
|
|
322
|
+
FileUtils.mkdir_p(skills_root)
|
|
283
323
|
FileUtils.mkdir_p(plastic_dir)
|
|
284
324
|
|
|
285
325
|
installed = []
|
|
@@ -299,19 +339,19 @@ def install_claude(config, force)
|
|
|
299
339
|
installed << dest
|
|
300
340
|
end
|
|
301
341
|
|
|
302
|
-
# Copy skills
|
|
342
|
+
# Copy skills as flat, hyphen-namespaced personal skills (plastic-<name>/)
|
|
303
343
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
304
|
-
installed +=
|
|
344
|
+
installed += install_skills_flat(skills_source, skills_root) if File.directory?(skills_source)
|
|
305
345
|
|
|
306
346
|
# Write VERSION
|
|
307
347
|
version_file = File.join(plastic_dir, "VERSION")
|
|
308
348
|
File.write(version_file, "#{VERSION}\n")
|
|
309
349
|
installed << version_file
|
|
310
350
|
|
|
311
|
-
#
|
|
312
|
-
|
|
351
|
+
# Remove any prior plugin/marketplace install so the layouts don't coexist
|
|
352
|
+
migrate_legacy_plugin(config[:dir])
|
|
313
353
|
|
|
314
|
-
# Merge hooks +
|
|
354
|
+
# Merge hooks + statusline into settings.json (no plugin registration)
|
|
315
355
|
settings_path = File.join(config[:dir], "settings.json")
|
|
316
356
|
merge_claude_hooks(settings_path)
|
|
317
357
|
|
|
@@ -323,99 +363,106 @@ def install_claude(config, force)
|
|
|
323
363
|
end
|
|
324
364
|
|
|
325
365
|
def install_codex(config, force)
|
|
326
|
-
skills_dir = File.join(config[:dir], "skills", "plastic")
|
|
327
|
-
FileUtils.mkdir_p(skills_dir)
|
|
328
|
-
|
|
329
366
|
installed = []
|
|
330
367
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
331
|
-
installed +=
|
|
332
|
-
|
|
333
|
-
manifest_path = File.join(config[:dir], "plastic-manifest.json")
|
|
334
|
-
write_manifest(installed, manifest_path)
|
|
368
|
+
installed += install_skills_flat(skills_source, File.join(config[:dir], "skills")) if File.directory?(skills_source)
|
|
335
369
|
|
|
370
|
+
write_manifest(installed, File.join(config[:dir], "plastic-manifest.json"))
|
|
336
371
|
{ agent: config[:name], success: true, files: installed.size }
|
|
337
372
|
end
|
|
338
373
|
|
|
339
374
|
def install_hermes(config, force)
|
|
340
|
-
skills_dir = File.join(config[:dir], "skills", "plastic")
|
|
341
|
-
FileUtils.mkdir_p(skills_dir)
|
|
342
|
-
|
|
343
375
|
installed = []
|
|
344
376
|
skills_source = File.join(PACKAGE_ROOT, "skills")
|
|
345
|
-
installed +=
|
|
346
|
-
|
|
347
|
-
manifest_path = File.join(config[:dir], "plastic-manifest.json")
|
|
348
|
-
write_manifest(installed, manifest_path)
|
|
377
|
+
installed += install_skills_flat(skills_source, File.join(config[:dir], "skills")) if File.directory?(skills_source)
|
|
349
378
|
|
|
379
|
+
write_manifest(installed, File.join(config[:dir], "plastic-manifest.json"))
|
|
350
380
|
{ agent: config[:name], success: true, files: installed.size }
|
|
351
381
|
end
|
|
352
382
|
|
|
353
|
-
#
|
|
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
|
|
354
425
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
|
358
446
|
|
|
359
|
-
|
|
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
|
|
360
455
|
|
|
361
|
-
|
|
362
|
-
"
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
"author" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" },
|
|
366
|
-
"homepage" => "https://github.com/zalom/plastic",
|
|
367
|
-
"repository" => "https://github.com/zalom/plastic",
|
|
368
|
-
"license" => "MIT",
|
|
369
|
-
"keywords" => %w[intent-driven state-management zettelkasten ai-agent]
|
|
370
|
-
}
|
|
371
|
-
write_json_atomic(File.join(plugin_meta_dir, "plugin.json"), plugin_json)
|
|
372
|
-
|
|
373
|
-
marketplace_json = {
|
|
374
|
-
"name" => "plastic",
|
|
375
|
-
"description" => "Marketplace for Plastic — intent-driven state management",
|
|
376
|
-
"owner" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" },
|
|
377
|
-
"plugins" => [{
|
|
378
|
-
"name" => "plastic",
|
|
379
|
-
"description" => "Intent-driven state management for AI coding sessions",
|
|
380
|
-
"version" => VERSION,
|
|
381
|
-
"source" => "./",
|
|
382
|
-
"author" => { "name" => "Zlatko Alomerovic", "email" => "zlatko.alomerovic@gmail.com" }
|
|
383
|
-
}]
|
|
384
|
-
}
|
|
385
|
-
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
|
|
386
460
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
copy_dir_recursive(skills_source, skills_dest)
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# Sync agents
|
|
396
|
-
agents_source = File.join(PACKAGE_ROOT, "agents")
|
|
397
|
-
agents_dest = File.join(marketplace_dir, "agents")
|
|
398
|
-
if File.directory?(agents_source)
|
|
399
|
-
FileUtils.rm_rf(agents_dest)
|
|
400
|
-
copy_dir_recursive(agents_source, agents_dest)
|
|
401
|
-
end
|
|
402
|
-
|
|
403
|
-
# Write .claude/settings.json (permissions)
|
|
404
|
-
claude_settings_dir = File.join(marketplace_dir, ".claude")
|
|
405
|
-
FileUtils.mkdir_p(claude_settings_dir)
|
|
406
|
-
write_json_atomic(File.join(claude_settings_dir, "settings.json"), {
|
|
407
|
-
"permissions" => {
|
|
408
|
-
"allow" => [
|
|
409
|
-
"Bash(ruby *)",
|
|
410
|
-
"Bash(ls *)",
|
|
411
|
-
"Bash(find *)",
|
|
412
|
-
"Bash(git *)",
|
|
413
|
-
"Bash(mkdir *)",
|
|
414
|
-
"Bash(chmod *)",
|
|
415
|
-
"mcp__serena__*"
|
|
416
|
-
]
|
|
417
|
-
}
|
|
418
|
-
})
|
|
461
|
+
removed
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def tilde(path)
|
|
465
|
+
path.sub(Dir.home, "~")
|
|
419
466
|
end
|
|
420
467
|
|
|
421
468
|
# --- settings.json merge (read-modify-write, never clobber) ---
|
|
@@ -486,13 +533,8 @@ def merge_claude_hooks(settings_path)
|
|
|
486
533
|
|
|
487
534
|
settings["statusLine"] = { "type" => "command", "command" => "#{hook_dir}/plastic-statusline" }
|
|
488
535
|
|
|
489
|
-
#
|
|
490
|
-
|
|
491
|
-
plugins["plastic@plastic"] = true
|
|
492
|
-
|
|
493
|
-
# Register the marketplace source
|
|
494
|
-
marketplaces = settings["extraKnownMarketplaces"] ||= {}
|
|
495
|
-
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.
|
|
496
538
|
|
|
497
539
|
write_json_atomic(settings_path, settings)
|
|
498
540
|
end
|
|
@@ -526,14 +568,25 @@ def handle_uninstall(agents)
|
|
|
526
568
|
next unless config
|
|
527
569
|
|
|
528
570
|
result = uninstall_agent(key, config)
|
|
529
|
-
|
|
530
|
-
puts " \u{2705} #{config[:name]}: uninstalled (#{result[:files]} files removed)"
|
|
531
|
-
else
|
|
571
|
+
unless result[:success]
|
|
532
572
|
puts " \u{26a0}\u{fe0f} #{config[:name]}: #{result[:reason]}"
|
|
573
|
+
next
|
|
533
574
|
end
|
|
575
|
+
|
|
576
|
+
puts " \u{2705} #{config[:name]}: uninstalled (#{result[:files]} files removed)"
|
|
577
|
+
result[:removed].each { |r| puts " removed: #{tilde(r)}" }
|
|
534
578
|
end
|
|
535
579
|
|
|
536
|
-
|
|
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"
|
|
537
590
|
end
|
|
538
591
|
|
|
539
592
|
def uninstall_agent(key, config)
|
|
@@ -541,45 +594,47 @@ def uninstall_agent(key, config)
|
|
|
541
594
|
return { success: false, reason: "#{config[:dir]} not found" }
|
|
542
595
|
end
|
|
543
596
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
else File.join(config[:dir], "plastic-manifest.json")
|
|
547
|
-
end
|
|
548
|
-
|
|
549
|
-
files_removed = 0
|
|
597
|
+
removed = []
|
|
598
|
+
manifest_path = manifest_path_for(key, config)
|
|
550
599
|
|
|
551
600
|
if File.exist?(manifest_path)
|
|
552
601
|
manifest = JSON.parse(File.read(manifest_path)) rescue {}
|
|
553
602
|
(manifest["files"] || {}).each_key do |f|
|
|
554
603
|
if File.exist?(f)
|
|
555
604
|
File.delete(f)
|
|
556
|
-
|
|
605
|
+
removed << f
|
|
557
606
|
end
|
|
558
607
|
end
|
|
559
608
|
File.delete(manifest_path)
|
|
560
|
-
|
|
609
|
+
removed << manifest_path
|
|
561
610
|
end
|
|
562
611
|
|
|
563
|
-
#
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
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
|
|
573
622
|
|
|
574
|
-
|
|
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
|
|
575
629
|
|
|
576
|
-
#
|
|
630
|
+
# Claude Code: clean hooks/statusline and any legacy plugin registration
|
|
577
631
|
if key == "claude"
|
|
578
632
|
settings_path = File.join(config[:dir], "settings.json")
|
|
579
633
|
remove_claude_hooks(settings_path) if File.exist?(settings_path)
|
|
634
|
+
removed.concat(migrate_legacy_plugin(config[:dir]))
|
|
580
635
|
end
|
|
581
636
|
|
|
582
|
-
{ success: true, files:
|
|
637
|
+
{ success: true, files: removed.size, removed: removed }
|
|
583
638
|
end
|
|
584
639
|
|
|
585
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
|
|