devrites 4.5.0 → 4.7.0
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 +12 -0
- package/NOTICE.md +0 -13
- package/README.md +4 -3
- package/docs/adr/0029-v5-workspace-schema-and-native-migration.md +62 -0
- package/docs/adr/README.md +3 -2
- package/docs/architecture.md +1 -1
- package/docs/cli.md +2 -2
- package/docs/command-map.md +2 -2
- package/docs/engine/commands.md +9 -0
- package/docs/engine/state-schema.md +2 -2
- package/docs/engine/workspace-schema.md +9 -3
- package/docs/usage.md +1 -1
- package/engine/commands.go +23 -0
- package/engine/internal/fsutil/copy_test.go +193 -0
- package/engine/internal/gate/gate_test.go +7 -7
- package/engine/internal/gate/readiness_binding_test.go +8 -2
- package/engine/internal/install/apply.go +314 -0
- package/engine/internal/install/binary.go +255 -0
- package/engine/internal/install/install.go +0 -1596
- package/engine/internal/install/manifest.go +109 -0
- package/engine/internal/install/merge.go +468 -0
- package/engine/internal/install/preflight.go +234 -0
- package/engine/internal/install/uninstall.go +119 -0
- package/engine/internal/install/update.go +184 -0
- package/engine/internal/lib/candidate_test.go +3 -0
- package/engine/internal/lib/closeout.go +4 -0
- package/engine/internal/lib/closeout_test.go +3 -3
- package/engine/internal/lib/migrate.go +219 -0
- package/engine/internal/lib/migrate_test.go +154 -0
- package/engine/internal/lib/resolve.go +3 -0
- package/engine/internal/lib/resolve_remediation_test.go +1 -1
- package/engine/internal/lib/secretscan_test.go +1 -0
- package/engine/internal/lib/taskgraph_test.go +2 -2
- package/engine/internal/parallel/ops.go +67 -16
- package/engine/internal/parallel/ops_test.go +211 -0
- package/engine/internal/state/cmd/workflowmanifest/main.go +24 -13
- package/engine/internal/state/cmd/workflowmanifest/main_test.go +96 -0
- package/engine/internal/state/cursor.go +74 -0
- package/engine/internal/state/schema.go +3 -2
- package/engine/internal/state/state_test.go +2 -2
- package/engine/internal/state/workflow_manifest.json +1 -1
- package/engine/internal/state/workspaceschema.go +58 -0
- package/engine/internal/state/workspaceschema_test.go +67 -0
- package/engine/main.go +7 -31
- package/engine/root_routing_test.go +10 -2
- package/engine/testdata/fixtures/basic/devrites-root/work/auth-tokens/state.md +1 -0
- package/engine/testdata/fixtures/basic/devrites-root/work/search-ranking/state.md +1 -0
- package/engine/testdata/golden/TestParityResolve/answer/state.md.golden +1 -0
- package/engine/testdata/golden/TestParityResolve/drop/state.md.golden +1 -0
- package/engine/tests/parity_closeout_test.go +3 -0
- package/engine/tests/parity_resolve_test.go +1 -0
- package/pack/.claude/agents/devrites-code-reviewer.md +8 -0
- package/pack/.claude/agents/devrites-devex-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-doubt-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-evidence-scout.md +4 -0
- package/pack/.claude/agents/devrites-frontend-reviewer.md +8 -0
- package/pack/.claude/agents/devrites-performance-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-plan-reviewer.md +8 -0
- package/pack/.claude/agents/devrites-retrospector.md +2 -0
- package/pack/.claude/agents/devrites-security-auditor.md +7 -0
- package/pack/.claude/agents/devrites-simplifier-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-spec-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-strategy-reviewer.md +7 -0
- package/pack/.claude/agents/devrites-test-analyst.md +7 -0
- package/pack/.claude/skills/devrites-frontend-craft/SKILL.md +4 -7
- package/pack/.claude/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
- package/pack/.claude/skills/devrites-lib/reference/intent-map.md +7 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/reply-contract.md +9 -7
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/code-review.md +6 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/coding-style.md +4 -3
- package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/core.md +3 -6
- package/pack/.claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/documentation.md +6 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/elicitation.md +5 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/error-handling.md +14 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
- package/pack/.claude/skills/devrites-lib/reference/standards/patterns.md +16 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/performance.md +4 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/security.md +10 -2
- package/pack/.claude/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
- package/pack/.claude/skills/devrites-lib/reference/standards/testing.md +2 -2
- package/pack/.claude/skills/devrites-lib/reference/standards/tooling.md +1 -1
- package/pack/.claude/skills/rite-define/reference/plan-template.md +5 -2
- package/pack/.claude/skills/rite-learn/SKILL.md +6 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +4 -0
- package/pack/.claude/skills/rite-polish/reference/anti-ai-slop.md +12 -53
- package/pack/.claude/skills/rite-pr-feedback/SKILL.md +7 -2
- package/pack/.claude/skills/rite-spec/reference/spec-template.md +5 -4
- package/pack/.claude/skills/rite-status/SKILL.md +2 -0
- package/pack/generated/claude/agents/devrites-code-reviewer.md +8 -0
- package/pack/generated/claude/agents/devrites-devex-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-doubt-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-evidence-scout.md +4 -0
- package/pack/generated/claude/agents/devrites-frontend-reviewer.md +8 -0
- package/pack/generated/claude/agents/devrites-performance-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-plan-reviewer.md +8 -0
- package/pack/generated/claude/agents/devrites-retrospector.md +2 -0
- package/pack/generated/claude/agents/devrites-security-auditor.md +7 -0
- package/pack/generated/claude/agents/devrites-simplifier-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-spec-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-strategy-reviewer.md +7 -0
- package/pack/generated/claude/agents/devrites-test-analyst.md +7 -0
- package/pack/generated/claude/skills/devrites-frontend-craft/SKILL.md +4 -7
- package/pack/generated/claude/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
- package/pack/generated/claude/skills/devrites-lib/reference/intent-map.md +7 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/reply-contract.md +9 -7
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/code-review.md +6 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/coding-style.md +4 -3
- package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/core.md +3 -6
- package/pack/generated/claude/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/documentation.md +6 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/elicitation.md +5 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/error-handling.md +14 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
- package/pack/generated/claude/skills/devrites-lib/reference/standards/patterns.md +16 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/performance.md +4 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/security.md +10 -2
- package/pack/generated/claude/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
- package/pack/generated/claude/skills/devrites-lib/reference/standards/testing.md +2 -2
- package/pack/generated/claude/skills/devrites-lib/reference/standards/tooling.md +1 -1
- package/pack/generated/claude/skills/rite-define/reference/plan-template.md +5 -2
- package/pack/generated/claude/skills/rite-learn/SKILL.md +6 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +4 -0
- package/pack/generated/claude/skills/rite-polish/reference/anti-ai-slop.md +12 -53
- package/pack/generated/claude/skills/rite-pr-feedback/SKILL.md +7 -2
- package/pack/generated/claude/skills/rite-spec/reference/spec-template.md +5 -4
- package/pack/generated/claude/skills/rite-status/SKILL.md +2 -0
- package/pack/generated/codex/agents/devrites-code-reviewer.toml +8 -0
- package/pack/generated/codex/agents/devrites-devex-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-doubt-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-evidence-scout.toml +4 -0
- package/pack/generated/codex/agents/devrites-frontend-reviewer.toml +8 -0
- package/pack/generated/codex/agents/devrites-performance-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-plan-reviewer.toml +8 -0
- package/pack/generated/codex/agents/devrites-retrospector.toml +2 -0
- package/pack/generated/codex/agents/devrites-security-auditor.toml +7 -0
- package/pack/generated/codex/agents/devrites-simplifier-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-spec-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-strategy-reviewer.toml +7 -0
- package/pack/generated/codex/agents/devrites-test-analyst.toml +7 -0
- package/pack/generated/codex/skills/devrites-frontend-craft/SKILL.md +4 -7
- package/pack/generated/codex/skills/devrites-frontend-craft/reference/quality-standards.md +9 -4
- package/pack/generated/codex/skills/devrites-lib/reference/intent-map.md +7 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/reply-contract.md +9 -7
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/code-review.md +6 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/coding-style.md +4 -3
- package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +4 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/core.md +3 -6
- package/pack/generated/codex/skills/devrites-lib/reference/standards/data-integrity.md +4 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/development-workflow.md +8 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/documentation.md +6 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/edge-case-trace.md +3 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/elicitation.md +5 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/error-handling.md +14 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/integration-reliability.md +18 -6
- package/pack/generated/codex/skills/devrites-lib/reference/standards/patterns.md +16 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/performance.md +4 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/repository-topology.md +6 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/security-checklist.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/security.md +10 -2
- package/pack/generated/codex/skills/devrites-lib/reference/standards/skill-authoring.md +8 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/spec-grammar.md +5 -6
- package/pack/generated/codex/skills/devrites-lib/reference/standards/testing.md +2 -2
- package/pack/generated/codex/skills/devrites-lib/reference/standards/tooling.md +1 -1
- package/pack/generated/codex/skills/rite-define/reference/plan-template.md +5 -2
- package/pack/generated/codex/skills/rite-learn/SKILL.md +6 -0
- package/pack/generated/codex/skills/rite-polish/SKILL.md +4 -0
- package/pack/generated/codex/skills/rite-polish/reference/anti-ai-slop.md +12 -53
- package/pack/generated/codex/skills/rite-pr-feedback/SKILL.md +7 -2
- package/pack/generated/codex/skills/rite-spec/reference/spec-template.md +5 -4
- package/pack/generated/codex/skills/rite-status/SKILL.md +2 -0
- package/package.json +1 -1
- package/docs/markdown-instruction-upgrade-2026-08-27.md +0 -127
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
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
|
+
## [4.7.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.6.0...v4.7.0) (2026-08-30)
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
* **devrites:** markdown knowledge-layer round 5 and engine v5 slate ([#49](https://github.com/ViktorsBaikers/DevRites/issues/49)) ([2ef8e9a](https://github.com/ViktorsBaikers/DevRites/commit/2ef8e9a20c750d37f506d6ed0fbcd6bce74fce55))
|
|
10
|
+
|
|
11
|
+
## [4.6.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.5.0...v4.6.0) (2026-08-28)
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
* **skills:** markdown knowledge-layer round 4 ([#48](https://github.com/ViktorsBaikers/DevRites/issues/48)) ([9dab7af](https://github.com/ViktorsBaikers/DevRites/commit/9dab7af644c219ab3f3411b4a701947b9da9108b))
|
|
16
|
+
|
|
5
17
|
## [4.5.0](https://github.com/ViktorsBaikers/DevRites/compare/v4.4.2...v4.5.0) (2026-08-27)
|
|
6
18
|
|
|
7
19
|
### Added
|
package/NOTICE.md
CHANGED
|
@@ -7,19 +7,6 @@ The Nielsen heuristics 0–4 scoring rubric in
|
|
|
7
7
|
`pack/.claude/skills/rite-review/reference/nielsen-heuristics.md` references the heuristics themselves
|
|
8
8
|
(Jakob Nielsen, 1994 / 2020 — public usability canon).
|
|
9
9
|
|
|
10
|
-
## Research attribution (2026-08-27 instruction upgrade)
|
|
11
|
-
|
|
12
|
-
The 2026-08-27 Markdown methodology round adapted *concepts* (not text) from external,
|
|
13
|
-
untrusted-at-research-time repositories. Adaptations below are rewritten in DevRites language;
|
|
14
|
-
no upstream prose ships in this pack.
|
|
15
|
-
|
|
16
|
-
- [rebelytics/one-skill-to-rule-them-all](https://github.com/rebelytics/one-skill-to-rule-them-all) — CC BY 4.0: its capture/promotion/self-improvement loop **structure** informed `rite-learn` and `devrites-retrospector` guardrails (qualification contract, consolidation duty, growth cap, rejection persistence). Attribution required by license and hereby given.
|
|
17
|
-
- [mukul975/Anthropic-Cybersecurity-Skills](https://github.com/mukul975/Anthropic-Cybersecurity-Skills) — Apache 2.0: its change-type→domain routing shape and co-located framework-identifier binding method informed `standards/security.md`. Concept-level adaptation; catalog not imported.
|
|
18
|
-
- [pbakaus/impeccable](https://github.com/pbakaus/impeccable) — Apache 2.0: earlier derivative material remains in-tree; this round's Verify-vs-Refuse framing influenced the browser-proof checklist rewrite. Concept-level adaptation.
|
|
19
|
-
- [netresearch/file-search-skill](https://github.com/netresearch/file-search-skill) — MIT AND CC BY-SA 4.0: only the *idea* of a question-type→tool matrix was used; `standards/tooling.md`'s table was rebuilt natively from DevRites tools (no structural transplant, honoring share-alike).
|
|
20
|
-
- [Nutlope/hallmark](https://github.com/Nutlope/hallmark), [Leonxlnx/taste-skill](https://github.com/Leonxlnx/taste-skill), [educlopez/ui-craft](https://github.com/educlopez/ui-craft), [Weizhena/Deep-Research-skills](https://github.com/Weizhena/Deep-Research-skills) — MIT: viewport/state-capture checks, mechanical slop counters, coverage markers, and research checkpoint ideas, adapted concept-level.
|
|
21
|
-
|
|
22
|
-
|
|
23
10
|
## License
|
|
24
11
|
Free to use. Modification is permitted **for personal use**; any other use —
|
|
25
12
|
distributing it, distributing modified versions, or commercial/organizational use —
|
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ project-conventional push, tag, or PR action, and archive the workspace.
|
|
|
28
28
|
Unattended runs may create local WIP checkpoint commits along the way, but they
|
|
29
29
|
remain local unless Ship's disclosed plan includes an approved remote action.
|
|
30
30
|
|
|
31
|
-
**Status:** [`v4.
|
|
31
|
+
**Status:** [`v4.7.0`](https://github.com/ViktorsBaikers/DevRites/releases/tag/v4.7.0): see [`CHANGELOG.md`](CHANGELOG.md) for release notes.
|
|
32
32
|
|
|
33
33
|
This is the latest published release; `main` may contain unreleased work.
|
|
34
34
|
|
|
@@ -123,8 +123,9 @@ with the self-contained updater.
|
|
|
123
123
|
`/rite-upgrade` is the separate native, preservation-first route for reconciling
|
|
124
124
|
an unfinished workspace. It proves a current-contract defect before routing
|
|
125
125
|
Clarify, Plan repair, Converge, Vet, Prove, Polish, Review, or Seal; it never
|
|
126
|
-
migrates cursor format or invents historical proof.
|
|
127
|
-
|
|
126
|
+
migrates cursor format or invents historical proof. `devrites-engine migrate`
|
|
127
|
+
owns deterministic v5 schema normalization; see the [CLI contract](docs/cli.md)
|
|
128
|
+
and [ADR-0029](docs/adr/0029-v5-workspace-schema-and-native-migration.md).
|
|
128
129
|
|
|
129
130
|
The [command map](docs/command-map.md) covers every command, trigger, input, and
|
|
130
131
|
output. The [worked examples](docs/usage.md) show normal features, plan drift,
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# ADR-0029: v5 workspace schema and native migration
|
|
2
|
+
|
|
3
|
+
- **Status:** Accepted
|
|
4
|
+
- **Date:** 2026-08-28
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The v5 engine changes the workspace contract: `state.md` carries a `schema`
|
|
9
|
+
cursor row declaring the workspace schema version, and the current contract is
|
|
10
|
+
schema 3. Workspaces written before v5 carry no row and resolve to schema 2,
|
|
11
|
+
the last pre-v5 contract.
|
|
12
|
+
|
|
13
|
+
The repository's preservation-first stance (ADR-0022, as applied by ADR-0025)
|
|
14
|
+
forbade an engine migrator: judgment work belongs to skills, and
|
|
15
|
+
`/rite-upgrade` routes repairs through phase owners. That stance served
|
|
16
|
+
read-compatible releases where no normalization was needed. A schema bump is
|
|
17
|
+
different: normalization — cursor encoding, missing-artifact reconciliation —
|
|
18
|
+
is deterministic, content-preserving work that needs per-feature locking,
|
|
19
|
+
atomic writes, and fail-closed refusal on ambiguity. Those primitives are
|
|
20
|
+
engine-owned.
|
|
21
|
+
|
|
22
|
+
## Decision
|
|
23
|
+
|
|
24
|
+
- The engine declares the current workspace schema (`state.SchemaVersion`), and
|
|
25
|
+
each workspace declares its own through a `schema` row in its `state.md`
|
|
26
|
+
cursor. An absent row means schema 2.
|
|
27
|
+
- `state resolve` and `state close` refuse workspaces whose declared schema
|
|
28
|
+
differs from the engine's, naming the recovery path:
|
|
29
|
+
`devrites-engine migrate <slug>` for pre-v5 workspaces, `upgrade devrites`
|
|
30
|
+
for newer ones. Diagnostic checks (readiness, candidate, task-graph, observe)
|
|
31
|
+
keep reading pre-v5 workspaces and reporting their established named gaps.
|
|
32
|
+
- `devrites-engine migrate` performs fail-closed normalization: cursor-form
|
|
33
|
+
conversion through the existing dual-form primitives, missing required
|
|
34
|
+
artifacts as empty stubs with no synthesized content, and byte-exact
|
|
35
|
+
preservation of bound proof files. The v2→v3 normalization cannot invalidate
|
|
36
|
+
a binding — it only creates missing files and rewrites the unbound ledger —
|
|
37
|
+
and any future schema delta that would modify a bound file requires explicit
|
|
38
|
+
per-finding confirmation before it applies.
|
|
39
|
+
- This supersedes the no-migrator invariant of ADR-0022 as applied by ADR-0025
|
|
40
|
+
for deterministic schema normalization only. `/rite-upgrade` keeps
|
|
41
|
+
preservation-first routing for judgment work and may hand mechanical
|
|
42
|
+
normalization to the engine command.
|
|
43
|
+
|
|
44
|
+
## Alternatives considered
|
|
45
|
+
|
|
46
|
+
| Option | Why not |
|
|
47
|
+
|--------|---------|
|
|
48
|
+
| Keep the no-migrator stance | Every pre-v5 workspace would strand mid-feature or silently diverge from the declared contract; the manifest version alone cannot normalize. |
|
|
49
|
+
| Refuse all reads of pre-v5 workspaces | Diagnostics are the user's fastest evidence of what migration will change; refusing them removes actionable output without adding safety. |
|
|
50
|
+
| Migrate as a skill checklist | Cursor rewrites and artifact stubs need the feature lock, atomic writes, and deterministic refusal on ambiguity — engine-owned primitives. |
|
|
51
|
+
| New marker file per workspace | Duplicates the ledger; the cursor row travels inside the file every workspace already has. |
|
|
52
|
+
|
|
53
|
+
## Consequences
|
|
54
|
+
|
|
55
|
+
Pre-v5 workspaces remain readable; mutating or closing them requires
|
|
56
|
+
migration. Migration is deterministic and byte-preserving by default, so
|
|
57
|
+
recorded proof stays valid, and invalidation is never silent. The schema row
|
|
58
|
+
travels inside `state.md`, so no new file format exists. New workspaces created
|
|
59
|
+
by the v5 pack declare the row at creation.
|
|
60
|
+
|
|
61
|
+
Guard tests: `engine/internal/state/workspaceschema_test.go`,
|
|
62
|
+
`engine/tests/parity_resolve_test.go`, `engine/tests/parity_closeout_test.go`.
|
package/docs/adr/README.md
CHANGED
|
@@ -67,10 +67,11 @@ architecture; per-feature `decisions.md` files stay scoped to that feature.
|
|
|
67
67
|
| [0019](0019-native-boundary-with-deterministic-gates.md) | Native boundary with deterministic gates | Accepted in part; semantic gates superseded by 0022, native writer and fresh approval retained | `engine/internal/gate/gate_test.go` |
|
|
68
68
|
| [0020](0020-thin-engine-native-orchestration-boundary.md) | Thin engine and native host orchestration | Accepted in part; engine-surface/migration scope superseded by 0022, native orchestration retained | `tests/native-orchestration-contract-test.sh`, `engine/root_routing_test.go` |
|
|
69
69
|
| [0021](0021-observable-workspace-compatibility.md) | Observable and reversible workspace compatibility | Superseded by 0022 | historical |
|
|
70
|
-
| [0022](0022-native-orchestration-thin-engine.md) | Native orchestration with a thin deterministic engine | Accepted in part; snapshot/JSON-output clauses superseded by 0023; retained policy-command/install clauses narrowed by 0024 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `engine/internal/lib/cursor_compat_test.go`, `tests/native-orchestration-contract-test.sh` |
|
|
70
|
+
| [0022](0022-native-orchestration-thin-engine.md) | Native orchestration with a thin deterministic engine | Accepted in part; snapshot/JSON-output clauses superseded by 0023; retained policy-command/install clauses narrowed by 0024; no-migrator normalization scope superseded by 0029 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `engine/internal/lib/cursor_compat_test.go`, `tests/native-orchestration-contract-test.sh` |
|
|
71
71
|
| [0023](0023-native-workspace-reads-line-output.md) | Native workspace reads and line-oriented engine output | Accepted in part; AFK/recovery/doctor retention superseded by 0024 | `engine/root_routing_test.go`, `engine/internal/gate/gate_test.go`, `tests/native-orchestration-contract-test.sh`, `tests/npx-pack-smoke.sh` |
|
|
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
|
-
| [0025](0025-evidence-gated-workspace-upgrades.md) | Evidence-gated semantic workspace upgrades | Accepted | `tests/phase-gate-routing-test.sh`, `tests/host-artifacts-test.sh` |
|
|
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
75
|
| [0027](0027-content-bound-build-readiness.md) | Content-bound build readiness | Accepted | `engine/tests/adr_0027_readiness_binding_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
|
+
| [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` |
|
package/docs/architecture.md
CHANGED
|
@@ -212,7 +212,7 @@ delta; Clarify, Plan repair, Converge, Vet, Prove, Polish, Review, or Seal then
|
|
|
212
212
|
performs it under normal gates. Candidate repair runs current real proof and
|
|
213
213
|
never synthesizes a historical pass; ambiguous legacy candidate scope is a gap.
|
|
214
214
|
|
|
215
|
-
The engine
|
|
215
|
+
The engine owns deterministic v5 schema normalization (`devrites-engine migrate`) or compatibility telemetry. It
|
|
216
216
|
directly reads the official v1/v2 bullet and v3 table `state.md` cursors without
|
|
217
217
|
rewriting them. Wider pre-release compatibility experiments are not runtime
|
|
218
218
|
contracts. See [ADR-0025](adr/0025-evidence-gated-workspace-upgrades.md) and
|
package/docs/cli.md
CHANGED
|
@@ -22,6 +22,7 @@ devrites-engine check seal <slug>
|
|
|
22
22
|
|
|
23
23
|
devrites-engine state resolve <qid> "<answer>"
|
|
24
24
|
devrites-engine state close <slug>
|
|
25
|
+
devrites-engine migrate <slug> [--dry-run] [--answer id=choice]
|
|
25
26
|
|
|
26
27
|
devrites-engine secret-scan [--staged] [--stdin] [slug]
|
|
27
28
|
devrites-engine open-visual <path-or-name> [--slug <slug>] [--no-open]
|
|
@@ -30,8 +31,7 @@ devrites-engine version
|
|
|
30
31
|
|
|
31
32
|
Commands outside this operational list and the standard help/version forms are
|
|
32
33
|
unsupported. There are no legacy engine aliases, tombstones, agent-protocol
|
|
33
|
-
versions, semantic-readiness digests, compatibility telemetry
|
|
34
|
-
migration command.
|
|
34
|
+
versions, semantic-readiness digests, or compatibility telemetry.
|
|
35
35
|
`check candidate` is additive; no existing engine command or public `/rite-*`
|
|
36
36
|
workflow was removed or renamed.
|
|
37
37
|
|
package/docs/command-map.md
CHANGED
|
@@ -41,7 +41,7 @@ The Go surface is intentionally closed and deterministic:
|
|
|
41
41
|
| Lane | Commands | Owner |
|
|
42
42
|
|---|---|---|
|
|
43
43
|
| Candidate and deterministic checks | `check candidate`, `check readiness [--emit-binding]`, `check seal`, `check task-graph`, `check path-disjoint`, `check skill-trust` | Candidate validates/hashes the strict project manifest; readiness checks phase files, the required `tasks.md` slice graph, or emits the vetted Build-input binding; seal checks final files/open gates, that graph and binding, then exact candidate bindings. |
|
|
44
|
-
| Atomic workspace state | `state resolve`, `state close` | Go owns answer/drop/batch resolution and
|
|
44
|
+
| Atomic workspace state | `state resolve`, `state close`, `migrate [--dry-run] [--answer id=choice]` | Go owns answer/drop/batch resolution, transactional close, and fail-closed v5 schema normalization. |
|
|
45
45
|
| Security | `secret-scan` | `/rite-ship`, safe hooks, or an operator scans staged blobs, stdin, or touched files. |
|
|
46
46
|
| Install/operator | `install`, `update`, `uninstall`, `version` | Direct update acquires latest; npm/bootstrap may supply local candidates; the engine performs manifest-owned local changes. |
|
|
47
47
|
| Native policy | no engine command | Skills/root own spec grammar re-read, qid allocation, Clarify cursor edits, AFK/recovery accounting, and read-only `/rite-doctor`. |
|
|
@@ -53,7 +53,7 @@ prints exactly
|
|
|
53
53
|
`candidate-sha256: <64 lowercase hex>` and `candidate-files: <row count>` on a
|
|
54
54
|
pass; usage/root errors exit `2` and candidate blocks exit `3`. There are no
|
|
55
55
|
semantic readiness commands, reviewer-prose parsers, capability-ledger engine,
|
|
56
|
-
compatibility telemetry
|
|
56
|
+
or compatibility telemetry.
|
|
57
57
|
|
|
58
58
|
Claude Code and Codex own native dispatch. Installed skills and exact agents
|
|
59
59
|
own semantic readiness, traceability, acceptance/evidence quality, doubt,
|
package/docs/engine/commands.md
CHANGED
|
@@ -21,6 +21,7 @@ workflow strategy.
|
|
|
21
21
|
| `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. |
|
|
22
22
|
| `state resolve <qid> "<answer>"` | Resolve an open question and update `questions.md` plus `state.md` atomically. |
|
|
23
23
|
| `state close <slug>` | Archive a shipped workspace and clear matching `ACTIVE`. |
|
|
24
|
+
| `migrate <slug> [--dry-run] [--answer id=choice]` | Normalize a pre-v5 workspace to the current schema; fail-closed, one-shot. |
|
|
24
25
|
| `secret-scan [--staged] [--stdin] [slug]` | Scan exact staged blobs, stdin, or touched regular files for credential material. |
|
|
25
26
|
| `open-visual <path-or-name> [--slug <slug>] [--no-open]` | Resolve a local visual HTML file, optionally open it in the OS browser, warn if the sibling outline is missing or inventory ids are absent from HTML, and print agent path tips. No network. |
|
|
26
27
|
| `version` | Print the engine version. |
|
|
@@ -55,6 +56,14 @@ State mutations use the shared physical-root checks, feature lock, and atomic
|
|
|
55
56
|
write path. `state resolve` additionally supports `--drop` and `--batch`;
|
|
56
57
|
`state close` owns transactional archive plus `ACTIVE` clearing.
|
|
57
58
|
|
|
59
|
+
`migrate` normalizes a pre-v5 workspace: legacy bullet cursor fields become
|
|
60
|
+
canonical table rows, the `schema` row is recorded, and missing required
|
|
61
|
+
artifacts are created as empty stubs (content is never synthesized, and bound
|
|
62
|
+
proof files stay byte-exact). It is one-shot and fail-closed: on ambiguity it
|
|
63
|
+
writes nothing, prints its questions, and exits `3`; answers arrive on rerun
|
|
64
|
+
via `--answer id=choice`. `--dry-run` prints the plan and always writes
|
|
65
|
+
nothing. See [ADR-0029](../adr/0029-v5-workspace-schema-and-native-migration.md).
|
|
66
|
+
|
|
58
67
|
Normative spec grammar checks, qid allocation, Clarify cursor transitions, AFK
|
|
59
68
|
slice accounting, recovery attempt accounting, and installation diagnostics are
|
|
60
69
|
explicit root-owned native procedures. The workflow owns reproduction,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# `.devrites/` state schema (
|
|
1
|
+
# `.devrites/` state schema (v3)
|
|
2
2
|
|
|
3
3
|
The engine reads workflow state from plain files under `.devrites/`. Those
|
|
4
4
|
human-editable files remain authoritative; the engine has no database or
|
|
5
5
|
semantic cache.
|
|
6
6
|
|
|
7
7
|
<!-- authority:schema-version:start -->
|
|
8
|
-
`schemaVersion:
|
|
8
|
+
`schemaVersion: 3`.
|
|
9
9
|
<!-- authority:schema-version:end -->
|
|
10
10
|
|
|
11
11
|
For root selection, supported legacy cursors, and the full artifact layout, see
|
|
@@ -125,7 +125,12 @@ The runtime reads only official released workspace formats:
|
|
|
125
125
|
The canonical workspace location remains `.devrites/work/<slug>/`; canonical
|
|
126
126
|
map, cursor, and proof files are `README.md`, `state.md`, and `evidence.md`.
|
|
127
127
|
Compatibility reads do not rewrite a workspace and do not emit local telemetry.
|
|
128
|
-
|
|
128
|
+
Since engine v5, workspaces declare their schema through a `schema` row in the
|
|
129
|
+
`state.md` cursor; an absent row means the pre-v5 schema 2 contract. `state
|
|
130
|
+
resolve` and `state close` refuse workspaces whose declared schema is not the
|
|
131
|
+
current one: pre-v5 workspaces run `devrites-engine migrate <slug>`, and newer
|
|
132
|
+
workspaces require upgrading devrites.
|
|
133
|
+
`/rite-upgrade` first audits an older
|
|
129
134
|
active workspace against named current contracts; only a cited defect may route
|
|
130
135
|
an edit through its normal phase owner. For released unfinished post-Build
|
|
131
136
|
workspaces, candidate defects route through current Prove, Polish, Review, and
|
|
@@ -134,8 +139,9 @@ an old pass.
|
|
|
134
139
|
|
|
135
140
|
Other pre-release layouts, filename substitutions, and phase encodings are
|
|
136
141
|
not runtime authorities. See
|
|
137
|
-
[ADR-0022](../adr/0022-native-orchestration-thin-engine.md)
|
|
138
|
-
[ADR-0025](../adr/0025-evidence-gated-workspace-upgrades.md)
|
|
142
|
+
[ADR-0022](../adr/0022-native-orchestration-thin-engine.md),
|
|
143
|
+
[ADR-0025](../adr/0025-evidence-gated-workspace-upgrades.md), and
|
|
144
|
+
[ADR-0029](../adr/0029-v5-workspace-schema-and-native-migration.md).
|
|
139
145
|
|
|
140
146
|
## Native policy state
|
|
141
147
|
|
package/docs/usage.md
CHANGED
|
@@ -61,7 +61,7 @@ preservation-first audit: age/cursor encoding alone is not a defect, and only a
|
|
|
61
61
|
cited current-contract failure routes repair through its phase owner. Completed
|
|
62
62
|
work and evidence stay intact. Candidate defects may route current Prove,
|
|
63
63
|
Polish, Review, and Seal in that order; old passes are never synthesized. The
|
|
64
|
-
engine
|
|
64
|
+
engine owns deterministic v5 schema normalization.
|
|
65
65
|
|
|
66
66
|
Project-root sentinel (outside the workspace):
|
|
67
67
|
|
package/engine/commands.go
CHANGED
|
@@ -17,6 +17,7 @@ const (
|
|
|
17
17
|
rootUnused rootMode = iota
|
|
18
18
|
rootLenient
|
|
19
19
|
rootStrict
|
|
20
|
+
rootStrictUsage
|
|
20
21
|
)
|
|
21
22
|
|
|
22
23
|
// rootModeFor is the single policy boundary between diagnostic/read-only
|
|
@@ -34,6 +35,20 @@ func rootModeFor(command string, args []string) rootMode {
|
|
|
34
35
|
return rootStrict
|
|
35
36
|
}
|
|
36
37
|
return rootUnused
|
|
38
|
+
case "migrate":
|
|
39
|
+
return rootStrictUsage
|
|
40
|
+
case "check":
|
|
41
|
+
switch subcommand {
|
|
42
|
+
case "candidate", "readiness", "seal", "task-graph":
|
|
43
|
+
return rootStrictUsage
|
|
44
|
+
}
|
|
45
|
+
return rootUnused
|
|
46
|
+
case "observe":
|
|
47
|
+
switch subcommand {
|
|
48
|
+
case "summary":
|
|
49
|
+
return rootStrictUsage
|
|
50
|
+
}
|
|
51
|
+
return rootUnused
|
|
37
52
|
default:
|
|
38
53
|
return rootUnused
|
|
39
54
|
}
|
|
@@ -67,6 +82,14 @@ func resolveRootFor(command string, args []string) (string, int, error) {
|
|
|
67
82
|
return fallbackRoot(), exitOK, nil
|
|
68
83
|
}
|
|
69
84
|
return "", exitUsage, err
|
|
85
|
+
case rootStrictUsage:
|
|
86
|
+
root, err := state.ResolveRoot(os.Getenv("DEVRITES_ROOT"))
|
|
87
|
+
if err != nil {
|
|
88
|
+
// Workspace-requiring read/check commands have always mapped every
|
|
89
|
+
// resolution failure to the usage exit code.
|
|
90
|
+
return "", exitUsage, err
|
|
91
|
+
}
|
|
92
|
+
return root, exitOK, nil
|
|
70
93
|
default:
|
|
71
94
|
panic("unknown root resolution mode")
|
|
72
95
|
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
package fsutil
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"runtime"
|
|
7
|
+
"strings"
|
|
8
|
+
"testing"
|
|
9
|
+
"time"
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
func TestWriteFileAtomicWritesAndOverwrites(t *testing.T) {
|
|
13
|
+
dir := t.TempDir()
|
|
14
|
+
path := filepath.Join(dir, "nested", "file.txt")
|
|
15
|
+
if err := WriteFileAtomic(path, []byte("first\n"), 0o644); err != nil {
|
|
16
|
+
t.Fatal(err)
|
|
17
|
+
}
|
|
18
|
+
got, err := os.ReadFile(path)
|
|
19
|
+
if err != nil {
|
|
20
|
+
t.Fatal(err)
|
|
21
|
+
}
|
|
22
|
+
if string(got) != "first\n" {
|
|
23
|
+
t.Fatalf("content %q", got)
|
|
24
|
+
}
|
|
25
|
+
if !PermissionsMatch(mustMode(t, path), 0o644) {
|
|
26
|
+
t.Fatalf("perm %v", mustMode(t, path))
|
|
27
|
+
}
|
|
28
|
+
// Overwrite in place.
|
|
29
|
+
if err := WriteFileAtomic(path, []byte("second\n"), 0o644); err != nil {
|
|
30
|
+
t.Fatal(err)
|
|
31
|
+
}
|
|
32
|
+
if got, _ := os.ReadFile(path); string(got) != "second\n" {
|
|
33
|
+
t.Fatalf("content after overwrite %q", got)
|
|
34
|
+
}
|
|
35
|
+
// No temp files left behind.
|
|
36
|
+
entries, err := os.ReadDir(filepath.Dir(path))
|
|
37
|
+
if err != nil {
|
|
38
|
+
t.Fatal(err)
|
|
39
|
+
}
|
|
40
|
+
for _, e := range entries {
|
|
41
|
+
if name := e.Name(); name != "file.txt" && name != "nested" {
|
|
42
|
+
t.Fatalf("temp file left behind: %s", name)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func mustMode(t *testing.T, path string) os.FileMode {
|
|
48
|
+
t.Helper()
|
|
49
|
+
info, err := os.Stat(path)
|
|
50
|
+
if err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
return info.Mode()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
func TestFileModTime(t *testing.T) {
|
|
57
|
+
dir := t.TempDir()
|
|
58
|
+
path := filepath.Join(dir, "file.txt")
|
|
59
|
+
if err := os.WriteFile(path, []byte("x"), 0o644); err != nil {
|
|
60
|
+
t.Fatal(err)
|
|
61
|
+
}
|
|
62
|
+
modified, ok := FileModTime(path)
|
|
63
|
+
if !ok {
|
|
64
|
+
t.Fatal("regular file should report mod time")
|
|
65
|
+
}
|
|
66
|
+
if modified <= 0 {
|
|
67
|
+
t.Fatalf("mod time %d", modified)
|
|
68
|
+
}
|
|
69
|
+
if _, ok := FileModTime(filepath.Join(dir, "missing.txt")); ok {
|
|
70
|
+
t.Fatal("missing file should report !ok")
|
|
71
|
+
}
|
|
72
|
+
if _, ok := FileModTime(dir); ok {
|
|
73
|
+
t.Fatal("directory should report !ok")
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
func TestNewestModTime(t *testing.T) {
|
|
78
|
+
dir := t.TempDir()
|
|
79
|
+
old := filepath.Join(dir, "old.txt")
|
|
80
|
+
new := filepath.Join(dir, "new.txt")
|
|
81
|
+
if err := os.WriteFile(old, []byte("x"), 0o644); err != nil {
|
|
82
|
+
t.Fatal(err)
|
|
83
|
+
}
|
|
84
|
+
if err := os.WriteFile(new, []byte("x"), 0o644); err != nil {
|
|
85
|
+
t.Fatal(err)
|
|
86
|
+
}
|
|
87
|
+
future := time.Now().Add(2 * time.Hour)
|
|
88
|
+
if err := os.Chtimes(new, future, future); err != nil {
|
|
89
|
+
t.Skipf("cannot set file times on this platform: %v", err)
|
|
90
|
+
}
|
|
91
|
+
newest, ok := NewestModTime(old, new)
|
|
92
|
+
if !ok {
|
|
93
|
+
t.Fatal("expected ok")
|
|
94
|
+
}
|
|
95
|
+
want, _ := FileModTime(new)
|
|
96
|
+
if newest != want {
|
|
97
|
+
t.Fatalf("newest=%d want %d", newest, want)
|
|
98
|
+
}
|
|
99
|
+
if _, ok := NewestModTime(filepath.Join(dir, "missing.txt")); ok {
|
|
100
|
+
t.Fatal("missing-only inputs should report !ok")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func TestCopyTreeMissingSourceIsNoOp(t *testing.T) {
|
|
105
|
+
dir := t.TempDir()
|
|
106
|
+
dst := filepath.Join(dir, "dst")
|
|
107
|
+
if err := CopyTree(filepath.Join(dir, "missing"), dst); err != nil {
|
|
108
|
+
t.Fatalf("missing src must not error: %v", err)
|
|
109
|
+
}
|
|
110
|
+
if _, err := os.Stat(dst); !os.IsNotExist(err) {
|
|
111
|
+
t.Fatalf("dst must not be created, got %v", err)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
func TestCopyTreeFileAndTree(t *testing.T) {
|
|
116
|
+
dir := t.TempDir()
|
|
117
|
+
src := filepath.Join(dir, "src")
|
|
118
|
+
if err := os.MkdirAll(filepath.Join(src, "sub"), 0o755); err != nil {
|
|
119
|
+
t.Fatal(err)
|
|
120
|
+
}
|
|
121
|
+
if err := os.WriteFile(filepath.Join(src, "root.txt"), []byte("root\n"), 0o644); err != nil {
|
|
122
|
+
t.Fatal(err)
|
|
123
|
+
}
|
|
124
|
+
if err := os.WriteFile(filepath.Join(src, "sub", "leaf.txt"), []byte("leaf\n"), 0o644); err != nil {
|
|
125
|
+
t.Fatal(err)
|
|
126
|
+
}
|
|
127
|
+
dst := filepath.Join(dir, "dst")
|
|
128
|
+
if err := CopyTree(src, dst); err != nil {
|
|
129
|
+
t.Fatal(err)
|
|
130
|
+
}
|
|
131
|
+
for rel, want := range map[string]string{
|
|
132
|
+
"root.txt": "root\n",
|
|
133
|
+
"sub/leaf.txt": "leaf\n",
|
|
134
|
+
} {
|
|
135
|
+
got, err := os.ReadFile(filepath.Join(dst, filepath.FromSlash(rel)))
|
|
136
|
+
if err != nil {
|
|
137
|
+
t.Fatalf("%s: %v", rel, err)
|
|
138
|
+
}
|
|
139
|
+
if string(got) != want {
|
|
140
|
+
t.Fatalf("%s: %q want %q", rel, got, want)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Copying a single file copies its contents.
|
|
145
|
+
single := filepath.Join(dir, "single.txt")
|
|
146
|
+
if err := os.WriteFile(single, []byte("one\n"), 0o644); err != nil {
|
|
147
|
+
t.Fatal(err)
|
|
148
|
+
}
|
|
149
|
+
singleDst := filepath.Join(dir, "single-copy.txt")
|
|
150
|
+
if err := CopyTree(single, singleDst); err != nil {
|
|
151
|
+
t.Fatal(err)
|
|
152
|
+
}
|
|
153
|
+
if got, _ := os.ReadFile(singleDst); string(got) != "one\n" {
|
|
154
|
+
t.Fatalf("single copy %q", got)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Recopying overwrites existing files.
|
|
158
|
+
if err := os.WriteFile(filepath.Join(src, "root.txt"), []byte("changed\n"), 0o644); err != nil {
|
|
159
|
+
t.Fatal(err)
|
|
160
|
+
}
|
|
161
|
+
if err := CopyTree(src, dst); err != nil {
|
|
162
|
+
t.Fatal(err)
|
|
163
|
+
}
|
|
164
|
+
if got, _ := os.ReadFile(filepath.Join(dst, "root.txt")); string(got) != "changed\n" {
|
|
165
|
+
t.Fatalf("overwrite %q", got)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func TestPermissionsMatch(t *testing.T) {
|
|
170
|
+
if !PermissionsMatch(0o644, 0o644) {
|
|
171
|
+
t.Fatal("matching perms must match")
|
|
172
|
+
}
|
|
173
|
+
if runtime.GOOS != "windows" && PermissionsMatch(0o600, 0o644) {
|
|
174
|
+
t.Fatal("differing POSIX perms must not match")
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
func TestWriteFileAtomicSurfacesFailure(t *testing.T) {
|
|
179
|
+
dir := t.TempDir()
|
|
180
|
+
blocker := filepath.Join(dir, "blocker")
|
|
181
|
+
if err := os.WriteFile(blocker, []byte("x"), 0o644); err != nil {
|
|
182
|
+
t.Fatal(err)
|
|
183
|
+
}
|
|
184
|
+
// The parent path is an existing file, so creating the destination
|
|
185
|
+
// directory must fail and the error must surface to the caller.
|
|
186
|
+
err := WriteFileAtomic(filepath.Join(blocker, "nested", "file.txt"), []byte("x"), 0o644)
|
|
187
|
+
if err == nil {
|
|
188
|
+
t.Fatal("expected the write failure to surface")
|
|
189
|
+
}
|
|
190
|
+
if !strings.Contains(err.Error(), "atomic write") {
|
|
191
|
+
t.Fatalf("error should identify the operation, got %v", err)
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -123,7 +123,7 @@ Goal: looks complete without an ordering field
|
|
|
123
123
|
func TestCheckAndRenderReadiness(t *testing.T) {
|
|
124
124
|
root := t.TempDir()
|
|
125
125
|
writeFeature(t, root, "alpha", map[string]string{
|
|
126
|
-
"state.md": "- Phase: build\n",
|
|
126
|
+
"state.md": "- Phase: build\n- Schema: 3\n",
|
|
127
127
|
"brief.md": "brief\n",
|
|
128
128
|
"spec.md": "real spec\n",
|
|
129
129
|
"assumptions.md": "none\n",
|
|
@@ -164,7 +164,7 @@ func TestCheckAndRenderReadiness(t *testing.T) {
|
|
|
164
164
|
func TestCheckUsesConcreteWorkspaceRequirements(t *testing.T) {
|
|
165
165
|
root := t.TempDir()
|
|
166
166
|
files := map[string]string{
|
|
167
|
-
"state.md": "| phase | vet |\n",
|
|
167
|
+
"state.md": "| phase | vet |\n| schema | 3 |\n",
|
|
168
168
|
"brief.md": "brief\n",
|
|
169
169
|
"spec.md": "spec\n",
|
|
170
170
|
"decisions.md": "decisions\n",
|
|
@@ -192,7 +192,7 @@ func TestCheckUsesConcreteWorkspaceRequirements(t *testing.T) {
|
|
|
192
192
|
func TestSealRequiresDurableReviewAndSealArtifacts(t *testing.T) {
|
|
193
193
|
root := t.TempDir()
|
|
194
194
|
files := map[string]string{
|
|
195
|
-
"state.md": "| phase | seal |\n",
|
|
195
|
+
"state.md": "| phase | seal |\n| schema | 3 |\n",
|
|
196
196
|
"brief.md": "brief\n",
|
|
197
197
|
"spec.md": "spec\n",
|
|
198
198
|
"decisions.md": "decisions\n",
|
|
@@ -373,7 +373,7 @@ func TestCheckBlocksOpenHumanQuestions(t *testing.T) {
|
|
|
373
373
|
t.Run(tc.name, func(t *testing.T) {
|
|
374
374
|
root := t.TempDir()
|
|
375
375
|
writeFeature(t, root, "alpha", map[string]string{
|
|
376
|
-
"state.md": "- Phase: build\n- Status: " + tc.status + "\n",
|
|
376
|
+
"state.md": "- Phase: build\n- Status: " + tc.status + "\n- Schema: 3\n",
|
|
377
377
|
"spec.md": "spec\n",
|
|
378
378
|
"plan.md": "plan\n",
|
|
379
379
|
"decisions.md": "decisions\n",
|
|
@@ -412,7 +412,7 @@ func TestCheckUsesWorkspaceOverrideForStateInvariants(t *testing.T) {
|
|
|
412
412
|
override := filepath.Join(physicalRoot, "work", "alpha")
|
|
413
413
|
t.Setenv("DEVRITES_WORKSPACE", override)
|
|
414
414
|
testutil.WriteFile(t, filepath.Join(override, "README.md"), "---\nphase: spec\nschemaVersion: 1\n---\n")
|
|
415
|
-
testutil.WriteFile(t, filepath.Join(override, "state.md"), "- Phase: build\n- Status: running\n")
|
|
415
|
+
testutil.WriteFile(t, filepath.Join(override, "state.md"), "- Phase: build\n- Status: running\n- Schema: 3\n")
|
|
416
416
|
testutil.WriteFile(t, filepath.Join(override, "spec.md"), "spec\n")
|
|
417
417
|
testutil.WriteFile(t, filepath.Join(override, "questions.md"), "## q-1\nstatus: open\ngate: blocking\n")
|
|
418
418
|
|
|
@@ -443,7 +443,7 @@ func TestCheckObservationUsesRetainedPhaseQuestionsReadinessAndReview(t *testing
|
|
|
443
443
|
if err != nil {
|
|
444
444
|
t.Fatal(err)
|
|
445
445
|
}
|
|
446
|
-
testutil.WriteFile(t, filepath.Join(workspace, "state.md"), "- Phase: spec\n- Status: running\n")
|
|
446
|
+
testutil.WriteFile(t, filepath.Join(workspace, "state.md"), "- Phase: spec\n- Status: running\n- Schema: 3\n")
|
|
447
447
|
testutil.WriteFile(t, filepath.Join(workspace, "questions.md"), "## q-1\nstatus: open\ngate: blocking\n")
|
|
448
448
|
testutil.WriteFile(t, filepath.Join(workspace, "plan.md"), "# Plan\n\nChanged after observation.\n")
|
|
449
449
|
testutil.WriteFile(t, filepath.Join(workspace, "eng-review.md"), "# Engineering review\n\nNo binding.\n")
|
|
@@ -609,7 +609,7 @@ func writeCompleteGateFeature(t *testing.T, root, slug string, current, required
|
|
|
609
609
|
content := "# " + name + "\n\nreal\n"
|
|
610
610
|
switch name {
|
|
611
611
|
case "state.md":
|
|
612
|
-
content = "- Phase: " + string(current) + "\n- Status: running\n"
|
|
612
|
+
content = "- Phase: " + string(current) + "\n- Status: running\n- Schema: 3\n"
|
|
613
613
|
case "questions.md":
|
|
614
614
|
questionsRequired = true
|
|
615
615
|
content = questions
|
|
@@ -77,7 +77,13 @@ func TestReadinessBindingBindsOnlyStableBuildInputs(t *testing.T) {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
for _, name := range []string{"state.md", "questions.md", "decisions.md", "assumptions.md", "eng-review.md", "evidence.md", "review.md", "seal.md", "handoff.md", "ambient.md"} {
|
|
80
|
-
|
|
80
|
+
// state.md stays a valid v5 workspace when mutated: the schema row is
|
|
81
|
+
// part of the contract, not part of the bound inputs.
|
|
82
|
+
body := "# Mutable\n\nChanged outside the stable contract.\n"
|
|
83
|
+
if name == "state.md" {
|
|
84
|
+
body = "| phase | build |\n| schema | 3 |\n" + body
|
|
85
|
+
}
|
|
86
|
+
testutil.WriteFile(t, filepath.Join(workspace, name), body)
|
|
81
87
|
if got := mustReadinessBinding(t, root, "stable"); got != baseline {
|
|
82
88
|
t.Fatalf("excluded %s altered binding", name)
|
|
83
89
|
}
|
|
@@ -365,7 +371,7 @@ func writeReadinessFixture(t *testing.T, root, slug, phase string) string {
|
|
|
365
371
|
t.Helper()
|
|
366
372
|
workspace := filepath.Join(root, "work", slug)
|
|
367
373
|
for name, body := range map[string]string{
|
|
368
|
-
"state.md": "| phase | " + phase + " |\n",
|
|
374
|
+
"state.md": "| phase | " + phase + " |\n| schema | 3 |\n",
|
|
369
375
|
"brief.md": "# Brief\n\nReady.\n",
|
|
370
376
|
"spec.md": "# Spec\n\nReady.\n",
|
|
371
377
|
"decisions.md": "# Decisions\n\nNone.\n",
|