@zalom/plastic 1.0.0-beta.1 → 1.0.0-beta.10

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 (50) hide show
  1. package/PLASTIC.md +21 -5
  2. package/agents/plastic-brainstorming.md +9 -1
  3. package/agents/plastic-executor.md +10 -0
  4. package/agents/plastic-intent-curator.md +7 -5
  5. package/agents/plastic-planner.md +11 -1
  6. package/agents/plastic-spec-specialist.md +9 -1
  7. package/hooks/statusline +150 -41
  8. package/package.json +1 -1
  9. package/scripts/agent-report +142 -0
  10. package/scripts/dashboard.rb +5 -3
  11. package/scripts/doctor.rb +243 -0
  12. package/scripts/lib/bridge.rb +72 -24
  13. package/scripts/lib/frontmatter_writer.rb +130 -0
  14. package/scripts/lib/graph_rebuild.rb +328 -0
  15. package/scripts/lib/installer_core.rb +5 -0
  16. package/scripts/lib/intent_validator.rb +79 -0
  17. package/scripts/lib/links_projection.rb +160 -0
  18. package/scripts/lib/links_section.rb +207 -0
  19. package/scripts/lib/power_tools.rb +76 -0
  20. package/scripts/lib/qmd_hook.rb +38 -25
  21. package/scripts/lib/qmd_sync.rb +21 -0
  22. package/scripts/new-intent +172 -22
  23. package/scripts/project-links +287 -0
  24. package/scripts/qmd-sync +50 -3
  25. package/scripts/rebuild-graph +244 -0
  26. package/scripts/spawn-preamble +18 -1
  27. package/skills/auto/SKILL.md +13 -3
  28. package/skills/auto/evals/evals.json +48 -0
  29. package/skills/auto/references/agent-architecture.md +20 -0
  30. package/skills/auto/references/agent-report-contract.md +86 -0
  31. package/skills/brainstorming/SKILL.md +1 -0
  32. package/skills/brainstorming/evals/evals.json +22 -0
  33. package/skills/continuing/SKILL.md +8 -1
  34. package/skills/continuing/evals/evals.json +9 -0
  35. package/skills/creating-intent/SKILL.md +28 -8
  36. package/skills/creating-intent/evals/evals.json +72 -0
  37. package/skills/creating-intent/references/lifecycle.md +12 -4
  38. package/skills/dashboard/SKILL.md +5 -0
  39. package/skills/dashboard/evals/evals.json +22 -0
  40. package/skills/executing-plan/SKILL.md +2 -2
  41. package/skills/intent-curator/SKILL.md +3 -1
  42. package/skills/intent-curator/evals/evals.json +22 -0
  43. package/skills/linking-intents/SKILL.md +17 -6
  44. package/skills/linking-intents/evals/evals.json +22 -0
  45. package/skills/linking-intents/references/zettelkasten.md +15 -3
  46. package/skills/managing-index/SKILL.md +6 -0
  47. package/skills/managing-index/evals/evals.json +22 -0
  48. package/skills/managing-index/references/zettelkasten-linking.md +7 -2
  49. package/skills/research/SKILL.md +8 -0
  50. package/skills/research/evals/evals.json +22 -0
@@ -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.
@@ -0,0 +1,86 @@
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
+ ## Common envelope
21
+
22
+ Every role report, whatever the stage, carries these fields:
23
+
24
+ - **Role**: which specialist produced this (brainstorming, spec, planner, executor, reviewer).
25
+ - **Intent id and stage**: the active intent id and the cycle stage just completed.
26
+ - **Status**: `delivered` or `blocked`.
27
+ - **Artifacts written**: the files produced or changed (store paths, and project paths for the
28
+ executor).
29
+ - **Verification / tests run**: the command run and its result, or `n/a` for stages that write
30
+ no code.
31
+ - **Checklist deltas**: which checklist items this turn checked off (executor), or `n/a`.
32
+ - **Deviations from spec**: anything done differently from the spec or plan, and why, or `none`.
33
+ - **Blockers / handoff notes**: what the next stage must watch for, or `none`.
34
+
35
+ ## Per-role payload
36
+
37
+ Each role appends a payload that fulfils its place in the What, Why, How, Exec cycle (decision
38
+ D2). The payload is what makes the report useful to the orchestrator beyond the envelope.
39
+
40
+ ### brainstorming (Why exploration)
41
+ - Decisions recorded in `### Decisions`, each with its one-line rationale.
42
+ - Context enriched: what was researched and the key findings.
43
+ - Open questions resolved, and any deliberately left for the spec.
44
+
45
+ ### spec-specialist (Why to How boundary)
46
+ - Spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria).
47
+ - How the recorded decisions resolved into the chosen approach.
48
+ - Acceptance-criteria count, so the planner knows the surface to cover.
49
+
50
+ ### planner (How): worked exemplar
51
+ The planner report EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR. It carries:
52
+ - The ordered actions, one line each: what the action does and how it is verified.
53
+ - Decomposition rationale: why this order, and why the actions are independent.
54
+ - Checklist coverage: item count and that every action plus suite-green is covered.
55
+ This is the exemplar because the plan is an argument, and the orchestrator gates on whether that
56
+ argument is sound before any code is written.
57
+
58
+ ### executor (Exec)
59
+ - Actions implemented this turn, mapped to checklist items checked off (checked / total).
60
+ - A summary of the code changed (files and the shape of the change).
61
+ - Test result: the full-suite command and its pass / fail counts.
62
+ - Insights appended, with the `(autonomous)` marker.
63
+
64
+ ### final reviewer (final gate)
65
+ - Verdict: `pass` or `blockers found`.
66
+ - Each acceptance criterion checked, with the evidence that confirms or refutes it.
67
+ - Gaps or risks found, ranked, with a recommended disposition.
68
+
69
+ ## Fallback: always a report
70
+
71
+ Decision-shaping (the preamble plus these prompts) makes the report mandatory, but child-agent
72
+ honor is best-effort across harnesses (Tier B/C in `docs/reference/harness-adapters.md`), so the
73
+ contract is never a hard block (decision D3). When a dispatched agent returns no usable report
74
+ (it went idle, emitted only a bare ping, or its message was lost to a mid-run interjection), the
75
+ enforcer synthesizes one:
76
+
77
+ ```
78
+ scripts/agent-report <intent_dir> --role <role>
79
+ ```
80
+
81
+ `scripts/agent-report` is a pure function of the intent directory (no network, clock, or
82
+ randomness, mirroring `scripts/spawn-preamble`): it reads the current stage from the savepoint
83
+ ledger, the lifecycle artifacts present, the checklist checked / total, and the `## Outcome`
84
+ line, and emits a filesystem-derived report labelled `synthesized`. So a handoff account always
85
+ exists: authored by the agent when possible, reconstructed deterministically when not. This
86
+ formalizes the by-hand reconstruction the orchestrator did while delivering intent 68.
@@ -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
+ }
@@ -54,7 +54,14 @@ intents. Do not start executing work. The branches below are the only follow-ups
54
54
  ## Conditional Ledger-Resume
55
55
 
56
56
  Fires ONLY when the user explicitly asks to continue a SPECIFIC intent, or an agent is
57
- instructed to continue one. It is not part of every boot. For that intent's directory:
57
+ instructed to continue one. It is not part of every boot.
58
+
59
+ QMD-first (when available): when the user names the intent by description rather than id, before
60
+ scanning the store with grep/Read run `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to
61
+ surface the candidate intent, then open the authoritative intent file for the hit you resume. The
62
+ command is a no-op when QMD is absent, so fall back to the existing INDEX.md / file scan.
63
+
64
+ For that intent's directory:
58
65
 
59
66
  1. **Read `savepoint.md`.** It is a deterministic, append-only stage ledger (one line per
60
67
  milestone, newest at the bottom): `{utc-iso8601} {Stage} {milestone}`. The **last line =
@@ -131,6 +131,15 @@
131
131
  "assertions": [
132
132
  { "type": "convention", "check": "Determine Store step 2 documents local/project store detection via projects.yml + CWD match", "observed": "filled", "result": "pass" }
133
133
  ]
134
+ },
135
+ {
136
+ "id": 15, "scope": "behavior", "set": "validation",
137
+ "prompt": "QMD is present. The user says: continue the work on the statusline coloring (names the intent by description, not id).",
138
+ "expected_output": "Before scanning the store with grep/Read to locate the named intent, runs `ruby ~/.plastic/scripts/qmd-sync search \"statusline coloring\"` to surface the candidate intent, then opens the authoritative intent file for the hit it resumes. No-op fallback to INDEX.md / file scan when QMD is absent.",
139
+ "files": ["skills/continuing/SKILL.md"],
140
+ "assertions": [
141
+ { "type": "human", "check": "qmd-sync search is run before grep/Read when the intent is named by description; authoritative file opened for the hit", "observed": "Conditional Ledger-Resume carries the QMD-first step: run qmd-sync search before grep/Read, then open the authoritative file; no-op fallback when QMD is absent", "result": "pass" }
142
+ ]
134
143
  }
135
144
  ]
136
145
  }
@@ -30,7 +30,7 @@ description: Use when new work begins, the user expresses a new goal, says "new
30
30
 
31
31
  When creating a tactical intent in a project store:
32
32
  - Read the project's `AGENTS.md` for project context and decisions
33
- - Link back to the project's governing intent (from `projects.yml` `parent` field) via `sources`
33
+ - Link back to the project's governing intent (from `projects.yml` `parent` field) via `sources` (the project genuinely is formed from its founding intent, a true formative edge, reciprocated on the founding intent's `chain`)
34
34
  - Add `[[global:<parent_ID>]]` backlink in `## Links`
35
35
  - The intent's Folgezettel ID is scoped to the project store (run `folgezettel-id` against the project's store at `~/.plastic/projects/{slug}/store/`)
36
36
 
@@ -41,6 +41,14 @@ When creating a tactical intent in a project store:
41
41
  - **Global:** `~/.plastic/store/`
42
42
  - **Project:** `~/.plastic/projects/{slug}/store/`
43
43
 
44
+ ### 1b. Check for a Duplicate or Predecessor (QMD-first)
45
+
46
+ QMD-first (when available): before scanning the store with grep/Read, run
47
+ `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate, prior, or duplicate
48
+ intents, then open the authoritative intent file for any hit you act on. The command is a no-op
49
+ when QMD is absent, so fall back to the existing INDEX.md / file scan. Do this before allocating
50
+ the id so a near-duplicate can be reused and a true predecessor can be set in `--sources`.
51
+
44
52
  ### 2. Decide Branch vs Root
45
53
 
46
54
  Decide this BEFORE scaffolding, because it sets whether you pass `--parent`.
@@ -48,10 +56,17 @@ Having a "parent" in mind does NOT automatically mean branch. Choose by meaning:
48
56
 
49
57
  - **Branch (`14a`, `14b`)**: a sub-task, refinement, or direct continuation. It only
50
58
  makes sense as part of the parent's work. Pass `--parent <parent_id>`.
51
- - **Root (`15`, `16`)**: an independent thought, even if inspired by another intent.
52
- Capture the inspiration in `--sources`, not in the id. Omit `--parent`.
53
- - **Rule of thumb:** if the intent could exist without its parent, make it a root and
54
- set `--sources`. Only branch when it genuinely cannot stand alone.
59
+ - **Root (`15`, `16`)**: an independent thought. Two cases, decided by ORIGIN:
60
+ - **Created from another intent** (it emerged from that intent's lifecycle): make it a
61
+ root and set `--sources <ascendant_id>`. `sources` is reserved for true created-from /
62
+ direct-ascendant provenance (D1).
63
+ - **Merely related to / inspired by another intent** (it did NOT come out of that
64
+ intent's lifecycle): carry NO `--sources`. Record the relation on the PREDECESSOR's
65
+ `chain` instead, and mirror it as a `[[id]]` wikilink in `## Links` (the
66
+ related-but-not-spawned rule).
67
+ - **Rule of thumb:** if the intent could exist without its parent, make it a root; only set
68
+ `--sources` when it was genuinely created from / emerged from that intent's lifecycle.
69
+ Topic similarity alone is not a `sources` edge.
55
70
 
56
71
  ### 3. Determine Intent Properties
57
72
 
@@ -59,11 +74,16 @@ Ask or infer from context:
59
74
  - **intent**: one-line description
60
75
  - **slug**: short hyphenated handle for the directory name
61
76
  - **author**: `human` | `claude-code` | other agent name
62
- - **sources**: Folgezettel ids that influenced this intent (e.g., `4a1`). For a
63
- project intent, include the governing intent's id.
77
+ - **sources**: the direct ascendant(s) this intent was created from / emerged from the
78
+ lifecycle of (formation, not topic similarity), e.g., `4a1`. For a project intent,
79
+ include the governing intent's id. A branch's structural parent is ALSO recorded in
80
+ `sources` (the ID carries it for the human/paper tree, `sources` carries it for
81
+ software), which `new-intent` does automatically (see `new-intent:126`).
64
82
  - **tags**: freeform list (use `project-<name>` for project membership)
65
83
 
66
- `chain` starts empty and is populated later when this intent spawns others.
84
+ `chain` carries what this intent spawns AND related-but-not-spawned successors it leads to;
85
+ it starts empty and is populated later. See
86
+ `docs/concepts/how-plastic-sources-and-chains-intents.md` for the full model.
67
87
  Place the intent in `## Active` or `## Future` in INDEX.md (status is
68
88
  convention-derived, not a frontmatter field).
69
89
 
@@ -0,0 +1,72 @@
1
+ {
2
+ "skill_name": "plastic-creating-intent",
3
+ "notes": "Intent 68. Scope: output-quality for the sources-vs-chain construction rules (D1/D2). Asserts the related-but-not-spawned case produces NO sources plus a predecessor chain link and a ## Links mirror, contrasted with the created-from case (true ascendant -> --sources set, reciprocal chain). The machine-checkable half lives in test/new_intent_test.rb (test_sources_path_gets_child_in_chain_frontmatter); this file documents the agent-facing scenario for skill evaluation and is NOT run by bin/test.",
4
+ "evals": [
5
+ {
6
+ "id": 1,
7
+ "scope": "behavior",
8
+ "set": "train",
9
+ "prompt": "Create an intent for adding a retry policy to the uploader. It's related to intent 41 (the upload pipeline work) but it's independent: it did not come out of intent 41's lifecycle.",
10
+ "expected_output": "A new root intent is created with EMPTY sources (it was not created from 41). The relation is recorded on the PREDECESSOR: intent 41 gains the new intent's id in its frontmatter chain, and a [[<new-id>]] wikilink is added to intent 41's ## Links. The new intent is NOT given 41 in --sources (the related-but-not-spawned rule). No false symmetry: 41 keeps the new id on chain with no reciprocal sources.",
11
+ "files": [],
12
+ "assertions": [
13
+ {
14
+ "type": "code",
15
+ "check": "new intent sources is empty",
16
+ "observed": "sources: []",
17
+ "result": "pass"
18
+ },
19
+ {
20
+ "type": "code",
21
+ "check": "predecessor 41 chain includes new id",
22
+ "observed": "41.chain includes <new-id>",
23
+ "result": "pass"
24
+ },
25
+ {
26
+ "type": "code",
27
+ "check": "predecessor 41 ## Links has [[<new-id>]] mirror",
28
+ "observed": "[[<new-id>]] present in 41 ## Links",
29
+ "result": "pass"
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "id": 2,
35
+ "scope": "behavior",
36
+ "set": "validation",
37
+ "prompt": "Create an intent that is the direct continuation of intent 41: it emerged from intent 41's lifecycle and could not exist without it.",
38
+ "expected_output": "Because the new intent was genuinely CREATED FROM 41 (D1), it carries 41 in --sources (or branches from 41, which folds 41 into sources via the redundant-explicit rule). The reciprocal I1 backlink lands: intent 41's frontmatter chain gains the new intent's id. This is the created-from case, contrasted with the related-but-not-spawned case in eval 1.",
39
+ "files": [],
40
+ "assertions": [
41
+ {
42
+ "type": "code",
43
+ "check": "new intent sources includes 41",
44
+ "observed": "sources includes 41",
45
+ "result": "pass"
46
+ },
47
+ {
48
+ "type": "code",
49
+ "check": "predecessor 41 chain includes new id (I1 reciprocity)",
50
+ "observed": "41.chain includes <new-id>",
51
+ "result": "pass"
52
+ }
53
+ ]
54
+ },
55
+ {
56
+ "id": 3,
57
+ "scope": "behavior",
58
+ "set": "validation",
59
+ "prompt": "QMD is present. The user says: create an intent to add retry to the uploader.",
60
+ "expected_output": "Before allocating the id / scaffolding, runs `ruby ~/.plastic/scripts/qmd-sync search \"add retry to the uploader\"` to surface a near-duplicate or true predecessor, then opens the authoritative intent file for any hit (reusing a near-duplicate or setting a real predecessor in --sources). No-op fallback to INDEX.md / file scan when QMD is absent.",
61
+ "files": [],
62
+ "assertions": [
63
+ {
64
+ "type": "human",
65
+ "check": "qmd-sync search is run before id allocation; authoritative file opened for any hit; informs reuse / --sources",
66
+ "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",
67
+ "result": "pass"
68
+ }
69
+ ]
70
+ }
71
+ ]
72
+ }
@@ -36,13 +36,21 @@ Never modified, only appended.
36
36
 
37
37
  Tracks: stage transitions, decisions, shifts, blocks, cancellations, material for future intents.
38
38
  This is how execution is tracked. When this intent completes, Insights
39
- is where to look for what comes next. New intents spawned from Insights
40
- appear in the `chain` field.
39
+ is where to look for what comes next. New intents spawned from this one,
40
+ plus related-but-not-spawned successors it leads to, appear in the `chain`
41
+ field.
41
42
 
42
43
  ## `## Links`
43
44
 
44
- Wikilinks for Obsidian graph navigation. Human-facing counterpart to the
45
- frontmatter knowledge graph.
45
+ The human-readable projection of the local knowledge graph, mirroring the
46
+ frontmatter exactly. Each entry is `- [[id--slug|<target's full intent: text>]]`,
47
+ a clickable `id--slug` wikilink target with the target intent's full `intent:`
48
+ text as the label (cross-store targets render
49
+ `- [[store:id--slug|<target's full intent: text>]]`). Ordering is mandatory: all
50
+ `sources` first (top), then all `chain`, frontmatter order preserved within each
51
+ group. Sources never appear at the end. No source/chain tags, no sub-grouping. An
52
+ intent with empty `sources` and `chain` carries the empty-state comment. Counterpart
53
+ to the frontmatter `sources` / `chain` edges, for Obsidian graph navigation.
46
54
 
47
55
  ## Conventions — Filesystem as Schema
48
56
 
@@ -67,6 +67,11 @@ instead of showing it.
67
67
 
68
68
  ### Step 4 — Entry flow (the board is the menu)
69
69
 
70
+ QMD-first (when available): when the user navigates by describing an intent rather than giving its
71
+ id, before scanning the store with grep/Read run `ruby ~/.plastic/scripts/qmd-sync search "<terms>"`
72
+ to surface the candidate intent, then open the authoritative intent file for the hit. The command is
73
+ a no-op when QMD is absent, so fall back to the existing INDEX.md / file scan.
74
+
70
75
  The board lists everything; the user navigates by free prose (no capped picker):
71
76
  - On the **global** board, the user replies with an **intent id** (work it), a **project
72
77
  name** (re-run `project <slug> --data` and present that board), or **"new"** (start a new
@@ -0,0 +1,22 @@
1
+ {
2
+ "skill_name": "plastic-dashboard",
3
+ "notes": "Intent 66a. Spec for the QMD-first step in the entry flow (resolve a free-prose intent reference 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. On the board, the user navigates by describing an intent (the search verb work) instead of giving its id.",
10
+ "expected_output": "Before scanning the store with grep/Read to resolve the free-prose reference, runs `ruby ~/.plastic/scripts/qmd-sync search \"search verb\"` to surface the candidate intent, then opens the authoritative intent file for the hit. 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 before grep/Read when the user navigates by description; authoritative file opened for the 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
+ }
@@ -75,7 +75,7 @@ Capture observations in `## Insights`. When ALL checklist items are checked:
75
75
  3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
76
76
  4. Update cluster entries to show `_(completed)_`
77
77
  5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
78
- 6. Refresh the QMD search index for this store (optional, no-op when QMD is absent): `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root>`. Delivery is the lifecycle event that keeps the search index fresh.
78
+ 6. On completion, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh.
79
79
 
80
80
  **This is NOT optional.** An intent with all checklist items done but no Outcome is a broken state. Complete the intent immediately — do not leave it for later.
81
81
 
@@ -101,7 +101,7 @@ Capture observations in `## Insights`. When ALL checklist items are checked:
101
101
  3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
102
102
  4. Update cluster entries to show `_(completed)_`
103
103
  5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
104
- 6. Refresh the QMD search index for this store (optional, no-op when QMD is absent): `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root>`. Delivery is the lifecycle event that keeps the search index fresh.
104
+ 6. On completion, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh.
105
105
 
106
106
  **This is NOT optional.** Complete the intent immediately when work is done.
107
107
 
@@ -33,9 +33,11 @@ Invoke the `plastic-intent-curator` agent via the Agent tool with `subagent_type
33
33
 
34
34
  The agent handles:
35
35
  - Intent lifecycle management (status transitions, Outcome sections)
36
- - INDEX.md maintenance (Active/Future/Clusters/Completed sections)
36
+ - INDEX.md maintenance (Active/Future/Clusters/Completed/Abandoned sections)
37
37
  - Link discovery between related intents
38
38
  - Cluster management (create, merge, rename)
39
39
  - Orphan detection
40
40
 
41
+ When an intent reaches a terminal state — moved to Completed OR Abandoned — refresh the QMD index for the affected store (no-op when QMD absent), running in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`.
42
+
41
43
  After the agent completes, report what changed.
@@ -0,0 +1,22 @@
1
+ {
2
+ "skill_name": "plastic-intent-curator",
3
+ "notes": "Intent 66a. Spec for the QMD-first discovery step in agents/plastic-intent-curator.md (locate a specific intent before grep/find; distinct from the completion-time reindex step). Runner is intent 76; spec only.",
4
+ "evals": [
5
+ {
6
+ "id": 1,
7
+ "scope": "behavior",
8
+ "set": "validation",
9
+ "prompt": "QMD is present. The user says: find and reclassify the intent about orphan detection.",
10
+ "expected_output": "During discovery (How You Work), before scanning the store with grep/find to locate the intent, runs `ruby ~/.plastic/scripts/qmd-sync search \"orphan detection\"` to surface the candidate or related intents, then opens the authoritative intent file for the hit it acts on. This discovery step is distinct from the completion-time reindex step. No-op fallback to the full scan when QMD is absent.",
11
+ "files": [],
12
+ "assertions": [
13
+ {
14
+ "type": "human",
15
+ "check": "qmd-sync search is run before grep/find during discovery; authoritative file opened for the hit; reindex step stays separate",
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
+ }
@@ -21,13 +21,13 @@ Explicit wikilinks in the `## Links` section. Bidirectional — add to both inte
21
21
  ```
22
22
 
23
23
  ### 2. Sources (Backward)
24
- The `sources` array in frontmatter. What influenced this intent — backward links to parent/prior work:
24
+ The `sources` array in frontmatter. The direct ascendant(s) this intent was created from / emerged from the lifecycle of (formation, not topic similarity), backward links to the work it was built out of:
25
25
  ```yaml
26
26
  sources: ["1a", "1a2"]
27
27
  ```
28
28
 
29
29
  ### 3. Chain (Forward)
30
- The `chain` array in frontmatter. What this intent spawned — forward links to children/follow-on work:
30
+ The `chain` array in frontmatter. What this intent spawned AND related-but-not-spawned successors it leads to, forward links to children, follow-on, and related work:
31
31
  ```yaml
32
32
  chain: ["1b1", "1b2"]
33
33
  ```
@@ -41,6 +41,12 @@ tags: [plastic, project-reddit-kb]
41
41
  ## Workflow
42
42
 
43
43
  ### 1. Identify Intents to Connect
44
+
45
+ QMD-first (when available): before scanning the store with grep/Read, run
46
+ `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate, prior, or related
47
+ intents to propose as sources/chain, then open the authoritative intent file for any hit you act
48
+ on. The command is a no-op when QMD is absent, so fall back to the directory scan below.
49
+
44
50
  Show existing intents by scanning the store's directory for intent files:
45
51
  ```bash
46
52
  for dir in $STORE_ROOT/store/*/; do
@@ -55,16 +61,21 @@ done
55
61
 
56
62
  ### 2. Choose Connection Type
57
63
  Ask the user which type of connection:
58
- - **source** — "this was influenced by that" (add to `sources[]`, update `chain[]` on the target)
59
- - **cross-reference** — "these are related" (add wikilink in `## Links` of both intents)
64
+ - **source**: "this was CREATED FROM that" (D1). The reciprocal update is one-directional (I1): add the ascendant id to this intent's `sources[]` AND add this intent's id to the ascendant's `chain[]`. A merely-related (not-created-from) connection is NOT a source: record it on the predecessor's `chain[]` only, plus a `## Links` wikilink, with NO `sources` (the related-but-not-spawned rule).
65
+ - **cross-reference**: "these are related" (add wikilink in `## Links` of both intents)
60
66
 
61
67
  ### 3. Apply Connection
62
68
 
63
- **For sources:**
64
- Update frontmatter arrays on both intents:
69
+ **For sources (a true created-from edge only):**
70
+ Update frontmatter arrays on both intents (I1, two-sided):
65
71
  - Add the parent's ID to the child's `sources` array
66
72
  - Add the child's ID to the parent's `chain` array
67
73
 
74
+ For the merely-related case, only the predecessor's `chain` (and both sides' `## Links`)
75
+ get the link, never `sources`. `chain` is NOT strictly the reverse of `sources` (I2):
76
+ relational `chain` entries are valid and must never be "corrected" by adding a reciprocal
77
+ `sources`.
78
+
68
79
  **For cross-references:**
69
80
  Add a wikilink in the `## Links` section of **both** intents (bidirectional).
70
81
 
@@ -0,0 +1,22 @@
1
+ {
2
+ "skill_name": "plastic-linking-intents",
3
+ "notes": "Intent 66a. Spec for the QMD-first discovery step (surface candidate/related intents to propose as sources/chain before grep/Read). Runner is intent 76; this case is a spec, not executed.",
4
+ "evals": [
5
+ {
6
+ "id": 1,
7
+ "scope": "behavior",
8
+ "set": "validation",
9
+ "prompt": "QMD is present. The user says: link this intent to the related upload-pipeline work.",
10
+ "expected_output": "Before scanning the store directory with grep/Read to identify related intents, runs `ruby ~/.plastic/scripts/qmd-sync search \"upload pipeline\"` to surface candidate or related intents to propose as sources/chain, then opens the authoritative intent file for any hit it links. No-op fallback to the directory scan when QMD is absent.",
11
+ "files": [],
12
+ "assertions": [
13
+ {
14
+ "type": "human",
15
+ "check": "qmd-sync search is run before the directory scan; authoritative file opened for any hit before proposing sources/chain",
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
+ }
@@ -10,6 +10,13 @@ Plastic implements three Zettelkasten structures:
10
10
 
11
11
  INDEX.md is a structure note (hub), not a table of contents.
12
12
 
13
+ `## Links` mirrors the frontmatter graph exactly. Each entry is
14
+ `- [[id--slug|<target's full intent: text>]]` (cross-store: `- [[store:id--slug|...]]`),
15
+ a clickable `id--slug` target with the target's full `intent:` text as the label.
16
+ Ordering is mandatory: all `sources` first (top), then all `chain`, frontmatter order
17
+ preserved within each group. Sources never appear at the end. No source/chain tags, no
18
+ sub-grouping. An intent with empty `sources` and `chain` carries the empty-state comment.
19
+
13
20
  ## Folgezettel IDs
14
21
 
15
22
  IDs encode lineage using Luhmann's alternating convention:
@@ -20,9 +27,14 @@ IDs encode lineage using Luhmann's alternating convention:
20
27
 
21
28
  ## Knowledge Graph
22
29
 
23
- `sources` + `chain` form the double-linked knowledge graph:
24
- - `sources` = what fed into this intent (parents, inspirations, prerequisites)
25
- - `chain` = what this intent produced (children, follow-ups, spin-offs)
30
+ `sources` and `chain` form the directed knowledge graph:
31
+ - `sources` = the direct ascendant(s) this intent was created from / emerged from the
32
+ lifecycle of (formation, not topic similarity); a DAG (acyclic), strong must-load context.
33
+ - `chain` = forward continuations AND related-but-not-spawned successors it leads to; a
34
+ directed graph that may cycle, lighter contributory context.
35
+ - Reciprocity is one-directional: every `sources` edge has a reciprocal `chain` entry (I1),
36
+ but `chain` may carry relational entries with no reciprocal `sources` (I2), so the graph is
37
+ NOT strictly double-linked.
26
38
 
27
39
  ## Dual-Mode
28
40
 
@@ -43,6 +43,12 @@ All completed intents with dates. Links preserved, never deleted.
43
43
 
44
44
  ## Workflow
45
45
 
46
+ QMD-first (when available): when you need to locate a specific intent (to reclassify, flag, or
47
+ cluster it) rather than rebuild every section, before scanning the store with grep/Read run
48
+ `ruby ~/.plastic/scripts/qmd-sync search "<terms>"` to surface candidate or related intents, then
49
+ open the authoritative intent file for any hit you act on. The command is a no-op when QMD is
50
+ absent, so fall back to the directory scan below.
51
+
46
52
  ### Rebuild Sections
47
53
  Scan the active store's `store/` directory for intent files and rebuild each section:
48
54