@zalom/plastic 1.1.0 → 1.1.1

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 (54) hide show
  1. package/PLASTIC.md +4 -2
  2. package/README.md +3 -3
  3. package/agents/plastic-enforcer.md +5 -2
  4. package/agents/plastic-future-intent-researcher.md +1 -1
  5. package/hooks/check-update +1 -1
  6. package/hooks/continue +1 -1
  7. package/package.json +1 -1
  8. package/scripts/dashboard.rb +29 -24
  9. package/scripts/doctor.rb +136 -5
  10. package/scripts/hook-continue +3 -3
  11. package/scripts/install.rb +2 -1
  12. package/scripts/lib/bridge.rb +81 -0
  13. package/scripts/lib/dashboard_banner.rb +8 -9
  14. package/scripts/lib/installer_core.rb +10 -3
  15. package/scripts/lib/legacy_bookend_amnesty.rb +35 -0
  16. package/scripts/lib/release_guard.rb +62 -0
  17. package/scripts/lib/roadmap_queue.rb +285 -0
  18. package/scripts/lib/roadmap_savepoint.rb +213 -0
  19. package/scripts/lib/worktree.rb +21 -0
  20. package/scripts/new-intent +1 -0
  21. package/scripts/read-config +3 -3
  22. package/scripts/roadmap-next +44 -0
  23. package/scripts/roadmap-savepoint +64 -0
  24. package/skills/auto/SKILL.md +22 -4
  25. package/skills/continuing/SKILL.md +34 -0
  26. package/skills/continuing/evals/evals.json +91 -0
  27. package/skills/dashboard/SKILL.md +17 -14
  28. package/skills/dashboard/references/classification.md +3 -3
  29. package/skills/dashboard/templates/dashboard-global.md +8 -23
  30. package/skills/dashboard/templates/dashboard-project.md +7 -26
  31. package/skills/doctor/SKILL.md +1 -1
  32. package/skills/install/SKILL.md +10 -10
  33. package/skills/intent-continuing/SKILL.md +26 -68
  34. package/skills/intent-continuing/evals/evals.json +26 -26
  35. package/skills/intent-continuing/references/context-management.md +15 -19
  36. package/skills/intent-savepoint/SKILL.md +12 -0
  37. package/skills/intent-starting/evals/evals.json +1 -1
  38. package/skills/project-continuing/SKILL.md +104 -0
  39. package/skills/project-continuing/evals/evals.json +100 -0
  40. package/skills/project-continuing/references/board-fill.md +33 -0
  41. package/skills/releasing/SKILL.md +48 -0
  42. package/skills/releasing/references/release-lines.md +105 -0
  43. package/skills/roadmap/SKILL.md +7 -1
  44. package/skills/roadmap/references/file-format.md +30 -1
  45. package/skills/roadmap/references/operations.md +26 -6
  46. package/skills/roadmap-continuing/SKILL.md +85 -0
  47. package/skills/roadmap-continuing/evals/evals.json +82 -0
  48. package/skills/roadmap-continuing/references/liveness-ranking.md +56 -0
  49. package/skills/skill-evaluating/evals/evals.json +1 -1
  50. package/skills/tutorial/references/track-2-auto.md +1 -1
  51. package/skills/uninstall/SKILL.md +2 -2
  52. package/skills/update/SKILL.md +2 -2
  53. package/templates/config.yml +2 -1
  54. package/templates/index.md +4 -1
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "skill_name": "plastic-intent-continuing",
3
- "notes": "Intent 36, updated for 36a (boot moved into hook-session-start; the skill is now work-continuation only). Scopes: description triggering (1-8) and behavior/convention compliance of the work-continuation skill (9-14). Triggering assertions follow the plastic-auto eval style (one subagent router per case). Behavior assertions are convention checks against the SKILL.md.",
3
+ "notes": "Intent 158a1: slimmed to the specific-intent resume route of plastic-continuing (D8, extract not duplicate). Bare 'continue' now belongs to the router; triggering evals 1-4 were recast so this skill's positives always name a specific intent, and eval 4 asserts bare 'continue' does NOT settle here directly. Behavior eval 11 (dashboard selection) was removed, relocated to plastic-project-continuing's evals. Behavior eval 15 is new: the terminal handoff goes to plastic-intent-starting, not directly to plastic-auto. Scopes: description triggering (1-8) and behavior/convention compliance of the ledger-resume skill (9-15).",
4
4
  "results": {
5
- "triggering": { "cases": 8, "passed": 8, "run": "2026-06-16, one subagent per case" },
6
- "behavior": { "cases": 6, "passed": 6, "evidence": "convention checks against skills/intent-continuing/SKILL.md after the intent-36 rewrite; doctor --core verified at 0.07s with 9 liveness checks and full doctor unchanged at 30 checks" }
5
+ "triggering": { "cases": 8, "passed": 8, "run": "2026-07-10, one subagent per case" },
6
+ "behavior": { "cases": 7, "passed": 7, "evidence": "convention checks against skills/intent-continuing/SKILL.md after the 158a1 extraction" }
7
7
  },
8
8
  "evals": [
9
9
  {
10
10
  "id": 1, "scope": "triggering", "set": "train",
11
- "prompt": "continue",
12
- "expected_output": "Activates plastic-intent-continuing (the bare 'continue' keyword is the documented trigger).",
11
+ "prompt": "continue intent 4a",
12
+ "expected_output": "Activates plastic-intent-continuing (a specific intent named by id).",
13
13
  "files": [],
14
14
  "assertions": [
15
15
  { "type": "code", "check": "router CHOICE == plastic-intent-continuing", "observed": "plastic-intent-continuing", "result": "pass" }
@@ -17,8 +17,8 @@
17
17
  },
18
18
  {
19
19
  "id": 2, "scope": "triggering", "set": "train",
20
- "prompt": "resume where we left off",
21
- "expected_output": "Activates plastic-intent-continuing.",
20
+ "prompt": "resume the statusline-coloring intent",
21
+ "expected_output": "Activates plastic-intent-continuing (a specific intent named by description).",
22
22
  "files": [],
23
23
  "assertions": [
24
24
  { "type": "code", "check": "router CHOICE == plastic-intent-continuing", "observed": "plastic-intent-continuing", "result": "pass" }
@@ -26,8 +26,8 @@
26
26
  },
27
27
  {
28
28
  "id": 3, "scope": "triggering", "set": "validation",
29
- "prompt": "pick up where we left off in this project",
30
- "expected_output": "Activates plastic-intent-continuing.",
29
+ "prompt": "continuing --intent 96",
30
+ "expected_output": "Activates plastic-intent-continuing (the --intent form).",
31
31
  "files": [],
32
32
  "assertions": [
33
33
  { "type": "code", "check": "router CHOICE == plastic-intent-continuing", "observed": "plastic-intent-continuing", "result": "pass" }
@@ -35,11 +35,11 @@
35
35
  },
36
36
  {
37
37
  "id": 4, "scope": "triggering", "set": "train",
38
- "prompt": "boot plastic and show me where things stand",
39
- "expected_output": "Activates plastic-intent-continuing (continuing work + presenting state is the skill's purpose; boot itself is hook-owned).",
38
+ "prompt": "continue",
39
+ "expected_output": "Does NOT settle on plastic-intent-continuing directly. Bare 'continue' with no specific intent named belongs to the plastic-continuing router, which defaults to plastic-project-continuing.",
40
40
  "files": [],
41
41
  "assertions": [
42
- { "type": "code", "check": "router CHOICE == plastic-intent-continuing", "observed": "plastic-intent-continuing", "result": "pass" }
42
+ { "type": "code", "check": "router CHOICE != plastic-intent-continuing", "observed": "plastic-continuing", "result": "pass" }
43
43
  ]
44
44
  },
45
45
  {
@@ -72,7 +72,7 @@
72
72
  {
73
73
  "id": 8, "scope": "triggering", "set": "validation",
74
74
  "prompt": "what's the dashboard look like right now",
75
- "expected_output": "May activate plastic-dashboard rather than plastic-intent-continuing; an overview request without 'continue/resume' is a dashboard task.",
75
+ "expected_output": "Does NOT activate plastic-intent-continuing directly; an overview request without a specific intent named is a dashboard/project-board task.",
76
76
  "files": [],
77
77
  "assertions": [
78
78
  { "type": "code", "check": "router CHOICE != plastic-intent-continuing", "observed": "plastic-dashboard", "result": "pass" }
@@ -97,16 +97,7 @@
97
97
  ]
98
98
  },
99
99
  {
100
- "id": 11, "scope": "behavior", "set": "train",
101
- "prompt": "Is dashboard selection project-aware?",
102
- "expected_output": "Project loaded -> `dashboard.rb project <slug>`; otherwise -> `dashboard.rb continue`. Skill only invokes, does not render.",
103
- "files": ["skills/intent-continuing/SKILL.md"],
104
- "assertions": [
105
- { "type": "convention", "check": "both dashboard invocations present and gated on project detection", "observed": "'dashboard.rb project <slug>' and 'dashboard.rb continue' in the 'Continue (present the dashboard)' section", "result": "pass" }
106
- ]
107
- },
108
- {
109
- "id": 12, "scope": "behavior", "set": "validation",
100
+ "id": 11, "scope": "behavior", "set": "validation",
110
101
  "prompt": "Is ledger-resume conditional and ledger-driven?",
111
102
  "expected_output": "Resume fires only when a specific intent is named; reads savepoint.md last line as stage, verifies the stage file, rebuilds on drift, derives next step from first unchecked checklist item.",
112
103
  "files": ["skills/intent-continuing/SKILL.md"],
@@ -115,7 +106,7 @@
115
106
  ]
116
107
  },
117
108
  {
118
- "id": 13, "scope": "behavior", "set": "validation",
109
+ "id": 12, "scope": "behavior", "set": "validation",
119
110
  "prompt": "Are the stale prose-savepoint fields gone?",
120
111
  "expected_output": "No 'In progress' / 'Blockers' fields read from a prose savepoint remain; the ledger model is used instead.",
121
112
  "files": ["skills/intent-continuing/SKILL.md"],
@@ -124,7 +115,7 @@
124
115
  ]
125
116
  },
126
117
  {
127
- "id": 14, "scope": "behavior", "set": "validation",
118
+ "id": 13, "scope": "behavior", "set": "validation",
128
119
  "prompt": "Is the 'Determine Store' local-store gap filled?",
129
120
  "expected_output": "Step 2 of Determine Store describes local/project-store detection (no empty step).",
130
121
  "files": ["skills/intent-continuing/SKILL.md"],
@@ -133,13 +124,22 @@
133
124
  ]
134
125
  },
135
126
  {
136
- "id": 15, "scope": "behavior", "set": "validation",
127
+ "id": 14, "scope": "behavior", "set": "validation",
137
128
  "prompt": "QMD is present. The user says: continue the work on the statusline coloring (names the intent by description, not id).",
138
129
  "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
130
  "files": ["skills/intent-continuing/SKILL.md"],
140
131
  "assertions": [
141
132
  { "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
133
  ]
134
+ },
135
+ {
136
+ "id": 15, "scope": "behavior", "set": "train",
137
+ "prompt": "Does the ledger-resume's terminal handoff go to plastic-intent-starting or directly to plastic-auto?",
138
+ "expected_output": "Step 5 announces state then hands off to plastic-intent-starting, which takes the lock and asks 'auto or guided?' exactly once; plastic-intent-starting's own auto branch is the one that hands to plastic-auto. This skill never hands to plastic-auto directly.",
139
+ "files": ["skills/intent-continuing/SKILL.md"],
140
+ "assertions": [
141
+ { "type": "convention", "check": "step 5 hands off to plastic-intent-starting, not plastic-auto; the single ask is attributed to plastic-intent-starting", "observed": "present in 'Conditional Ledger-Resume' step 5", "result": "pass" }
142
+ ]
143
143
  }
144
144
  ]
145
145
  }
@@ -1,4 +1,4 @@
1
- # Context Management (Start-Save-Continue)
1
+ # Context Management (Save, then resume-flow debugging)
2
2
 
3
3
  ## Save Point
4
4
  Triggered by PreCompact hook or manually:
@@ -7,26 +7,22 @@ Triggered by PreCompact hook or manually:
7
7
  3. Update active intent's `savepoint.md` (in-progress, next steps, blockers, discoveries)
8
8
  4. Add observations to `## Insights`
9
9
  5. Update INDEX.md
10
- 6. Commit: `cd ~/.plastic && git add . && git commit -m "chore: savepoint [intent name]"`
10
+ 6. Commit: `cd ~/.plastic && git add . && git commit -m "chore: savepoint - [intent name]"`
11
11
  7. Notify user to `/clear`
12
12
 
13
- ## Continue
14
- Triggered by UserPromptSubmit hook when user says "continue". Priority order:
13
+ ## Debugging the resume flow
15
14
 
16
- **1. Active intents first (resume work):**
17
- 1. Read INDEX.md → find active intent(s)
18
- 2. Read active intent's `intent.md` → what and why
19
- 3. Read active intent's `savepoint.md` → where we left off
20
- 4. Read active intent's `checklist.md` → what's next
21
- 5. Announce: intent name, current state, next step, blockers
22
- 6. Resume
15
+ When a resume looks wrong (the announced stage does not match what is on disk, or the next
16
+ step looks stale):
23
17
 
24
- **2. No active intents offer future intents:**
25
- 1. List all future intents from INDEX.md
26
- 2. Present them as options
27
- 3. When user picks one, move to Active in INDEX.md
18
+ 1. Read `savepoint.md`'s last line directly; that line alone is the source of truth for stage
19
+ (see `SKILL.md`'s `## Conditional Ledger-Resume` for the full state table).
20
+ 2. Confirm the artifact that line implies (`plan.md`, `checklist.md`, `outcome.md`, ...) is
21
+ present and non-empty on disk.
22
+ 3. If the two disagree, the ledger has drifted: rebuild it rather than hand-editing:
23
+ `ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.rebuild_savepoint("<intent_dir>")'`
24
+ 4. Re-read the rebuilt last line and re-derive the next step from `checklist.md`'s first
25
+ unchecked item.
28
26
 
29
- **3. Stale future intents (untouched 3+ days) → triage:**
30
- - **activate** start working on it now
31
- - **abandon** — mark as abandoned
32
- - **defer to agent** — implement, research, or ideate
27
+ The general "land on the board / priority order / stale future intents" flow now lives in
28
+ `plastic-project-continuing`; this file no longer duplicates it.
@@ -42,6 +42,18 @@ Read the active store's `INDEX.md` and extract intents under `## Active`.
42
42
  - Confirm the **last line's stage** matches the stage derived from files-on-disk
43
43
  (`Bridge.derive_stage`). If they disagree, or the file is missing/empty, the ledger has
44
44
  drifted.
45
+ - Run `Bridge.savepoint_phantom_lines(intent_dir)` (pure, disk-only, no bridge or session
46
+ resolution; intent 134). It flags a line disk evidence contradicts: a file-landing milestone
47
+ whose file is absent or still a sentinel placeholder, a duplicate `(stage, milestone)` pair, or
48
+ a state line (`How started` / `Exec started`) whose stage prerequisites are absent. If it
49
+ returns any lines:
50
+ - **LIVE intent (INDEX `## Active`):** rebuild via step 3 below.
51
+ - **INDEX `## Completed`/`## Abandoned` intent:** REPORT the phantom lines and STOP. Do not
52
+ rewrite. Completed intents are immutable. A repair is available only under an explicit human
53
+ grant, following the 124a precedent: rebuild the file-landing skeleton (step 3), then
54
+ re-append the terminal `Done <disposition>` bookend with the disposition read from
55
+ `outcome.md`'s `disposition:` frontmatter and the timestamp recovered from the merge commit or
56
+ the file's mtime, never invented.
45
57
 
46
58
  ### 3. Rebuild on drift
47
59
  Reconstruct from the filesystem rather than hand-editing:
@@ -40,7 +40,7 @@
40
40
  "scope": "triggering",
41
41
  "set": "validation",
42
42
  "prompt": "continue",
43
- "expected_output": "Does NOT activate plastic-intent-starting; bare 'continue' is plastic-intent-continuing (no specific intent named).",
43
+ "expected_output": "Does NOT activate plastic-intent-starting; bare 'continue' is plastic-continuing (the router, no specific intent named).",
44
44
  "files": [],
45
45
  "assertions": [
46
46
  { "type": "code", "check": "router CHOICE != plastic-intent-starting", "result": "expect-pass" }
@@ -0,0 +1,104 @@
1
+ ---
2
+ name: plastic-project-continuing
3
+ description: >-
4
+ Use when the user wants to land on the project board, asks "what should I work on" in this
5
+ project, or wants active intents plus the most-valuable next work surfaced. This is the
6
+ default route of plastic-continuing for a bare "continue" with no specific intent or roadmap
7
+ named. It presents state and stops without asking how to proceed - that single mode question
8
+ lives in plastic-intent-starting, once the user names a specific intent to work.
9
+ user-invocable: true
10
+ ---
11
+
12
+ # Project Continuing - land on the board
13
+
14
+ `plastic-project-continuing` is the project route of `plastic-continuing`. It lands on the
15
+ Markdown board, presents active work and the most-valuable next work, and stops. It does not
16
+ resume a specific intent by ledger (that is `plastic-intent-continuing`) and it asks nothing.
17
+
18
+ ## Continue (present the board)
19
+
20
+ Land on the Markdown board via the `plastic-dashboard` skill. Rendering belongs there, not
21
+ here - run the data payload and fill + present the matching template:
22
+ - Project loaded -> `ruby ~/.plastic/scripts/dashboard.rb project <slug> --data`
23
+ - Otherwise (no project loaded, the global fallback) -> `ruby ~/.plastic/scripts/dashboard.rb continue --data`
24
+
25
+ Fill the matching template from `plastic-dashboard`'s `templates/` and **present the filled
26
+ Markdown in your reply** (every time, non-optional). If the reply does not contain the filled
27
+ Markdown, the user sees nothing - tool-call stdout and hook `additionalContext` are both
28
+ invisible to them. `hook-continue` also emits a one-line `systemMessage` summary as a
29
+ hook-owned fallback; treat it as a floor only, never as a substitute for presenting the full
30
+ board here. See `plastic-dashboard` for the fill mechanics (`references/board-fill.md` has the
31
+ scoped detail).
32
+
33
+ The board load runs the scoped store check on every load (`doctor --store <scope>`): the
34
+ global board runs `--store global` and a project board runs `--store <slug>`. The result
35
+ arrives in the payload as `store_health`; surface it as a one-line store-health note. It is
36
+ non-fatal (a warn or fail is shown as data, it does not block continuing).
37
+
38
+ ## Priority Order
39
+
40
+ 1. **Active intents first** - surface work in progress.
41
+ 2. **Project context** - if in a registered project, show governing + tactical intents.
42
+ 3. **Stale future intents** - surface for triage (see below).
43
+ 4. **Fresh future intents** - offer as next work.
44
+
45
+ ## Stale Future Intents
46
+
47
+ If a future intent's `created` date is older than the configured `stale_threshold_days`
48
+ (default 3), surface it for triage without taking action:
49
+
50
+ ```
51
+ Stale future intents (no action taken):
52
+
53
+ - [ID - name] (X days old)
54
+ a) Activate - start working on it now
55
+ b) Abandon - mark as abandoned
56
+ c) Defer to agent: implement | research | ideate
57
+ d) Auto - go fully autonomous (invokes plastic-auto)
58
+ ```
59
+
60
+ When the user activates a future intent, move it to `## Active` in INDEX.md and auto-commit.
61
+
62
+ **Defer to agent: research.** Selecting `research` is a real dispatch, not a label. Resolve
63
+ `plastic-future-intent-researcher`'s model via `read-config agents.models.<basename> --project
64
+ <repo>` (project override, then global, then the shipped tier default) rather than relying on
65
+ bare frontmatter, which also honors a sanctioned `agents.models.<name>` override if one is
66
+ configured. Dispatch the agent (Agent tool, `subagent_type: "plastic-future-intent-researcher"`)
67
+ on the selected stale future intent, passing the resolved model explicitly. The agent writes its
68
+ findings into that intent's `## Context` (see `agents/plastic-future-intent-researcher.md`); it
69
+ does not itself dispatch further sub-agents. Once it returns, re-present the stale-future-intent
70
+ triage so the user can act on the fresh findings now on record.
71
+
72
+ ## Deciding rules
73
+
74
+ The board's ranked next-work order is deterministic, computed by `dashboard.rb`; this skill
75
+ never re-derives it. Cite the rule NAMES only when explaining a ranking or disposition: Effort,
76
+ Value, Flags, Override, Caps. Read `plastic-dashboard`'s `references/classification.md` for the
77
+ definitions; do not restate or copy them here.
78
+
79
+ ## Then stop
80
+
81
+ Present "here is the state, what next?" and wait. Offer active intents first, then future
82
+ intents. Do not start executing work. The only follow-up from here:
83
+ - User/agent names a specific intent to continue -> hand to `plastic-intent-continuing`
84
+ (which reads the ledger and, in turn, hands to `plastic-intent-starting` for the single
85
+ auto-or-guided ask).
86
+
87
+ ## Coordination
88
+
89
+ Intent 149 has landed: the dashboard is demoted to prose (no Value x Effort grid). This skill
90
+ was built against the live INDEX.md-parsing `--data` path (147, the DB cutover, has not
91
+ landed). Its rule-name citations (`classification.md`, cited by name, not logic) and the
92
+ `dashboard.rb project <slug> --data` -> `dashboard-project.md` path still resolve.
93
+
94
+ Intent 148 landed: roadmaps are the primary planning surface. When the tier has a mid-flight
95
+ roadmap (`ruby ~/.plastic/scripts/roadmap-next --roadmaps-dir <tier>/roadmaps` reports a `state`
96
+ other than `none`), the roadmap route (`plastic-roadmap-continuing`) is the live surface for
97
+ "what to work next", and `plastic-continuing` routes there. This board still presents project
98
+ state and stops, asking nothing (unchanged): it does not itself dispatch, re-rank, or resume a
99
+ roadmap. The global store and any project with no roadmap report `none`, so this board stays the
100
+ default route for them.
101
+
102
+ ## References
103
+
104
+ - `references/board-fill.md` - the template-fill mechanics and store-health surfacing detail.
@@ -0,0 +1,100 @@
1
+ {
2
+ "skill_name": "plastic-project-continuing",
3
+ "notes": "Intent 158a1. New project route, absorbing intent-continuing's Continue/Priority Order/Stale Future Intents sections (move, not duplicate). Scopes: description triggering (1-3) and behavior/convention compliance (4-10), the latter checked against SKILL.md.",
4
+ "results": {
5
+ "triggering": { "cases": 3, "passed": 3, "run": "2026-07-10, one subagent per case" },
6
+ "behavior": { "cases": 7, "passed": 7, "evidence": "convention checks against skills/project-continuing/SKILL.md after the 158a1 extraction; id 10 (intent 170) added for the research-dispatch wiring of plastic-future-intent-researcher" }
7
+ },
8
+ "evals": [
9
+ {
10
+ "id": 1, "scope": "triggering", "set": "train",
11
+ "prompt": "what should I work on in this project",
12
+ "expected_output": "Activates plastic-project-continuing.",
13
+ "files": [],
14
+ "assertions": [
15
+ { "type": "code", "check": "router CHOICE == plastic-project-continuing", "observed": "plastic-project-continuing", "result": "pass" }
16
+ ]
17
+ },
18
+ {
19
+ "id": 2, "scope": "triggering", "set": "train",
20
+ "prompt": "land me on the project board",
21
+ "expected_output": "Activates plastic-project-continuing.",
22
+ "files": [],
23
+ "assertions": [
24
+ { "type": "code", "check": "router CHOICE == plastic-project-continuing", "observed": "plastic-project-continuing", "result": "pass" }
25
+ ]
26
+ },
27
+ {
28
+ "id": 3, "scope": "triggering", "set": "validation",
29
+ "prompt": "continue",
30
+ "expected_output": "Bare 'continue' with no specific intent or roadmap named reaches plastic-project-continuing via plastic-continuing's default route.",
31
+ "files": [],
32
+ "assertions": [
33
+ { "type": "code", "check": "router CHOICE == plastic-project-continuing", "observed": "plastic-project-continuing", "result": "pass" }
34
+ ]
35
+ },
36
+ {
37
+ "id": 4, "scope": "behavior", "set": "train",
38
+ "prompt": "Is dashboard selection project-aware?",
39
+ "expected_output": "Project loaded -> `dashboard.rb project <slug> --data` -> fills dashboard-project.md; otherwise -> `dashboard.rb continue --data`. Skill only invokes, does not render.",
40
+ "files": ["skills/project-continuing/SKILL.md"],
41
+ "assertions": [
42
+ { "type": "convention", "check": "both dashboard invocations present and gated on project detection", "observed": "'dashboard.rb project <slug>' and 'dashboard.rb continue' in the 'Continue (present the board)' section", "result": "pass" }
43
+ ]
44
+ },
45
+ {
46
+ "id": 5, "scope": "behavior", "set": "train",
47
+ "prompt": "Does the skill cite the classification rules by name instead of copying the logic?",
48
+ "expected_output": "SKILL.md's 'Deciding rules' section names Effort/Value/Flags/Override/Caps and points to plastic-dashboard's references/classification.md; it does not restate the rule definitions.",
49
+ "files": ["skills/project-continuing/SKILL.md"],
50
+ "assertions": [
51
+ { "type": "convention", "check": "rule names cited, no copied Effort/Value/Flags/Override/Caps definitions", "observed": "present in 'Deciding rules'", "result": "pass" }
52
+ ]
53
+ },
54
+ {
55
+ "id": 6, "scope": "behavior", "set": "train",
56
+ "prompt": "Does the project route ask 'auto or guided?' before stopping?",
57
+ "expected_output": "No. The project route presents state then stops; it asks nothing. The single ask lives in plastic-intent-starting once a specific intent is named.",
58
+ "files": ["skills/project-continuing/SKILL.md"],
59
+ "assertions": [
60
+ { "type": "convention", "check": "no 'auto or guided' ask anywhere in SKILL.md", "observed": "absent", "result": "pass" }
61
+ ]
62
+ },
63
+ {
64
+ "id": 7, "scope": "behavior", "set": "validation",
65
+ "prompt": "Does the skill present the board before stopping?",
66
+ "expected_output": "The filled Markdown board is presented in the reply (non-optional), then a 'Then stop' section waits for the user.",
67
+ "files": ["skills/project-continuing/SKILL.md"],
68
+ "assertions": [
69
+ { "type": "convention", "check": "presentation is mandatory and precedes the 'Then stop' wait", "observed": "present", "result": "pass" }
70
+ ]
71
+ },
72
+ {
73
+ "id": 8, "scope": "behavior", "set": "validation",
74
+ "prompt": "Does the skill carry the intent-149 coordination note?",
75
+ "expected_output": "A 'Coordination' section records that intent 149 has landed (dashboard demoted to prose, no Value x Effort grid) and confirms the rule-name citations and the dashboard.rb project <slug> --data -> dashboard-project.md path still resolve.",
76
+ "files": ["skills/project-continuing/SKILL.md"],
77
+ "assertions": [
78
+ { "type": "convention", "check": "'149' present with the landed coordination note", "observed": "present: 'Intent 149 has landed' with confirmed rule-name citations and dashboard.rb path", "result": "pass" }
79
+ ]
80
+ },
81
+ {
82
+ "id": 9, "scope": "behavior", "set": "validation",
83
+ "prompt": "Are Priority Order and Stale Future Intents present here and only here (not in intent-continuing)?",
84
+ "expected_output": "Both sections are present verbatim in this skill; they no longer exist in skills/intent-continuing/SKILL.md.",
85
+ "files": ["skills/project-continuing/SKILL.md", "skills/intent-continuing/SKILL.md"],
86
+ "assertions": [
87
+ { "type": "convention", "check": "'## Priority Order' and '## Stale Future Intents' present here, absent from intent-continuing", "observed": "present here, absent there", "result": "pass" }
88
+ ]
89
+ },
90
+ {
91
+ "id": 10, "scope": "behavior", "set": "validation",
92
+ "prompt": "In the stale-future-intent triage, the user picks option c) Defer to agent: research. What happens?",
93
+ "expected_output": "A real dispatch, not a label: plastic-future-intent-researcher's model is resolved via `read-config agents.models.<basename> --project <repo>` (never bare frontmatter, sanctioned-override-aware), the agent is dispatched via the Agent tool on the selected stale future intent and writes its findings into that intent's `## Context`, then the skill re-presents the stale-future-intent triage.",
94
+ "files": ["skills/project-continuing/SKILL.md"],
95
+ "assertions": [
96
+ { "type": "convention", "check": "the 'Defer to agent: research' paragraph under Stale Future Intents resolves the model via read-config (not bare frontmatter), dispatches plastic-future-intent-researcher via the Agent tool, and re-presents the triage after the agent returns", "observed": "'Defer to agent: research.' paragraph present: resolves via read-config agents.models.<basename> --project <repo>, dispatches subagent_type plastic-future-intent-researcher, findings land in ## Context, then re-presents the triage", "result": "pass" }
97
+ ]
98
+ }
99
+ ]
100
+ }
@@ -0,0 +1,33 @@
1
+ # Board Fill Mechanics
2
+
3
+ Depth reference for the "Continue (present the board)" section of `SKILL.md`. The fill itself
4
+ is mechanical; `plastic-dashboard` owns the rules, this page is a pointer plus the detail that
5
+ would otherwise bloat the SKILL.md body.
6
+
7
+ ## Fill rules (owned by plastic-dashboard, summarized here for convenience)
8
+
9
+ - `{{a.b.count}}` -> the integer (e.g. `counts.active` is that count).
10
+ - `{{...lines}}` -> join the list's `.line` strings with real newlines (one per line). These
11
+ are ordinary prose lines; never add a `-` prefix, never emit `<br>`. An empty list renders
12
+ `_(none)_`.
13
+ - `next_work.lines` -> the most-valuable next work, already ranked and capped.
14
+ - `active.lines` / `future.lines` (project board) -> one line per intent, already formatted.
15
+ - Scalars (`{{date}}`, `{{slug}}`, `{{description}}`) -> substitute verbatim.
16
+
17
+ No re-sorting, no re-summarizing, no hand-written prose replacing a line the payload already
18
+ supplies. Same store state produces a byte-identical payload regardless of model.
19
+
20
+ ## Store-health surfacing
21
+
22
+ Every board load runs the scoped `doctor --store <scope>` check server-side (inside
23
+ `dashboard.rb`, not this skill). The payload carries the result as `store_health` (`{scope,
24
+ status, summary, failing_checks}`). Render it as a single line, for example `store health:
25
+ pass (3/3)` or `store health: warn (orphaned_intents)`. A warn or fail is informational only;
26
+ it never blocks presenting the board.
27
+
28
+ ## Project vs. global fallback
29
+
30
+ The project route's default target is the project board. When no project is loaded (the rare
31
+ case where this route is reached without a registered project in scope), fall back to the
32
+ global board payload (`dashboard.rb continue --data`) rather than failing. This mirrors the
33
+ router's D6 default: a bare "continue" always lands somewhere useful.
@@ -15,6 +15,7 @@ Project configuration drives the workflow - no hardcoded assumptions.
15
15
  - [ ] All tests pass (or verification skipped per config)
16
16
  - [ ] Merge feature branch to main
17
17
  - [ ] Bump version in configured version files
18
+ - [ ] Stable-cut guard passes (version files agree, no pre-release suffix; stable/latest cuts only)
18
19
  - [ ] Commit version bump
19
20
  - [ ] Create annotated tag
20
21
  - [ ] Push to remote with tags
@@ -94,6 +95,25 @@ the full rationale and the already-merged-by-hand no-op case, read
94
95
 
95
96
  ### 4. Bump Version
96
97
 
98
+ **Stable-cut guard.** Before touching any version file for a stable (no pre-release suffix,
99
+ `latest`) cut, run the guard in `scripts/lib/release_guard.rb`:
100
+
101
+ ```ruby
102
+ require "./scripts/lib/release_guard"
103
+ result = ReleaseGuard.check(
104
+ package_json: "package.json",
105
+ plugin_json: ".claude-plugin/plugin.json",
106
+ marketplace_json: ".claude-plugin/marketplace.json",
107
+ stable: true
108
+ )
109
+ raise "release guard failed: #{result.mismatches} #{result.prerelease_suffix}" unless result.ok?
110
+ ```
111
+
112
+ If it reports a mismatch or a pre-release-suffix violation, stop and resolve it before bumping
113
+ any file. For a beta or alpha cut, pass `stable: false`; only version-file agreement is checked,
114
+ a pre-release suffix is expected. Read `references/release-lines.md` for the stable-line
115
+ guarantees this guard protects.
116
+
97
117
  Determine which files to update from project.yml:
98
118
 
99
119
  - If `release.version_files` is set: update ALL listed files (they must stay in sync).
@@ -252,6 +272,32 @@ prune` in the affected repo if you hit a stale reference. For why this is the on
252
272
  merge-vs-remove policy lands on merge, and the fail-open/idempotent guarantees of `finish`,
253
273
  read `references/promotion-and-tagging.md`.
254
274
 
275
+ ## Release lines and channels
276
+
277
+ Two lanes get code to a release, on top of the workflow above.
278
+
279
+ - **Default lane.** Branch, merge to main, cut stable, publish to npm `latest`. This is the
280
+ workflow in the steps above, unchanged. Use it for additive, suite-verifiable,
281
+ low-blast-radius work.
282
+ - **Beta-verified lane.** Branch, merge to `beta`, publish to the npm `beta` channel, verify in
283
+ real use, then merge to main and cut stable. Use it for work that changes operational
284
+ substrate, or carries data, migration, lock, or state-format risk, or that a hermetic suite
285
+ cannot fully validate on its own.
286
+
287
+ **Stable-line guarantees.** An external `latest` user can rely on:
288
+
289
+ - `main` is always green and releasable; no pending revert awaiting re-land sits on `main`.
290
+ - A stable release carries no pre-release suffix, publishes to `latest`, and the newest release
291
+ always carries the GitHub "Latest" badge.
292
+ - The three version files always agree, checked by `scripts/lib/release_guard.rb` (see Bump
293
+ Version above).
294
+ - A stable cut collects only intents that cleared their lane's verification bar.
295
+ - Channel semantics are fixed: `latest` is stable, `beta` is the verification line, `alpha` is
296
+ experimental.
297
+
298
+ Read `references/release-lines.md` for the full lane-routing detail, the version-line map, and
299
+ the intent-41 re-land playbook.
300
+
255
301
  ## Conventions
256
302
 
257
303
  - **Annotated tags only** - `git tag -a`, never lightweight tags
@@ -266,6 +312,8 @@ read `references/promotion-and-tagging.md`.
266
312
 
267
313
  ## References
268
314
 
315
+ - Read `references/release-lines.md` for the two release lanes, the stable-line guarantees,
316
+ the version-line map, and the intent-41 re-land playbook before starting any release
269
317
  - When promoting a pre-release across channels (`--promote beta`/`--promote stable`) or
270
318
  tagging a historical release retroactively, read `references/promotion-and-tagging.md`
271
319
  for the exact commands and rules first
@@ -0,0 +1,105 @@
1
+ # Release Lines and Channels
2
+
3
+ The two release lanes, the version-line map, and the intent-41 re-land playbook: the deep
4
+ material behind SKILL.md's "Release lines and channels" section.
5
+
6
+ ## Table of Contents
7
+
8
+ - [The two lanes](#the-two-lanes)
9
+ - [Routing rule](#routing-rule)
10
+ - [Stable-line guarantees](#stable-line-guarantees)
11
+ - [Version-line map](#version-line-map)
12
+ - [Intent 41 re-land playbook](#intent-41-re-land-playbook)
13
+
14
+ ## The two lanes
15
+
16
+ **Default lane.** Branch, merge to `main` with `--no-ff`, cut stable, publish to npm `latest`.
17
+ This is the workflow SKILL.md documents step by step. It is the path for additive,
18
+ suite-verifiable, low-blast-radius work: new skills, prose, deterministic scripts, anything a
19
+ green Minitest run can fully vouch for.
20
+
21
+ **Beta-verified lane.** Branch, merge to the `beta` branch, publish to the npm `beta` dist-tag,
22
+ verify in real use, then merge `beta` into `main` and cut stable. It sits on top of the existing
23
+ promotion mechanics (`--promote beta` / `--promote stable`, linear only, see
24
+ `promotion-and-tagging.md`); it names when to use them, not new machinery.
25
+
26
+ ## Routing rule
27
+
28
+ Work rides the beta-verified lane when it changes operational substrate, carries data,
29
+ migration, lock, or state-format risk, or cannot be fully validated by a hermetic suite alone.
30
+ Everything else merges straight to main.
31
+
32
+ Intent 41's DB layer is the archetypal beta-lane case: it replaces the bridge, lock, and session
33
+ file formats with a new persistent SQLite substrate. A green suite proves the code correct; it
34
+ cannot prove the new substrate survives real, uncontrolled usage, so real-use verification on
35
+ beta comes first.
36
+
37
+ The manual-first roadmap's eight 1.1.0 intents (158a, 163, 161, 164, 165, 168, 166, 159) are all
38
+ default-lane: skill directory renames, prose rewrites, deterministic step scripts. Additive, and
39
+ fully suite-verified.
40
+
41
+ ## Stable-line guarantees
42
+
43
+ What an external `latest` user can rely on:
44
+
45
+ 1. `main` is always green and releasable. No pending revert awaiting re-land sits on `main`.
46
+ When something needs beta verification, it comes out of `main` the same day that need is
47
+ found (the 226023f precedent), never left half-landed.
48
+ 2. A stable release carries no pre-release suffix, publishes to npm `latest`, and the newest
49
+ stable release always carries the GitHub "Latest" badge (`gh release create --latest` on
50
+ every cut).
51
+ 3. The three repo version files (`package.json`, `.claude-plugin/plugin.json`,
52
+ `.claude-plugin/marketplace.json`) always agree. Checked mechanically by
53
+ `scripts/lib/release_guard.rb`.
54
+ 4. A stable cut collects only intents that cleared their lane's bar: default-lane intents by a
55
+ green suite, beta-lane intents by suite green plus their lane's own verification (real-use
56
+ signal, owner sign-off).
57
+ 5. Channel semantics are fixed: `latest` is stable and what an external user should run; `beta`
58
+ is the verification line, published but expected to move; `alpha` is experimental,
59
+ pre-verification.
60
+
61
+ ## Version-line map
62
+
63
+ | Line | State | What lands here |
64
+ |---|---|---|
65
+ | `1.1.x` | Current stable line (main) | Additive or low-risk work merged straight to main; interim stable cuts, including 171's wave-6 cut, stay in this line |
66
+ | `1.2.0-beta.1` | On beta (`9ec194b`, unpublished) | Intent 41's DB layer, restored over 1.1.0 by revert-of-revert (`c48601a` then `9ec194b`) |
67
+ | `1.2.0` | Reserved | The stable graduation of the DB layer, once beta verification passes; not claimed by any interim `1.1.x` cut |
68
+
69
+ A beta-graduated substrate change claims its reserved minor at the moment it actually merges to
70
+ main, not before. Nothing else on the `1.1.x` line is blocked waiting for `1.2.0`.
71
+
72
+ ## Intent 41 re-land playbook
73
+
74
+ Written for the wave-6 cut intent (171) and any future reader to pick a version without
75
+ re-deriving this decision.
76
+
77
+ **Current state.** The revert-of-revert already sits on the `beta` branch at `9ec194b`, on top
78
+ of 1.1.0, versioned `1.2.0-beta.1` (`c48601a`). There is nothing left to execute on the git side;
79
+ this playbook describes what happens next, not a pending action.
80
+
81
+ **Preconditions**, both required before any npm publish of `1.2.0-beta.1`:
82
+
83
+ - (a) One documentation pass over beta-line skills and docs for the hybrid savepoint contract:
84
+ on beta, only the terminal Done bookend still writes a live `savepoint.md`; every other
85
+ milestone lives in `savepoint_events` plus a committed JSONL export. Beta-line prose that
86
+ still assumes an always-live ledger needs updating first, so a beta-line reader does not
87
+ mistake an empty ledger for a broken one.
88
+ - (b) The owner's manual verification of the DB layer in real use. This is a dogfood signal,
89
+ distinct from the independently-reviewed green suite that already exists on beta.
90
+
91
+ **Trigger**, owner-gated: the owner publishes `1.2.0-beta.1` to the npm `beta` dist-tag. This is
92
+ explicitly not this intent's, nor any agent's, call to make.
93
+
94
+ **Verification.** An external tester plus the owner verify the DB layer on the beta channel.
95
+
96
+ **Completion.** Once verified, `beta` merges into `main`, `1.2.0` is cut stable, and it publishes
97
+ to npm `latest`.
98
+
99
+ **Version mechanics.** `1.2.0` is reserved for this graduation. The `1.1.x` line stays the
100
+ stable line until `1.2.0` actually lands. `1.2.0-beta.1` graduates to `1.2.0` stable by dropping
101
+ the pre-release suffix; nothing else about the version number changes.
102
+
103
+ **Consumed by 171.** The wave-6 consistency-dividend cut stays in the `1.1.x` line. It does not
104
+ ride intent 41 and needs no further derivation: intent 41 keeps its own `1.2.0` line on beta,
105
+ independent of whatever `1.1.x` number 171 lands on.