@zalom/plastic 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/PLASTIC.md +23 -8
  2. package/README.md +15 -4
  3. package/agents/plastic-enforcer.md +3 -2
  4. package/agents/plastic-intent-discovery.md +7 -0
  5. package/bin/plastic.js +17 -8
  6. package/hooks/auto-arm +2 -2
  7. package/hooks/bash-gate +1 -1
  8. package/hooks/check-update +1 -1
  9. package/hooks/continue +2 -2
  10. package/hooks/edit-gates +1 -1
  11. package/hooks/future-intent-check +2 -2
  12. package/hooks/gate-check +3 -3
  13. package/hooks/power-tools +1 -1
  14. package/hooks/session-start +1 -1
  15. package/package.json +1 -1
  16. package/scripts/codex-hook +50 -106
  17. package/scripts/doctor.rb +132 -1080
  18. package/scripts/exec-worktree +103 -0
  19. package/scripts/hash-intent +1 -1
  20. package/scripts/hook-bash-gate +19 -0
  21. package/scripts/hook-code-gate +4 -1
  22. package/scripts/hook-continue +2 -2
  23. package/scripts/hook-create-gate +6 -3
  24. package/scripts/hook-gate-check +17 -0
  25. package/scripts/hook-links-gate +4 -1
  26. package/scripts/hook-lock-gate +7 -3
  27. package/scripts/hook-savepoint-pre +4 -1
  28. package/scripts/hook-session-start +21 -15
  29. package/scripts/lib/apply_patch_envelope.rb +46 -13
  30. package/scripts/lib/bridge.rb +83 -15
  31. package/scripts/lib/codex_edit_gates.rb +138 -0
  32. package/scripts/lib/doctor_core.rb +1087 -0
  33. package/scripts/lib/edit_gates.rb +61 -5
  34. package/scripts/lib/exec_worktree.rb +325 -0
  35. package/scripts/lib/harness_text.rb +57 -0
  36. package/scripts/lib/hook_registry.rb +32 -28
  37. package/scripts/lib/installer_core.rb +67 -7
  38. package/scripts/lib/lock.rb +196 -47
  39. package/scripts/lib/ruby_probe.rb +60 -0
  40. package/scripts/lib/scaffold_intent.rb +392 -0
  41. package/scripts/lib/spec_header.rb +83 -0
  42. package/scripts/lib/start_intent.rb +296 -0
  43. package/scripts/lib/verify_intent.rb +262 -0
  44. package/scripts/lib/worktree.rb +15 -1
  45. package/scripts/link-suggest +1 -1
  46. package/scripts/maintenance-run +5 -5
  47. package/scripts/migrate-to-global +2 -2
  48. package/scripts/restore-intent-v1 +1 -1
  49. package/scripts/scaffold-intent +120 -0
  50. package/scripts/start-intent +89 -0
  51. package/scripts/verify-intent +73 -0
  52. package/skills/agent-advisor/SKILL.md +5 -5
  53. package/skills/auto/SKILL.md +42 -32
  54. package/skills/auto/references/agent-architecture.md +1 -1
  55. package/skills/auto/references/agent-report-contract.md +1 -1
  56. package/skills/auto/references/human-report-contract.md +22 -3
  57. package/skills/auto/references/tiers.md +24 -2
  58. package/skills/conventions/references/completion-and-done.md +3 -0
  59. package/skills/conventions/references/gates-and-enforcement.md +28 -12
  60. package/skills/conventions/references/locks-and-worktrees.md +3 -3
  61. package/skills/conventions/references/tiers-and-dispatch.md +7 -6
  62. package/skills/dashboard/SKILL.md +1 -1
  63. package/skills/doctor/SKILL.md +6 -5
  64. package/skills/doctor/references/gates-stuck-detection.md +13 -8
  65. package/skills/doctor/report.md +1 -1
  66. package/skills/install/SKILL.md +1 -1
  67. package/skills/intent-brainstorming/SKILL.md +0 -2
  68. package/skills/intent-creating/SKILL.md +6 -6
  69. package/skills/intent-creating/references/lifecycle.md +1 -1
  70. package/skills/intent-discovering/SKILL.md +10 -3
  71. package/skills/intent-ending/SKILL.md +8 -7
  72. package/skills/intent-executing/SKILL.md +27 -19
  73. package/skills/intent-grilling/SKILL.md +5 -3
  74. package/skills/intent-planning/SKILL.md +7 -3
  75. package/skills/intent-researching/SKILL.md +0 -2
  76. package/skills/intent-starting/SKILL.md +10 -2
  77. package/skills/project-creating/SKILL.md +0 -2
  78. package/skills/project-creating/references/project-scaffolding.md +1 -1
  79. package/skills/releasing/SKILL.md +1 -1
  80. package/skills/releasing/references/promotion-and-tagging.md +14 -8
  81. package/skills/releasing/references/release-lines.md +1 -1
  82. package/skills/skill-creating/SKILL.md +5 -2
  83. package/skills/store-indexing/SKILL.md +8 -5
  84. package/skills/store-indexing/references/zettelkasten-linking.md +1 -1
  85. package/skills/tutorial/references/track-1-guided.md +2 -2
  86. package/skills/tutorial/references/track-2-auto.md +9 -6
  87. package/skills/tutorial/references/track-3-projects-and-roadmaps.md +1 -1
  88. package/skills/uninstall/SKILL.md +6 -9
  89. package/templates/agents.md +12 -12
  90. package/templates/config.yml +6 -7
  91. package/templates/index.md +6 -3
  92. package/templates/spec.md +1 -1
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # scaffold-intent (intent 213) - one CLI, three subcommands, each writes one lifecycle
6
+ # artifact by copying or mechanically deriving it from an already-committed source. Every
7
+ # field it writes is a verbatim copy or a mechanical derivation (a `git diff --stat`, a
8
+ # supplied file's contents) of an already-committed artifact; no subcommand interprets or
9
+ # invents prose. A field it cannot derive mechanically is left as the template's own stub
10
+ # text instead of being guessed at.
11
+ #
12
+ # Usage:
13
+ # scaffold-intent spec|checklist|outcome --store <path> --id <intent_id> [--force]
14
+ # [--test-summary <path>] (outcome subcommand only)
15
+ #
16
+ # Subcommand is the first positional argument. --store and --id are required for every
17
+ # subcommand. --test-summary is valid only on the outcome subcommand; on spec or
18
+ # checklist, or naming a path that does not exist, it is a usage error (exit 1). No other
19
+ # flags exist: --store, --id, --force, and --test-summary (outcome only) are the whole
20
+ # shipped surface (D5, a one-way door).
21
+ #
22
+ # Exit codes:
23
+ # 0 scaffolded
24
+ # 1 usage or path-resolution failure
25
+ # 2 refused to overwrite real content without --force
26
+ # 3 a prerequisite committed artifact is missing or still sentinel
27
+ #
28
+ # The actions/ rule (intent 133a, D11): scaffold-intent does not scaffold actions/ in any
29
+ # form. Actions require judgment and stay entirely with the planner. No subcommand
30
+ # creates the directory, writes a .gitkeep, or writes a sentinel-only ACTION_*.md.
31
+ #
32
+ # outcome is an end-of-Exec step. Run it once the diff and the test summary exist, right
33
+ # before plastic-intent-ending writes the narrative. Writing outcome.md makes the intent
34
+ # read as Done to the statusline and to any stage-derived display (Bridge.derive_stage
35
+ # keys on outcome.md's presence). This does NOT purge a bridge:
36
+ # Bridge.purge_done_bridges keys on INDEX Active status plus lock presence, never on the
37
+ # derived stage.
38
+
39
+ require_relative "lib/scaffold_intent"
40
+
41
+ SUBCOMMANDS = %w[spec checklist outcome].freeze
42
+
43
+ def parse_args(argv)
44
+ opts = { subcommand: nil, store: nil, id: nil, force: false, test_summary: nil }
45
+ args = argv.dup
46
+ opts[:subcommand] = args.shift
47
+
48
+ i = 0
49
+ while i < args.length
50
+ arg = args[i]
51
+ case arg
52
+ when "--store" then opts[:store] = args[i += 1]
53
+ when "--id" then opts[:id] = args[i += 1]
54
+ when "--force" then opts[:force] = true
55
+ when "--test-summary" then opts[:test_summary] = args[i += 1]
56
+ else
57
+ usage_abort("unknown argument #{arg.inspect}")
58
+ end
59
+ i += 1
60
+ end
61
+ opts
62
+ end
63
+
64
+ def usage
65
+ "usage: scaffold-intent spec|checklist|outcome --store <store_path> --id <intent_id> " \
66
+ "[--force] [--test-summary <path>]"
67
+ end
68
+
69
+ def usage_abort(message)
70
+ warn "scaffold-intent: #{message}"
71
+ warn usage
72
+ exit 1
73
+ end
74
+
75
+ def main(argv)
76
+ opts = parse_args(argv)
77
+
78
+ usage_abort("subcommand must be one of #{SUBCOMMANDS.join('|')}") unless SUBCOMMANDS.include?(opts[:subcommand])
79
+ usage_abort("--store is required") if opts[:store].nil? || opts[:store].empty?
80
+ usage_abort("--id is required") if opts[:id].nil? || opts[:id].empty?
81
+ if opts[:test_summary] && opts[:subcommand] != "outcome"
82
+ usage_abort("--test-summary is valid only on the outcome subcommand")
83
+ end
84
+
85
+ store = ScaffoldIntent.expand(opts[:store])
86
+ usage_abort("store dir does not exist: #{store}") unless Dir.exist?(store)
87
+
88
+ intent_dir, err = ScaffoldIntent.resolve_intent_dir(store, opts[:id])
89
+ usage_abort(err) if intent_dir.nil?
90
+
91
+ test_summary_path = nil
92
+ if opts[:test_summary]
93
+ test_summary_path = ScaffoldIntent.expand(opts[:test_summary])
94
+ usage_abort("--test-summary path does not exist: #{test_summary_path}") unless File.exist?(test_summary_path)
95
+ end
96
+
97
+ result =
98
+ case opts[:subcommand]
99
+ when "spec"
100
+ ScaffoldIntent.scaffold_spec(intent_dir: intent_dir, force: opts[:force])
101
+ when "checklist"
102
+ ScaffoldIntent.scaffold_checklist(intent_dir: intent_dir, force: opts[:force])
103
+ when "outcome"
104
+ ScaffoldIntent.scaffold_outcome(intent_dir: intent_dir, force: opts[:force], store: store,
105
+ id: opts[:id], test_summary: test_summary_path)
106
+ end
107
+
108
+ if result[:message]
109
+ if result[:code] == 0
110
+ puts result[:message]
111
+ else
112
+ warn "scaffold-intent: #{result[:message]}"
113
+ end
114
+ end
115
+ puts result[:path] if result[:path]
116
+
117
+ exit result[:code]
118
+ end
119
+
120
+ main(ARGV) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # start-intent (intent 213, group 2) - board a session onto an intent: arm the delivery
6
+ # lock, then print a read-only resume-station report.
7
+ #
8
+ # Usage:
9
+ # start-intent --store <path> --id <intent_id> --mode auto|guided [--session <session_id>]
10
+ #
11
+ # --store, --id, and --mode are required. --session is optional and defaults to
12
+ # CLAUDE_CODE_SESSION_ID, matching end-intent's resolution order. No other flags exist.
13
+ #
14
+ # Steps performed:
15
+ # 0. Resolve inputs: the intent dir, the intent name from frontmatter (falling back to
16
+ # the dir basename), the calling session (--session, else CLAUDE_CODE_SESSION_ID,
17
+ # else nil; deliberately no lock-owner fallback), and harness/thread from
18
+ # CODEX_THREAD_ID / CLAUDE_CODE_SESSION_ID (mirroring skills/auto/SKILL.md's arming
19
+ # snippet).
20
+ # 1. Pre-flight lock read (read-only): refuse a fresh foreign lock, an unparseable
21
+ # lock, or a lock present with no resolvable session identity. A stale foreign lock
22
+ # falls through to arming, which arbitrates it itself.
23
+ # 2. Arm: Bridge.arm_auto (--mode auto) or Bridge.arm_guided (--mode guided). Nothing
24
+ # else acquires anything.
25
+ # 3. Print the resume-station report (read-only): which lifecycle files are real, and
26
+ # where the intent should resume (Why/How/Exec/Done).
27
+ #
28
+ # This script never releases and never takes over a lock: it never calls the Lock
29
+ # module's release or takeover operations (parked intent 254's territory). A held or
30
+ # corrupt lock is reported and refused, never repaired.
31
+ #
32
+ # Exit codes:
33
+ # 0 armed and resume-station printed
34
+ # 1 usage or path-resolution failure
35
+ # 3 lock held by another live session (deny, mirrors end-intent's pre-flight refusal)
36
+ # 4 unparseable lock, or no session identity with a lock present
37
+ #
38
+ # There is deliberately no exit 2 in this script's contract.
39
+
40
+ require_relative "lib/start_intent"
41
+
42
+ def parse_args(argv)
43
+ opts = { store: nil, id: nil, mode: nil, session: nil }
44
+ i = 0
45
+ while i < argv.length
46
+ arg = argv[i]
47
+ case arg
48
+ when "--store" then opts[:store] = argv[i += 1]
49
+ when "--id" then opts[:id] = argv[i += 1]
50
+ when "--mode" then opts[:mode] = argv[i += 1]
51
+ when "--session" then opts[:session] = argv[i += 1]
52
+ else
53
+ usage_abort("unknown argument #{arg.inspect}")
54
+ end
55
+ i += 1
56
+ end
57
+ opts
58
+ end
59
+
60
+ def usage
61
+ "usage: start-intent --store <store_path> --id <intent_id> --mode auto|guided " \
62
+ "[--session <session_id>]"
63
+ end
64
+
65
+ def usage_abort(message)
66
+ warn "start-intent: #{message}"
67
+ warn usage
68
+ exit 1
69
+ end
70
+
71
+ def main(argv)
72
+ opts = parse_args(argv)
73
+
74
+ codex_thread_id = ENV["CODEX_THREAD_ID"]
75
+ claude_session_id = ENV["CLAUDE_CODE_SESSION_ID"]
76
+ detected = StartIntent.resolve_harness(codex_thread_id, claude_session_id)
77
+
78
+ result = StartIntent.run(
79
+ store: opts[:store], id: opts[:id], mode: opts[:mode], session: opts[:session],
80
+ env_session: claude_session_id, harness: detected[:harness], thread: detected[:thread]
81
+ )
82
+
83
+ Array(result[:stderr]).each { |line| warn line }
84
+ warn usage if result[:exit_code] == StartIntent::EXIT_USAGE
85
+ Array(result[:stdout]).each { |line| puts line }
86
+ exit result[:exit_code]
87
+ end
88
+
89
+ main(ARGV) if $PROGRAM_NAME == __FILE__
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # verify-intent (intent 213) - bundles the checks that need no project-specific knowledge
6
+ # into one verdict: the per-intent doctor scan, an added-line em-dash diff guard, a
7
+ # diffstat, and an optional caller-supplied suite command.
8
+ #
9
+ # Usage:
10
+ # verify-intent --store <path> --id <intent_id> [--base <ref>] [--suite <command>]
11
+ #
12
+ # --base overrides the auto-detected diff base (default: the merge base with the repo's
13
+ # detected default branch) for repos where detection is ambiguous. --suite is an optional
14
+ # caller-supplied command to run (in the repo directory) and fold into the verdict. No
15
+ # other flags exist.
16
+ #
17
+ # Checks, all run every invocation (the fourth only when --suite is given):
18
+ # 1. doctor - Doctor#run_intent_check(id, store: scope), fail-open on a crash
19
+ # 2. em-dash - added lines only of `git diff <base>...HEAD`, never the whole tree
20
+ # 3. diffstat - `git diff --stat <base>...HEAD`, never a failure on its own
21
+ # 4. suite - the supplied --suite command, run with RUBYOPT cleared
22
+ #
23
+ # Every check runs, then the run exits on the LOWEST-numbered failure that occurred, so one
24
+ # invocation gives the full picture on stdout even when several checks fail.
25
+ #
26
+ # Exit codes:
27
+ # 0 every check passed
28
+ # 1 usage or path-resolution failure
29
+ # 2 doctor reported an issue
30
+ # 3 the em-dash guard found a violation on an added line
31
+ # 4 the supplied --suite command exited non-zero
32
+
33
+ require_relative "lib/verify_intent"
34
+
35
+ def parse_args(argv)
36
+ opts = { store: nil, id: nil, base: nil, suite: nil }
37
+ i = 0
38
+ while i < argv.length
39
+ arg = argv[i]
40
+ case arg
41
+ when "--store" then opts[:store] = argv[i += 1]
42
+ when "--id" then opts[:id] = argv[i += 1]
43
+ when "--base" then opts[:base] = argv[i += 1]
44
+ when "--suite" then opts[:suite] = argv[i += 1]
45
+ else
46
+ usage_abort("unknown argument #{arg.inspect}")
47
+ end
48
+ i += 1
49
+ end
50
+ opts
51
+ end
52
+
53
+ def usage
54
+ "usage: verify-intent --store <path> --id <intent_id> [--base <ref>] [--suite <command>]"
55
+ end
56
+
57
+ def usage_abort(message)
58
+ warn "verify-intent: #{message}"
59
+ warn usage
60
+ exit 1
61
+ end
62
+
63
+ def main(argv)
64
+ opts = parse_args(argv)
65
+ usage_abort("--store is required") if opts[:store].nil? || opts[:store].empty?
66
+ usage_abort("--id is required") if opts[:id].nil? || opts[:id].empty?
67
+
68
+ verdict = VerifyIntent.run(store: opts[:store], id: opts[:id], base: opts[:base], suite: opts[:suite])
69
+ verdict[:lines].each { |line| puts line }
70
+ exit verdict[:exit_code]
71
+ end
72
+
73
+ main(ARGV) if $PROGRAM_NAME == __FILE__
@@ -39,12 +39,12 @@ a session.
39
39
 
40
40
  ## Routing: which advisor answers
41
41
 
42
- 1. Read the harness-scoped config: `advisor.claude.default`. If unset, fall back to
43
- `advisor.claude.secondary`, then to `plastic-faux-advisor`.
42
+ 1. Read the harness-scoped config: `advisor.claude.default`, the only advisor routing key
43
+ the installer writes. If unset, use `plastic-advisor`, the shipped default.
44
44
  2. If the user names which advisor they want ("ask the real one", "use Fable", "ask the
45
- cheap one"), honor that directly and dispatch `advisor.claude.primary` (default
46
- `plastic-advisor`) or `advisor.claude.secondary` (default `plastic-faux-advisor`)
47
- accordingly, overriding step 1 for this consultation only.
45
+ cheap one"), honor that directly and dispatch `plastic-advisor` (the real advisor) or
46
+ `plastic-faux-advisor` (the cheaper imitation) accordingly, overriding step 1 for this
47
+ consultation only.
48
48
  3. If `advisor.enabled` reads `false`, neither advisor agent nor this skill is
49
49
  installed; this step should not be reachable, but if it is, tell the user the
50
50
  advisor is disabled and point at "Setting the default" below.
@@ -27,7 +27,7 @@ Claude Code).
27
27
 
28
28
  An active intent MUST exist in INDEX.md. If none exists, refuse: "No active intent found. Create one first with /plastic-intent-creating."
29
29
 
30
- If multiple active intents exist, ask the user which one to deliver (this is the only question auto asks).
30
+ If multiple active intents exist, ask the user which one to deliver (the one question auto asks at boarding, before delivery starts).
31
31
 
32
32
  **Picking work when no intent is specified.** If the user says "auto" without naming an
33
33
  intent and none is active, consult the roadmap first (the primary planning surface), then
@@ -75,15 +75,22 @@ and artifact depth to that size. Extended walkthrough: `references/tiers.md`.
75
75
  place, under the same gate.
76
76
  3. **Per-tier topology.** S/M: one thinker agent, one boot, two stations, sonnet
77
77
  executor; the thinker writes at least one real action file (one consolidated
78
- `actions/ACTION_1.md`), never an empty `actions/`. S may also skip the QMD discovery
79
- deposit when chain and sources are both empty. L: today's full team (`## Team Spin-Up`
80
- below), one `actions/ACTION_N.md` per task.
78
+ `actions/ACTION_1.md`), never an empty `actions/`. That executor runs as ONE dispatch
79
+ for the whole consolidated action, with no per-task two-stage review loop
80
+ (`plastic-intent-executing` holds that gate). S cuts two more things: it skips the QMD
81
+ discovery deposit when chain and sources are both empty AND a size of S is already on
82
+ record (a stamped `Tier: S` line in spec.md), which a first activation does not have,
83
+ and it sends ONE mid-flight owner briefing, at How. M and L send all four. L: today's
84
+ full team (`## Team Spin-Up` below), one `actions/ACTION_N.md` per task, and a per-task
85
+ implementer plus two-stage review.
81
86
  4. **Never-cut list**, any tier or mode: the independent reviewer (separate agent, fresh
82
87
  context, never the maker), `outcome.md` as truth of delivery, the delivery lock,
83
88
  worktree isolation, intent creation via skill, INDEX as status truth, the QMD reindex
84
89
  at End. Lightness is about ceremony, never about these guarantees.
85
90
  5. **Tier record.** `Tier: S|M|L` at the top of spec.md. Convention-only: read by the
86
- orchestrator, never validated by any gate or by doctor.
91
+ orchestrator, never validated by any gate or by doctor. A separate
92
+ `Settled: yes (<reason>)` line may sit directly beneath the `Tier:` line; an absent
93
+ line means not settled, and settledness is independent of scope.
87
94
 
88
95
  Read `../plastic-conventions/references/tiers-and-dispatch.md` for tier sizing, agent-model
89
96
  config, advisor routing, and the auto-mode human report contract behind the sizing above. This
@@ -137,7 +144,7 @@ Roster (one role per cycle stage):
137
144
 
138
145
  Dispatch rule: sequential, one specialist per stage on one branch (the deliverables share files). Gate each deliverable against the stage's exit criteria before handing off. The How and Exec phases below default to Plastic's native dispatch (`plastic-intent-executing`) and delegate to the superpowers skills only when they are available or the user asks; do not restate the phase mechanics here.
139
146
 
140
- Spawn preamble (live-state injection): before dispatching any specialist, run `scripts/spawn-preamble <intent_dir> --role <role>` and PREPEND its output to that specialist's prompt. The preamble is a deterministic, filesystem-only snapshot of the active intent (id, intent line, current stage, and the provisioned code worktree path when one exists on disk) plus the honoring instruction, so every spawned agent boots with accurate live state instead of guessing. This is the authoritative L2 mechanism for harnesses whose sub-agents do not inherit a top-level session event (see `docs/reference/harness-adapters.md`).
147
+ Spawn preamble (live-state injection): before dispatching any specialist, run `scripts/spawn-preamble <intent_dir> --role <role>` and PREPEND its output to that specialist's prompt. The preamble is a deterministic, filesystem-only snapshot of the active intent (id, intent line, current stage, and the provisioned code worktree path when one exists on disk) plus the honoring instruction, so every spawned agent boots with accurate live state instead of guessing. This is the authoritative L2 mechanism for harnesses whose sub-agents do not inherit a top-level session event (see [`harness-adapters.md`](https://github.com/zalom/plastic/blob/main/docs/reference/harness-adapters.md)).
141
148
 
142
149
  Dispatch-time model contract (belt-and-braces): alongside the preamble, resolve each specialist's model through the config chain (`read-config agents.models.<basename> --project <repo>`: project override, then global, then the shipped tier default) and pass it explicitly at dispatch. Never rely on the dispatched role's frontmatter alone; a resolved subagent model is never Fable,
143
150
  unless an explicit `agents.models.<name>` config override names Fable for that role, in which
@@ -217,9 +224,9 @@ Filesystem fallback (ledger missing only):
217
224
 
218
225
  Announce which stage you're entering and why.
219
226
 
220
- Notify user (What briefing): brief per `references/human-report-contract.md`
227
+ Notify user (What briefing, M and L only): brief per `references/human-report-contract.md`
221
228
  (State: the work picked up and why it matters now; Risk: scope uncertainty; Call: confirm
222
- this is worth doing, or proceed).
229
+ this is worth doing, or proceed). At S this briefing does not fire; the How briefing carries it.
223
230
 
224
231
  ## Why Completion (Autonomous)
225
232
 
@@ -235,9 +242,10 @@ When entering at Why stage:
235
242
  5. Make decisions - pick best option, document in `## Context > ### Decisions` with rationale
236
243
  6. Log all autonomous decisions in `## Insights` with `(autonomous)` marker: "Decision: chose X because Y (autonomous)"
237
244
  7. Write `spec.md` - consolidated specification
238
- 8. Notify user (Why briefing): brief per `references/human-report-contract.md`
245
+ 8. Notify user (Why briefing, M and L only): brief per `references/human-report-contract.md`
239
246
  (State: the approach chosen, one line; Risk: the main trade-off; Call: the one decision
240
- needed, approve or pick an option).
247
+ needed, approve or pick an option). At S this briefing does not fire; the How briefing
248
+ carries it.
241
249
 
242
250
  Then proceed to How.
243
251
 
@@ -253,9 +261,12 @@ one consolidated `actions/ACTION_1.md`, L writes one `actions/ACTION_N.md` per t
253
261
  2. Otherwise, write `plan.md` directly - implementation plan with numbered tasks
254
262
  3. Write at least one real `ACTION_N.md` into the existing `actions/` directory, self-contained (S/M: one consolidated `ACTION_1.md`; L: one per task)
255
263
  4. Write `checklist.md` - execution registry with checkboxes covering all actions
256
- 5. Notify user (How briefing): brief per `references/human-report-contract.md`
264
+ 5. Notify user (How briefing, every tier): brief per `references/human-report-contract.md`
257
265
  (State: the plan shape, task count and what it builds; Risk: the riskiest task or
258
- dependency; Call: approve the plan to build).
266
+ dependency; Call: approve the plan to build). At S this is the ONE mid-flight briefing:
267
+ fold into the same three lines what the What and Why briefings would have said (the work
268
+ picked up, the approach chosen), and send it here, with the plan ready and before any
269
+ code is written.
259
270
 
260
271
  Then proceed to Exec.
261
272
 
@@ -264,7 +275,7 @@ Then proceed to Exec.
264
275
  If the plan calls for creating a new project (the intent is an implementation intent that needs a new codebase):
265
276
 
266
277
  1. Determine project path from `~/.plastic/config.yml` `project_roots` or from intent context
267
- 2. **Confirm path with user** - this is the ONE human interaction during auto delivery:
278
+ 2. **Confirm path with user** - the one human interaction added mid-delivery, and only when this gate fires:
268
279
  > "Creating project `<slug>` at `<path>`. Confirm path, or provide alternative."
269
280
  3. Invoke `plastic-project-creating` skill
270
281
  4. The global intent is now Completed (creating-project handles this)
@@ -279,9 +290,10 @@ If the plan calls for creating a new project (the intent is an implementation in
279
290
  4. Check off items in `checklist.md` as completed
280
291
  5. Append observations to `## Insights` with `(autonomous)` marker
281
292
  6. Sub-agents can be spawned for parallel actions (one agent per action)
282
- 7. Notify user (Exec briefing): brief per `references/human-report-contract.md`
293
+ 7. Notify user (Exec briefing, M and L only): brief per `references/human-report-contract.md`
283
294
  (State: what got built and the test result; Risk: residual failures or deviations;
284
- Call: go to review, or done).
295
+ Call: go to review, or done). At S this briefing does not fire; the final owner report at
296
+ End carries what it would have said.
285
297
 
286
298
  ## Permission Model - Safe-by-Default
287
299
 
@@ -314,16 +326,15 @@ Read `../plastic-conventions/references/completion-and-done.md` for what "intent
314
326
  the End-stage tail the steps below walk through.
315
327
 
316
328
  1. Verify all checklist items are checked
317
- 2. Write `outcome.md` with detailed results, from `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md`.
329
+ 2. Write `outcome.md` with detailed results, from `~/.plastic/templates/outcome.md`.
318
330
  Set the frontmatter `disposition: delivered` (this is the delivered terminal). `outcome.md`
319
331
  is mandatory at every terminal and self-declares its disposition (see the canonical done-marker
320
332
  and End tail in PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
321
- 3. Write `## Outcome` summary in the intent file (1-2 sentences)
322
- 4. **Release (if configured)**
323
- 1. Detect project - match CWD against paths in `~/.plastic/projects.yml` to find the project slug. If no match, skip to step 5 (default commit-only behavior).
324
- 2. Read `~/.plastic/projects/{slug}/project.yml`. If the file doesn't exist or has no `release` key, skip to step 5.
333
+ 3. **Release (if configured)**
334
+ 1. Detect project - match CWD against paths in `~/.plastic/projects.yml` to find the project slug. If no match, skip to step 4 (default commit-only behavior).
335
+ 2. Read `~/.plastic/projects/{slug}/project.yml`. If the file doesn't exist or has no `release` key, skip to step 4.
325
336
  3. Based on `release.on_complete`:
326
- - `commit` - git add + commit (same as default, proceed to step 5)
337
+ - `commit` - git add + commit (same as default, proceed to step 4)
327
338
  - `commit_and_push` - git add + commit + push
328
339
  - `manual` - skip auto-commit, notify user: "Release configured as manual - commit when ready."
329
340
  4. If `release.verify` is set, run the verify command (e.g. `bundle exec rake test`):
@@ -333,14 +344,15 @@ the End-stage tail the steps below walk through.
333
344
  - `stop` - write `savepoint.md` with current state, notify user: "Verify failed - savepoint written.", **STOP**
334
345
  - `manual` - notify user: "Verify failed: [summary]. Resolve manually."
335
346
  5. If `release.on_green` has items, invoke `plastic-releasing` to handle them (tag, changelog, publish, etc.). Do NOT duplicate release logic - delegate entirely.
336
- 5. Review `## Insights` for observations that should spawn future intents. If any:
347
+ 4. Review `## Insights` for observations that should spawn future intents. If any:
337
348
  - Create them (using `plastic-intent-creating` conventions)
338
349
  - Update `chain` in the current intent's frontmatter
339
- 6. Run the mechanical close through `plastic-intent-ending`: it owns steps 1-6 of the Done
340
- procedure (outcome/INDEX/savepoint/commit, disarm, and the QMD reindex last) as ONE
341
- delegation, not five separate one-liners restated here. `scripts/end-intent` now performs
342
- steps 1-5 itself, INCLUDING disarm (worktree release plus clearing `delivery.lock`): a
343
- single call closes the intent AND clears its lock, so exit 0 means both are done. Pass
350
+ 5. Run the mechanical close through `plastic-intent-ending`: it owns steps 1-7 of the Done
351
+ procedure (outcome/INDEX/savepoint/commit, disarm, the QMD reindex last, and the single
352
+ EM-to-CTO owner report) as ONE delegation, not a series of separate one-liners restated
353
+ here. `scripts/end-intent` now performs steps 1-5 itself, INCLUDING disarm (worktree
354
+ release plus clearing `delivery.lock`): a single call closes the intent AND clears its
355
+ lock, so exit 0 means both are done. Pass
344
356
  `--session` (this session's id, or rely on the `CLAUDE_CODE_SESSION_ID` fallback) so
345
357
  disarm resolves the right bridge, and `--index-note` with a rich Completed/Abandoned entry
346
358
  description (mode/tier, what shipped or why abandoned, suite result):
@@ -354,13 +366,11 @@ the End-stage tail the steps below walk through.
354
366
  lock (back off), 5 means the code worktree is dirty (commit/stash first, or pass
355
367
  `--discard-worktree-changes` deliberately), 3 means disarm ran but the lock is still
356
368
  present (run `/plastic-doctor check the lock status`), 6 means the structure gate refused
357
- (see the named reason on stderr; fix via the owning tool named above, then re-run). Only
358
- Step 6 (QMD reindex, async, last) remains a separate action after this call succeeds.
369
+ (see the named reason on stderr; fix via the owning tool named above, then re-run). Two
370
+ steps remain as separate actions after this call succeeds: Step 6 (QMD reindex, async,
371
+ last) and Step 7 (the EM-to-CTO owner report, the single report the owner reads).
359
372
  Never leave an orphaned worktree; run `git worktree prune` on a stale reference. If any of
360
373
  this ever needs to change, change `plastic-intent-ending`, not this skill.
361
- 7. Notify user (Done briefing): brief per `references/human-report-contract.md`
362
- (State: the delivered impact; Risk: residual risk; Call: the decision left to you, merge,
363
- release, or accept). See `outcome.md` for details.
364
374
 
365
375
  ## Error Handling
366
376
 
@@ -59,7 +59,7 @@ savepoint line, else stage derived from which lifecycle files exist), the cycle
59
59
  role, and the honoring instruction that the agent must emit valid lifecycle artifacts
60
60
  and not hallucinate intents or stages. This is the standard L2 live-state mechanism
61
61
  for harnesses whose spawned sub-agents do not inherit the top-level session event. See
62
- `docs/reference/harness-adapters.md` for how it slots into the per-harness contract.
62
+ [`harness-adapters.md`](https://github.com/zalom/plastic/blob/main/docs/reference/harness-adapters.md) for how it slots into the per-harness contract.
63
63
 
64
64
  ### Completion Reports
65
65
 
@@ -90,7 +90,7 @@ argument is sound before any code is written.
90
90
  ## Fallback: always a report
91
91
 
92
92
  Decision-shaping (the preamble plus these prompts) makes the report mandatory, but child-agent
93
- honor is best-effort across harnesses (Tier B/C in `docs/reference/harness-adapters.md`), so the
93
+ honor is best-effort across harnesses (Tier B/C in [`harness-adapters.md`](https://github.com/zalom/plastic/blob/main/docs/reference/harness-adapters.md)), so the
94
94
  contract is never a hard block (decision D3). When a dispatched agent returns no usable report
95
95
  (it went idle, emitted only a bare ping, or its message was lost to a mid-run interjection), the
96
96
  enforcer synthesizes one:
@@ -1,7 +1,8 @@
1
1
  # Human Report Contract (per-stage EM-to-CTO briefing)
2
2
 
3
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
4
+ (What, Why, How, Exec, Done) in auto mode, at M and L; at S only the How boundary fires (see
5
+ `## Depth at Tier S`). It is the outward, human-facing counterpart to the
5
6
  internal report contract in `references/agent-report-contract.md`. Voice: an engineering
6
7
  manager briefing a CTO. Lead with impact, name the risk, leave the decision.
7
8
 
@@ -29,6 +30,23 @@ short. The words can flex to fit the stage.
29
30
  - **Done**: State = the delivered impact. Risk = residual risk. Call = the decision left to you
30
31
  (merge, release, accept).
31
32
 
33
+ ## Depth at Tier S
34
+
35
+ At Tier S in auto mode the mid-flight briefings collapse to one. Only the How briefing fires, and
36
+ it folds in what the What and Why briefings would have said: the work picked up and the approach
37
+ chosen go into its State line. The Exec briefing folds into the final owner report at End. M and L
38
+ send all four. The shape does not change: still State, then Risk, then Call, and the per-stage
39
+ content above still says what each line covers. This is a depth cut, not a new report. A delivery
40
+ still ends with `outcome.md` plus one owner report.
41
+
42
+ ## One report per audience
43
+
44
+ A delivery produces exactly two artifacts: `outcome.md` (authored by `plastic-intent-ending`)
45
+ and one EM-to-CTO owner report at the End stage. No stage or skill restates a delivery
46
+ already written to `outcome.md`; point at it instead. Skills do not open with a banner that
47
+ names the skill or restates the intent id and name the owner just typed. Announce only what
48
+ the reader cannot already know: an error, a result, a choice with its reason, or a handoff.
49
+
32
50
  ## Boundary vs intent 74
33
51
 
34
52
  Intent 74's report contract (`references/agent-report-contract.md`) is the INTERNAL,
@@ -50,6 +68,7 @@ always-on layer for everything else.
50
68
  In guided mode, the briefing lands at each stage boundary and the human acts on the Call line
51
69
  before the next stage starts.
52
70
 
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
71
+ In auto mode, at M and L the orchestrator still emits the briefing at each boundary, as a running
72
+ EM-to-CTO account. At S only the How briefing fires; see `## Depth at Tier S` above for what it
73
+ folds in. The Call line becomes the go-ahead the orchestrator takes itself and moves on, except at
55
74
  the existing hard stops (destructive action without a safe alternative, project-path confirm).
@@ -37,15 +37,31 @@ One thinker agent boots ONCE and stays in a single context for two stations:
37
37
 
38
38
  Then a sonnet executor (a fresh dispatch, this is the one topology split that always
39
39
  happens) implements from plan.md + checklist.md, checks off items, appends `## Insights`,
40
- and drives the suite green, exactly as it does for L.
40
+ and drives the suite green. At S/M that is ONE executor dispatch for the whole consolidated
41
+ action: no per-task implementer, and no per-task spec review or quality review. L keeps the
42
+ per-task loop. The gate lives in `plastic-intent-executing`'s Subagent-Driven workflow,
43
+ which reads the `Tier:` line stamped at the top of spec.md, the authoritative record. The
44
+ arithmetic: an S plan with N tasks costs 3N plus 1 agent boots under the L shape, and 2
45
+ boots under this one.
41
46
 
42
47
  The independent reviewer still runs at the final gate for S/M, in its own fresh context,
43
48
  never the maker. This is on the never-cut list; it does not collapse.
44
49
 
50
+ S sends ONE mid-flight owner briefing instead of four. Only the How briefing fires, at the
51
+ point the plan is ready and before any code is written, and it folds in what the What and
52
+ Why briefings would have said; the Exec briefing folds into the final owner report at End.
53
+ M and L send all four. The briefing calls live in `plastic-auto`'s stage sections, and the
54
+ depth note lives in `references/human-report-contract.md`.
55
+
45
56
  S may skip the QMD discovery deposit (normally a `plastic-intent-discovery` pass before
46
57
  Why) when the intent's `chain` and `sources` are both empty in frontmatter. With no graph
47
58
  edges there is nothing to discover, so the deposit is pure overhead; a one-line context
48
- note ("no chain/sources, discovery skipped") takes its place.
59
+ note ("no chain/sources, discovery skipped") takes its place, written to
60
+ `resources/discovery--<slug>.md`. Three places implement the skip: the dispatch site in
61
+ `plastic-intent-starting` step 4, the precondition in `plastic-intent-discovering`, and the
62
+ same precondition in the `plastic-intent-discovery` agent file. The skip needs a size already
63
+ on record (a stamped `Tier: S` line in spec.md), and sizing happens at Why, so a first
64
+ activation usually runs the full pass.
49
65
 
50
66
  ## L topology, unchanged
51
67
 
@@ -70,6 +86,12 @@ nothing else depends on it. No frontmatter schema change, no new file, no doctor
70
86
  gate check. If a later intent wants doctor or a gate to validate the line, that is a
71
87
  separate, explicit follow-up; this system deliberately adds no new operational surface.
72
88
 
89
+ Settledness is a separate line, `Settled: yes (<reason>)`, directly beneath the `Tier:` line
90
+ and above the `# Spec:` heading. It records that a design has already done its thinking. An
91
+ absent line means not settled; there is no `Settled: no` form. Settledness and scope are
92
+ independent, so a large intent can be settled without becoming a smaller tier.
93
+ `scripts/lib/spec_header.rb` is the only parser of both lines.
94
+
73
95
  ## Never-cut list, the safety floor
74
96
 
75
97
  At any tier or mode: the independent reviewer (separate agent, fresh context, never the
@@ -38,3 +38,6 @@ directory is frozen. A crash mid-tail is recovered by stale-lock reclaim plus fi
38
38
  tail; `doctor` surfaces this as a "stalled completion" (terminal in INDEX but the lock is
39
39
  still present or stale). Finishing the tail is FINISHING a completion, never a reactivation:
40
40
  a done intent is never moved back to `## Active`.
41
+
42
+ One report per audience: a delivery produces `outcome.md` plus one EM-to-CTO owner report, and
43
+ no other step restates either (see `skills/auto/references/human-report-contract.md`).
@@ -4,20 +4,36 @@ This chapter holds the escape-and-logging depth for each transition gate.
4
4
 
5
5
  #### The gates by name
6
6
 
7
- Each gate guards one thing. All are hard:
7
+ Each gate guards one thing. `scripts/lib/hook_registry.rb` is the single source of truth for
8
+ registration. On the edit path (Write, Edit, NotebookEdit, and MCP structural edits) five
9
+ gates run inside one dispatcher process per write, in this fixed order with the first deny
10
+ winning:
8
11
 
9
- - **create-gate** validates the proposed intent file at What write-time (Write, Edit, and MCP
10
- edits), so a malformed or incomplete intent never lands.
11
- - **gate-check** enforces lifecycle stage order (spec.md before plan.md, the plan triplet before
12
- the checklist, all checklist items before outcome.md).
12
+ - **savepoint-pre** appends the `started` savepoint ledger line before a write into an intent
13
+ directory; it records and never denies.
13
14
  - **lock-gate** arbitrates ownership and claims: it admits only the intent's lock owner or a
14
15
  registered delegate to write into an active intent directory, and every deny names the
15
16
  resolving `plastic-lock` command.
16
- - **bash-gate** intercepts a write attempted through a bash or interpreter one-liner (a heredoc, a
17
- `>` redirect, a `ruby -e` or `python -c` write), so the same rules apply whether an edit goes
18
- through the Write tool or a shell. A trailing `# plastic-ok` comment is an auditable escape that
19
- lets a deliberate command through, and every use is logged to
20
- `~/.plastic/.cache/gate-escapes.log`. The code gate (Write and Edit) carries the identical
21
- audited `# plastic-ok` escape, logged to the same file. The escape does not extend to
22
- `NotebookEdit` or MCP structural edits: they are still gated, just without an escape hatch.
17
+ - **code-gate** denies a code edit before How is delivered and a code edit outside the
18
+ provisioned worktree (stage rule or worktree rule, first match wins). It carries the audited
19
+ `# plastic-ok` escape described under bash-gate.
20
+ - **links-gate** is the write-time belt for the `## Links` contract: hand-written links lines
21
+ that do not project from the `sources`/`chain` frontmatter are denied.
22
+ - **create-gate** validates the proposed intent file at What write-time (Write, Edit, and MCP
23
+ edits), so a malformed or incomplete intent never lands.
24
+
25
+ Alongside the edit-path dispatcher the registry defines:
26
+
27
+ - **bash-gate** (on the Bash matcher) intercepts a write attempted through a bash or interpreter
28
+ one-liner (a heredoc, a `>` redirect, a `ruby -e` or `python -c` write), so the same rules
29
+ apply whether an edit goes through the Write tool or a shell. A trailing `# plastic-ok`
30
+ comment is an auditable escape that lets a deliberate command through, and every use is
31
+ logged to `~/.plastic/.cache/gate-escapes.log`. The code gate (Write and Edit) carries the
32
+ identical audited `# plastic-ok` escape, logged to the same file. The escape does not extend
33
+ to `NotebookEdit` or MCP structural edits: they are still gated, just without an escape hatch.
34
+ - **gate-check** (PostToolUse on Write and Edit) enforces lifecycle stage order (spec.md before
35
+ plan.md, the plan triplet before the checklist, all checklist items before outcome.md).
36
+ - **future-intent-check** (UserPromptSubmit) surfaces parked future intents whose keywords match
37
+ the user's message; like the other prompt-time hooks (continue, auto-arm, power-tools) it
38
+ informs and never denies.
23
39