dsh-vibe-math 2.7.2 → 2.8.0

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.
Files changed (153) hide show
  1. package/LICENSE +21 -21
  2. package/README.en.md +682 -681
  3. package/README.md +683 -682
  4. package/cordis.patch.yml +112 -16
  5. package/docs/AUDIT-CHECKLIST.md +755 -518
  6. package/docs/COMPAT-AUDIT-ROUND2.md +354 -328
  7. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  8. package/docs/audit-methodology.md +93 -0
  9. package/docs/final-paper.md +172 -172
  10. package/docs/formal-verification.md +613 -588
  11. package/docs/generate_framework_diagram_v2.py +114 -114
  12. package/docs/generate_framework_diagram_v3.py +127 -127
  13. package/docs/math-computation.md +195 -187
  14. package/docs/parameter-schema.md +37 -0
  15. package/docs/release-notes/README.en.md +2 -0
  16. package/docs/release-notes/README.md +2 -0
  17. package/docs/release-notes/RELEASE-NOTES-2.0.22.md +112 -112
  18. package/docs/release-notes/RELEASE-NOTES-2.1.0.md +143 -143
  19. package/docs/release-notes/RELEASE-NOTES-2.2.0.md +266 -266
  20. package/docs/release-notes/RELEASE-NOTES-2.2.1.md +43 -43
  21. package/docs/release-notes/RELEASE-NOTES-2.2.2.md +88 -88
  22. package/docs/release-notes/RELEASE-NOTES-2.3.0.md +207 -207
  23. package/docs/release-notes/RELEASE-NOTES-2.3.1.md +134 -134
  24. package/docs/release-notes/RELEASE-NOTES-2.3.10.md +105 -105
  25. package/docs/release-notes/RELEASE-NOTES-2.3.11.md +57 -57
  26. package/docs/release-notes/RELEASE-NOTES-2.3.12.md +80 -80
  27. package/docs/release-notes/RELEASE-NOTES-2.3.2.md +145 -145
  28. package/docs/release-notes/RELEASE-NOTES-2.3.3.md +115 -115
  29. package/docs/release-notes/RELEASE-NOTES-2.3.4.md +69 -69
  30. package/docs/release-notes/RELEASE-NOTES-2.3.5.md +63 -63
  31. package/docs/release-notes/RELEASE-NOTES-2.3.6.md +66 -66
  32. package/docs/release-notes/RELEASE-NOTES-2.3.7.md +59 -59
  33. package/docs/release-notes/RELEASE-NOTES-2.3.8.md +45 -45
  34. package/docs/release-notes/RELEASE-NOTES-2.3.9.md +70 -70
  35. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
  36. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
  37. package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
  38. package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
  39. package/docs/release-notes/RELEASE-NOTES-2.8.0.en.md +65 -0
  40. package/docs/release-notes/RELEASE-NOTES-2.8.0.md +65 -0
  41. package/docs/release-notes/TEMPLATE.md +6 -0
  42. package/docs/release-playbook.md +103 -0
  43. package/docs/slv-playbook.md +117 -0
  44. package/docs/status-report-fields.md +167 -0
  45. package/docs/test-timing.md +158 -154
  46. package/docs//346/236/266/346/236/204/345/233/276.md +77 -77
  47. package/installer.js +793 -783
  48. package/package.json +260 -203
  49. package/prompt-corpus-persona/persona-corpus.json +9 -9
  50. package/prompt-corpus-persona/persona-corpus.md +225 -33
  51. package/prompt-corpus-v2/formal-verify-v2.json +295 -115
  52. package/prompt-corpus-v2/formal-verify-v2.md +2290 -282
  53. package/prompt-corpus-v3/formal-verify-v3.json +112 -112
  54. package/prompt-corpus-v3/formal-verify-v3.md +227 -143
  55. package/prompt-corpus-v4/formal-verify-v4.json +18 -18
  56. package/prompt-corpus-v4/formal-verify-v4.md +49 -44
  57. package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
  58. package/prompt-corpus-v5/prompt-corpus-v5.md +573 -207
  59. package/scripts/clean-temp.mjs +234 -0
  60. package/scripts/update-doc-counts.mjs +71 -0
  61. package/tests/audit-artifact-docs.mjs +72 -0
  62. package/tests/audit-artifact-docs.mutants.mjs +58 -0
  63. package/tests/audit-engine-faces.mjs +298 -0
  64. package/tests/audit-formal-sensitivity.mjs +345 -342
  65. package/tests/audit-installer-assertions.mutants.mjs +63 -0
  66. package/tests/audit-installer-compat.test.mjs +256 -208
  67. package/tests/audit-installer-policy.test.mjs +41 -8
  68. package/tests/audit-math-computation-contract.mjs +64 -8
  69. package/tests/audit-math-computation-contract.mutants.mjs +106 -0
  70. package/tests/audit-math-computation-parity.mjs +164 -3
  71. package/tests/audit-math-computation-sensitivity.mjs +5 -3
  72. package/tests/audit-package-membership.mjs +57 -0
  73. package/tests/audit-package-membership.mutants.mjs +28 -0
  74. package/tests/audit-participant-set-parity.mjs +160 -0
  75. package/tests/audit-path-discipline.mjs +83 -0
  76. package/tests/audit-path-discipline.mutants.mjs +51 -0
  77. package/tests/audit-persona-sensitivity.mjs +323 -309
  78. package/tests/audit-persona-surface.mutants.mjs +69 -0
  79. package/tests/audit-persona-surface.test.mjs +431 -390
  80. package/tests/audit-prompt-duplication.mjs +143 -0
  81. package/tests/audit-prompt-duplication.mutants.mjs +51 -0
  82. package/tests/audit-prompt-invariants.mjs +589 -508
  83. package/tests/audit-readme-bilingual.test.mjs +152 -0
  84. package/tests/audit-readme-counts.mjs +95 -0
  85. package/tests/audit-readme-counts.mutants.mjs +52 -0
  86. package/tests/audit-registration.mjs +78 -0
  87. package/tests/audit-spec-traceability.mjs +193 -193
  88. package/tests/audit-status-report-fields.mjs +172 -0
  89. package/tests/audit-status-report-fields.mutants.mjs +64 -0
  90. package/tests/audit-v3-registration-parity.mjs +156 -0
  91. package/tests/audit-v5-integrity.mjs +795 -508
  92. package/tests/audit-v5-lean-abstention.mjs +392 -0
  93. package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
  94. package/tests/audit-v5-prompt-duplication.mjs +88 -0
  95. package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
  96. package/tests/audit-v5-sensitivity.mjs +433 -427
  97. package/tests/e2e-identity-a6.mutants.mjs +65 -0
  98. package/tests/e2e-v5-round2.test.mjs +2453 -1981
  99. package/tests/formal-verify-v2.test.mjs +1778 -1724
  100. package/tests/formal-verify-v3.mutants.mjs +117 -0
  101. package/tests/formal-verify-v3.test.mjs +1724 -1671
  102. package/tests/formal-verify-v4.mutants.mjs +152 -0
  103. package/tests/formal-verify-v4.test.mjs +2114 -1574
  104. package/tests/formal-verify-v5.test.mjs +790 -724
  105. package/tests/helpers/doc-counts-format.mjs +41 -0
  106. package/tests/helpers/math-computation-fake-seam.mjs +4 -0
  107. package/tests/math-computation-a1.mutants.mjs +72 -0
  108. package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
  109. package/tests/math-computation-discovery.mutants.mjs +76 -0
  110. package/tests/math-computation-shared.test.mjs +392 -6
  111. package/tests/math-computation-v2.test.mjs +779 -0
  112. package/tests/math-computation-v3.test.mjs +771 -0
  113. package/tests/math-computation-v4.mutants.mjs +133 -0
  114. package/tests/math-computation-v4.test.mjs +739 -0
  115. package/tests/prompt-v5-integrity.test.mjs +1291 -1280
  116. package/tests/run-tests.mjs +393 -202
  117. package/tests/run-tests.mutants.mjs +70 -0
  118. package/tests/selfdrive-v5.mjs +560 -508
  119. package/tests/selfdrive-v5.mutants.mjs +132 -0
  120. package/tests/temp-hygiene.mutants.mjs +80 -0
  121. package/tests/v2-fix-probes.mutants.mjs +104 -0
  122. package/tests/v2-fix-probes.test.mjs +1040 -0
  123. package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
  124. package/tests/v2v3-interrupt.mutants.mjs +111 -0
  125. package/tests/v3-fix-probes.mutants.mjs +99 -0
  126. package/tests/v3-fix-probes.test.mjs +892 -0
  127. package/tests/v4-final-paper.mutants.mjs +108 -0
  128. package/tests/v5-institute-fixes.mutants.mjs +218 -0
  129. package/vibe-math-v2/agent.cordis.yml +378 -322
  130. package/vibe-math-v2/math-computation.js +144 -53
  131. package/vibe-math-v2/math-engines.js +12 -12
  132. package/vibe-math-v2/preset.yml +2 -2
  133. package/vibe-math-v2/v2/347/232/204/351/242/235/345/244/226/350/246/201/346/261/202.txt +21 -21
  134. package/vibe-math-v2/vibe-math-v2.js +5097 -4755
  135. package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +491 -487
  136. package/vibe-math-v3/agent.cordis.yml +422 -356
  137. package/vibe-math-v3/math-computation.js +144 -53
  138. package/vibe-math-v3/math-engines.js +12 -12
  139. package/vibe-math-v3/preset.yml +2 -2
  140. package/vibe-math-v3/vibe-math-v3.js +6235 -5873
  141. package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +649 -649
  142. package/vibe-math-v4/agent.cordis.yml +438 -402
  143. package/vibe-math-v4/math-computation.js +144 -53
  144. package/vibe-math-v4/math-engines.js +12 -12
  145. package/vibe-math-v4/preset.yml +2 -2
  146. package/vibe-math-v4/vibe-math-v4.js +4490 -4269
  147. package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +1015 -1013
  148. package/vibe-math-v5/agent.cordis.yml +46 -12
  149. package/vibe-math-v5/math-computation.js +144 -53
  150. package/vibe-math-v5/math-engines.js +12 -12
  151. package/vibe-math-v5/vibe-math-v5.js +1266 -229
  152. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1660
  153. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -0,0 +1,143 @@
1
+ // ============================================================================
2
+ // audit-prompt-duplication.mjs — shipped invariant for v2/v3/v4 (v5 has its own,
3
+ // tests/audit-v5-prompt-duplication.mjs): every preset's injected toolkit prompt
4
+ // text exists TWICE in its `agent.cordis.yml` (two agent-instructions entries:
5
+ // office + members). In these three presets the SECOND entry continues with more
6
+ // YAML after the shared toolkit text, so the invariant is stated as:
7
+ // (1) the two copies share a substantial COMMON PREFIX of prompt lines (≥ 60% of
8
+ // the shorter copy, and every line the first copy adds beyond the prefix is
9
+ // pure YAML scaffolding — never prompt prose), and
10
+ // (2) the review-10 clauses (V2-1 disclosures, V2-3 path bases) are present inside
11
+ // that shared prefix, i.e. in BOTH copies.
12
+ // No YAML anchor is used on purpose (it would change what the loader expands).
13
+ // Falsifiability: `tests/audit-prompt-duplication.mutants.mjs` (single-copy edit ⇒ named red).
14
+ // Run: node tests/audit-prompt-duplication.mjs [--json]
15
+ // ============================================================================
16
+ import { readFileSync, existsSync } from 'node:fs'
17
+ import { fileURLToPath } from 'node:url'
18
+ import { dirname, join } from 'node:path'
19
+
20
+ const HERE = dirname(fileURLToPath(import.meta.url))
21
+ const REPO = join(HERE, '..')
22
+ const PRESETS = ['vibe-math-v2', 'vibe-math-v3', 'vibe-math-v4']
23
+ // In-memory single-site mutation for the mutants driver: [preset, from, to] (first occurrence only).
24
+ const MUT = process.env.PROMPT_DUP_MUTATE ? JSON.parse(process.env.PROMPT_DUP_MUTATE) : null
25
+ const SKIP_CLAUSES = process.env.PROMPT_DUP_SKIP_CLAUSES === '1'
26
+
27
+ const PATH_CLAUSES = [
28
+ ['V2-3 base statement', /every path here is resolved against the SESSION WORKING DIRECTORY/],
29
+ ['V2-3 short-form equivalence', /names the SAME file/],
30
+ ]
31
+ // Clause expectations are CODE-TRUTH-DRIVEN, not global: a DISCLOSURE is required only in presets
32
+ // whose code actually implements that behaviour, and the guard checks BOTH directions (prompt
33
+ // declares it ⇔ the JS carries the behaviour marker, re-measured every run). Each entry is
34
+ // [name, prompt regex, code marker regex, which presets the code justifies]. A preset whose JS lacks
35
+ // the marker must NOT declare the clause (`must NOT be declared here`) — declaring a behaviour that
36
+ // does not exist is worse than the undisclosed-behaviour gap it was meant to close.
37
+ const DISCLOSURES = [
38
+ // window 1 (V2-1 ①–③)
39
+ ['V2-1 paper force-finalise / reaping disclosure', /DISCLOSURE \(existing behaviour\): the FINAL PAPER/, /reapedThisRun|forcedAfterCap/, ['v2', 'v3']],
40
+ ['V2-1 auto-resolve disclosure', /DISCLOSURE \(existing behaviour\): switching the mode to `auto`/, /auto-resolved/, ['v2', 'v3']],
41
+ ['V2-1 progress-push disclosure', /DISCLOSURE \(existing behaviour\): the framework itself pushes a progress/, /\u8fdb\u5ea6\u66f4\u65b0\uff1a/, ['v2', 'v3']],
42
+ // window 2 (V2-1 ④–⑦)
43
+ ['V2-1 project-lock takeover disclosure', /DISCLOSURE \(existing behaviour\): a second session can TAKE OVER/, /lock taken over/, ['v3']],
44
+ ['V2-1 auto-reprioritisation disclosure', /DISCLOSURE \(existing behaviour\): the framework can adjust problem\/proposition priorities/, /priorities auto-adjusted/, ['v2', 'v3']],
45
+ ['V2-1 math-policy refusal disclosure', /DISCLOSURE \(existing behaviour\): when mathMode is `typed`/, /mathMode|typed\+shell/, ['v2', 'v3', 'v4']],
46
+ ['V2-3e math-policy line', /math_computation policy: mathComputation/, /mathComputation/, ['v2', 'v3', 'v4']],
47
+ ['V2-1 post-compaction recap disclosure', /DISCLOSURE \(existing behaviour\): after a real context compaction the framework injects/, /\u6838\u5fc3\u89c4\u5219\u91cd\u7533|CONTEXT COMPACT/, ['v4']],
48
+ ]
49
+ const PRESET_KEY = { 'vibe-math-v2': 'v2', 'vibe-math-v3': 'v3', 'vibe-math-v4': 'v4' }
50
+ // Declaration-syntax scanner (§9.7⑨): anchored on the SHAPE of a declaration, not on a substring.
51
+ const NAME_RE = '[a-zA-Z][A-Za-z0-9]*'
52
+ const DECL_BULLET = new RegExp('^\\s*-\\s+`?(' + NAME_RE + ')`?\\s*=\\s*\\S')
53
+ const DECL_LIST = new RegExp('^\\s*(?:' + NAME_RE + '\\s*=\\s*[^,\\n]{1,40})(?:\\s*[,,]\\s*' + NAME_RE + '\\s*=\\s*[^,\\n]{1,40})+\\s*$')
54
+ function declarations(rawLines) {
55
+ const out = []
56
+ rawLines.forEach((raw, i) => {
57
+ const line = raw.replace(/\r$/, '')
58
+ const m1 = DECL_BULLET.exec(line)
59
+ if (m1) { out.push({ name: m1[1], line: i + 1 }); return }
60
+ if (DECL_LIST.test(line)) for (const m of line.matchAll(new RegExp('\\b(' + NAME_RE + ')\\s*=\\s*', 'g'))) out.push({ name: m[1], line: i + 1 })
61
+ })
62
+ return out
63
+ }
64
+ const normLine = (l) => l.replace(/\s+/g, ' ').replace(/\{\{cwd\}\}|\{\{model\}\}/g, '<T>').trim()
65
+ const SCAFFOLD = /^(-|#|\s*$)|^(suffix|text|priority|config|name|id|content|role):|^You are a coding agent powered by/
66
+
67
+ const failures = []
68
+ let passed = 0
69
+ const check = (cond, msg) => { if (cond) passed += 1; else failures.push(msg) }
70
+ const report = []
71
+
72
+ for (const preset of PRESETS) {
73
+ const rel = preset + '/agent.cordis.yml'
74
+ const abs = join(REPO, rel)
75
+ if (!existsSync(abs)) { check(false, rel + ' is missing'); continue }
76
+ let text = readFileSync(abs, 'utf8')
77
+ if (MUT && MUT[0] === preset) {
78
+ const i = text.indexOf(MUT[1])
79
+ if (i === -1) { check(false, preset + ': mutant anchor not found (harness problem)'); continue }
80
+ text = text.slice(0, i) + MUT[2] + text.slice(i + MUT[1].length)
81
+ }
82
+ const lines = text.split('\n')
83
+ const starts = []
84
+ lines.forEach((l, i) => { if (/## Vibe Math V\d+ toolkit/.test(l) || /This session includes "Vibe Math V/.test(l)) starts.push(i) })
85
+ const heads = []
86
+ for (const s of starts) if (!heads.length || s - heads[heads.length - 1] > 3) heads.push(s)
87
+ check(heads.length === 2, preset + ': exactly TWO toolkit prompt copies (got ' + heads.length + ' at ' + JSON.stringify(heads.map((h) => h + 1)) + ')')
88
+ if (heads.length !== 2) { report.push({ preset, copies: heads.length }); continue }
89
+ const A = lines.slice(heads[0], heads[1]).map(normLine).filter((x) => x)
90
+ const B = lines.slice(heads[1]).map(normLine).filter((x) => x)
91
+ let common = 0
92
+ while (common < A.length && common < B.length && A[common] === B[common]) common += 1
93
+ const shared = A.slice(0, common).join(' ')
94
+ const tailA = A.slice(common)
95
+ const shorter = Math.min(A.length, B.length)
96
+ const ratio = common / Math.max(1, shorter)
97
+ check(ratio >= 0.6, preset + ': the two copies share a substantial common prefix (' + common + '/' + shorter + ' = ' + ratio.toFixed(2) + ' ≥ 0.60)')
98
+ const proseTail = tailA.filter((l) => !SCAFFOLD.test(l))
99
+ check(proseTail.length === 0, preset + ': everything the first copy adds beyond the shared prefix is YAML scaffolding, not prompt prose (' + proseTail.length + ' prose line(s) left'
100
+ + (proseTail.length ? ': ' + JSON.stringify(proseTail.slice(0, 2).map((x) => x.slice(0, 60))) : '') + ')')
101
+ if (!SKIP_CLAUSES) {
102
+ for (const [name, re] of PATH_CLAUSES) check(re.test(shared), preset + ': ' + name + ' present in BOTH copies (inside the shared prefix)')
103
+ // bidirectional: the prompt declares the behaviour ⇔ the JS carries its marker
104
+ const jsAbs = join(REPO, preset, preset + '.js')
105
+ const js = existsSync(jsAbs) ? readFileSync(jsAbs, 'utf8') : ''
106
+ check(!!js, preset + ': the JS source is readable for the code-truth check')
107
+ DISCLOSURES.forEach(([name, promptRe, codeRe, applies]) => {
108
+ const expected = applies.indexOf(PRESET_KEY[preset]) !== -1
109
+ const inCode = codeRe.test(js)
110
+ check(inCode === expected, preset + ': code-truth for "' + name + '" (expected=' + expected + ', code marker present=' + inCode + ')')
111
+ if (expected) check(promptRe.test(shared), preset + ': ' + name + ' present in BOTH copies (inside the shared prefix)')
112
+ else check(!promptRe.test(shared), preset + ': ' + name + ' must NOT be declared here (the code does not implement it)')
113
+ })
114
+ // ── declaration-syntax scanner (§9.7⑨) ────────────────────────────────────────────────
115
+ // A parameter the prompt PRESENTS AS A DECLARATION must be readable as `params.<name>` in that
116
+ // preset's JS, and must be declared in BOTH copies. The scan anchors on the declaration's
117
+ // SYNTAX (a bullet starting with `- <name> = …`, or a pure `name = value, name2 = value2`
118
+ // list), never on a substring: a mention inside a disclosure sentence must not count as a
119
+ // declaration. On today's prompts this scan is EMPTY by construction (vacuous-by-construction);
120
+ // the mutant driver injects a declaration line into ONE copy to prove the scan bites.
121
+ const declA = declarations(lines.slice(heads[0], heads[1]))
122
+ const declB = declarations(lines.slice(heads[1]))
123
+ const namesA = [...new Set(declA.map((d) => d.name))]
124
+ const namesB = new Set(declB.map((d) => d.name))
125
+ for (const name of namesA) {
126
+ check(namesB.has(name), preset + ': parameter "' + name + '" declared in copy 1 (L' + declA.find((d) => d.name === name).line + ' of its slice) must also be declared in copy 2')
127
+ check(new RegExp('params\\.' + name + '\\b').test(js), preset + ': parameter "' + name + '" is presented as a declaration but the JS never reads params.' + name)
128
+ }
129
+ for (const d of declB) {
130
+ if (!namesA.includes(d.name)) check(false, preset + ': parameter "' + d.name + '" is declared in copy 2 only (L' + d.line + ' of its slice) — both copies must agree')
131
+ }
132
+ report.push({ preset, copies: 2, sharedLines: common, ratio: Number(ratio.toFixed(3)), declarations: namesA.length })
133
+ }
134
+ }
135
+
136
+ if (process.argv.includes('--json')) console.log(JSON.stringify({ passed, failed: failures.length, failures, report }))
137
+ else {
138
+ console.log('v2/v3/v4 prompt duplication audit')
139
+ for (const r of report) console.log(' ' + r.preset + ': copies=' + (r.copies || 2) + ' sharedLines=' + r.sharedLines + ' ratio=' + r.ratio + ' declaredParams=' + (r.declarations === undefined ? '-' : r.declarations))
140
+ for (const f of failures) console.log(' FAIL - ' + f)
141
+ console.log((failures.length ? 'FAILED' : 'ALL GREEN') + ' — passed=' + passed + ' failed=' + failures.length)
142
+ }
143
+ process.exit(failures.length ? 1 : 0)
@@ -0,0 +1,51 @@
1
+ // Falsifiability driver for tests/audit-prompt-duplication.mjs (v2/v3/v4): a SINGLE-SITE edit in ONE
2
+ // prompt copy of ONE preset must turn both the equality check and the clause check NAMED-red, while
3
+ // the unmutated control stays green.
4
+ import { spawnSync } from 'node:child_process'
5
+ const SUITE = 'D:/wd/vibemath开发/Vibe-Mathematics/tests/audit-prompt-duplication.mjs'
6
+ const run = (env) => {
7
+ const r = spawnSync(process.execPath, [SUITE, '--json'], { encoding: 'utf8', timeout: 120000, env: Object.assign({}, process.env, env || {}) })
8
+ let parsed = null
9
+ try { parsed = JSON.parse(r.stdout) } catch (e) { /* reported */ }
10
+ return { code: r.status, parsed, out: String(r.stdout || '') + String(r.stderr || '') }
11
+ }
12
+ const control = run({}) // task-9: with all four presets carrying the shared LaTeX-missing block, the clause
13
+ // block is ACTIVE in the control too — a skipped clause check is a vacuous guard.
14
+ const MUTS = [
15
+ ['v2 single-copy drift', ['vibe-math-v2', 'Data lives under {{cwd}}/VibeMath/Projects/<project>/', 'Data lives under {{cwd}}/VibeMath/Projects/<project>/ (mutated in ONE copy only)']],
16
+ ['v3 single-copy drift', ['vibe-math-v3', 'TRUST RULE: only Verified/', 'TRUST RULE (mutated in ONE copy only): only Verified/']],
17
+ ['v4 single-copy drift', ['vibe-math-v4', 'TRUST RULE: only Verified/', 'TRUST RULE (mutated in ONE copy only): only Verified/']],
18
+ ]
19
+ // Declaration-syntax scanner self-probe (§9.7⑨): inject ONE declaration-syntax line into ONE copy at
20
+ // that preset's own anchor. v2/v3 read params.activityLogCap ⇒ the single-copy rule must fire; v4
21
+ // never reads it ⇒ the code-truth rule must fire.
22
+ const DECL_MUTS = [
23
+ ['v2 declaration line (one copy)', ['vibe-math-v2', 'When the user asks about progress, call vibe_math_report and summarize in plain language.', 'When the user asks about progress, call vibe_math_report and summarize in plain language.\r\n - activityLogCap = 100']],
24
+ ['v3 declaration line (one copy)', ['vibe-math-v3', 'When the user asks about progress, call vibe_math_report and summarize in plain language.', 'When the user asks about progress, call vibe_math_report and summarize in plain language.\r\n - activityLogCap = 100']],
25
+ ['v4 declaration line (unread field)', ['vibe-math-v4', 'When the user asks about progress, call vibe_v4_report and summarize in plain language.', 'When the user asks about progress, call vibe_v4_report and summarize in plain language.\r\n - activityLogCap = 100']],
26
+ ]
27
+ let problems = 0
28
+ console.log('control (duplication + clauses, default mode): exit=' + control.code + ' | ' + JSON.stringify(control.parsed && { passed: control.parsed.passed, failed: control.parsed.failed })
29
+ + (control.parsed && control.parsed.report ? ' | ' + control.parsed.report.map((r) => r.preset + ':ratio=' + r.ratio).join(' ') : ''))
30
+ if (control.code !== 0) problems += 1
31
+ const NAMED = /share a substantial common prefix|present in BOTH copies|scaffolding, not prompt prose/
32
+ for (const [name, mut] of MUTS) {
33
+ const r = run({ PROMPT_DUP_MUTATE: JSON.stringify(mut), PROMPT_DUP_SKIP_CLAUSES: '1' })
34
+ const named = !!(r.parsed && (r.parsed.failures || []).some((f) => NAMED.test(f)))
35
+ const ok = r.code !== 0 && named
36
+ if (!ok) problems += 1
37
+ console.log(name.padEnd(24) + ': exit=' + r.code + (ok ? ' RED (as required)' : ' NOT RED'))
38
+ for (const f of ((r.parsed && r.parsed.failures) || []).slice(0, 1)) console.log(' → ' + String(f).slice(0, 150))
39
+ }
40
+ // declaration-syntax scanner probes run in DEFAULT mode (the scan lives in the clause block)
41
+ const DECL_NAMED = /must also be declared in copy 2|never reads params\.|declared in copy 2 only/
42
+ for (const [name, mut] of DECL_MUTS) {
43
+ const r = run({ PROMPT_DUP_MUTATE: JSON.stringify(mut) })
44
+ const named = !!(r.parsed && (r.parsed.failures || []).some((f) => DECL_NAMED.test(f)))
45
+ const ok = r.code !== 0 && named
46
+ if (!ok) problems += 1
47
+ console.log(name.padEnd(32) + ': exit=' + r.code + (ok ? ' RED (as required)' : ' NOT RED'))
48
+ for (const f of ((r.parsed && r.parsed.failures) || []).filter((f) => DECL_NAMED.test(f)).slice(0, 2)) console.log(' → ' + String(f).slice(0, 160))
49
+ }
50
+ console.log(problems === 0 ? '\nPROMPT-DUPLICATION (v2/v3/v4) MUTANTS: every single-copy edit turns the NAMED check red' : '\nPROMPT-DUPLICATION MUTANTS: ' + problems + ' problem(s)')
51
+ process.exit(problems === 0 ? 0 : 1)