@zalom/plastic 1.0.0-alpha.19 → 1.0.0-alpha.20
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 +11 -2
- package/package.json +1 -1
- package/scripts/hook-gate-check +16 -1
- package/scripts/lib/bridge.rb +62 -0
- package/skills/savepoint/SKILL.md +43 -38
- package/templates/savepoint.md +14 -13
package/PLASTIC.md
CHANGED
|
@@ -19,7 +19,7 @@ store/
|
|
|
19
19
|
outcome.md # optional — detailed result (Exec deliverable)
|
|
20
20
|
actions/ # optional — individual work items
|
|
21
21
|
resources/ # optional — research, references, screenshots, diagrams
|
|
22
|
-
savepoint.md # optional —
|
|
22
|
+
savepoint.md # optional — deterministic cycle-step ledger (auto-written)
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
Lifecycle files (`spec.md`, `plan.md`, `checklist.md`, `outcome.md`) have defined
|
|
@@ -66,9 +66,18 @@ The connection: an intent's `## Insights` feeds the Coordinator's Observe phase.
|
|
|
66
66
|
| **How** | Planning | `plan.md` + `actions/` + `checklist.md` | `plastic-writing-plans` |
|
|
67
67
|
| **Exec** | Execution | `outcome.md` | `plastic-executing-plan` |
|
|
68
68
|
|
|
69
|
-
`## Insights` — append-only work log captured throughout ALL stages.
|
|
69
|
+
`## Insights` — append-only work log captured throughout ALL stages. **Append-only means
|
|
70
|
+
newest entry at the bottom; never prepend.** This ordering is a hard convention: Insights
|
|
71
|
+
are the semantic trace of an intent, and a consistent newest-last order keeps that trace
|
|
72
|
+
readable across every intent.
|
|
70
73
|
For full lifecycle detail, the skills in the Detail column have references/.
|
|
71
74
|
|
|
75
|
+
`savepoint.md` — a deterministic, append-only ledger of cycle-step milestones (one line per
|
|
76
|
+
lifecycle boundary, newest at the bottom), written automatically by the gate hook. It is
|
|
77
|
+
sugar on top of the conventions, not a source of truth: state is always derivable from
|
|
78
|
+
files-on-disk, and the ledger is rebuildable. It exists so a resuming agent reads the cycle's
|
|
79
|
+
succession at a glance (last line = where we are).
|
|
80
|
+
|
|
72
81
|
## Gotchas
|
|
73
82
|
|
|
74
83
|
- **Artifacts go in the intent directory.** Never create `docs/plans/`,
|
package/package.json
CHANGED
package/scripts/hook-gate-check
CHANGED
|
@@ -98,6 +98,14 @@ if is_stage_file || is_action_file
|
|
|
98
98
|
|
|
99
99
|
Bridge.write(session, bridge_data)
|
|
100
100
|
|
|
101
|
+
# Cycle-step savepoint ledger (intent 34) — record this stage-file milestone.
|
|
102
|
+
# Best-effort: the ledger is derived sugar, so a failure must never block.
|
|
103
|
+
begin
|
|
104
|
+
Bridge.append_savepoint(intent_dir_abs, file_path_abs)
|
|
105
|
+
rescue StandardError
|
|
106
|
+
# ignore — rebuildable from disk
|
|
107
|
+
end
|
|
108
|
+
|
|
101
109
|
# Build transition context
|
|
102
110
|
stage_labels = { "what" => "What", "why" => "Why", "how" => "How", "exec" => "Exec", "done" => "Done" }
|
|
103
111
|
next_hints = {
|
|
@@ -129,7 +137,14 @@ if is_stage_file || is_action_file
|
|
|
129
137
|
puts JSON.generate(payload)
|
|
130
138
|
exit 0
|
|
131
139
|
else
|
|
132
|
-
# Regular file in intent dir — just update last_activity
|
|
140
|
+
# Regular file in intent dir — just update last_activity. The intent file
|
|
141
|
+
# (What milestone) lands here, so still offer it to the savepoint ledger;
|
|
142
|
+
# non-milestone files self-filter to a no-op (intent 34).
|
|
143
|
+
begin
|
|
144
|
+
Bridge.append_savepoint(intent_dir_abs, file_path_abs)
|
|
145
|
+
rescue StandardError
|
|
146
|
+
# ignore — rebuildable from disk
|
|
147
|
+
end
|
|
133
148
|
bridge_data["build"]["last_activity"] = Time.now.utc.iso8601
|
|
134
149
|
Bridge.write(session, bridge_data)
|
|
135
150
|
exit 0
|
package/scripts/lib/bridge.rb
CHANGED
|
@@ -70,6 +70,68 @@ module Bridge
|
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
+
# --- Cycle-step savepoint ledger (intent 34) ------------------------------
|
|
74
|
+
#
|
|
75
|
+
# savepoint.md is a deterministic, append-only, one-line-per-milestone ledger
|
|
76
|
+
# (newest at the bottom). It is sugar on top of the conventions: derived from
|
|
77
|
+
# files-on-disk, rebuildable, never a source of truth. Milestones are
|
|
78
|
+
# file-event boundaries only; action/resource files record nothing.
|
|
79
|
+
|
|
80
|
+
SAVEPOINT_FILE = "savepoint.md"
|
|
81
|
+
|
|
82
|
+
# Map a written filename to [stage_label, milestone_text], or nil if the file
|
|
83
|
+
# is not a lifecycle milestone.
|
|
84
|
+
def self.savepoint_milestone(intent_dir, basename)
|
|
85
|
+
return ["What", basename] if basename == File.basename(intent_file(intent_dir))
|
|
86
|
+
|
|
87
|
+
case basename
|
|
88
|
+
when "spec.md" then ["Why", "spec.md created"]
|
|
89
|
+
when "plan.md" then ["How", "plan.md created"]
|
|
90
|
+
when "checklist.md" then ["How", "checklist.md created"]
|
|
91
|
+
when "outcome.md" then ["Exec", "outcome.md created"]
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Milestones already recorded in the ledger (field 3 of each line).
|
|
96
|
+
def self.savepoint_recorded_milestones(intent_dir)
|
|
97
|
+
f = File.join(intent_dir, SAVEPOINT_FILE)
|
|
98
|
+
return [] unless File.exist?(f)
|
|
99
|
+
File.read(f).each_line.map do |line|
|
|
100
|
+
parts = line.strip.split(/\s{2,}/)
|
|
101
|
+
parts.length >= 3 ? parts[2] : nil
|
|
102
|
+
end.compact
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Append a milestone line for file_path if (and only if) it is a milestone
|
|
106
|
+
# not already recorded. Returns true when a line was written, false otherwise.
|
|
107
|
+
def self.append_savepoint(intent_dir, file_path, now: Time.now)
|
|
108
|
+
stage, milestone = savepoint_milestone(intent_dir, File.basename(file_path))
|
|
109
|
+
return false unless milestone
|
|
110
|
+
return false if savepoint_recorded_milestones(intent_dir).include?(milestone)
|
|
111
|
+
|
|
112
|
+
line = "#{now.utc.iso8601} #{stage} #{milestone}\n"
|
|
113
|
+
File.open(File.join(intent_dir, SAVEPOINT_FILE), "a") { |io| io.write(line) }
|
|
114
|
+
true
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Reconstruct the ledger from files on disk (timestamps from mtimes), in
|
|
118
|
+
# stage order, overwriting savepoint.md. Returns the number of lines written.
|
|
119
|
+
def self.rebuild_savepoint(intent_dir)
|
|
120
|
+
ordered = [
|
|
121
|
+
File.basename(intent_file(intent_dir)),
|
|
122
|
+
"spec.md", "plan.md", "checklist.md", "outcome.md",
|
|
123
|
+
]
|
|
124
|
+
lines = ordered.filter_map do |basename|
|
|
125
|
+
path = File.join(intent_dir, basename)
|
|
126
|
+
next unless File.exist?(path)
|
|
127
|
+
stage, milestone = savepoint_milestone(intent_dir, basename)
|
|
128
|
+
next unless milestone
|
|
129
|
+
"#{File.mtime(path).utc.iso8601} #{stage} #{milestone}\n"
|
|
130
|
+
end
|
|
131
|
+
File.write(File.join(intent_dir, SAVEPOINT_FILE), lines.join)
|
|
132
|
+
lines.length
|
|
133
|
+
end
|
|
134
|
+
|
|
73
135
|
def self.derive(session, intent_id:, intent_dir:, store:, name:)
|
|
74
136
|
stage = derive_stage(intent_dir)
|
|
75
137
|
has = has_files(intent_dir)
|
|
@@ -1,61 +1,66 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: plastic-savepoint
|
|
3
|
-
description: Use when
|
|
3
|
+
description: Use when verifying or repairing an intent's savepoint ledger, when the user says "save" or "savepoint", or when a PreCompact hook fires. The ledger is written automatically by the gate hook at each lifecycle boundary; this skill only reads, verifies, and rebuilds it.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Savepoint
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- User says "save", "savepoint", or "save progress"
|
|
11
|
-
- Before ending a long session
|
|
12
|
-
- Before switching to a different intent
|
|
8
|
+
`savepoint.md` is a deterministic, append-only ledger of an intent's cycle steps, one line
|
|
9
|
+
per lifecycle milestone, newest at the bottom:
|
|
13
10
|
|
|
14
|
-
|
|
11
|
+
```
|
|
12
|
+
2026-06-16T14:02Z What 34--cycle-step-savepoints.md
|
|
13
|
+
2026-06-16T14:20Z Why spec.md created
|
|
14
|
+
2026-06-16T15:10Z How plan.md created
|
|
15
|
+
2026-06-16T15:11Z How checklist.md created
|
|
16
|
+
2026-06-16T16:40Z Exec outcome.md created
|
|
17
|
+
```
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
It is **sugar on top of the conventions**, not a source of truth. The gate hook
|
|
20
|
+
(`hook-gate-check`) writes each line automatically when a stage file is created, so there is
|
|
21
|
+
nothing to save by hand. State is always derivable from files-on-disk; the ledger just lets
|
|
22
|
+
a resuming agent read the cycle's succession from one glance (last line = where we are).
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
## When to Use
|
|
25
|
+
- A PreCompact hook fires, or the user says "save" / "savepoint": verify the ledger is current.
|
|
26
|
+
- Resuming an intent: read the ledger to learn the cycle's succession quickly.
|
|
27
|
+
- The ledger looks stale, empty, or missing: rebuild it from the filesystem.
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
-
|
|
24
|
-
|
|
29
|
+
## What this skill does NOT do
|
|
30
|
+
- It does not write prose, "in progress", "next step", or "blockers". The old 50%-context
|
|
31
|
+
prose savepoint is retired. The semantic trace lives in `## Insights` (append-only,
|
|
32
|
+
newest at the bottom); the byte history lives in git.
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
```markdown
|
|
28
|
-
# Savepoint
|
|
34
|
+
## Workflow
|
|
29
35
|
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
### 1. Find Active Intent(s)
|
|
37
|
+
Read the active store's `INDEX.md` and extract intents under `## Active`.
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
39
|
+
### 2. For Each Active Intent — verify the ledger
|
|
40
|
+
- Read `savepoint.md`. Confirm it exists and is non-empty.
|
|
41
|
+
- Confirm the **last line's stage** matches the stage derived from files-on-disk
|
|
42
|
+
(`Bridge.derive_stage`). If they disagree, or the file is missing/empty, the ledger has
|
|
43
|
+
drifted.
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
### 3. Rebuild on drift
|
|
46
|
+
Reconstruct from the filesystem rather than hand-editing:
|
|
39
47
|
|
|
40
|
-
|
|
41
|
-
-
|
|
48
|
+
```bash
|
|
49
|
+
ruby -r ~/.plastic/scripts/lib/bridge -e \
|
|
50
|
+
'Bridge.rebuild_savepoint("<intent_dir>")'
|
|
42
51
|
```
|
|
43
52
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
### 3. Update INDEX.md
|
|
48
|
-
Verify the `## Active` section is accurate.
|
|
53
|
+
This rewrites `savepoint.md` from the milestone files present (timestamps from mtimes),
|
|
54
|
+
in stage order. Safe to run anytime: the ledger is derived.
|
|
49
55
|
|
|
50
|
-
### 4. Commit
|
|
56
|
+
### 4. Commit (store only)
|
|
57
|
+
If a rebuild changed the ledger, commit it in the store repo:
|
|
51
58
|
```bash
|
|
52
|
-
git add .
|
|
53
|
-
git commit -m "chore: savepoint — [active intent name]"
|
|
59
|
+
cd <store-root> && git add . && git commit -m "chore: rebuild savepoint ledger — [intent]"
|
|
54
60
|
```
|
|
55
|
-
|
|
56
|
-
### 5. Notify User
|
|
57
|
-
Tell the user: "Context is getting large. I've saved progress to intent [ID] — [name]. Please run `/clear` and say `continue` to resume."
|
|
61
|
+
Never push `~/.plastic/`.
|
|
58
62
|
|
|
59
63
|
## References
|
|
60
64
|
|
|
61
|
-
- Read `references/context-management.md` for the full save/continue protocol
|
|
65
|
+
- Read `references/context-management.md` for the full save/continue protocol and how the
|
|
66
|
+
resume flow consumes the ledger.
|
package/templates/savepoint.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
{{
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
# Deterministic cycle-step ledger, written automatically by the gate hook.
|
|
2
|
+
# One line per lifecycle milestone, append-only, newest at the bottom:
|
|
3
|
+
#
|
|
4
|
+
# {UTC-iso8601} {Stage} {milestone}
|
|
5
|
+
#
|
|
6
|
+
# Example:
|
|
7
|
+
# 2026-06-16T14:02:00Z What ID--slug.md
|
|
8
|
+
# 2026-06-16T14:20:00Z Why spec.md created
|
|
9
|
+
# 2026-06-16T15:10:00Z How plan.md created
|
|
10
|
+
# 2026-06-16T15:11:00Z How checklist.md created
|
|
11
|
+
# 2026-06-16T16:40:00Z Exec outcome.md created
|
|
12
|
+
#
|
|
13
|
+
# This file is sugar on top of the conventions, not a source of truth. It is
|
|
14
|
+
# rebuildable from files-on-disk via Bridge.rebuild_savepoint. Do not hand-edit.
|