@zalom/plastic 2.0.0-alpha.18 → 2.0.0-alpha.19

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.
Files changed (45) hide show
  1. package/package.json +2 -2
  2. package/scripts/dashboard.rb +20 -0
  3. package/scripts/doctor.rb +120 -2
  4. package/scripts/end-intent +134 -8
  5. package/scripts/lib/action_graph_shim.rb +277 -0
  6. package/scripts/lib/atomic_write.rb +31 -0
  7. package/scripts/lib/graph_edges.rb +121 -0
  8. package/scripts/lib/graph_file.rb +246 -0
  9. package/scripts/lib/guarded_append.rb +155 -0
  10. package/scripts/lib/installer_core.rb +32 -0
  11. package/scripts/lib/node_file.rb +214 -0
  12. package/scripts/lib/node_ids.rb +99 -0
  13. package/scripts/lib/node_ledger.rb +377 -0
  14. package/scripts/lib/node_packet.rb +873 -0
  15. package/scripts/lib/outcome_report.rb +440 -0
  16. package/scripts/lib/packet_wrapper.rb +132 -0
  17. package/scripts/lib/ready_set.rb +462 -0
  18. package/scripts/lib/release_guard.rb +16 -0
  19. package/scripts/lib/report_screen.rb +122 -12
  20. package/scripts/lib/roadmap_queue.rb +161 -3
  21. package/scripts/lib/roadmap_savepoint.rb +26 -5
  22. package/scripts/lib/savepoint.rb +123 -12
  23. package/scripts/lib/work_graph_validator.rb +201 -0
  24. package/scripts/node-packet +92 -0
  25. package/scripts/node-transition +291 -0
  26. package/scripts/outcome-report +74 -0
  27. package/scripts/ready-set +126 -0
  28. package/scripts/release-check +118 -0
  29. package/scripts/report-screen +8 -1
  30. package/scripts/roadmap-savepoint +7 -0
  31. package/scripts/validate-work-graph +39 -0
  32. package/skills/auto/SKILL.md +2 -3
  33. package/skills/auto/references/human-report-contract.md +3 -2
  34. package/skills/intent-continuing/references/boarding-matrix.md +1 -0
  35. package/skills/intent-ending/SKILL.md +30 -19
  36. package/skills/intent-executing/SKILL.md +1 -1
  37. package/skills/releasing/SKILL.md +39 -0
  38. package/skills/releasing/references/promotion-and-tagging.md +10 -6
  39. package/skills/releasing/references/release-lines.md +1 -1
  40. package/templates/graph.md +16 -0
  41. package/templates/node-decision.md +11 -0
  42. package/templates/node-research.md +11 -0
  43. package/templates/node-verify.md +13 -0
  44. package/templates/node-work.md +22 -0
  45. package/templates/outcome.md +8 -6
@@ -17,6 +17,7 @@
17
17
  # report-screen session <tier_root> [--session <id>] [--since <iso>]
18
18
  # [--ledger-root <dir>] [--ansi]
19
19
  # report-screen roadmap <roadmap.md> plan|state|delivered [--ansi] [--store-root <dir>]
20
+ # report-screen archive <store_root> [--ansi]
20
21
  #
21
22
  # --ansi delegates to ScreenPaint (intent 317a, D1), the parser/re-layouter
22
23
  # in the shared TUI core. Selection is by capability, never by harness:
@@ -267,8 +268,14 @@ when "roadmap"
267
268
  roadmap_store_root = store_root_flag ? File.expand_path(store_root_flag) : nil
268
269
  out = ReportScreen.render_roadmap(path: roadmap_path, verb: sub_verb, store_root: roadmap_store_root)
269
270
  $stdout.write paint(out, ansi_enabled)
271
+ when "archive"
272
+ usage_abort("usage: report-screen archive <store_root>") unless target
273
+ store_root = File.expand_path(target)
274
+ usage_abort("#{store_root} is not a store (no INDEX.md)") unless File.exist?(File.join(store_root, "INDEX.md"))
275
+ out = ReportScreen.render_archive(store_root)
276
+ $stdout.write paint(out, ansi_enabled)
270
277
  else
271
- usage_abort("unknown verb #{verb.inspect} (use plan|state|delivered|delay|session|roadmap)")
278
+ usage_abort("unknown verb #{verb.inspect} (use plan|state|delivered|delay|session|roadmap|archive)")
272
279
  end
273
280
 
274
281
  exit 0
@@ -18,6 +18,7 @@
18
18
  # rebuilds an empty ledger.
19
19
 
20
20
  require_relative "lib/roadmap_savepoint"
21
+ require_relative "lib/guarded_append"
21
22
 
22
23
  def opt(args, name)
23
24
  (i = args.index(name)) && args[i + 1]
@@ -47,6 +48,12 @@ when "append"
47
48
  rescue ArgumentError => e
48
49
  warn "append: #{e.message}"
49
50
  exit 2
51
+ rescue GuardedAppend::Unavailable => e
52
+ # Intent 335 (spec D14): the write guard could not be taken; nothing was written. Exit 4
53
+ # rather than let a backtrace reach the six skill recipes that drive this CLI - it rescues
54
+ # ArgumentError alone today and would otherwise print one straight to the terminal.
55
+ warn "append: #{e.message}"
56
+ exit 4
50
57
  end
51
58
 
52
59
  when "rebuild"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ # frozen_string_literal: true
4
+
5
+ # validate-work-graph - deterministic CLI over WorkGraphValidator (intent 334, n4).
6
+ #
7
+ # Checks whether one intent directory's graph.md and nodes/ form a valid work
8
+ # graph: needs targets resolve, node files reconcile with the declared graph,
9
+ # per-kind body sections are present, and (above the trivial bar) a verify
10
+ # node gates two or more work nodes with a failure-mode matrix per work node.
11
+ # Modelled on scripts/validate-intent: errors to stderr, same exit-code shape.
12
+ #
13
+ # Usage:
14
+ # validate-work-graph <intent_dir>
15
+ #
16
+ # Exit codes: 0 (valid), 1 (invalid or unreadable; report on stderr),
17
+ # 2 (usage - no argument given).
18
+
19
+ require_relative "lib/work_graph_validator"
20
+
21
+ intent_dir = ARGV.find { |a| !a.start_with?("--") }
22
+
23
+ if intent_dir.nil?
24
+ warn "usage: validate-work-graph <intent_dir>"
25
+ exit 2
26
+ end
27
+
28
+ dir = File.expand_path(intent_dir)
29
+ result = WorkGraphValidator.validate(dir)
30
+
31
+ if result[:ok]
32
+ puts "OK: #{dir}"
33
+ exit 0
34
+ end
35
+
36
+ warn "INVALID: #{dir}"
37
+ result[:missing].each { |m| warn "missing: #{m}" }
38
+ result[:errors].each { |e| warn e }
39
+ exit 1
@@ -169,6 +169,7 @@ ledger is missing (then rebuild it with `Savepoint.rebuild_savepoint`).
169
169
  | `How plan.md created` / `How checklist.md created` / `Exec started` | Exec (verify plan, matrix, checklist) |
170
170
  | `Exec outcome.md created` | Exec done; complete the intent |
171
171
  | `Done delivered|abandoned` | Terminal; do not resume |
172
+ | A node or `Intent` transition line (`n1 running ...`, `Intent needs_decision ...`) | Exec; a graph delivery is in progress - read node status through `NodeLedger.status` before dispatching anything, never re-derive it by eye |
172
173
 
173
174
  Filesystem fallback, in order: `checklist.md` with items checked means resume Exec from the
174
175
  first unchecked item; `plan.md` plus `checklist.md` means enter Exec; `spec.md` alone means
@@ -184,9 +185,7 @@ Announce which stage you are entering and why.
184
185
  3. Decide: pick the best option per gap, record it in `## Context > ### Decisions` with the
185
186
  rationale, and log it in `## Insights` with the `(autonomous)` marker through
186
187
  `scripts/insight-append`.
187
- 4. Write `spec.md`.
188
-
189
- Then How.
188
+ 4. Write `spec.md`. Then How.
190
189
 
191
190
  ## How (the lead), then the plan review
192
191
 
@@ -102,8 +102,9 @@ A delivery still ends with `outcome.md` plus one `delivered` screen.
102
102
 
103
103
  ## One report per audience
104
104
 
105
- A delivery produces exactly two artifacts: `outcome.md` (authored by `plastic-intent-ending`)
106
- and one `delivered` screen at the End stage. No stage or skill restates a delivery already
105
+ A delivery produces exactly two artifacts: `outcome.md` (generated by `scripts/end-intent`
106
+ from `graph.md`, `nodes/`, and the ledger when the intent has one, intent 339; hand-authored
107
+ by `plastic-intent-ending` otherwise) and one `delivered` screen at the End stage. No stage or skill restates a delivery already
107
108
  written to `outcome.md`; point at it instead. Skills do not open with a banner that names the
108
109
  skill or restates the intent id and name the owner just typed. Announce only what the reader
109
110
  cannot already know: an error, a result, a choice with its reason, or a handoff.
@@ -13,6 +13,7 @@ Classify from the last line alone, then verify only that line's artifact is real
13
13
  | `How checklist.md created` / `Exec started` | How | **Exec** | do the work, check off the checklist |
14
14
  | `Exec outcome.md created` | Exec | **ready to complete** | the ending procedure (`plastic-intent-ending`) |
15
15
  | `Done delivered` / `Done abandoned` | terminal | **report only** | immutable; ask what is next |
16
+ | A node or `Intent` transition line (`n1 running ...`, `Intent needs_decision ...`) | Exec | **Exec** | a graph delivery is in progress; read node status through `NodeLedger.status`, never re-derive it by eye |
16
17
 
17
18
  ## Per-stage behaviour (what "continue" means)
18
19
 
@@ -51,28 +51,39 @@ on disk is what the record becomes, so before the call:
51
51
  reported gap that lands verbatim in the backfilled `## Follow-ups`.
52
52
  2. Confirm every acceptance criterion in spec.md is verifiable (tests pass,
53
53
  or the manual check described in its HOW line was actually run).
54
- 3. Decide what you have to say. A spec.md, plan.md, action file, or outcome.md
55
- left as the scaffold placeholder is written from the record by
56
- `scripts/end-intent` (the intent file's `## Intent`, `### Decisions`, and
57
- `## Insights`, the checklist, the diff on the intent's own worktree). A
58
- file you wrote, even under a still-present sentinel, is never touched.
59
- Write outcome.md yourself when the summary deserves more than the
60
- `--outcome-summary` line; otherwise let the backfill carry it.
54
+ 3. Decide what you have to say. For an intent with a `graph.md`, `outcome.md` is
55
+ GENERATED by `scripts/end-intent` itself, through `scripts/lib/outcome_report.rb`
56
+ (`scripts/outcome-report` is its standalone CLI, useful for checking the
57
+ generated text before the close): `## Delivered`, `## Verification`,
58
+ `## Graph diff`, and `## Findings` are read straight from `graph.md`, `nodes/`,
59
+ and the ledger every time, never hand-typed. `## Summary`, `## Needs you`, and
60
+ `## Follow-ups`, and every frontmatter key but `disposition`, are preserved
61
+ byte for byte when you author them and generated as plain facts otherwise, so
62
+ writing a summary yourself still works exactly as before. For an intent with
63
+ no `graph.md`, or when the generated text would be refused by the close's own
64
+ gates, a spec.md, plan.md, action file, or outcome.md left as the scaffold
65
+ placeholder is written from the record by `scripts/end-intent` (the intent
66
+ file's `## Intent`, `### Decisions`, and `## Insights`, the checklist, the
67
+ diff on the intent's own worktree). A file you wrote, even under a
68
+ still-present sentinel, is never touched.
61
69
 
62
70
  ### Step 1-5. Run `scripts/end-intent`
63
71
 
64
- Author outcome.md yourself when it deserves prose: copy `templates/outcome.md`,
65
- set the frontmatter to `disposition: delivered` or `disposition: abandoned`, and
66
- fill `## Summary`, `## Delivered`, `## Verification`, `## Follow-ups`. `## Delivered` is a
67
- `| Row | What |` table: one row per thing delivered, in plain wording a reader
68
- recognizes, not a method name or an implementation summary (that detail
69
- belongs in `## Summary`). Each row's label must appear as a standalone token
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
74
- `| N | What | Why |` table. On abandon, `## Summary` states the abandonment reason and the trail (see Pivot
75
- below). A placeholder outcome.md is backfilled from the record instead, with the
72
+ Author outcome.md yourself when the summary deserves more prose than the generator's
73
+ plain facts: copy `templates/outcome.md`, set the frontmatter to `disposition: delivered`
74
+ or `disposition: abandoned`, and fill `## Summary`, `## Delivered`, `## Verification`,
75
+ `## Follow-ups`. `## Delivered` is a `| Row | What |` table: one row per thing delivered,
76
+ in plain wording a reader recognizes, not a method name or an implementation summary
77
+ (that detail belongs in `## Summary`). Each row's label must appear as a standalone token
78
+ in an action-file heading (or a node-file heading, for an intent whose graph replaces
79
+ actions/ with nodes/) that owns the matrix table (`### S1 - ...` or
80
+ `## n1 failure-mode matrix` with a table beneath it proves that row; a table-less
81
+ heading naming the label is skipped); that heading's matrix rows become the row's
82
+ Proven-by cell on `report-screen delivered`'s post-delivery screen. `## Needs you` is
83
+ the literal None or a `| N | What | Why |` table. On abandon, `## Summary` states the
84
+ abandonment reason and the trail (see Pivot below). An intent with a `graph.md` and no
85
+ hand-written outcome.md gets one generated instead (Step 0.3 above); a placeholder
86
+ outcome.md on an intent with no `graph.md` is backfilled from the record, with the
76
87
  close's disposition and the `--outcome-summary` line as its summary. Also author
77
88
  the rich INDEX entry note now (a short line in the store's existing
78
89
  Completed/Abandoned convention: mode, what shipped or why it was
@@ -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 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.
78
+ **The D19 heading convention.** An action file's `## Delivered` row (in `outcome.md`) is proven by the first `actions/ACTION_N.md` OR `nodes/*.md` heading that carries that row's label as a standalone token AND owns the matrix table (322 D1r, 334 D10r) - `### Row A -` with a table beneath it proves row A, `### S1 -` proves row S1, `## n1 failure-mode matrix` proves row n1; a heading that only names the label, with no table under it, is skipped. Readers check `actions/` first, then `nodes/` (334 D15r). Write action-file or node-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:
@@ -206,6 +206,45 @@ The dist-tag is derived from the version string in `package.json`:
206
206
  - Contains `-beta` → `--tag beta`
207
207
  - No pre-release suffix → no `--tag` flag (publishes to `latest`)
208
208
 
209
+ #### `npm_publish_workflow`
210
+
211
+ The tag pushed in step 6 starts the project's publish workflow (GitHub Actions, keyed on the
212
+ workflow file `publish.yml`) instead of a local `npm publish`. The workflow runs with a
213
+ short-lived, per-run OIDC credential, so no npm token exists in this session or on this
214
+ machine.
215
+
216
+ 1. **Confirm a run exists for the tag.** A tag cut from a ref that does not carry the
217
+ workflow starts no run at all, and silence would read as success:
218
+
219
+ ```bash
220
+ gh run list --workflow publish.yml --limit 5
221
+ ```
222
+
223
+ 2. **Follow the run.**
224
+
225
+ ```bash
226
+ gh run watch <run-id>
227
+ ```
228
+
229
+ 3. **Verify the registry, not just the run.** The release is not done until the new version
230
+ shows up on the expected channel:
231
+
232
+ ```bash
233
+ npm view <package> dist-tags
234
+ ```
235
+
236
+ The dist-tag is derived from the version string in `package.json`, the same rule
237
+ `ReleaseGuard.dist_tag` implements:
238
+
239
+ | Version contains | dist-tag |
240
+ | --- | --- |
241
+ | `-alpha` | `alpha` |
242
+ | `-beta` | `beta` |
243
+ | no pre-release suffix | `latest` |
244
+
245
+ Do not run `npm whoami` on this path. npm documents that `whoami` does not reflect OIDC
246
+ authentication, so on a workflow-published project it can only mislead.
247
+
209
248
  #### Other values
210
249
 
211
250
  If `on_green` contains an action not listed above, log it:
@@ -34,18 +34,22 @@ cleared is a no-op.
34
34
  ## Promotion
35
35
 
36
36
  Promotion is not a CLI flag; there is no `--promote` command. It is a set of steps the
37
- agent performs during the releasing workflow, reusing the normal release mechanics
38
- (version bump, tag, `npm publish` with the channel's dist-tag, GitHub release):
37
+ agent performs during the releasing workflow, reusing the normal release mechanics (version
38
+ bump, tag, GitHub release). For a project on the `npm_publish_workflow` post-push action,
39
+ the tag push itself starts the publish; there is no local publish command to run.
39
40
 
40
41
  ```bash
41
- # Promote alpha → beta: set version files from -alpha.N to -beta.1, commit, tag, then
42
- npm publish --access public --tag beta
42
+ # Promote alpha → beta: set version files from -alpha.N to -beta.1, commit, tag, push.
43
+ # The publish workflow reads the new version and publishes to the beta dist-tag.
43
44
 
44
45
  # Promote beta → stable: strip the pre-release suffix (e.g., 1.0.0-beta.3 → 1.0.0),
45
- # commit, tag, then
46
- npm publish --access public # no --tag flag publishes to latest
46
+ # commit, tag, push. The publish workflow reads the new version and publishes to latest.
47
47
  ```
48
48
 
49
+ A project still on the `npm_publish` action (publishing locally from the session) runs
50
+ `npm publish --access public --tag <channel>` at this point instead, per that action's own
51
+ section in SKILL.md.
52
+
49
53
  **Promotion rules:**
50
54
  - Linear only: alpha → beta → stable. Cannot skip channels.
51
55
  - Version files are bumped and committed as in a normal release.
@@ -78,7 +78,7 @@ re-deriving this decision.
78
78
  of 1.1.0, versioned `1.2.0-beta.1` (`c48601a`). There is nothing left to execute on the git side;
79
79
  this playbook describes what happens next, not a pending action.
80
80
 
81
- **Preconditions**, both required before any npm publish of `1.2.0-beta.1`:
81
+ **Preconditions**, both required before any publish of `1.2.0-beta.1`:
82
82
 
83
83
  - (a) One documentation pass over beta-line skills and docs for the hybrid savepoint contract:
84
84
  on beta, only the terminal Done bookend still writes a live `savepoint.md`; every other
@@ -0,0 +1,16 @@
1
+ # Graph: <intent name>
2
+
3
+ ## Goal
4
+ <What this intent's graph delivers once every node reaches done.>
5
+
6
+ ## Decisions
7
+ - D1 <first ruling made while building this graph>
8
+
9
+ ## Graph
10
+ Edges, `needs` only; the head needs the tail done. The literal target `nothing` declares a root.
11
+ - n1 needs nothing
12
+
13
+ ## Status
14
+ | Node | State | Detail |
15
+ | --- | --- | --- |
16
+ | n1 | planned | |
@@ -0,0 +1,11 @@
1
+ ---
2
+ node: d1
3
+ kind: decision
4
+ files: []
5
+ budget: 20000
6
+ ---
7
+ # d1 - <the decision to make>
8
+
9
+ ## Question
10
+ <The question needing an owner ruling, framed so a yes/no answer or a pick
11
+ from a short list settles it.>
@@ -0,0 +1,11 @@
1
+ ---
2
+ node: r1
3
+ kind: research
4
+ files: []
5
+ budget: 60000
6
+ ---
7
+ # r1 - <what to find out>
8
+
9
+ ## Deposit
10
+ <Where the finding lands: a resources/ file, an artifact URL, or a ruling
11
+ recorded in the intent's ## Decisions.>
@@ -0,0 +1,13 @@
1
+ ---
2
+ node: v1
3
+ kind: verify
4
+ files: []
5
+ budget: 40000
6
+ ---
7
+ # v1 - <what this node verifies>
8
+
9
+ <What this node checks, and why it gates the work nodes it needs.>
10
+
11
+ ## Criteria
12
+ - <acceptance criterion one>
13
+ - <acceptance criterion two>
@@ -0,0 +1,22 @@
1
+ ---
2
+ node: n1
3
+ kind: work
4
+ files: [path/to/file.rb, test/path_to_file_test.rb]
5
+ budget: 100000
6
+ ---
7
+ # n1 - <one-line description of what this node builds>
8
+
9
+ <Why this node exists and what it changes, in a sentence or two.>
10
+
11
+ ## n1 failure-mode matrix
12
+ | Operation | Failure mode | Test |
13
+ | --- | --- | --- |
14
+ | <operation> | <what goes wrong without this code, and its consequence> | `some_test#test_name` |
15
+
16
+ ## Steps
17
+ 1. Red: the matrix's tests above, committed before any code.
18
+ 2. Write the code that makes them pass.
19
+ 3. Green, then the whole suite at its baseline.
20
+
21
+ ## Proven by
22
+ (filled at close from the ledger: commit, suite counts, review verdict)
@@ -9,12 +9,14 @@ disposition: delivered|abandoned
9
9
  ## Delivered
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
- row's label must appear as a standalone token in an actions/*.md heading
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). -->
12
+ row's label must appear as a standalone token in an actions/*.md OR
13
+ nodes/*.md heading that owns the matrix table (for example "### S1 - ..."
14
+ with a table beneath it proves row S1, or "## n1 failure-mode matrix" proves
15
+ row n1); that heading's matrix rows become the row's Proven-by cell on the
16
+ delivered screen (intent 317 D19, 317a, 322 D1r, 334 D10r). Readers resolve
17
+ actions/ first, then nodes/ (334 D15r). A label with no owning heading falls
18
+ back to a matrix row cell that carries it, when one under a heading named
19
+ "matrix" exists (322 D3r). -->
18
20
  | Row | What |
19
21
  | --- | --- |
20
22
  | S1 | ... |