@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.
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 +174 -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
@@ -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
- case key
270
- when "claude" then install_claude(config, force)
271
- when "codex" then install_codex(config, force)
272
- 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)
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
- skills_dir = File.join(config[:dir], "skills", "plastic")
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(skills_dir)
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 recursively
342
+ # Copy skills as flat, hyphen-namespaced personal skills (plastic-<name>/)
303
343
  skills_source = File.join(PACKAGE_ROOT, "skills")
304
- 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)
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
- # Sync marketplace plugin so Claude Code discovers skills
312
- sync_marketplace_plugin(config[:dir])
351
+ # Remove any prior plugin/marketplace install so the layouts don't coexist
352
+ migrate_legacy_plugin(config[:dir])
313
353
 
314
- # Merge hooks + enabledPlugins into settings.json
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 += copy_dir_recursive(skills_source, skills_dir) if File.directory?(skills_source)
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 += copy_dir_recursive(skills_source, skills_dir) if File.directory?(skills_source)
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
- # --- 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
354
425
 
355
- def sync_marketplace_plugin(claude_dir)
356
- marketplace_dir = File.join(claude_dir, "plugins", "marketplaces", "plastic")
357
- 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
358
446
 
359
- 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
360
455
 
361
- plugin_json = {
362
- "name" => "plastic",
363
- "description" => "Intent-driven state management for AI coding sessions. Neuroplasticity for your codebase.",
364
- "version" => VERSION,
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
- # Sync skills
388
- skills_source = File.join(PACKAGE_ROOT, "skills")
389
- skills_dest = File.join(marketplace_dir, "skills")
390
- if File.directory?(skills_source)
391
- FileUtils.rm_rf(skills_dest)
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
- # Register Plastic as an enabled plugin for skill discovery
490
- plugins = settings["enabledPlugins"] ||= {}
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
- if result[:success]
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
- 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"
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
- manifest_path = case key
545
- when "claude" then File.join(config[:dir], "plastic", "manifest.json")
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
- files_removed += 1
605
+ removed << f
557
606
  end
558
607
  end
559
608
  File.delete(manifest_path)
560
- files_removed += 1
609
+ removed << manifest_path
561
610
  end
562
611
 
563
- # Clean known directories
564
- dirs_to_clean = case key
565
- when "claude"
566
- [
567
- File.join(config[:dir], "plastic"),
568
- File.join(config[:dir], "skills", "plastic"),
569
- File.join(config[:dir], "plugins", "marketplaces", "plastic"),
570
- ]
571
- else [File.join(config[:dir], "skills", "plastic")]
572
- 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
573
622
 
574
- 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
575
629
 
576
- # Clean hooks from settings.json (Claude Code only)
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: files_removed }
637
+ { success: true, files: removed.size, removed: removed }
583
638
  end
584
639
 
585
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