@zalom/plastic 1.4.0 → 1.5.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 +2 -0
- package/PLASTIC.md +160 -42
- package/agents/plastic-intent-curator.md +10 -2
- package/package.json +1 -1
- package/scripts/codex-hook +122 -8
- package/scripts/dashboard.rb +323 -71
- package/scripts/doctor.rb +271 -15
- package/scripts/end-intent +32 -7
- package/scripts/hook-lock-gate +8 -3
- package/scripts/install.rb +51 -6
- package/scripts/lib/bridge.rb +79 -30
- package/scripts/lib/hook_registry.rb +44 -2
- package/scripts/lib/installer_core.rb +45 -6
- package/scripts/lib/lock.rb +186 -11
- package/scripts/lib/maintenance_git.rb +94 -0
- package/scripts/lib/revisions_writer.rb +69 -0
- package/scripts/lib/worktree.rb +14 -32
- package/scripts/lib/worktree_sweep.rb +129 -0
- package/scripts/maintenance-run +236 -0
- package/scripts/plastic-lock +76 -9
- package/scripts/project-links +127 -24
- package/scripts/rebuild-graph +37 -3
- package/scripts/restore-intent-v1 +37 -3
- package/scripts/sweep-store-worktrees +53 -0
- package/skills/auto/SKILL.md +29 -6
- package/skills/auto/references/agent-architecture.md +7 -0
- package/skills/auto/references/end-tail.md +8 -6
- package/skills/dashboard/SKILL.md +48 -25
- package/skills/dashboard/evals/evals.json +4 -4
- package/skills/dashboard/templates/dashboard-global.md +3 -5
- package/skills/dashboard/templates/dashboard-project.md +6 -18
- package/skills/doctor/SKILL.md +6 -0
- package/skills/intent-locking/SKILL.md +20 -2
- package/skills/intent-starting/SKILL.md +6 -4
- package/skills/project-continuing/SKILL.md +10 -0
- package/skills/project-continuing/evals/evals.json +3 -3
- package/skills/project-continuing/references/board-fill.md +13 -11
- package/skills/releasing/SKILL.md +3 -3
- package/skills/store-curating/SKILL.md +9 -0
- package/skills/store-curating/evals/evals.json +16 -0
- package/skills/tutorial/SKILL.md +4 -4
- package/skills/tutorial/references/track-1-guided.md +2 -1
- package/skills/tutorial/references/track-2-auto.md +2 -1
- package/skills/tutorial/references/track-3-projects-and-roadmaps.md +2 -1
package/skills/auto/SKILL.md
CHANGED
|
@@ -93,13 +93,15 @@ edited before the plan exists (the gate applies to YOU, the orchestrator):
|
|
|
93
93
|
|
|
94
94
|
```bash
|
|
95
95
|
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
96
|
-
'
|
|
96
|
+
'codex=ENV["CODEX_THREAD_ID"].to_s.strip; claude=ENV["CLAUDE_CODE_SESSION_ID"].to_s.strip; harness=!codex.empty? ? "codex" : (!claude.empty? ? "claude" : nil); session=!codex.empty? ? codex : (!claude.empty? ? claude : nil); Bridge.arm_auto(session, intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>", harness: harness, agent: "plastic-enforcer", thread: (!codex.empty? ? codex : nil))'
|
|
97
97
|
```
|
|
98
98
|
|
|
99
99
|
Replace `<ID>`, `<STORE>` (e.g. `~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
|
|
100
100
|
`<dir>` (the `ID--slug` directory), and `<name>`. The first argument is the session id you
|
|
101
|
-
want the bridge keyed by: pass the hook stdin `session_id` when you have it
|
|
102
|
-
|
|
101
|
+
want the bridge keyed by: pass the hook stdin `session_id` when you have it. The executable
|
|
102
|
+
snippet trusts a nonblank `CODEX_THREAD_ID` as Codex, otherwise a nonblank
|
|
103
|
+
`CLAUDE_CODE_SESSION_ID` as Claude, otherwise leaves harness and thread unknown. Never guess
|
|
104
|
+
identity from an absent runtime variable. Arming always succeeds and acquires the
|
|
103
105
|
durable `delivery.lock` in the intent dir. For the `resolve_session` fallback chain
|
|
104
106
|
(why arming never needs a non-empty session env var, and what the lock ownership model
|
|
105
107
|
implies for later tool calls) read `references/end-tail.md`.
|
|
@@ -148,12 +150,33 @@ The enforcer's session owns the delivery lock. Per-stage specialists run in
|
|
|
148
150
|
their own sessions and would be denied by the lock gate, so register each one
|
|
149
151
|
as a delegate before (or when) it needs to write into the intent dir:
|
|
150
152
|
|
|
151
|
-
1. Instruct each spawned specialist to report its session id
|
|
152
|
-
|
|
153
|
+
1. Instruct each spawned specialist to report its session id and runtime identity in its first
|
|
154
|
+
message: `CODEX_THREAD_ID` for Codex, or `CLAUDE_CODE_SESSION_ID` for Claude. Use the
|
|
155
|
+
specialist/hook identity when known; never infer a harness or model from missing context.
|
|
153
156
|
2. As the lock owner, run:
|
|
154
|
-
`ruby ~/.plastic/scripts/plastic-lock delegate --delegate <specialist-session-id>`
|
|
157
|
+
`ruby ~/.plastic/scripts/plastic-lock delegate --intent-dir <intent-dir> --delegate <specialist-session-id> --harness <specialist-harness-when-known> --agent <role> --model <resolved-model-when-known> --thread <reported-CODEX_THREAD_ID-when-Codex>`
|
|
158
|
+
Omit `--harness`, `--model`, or `--thread` when that value is unknown; `--agent <role>` is
|
|
159
|
+
always known from the dispatch roster.
|
|
155
160
|
3. If a specialist hits a lock-gate deny, the deny message names this exact
|
|
156
161
|
command; run it and have the specialist retry.
|
|
162
|
+
4. Immediately after the specialist returns, and before validating or dispatching
|
|
163
|
+
the next handoff, classify the return and record its activity status as the owner:
|
|
164
|
+
- `finished` means the specialist returned a usable completion report, whether
|
|
165
|
+
agent-authored or synthesized through `scripts/agent-report`.
|
|
166
|
+
- `failed` means the specialist returned blocked, errored, or without a usable
|
|
167
|
+
completion report that can be synthesized.
|
|
168
|
+
5. Record the classification with exactly one of:
|
|
169
|
+
```bash
|
|
170
|
+
ruby ~/.plastic/scripts/plastic-lock delegate --intent-dir <intent-dir> \
|
|
171
|
+
--delegate <specialist-session-id> --status finished --harness <same-specialist-harness-when-known> \
|
|
172
|
+
--agent <same-role> --model <same-resolved-model-when-known> --thread <same-CODEX_THREAD_ID-when-Codex>
|
|
173
|
+
ruby ~/.plastic/scripts/plastic-lock delegate --intent-dir <intent-dir> \
|
|
174
|
+
--delegate <specialist-session-id> --status failed --harness <same-specialist-harness-when-known> \
|
|
175
|
+
--agent <same-role> --model <same-resolved-model-when-known> --thread <same-CODEX_THREAD_ID-when-Codex>
|
|
176
|
+
```
|
|
177
|
+
Apply the same omission rule to unknown values on terminal status commands.
|
|
178
|
+
A failed specialist stops that handoff under the normal blocker/error procedure;
|
|
179
|
+
never dispatch the next specialist first.
|
|
157
180
|
|
|
158
181
|
Only the owner can delegate. Delegates cannot re-delegate or release.
|
|
159
182
|
|
|
@@ -81,6 +81,13 @@ account therefore always exists: agent-authored when present, deterministically
|
|
|
81
81
|
otherwise. This structures the finish notification only; in-flight observations stay in
|
|
82
82
|
`## Insights`, no progress chatter is added.
|
|
83
83
|
|
|
84
|
+
Immediately after a specialist returns and before the next handoff, the enforcer records the
|
|
85
|
+
delegate's activity through `plastic-lock delegate --intent-dir <intent-dir> --delegate <id>
|
|
86
|
+
--status finished|failed`. `finished` requires a usable agent-authored or synthesized completion
|
|
87
|
+
report. A blocked or errored return, or one with no report that can be synthesized, is `failed`
|
|
88
|
+
and stops the handoff under the normal error procedure. Activity status is descriptive and does
|
|
89
|
+
not revoke the registered delegate's authorization.
|
|
90
|
+
|
|
84
91
|
### Gate Ownership
|
|
85
92
|
|
|
86
93
|
The enforcer arms and verifies the lifecycle gate, then gates every stage transition.
|
|
@@ -44,12 +44,14 @@ deliberately); and the durable lock file is checked again after disarm, never me
|
|
|
44
44
|
trusted (exit 3 if it is somehow still present).
|
|
45
45
|
|
|
46
46
|
**Worktree cleanup (mandatory, intent 73c3).** `end-intent`'s step 5 calls
|
|
47
|
-
`Bridge.disarm_auto` by default, which calls `Worktree.release`, which removes
|
|
48
|
-
|
|
49
|
-
the
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
`Bridge.disarm_auto` by default, which calls `Worktree.release`, which removes the
|
|
48
|
+
intent's code worktree under `<repo>/.claude/worktrees/{id}--{slug}`, prunes the repo, and
|
|
49
|
+
clears the worktree block from the bridge. (Plastic used to also provision a paired store
|
|
50
|
+
worktree under `<plastic_home>/.worktrees/{id}--{slug}`; intent 178 retired it, since
|
|
51
|
+
lifecycle-doc writes go straight to the main store checkout, and intent 197's
|
|
52
|
+
branch-from-main plus scoped commit already gives them their own write safety.) This is the
|
|
53
|
+
plain remove path: the disarm route does NOT merge, so use it only when no release merges
|
|
54
|
+
the branch (the branch survives and can be reclaimed).
|
|
53
55
|
|
|
54
56
|
When the work is being shipped through a release, do NOT rely on this plain remove.
|
|
55
57
|
`skills/releasing/SKILL.md` reorders its own two steps for exactly this reason (intent 188,
|
|
@@ -33,14 +33,19 @@ ruby ~/.plastic/scripts/dashboard.rb [continue|project <slug>] --data
|
|
|
33
33
|
- `continue` (default) → the **global** board payload (`mode: "global"`).
|
|
34
34
|
- `project <slug>` → that **project** board payload (`mode: "project"`).
|
|
35
35
|
|
|
36
|
-
The payload is read-only JSON. Global-board fields: `date`, `store_health`, `
|
|
37
|
-
`next_work`, `counts`, `projects`, `project_totals
|
|
38
|
-
`
|
|
39
|
-
|
|
36
|
+
The payload is read-only JSON. Global-board fields: `date`, `store_health`, `summary`,
|
|
37
|
+
`next_work`, `next_total`, `next_shown`, `counts`, `projects`, `project_totals`, `footer`.
|
|
38
|
+
Project-board fields: `slug`, `store_health`, `description`, `summary`, `counts`, `active`,
|
|
39
|
+
`active_total`, `active_shown`, `next_work`, `next_total`, `next_shown`, `footer`. `summary`
|
|
40
|
+
and `footer` are finished prose strings (2-3 sentences and one line respectively), built in
|
|
41
|
+
`dashboard.rb` and substituted verbatim, exactly like `{{date}}`/`{{description}}` already
|
|
42
|
+
are - never re-worded or re-derived by the skill. Each list carries cell-ready fields for
|
|
43
|
+
its table: `next_work` rows are
|
|
40
44
|
`{id, intent, scope, lifecycle, value, disposition, flags, what, flags_label, line}`;
|
|
41
|
-
`
|
|
42
|
-
`
|
|
43
|
-
`scope`, and `flags_label` cell fields arrive pipe-escaped and
|
|
45
|
+
`active` rows carry
|
|
46
|
+
`{id, intent, created, bullet, scope, what, stage, worker, activity, line}`. The `what`,
|
|
47
|
+
`scope`, `worker`, `activity`, and `flags_label` cell fields arrive pipe-escaped and
|
|
48
|
+
whitespace-normalized.
|
|
44
49
|
|
|
45
50
|
Each board load runs the scoped store check (`doctor --store <scope>`): the global board runs
|
|
46
51
|
`--store global` and a project board runs `--store <slug>`. The result rides in the payload as
|
|
@@ -57,24 +62,42 @@ Templates live in this skill's `templates/` directory:
|
|
|
57
62
|
|
|
58
63
|
Fill mechanically, no rewriting, no re-sorting:
|
|
59
64
|
- `{{a.b.count}}` → the integer (e.g. `counts.active` = that count).
|
|
60
|
-
- `{{<list>.rows}}` → the
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
re-
|
|
66
|
-
- `recently_worked` (global) → `| {id} | {what} | {state} | {scope} |`
|
|
67
|
-
- `recently_worked` (project) → `| {id} | {what} | {state} |`
|
|
65
|
+
- `{{<list>.rows}}` → the two intent lists (`active`, `next_work`) render as **Markdown table
|
|
66
|
+
rows**. The template hard-codes each table's header and separator; this placeholder becomes
|
|
67
|
+
one data row per list entry, joined with real newlines, in that table's fixed column order
|
|
68
|
+
(below). Drop each cell from the named payload field **verbatim**: cells arrive pre-escaped
|
|
69
|
+
and whitespace-normalized from the script (pipes escaped as `\|`), so never re-escape,
|
|
70
|
+
re-truncate, or reword them. Never emit `<br>`.
|
|
68
71
|
- `next_work` → `| {id} | {what} | {value} | {disposition} | {flags_label} |`
|
|
69
|
-
- `active` → `| {id} | {what} | {stage} |`
|
|
70
|
-
- `
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
72
|
+
- `active` → `| {id} | {what} | {stage} | {worker} | {activity} |`
|
|
73
|
+
Empty list → one full-width row with `_(none)_` in the Id column and every other cell blank,
|
|
74
|
+
matching that table's column count (e.g. `| _(none)_ | | | | |` for the 5-column next_work
|
|
75
|
+
table, `| _(none)_ | | | |` for the 5-column active table). Neither list carries an overflow
|
|
76
|
+
"+N more" row anymore (D5, intent 202): the true pool size rides on the payload as
|
|
77
|
+
`active_total`/`next_total` (shown counts as `active_shown`/`next_shown`), and `{{footer}}`
|
|
78
|
+
states it in prose instead.
|
|
79
|
+
- `{{projects.lines}}` (global board only) → the project rollup stays **prose**, one line per
|
|
80
|
+
project (not a table):
|
|
76
81
|
`- **{slug}**: {description}, active {active}, done {done}, future {future}, last accessed {last_accessed_at[0,10]}`.
|
|
77
|
-
- Scalars (`{{date}}`, `{{slug}}`, `{{
|
|
82
|
+
- Scalars (`{{date}}`, `{{slug}}`, `{{summary}}`, `{{footer}}`) → substitute verbatim. `summary`
|
|
83
|
+
and `footer` are finished prose built in `dashboard.rb`; do not rewrite, shorten, or
|
|
84
|
+
re-derive them from the counts - that is exactly the non-determinism D5 rules out.
|
|
85
|
+
|
|
86
|
+
### Paging (conversational, D4)
|
|
87
|
+
|
|
88
|
+
The board shows a short page by default (Active capped at 3, Next-work at 5). When the
|
|
89
|
+
user asks for "more" or "all", re-invoke Step 1 with an explicit flag and re-fill the
|
|
90
|
+
template with the new payload - nothing is persisted to disk, the offset lives only in the
|
|
91
|
+
chat turn:
|
|
92
|
+
- "all" → add `--all` (lifts both caps to unbounded; the footer then shows equal shown/total).
|
|
93
|
+
- "more" → add `--limit-active N`/`--limit-next N` with a larger `N` for whichever list the
|
|
94
|
+
user is paging.
|
|
95
|
+
|
|
96
|
+
For a real, own-terminal pager instead, point the owner at `--plain`:
|
|
97
|
+
`ruby ~/.plastic/scripts/dashboard.rb project <slug> --plain | less` (or `continue --plain`
|
|
98
|
+
for the global board). `--plain` prints the full, uncapped board as plain text with no
|
|
99
|
+
Markdown table syntax; it is a separate CLI mode from `--data`, not something this skill
|
|
100
|
+
fills a template from.
|
|
78
101
|
|
|
79
102
|
### Step 3 — Present it (mandatory, every invocation)
|
|
80
103
|
|
|
@@ -139,8 +162,8 @@ intentional change means the skill is broken.
|
|
|
139
162
|
|
|
140
163
|
## Notes
|
|
141
164
|
|
|
142
|
-
- The
|
|
143
|
-
|
|
165
|
+
- The two intent lists (active, next work) render as Markdown tables; the prose summary,
|
|
166
|
+
footer, counts, and the project rollup stay prose (intent 202). No Value x Effort grid
|
|
144
167
|
returns. Never emit `<br>`.
|
|
145
168
|
- Clusters (Zettelkasten grouping in INDEX.md) are intentionally not rendered.
|
|
146
169
|
- Additive: changes no core lifecycle, gate, or cycle logic.
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
"id": 2,
|
|
23
23
|
"scope": "behavior",
|
|
24
24
|
"set": "validation",
|
|
25
|
-
"prompt": "How
|
|
26
|
-
"expected_output": "
|
|
25
|
+
"prompt": "How does the project board render after intent 202 (short by default, paged on request)?",
|
|
26
|
+
"expected_output": "The project board shows, in order, and nothing else: a 2-3 sentence prose summary of what was delivered most recently (built in dashboard.rb from completed/completed_on data, not the 24h-windowed recently_worked), an Active table capped at 3 rows (Id|What|Stage, ordered lifecycle-stage descending with a savepoint tie-break), a Next-work table capped at 5 rows (Id|What|Value|Disposition|Flags), and a one-line footer stating true totals (e.g. '3 of 12 active, 5 of 47 next work') plus how to see everything. The raw Future table and its {{future.rows}} placeholder, and the separate Recently-worked table, are both gone from dashboard-project.md. dashboard-global.md carries the equivalent fix (D6): a prose summary in place of its recently-worked table, the same honest footer, and the same --limit-active/--limit-next/--all/--plain mechanics.",
|
|
27
27
|
"files": ["skills/dashboard/templates/dashboard-global.md", "skills/dashboard/templates/dashboard-project.md", "skills/dashboard/SKILL.md"],
|
|
28
28
|
"assertions": [
|
|
29
29
|
{
|
|
30
30
|
"type": "convention",
|
|
31
|
-
"check": "
|
|
32
|
-
"observed": "templates
|
|
31
|
+
"check": "project board template has summary+active+next_work+footer only (no future, no recently_worked table); global template has summary+footer added; SKILL.md documents the new payload fields and CLI flags",
|
|
32
|
+
"observed": "templates carry {{summary}}, {{active.rows}} (Id|What|Stage), {{next_work.rows}} (Id|What|Value|Disposition|Flags), {{footer}}; {{future.rows}} and the recently-worked table are removed from both; SKILL.md Step 1/2 document summary/footer/active_total/active_shown/next_total/next_shown and --limit-active/--limit-next/--all/--plain",
|
|
33
33
|
"result": "pass"
|
|
34
34
|
}
|
|
35
35
|
]
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
# 🧩 Plastic · Global Board, {{date}}
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
| Id | What | State | Scope |
|
|
6
|
-
| --- | --- | --- | --- |
|
|
7
|
-
{{recently_worked.rows}}
|
|
3
|
+
{{summary}}
|
|
8
4
|
|
|
9
5
|
## Where we are
|
|
10
6
|
|
|
@@ -19,4 +15,6 @@
|
|
|
19
15
|
| --- | --- | --- | --- | --- |
|
|
20
16
|
{{next_work.rows}}
|
|
21
17
|
|
|
18
|
+
{{footer}}
|
|
19
|
+
|
|
22
20
|
**What would you like to work on next?** (type an **intent id**, a **project name**, or anything **new** you'd like to start)
|
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
# 📦 {{slug}} · Project Board, {{date}}
|
|
2
2
|
|
|
3
|
-
{{
|
|
4
|
-
|
|
5
|
-
**Recently worked** (last active work, last 24h)
|
|
6
|
-
|
|
7
|
-
| Id | What | State |
|
|
8
|
-
| --- | --- | --- |
|
|
9
|
-
{{recently_worked.rows}}
|
|
10
|
-
|
|
11
|
-
## Intents, active {{counts.active}}, done {{counts.done}}, future {{counts.future}}
|
|
3
|
+
{{summary}}
|
|
12
4
|
|
|
13
5
|
**Active**
|
|
14
6
|
|
|
15
|
-
| Id | What | Stage |
|
|
16
|
-
| --- | --- | --- |
|
|
7
|
+
| Id | What | Stage | Worker | Activity |
|
|
8
|
+
| --- | --- | --- | --- | --- |
|
|
17
9
|
{{active.rows}}
|
|
18
10
|
|
|
19
|
-
**
|
|
20
|
-
|
|
21
|
-
| Id | What |
|
|
22
|
-
| --- | --- |
|
|
23
|
-
{{future.rows}}
|
|
24
|
-
|
|
25
|
-
## Most-valuable next work
|
|
11
|
+
**Most-valuable next work**
|
|
26
12
|
|
|
27
13
|
| Id | What | Value | Disposition | Flags |
|
|
28
14
|
| --- | --- | --- | --- | --- |
|
|
29
15
|
{{next_work.rows}}
|
|
30
16
|
|
|
17
|
+
{{footer}}
|
|
18
|
+
|
|
31
19
|
**What would you like to work on next?** (type an **intent id**, or **global** to go back)
|
package/skills/doctor/SKILL.md
CHANGED
|
@@ -106,6 +106,11 @@ If any checks have `fixable: true` AND status is not `pass`:
|
|
|
106
106
|
|
|
107
107
|
If no fixable issues exist, skip this step.
|
|
108
108
|
|
|
109
|
+
This "Fix all / Select individually / Skip" prompt IS the router the spec calls
|
|
110
|
+
`doctor --fix-all` (intent 197): doctor itself never mutates anything (see Step 5's table and
|
|
111
|
+
"Important Notes" below); "Fix all" means "dispatch every fixable finding to the maintenance
|
|
112
|
+
tool or skill that owns that class of repair," one row per fix_hint pattern.
|
|
113
|
+
|
|
109
114
|
### Step 5: Apply fixes
|
|
110
115
|
|
|
111
116
|
Use the `fix_hint` value to determine the correct action:
|
|
@@ -121,6 +126,7 @@ Use the `fix_hint` value to determine the correct action:
|
|
|
121
126
|
| "Run: provision-project-store {slug}" | Run `provision-project-store <slug>` (or invoke the `plastic-store-provisioning` skill) to create the missing store |
|
|
122
127
|
| "Re-run installer" | Run `npx -y @zalom/plastic@<channel> install --agent <agent>` (channel: -alpha->@alpha, -beta->@beta, else @latest) |
|
|
123
128
|
| "Dispatch plastic-store-curating ... revisions.md ..." | Invoke the `plastic-store-curating` (or the agent) to relocate the flagged section or ref into the intent's `revisions.md` via move-and-record (one dated, `[rule: <tag>]`-tagged entry per item), per PLASTIC.md > Structural maintenance and revisions.md. For a missing required section, restore or reproject it instead. |
|
|
129
|
+
| "Run scripts/project-links ... PRESERVES ... --drop-unbacked-links" | Run `ruby ~/.plastic/scripts/maintenance-run --tool project-links --intent <id> --apply` for the one flagged id (never run bare `project-links` against a real store outside the rare owner-approved batch exception, D2) |
|
|
124
130
|
|
|
125
131
|
For fixes the agent cannot handle automatically, explain what the user needs
|
|
126
132
|
to do manually. The `revisions.md` remedy is curator-applied (a move-and-record
|
|
@@ -19,23 +19,41 @@ Run from the project (the intent resolves from this session's bridge), or pass
|
|
|
19
19
|
|
|
20
20
|
| Verb | What it does | When |
|
|
21
21
|
|---|---|---|
|
|
22
|
+
| `who` | Print a compact owner, heartbeat, claim, and delegate view from durable files only | Safe human inspection; requires `--intent-dir` |
|
|
22
23
|
| `status` | Report the lock file, bridge cache, freshness, agreement | Always safe; run first |
|
|
23
24
|
| `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 |
|
|
24
25
|
| `release` | Owner clears the lock | Ending or abandoning a boarding |
|
|
25
26
|
| `reclaim` | Explicit takeover of a STALE lock; appends an audit line to savepoint.md | The owner is gone and the lease expired |
|
|
26
|
-
| `delegate` | Owner registers a subagent session
|
|
27
|
+
| `delegate` | Owner registers a subagent session and optional provenance, or marks it `finished`/`failed` | Auto-mode orchestration |
|
|
27
28
|
|
|
28
29
|
```
|
|
29
30
|
ruby ~/.plastic/scripts/plastic-lock status
|
|
31
|
+
ruby ~/.plastic/scripts/plastic-lock who --intent-dir <store>/<id>--<slug>
|
|
30
32
|
ruby ~/.plastic/scripts/plastic-lock fix --intent-dir <store>/<id>--<slug>
|
|
31
33
|
ruby ~/.plastic/scripts/plastic-lock reclaim --intent-dir <store>/<id>--<slug>
|
|
32
|
-
ruby ~/.plastic/scripts/plastic-lock delegate --delegate <subagent-session-id>
|
|
34
|
+
ruby ~/.plastic/scripts/plastic-lock delegate --delegate <subagent-session-id> \
|
|
35
|
+
--harness codex --agent plastic-executor --model <model> --thread <thread-id>
|
|
36
|
+
ruby ~/.plastic/scripts/plastic-lock delegate --delegate <subagent-session-id> --status finished
|
|
33
37
|
```
|
|
34
38
|
|
|
39
|
+
When the current controller knows its provenance, `fix` and `reclaim` accept
|
|
40
|
+
`--harness`, `--agent`, `--model`, `--thread`, and `--mode auto|guided`.
|
|
41
|
+
Provenance is descriptive; the session remains the authorization identity.
|
|
42
|
+
|
|
35
43
|
## Rules
|
|
36
44
|
|
|
37
45
|
- `fix` exits non-zero when another session holds a FRESH lock: back off, do
|
|
38
46
|
not retry in a loop. `status` shows the owner.
|
|
47
|
+
- `who` is strictly read-only. It reads `delivery.lock`, its mtime, and claim
|
|
48
|
+
files; it never reads or repairs a bridge, searches transcripts, heartbeats,
|
|
49
|
+
or writes. Missing legacy provenance displays as `Unknown` rather than being
|
|
50
|
+
inferred.
|
|
51
|
+
- The `delivery.lock` file mtime is the sole heartbeat and freshness truth.
|
|
52
|
+
Provenance timestamps do not replace it.
|
|
53
|
+
- Only the lock owner may register delegates or mark them `finished` or
|
|
54
|
+
`failed`. Terminal status is observational and does not remove the delegate
|
|
55
|
+
session from the authorization list. Finished and failed activity history is
|
|
56
|
+
bounded to the 20 most recent terminal entries.
|
|
39
57
|
- `reclaim` refuses a fresh lock. There is no silent reclaim anywhere; every
|
|
40
58
|
takeover is audited in the intent's savepoint.md.
|
|
41
59
|
- Acquiring a lock for new work is NOT this skill's job: board through
|
|
@@ -47,10 +47,10 @@ enforces it: without a held lock, mutating writes to this active intent's dir ar
|
|
|
47
47
|
```bash
|
|
48
48
|
# guided (lock only):
|
|
49
49
|
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
50
|
-
'
|
|
50
|
+
'codex=ENV["CODEX_THREAD_ID"].to_s.strip; claude=ENV["CLAUDE_CODE_SESSION_ID"].to_s.strip; harness=!codex.empty? ? "codex" : (!claude.empty? ? "claude" : nil); session=!codex.empty? ? codex : (!claude.empty? ? claude : nil); Bridge.arm_guided(session, intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>", harness: harness, agent: "plastic-enforcer", thread: (!codex.empty? ? codex : nil))'
|
|
51
51
|
# auto (lock + auto), then hand to plastic-auto:
|
|
52
52
|
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
53
|
-
'
|
|
53
|
+
'codex=ENV["CODEX_THREAD_ID"].to_s.strip; claude=ENV["CLAUDE_CODE_SESSION_ID"].to_s.strip; harness=!codex.empty? ? "codex" : (!claude.empty? ? "claude" : nil); session=!codex.empty? ? codex : (!claude.empty? ? claude : nil); Bridge.arm_auto(session, intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>", harness: harness, agent: "plastic-enforcer", thread: (!codex.empty? ? codex : nil))'
|
|
54
54
|
```
|
|
55
55
|
Replace `<ID>`, `<STORE>` (`~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
|
|
56
56
|
`<dir>` (the `ID--slug` directory), and `<name>`.
|
|
@@ -67,8 +67,10 @@ enforces it: without a held lock, mutating writes to this active intent's dir ar
|
|
|
67
67
|
discovery yields nothing, proceed to Why normally.
|
|
68
68
|
|
|
69
69
|
**Session id resolution (verbatim from `plastic-auto`).** The first argument is the session
|
|
70
|
-
id the bridge is keyed by: pass the hook stdin `session_id` when you have it
|
|
71
|
-
|
|
70
|
+
id the bridge is keyed by: pass the hook stdin `session_id` when you have it; in the executable
|
|
71
|
+
snippets, a nonblank `CODEX_THREAD_ID` identifies Codex, otherwise a nonblank
|
|
72
|
+
`CLAUDE_CODE_SESSION_ID` identifies Claude, otherwise identity remains unknown. Never infer a
|
|
73
|
+
harness from an absent variable. Both arms call `resolve_session`, which
|
|
72
74
|
picks the first non-empty of: the explicit id you pass → `CLAUDE_CODE_SESSION_ID` → a
|
|
73
75
|
deterministic derived key (a hash of the store and intent id). It never returns nil, so the
|
|
74
76
|
lock is taken even when every session env var is empty; arming prints a one-line stderr
|
|
@@ -104,6 +104,16 @@ state and stops, asking nothing (unchanged): it does not itself dispatch, re-ran
|
|
|
104
104
|
roadmap. The global store and any project with no roadmap report `none`, so this board stays the
|
|
105
105
|
default route for them.
|
|
106
106
|
|
|
107
|
+
Intent 202 has landed on top of 149/149a: the project board is short by default. The
|
|
108
|
+
Recently-worked table and the raw Future table are both gone, replaced by a 2-3 sentence
|
|
109
|
+
prose summary (built in `dashboard.rb`, not by this skill) plus a one-line footer stating
|
|
110
|
+
true totals. Active is capped at 3 (ordered lifecycle-stage descending, a later savepoint
|
|
111
|
+
breaking a tie - D2), Next-work at 5. Conversational paging ("more"/"all") re-invokes
|
|
112
|
+
`dashboard.rb ... --data` with `--limit-active`/`--limit-next`/`--all`, carrying no state on
|
|
113
|
+
disk; `--plain` prints the full uncapped board as plain text for a real pager. The rule-name
|
|
114
|
+
citations and the `dashboard.rb project <slug> --data` -> `dashboard-project.md` path still
|
|
115
|
+
resolve.
|
|
116
|
+
|
|
107
117
|
## References
|
|
108
118
|
|
|
109
119
|
- `references/board-fill.md` - the template-fill mechanics and store-health surfacing detail.
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
},
|
|
72
72
|
{
|
|
73
73
|
"id": 8, "scope": "behavior", "set": "validation",
|
|
74
|
-
"prompt": "Does the skill carry the intent-
|
|
75
|
-
"expected_output": "A 'Coordination' section records that intent
|
|
74
|
+
"prompt": "Does the skill carry the intent-202 coordination note (project board short by default)?",
|
|
75
|
+
"expected_output": "A 'Coordination' section records that intent 202 has landed: the Recently-worked and raw Future tables are gone, replaced by a prose summary (built in dashboard.rb) and an honest-totals footer; Active is capped at 3 (lifecycle-stage descending, D2) and Next-work at 5; conversational paging re-invokes dashboard.rb --data with --limit-active/--limit-next/--all (no state on disk); --plain prints the full uncapped plain-text board. The rule-name citations and the dashboard.rb project <slug> --data -> dashboard-project.md path still resolve.",
|
|
76
76
|
"files": ["skills/project-continuing/SKILL.md"],
|
|
77
77
|
"assertions": [
|
|
78
|
-
{ "type": "convention", "check": "'
|
|
78
|
+
{ "type": "convention", "check": "'202' present with the landed coordination note describing the new shape, caps, paging flags, and --plain", "observed": "present: 'Intent 202 has landed' paragraph with the described content", "result": "pass" }
|
|
79
79
|
]
|
|
80
80
|
},
|
|
81
81
|
{
|
|
@@ -7,20 +7,22 @@ would otherwise bloat the SKILL.md body.
|
|
|
7
7
|
## Fill rules (owned by plastic-dashboard, summarized here for convenience)
|
|
8
8
|
|
|
9
9
|
- `{{a.b.count}}` -> the integer (e.g. `counts.active` is that count).
|
|
10
|
-
- `{{<list>.rows}}` -> the
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
- `recently_worked` -> `| id | what | state | scope |` (global), `| id | what | state |` (project)
|
|
10
|
+
- `{{<list>.rows}}` -> the two intent lists (`active`, `next_work`) render as Markdown table
|
|
11
|
+
rows. The template hard-codes each table's header and separator; the placeholder becomes one
|
|
12
|
+
data row per entry, in that table's fixed column order, cells dropped verbatim from the
|
|
13
|
+
payload (cells arrive pipe-escaped and whitespace-normalized; do not re-escape or
|
|
14
|
+
re-truncate). Column order per table:
|
|
16
15
|
- `next_work` -> `| id | what | value | disposition | flags_label |`
|
|
17
|
-
- `active` -> `| id | what | stage
|
|
18
|
-
Overflow entry (empty `id`, `what` = `+N more`) -> `+N more` in the Id column, other cells blank.
|
|
16
|
+
- `active` -> `| id | what | stage |`
|
|
19
17
|
Empty list -> one full-width row with `_(none)_` in the Id column, other cells blank, matching
|
|
20
|
-
that table's column count
|
|
21
|
-
|
|
18
|
+
that table's column count. Neither list carries an overflow "+N more" row (intent 202): the
|
|
19
|
+
true pool size rides on the payload (`active_total`/`next_total`, shown as
|
|
20
|
+
`active_shown`/`next_shown`), stated in prose by `{{footer}}` instead. Never emit `<br>`.
|
|
22
21
|
- `{{projects.lines}}` (global board) -> the project rollup stays prose, one line per project.
|
|
23
|
-
- Scalars (`{{date}}`, `{{slug}}`, `{{
|
|
22
|
+
- Scalars (`{{date}}`, `{{slug}}`, `{{summary}}`, `{{footer}}`) -> substitute verbatim.
|
|
23
|
+
`summary` (the 2-3 sentence "what was delivered most recently") and `footer` (the
|
|
24
|
+
honest-totals + how-to-see-everything line) are finished prose built in `dashboard.rb`,
|
|
25
|
+
replacing the old recently-worked table and the raw future table respectively.
|
|
24
26
|
|
|
25
27
|
No re-sorting, no re-summarizing, no hand-written prose replacing a line the payload already
|
|
26
28
|
supplies. Same store state produces a byte-identical payload regardless of model.
|
|
@@ -247,11 +247,11 @@ lock correctly (G5): before intent 188 this path left the lock stranded, exactly
|
|
|
247
247
|
of bug closed by the End-tail enforcement work.
|
|
248
248
|
|
|
249
249
|
This is the release branch of `plastic-intent-ending`'s Step 5 disarm (`merge: true`), not a
|
|
250
|
-
separate concern: a release is the merge-then-remove path for the intent's
|
|
250
|
+
separate concern: a release is the merge-then-remove path for the intent's worktree (intent
|
|
251
251
|
73c3), so the intent's code branch is merged back into the default branch BEFORE the worktree
|
|
252
252
|
is removed. Drive it through `Worktree.finish` with `merge: true`, which merges the code
|
|
253
|
-
branch, then removes
|
|
254
|
-
|
|
253
|
+
branch, then removes the worktree, prunes the repo, and clears the worktree block from the
|
|
254
|
+
bridge:
|
|
255
255
|
|
|
256
256
|
```bash
|
|
257
257
|
ruby -r ~/.plastic/scripts/lib/worktree -r ~/.plastic/scripts/lib/bridge -e \
|
|
@@ -45,3 +45,12 @@ When an intent reaches a terminal state, moved to Completed OR Abandoned, do the
|
|
|
45
45
|
2. Call `plastic-intent-ending` for the terminal-transition close (INDEX move, savepoint `Done` bookend, store commit, disarm, and the QMD reindex last): `ruby ~/.plastic/scripts/end-intent --store <store> --id <id> --disposition delivered|abandoned`, then follow that skill's own disarm and reindex steps. Never restate those one-liners here.
|
|
46
46
|
|
|
47
47
|
After the agent completes, report what changed.
|
|
48
|
+
|
|
49
|
+
## Maintenance dispatch (intent 197)
|
|
50
|
+
|
|
51
|
+
When invoked to fix a structural finding on an intent OTHER than one currently being delivered
|
|
52
|
+
(for example, from `/plastic-doctor`'s fix-all routing), the `plastic-intent-curator` agent
|
|
53
|
+
follows its own step 7: it detects (never acquires) the target's delivery lock, requires a
|
|
54
|
+
clean working tree, and performs the fix on a fresh branch merged back to main as one closed
|
|
55
|
+
operation, with an append-only `revisions.md` receipt in the same pass as the edit. See
|
|
56
|
+
`agents/plastic-intent-curator.md` for the exact mechanics.
|
|
@@ -17,6 +17,22 @@
|
|
|
17
17
|
"result": "pass"
|
|
18
18
|
}
|
|
19
19
|
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 2,
|
|
23
|
+
"scope": "behavior",
|
|
24
|
+
"set": "validation",
|
|
25
|
+
"prompt": "The user says: intent 26 (Completed) has a stale ## Links comment that contradicts its own real chain edge; fix it. Intent 26 is NOT the intent currently being delivered by this session.",
|
|
26
|
+
"expected_output": "Before editing intent 26, checks its delivery lock freshness (plastic-lock status --intent-dir, reading lock_fresh) and defers if fresh; requires a clean store working tree; creates a fresh maintenance branch off main; makes the scoped edit AND appends a revisions.md receipt in the same pass, refusing the edit if the receipt cannot be written; stages only the changed paths (never git add -A); merges the branch back to main and deletes it before reporting done.",
|
|
27
|
+
"files": [],
|
|
28
|
+
"assertions": [
|
|
29
|
+
{
|
|
30
|
+
"type": "human",
|
|
31
|
+
"check": "agent file states detect-only lock check, clean-tree precheck, branch-and-merge-back, and refuse-without-receipt as an unconditional sequence for maintenance on a non-current intent",
|
|
32
|
+
"observed": "agents/plastic-intent-curator.md step 7 covers all five sub-steps (a-f) exactly as asked",
|
|
33
|
+
"result": "pass"
|
|
34
|
+
}
|
|
35
|
+
]
|
|
20
36
|
}
|
|
21
37
|
]
|
|
22
38
|
}
|
package/skills/tutorial/SKILL.md
CHANGED
|
@@ -59,7 +59,7 @@ remember where they left off.
|
|
|
59
59
|
|
|
60
60
|
## Before any track
|
|
61
61
|
|
|
62
|
-
Every track opens with the same two checks: run `/plastic-update` first
|
|
63
|
-
matches what is actually installed, and work in a sandbox (a
|
|
64
|
-
intent) so nothing real is touched by mistake. Each reference
|
|
65
|
-
skip it even if the user seems experienced.
|
|
62
|
+
Every track opens with the same two checks: run `/plastic-update` first (`$plastic-update` on
|
|
63
|
+
Codex), so the walkthrough matches what is actually installed, and work in a sandbox (a
|
|
64
|
+
throwaway repo, or a global-store intent) so nothing real is touched by mistake. Each reference
|
|
65
|
+
restates this briefly; do not skip it even if the user seems experienced.
|
|
@@ -9,7 +9,8 @@ end to end: a piece of work moved through What, Why, How, and Exec, with a finis
|
|
|
9
9
|
|
|
10
10
|
## Before you start
|
|
11
11
|
|
|
12
|
-
Run `/plastic-update` first, so the commands below match what is
|
|
12
|
+
Run `/plastic-update` first (`$plastic-update` on Codex), so the commands below match what is
|
|
13
|
+
actually installed.
|
|
13
14
|
|
|
14
15
|
Work in a sandbox: this track always creates a global-store intent; the throwaway repo below
|
|
15
16
|
is never registered as a Plastic project. Pick a throwaway git repository if you have one
|
|
@@ -9,7 +9,8 @@ agent end to end, and pausing and resuming that delivery will feel familiar.
|
|
|
9
9
|
|
|
10
10
|
## Before you start
|
|
11
11
|
|
|
12
|
-
Run `/plastic-update` first, so the commands below match what is
|
|
12
|
+
Run `/plastic-update` first (`$plastic-update` on Codex), so the commands below match what is
|
|
13
|
+
actually installed.
|
|
13
14
|
|
|
14
15
|
Work in a sandbox: a throwaway git repository, or a global-store intent. Nothing in this
|
|
15
16
|
track touches a real project.
|
|
@@ -9,7 +9,8 @@ will exist with more than one intent inside it and a roadmap file describing the
|
|
|
9
9
|
|
|
10
10
|
## Before you start
|
|
11
11
|
|
|
12
|
-
Run `/plastic-update` first, so the commands below match what is
|
|
12
|
+
Run `/plastic-update` first (`$plastic-update` on Codex), so the commands below match what is
|
|
13
|
+
actually installed.
|
|
13
14
|
|
|
14
15
|
Work in a sandbox: this whole track is a walked example. It creates a real project directory
|
|
15
16
|
and a real roadmap file on disk, but the project is a throwaway one made for learning, not
|