@zalom/plastic 1.0.0-beta.25 → 1.0.0-beta.27
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 +56 -1
- package/agents/plastic-intent-curator.md +4 -1
- package/package.json +1 -1
- package/scripts/doctor.rb +187 -5
- package/skills/auto/SKILL.md +15 -9
- package/skills/executing-plan/SKILL.md +4 -4
- package/skills/intent-curator/SKILL.md +4 -3
- package/skills/managing-index/SKILL.md +5 -1
- package/templates/outcome.md +3 -0
package/PLASTIC.md
CHANGED
|
@@ -362,9 +362,64 @@ each station.
|
|
|
362
362
|
| Why | `spec.md` | owner writes refresh the lease (lock file mtime heartbeat) | gate-check requires the intent file with `## Intent` before spec.md; lock-gate admits only the owner or a delegate | savepoint `Why started`, `Why spec.md created` |
|
|
363
363
|
| How | `plan.md`, `actions/`, `checklist.md` | heartbeat on writes; the code gate stays closed until plan.md plus checklist.md exist | gate-check requires spec.md before plan.md, and plan.md plus actions/ before checklist.md | savepoint `How started`, `How plan.md created`, `How checklist.md created`, `Exec started` |
|
|
364
364
|
| Exec | code on the intent branch, checklist checked off | heartbeat; code edits confined to the provisioned worktree; delegates write under the owner's lock; bash, interpreter, and MCP writes gated the same way | code-gate, worktree-gate, bash-gate, lock-gate | checklist boxes; savepoint milestones |
|
|
365
|
-
| End (done) | `outcome.md
|
|
365
|
+
| End (done) | mandatory `outcome.md` (`disposition: delivered\|abandoned`), INDEX moves to Completed or Abandoned | ordered End tail: verify, merge and remove worktrees, disarm clears `delivery.lock`, then the bridge is purge-eligible, and the QMD reindex runs LAST (after purge) | gate-check blocks outcome.md while checklist items are unchecked | savepoint `Done delivered` (or `abandoned`); takeover audits, if any, remain in savepoint.md |
|
|
366
366
|
| Maintenance (any stage) | `revisions.md` move-and-record entries | future `maintenance.lock` (short TTL), mutually exclusive with `delivery.lock` in either direction; 108 ships the schema seam only, the implementation follows intent 93 in a chained intent | acquisition refuses while the other lock type is fresh; a terminal intent with no lock held is read-only | dated, rule-tagged `revisions.md` entry; savepoint untouched |
|
|
367
367
|
|
|
368
|
+
### What "intent done" means (intent 93)
|
|
369
|
+
|
|
370
|
+
Done is one law with three signals, and they must agree. INDEX `## Completed` /
|
|
371
|
+
`## Abandoned` is the single canonical terminal marker: it is the store-wide ledger a fresh
|
|
372
|
+
session reads first, so it wins on any conflict. `outcome.md` is the "deliverable exists"
|
|
373
|
+
signal, and the savepoint `Done delivered|abandoned` line is the audit echo. All three must
|
|
374
|
+
agree; when they disagree, INDEX is authoritative and `doctor` flags the mismatch (the
|
|
375
|
+
`done_signals` check: `outcome.md` real but still under `## Active`, or terminal without a
|
|
376
|
+
real `outcome.md`, or a terminal intent whose savepoint carries no `Done` line).
|
|
377
|
+
|
|
378
|
+
`outcome.md` is mandatory at every terminal transition, delivered and abandoned alike. It
|
|
379
|
+
self-declares its disposition through a `disposition: delivered|abandoned` frontmatter
|
|
380
|
+
header. The delivered path authors it with the result; the abandoned path authors it with
|
|
381
|
+
the abandonment reason and no longer leaves the scaffolded placeholder sentinel in place.
|
|
382
|
+
|
|
383
|
+
The canonical End tail runs in this order, and the QMD reindex is always LAST, after the
|
|
384
|
+
purge: `outcome.md -> INDEX terminal -> savepoint Done -> commit -> disarm (Worktree.release
|
|
385
|
+
-> Lock.release -> purge) -> QMD reindex`. Running the reindex last keeps the index from
|
|
386
|
+
ever referencing a bridge or lock that disarm is about to remove.
|
|
387
|
+
|
|
388
|
+
The post-done access window is lock-bounded: `[INDEX terminal -> Lock.release]`. Through it
|
|
389
|
+
the completing session keeps full read and write access to the terminal directory and no
|
|
390
|
+
purge can fire (108's lock-held keep-guard keeps the bridge while `delivery.lock` exists).
|
|
391
|
+
Once the lock is released the window closes: the bridge becomes purge-eligible and the
|
|
392
|
+
directory is frozen. A crash mid-tail is recovered by stale-lock reclaim plus finishing the
|
|
393
|
+
tail; `doctor` surfaces this as a "stalled completion" (terminal in INDEX but the lock is
|
|
394
|
+
still present or stale). Finishing the tail is FINISHING a completion, never a reactivation:
|
|
395
|
+
a done intent is never moved back to `## Active`.
|
|
396
|
+
|
|
397
|
+
Terminal immutability (the contract intent 112 enforces): a terminal directory is writable
|
|
398
|
+
ONLY while a lock is held. The delivery lock covers the completing session's End tail up to
|
|
399
|
+
`Lock.release`; the maintenance lock covers sanctioned structural move-and-record edits
|
|
400
|
+
after. Terminal with no lock held is frozen. There are only two locks in the system,
|
|
401
|
+
delivery and maintenance (108 D11). This governs WRITES only: reads of a terminal intent are
|
|
402
|
+
always allowed and unbounded (curator reindex, dashboards, and future intents that reference
|
|
403
|
+
its id or chain), so a done intent stays fully readable forever. Intent 93 states this rule;
|
|
404
|
+
intent 112 builds the gate that enforces it.
|
|
405
|
+
|
|
406
|
+
Fail-safe lock doctrine (the contract intent 111 implements): the lock system never traps a
|
|
407
|
+
session or burns credits. When a gate cannot verify lock integrity it fails open, degrading
|
|
408
|
+
to advisory (warn) rather than hard-blocking. Repair is orchestrator-driven: on a lock-issue
|
|
409
|
+
signal the orchestrator inspects and repairs the lock automatically, and the human
|
|
410
|
+
`plastic-lock` command is a fallback path, not the trigger. Intent 93 states this doctrine;
|
|
411
|
+
intent 111 builds the fail-open behavior, the lock-liveness surface, the lock-issue message,
|
|
412
|
+
and the auto-repair.
|
|
413
|
+
|
|
414
|
+
Scope split. Intent 93 ships doctrine plus the low-risk reconciliation that needs no new
|
|
415
|
+
lock: the canonical done-marker and three-signal reconciliation, the mandatory `outcome.md`
|
|
416
|
+
plus `disposition` header at both terminals, the End tail with the reindex moved last, the
|
|
417
|
+
`done_signals` doctor check (three-signal agreement plus stalled-completion detection), and
|
|
418
|
+
the lock-bounded post-done window with its keep-guard test. Intent 111 owns the lock
|
|
419
|
+
liveness surface, the lock-issue message, orchestrator auto-repair, and the fail-open
|
|
420
|
+
behavior itself. Intent 112 owns the maintenance lock and the immutability gate (it inherits
|
|
421
|
+
fail-open from 111). Intent 4a1b1 owns deep agent stuck-detection and is not superseded.
|
|
422
|
+
|
|
368
423
|
## Deprecation Process
|
|
369
424
|
|
|
370
425
|
Deprecations live in `deprecations.yml` and are shown at SessionStart. While Plastic is
|
|
@@ -32,7 +32,10 @@ You are the Plastic Intent Curator. Your role is to maintain the health and navi
|
|
|
32
32
|
2. Read `~/.plastic/INDEX.md` (or project INDEX.md) to understand current organization
|
|
33
33
|
3. Compare: are there intents not in any cluster? Missing from Active/Completed/Abandoned? Status mismatches?
|
|
34
34
|
4. Make targeted edits to INDEX.md and intent frontmatter/links
|
|
35
|
-
5. On a terminal-state transition
|
|
35
|
+
5. On a terminal-state transition (Completed OR Abandoned), run the closing acts in the canonical End-tail order (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`):
|
|
36
|
+
a. Author a real `outcome.md` in the intent directory from `~/.plastic/templates/outcome.md`, with the frontmatter `disposition: delivered` for a completed intent or `disposition: abandoned` for an abandoned one. `outcome.md` is MANDATORY at every terminal, delivered and abandoned alike: on abandon it records the abandonment reason and replaces the scaffolded placeholder sentinel (never leave `outcome.md` a placeholder at a terminal).
|
|
37
|
+
b. Stamp the terminal savepoint bookend (intent 81) recording the disposition. Idempotent.
|
|
38
|
+
c. Refresh the QMD index for that store LAST, after the terminal move, outcome.md, and savepoint, so the new outcome (or abandonment rationale) is searchable. This is mandatory on any terminal-state move and a no-op when QMD is absent, and it runs in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`
|
|
36
39
|
6. Structural maintenance is move-and-record: remove the misplaced section, file, or ref from its artifact, then create or append `revisions.md` in that intent directory (copy the FORM from `~/.plastic/templates/revisions.md`). One entry per relocated item, newest at the bottom: a `## Revision vN - YYYY-MM-DD-HH:MM` header, a one-sentence `Why` ending with `[rule: <tag>]`, `Prior location`, and either `Content held` (verbatim) or a one-line `Change` for a frontmatter edit. For a stray file, embed its full content and delete the original. The violation-tag catalog is canonical in PLASTIC.md.
|
|
37
40
|
7. Report what you changed
|
|
38
41
|
|
package/package.json
CHANGED
package/scripts/doctor.rb
CHANGED
|
@@ -21,6 +21,8 @@ require_relative "lib/graph_rebuild"
|
|
|
21
21
|
require_relative "lib/links_projection"
|
|
22
22
|
require_relative "lib/links_section"
|
|
23
23
|
require_relative "lib/hook_registry"
|
|
24
|
+
require_relative "lib/lock"
|
|
25
|
+
require_relative "lib/bridge"
|
|
24
26
|
|
|
25
27
|
# Diagnostic engine, instantiable with an injected store/agent map so tests can
|
|
26
28
|
# run it hermetically (no eval, no global-constant rewriting).
|
|
@@ -505,6 +507,184 @@ class Doctor
|
|
|
505
507
|
checks
|
|
506
508
|
end
|
|
507
509
|
|
|
510
|
+
# --- Check: the three done-signals agree + stalled completion (intent 93) ---
|
|
511
|
+
#
|
|
512
|
+
# "Done" is one law with three signals that MUST agree: INDEX
|
|
513
|
+
# `## Completed`/`## Abandoned` is the single canonical terminal marker;
|
|
514
|
+
# `outcome.md` is the "deliverable exists" signal (real, non-placeholder, with a
|
|
515
|
+
# `disposition:` header); the savepoint `Done` line is the audit echo. INDEX
|
|
516
|
+
# wins on conflict. This check flags any disagreement, and separately surfaces a
|
|
517
|
+
# "stalled completion": an intent that is terminal in INDEX but whose End tail
|
|
518
|
+
# never released its `delivery.lock` (the post-done window never closed). Read
|
|
519
|
+
# only, dependency-light: it uses INDEX parsing, file presence, the placeholder
|
|
520
|
+
# sentinel, and `Lock.fresh?` — no new lock, no 111 lock-liveness surface.
|
|
521
|
+
|
|
522
|
+
# For one store's INDEX.md, map each referenced intent directory name to the
|
|
523
|
+
# `## ...` section(s) it appears under: { "<id>--<slug>" => ["Active", ...] }.
|
|
524
|
+
def index_sections_by_dir(index_path)
|
|
525
|
+
sections = Hash.new { |h, k| h[k] = [] }
|
|
526
|
+
return sections unless File.exist?(index_path)
|
|
527
|
+
|
|
528
|
+
current = nil
|
|
529
|
+
File.foreach(index_path) do |line|
|
|
530
|
+
if (m = line.match(/^##\s+(.+?)\s*$/))
|
|
531
|
+
current = m[1]
|
|
532
|
+
next
|
|
533
|
+
end
|
|
534
|
+
next unless current
|
|
535
|
+
|
|
536
|
+
line.scan(%r{store/([\w][\w.-]*?)(?:/|\))}) do |(dirname)|
|
|
537
|
+
sections[dirname] << current unless sections[dirname].include?(current)
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
sections
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# Stores to reconcile: the global store plus each project store, filtered by
|
|
544
|
+
# `scopes` (nil = all) exactly like check_conventions.
|
|
545
|
+
def done_signal_stores(scopes)
|
|
546
|
+
stores = []
|
|
547
|
+
|
|
548
|
+
if scopes.nil? || scopes.include?("global")
|
|
549
|
+
stores << {
|
|
550
|
+
scope: "global",
|
|
551
|
+
index: File.join(plastic_home, "INDEX.md"),
|
|
552
|
+
store_dir: File.join(plastic_home, "store"),
|
|
553
|
+
}
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
projects_root = File.join(plastic_home, "projects")
|
|
557
|
+
if File.directory?(projects_root)
|
|
558
|
+
Dir.children(projects_root).sort.each do |project|
|
|
559
|
+
scope = "project:#{project}"
|
|
560
|
+
next unless scopes.nil? || scopes.include?(scope)
|
|
561
|
+
|
|
562
|
+
store_dir = File.join(projects_root, project, "store")
|
|
563
|
+
next unless File.directory?(store_dir)
|
|
564
|
+
|
|
565
|
+
stores << {
|
|
566
|
+
scope: scope,
|
|
567
|
+
index: File.join(projects_root, project, "INDEX.md"),
|
|
568
|
+
store_dir: store_dir,
|
|
569
|
+
}
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
stores
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def check_done_signals(scopes: nil)
|
|
577
|
+
conflicts = [] # canonical INDEX-wins disagreement (fail)
|
|
578
|
+
gaps = [] # terminal completeness gaps on immutable/legacy history (warn)
|
|
579
|
+
stalled = [] # terminal but End tail unfinished (warn)
|
|
580
|
+
|
|
581
|
+
done_signal_stores(scopes).each do |store|
|
|
582
|
+
index_sections_by_dir(store[:index]).each do |dirname, in_sections|
|
|
583
|
+
dir = File.join(store[:store_dir], dirname)
|
|
584
|
+
next unless File.directory?(dir)
|
|
585
|
+
|
|
586
|
+
terminal = (in_sections & ["Completed", "Abandoned"]).any?
|
|
587
|
+
active = in_sections.include?("Active") && !terminal
|
|
588
|
+
outcome = File.join(dir, "outcome.md")
|
|
589
|
+
outcome_real = Bridge.stage_file_present?(outcome)
|
|
590
|
+
label = "#{store[:scope]} store/#{dirname}"
|
|
591
|
+
|
|
592
|
+
# HARD conflict: the deliverable exists but INDEX still says Active. This
|
|
593
|
+
# is the one true INDEX-wins disagreement, so it stays a fail.
|
|
594
|
+
if active && outcome_real
|
|
595
|
+
conflicts << "#{label}: outcome.md is real but the intent is still under ## Active " \
|
|
596
|
+
"(INDEX is canonical — move it to its terminal section or revert outcome.md)"
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
# Completeness gap (warn, not fail): a terminal intent whose outcome.md is
|
|
600
|
+
# missing/placeholder. Legacy terminal intents predate this convention and
|
|
601
|
+
# are immutable, so this is advisory, never breakage.
|
|
602
|
+
if terminal && !outcome_real
|
|
603
|
+
state = File.exist?(outcome) ? "still a placeholder" : "missing"
|
|
604
|
+
gaps << "#{label}: terminal in INDEX but outcome.md is #{state} " \
|
|
605
|
+
"(author outcome.md with the disposition header)"
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
next unless terminal
|
|
609
|
+
|
|
610
|
+
# Audit echo (weakest signal, D1): the savepoint should carry a
|
|
611
|
+
# `Done delivered|abandoned` line. Missing on legacy history -> advisory gap.
|
|
612
|
+
savepoint = File.join(dir, "savepoint.md")
|
|
613
|
+
if File.exist?(savepoint) && File.read(savepoint) !~ /\bDone\b.*\b(delivered|abandoned)\b/
|
|
614
|
+
gaps << "#{label}: terminal in INDEX but savepoint.md has no " \
|
|
615
|
+
"`Done delivered|abandoned` line (audit echo missing)"
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# Stalled completion: the End tail never released the delivery lock, so the
|
|
619
|
+
# post-done window `[INDEX terminal -> Lock.release]` never closed.
|
|
620
|
+
if File.exist?(Lock.path(dir))
|
|
621
|
+
note = Lock.fresh?(dir) ? "delivery.lock still present (post-done window not closed)"
|
|
622
|
+
: "delivery.lock is present and STALE"
|
|
623
|
+
stalled << "#{label}: #{note} — the End tail did not finish"
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
checks = []
|
|
629
|
+
|
|
630
|
+
# signals_agree: only the canonical INDEX-wins conflict is a hard fail, so
|
|
631
|
+
# `doctor` never goes red on immutable legacy terminal intents.
|
|
632
|
+
if conflicts.empty?
|
|
633
|
+
checks << check(
|
|
634
|
+
category: "done_signals", name: "signals_agree", status: "pass",
|
|
635
|
+
message: "No done-signal conflicts (no intent has a real outcome.md while still Active)"
|
|
636
|
+
)
|
|
637
|
+
else
|
|
638
|
+
checks << check(
|
|
639
|
+
category: "done_signals", name: "signals_agree", status: "fail",
|
|
640
|
+
message: "#{conflicts.size} done-signal conflict#{conflicts.size == 1 ? "" : "s"} " \
|
|
641
|
+
"(INDEX ## Completed/## Abandoned is canonical; a real outcome.md must not stay Active)",
|
|
642
|
+
details: conflicts, fixable: true,
|
|
643
|
+
fix_hint: "Reconcile to INDEX (canonical): move the intent to its terminal section, or revert " \
|
|
644
|
+
"outcome.md to a placeholder. Deliverable-exists but still-Active is the one done-signal " \
|
|
645
|
+
"state that must never persist."
|
|
646
|
+
)
|
|
647
|
+
end
|
|
648
|
+
|
|
649
|
+
# signals_complete: terminal completeness gaps are advisory (warn), because
|
|
650
|
+
# completed intents are immutable and legacy history predates this convention.
|
|
651
|
+
if gaps.empty?
|
|
652
|
+
checks << check(
|
|
653
|
+
category: "done_signals", name: "signals_complete", status: "pass",
|
|
654
|
+
message: "Every terminal intent carries a real outcome.md and a Done savepoint echo"
|
|
655
|
+
)
|
|
656
|
+
else
|
|
657
|
+
checks << check(
|
|
658
|
+
category: "done_signals", name: "signals_complete", status: "warn",
|
|
659
|
+
message: "#{gaps.size} terminal completeness gap#{gaps.size == 1 ? "" : "s"} " \
|
|
660
|
+
"(outcome.md or the savepoint Done echo is missing; advisory on immutable history)",
|
|
661
|
+
details: gaps, fixable: true,
|
|
662
|
+
fix_hint: "For live terminals, author a real outcome.md with the `disposition:` header via the " \
|
|
663
|
+
"completion/abandon path (plastic-auto or plastic-intent-curator) and stamp the terminal " \
|
|
664
|
+
"savepoint. Legacy terminal intents are immutable, so pre-convention gaps stay advisory."
|
|
665
|
+
)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
if stalled.empty?
|
|
669
|
+
checks << check(
|
|
670
|
+
category: "done_signals", name: "stalled_completion", status: "pass",
|
|
671
|
+
message: "No stalled completions (every terminal intent released its delivery lock)"
|
|
672
|
+
)
|
|
673
|
+
else
|
|
674
|
+
checks << check(
|
|
675
|
+
category: "done_signals", name: "stalled_completion", status: "warn",
|
|
676
|
+
message: "#{stalled.size} stalled completion#{stalled.size == 1 ? "" : "s"} " \
|
|
677
|
+
"(terminal in INDEX but the End tail did not finish)",
|
|
678
|
+
details: stalled, fixable: true,
|
|
679
|
+
fix_hint: "Finish the End tail via stale-lock reclaim: `plastic-lock reclaim`, then complete the " \
|
|
680
|
+
"tail (Worktree.release -> Lock.release -> purge -> QMD reindex last). This FINISHES a " \
|
|
681
|
+
"completion; it is NOT a reactivation of a done intent."
|
|
682
|
+
)
|
|
683
|
+
end
|
|
684
|
+
|
|
685
|
+
checks
|
|
686
|
+
end
|
|
687
|
+
|
|
508
688
|
# Build the cross-store node maps (basename + label per store) + relocation map
|
|
509
689
|
# from ALL stores, then for every intent compute its canonical `## Links`
|
|
510
690
|
# projection and flag any whose ACTUAL `## Links` section differs (membership or
|
|
@@ -839,9 +1019,9 @@ class Doctor
|
|
|
839
1019
|
groups = group.is_a?(Array) ? group : [group]
|
|
840
1020
|
live = settings.dig("hooks", event) || []
|
|
841
1021
|
groups.each do |g|
|
|
842
|
-
|
|
1022
|
+
matches = live.select { |h| h.is_a?(Hash) && h["matcher"] == g["matcher"] }
|
|
843
1023
|
wanted = g["hooks"].map { |h| h["command"] }
|
|
844
|
-
got =
|
|
1024
|
+
got = matches.flat_map { |m| Array(m["hooks"]).map { |h| h["command"] } }
|
|
845
1025
|
missing = wanted - got
|
|
846
1026
|
diffs << "#{event}[#{g['matcher']}] missing: #{missing.join(', ')}" unless missing.empty?
|
|
847
1027
|
end
|
|
@@ -1457,6 +1637,7 @@ class Doctor
|
|
|
1457
1637
|
all_checks += check_project_stores
|
|
1458
1638
|
all_checks += check_deprecations
|
|
1459
1639
|
all_checks += check_qmd
|
|
1640
|
+
all_checks += check_done_signals
|
|
1460
1641
|
|
|
1461
1642
|
summarize(all_checks, agent_key)
|
|
1462
1643
|
end
|
|
@@ -1483,9 +1664,9 @@ class Doctor
|
|
|
1483
1664
|
all_checks =
|
|
1484
1665
|
case store
|
|
1485
1666
|
when :all
|
|
1486
|
-
check_global_store + check_project_stores + check_conventions
|
|
1667
|
+
check_global_store + check_project_stores + check_conventions + check_done_signals
|
|
1487
1668
|
when :global
|
|
1488
|
-
check_global_store + check_conventions(scopes: ["global"])
|
|
1669
|
+
check_global_store + check_conventions(scopes: ["global"]) + check_done_signals(scopes: ["global"])
|
|
1489
1670
|
else
|
|
1490
1671
|
all_checks_for_project_slug(store)
|
|
1491
1672
|
end
|
|
@@ -1508,7 +1689,8 @@ class Doctor
|
|
|
1508
1689
|
end
|
|
1509
1690
|
|
|
1510
1691
|
check_project_store(slug, projects[slug]) +
|
|
1511
|
-
check_conventions(scopes: ["project:#{slug}"])
|
|
1692
|
+
check_conventions(scopes: ["project:#{slug}"]) +
|
|
1693
|
+
check_done_signals(scopes: ["project:#{slug}"])
|
|
1512
1694
|
end
|
|
1513
1695
|
|
|
1514
1696
|
# Roll a list of checks up into the standard result envelope.
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -209,7 +209,10 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
209
209
|
## Completion
|
|
210
210
|
|
|
211
211
|
1. Verify all checklist items are checked
|
|
212
|
-
2. Write `outcome.md` with detailed results
|
|
212
|
+
2. Write `outcome.md` with detailed results, from `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md`.
|
|
213
|
+
Set the frontmatter `disposition: delivered` (this is the delivered terminal). `outcome.md`
|
|
214
|
+
is mandatory at every terminal and self-declares its disposition (see the canonical done-marker
|
|
215
|
+
and End tail in PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
|
|
213
216
|
3. Write `## Outcome` summary in the intent file (1-2 sentences)
|
|
214
217
|
4. **Release (if configured)**
|
|
215
218
|
1. Detect project — match CWD against paths in `~/.plastic/projects.yml` to find the project slug. If no match, skip to step 5 (default commit-only behavior).
|
|
@@ -236,14 +239,7 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
236
239
|
```
|
|
237
240
|
(Use `"abandoned"` instead when the intent is being moved to `## Abandoned`.) Idempotent.
|
|
238
241
|
7. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
|
|
239
|
-
8.
|
|
240
|
-
It runs in the background so it never blocks the turn:
|
|
241
|
-
```bash
|
|
242
|
-
ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async
|
|
243
|
-
```
|
|
244
|
-
Completion is the lifecycle event that keeps the search index fresh. `<store-root>` is the
|
|
245
|
-
store that holds this intent (the global store or the project store).
|
|
246
|
-
9. Disarm the lifecycle gate (auto delivery is finished):
|
|
242
|
+
8. Disarm the lifecycle gate (auto delivery is finished):
|
|
247
243
|
```bash
|
|
248
244
|
ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_CODE_SESSION_ID"])'
|
|
249
245
|
```
|
|
@@ -266,6 +262,16 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
266
262
|
removed, so the integrated work is not lost. It does this with `Worktree.finish(bridge_data,
|
|
267
263
|
merge: true)` (merge-then-remove). Never leave an orphaned worktree, and run `git worktree
|
|
268
264
|
prune` if you hit a stale reference.
|
|
265
|
+
9. QMD reindex LAST (canonical End tail). AFTER disarm has released the worktrees, cleared the
|
|
266
|
+
`delivery.lock`, and purged the bridge, refresh the QMD search index for this store (no-op when
|
|
267
|
+
QMD is absent). It runs in the background so it never blocks the turn:
|
|
268
|
+
```bash
|
|
269
|
+
ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async
|
|
270
|
+
```
|
|
271
|
+
Completion is the lifecycle event that keeps the search index fresh. `<store-root>` is the store
|
|
272
|
+
that holds this intent (the global store or the project store). The reindex is the LAST End-tail
|
|
273
|
+
step, run after purge, so the index never references a bridge or lock that is about to disappear
|
|
274
|
+
(see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
|
|
269
275
|
10. Notify user: "Intent [ID] — [name] delivered. [1-2 sentence summary]. See outcome.md for details."
|
|
270
276
|
|
|
271
277
|
## Error Handling
|
|
@@ -70,12 +70,12 @@ After all tasks complete, dispatch a final reviewer for the entire implementatio
|
|
|
70
70
|
### Step 4: Update Intent and Complete
|
|
71
71
|
Capture observations in `## Insights`. When ALL checklist items are checked:
|
|
72
72
|
|
|
73
|
-
1. Write `outcome.md` with detailed results in the intent directory, using the `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md` form
|
|
73
|
+
1. Write `outcome.md` with detailed results in the intent directory, using the `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md` form. Set the frontmatter `disposition: delivered` (the delivered terminal). `outcome.md` is mandatory at every terminal and self-declares its disposition (canonical done-marker in PLASTIC.md).
|
|
74
74
|
2. Write `## Outcome` summary in the intent file (1-2 sentences)
|
|
75
75
|
3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
76
76
|
4. Update cluster entries to show `_(completed)_`
|
|
77
77
|
5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
|
|
78
|
-
6.
|
|
78
|
+
6. QMD reindex LAST (canonical End tail). As the final End-tail step, after the terminal move and any disarm, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh, and the reindex runs last so the index never references a bridge or lock that is about to disappear (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
|
|
79
79
|
|
|
80
80
|
**This is NOT optional.** An intent with all checklist items done but no Outcome is a broken state. Complete the intent immediately — do not leave it for later.
|
|
81
81
|
|
|
@@ -96,12 +96,12 @@ For each task:
|
|
|
96
96
|
### Step 3: Update Intent and Complete
|
|
97
97
|
Capture observations in `## Insights`. When ALL checklist items are checked:
|
|
98
98
|
|
|
99
|
-
1. Write `outcome.md` with detailed results in the intent directory, using the `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md` form
|
|
99
|
+
1. Write `outcome.md` with detailed results in the intent directory, using the `${CLAUDE_PLUGIN_ROOT}/templates/outcome.md` form. Set the frontmatter `disposition: delivered` (the delivered terminal). `outcome.md` is mandatory at every terminal and self-declares its disposition (canonical done-marker in PLASTIC.md).
|
|
100
100
|
2. Write `## Outcome` summary in the intent file (1-2 sentences)
|
|
101
101
|
3. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
102
102
|
4. Update cluster entries to show `_(completed)_`
|
|
103
103
|
5. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: complete intent <ID> — <name>"`
|
|
104
|
-
6.
|
|
104
|
+
6. QMD reindex LAST (canonical End tail). As the final End-tail step, after the terminal move and any disarm, ALWAYS refresh the QMD search index for this store (no-op when QMD is absent), running in the background so it never blocks the turn: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`. Completion is the lifecycle event that keeps the search index fresh, and the reindex runs last so the index never references a bridge or lock that is about to disappear (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`).
|
|
105
105
|
|
|
106
106
|
**This is NOT optional.** Complete the intent immediately when work is done.
|
|
107
107
|
|
|
@@ -38,9 +38,10 @@ The agent handles:
|
|
|
38
38
|
- Cluster management (create, merge, rename)
|
|
39
39
|
- Orphan detection
|
|
40
40
|
|
|
41
|
-
When an intent reaches a terminal state — moved to Completed OR Abandoned — do
|
|
41
|
+
When an intent reaches a terminal state — moved to Completed OR Abandoned — do these things as the closing act of the transfer, in the canonical End-tail order (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`):
|
|
42
42
|
|
|
43
|
-
1.
|
|
44
|
-
2.
|
|
43
|
+
1. Author a real `outcome.md` in the intent directory from `~/.plastic/templates/outcome.md`, with the frontmatter `disposition: delivered` for a completed intent or `disposition: abandoned` for an abandoned one. `outcome.md` is MANDATORY at every terminal, delivered and abandoned alike: on abandon it records the abandonment reason and replaces the scaffolded placeholder sentinel (never leave `outcome.md` a placeholder at a terminal).
|
|
44
|
+
2. Stamp the terminal savepoint bookend (intent 81), so the ledger's last line records the disposition: `ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.append_terminal_savepoint("<intent_dir>", "delivered")'` (use `"abandoned"` for an abandoned intent). Idempotent.
|
|
45
|
+
3. Refresh the QMD index for the affected store LAST, after the terminal move and savepoint (no-op when QMD absent), running in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`.
|
|
45
46
|
|
|
46
47
|
After the agent completes, report what changed.
|
|
@@ -41,7 +41,11 @@ Topic-based groupings. Manually curated. Create a new cluster when 3+ intents sh
|
|
|
41
41
|
### Completed
|
|
42
42
|
All completed intents with dates. Links preserved, never deleted.
|
|
43
43
|
|
|
44
|
-
When you move an intent INTO Completed or Abandoned,
|
|
44
|
+
When you move an intent INTO Completed or Abandoned, run the closing acts of the transfer in the canonical End-tail order (see PLASTIC.md `## Delivery Isolation and the Single-Owner Lock`):
|
|
45
|
+
|
|
46
|
+
1. Author a real `outcome.md` in the intent directory from `~/.plastic/templates/outcome.md`, with the frontmatter `disposition: delivered` for a completed intent or `disposition: abandoned` for an abandoned one. `outcome.md` is MANDATORY at every terminal, delivered and abandoned alike: on abandon it records the abandonment reason and replaces the scaffolded placeholder sentinel (never leave `outcome.md` a placeholder at a terminal).
|
|
47
|
+
2. Stamp the terminal savepoint bookend (intent 81), so the ledger's last line records the disposition: `ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.append_terminal_savepoint("<intent_dir>", "delivered")'` (use `"abandoned"` for an abandoned intent). Idempotent.
|
|
48
|
+
3. Refresh the QMD index for that store LAST, after the terminal move and savepoint (no-op when QMD is absent), in the background so it never blocks: `ruby ~/.plastic/scripts/qmd-sync reindex --store <store-root> --async`.
|
|
45
49
|
|
|
46
50
|
## Workflow
|
|
47
51
|
|