Merge remote-tracking branch 'origin/master' into worktree/fix-minimal-preset-prompt

This commit is contained in:
Yichen Jiang
2026-08-10 22:18:22 +08:00
1411 changed files with 4451 additions and 3320 deletions

View File

@@ -21,15 +21,15 @@ const workspaceGlobs = [
{ dir: 'apps', depth: 1 },
] as const
const vendoredPackages = new Set([
'cordis',
'cosmokit',
'schemastery',
'@cordisjs/plugin-loader',
'@cordisjs/plugin-include',
'@cordisjs/plugin-group',
'@cordisjs/plugin-timer',
'@cordisjs/plugin-hmr',
'@cordisjs/plugin-logger-console',
'@deepseek-ai/cordis',
'@deepseek-ai/cosmokit',
'@deepseek-ai/schemastery',
'@deepseek-ai/cordis-plugin-loader',
'@deepseek-ai/cordis-plugin-include',
'@deepseek-ai/cordis-plugin-group',
'@deepseek-ai/cordis-plugin-timer',
'@deepseek-ai/cordis-plugin-hmr',
'@deepseek-ai/cordis-plugin-logger-console',
])
const publicLandlockPackages = new Set([
'@deepseek-ai/node-addon-landlock-run',
@@ -275,13 +275,13 @@ function checkWorkspace({ dir, manifest }: WorkspaceManifest): string[] {
}
if (dir.startsWith('packages/') && manifest.name?.startsWith('@deepseek-ai/dsh-')) {
const peer = manifest.peerDependencies?.cordis
const dev = manifest.devDependencies?.cordis
const peer = manifest.peerDependencies?.['@deepseek-ai/cordis']
const dev = manifest.devDependencies?.['@deepseek-ai/cordis']
if (!peer) errors.push(`${label}: cordis must be a peerDependency`)
if (!dev) errors.push(`${label}: cordis must also be a devDependency`)
if (!peer) errors.push(`${label}: @deepseek-ai/cordis must be a peerDependency`)
if (!dev) errors.push(`${label}: @deepseek-ai/cordis must also be a devDependency`)
if (peer && dev && peer !== dev) {
errors.push(`${label}: cordis peer (${peer}) and dev (${dev}) ranges must match`)
errors.push(`${label}: @deepseek-ai/cordis peer (${peer}) and dev (${dev}) ranges must match`)
}
if (manifest.version !== repositoryVersion) {
errors.push(`${label}: package.json version must match root version ${repositoryVersion ?? '(missing)'}`)

View File

@@ -11,12 +11,12 @@ import ts from 'typescript'
/** Cheap textual prefilter for a cordis module merge, quote-style agnostic
* (the AST match below reads `stmt.name.text` and never sees the quotes). */
const MERGE_HEAD = /declare module ['"](?:cordis|\.\/context\.ts)['"]/
const MERGE_HEAD = /declare module ['"](?:@deepseek-ai\/cordis|\.\/context\.ts)['"]/
/**
* Parse every file matching `patterns` (repo-relative, sorted, `/`-normalized)
* that textually contains a cordis module merge, yielding one entry per merge
* BLOCK — a file may legally hold several `declare module 'cordis'` blocks
* BLOCK — a file may legally hold several `declare module '@deepseek-ai/cordis'` blocks
* (the Typert analyzer reads them all), so the exhaustiveness scan must too.
* Files without a merge are skipped.
* @param scanRoot - Repository root the patterns are resolved against.
@@ -39,14 +39,14 @@ export function contextMergeFiles(
return out
}
/** Every cordis module-merge body in `sf`: `declare module 'cordis'` (harness
/** Every cordis module-merge body in `sf`: `declare module '@deepseek-ai/cordis'` (harness
* packages) or `declare module './context.ts'` (vendor core), in source order.
* Module-local: consumers walk blocks through {@link contextMergeFiles}. */
function cordisModuleBodies(sf: ts.SourceFile): ts.ModuleBlock[] {
const bodies: ts.ModuleBlock[] = []
for (const stmt of sf.statements) {
if (!ts.isModuleDeclaration(stmt) || !ts.isStringLiteral(stmt.name)) continue
if (stmt.name.text !== 'cordis' && stmt.name.text !== './context.ts') continue
if (stmt.name.text !== '@deepseek-ai/cordis' && stmt.name.text !== './context.ts') continue
if (stmt.body && ts.isModuleBlock(stmt.body)) bodies.push(stmt.body)
}
return bodies
@@ -60,7 +60,7 @@ export function cordisModuleBody(sf: ts.SourceFile): ts.ModuleBlock | null {
}
/**
* Every `key: Type` property a `declare module 'cordis'` Context merge
* Every `key: Type` property a `declare module '@deepseek-ai/cordis'` Context merge
* declares in one module body.
* @param body - The cordis module augmentation block.
* @param sf - Owning source file (for text extraction).
@@ -79,7 +79,7 @@ export function contextKeyMap(body: ts.ModuleBlock, sf: ts.SourceFile): Map<stri
}
/**
* Every event name a `declare module 'cordis'` Events merge declares in one
* Every event name a `declare module '@deepseek-ai/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 form the projector
* would reject still enters the exhaustiveness scan.

View File

@@ -128,7 +128,7 @@ describe('cordis-walk scan reach', () => {
const dir = join(root, 'packages/client/ui-x/src/client')
mkdirSync(dir, { recursive: true })
writeFileSync(join(dir, 'index.ts'), [
"declare module 'cordis' {",
"declare module '@deepseek-ai/cordis' {",
' interface Events {',
" 'x/changed'(): void",
' }',
@@ -153,12 +153,12 @@ describe('cordis-walk scan reach', () => {
// backstop must not stop at the first one, skip the double-quoted legal
// form, or ignore .tsx sources.
writeFileSync(join(dir, 'split.ts'), [
"declare module 'cordis' {",
"declare module '@deepseek-ai/cordis' {",
' interface Context {',
' first: FirstService',
' }',
'}',
'declare module "cordis" {',
'declare module "@deepseek-ai/cordis" {',
' interface Events {',
" 'second/changed'(): void",
' }',
@@ -167,7 +167,7 @@ describe('cordis-walk scan reach', () => {
'',
].join('\n'))
writeFileSync(join(dir, 'view.tsx'), [
"declare module 'cordis' {",
"declare module '@deepseek-ai/cordis' {",
' interface Context {',
' fromTsx: TsxService',
' }',
@@ -189,7 +189,7 @@ describe('cordis-walk scan reach', () => {
it('reads string-literal and identifier member names from an Events merge', () => {
const sf = ts.createSourceFile('x.ts', [
"declare module 'cordis' {",
"declare module '@deepseek-ai/cordis' {",
' interface Events {',
" 'scope/list'(items: string[]): void",
' plain(): void',

View File

@@ -99,7 +99,7 @@ export const SERVICE_PAGE: Record<string, string> = {
/**
* Context keys declared in `interface Context` merges that the rendering
* projection cannot see, each with the reason and its documentation owner.
* The scan that enforces this list reads EVERY `declare module 'cordis'`
* The scan that enforces this list reads EVERY `declare module '@deepseek-ai/cordis'`
* Context merge under `packages/x/x/src/**` — any depth, not only root
* `index.ts` files with a same-named service class — so a new service can
* never silently join this blind spot: it either enters {@link SERVICE_PAGE}
@@ -168,7 +168,7 @@ export const EVENT_SCOPE_PAGE: Record<string, string> = {
* Event names declared in `interface Events` merges that the rendering
* projection cannot see, each with the reason and its documentation owner.
* The mirror of {@link SERVICE_WALK_EXEMPTIONS} for events: an independent
* scan reads EVERY `declare module 'cordis'` Events merge under
* scan reads EVERY `declare module '@deepseek-ai/cordis'` Events merge under
* `packages/x/x/src/**`, so a declared event either renders onto a subsystems
* page (via {@link EVENT_SCOPE_PAGE}) or names itself here — never vanishes
* silently. Keys are full event names, not scopes: client-face events share

View File

@@ -309,14 +309,14 @@ class ScopedEventGenerator {
}
}
/** Return whether an Events interface is inside declare module 'cordis'. */
/** Return whether an Events interface is inside declare module '@deepseek-ai/cordis'. */
function isCordisModuleInterface(node: ts.InterfaceDeclaration): boolean {
const block = node.parent
const declaration = block.parent
return ts.isModuleBlock(block)
&& ts.isModuleDeclaration(declaration)
&& ts.isStringLiteral(declaration.name)
&& declaration.name.text === 'cordis'
&& declaration.name.text === '@deepseek-ai/cordis'
}
/** Return whether a parameter is the explicit TypeScript this receiver. */

View File

@@ -134,13 +134,17 @@ describe('parseVendoredRows', () => {
const rows = parseVendoredRows(readFileSync(resolve(root, 'vendor/README.md'), 'utf8'))
expect(rows.length).toBeGreaterThan(0)
expect(rows).toContainEqual({ npmName: 'cordis', upstream: 'https://github.com/cordiverse/cordis' })
expect(rows).toContainEqual({
npmName: '@deepseek-ai/cordis',
upstreamName: 'cordis',
upstream: 'https://github.com/cordiverse/cordis',
})
// The upstream column carries a trailing package path for some rows; it is not part of the URL.
expect(rows.every(row => /^https:\/\/\S+$/.test(row.upstream))).toBe(true)
})
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([])
expect(parseVendoredRows('| `cordis/` | `@deepseek-ai/cordis` | cordis | 4.0.0 | https://example.com | `abc123` |\n')).toEqual([])
})
it('covers every vendored directory, so no package can drop out of the notices', () => {

View File

@@ -387,6 +387,8 @@ export function tierExternalDeps(manifests: Map<string, Manifest>, names: Set<st
/** A vendored package row parsed out of the `vendor/README.md` manifest table. */
export interface VendoredRow {
npmName: string
/** The name this package carries upstream; MIT attribution names the fork's origin, not our scope. */
upstreamName: string
upstream: string
}
@@ -398,11 +400,12 @@ export interface VendoredRow {
export function parseVendoredRows(text: string): VendoredRow[] {
const rows: VendoredRow[] = []
for (const line of text.split('\n')) {
const match = /^\| \x60\S+\/\x60 \| \x60([^\x60]+)\x60 \| \S+ \| (https:\/\/\S+?)(?: \([^)]*\))? \| \x60[0-9a-f]+\x60 \|$/.exec(line)
const match = new RegExp(String.raw`^\| \x60\S+\/\x60 \| \x60([^\x60]+)\x60 \| \x60([^\x60]+)\x60 \| \S+ \| `
+ String.raw`(https:\/\/\S+?)(?: \([^)]*\))? \| \x60[0-9a-f]+\x60 \|$`).exec(line)
if (match === null) continue
const [, npmName, upstream] = match
if (npmName === undefined || upstream === undefined) continue
rows.push({ npmName, upstream })
const [, npmName, upstreamName, upstream] = match
if (npmName === undefined || upstreamName === undefined || upstream === undefined) continue
rows.push({ npmName, upstreamName, upstream })
}
return rows
}
@@ -696,11 +699,11 @@ The complete npm transitive closure, including the Landlock launcher workspace,
## Vendored source (\`vendor/\`)
The Cordis framework and its foundation libraries are source-vendored into this repository rather than consumed from npm. All are MIT-licensed; each directory preserves its upstream \`LICENSE\` file. Exact upstream commits and local modifications are recorded in [\`vendor/README.md\`](vendor/README.md).
The Cordis framework and its foundation libraries are source-vendored into this repository rather than consumed from npm, and republished under the \`@deepseek-ai\` scope. All are MIT-licensed; each directory preserves its upstream \`LICENSE\` file. Exact upstream commits and local modifications are recorded in [\`vendor/README.md\`](vendor/README.md).
| Package | Upstream | License |
| --- | --- | --- |
${vendored.map(row => `| \`${row.npmName}\` | [${row.upstream.replace('https://', '')}](${row.upstream}) | MIT |`).join('\n')}
| Package | Upstream name | Upstream | License |
| --- | --- | --- | --- |
${vendored.map(row => `| \`${row.npmName}\` | \`${row.upstreamName}\` | [${row.upstream.replace('https://', '')}](${row.upstream}) | MIT |`).join('\n')}
## Runtime npm dependencies

View File

@@ -8,7 +8,7 @@
import { globSync, readFileSync, writeFileSync } from 'node:fs'
import { basename, resolve } from 'node:path'
import { Context } from 'cordis'
import { Context } from '@deepseek-ai/cordis'
import type { ToolSchema } from '@deepseek-ai/dsh-llm'
import AgentRegistry from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'

View File

@@ -258,7 +258,9 @@ class WorkspacePackageSet {
const name = expectString(manifest, 'name', manifestPath)
const version = expectString(manifest, 'version', manifestPath)
const isVendored = manifestPath.startsWith('vendor/')
if (!isVendored && !name.startsWith('@deepseek-ai/')) {
// Vendored packages are rescoped too (vendor/README.md), so publication
// never carries an upstream name that would squat it on the registry.
if (!name.startsWith('@deepseek-ai/')) {
throw new Error(`${manifestPath} must name an @deepseek-ai package`)
}
if (name === '@deepseek-ai/dsh-root') {

View File

@@ -0,0 +1,41 @@
/**
* Acceptance-path coverage for the rescope codemod's exact-edit classifier: a
* duplicated insertion — what a non-idempotent apply produces — must be
* rejected rather than applied again.
*/
import { describe, expect, it } from 'vitest'
import { exactEditState } from './rescope-vendor.ts'
const ANCHOR = '\n## Sync procedure'
const INSERTED = `\n15. **rescope**: one log entry.\n${ANCHOR}`
describe('exactEditState', () => {
it('classifies an insertion by its target form, so a duplicate is invalid', () => {
expect(exactEditState(`log\n${ANCHOR}\n`, ANCHOR, INSERTED, 1)).toBe('pending')
expect(exactEditState(`log${INSERTED}\n`, ANCHOR, INSERTED, 1)).toBe('applied')
// The anchor survives an insertion, so counting the source form would have
// called this pending and inserted the entry a second time.
expect(exactEditState(`log${INSERTED}${INSERTED}\n`, ANCHOR, INSERTED, 1)).toBe('invalid')
expect(exactEditState('log\n', ANCHOR, INSERTED, 1)).toBe('invalid')
})
it('classifies a deletion by its source form, and requires its remainder to survive', () => {
const remainder = 'exclude:\n'
const withEntries = 'exclude:\n - cordis@4\n'
expect(exactEditState(withEntries, withEntries, remainder, 1)).toBe('pending')
expect(exactEditState(remainder, withEntries, remainder, 1)).toBe('applied')
// Upstream dropped the whole field: the source form is gone, but so is the
// remainder, so this is a moved site rather than a completed deletion.
expect(exactEditState('unrelated:\n', withEntries, remainder, 1)).toBe('invalid')
})
it('requires a replacement to leave no source form and the exact target count', () => {
expect(exactEditState('a = 1\n', 'a = 1', 'b = 2', 1)).toBe('pending')
expect(exactEditState('b = 2\n', 'a = 1', 'b = 2', 1)).toBe('applied')
expect(exactEditState('b = 2\nb = 2\n', 'a = 1', 'b = 2', 1)).toBe('invalid')
// A moved or partially applied site: neither state is complete.
expect(exactEditState('a = 1\nb = 2\n', 'a = 1', 'b = 2', 1)).toBe('invalid')
expect(exactEditState('x\n', 'a = 1', 'b = 2', 1)).toBe('invalid')
})
})

771
scripts/rescope-vendor.ts Normal file
View File

@@ -0,0 +1,771 @@
/**
* Rescope the vendored Cordis packages into the `@deepseek-ai` scope, and undo
* that rescope with `--reverse`. Every harness package declares `cordis` as a
* peer dependency, so publication carries this framework layer too; publishing
* it under the upstream names would squat them on the registry
* ([rationale](../.agents/notes/implemented/process/2026-08-10-vendor-package-rescope.md),
* [name mapping](../docs/rescope.md)).
*
* The generic pass rewrites ONLY delimited, complete package-name tokens:
* `'old'` / `"old"` / `` `old` `` / `'old/subpath'`, plus a YAML `name: old`
* scalar. A match needs a quote (or `name: `) immediately left and the matching
* quote — optionally after a `/subpath` — immediately right, which excludes
* `cordis.yml`, the Loader's `cordis:` builtin prefix, `cordis-config-entry`,
* `@deepseek-ai/dsh-tool-cordis`, and `cordiverse/cordis`, and makes the
* rewrite idempotent because the scoped name's `cordis` is preceded by `/`.
* Markdown follows the rename inside every fence, and in `docs/` prose too:
* a tutorial that teaches an unresolvable name is wrong, while prose elsewhere
* records what was true when it was written.
*
* Sites the token rule cannot express (dot-notation access, unquoted object
* keys, regex literals, the vendored-manifest table) are listed in
* {@link EXACT_EDITS} with an exact hit count, so an upstream change to one of
* them fails loudly instead of being silently skipped.
*
* Usage: `pnpm run rescope-vendor [--apply|--check] [--reverse]`. Without a
* mode it reports what would change. `--check` asserts the post-state: no
* residue, every exact edit landed, every postcondition holds, and a second
* `--apply` would be a no-op.
*/
import { execFileSync } from 'node:child_process'
import { existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = resolve(import.meta.dirname, '..')
/** One vendored package's directory, upstream npm name, and rescoped name. */
interface Rename {
readonly directory: string
readonly upstream: string
readonly scoped: string
}
/** The mapping this codemod applies; `vendor/README.md` carries the same table. */
const RENAMES: readonly Rename[] = [
{ directory: 'cordis', upstream: 'cordis', scoped: '@deepseek-ai/cordis' },
{ directory: 'cosmokit', upstream: 'cosmokit', scoped: '@deepseek-ai/cosmokit' },
{ directory: 'schemastery', upstream: 'schemastery', scoped: '@deepseek-ai/schemastery' },
{ directory: 'loader', upstream: '@cordisjs/plugin-loader', scoped: '@deepseek-ai/cordis-plugin-loader' },
{ directory: 'include', upstream: '@cordisjs/plugin-include', scoped: '@deepseek-ai/cordis-plugin-include' },
{ directory: 'group', upstream: '@cordisjs/plugin-group', scoped: '@deepseek-ai/cordis-plugin-group' },
{ directory: 'timer', upstream: '@cordisjs/plugin-timer', scoped: '@deepseek-ai/cordis-plugin-timer' },
{ directory: 'hmr', upstream: '@cordisjs/plugin-hmr', scoped: '@deepseek-ai/cordis-plugin-hmr' },
{ directory: 'logger-console', upstream: '@cordisjs/plugin-logger-console', scoped: '@deepseek-ai/cordis-plugin-logger-console' },
]
const EXTENSIONS = ['.ts', '.tsx', '.js', '.mjs', '.cjs', '.tpl', '.json', '.yml', '.yaml', '.md'] as const
/** An exact-string edit the token rule cannot express, with its required hit count. */
interface ExactEdit {
readonly id: string
readonly file: string
readonly find: string
readonly replace: string
readonly expect: number
}
/**
* A file where an upstream name also appears as a vendor DIRECTORY name or an
* upstream runtime identifier: the generic pass is disabled for the listed
* names and {@link EXACT_EDITS} renames the real package-name occurrences.
*/
interface GenericSkip {
readonly file: string
readonly upstream: readonly string[]
}
const GENERIC_SKIPS: readonly GenericSkip[] = [
// `vendorPackages` lists vendor/ directory names, joined with 'vendor' below it.
{ file: 'packages/examples/acp-demo/tests/built-bin.e2e.ts', upstream: ['cordis', 'cosmokit', 'schemastery'] },
// Mixes join(root, 'vendor', 'cordis') paths with real manifest names.
{ file: 'packages/scaffold/helper/tests/documents.spec.ts', upstream: ['cordis'] },
// `Symbol.for('schemastery')` and the `vendor:` metadata field are upstream identifiers.
{ file: 'vendor/schemastery/src/index.ts', upstream: ['schemastery'] },
// Asserts the vendored-manifest table, which gains an upstream-name column.
{ file: 'scripts/gen-third-party-notices.spec.ts', upstream: RENAMES.map(rename => rename.upstream) },
// `cordis` is also an agent-preset id — the directory name under
// apps/cli/config/agent-presets/ — so in these files the bare name is
// product data, not a package reference. Renaming it changed which preset
// the creator flow stages and which id the roster reports.
{ file: 'packages/client/ui-agent-preset/src/client/AgentPresetSection.tsx', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/src/client/index.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/apply.spec.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/locales.spec.ts', upstream: ['cordis'] },
{ file: 'packages/client/ui-agent-preset/tests/section.spec.tsx', upstream: ['cordis'] },
{ file: 'apps/cli/tests/web-agent-presets.e2e.ts', upstream: ['cordis'] },
{ file: 'apps/web/tests/agent-preset-authoring.e2e.ts', upstream: ['cordis'] },
{ file: 'packages/preset/agent-presets/tests/session.spec.ts', upstream: ['cordis'] },
// The preset's own composition: its header comment and its system prompt name
// the preset a model mounts, so the scoped name would send the model after an
// id no roster reports.
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', upstream: ['cordis'] },
// GROUP_ORDER holds `packages/<group>/` directory names, not package names.
{ file: 'scripts/gen-module-graph.ts', upstream: ['cordis'] },
{ file: 'scripts/gen-doc-graphs.ts', upstream: ['cordis'] },
]
/** A string that must appear exactly `count` times once the rescope has run. */
interface PostCondition {
readonly file: string
readonly text: string
readonly count: number
}
const POSTCONDITIONS: readonly PostCondition[] = [
{ file: 'vendor/cordis/package.json', text: '"name": "@deepseek-ai/cordis"', count: 1 },
{ file: 'vendor/hmr/package.json', text: '"name": "@deepseek-ai/cordis-plugin-hmr"', count: 1 },
{ file: 'scripts/cordis-walk.ts', text: '@deepseek-ai\\/cordis', count: 1 },
{ file: 'scripts/cordis-walk.ts', text: '!== \'@deepseek-ai/cordis\'', count: 1 },
{ file: 'scripts/gen-scoped-events.ts', text: '=== \'@deepseek-ai/cordis\'', count: 1 },
{ file: 'packages/typert/generator/src/analyzer.ts', text: '!== \'@deepseek-ai/cordis\'', count: 2 },
{ file: 'scripts/check-workspace-constraints.ts', text: '?.[\'@deepseek-ai/cordis\']', count: 2 },
{ file: 'packages/scaffold/helper/src/project/npm-dependency-policy.ts', text: '\'@deepseek-ai/cordis\': \'^4.0.0-rc.7\'', count: 1 },
{ file: 'packages/scaffold/helper/src/plugins/local-plugin-blueprint.ts', text: '\'@deepseek-ai/cordis\': cordisSpec', count: 2 },
{ file: 'packages/boot/app-boot/tsdown.config.ts', text: '[\'@deepseek-ai/cordis-plugin-include\']', count: 1 },
{ file: 'tsconfig.base.json', text: '"@deepseek-ai/cordis-plugin-loader": ["./vendor/loader/src"]', count: 1 },
// One insertion, once: a duplicated log entry is what a non-idempotent apply produced.
{ file: 'vendor/README.md', text: '15. **`@deepseek-ai` rescope**', count: 1 },
{ file: 'knip.json', text: '@cordisjs', count: 0 },
{ file: 'pnpm-workspace.yaml', text: 'cordis@4.0.0-rc.7', count: 0 },
// The preset ids in this table are product data, not package names.
{ file: 'packages/client/ui-agent-preset/tests/locales.spec.ts', text: '[\'cordis\', \'presetCordisName\'', count: 1 },
// The preset id the shipped composition documents to its own model.
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', text: 'The `cordis` agent preset', count: 1 },
{ file: 'apps/cli/config/agent-presets/cordis/agent.cordis.yml', text: 'corrupting the `cordis` preset', count: 1 },
// The vendor-directory paths in these fixtures must survive the rename.
{ file: 'packages/scaffold/helper/tests/documents.spec.ts', text: 'join(root, \'vendor\', \'cordis\')', count: 2 },
{ file: 'packages/examples/acp-demo/tests/built-bin.e2e.ts', text: '\'cordis\', \'loader\', \'include\', \'timer\', \'hmr\', \'logger-console\',', count: 1 },
]
/**
* Every exact edit, in application order. Each `find` is written against the
* PRE-rename text because these run before the generic pass, so no `find` may
* quote a neighbouring line the generic pass would rewrite.
*/
const EXACT_EDITS: readonly ExactEdit[] = [
{
id: 'cordis-walk-merge-head',
file: 'scripts/cordis-walk.ts',
find: 'const MERGE_HEAD = /declare module [\'"](?:cordis|\\.\\/context\\.ts)[\'"]/',
replace: 'const MERGE_HEAD = /declare module [\'"](?:@deepseek-ai\\/cordis|\\.\\/context\\.ts)[\'"]/',
expect: 1,
},
{
id: 'constraints-manifest-lookup',
file: 'scripts/check-workspace-constraints.ts',
find: ` const peer = manifest.peerDependencies?.cordis
const dev = manifest.devDependencies?.cordis
if (!peer) errors.push(\`\${label}: cordis must be a peerDependency\`)
if (!dev) errors.push(\`\${label}: cordis must also be a devDependency\`)
if (peer && dev && peer !== dev) {
errors.push(\`\${label}: cordis peer (\${peer}) and dev (\${dev}) ranges must match\`)`,
replace: ` const peer = manifest.peerDependencies?.['@deepseek-ai/cordis']
const dev = manifest.devDependencies?.['@deepseek-ai/cordis']
if (!peer) errors.push(\`\${label}: @deepseek-ai/cordis must be a peerDependency\`)
if (!dev) errors.push(\`\${label}: @deepseek-ai/cordis must also be a devDependency\`)
if (peer && dev && peer !== dev) {
errors.push(\`\${label}: @deepseek-ai/cordis peer (\${peer}) and dev (\${dev}) ranges must match\`)`,
expect: 1,
},
{
id: 'scaffold-dependency-policy',
file: 'packages/scaffold/helper/src/project/npm-dependency-policy.ts',
find: ' cordis: \'^4.0.0-rc.7\',',
replace: ' \'@deepseek-ai/cordis\': \'^4.0.0-rc.7\',',
expect: 1,
},
{
id: 'scaffold-plugin-blueprint',
file: 'packages/scaffold/helper/src/plugins/local-plugin-blueprint.ts',
find: ` cordis: cordisSpec,
},
devDependencies: {
cordis: cordisSpec,
},`,
replace: ` '@deepseek-ai/cordis': cordisSpec,
},
devDependencies: {
'@deepseek-ai/cordis': cordisSpec,
},`,
expect: 1,
},
{
id: 'scaffold-link-workspace-lookup',
file: 'packages/scaffold/create-sdk/tests/link-workspace.e2e.ts',
find: 'manifest.dependencies.cordis',
replace: 'manifest.dependencies[\'@deepseek-ai/cordis\']',
expect: 1,
},
{
id: 'documents-spec-manifest-name',
file: 'packages/scaffold/helper/tests/documents.spec.ts',
find: 'JSON.stringify({ name: \'cordis\' })',
replace: 'JSON.stringify({ name: \'@deepseek-ai/cordis\' })',
expect: 1,
},
{
id: 'documents-spec-peer-key',
file: 'packages/scaffold/helper/tests/documents.spec.ts',
find: 'peerDependencies: { cordis: \'^4\' },',
replace: 'peerDependencies: { \'@deepseek-ai/cordis\': \'^4\' },',
expect: 1,
},
{
id: 'documents-spec-closure-order',
file: 'packages/scaffold/helper/tests/documents.spec.ts',
find: ' \'@deepseek-ai/dsh-helper\', \'@deepseek-ai/dsh-scripts\', \'cordis\',',
replace: ' \'@deepseek-ai/cordis\', \'@deepseek-ai/dsh-helper\', \'@deepseek-ai/dsh-scripts\',',
expect: 1,
},
{
id: 'documents-spec-lookups',
file: 'packages/scaffold/helper/tests/documents.spec.ts',
find: ` expect(manifest.npmDependency('cordis')?.spec).toMatch(/^link:/)
expect(pnpmWorkspace.serialize()).toContain('autoInstallPeers: false')
expect(workspace.packageDirectory('cordis')).toBe(join(root, 'vendor', 'cordis'))
expect(await readFile(join(root, 'vendor', 'cordis', 'package.json'), 'utf8')).toContain('cordis')`,
replace: ` expect(manifest.npmDependency('@deepseek-ai/cordis')?.spec).toMatch(/^link:/)
expect(pnpmWorkspace.serialize()).toContain('autoInstallPeers: false')
expect(workspace.packageDirectory('@deepseek-ai/cordis')).toBe(join(root, 'vendor', 'cordis'))
expect(await readFile(join(root, 'vendor', 'cordis', 'package.json'), 'utf8')).toContain('@deepseek-ai/cordis')`,
expect: 1,
},
{
id: 'documents-spec-policy-lookup',
file: 'packages/scaffold/helper/tests/documents.spec.ts',
find: ' expect(resolveNpmDependency(\'cordis\', \'devDependencies\', \'0.0.1\')).toEqual({',
replace: ' expect(resolveNpmDependency(\'@deepseek-ai/cordis\', \'devDependencies\', \'0.0.1\')).toEqual({',
expect: 1,
},
{
// The rescoped name is already covered by the `@deepseek-ai/.+` pattern beside it.
id: 'knip-logger-console',
file: 'knip.json',
find: ` "ignoreDependencies": [
"@cordisjs/plugin-logger-console",
"@deepseek-ai/.+"
]
},
"packages/util/home": {`,
replace: ` "ignoreDependencies": [
"@deepseek-ai/.+"
]
},
"packages/util/home": {`,
expect: 1,
},
{
id: 'knip-bundle-base',
file: 'knip.json',
find: ` "packages/bundle/base": {
"ignoreDependencies": [
"@deepseek-ai/.+",
"@cordisjs/.+"
]`,
replace: ` "packages/bundle/base": {
"ignoreDependencies": [
"@deepseek-ai/.+"
]`,
expect: 1,
},
{
// Rescoped packages are never fetched from a registry, so the exclusion is dead config.
id: 'pnpm-release-age',
file: 'pnpm-workspace.yaml',
find: `minimumReleaseAgeExclude:
# Cordis release candidates are source-vendored and pinned in vendor/README.md
# during the same-day sync that updates package manifests and the lockfile.
- '@cordisjs/plugin-loader@1.0.0-rc.5'
- cordis@4.0.0-rc.7
`,
replace: 'minimumReleaseAgeExclude:\n',
expect: 1,
},
{
id: 'publication-set-scope-assertion',
file: 'scripts/publish-npm-baseline.ts',
find: ' if (!isVendored && !name.startsWith(\'@deepseek-ai/\')) {',
replace: ` // Vendored packages are rescoped too (vendor/README.md), so publication
// never carries an upstream name that would squat it on the registry.
if (!name.startsWith('@deepseek-ai/')) {`,
expect: 1,
},
{
id: 'vendor-readme-preamble',
file: 'vendor/README.md',
find: 'All vendored packages keep their **original npm names** and are marked `private: true` — they are never published from this repo. `pnpm-workspace.yaml#linkWorkspacePackages` makes matching upstream semver ranges resolve these pinned workspaces, including imports from built `lib/`; disabling it substitutes npm copies behind the same names.',
replace: 'All vendored packages are **renamed into the `@deepseek-ai` scope** (`cordis` → `@deepseek-ai/cordis`, `@cordisjs/plugin-<x>` → `@deepseek-ai/cordis-plugin-<x>`): every harness package declares `cordis` as a peer dependency, so publishing the harness publishes this framework layer too, and a publication under the upstream names would squat them on the registry. Directory names and upstream version numbers are deliberately unchanged, so the manifest below still reads as an upstream snapshot. `pnpm-workspace.yaml#linkWorkspacePackages` makes those preserved semver ranges resolve these pinned workspaces, including imports from built `lib/`.',
expect: 1,
},
{
id: 'vendor-readme-schemastery-note',
file: 'vendor/README.md',
find: 'whose lazy `require(\'cosmokit\')` can race',
replace: 'whose lazy `require(\'@deepseek-ai/cosmokit\')` can race',
expect: 1,
},
{
id: 'vendor-readme-table-head',
file: 'vendor/README.md',
find: '| Directory | npm name | Version | Upstream repo | Commit |\n|---|---|---|---|---|',
replace: '| Directory | npm name | Upstream name | Version | Upstream repo | Commit |\n|---|---|---|---|---|---|',
expect: 1,
},
{
id: 'vendor-readme-local-modification-log',
file: 'vendor/README.md',
find: '\n## Sync procedure',
replace: '15. **`@deepseek-ai` rescope**: every vendored manifest `name`, every internal dependency entry among the vendored set, and every module specifier that reaches them use the scoped names in the manifest table\'s `npm name` column. Directory names, version numbers, and dependency ranges are unchanged, and no upstream runtime identifier is renamed — `Symbol.for(\'schemastery\')` and Schemastery\'s `vendor:` metadata field keep their upstream values. Re-apply with `pnpm run rescope-vendor --apply` after a sync; the table\'s two name columns are the mapping, restated for consumers in [docs/rescope.md](../docs/rescope.md).\n\n## Sync procedure',
expect: 1,
},
{
// A plain fence listing the bundle's mounted tree: a bare token, no quotes.
id: 'agent-spine-demo-mounted-tree',
file: 'packages/examples/agent-spine-demo/README.md',
find: '@cordisjs/plugin-timer timer service',
replace: '@deepseek-ai/cordis-plugin-timer timer service',
expect: 1,
},
{
id: 'agent-spine-demo-mounted-tree-zh',
file: 'packages/examples/agent-spine-demo/README.zh.md',
find: '@cordisjs/plugin-timer timer service',
replace: '@deepseek-ai/cordis-plugin-timer timer service',
expect: 1,
},
{
// The root contract claimed vendored packages keep their upstream names.
id: 'root-agents-vendored-name-contract',
file: 'AGENTS.md',
find: 'vendored packages keep upstream names and are `private: true`. `cordis` is a peerDependency (+ dev) of every harness package.',
replace: 'vendored packages are rescoped ([mapping](docs/rescope.md)) and `private: true`. `@deepseek-ai/cordis` is a peerDependency (+ dev) of every harness package.',
expect: 1,
},
{
// The client purity gate reads `@deepseek-ai/` as "another plugin package".
// The rescope moves the vendored framework and its libraries into that
// namespace, where the gate would reject the library imports client
// bundles have always inlined, so it needs their names.
id: 'client-purity-vendored-libraries',
file: 'packages/client/tsdown.client.ts',
find: '/** Generated descriptor/codec contribution with no shared runtime identity. */',
replace: `/**
* Vendored framework libraries: rescoped into @deepseek-ai, so the gate below
* would read them as plugin packages. They carry no cross-plugin runtime
* identity to share — the framework itself is a platform module (external),
* while these are ordinary libraries a browser bundle inlines.
*/
const VENDORED_LIBRARY = /^@deepseek-ai\\/(cosmokit|schemastery)(\\/|$)/
/** Generated descriptor/codec contribution with no shared runtime identity. */`,
expect: 1,
},
{
id: 'client-purity-vendored-libraries-predicate',
file: 'packages/client/tsdown.client.ts',
find: ' if (INLINE_SAFE.test(source) || GENERATED_REMOTE.test(source)) return null // wire contribution: inline is the point',
replace: ` if (VENDORED_LIBRARY.test(source)) return null // vendored library: inline, no shared identity
if (INLINE_SAFE.test(source) || GENERATED_REMOTE.test(source)) return null // wire contribution: inline is the point`,
expect: 1,
},
{
// The step-1 file tree told the reader to keep the upstream name, one
// paragraph above the invariant that says to rescope it.
id: 'vendoring-cookbook-tree-comment',
file: 'docs/cookbook/adding-a-vendored-package.md',
find: ' package.json # from upstream; set "private": true, keep name/exports/type',
replace: ' package.json # from upstream; set "private": true, rescope the name, keep exports/type',
expect: 1,
},
{
id: 'vendoring-cookbook-tree-comment-zh',
file: 'docs/cookbook/adding-a-vendored-package.zh.md',
find: ' package.json # from upstream; set "private": true, keep name/exports/type',
replace: ' package.json # from upstream; set "private": true, rescope the name, keep exports/type',
expect: 1,
},
{
// The checklist told the next vendoring to keep upstream's name.
id: 'vendoring-cookbook-name-invariant',
file: 'docs/cookbook/adding-a-vendored-package.md',
find: "keep upstream's `name`/`version`/`exports`/`type`",
replace: "rescope the `name` ([mapping](../rescope.md)) while keeping upstream's `version`/`exports`/`type`",
expect: 1,
},
{
id: 'vendoring-cookbook-name-invariant-zh',
file: 'docs/cookbook/adding-a-vendored-package.zh.md',
find: '保留上游的 `name`/`version`/`exports`/`type`',
replace: '改写 `name` 的 scope[映射](../rescope.md)),保留上游的 `version`/`exports`/`type`',
expect: 1,
},
{
// The real package references in files whose other `cordis` strings are preset ids.
id: 'agent-preset-spec-framework-import',
file: 'packages/client/ui-agent-preset/tests/apply.spec.ts',
find: "import { Context } from 'cordis'",
replace: "import { Context } from '@deepseek-ai/cordis'",
expect: 1,
},
{
id: 'web-agent-presets-e2e-framework-import',
file: 'apps/cli/tests/web-agent-presets.e2e.ts',
find: "import { Context } from 'cordis'",
replace: "import { Context } from '@deepseek-ai/cordis'",
expect: 1,
},
{
id: 'notices-vendored-row-type',
file: 'scripts/gen-third-party-notices.ts',
find: `export interface VendoredRow {
npmName: string
upstream: string
}`,
replace: `export interface VendoredRow {
npmName: string
/** The name this package carries upstream; MIT attribution names the fork's origin, not our scope. */
upstreamName: string
upstream: string
}`,
expect: 1,
},
{
id: 'notices-vendored-row-parse',
file: 'scripts/gen-third-party-notices.ts',
find: ` const match = /^\\| \\x60\\S+\\/\\x60 \\| \\x60([^\\x60]+)\\x60 \\| \\S+ \\| (https:\\/\\/\\S+?)(?: \\([^)]*\\))? \\| \\x60[0-9a-f]+\\x60 \\|$/.exec(line)
if (match === null) continue
const [, npmName, upstream] = match
if (npmName === undefined || upstream === undefined) continue
rows.push({ npmName, upstream })`,
replace: ` const match = new RegExp(String.raw\`^\\| \\x60\\S+\\/\\x60 \\| \\x60([^\\x60]+)\\x60 \\| \\x60([^\\x60]+)\\x60 \\| \\S+ \\| \`
+ String.raw\`(https:\\/\\/\\S+?)(?: \\([^)]*\\))? \\| \\x60[0-9a-f]+\\x60 \\|$\`).exec(line)
if (match === null) continue
const [, npmName, upstreamName, upstream] = match
if (npmName === undefined || upstreamName === undefined || upstream === undefined) continue
rows.push({ npmName, upstreamName, upstream })`,
expect: 1,
},
{
id: 'notices-vendored-section',
file: 'scripts/gen-third-party-notices.ts',
find: 'The Cordis framework and its foundation libraries are source-vendored into this repository rather than consumed from npm. All are MIT-licensed',
replace: 'The Cordis framework and its foundation libraries are source-vendored into this repository rather than consumed from npm, and republished under the \\`@deepseek-ai\\` scope. All are MIT-licensed',
expect: 1,
},
{
id: 'notices-vendored-table',
file: 'scripts/gen-third-party-notices.ts',
find: `| Package | Upstream | License |
| --- | --- | --- |
\${vendored.map(row => \`| \\\`\${row.npmName}\\\` | [\${row.upstream.replace('https://', '')}](\${row.upstream}) | MIT |\`).join('\\n')}`,
replace: `| Package | Upstream name | Upstream | License |
| --- | --- | --- | --- |
\${vendored.map(row => \`| \\\`\${row.npmName}\\\` | \\\`\${row.upstreamName}\\\` | [\${row.upstream.replace('https://', '')}](\${row.upstream}) | MIT |\`).join('\\n')}`,
expect: 1,
},
{
id: 'notices-spec-row-fixture',
file: 'scripts/gen-third-party-notices.spec.ts',
find: ' expect(rows).toContainEqual({ npmName: \'cordis\', upstream: \'https://github.com/cordiverse/cordis\' })',
replace: ` expect(rows).toContainEqual({
npmName: '@deepseek-ai/cordis',
upstreamName: 'cordis',
upstream: 'https://github.com/cordiverse/cordis',
})`,
expect: 1,
},
{
id: 'notices-spec-shape-fixture',
file: 'scripts/gen-third-party-notices.spec.ts',
find: 'parseVendoredRows(\'| `cordis/` | cordis | 4.0.0 | https://example.com | `abc123` |\\n\')',
replace: 'parseVendoredRows(\'| `cordis/` | `@deepseek-ai/cordis` | cordis | 4.0.0 | https://example.com | `abc123` |\\n\')',
expect: 1,
},
{
// The framework peer is no longer a registry name, so the rehearsal must install this
// repository's vendored copies; cosmokit comes along as cordis's own dependency.
id: 'packed-install-vendored-peer',
file: 'packages/sandbox/sandbox-local/tests/packed-install.e2e.ts',
find: ` 'packages/support/invariants',
]`,
replace: ` 'packages/support/invariants',
// The framework and the vendored packages the closure declares outright:
// rescoped into @deepseek-ai, so the consumer installs this repository's
// copies. Schemastery is a hard dependency of three members above, not a
// peer, so npm resolves it while installing them.
'vendor/cordis',
'vendor/cosmokit',
'vendor/schemastery',
]`,
expect: 1,
},
{
id: 'packed-install-registry-spec',
file: 'packages/sandbox/sandbox-local/tests/packed-install.e2e.ts',
find: ` // Peer ranges resolve to the tarballs; Cordis is pinned to their peer range. Do not omit optional
// dependencies because the launcher selects its OS/CPU package through one.
writeFileSync(join(consumerDir, 'package.json'), JSON.stringify({ name: 'dsh-packed-consumer', private: true, type: 'module' }))
const install = spawnSync('npm', ['install', '--no-audit', '--no-fund', ...tarballs, 'cordis@4.0.0-rc.7'], {`,
replace: ` // Peer ranges resolve to the tarballs, the framework peer included. Do not omit optional
// dependencies because the launcher selects its OS/CPU package through one.
writeFileSync(join(consumerDir, 'package.json'), JSON.stringify({ name: 'dsh-packed-consumer', private: true, type: 'module' }))
const install = spawnSync('npm', ['install', '--no-audit', '--no-fund', ...tarballs], {`,
expect: 1,
},
{
id: 'packed-install-module-doc',
file: 'packages/sandbox/sandbox-local/tests/packed-install.e2e.ts',
find: ` * Keyless publish-path rehearsal. It packs the provider, its workspace peers, and the current
* repository's Landlock entry/platform packages, then installs those exact tarballs in an external
* plain-Node consumer. The host launcher comes from the exact local tarballs, so no registry copy,
* tsx, path mapping, or workspace resolution can hide missing files, dependency errors, or lost
* executable modes.`,
replace: ` * Keyless publish-path rehearsal. It packs the provider, its workspace peers, the vendored framework
* peer, and the current repository's Landlock entry/platform packages, then installs those exact
* tarballs in an external plain-Node consumer. The host launcher comes from the exact local tarballs,
* so no registry copy, tsx, path mapping, or workspace resolution can hide missing files, dependency
* errors, or lost executable modes.`,
expect: 1,
},
// The manifest table's name column plus the new upstream-name column, one edit per row.
...RENAMES.map(rename => ({
id: `vendor-readme-row-${rename.directory}`,
file: 'vendor/README.md',
find: `| \`${rename.directory}/\` | \`${rename.upstream}\` | `,
replace: `| \`${rename.directory}/\` | \`${rename.scoped}\` | \`${rename.upstream}\` | `,
expect: 1,
})),
]
/** Files the rescope must never rewrite. */
function excluded(file: string): boolean {
if (file === 'scripts/rescope-vendor.ts') return true // the mapping itself
if (file.startsWith('.agents/notes/')) return true // notes record what was true when written
// Recorded model payloads quote documentation verbatim, so they must mirror the
// sources on disk — including the notes this rescope leaves alone.
if (file.startsWith('scripts/snapshots/')) return true
// The mapping documents state both names on purpose.
if (file === 'docs/rescope.md' || file === 'docs/rescope.zh.md') return true
if (file.endsWith('.i18n.yaml')) return true // blob-hash records, re-recorded by the pairing gate
if (file === 'pnpm-lock.yaml') return true // regenerated by pnpm install
if (/^vendor\/[^/]+\/(README\.md|LICENSE)$/.test(file)) return true // upstream files kept verbatim
return !EXTENSIONS.some(extension => file.endsWith(extension))
}
function escapeRegExp(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
}
/** One name's rewrite, precompiled for both delimited forms. */
interface Pattern {
readonly upstream: string
readonly from: string
readonly to: string
readonly token: RegExp
readonly yamlName: RegExp
}
function patterns(reverse: boolean): Pattern[] {
return RENAMES
.map(rename => ({
upstream: rename.upstream,
from: reverse ? rename.scoped : rename.upstream,
to: reverse ? rename.upstream : rename.scoped,
}))
.sort((left, right) => right.from.length - left.from.length)
.map(rename => ({
...rename,
token: new RegExp(`(['"\`])${escapeRegExp(rename.from)}((?:/[^'"\`\\s]*)?)\\1`, 'g'),
yamlName: new RegExp(`^(\\s*(?:-\\s*)?name:[ \\t]+)${escapeRegExp(rename.from)}([ \\t]*(?:#.*)?)$`, 'gm'),
}))
}
function skipped(file: string, pattern: Pattern): boolean {
return GENERIC_SKIPS.some(skip => skip.file === file && skip.upstream.includes(pattern.upstream))
}
function rewriteLine(line: string, file: string, all: readonly Pattern[]): string {
let out = line
for (const pattern of all) {
if (skipped(file, pattern)) continue
out = out.replace(pattern.token, (_match, quote: string, subpath: string) => `${quote}${pattern.to}${subpath}${quote}`)
out = out.replace(pattern.yamlName, (_match, prefix: string, suffix: string) => `${prefix}${pattern.to}${suffix}`)
}
return out
}
/**
* Rewrite a file's eligible lines.
*
* Markdown splits in two. Every fence is code a reader copies or a
* configuration they mount, so every fence follows the rename regardless of its
* info string. Prose follows it only under `docs/`, where a sentence quoting
* `` `cordis` `` teaches a name this repository no longer resolves; elsewhere
* prose is a record of what was true when it was written, and the same spelling
* can mean something else entirely — the Python SDK's `cordis` option, or the
* unvendored `@cordisjs/plugin-http`.
*/
function rewrite(text: string, file: string, all: readonly Pattern[]): { text: string; lines: number } {
const markdown = file.endsWith('.md')
const prose = markdown && file.startsWith('docs/')
let insideFence = false
let lines = 0
const out = text.split('\n').map((line) => {
if (markdown) {
if (/^\s*```/.test(line)) {
insideFence = !insideFence
return line
}
if (!insideFence && !prose) return line
}
const next = rewriteLine(line, file, all)
if (next !== line) lines += 1
return next
})
return { text: out.join('\n'), lines }
}
function classify(file: string): string {
if (/^vendor\/[^/]+\/package\.json$/.test(file)) return 'vendor manifest name'
if (file.endsWith('package.json')) return 'package.json dependencies'
if (/\.(ts|tsx|js|mjs|cjs|tpl)$/.test(file)) return 'code specifiers'
if (/\.(yml|yaml)$/.test(file)) return 'YAML plugin names'
if (file.endsWith('.json')) return 'JSON configuration'
return 'Markdown fences and docs prose'
}
/**
* One exact edit's state in the text it targets. `pending` means the source
* form is present and the target form absent; `applied` means the reverse;
* anything else — a partial application, a moved site, or a DUPLICATED
* insertion — is `invalid`, so it fails the run instead of being applied again.
*/
export type ExactEditState = 'pending' | 'applied' | 'invalid'
/**
* Classify one exact edit against its target text.
*
* An insertion keeps its anchor (`replace` contains `find`) and a deletion
* keeps its remainder (`find` contains `replace`), so neither can be judged by
* the source form alone: the surviving side counts the target form instead.
* @param text - the complete current text of the edited file.
* @param find - the source form, already oriented for the running direction.
* @param replace - the target form, already oriented for the running direction.
* @param expect - how many occurrences one complete application produces.
* @returns Whether the edit is pending, already applied, or invalid.
*/
export function exactEditState(text: string, find: string, replace: string, expect: number): ExactEditState {
const hits = text.split(find).length - 1
const landed = text.split(replace).length - 1
if (replace.includes(find)) {
if (landed === expect) return 'applied'
return landed === 0 && hits === expect ? 'pending' : 'invalid'
}
if (find.includes(replace)) {
if (hits === 0) return landed === expect ? 'applied' : 'invalid'
return hits === expect ? 'pending' : 'invalid'
}
if (hits === 0 && landed === expect) return 'applied'
return hits === expect && landed === 0 ? 'pending' : 'invalid'
}
function main(): void {
const args = process.argv.slice(2)
const mode = args.includes('--apply') ? 'apply' : args.includes('--check') ? 'check' : 'dry'
const reverse = args.includes('--reverse')
const all = patterns(reverse)
const files = execFileSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf8' })
.split('\0')
.filter(file => file !== '' && !excluded(file))
const counts = new Map<string, { files: number; lines: number }>()
const failures: string[] = []
const outstanding: string[] = []
// Classify every exact edit before writing anything: a single invalid site
// means the mapping and the tree disagree, and a half-applied tree is worse
// than an untouched one.
const planned: { edit: ExactEdit; path: string; find: string; replace: string }[] = []
for (const edit of EXACT_EDITS) {
const path = resolve(root, edit.file)
const before = readFileSync(path, 'utf8')
const find = reverse ? edit.replace : edit.find
const replace = reverse ? edit.find : edit.replace
const state = exactEditState(before, find, replace, edit.expect)
if (state === 'invalid') {
failures.push(`exact edit ${edit.id}: ${edit.file} is neither pending nor cleanly applied (duplicated, partial, or moved)`)
continue
}
if (mode === 'check') {
if (state !== 'applied') failures.push(`exact edit ${edit.id} did not land in ${edit.file}`)
continue
}
if (state === 'pending') planned.push({ edit, path, find, replace })
}
if (failures.length > 0) {
for (const failure of failures) console.error(`rescope-vendor: ${failure}`)
console.error(`rescope-vendor: ${String(failures.length)} problem(s); nothing was written.`)
process.exitCode = 1
return
}
if (mode === 'apply') {
// Re-read per edit: two edits can target one file, and a stale snapshot
// would let the second write discard the first.
for (const { path, find, replace } of planned) {
writeFileSync(path, readFileSync(path, 'utf8').split(find).join(replace))
}
}
for (const file of files) {
const path = resolve(root, file)
const before = readFileSync(path, 'utf8')
const { text: after, lines } = rewrite(before, file, all)
if (after === before) continue
outstanding.push(file)
const kind = classify(file)
const current = counts.get(kind) ?? { files: 0, lines: 0 }
counts.set(kind, { files: current.files + 1, lines: current.lines + lines })
if (mode === 'apply') writeFileSync(path, after)
}
console.log(`rescope-vendor: ${mode}${reverse ? ' --reverse' : ''} over ${String(files.length)} tracked files`)
for (const kind of [...counts.keys()].sort()) {
const { files: count, lines } = counts.get(kind) ?? { files: 0, lines: 0 }
console.log(` ${kind.padEnd(24)} ${String(count).padStart(4)} file(s), ${String(lines)} line(s)`)
}
if (mode !== 'dry') {
for (const check of POSTCONDITIONS) {
if (reverse) break
const path = resolve(root, check.file)
const hits = existsSync(path) ? readFileSync(path, 'utf8').split(check.text).length - 1 : -1
if (hits !== check.count) {
failures.push(`postcondition: ${check.file} has ${String(hits)} occurrence(s) of ${JSON.stringify(check.text)}, expected ${String(check.count)}`)
}
}
// The generic pass above already told us which files would still change,
// which in check mode is exactly the residue-and-idempotency signal.
if (mode === 'check') {
for (const file of outstanding) failures.push(`residue: ${file} still carries a pre-rescope name token`)
}
}
if (failures.length > 0) {
for (const failure of failures) console.error(`rescope-vendor: ${failure}`)
console.error(`rescope-vendor: ${String(failures.length)} problem(s); the mapping or an upstream site moved.`)
process.exitCode = 1
} else if (mode === 'check') {
console.log('rescope-vendor: post-state verified — no residue, every exact edit landed, idempotent.')
} else if (mode === 'apply') {
console.log('rescope-vendor: applied. Run `pnpm install`, `pnpm run gen-third-party-notices`, and re-record the touched bilingual pairs.')
}
}
// Importing this module for its exported classifier must not run the codemod.
if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
main()
}

View File

@@ -553,6 +553,7 @@ function flagEnabled(envName: string): boolean {
function hygieneLeafGates(options: { artifactNeeds?: string[] } = {}): Gate[] {
const artifactOptions = options.artifactNeeds === undefined ? {} : { needs: options.artifactNeeds }
return [
pnpmScript('rescope-vendor', 'rescope-vendor:check', { label: 'vendor rescope' }),
pnpmScript('knip', 'knip'),
pnpmScript('publint', 'publint', artifactOptions),
pnpmScript('constraints', 'constraints'),

View File

@@ -1,7 +1,7 @@
import { describe, expect, it, vi } from 'vitest'
import { Context, FiberState, Service, ValidationError } from 'cordis'
import Loader from '@cordisjs/plugin-loader'
import z from 'schemastery'
import { Context, FiberState, Service, ValidationError } from '@deepseek-ai/cordis'
import Loader from '@deepseek-ai/cordis-plugin-loader'
import z from '@deepseek-ai/schemastery'
import InvariantService from '@deepseek-ai/dsh-invariants'
import type { InvariantInstaller } from '@deepseek-ai/dsh-invariants'
import { packageInvariantOwners } from './package-invariants.ts'
@@ -13,7 +13,7 @@ import {
usesManualInvariantTree,
} from './test-invariants.ts'
declare module 'cordis' {
declare module '@deepseek-ai/cordis' {
interface Context {
testInvariantProbe: TestInvariantProbe
}

View File

@@ -6,8 +6,8 @@
*/
import { expect } from 'vitest'
import { FiberState, Inject, RegistryService } from 'cordis'
import type { Context, Plugin } from 'cordis'
import { FiberState, Inject, RegistryService } from '@deepseek-ai/cordis'
import type { Context, Plugin } from '@deepseek-ai/cordis'
import { AttachmentStore } from '@deepseek-ai/dsh-attachment'
import type {
ImageAttachmentLimits,

View File

@@ -165,7 +165,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
}
recordPlugin(value, file)
validateMetadata(value, file, path)
if ((value.group === true || value.name === '@cordisjs/plugin-group') && isUnknownArray(value.config)) {
if ((value.group === true || value.name === '@deepseek-ai/cordis-plugin-group') && isUnknownArray(value.config)) {
for (let index = 0; index < value.config.length; index++) {
validateEntry(value.config[index], file, `${path}.config[${index}]`)
}
@@ -175,7 +175,7 @@ function validateEntry(value: unknown, file: string, path: string): void {
validateEntry(value.insert[index], file, `${path}.insert[${index}]`)
}
}
if (value.name !== '@cordisjs/plugin-include') return
if (value.name !== '@deepseek-ai/cordis-plugin-include') return
const config = value.config
if (!isRecord(config) || !isUnknownArray(config.patches)) return
for (let index = 0; index < config.patches.length; index++) {