dsh-openwrite 0.2.28 → 0.2.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +49 -8
- package/THIRD_PARTY_NOTICES.md +4 -3
- package/companions.mjs +508 -0
- package/cordis.patch.yml +0 -2
- package/docs/CLOSED_ISSUE_FOLLOWUPS.md +1 -1
- package/docs/COMPANION_PLUGINS.md +142 -0
- package/docs/CONTEXT_SLOTS.md +1 -1
- package/docs/COST_MEASUREMENT.md +2 -2
- package/docs/DEVELOPMENT.md +6 -11
- package/docs/DSH_STORE.md +14 -2
- package/docs/EDITOR_SAFETY_REPAIRS.md +2 -2
- package/docs/INSTALL.md +112 -4
- package/docs/ISSUES_121_123.md +4 -4
- package/docs/ISSUES_140_142.md +58 -0
- package/docs/ISSUES_149_150_151.md +77 -0
- package/docs/ISSUE_123_FOLLOWUP.md +3 -2
- package/docs/ISSUE_146_REVIEW_EVIDENCE.md +53 -0
- package/docs/LEARNING.md +1 -1
- package/docs/MOBILE_READINESS.md +62 -0
- package/docs/PLUGIN_MAINTENANCE.md +1 -3
- package/docs/RELEASE_ACCEPTANCE.md +20 -3
- package/docs/REVIEW_ROUND_22.md +67 -0
- package/docs/REVIEW_ROUND_23.md +48 -0
- package/docs/REVIEW_ROUND_25.md +51 -0
- package/docs/SELECTION_UPGRADE_REPAIRS.md +2 -2
- package/docs/TOOLS.md +181 -604
- package/docs/WORKFLOWS.md +5 -5
- package/package.json +17 -21
- package/packages/openwrite-bridge/lib/client.d.ts +0 -1
- package/packages/openwrite-bridge/lib/client.js +90 -9
- package/packages/openwrite-bridge/lib/contracts-generated.js +2 -1
- package/packages/openwrite-bridge/lib/dog-delivery.d.ts +1 -0
- package/packages/openwrite-bridge/lib/dog-delivery.js +3 -1
- package/packages/openwrite-bridge/lib/dog-review.js +15 -3
- package/packages/openwrite-bridge/lib/domain.d.ts +27 -0
- package/packages/openwrite-bridge/lib/domain.js +250 -29
- package/packages/openwrite-bridge/lib/host-llm.d.ts +188 -0
- package/packages/openwrite-bridge/lib/host-llm.js +657 -0
- package/packages/openwrite-bridge/lib/host-web.d.ts +61 -0
- package/packages/openwrite-bridge/lib/host-web.js +166 -0
- package/packages/openwrite-bridge/lib/index.d.ts +4 -0
- package/packages/openwrite-bridge/lib/index.js +150 -6
- package/packages/openwrite-bridge/lib/managed-runtime.d.ts +47 -2
- package/packages/openwrite-bridge/lib/managed-runtime.js +176 -34
- package/packages/openwrite-bridge/lib/tool-policy.js +37 -12
- package/packages/openwrite-bridge/lib/tools.js +1223 -1227
- package/packages/openwrite-bridge/package.json +7 -5
- package/packages/studio-panel/lib/client.js +123207 -121534
- package/packages/studio-panel/lib/client.js.map +1 -1
- package/packages/studio-panel/lib/types/client/LaunchOpenWrite.d.ts +10 -0
- package/packages/studio-panel/lib/types/client/ModelView.d.ts +28 -4
- package/packages/studio-panel/lib/types/client/OpenWriteSettings.d.ts +64 -0
- package/packages/studio-panel/lib/types/client/SceneWorkbench.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/SearchSettings.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/SerialWritingView.d.ts +2 -1
- package/packages/studio-panel/lib/types/client/TurnMutationSummary.d.ts +2 -0
- package/packages/studio-panel/lib/types/client/VditorBody.d.ts +1 -1
- package/packages/studio-panel/lib/types/client/api.d.ts +41 -2
- package/packages/studio-panel/lib/types/client/browser-download.d.ts +4 -0
- package/packages/studio-panel/lib/types/client/companions.d.ts +46 -0
- package/packages/studio-panel/lib/types/client/dto.d.ts +22 -0
- package/packages/studio-panel/lib/types/client/locales.d.ts +1 -1
- package/packages/studio-panel/lib/types/client/workspace-chrome.d.ts +41 -0
- package/packages/studio-panel/package.json +5 -3
- package/plugin.mjs +159 -20
- package/preset-definition.mjs +144 -4
- package/presets/openwrite/agent.cordis.yml +7 -9
- package/presets/openwrite/ops-integration.mjs +28 -0
- package/presets/openwrite/preset.yml +2 -1
- package/presets/openwrite/skills/canon/SKILL.md +3 -3
- package/presets/openwrite/skills/delivery-artifact-query/SKILL.md +41 -0
- package/presets/openwrite/skills/dialoguequality/SKILL.md +16 -17
- package/presets/openwrite/skills/dog-delivery-verification/SKILL.md +113 -0
- package/presets/openwrite/skills/export-book/SKILL.md +3 -3
- package/presets/openwrite/skills/foreshadow/SKILL.md +1 -1
- package/presets/openwrite/skills/foreshadowing-system/SKILL.md +16 -25
- package/presets/openwrite/skills/import-artifact-query/SKILL.md +30 -0
- package/presets/openwrite/skills/learn/SKILL.md +3 -3
- package/presets/openwrite/skills/novel-creator/SKILL.md +10 -10
- package/presets/openwrite/skills/novel-reviewer/SKILL.md +11 -11
- package/presets/openwrite/skills/progress/SKILL.md +1 -1
- package/presets/openwrite/skills/review-artifact-query/SKILL.md +41 -0
- package/presets/openwrite/skills/review-chapter/SKILL.md +2 -2
- package/presets/openwrite/skills/revise-span/SKILL.md +5 -5
- package/presets/openwrite/skills/style-system/SKILL.md +13 -6
- package/presets/openwrite/skills/truth-validation/SKILL.md +9 -12
- package/presets/openwrite/skills/workflow-manager/SKILL.md +13 -13
- package/presets/openwrite/skills/world-query/SKILL.md +16 -11
- package/presets/openwrite/skills/write-next/SKILL.md +1 -1
- package/release/{openwrite-5.8.20-py3-none-any.whl → openwrite-5.8.30-py3-none-any.whl} +0 -0
- package/release/runtime-manifest.json +4 -13
- package/release/source.json +21 -21
- package/release/wheels/licenses/OPENSSL-3.6.3-LICENSE.txt +225 -0
- package/scripts/maintenance.mjs +67 -7
- package/scripts/plugin-doctor.mjs +179 -0
- package/scripts/prepare.mjs +11 -2
- package/scripts/safe-upgrade.mjs +38 -5
- package/dog-tools.mjs +0 -13
- package/presets/openwrite/skills/dog-delivery-query/SKILL.md +0 -42
- package/presets/openwrite/skills/dog-import-query/SKILL.md +0 -29
- package/presets/openwrite/skills/dog-review-query/SKILL.md +0 -42
- package/scripts/dog/delivery-stage.js +0 -32
- package/scripts/dog/import-chapter.js +0 -20
- package/scripts/dog/import-record.js +0 -35
- package/scripts/dog/review-dimension.js +0 -41
- package/scripts/dog/review-record.js +0 -42
- package/vendor/dsh-dog/LICENSE +0 -29
- package/vendor/dsh-dog/README.md +0 -114
- package/vendor/dsh-dog/UPSTREAM.md +0 -30
- package/vendor/dsh-dog/cordis.patch.yml +0 -14
- package/vendor/dsh-dog/lib/client.js +0 -2960
- package/vendor/dsh-dog/lib/client.js.map +0 -1
- package/vendor/dsh-dog/lib/core--byUPJL8.js +0 -2191
- package/vendor/dsh-dog/lib/core-BiEBMLz3.d.ts +0 -495
- package/vendor/dsh-dog/lib/core.d.ts +0 -2
- package/vendor/dsh-dog/lib/core.js +0 -2
- package/vendor/dsh-dog/lib/index.d.ts +0 -143
- package/vendor/dsh-dog/lib/index.js +0 -1230
- package/vendor/dsh-dog/package.json +0 -110
- package/vendor/dsh-dog/schemas/schema-0.2/graph.schema.json +0 -139
- package/vendor/dsh-dog/schemas/schema-0.2/report.schema.json +0 -87
- package/vendor/dsh-dog/schemas/schema-0.2/run.schema.json +0 -259
- package/vendor/dsh-dog/schemas/schema-0.2/runtime-event.schema.json +0 -105
- package/vendor/dsh-dog/schemas/schema-0.2/verification.schema.json +0 -40
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Offline maintenance checks. Never reads credentials, starts a model, or edits profiles.
|
|
3
|
+
import { readFile, realpath } from 'node:fs/promises'
|
|
4
|
+
import { execFileSync } from 'node:child_process'
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
6
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
7
|
+
import { homedir } from 'node:os'
|
|
8
|
+
import { load } from 'js-yaml'
|
|
9
|
+
import { inspectProfile } from './maintenance.mjs'
|
|
10
|
+
import { detectCompanions } from '../companions.mjs'
|
|
11
|
+
|
|
12
|
+
const root = fileURLToPath(new URL('../', import.meta.url))
|
|
13
|
+
const packageDirs = ['.', 'packages/openwrite-bridge', 'packages/studio-panel']
|
|
14
|
+
const isDsh = name => /^@deepseek-ai\/dsh(?:-|$)/.test(name)
|
|
15
|
+
const lockName = path => path.match(/(?:^|\/)node_modules\/(@deepseek-ai\/[^/]+)$/)?.[1]
|
|
16
|
+
const readJson = async path => JSON.parse(await readFile(path, 'utf8'))
|
|
17
|
+
const exactVersion = value => /^\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(value ?? '')
|
|
18
|
+
|
|
19
|
+
export function auditVersions(manifest, lock, baseline, graph = {}) {
|
|
20
|
+
const errors = []
|
|
21
|
+
if (!exactVersion(baseline)) errors.push('DSH baseline must be an exact version')
|
|
22
|
+
for (const field of ['dependencies', 'devDependencies', 'peerDependencies']) {
|
|
23
|
+
const declared = manifest[field] ?? {}
|
|
24
|
+
const locked = lock.packages?.['']?.[field] ?? {}
|
|
25
|
+
for (const name of new Set([...Object.keys(declared), ...Object.keys(locked)])) {
|
|
26
|
+
if (declared[name] !== locked[name]) errors.push(`lock metadata differs: ${field}.${name}`)
|
|
27
|
+
// Build dependencies remain pinned; public peers may enumerate verified hosts.
|
|
28
|
+
const peerVersions = typeof declared[name] === 'string' ? declared[name].split(' || ') : []
|
|
29
|
+
const verified = manifest.dsh?.compatibility?.dshReleases ?? {}
|
|
30
|
+
const compatiblePeer = field === 'peerDependencies' && peerVersions.length > 0
|
|
31
|
+
&& peerVersions.includes(graph[name] ?? baseline)
|
|
32
|
+
&& peerVersions.every(value => exactVersion(value)
|
|
33
|
+
&& (value === (graph[name] ?? baseline) || verified[value] === 'compatible'))
|
|
34
|
+
if (isDsh(name) && declared[name] !== undefined && declared[name] !== (graph[name] ?? baseline) && !compatiblePeer) {
|
|
35
|
+
errors.push(`unpinned DSH dependency: ${name}=${declared[name]}`)
|
|
36
|
+
}
|
|
37
|
+
if (isDsh(name) && declared[name] !== undefined
|
|
38
|
+
&& (field !== 'peerDependencies' || !manifest.peerDependenciesMeta?.[name]?.optional)
|
|
39
|
+
&& !lock.packages?.[`node_modules/${name}`]) {
|
|
40
|
+
errors.push(`required SDK missing from lock: ${name}`)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
for (const [path, entry] of Object.entries(lock.packages ?? {})) {
|
|
45
|
+
const name = lockName(path)
|
|
46
|
+
if (name && isDsh(name) && entry.version !== (graph[name] ?? baseline)) {
|
|
47
|
+
errors.push(`DSH lock drift: ${name}=${entry.version}, expected ${baseline}`)
|
|
48
|
+
}
|
|
49
|
+
if (name === '@deepseek-ai/cordis' && entry.version !== (graph[name] ?? '4.0.2')) {
|
|
50
|
+
errors.push(`Cordis lock drift: ${entry.version}, expected ${graph[name] ?? '4.0.2'}`)
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!lock.packages?.['']) errors.push('missing lockfile root metadata')
|
|
54
|
+
return errors
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const exportPaths = value => typeof value === 'string' ? [value]
|
|
58
|
+
: value && typeof value === 'object' ? Object.values(value).flatMap(exportPaths) : []
|
|
59
|
+
const packagePath = path => path?.replace(/^\.\//, '')
|
|
60
|
+
|
|
61
|
+
export function auditBundle(manifest, files, patch) {
|
|
62
|
+
const errors = []
|
|
63
|
+
const available = new Set(files)
|
|
64
|
+
const required = [manifest.main, manifest.types, manifest.dsh?.bundle?.patch,
|
|
65
|
+
...exportPaths(manifest.exports)]
|
|
66
|
+
if (!manifest.dsh?.bundle?.patch) errors.push('missing dsh.bundle.patch')
|
|
67
|
+
if (!manifest.main || !manifest.types) errors.push('missing host JS/types entry')
|
|
68
|
+
for (const path of required.filter(Boolean)) {
|
|
69
|
+
if (isAbsolute(path) || path.split(/[\\/]/).includes('..') || path.includes('\\')) {
|
|
70
|
+
errors.push(`entry escapes package: ${path}`)
|
|
71
|
+
} else if (!available.has(packagePath(path))) errors.push(`entry missing from tarball: ${path}`)
|
|
72
|
+
}
|
|
73
|
+
const rows = Array.isArray(patch) ? patch.flatMap(op => op?.insert ?? []) : []
|
|
74
|
+
if (rows.filter(row => row.name === manifest.name).length !== 1) {
|
|
75
|
+
errors.push('bundle patch must mount its host package exactly once')
|
|
76
|
+
}
|
|
77
|
+
const ids = rows.map(row => row.id)
|
|
78
|
+
if (ids.some(id => typeof id !== 'string' || !id) || new Set(ids).size !== ids.length) {
|
|
79
|
+
errors.push('bundle patch has missing/duplicate row ids')
|
|
80
|
+
}
|
|
81
|
+
if (manifest.dsh?.client || manifest.exports?.['./client']) {
|
|
82
|
+
if (manifest.dsh?.client?.platform !== 'web' || !manifest.exports?.['./client']) {
|
|
83
|
+
errors.push('web client needs platform=web and ./client export')
|
|
84
|
+
}
|
|
85
|
+
// The host route serves these on demand; a source-tree-only build can mask omissions.
|
|
86
|
+
for (const path of ['LICENSE', 'dist/index.css', 'dist/index.min.js',
|
|
87
|
+
'dist/css/content-theme/dark.css', 'dist/css/content-theme/light.css',
|
|
88
|
+
'dist/js/lute/lute.min.js', 'dist/js/icons/ant.js', 'dist/js/i18n/zh_CN.js']) {
|
|
89
|
+
if (!available.has(`vendor/vditor/${path}`)) errors.push(`editor asset missing from tarball: ${path}`)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return errors
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function checkProfiles(report) {
|
|
96
|
+
const home = process.env.DSH_HOME ?? join(process.env.HOME ?? '', '.dsh')
|
|
97
|
+
for (const profile of ['web', 'headless']) {
|
|
98
|
+
const dir = join(home, 'profiles', profile)
|
|
99
|
+
await report(`profile ${profile}`, async () => {
|
|
100
|
+
const manifest = await readJson(join(dir, 'package.json'))
|
|
101
|
+
const name = 'dsh-openwrite'
|
|
102
|
+
if (manifest.dsh?.profile?.bundles?.filter(n => n === name).length !== 1) throw new Error('standard bundle not installed exactly once')
|
|
103
|
+
const installed = await realpath(join(dir, 'node_modules', name))
|
|
104
|
+
const pkg = await readJson(join(installed, 'package.json'))
|
|
105
|
+
if (pkg.name !== name || !pkg.dsh?.bundle?.patch) throw new Error('invalid installed bundle')
|
|
106
|
+
await readFile(join(installed, 'release/runtime-manifest.json'))
|
|
107
|
+
const leftover = inspectProfile(manifest).leftover
|
|
108
|
+
if (leftover.length) throw new Error(`legacy packages still in bundles or dependencies (${leftover.join(', ')}); run openwrite-maintenance migrate --apply with dsh stopped`)
|
|
109
|
+
})
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function doctor({ profiles = false } = {}) {
|
|
114
|
+
let failures = 0
|
|
115
|
+
const report = async (label, check) => {
|
|
116
|
+
try { await check(); console.log(`PASS ${label}`) }
|
|
117
|
+
catch (error) { failures++; console.error(`FAIL ${label}: ${error.message}`) }
|
|
118
|
+
}
|
|
119
|
+
const assertClean = errors => { if (errors.length) throw new Error(errors.slice(0, 8).join('; ') + (errors.length > 8 ? `; +${errors.length - 8} more` : '')) }
|
|
120
|
+
const rootManifest = await readJson(join(root, 'package.json'))
|
|
121
|
+
const compatibility = await readJson(join(root, 'release/sdk-compatibility.json'))
|
|
122
|
+
const baseline = rootManifest.devDependencies?.['@deepseek-ai/dsh']
|
|
123
|
+
console.log(`dsh-Openwrite doctor — DSH ${baseline}, Cordis 4.0.2 (offline)`)
|
|
124
|
+
await report('Node >= 22.19.0', () => {
|
|
125
|
+
const [major, minor] = process.versions.node.split('.').map(Number)
|
|
126
|
+
if (major < 22 || (major === 22 && minor < 19)) throw new Error(`found ${process.versions.node}`)
|
|
127
|
+
})
|
|
128
|
+
for (const relative of packageDirs) {
|
|
129
|
+
const dir = resolve(root, relative)
|
|
130
|
+
const manifest = await readJson(join(dir, 'package.json'))
|
|
131
|
+
const lock = await readJson(join(dir, 'package-lock.json'))
|
|
132
|
+
await report(`${relative}: manifest + lock compatibility`, () => assertClean(auditVersions(manifest, lock, baseline, compatibility.packages)))
|
|
133
|
+
await report(`${relative}: installed SDK matches lock`, async () => {
|
|
134
|
+
const errors = []
|
|
135
|
+
for (const [path, entry] of Object.entries(lock.packages ?? {})) {
|
|
136
|
+
const name = lockName(path)
|
|
137
|
+
if (!name || (!isDsh(name) && name !== '@deepseek-ai/cordis')) continue
|
|
138
|
+
try {
|
|
139
|
+
const installed = await readJson(join(dir, path, 'package.json'))
|
|
140
|
+
if (installed.version !== entry.version) errors.push(`${name}=${installed.version}, lock=${entry.version}`)
|
|
141
|
+
} catch { errors.push(`missing ${name}; run npm ci in ${relative}`) }
|
|
142
|
+
}
|
|
143
|
+
assertClean(errors)
|
|
144
|
+
})
|
|
145
|
+
if (relative === '.') continue
|
|
146
|
+
await report(`${manifest.name}: packed entries + patch + assets`, async () => {
|
|
147
|
+
const packed = JSON.parse(execFileSync('npm', ['pack', '--dry-run', '--json', '--ignore-scripts'],
|
|
148
|
+
{ cwd: dir, encoding: 'utf8', timeout: 30000, stdio: ['ignore', 'pipe', 'pipe'] }))
|
|
149
|
+
const patch = load(await readFile(join(dir, manifest.dsh.bundle.patch), 'utf8'))
|
|
150
|
+
assertClean(auditBundle(manifest, packed[0].files.map(file => file.path), patch))
|
|
151
|
+
const host = await import(pathToFileURL(join(dir, manifest.main)).href)
|
|
152
|
+
if (host.name !== manifest.name || typeof host.apply !== 'function') throw new Error('host must export name + apply')
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
if (profiles) await checkProfiles(report)
|
|
156
|
+
await report('companion plugins', async () => {
|
|
157
|
+
const home = process.env.DSH_HOME?.trim() || join(homedir(), '.dsh')
|
|
158
|
+
const companions = await detectCompanions(home)
|
|
159
|
+
const lines = []
|
|
160
|
+
if (companions.dog.installed) lines.push(`dsh-dog ${companions.dog.usable ? 'active' : `inactive (${companions.dog.reason})`}`)
|
|
161
|
+
if (companions.ops.installed) lines.push(`dsh-ops ${companions.ops.usable ? 'active' : `inactive (${companions.ops.reason})`}`)
|
|
162
|
+
if (companions.freeModel.installed) lines.push(`dsh-our-free-model ${companions.freeModel.usable ? 'active' : `inactive (${companions.freeModel.reason})`}`)
|
|
163
|
+
if (companions.host === null && (companions.dog.installed || companions.ops.installed || companions.freeModel.installed)) {
|
|
164
|
+
throw new Error(`host version unresolved; companion decisions stay fail-closed (${lines.join(', ')})`)
|
|
165
|
+
}
|
|
166
|
+
console.log(` ${lines.join(', ') || 'none installed'}`)
|
|
167
|
+
})
|
|
168
|
+
console.log(failures ? `${failures} check(s) failed.` : 'All checks passed. No model or live-service call was made.')
|
|
169
|
+
return failures ? 1 : 0
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
173
|
+
const unknown = process.argv.slice(2).filter(arg => arg !== '--profiles')
|
|
174
|
+
if (unknown.length) { console.error('Usage: node scripts/plugin-doctor.mjs [--profiles]'); process.exitCode = 2 }
|
|
175
|
+
else {
|
|
176
|
+
try { process.exitCode = await doctor({ profiles: process.argv.includes('--profiles') }) }
|
|
177
|
+
catch (error) { console.error(`FAIL ${error.message}`); process.exitCode = 1 }
|
|
178
|
+
}
|
|
179
|
+
}
|
package/scripts/prepare.mjs
CHANGED
|
@@ -7,7 +7,7 @@ const root = fileURLToPath(new URL('../', import.meta.url))
|
|
|
7
7
|
const manifest = JSON.parse(await readFile(new URL('../release/runtime-manifest.json', import.meta.url), 'utf8'))
|
|
8
8
|
let built = false
|
|
9
9
|
await access(new URL('../release/' + manifest.wheel.file, import.meta.url))
|
|
10
|
-
for (const dir of ['packages/openwrite-bridge', 'packages/studio-panel'
|
|
10
|
+
for (const dir of ['packages/openwrite-bridge', 'packages/studio-panel']) {
|
|
11
11
|
try { await access(new URL(`../${dir}/lib/index.js`, import.meta.url)); await access(new URL(`../${dir}/lib/${dir.endsWith('openwrite-bridge') ? 'preset-tools.js' : 'client.js'}`, import.meta.url)); continue } catch {}
|
|
12
12
|
built = true
|
|
13
13
|
for (const args of [['ci', '--ignore-scripts', '--no-audit', '--no-fund'], ['run', 'build']]) {
|
|
@@ -15,7 +15,16 @@ for (const dir of ['packages/openwrite-bridge', 'packages/studio-panel', 'vendor
|
|
|
15
15
|
if (result.status !== 0) throw new Error(`${dir}: ${args.join(' ')} failed`)
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
|
|
18
|
+
// Provenance must be refreshed whenever the source tree no longer matches the
|
|
19
|
+
// recorded hash, not merely when this run happened to build. Gating on `built`
|
|
20
|
+
// alone means "edit src/, then pack without rebuilding" publishes a tarball
|
|
21
|
+
// whose release/source.json still names the previous tree — and that file is
|
|
22
|
+
// gitignored, so release-report.mjs's dirty check cannot see it either.
|
|
23
|
+
let provenanceStale = true
|
|
24
|
+
try {
|
|
25
|
+
provenanceStale = !JSON.parse(await readFile(new URL('../release/source.json', import.meta.url), 'utf8')).sourceTreeSha256
|
|
26
|
+
} catch { /* missing or unreadable: regenerate */ }
|
|
27
|
+
if (built || provenanceStale) {
|
|
19
28
|
const result = spawnSync(process.execPath, [fileURLToPath(new URL('./release-provenance.mjs', import.meta.url))], { cwd: root, stdio: 'inherit' })
|
|
20
29
|
if (result.status !== 0) throw new Error('Release provenance generation failed')
|
|
21
30
|
}
|
package/scripts/safe-upgrade.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Offline profile upgrades retain the previous complete dependency tree until commit.
|
|
3
|
-
import { cp, mkdir, readFile, readdir, rename, writeFile, open, access, readlink, symlink, unlink, realpath, lstat, rmdir, utimes } from 'node:fs/promises'
|
|
3
|
+
import { cp, mkdir, readFile, readdir, rename, writeFile, open, access, readlink, symlink, unlink, realpath, lstat, rmdir, rm, utimes } from 'node:fs/promises'
|
|
4
4
|
import { spawn, spawnSync } from 'node:child_process'
|
|
5
5
|
import { join, resolve, relative, isAbsolute, dirname, sep } from 'node:path'
|
|
6
6
|
import { randomUUID, createHash } from 'node:crypto'
|
|
@@ -163,6 +163,16 @@ async function runInstall(home, profile, spec, log) {
|
|
|
163
163
|
} finally { clearTimeout(timer); clearTimeout(drainTimer); clearTimeout(escalation); await writes }
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
+
/**
|
|
167
|
+
* `.plugin-manager` is host-owned state, not ours: it must never be staged, or a
|
|
168
|
+
* POSIX upgrade would commit a snapshot of it that later upgrades then restore.
|
|
169
|
+
* Compared on a normalized relative path so the check is separator-agnostic —
|
|
170
|
+
* `path.join` yields `\` on win32, which a literal '/' test can never match.
|
|
171
|
+
*/
|
|
172
|
+
function isHostManagedState(relativePath) {
|
|
173
|
+
return relativePath === '.plugin-manager' || relativePath.startsWith(`.plugin-manager${sep}`)
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
function profileDirectory(home, profile) {
|
|
167
177
|
if (!PROFILE.test(profile)) throw new Error('Invalid profile name')
|
|
168
178
|
return join(resolve(home), 'profiles', profile)
|
|
@@ -175,7 +185,8 @@ export async function recoverUpgrade(home, operationId, options = {}) {
|
|
|
175
185
|
const operation = JSON.parse(await readFile(join(root, 'operation.json'), 'utf8'))
|
|
176
186
|
const target = profileDirectory(home, operation.profile)
|
|
177
187
|
await (options.assertStopped || assertHostStopped)(home, operation.profile)
|
|
178
|
-
|
|
188
|
+
|
|
189
|
+
const previous = join(root, 'previous-profile')
|
|
179
190
|
const lease = await acquireRecoveryLock(home)
|
|
180
191
|
try {
|
|
181
192
|
if (!await exists(target) && await exists(previous)) {
|
|
@@ -200,6 +211,9 @@ export async function recoverUpgrade(home, operationId, options = {}) {
|
|
|
200
211
|
/** Stage a complete profile beside the active tree, validate, then swap while stopped. */
|
|
201
212
|
export async function upgradeProfile(home, profile, spec, options = {}) {
|
|
202
213
|
if (typeof spec !== 'string' || !spec || spec.startsWith('-') || /[\r\n]/.test(spec)) throw new Error('A package spec or Release tarball is required')
|
|
214
|
+
// A bare add can preserve a profile's old exact dependency/lock selection
|
|
215
|
+
// even after npm latest moved (#142). An upgrade must request a new version.
|
|
216
|
+
if (spec === 'dsh-openwrite') spec = 'dsh-openwrite@latest'
|
|
203
217
|
const target = profileDirectory(home, profile)
|
|
204
218
|
const stopped = options.assertStopped || assertHostStopped
|
|
205
219
|
await stopped(home, profile)
|
|
@@ -222,7 +236,7 @@ export async function upgradeProfile(home, profile, spec, options = {}) {
|
|
|
222
236
|
operation.old_manifest_sha256 = digest(oldManifest)
|
|
223
237
|
await save(join(root, 'operation.json'), operation)
|
|
224
238
|
await cp(target, staging, { recursive: true, dereference: false, verbatimSymlinks: true,
|
|
225
|
-
filter: source => !
|
|
239
|
+
filter: source => !isHostManagedState(relative(target, source)) })
|
|
226
240
|
await relocateProfileLinks(staging, target, staging, yaml)
|
|
227
241
|
// A downloaded tarball may live in a disposable directory. Pin its bytes
|
|
228
242
|
// below DSH_HOME before the installer writes file: references into locks.
|
|
@@ -266,7 +280,16 @@ export async function upgradeProfile(home, profile, spec, options = {}) {
|
|
|
266
280
|
await save(join(root, 'operation.json'), operation)
|
|
267
281
|
error.message += `; operation=${id}; log=${logPath}`
|
|
268
282
|
throw error
|
|
269
|
-
} finally {
|
|
283
|
+
} finally {
|
|
284
|
+
try { await log?.close() } finally {
|
|
285
|
+
// The staging directory is a full profile copy including node_modules, so
|
|
286
|
+
// every failed upgrade used to leave hundreds of MB behind with nothing to
|
|
287
|
+
// reclaim it. On success it has been renamed onto the target, so removing
|
|
288
|
+
// it here is a no-op; on any failure path it is reclaimed.
|
|
289
|
+
try { await rm(staging, { recursive: true, force: true }) } catch { /* best effort */ }
|
|
290
|
+
await release()
|
|
291
|
+
}
|
|
292
|
+
}
|
|
270
293
|
}
|
|
271
294
|
|
|
272
295
|
/** Disable only an incomplete OpenWrite bundle so a stopped profile can boot for repair. */
|
|
@@ -297,7 +320,17 @@ if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1]
|
|
|
297
320
|
const value = name => { const i = process.argv.indexOf(name); return i < 0 ? undefined : process.argv[i + 1] }
|
|
298
321
|
const home = resolve(process.env.DSH_HOME || join(homedir(), '.dsh'))
|
|
299
322
|
try {
|
|
300
|
-
|
|
323
|
+
// `--profile` as the last argument yields undefined, and `|| 'web'` then
|
|
324
|
+
// silently operates on `web` and exits 0 - the same bug fixed in
|
|
325
|
+
// maintenance.mjs. A trailing flag is a usage error, not a default.
|
|
326
|
+
const profileIndex = process.argv.indexOf('--profile')
|
|
327
|
+
if (profileIndex >= 0) {
|
|
328
|
+
const named = process.argv[profileIndex + 1]
|
|
329
|
+
if (named === undefined || named === '') {
|
|
330
|
+
throw new Error('Usage: openwrite-safe-upgrade [--upgrade|--repair|--recover <id>] --profile <name> [--apply] [--package <path>]')
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const profile = profileIndex < 0 ? 'web' : process.argv[profileIndex + 1]
|
|
301
334
|
const result = value('--recover') ? await recoverUpgrade(home, value('--recover'))
|
|
302
335
|
: process.argv.includes('--repair') ? await repairProfile(home, profile, process.argv.includes('--apply'))
|
|
303
336
|
: await upgradeProfile(home, profile, value('--package'))
|
package/dog-tools.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export const name = '@dsh-novel/openwrite-dog-tools'
|
|
2
|
-
export const inject = ['tools', 'openwriteDog']
|
|
3
|
-
export function apply(ctx) {
|
|
4
|
-
for (const tool of ctx.openwriteDog.tools) ctx.effect(() => ctx.tools.register(tool))
|
|
5
|
-
ctx.effect(() => ctx.tools.guard(execution => {
|
|
6
|
-
if (!execution.name.startsWith('dog_') || ['dog_status', 'dog_wait', 'dog_validate'].includes(execution.name)) return
|
|
7
|
-
if (!execution.agent) return 'DOG_AGENT_SESSION_REQUIRED: DoG execution requires an Agent session; run this tool inside a writing Agent session.'
|
|
8
|
-
if (execution.agent.ctx.get('planMode')?.get(execution.agent)?.active) return 'DOG_PLAN_MODE: DoG execution is disabled in plan mode; leave plan mode before running it.'
|
|
9
|
-
const policy = ctx.get('sandboxPolicy')
|
|
10
|
-
if (!policy) return 'DOG_SANDBOX_POLICY_UNAVAILABLE: The host sandbox policy service is unavailable; check the host configuration.'
|
|
11
|
-
if (policy.resolve({ session: execution.agent.session }).mode === 'read-only') return 'DOG_READ_ONLY: DoG execution requires a writable session; the current sandbox is read-only.'
|
|
12
|
-
}))
|
|
13
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dog-delivery-query
|
|
3
|
-
description: Query a chapter delivery DoG graph across manuscript, review, revision application, and re-review closure. Use after writing or reviewing a chapter, after revision actions, or when the user asks whether a chapter is truly ready to deliver.
|
|
4
|
-
user-invocable: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Language: follow the author's current conversation language for responses and help; preserve the manuscript language. Translate these instructions when presenting them.
|
|
8
|
-
|
|
9
|
-
# 章节交付 DoG 查询
|
|
10
|
-
|
|
11
|
-
章节写作、评审和修订工具会生成:
|
|
12
|
-
|
|
13
|
-
`data/novels/{id}/data/dog/deliveries/{chapter}/dog-graph.json`
|
|
14
|
-
|
|
15
|
-
这张图查询六个阶段:`writing → review → revision → application → rereview → closure`。
|
|
16
|
-
它不替代 OpenWrite 的写章、六域累加评审或修订服务。
|
|
17
|
-
|
|
18
|
-
## 查询流程
|
|
19
|
-
|
|
20
|
-
1. 从最近一次 `novel_write_chapter`、`novel_review_chapter`、`novel_revision_*`
|
|
21
|
-
或 `novel_task_get` 结果的 `dog_delivery.graphPath` 找到图;conductor 输出使用
|
|
22
|
-
`dog_delivery_graph_path`。
|
|
23
|
-
2. 依次调用 `dog_validate`、`dog_create`、`dog_run`、`dog_wait`,等待终态。
|
|
24
|
-
3. 用 `dog_status` 读取 `review`、`revision` 和 `closure` evidence,再决定领域动作。
|
|
25
|
-
4. 修订应用后必须重新调用 `novel_review_chapter`;只有复评对应当前正文 SHA 且门禁通过,
|
|
26
|
-
`closure` 才能成为 `closed`。
|
|
27
|
-
|
|
28
|
-
## 状态解释
|
|
29
|
-
|
|
30
|
-
- `review.missing`:正文存在但尚未评审。
|
|
31
|
-
- `closure.review_failed`:当前正文已评审但未通过门禁,进入修订闭环。
|
|
32
|
-
- `revision.proposal_pending`:提案已生成,等待作者确认应用或驳回。
|
|
33
|
-
- `revision.applied_requires_rereview`:修订已写入正文,但不能视为问题已解决。
|
|
34
|
-
- `closure.rereview_required`:必须对修订后的正文重新跑六域评审。
|
|
35
|
-
- `closure.closed`:当前正文的最新评审通过,可以交付。
|
|
36
|
-
|
|
37
|
-
## 约束
|
|
38
|
-
|
|
39
|
-
- DoG 只查询 canonical manuscript/review/revision 文件,不直接修改它们。
|
|
40
|
-
- 不因提案状态为 `applied` 就宣称问题关闭;必须以当前正文 SHA 对应的复评为准。
|
|
41
|
-
- `needs_human` 是等待评审、确认或复评,不得描述成成功。
|
|
42
|
-
- 应用或驳回修订仍遵守 OpenWrite 创作 Agent 的用户确认边界。
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dog-import-query
|
|
3
|
-
description: Query a smart-import DoG graph after a novel has been split into chapters. Use to inspect import completeness, AI precheck errors, and pending outline/asset/canonical setup.
|
|
4
|
-
user-invocable: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Language: follow the author's current conversation language for responses and help; preserve the manuscript language. Translate these instructions when presenting them.
|
|
8
|
-
|
|
9
|
-
# 拆书导入 DoG 验收
|
|
10
|
-
|
|
11
|
-
`conductor/smart_import.py` 完成导入后会生成 `dog-graph.json`。这张图只验收导入
|
|
12
|
-
结果,不负责写入章节、资产或大纲。
|
|
13
|
-
|
|
14
|
-
## 工作流
|
|
15
|
-
|
|
16
|
-
1. 从导入输出中找到 `data/novels/{id}/data/dog/imports/{IMPORT_ID}/dog-graph.json`。
|
|
17
|
-
2. 依次调用 `dog_validate`、`dog_create`、`dog_run`、`dog_wait`,直到终态。
|
|
18
|
-
3. 用 `dog_status` 查看 `manifest` 和每个 `chapter-ch_*` 节点的 evidence。
|
|
19
|
-
4. 根节点通过后,读取 manifest 的 `construction.nextActions`,再调用规划和资产工具建立大纲、角色、世界观、进度和正典事件。
|
|
20
|
-
5. 资产/大纲建立完成后重新运行 `novel_review_chapter`,再用 `dog-review-query`
|
|
21
|
-
查询六域评审和 37 个 legacy leaf。
|
|
22
|
-
|
|
23
|
-
## 约束
|
|
24
|
-
|
|
25
|
-
- DoG 失败只说明导入文件缺失、为空或 manifest 不一致;不要直接修改正文。
|
|
26
|
-
- `status=partial|failed` 表示导入中途出错;先读取 manifest 的 `error` 和已完成的章节,再修复或重试导入。
|
|
27
|
-
- `aiCheck.status=failed` 要如实报告,不能当成“无问题”。
|
|
28
|
-
- `construction` 中的 `pending` 是后续建模待办,不是导入失败。
|
|
29
|
-
- 不要让 DoG verifier 再调用 Studio 导入接口或重新读取原始整本小说。
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: dog-review-query
|
|
3
|
-
description: Query an existing hierarchical OpenWrite review-v2 artifact through dsh-dog. Use after novel_review_chapter or when inspecting six quality domains, evidence, coverage, blockers, delivery status, or the 37 legacy checks. Never rerun model review from DoG.
|
|
4
|
-
user-invocable: false
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
Language: follow the author's current conversation language for responses and help; preserve the manuscript language. Translate these instructions when presenting them.
|
|
8
|
-
|
|
9
|
-
# 分层评审 DoG 查询
|
|
10
|
-
|
|
11
|
-
这个 Skill 只查询 OpenWrite 已物化的 review-v2 artifact。OpenWrite 的
|
|
12
|
-
`novel_review_chapter` / conductor 是评审真源;DoG 读取 47 节点分层图:上下文、
|
|
13
|
-
六个质量域、硬门禁、确定性聚合和 37 个兼容查询 leaf。DoG 不调用评审模型。
|
|
14
|
-
|
|
15
|
-
## 工作流
|
|
16
|
-
|
|
17
|
-
1. 如果本轮还没有评审结果,先按 OpenWrite 写作流程调用 `novel_review_chapter`。
|
|
18
|
-
2. 找到评审输出目录中的 `dog-graph.json`。graph 与 `review.json`、`context.json`、
|
|
19
|
-
`domain-*.json`、`gate.json`、`aggregate.json` 和 `dim_01.json` …
|
|
20
|
-
`dim_37.json` 在同一目录。
|
|
21
|
-
3. 读取 graph 后依次调用 `dog_validate`、`dog_create`、`dog_run`、`dog_wait`。
|
|
22
|
-
所有节点均使用 programmatic `review-record` verifier;`dog_run` 后必须等到终态。
|
|
23
|
-
4. 用 `dog_status` 分别汇报 `qualityScore`、`coverage`、`gateStatus` 和
|
|
24
|
-
`deliveryStatus`,再按需展开域、criterion、evidence、issue 和 legacy ID。
|
|
25
|
-
`inconclusive` 表示覆盖不足、未请求或 artifact 不可判定,不能描述为通过。
|
|
26
|
-
5. 只有 OpenWrite 的审查问题才能进入 `novel_revisions_*` 回炉闭环。应用修订后
|
|
27
|
-
必须复评,再用 `dog-delivery-query` 确认 `closure.closed`。DoG 的 `failure`
|
|
28
|
-
只负责定位问题,不直接改正文或资产。
|
|
29
|
-
|
|
30
|
-
## 状态约定
|
|
31
|
-
|
|
32
|
-
- `qualityScore` 是有可定位正文证据的正向累加分;issue 不直接扣分。
|
|
33
|
-
- `coverage` 单独表达实际完成的评审范围;`inconclusive` 会降低覆盖率。
|
|
34
|
-
- `gateStatus=blocked` 来自 hard-gate 或 critical finding,不改写质量分。
|
|
35
|
-
- `deliveryStatus` 综合质量、覆盖、门禁和正文 SHA 新鲜度;它不是 `passed` 的别名。
|
|
36
|
-
- 37 个 leaf 是 `legacy_check_ids` 查询入口,不是 37 个顶层计分维度。
|
|
37
|
-
|
|
38
|
-
## 边界
|
|
39
|
-
|
|
40
|
-
- 不要用 DoG verifier 直接调用 Studio 写入接口。
|
|
41
|
-
- 不要把 warning 自动升级成失败;先展示 evidence,由 `review_gate` 和作者决定。
|
|
42
|
-
- 不要用 `dog_run` 生成、补写或刷新评审;artifact 缺失或 stale 时回到 OpenWrite 流程。
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// dsh-dog verifier for one materialized chapter-delivery stage.
|
|
3
|
-
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
|
|
6
|
-
const inputPath = process.argv[2]
|
|
7
|
-
if (!inputPath) {
|
|
8
|
-
process.stdout.write(JSON.stringify({ verdict: 'inconclusive', evidence: { error: 'missing input path' } }))
|
|
9
|
-
process.exit(0)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const value = JSON.parse(await readFile(inputPath, 'utf8'))
|
|
14
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('delivery stage must be an object')
|
|
15
|
-
if (!['delivery-stage', 'chapter-delivery'].includes(value.recordType)) throw new Error('record is not a delivery artifact')
|
|
16
|
-
if (!['pass', 'fail', 'inconclusive'].includes(value.verdict)) throw new Error('delivery stage has an invalid verdict')
|
|
17
|
-
process.stdout.write(JSON.stringify({
|
|
18
|
-
verdict: value.verdict,
|
|
19
|
-
evidence: {
|
|
20
|
-
chapterId: value.chapterId,
|
|
21
|
-
stage: value.stage,
|
|
22
|
-
status: value.status ?? (value.readyForDelivery ? 'ready' : 'not_ready'),
|
|
23
|
-
readyForDelivery: value.readyForDelivery,
|
|
24
|
-
...(value.evidence !== null && typeof value.evidence === 'object' ? value.evidence : {}),
|
|
25
|
-
},
|
|
26
|
-
}))
|
|
27
|
-
} catch (error) {
|
|
28
|
-
process.stdout.write(JSON.stringify({
|
|
29
|
-
verdict: 'inconclusive',
|
|
30
|
-
evidence: { error: error instanceof Error ? error.message : String(error) },
|
|
31
|
-
}))
|
|
32
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// dsh-dog verifier for one imported manuscript chapter.
|
|
3
|
-
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
|
|
6
|
-
const inputPath = process.argv[2]
|
|
7
|
-
if (!inputPath) {
|
|
8
|
-
process.stdout.write(JSON.stringify({ verdict: 'inconclusive', evidence: { error: 'missing input path' } }))
|
|
9
|
-
process.exit(0)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const text = await readFile(inputPath, 'utf8')
|
|
14
|
-
if (!text.trim()) throw new Error('chapter file is empty')
|
|
15
|
-
const heading = text.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? ''
|
|
16
|
-
if (!heading) throw new Error('chapter file has no markdown title')
|
|
17
|
-
process.stdout.write(JSON.stringify({ verdict: 'pass', evidence: { heading, bytes: Buffer.byteLength(text) } }))
|
|
18
|
-
} catch (error) {
|
|
19
|
-
process.stdout.write(JSON.stringify({ verdict: 'fail', evidence: { error: error instanceof Error ? error.message : String(error) } }))
|
|
20
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// dsh-dog verifier for a smart-import manifest.
|
|
3
|
-
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
|
|
6
|
-
const inputPath = process.argv[2]
|
|
7
|
-
if (!inputPath) {
|
|
8
|
-
process.stdout.write(JSON.stringify({ verdict: 'inconclusive', evidence: { error: 'missing input path' } }))
|
|
9
|
-
process.exit(0)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const value = JSON.parse(await readFile(inputPath, 'utf8'))
|
|
14
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) throw new Error('manifest must be an object')
|
|
15
|
-
if (value.recordType !== 'smart-import' || !['completed', 'partial', 'failed'].includes(value.status)) throw new Error('manifest has an invalid status')
|
|
16
|
-
if (!Number.isInteger(value.chapterCount) || !Array.isArray(value.chapters) || value.chapterCount !== value.chapters.length) {
|
|
17
|
-
throw new Error('chapterCount does not match chapters')
|
|
18
|
-
}
|
|
19
|
-
const invalid = value.chapters.some(item => item === null || typeof item !== 'object' || typeof item.target !== 'string' || !item.target.endsWith('.md'))
|
|
20
|
-
if (invalid) throw new Error('manifest contains an invalid chapter target')
|
|
21
|
-
process.stdout.write(JSON.stringify({
|
|
22
|
-
verdict: value.status === 'completed' ? 'pass' : 'fail',
|
|
23
|
-
evidence: {
|
|
24
|
-
importId: value.importId,
|
|
25
|
-
status: value.status,
|
|
26
|
-
error: value.error,
|
|
27
|
-
source: value.source,
|
|
28
|
-
chapterCount: value.chapterCount,
|
|
29
|
-
aiCheck: value.aiCheck,
|
|
30
|
-
construction: value.construction,
|
|
31
|
-
},
|
|
32
|
-
}))
|
|
33
|
-
} catch (error) {
|
|
34
|
-
process.stdout.write(JSON.stringify({ verdict: 'fail', evidence: { error: error instanceof Error ? error.message : String(error) } }))
|
|
35
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// dsh-dog programmatic verifier for one materialized OpenWrite dimension.
|
|
3
|
-
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
|
|
6
|
-
const inputPath = process.argv[2]
|
|
7
|
-
if (!inputPath) {
|
|
8
|
-
process.stdout.write(JSON.stringify({ verdict: 'inconclusive', evidence: { error: 'missing input path' } }))
|
|
9
|
-
process.exit(0)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const value = JSON.parse(await readFile(inputPath, 'utf8'))
|
|
14
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
15
|
-
throw new Error('dimension record must be an object')
|
|
16
|
-
}
|
|
17
|
-
if (!['dimension', 'review-dimension'].includes(value.recordType)) {
|
|
18
|
-
throw new Error('record is not a review dimension')
|
|
19
|
-
}
|
|
20
|
-
const verdict = value.verdict
|
|
21
|
-
if (verdict !== 'pass' && verdict !== 'fail' && verdict !== 'inconclusive') {
|
|
22
|
-
throw new Error('dimension record has an invalid verdict')
|
|
23
|
-
}
|
|
24
|
-
process.stdout.write(JSON.stringify({
|
|
25
|
-
verdict,
|
|
26
|
-
evidence: {
|
|
27
|
-
chapterId: value.chapterId,
|
|
28
|
-
dimension: value.dimension,
|
|
29
|
-
name: value.name,
|
|
30
|
-
status: value.status,
|
|
31
|
-
criterionId: value.criterionId,
|
|
32
|
-
issueCount: value.issueCount,
|
|
33
|
-
issues: Array.isArray(value.issues) ? value.issues : [],
|
|
34
|
-
},
|
|
35
|
-
}))
|
|
36
|
-
} catch (error) {
|
|
37
|
-
process.stdout.write(JSON.stringify({
|
|
38
|
-
verdict: 'inconclusive',
|
|
39
|
-
evidence: { error: error instanceof Error ? error.message : String(error) },
|
|
40
|
-
}))
|
|
41
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Programmatic verifier for hierarchical review context/domain/gate/aggregate records.
|
|
3
|
-
|
|
4
|
-
import { readFile } from 'node:fs/promises'
|
|
5
|
-
|
|
6
|
-
const inputPath = process.argv[2]
|
|
7
|
-
if (!inputPath) {
|
|
8
|
-
process.stdout.write(JSON.stringify({ verdict: 'inconclusive', evidence: { error: 'missing input path' } }))
|
|
9
|
-
process.exit(0)
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
try {
|
|
13
|
-
const value = JSON.parse(await readFile(inputPath, 'utf8'))
|
|
14
|
-
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
15
|
-
throw new Error('review record must be an object')
|
|
16
|
-
}
|
|
17
|
-
if (!String(value.recordType ?? '').startsWith('review')) {
|
|
18
|
-
throw new Error('recordType is not a review artifact')
|
|
19
|
-
}
|
|
20
|
-
if (!['pass', 'fail', 'inconclusive'].includes(value.verdict)) {
|
|
21
|
-
throw new Error('review record has an invalid verdict')
|
|
22
|
-
}
|
|
23
|
-
process.stdout.write(JSON.stringify({
|
|
24
|
-
verdict: value.verdict,
|
|
25
|
-
evidence: {
|
|
26
|
-
recordType: value.recordType,
|
|
27
|
-
chapterId: value.chapterId,
|
|
28
|
-
id: value.id,
|
|
29
|
-
status: value.status ?? value.deliveryStatus ?? value.executionStatus,
|
|
30
|
-
qualityScore: value.qualityScore,
|
|
31
|
-
coverage: value.coverage,
|
|
32
|
-
gateStatus: value.gateStatus,
|
|
33
|
-
deliveryStatus: value.deliveryStatus,
|
|
34
|
-
issueCount: Array.isArray(value.issues) ? value.issues.length : value.issueCount,
|
|
35
|
-
},
|
|
36
|
-
}))
|
|
37
|
-
} catch (error) {
|
|
38
|
-
process.stdout.write(JSON.stringify({
|
|
39
|
-
verdict: 'inconclusive',
|
|
40
|
-
evidence: { error: error instanceof Error ? error.message : String(error) },
|
|
41
|
-
}))
|
|
42
|
-
}
|
package/vendor/dsh-dog/LICENSE
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
BSD 3-Clause License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026, DoG contributors
|
|
4
|
-
All rights reserved.
|
|
5
|
-
|
|
6
|
-
Redistribution and use in source and binary forms, with or without
|
|
7
|
-
modification, are permitted provided that the following conditions are met:
|
|
8
|
-
|
|
9
|
-
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
-
list of conditions and the following disclaimer.
|
|
11
|
-
|
|
12
|
-
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
-
this list of conditions and the following disclaimer in the documentation
|
|
14
|
-
and/or other materials provided with the distribution.
|
|
15
|
-
|
|
16
|
-
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
-
contributors may be used to endorse or promote products derived from
|
|
18
|
-
this software without specific prior written permission.
|
|
19
|
-
|
|
20
|
-
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
-
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
-
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
-
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
-
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
-
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
-
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
-
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
-
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
-
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|