@zalom/plastic 1.3.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/PLASTIC-reference.md +8 -6
- package/PLASTIC.md +52 -14
- package/hooks/hooks.json +5 -0
- package/hooks/links-gate +3 -0
- package/package.json +1 -1
- package/scripts/codex-hook +122 -8
- package/scripts/dashboard.rb +323 -71
- package/scripts/doctor.rb +393 -58
- package/scripts/end-intent +347 -43
- package/scripts/hook-links-gate +74 -0
- package/scripts/hook-lock-gate +8 -3
- package/scripts/install.rb +51 -6
- package/scripts/lib/bridge.rb +105 -27
- package/scripts/lib/config_asks.rb +110 -0
- package/scripts/lib/graph_rebuild.rb +30 -6
- package/scripts/lib/hook_registry.rb +34 -3
- package/scripts/lib/installer_core.rb +70 -13
- package/scripts/lib/intent_validator.rb +38 -10
- package/scripts/lib/links_gate.rb +140 -0
- package/scripts/lib/links_projection.rb +71 -12
- package/scripts/lib/lock.rb +186 -11
- package/scripts/lib/power_tools.rb +57 -14
- package/scripts/lib/project_validator.rb +113 -0
- package/scripts/lib/qmd_hook.rb +12 -8
- package/scripts/lib/restore_intent_v1.rb +154 -0
- package/scripts/lib/roadmap_queue.rb +1 -1
- package/scripts/lib/roadmap_savepoint.rb +38 -10
- package/scripts/lib/store_discovery.rb +77 -0
- package/scripts/lib/store_provisioning.rb +21 -12
- package/scripts/new-intent +10 -12
- package/scripts/plastic-lock +76 -9
- package/scripts/project-links +132 -35
- package/scripts/provision-project-store +18 -5
- package/scripts/read-config +1 -0
- package/scripts/rebuild-graph +42 -17
- package/scripts/restore-intent-v1 +288 -0
- package/scripts/roadmap-next +9 -2
- package/scripts/roadmap-savepoint +9 -1
- package/scripts/update.rb +50 -1
- package/scripts/validate-intent +3 -1
- package/scripts/validate-project +53 -0
- package/scripts/write-config +105 -0
- package/skills/auto/SKILL.md +45 -16
- package/skills/auto/references/agent-architecture.md +7 -0
- package/skills/auto/references/end-tail.md +27 -13
- 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/install/SKILL.md +4 -4
- package/skills/intent-creating/SKILL.md +5 -0
- package/skills/intent-ending/SKILL.md +49 -36
- 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/project-creating/SKILL.md +29 -1
- package/skills/releasing/SKILL.md +37 -19
- package/skills/roadmap/SKILL.md +9 -7
- package/skills/roadmap/references/file-format.md +14 -10
- package/skills/roadmap/references/operations.md +22 -18
- package/skills/roadmap-continuing/SKILL.md +5 -5
- package/skills/roadmap-continuing/evals/evals.json +3 -3
- package/skills/roadmap-continuing/references/liveness-ranking.md +6 -5
- 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 +12 -11
- package/skills/update/SKILL.md +30 -17
- package/templates/roadmap.md +8 -8
package/skills/auto/SKILL.md
CHANGED
|
@@ -39,7 +39,7 @@ ruby ~/.plastic/scripts/roadmap-next --roadmaps-dir <tier>/roadmaps
|
|
|
39
39
|
|
|
40
40
|
Branch on `state`:
|
|
41
41
|
- `dispatchable`: work its `dispatchable_queue` in `rank` order (the head is the next batch
|
|
42
|
-
entry). These are the current batch's `queued` intents, parallel-safe within the
|
|
42
|
+
entry). These are the current batch's `queued` intents, parallel-safe within the batch.
|
|
43
43
|
- `in_flight`: the frontier batch is still delivering. Report it and wait. Do NOT dispatch a
|
|
44
44
|
later batch and do NOT fall through to the dashboard, the roadmap is live.
|
|
45
45
|
- `none` or `exhausted`: no roadmap, or nothing left to dispatch. Fall back to the dashboard
|
|
@@ -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
|
|
|
@@ -305,19 +328,25 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
305
328
|
- Update `chain` in the current intent's frontmatter
|
|
306
329
|
6. Run the mechanical close through `plastic-intent-ending`: it owns steps 1-6 of the Done
|
|
307
330
|
procedure (outcome/INDEX/savepoint/commit, disarm, and the QMD reindex last) as ONE
|
|
308
|
-
delegation, not five separate one-liners restated here.
|
|
309
|
-
|
|
310
|
-
|
|
331
|
+
delegation, not five separate one-liners restated here. `scripts/end-intent` now performs
|
|
332
|
+
steps 1-5 itself, INCLUDING disarm (worktree release plus clearing `delivery.lock`): a
|
|
333
|
+
single call closes the intent AND clears its lock, so exit 0 means both are done. Pass
|
|
334
|
+
`--session` (this session's id, or rely on the `CLAUDE_CODE_SESSION_ID` fallback) so
|
|
335
|
+
disarm resolves the right bridge, and `--index-note` with a rich Completed/Abandoned entry
|
|
336
|
+
description (mode/tier, what shipped or why abandoned, suite result):
|
|
311
337
|
```bash
|
|
312
338
|
ruby ~/.plastic/scripts/end-intent --store <store_path> --id <ID> --disposition delivered \
|
|
339
|
+
--session "$CLAUDE_CODE_SESSION_ID" \
|
|
313
340
|
--index-note "<mode, tier>; <what shipped>; <suite result>"
|
|
314
341
|
```
|
|
315
|
-
(Use `--disposition abandoned` when the intent is being moved to `## Abandoned`.)
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
342
|
+
(Use `--disposition abandoned` when the intent is being moved to `## Abandoned`.) A
|
|
343
|
+
non-zero exit needs attention before moving on: 4 means a live foreign session holds the
|
|
344
|
+
lock (back off), 5 means the code worktree is dirty (commit/stash first, or pass
|
|
345
|
+
`--discard-worktree-changes` deliberately), 3 means disarm ran but the lock is still
|
|
346
|
+
present (run `/plastic-doctor check the lock status`). Only Step 6 (QMD reindex, async,
|
|
347
|
+
last) remains a separate action after this call succeeds. Never leave an orphaned
|
|
348
|
+
worktree; run `git worktree prune` on a stale reference. If any of this ever needs to
|
|
349
|
+
change, change `plastic-intent-ending`, not this skill.
|
|
321
350
|
7. Notify user (Done briefing): brief per `references/human-report-contract.md`
|
|
322
351
|
(State: the delivered impact; Risk: residual risk; Call: the decision left to you, merge,
|
|
323
352
|
release, or accept). See `outcome.md` for details.
|
|
@@ -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.
|
|
@@ -33,19 +33,33 @@ purge-eligible. Disarming also purges stale bridge files from the temp directory
|
|
|
33
33
|
automatically (it keeps the current bridge, any live run, and any bridge whose intent
|
|
34
34
|
still holds a delivery lock), so no manual `/tmp` cleanup is needed.
|
|
35
35
|
|
|
36
|
-
**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
`
|
|
48
|
-
|
|
36
|
+
**Mechanized since intent 188.** `scripts/end-intent` performs this disarm itself, as its
|
|
37
|
+
own step 5, after steps 1-4 (outcome/INDEX/savepoint/commit) commit. No agent needs to run
|
|
38
|
+
a separate `Bridge.disarm_auto` one-liner any more on the auto mode / curator path: the
|
|
39
|
+
single `end-intent` call in `SKILL.md`'s Completion section already does it. A pre-flight
|
|
40
|
+
lock guard (before anything is written) refuses on a live foreign session (exit 4) and
|
|
41
|
+
reclaims a stale foreign lock automatically (audited to savepoint.md); a dirty code
|
|
42
|
+
worktree refuses before removal (exit 5, `--discard-worktree-changes` overrides
|
|
43
|
+
deliberately); and the durable lock file is checked again after disarm, never merely
|
|
44
|
+
trusted (exit 3 if it is somehow still present).
|
|
45
|
+
|
|
46
|
+
**Worktree cleanup (mandatory, intent 73c3).** `end-intent`'s step 5 calls
|
|
47
|
+
`Bridge.disarm_auto` by default, which calls `Worktree.release`, which removes both
|
|
48
|
+
per-intent worktrees (the code worktree under `<repo>/.claude/worktrees/{id}--{slug}` and
|
|
49
|
+
the paired store worktree under `<plastic_home>/.worktrees/{id}--{slug}`), prunes both
|
|
50
|
+
repos, and clears the worktree block from the bridge. This is the plain remove path: the
|
|
51
|
+
disarm route does NOT merge, so use it only when no release merges the branch (the branch
|
|
52
|
+
survives and can be reclaimed).
|
|
53
|
+
|
|
54
|
+
When the work is being shipped through a release, do NOT rely on this plain remove.
|
|
55
|
+
`skills/releasing/SKILL.md` reorders its own two steps for exactly this reason (intent 188,
|
|
56
|
+
D7): its worktree-merge step now runs BEFORE its `end-intent` call, merging the intent's
|
|
57
|
+
code branch (`plastic/{id}--{slug}`) back to the repo's default branch BEFORE the worktree
|
|
58
|
+
is removed, via `Worktree.finish(bridge_data, merge: true)` (merge-then-remove), so the
|
|
59
|
+
integrated work is not lost. By the time `end-intent`'s own step 5 runs afterward, the
|
|
60
|
+
worktree is already gone (a harmless no-op) and only the delivery lock is left to clear,
|
|
61
|
+
correctly, for the first time on that path. Never leave an orphaned worktree, and run
|
|
62
|
+
`git worktree prune` if you hit a stale reference.
|
|
49
63
|
|
|
50
64
|
## QMD reindex ordering rationale
|
|
51
65
|
|
|
@@ -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/install/SKILL.md
CHANGED
|
@@ -103,10 +103,10 @@ Non-interactive sessions (no tty) skip the question entirely: the install ships
|
|
|
103
103
|
shipped default, advisor enabled with no `--advisor` flag (the `plastic-agent-advisor`
|
|
104
104
|
skill's own routing falls back to `plastic-faux-advisor` at consult time).
|
|
105
105
|
|
|
106
|
-
Update flow:
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
Update flow: pending config questions, including this one, are now announced
|
|
107
|
+
generically by `plastic-update`'s Step 2, sourced from `config_asks.yml` - not
|
|
108
|
+
duplicated here. A value already set by either path is never re-asked by the
|
|
109
|
+
other.
|
|
110
110
|
|
|
111
111
|
**Statusline**
|
|
112
112
|
|
|
@@ -69,6 +69,11 @@ Having a "parent" in mind does NOT automatically mean branch. Choose by meaning:
|
|
|
69
69
|
`--sources` when it was genuinely created from / emerged from that intent's lifecycle.
|
|
70
70
|
Topic similarity alone is not a `sources` edge.
|
|
71
71
|
|
|
72
|
+
When a branch intent exists because a late ruling arrived AFTER its parent was already
|
|
73
|
+
completed (the owner's late-ruling rule), the parent is restored to v1 via
|
|
74
|
+
`scripts/restore-intent-v1`, never by a hand-run `git checkout`/revert. See `PLASTIC.md` >
|
|
75
|
+
Terminal immutability for the rule and the tool.
|
|
76
|
+
|
|
72
77
|
`## Links` is a DERIVED view of `sources`/`chain`. Never hand-write a `## Links` line; add the
|
|
73
78
|
frontmatter edge and reproject. Links are decided by context influence (a `chain` edge has a high
|
|
74
79
|
bar: the candidate's context must materially help deliver this intent), not by shared files or a
|
|
@@ -24,15 +24,16 @@ failure branch: only outcome.md content and the INDEX section differ.
|
|
|
24
24
|
| 2 | INDEX.md terminal move (Active -> Completed/Abandoned) | `scripts/end-intent` |
|
|
25
25
|
| 3 | savepoint `Done` bookend | `scripts/end-intent` |
|
|
26
26
|
| 4 | store auto-commit | `scripts/end-intent` |
|
|
27
|
-
| 5 | disarm (worktree + lock) |
|
|
27
|
+
| 5 | disarm (worktree + lock) | `scripts/end-intent` (intent 188) |
|
|
28
28
|
| 6 | QMD reindex, async, LAST | You |
|
|
29
29
|
| 7 | EM-to-CTO report | You |
|
|
30
30
|
|
|
31
|
-
Steps 1-
|
|
32
|
-
exactly what the failure mode this intent fixes looked like (releasing hand
|
|
31
|
+
Steps 1-5 are ONE callable script call, not several separate one-liners: this
|
|
32
|
+
is exactly what the failure mode this intent fixes looked like (releasing hand
|
|
33
33
|
authored the close in prose and dropped the savepoint bookend for two real
|
|
34
|
-
deliveries
|
|
35
|
-
|
|
34
|
+
deliveries; separately, one session delivered four intents back to back and
|
|
35
|
+
never ran the old step-5 one-liner at all, intent 188). Never restate
|
|
36
|
+
outcome/INDEX/savepoint/disarm prose inline again; call `scripts/end-intent`.
|
|
36
37
|
|
|
37
38
|
### Step 0. Precondition (the gate is section-blind, not selective)
|
|
38
39
|
|
|
@@ -55,7 +56,7 @@ for orchestrator-owned or completion-tracking items.
|
|
|
55
56
|
(tick it if the described work is actually done, or do the remaining
|
|
56
57
|
work); do not attempt outcome.md and fight the gate's deny.
|
|
57
58
|
|
|
58
|
-
### Step 1-
|
|
59
|
+
### Step 1-5. Run `scripts/end-intent`
|
|
59
60
|
|
|
60
61
|
First author outcome.md for real (never leave the scaffold placeholder in
|
|
61
62
|
place): copy `templates/outcome.md`, set the frontmatter to
|
|
@@ -72,40 +73,52 @@ Then call the script once:
|
|
|
72
73
|
```bash
|
|
73
74
|
ruby ~/.plastic/scripts/end-intent \
|
|
74
75
|
--store <store_path> --id <intent_id> --disposition delivered|abandoned \
|
|
76
|
+
--session "$CLAUDE_CODE_SESSION_ID" \
|
|
75
77
|
--outcome-summary "<one-line ## Outcome summary for the intent file>" \
|
|
76
78
|
--index-note "<rich Completed/Abandoned entry description>"
|
|
77
79
|
```
|
|
78
80
|
|
|
79
|
-
This does all of steps 1-
|
|
81
|
+
This does all of steps 1-5 in order: guards outcome.md (refuses a missing,
|
|
80
82
|
still-placeholder, or wrong-disposition file with exit 2 and authors
|
|
81
83
|
nothing), stamps the intent file's `## Outcome` section, moves the INDEX.md
|
|
82
84
|
line from `## Active` to `## Completed` or `## Abandoned` (dated today,
|
|
83
|
-
idempotent
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
85
|
+
idempotent, accepting either a real em dash or a plain hyphen as the id/
|
|
86
|
+
title separator on read while always emitting the real em dash on write)
|
|
87
|
+
with the `--index-note` text appended after the date so the entry stays
|
|
88
|
+
rich, appends the savepoint `Done` bookend, commits the store repo, and
|
|
89
|
+
disarms (releases the code worktree and clears `delivery.lock`, verified
|
|
90
|
+
against the durable lock file on disk, never merely trusted). Omit
|
|
91
|
+
`--index-note` for a thin id+date entry, add `--no-commit` when a separate
|
|
92
|
+
commit step already covers the store (this never skips disarm), and
|
|
93
|
+
`--dry-run` to preview steps 1-5 with no writes.
|
|
94
|
+
|
|
95
|
+
A pre-flight lock guard runs before anything is written: it resolves the
|
|
96
|
+
calling session (`--session`, else `CLAUDE_CODE_SESSION_ID`, else the
|
|
97
|
+
existing lock's own recorded owner, else a no-op) and checks it against any
|
|
98
|
+
existing `delivery.lock`. A live foreign session refuses the whole run
|
|
99
|
+
(exit 4, nothing written); a stale foreign lock is reclaimed automatically
|
|
100
|
+
(audited to savepoint.md) and the run proceeds as the new owner. Before
|
|
101
|
+
removing the worktree, step 5 also refuses on an unexpectedly dirty code
|
|
102
|
+
worktree (exit 5, naming the worktree path) rather than force-discarding
|
|
103
|
+
uncommitted changes; pass `--discard-worktree-changes` only when you mean
|
|
104
|
+
to override that deliberately.
|
|
105
|
+
|
|
106
|
+
On the auto mode / curator path (no release), this single call performs the
|
|
107
|
+
FULL disarm (plain worktree remove, since the branch survives for later
|
|
108
|
+
reclaim). On a release-shipped path, `skills/releasing/SKILL.md` merges and
|
|
109
|
+
removes the worktree FIRST (its own step 8, merge-then-remove) before ever
|
|
110
|
+
calling this script, so by the time this call's step 5 runs, the worktree is
|
|
111
|
+
already gone (a harmless no-op) and only the lock is left to clear,
|
|
112
|
+
correctly, for the first time on that path (D7).
|
|
113
|
+
|
|
114
|
+
Exit codes: 0 success (the intent is closed AND its delivery lock is gone);
|
|
115
|
+
1 a usage or resolution failure, OR an INDEX id that resolves to neither
|
|
116
|
+
`## Active` nor the terminal section; 2 the outcome.md guard refusing (fix
|
|
117
|
+
outcome.md and re-run, nothing was written); 3 steps 1-4 already committed
|
|
118
|
+
but disarm could not verify the lock is gone afterward (run `/plastic-doctor
|
|
119
|
+
check the lock status`); 4 a live foreign session holds the lock (back off);
|
|
120
|
+
5 the code worktree is dirty (commit/stash first, or pass
|
|
121
|
+
`--discard-worktree-changes` deliberately).
|
|
109
122
|
|
|
110
123
|
### Step 6. QMD reindex, LAST
|
|
111
124
|
|
|
@@ -146,6 +159,6 @@ historical record of what was planned.
|
|
|
146
159
|
|
|
147
160
|
`plastic-releasing`, `plastic-auto`, the curator agent, `store-curating`, and
|
|
148
161
|
`store-indexing` all delegate their mechanical close to this skill (or call
|
|
149
|
-
`scripts/end-intent` directly for steps 1-
|
|
150
|
-
outcome/INDEX/savepoint prose inline any more; if you find one that
|
|
151
|
-
that surface has drifted and should route here instead.
|
|
162
|
+
`scripts/end-intent` directly for steps 1-5). None of them restate the
|
|
163
|
+
outcome/INDEX/savepoint/disarm prose inline any more; if you find one that
|
|
164
|
+
does, that surface has drifted and should route here instead.
|
|
@@ -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.
|