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:
+1
-1
@@ -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.
|
||||
@@ -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>>
|
||||
|
||||
@@ -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,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'
|
||||
|
||||
@@ -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,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,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`
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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).',
|
||||
'',
|
||||
|
||||
@@ -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/)
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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`.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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[]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,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`.
|
||||
*/
|
||||
|
||||
|
||||
@@ -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`. */
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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,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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user