@zalom/plastic 1.8.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 (127) hide show
  1. package/PLASTIC.md +94 -556
  2. package/README.md +18 -6
  3. package/agents/plastic-enforcer.md +3 -2
  4. package/agents/plastic-intent-curator.md +2 -2
  5. package/agents/plastic-intent-discovery.md +7 -0
  6. package/bin/plastic.js +17 -8
  7. package/deprecations.yml +10 -2
  8. package/hooks/auto-arm +2 -2
  9. package/hooks/bash-gate +1 -1
  10. package/hooks/check-update +1 -1
  11. package/hooks/continue +2 -2
  12. package/hooks/edit-gates +3 -0
  13. package/hooks/future-intent-check +2 -2
  14. package/hooks/gate-check +3 -3
  15. package/hooks/hooks.json +4 -44
  16. package/hooks/power-tools +8 -0
  17. package/hooks/session-start +1 -1
  18. package/package.json +1 -2
  19. package/scripts/codex-hook +57 -118
  20. package/scripts/doctor.rb +146 -1012
  21. package/scripts/exec-worktree +103 -0
  22. package/scripts/hash-intent +1 -1
  23. package/scripts/hook-bash-gate +19 -0
  24. package/scripts/hook-code-gate +16 -28
  25. package/scripts/hook-continue +2 -2
  26. package/scripts/hook-create-gate +13 -57
  27. package/scripts/hook-edit-gates +58 -0
  28. package/scripts/hook-gate-check +17 -0
  29. package/scripts/hook-links-gate +11 -31
  30. package/scripts/hook-lock-gate +19 -60
  31. package/scripts/hook-power-tools +38 -0
  32. package/scripts/hook-savepoint-pre +11 -11
  33. package/scripts/hook-session-start +21 -15
  34. package/scripts/lib/apply_patch_envelope.rb +46 -13
  35. package/scripts/lib/bridge.rb +85 -17
  36. package/scripts/lib/codex_edit_gates.rb +138 -0
  37. package/scripts/lib/doctor_core.rb +1087 -0
  38. package/scripts/lib/edit_gates.rb +398 -0
  39. package/scripts/lib/exec_worktree.rb +325 -0
  40. package/scripts/lib/harness_text.rb +57 -0
  41. package/scripts/lib/hook_registry.rb +64 -28
  42. package/scripts/lib/installer_core.rb +166 -24
  43. package/scripts/lib/lock.rb +196 -47
  44. package/scripts/lib/qmd_hook.rb +21 -41
  45. package/scripts/lib/qmd_sync.rb +0 -15
  46. package/scripts/lib/revisions_writer.rb +1 -1
  47. package/scripts/lib/ruby_probe.rb +60 -0
  48. package/scripts/lib/scaffold_intent.rb +392 -0
  49. package/scripts/lib/spec_header.rb +83 -0
  50. package/scripts/lib/start_intent.rb +296 -0
  51. package/scripts/lib/verify_intent.rb +262 -0
  52. package/scripts/lib/worktree.rb +15 -1
  53. package/scripts/link-suggest +1 -1
  54. package/scripts/maintenance-run +5 -5
  55. package/scripts/migrate-to-global +2 -2
  56. package/scripts/restore-intent-v1 +17 -12
  57. package/scripts/scaffold-intent +120 -0
  58. package/scripts/start-intent +89 -0
  59. package/scripts/verify-intent +73 -0
  60. package/skills/agent-advisor/SKILL.md +5 -5
  61. package/skills/auto/SKILL.md +52 -32
  62. package/skills/auto/evals/evals.json +2 -2
  63. package/skills/auto/references/agent-architecture.md +1 -1
  64. package/skills/auto/references/agent-report-contract.md +1 -1
  65. package/skills/auto/references/human-report-contract.md +22 -3
  66. package/skills/auto/references/tiers.md +24 -2
  67. package/skills/conventions/SKILL.md +31 -0
  68. package/skills/conventions/references/completion-and-done.md +43 -0
  69. package/skills/conventions/references/gates-and-enforcement.md +39 -0
  70. package/skills/conventions/references/knowledge-graph.md +47 -0
  71. package/skills/conventions/references/lifecycle-and-savepoints.md +11 -0
  72. package/skills/conventions/references/locks-and-worktrees.md +113 -0
  73. package/skills/conventions/references/maintenance-and-revisions.md +170 -0
  74. package/skills/conventions/references/roadmaps.md +44 -0
  75. package/skills/conventions/references/tiers-and-dispatch.md +135 -0
  76. package/skills/dashboard/SKILL.md +1 -1
  77. package/skills/doctor/SKILL.md +15 -6
  78. package/skills/doctor/references/gates-stuck-detection.md +13 -8
  79. package/skills/doctor/report.md +1 -1
  80. package/skills/install/SKILL.md +1 -1
  81. package/skills/intent-brainstorming/SKILL.md +4 -2
  82. package/skills/intent-continuing/SKILL.md +4 -0
  83. package/skills/intent-creating/SKILL.md +13 -8
  84. package/skills/intent-creating/references/lifecycle.md +1 -1
  85. package/skills/intent-discovering/SKILL.md +10 -3
  86. package/skills/intent-ending/SKILL.md +12 -7
  87. package/skills/intent-executing/SKILL.md +37 -19
  88. package/skills/intent-grilling/SKILL.md +5 -3
  89. package/skills/intent-linking/SKILL.md +4 -0
  90. package/skills/intent-locking/SKILL.md +4 -0
  91. package/skills/intent-planning/SKILL.md +14 -3
  92. package/skills/intent-researching/SKILL.md +0 -2
  93. package/skills/intent-savepoint/SKILL.md +4 -0
  94. package/skills/intent-speccing/SKILL.md +4 -0
  95. package/skills/intent-starting/SKILL.md +20 -2
  96. package/skills/project-creating/SKILL.md +0 -2
  97. package/skills/project-creating/references/project-scaffolding.md +3 -3
  98. package/skills/releasing/SKILL.md +1 -1
  99. package/skills/releasing/references/promotion-and-tagging.md +14 -8
  100. package/skills/releasing/references/release-lines.md +1 -1
  101. package/skills/roadmap/SKILL.md +4 -0
  102. package/skills/roadmap-continuing/SKILL.md +4 -0
  103. package/skills/skill-creating/SKILL.md +8 -2
  104. package/skills/skill-creating/references/defaults-first.md +23 -0
  105. package/skills/skill-creating/references/hooks.md +4 -1
  106. package/skills/store-curating/SKILL.md +8 -0
  107. package/skills/store-indexing/SKILL.md +16 -5
  108. package/skills/store-indexing/references/zettelkasten-linking.md +1 -1
  109. package/skills/tutorial/references/track-1-guided.md +2 -2
  110. package/skills/tutorial/references/track-2-auto.md +10 -8
  111. package/skills/tutorial/references/track-3-projects-and-roadmaps.md +1 -1
  112. package/skills/uninstall/SKILL.md +6 -9
  113. package/templates/agents.md +12 -12
  114. package/templates/config.yml +6 -7
  115. package/templates/index.md +6 -3
  116. package/templates/spec.md +1 -1
  117. package/PLASTIC-reference.md +0 -138
  118. package/hooks/code-gate +0 -27
  119. package/hooks/create-gate +0 -3
  120. package/hooks/links-gate +0 -3
  121. package/hooks/lock-gate +0 -21
  122. package/hooks/qmd-search +0 -8
  123. package/hooks/retrieval-gate +0 -10
  124. package/hooks/savepoint-pre +0 -10
  125. package/scripts/hook-qmd-search +0 -44
  126. package/scripts/hook-retrieval-gate +0 -148
  127. package/scripts/lib/retrieval_gate.rb +0 -211
@@ -6,9 +6,10 @@
6
6
  # while preserving its frontmatter GRAPH (sources/chain) as a target-resolved
7
7
  # union of the v1 snapshot and the current snapshot (intent 193). This is the
8
8
  # ONLY sanctioned way to restore a completed intent to v1; a hand-run whole-file
9
- # `git checkout`/revert is forbidden (see PLASTIC.md > Terminal immutability),
10
- # because it cannot tell prose from graph metadata and silently destroys
11
- # backlinks written after v1 (the 124/131 incident this tool exists to prevent).
9
+ # `git checkout`/revert is forbidden (see plastic-conventions > references/maintenance-and-revisions.md,
10
+ # WORK vs MAINTENANCE and its Restore-to-v1 paragraph), because it cannot tell prose from graph
11
+ # metadata and silently destroys backlinks written after v1 (the 124/131 incident this tool
12
+ # exists to prevent).
12
13
  #
13
14
  # Usage:
14
15
  # restore-intent-v1 <intent-id> --at <git-ref> [--plastic-home PATH] [--apply] \
@@ -19,12 +20,14 @@
19
20
  # class of tool already destroyed live store data once. --apply is required to
20
21
  # write.
21
22
  #
22
- # Restore-to-v1 runs under the MAINTENANCE lock (PLASTIC.md > Terminal
23
- # immutability: the maintenance lock covers sanctioned structural move-and-record
24
- # edits after completion). This tool does NOT acquire, check, or manage that
25
- # lock itself (fail-open doctrine, intent 111: lock management is the
26
- # orchestrator's job, never built into a CLI as a trap); it only prints a
27
- # one-line reminder on --apply.
23
+ # There is exactly one lock in Plastic, `delivery.lock`, and MAINTENANCE (this tool included)
24
+ # never acquires or checks it: WORK vs MAINTENANCE (plastic-conventions >
25
+ # references/maintenance-and-revisions.md) holds the corrected doctrine after intent 112's
26
+ # proposed second "maintenance.lock" was abandoned before merge and never shipped. This tool does
27
+ # NOT acquire, check, or manage `delivery.lock` itself (fail-open doctrine, intent 111: lock
28
+ # management is the orchestrator's job, never built into a CLI as a trap); it only prints a
29
+ # one-line reminder on --apply that the target intent must be terminal, which the caller is
30
+ # responsible for confirming before running with --apply.
28
31
  #
29
32
  # LINKS BLAST RADIUS: an applied restore reprojects `## Links` via
30
33
  # scripts/project-links, which is a STORE-WIDE operation with no per-intent
@@ -142,8 +145,10 @@ class RestoreIntentV1CLI
142
145
  File.write(md_path, new_md) if md_changed
143
146
  other_files.each { |f, content| File.write(File.join(dir, f), content) }
144
147
 
145
- puts "Reminder: restore-to-v1 runs under the maintenance lock (PLASTIC.md > Terminal " \
146
- "immutability). Confirm the maintenance lock is held before this --apply."
148
+ puts "Reminder: restore-to-v1 is MAINTENANCE, not WORK (plastic-conventions > " \
149
+ "references/maintenance-and-revisions.md, WORK vs MAINTENANCE). There is no " \
150
+ "maintenance lock to hold; confirm the target intent is terminal (Completed or " \
151
+ "Abandoned) with no fresh delivery.lock before this --apply."
147
152
 
148
153
  handle_links_reprojection(base)
149
154
  append_revision(dir, base, graph, files: prose_changes,
@@ -266,7 +271,7 @@ class RestoreIntentV1CLI
266
271
 
267
272
  def reproject_links
268
273
  project_links = File.expand_path("project-links", __dir__)
269
- system(RbConfig.ruby, project_links, "--plastic-home", plastic_home)
274
+ system({"RUBYOPT" => nil}, RbConfig.ruby, project_links, "--plastic-home", plastic_home)
270
275
  return if $?.success?
271
276
 
272
277
  warn "restore-intent-v1: ## Links may now be stale (project-links reprojection failed). " \
@@ -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,26 @@ 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.
94
+
95
+ Read `../plastic-conventions/references/tiers-and-dispatch.md` for tier sizing, agent-model
96
+ config, advisor routing, and the auto-mode human report contract behind the sizing above. This
97
+ path resolves relative to this skill's own installed directory.
87
98
 
88
99
  ## Arm the Lifecycle Gate (do this FIRST)
89
100
 
@@ -110,6 +121,9 @@ implies for later tool calls) read `references/end-tail.md`.
110
121
  intent directory / `~/.plastic/`) until `plan.md` AND `checklist.md` exist for the intent.
111
122
  Honor the cycle: What → Why (spec.md) → How (plan.md + actions/ + checklist.md) → Exec.
112
123
 
124
+ Read `../plastic-conventions/references/locks-and-worktrees.md` for delivery isolation: the
125
+ single-owner lock, claims, worktrees, solo mode, and the station ledger behind the arming above.
126
+
113
127
  ## Flags
114
128
 
115
129
  - `--skip-permissions` - bypass hard stops on destructive actions on existing projects. Full trust mode. Default: off.
@@ -130,7 +144,7 @@ Roster (one role per cycle stage):
130
144
 
131
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.
132
146
 
133
- 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)).
134
148
 
135
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,
136
150
  unless an explicit `agents.models.<name>` config override names Fable for that role, in which
@@ -210,9 +224,9 @@ Filesystem fallback (ledger missing only):
210
224
 
211
225
  Announce which stage you're entering and why.
212
226
 
213
- 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`
214
228
  (State: the work picked up and why it matters now; Risk: scope uncertainty; Call: confirm
215
- 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.
216
230
 
217
231
  ## Why Completion (Autonomous)
218
232
 
@@ -228,9 +242,10 @@ When entering at Why stage:
228
242
  5. Make decisions - pick best option, document in `## Context > ### Decisions` with rationale
229
243
  6. Log all autonomous decisions in `## Insights` with `(autonomous)` marker: "Decision: chose X because Y (autonomous)"
230
244
  7. Write `spec.md` - consolidated specification
231
- 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`
232
246
  (State: the approach chosen, one line; Risk: the main trade-off; Call: the one decision
233
- 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.
234
249
 
235
250
  Then proceed to How.
236
251
 
@@ -246,9 +261,12 @@ one consolidated `actions/ACTION_1.md`, L writes one `actions/ACTION_N.md` per t
246
261
  2. Otherwise, write `plan.md` directly - implementation plan with numbered tasks
247
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)
248
263
  4. Write `checklist.md` - execution registry with checkboxes covering all actions
249
- 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`
250
265
  (State: the plan shape, task count and what it builds; Risk: the riskiest task or
251
- 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.
252
270
 
253
271
  Then proceed to Exec.
254
272
 
@@ -257,7 +275,7 @@ Then proceed to Exec.
257
275
  If the plan calls for creating a new project (the intent is an implementation intent that needs a new codebase):
258
276
 
259
277
  1. Determine project path from `~/.plastic/config.yml` `project_roots` or from intent context
260
- 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:
261
279
  > "Creating project `<slug>` at `<path>`. Confirm path, or provide alternative."
262
280
  3. Invoke `plastic-project-creating` skill
263
281
  4. The global intent is now Completed (creating-project handles this)
@@ -272,9 +290,10 @@ If the plan calls for creating a new project (the intent is an implementation in
272
290
  4. Check off items in `checklist.md` as completed
273
291
  5. Append observations to `## Insights` with `(autonomous)` marker
274
292
  6. Sub-agents can be spawned for parallel actions (one agent per action)
275
- 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`
276
294
  (State: what got built and the test result; Risk: residual failures or deviations;
277
- 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.
278
297
 
279
298
  ## Permission Model - Safe-by-Default
280
299
 
@@ -303,17 +322,19 @@ During initial project creation, all decisions are non-destructive by definition
303
322
 
304
323
  ## Completion
305
324
 
325
+ Read `../plastic-conventions/references/completion-and-done.md` for what "intent done" means and
326
+ the End-stage tail the steps below walk through.
327
+
306
328
  1. Verify all checklist items are checked
307
- 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`.
308
330
  Set the frontmatter `disposition: delivered` (this is the delivered terminal). `outcome.md`
309
331
  is mandatory at every terminal and self-declares its disposition (see the canonical done-marker
310
332
  and End tail in PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
311
- 3. Write `## Outcome` summary in the intent file (1-2 sentences)
312
- 4. **Release (if configured)**
313
- 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).
314
- 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.
315
336
  3. Based on `release.on_complete`:
316
- - `commit` - git add + commit (same as default, proceed to step 5)
337
+ - `commit` - git add + commit (same as default, proceed to step 4)
317
338
  - `commit_and_push` - git add + commit + push
318
339
  - `manual` - skip auto-commit, notify user: "Release configured as manual - commit when ready."
319
340
  4. If `release.verify` is set, run the verify command (e.g. `bundle exec rake test`):
@@ -323,14 +344,15 @@ During initial project creation, all decisions are non-destructive by definition
323
344
  - `stop` - write `savepoint.md` with current state, notify user: "Verify failed - savepoint written.", **STOP**
324
345
  - `manual` - notify user: "Verify failed: [summary]. Resolve manually."
325
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.
326
- 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:
327
348
  - Create them (using `plastic-intent-creating` conventions)
328
349
  - Update `chain` in the current intent's frontmatter
329
- 6. Run the mechanical close through `plastic-intent-ending`: it owns steps 1-6 of the Done
330
- procedure (outcome/INDEX/savepoint/commit, disarm, and the QMD reindex last) as ONE
331
- delegation, not five separate one-liners restated here. `scripts/end-intent` now performs
332
- steps 1-5 itself, INCLUDING disarm (worktree release plus clearing `delivery.lock`): a
333
- single call closes the intent AND clears its lock, so exit 0 means both are done. Pass
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
334
356
  `--session` (this session's id, or rely on the `CLAUDE_CODE_SESSION_ID` fallback) so
335
357
  disarm resolves the right bridge, and `--index-note` with a rich Completed/Abandoned entry
336
358
  description (mode/tier, what shipped or why abandoned, suite result):
@@ -344,13 +366,11 @@ During initial project creation, all decisions are non-destructive by definition
344
366
  lock (back off), 5 means the code worktree is dirty (commit/stash first, or pass
345
367
  `--discard-worktree-changes` deliberately), 3 means disarm ran but the lock is still
346
368
  present (run `/plastic-doctor check the lock status`), 6 means the structure gate refused
347
- (see the named reason on stderr; fix via the owning tool named above, then re-run). Only
348
- 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).
349
372
  Never leave an orphaned worktree; run `git worktree prune` on a stale reference. If any of
350
373
  this ever needs to change, change `plastic-intent-ending`, not this skill.
351
- 7. Notify user (Done briefing): brief per `references/human-report-contract.md`
352
- (State: the delivered impact; Risk: residual risk; Call: the decision left to you, merge,
353
- release, or accept). See `outcome.md` for details.
354
374
 
355
375
  ## Error Handling
356
376
 
@@ -208,13 +208,13 @@
208
208
  "scope": "behavior",
209
209
  "set": "validation",
210
210
  "prompt": "A power-tool is present (qmd on PATH, or a .serena marker / serena on PATH). A substantive prompt arrives in auto mode.",
211
- "expected_output": "The UserPromptSubmit power-tools hook appends a MANDATORY obligation per present tool: a MUST-use-QMD line when qmd is present (to check for an existing or related intent before treating work as new), and a MUST-use-Serena line when serena is present (symbolic tools before grep/Read). QMD hits are still injected when above threshold.",
211
+ "expected_output": "The UserPromptSubmit power-tools hook appends a MANDATORY obligation per present tool: a MUST-use-QMD line when qmd is present (to check for an existing or related intent before treating work as new), and a MUST-use-Serena line when serena is present (symbolic tools before grep/Read). No search hits are injected: intent 246 removed the per-prompt qmd hit search, so the hook emits the mandate and nothing else.",
212
212
  "files": [],
213
213
  "assertions": [
214
214
  {
215
215
  "type": "code",
216
216
  "check": "PowerTools.mandate returns MUST/MANDATORY lines for each present tool; QmdHook.run appends the mandate",
217
- "observed": "power_tools_test.rb + qmd_hook_test.rb assert MUST wording; serena line gated on the serena detector",
217
+ "observed": "power_tools_test.rb + qmd_hook_test.rb assert MUST wording; serena line gated on the serena detector; qmd_hook_test.rb test_no_retrieval_hits_are_ever_injected asserts no hit header for any tool combination",
218
218
  "result": "pass"
219
219
  }
220
220
  ]
@@ -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).