@zalom/plastic 1.14.0 → 2.0.0-alpha.1
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 +101 -294
- package/README.md +5 -6
- package/agents/plastic-advisor.md +18 -17
- package/agents/plastic-enforcer.md +57 -69
- package/agents/plastic-executor.md +34 -20
- package/agents/plastic-faux-advisor.md +18 -18
- package/config_asks.yml +38 -0
- package/hooks/capture +4 -0
- package/hooks/check-update +12 -3
- package/hooks/close +5 -0
- package/hooks/hooks.json +9 -49
- package/hooks/record +4 -0
- package/package.json +3 -2
- package/scripts/agent-report +8 -9
- package/scripts/append-ledger +195 -0
- package/scripts/codex-hook +101 -95
- package/scripts/dashboard.rb +4 -4
- package/scripts/doctor.rb +106 -43
- package/scripts/end-intent +77 -76
- package/scripts/exec-worktree +11 -20
- package/scripts/file-session-intent +61 -0
- package/scripts/hook-capture +242 -0
- package/scripts/hook-close +34 -0
- package/scripts/hook-record +149 -0
- package/scripts/hook-session-start +95 -12
- package/scripts/install.rb +8 -6
- package/scripts/lib/agent_models.rb +1 -7
- package/scripts/lib/arm.rb +283 -0
- package/scripts/lib/backfill_intent.rb +316 -0
- package/scripts/lib/bridge.rb +24 -1597
- package/scripts/lib/doctor_core.rb +17 -88
- package/scripts/lib/doctor_session_ledger.rb +106 -0
- package/scripts/lib/exec_worktree.rb +14 -60
- package/scripts/lib/harness_text.rb +5 -4
- package/scripts/lib/hook_registry.rb +44 -107
- package/scripts/lib/insights.rb +3 -4
- package/scripts/lib/installer_core.rb +19 -30
- package/scripts/lib/lock.rb +6 -26
- package/scripts/lib/outcome_guard.rb +7 -5
- package/scripts/lib/project_validator.rb +30 -1
- package/scripts/lib/roadmap_queue.rb +3 -3
- package/scripts/lib/roadmap_savepoint.rb +1 -1
- package/scripts/lib/rule_catalog.rb +6 -3
- package/scripts/lib/savepoint.rb +320 -0
- package/scripts/lib/scaffold_intent.rb +13 -179
- package/scripts/lib/session_backfill.rb +184 -0
- package/scripts/lib/session_close.rb +84 -0
- package/scripts/lib/session_git.rb +469 -0
- package/scripts/lib/session_ledger.rb +497 -0
- package/scripts/lib/store_discovery.rb +1 -1
- package/scripts/lib/store_provisioning.rb +1 -1
- package/scripts/maintenance-run +7 -7
- package/scripts/new-intent +63 -4
- package/scripts/plastic-lock +94 -41
- package/scripts/promote-session-item +150 -0
- package/scripts/rebuild-graph +1 -1
- package/scripts/roadmap-next +2 -2
- package/scripts/rollback.rb +42 -3
- package/scripts/scaffold-intent +37 -63
- package/scripts/session-commit +116 -0
- package/scripts/spawn-preamble +5 -5
- package/scripts/update.rb +7 -3
- package/skills/agent-advisor/SKILL.md +3 -3
- package/skills/agent-advisor/references/advisor-protocol.md +20 -20
- package/skills/auto/SKILL.md +218 -317
- package/skills/auto/evals/evals.json +10 -10
- package/skills/auto/references/agent-architecture.md +102 -90
- package/skills/auto/references/agent-report-contract.md +4 -21
- package/skills/auto/references/end-tail.md +47 -53
- package/skills/auto/references/human-report-contract.md +8 -8
- package/skills/conventions/SKILL.md +1 -3
- package/skills/conventions/references/lifecycle-and-savepoints.md +35 -1
- package/skills/conventions/references/locks-and-worktrees.md +47 -55
- package/skills/conventions/references/maintenance-and-revisions.md +12 -12
- package/skills/dashboard/SKILL.md +1 -1
- package/skills/direct/SKILL.md +66 -0
- package/skills/direct/references/request-signals.md +59 -0
- package/skills/doctor/SKILL.md +58 -11
- package/skills/doctor/report.md +3 -1
- package/skills/install/SKILL.md +11 -9
- package/skills/intent-continuing/SKILL.md +131 -89
- package/skills/intent-continuing/references/boarding-matrix.md +34 -0
- package/skills/intent-continuing/references/context-management.md +2 -2
- package/skills/intent-creating/SKILL.md +2 -2
- package/skills/intent-creating/references/lifecycle.md +1 -1
- package/skills/intent-ending/SKILL.md +28 -37
- package/skills/intent-ending/evals/evals.json +4 -4
- package/skills/intent-executing/SKILL.md +12 -49
- package/skills/intent-executing/plan-reviewer-prompt.md +39 -0
- package/skills/intent-speccing/SKILL.md +108 -51
- package/skills/intent-speccing/evals/evals.json +5 -5
- package/skills/intent-speccing/references/per-section-fill-rules.md +4 -4
- package/skills/intent-speccing/references/self-verify-checklist.md +1 -2
- package/skills/project-creating/SKILL.md +1 -1
- package/skills/releasing/SKILL.md +7 -8
- package/skills/tutorial/SKILL.md +2 -2
- package/skills/tutorial/references/track-1-guided.md +16 -18
- package/skills/tutorial/references/track-2-auto.md +12 -13
- package/skills/tutorial/references/track-3-projects-and-roadmaps.md +2 -2
- package/templates/agents.md +4 -7
- package/templates/project.yml +7 -0
- package/templates/roadmap.md +1 -1
- package/templates/savepoint.md +2 -2
- package/templates/session-intent.md +29 -0
- package/agents/plastic-brainstorming.md +0 -48
- package/agents/plastic-future-intent-researcher.md +0 -35
- package/agents/plastic-intent-curator.md +0 -47
- package/agents/plastic-intent-discovery.md +0 -43
- package/agents/plastic-planner.md +0 -56
- package/agents/plastic-spec-specialist.md +0 -53
- package/hooks/auto-arm +0 -5
- package/hooks/bash-gate +0 -3
- package/hooks/continue +0 -31
- package/hooks/edit-gates +0 -3
- package/hooks/future-intent-check +0 -25
- package/hooks/gate-check +0 -12
- package/hooks/power-tools +0 -8
- package/scripts/hook-auto-arm +0 -51
- package/scripts/hook-bash-gate +0 -78
- package/scripts/hook-code-gate +0 -38
- package/scripts/hook-continue +0 -48
- package/scripts/hook-create-gate +0 -51
- package/scripts/hook-edit-gates +0 -58
- package/scripts/hook-future-intent-check +0 -90
- package/scripts/hook-gate-check +0 -169
- package/scripts/hook-links-gate +0 -54
- package/scripts/hook-lock-gate +0 -47
- package/scripts/hook-power-tools +0 -38
- package/scripts/hook-savepoint-pre +0 -32
- package/scripts/lib/codex_edit_gates.rb +0 -138
- package/scripts/lib/edit_gates.rb +0 -398
- package/scripts/lib/links_gate.rb +0 -140
- package/scripts/lib/qmd_hook.rb +0 -41
- package/scripts/lib/spec_header.rb +0 -83
- package/scripts/lib/start_intent.rb +0 -296
- package/scripts/start-intent +0 -89
- package/skills/_active-intent-gate.md +0 -26
- package/skills/auto/references/tiers.md +0 -100
- package/skills/continuing/SKILL.md +0 -34
- package/skills/continuing/evals/evals.json +0 -91
- package/skills/conventions/references/gates-and-enforcement.md +0 -53
- package/skills/conventions/references/tiers-and-dispatch.md +0 -135
- package/skills/doctor/references/gates-stuck-detection.md +0 -43
- package/skills/intent-brainstorming/SKILL.md +0 -118
- package/skills/intent-brainstorming/evals/evals.json +0 -67
- package/skills/intent-continuing/evals/evals.json +0 -145
- package/skills/intent-discovering/SKILL.md +0 -56
- package/skills/intent-grilling/SKILL.md +0 -108
- package/skills/intent-linking/SKILL.md +0 -128
- package/skills/intent-linking/evals/evals.json +0 -22
- package/skills/intent-linking/references/zettelkasten.md +0 -45
- package/skills/intent-locking/SKILL.md +0 -64
- package/skills/intent-planning/SKILL.md +0 -145
- package/skills/intent-planning/evals/evals.json +0 -81
- package/skills/intent-planning/references/plan-format.md +0 -130
- package/skills/intent-researching/SKILL.md +0 -123
- package/skills/intent-researching/evals/evals.json +0 -22
- package/skills/intent-savepoint/SKILL.md +0 -83
- package/skills/intent-savepoint/references/context-management.md +0 -32
- package/skills/intent-starting/SKILL.md +0 -151
- package/skills/intent-starting/evals/evals.json +0 -117
- package/skills/intent-starting/references/boarding-matrix.md +0 -35
- package/skills/project-continuing/SKILL.md +0 -119
- package/skills/project-continuing/evals/evals.json +0 -100
- package/skills/roadmap-continuing/SKILL.md +0 -89
- package/skills/roadmap-continuing/evals/evals.json +0 -82
- package/skills/skill-creating/SKILL.md +0 -75
- package/skills/skill-creating/evals/evals.json +0 -108
- package/skills/skill-creating/references/agents.md +0 -168
- package/skills/skill-creating/references/defaults-first.md +0 -23
- package/skills/skill-creating/references/evals.md +0 -41
- package/skills/skill-creating/references/hooks.md +0 -251
- package/skills/skill-creating/references/progressive-disclosure.md +0 -176
- package/skills/skill-creating/references/scripts.md +0 -166
- package/skills/skill-creating/references/skills.md +0 -169
- package/skills/skill-creating/scripts/scaffold.rb +0 -313
- package/skills/skill-evaluating/SKILL.md +0 -141
- package/skills/skill-evaluating/assets/eval-template.json +0 -12
- package/skills/skill-evaluating/evals/evals.json +0 -75
- package/skills/skill-evaluating/references/convention-checks.md +0 -76
- package/skills/skill-evaluating/references/eval-methodology.md +0 -154
- package/skills/store-curating/SKILL.md +0 -64
- package/skills/store-curating/evals/evals.json +0 -38
- package/skills/store-indexing/SKILL.md +0 -93
- package/skills/store-indexing/evals/evals.json +0 -22
- package/skills/store-indexing/references/zettelkasten-linking.md +0 -32
- package/skills/store-provisioning/SKILL.md +0 -55
- /package/skills/{project-continuing → intent-continuing}/references/board-fill.md +0 -0
- /package/skills/{roadmap-continuing → intent-continuing}/references/liveness-ranking.md +0 -0
- /package/skills/{intent-brainstorming → intent-speccing}/references/design-principles.md +0 -0
|
@@ -10,9 +10,8 @@
|
|
|
10
10
|
module HookRegistry
|
|
11
11
|
module_function
|
|
12
12
|
|
|
13
|
-
# MCP tools that MUTATE files.
|
|
14
|
-
#
|
|
15
|
-
# bypass found in 108's gate inventory).
|
|
13
|
+
# MCP tools that MUTATE files. The record hook must match them, or a symbolic
|
|
14
|
+
# edit never reaches the savepoint ledger or the day ledger.
|
|
16
15
|
SERENA_EDIT_TOOLS = %w[
|
|
17
16
|
mcp__serena__replace_content
|
|
18
17
|
mcp__serena__replace_symbol_body
|
|
@@ -24,40 +23,6 @@ module HookRegistry
|
|
|
24
23
|
|
|
25
24
|
WRITE_MATCHER = (%w[Write Edit NotebookEdit] + SERENA_EDIT_TOOLS).join("|")
|
|
26
25
|
|
|
27
|
-
# Per-gate tool applicability for the merged edit-path dispatcher (intent 244,
|
|
28
|
-
# spec D-d/D-l). Registration collapses to ONE PreToolUse hook on WRITE_MATCHER
|
|
29
|
-
# (a strict superset of the three former matcher groups), and this table is what
|
|
30
|
-
# keeps each gate's own coverage exactly what it was: scripts/hook-edit-gates
|
|
31
|
-
# reads tool_name off the payload and skips any gate whose list excludes it.
|
|
32
|
-
# Key order IS the evaluation order (spec D-a): savepoint-pre first because it
|
|
33
|
-
# never denies and its ledger append must stay unconditional; lock-gate leads
|
|
34
|
-
# the deniers because holding the delivery lock is the precondition the other
|
|
35
|
-
# rules assume. A test pins this table against today's three matcher groups, so
|
|
36
|
-
# no gate's coverage can widen or narrow silently.
|
|
37
|
-
GATE_TOOLS = {
|
|
38
|
-
"savepoint-pre" => %w[Write Edit].freeze,
|
|
39
|
-
"lock-gate" => (%w[Write Edit NotebookEdit] + SERENA_EDIT_TOOLS).freeze,
|
|
40
|
-
"code-gate" => (%w[Write Edit NotebookEdit] + SERENA_EDIT_TOOLS).freeze,
|
|
41
|
-
"links-gate" => %w[Write Edit].freeze,
|
|
42
|
-
"create-gate" => (%w[Write Edit] + SERENA_EDIT_TOOLS).freeze,
|
|
43
|
-
}.freeze
|
|
44
|
-
|
|
45
|
-
# Per-gate tool applicability for the merged CODEX edit-path dispatcher (intent
|
|
46
|
-
# 251, spec D1). Codex reports exactly one file-mutation tool name, apply_patch
|
|
47
|
-
# (intent 181 F4), so every value is the same single-entry list. The table is
|
|
48
|
-
# NOT decoration: EditGates.tool_applies? builds a regex from these values, and
|
|
49
|
-
# reusing GATE_TOOLS here would match none of them against "apply_patch" and
|
|
50
|
-
# silently skip all five gates. Key order IS the evaluation order and is the
|
|
51
|
-
# SAME order GATE_TOOLS uses (spec D5), so the two harnesses evaluate the five
|
|
52
|
-
# gates in one order, not two.
|
|
53
|
-
CODEX_GATE_TOOLS = {
|
|
54
|
-
"savepoint-pre" => %w[apply_patch].freeze,
|
|
55
|
-
"lock-gate" => %w[apply_patch].freeze,
|
|
56
|
-
"code-gate" => %w[apply_patch].freeze,
|
|
57
|
-
"links-gate" => %w[apply_patch].freeze,
|
|
58
|
-
"create-gate" => %w[apply_patch].freeze,
|
|
59
|
-
}.freeze
|
|
60
|
-
|
|
61
26
|
# event => ordered list of { "matcher" =>, "hooks" => [{ "name" =>, "status" => }] }
|
|
62
27
|
# The name is the hooks/<name> launcher; the flat install ships it as
|
|
63
28
|
# ~/.claude/hooks/plastic-<name>.
|
|
@@ -74,75 +39,54 @@ module HookRegistry
|
|
|
74
39
|
{ "name" => "savepoint", "status" => "Saving Plastic intent state..." },
|
|
75
40
|
] },
|
|
76
41
|
],
|
|
77
|
-
"
|
|
78
|
-
# ONE registered edit-path hook (intent 244, spec D-d). WRITE_MATCHER is a
|
|
79
|
-
# strict superset of the three matcher groups this replaces; per-gate tool
|
|
80
|
-
# applicability lives in GATE_TOOLS above and is read by
|
|
81
|
-
# scripts/hook-edit-gates, so no gate's coverage widened or narrowed.
|
|
42
|
+
"PostToolUse" => [
|
|
82
43
|
{ "matcher" => WRITE_MATCHER, "hooks" => [
|
|
83
|
-
{ "name" => "
|
|
84
|
-
] },
|
|
85
|
-
{ "matcher" => "Bash", "hooks" => [
|
|
86
|
-
{ "name" => "bash-gate", "status" => "Checking lifecycle gate..." },
|
|
44
|
+
{ "name" => "record", "status" => "Recording Plastic session state..." },
|
|
87
45
|
] },
|
|
88
46
|
],
|
|
89
|
-
"
|
|
90
|
-
{ "matcher" => "
|
|
91
|
-
{ "name" => "
|
|
47
|
+
"SessionEnd" => [
|
|
48
|
+
{ "matcher" => "", "hooks" => [
|
|
49
|
+
{ "name" => "close", "status" => "Closing the Plastic session..." },
|
|
92
50
|
] },
|
|
93
51
|
],
|
|
94
52
|
"UserPromptSubmit" => [
|
|
95
53
|
{ "matcher" => "", "hooks" => [
|
|
96
|
-
{ "name" => "
|
|
97
|
-
{ "name" => "future-intent-check", "status" => "Checking future intents..." },
|
|
98
|
-
{ "name" => "auto-arm", "status" => "Checking auto mode..." },
|
|
99
|
-
{ "name" => "power-tools", "status" => "Checking power tools..." },
|
|
54
|
+
{ "name" => "capture", "status" => "Capturing prompt into the session ledger..." },
|
|
100
55
|
] },
|
|
101
56
|
],
|
|
102
57
|
}
|
|
103
58
|
end
|
|
104
59
|
|
|
105
60
|
# Codex registration (~/.codex/hooks.json, intent 102). Derived from `events`:
|
|
106
|
-
# the
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
# [
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# Codex's shell-tool gate hole (intent 203): bash-gate (denies a shell write to
|
|
125
|
-
# project code before How) belongs on the Bash matcher, and ONLY Bash: the
|
|
126
|
-
# official Codex hooks doc's PreToolUse event catalog enumerates exactly Bash,
|
|
127
|
-
# apply_patch, and MCP tool calls, and neither it nor the two prior Codex
|
|
128
|
-
# research passes (198's official-docs research, 181's deep research)
|
|
129
|
-
# documents a discrete Read, Grep, or Glob tool name (D3). Registering a tool
|
|
130
|
-
# name Codex never reports would be dead weight that looks alive, the exact
|
|
131
|
-
# defect this intent exists to fix.
|
|
132
|
-
CODEX_BASH_HOOKS = %w[bash-gate].freeze
|
|
133
|
-
|
|
134
|
-
# Live-state events registered WHOLE (intent 199), unlike CODEX_PRE_HOOKS/
|
|
135
|
-
# CODEX_POST_HOOKS above: Codex's SessionStart/UserPromptSubmit/PreCompact already
|
|
136
|
-
# match Claude's shape exactly, one matcher group each ("", no tool to collapse
|
|
137
|
-
# onto), so every hook `events` lists under these three events projects straight
|
|
138
|
-
# through with no allowlist to keep in sync. A hook added to any of them on the
|
|
139
|
-
# Claude side registers for Codex automatically.
|
|
61
|
+
# the PostToolUse record hook collapses from Claude's multi-tool matcher onto
|
|
62
|
+
# Codex's single apply_patch tool (181 F4: apply_patch is Codex's sole
|
|
63
|
+
# file-mutation tool; tool_name always reports apply_patch), and the live-state
|
|
64
|
+
# events project through whole. Since intent 302 there is no PreToolUse group at
|
|
65
|
+
# all: the edit-path gates are gone on both harnesses. Command invokes the
|
|
66
|
+
# codex-hook dispatcher with the hook name. Guide-settled shape [guide Part 3]:
|
|
67
|
+
# top-level {"hooks":{<Event>: [{"matcher","hooks":[{"type":"command","command",
|
|
68
|
+
# "statusMessage"}]}]}}, identical to Claude's shape, string command. Single
|
|
69
|
+
# source of truth (108 D7): any drift from `events` is a bug, pinned by test.
|
|
70
|
+
CODEX_POST_HOOKS = %w[record].freeze
|
|
71
|
+
|
|
72
|
+
# Live-state events registered WHOLE (intent 199): Codex's SessionStart/
|
|
73
|
+
# UserPromptSubmit/PreCompact already match Claude's shape exactly, one matcher
|
|
74
|
+
# group each ("", no tool to collapse onto), so every hook `events` lists under
|
|
75
|
+
# these three events projects straight through with no allowlist to keep in
|
|
76
|
+
# sync. A hook added to any of them on the Claude side registers for Codex
|
|
77
|
+
# automatically.
|
|
140
78
|
CODEX_LIVE_STATE_EVENTS = %w[SessionStart UserPromptSubmit PreCompact].freeze
|
|
141
79
|
|
|
80
|
+
# SessionEnd on Codex (intent 309): projected from its own constant, never as a fourth
|
|
81
|
+
# live-state event, because the dispatch differs. Codex kills a SessionEnd hook after
|
|
82
|
+
# 3 seconds, so scripts/codex-hook hands `close` to its launcher detached and returns at
|
|
83
|
+
# once instead of the synchronous relay the live-state hooks get. Codex ships both Stop
|
|
84
|
+
# and SessionEnd (codex-rs/hooks at rust-v0.149.1); the design uses SessionEnd on both
|
|
85
|
+
# harnesses. Like STATE_HOOKS, the dispatcher's list is hand-kept and cross-checked by
|
|
86
|
+
# test/hook_registry_test.rb.
|
|
87
|
+
CODEX_SESSION_END_HOOKS = %w[close].freeze
|
|
88
|
+
|
|
142
89
|
def codex_hooks_json(dispatcher_path:)
|
|
143
|
-
# Every Codex hook name is now a name `events` itself carries (intent 251,
|
|
144
|
-
# spec D9): edit-gates is Claude's own PreToolUse hook name, so there is
|
|
145
|
-
# nothing left to merge in from a separate status table.
|
|
146
90
|
status_by_name = events.values.flatten.flat_map { |g| g["hooks"] }
|
|
147
91
|
.each_with_object({}) { |h, m| m[h["name"]] = h["status"] }
|
|
148
92
|
cmd = ->(name) {
|
|
@@ -150,27 +94,19 @@ module HookRegistry
|
|
|
150
94
|
"command" => "\"#{dispatcher_path}\" #{name}",
|
|
151
95
|
"statusMessage" => status_by_name[name].to_s }
|
|
152
96
|
}
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
# PreToolUse group carries (intent 251, spec D9), so the intersection with
|
|
157
|
-
# pre_order validates the Codex gate name against the single source of truth,
|
|
158
|
-
# `events`, rather than against a table of Claude tool applicability.
|
|
159
|
-
pre = (CODEX_PRE_HOOKS & pre_order).map { |n| cmd.call(n) }
|
|
160
|
-
bash = (pre_order & CODEX_BASH_HOOKS).map { |n| cmd.call(n) }
|
|
161
|
-
post = CODEX_POST_HOOKS.map { |n| cmd.call(n) }
|
|
97
|
+
# Validate the Codex name against the single source of truth, `events`.
|
|
98
|
+
post_order = events["PostToolUse"].flat_map { |g| g["hooks"].map { |h| h["name"] } }
|
|
99
|
+
post = (CODEX_POST_HOOKS & post_order).map { |n| cmd.call(n) }
|
|
162
100
|
|
|
163
101
|
result = {
|
|
164
|
-
"PreToolUse" => [
|
|
165
|
-
{ "matcher" => "apply_patch", "hooks" => pre },
|
|
166
|
-
{ "matcher" => "Bash", "hooks" => bash },
|
|
167
|
-
],
|
|
168
102
|
"PostToolUse" => [{ "matcher" => "apply_patch", "hooks" => post }],
|
|
169
103
|
}
|
|
170
104
|
CODEX_LIVE_STATE_EVENTS.each do |event|
|
|
171
105
|
names = events[event].flat_map { |g| g["hooks"].map { |h| h["name"] } }
|
|
172
106
|
result[event] = [{ "matcher" => "", "hooks" => names.map { |n| cmd.call(n) } }]
|
|
173
107
|
end
|
|
108
|
+
end_order = events["SessionEnd"].flat_map { |g| g["hooks"].map { |h| h["name"] } }
|
|
109
|
+
result["SessionEnd"] = [{ "matcher" => "", "hooks" => (CODEX_SESSION_END_HOOKS & end_order).map { |n| cmd.call(n) } }]
|
|
174
110
|
result
|
|
175
111
|
end
|
|
176
112
|
|
|
@@ -179,8 +115,7 @@ module HookRegistry
|
|
|
179
115
|
# installed as ~/.claude/hooks/plastic-<name>. The single derivation doctor's
|
|
180
116
|
# hooks_exist/hooks_executable/hooks_no_orphans checks read from, so a
|
|
181
117
|
# hand-kept list of launchers can never drift out of step with `events`
|
|
182
|
-
# again (the gap that let 8 of 15 launchers
|
|
183
|
-
# unchecked).
|
|
118
|
+
# again (the gap that once let 8 of 15 launchers go unchecked).
|
|
184
119
|
def claude_launcher_names
|
|
185
120
|
events.values.flatten.flat_map { |g| g["hooks"].map { |h| h["name"] } }
|
|
186
121
|
.uniq.sort.map { |name| "plastic-#{name}" }
|
|
@@ -204,8 +139,10 @@ module HookRegistry
|
|
|
204
139
|
# hooks_exist demands be present on disk, so a retired name there makes a correct
|
|
205
140
|
# install report missing launchers.
|
|
206
141
|
RETIRED_HOOK_NAMES = %w[
|
|
207
|
-
code-gate create-gate links-gate lock-gate savepoint-pre
|
|
142
|
+
edit-gates bash-gate code-gate create-gate links-gate lock-gate savepoint-pre
|
|
208
143
|
qmd-search retrieval-gate model-instructions opus-manual
|
|
144
|
+
continue future-intent-check auto-arm gate-check
|
|
145
|
+
power-tools
|
|
209
146
|
].freeze
|
|
210
147
|
|
|
211
148
|
RETIRED_CLAUDE_LAUNCHERS = RETIRED_HOOK_NAMES.map { |n| "plastic-#{n}" }.freeze
|
|
@@ -226,7 +163,7 @@ module HookRegistry
|
|
|
226
163
|
live = CODEX_LIVE_STATE_EVENTS.flat_map do |event|
|
|
227
164
|
events[event].flat_map { |g| g["hooks"].map { |h| h["name"] } }
|
|
228
165
|
end
|
|
229
|
-
(
|
|
166
|
+
(CODEX_POST_HOOKS + live + CODEX_SESSION_END_HOOKS).uniq.sort
|
|
230
167
|
end
|
|
231
168
|
|
|
232
169
|
def codex_purgeable_hook_names
|
package/scripts/lib/insights.rb
CHANGED
|
@@ -7,14 +7,13 @@
|
|
|
7
7
|
#
|
|
8
8
|
# Every entry leads with a fixed, machine-parseable prefix
|
|
9
9
|
# `{utc-iso8601} · {stage} · {author}` where the timestamp is `now.utc.iso8601`,
|
|
10
|
-
# the SAME convention `
|
|
10
|
+
# the SAME convention `Savepoint.append_savepoint_line` uses, so the store has one
|
|
11
11
|
# timestamp convention across both ledgers. Entries are append-only, newest at
|
|
12
12
|
# the BOTTOM (the [[34]] ordering law): the per-entry prefix is not prepending
|
|
13
13
|
# the entry, and existing entries are never reordered.
|
|
14
14
|
|
|
15
15
|
require "time"
|
|
16
|
-
require_relative "
|
|
17
|
-
|
|
16
|
+
require_relative "savepoint"
|
|
18
17
|
module Insights
|
|
19
18
|
HEADING = "## Insights"
|
|
20
19
|
# The middle dot (U+00B7) with a single space on each side separates the
|
|
@@ -35,7 +34,7 @@ module Insights
|
|
|
35
34
|
end
|
|
36
35
|
|
|
37
36
|
entry = "#{prefix} — #{text}"
|
|
38
|
-
path =
|
|
37
|
+
path = Savepoint.intent_file(intent_dir)
|
|
39
38
|
File.write(path, with_entry(File.exist?(path) ? File.read(path) : "", entry))
|
|
40
39
|
entry
|
|
41
40
|
end
|
|
@@ -174,13 +174,6 @@ class InstallerCore
|
|
|
174
174
|
keys.uniq
|
|
175
175
|
end
|
|
176
176
|
|
|
177
|
-
def channel_from(argv, default: "latest")
|
|
178
|
-
return "alpha" if argv.include?("--alpha")
|
|
179
|
-
return "beta" if argv.include?("--beta")
|
|
180
|
-
return "latest" if argv.include?("--latest")
|
|
181
|
-
default
|
|
182
|
-
end
|
|
183
|
-
|
|
184
177
|
def prompt_agents(input: $stdin)
|
|
185
178
|
return ["claude"] unless input.tty?
|
|
186
179
|
|
|
@@ -329,22 +322,17 @@ class InstallerCore
|
|
|
329
322
|
"scripts/write-config" => "scripts/write-config",
|
|
330
323
|
"scripts/select-update-target" => "scripts/select-update-target",
|
|
331
324
|
"scripts/hook-session-start" => "scripts/hook-session-start",
|
|
332
|
-
"scripts/hook-
|
|
333
|
-
"scripts/hook-
|
|
334
|
-
"scripts/hook-
|
|
335
|
-
"scripts/hook-savepoint-pre" => "scripts/hook-savepoint-pre",
|
|
336
|
-
"scripts/hook-power-tools" => "scripts/hook-power-tools",
|
|
337
|
-
"scripts/lib/qmd_hook.rb" => "scripts/lib/qmd_hook.rb",
|
|
325
|
+
"scripts/hook-capture" => "scripts/hook-capture",
|
|
326
|
+
"scripts/hook-record" => "scripts/hook-record",
|
|
327
|
+
"scripts/hook-close" => "scripts/hook-close",
|
|
338
328
|
"scripts/lib/power_tools.rb" => "scripts/lib/power_tools.rb",
|
|
339
329
|
"scripts/lib/ruby_probe.rb" => "scripts/lib/ruby_probe.rb",
|
|
340
330
|
"scripts/lib/agent_models.rb" => "scripts/lib/agent_models.rb",
|
|
341
331
|
"scripts/lib/config_asks.rb" => "scripts/lib/config_asks.rb",
|
|
342
332
|
"scripts/lib/release_guard.rb" => "scripts/lib/release_guard.rb",
|
|
343
|
-
"scripts/hook-code-gate" => "scripts/hook-code-gate",
|
|
344
|
-
"scripts/hook-lock-gate" => "scripts/hook-lock-gate",
|
|
345
|
-
"scripts/hook-bash-gate" => "scripts/hook-bash-gate",
|
|
346
|
-
"scripts/hook-auto-arm" => "scripts/hook-auto-arm",
|
|
347
333
|
"scripts/lib/bridge.rb" => "scripts/lib/bridge.rb",
|
|
334
|
+
"scripts/lib/savepoint.rb" => "scripts/lib/savepoint.rb",
|
|
335
|
+
"scripts/lib/arm.rb" => "scripts/lib/arm.rb",
|
|
348
336
|
"scripts/lib/lock.rb" => "scripts/lib/lock.rb",
|
|
349
337
|
"scripts/plastic-lock" => "scripts/plastic-lock",
|
|
350
338
|
"scripts/lib/hook_registry.rb" => "scripts/lib/hook_registry.rb",
|
|
@@ -380,13 +368,7 @@ class InstallerCore
|
|
|
380
368
|
"scripts/validate-intent" => "scripts/validate-intent",
|
|
381
369
|
"scripts/new-intent" => "scripts/new-intent",
|
|
382
370
|
"scripts/end-intent" => "scripts/end-intent",
|
|
383
|
-
"scripts/hook-create-gate" => "scripts/hook-create-gate",
|
|
384
|
-
"scripts/hook-links-gate" => "scripts/hook-links-gate",
|
|
385
|
-
"scripts/lib/links_gate.rb" => "scripts/lib/links_gate.rb",
|
|
386
|
-
"scripts/lib/edit_gates.rb" => "scripts/lib/edit_gates.rb",
|
|
387
|
-
"scripts/hook-edit-gates" => "scripts/hook-edit-gates",
|
|
388
371
|
"scripts/lib/apply_patch_envelope.rb" => "scripts/lib/apply_patch_envelope.rb",
|
|
389
|
-
"scripts/lib/codex_edit_gates.rb" => "scripts/lib/codex_edit_gates.rb",
|
|
390
372
|
"scripts/lib/harness_text.rb" => "scripts/lib/harness_text.rb",
|
|
391
373
|
"scripts/codex-hook" => "scripts/codex-hook",
|
|
392
374
|
"scripts/spawn-preamble" => "scripts/spawn-preamble",
|
|
@@ -401,24 +383,31 @@ class InstallerCore
|
|
|
401
383
|
"scripts/uninstall.rb" => "scripts/uninstall.rb",
|
|
402
384
|
"scripts/rollback.rb" => "scripts/rollback.rb",
|
|
403
385
|
"scripts/lib/outcome_guard.rb" => "scripts/lib/outcome_guard.rb",
|
|
404
|
-
"scripts/lib/spec_header.rb" => "scripts/lib/spec_header.rb",
|
|
405
386
|
"scripts/lib/scaffold_intent.rb" => "scripts/lib/scaffold_intent.rb",
|
|
387
|
+
"scripts/lib/backfill_intent.rb" => "scripts/lib/backfill_intent.rb",
|
|
406
388
|
"scripts/scaffold-intent" => "scripts/scaffold-intent",
|
|
407
389
|
"scripts/lib/verify_intent.rb" => "scripts/lib/verify_intent.rb",
|
|
408
390
|
"scripts/verify-intent" => "scripts/verify-intent",
|
|
409
|
-
"scripts/lib/start_intent.rb" => "scripts/lib/start_intent.rb",
|
|
410
|
-
"scripts/start-intent" => "scripts/start-intent",
|
|
411
391
|
"scripts/lib/exec_worktree.rb" => "scripts/lib/exec_worktree.rb",
|
|
412
392
|
"scripts/exec-worktree" => "scripts/exec-worktree",
|
|
413
393
|
"scripts/doctor.rb" => "scripts/doctor.rb",
|
|
414
394
|
"scripts/lib/doctor_core.rb" => "scripts/lib/doctor_core.rb",
|
|
415
395
|
"scripts/lib/rule_catalog.rb" => "scripts/lib/rule_catalog.rb",
|
|
416
396
|
"scripts/lib/doctor_exclusions.rb" => "scripts/lib/doctor_exclusions.rb",
|
|
397
|
+
"scripts/lib/doctor_session_ledger.rb" => "scripts/lib/doctor_session_ledger.rb",
|
|
417
398
|
"scripts/dashboard.rb" => "scripts/dashboard.rb",
|
|
418
399
|
"scripts/skill-lint" => "scripts/skill-lint",
|
|
419
400
|
"scripts/lib/skill_lint.rb" => "scripts/lib/skill_lint.rb",
|
|
420
401
|
"scripts/feedback-report" => "scripts/feedback-report",
|
|
421
402
|
"scripts/lib/feedback_report.rb" => "scripts/lib/feedback_report.rb",
|
|
403
|
+
"scripts/append-ledger" => "scripts/append-ledger",
|
|
404
|
+
"scripts/lib/session_ledger.rb" => "scripts/lib/session_ledger.rb",
|
|
405
|
+
"scripts/session-commit" => "scripts/session-commit",
|
|
406
|
+
"scripts/file-session-intent" => "scripts/file-session-intent",
|
|
407
|
+
"scripts/promote-session-item" => "scripts/promote-session-item",
|
|
408
|
+
"scripts/lib/session_backfill.rb" => "scripts/lib/session_backfill.rb",
|
|
409
|
+
"scripts/lib/session_close.rb" => "scripts/lib/session_close.rb",
|
|
410
|
+
"scripts/lib/session_git.rb" => "scripts/lib/session_git.rb",
|
|
422
411
|
}
|
|
423
412
|
end
|
|
424
413
|
|
|
@@ -718,7 +707,7 @@ class InstallerCore
|
|
|
718
707
|
end
|
|
719
708
|
|
|
720
709
|
# `install_skills_flat` relocates any top-level underscore-prefixed markdown
|
|
721
|
-
# fragment (today: `
|
|
710
|
+
# fragment (today: `_decision-tables.md`) out of the
|
|
722
711
|
# per-agent skills tree and into `plastic_home` directly, then lists them in
|
|
723
712
|
# `installed`, so they are manifest-tracked and eligible for prune. But
|
|
724
713
|
# `install_for_agent`'s prune roots are `[config[:dir], config[:home_dir]]`,
|
|
@@ -1012,7 +1001,7 @@ class InstallerCore
|
|
|
1012
1001
|
|
|
1013
1002
|
# Copy each skills/<name>/ to <skills_root>/plastic-<name>/ (flat, namespaced by
|
|
1014
1003
|
# directory name -- the only personal-skill namespacing Claude Code supports).
|
|
1015
|
-
# Any top-level underscore-prefixed markdown fragment (e.g. `
|
|
1004
|
+
# Any top-level underscore-prefixed markdown fragment (e.g. `_decision-tables.md`,
|
|
1016
1005
|
# `_decision-tables.md`) is a shared non-skill fragment and relocates to ~/.plastic/
|
|
1017
1006
|
# instead, so every skill can read it from one shared location. `exclude` skips
|
|
1018
1007
|
# named top-level skill directories entirely (intent 185: the agent-advisor skill
|
|
@@ -1273,8 +1262,8 @@ class InstallerCore
|
|
|
1273
1262
|
plastic_hooks.each do |event, group|
|
|
1274
1263
|
hooks[event] ||= []
|
|
1275
1264
|
|
|
1276
|
-
# An event may map to a LIST of plastic groups (
|
|
1277
|
-
#
|
|
1265
|
+
# An event may map to a LIST of plastic groups (none does since the edit-path
|
|
1266
|
+
# gates left in 2.0, intent 302; the shape stays). The purge pass above already removed all
|
|
1278
1267
|
# prior plastic groups, so appending each desired group fresh is idempotent
|
|
1279
1268
|
# across re-runs and never collapses two matchers into one group.
|
|
1280
1269
|
groups = group.is_a?(Array) ? group : [group]
|
package/scripts/lib/lock.rb
CHANGED
|
@@ -49,11 +49,11 @@ module Lock
|
|
|
49
49
|
DELEGATE_ACTIVITY_LIMIT = 20
|
|
50
50
|
DELEGATE_STATUSES = %w[active finished failed].freeze
|
|
51
51
|
|
|
52
|
-
# Lease TTL. Heartbeats fire from the
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
52
|
+
# Lease TTL. Heartbeats fire from the PostToolUse record hook on every write
|
|
53
|
+
# inside the intent dir (intent 302), so a delivering session refreshes
|
|
54
|
+
# constantly; 30 minutes tolerates long read-only stretches without opening
|
|
55
|
+
# a takeover window mid-delivery. Reclaim is explicit either way (takeover),
|
|
56
|
+
# so the TTL only bounds WHEN takeover is allowed.
|
|
57
57
|
TTL_SECONDS = 1800
|
|
58
58
|
|
|
59
59
|
# The write guard is a mutex, not a lock in the Plastic sense: it carries no
|
|
@@ -122,7 +122,7 @@ module Lock
|
|
|
122
122
|
Array(data["delegates"]).map(&:to_s).include?(session.to_s)
|
|
123
123
|
end
|
|
124
124
|
|
|
125
|
-
# The one question
|
|
125
|
+
# The one question the write path asks: does session hold this intent's lock?
|
|
126
126
|
# Owner/delegate on an EXISTING lock counts even when stale (a stale lock is
|
|
127
127
|
# still theirs until an explicit takeover replaces it); freshness only
|
|
128
128
|
# guards AGAINST other sessions.
|
|
@@ -676,24 +676,4 @@ module Claim
|
|
|
676
676
|
end
|
|
677
677
|
end
|
|
678
678
|
|
|
679
|
-
# Second, independent write gate at the artifact grain (intent 111 D7). Returns a
|
|
680
|
-
# deny reason String to BLOCK, or nil to ALLOW. Composes UNDER the delivery-lock
|
|
681
|
-
# gate: only reached after the session already holds the intent's delivery lock.
|
|
682
|
-
# ENGAGES only when a claim file exists (dormant otherwise, so single-owner flows
|
|
683
|
-
# and the existing suite stay green, AC7). Fails open on stale/corrupt via
|
|
684
|
-
# fail_open?, the named contract.
|
|
685
|
-
def claim_gate_reason(intent_dir, artifact, session:, ttl: Lock::TTL_SECONDS, now: Time.now,
|
|
686
|
-
harness: :claude)
|
|
687
|
-
return nil if Lock.blank?(artifact)
|
|
688
|
-
return nil unless File.exist?(path(intent_dir, artifact)) # dormant: no claim
|
|
689
|
-
return nil if holds_claim?(intent_dir, artifact, session: session) # you hold it
|
|
690
|
-
return nil if fail_open?(intent_dir, artifact, ttl: ttl, now: now) # stale/corrupt: yield
|
|
691
|
-
data = read(intent_dir, artifact)
|
|
692
|
-
holder = data && data["owner_session"]
|
|
693
|
-
since = data && data["acquired_at"]
|
|
694
|
-
"artifact #{artifact} is claimed by #{holder} since #{since}; another writer holds " \
|
|
695
|
-
"it. Back off or run #{Lock.skill_ref('plastic-doctor', harness: harness)} check the " \
|
|
696
|
-
"lock status. If you are a distinct delegate, the owner must register you: " \
|
|
697
|
-
"plastic-lock delegate --intent-dir #{intent_dir} --session <your-session-id>"
|
|
698
|
-
end
|
|
699
679
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# encoding: UTF-8
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
require_relative "
|
|
4
|
+
require_relative "savepoint"
|
|
5
5
|
require_relative "intent_validator"
|
|
6
6
|
|
|
7
7
|
# OutcomeGuard - the single definition of "is outcome.md real and disposition-matched for
|
|
8
8
|
# THIS close" (intent 222). Extracted verbatim from scripts/end-intent's own
|
|
9
|
-
# outcome_guard_reason (intent 161/188) so
|
|
10
|
-
#
|
|
11
|
-
# as a valid outcome.md.
|
|
9
|
+
# outcome_guard_reason (intent 161/188) so its two callers, doctor's per-intent check
|
|
10
|
+
# (intent_lifecycle_artifacts) and end-intent's post-backfill self-check, can never drift on
|
|
11
|
+
# what counts as a valid outcome.md. Since intent 308 nothing blocks on it: end-intent
|
|
12
|
+
# backfills a missing or placeholder outcome.md first, prints a non-nil reason, and proceeds.
|
|
13
|
+
# Pure, read-only: authors nothing regardless of outcome.
|
|
12
14
|
module OutcomeGuard
|
|
13
15
|
module_function
|
|
14
16
|
|
|
@@ -23,7 +25,7 @@ module OutcomeGuard
|
|
|
23
25
|
|
|
24
26
|
content = File.read(path)
|
|
25
27
|
first_line = content.each_line.first.to_s.chomp
|
|
26
|
-
if first_line ==
|
|
28
|
+
if first_line == Savepoint::PLACEHOLDER_SENTINEL
|
|
27
29
|
return "outcome.md is still the scaffold placeholder (first line is the sentinel)"
|
|
28
30
|
end
|
|
29
31
|
|
|
@@ -24,6 +24,15 @@ require "yaml"
|
|
|
24
24
|
module ProjectValidator
|
|
25
25
|
module_function
|
|
26
26
|
|
|
27
|
+
# The two enumerated flow: knobs (intent 300, spec D2). Kept as a plain
|
|
28
|
+
# literal here rather than a require on SessionGit: this validator only
|
|
29
|
+
# reports a bad value in `errors` (project.yml is still "accepted", spec
|
|
30
|
+
# D2's matrix row), it never blocks spawn completeness, since a bad value
|
|
31
|
+
# degrades gracefully at commit time (SessionGit falls back with a Note)
|
|
32
|
+
# rather than blocking the spawn.
|
|
33
|
+
FLOW_MODES = %w[direct pull_request].freeze
|
|
34
|
+
FLOW_WORKSPACES = %w[checkout worktree].freeze
|
|
35
|
+
|
|
27
36
|
def validate(slug, plastic_home: File.join(Dir.home, ".plastic"))
|
|
28
37
|
missing = []
|
|
29
38
|
errors = []
|
|
@@ -53,7 +62,9 @@ module ProjectValidator
|
|
|
53
62
|
rescue StandardError
|
|
54
63
|
nil
|
|
55
64
|
end
|
|
56
|
-
|
|
65
|
+
if parsed.is_a?(Hash)
|
|
66
|
+
validate_flow_block(parsed, errors)
|
|
67
|
+
else
|
|
57
68
|
missing << "project.yml (valid YAML)"
|
|
58
69
|
errors << "project.yml exists at #{project_yml_path} but does not parse as YAML"
|
|
59
70
|
end
|
|
@@ -88,6 +99,24 @@ module ProjectValidator
|
|
|
88
99
|
{ ok: missing.empty?, missing: missing, errors: errors }
|
|
89
100
|
end
|
|
90
101
|
|
|
102
|
+
# Non-blocking: an unknown `mode` or `workspace` value in project.yml's
|
|
103
|
+
# `flow:` block, if present, is reported in `errors` but never added to
|
|
104
|
+
# `missing`, so it never flips `ok`.
|
|
105
|
+
def validate_flow_block(parsed, errors)
|
|
106
|
+
flow = parsed["flow"]
|
|
107
|
+
return unless flow.is_a?(Hash)
|
|
108
|
+
|
|
109
|
+
validate_flow_knob(flow, "mode", FLOW_MODES, errors)
|
|
110
|
+
validate_flow_knob(flow, "workspace", FLOW_WORKSPACES, errors)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def validate_flow_knob(flow, key, allowed, errors)
|
|
114
|
+
value = flow[key]
|
|
115
|
+
return if value.nil? || allowed.include?(value.to_s)
|
|
116
|
+
|
|
117
|
+
errors << "project.yml flow.#{key} is #{value.inspect}, must be one of #{allowed.join(", ")}"
|
|
118
|
+
end
|
|
119
|
+
|
|
91
120
|
# Invariant 1: registered in projects.yml with a 'path'. Returns the
|
|
92
121
|
# project's entry Hash, or nil when unregistered or the entry has no path.
|
|
93
122
|
def registration_for(slug, plastic_home)
|
|
@@ -20,10 +20,10 @@ class FileOrderRanker
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# RoadmapQueue - the one deterministic reader the auto loop and plastic-
|
|
23
|
+
# RoadmapQueue - the one deterministic reader the auto loop and plastic-intent-continuing
|
|
24
24
|
# both call (intent 148). Constructor-DI, hermetic: clock and paths injected, no eval, no ENV
|
|
25
25
|
# or global config seam. It does two things: liveness-ranks a tier's roadmaps/*.md files
|
|
26
|
-
# (porting plastic-
|
|
26
|
+
# (porting plastic-intent-continuing's read-time algorithm), and, within the winning
|
|
27
27
|
# roadmap, selects the frontier wave plus its dispatchable set (D-b), value-ordered by the
|
|
28
28
|
# injected ranker (default FileOrderRanker, the intent-173 swap seam). Every frontier token is
|
|
29
29
|
# reconciled against INDEX.md first, INDEX wins. Reads through the 134 ledger via the public
|
|
@@ -181,7 +181,7 @@ class RoadmapQueue
|
|
|
181
181
|
File.join(File.dirname(@roadmaps_dir), "INDEX.md")
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
# --- liveness ranking (ports plastic-
|
|
184
|
+
# --- liveness ranking (ports plastic-intent-continuing's read-time algorithm) -
|
|
185
185
|
|
|
186
186
|
def rank_candidates(parsed_list)
|
|
187
187
|
parsed_list.map do |c|
|
|
@@ -102,7 +102,7 @@ module RoadmapSavepoint
|
|
|
102
102
|
# entry has a `merged` line. Every timestamp comes from an on-disk source (the Log, or INDEX
|
|
103
103
|
# `## Completed`); an entry with no recoverable timestamp is not emitted (D4, never invented).
|
|
104
104
|
# Overwrites the ledger (the one operation allowed to rewrite it, matching
|
|
105
|
-
# `
|
|
105
|
+
# `Savepoint.rebuild_savepoint`). Returns the number of lines written.
|
|
106
106
|
def rebuild(roadmap_path)
|
|
107
107
|
text = File.read(roadmap_path)
|
|
108
108
|
log_lines = classify_log(section_body(text, "Log"))
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
# different axes so there is exactly one place to look up or register a rule name:
|
|
6
6
|
#
|
|
7
7
|
# EXCLUDABLE_CHECKS - a doctor check `name` a per-store doctor-exclusions file may name. A
|
|
8
|
-
# check name says WHICH DIAGNOSTIC FIRED.
|
|
9
|
-
# (
|
|
10
|
-
#
|
|
8
|
+
# check name says WHICH DIAGNOSTIC FIRED. Two keys: savepoint_operational (274, spec D3) and
|
|
9
|
+
# backfilled_complete (308). Most doctor checks have no exclusion mechanism at all; these two
|
|
10
|
+
# are the ones whose gap can be a known, accepted fact on an old or migrated intent.
|
|
11
11
|
#
|
|
12
12
|
# REVISION_RULES - the `[rule: <tag>]` vocabulary every revisions.md entry must carry
|
|
13
13
|
# (scripts/lib/revisions_writer.rb). A tag says WHY an intent's files were structurally
|
|
@@ -33,6 +33,9 @@ module RuleCatalog
|
|
|
33
33
|
"delivered|abandoned echo, on a terminal intent. Usually " \
|
|
34
34
|
"repairable via maintenance-run --tool rebuild-savepoint; " \
|
|
35
35
|
"excludable for the gaps 219 D6 forbids ever repairing.",
|
|
36
|
+
"backfilled_complete" => "spec.md or plan.md missing or still the placeholder, or no real " \
|
|
37
|
+
"action file, on a terminal intent. Repairable via scaffold-intent " \
|
|
38
|
+
"backfill; excludable for an old intent whose record was never kept.",
|
|
36
39
|
}.freeze
|
|
37
40
|
|
|
38
41
|
# Measured from live store data across all eight stores, 2026-08-24 (spec D1).
|