@ontrails/trails 1.0.0-beta.32 → 1.0.0-beta.39
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 +64 -0
- package/README.md +2 -2
- package/package.json +17 -16
- package/src/app.ts +27 -6
- package/src/cli.ts +2 -2
- package/src/mcp-app.ts +2 -0
- package/src/mcp-options.ts +20 -6
- package/src/regrade/history.ts +320 -0
- package/src/regrade/live-api-preserve.ts +8 -0
- package/src/regrade/plan-artifact.ts +265 -0
- package/src/release/index.ts +17 -0
- package/src/release/lock-roundtrip-smoke.ts +230 -0
- package/src/release/notes-cli.ts +171 -0
- package/src/release/notes.ts +390 -0
- package/src/release/smoke.ts +11 -1
- package/src/release/wayfinder-dogfood-smoke.ts +1 -1
- package/src/run-schema.ts +34 -1
- package/src/trails/compile.ts +4 -5
- package/src/trails/guide.ts +1 -1
- package/src/trails/load-app.ts +30 -48
- package/src/trails/regrade.ts +2877 -107
- package/src/trails/release-smoke.ts +2 -1
- package/src/trails/survey.ts +25 -38
- package/src/trails/topo-read-support.ts +15 -11
- package/src/trails/topo-store-support.ts +21 -12
- package/src/trails/topo-support.ts +1 -0
- package/src/trails/validate.ts +1 -1
- package/src/trails/wayfind.ts +64 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,69 @@
|
|
|
1
1
|
# trails
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.39
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`9518f5c`](https://github.com/outfitter-dev/trails/commit/9518f5cc892a87273a3bfdaa0d7319f98004ecb8): `trails regrade adjust <transition>` pulls a graduated transition back from consolidated history into an active plan minus the run ledger, preserving the stable transition id so subsequent applies append to the same history spine. Plan re-derivation (`regrade plan`) now carries `transitionId` forward from an existing active plan.
|
|
8
|
+
- [`714939d`](https://github.com/outfitter-dev/trails/commit/714939d6ec143c55a1aba55b65bd11a8a2bfbf1e): Stacked/phased regrades ride the primary tooling: class-mode Regrade plans accept `include` path globs so one transition can be applied in scoped parts that all append to the same consolidated history, and an authored `--name` keys multi-class transitions to a clean transition-name filename instead of concatenated class ids.
|
|
9
|
+
- [`65b362f`](https://github.com/outfitter-dev/trails/commit/65b362f65fa2a75e2121d1a8b31882d52fa0376b): Regrade history consolidates per transition, append-only: `regrade apply` appends a run entry stamped `{ planContentHash, lockHashAtRun }` to `.trails/regrade/history/<transition>.json` instead of overwriting lockhash-named files, identical re-runs are recognized as replays, the artifact carries a stable internal transition `id`, and `regrade check <transition>` verifies each recorded run at its own stamped lock. The report `history.status` union widens to `applied | checked | replay`.
|
|
10
|
+
- [`7c05376`](https://github.com/outfitter-dev/trails/commit/7c05376a4ee2a66ef477d8f165205bf2fbbdc37f): `trails compile` and `trails validate` now collect adapter overlay overlays from the app module's `trailsOverlays` export and embed the validated facts as `overlays.<namespace>` in `trails.lock`, threading the registrations through every derivation site so compiled and freshly derived graphs stay hash-identical.
|
|
11
|
+
- [`adf6419`](https://github.com/outfitter-dev/trails/commit/adf64191ea9e458d7405465ab6fa96ca72ab574c): Add `trails wayfind --overlay <namespace>`: a generic read of namespaced lock overlays (adapter-contributed facts) from saved artifacts, listing available namespaces on a miss — new fact families become inspectable with zero wayfinder edits.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- [`f42ca6e`](https://github.com/outfitter-dev/trails/commit/f42ca6e40b29155acec446e5bf44e52e014466bd): Hard cutover: the CLI consumes `cli` bindings from the app-authored surfaces overlay. Scalar bindings behave identically to the removed cliAliases (parity-tested) — the binding name splits on `.` into a transparent synonym command path for exactly one trail. List bindings arrive as command groups: each expanded member trail gets a group-prefixed route that dispatches the member trail with its identity preserved, and a singleton list stays a group. Expansion is fail-fast boundary validation: a scalar binding resolving to zero or multiple trails, or a group with an empty member union, is a `ValidationError` naming the binding. `DeriveTopoGraphOptions.cliAliases`, the `cliAliases`/`trailsCliAliases` app-module export convention, and the per-kind compile lift are deleted; `deriveCliCommands`/`createProgram` take `overlays` instead of `aliases`, and both topo-graph derivation pipelines expand the same bindings through one shared helper so runtime CLI routes and lock routes come from one semantic. A leftover legacy export is now a Warden error (`no-legacy-cli-alias-export`) naming the `surfaceOverlay({ cli: { ... } })` rewrite.
|
|
16
|
+
|
|
17
|
+
This is a breaking API removal shipped under the lockstep beta patch convention (pre-1.0 hard-cutover posture, zero external adoption); the removed options have no deprecation window by design.
|
|
18
|
+
|
|
19
|
+
- [`bafde1f`](https://github.com/outfitter-dev/trails/commit/bafde1fc8172abb8d8617f69a3c7a70667626d10): Fresh derivations now collect app-module overlays through the shared channel compile uses. `@ontrails/adapter-kit` exports `resolveTrailsOverlays()`, the one reader of an app module's `trailsOverlays` export; the compile-path fresh app lease and Warden's fresh topo loading both go through it, making per-namespace drift asymmetry structurally impossible. Warden drift checks (`checkDrift` now accepts derive options carrying overlays) and the topo-aware rule context graph derive with the same overlays the committed lock embeds, so rules like `surface-overlay-coherence` fire on standard runs. Stale drift results name the drifted overlay namespaces (`DriftResult.driftedOverlayNamespaces`) and point at `trails compile` as the remediation.
|
|
20
|
+
- [`b077fb7`](https://github.com/outfitter-dev/trails/commit/b077fb7ba6d9724cac6f0e59bc3fec9aec28984c): Add the export-restructure Regrade class family (TRL-1210). `export-restructure:cli-aliases` inverts legacy `cliAliases`/`trailsCliAliases` exports into `surfaceOverlay({ cli })` bindings inside the module's `trailsOverlays` export — adding the `@ontrails/core` import, deleting the legacy export, and routing anything it cannot prove safe (computed keys, spreads, in-module `aliases:` references) to `needs-review` with the exact target shape named. `export-restructure:mcp-trailheads` projects call-site MCP trailhead maps into `surfaceOverlay({ mcp })` group bindings: it rewrites in place when the same module exports `trailsOverlays`, and otherwise emits a classified `needs-review` handoff naming the module-overlay target while the call-site map stays as the richer-metadata override-in-context. Warden's fix-class union grows to `'export-restructure' | 'term-rewrite'`, `no-legacy-cli-alias-export` now advertises the `export-restructure` class, and `loadWardenRegradeClasses` supersedes `loadWardenTermRewriteClasses` (still exported) as the full Warden-routed class loader. Class-mode Regrade also gains the full plan lifecycle: `trails regrade plan --type class --class-ids ...` writes a `.trails/regrade/<slug>.json` plan carrying class ids, scope, and intent, `regrade check` re-runs the dry run and gates on outstanding rewrites or review, and `regrade apply` applies and graduates the plan to `.trails/regrade/history/<slug>-<hash>.json` — the same plan → check → apply → history evidence trail vocabulary regrades already had, now available to structural transforms. The class family ships for downstream apps bridging the pre-1.0 gap, so pre-cutover alias exports and trailhead maps migrate mechanically instead of by hand.
|
|
21
|
+
- [`81373bc`](https://github.com/outfitter-dev/trails/commit/81373bc5e980bb06d56fb06af4f0986f72e318c7): Wave-2 MCP cutover to the app-authored `surfaces` overlay. The overlay's `mcp` bindings are now the authored, lockable default for the MCP surface: a list binding derives one grouped trailhead tool (member selection in `{ trail, input }`, member identity preserved in `{ trail, output }`, deterministic derived description), and a scalar binding derives an additional tool synonym whose MCP-safe name is published verbatim and must expand to exactly one trail. `deriveMcpTools`/`createServer` accept the new `overlays` option; `@ontrails/core` gains `expandMcpSurfaceBindings` and `deriveMcpTrailheadDescription`.
|
|
22
|
+
|
|
23
|
+
The call-site `CreateServerOptions.trailheads` map survives as permanent override-in-context design, not a compatibility bridge: when both channels are present, the call-site map wins at runtime. Warden's new `trailhead-override-divergence` rule (warn) names both sides when a call-site map's binding names or member selectors diverge from the authored overlay default.
|
|
24
|
+
|
|
25
|
+
Topographer now derives `graph.trailheads` from the overlay's `mcp` list bindings in both `deriveTopoGraph` and the store-side graph build, so trailhead facts flow from compiled locks into Wayfinder reads for the first time. The never-wired `DeriveTopoGraphOptions.trailheads` option and the `TopoGraphTrailheadDeclaration`/`TopoGraphTrailheadTrailSelector` types are removed — a beta-window hard cutover of an option no caller could reach; author the equivalent `mcp` list binding in `surfaceOverlay({ mcp })` instead.
|
|
26
|
+
|
|
27
|
+
- [`a0517ef`](https://github.com/outfitter-dev/trails/commit/a0517efa01014754b1dc307169b21ae71a6e3620): Fresh app loading now resolves extensionless relative imports the way Bun does at runtime, so runtime-valid apps stay operator-loadable (`trails compile`, `trails warden`, and every other fresh-load path). When a relative import cannot be resolved at all, the failure is an actionable `ValidationError` naming the importer file and specifier instead of an opaque redacted "Internal server error".
|
|
28
|
+
- [`a89d469`](https://github.com/outfitter-dev/trails/commit/a89d4696aa78f3dda9394c14665ab3ea8c0f313c): Make `trails compile` → `trails validate` round-trip deterministically (TRL-1191). The per-user topo store no longer reuses previously stored JSON Schema bytes by zod definition hash — that hash cannot see `.describe()` metadata or object field order, so a warm store could serve pre-edit schema values into a freshly compiled lock and make `validate` report it stale immediately. Every snapshot now regenerates schema JSON from the live Zod schema, the store's graph hash goes through the same shared `deriveStableHash` path as `deriveTopoGraphHash`, and the committed `trails.lock` omits the wallclock `generatedAt` field so recompiling unchanged sources yields a byte-identical lock. `TopoGraph.generatedAt` is now optional; locks written by earlier versions still parse.
|
|
29
|
+
- [`a89d469`](https://github.com/outfitter-dev/trails/commit/a89d4696aa78f3dda9394c14665ab3ea8c0f313c): Make the per-user topo store an honest cache (TRL-1196). Every snapshot now records a content fingerprint of the app source set (`topo_snapshots.source_fingerprint`, store schema v14), `trails compile` reports it in its output, and Wayfinder artifact loading compares it against a freshly derived fingerprint — a mismatch surfaces as a `topo-store-source-fingerprint-mismatch` stale reason instead of silently serving pre-edit facts. Compile derives everything from live source on every run, so a poisoned or stale store can never reach `trails.lock`, with or without `--force`.
|
|
30
|
+
- [`a89d469`](https://github.com/outfitter-dev/trails/commit/a89d4696aa78f3dda9394c14665ab3ea8c0f313c): Add the lock round-trip invariant gate (TRL-1200): `trails release smoke --check lock-roundtrip` discovers every committed `trails.lock`, recompiles each against a cold per-user store in a temporary state home, and asserts `validate` is green and the recompiled lock is byte-identical to the committed one. Failure output names the diverging section and the `trails compile` command that fixes it — hand-editing a lock is never the remediation. Wired into `bun run check` and the CI Governance job via `bun run lock:roundtrip`.
|
|
31
|
+
|
|
32
|
+
## 1.0.0-beta.38
|
|
33
|
+
|
|
34
|
+
## 1.0.0-beta.37
|
|
35
|
+
|
|
36
|
+
### Patch Changes
|
|
37
|
+
|
|
38
|
+
- [`09f15de`](https://github.com/outfitter-dev/trails/commit/09f15def8fcc8c28b0d604f436e6eeed46da8f37): Stage wide-net Regrade expansion candidates as structured plan review inventory with evidence, status, and pending counts.
|
|
39
|
+
|
|
40
|
+
## 1.0.0-beta.36
|
|
41
|
+
|
|
42
|
+
### Patch Changes
|
|
43
|
+
|
|
44
|
+
- [`6e63e48`](https://github.com/outfitter-dev/trails/commit/6e63e483617b84cb6868d0c4d58d5b5a8d3b9ed2): Complete the v1 grouped surface-entry vocabulary cutover from facet to trailhead, including Regrade dogfood support for governed string literal renames and composed AST rewrite application.
|
|
45
|
+
- [`26786a1`](https://github.com/outfitter-dev/trails/commit/26786a14acbe9ed03f69adbdac22968891e33df1): Persist vocabulary Regrade plan artifacts, expose plan/check/preview/apply flows across CLI and MCP, and write applied plan history for reviewed vocabulary migrations.
|
|
46
|
+
|
|
47
|
+
## 1.0.0-beta.35
|
|
48
|
+
|
|
49
|
+
## 1.0.0-beta.34
|
|
50
|
+
|
|
51
|
+
### Patch Changes
|
|
52
|
+
|
|
53
|
+
- [`d67558b`](https://github.com/outfitter-dev/trails/commit/d67558bea3bfa363ed57e0f4091b6eccbf2a7710): Run governed AST symbol renames from the registry-backed `trails regrade` command path, including MCP parity, while preserving derived live API forms.
|
|
54
|
+
|
|
55
|
+
## 1.0.0-beta.33
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- [`04a6057`](https://github.com/outfitter-dev/trails/commit/04a6057502281fb81d1eb77df140d07181bfdd07): Clean generated release output by suppressing internal dependency cascade noise in package changelogs and rendering generated release PR and GitHub Release notes from the same Highlights, Changes, and Package Versions summary.
|
|
60
|
+
- [`0c40138`](https://github.com/outfitter-dev/trails/commit/0c40138efb962e779710daa172bdfd756d9d992f): Harden vocabulary regrades by deferring Markdown code contexts for review and
|
|
61
|
+
exposing structured preserve rules through the Trails `regrade` command.
|
|
62
|
+
- [`7d65189`](https://github.com/outfitter-dev/trails/commit/7d65189cc33408755fe07a0b5679f1ed01123455): Expose derived live-API preserve inventory in vocabulary Regrade runs and have the Trails operator regrade surface derive current facet API preserves from live topo and MCP surface facts.
|
|
63
|
+
- [`04bb8a4`](https://github.com/outfitter-dev/trails/commit/04bb8a42af4ada51a74b1d8c83697db92035b5e9): Expose vocabulary Regrade occurrence dispositions and disposition summary counts alongside mechanical verdicts.
|
|
64
|
+
|
|
65
|
+
Accept explicit preserve-rule dispositions through the `trails regrade` CLI/MCP contract.
|
|
66
|
+
|
|
3
67
|
## 1.0.0-beta.32
|
|
4
68
|
|
|
5
69
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -16,9 +16,9 @@ Common workflows:
|
|
|
16
16
|
- `trails compile` writes root `trails.lock`.
|
|
17
17
|
- `trails validate` checks root `trails.lock` for drift.
|
|
18
18
|
- `trails wayfind`, `trails wayfind --trails --intent read`, `trails wayfind <id> --contract`, `trails wayfind <id> --deps`, `trails wayfind <id> --impact`, `trails wayfind pattern "wayfind.*"`, `trails wayfind query "release drift"`, `trails wayfind file <file> --outline`, and `trails wayfind diff ...` read graph artifacts and source outlines through Wayfinder for local navigation.
|
|
19
|
-
- `trails schema <command...>` shows accepted CLI routes, aliases, flags, and schemas for an operator command.
|
|
19
|
+
- `trails schema <command...>` shows accepted CLI routes, aliases, flags, and schemas for an operator command or command namespace.
|
|
20
20
|
- `trails warden` runs Trails governance checks for contract and architecture drift. Use `--scope-exclude <glob>` or project `warden.scope.exclude` config when local notes, scratch space, or generated state should not be governed by Warden.
|
|
21
|
-
- `trails regrade --root-dir <path> --json`
|
|
21
|
+
- `trails regrade plan <from> <to> --root-dir <path> --json` writes an active Regrade plan, `trails regrade plan --expand` stages wide-net review candidates in that plan, `trails regrade preview` reruns it without writing, and `trails regrade apply` consumes the plan and writes history. Use `plan --expand --dry-run` to inspect staged candidates without writing the active plan, and add `--dry-run` to `apply` to prove the apply path without mutating source. Use `trails regrade plans` and `trails regrade check` when a workspace has active plans that need inspection before apply. MCP exposes the same lifecycle as `trails_plan_regrade`, `trails_list_regrades`, `trails_check_regrade`, `trails_preview_regrade`, and `trails_apply_regrade`.
|
|
22
22
|
- `trails guide` remains available for compatibility; prefer `trails wayfind --source live --module <app-module>` or saved-artifact Wayfinder reads for agent navigation.
|
|
23
23
|
|
|
24
24
|
Trails is contract-first: define trails once with typed input, Result output, examples, and meta; the framework derives CLI, MCP, HTTP, and future surfaces from the same contracts.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/trails",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.39",
|
|
4
4
|
"bin": {
|
|
5
5
|
"trails": "./bin/trails.ts"
|
|
6
6
|
},
|
|
@@ -27,25 +27,26 @@
|
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@clack/prompts": "^1.1.0",
|
|
30
|
-
"@ontrails/adapter-kit": "^1.0.0-beta.
|
|
31
|
-
"@ontrails/cli": "^1.0.0-beta.
|
|
32
|
-
"@ontrails/commander": "^1.0.0-beta.
|
|
33
|
-
"@ontrails/config": "^1.0.0-beta.
|
|
34
|
-
"@ontrails/core": "^1.0.0-beta.
|
|
35
|
-
"@ontrails/http": "^1.0.0-beta.
|
|
36
|
-
"@ontrails/mcp": "^1.0.0-beta.
|
|
37
|
-
"@ontrails/observe": "^1.0.0-beta.
|
|
38
|
-
"@ontrails/permits": "^1.0.0-beta.
|
|
39
|
-
"@ontrails/regrade": "^1.0.0-beta.
|
|
40
|
-
"@ontrails/topographer": "^1.0.0-beta.
|
|
41
|
-
"@ontrails/tracing": "^1.0.0-beta.
|
|
42
|
-
"@ontrails/warden": "^1.0.0-beta.
|
|
43
|
-
"@ontrails/wayfinder": "^1.0.0-beta.
|
|
30
|
+
"@ontrails/adapter-kit": "^1.0.0-beta.39",
|
|
31
|
+
"@ontrails/cli": "^1.0.0-beta.39",
|
|
32
|
+
"@ontrails/commander": "^1.0.0-beta.39",
|
|
33
|
+
"@ontrails/config": "^1.0.0-beta.39",
|
|
34
|
+
"@ontrails/core": "^1.0.0-beta.39",
|
|
35
|
+
"@ontrails/http": "^1.0.0-beta.39",
|
|
36
|
+
"@ontrails/mcp": "^1.0.0-beta.39",
|
|
37
|
+
"@ontrails/observe": "^1.0.0-beta.39",
|
|
38
|
+
"@ontrails/permits": "^1.0.0-beta.39",
|
|
39
|
+
"@ontrails/regrade": "^1.0.0-beta.39",
|
|
40
|
+
"@ontrails/topographer": "^1.0.0-beta.39",
|
|
41
|
+
"@ontrails/tracing": "^1.0.0-beta.39",
|
|
42
|
+
"@ontrails/warden": "^1.0.0-beta.39",
|
|
43
|
+
"@ontrails/wayfinder": "^1.0.0-beta.39",
|
|
44
44
|
"commander": "^14.0.3",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"zod": "^4.3.5"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
|
-
"@ontrails/
|
|
49
|
+
"@ontrails/cloudflare": "^1.0.0-beta.39",
|
|
50
|
+
"@ontrails/testing": "^1.0.0-beta.39"
|
|
50
51
|
}
|
|
51
52
|
}
|
package/src/app.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { topo } from '@ontrails/core';
|
|
1
|
+
import { surfaceOverlay, topo } from '@ontrails/core';
|
|
2
2
|
import {
|
|
3
3
|
wayfindAdaptersTrail,
|
|
4
4
|
wayfindContoursTrail,
|
|
@@ -7,7 +7,8 @@ import {
|
|
|
7
7
|
wayfindDiffTrail,
|
|
8
8
|
wayfindErrorsTrail,
|
|
9
9
|
wayfindExamplesTrail,
|
|
10
|
-
|
|
10
|
+
wayfindOverlayTrail,
|
|
11
|
+
wayfindTrailheadsTrail,
|
|
11
12
|
wayfindImpactTrail,
|
|
12
13
|
wayfindNearbyTrail,
|
|
13
14
|
wayfindOverviewTrail,
|
|
@@ -111,15 +112,16 @@ const cliWayfinderSupportTrails = {
|
|
|
111
112
|
wayfindDescribeTrail,
|
|
112
113
|
wayfindErrorsTrail,
|
|
113
114
|
wayfindExamplesTrail,
|
|
114
|
-
wayfindFacetsTrail,
|
|
115
115
|
wayfindImpactTrail,
|
|
116
116
|
wayfindNearbyTrail,
|
|
117
117
|
wayfindOutlineTrail,
|
|
118
|
+
wayfindOverlayTrail,
|
|
118
119
|
wayfindOverviewTrail,
|
|
119
120
|
wayfindResourcesTrail,
|
|
120
121
|
wayfindSearchTrail,
|
|
121
122
|
wayfindSignalsTrail,
|
|
122
123
|
wayfindSurfacesTrail,
|
|
124
|
+
wayfindTrailheadsTrail,
|
|
123
125
|
wayfindTrailsTrail,
|
|
124
126
|
wayfindVersionsTrail,
|
|
125
127
|
};
|
|
@@ -138,8 +140,27 @@ export const trailsCliIncludedTrails = [
|
|
|
138
140
|
]),
|
|
139
141
|
];
|
|
140
142
|
|
|
141
|
-
export const
|
|
142
|
-
|
|
143
|
-
|
|
143
|
+
export const trailsOverlays = [
|
|
144
|
+
surfaceOverlay({
|
|
145
|
+
cli: { diff: 'survey.diff' },
|
|
146
|
+
// Authored, lockable default for the operator MCP `inspect` trailhead.
|
|
147
|
+
// The call-site map in `mcp-options.ts` overrides it at runtime with
|
|
148
|
+
// richer metadata (description, deferred loading) over the same members.
|
|
149
|
+
mcp: {
|
|
150
|
+
inspect: [
|
|
151
|
+
'survey',
|
|
152
|
+
'topo',
|
|
153
|
+
'guide',
|
|
154
|
+
'survey.brief',
|
|
155
|
+
'survey.diff',
|
|
156
|
+
'survey.resource',
|
|
157
|
+
'survey.signal',
|
|
158
|
+
'survey.surfaces',
|
|
159
|
+
'survey.trail',
|
|
160
|
+
'topo.history',
|
|
161
|
+
],
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
];
|
|
144
165
|
|
|
145
166
|
export const app = topo('trails', operatorTrails, cliWayfinderTrails);
|
package/src/cli.ts
CHANGED
|
@@ -21,7 +21,7 @@ import type { CreateProgramOptions } from '@ontrails/commander';
|
|
|
21
21
|
import { resolvePermitFromBearerToken } from '@ontrails/permits';
|
|
22
22
|
import { deriveTopoGraph } from '@ontrails/topographer';
|
|
23
23
|
|
|
24
|
-
import { app,
|
|
24
|
+
import { app, trailsCliIncludedTrails, trailsOverlays } from './app.js';
|
|
25
25
|
import { resolveInputWithClack } from './clack.js';
|
|
26
26
|
import { getRetiredTopoCommandDiagnostic } from './retired-topo-command.js';
|
|
27
27
|
import { attachCompletionsInstallCommand } from './run-completions-install.js';
|
|
@@ -302,11 +302,11 @@ const runSurfaceOnce = async (): Promise<void> => {
|
|
|
302
302
|
const session = maybeInstallTraceSession();
|
|
303
303
|
try {
|
|
304
304
|
const surfaceOptions = {
|
|
305
|
-
aliases: trailsCliAliases,
|
|
306
305
|
description: 'Agent-native, contract-first TypeScript framework',
|
|
307
306
|
include: trailsCliIncludedTrails,
|
|
308
307
|
name: 'trails',
|
|
309
308
|
onResult: buildOnResult(session),
|
|
309
|
+
overlays: trailsOverlays,
|
|
310
310
|
presets: [
|
|
311
311
|
outputModePreset(),
|
|
312
312
|
tracePreset(),
|
package/src/mcp-app.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
wayfindDiffTrail,
|
|
7
7
|
wayfindErrorsTrail,
|
|
8
8
|
wayfindExamplesTrail,
|
|
9
|
+
wayfindOverlayTrail,
|
|
9
10
|
wayfindImpactTrail,
|
|
10
11
|
wayfindNearbyTrail,
|
|
11
12
|
wayfindOverviewTrail,
|
|
@@ -32,6 +33,7 @@ export const trailsMcpApp = topo('trails', operatorTrails, {
|
|
|
32
33
|
wayfindImpactTrail,
|
|
33
34
|
wayfindNearbyTrail,
|
|
34
35
|
wayfindOutlineTrail,
|
|
36
|
+
wayfindOverlayTrail,
|
|
35
37
|
wayfindOverviewTrail,
|
|
36
38
|
wayfindResourcesTrail,
|
|
37
39
|
wayfindSearchTrail,
|
package/src/mcp-options.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CreateServerOptions,
|
|
3
|
+
McpSurfaceTrailheadMap,
|
|
4
|
+
} from '@ontrails/mcp';
|
|
5
|
+
|
|
6
|
+
import { trailsOverlays } from './app.js';
|
|
2
7
|
|
|
3
8
|
export const trailsMcpIncludedTrails = [
|
|
4
9
|
'adapter.check',
|
|
@@ -14,7 +19,12 @@ export const trailsMcpIncludedTrails = [
|
|
|
14
19
|
'doctor',
|
|
15
20
|
'draft.promote',
|
|
16
21
|
'guide',
|
|
17
|
-
'regrade',
|
|
22
|
+
'adjust.regrade',
|
|
23
|
+
'apply.regrade',
|
|
24
|
+
'check.regrade',
|
|
25
|
+
'list.regrades',
|
|
26
|
+
'plan.regrade',
|
|
27
|
+
'preview.regrade',
|
|
18
28
|
'release.check',
|
|
19
29
|
'release.smoke',
|
|
20
30
|
'revise',
|
|
@@ -47,7 +57,7 @@ export const trailsMcpIncludedTrails = [
|
|
|
47
57
|
'wayfind.trails',
|
|
48
58
|
] as const;
|
|
49
59
|
|
|
50
|
-
export const
|
|
60
|
+
export const trailsMcpTrailheads = {
|
|
51
61
|
inspect: {
|
|
52
62
|
description:
|
|
53
63
|
'Inspect saved topo structure, resources, signals, surfaces, and diffs.',
|
|
@@ -65,13 +75,17 @@ export const trailsMcpFacets = {
|
|
|
65
75
|
'topo.history',
|
|
66
76
|
],
|
|
67
77
|
},
|
|
68
|
-
} satisfies
|
|
78
|
+
} satisfies McpSurfaceTrailheadMap;
|
|
69
79
|
|
|
70
80
|
export const trailsMcpSurfaceOptions = {
|
|
71
81
|
description:
|
|
72
|
-
'Trails framework operator surface. Use MCP resources for cold context, direct tools for high-signal work, and the inspect
|
|
73
|
-
facets: trailsMcpFacets,
|
|
82
|
+
'Trails framework operator surface. Use MCP resources for cold context, direct tools for high-signal work, and the inspect trailhead for saved topo reads.',
|
|
74
83
|
include: trailsMcpIncludedTrails,
|
|
75
84
|
mcpResources: { examples: true, graph: true, surfaceMap: true },
|
|
76
85
|
name: 'trails',
|
|
86
|
+
// The overlay authors the lockable `inspect` default; the call-site map
|
|
87
|
+
// below is the runtime override-in-context with richer metadata
|
|
88
|
+
// (description, deferred loading) over the same member selectors.
|
|
89
|
+
overlays: trailsOverlays,
|
|
90
|
+
trailheads: trailsMcpTrailheads,
|
|
77
91
|
} satisfies CreateServerOptions;
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Consolidated, append-only Regrade transition history. One file per
|
|
3
|
+
* transition at `.trails/regrade/history/<transition>.json`; each apply
|
|
4
|
+
* appends a run entry stamped with the plan content hash and the lock hash
|
|
5
|
+
* observed at that run.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { InternalError, Result, ValidationError } from '@ontrails/core';
|
|
9
|
+
import type { Result as TrailsResult } from '@ontrails/core';
|
|
10
|
+
import { regradeReportOutput } from '@ontrails/regrade';
|
|
11
|
+
import type { RegradeReport } from '@ontrails/regrade';
|
|
12
|
+
import { createHash } from 'node:crypto';
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
15
|
+
import { z } from 'zod';
|
|
16
|
+
|
|
17
|
+
import {
|
|
18
|
+
regradePlanArtifactSchema,
|
|
19
|
+
regradePlanContentHash,
|
|
20
|
+
regradePlanDirectory,
|
|
21
|
+
regradePlanSlugForBody,
|
|
22
|
+
regradeSourceHash,
|
|
23
|
+
rootRelativePath,
|
|
24
|
+
} from './plan-artifact.js';
|
|
25
|
+
import type { RegradePlanArtifact, RegradePlanBody } from './plan-artifact.js';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Consolidated history schema version. Version 1 was the retired
|
|
29
|
+
* one-file-per-run shape whose filename carried the lock hash.
|
|
30
|
+
*/
|
|
31
|
+
export const REGRADE_HISTORY_SCHEMA_VERSION = 2;
|
|
32
|
+
|
|
33
|
+
const regradeHistoryRunSchema = z
|
|
34
|
+
.object({
|
|
35
|
+
lockHashAtRun: z
|
|
36
|
+
.string()
|
|
37
|
+
.min(1)
|
|
38
|
+
.describe('Regrade source hash observed when this run applied'),
|
|
39
|
+
plan: regradePlanArtifactSchema.describe(
|
|
40
|
+
'Plan artifact consumed by this run'
|
|
41
|
+
),
|
|
42
|
+
planContentHash: z
|
|
43
|
+
.string()
|
|
44
|
+
.min(1)
|
|
45
|
+
.describe('Canonical content hash of the resolved plan body'),
|
|
46
|
+
report: regradeReportOutput.describe('Applied report recorded by the run'),
|
|
47
|
+
})
|
|
48
|
+
.strict();
|
|
49
|
+
|
|
50
|
+
const regradeHistoryArtifactSchema = z
|
|
51
|
+
.object({
|
|
52
|
+
id: z.string().min(1).describe('Stable transition identity'),
|
|
53
|
+
kind: z.literal('regrade-history'),
|
|
54
|
+
path: z.string().describe('Root-relative consolidated history path'),
|
|
55
|
+
runs: z.array(regradeHistoryRunSchema).min(1),
|
|
56
|
+
schemaVersion: z.literal(REGRADE_HISTORY_SCHEMA_VERSION),
|
|
57
|
+
})
|
|
58
|
+
.strict();
|
|
59
|
+
|
|
60
|
+
interface RegradeHistoryRun {
|
|
61
|
+
readonly lockHashAtRun: string;
|
|
62
|
+
readonly plan: RegradePlanArtifact;
|
|
63
|
+
readonly planContentHash: string;
|
|
64
|
+
readonly report: RegradeReport;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface RegradeHistoryArtifact {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly kind: 'regrade-history';
|
|
70
|
+
readonly path: string;
|
|
71
|
+
readonly runs: readonly RegradeHistoryRun[];
|
|
72
|
+
readonly schemaVersion: typeof REGRADE_HISTORY_SCHEMA_VERSION;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface RegradeHistorySummary {
|
|
76
|
+
readonly path: string;
|
|
77
|
+
readonly schemaVersion: number;
|
|
78
|
+
readonly status: 'applied' | 'replay';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export const regradeHistoryPathForPlan = (
|
|
82
|
+
rootDir: string,
|
|
83
|
+
plan: RegradePlanBody
|
|
84
|
+
): string =>
|
|
85
|
+
join(
|
|
86
|
+
regradePlanDirectory(rootDir),
|
|
87
|
+
'history',
|
|
88
|
+
`${regradePlanSlugForBody(plan)}.json`
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
export const readRegradeHistoryArtifact = (
|
|
92
|
+
path: string
|
|
93
|
+
): TrailsResult<RegradeHistoryArtifact, InternalError | ValidationError> => {
|
|
94
|
+
if (!existsSync(path)) {
|
|
95
|
+
return Result.err(
|
|
96
|
+
new ValidationError(`Regrade history "${path}" not found.`)
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
let parsedJson: unknown;
|
|
100
|
+
try {
|
|
101
|
+
parsedJson = JSON.parse(readFileSync(path, 'utf8'));
|
|
102
|
+
} catch (error) {
|
|
103
|
+
return Result.err(
|
|
104
|
+
new InternalError('Failed to read Regrade history artifact.', {
|
|
105
|
+
...(error instanceof Error ? { cause: error } : {}),
|
|
106
|
+
context: { path },
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const parsed = regradeHistoryArtifactSchema.safeParse(parsedJson);
|
|
111
|
+
if (!parsed.success) {
|
|
112
|
+
return Result.err(
|
|
113
|
+
new ValidationError('Invalid Regrade history artifact.', {
|
|
114
|
+
context: { issues: parsed.error.issues, path },
|
|
115
|
+
})
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return Result.ok(parsed.data as unknown as RegradeHistoryArtifact);
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Deterministic transition identity minted at the first recorded run and
|
|
123
|
+
* preserved for the life of the consolidated history file.
|
|
124
|
+
*/
|
|
125
|
+
export const mintTransitionId = (
|
|
126
|
+
slug: string,
|
|
127
|
+
planContentHash: string,
|
|
128
|
+
lockHashAtRun: string
|
|
129
|
+
): string =>
|
|
130
|
+
createHash('sha256')
|
|
131
|
+
.update(`${slug}\n${planContentHash}\n${lockHashAtRun}`)
|
|
132
|
+
.digest('hex')
|
|
133
|
+
.slice(0, 12);
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Append one applied run to the transition's consolidated history file. A
|
|
137
|
+
* run whose plan content hash and lock hash both equal the last recorded
|
|
138
|
+
* run's stamps is a replay: nothing is written and `status: 'replay'` is
|
|
139
|
+
* surfaced instead of a duplicate record.
|
|
140
|
+
*/
|
|
141
|
+
export const appendRegradeHistoryRun = (params: {
|
|
142
|
+
readonly artifact: RegradePlanArtifact;
|
|
143
|
+
readonly report: RegradeReport;
|
|
144
|
+
readonly rootDir: string;
|
|
145
|
+
}): TrailsResult<RegradeHistorySummary, Error> => {
|
|
146
|
+
const absolutePath = regradeHistoryPathForPlan(
|
|
147
|
+
params.rootDir,
|
|
148
|
+
params.artifact.plan
|
|
149
|
+
);
|
|
150
|
+
const relativePath = rootRelativePath(params.rootDir, absolutePath);
|
|
151
|
+
const planContentHash = regradePlanContentHash(params.artifact.plan);
|
|
152
|
+
const lockHashAtRun = regradeSourceHash(params.report);
|
|
153
|
+
const entry: RegradeHistoryRun = {
|
|
154
|
+
lockHashAtRun,
|
|
155
|
+
plan: params.artifact,
|
|
156
|
+
planContentHash,
|
|
157
|
+
report: params.report,
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
let prior: RegradeHistoryArtifact | undefined;
|
|
161
|
+
if (existsSync(absolutePath)) {
|
|
162
|
+
const existing = readRegradeHistoryArtifact(absolutePath);
|
|
163
|
+
if (existing.isErr()) {
|
|
164
|
+
return existing;
|
|
165
|
+
}
|
|
166
|
+
prior = existing.value;
|
|
167
|
+
if (
|
|
168
|
+
params.artifact.transitionId !== undefined &&
|
|
169
|
+
params.artifact.transitionId !== prior.id
|
|
170
|
+
) {
|
|
171
|
+
return Result.err(
|
|
172
|
+
new ValidationError(
|
|
173
|
+
'Regrade plan transition id mismatch — refusing to fork the consolidated history.',
|
|
174
|
+
{
|
|
175
|
+
context: {
|
|
176
|
+
history: prior.id,
|
|
177
|
+
path: relativePath,
|
|
178
|
+
plan: params.artifact.transitionId,
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
const lastRun = prior.runs.at(-1);
|
|
185
|
+
// A plan that carries the transition id (adjust round-trips, plan
|
|
186
|
+
// re-derivation) may evolve the plan identity on the same spine. A plan
|
|
187
|
+
// WITHOUT the id that disagrees with the recorded plan identity is a
|
|
188
|
+
// name collision, not a continuation — refuse instead of mixing runs
|
|
189
|
+
// from unrelated transitions into one history.
|
|
190
|
+
if (
|
|
191
|
+
params.artifact.transitionId === undefined &&
|
|
192
|
+
lastRun !== undefined &&
|
|
193
|
+
lastRun.plan.plan.id !== params.artifact.plan.id
|
|
194
|
+
) {
|
|
195
|
+
return Result.err(
|
|
196
|
+
new ValidationError(
|
|
197
|
+
'Regrade history already records a different plan identity under this transition name. Use `regrade adjust <transition>` to continue it, or pick a different plan name.',
|
|
198
|
+
{
|
|
199
|
+
context: {
|
|
200
|
+
history: lastRun.plan.plan.id,
|
|
201
|
+
path: relativePath,
|
|
202
|
+
plan: params.artifact.plan.id,
|
|
203
|
+
},
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (
|
|
209
|
+
lastRun !== undefined &&
|
|
210
|
+
lastRun.planContentHash === planContentHash &&
|
|
211
|
+
lastRun.lockHashAtRun === lockHashAtRun
|
|
212
|
+
) {
|
|
213
|
+
return Result.ok({
|
|
214
|
+
path: relativePath,
|
|
215
|
+
schemaVersion: REGRADE_HISTORY_SCHEMA_VERSION,
|
|
216
|
+
status: 'replay',
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const artifact: RegradeHistoryArtifact = {
|
|
222
|
+
id:
|
|
223
|
+
prior === undefined
|
|
224
|
+
? (params.artifact.transitionId ??
|
|
225
|
+
mintTransitionId(
|
|
226
|
+
regradePlanSlugForBody(params.artifact.plan),
|
|
227
|
+
planContentHash,
|
|
228
|
+
lockHashAtRun
|
|
229
|
+
))
|
|
230
|
+
: prior.id,
|
|
231
|
+
kind: 'regrade-history',
|
|
232
|
+
path: relativePath,
|
|
233
|
+
runs: prior === undefined ? [entry] : [...prior.runs, entry],
|
|
234
|
+
schemaVersion: REGRADE_HISTORY_SCHEMA_VERSION,
|
|
235
|
+
};
|
|
236
|
+
const parsed = regradeHistoryArtifactSchema.safeParse(artifact);
|
|
237
|
+
if (!parsed.success) {
|
|
238
|
+
return Result.err(
|
|
239
|
+
new ValidationError('Invalid Regrade history artifact.', {
|
|
240
|
+
context: { issues: parsed.error.issues, path: relativePath },
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
mkdirSync(dirname(absolutePath), { recursive: true });
|
|
246
|
+
writeFileSync(absolutePath, `${JSON.stringify(parsed.data, null, 2)}\n`);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
return Result.err(
|
|
249
|
+
new InternalError('Failed to write Regrade history entry.', {
|
|
250
|
+
...(error instanceof Error ? { cause: error } : {}),
|
|
251
|
+
context: { path: relativePath },
|
|
252
|
+
})
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
return Result.ok({
|
|
256
|
+
path: relativePath,
|
|
257
|
+
schemaVersion: REGRADE_HISTORY_SCHEMA_VERSION,
|
|
258
|
+
status: 'applied',
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Verify every recorded run at its own stamped lock: recompute the plan
|
|
264
|
+
* content hash and lock hash from the recorded plan and report, then compare
|
|
265
|
+
* with the stamped values.
|
|
266
|
+
*/
|
|
267
|
+
export const verifyRegradeHistoryRuns = (
|
|
268
|
+
artifact: RegradeHistoryArtifact
|
|
269
|
+
): TrailsResult<{ readonly runs: number }, ValidationError> => {
|
|
270
|
+
for (const [index, run] of artifact.runs.entries()) {
|
|
271
|
+
if (regradePlanContentHash(run.plan.plan) !== run.planContentHash) {
|
|
272
|
+
return Result.err(
|
|
273
|
+
new ValidationError('Regrade history run stamp mismatch.', {
|
|
274
|
+
context: {
|
|
275
|
+
field: 'planContentHash',
|
|
276
|
+
path: artifact.path,
|
|
277
|
+
run: index,
|
|
278
|
+
},
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
if (regradeSourceHash(run.report) !== run.lockHashAtRun) {
|
|
283
|
+
return Result.err(
|
|
284
|
+
new ValidationError('Regrade history run stamp mismatch.', {
|
|
285
|
+
context: { field: 'lockHashAtRun', path: artifact.path, run: index },
|
|
286
|
+
})
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return Result.ok({ runs: artifact.runs.length });
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
const hasPathSeparator = (value: string): boolean =>
|
|
294
|
+
value.includes('/') || value.includes('\\');
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Resolve a transition name (with or without a `.json` suffix) to its
|
|
298
|
+
* consolidated history file. Path references are rejected — graduated
|
|
299
|
+
* history lookups are by transition name only.
|
|
300
|
+
*/
|
|
301
|
+
export const resolveRegradeHistoryPath = (
|
|
302
|
+
rootDir: string,
|
|
303
|
+
ref: string
|
|
304
|
+
): TrailsResult<string, ValidationError> => {
|
|
305
|
+
if (hasPathSeparator(ref) || isAbsolute(ref)) {
|
|
306
|
+
return Result.err(
|
|
307
|
+
new ValidationError(
|
|
308
|
+
`Regrade history reference "${ref}" must be a transition name.`
|
|
309
|
+
)
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
const name = ref.endsWith('.json') ? ref.slice(0, -'.json'.length) : ref;
|
|
313
|
+
const path = join(regradePlanDirectory(rootDir), 'history', `${name}.json`);
|
|
314
|
+
if (!existsSync(path)) {
|
|
315
|
+
return Result.err(
|
|
316
|
+
new ValidationError(`No Regrade history for transition "${ref}" found.`)
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return Result.ok(path);
|
|
320
|
+
};
|