@zalom/plastic 1.8.0 → 1.9.0

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 (67) hide show
  1. package/PLASTIC.md +75 -552
  2. package/README.md +3 -2
  3. package/agents/plastic-intent-curator.md +2 -2
  4. package/deprecations.yml +10 -2
  5. package/hooks/{links-gate → edit-gates} +1 -1
  6. package/hooks/hooks.json +4 -44
  7. package/hooks/power-tools +8 -0
  8. package/package.json +1 -2
  9. package/scripts/codex-hook +17 -22
  10. package/scripts/doctor.rb +85 -3
  11. package/scripts/hook-code-gate +13 -28
  12. package/scripts/hook-create-gate +10 -57
  13. package/scripts/hook-edit-gates +58 -0
  14. package/scripts/hook-links-gate +8 -31
  15. package/scripts/hook-lock-gate +13 -58
  16. package/scripts/hook-power-tools +38 -0
  17. package/scripts/hook-savepoint-pre +8 -11
  18. package/scripts/lib/bridge.rb +2 -2
  19. package/scripts/lib/edit_gates.rb +342 -0
  20. package/scripts/lib/hook_registry.rb +61 -29
  21. package/scripts/lib/installer_core.rb +99 -17
  22. package/scripts/lib/qmd_hook.rb +21 -41
  23. package/scripts/lib/qmd_sync.rb +0 -15
  24. package/scripts/lib/revisions_writer.rb +1 -1
  25. package/scripts/restore-intent-v1 +16 -11
  26. package/skills/auto/SKILL.md +10 -0
  27. package/skills/auto/evals/evals.json +2 -2
  28. package/skills/conventions/SKILL.md +31 -0
  29. package/skills/conventions/references/completion-and-done.md +40 -0
  30. package/skills/conventions/references/gates-and-enforcement.md +23 -0
  31. package/skills/conventions/references/knowledge-graph.md +47 -0
  32. package/skills/conventions/references/lifecycle-and-savepoints.md +11 -0
  33. package/skills/conventions/references/locks-and-worktrees.md +113 -0
  34. package/skills/conventions/references/maintenance-and-revisions.md +170 -0
  35. package/skills/conventions/references/roadmaps.md +44 -0
  36. package/skills/conventions/references/tiers-and-dispatch.md +134 -0
  37. package/skills/doctor/SKILL.md +9 -1
  38. package/skills/intent-brainstorming/SKILL.md +4 -0
  39. package/skills/intent-continuing/SKILL.md +4 -0
  40. package/skills/intent-creating/SKILL.md +7 -2
  41. package/skills/intent-ending/SKILL.md +4 -0
  42. package/skills/intent-executing/SKILL.md +10 -0
  43. package/skills/intent-linking/SKILL.md +4 -0
  44. package/skills/intent-locking/SKILL.md +4 -0
  45. package/skills/intent-planning/SKILL.md +7 -0
  46. package/skills/intent-savepoint/SKILL.md +4 -0
  47. package/skills/intent-speccing/SKILL.md +4 -0
  48. package/skills/intent-starting/SKILL.md +10 -0
  49. package/skills/project-creating/references/project-scaffolding.md +2 -2
  50. package/skills/roadmap/SKILL.md +4 -0
  51. package/skills/roadmap-continuing/SKILL.md +4 -0
  52. package/skills/skill-creating/SKILL.md +3 -0
  53. package/skills/skill-creating/references/defaults-first.md +23 -0
  54. package/skills/skill-creating/references/hooks.md +4 -1
  55. package/skills/store-curating/SKILL.md +8 -0
  56. package/skills/store-indexing/SKILL.md +8 -0
  57. package/skills/tutorial/references/track-2-auto.md +2 -3
  58. package/PLASTIC-reference.md +0 -138
  59. package/hooks/code-gate +0 -27
  60. package/hooks/create-gate +0 -3
  61. package/hooks/lock-gate +0 -21
  62. package/hooks/qmd-search +0 -8
  63. package/hooks/retrieval-gate +0 -10
  64. package/hooks/savepoint-pre +0 -10
  65. package/scripts/hook-qmd-search +0 -44
  66. package/scripts/hook-retrieval-gate +0 -148
  67. package/scripts/lib/retrieval_gate.rb +0 -211
@@ -22,6 +22,10 @@ It is **sugar on top of the conventions**, not a source of truth. The gate hook
22
22
  nothing to save by hand. State is always derivable from files-on-disk; the ledger just lets
23
23
  a resuming agent read the cycle's succession from one glance (last line = where we are).
24
24
 
25
+ Read `../plastic-conventions/references/lifecycle-and-savepoints.md` for the subagent
26
+ report-home contract that this ledger feeds. This path resolves relative to this skill's own
27
+ installed directory.
28
+
25
29
  ## When to Use
26
30
  - A PreCompact hook fires, or the user says "save" / "savepoint": verify the ledger is current.
27
31
  - Resuming an intent: read the ledger to learn the cycle's succession quickly.
@@ -47,6 +47,10 @@ not the Transition Gates table row). Writing `plan.md` is what opens the code ga
47
47
  writing `plan.md` is not this skill's job, that is `plastic-intent-planning`. State this to the user
48
48
  at step 6 so the handoff at step 8 is expected, not a surprise.
49
49
 
50
+ Read `../plastic-conventions/references/lifecycle-and-savepoints.md` for the subagent
51
+ report-home contract behind this handoff. This path resolves relative to this skill's own
52
+ installed directory.
53
+
50
54
  ## Tier stamp (step 3, convention only)
51
55
 
52
56
  Write `Tier: S|M|L` as the literal first line of the file, above the `# Spec:` heading. This line
@@ -32,6 +32,10 @@ The lock is non-negotiable and comes before any mutating work. The ACTION-3 lock
32
32
  enforces it: without a held lock, mutating writes to this active intent's dir are denied with
33
33
  "run /plastic-intent-starting to lock and begin".
34
34
 
35
+ Read `../plastic-conventions/references/locks-and-worktrees.md` for delivery isolation: the
36
+ single-owner lock, claims, worktrees, solo mode, and the station ledger behind everything below.
37
+ This path resolves relative to this skill's own installed directory.
38
+
35
39
  1. **Ensure the intent is in INDEX `## Active`.** If it sits in `## Future`, activate it
36
40
  (move it to `## Active`, auto-commit) before arming. Creation precedes activation, so a
37
41
  brand-new What intent is activated here, then locked.
@@ -106,12 +110,18 @@ Never re-ask at a later station.
106
110
  - **auto** → `arm_auto` (lock + auto), then hand off to `plastic-auto`. The auto branch's
107
111
  only remaining job is the handoff; `plastic-auto` runs the cycle from here.
108
112
 
113
+ Read `../plastic-conventions/references/tiers-and-dispatch.md` for tier sizing and the
114
+ stage-to-agent dispatch rules behind the auto branch above.
115
+
109
116
  ## Board at the latest delivered station
110
117
 
111
118
  The station is derived from `savepoint.md` last line + real artifacts on disk. See
112
119
  `references/boarding-matrix.md` for the full table (last line → latest delivered → boards at →
113
120
  continue with) and the per-station notes. Summary of what "continue" means per station:
114
121
 
122
+ Read `../plastic-conventions/references/gates-and-enforcement.md` for the transition-gate
123
+ mechanics, the audited escape, and gate logging that govern moving between the stations below.
124
+
115
125
  - **What** → do what What requires (106-expanded), then brainstorm → `spec.md`.
116
126
  - **Why** → continue brainstorming → `spec.md`.
117
127
  - **How** → continue `plan.md` + `actions/` + `checklist.md`.
@@ -17,8 +17,8 @@ Create `AGENTS.md` in the project root with:
17
17
  ```markdown
18
18
  # <Project Name> — Agent Instructions
19
19
 
20
- Read `PLASTIC.md` in `~/.plastic/`. It contains all Plastic conventions.
21
- Follow it exactly.
20
+ Read `PLASTIC.md` in `~/.plastic/` for the core conventions; deeper doctrine lives in
21
+ the `plastic-conventions` skill's chapters. Follow it exactly.
22
22
 
23
23
  This file is the operating contract for this project. Any agent entering
24
24
  this project reads this file first.
@@ -43,6 +43,10 @@ See `references/file-format.md` for the exact entry-line shape, status vocabular
43
43
  format, and a worked example. See `references/operations.md` for step-by-step mechanics of each
44
44
  verb above.
45
45
 
46
+ Read `../plastic-conventions/references/roadmaps.md` for the roadmap file format, batch
47
+ semantics, and the status-mirror rule that this skill's own file-format reference builds on. This
48
+ path resolves relative to this skill's own installed directory.
49
+
46
50
  ## Notes
47
51
 
48
52
  - File location and the four-section shape are identical across tiers; do not invent a different
@@ -45,6 +45,10 @@ roadmap handoff had to be resumed by hand, carried as a free-prose note in `171`
45
45
  4. A genuine tie (two candidates equally live) is presented to the user and resolved by the
46
46
  single ask below, not silently picked.
47
47
 
48
+ Read `../plastic-conventions/references/roadmaps.md` for the roadmap file format, batch
49
+ semantics, and the status-mirror rule behind the candidates gathered above. This path resolves
50
+ relative to this skill's own installed directory.
51
+
48
52
  ## Present state
49
53
 
50
54
  Present the chosen roadmap's `## Goal`, the current batch with each entry's mirrored status, the
@@ -49,6 +49,9 @@ routes each authoring task to the reference that holds the depth.
49
49
  | Deciding script versus prose, or writing a script | `references/scripts.md` |
50
50
  | Building evals for a skill | `references/evals.md` |
51
51
 
52
+ Read `references/defaults-first.md` before naming an external skill as a default, for the
53
+ Plastic-first, delegate-by-exception rule.
54
+
52
55
  ## Shrink context, or let a skill self-improve
53
56
 
54
57
  - When prompts or tool output blow the context budget, open `references/hooks.md` (E7) for
@@ -0,0 +1,23 @@
1
+ # Defaults First
2
+
3
+ This chapter holds the Plastic-first, delegate-by-exception rule for naming an external skill as a default.
4
+
5
+ ### Defaults-First
6
+
7
+ Plastic stands on its own. Skills and agents use Plastic's own defaults; an
8
+ external skill (for example `superpowers:*`) is opt-in, never load-bearing.
9
+
10
+ - **Default to Plastic, delegate by exception.** Name the Plastic-native path as
11
+ the default. Delegate to an external skill only when (a) it is available in the
12
+ harness, or (b) the user explicitly asks for it. A user without that plugin must
13
+ still get the core behavior.
14
+ - **Phrase external skills as enhancements.** Write "use Plastic's native X by
15
+ default; if `superpowers:<skill>` is available, or the user prefers it, delegate
16
+ to it" never "delegate to `superpowers:<skill>`" as the only path.
17
+ - **Optional dependencies detect then degrade.** `qmd` is the reference shape:
18
+ `scripts/lib/qmd_sync.rb` detects the binary first and every verb no-ops cleanly
19
+ when it is absent (see `scripts/qmd-sync`). Optional CLIs and MCP servers follow
20
+ the same detect-then-skip pattern, so a missing tool never crashes a session.
21
+ - **Legitimate hard dependencies are exempt.** Ruby, Node, git, and POSIX tools are
22
+ the cost of running Plastic, not silent coupling. The principle targets accidental
23
+ dependence on external skills doing work Plastic should do itself.
@@ -77,7 +77,10 @@ Notes that change the choice of event: [E2]
77
77
  - PreCompact fires before the runtime compacts the conversation. Use it to write a savepoint
78
78
  while the full context still exists.
79
79
 
80
- Plastic ships working instances of each: `scripts/hook-code-gate` (PreToolUse gate),
80
+ Plastic ships working instances of each: `hooks/edit-gates` -> `scripts/hook-edit-gates`
81
+ (PreToolUse, the merged dispatcher that runs code-gate, lock-gate, savepoint-pre, links-gate,
82
+ and create-gate in one process; each gate's own logic lives in `scripts/lib/edit_gates.rb`
83
+ and is also reachable through the standalone `scripts/hook-code-gate` CLI wrapper),
81
84
  `scripts/hook-session-start` (SessionStart boot and inject), `scripts/hook-savepoint-pre`
82
85
  (PreCompact savepoint). Read one before authoring a new hook of the same shape.
83
86
 
@@ -39,6 +39,10 @@ The agent handles:
39
39
  - Cluster management (create, merge, rename)
40
40
  - Orphan detection
41
41
 
42
+ Read `../plastic-conventions/references/knowledge-graph.md` for the linking doctrine: tiers of
43
+ influence, sources versus chain, and the `## Links` projection, before judging a link discovery or
44
+ orphan finding. This path resolves relative to this skill's own installed directory.
45
+
42
46
  When an intent reaches a terminal state, moved to Completed OR Abandoned, do these things:
43
47
 
44
48
  1. Author a real `outcome.md` in the intent directory from `~/.plastic/templates/outcome.md`, with the frontmatter `disposition: delivered` for a completed intent or `disposition: abandoned` for an abandoned one. `outcome.md` is MANDATORY at every terminal, delivered and abandoned alike: on abandon it records the abandonment reason and replaces the scaffolded placeholder sentinel (never leave `outcome.md` a placeholder at a terminal).
@@ -54,3 +58,7 @@ follows its own step 7: it detects (never acquires) the target's delivery lock,
54
58
  clean working tree, and performs the fix on a fresh branch merged back to main as one closed
55
59
  operation, with an append-only `revisions.md` receipt in the same pass as the edit. See
56
60
  `agents/plastic-intent-curator.md` for the exact mechanics.
61
+
62
+ Read `../plastic-conventions/references/maintenance-and-revisions.md` for WORK versus
63
+ MAINTENANCE, the `revisions.md` move-and-record contract, and the violation-tag catalog before
64
+ running a maintenance dispatch like this one.
@@ -75,8 +75,16 @@ When 3+ intents share tags but aren't in a cluster, suggest a new cluster headin
75
75
  ### Flag Orphans
76
76
  Intents with no links (empty `sources`, empty `chain`, no `## Links` entries, not in any cluster) should be flagged for curation.
77
77
 
78
+ Before reclassifying a structural finding outside routine indexing, read
79
+ `../plastic-conventions/references/maintenance-and-revisions.md` for WORK versus MAINTENANCE, the
80
+ `revisions.md` move-and-record contract, and the violation-tag catalog.
81
+
78
82
  REQUIRED BACKGROUND: intent-linking (for understanding connection types and Zettelkasten theory)
79
83
 
84
+ Read `../plastic-conventions/references/knowledge-graph.md` for the linking doctrine: tiers of
85
+ influence, sources versus chain, and the `## Links` projection. This path resolves relative to
86
+ this skill's own installed directory.
87
+
80
88
  ## References
81
89
 
82
90
  - Read `references/zettelkasten-linking.md` for the three structural layers (Folgezettel, directed graph, tags) and how they map to INDEX.md organization
@@ -56,10 +56,9 @@ Checkpoint: name one thing auto will always stop and ask about, rather than deci
56
56
  No new command. Auto still honors every hard gate a guided session would hit: the delivery
57
57
  lock (one owner at a time), the code gate (shut until `plan.md` and `checklist.md` exist),
58
58
  and the create gate (intents only come from the tool that makes them, never hand-authored).
59
- One gate, retrieval, is advisory only and never blocks anything; it just adds a note.
60
59
 
61
- Checkpoint: read one gate message from the run so far and say whether it is one of the hard
62
- gates or the one advisory note.
60
+ Checkpoint: read one gate message from the run so far and name which of the three gates it
61
+ came from.
63
62
 
64
63
  ### 4. Reading the per-stage reports
65
64
 
@@ -1,138 +0,0 @@
1
- # Plastic - Reference
2
-
3
- > **This file is maintained by Plastic.** It will be overwritten when the
4
- > plugin is updated. It holds reference material: read it on demand, it is
5
- > not injected at session start.
6
-
7
- ### Structural maintenance and revisions.md
8
-
9
- When a delivered intent accumulates structural junk (an unsanctioned section, a stray file, a
10
- frontmatter edge to an intent that no longer exists), the intent-curator relocates it into
11
- `revisions.md` instead of reopening the work. Each entry is a versioned, dated header
12
- (`## Revision vN - YYYY-MM-DD-HH:MM`) plus `Why` (one sentence naming the broken rule, ending
13
- with `[rule: <tag>]`), `Prior location`, and either `Content held` (the verbatim removed
14
- content) or, for a frontmatter edit, a one-line `Change` (before and after). A stray file has
15
- its full content embedded and the original is deleted.
16
-
17
- Violation tags (starter set, free-text tags allowed):
18
- - `unsanctioned-section`: a top-level section the sanctioned-section rule now rejects
19
- - `phantom-section`: a section referenced but not present or not sanctioned
20
- - `stray-file`: a file that does not belong in the intent directory
21
- - `dangling-ref`: a link or reference to something that no longer exists
22
- - `broken-chain`: a chain frontmatter edge to an intent that no longer exists
23
- - `broken-source`: a sources frontmatter edge to an intent that no longer exists
24
- - `misplaced-content`: content that belongs in a different artifact or section
25
- - `links-projection`: a tool-authored `## Links` regeneration (project-links; intent 197)
26
- - `graph-rebuild`: a tool-authored sources/chain frontmatter rebuild (rebuild-graph; intent 197)
27
-
28
- ## Two Processes
29
-
30
- | Process | Scope | Type | Actor |
31
- |---|---|---|---|
32
- | **Build → Observe → Repeat** | The system | Continuous loop | Coordinator |
33
- | **What → Why → How → Exec** | One intent | Finite lifecycle | Agent |
34
-
35
- B→O→R is the Coordinator's heartbeat. W→W→H→E is what happens inside each intent.
36
- The connection: an intent's `## Insights` feeds the Coordinator's Observe phase.
37
-
38
- ## Defaults-First
39
-
40
- Plastic stands on its own. Skills and agents use Plastic's own defaults; an
41
- external skill (for example `superpowers:*`) is opt-in, never load-bearing.
42
-
43
- - **Default to Plastic, delegate by exception.** Name the Plastic-native path as
44
- the default. Delegate to an external skill only when (a) it is available in the
45
- harness, or (b) the user explicitly asks for it. A user without that plugin must
46
- still get the core behavior.
47
- - **Phrase external skills as enhancements.** Write "use Plastic's native X by
48
- default; if `superpowers:<skill>` is available, or the user prefers it, delegate
49
- to it" never "delegate to `superpowers:<skill>`" as the only path.
50
- - **Optional dependencies detect then degrade.** `qmd` is the reference shape:
51
- `scripts/lib/qmd_sync.rb` detects the binary first and every verb no-ops cleanly
52
- when it is absent (see `scripts/qmd-sync`). Optional CLIs and MCP servers follow
53
- the same detect-then-skip pattern, so a missing tool never crashes a session.
54
- - **Legitimate hard dependencies are exempt.** Ruby, Node, git, and POSIX tools are
55
- the cost of running Plastic, not silent coupling. The principle targets accidental
56
- dependence on external skills doing work Plastic should do itself.
57
-
58
- ## Roadmaps
59
-
60
- Roadmaps exist for planned parallel delivery of intents in a coherent and organized way. A roadmap
61
- is a named, ordered, delivery-side collection of intents: the delivery-side counterpart to a
62
- release (completion-side, tracked in `CHANGELOG.md`). Use `plastic-roadmap` to create, order,
63
- close, and consume one.
64
-
65
- File location: `roadmaps/{slug}.md`, a sibling of `INDEX.md`, wherever `INDEX.md` lives, never
66
- inside `store/` (store holds intent directories, not project artifacts). For a project that is its
67
- root, `~/.plastic/projects/{slug}/roadmaps/`, beside `project.yml`; for the global tier it is
68
- `~/.plastic/roadmaps/`, beside `~/.plastic/INDEX.md`. `roadmaps/` lists only live (open or
69
- in-flight) roadmaps: once a roadmap's goal is reached, it moves to `roadmaps/archived/{slug}.md`,
70
- a sibling subdirectory scaffolded once with a `.gitkeep`.
71
-
72
- A roadmap file has four sections, in order: a title/meta header, `## Goal`, `## Batches`, and an
73
- append-only dated `## Log`. `## Goal` is a checkable prose condition read by a human or agent, not
74
- an executable checker. `## Batches` holds ordered batches; entries inside a batch are
75
- parallel-safe, batches run sequentially, top to bottom. A roadmap written before owner ruling 145
76
- may instead use the legacy `## Waves` heading; the tooling accepts both, but never renames an
77
- existing roadmap file to migrate it.
78
-
79
- Each batch entry carries a status token (`queued`/`delivering`/`delivered`/`abandoned`/`blocked`)
80
- that mirrors that intent's status in `INDEX.md`. `INDEX.md` is the single writer of intent status;
81
- on any conflict INDEX wins and the roadmap entry is corrected to match.
82
-
83
- **Human-comprehension surface.** A roadmap is also written to be read cold. Batch entries render as
84
- checkboxes (checked once delivered, unchecked otherwise) next to the status token, and each `## Log`
85
- line is one plain-language sentence, starting `YYYY-MM-DD HH:MM UTC`, written the way an
86
- engineering manager would brief a non-expert executive: what shipped and why it matters, no jargon
87
- or codenames, ending with a link
88
- to that intent's `outcome.md`. The log points at the detail instead of repeating it, so a person
89
- opening the file with no other context can tell what shipped, what is running now, and what is
90
- next in under a minute.
91
-
92
- **Relationship to loop engineering (intent 69).** A roadmap is the planning half of the work; the
93
- loop is its runtime. Batches lay out the parallelism plan: what can run together, and in what order.
94
- Loop engineering (intent 69, not yet delivered) is expected to consume that plan and supply the
95
- running parts, the heartbeat, how many dispatches run at once, checking the goal, and resuming
96
- after a stop. This section only states the relationship and points to intent 69 as the future
97
- consumer; it does not change intent 69's own design.
98
-
99
- ## Context-economy measurement buckets (84a)
100
-
101
- Intent 84 defines three buckets for sibling 84a to audit against; 84 does not run the audit.
102
-
103
- - (a) gate-hook prose tokens: the per-transition narration emitted by the gate hook.
104
- - (b) main-loop store-read tokens: tokens the main agent spends reading or grepping the store
105
- in the transcript.
106
- - (c) authored-section sizes: sizes of authored artifacts (INDEX entries and the like).
107
-
108
- ## Deprecation Process
109
-
110
- Deprecations live in `deprecations.yml` and are shown at SessionStart. While Plastic is
111
- pre-1.0, a satisfied deprecation (its migration is already done on installed machines) may be
112
- removed immediately instead of waiting for its declared `removal` version. From `1.0.0` on,
113
- the steady-state grace rule applies (removal at least two minors ahead). For the full process,
114
- severity levels, and the pre-1.0 exception, see the `plastic-releasing` skill.
115
-
116
- ## Skills Reference
117
-
118
- Detailed conventions live inside the skills that use them, not in this file.
119
-
120
- | Topic | Skill | References in skill |
121
- |-------|-------|-------------------|
122
- | Creating intents, lifecycle | `plastic-intent-creating` | lifecycle, wikilinks |
123
- | Brainstorming, spec writing | `plastic-intent-brainstorming` | — |
124
- | Planning, actions | `plastic-intent-planning` | — |
125
- | Execution, delivery | `plastic-intent-executing` | — |
126
- | Autonomous delivery | `plastic-auto` | agent architecture |
127
- | Save/restore state | `plastic-intent-savepoint`, `plastic-intent-continuing` | context management |
128
- | Knowledge graph, linking | `plastic-intent-linking` | zettelkasten, wikilinks |
129
- | Projects, hubs | `plastic-project-creating` | hubs, project stores |
130
- | Provision a project store | `plastic-store-provisioning` | project stores |
131
- | Index maintenance | `plastic-store-indexing` | — |
132
- | Releases, deprecations | `plastic-releasing` | deprecation process |
133
- | 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 |
134
- | Report a Plastic quirk, bug, or feature idea | `plastic-feedback` | transport and privacy (redaction checklist, why a prefilled URL) |
135
- | Authoring skills, agents, hooks | `plastic-skill-creating` | progressive disclosure, agentskills.io spec |
136
- | Evaluating skills, evals | `plastic-skill-evaluating` | eval methodology, convention checks |
137
- | Create, order, and consume a roadmap of intents | `plastic-roadmap` | file format, operations |
138
-
package/hooks/code-gate DELETED
@@ -1,27 +0,0 @@
1
- #!/bin/bash
2
- INPUT=$(cat)
3
- FILE_PATH=$(echo "$INPUT" | ruby -rjson -e '
4
- data = JSON.parse(STDIN.read)
5
- ti = data["tool_input"] || data["tool_params"] || {}
6
- path = ti["file_path"] || ti["notebook_path"] || ti["relative_path"] || ""
7
- if !path.empty? && !path.start_with?("/")
8
- root = ti["project_root"] || data["cwd"] || ""
9
- path = File.join(root, path) unless root.empty?
10
- end
11
- puts path
12
- ' 2>/dev/null)
13
-
14
- if [ -z "$FILE_PATH" ]; then
15
- exit 0
16
- fi
17
-
18
- SESSION_ID=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("session_id") || ""' 2>/dev/null)
19
-
20
- NEW_CONTENT=$(echo "$INPUT" | ruby -rjson -e '
21
- data = JSON.parse(STDIN.read)
22
- ti = data["tool_input"] || data["tool_params"] || {}
23
- print ti["content"] || ti["new_string"] || ""
24
- ' 2>/dev/null)
25
-
26
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
27
- ruby "$SCRIPT_DIR/../scripts/hook-code-gate" "$FILE_PATH" "$SESSION_ID" "$NEW_CONTENT"
package/hooks/create-gate DELETED
@@ -1,3 +0,0 @@
1
- #!/bin/bash
2
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
3
- exec ruby "$SCRIPT_DIR/../scripts/hook-create-gate"
package/hooks/lock-gate DELETED
@@ -1,21 +0,0 @@
1
- #!/bin/bash
2
- INPUT=$(cat)
3
- FILE_PATH=$(echo "$INPUT" | ruby -rjson -e '
4
- data = JSON.parse(STDIN.read)
5
- ti = data["tool_input"] || data["tool_params"] || {}
6
- path = ti["file_path"] || ti["notebook_path"] || ti["relative_path"] || ""
7
- if !path.empty? && !path.start_with?("/")
8
- root = ti["project_root"] || data["cwd"] || ""
9
- path = File.join(root, path) unless root.empty?
10
- end
11
- puts path
12
- ' 2>/dev/null)
13
-
14
- if [ -z "$FILE_PATH" ]; then
15
- exit 0
16
- fi
17
-
18
- SESSION_ID=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("session_id") || ""' 2>/dev/null)
19
-
20
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
21
- ruby "$SCRIPT_DIR/../scripts/hook-lock-gate" "$FILE_PATH" "$SESSION_ID"
package/hooks/qmd-search DELETED
@@ -1,8 +0,0 @@
1
- #!/bin/bash
2
- # qmd-first search hook launcher (intent 66). No-op when there is no global store.
3
- GLOBAL_INDEX="$HOME/.plastic/INDEX.md"
4
- if [ ! -f "$GLOBAL_INDEX" ]; then
5
- exit 0
6
- fi
7
- SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
8
- exec ruby "$SCRIPT_DIR/../scripts/hook-qmd-search" "$HOME/.plastic"
@@ -1,10 +0,0 @@
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"
@@ -1,10 +0,0 @@
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"
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: UTF-8
3
- # frozen_string_literal: true
4
-
5
- # qmd-first UserPromptSubmit hook (intent 66). Reads the prompt from stdin JSON,
6
- # delegates the decision to QmdHook.run under a hard timeout, and prints
7
- # hookSpecificOutput when there is something to inject/remind. Silent no-op (exit
8
- # 0) when qmd is absent, the prompt is trivial, or anything goes wrong. ARGV[0]
9
- # is the plastic_home (passed by the launcher); defaults to ~/.plastic.
10
- require "json"
11
- require "timeout"
12
- require_relative "lib/qmd_hook"
13
-
14
- raw = begin
15
- STDIN.read
16
- rescue StandardError
17
- ""
18
- end
19
-
20
- prompt = begin
21
- parsed = JSON.parse(raw)
22
- parsed.is_a?(Hash) ? parsed["user_prompt"].to_s : ""
23
- rescue StandardError
24
- ""
25
- end
26
-
27
- plastic_home = (ARGV[0] && !ARGV[0].empty?) ? ARGV[0] : File.expand_path("~/.plastic")
28
-
29
- context = begin
30
- Timeout.timeout(2) do
31
- QmdHook.run(prompt: prompt, cwd: Dir.pwd, plastic_home: plastic_home)
32
- end
33
- rescue Exception
34
- nil
35
- end
36
-
37
- exit 0 if context.nil? || context.strip.empty?
38
-
39
- puts JSON.generate(
40
- "hookSpecificOutput" => {
41
- "hookEventName" => "UserPromptSubmit",
42
- "additionalContext" => context,
43
- }
44
- )
@@ -1,148 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: UTF-8
3
- # frozen_string_literal: true
4
-
5
- # PreToolUse retrieval gate (intent 84, Lever 2; operation-based redesign 89a;
6
- # ADVISORY since intent 108, D8). Reads the tool call (JSON on stdin: tool_name +
7
- # tool_input), computes capabilities (QMD detect + freshness), and delegates the
8
- # decision to RetrievalGate. It NEVER blocks: reads and searches always run
9
- # (exit 0). A store content search that QMD could serve better gets a QMD hint
10
- # via PreToolUse additionalContext on stdout. Hard gates guard writes, locks,
11
- # and structure; never reads.
12
- # Fail-open: any parse error, timeout, or unexpected exception exits 0. On the
13
- # STALE QMD path RetrievalGate fires QmdSync.reindex_async (NEVER synchronous).
14
- # Binds subagents (PreToolUse hooks apply to subagent tool calls too).
15
- #
16
- # Only CONTENT SEARCH over store markdown gets the hint; reads and structural
17
- # ops stay silent. Code navigation is a soft prompt recommendation
18
- # (UserPromptSubmit power-tools), not handled here.
19
- #
20
- # Scope: only the agent's own Bash/Read/Grep/Glob calls. Ruby `File.read` inside
21
- # scripts is invisible to a PreToolUse hook and is out of scope (no exemptions).
22
- #
23
- # ARGV[0] is plastic_home (passed by the launcher), like hook-qmd-search.
24
-
25
- require "json"
26
- require "timeout"
27
- require_relative "lib/retrieval_gate"
28
- require_relative "lib/qmd_sync"
29
-
30
- module RetrievalGateHook
31
- module_function
32
-
33
- # Pure-ish core: capabilities and reindex are injected so this is unit-testable
34
- # with no real qmd. Returns [exit_code, stderr_string, stdout_json].
35
- # stdin: raw PreToolUse JSON
36
- # capabilities: { qmd:, qmd_fresh: }
37
- # reindex: callable fired on the STALE path
38
- def run(stdin:, plastic_home:, cwd:, capabilities:, reindex: -> {})
39
- payload = parse(stdin)
40
- return [0, nil, nil] unless payload
41
-
42
- tool_name = payload["tool_name"].to_s
43
- tool_input = payload["tool_input"]
44
- tool_input = {} unless tool_input.is_a?(Hash)
45
-
46
- bypassed = false
47
- reason = RetrievalGate.decision(
48
- tool_name: tool_name, tool_input: tool_input,
49
- plastic_home: plastic_home, cwd: cwd,
50
- capabilities: capabilities, reindex: reindex
51
- ) { |_sig| bypassed = true }
52
-
53
- if reason
54
- # Advisory (intent 108, D8): reads and searches are never denied. The
55
- # would-block reason becomes a hint the agent sees alongside the result.
56
- hint = JSON.generate(
57
- "hookSpecificOutput" => {
58
- "hookEventName" => "PreToolUse",
59
- "additionalContext" =>
60
- "PLASTIC advisory: #{reason} (this search ran; the hint is not a block)"
61
- }
62
- )
63
- [0, nil, hint]
64
- else
65
- # The `# qmd-ok` bypass token is still accepted (bypassed is set), but
66
- # with nothing to bypass it no longer announces itself.
67
- [0, nil, nil]
68
- end
69
- rescue StandardError
70
- [0, nil, nil] # fail-open
71
- end
72
-
73
- def parse(raw)
74
- data = JSON.parse(raw.to_s)
75
- data.is_a?(Hash) ? data : nil
76
- rescue StandardError
77
- nil
78
- end
79
-
80
- # Detect real capabilities for the live executable. Probes are injected so the
81
- # three-tier QMD failure model is unit-testable:
82
- # - absent : QMD not on PATH -> allow, no warn.
83
- # - broken : QMD present but freshness probe -> tier-b: WARN once, then treat
84
- # raises/times out as absent this turn (allow).
85
- # - fresh : probe returns truthy -> gate is live.
86
- # A slow `qmd status` cannot stall a tool call: a 2s Timeout bounds the probe.
87
- def detect_capabilities(cwd:,
88
- detect: -> { QmdSync.detect },
89
- fresh: -> { QmdSync.fresh? },
90
- warn: ->(m) { $stderr.puts(m) })
91
- qmd = detect.call
92
- qmd_fresh = false
93
- if qmd
94
- begin
95
- qmd_fresh = Timeout.timeout(2) { fresh.call }
96
- rescue StandardError
97
- # Tier-b: QMD is present but its freshness probe broke/stalled. Distinct
98
- # from QMD being absent — warn so a degraded QMD is visible, then fail open
99
- # (allow this turn, no reindex).
100
- warn.call("PLASTIC GATE — QMD is present but its freshness probe failed; " \
101
- "allowing this turn without routing search to QMD (check qmd).")
102
- qmd = false
103
- qmd_fresh = false
104
- end
105
- end
106
- { qmd: qmd, qmd_fresh: qmd_fresh }
107
- end
108
-
109
- # Best-effort reindex callable for the STALE path. Resolves the collection from
110
- # cwd (project + global) and fires the async reindexer for each; never raises.
111
- def reindex_for(cwd:, plastic_home:)
112
- lambda do
113
- begin
114
- cols = QmdSync.collections_for_cwd(cwd, plastic_home: plastic_home)
115
- cols.each { |c| QmdSync.reindex_async(collection: c) }
116
- rescue StandardError
117
- # non-fatal; the read is already allowed this turn
118
- end
119
- end
120
- end
121
- end
122
-
123
- # --- executable entrypoint ---
124
- if $PROGRAM_NAME == __FILE__
125
- raw = begin
126
- $stdin.read
127
- rescue StandardError
128
- ""
129
- end
130
-
131
- plastic_home = (ARGV[0] && !ARGV[0].empty?) ? ARGV[0] : File.expand_path("~/.plastic")
132
- cwd = Dir.pwd
133
-
134
- code, err, out = begin
135
- caps = RetrievalGateHook.detect_capabilities(cwd: cwd)
136
- RetrievalGateHook.run(
137
- stdin: raw, plastic_home: plastic_home, cwd: cwd,
138
- capabilities: caps,
139
- reindex: RetrievalGateHook.reindex_for(cwd: cwd, plastic_home: plastic_home)
140
- )
141
- rescue StandardError
142
- [0, nil, nil] # fail-open at the outermost boundary too
143
- end
144
-
145
- $stderr.puts(err) if err && !err.empty?
146
- print out if out && !out.empty?
147
- exit code
148
- end