@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.
- package/PLASTIC.md +23 -8
- package/README.md +15 -4
- package/agents/plastic-enforcer.md +3 -2
- package/agents/plastic-intent-discovery.md +7 -0
- package/bin/plastic.js +17 -8
- package/hooks/auto-arm +2 -2
- package/hooks/bash-gate +1 -1
- package/hooks/check-update +1 -1
- package/hooks/continue +2 -2
- package/hooks/edit-gates +1 -1
- package/hooks/future-intent-check +2 -2
- package/hooks/gate-check +3 -3
- package/hooks/power-tools +1 -1
- package/hooks/session-start +1 -1
- package/package.json +1 -1
- package/scripts/codex-hook +50 -106
- package/scripts/doctor.rb +132 -1080
- package/scripts/exec-worktree +103 -0
- package/scripts/hash-intent +1 -1
- package/scripts/hook-bash-gate +19 -0
- package/scripts/hook-code-gate +4 -1
- package/scripts/hook-continue +2 -2
- package/scripts/hook-create-gate +6 -3
- package/scripts/hook-gate-check +17 -0
- package/scripts/hook-links-gate +4 -1
- package/scripts/hook-lock-gate +7 -3
- package/scripts/hook-savepoint-pre +4 -1
- package/scripts/hook-session-start +21 -15
- package/scripts/lib/apply_patch_envelope.rb +46 -13
- package/scripts/lib/bridge.rb +83 -15
- package/scripts/lib/codex_edit_gates.rb +138 -0
- package/scripts/lib/doctor_core.rb +1087 -0
- package/scripts/lib/edit_gates.rb +61 -5
- package/scripts/lib/exec_worktree.rb +325 -0
- package/scripts/lib/harness_text.rb +57 -0
- package/scripts/lib/hook_registry.rb +32 -28
- package/scripts/lib/installer_core.rb +67 -7
- package/scripts/lib/lock.rb +196 -47
- package/scripts/lib/ruby_probe.rb +60 -0
- package/scripts/lib/scaffold_intent.rb +392 -0
- package/scripts/lib/spec_header.rb +83 -0
- package/scripts/lib/start_intent.rb +296 -0
- package/scripts/lib/verify_intent.rb +262 -0
- package/scripts/lib/worktree.rb +15 -1
- package/scripts/link-suggest +1 -1
- package/scripts/maintenance-run +5 -5
- package/scripts/migrate-to-global +2 -2
- package/scripts/restore-intent-v1 +1 -1
- package/scripts/scaffold-intent +120 -0
- package/scripts/start-intent +89 -0
- package/scripts/verify-intent +73 -0
- package/skills/agent-advisor/SKILL.md +5 -5
- package/skills/auto/SKILL.md +42 -32
- package/skills/auto/references/agent-architecture.md +1 -1
- package/skills/auto/references/agent-report-contract.md +1 -1
- package/skills/auto/references/human-report-contract.md +22 -3
- package/skills/auto/references/tiers.md +24 -2
- package/skills/conventions/references/completion-and-done.md +3 -0
- package/skills/conventions/references/gates-and-enforcement.md +28 -12
- package/skills/conventions/references/locks-and-worktrees.md +3 -3
- package/skills/conventions/references/tiers-and-dispatch.md +7 -6
- package/skills/dashboard/SKILL.md +1 -1
- package/skills/doctor/SKILL.md +6 -5
- package/skills/doctor/references/gates-stuck-detection.md +13 -8
- package/skills/doctor/report.md +1 -1
- package/skills/install/SKILL.md +1 -1
- package/skills/intent-brainstorming/SKILL.md +0 -2
- package/skills/intent-creating/SKILL.md +6 -6
- package/skills/intent-creating/references/lifecycle.md +1 -1
- package/skills/intent-discovering/SKILL.md +10 -3
- package/skills/intent-ending/SKILL.md +8 -7
- package/skills/intent-executing/SKILL.md +27 -19
- package/skills/intent-grilling/SKILL.md +5 -3
- package/skills/intent-planning/SKILL.md +7 -3
- package/skills/intent-researching/SKILL.md +0 -2
- package/skills/intent-starting/SKILL.md +10 -2
- package/skills/project-creating/SKILL.md +0 -2
- package/skills/project-creating/references/project-scaffolding.md +1 -1
- package/skills/releasing/SKILL.md +1 -1
- package/skills/releasing/references/promotion-and-tagging.md +14 -8
- package/skills/releasing/references/release-lines.md +1 -1
- package/skills/skill-creating/SKILL.md +5 -2
- package/skills/store-indexing/SKILL.md +8 -5
- package/skills/store-indexing/references/zettelkasten-linking.md +1 -1
- package/skills/tutorial/references/track-1-guided.md +2 -2
- package/skills/tutorial/references/track-2-auto.md +9 -6
- package/skills/tutorial/references/track-3-projects-and-roadmaps.md +1 -1
- package/skills/uninstall/SKILL.md +6 -9
- package/templates/agents.md +12 -12
- package/templates/config.yml +6 -7
- package/templates/index.md +6 -3
- 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
|
|
43
|
-
|
|
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
|
|
46
|
-
`plastic-advisor`)
|
|
47
|
-
|
|
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.
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -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 (
|
|
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/`.
|
|
79
|
-
|
|
80
|
-
|
|
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** -
|
|
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
|
|
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.
|
|
322
|
-
|
|
323
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
procedure (outcome/INDEX/savepoint/commit, disarm,
|
|
341
|
-
delegation, not
|
|
342
|
-
steps 1-5 itself, INCLUDING disarm (worktree
|
|
343
|
-
single call closes the intent AND clears its
|
|
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).
|
|
358
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
54
|
-
account.
|
|
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
|
|
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.
|
|
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
|
-
- **
|
|
10
|
-
|
|
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
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
|