@zalom/plastic 1.0.0-alpha.13 → 1.0.0-alpha.15
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 +15 -0
- package/hooks/auto-arm +5 -0
- package/hooks/code-gate +10 -0
- package/hooks/hooks.json +22 -0
- package/package.json +1 -1
- package/scripts/hook-auto-arm +52 -0
- package/scripts/hook-code-gate +39 -0
- package/scripts/install.rb +9 -0
- package/scripts/lib/bridge.rb +56 -0
- package/skills/auto/SKILL.md +24 -1
- package/skills/auto/evals/evals.json +92 -0
- package/skills/creating-intent/SKILL.md +10 -0
package/PLASTIC.md
CHANGED
|
@@ -18,9 +18,15 @@ store/
|
|
|
18
18
|
checklist.md # optional — execution registry (How deliverable)
|
|
19
19
|
outcome.md # optional — detailed result (Exec deliverable)
|
|
20
20
|
actions/ # optional — individual work items
|
|
21
|
+
resources/ # optional — research, references, screenshots, diagrams
|
|
21
22
|
savepoint.md # optional — session state for resume
|
|
22
23
|
```
|
|
23
24
|
|
|
25
|
+
Lifecycle files (`spec.md`, `plan.md`, `checklist.md`, `outcome.md`) have defined
|
|
26
|
+
roles. Supporting artifacts that aren't lifecycle deliverables — research reports,
|
|
27
|
+
reference docs, external API snapshots, screenshots, diagrams — go in `resources/`.
|
|
28
|
+
Name files inside as `{type}--{description}.md` (e.g., `deep-research--gsd-core.md`).
|
|
29
|
+
|
|
24
30
|
## Frontmatter
|
|
25
31
|
|
|
26
32
|
Identity and knowledge graph only. Nothing operational.
|
|
@@ -115,6 +121,15 @@ Format: `ID--three-to-five-words` (all stores).
|
|
|
115
121
|
- Intent file matches directory: `1a1--slug/1a1--slug.md`
|
|
116
122
|
- Next ID: `"${CLAUDE_PLUGIN_ROOT}/scripts/folgezettel-id" <parent_id> <store_path>`
|
|
117
123
|
|
|
124
|
+
**Branch vs root — the semantic decision.** The numbering is mechanics; choosing
|
|
125
|
+
*whether* to branch is meaning:
|
|
126
|
+
|
|
127
|
+
- **Branch (`14a`, `14b`)** — a sub-task, refinement, or direct continuation of the
|
|
128
|
+
parent. It cannot stand on its own; it only makes sense as part of the parent's work.
|
|
129
|
+
- **Root (`15`, `16`)** — an independent thought, even if inspired by another intent.
|
|
130
|
+
Record provenance with `sources: ["14"]`, not by branching.
|
|
131
|
+
- **Rule of thumb:** if the intent could exist without its parent, it's a root.
|
|
132
|
+
|
|
118
133
|
## INDEX.md
|
|
119
134
|
|
|
120
135
|
A Zettelkasten structure note, not a table of contents. Clusters by meaning.
|
package/hooks/auto-arm
ADDED
package/hooks/code-gate
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
INPUT=$(cat)
|
|
3
|
+
FILE_PATH=$(echo "$INPUT" | ruby -rjson -e 'data = JSON.parse(STDIN.read); puts data.dig("tool_params", "file_path") || data.dig("tool_input", "file_path") || ""' 2>/dev/null)
|
|
4
|
+
|
|
5
|
+
if [ -z "$FILE_PATH" ]; then
|
|
6
|
+
exit 0
|
|
7
|
+
fi
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
ruby "$SCRIPT_DIR/../scripts/hook-code-gate" "$FILE_PATH"
|
package/hooks/hooks.json
CHANGED
|
@@ -29,6 +29,18 @@
|
|
|
29
29
|
]
|
|
30
30
|
}
|
|
31
31
|
],
|
|
32
|
+
"PreToolUse": [
|
|
33
|
+
{
|
|
34
|
+
"matcher": "Write|Edit|NotebookEdit",
|
|
35
|
+
"hooks": [
|
|
36
|
+
{
|
|
37
|
+
"type": "command",
|
|
38
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/run-hook\" code-gate",
|
|
39
|
+
"statusMessage": "Checking lifecycle gate..."
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
],
|
|
32
44
|
"PostToolUse": [
|
|
33
45
|
{
|
|
34
46
|
"matcher": "Write|Edit",
|
|
@@ -61,6 +73,16 @@
|
|
|
61
73
|
"statusMessage": "Checking future intents..."
|
|
62
74
|
}
|
|
63
75
|
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"matcher": "",
|
|
79
|
+
"hooks": [
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "\"${CLAUDE_PLUGIN_ROOT}/hooks/auto-arm\"",
|
|
83
|
+
"statusMessage": "Checking auto mode..."
|
|
84
|
+
}
|
|
85
|
+
]
|
|
64
86
|
}
|
|
65
87
|
]
|
|
66
88
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Usage: hook-auto-arm <message>
|
|
6
|
+
# UserPromptSubmit arming (intent 27). Two jobs, both additive context — never blocks:
|
|
7
|
+
# 1. Detect auto-trigger phrases -> steer the agent to invoke plastic:auto.
|
|
8
|
+
# 2. If auto mode is armed and the active intent hasn't reached How -> remind the agent
|
|
9
|
+
# that project-code edits are gate-blocked until plan.md + checklist.md exist.
|
|
10
|
+
# Outputs UserPromptSubmit hookSpecificOutput JSON, or exits silently.
|
|
11
|
+
|
|
12
|
+
require "json"
|
|
13
|
+
require_relative "lib/bridge"
|
|
14
|
+
|
|
15
|
+
message = (ARGV[0] || "").downcase
|
|
16
|
+
parts = []
|
|
17
|
+
|
|
18
|
+
# --- 1. Auto-trigger steering ---
|
|
19
|
+
AUTO_TRIGGERS = ["take it from here", "deliver this", "/plastic:auto", "auto mode"].freeze
|
|
20
|
+
# bare "auto" only as a standalone word to avoid matching "automatic", "automation", etc.
|
|
21
|
+
triggered = AUTO_TRIGGERS.any? { |p| message.include?(p) } || message.match?(/\bauto\b/)
|
|
22
|
+
if triggered
|
|
23
|
+
parts << "Auto-mode request detected. Invoke the plastic:auto skill to deliver the " \
|
|
24
|
+
"active intent autonomously — it arms the lifecycle gate so project code " \
|
|
25
|
+
"cannot be edited before plan.md + checklist.md exist."
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# --- 2. Standing gate reminder when armed and pre-How ---
|
|
29
|
+
session = ENV["CLAUDE_SESSION_ID"]
|
|
30
|
+
bridge_data = (session && !session.empty? && File.exist?(Bridge.path(session))) ? Bridge.read(session) : nil
|
|
31
|
+
if bridge_data && bridge_data.dig("build", "auto") == true
|
|
32
|
+
intent = bridge_data["intent"] || {}
|
|
33
|
+
store = intent["store"]; dir = intent["dir"]
|
|
34
|
+
if store && dir
|
|
35
|
+
intent_dir = File.expand_path("#{store}/#{dir}")
|
|
36
|
+
reached_how = File.exist?("#{intent_dir}/plan.md") && File.exist?("#{intent_dir}/checklist.md")
|
|
37
|
+
unless reached_how
|
|
38
|
+
parts << "Auto mode armed on intent #{intent["id"]} — produce spec -> plan -> " \
|
|
39
|
+
"checklist before editing project code. Code edits are gate-blocked until then."
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
exit 0 if parts.empty?
|
|
45
|
+
|
|
46
|
+
payload = {
|
|
47
|
+
"hookSpecificOutput" => {
|
|
48
|
+
"hookEventName" => "UserPromptSubmit",
|
|
49
|
+
"additionalContext" => parts.join("\n")
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
puts JSON.generate(payload)
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# encoding: UTF-8
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
# Usage: hook-code-gate <file_path>
|
|
6
|
+
# PreToolUse gate (intent 27): when auto mode is armed and the active intent has not
|
|
7
|
+
# reached How (plan.md + checklist.md), block edits to project code outside the store.
|
|
8
|
+
#
|
|
9
|
+
# Exit 0 = allow. Exit 2 = block (reason on stderr, shown to the agent).
|
|
10
|
+
# No bridge / auto not armed / How reached / path under ~/.plastic or the intent dir = allow.
|
|
11
|
+
|
|
12
|
+
require_relative "lib/bridge"
|
|
13
|
+
|
|
14
|
+
file_path = ARGV[0]
|
|
15
|
+
exit 0 unless file_path && !file_path.empty?
|
|
16
|
+
|
|
17
|
+
# --- Load bridge (mirror hook-gate-check resolution) ---
|
|
18
|
+
session = ENV["CLAUDE_SESSION_ID"]
|
|
19
|
+
bridge_data = nil
|
|
20
|
+
|
|
21
|
+
if session && !session.empty?
|
|
22
|
+
bridge_data = Bridge.read(session) if File.exist?(Bridge.path(session))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
unless bridge_data
|
|
26
|
+
candidates = Dir.glob("/tmp/plastic-*.json").reject { |f| f.end_with?(".tmp") }
|
|
27
|
+
if candidates.any?
|
|
28
|
+
newest = candidates.max_by { |f| File.mtime(f) }
|
|
29
|
+
bridge_data = JSON.parse(File.read(newest)) rescue nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
exit 0 unless bridge_data
|
|
34
|
+
|
|
35
|
+
reason = Bridge.code_gate_decision(bridge_data, file_path)
|
|
36
|
+
exit 0 unless reason
|
|
37
|
+
|
|
38
|
+
$stderr.puts "PLASTIC GATE — #{reason}"
|
|
39
|
+
exit 2
|
package/scripts/install.rb
CHANGED
|
@@ -164,6 +164,8 @@ def distribute(mode)
|
|
|
164
164
|
"scripts/hook-continue" => "scripts/hook-continue",
|
|
165
165
|
"scripts/hook-future-intent-check" => "scripts/hook-future-intent-check",
|
|
166
166
|
"scripts/hook-gate-check" => "scripts/hook-gate-check",
|
|
167
|
+
"scripts/hook-code-gate" => "scripts/hook-code-gate",
|
|
168
|
+
"scripts/hook-auto-arm" => "scripts/hook-auto-arm",
|
|
167
169
|
"scripts/lib/bridge.rb" => "scripts/lib/bridge.rb",
|
|
168
170
|
"scripts/doctor.rb" => "scripts/doctor.rb",
|
|
169
171
|
}
|
|
@@ -441,6 +443,12 @@ def merge_claude_hooks(settings_path)
|
|
|
441
443
|
{ "type" => "command", "command" => "#{hook_dir}/plastic-savepoint", "statusMessage" => "Saving Plastic intent state..." },
|
|
442
444
|
],
|
|
443
445
|
},
|
|
446
|
+
"PreToolUse" => {
|
|
447
|
+
"matcher" => "Write|Edit|NotebookEdit",
|
|
448
|
+
"hooks" => [
|
|
449
|
+
{ "type" => "command", "command" => "#{hook_dir}/plastic-code-gate", "statusMessage" => "Checking lifecycle gate..." },
|
|
450
|
+
],
|
|
451
|
+
},
|
|
444
452
|
"PostToolUse" => {
|
|
445
453
|
"matcher" => "Write|Edit",
|
|
446
454
|
"hooks" => [
|
|
@@ -452,6 +460,7 @@ def merge_claude_hooks(settings_path)
|
|
|
452
460
|
"hooks" => [
|
|
453
461
|
{ "type" => "command", "command" => "#{hook_dir}/plastic-continue", "statusMessage" => "Checking for continue..." },
|
|
454
462
|
{ "type" => "command", "command" => "#{hook_dir}/plastic-future-intent-check", "statusMessage" => "Checking future intents..." },
|
|
463
|
+
{ "type" => "command", "command" => "#{hook_dir}/plastic-auto-arm", "statusMessage" => "Checking auto mode..." },
|
|
455
464
|
],
|
|
456
465
|
},
|
|
457
466
|
}
|
package/scripts/lib/bridge.rb
CHANGED
|
@@ -88,6 +88,7 @@ module Bridge
|
|
|
88
88
|
"has" => has,
|
|
89
89
|
"missing" => missing,
|
|
90
90
|
"gate_failures" => 0,
|
|
91
|
+
"auto" => false,
|
|
91
92
|
"last_activity" => Time.now.utc.iso8601
|
|
92
93
|
},
|
|
93
94
|
"observe" => {
|
|
@@ -159,6 +160,61 @@ module Bridge
|
|
|
159
160
|
PROJECT_CONFIG_DEFAULTS.dup
|
|
160
161
|
end
|
|
161
162
|
|
|
163
|
+
# --- Auto mode (intent 27) ---
|
|
164
|
+
|
|
165
|
+
# Arm auto mode for a session+intent. Works even when no bridge exists yet
|
|
166
|
+
# (mid-session intent creation). Re-derives intent state, then sets build.auto.
|
|
167
|
+
def self.arm_auto(session, intent_id:, intent_dir:, store:, name:)
|
|
168
|
+
data = derive(session, intent_id: intent_id, intent_dir: intent_dir, store: store, name: name)
|
|
169
|
+
data["build"]["auto"] = true
|
|
170
|
+
write(session, data)
|
|
171
|
+
data
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Disarm auto mode. No-op if no bridge exists for the session.
|
|
175
|
+
def self.disarm_auto(session)
|
|
176
|
+
data = read(session)
|
|
177
|
+
return nil unless data
|
|
178
|
+
data["build"] ||= {}
|
|
179
|
+
data["build"]["auto"] = false
|
|
180
|
+
write(session, data)
|
|
181
|
+
data
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Decide whether a code edit should be blocked while auto mode is armed.
|
|
185
|
+
# Returns a reason string to BLOCK, or nil to ALLOW.
|
|
186
|
+
#
|
|
187
|
+
# Blocks iff: auto armed AND intent hasn't reached How (stage what/why) AND the
|
|
188
|
+
# target is project code — i.e. NOT under ~/.plastic and NOT inside the intent dir.
|
|
189
|
+
def self.code_gate_decision(bridge_data, file_path, home: Dir.home)
|
|
190
|
+
return nil unless bridge_data.is_a?(Hash)
|
|
191
|
+
build = bridge_data["build"] || {}
|
|
192
|
+
return nil unless build["auto"] == true
|
|
193
|
+
|
|
194
|
+
intent_info = bridge_data["intent"] || {}
|
|
195
|
+
store = intent_info["store"]
|
|
196
|
+
dir = intent_info["dir"]
|
|
197
|
+
return nil unless store && dir
|
|
198
|
+
intent_dir_abs = File.expand_path("#{store}/#{dir}")
|
|
199
|
+
|
|
200
|
+
# "How reached" = the plan triplet exists. Gate by artifact presence, not the
|
|
201
|
+
# stage label (derive_stage returns "how" as soon as spec.md exists, before any
|
|
202
|
+
# plan). Code edits stay blocked until plan.md + checklist.md are both present.
|
|
203
|
+
reached_how = File.exist?("#{intent_dir_abs}/plan.md") &&
|
|
204
|
+
File.exist?("#{intent_dir_abs}/checklist.md")
|
|
205
|
+
return nil if reached_how
|
|
206
|
+
|
|
207
|
+
file_abs = File.expand_path(file_path.to_s)
|
|
208
|
+
plastic_home = File.expand_path(File.join(home, ".plastic"))
|
|
209
|
+
return nil if file_abs == plastic_home || file_abs.start_with?("#{plastic_home}/")
|
|
210
|
+
return nil if file_abs == intent_dir_abs || file_abs.start_with?("#{intent_dir_abs}/")
|
|
211
|
+
|
|
212
|
+
id = intent_info["id"]
|
|
213
|
+
"intent #{id} has not reached How — write plan.md + checklist.md before " \
|
|
214
|
+
"editing project code. Run plastic:auto or plastic:writing-plans first. " \
|
|
215
|
+
"(blocked edit: #{file_abs})"
|
|
216
|
+
end
|
|
217
|
+
|
|
162
218
|
def self.deep_merge(base, overlay)
|
|
163
219
|
result = base.dup
|
|
164
220
|
overlay.each do |key, value|
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -16,6 +16,25 @@ An active intent MUST exist in INDEX.md. If none exists, refuse: "No active inte
|
|
|
16
16
|
|
|
17
17
|
If multiple active intents exist, ask the user which one to deliver (this is the only question auto asks).
|
|
18
18
|
|
|
19
|
+
## Arm the Lifecycle Gate (do this FIRST)
|
|
20
|
+
|
|
21
|
+
Immediately after selecting the intent — before any other work — arm auto mode. This
|
|
22
|
+
writes the session bridge that makes the code-edit gate live, so project code cannot be
|
|
23
|
+
edited before the plan exists (the gate applies to YOU, the orchestrator):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
27
|
+
'Bridge.arm_auto(ENV["CLAUDE_SESSION_ID"], intent_id: "<ID>", intent_dir: "<STORE>/<dir>", store: "<STORE>", name: "<name>")'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Replace `<ID>`, `<STORE>` (e.g. `~/.plastic/projects/<slug>/store` or `~/.plastic/store`),
|
|
31
|
+
`<dir>` (the `ID--slug` directory), and `<name>`. If `CLAUDE_SESSION_ID` is unset, arming
|
|
32
|
+
is best-effort — proceed regardless; the gate simply won't engage.
|
|
33
|
+
|
|
34
|
+
**Hard rule for the rest of this run:** do NOT edit project code (anything outside the
|
|
35
|
+
intent directory / `~/.plastic/`) until `plan.md` AND `checklist.md` exist for the intent.
|
|
36
|
+
Honor the cycle: What → Why (spec.md) → How (plan.md + actions/ + checklist.md) → Exec.
|
|
37
|
+
|
|
19
38
|
## Flags
|
|
20
39
|
|
|
21
40
|
- `--skip-permissions` — bypass hard stops on destructive actions on existing projects. Full trust mode. Default: off.
|
|
@@ -130,7 +149,11 @@ During initial project creation, all decisions are non-destructive by definition
|
|
|
130
149
|
- Update `chain` in the current intent's frontmatter
|
|
131
150
|
6. Move intent from `## Active` to `## Completed` in INDEX.md (with today's date)
|
|
132
151
|
7. Auto-commit: `cd <store-root> && git add . && git commit -m "feat: deliver intent <ID> — <name>"`
|
|
133
|
-
8.
|
|
152
|
+
8. Disarm the lifecycle gate (auto delivery is finished):
|
|
153
|
+
```bash
|
|
154
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e 'Bridge.disarm_auto(ENV["CLAUDE_SESSION_ID"])'
|
|
155
|
+
```
|
|
156
|
+
9. Notify user: "Intent [ID] — [name] delivered. [1-2 sentence summary]. See outcome.md for details."
|
|
134
157
|
|
|
135
158
|
## Error Handling
|
|
136
159
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "plastic:auto",
|
|
3
|
+
"notes": "Intent 27. Scopes: description triggering (1-8) and behavior/output quality (9). Assertions written after observing Step-3 runs (one clean subagent router per case).",
|
|
4
|
+
"results": {
|
|
5
|
+
"triggering": { "cases": 8, "passed": 8, "pass_at_1": 1.0, "run": "2026-06-10, one subagent per case" },
|
|
6
|
+
"behavior": { "cases": 1, "passed": 1, "evidence": "dogfood: intent 27 itself delivered via auto produced spec->plan->checklist before any code edit; code-gate unit test proves pre-How project-code edits are blocked" }
|
|
7
|
+
},
|
|
8
|
+
"evals": [
|
|
9
|
+
{
|
|
10
|
+
"id": 1, "scope": "triggering", "set": "train",
|
|
11
|
+
"prompt": "auto",
|
|
12
|
+
"expected_output": "Activates plastic:auto (the bare 'auto' keyword is the documented trigger).",
|
|
13
|
+
"files": [],
|
|
14
|
+
"assertions": [
|
|
15
|
+
{ "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": 2, "scope": "triggering", "set": "train",
|
|
20
|
+
"prompt": "take it from here and deliver intent 27 end to end",
|
|
21
|
+
"expected_output": "Activates plastic:auto (autonomous delivery of an active intent).",
|
|
22
|
+
"files": [],
|
|
23
|
+
"assertions": [
|
|
24
|
+
{ "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": 3, "scope": "triggering", "set": "validation",
|
|
29
|
+
"prompt": "go fully autonomous on the active intent, don't ask me questions",
|
|
30
|
+
"expected_output": "Activates plastic:auto.",
|
|
31
|
+
"files": [],
|
|
32
|
+
"assertions": [
|
|
33
|
+
{ "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"id": 4, "scope": "triggering", "set": "train",
|
|
38
|
+
"prompt": "deliver this intent for me",
|
|
39
|
+
"expected_output": "Activates plastic:auto.",
|
|
40
|
+
"files": [],
|
|
41
|
+
"assertions": [
|
|
42
|
+
{ "type": "code", "check": "router CHOICE == plastic:auto", "observed": "plastic:auto", "result": "pass" }
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": 5, "scope": "triggering", "set": "train",
|
|
47
|
+
"prompt": "set up a hook to automatically format the file on every save",
|
|
48
|
+
"expected_output": "Does NOT activate plastic:auto. Near-miss: shares 'auto*' but is a settings/hooks task (update-config).",
|
|
49
|
+
"files": [],
|
|
50
|
+
"assertions": [
|
|
51
|
+
{ "type": "code", "check": "router CHOICE != plastic:auto", "observed": "update-config", "result": "pass" }
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": 6, "scope": "triggering", "set": "validation",
|
|
56
|
+
"prompt": "deliver the built package to the dist directory",
|
|
57
|
+
"expected_output": "Does NOT activate plastic:auto. Near-miss: shares 'deliver' but is a build/file task.",
|
|
58
|
+
"files": [],
|
|
59
|
+
"assertions": [
|
|
60
|
+
{ "type": "code", "check": "router CHOICE != plastic:auto", "observed": "none", "result": "pass" }
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": 7, "scope": "triggering", "set": "train",
|
|
65
|
+
"prompt": "create a new intent for the dashboard idea",
|
|
66
|
+
"expected_output": "Does NOT activate plastic:auto; activates plastic:creating-intent.",
|
|
67
|
+
"files": [],
|
|
68
|
+
"assertions": [
|
|
69
|
+
{ "type": "code", "check": "router CHOICE != plastic:auto", "observed": "plastic:creating-intent", "result": "pass" }
|
|
70
|
+
]
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": 8, "scope": "triggering", "set": "validation",
|
|
74
|
+
"prompt": "what's the status of my active intents?",
|
|
75
|
+
"expected_output": "Does NOT activate plastic:auto; this is a read/continuing/managing-index query.",
|
|
76
|
+
"files": [],
|
|
77
|
+
"assertions": [
|
|
78
|
+
{ "type": "code", "check": "router CHOICE != plastic:auto", "observed": "plastic:managing-index", "result": "pass" }
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": 9, "scope": "behavior", "set": "train",
|
|
83
|
+
"prompt": "Active intent X exists with only a '## Intent' section. Deliver it in auto mode.",
|
|
84
|
+
"expected_output": "Arms the lifecycle gate first, then produces spec.md (Why), then plan.md + actions/ + checklist.md (How), and edits NO project code before plan.md + checklist.md exist. Disarms on completion.",
|
|
85
|
+
"files": [],
|
|
86
|
+
"assertions": [
|
|
87
|
+
{ "type": "human", "check": "spec.md written before plan.md before any project-code edit", "observed": "dogfood run of intent 27 followed this order", "result": "pass" },
|
|
88
|
+
{ "type": "code", "check": "code-gate blocks project-code Edit/Write while pre-How (test/code_gate_test.rb)", "observed": "test green", "result": "pass" }
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
}
|
|
@@ -60,6 +60,16 @@ IDs are scoped to the store they live in. Use the correct store path:
|
|
|
60
60
|
"${CLAUDE_PLUGIN_ROOT}/scripts/folgezettel-id" "<STORE>" "<parent_id>"
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
**Branch vs root — decide before assigning the ID.** Having a "parent" in mind does
|
|
64
|
+
NOT automatically mean branch. Choose by meaning:
|
|
65
|
+
|
|
66
|
+
- **Branch (`14a`, `14b`)** — a sub-task, refinement, or direct continuation. It only
|
|
67
|
+
makes sense as part of the parent's work.
|
|
68
|
+
- **Root (`15`, `16`)** — an independent thought, even if inspired by another intent.
|
|
69
|
+
Capture the inspiration in `sources` (e.g., `sources: ["14"]`), not in the ID.
|
|
70
|
+
- **Rule of thumb:** if the intent could exist without its parent, make it a root and
|
|
71
|
+
set `sources`. Only branch when it genuinely cannot stand alone.
|
|
72
|
+
|
|
63
73
|
### 3. Determine Intent Properties
|
|
64
74
|
|
|
65
75
|
Ask or infer from context:
|