@zalom/plastic 2.0.0-alpha.10 → 2.0.0-alpha.11
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/package.json +1 -1
- package/scripts/lib/report_screen.rb +28 -5
- package/scripts/report-screen +49 -12
- package/skills/auto/SKILL.md +4 -2
package/package.json
CHANGED
|
@@ -303,10 +303,33 @@ module ReportScreen
|
|
|
303
303
|
{ kind: "red", what: "#{sha} proven test-only and red", source: "outcome.md ## Verification" }
|
|
304
304
|
end
|
|
305
305
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
306
|
+
# Fix 2026-09-01: the record is the truth of delivery (D14: never a guess).
|
|
307
|
+
# The shipped version comes from outcome.md's own ship line first ("Shipped
|
|
308
|
+
# as `v2.0.0-alpha.10`", "released as **v2.0.0-alpha.5**", "released
|
|
309
|
+
# v2.0.0-alpha.9", "Tagged v1.14.1", "Delivered in", "Release v"); the injected tag reader (git) is the
|
|
310
|
+
# fallback when the record is silent. A bare version with no ship verb
|
|
311
|
+
# ("from 1.14.1") is not a shipped version.
|
|
312
|
+
SHIP_VERSION_RE = /\b(?:shipped|released?|delivered|tagged)\b(?:\s+(?:as|in))?[\s`*]*v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z]+(?:\.[0-9A-Za-z]+)*)?)/i.freeze
|
|
313
|
+
|
|
314
|
+
def self.shipped_version(intent_dir)
|
|
315
|
+
text = outcome_text(intent_dir)
|
|
316
|
+
return nil unless text
|
|
317
|
+
m = text.match(SHIP_VERSION_RE)
|
|
318
|
+
m && m[1]
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# The merge commit named on outcome.md's merge line, or nil. The CLI's tag
|
|
322
|
+
# reader asks git which tag contains it; the ship row prints it.
|
|
323
|
+
def self.merge_sha(intent_dir)
|
|
324
|
+
text = outcome_text(intent_dir)
|
|
325
|
+
return nil unless text
|
|
326
|
+
line = text.lines.find { |l| l =~ /\bmerge(d)?\b/i && l =~ /\b[0-9a-f]{7,40}\b/ }
|
|
327
|
+
line && line.match(/\b([0-9a-f]{7,40})\b/)[1]
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def self.ship_row(_text, intent_dir, tag_reader)
|
|
331
|
+
sha = merge_sha(intent_dir)
|
|
332
|
+
version = shipped_version(intent_dir) || tag_reader.call(intent_dir)
|
|
310
333
|
return nil if sha.nil? && (version.nil? || version.to_s.empty?)
|
|
311
334
|
ver_text = version && !version.to_s.empty? ? "v#{version.to_s.sub(/\Av/, '')}" : NOT_RECORDED
|
|
312
335
|
sha_text = sha || NOT_RECORDED
|
|
@@ -524,7 +547,7 @@ module ReportScreen
|
|
|
524
547
|
ts = delivered_timestamp(intent_dir)
|
|
525
548
|
m = mode(intent_dir)
|
|
526
549
|
dur = duration(intent_dir)
|
|
527
|
-
version = tag_reader.call(intent_dir)
|
|
550
|
+
version = shipped_version(intent_dir) || tag_reader.call(intent_dir)
|
|
528
551
|
ver_text = version && !version.to_s.empty? ? "v#{version.to_s.sub(/\Av/, '')}" : NOT_RECORDED
|
|
529
552
|
|
|
530
553
|
lines = []
|
package/scripts/report-screen
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
# Usage:
|
|
10
10
|
# report-screen state <intent_dir> [--changed "<text>"] [--ansi]
|
|
11
11
|
# report-screen state --all <store_root> [--changed "<text>"] [--ansi]
|
|
12
|
-
# report-screen delivered <intent_dir> [--ansi]
|
|
12
|
+
# report-screen delivered <intent_dir> [--ansi] [--repo <dir>]
|
|
13
13
|
# report-screen delay <intent_dir> [--ansi]
|
|
14
14
|
#
|
|
15
15
|
# --ansi delegates to ScreenPaint (intent 317a, D1), the parser/re-layouter
|
|
@@ -23,6 +23,8 @@
|
|
|
23
23
|
# 2 - usage error, or the path/store is not what the verb expects (one line
|
|
24
24
|
# on stderr, stdout empty)
|
|
25
25
|
|
|
26
|
+
require "yaml"
|
|
27
|
+
require "shellwords"
|
|
26
28
|
require_relative "lib/report_screen"
|
|
27
29
|
require_relative "lib/intent_screen"
|
|
28
30
|
require_relative "lib/screen_paint"
|
|
@@ -32,15 +34,47 @@ def usage_abort(message)
|
|
|
32
34
|
exit 2
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
# The one place allowed to shell out (D2/D20): resolves the
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
# The one place allowed to shell out (D2/D20): resolves the tag that CONTAINS
|
|
38
|
+
# the intent's merge commit, in the project's own repository, so the version
|
|
39
|
+
# on the delivered screen is the release that merge shipped in. The
|
|
40
|
+
# repository comes from --repo, else from projects.yml beside the store (the
|
|
41
|
+
# installed layout, <home>/projects/<slug>/store/<id>), else the repository
|
|
42
|
+
# this script lives in (the in-repo layout). No merge sha, no repository, or
|
|
43
|
+
# no containing tag all answer nil, and the screen says "not recorded"
|
|
44
|
+
# rather than guessing from HEAD (D14). The pure module never reads git.
|
|
45
|
+
def resolve_repo(intent_dir, explicit_repo, script_dir)
|
|
46
|
+
candidates = []
|
|
47
|
+
candidates << File.expand_path(explicit_repo) if explicit_repo
|
|
48
|
+
if (m = intent_dir.match(%r{\A(.*)/projects/([^/]+)/store/[^/]+\z}))
|
|
49
|
+
home, slug = m[1], m[2]
|
|
50
|
+
projects_yml = File.join(home, "projects.yml")
|
|
51
|
+
if File.exist?(projects_yml)
|
|
52
|
+
data = begin
|
|
53
|
+
YAML.safe_load(File.read(projects_yml))
|
|
54
|
+
rescue StandardError
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
57
|
+
entry = data.is_a?(Hash) && data["projects"].is_a?(Hash) ? data["projects"][slug] : nil
|
|
58
|
+
path = entry.is_a?(Hash) ? entry["path"].to_s : ""
|
|
59
|
+
candidates << File.expand_path(path) unless path.empty?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
candidates << File.expand_path("..", script_dir)
|
|
63
|
+
candidates.find { |c| File.exist?(File.join(c, ".git")) }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def git_tag_reader(explicit_repo:, script_dir:)
|
|
67
|
+
lambda do |intent_dir|
|
|
68
|
+
sha = ReportScreen.merge_sha(intent_dir)
|
|
69
|
+
return nil unless sha
|
|
70
|
+
repo = resolve_repo(intent_dir, explicit_repo, script_dir)
|
|
71
|
+
return nil unless repo
|
|
72
|
+
tags = `git -C #{repo.shellescape} tag --contains #{sha.shellescape} 2>/dev/null`
|
|
73
|
+
versions = tags.lines.map(&:strip).reject(&:empty?).filter_map do |tag|
|
|
74
|
+
v = tag.sub(/\Av/, "")
|
|
75
|
+
Gem::Version.correct?(v) ? [Gem::Version.new(v), tag] : nil
|
|
76
|
+
end
|
|
77
|
+
versions.min_by(&:first)&.last
|
|
44
78
|
end
|
|
45
79
|
end
|
|
46
80
|
|
|
@@ -49,6 +83,7 @@ verb = args.shift
|
|
|
49
83
|
changed = nil
|
|
50
84
|
ansi = false
|
|
51
85
|
template_path = nil
|
|
86
|
+
repo_flag = nil
|
|
52
87
|
positional = []
|
|
53
88
|
|
|
54
89
|
while (arg = args.shift)
|
|
@@ -60,6 +95,8 @@ while (arg = args.shift)
|
|
|
60
95
|
ansi = true
|
|
61
96
|
when "--all"
|
|
62
97
|
positional << "--all"
|
|
98
|
+
when "--repo"
|
|
99
|
+
repo_flag = args.shift or usage_abort("--repo needs a path")
|
|
63
100
|
when "--template"
|
|
64
101
|
template_path = args.shift or usage_abort("--template needs a path")
|
|
65
102
|
else
|
|
@@ -104,8 +141,8 @@ when "delivered"
|
|
|
104
141
|
usage_abort("usage: report-screen delivered <intent_dir>") unless target
|
|
105
142
|
intent_dir = File.expand_path(target)
|
|
106
143
|
usage_abort("#{intent_dir} is not an intent directory") unless IntentScreen.intent_dir?(intent_dir)
|
|
107
|
-
|
|
108
|
-
|
|
144
|
+
out = ReportScreen.render_delivered(intent_dir: intent_dir,
|
|
145
|
+
tag_reader: git_tag_reader(explicit_repo: repo_flag, script_dir: __dir__))
|
|
109
146
|
$stdout.write paint(out, ansi_enabled)
|
|
110
147
|
when "delay"
|
|
111
148
|
usage_abort("usage: report-screen delay <intent_dir>") unless target
|
package/skills/auto/SKILL.md
CHANGED
|
@@ -204,7 +204,8 @@ Then How.
|
|
|
204
204
|
5. Print `ruby ~/.plastic/scripts/report-screen state <intent_dir> --changed "How written, plan review next"`
|
|
205
205
|
(D15; see `references/human-report-contract.md` for the full trigger list). This replaces
|
|
206
206
|
the old prose State/Risk/Call briefing at this boundary. In auto mode the screen informs;
|
|
207
|
-
it does not wait.
|
|
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.
|
|
208
209
|
|
|
209
210
|
Then Exec.
|
|
210
211
|
|
|
@@ -276,7 +277,8 @@ Read `../plastic-conventions/references/completion-and-done.md` for what "intent
|
|
|
276
277
|
disarm (`/plastic-doctor check the lock status`); 6 means the structure check refused. Never
|
|
277
278
|
leave an orphaned worktree; run `git worktree prune` on a stale reference.
|
|
278
279
|
6. Print `ruby ~/.plastic/scripts/report-screen delivered <intent_dir>` once (D15): this is the
|
|
279
|
-
owner report at End, replacing the old prose Done briefing.
|
|
280
|
+
owner report at End, replacing the old prose Done briefing. Print it as the first thing in
|
|
281
|
+
the reply, nothing before it and no code fence, or the hook cannot paint it.
|
|
280
282
|
|
|
281
283
|
## Error Handling
|
|
282
284
|
|