devrites 6.0.2 → 6.0.4
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/CHANGELOG.md +16 -0
- package/README.md +1 -1
- package/docs/adr/0034-workflow-artifact-byte-corrections-get-code-review.md +60 -0
- package/docs/adr/README.md +2 -1
- package/docs/engine/commands.md +8 -8
- package/engine/help.go +12 -7
- package/engine/help_test.go +1 -0
- package/engine/internal/gate/readiness_binding.go +19 -0
- package/engine/internal/lib/cli_observe.go +16 -0
- package/engine/internal/lib/context.go +197 -19
- package/engine/internal/lib/context_next_test.go +238 -0
- package/engine/internal/lib/dispatch.go +189 -15
- package/engine/internal/lib/drift.go +10 -2
- package/engine/internal/lib/drift_test.go +29 -0
- package/engine/internal/lib/metrics.go +74 -1
- package/engine/internal/lib/observe_budgets_test.go +32 -0
- package/engine/internal/lib/observe_summary.go +8 -0
- package/engine/internal/lib/slicecheck.go +19 -0
- package/engine/internal/lib/slicecheck_test.go +41 -0
- package/engine/internal/lib/windows.go +109 -13
- package/engine/internal/lib/windows_test.go +85 -0
- package/engine/main.go +3 -3
- package/pack/.claude/agents/devrites-slice-wright.md +7 -2
- package/pack/.claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/.claude/skills/rite-build/SKILL.md +1 -1
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/.claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/.claude/skills/rite-build/reference/output.md +1 -1
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/.claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/.claude/skills/rite-define/SKILL.md +2 -1
- package/pack/.claude/skills/rite-plan/SKILL.md +2 -1
- package/pack/.claude/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/.claude/skills/rite-vet/SKILL.md +9 -1
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +6 -2
- package/pack/generated/claude/agents/devrites-slice-wright.md +7 -2
- package/pack/generated/claude/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/generated/claude/skills/rite-build/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/claude/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/claude/skills/rite-build/reference/output.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/generated/claude/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/generated/claude/skills/rite-define/SKILL.md +2 -1
- package/pack/generated/claude/skills/rite-plan/SKILL.md +2 -1
- package/pack/generated/claude/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -1
- package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +6 -2
- package/pack/generated/codex/agents/devrites-slice-wright.toml +7 -2
- package/pack/generated/codex/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/generated/codex/skills/rite-build/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/codex/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/codex/skills/rite-build/reference/output.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/generated/codex/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/generated/codex/skills/rite-define/SKILL.md +2 -1
- package/pack/generated/codex/skills/rite-plan/SKILL.md +2 -1
- package/pack/generated/codex/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -1
- package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +6 -2
- package/pack/generated/devin/agents/devrites-slice-wright.md +7 -2
- package/pack/generated/devin/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/generated/devin/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/generated/devin/skills/rite-build/SKILL.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/devin/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/devin/skills/rite-build/reference/output.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/generated/devin/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/generated/devin/skills/rite-define/SKILL.md +2 -1
- package/pack/generated/devin/skills/rite-plan/SKILL.md +2 -1
- package/pack/generated/devin/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -1
- package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +6 -2
- package/pack/generated/omp/agents/devrites-slice-wright.md +7 -2
- package/pack/generated/omp/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/generated/omp/skills/rite-build/SKILL.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/omp/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/omp/skills/rite-build/reference/output.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/generated/omp/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/generated/omp/skills/rite-define/SKILL.md +2 -1
- package/pack/generated/omp/skills/rite-plan/SKILL.md +2 -1
- package/pack/generated/omp/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -1
- package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +6 -2
- package/pack/generated/pi/agents/devrites-slice-wright.md +7 -2
- package/pack/generated/pi/skills/devrites-debug-recovery/reference/hypotheses.md +6 -0
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +1 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/README.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +2 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/code-review.md +3 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/glossary.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/security.md +1 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/windows.md +11 -3
- package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +5 -2
- package/pack/generated/pi/skills/rite-build/SKILL.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +3 -1
- package/pack/generated/pi/skills/rite-build/reference/one-slice-cycle.md +3 -2
- package/pack/generated/pi/skills/rite-build/reference/output.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +5 -3
- package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +7 -3
- package/pack/generated/pi/skills/rite-build/reference/spec-drift-guard.md +3 -1
- package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +23 -9
- package/pack/generated/pi/skills/rite-define/SKILL.md +2 -1
- package/pack/generated/pi/skills/rite-plan/SKILL.md +2 -1
- package/pack/generated/pi/skills/rite-pressure-test/SKILL.md +6 -3
- package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -1
- package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +6 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
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.4](https://github.com/ViktorsBaikers/DevRites/compare/v6.0.3...v6.0.4) (2026-10-15)
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
* **devrites:** anchor root bundles, wright self-review, per-slice cost ([#105](https://github.com/ViktorsBaikers/DevRites/issues/105)) ([3569ab3](https://github.com/ViktorsBaikers/DevRites/commit/3569ab3d7c313a05c6371c696df9b2b40971ce2b))
|
|
10
|
+
|
|
11
|
+
## [6.0.3](https://github.com/ViktorsBaikers/DevRites/compare/v6.0.2...v6.0.3) (2026-10-14)
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
* **devrites:** cut rite-build time without weakening gates ([#104](https://github.com/ViktorsBaikers/DevRites/issues/104)) ([64133ca](https://github.com/ViktorsBaikers/DevRites/commit/64133ca070947c3e9b170f1ee978302d63937c03))
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
* **no-release:** accept --help after dispatch subcommand ([#103](https://github.com/ViktorsBaikers/DevRites/issues/103)) ([532ce43](https://github.com/ViktorsBaikers/DevRites/commit/532ce4324ae155f891345f825cbd2e04dd37ae85))
|
|
20
|
+
|
|
5
21
|
## [6.0.2](https://github.com/ViktorsBaikers/DevRites/compare/v6.0.1...v6.0.2) (2026-10-14)
|
|
6
22
|
|
|
7
23
|
### 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.
|
|
34
|
+
**Status:** [`v6.0.4`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v6.0.4): 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. |
|
package/docs/adr/README.md
CHANGED
|
@@ -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` |
|
package/docs/engine/commands.md
CHANGED
|
@@ -18,24 +18,24 @@ 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
|
|
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. |
|
|
30
|
-
| `observe summary <slug>` | Emit sanitized JSON workspace summary from one retained observation. `task_graph.ok` is true iff `task_graph.problems` is empty; `problems` lists cycle, unknown-dep, malformed-token, duplicate-id, and missing-`Dependencies` blockers. |
|
|
30
|
+
| `observe summary <slug>` | Emit sanitized JSON workspace summary from one retained observation. `task_graph.ok` is true iff `task_graph.problems` is empty; `problems` lists cycle, unknown-dep, malformed-token, duplicate-id, and missing-`Dependencies` blockers. `inflight_waves` lists dispatch waves still `open` or `sealed`, with their pending roles. |
|
|
31
31
|
| `orient <slug>` | Alias for `observe summary`. |
|
|
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 bundle without `--role` (the root's own read) gives over-budget artifacts as by-reference indexes; 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. `open` also warns when an earlier wave for the same slice never returned. |
|
|
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
|
-
| `metrics summary [slug]` | Reduce `metrics.jsonl` into a per-phase roll-up. |
|
|
38
|
+
| `metrics summary [slug] [--by-slice]` | Reduce `metrics.jsonl` into a per-phase roll-up. `--by-slice` reads `dispatch.json` instead: per slice, build wright rounds, slice-bound Vet rechecks, and agent-minutes (role start to `returned_at`; abandoned waves excluded). |
|
|
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. |
|
|
40
40
|
| `note <add\|list\|check\|rm> <slug> ...` | Anchored workspace notes in `notes.md`: `add <slug> <subject> <quote> <title> [body]` binds a note to a verbatim quote in one repository file and assigns `NOTE-###`; `list` and `check` regrade each anchor `exact`/`moved`/`stale`/`ambiguous`/`lost` by searching the repo for the quote (dependency and workspace trees excluded); `check --repair` rewrites a `moved` subject to its new file; `rm` deletes by ID. `check seal` refuses a malformed `notes.md` and any non-`exact` anchor. |
|
|
41
41
|
| `gates <scaffold\|status\|run\|reverify\|lint\|attest\|abandon> <slug>` | Operate the `gates.md` acceptance ledger: seed per-AC gates, execute runnable gates bound to `test-plan.md` preflight rows, attest manual gates, and reduce to `all-met`/`not-met`/`handoff`/`malformed`. |
|
package/engine/help.go
CHANGED
|
@@ -125,16 +125,20 @@ func takesSubcommand(command string) bool {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
//
|
|
129
|
-
|
|
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.
|
|
136
|
-
//
|
|
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
|
-
|
|
150
|
+
known, ok := valueFlags[command]
|
|
151
|
+
if ok && known[args[i]] {
|
|
147
152
|
i++
|
|
148
|
-
} else if
|
|
153
|
+
} else if !ok && !strings.HasPrefix(args[i], "-") {
|
|
149
154
|
return false
|
|
150
155
|
}
|
|
151
156
|
}
|
package/engine/help_test.go
CHANGED
|
@@ -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,40 @@ 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
|
-
|
|
155
|
-
|
|
206
|
+
abs := filepath.Join(featureDir, filepath.FromSlash(rel))
|
|
207
|
+
// Role bundles without --slice (full Vet, Review, Seal) read
|
|
208
|
+
// everything; root and --slice bundles take over-budget
|
|
209
|
+
// artifacts by anchor.
|
|
210
|
+
if opts.slice == "" && opts.role != "" {
|
|
211
|
+
if writeContextEntry(&bundle, abs, "workspace/"+rel, seen, false) {
|
|
212
|
+
loaded++
|
|
213
|
+
}
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
ok, byRef := writeSliceEntry(&bundle, filepath.Dir(root), abs, "workspace/"+rel, opts.slice, sliceBlock, sliceIDs, seen)
|
|
217
|
+
if ok {
|
|
156
218
|
loaded++
|
|
157
219
|
}
|
|
220
|
+
if byRef {
|
|
221
|
+
byReference++
|
|
222
|
+
} else if ok {
|
|
223
|
+
whole++
|
|
224
|
+
}
|
|
158
225
|
}
|
|
159
226
|
} else if hasManifest && len(workspaceList) > 0 {
|
|
160
227
|
bundle.WriteString("===== workspace =====\nskipped: no feature workspace (slug omitted)\n\n")
|
|
161
228
|
}
|
|
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
229
|
out := opts.out
|
|
176
230
|
if out == "" {
|
|
177
231
|
out = filepath.Join(featureDir, "ctx", bundleName(opts))
|
|
@@ -216,6 +270,11 @@ func RunContext(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
216
270
|
fmt.Fprintf(stdout, ", MISSING: %s", strings.Join(missingRequired, ","))
|
|
217
271
|
}
|
|
218
272
|
fmt.Fprintln(stdout, ")")
|
|
273
|
+
if opts.slice != "" {
|
|
274
|
+
fmt.Fprintf(stdout, "scope: slice %s (%d whole, %d by-reference)\n", opts.slice, whole, byReference)
|
|
275
|
+
} else if byReference > 0 {
|
|
276
|
+
fmt.Fprintf(stdout, "scope: root (%d whole, %d by-reference over budget)\n", whole, byReference)
|
|
277
|
+
}
|
|
219
278
|
if workspaceScoped {
|
|
220
279
|
fmt.Fprintf(stdout, "workspace: role-scoped for %s (%d of %d declared artifacts)\n",
|
|
221
280
|
opts.role, len(workspaceList), len(manifest.Workspace))
|
|
@@ -280,6 +339,111 @@ func expandIncludes(abs string, data []byte) []byte {
|
|
|
280
339
|
})
|
|
281
340
|
}
|
|
282
341
|
|
|
342
|
+
// writeSliceEntry writes one workspace artifact of a --slice or root bundle:
|
|
343
|
+
// with a slice, tasks.md as the slice block plus a by-reference index; any
|
|
344
|
+
// canonical artifact over its line/byte budget as a by-reference index;
|
|
345
|
+
// everything else whole.
|
|
346
|
+
func writeSliceEntry(bundle *strings.Builder, project, abs, label, slice, block string, ids []citedID, seen map[string]bool) (loaded, byRef bool) {
|
|
347
|
+
if seen[abs] {
|
|
348
|
+
return false, false
|
|
349
|
+
}
|
|
350
|
+
data, err := os.ReadFile(abs) // #nosec G304 -- abs is resolved from the validated feature workspace
|
|
351
|
+
name := filepath.Base(abs)
|
|
352
|
+
if err == nil && name == "tasks.md" && slice != "" {
|
|
353
|
+
seen[abs] = true
|
|
354
|
+
fmt.Fprintf(bundle, "===== %s (%s block) =====\n%s\n", label, slice, block)
|
|
355
|
+
} else if status, ok := state.ArtifactBudget(name, data); err != nil || !ok || !status.Over {
|
|
356
|
+
return writeContextEntry(bundle, abs, label, seen, false), false
|
|
357
|
+
} else {
|
|
358
|
+
seen[abs] = true
|
|
359
|
+
}
|
|
360
|
+
writeAnchorIndex(bundle, project, abs, label, data, ids)
|
|
361
|
+
return true, true
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
var (
|
|
365
|
+
citedIDRE = regexp.MustCompile(`\b[A-Z]{1,6}-\d{2,}(?:-[A-Z][A-Z0-9]*)*\b`)
|
|
366
|
+
anchorHeadingRE = regexp.MustCompile(`^#{1,3}\s`)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
// maxIDHits bounds the line anchors listed per ID; the live file holds the rest.
|
|
370
|
+
const maxIDHits = 20
|
|
371
|
+
|
|
372
|
+
// citedID is one ID the slice block cites with its word-bounded matcher.
|
|
373
|
+
type citedID struct {
|
|
374
|
+
id string
|
|
375
|
+
re *regexp.Regexp
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// citedIDs returns each distinct ID the slice block cites (AC-###, DEC-###,
|
|
379
|
+
// T-###, PF-##, SLICE-###, ...).
|
|
380
|
+
func citedIDs(block string) []citedID {
|
|
381
|
+
var out []citedID
|
|
382
|
+
seen := map[string]bool{}
|
|
383
|
+
for _, id := range citedIDRE.FindAllString(block, -1) {
|
|
384
|
+
if !seen[id] {
|
|
385
|
+
seen[id] = true
|
|
386
|
+
out = append(out, citedID{id, regexp.MustCompile(`\b` + regexp.QuoteMeta(id) + `\b`)})
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return out
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// writeAnchorIndex emits a by-reference entry: identity, heading anchors, and
|
|
393
|
+
// line hits for each cited ID, so the agent reads owning sections from the
|
|
394
|
+
// live file instead of the whole artifact.
|
|
395
|
+
func writeAnchorIndex(bundle *strings.Builder, project, abs, label string, data []byte, ids []citedID) {
|
|
396
|
+
rel, err := filepath.Rel(project, abs)
|
|
397
|
+
if err != nil {
|
|
398
|
+
rel = abs
|
|
399
|
+
}
|
|
400
|
+
lines := strings.Split(strings.ReplaceAll(string(data), "\r\n", "\n"), "\n")
|
|
401
|
+
structural := lines
|
|
402
|
+
if masked, err := markdowntext.Structural(data); err == nil {
|
|
403
|
+
structural = strings.Split(strings.ReplaceAll(string(masked), "\r\n", "\n"), "\n")
|
|
404
|
+
}
|
|
405
|
+
fmt.Fprintf(bundle, "===== %s (by reference) =====\n", label)
|
|
406
|
+
fmt.Fprintf(bundle, "path: %s sha256: %x bytes: %d lines: %d\n", filepath.ToSlash(rel), sha256.Sum256(data), len(data), len(lines))
|
|
407
|
+
bundle.WriteString("Not inlined. Read owning sections from the live file by line anchor; nothing is omitted there.\n")
|
|
408
|
+
bundle.WriteString("headings:\n")
|
|
409
|
+
for i, line := range structural {
|
|
410
|
+
if anchorHeadingRE.MatchString(line) {
|
|
411
|
+
fmt.Fprintf(bundle, "- L%d %s\n", i+1, clipRunes(lines[i], 120))
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if len(ids) > 0 {
|
|
415
|
+
bundle.WriteString("id hits (IDs cited by the slice block):\n")
|
|
416
|
+
}
|
|
417
|
+
for _, cited := range ids {
|
|
418
|
+
var at []string
|
|
419
|
+
total := 0
|
|
420
|
+
for i, line := range lines {
|
|
421
|
+
if cited.re.MatchString(line) {
|
|
422
|
+
total++
|
|
423
|
+
if len(at) < maxIDHits {
|
|
424
|
+
at = append(at, fmt.Sprintf("L%d", i+1))
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
hits := "none"
|
|
429
|
+
if total > 0 {
|
|
430
|
+
hits = strings.Join(at, " ")
|
|
431
|
+
if total > len(at) {
|
|
432
|
+
hits += fmt.Sprintf(" (+%d more)", total-len(at))
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
fmt.Fprintf(bundle, "- %s: %s\n", cited.id, hits)
|
|
436
|
+
}
|
|
437
|
+
bundle.WriteByte('\n')
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
func clipRunes(s string, n int) string {
|
|
441
|
+
if r := []rune(s); len(r) > n {
|
|
442
|
+
return string(r[:n]) + "…"
|
|
443
|
+
}
|
|
444
|
+
return s
|
|
445
|
+
}
|
|
446
|
+
|
|
283
447
|
func writeContextEntry(bundle *strings.Builder, abs, label string, seen map[string]bool, expand bool) bool {
|
|
284
448
|
if seen[abs] {
|
|
285
449
|
return false
|
|
@@ -308,6 +472,8 @@ type contextOpts struct {
|
|
|
308
472
|
phase string
|
|
309
473
|
skill string
|
|
310
474
|
role string
|
|
475
|
+
slice string // display label: slices joined with "+"
|
|
476
|
+
slices []string
|
|
311
477
|
triggers []string
|
|
312
478
|
skillsRoot string
|
|
313
479
|
out string
|
|
@@ -325,6 +491,8 @@ func parseContextArgs(args []string, stderr io.Writer) (contextOpts, int) {
|
|
|
325
491
|
opts.skill = value
|
|
326
492
|
case "--role":
|
|
327
493
|
opts.role = value
|
|
494
|
+
case "--slice":
|
|
495
|
+
opts.slices = append(opts.slices, value)
|
|
328
496
|
case "--trigger", "--triggers":
|
|
329
497
|
for _, name := range strings.Split(value, ",") {
|
|
330
498
|
if name = strings.TrimSpace(name); name != "" {
|
|
@@ -353,6 +521,13 @@ func parseContextArgs(args []string, stderr io.Writer) (contextOpts, int) {
|
|
|
353
521
|
fmt.Fprintf(stderr, "context: --skill must be a directory name, not a path\n")
|
|
354
522
|
return opts, 2
|
|
355
523
|
}
|
|
524
|
+
for _, id := range opts.slices {
|
|
525
|
+
if !state.ValidSliceID(id) || len(positional) == 0 {
|
|
526
|
+
fmt.Fprintf(stderr, "context: --slice needs a slug and a SLICE-### id, got %q\n", id)
|
|
527
|
+
return opts, 2
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
opts.slice = strings.Join(opts.slices, "+")
|
|
356
531
|
if len(positional) == 1 {
|
|
357
532
|
opts.slug = positional[0]
|
|
358
533
|
} else if opts.out == "" {
|
|
@@ -429,6 +604,9 @@ func bundleName(opts contextOpts) string {
|
|
|
429
604
|
if opts.role != "" {
|
|
430
605
|
name += "-" + opts.role
|
|
431
606
|
}
|
|
607
|
+
if opts.slice != "" {
|
|
608
|
+
name += "." + opts.slice
|
|
609
|
+
}
|
|
432
610
|
return name + ".bundle.md"
|
|
433
611
|
}
|
|
434
612
|
|