@zalom/plastic 2.0.0-alpha.23 → 2.0.0-alpha.24

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.
Files changed (63) hide show
  1. package/PLASTIC.md +6 -5
  2. package/agents/plastic-node-research.md +4 -4
  3. package/agents/plastic-node-verify.md +4 -4
  4. package/agents/plastic-node-work.md +4 -4
  5. package/package.json +1 -1
  6. package/scripts/dashboard.rb +1 -1
  7. package/scripts/end-intent +19 -16
  8. package/scripts/exec-worktree +5 -5
  9. package/scripts/hook-call-budget +6 -6
  10. package/scripts/hook-capture +15 -15
  11. package/scripts/hook-record +21 -14
  12. package/scripts/hook-savepoint +2 -1
  13. package/scripts/hook-session-start +9 -10
  14. package/scripts/lib/active_delivery.rb +44 -0
  15. package/scripts/lib/arm.rb +41 -102
  16. package/scripts/lib/codex_adapter.rb +2 -2
  17. package/scripts/lib/{packet_wrapper.rb → data_boundary.rb} +7 -7
  18. package/scripts/lib/day_summary.rb +19 -11
  19. package/scripts/lib/doctor_session_ledger.rb +3 -52
  20. package/scripts/lib/exec_worktree.rb +24 -21
  21. package/scripts/lib/graph_measure_budget.rb +29 -28
  22. package/scripts/lib/handoff.rb +4 -8
  23. package/scripts/lib/harness_adapter.rb +6 -6
  24. package/scripts/lib/index_entry.rb +53 -0
  25. package/scripts/lib/insights.rb +1 -1
  26. package/scripts/lib/installer_core.rb +120 -7
  27. package/scripts/lib/lock.rb +8 -9
  28. package/scripts/lib/{node_packet.rb → node_input.rb} +49 -49
  29. package/scripts/lib/node_input_compatibility.rb +62 -0
  30. package/scripts/lib/node_ledger.rb +4 -2
  31. package/scripts/lib/outcome_report.rb +1 -1
  32. package/scripts/lib/project_config.rb +45 -0
  33. package/scripts/lib/ready_set.rb +1 -1
  34. package/scripts/lib/roadmap_savepoint.rb +1 -1
  35. package/scripts/lib/runner_absorb.rb +5 -5
  36. package/scripts/lib/runner_dispatch.rb +44 -44
  37. package/scripts/lib/runner_sweep.rb +4 -4
  38. package/scripts/lib/runner_until_empty.rb +1 -1
  39. package/scripts/lib/savepoint.rb +6 -7
  40. package/scripts/lib/scaffold_intent.rb +6 -3
  41. package/scripts/lib/session_close.rb +30 -28
  42. package/scripts/lib/session_git.rb +20 -14
  43. package/scripts/lib/session_ledger.rb +44 -4
  44. package/scripts/lib/worktree.rb +24 -24
  45. package/scripts/lib/worktree_sweep.rb +3 -3
  46. package/scripts/{node-packet → node-input} +15 -15
  47. package/scripts/node-run +19 -19
  48. package/scripts/plastic-lock +33 -32
  49. package/scripts/runner +1 -1
  50. package/skills/auto/SKILL.md +7 -7
  51. package/skills/auto/references/end-tail.md +9 -11
  52. package/skills/conventions/references/completion-and-done.md +9 -10
  53. package/skills/conventions/references/knowledge-graph.md +1 -1
  54. package/skills/conventions/references/locks-and-worktrees.md +10 -12
  55. package/skills/direct/SKILL.md +2 -2
  56. package/skills/doctor/SKILL.md +1 -1
  57. package/skills/intent-executing/SKILL.md +1 -1
  58. package/skills/releasing/SKILL.md +2 -2
  59. package/skills/releasing/references/promotion-and-tagging.md +1 -1
  60. package/skills/releasing/references/release-lines.md +1 -1
  61. package/templates/index.md +1 -1
  62. package/templates/project.yml +1 -1
  63. package/scripts/lib/bridge.rb +0 -116
@@ -4,10 +4,10 @@ This chapter holds the delivery lock, claims, worktrees, the fail-safe doctrine,
4
4
 
5
5
  ### Delivery Isolation and the Single-Owner Lock
6
6
 
7
- Locks and worktrees exist only for auto teams. An interactive session working direct or
8
- thinking takes no lock: it records into the day ledger, or into the intent its per-session
9
- pointer names (`~/.plastic/store/.tmp/<session>/current`, where `<session>` is the first eight
10
- characters of the session id; the file holds today's day id or an intent id).
7
+ The delivery lock names the session delivering an intent, as its owner or a delegate. Locks
8
+ and worktrees exist only for auto teams: an interactive session working direct or thinking
9
+ takes no lock and records into its day ledger instead (the oldest day in the last seven whose
10
+ checklist carries the session's line, else today).
11
11
 
12
12
  For an auto team, exactly one team develops an intent's delivery at a time. Ownership is
13
13
  session-keyed and durable: arming acquires `delivery.lock` inside the intent directory
@@ -17,9 +17,7 @@ but never grants access and is never inferred from transcripts or filesystem pat
17
17
  fields on legacy locks display as `Unknown`. Liveness is a lease: the record hook refreshes
18
18
  the lock file's mtime on every write the owning session makes, and that mtime is the sole
19
19
  heartbeat truth. The lock counts as stale only when the mtime is older than the TTL. No
20
- process id is consulted anywhere. The pointer file is a cache of which intent a session
21
- records into; the lock file is the truth of who owns a delivery, and wins on any
22
- disagreement. Another team that finds a fresh lock backs off; a stale lock is reclaimed only
20
+ process id is consulted anywhere. The lock file is the truth of who owns a delivery. Another team that finds a fresh lock backs off; a stale lock is reclaimed only
23
21
  by explicit takeover, which replaces the lock and appends an audit line to the intent's
24
22
  savepoint.md. Rearming the same session preserves its acquired identity and refreshes known
25
23
  provenance; an explicit takeover replaces the controller and starts new provenance.
@@ -31,7 +29,7 @@ retained as descriptive history, bounded to the 20 most recent terminal entries.
31
29
  a delegate, and an artifact claim are distinct evidence: controller ownership authorizes the
32
30
  delivery, delegate registration authorizes a child session, and a claim selects one current
33
31
  writer for one artifact. Disarm clears the lock; the End tail is ordered: verify, merge and
34
- remove worktrees, clear the lock, and only then is the session pointer purge-eligible. Repair
32
+ remove worktrees, then clear the lock. Repair
35
33
  is one idempotent function with two entry points: the `plastic-lock` command (`who`, status,
36
34
  fix, release, reclaim, delegate) and the `plastic-doctor` skill's lock section, so repair
37
35
  self-heals. `who` is read-only and reports the controller, mtime heartbeat, delegates, and
@@ -90,18 +88,18 @@ an orphaned worktree behind, and clear a stale worktree reference with `git work
90
88
 
91
89
  #### Intent delivery, station by station
92
90
 
93
- How one auto-team intent travels from boarding to the End tail, and what the lock, the pointer, and
91
+ How one auto-team intent travels from boarding to the End tail, and what the lock and
94
92
  the record hook do at each station. Nothing in the third column blocks; the fourth column is
95
93
  what gets written down.
96
94
 
97
- | Station | Delivered artifact | Lock and pointer steps | Record |
95
+ | Station | Delivered artifact | Lock steps | Record |
98
96
  |---|---|---|---|
99
- | Start (board) | none (a procedure, not a stage) | `plastic-lock fix` self-heals stale, corrupt, or legacy state; arm acquires `delivery.lock` (O_EXCL, session-keyed), provisions the code worktree, writes the session pointer | savepoint confirms the boarding station |
97
+ | Start (board) | none (a procedure, not a stage) | `plastic-lock fix` self-heals stale, corrupt, or legacy state; arm acquires `delivery.lock` (O_EXCL, session-keyed), provisions the code worktree | savepoint confirms the boarding station |
100
98
  | What (create) | `<id>--<slug>.md`, born complete | no lock yet; `new-intent` validates the file it writes (`scripts/validate-intent`) | savepoint `What` line; intent listed in INDEX `## Active` |
101
99
  | Why | `spec.md` | owner writes refresh the lease (lock file mtime heartbeat) | savepoint `Why started`, `Why spec.md created` |
102
100
  | How | `plan.md`, `actions/ACTION_N.md` (at least one), `checklist.md` | heartbeat on writes | savepoint `How started`, `How plan.md created`, `How checklist.md created`, `Exec started` |
103
101
  | Exec | code on the intent branch, checklist checked off | heartbeat; code edits confined to the provisioned worktree; delegates write under the owner's lock | checklist boxes; savepoint milestones; the day-ledger line promotes when a project file lands |
104
- | 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 pointer is purge-eligible, and the QMD reindex runs LAST (after purge); `end-intent` backfills a placeholder `outcome.md` from the record and its structure check reports (never refuses) | the savepoint's terminal `delivered` (or `abandoned`) line; takeover audits, if any, remain in savepoint.md |
102
+ | 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`, and the QMD reindex runs LAST; `end-intent` backfills a placeholder `outcome.md` from the record and its structure check reports (never refuses) | the savepoint's terminal `delivered` (or `abandoned`) line; takeover audits, if any, remain in savepoint.md |
105
103
  | Maintenance (Future, Terminal, or Active-with-a-stale-or-no-lock) | `revisions.md` move-and-record entries | detects (never acquires) `delivery.lock`; defers and reports while the target's lock is FRESH (`Lock.fresh?`); a stale or absent lock is not-active, maintenance proceeds | append-only, rule-tagged `revisions.md` entry written in the same operation as the change, or the change is refused; lands via a fresh branch off store main merged back as one closed op, never `git add -A` |
106
104
 
107
105
  ## The write guard is not residue
@@ -54,8 +54,8 @@ the 15 observable signals and the response each one selects.
54
54
  item.
55
55
  - Only a prompt that changes something on disk or produces an artifact becomes a checklist item.
56
56
  A pure question is answered inline and recorded nowhere.
57
- - Direct work records into the day ledger that the per-session pointer names. Assume the pointer
58
- exists. Never write it.
57
+ - Direct work records into the day ledger for the session's day (the oldest day in the last
58
+ seven whose checklist carries the session's line, else today). Never write that lookup by hand.
59
59
 
60
60
  ## 5. What direct does not take
61
61
 
@@ -268,7 +268,7 @@ the former locking skill here):
268
268
  | Verb | What it does | When |
269
269
  |---|---|---|
270
270
  | `who` | Owner, heartbeat, claims, delegates, from durable files only | Safe inspection; needs `--intent-dir` |
271
- | `status` | Lock file, freshness, the derived worktree, whether this session's pointer names the intent, claims | Always safe; run first |
271
+ | `status` | Lock file, freshness, the derived worktree, whether this session is delivering the intent, claims | Always safe; run first |
272
272
  | `fix` | Idempotent repair from disk truth for this session; never touches a fresh foreign lock | Interrupted work, corrupt state, `/tmp` wiped |
273
273
  | `release` | The owner clears the lock | Ending or abandoning an auto delivery |
274
274
  | `reclaim` | Explicit takeover of a stale lock; appends an audit line to `savepoint.md` | The owner is gone and the lease expired |
@@ -26,7 +26,7 @@ touching the worktree above.
26
26
 
27
27
  `ruby scripts/runner step <intent_dir>` computes which nodes in `graph.md`/`nodes/*.md` are
28
28
  ready, applies dispatch policy (model, call cap), and prints a spawn block per dispatched node
29
- - agent, model, packet path, the one test command, the call cap - fenced in its own stdout.
29
+ - agent, model, node input path, the one test command, the call cap - fenced in its own stdout.
30
30
  The runner itself never spawns an agent (327 D42). Call `step` again after each dispatched
31
31
  node returns.
32
32
 
@@ -293,11 +293,11 @@ branch, then removes the worktree, and prunes the repo:
293
293
 
294
294
  ```bash
295
295
  ruby -r ~/.plastic/scripts/lib/worktree -r ~/.plastic/scripts/lib/arm -e \
296
- 'Worktree.finish(Arm.bridge_hash(intent_dir: "<STORE>/<dir>"), merge: true)'
296
+ 'Worktree.finish(Arm.delivery(intent_dir: "<STORE>/<dir>"), merge: true)'
297
297
  ```
298
298
 
299
299
  (The worktree block is derived from `projects.yml` and the intent id, so the one-liner needs
300
- only the intent directory; the `/tmp` bridge it once discovered was removed in 2.0, intent 307.)
300
+ only the intent directory.)
301
301
 
302
302
  Honor the worktree-cleanup rule: never leave an orphaned worktree, and run `git worktree
303
303
  prune` in the affected repo if you hit a stale reference. For why this is the one place the
@@ -13,7 +13,7 @@ for why the intent's worktree is merged before removal.
13
13
  ## Worktree merge-then-remove rationale
14
14
 
15
15
  **Worktree-isolated intents (intent 73c3).** When the intent was delivered in a Plastic
16
- worktree (the bridge has a provisioned `worktree` block), its code lives on the branch
16
+ worktree (the delivery lock has a provisioned `worktree` block), its code lives on the branch
17
17
  `plastic/{id}--{slug}` inside `<repo>/.claude/worktrees/{id}--{slug}`, not on a hand-made
18
18
  feature branch. The merge-then-remove of that worktree is handled together with cleanup in
19
19
  Workflow step 9, which merges `plastic/{id}--{slug}` into the default branch BEFORE removing the
@@ -29,7 +29,7 @@ Work rides the beta-verified lane when it changes operational substrate, carries
29
29
  migration, lock, or state-format risk, or cannot be fully validated by a hermetic suite alone.
30
30
  Everything else merges straight to main.
31
31
 
32
- Intent 41's DB layer is the archetypal beta-lane case: it replaces the bridge, lock, and session
32
+ Intent 41's DB layer is the archetypal beta-lane case: it replaces the lock and session
33
33
  file formats with a new persistent SQLite substrate. A green suite proves the code correct; it
34
34
  cannot prove the new substrate survives real, uncontrolled usage, so real-use verification on
35
35
  beta comes first.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Active
4
4
  <!-- Line shape: `- [ID — Title](path)`. Writes emit the em-dash (U+2014) between
5
- id and title; readers (Bridge.index_entry_match) accept the em-dash or a plain
5
+ id and title; readers (IndexEntry.match) accept the em-dash or a plain
6
6
  hyphen, so either form parses (see https://github.com/zalom/plastic/blob/main/docs/internals.md). -->
7
7
  (no active intents)
8
8
 
@@ -8,5 +8,5 @@ release:
8
8
  # mode: direct # direct | pull_request
9
9
  # base: main # defaults to origin/HEAD, then main, then master
10
10
  # branch_template: "session/{{day}}" # {{day}}, {{ticket}}, {{slug}} tokens
11
- # ticket_source: intent_id # intent_id (the session pointer's intent id, else the day id)
11
+ # ticket_source: intent_id # intent_id (the delivering intent's id from its delivery lock, else the day id)
12
12
  # workspace: checkout # checkout | worktree (worktree applies to direct mode only)
@@ -1,116 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # encoding: UTF-8
3
-
4
- require "yaml"
5
- require_relative "lock"
6
-
7
- # Bridge - the shared helpers that outlived the /tmp bridge JSON.
8
- #
9
- # Until 2.0 this file cached a session's delivery state in
10
- # `<tmp>/plastic-<session>--<id>.json` (stage, worktree, lock, auto flag) and
11
- # arbitrated which bridge a gate hook should read. Ruling 6 of intent 296
12
- # retired that: the per-session pointer (`~/.plastic/store/.tmp/<session>/current`)
13
- # plus `delivery.lock` in the intent directory is the whole bridge, and
14
- # `scripts/lib/arm.rb` is how a team takes and gives back an intent (intent
15
- # 307). What stays here is the handful of pure helpers a dozen callers still
16
- # share: the INDEX entry matcher, the Active check, the id-from-dir parser,
17
- # the project config reader, `blank?`, and the skill-reference delegator.
18
- module Bridge
19
- # Single place a skill-reference string gets built (intent 201, D3). The
20
- # prefix table lives on Lock; this is a thin delegator so call sites read
21
- # Bridge.skill_ref.
22
- def self.skill_ref(name, harness: :claude)
23
- Lock.skill_ref(name, harness: harness)
24
- end
25
-
26
- def self.blank?(value)
27
- value.nil? || value.to_s.strip.empty?
28
- end
29
-
30
- # --- Shared INDEX entry matcher (intent 188, D12/D13) -----------------------
31
- #
32
- # ONE definition site for the "- [ID <sep> Title](link)" shape both
33
- # `intent_active?` (below) and `scripts/end-intent`'s own INDEX-move parser
34
- # depend on, so the two regexes can never drift apart. Accepts a real em dash
35
- # (U+2014) OR a plain hyphen as the id/title separator on READ; every WRITE
36
- # still emits the real em dash (D10). The separator is built from the
37
- # codepoint, not a literal byte, so this file stays em-dash free.
38
- EM_DASH = "\u2014".freeze
39
- INDEX_ENTRY_RE = /\A- \[(\S+)\s+(?:#{Regexp.escape(EM_DASH)}|-)\s+(.*?)\]\(([^)]+)\)/.freeze
40
-
41
- # Match `line` (already chomped) against the shared INDEX entry shape.
42
- # Returns a MatchData (captures: 1 = id, 2 = title, 3 = link) or nil.
43
- def self.index_entry_match(line)
44
- line.to_s.match(INDEX_ENTRY_RE)
45
- end
46
-
47
- # True iff the intent is Active in its store's INDEX.md, which lives at the
48
- # PARENT of the store/ dir. Non-raising: any failure (missing or unreadable
49
- # INDEX, bad arg) returns false. `index_active_ids` is a pure-data test
50
- # seam: when an Array of id strings is supplied, membership is checked
51
- # against it directly with no file read.
52
- def self.intent_active?(intent_id, store:, index_active_ids: nil)
53
- target = intent_id.to_s
54
- return index_active_ids.include?(target) if index_active_ids.is_a?(Array)
55
-
56
- index = File.join(File.dirname(store.to_s), "INDEX.md")
57
- return false unless File.exist?(index)
58
-
59
- in_active = false
60
- File.foreach(index) do |line|
61
- stripped = line.chomp
62
- if stripped == "## Active"
63
- in_active = true
64
- next
65
- end
66
- next unless in_active
67
- break if stripped.start_with?("## ") # next section ends the Active block
68
- m = index_entry_match(stripped)
69
- return true if m && m[1] == target
70
- end
71
- false
72
- rescue StandardError
73
- false
74
- end
75
-
76
- # --- Project config -------------------------------------------------------------
77
-
78
- PROJECT_CONFIG_DEFAULTS = {
79
- "governing_docs" => ["AGENTS.md"],
80
- "release" => {
81
- "on_complete" => "commit",
82
- },
83
- }.freeze
84
-
85
- def self.read_project_config(slug)
86
- path = File.join(Dir.home, ".plastic", "projects", slug, "project.yml")
87
- config = if File.exist?(path)
88
- YAML.safe_load(File.read(path)) || {}
89
- else
90
- {}
91
- end
92
-
93
- deep_merge(PROJECT_CONFIG_DEFAULTS, config)
94
- rescue => e
95
- $stderr.puts "Warning: failed to read project config for #{slug}: #{e.message}"
96
- PROJECT_CONFIG_DEFAULTS.dup
97
- end
98
-
99
- # "<id>" from a ".../store/<id>--<slug>" dir, else nil.
100
- def self.intent_id_from_dir(dir)
101
- base = File.basename(dir.to_s)
102
- base.include?("--") ? base.split("--", 2).first : nil
103
- end
104
-
105
- def self.deep_merge(base, overlay)
106
- result = base.dup
107
- overlay.each do |key, value|
108
- if value.is_a?(Hash) && result[key].is_a?(Hash)
109
- result[key] = deep_merge(result[key], value)
110
- else
111
- result[key] = value
112
- end
113
- end
114
- result
115
- end
116
- end