dsh-vibe-math 2.7.0 → 2.7.2

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 (55) hide show
  1. package/README.en.md +681 -685
  2. package/README.md +9 -13
  3. package/cordis.patch.yml +12 -6
  4. package/docs/AUDIT-CHECKLIST.md +518 -518
  5. package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
  6. package/docs/final-paper.md +172 -172
  7. package/docs/formal-verification.md +588 -588
  8. package/docs/math-computation.md +187 -121
  9. package/docs/release-notes/README.en.md +48 -0
  10. package/docs/release-notes/README.md +47 -0
  11. package/docs/release-notes/RELEASE-NOTES-2.7.1.en.md +86 -0
  12. package/docs/release-notes/RELEASE-NOTES-2.7.1.md +86 -0
  13. package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
  14. package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
  15. package/docs/release-notes/TEMPLATE.md +120 -0
  16. package/docs/test-timing.md +154 -154
  17. package/package.json +208 -201
  18. package/prompt-corpus-persona/persona-corpus.json +8 -8
  19. package/prompt-corpus-persona/persona-corpus.md +24 -12
  20. package/prompt-corpus-v2/formal-verify-v2.json +176 -98
  21. package/prompt-corpus-v2/formal-verify-v2.md +1161 -96
  22. package/prompt-corpus-v3/formal-verify-v3.json +201 -63
  23. package/prompt-corpus-v3/formal-verify-v3.md +1401 -57
  24. package/prompt-corpus-v4/formal-verify-v4.json +10 -10
  25. package/prompt-corpus-v4/formal-verify-v4.md +40 -20
  26. package/prompt-corpus-v5/prompt-corpus-v5.json +81 -81
  27. package/prompt-corpus-v5/prompt-corpus-v5.md +199 -128
  28. package/tests/audit-math-computation-contract.mjs +56 -1
  29. package/tests/audit-math-computation-parity.mjs +30 -1
  30. package/tests/audit-math-computation-sensitivity.mjs +2 -2
  31. package/tests/audit-v5-sensitivity.mjs +3 -1
  32. package/tests/e2e-v5-round2.test.mjs +116 -0
  33. package/tests/formal-verify-v2.test.mjs +108 -2
  34. package/tests/formal-verify-v3.test.mjs +117 -2
  35. package/tests/formal-verify-v4.test.mjs +87 -0
  36. package/tests/helpers/math-computation-fake-seam.mjs +30 -2
  37. package/tests/math-computation-shared.test.mjs +430 -3
  38. package/vibe-math-v2/agent.cordis.yml +4 -2
  39. package/vibe-math-v2/math-computation.js +509 -55
  40. package/vibe-math-v2/math-engines.js +47 -6
  41. package/vibe-math-v2/vibe-math-v2.js +92 -12
  42. package/vibe-math-v3/agent.cordis.yml +4 -2
  43. package/vibe-math-v3/math-computation.js +509 -55
  44. package/vibe-math-v3/math-engines.js +47 -6
  45. package/vibe-math-v3/vibe-math-v3.js +77 -14
  46. package/vibe-math-v4/agent.cordis.yml +8 -4
  47. package/vibe-math-v4/math-computation.js +509 -55
  48. package/vibe-math-v4/math-engines.js +47 -6
  49. package/vibe-math-v4/vibe-math-v4.js +70 -15
  50. package/vibe-math-v5/agent.cordis.yml +8 -4
  51. package/vibe-math-v5/math-computation.js +509 -55
  52. package/vibe-math-v5/math-engines.js +47 -6
  53. package/vibe-math-v5/vibe-math-v5.js +152 -31
  54. package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1660 -1655
  55. package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
@@ -22,14 +22,47 @@ export const MATH_ENGINES = {
22
22
  candidates: ['python3', 'python', 'py'], winPrefix: ['-3'],
23
23
  versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
24
24
  scriptArgv: ['<script>'], evalArgv: ['-c', '<expr>'], stdinArgv: ['-'], ext: '.py',
25
- probeCode: "import importlib.util,sys;print('|'.join('%s:%s'%(p,'ok' if importlib.util.find_spec(p) else 'missing') for p in sys.argv[1:]))",
26
- packageProbe: { argv: ['-c', '<probeCode>', '<pkgs>'], parse: 'pairs' },
25
+ // round-7 (live): NO shell metacharacters (`|`, `%`) in any probe argv - a real host rejected
26
+ // them with a null spawn. One line per package: "name ok|missing".
27
+ probeCode: "import importlib.util,sys\nfor p in sys.argv[1:]: print(p, 'ok' if importlib.util.find_spec(p) is not None else 'missing')",
28
+ // round-7 (finding 6, found on the real bundled python): the probe code iterates `sys.argv[1:]`,
29
+ // so the names must be SEPARATE argv items - the old comma-joined `<pkgs>` made it look up one
30
+ // package literally named "numpy,pandas" and reported everything missing.
31
+ packageProbe: { argv: ['-c', '<probeCode>', '<pkgs...>'], parse: 'pairs' },
27
32
  license: 'free', argErrorHints: ARG_ERROR_HINTS,
28
33
  install: {
29
34
  manager: 'pip',
30
35
  userArgv: ['<exe>', '-m', 'pip', 'install', '--user', '<pkg>'],
31
36
  systemArgv: ['<exe>', '-m', 'pip', 'install', '<pkg>'],
32
37
  uninstallArgv: ['<exe>', '-m', 'pip', 'uninstall', '-y', '<pkg>'],
38
+ // Round-6 (B): DISPATCH by the detected interpreter environment. `pip` is the documented
39
+ // fallback; the module picks conda/mamba/uv only when the interpreter's OWN environment says so
40
+ // (detect, don't guess) and the plan states which manager it assumed.
41
+ managers: {
42
+ conda: {
43
+ detect: 'conda-markers',
44
+ userArgv: ['conda', 'install', '-y', '-c', 'conda-forge', '<pkg>'],
45
+ uninstallArgv: ['conda', 'remove', '-y', '<pkg>'],
46
+ systemUnsupportedReason: 'conda 环境是用户级的;系统级安装不在 conda 的概念里(本工具只出用户级计划)',
47
+ },
48
+ mamba: {
49
+ detect: 'conda-markers',
50
+ userArgv: ['mamba', 'install', '-y', '-c', 'conda-forge', '<pkg>'],
51
+ uninstallArgv: ['mamba', 'remove', '-y', '<pkg>'],
52
+ systemUnsupportedReason: 'mamba 环境是用户级的;系统级安装不在 mamba 的概念里(本工具只出用户级计划)',
53
+ },
54
+ uv: {
55
+ detect: 'uv-next-to-interpreter',
56
+ userArgv: ['uv', 'pip', 'install', '<pkg>'],
57
+ uninstallArgv: ['uv', 'pip', 'uninstall', '<pkg>'],
58
+ systemUnsupportedReason: 'uv 装进当前虚拟环境(用户级);系统级需要发行版包管理器,本工具只出用户级计划',
59
+ },
60
+ pip: {
61
+ detect: 'fallback',
62
+ systemArgv: ['<exe>', '-m', 'pip', 'install', '<pkg>'],
63
+ uninstallArgv: ['<exe>', '-m', 'pip', 'uninstall', '-y', '<pkg>'],
64
+ },
65
+ },
33
66
  },
34
67
  userInstall: { windows: 'winget install --id Python.Python.3.12', macos: 'brew install python', linux: 'apt install python3' },
35
68
  },
@@ -38,13 +71,16 @@ export const MATH_ENGINES = {
38
71
  candidates: ['Rscript', 'R'],
39
72
  versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
40
73
  scriptArgv: ['<script>'], evalArgv: ['-e', '<expr>'], stdinArgv: ['--no-save', '--slave'], ext: '.R',
41
- probeCode: "cat(paste(sapply(strsplit('__PKGS__',',')[[1]],function(p) paste0(p,':',requireNamespace(p,quietly=TRUE))),collapse='|'))",
74
+ // round-7 (live): line-per-package, no `|` in the argv.
75
+ probeCode: "for (p in strsplit('__PKGS__',',')[[1]]) cat(p, if (requireNamespace(p, quietly=TRUE)) 'ok' else 'missing', '\\n')",
42
76
  packageProbe: { argv: ['-e', '<probeCode>'], parse: 'pairs' },
43
77
  license: 'free', argErrorHints: ARG_ERROR_HINTS,
44
78
  install: {
45
79
  manager: 'r',
46
80
  userArgv: ['<exe>', '-e', "install.packages('__PKG__',lib=Sys.getenv('R_LIBS_USER'),repos='https://cloud.r-project.org')"],
47
81
  uninstallArgv: ['<exe>', '-e', "remove.packages('__PKG__',lib=Sys.getenv('R_LIBS_USER'))"],
82
+ // Round-6 (C): user-only BY DESIGN, and the refusal now says why per engine.
83
+ systemUnsupportedReason: 'R 的用户库是 R_LIBS_USER;系统级安装要写发行版的包目录(需 root 或发行版包管理器),本工具只出用户级计划',
48
84
  },
49
85
  userInstall: { windows: 'winget install --id RProject.R', macos: 'brew install r', linux: 'apt install r-base' },
50
86
  },
@@ -54,13 +90,15 @@ export const MATH_ENGINES = {
54
90
  versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+\\.\\d+)',
55
91
  scriptArgv: ['--no-gui', '--quiet', '<script>'], evalArgv: ['--no-gui', '--quiet', '--eval', '<expr>'],
56
92
  stdinArgv: ['--no-gui', '--quiet'], ext: '.m',
57
- probeCode: "s=pkg('list'); n={'__QPKGS__'}; for i=1:numel(n); printf('%s:%s|',n{i},merge(any(cellfun(@(x) strcmp(x.name,n{i}),s)),'ok','missing')); end",
93
+ // round-7 (live): line-per-package via disp, no `|` and no `%` in the argv.
94
+ probeCode: "s=pkg('list'); n={'__QPKGS__'}; for i=1:numel(n); if any(cellfun(@(x) strcmp(x.name,n{i}),s)); disp([n{i} ' ok']); else; disp([n{i} ' missing']); end; end",
58
95
  packageProbe: { argv: ['--no-gui', '--quiet', '--eval', '<probeCode>'], parse: 'pairs' },
59
96
  license: 'free', argErrorHints: ARG_ERROR_HINTS,
60
97
  install: {
61
98
  manager: 'octave-pkg',
62
99
  userArgv: ['<exe>', '--no-gui', '--quiet', '--eval', "pkg install -forge __PKG__"],
63
100
  uninstallArgv: ['<exe>', '--no-gui', '--quiet', '--eval', 'pkg uninstall __PKG__'],
101
+ systemUnsupportedReason: 'Octave 的 pkg install 装进用户包目录;系统级要写 Octave 的 share/packages(需 root),本工具只出用户级计划',
64
102
  },
65
103
  userInstall: { windows: 'winget install --id GNU.Octave', macos: 'brew install octave', linux: 'apt install octave' },
66
104
  },
@@ -76,6 +114,7 @@ export const MATH_ENGINES = {
76
114
  manager: 'julia-pkg',
77
115
  userArgv: ['<exe>', '-e', 'using Pkg; Pkg.add("__PKG__")'],
78
116
  uninstallArgv: ['<exe>', '-e', 'using Pkg; Pkg.rm("__PKG__")'],
117
+ systemUnsupportedReason: 'Julia 的 Pkg.add 装进当前活动环境 / 用户 depot(JULIA_DEPOT_PATH);系统级不是 Julia 的概念,本工具只出用户级计划',
79
118
  },
80
119
  userInstall: { windows: 'winget install --id Julialang.Julia', macos: 'brew install julia', linux: 'apt install julia' },
81
120
  },
@@ -84,7 +123,8 @@ export const MATH_ENGINES = {
84
123
  candidates: ['matlab'],
85
124
  versionArgv: ['-batch', 'disp(version)'], versionRe: '(\\d+\\.\\d+)',
86
125
  scriptArgv: ['-batch', "run('<script>')"], evalArgv: ['-batch', '<expr>'], stdinArgv: null, ext: '.m',
87
- probeCode: "t={'__QPKGS__'}; for i=1:numel(t); printf('%s:%d|',t{i},license('test',t{i})); end",
126
+ // round-7 (live): no `|`/`%` in any probe argv (a real host rejected such argv with a null spawn).
127
+ probeCode: "t={'__QPKGS__'}; for i=1:numel(t); if license('test',t{i}); disp([t{i} ' ok']); else; disp([t{i} ' missing']); end; end",
88
128
  packageProbe: { argv: ['-batch', '<probeCode>'], parse: 'pairs' },
89
129
  license: 'commercial', argErrorHints: ARG_ERROR_HINTS,
90
130
  licenseProbe: { argv: ['-batch', "disp(license('test','MATLAB'))"], okWhen: 'trim-1' },
@@ -96,7 +136,8 @@ export const MATH_ENGINES = {
96
136
  candidates: ['maple'],
97
137
  versionArgv: ['--version'], versionRe: '(\\d+\\.\\d+)',
98
138
  scriptArgv: ['-q', '<script>'], evalArgv: ['-q', '-c', '<expr>'], stdinArgv: null, ext: '.mpl',
99
- probeCode: 'for p in ["__PKGS__"] do try with(p) catch: printf("%s:missing|", p); next end try; printf("%s:ok|", p) end do',
139
+ // round-7 (live): `print` instead of printf so the argv carries no `|`/`%`.
140
+ probeCode: 'for p in ["__PKGS__"] do try with(p) catch: print(p, " missing"); next end try; print(p, " ok") end do',
100
141
  packageProbe: { argv: ['-q', '-c', '<probeCode>'], parse: 'pairs' },
101
142
  license: 'commercial', argErrorHints: ARG_ERROR_HINTS,
102
143
  licenseProbe: { argv: ['-q', '-c', 'printf("1")'], okWhen: 'exit-0' },
@@ -32,6 +32,8 @@ import {
32
32
  MATH_RULE_LINES_EN,
33
33
  MATH_ARCHIVE_WORKFLOW_LINE,
34
34
  MATH_ARCHIVE_WORKFLOW_LINE_EN,
35
+ MATH_SUBSTITUTION_RULE_LINE,
36
+ MATH_SUBSTITUTION_RULE_LINE_EN,
35
37
  MATH_PARAM_DEFAULTS,
36
38
  MATH_PARAM_NAMES,
37
39
  normalizeMathParams,
@@ -194,6 +196,11 @@ export function apply(ctx) {
194
196
  // Same capability flag as the registered host: the prompt-side probe should not advertise
195
197
  // engines on a host that cannot execute at all.
196
198
  hasSubprocess: ()=>!!subprocessOf(),
199
+ // round-7 (fix 2): DSH's own bundled runtimes (`<home>/.dsh/dsh-runtimes/*/dependencies/<engine>/`)
200
+ // as a LAST resort after PATH. Generic roots only; the module globs the tree name (never
201
+ // `dsh-primary-runtime`) and accepts only the descriptor's own candidate names.
202
+ runtimeRoots: () => { const env = process.env.DSH_HOME; if (env) return [String(env)]; const home = String(process.env.HOME || process.env.USERPROFILE || ''); return home ? [home.replace(/[\\/]+$/, '') + '/.dsh'] : [] },
203
+ listDirAbs: async (abs)=>{ try { const t=await fsTarget(abs); const st=await fs.stat(t); if(!st) return []; return (await fs.listDir(t))||[] } catch(e){ return [] } },
197
204
  log: (kind,msg)=>logActivity(String(kind||'math'),String(msg||'')),
198
205
  refreshAvailability: ()=>refreshMathAvailability(),
199
206
  }
@@ -248,6 +255,11 @@ export function apply(ctx) {
248
255
  }
249
256
  let running = false, autoDone = false, phase = 'idle'
250
257
  let residents = new Map(), mailboxes = new Map(), taskboard = [], decisions = []
258
+ // round-9 (P2/D4): the participant set is frozen per verify/meeting together with this version, so
259
+ // every view reads the SAME roster instead of re-deriving it from the live map (mid-flight
260
+ // addMember/removeMember is possible: removeMember reconciles in-flight work, addMember has no
261
+ // verify/meeting gate).
262
+ let rosterVersion = 0
251
263
  let meetings = [], reports = [], activityLog = []
252
264
  let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
253
265
  // Lean formalization records, keyed by object id. v4 has no session-log projection, so this
@@ -1095,6 +1107,11 @@ export function apply(ctx) {
1095
1107
  // both persona blocks by this preset — this is the drift check for the former.
1096
1108
  if(mathAvailZh.indexOf(MATH_ARCHIVE_WORKFLOW_LINE.trim())===-1||mathAvailEn.indexOf(MATH_ARCHIVE_WORKFLOW_LINE_EN.trim())===-1){
1097
1109
  logActivity('math','警告:mathAvailabilityLine 未附带归档工作流规则(MATH_ARCHIVE_WORKFLOW_LINE/_EN)')
1110
+ // round-6 A: substitution honesty is part of the same contract (an alternative that weakens
1111
+ // exactness must be declared). It is appended to the availability line by the module.
1112
+ if(mathAvailZh.indexOf(MATH_SUBSTITUTION_RULE_LINE.trim())===-1||mathAvailEn.indexOf(MATH_SUBSTITUTION_RULE_LINE_EN.trim())===-1){
1113
+ logActivity('math','警告:mathAvailabilityLine 未附带替代诚实性规则(MATH_SUBSTITUTION_RULE_LINE/_EN)')
1114
+ }
1098
1115
  }
1099
1116
  } catch(e){ /* a probe failure must never break a prompt; keep the last known line */ }
1100
1117
  finally { mathAvailBusy=false }
@@ -1545,6 +1562,7 @@ export function apply(ctx) {
1545
1562
  +' 然后 ## 陈述 <陈述>;## 证明尝试;## 证伪尝试。\n'
1546
1563
  +' Methods/<你>/<id>.md —— 你的理论/方法/工具。格式:- ID: m-<id>; - 状态: 经验; - 可信断言: []; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 核心内容;## 定义与记号;## 应用记录;## 改进历史。\n'
1547
1564
  +' Subproblems/<你>/<id>.md —— 你的子问题。格式:- ID: s-<id>; - 状态: 求解中; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 陈述;## 进度。\n'
1565
+ +' ⚠ 你的文件工具(读/写)按**会话 cwd** 解析路径:上面列出的相对子路径都要先拼上**绝对前缀** '+base+'/ 再使用(例如 '+base+'/Propos/<你>/p-1.md);计算产物同理——请用回执里的**绝对**字段 receipt.scriptAbs,或把 receipt.cwd 与 receipt.scriptPath 拼起来,不要用相对路径。\n'
1548
1566
  }
1549
1567
  function toolList(){
1550
1568
  return 'vibe_v4_send_message {to, content} —— 给某常驻发消息(to=all 广播)。\n'
@@ -1886,10 +1904,23 @@ export function apply(ctx) {
1886
1904
  function byChild(childId){ for(const [,r] of residents){ if(r.childId===childId) return r } return undefined }
1887
1905
 
1888
1906
  // ---- artifact writers (resident-facing) ----
1889
- async function publishProgress(rId,content){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const rel='Progress/'+rId+'/progress.md'; const prev=(await readText(rel))||''; await writeText(rel, prev+'\n### '+fmtTime()+'|'+rId+'\n'+String(content||'')+'\n'); return {ok:true} }
1890
- async function recordProposition(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id?idSafe(o.id):('p-'+shortId()); const lines=['# 命题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: 命题','- 状态: 未定论','- 概率: '+cl(o.prob!=null?o.prob:0.5),'- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 证明尝试','','## 证伪尝试','']; await writeText('Propos/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 命题 '+id); bumpArtifacts(); return {ok:true,id,file:'Propos/'+rId+'/'+id+'.md'} }
1891
- async function recordMethod(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id?idSafe(o.id):('m-'+shortId()); const lines=['# 方法|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: '+(o.type||'方法'),'- 状态: 经验','- 可信断言: []','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'','## 核心内容',String(o.content||''),'','## 定义与记号',String(o.notation||''),'','## 应用记录','## 改进历史','']; await writeText('Methods/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 方法 '+id); bumpArtifacts(); return {ok:true,id,file:'Methods/'+rId+'/'+id+'.md'} }
1892
- async function recordSubproblem(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id?idSafe(o.id):('s-'+shortId()); const lines=['# 子问题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 状态: 求解中','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 进度','']; await writeText('Subproblems/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 子问题 '+id); bumpArtifacts(); return {ok:true,id,file:'Subproblems/'+rId+'/'+id+'.md'} }
1907
+ /** Cross-preset audit D1: a member-facing tool must never answer with a bare `no such resident`.
1908
+ * "not started yet" / "wrong id" / "already dismissed" are DIFFERENT problems, so the answer
1909
+ * carries a code, the LIVE roster state and an actionable next step (v5's `memberDiagnosis`). */
1910
+ function noSuchResident(id, tool){
1911
+ const ids=[...residents.keys()]
1912
+ const active=[...residents.values()].filter(r=>r&&r.childId).length
1913
+ const nextTool=tool||(running?'vibe_v4_list_members':'vibe_v4_start')
1914
+ return {ok:false,code:'V4_NO_SUCH_RESIDENT',
1915
+ message:'no such resident: '+String(id||'(none)')+(running?('(当前在册 '+residents.size+' 名,active '+active+' 名)'):'(本次运行尚未 start)'),
1916
+ status:{running:!!running,autoDone:!!autoDone,residentCount:residents.size,activeCount:active,ids:ids},
1917
+ next:{tool:nextTool,
1918
+ hint:running?'核对有效 id(新建成员可能仍在 spawn 中);列表用 vibe_v4_list_members':'先 vibe_v4_start 或 vibe_v4_add_member,再调用 '+nextTool}}
1919
+ }
1920
+ async function publishProgress(rId,content){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const rel='Progress/'+rId+'/progress.md'; const prev=(await readText(rel))||''; await writeText(rel, prev+'\n### '+fmtTime()+'|'+rId+'\n'+String(content||'')+'\n'); return {ok:true} }
1921
+ async function recordProposition(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('p-'+shortId()); const lines=['# 命题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: 命题','- 状态: 未定论','- 概率: '+cl(o.prob!=null?o.prob:0.5),'- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 证明尝试','','## 证伪尝试','']; await writeText('Propos/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 命题 '+id); bumpArtifacts(); return {ok:true,id,file:'Propos/'+rId+'/'+id+'.md'} }
1922
+ async function recordMethod(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('m-'+shortId()); const lines=['# 方法|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: '+(o.type||'方法'),'- 状态: 经验','- 可信断言: []','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'','## 核心内容',String(o.content||''),'','## 定义与记号',String(o.notation||''),'','## 应用记录','## 改进历史','']; await writeText('Methods/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 方法 '+id); bumpArtifacts(); return {ok:true,id,file:'Methods/'+rId+'/'+id+'.md'} }
1923
+ async function recordSubproblem(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('s-'+shortId()); const lines=['# 子问题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 状态: 求解中','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 进度','']; await writeText('Subproblems/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 子问题 '+id); bumpArtifacts(); return {ok:true,id,file:'Subproblems/'+rId+'/'+id+'.md'} }
1893
1924
  // ── auto-sync meeting: every `meetingKeepEvery` NEW artifacts, convene a coordination meeting ──
1894
1925
  //
1895
1926
  // The counting basis is the RESIDENTS' card libraries themselves (Propos/Methods/Subproblems),
@@ -2003,7 +2034,7 @@ export function apply(ctx) {
2003
2034
 
2004
2035
  // ---- messaging ----
2005
2036
  async function postMessage(from,to,content){
2006
- const r=residents.get(to); if(!r) return {ok:false,message:'no such resident'}
2037
+ const r=residents.get(to); if(!r) return noSuchResident(to,'vibe_v4_send_message')
2007
2038
  if(!busy.has(to)){
2008
2039
  currentResident=r.rId
2009
2040
  const ok=await wakeResident(r, (await normalPrompt(r))+'\n\n[NEW MESSAGE from '+from+']\n'+content,'normal')
@@ -2063,7 +2094,7 @@ export function apply(ctx) {
2063
2094
  // who sees no one else's contribution"; a real discussion lets each member lead sometimes.
2064
2095
  const rot=Math.floor(Math.random()*Math.max(1,ids.length))
2065
2096
  const order=ids.slice(rot).concat(ids.slice(0,rot))
2066
- meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order,at:now(),lastInputAt:now()}
2097
+ meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order,at:now(),lastInputAt:now(),rosterSnapshot:order.slice(),rosterVersion}
2067
2098
  markProgress();
2068
2099
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
2069
2100
  }
@@ -2078,7 +2109,7 @@ export function apply(ctx) {
2078
2109
  meetingState=null; wakeKind.clear(); logActivity('meeting','abandoned (stuck: no resident spoke)')
2079
2110
  await saveAll(); await scheduleNext(); return
2080
2111
  }
2081
- const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
2112
+ const ids=(st.rosterSnapshot||Array.from(residents.keys())).slice(); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
2082
2113
  if(allSpoke){ await finalizeMeeting(); return }
2083
2114
  // only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
2084
2115
  // NOTE: we deliberately do NOT flush mailboxes here — drafting an un-spoken resident into a normal
@@ -2097,7 +2128,7 @@ export function apply(ctx) {
2097
2128
  let doSchedule=false
2098
2129
  try {
2099
2130
  const st=meetingState
2100
- const ids=Array.from(residents.keys()); const allSpoke=ids.length>0 && ids.every(id=>st.inputs[id]!==undefined)
2131
+ const ids=(st.rosterSnapshot||Array.from(residents.keys())).slice(); const allSpoke=ids.length>0 && ids.every(id=>st.inputs[id]!==undefined)
2101
2132
  const lines=['# 会议 '+st.id+'|'+fmtTime(),'','**议程**:'+st.agenda,'']
2102
2133
  for(const [id,iv] of Object.entries(st.inputs)){ lines.push('### '+id); lines.push(iv.input||''); lines.push('') }
2103
2134
  await writeText('Shared/meetings/'+st.id+'.md', lines.join('\n'))
@@ -2166,7 +2197,7 @@ export function apply(ctx) {
2166
2197
  await saveAll(); await scheduleNext(); return
2167
2198
  }
2168
2199
  }
2169
- verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now()}
2200
+ verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now(),rosterSnapshot:Array.from(residents.keys()),rosterVersion}
2170
2201
  markProgress();
2171
2202
  logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
2172
2203
  }
@@ -2181,7 +2212,7 @@ export function apply(ctx) {
2181
2212
  verifyState=null; wakeKind.clear(); logActivity('verify',vs.targetId+' abandoned (stuck: no unanimous verdict reachable)')
2182
2213
  await saveAll(); await scheduleNext(); return
2183
2214
  }
2184
- const ids=Array.from(residents.keys()); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
2215
+ const ids=(vs.rosterSnapshot||Array.from(residents.keys())).slice(); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
2185
2216
  if(allVoted){ await finalizeVerify(); return }
2186
2217
  // NOTE: we deliberately do NOT flush mailboxes here — drafting an un-voted resident into a normal
2187
2218
  // mail round would delay its verdict and can starve the verify past its watchdog when the backlog
@@ -3345,6 +3376,10 @@ export function apply(ctx) {
3345
3376
  return {
3346
3377
  params:{finalPaper:params.finalPaper,paperFormat:params.paperFormat,paperLanguage:params.paperLanguage,paperCompilePdf:params.paperCompilePdf,paperEditor:params.paperEditor,paperLatexCommand:params.paperLatexCommand},
3347
3378
  id:ps?ps.id:paperIdFor(), dir:ps?ps.dir:paperDirFor(), status:ps?ps.status:'idle',
3379
+ // round-9 (F2): with no paper state this path is a PROJECTION (f2: paperIdFor/paperDirFor are
3380
+ // pure - reading `status` touches no fs and creates no directory). Say so explicitly instead of
3381
+ // handing callers a path that looks like it exists.
3382
+ dirProjected:!ps, dirSource:ps?'paper-state':'projection', readSideEffect:false,
3348
3383
  running:!!(ps&&ps.status!=='done'&&ps.status!=='failed'), trigger:ps?ps.trigger:'',
3349
3384
  rounds:ps?ps.round:0, maxRounds:ps?ps.maxRounds:PAPER_MAX_ROUNDS, participants:ps?ps.participants:[],
3350
3385
  editor:ps?(ps.editorId||ps.cfg.editor):params.paperEditor, editorDowngraded:!!(ps&&ps.editorDowngraded),
@@ -3356,6 +3391,8 @@ export function apply(ctx) {
3356
3391
  function paperStatusSummary(){
3357
3392
  const v=paperStatusView()
3358
3393
  return {ok:true, id:v.id, dir:v.dir, status:v.status, running:v.running, trigger:v.trigger, lang:v.params.paperLanguage,
3394
+ // round-9 (F2): carry the projection flags through the SUMMARY too (this is what `status` returns).
3395
+ dirProjected:v.dirProjected, dirSource:v.dirSource, readSideEffect:v.readSideEffect,
3359
3396
  format:v.params.paperFormat, editor:v.editor, editorDowngraded:v.editorDowngraded, participants:v.participants,
3360
3397
  parts:v.parts, reviews:v.reviews, rounds:v.rounds+'/'+v.maxRounds, finalizedAt:v.finalizedAt, files:v.files,
3361
3398
  params:v.params,
@@ -3554,6 +3591,10 @@ export function apply(ctx) {
3554
3591
  logActivity('resume','restarted'+(crossProcess?' (cross-process: re-spawned)':needRespawn?' (re-spawned)':'')); await saveAll(); await scheduleNext(); return {ok:true,message:'resumed',project:currentProject}
3555
3592
  }
3556
3593
  function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
3594
+ // round-9 (P2): the roster VERSION travels with every status read, and an active verify/meeting
3595
+ // reports the participant set it froze (never a re-derivation from the live map).
3596
+ rosterVersion,
3597
+ frozenParticipants: verifyState&&Array.isArray(verifyState.rosterSnapshot)?verifyState.rosterSnapshot:(meetingState&&Array.isArray(meetingState.rosterSnapshot)?meetingState.rosterSnapshot:null),
3557
3598
  residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
3558
3599
  // Residents the host's live-child cap refused (maxActiveSubagents): queued, not lost. Without
3559
3600
  // this the only trace would be the one console line, and `residentCount` alone cannot tell a
@@ -3564,8 +3605,8 @@ export function apply(ctx) {
3564
3605
  // The coordination counters (`spoke k/N`, `voted k/N`, the verification round) used to live only
3565
3606
  // in `report()`: from `status()` alone an operator could not tell "the group is progressing"
3566
3607
  // from "the group is stuck at 2/4 votes", which is exactly how F1/F2/F7 stay invisible.
3567
- consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,spoke:Object.keys(meetingState.inputs).length,expected:residents.size,solvedVotes:Object.values(meetingState.inputs).filter(iv=>iv.voteSolved===true).length}
3568
- :(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,voted:Object.keys(verifyState.verdicts).length,expected:residents.size}:null),
3608
+ consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,spoke:Object.keys(meetingState.inputs).length,expected:(meetingState.rosterSnapshot||Array.from(residents.keys())).length,solvedVotes:Object.values(meetingState.inputs).filter(iv=>iv.voteSolved===true).length,rosterVersion:meetingState.rosterVersion}
3609
+ :(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,voted:Object.keys(verifyState.verdicts).length,expected:(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null),
3569
3610
  artifactCount: artifactCount, artifactBaseline: artifactBaseline,
3570
3611
  paper: paperStatusSummary(),
3571
3612
  // The Lean knobs and the per-object formal records are part of the readable status: without
@@ -3586,8 +3627,8 @@ export function apply(ctx) {
3586
3627
  }
3587
3628
  function report(){ return { ok:true, running, phase, autoDone, project:currentProject, problem:problemText,
3588
3629
  residents:listResidents(), taskboard:taskboard.filter(t=>t.status!=='done'),
3589
- meeting: meetingState?{id:meetingState.id, agenda:meetingState.agenda, spoke:Object.keys(meetingState.inputs).length+'/'+residents.size}:null,
3590
- verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+residents.size}:null,
3630
+ meeting: meetingState?{id:meetingState.id, agenda:meetingState.agenda, spoke:Object.keys(meetingState.inputs).length+'/'+(meetingState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:meetingState.rosterVersion}:null,
3631
+ verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null,
3591
3632
  pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null,
3592
3633
  parkedMeeting: pendingMeeting?pendingMeeting.agenda:null,
3593
3634
  formal: formalView(),
@@ -3607,10 +3648,15 @@ export function apply(ctx) {
3607
3648
  // residents) can never be true for the new member (not in the snapshot order) and the meeting is
3608
3649
  // only ever released by the stuck watchdog instead of finalizing with everyone's input.
3609
3650
  if(meetingState){ if(!Array.isArray(meetingState.order)) meetingState.order=Array.from(residents.keys()); if(!meetingState.order.includes(r.rId)) meetingState.order.push(r.rId) }
3651
+ // P2: the roster CHANGED - bump the version so any frozen participant set is visibly stale.
3652
+ rosterVersion++
3610
3653
  // Mid-verify additions are automatically asked to vote (continueVerifyRound recomputes ids from
3611
3654
  // the live residents map), so no extra handling is needed there.
3612
3655
  return {ok:true,id:r.rId,direction:r.direction} }
3613
- async function removeMember(id){ const r=residents.get(id); if(!r) return {ok:false}; if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); wakeKind.delete(id); if(currentResident===id) currentResident=''
3656
+ async function removeMember(id){ const r=residents.get(id); if(!r) return noSuchResident(id,'vibe_v4_remove_member'); if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); wakeKind.delete(id); if(currentResident===id) currentResident=''
3657
+ // P2: the roster CHANGED - bump the version (a frozen participant set stays readable, but is
3658
+ // now visibly stale; every view reports the same version it snapshotted).
3659
+ rosterVersion++
3614
3660
  // Reconcile in-progress coordination so a removed member cannot hang consensus or crash a round:
3615
3661
  // drop its meeting speech / verify verdict and prune it from the meeting's speaking order so the
3616
3662
  // find() there never selects a ghost. Its QUEUED verify proposals are deliberately KEPT: a
@@ -3620,6 +3666,12 @@ export function apply(ctx) {
3620
3666
  // only the first entry, which may belong to the removed member).
3621
3667
  if(meetingState){ delete meetingState.inputs[id]; meetingState.order=(meetingState.order||[]).filter(x=>x!==id) }
3622
3668
  if(verifyState){ delete verifyState.verdicts[id] }
3669
+ // P2 (round-9): REMOVAL prunes the FROZEN participant set as well - v4's long-standing contract is
3670
+ // that removing an in-flight voter RELEASES the wait (T26/T31), while the snapshot still gives
3671
+ // every view one identical set + version. Additions never touch a frozen set (that is the point
3672
+ // of freezing); the version bump above marks the set as changed.
3673
+ if(meetingState&&Array.isArray(meetingState.rosterSnapshot)) meetingState.rosterSnapshot=meetingState.rosterSnapshot.filter(x=>x!==id)
3674
+ if(verifyState&&Array.isArray(verifyState.rosterSnapshot)) verifyState.rosterSnapshot=verifyState.rosterSnapshot.filter(x=>x!==id)
3623
3675
  await saveAll()
3624
3676
  // Re-drive the scheduler right away. If the removed member was the ONLY turn in flight (e.g. the
3625
3677
  // last unspoken meeting speaker / the last unvoted voter, interrupted mid-turn), NO subagent/end
@@ -3981,6 +4033,9 @@ export function apply(ctx) {
3981
4033
  // audit-R2 lens-2: attached ONLY when the session surface really has it, so a surface without
3982
4034
  // listDir reaches the module as an ABSENT field (the module's optional-callback contract).
3983
4035
  ...(typeof hostObj.listDir === 'function' ? { listDir: (rel)=>hostObj.listDir(rel) } : {}),
4036
+ // round-7 (fix 2): pass the bundled-runtime seam through to the per-session module instance.
4037
+ ...(typeof hostObj.runtimeRoots === 'function' ? { runtimeRoots: ()=>hostObj.runtimeRoots() } : {}),
4038
+ ...(typeof hostObj.listDirAbs === 'function' ? { listDirAbs: (abs)=>hostObj.listDirAbs(abs) } : {}),
3984
4039
  resolveExecutable: (cmd)=>hostObj.resolveExecutable(cmd),
3985
4040
  spawn: (o)=>hostObj.spawn(o),
3986
4041
  // Optional capability flag: a host that KNOWS it has no subprocess service reports
@@ -102,8 +102,10 @@
102
102
  verification (execute / archive / list the reuse library / read an archived file
103
103
  verbatim / watch the background compile queue). Members use them too; they work in every mode.
104
104
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
105
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>),你可以用普通文件工具打开并编辑它;编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
106
- - Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext>) and you may open/edit it with your normal file tools; after editing, re-run it with mode:'file' to write a NEW receipt/attempt with a NEW scriptHash. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.
105
+ - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
106
+ - Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so open it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt); after editing, re-run it with mode:'file' to write a NEW receipt/attempt with a NEW scriptHash. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.
107
+ - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
108
+ - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
107
109
  A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
108
110
 
109
111
  LEAN FORMAL VERIFICATION (formalVerify, a tunable parameter):
@@ -216,8 +218,10 @@
216
218
  verification (execute / archive / list the reuse library / read an archived file
217
219
  verbatim / watch the background compile queue). Members use them too; they work in every mode.
218
220
  - math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
219
- - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>),你可以用普通文件工具打开并编辑它;编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
220
- - Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext>) and you may open/edit it with your normal file tools; after editing, re-run it with mode:'file' to write a NEW receipt/attempt with a NEW scriptHash. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.
221
+ - 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
222
+ - Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so open it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt); after editing, re-run it with mode:'file' to write a NEW receipt/attempt with a NEW scriptHash. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.
223
+ - 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
224
+ - Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
221
225
  A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
222
226
 
223
227
  LEAN FORMAL VERIFICATION (formalVerify, a tunable parameter):