@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.
Files changed (38) hide show
  1. package/PLASTIC.md +18 -18
  2. package/README.md +13 -6
  3. package/agents/future-intent-researcher.md +1 -1
  4. package/agents/intent-curator.md +1 -1
  5. package/deprecations.yml +6 -5
  6. package/package.json +1 -1
  7. package/scripts/doctor.rb +37 -30
  8. package/scripts/hook-auto-arm +3 -3
  9. package/scripts/hook-continue +1 -1
  10. package/scripts/hook-gate-check +3 -3
  11. package/scripts/hook-session-start +1 -1
  12. package/scripts/install.rb +175 -119
  13. package/scripts/lib/bridge.rb +1 -1
  14. package/scripts/migrate-to-global +1 -1
  15. package/skills/_active-intent-gate.md +1 -1
  16. package/skills/auto/SKILL.md +6 -6
  17. package/skills/auto/evals/evals.json +17 -17
  18. package/skills/auto/references/agent-architecture.md +1 -1
  19. package/skills/brainstorming/SKILL.md +8 -8
  20. package/skills/brainstorming-grill-me/SKILL.md +5 -5
  21. package/skills/continuing/SKILL.md +4 -4
  22. package/skills/creating-intent/SKILL.md +1 -1
  23. package/skills/creating-project/SKILL.md +4 -4
  24. package/skills/doctor/SKILL.md +4 -4
  25. package/skills/evaluating-skills/SKILL.md +1 -1
  26. package/skills/executing-plan/SKILL.md +1 -1
  27. package/skills/install/SKILL.md +32 -8
  28. package/skills/intent-curator/SKILL.md +3 -3
  29. package/skills/linking-intents/SKILL.md +1 -1
  30. package/skills/managing-index/SKILL.md +1 -1
  31. package/skills/releasing/SKILL.md +3 -3
  32. package/skills/research/SKILL.md +2 -2
  33. package/skills/savepoint/SKILL.md +1 -1
  34. package/skills/uninstall/SKILL.md +39 -28
  35. package/skills/update/SKILL.md +2 -2
  36. package/skills/writing-instructions/SKILL.md +2 -2
  37. package/skills/writing-plans/SKILL.md +5 -5
  38. package/templates/agents.md +7 -7
@@ -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
- case key
269
- when "claude" then install_claude(config, force)
270
- when "codex" then install_codex(config, force)
271
- when "hermes" then install_hermes(config, force)
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
- skills_dir = File.join(config[:dir], "skills", "plastic")
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(skills_dir)
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 recursively
342
+ # Copy skills as flat, hyphen-namespaced personal skills (plastic-<name>/)
302
343
  skills_source = File.join(PACKAGE_ROOT, "skills")
303
- installed += copy_dir_recursive(skills_source, skills_dir) if File.directory?(skills_source)
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
- # Sync marketplace plugin so Claude Code discovers skills
311
- sync_marketplace_plugin(config[:dir])
351
+ # Remove any prior plugin/marketplace install so the layouts don't coexist
352
+ migrate_legacy_plugin(config[:dir])
312
353
 
313
- # Merge hooks + enabledPlugins into settings.json
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 += copy_dir_recursive(skills_source, skills_dir) if File.directory?(skills_source)
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 += copy_dir_recursive(skills_source, skills_dir) if File.directory?(skills_source)
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
- # --- Marketplace plugin sync ---
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
- def sync_marketplace_plugin(claude_dir)
355
- marketplace_dir = File.join(claude_dir, "plugins", "marketplaces", "plastic")
356
- plugin_meta_dir = File.join(marketplace_dir, ".claude-plugin")
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
- FileUtils.mkdir_p(plugin_meta_dir)
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
- plugin_json = {
361
- "name" => "plastic",
362
- "description" => "Intent-driven state management for AI coding sessions. Neuroplasticity for your codebase.",
363
- "version" => VERSION,
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
- # Sync skills
387
- skills_source = File.join(PACKAGE_ROOT, "skills")
388
- skills_dest = File.join(marketplace_dir, "skills")
389
- if File.directory?(skills_source)
390
- FileUtils.rm_rf(skills_dest)
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
- # Register Plastic as an enabled plugin for skill discovery
489
- plugins = settings["enabledPlugins"] ||= {}
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
- if result[:success]
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
- puts "\n Note: ~/.plastic/ (your intent store) is preserved.\n\n"
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
- manifest_path = case key
544
- when "claude" then File.join(config[:dir], "plastic", "manifest.json")
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
- files_removed += 1
605
+ removed << f
556
606
  end
557
607
  end
558
608
  File.delete(manifest_path)
559
- files_removed += 1
609
+ removed << manifest_path
560
610
  end
561
611
 
562
- # Clean known directories
563
- dirs_to_clean = case key
564
- when "claude"
565
- [
566
- File.join(config[:dir], "plastic"),
567
- File.join(config[:dir], "skills", "plastic"),
568
- File.join(config[:dir], "plugins", "marketplaces", "plastic"),
569
- ]
570
- else [File.join(config[:dir], "skills", "plastic")]
571
- end
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
- dirs_to_clean.each { |d| FileUtils.rm_rf(d) if File.directory?(d) }
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
- # Clean hooks from settings.json (Claude Code only)
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: files_removed }
637
+ { success: true, files: removed.size, removed: removed }
582
638
  end
583
639
 
584
640
  def remove_claude_hooks(settings_path)
@@ -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:auto or plastic:writing-plans first. " \
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:install first." >&2
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:creating-intent"
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}/`
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: plastic:auto
2
+ name: plastic-auto
3
3
  description: >-
4
4
  Autonomous intent delivery — agent takes over How and Exec. Use when user says
5
5
  "auto", "take it from here", "deliver this", or when brainstorming-grill-me concludes
@@ -12,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:creating-intent."
15
+ An active intent MUST exist in INDEX.md. If none exists, refuse: "No active intent found. Create one first with /plastic-creating-intent."
16
16
 
17
17
  If multiple active intents exist, ask the user which one to deliver (this is the only question auto asks).
18
18
 
@@ -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:creating-project` skill
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:executing-plan`
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:releasing` to handle them (tag, changelog, publish, etc.). Do NOT duplicate release logic — delegate entirely.
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:creating-intent` conventions)
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:auto",
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:auto (the bare 'auto' keyword is the documented trigger).",
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:auto", "observed": "plastic:auto", "result": "pass" }
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:auto (autonomous delivery of an active intent).",
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:auto", "observed": "plastic:auto", "result": "pass" }
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:auto.",
30
+ "expected_output": "Activates plastic-auto.",
31
31
  "files": [],
32
32
  "assertions": [
33
- { "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
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:auto.",
39
+ "expected_output": "Activates plastic-auto.",
40
40
  "files": [],
41
41
  "assertions": [
42
- { "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
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:auto. Near-miss: shares 'auto*' but is a settings/hooks task (update-config).",
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:auto", "observed": "update-config", "result": "pass" }
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:auto. Near-miss: shares 'deliver' but is a build/file task.",
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:auto", "observed": "none", "result": "pass" }
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:auto; activates plastic:creating-intent.",
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:auto", "observed": "plastic:creating-intent", "result": "pass" }
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:auto; this is a read/continuing/managing-index query.",
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:auto", "observed": "plastic:managing-index", "result": "pass" }
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:auto` — the agent takes over How and Exec autonomously.
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:brainstorming
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:creating-intent"
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:writing-plans`
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:writing-plans" [shape=doublecircle];
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:writing-plans" [label="approved"];
68
+ "User reviews spec?" -> "Invoke plastic-writing-plans" [label="approved"];
69
69
  }
70
70
  ```
71
71
 
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`.
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:writing-plans` to create the implementation plan
134
- - Do NOT invoke any other skill. `plastic:writing-plans` is the next step.
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