@zalom/plastic 2.0.0-alpha.6 → 2.0.0-alpha.7

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.
@@ -24,6 +24,8 @@
24
24
  # ALWAYS handed off, whatever it looks like, since it is the one that must
25
25
  # not race. Ruby is the one place that actually waits (bounded, injectable
26
26
  # for tests); this script only ever decides once, fast, and never sleeps.
27
+ #
28
+ # Claude adapter: Claude Code only; the core is harness-agnostic.
27
29
  IFS= read -r -d '' INPUT # returns 1 at EOF: do NOT set -e
28
30
  case $INPUT in *'"message_id"'*) ;; *) exit 0 ;; esac
29
31
  rest=${INPUT#*\"message_id\"}; rest=${rest#*\"}; mid=${rest%%\"*}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "2.0.0-alpha.6",
3
+ "version": "2.0.0-alpha.7",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,6 +12,8 @@
12
12
  # This script is the one place in the O4 stack allowed to read ENV, the
13
13
  # clock, or Dir.tmpdir: MessageDisplay itself takes tmp_root/plastic_home/
14
14
  # color/now as constructor arguments and touches none of them directly.
15
+ #
16
+ # Claude adapter: Claude Code only; the core is harness-agnostic.
15
17
 
16
18
  require "json"
17
19
  require "time"
@@ -26,6 +26,10 @@
26
26
  # Exit codes:
27
27
  # 0 - the screen is on stdout
28
28
  # 2 - usage error, or the path is not an intent directory (one line on stderr)
29
+ #
30
+ # Harness-agnostic core: no harness assumption lives here. This script holds
31
+ # the plain/ANSI selection (intent 316a1, D3) and belongs to the same
32
+ # harness-agnostic core as lib/intent_screen.rb and lib/intent_screen_ansi.rb.
29
33
 
30
34
  require_relative "lib/intent_screen"
31
35
  require_relative "lib/intent_screen_ansi"
@@ -54,9 +54,11 @@ module HookRegistry
54
54
  { "name" => "capture", "status" => "Capturing prompt into the session ledger..." },
55
55
  ] },
56
56
  ],
57
- # Intent 316a (D6): Claude Code only, no Codex projection — MessageDisplay
58
- # is not one of CODEX_LIVE_STATE_EVENTS, so codex_hooks_json (below) never
59
- # picks it up; codex_hook_names stays exactly what it was (pinned by
57
+ # This entry belongs to the Claude adapter half of Plastic's
58
+ # harness-agnostic-core / Claude-adapter split (intent 316a1, D3
59
+ # supersedes 316a's D6): MessageDisplay is not one of
60
+ # CODEX_LIVE_STATE_EVENTS, so codex_hooks_json (below) never picks it
61
+ # up; codex_hook_names stays exactly what it was (pinned by
60
62
  # test/hook_registry_test.rb:82 and :110-111). Fires on every streamed
61
63
  # chunk of every assistant message (D11); the launcher (hooks/message-
62
64
  # display) decides with shell builtins and forks nothing on the common
@@ -12,6 +12,8 @@
12
12
  # text cut mid-sentence. `step_text`, `insight_fields` and `next_fields` are
13
13
  # public so the ANSI renderer reuses the exact same trims (D3) rather than
14
14
  # re-deriving them and drifting.
15
+ #
16
+ # Harness-agnostic core: no harness assumption lives here.
15
17
  module IntentScreen
16
18
  BAR_WIDTH = 20
17
19
  ON = "█"
@@ -15,6 +15,13 @@ require_relative "intent_screen"
15
15
  # `color:` is a constructor/call argument, never an environment read (D18):
16
16
  # the plain path (`color: false`) is one call away and testable without
17
17
  # touching NO_COLOR or a TTY. No ENV, no Dir.pwd, no Dir.home.
18
+ #
19
+ # Harness-agnostic core: no harness assumption lives here. `markdown_safe:`
20
+ # (intent 316a1, D3/D5) is the one choice a caller supplies rather than a
21
+ # choice this module makes for itself: a display surface that passes raw
22
+ # ANSI through untouched should not inherit a concession it never needed.
23
+ # See docs/reference/harness-adapters.md for which caller asks for it and
24
+ # why.
18
25
  module IntentScreenAnsi
19
26
  ESC = "\e"
20
27
  RESET = "#{ESC}[0m".freeze
@@ -32,7 +39,7 @@ module IntentScreenAnsi
32
39
 
33
40
  ELLIPSIS = "…"
34
41
 
35
- def self.render(intent_dir:, store_root:, color: true, width: DEFAULT_WIDTH)
42
+ def self.render(intent_dir:, store_root:, color: true, width: DEFAULT_WIDTH, markdown_safe: false)
36
43
  base = File.basename(intent_dir)
37
44
  id = base.split("--", 2).first
38
45
  intent_text = File.read(File.join(intent_dir, "#{base}.md"))
@@ -49,7 +56,7 @@ module IntentScreenAnsi
49
56
  fields.merge!(IntentScreen.progress_fields(items))
50
57
  fields.merge!(IntentScreen.next_fields(items, status, checklist_present: IntentScreen.items_present?(intent_dir), escape_pipes: false))
51
58
  fields.merge!(IntentScreen.insight_fields(intent_text, escape_pipes: false))
52
- fields.transform_values! { |v| clean(v) }
59
+ fields.transform_values! { |v| markdown_safe ? clean(v) : v }
53
60
 
54
61
  done_n = fields["progress.done"].to_i
55
62
  total_n = fields["progress.total"].to_i
@@ -103,7 +110,8 @@ module IntentScreenAnsi
103
110
  badge = status_cell(item[:done], color)
104
111
  prefix_plain = " #{num} [ #{item[:done] ? 'done' : 'open'} ] "
105
112
  text_budget = [width - prefix_plain.length, 0].max
106
- text = fit_plain(clean(IntentScreen.step_text(item[:text])), text_budget)
113
+ step = IntentScreen.step_text(item[:text])
114
+ text = fit_plain(markdown_safe ? clean(step) : step, text_budget)
107
115
  out << " #{num} [#{badge}] #{text}\n"
108
116
  end
109
117
  end
@@ -111,12 +119,13 @@ module IntentScreenAnsi
111
119
  out
112
120
  end
113
121
 
114
- # --- markdown-noise stripping (intent 316a, S1 answer 5 / matrix 19b) ------
122
+ # --- markdown-noise stripping, adapter-optional (intent 316a1, D3/D5) ------
115
123
  #
116
- # `displayContent` is still Markdown-processed by Claude Code even inside a
117
- # raw ANSI block (a live capture showed backticks silently stripped from
118
- # step text). Strip backticks and neutralise `*`/`_` runs from every value
119
- # before it reaches the block, so nothing is left for that pass to act on.
124
+ # Not every display surface passes text through a Markdown renderer, so
125
+ # stripping is not this module's call to make (see `markdown_safe:` on
126
+ # `render` above; the justification for WHY a caller would ever ask for
127
+ # this lives with that caller, in scripts/lib/message_display.rb). When
128
+ # asked, strips backticks and neutralises `*`/`_` runs from a value.
120
129
  # Single underscores are left alone: they are common inside ordinary words
121
130
  # (`intent_screen.rb`) and GFM does not treat an intraword underscore as
122
131
  # emphasis; only a run of 2+ (the bold marker `__`) is markdown-active.
@@ -126,9 +135,11 @@ module IntentScreenAnsi
126
135
 
127
136
  # --- width cap (D15, matrix 18) --------------------------------------------
128
137
 
129
- # Truncates `text` (already markdown-clean) to `max` visible columns with a
130
- # trailing ellipsis when cut, then yields the truncated plain text to the
131
- # block for coloring. Coloring never adds visible width.
138
+ # Truncates `text` to `max` visible columns with a trailing ellipsis when
139
+ # cut, then yields the truncated plain text to the block for coloring.
140
+ # Coloring never adds visible width. The cap itself is harness-neutral: a
141
+ # fixed width, not a re-flow, is what lets a column layout survive whatever
142
+ # display eventually shows it — no display's own wrapping is assumed here.
132
143
  def self.fit(text, max)
133
144
  plain = fit_plain(text, max)
134
145
  block_given? ? yield(plain) : plain
@@ -15,6 +15,11 @@ require_relative "store_provisioning"
15
15
  # the real Time.now/Kernel#sleep — the thin CLI (scripts/hook-message-display)
16
16
  # is the one place allowed to read any of those.
17
17
  #
18
+ # Claude adapter: Claude Code only; the core is harness-agnostic. (intent
19
+ # 316a1, D3 supersedes 316a's D6.) This is the sole caller that asks
20
+ # IntentScreenAnsi.render for `markdown_safe: true` (scripts/lib/
21
+ # intent_screen_ansi.rb) — see `finalize` below for why.
22
+ #
18
23
  # A live run under a real pty (round 3) found that Claude Code fires the
19
24
  # per-chunk hook processes CONCURRENTLY, not strictly in order. Chunk 0 is
20
25
  # the one that recognizes the screen and creates the buffer (D13), and it can
@@ -230,7 +235,13 @@ class MessageDisplay
230
235
  def finalize(buffered, decision)
231
236
  intent_dir = decision[:intent_dir]
232
237
  store_root = decision[:store_root]
233
- ansi = IntentScreenAnsi.render(intent_dir: intent_dir, store_root: store_root, color: true)
238
+ # markdown_safe: true (intent 316a1, D5) - Claude Code still Markdown-
239
+ # processes displayContent even inside a raw ANSI block (316a's live
240
+ # capture showed backticks silently stripped from step text), so the
241
+ # Claude adapter asks the harness-agnostic core to strip markdown noise
242
+ # before it ever reaches the block. A harness whose display surface
243
+ # passes raw ANSI through untouched would ask for false instead.
244
+ ansi = IntentScreenAnsi.render(intent_dir: intent_dir, store_root: store_root, color: true, markdown_safe: true)
234
245
  plain = IntentScreen.render(intent_dir: intent_dir, store_root: store_root, template: File.read(template_path))
235
246
  splice(buffered, plain, ansi)
236
247
  end