@ontrails/trails 1.0.0-beta.45 → 1.0.0-beta.46

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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.46
4
+
5
+ ### Minor Changes
6
+
7
+ - [`701ab85`](https://github.com/outfitter-dev/trails/commit/701ab85bd237e5fcf03725c56b70adb3612d6c15): Reuse fresh Regrade apply preflight evaluations without persisting cache state or weakening completion scans.
8
+ - [`c83d0b6`](https://github.com/outfitter-dev/trails/commit/c83d0b67ac928af1b44c1f2d2c4b36aa09a24a70): Inventory parser-native source comments and TSDoc as exact review-only entries
9
+ for governed classified vocabulary transitions across CLI, MCP, and audit.
10
+ - [`93757ba`](https://github.com/outfitter-dev/trails/commit/93757ba102ae7ba8d9a7a6c17f119cda47d342a7): Expose ordered Regrade lifecycle phases and wall-clock timings across CLI and
11
+ MCP completion results, with concise human CLI progress isolated on stderr.
12
+ Keep topo-scoped JSON and JSONL environment selectors effective when the
13
+ operator does not explicitly choose an output mode.
14
+ - [`fb0ba0a`](https://github.com/outfitter-dev/trails/commit/fb0ba0ab706bbdce470123e9a6fb2ef9f1822806): Convert the eight committed governed Regrade histories to canonical v3 receipts and remove the temporary schema-v2 compatibility path after migration.
15
+ - [`9a8b6e4`](https://github.com/outfitter-dev/trails/commit/9a8b6e4af394c76c11e6d0007e0f5f94d0be2cb3): Persist Regrade lifecycle runs as canonical v3 receipts with exact Git blob evidence and authored field provenance, and validate their compact classified-form projection independently in Warden.
16
+
17
+ ### Patch Changes
18
+
19
+ - [`768cc79`](https://github.com/outfitter-dev/trails/commit/768cc79ca10947b8808b376e281e1a81131b4acc): Close missed projection vocabulary residue in Regrade internals and public
20
+ error-rendering guidance, and keep lifecycle-ambiguous governed identifiers in
21
+ the Warden review inventory instead of assigning them an unsafe automatic
22
+ target.
23
+
3
24
  ## 1.0.0-beta.45
4
25
 
5
26
  ### Patch Changes
package/README.md CHANGED
@@ -18,7 +18,7 @@ Common workflows:
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"`, and `trails wayfind diff ...` read graph artifacts through Wayfinder. `trails wayfind file <file> --outline` is the operator-owned live-source exception, assembled with `@ontrails/source` and enriched with saved graph context when available.
19
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 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. The minimal `from`/`to` seed is the primary workflow: planning deterministically derives morphology proposals, source-observed naming and public-identifier review candidates, review-only filename moves with reference-closure evidence, a namespace census, and current live-topo API preserves into the artifact's `derivation` section. Each derived item records provenance; uncertain forms and file moves remain review inventory until explicitly authored into the plan. A malformed or incompatible `trails.lock` fails planning instead of silently dropping live-API preserves. Use `plan --expand --dry-run` to inspect additional observed 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 artifact and lifecycle as `trails_plan_regrade`, `trails_list_regrades`, `trails_check_regrade`, `trails_preview_regrade`, and `trails_apply_regrade`.
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. The minimal `from`/`to` seed is the primary workflow: planning deterministically derives morphology proposals, source-observed naming and public-identifier review candidates, review-only filename moves with reference-closure evidence, a namespace census, and current live-topo API preserves into the artifact's `derivation` section. Each derived item records provenance; uncertain forms and file moves remain review inventory until explicitly authored into the plan. A malformed or incompatible `trails.lock` fails planning instead of silently dropping live-API preserves. Use `plan --expand --dry-run` to inspect additional observed 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. Human CLI runs stream concise phase progress to stderr; `--json` and `--jsonl` keep that channel quiet while returning phase names and timings in the structured result. MCP returns the same lifecycle facts from `trails_plan_regrade`, `trails_check_regrade`, `trails_preview_regrade`, `trails_apply_regrade`, and `trails_adjust_regrade` and forwards progress notifications when the caller supplies a progress token. Lifecycle timings describe the command invocation and are never persisted into plans or history receipts. Apply reuses its command-local preflight evaluation only when exact source bytes plus plan, policy, scope, lock, and tool identities still match. Any stale or unreadable fact fails closed; no prepared handle is written to disk or required for receipt regeneration, and the post-apply completion scan still runs independently.
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.45",
3
+ "version": "1.0.0-beta.46",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/outfitter-dev/trails.git",
@@ -32,25 +32,25 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@clack/prompts": "^1.1.0",
35
- "@ontrails/adapter-kit": "^1.0.0-beta.45",
36
- "@ontrails/cli": "^1.0.0-beta.45",
37
- "@ontrails/commander": "^1.0.0-beta.45",
38
- "@ontrails/config": "^1.0.0-beta.45",
39
- "@ontrails/core": "^1.0.0-beta.45",
40
- "@ontrails/http": "^1.0.0-beta.45",
41
- "@ontrails/mcp": "^1.0.0-beta.45",
42
- "@ontrails/observability": "^1.0.0-beta.45",
43
- "@ontrails/permits": "^1.0.0-beta.45",
44
- "@ontrails/regrade": "^1.0.0-beta.45",
45
- "@ontrails/source": "^1.0.0-beta.45",
46
- "@ontrails/topography": "^1.0.0-beta.45",
47
- "@ontrails/warden": "^1.0.0-beta.45",
35
+ "@ontrails/adapter-kit": "^1.0.0-beta.46",
36
+ "@ontrails/cli": "^1.0.0-beta.46",
37
+ "@ontrails/commander": "^1.0.0-beta.46",
38
+ "@ontrails/config": "^1.0.0-beta.46",
39
+ "@ontrails/core": "^1.0.0-beta.46",
40
+ "@ontrails/http": "^1.0.0-beta.46",
41
+ "@ontrails/mcp": "^1.0.0-beta.46",
42
+ "@ontrails/observability": "^1.0.0-beta.46",
43
+ "@ontrails/permits": "^1.0.0-beta.46",
44
+ "@ontrails/regrade": "^1.0.0-beta.46",
45
+ "@ontrails/source": "^1.0.0-beta.46",
46
+ "@ontrails/topography": "^1.0.0-beta.46",
47
+ "@ontrails/warden": "^1.0.0-beta.46",
48
48
  "commander": "^14.0.3",
49
49
  "typescript": "^5.9.3",
50
50
  "zod": "^4.3.5"
51
51
  },
52
52
  "devDependencies": {
53
- "@ontrails/cloudflare": "^1.0.0-beta.45",
54
- "@ontrails/testing": "^1.0.0-beta.45"
53
+ "@ontrails/cloudflare": "^1.0.0-beta.46",
54
+ "@ontrails/testing": "^1.0.0-beta.46"
55
55
  }
56
56
  }
package/src/cli.ts CHANGED
@@ -18,6 +18,7 @@ import type {
18
18
  } from '@ontrails/cli';
19
19
  import { createProgram } from '@ontrails/commander';
20
20
  import type { CreateProgramOptions } from '@ontrails/commander';
21
+ import { createTrailContext } from '@ontrails/core';
21
22
  import { resolvePermitFromBearerToken } from '@ontrails/permits';
22
23
  import { deriveTopoGraph } from '@ontrails/topography';
23
24
 
@@ -25,6 +26,7 @@ import { app, trailsCliIncludedTrails, trailsOverlays } from './app.js';
25
26
  import { resolveInputWithClack } from './clack.js';
26
27
  import { getRetiredTopoCommandDiagnostic } from './retired-topo-command.js';
27
28
  import { attachCompletionsInstallCommand } from './run-completions-install.js';
29
+ import { usesStructuredCliOutput } from './run-regrade-progress.js';
28
30
  import { attachSchemaCommand } from './run-schema.js';
29
31
  import {
30
32
  applyAdapterCheckExitCode,
@@ -302,6 +304,17 @@ const runSurfaceOnce = async (): Promise<void> => {
302
304
  const session = maybeInstallTraceSession();
303
305
  try {
304
306
  const surfaceOptions = {
307
+ createContext: () =>
308
+ createTrailContext({
309
+ progress: (event) => {
310
+ if (
311
+ !usesStructuredCliOutput(process.argv, process.env) &&
312
+ event.message?.startsWith('Regrade:') === true
313
+ ) {
314
+ process.stderr.write(`${event.message}\n`);
315
+ }
316
+ },
317
+ }),
305
318
  description: 'Agent-native, contract-first TypeScript framework',
306
319
  include: trailsCliIncludedTrails,
307
320
  name: 'trails',
@@ -13,6 +13,7 @@ import {
13
13
  runVocabularyRegrade,
14
14
  vocabularyDispositionValues,
15
15
  vocabularyRegradePlanForInput,
16
+ vocabularyRegradeTransitionForInput,
16
17
  } from '@ontrails/regrade';
17
18
  import type { VocabularyRegradePlan } from '@ontrails/regrade';
18
19
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
@@ -21,9 +22,21 @@ import { z } from 'zod';
21
22
 
22
23
  import { deriveLiveApiPreserveInventory } from './live-api-preserve.js';
23
24
  import {
24
- regradePlanArtifactSchema,
25
- rootRelativePath,
26
- } from './plan-artifact.js';
25
+ readRegradeHistoryArtifact,
26
+ regradeHistoryPathForPlan,
27
+ } from './history.js';
28
+ import { rootRelativePath } from './plan-artifact.js';
29
+
30
+ const sourceCommentExtensions = [
31
+ '.cjs',
32
+ '.cts',
33
+ '.js',
34
+ '.jsx',
35
+ '.mjs',
36
+ '.mts',
37
+ '.ts',
38
+ '.tsx',
39
+ ] as const;
27
40
 
28
41
  export const regradeAuditInputSchema = z.object({
29
42
  failOnOpen: z
@@ -49,7 +62,7 @@ const regradeAuditTransitionSchema = z.object({
49
62
  z.enum(vocabularyDispositionValues),
50
63
  z.number().int().nonnegative()
51
64
  )
52
- .describe('Current Markdown occurrence counts by classification'),
65
+ .describe('Current source occurrence counts by classification'),
53
66
  entries: regradeReportOutput.shape.entries
54
67
  .readonly()
55
68
  .describe('Actionable file-level residue details'),
@@ -57,12 +70,12 @@ const regradeAuditTransitionSchema = z.object({
57
70
  .number()
58
71
  .int()
59
72
  .nonnegative()
60
- .describe('Total classified Markdown occurrences'),
73
+ .describe('Total classified current-source occurrences'),
61
74
  open: z
62
75
  .number()
63
76
  .int()
64
77
  .nonnegative()
65
- .describe('Unresolved Markdown occurrences'),
78
+ .describe('Unresolved current-source occurrences'),
66
79
  scanned: z.number().int().nonnegative().describe('Files scanned'),
67
80
  status: z.enum(['green', 'open']).describe('Transition audit status'),
68
81
  })
@@ -87,11 +100,6 @@ export const regradeAuditOutputSchema = z.object({
87
100
  .describe('Applied vocabulary transitions audited against current source'),
88
101
  });
89
102
 
90
- const regradeAuditHistoryPlanSchema = z.object({
91
- id: z.string().min(1),
92
- runs: z.array(z.object({ plan: regradePlanArtifactSchema })).min(1),
93
- });
94
-
95
103
  export type RegradeAuditInput = z.output<typeof regradeAuditInputSchema>;
96
104
  export type RegradeAuditOutput = z.output<typeof regradeAuditOutputSchema>;
97
105
 
@@ -101,18 +109,36 @@ interface RegradeAuditCandidate {
101
109
  readonly transitionId: string;
102
110
  }
103
111
 
104
- const historyTransitionId = (rawHistory: unknown): string | undefined => {
112
+ /**
113
+ * Read the governed transition id from legacy history or v3 embedded intent.
114
+ *
115
+ * @internal
116
+ */
117
+ export const historyTransitionId = (
118
+ rawHistory: unknown
119
+ ): string | undefined => {
105
120
  if (!isPlainObject(rawHistory)) {
106
121
  return undefined;
107
122
  }
108
123
  const { id, runs } = rawHistory;
109
- const latestRun = Array.isArray(runs) ? runs.at(-1) : undefined;
124
+ const historyRuns = Array.isArray(runs) ? runs : [];
125
+ const latestRun = historyRuns.at(-1);
110
126
  const planArtifact = isPlainObject(latestRun) ? latestRun['plan'] : undefined;
111
127
  const plan = isPlainObject(planArtifact) ? planArtifact['plan'] : undefined;
112
128
  const planId = isPlainObject(plan) ? plan['id'] : undefined;
113
129
  if (typeof planId === 'string') {
114
130
  return planId;
115
131
  }
132
+ for (const run of historyRuns.toReversed()) {
133
+ const intent = isPlainObject(run) ? run['intent'] : undefined;
134
+ const intentPlan = isPlainObject(intent) ? intent['plan'] : undefined;
135
+ const intentPlanId = isPlainObject(intentPlan)
136
+ ? intentPlan['id']
137
+ : undefined;
138
+ if (typeof intentPlanId === 'string') {
139
+ return intentPlanId;
140
+ }
141
+ }
116
142
  return typeof id === 'string' ? id : undefined;
117
143
  };
118
144
 
@@ -125,7 +151,9 @@ const selectedHistoryFileNamesFor = (
125
151
  ? []
126
152
  : [...selectedTransitionIds].flatMap((transitionId) => {
127
153
  const plan = registryPlansById.get(transitionId);
128
- return plan === undefined ? [] : [`${plan.from}-to-${plan.to}.json`];
154
+ return plan === undefined
155
+ ? []
156
+ : [basename(regradeHistoryPathForPlan('.', plan))];
129
157
  })
130
158
  );
131
159
 
@@ -239,21 +267,17 @@ const readRegradeAuditCandidates = (
239
267
  ) {
240
268
  continue;
241
269
  }
242
- const history = regradeAuditHistoryPlanSchema.safeParse(rawHistory);
243
- if (!history.success) {
244
- return Result.err(
245
- new ValidationError('Invalid Regrade history audit plan.', {
246
- context: { historyFile, issues: history.error.issues },
247
- })
248
- );
270
+ const history = readRegradeHistoryArtifact(historyFile);
271
+ if (history.isErr()) {
272
+ return history;
249
273
  }
250
- const latestPlan = history.data.runs.at(-1)?.plan.plan as
274
+ const latestPlan = history.value.runs.at(-1)?.plan.plan as
251
275
  | VocabularyRegradePlan
252
276
  | undefined;
253
277
  if (latestPlan?.kind !== 'vocabulary') {
254
278
  continue;
255
279
  }
256
- const transitionId = latestPlan.id ?? history.data.id;
280
+ const transitionId = latestPlan.id ?? history.value.id;
257
281
  if (
258
282
  selectedTransitionIds !== null &&
259
283
  !selectedTransitionIds.has(transitionId)
@@ -281,10 +305,35 @@ type VocabularyPolicyClassified = NonNullable<
281
305
  NonNullable<VocabularyRegradePlan['scope']>['policyClassified']
282
306
  >;
283
307
 
308
+ /**
309
+ * Select full evidence for classified paths and comments elsewhere.
310
+ *
311
+ * @internal
312
+ */
313
+ export const sourceKindForRegradeAuditPath = (
314
+ path: string,
315
+ policyClassified: VocabularyPolicyClassified | undefined
316
+ ): 'all' | 'comments' => {
317
+ const isPolicyClassified = policyClassified?.some((policy) =>
318
+ matchesAnyPathGlob(path, policy.paths)
319
+ );
320
+ return sourceCommentExtensions.includes(
321
+ extname(path) as (typeof sourceCommentExtensions)[number]
322
+ ) && !isPolicyClassified
323
+ ? 'comments'
324
+ : 'all';
325
+ };
326
+
284
327
  export const projectPolicyClassifiedForMarkdownAudit = (
285
- plan: VocabularyRegradePlan
286
- ): VocabularyPolicyClassified | undefined =>
287
- plan.scope?.policyClassified
328
+ plan: VocabularyRegradePlan,
329
+ includeCodeComments = false
330
+ ): VocabularyPolicyClassified | undefined => {
331
+ const auditedExtensions = new Set<string>([
332
+ '.md',
333
+ '.mdx',
334
+ ...(includeCodeComments ? sourceCommentExtensions : []),
335
+ ]);
336
+ return plan.scope?.policyClassified
288
337
  ?.map((policy) => ({
289
338
  ...policy,
290
339
  paths: policy.paths.filter(
@@ -297,18 +346,40 @@ export const projectPolicyClassifiedForMarkdownAudit = (
297
346
  }))
298
347
  .filter((policy) => policy.paths.length > 0)
299
348
  .map((policy) => {
300
- const hasExplicitMarkdownPath = policy.paths.some((path) => {
349
+ const hasAuditablePath = policy.paths.some((path) => {
301
350
  const extension = extname(path);
302
- return extension === '.md' || extension === '.mdx';
351
+ if (auditedExtensions.has(extension)) {
352
+ return true;
353
+ }
354
+ const terminalPattern = basename(path);
355
+ const trailingWildcards = terminalPattern.match(/[*?]+$/)?.[0];
356
+ const canSelectAuditedExtension =
357
+ extension === '' &&
358
+ trailingWildcards !== undefined &&
359
+ [...auditedExtensions].some(
360
+ (auditedExtension) =>
361
+ trailingWildcards.includes('*') ||
362
+ (trailingWildcards.length >= auditedExtension.length &&
363
+ (terminalPattern.length > trailingWildcards.length ||
364
+ trailingWildcards.length > auditedExtension.length))
365
+ );
366
+ return (
367
+ includeCodeComments &&
368
+ (canSelectAuditedExtension ||
369
+ [...auditedExtensions].some((auditedExtension) =>
370
+ matchesAnyPathGlob(auditedExtension, [extension])
371
+ ))
372
+ );
303
373
  });
304
- if (policy.expectMatches !== true || hasExplicitMarkdownPath) {
374
+ if (policy.expectMatches !== true || hasAuditablePath) {
305
375
  return policy;
306
376
  }
307
- // A Markdown-only audit cannot prove evidence for extensionless source
308
- // globs. Markdown evidence requirements must name .md/.mdx explicitly.
377
+ // An extension-filtered audit cannot prove evidence for a path whose
378
+ // authored pattern does not identify one of the audited file families.
309
379
  const { expectMatches: _, ...markdownPolicy } = policy;
310
380
  return markdownPolicy;
311
381
  });
382
+ };
312
383
 
313
384
  export const projectExcludesForMarkdownAudit = (
314
385
  plan: VocabularyRegradePlan,
@@ -342,8 +413,12 @@ const runRegradeAuditCandidate = async (
342
413
  input: RegradeAuditInput,
343
414
  rootDir: string
344
415
  ): Promise<TrailsResult<RegradeAuditOutput['transitions'][number], Error>> => {
416
+ const includeCodeComments =
417
+ vocabularyRegradeTransitionForInput(candidate.plan.from, candidate.plan.to)
418
+ ?.target.kind === 'classified';
345
419
  const policyClassified = projectPolicyClassifiedForMarkdownAudit(
346
- candidate.plan
420
+ candidate.plan,
421
+ includeCodeComments
347
422
  );
348
423
  const exclude = projectExcludesForMarkdownAudit(
349
424
  candidate.plan,
@@ -357,7 +432,11 @@ const runRegradeAuditCandidate = async (
357
432
  ...candidate.plan,
358
433
  scope: {
359
434
  ...candidate.plan.scope,
360
- extensions: ['.md', '.mdx'],
435
+ extensions: [
436
+ '.md',
437
+ '.mdx',
438
+ ...(includeCodeComments ? sourceCommentExtensions : []),
439
+ ],
361
440
  ...(exclude === undefined ? {} : { exclude }),
362
441
  ...(include === undefined ? {} : { include }),
363
442
  ...(policyClassified === undefined ? {} : { policyClassified }),
@@ -378,6 +457,12 @@ const runRegradeAuditCandidate = async (
378
457
  ? {}
379
458
  : { preserveInventory: preserveResult.value }),
380
459
  root: rootDir,
460
+ ...(includeCodeComments
461
+ ? {
462
+ sourceKindForPath: (path: string) =>
463
+ sourceKindForRegradeAuditPath(path, policyClassified),
464
+ }
465
+ : {}),
381
466
  });
382
467
  if (reportResult.isErr()) {
383
468
  return reportResult;