Merge pull request #361 from anywhere-labs/architecture/desktop-architecture-gates

ci: add bilingual and dependency direction gates
This commit is contained in:
Qiu
2026-08-20 22:46:45 +08:00
committed by GitHub
10 changed files with 279 additions and 28 deletions
@@ -1,5 +1,5 @@
# Bilingual-pair consistency record: the git blob hash of each side as of the last
# confirmed-consistent state. Both languages carry equal authority. Update both files
# and re-record their hashes after editing either side.
2026-08-16-desktop-logging.md: 8e1df3e52fcc58688fac242a8688b1d18816edad
2026-08-16-desktop-logging.zh.md: 97346bb31546427cf9e98e9039c6ad6ba33663a5
2026-08-16-desktop-logging.md: f97287a0b0ed06a08b31aeb0da303c3a23ea5310
2026-08-16-desktop-logging.zh.md: f1a3b3717c5c59b151768ae4415abeb8b2f1f904
+4
View File
@@ -41,6 +41,10 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
run: |
set -euo pipefail
node --test scripts/bilingual-docs.test.mjs
node scripts/verify-bilingual-docs.mjs
node --test scripts/market-dependency-direction.test.mjs
node scripts/verify-market-dependency-direction.mjs
if [[ "$EVENT_NAME" == "workflow_dispatch" || -z "$BASE_SHA" || "$BASE_SHA" =~ ^0+$ ]]; then
product=true
else
+5 -1
View File
@@ -58,7 +58,11 @@
"build": "yarn workspace dsh-community-market build && yarn workspace dsh-plugin-desktop build",
"typecheck": "yarn workspace dsh-plugin-desktop typecheck && yarn workspace dsh-community-market typecheck",
"test": "yarn workspace dsh-plugin-desktop test && yarn workspace dsh-community-market test",
"check:layout": "node scripts/verify-layout.mjs",
"test:bilingual-docs": "node --test scripts/bilingual-docs.test.mjs",
"check:bilingual-docs": "yarn test:bilingual-docs && node scripts/verify-bilingual-docs.mjs",
"test:architecture-gates": "node --test scripts/market-dependency-direction.test.mjs",
"check:architecture": "yarn test:architecture-gates && node scripts/verify-market-dependency-direction.mjs",
"check:layout": "yarn check:bilingual-docs && yarn check:architecture && node scripts/verify-layout.mjs",
"check": "yarn check:layout && yarn workspace dsh-community-fabric check && yarn workspace dsh-community-market check && yarn workspace dsh-plugin-desktop check",
"dev": "yarn workspace dsh-community-market build && yarn workspace dsh-plugin-desktop dev",
"start": "yarn workspace dsh-plugin-desktop start",
+102
View File
@@ -0,0 +1,102 @@
import { execFileSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path'
const HASH_PATTERN = /^[0-9a-f]{40}$/u
const DOCUMENT_PATTERN = /\.mdx?$/u
function normalizePath(path) {
return path.replaceAll('\\', '/')
}
function runGit(root, args) {
return execFileSync('git', args, {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
}
export function parseBilingualRecord(recordPath, content) {
const entries = []
for (const [index, line] of content.split(/\r?\n/u).entries()) {
const trimmed = line.trim()
if (trimmed.length === 0 || trimmed.startsWith('#')) continue
const match = /^([^:]+):\s*([0-9a-f]{40})$/u.exec(trimmed)
if (match === null) {
throw new Error(`${recordPath}:${String(index + 1)} is not a filename and Git blob hash`)
}
const filename = match[1]?.trim() ?? ''
const hash = match[2] ?? ''
if (filename !== basename(filename) || isAbsolute(filename) || !DOCUMENT_PATTERN.test(filename)) {
throw new Error(`${recordPath}:${String(index + 1)} must name one Markdown file beside the record`)
}
if (!HASH_PATTERN.test(hash)) {
throw new Error(`${recordPath}:${String(index + 1)} has an invalid Git blob hash`)
}
entries.push({ filename, hash })
}
if (entries.length !== 2) {
throw new Error(`${recordPath} must contain exactly two bilingual document records`)
}
if (entries[0]?.filename === entries[1]?.filename) {
throw new Error(`${recordPath} must reference two distinct bilingual documents`)
}
return entries
}
export function verifyBilingualRecords({ root, recordPaths, readText, hashDocument }) {
let documentCount = 0
const violations = []
for (const recordPath of recordPaths) {
const normalizedRecord = normalizePath(recordPath)
const recordDirectory = dirname(resolve(root, normalizedRecord))
let entries
try {
entries = parseBilingualRecord(normalizedRecord, readText(resolve(root, normalizedRecord)))
} catch (cause) {
violations.push(cause instanceof Error ? cause.message : String(cause))
continue
}
for (const entry of entries) {
const documentPath = resolve(recordDirectory, entry.filename)
const relativeDocument = normalizePath(relative(root, documentPath))
if (relativeDocument.startsWith('../') || isAbsolute(relativeDocument)) {
violations.push(`${normalizedRecord} references a document outside the repository`)
continue
}
try {
readText(documentPath)
const actual = hashDocument(relativeDocument)
if (actual !== entry.hash) {
violations.push(`${normalizedRecord} is stale for ${relativeDocument}: expected ${actual}, recorded ${entry.hash}`)
}
} catch (cause) {
violations.push(`${normalizedRecord} cannot verify ${relativeDocument}: ${cause instanceof Error ? cause.message : String(cause)}`)
}
documentCount += 1
}
}
if (violations.length > 0) {
throw new Error(`bilingual documentation gate failed:\n- ${violations.join('\n- ')}`)
}
return { recordCount: recordPaths.length, documentCount }
}
export function verifyTrackedBilingualRecords(root) {
const recordPaths = runGit(root, ['ls-files', '-z', '--', '*.i18n.yaml'])
.split('\0')
.filter(Boolean)
.map(normalizePath)
return verifyBilingualRecords({
root,
recordPaths,
readText: path => readFileSync(path, 'utf8'),
hashDocument: relativePath => runGit(root, [
'hash-object',
`--path=${relativePath}`,
'--',
relativePath,
]).trim(),
})
}
+85
View File
@@ -0,0 +1,85 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { resolve } from 'node:path'
import {
parseBilingualRecord,
verifyBilingualRecords,
} from './bilingual-docs.mjs'
test('parses exactly two Markdown blob records', () => {
assert.deepEqual(parseBilingualRecord('docs/topic.i18n.yaml', [
'# Both languages carry equal authority.',
`topic.md: ${'a'.repeat(40)}`,
`topic.zh.md: ${'b'.repeat(40)}`,
'',
].join('\n')), [
{ filename: 'topic.md', hash: 'a'.repeat(40) },
{ filename: 'topic.zh.md', hash: 'b'.repeat(40) },
])
})
test('rejects malformed, incomplete, and escaping bilingual records', () => {
assert.throws(
() => parseBilingualRecord('docs/topic.i18n.yaml', `topic.md: ${'a'.repeat(40)}\n`),
/exactly two/u,
)
assert.throws(
() => parseBilingualRecord('docs/topic.i18n.yaml', `topic.md = ${'a'.repeat(40)}\ntopic.zh.md: ${'b'.repeat(40)}\n`),
/not a filename/u,
)
assert.throws(
() => parseBilingualRecord('docs/topic.i18n.yaml', `../topic.md: ${'a'.repeat(40)}\ntopic.zh.md: ${'b'.repeat(40)}\n`),
/beside the record/u,
)
})
test('verifies every recorded document hash', () => {
const root = resolve('repository')
const recordPath = 'docs/topic.i18n.yaml'
const record = `topic.md: ${'a'.repeat(40)}\ntopic.zh.md: ${'b'.repeat(40)}\n`
const reads = new Map([
[resolve(root, recordPath), record],
[resolve(root, 'docs/topic.md'), 'English'],
[resolve(root, 'docs/topic.zh.md'), 'Chinese'],
])
const result = verifyBilingualRecords({
root,
recordPaths: [recordPath],
readText: path => {
const value = reads.get(path)
if (value === undefined) throw new Error(`missing ${path}`)
return value
},
hashDocument: path => path.endsWith('.zh.md') ? 'b'.repeat(40) : 'a'.repeat(40),
})
assert.deepEqual(result, { recordCount: 1, documentCount: 2 })
assert.throws(() => verifyBilingualRecords({
root,
recordPaths: [recordPath],
readText: path => reads.get(path) ?? '',
hashDocument: () => 'c'.repeat(40),
}), /is stale/u)
})
test('reports malformed records and missing documents in one failure', () => {
const root = resolve('repository')
const records = new Map([
[resolve(root, 'docs/broken.i18n.yaml'), 'not a record\n'],
[resolve(root, 'docs/missing.i18n.yaml'), [
`missing.md: ${'a'.repeat(40)}`,
`missing.zh.md: ${'b'.repeat(40)}`,
'',
].join('\n')],
])
assert.throws(() => verifyBilingualRecords({
root,
recordPaths: ['docs/broken.i18n.yaml', 'docs/missing.i18n.yaml'],
readText: path => {
const value = records.get(path)
if (value === undefined) throw new Error(`missing ${path}`)
return value
},
hashDocument: () => 'a'.repeat(40),
}), /broken\.i18n\.yaml:1[\s\S]*cannot verify docs\/missing\.md[\s\S]*cannot verify docs\/missing\.zh\.md/u)
})
+41
View File
@@ -0,0 +1,41 @@
import { execFileSync } from 'node:child_process'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
const IMPORT_SPECIFIER = /(?:\bfrom\s*|\bimport\s*\(\s*|\brequire\s*\(\s*|\bimport\s*)['"]([^'"]+)['"]/gu
const DESKTOP_MODULE = /(?:^|\/)dsh-plugin-desktop(?:\/|$)/u
function normalizePath(path) {
return path.replaceAll('\\', '/')
}
export function verifyMarketDependencyDirection(files) {
const violations = []
for (const [path, source] of files) {
for (const match of source.matchAll(IMPORT_SPECIFIER)) {
const specifier = match[1] ?? ''
if (DESKTOP_MODULE.test(normalizePath(specifier))) {
violations.push(normalizePath(path))
break
}
}
}
if (violations.length > 0) {
throw new Error(`Market source must not import Desktop implementation: ${violations.join(', ')}`)
}
return { fileCount: files.length }
}
export function verifyTrackedMarketDependencyDirection(root) {
const paths = execFileSync('git', ['ls-files', '-z', '--', 'dsh-community-market/src'], {
cwd: root,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'pipe'],
})
.split('\0')
.filter(path => /\.[cm]?[jt]sx?$/u.test(path))
.map(normalizePath)
return verifyMarketDependencyDirection(
paths.map(path => [path, readFileSync(resolve(root, path), 'utf8')]),
)
}
@@ -0,0 +1,21 @@
import assert from 'node:assert/strict'
import { test } from 'node:test'
import { verifyMarketDependencyDirection } from './market-dependency-direction.mjs'
test('allows product-name data in Market source', () => {
assert.deepEqual(verifyMarketDependencyDirection([
['dsh-community-market/src/install.ts', "const blocked = new Set(['dsh-plugin-desktop'])\n"],
]), { fileCount: 1 })
})
test('rejects Market imports of Desktop implementation', () => {
assert.throws(() => verifyMarketDependencyDirection([
['dsh-community-market/src/index.ts', "import type { DesktopRuntime } from 'dsh-plugin-desktop/runtime'\n"],
]), /must not import Desktop implementation/u)
assert.throws(() => verifyMarketDependencyDirection([
['dsh-community-market/src/index.ts', "const desktop = await import('dsh-plugin-desktop')\n"],
]), /must not import Desktop implementation/u)
assert.throws(() => verifyMarketDependencyDirection([
['dsh-community-market/src/index.ts', "export { runtime } from '../../dsh-plugin-desktop/src/runtime.js'\n"],
]), /must not import Desktop implementation/u)
})
+9
View File
@@ -0,0 +1,9 @@
import { resolve } from 'node:path'
import { verifyTrackedBilingualRecords } from './bilingual-docs.mjs'
const root = resolve(import.meta.dirname, '..')
const result = verifyTrackedBilingualRecords(root)
process.stdout.write(
`verify-bilingual-docs: ${String(result.recordCount)} records and ${String(result.documentCount)} documents are consistent\n`,
)
+1 -25
View File
@@ -1,6 +1,6 @@
import { execFileSync } from 'node:child_process'
import { existsSync, lstatSync, readFileSync, readlinkSync } from 'node:fs'
import { basename, resolve } from 'node:path'
import { resolve } from 'node:path'
const root = resolve(import.meta.dirname, '..')
const readJson = path => JSON.parse(readFileSync(resolve(root, path), 'utf8'))
@@ -17,10 +17,6 @@ const plugin = readJson('dsh-plugin-desktop/package.json')
const fabric = readJson('dsh-community-fabric/package.json')
const market = readJson('dsh-community-market/package.json')
const upstreamPackage = readJson('deepseek-harness/package.json')
const noteDirectory = '.agents/notes/implemented/process'
const noteName = '2026-08-15-pinned-upstream-and-isolated-yarn-workspace'
const notePaths = [`${noteDirectory}/${noteName}.md`, `${noteDirectory}/${noteName}.zh.md`]
const noteRecordPath = `${noteDirectory}/${noteName}.i18n.yaml`
if (workspace.packageManager !== '[email protected]') {
fail('the product workspace must pin [email protected]')
@@ -113,24 +109,4 @@ for (const name of Object.keys(plugin.dependencies).filter(name => name === '@de
}
}
const noteRecord = readFileSync(resolve(root, noteRecordPath), 'utf8')
for (const notePath of notePaths) {
// Hash the committed blob, not the working tree: checkout line endings
// differ per host, while HEAD:<path> is identical everywhere.
const expected = run('git', ['rev-parse', `HEAD:${notePath}`])
const recordLine = `${basename(notePath)}: ${expected}`
if (!noteRecord.split(/\r?\n/u).includes(recordLine)) {
fail(`${noteRecordPath} is stale for ${notePath}`)
}
}
const readmeRecord = readFileSync(resolve(root, 'README.i18n.yaml'), 'utf8')
for (const readmeName of ['README.md', 'README.en.md']) {
const expected = run('git', ['rev-parse', `HEAD:${readmeName}`])
const recordLine = `${readmeName}: ${expected}`
if (!readmeRecord.split(/\r?\n/u).includes(recordLine)) {
fail(`README.i18n.yaml is stale for ${readmeName}`)
}
}
process.stdout.write(`verify-layout: Yarn workspace and upstream ${upstream.commit.slice(0, 10)} are consistent\n`)
@@ -0,0 +1,9 @@
import { resolve } from 'node:path'
import { verifyTrackedMarketDependencyDirection } from './market-dependency-direction.mjs'
const root = resolve(import.meta.dirname, '..')
const result = verifyTrackedMarketDependencyDirection(root)
process.stdout.write(
`verify-market-dependency-direction: ${String(result.fileCount)} Market source files are independent of Desktop implementation\n`,
)