mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-08 17:36:57 +08:00
The Windows CI run failed because the note-hash check hashes the working tree with git hash-object, and a CRLF checkout changes the blob hash. Hash the committed blob via git rev-parse HEAD:<path> instead, which is identical on every host and keeps the recorded values valid. Also accept the Windows materialization of the CLAUDE.md symlink (a regular file holding the target name), and enforce LF for the outer working tree with a root .gitattributes, matching the submodule convention.
107 lines
4.9 KiB
JavaScript
107 lines
4.9 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import { existsSync, lstatSync, readFileSync, readlinkSync } from 'node:fs'
|
|
import { basename, resolve } from 'node:path'
|
|
|
|
const root = resolve(import.meta.dirname, '..')
|
|
const readJson = path => JSON.parse(readFileSync(resolve(root, path), 'utf8'))
|
|
const run = (command, args, cwd = root) => execFileSync(command, args, {
|
|
cwd,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim()
|
|
const fail = message => { throw new Error(`verify-layout: ${message}`) }
|
|
|
|
const workspace = readJson('package.json')
|
|
const upstream = readJson('upstream.json')
|
|
const plugin = readJson('dsh-plugin-desktop/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]')
|
|
}
|
|
if (JSON.stringify(workspace.workspaces) !== JSON.stringify(['dsh-plugin-desktop'])) {
|
|
fail('the root Yarn workspace must contain only dsh-plugin-desktop')
|
|
}
|
|
if (plugin.packageManager !== undefined) {
|
|
fail('dsh-plugin-desktop must inherit the root Yarn release')
|
|
}
|
|
const claudePath = resolve(root, 'CLAUDE.md')
|
|
const claudeStat = lstatSync(claudePath)
|
|
// Windows checkouts materialize the symlink as a regular file holding the
|
|
// target name; accept both forms so the pointer stays verified on every host.
|
|
const claudeTarget = claudeStat.isSymbolicLink()
|
|
? readlinkSync(claudePath)
|
|
: readFileSync(claudePath, 'utf8').trim()
|
|
if (claudeTarget !== 'AGENTS.md') {
|
|
fail('CLAUDE.md must link to the outer repository AGENTS.md')
|
|
}
|
|
for (const legacyFile of [
|
|
'pnpm-lock.yaml',
|
|
'pnpm-workspace.yaml',
|
|
'dsh-plugin-desktop/pnpm-lock.yaml',
|
|
'dsh-plugin-desktop/pnpm-workspace.yaml',
|
|
]) {
|
|
if (existsSync(resolve(root, legacyFile))) fail(`${legacyFile} must not exist`)
|
|
}
|
|
if (run('git', ['config', '-f', '.gitmodules', '--get', 'submodule.deepseek-harness.path']) !== 'deepseek-harness') {
|
|
fail('the upstream submodule path must be deepseek-harness')
|
|
}
|
|
if (run('git', ['config', '-f', '.gitmodules', '--get', 'submodule.deepseek-harness.url']) !== upstream.repository) {
|
|
fail('the upstream submodule URL differs from upstream.json')
|
|
}
|
|
if (typeof upstreamPackage.packageManager !== 'string' || !upstreamPackage.packageManager.startsWith('pnpm@')) {
|
|
fail('the upstream checkout must retain its pnpm package manager')
|
|
}
|
|
|
|
for (const [owner, manifest] of [['root', workspace], ['plugin', plugin]]) {
|
|
for (const field of ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies', 'resolutions']) {
|
|
for (const [name, range] of Object.entries(manifest[field] ?? {})) {
|
|
if (typeof range !== 'string') continue
|
|
if (/^(?:workspace|portal|link):/u.test(range)
|
|
|| (range.startsWith('file:') && range.includes('deepseek-harness'))) {
|
|
fail(`${owner} ${field}.${name} bypasses the published DSH package boundary`)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const [mode, object] = run('git', ['ls-files', '--stage', '--', 'deepseek-harness']).split(/\s+/u)
|
|
if (mode !== '160000') fail('deepseek-harness must be tracked as a Git submodule')
|
|
if (object !== upstream.commit) fail(`submodule index is ${object}, expected ${upstream.commit}`)
|
|
|
|
const upstreamDir = resolve(root, 'deepseek-harness')
|
|
if (run('git', ['rev-parse', 'HEAD'], upstreamDir) !== upstream.commit) {
|
|
fail('checked-out upstream commit differs from upstream.json')
|
|
}
|
|
if (run('git', ['status', '--porcelain'], upstreamDir) !== '') {
|
|
fail('deepseek-harness contains local changes')
|
|
}
|
|
if (run('git', ['remote', 'get-url', 'origin'], upstreamDir) !== upstream.repository) {
|
|
fail('deepseek-harness origin differs from upstream.json')
|
|
}
|
|
if (upstreamPackage.version !== upstream.sourceVersion) {
|
|
fail('deepseek-harness package version differs from upstream.json')
|
|
}
|
|
for (const name of Object.keys(plugin.dependencies).filter(name => name === '@deepseek-ai/dsh' || name.startsWith('@deepseek-ai/dsh-'))) {
|
|
if (plugin.dependencies[name] !== upstream.runtimePackageVersion) {
|
|
fail(`${name} must use the recorded DSH runtime package family`)
|
|
}
|
|
}
|
|
|
|
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('\n').includes(recordLine)) {
|
|
fail(`${noteRecordPath} is stale for ${notePath}`)
|
|
}
|
|
}
|
|
|
|
process.stdout.write(`verify-layout: Yarn workspace and upstream ${upstream.commit.slice(0, 10)} are consistent\n`)
|