@zalom/plastic 1.0.0-beta.13 → 1.0.0-beta.15

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 CHANGED
@@ -79,10 +79,29 @@ The connection: an intent's `## Insights` feeds the Coordinator's Observe phase.
79
79
  | **How** | Planning | `plan.md` + `actions/` + `checklist.md` | `plastic-writing-plans` |
80
80
  | **Exec** | Execution | `outcome.md` | `plastic-executing-plan` |
81
81
 
82
- `## Insights` append-only work log captured throughout ALL stages. **Append-only means
83
- newest entry at the bottom; never prepend.** This ordering is a hard convention: Insights
84
- are the semantic trace of an intent, and a consistent newest-last order keeps that trace
85
- readable across every intent.
82
+ `## Insights` is the append-only log of durable discoveries captured throughout ALL stages.
83
+ An insight is a discovery worth keeping for later reads: novel, or old but newly relevant,
84
+ surfaced at any stage (What, Why, How, Exec). It is the most interesting residue of an
85
+ intent, the part a future reader most wants. **Append-only means newest entry at the bottom;
86
+ never prepend.** This ordering is a hard convention: Insights are the semantic trace of an
87
+ intent, and a consistent newest-last order keeps that trace readable across every intent.
88
+
89
+ Every entry leads with a fixed, machine-parseable prefix `{utc-iso8601} · {stage} · {author}`,
90
+ for example `2026-06-24T08:13:05Z · Why · plastic-brainstorming (autonomous)`. The UTC ISO8601
91
+ timestamp (to the second, trailing `Z`) is the same convention the savepoint ledger uses, so
92
+ the store has one timestamp convention. This per-entry prefix is not prepending the entry:
93
+ entries stay append-only, newest at the bottom; the prefix only stamps each line with when,
94
+ which stage, and who.
95
+
96
+ The blessed write path is the `insight-append` helper
97
+ (`scripts/insight-append <intent_dir> <text> --stage S --author A`), which formats the prefix,
98
+ validates it, and appends at the bottom. Hand-editing `## Insights` is an escape hatch; the
99
+ helper is the default so the format cannot drift.
100
+
101
+ Background sessions and dispatched sub-agents do not write the insight themselves. They carry
102
+ each nugget home in the completion report's `insights:` field, and the orchestrator (or any
103
+ agent that can write the file) persists it via the helper. A session that cannot write the
104
+ intent file still returns its report, so the insight survives.
86
105
  For full lifecycle detail, the skills in the Detail column have references/.
87
106
 
88
107
  `savepoint.md` — a deterministic, append-only ledger of cycle-step milestones (one line per
@@ -31,7 +31,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
31
31
 
32
32
  ## Completion Report
33
33
 
34
- END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers) plus the brainstorming payload:
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the brainstorming payload:
35
35
 
36
36
  - Decisions recorded in `### Decisions`, each with its one-line rationale
37
37
  - Context enriched: what was researched and the key findings
@@ -18,7 +18,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
18
18
 
19
19
  1. **Implement the actions** — make the code changes for each action in order
20
20
  2. **Track progress** — check off `checklist.md` items as they complete
21
- 3. **Record insights** — append observations to `## Insights` with the `(autonomous)` marker
21
+ 3. **Record insights** — capture durable discoveries and report them in the `insights:` field; persist each to `## Insights` via the `insight-append` helper (`scripts/insight-append <intent_dir> <text> --stage Exec --author "plastic-executor (autonomous)"`), the blessed write path that stamps the `{utc-iso8601} · {stage} · {author}` prefix
22
22
  4. **Prove it green** — run the full test suite and reach zero failures before reporting done
23
23
 
24
24
  ## How You Work
@@ -32,7 +32,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
32
32
 
33
33
  ## Completion Report
34
34
 
35
- END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers) plus the executor payload:
35
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the executor payload:
36
36
 
37
37
  - Actions implemented this turn, mapped to checklist items checked off (checked / total)
38
38
  - A summary of the code changed (files and the shape of the change)
@@ -31,7 +31,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
31
31
 
32
32
  ## Completion Report
33
33
 
34
- END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers) plus the planner payload, which EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR:
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the planner payload, which EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR:
35
35
 
36
36
  - The ordered actions, one line each: what the action does and how it is verified
37
37
  - Decomposition rationale: why this order, and why the actions are independent
@@ -31,7 +31,7 @@ When dispatched in auto mode you receive the standard Plastic spawn preamble (fr
31
31
 
32
32
  ## Completion Report
33
33
 
34
- END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers) plus the spec payload:
34
+ END your turn with a structured completion report as your final message, per the spawn preamble's `REPORT_CONTRACT` and `skills/auto/references/agent-report-contract.md`. Do not finish silently. Carry the common envelope (role, intent id, stage, status, artifacts written, verification, checklist deltas, deviations, blockers, insights) plus the spec payload:
35
35
 
36
36
  - The spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria)
37
37
  - How the recorded decisions resolved into the chosen approach
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "1.0.0-beta.13",
3
+ "version": "1.0.0-beta.15",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -108,6 +108,26 @@ def outcome_line(intent_dir)
108
108
  "(no outcome yet)"
109
109
  end
110
110
 
111
+ # The insights line: the LAST line of the intent file's `## Insights` section
112
+ # (the newest nugget, since entries are append-only newest-at-bottom), else
113
+ # `(none)`. Pure read of the intent dir, no clock.
114
+ def insights_line(intent_dir)
115
+ ifile = Bridge.intent_file(intent_dir)
116
+ return "(none)" unless File.exist?(ifile)
117
+
118
+ lines = File.read(ifile).split("\n", -1)
119
+ idx = lines.index { |l| l.strip == "## Insights" }
120
+ return "(none)" if idx.nil?
121
+
122
+ last = nil
123
+ (idx + 1).upto(lines.length - 1) do |i|
124
+ break if lines[i].start_with?("## ")
125
+
126
+ last = lines[i] unless lines[i].strip.empty?
127
+ end
128
+ last || "(none)"
129
+ end
130
+
111
131
  intent_dir_arg, role = parse_args(ARGV)
112
132
 
113
133
  if intent_dir_arg.nil? || intent_dir_arg.empty?
@@ -137,6 +157,7 @@ lines << "Status: synthesized (filesystem-derived; no agent-authored report)"
137
157
  lines << "Artifacts present: #{artifacts_str}"
138
158
  lines << "Checklist: #{checklist_progress(intent_dir)}"
139
159
  lines << "Outcome: #{outcome_line(intent_dir)}"
160
+ lines << "Insights: #{insights_line(intent_dir)}"
140
161
  lines << "=== end report ==="
141
162
 
142
163
  puts lines.join("\n")
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # insight-append - the blessed write path for an intent's `## Insights` section
6
+ # (intent 82). A thin CLI wrapper over Insights.append_insight: it formats the
7
+ # `{utc-iso8601} · {stage} · {author}` prefix, validates it, and appends one
8
+ # entry at the bottom of the section (creating the section or file if absent).
9
+ #
10
+ # Sibling to scripts/spawn-preamble and scripts/agent-report. The library's
11
+ # `now:` seam is the test seam; the CLI uses the default Time.now, which is fine
12
+ # because determinism is covered at the library level (test/insights_test.rb).
13
+ #
14
+ # Usage:
15
+ # insight-append <intent_dir> <text> --stage S --author A
16
+ #
17
+ # Exit codes: 0 (entry appended), 2 (usage).
18
+
19
+ require_relative "lib/insights"
20
+
21
+ def parse_args(argv)
22
+ stage = nil
23
+ author = nil
24
+ positional = []
25
+ i = 0
26
+ while i < argv.length
27
+ case argv[i]
28
+ when "--stage"
29
+ stage = argv[i + 1]
30
+ i += 2
31
+ when "--author"
32
+ author = argv[i + 1]
33
+ i += 2
34
+ else
35
+ positional << argv[i]
36
+ i += 1
37
+ end
38
+ end
39
+ [positional[0], positional[1], stage, author]
40
+ end
41
+
42
+ intent_dir, text, stage, author = parse_args(ARGV)
43
+
44
+ if [intent_dir, text, stage, author].any? { |v| v.nil? || v.to_s.empty? }
45
+ warn "usage: insight-append <intent_dir> <text> --stage S --author A"
46
+ exit 2
47
+ end
48
+
49
+ entry = Insights.append_insight(File.expand_path(intent_dir), text,
50
+ stage: stage, author: author)
51
+ puts "appended: #{entry}"
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ # insights.rb - the blessed write path for an intent's `## Insights` section
5
+ # (intent 82). A sibling library to bridge.rb so the insight write path stays
6
+ # independent of the savepoint ledger (the spec forbids touching the ledger).
7
+ #
8
+ # Every entry leads with a fixed, machine-parseable prefix
9
+ # `{utc-iso8601} · {stage} · {author}` where the timestamp is `now.utc.iso8601`,
10
+ # the SAME convention `Bridge.append_savepoint_line` uses, so the store has one
11
+ # timestamp convention across both ledgers. Entries are append-only, newest at
12
+ # the BOTTOM (the [[34]] ordering law): the per-entry prefix is not prepending
13
+ # the entry, and existing entries are never reordered.
14
+
15
+ require "time"
16
+ require_relative "bridge"
17
+
18
+ module Insights
19
+ HEADING = "## Insights"
20
+ # The middle dot (U+00B7) with a single space on each side separates the
21
+ # timestamp, stage, and author fields.
22
+ SEPARATOR = " · "
23
+ # A well-formed prefix: UTC ISO8601 timestamp to the second with a trailing
24
+ # `Z`, then a non-empty stage, then a non-empty author, each ` · `-separated.
25
+ PREFIX_RE = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z · [^·]+ · [^·]+/.freeze
26
+
27
+ # Append one insight entry at the BOTTOM of the `## Insights` section of the
28
+ # intent file, creating the section (or the file) if absent. `now:` is the DI
29
+ # seam: defaults to Time.now, tests inject Time.utc(...). Raises ArgumentError
30
+ # if the assembled prefix is not well-formed (guard on write).
31
+ def self.append_insight(intent_dir, text, stage:, author:, now: Time.now)
32
+ prefix = "#{now.utc.iso8601}#{SEPARATOR}#{stage}#{SEPARATOR}#{author}"
33
+ unless valid_insight_prefix?(prefix)
34
+ raise ArgumentError, "malformed insight prefix: #{prefix.inspect}"
35
+ end
36
+
37
+ entry = "#{prefix} — #{text}"
38
+ path = Bridge.intent_file(intent_dir)
39
+ File.write(path, with_entry(File.exist?(path) ? File.read(path) : "", entry))
40
+ entry
41
+ end
42
+
43
+ # Pure validator: true when `line` begins with a well-formed prefix. No IO,
44
+ # no clock. Rejects a date-only prefix, a missing separator, a missing `Z`,
45
+ # and sub-second precision.
46
+ def self.valid_insight_prefix?(line)
47
+ line.is_a?(String) && line.match?(PREFIX_RE)
48
+ end
49
+
50
+ # Insert `entry` at the bottom of the `## Insights` section of `content`,
51
+ # preserving every existing line and their order. Pure string transform.
52
+ def self.with_entry(content, entry)
53
+ lines = content.empty? ? [] : content.split("\n", -1)
54
+ heading_idx = lines.index { |l| l.strip == HEADING }
55
+ return append_new_section(lines, entry) if heading_idx.nil?
56
+
57
+ insert_at = section_end(lines, heading_idx)
58
+ lines.insert(insert_at, entry)
59
+ lines.join("\n")
60
+ end
61
+
62
+ # The insertion index for a new entry: just after the last non-empty content
63
+ # line that belongs to the `## Insights` section (before the next `## `
64
+ # heading or EOF).
65
+ def self.section_end(lines, heading_idx)
66
+ last_content = heading_idx
67
+ idx = heading_idx + 1
68
+ while idx < lines.length
69
+ break if lines[idx].start_with?("## ")
70
+
71
+ last_content = idx unless lines[idx].strip.empty?
72
+ idx += 1
73
+ end
74
+ last_content + 1
75
+ end
76
+
77
+ # Append a fresh `## Insights` section (blank line, heading, entry) to the end
78
+ # of the existing content.
79
+ def self.append_new_section(lines, entry)
80
+ body = lines.join("\n")
81
+ body = body.sub(/\n+\z/, "") unless body.empty?
82
+ pieces = body.empty? ? [] : [body, ""]
83
+ pieces << HEADING << entry
84
+ "#{pieces.join("\n")}\n"
85
+ end
86
+ end
@@ -213,6 +213,7 @@ class InstallerCore
213
213
  "scripts/lib/retrieval_gate.rb" => "scripts/lib/retrieval_gate.rb",
214
214
  "scripts/hook-auto-arm" => "scripts/hook-auto-arm",
215
215
  "scripts/lib/bridge.rb" => "scripts/lib/bridge.rb",
216
+ "scripts/lib/insights.rb" => "scripts/lib/insights.rb",
216
217
  "scripts/lib/worktree.rb" => "scripts/lib/worktree.rb",
217
218
  "scripts/lib/boot_banner.rb" => "scripts/lib/boot_banner.rb",
218
219
  "scripts/lib/qmd_sync.rb" => "scripts/lib/qmd_sync.rb",
@@ -222,6 +223,8 @@ class InstallerCore
222
223
  "scripts/lib/frontmatter_writer.rb" => "scripts/lib/frontmatter_writer.rb",
223
224
  "scripts/lib/links_projection.rb" => "scripts/lib/links_projection.rb",
224
225
  "scripts/lib/links_section.rb" => "scripts/lib/links_section.rb",
226
+ "scripts/project-links" => "scripts/project-links",
227
+ "scripts/rebuild-graph" => "scripts/rebuild-graph",
225
228
  "scripts/validate-intent" => "scripts/validate-intent",
226
229
  "scripts/new-intent" => "scripts/new-intent",
227
230
  "scripts/hook-create-gate" => "scripts/hook-create-gate",
@@ -36,7 +36,7 @@ module RetrievalGate
36
36
  SERENA_EXTENSIONS = %w[
37
37
  rb js jsx ts tsx mjs cjs py go rs java kt scala c h cpp hpp cc
38
38
  cs php rb swift sh bash zsh lua ex exs erl clj sql
39
- json yaml yml toml md markdown
39
+ json yaml yml toml
40
40
  ].freeze
41
41
 
42
42
  # Image / binary extensions that are always allowed (plain read is fine).
@@ -41,10 +41,16 @@ REPORT_CONTRACT =
41
41
  "FINAL MESSAGE (your return value), not a side-channel file. Do not go idle or " \
42
42
  "finish silently. The report carries a common envelope: role, intent id, stage, " \
43
43
  "status (delivered or blocked), artifacts written, verification or tests run, " \
44
- "checklist deltas, deviations from spec, and blockers or handoff notes; plus a " \
44
+ "checklist deltas, deviations from spec, blockers or handoff notes, and an " \
45
+ "insights field carrying 0..N durable nuggets (what I discovered worth keeping, " \
46
+ "the most interesting residue of this turn; none if there were none); plus a " \
45
47
  "role-specific payload that fulfils your place in the What, Why, How, Exec cycle " \
46
- "(for example the planner explains the plan back to the orchestrator). Keep the " \
47
- "report prose-stripped: the envelope and payload only, no greeting, no preamble, " \
48
+ "(for example the planner explains the plan back to the orchestrator). Populate " \
49
+ "the insights field even when you cannot write the intent file yourself: a " \
50
+ "background or dispatched agent carries each nugget home in the report and the " \
51
+ "orchestrator persists it via scripts/insight-append, so an insight never " \
52
+ "depends on the discovering session's file-write access. Keep the report " \
53
+ "prose-stripped: the envelope and payload only, no greeting, no preamble, " \
48
54
  "no end-recap, no restating of the task; reasoning stays in the thinking channel. " \
49
55
  "See skills/auto/references/agent-report-contract.md for the per-role format."
50
56
 
@@ -39,6 +39,11 @@ Every role report, whatever the stage, carries these fields:
39
39
  - **Checklist deltas**: which checklist items this turn checked off (executor), or `n/a`.
40
40
  - **Deviations from spec**: anything done differently from the spec or plan, and why, or `none`.
41
41
  - **Blockers / handoff notes**: what the next stage must watch for, or `none`.
42
+ - **Insights**: 0..N durable nuggets discovered this turn (the most interesting residue),
43
+ each one a `## Insights`-worthy line; `none` if there were none. Background and dispatched
44
+ agents MUST populate this: they carry each nugget home in the report and the orchestrator
45
+ persists it (see Insights delivery below), so an insight never depends on the discovering
46
+ session having file-write access.
42
47
 
43
48
  ## Per-role payload
44
49
 
@@ -49,11 +54,13 @@ D2). The payload is what makes the report useful to the orchestrator beyond the
49
54
  - Decisions recorded in `### Decisions`, each with its one-line rationale.
50
55
  - Context enriched: what was researched and the key findings.
51
56
  - Open questions resolved, and any deliberately left for the spec.
57
+ - Insights: durable discoveries from the Why exploration, reported in the `insights:` field.
52
58
 
53
59
  ### spec-specialist (Why to How boundary)
54
60
  - Spec sections produced (Problem, Goals, Non-Goals, Approach, Decisions, Acceptance Criteria).
55
61
  - How the recorded decisions resolved into the chosen approach.
56
62
  - Acceptance-criteria count, so the planner knows the surface to cover.
63
+ - Insights: durable discoveries from consolidating the spec, reported in the `insights:` field.
57
64
 
58
65
  ### planner (How): worked exemplar
59
66
  The planner report EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR. It carries:
@@ -62,17 +69,20 @@ The planner report EXPLAINS THE PLAN BACK TO THE ORCHESTRATOR. It carries:
62
69
  - Checklist coverage: item count and that every action plus suite-green is covered.
63
70
  This is the exemplar because the plan is an argument, and the orchestrator gates on whether that
64
71
  argument is sound before any code is written.
72
+ - Insights: durable discoveries from planning, reported in the `insights:` field.
65
73
 
66
74
  ### executor (Exec)
67
75
  - Actions implemented this turn, mapped to checklist items checked off (checked / total).
68
76
  - A summary of the code changed (files and the shape of the change).
69
77
  - Test result: the full-suite command and its pass / fail counts.
70
- - Insights appended, with the `(autonomous)` marker.
78
+ - Insights reported in the `insights:` field (each with the `(autonomous)` marker); the
79
+ executor or the orchestrator persists them to `## Insights` via the `insight-append` helper.
71
80
 
72
81
  ### final reviewer (final gate)
73
82
  - Verdict: `pass` or `blockers found`.
74
83
  - Each acceptance criterion checked, with the evidence that confirms or refutes it.
75
84
  - Gaps or risks found, ranked, with a recommended disposition.
85
+ - Insights: durable discoveries from the review, reported in the `insights:` field.
76
86
 
77
87
  ## Fallback: always a report
78
88
 
@@ -92,3 +102,20 @@ ledger, the lifecycle artifacts present, the checklist checked / total, and the
92
102
  line, and emits a filesystem-derived report labelled `synthesized`. So a handoff account always
93
103
  exists: authored by the agent when possible, reconstructed deterministically when not. This
94
104
  formalizes the by-hand reconstruction the orchestrator did while delivering intent 68.
105
+
106
+ ## Insights delivery
107
+
108
+ Insights ride home in the completion report. Every agent reports its durable nuggets in the
109
+ `insights:` field; the orchestrator (or any agent that can write the intent file) then persists
110
+ each one via the helper:
111
+
112
+ ```
113
+ scripts/insight-append <intent_dir> <text> --stage S --author A
114
+ ```
115
+
116
+ The helper formats the `{utc-iso8601} · {stage} · {author}` prefix (the same timestamp
117
+ convention as the savepoint ledger), validates it, and appends the entry at the bottom of the
118
+ `## Insights` section, newest last. This is the fix for dropped background and sub-agent
119
+ insights: a session that cannot write the intent file still returns its report, so the insight
120
+ survives and the orchestrator writes it on receipt. Hand-editing `## Insights` is an escape
121
+ hatch; the helper is the default so the prefix format cannot drift.