@zalom/plastic 1.0.0-beta.2 → 1.0.0-beta.21

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 (82) hide show
  1. package/PLASTIC.md +131 -7
  2. package/agents/plastic-brainstorming.md +9 -1
  3. package/agents/plastic-enforcer.md +1 -1
  4. package/agents/plastic-executor.md +11 -1
  5. package/agents/plastic-intent-curator.md +7 -5
  6. package/agents/plastic-planner.md +11 -1
  7. package/agents/plastic-spec-specialist.md +9 -1
  8. package/hooks/hooks.json +20 -0
  9. package/hooks/retrieval-gate +10 -0
  10. package/hooks/savepoint-pre +10 -0
  11. package/hooks/statusline +150 -41
  12. package/package.json +1 -1
  13. package/scripts/agent-report +163 -0
  14. package/scripts/doctor.rb +172 -0
  15. package/scripts/hook-auto-arm +1 -1
  16. package/scripts/hook-bash-gate +2 -2
  17. package/scripts/hook-code-gate +11 -6
  18. package/scripts/hook-create-gate +2 -2
  19. package/scripts/hook-gate-check +14 -23
  20. package/scripts/hook-retrieval-gate +136 -0
  21. package/scripts/hook-savepoint-pre +32 -0
  22. package/scripts/hook-session-start +1 -1
  23. package/scripts/insight-append +51 -0
  24. package/scripts/lib/bridge.rb +374 -34
  25. package/scripts/lib/frontmatter_writer.rb +130 -0
  26. package/scripts/lib/graph_rebuild.rb +328 -0
  27. package/scripts/lib/insights.rb +86 -0
  28. package/scripts/lib/installer_core.rb +23 -0
  29. package/scripts/lib/link_suggestions.rb +322 -0
  30. package/scripts/lib/links_projection.rb +160 -0
  31. package/scripts/lib/links_section.rb +207 -0
  32. package/scripts/lib/power_tools.rb +76 -0
  33. package/scripts/lib/qmd_hook.rb +38 -25
  34. package/scripts/lib/qmd_sync.rb +36 -0
  35. package/scripts/lib/retrieval_gate.rb +211 -0
  36. package/scripts/lib/worktree.rb +409 -0
  37. package/scripts/link-suggest +211 -0
  38. package/scripts/new-intent +138 -29
  39. package/scripts/project-links +287 -0
  40. package/scripts/qmd-sync +50 -3
  41. package/scripts/rebuild-graph +244 -0
  42. package/scripts/spawn-preamble +26 -1
  43. package/skills/auto/SKILL.md +58 -11
  44. package/skills/auto/evals/evals.json +48 -0
  45. package/skills/auto/references/agent-architecture.md +27 -4
  46. package/skills/auto/references/agent-report-contract.md +121 -0
  47. package/skills/brainstorming/SKILL.md +1 -0
  48. package/skills/brainstorming/evals/evals.json +22 -0
  49. package/skills/continuing/SKILL.md +30 -8
  50. package/skills/continuing/evals/evals.json +9 -0
  51. package/skills/creating-intent/SKILL.md +16 -2
  52. package/skills/creating-intent/evals/evals.json +16 -0
  53. package/skills/creating-intent/references/lifecycle.md +9 -4
  54. package/skills/creating-skills/SKILL.md +65 -0
  55. package/skills/creating-skills/evals/evals.json +108 -0
  56. package/skills/creating-skills/references/agents.md +168 -0
  57. package/skills/creating-skills/references/evals.md +41 -0
  58. package/skills/creating-skills/references/hooks.md +248 -0
  59. package/skills/creating-skills/references/progressive-disclosure.md +176 -0
  60. package/skills/creating-skills/references/scripts.md +166 -0
  61. package/skills/creating-skills/references/skills.md +165 -0
  62. package/skills/creating-skills/scripts/scaffold.rb +313 -0
  63. package/skills/dashboard/SKILL.md +5 -0
  64. package/skills/dashboard/evals/evals.json +22 -0
  65. package/skills/executing-plan/SKILL.md +2 -2
  66. package/skills/humanizer/SKILL.md +39 -0
  67. package/skills/humanizer/evals/evals.json +70 -0
  68. package/skills/humanizer/references/always-on-snippet.md +9 -0
  69. package/skills/humanizer/references/examples.md +48 -0
  70. package/skills/intent-curator/SKILL.md +6 -1
  71. package/skills/intent-curator/evals/evals.json +22 -0
  72. package/skills/linking-intents/SKILL.md +54 -12
  73. package/skills/linking-intents/evals/evals.json +22 -0
  74. package/skills/linking-intents/references/zettelkasten.md +7 -0
  75. package/skills/managing-index/SKILL.md +8 -0
  76. package/skills/managing-index/evals/evals.json +22 -0
  77. package/skills/managing-index/references/zettelkasten-linking.md +6 -1
  78. package/skills/releasing/SKILL.md +32 -0
  79. package/skills/research/SKILL.md +8 -0
  80. package/skills/research/evals/evals.json +22 -0
  81. package/skills/writing-instructions/SKILL.md +0 -159
  82. package/skills/writing-instructions/references/agentskills-spec.md +0 -135
@@ -0,0 +1,244 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # rebuild-graph — repair the store-wide sources/chain frontmatter graph across the
6
+ # global, plastic, and knowdb stores (intent 49). Deterministic, idempotent, and
7
+ # one-directional (intent 68 I-invariants): dedupe, I3 (formative edge wins), I1
8
+ # in-store backlinks, I2 preserved; cross-store refs resolved via a multi-hop
9
+ # relocation map (relocation wins over coincidental id reuse). Emits a
10
+ # before/after audit, then writes minimal style-preserving frontmatter.
11
+ #
12
+ # Usage:
13
+ # rebuild-graph [--plastic-home PATH] [--dry-run] [--audit-path PATH]
14
+ #
15
+ # Pure-Ruby (no bash). The pure logic lives in lib/graph_rebuild.rb and
16
+ # lib/frontmatter_writer.rb; this shell does only discovery, IO, and reporting.
17
+ # Never pushes ~/.plastic (no git ops here).
18
+
19
+ require "yaml"
20
+ require "date"
21
+ require "time"
22
+ require "fileutils"
23
+
24
+ require_relative "lib/graph_rebuild"
25
+ require_relative "lib/frontmatter_writer"
26
+ require_relative "lib/intent_validator"
27
+
28
+ class RebuildGraph
29
+ DEFAULT_HOME = File.join(Dir.home, ".plastic")
30
+
31
+ # The 49 intent dir audit destination (relative to plastic_home).
32
+ DEFAULT_AUDIT_REL =
33
+ "projects/plastic/store/49--store-wide-double-link-symmetry/resources/audit--graph-rebuild.md"
34
+
35
+ KIND_LABELS = {
36
+ dedupe: "Dedupes",
37
+ i3: "I3 resolutions (kept in sources, dropped from chain)",
38
+ repoint: "Cross-store repoints",
39
+ collapse: "Cross-store collapses (to bare same-store id)",
40
+ drop: "Dropped dead refs",
41
+ i1_backlink: "I1 backlinks added",
42
+ }.freeze
43
+
44
+ KIND_ORDER = %i[dedupe i3 repoint collapse drop i1_backlink].freeze
45
+
46
+ def initialize(plastic_home: DEFAULT_HOME, dry_run: false, audit_path: nil)
47
+ @plastic_home = plastic_home
48
+ @dry_run = dry_run
49
+
50
+ # A dry run must NOT stomp the canonical audit (the spec/checklist tell humans
51
+ # to run --dry-run to review the plan). When no explicit --audit-path is given,
52
+ # a dry run writes to a distinct `.dry-run.md` sibling, leaving the canonical
53
+ # real-run audit untouched. An explicit --audit-path is always honored verbatim
54
+ # (it is the caller's responsibility, and tests inject it).
55
+ canonical = File.join(plastic_home, DEFAULT_AUDIT_REL)
56
+ @audit_path =
57
+ if audit_path
58
+ audit_path
59
+ elsif dry_run
60
+ canonical.sub(/\.md\z/, ".dry-run.md")
61
+ else
62
+ canonical
63
+ end
64
+ end
65
+
66
+ attr_reader :plastic_home, :dry_run, :audit_path
67
+
68
+ # The three in-scope stores, each as { key:, root:, store:, index: }.
69
+ # `root` is the dir holding INDEX.md; `store` is the intents dir.
70
+ def stores
71
+ list = []
72
+ global_store = File.join(plastic_home, "store")
73
+ list << { key: "global", root: plastic_home, store: global_store,
74
+ index: File.join(plastic_home, "INDEX.md") } if File.directory?(global_store)
75
+
76
+ %w[plastic knowdb].each do |slug|
77
+ root = File.join(plastic_home, "projects", slug)
78
+ store = File.join(root, "store")
79
+ next unless File.directory?(store)
80
+
81
+ list << { key: "project:#{slug}", root: root, store: store,
82
+ index: File.join(root, "INDEX.md") }
83
+ end
84
+ list
85
+ end
86
+
87
+ # { id => { sources:, chain:, path: } } for one store.
88
+ def load_nodes(store_dir)
89
+ nodes = {}
90
+ Dir.children(store_dir).reject { |e| e.start_with?(".") }.sort.each do |entry|
91
+ dir = File.join(store_dir, entry)
92
+ next unless File.directory?(dir)
93
+
94
+ md = File.join(dir, "#{entry}.md")
95
+ next unless File.exist?(md)
96
+
97
+ fm = IntentValidator.parse_frontmatter(md)
98
+ next unless fm.is_a?(Hash) && fm["id"]
99
+
100
+ nodes[fm["id"].to_s] = {
101
+ sources: Array(fm["sources"]).map(&:to_s),
102
+ chain: Array(fm["chain"]).map(&:to_s),
103
+ path: md,
104
+ }
105
+ end
106
+ nodes
107
+ end
108
+
109
+ def run
110
+ store_list = stores
111
+ nodes_by_store = {}
112
+ index_texts = {}
113
+ store_index = {}
114
+
115
+ store_list.each do |s|
116
+ nodes_by_store[s[:key]] = load_nodes(s[:store])
117
+ index_texts[s[:key]] = File.exist?(s[:index]) ? File.read(s[:index]) : ""
118
+ store_index[s[:key]] = nodes_by_store[s[:key]].keys
119
+ end
120
+
121
+ relocation_map = GraphRebuild.build_relocation_map(index_texts)
122
+
123
+ results = {}
124
+ store_list.each do |s|
125
+ key = s[:key]
126
+ input = nodes_by_store[key].transform_values { |v| { sources: v[:sources], chain: v[:chain] } }
127
+ results[key] = GraphRebuild.rebuild_store(
128
+ input,
129
+ referer_store: key,
130
+ relocation_map: relocation_map,
131
+ store_index: store_index
132
+ )
133
+ end
134
+
135
+ write_back(store_list, nodes_by_store, results) unless dry_run
136
+ emit_audit(store_list, nodes_by_store, results)
137
+
138
+ results
139
+ end
140
+
141
+ # Write changed frontmatter back via the minimal style-preserving writer.
142
+ def write_back(store_list, nodes_by_store, results)
143
+ store_list.each do |s|
144
+ key = s[:key]
145
+ new_nodes = results[key][:nodes]
146
+ nodes_by_store[key].each do |id, original|
147
+ rebuilt = new_nodes[id]
148
+ next if rebuilt.nil?
149
+ next if rebuilt[:sources] == original[:sources] && rebuilt[:chain] == original[:chain]
150
+
151
+ content = File.read(original[:path])
152
+ updated = FrontmatterWriter.rewrite_arrays(content,
153
+ sources: rebuilt[:sources],
154
+ chain: rebuilt[:chain])
155
+ File.write(original[:path], updated) if updated != content
156
+ end
157
+ end
158
+ end
159
+
160
+ # Render the audit and write it (always, even in dry-run, so the human reviews
161
+ # the dry-run plan). Returns the rendered string.
162
+ def emit_audit(store_list, _nodes_by_store, results)
163
+ text = render_audit(store_list, results)
164
+ FileUtils.mkdir_p(File.dirname(audit_path))
165
+ File.write(audit_path, text)
166
+ text
167
+ end
168
+
169
+ # PURE-ish formatter (string from results). Per-store, grouped by kind.
170
+ def render_audit(store_list, results)
171
+ total = store_list.sum { |s| results[s[:key]][:changes].size }
172
+ lines = []
173
+ lines << "# Audit: store-wide sources/chain graph rebuild (intent 49)"
174
+ lines << ""
175
+ lines << "Generated: #{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")}#{dry_run ? " (DRY RUN)" : ""}"
176
+ lines << ""
177
+ lines << "Total changes across all stores: #{total}"
178
+ lines << ""
179
+
180
+ store_list.each do |s|
181
+ key = s[:key]
182
+ changes = results[key][:changes]
183
+ lines << "## #{key}"
184
+ lines << ""
185
+ if changes.empty?
186
+ lines << "No changes."
187
+ lines << ""
188
+ next
189
+ end
190
+
191
+ KIND_ORDER.each do |kind|
192
+ group = changes.select { |c| c[:kind] == kind }
193
+ next if group.empty?
194
+
195
+ lines << "### #{KIND_LABELS[kind]} (#{group.size})"
196
+ group.each { |c| lines << "- #{format_change(c)}" }
197
+ lines << ""
198
+ end
199
+ end
200
+
201
+ lines.join("\n") + "\n"
202
+ end
203
+
204
+ def format_change(c)
205
+ case c[:kind]
206
+ when :dedupe
207
+ "#{c[:intent]}: sources #{c[:before][:sources].inspect} → #{c[:after][:sources].inspect}, " \
208
+ "chain #{c[:before][:chain].inspect} → #{c[:after][:chain].inspect}"
209
+ when :i3
210
+ "#{c[:intent]}: #{c[:before]} kept in sources, dropped from chain"
211
+ when :repoint
212
+ "#{c[:intent]}.#{c[:field]}: #{c[:before]} → #{c[:after]} (relocated cross-store)"
213
+ when :collapse
214
+ "#{c[:intent]}.#{c[:field]}: #{c[:before]} → #{c[:after]} (collapsed to bare same-store id)"
215
+ when :drop
216
+ "#{c[:intent]}.#{c[:field]}: #{c[:before]} dropped (resolves nowhere)"
217
+ when :i1_backlink
218
+ "#{c[:intent]}.chain += #{c[:backlink]} (formative backlink)"
219
+ else
220
+ c.inspect
221
+ end
222
+ end
223
+ end
224
+
225
+ if $PROGRAM_NAME == __FILE__
226
+ home = RebuildGraph::DEFAULT_HOME
227
+ dry = false
228
+ audit = nil
229
+ i = 0
230
+ while i < ARGV.length
231
+ case ARGV[i]
232
+ when "--plastic-home" then home = ARGV[i + 1]; i += 2
233
+ when "--dry-run" then dry = true; i += 1
234
+ when "--audit-path" then audit = ARGV[i + 1]; i += 2
235
+ else i += 1
236
+ end
237
+ end
238
+
239
+ tool = RebuildGraph.new(plastic_home: home, dry_run: dry, audit_path: audit)
240
+ results = tool.run
241
+ total = results.values.sum { |r| r[:changes].size }
242
+ puts "rebuild-graph #{dry ? "DRY RUN" : "applied"}: #{total} change(s) across #{results.size} store(s)."
243
+ puts "Audit: #{tool.audit_path}"
244
+ end
@@ -29,7 +29,30 @@ require_relative "lib/bridge"
29
29
  HONOR_INSTRUCTION =
30
30
  "You are operating inside Plastic. Use it as your operating scaffold. " \
31
31
  "Emit VALID lifecycle artifacts; do not hallucinate intents or stages. " \
32
- "Your output is a deliverable, not a message."
32
+ "Your primary output is valid lifecycle artifacts; you close with a structured report about them."
33
+
34
+ # Verbatim completion-report contract (intent 74). Kept as one constant so the
35
+ # contract doc (skills/auto/references/agent-report-contract.md), the role prompts,
36
+ # and the test assert against the exact same string. SINGLE SOURCE OF TRUTH for the
37
+ # report wording: the work output is lifecycle artifacts, the final message is a
38
+ # structured report about them. Both are required and they do not contradict.
39
+ REPORT_CONTRACT =
40
+ "Before you finish, END your turn with a structured completion report as your " \
41
+ "FINAL MESSAGE (your return value), not a side-channel file. Do not go idle or " \
42
+ "finish silently. The report carries a common envelope: role, intent id, stage, " \
43
+ "status (delivered or blocked), artifacts written, verification or tests run, " \
44
+ "checklist deltas, deviations from spec, blockers or handoff notes, and an " \
45
+ "insights field carrying 0..N durable nuggets (what I discovered worth keeping, " \
46
+ "the most interesting residue of this turn; none if there were none); plus a " \
47
+ "role-specific payload that fulfils your place in the What, Why, How, Exec cycle " \
48
+ "(for example the planner explains the plan back to the orchestrator). Populate " \
49
+ "the insights field even when you cannot write the intent file yourself: a " \
50
+ "background or dispatched agent carries each nugget home in the report and the " \
51
+ "orchestrator persists it via scripts/insight-append, so an insight never " \
52
+ "depends on the discovering session's file-write access. Keep the report " \
53
+ "prose-stripped: the envelope and payload only, no greeting, no preamble, " \
54
+ "no end-recap, no restating of the task; reasoning stays in the thinking channel. " \
55
+ "See skills/auto/references/agent-report-contract.md for the per-role format."
33
56
 
34
57
  def parse_args(argv)
35
58
  role = nil
@@ -116,6 +139,8 @@ lines << "Current stage: #{stage}"
116
139
  lines << "Cycle step / role: #{cycle}"
117
140
  lines << ""
118
141
  lines << HONOR_INSTRUCTION
142
+ lines << ""
143
+ lines << REPORT_CONTRACT
119
144
  lines << "=== end preamble ==="
120
145
 
121
146
  puts lines.join("\n")
@@ -28,6 +28,13 @@ Work `dispatchable_queue` in `rank` order (these are `defer`/`research` disposit
28
28
  safe to deliver autonomously). Leave `human_only` and `next_big_thing` for the user — those
29
29
  are `drive`/`triage` items the human should lead. See the `plastic-dashboard` skill.
30
30
 
31
+ QMD-first (when available): when the user describes the work to deliver rather than naming an
32
+ intent, before scanning the store with grep/Read run
33
+ `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate, prior, or duplicate
34
+ intents, then open the authoritative intent file for the hit you take over. The command is a no-op
35
+ when QMD is absent, so fall back to the existing INDEX.md / file scan. (This is discovery; the
36
+ reindex step under Completion is separate.)
37
+
31
38
  ## Arm the Lifecycle Gate (do this FIRST)
32
39
 
33
40
  Immediately after selecting the intent — before any other work — arm auto mode. This
@@ -36,13 +43,18 @@ edited before the plan exists (the gate applies to YOU, the orchestrator):
36
43
 
37
44
  ```bash
38
45
  ruby -r ~/.plastic/scripts/lib/bridge -e \
39
- 'Bridge.arm_auto(ENV["CLAUDE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
46
+ 'Bridge.arm_auto(ENV["CLAUDE_CODE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
40
47
  ```
41
48
 
42
49
  Replace `<ID>`, `<STORE>` (e.g. `~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
43
- `<dir>` (the `ID--slug` directory), and `<name>`. If `CLAUDE_SESSION_ID` is unset, `arm_auto`
44
- falls back to a deterministic derived bridge key (a hash of the store and intent id), so the
45
- gate still engages; arming prints a one-line notice to stderr in that case.
50
+ `<dir>` (the `ID--slug` directory), and `<name>`. The first argument is the session id you
51
+ want the bridge keyed by: pass the hook stdin `session_id` when you have it, otherwise
52
+ `ENV["CLAUDE_CODE_SESSION_ID"]`, otherwise `nil`. `arm_auto` calls `resolve_session`, which
53
+ picks the first non-empty of: the explicit id you pass -> `CLAUDE_CODE_SESSION_ID` -> a
54
+ deterministic derived key (a hash of the store and intent id).
55
+ It never returns nil, so the gate engages even when every session env var is empty; the call
56
+ never needs a non-empty session env var to function. Arming prints a one-line notice to
57
+ stderr when it falls through to the derived key.
46
58
 
47
59
  **Hard rule for the rest of this run:** do NOT edit project code (anything outside the
48
60
  intent directory / `~/.plastic/`) until `plan.md` AND `checklist.md` exist for the intent.
@@ -68,15 +80,29 @@ Dispatch rule: sequential, one specialist per stage on one branch (the deliverab
68
80
 
69
81
  Spawn preamble (live-state injection): before dispatching any specialist, run `scripts/spawn-preamble <intent_dir> --role <role>` and PREPEND its output to that specialist's prompt. The preamble is a deterministic, filesystem-only snapshot of the active intent (id, intent line, current stage) plus the honoring instruction, so every spawned agent boots with accurate live state instead of guessing. This is the authoritative L2 mechanism for harnesses whose sub-agents do not inherit a top-level session event (see `docs/reference/harness-adapters.md`).
70
82
 
83
+ Completion report (require-then-synthesize): every dispatched specialist MUST end with a structured completion report as its final message. The preamble's `REPORT_CONTRACT` injects this and the role prompts carry the per-role format (see `references/agent-report-contract.md`). Because child-agent honor is best-effort across harnesses, this is decision-shaping, not a hard block. When a specialist returns no usable report (it went idle, emitted only a bare ping, or its message was lost to a mid-run interjection), run `scripts/agent-report <intent_dir> --role <role>` to synthesize a deterministic filesystem-derived report so the handoff account always exists. Use the agent-authored report when present, the synthesized one otherwise.
84
+
71
85
  Final-gate review: dispatch an independent reviewer subagent at the final gate only, not as a standing role.
72
86
 
73
- Headless manual gate: when running headless or in the background, enforce gates manually and do not rely on hooks, because `CLAUDE_SESSION_ID` may be unset (this ties to the arm-gate fallback above).
87
+ Headless manual gate: when running headless or in the background, still enforce gates manually rather than relying on hooks alone. The PostToolUse gate hook reads `session_id` from hook stdin, and the savepoint ledger write is decoupled from the bridge (derived from the file path, so it fires even with no session id) - these do NOT no-op. What can degrade is the bridge-keyed stage enforcement: if no session id reaches the bridge and no matching bridge is discovered, the stage-gate enforcement step exits without acting, so verify state yourself. The bridge still resolves arming via `CLAUDE_CODE_SESSION_ID` or the derived-key fallback (see the arm-gate note above).
74
88
 
75
89
  Solo fallback: if the harness has no subagent dispatch, fall back to a single agent walking the full What, Why, How, Exec cycle yourself. This preserves current behavior.
76
90
 
77
91
  ## Stage-Aware Entry
78
92
 
79
- Read the active intent's directory. Determine current lifecycle stage from filesystem state:
93
+ Read the active intent's `savepoint.md` FIRST (intent 81): the last line classifies the stage,
94
+ and you then verify only that line's artifact before entering. Fall back to the filesystem probe
95
+ below only when the ledger is missing (then rebuild it with `Bridge.rebuild_savepoint`).
96
+
97
+ | Ledger last line | Enter |
98
+ |---|---|
99
+ | `What {id}--{slug}.md` (born) or no spec | Start / complete Why (write spec.md) |
100
+ | `Why spec.md created` | Enter How |
101
+ | `How plan.md created` / `How checklist.md created` / `Exec started` | Enter Exec (verify plan + checklist) |
102
+ | `Exec outcome.md created` | Exec done; complete the intent |
103
+ | `Done delivered|abandoned` | Terminal; do not resume |
104
+
105
+ Filesystem fallback (ledger missing only):
80
106
 
81
107
  | Check (in order) | Stage |
82
108
  |---|---|
@@ -182,20 +208,41 @@ During initial project creation, all decisions are non-destructive by definition
182
208
  5. Review `## Insights` for observations that should spawn future intents. If any:
183
209
  - Create them (using `plastic-creating-intent` conventions)
184
210
  - Update `chain` in the current intent's frontmatter
185
- 6. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
211
+ 6. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date). As the
212
+ closing act of the transfer, stamp the terminal ledger bookend (intent 81) so the savepoint's
213
+ last line records delivery:
214
+ ```bash
215
+ ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.append_terminal_savepoint("<intent_dir>", "delivered")'
216
+ ```
217
+ (Use `"abandoned"` instead when the intent is being moved to `## Abandoned`.) Idempotent.
186
218
  7. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
187
- 8. Refresh the QMD search index for this store (optional, no-op when QMD is absent):
219
+ 8. On completion, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent).
220
+ It runs in the background so it never blocks the turn:
188
221
  ```bash
189
- ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root>
222
+ ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async
190
223
  ```
191
- Delivery is the lifecycle event that keeps the search index fresh. `<store-root>` is the
224
+ Completion is the lifecycle event that keeps the search index fresh. `<store-root>` is the
192
225
  store that holds this intent (the global store or the project store).
193
226
  9. Disarm the lifecycle gate (auto delivery is finished):
194
227
  ```bash
195
- ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_SESSION_ID"])'
228
+ ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_CODE_SESSION_ID"])'
196
229
  ```
197
230
  Disarming also purges stale bridge files from the temp directory automatically (it keeps the
198
231
  current bridge and any live run), so no manual `/tmp` cleanup is needed.
232
+
233
+ **Worktree cleanup (mandatory, intent 73c3).** Disarming performs the worktree release:
234
+ `disarm_auto` calls `Worktree.release`, which removes both per-intent worktrees (the code
235
+ worktree under `<repo>/.claude/worktrees/{id}--{slug}` and the paired store worktree under
236
+ `<plastic_home>/.worktrees/{id}--{slug}`), prunes both repos, and clears the worktree block
237
+ from the bridge. This is the plain remove path: the disarm route does NOT merge, so use it
238
+ only when no release merges the branch (the branch survives and can be reclaimed).
239
+
240
+ When the work is being shipped through a release, do NOT rely on this plain remove. The
241
+ release path (step 4 above, via `plastic-releasing`) is responsible for merging the intent's
242
+ code branch (`plastic/{id}--{slug}`) back to the repo's default branch BEFORE the worktree is
243
+ removed, so the integrated work is not lost. It does this with `Worktree.finish(bridge_data,
244
+ merge: true)` (merge-then-remove). Never leave an orphaned worktree, and run `git worktree
245
+ prune` if you hit a stale reference.
199
246
  10. Notify user: "Intent [ID] — [name] delivered. [1-2 sentence summary]. See outcome.md for details."
200
247
 
201
248
  ## Error Handling
@@ -202,6 +202,54 @@
202
202
  "result": "pass"
203
203
  }
204
204
  ]
205
+ },
206
+ {
207
+ "id": 12,
208
+ "scope": "behavior",
209
+ "set": "validation",
210
+ "prompt": "A power-tool is present (qmd on PATH, or a .serena marker / serena on PATH). A substantive prompt arrives in auto mode.",
211
+ "expected_output": "The UserPromptSubmit power-tools hook appends a MANDATORY obligation per present tool: a MUST-use-QMD line when qmd is present (to check for an existing or related intent before treating work as new), and a MUST-use-Serena line when serena is present (symbolic tools before grep/Read). QMD hits are still injected when above threshold.",
212
+ "files": [],
213
+ "assertions": [
214
+ {
215
+ "type": "code",
216
+ "check": "PowerTools.mandate returns MUST/MANDATORY lines for each present tool; QmdHook.run appends the mandate",
217
+ "observed": "power_tools_test.rb + qmd_hook_test.rb assert MUST wording; serena line gated on the serena detector",
218
+ "result": "pass"
219
+ }
220
+ ]
221
+ },
222
+ {
223
+ "id": 13,
224
+ "scope": "behavior",
225
+ "set": "validation",
226
+ "prompt": "Neither qmd nor serena is present (no qmd on PATH, no .serena marker, no serena on PATH). A substantive prompt arrives.",
227
+ "expected_output": "Detect-then-degrade: the hook emits nothing (silent no-op, exit 0). No mandate text appears. Nothing is required to install.",
228
+ "files": [],
229
+ "assertions": [
230
+ {
231
+ "type": "code",
232
+ "check": "PowerTools.mandate returns nil and QmdHook.run returns nil when neither tool is present",
233
+ "observed": "power_tools_test.rb test_mandate_neither_is_nil + qmd_hook_test.rb test_nil_when_neither_tool_present",
234
+ "result": "pass"
235
+ }
236
+ ]
237
+ },
238
+ {
239
+ "id": 14,
240
+ "scope": "behavior",
241
+ "set": "validation",
242
+ "prompt": "QMD is present. In auto mode the user says: deliver the work on the uploader retry policy (no intent id given).",
243
+ "expected_output": "Before scanning the store with grep/Read to find the matching intent, runs `ruby ~/.plastic/scripts/qmd-sync search \"uploader retry policy\"` to surface the candidate intent, then opens the authoritative intent file for the hit it takes over. This discovery step is distinct from the completion-time reindex step. No-op fallback to INDEX.md / file scan when QMD is absent.",
244
+ "files": [],
245
+ "assertions": [
246
+ {
247
+ "type": "human",
248
+ "check": "qmd-sync search is run before grep/Read during discovery; authoritative file opened for the hit; reindex step stays separate",
249
+ "observed": "SKILL.md (or agent file) carries the QMD-first step: run qmd-sync search before grep/Read, then open the authoritative file; no-op fallback when QMD is absent",
250
+ "result": "pass"
251
+ }
252
+ ]
205
253
  }
206
254
  ]
207
255
  }
@@ -61,6 +61,26 @@ and not hallucinate intents or stages. This is the standard L2 live-state mechan
61
61
  for harnesses whose spawned sub-agents do not inherit the top-level session event. See
62
62
  `docs/reference/harness-adapters.md` for how it slots into the per-harness contract.
63
63
 
64
+ ### Completion Reports
65
+
66
+ Every dispatched specialist ends its turn with a structured completion report as its final
67
+ message (its return value), so the agent that did the work is the one that accounts for it. The
68
+ report carries a common envelope plus a role-specific payload that fulfils the agent's place in
69
+ the cycle; the planner explains the plan back to the orchestrator, the executor reports what was
70
+ built and the test result, and so on. The format lives in `references/agent-report-contract.md`,
71
+ and the verbatim instruction is injected once via the spawn preamble's `REPORT_CONTRACT`
72
+ constant, which the role prompts reproduce.
73
+
74
+ Enforcement is require-report then synthesize-fallback. The preamble and prompts make the report
75
+ mandatory (decision-shaping), but child-agent honor is best-effort across harnesses (Tier B/C),
76
+ so it is never a hard block. When a specialist returns no usable report, the enforcer runs
77
+ `scripts/agent-report <intent_dir> --role <role>`, a pure function of the intent dir (no network,
78
+ clock, or randomness, mirroring `spawn-preamble`) that emits a filesystem-derived report from the
79
+ savepoint, the artifacts present, the checklist checked/total, and the outcome line. A handoff
80
+ account therefore always exists: agent-authored when present, deterministically reconstructed
81
+ otherwise. This structures the finish notification only; in-flight observations stay in
82
+ `## Insights`, no progress chatter is added.
83
+
64
84
  ### Gate Ownership
65
85
 
66
86
  The enforcer arms and verifies the lifecycle gate, then gates every stage transition.
@@ -70,10 +90,13 @@ permanent sixth role, it exists only for the final review.
70
90
 
71
91
  ### Headless Manual Gate
72
92
 
73
- When running headless or in the background, the enforcer enforces gates manually and
74
- does not rely on hooks, because `CLAUDE_SESSION_ID` may be unset in those runs (the
75
- gate-check and savepoint hooks no-op without it). The enforcer arms via the bridge's
76
- derived-key fallback and verifies state itself.
93
+ When running headless or in the background, the enforcer enforces gates manually rather
94
+ than relying on hooks alone. The savepoint ledger and PostToolUse gate hook still fire
95
+ (the gate hook reads `session_id` from stdin; the savepoint write is path-derived and
96
+ bridge-independent), so they do not blanket no-op. Only the bridge-keyed stage-enforcement
97
+ step degrades when no session id reaches the bridge and no bridge is discovered. The
98
+ enforcer arms via `CLAUDE_CODE_SESSION_ID` or the bridge's derived-key fallback and
99
+ verifies state itself.
77
100
 
78
101
  ### Delegation
79
102
 
@@ -0,0 +1,121 @@
1
+ # Agent Completion Report Contract
2
+
3
+ Every agent dispatched by the auto-mode enforcer MUST end its turn with a structured
4
+ completion report. This doc defines that report: one common envelope plus a per-role payload.
5
+ It is the format the `REPORT_CONTRACT` constant in `scripts/spawn-preamble` points at, the
6
+ role prompts (`agents/plastic-*.md`) reproduce, and the deterministic fallback
7
+ (`scripts/agent-report`) approximates. Keep all four in agreement; the constant in
8
+ `scripts/spawn-preamble` is the single source of truth for the injected wording.
9
+
10
+ ## Purpose
11
+
12
+ The report is the agent's FINAL MESSAGE (its return value), not a side-channel file. Every
13
+ harness hands a spawned agent's final text back to the dispatcher, so the final message is the
14
+ one carrier that works everywhere (decision D1). The report structures the FINISH notification
15
+ only. In-flight observations still go in `## Insights`; the report does not add progress chatter
16
+ (decision D5). An agent that finishes correct artifacts but goes idle without a report has not
17
+ completed its handoff: the agent that did the work is the cheapest, most accurate source of the
18
+ account.
19
+
20
+ ## Prose-stripped (intent 84)
21
+
22
+ The report is the envelope and the per-role payload, nothing else. Dispatched and background
23
+ subagents report and do their job; they do not narrate. Strip conversational prose: no
24
+ greeting, no preamble, no "Here is what I did" framing, no end-recap, no restating of the task.
25
+ Reasoning belongs in the thinking channel, not the report body. This tightens the FORM (the
26
+ fields stay exactly as below); it does not remove any required field.
27
+
28
+ ## Common envelope
29
+
30
+ Every role report, whatever the stage, carries these fields:
31
+
32
+ - **Role**: which specialist produced this (brainstorming, spec, planner, executor, reviewer).
33
+ - **Intent id and stage**: the active intent id and the cycle stage just completed.
34
+ - **Status**: `delivered` or `blocked`.
35
+ - **Artifacts written**: the files produced or changed (store paths, and project paths for the
36
+ executor).
37
+ - **Verification / tests run**: the command run and its result, or `n/a` for stages that write
38
+ no code.
39
+ - **Checklist deltas**: which checklist items this turn checked off (executor), or `n/a`.
40
+ - **Deviations from spec**: anything done differently from the spec or plan, and why, or `none`.
41
+ - **Blockers / handoff notes**: what the next stage must watch for, or `none`.
42
+ - **Insights**: 0..N durable nuggets discovered this turn (the most interesting residue),
43
+ each one a `## Insights`-worthy line; `none` if there were none. Background and dispatched
44
+ agents MUST populate this: they carry each nugget home in the report and the orchestrator
45
+ persists it (see Insights delivery below), so an insight never depends on the discovering
46
+ session having file-write access.
47
+
48
+ ## Per-role payload
49
+
50
+ Each role appends a payload that fulfils its place in the What, Why, How, Exec cycle (decision
51
+ D2). The payload is what makes the report useful to the orchestrator beyond the envelope.
52
+
53
+ ### brainstorming (Why exploration)
54
+ - Decisions recorded in `### Decisions`, each with its one-line rationale.
55
+ - Context enriched: what was researched and the key findings.
56
+ - Open questions resolved, and any deliberately left for the spec.
57
+ - Insights: durable discoveries from the Why exploration, reported in the `insights:` field.
58
+
59
+ ### spec-specialist (Why to How boundary)
60
+ - Spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria).
61
+ - How the recorded decisions resolved into the chosen approach.
62
+ - Acceptance-criteria count, so the planner knows the surface to cover.
63
+ - Insights: durable discoveries from consolidating the spec, reported in the `insights:` field.
64
+
65
+ ### planner (How): worked exemplar
66
+ The planner report EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR. It carries:
67
+ - The ordered actions, one line each: what the action does and how it is verified.
68
+ - Decomposition rationale: why this order, and why the actions are independent.
69
+ - Checklist coverage: item count and that every action plus suite-green is covered.
70
+ This is the exemplar because the plan is an argument, and the orchestrator gates on whether that
71
+ argument is sound before any code is written.
72
+ - Insights: durable discoveries from planning, reported in the `insights:` field.
73
+
74
+ ### executor (Exec)
75
+ - Actions implemented this turn, mapped to checklist items checked off (checked / total).
76
+ - A summary of the code changed (files and the shape of the change).
77
+ - Test result: the full-suite command and its pass / fail counts.
78
+ - Insights reported in the `insights:` field (each with the `(autonomous)` marker); the
79
+ executor or the orchestrator persists them to `## Insights` via the `insight-append` helper.
80
+
81
+ ### final reviewer (final gate)
82
+ - Verdict: `pass` or `blockers found`.
83
+ - Each acceptance criterion checked, with the evidence that confirms or refutes it.
84
+ - Gaps or risks found, ranked, with a recommended disposition.
85
+ - Insights: durable discoveries from the review, reported in the `insights:` field.
86
+
87
+ ## Fallback: always a report
88
+
89
+ Decision-shaping (the preamble plus these prompts) makes the report mandatory, but child-agent
90
+ honor is best-effort across harnesses (Tier B/C in `docs/reference/harness-adapters.md`), so the
91
+ contract is never a hard block (decision D3). When a dispatched agent returns no usable report
92
+ (it went idle, emitted only a bare ping, or its message was lost to a mid-run interjection), the
93
+ enforcer synthesizes one:
94
+
95
+ ```
96
+ scripts/agent-report <intent_dir> --role <role>
97
+ ```
98
+
99
+ `scripts/agent-report` is a pure function of the intent directory (no network, clock, or
100
+ randomness, mirroring `scripts/spawn-preamble`): it reads the current stage from the savepoint
101
+ ledger, the lifecycle artifacts present, the checklist checked / total, and the `## Outcome`
102
+ line, and emits a filesystem-derived report labelled `synthesized`. So a handoff account always
103
+ exists: authored by the agent when possible, reconstructed deterministically when not. This
104
+ formalizes the by-hand reconstruction the orchestrator did while delivering intent 68.
105
+
106
+ ## Insights delivery
107
+
108
+ Insights ride home in the completion report. Every agent reports its durable nuggets in the
109
+ `insights:` field; the orchestrator (or any agent that can write the intent file) then persists
110
+ each one via the helper:
111
+
112
+ ```
113
+ scripts/insight-append <intent_dir> <text> --stage S --author A
114
+ ```
115
+
116
+ The helper formats the `{utc-iso8601} · {stage} · {author}` prefix (the same timestamp
117
+ convention as the savepoint ledger), validates it, and appends the entry at the bottom of the
118
+ `## Insights` section, newest last. This is the fix for dropped background and sub-agent
119
+ insights: a session that cannot write the intent file still returns its report, so the insight
120
+ survives and the orchestrator writes it on receipt. Hand-editing `## Insights` is an escape
121
+ hatch; the helper is the default so the prefix format cannot drift.
@@ -74,6 +74,7 @@ digraph brainstorming {
74
74
  ## The Process
75
75
 
76
76
  **Understanding the idea:**
77
+ - QMD-first (when available): before scanning the store with grep/Read for prior decisions, specs, or outcomes, run `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate, prior, or related intents, then open the authoritative intent file for any hit you act on. The command is a no-op when QMD is absent, so fall back to the existing INDEX.md / file scan.
77
78
  - Check out the current project state first (files, docs, recent commits)
78
79
  - Before asking detailed questions, assess scope: if the request describes multiple independent subsystems (e.g., "build a platform with chat, file storage, billing, and analytics"), flag this immediately. Don't spend questions refining details of a project that needs to be decomposed first.
79
80
  - If the project is too large for a single spec, help the user decompose into sub-projects: what are the independent pieces, how do they relate, what order should they be built? Then brainstorm the first sub-project through the normal design flow. Each sub-project gets its own spec → plan → implementation cycle.
@@ -0,0 +1,22 @@
1
+ {
2
+ "skill_name": "plastic-brainstorming",
3
+ "notes": "Intent 66a. Spec for the QMD-first step in the Why/explore-context phase (surface prior decisions/specs/outcomes before grep/Read). Runner is intent 76; spec only.",
4
+ "evals": [
5
+ {
6
+ "id": 1,
7
+ "scope": "behavior",
8
+ "set": "validation",
9
+ "prompt": "QMD is present. Brainstorming the active intent during the Why phase, the agent needs prior decisions and specs on caching.",
10
+ "expected_output": "In the explore-project-context (Why) step, before scanning the store with grep/Read, runs `ruby ~/.plastic/scripts/qmd-sync search \"caching decisions\"` to surface prior decisions, specs, or outcomes, then opens the authoritative intent file for any hit it acts on. No-op fallback to INDEX.md / file scan when QMD is absent.",
11
+ "files": [],
12
+ "assertions": [
13
+ {
14
+ "type": "human",
15
+ "check": "qmd-sync search is run during Why before grep/Read; authoritative file opened for any hit",
16
+ "observed": "SKILL.md (or agent file) carries the QMD-first step: run qmd-sync search before grep/Read, then open the authoritative file; no-op fallback when QMD is absent",
17
+ "result": "pass"
18
+ }
19
+ ]
20
+ }
21
+ ]
22
+ }