@zalom/plastic 2.0.0-alpha.12 → 2.0.0-alpha.14
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/hooks/message-display +31 -2
- package/package.json +1 -1
- package/scripts/dashboard.rb +238 -8
- package/scripts/doctor.rb +291 -4
- package/scripts/lib/dashboard_screen.rb +40 -0
- package/scripts/lib/doctor_core.rb +97 -2
- package/scripts/lib/hook_replay.rb +128 -0
- package/scripts/lib/installer_core.rb +23 -3
- package/scripts/lib/message_display.rb +151 -37
- package/scripts/lib/report_screen.rb +1139 -35
- package/scripts/lib/roadmap_queue.rb +19 -2
- package/scripts/lib/roadmap_savepoint.rb +36 -7
- package/scripts/lib/savepoint.rb +12 -0
- package/scripts/lib/screen_paint.rb +244 -12
- package/scripts/lib/screens/dashboard.rb +20 -0
- package/scripts/lib/screens/plan.rb +18 -0
- package/scripts/lib/screens/roadmap.rb +15 -0
- package/scripts/lib/session_ledger.rb +4 -0
- package/scripts/lib/verify_intent.rb +33 -0
- package/scripts/report-screen +127 -10
- package/scripts/savepoint-note +11 -9
- package/skills/auto/SKILL.md +13 -12
- package/skills/auto/references/human-report-contract.md +83 -8
- package/skills/dashboard/SKILL.md +13 -2
- package/skills/dashboard/templates/dashboard-global.md +1 -1
- package/skills/dashboard/templates/dashboard-project.md +2 -2
- package/skills/doctor/SKILL.md +10 -4
- package/skills/intent-continuing/SKILL.md +28 -26
- package/skills/intent-continuing/references/board-fill.md +9 -0
- package/skills/intent-ending/SKILL.md +6 -4
- package/skills/intent-executing/SKILL.md +2 -0
- package/skills/intent-speccing/SKILL.md +7 -4
- package/skills/roadmap/SKILL.md +9 -0
- package/skills/roadmap/references/file-format.md +10 -0
- package/templates/dashboard-screen.md +22 -0
- package/templates/display-fixture.md +21 -0
- package/templates/intent-screen.md +1 -1
- package/templates/report-plan.md +15 -0
- package/templates/report-roadmap-delivered.md +10 -0
- package/templates/report-roadmap-plan.md +9 -0
- package/templates/report-roadmap-state.md +9 -0
- package/templates/report-state.md +1 -1
package/scripts/report-screen
CHANGED
|
@@ -2,15 +2,21 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# report-screen - the
|
|
6
|
-
# state, post-delivery delivered, and delay
|
|
5
|
+
# report-screen - the pre-delivery plan (intent 331b), the four delivery
|
|
6
|
+
# reports: mid-delivery state, post-delivery delivered, and delay (intent
|
|
7
|
+
# 317), plus session, the whole of one session's delivered work followed by
|
|
8
|
+
# the roster (intent 330). Each fills from the record via
|
|
7
9
|
# scripts/lib/report_screen.rb; no number here is written by eye.
|
|
8
10
|
#
|
|
9
11
|
# Usage:
|
|
12
|
+
# report-screen plan <intent_dir> [--ansi]
|
|
10
13
|
# report-screen state <intent_dir> [--changed "<text>"] [--ansi]
|
|
11
14
|
# report-screen state --all <store_root> [--changed "<text>"] [--ansi]
|
|
12
15
|
# report-screen delivered <intent_dir> [--ansi] [--repo <dir>]
|
|
13
16
|
# report-screen delay <intent_dir> [--ansi]
|
|
17
|
+
# report-screen session <tier_root> [--session <id>] [--since <iso>]
|
|
18
|
+
# [--ledger-root <dir>] [--ansi]
|
|
19
|
+
# report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]
|
|
14
20
|
#
|
|
15
21
|
# --ansi delegates to ScreenPaint (intent 317a, D1), the parser/re-layouter
|
|
16
22
|
# in the shared TUI core. Selection is by capability, never by harness:
|
|
@@ -28,6 +34,14 @@ require "shellwords"
|
|
|
28
34
|
require_relative "lib/report_screen"
|
|
29
35
|
require_relative "lib/intent_screen"
|
|
30
36
|
require_relative "lib/screen_paint"
|
|
37
|
+
require_relative "lib/session_ledger"
|
|
38
|
+
|
|
39
|
+
# Intent 331a (D6/R8): every caller-added screen kind file registers itself
|
|
40
|
+
# on load (ScreenPaint.register), so this glob is the ONLY wiring a new kind
|
|
41
|
+
# needs - no edit here, no edit to screen_paint.rb. Sorted for a
|
|
42
|
+
# deterministic load order; tolerates the directory being absent or empty
|
|
43
|
+
# (Dir.glob answers [] either way), which is this repo's own state today.
|
|
44
|
+
Dir.glob(File.join(__dir__, "lib", "screens", "*.rb")).sort.each { |f| require_relative f }
|
|
31
45
|
|
|
32
46
|
def usage_abort(message)
|
|
33
47
|
warn "report-screen: #{message}"
|
|
@@ -39,10 +53,13 @@ end
|
|
|
39
53
|
# on the delivered screen is the release that merge shipped in. The
|
|
40
54
|
# repository comes from --repo, else from projects.yml beside the store (the
|
|
41
55
|
# installed layout, <home>/projects/<slug>/store/<id>), else the repository
|
|
42
|
-
# this script lives in (the in-repo layout
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
|
|
56
|
+
# this script lives in (the in-repo layout, `include_self: true`) - unless
|
|
57
|
+
# `include_self: false` (the branch reader, D23), in which case a global
|
|
58
|
+
# intent's repo never falls back to the script's own repository, since that
|
|
59
|
+
# is not the intent's repository at all. No merge sha, no repository, or no
|
|
60
|
+
# containing tag all answer nil, and the screen says "not recorded" rather
|
|
61
|
+
# than guessing from HEAD (D14). The pure module never reads git.
|
|
62
|
+
def resolve_repo(intent_dir, explicit_repo, script_dir, include_self: true)
|
|
46
63
|
candidates = []
|
|
47
64
|
candidates << File.expand_path(explicit_repo) if explicit_repo
|
|
48
65
|
if (m = intent_dir.match(%r{\A(.*)/projects/([^/]+)/store/[^/]+\z}))
|
|
@@ -59,7 +76,7 @@ def resolve_repo(intent_dir, explicit_repo, script_dir)
|
|
|
59
76
|
candidates << File.expand_path(path) unless path.empty?
|
|
60
77
|
end
|
|
61
78
|
end
|
|
62
|
-
candidates << File.expand_path("..", script_dir)
|
|
79
|
+
candidates << File.expand_path("..", script_dir) if include_self
|
|
63
80
|
candidates.find { |c| File.exist?(File.join(c, ".git")) }
|
|
64
81
|
end
|
|
65
82
|
|
|
@@ -78,12 +95,40 @@ def git_tag_reader(explicit_repo:, script_dir:)
|
|
|
78
95
|
end
|
|
79
96
|
end
|
|
80
97
|
|
|
98
|
+
# D9/D23: the injected branch reader. flow_base (a pure YAML read, never
|
|
99
|
+
# git) wins when the intent's project names a flow base; otherwise the
|
|
100
|
+
# repository's remote HEAD, else whichever of main/master exists. Resolves
|
|
101
|
+
# the repo with `include_self: false` (D23): a global-store intent must
|
|
102
|
+
# never fall back to the script's OWN repository (~/.plastic itself, on
|
|
103
|
+
# "main" in the installed layout) and print a confident, wrong branch.
|
|
104
|
+
def git_branch_reader(explicit_repo:, script_dir:)
|
|
105
|
+
lambda do |intent_dir|
|
|
106
|
+
flow = ReportScreen.flow_base(intent_dir)
|
|
107
|
+
next flow if flow && !flow.to_s.empty?
|
|
108
|
+
|
|
109
|
+
repo = resolve_repo(intent_dir, explicit_repo, script_dir, include_self: false)
|
|
110
|
+
next nil unless repo
|
|
111
|
+
|
|
112
|
+
branch = `git -C #{repo.shellescape} symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null`.strip
|
|
113
|
+
branch = branch.sub(%r{\Aorigin/}, "")
|
|
114
|
+
next branch unless branch.empty?
|
|
115
|
+
|
|
116
|
+
%w[main master].find do |b|
|
|
117
|
+
system("git", "-C", repo, "rev-parse", "--verify", "--quiet", b, out: File::NULL, err: File::NULL)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
81
122
|
args = ARGV.dup
|
|
82
123
|
verb = args.shift
|
|
83
124
|
changed = nil
|
|
84
125
|
ansi = false
|
|
85
126
|
template_path = nil
|
|
86
127
|
repo_flag = nil
|
|
128
|
+
session_flag = nil
|
|
129
|
+
since_flag = nil
|
|
130
|
+
ledger_root_flag = nil
|
|
131
|
+
store_root_flag = nil
|
|
87
132
|
positional = []
|
|
88
133
|
|
|
89
134
|
while (arg = args.shift)
|
|
@@ -99,13 +144,21 @@ while (arg = args.shift)
|
|
|
99
144
|
repo_flag = args.shift or usage_abort("--repo needs a path")
|
|
100
145
|
when "--template"
|
|
101
146
|
template_path = args.shift or usage_abort("--template needs a path")
|
|
147
|
+
when "--session"
|
|
148
|
+
session_flag = args.shift or usage_abort("--session needs a value")
|
|
149
|
+
when "--since"
|
|
150
|
+
since_flag = args.shift or usage_abort("--since needs a value")
|
|
151
|
+
when "--ledger-root"
|
|
152
|
+
ledger_root_flag = args.shift or usage_abort("--ledger-root needs a path")
|
|
153
|
+
when "--store-root"
|
|
154
|
+
store_root_flag = args.shift or usage_abort("--store-root needs a path")
|
|
102
155
|
else
|
|
103
156
|
usage_abort("unknown flag #{arg.inspect}") if arg.start_with?("--") && arg != "--all"
|
|
104
157
|
positional << arg
|
|
105
158
|
end
|
|
106
159
|
end
|
|
107
160
|
|
|
108
|
-
usage_abort("usage: report-screen state|delivered|delay <intent_dir> [--changed \"<text>\"] [--ansi]") unless verb
|
|
161
|
+
usage_abort("usage: report-screen plan|state|delivered|delay|session <intent_dir> [--changed \"<text>\"] [--ansi]") unless verb
|
|
109
162
|
|
|
110
163
|
all_mode = positional.delete("--all") ? true : false
|
|
111
164
|
target = positional.first
|
|
@@ -119,6 +172,16 @@ def paint(text, ansi_enabled, _renderer_path = nil)
|
|
|
119
172
|
end
|
|
120
173
|
|
|
121
174
|
case verb
|
|
175
|
+
when "plan"
|
|
176
|
+
usage_abort("usage: report-screen plan <intent_dir> [--ansi]") unless target
|
|
177
|
+
intent_dir = File.expand_path(target)
|
|
178
|
+
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
179
|
+
store_root = File.expand_path("../..", intent_dir)
|
|
180
|
+
template_path ||= File.expand_path("../templates/report-plan.md", __dir__)
|
|
181
|
+
usage_abort("template not found at #{template_path}") unless File.exist?(template_path)
|
|
182
|
+
out = ReportScreen.render_plan(intent_dir: intent_dir, store_root: store_root,
|
|
183
|
+
template: File.read(template_path))
|
|
184
|
+
$stdout.write paint(out, ansi_enabled)
|
|
122
185
|
when "state"
|
|
123
186
|
if all_mode
|
|
124
187
|
usage_abort("usage: report-screen state --all <store_root>") unless target
|
|
@@ -142,7 +205,8 @@ when "delivered"
|
|
|
142
205
|
intent_dir = File.expand_path(target)
|
|
143
206
|
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
144
207
|
out = ReportScreen.render_delivered(intent_dir: intent_dir,
|
|
145
|
-
tag_reader: git_tag_reader(explicit_repo: repo_flag, script_dir: __dir__)
|
|
208
|
+
tag_reader: git_tag_reader(explicit_repo: repo_flag, script_dir: __dir__),
|
|
209
|
+
branch_reader: git_branch_reader(explicit_repo: repo_flag, script_dir: __dir__))
|
|
146
210
|
$stdout.write paint(out, ansi_enabled)
|
|
147
211
|
when "delay"
|
|
148
212
|
usage_abort("usage: report-screen delay <intent_dir>") unless target
|
|
@@ -150,8 +214,61 @@ when "delay"
|
|
|
150
214
|
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
151
215
|
out = ReportScreen.render_delay(intent_dir: intent_dir)
|
|
152
216
|
$stdout.write paint(out, ansi_enabled)
|
|
217
|
+
when "session"
|
|
218
|
+
usage_abort("usage: report-screen session <tier_root> [--session <id>] [--since <iso>] [--ledger-root <dir>] [--ansi]") unless target
|
|
219
|
+
store_root = File.expand_path(target)
|
|
220
|
+
usage_abort("#{store_root} is not a store (no INDEX.md)") unless File.exist?(File.join(store_root, "INDEX.md"))
|
|
221
|
+
|
|
222
|
+
now = Time.now
|
|
223
|
+
session_id = session_flag || ENV[SessionLedger::SESSION_ID_ENV_KEY]
|
|
224
|
+
ledger_root = ledger_root_flag ? File.expand_path(ledger_root_flag) : ReportScreen.default_ledger_root(store_root)
|
|
225
|
+
|
|
226
|
+
if since_flag
|
|
227
|
+
begin
|
|
228
|
+
Time.parse(since_flag)
|
|
229
|
+
rescue ArgumentError
|
|
230
|
+
usage_abort("--since needs an ISO timestamp, got #{since_flag.inspect}")
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
dirs, skipped = ReportScreen.session_delivered_dirs(ledger_root: ledger_root, tier_root: store_root,
|
|
235
|
+
session: session_id, since: since_flag, now: now)
|
|
236
|
+
# D17: the widened window is announced whether the id was missing or simply
|
|
237
|
+
# matched nothing. A resumed background job carries a session id that is not
|
|
238
|
+
# the one on the ledger lines, and that case must not answer silently. An
|
|
239
|
+
# explicit --since is the caller naming the window, so it needs no note.
|
|
240
|
+
note =
|
|
241
|
+
if since_flag
|
|
242
|
+
nil
|
|
243
|
+
elsif session_id.nil? || session_id.to_s.strip.empty?
|
|
244
|
+
ReportScreen.window_note(ReportScreen.fallback_day(ledger_root, SessionLedger.day_id(now)),
|
|
245
|
+
"no session id given")
|
|
246
|
+
elsif !ReportScreen.session_tagged?(ledger_root: ledger_root, session: session_id, now: now)
|
|
247
|
+
ReportScreen.window_note(ReportScreen.fallback_day(ledger_root, SessionLedger.day_id(now)),
|
|
248
|
+
"this session has no line in the day ledger")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
out = ReportScreen.render_session(
|
|
252
|
+
dirs: dirs, skipped: skipped, store_root: store_root,
|
|
253
|
+
tag_reader: git_tag_reader(explicit_repo: nil, script_dir: __dir__),
|
|
254
|
+
branch_reader: git_branch_reader(explicit_repo: nil, script_dir: __dir__),
|
|
255
|
+
note: note, now: now, painter: ->(text) { paint(text, ansi_enabled) }
|
|
256
|
+
)
|
|
257
|
+
$stdout.write out
|
|
258
|
+
when "roadmap"
|
|
259
|
+
usage_abort("usage: report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]") if positional.empty?
|
|
260
|
+
roadmap_path = File.expand_path(positional[0])
|
|
261
|
+
usage_abort("#{roadmap_path} does not exist") unless File.exist?(roadmap_path)
|
|
262
|
+
sub_verb = positional[1]
|
|
263
|
+
usage_abort("usage: report-screen roadmap <roadmap.md> plan|state|delivered") unless sub_verb
|
|
264
|
+
unless %w[plan state delivered].include?(sub_verb)
|
|
265
|
+
usage_abort("unknown sub-verb #{sub_verb.inspect} (use plan|state|delivered)")
|
|
266
|
+
end
|
|
267
|
+
roadmap_store_root = store_root_flag ? File.expand_path(store_root_flag) : nil
|
|
268
|
+
out = ReportScreen.render_roadmap(path: roadmap_path, verb: sub_verb, store_root: roadmap_store_root)
|
|
269
|
+
$stdout.write paint(out, ansi_enabled)
|
|
153
270
|
else
|
|
154
|
-
usage_abort("unknown verb #{verb.inspect} (use state|delivered|delay)")
|
|
271
|
+
usage_abort("unknown verb #{verb.inspect} (use plan|state|delivered|delay|session|roadmap)")
|
|
155
272
|
end
|
|
156
273
|
|
|
157
274
|
exit 0
|
package/scripts/savepoint-note
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
# encoding: UTF-8
|
|
3
3
|
# frozen_string_literal: true
|
|
4
4
|
|
|
5
|
-
# savepoint-note - the writer CLI for
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
# ledger; the executor's own commits are plain git
|
|
10
|
-
# thin wrapper on
|
|
5
|
+
# savepoint-note - the writer CLI for three savepoint kinds: Review (one line per
|
|
6
|
+
# plan-review or post-execution-review verdict) and Commit (one line per commit landing
|
|
7
|
+
# during Exec), both from intent 317, D17; and Report (one line per report screen printed,
|
|
8
|
+
# intent 331f). None has an automatic seam (session-commit writes the day ledger, not the
|
|
9
|
+
# intent ledger; the executor's own commits are plain git; a printed screen is a skill's own
|
|
10
|
+
# reply, not a file write), so this is an explicit thin wrapper on
|
|
11
|
+
# Savepoint.append_review_savepoint / append_commit_savepoint / append_report_savepoint.
|
|
11
12
|
#
|
|
12
13
|
# Usage:
|
|
13
|
-
# savepoint-note <intent_dir> --kind Review|Commit --text "<text>"
|
|
14
|
+
# savepoint-note <intent_dir> --kind Review|Commit|Report --text "<text>"
|
|
14
15
|
#
|
|
15
16
|
# --text is normalized (D21): runs of two or more spaces collapse to one, so a
|
|
16
17
|
# free-text kind that happens to contain a double space cannot key the dedup
|
|
@@ -46,12 +47,12 @@ while (arg = args.shift)
|
|
|
46
47
|
end
|
|
47
48
|
end
|
|
48
49
|
|
|
49
|
-
usage_abort("usage: savepoint-note <intent_dir> --kind Review|Commit --text \"<text>\"") unless positional.length == 1
|
|
50
|
+
usage_abort("usage: savepoint-note <intent_dir> --kind Review|Commit|Report --text \"<text>\"") unless positional.length == 1
|
|
50
51
|
|
|
51
52
|
intent_dir = File.expand_path(positional.first)
|
|
52
53
|
usage_abort("#{intent_dir} is not an intent directory") unless File.exist?(Savepoint.intent_file(intent_dir))
|
|
53
54
|
|
|
54
|
-
KINDS = %w[Review Commit].freeze
|
|
55
|
+
KINDS = %w[Review Commit Report].freeze
|
|
55
56
|
usage_abort("--kind must be one of #{KINDS.join(', ')}, got #{kind.inspect}") unless KINDS.include?(kind)
|
|
56
57
|
usage_abort("--text is required") if text.nil? || text.empty?
|
|
57
58
|
usage_abort("--text must not contain a newline") if text.include?("\n")
|
|
@@ -62,6 +63,7 @@ usage_abort("--text is empty after normalization") if normalized.empty?
|
|
|
62
63
|
case kind
|
|
63
64
|
when "Review" then Savepoint.append_review_savepoint(intent_dir, normalized)
|
|
64
65
|
when "Commit" then Savepoint.append_commit_savepoint(intent_dir, normalized)
|
|
66
|
+
when "Report" then Savepoint.append_report_savepoint(intent_dir, normalized)
|
|
65
67
|
end
|
|
66
68
|
|
|
67
69
|
exit 0
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -201,11 +201,9 @@ Then How.
|
|
|
201
201
|
into the spec, the matrix, and the tests; record what was dropped and why in the action
|
|
202
202
|
file's review fold. A REVISE verdict is folded and not re-reviewed unless a finding changes
|
|
203
203
|
a decision.
|
|
204
|
-
5. Print `ruby ~/.plastic/scripts/report-screen
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
it does not wait. The screen opens the reply with nothing before it and no code fence: the
|
|
208
|
-
`MessageDisplay` hook paints only a reply whose first characters are the screen marker.
|
|
204
|
+
5. Print `ruby ~/.plastic/scripts/report-screen plan <intent_dir>` as the first characters of
|
|
205
|
+
the reply, nothing before it, no fence, before dispatching the executor (see
|
|
206
|
+
`references/human-report-contract.md` for the full binding table). It informs; it does not wait.
|
|
209
207
|
|
|
210
208
|
Then Exec.
|
|
211
209
|
|
|
@@ -272,13 +270,16 @@ Read `../plastic-conventions/references/completion-and-done.md` for what "intent
|
|
|
272
270
|
--session "$CLAUDE_CODE_SESSION_ID" \
|
|
273
271
|
--index-note "<what shipped>; <suite result>"
|
|
274
272
|
```
|
|
275
|
-
Exit 4
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
6. Print `ruby ~/.plastic/scripts/report-screen delivered <intent_dir>` once (D15)
|
|
280
|
-
|
|
281
|
-
|
|
273
|
+
Exit 4: a live foreign session holds the lock. 5: the worktree is dirty (commit first, or
|
|
274
|
+
pass `--discard-worktree-changes` deliberately). 3: the lock survived the disarm
|
|
275
|
+
(`/plastic-doctor check the lock status`). 6: the structure check refused. Never leave an
|
|
276
|
+
orphaned worktree; run `git worktree prune` on a stale reference.
|
|
277
|
+
6. Print `ruby ~/.plastic/scripts/report-screen delivered <intent_dir>` once (D15/331f), and
|
|
278
|
+
`report-screen state` at each of the five triggers in `references/human-report-contract.md`
|
|
279
|
+
(a review verdict, a blocker, a merge/release, or an owner status ask; a mid-batch ask
|
|
280
|
+
instead runs `report-screen session <tier_root> --session "$CLAUDE_CODE_SESSION_ID"`, intent
|
|
281
|
+
330). Print each as the first characters of the reply: nothing before it, no fence, or the
|
|
282
|
+
hook cannot paint it.
|
|
282
283
|
|
|
283
284
|
## Error Handling
|
|
284
285
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
# Human Report Contract (the
|
|
1
|
+
# Human Report Contract (the report screens, intent 317)
|
|
2
2
|
|
|
3
3
|
D15: the prose EM-to-CTO briefing this doc used to define is retired. The orchestrator now
|
|
4
|
-
prints one of
|
|
4
|
+
prints one of these report screens, filled from the record by `scripts/report-screen`, never
|
|
5
5
|
written by eye:
|
|
6
6
|
|
|
7
|
+
- **`report-screen plan <intent_dir>`** - the pre-delivery report (intent 331b), printed once
|
|
8
|
+
at the How boundary, before the executor is dispatched: Asked, Decisions, Steps, Mode,
|
|
9
|
+
Reviewer, then the Steps table (Step, Action, What) and the Risks table.
|
|
7
10
|
- **`report-screen state <intent_dir> [--changed "<text>"]`** - the mid-delivery report. One
|
|
8
11
|
intent's field table (Store, Status, Stage, Savepoint, Progress, Next, Insight) plus a
|
|
9
12
|
`Changed` row naming what caused the print, and its Steps table.
|
|
@@ -14,6 +17,59 @@ written by eye:
|
|
|
14
17
|
Asked, Delivered (with a Proven-by column), Evidence, Needs you.
|
|
15
18
|
- **`report-screen delay <intent_dir>`** - printed only on request ("why did X take so long"):
|
|
16
19
|
the delivery as a timeline plus the derived `Where the time went` line.
|
|
20
|
+
- **`report-screen session <tier_root>`** - the answer to an UNNAMED status ask ("where are we
|
|
21
|
+
with delivery", "what is the status"): one `delivered` screen per intent this session
|
|
22
|
+
completed, oldest first, then the `state --all` roster. Intent 330's ruling: a status ask
|
|
23
|
+
answers with what actually shipped, not the in-flight roster alone.
|
|
24
|
+
- **`dashboard.rb continue|project <slug> --screen`** - the dashboard screen (intent 331d):
|
|
25
|
+
Active, In delivery, Delivered, Roadmap, Sessions, Changed, then the Where-we-are and
|
|
26
|
+
Where-we-go-next tables. A separate script from the other four (`dashboard.rb`, not
|
|
27
|
+
`report-screen`), since it aggregates across a whole store or project rather than one
|
|
28
|
+
intent; it prints on `continue` and on loading a project, not as a delivery trigger.
|
|
29
|
+
|
|
30
|
+
## Binding table (intent 331f)
|
|
31
|
+
|
|
32
|
+
Every skill that shows state names its own report verb, one row per skill and trigger. Each
|
|
33
|
+
bound skill's file carries the SAME rule next to its verb: print the screen as the first
|
|
34
|
+
characters of the reply, nothing before it, no fence, or the hook cannot paint it.
|
|
35
|
+
|
|
36
|
+
| Skill | Trigger | Verb |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `plastic-intent-continuing` | project route (continue, load project) | `dashboard.rb ... --screen` |
|
|
39
|
+
| `plastic-intent-continuing` | a named intent | `report-screen state` |
|
|
40
|
+
| `plastic-intent-continuing` | "where are we" (a status ask) | `report-screen session` |
|
|
41
|
+
| `plastic-intent-continuing` | "why so long" | `report-screen delay` |
|
|
42
|
+
| `plastic-intent-continuing` | a roadmap route | `report-screen roadmap ... state` |
|
|
43
|
+
| `plastic-auto` | the How boundary, before the executor | `report-screen plan` |
|
|
44
|
+
| `plastic-auto` | each of the five triggers | `report-screen state` |
|
|
45
|
+
| `plastic-auto` | close | `report-screen delivered` |
|
|
46
|
+
| `plastic-intent-ending` | the close | `report-screen delivered` |
|
|
47
|
+
| `plastic-intent-speccing` | the action files are written | `report-screen plan` |
|
|
48
|
+
| `plastic-roadmap` | create | `report-screen roadmap ... plan` |
|
|
49
|
+
| `plastic-roadmap` | read | `report-screen roadmap ... state` |
|
|
50
|
+
| `plastic-roadmap` | close | `report-screen roadmap ... delivered` |
|
|
51
|
+
| `plastic-dashboard` | any invocation | `dashboard.rb ... --screen` |
|
|
52
|
+
| `plastic-intent-executing` | after the red commit, and after the suite | `report-screen state` |
|
|
53
|
+
|
|
54
|
+
## A roadmap's own three reports (intent 331c)
|
|
55
|
+
|
|
56
|
+
A roadmap gets the same pre-, in-, and post-delivery shape as an intent, through
|
|
57
|
+
`report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]`:
|
|
58
|
+
|
|
59
|
+
- **`roadmap plan`** - the pre-delivery report: the Goal's first sentence, the batch (or legacy
|
|
60
|
+
wave) count and intent count, the batch order, and when the roadmap was created; then the
|
|
61
|
+
full entries table.
|
|
62
|
+
- **`roadmap state`** - the in-delivery report: Goal, a Progress bar over intents delivered of
|
|
63
|
+
intents total (never batches), the frontier batch, who is delivering it and their lead, the
|
|
64
|
+
next queued entry, and the last ledger event; then the entries table with each entry's own
|
|
65
|
+
checklist progress and lead.
|
|
66
|
+
- **`roadmap delivered`** - the post-delivery report: a meta line (closed time, or `in progress`
|
|
67
|
+
when the goal is not yet reached; intent count; duration) directly under the title, the
|
|
68
|
+
delivered table with each entry's merge sha, and the `## Log` table.
|
|
69
|
+
|
|
70
|
+
Every cell traces to the roadmap file, `INDEX.md` (which always wins on status), the roadmap's
|
|
71
|
+
own savepoint ledger, or (falling back when no ledger file exists) the roadmap's `## Log` -
|
|
72
|
+
never a second parser: `RoadmapQueue`'s own public `roadmap` reader supplies every entry.
|
|
17
73
|
|
|
18
74
|
## The five triggers for `state`
|
|
19
75
|
|
|
@@ -38,9 +94,9 @@ on harness name to decide.
|
|
|
38
94
|
|
|
39
95
|
## Depth for small work
|
|
40
96
|
|
|
41
|
-
For small work in auto mode, only the How-boundary `
|
|
42
|
-
|
|
43
|
-
|
|
97
|
+
For small work in auto mode, only the How-boundary `plan` screen prints mid-flight (intent
|
|
98
|
+
331f moved this print off `state`, since there is no separate briefing per stage any more).
|
|
99
|
+
Larger work prints `state` at every trigger in the table above. This is a
|
|
44
100
|
depth cut, not a different report: the screen's shape never changes, only how often it fires.
|
|
45
101
|
A delivery still ends with `outcome.md` plus one `delivered` screen.
|
|
46
102
|
|
|
@@ -73,6 +129,25 @@ In guided mode, `state` prints at each stage boundary and the human decides befo
|
|
|
73
129
|
stage starts.
|
|
74
130
|
|
|
75
131
|
In auto mode, `state` prints at every trigger for larger work; for small work only the How
|
|
76
|
-
boundary prints (see `## Depth for small work` above). The orchestrator takes
|
|
77
|
-
itself and moves on, except at the existing hard stops (destructive action without
|
|
78
|
-
alternative, project-path confirm).
|
|
132
|
+
boundary's `plan` screen prints (see `## Depth for small work` above). The orchestrator takes
|
|
133
|
+
the go-ahead itself and moves on, except at the existing hard stops (destructive action without
|
|
134
|
+
a safe alternative, project-path confirm).
|
|
135
|
+
|
|
136
|
+
## Column vocabulary (D5, intent 331f)
|
|
137
|
+
|
|
138
|
+
Owner ruling 2026-09-05 11:05 UTC: "What" is never a column name, because What is a stage, not
|
|
139
|
+
a value. The id column reads `Graph ID`; the title column reads `Intent`. Every Steps table
|
|
140
|
+
reads `Step | Status | Detail` (the plan screen's own Steps table reads
|
|
141
|
+
`Step | Action | Detail`); every Risks table reads `N | Risk`; the Delivered/Evidence/Needs-you
|
|
142
|
+
tables on the `delivered` screen read `Row | Detail | Proven by`, `Kind | Detail | Source`, and
|
|
143
|
+
`N | Need | Reason`. The plan screen's Asked row prints the intent title before its first
|
|
144
|
+
colon, never the whole intent line. This applies to every screen the family prints: `state`,
|
|
145
|
+
`roster`, `session`, `delivered`, `delay`, `plan`, `roadmap` (`plan`/`state`/`delivered`), and
|
|
146
|
+
`dashboard`. `outcome.md`'s own `| Row | What |` heading is an AUTHORING convention inside the
|
|
147
|
+
file a human writes, never a rendered header, and stays unchanged.
|
|
148
|
+
|
|
149
|
+
## Width bound (D7, intent 331f)
|
|
150
|
+
|
|
151
|
+
No rendered row passes 115 visible columns. A long cell (the roadmap Goal, an intent title, an
|
|
152
|
+
Asked line) truncates on a word boundary with a single ellipsis; `ReportScreen.fit_screen`
|
|
153
|
+
shrinks a table's widest column first, floor 8, before it ever truncates a whole row.
|
|
@@ -7,7 +7,8 @@ user-invocable: false
|
|
|
7
7
|
# Dashboard — Plastic Work Cockpit
|
|
8
8
|
|
|
9
9
|
A deterministic overview of the intent store(s). It answers three questions at a glance:
|
|
10
|
-
**where we are** (recently worked
|
|
10
|
+
**where we are** (recently worked; `Graph ID | Intent | Stage | Progress | Lead`),
|
|
11
|
+
**where we go next** (the most-valuable next work; `Rank | Graph ID | Intent | Reason`), and
|
|
11
12
|
**how to conduct it** (a disposition per intent). The human-facing surface is **Markdown**,
|
|
12
13
|
because the user's UI renders Markdown natively but collapses raw tool-call stdout.
|
|
13
14
|
|
|
@@ -22,6 +23,16 @@ state → byte-identical payload, regardless of model. Do NOT hand-summarize int
|
|
|
22
23
|
- `plastic-intent-continuing` lands on the board on a bare resume
|
|
23
24
|
- `plastic-auto` reads `--json` to choose the next dispatchable intent
|
|
24
25
|
|
|
26
|
+
## The screen surface (intent 331d/331f)
|
|
27
|
+
|
|
28
|
+
`dashboard.rb project <slug> --screen` (or `continue --screen` for the global board) is the
|
|
29
|
+
default surface on every invocation: print it as the first characters of the reply, nothing
|
|
30
|
+
before it, no fence, or the hook cannot paint it. It carries a title, six fields (Active, In
|
|
31
|
+
delivery, Delivered, Roadmap, Sessions, Changed), then the Where-we-are and Where-we-go-next
|
|
32
|
+
tables, its own grammar and painted form standing in for the filled Markdown template below.
|
|
33
|
+
`plastic-intent-continuing`'s project route prints it this way. The Markdown board (Step 1-4
|
|
34
|
+
below) stays available as the deeper prose surface a reader can ask for.
|
|
35
|
+
|
|
25
36
|
## Procedure (the Markdown board — default human surface)
|
|
26
37
|
|
|
27
38
|
### Step 1 — Get the data payload
|
|
@@ -70,7 +81,7 @@ Fill mechanically, no rewriting, no re-sorting:
|
|
|
70
81
|
re-truncate, or reword them. Never emit `<br>`.
|
|
71
82
|
- `next_work` → `| {id} | {what} | {value} | {disposition} | {flags_label} |`
|
|
72
83
|
- `active` → `| {id} | {what} | {stage} | {worker} | {activity} |`
|
|
73
|
-
Empty list → one full-width row with `_(none)_` in the
|
|
84
|
+
Empty list → one full-width row with `_(none)_` in the Graph ID column and every other cell blank,
|
|
74
85
|
matching that table's column count (e.g. `| _(none)_ | | | | |` for the 5-column next_work
|
|
75
86
|
table, `| _(none)_ | | | |` for the 5-column active table). Neither list carries an overflow
|
|
76
87
|
"+N more" row anymore (D5, intent 202): the true pool size rides on the payload as
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
**Active**
|
|
6
6
|
|
|
7
|
-
|
|
|
7
|
+
| Graph ID | Intent | Stage | Worker | Activity |
|
|
8
8
|
| --- | --- | --- | --- | --- |
|
|
9
9
|
{{active.rows}}
|
|
10
10
|
|
|
11
11
|
**Most-valuable next work**
|
|
12
12
|
|
|
13
|
-
|
|
|
13
|
+
| Graph ID | Intent | Value | Disposition | Flags |
|
|
14
14
|
| --- | --- | --- | --- | --- |
|
|
15
15
|
{{next_work.rows}}
|
|
16
16
|
|
package/skills/doctor/SKILL.md
CHANGED
|
@@ -78,10 +78,15 @@ project alone.
|
|
|
78
78
|
Runs the install-wide surface: agent registration, core files (including config-honoring
|
|
79
79
|
drift), manifest sync is core-only and not part of this run, deprecation checks, config-ask
|
|
80
80
|
checks, install-integrity checks, skill-lint (advisory), QMD reachability (unscoped, every
|
|
81
|
-
collection),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
collection), the global store's own conventions/done-signals content, and the `display`
|
|
82
|
+
category (intent 331e): `display_hook_registered` (also runs at `--core`), `display_hook_paints`
|
|
83
|
+
(replays the shipped display fixture through the installed MessageDisplay hook and expects a
|
|
84
|
+
painted screen back, a pass when a known defeater like `NO_COLOR` is active, never a fail),
|
|
85
|
+
`display_not_defeated` (warns per active defeater and always names the undetectable verbose
|
|
86
|
+
transcript view), and `display_surfaces_documented` (the harness-adapters doc still names every
|
|
87
|
+
surface class). **Never carries a per-project finding**; that is `--store <slug>`'s job (see
|
|
88
|
+
above). This is what `/plastic-doctor` invokes, and it also runs automatically after every
|
|
89
|
+
`plastic-update` (informational, does not block or revert the update).
|
|
85
90
|
|
|
86
91
|
## When to Use
|
|
87
92
|
|
|
@@ -167,6 +172,7 @@ Use the `fix_hint` value to determine the correct action:
|
|
|
167
172
|
| "Remove each listed .tmp/<session>/ directory after confirming that session is gone" | For each listed directory, confirm no live session uses it (a live session rewrites its heartbeat on every prompt and edit), then remove that directory by hand; never remove an unlisted one |
|
|
168
173
|
| "For a day directory missing its <day>.md, run `file-session-intent --day <day> ...`" | Run `ruby ~/.plastic/scripts/file-session-intent --day <day> --carry-to <today> --store <store>` for the named day; rename or remove an entry that is not a `YYYYMMDD` day directory |
|
|
169
174
|
| "Run scripts/project-links ... PRESERVES ... --drop-unbacked-links" | Run `ruby ~/.plastic/scripts/maintenance-run --tool project-links --intent <id> --apply` for the one flagged id (never run bare `project-links` against a real store outside the rare owner-approved batch exception, D2) |
|
|
175
|
+
| "Re-run the Plastic installer to repair the hook registration ... (plastic-install --repair)" (`display_hook_registered`) | Run `npx -y @zalom/plastic@<channel> install --reinstall --claude` (the `plastic-install` skill's repair mode), then re-run doctor |
|
|
170
176
|
|
|
171
177
|
For fixes the agent cannot handle automatically, explain what the user needs
|
|
172
178
|
to do manually. The `revisions.md` remedy is curator-applied (a move-and-record
|
|
@@ -45,32 +45,29 @@ specific intent or roadmap named.").
|
|
|
45
45
|
|
|
46
46
|
## Project route: land on the board
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
Priority order on the board: active intents first, then project context (governing plus
|
|
60
|
-
tactical intents in a registered project), then stale future intents for triage, then fresh
|
|
61
|
-
future intents as next work. A future intent older than `stale_threshold_days` (default 3) is
|
|
62
|
-
surfaced for triage without action: activate, abandon, or leave. Activating moves it to
|
|
48
|
+
Print `ruby ~/.plastic/scripts/dashboard.rb project <slug> --screen` (or `continue --screen`
|
|
49
|
+
for the global fallback) as the first characters of the reply: nothing before it, no fence, or
|
|
50
|
+
the hook cannot paint it (intent 331d/331f). It carries a title, six fields (Active, In
|
|
51
|
+
delivery, Delivered, Roadmap, Sessions, Changed), then the Where-we-are and Where-we-go-next
|
|
52
|
+
tables. The board load runs the scoped store check (`doctor --store <scope>`); its result
|
|
53
|
+
rides in the payload as `store_health` and prints as one line of data, never a blocker.
|
|
54
|
+
|
|
55
|
+
Priority order on the underlying data: active intents first, then project context (governing
|
|
56
|
+
plus tactical intents in a registered project), then stale future intents for triage, then
|
|
57
|
+
fresh future intents as next work. A future intent older than `stale_threshold_days` (default
|
|
58
|
+
3) is surfaced for triage without action: activate, abandon, or leave. Activating moves it to
|
|
63
59
|
`## Active` in `INDEX.md` and auto-commits. The board's ranked next-work order is computed by
|
|
64
60
|
`dashboard.rb`; cite the rule names only (Effort, Value, Flags, Override, Caps) and read
|
|
65
61
|
`plastic-dashboard`'s `references/classification.md` for their definitions.
|
|
66
62
|
|
|
67
63
|
When the tier root (the directory holding `INDEX.md`) has a mid-flight roadmap
|
|
68
64
|
(`ruby ~/.plastic/scripts/roadmap-next --roadmaps-dir <root>/roadmaps` reports a `state`
|
|
69
|
-
other than `none`), say so in one line and offer the roadmap route; the
|
|
65
|
+
other than `none`), say so in one line and offer the roadmap route; the screen still presents
|
|
70
66
|
project state and stops.
|
|
71
67
|
|
|
72
68
|
Then stop: "here is the state, what next?". Do not start executing work. When the user names
|
|
73
|
-
an intent, take the intent route.
|
|
69
|
+
an intent, take the intent route. Read `references/board-fill.md` only when the reader asks
|
|
70
|
+
for the deeper prose board (`plastic-dashboard`'s Markdown surface still exists for that ask).
|
|
74
71
|
|
|
75
72
|
## Intent route: resume one intent from its ledger
|
|
76
73
|
|
|
@@ -111,11 +108,15 @@ For a live intent's directory:
|
|
|
111
108
|
`ruby ~/.plastic/scripts/report-screen state <intent_dir>` and print its output as it is:
|
|
112
109
|
the title, the field table, the `Changed` row, and the Steps table come from the record,
|
|
113
110
|
never by eye. For "where are we" with no intent named, run
|
|
114
|
-
`ruby ~/.plastic/scripts/report-screen
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
111
|
+
`ruby ~/.plastic/scripts/report-screen session <tier_root> --session <this session's id>`
|
|
112
|
+
(intent 330; pass the id your harness gives you, or the screen widens to the whole day and
|
|
113
|
+
says so): it prints one
|
|
114
|
+
`delivered` screen per intent this session actually completed, oldest first, then the same
|
|
115
|
+
roster `report-screen state --all <store_root>` prints on its own - `state --all` stays the
|
|
116
|
+
right call when only the in-flight roster is wanted, with nothing delivered above it. Route
|
|
117
|
+
"why did X take so long" to `ruby ~/.plastic/scripts/report-screen delay <intent_dir>`
|
|
118
|
+
instead - every verb prints the same plain screen on any harness, painted only where the
|
|
119
|
+
harness supports it, with no branching on harness name. Under the `state` screen
|
|
119
120
|
write **What this means** as two to four bullets in plain words (what the intent is for,
|
|
120
121
|
what has landed, what is left, any defect named by step), then close with **needs input:**
|
|
121
122
|
naming the first open step. Then continue the work in the
|
|
@@ -135,10 +136,11 @@ For a live intent's directory:
|
|
|
135
136
|
`delivering` or `blocked` entry wins, else the newest ledger or `## Log` timestamp.
|
|
136
137
|
`roadmaps/<slug>.savepoint.md` is a derived signal read here, never a status field;
|
|
137
138
|
`INDEX.md` stays the sole status writer.
|
|
138
|
-
2. **
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
2. **Print state.** Print `ruby ~/.plastic/scripts/report-screen roadmap <roadmap.md> state` as
|
|
140
|
+
the first characters of the reply: nothing before it, no fence, or the hook cannot paint it.
|
|
141
|
+
It carries Goal, Progress, Frontier, Delivering, Next, and Changed, then the entries table -
|
|
142
|
+
never hand-typed. Read `../plastic-conventions/references/roadmaps.md` for the file format
|
|
143
|
+
and the status-mirror rule when a roadmap file needs interpreting.
|
|
142
144
|
3. Then continue with the next dispatchable entry in the session's mode: direct work on it,
|
|
143
145
|
or `plastic-auto` when the owner says auto. The coordinator that drives a batch appends
|
|
144
146
|
to `roadmaps/<slug>.savepoint.md` at its dispatch, merge, park, and handoff points with
|
|
@@ -41,3 +41,12 @@ The project route's default target is the project board. When no project is load
|
|
|
41
41
|
case where this route is reached without a registered project in scope), fall back to the
|
|
42
42
|
global board payload (`dashboard.rb continue --data`) rather than failing. This mirrors the
|
|
43
43
|
router's D6 default: a bare "continue" always lands somewhere useful.
|
|
44
|
+
|
|
45
|
+
## The screen surface (intent 331d)
|
|
46
|
+
|
|
47
|
+
`dashboard.rb project <slug> --screen` (or `continue --screen`) prints the identical state -
|
|
48
|
+
Active, In delivery, Delivered, Roadmap, Sessions, Changed, then the Where-we-are and
|
|
49
|
+
Where-we-go-next tables - as a screen with its own grammar instead of a filled Markdown
|
|
50
|
+
template. It replaces this fill mechanism once intent 331f wires the project route to print
|
|
51
|
+
it first, the way the intent route already prints the intent screen first today; until then,
|
|
52
|
+
this page's fill rules stay the route's own surface.
|