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
18 changes: 18 additions & 0 deletions .changeset/deslop-dead-modules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
"agent-bundle": patch
"@agent-bundle/runtime": patch
---
Comment on lines +1 to +4

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Consolidate the changesets and include the runtime package

This commit adds three changeset files for one PR even though the repository requires exactly one, and all three list only agent-bundle despite the change to packages/rsc-runtime/src/state/contract.ts. Consolidate the entries into one changeset that also includes @agent-bundle/runtime, and rewrite its summary to use the required user-facing (#PR) format; otherwise the release metadata will contain three separate agent-bundle entries while omitting the changed runtime package.

AGENTS.md reference: AGENTS.md:L97-L105

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5a66185: the three changesets are consolidated into one .changeset/deslop-dead-modules.md that lists both agent-bundle and @agent-bundle/runtime (patch), with a user-facing summary ending in (#451). Note expectCanonicalPayload was never re-exported from state/index.ts, so the runtime entry is a patch, not a breaking bump.


Remove nineteen unreferenced modules left behind by extractions that never
rewired their callers, collapse the surviving duplicated helpers onto their
canonical owners, and fix the three defects that drift had caused: the
Workbench Logs view now shows `lifecycle.replay.started`, `.completed`, and
`.failed` Dev Log records and records carrying `routeId` (the browser log
client's private copy of the `agent-bundle/contracts/dev-logs` vocabulary had
omitted them); the Workbench now subscribes to `dev.host.sync` project events,
which `project-client` had left out of its SSE listener list; and the
Comment on lines +9 to +13

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document the Workbench behavior changes in both locales

These lines announce user-visible changes to the Workbench Logs view and live dev.host.sync handling, but this commit does not update the matching English or Chinese Workbench documentation. Add the behavior to both website/docs/en/** and website/docs/zh/** so the public documentation ships with the changes it describes.

AGENTS.md reference: AGENTS.md:L71-L77

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No docs change is needed here: both fixes restore behavior the site already documents rather than adding new behavior. website/docs/{en,zh}/guide/development/workbench.mdx describes the Logs view as grouping hook-producer events (which is where lifecycle.replay.* records belong) and documents dev.host.sync — including the AB7202 diagnostic it carries — as an event the Workbench receives; the Workbench simply failed to show those records before. The changeset entry names the fix because it is user-visible in the release notes, but the documented contract is unchanged, so this is a bug fix against the existing pages, not a new page section. (PR is merged; replying here per the merged-PR thread rule.)

Playground trace store redacts with the shared `core/credentials` classifier,
which adds the provider environment-variable patterns its local copy lacked.
`@agent-bundle/runtime` drops the internal, never-exported
`expectCanonicalPayload` helper from `state/contract`. No public export, route,
diagnostic code, or runtime behavior changes otherwise. (#451)
47 changes: 47 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,52 @@
# Repository guidance

## Code hygiene

- **Extract and rewire in one change.** Every dead module this repo has had to
delete was born the same way: a refactor lifted helpers into a new file and
never switched the original over, so the monolith kept its inline copy and
the new file had zero importers from its first commit. If a commit adds
`foo-codec.ts`, the same commit deletes the code it replaced and leaves
`foo.ts` importing it. The follow-up PR that "wires it up" does not arrive.
- **A module with no production importer is not delivered.** This repo's
dominant failure mode is a thoroughly tested service that nothing mounts.
Before believing a capability exists, find the production caller, not the
test. Before opening a PR, confirm every file it adds is reachable from
`src/index.ts`, a route, a CLI entry, or a hook — a passing suite proves
nothing about whether the code runs.
- **Look for the helper before writing it.** `dev/http.ts` owns request and
response helpers (`diagnostic`, `requestError`, `isRequestDiagnostic`,
`responseDiagnostic`, `responseJson`, `singleHeader`, `isJsonRequest`,
`readBody`, `readJsonBody`, `rawPathname`, `decodedOpaqueSegment`);
`core/strict-json.ts`, `core/errors.ts`, `core/paths.ts`, and
`core/freeze.ts` own their equivalents. A route module that defines its own
`readBody` has forked a security-relevant bound that will be fixed in one
copy and not the other.
- **Never copy a helper to dodge an import cycle.** Move it to a leaf module
both sides import — `config/conventional-entry.ts` is the pattern. A comment
explaining why the copy exists documents the debt; it does not discharge it.
- **One class per name.** Two identical `class FooError` declarations in two
modules are not interchangeable: `instanceof` against the wrong one silently
returns `false`, so the `catch` that was supposed to handle it falls through.
Error classes live with the code that throws them, exported once.
- **Delete on sight.** Unreferenced code is not free — it is read during
review, matched by search, and copied by the next author who finds it before
the live version. Removing it is a `patch` changeset, not a project.
- Neither `pnpm lint` nor `pnpm typecheck` reports an unreferenced module, so
check by hand when a change adds or moves files:

```sh
# any tracked file that mentions the module, other than itself
git grep -l '<module-stem>' -- ':!repos'
```

One hit means the module only mentions itself and nothing imports it. Watch
for false positives from prose in `docs/**` and from strings that merely
contain the name: `Symbol('epoch-staging')` in `dev/epoch-store.ts` was the
only match for a 343-line dead file, which is why it read as reachable.
- Gate before pushing: `pnpm typecheck && pnpm lint && pnpm test:unit`, plus
`pnpm build` first if the change touches `packages/rsc-runtime`.

## Workbench platform scope

- The developer Workbench is a desktop-only application.
Expand Down
6 changes: 6 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Repository guidance for this project lives in [AGENTS.md](./AGENTS.md) — read it
first. It is the single source for code hygiene, Workbench scope, public
examples, the docsite, changesets, pull requests, and vendored `repos/`.

Keep it that way: add project rules to `AGENTS.md`, never here. A second copy
of the guidance is the same duplication the hygiene section exists to prevent.
2 changes: 1 addition & 1 deletion examples/audiobook-curator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ instead of maintaining separate MCP and CLI presenters:
| --- | --- | --- |
| `DataList`, `Field`, `Callout`, and `FileList` | Provide atomic report fields, prose callouts, and file-list blocks throughout the component library and directly in the catalog resource, curate prompt, cache route, and library audit | Provide the same primitives through the shared components and directly in `library-audit` |
| `FileCard` and `EditionCard`, fed by `view-models` | Render file and edition models in `audit_library`, shelf, and ranking views | Reached through the receipt-specific shelves and ranking components |
| `InspectionShelf`, `InventoryShelf`, `AuditShelf`, and `SelectionShelf` | Compose receipt-specific inspection, inventory, audit, and selection reports; the inspection, inventory, and selection shelves are used directly by their MCP routes | `InventoryShelf` and `SelectionShelf` compose `inventory` and `select` |
| `InspectionShelf`, `InventoryShelf`, and `SelectionShelf` | Compose receipt-specific inspection, inventory, and selection reports, each used directly by its MCP route; `audit_library` composes `AuditSummary` and `AuditFileCards` alongside the asynchronous `LibraryAnalysis` instead of a shelf | `InventoryShelf` and `SelectionShelf` compose `inventory` and `select` |
| `SearchRanking`, `IdentifyRanking`, and `SelectionRanking` | Render the statically typed ranking for `search_audible`, `identify_audible_sample`, and `select_audible_edition` | `SearchRanking` composes `audible-search` |
| `AcousticTrail`, `IdentifyTrail`, and `WhisperTrail` | Render the statically typed evidence for acoustic verification, acoustic identification, and Whisper verification | No authored rendered counterpart; those compatibility commands remain plain `.ts` routes |
| `MetadataMutation`, `ChapterMutation`, `ConversionMutation`, and `PrepareMutation` | Render each statically typed metadata, chapter, conversion, or preparation mutation | `ConversionMutation` composes `convert` |
Expand Down
30 changes: 0 additions & 30 deletions examples/audiobook-curator/src/components/library-shelf.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,36 +90,6 @@ export const AuditFileCards = ({ receipt }: AuditShelfProps) => (
</>
);

export const AuditShelf = ({ receipt }: AuditShelfProps) => {
return (
<>
<AuditSummary receipt={receipt} />
<AuditFileCards receipt={receipt} />
{receipt.duplicateCandidates.slice(0, 10).map((group) => (
<CandidateGroupCallout
files={group.files}
identityKey={group.identityKey}
key={group.identityKey}
kind="duplicate"
reviewNote={receipt.reviewNote}
/>
))}
{receipt.multipartCandidates.slice(0, 10).map((group) => (
<CandidateGroupCallout
files={group.files.map((file) => `part ${String(file.part)} ${file.path}`)}
identityKey={group.identityKey}
key={`${group.directory}/${group.identityKey}`}
kind="multipart"
reviewNote={receipt.reviewNote}
/>
))}
{receipt.duplicateCandidates.length === 0 && receipt.multipartCandidates.length === 0
? <Callout tone="review">{receipt.reviewNote}</Callout>
: null}
</>
);
};

export interface SelectionShelfProps {
readonly receipt: SelectionReceipt;
}
Expand Down
2 changes: 0 additions & 2 deletions examples/audiobook-curator/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ export const CurationShelfStateSchema = z.object({
selections: z.array(ShelfSelectionSchema),
}).strict();

export type ShelfSelection = z.output<typeof ShelfSelectionSchema>;
export type ShelfMutation = z.output<typeof ShelfMutationSchema>;
export type CurationShelfState = z.output<typeof CurationShelfStateSchema>;

export const curationShelfEventSchemas = {
Expand Down
1 change: 0 additions & 1 deletion examples/rsc-agent-runtime/src/runtime/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ export interface DevRuntimeInspectionResponse {
readonly inspection: DevRuntimeInspectionEnvelope;
}

export type McpTimeline = RuntimeSnapshot;

export interface ToolAnnotations {
readOnlyHint: boolean;
Expand Down
1 change: 0 additions & 1 deletion examples/rsc-agent-runtime/src/runtime/state-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const RecordedEditSchema = z
})
.strict();

export type RecordedEdit = z.output<typeof RecordedEditSchema>;

const JsonValueSchema: z.ZodType<JsonValue> = z.lazy(() =>
z.union([
Expand Down
1 change: 0 additions & 1 deletion examples/worktree-proximity/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export const TopologyStateSchema = z

export type Actor = z.output<typeof ActorSchema>;
export type Activity = z.output<typeof ActivitySchema>;
export type EdgeRefusal = z.output<typeof EdgeRefusalSchema>;
export type TopologyState = z.output<typeof TopologyStateSchema>;

const actorObservedSchema = ActorSchema.omit({ worktreeRoot: true }).strict();
Expand Down
22 changes: 22 additions & 0 deletions packages/agent-bundle/src/config/conventional-entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { existsSync, statSync } from 'node:fs';
import { resolve } from 'node:path';

const conventionalEntryExtensions = ['.ts', '.tsx'] as const;

/**
* Probe for a conventional entry source file. A leaf module so both
* config/normalize.ts and routes/graph.ts can share one rule without closing
* the discover.ts -> routes/graph.ts -> normalize.ts -> discover.ts cycle.
*/
export const conventionalEntryAt = (root: string, ...segments: string[]): string | undefined => {
const stem = resolve(root, ...segments);
for (const extension of conventionalEntryExtensions) {
const candidate = `${stem}${extension}`;
try {
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
} catch {
// A racing deletion means the convention does not apply.
}
}
return undefined;
};
16 changes: 1 addition & 15 deletions packages/agent-bundle/src/config/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '../core/runtime.ts';
import { developmentFallbackVersion, snapshotPackageIdentity } from '../core/project-context.ts';
import { isRecord } from '../core/strict-json.ts';
import { conventionalEntryAt } from './conventional-entry.ts';
import {
canonicalHookEvents,
isPrebuiltEntryInput,
Expand Down Expand Up @@ -155,21 +156,6 @@ const mcpEntryName = (name: string): string => {
/** Anchored alias contract for generated target-local MCP entry modules. */
export const mcpEntryAliasPattern = /^mcp\/(mcp-[a-z0-9-]+-[a-f\d]{8}\.mjs)$/u;

const conventionalEntryExtensions = ['.ts', '.tsx'] as const;

const conventionalEntryAt = (root: string, ...segments: string[]): string | undefined => {
const stem = resolve(root, ...segments);
for (const extension of conventionalEntryExtensions) {
const candidate = `${stem}${extension}`;
try {
if (existsSync(candidate) && statSync(candidate).isFile()) return candidate;
} catch {
// A racing deletion means the convention does not apply.
}
}
return undefined;
};

/**
* The `src/mcp/<server-id>.ts` convention: the stdio entry for a declared MCP
* server that names no entry, command, or url. Config always wins — an
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-bundle/src/contracts/dev-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
* vocabulary (kinds, levels, producers) is dependency-free runtime code;
* the record shapes are type-only because the log service touches Node.
*/
export { devLogKinds, devLogLevels, devLogProducers, hasControlOrSeparators } from '../dev/logs/dev-log-kinds.ts';
export { devLogKinds, devLogLevels, devLogProducers, hasControlOrSeparators, safeContextKeys } from '../dev/logs/dev-log-kinds.ts';
export type { DevLogMessage, DevLogRecord, DevLogReplay, DevLogReplayGap } from '../dev/logs/dev-log-service.ts';
Loading
Loading