Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 22 additions & 11 deletions .agents/skills/trails-adrs/scripts/adr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { dirname, join } from 'node:path';

import { parseArgs, shouldApply, previewBanner, printHelp } from './lib/cli.ts';
import type { Args } from './lib/cli.ts';
import { writeDecisionMap } from './lib/decision-map.ts';
import {
buildDecisionMapContents,
writeDecisionMap,
} from './lib/decision-map.ts';
import {
listNumberedAdrs,
listDrafts,
Expand All @@ -40,7 +43,7 @@ import { extractTitle, serializeFrontmatter } from './lib/frontmatter.ts';
import type { Frontmatter } from './lib/frontmatter.ts';
import { gitMove } from './lib/git.ts';
import { rebuildIndex } from './lib/index.ts';
import { ADR_DIR, DRAFTS_DIR, INDEX_PATH, MAP_PATH } from './lib/paths.ts';
import { ADR_DIR, DRAFTS_DIR, INDEX_PATH } from './lib/paths.ts';
import {
fixCrossReferences,
rewriteDraftLinks,
Expand Down Expand Up @@ -477,21 +480,29 @@ const cmdCheck = (args: Args): void => {
report('error', 'README.md', 'index file does not exist');
}

console.log('Checking decision map...');
if (!existsSync(MAP_PATH)) {
report(
'warn',
'decision-map.json',
'decision map does not exist — run "adr map" to generate'
);
}

if (fix && fixes > 0) {
console.log(`\nApplied ${fixes} fixes. Rebuilding index and map...`);
rebuildIndex();
writeDecisionMap();
}

console.log('Checking decision map...');
for (const artifact of buildDecisionMapContents()) {
const label = artifact.path.slice(process.cwd().length + 1);
if (!existsSync(artifact.path)) {
report('error', label, 'generated ADR artifact is missing');
continue;
}
const committed = readFileSync(artifact.path, 'utf8');
if (committed !== artifact.content) {
report(
'error',
label,
'generated ADR artifact is stale; run "bun scripts/adr.ts map"'
);
}
}

console.log(`\n${errors} errors, ${warnings} warnings`);
if (errors > 0) {
process.exit(1);
Expand Down
40 changes: 23 additions & 17 deletions .agents/skills/trails-adrs/scripts/lib/decision-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,34 +369,40 @@ const stringifyFormattedJson = (
return JSON.stringify(value);
};

const writeJson = (path: string, data: unknown): void => {
writeFileSync(path, `${stringifyFormattedJson(data)}\n`, 'utf8');
};

/**
* Write all generated ADR artifacts:
* Build all generated ADR artifacts:
* - docs/adr/decision-map.json (accepted ADRs only — stable)
* - docs/adr/drafts/decision-map.json (drafts only — changes with drafts)
* - docs/adr/drafts/README.md (generated index)
*/
export const writeDecisionMap = (): void => {
export const buildDecisionMapContents = (): readonly {
readonly path: string;
readonly content: string;
}[] => {
const allFiles = [...listNumberedAdrs(), ...listDrafts()];
const numberedEntries = buildNumberedEntries(allFiles);
const draftEntries = buildDraftEntries(allFiles);

// Accepted ADR map (stable — only changes when ADRs are promoted/modified)
const acceptedMap: DecisionMap = { entries: numberedEntries, version: 1 };
writeJson(MAP_PATH, acceptedMap);
console.log(`Updated ${MAP_PATH}`);
const draftsMap: DecisionMap = { entries: draftEntries, version: 1 };
const draftsIndex = buildDraftsIndex(draftEntries, numberedEntries);

return [
{ content: `${stringifyFormattedJson(acceptedMap)}\n`, path: MAP_PATH },
{
content: `${stringifyFormattedJson(draftsMap)}\n`,
path: DRAFTS_MAP_PATH,
},
{ content: draftsIndex, path: DRAFTS_INDEX_PATH },
];
};

// Drafts map (changes with draft edits)
/** Write all generated ADR artifacts. */
export const writeDecisionMap = (): void => {
mkdirSync(DRAFTS_DIR, { recursive: true });
const draftsMap: DecisionMap = { entries: draftEntries, version: 1 };
writeJson(DRAFTS_MAP_PATH, draftsMap);
console.log(`Updated ${DRAFTS_MAP_PATH}`);

// Drafts README
const draftsIndex = buildDraftsIndex(draftEntries, numberedEntries);
writeFileSync(DRAFTS_INDEX_PATH, draftsIndex, 'utf8');
console.log(`Updated ${DRAFTS_INDEX_PATH}`);
for (const artifact of buildDecisionMapContents()) {
writeFileSync(artifact.path, artifact.content, 'utf8');
console.log(`Updated ${artifact.path}`);
}
};
9 changes: 9 additions & 0 deletions .changeset/trl-774-resource-context-hygiene.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@ontrails/config": patch
"@ontrails/core": patch
"@ontrails/permits": patch
"@ontrails/topographer": patch
---

Clean up resource context naming in shipped source and examples so resource
factories consistently use resource vocabulary.
33 changes: 22 additions & 11 deletions .claude/skills/trails-adrs/scripts/adr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ import { dirname, join } from 'node:path';

import { parseArgs, shouldApply, previewBanner, printHelp } from './lib/cli.ts';
import type { Args } from './lib/cli.ts';
import { writeDecisionMap } from './lib/decision-map.ts';
import {
buildDecisionMapContents,
writeDecisionMap,
} from './lib/decision-map.ts';
import {
listNumberedAdrs,
listDrafts,
Expand All @@ -40,7 +43,7 @@ import { extractTitle, serializeFrontmatter } from './lib/frontmatter.ts';
import type { Frontmatter } from './lib/frontmatter.ts';
import { gitMove } from './lib/git.ts';
import { rebuildIndex } from './lib/index.ts';
import { ADR_DIR, DRAFTS_DIR, INDEX_PATH, MAP_PATH } from './lib/paths.ts';
import { ADR_DIR, DRAFTS_DIR, INDEX_PATH } from './lib/paths.ts';
import {
fixCrossReferences,
rewriteDraftLinks,
Expand Down Expand Up @@ -477,21 +480,29 @@ const cmdCheck = (args: Args): void => {
report('error', 'README.md', 'index file does not exist');
}

console.log('Checking decision map...');
if (!existsSync(MAP_PATH)) {
report(
'warn',
'decision-map.json',
'decision map does not exist — run "adr map" to generate'
);
}

if (fix && fixes > 0) {
console.log(`\nApplied ${fixes} fixes. Rebuilding index and map...`);
rebuildIndex();
writeDecisionMap();
}

console.log('Checking decision map...');
for (const artifact of buildDecisionMapContents()) {
const label = artifact.path.slice(process.cwd().length + 1);
if (!existsSync(artifact.path)) {
report('error', label, 'generated ADR artifact is missing');
continue;
}
const committed = readFileSync(artifact.path, 'utf8');
if (committed !== artifact.content) {
report(
'error',
label,
'generated ADR artifact is stale; run "bun scripts/adr.ts map"'
);
}
}

console.log(`\n${errors} errors, ${warnings} warnings`);
if (errors > 0) {
process.exit(1);
Expand Down
40 changes: 23 additions & 17 deletions .claude/skills/trails-adrs/scripts/lib/decision-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,34 +369,40 @@ const stringifyFormattedJson = (
return JSON.stringify(value);
};

const writeJson = (path: string, data: unknown): void => {
writeFileSync(path, `${stringifyFormattedJson(data)}\n`, 'utf8');
};

/**
* Write all generated ADR artifacts:
* Build all generated ADR artifacts:
* - docs/adr/decision-map.json (accepted ADRs only — stable)
* - docs/adr/drafts/decision-map.json (drafts only — changes with drafts)
* - docs/adr/drafts/README.md (generated index)
*/
export const writeDecisionMap = (): void => {
export const buildDecisionMapContents = (): readonly {
readonly path: string;
readonly content: string;
}[] => {
const allFiles = [...listNumberedAdrs(), ...listDrafts()];
const numberedEntries = buildNumberedEntries(allFiles);
const draftEntries = buildDraftEntries(allFiles);

// Accepted ADR map (stable — only changes when ADRs are promoted/modified)
const acceptedMap: DecisionMap = { entries: numberedEntries, version: 1 };
writeJson(MAP_PATH, acceptedMap);
console.log(`Updated ${MAP_PATH}`);
const draftsMap: DecisionMap = { entries: draftEntries, version: 1 };
const draftsIndex = buildDraftsIndex(draftEntries, numberedEntries);

return [
{ content: `${stringifyFormattedJson(acceptedMap)}\n`, path: MAP_PATH },
{
content: `${stringifyFormattedJson(draftsMap)}\n`,
path: DRAFTS_MAP_PATH,
},
{ content: draftsIndex, path: DRAFTS_INDEX_PATH },
];
};

// Drafts map (changes with draft edits)
/** Write all generated ADR artifacts. */
export const writeDecisionMap = (): void => {
mkdirSync(DRAFTS_DIR, { recursive: true });
const draftsMap: DecisionMap = { entries: draftEntries, version: 1 };
writeJson(DRAFTS_MAP_PATH, draftsMap);
console.log(`Updated ${DRAFTS_MAP_PATH}`);

// Drafts README
const draftsIndex = buildDraftsIndex(draftEntries, numberedEntries);
writeFileSync(DRAFTS_INDEX_PATH, draftsIndex, 'utf8');
console.log(`Updated ${DRAFTS_INDEX_PATH}`);
for (const artifact of buildDecisionMapContents()) {
writeFileSync(artifact.path, artifact.content, 'utf8');
console.log(`Updated ${artifact.path}`);
}
};
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ jobs:
- name: Warden
run: bun packages/warden/bin/warden.ts --ci --apps trails,trails-demo

- name: ADR check
run: bun scripts/adr.ts check

changeset:
name: Changeset
if: github.event_name == 'pull_request'
Expand Down
12 changes: 8 additions & 4 deletions docs/adr/0009-first-class-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ Resources are frozen definition objects with `kind: 'resource'`, parallel to `tr

```typescript
const db = resource('db.main', {
create: (svc) => Result.ok(openDatabase(svc.env?.DATABASE_URL)),
create: (resourceCtx) =>
Result.ok(openDatabase(resourceCtx.env?.DATABASE_URL)),
dispose: (conn) => conn.close(),
health: (conn) => conn.ping(),
mock: () => createInMemoryDb(),
description: 'Primary database connection',
});
// svc is ResourceContext — env, cwd, workspaceRoot only. Not the full TrailContext.
// resourceCtx is ResourceContext: env, cwd, workspaceRoot, and config.
// It is not the full TrailContext.
```

The type is inferred from the `create` factory's return value. `db` knows it produces a `Database` instance. No manual generic annotation needed.
Expand Down Expand Up @@ -267,11 +269,13 @@ A Trails-native package can ship both a resource and a layer that uses it:
```typescript
// @ontrails/storage could provide:
export const storageResource = resource('storage', { /* ... */ });
export const transactionLayer = (svc: Resource<Storage>): Layer => ({
export const transactionLayer = (
storageResource: Resource<Storage>
): Layer => ({
name: 'transaction',
wrap: (trail, impl) => async (input, ctx) => {
if (trail.intent === 'read') return impl(input, ctx);
const store = svc.from(ctx);
const store = storageResource.from(ctx);
return store.withTransaction(() => impl(input, ctx));
},
});
Expand Down
5 changes: 3 additions & 2 deletions docs/adr/0049-composition-is-compose-not-cross.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ slug: composition-is-compose-not-cross
title: Composition is `compose`, not `cross`
status: accepted
created: 2026-05-25
updated: 2026-05-26
updated: 2026-06-09
owners: ['[galligan](https://github.com/galligan)']
depends_on: [1, 24, 28]
depends_on: [1, 3, 24, 28]
---

# ADR-0049: Composition is `compose`, not `cross`
Expand Down Expand Up @@ -148,6 +148,7 @@ Per ADR-0001's in-place cutover precedent[^cutover], the ADR record itself is up
- TRL-784 — the tracking issue carrying the full file:line census, the cutover blast radius, and the dependency blockers.[^trl784]
- `.agents/memory/decisions.md` — the logged decision (2026-05-24, verb reversed call→compose 2026-05-25).
- [ADR-0001: Naming Conventions](0001-naming-conventions.md#a-note-on-the-adr-record) — the brand-vs-plain heuristic and the in-place cutover precedent; gains a Cutover 4 log entry pointing here.
- [ADR-0003: Unified Trail Primitive](0003-unified-trail-primitive.md) — the unified trail primitive whose composition field and runtime call were renamed in place.
- [ADR-0024: Typed Trail Composition](0024-typed-trail-composition.md) — the composition contract this renames in place (`crossInput` → `composeInput`).
- [ADR-0028: Concurrent Trail Composition](0028-concurrent-crossing.md) — the concurrent `ctx.compose([...])` overload after this in-place rename.
- The prior `follow`→`cross` cutover, encoded in `scripts/vocab-cutover-rewrite.ts` — the codemod precedent this extends.
Expand Down
11 changes: 8 additions & 3 deletions docs/adr/decision-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@
"from": "0048",
"fromPath": "docs/adr/0048-trail-versioning-v3.md"
},
{
"context": "- [ADR-0003: Unified Trail Primitive](0003-unified-trail-primitive.md) — the unified trail primitive whose composition field and runtime call were renamed in place.",
"from": "0049",
"fromPath": "docs/adr/0049-composition-is-compose-not-cross.md"
},
{
"context": "- docs/adr/0003-unified-trail-primitive.md",
"from": "20260331",
Expand Down Expand Up @@ -2013,7 +2018,7 @@
"status": "accepted",
"superseded_by": null,
"title": "Adapter Extraction and Composition Around Core Contracts",
"updated": "2026-05-16"
"updated": "2026-06-09"
},
{
"created": "2026-04-09",
Expand Down Expand Up @@ -2754,7 +2759,7 @@
},
{
"created": "2026-05-25",
"depends_on": ["1", "24", "28"],
"depends_on": ["1", "3", "24", "28"],
"inbound": [
{
"context": "- **Cutover 4** ([ADR-0049](0049-composition-is-compose-not-cross.md)): Composition vocabulary cleanup. Retired `cross` / `crosses` in favor of `compose` / `composes`, including `ctx.compose()`, `comp",
Expand All @@ -2769,7 +2774,7 @@
"status": "accepted",
"superseded_by": null,
"title": "Composition is `compose`, not `cross`",
"updated": "2026-05-26"
"updated": "2026-06-09"
}
],
"version": 1
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/drafts/decision-map.json
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@
"status": "draft",
"superseded_by": null,
"title": "Release Provenance as Lifecycle Projection",
"updated": "2026-06-08"
"updated": "2026-06-09"
}
],
"version": 1
Expand Down
1 change: 0 additions & 1 deletion knip.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ const config: KnipConfig = {
workspaces: {
'.': {
entry: [
'scripts/adr.ts',
'scripts/bootstrap/main.ts',
'scripts/verify-oxc-resolver-published.ts',
'scripts/__tests__/**/*.ts',
Expand Down
4 changes: 4 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ pre-push:
env:
GIT_PAGER: cat
run: bun trails warden --pre-push
adr:
env:
GIT_PAGER: cat
run: bun scripts/adr.ts check
test:
env:
GIT_PAGER: cat
Expand Down
Loading
Loading