@zalom/plastic 1.0.0-beta.35 → 1.0.0-beta.36
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 +21 -145
- package/agents/plastic-brainstorming.md +2 -6
- package/agents/plastic-enforcer.md +11 -6
- package/agents/plastic-executor.md +2 -6
- package/agents/plastic-future-intent-researcher.md +2 -7
- package/agents/plastic-intent-curator.md +2 -10
- package/agents/plastic-intent-discovery.md +2 -7
- package/agents/plastic-planner.md +2 -6
- package/agents/plastic-spec-specialist.md +2 -6
- package/package.json +2 -1
- package/scripts/lib/bridge.rb +73 -2
- package/scripts/lib/installer_core.rb +1 -0
- package/scripts/lib/power_tools.rb +18 -16
- package/skills/auto/SKILL.md +40 -38
- package/skills/auto/references/end-tail.md +56 -0
- package/skills/auto/references/human-report-contract.md +55 -0
- package/skills/brainstorming/SKILL.md +7 -34
- package/skills/brainstorming/references/design-principles.md +49 -0
- package/skills/creating-intent/SKILL.md +5 -26
- package/skills/creating-project/SKILL.md +11 -74
- package/skills/creating-project/references/project-scaffolding.md +97 -0
- package/skills/dashboard/SKILL.md +2 -17
- package/skills/dashboard/references/classification.md +22 -0
- package/skills/releasing/SKILL.md +14 -46
- package/skills/releasing/references/promotion-and-tagging.md +60 -0
- package/skills/writing-plans/SKILL.md +10 -88
- package/skills/writing-plans/references/plan-format.md +102 -0
package/scripts/lib/bridge.rb
CHANGED
|
@@ -921,6 +921,49 @@ module Bridge
|
|
|
921
921
|
"(blocked edit: #{file_abs})"
|
|
922
922
|
end
|
|
923
923
|
|
|
924
|
+
# --- Solo-mode detection (intent 128) ---------------------------------------
|
|
925
|
+
#
|
|
926
|
+
# Positive-only confirmation that exactly one session is delivering, from the
|
|
927
|
+
# durable delivery.lock files (never the /tmp bridge cache, D2). Used to relax
|
|
928
|
+
# the two ARBITRATION gates (lock_gate_decision, worktree_gate_decision) from
|
|
929
|
+
# a hard deny to an advisory allow when there is nothing to arbitrate.
|
|
930
|
+
#
|
|
931
|
+
# SOLO iff exactly ONE fresh delivery.lock exists across scan_roots, that
|
|
932
|
+
# lock's owner_session equals the resolved session, and its delegates array
|
|
933
|
+
# is empty. Any ambiguity (more than one fresh lock, including several under
|
|
934
|
+
# the SAME owner_session, which reads as parallel-in-play), a foreign owner,
|
|
935
|
+
# a non-empty delegates array, a blank/unresolvable session, or any error
|
|
936
|
+
# during the scan all return false (fail-closed direction preserved).
|
|
937
|
+
def self.solo_delivery?(scan_roots:, session:, ttl: Lock::TTL_SECONDS, now: Time.now)
|
|
938
|
+
return false if blank?(session)
|
|
939
|
+
|
|
940
|
+
lock_dirs = Array(scan_roots).compact.flat_map { |root|
|
|
941
|
+
Dir.glob(File.join(File.expand_path(root), "*", "delivery.lock"))
|
|
942
|
+
}.uniq.map { |lock_file| File.dirname(lock_file) }
|
|
943
|
+
|
|
944
|
+
fresh_dirs = lock_dirs.select { |dir| Lock.fresh?(dir, ttl: ttl, now: now) }
|
|
945
|
+
fresh_locks = fresh_dirs.map { |dir| Lock.read(dir) }
|
|
946
|
+
|
|
947
|
+
# A fresh-but-unreadable (corrupt) lock is real ambiguity, not an absence:
|
|
948
|
+
# dropping it via filter_map could leave exactly one READABLE lock and
|
|
949
|
+
# misconfirm solo while a second, unreadable-but-live lock is in play.
|
|
950
|
+
# Any unreadable fresh lock keeps this fail-closed (review finding 2).
|
|
951
|
+
return false if fresh_locks.any?(&:nil?)
|
|
952
|
+
return false unless fresh_locks.length == 1
|
|
953
|
+
|
|
954
|
+
lock = fresh_locks.first
|
|
955
|
+
lock["owner_session"].to_s == session.to_s && Array(lock["delegates"]).empty?
|
|
956
|
+
rescue StandardError
|
|
957
|
+
false
|
|
958
|
+
end
|
|
959
|
+
|
|
960
|
+
# One terse advisory line (no em-dashes), then ALLOW (nil). Shared by both
|
|
961
|
+
# arbitration gates so a relaxed deny always logs the same shape.
|
|
962
|
+
def self.solo_allow(id, reason)
|
|
963
|
+
$stderr.puts "plastic: solo delivery confirmed for intent #{id} (#{reason}); allowing"
|
|
964
|
+
nil
|
|
965
|
+
end
|
|
966
|
+
|
|
924
967
|
# --- Fail-closed lock gate (intent 96) -------------------------------------
|
|
925
968
|
|
|
926
969
|
# Returns a reason String to BLOCK, or nil to ALLOW. Decides from the
|
|
@@ -931,7 +974,7 @@ module Bridge
|
|
|
931
974
|
# target's lock names as owner or delegate (even when stale: a stale lock is
|
|
932
975
|
# still its owner's until an explicit takeover).
|
|
933
976
|
def self.lock_gate_decision(bridge_data, file_path, session: nil,
|
|
934
|
-
ttl: Lock::TTL_SECONDS, now: Time.now)
|
|
977
|
+
ttl: Lock::TTL_SECONDS, now: Time.now, home: Dir.home)
|
|
935
978
|
return nil if blank?(file_path)
|
|
936
979
|
|
|
937
980
|
target_dir = intent_dir_for(file_path)
|
|
@@ -943,23 +986,34 @@ module Bridge
|
|
|
943
986
|
sess = session
|
|
944
987
|
sess = bridge_data["session"] if blank?(sess) && bridge_data.is_a?(Hash)
|
|
945
988
|
|
|
989
|
+
# Solo-mode detection (intent 128): scan this intent's store plus the
|
|
990
|
+
# global store under `home` for fresh delivery locks. Computed once; used
|
|
991
|
+
# at every arbitration deny below to relax a hard deny to an advisory
|
|
992
|
+
# allow when solo delivery is positively confirmed.
|
|
993
|
+
scan_roots = [store, File.join(File.expand_path(home), ".plastic", "store")]
|
|
994
|
+
solo = solo_delivery?(scan_roots: scan_roots, session: sess, ttl: ttl, now: now)
|
|
995
|
+
|
|
946
996
|
lock = Lock.read(target_dir)
|
|
947
997
|
if lock
|
|
948
998
|
return nil if Lock.authorized?(lock, sess)
|
|
949
999
|
if Lock.fresh?(target_dir, ttl: ttl, now: now)
|
|
1000
|
+
return solo_allow(id, "fresh delivery lock") if solo
|
|
950
1001
|
return "intent #{id} delivery lock is held by session " \
|
|
951
1002
|
"#{lock['owner_session']}. Back off; if you are the owner's " \
|
|
952
1003
|
"subagent, the owner must run: plastic-lock delegate " \
|
|
953
1004
|
"--intent-dir #{target_dir} --session <your-session-id>. " \
|
|
954
1005
|
"Inspect with /plastic-lock status"
|
|
955
1006
|
end
|
|
1007
|
+
return solo_allow(id, "stale delivery lock") if solo
|
|
956
1008
|
return "intent #{id} has a stale delivery lock (owner " \
|
|
957
1009
|
"#{lock['owner_session']}); run /plastic-lock reclaim to take " \
|
|
958
1010
|
"it over, or /plastic-lock fix"
|
|
959
1011
|
end
|
|
960
1012
|
if Lock.corrupt?(target_dir)
|
|
1013
|
+
return solo_allow(id, "unreadable delivery.lock") if solo
|
|
961
1014
|
return "delivery.lock for intent #{id} is unreadable; run /plastic-lock fix"
|
|
962
1015
|
end
|
|
1016
|
+
return solo_allow(id, "no delivery lock") if solo
|
|
963
1017
|
"no delivery lock held for intent #{id}; run /plastic-intent-starting " \
|
|
964
1018
|
"to lock and begin"
|
|
965
1019
|
end
|
|
@@ -1012,6 +1066,19 @@ module Bridge
|
|
|
1012
1066
|
under_own_intent = intent_dir_abs &&
|
|
1013
1067
|
(file_abs == intent_dir_abs || file_abs.start_with?("#{intent_dir_abs}/"))
|
|
1014
1068
|
|
|
1069
|
+
# Solo-mode detection (intent 128): current session first, else the
|
|
1070
|
+
# bridge's own session; scan roots are this intent's store, the global
|
|
1071
|
+
# store under `home`, AND the EDIT TARGET's own store (when the target
|
|
1072
|
+
# lives inside a store dir), so a live foreign lock on the intent being
|
|
1073
|
+
# edited is never invisible to the scan just because it belongs to a
|
|
1074
|
+
# different project than the acting bridge's own store (review finding 1;
|
|
1075
|
+
# duplicate roots are harmless, solo_delivery? dedupes). Computed once;
|
|
1076
|
+
# used by both rules below.
|
|
1077
|
+
sess = blank?(current_session) ? bridge_data["session"] : current_session
|
|
1078
|
+
target_store = parse_store_target(file_abs, plastic_home)&.fetch(:store, nil)
|
|
1079
|
+
scan_roots = [store, File.join(plastic_home, "store"), target_store]
|
|
1080
|
+
solo = solo_delivery?(scan_roots: scan_roots, session: sess)
|
|
1081
|
+
|
|
1015
1082
|
# Rule 1 (fixed in intent 108, D7): confinement applies ONLY to paths
|
|
1016
1083
|
# inside the project repo. The repo root is derived from the provisioned
|
|
1017
1084
|
# code worktree path, which is <repo>/.claude/worktrees/{id}--{slug} by
|
|
@@ -1029,6 +1096,7 @@ module Bridge
|
|
|
1029
1096
|
inside_code = file_abs == code_abs || file_abs.start_with?("#{code_abs}/")
|
|
1030
1097
|
if inside_repo && !inside_code
|
|
1031
1098
|
id = intent_info["id"]
|
|
1099
|
+
return solo_allow(id, "worktree confinement") if solo
|
|
1032
1100
|
return "intent #{id} is isolated to its worktree - edit project code " \
|
|
1033
1101
|
"inside #{code_abs}, not the shared checkout. (blocked edit: #{file_abs})"
|
|
1034
1102
|
end
|
|
@@ -1040,7 +1108,10 @@ module Bridge
|
|
|
1040
1108
|
reason = non_owner_store_edit_reason(file_abs, plastic_home, intent_dir_abs,
|
|
1041
1109
|
home: home, current_session: current_session,
|
|
1042
1110
|
own_session: bridge_data["session"])
|
|
1043
|
-
|
|
1111
|
+
if reason
|
|
1112
|
+
return solo_allow(intent_info["id"], "non-owner store edit") if solo
|
|
1113
|
+
return reason
|
|
1114
|
+
end
|
|
1044
1115
|
end
|
|
1045
1116
|
|
|
1046
1117
|
nil
|
|
@@ -228,6 +228,7 @@ class InstallerCore
|
|
|
228
228
|
def core_files
|
|
229
229
|
{
|
|
230
230
|
"PLASTIC.md" => "PLASTIC.md",
|
|
231
|
+
"PLASTIC-reference.md" => "PLASTIC-reference.md",
|
|
231
232
|
"deprecations.yml" => "deprecations.yml",
|
|
232
233
|
"scripts/folgezettel-id" => "scripts/folgezettel-id",
|
|
233
234
|
"scripts/read-config" => "scripts/read-config",
|
|
@@ -52,24 +52,26 @@ module PowerTools
|
|
|
52
52
|
false
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
if qmd?(detector: qmd_detector)
|
|
61
|
-
lines << "QMD is available: prefer `qmd search` / `qmd query` over the " \
|
|
62
|
-
"`plastic-*` collections to check for existing or related intents " \
|
|
63
|
-
"before treating work as new."
|
|
64
|
-
end
|
|
55
|
+
QMD_OBLIGATION = "prefer `qmd search` / `qmd query` over the `plastic-*` " \
|
|
56
|
+
"collections to check for existing or related intents before " \
|
|
57
|
+
"treating work as new"
|
|
58
|
+
SERENA_OBLIGATION = "prefer its symbolic tools (find_symbol / get_symbols_overview / " \
|
|
59
|
+
"find_referencing_symbols) for code navigation"
|
|
65
60
|
|
|
61
|
+
# Recommendation text for whichever tools are present, or nil when none are.
|
|
62
|
+
# Both present collapse to ONE combined line naming both obligations (no
|
|
63
|
+
# embedded newline); one present returns that tool's own line; neither
|
|
64
|
+
# returns nil.
|
|
65
|
+
def mandate(cwd:, qmd_detector: QmdSync.method(:detect), serena_detector: nil)
|
|
66
|
+
qmd_present = qmd?(detector: qmd_detector)
|
|
66
67
|
serena_present = serena_detector ? !!serena_detector.call : serena?(cwd: cwd)
|
|
67
|
-
if serena_present
|
|
68
|
-
lines << "Serena is available: prefer its symbolic tools (find_symbol / " \
|
|
69
|
-
"get_symbols_overview / find_referencing_symbols) for code navigation."
|
|
70
|
-
end
|
|
71
68
|
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
if qmd_present && serena_present
|
|
70
|
+
"QMD and Serena are available: #{QMD_OBLIGATION}, and #{SERENA_OBLIGATION}."
|
|
71
|
+
elsif qmd_present
|
|
72
|
+
"QMD is available: #{QMD_OBLIGATION}."
|
|
73
|
+
elsif serena_present
|
|
74
|
+
"Serena is available: #{SERENA_OBLIGATION}."
|
|
75
|
+
end
|
|
74
76
|
end
|
|
75
77
|
end
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -76,17 +76,10 @@ ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
|
76
76
|
Replace `<ID>`, `<STORE>` (e.g. `~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
|
|
77
77
|
`<dir>` (the `ID--slug` directory), and `<name>`. The first argument is the session id you
|
|
78
78
|
want the bridge keyed by: pass the hook stdin `session_id` when you have it, otherwise
|
|
79
|
-
`ENV["CLAUDE_CODE_SESSION_ID"]`, otherwise `nil`.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
never needs a non-empty session env var to function. Arming prints a one-line notice to
|
|
84
|
-
stderr when it falls through to the derived key.
|
|
85
|
-
|
|
86
|
-
Arming acquires the durable `delivery.lock` in the intent dir, keyed by that resolved
|
|
87
|
-
session. Ownership is session-keyed, not process-keyed, so the arm one-liner exiting
|
|
88
|
-
immediately is fine by construction: the lock stays yours for every later tool call in this
|
|
89
|
-
session. A failed arm raises with a message naming the resolving `plastic-lock` verb.
|
|
79
|
+
`ENV["CLAUDE_CODE_SESSION_ID"]`, otherwise `nil`. Arming always succeeds and acquires the
|
|
80
|
+
durable `delivery.lock` in the intent dir. For the `resolve_session` fallback chain
|
|
81
|
+
(why arming never needs a non-empty session env var, and what the lock ownership model
|
|
82
|
+
implies for later tool calls) read `references/end-tail.md`.
|
|
90
83
|
|
|
91
84
|
**Hard rule for the rest of this run:** do NOT edit project code (anything outside the
|
|
92
85
|
intent directory / `~/.plastic/`) until `plan.md` AND `checklist.md` exist for the intent.
|
|
@@ -165,6 +158,10 @@ Filesystem fallback (ledger missing only):
|
|
|
165
158
|
|
|
166
159
|
Announce which stage you're entering and why.
|
|
167
160
|
|
|
161
|
+
Notify user (What briefing): brief per `references/human-report-contract.md`
|
|
162
|
+
(State: the work picked up and why it matters now; Risk: scope uncertainty; Call: confirm
|
|
163
|
+
this is worth doing, or proceed).
|
|
164
|
+
|
|
168
165
|
## Why Completion (Autonomous)
|
|
169
166
|
|
|
170
167
|
When entering at Why stage:
|
|
@@ -179,6 +176,9 @@ When entering at Why stage:
|
|
|
179
176
|
5. Make decisions — pick best option, document in `## Context > ### Decisions` with rationale
|
|
180
177
|
6. Log all autonomous decisions in `## Insights` with `(autonomous)` marker: "Decision: chose X because Y (autonomous)"
|
|
181
178
|
7. Write `spec.md` — consolidated specification
|
|
179
|
+
8. Notify user (Why briefing): brief per `references/human-report-contract.md`
|
|
180
|
+
(State: the approach chosen, one line; Risk: the main trade-off; Call: the one decision
|
|
181
|
+
needed, approve or pick an option).
|
|
182
182
|
|
|
183
183
|
Then proceed to How.
|
|
184
184
|
|
|
@@ -193,6 +193,9 @@ only (S/M leave the directory empty).
|
|
|
193
193
|
2. Otherwise, write `plan.md` directly — implementation plan with numbered tasks
|
|
194
194
|
3. Write `ACTION_N.md` files into the existing `actions/` directory (one per task, self-contained) — L only
|
|
195
195
|
4. Write `checklist.md` — execution registry with checkboxes covering all actions
|
|
196
|
+
5. Notify user (How briefing): brief per `references/human-report-contract.md`
|
|
197
|
+
(State: the plan shape, task count and what it builds; Risk: the riskiest task or
|
|
198
|
+
dependency; Call: approve the plan to build).
|
|
196
199
|
|
|
197
200
|
Then proceed to Exec.
|
|
198
201
|
|
|
@@ -216,6 +219,9 @@ If the plan calls for creating a new project (the intent is an implementation in
|
|
|
216
219
|
4. Check off items in `checklist.md` as completed
|
|
217
220
|
5. Append observations to `## Insights` with `(autonomous)` marker
|
|
218
221
|
6. Sub-agents can be spawned for parallel actions (one agent per action)
|
|
222
|
+
7. Notify user (Exec briefing): brief per `references/human-report-contract.md`
|
|
223
|
+
(State: what got built and the test result; Risk: residual failures or deviations;
|
|
224
|
+
Call: go to review, or done).
|
|
219
225
|
|
|
220
226
|
## Permission Model — Safe-by-Default
|
|
221
227
|
|
|
@@ -281,36 +287,27 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
281
287
|
```bash
|
|
282
288
|
ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_CODE_SESSION_ID"], intent_id: "<ID>")'
|
|
283
289
|
```
|
|
284
|
-
Disarm runs the ordered End tail
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
only when no release merges the branch (the branch survives and can be reclaimed).
|
|
296
|
-
|
|
297
|
-
When the work is being shipped through a release, do NOT rely on this plain remove. The
|
|
298
|
-
release path (step 4 above, via `plastic-releasing`) is responsible for merging the intent's
|
|
299
|
-
code branch (`plastic/{id}--{slug}`) back to the repo's default branch BEFORE the worktree is
|
|
300
|
-
removed, so the integrated work is not lost. It does this with `Worktree.finish(bridge_data,
|
|
301
|
-
merge: true)` (merge-then-remove). Never leave an orphaned worktree, and run `git worktree
|
|
302
|
-
prune` if you hit a stale reference.
|
|
303
|
-
9. QMD reindex LAST (canonical End tail). AFTER disarm has released the worktrees, cleared the
|
|
304
|
-
`delivery.lock`, and purged the bridge, refresh the QMD search index for this store (no-op when
|
|
305
|
-
QMD is absent). It runs in the background so it never blocks the turn:
|
|
290
|
+
Disarm runs the ordered End tail (release worktrees, then clear the `delivery.lock`,
|
|
291
|
+
then the bridge becomes purge-eligible) and performs the mandatory worktree cleanup
|
|
292
|
+
(intent 73c3): both per-intent worktrees are removed and both repos pruned. This is
|
|
293
|
+
the plain remove path (no merge); when the work ships through a release, the release
|
|
294
|
+
path merges the branch BEFORE the worktree is removed instead of relying on this step.
|
|
295
|
+
Never leave an orphaned worktree, and run `git worktree prune` if you hit a stale
|
|
296
|
+
reference. For the full ordering rationale and the release-vs-plain-disarm
|
|
297
|
+
distinction, read `references/end-tail.md`.
|
|
298
|
+
9. QMD reindex LAST (canonical End tail), run only after disarm has released the
|
|
299
|
+
worktrees, cleared the `delivery.lock`, and purged the bridge. It runs in the
|
|
300
|
+
background so it never blocks the turn:
|
|
306
301
|
```bash
|
|
307
302
|
ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async
|
|
308
303
|
```
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
10. Notify user
|
|
304
|
+
`<store-root>` is the store that holds this intent (the global store or the project
|
|
305
|
+
store); the command is a no-op when QMD is absent. For why the reindex must be last
|
|
306
|
+
(so the index never references a bridge or lock about to disappear), read
|
|
307
|
+
`references/end-tail.md`.
|
|
308
|
+
10. Notify user (Done briefing): brief per `references/human-report-contract.md`
|
|
309
|
+
(State: the delivered impact; Risk: residual risk; Call: the decision left to you, merge,
|
|
310
|
+
release, or accept). See `outcome.md` for details.
|
|
314
311
|
|
|
315
312
|
## Error Handling
|
|
316
313
|
|
|
@@ -324,3 +321,8 @@ If the agent gets stuck (can't resolve a gap, dependency is missing, tests fail
|
|
|
324
321
|
|
|
325
322
|
- Read `references/agent-architecture.md` for the full team model (the 5-role enforcer-led team, per-stage handoffs, gate ownership, headless note, solo fallback) and the orchestrator hierarchy (Main Orchestrator, Project Orchestrators, coordination loop) when spinning up the team or understanding autonomous delivery scope
|
|
326
323
|
- Read `references/tiers.md` for the extended per-tier walkthrough (S/M/L worked examples, the collapsed one-thinker flow, the QMD-skip case for S) and rationale
|
|
324
|
+
- Read `references/human-report-contract.md` for the human-facing per-stage briefing (the
|
|
325
|
+
State/Risk/Call skeleton used at each "Notify user" step above, and how it differs from the
|
|
326
|
+
internal `agent-report-contract.md`)
|
|
327
|
+
- Read `references/end-tail.md` for the `resolve_session` fallback chain and the disarm
|
|
328
|
+
ordering / worktree cleanup / QMD reindex rationale referenced above
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# End-Tail Mechanics: resolve_session and Disarm Ordering
|
|
2
|
+
|
|
3
|
+
Deep WHY/mechanics detail behind two spots in `SKILL.md`: how `arm_auto` resolves a
|
|
4
|
+
session id when arming the gate, and why the End-tail steps in Completion (release
|
|
5
|
+
worktrees, clear the lock, purge the bridge, reindex) run in that exact order.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [resolve_session fallback internals](#resolve_session-fallback-internals)
|
|
10
|
+
- [Disarm ordering and worktree cleanup rationale](#disarm-ordering-and-worktree-cleanup-rationale)
|
|
11
|
+
- [QMD reindex ordering rationale](#qmd-reindex-ordering-rationale)
|
|
12
|
+
|
|
13
|
+
## resolve_session fallback internals
|
|
14
|
+
|
|
15
|
+
`arm_auto` calls `resolve_session`, which picks the first non-empty of: the explicit
|
|
16
|
+
id you pass -> `CLAUDE_CODE_SESSION_ID` -> a deterministic derived key (a hash of the
|
|
17
|
+
store and intent id). It never returns nil, so the gate engages even when every
|
|
18
|
+
session env var is empty; the call never needs a non-empty session env var to
|
|
19
|
+
function. Arming prints a one-line notice to stderr when it falls through to the
|
|
20
|
+
derived key.
|
|
21
|
+
|
|
22
|
+
Arming acquires the durable `delivery.lock` in the intent dir, keyed by that resolved
|
|
23
|
+
session. Ownership is session-keyed, not process-keyed, so the arm one-liner exiting
|
|
24
|
+
immediately is fine by construction: the lock stays yours for every later tool call in
|
|
25
|
+
this session. A failed arm raises with a message naming the resolving `plastic-lock`
|
|
26
|
+
verb.
|
|
27
|
+
|
|
28
|
+
## Disarm ordering and worktree cleanup rationale
|
|
29
|
+
|
|
30
|
+
Disarm runs the ordered End tail: it releases the worktrees first, then clears the
|
|
31
|
+
intent's `delivery.lock` (and the bridge's lock cache), and only then is the bridge
|
|
32
|
+
purge-eligible. Disarming also purges stale bridge files from the temp directory
|
|
33
|
+
automatically (it keeps the current bridge, any live run, and any bridge whose intent
|
|
34
|
+
still holds a delivery lock), so no manual `/tmp` cleanup is needed.
|
|
35
|
+
|
|
36
|
+
**Worktree cleanup (mandatory, intent 73c3).** Disarming performs the worktree release:
|
|
37
|
+
`disarm_auto` calls `Worktree.release`, which removes both per-intent worktrees (the code
|
|
38
|
+
worktree under `<repo>/.claude/worktrees/{id}--{slug}` and the paired store worktree under
|
|
39
|
+
`<plastic_home>/.worktrees/{id}--{slug}`), prunes both repos, and clears the worktree block
|
|
40
|
+
from the bridge. This is the plain remove path: the disarm route does NOT merge, so use it
|
|
41
|
+
only when no release merges the branch (the branch survives and can be reclaimed).
|
|
42
|
+
|
|
43
|
+
When the work is being shipped through a release, do NOT rely on this plain remove. The
|
|
44
|
+
release path (Completion step 4, via `plastic-releasing`) is responsible for merging the
|
|
45
|
+
intent's code branch (`plastic/{id}--{slug}`) back to the repo's default branch BEFORE the
|
|
46
|
+
worktree is removed, so the integrated work is not lost. It does this with
|
|
47
|
+
`Worktree.finish(bridge_data, merge: true)` (merge-then-remove). Never leave an orphaned
|
|
48
|
+
worktree, and run `git worktree prune` if you hit a stale reference.
|
|
49
|
+
|
|
50
|
+
## QMD reindex ordering rationale
|
|
51
|
+
|
|
52
|
+
Completion is the lifecycle event that keeps the search index fresh. `<store-root>` is
|
|
53
|
+
the store that holds this intent (the global store or the project store). The reindex is
|
|
54
|
+
the LAST End-tail step, run after purge, so the index never references a bridge or lock
|
|
55
|
+
that is about to disappear (see PLASTIC.md `## Delivery Isolation and the Single-Owner
|
|
56
|
+
Lock`).
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Human Report Contract (per-stage EM-to-CTO briefing)
|
|
2
|
+
|
|
3
|
+
This doc defines how the orchestrator briefs the human at each of the five stage boundaries
|
|
4
|
+
(What, Why, How, Exec, Done) in auto mode. It is the outward, human-facing counterpart to the
|
|
5
|
+
internal report contract in `references/agent-report-contract.md`. Voice: an engineering
|
|
6
|
+
manager briefing a CTO. Lead with impact, name the risk, leave the decision.
|
|
7
|
+
|
|
8
|
+
## The skeleton
|
|
9
|
+
|
|
10
|
+
One fixed 3-line shape, reused at every stage:
|
|
11
|
+
|
|
12
|
+
1. **State**: what happened and what it means, impact first, one line.
|
|
13
|
+
2. **Risk**: the one thing that could bite, or "nothing flagged."
|
|
14
|
+
3. **Call**: the decision left to you, or the go-ahead I am taking.
|
|
15
|
+
|
|
16
|
+
This is a shape, not a rigid template. Keep the order (State, then Risk, then Call) and keep it
|
|
17
|
+
short. The words can flex to fit the stage.
|
|
18
|
+
|
|
19
|
+
## Per-stage content
|
|
20
|
+
|
|
21
|
+
- **What**: State = the work I picked up and why it matters now. Risk = scope uncertainty.
|
|
22
|
+
Call = confirm this is worth doing, or I proceed.
|
|
23
|
+
- **Why**: State = the approach I chose, one line. Risk = the main trade-off. Call = the one
|
|
24
|
+
decision I need (approve, or pick an option).
|
|
25
|
+
- **How**: State = the plan shape (task count and what it builds). Risk = the riskiest task or
|
|
26
|
+
dependency. Call = approve the plan to build.
|
|
27
|
+
- **Exec**: State = what got built and the test result. Risk = residual failures or deviations.
|
|
28
|
+
Call = go to review, or done.
|
|
29
|
+
- **Done**: State = the delivered impact. Risk = residual risk. Call = the decision left to you
|
|
30
|
+
(merge, release, accept).
|
|
31
|
+
|
|
32
|
+
## Boundary vs intent 74
|
|
33
|
+
|
|
34
|
+
Intent 74's report contract (`references/agent-report-contract.md`) is the INTERNAL,
|
|
35
|
+
machine-checked handoff from a dispatched specialist back to the orchestrator: a structured
|
|
36
|
+
envelope plus a per-role payload. This contract is the OUTWARD human briefing, orchestrator to
|
|
37
|
+
user, in prose. Different direction, different audience, different form. The orchestrator
|
|
38
|
+
CONSUMES the intent 74 report to WRITE the human briefing defined here. The two never merge.
|
|
39
|
+
|
|
40
|
+
## Brevity: point, don't repeat
|
|
41
|
+
|
|
42
|
+
Surface rules (no em-dashes, plain words, no filler openers, and so on) are owned by the
|
|
43
|
+
shipped `plastic-humanizer` skill and the always-on plain-language layer. This contract does not
|
|
44
|
+
re-list that catalog. It restates only the hard bans as one line: no em-dashes, no "not X but Y",
|
|
45
|
+
no rule of three, no hype words, no sycophancy, no over-bolding. Apply `plastic-humanizer` and the
|
|
46
|
+
always-on layer for everything else.
|
|
47
|
+
|
|
48
|
+
## Emission: guided vs auto
|
|
49
|
+
|
|
50
|
+
In guided mode, the briefing lands at each stage boundary and the human acts on the Call line
|
|
51
|
+
before the next stage starts.
|
|
52
|
+
|
|
53
|
+
In auto mode, the orchestrator still emits the briefing at each boundary, as a running EM-to-CTO
|
|
54
|
+
account. The Call line becomes the go-ahead the orchestrator takes itself and moves on, except at
|
|
55
|
+
the existing hard stops (destructive action without a safe alternative, project-path confirm).
|
|
@@ -44,30 +44,8 @@ You MUST create a task for each of these items and complete them in order:
|
|
|
44
44
|
|
|
45
45
|
## Process Flow
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
"Explore project context" [shape=box];
|
|
50
|
-
"Ask clarifying questions" [shape=box];
|
|
51
|
-
"Propose 2-3 approaches" [shape=box];
|
|
52
|
-
"Present design sections" [shape=box];
|
|
53
|
-
"User approves design?" [shape=diamond];
|
|
54
|
-
"Write spec" [shape=box];
|
|
55
|
-
"Spec self-review\n(fix inline)" [shape=box];
|
|
56
|
-
"User reviews spec?" [shape=diamond];
|
|
57
|
-
"Invoke plastic-writing-plans" [shape=doublecircle];
|
|
58
|
-
|
|
59
|
-
"Explore project context" -> "Ask clarifying questions";
|
|
60
|
-
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
|
61
|
-
"Propose 2-3 approaches" -> "Present design sections";
|
|
62
|
-
"Present design sections" -> "User approves design?";
|
|
63
|
-
"User approves design?" -> "Present design sections" [label="no, revise"];
|
|
64
|
-
"User approves design?" -> "Write spec" [label="yes"];
|
|
65
|
-
"Write spec" -> "Spec self-review\n(fix inline)";
|
|
66
|
-
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
|
67
|
-
"User reviews spec?" -> "Write spec" [label="changes requested"];
|
|
68
|
-
"User reviews spec?" -> "Invoke plastic-writing-plans" [label="approved"];
|
|
69
|
-
}
|
|
70
|
-
```
|
|
47
|
+
The Checklist above states the ordered flow (steps 1-8). For the same flow as a
|
|
48
|
+
diagram, read `references/design-principles.md`.
|
|
71
49
|
|
|
72
50
|
**The terminal state is invoking `plastic-writing-plans`.** Do NOT invoke any other implementation skill. The ONLY skill you invoke after brainstorming is `plastic-writing-plans`.
|
|
73
51
|
|
|
@@ -95,16 +73,11 @@ digraph brainstorming {
|
|
|
95
73
|
- Cover: architecture, components, data flow, error handling, testing
|
|
96
74
|
- Be ready to go back and clarify if something doesn't make sense
|
|
97
75
|
|
|
98
|
-
**Design for isolation and clarity:**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
**Working in existing codebases:**
|
|
105
|
-
- Explore the current structure before proposing changes. Follow existing patterns.
|
|
106
|
-
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
|
|
107
|
-
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
|
|
76
|
+
**Design for isolation and clarity, and working in existing codebases:** before
|
|
77
|
+
proposing a design, read `references/design-principles.md` for unit-boundary
|
|
78
|
+
guidance (what makes a good interface, when a file has grown too large) and
|
|
79
|
+
existing-codebase guidance (follow established patterns, fold in targeted
|
|
80
|
+
improvements without unrelated refactoring).
|
|
108
81
|
|
|
109
82
|
## After the Design
|
|
110
83
|
**Documentation:**
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Design Principles: Unit Boundaries and Existing Codebases
|
|
2
|
+
|
|
3
|
+
General good-developer guidance behind two parts of the Process: how to design for
|
|
4
|
+
isolation and clarity, and how to behave in an existing codebase. Also holds the
|
|
5
|
+
Process Flow diagram (the same ordered flow the Checklist already states as numbered
|
|
6
|
+
steps).
|
|
7
|
+
|
|
8
|
+
## Design for isolation and clarity
|
|
9
|
+
|
|
10
|
+
- Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
|
|
11
|
+
- For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
|
|
12
|
+
- Can someone understand what a unit does without reading its internals? Can you change the internals without breaking consumers? If not, the boundaries need work.
|
|
13
|
+
- Smaller, well-bounded units are also easier for you to work with - you reason better about code you can hold in context at once, and your edits are more reliable when files are focused. When a file grows large, that's often a signal that it's doing too much.
|
|
14
|
+
|
|
15
|
+
## Working in existing codebases
|
|
16
|
+
|
|
17
|
+
- Explore the current structure before proposing changes. Follow existing patterns.
|
|
18
|
+
- Where existing code has problems that affect the work (e.g., a file that's grown too large, unclear boundaries, tangled responsibilities), include targeted improvements as part of the design - the way a good developer improves code they're working in.
|
|
19
|
+
- Don't propose unrelated refactoring. Stay focused on what serves the current goal.
|
|
20
|
+
|
|
21
|
+
## Process Flow (diagram)
|
|
22
|
+
|
|
23
|
+
The Checklist above already states this ordered flow as numbered steps 1-8; this
|
|
24
|
+
diagram is the same flow in a visual form.
|
|
25
|
+
|
|
26
|
+
```dot
|
|
27
|
+
digraph brainstorming {
|
|
28
|
+
"Explore project context" [shape=box];
|
|
29
|
+
"Ask clarifying questions" [shape=box];
|
|
30
|
+
"Propose 2-3 approaches" [shape=box];
|
|
31
|
+
"Present design sections" [shape=box];
|
|
32
|
+
"User approves design?" [shape=diamond];
|
|
33
|
+
"Write spec" [shape=box];
|
|
34
|
+
"Spec self-review\n(fix inline)" [shape=box];
|
|
35
|
+
"User reviews spec?" [shape=diamond];
|
|
36
|
+
"Invoke plastic-writing-plans" [shape=doublecircle];
|
|
37
|
+
|
|
38
|
+
"Explore project context" -> "Ask clarifying questions";
|
|
39
|
+
"Ask clarifying questions" -> "Propose 2-3 approaches";
|
|
40
|
+
"Propose 2-3 approaches" -> "Present design sections";
|
|
41
|
+
"Present design sections" -> "User approves design?";
|
|
42
|
+
"User approves design?" -> "Present design sections" [label="no, revise"];
|
|
43
|
+
"User approves design?" -> "Write spec" [label="yes"];
|
|
44
|
+
"Write spec" -> "Spec self-review\n(fix inline)";
|
|
45
|
+
"Spec self-review\n(fix inline)" -> "User reviews spec?";
|
|
46
|
+
"User reviews spec?" -> "Write spec" [label="changes requested"];
|
|
47
|
+
"User reviews spec?" -> "Invoke plastic-writing-plans" [label="approved"];
|
|
48
|
+
}
|
|
49
|
+
```
|
|
@@ -124,32 +124,11 @@ cleanly, and do not work around the failure by hand-writing the files.
|
|
|
124
124
|
|
|
125
125
|
### 6. If Implementation Intent Spawns a Project
|
|
126
126
|
|
|
127
|
-
When the user says "start building" or the plan calls for a new project
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
mkdir -p <project_root>/<slug>
|
|
133
|
-
cd <project_root>/<slug>
|
|
134
|
-
git init
|
|
135
|
-
```
|
|
136
|
-
3. Copy `AGENTS.md` template from `${CLAUDE_PLUGIN_ROOT}/templates/agents.md`
|
|
137
|
-
4. Register in `~/.plastic/projects.yml`:
|
|
138
|
-
```yaml
|
|
139
|
-
<slug>:
|
|
140
|
-
path: <full-path>
|
|
141
|
-
parent: "ID"
|
|
142
|
-
registered: <today>
|
|
143
|
-
status: active
|
|
144
|
-
```
|
|
145
|
-
5. Provision the project store (the single source of truth for store creation;
|
|
146
|
-
runs after step 4 because the provisioner requires the project to be
|
|
147
|
-
registered):
|
|
148
|
-
```bash
|
|
149
|
-
ruby ~/.plastic/scripts/provision-project-store <slug>
|
|
150
|
-
```
|
|
151
|
-
6. Add `project-<slug>` to the intent's `tags` array
|
|
152
|
-
7. Auto-commit in both `~/.plastic/` and the new project
|
|
127
|
+
When the user says "start building" or the plan calls for a new project, invoke the
|
|
128
|
+
`plastic-creating-project` skill; it owns project directory creation, AGENTS.md
|
|
129
|
+
population, projects.yml registration, store provisioning, and the auto-commit of
|
|
130
|
+
both stores. Add `project-<slug>` to this intent's `tags` array either before
|
|
131
|
+
invoking it or as part of that skill's handoff.
|
|
153
132
|
|
|
154
133
|
### 7. Update INDEX.md
|
|
155
134
|
|