@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.
@@ -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
- return reason if reason
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
- # Recommendation text for whichever tools are present, joined by newlines, or
56
- # nil when none are. One recommendation line per present tool.
57
- def mandate(cwd:, qmd_detector: QmdSync.method(:detect), serena_detector: nil)
58
- lines = []
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
- return nil if lines.empty?
73
- lines.join("\n")
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
@@ -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`. `arm_auto` calls `resolve_session`, which
80
- picks the first non-empty of: the explicit id you pass -> `CLAUDE_CODE_SESSION_ID` -> a
81
- deterministic derived key (a hash of the store and intent id).
82
- It never returns nil, so the gate engages even when every session env var is empty; the call
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: it releases the worktrees first, then clears the
285
- intent's `delivery.lock` (and the bridge's lock cache), and only then is the bridge
286
- purge-eligible. Disarming also purges stale bridge files from the temp directory
287
- automatically (it keeps the current bridge, any live run, and any bridge whose intent
288
- still holds a delivery lock), so no manual `/tmp` cleanup is needed.
289
-
290
- **Worktree cleanup (mandatory, intent 73c3).** Disarming performs the worktree release:
291
- `disarm_auto` calls `Worktree.release`, which removes both per-intent worktrees (the code
292
- worktree under `<repo>/.claude/worktrees/{id}--{slug}` and the paired store worktree under
293
- `<plastic_home>/.worktrees/{id}--{slug}`), prunes both repos, and clears the worktree block
294
- from the bridge. This is the plain remove path: the disarm route does NOT merge, so use it
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
- Completion is the lifecycle event that keeps the search index fresh. `<store-root>` is the store
310
- that holds this intent (the global store or the project store). The reindex is the LAST End-tail
311
- step, run after purge, so the index never references a bridge or lock that is about to disappear
312
- (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
313
- 10. Notify user: "Intent [ID] [name] delivered. [1-2 sentence summary]. See outcome.md for details."
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
- ```dot
48
- digraph brainstorming {
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
- - Break the system into smaller units that each have one clear purpose, communicate through well-defined interfaces, and can be understood and tested independently
100
- - For each unit, you should be able to answer: what does it do, how do you use it, and what does it depend on?
101
- - 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.
102
- - 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.
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
- 1. Determine project slug from intent name
130
- 2. Create project directory in first `project_roots` path (from `~/.plastic/config.yml`):
131
- ```bash
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