@zalom/plastic 2.0.0-alpha.26 → 2.0.0-alpha.28

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 (224) hide show
  1. package/PLASTIC.md +13 -139
  2. package/README.md +337 -133
  3. package/agents/plastic-enforcer.md +12 -12
  4. package/agents/plastic-executor.md +2 -1
  5. package/agents/plastic-node-research.md +4 -2
  6. package/agents/plastic-node-verify.md +1 -0
  7. package/agents/plastic-node-work.md +1 -0
  8. package/agents/{plastic-advisor.md → plastic-primary-advisor.md} +6 -8
  9. package/agents/{plastic-faux-advisor.md → plastic-secondary-advisor.md} +9 -12
  10. package/bin/crap +4 -0
  11. package/bin/lib/context_budget.rb +35 -1
  12. package/bin/lib/skill_census.rb +839 -0
  13. package/bin/plastic +6 -0
  14. package/bin/plastic-skill-census +114 -0
  15. package/bin/verify-change +345 -0
  16. package/config_asks.yml +4 -4
  17. package/deprecations.yml +1 -1
  18. package/{skills/agent-advisor/references → docs/help}/advisor-protocol.md +19 -24
  19. package/{skills/auto/references → docs/help}/agent-architecture.md +7 -7
  20. package/{skills/conventions/references → docs/help}/completion-and-done.md +1 -1
  21. package/{skills/auto/references → docs/help}/human-report-contract.md +17 -19
  22. package/{skills/conventions/references → docs/help}/roadmaps.md +2 -2
  23. package/{skills/tutorial/references → docs/help}/track-1-guided.md +8 -8
  24. package/{skills/tutorial/references → docs/help}/track-2-auto.md +5 -5
  25. package/{skills/tutorial/references → docs/help}/track-3-projects-and-roadmaps.md +24 -17
  26. package/package.json +3 -2
  27. package/scripts/append-ledger +2 -1
  28. package/scripts/dashboard.rb +10 -9
  29. package/scripts/day-summary +2 -1
  30. package/scripts/doctor.rb +57 -49
  31. package/scripts/end-intent +2 -8
  32. package/scripts/file-session-intent +2 -1
  33. package/scripts/hook-capture +4 -3
  34. package/scripts/hook-close +2 -1
  35. package/scripts/hook-record +3 -2
  36. package/scripts/hook-savepoint +3 -2
  37. package/scripts/hook-session-start +5 -4
  38. package/scripts/hook-stop +2 -1
  39. package/scripts/insight-append +1 -2
  40. package/scripts/install.rb +7 -5
  41. package/scripts/lib/action_graph_shim.rb +3 -1
  42. package/scripts/lib/active_delivery.rb +1 -1
  43. package/scripts/lib/agent_models.rb +44 -26
  44. package/scripts/lib/arm.rb +2 -1
  45. package/scripts/lib/backup.rb +63 -0
  46. package/scripts/lib/cli/command.rb +85 -0
  47. package/scripts/lib/cli/commands/auto.rb +18 -0
  48. package/scripts/lib/cli/commands/auto_brief.rb +44 -0
  49. package/scripts/lib/cli/commands/auto_lock.rb +60 -0
  50. package/scripts/lib/cli/commands/auto_report.rb +50 -0
  51. package/scripts/lib/cli/commands/auto_take.rb +25 -0
  52. package/scripts/lib/cli/commands/backup.rb +43 -0
  53. package/scripts/lib/cli/commands/checkout.rb +25 -0
  54. package/scripts/lib/cli/commands/continue.rb +66 -0
  55. package/scripts/lib/cli/commands/doctor.rb +20 -0
  56. package/scripts/lib/cli/commands/feedback.rb +40 -0
  57. package/scripts/lib/cli/commands/help.rb +69 -0
  58. package/scripts/lib/cli/commands/hook.rb +32 -0
  59. package/scripts/lib/cli/commands/index.rb +23 -0
  60. package/scripts/lib/cli/commands/install.rb +21 -0
  61. package/scripts/lib/cli/commands/installer_verb.rb +37 -0
  62. package/scripts/lib/cli/commands/intent.rb +19 -0
  63. package/scripts/lib/cli/commands/intent_answer.rb +37 -0
  64. package/scripts/lib/cli/commands/intent_command.rb +53 -0
  65. package/scripts/lib/cli/commands/intent_end.rb +61 -0
  66. package/scripts/lib/cli/commands/intent_new.rb +62 -0
  67. package/scripts/lib/cli/commands/intent_note.rb +43 -0
  68. package/scripts/lib/cli/commands/intent_rule.rb +36 -0
  69. package/scripts/lib/cli/commands/intent_show.rb +25 -0
  70. package/scripts/lib/cli/commands/intent_spec.rb +44 -0
  71. package/scripts/lib/cli/commands/intent_step.rb +43 -0
  72. package/scripts/lib/cli/commands/intent_verify.rb +26 -0
  73. package/scripts/lib/cli/commands/migrate.rb +16 -0
  74. package/scripts/lib/cli/commands/migrate_stores.rb +31 -0
  75. package/scripts/lib/cli/commands/next.rb +49 -0
  76. package/scripts/lib/cli/commands/project.rb +19 -0
  77. package/scripts/lib/cli/commands/project_links.rb +42 -0
  78. package/scripts/lib/cli/commands/project_list.rb +20 -0
  79. package/scripts/lib/cli/commands/project_new.rb +72 -0
  80. package/scripts/lib/cli/commands/query.rb +31 -0
  81. package/scripts/lib/cli/commands/render.rb +25 -0
  82. package/scripts/lib/cli/commands/roadmap.rb +19 -0
  83. package/scripts/lib/cli/commands/roadmap_check.rb +44 -0
  84. package/scripts/lib/cli/commands/roadmap_log.rb +54 -0
  85. package/scripts/lib/cli/commands/roadmap_next.rb +34 -0
  86. package/scripts/lib/cli/commands/roadmap_show.rb +45 -0
  87. package/scripts/lib/cli/commands/rollback.rb +20 -0
  88. package/scripts/lib/cli/commands/search.rb +60 -0
  89. package/scripts/lib/cli/commands/session.rb +18 -0
  90. package/scripts/lib/cli/commands/session_commit.rb +42 -0
  91. package/scripts/lib/cli/commands/session_handoff.rb +34 -0
  92. package/scripts/lib/cli/commands/session_summary.rb +35 -0
  93. package/scripts/lib/cli/commands/status.rb +68 -0
  94. package/scripts/lib/cli/commands/subcommand_list.rb +36 -0
  95. package/scripts/lib/cli/commands/sync.rb +46 -0
  96. package/scripts/lib/cli/commands/uninstall.rb +20 -0
  97. package/scripts/lib/cli/commands/update.rb +20 -0
  98. package/scripts/lib/cli/commands/version.rb +53 -0
  99. package/scripts/lib/cli/frontier.rb +84 -0
  100. package/scripts/lib/cli/legacy.rb +50 -0
  101. package/scripts/lib/cli/output.rb +102 -0
  102. package/scripts/lib/cli/scope.rb +127 -0
  103. package/scripts/lib/cli/table.rb +64 -0
  104. package/scripts/lib/cli.rb +94 -0
  105. package/scripts/lib/codex_adapter.rb +5 -4
  106. package/scripts/lib/compact_instructions.rb +8 -0
  107. package/scripts/lib/day_summary.rb +4 -3
  108. package/scripts/lib/doctor_core.rb +7 -32
  109. package/scripts/lib/doctor_session_ledger.rb +2 -1
  110. package/scripts/lib/feedback_report.rb +1 -1
  111. package/scripts/lib/graph_measure_models.rb +4 -2
  112. package/scripts/lib/index_entry.rb +9 -0
  113. package/scripts/lib/installer_core.rb +175 -56
  114. package/scripts/lib/intent_screen.rb +3 -3
  115. package/scripts/lib/lock.rb +2 -2
  116. package/scripts/lib/node_file.rb +22 -2
  117. package/scripts/lib/node_input.rb +11 -6
  118. package/scripts/lib/node_return.rb +8 -3
  119. package/scripts/lib/preflight.rb +2 -4
  120. package/scripts/lib/project_config.rb +2 -1
  121. package/scripts/lib/project_validator.rb +3 -2
  122. package/scripts/lib/qmd_sync.rb +8 -7
  123. package/scripts/lib/reference_archive.rb +45 -0
  124. package/scripts/lib/release_guard.rb +2 -0
  125. package/scripts/lib/report_screen.rb +4 -3
  126. package/scripts/lib/rlm/corpus.rb +13 -0
  127. package/scripts/lib/rlm/probe.rb +29 -0
  128. package/scripts/lib/rlm/query.rb +22 -0
  129. package/scripts/lib/roadmap_queue.rb +2 -2
  130. package/scripts/lib/roadmap_savepoint.rb +1 -1
  131. package/scripts/lib/runner_absorb.rb +59 -2
  132. package/scripts/lib/runner_dispatch.rb +12 -8
  133. package/scripts/lib/runner_policy.rb +28 -10
  134. package/scripts/lib/search_index.rb +55 -0
  135. package/scripts/lib/session_git.rb +4 -3
  136. package/scripts/lib/sqlite.rb +22 -0
  137. package/scripts/lib/store_discovery.rb +7 -6
  138. package/scripts/lib/store_layout.rb +54 -0
  139. package/scripts/lib/store_provisioning.rb +2 -1
  140. package/scripts/lib/store_sync.rb +85 -0
  141. package/scripts/lib/stores_move.rb +93 -0
  142. package/scripts/lib/verify_intent.rb +2 -7
  143. package/scripts/lib/version_number.rb +48 -0
  144. package/scripts/lib/work_graph.rb +59 -0
  145. package/scripts/lib/worktree.rb +3 -8
  146. package/scripts/lib/worktree_sweep.rb +3 -2
  147. package/scripts/link-suggest +2 -1
  148. package/scripts/migrate-to-global +1 -1
  149. package/scripts/new-intent +3 -12
  150. package/scripts/node-run +2 -1
  151. package/scripts/plastic-lock +3 -2
  152. package/scripts/promote-session-item +3 -2
  153. package/scripts/read-config +44 -6
  154. package/scripts/release-check +10 -5
  155. package/scripts/report-screen +1 -1
  156. package/scripts/session-commit +2 -1
  157. package/scripts/spawn-preamble +2 -2
  158. package/scripts/update.rb +25 -4
  159. package/scripts/write-handoff +2 -1
  160. package/templates/agents.md +6 -6
  161. package/templates/config.yml +13 -6
  162. package/templates/node-research.md +4 -2
  163. package/templates/render.css +10 -0
  164. package/bin/plastic.js +0 -70
  165. package/skills/agent-advisor/SKILL.md +0 -92
  166. package/skills/auto/SKILL.md +0 -297
  167. package/skills/auto/evals/evals.json +0 -255
  168. package/skills/auto/references/end-tail.md +0 -64
  169. package/skills/conventions/SKILL.md +0 -29
  170. package/skills/dashboard/SKILL.md +0 -180
  171. package/skills/dashboard/evals/evals.json +0 -38
  172. package/skills/dashboard/references/classification.md +0 -22
  173. package/skills/dashboard/templates/dashboard-global.md +0 -20
  174. package/skills/dashboard/templates/dashboard-project.md +0 -19
  175. package/skills/direct/SKILL.md +0 -66
  176. package/skills/direct/references/request-signals.md +0 -59
  177. package/skills/doctor/SKILL.md +0 -305
  178. package/skills/doctor/report.md +0 -102
  179. package/skills/feedback/SKILL.md +0 -98
  180. package/skills/feedback/references/transport-and-privacy.md +0 -65
  181. package/skills/feedback/report.md +0 -36
  182. package/skills/install/SKILL.md +0 -217
  183. package/skills/intent-continuing/SKILL.md +0 -156
  184. package/skills/intent-continuing/references/board-fill.md +0 -52
  185. package/skills/intent-continuing/references/boarding-matrix.md +0 -35
  186. package/skills/intent-continuing/references/context-management.md +0 -28
  187. package/skills/intent-continuing/references/liveness-ranking.md +0 -57
  188. package/skills/intent-creating/SKILL.md +0 -89
  189. package/skills/intent-creating/evals/evals.json +0 -72
  190. package/skills/intent-creating/references/lifecycle.md +0 -81
  191. package/skills/intent-creating/references/wikilinks.md +0 -8
  192. package/skills/intent-ending/SKILL.md +0 -182
  193. package/skills/intent-ending/evals/evals.json +0 -74
  194. package/skills/intent-executing/SKILL.md +0 -87
  195. package/skills/intent-executing/evals/evals.json +0 -66
  196. package/skills/intent-executing/implementer-prompt.md +0 -47
  197. package/skills/intent-executing/spec-reviewer-prompt.md +0 -27
  198. package/skills/intent-speccing/SKILL.md +0 -136
  199. package/skills/intent-speccing/evals/evals.json +0 -126
  200. package/skills/intent-speccing/references/design-principles.md +0 -44
  201. package/skills/intent-speccing/references/per-section-fill-rules.md +0 -92
  202. package/skills/intent-speccing/references/self-verify-checklist.md +0 -37
  203. package/skills/project-creating/SKILL.md +0 -162
  204. package/skills/project-creating/references/hubs-projects.md +0 -55
  205. package/skills/project-creating/references/project-scaffolding.md +0 -97
  206. package/skills/releasing/SKILL.md +0 -376
  207. package/skills/releasing/references/deprecations.md +0 -60
  208. package/skills/releasing/references/promotion-and-tagging.md +0 -70
  209. package/skills/releasing/references/release-lines.md +0 -105
  210. package/skills/roadmap/SKILL.md +0 -90
  211. package/skills/roadmap/references/file-format.md +0 -134
  212. package/skills/roadmap/references/operations.md +0 -112
  213. package/skills/rollback/SKILL.md +0 -91
  214. package/skills/tutorial/SKILL.md +0 -66
  215. package/skills/tutorial/evals/evals.json +0 -186
  216. package/skills/uninstall/SKILL.md +0 -75
  217. package/skills/update/SKILL.md +0 -126
  218. /package/{skills/auto/references → docs/help}/agent-report-contract.md +0 -0
  219. /package/{skills/intent-executing → docs/help}/code-quality-reviewer-prompt.md +0 -0
  220. /package/{skills/conventions/references → docs/help}/knowledge-graph.md +0 -0
  221. /package/{skills/conventions/references → docs/help}/lifecycle-and-savepoints.md +0 -0
  222. /package/{skills/conventions/references → docs/help}/locks-and-worktrees.md +0 -0
  223. /package/{skills/conventions/references → docs/help}/maintenance-and-revisions.md +0 -0
  224. /package/{skills/intent-executing → docs/help}/plan-reviewer-prompt.md +0 -0
package/scripts/doctor.rb CHANGED
@@ -9,6 +9,7 @@
9
9
  # Exit codes: 0 (all pass), 1 (warnings only), 2 (failures present)
10
10
  # Read-only — never modifies files.
11
11
 
12
+ require_relative "lib/store_layout"
12
13
  require "date"
13
14
  require "open3"
14
15
  require "timeout"
@@ -40,6 +41,7 @@ require_relative "lib/preflight"
40
41
  require_relative "lib/ruby_probe"
41
42
  require_relative "lib/doctor_session_ledger"
42
43
  require_relative "lib/intent_screen"
44
+ require_relative "lib/store_sync"
43
45
 
44
46
  # Diagnostic engine, instantiable with an injected store/agent map so tests can
45
47
  # run it hermetically (no eval, no global-constant rewriting).
@@ -216,7 +218,7 @@ class Doctor
216
218
  def check_global_store
217
219
  checks = []
218
220
 
219
- index_path = File.join(plastic_home, "INDEX.md")
221
+ index_path = File.join(Plastic::StoreLayout.global_root(plastic_home), "INDEX.md")
220
222
 
221
223
  # index_exists
222
224
  if File.exist?(index_path)
@@ -252,7 +254,7 @@ class Doctor
252
254
  end
253
255
 
254
256
  # orphaned_intents — directories in store/ not referenced in INDEX.md
255
- store_dir = File.join(plastic_home, "store")
257
+ store_dir = Plastic::StoreLayout.global_store(plastic_home)
256
258
  if File.directory?(store_dir)
257
259
  intent_dirs = store_intent_dirs(store_dir)
258
260
  orphans = intent_dirs.reject { |d| content.include?("store/#{d}") }
@@ -278,7 +280,7 @@ class Doctor
278
280
  store_paths = content.scan(%r{store/\S+}).map { |ref| ref.gsub(/[)\]>].*/, "").chomp("/") }.uniq
279
281
 
280
282
  ghosts = store_paths.select do |ref|
281
- full_path = File.join(plastic_home, ref)
283
+ full_path = File.join(Plastic::StoreLayout.global_root(plastic_home), ref)
282
284
  !File.exist?(full_path) && !File.directory?(full_path)
283
285
  end
284
286
 
@@ -330,7 +332,7 @@ class Doctor
330
332
  category: "core_files", name: name, status: "warn",
331
333
  message: "#{config[:name]}'s agent-side VERSION file not found at #{tilde(agent_version_path)}",
332
334
  fixable: true,
333
- fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or plastic-rollback to a prior version"
335
+ fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or `plastic rollback` to a prior version"
334
336
  )
335
337
  else
336
338
  agent_version = File.read(agent_version_path).strip
@@ -348,7 +350,7 @@ class Doctor
348
350
  "#{tilde(agent_version_path)}: #{agent_version}",
349
351
  ],
350
352
  fixable: true,
351
- fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or plastic-rollback to a prior version"
353
+ fix_hint: "Re-sync the stale harness: npx @zalom/plastic@latest install --reinstall <flag>, or `plastic rollback` to a prior version"
352
354
  )
353
355
  end
354
356
  end
@@ -613,7 +615,7 @@ class Doctor
613
615
  fixable: true,
614
616
  fix_hint: "Relocate each unsanctioned section into the " \
615
617
  "intent's revisions.md via move-and-record (a missing required section is restored " \
616
- "or reprojected instead); see plastic-conventions > references/maintenance-and-revisions.md"
618
+ "or reprojected instead); see `plastic help maintenance-and-revisions`"
617
619
  )
618
620
  end
619
621
 
@@ -644,7 +646,7 @@ class Doctor
644
646
  # unpromoted_rules (intent 341, G8, C37): an Insights entry tagged
645
647
  # `rule:` (via `insight-append --rule`) is a promise the rule will make
646
648
  # it into project doctrine. Advisory only.
647
- checks.concat(unpromoted_rules_checks(intent_dirs, home: Dir.home))
649
+ checks.concat(unpromoted_rules_checks(intent_dirs))
648
650
 
649
651
  # cross_store_resolution — RESOLVES (not just shape-checks) every cross-store
650
652
  # `store:id` ref against the FULL store family via the relocation map
@@ -705,14 +707,14 @@ class Doctor
705
707
  if scopes.nil? || scopes.include?("global")
706
708
  stores << {
707
709
  scope: "global",
708
- index: File.join(plastic_home, "INDEX.md"),
709
- store_dir: File.join(plastic_home, "store"),
710
+ index: File.join(Plastic::StoreLayout.global_root(plastic_home), "INDEX.md"),
711
+ store_dir: Plastic::StoreLayout.global_store(plastic_home),
710
712
  }
711
713
  end
712
714
 
713
- projects_root = File.join(plastic_home, "projects")
715
+ projects_root = Plastic::StoreLayout.projects_root(plastic_home)
714
716
  if File.directory?(projects_root)
715
- Dir.children(projects_root).sort.each do |project|
717
+ Plastic::StoreLayout.project_slugs(plastic_home).each do |project|
716
718
  scope = "project:#{project}"
717
719
  next unless scopes.nil? || scopes.include?(scope)
718
720
 
@@ -1228,7 +1230,7 @@ end
1228
1230
  "project-links --intent <id> --apply` over running project-links directly: it detects " \
1229
1231
  "(never acquires) the target's delivery lock, requires a clean store working tree, and " \
1230
1232
  "commits the scoped change plus its revisions.md receipt as one merged operation " \
1231
- "(plastic-conventions > references/maintenance-and-revisions.md)."
1233
+ "(`plastic help maintenance-and-revisions`)."
1232
1234
  )
1233
1235
  end
1234
1236
 
@@ -1692,7 +1694,7 @@ end
1692
1694
  "Every sources/chain id resolves to a real intent (I4)",
1693
1695
  "Record the dangling sources/chain edge as a " \
1694
1696
  "broken-source/broken-chain move-and-record entry in the intent's revisions.md (see " \
1695
- "plastic-conventions > references/maintenance-and-revisions.md), or restore the missing intent"
1697
+ "`plastic help maintenance-and-revisions`), or restore the missing intent"
1696
1698
  )
1697
1699
  checks
1698
1700
  end
@@ -1865,31 +1867,25 @@ end
1865
1867
  #
1866
1868
  # `insight-append --rule` tags an entry "... - rule: <text>". A tag is a
1867
1869
  # promise the rule will make it into project doctrine; until the exact
1868
- # rule text shows up in some skills/conventions/references/*.md chapter,
1869
- # it is only visible to a session that happens to read this one intent
1870
- # file, and the next session repeats the mistake the rule names. Advisory
1871
- # only (warn, never fail): a freshly tagged rule is not yet promoted by
1872
- # design, and nothing here can auto-promote it (that is an editorial call,
1873
- # not a mechanical one).
1870
+ # rule text shows up in some docs/help/*.md chapter, it is only visible to
1871
+ # a session that happens to read this one intent file, and the next
1872
+ # session repeats the mistake the rule names. Advisory only (warn, never
1873
+ # fail): a freshly tagged rule is not yet promoted by design, and nothing
1874
+ # here can auto-promote it (that is an editorial call, not a mechanical
1875
+ # one).
1874
1876
  RULE_ENTRY_RE = /—\s*rule:\s*(.+?)\s*\z/.freeze
1875
1877
 
1876
- def unpromoted_rules_checks(intent_dirs, package_root: PACKAGE_ROOT, home: nil)
1877
- # The installed doctor runs from ~/.plastic/scripts, so PACKAGE_ROOT
1878
- # (~/.plastic) has no skills/ directory: the conventions chapters install
1879
- # to the agent home layout instead. `home` is caller-injected (never an
1880
- # ENV read here) so this stays hermetic in tests; the real call site
1881
- # passes the process's actual home directory.
1882
- chapter_dirs = []
1883
- if home
1884
- chapter_dirs << File.join(home, ".claude", "skills", "plastic-conventions", "references")
1885
- chapter_dirs << File.join(home, ".agents", "skills", "plastic-conventions", "references")
1886
- end
1887
- chapter_dirs << File.join(package_root, "skills", "conventions", "references")
1878
+ def unpromoted_rules_checks(intent_dirs, package_root: PACKAGE_ROOT)
1879
+ # docs/help installs straight under PACKAGE_ROOT for every harness alike
1880
+ # (~/.plastic/docs/help once installed, repo docs/help in a checkout), so
1881
+ # a single path covers both the repo and the installed layout; unlike
1882
+ # the old skills-based chapters, help topics are no longer agent-home
1883
+ # specific, so this needs no `home:` split.
1884
+ chapter_dir = File.join(package_root, "docs", "help")
1888
1885
 
1889
- chapters_text = chapter_dirs.select { |d| Dir.exist?(d) }
1890
- .flat_map { |d| Dir.glob(File.join(d, "*.md")) }
1891
- .map { |f| File.read(f) }
1892
- .join("\n\n")
1886
+ chapters_text = Dir.glob(File.join(chapter_dir, "*.md"))
1887
+ .map { |f| File.read(f) }
1888
+ .join("\n\n")
1893
1889
 
1894
1890
  unpromoted = []
1895
1891
  intent_dirs.each do |d|
@@ -1917,7 +1913,7 @@ end
1917
1913
  category: "conventions", name: "unpromoted_rules", status: "warn",
1918
1914
  message: "#{unpromoted.size} tagged rule(s) not yet carried by any conventions chapter",
1919
1915
  details: unpromoted, fixable: false,
1920
- fix_hint: "Promote the rule into the right skills/conventions/references/*.md chapter, " \
1916
+ fix_hint: "Promote the rule into the right docs/help/*.md chapter, " \
1921
1917
  "or drop the tag if the finding does not belong in doctrine"
1922
1918
  )]
1923
1919
  end
@@ -1934,8 +1930,7 @@ end
1934
1930
  # skill directory under agent_dir/skills that has NO corresponding entry in the
1935
1931
  # current install manifest is a stray (e.g. a leftover old-name copy the
1936
1932
  # install/update prune should have removed, or one it never saw because the
1937
- # manifest predates it). Complements flat_skills_check (which only confirms at
1938
- # least one skill exists). Defers to the manifest check when the manifest itself
1933
+ # manifest predates it). Defers to the manifest check when the manifest itself
1939
1934
  # is missing or malformed, so the two checks never double-report the same gap.
1940
1935
 
1941
1936
  # Auto-mode role agents install as <dir>/agents/plastic-*.md. Pass if at least
@@ -2159,6 +2154,8 @@ end
2159
2154
 
2160
2155
  global_config = load_yaml_safe(File.join(plastic_home, "config.yml")) || {}
2161
2156
  overrides = AgentModels.override_map(project_config: {}, global_config: global_config, harness: "codex")
2157
+ effort_overrides = AgentModels.effort_override_map(project_config: {}, global_config: global_config,
2158
+ harness: "codex")
2162
2159
 
2163
2160
  drifted = []
2164
2161
  sanctioned = []
@@ -2169,13 +2166,13 @@ end
2169
2166
  fields = codex_agent_toml_model_fields(File.read(path))
2170
2167
  override = overrides[basename]
2171
2168
 
2172
- if override
2169
+ if override || effort_overrides[basename]
2173
2170
  sanctioned << "#{basename}: toml model=#{fields[:model].inspect}, " \
2174
- "toml effort=#{fields[:effort].inspect}, sanctioned override=#{override.inspect}"
2175
- elsif AgentModels::TIER_DEFAULTS.key?(basename)
2176
- tier = AgentModels::TIER_DEFAULTS[basename]
2177
- expected_model = AgentModels.codex_model_for(tier)
2178
- expected_effort = AgentModels.effort_for(tier)
2171
+ "toml effort=#{fields[:effort].inspect}, sanctioned model override=#{override.inspect}, " \
2172
+ "effort override=#{effort_overrides[basename].inspect}"
2173
+ elsif AgentModels::SHIPPED_MODEL_DEFAULTS.key?(basename)
2174
+ expected_model = AgentModels.shipped_model_for(basename, harness: "codex")
2175
+ expected_effort = AgentModels.shipped_effort_for(basename)
2179
2176
  mismatches = []
2180
2177
  if expected_model && fields[:model] != expected_model
2181
2178
  mismatches << "model=#{fields[:model].inspect}, resolved default model=#{expected_model.inspect}"
@@ -2187,7 +2184,7 @@ end
2187
2184
  else
2188
2185
  unclassified << "#{basename}: toml model=#{fields[:model].inspect}, " \
2189
2186
  "toml effort=#{fields[:effort].inspect}, no resolved default (basename is in " \
2190
- "neither AgentModels::TIER_DEFAULTS nor AgentModels::CONSULTATION_AGENTS in " \
2187
+ "AgentModels::SHIPPED_MODEL_DEFAULTS in " \
2191
2188
  "scripts/lib/agent_models.rb; add it there, or set agents.models.codex.#{basename} " \
2192
2189
  "to sanction a model explicitly)"
2193
2190
  end
@@ -2299,7 +2296,7 @@ end
2299
2296
  # project can be checked in isolation (used by `--store <slug>`).
2300
2297
  def check_project_store(slug, project_info)
2301
2298
  checks = []
2302
- project_dir = File.join(plastic_home, "projects", slug)
2299
+ project_dir = Plastic::StoreLayout.project_root(plastic_home, slug)
2303
2300
 
2304
2301
  # project_dir_exists
2305
2302
  if File.directory?(project_dir)
@@ -2349,7 +2346,7 @@ end
2349
2346
  end
2350
2347
 
2351
2348
  # project_yml_exists
2352
- project_yml_path = File.join(plastic_home, "projects", slug, "project.yml")
2349
+ project_yml_path = File.join(Plastic::StoreLayout.project_root(plastic_home, slug), "project.yml")
2353
2350
  project_yml_data = nil
2354
2351
 
2355
2352
  if File.exist?(project_yml_path)
@@ -2396,7 +2393,7 @@ end
2396
2393
  return checks unless parent_id
2397
2394
 
2398
2395
  # Find the intent directory for the parent ID
2399
- store_dir = File.join(plastic_home, "store")
2396
+ store_dir = Plastic::StoreLayout.global_store(plastic_home)
2400
2397
  parent_dir = nil
2401
2398
  if File.directory?(store_dir)
2402
2399
  parent_dir = Dir.children(store_dir).find { |d| d.start_with?("#{parent_id}--") }
@@ -2553,7 +2550,7 @@ end
2553
2550
  # A config-asks warn in that tier would flip every post-update doctor to
2554
2551
  # "fail" on an otherwise healthy install until the question is answered,
2555
2552
  # re-noising the post-update surface intent 126 deliberately quieted. The
2556
- # recoverable-later path for a pending question is a full `/plastic-doctor`
2553
+ # recoverable-later path for a pending question is a full `plastic doctor`
2557
2554
  # run, the declared maintenance front door; the moment-it-happens path is
2558
2555
  # update.rb#announce_pending_config_asks, which already runs on every hop.
2559
2556
  #
@@ -3017,6 +3014,16 @@ end
3017
3014
  end
3018
3015
  end
3019
3016
 
3017
+ def check_graph_links
3018
+ return [] unless File.exist?(Plastic::WorkGraph.path(plastic_home))
3019
+
3020
+ orphans = Plastic::StoreSync.orphans(plastic_home)
3021
+ return [check(category: "graph_links", name: "graph_links", status: "pass", message: "every stored file belongs to an intent in work_graph.db")] if orphans.empty?
3022
+
3023
+ [check(category: "graph_links", name: "graph_links", status: "warn", details: orphans, fixable: false,
3024
+ message: "#{orphans.size} intent id(s) hold files and have no row in work_graph.db")]
3025
+ end
3026
+
3020
3027
  # --- Run all checks ---
3021
3028
 
3022
3029
  def run_checks(agent_key)
@@ -3035,6 +3042,7 @@ end
3035
3042
  all_checks += check_session_ledger(scopes: ["global"])
3036
3043
  all_checks += check_skill_lint
3037
3044
  all_checks += check_install_integrity
3045
+ all_checks += check_graph_links
3038
3046
  all_checks += check_display_registration(agent_key)
3039
3047
  all_checks += check_display_paints(agent_key)
3040
3048
  all_checks += check_display_not_defeated(agent_key)
@@ -68,6 +68,7 @@
68
68
  # 7 hollow delivered report refused (317a D7) - intent 308's one deliberate
69
69
  # exception to report-and-proceed; --allow-hollow-report overrides.
70
70
 
71
+ require_relative "lib/store_layout"
71
72
  require "fileutils"
72
73
  require "date"
73
74
  require "open3"
@@ -170,14 +171,7 @@ end
170
171
  # "projects" (the StoreDiscovery convention, scripts/lib/store_discovery.rb), never by
171
172
  # hardcoding ".plastic" as a name.
172
173
  def resolve_plastic_home_and_scope(store)
173
- parent = File.dirname(store)
174
- grandparent = File.dirname(parent)
175
- if File.basename(grandparent) == "projects"
176
- slug = File.basename(parent)
177
- [File.dirname(grandparent), "project:#{slug}"]
178
- else
179
- [parent, "global"]
180
- end
174
+ Plastic::StoreLayout.home_and_scope(store)
181
175
  end
182
176
 
183
177
  # --- outcome generation, before the backfill (intent 339, G6, spec D10) -----
@@ -14,6 +14,7 @@
14
14
  # and on any filing error (reported on stderr; the next boot tries again), 2 on
15
15
  # a usage error.
16
16
 
17
+ require_relative "lib/store_layout"
17
18
  require_relative "lib/session_ledger"
18
19
  require_relative "lib/session_backfill"
19
20
 
@@ -48,7 +49,7 @@ if opts[:carry_to] && !SessionLedger.valid_day_id?(opts[:carry_to])
48
49
  usage_abort("--carry-to must be a real calendar date, YYYYMMDD")
49
50
  end
50
51
 
51
- store = opts[:store] || File.join(File.expand_path(ENV.fetch("PLASTIC_HOME", "~/.plastic")), "store")
52
+ store = opts[:store] || Plastic::StoreLayout.global_store(File.expand_path(ENV.fetch("PLASTIC_HOME", "~/.plastic")))
52
53
  templates = opts[:templates] || File.expand_path("../templates", __dir__)
53
54
 
54
55
  begin
@@ -9,6 +9,7 @@
9
9
  # runs in its own rescue and the hook always exits 0 (spec D2). Intent 345
10
10
  # (D7, 323) removed the per-prompt Future-intent hint step entirely.
11
11
 
12
+ require_relative "lib/store_layout"
12
13
  require "json"
13
14
  require "open3"
14
15
  require "time"
@@ -38,7 +39,7 @@ cwd = payload["cwd"].to_s
38
39
  cwd = Dir.pwd if cwd.empty?
39
40
 
40
41
  plastic_home = File.expand_path(ENV["PLASTIC_HOME"] || "~/.plastic")
41
- store = File.join(plastic_home, "store")
42
+ store = Plastic::StoreLayout.global_store(plastic_home)
42
43
  templates = File.expand_path("../templates", __dir__)
43
44
  sid = SessionLedger.short_session_id(nil, session_id)
44
45
  today = SessionLedger.day_id
@@ -108,7 +109,7 @@ begin
108
109
  cockpit, _err, status = Open3.capture3({ "RUBYOPT" => nil }, "ruby", dashboard, "continue")
109
110
  if status.success? && !cockpit.strip.empty?
110
111
  context_parts << (cockpit.rstrip +
111
- "\n\nFollow the plastic-intent-continuing skill workflow to resume: " \
112
+ "\n\nRun `plastic continue` to resume: " \
112
113
  "if active intents exist, read their state and resume; otherwise surface the " \
113
114
  "most-valuable next work and any stale or triage intents from the board above.")
114
115
 
@@ -135,7 +136,7 @@ begin
135
136
  auto_triggers = ["take it from here", "deliver this", "/plastic-auto", "auto mode"].freeze
136
137
  triggered = auto_triggers.any? { |p| message_lower.include?(p) } || message_lower.match?(/\bauto\b/)
137
138
  if triggered
138
- context_parts << "Auto-mode request detected. Invoke the plastic-auto skill to deliver the " \
139
+ context_parts << "Auto-mode request detected. Run `plastic auto take ID` to deliver the " \
139
140
  "active intent autonomously - it takes the intent under a lock and a worktree " \
140
141
  "and points this session at it."
141
142
  end
@@ -8,6 +8,7 @@
8
8
  #
9
9
  # Usage: hook-close <plastic_home> (stdin: the hook JSON)
10
10
 
11
+ require_relative "lib/store_layout"
11
12
  require "json"
12
13
  require_relative "lib/session_ledger"
13
14
  require_relative "lib/session_close"
@@ -23,7 +24,7 @@ payload =
23
24
  {}
24
25
  end
25
26
 
26
- store = File.join(File.expand_path(plastic_home), "store")
27
+ store = Plastic::StoreLayout.global_store(File.expand_path(plastic_home))
27
28
  filer = File.expand_path("file-session-intent", __dir__)
28
29
  templates = File.expand_path("../templates", __dir__)
29
30
  begin
@@ -10,6 +10,7 @@
10
10
  # lands, and calls the session-commit seam when that script exists. Always
11
11
  # exits 0 (spec D3).
12
12
 
13
+ require_relative "lib/store_layout"
13
14
  require "json"
14
15
  require "timeout"
15
16
  require "fileutils"
@@ -77,7 +78,7 @@ skip_day_ledger = inside_plastic || !intent_dir_abs.nil?
77
78
  # --- (c) promote the newest pending line, Item savepoint, seam -------------
78
79
  unless skip_day_ledger
79
80
  begin
80
- store = File.join(plastic_home, "store")
81
+ store = Plastic::StoreLayout.global_store(plastic_home)
81
82
  sid = SessionLedger.short_session_id(nil, session_id)
82
83
  today = SessionLedger.day_id
83
84
 
@@ -152,7 +153,7 @@ end
152
153
  # the heartbeat is not written and no directory is created here either --
153
154
  # the session tmp directory, created by session start, is the guard.
154
155
  begin
155
- store ||= File.join(plastic_home, "store")
156
+ store ||= Plastic::StoreLayout.global_store(plastic_home)
156
157
  sid ||= SessionLedger.short_session_id(nil, session_id)
157
158
  if Dir.exist?(SessionLedger.session_tmp_dir(store, sid))
158
159
  File.write(SessionLedger.heartbeat_path(store, sid), "#{Time.now.utc.iso8601}\n")
@@ -13,6 +13,7 @@
13
13
  #
14
14
  # Usage: hook-savepoint <plastic_home> (stdin: the hook JSON)
15
15
 
16
+ require_relative "lib/store_layout"
16
17
  require "json"
17
18
  require_relative "lib/session_ledger"
18
19
  require_relative "lib/handoff"
@@ -31,7 +32,7 @@ MESSAGE = "PLASTIC SAVEPOINT - context is being compacted. The hand-off for this
31
32
  def resolve_intent(plastic_home, session_id, read_config_path)
32
33
  return nil if session_id.strip.empty?
33
34
 
34
- global_store = File.join(plastic_home, "store")
35
+ global_store = Plastic::StoreLayout.global_store(plastic_home)
35
36
  roots_raw = IO.popen({ "PLASTIC_HOME" => plastic_home }, [read_config_path, "project_roots"],
36
37
  err: File::NULL, &:read).to_s.strip
37
38
  project_roots = roots_raw.empty? ? [] : Array(JSON.parse(roots_raw))
@@ -51,7 +52,7 @@ begin
51
52
 
52
53
  unless plastic_home.empty? || session_id.strip.empty?
53
54
  home = File.expand_path(plastic_home)
54
- store = File.join(home, "store")
55
+ store = Plastic::StoreLayout.global_store(home)
55
56
  session = SessionLedger.short_session_id(session_id, nil)
56
57
  day = Handoff.day_for(store, session, today: SessionLedger.day_id)
57
58
  read_config_path = File.expand_path("read-config", __dir__)
@@ -3,6 +3,7 @@
3
3
  # Usage: hook-session-start <index_path> <plastic_home> <mode> [plugin_root]
4
4
  # Outputs hook JSON for the session-start hook. Exits silently if nothing to show.
5
5
 
6
+ require_relative "lib/store_layout"
6
7
  require "json"
7
8
  require "date"
8
9
  require "yaml"
@@ -58,7 +59,7 @@ end
58
59
  # home (~/.plastic) as argument 2; the store lives one level below it. Compose the
59
60
  # store exactly once here, so no later line re-derives it and no path can gain a
60
61
  # doubled store/ segment.
61
- store_dir = File.join(plastic_home, "store")
62
+ store_dir = Plastic::StoreLayout.global_store(plastic_home)
62
63
 
63
64
  # --- Current version ---
64
65
  # Read early, before any of the richer INDEX/project parsing below, so the
@@ -154,7 +155,7 @@ begin
154
155
  project_path = File.expand_path(info["path"])
155
156
  if cwd.start_with?(project_path)
156
157
  current_project = { "slug" => slug, "parent" => info["parent"], "path" => project_path }
157
- project_index = "#{plastic_home}/projects/#{slug}/INDEX.md"
158
+ project_index = File.join(Plastic::StoreLayout.project_root(plastic_home, slug), "INDEX.md")
158
159
  if File.exist?(project_index)
159
160
  p_section = nil
160
161
  File.readlines(project_index).each do |pline|
@@ -220,7 +221,7 @@ begin
220
221
  if File.exist?(cache_file)
221
222
  cache = JSON.parse(File.read(cache_file)) rescue {}
222
223
  if cache["updateAvailable"]
223
- update_notice = "Plastic update available: #{cache["current"]} -> #{cache["latest"]} — run /plastic-update"
224
+ update_notice = "Plastic update available: #{cache["current"]} -> #{cache["latest"]} — run `plastic update`"
224
225
  end
225
226
  end
226
227
 
@@ -291,7 +292,7 @@ begin
291
292
  if plastic_md && !subagent_session
292
293
  if current_project
293
294
  slug = current_project["slug"]
294
- banner = "Project: #{slug} | Store: ~/.plastic/projects/#{slug}/store/"
295
+ banner = "Project: #{slug} | Store: #{File.join(Plastic::StoreLayout.project_root(plastic_home, slug), "store").sub(Dir.home, "~")}/"
295
296
  if project_active.any? && project_active.first =~ /\[([^\]]+)\].*store\/([\w-]+)\//
296
297
  intent_name, dir_name = $1, $2
297
298
  intent_id = dir_name.split("--").first
package/scripts/hook-stop CHANGED
@@ -14,6 +14,7 @@
14
14
  #
15
15
  # Usage: hook-stop <plastic_home> (stdin: the hook JSON)
16
16
 
17
+ require_relative "lib/store_layout"
17
18
  require "json"
18
19
  require_relative "lib/stop_gate"
19
20
 
@@ -32,7 +33,7 @@ begin
32
33
  payload = {} unless payload.is_a?(Hash)
33
34
 
34
35
  home = File.expand_path(plastic_home)
35
- global_store = File.join(home, "store")
36
+ global_store = Plastic::StoreLayout.global_store(home)
36
37
  read_config = File.expand_path("read-config", __dir__)
37
38
 
38
39
  config_stop_hook = read_config_value("runner.stop_hook", home, read_config)
@@ -16,8 +16,7 @@
16
16
  # ever reaches Insights.append_insight; the library itself stays untouched,
17
17
  # since a tag is a text-level convention, not a new field on the ledger.
18
18
  # Doctor's unpromoted_rules check (scripts/doctor.rb) later lists any tagged
19
- # entry whose exact text no skills/conventions/references/*.md chapter
20
- # carries yet.
19
+ # entry whose exact text no docs/help/*.md chapter carries yet.
21
20
  #
22
21
  # Usage:
23
22
  # insight-append <intent_dir> <text> --stage S --author A [--rule]
@@ -75,12 +75,15 @@ class Install < InstallerCore
75
75
  # they are reported, never re-installed, so the summary line and the Codex
76
76
  # trust reminder only ever count agents that actually changed this run.
77
77
  def run(selected:, force: false, reinstall: false, ledger_action: nil, argv: ARGV, input: $stdin,
78
- already_registered: [])
78
+ already_registered: [], qmd_runner: QmdSync.default_runner, qmd_detector: QmdSync.method(:detect))
79
79
  fresh = !installed?
80
80
  mode = fresh ? :install : :update # :update here means "re-sync, skip bootstrap"
81
81
 
82
82
  distribute(mode)
83
83
  bootstrap if fresh
84
+ git_init_if_absent
85
+ register_with_qmd(runner: qmd_runner, detector: qmd_detector)
86
+ migrate_advisor_config_file(File.join(plastic_home, "config.yml"))
84
87
  apply_config_flags(argv)
85
88
 
86
89
  results = selected.map do |key|
@@ -239,10 +242,9 @@ class Install < InstallerCore
239
242
  --no-advisor Skip installing both advisor agents and the agent-advisor
240
243
  skill (advisor.enabled: false)
241
244
  --advisor VALUE Which advisor agent is the default: an agent name, or the
242
- shorthand "real" (plastic-advisor) or "faux"
243
- (plastic-faux-advisor). Writes advisor.claude.default. Left
244
- unset, the agent-advisor skill falls back to
245
- plastic-faux-advisor at consult time.
245
+ shorthand "primary" or "secondary". Legacy "real" and
246
+ "faux" aliases remain accepted. Writes advisor.claude.default.
247
+ Left unset, the agent-advisor skill falls back to Primary Advisor.
246
248
  -h, --help Show this help
247
249
 
248
250
  Notes:
@@ -83,7 +83,7 @@ module ActionGraphShim
83
83
  end
84
84
 
85
85
  # Node records for either shape (D8), always carrying NodeFile.parse's own
86
- # key set (ok:, node:, kind:, files:, budget:, body:, errors:) plus
86
+ # key set (ok:, node:, kind:, files:, budget:, report:, body:, errors:) plus
87
87
  # needs:, path: and proven_by:. [] on :none, never a raise on a real
88
88
  # directory.
89
89
  def nodes(intent_dir)
@@ -153,6 +153,7 @@ module ActionGraphShim
153
153
  kind: "work",
154
154
  files: files_section_paths(text),
155
155
  budget: nil,
156
+ report: nil,
156
157
  body: text,
157
158
  errors: [],
158
159
  needs: needs_targets,
@@ -166,6 +167,7 @@ module ActionGraphShim
166
167
  kind: "work",
167
168
  files: [],
168
169
  budget: nil,
170
+ report: nil,
169
171
  body: nil,
170
172
  errors: ["could not read #{path}: #{e.message}"],
171
173
  needs: needs_targets,
@@ -60,7 +60,7 @@ module ActiveDelivery
60
60
  value.nil? || value.to_s.strip.empty?
61
61
  end
62
62
 
63
- DEFAULT_PROJECT_ROOTS = ["~/.plastic/projects"].freeze
63
+ DEFAULT_PROJECT_ROOTS = ["~/.plastic/projects", "~/.plastic/stores"].freeze
64
64
 
65
65
  # The configured project roots (344 n2, D6): the `project_roots` list in
66
66
  # `<plastic_home>/config.yml`, each expanded, or the default