@zalom/plastic 2.0.0-alpha.16 → 2.0.0-alpha.17

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalom/plastic",
3
- "version": "2.0.0-alpha.16",
3
+ "version": "2.0.0-alpha.17",
4
4
  "description": "Intent-driven idea development system for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
@@ -577,30 +577,69 @@ module ReportScreen
577
577
  rows.compact
578
578
  end
579
579
 
580
- # Intent 331b (plan.md, "The one non-additive edit"): the standalone-token
581
- # rule, extracted so `action_file_for` (the plan screen's Action column)
582
- # calls the exact same rule as `matching_action_heading` and the two can
583
- # never drift on what counts as a match. `matching_action_heading`'s own
584
- # signature, return shape and behavior are unchanged (row P16).
585
- def self.heading_tokens(heading)
586
- heading.to_s.sub(/\A#+\s*/, "").split(/[^A-Za-z0-9]+/)
587
- end
588
-
589
- # Rows 25-27: D19 - the label must appear as a standalone token in an action
590
- # file heading (any level); the count is the matched section's table rows only.
591
- def self.matching_action_heading(intent_dir, label)
580
+ # Intent 331b (plan.md, "The one non-additive edit"): the standalone-token
581
+ # rule, extracted so `action_file_for` (the plan screen's Action column)
582
+ # calls the exact same rule as `matching_action_heading` and the two can
583
+ # never drift on what counts as a match.
584
+ def self.heading_tokens(heading)
585
+ heading.to_s.sub(/\A#+\s*/, "").split(/[^A-Za-z0-9]+/)
586
+ end
587
+
588
+ # Rows 25-27: D19/D1r - the label must appear as a standalone token in an
589
+ # action file heading (any level), AND that heading must own at least one
590
+ # matrix data row - a heading that only names the label, with no table
591
+ # beneath it (or a table with a separator but no data row), is skipped and
592
+ # the walk keeps going. Lexicographic path order (D8), then file order.
593
+ #
594
+ # Merge note (322 into alpha, 2026-09-05): 322's table-owning rule and 331b's
595
+ # extracted `heading_tokens` are both kept. The token split now comes from the
596
+ # shared helper so `action_file_for` cannot drift from this walk, while the
597
+ # `table_rows(body).any?` guard stays the thing that decides the match.
598
+ def self.matching_action_heading(intent_dir, label)
599
+ Dir.glob(File.join(intent_dir, "actions", "*.md")).sort.each do |path|
600
+ split_by_headings(File.read(path)).each do |heading, body|
601
+ next unless heading_tokens(heading).include?(label)
602
+ return [heading, body] if table_rows(body).any?
603
+ end
604
+ end
605
+ [nil, nil]
606
+ end
607
+
608
+ # D3r: the row-cell fallback, for the shape where the label never appears in
609
+ # a heading at all, only as the first cell of a matrix data row. Restricted
610
+ # to tables under a heading that names itself a matrix (/matrix/i) - never a
611
+ # step list or any other table - so it cannot answer for a record that has
612
+ # no matrix anywhere (the close-gate defeat the plan review measured).
613
+ # Emphasis (bold/italic/code) is stripped from the compared cell; the count
614
+ # sums matching rows across every matrix heading, in every action file.
615
+ def self.matching_matrix_rows(intent_dir, label)
616
+ count = 0
592
617
  Dir.glob(File.join(intent_dir, "actions", "*.md")).sort.each do |path|
593
618
  split_by_headings(File.read(path)).each do |heading, body|
594
- return [heading, body] if heading_tokens(heading).include?(label)
619
+ next unless heading.to_s.match?(/matrix/i)
620
+ table_rows(body).each do |cells|
621
+ cell = cells[0].to_s.gsub(/[*_`]/, "").strip
622
+ count += 1 if cell == label
623
+ end
595
624
  end
596
625
  end
597
- [nil, nil]
626
+ count
598
627
  end
599
628
 
629
+ # D7: a label with no letter never resolves, on either path - it is a
630
+ # bullet-derived Delivered number (delivered_rows), never a label anyone
631
+ # wrote, and would otherwise fabricate proof from a numbered heading like
632
+ # "## 1. What this intent is" or from a numbered matrix row-cell column.
600
633
  def self.proven_by(intent_dir, label)
634
+ return NOT_RECORDED unless label.to_s.match?(/[A-Za-z]/)
635
+
601
636
  _heading, body = matching_action_heading(intent_dir, label)
602
- return NOT_RECORDED unless body
603
- n = table_rows(body).length
637
+ if body
638
+ n = table_rows(body).length
639
+ return n.positive? ? "#{n} test#{n == 1 ? '' : 's'}" : NOT_RECORDED
640
+ end
641
+
642
+ n = matching_matrix_rows(intent_dir, label)
604
643
  n.positive? ? "#{n} test#{n == 1 ? '' : 's'}" : NOT_RECORDED
605
644
  end
606
645
 
@@ -67,9 +67,10 @@ fill `## Summary`, `## Delivered`, `## Verification`, `## Follow-ups`. `## Deliv
67
67
  `| Row | What |` table: one row per thing delivered, in plain wording a reader
68
68
  recognizes, not a method name or an implementation summary (that detail
69
69
  belongs in `## Summary`). Each row's label must appear as a standalone token
70
- in an action-file heading (`### S1 - ...` proves row S1); that heading's
71
- matrix rows become the row's Proven-by cell on `report-screen delivered`'s
72
- post-delivery screen. `## Needs you` is the literal None or a
70
+ in an action-file heading that owns the matrix table (`### S1 - ...` with a
71
+ table beneath it proves row S1; a table-less heading naming the label is
72
+ skipped); that heading's matrix rows become the row's Proven-by cell on
73
+ `report-screen delivered`'s post-delivery screen. `## Needs you` is the literal None or a
73
74
  `| N | What | Why |` table. On abandon, `## Summary` states the abandonment reason and the trail (see Pivot
74
75
  below). A placeholder outcome.md is backfilled from the record instead, with the
75
76
  close's disposition and the `--outcome-summary` line as its summary. Also author
@@ -75,7 +75,7 @@ Apply the auto skill's risk rule to the executor's return and the diff: a matrix
75
75
 
76
76
  Whenever a review verdict returns - the plan review before code, or the post-execution review above - the lead appends a `Review` line: `ruby ~/.plastic/scripts/savepoint-note <intent_dir> --kind Review --text "<verdict, what changed>"` (intent 317, D17). This is the other half of what `report-screen delay` reads.
77
77
 
78
- **The D19 heading convention.** An action file's `## Delivered` row (in `outcome.md`) is proven by whichever `actions/ACTION_N.md` heading carries that row's label as a standalone token - `### Row A -` proves row A, `### S1 -` proves row S1. Write action-file section headings so the label they prove is unambiguous (never a substring another label could also match, like `A` inside `AB`); `report-screen delivered`'s Proven-by column renders `not recorded` when no heading matches.
78
+ **The D19 heading convention.** An action file's `## Delivered` row (in `outcome.md`) is proven by the first `actions/ACTION_N.md` heading that carries that row's label as a standalone token AND owns the matrix table (322 D1r) - `### Row A -` with a table beneath it proves row A, `### S1 -` proves row S1; a heading that only names the label, with no table under it, is skipped. Write action-file section headings so the label they prove is unambiguous (never a substring another label could also match, like `A` inside `AB`); `report-screen delivered`'s Proven-by column renders `not recorded` when no heading owns a matching table and no matrix row cell carries the label either.
79
79
 
80
80
  ### Step 4: Update Intent and Complete
81
81
  Capture observations in `## Insights`. When ALL checklist items are checked:
@@ -10,8 +10,11 @@ disposition: delivered|abandoned
10
10
  <!-- One row per thing delivered, in plain wording a reader recognizes, not
11
11
  an implementation summary; the technical detail belongs in ## Summary. Each
12
12
  row's label must appear as a standalone token in an actions/*.md heading
13
- (for example "### S1 - ..." proves row S1): that heading's matrix rows become
14
- the row's Proven-by cell on the delivered screen (intent 317 D19, 317a). -->
13
+ that owns the matrix table (for example "### S1 - ..." with a table beneath
14
+ it proves row S1); that heading's matrix rows become the row's Proven-by
15
+ cell on the delivered screen (intent 317 D19, 317a, 322 D1r). A label with no
16
+ owning heading falls back to a matrix row cell that carries it, when one
17
+ under a heading named "matrix" exists (322 D3r). -->
15
18
  | Row | What |
16
19
  | --- | --- |
17
20
  | S1 | ... |