mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
Rebuild of the region machinery (PR3) on the post-#904 Typert projection: renderPageRegion/renderInheritedPage live in dsh-typert-generator beside the projection; scripts/gen-cordis-catalog.ts owns the curated SERVICE_PAGE / EVENT_SCOPE_PAGE / SERVICE_WALK_EXEMPTIONS / LINK_MAP partition (fail-loud in both directions, with the independent Context-merge scan backstopping the projection's blind spot), spliceRegion, and the guarded pair auto-record. docs/cordis-catalog/ is deleted: the flat events/services catalogs dissolve into per-page regions and docs/cordis-catalog/core moves to docs/cordis-api/ with the inherited tier as its own generated page. The partition absorbs the post-regrouping surface: ctx.typert → invariants.md, ctx.directoryPicker → workspace.md, skills/* events → skills.md, and the four launcher-provided tui accessor values join the named exemptions.
67 lines
2.8 KiB
TypeScript
67 lines
2.8 KiB
TypeScript
/**
|
|
* AST helpers shared by the Cordis generators: locate the Cordis module merge
|
|
* in a source file and enumerate the `interface Context` keys it declares.
|
|
* The vendored core API projector consumes the merge body; the per-subsystem
|
|
* region generator's exhaustiveness backstop consumes the key scan.
|
|
*/
|
|
|
|
import { globSync, readFileSync } from 'node:fs'
|
|
import { resolve, sep } from 'node:path'
|
|
import ts from 'typescript'
|
|
|
|
/**
|
|
* Parse every file matching `pattern` (repo-relative, sorted, `/`-normalized)
|
|
* that textually mentions `interface Context`, yielding each file's cordis
|
|
* module-merge body. Files without a merge are skipped.
|
|
* @param scanRoot - Repository root the pattern is resolved against.
|
|
* @param pattern - Glob selecting the TypeScript files to scan.
|
|
* @returns One entry per file with a cordis module merge, in path order.
|
|
*/
|
|
export function contextMergeFiles(
|
|
scanRoot: string,
|
|
pattern: string,
|
|
): { rel: string; sf: ts.SourceFile; text: string; body: ts.ModuleBlock }[] {
|
|
const out: { rel: string; sf: ts.SourceFile; text: string; body: ts.ModuleBlock }[] = []
|
|
for (const rel of globSync(pattern, { cwd: scanRoot }).map(s => s.split(sep).join('/')).sort()) {
|
|
const abs = resolve(scanRoot, rel)
|
|
const text = readFileSync(abs, 'utf8')
|
|
if (!text.includes('interface Context')) continue
|
|
const sf = ts.createSourceFile(abs, text, ts.ScriptTarget.Latest, true)
|
|
const body = cordisModuleBody(sf)
|
|
if (!body) continue
|
|
out.push({ rel, sf, text, body })
|
|
}
|
|
return out
|
|
}
|
|
|
|
/** The body of the cordis module merge in `sf`: `declare module 'cordis'`
|
|
* (harness packages) or `declare module './context.ts'` (vendor core), or
|
|
* null when the file has neither. */
|
|
export function cordisModuleBody(sf: ts.SourceFile): ts.ModuleBlock | null {
|
|
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.body && ts.isModuleBlock(stmt.body)) return stmt.body
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Every `key: Type` property a `declare module 'cordis'` Context merge
|
|
* declares in one module body.
|
|
* @param body - The cordis module augmentation block.
|
|
* @param sf - Owning source file (for text extraction).
|
|
* @returns key → declared type-name text, in declaration order.
|
|
*/
|
|
export function contextKeyMap(body: ts.ModuleBlock, sf: ts.SourceFile): Map<string, string> {
|
|
const keyToType = new Map<string, string>()
|
|
for (const stmt of body.statements) {
|
|
if (!ts.isInterfaceDeclaration(stmt) || stmt.name.text !== 'Context') continue
|
|
for (const member of stmt.members) {
|
|
if (!ts.isPropertySignature(member) || !member.type) continue
|
|
keyToType.set(member.name.getText(sf), member.type.getText(sf))
|
|
}
|
|
}
|
|
return keyToType
|
|
}
|