@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.
- package/agents/plastic-brainstorming.md +2 -0
- package/agents/plastic-executor.md +2 -0
- package/agents/plastic-planner.md +2 -0
- package/agents/plastic-spec-specialist.md +2 -0
- package/package.json +1 -1
- package/scripts/hook-gate-check +19 -0
- package/scripts/lib/installer_core.rb +1 -0
- package/scripts/spawn-preamble +121 -0
- package/skills/auto/SKILL.md +2 -0
- package/skills/auto/references/agent-architecture.md +13 -0
|
@@ -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
package/scripts/hook-gate-check
CHANGED
|
@@ -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")
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -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.
|