@zalom/plastic 1.9.0 → 1.11.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 +16 -5
- 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 +25 -8
- 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 +10 -9
- 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
- package/skills/humanizer/SKILL.md +0 -40
- package/skills/humanizer/evals/evals.json +0 -70
- package/skills/humanizer/references/always-on-snippet.md +0 -9
- package/skills/humanizer/references/examples.md +0 -48
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# encoding: UTF-8
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# CodexEditGates (intent 251): the merged Codex apply_patch edit path. One
|
|
5
|
+
# process, one stdin parse, one envelope parse, five gates in-process.
|
|
6
|
+
#
|
|
7
|
+
# Before this, ~/.codex/hooks.json registered five separate PreToolUse commands
|
|
8
|
+
# on the apply_patch matcher and three of them spawned a further run_core child
|
|
9
|
+
# per file operation: eight processes per edit, ten across a full edit cycle.
|
|
10
|
+
# This is the Codex mirror of what intent 244 did for Claude (spec D-e of that
|
|
11
|
+
# intent parked it as a successor).
|
|
12
|
+
#
|
|
13
|
+
# Every DECISION here comes from scripts/lib/edit_gates.rb, the same functions
|
|
14
|
+
# scripts/hook-edit-gates drives for Claude. This file contributes exactly three
|
|
15
|
+
# Codex-specific things and nothing else:
|
|
16
|
+
# 1. the per-operation Context (the patch envelope carries a path plus ADDED
|
|
17
|
+
# lines, not Claude's file_path plus content pair),
|
|
18
|
+
# 2. the add-only rule for create-gate (Update/Delete/Move defer to the
|
|
19
|
+
# PostToolUse gate-check backstop, intent 102 spec Decision 5), and
|
|
20
|
+
# 3. the two-pass evaluation that keeps savepoint-pre's ledger append
|
|
21
|
+
# unconditional across a multi-operation patch (intent 244 spec D-c).
|
|
22
|
+
#
|
|
23
|
+
# NO SUBPROCESS MAY EVER BE ADDED TO THIS FILE. That is the whole point of the
|
|
24
|
+
# intent, and test/codex_edit_gates_test.rb asserts it against this source.
|
|
25
|
+
require "stringio"
|
|
26
|
+
require_relative "edit_gates"
|
|
27
|
+
require_relative "hook_registry"
|
|
28
|
+
|
|
29
|
+
module CodexEditGates
|
|
30
|
+
module_function
|
|
31
|
+
|
|
32
|
+
# Pass 1 and pass 2 of the two-pass evaluation (spec D3). Both slices preserve
|
|
33
|
+
# HookRegistry::CODEX_GATE_TOOLS key order, which is Claude's evaluation order
|
|
34
|
+
# (spec D5): savepoint-pre, lock-gate, code-gate, links-gate, create-gate.
|
|
35
|
+
SAVEPOINT_TOOLS = HookRegistry::CODEX_GATE_TOOLS.select { |gate, _| gate == "savepoint-pre" }.freeze
|
|
36
|
+
DENY_TOOLS = HookRegistry::CODEX_GATE_TOOLS.reject { |gate, _| gate == "savepoint-pre" }.freeze
|
|
37
|
+
|
|
38
|
+
# One Context per file operation. gate_path and file_path both carry the
|
|
39
|
+
# envelope's raw path, exactly as the five popened cores received it in ARGV[0]
|
|
40
|
+
# before the merge. content is always a String (never nil), because
|
|
41
|
+
# ApplyPatchEnvelope gives a Delete op a nil added_content and today's
|
|
42
|
+
# dispatcher already called .to_s on it at every call site. harness is "codex"
|
|
43
|
+
# so lock-gate and the claim gate render deny reasons in the $plastic-<name>
|
|
44
|
+
# form (intent 201 D2).
|
|
45
|
+
def context_for(payload, op, env: ENV)
|
|
46
|
+
session = payload["session_id"]
|
|
47
|
+
session = env["CLAUDE_CODE_SESSION_ID"] if session.to_s.empty?
|
|
48
|
+
|
|
49
|
+
EditGates::Context.new(
|
|
50
|
+
tool_name: payload["tool_name"],
|
|
51
|
+
session: (session unless session.to_s.empty?),
|
|
52
|
+
gate_path: op.path,
|
|
53
|
+
file_path: op.path,
|
|
54
|
+
savepoint_path: op.path,
|
|
55
|
+
content: op.added_content.to_s,
|
|
56
|
+
harness: "codex",
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# create-gate is a pre-write veto for ADD operations only (spec D2).
|
|
61
|
+
# EditGates.create_gate has no such rule: fed a partial Update hunk as content
|
|
62
|
+
# it would run IntentValidator on a fragment and FALSE-DENY a legitimate write.
|
|
63
|
+
def create_gate(ctx, op)
|
|
64
|
+
return EditGates::ALLOW unless op.op == :add
|
|
65
|
+
EditGates.create_gate(ctx)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def route_for(op)
|
|
69
|
+
lambda do |gate, ctx|
|
|
70
|
+
case gate
|
|
71
|
+
when "savepoint-pre" then EditGates.savepoint_pre(ctx)
|
|
72
|
+
when "lock-gate" then EditGates.lock_gate(ctx)
|
|
73
|
+
when "code-gate" then EditGates.code_gate(ctx)
|
|
74
|
+
when "links-gate" then EditGates.links_gate(ctx)
|
|
75
|
+
when "create-gate" then create_gate(ctx, op)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# True iff tool_name is a NON-EMPTY string that no CODEX_GATE_TOOLS entry
|
|
81
|
+
# matches. An empty/missing tool_name already means "the harness matched but
|
|
82
|
+
# did not name the tool" and every gate runs (EditGates.tool_applies?'s own
|
|
83
|
+
# empty-name rule); this is the OTHER unmatched case, a non-empty tool_name
|
|
84
|
+
# this dispatcher does not recognize. Checked once against "apply_patch",
|
|
85
|
+
# since every CODEX_GATE_TOOLS value is the identical single-entry list.
|
|
86
|
+
def unexpected_tool_name?(tool_name)
|
|
87
|
+
return false if tool_name.to_s.empty?
|
|
88
|
+
!EditGates.tool_applies?(tool_name, %w[apply_patch])
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns the process exit code. Two passes (spec D3):
|
|
92
|
+
# pass 1 runs savepoint-pre over EVERY op. It never denies, and running it
|
|
93
|
+
# before any deny check keeps its started ledger append unconditional, so
|
|
94
|
+
# op B's ledger line still lands when op A is blocked.
|
|
95
|
+
# pass 2 runs the four denying gates per op, first deny wins.
|
|
96
|
+
# EditGates.dispatch returns 0 both for "allowed" and for lock-gate's JSON
|
|
97
|
+
# deny, so pass 2 captures stdout per op: a non-empty buffer IS a deny, and it
|
|
98
|
+
# is printed and evaluation stops. Without that capture a second op could emit
|
|
99
|
+
# a second deny JSON.
|
|
100
|
+
#
|
|
101
|
+
# Codex only ever invokes this dispatcher from the apply_patch matcher, so by
|
|
102
|
+
# the time we get here the harness HAS already matched, regardless of what
|
|
103
|
+
# tool_name the payload itself carries. A non-empty, unrecognized tool_name
|
|
104
|
+
# (a future Codex rename, or a malformed/adversarial payload) must therefore
|
|
105
|
+
# never silently skip every gate: that is the coverage WEAKENING
|
|
106
|
+
# EditGates.tool_applies?'s own empty-name rule exists to prevent, just
|
|
107
|
+
# triggered from the opposite direction (an unrecognized name here, not a
|
|
108
|
+
# missing one there). Loud and safe: warn once to err and run all five gates
|
|
109
|
+
# exactly as if tool_name had been "apply_patch", rather than silent and
|
|
110
|
+
# permissive.
|
|
111
|
+
def dispatch(ops:, payload:, out: $stdout, err: $stderr)
|
|
112
|
+
if unexpected_tool_name?(payload["tool_name"])
|
|
113
|
+
err.puts "plastic codex edit-gates: unexpected tool_name #{payload["tool_name"].inspect}; " \
|
|
114
|
+
"the apply_patch matcher already selected this dispatcher, so all five gates run anyway"
|
|
115
|
+
payload = payload.merge("tool_name" => "apply_patch")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
pairs = ops.map { |op| [op, context_for(payload, op)] }
|
|
119
|
+
|
|
120
|
+
pairs.each do |op, ctx|
|
|
121
|
+
EditGates.dispatch(ctx: ctx, route: route_for(op), gate_tools: SAVEPOINT_TOOLS,
|
|
122
|
+
out: StringIO.new, err: err)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
pairs.each do |op, ctx|
|
|
126
|
+
buffer = StringIO.new
|
|
127
|
+
code = EditGates.dispatch(ctx: ctx, route: route_for(op), gate_tools: DENY_TOOLS,
|
|
128
|
+
out: buffer, err: err)
|
|
129
|
+
return code unless code.zero?
|
|
130
|
+
next if buffer.string.empty?
|
|
131
|
+
|
|
132
|
+
out.print buffer.string
|
|
133
|
+
return 0
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
0
|
|
137
|
+
end
|
|
138
|
+
end
|