@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
package/PLASTIC.md CHANGED
@@ -50,6 +50,31 @@ tags: [plastic, architecture]
50
50
  - Context contract: load `sources` strongly (they are what the intent was built from);
51
51
  traverse `chain` lightly for discovery. See
52
52
  docs/concepts/how-plastic-sources-and-chains-intents.md for the full model.
53
+ - `## Links` (I5) is the human-readable projection of the graph. It mirrors the
54
+ frontmatter exactly: every entry is `- [[id--slug|<target's full intent: text>]]`, a
55
+ clickable `id--slug` wikilink target with the target intent's full `intent:` text as the
56
+ label (cross-store targets render `- [[store:id--slug|<target's full intent: text>]]`).
57
+ Ordering is mandatory: all `sources` first (top), then all `chain`, frontmatter order
58
+ preserved within each group. Sources never appear at the end. No source/chain tags, no
59
+ sub-grouping. An intent with empty `sources` and `chain` carries the empty-state comment.
60
+ - `## Links` is a DERIVED view, not a place to author links (Convention over Configuration).
61
+ It equals the projection of `sources` (first) then `chain`. Never hand-write or hand-edit a
62
+ `## Links` line, and never auto-delete one. The edge lives in the frontmatter graph; the
63
+ section is regenerated from it (doctor `graph_links_projection` enforces this identity). To
64
+ add a link, add the frontmatter edge, then reproject.
65
+ - Links are decided by CONTEXT INFLUENCE, not by shared files, shared symbols, or a topic
66
+ similarity score. The question is whether one intent's context actually informed another.
67
+ Three tiers:
68
+ - **sources:** the foundational context that shaped this intent's creation (a split, an idea
69
+ born during development, a merge). Earns an edge.
70
+ - **chain:** the context that materially helps DELIVER this intent. This is a HIGH bar: only
71
+ the genuinely delivery-moving intents, not everything in the same area. Earns an edge,
72
+ reflected in `## Links`.
73
+ - **tags:** a loose theme grouping for search. NOT a link. A shared tag is a door INTO the
74
+ store (filtered discovery), not a pathway BETWEEN two notes.
75
+ Judging influence is an agent's call, made by reading the candidate's Intent and Context. A
76
+ script cannot grade it, so `scripts/link-suggest` only gathers candidates with that evidence,
77
+ records a confirmed edge with a rating and reason, and flags drift.
53
78
  - IDs use Luhmann's alternating convention: `1` → `1a` → `1a1` → `1a1a`
54
79
  - Multiple branches increment: `1a`, `1b`, `1c`
55
80
 
@@ -72,10 +97,29 @@ The connection: an intent's `## Insights` feeds the Coordinator's Observe phase.
72
97
  | **How** | Planning | `plan.md` + `actions/` + `checklist.md` | `plastic-writing-plans` |
73
98
  | **Exec** | Execution | `outcome.md` | `plastic-executing-plan` |
74
99
 
75
- `## Insights` append-only work log captured throughout ALL stages. **Append-only means
76
- newest entry at the bottom; never prepend.** This ordering is a hard convention: Insights
77
- are the semantic trace of an intent, and a consistent newest-last order keeps that trace
78
- readable across every intent.
100
+ `## Insights` is the append-only log of durable discoveries captured throughout ALL stages.
101
+ An insight is a discovery worth keeping for later reads: novel, or old but newly relevant,
102
+ surfaced at any stage (What, Why, How, Exec). It is the most interesting residue of an
103
+ intent, the part a future reader most wants. **Append-only means newest entry at the bottom;
104
+ never prepend.** This ordering is a hard convention: Insights are the semantic trace of an
105
+ intent, and a consistent newest-last order keeps that trace readable across every intent.
106
+
107
+ Every entry leads with a fixed, machine-parseable prefix `{utc-iso8601} · {stage} · {author}`,
108
+ for example `2026-06-24T08:13:05Z · Why · plastic-brainstorming (autonomous)`. The UTC ISO8601
109
+ timestamp (to the second, trailing `Z`) is the same convention the savepoint ledger uses, so
110
+ the store has one timestamp convention. This per-entry prefix is not prepending the entry:
111
+ entries stay append-only, newest at the bottom; the prefix only stamps each line with when,
112
+ which stage, and who.
113
+
114
+ The blessed write path is the `insight-append` helper
115
+ (`scripts/insight-append <intent_dir> <text> --stage S --author A`), which formats the prefix,
116
+ validates it, and appends at the bottom. Hand-editing `## Insights` is an escape hatch; the
117
+ helper is the default so the format cannot drift.
118
+
119
+ Background sessions and dispatched sub-agents do not write the insight themselves. They carry
120
+ each nugget home in the completion report's `insights:` field, and the orchestrator (or any
121
+ agent that can write the file) persists it via the helper. A session that cannot write the
122
+ intent file still returns its report, so the insight survives.
79
123
  For full lifecycle detail, the skills in the Detail column have references/.
80
124
 
81
125
  `savepoint.md` — a deterministic, append-only ledger of cycle-step milestones (one line per
@@ -164,8 +208,8 @@ Format: `ID--three-to-five-words` (all stores).
164
208
  - **Root (`15`, `16`)**: an independent thought, even if inspired by another intent.
165
209
  Reserve `sources` for true created-from provenance (intents this was built out of). An
166
210
  independent intent merely related to or inspired by another carries NO `sources`; record
167
- the relation on the PREDECESSOR's `chain` (and mirror it as a `[[id]]` wikilink in
168
- `## Links`).
211
+ the relation on the PREDECESSOR's `chain` (and mirror it as a
212
+ `[[id--slug|<target's full intent: text>]]` wikilink in `## Links`).
169
213
  - **Rule of thumb:** if the intent could exist without its parent, it's a root.
170
214
 
171
215
  ## INDEX.md
@@ -176,6 +220,10 @@ Sections: `## Active`, `## Future`, `## Clusters`, `## Abandoned`, `## Completed
176
220
 
177
221
  For index maintenance, use `plastic-managing-index`.
178
222
 
223
+ One-line entry convention. Each index entry is ONE line: `- [<id> <terse title>](<dir>) <tags>`.
224
+ The title is the title, not a summary: aim for about 80 characters, no multi-sentence
225
+ descriptions. This is a self-check, not a gate.
226
+
179
227
  ## Rules for Skills
180
228
 
181
229
  ALL work flows through intents.
@@ -187,6 +235,62 @@ ALL work flows through intents.
187
235
  5. Researches are intents. No separate folder.
188
236
  6. Intents are created only via `plastic-creating-intent`. Never hand-author an intent file. The skill self-verifies the written intent with `scripts/validate-intent` before announcing or committing, so every intent is born complete.
189
237
 
238
+ ## House Style (self-check)
239
+
240
+ The agent is the heaviest contributor to the transcript, so terseness pays every turn. These
241
+ are pre-send self-checks the agent applies to its own output. They are not gated.
242
+
243
+ - Answer or decision first. Lead with the result, then support it.
244
+ - Bullets over paragraphs.
245
+ - No preamble, no end-recap. Do not restate the question or summarize what you just said.
246
+ - One question-cluster at a time when asking the human.
247
+ - Reasoning goes in the thinking channel, not duplicated into the visible reply. This keeps
248
+ the human's visibility into your reasoning without paying for it twice in the transcript.
249
+
250
+ Active-intent cache rule. For the intent under active development you already hold its
251
+ delivered artifacts in your own context: prefer revisiting that in-context memory (hit the
252
+ cache) over re-reading them from disk, which only widens context. QMD is for OTHER or indexed
253
+ intents, not for re-reading what you just wrote. Pairs with `/clear` plus savepoint-resume
254
+ hygiene after each intent. Advisory self-check, not hard-verifiable.
255
+
256
+ ## Retrieval Gate
257
+
258
+ A single capability-aware PreToolUse gate enforces retrieval-first routing on the agent's own
259
+ Bash/Read/Grep/Glob calls (and on subagents, since PreToolUse binds them). The gate is
260
+ OPERATION-based: it separates searching from reading, and it never stands between you and
261
+ reading something you have already located.
262
+
263
+ - Only CONTENT SEARCH over a Plastic store is gated. The Grep tool and bash `grep`/`rg`/`ag`
264
+ whose target is at or under a store route to QMD when QMD is present and the index is fresh:
265
+ the raw scan is blocked and you use `qmd search`/`qmd query` (or `scripts/qmd-sync search`)
266
+ instead. When QMD is present but stale, the search is allowed this turn and a background
267
+ reindex is fired so the next turn enforces against a fresh index; reindex is never
268
+ synchronous. When QMD is absent, the search is allowed.
269
+ - Reading a known target (the Read tool, bash `cat`/`head`/`tail`) and structural discovery
270
+ (the Glob tool, bash `find`/`ls`) are always allowed, including over the store. QMD cannot
271
+ list directories or hand back one specific file, so these are never gated.
272
+ - Code is never hard-gated here. Symbolic code navigation via Serena is a soft prompt mandate
273
+ (the UserPromptSubmit power-tools hook), not a block: content grep over code is allowed,
274
+ because Serena navigates symbols and cannot grep arbitrary strings.
275
+ - QMD failure model. Absent or stale degrades to allow (stale also fires the background
276
+ reindex). A broken QMD, where the freshness probe errors or times out, also fails open, and
277
+ the hook emits a one-line warning so a degraded QMD is visible rather than silent.
278
+ - Bypass: append a trailing `# qmd-ok` shell comment to a Bash command when you attempted
279
+ discovery and it did not serve you (no hits, or results that do not answer your need by your
280
+ reading of the snippets, not their score). A quoted or echoed occurrence does not bypass.
281
+ Bypasses are logged. The gate enforces that discovery was attempted, never that it succeeded.
282
+ - Scope: only the agent's tool calls. Ruby `File.read` inside a script is invisible to the gate
283
+ and is out of scope by design.
284
+
285
+ ## Context-economy measurement buckets (84a)
286
+
287
+ Intent 84 defines three buckets for sibling 84a to audit against; 84 does not run the audit.
288
+
289
+ - (a) gate-hook prose tokens: the per-transition narration emitted by the gate hook.
290
+ - (b) main-loop store-read tokens: tokens the main agent spends reading or grepping the store
291
+ in the transcript.
292
+ - (c) authored-section sizes: sizes of authored artifacts (INDEX entries and the like).
293
+
190
294
  ## Transition Gates
191
295
 
192
296
  | Transition | Trigger | Gate |
@@ -198,6 +302,26 @@ ALL work flows through intents.
198
302
 
199
303
  Hard blocking — hooks exit code 2 on gate failure.
200
304
 
305
+ ## Delivery Isolation and the Single-Owner Lock
306
+
307
+ Exactly one session or agent develops an intent's delivery at a time. Ownership is the armed
308
+ session bridge, which doubles as the delivery lock: arming records the owning session, the
309
+ owner pid, an acquired-at timestamp, and the host. Another session that finds an armed bridge
310
+ for the same intent with a live owner backs off; if the owner pid is dead the lock is
311
+ reclaimable. This is mandatory, not a convention.
312
+
313
+ Every code-touching intent gets its own git worktree named `{id}--{slug}`, and all code edits
314
+ for that intent happen only inside it. Plastic provisions the worktree deterministically: it
315
+ resolves the project repo from `projects.yml` and runs `git -C <repo> worktree add`, so
316
+ isolation never depends on the current working directory. There are two worktrees per project
317
+ intent: a code worktree at `<repo>/.claude/worktrees/{id}--{slug}` (branch `plastic/{id}--{slug}`)
318
+ and a store worktree at `<plastic_home>/.worktrees/{id}--{slug}` (branch
319
+ `plastic-store/{id}--{slug}`), so lifecycle-doc commits and code commits move as one unit.
320
+
321
+ Provisioning fails open for intents that touch no project code (pure research or decision
322
+ intents in the global store, or a non-git repo): those get the lock only, and the worktree
323
+ block stays unprovisioned. The fail-open path is always logged, never silent.
324
+
201
325
  ## Deprecation Process
202
326
 
203
327
  Deprecations live in `deprecations.yml` and are shown at SessionStart. While Plastic is
@@ -224,5 +348,5 @@ Detailed conventions live inside the skills that use them, not in this file.
224
348
  | Index maintenance | `plastic-managing-index` | — |
225
349
  | Releases, deprecations | `plastic-releasing` | deprecation process |
226
350
  | Health diagnostics | `plastic-doctor` | three scopes: `--core` (binary install-integrity check, runs on SessionStart), `--store [global\|<slug>]` (per-store check, runs on dashboard load), no flag = full check (runs after every update); gate enforcement, stuck detection |
227
- | Writing agent instructions | `plastic-writing-instructions` | agentskills.io spec |
351
+ | Authoring skills, agents, hooks | `plastic-creating-skills` | progressive disclosure, agentskills.io spec |
228
352
  | Evaluating skills, evals | `plastic-evaluating-skills` | eval methodology, convention checks |
@@ -27,7 +27,15 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
27
27
  2. Read the intent file, its `## Links`, and related code or docs
28
28
  3. Research with the adaptive budget the enforcer set (simple 2-3, medium 5-8, complex 10-15 steps)
29
29
  4. Produce (output handoff): an enriched `## Context` plus `### Decisions` with rationale
30
- 5. Log autonomous calls in `## Insights` with the `(autonomous)` marker, then report for gating
30
+ 5. Log autonomous calls in `## Insights` with the `(autonomous)` marker, then report for gating (see `## Completion Report`)
31
+
32
+ ## Completion Report
33
+
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the brainstorming payload:
35
+
36
+ - Decisions recorded in `### Decisions`, each with its one-line rationale
37
+ - Context enriched: what was researched and the key findings
38
+ - Open questions resolved, and any deliberately left for the spec
31
39
 
32
40
  ## Constraints
33
41
 
@@ -30,7 +30,7 @@ You are the Plastic Enforcer. You ARE the auto orchestrator, spanning the whole
30
30
 
31
31
  ## Constraints
32
32
 
33
- - Enforce gates manually; do not rely on hooks, because `CLAUDE_SESSION_ID` may be unset in headless or background runs
33
+ - Enforce gates manually; do not rely on hooks, because the session id may be unset in headless or background runs
34
34
  - You never delegate gate ownership; the orchestrator is always the gate-keeper
35
35
  - Roles are thin handoff contracts, not an execution engine; dispatch through `plastic-executing-plan` by default, and through the superpowers skills only when they are available or the user prefers them
36
36
  - Fall back by case: if the harness supports subagents but superpowers is absent, use the native `plastic-executing-plan` engine; if the harness has no subagent dispatch at all, fall back to a single agent walking the full cycle
@@ -18,7 +18,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
18
18
 
19
19
  1. **Implement the actions** — make the code changes for each action in order
20
20
  2. **Track progress** — check off `checklist.md` items as they complete
21
- 3. **Record insights** — append observations to `## Insights` with the `(autonomous)` marker
21
+ 3. **Record insights** — capture durable discoveries and report them in the `insights:` field; persist each to `## Insights` via the `insight-append` helper (`scripts/insight-append <intent_dir> <text> --stage Exec --author "plastic-executor (autonomous)"`), the blessed write path that stamps the `{utc-iso8601} · {stage} · {author}` prefix
22
22
  4. **Prove it green** — run the full test suite and reach zero failures before reporting done
23
23
 
24
24
  ## How You Work
@@ -28,6 +28,16 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
28
28
  3. Edit project code (the gate is open now that plan and checklist exist)
29
29
  4. Run the full suite, iterate to zero failures and zero errors
30
30
  5. Produce (output handoff): the code changes, a checked-off `checklist.md`, and `## Insights`
31
+ 6. Report for gating (see `## Completion Report`); the enforcer reviews the work at the final gate
32
+
33
+ ## Completion Report
34
+
35
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the executor payload:
36
+
37
+ - Actions implemented this turn, mapped to checklist items checked off (checked / total)
38
+ - A summary of the code changed (files and the shape of the change)
39
+ - Test result: the full-suite command and its pass / fail counts
40
+ - Insights appended, with the `(autonomous)` marker
31
41
 
32
42
  ## Constraints
33
43
 
@@ -18,23 +18,25 @@ You are the Plastic Intent Curator. Your role is to maintain the health and navi
18
18
 
19
19
  ## Your Responsibilities
20
20
 
21
- 1. **Intent lifecycle management** — move intents between Active/Future/Completed in INDEX.md, fill in `## Outcome` sections
22
- 2. **INDEX.md maintenance** — keep Active/Future/Clusters/Completed sections accurate and well-organized
21
+ 1. **Intent lifecycle management** — move intents between Active/Future/Completed/Abandoned in INDEX.md, fill in `## Outcome` sections (including the abandonment rationale when an intent is abandoned)
22
+ 2. **INDEX.md maintenance** — keep Active/Future/Clusters/Completed/Abandoned sections accurate and well-organized
23
23
  3. **Link discovery** — suggest connections between intents that share topics but aren't linked
24
24
  4. **Cluster management** — create new clusters when 3+ unlinked intents share tags, merge or rename clusters as topics evolve
25
25
  5. **Orphan detection** — flag intents with no links and no cluster membership
26
26
 
27
27
  ## How You Work
28
28
 
29
+ 0. QMD-first (when available): when you need to locate a specific intent (to reclassify, link, or cluster it) rather than rebuild the whole landscape, before scanning the store with grep/Read run `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate 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 full scan below. (This is discovery; the reindex step at a terminal-state transition is separate.)
29
30
  1. Scan `~/.plastic/store/*/ID--slug.md` (or project store) to understand the full intent landscape
30
31
  2. Read `~/.plastic/INDEX.md` (or project INDEX.md) to understand current organization
31
- 3. Compare: are there intents not in any cluster? Missing from Active/Completed? Status mismatches?
32
+ 3. Compare: are there intents not in any cluster? Missing from Active/Completed/Abandoned? Status mismatches?
32
33
  4. Make targeted edits to INDEX.md and intent frontmatter/links
33
- 5. Report what you changed
34
+ 5. On a terminal-state transition: whenever you move an intent to Completed OR Abandoned, refresh the QMD index for that store so the new outcome (or abandonment rationale) is searchable. This is mandatory on any terminal-state move and a no-op when QMD is absent, and it runs in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`
35
+ 6. Report what you changed
34
36
 
35
37
  ## Constraints
36
38
 
37
39
  - You only edit `~/.plastic/INDEX.md` (or project INDEX.md) and `~/.plastic/store/*/ID--slug.md` (or project store) files
38
40
  - You never create new intents — that's the creating-intent skill's job
39
41
  - You never modify `## Insights`, `## Context`, or `## Outcome` content sections — those belong to the worker
40
- - You use Read and grep/find for discovery, Edit for targeted changes
42
+ - For discovery, put QMD first when available (`qmd-sync search`), then fall back to Read and grep/find; use Edit for targeted changes
@@ -27,7 +27,17 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
27
27
  2. Read `spec.md` and the plan/checklist templates; match their FORM
28
28
  3. Write `plan.md`, the `actions/` directory, and `checklist.md` into the intent directory
29
29
  4. Produce (output handoff): `plan.md` plus `actions/` plus `checklist.md`
30
- 5. Report for gating; the enforcer verifies plan and checklist exist before Exec is allowed
30
+ 5. Report for gating (see `## Completion Report`); the enforcer verifies plan and checklist exist before Exec is allowed
31
+
32
+ ## Completion Report
33
+
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the planner payload, which EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR:
35
+
36
+ - The ordered actions, one line each: what the action does and how it is verified
37
+ - Decomposition rationale: why this order, and why the actions are independent
38
+ - Checklist coverage: the item count, and that every action plus suite-green is covered
39
+
40
+ The plan is an argument; the orchestrator gates on whether that argument is sound before any code is written, so make the report make that case.
31
41
 
32
42
  ## Constraints
33
43
 
@@ -27,7 +27,15 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
27
27
  2. Read the spec template (`templates/spec.md`) and match its FORM exactly
28
28
  3. Write `spec.md` into the intent directory, resolving the decisions into a coherent approach
29
29
  4. Produce (output handoff): a complete `spec.md`
30
- 5. Report for gating; the enforcer checks the spec against its exit criteria before How begins
30
+ 5. Report for gating (see `## Completion Report`); the enforcer checks the spec against its exit criteria before How begins
31
+
32
+ ## Completion Report
33
+
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the spec payload:
35
+
36
+ - The spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria)
37
+ - How the recorded decisions resolved into the chosen approach
38
+ - The acceptance-criteria count, so the planner knows the surface to cover
31
39
 
32
40
  ## Constraints
33
41
 
package/hooks/hooks.json CHANGED
@@ -40,6 +40,16 @@
40
40
  }
41
41
  ]
42
42
  },
43
+ {
44
+ "matcher": "Write|Edit",
45
+ "hooks": [
46
+ {
47
+ "type": "command",
48
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" savepoint-pre",
49
+ "statusMessage": "Recording stage start..."
50
+ }
51
+ ]
52
+ },
43
53
  {
44
54
  "matcher": "Write",
45
55
  "hooks": [
@@ -59,6 +69,16 @@
59
69
  "statusMessage": "Checking lifecycle gate..."
60
70
  }
61
71
  ]
72
+ },
73
+ {
74
+ "matcher": "Bash|Read|Grep|Glob",
75
+ "hooks": [
76
+ {
77
+ "type": "command",
78
+ "command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" retrieval-gate",
79
+ "statusMessage": "Checking retrieval gate..."
80
+ }
81
+ ]
62
82
  }
63
83
  ],
64
84
  "PostToolUse": [
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ # Retrieval-gate hook launcher (intent 84, Lever 2). No-op when there is no
3
+ # global store. Passes the PreToolUse JSON through on stdin (exec inherits it)
4
+ # and the plastic_home as ARGV[0], mirroring the qmd-search launcher.
5
+ GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
6
+ if [ ! -f "$GLOBAL_INDEX" ]; then
7
+ exit 0
8
+ fi
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ exec ruby "$SCRIPT_DIR/../scripts/hook-retrieval-gate" "$HOME/.plastic"
@@ -0,0 +1,10 @@
1
+ #!/bin/bash
2
+ INPUT=$(cat)
3
+ FILE_PATH=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("tool_params", "file_path") || data.dig("tool_input", "file_path") || ""' 2>/dev/null)
4
+
5
+ if [ -z "$FILE_PATH" ]; then
6
+ exit 0
7
+ fi
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ ruby "$SCRIPT_DIR/../scripts/hook-savepoint-pre" "$FILE_PATH"
package/hooks/statusline CHANGED
@@ -1,56 +1,165 @@
1
1
  #!/bin/bash
2
- # plastic-hook-version: 2.0.0
3
- # StatusLine hook chains user's original statusline, adds Plastic info
2
+ # plastic-hook-version: 3.0.0
3
+ # StatusLine hook - Plastic owns the full statusline (no chaining).
4
+ # Renders: {model} - Plastic {version}[ -> {next}] - {path} - [{project}: {id} {title}]
5
+ # Pure bash (macOS 3.2 compatible). No ruby, no jq. Reads stdin once; small file reads only.
4
6
 
5
7
  INPUT=$(cat)
6
8
 
7
- # --- Chain original statusline ---
8
- ORIGINAL_CONFIG="$HOME/.plastic/.cache/original-statusline.json"
9
- ORIGINAL_OUTPUT=""
10
- if [ -f "$ORIGINAL_CONFIG" ]; then
11
- ORIGINAL_CMD=$(grep -o '"command":"[^"]*"' "$ORIGINAL_CONFIG" 2>/dev/null | head -1 | cut -d'"' -f4)
12
- if [ -z "$ORIGINAL_CMD" ]; then
13
- ORIGINAL_CMD=$(grep -o '"command": "[^"]*"' "$ORIGINAL_CONFIG" 2>/dev/null | head -1 | sed 's/.*"command": "//;s/".*//')
14
- fi
15
- if [ -n "$ORIGINAL_CMD" ] && [ -x "$ORIGINAL_CMD" ]; then
16
- ORIGINAL_OUTPUT=$(echo "$INPUT" | "$ORIGINAL_CMD" 2>/dev/null)
9
+ # --- Colors (ANSI-C quoting; bash 3.2 supports $'\033') ---
10
+ C_MODEL=$'\033[38;2;90;140;220m' # navy blue - model (lightened for legibility)
11
+ C_VER=$'\033[37m' # white - "Plastic {version}" and update arrow
12
+ C_NEXT=$'\033[33m' # yellow - next version
13
+ C_PATH=$'\033[38;2;128;134;144m' # gray - path (matches dim UI text)
14
+ C_BRACK=$'\033[33m' # yellow - work-unit brackets
15
+ C_PROJ=$'\033[38;2;249;140;30m' # orange - project name
16
+ C_ID=$'\033[38;2;45;212;191m' # teal - intent id (accent between orange + yellow)
17
+ C_INTENT=$'\033[33m' # yellow - intent title
18
+ C_SEP=$'\033[38;2;128;134;144m' # gray - separators
19
+ RST=$'\033[0m'
20
+ SEP="${C_SEP} "$'\302\267'" ${RST}" # " . " middot, dim
21
+
22
+ # --- Extract a flat JSON string field from stdin: json_str <key> ---
23
+ json_str() {
24
+ printf '%s' "$INPUT" \
25
+ | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" \
26
+ | head -1 \
27
+ | sed "s/.*\"$1\"[[:space:]]*:[[:space:]]*\"//;s/\"$//"
28
+ }
29
+
30
+ # --- Model (model.display_name) ---
31
+ MODEL=$(json_str "display_name")
32
+
33
+ # --- Path (workspace.current_dir, fallback top-level cwd) ---
34
+ REAL_CWD=$(json_str "current_dir")
35
+ [ -z "$REAL_CWD" ] && REAL_CWD=$(json_str "cwd")
36
+ # Abbreviate $HOME -> ~
37
+ CWD="$REAL_CWD"
38
+ case "$CWD" in
39
+ "$HOME") CWD="~" ;;
40
+ "$HOME"/*) CWD="~${CWD#"$HOME"}" ;;
41
+ esac
42
+
43
+ # --- Plastic version ---
44
+ VERSION=""
45
+ [ -f "$HOME/.plastic/VERSION" ] && VERSION=$(tr -d ' \t\n' < "$HOME/.plastic/VERSION")
46
+
47
+ # --- Update (next version when one is available) ---
48
+ NEXT=""
49
+ CACHE_FILE="$HOME/.plastic/.cache/update-check.json"
50
+ if [ -f "$CACHE_FILE" ]; then
51
+ if grep -q '"updateAvailable"[[:space:]]*:[[:space:]]*true' "$CACHE_FILE" 2>/dev/null; then
52
+ LATEST=$(grep -o '"latest"[[:space:]]*:[[:space:]]*"[^"]*"' "$CACHE_FILE" 2>/dev/null \
53
+ | head -1 | sed 's/.*"latest"[[:space:]]*:[[:space:]]*"//;s/"$//')
54
+ if [ -n "$LATEST" ]; then
55
+ case "$LATEST" in
56
+ *-*) NEXT="${LATEST##*-}" ;; # 1.0.0-beta.3 -> beta.3
57
+ *) NEXT="$LATEST" ;;
58
+ esac
59
+ fi
17
60
  fi
18
61
  fi
19
62
 
20
- # --- Plastic status ---
21
- PLASTIC_PARTS=""
63
+ # --- Scope resolution: cwd -> registered project store, else global ---
64
+ # Match the longest registered project path that prefixes the real cwd.
65
+ PROJECTS_YML="$HOME/.plastic/projects.yml"
66
+ SLUG=""
67
+ if [ -n "$REAL_CWD" ] && [ -f "$PROJECTS_YML" ]; then
68
+ SLUG=$(awk -v cwd="$REAL_CWD" '
69
+ /^ [A-Za-z0-9_.-]+:[[:space:]]*$/ { s=$1; sub(/:$/,"",s); next }
70
+ /^ path:/ {
71
+ p=$0; sub(/^[[:space:]]*path:[[:space:]]*/,"",p); gsub(/"/,"",p);
72
+ if (length(p)>0 && (cwd==p || index(cwd, p"/")==1) && length(p)>best) { best=length(p); win=s }
73
+ }
74
+ END { if (win!="") print win }
75
+ ' "$PROJECTS_YML")
76
+ fi
22
77
 
23
- # Active intent from INDEX.md
24
- INDEX="$HOME/.plastic/INDEX.md"
25
- if [ -f "$INDEX" ]; then
26
- ACTIVE=$(sed -n '/^## Active$/,/^## /{/^- \[/p;}' "$INDEX" | head -1)
27
- if [ -n "$ACTIVE" ]; then
28
- INTENT=$(echo "$ACTIVE" | sed 's/^- \[\([^]]*\)\].*/\1/')
29
- PLASTIC_PARTS="plastic: ${INTENT}"
30
- fi
78
+ if [ -n "$SLUG" ]; then
79
+ INDEX="$HOME/.plastic/projects/$SLUG/INDEX.md"
80
+ PROJ_LABEL="$SLUG"
81
+ else
82
+ INDEX="$HOME/.plastic/INDEX.md"
83
+ PROJ_LABEL="global"
31
84
  fi
85
+ INDEX_DIR=$(dirname "$INDEX")
32
86
 
33
- # Update notification
34
- CACHE_FILE="$HOME/.plastic/.cache/update-check.json"
35
- if [ -f "$CACHE_FILE" ]; then
36
- UPDATE_AVAILABLE=$(grep -o '"updateAvailable":true' "$CACHE_FILE" 2>/dev/null)
37
- if [ -n "$UPDATE_AVAILABLE" ]; then
38
- LATEST=$(grep -o '"latest":"[^"]*"' "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f4)
39
- CURRENT=$(grep -o '"current":"[^"]*"' "$CACHE_FILE" 2>/dev/null | head -1 | cut -d'"' -f4)
40
- UPDATE_MSG="plastic update: $CURRENT -> $LATEST"
41
- if [ -n "$PLASTIC_PARTS" ]; then
42
- PLASTIC_PARTS="$PLASTIC_PARTS | $UPDATE_MSG"
43
- else
44
- PLASTIC_PARTS="$UPDATE_MSG"
87
+ # --- Current-session work-unit: the live bridge wins over savepoint recency ---
88
+ # The statusline receives the real session_id on stdin; the session's bridge
89
+ # (plastic-{session_id}.json) is the authoritative "what THIS session is driving".
90
+ # This beats the shared, savepoint-recency heuristic so parallel sessions on one
91
+ # store no longer overwrite each other's line. grep/sed only - no jq, no ruby.
92
+ WORK=""
93
+ SID=$(json_str "session_id")
94
+ if [ -n "$SID" ]; then
95
+ BRIDGE="${PLASTIC_TMP:-/tmp}/plastic-${SID}.json"
96
+ if [ -f "$BRIDGE" ]; then
97
+ # Bridge JSON is pretty-printed (one field per line). Scope extraction to the
98
+ # "intent" object so a sibling top-level "id"/"name" can never win (the intent
99
+ # object holds only string fields, so the first "}" closes it). grep/sed only.
100
+ INTENT_BLOCK=$(sed -n '/"intent"[[:space:]]*:[[:space:]]*{/,/}/p' "$BRIDGE" 2>/dev/null)
101
+ B_ID=$(printf '%s\n' "$INTENT_BLOCK" | grep -o '"id"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 \
102
+ | sed 's/.*"id"[[:space:]]*:[[:space:]]*"//;s/"$//')
103
+ B_NAME=$(printf '%s\n' "$INTENT_BLOCK" | grep -o '"name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 \
104
+ | sed 's/.*"name"[[:space:]]*:[[:space:]]*"//;s/"$//')
105
+ if [ -n "$B_ID" ] && [ -n "$B_NAME" ]; then
106
+ EMDASH=$'\342\200\224'
107
+ WORK="${B_ID} ${EMDASH} ${B_NAME}"
45
108
  fi
46
109
  fi
47
110
  fi
48
111
 
49
- # --- Combine output ---
50
- if [ -n "$ORIGINAL_OUTPUT" ] && [ -n "$PLASTIC_PARTS" ]; then
51
- echo "$ORIGINAL_OUTPUT | $PLASTIC_PARTS"
52
- elif [ -n "$ORIGINAL_OUTPUT" ]; then
53
- echo "$ORIGINAL_OUTPUT"
54
- elif [ -n "$PLASTIC_PARTS" ]; then
55
- echo "$PLASTIC_PARTS"
112
+ # --- Most-recently-accessed active intent (mirror dashboard: last savepoint ts) ---
113
+ # Fallback only: runs when no session bridge resolved a work-unit above.
114
+ if [ -z "$WORK" ] && [ -f "$INDEX" ]; then
115
+ BEST_TS=""
116
+ while IFS= read -r line; do
117
+ [ -z "$line" ] && continue
118
+ inside=${line#*[}; inside=${inside%%]*} # "{id} - {title}"
119
+ dir=${line#*](store/}; dir=${dir%%/*} # intent directory name
120
+ [ -z "$dir" ] && continue
121
+ sp="$INDEX_DIR/store/$dir/savepoint.md"
122
+ ts=""
123
+ if [ -f "$sp" ]; then
124
+ ts=$(grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z' "$sp" 2>/dev/null | tail -1)
125
+ fi
126
+ # ISO8601 sorts lexicographically; keep the newest. Empty ts sorts lowest.
127
+ if [ -z "$BEST_TS" ] || { [ -n "$ts" ] && [ "$ts" \> "$BEST_TS" ]; }; then
128
+ BEST_TS="$ts"
129
+ WORK="$inside"
130
+ fi
131
+ done <<EOF
132
+ $(sed -n '/^## Active$/,/^## /{/^- \[/p;}' "$INDEX")
133
+ EOF
134
+ fi
135
+
136
+ # --- Build the work-unit "[{project}: {id} {title}]" with title truncation ---
137
+ WORK_SEG=""
138
+ if [ -n "$WORK" ]; then
139
+ # Split "{id} - {title}" on the em-dash (U+2014).
140
+ EMDASH=$'\342\200\224'
141
+ id=${WORK%% $EMDASH *}
142
+ title=${WORK#* $EMDASH }
143
+ # Fallback if the em-dash form is absent: split on first space.
144
+ if [ "$id" = "$WORK" ]; then id=${WORK%% *}; title=${WORK#* }; fi
145
+ # Truncate long titles (~40 chars).
146
+ if [ ${#title} -gt 40 ]; then title="$(printf '%.40s' "$title")..."; fi
147
+ WORK_SEG="${C_BRACK}[${RST}${C_PROJ}${PROJ_LABEL}${RST}${C_INTENT}: ${RST}${C_ID}${id}${RST}${C_INTENT} ${title}${RST}${C_BRACK}]${RST}"
56
148
  fi
149
+
150
+ # --- Assemble segments in order, joined by " . " ---
151
+ PARTS=""
152
+ add() { [ -z "$1" ] && return; if [ -z "$PARTS" ]; then PARTS="$1"; else PARTS="$PARTS$SEP$1"; fi; }
153
+
154
+ # Order: stable values first (model, work-unit), then the volatile-length
155
+ # values last (version, path) so the line does not visually jump as they change.
156
+ [ -n "$MODEL" ] && add "${C_MODEL}${MODEL}${RST}"
157
+ [ -n "$WORK_SEG" ] && add "$WORK_SEG"
158
+ if [ -n "$VERSION" ]; then
159
+ vseg="${C_VER}Plastic ${VERSION}${RST}"
160
+ [ -n "$NEXT" ] && vseg="${vseg}${C_VER} "$'\342\206\222'" ${RST}${C_NEXT}${NEXT}${RST}"
161
+ add "$vseg"
162
+ fi
163
+ [ -n "$CWD" ] && add "${C_PATH}${CWD}${RST}"
164
+
165
+ printf '%s\n' "$PARTS"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "1.0.0-beta.2",
3
+ "version": "1.0.0-beta.21",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {