@zalom/plastic 1.0.0-beta.8 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLASTIC-reference.md +133 -0
- package/PLASTIC.md +407 -84
- package/README.md +99 -82
- package/agents/plastic-brainstorming.md +12 -9
- package/agents/plastic-enforcer.md +52 -12
- package/agents/plastic-executor.md +10 -10
- package/agents/plastic-future-intent-researcher.md +4 -8
- package/agents/plastic-intent-curator.md +12 -14
- package/agents/plastic-intent-discovery.md +35 -0
- package/agents/plastic-planner.md +15 -10
- package/agents/plastic-spec-specialist.md +15 -10
- package/bin/plastic.js +7 -3
- package/hooks/code-gate +10 -1
- package/hooks/hooks.json +28 -3
- package/hooks/lock-gate +21 -0
- package/hooks/retrieval-gate +10 -0
- package/hooks/savepoint-pre +10 -0
- package/hooks/statusline +48 -6
- package/package.json +2 -1
- package/scripts/agent-report +21 -0
- package/scripts/dashboard.rb +98 -23
- package/scripts/doctor.rb +404 -5
- package/scripts/hook-auto-arm +1 -1
- package/scripts/hook-bash-gate +22 -4
- package/scripts/hook-code-gate +11 -6
- package/scripts/hook-continue +17 -0
- package/scripts/hook-create-gate +45 -9
- package/scripts/hook-gate-check +24 -23
- package/scripts/hook-lock-gate +83 -0
- package/scripts/hook-retrieval-gate +148 -0
- package/scripts/hook-savepoint-pre +32 -0
- package/scripts/hook-session-start +1 -1
- package/scripts/insight-append +51 -0
- package/scripts/install.rb +44 -7
- package/scripts/lib/agent_models.rb +43 -0
- package/scripts/lib/bridge.rb +924 -84
- package/scripts/lib/dashboard_banner.rb +42 -0
- package/scripts/lib/frontmatter_writer.rb +130 -0
- package/scripts/lib/graph_rebuild.rb +328 -0
- package/scripts/lib/hook_registry.rb +95 -0
- package/scripts/lib/insights.rb +86 -0
- package/scripts/lib/installer_core.rb +104 -60
- package/scripts/lib/link_suggestions.rb +319 -0
- package/scripts/lib/links_projection.rb +160 -0
- package/scripts/lib/links_section.rb +207 -0
- package/scripts/lib/lock.rb +375 -0
- package/scripts/lib/power_tools.rb +22 -21
- package/scripts/lib/preflight.rb +79 -0
- package/scripts/lib/qmd_sync.rb +15 -0
- package/scripts/lib/retrieval_gate.rb +211 -0
- package/scripts/lib/worktree.rb +384 -0
- package/scripts/link-suggest +213 -0
- package/scripts/new-intent +138 -29
- package/scripts/plastic-lock +164 -0
- package/scripts/project-links +287 -0
- package/scripts/read-config +4 -0
- package/scripts/rebuild-graph +244 -0
- package/scripts/spawn-preamble +11 -3
- package/scripts/update.rb +16 -7
- package/skills/auto/SKILL.md +126 -19
- package/skills/auto/references/agent-architecture.md +7 -4
- package/skills/auto/references/agent-report-contract.md +36 -1
- package/skills/auto/references/end-tail.md +56 -0
- package/skills/auto/references/human-report-contract.md +55 -0
- package/skills/auto/references/tiers.md +77 -0
- package/skills/brainstorming/SKILL.md +7 -34
- package/skills/brainstorming/references/design-principles.md +49 -0
- package/skills/continuing/SKILL.md +26 -7
- package/skills/creating-intent/SKILL.md +13 -28
- package/skills/creating-intent/references/lifecycle.md +9 -4
- package/skills/creating-project/SKILL.md +11 -74
- package/skills/creating-project/references/project-scaffolding.md +97 -0
- package/skills/creating-skills/SKILL.md +65 -0
- package/skills/creating-skills/evals/evals.json +108 -0
- package/skills/creating-skills/references/agents.md +168 -0
- package/skills/creating-skills/references/evals.md +41 -0
- package/skills/creating-skills/references/hooks.md +248 -0
- package/skills/creating-skills/references/progressive-disclosure.md +176 -0
- package/skills/creating-skills/references/scripts.md +166 -0
- package/skills/creating-skills/references/skills.md +165 -0
- package/skills/creating-skills/scripts/scaffold.rb +313 -0
- package/skills/dashboard/SKILL.md +13 -11
- package/skills/dashboard/references/classification.md +22 -0
- package/skills/doctor/SKILL.md +10 -7
- package/skills/executing-plan/SKILL.md +4 -4
- package/skills/humanizer/SKILL.md +39 -0
- package/skills/humanizer/evals/evals.json +70 -0
- package/skills/humanizer/references/always-on-snippet.md +9 -0
- package/skills/humanizer/references/examples.md +48 -0
- package/skills/install/SKILL.md +75 -84
- package/skills/intent-curator/SKILL.md +5 -1
- package/skills/intent-discovery/SKILL.md +47 -0
- package/skills/intent-starting/SKILL.md +130 -0
- package/skills/intent-starting/evals/evals.json +117 -0
- package/skills/intent-starting/references/boarding-matrix.md +35 -0
- package/skills/linking-intents/SKILL.md +48 -12
- package/skills/linking-intents/references/zettelkasten.md +7 -0
- package/skills/lock/SKILL.md +41 -0
- package/skills/managing-index/SKILL.md +6 -0
- package/skills/managing-index/references/zettelkasten-linking.md +6 -1
- package/skills/releasing/SKILL.md +44 -29
- package/skills/releasing/references/promotion-and-tagging.md +60 -0
- package/skills/roadmap/SKILL.md +51 -0
- package/skills/roadmap/references/file-format.md +91 -0
- package/skills/roadmap/references/operations.md +78 -0
- package/skills/uninstall/SKILL.md +29 -11
- package/skills/update/SKILL.md +34 -23
- package/skills/versions/SKILL.md +27 -12
- package/skills/writing-plans/SKILL.md +10 -88
- package/skills/writing-plans/references/plan-format.md +102 -0
- package/templates/config.yml +8 -0
- package/templates/outcome.md +3 -0
- package/templates/revisions.md +58 -0
- package/templates/roadmap.md +30 -0
- package/skills/writing-instructions/SKILL.md +0 -159
- package/skills/writing-instructions/references/agentskills-spec.md +0 -135
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plastic-intent-starting
|
|
3
|
+
description: >-
|
|
4
|
+
Board a session onto an intent: take the lock FIRST, confirm savepoint state, ask auto or
|
|
5
|
+
guided ONCE, then resume at the latest delivered station and run the cycle to Done. Use on
|
|
6
|
+
`continuing --intent {id}`, when a new intent is registered and the user asks to work it,
|
|
7
|
+
or when the user picks an intent to work. Requires the intent in INDEX `## Active`.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Intent Starting — board a session onto an intent
|
|
11
|
+
|
|
12
|
+
Announce: "Boarding intent [ID] — [name]."
|
|
13
|
+
|
|
14
|
+
`plastic-intent-starting` is the Start procedure. It boards a session onto one intent: take
|
|
15
|
+
the lock FIRST, confirm the delivery state, ask **auto or guided ONCE**, board at the latest
|
|
16
|
+
delivered station, then run the cycle to Done. The What → Why → How → Exec stations are the
|
|
17
|
+
train track; Start boards the train, the ending procedure (~93) exits it.
|
|
18
|
+
|
|
19
|
+
## Precondition + trigger
|
|
20
|
+
|
|
21
|
+
Fires when the user picks an intent to work, when an agent is told to continue a SPECIFIC
|
|
22
|
+
intent, or on `continuing --intent {id}` (the `continuing` → `starting` router is 106's job;
|
|
23
|
+
this skill is invokable standalone now).
|
|
24
|
+
|
|
25
|
+
If the intent is **terminal** (Done / Abandoned in INDEX): report only. Take NO lock, run NO
|
|
26
|
+
resume, do NOT reopen it. Summarize the outcome and ask what is next. Stop here.
|
|
27
|
+
|
|
28
|
+
## Lock FIRST (the spine)
|
|
29
|
+
|
|
30
|
+
The lock is non-negotiable and comes before any mutating work. The ACTION-3 lock-gate
|
|
31
|
+
enforces it: without a held lock, mutating writes to this active intent's dir are denied with
|
|
32
|
+
"run /plastic-intent-starting to lock and begin".
|
|
33
|
+
|
|
34
|
+
1. **Ensure the intent is in INDEX `## Active`.** If it sits in `## Future`, activate it
|
|
35
|
+
(move it to `## Active`, auto-commit) before arming. Creation precedes activation, so a
|
|
36
|
+
brand-new What intent is activated here, then locked.
|
|
37
|
+
2. **Self-heal the lock state first.** Run:
|
|
38
|
+
`ruby ~/.plastic/scripts/plastic-lock fix --intent-dir <STORE>/<dir>`
|
|
39
|
+
This is the one repair function (same one /plastic-lock exposes): it removes
|
|
40
|
+
corrupt or legacy lock state and rebuilds the lock and bridge from disk for
|
|
41
|
+
this session. If it reports `held`, another session owns the intent: STOP
|
|
42
|
+
and tell the user who holds it. If it reports `stale`, ask the user before
|
|
43
|
+
running `plastic-lock reclaim` (takeover is audited).
|
|
44
|
+
3. **Arm the bridge.** Which arm is chosen by the mode answer (below), but the lock itself is
|
|
45
|
+
taken first. Reuse the arm one-liner shape from `plastic-auto`:
|
|
46
|
+
```bash
|
|
47
|
+
# guided (lock only):
|
|
48
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
49
|
+
'Bridge.arm_guided(ENV["CLAUDE_CODE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
|
|
50
|
+
# auto (lock + auto), then hand to plastic-auto:
|
|
51
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
52
|
+
'Bridge.arm_auto(ENV["CLAUDE_CODE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
|
|
53
|
+
```
|
|
54
|
+
Replace `<ID>`, `<STORE>` (`~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
|
|
55
|
+
`<dir>` (the `ID--slug` directory), and `<name>`.
|
|
56
|
+
4. **Dispatch What-stage discovery (under the lock).** Right after arming, when the intent
|
|
57
|
+
was just activated in step 1 (on a resume that already has
|
|
58
|
+
`resources/discovery--<slug>.md`, skip: discovery runs once per intent, at activation
|
|
59
|
+
only), dispatch the `plastic-intent-discovery` agent (see the `plastic-intent-discovery`
|
|
60
|
+
skill), now that this session owns the lock, deposit authorized as the owner session. Resolve its
|
|
61
|
+
model explicitly and pass it at dispatch time (belt-and-braces): `read-config
|
|
62
|
+
agents.models.plastic-intent-discovery --project <repo>`. The agent runs QMD discovery
|
|
63
|
+
over the intent's `chain`/`sources` and deposits findings to
|
|
64
|
+
`resources/discovery--<slug>.md` only; it never writes the intent file, so the
|
|
65
|
+
lock-owner-only rule is untouched. This is advisory context for Why, not a gate: if
|
|
66
|
+
discovery yields nothing, proceed to Why normally.
|
|
67
|
+
|
|
68
|
+
**Session id resolution (verbatim from `plastic-auto`).** The first argument is the session
|
|
69
|
+
id the bridge is keyed by: pass the hook stdin `session_id` when you have it, otherwise
|
|
70
|
+
`ENV["CLAUDE_CODE_SESSION_ID"]`, otherwise `nil`. Both arms call `resolve_session`, which
|
|
71
|
+
picks the first non-empty of: the explicit id you pass → `CLAUDE_CODE_SESSION_ID` → a
|
|
72
|
+
deterministic derived key (a hash of the store and intent id). It never returns nil, so the
|
|
73
|
+
lock is taken even when every session env var is empty; arming prints a one-line stderr
|
|
74
|
+
notice when it falls through to the derived key.
|
|
75
|
+
|
|
76
|
+
**What the lock IS.** Ownership is session-keyed and lease-based: arming writes a durable
|
|
77
|
+
`delivery.lock` file in the intent dir naming this session as owner, and the owner's hooks
|
|
78
|
+
refresh the file mtime on tool activity (the lease heartbeat). The /tmp bridge is only a
|
|
79
|
+
cache of that file; on any disagreement the lock file wins, so a wiped /tmp never strands
|
|
80
|
+
the owner. Idempotent re-arm: arming again with the same owner just refreshes the lock; it
|
|
81
|
+
is not an error to re-board an intent this session already owns. A failed arm raises with
|
|
82
|
+
a message naming the resolving `plastic-lock` verb (`status`, `reclaim`, or `fix`): follow
|
|
83
|
+
that message, never delete a lock file by hand.
|
|
84
|
+
|
|
85
|
+
## Confirm delivery state
|
|
86
|
+
|
|
87
|
+
Read `savepoint.md` and classify from the **last line** alone, then verify ONLY that line's
|
|
88
|
+
artifact is real (sentinel-aware via `Bridge.stage_file_present?`). On drift (the last line
|
|
89
|
+
disagrees with files on disk), rebuild the ledger from disk and note the correction. Do not
|
|
90
|
+
inline the rebuild; the `plastic-savepoint` skill owns it:
|
|
91
|
+
```bash
|
|
92
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.rebuild_savepoint("<intent_dir>")'
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Report + ask "auto or guided?" ONCE
|
|
96
|
+
|
|
97
|
+
Report: the intent, the station it lands at (the matrix below), what is delivered, the next
|
|
98
|
+
step. Then ask the user **"auto or guided?"** — exactly ONCE, whatever station it lands at.
|
|
99
|
+
Never re-ask at a later station.
|
|
100
|
+
|
|
101
|
+
- **guided** → `arm_guided` (lock only); continue step by step with the user through the
|
|
102
|
+
station's work below.
|
|
103
|
+
- **auto** → `arm_auto` (lock + auto), then hand off to `plastic-auto`. The auto branch's
|
|
104
|
+
only remaining job is the handoff; `plastic-auto` runs the cycle from here.
|
|
105
|
+
|
|
106
|
+
## Board at the latest delivered station
|
|
107
|
+
|
|
108
|
+
The station is derived from `savepoint.md` last line + real artifacts on disk. See
|
|
109
|
+
`references/boarding-matrix.md` for the full table (last line → latest delivered → boards at →
|
|
110
|
+
continue with) and the per-station notes. Summary of what "continue" means per station:
|
|
111
|
+
|
|
112
|
+
- **What** → do what What requires (106-expanded), then brainstorm → `spec.md`.
|
|
113
|
+
- **Why** → continue brainstorming → `spec.md`.
|
|
114
|
+
- **How** → continue `plan.md` + `actions/` + `checklist.md`.
|
|
115
|
+
- **Exec** → verify what has been delivered, then continue (or restart) the delivery /
|
|
116
|
+
research; tick the checklist.
|
|
117
|
+
- **ready to complete** (`Exec outcome.md created`) → exit at Done.
|
|
118
|
+
- **Done** → report only, ask what is next, never reopen.
|
|
119
|
+
|
|
120
|
+
## Disarm / release on done
|
|
121
|
+
|
|
122
|
+
When delivery finishes, disarm and release per the `plastic-auto` disarm/release prose (do
|
|
123
|
+
not duplicate it here). The guided branch releases the lock via `disarm_auto`, which is
|
|
124
|
+
mode-agnostic (it sets `auto = false` and calls `Worktree.release`), so it releases a guided
|
|
125
|
+
lock too. When the work ships through a release, the release path merges the branch before the
|
|
126
|
+
worktree is removed; the plain disarm remove is only for the no-release case.
|
|
127
|
+
|
|
128
|
+
## References
|
|
129
|
+
|
|
130
|
+
- `references/boarding-matrix.md` — the full boarding table and per-station behaviour.
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "plastic-intent-starting",
|
|
3
|
+
"notes": "Intent 96. Scopes: description triggering (1-5) and behavior (6-10: lock-first spine, ask-mode-once, terminal report-only, mode->arm mapping, boarding matrix). The lock-first and gate behaviors are also proven by Ruby tests (test/bridge_guided_test.rb, test/lock_gate_test.rb, test/lock_gate_hook_test.rb).",
|
|
4
|
+
"evals": [
|
|
5
|
+
{
|
|
6
|
+
"id": 1,
|
|
7
|
+
"scope": "triggering",
|
|
8
|
+
"set": "train",
|
|
9
|
+
"prompt": "start work on intent 96",
|
|
10
|
+
"expected_output": "Activates plastic-intent-starting (board a session onto the named intent).",
|
|
11
|
+
"files": [],
|
|
12
|
+
"assertions": [
|
|
13
|
+
{ "type": "code", "check": "router CHOICE == plastic-intent-starting", "result": "expect-pass" }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": 2,
|
|
18
|
+
"scope": "triggering",
|
|
19
|
+
"set": "train",
|
|
20
|
+
"prompt": "continuing --intent 96",
|
|
21
|
+
"expected_output": "Activates plastic-intent-starting (the --intent form boards a specific intent).",
|
|
22
|
+
"files": [],
|
|
23
|
+
"assertions": [
|
|
24
|
+
{ "type": "code", "check": "router CHOICE == plastic-intent-starting", "result": "expect-pass" }
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": 3,
|
|
29
|
+
"scope": "triggering",
|
|
30
|
+
"set": "validation",
|
|
31
|
+
"prompt": "work this intent",
|
|
32
|
+
"expected_output": "Activates plastic-intent-starting when an intent is in context to be worked.",
|
|
33
|
+
"files": [],
|
|
34
|
+
"assertions": [
|
|
35
|
+
{ "type": "code", "check": "router CHOICE == plastic-intent-starting", "result": "expect-pass" }
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
"id": 4,
|
|
40
|
+
"scope": "triggering",
|
|
41
|
+
"set": "validation",
|
|
42
|
+
"prompt": "continue",
|
|
43
|
+
"expected_output": "Does NOT activate plastic-intent-starting; bare 'continue' is plastic-continuing (no specific intent named).",
|
|
44
|
+
"files": [],
|
|
45
|
+
"assertions": [
|
|
46
|
+
{ "type": "code", "check": "router CHOICE != plastic-intent-starting", "result": "expect-pass" }
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"id": 5,
|
|
51
|
+
"scope": "triggering",
|
|
52
|
+
"set": "validation",
|
|
53
|
+
"prompt": "create a new intent for the uploader retry policy",
|
|
54
|
+
"expected_output": "Does NOT activate plastic-intent-starting; activates plastic-creating-intent.",
|
|
55
|
+
"files": [],
|
|
56
|
+
"assertions": [
|
|
57
|
+
{ "type": "code", "check": "router CHOICE != plastic-intent-starting", "result": "expect-pass" }
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": 6,
|
|
62
|
+
"scope": "behavior",
|
|
63
|
+
"set": "train",
|
|
64
|
+
"prompt": "Active intent X exists at the Why station. Start work on it (guided).",
|
|
65
|
+
"expected_output": "Takes the lock FIRST via Bridge.arm_guided (auto stays false) before any mutating work, confirms savepoint, then boards at Why and continues brainstorming toward spec.md.",
|
|
66
|
+
"files": [],
|
|
67
|
+
"assertions": [
|
|
68
|
+
{ "type": "human", "check": "lock acquired before any mutating write; SKILL.md 'Lock FIRST' section precedes station work", "result": "expect-pass" },
|
|
69
|
+
{ "type": "code", "check": "Bridge.arm_guided stamps the lock with auto=false (test/bridge_guided_test.rb green); lock-gate denies a no-lock write to the active intent dir (test/lock_gate_hook_test.rb green)", "result": "pass" }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": 7,
|
|
74
|
+
"scope": "behavior",
|
|
75
|
+
"set": "train",
|
|
76
|
+
"prompt": "Start work on an active intent without saying auto or guided.",
|
|
77
|
+
"expected_output": "Asks 'auto or guided?' exactly once after reporting the station; never re-asks at later stations.",
|
|
78
|
+
"files": [],
|
|
79
|
+
"assertions": [
|
|
80
|
+
{ "type": "human", "check": "SKILL.md asks the mode exactly once and states 'never re-asked at a later station'", "result": "expect-pass" }
|
|
81
|
+
]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": 8,
|
|
85
|
+
"scope": "behavior",
|
|
86
|
+
"set": "train",
|
|
87
|
+
"prompt": "Start work on an intent that is Done in INDEX.",
|
|
88
|
+
"expected_output": "Reports only: no lock, no resume, no reopen. Summarizes the outcome and asks what is next.",
|
|
89
|
+
"files": [],
|
|
90
|
+
"assertions": [
|
|
91
|
+
{ "type": "human", "check": "terminal intent -> report only, no arm call", "result": "expect-pass" }
|
|
92
|
+
]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": 9,
|
|
96
|
+
"scope": "behavior",
|
|
97
|
+
"set": "validation",
|
|
98
|
+
"prompt": "Start work and choose auto.",
|
|
99
|
+
"expected_output": "guided -> Bridge.arm_guided; auto -> Bridge.arm_auto then hand off to plastic-auto (auto branch's only remaining job is the handoff).",
|
|
100
|
+
"files": [],
|
|
101
|
+
"assertions": [
|
|
102
|
+
{ "type": "human", "check": "SKILL.md maps guided->arm_guided and auto->arm_auto+handoff to plastic-auto", "result": "expect-pass" }
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"id": 10,
|
|
107
|
+
"scope": "behavior",
|
|
108
|
+
"set": "validation",
|
|
109
|
+
"prompt": "Start an intent whose savepoint last line is 'How checklist.md created'.",
|
|
110
|
+
"expected_output": "Boards at Exec (verify plan + checklist real), per references/boarding-matrix.md, and continues delivery/ticks the checklist.",
|
|
111
|
+
"files": [],
|
|
112
|
+
"assertions": [
|
|
113
|
+
{ "type": "human", "check": "boarding matrix lands 'How checklist.md created' / 'Exec started' at Exec", "result": "expect-pass" }
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
]
|
|
117
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Boarding matrix — which station Start drops you at
|
|
2
|
+
|
|
3
|
+
The station is derived from `savepoint.md`'s last line plus the real artifacts on disk.
|
|
4
|
+
Classify from the last line alone, then verify ONLY that line's artifact is real
|
|
5
|
+
(sentinel-aware). On drift, rebuild the ledger from disk and note it.
|
|
6
|
+
|
|
7
|
+
| savepoint last line | latest delivered | boards at | continue with |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| `What {id}--{slug}.md` (born) | What | **What / Why** | What work (106-expanded), then brainstorm → `spec.md` |
|
|
10
|
+
| `Why started` (spec still sentinel) | What | **Why** | continue brainstorming → `spec.md` |
|
|
11
|
+
| `Why spec.md created` | Why | **How** | `plan.md` + `actions/` + `checklist.md` |
|
|
12
|
+
| `How started` / `How plan.md created` | (How in progress) | **How** | finish `plan.md` → `checklist.md` |
|
|
13
|
+
| `How checklist.md created` / `Exec started` | How | **Exec** | implement, tick the checklist |
|
|
14
|
+
| `Exec outcome.md created` | Exec | **ready to complete** | exit at Done |
|
|
15
|
+
| `Done delivered` / `Done abandoned` | terminal | **report only** | immutable; ask what is next |
|
|
16
|
+
|
|
17
|
+
## Per-station behaviour (what "continue" means)
|
|
18
|
+
|
|
19
|
+
- **What** → do what What requires (to be expanded in 106), then brainstorm → `spec.md`.
|
|
20
|
+
- **Why** → continue brainstorming; deliver `spec.md`.
|
|
21
|
+
- **How** → continue `plan.md` + `actions/` + `checklist.md`.
|
|
22
|
+
- **Exec** → verify what has been delivered, then continue (or restart) the delivery /
|
|
23
|
+
research. The first unchecked `checklist.md` item is the next step; the newest `## Insights`
|
|
24
|
+
entry supplies human-readable context.
|
|
25
|
+
- **ready to complete** → `outcome.md` is real; run the ending procedure (~93).
|
|
26
|
+
- **Done** → terminal. Report the outcome, ask what is next. Never reopen; INDEX is
|
|
27
|
+
authoritative.
|
|
28
|
+
|
|
29
|
+
## Notes
|
|
30
|
+
|
|
31
|
+
- The mode (auto / guided) is asked exactly ONCE, whatever station Start lands at. It is never
|
|
32
|
+
re-asked at a later station. The lock is taken FIRST regardless of station (terminal intents
|
|
33
|
+
excepted: they get no lock and no resume).
|
|
34
|
+
- An `## Insights` entry marked `(autonomous)` means the intent was being delivered
|
|
35
|
+
autonomously; in guided mode, surface that and offer to hand back to `plastic-auto`.
|
|
@@ -6,34 +6,70 @@ description: Use when creating connections between intents, the user says "link"
|
|
|
6
6
|
# Linking Intents
|
|
7
7
|
|
|
8
8
|
## When to Use
|
|
9
|
-
- During intent creation (automatic
|
|
9
|
+
- During intent creation (automatic: ask about related intents)
|
|
10
10
|
- User says "link", "connect", "relates to"
|
|
11
11
|
- Agent discovers a relationship between intents during work
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## Discovery and ranking are separate
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
Explicit wikilinks in the `## Links` section. Bidirectional — add to both intents.
|
|
15
|
+
Two distinct steps, do not conflate them:
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
1. **Discovery** (finding candidate related intents) may use any tool: grep, find, ripgrep,
|
|
18
|
+
or QMD/Serena when present (QMD-first per the project rule). Discovery casts a wide net.
|
|
19
|
+
2. **Ranking** the candidates is a CONTEXT-INFLUENCE judgement: read each candidate's `## Intent`
|
|
20
|
+
and `## Context` and ask whether that context actually informed this intent. Ranking is NOT a
|
|
21
|
+
structural metric (no shared-file or shared-symbol grading: on intent 90, matching whole files
|
|
22
|
+
flagged 35 intents because ~20 touch `bridge.rb`). It is NOT a similarity score either (QMD
|
|
23
|
+
relevance measures topic proximity, not influence). A script cannot make this call; an agent
|
|
24
|
+
does.
|
|
25
|
+
|
|
26
|
+
## The three tiers (by context influence)
|
|
27
|
+
|
|
28
|
+
- **sources:** the foundational context that shaped this intent's CREATION (a split, an idea born
|
|
29
|
+
during development, a merge). Earns an edge. Decided by origin, never inferred.
|
|
30
|
+
- **chain:** the context that materially helps DELIVER this intent. HIGH bar: only the genuinely
|
|
31
|
+
delivery-moving intents, not everything in the same area. Earns an edge, reflected in `## Links`.
|
|
32
|
+
Worked example (intent 90): 79 created it so 79 is a source; 80 deferred the exact fix 90 makes,
|
|
33
|
+
so its context directly helps delivery and 80 is chain; 49/66/73 are same-area background, so
|
|
34
|
+
they get a shared tag and no link.
|
|
35
|
+
- **tags:** loose theme grouping for search. NOT a link.
|
|
36
|
+
|
|
37
|
+
**Timing.** The influence judgement happens at What/Why (and during upkeep), guided by this rule.
|
|
38
|
+
It does not wait for code to exist; it is reasoning over the candidate's context, not over a diff.
|
|
39
|
+
|
|
40
|
+
**Record the call.** For every edge an agent adds, store a rating (high / medium / low) plus a
|
|
41
|
+
one-line reason as a dated line under the intent file's `## Insights` section (per 96 D3, link
|
|
42
|
+
rationale lives in the intent file, not a side file). It stays out of frontmatter (graph only) and
|
|
43
|
+
out of the projected `## Links` label, so the audit trail never breaks the projection identity.
|
|
44
|
+
|
|
45
|
+
## `## Links` is derived (never author it by hand)
|
|
46
|
+
|
|
47
|
+
`## Links` is a DERIVED view of `sources` then `chain`, not a place to write links. Never
|
|
48
|
+
hand-write a `## Links` line, and never auto-delete one. To add a link, add the frontmatter
|
|
49
|
+
edge (below), then let the projection regenerate the section (`scripts/project-links`).
|
|
50
|
+
|
|
51
|
+
Run `scripts/link-suggest <id>` to gather candidate intents WITH each one's Intent and Context (the
|
|
52
|
+
evidence you judge influence on) and to flag drift (a `## Links` line with no frontmatter edge
|
|
53
|
+
behind it). To record a confirmed edge plus its rating and reason, run it with
|
|
54
|
+
`--record <id> --edge <sources|chain> --rating <high|medium|low> --reason "..." --confirm`. It never
|
|
55
|
+
grades influence itself, never writes an edge without `--confirm`, and never deletes.
|
|
56
|
+
|
|
57
|
+
## Connection Types (the frontmatter edges)
|
|
22
58
|
|
|
23
|
-
###
|
|
59
|
+
### 1. Sources (Backward)
|
|
24
60
|
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
61
|
```yaml
|
|
26
62
|
sources: ["1a", "1a2"]
|
|
27
63
|
```
|
|
28
64
|
|
|
29
|
-
###
|
|
65
|
+
### 2. Chain (Forward)
|
|
30
66
|
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
67
|
```yaml
|
|
32
68
|
chain: ["1b1", "1b2"]
|
|
33
69
|
```
|
|
34
70
|
|
|
35
|
-
###
|
|
36
|
-
Shared tags in frontmatter enable filtered discovery. Use `project-<name>` tags for project membership.
|
|
71
|
+
### 3. Tags (for discovery, not links)
|
|
72
|
+
Shared tags in frontmatter enable filtered discovery. Use `project-<name>` tags for project membership. A shared tag is a loose theme grouping: it earns NO edge.
|
|
37
73
|
```yaml
|
|
38
74
|
tags: [plastic, project-reddit-kb]
|
|
39
75
|
```
|
|
@@ -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:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lock
|
|
3
|
+
description: Inspect, repair, release, or reclaim an intent's delivery lock. Use when a lock-gate deny names /plastic-lock, when resuming interrupted work after a crash, reboot, or /tmp wipe, when a lock reads held or stale, or when the user says "fix the lock", "who holds the lock", or "reclaim the lock".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Plastic Lock
|
|
7
|
+
|
|
8
|
+
Command-only wrapper around `~/.plastic/scripts/plastic-lock`. The durable
|
|
9
|
+
delivery lock is a `delivery.lock` file in the intent directory: ownership is
|
|
10
|
+
session-keyed, liveness is a lease (the owner's hooks refresh the file mtime;
|
|
11
|
+
stale means the heartbeat is older than the TTL). The /tmp bridge is only a
|
|
12
|
+
cache; the lock file wins every disagreement.
|
|
13
|
+
|
|
14
|
+
## Verbs
|
|
15
|
+
|
|
16
|
+
Run from the project (the intent resolves from this session's bridge), or pass
|
|
17
|
+
`--intent-dir` explicitly:
|
|
18
|
+
|
|
19
|
+
| Verb | What it does | When |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `status` | Report the lock file, bridge cache, freshness, agreement | Always safe; run first |
|
|
22
|
+
| `fix` | Idempotent repair: rebuild lock + bridge from disk truth for THIS session. Never touches a fresh foreign lock | Interrupted work, corrupted state, /tmp wiped, legacy pid locks |
|
|
23
|
+
| `release` | Owner clears the lock | Ending or abandoning a boarding |
|
|
24
|
+
| `reclaim` | Explicit takeover of a STALE lock; appends an audit line to savepoint.md | The owner is gone and the lease expired |
|
|
25
|
+
| `delegate` | Owner registers a subagent session under the lock (`--delegate <session-id>`) | Auto-mode orchestration |
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
ruby ~/.plastic/scripts/plastic-lock status
|
|
29
|
+
ruby ~/.plastic/scripts/plastic-lock fix --intent-dir <store>/<id>--<slug>
|
|
30
|
+
ruby ~/.plastic/scripts/plastic-lock reclaim --intent-dir <store>/<id>--<slug>
|
|
31
|
+
ruby ~/.plastic/scripts/plastic-lock delegate --delegate <subagent-session-id>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Rules
|
|
35
|
+
|
|
36
|
+
- `fix` exits non-zero when another session holds a FRESH lock: back off, do
|
|
37
|
+
not retry in a loop. `status` shows the owner.
|
|
38
|
+
- `reclaim` refuses a fresh lock. There is no silent reclaim anywhere; every
|
|
39
|
+
takeover is audited in the intent's savepoint.md.
|
|
40
|
+
- Acquiring a lock for new work is NOT this skill's job: board through
|
|
41
|
+
`/plastic-intent-starting`, which calls the same repair internally.
|
|
@@ -41,6 +41,12 @@ Topic-based groupings. Manually curated. Create a new cluster when 3+ intents sh
|
|
|
41
41
|
### Completed
|
|
42
42
|
All completed intents with dates. Links preserved, never deleted.
|
|
43
43
|
|
|
44
|
+
When you move an intent INTO Completed or Abandoned, run the closing acts of the transfer in the canonical End-tail order (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`):
|
|
45
|
+
|
|
46
|
+
1. Author a real `outcome.md` in the intent directory from `~/.plastic/templates/outcome.md`, with the frontmatter `disposition: delivered` for a completed intent or `disposition: abandoned` for an abandoned one. `outcome.md` is MANDATORY at every terminal, delivered and abandoned alike: on abandon it records the abandonment reason and replaces the scaffolded placeholder sentinel (never leave `outcome.md` a placeholder at a terminal).
|
|
47
|
+
2. Stamp the terminal savepoint bookend (intent 81), so the ledger's last line records the disposition: `ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.append_terminal_savepoint("<intent_dir>", "delivered")'` (use `"abandoned"` for an abandoned intent). Idempotent.
|
|
48
|
+
3. Refresh the QMD index for that store LAST, after the terminal move and savepoint (no-op when QMD is absent), in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`.
|
|
49
|
+
|
|
44
50
|
## Workflow
|
|
45
51
|
|
|
46
52
|
QMD-first (when available): when you need to locate a specific intent (to reclassify, flag, or
|
|
@@ -8,7 +8,12 @@
|
|
|
8
8
|
|
|
9
9
|
## Three Connection Types (Ranked)
|
|
10
10
|
|
|
11
|
-
1. **Direct links** (strongest)
|
|
11
|
+
1. **Direct links** (strongest): wikilinks in the `## Links` section, the projection of the
|
|
12
|
+
frontmatter graph. Each entry is `- [[id--slug|<target's full intent: text>]]` (cross-store:
|
|
13
|
+
`- [[store:id--slug|...]]`), a clickable `id--slug` target labeled with the target's full
|
|
14
|
+
`intent:` text. Ordering is mandatory: all `sources` first (top), then all `chain`,
|
|
15
|
+
frontmatter order preserved within each group. Sources never appear at the end. No
|
|
16
|
+
source/chain tags, no sub-grouping.
|
|
12
17
|
2. **Sources/Chain** (knowledge graph): `sources` = direct ascendants this was created from (formation, acyclic, must-load); `chain` = forward continuations and related successors (may cycle, lighter context). See `docs/concepts/how-plastic-sources-and-chains-intents.md` for the full model.
|
|
13
18
|
3. **Tags** (weakest) — shared tags, `project-<name>` for project membership
|
|
14
19
|
|
|
@@ -20,6 +20,7 @@ Project configuration drives the workflow - no hardcoded assumptions.
|
|
|
20
20
|
- [ ] Run post-push actions (GitHub release, npm publish, etc.)
|
|
21
21
|
- [ ] Verify release sync (npm dist-tag, GitHub "Latest", git tag all show the new version)
|
|
22
22
|
- [ ] Complete active intent
|
|
23
|
+
- [ ] Clean up the intent's worktrees (merge-then-remove)
|
|
23
24
|
|
|
24
25
|
## Workflow
|
|
25
26
|
|
|
@@ -84,6 +85,12 @@ git merge <branch-name> --no-ff -m "feat: merge intent [ID] - [description]"
|
|
|
84
85
|
|
|
85
86
|
Always `--no-ff` to preserve branch history in the merge commit.
|
|
86
87
|
|
|
88
|
+
**Worktree-isolated intents (intent 73c3).** A worktree-delivered intent's code lives on
|
|
89
|
+
`plastic/{id}--{slug}`, merged together with cleanup in step 9, not on a hand-made feature
|
|
90
|
+
branch. Do not delete the worktree before its branch is merged, or the work is lost. For
|
|
91
|
+
the full rationale and the already-merged-by-hand no-op case, read
|
|
92
|
+
`references/promotion-and-tagging.md`.
|
|
93
|
+
|
|
87
94
|
### 4. Bump Version
|
|
88
95
|
|
|
89
96
|
Determine which files to update from project.yml:
|
|
@@ -94,8 +101,20 @@ Determine which files to update from project.yml:
|
|
|
94
101
|
|
|
95
102
|
Update the version string in each file, then commit:
|
|
96
103
|
|
|
104
|
+
**Cut the CHANGELOG entry.** Before committing, edit `CHANGELOG.md` at the repo root so
|
|
105
|
+
the changelog change rides this same version-bump commit and reaches the tag. Write one
|
|
106
|
+
line in the existing shape:
|
|
107
|
+
|
|
108
|
+
`` `<version>` - shipped <date>; collected <intent-id> (<one-line summary>) ``
|
|
109
|
+
|
|
110
|
+
Prepend it as the first bullet under `## Released` (newest-first). If this version was
|
|
111
|
+
sitting under `## Unreleased`, move it out of that section and into `## Released`. Keep
|
|
112
|
+
the line intent-centric narrative (which intents the cut collected and why), NOT commit
|
|
113
|
+
detail: step 5's tag-message changelog and step 7's `gh release create --generate-notes`
|
|
114
|
+
already own the commit-level detail, so do not duplicate it here.
|
|
115
|
+
|
|
97
116
|
```bash
|
|
98
|
-
git add <version-files>
|
|
117
|
+
git add <version-files> CHANGELOG.md
|
|
99
118
|
git commit -m "chore: bump version to X.Y.Z - [one-line summary]"
|
|
100
119
|
```
|
|
101
120
|
|
|
@@ -207,6 +226,27 @@ A release IS a delivery. The active intent that drove this work must be complete
|
|
|
207
226
|
|
|
208
227
|
**If no active intent exists for this release**, that itself is a problem - work happened outside the intent system. Log it and move on, but flag it.
|
|
209
228
|
|
|
229
|
+
### 9. Clean Up the Intent's Worktrees (merge-then-remove)
|
|
230
|
+
|
|
231
|
+
A release is the merge-then-remove path for the intent's worktrees (intent 73c3): the
|
|
232
|
+
intent's code branch is merged back into the default branch BEFORE the worktree is removed.
|
|
233
|
+
Drive it through `Worktree.finish` with `merge: true`, which merges the code branch, then
|
|
234
|
+
removes both worktrees (code + paired store), prunes both repos, and clears the worktree block
|
|
235
|
+
from the bridge:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
ruby -r ~/.plastic/scripts/lib/worktree -r ~/.plastic/scripts/lib/bridge -e \
|
|
239
|
+
'b = Bridge.discover_bridge(session: ENV["CLAUDE_CODE_SESSION_ID"], cwd: Dir.pwd); Worktree.finish(b, merge: true) if b'
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
(Uses `discover_bridge`, not a bare session-keyed `Bridge.read`, because a session can own more
|
|
243
|
+
than one live bridge now — intent 131 — and `discover_bridge` resolves the right one for this cwd.)
|
|
244
|
+
|
|
245
|
+
Honor the worktree-cleanup rule: never leave an orphaned worktree, and run `git worktree
|
|
246
|
+
prune` in the affected repo if you hit a stale reference. For why this is the one place the
|
|
247
|
+
merge-vs-remove policy lands on merge, and the fail-open/idempotent guarantees of `finish`,
|
|
248
|
+
read `references/promotion-and-tagging.md`.
|
|
249
|
+
|
|
210
250
|
## Conventions
|
|
211
251
|
|
|
212
252
|
- **Annotated tags only** - `git tag -a`, never lightweight tags
|
|
@@ -219,34 +259,9 @@ A release IS a delivery. The active intent that drove this work must be complete
|
|
|
219
259
|
- **Verify sync** - after pushing, confirm npm dist-tag, GitHub "Latest", and the git tag all show the new version
|
|
220
260
|
- **Branch cleanup** - delete merged feature branches: `git branch -d <branch>`
|
|
221
261
|
|
|
222
|
-
## Promotion
|
|
223
|
-
|
|
224
|
-
To promote a release across channels, use `--promote`:
|
|
225
|
-
|
|
226
|
-
```bash
|
|
227
|
-
plastic-releasing --promote beta # promotes current alpha → beta
|
|
228
|
-
plastic-releasing --promote stable # promotes current beta → stable
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
**Promotion rules:**
|
|
232
|
-
- Linear only: alpha → beta → stable. Cannot skip channels.
|
|
233
|
-
- `--promote beta`: reads version from `package.json`, changes `-alpha.N` suffix
|
|
234
|
-
to `-beta.1`, publishes with `--tag beta`.
|
|
235
|
-
- `--promote stable`: reads version from `package.json`, strips pre-release suffix
|
|
236
|
-
entirely (e.g., `1.0.0-beta.3` → `1.0.0`), publishes to `latest`.
|
|
237
|
-
- Version files are bumped and committed as in a normal release.
|
|
238
|
-
- An annotated tag is created for the promoted version.
|
|
239
|
-
|
|
240
|
-
## Retroactive Tagging
|
|
241
|
-
|
|
242
|
-
For repos without prior tags, tag historical releases:
|
|
243
|
-
|
|
244
|
-
```bash
|
|
245
|
-
git tag -a v0.1.0 <commit-sha> -m "v0.1.0 - [description]"
|
|
246
|
-
```
|
|
247
|
-
|
|
248
|
-
Use `git log --oneline` to find the right commits (look for version bump commits or major feature merges).
|
|
249
|
-
|
|
250
262
|
## References
|
|
251
263
|
|
|
264
|
+
- When promoting a pre-release across channels (`--promote beta`/`--promote stable`) or
|
|
265
|
+
tagging a historical release retroactively, read `references/promotion-and-tagging.md`
|
|
266
|
+
for the exact commands and rules first
|
|
252
267
|
- Read `references/deprecations.md` for the full deprecation process, severity levels, deprecations.yml schema, and dismissal rules when adding or managing deprecations
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Promotion, Retroactive Tagging, and Worktree Merge Rationale
|
|
2
|
+
|
|
3
|
+
Occasional variant paths off the main release workflow: promoting a pre-release
|
|
4
|
+
across channels, tagging historical releases retroactively, and the deep rationale
|
|
5
|
+
for why the intent's worktree is merged before removal.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Worktree merge-then-remove rationale](#worktree-merge-then-remove-rationale)
|
|
10
|
+
- [Promotion](#promotion)
|
|
11
|
+
- [Retroactive Tagging](#retroactive-tagging)
|
|
12
|
+
|
|
13
|
+
## Worktree merge-then-remove rationale
|
|
14
|
+
|
|
15
|
+
**Worktree-isolated intents (intent 73c3).** When the intent was delivered in a Plastic
|
|
16
|
+
worktree (the bridge has a provisioned `worktree` block), its code lives on the branch
|
|
17
|
+
`plastic/{id}--{slug}` inside `<repo>/.claude/worktrees/{id}--{slug}`, not on a hand-made
|
|
18
|
+
feature branch. The merge-then-remove of that worktree is handled together with cleanup in
|
|
19
|
+
Workflow step 9, which merges `plastic/{id}--{slug}` into the default branch BEFORE removing the
|
|
20
|
+
worktree. If you already merged here by hand, step 9 is a clean no-op merge ("Already up to
|
|
21
|
+
date") and proceeds straight to removal. Do not delete the worktree before its branch is
|
|
22
|
+
merged, or the work is lost.
|
|
23
|
+
|
|
24
|
+
A release is the merge-then-remove path for the intent's worktrees. This is the one place
|
|
25
|
+
the merge-vs-remove policy lands on "merge": the intent's code branch (`plastic/{id}--{slug}`)
|
|
26
|
+
is merged back into the repo's default branch BEFORE the worktree is removed, so the
|
|
27
|
+
integrated work is never lost. (The disarm path in `plastic-auto`, by contrast, is a plain
|
|
28
|
+
remove because no release is merging the branch.)
|
|
29
|
+
|
|
30
|
+
`Worktree.finish` is fail-open and idempotent: a conflicting merge is aborted and logged (the
|
|
31
|
+
worktree is still removed rather than stranded), and a second call with the block already
|
|
32
|
+
cleared is a no-op.
|
|
33
|
+
|
|
34
|
+
## Promotion
|
|
35
|
+
|
|
36
|
+
To promote a release across channels, use `--promote`:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
plastic-releasing --promote beta # promotes current alpha → beta
|
|
40
|
+
plastic-releasing --promote stable # promotes current beta → stable
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Promotion rules:**
|
|
44
|
+
- Linear only: alpha → beta → stable. Cannot skip channels.
|
|
45
|
+
- `--promote beta`: reads version from `package.json`, changes `-alpha.N` suffix
|
|
46
|
+
to `-beta.1`, publishes with `--tag beta`.
|
|
47
|
+
- `--promote stable`: reads version from `package.json`, strips pre-release suffix
|
|
48
|
+
entirely (e.g., `1.0.0-beta.3` → `1.0.0`), publishes to `latest`.
|
|
49
|
+
- Version files are bumped and committed as in a normal release.
|
|
50
|
+
- An annotated tag is created for the promoted version.
|
|
51
|
+
|
|
52
|
+
## Retroactive Tagging
|
|
53
|
+
|
|
54
|
+
For repos without prior tags, tag historical releases:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git tag -a v0.1.0 <commit-sha> -m "v0.1.0 - [description]"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `git log --oneline` to find the right commits (look for version bump commits or major feature merges).
|