@zalom/plastic 1.1.0 → 1.1.2

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 (63) hide show
  1. package/PLASTIC.md +12 -9
  2. package/README.md +3 -3
  3. package/agents/plastic-enforcer.md +8 -4
  4. package/agents/plastic-executor.md +5 -5
  5. package/agents/plastic-future-intent-researcher.md +1 -1
  6. package/agents/plastic-planner.md +15 -11
  7. package/hooks/check-update +1 -1
  8. package/hooks/continue +1 -1
  9. package/package.json +1 -1
  10. package/scripts/dashboard.rb +29 -24
  11. package/scripts/doctor.rb +180 -5
  12. package/scripts/hook-continue +3 -3
  13. package/scripts/install.rb +2 -1
  14. package/scripts/lib/bridge.rb +114 -11
  15. package/scripts/lib/dashboard_banner.rb +8 -9
  16. package/scripts/lib/installer_core.rb +12 -3
  17. package/scripts/lib/legacy_bookend_amnesty.rb +35 -0
  18. package/scripts/lib/release_guard.rb +62 -0
  19. package/scripts/lib/roadmap_queue.rb +285 -0
  20. package/scripts/lib/roadmap_savepoint.rb +213 -0
  21. package/scripts/lib/skill_lint.rb +304 -0
  22. package/scripts/lib/worktree.rb +21 -0
  23. package/scripts/new-intent +1 -0
  24. package/scripts/read-config +3 -3
  25. package/scripts/roadmap-next +44 -0
  26. package/scripts/roadmap-savepoint +64 -0
  27. package/scripts/skill-lint +50 -0
  28. package/skills/auto/SKILL.md +32 -11
  29. package/skills/auto/references/tiers.md +4 -3
  30. package/skills/continuing/SKILL.md +34 -0
  31. package/skills/continuing/evals/evals.json +91 -0
  32. package/skills/dashboard/SKILL.md +17 -14
  33. package/skills/dashboard/references/classification.md +3 -3
  34. package/skills/dashboard/templates/dashboard-global.md +8 -23
  35. package/skills/dashboard/templates/dashboard-project.md +7 -26
  36. package/skills/doctor/SKILL.md +1 -1
  37. package/skills/install/SKILL.md +10 -10
  38. package/skills/intent-continuing/SKILL.md +26 -68
  39. package/skills/intent-continuing/evals/evals.json +26 -26
  40. package/skills/intent-continuing/references/context-management.md +15 -19
  41. package/skills/intent-planning/SKILL.md +11 -11
  42. package/skills/intent-planning/evals/evals.json +20 -5
  43. package/skills/intent-planning/references/plan-format.md +9 -5
  44. package/skills/intent-savepoint/SKILL.md +12 -0
  45. package/skills/intent-starting/evals/evals.json +1 -1
  46. package/skills/project-continuing/SKILL.md +104 -0
  47. package/skills/project-continuing/evals/evals.json +100 -0
  48. package/skills/project-continuing/references/board-fill.md +33 -0
  49. package/skills/releasing/SKILL.md +48 -0
  50. package/skills/releasing/references/release-lines.md +105 -0
  51. package/skills/roadmap/SKILL.md +7 -1
  52. package/skills/roadmap/references/file-format.md +30 -1
  53. package/skills/roadmap/references/operations.md +26 -6
  54. package/skills/roadmap-continuing/SKILL.md +85 -0
  55. package/skills/roadmap-continuing/evals/evals.json +82 -0
  56. package/skills/roadmap-continuing/references/liveness-ranking.md +56 -0
  57. package/skills/skill-evaluating/evals/evals.json +1 -1
  58. package/skills/tutorial/references/track-1-guided.md +5 -4
  59. package/skills/tutorial/references/track-2-auto.md +1 -1
  60. package/skills/uninstall/SKILL.md +2 -2
  61. package/skills/update/SKILL.md +2 -2
  62. package/templates/config.yml +2 -1
  63. package/templates/index.md +4 -1
@@ -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.
@@ -21,7 +21,10 @@ wave entry mirrors that intent's status in `INDEX.md` (`queued`/`delivering`/`de
21
21
  **`INDEX.md` is the single writer of intent status; on any conflict INDEX wins and the roadmap
22
22
  entry is corrected to match.**
23
23
 
24
- The skill operates on the file directly via Read/Edit; no helper script.
24
+ The skill operates on the roadmap file directly via Read/Edit. The one deterministic helper it
25
+ uses is the savepoint ledger writer (`scripts/roadmap-savepoint`, `append`/`rebuild`); every verb's
26
+ closing step calls `append` after its Read/Edit, and the roadmap `.md` file itself stays
27
+ Read/Edit-only.
25
28
 
26
29
  ## Verbs
27
30
 
@@ -50,3 +53,6 @@ verb above.
50
53
  - Additive: this skill introduces no gate, lock, or hook, and does not change `INDEX.md`'s section
51
54
  list or the intent frontmatter schema.
52
55
  - Closing a roadmap moves it to `roadmaps/archived/{slug}.md` so `roadmaps/` lists only live ones.
56
+ - Every verb also appends a machine ledger line to the roadmap's name-paired
57
+ `roadmaps/{slug}.savepoint.md`, the derived counterpart to the human `## Log`; see
58
+ `references/file-format.md` for its shape and location.
@@ -11,7 +11,9 @@ roadmap.
11
11
 
12
12
  `roadmaps/` lists only live (open or in-flight) roadmaps. Once a roadmap's `## Goal` is reached,
13
13
  its file moves to `roadmaps/archived/{slug}.md` (see Close/archive in `operations.md`); the
14
- `archived/` subdirectory is scaffolded once, alongside `roadmaps/`, with a `.gitkeep`.
14
+ `archived/` subdirectory is scaffolded once, alongside `roadmaps/`, with a `.gitkeep`. Its
15
+ name-paired ledger, `roadmaps/{slug}.savepoint.md` (see Savepoint ledger below), moves alongside
16
+ it in the same Close/archive step.
15
17
 
16
18
  ## The four sections (in order)
17
19
 
@@ -63,6 +65,33 @@ that entry-intent's `outcome.md`:
63
65
  The log line never restates `outcome.md` detail; it points at it (lossless-by-reference). This
64
66
  complements, and does not replace, `INDEX.md`'s `## Completed` section or `CHANGELOG.md`.
65
67
 
68
+ ## Savepoint ledger
69
+
70
+ `roadmaps/{slug}.savepoint.md` is the name-paired sibling of `roadmaps/{slug}.md`: the machine
71
+ counterpart to the human `## Log`, moving to `roadmaps/archived/{slug}.savepoint.md` alongside its
72
+ roadmap on close (see Close/archive). It is created lazily by the first `append` call; there is no
73
+ template to scaffold.
74
+
75
+ Line shape, one event per line, append-only, newest at the bottom:
76
+
77
+ ```
78
+ <UTC-iso8601> <event> <detail>
79
+ ```
80
+
81
+ Two-space fields, mirroring the intent-dir cycle-step ledger (`savepoint.md`). The controlled event
82
+ vocabulary: `created`, `dispatched`, `parked`, `merged`, `release`, `handoff`, `closed`, and
83
+ optionally `added`, `reordered`, `wave`. The `(event, detail)` pair is the idempotency key, so
84
+ re-appending the same pair is a no-op.
85
+
86
+ `## Log` and the ledger record the same events in two voices: the Log is the dated, one-sentence,
87
+ EM-to-CTO-plain-language record a human reads cold; the ledger is the terse, machine-timestamped,
88
+ controlled-vocabulary record a coordinator reads at a glance. Both are append-only; neither edits
89
+ the other.
90
+
91
+ The ledger is derived and rebuildable (`ruby ~/.plastic/scripts/roadmap-savepoint rebuild --roadmap
92
+ roadmaps/{slug}.md`, reconstructing it from `## Log`), never a status source: `INDEX.md` stays the
93
+ single writer of intent status, exactly as for the roadmap file itself.
94
+
66
95
  ## Worked example
67
96
 
68
97
  ```
@@ -20,7 +20,10 @@ next" in under a minute, just from this one file.
20
20
  status mirroring that intent's current `INDEX.md` status.
21
21
  6. Append the first `## Log` line, a short `YYYY-MM-DD HH:MM UTC`-prefixed plain-language note
22
22
  that the roadmap was created.
23
- 7. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
23
+ 7. Append the ledger event (derived, idempotent, safe to re-run; never writes INDEX or roadmap
24
+ status; creates `roadmaps/<slug>.savepoint.md` lazily): `ruby ~/.plastic/scripts/roadmap-savepoint
25
+ append --roadmap roadmaps/<slug>.md --event created --detail "<slug>: <title>"`.
26
+ 8. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
24
27
  never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <roadmaps-dir> --async`.
25
28
 
26
29
  ## Add / reorder entries
@@ -34,6 +37,10 @@ next" in under a minute, just from this one file.
34
37
  entry is eligible to run.
35
38
  - After any add/reorder, append a `## Log` line describing the change (e.g.
36
39
  `- <YYYY-MM-DD HH:MM UTC> added 132 to wave 2`).
40
+ - Append the ledger event (derived, idempotent, safe to re-run; never writes INDEX or roadmap
41
+ status): `ruby ~/.plastic/scripts/roadmap-savepoint append --roadmap roadmaps/<slug>.md --event
42
+ added --detail "<id> to wave N"` for an add, or `--event reordered` for a reorder (describe the
43
+ move in `<detail>`).
37
44
  - Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
38
45
  never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <roadmaps-dir> --async`.
39
46
 
@@ -49,7 +56,12 @@ next" in under a minute, just from this one file.
49
56
  one-line EM-to-CTO entry described in `file-format.md` (date, what shipped and its impact in
50
57
  plain language, then a link to that intent's `outcome.md`). For other transitions, write a
51
58
  short dated plain-language line (no codenames, no jargon).
52
- 5. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
59
+ 5. Append the ledger event, only when the status token actually changed (derived, idempotent,
60
+ never writes INDEX or roadmap status): map the new status to its mechanized event (`delivered`
61
+ -> `merged`, `delivering` -> `dispatched`, `blocked`/`abandoned` -> `parked`), then `ruby
62
+ ~/.plastic/scripts/roadmap-savepoint append --roadmap roadmaps/<slug>.md --event <event>
63
+ --detail "<intent-id>"` (add a sha in `<detail>` when one is known).
64
+ 6. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
53
65
  never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <roadmaps-dir> --async`.
54
66
 
55
67
  ## Append a log line
@@ -60,6 +72,10 @@ next" in under a minute, just from this one file.
60
72
  A delivery event follows the EM-to-CTO one-line shape with an `outcome.md` link (see
61
73
  `file-format.md`); bookkeeping events (created, an intent added to a wave, a wave completed, a
62
74
  roadmap closed) are short dated plain-language lines.
75
+ - Append the matching ledger event (derived, idempotent, never writes INDEX or roadmap status):
76
+ when the line records a release cut, `ruby ~/.plastic/scripts/roadmap-savepoint append --roadmap
77
+ roadmaps/<slug>.md --event release --detail "<version>"`; otherwise append the mechanized event
78
+ matching the bookkeeping line just written (see the per-verb event mapping on this page).
63
79
  - Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
64
80
  never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <roadmaps-dir> --async`.
65
81
 
@@ -80,9 +96,13 @@ next" in under a minute, just from this one file.
80
96
  `abandoned` with a recorded reason, plus whatever else the goal states).
81
97
  2. Create `roadmaps/archived/` beside `roadmaps/` (both siblings of `INDEX.md`) if it does not
82
98
  exist yet.
83
- 3. Move the file: `roadmaps/{slug}.md` -> `roadmaps/archived/{slug}.md`. `roadmaps/` itself then
84
- lists only live (open or in-flight) roadmaps.
85
- 4. Append the final `## Log` line before or as part of the move:
99
+ 3. Append the ledger closed event, while the roadmap is still at its live path (derived,
100
+ idempotent, never writes INDEX or roadmap status): `ruby ~/.plastic/scripts/roadmap-savepoint
101
+ append --roadmap roadmaps/<slug>.md --event closed --detail "<slug>"`.
102
+ 4. Move BOTH files: `roadmaps/{slug}.md` -> `roadmaps/archived/{slug}.md` AND
103
+ `roadmaps/{slug}.savepoint.md` -> `roadmaps/archived/{slug}.savepoint.md`. `roadmaps/` itself
104
+ then lists only live (open or in-flight) roadmaps.
105
+ 5. Append the final `## Log` line before or as part of the move:
86
106
  `- <YYYY-MM-DD HH:MM UTC> roadmap closed`.
87
- 5. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
107
+ 6. Refresh the QMD index for this roadmap (no-op when QMD is absent), in the background so it
88
108
  never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <roadmaps-dir> --async`.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: plastic-roadmap-continuing
3
+ description: >-
4
+ Use when the user wants to continue or resume a roadmap, pick up a mid-flight delivery batch,
5
+ asks "where is the roadmap", or wants to resume the wave that was shipping, including an
6
+ indirect ask that never names a roadmap directly (for example "where did that batch of
7
+ tickets land"). This is the roadmap route of plastic-continuing: it finds the tier's
8
+ mid-flight roadmap, presents its state, then asks how to proceed exactly once.
9
+ user-invocable: true
10
+ ---
11
+
12
+ # Roadmap Continuing - resume the mid-flight roadmap
13
+
14
+ `plastic-roadmap-continuing` is the roadmap route of `plastic-continuing`. It finds the tier's
15
+ mid-flight roadmap, presents its state, then asks the user how to proceed, exactly once.
16
+
17
+ Before intent 158a1 there was no automated way to do this: the `171` (consistency-dividend)
18
+ roadmap handoff had to be resumed by hand, carried as a free-prose note in `171`'s own
19
+ `## Insights`. This skill closes that gap.
20
+
21
+ ## Find the mid-flight roadmap
22
+
23
+ 1. Determine the tier (project vs. global) and enumerate that tier's live `roadmaps/*.md`
24
+ (exclude `roadmaps/archived/`). Read via Read/glob, or `plastic-roadmap`'s Read/consume
25
+ verb. See `plastic-roadmap`'s `references/file-format.md` for the file grammar; do not
26
+ duplicate it here.
27
+ 2. For each candidate, also read its paired ledger `roadmaps/<slug>.savepoint.md` when present
28
+ (see `plastic-roadmap`'s `references/file-format.md#savepoint-ledger`): its last line(s) are a
29
+ cheaper, precise last-event signal (for example `dispatched 134` or `merged 172`), read
30
+ alongside the existing `## Waves`/`## Log` judgment. The ledger is read-only here, a derived
31
+ signal, never a new status field; INDEX.md stays the sole status writer.
32
+ 3. Rank liveness by calling the shared reader in which mode (one implementation across the
33
+ auto loop and this skill):
34
+
35
+ ```bash
36
+ ruby ~/.plastic/scripts/roadmap-next --roadmaps-dir <tier>/roadmaps --which
37
+ ```
38
+
39
+ Read `state` and the winning `roadmap`. When `state` is `tie`, `tie_candidates` lists the
40
+ equally-live roadmaps to present side by side; the single "auto or guided?" ask below
41
+ doubles as the resolution. `roadmap-next` liveness-ranks the same way this skill used to do
42
+ by eye (a `delivering` or `blocked` entry wins, else the newest ledger or `## Log`
43
+ timestamp), now deterministically. See `references/liveness-ranking.md` for the algorithm it
44
+ implements.
45
+ 4. A genuine tie (two candidates equally live) is presented to the user and resolved by the
46
+ single ask below, not silently picked.
47
+
48
+ ## Present state
49
+
50
+ Present the chosen roadmap's `## Goal`, the current wave with each entry's mirrored status, the
51
+ ledger's newest line(s) (the last mechanized event) alongside the newest `## Log` line, before any
52
+ ask, so the coordinator sees the machine last-event at a glance.
53
+
54
+ ## Ask once
55
+
56
+ Ask "auto or guided?" exactly once, after presenting state, mirroring
57
+ `plastic-intent-starting`'s single-ask contract:
58
+ - **guided** -> continue step by step with the user.
59
+ - **auto** -> hand off to `plastic-auto` to drive the next wave or entry.
60
+
61
+ Never re-ask. No new roadmap or INDEX status field is invented anywhere in this flow.
62
+
63
+ ## The shared reader
64
+
65
+ Liveness ranking and frontier selection live in one place, `scripts/lib/roadmap_queue.rb`
66
+ (intent 148), behind the `scripts/roadmap-next` CLI. This skill calls it in which mode rather
67
+ than ranking roadmaps in prose, so the auto loop (`plastic-auto`) and this skill share exactly
68
+ one implementation (no forked ranking logic). 158a1 originally added no script because the
69
+ benefit was marginal for a read-time judgment; the auto-loop consumer flipped that calculus by
70
+ making the ranking load-bearing and deterministic. The reader is read-only: it consumes the
71
+ roadmap `.md`, the 134 `<slug>.savepoint.md` ledger, and INDEX.md, and writes nothing. INDEX.md
72
+ stays the single status writer.
73
+
74
+ ## Caller contract: who writes the ledger
75
+
76
+ This skill is a reader, not a writer, of `roadmaps/<slug>.savepoint.md`. The coordinator flows
77
+ (`plastic-auto`, the enforcer, and this skill's own resume-and-hand-off path) call `ruby
78
+ ~/.plastic/scripts/roadmap-savepoint append` at their own dispatch, merge, park, handoff, and
79
+ release points, the same events `plastic-roadmap`'s verbs append at their closing steps. A
80
+ resuming coordinator therefore both reads the ledger here and writes to it as it drives the next
81
+ wave or entry.
82
+
83
+ ## References
84
+
85
+ - `references/liveness-ranking.md` - the read-time ranking algorithm and the tie rule.
@@ -0,0 +1,82 @@
1
+ {
2
+ "skill_name": "plastic-roadmap-continuing",
3
+ "notes": "Intent 158a1. New roadmap route, closing the gap that forced the 171 (consistency-dividend) roadmap handoff to be resumed by hand. Scopes: description triggering (1-3) and behavior/convention compliance (4-8), 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": 5, "passed": 5, "evidence": "convention checks against skills/roadmap-continuing/SKILL.md and references/liveness-ranking.md" }
7
+ },
8
+ "evals": [
9
+ {
10
+ "id": 1, "scope": "triggering", "set": "train",
11
+ "prompt": "continue the roadmap",
12
+ "expected_output": "Activates plastic-roadmap-continuing.",
13
+ "files": [],
14
+ "assertions": [
15
+ { "type": "code", "check": "router CHOICE == plastic-roadmap-continuing", "observed": "plastic-roadmap-continuing", "result": "pass" }
16
+ ]
17
+ },
18
+ {
19
+ "id": 2, "scope": "triggering", "set": "train",
20
+ "prompt": "resume the delivery batch we were shipping",
21
+ "expected_output": "Activates plastic-roadmap-continuing.",
22
+ "files": [],
23
+ "assertions": [
24
+ { "type": "code", "check": "router CHOICE == plastic-roadmap-continuing", "observed": "plastic-roadmap-continuing", "result": "pass" }
25
+ ]
26
+ },
27
+ {
28
+ "id": 3, "scope": "triggering", "set": "validation",
29
+ "prompt": "where did that batch of tickets land",
30
+ "expected_output": "Activates plastic-roadmap-continuing (indirect trigger: a roadmap-resume request that never names 'roadmap' or 'continue').",
31
+ "files": [],
32
+ "assertions": [
33
+ { "type": "code", "check": "router CHOICE == plastic-roadmap-continuing", "observed": "plastic-roadmap-continuing", "result": "pass" }
34
+ ]
35
+ },
36
+ {
37
+ "id": 4, "scope": "behavior", "set": "train",
38
+ "prompt": "Does the skill rank liveness correctly when a wave entry is delivering or blocked?",
39
+ "expected_output": "A roadmap with any delivering/blocked wave entry wins the ranking outright over every other candidate.",
40
+ "files": ["skills/roadmap-continuing/references/liveness-ranking.md"],
41
+ "assertions": [
42
+ { "type": "convention", "check": "delivering/blocked-wins rule documented as step 2 of the algorithm", "observed": "present", "result": "pass" }
43
+ ]
44
+ },
45
+ {
46
+ "id": 5, "scope": "behavior", "set": "train",
47
+ "prompt": "Does the skill fall back to newest Log date when no candidate is delivering/blocked?",
48
+ "expected_output": "Among remaining candidates, the most recent ## Log entry timestamp wins.",
49
+ "files": ["skills/roadmap-continuing/references/liveness-ranking.md"],
50
+ "assertions": [
51
+ { "type": "convention", "check": "newest-Log-date fallback documented as step 3", "observed": "present", "result": "pass" }
52
+ ]
53
+ },
54
+ {
55
+ "id": 6, "scope": "behavior", "set": "train",
56
+ "prompt": "Does the skill present the roadmap state before asking?",
57
+ "expected_output": "The '## Present state' section (Goal, current wave, newest Log line) runs before the '## Ask once' section.",
58
+ "files": ["skills/roadmap-continuing/SKILL.md"],
59
+ "assertions": [
60
+ { "type": "convention", "check": "Present state precedes Ask once in document order", "observed": "present", "result": "pass" }
61
+ ]
62
+ },
63
+ {
64
+ "id": 7, "scope": "behavior", "set": "validation",
65
+ "prompt": "Does the skill ask 'auto or guided?' exactly once?",
66
+ "expected_output": "Exactly one ask, after presenting state; never re-asked.",
67
+ "files": ["skills/roadmap-continuing/SKILL.md"],
68
+ "assertions": [
69
+ { "type": "convention", "check": "the literal phrase 'auto or guided' appears on exactly one line of SKILL.md", "observed": "one occurrence", "result": "pass" }
70
+ ]
71
+ },
72
+ {
73
+ "id": 8, "scope": "behavior", "set": "validation",
74
+ "prompt": "Does the skill invent a new roadmap or INDEX status field, or add a new script?",
75
+ "expected_output": "No. Liveness is computed at read time from existing files; INDEX.md stays the sole status writer; the enumeration/ranking is prose, not a helper script.",
76
+ "files": ["skills/roadmap-continuing/SKILL.md", "skills/roadmap-continuing/references/liveness-ranking.md"],
77
+ "assertions": [
78
+ { "type": "convention", "check": "no new field or script introduced; 'No new script' section present", "observed": "present", "result": "pass" }
79
+ ]
80
+ }
81
+ ]
82
+ }