@zalom/plastic 1.0.0-alpha.9 → 1.0.0-beta.2
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 +163 -469
- package/README.md +95 -58
- package/agents/plastic-brainstorming.md +37 -0
- package/agents/plastic-enforcer.md +36 -0
- package/agents/plastic-executor.md +37 -0
- package/agents/{future-intent-researcher.md → plastic-future-intent-researcher.md} +1 -1
- package/agents/{intent-curator.md → plastic-intent-curator.md} +1 -1
- package/agents/plastic-planner.md +37 -0
- package/agents/plastic-spec-specialist.md +37 -0
- package/bin/plastic.js +57 -0
- package/bin/test +28 -0
- package/deprecations.yml +1 -10
- package/hooks/auto-arm +5 -0
- package/hooks/bash-gate +3 -0
- package/hooks/check-update +12 -8
- package/hooks/code-gate +12 -0
- package/hooks/create-gate +3 -0
- package/hooks/gate-check +3 -1
- package/hooks/hooks.json +52 -0
- package/hooks/qmd-search +8 -0
- package/package.json +2 -2
- package/scripts/dashboard.rb +687 -0
- package/scripts/doctor.rb +1054 -628
- package/scripts/hook-auto-arm +51 -0
- package/scripts/hook-bash-gate +41 -0
- package/scripts/hook-code-gate +27 -0
- package/scripts/hook-continue +15 -114
- package/scripts/hook-create-gate +59 -0
- package/scripts/hook-gate-check +47 -32
- package/scripts/hook-qmd-search +44 -0
- package/scripts/hook-session-start +106 -38
- package/scripts/install.rb +91 -529
- package/scripts/lib/boot_banner.rb +28 -0
- package/scripts/lib/bridge.rb +404 -19
- package/scripts/lib/installer_core.rb +807 -0
- package/scripts/lib/intent_validator.rb +235 -0
- package/scripts/lib/qmd_hook.rb +44 -0
- package/scripts/lib/qmd_sync.rb +209 -0
- package/scripts/lib/store_provisioning.rb +100 -0
- package/scripts/migrate-to-global +1 -1
- package/scripts/new-intent +226 -0
- package/scripts/provision-project-store +53 -0
- package/scripts/qmd-sync +92 -0
- package/scripts/select-update-target +93 -0
- package/scripts/spawn-preamble +121 -0
- package/scripts/uninstall.rb +53 -0
- package/scripts/update.rb +164 -0
- package/scripts/validate-intent +54 -0
- package/scripts/versions.rb +141 -0
- package/skills/_active-intent-gate.md +1 -1
- package/skills/add-project-store/SKILL.md +54 -0
- package/skills/auto/SKILL.md +77 -7
- package/skills/auto/evals/evals.json +207 -0
- package/skills/auto/references/agent-architecture.md +135 -0
- package/skills/brainstorming/SKILL.md +9 -9
- package/skills/brainstorming-grill-me/SKILL.md +6 -6
- package/skills/continuing/SKILL.md +92 -82
- package/skills/continuing/evals/evals.json +136 -0
- package/skills/continuing/references/context-management.md +32 -0
- package/skills/creating-intent/SKILL.md +74 -36
- package/skills/creating-intent/evals/evals.json +56 -0
- package/skills/creating-intent/references/lifecycle.md +76 -0
- package/skills/creating-intent/references/wikilinks.md +8 -0
- package/skills/creating-project/SKILL.md +40 -8
- package/skills/creating-project/references/hubs-projects.md +55 -0
- package/skills/dashboard/SKILL.md +121 -0
- package/skills/dashboard/templates/dashboard-global.md +31 -0
- package/skills/dashboard/templates/dashboard-project.md +40 -0
- package/skills/doctor/SKILL.md +51 -4
- package/skills/doctor/references/gates-stuck-detection.md +38 -0
- package/skills/doctor/report.md +4 -0
- package/skills/evaluating-skills/SKILL.md +140 -0
- package/skills/evaluating-skills/assets/eval-template.json +12 -0
- package/skills/evaluating-skills/evals/evals.json +75 -0
- package/skills/evaluating-skills/references/convention-checks.md +76 -0
- package/skills/evaluating-skills/references/eval-methodology.md +154 -0
- package/skills/executing-plan/SKILL.md +5 -3
- package/skills/install/SKILL.md +69 -8
- package/skills/intent-curator/SKILL.md +3 -3
- package/skills/linking-intents/SKILL.md +16 -7
- package/skills/linking-intents/references/zettelkasten.md +38 -0
- package/skills/managing-index/SKILL.md +5 -1
- package/skills/managing-index/references/zettelkasten-linking.md +1 -1
- package/skills/releasing/SKILL.md +80 -23
- package/skills/releasing/references/deprecations.md +60 -0
- package/skills/research/SKILL.md +2 -2
- package/skills/savepoint/SKILL.md +46 -37
- package/skills/savepoint/references/context-management.md +32 -0
- package/skills/uninstall/SKILL.md +39 -28
- package/skills/update/SKILL.md +41 -44
- package/skills/versions/SKILL.md +65 -0
- package/skills/writing-instructions/SKILL.md +159 -0
- package/skills/writing-instructions/references/agentskills-spec.md +135 -0
- package/skills/writing-plans/SKILL.md +5 -5
- package/templates/agents.md +7 -7
- package/templates/outcome.md +13 -0
- package/templates/savepoint.md +14 -13
- package/templates/spec.md +25 -0
- package/bin/install.js +0 -29
|
@@ -7,6 +7,9 @@ require "json"
|
|
|
7
7
|
require "date"
|
|
8
8
|
require "yaml"
|
|
9
9
|
require_relative "lib/bridge"
|
|
10
|
+
require_relative "lib/boot_banner"
|
|
11
|
+
require_relative "lib/qmd_sync"
|
|
12
|
+
require_relative "doctor"
|
|
10
13
|
|
|
11
14
|
index_path, store_root, mode, plugin_root = ARGV
|
|
12
15
|
exit 0 unless index_path && store_root && mode
|
|
@@ -117,15 +120,23 @@ if File.exist?(projects_path)
|
|
|
117
120
|
end
|
|
118
121
|
end
|
|
119
122
|
|
|
123
|
+
# --- Load PLASTIC.md conventions ---
|
|
124
|
+
|
|
125
|
+
plastic_md_path = "#{store_root}/PLASTIC.md"
|
|
126
|
+
plastic_md = File.exist?(plastic_md_path) ? File.read(plastic_md_path).strip : nil
|
|
127
|
+
|
|
120
128
|
# --- Load deprecations ---
|
|
121
129
|
|
|
130
|
+
dep_file = if plugin_root && !plugin_root.empty?
|
|
131
|
+
"#{plugin_root}/deprecations.yml"
|
|
132
|
+
else
|
|
133
|
+
"#{store_root}/deprecations.yml"
|
|
134
|
+
end
|
|
135
|
+
|
|
122
136
|
deprecations = []
|
|
123
|
-
if
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
dep_data = YAML.safe_load(File.read(dep_file)) rescue {}
|
|
127
|
-
deprecations = dep_data["deprecations"] || []
|
|
128
|
-
end
|
|
137
|
+
if File.exist?(dep_file)
|
|
138
|
+
dep_data = YAML.safe_load(File.read(dep_file)) rescue {}
|
|
139
|
+
deprecations = dep_data["deprecations"] || []
|
|
129
140
|
end
|
|
130
141
|
|
|
131
142
|
dismissed_json = `"#{read_config}" deprecations_dismissed`.strip
|
|
@@ -136,7 +147,10 @@ rescue
|
|
|
136
147
|
end
|
|
137
148
|
|
|
138
149
|
current_version = nil
|
|
139
|
-
|
|
150
|
+
version_file = "#{store_root}/VERSION"
|
|
151
|
+
if File.exist?(version_file)
|
|
152
|
+
current_version = File.read(version_file).strip
|
|
153
|
+
elsif plugin_root && !plugin_root.empty?
|
|
140
154
|
plugin_json_path = "#{plugin_root}/.claude-plugin/plugin.json"
|
|
141
155
|
if File.exist?(plugin_json_path)
|
|
142
156
|
pj = JSON.parse(File.read(plugin_json_path)) rescue {}
|
|
@@ -157,53 +171,98 @@ cache_file = "#{store_root}/.cache/update-check.json"
|
|
|
157
171
|
if File.exist?(cache_file)
|
|
158
172
|
cache = JSON.parse(File.read(cache_file)) rescue {}
|
|
159
173
|
if cache["updateAvailable"]
|
|
160
|
-
update_notice = "Plastic update available: #{cache["current"]} -> #{cache["latest"]} — run /plastic
|
|
174
|
+
update_notice = "Plastic update available: #{cache["current"]} -> #{cache["latest"]} — run /plastic-update"
|
|
161
175
|
end
|
|
162
176
|
end
|
|
163
177
|
|
|
164
|
-
# ---
|
|
178
|
+
# --- Run core health in-process (intent 36a) ---
|
|
179
|
+
# Reuse Doctor's own --core checks so there is one source of truth for "core
|
|
180
|
+
# health" (no second process spawn, no duplicated check list). Never blocks the
|
|
181
|
+
# session: any failure or exception degrades to a banner and we continue.
|
|
182
|
+
|
|
183
|
+
core_health = begin
|
|
184
|
+
Doctor.new(plastic_home: store_root).run_core_checks("claude")
|
|
185
|
+
rescue
|
|
186
|
+
nil
|
|
187
|
+
end
|
|
188
|
+
core_banner = BootBanner.render(health: core_health, version: current_version)
|
|
189
|
+
|
|
190
|
+
# --- Assemble session context ---
|
|
165
191
|
|
|
166
192
|
all_active = active + project_active
|
|
167
193
|
all_future = future + project_future
|
|
168
|
-
exit 0 if all_active.empty? && stale.empty? && all_future.empty? && active_deprecations.empty? && update_notice.nil?
|
|
169
194
|
|
|
170
195
|
# --- Build context ---
|
|
196
|
+
# PLASTIC.md conventions and intent context render only when conventions are
|
|
197
|
+
# installed. Deprecation warnings and update notices render regardless, so a
|
|
198
|
+
# partial install (or a missing PLASTIC.md) still surfaces critical warnings.
|
|
171
199
|
|
|
172
200
|
parts = []
|
|
173
201
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
202
|
+
# Boot banner first: every session start surfaces that Plastic loaded, with the
|
|
203
|
+
# version (clean) or the first failing core check (degraded). Owned by the hook
|
|
204
|
+
# so it runs by construction — the plastic-continuing skill no longer does this.
|
|
205
|
+
parts << core_banner
|
|
206
|
+
parts << ""
|
|
207
|
+
|
|
208
|
+
# --- QMD search status (intent 45a, READ-ONLY) ---
|
|
209
|
+
# Report-only line for the model (additionalContext), never systemMessage and
|
|
210
|
+
# never the exit code. QmdSync.status shells out to `qmd collection list`, so the
|
|
211
|
+
# whole block is guarded: a 2s timeout caps any hang, and rescue-all guarantees a
|
|
212
|
+
# slow/broken/missing qmd appends nothing and the hook continues cleanly.
|
|
213
|
+
begin
|
|
214
|
+
require "timeout"
|
|
215
|
+
qmd_status = Timeout.timeout(2) { QmdSync.status(plastic_home: store_root) }
|
|
216
|
+
if qmd_status[:present]
|
|
217
|
+
if qmd_status[:all_registered]
|
|
218
|
+
parts << "QMD: #{qmd_status[:registered].size} Plastic collections indexed (search with the qmd skill)."
|
|
219
|
+
else
|
|
220
|
+
parts << "QMD detected — run `qmd-sync register --all` to index your Plastic stores for search."
|
|
221
|
+
end
|
|
181
222
|
end
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
parts << "PLASTIC — Global store loaded from ~/.plastic/\n"
|
|
223
|
+
rescue Exception
|
|
224
|
+
# Any failure (timeout, missing binary, parse error) — stay silent, never crash.
|
|
185
225
|
end
|
|
186
226
|
|
|
187
|
-
if
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
227
|
+
if plastic_md
|
|
228
|
+
# Conventions always loaded first
|
|
229
|
+
parts << plastic_md
|
|
230
|
+
parts << "\n---\n"
|
|
231
|
+
|
|
232
|
+
if current_project
|
|
233
|
+
slug = current_project["slug"]
|
|
234
|
+
banner = "Project: #{slug} | Store: ~/.plastic/projects/#{slug}/store/"
|
|
235
|
+
if project_active.any? && project_active.first =~ /\[([^\]]+)\].*store\/([\w-]+)\//
|
|
236
|
+
intent_name, dir_name = $1, $2
|
|
237
|
+
intent_id = dir_name.split("--").first
|
|
238
|
+
banner += "\nActive: [#{intent_id} — #{intent_name}] | Artifacts → store/#{dir_name}/"
|
|
239
|
+
end
|
|
240
|
+
parts << banner + "\n"
|
|
241
|
+
else
|
|
242
|
+
parts << "PLASTIC — Global store loaded from ~/.plastic/\n"
|
|
195
243
|
end
|
|
196
|
-
parts << ""
|
|
197
|
-
else
|
|
198
|
-
parts << "No active intents. #{future.length} future intents available.\n"
|
|
199
|
-
end
|
|
200
244
|
|
|
201
|
-
if
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
245
|
+
if all_active.any?
|
|
246
|
+
parts << "Active intents:\n"
|
|
247
|
+
all_active.each { |a| parts << a }
|
|
248
|
+
if bridge_data
|
|
249
|
+
stage = bridge_data["build"]["stage"]
|
|
250
|
+
missing = bridge_data["build"]["missing"]
|
|
251
|
+
missing_str = missing.empty? ? "none" : missing.join(", ")
|
|
252
|
+
parts << "Stage: #{stage} | Next: #{missing_str}"
|
|
253
|
+
end
|
|
254
|
+
parts << ""
|
|
255
|
+
else
|
|
256
|
+
parts << "No active intents. #{future.length} future intents available.\n"
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
if stale.any?
|
|
260
|
+
parts << "\nStale future intents (untouched for days):\n"
|
|
261
|
+
stale.each do |s|
|
|
262
|
+
parts << "- #{s[:name]} (#{s[:age]} days) — consider: activate, abandon, or defer to agent"
|
|
263
|
+
end
|
|
264
|
+
parts << "\nWhen appropriate, ask the user what to do with stale intents."
|
|
205
265
|
end
|
|
206
|
-
parts << "\nWhen appropriate, ask the user what to do with stale intents."
|
|
207
266
|
end
|
|
208
267
|
|
|
209
268
|
if active_deprecations.any?
|
|
@@ -237,10 +296,19 @@ if update_notice
|
|
|
237
296
|
parts.unshift("! #{update_notice}\n")
|
|
238
297
|
end
|
|
239
298
|
|
|
299
|
+
# Emit nothing when there is genuinely nothing to surface (no conventions,
|
|
300
|
+
# no deprecations, no update notice).
|
|
301
|
+
exit 0 if parts.join.strip.empty?
|
|
302
|
+
|
|
240
303
|
payload = {
|
|
241
304
|
"hookSpecificOutput" => {
|
|
242
305
|
"hookEventName" => "SessionStart",
|
|
243
306
|
"additionalContext" => parts.join("\n")
|
|
244
|
-
}
|
|
307
|
+
},
|
|
308
|
+
# Intent 54: additionalContext is model-only, so the banner stays invisible to
|
|
309
|
+
# the human. The top-level systemMessage channel is rendered in the user's
|
|
310
|
+
# terminal (and re-fires on /clear). Reuse the same BootBanner line so the
|
|
311
|
+
# visible banner and the model-facing banner cannot drift.
|
|
312
|
+
"systemMessage" => core_banner
|
|
245
313
|
}
|
|
246
314
|
puts JSON.generate(payload)
|