devrites 6.0.2 → 6.0.3

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 (146) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +1 -1
  3. package/docs/adr/0034-workflow-artifact-byte-corrections-get-code-review.md +60 -0
  4. package/docs/adr/README.md +2 -1
  5. package/docs/engine/commands.md +6 -6
  6. package/engine/help.go +12 -7
  7. package/engine/help_test.go +1 -0
  8. package/engine/internal/gate/readiness_binding.go +19 -0
  9. package/engine/internal/lib/cli_observe.go +16 -0
  10. package/engine/internal/lib/context.go +191 -19
  11. package/engine/internal/lib/context_next_test.go +186 -0
  12. package/engine/internal/lib/dispatch.go +146 -11
  13. package/engine/internal/lib/drift.go +10 -2
  14. package/engine/internal/lib/drift_test.go +29 -0
  15. package/engine/internal/lib/slicecheck.go +19 -0
  16. package/engine/internal/lib/slicecheck_test.go +41 -0
  17. package/engine/internal/lib/windows.go +109 -13
  18. package/engine/internal/lib/windows_test.go +85 -0
  19. package/engine/main.go +2 -2
  20. package/pack/.claude/agents/devrites-slice-wright.md +3 -1
  21. package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  22. package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  23. package/pack/.claude/skills/devrites-lib/reference/standards/README.md +1 -1
  24. package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  25. package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
  26. package/pack/.claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
  27. package/pack/.claude/skills/devrites-lib/reference/standards/security.md +1 -0
  28. package/pack/.claude/skills/devrites-lib/reference/standards/windows.md +11 -3
  29. package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  30. package/pack/.claude/skills/rite-build/SKILL.md +1 -1
  31. package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  32. package/pack/.claude/skills/rite-build/reference/parallel-batch.md +5 -3
  33. package/pack/.claude/skills/rite-build/reference/phase-contract.md +5 -3
  34. package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
  35. package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +23 -9
  36. package/pack/.claude/skills/rite-define/SKILL.md +2 -1
  37. package/pack/.claude/skills/rite-plan/SKILL.md +2 -1
  38. package/pack/.claude/skills/rite-pressure-test/SKILL.md +6 -3
  39. package/pack/.claude/skills/rite-vet/SKILL.md +9 -1
  40. package/pack/.claude/skills/rite-vet/reference/review-axes.md +6 -2
  41. package/pack/generated/claude/agents/devrites-slice-wright.md +3 -1
  42. package/pack/generated/claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  43. package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  44. package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +1 -1
  45. package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  46. package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
  47. package/pack/generated/claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
  48. package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +1 -0
  49. package/pack/generated/claude/skills/devrites-lib/reference/standards/windows.md +11 -3
  50. package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  51. package/pack/generated/claude/skills/rite-build/SKILL.md +1 -1
  52. package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
  53. package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +5 -3
  54. package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +5 -3
  55. package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
  56. package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +23 -9
  57. package/pack/generated/claude/skills/rite-define/SKILL.md +2 -1
  58. package/pack/generated/claude/skills/rite-plan/SKILL.md +2 -1
  59. package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +6 -3
  60. package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -1
  61. package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +6 -2
  62. package/pack/generated/codex/agents/devrites-slice-wright.toml +3 -1
  63. package/pack/generated/codex/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  64. package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  65. package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +1 -1
  66. package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  67. package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +3 -0
  68. package/pack/generated/codex/skills/devrites-lib/reference/standards/glossary.md +1 -1
  69. package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +1 -0
  70. package/pack/generated/codex/skills/devrites-lib/reference/standards/windows.md +11 -3
  71. package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  72. package/pack/generated/codex/skills/rite-build/SKILL.md +1 -1
  73. package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +3 -2
  74. package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +5 -3
  75. package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +5 -3
  76. package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +3 -1
  77. package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +23 -9
  78. package/pack/generated/codex/skills/rite-define/SKILL.md +2 -1
  79. package/pack/generated/codex/skills/rite-plan/SKILL.md +2 -1
  80. package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -3
  81. package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -1
  82. package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +6 -2
  83. package/pack/generated/devin/agents/devrites-slice-wright.md +3 -1
  84. package/pack/generated/devin/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  85. package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  86. package/pack/generated/devin/skills/devrites-lib/reference/standards/README.md +1 -1
  87. package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  88. package/pack/generated/devin/skills/devrites-lib/reference/standards/code-review.md +3 -0
  89. package/pack/generated/devin/skills/devrites-lib/reference/standards/glossary.md +1 -1
  90. package/pack/generated/devin/skills/devrites-lib/reference/standards/security.md +1 -0
  91. package/pack/generated/devin/skills/devrites-lib/reference/standards/windows.md +11 -3
  92. package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  93. package/pack/generated/devin/skills/rite-build/SKILL.md +1 -1
  94. package/pack/generated/devin/skills/rite-build/reference/one-slice-cycle.md +3 -2
  95. package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +5 -3
  96. package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +5 -3
  97. package/pack/generated/devin/skills/rite-build/reference/spec-drift-guard.md +3 -1
  98. package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +23 -9
  99. package/pack/generated/devin/skills/rite-define/SKILL.md +2 -1
  100. package/pack/generated/devin/skills/rite-plan/SKILL.md +2 -1
  101. package/pack/generated/devin/skills/rite-pressure-test/SKILL.md +6 -3
  102. package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -1
  103. package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +6 -2
  104. package/pack/generated/omp/agents/devrites-slice-wright.md +3 -1
  105. package/pack/generated/omp/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  106. package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  107. package/pack/generated/omp/skills/devrites-lib/reference/standards/README.md +1 -1
  108. package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  109. package/pack/generated/omp/skills/devrites-lib/reference/standards/code-review.md +3 -0
  110. package/pack/generated/omp/skills/devrites-lib/reference/standards/glossary.md +1 -1
  111. package/pack/generated/omp/skills/devrites-lib/reference/standards/security.md +1 -0
  112. package/pack/generated/omp/skills/devrites-lib/reference/standards/windows.md +11 -3
  113. package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  114. package/pack/generated/omp/skills/rite-build/SKILL.md +1 -1
  115. package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +3 -2
  116. package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +5 -3
  117. package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +5 -3
  118. package/pack/generated/omp/skills/rite-build/reference/spec-drift-guard.md +3 -1
  119. package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +23 -9
  120. package/pack/generated/omp/skills/rite-define/SKILL.md +2 -1
  121. package/pack/generated/omp/skills/rite-plan/SKILL.md +2 -1
  122. package/pack/generated/omp/skills/rite-pressure-test/SKILL.md +6 -3
  123. package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -1
  124. package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +6 -2
  125. package/pack/generated/pi/agents/devrites-slice-wright.md +3 -1
  126. package/pack/generated/pi/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
  127. package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
  128. package/pack/generated/pi/skills/devrites-lib/reference/standards/README.md +1 -1
  129. package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
  130. package/pack/generated/pi/skills/devrites-lib/reference/standards/code-review.md +3 -0
  131. package/pack/generated/pi/skills/devrites-lib/reference/standards/glossary.md +1 -1
  132. package/pack/generated/pi/skills/devrites-lib/reference/standards/security.md +1 -0
  133. package/pack/generated/pi/skills/devrites-lib/reference/standards/windows.md +11 -3
  134. package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
  135. package/pack/generated/pi/skills/rite-build/SKILL.md +1 -1
  136. package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +3 -2
  137. package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +5 -3
  138. package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +5 -3
  139. package/pack/generated/pi/skills/rite-build/reference/spec-drift-guard.md +3 -1
  140. package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +23 -9
  141. package/pack/generated/pi/skills/rite-define/SKILL.md +2 -1
  142. package/pack/generated/pi/skills/rite-plan/SKILL.md +2 -1
  143. package/pack/generated/pi/skills/rite-pressure-test/SKILL.md +6 -3
  144. package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -1
  145. package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +6 -2
  146. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to DevRites are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and DevRites adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Releases are generated automatically by [semantic-release](https://semantic-release.gitbook.io/) from Conventional Commits on `main`.
4
4
 
5
+ ## [6.0.3](https://github.com/ViktorsBaikers/DevRites/compare/v6.0.2...v6.0.3) (2026-10-14)
6
+
7
+ ### Changed
8
+
9
+ * **devrites:** cut rite-build time without weakening gates ([#104](https://github.com/ViktorsBaikers/DevRites/issues/104)) ([64133ca](https://github.com/ViktorsBaikers/DevRites/commit/64133ca070947c3e9b170f1ee978302d63937c03))
10
+
11
+ ### Fixed
12
+
13
+ * **no-release:** accept --help after dispatch subcommand ([#103](https://github.com/ViktorsBaikers/DevRites/issues/103)) ([532ce43](https://github.com/ViktorsBaikers/DevRites/commit/532ce4324ae155f891345f825cbd2e04dd37ae85))
14
+
5
15
  ## [6.0.2](https://github.com/ViktorsBaikers/DevRites/compare/v6.0.1...v6.0.2) (2026-10-14)
6
16
 
7
17
  ### Fixed
package/README.md CHANGED
@@ -31,7 +31,7 @@ history can't lose it. After you clear the conversation, switch hosts, or hand
31
31
  the feature to another engineer, the next session reads the same plan,
32
32
  decisions, and evidence before it continues.
33
33
 
34
- **Status:** [`v6.0.2`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v6.0.2): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
34
+ **Status:** [`v6.0.3`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v6.0.3): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
35
35
 
36
36
  This is the latest published release; `main` may contain unreleased work.
37
37
 
@@ -0,0 +1,60 @@
1
+ # ADR-0034: Workflow Artifact byte corrections get code review, not a plan loop
2
+
3
+ - **Status:** Accepted
4
+ - **Date:** 2026-10-15
5
+
6
+ ## Context
7
+
8
+ A Workflow Artifact is a root-authored executable file under `.devrites/` that
9
+ proves a slice (for example a native UI test launcher). Vet admits its design:
10
+ paths, modes, behavior/interface/fixture rows, and the proof command. The root
11
+ authors the bytes in a disposable preflight after Vet `READY`. The source handle
12
+ derives from the readiness binding (ADR-0027), so promoting corrected bytes
13
+ needs a new binding, and only Vet may record one.
14
+
15
+ In practice every bug fix in such a script ran `/rite-plan repair` (plan-drafter)
16
+ plus an affected `/rite-vet` (plan-reviewer). The plan reviewer then reviewed
17
+ Python cleanup and rollback code. One measured feature spent 36% of agent time
18
+ after Build started in Plan and Vet. All 144 of its Plan/Vet waves served one
19
+ slice's proof harness, for example four consecutive archive-policy repair/Vet
20
+ pairs for close and rollback bugs.
21
+
22
+ ## Decision
23
+
24
+ - A correction that leaves every admission row and `WA-*` reference row
25
+ unchanged is a technical fix. It follows `OFFLINE_RECOVERY` and never
26
+ `/rite-plan repair`.
27
+ - The root records the corrected bytes' identity digest as one exact
28
+ `test-plan.md` line below the admission block:
29
+ `Workflow Artifact reviewed identity: <64 lowercase hex>`.
30
+ - `devrites-engine check drift` keeps a second digest of `test-plan.md` with
31
+ only that exact line shape removed. When it is the only change across all
32
+ readiness inputs, the drift is reported as `reviewed-identity-only`.
33
+ - In that case narrow `/rite-vet` dispatches one fresh `devrites-code-reviewer`
34
+ on the prior-source→re-preflight diff against the unchanged admission rows,
35
+ instead of a plan drafter and plan reviewer. Fold and recheck follow the
36
+ canonical retry contract. Green review lets Vet record the new binding as
37
+ usual (ADR-0027 still applies; the engine never refreshes it).
38
+ - Any other drift, including a malformed identity line, keeps the existing
39
+ narrow recheck by the plan reviewer.
40
+
41
+ This narrows ADR-0027's "semantic review" for this one case to an independent
42
+ code review of the exact bytes. The Workflow Artifact module, source-handle
43
+ derivation, routes, and golden values are unchanged.
44
+
45
+ ## Consequences
46
+
47
+ - The reviewed bytes are now bound into a readiness input. Before this, Vet
48
+ approved the design but never the exact corrected bytes.
49
+ - A contract change to a Workflow Artifact still goes through Plan and Vet.
50
+ - Slice-bound Vet rechecks also count toward the per-slice round cap
51
+ (`dispatch open --slice`), so a harness that keeps failing forces a recorded
52
+ reslice or simpler-proof review.
53
+
54
+ ## Alternatives considered
55
+
56
+ | Option | Why not |
57
+ |---|---|
58
+ | Derive the source handle from the identity digest | Changes the protocol's crypto, golden values, and the reference model test, for the same saving. |
59
+ | Let the engine refresh the binding after review | ADR-0027 keeps binding renewal a Vet act; the engine stays thin. |
60
+ | Keep plan-reviewer for byte fixes | Measured cost above; a plan reviewer is the wrong specialist for code defects. |
@@ -72,10 +72,11 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
72
72
  | [0024](0024-native-policy-offline-installer-boundary.md) | Native policy and offline installer boundary | Accepted in part; offline engine-update clause superseded by 0028 | `tests/native-orchestration-contract-test.sh`, `tests/phase-gate-routing-test.sh`, `tests/install-smoke.sh`, `tests/host-artifacts-test.sh` |
73
73
  | [0025](0025-evidence-gated-workspace-upgrades.md) | Evidence-gated semantic workspace upgrades | Accepted; no-migrator scope superseded by 0029 | `tests/phase-gate-routing-test.sh`, `tests/host-artifacts-test.sh` |
74
74
  | [0026](0026-content-bound-proof-and-bounded-inputs.md) | Content-bound proof and bounded inputs | Accepted | `engine/internal/lib/candidate_test.go`, `engine/internal/lib/evidencefresh_test.go`, `engine/internal/gate/gate_test.go`, `engine/root_routing_test.go`, `engine/tests/parity_githelpers_test.go`, `tests/native-orchestration-contract-test.sh`, `tests/phase-gate-routing-test.sh`, `tests/install-smoke.sh`, `tests/update-smoke.sh`, `tests/npx-pack-smoke.sh`, `tests/release-tarball-test.sh` |
75
- | [0027](0027-content-bound-build-readiness.md) | Content-bound build readiness | Accepted; AC ID map amended 2026-09-04 | `engine/tests/adr_0027_readiness_binding_test.go`, `engine/internal/state/acceptance_test.go` |
75
+ | [0027](0027-content-bound-build-readiness.md) | Content-bound build readiness | Accepted; AC ID map amended 2026-09-04; semantic review narrowed by 0034 for Workflow Artifact byte fixes | `engine/tests/adr_0027_readiness_binding_test.go`, `engine/internal/state/acceptance_test.go` |
76
76
  | [0028](0028-self-contained-engine-update.md) | Self-contained engine update | Accepted | `engine/internal/install/install_test.go`, `engine/internal/release/release_test.go`, `engine/tests/meta_test.go` |
77
77
  | [0029](0029-v5-workspace-schema-and-native-migration.md) | v5 workspace schema and native migration | Accepted | `engine/internal/state/workspaceschema_test.go`, `engine/tests/parity_resolve_test.go`, `engine/tests/parity_closeout_test.go` |
78
78
  | 0030 | Reserved number; no ADR is published under it | Unpublished | none |
79
79
  | [0031](0031-overhaul-agents-keep-every-tool.md) | `/overhaul` ships its own agents that keep every tool | Accepted; scopes the one-writer clauses of 0010/0015/0017/0018/0019 to `devrites-*` specialists | `scripts/validate-agent-composition.py`, `tests/codex-agent-generation-test.sh` |
80
80
  | [0032](0032-single-lint-gate-and-cached-pr-tests.md) | One engine lint gate, and PR tests that reuse main's results | Accepted | `tests/validation-governance-test.sh` |
81
81
  | [0033](0033-rite-fast-own-agents.md) | `/rite-fast` ships its own agents and its own writer | Accepted; adds `fast-builder` as a writer outside the `devrites-*` one-writer rule | `scripts/validate-agent-composition.py`, `tests/rite-fast-contract-test.sh` |
82
+ | [0034](0034-workflow-artifact-byte-corrections-get-code-review.md) | Workflow Artifact byte corrections get code review, not a plan loop | Accepted; narrows 0027's semantic review for contract-preserving Workflow Artifact byte fixes | `engine/internal/lib/drift_test.go` |
@@ -18,12 +18,12 @@ workflow strategy.
18
18
  | `check path-disjoint [--root <dir>] [<json-file> | -]` | Verify slice path sets are pairwise disjoint. |
19
19
  | `parallel select --cap <1-10> [--root <dir>] [<json-file> | -]` | Choose the greedy path-disjoint subset ≤ cap from supplied ready slices. |
20
20
  | `parallel <create\|integrate\|cleanup\|status\|abort\|lease-read\|lease-write\|lease-clear\|record-green>` | Deterministic parallel worktree leases, integration, and cleanup for parallel writer batches. |
21
- | `check task-graph <slug>` | Validate `tasks.md` slice dependency graph for cycles, unknown deps, malformed tokens, duplicate IDs, missing `Dependencies`, and `depends_on` mismatch. |
21
+ | `check task-graph <slug>` | Validate `tasks.md` slice dependency graph for cycles, unknown deps, malformed tokens, duplicate IDs, missing `Dependencies`, and `depends_on` mismatch. Prints a non-blocking `ADVISORY size:` line (and records a `size-advisory` metric) for each slice with 12+ Writer allowlist files and no `Size rationale:`. |
22
22
  | `check regression <slug> [--update]` | Compare the workspace's structural progress facts (phase ordinal, checked ACs, met/abandoned gates, done slices, resolved questions, present artifacts) against the recorded `regression-baseline.json` high-water mark; `--update` ratchets the baseline forward under the feature lock and prints each blessed regression. |
23
- | `check drift <slug> [--record]` | Attribute readiness-input changes to the exact artifact. `--record` writes the per-input `readiness-inputs.json` digest baseline under the feature lock; without it the workspace diffs current inputs against that baseline (or falls back to the aggregate binding recorded in `eng-review.md`). Advisory: exit `0` on drift; a corrupt baseline exits `3`. |
24
- | `check windows <slug> [--worktree\|--staged\|--base <ref>]` | Fail on deferral markers (TODO/FIXME/XXX/HACK, skipped-test and stub idioms) the change's added lines introduce without a waiver row in the workspace's `windows.md`. |
23
+ | `check drift <slug> [--record]` | Attribute readiness-input changes to the exact artifact. `--record` writes the per-input `readiness-inputs.json` digest baseline under the feature lock; without it the workspace diffs current inputs against that baseline (or falls back to the aggregate binding recorded in `eng-review.md`). Advisory: exit `0` on drift; a corrupt baseline exits `3`. A `test-plan.md` change limited to exact `Workflow Artifact reviewed identity: <64hex>` lines is reported as `reviewed-identity-only` (ADR-0034). |
24
+ | `check windows <slug> [--worktree\|--staged\|--base <ref>]` | Fail on deferral and bar-lowering markers the change introduces without a waiver row in the workspace's `windows.md`: added TODO/FIXME/XXX/HACK, skipped/focused-test and stub idioms, checker suppressions, one-line empty catch; removed `deleted-test` files and net `removed-assertion` lines in remaining test files. |
25
25
  | `check dup [slug] [--all\|--worktree\|--staged\|--base <ref>] [--min-lines n] [--threshold f] [--ignore-file <path>] [--limit n] [--exclude <csv>]` | Report near-duplicate code clusters that survive comment stripping, literal collapsing, and identifier renaming — token-shingle matches chained into segments, clustered transitively, ranked with bounded path/line-distance boosts. Diff modes mark units overlapping changed hunks with `*`; stable content-derived cluster hashes let `.devrites/dup-ignore` dismissals survive line moves but resurface on structural edits. Staged mode reads index blobs and excludes untracked files. Bounded heuristic: successful scans/leads exit `0`; usage, unavailable input, and partial scans exit `2`. |
26
- | `check slice <slug> <SLICE-ID>` | Pre-dispatch lint of one slice's wright contract: contract present, exact Writer allowlist paths, resolvable `Satisfies` AC ids, bounded scope. |
26
+ | `check slice <slug> <SLICE-ID>` | Pre-dispatch lint of one slice's wright contract: contract present, exact Writer allowlist paths, resolvable `Satisfies` AC ids, bounded scope. Same non-blocking size advisory as `check task-graph`. |
27
27
  | `check diff-scope <slug> --allow <csv>\|--allow-file <path> [--worktree\|--staged\|--base <ref>]` | Verify the changed-path set stays inside the declared allowlist; the mechanical gate run before reviewer dispatch. |
28
28
  | `check skill-trust <path>` | Scan one skill/agent Markdown file for structural trust violations. |
29
29
  | `check indexes [--root <dir>]` | Report install manifest and code-index presence as JSON. |
@@ -32,8 +32,8 @@ workflow strategy.
32
32
  | `observe slice <slug> <SLICE-ID>` | Print one `SLICE-###` section of `tasks.md` so Build reads a slice, not the file. |
33
33
  | `next [slug]` | Print the minimal remaining lifecycle path plus advisory skips. |
34
34
  | `handoff [slug]` | Emit the deterministic resume record: cursor fields, `awaiting_human`, open `questions.md` gate kinds, the `gates.md` reduction with unmet/stale/abandoned ids, `decisions.md` dead ends, missing required files, and the canonical read-next order. Read-only; the `/rite-handoff` spine and the compaction-fallback entry point. |
35
- | `context [slug] (--phase <p>\|--skill <name>) [--role <r>] [--trigger a,b]` | Emit one deduplicated read-set bundle per phase/role/skill from each skill's `loads:` manifest; `--skill` works without a workspace when `--out` is given. |
36
- | `dispatch <slug> <open\|start\|seal\|return\|status\|abandon>` | Launch-wave barrier for parallel dispatch: seal needs a handle per role, return needs seal; start/return auto-record metrics. |
35
+ | `context [slug] (--phase <p>\|--skill <name>) [--role <r>] [--slice <SLICE-ID>] [--trigger a,b]` | Emit one deduplicated read-set bundle per phase/role/skill from each skill's `loads:` manifest; `--skill` works without a workspace when `--out` is given. A `--role` bundle leads with the role's agent contract and names the manifest's `rootOnly` paths instead of inlining them. `--slice` inlines that slice's `tasks.md` block and turns each over-budget artifact into a by-reference index (path, SHA-256, headings, line hits for every ID the block cites); repeat `--slice` for several slices; an unknown slice exits `3`. |
36
+ | `dispatch <slug> <open\|start\|seal\|return\|status\|abandon>` | Launch-wave barrier for parallel dispatch: seal needs a handle per role, return needs seal; start/return auto-record metrics. A build wave with a wright role needs `--slice <SLICE-ID>` per slice, and a slice-bound vet wave takes it too; every 5 rounds of either kind on one slice, `open` exits `3` until `--reslice-review <DRIFT-ID>` names an unused `drift.md` `## <DRIFT-ID>` heading for that slice. |
37
37
  | `metrics record <slug> --phase <p> --event <e> [--role r] [--bytes n] [--note s]` | Append an event to the workspace's `metrics.jsonl` ledger. |
38
38
  | `metrics summary [slug]` | Reduce `metrics.jsonl` into a per-phase roll-up. |
39
39
  | `claim <add\|release\|list\|check>` | Advisory session-scoped file claims in `.devrites/claims.jsonl` (append-only). `add` records intent to write a path set with a TTL (1–240 min, default 30); a live claim by another session exits `3` and names the holder. `release` appends a release event owned by the claiming session. `list` shows live claims (`--all` includes expired/released). `check` is the read-only preflight for a path set. Coordination aid only — never a substitute for one-writer-per-worktree. |
package/engine/help.go CHANGED
@@ -125,16 +125,20 @@ func takesSubcommand(command string) bool {
125
125
  }
126
126
  }
127
127
 
128
- // claimValueFlags are the claim flags that consume the next argument as a value.
129
- var claimValueFlags = map[string]bool{"--session": true, "--reason": true, "--ttl": true, "--id": true}
128
+ // valueFlags lists, per command, the flags that consume the next argument as a
129
+ // value. A command listed here has known flags, so its help flag is honoured
130
+ // anywhere except as the value of one of these flags.
131
+ var valueFlags = map[string]map[string]bool{
132
+ "claim": {"--session": true, "--reason": true, "--ttl": true, "--id": true},
133
+ "dispatch": {"--phase": true, "--wave": true, "--role": true, "--handle": true, "--reason": true},
134
+ }
130
135
 
131
136
  // hasHelpFlag reports whether args (the arguments after the command word) are a
132
137
  // help request. The command path is consumed by dispatch, so a help flag is
133
138
  // honoured among the leading flags that follow it. The scan stops at the first
134
139
  // argument that does not start with a dash, because from there the engine
135
- // cannot tell a flag from a value or operand. Only claim, whose value flags are
136
- // known, honours a help flag later, and never one that is the value of such a
137
- // flag.
140
+ // cannot tell a flag from a value or operand. Commands in valueFlags honour a
141
+ // help flag later, and never one that is the value of such a flag.
138
142
  func hasHelpFlag(command string, args []string) bool {
139
143
  if len(args) > 0 && !strings.HasPrefix(args[0], "-") && takesSubcommand(command) {
140
144
  args = args[1:]
@@ -143,9 +147,10 @@ func hasHelpFlag(command string, args []string) bool {
143
147
  if isHelpFlag(args[i]) {
144
148
  return true
145
149
  }
146
- if command == "claim" && claimValueFlags[args[i]] {
150
+ known, ok := valueFlags[command]
151
+ if ok && known[args[i]] {
147
152
  i++
148
- } else if command != "claim" && !strings.HasPrefix(args[i], "-") {
153
+ } else if !ok && !strings.HasPrefix(args[i], "-") {
149
154
  return false
150
155
  }
151
156
  }
@@ -48,6 +48,7 @@ func TestCommandHelp(t *testing.T) {
48
48
  {args: []string{"handoff", "--help"}, want: "handoff [slug]"},
49
49
  {args: []string{"next", "help"}, want: "devrites-engine next [slug]"},
50
50
  {args: []string{"dispatch", "help"}, want: "devrites-engine dispatch <slug>"},
51
+ {args: []string{"dispatch", "x", "open", "--help"}, want: "devrites-engine dispatch <slug>"},
51
52
  {args: []string{"secret-scan", "help"}, want: "secret-scan [--staged] [--stdin]"},
52
53
  {args: []string{"open-visual", "help"}, want: "open-visual <path-or-name>"},
53
54
  {args: []string{"claim", "--help"}, want: "claim <add|release|list|check>"},
@@ -7,6 +7,7 @@ import (
7
7
  "errors"
8
8
  "fmt"
9
9
  "hash"
10
+ "regexp"
10
11
  "strings"
11
12
 
12
13
  "github.com/devrites/devrites/internal/markdowntext"
@@ -147,6 +148,20 @@ type ReadinessInputDigest struct {
147
148
  Path string `json:"path"`
148
149
  Present bool `json:"present"`
149
150
  SHA256 string `json:"sha256,omitempty"`
151
+ // Normalized is test-plan.md's digest without its reviewed-identity lines,
152
+ // so drift can prove a Workflow Artifact byte correction changed nothing
153
+ // else Vet approved.
154
+ Normalized string `json:"normalized,omitempty"`
155
+ }
156
+
157
+ // reviewedIdentityRE is the exact test-plan.md line binding the Workflow
158
+ // Artifact bytes an independent code review approved. Only this exact shape
159
+ // is ignored, so the exclusion cannot hide any other edit.
160
+ var reviewedIdentityRE = regexp.MustCompile(`(?m)^Workflow Artifact reviewed identity: [0-9a-f]{64}\r?\n?`)
161
+
162
+ // withoutReviewedIdentity drops every exact reviewed-identity line.
163
+ func withoutReviewedIdentity(content []byte) []byte {
164
+ return reviewedIdentityRE.ReplaceAll(content, nil)
150
165
  }
151
166
 
152
167
  // ReadinessInputDigests returns each readiness input's content digest plus the
@@ -172,6 +187,10 @@ func ReadinessInputDigests(root, slug string) ([]ReadinessInputDigest, string, e
172
187
  if present {
173
188
  sum := sha256.Sum256(content)
174
189
  entry.SHA256 = hex.EncodeToString(sum[:])
190
+ if input.logical == "test-plan.md" {
191
+ normalized := sha256.Sum256(withoutReviewedIdentity(content))
192
+ entry.Normalized = hex.EncodeToString(normalized[:])
193
+ }
175
194
  }
176
195
  digests = append(digests, entry)
177
196
  }
@@ -5,6 +5,7 @@ import (
5
5
  "io"
6
6
 
7
7
  "github.com/devrites/devrites/internal/devritespaths"
8
+ "github.com/devrites/devrites/internal/state"
8
9
  )
9
10
 
10
11
  // RunTaskGraphCheck validates tasks.md for one slug.
@@ -21,6 +22,21 @@ func RunTaskGraphCheck(root, slug string, stdout, stderr io.Writer) int {
21
22
  return 3
22
23
  }
23
24
  fmt.Fprintf(stdout, "task-graph: ok (%d slices)\n", len(graph.Slices))
25
+ // Size advisory: never changes the exit code; the metric shows how often
26
+ // it would have fired before anyone makes it blocking.
27
+ if raw, err := readWorkspaceArtifact(root, slug, "tasks.md"); err == nil {
28
+ over := 0
29
+ for _, s := range graph.Slices {
30
+ block, _ := state.ExtractTaskSlice(raw, s.ID)
31
+ if n, ok := sliceSizeAdvisory(block); ok {
32
+ fmt.Fprintf(stdout, "task-graph: ADVISORY size: %s has %d Writer allowlist files (>= %d); split it (acceptance-preserving) or record `Size rationale:`\n", s.ID, n, sliceFileAdvisory)
33
+ over++
34
+ }
35
+ }
36
+ if over > 0 {
37
+ recordMetric(root, slug, "define", "size-advisory", "", int64(over))
38
+ }
39
+ }
24
40
  return 0
25
41
  }
26
42
 
@@ -14,6 +14,8 @@ import (
14
14
  "strings"
15
15
 
16
16
  "github.com/devrites/devrites/internal/devritespaths"
17
+ "github.com/devrites/devrites/internal/markdowntext"
18
+ "github.com/devrites/devrites/internal/state"
17
19
  )
18
20
 
19
21
  // loadsManifest is the machine-readable read-set a skill declares in an HTML
@@ -24,29 +26,35 @@ import (
24
26
  // role key is present its list replaces "workspace" entirely (an empty list
25
27
  // means the role needs no workspace artifacts). "agents" maps a --role name to
26
28
  // an agents-root-relative path when it deviates from the devrites-<role>
27
- // convention.
29
+ // convention. "rootOnly" lists always/trigger paths that only the controlling
30
+ // root executes; a --role bundle names them instead of inlining them.
28
31
  type loadsManifest struct {
29
32
  Always []string `json:"always"`
30
33
  Triggers map[string][]string `json:"triggers"`
31
34
  Workspace []string `json:"workspace"`
32
35
  WorkspaceByRole map[string][]string `json:"workspaceByRole"`
33
36
  Agents map[string]string `json:"agents"`
37
+ RootOnly []string `json:"rootOnly"`
34
38
  }
35
39
 
36
40
  const loadsMarker = "<!-- loads:"
37
41
 
38
- const contextUsage = `usage: devrites-engine context [slug] (--phase <p> | --skill <name>) [--role <r>] [--trigger a,b] [--skills-root <dir>] [--out <path>]
42
+ const contextUsage = `usage: devrites-engine context [slug] (--phase <p> | --skill <name>) [--role <r>] [--slice <SLICE-###>] [--trigger a,b] [--skills-root <dir>] [--out <path>]
39
43
 
40
44
  Emits one deduplicated context bundle for a phase or standalone skill
41
45
  (+ optional dispatch role) into .devrites/work/<slug>/ctx/ so an agent
42
46
  reads a single file. With --skill, slug may be omitted; then --out is
43
- required and no workspace section is included.
47
+ required and no workspace section is included. --slice scopes the
48
+ workspace section to one slice: tasks.md contributes that slice's block,
49
+ and an artifact over its line/byte budget becomes a by-reference index
50
+ (path, SHA-256, headings, and line hits for every ID the block cites).
51
+ Repeat --slice for a recheck that spans several slices.
44
52
  `
45
53
 
46
54
  // RunContext emits one deduplicated context bundle for a phase (+ optional
47
55
  // dispatch role) so an agent reads a single file instead of selecting files.
48
56
  //
49
- // context <slug> --phase <p> [--role <r>] [--trigger a,b] [--skills-root <dir>] [--out <path>]
57
+ // context <slug> --phase <p> [--role <r>] [--slice <SLICE-###>] [--trigger a,b] [--skills-root <dir>] [--out <path>]
50
58
  func RunContext(root string, args []string, stdout, stderr io.Writer) int {
51
59
  opts, code := parseContextArgs(args, stderr)
52
60
  if code != 0 {
@@ -82,6 +90,27 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
82
90
  return 2
83
91
  }
84
92
 
93
+ var sliceBlock string
94
+ var sliceIDs []citedID
95
+ if opts.slice != "" {
96
+ raw, err := os.ReadFile(filepath.Join(featureDir, "tasks.md")) // #nosec G304 -- featureDir is the validated feature workspace
97
+ if err != nil {
98
+ fmt.Fprintf(stderr, "context: %v\n", err)
99
+ return 2
100
+ }
101
+ var blocks []string
102
+ for _, id := range opts.slices {
103
+ block, ok := state.ExtractTaskSlice(raw, id)
104
+ if !ok {
105
+ fmt.Fprintf(stdout, "context: BLOCKED: %s not found in tasks.md\n", id)
106
+ return 3
107
+ }
108
+ blocks = append(blocks, block)
109
+ }
110
+ sliceBlock = strings.Join(blocks, "\n")
111
+ sliceIDs = citedIDs(sliceBlock)
112
+ }
113
+
85
114
  var files []string
86
115
  var missingRequired []string
87
116
  var autoTriggers []string
@@ -124,12 +153,31 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
124
153
 
125
154
  seen := map[string]bool{}
126
155
  loaded := 0
156
+ // The role contract leads the bundle so the agent reads its rules before
157
+ // reference text.
158
+ if opts.role != "" {
159
+ rel := roleAgentPath(manifest, opts.role)
160
+ if writeContextEntry(&bundle, filepath.Join(skillsRoot, "..", "agents", filepath.FromSlash(rel)),
161
+ "agents/"+rel, seen, true) {
162
+ loaded++
163
+ } else {
164
+ // The agent contract is a pack file, not a workspace artifact: a
165
+ // miss means a typo'd role or a pack bug, and a dispatch without
166
+ // its contract must fail loudly.
167
+ missingRequired = append(missingRequired, "agents/"+rel)
168
+ }
169
+ }
170
+ var rootOnly []string
127
171
  for _, rel := range files {
128
172
  abs := filepath.Join(skillsRoot, filepath.FromSlash(rel))
129
173
  if seen[abs] {
130
174
  continue
131
175
  }
132
176
  seen[abs] = true
177
+ if opts.role != "" && slices.Contains(manifest.RootOnly, rel) {
178
+ rootOnly = append(rootOnly, rel)
179
+ continue
180
+ }
133
181
  data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated skills root
134
182
  fmt.Fprintf(&bundle, "===== %s =====\n", rel)
135
183
  if err != nil {
@@ -144,34 +192,37 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
144
192
  bundle.WriteByte('\n')
145
193
  loaded++
146
194
  }
195
+ if len(rootOnly) > 0 {
196
+ fmt.Fprintf(&bundle, "===== root-only (not inlined for role %s) =====\n%s\n\n", opts.role, strings.Join(rootOnly, "\n"))
197
+ }
147
198
 
199
+ whole, byReference := 0, 0
148
200
  workspaceList, workspaceScoped := workspaceReadSet(manifest, opts.role)
149
201
  if hasManifest && featureDir != "" {
150
202
  if workspaceScoped && len(workspaceList) == 0 {
151
203
  bundle.WriteString("===== workspace =====\nskipped: role-scoped read-set declares no workspace artifacts\n\n")
152
204
  }
153
205
  for _, rel := range workspaceList {
154
- if writeContextEntry(&bundle, filepath.Join(featureDir, filepath.FromSlash(rel)),
155
- "workspace/"+rel, seen, false) {
206
+ abs := filepath.Join(featureDir, filepath.FromSlash(rel))
207
+ if opts.slice == "" {
208
+ if writeContextEntry(&bundle, abs, "workspace/"+rel, seen, false) {
209
+ loaded++
210
+ }
211
+ continue
212
+ }
213
+ ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, opts.slice, sliceBlock, sliceIDs, seen)
214
+ if ok {
156
215
  loaded++
157
216
  }
217
+ if byRef {
218
+ byReference++
219
+ } else if ok {
220
+ whole++
221
+ }
158
222
  }
159
223
  } else if hasManifest && len(workspaceList) > 0 {
160
224
  bundle.WriteString("===== workspace =====\nskipped: no feature workspace (slug omitted)\n\n")
161
225
  }
162
- if opts.role != "" {
163
- rel := roleAgentPath(manifest, opts.role)
164
- if writeContextEntry(&bundle, filepath.Join(skillsRoot, "..", "agents", filepath.FromSlash(rel)),
165
- "agents/"+rel, seen, true) {
166
- loaded++
167
- } else {
168
- // The agent contract is a pack file, not a workspace artifact: a
169
- // miss means a typo'd role or a pack bug, and a dispatch without
170
- // its contract must fail loudly.
171
- missingRequired = append(missingRequired, "agents/"+rel)
172
- }
173
- }
174
-
175
226
  out := opts.out
176
227
  if out == "" {
177
228
  out = filepath.Join(featureDir, "ctx", bundleName(opts))
@@ -216,6 +267,9 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
216
267
  fmt.Fprintf(stdout, ", MISSING: %s", strings.Join(missingRequired, ","))
217
268
  }
218
269
  fmt.Fprintln(stdout, ")")
270
+ if opts.slice != "" {
271
+ fmt.Fprintf(stdout, "scope: slice %s (%d whole, %d by-reference)\n", opts.slice, whole, byReference)
272
+ }
219
273
  if workspaceScoped {
220
274
  fmt.Fprintf(stdout, "workspace: role-scoped for %s (%d of %d declared artifacts)\n",
221
275
  opts.role, len(workspaceList), len(manifest.Workspace))
@@ -280,6 +334,110 @@ func expandIncludes(abs string, data []byte) []byte {
280
334
  })
281
335
  }
282
336
 
337
+ // writeSliceEntry writes one workspace artifact of a --slice bundle: tasks.md
338
+ // as the slice block plus a by-reference index, any other canonical artifact
339
+ // over its line/byte budget as a by-reference index, everything else whole.
340
+ func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
341
+ if seen[abs] {
342
+ return false, false
343
+ }
344
+ data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated feature workspace
345
+ name := filepath.Base(abs)
346
+ if err == nil && name == "tasks.md" {
347
+ seen[abs] = true
348
+ fmt.Fprintf(bundle, "===== %s (%s block) =====\n%s\n", label, slice, block)
349
+ } else if status, ok := state.ArtifactBudget(name, data); err != nil || !ok || !status.Over {
350
+ return writeContextEntry(bundle, abs, label, seen, false), false
351
+ } else {
352
+ seen[abs] = true
353
+ }
354
+ writeAnchorIndex(bundle, project, abs, label, data, ids)
355
+ return true, true
356
+ }
357
+
358
+ var (
359
+ citedIDRE = regexp.MustCompile(`\b[A-Z]{1,6}-\d{2,}(?:-[A-Z][A-Z0-9]*)*\b`)
360
+ anchorHeadingRE = regexp.MustCompile(`^#{1,3}\s`)
361
+ )
362
+
363
+ // maxIDHits bounds the line anchors listed per ID; the live file holds the rest.
364
+ const maxIDHits = 20
365
+
366
+ // citedID is one ID the slice block cites with its word-bounded matcher.
367
+ type citedID struct {
368
+ id string
369
+ re *regexp.Regexp
370
+ }
371
+
372
+ // citedIDs returns each distinct ID the slice block cites (AC-###, DEC-###,
373
+ // T-###, PF-##, SLICE-###, ...).
374
+ func citedIDs(block string) []citedID {
375
+ var out []citedID
376
+ seen := map[string]bool{}
377
+ for _, id := range citedIDRE.FindAllString(block, -1) {
378
+ if !seen[id] {
379
+ seen[id] = true
380
+ out = append(out, citedID{id, regexp.MustCompile(`\b` + regexp.QuoteMeta(id) + `\b`)})
381
+ }
382
+ }
383
+ return out
384
+ }
385
+
386
+ // writeAnchorIndex emits a by-reference entry: identity, heading anchors, and
387
+ // line hits for each cited ID, so the agent reads owning sections from the
388
+ // live file instead of the whole artifact.
389
+ func writeAnchorIndex(bundle *strings.Builder, project, abs, label string, data []byte, ids []citedID) {
390
+ rel, err := filepath.Rel(project, abs)
391
+ if err != nil {
392
+ rel = abs
393
+ }
394
+ lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n")
395
+ structural := lines
396
+ if masked, err := markdowntext.Structural(data); err == nil {
397
+ structural = strings.Split(strings.ReplaceAll(string(masked), "\r\n", "\n"), "\n")
398
+ }
399
+ fmt.Fprintf(bundle, "===== %s (by reference) =====\n", label)
400
+ fmt.Fprintf(bundle, "path: %s sha256: %x bytes: %d lines: %d\n", filepath.ToSlash(rel), sha256.Sum256(data), len(data), len(lines))
401
+ bundle.WriteString("Not inlined. Read owning sections from the live file by line anchor; nothing is omitted there.\n")
402
+ bundle.WriteString("headings:\n")
403
+ for i, line := range structural {
404
+ if anchorHeadingRE.MatchString(line) {
405
+ fmt.Fprintf(bundle, "- L%d %s\n", i+1, clipRunes(lines[i], 120))
406
+ }
407
+ }
408
+ if len(ids) > 0 {
409
+ bundle.WriteString("id hits (IDs cited by the slice block):\n")
410
+ }
411
+ for _, cited := range ids {
412
+ var at []string
413
+ total := 0
414
+ for i, line := range lines {
415
+ if cited.re.MatchString(line) {
416
+ total++
417
+ if len(at) < maxIDHits {
418
+ at = append(at, fmt.Sprintf("L%d", i+1))
419
+ }
420
+ }
421
+ }
422
+ hits := "none"
423
+ if total > 0 {
424
+ hits = strings.Join(at, " ")
425
+ if total > len(at) {
426
+ hits += fmt.Sprintf(" (+%d more)", total-len(at))
427
+ }
428
+ }
429
+ fmt.Fprintf(bundle, "- %s: %s\n", cited.id, hits)
430
+ }
431
+ bundle.WriteByte('\n')
432
+ }
433
+
434
+ func clipRunes(s string, n int) string {
435
+ if r := []rune(s); len(r) > n {
436
+ return string(r[:n]) + "…"
437
+ }
438
+ return s
439
+ }
440
+
283
441
  func writeContextEntry(bundle *strings.Builder, abs, label string, seen map[string]bool, expand bool) bool {
284
442
  if seen[abs] {
285
443
  return false
@@ -308,6 +466,8 @@ type contextOpts struct {
308
466
  phase string
309
467
  skill string
310
468
  role string
469
+ slice string // display label: slices joined with "+"
470
+ slices []string
311
471
  triggers []string
312
472
  skillsRoot string
313
473
  out string
@@ -325,6 +485,8 @@ func parseContextArgs(args []string, stderr io.Writer) (contextOpts, int) {
325
485
  opts.skill = value
326
486
  case "--role":
327
487
  opts.role = value
488
+ case "--slice":
489
+ opts.slices = append(opts.slices, value)
328
490
  case "--trigger", "--triggers":
329
491
  for _, name := range strings.Split(value, ",") {
330
492
  if name = strings.TrimSpace(name); name != "" {
@@ -353,6 +515,13 @@ func parseContextArgs(args []string, stderr io.Writer) (contextOpts, int) {
353
515
  fmt.Fprintf(stderr, "context: --skill must be a directory name, not a path\n")
354
516
  return opts, 2
355
517
  }
518
+ for _, id := range opts.slices {
519
+ if !state.ValidSliceID(id) || len(positional) == 0 {
520
+ fmt.Fprintf(stderr, "context: --slice needs a slug and a SLICE-### id, got %q\n", id)
521
+ return opts, 2
522
+ }
523
+ }
524
+ opts.slice = strings.Join(opts.slices, "+")
356
525
  if len(positional) == 1 {
357
526
  opts.slug = positional[0]
358
527
  } else if opts.out == "" {
@@ -429,6 +598,9 @@ func bundleName(opts contextOpts) string {
429
598
  if opts.role != "" {
430
599
  name += "-" + opts.role
431
600
  }
601
+ if opts.slice != "" {
602
+ name += "." + opts.slice
603
+ }
432
604
  return name + ".bundle.md"
433
605
  }
434
606