@zalom/plastic 1.0.0-alpha.34 → 1.0.0-alpha.35

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.
@@ -12,6 +12,8 @@ model: inherit
12
12
 
13
13
  You are the Plastic Brainstorming specialist. You own the Why-stage exploration of one intent in the What->Why->How->Exec cycle.
14
14
 
15
+ When dispatched in auto mode you receive the standard Plastic spawn preamble (from `scripts/spawn-preamble`) prepended to your prompt: it states the active intent id, intent line, current stage, your role, and the instruction to emit valid lifecycle artifacts. Honor it as your live state; do not re-derive or contradict it.
16
+
15
17
  ## Your Responsibilities
16
18
 
17
19
  1. **Explore the problem** — read the intent's `## Intent` and `## Context`, the linked intents, and the relevant code
@@ -12,6 +12,8 @@ model: inherit
12
12
 
13
13
  You are the Plastic Executor. You own the Exec stage of the What->Why->How->Exec cycle.
14
14
 
15
+ When dispatched in auto mode you receive the standard Plastic spawn preamble (from `scripts/spawn-preamble`) prepended to your prompt: it states the active intent id, intent line, current stage, your role, and the instruction to emit valid lifecycle artifacts. Honor it as your live state; do not re-derive or contradict it.
16
+
15
17
  ## Your Responsibilities
16
18
 
17
19
  1. **Implement the actions** — make the code changes for each action in order
@@ -12,6 +12,8 @@ model: inherit
12
12
 
13
13
  You are the Plastic Planner. You own the How stage of the What->Why->How->Exec cycle.
14
14
 
15
+ When dispatched in auto mode you receive the standard Plastic spawn preamble (from `scripts/spawn-preamble`) prepended to your prompt: it states the active intent id, intent line, current stage, your role, and the instruction to emit valid lifecycle artifacts. Honor it as your live state; do not re-derive or contradict it.
16
+
15
17
  ## Your Responsibilities
16
18
 
17
19
  1. **Decompose the spec** — break the approach into ordered, independent actions
@@ -12,6 +12,8 @@ model: inherit
12
12
 
13
13
  You are the Plastic Spec Specialist. You own the Why-to-How boundary in the What->Why->How->Exec cycle.
14
14
 
15
+ When dispatched in auto mode you receive the standard Plastic spawn preamble (from `scripts/spawn-preamble`) prepended to your prompt: it states the active intent id, intent line, current stage, your role, and the instruction to emit valid lifecycle artifacts. Honor it as your live state; do not re-derive or contradict it.
16
+
15
17
  ## Your Responsibilities
16
18
 
17
19
  1. **Consolidate the Why** — turn the enriched `## Context` and `### Decisions` into one spec
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "1.0.0-alpha.34",
3
+ "version": "1.0.0-alpha.35",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
 
9
9
  require "json"
10
10
  require_relative "lib/bridge"
11
+ require_relative "lib/intent_validator"
11
12
 
12
13
  file_path = ARGV[0]
13
14
  exit 0 unless file_path && !file_path.empty?
@@ -28,6 +29,24 @@ if intent_dir_abs
28
29
  end
29
30
  end
30
31
 
32
+ # --- Artifact-validity backstop (intent 4a1c1) ---
33
+ # When the written file IS the intent file itself (the `<id>--<slug>.md` directly
34
+ # inside `store/<id>--<slug>/`), run IntentValidator on it. This is headless-safe:
35
+ # it depends only on the file path and disk, never on a bridge or session. In the
36
+ # PostToolUse model the write has already happened, so we cannot prevent it; the
37
+ # loud non-zero exit + stderr is the rejection signal. NOT for spec.md/plan.md/
38
+ # checklist.md/outcome.md/savepoint.md — those are validated by the stage gates.
39
+ if intent_dir_abs && File.basename(file_path_abs) == File.basename(Bridge.intent_file(intent_dir_abs))
40
+ result = IntentValidator.validate(intent_dir_abs)
41
+ unless result[:ok]
42
+ warn "PLASTIC ARTIFACT INVALID — #{File.basename(file_path_abs)} is not born complete:"
43
+ result[:missing].each { |field| warn " missing required field: #{field}" }
44
+ result[:errors].each { |error| warn " #{error}" }
45
+ warn "Fix the frontmatter; the intent is not valid until every required field is present and sources/chain are well-formed."
46
+ exit 1
47
+ end
48
+ end
49
+
31
50
  # --- Find bridge file ---
32
51
  bridge_data = Bridge.discover_bridge(session: session, cwd: Dir.pwd)
33
52
 
@@ -208,6 +208,7 @@ class InstallerCore
208
208
  "scripts/qmd-sync" => "scripts/qmd-sync",
209
209
  "scripts/lib/intent_validator.rb" => "scripts/lib/intent_validator.rb",
210
210
  "scripts/validate-intent" => "scripts/validate-intent",
211
+ "scripts/spawn-preamble" => "scripts/spawn-preamble",
211
212
  "scripts/lib/store_provisioning.rb" => "scripts/lib/store_provisioning.rb",
212
213
  "scripts/provision-project-store" => "scripts/provision-project-store",
213
214
  "scripts/lib/installer_core.rb" => "scripts/lib/installer_core.rb",
@@ -0,0 +1,121 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # spawn-preamble — deterministic live-state injection for spawned agents
6
+ # (intent 4a1c1, agent harness foundation).
7
+ #
8
+ # Emits a preamble block built ONLY from filesystem state for a single intent
9
+ # directory. It is a PURE function of the intent dir: no network, no randomness,
10
+ # no wall-clock reads. Two runs over the same on-disk state produce byte-identical
11
+ # output. This is the authoritative L2 (live-state) mechanism for harnesses whose
12
+ # spawned sub-agents do not inherit a top-level session event (see
13
+ # docs/reference/harness-adapters.md).
14
+ #
15
+ # Usage:
16
+ # spawn-preamble <intent_dir> [--role ROLE] [--step STEP]
17
+ #
18
+ # The preamble reports the active intent (id + intent line from frontmatter), the
19
+ # current lifecycle stage (last savepoint line if present, else derived from which
20
+ # lifecycle files exist), the cycle role/step (from --role/--step, else the
21
+ # derived stage), and an imperative honoring instruction.
22
+ #
23
+ # Exit codes: 0 (preamble emitted), 2 (usage).
24
+
25
+ require_relative "lib/bridge"
26
+
27
+ # Verbatim honoring instruction. Kept as one constant so the contract doc and the
28
+ # test assert against the exact same string.
29
+ HONOR_INSTRUCTION =
30
+ "You are operating inside Plastic. Use it as your operating scaffold. " \
31
+ "Emit VALID lifecycle artifacts; do not hallucinate intents or stages. " \
32
+ "Your output is a deliverable, not a message."
33
+
34
+ def parse_args(argv)
35
+ role = nil
36
+ step = nil
37
+ positional = []
38
+ i = 0
39
+ while i < argv.length
40
+ case argv[i]
41
+ when "--role"
42
+ role = argv[i + 1]
43
+ i += 2
44
+ when "--step"
45
+ step = argv[i + 1]
46
+ i += 2
47
+ else
48
+ positional << argv[i]
49
+ i += 1
50
+ end
51
+ end
52
+ [positional.first, role, step]
53
+ end
54
+
55
+ # Read the intent file frontmatter via the same parser the validator uses, so the
56
+ # id/intent we report match what the rest of Plastic sees. Returns a Hash (possibly
57
+ # empty) — never raises.
58
+ def frontmatter_for(intent_dir)
59
+ ifile = Bridge.intent_file(intent_dir)
60
+ return {} unless File.exist?(ifile)
61
+
62
+ content = File.read(ifile)
63
+ return {} unless content.start_with?("---")
64
+
65
+ parts = content.split("---", 3)
66
+ return {} if parts.length < 3
67
+
68
+ require "yaml"
69
+ require "date"
70
+ require "time"
71
+ YAML.safe_load(parts[1], permitted_classes: [Date, Time]) || {}
72
+ rescue StandardError
73
+ {}
74
+ end
75
+
76
+ # Current stage label. Prefer the last non-empty line of savepoint.md (the ledger
77
+ # already encodes the furthest-reached milestone). Fall back to the file-derived
78
+ # stage when there is no ledger.
79
+ STAGE_LABELS = {
80
+ "what" => "What", "why" => "Why", "how" => "How",
81
+ "exec" => "Exec", "done" => "Done"
82
+ }.freeze
83
+
84
+ def current_stage(intent_dir)
85
+ ledger = File.join(intent_dir, Bridge::SAVEPOINT_FILE)
86
+ if File.exist?(ledger)
87
+ last = File.read(ledger).each_line.map(&:strip).reject(&:empty?).last
88
+ return last if last
89
+ end
90
+ STAGE_LABELS.fetch(Bridge.derive_stage(intent_dir), Bridge.derive_stage(intent_dir))
91
+ end
92
+
93
+ intent_dir_arg, role, step = parse_args(ARGV)
94
+
95
+ if intent_dir_arg.nil? || intent_dir_arg.empty?
96
+ warn "usage: spawn-preamble <intent_dir> [--role ROLE] [--step STEP]"
97
+ exit 2
98
+ end
99
+
100
+ intent_dir = File.expand_path(intent_dir_arg)
101
+
102
+ fm = frontmatter_for(intent_dir)
103
+ id = fm["id"].to_s.strip
104
+ intent_name = fm["intent"].to_s.strip
105
+ id = "(unknown)" if id.empty?
106
+ intent_name = "(unknown)" if intent_name.empty?
107
+
108
+ stage = current_stage(intent_dir)
109
+ cycle = role || step || stage
110
+
111
+ lines = []
112
+ lines << "=== Plastic spawn preamble ==="
113
+ lines << "Store: #{intent_dir}"
114
+ lines << "Active intent: #{id} - #{intent_name}"
115
+ lines << "Current stage: #{stage}"
116
+ lines << "Cycle step / role: #{cycle}"
117
+ lines << ""
118
+ lines << HONOR_INSTRUCTION
119
+ lines << "=== end preamble ==="
120
+
121
+ puts lines.join("\n")
@@ -66,6 +66,8 @@ Roster (one role per cycle stage):
66
66
 
67
67
  Dispatch rule: sequential, one specialist per stage on one branch (the deliverables share files). Gate each deliverable against the stage's exit criteria before handing off. The How and Exec phases below default to Plastic's native dispatch (`plastic-executing-plan`) and delegate to the superpowers skills only when they are available or the user asks; do not restate the phase mechanics here.
68
68
 
69
+ Spawn preamble (live-state injection): before dispatching any specialist, run `scripts/spawn-preamble <intent_dir> --role <role>` and PREPEND its output to that specialist's prompt. The preamble is a deterministic, filesystem-only snapshot of the active intent (id, intent line, current stage) plus the honoring instruction, so every spawned agent boots with accurate live state instead of guessing. This is the authoritative L2 mechanism for harnesses whose sub-agents do not inherit a top-level session event (see `docs/reference/harness-adapters.md`).
70
+
69
71
  Final-gate review: dispatch an independent reviewer subagent at the final gate only, not as a standing role.
70
72
 
71
73
  Headless manual gate: when running headless or in the background, enforce gates manually and do not rely on hooks, because `CLAUDE_SESSION_ID` may be unset (this ties to the arm-gate fallback above).
@@ -48,6 +48,19 @@ The chain: intent `## Intent` / `## Context`, then enriched `## Context` plus
48
48
  `### Decisions`, then `spec.md`, then `plan.md` plus `actions/` plus `checklist.md`,
49
49
  then the code changes plus a checked-off checklist plus `## Insights`.
50
50
 
51
+ ### Spawn Preamble (L2 live-state injection)
52
+
53
+ Every dispatched specialist is booted with a spawn preamble: the enforcer runs
54
+ `scripts/spawn-preamble <intent_dir> --role <role>` and prepends its output to the
55
+ specialist's prompt. The preamble is a pure function of the intent directory on disk
56
+ (no network, no clock, no randomness), so it is deterministic and rebuildable. It
57
+ carries the active intent id and intent line, the current lifecycle stage (the last
58
+ savepoint line, else stage derived from which lifecycle files exist), the cycle
59
+ role, and the honoring instruction that the agent must emit valid lifecycle artifacts
60
+ and not hallucinate intents or stages. This is the standard L2 live-state mechanism
61
+ for harnesses whose spawned sub-agents do not inherit the top-level session event. See
62
+ `docs/reference/harness-adapters.md` for how it slots into the per-harness contract.
63
+
51
64
  ### Gate Ownership
52
65
 
53
66
  The enforcer arms and verifies the lifecycle gate, then gates every stage transition.