Merge branch 'master' into feat/read-image-context

# Conflicts:
#	docs/module-graph.i18n.yaml
#	docs/module-graph.md
#	docs/module-graph.zh.md
This commit is contained in:
creatixchu
2026-08-10 19:01:47 +08:00
824 changed files with 11131 additions and 2566 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# AGENTS.md — Repository scripts
Gate scripts invoke pnpm shell-free, normalize repository-relative glob paths to `/` at ingestion, and keep platform adaptation at the owning gate boundary instead of a shared platform layer.
Gate scripts invoke pnpm shell-free, normalize repository-relative glob paths to `/` at ingestion, and keep platform adaptation in the gate that needs it instead of a shared platform layer.
+1 -1
View File
@@ -4,7 +4,7 @@ import { createHash } from 'node:crypto'
import { basename } from 'node:path'
import { AGENT_NOTE_CLASSES } from './agent-note-tree.ts'
/** Versioned shape of the frozen-content manifest. */
/** Versioned fields in the frozen-content manifest. */
export interface ArchiveManifest {
version: 1
files: Readonly<Record<string, string>>
+6 -2
View File
@@ -119,12 +119,16 @@ function workspaceManifests(): WorkspaceManifest[] {
}
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
// Profile bundles publish their dsh.bundle.patch layer beside the lib.
'@deepseek-ai/dsh-base': ['cordis.patch.yml'],
// Profile bundles publish their dsh.bundle.patch layer beside the lib;
// dsh-base also ships the win32 shell platform layer the launcher reads.
'@deepseek-ai/dsh-base': ['cordis.patch.yml', 'windows.cordis.patch.yml'],
'@deepseek-ai/dsh-web-app': ['cordis.patch.yml'],
'@deepseek-ai/dsh-headless': ['cordis.patch.yml'],
'@deepseek-ai/dsh-client-ui-theme': ['lib/styles'],
'@deepseek-ai/dsh-helper': ['lib/assets'],
// The argv-prefix runner entry ships beside the lib as its own bundle;
// sandbox-local resolves it through the package's ./runner export.
'@deepseek-ai/dsh-sandbox-windows-acl': ['lib/runner.js'],
'@deepseek-ai/dsh-skill-badge': ['assets'],
'@deepseek-ai/dsh-subprocess-local': ['scripts/ensure-spawn-helper.mjs'],
'@deepseek-ai/dsh-scripts': [
+1 -1
View File
@@ -1,5 +1,5 @@
/**
* Pins shared client-bundle preset contracts: the module-edge purity gate and
* Pins shared client-bundle preset rules: the module-edge purity gate and
* the physical watch dependencies hidden behind virtual CSS Modules.
*/
import { fileURLToPath } from 'node:url'
+1 -1
View File
@@ -81,7 +81,7 @@ export function contextKeyMap(body: ts.ModuleBlock, sf: ts.SourceFile): Map<stri
/**
* Every event name a `declare module 'cordis'` Events merge declares in one
* module body. Names are the literal member keys (`'agent/created'`), read
* from method and property members alike so a declaration shape the projector
* from method and property members alike so a declaration form the projector
* would reject still enters the exhaustiveness scan.
* @param body - The cordis module augmentation block.
* @param sf - Owning source file (for computed-name text extraction).
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* Heavy suites the coverage aggregate runs uninstrumented in a parallel gate.
* Membership contract: a suite qualifies only when every coverage-measured
* Membership rule: a suite qualifies only when every coverage-measured
* file it executes in-process (`coverage.include` spans package src trees;
* typert generator src is threshold-excluded in vitest.config.ts) is already
* fully covered by other suites, so removing it from the instrumented run
+1 -1
View File
@@ -1,6 +1,6 @@
/** Map one workspace source alias target to its declaration-build target. */
export function builtDeclarationPath(candidate: string): string {
// Two workspace shapes exist: whole-package entries end in /src, subpath
// Two workspace path forms exist: whole-package entries end in /src, subpath
// wildcards (apiproxy's browser-safe /api and /client channels) in /src/*.
if (candidate.endsWith('/src')) {
return `${candidate.slice(0, -'/src'.length)}/lib/types`
+1 -1
View File
@@ -221,7 +221,7 @@ const { primary: all, derivatives } = partitionPairedMarkdownDerivatives(
const checked = all.filter(b => b.kind === 'check')
const ignored = all.filter(b => b.kind === 'ignore')
// Only compile-eligible fences belong in the opt-out ratio; every other skipped
// kind has an independent verifier named in BlockKind's contract above.
// kind has an independent verifier named in the BlockKind rules above.
const ratioDenominator = checked.length + ignored.length
if (checked.length === 0) {
+6 -6
View File
@@ -2,7 +2,7 @@
* Generate `docs/config-catalog.md` from package entry points, config types,
* JSDoc, and static Schemastery schemas. Every package must classify, referenced
* types must resolve without collisions, and every enumerable schema path must
* exist on the declared config type. External and dynamic shapes stay unknown;
* exist on the declared config type. External and dynamic types stay unknown;
* declared runtime-only fields need not appear in the schema. `--check` verifies
* the committed artifact.
*/
@@ -220,7 +220,7 @@ interface World {
}
/** How a schema key path fared against the declared config type: definitely
* present, definitely absent, or crossing a shape the walk cannot enumerate
* present, definitely absent, or crossing a type the walk cannot enumerate
* (only `missing` is a violation — `unknown` must never mis-report). */
type PathLookup = 'found' | 'missing' | 'unknown'
@@ -307,7 +307,7 @@ const PASSTHROUGH_WRAPPERS = new Set(['Partial', 'Required', 'Readonly', 'NonNul
/**
* Walk a schema key path against a declared type. This is a PRESENCE check,
* not a shape check: it answers "does the declared config type have a member
* not a runtime value check: it answers "does the declared config type have a member
* here", resolving interfaces (heritage included), type aliases, literals,
* intersections, unions, arrays, indexed access, pass-through utility
* wrappers, and type references across package-local and workspace imports.
@@ -412,7 +412,7 @@ function unwrapExpr(expr: ts.Expression): ts.Expression {
* Statically walk a schemastery schema expression to its key paths plus the
* packages whose schemas an intersect composes. A key path is the top-level
* key or a nested path through object/array compositions (`agents[].id`).
* Handles the shapes the repo declares — `z.object({…})` (possibly behind
* Handles the declaration forms the repo uses — `z.object({…})` (possibly behind
* chained calls) and `z.intersect([X.Config, …])` — and hard-errors on
* anything else, so a schema the walk cannot see fails the gate instead of
* silently thinning it. Nested values that are neither `object` nor `array`
@@ -521,7 +521,7 @@ function findSchemaExpr(ctx: FileCtx, pluginClass: ts.ClassDeclaration | null):
function findInject(ctx: FileCtx, pluginClass: ts.ClassDeclaration | null, violations: string[]): string[] {
const fromArray = (expr: ts.Expression, where: string): string[] => {
if (!ts.isArrayLiteralExpression(expr)) {
violations.push(`${where}: inject is not a plain string-array literal; teach the generator the new shape.`)
violations.push(`${where}: inject is not a plain string-array literal; teach the generator the new declaration form.`)
return []
}
return expr.elements.map(el => ts.isStringLiteral(el) ? el.text : el.getText(ctx.sf))
@@ -727,7 +727,7 @@ export function collectConfigCatalog(scanRoot: string = root): CatalogEntry[] {
}
// Fold composed schemas' key paths in, then check each path against the type.
// Only a definite miss fails; shapes the walk cannot enumerate stay unknown.
// Only a definite miss fails; types the walk cannot enumerate stay unknown.
const byName = new Map(entries.map(e => [e.pkg, e]))
for (const entry of entries) {
if (entry.kind !== 'config' || entry.schemaKeys === null || entry.schemaKeys === undefined) continue
+9 -8
View File
@@ -111,16 +111,16 @@ export const SERVICE_PAGE: Record<string, string> = {
*/
export const SERVICE_WALK_EXEMPTIONS: Record<string, string> = {
agent: 'not a service: the DX accessor field on Agent.ctx (root accessor defaulting to undefined) — docs/subsystems/core.md owns the Agent handle',
configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns the launcher contract',
launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns the launcher contract',
configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns this launcher contract',
launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns this launcher contract',
dshHomePath: 'not a service: boot-provided root accessor function (typeof dshHomePath | undefined) for Loader !!js config expressions — packages/boot/app-boot/README.md owns the boot contract',
headlessIo: 'not a service: launcher-provided root accessor value (HeadlessIo | undefined) for the headless bundle runner — packages/bundle/headless/README.md owns the launcher contract',
launcherEnvironment: 'not a service: launcher-provided root accessor value (EnvironmentSnapshot | undefined) — packages/util/environment/README.md owns the launcher contract',
headlessIo: 'not a service: launcher-provided root accessor value (HeadlessIo | undefined) for the headless bundle runner — packages/bundle/headless/README.md owns this launcher contract',
launcherEnvironment: 'not a service: launcher-provided root accessor value (EnvironmentSnapshot | undefined) — packages/util/environment/README.md owns this launcher contract',
lsp: 'interface-typed (LspService); implementing class Lsp is not the declared type name — packages/lsp/lsp/README.md owns the surface',
apiProxy: 'interface-typed (ApiProxy) with the class in api-proxy.ts, not index.ts — packages/host/apiproxy/README.md owns the surface',
appShell: 'client-side interface-typed browser service — packages/client/web/README.md owns the surface',
connection: 'client-side interface-typed browser service — packages/client/connection/README.md owns the surface',
chatFileMentions: 'client-side slot-contract accessor (ChatFileMentions) — packages/client/ui-conversation/README.md owns the surface',
chatFileMentions: 'client-side slot-contract accessor (ChatFileMentions) — packages/client/ui-conversation/README.md owns the API',
command: 'client-side interface-typed browser service — packages/client/ui-command/README.md owns the surface',
conversation: 'client-side interface-typed browser service — packages/client/ui-conversation/README.md owns the surface',
conversationEvents: 'client-side interface-typed registry — packages/client/runtime/README.md owns the surface',
@@ -181,6 +181,7 @@ export const EVENT_WALK_EXEMPTIONS: Record<string, string> = {
'credentials/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'locale/change': 'client-face locale switch signal — packages/client/locale/README.md owns the surface',
'models/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'session/preset-changed': 'client-face per-session catalog invalidation signal — packages/client/runtime/README.md owns the surface',
'settings/changed': 'client-face registry invalidation signal — packages/client/runtime/README.md owns the surface',
'slash/input-begin-command': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
'slash/input-consume-token': 'client-face slash-input protocol — packages/client/ui-slash/README.md owns the surface',
@@ -483,13 +484,13 @@ export const TYPE_LINK_EXEMPTIONS: Readonly<Record<string, string>> = {
'z.core.ToJSONSchemaParams': 'zod projection parameters are owned by the zod v4 API',
TypeRTDisposer: 'TypeRT lifecycle contract is owned by packages/typert/type-meta/README.md',
InvokeRemoteRequest: 'gateway invocation contract is owned by packages/api/gateway/README.md',
LocaleDict: 'service-local dictionary shape is owned by packages/client/i18n/src/index.ts',
LocaleDict: 'service-local dictionary fields are owned by packages/client/i18n/src/index.ts',
ThemeTokens: 'service-local token dictionary is owned by packages/client/ui-theme/src/index.ts',
Translate: 'service-local bound translator is owned by packages/client/i18n/src/index.ts',
WebUpgradeRoute:
'upgrade route registration contract is owned by packages/host/webserver/src/index.ts',
InvariantRegistration: 'service-local lifecycle handle is owned by packages/support/invariants/README.md',
KnobState: 'projection unit state shape is owned by packages/interaction/permission/README.md',
KnobState: 'projection unit state fields are owned by packages/interaction/permission/README.md',
PermissionSelect: 'permissions projection payload is owned by packages/interaction/permission/src/types.ts',
PromptAssembly: 'assembly result is owned by packages/core/system-prompt/README.md',
Sandbox: 'external E2B SDK handle is owned by packages/e2b/e2b/README.md',
@@ -752,7 +753,7 @@ export function maybeRecordPair(pageRel: string, before: Map<string, Buffer>, sc
// after review, never silently by regeneration.
return false
}
// The record must be exactly the well-formed two-entry shape for THIS pair;
// The record must contain exactly the two valid entries for THIS pair;
// a malformed or renamed-key sidecar is the pairing gate's problem to
// report, never something regeneration silently repairs into validity.
const recorded = parsePairMeta(meta)
+24 -13
View File
@@ -268,7 +268,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Human question/answer seam',
mode: 'seam',
consumers: ['tool-ask-user'],
note: 'UI front doors provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
note: 'UI front ends provide the active human-answer provider; tool-ask-user pauses a tool call on the provider-neutral ask() promise.',
},
{
key: 'planMode',
@@ -330,7 +330,7 @@ const SERVICE_ROLES: ServiceRole[] = [
title: 'Default Agent model selection',
mode: 'core',
consumers: ['headless', 'host-apiproxy'],
note: 'Layers the default ModelSelection through settings so direct and Host-backed Agent front doors share one state owner.',
note: 'Layers the default ModelSelection through settings so direct and Host-backed Agent entry points share one state owner.',
},
{
key: 'agentLoop',
@@ -521,7 +521,7 @@ const SERVICE_ROLES: ServiceRole[] = [
mode: 'seam',
implementations: ['workflow-workerthread'],
consumers: ['tool-workflow', 'tool-ralph'],
note: 'One engine per context (bash shape, no named-provider registry); the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents.',
note: 'One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents.',
},
]
@@ -692,7 +692,7 @@ function renderAppExpansion(lines: string[], appNode: string, pluginName: string
lines.push(` ${appNode} --> ${agentCore}["@deepseek-ai/dsh-agent-spine-demo"]`)
lines.push(` ${appNode} --> ${jsonl}["@deepseek-ai/dsh-session-persistence-jsonl"]`)
if (pluginName === '@deepseek-ai/dsh-acp-demo') {
lines.push(` ${appNode} --> ${nodeId('frontdoor', 'acp')}["@deepseek-ai/dsh-acp<br/>automation-only JSON-RPC stdio<br/>fresh sessions created by client"]`)
lines.push(` ${appNode} --> ${nodeId('entrypoint', 'acp')}["@deepseek-ai/dsh-acp<br/>automation-only JSON-RPC stdio<br/>fresh sessions created by client"]`)
}
lines.push(
` ${agentCore} --> ${nodeId('spine', 'llm')}["ctx.llm"]`,
@@ -744,7 +744,18 @@ type CallSiteIndex = Map<ts.SignatureDeclaration | ts.JSDocSignature, ts.CallExp
*/
const EVENT_API_METHODS = new Set(['on', 'once', 'emit', 'parallel', 'serial', 'waterfall', 'dispatch'])
/** Collect event dispatch/listener relations from real cross-file receiver types. */
/**
* Collect event dispatch/listener relations from real cross-file receiver types.
*
* TODO: the program is seeded from the host aggregate alone (ts-project.ts
* documents why: one program cannot hold both faces' Context merges), so a
* Client package enters only when a host file imports it. Client-face
* listeners on client-face events are therefore under-reported —
* `connection/reset` omits `ui-skill`/`ui-agent-preset`, `models/changed`
* omits `ui-model`, `session/preset-changed` omits `ui-skill`. Closing it
* needs a second Client program whose relations merge into these, not a
* wider seed.
*/
export class EventRelationCollector {
private readonly relations = new Map<string, EventRelation>()
private readonly fileCallSites = new Map<ts.SourceFile, CallSiteIndex>()
@@ -804,7 +815,7 @@ export class EventRelationCollector {
* Return every indexed call resolving to one local helper declaration.
* Fast path: when every same-file reference to the non-exported helper is
* provably a direct callee, module scoping confines all of its calls to that
* file, so only that file is indexed. Any other reference shape may alias
* file, so only that file is indexed. Any other reference form may alias
* the function value outward, so the original full package-source index
* decides instead.
*/
@@ -1144,7 +1155,7 @@ function renderEventRelations(pkgs: Pkg[], events: readonly EventEntry[]): strin
lines.push(`| \`${event.name}\` | \`${event.mode}\` | ${sourceLink(event.source)} | ${relationPackages(relation.dispatchers, pkgsByShort)} | ${listenerPackages(relation.listeners, pkgsByShort)} |`)
}
// Every declared event needs a dispatcher: zero means dead vocabulary or an
// unrecognized semantic dispatch shape. Listener-free extension points remain
// unrecognized semantic dispatch form. Listener-free extension points remain
// valid. Client-declared events are exempt: the relation scan seeds the HOST
// aggregate program only (host+client cannot share one program — the cordis
// Context merges collide), so client dispatch sites are structurally
@@ -1157,8 +1168,8 @@ function renderEventRelations(pkgs: Pkg[], events: readonly EventEntry[]): strin
if (undispatched.length > 0) {
throw new Error(
`event-producer-consumer matrix: no dispatcher found for declared event${undispatched.length > 1 ? 's' : ''} `
+ `${undispatched.map(name => `"${name}"`).join(', ')} — dead vocabulary, or a dispatch shape the semantic scan misses `
+ '(teach scripts/gen-doc-graphs.ts the shape)',
+ `${undispatched.map(name => `"${name}"`).join(', ')} — dead vocabulary, or a dispatch form the semantic scan misses `
+ '(teach scripts/gen-doc-graphs.ts that form)',
)
}
const declared = new Set(events.map(event => event.name))
@@ -1251,9 +1262,9 @@ function renderLifecycle(): string {
'',
'`dsh-compact-basic` uses `agent/pre-step` for pressure before request derivation and `agent/request-error` only for canonical context overflow. Once either trigger qualifies, optional tool-result pruning runs before summary selection. Recovery works between the closed failed step and failed turn close, and opens a fresh retry turn only when pruning or summarization advances the surface replacement generation; otherwise the original request error remains authoritative.',
'',
'The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later boundary claims their next-step batch.',
'The returned `agent/pre-step` decision is authoritative; listeners wrapping `next()` preserve downstream messages unless replacement is intentional. Steering and injected context pass through the same waterfall after a later claim operation takes their next-step batch.',
'',
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request shaping, steering, continuation, and errors.',
'SDK users that need replayable transcript data should consume `session/event`; `agent/*` is the live coordination surface for queue/status, prompt interception, request construction, steering, continuation, and errors.',
'',
...maintenanceFooter(maintenance),
].join('\n')
@@ -1263,7 +1274,7 @@ function renderToolPipeline(): string {
const maintenance = 'curated Mermaid flow; exact tool schemas and event signatures live in generated catalogs'
return [
...generatedHeader('Tool Execution Pipeline'),
'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering fit without changing the loop. The transformable extension points are the `tools/pre-execute`, `tools/execute`, and `tools/post-execute` waterfalls; monotonic guards, definition-owned `finalizeContent`, and `tools/result` are the owner-enforced boundaries around them.',
'This graph shows where policy, hooks, sandboxing, filesystem guards, result rewriting, final-outcome observation, and UI rendering run without changing the loop. The `tools/pre-execute` waterfall runs first, monotonic guards run next, and the `tools/execute` and `tools/post-execute` waterfalls follow; the three waterfalls may transform a call. Definition-owned `finalizeContent` and `tools/result` run afterward.',
'',
'```mermaid',
'flowchart TD',
@@ -1369,7 +1380,7 @@ function renderIndex(docs: GraphDoc[]): string {
const maintenance = 'mixed: each linked page declares generated, hybrid, or curated mode'
return [
...generatedHeader('Documentation Graph Index'),
'These diagrams are the relationship layer above the generated catalogs. Use them to navigate package topology, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type shapes still live in the [subsystem pages](subsystems/core.md) (types + the generated `cordis-surface` regions) and [tool-catalog.md](tool-catalog.md).',
'These diagrams show relationships that the generated catalogs do not. Use them to find package relationships, capability seams, event flow, model-facing tools, app composition, and runtime lifecycle paths. Exact signatures and type definitions still live in the [subsystem pages](subsystems/core.md) (types + the generated `cordis-surface` regions) and [tool-catalog.md](tool-catalog.md).',
'',
'The process decision behind this index is recorded in [the documentation graph Agent Note](../.agents/notes/archived/process/2026-07-03-documentation-graph-atlas.md).',
'',
+2 -2
View File
@@ -139,7 +139,7 @@ describe('parseVendoredRows', () => {
expect(rows.every(row => /^https:\/\/\S+$/.test(row.upstream))).toBe(true)
})
it('yields nothing when the table shape changes, so the generator fails loud', () => {
it('yields nothing when the table columns change, so the generator fails loud', () => {
expect(parseVendoredRows('| `cordis/` | cordis | 4.0.0 | https://example.com | `abc123` |\n')).toEqual([])
})
@@ -215,7 +215,7 @@ describe('parsePyprojectRequirements', () => {
].join('\n'))).toEqual(['pydantic', 'tomli', 'pytest'])
})
it('accepts dependency-group includes and rejects unsupported requirement shapes', () => {
it('accepts dependency-group includes and rejects unsupported requirement forms', () => {
expect(parsePyprojectRequirements('[dependency-groups]\nbase = ["pytest"]\nall = [{ include-group = "base" }]\n'))
.toEqual(['pytest'])
expect(() => parsePyprojectRequirements('[project]\ndependencies = "pytest"\n')).toThrow(/must be an array/)
+2 -2
View File
@@ -475,7 +475,7 @@ function collectPythonRequirementArray(
}
}
/** Read an optional TOML table and reject a present value of another shape. */
/** Read an optional TOML table and reject a present non-table value. */
function optionalTomlTable(value: TomlValueWithoutBigInt | undefined, location: string): TomlTableWithoutBigInt | undefined {
if (value === undefined || isTomlTable(value)) return value
throw new Error(`gen-third-party-notices: ${location} must be a table.`)
@@ -487,7 +487,7 @@ function optionalTomlTable(value: TomlValueWithoutBigInt | undefined, location:
* `[build-system]`, `dependencies` under `[project]`, and every key under
* `[project.optional-dependencies]` and `[dependency-groups]`. A TOML parser
* owns comments, quoted keys, escapes, and array boundaries; unsupported
* requirement shapes fail instead of disappearing from the notices.
* requirement forms fail instead of disappearing from the notices.
* @param text - the complete `pyproject.toml` contents.
* @returns the local project name and declared requirement names.
*/
+1 -1
View File
@@ -7,7 +7,7 @@
* the narrowest safe granularity — code-fence-only splice, changed
* Markdown units, heading sections, whole document — and `--apply` writes
* the computed counterpart for pairs whose change is code-fence-only.
* The briefing contract lives in `scripts/translation-brief.ts`; the
* The briefing rules live in `scripts/translation-brief.ts`; the
* consuming workflow is `.agents/skills/dsh-translate-docs/SKILL.md`.
*/
-78
View File
@@ -1,78 +0,0 @@
// Regression drive for the unified hero composer:
// cold start with zero workspaces -> create a workspace -> type. Asserts the
// composer textarea is the SAME DOM node across the disabled->live flip (a
// remount drops the __heroMark marker property) — the session-maybe
// composer.bar contract.
//
// Prereqs: `pnpm run build`, then a fresh server against empty state:
// rm -rf .storages && DSH_HOME=$(mktemp -d) node --experimental-transform-types \
// --import ./scripts/tspath-loader.ts apps/cli/src/bin.ts web --port 44285 \
// --workspace-root $(mktemp -d)
// Run: node scripts/hero-composer-dom-continuity.mjs
// (BASE_URL overrides the target; screenshots land in .artifacts/.)
import { createRequire } from 'node:module'
// playwright is a devDependency of apps/web only — resolve through its tree.
const require = createRequire(new URL('../apps/web/package.json', import.meta.url))
const { chromium } = require('playwright')
const BASE = process.env.BASE_URL ?? 'http://127.0.0.1:44285'
const SHOTS = new URL('../.artifacts/screenshots/0729-0357-hero-unify/', import.meta.url).pathname
const browser = await chromium.launch()
const page = await browser.newPage({ viewport: { width: 1280, height: 800 } })
page.on('console', msg => { if (msg.type() === 'error') console.log('[console.error]', msg.text()) })
page.on('pageerror', err => { console.log('[pageerror]', err.message) })
await page.goto(BASE)
await page.waitForSelector('textarea', { timeout: 20000 })
await page.screenshot({ path: SHOTS + '01-cold-start.png' })
const initial = await page.evaluate(() => {
const boxes = [...document.querySelectorAll('textarea')]
boxes.forEach((b, i) => { b.__heroMark = 'alive-' + i })
return boxes.map(b => ({ disabled: b.disabled, placeholder: b.placeholder }))
})
console.log('cold-start textareas:', JSON.stringify(initial))
// Open the picker and create a workspace by name (typed-input flow). The name
// must be unique per registry; keystrokes go through pressSequentially so the
// dialog's React onChange enables the submit button.
await page.getByRole('button', { name: 'Choose workspace' }).click()
await page.getByText('Create a new workspace').click()
await page.screenshot({ path: SHOTS + '03-create-form.png' })
const nameBox = page.getByPlaceholder('Workspace name')
await nameBox.click()
const wsName = 'proj-' + Date.now().toString(36)
await nameBox.pressSequentially(wsName, { delay: 30 })
await page.locator('button:text-is("Create workspace")').click()
// Wait for the composer to go live (placeholder flips, textarea enabled).
await page.waitForFunction(() => {
const box = document.querySelector('textarea')
return box !== null && !box.disabled
}, { timeout: 20000 })
await page.screenshot({ path: SHOTS + '04-live.png' })
const after = await page.evaluate(() => {
const boxes = [...document.querySelectorAll('textarea')]
return boxes.map(b => ({
mark: b.__heroMark ?? 'REMOUNTED',
disabled: b.disabled,
placeholder: b.placeholder,
}))
})
console.log('post-pick textareas:', JSON.stringify(after))
// Type into the live composer.
await page.locator('textarea').first().fill('hello from acceptance run')
const typed = await page.evaluate(() => document.querySelector('textarea')?.value)
console.log('typed value:', JSON.stringify(typed))
await page.screenshot({ path: SHOTS + '05-typed.png' })
const survived = after.length === 1 && after[0].mark === 'alive-0'
console.log(survived
? 'DOM-CONTINUITY: PASS (same textarea node across cold-start -> live)'
: 'DOM-CONTINUITY: FAIL ' + JSON.stringify(after))
await browser.close()
process.exit(survived && typed === 'hello from acceptance run' ? 0 : 1)
+2 -2
View File
@@ -15,7 +15,7 @@ interface Profile {
// A one-time audit against eslint.config.mjs blob 696b08282885296830189fdafe7051a356806fc2
// mapped @typescript-eslint/* to typescript/* and four extension rules to their
// Oxlint core equivalents. These fingerprints pin the resulting repository
// contract; they do not re-evaluate that deleted baseline or track its preset.
// snapshot; they do not re-evaluate that deleted baseline or track its preset.
const profiles = {
source: {
count: 88,
@@ -84,7 +84,7 @@ describe('Oxlint repository rule fingerprint', () => {
}
const overrides: readonly unknown[] = parsed.overrides
it('pins the complete override shape', () => {
it('pins every override field', () => {
expect(overrides).toHaveLength(8)
})
+2 -2
View File
@@ -19,7 +19,7 @@ interface PackageManifest {
devDependencies?: Record<string, string>
}
/** One package and the files participating in its invariant publication contract. */
/** One package and the files participating in its invariant publication rules. */
export interface PackageInvariantOwner {
readonly dir: string
readonly manifestPath: string
@@ -53,7 +53,7 @@ export function packageInvariantOwners(root: string): PackageInvariantOwner[] {
})
}
/** Return all violations of the package-invariant companion contract. */
/** Return all violations of the package-invariant companion rules. */
export function collectPackageInvariantViolations(root: string): PackageInvariantViolation[] {
const violations: PackageInvariantViolation[] = []
for (const owner of packageInvariantOwners(root)) {
+2 -2
View File
@@ -341,7 +341,7 @@ function nodeCompatSmokeGates(options: { cliSmoke?: boolean } = {}): Gate[] {
return gates
}
/** Active Node major used to scope version-specific compatibility contracts. */
/** Active Node major used to select version-specific compatibility checks. */
function runningNodeMajor(): number {
const major = Number.parseInt(process.versions.node.split('.')[0] ?? '', 10)
if (!Number.isSafeInteger(major)) {
@@ -473,7 +473,7 @@ function lintGate(options: { needs?: string[] } = {}): Gate {
// The heavy suites run uninstrumented beside the thresholded gate: their
// compiler- and subprocess-bound fixtures pay a multiple of their runtime
// under v8 instrumentation while contributing nothing the thresholds need
// (membership contract in scripts/coverage-exempt.ts).
// (membership rules in scripts/coverage-exempt.ts).
//
// DSH_COVERAGE_MAX_WORKERS is the lane's worker budget, so the two parallel
// gates split it instead of each claiming it whole (the failover pool's
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -137,7 +137,7 @@ describe('global test invariant host', () => {
.toEqual(Object.keys(testInvariantCompanions).sort())
})
it('loads and executes every source companion through the real Loader shape', async () => {
it('loads and executes every source companion through the real Loader setup', async () => {
const owners = new Map(packageInvariantOwners(process.cwd()).map(owner => [owner.sourcePath, owner.packageName]))
const registrations = new Map<string, string>()
const loader = Object.create(Loader.prototype) as Loader
+1 -1
View File
@@ -24,7 +24,7 @@ declare global {
}
}
/** Loader-safe shape shared by every package invariant companion. */
/** Loader-safe exports shared by every package invariant companion. */
export interface TestInvariantCompanion {
readonly name: string
readonly inject: readonly string[]
+1 -1
View File
@@ -54,7 +54,7 @@ export interface GitIndexBlob {
* @param root - Repository root.
* @param path - Repository-relative path.
* @returns The stage-zero blob, or `undefined` when the path is absent.
* @throws Error when the path is unmerged or has an invalid index shape.
* @throws Error when the path is unmerged or its index entries are not a valid merge state.
*/
export function readGitIndexBlob(root: string, path: string): GitIndexBlob | undefined {
const output = runGit(
+2 -2
View File
@@ -80,7 +80,7 @@ const PAIR_META_LINE = /^([^:#]+\.md): ([0-9a-f]{40})$/
/**
* Parse a `foo.i18n.yaml` consistency record into basename → recorded blob
* hash, or undefined when any non-comment line deviates from the exact
* `<basename>.md: <40-hex>` shape or repeats a key. Consumers must
* `<basename>.md: <40-hex>` format or repeats a key. Consumers must
* additionally require exactly the two expected basenames — a renamed key is
* a malformed record, never a silently-missing entry.
* @param content - Sidecar file text.
@@ -118,7 +118,7 @@ export function renderPairMeta(source: string, sourceHash: string, zh: string, z
].join('\n')
}
/** Validated shape of `scripts/translation-pairing.manifest.json`. */
/** Validated fields of `scripts/translation-pairing.manifest.json`. */
export interface TranslationPairingManifest {
/** Source documents exempt from pairing because they are generated, instructional, or bilingual by construction. */
excluded: string[]
+2 -2
View File
@@ -21,7 +21,7 @@ const retainedExamples = [
['### Stiff passive voice → Active and natural', 'a green gate means the pair was confirmed consistent at these exact contents, not that the confirmation was sound.', '门禁通过意味着这组文档在当前内容上的一致性得到了确认,不代表确认本身正确可靠。'],
['### Invented word → Natural expression', 'A sidecar record of both blob hashes makes consistency checkable', '伴随记录保存两侧 blob hash,使一致性可检查'],
['### Em-dash → Colon/period', 'FIXME — an issue that should block a new release.', 'FIXME:应当阻塞新版本发布的问题。'],
['### Overly literal → Meaningful rendering', 'awkward phrasing is easier to hear without the source anchoring you', '不对照原文时,更容易察觉别扭的表达'],
['### Overly literal → Meaningful rendering', 'awkward phrasing is easier to notice when you read the translation without comparing it with the source', '不对照原文阅读译文时,更容易察觉别扭的表达'],
['### Terminology — do not translate what should be kept in English', 'typed service seams, and explicit extension points', '类型化的服务 seam 与显式扩展点'],
['### Slang/jargon → Professional phrasing', 'The committed agent workflow lives in .agents/skills/dsh-translate-docs', '仓库内置的 agent 工作流见 .agents/skills/dsh-translate-docs'],
['### "For humans" — translate the intent, not the word', 'For humans, start with the development guide', '面向开发者:请先阅读开发指南'],
@@ -44,7 +44,7 @@ describe('translation prompt rendering', () => {
expect(zh).toContain('from Chinese to English')
})
it('retains every v4 embedded example', () => {
it('contains every embedded example', () => {
for (const example of retainedExamples) {
for (const fragment of example) expect(document).toContain(fragment)
}
+2 -2
View File
@@ -169,7 +169,7 @@ function unescapeResponseBody(value: string): string {
}).join('\n')
}
/** Serialize a response in the exact escaped three-section shape the prompt requests. */
/** Serialize a response in the exact escaped three-section format the prompt requests. */
export function renderTranslationResponse(response: TranslationResponse): string {
return RESPONSE_SECTIONS.map(section => `<${section}>\n${escapeResponseBody(response[section])}\n</${section}>`).join('\n\n')
}
@@ -178,7 +178,7 @@ export function renderTranslationResponse(response: TranslationResponse): string
* Parse the three-section response. Sections must each appear exactly once
* and in order; escaped delimiter lines in Markdown bodies are restored.
* A fenced ```xml wrapper around the whole response is tolerated, matching
* the shape some models echo back from the prompt's own example.
* the wrapper some models copy from the prompt's own example.
*/
export function parseTranslationResponse(text: string): TranslationResponse {
let body = text.trim()
+1 -1
View File
@@ -1,6 +1,6 @@
/**
* Enforce Agent Note lifecycle/class paths and dated filenames. Structural rules
* are shared with `agent-note-tree.ts`; the closed classification contract lives
* are shared with `agent-note-tree.ts`; the closed classification rules live
* in `.agents/notes/README.md`.
*/
+1 -1
View File
@@ -9,7 +9,7 @@ import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { agentNoteRoot, walkAgentNoteTree } from './agent-note-tree.ts'
/** The date the format contract landed; the grandfather comment is valid only before it. */
/** The date these format rules took effect; the grandfather comment is valid only before it. */
const FORMAT_ADOPTED = '2026-07-05'
/** The exact comment a pre-format Agent Note carries in place of `## Alternatives considered`. */
+1 -1
View File
@@ -99,7 +99,7 @@ if (!writeMode) {
}
if (errors.length > 0) {
console.error('verify-archived-agent-notes: archive contract violated:')
console.error('verify-archived-agent-notes: archive rules violated:')
for (const error of errors) console.error(` ${error}`)
process.exit(1)
}
+1 -1
View File
@@ -19,7 +19,7 @@ const SHIPPED_CONFIG_GLOBS = [
'python/*/src/**/cordis.yml',
]
/** Ordinary single-line forms this narrow source-shape check rejects; not full YAML analysis. */
/** Ordinary single-line configuration forms this source check rejects; not full YAML analysis. */
const INLINE_DENY = /^\s*(apiKey|baseURL|apiKeyEnv|authToken|headers)\s*:\s*!!js\b/
/** Return every forbidden inline environment form in shipped configuration. */
+3 -3
View File
@@ -75,9 +75,9 @@ function unwrapExpression(e: ts.Expression): ts.Expression {
/**
* Classify inline callable annotations. Mixed callable literals fail closed;
* other annotations are ordinary value shapes.
* other annotations are ordinary value types.
* @param type - the declarator's type annotation.
* @returns the signature to check, 'refuse' for an unclassifiable callable literal, or null for a non-callable shape.
* @returns the signature to check, 'refuse' for an unclassifiable callable literal, or null for a non-callable type.
*/
function callableAnnotation(type: ts.TypeNode): ts.SignatureDeclarationBase | 'refuse' | null {
if (ts.isFunctionTypeNode(type)) return type
@@ -446,7 +446,7 @@ function checkScope(
if (ts.isExportAssignment(stmt)) {
if (stmt.isExportEquals) {
// `export =` has no ESM consumer surface in this repo and the walk
// cannot classify its operand's shape; refuse rather than fail open.
// cannot classify its operand's type; refuse rather than fail open.
w.violations.push(`export-equals assignment (${pointer(w.rel, w.sf, stmt)}) is not a gate-supported export form; use ESM named exports.`)
continue
}
+1 -1
View File
@@ -1,4 +1,4 @@
/** Verify package-owned invariant source and publication contracts. */
/** Verify package-owned invariant source and publication rules. */
import { resolve } from 'node:path'
import {
@@ -109,6 +109,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/e2b/subprocess-e2b': { kind: 'indirect', reason: 'The remote spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/subprocess/subprocess-local': { kind: 'indirect', reason: 'The spawn backend delegates model rendering to consumer seams such as the bash executor family.' },
'packages/sandbox/sandbox-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-bash-sandbox and dsh-tool-bash.' },
'packages/sandbox/sandbox-windows-acl': { kind: 'indirect', reason: 'The provider backend delegates model rendering to the bash/pwsh sandbox executors and their tools.' },
'packages/scaffold/create-sdk': { kind: 'indirect', reason: 'The initializer only writes project files; selected runtime plugins provide the generated project model surface.' },
'packages/scaffold/helper': { kind: 'none', reason: 'The project domain edits files and registers no live agent or model surface.' },
'packages/scaffold/scripts': { kind: 'indirect', reason: 'The launcher delegates model context to the loaded project plugin tree.' },
@@ -126,6 +127,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
'packages/util/atomic-write': { kind: 'none', reason: 'Pure filesystem write primitive; registers no model surface.' },
'packages/session/session-telemetry': { kind: 'none', reason: 'The seam observes the session stream and hands redacted copies outward; it registers no model surface.' },
'packages/session/session-telemetry-otel': { kind: 'none', reason: 'The backend forwards seam records into the OTel SDK pipeline and registers no model surface.' },
'packages/session/user-id': { kind: 'none', reason: 'The shared identifier appears only in telemetry metadata and a direct human command response; it registers no model surface.' },
'packages/skill/skill': { kind: 'indirect', reason: 'The provider registry delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-badge': { kind: 'indirect', reason: 'The bundled provider delegates model rendering to dsh-tool-skill.' },
'packages/skill/skill-local': { kind: 'indirect', reason: 'The provider backend delegates model rendering to dsh-tool-skill.' },
@@ -215,12 +217,12 @@ function validateNestedVerbatim(raw: readonly string[], fragments: Set<string>):
return { blocks }
}
/** GitHub-style fragment for the simple ASCII nested titles allowed by this contract. */
/** GitHub-style fragment for the simple ASCII nested titles allowed by these rules. */
function headingFragment(title: string): string {
return title.toLowerCase().replaceAll('`', '').replaceAll(/[^a-z0-9 _-]/g, '').trim().replaceAll(/\s+/g, '-')
}
/** A direct stable system-prompt contribution, as named by the README contract. */
/** A direct stable system-prompt contribution, as named by the README rules. */
function isDirectSystemPromptSurface(title: string): boolean {
return /\bsystem prompt\b/i.test(title)
}
+1 -1
View File
@@ -291,6 +291,6 @@ if (errors.length === 0) {
process.exit(0)
}
console.error('verify-translation-pairing: bilingual pairing contract violated (see docs/i18n/README.md):')
console.error('verify-translation-pairing: bilingual pairing rules violated (see docs/i18n/README.md):')
for (const message of errors) console.error(` ${message}`)
process.exit(1)