dsh-vibe-math 2.8.3 → 2.8.5
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.en.md +19 -4
- package/README.md +19 -4
- package/cordis.patch.yml +8 -8
- package/docs/COMPAT-AUDIT-ROUND2.md +27 -0
- package/docs/final-paper.md +13 -0
- package/docs/formal-verification.md +6 -6
- package/docs/release-notes/README.en.md +2 -0
- package/docs/release-notes/README.md +2 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.4.en.md +48 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.4.md +48 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.5.en.md +75 -0
- package/docs/release-notes/RELEASE-NOTES-2.8.5.md +75 -0
- package/package.json +7 -3
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +17 -17
- package/prompt-corpus-v2/formal-verify-v2.json +109 -109
- package/prompt-corpus-v2/formal-verify-v2.md +216 -216
- package/prompt-corpus-v3/formal-verify-v3.json +77 -77
- package/prompt-corpus-v3/formal-verify-v3.md +147 -147
- package/prompt-corpus-v4/formal-verify-v4.json +10 -10
- package/prompt-corpus-v4/formal-verify-v4.md +40 -40
- package/prompt-corpus-v5/prompt-corpus-v5.json +61 -61
- package/prompt-corpus-v5/prompt-corpus-v5.md +613 -186
- package/tests/audit-artifact-docs.mjs +38 -2
- package/tests/audit-artifact-docs.mutants.mjs +66 -1
- package/tests/audit-engine-faces.mjs +2 -2
- package/tests/audit-persona-surface.test.mjs +4 -1
- package/tests/audit-prompt-invariants.mjs +292 -2
- package/tests/e2e-v5-round2.test.mjs +248 -2
- package/tests/formal-verify-v2.test.mjs +6 -5
- package/tests/formal-verify-v3.test.mjs +6 -5
- package/tests/formal-verify-v4.test.mjs +4 -4
- package/tests/prompt-v5-integrity.test.mjs +6 -6
- package/tests/run-tests.mjs +18 -2
- package/tests/v4-final-paper.mutants.mjs +14 -0
- package/tests/v4-final-paper.test.mjs +18 -0
- package/tests/v5-institute-fixes.mutants.mjs +115 -0
- package/vibe-math-v2/agent.cordis.yml +2 -2
- package/vibe-math-v2/math-computation.js +5 -5
- package/vibe-math-v2/vibe-math-v2.js +66 -17
- package/vibe-math-v3/agent.cordis.yml +2 -2
- package/vibe-math-v3/math-computation.js +5 -5
- package/vibe-math-v3/vibe-math-v3.js +64 -15
- package/vibe-math-v4/agent.cordis.yml +6 -6
- package/vibe-math-v4/math-computation.js +5 -5
- package/vibe-math-v4/vibe-math-v4.js +71 -20
- package/vibe-math-v5/agent.cordis.yml +6 -6
- package/vibe-math-v5/math-computation.js +5 -5
- package/vibe-math-v5/vibe-math-v5.js +385 -34
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -2
|
@@ -11,8 +11,15 @@
|
|
|
11
11
|
* sections must list it.
|
|
12
12
|
*
|
|
13
13
|
* Run: node tests/audit-artifact-docs.mjs
|
|
14
|
-
* Env (mutant harness): ARTIFACT_README / ARTIFACT_README_EN / ARTIFACT_DOC /
|
|
15
|
-
* ARTIFACT_V3_JS / ARTIFACT_V4_JS / ARTIFACT_V5_JS (absolute or repo-relative).
|
|
14
|
+
* Env (mutant harness): ARTIFACT_README / ARTIFACT_README_EN / ARTIFACT_DOC / ARTIFACT_INSTALL_DOC /
|
|
15
|
+
* ARTIFACT_V2_JS / ARTIFACT_V3_JS / ARTIFACT_V4_JS / ARTIFACT_V5_JS (absolute or repo-relative).
|
|
16
|
+
*
|
|
17
|
+
* Section 5 (issue #10, plugin-market install failure) guards the INSTALL/UPGRADE docs: DSH forwards
|
|
18
|
+
* plugin installs to pnpm and does not bundle it (missing pnpm ⇒ `exit 1`), and a bare package name is
|
|
19
|
+
* resolved through the profile's package.json / pnpm-lock.yaml, so it can stay on an OLD version —
|
|
20
|
+
* measured: `@latest`, `@^2`, `pnpm update --latest` and `pnpm add …@latest` all failed to upgrade,
|
|
21
|
+
* only an explicit `@<version>` did. Both are outside this package, but the docs must not send a user
|
|
22
|
+
* into either trap (and must never claim `@latest` is the upgrade recipe).
|
|
16
23
|
*/
|
|
17
24
|
import { readFileSync } from 'node:fs'
|
|
18
25
|
import { dirname, isAbsolute, join } from 'node:path'
|
|
@@ -66,6 +73,35 @@ ok(docScope, '★ docs/final-paper.md scopes ' + ARTIFACT + ' to v2/v3 AND state
|
|
|
66
73
|
ok(/NOT IMPLEMENTED/.test(presets.v4) && /claim_write/.test(presets.v4),
|
|
67
74
|
'vibe-math-v4.js still carries the explicit NOT-IMPLEMENTED shared-lock disclaimer (the source of the doc denial)')
|
|
68
75
|
|
|
76
|
+
// 5) INSTALL/UPGRADE DOCS (issue #10): the plugin market forwards installs to pnpm (which DSH does not
|
|
77
|
+
// bundle) and resolves a bare package name through the profile's package.json / pnpm-lock.yaml, so the
|
|
78
|
+
// install can stay on an OLD version. Both traps are outside this package — but the docs must not walk a
|
|
79
|
+
// user into either, and must never present `@latest` as the upgrade recipe (measured ineffective).
|
|
80
|
+
const INSTALL_DOC = 'docs/COMPAT-AUDIT-ROUND2.md'
|
|
81
|
+
for (const [name, envName, rel] of [['README.md', 'ARTIFACT_README', 'README.md'], ['README.en.md', 'ARTIFACT_README_EN', 'README.en.md']]) {
|
|
82
|
+
const text = read(envName, rel)
|
|
83
|
+
ok(/(\*\*前置\*\*|\*\*Prerequisite\*\*)[^\n]*pnpm/.test(text),
|
|
84
|
+
'★ ' + name + ': states the `pnpm` prerequisite (DSH forwards plugin installs to pnpm and does not bundle it)')
|
|
85
|
+
ok(/hub\.log/.test(text),
|
|
86
|
+
'★ ' + name + ': points at ~/.dsh/profiles/<profile>/hub.log (the failure the market hides)')
|
|
87
|
+
const bare = count(text, 'add dsh-vibe-math')
|
|
88
|
+
const pinned = count(text, 'add dsh-vibe-math@')
|
|
89
|
+
ok(pinned > 0 && bare === pinned,
|
|
90
|
+
'★ ' + name + ': EVERY `add dsh-vibe-math` pins an explicit version (a bare name can stay on an old version)',
|
|
91
|
+
'bare=' + (bare - pinned))
|
|
92
|
+
ok(!/dsh-vibe-math@latest/.test(text),
|
|
93
|
+
'★★ ' + name + ': never presents `dsh-vibe-math@latest` as the install/upgrade recipe (measured ineffective)')
|
|
94
|
+
ok(/^### (安装\/升级排查|Install\/upgrade troubleshooting)/m.test(text),
|
|
95
|
+
'★ ' + name + ': carries an install/upgrade troubleshooting section')
|
|
96
|
+
}
|
|
97
|
+
const idoc = read('ARTIFACT_INSTALL_DOC', INSTALL_DOC)
|
|
98
|
+
ok(/pnpm/.test(idoc) && /hub\.log/.test(idoc),
|
|
99
|
+
'★ ' + INSTALL_DOC + ' documents the pnpm prerequisite AND the hub.log pointer')
|
|
100
|
+
ok(/add dsh-vibe-math@/.test(idoc) && !/dsh-vibe-math@latest/.test(idoc),
|
|
101
|
+
'★ ' + INSTALL_DOC + ' shows the explicit-version install/upgrade (and never `@latest`)')
|
|
102
|
+
ok(/pnpm-lock\.yaml|package\.json/.test(idoc),
|
|
103
|
+
'★ ' + INSTALL_DOC + ' explains WHY a bare name can stay on an old version (profile package.json / pnpm-lock.yaml wins)')
|
|
104
|
+
|
|
69
105
|
console.log('')
|
|
70
106
|
console.log('=== ARTIFACT DOCS: ' + passed + ' passed, ' + failed + ' failed ===')
|
|
71
107
|
for (const f of failures) console.error(' - ' + f)
|
|
@@ -52,7 +52,72 @@ const namedB = b.failLines.some((l) => l.indexOf('the writing presets are exactl
|
|
|
52
52
|
ok(b.status !== 0 && namedB, '★ B: a v4 that mentions the artifact reddens the "writers are exactly v2,v3" assertion', 'exit=' + b.status)
|
|
53
53
|
if (!namedB) for (const l of b.failLines.slice(0, 3)) console.log(' guard failure: ' + l.trim().slice(0, 130))
|
|
54
54
|
|
|
55
|
+
// ---- issue #10: the install/upgrade docs must keep teaching the pnpm prerequisite and an EXPLICIT
|
|
56
|
+
// version, and must never present `@latest` as the recipe. One single-site mutation per requirement.
|
|
57
|
+
const zhAll = readFileSync(join(REPO, 'README.md'), 'utf8')
|
|
58
|
+
const enAll = readFileSync(join(REPO, 'README.en.md'), 'utf8')
|
|
59
|
+
const probes = [
|
|
60
|
+
{
|
|
61
|
+
tag: 'C',
|
|
62
|
+
file: 'README-c.md',
|
|
63
|
+
env: { ARTIFACT_README: null },
|
|
64
|
+
from: /^> \*\*前置\*\*:[^\n]*$/m,
|
|
65
|
+
to: '> 前置未写明。',
|
|
66
|
+
expect: 'states the `pnpm` prerequisite',
|
|
67
|
+
what: 'dropping the pnpm prerequisite',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
tag: 'D',
|
|
71
|
+
file: 'README-d.md',
|
|
72
|
+
env: { ARTIFACT_README: null },
|
|
73
|
+
from: 'add dsh-vibe-math@<版本>',
|
|
74
|
+
to: 'add dsh-vibe-math@latest',
|
|
75
|
+
expect: 'never presents `dsh-vibe-math@latest`',
|
|
76
|
+
what: 'teaching `@latest` as the install/upgrade command',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
tag: 'E',
|
|
80
|
+
file: 'README-e.md',
|
|
81
|
+
env: { ARTIFACT_README: null },
|
|
82
|
+
from: 'hub.log',
|
|
83
|
+
to: '安装日志',
|
|
84
|
+
expect: 'points at ~/.dsh/profiles/<profile>/hub.log',
|
|
85
|
+
what: 'dropping the hub.log pointer',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
tag: 'F',
|
|
89
|
+
file: 'README-f.md',
|
|
90
|
+
env: { ARTIFACT_README_EN: null },
|
|
91
|
+
from: '### Install/upgrade troubleshooting (pnpm and versions)',
|
|
92
|
+
to: '### Install/upgrade notes',
|
|
93
|
+
expect: 'carries an install/upgrade troubleshooting section',
|
|
94
|
+
what: 'dropping the troubleshooting section',
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
tag: 'G',
|
|
98
|
+
file: 'compat-doc.md',
|
|
99
|
+
env: { ARTIFACT_INSTALL_DOC: null },
|
|
100
|
+
from: 'add dsh-vibe-math@<版本>',
|
|
101
|
+
to: 'add dsh-vibe-math',
|
|
102
|
+
expect: 'shows the explicit-version install/upgrade',
|
|
103
|
+
what: 'dropping the explicit version from the install/upgrade doc',
|
|
104
|
+
doc: 'docs/COMPAT-AUDIT-ROUND2.md',
|
|
105
|
+
},
|
|
106
|
+
]
|
|
107
|
+
for (const p of probes) {
|
|
108
|
+
const base = p.doc ? readFileSync(join(REPO, p.doc), 'utf8') : (p.tag === 'F' ? enAll : zhAll)
|
|
109
|
+
const anchor = p.from instanceof RegExp ? (base.match(p.from) || [])[0] : p.from
|
|
110
|
+
const n = anchor ? base.split(anchor).length - 1 : 0
|
|
111
|
+
if (!anchor || n < 1) { ok(false, p.tag + ': the anchor for ' + p.what + ' exists (harness and guard agree)', 'ANCHOR MISS'); continue }
|
|
112
|
+
writeFileSync(join(dir, p.file), base.split(anchor).join(p.to))
|
|
113
|
+
const envKey = Object.keys(p.env)[0]
|
|
114
|
+
const r = run({ [envKey]: join(dir, p.file) })
|
|
115
|
+
const named = r.failLines.some((l) => l.indexOf(p.expect) !== -1)
|
|
116
|
+
ok(r.status !== 0 && named, '★ ' + p.tag + ': ' + p.what + ' reddens the guard by name', 'exit=' + r.status)
|
|
117
|
+
if (!named) for (const l of r.failLines.slice(0, 3)) console.log(' guard failure: ' + l.trim().slice(0, 130))
|
|
118
|
+
}
|
|
119
|
+
|
|
55
120
|
rmSync(dir, { recursive: true, force: true })
|
|
56
121
|
console.log('')
|
|
57
122
|
console.log('=== ARTIFACT DOCS MUTANTS: ' + passed + ' passed, ' + failed + ' failed ===')
|
|
58
|
-
process.exit(failed === 0 ? 0 : 1)
|
|
123
|
+
process.exit(failed === 0 ? 0 : 1)
|
|
@@ -250,8 +250,8 @@ if (!XELATEX) {
|
|
|
250
250
|
ok(bad && typeof bad === 'object' && bad.ok === true && (bad.started === true || bad.code === undefined),
|
|
251
251
|
'★ engine-faces latex: a NONEXISTENT `paperLatexCommand` neither crashes nor raises an error code (entry stays ok:true, code undefined) (' + JSON.stringify({ ok: bad && bad.ok, code: bad && bad.code, note: String(bad && bad.note || '').slice(0, 50) }) + ')')
|
|
252
252
|
console.log(' note: the LaTeX COMPILE (paper writer subagent) is not reachable from this probe; it asserts the ENTRY only — crash-freedom and no error code. The named degradation for an unresolvable command is a COMPILE-time behaviour (docs/final-paper.md) and is deliberately NOT claimed here.')
|
|
253
|
-
console.log(' This probe
|
|
254
|
-
console.log('
|
|
253
|
+
console.log(' This probe carries its own candidate list (D:\\texlive\\2025\\bin\\windows); since task-28 the')
|
|
254
|
+
console.log(' PRODUCT probes that documented TeX Live drive-root position too (audit-prompt-invariants I19).')
|
|
255
255
|
} finally { rmSync(WS, { recursive: true, force: true }) }
|
|
256
256
|
}
|
|
257
257
|
|
|
@@ -196,7 +196,7 @@ const PRESETS = [
|
|
|
196
196
|
dir: 'vibe-math-v5',
|
|
197
197
|
js: 'vibe-math-v5.js',
|
|
198
198
|
prefix: 'vibe_v5_',
|
|
199
|
-
tools:
|
|
199
|
+
tools: 41,
|
|
200
200
|
// Member- and academician-facing tools that the v5 persona describes GENERICALLY; the office (main
|
|
201
201
|
// agent) holds only the institute-level controls plus the hiring authority. The final-paper pair is
|
|
202
202
|
// an OFFICE control and IS named in the persona (vibe_v5_paper / vibe_v5_finalize_paper), so it must
|
|
@@ -207,6 +207,9 @@ const PRESETS = [
|
|
|
207
207
|
'vibe_v5_propose_verify', 'vibe_v5_verdict', 'vibe_v5_task_create',
|
|
208
208
|
'vibe_v5_task_list', 'vibe_v5_task_get', 'vibe_v5_task_update',
|
|
209
209
|
'vibe_v5_overview', 'vibe_v5_assign', 'vibe_v5_prioritize', 'vibe_v5_nudge',
|
|
210
|
+
// task-30: the methodology/collaboration feedback tool is member-facing and described GENERICALLY
|
|
211
|
+
// by the persona; the plugin's per-round hint names it and explains the three routes.
|
|
212
|
+
'vibe_v5_feedback',
|
|
210
213
|
],
|
|
211
214
|
required: [],
|
|
212
215
|
lean: { tools: ['vibe_v5_lean_run', 'vibe_v5_lean_archive', 'vibe_v5_lean_lib', 'vibe_v5_lean_read', 'vibe_v5_lean_job'], extra: [] },
|
|
@@ -20,6 +20,17 @@
|
|
|
20
20
|
* I13 the Lean switch unreachable THROUGH the closed tool schema (v3 2.3.1: all four params
|
|
21
21
|
* missing from vibe_math_set_params, invisible to every suite because suites call handlers)
|
|
22
22
|
* I14 a parameter the tool schema advertises but the parameter layer silently drops
|
|
23
|
+
* I15 the LaTeX-missing guidance drifting between presets (four steps + three hard boundaries)
|
|
24
|
+
* I16 a member prompt demanding an action the member CANNOT perform (direct user contact)
|
|
25
|
+
* I17 the Lean-3 `--search-path` spelling surviving in an agent-facing STRING (only exception: the
|
|
26
|
+
* guard's explicit legacy-compatibility array)
|
|
27
|
+
* I18 the same spelling surviving in the AGENT-FACING preset text (`vibe-math-vN/agent.cordis.yml`,
|
|
28
|
+
* checked per preset) or in its generated copy (`cordis.patch.yml`, I18b)
|
|
29
|
+
* I19 the paper-engine detection missing TeX Live's documented install positions, or losing its
|
|
30
|
+
* explicit BOUND on candidate probes (never a whole-drive scan) — asserted for all four presets
|
|
31
|
+
* I20 the env-gated TeX sandbox seam (V2/V3/V4/V5_TEX_ROOTS_SANDBOX): every known-install probe site
|
|
32
|
+
* must stay rebased through it (unset = identity), and with it set the stage finds nothing while
|
|
33
|
+
* still naming its probes — static (I20) plus behavioural (I20b)
|
|
23
34
|
*
|
|
24
35
|
* Run: node tests/audit-prompt-invariants.mjs (add --json for a machine-readable report)
|
|
25
36
|
* node tests/audit-prompt-invariants.mjs --self-probe
|
|
@@ -129,6 +140,111 @@ const SELF_PROBE_MUTATIONS = [
|
|
|
129
140
|
to: ', then continue',
|
|
130
141
|
expect: 'v2 I15: the LaTeX-missing guidance carries the four steps + the three hard boundaries',
|
|
131
142
|
},
|
|
143
|
+
{
|
|
144
|
+
name: 'v5: the member prompt sends the TeX approval back to the USER instead of the OFFICE (I16)',
|
|
145
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
146
|
+
from: '如实上报所办(或群聊)**,由**所办**向用户确认',
|
|
147
|
+
to: '向用户问一次',
|
|
148
|
+
expect: 'v5 I16: a member-visible prompt never demands an action the member cannot perform',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
name: 'v3: the member prompt sends the TeX approval back to the USER instead of the OFFICE (I16)',
|
|
152
|
+
rel: 'vibe-math-v3/vibe-math-v3.js',
|
|
153
|
+
from: 'REPORT IT TO THE OFFICE (or the group chat) and let the OFFICE confirm with the user',
|
|
154
|
+
to: 'ask the user once',
|
|
155
|
+
expect: 'v3 I16: a member-visible prompt never demands an action the member cannot perform',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'v5: a tool description teaches the Lean-3 `--search-path` again (I17 must read the string, not the comment)',
|
|
159
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
160
|
+
from: 'leanSearchPaths (string[]) adds extra compiler search roots',
|
|
161
|
+
to: 'leanSearchPaths (string[]) adds extra --search-path roots',
|
|
162
|
+
expect: 'v5 I17:',
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'v5: the AGENT-FACING preset text reverts to the Lean-3 `--search-path` (I18 — the real-host SLV shape)',
|
|
166
|
+
rel: 'vibe-math-v5/agent.cordis.yml',
|
|
167
|
+
from: 'gets `-R <VibeMath root>`, preceded by any leanSearchPaths',
|
|
168
|
+
to: 'gets `--search-path <VibeMath root>`, preceded by any leanSearchPaths',
|
|
169
|
+
expect: 'v5 I18:',
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
name: 'the generated bundle patch keeps the Lean-3 `--search-path` (I18b: source fixed but not regenerated)',
|
|
173
|
+
rel: 'cordis.patch.yml',
|
|
174
|
+
from: '-R <VibeMath root>',
|
|
175
|
+
to: '--search-path <VibeMath root>',
|
|
176
|
+
expect: 'I18b:',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'v5: the DRIVE-ROOT TeX Live candidates are deleted (I19 — the real-host "installed but not on PATH" shape)',
|
|
180
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
181
|
+
from: "const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive']",
|
|
182
|
+
to: 'const TEX_DRIVE_YEAR_ROOTS = []',
|
|
183
|
+
expect: 'v5 I19:',
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: 'v5: the TeX probe cap is removed (I19: the known-root stage must stay bounded)',
|
|
187
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
188
|
+
from: 'const TEX_CANDIDATE_CAP = 40',
|
|
189
|
+
to: 'const TEX_CANDIDATE_CAP = 100000',
|
|
190
|
+
expect: 'v5 I19:',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'v5: a bare DRIVE ROOT enters the TeX candidates (I19: never a whole-drive scan)',
|
|
194
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
195
|
+
from: "const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive']",
|
|
196
|
+
to: "const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive', 'D:/']",
|
|
197
|
+
expect: 'v5 I19:',
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'v5: the TeX sandbox gate is ignored (I20 — a suite can no longer pin "no LaTeX on this host")',
|
|
201
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
202
|
+
from: "const texSandbox = kind === 'tex' ? String(env.V5_TEX_ROOTS_SANDBOX || '').trim() : ''",
|
|
203
|
+
to: "const texSandbox = ''",
|
|
204
|
+
expect: 'v5 I20:',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'v5: one known-install TeX probe site stops rebasing into the sandbox (I20)',
|
|
208
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
209
|
+
from: "const p = await resolvePath(texProbeBase(base) + '/' + name)",
|
|
210
|
+
to: "const p = await resolvePath(base + '/' + name)",
|
|
211
|
+
expect: 'v5 I20:',
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
name: 'v4: one known-install TeX probe site stops rebasing into the sandbox (I20, second preset — not a v5-only check)',
|
|
215
|
+
rel: 'vibe-math-v4/vibe-math-v4.js',
|
|
216
|
+
from: "const p = await resolvePath(texProbeBase(base) + '/' + name)",
|
|
217
|
+
to: "const p = await resolvePath(base + '/' + name)",
|
|
218
|
+
expect: 'v4 I20:',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'v3: one known-install TeX probe site stops rebasing into the sandbox (I20)',
|
|
222
|
+
rel: 'vibe-math-v3/vibe-math-v3.js',
|
|
223
|
+
from: "const p = await resolvePath(texProbeBase(base) + '/' + name)",
|
|
224
|
+
to: "const p = await resolvePath(base + '/' + name)",
|
|
225
|
+
expect: 'v3 I20:',
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: 'v2: one known-install TeX probe site stops rebasing into the sandbox (I20)',
|
|
229
|
+
rel: 'vibe-math-v2/vibe-math-v2.js',
|
|
230
|
+
from: "const p = await resolvePath(texProbeBase(base) + '/' + name)",
|
|
231
|
+
to: "const p = await resolvePath(base + '/' + name)",
|
|
232
|
+
expect: 'v2 I20:',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
name: 'run-tests: the gate stops sandboxing the TeX install roots (X4 — the sweep becomes host-dependent again)',
|
|
236
|
+
rel: 'tests/run-tests.mjs',
|
|
237
|
+
from: 'process.env.V4_TEX_ROOTS_SANDBOX = TEX_ROOTS_SANDBOX_DIR',
|
|
238
|
+
to: '',
|
|
239
|
+
expect: 'X4:',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
name: 'v5: a drive-letter absolute path is written back into the agent-facing guidance (I9b — reddens before any corpus regeneration)',
|
|
243
|
+
rel: 'vibe-math-v5/vibe-math-v5.js',
|
|
244
|
+
from: 'macOS at `/Library/TeX/texbin`',
|
|
245
|
+
to: 'macOS at `C:/TeX/texbin`',
|
|
246
|
+
expect: 'v5 I9b:',
|
|
247
|
+
},
|
|
132
248
|
]
|
|
133
249
|
|
|
134
250
|
if (process.argv.includes('--self-probe')) {
|
|
@@ -352,14 +468,92 @@ for (const P of PRESETS) {
|
|
|
352
468
|
// redden: requiring "either language" would have made the zh mutations no-ops once both exist).
|
|
353
469
|
const I15_LANG = { v4: 'zh', v5: 'zh', v3: 'en', v2: 'en' }
|
|
354
470
|
const I15_MARKERS = {
|
|
355
|
-
zh: ['检测不到 LaTeX 引擎时', '有界核查', '不要全盘扫描', '并重新检测', '
|
|
356
|
-
en: ['When no LaTeX engine is detected', 'never scan whole drives', 're-detect', '
|
|
471
|
+
zh: ['检测不到 LaTeX 引擎时', '有界核查', '不要全盘扫描', '并重新检测', '如实上报所办', '照旧降级', '绝不自动安装', '绝不写工作区之外', '当失败'],
|
|
472
|
+
en: ['When no LaTeX engine is detected', 'never scan whole drives', 're-detect', 'REPORT IT TO THE OFFICE', 'degrade exactly as today', 'never auto-install', 'never write outside the workspace'],
|
|
357
473
|
}
|
|
358
474
|
if (I15_LANG[P.tag]) {
|
|
359
475
|
check(I15_MARKERS[I15_LANG[P.tag]].every((s) => js.includes(s)),
|
|
360
476
|
P.tag + ' I15: the LaTeX-missing guidance carries the four steps + the three hard boundaries')
|
|
361
477
|
}
|
|
362
478
|
|
|
479
|
+
// I16 — a MEMBER-VISIBLE prompt must never demand an action the member cannot perform. A member's
|
|
480
|
+
// only channels are its own tools (the group chat / private messages): it cannot reach the user
|
|
481
|
+
// directly, so the TeX-install approval must be REPORTED to the OFFICE, which DOES own that channel.
|
|
482
|
+
// Reads the CODE stream (a comment may legitimately quote the old wording as history) and requires
|
|
483
|
+
// the reworded clause POSITIVELY, so putting the old instruction back reddens this invariant BY NAME
|
|
484
|
+
// (two self-probe mutations below: the zh clause on v5 and the en clause on v3).
|
|
485
|
+
check(!code.includes('向用户问一次') && !code.includes('ask the user once'),
|
|
486
|
+
P.tag + ' I16: a member-visible prompt never demands an action the member cannot perform (no direct user contact)')
|
|
487
|
+
check(code.includes('如实上报所办') || code.includes('let the OFFICE confirm with the user'),
|
|
488
|
+
P.tag + ' I16: the TeX-approval clause routes the confirmation through the OFFICE (the party that can reach the user)')
|
|
489
|
+
|
|
490
|
+
// I17 — the Lean-3 spelling `--search-path` must not survive in ANY agent-facing string. `code` has
|
|
491
|
+
// comments blanked, so a surviving occurrence is inside a string/template literal; the ONE exception a
|
|
492
|
+
// preset may keep is its explicit legacy-compatibility array (`ALSO_ACCEPTED`), and only there: that
|
|
493
|
+
// is what makes an agent-supplied old spelling still suppress injection. Everything else is the
|
|
494
|
+
// defect class task-27 removed (`lean --search-path <dir> <f>` fails at argument parsing, rc=1).
|
|
495
|
+
{
|
|
496
|
+
const hits = code.split(/\r?\n/).map((l, i) => [i + 1, l]).filter(([, l]) => l.includes('--search-path'))
|
|
497
|
+
const bad = hits.filter(([, l]) => !/ALSO_ACCEPTED/.test(l))
|
|
498
|
+
check(hits.length === 1 && bad.length === 0,
|
|
499
|
+
P.tag + ' I17: `--search-path` survives only in the legacy-compat array, never in agent-facing text',
|
|
500
|
+
bad.length ? 'code-stream lines ' + JSON.stringify(bad.map(([n]) => n))
|
|
501
|
+
: 'expected exactly 1 occurrence (the ALSO_ACCEPTED guard line), found ' + hits.length)
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// I18 — the AGENT-FACING preset text is a prompt too. `vibe-math-vN/agent.cordis.yml` is the persona/
|
|
505
|
+
// instruction surface the MAIN agent actually receives, and the plugin NEVER contains it — so I17
|
|
506
|
+
// cannot see it. The real-host SLV v5 run (15.2 min, candidate PASS) still showed exactly ONE
|
|
507
|
+
// `--search-path <VibeMath root>` in the v5 lean_lib "reuse first" paragraph. Guarded at the SOURCE;
|
|
508
|
+
// I18b below pins the generated copy so "edited the source but forgot to regenerate" also reddens.
|
|
509
|
+
{
|
|
510
|
+
const yml = read('vibe-math-' + P.tag + '/agent.cordis.yml')
|
|
511
|
+
const hits = yml ? yml.split(/\r?\n/).map((l, i) => [i + 1, l]).filter(([, l]) => l.includes('--search-path')) : []
|
|
512
|
+
check(yml !== null && hits.length === 0,
|
|
513
|
+
P.tag + ' I18: the agent-facing preset text never teaches the Lean-3 `--search-path`',
|
|
514
|
+
yml === null ? 'preset text unreadable (' + P.tag + '/agent.cordis.yml)'
|
|
515
|
+
: 'lines ' + JSON.stringify(hits.map(([n]) => n)))
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// I19 (all four presets carry the same candidates) — the paper-engine detection must cover TeX Live's
|
|
519
|
+
// DOCUMENTED install positions. A real-host SLV v5 run (15.2 min, candidate PASS) degraded to
|
|
520
|
+
// 'not-detected' although D:\texlive\2025\bin\windows\xelatex.exe existed, because only
|
|
521
|
+
// <ProgramFiles>/texlive was ever probed ("installed but not on PATH"). The stage must stay BOUNDED
|
|
522
|
+
// (an explicit cap plus a recent-year window) and must never become a whole-drive scan. Static check
|
|
523
|
+
// on the CODE stream: I15 guards the TEXT of the guidance, this guards the ROOTS it acts on.
|
|
524
|
+
{
|
|
525
|
+
const roots = ["'D:/texlive'", "'C:/texlive'", "'/usr/local/texlive'", "'/opt/texlive'", "'/Library/TeX/texbin'"]
|
|
526
|
+
const missing = roots.filter((s) => !code.includes(s))
|
|
527
|
+
check(missing.length === 0,
|
|
528
|
+
P.tag + ' I19: the TeX candidate roots cover the documented TeX Live install positions',
|
|
529
|
+
missing.length ? 'missing ' + JSON.stringify(missing) : '')
|
|
530
|
+
const cap = /const TEX_CANDIDATE_CAP = (\d+)/.exec(code)
|
|
531
|
+
const capEnforced = code.indexOf('texProbes++') >= 0 && code.indexOf('TEX_CANDIDATE_CAP', code.indexOf('texProbes++')) >= 0
|
|
532
|
+
check(!!cap && Number(cap[1]) >= 1 && Number(cap[1]) <= 40 && capEnforced,
|
|
533
|
+
P.tag + ' I19: the TeX known-root stage stays BOUNDED by an explicit cap <= 40',
|
|
534
|
+
(cap ? 'cap=' + cap[1] : 'no TEX_CANDIDATE_CAP declaration') + (capEnforced ? '' : '; declared but never enforced'))
|
|
535
|
+
const win = /const TEX_YEAR_LOOKBACK = (\d+)/.exec(code)
|
|
536
|
+
check(!!win && Number(win[1]) >= 1 && Number(win[1]) <= 4 && /Number\(b\) - Number\(a\)/.test(code),
|
|
537
|
+
P.tag + ' I19: the TeX Live year dirs stay bounded (on-disk newest first, then the recent-year window)',
|
|
538
|
+
win ? 'lookback=' + win[1] : 'no TEX_YEAR_LOOKBACK declaration')
|
|
539
|
+
check(!/['"][A-Za-z]:[\\/]['"]/.test(code),
|
|
540
|
+
P.tag + ' I19: no bare drive root among the candidate roots (never a whole-drive scan)')
|
|
541
|
+
|
|
542
|
+
// I20 (all four presets) — the TEST/DIAGNOSTIC sandbox seam must stay a RELOCATION of where the
|
|
543
|
+
// documented roots are looked for, never a product switch: every known-install TeX probe site goes
|
|
544
|
+
// through `texProbeBase`, and with the gate unset the helper returns its argument unchanged
|
|
545
|
+
// (identity = today's behaviour). Each preset uses its OWN gate name (V2/V3/V4/V5_TEX_ROOTS_SANDBOX).
|
|
546
|
+
// Static here; the behavioural half is I20b. The named-red proof is the probe-site mutations in
|
|
547
|
+
// SELF_PROBE_MUTATIONS: the audit's mutation mechanism is read-time, so it can redden this check but
|
|
548
|
+
// cannot reach a dynamically imported module.
|
|
549
|
+
const sandboxEnv = 'V' + P.tag.slice(1) + '_TEX_ROOTS_SANDBOX'
|
|
550
|
+
const allRebased = code.includes(sandboxEnv) && /if \(!texSandbox\) return p/.test(code) &&
|
|
551
|
+
/listDirs\(texProbeBase\(base\)\)/.test(code) && /resolvePath\(texProbeBase\(base\) \+ '\//.test(code) &&
|
|
552
|
+
!/listDirs\(base\)/.test(code) && !/resolvePath\(base \+ '\//.test(code)
|
|
553
|
+
check(allRebased,
|
|
554
|
+
P.tag + ' I20: every known-install TeX probe site is rebased through the ' + sandboxEnv + ' gate (unset = identity)')
|
|
555
|
+
}
|
|
556
|
+
|
|
363
557
|
// I3 — the defect rule is in the injected text.
|
|
364
558
|
check(js.includes('不要投 0'), P.tag + ' I3: injected text forbids a 0 vote on a defect')
|
|
365
559
|
check(/'defect'/.test(js) || /"defect"/.test(js), P.tag + ' I3: injected text names decision=\'defect\'')
|
|
@@ -411,6 +605,37 @@ for (const P of PRESETS) {
|
|
|
411
605
|
check(!/\d{4}-\d{2}-\d{2}[ T]\d{2}:\d{2}/.test(corpus), P.tag + ' I9: corpus carries no timestamp (deterministic)')
|
|
412
606
|
check(!/vibe-v[0-9]-[a-z]+-[A-Za-z0-9]{4,}/.test(corpus), P.tag + ' I9: corpus leaks no temp-dir name')
|
|
413
607
|
|
|
608
|
+
// I9b (task-29 follow-up) — the SOURCE-LEVEL early warning that I9 lacks: a drive-letter absolute path
|
|
609
|
+
// written into AGENT-FACING text must redden HERE, immediately, not only after the corpus is
|
|
610
|
+
// regenerated (I9 is the later, mirror-level net). It reads the comment-blanked `code` stream, so a
|
|
611
|
+
// comment may still quote a real host path as evidence (task-28's `D:\texlive\2025\...` line, and v5's
|
|
612
|
+
// task-17 `Z:/no/such/xelatex.exe` comment): "assertions read code, not comments".
|
|
613
|
+
// CRITERION — I9's `[A-Za-z]:[\\/]` is a CORPUS-level test (the corpus holds DECODED text). Applied to
|
|
614
|
+
// SOURCE it false-positives on every escape sequence that merely looks like `<letter>:\`
|
|
615
|
+
// (`'…:\n'`, `/…limit:\s*/`; measured on this tree: 24 / 27 / 15 / 11 such lines in v2/v3/v4/v5). The
|
|
616
|
+
// source SPELLING of the same path is `D:/…` or `D:\\…`, so the equivalent criterion is
|
|
617
|
+
// `/[A-Za-z]:(?:\/|\\\\)/` — a bare `:\n` escape never matches it.
|
|
618
|
+
// EXCLUSIONS (documented, auditable; PURE CODE constructs that never reach a prompt):
|
|
619
|
+
// 1. `TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive']` — the candidate-root list the detector
|
|
620
|
+
// walks; resolveKnownTool reads it, nothing concatenates it into agent text.
|
|
621
|
+
// 2. resolveKnownTool's PATH-fallback defaults `'C:/Users/Default'`, `'C:/Program Files'`,
|
|
622
|
+
// `'C:/Program Files (x86)'` — used only when the host env vars are absent.
|
|
623
|
+
// Where they live today: v2 L136/146/148/149 · v3 L169/179/181/182 · v4 L57/67/69/70 · v5 L67/77/79/80.
|
|
624
|
+
// The anti-vacuity check re-derives them on every run, so a refactor that drops them is CAUGHT instead
|
|
625
|
+
// of silently widening the exclusion.
|
|
626
|
+
{
|
|
627
|
+
const DRIVE_ABS = /[A-Za-z]:(?:\/|\\\\)/
|
|
628
|
+
const CODE_ONLY = /TEX_DRIVE_YEAR_ROOTS|env\.ProgramFiles|ProgramFiles\(x86\)|env\.USERPROFILE/
|
|
629
|
+
const flagged = [], excluded = []
|
|
630
|
+
code.split(/\r?\n/).forEach((l, i) => { if (DRIVE_ABS.test(l)) (CODE_ONLY.test(l) ? excluded : flagged).push(i + 1) })
|
|
631
|
+
check(flagged.length === 0,
|
|
632
|
+
P.tag + ' I9b: no drive-letter absolute path in agent-facing text (guidance, warnings, tool/param descriptions)',
|
|
633
|
+
flagged.length ? 'lines ' + JSON.stringify(flagged) + ' — ' + JSON.stringify(code.split(/\r?\n/)[flagged[0] - 1].trim().slice(0, 120)) : '')
|
|
634
|
+
check(excluded.length >= 3,
|
|
635
|
+
P.tag + ' I9b: the documented code-only exclusions are still present (the guard is not vacuous)',
|
|
636
|
+
'excluded lines ' + JSON.stringify(excluded))
|
|
637
|
+
}
|
|
638
|
+
|
|
414
639
|
// I10 — the prompt-rule probes exist for this preset (a guard nobody can prove is a guard nobody has).
|
|
415
640
|
const probeSrc = read('tests/audit-formal-sensitivity.mjs') || ''
|
|
416
641
|
for (const kind of ['fidelity-defect-rule-removed', 'abbreviated-tool-name-injected', 'require-wording-removed', 'defect-decision-not-offered']) {
|
|
@@ -506,6 +731,48 @@ for (const P of PRESETS) {
|
|
|
506
731
|
notes.push(P.tag + ': plugin ' + js.length + 'B · suite ' + suite.length + 'B · corpus ' + corpus.length + 'B')
|
|
507
732
|
}
|
|
508
733
|
|
|
734
|
+
// I20b — the sandbox seam, BEHAVIOURALLY and host-independently (ALL FOUR presets). Point each preset's
|
|
735
|
+
// own gate at a fresh EMPTY directory and require the known-install stage to
|
|
736
|
+
// (a) find NOTHING — a suite can pin "no LaTeX here" on ANY host,
|
|
737
|
+
// (b) still report a NAMED list — triedPaths does not go silent (task-9),
|
|
738
|
+
// (c) probe ONLY inside the sandbox — no real install is touched, and
|
|
739
|
+
// (d) stay within the cap — the relocation does not unbound the stage.
|
|
740
|
+
// Portable by construction: on a host WITH TeX Live (a) is the property under test, on one WITHOUT it
|
|
741
|
+
// is trivially true, and (c) is host-independent either way.
|
|
742
|
+
{
|
|
743
|
+
const items = PRESETS.map((P) => ({
|
|
744
|
+
tag: P.tag, file: join(HERE, P.js), env: 'V' + P.tag.slice(1) + '_TEX_ROOTS_SANDBOX', sandbox: '',
|
|
745
|
+
}))
|
|
746
|
+
const dir = mkdtempSync(join(tmpdir(), 'prompt-invariants-tex-sandbox-'))
|
|
747
|
+
try {
|
|
748
|
+
for (const it of items) it.sandbox = join(dir, it.tag)
|
|
749
|
+
const probe = join(dir, 'probe.mjs')
|
|
750
|
+
writeFileSync(probe, [
|
|
751
|
+
"import { pathToFileURL } from 'node:url'",
|
|
752
|
+
'const items = JSON.parse(process.argv[2])',
|
|
753
|
+
'for (const it of items) process.env[it.env] = it.sandbox',
|
|
754
|
+
'const out = []',
|
|
755
|
+
'for (const it of items) {',
|
|
756
|
+
' const mod = await import(pathToFileURL(it.file).href)',
|
|
757
|
+
" const r = await mod.resolveKnownTool({ resolveExecutable: async () => { throw new Error('no PATH') } }, { name: 'xelatex', explicit: '', kind: 'tex' })",
|
|
758
|
+
' out.push({ tag: it.tag, exe: r.exe, via: r.via, tried: r.tried })',
|
|
759
|
+
'}',
|
|
760
|
+
'console.log(JSON.stringify(out))',
|
|
761
|
+
].join('\n'))
|
|
762
|
+
const r = spawnSync(process.execPath, [probe, JSON.stringify(items)], { encoding: 'utf8' })
|
|
763
|
+
let rows = null
|
|
764
|
+
try { rows = JSON.parse(r.stdout) } catch (e) { /* reported below */ }
|
|
765
|
+
for (const it of items) {
|
|
766
|
+
const row = rows && rows.find((x) => x.tag === it.tag)
|
|
767
|
+
const slash = (s) => String(s).split('\\').join('/')
|
|
768
|
+
const inside = !!row && row.tried.length > 0 && row.tried.every((p) => slash(p).indexOf(slash(it.sandbox)) === 0)
|
|
769
|
+
check(!!row && row.exe === null && inside && row.tried.length <= 40,
|
|
770
|
+
'I20b: ' + it.tag + ' — with ' + it.env + ' set the known-install stage finds nothing, probes only inside the sandbox, and names <= 40 paths',
|
|
771
|
+
row ? JSON.stringify({ exe: row.exe, via: row.via, triedCount: row.tried.length, inside }) : 'probe failed: ' + String(r.stderr || r.stdout || '').slice(0, 160))
|
|
772
|
+
}
|
|
773
|
+
} finally { rmSync(dir, { recursive: true, force: true }) }
|
|
774
|
+
}
|
|
775
|
+
|
|
509
776
|
// Cross-preset: the probe script must refuse to report success on an empty selection (false green).
|
|
510
777
|
{
|
|
511
778
|
const probeSrc = read('tests/audit-formal-sensitivity.mjs') || ''
|
|
@@ -513,6 +780,29 @@ for (const P of PRESETS) {
|
|
|
513
780
|
const runner = read('tests/run-tests.mjs') || ''
|
|
514
781
|
check(/no suites matched/.test(runner), 'X2: the suite runner fails when no suite matches')
|
|
515
782
|
check(/argv\[i \+ 1\]/.test(runner) && /argv\[\+\+i\]/.test(runner), 'X3: the suite runner accepts both --flag=x and --flag x')
|
|
783
|
+
// X4 (task-29) — the GATE must be independent of whether the HOST has TeX installed: the runner points
|
|
784
|
+
// every child suite's documented-root sandbox gate at ONE empty scratch dir (children inherit env), and
|
|
785
|
+
// removes it on exit. Static here; the behavioural proof is the gate itself (the "no LaTeX on this
|
|
786
|
+
// host" suites pass on a machine that really HAS TeX Live). Named red: the mutation below drops one of
|
|
787
|
+
// the four assignments.
|
|
788
|
+
const texGateVars = ['V2_TEX_ROOTS_SANDBOX', 'V3_TEX_ROOTS_SANDBOX', 'V4_TEX_ROOTS_SANDBOX', 'V5_TEX_ROOTS_SANDBOX']
|
|
789
|
+
const texGateMissing = texGateVars.filter((v) => !runner.includes(v))
|
|
790
|
+
check(texGateMissing.length === 0 && /mkdtempSync\(/.test(runner) && /rmSync\(/.test(runner) && /process\.on\('exit'/.test(runner),
|
|
791
|
+
'X4: run-tests.mjs sandboxes the documented TeX install roots for every child suite (host-independent gate)',
|
|
792
|
+
texGateMissing.length ? 'missing ' + JSON.stringify(texGateMissing) : 'no mkdtemp/exit-cleanup found')
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// I18b — the GENERATED bundle patch repeats the preset text (`node scripts/build-preset-rows.mjs`
|
|
796
|
+
// writes cordis.patch.yml from the four agent.cordis.yml). Guarding the copy catches the second failure
|
|
797
|
+
// mode: the preset source is fixed but the patch was never regenerated, so the shipped prompt keeps the
|
|
798
|
+
// Lean-3 spelling. tests/audit-preset-rows.test.mjs already pins byte-identity with the generator; this
|
|
799
|
+
// pins the SEMANTICS (the -R text is really there, and the old spelling is not).
|
|
800
|
+
{
|
|
801
|
+
const patch = read('cordis.patch.yml')
|
|
802
|
+
const hits = patch ? patch.split(/\r?\n/).map((l, i) => [i + 1, l]).filter(([, l]) => l.includes('--search-path')) : []
|
|
803
|
+
check(patch !== null && patch.includes('-R <VibeMath root>') && hits.length === 0,
|
|
804
|
+
'I18b: the generated cordis.patch.yml teaches no `--search-path` (regenerate: node scripts/build-preset-rows.mjs)',
|
|
805
|
+
patch === null ? 'cordis.patch.yml unreadable' : 'lines ' + JSON.stringify(hits.map(([n]) => n)))
|
|
516
806
|
}
|
|
517
807
|
|
|
518
808
|
// X5–X7: guard the guard. I1/I13/I14 trust stripComments(), and all four presets contain a regex
|