@zalom/plastic 1.14.1 → 2.0.0-alpha.10
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/bin/lib/context_budget.rb +453 -0
- package/bin/plastic-bench +78 -0
- package/config_asks.yml +38 -0
- package/hooks/capture +4 -0
- package/hooks/close +5 -0
- package/hooks/hooks.json +14 -42
- package/hooks/message-display +81 -0
- package/hooks/record +4 -0
- package/hooks/savepoint +5 -5
- package/package.json +3 -2
- package/scripts/agent-report +15 -10
- package/scripts/append-ledger +208 -0
- package/scripts/codex-hook +78 -90
- package/scripts/dashboard.rb +43 -14
- package/scripts/day-summary +53 -0
- package/scripts/doctor.rb +269 -43
- package/scripts/end-intent +170 -76
- package/scripts/exec-worktree +11 -20
- package/scripts/file-session-intent +61 -0
- package/scripts/hook-capture +255 -0
- package/scripts/hook-close +36 -0
- package/scripts/hook-message-display +74 -0
- package/scripts/hook-record +157 -0
- package/scripts/hook-savepoint +45 -0
- package/scripts/hook-session-start +128 -12
- package/scripts/install.rb +8 -6
- package/scripts/intent-screen +77 -0
- package/scripts/lib/agent_models.rb +1 -7
- package/scripts/lib/arm.rb +308 -0
- package/scripts/lib/backfill_intent.rb +316 -0
- package/scripts/lib/bridge.rb +24 -1597
- package/scripts/lib/compact_instructions.rb +56 -0
- package/scripts/lib/day_summary.rb +211 -0
- package/scripts/lib/doctor_core.rb +66 -88
- package/scripts/lib/doctor_session_ledger.rb +158 -0
- package/scripts/lib/exec_worktree.rb +14 -60
- package/scripts/lib/handoff.rb +184 -0
- package/scripts/lib/harness_text.rb +5 -4
- package/scripts/lib/hook_registry.rb +57 -106
- package/scripts/lib/insights.rb +3 -4
- package/scripts/lib/installer_core.rb +136 -41
- package/scripts/lib/intent_screen.rb +309 -0
- package/scripts/lib/intent_screen_ansi.rb +262 -0
- package/scripts/lib/lock.rb +6 -26
- package/scripts/lib/message_display.rb +290 -0
- package/scripts/lib/outcome_guard.rb +7 -5
- package/scripts/lib/project_validator.rb +30 -1
- package/scripts/lib/report_screen.rb +648 -0
- 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 +334 -0
- package/scripts/lib/scaffold_intent.rb +13 -179
- package/scripts/lib/screen_paint.rb +276 -0
- package/scripts/lib/session_backfill.rb +184 -0
- package/scripts/lib/session_close.rb +104 -0
- package/scripts/lib/session_git.rb +500 -0
- package/scripts/lib/session_ledger.rb +621 -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 +101 -41
- package/scripts/promote-session-item +150 -0
- package/scripts/read-config +3 -0
- package/scripts/rebuild-graph +1 -1
- package/scripts/report-screen +120 -0
- package/scripts/roadmap-next +2 -2
- package/scripts/rollback.rb +48 -3
- package/scripts/savepoint-note +67 -0
- package/scripts/scaffold-intent +37 -63
- package/scripts/session-commit +116 -0
- package/scripts/spawn-preamble +14 -7
- package/scripts/update.rb +7 -3
- package/scripts/write-handoff +60 -0
- package/skills/agent-advisor/SKILL.md +3 -3
- package/skills/agent-advisor/references/advisor-protocol.md +20 -20
- package/skills/auto/SKILL.md +223 -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 +62 -56
- 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 +62 -58
- 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 +142 -91
- package/skills/intent-continuing/references/boarding-matrix.md +34 -0
- package/skills/intent-continuing/references/context-management.md +3 -3
- package/skills/intent-creating/SKILL.md +2 -2
- package/skills/intent-creating/references/lifecycle.md +1 -1
- package/skills/intent-ending/SKILL.md +34 -37
- package/skills/intent-ending/evals/evals.json +4 -4
- package/skills/intent-executing/SKILL.md +15 -46
- 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/config.yml +5 -0
- package/templates/intent-screen.md +17 -0
- package/templates/outcome.md +14 -1
- package/templates/project.yml +7 -0
- package/templates/report-state.md +11 -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
package/scripts/end-intent
CHANGED
|
@@ -28,8 +28,12 @@
|
|
|
28
28
|
# file that exists while no session identity resolves from ANY source
|
|
29
29
|
# also refuses (exit 4) rather than ever reaching Lock.takeover with a
|
|
30
30
|
# blank session, which would delete the lock file and then crash.
|
|
31
|
-
# 1.
|
|
32
|
-
# the
|
|
31
|
+
# 1. Backfill (intent 308): write spec.md, plan.md, actions/ACTION_1.md, and
|
|
32
|
+
# outcome.md from the record wherever a file is missing or still the
|
|
33
|
+
# placeholder (BackfillIntent; real content is never touched). Then run
|
|
34
|
+
# doctor's per-intent structure check and OutcomeGuard as a self-check
|
|
35
|
+
# that reports on stderr and proceeds. Then stamp the intent file's
|
|
36
|
+
# `## Outcome` section with --outcome-summary if given.
|
|
33
37
|
# 2. Move the intent's INDEX.md line from `## Active` into `## Completed`
|
|
34
38
|
# (delivered) or `## Abandoned` (abandoned), dated today, appending
|
|
35
39
|
# --index-note (if given). Accepts a real em dash OR a plain hyphen as
|
|
@@ -37,23 +41,21 @@
|
|
|
37
41
|
# always EMITS the real em dash on write. An id that resolves to neither
|
|
38
42
|
# `## Active` nor the terminal section is a loud failure (exit 1); an id
|
|
39
43
|
# already correctly in the terminal section is a quiet, idempotent success.
|
|
40
|
-
# 3. Append the savepoint `Done` bookend (
|
|
44
|
+
# 3. Append the savepoint `Done` bookend (Savepoint.append_terminal_savepoint).
|
|
41
45
|
# 4. Commit the store repo, unless --no-commit.
|
|
42
|
-
# 5. Disarm (intent 188, D2/D16): check the code worktree (
|
|
43
|
-
#
|
|
44
|
-
# --discard-worktree-changes); call the
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
# that a worktree may be orphaned), rather than stranding a committed,
|
|
50
|
-
# terminal intent still holding its lock.
|
|
46
|
+
# 5. Disarm (intent 188, D2/D16; intent 307): check the code worktree (derived
|
|
47
|
+
# from projects.yml and the intent id, Arm.worktree_block) for uncommitted
|
|
48
|
+
# changes first (exit 5 if dirty, unless --discard-worktree-changes); call the
|
|
49
|
+
# disarm seam (default Arm.disarm: worktree released, lock cleared, the session
|
|
50
|
+
# pointer back on the day ledger); verify the durable lock file is actually gone
|
|
51
|
+
# afterward, and release it directly when a foreign-keyed disarm left it (the
|
|
52
|
+
# /tmp bridge this step once read was removed in 2.0, intent 307).
|
|
51
53
|
#
|
|
52
54
|
# Exit codes:
|
|
53
55
|
# 0 ok - the intent is closed and no delivery.lock remains.
|
|
54
56
|
# 1 usage/resolution failure, OR an id that resolves to neither ## Active
|
|
55
57
|
# nor the terminal section (D11). Deliberately double duty; see plan.md.
|
|
56
|
-
# 2 the outcome.md guard
|
|
58
|
+
# 2 retired in 2.0 (intent 308): the outcome.md guard reports and proceeds.
|
|
57
59
|
# 3 steps 1-4 already committed, but the delivery lock genuinely could not
|
|
58
60
|
# be cleared (not a bridge cache miss; see step 5 above).
|
|
59
61
|
# 4 pre-flight refusal: a FRESH foreign lock is held by a live,
|
|
@@ -63,19 +65,22 @@
|
|
|
63
65
|
# (git status itself failed: corrupt index, git missing, not a repo);
|
|
64
66
|
# refused before removal; pass --discard-worktree-changes to override
|
|
65
67
|
# deliberately.
|
|
66
|
-
# 6
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
# (intent 222).
|
|
68
|
+
# 6 retired in 2.0 (intent 308): the structure check reports and proceeds.
|
|
69
|
+
# 7 hollow delivered report refused (317a D7) - intent 308's one deliberate
|
|
70
|
+
# exception to report-and-proceed; --allow-hollow-report overrides.
|
|
70
71
|
|
|
71
72
|
require "fileutils"
|
|
72
73
|
require "date"
|
|
73
74
|
require "open3"
|
|
74
75
|
require "pathname"
|
|
75
76
|
require_relative "lib/bridge"
|
|
77
|
+
require_relative "lib/arm"
|
|
78
|
+
require_relative "lib/savepoint"
|
|
76
79
|
require_relative "lib/lock"
|
|
77
80
|
require_relative "lib/intent_validator"
|
|
78
81
|
require_relative "lib/outcome_guard"
|
|
82
|
+
require_relative "lib/report_screen"
|
|
83
|
+
require_relative "lib/backfill_intent"
|
|
79
84
|
|
|
80
85
|
DISPOSITIONS = %w[delivered abandoned].freeze
|
|
81
86
|
|
|
@@ -97,6 +102,7 @@ def parse_args(argv)
|
|
|
97
102
|
opts = {
|
|
98
103
|
store: nil, id: nil, disposition: nil, index: nil,
|
|
99
104
|
outcome_summary: nil, index_note: nil, no_commit: false, dry_run: false,
|
|
105
|
+
allow_hollow_report: false,
|
|
100
106
|
session: nil, discard_worktree_changes: false,
|
|
101
107
|
}
|
|
102
108
|
i = 0
|
|
@@ -113,6 +119,7 @@ def parse_args(argv)
|
|
|
113
119
|
when "--discard-worktree-changes" then opts[:discard_worktree_changes] = true
|
|
114
120
|
when "--no-commit" then opts[:no_commit] = true
|
|
115
121
|
when "--dry-run" then opts[:dry_run] = true
|
|
122
|
+
when "--allow-hollow-report" then opts[:allow_hollow_report] = true
|
|
116
123
|
else
|
|
117
124
|
usage_abort("unknown argument #{arg.inspect}")
|
|
118
125
|
end
|
|
@@ -169,44 +176,41 @@ def resolve_plastic_home_and_scope(store)
|
|
|
169
176
|
end
|
|
170
177
|
end
|
|
171
178
|
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
179
|
+
# Backfill (intent 308): write the judgment documents that are missing or still the
|
|
180
|
+
# placeholder from the record (BackfillIntent). Fail-open: a crash here warns and the
|
|
181
|
+
# close proceeds, the same contract as the structure check below.
|
|
182
|
+
def run_backfill(intent_dir, store:, id:, disposition:, summary:)
|
|
183
|
+
result = BackfillIntent.run(intent_dir: intent_dir, store: store, id: id,
|
|
184
|
+
disposition: disposition, summary: summary)
|
|
185
|
+
result[:written].each { |rel| warn "end-intent: backfilled #{rel} from the record" }
|
|
186
|
+
result[:notes].each { |note| warn "end-intent: backfill note: #{note}" }
|
|
187
|
+
rescue StandardError => e
|
|
188
|
+
warn "end-intent: backfill crashed (#{e.message}); proceeding without it"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Doctor's per-intent structure check (intent 222; a self-check since intent 308). Runs
|
|
192
|
+
# after the backfill and REPORTS: every FAIL or WARN check prints its named reason on
|
|
193
|
+
# stderr and the close proceeds. Nothing here exits; a gap end-intent cannot write itself
|
|
194
|
+
# (an unchecked box, a malformed intent file, links out of projection) is the owner's to
|
|
195
|
+
# fix afterward, and doctor --intent keeps reporting it. A crash is rescued the same way.
|
|
196
|
+
# Injected `gate:` seam: tests drive the real Doctor against a hermetic home, or a raising
|
|
197
|
+
# stub to prove the fail-open contract, with no eval/ENV/global seam.
|
|
181
198
|
#
|
|
182
|
-
# Disposition is deliberately NOT passed to the real gate call
|
|
183
|
-
#
|
|
184
|
-
#
|
|
185
|
-
|
|
186
|
-
# it here would make this gate re-check the SAME disposition match the old guard already
|
|
187
|
-
# owns, needlessly widening which exit-2 fixtures become exit-6. doctor.rb --intent called
|
|
188
|
-
# directly (with --disposition) still exercises the fold-in per D2/acceptance criteria.
|
|
189
|
-
def run_structure_gate(id, store:, gate: ->(gate_home, gate_scope) {
|
|
199
|
+
# Disposition is deliberately NOT passed to the real gate call: OutcomeGuard.reason,
|
|
200
|
+
# called right after this in main, already owns disposition-matching, and
|
|
201
|
+
# intent_lifecycle_artifacts checks outcome.md PRESENCE regardless of disposition.
|
|
202
|
+
def run_structure_check(id, store:, gate: ->(gate_home, gate_scope) {
|
|
190
203
|
require_relative "doctor"
|
|
191
204
|
Doctor.new(plastic_home: gate_home).run_intent_check(id, store: gate_scope)
|
|
192
205
|
})
|
|
193
206
|
gate_home, gate_scope = resolve_plastic_home_and_scope(store)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
detail = Array(c[:details]).any? ? " (#{Array(c[:details]).join("; ")})" : ""
|
|
199
|
-
warn "end-intent: structure gate refused: #{c[:name]}: #{c[:message]}#{detail}"
|
|
200
|
-
end
|
|
201
|
-
exit 6
|
|
202
|
-
when "warn"
|
|
203
|
-
gate_verdict[:checks].select { |c| c[:status] == "warn" }.each do |c|
|
|
204
|
-
detail = Array(c[:details]).any? ? " (#{Array(c[:details]).join("; ")})" : ""
|
|
205
|
-
warn "end-intent: structure gate warning (proceeding): #{c[:name]}: #{c[:message]}#{detail}"
|
|
206
|
-
end
|
|
207
|
+
verdict = gate.call(gate_home, gate_scope)
|
|
208
|
+
verdict[:checks].select { |c| %w[fail warn].include?(c[:status]) }.each do |c|
|
|
209
|
+
detail = Array(c[:details]).any? ? " (#{Array(c[:details]).join("; ")})" : ""
|
|
210
|
+
warn "end-intent: structure check: #{c[:name]}: #{c[:message]}#{detail}"
|
|
207
211
|
end
|
|
208
212
|
rescue StandardError => e
|
|
209
|
-
warn "end-intent: structure
|
|
213
|
+
warn "end-intent: structure check crashed (#{e.message}); proceeding without it"
|
|
210
214
|
end
|
|
211
215
|
|
|
212
216
|
# outcome.md guard: see OutcomeGuard.reason (scripts/lib/outcome_guard.rb, extracted intent 222)
|
|
@@ -216,7 +220,75 @@ end
|
|
|
216
220
|
# Replace the body of the intent file's `## Outcome` section with `summary`,
|
|
217
221
|
# leaving every other section and the frontmatter untouched. No-op (returns
|
|
218
222
|
# content unchanged) when summary is blank or the section is not found.
|
|
223
|
+
# 317a S9 (D7): the self-render gate. Renders the delivered screen's sources on
|
|
224
|
+
# the intent's own record and names what would come out hollow. This is intent
|
|
225
|
+
# 308's ONE deliberate exception to report-and-proceed: a hollow DELIVERED
|
|
226
|
+
# close is refused with exit 7 (--allow-hollow-report overrides), because 317
|
|
227
|
+
# closed exactly this way and the owner read a screen full of "not recorded".
|
|
228
|
+
# Abandoned closes and machine-backfilled outcomes (the backfill marker) are
|
|
229
|
+
# exempt: the reader cannot expect labels the writer never had.
|
|
230
|
+
def hollow_report_reason(intent_dir, disposition)
|
|
231
|
+
return nil unless disposition == "delivered"
|
|
232
|
+
|
|
233
|
+
outcome = File.join(intent_dir, "outcome.md")
|
|
234
|
+
return nil unless File.exist?(outcome)
|
|
235
|
+
text = File.read(outcome)
|
|
236
|
+
return nil if text.include?("<!-- backfilled from the record by end-intent on ")
|
|
237
|
+
|
|
238
|
+
# The gate's evidence bar (A7/B7): it judges only records that CLAIM the
|
|
239
|
+
# modern convention - at least one actions/*.md heading carrying an S-label.
|
|
240
|
+
# A terse legacy close with no labeled matrix stays report-and-proceed.
|
|
241
|
+
heading_labels = Dir.glob(File.join(intent_dir, "actions", "*.md")).sort.flat_map do |path|
|
|
242
|
+
ReportScreen.split_by_headings(File.read(path)).filter_map do |heading, _body|
|
|
243
|
+
heading.to_s.sub(/\A#+\s*/, "").split(/[^A-Za-z0-9]+/).find { |tok| tok.match?(/\AS\d+\z/) }
|
|
244
|
+
end
|
|
245
|
+
end.uniq
|
|
246
|
+
return nil if heading_labels.empty?
|
|
247
|
+
|
|
248
|
+
rows = ReportScreen.delivered_rows(intent_dir)
|
|
249
|
+
return "outcome.md ## Delivered renders no rows although a labeled action matrix exists" if rows.empty?
|
|
250
|
+
|
|
251
|
+
needs = ReportScreen.section_of(text, "## Needs you").gsub(/<!--.*?-->/m, "").strip
|
|
252
|
+
if !needs.empty? && needs != "None" && ReportScreen.needs_you_rows(intent_dir).empty?
|
|
253
|
+
return "## Needs you has content that would render as None"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
labels = rows.map { |r| r[:label] }
|
|
257
|
+
if (labels & heading_labels).empty?
|
|
258
|
+
return "delivered row labels (#{labels.first(3).join(', ')}...) match no action-file heading (#{heading_labels.first(3).join(', ')}...)"
|
|
259
|
+
end
|
|
260
|
+
if rows.all? { |r| ReportScreen.proven_by(intent_dir, r[:label]) == ReportScreen::NOT_RECORDED }
|
|
261
|
+
return "every Proven-by cell renders not recorded although labeled action headings exist"
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
nil
|
|
265
|
+
rescue StandardError => e
|
|
266
|
+
warn "end-intent: hollow-report gate crashed (#{e.message}); proceeding without it"
|
|
267
|
+
nil
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# 317a S7 (D5, A8): stamp the live lock's run_mode into outcome.md frontmatter
|
|
271
|
+
# INSIDE the close step - after the gate, before the INDEX move and the store
|
|
272
|
+
# commit - so the stamped file rides the close commit and mode survives disarm.
|
|
273
|
+
def stamp_outcome_mode(intent_dir)
|
|
274
|
+
data = Lock.read(intent_dir)
|
|
275
|
+
run_mode = data && data["run_mode"].to_s
|
|
276
|
+
return if run_mode.nil? || run_mode.empty?
|
|
277
|
+
|
|
278
|
+
path = File.join(intent_dir, "outcome.md")
|
|
279
|
+
return unless File.exist?(path)
|
|
280
|
+
text = File.read(path)
|
|
281
|
+
return unless text.start_with?("---")
|
|
282
|
+
head, body = text[3..].split("---", 2)
|
|
283
|
+
return if body.nil? || head.match?(/^mode:/)
|
|
284
|
+
|
|
285
|
+
File.write(path, "---#{head}mode: #{run_mode}\n---#{body}")
|
|
286
|
+
rescue StandardError => e
|
|
287
|
+
warn "end-intent: mode stamp failed (#{e.message}); proceeding without it"
|
|
288
|
+
end
|
|
289
|
+
|
|
219
290
|
def stamp_outcome_summary(content, summary)
|
|
291
|
+
|
|
220
292
|
return content if summary.nil? || summary.to_s.strip.empty?
|
|
221
293
|
|
|
222
294
|
lines = content.lines
|
|
@@ -459,21 +531,21 @@ end
|
|
|
459
531
|
|
|
460
532
|
# --- step 5: disarm (D2/D16, intent 188) ------------------------------------
|
|
461
533
|
#
|
|
462
|
-
# Injected seams (D2):
|
|
463
|
-
# dirty-worktree check can resolve the code worktree
|
|
464
|
-
# first), disarm defaults to
|
|
534
|
+
# Injected seams (D2, re-pointed by intent 307): worktree_reader defaults to
|
|
535
|
+
# Arm.worktree_block (so the dirty-worktree check can resolve the code worktree
|
|
536
|
+
# path without disarming first), disarm defaults to Arm.disarm. Tests may inject
|
|
465
537
|
# fakes for in-process unit coverage of this function; the subprocess-driven
|
|
466
538
|
# end-to-end tests exercise the real defaults against a hermetic tmp home.
|
|
467
539
|
#
|
|
468
540
|
# Returns :ok, :dirty (exit 5), or :lock_remains (exit 3). A blank session
|
|
469
541
|
# (D3's "no lock at all" fallback case) is a clean no-op: :ok, nothing touched.
|
|
470
542
|
def run_disarm(intent_dir, id, session, discard_worktree_changes:,
|
|
471
|
-
|
|
472
|
-
disarm: ->(sess,
|
|
543
|
+
worktree_reader: ->(dir) { Arm.worktree_block(intent_dir: dir) },
|
|
544
|
+
disarm: ->(sess, dir) { Arm.disarm(intent_dir: dir, session: sess) })
|
|
473
545
|
return :ok if Bridge.blank?(session)
|
|
474
546
|
|
|
475
|
-
|
|
476
|
-
worktree_code =
|
|
547
|
+
block = worktree_reader.call(intent_dir)
|
|
548
|
+
worktree_code = block.is_a?(Hash) ? block["code"] : nil
|
|
477
549
|
|
|
478
550
|
if worktree_code && Dir.exist?(worktree_code)
|
|
479
551
|
begin
|
|
@@ -505,16 +577,15 @@ def run_disarm(intent_dir, id, session, discard_worktree_changes:,
|
|
|
505
577
|
end
|
|
506
578
|
end
|
|
507
579
|
|
|
508
|
-
disarm.call(session,
|
|
580
|
+
disarm.call(session, intent_dir)
|
|
509
581
|
|
|
510
|
-
# The
|
|
511
|
-
#
|
|
512
|
-
#
|
|
513
|
-
#
|
|
514
|
-
#
|
|
515
|
-
#
|
|
516
|
-
#
|
|
517
|
-
# is still ours to clear.
|
|
582
|
+
# The durable lock file is the truth (AGENTS.md). Arm.disarm releases as the
|
|
583
|
+
# lock's recorded owner, so a lock a delegate or a post-takeover owner is
|
|
584
|
+
# closing under a different key can survive it; that would strand a committed,
|
|
585
|
+
# terminal intent still holding its lock, the exact stalled completion this
|
|
586
|
+
# intent forbids. Pre-flight has already established that this session is the
|
|
587
|
+
# owner, a delegate, or the post-takeover owner, so clearing the durable lock
|
|
588
|
+
# directly IS safe, PROVIDED the lock actually present here is still ours to clear.
|
|
518
589
|
#
|
|
519
590
|
# TOCTOU fix (post-review hardening, intent 188): the first version of this
|
|
520
591
|
# fallback read the lock's OWN recorded owner and handed it straight back to
|
|
@@ -556,8 +627,8 @@ def run_disarm(intent_dir, id, session, discard_worktree_changes:,
|
|
|
556
627
|
end
|
|
557
628
|
|
|
558
629
|
unless File.exist?(Lock.path(intent_dir))
|
|
559
|
-
if
|
|
560
|
-
warn "end-intent: no
|
|
630
|
+
if block.nil?
|
|
631
|
+
warn "end-intent: no worktree block could be derived for intent #{id}; " \
|
|
561
632
|
"the delivery lock was cleared directly from disk, but any worktree this intent " \
|
|
562
633
|
"provisioned was NOT removed. Run /plastic-doctor to check for an orphaned worktree."
|
|
563
634
|
end
|
|
@@ -621,16 +692,10 @@ def main(argv)
|
|
|
621
692
|
exit 4
|
|
622
693
|
end
|
|
623
694
|
|
|
624
|
-
run_structure_gate(id, store: store)
|
|
625
|
-
|
|
626
|
-
reason = OutcomeGuard.reason(intent_dir, disposition)
|
|
627
|
-
if reason
|
|
628
|
-
warn "end-intent: #{reason}"
|
|
629
|
-
exit 2
|
|
630
|
-
end
|
|
631
|
-
|
|
632
695
|
if opts[:dry_run]
|
|
633
696
|
puts "end-intent: dry run for #{File.basename(intent_dir)} (#{disposition})"
|
|
697
|
+
targets = BackfillIntent.targets_for(intent_dir)
|
|
698
|
+
puts " would backfill: #{targets.empty? ? "(nothing)" : targets.join(", ")}"
|
|
634
699
|
puts " would stamp ## Outcome: #{opts[:outcome_summary].inspect}" if opts[:outcome_summary]
|
|
635
700
|
puts " would move INDEX.md entry from ## Active to #{index_target_heading(disposition)} (#{index_path})"
|
|
636
701
|
puts " would append index note: #{opts[:index_note].inspect}" if opts[:index_note]
|
|
@@ -653,9 +718,38 @@ def main(argv)
|
|
|
653
718
|
end
|
|
654
719
|
end
|
|
655
720
|
|
|
721
|
+
# 1. Backfill from the record (intent 308), then the structure self-check and the
|
|
722
|
+
# outcome guard, both report-only since 308.
|
|
723
|
+
run_backfill(intent_dir, store: store, id: id, disposition: disposition,
|
|
724
|
+
summary: opts[:outcome_summary])
|
|
725
|
+
run_structure_check(id, store: store)
|
|
726
|
+
|
|
727
|
+
reason = OutcomeGuard.reason(intent_dir, disposition)
|
|
728
|
+
warn "end-intent: outcome.md: #{reason} (proceeding; doctor --intent #{id} reports it)" if reason
|
|
729
|
+
|
|
730
|
+
# 1a2. The self-render gate (317a S9, D7): refuse a hollow delivered close
|
|
731
|
+
# BEFORE any terminal mutation - INDEX, savepoint, and store stay untouched
|
|
732
|
+
# on exit 7 (A9).
|
|
733
|
+
hollow = hollow_report_reason(intent_dir, disposition)
|
|
734
|
+
if hollow
|
|
735
|
+
if opts[:allow_hollow_report]
|
|
736
|
+
warn "end-intent: hollow delivered report overridden (--allow-hollow-report): #{hollow}"
|
|
737
|
+
else
|
|
738
|
+
warn "end-intent: refusing a hollow delivered close: #{hollow}"
|
|
739
|
+
warn "end-intent: write outcome.md's ## Delivered as the labeled | Row | What | table " \
|
|
740
|
+
"matching the action-file headings (templates/outcome.md shows the shape), " \
|
|
741
|
+
"or pass --allow-hollow-report to close anyway"
|
|
742
|
+
exit 7
|
|
743
|
+
end
|
|
744
|
+
end
|
|
745
|
+
|
|
746
|
+
# 1a3. Mode stamp (317a S7, D5): run_mode into outcome frontmatter while the
|
|
747
|
+
# lock still exists, so the stamped file rides the close commit.
|
|
748
|
+
stamp_outcome_mode(intent_dir)
|
|
749
|
+
|
|
656
750
|
# 1b. Intent-file `## Outcome` summary stamp (no-op unless given).
|
|
657
751
|
if opts[:outcome_summary]
|
|
658
|
-
intent_file =
|
|
752
|
+
intent_file = Savepoint.intent_file(intent_dir)
|
|
659
753
|
if File.exist?(intent_file)
|
|
660
754
|
original = File.read(intent_file)
|
|
661
755
|
updated = stamp_outcome_summary(original, opts[:outcome_summary])
|
|
@@ -682,7 +776,7 @@ def main(argv)
|
|
|
682
776
|
end
|
|
683
777
|
|
|
684
778
|
# 3. Savepoint Done bookend (idempotent).
|
|
685
|
-
|
|
779
|
+
Savepoint.append_terminal_savepoint(intent_dir, disposition)
|
|
686
780
|
|
|
687
781
|
# 4. Store auto-commit, unless --no-commit. Scoped to this intent's own paths (D17):
|
|
688
782
|
# never git add -A.
|
package/scripts/exec-worktree
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# exec-worktree (intent 213, group 2) - finish an intent's code worktree:
|
|
6
|
-
#
|
|
5
|
+
# exec-worktree (intent 213, group 2) - finish an intent's code worktree: the dirty-worktree
|
|
6
|
+
# guard, then Worktree.finish (delivered merges + removes, abandoned removes only). The order
|
|
7
|
+
# precondition it once ran first was removed in 2.0 with the gates (intent 302).
|
|
7
8
|
#
|
|
8
9
|
# Usage:
|
|
9
10
|
# exec-worktree --store <path> --id <intent_id> --home <plastic_home> \
|
|
@@ -18,26 +19,17 @@
|
|
|
18
19
|
# Steps performed:
|
|
19
20
|
# 0. Resolve inputs: the intent dir, normalize --home, resolve the calling session
|
|
20
21
|
# (--session, else CLAUDE_CODE_SESSION_ID, else the existing delivery.lock's own
|
|
21
|
-
# recorded owner_session when non-blank, else nil;
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
# 1. The
|
|
27
|
-
# Bridge.code_gate_decision predicate exactly as it exists. On an auto bridge that
|
|
28
|
-
# has not reached How, this BLOCKS (exit 2). Bridge.code_gate_decision returns nil
|
|
29
|
-
# UNCONDITIONALLY on a guided bridge (build.auto != true), so on a guided bridge
|
|
30
|
-
# this precondition is ADVISORY ONLY: it always passes and the run proceeds. This
|
|
31
|
-
# script is NOT the enforcement point either way; the hook layer
|
|
32
|
-
# (scripts/lib/bridge.rb code_gate_decision, wired through scripts/lib/edit_gates.rb)
|
|
33
|
-
# remains the actual gate. See scripts/lib/exec_worktree.rb's module doc for why the
|
|
34
|
-
# auto-only condition is never relaxed and never duplicated here.
|
|
35
|
-
# 2. The dirty-worktree guard (delivered only): git status --porcelain on the code
|
|
22
|
+
# recorded owner_session when non-blank, else nil; the lock-owner fallback IS
|
|
23
|
+
# used here, matching end-intent's teardown resolution order).
|
|
24
|
+
# Derive the worktree block from projects.yml and the intent id (Arm.worktree_block;
|
|
25
|
+
# the /tmp bridge this once read was removed in 2.0, intent 307). No code worktree on
|
|
26
|
+
# disk means nothing was provisioned (exit 0).
|
|
27
|
+
# 1. The dirty-worktree guard (delivered only): git status --porcelain on the code
|
|
36
28
|
# worktree. Not clean, or the check itself fails, fails CLOSED (exit 3), mirroring
|
|
37
29
|
# end-intent's dirty-worktree handling. abandoned skips this guard entirely: the
|
|
38
30
|
# branch survives and can be reclaimed, so a dirty worktree is not a reason to
|
|
39
31
|
# refuse.
|
|
40
|
-
#
|
|
32
|
+
# 2. Finish: Worktree.finish(bridge_data, home:, runner:, merge: disposition ==
|
|
41
33
|
# "delivered"). Worktree.finish is fail-open and never raises, so failure is
|
|
42
34
|
# detected by checking, for a delivered disposition only, whether the worktree
|
|
43
35
|
# directory still exists on disk afterward (release removes it on success).
|
|
@@ -49,8 +41,7 @@
|
|
|
49
41
|
# 0 finished (merged or removed per disposition), or nothing to finish (no bridge
|
|
50
42
|
# resolved, or no code worktree recorded on the bridge)
|
|
51
43
|
# 1 usage or path-resolution failure
|
|
52
|
-
# 2
|
|
53
|
-
# reached)
|
|
44
|
+
# 2 retired in 2.0 (intent 302): was the order precondition; never emitted, never reused
|
|
54
45
|
# 3 the worktree is not clean, or the merge/removal did not complete (delivered only)
|
|
55
46
|
# 4 lock or session resolution failure
|
|
56
47
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# file-session-intent (intent 301): backfill and close one session day ledger,
|
|
6
|
+
# carrying its open items into a target day.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# file-session-intent --day <YYYYMMDD> [--carry-to <YYYYMMDD>] [--store <dir>]
|
|
10
|
+
# [--templates <dir>] [--author <name>]
|
|
11
|
+
#
|
|
12
|
+
# --store defaults to $PLASTIC_HOME/store ($PLASTIC_HOME defaults to ~/.plastic).
|
|
13
|
+
# Prints one line: `filed <day>` or `skipped <day>: closed`. Exit 0 on success
|
|
14
|
+
# and on any filing error (reported on stderr; the next boot tries again), 2 on
|
|
15
|
+
# a usage error.
|
|
16
|
+
|
|
17
|
+
require_relative "lib/session_ledger"
|
|
18
|
+
require_relative "lib/session_backfill"
|
|
19
|
+
|
|
20
|
+
def usage_abort(message)
|
|
21
|
+
warn "file-session-intent: #{message}"
|
|
22
|
+
exit 2
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def next_value!(argv, i, flag)
|
|
26
|
+
value = argv[i + 1]
|
|
27
|
+
usage_abort("#{flag} needs a value") if value.nil? || value.start_with?("--")
|
|
28
|
+
value
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
opts = { day: nil, carry_to: nil, store: nil, templates: nil, author: "session" }
|
|
32
|
+
i = 0
|
|
33
|
+
while i < ARGV.length
|
|
34
|
+
case ARGV[i]
|
|
35
|
+
when "--day" then opts[:day] = next_value!(ARGV, i, "--day"); i += 1
|
|
36
|
+
when "--carry-to" then opts[:carry_to] = next_value!(ARGV, i, "--carry-to"); i += 1
|
|
37
|
+
when "--store" then opts[:store] = next_value!(ARGV, i, "--store"); i += 1
|
|
38
|
+
when "--templates" then opts[:templates] = next_value!(ARGV, i, "--templates"); i += 1
|
|
39
|
+
when "--author" then opts[:author] = next_value!(ARGV, i, "--author"); i += 1
|
|
40
|
+
else usage_abort("unknown argument #{ARGV[i].inspect}")
|
|
41
|
+
end
|
|
42
|
+
i += 1
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
usage_abort("--day is required") if opts[:day].nil?
|
|
46
|
+
usage_abort("--day must be a real calendar date, YYYYMMDD") unless SessionLedger.valid_day_id?(opts[:day])
|
|
47
|
+
if opts[:carry_to] && !SessionLedger.valid_day_id?(opts[:carry_to])
|
|
48
|
+
usage_abort("--carry-to must be a real calendar date, YYYYMMDD")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
store = opts[:store] || File.join(File.expand_path(ENV.fetch("PLASTIC_HOME", "~/.plastic")), "store")
|
|
52
|
+
templates = opts[:templates] || File.expand_path("../templates", __dir__)
|
|
53
|
+
|
|
54
|
+
begin
|
|
55
|
+
result = SessionBackfill.file_day(store: store, day: opts[:day], templates: templates,
|
|
56
|
+
author: opts[:author], carry_to: opts[:carry_to])
|
|
57
|
+
puts(result == :closed ? "skipped #{opts[:day]}: closed" : "filed #{opts[:day]}")
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
warn "file-session-intent: #{opts[:day]} not filed: #{e.class}: #{e.message}"
|
|
60
|
+
end
|
|
61
|
+
exit 0
|