dsh-math-modeling-agent 0.3.1 → 0.4.1
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/README.md +16 -1
- package/package.json +1 -1
- package/skills/math-modeling-agent/SKILL.md +16 -3
- package/skills/math-modeling-agent/references/original-project-parity.md +19 -0
- package/skills/math-modeling-agent/references/subagent-dispatch.md +3 -2
- package/skills/math-modeling-agent/references/tool-policy.md +11 -0
- package/skills/math-modeling-agent/references/workflow.md +10 -9
- package/skills/math-modeling-agent/scripts/computation/README.md +20 -0
- package/skills/math-modeling-agent/scripts/computation/backend-inventory.schema.json +78 -0
- package/skills/math-modeling-agent/scripts/computation/backend_inventory.ps1 +351 -0
- package/skills/math-modeling-agent/scripts/computation/backend_inventory.py +322 -0
- package/skills/math-modeling-agent/scripts/computation/computation_record.py +361 -0
- package/skills/math-modeling-agent/scripts/computation/probe_backends.ps1 +396 -0
- package/skills/math-modeling-agent/scripts/computation/probe_backends.py +230 -0
- package/skills/math-modeling-agent/scripts/distribution-parity.mjs +123 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import { lstat, readFile, readdir, realpath } from 'node:fs/promises'
|
|
3
|
+
import { isAbsolute, join, relative, resolve, sep } from 'node:path'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
|
|
6
|
+
const MAX_FILES = 10_000
|
|
7
|
+
const MAX_FILE_BYTES = 20 * 1024 * 1024
|
|
8
|
+
const MAX_TOTAL_BYTES = 100 * 1024 * 1024
|
|
9
|
+
const MAX_DEPTH = 32
|
|
10
|
+
|
|
11
|
+
function normalizeRelative(path) {
|
|
12
|
+
return path.split(sep).join('/')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function assertInside(root, path) {
|
|
16
|
+
const relativePath = relative(root, path)
|
|
17
|
+
if (isAbsolute(relativePath) || relativePath === '..' || relativePath.startsWith('..' + sep)) {
|
|
18
|
+
throw new Error('distribution entry escapes package root: ' + path)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function collectFiles(root, entries, { allowMissing = false } = {}) {
|
|
23
|
+
const realRoot = await realpath(root)
|
|
24
|
+
const files = new Map()
|
|
25
|
+
let totalBytes = 0
|
|
26
|
+
|
|
27
|
+
const visit = async (path, depth) => {
|
|
28
|
+
if (depth > MAX_DEPTH) throw new Error('distribution tree exceeds maximum depth: ' + path)
|
|
29
|
+
let information
|
|
30
|
+
try {
|
|
31
|
+
information = await lstat(path)
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (allowMissing && error?.code === 'ENOENT') return
|
|
34
|
+
throw error
|
|
35
|
+
}
|
|
36
|
+
if (information.isSymbolicLink()) throw new Error('symbolic link is not allowed in distribution: ' + path)
|
|
37
|
+
const realPath = await realpath(path)
|
|
38
|
+
assertInside(realRoot, realPath)
|
|
39
|
+
if (information.isDirectory()) {
|
|
40
|
+
for (const entry of await readdir(path)) await visit(join(path, entry), depth + 1)
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
if (!information.isFile()) return
|
|
44
|
+
if (information.size > MAX_FILE_BYTES) throw new Error('distribution file exceeds maximum size: ' + path)
|
|
45
|
+
totalBytes += information.size
|
|
46
|
+
if (totalBytes > MAX_TOTAL_BYTES) throw new Error('distribution tree exceeds maximum size')
|
|
47
|
+
if (files.size >= MAX_FILES) throw new Error('distribution tree exceeds maximum file count')
|
|
48
|
+
const relativePath = normalizeRelative(relative(root, path))
|
|
49
|
+
const digest = createHash('sha256').update(await readFile(path)).digest('hex')
|
|
50
|
+
files.set(relativePath, digest)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (typeof entry !== 'string' || !entry.trim()) throw new Error('package files entries must be non-empty strings')
|
|
55
|
+
const path = resolve(root, entry)
|
|
56
|
+
assertInside(root, path)
|
|
57
|
+
await visit(path, 0)
|
|
58
|
+
}
|
|
59
|
+
return files
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Compare the allowlisted package files of a source and distribution root. */
|
|
63
|
+
export async function compareDistribution(sourceRoot, candidateRoot) {
|
|
64
|
+
const source = resolve(sourceRoot)
|
|
65
|
+
const candidate = resolve(candidateRoot)
|
|
66
|
+
const manifest = JSON.parse(await readFile(join(source, 'package.json'), 'utf8'))
|
|
67
|
+
if (!Array.isArray(manifest.files) || manifest.files.length === 0) {
|
|
68
|
+
throw new Error('source package.json must declare a non-empty files allowlist')
|
|
69
|
+
}
|
|
70
|
+
const entries = [...new Set(['package.json', ...manifest.files])]
|
|
71
|
+
const expected = await collectFiles(source, entries)
|
|
72
|
+
const actual = await collectFiles(candidate, entries, { allowMissing: true })
|
|
73
|
+
const missing = [...expected.keys()].filter(path => !actual.has(path)).sort()
|
|
74
|
+
const extra = [...actual.keys()].filter(path => !expected.has(path)).sort()
|
|
75
|
+
const mismatched = [...expected.keys()]
|
|
76
|
+
.filter(path => actual.has(path) && actual.get(path) !== expected.get(path))
|
|
77
|
+
.sort()
|
|
78
|
+
const metadataMismatches = []
|
|
79
|
+
try {
|
|
80
|
+
const candidateManifest = JSON.parse(await readFile(join(candidate, 'package.json'), 'utf8'))
|
|
81
|
+
for (const key of ['name', 'version']) {
|
|
82
|
+
if (candidateManifest[key] !== manifest[key]) {
|
|
83
|
+
metadataMismatches.push(`${key}: expected ${manifest[key]}, received ${candidateManifest[key]}`)
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} catch (error) {
|
|
87
|
+
metadataMismatches.push('package.json: ' + (error instanceof Error ? error.message : String(error)))
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
ok: missing.length === 0 && extra.length === 0 && mismatched.length === 0 && metadataMismatches.length === 0,
|
|
91
|
+
sourceRoot: source,
|
|
92
|
+
candidateRoot: candidate,
|
|
93
|
+
expectedFileCount: expected.size,
|
|
94
|
+
candidateFileCount: actual.size,
|
|
95
|
+
missing,
|
|
96
|
+
extra,
|
|
97
|
+
mismatched,
|
|
98
|
+
metadataMismatches,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parseArguments(argv) {
|
|
103
|
+
if (argv.length !== 2 || argv.some(value => !value || value.startsWith('-'))) {
|
|
104
|
+
throw new Error('usage: node distribution-parity.mjs <source-root> <candidate-root>')
|
|
105
|
+
}
|
|
106
|
+
return { sourceRoot: argv[0], candidateRoot: argv[1] }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
110
|
+
try {
|
|
111
|
+
const { sourceRoot, candidateRoot } = parseArguments(argv)
|
|
112
|
+
const result = await compareDistribution(sourceRoot, candidateRoot)
|
|
113
|
+
console.log(JSON.stringify(result, null, 2))
|
|
114
|
+
return result.ok ? 0 : 1
|
|
115
|
+
} catch (error) {
|
|
116
|
+
console.error(error instanceof Error ? error.message : String(error))
|
|
117
|
+
return 2
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (process.argv[1] !== undefined && fileURLToPath(import.meta.url) === resolve(process.argv[1])) {
|
|
122
|
+
process.exitCode = await main()
|
|
123
|
+
}
|