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

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,34 @@
1
1
  # trails
2
2
 
3
+ ## 1.0.0-beta.47
4
+
5
+ ### Minor Changes
6
+
7
+ - [`90d394c`](https://github.com/outfitter-dev/trails/commit/90d394c005fdf6b898ba7052d0b56755af0f4954): Derive nested worktree, repository, and submodule collection boundaries in the
8
+ shared Source walker. Regrade and Warden now observe one directly targeted
9
+ working tree per run, and Regrade audit summaries expose boundary skip counts.
10
+
11
+ ## 1.0.0-beta.46
12
+
13
+ ### Minor Changes
14
+
15
+ - [`701ab85`](https://github.com/outfitter-dev/trails/commit/701ab85bd237e5fcf03725c56b70adb3612d6c15): Reuse fresh Regrade apply preflight evaluations without persisting cache state or weakening completion scans.
16
+ - [`c83d0b6`](https://github.com/outfitter-dev/trails/commit/c83d0b67ac928af1b44c1f2d2c4b36aa09a24a70): Inventory parser-native source comments and TSDoc as exact review-only entries
17
+ for governed classified vocabulary transitions across CLI, MCP, and audit.
18
+ - [`93757ba`](https://github.com/outfitter-dev/trails/commit/93757ba102ae7ba8d9a7a6c17f119cda47d342a7): Expose ordered Regrade lifecycle phases and wall-clock timings across CLI and
19
+ MCP completion results, with concise human CLI progress isolated on stderr.
20
+ Keep topo-scoped JSON and JSONL environment selectors effective when the
21
+ operator does not explicitly choose an output mode.
22
+ - [`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.
23
+ - [`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.
24
+
25
+ ### Patch Changes
26
+
27
+ - [`768cc79`](https://github.com/outfitter-dev/trails/commit/768cc79ca10947b8808b376e281e1a81131b4acc): Close missed projection vocabulary residue in Regrade internals and public
28
+ error-rendering guidance, and keep lifecycle-ambiguous governed identifiers in
29
+ the Warden review inventory instead of assigning them an unsafe automatic
30
+ target.
31
+
3
32
  ## 1.0.0-beta.45
4
33
 
5
34
  ### 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.47",
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.47",
36
+ "@ontrails/cli": "^1.0.0-beta.47",
37
+ "@ontrails/commander": "^1.0.0-beta.47",
38
+ "@ontrails/config": "^1.0.0-beta.47",
39
+ "@ontrails/core": "^1.0.0-beta.47",
40
+ "@ontrails/http": "^1.0.0-beta.47",
41
+ "@ontrails/mcp": "^1.0.0-beta.47",
42
+ "@ontrails/observability": "^1.0.0-beta.47",
43
+ "@ontrails/permits": "^1.0.0-beta.47",
44
+ "@ontrails/regrade": "^1.0.0-beta.47",
45
+ "@ontrails/source": "^1.0.0-beta.47",
46
+ "@ontrails/topography": "^1.0.0-beta.47",
47
+ "@ontrails/warden": "^1.0.0-beta.47",
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.47",
54
+ "@ontrails/testing": "^1.0.0-beta.47"
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,13 +70,16 @@ 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'),
80
+ skippedByReason: z
81
+ .record(z.string(), z.number().int().nonnegative())
82
+ .describe('Collection skips grouped by machine-readable reason'),
67
83
  status: z.enum(['green', 'open']).describe('Transition audit status'),
68
84
  })
69
85
  .describe('Concise current-tree audit of the latest applied plan'),
@@ -87,11 +103,6 @@ export const regradeAuditOutputSchema = z.object({
87
103
  .describe('Applied vocabulary transitions audited against current source'),
88
104
  });
89
105
 
90
- const regradeAuditHistoryPlanSchema = z.object({
91
- id: z.string().min(1),
92
- runs: z.array(z.object({ plan: regradePlanArtifactSchema })).min(1),
93
- });
94
-
95
106
  export type RegradeAuditInput = z.output<typeof regradeAuditInputSchema>;
96
107
  export type RegradeAuditOutput = z.output<typeof regradeAuditOutputSchema>;
97
108
 
@@ -101,18 +112,36 @@ interface RegradeAuditCandidate {
101
112
  readonly transitionId: string;
102
113
  }
103
114
 
104
- const historyTransitionId = (rawHistory: unknown): string | undefined => {
115
+ /**
116
+ * Read the governed transition id from legacy history or v3 embedded intent.
117
+ *
118
+ * @internal
119
+ */
120
+ export const historyTransitionId = (
121
+ rawHistory: unknown
122
+ ): string | undefined => {
105
123
  if (!isPlainObject(rawHistory)) {
106
124
  return undefined;
107
125
  }
108
126
  const { id, runs } = rawHistory;
109
- const latestRun = Array.isArray(runs) ? runs.at(-1) : undefined;
127
+ const historyRuns = Array.isArray(runs) ? runs : [];
128
+ const latestRun = historyRuns.at(-1);
110
129
  const planArtifact = isPlainObject(latestRun) ? latestRun['plan'] : undefined;
111
130
  const plan = isPlainObject(planArtifact) ? planArtifact['plan'] : undefined;
112
131
  const planId = isPlainObject(plan) ? plan['id'] : undefined;
113
132
  if (typeof planId === 'string') {
114
133
  return planId;
115
134
  }
135
+ for (const run of historyRuns.toReversed()) {
136
+ const intent = isPlainObject(run) ? run['intent'] : undefined;
137
+ const intentPlan = isPlainObject(intent) ? intent['plan'] : undefined;
138
+ const intentPlanId = isPlainObject(intentPlan)
139
+ ? intentPlan['id']
140
+ : undefined;
141
+ if (typeof intentPlanId === 'string') {
142
+ return intentPlanId;
143
+ }
144
+ }
116
145
  return typeof id === 'string' ? id : undefined;
117
146
  };
118
147
 
@@ -125,7 +154,9 @@ const selectedHistoryFileNamesFor = (
125
154
  ? []
126
155
  : [...selectedTransitionIds].flatMap((transitionId) => {
127
156
  const plan = registryPlansById.get(transitionId);
128
- return plan === undefined ? [] : [`${plan.from}-to-${plan.to}.json`];
157
+ return plan === undefined
158
+ ? []
159
+ : [basename(regradeHistoryPathForPlan('.', plan))];
129
160
  })
130
161
  );
131
162
 
@@ -239,21 +270,17 @@ const readRegradeAuditCandidates = (
239
270
  ) {
240
271
  continue;
241
272
  }
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
- );
273
+ const history = readRegradeHistoryArtifact(historyFile);
274
+ if (history.isErr()) {
275
+ return history;
249
276
  }
250
- const latestPlan = history.data.runs.at(-1)?.plan.plan as
277
+ const latestPlan = history.value.runs.at(-1)?.plan.plan as
251
278
  | VocabularyRegradePlan
252
279
  | undefined;
253
280
  if (latestPlan?.kind !== 'vocabulary') {
254
281
  continue;
255
282
  }
256
- const transitionId = latestPlan.id ?? history.data.id;
283
+ const transitionId = latestPlan.id ?? history.value.id;
257
284
  if (
258
285
  selectedTransitionIds !== null &&
259
286
  !selectedTransitionIds.has(transitionId)
@@ -281,10 +308,35 @@ type VocabularyPolicyClassified = NonNullable<
281
308
  NonNullable<VocabularyRegradePlan['scope']>['policyClassified']
282
309
  >;
283
310
 
311
+ /**
312
+ * Select full evidence for classified paths and comments elsewhere.
313
+ *
314
+ * @internal
315
+ */
316
+ export const sourceKindForRegradeAuditPath = (
317
+ path: string,
318
+ policyClassified: VocabularyPolicyClassified | undefined
319
+ ): 'all' | 'comments' => {
320
+ const isPolicyClassified = policyClassified?.some((policy) =>
321
+ matchesAnyPathGlob(path, policy.paths)
322
+ );
323
+ return sourceCommentExtensions.includes(
324
+ extname(path) as (typeof sourceCommentExtensions)[number]
325
+ ) && !isPolicyClassified
326
+ ? 'comments'
327
+ : 'all';
328
+ };
329
+
284
330
  export const projectPolicyClassifiedForMarkdownAudit = (
285
- plan: VocabularyRegradePlan
286
- ): VocabularyPolicyClassified | undefined =>
287
- plan.scope?.policyClassified
331
+ plan: VocabularyRegradePlan,
332
+ includeCodeComments = false
333
+ ): VocabularyPolicyClassified | undefined => {
334
+ const auditedExtensions = new Set<string>([
335
+ '.md',
336
+ '.mdx',
337
+ ...(includeCodeComments ? sourceCommentExtensions : []),
338
+ ]);
339
+ return plan.scope?.policyClassified
288
340
  ?.map((policy) => ({
289
341
  ...policy,
290
342
  paths: policy.paths.filter(
@@ -297,18 +349,40 @@ export const projectPolicyClassifiedForMarkdownAudit = (
297
349
  }))
298
350
  .filter((policy) => policy.paths.length > 0)
299
351
  .map((policy) => {
300
- const hasExplicitMarkdownPath = policy.paths.some((path) => {
352
+ const hasAuditablePath = policy.paths.some((path) => {
301
353
  const extension = extname(path);
302
- return extension === '.md' || extension === '.mdx';
354
+ if (auditedExtensions.has(extension)) {
355
+ return true;
356
+ }
357
+ const terminalPattern = basename(path);
358
+ const trailingWildcards = terminalPattern.match(/[*?]+$/)?.[0];
359
+ const canSelectAuditedExtension =
360
+ extension === '' &&
361
+ trailingWildcards !== undefined &&
362
+ [...auditedExtensions].some(
363
+ (auditedExtension) =>
364
+ trailingWildcards.includes('*') ||
365
+ (trailingWildcards.length >= auditedExtension.length &&
366
+ (terminalPattern.length > trailingWildcards.length ||
367
+ trailingWildcards.length > auditedExtension.length))
368
+ );
369
+ return (
370
+ includeCodeComments &&
371
+ (canSelectAuditedExtension ||
372
+ [...auditedExtensions].some((auditedExtension) =>
373
+ matchesAnyPathGlob(auditedExtension, [extension])
374
+ ))
375
+ );
303
376
  });
304
- if (policy.expectMatches !== true || hasExplicitMarkdownPath) {
377
+ if (policy.expectMatches !== true || hasAuditablePath) {
305
378
  return policy;
306
379
  }
307
- // A Markdown-only audit cannot prove evidence for extensionless source
308
- // globs. Markdown evidence requirements must name .md/.mdx explicitly.
380
+ // An extension-filtered audit cannot prove evidence for a path whose
381
+ // authored pattern does not identify one of the audited file families.
309
382
  const { expectMatches: _, ...markdownPolicy } = policy;
310
383
  return markdownPolicy;
311
384
  });
385
+ };
312
386
 
313
387
  export const projectExcludesForMarkdownAudit = (
314
388
  plan: VocabularyRegradePlan,
@@ -342,8 +416,12 @@ const runRegradeAuditCandidate = async (
342
416
  input: RegradeAuditInput,
343
417
  rootDir: string
344
418
  ): Promise<TrailsResult<RegradeAuditOutput['transitions'][number], Error>> => {
419
+ const includeCodeComments =
420
+ vocabularyRegradeTransitionForInput(candidate.plan.from, candidate.plan.to)
421
+ ?.target.kind === 'classified';
345
422
  const policyClassified = projectPolicyClassifiedForMarkdownAudit(
346
- candidate.plan
423
+ candidate.plan,
424
+ includeCodeComments
347
425
  );
348
426
  const exclude = projectExcludesForMarkdownAudit(
349
427
  candidate.plan,
@@ -357,7 +435,11 @@ const runRegradeAuditCandidate = async (
357
435
  ...candidate.plan,
358
436
  scope: {
359
437
  ...candidate.plan.scope,
360
- extensions: ['.md', '.mdx'],
438
+ extensions: [
439
+ '.md',
440
+ '.mdx',
441
+ ...(includeCodeComments ? sourceCommentExtensions : []),
442
+ ],
361
443
  ...(exclude === undefined ? {} : { exclude }),
362
444
  ...(include === undefined ? {} : { include }),
363
445
  ...(policyClassified === undefined ? {} : { policyClassified }),
@@ -378,6 +460,12 @@ const runRegradeAuditCandidate = async (
378
460
  ? {}
379
461
  : { preserveInventory: preserveResult.value }),
380
462
  root: rootDir,
463
+ ...(includeCodeComments
464
+ ? {
465
+ sourceKindForPath: (path: string) =>
466
+ sourceKindForRegradeAuditPath(path, policyClassified),
467
+ }
468
+ : {}),
381
469
  });
382
470
  if (reportResult.isErr()) {
383
471
  return reportResult;
@@ -404,6 +492,7 @@ const runRegradeAuditCandidate = async (
404
492
  occurrences: run.ledger.occurrences.length,
405
493
  open: run.report.open,
406
494
  scanned: reportResult.value.scanned,
495
+ skippedByReason: reportResult.value.skipsByReason,
407
496
  status: run.report.gate.status,
408
497
  },
409
498
  source: candidate.source,