dsh-vibe-math 2.8.4 → 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 +8 -0
- package/docs/formal-verification.md +6 -6
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -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 +5 -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 +214 -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 +107 -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 +378 -33
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +55 -2
|
@@ -56,6 +56,40 @@
|
|
|
56
56
|
// guessed; otherwise the host resolver (PATH) is tried first, then the known install locations for this
|
|
57
57
|
// platform. Provenance is returned so the caller can report \`leanFoundVia: 'explicit'|'path'|'known-install'\`,
|
|
58
58
|
// and the probed list travels on failure ("I looked here too"), never just "not found on PATH".
|
|
59
|
+
// ── task-28: TeX Live's DOCUMENTED typical install positions + a bounded probe budget ────────────────
|
|
60
|
+
// A real-host SLV v5 run (15.2 min, candidate PASS) archived compileStatus `not-detected` on a machine
|
|
61
|
+
// whose TeX Live lives at `D:\texlive\2025\bin\windows\xelatex.exe`: the known list only covered
|
|
62
|
+
// `<ProgramFiles>/texlive`, so an engine that IS installed but NOT on PATH was never found and the run
|
|
63
|
+
// degraded to tex+md only. These are DOCUMENTED roots, never a search: no whole-drive scan, no install,
|
|
64
|
+
// no write outside the workspace (the three hard boundaries stay in the guidance text).
|
|
65
|
+
const TEX_CANDIDATE_CAP = 40 // hard cap on TeX known-root candidate paths probed per detection
|
|
66
|
+
const TEX_YEAR_LOOKBACK = 4 // the newest N year dirs of a TeX Live root (current year + 3)
|
|
67
|
+
const TEX_DRIVE_YEAR_ROOTS = ['D:/texlive', 'C:/texlive'] // Windows: TeX Live at a DRIVE ROOT
|
|
68
|
+
const TEX_SYSTEM_BIN_ROOTS = ['/Library/TeX/texbin'] // macOS: system MacTeX (already a bin dir)
|
|
69
|
+
const TEX_SYSTEM_YEAR_ROOTS = ['/usr/local/texlive', '/opt/texlive'] // Unix: TeX Live (year-globbed)
|
|
70
|
+
// ── feedback library (Shared/Feedback/) ──────────────────────────────────────────────────────────────
|
|
71
|
+
// The METHODOLOGY / COLLABORATION layer: how the institute WORKS (organisation, workflow, cooperation,
|
|
72
|
+
// obstacles, friction). Deliberately separate from Progress/ (research progress) and Verified/
|
|
73
|
+
// (established results) — an entry here is never evidence for a mathematical claim.
|
|
74
|
+
const FEEDBACK_CATEGORIES = ['cooperation', 'management', 'process', 'obstacle', 'conflict']
|
|
75
|
+
const FEEDBACK_CATEGORY_LABELS = { cooperation: '合作', management: '管理', process: '流程', obstacle: '障碍', conflict: '矛盾' }
|
|
76
|
+
// The three ROUTES decide what "handling" an entry means — and, crucially, WHO has to do anything:
|
|
77
|
+
// self — my own way of working / habits / pitfalls ⇒ I adjust it MYSELF; nobody's approval,
|
|
78
|
+
// at most a share so colleagues do not hit the same wall.
|
|
79
|
+
// team — how the institute is organised/run (task assignment, workflow, organisation model)
|
|
80
|
+
// ⇒ also self-regulation: no approval, change it as soon as it looks wrong.
|
|
81
|
+
// interpersonal — the problem is caused by SOMEONE ELSE and only they can fix it ⇒ the ONLY route that
|
|
82
|
+
// needs a careful assessment first ("should anything change, and would the change
|
|
83
|
+
// really be better?") and a written-back verification afterwards.
|
|
84
|
+
const FEEDBACK_ROUTES = ['self', 'team', 'interpersonal']
|
|
85
|
+
// `open` = recorded, not yet handled; `adjusted` = the initiator adjusted their own way of working
|
|
86
|
+
// (for self/team this IS the closing record — no approval step exists); `closed` = a result was written
|
|
87
|
+
// back (interpersonal REQUIRES that write-back); `dropped` = evaluated and deliberately NOT changed
|
|
88
|
+
// (needs a reason). list's default "not yet closed" filter = open|adjusted.
|
|
89
|
+
const FEEDBACK_STATUSES = ['open', 'adjusted', 'closed', 'dropped']
|
|
90
|
+
const FEEDBACK_OPEN_STATUSES = ['open', 'adjusted']
|
|
91
|
+
const FEEDBACK_DIR = 'Shared/Feedback'
|
|
92
|
+
const FEEDBACK_DISABLED_MESSAGE = '反馈库已关闭(params.feedback = \'off\'):本条未写入、也未改动任何条目'
|
|
59
93
|
async function resolveKnownTool(sub, opts) {
|
|
60
94
|
const name = String((opts && opts.name) || '')
|
|
61
95
|
const explicit = String((opts && opts.explicit) || '').trim()
|
|
@@ -75,12 +109,27 @@ async function resolveKnownTool(sub, opts) {
|
|
|
75
109
|
return null
|
|
76
110
|
}
|
|
77
111
|
const listDirs = async (p) => { try { const fs = await import('node:fs'); return fs.readdirSync(p, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name) } catch (err) { return [] } }
|
|
112
|
+
// task-29: TEST/DIAGNOSTIC sandbox seam (env-gated; UNSET = identity, i.e. exactly today's behaviour).
|
|
113
|
+
// When `V5_TEX_ROOTS_SANDBOX` is set, every known-install TeX candidate is REBASED under that directory
|
|
114
|
+
// (same relative tail) so a suite can pin its "no LaTeX installed here" premise on ANY host: the
|
|
115
|
+
// candidates are still probed ONE BY ONE (triedPaths stays named, TEX_CANDIDATE_CAP still bounds them,
|
|
116
|
+
// still no whole-drive scan) but a real install is never touched. It is not a product switch: it only
|
|
117
|
+
// relocates where the documented roots are looked for, and it is never set by the plugin itself.
|
|
118
|
+
const texSandbox = kind === 'tex' ? String(env.V5_TEX_ROOTS_SANDBOX || '').trim() : ''
|
|
119
|
+
const texProbeBase = (p) => {
|
|
120
|
+
if (!texSandbox) return p
|
|
121
|
+
const tail = String(p).replace(/\\/g, '/').replace(/^[A-Za-z]:/, '').replace(/^\/+/, '')
|
|
122
|
+
return texSandbox.replace(/\\/g, '/').replace(/\/+$/, '') + '/' + tail
|
|
123
|
+
}
|
|
78
124
|
const knownLean = win
|
|
79
125
|
? [elan ? elan + '/bin' : null, home + '/.elan/bin', local ? local + '/Programs/lean' : null, pf + '/lean/bin']
|
|
80
126
|
: [elan ? elan + '/bin' : null, home + '/.elan/bin', home + '/.local/bin', '/usr/local/bin', '/opt/lean/bin']
|
|
127
|
+
// task-28: the DRIVE-ROOT TeX Live positions come first on Windows (the observed real-host layout);
|
|
128
|
+
// the legacy entries keep their relative order. Unix keeps the legacy order and adds the system
|
|
129
|
+
// MacTeX bin dir last (a documented root, not a guess).
|
|
81
130
|
const knownTex = win
|
|
82
|
-
? [pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64']
|
|
83
|
-
: [home + '/Library/TeX/texbin',
|
|
131
|
+
? TEX_DRIVE_YEAR_ROOTS.concat([pf + '/texlive', pf86 + '/texlive', local ? local + '/Programs/MiKTeX/miktex/bin/x64' : null, pf + '/MiKTeX/miktex/bin/x64'])
|
|
132
|
+
: [home + '/Library/TeX/texbin'].concat(TEX_SYSTEM_YEAR_ROOTS, [home + '/.TinyTeX/bin'], TEX_SYSTEM_BIN_ROOTS)
|
|
84
133
|
const list = kind === 'tex' ? knownTex : knownLean
|
|
85
134
|
// 1) EXPLICIT: only it, never a guess.
|
|
86
135
|
if (explicit) {
|
|
@@ -93,19 +142,35 @@ async function resolveKnownTool(sub, opts) {
|
|
|
93
142
|
if (typeof sub.resolveExecutable === 'function') {
|
|
94
143
|
try { const p = await sub.resolveExecutable(name); if (p) return { exe: String(p), via: 'path', tried, name } } catch (e) { /* PATH miss is expected */ }
|
|
95
144
|
}
|
|
96
|
-
// 3) KNOWN INSTALL LOCATIONS
|
|
145
|
+
// 3) KNOWN INSTALL LOCATIONS. A TeX root WITHOUT a `bin` segment is year-globbed
|
|
146
|
+
// (`<root>/<year>/bin/<platform>`): the years actually on disk (newest first) first, then the
|
|
147
|
+
// recent-year window, at most TEX_YEAR_LOOKBACK of them. task-28: the whole stage is BOUNDED by
|
|
148
|
+
// TEX_CANDIDATE_CAP candidate paths — documented roots, never a whole-drive scan.
|
|
149
|
+
const texYearSubdirs = win ? ['bin/windows'] : ['bin/x86_64-linux', 'bin/aarch64-linux', 'bin/universal-darwin', 'bin']
|
|
150
|
+
const texRecentYears = () => { const now = new Date().getFullYear(); const out = []; for (let i = 0; i < TEX_YEAR_LOOKBACK; i++) out.push(String(now - i)); return out }
|
|
151
|
+
let texProbes = 0
|
|
97
152
|
for (const base of list) {
|
|
98
153
|
if (!base) continue
|
|
99
154
|
if (kind === 'tex' && !/bin/i.test(base)) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
155
|
+
const onDisk = (await listDirs(texProbeBase(base))).filter((d) => /^\d{4}$/.test(d)).sort((a, b) => Number(b) - Number(a))
|
|
156
|
+
const years = onDisk.concat(texRecentYears().filter((y) => onDisk.indexOf(y) < 0)).slice(0, TEX_YEAR_LOOKBACK)
|
|
157
|
+
for (const year of years) {
|
|
158
|
+
for (const sub2 of texYearSubdirs) {
|
|
159
|
+
if (texProbes >= TEX_CANDIDATE_CAP) break
|
|
160
|
+
texProbes++
|
|
161
|
+
const p = await resolvePath(texProbeBase(base) + '/' + year + '/' + sub2 + '/' + name)
|
|
103
162
|
if (p) return { exe: p, via: 'known-install', tried, name }
|
|
104
163
|
}
|
|
164
|
+
if (texProbes >= TEX_CANDIDATE_CAP) break
|
|
105
165
|
}
|
|
166
|
+
if (texProbes >= TEX_CANDIDATE_CAP) break
|
|
106
167
|
continue
|
|
107
168
|
}
|
|
108
|
-
|
|
169
|
+
if (kind === 'tex') {
|
|
170
|
+
if (texProbes >= TEX_CANDIDATE_CAP) break
|
|
171
|
+
texProbes++
|
|
172
|
+
}
|
|
173
|
+
const p = await resolvePath(texProbeBase(base) + '/' + name)
|
|
109
174
|
if (p) return { exe: p, via: 'known-install', tried, name }
|
|
110
175
|
}
|
|
111
176
|
return { exe: null, via: null, tried, name, reason: 'not found on PATH or in the known install locations' }
|
|
@@ -188,15 +253,21 @@ const PAPER_SECTIONS = [
|
|
|
188
253
|
// the model compete for the same CPU, and the parameter surface deliberately gains only
|
|
189
254
|
// `leanAsync` (the per-compile budget reuses `leanTimeoutMs`).
|
|
190
255
|
const LEAN_QUEUE_CONCURRENCY = 1
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
|
|
256
|
+
// task-27 (real-host A round + local reproduction): Lean 4 accepts `-R`/`--root` (or LEAN_PATH); `--search-path`
|
|
257
|
+
// is the **Lean 3** spelling and makes `lean` fail at ARGUMENT PARSING (rc=1) before it reads the file — so every
|
|
258
|
+
// compile failed by default. Local check on Lean 4.34.0: `lean --search-path <dir> <f>` ⇒ rc=1; `lean -R <dir> <f>`
|
|
259
|
+
// ⇒ accepted (the compiler then reads the file). ONE place for the flag spelling: change this constant only.
|
|
260
|
+
const LEAN_SEARCH_PATH_FLAG = '-R'
|
|
194
261
|
const LEAN_READ_MAX_BYTES = 64 * 1024
|
|
195
262
|
// A user-supplied search root wins: never inject a second one (explicit override first).
|
|
196
263
|
function leanHasSearchFlag(args) {
|
|
264
|
+
// task-27: the flag we INJECT is `-R` (Lean 4). A user may still state any of these in `leanArgs` — the legacy
|
|
265
|
+
// Lean-3 spelling included — and this guard's only job is "never inject a SECOND search root".
|
|
266
|
+
const ALSO_ACCEPTED = ['--search-path', '--root']
|
|
197
267
|
return (Array.isArray(args) ? args : []).some((a) => {
|
|
198
268
|
const t = String(a)
|
|
199
|
-
|
|
269
|
+
if (t === LEAN_SEARCH_PATH_FLAG || t.indexOf(LEAN_SEARCH_PATH_FLAG + '=') === 0) return true
|
|
270
|
+
return ALSO_ACCEPTED.some((f) => t === f || t.indexOf(f + '=') === 0)
|
|
200
271
|
})
|
|
201
272
|
}
|
|
202
273
|
// Content identity for queue dedupe: CRLF normalised, trailing blank lines dropped.
|
|
@@ -413,6 +484,9 @@ export function apply(ctx) {
|
|
|
413
484
|
// The final-paper flow (stage machine + its artifacts state). Durable like everything
|
|
414
485
|
// else, so a restart resumes the same stage instead of writing a second paper.
|
|
415
486
|
paper: null,
|
|
487
|
+
// Methodology/collaboration feedback (Shared/Feedback/). Durable like `verdicts`, so a restart
|
|
488
|
+
// keeps the loop closed instead of losing what the institute learned about working together.
|
|
489
|
+
feedback: [],
|
|
416
490
|
}
|
|
417
491
|
}
|
|
418
492
|
// LOW (deep review): `state.order` used to be maintained here and preserved in the diagnostics
|
|
@@ -509,6 +583,15 @@ export function apply(ctx) {
|
|
|
509
583
|
: patch.paper
|
|
510
584
|
if (next !== undefined) n.paper = next
|
|
511
585
|
}
|
|
586
|
+
// Durable methodology/collaboration feedback (Shared/Feedback/). A FUNCTION is a mutation
|
|
587
|
+
// applied INSIDE the fold (the same trick `paper` uses): several members may add or update
|
|
588
|
+
// entries between two commits, and a read-modify-write outside the fold would lose one.
|
|
589
|
+
if (patch.feedback !== undefined) {
|
|
590
|
+
const next = typeof patch.feedback === 'function'
|
|
591
|
+
? patch.feedback(Array.isArray(n.feedback) ? n.feedback : [])
|
|
592
|
+
: patch.feedback
|
|
593
|
+
if (next !== undefined) n.feedback = Array.isArray(next) ? next : []
|
|
594
|
+
}
|
|
512
595
|
return n
|
|
513
596
|
})
|
|
514
597
|
}
|
|
@@ -911,8 +994,8 @@ export function apply(ctx) {
|
|
|
911
994
|
// today's behaviour (the reminder rides with formalVerify); 'eager' = push valuable small
|
|
912
995
|
// lemmas/propositions/definitions into the library even in daily rounds.
|
|
913
996
|
leanInitiative: 'normal',
|
|
914
|
-
// leanSearchPaths: extra
|
|
915
|
-
// root (deduped; an explicit
|
|
997
|
+
// leanSearchPaths: extra `-R` roots injected BEFORE the automatic VibeMath
|
|
998
|
+
// root (deduped; an explicit -R/--root in leanArgs wins).
|
|
916
999
|
leanSearchPaths: [],
|
|
917
1000
|
// leanJobsMaxParallel: how many background compiles may run at once (default 1 = serial).
|
|
918
1001
|
leanJobsMaxParallel: 1,
|
|
@@ -948,6 +1031,12 @@ export function apply(ctx) {
|
|
|
948
1031
|
paperCompilePdf: true,
|
|
949
1032
|
paperEditor: 'academician',
|
|
950
1033
|
paperLatexCommand: '',
|
|
1034
|
+
// ── methodology / collaboration feedback (Shared/Feedback/) ──
|
|
1035
|
+
// feedback — 'on' (default: the tool works AND the short per-round hint is injected) |
|
|
1036
|
+
// 'off' (the hint is NOT injected and every write is refused BY NAME, never
|
|
1037
|
+
// silently dropped). It is a switch, not a severity: recording is encouraged,
|
|
1038
|
+
// never mandatory.
|
|
1039
|
+
feedback: 'on',
|
|
951
1040
|
// ── model / tools ────────────────────────────────────────────────────
|
|
952
1041
|
provider: '',
|
|
953
1042
|
model: '',
|
|
@@ -2007,7 +2096,7 @@ export function apply(ctx) {
|
|
|
2007
2096
|
// someone who never asked.
|
|
2008
2097
|
if (m.kind === 'assign') return (m.from === 'office' ? '【所办分派】' : '【院士分派】') + m.text
|
|
2009
2098
|
if (m.kind === 'nudge') return '【督办 from ' + m.from + '】' + m.text
|
|
2010
|
-
if (m.kind === 'notice') return '
|
|
2099
|
+
if (m.kind === 'notice') return '【研究所提示】' + m.text
|
|
2011
2100
|
return '【研究所·' + m.kind + ' from ' + m.from + '】' + m.text
|
|
2012
2101
|
}
|
|
2013
2102
|
// Batch plain chat so a chatty institute cannot cause a wake storm; anything
|
|
@@ -2436,6 +2525,7 @@ export function apply(ctx) {
|
|
|
2436
2525
|
if (member.direction && !resume) { L.push('给你的起点方向:' + member.direction); L.push('') }
|
|
2437
2526
|
mathPushLine(L)
|
|
2438
2527
|
paperPushLine(L)
|
|
2528
|
+
feedbackPushLine(L)
|
|
2439
2529
|
L.push('------------')
|
|
2440
2530
|
L.push(stateBlock(member, roundNo))
|
|
2441
2531
|
L.push('------------')
|
|
@@ -2456,6 +2546,7 @@ export function apply(ctx) {
|
|
|
2456
2546
|
if (leanDailyOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
2457
2547
|
mathPushLine(L)
|
|
2458
2548
|
paperPushLine(L)
|
|
2549
|
+
feedbackPushLine(L)
|
|
2459
2550
|
L.push('')
|
|
2460
2551
|
L.push('------------')
|
|
2461
2552
|
L.push(stateBlock(member))
|
|
@@ -2474,6 +2565,7 @@ export function apply(ctx) {
|
|
|
2474
2565
|
if (leanDailyOn()) { L.push(''); L.push(formalWorkLine()) }
|
|
2475
2566
|
mathPushLine(L)
|
|
2476
2567
|
paperPushLine(L)
|
|
2568
|
+
feedbackPushLine(L)
|
|
2477
2569
|
L.push('')
|
|
2478
2570
|
L.push('------------')
|
|
2479
2571
|
L.push(stateBlock(member))
|
|
@@ -2510,6 +2602,7 @@ export function apply(ctx) {
|
|
|
2510
2602
|
L.push('一致认为是真时,本所才会停下来。')
|
|
2511
2603
|
mathPushLine(L)
|
|
2512
2604
|
paperPushLine(L)
|
|
2605
|
+
feedbackPushLine(L)
|
|
2513
2606
|
L.push('')
|
|
2514
2607
|
L.push('------------')
|
|
2515
2608
|
L.push(stateBlock(member))
|
|
@@ -2523,7 +2616,12 @@ export function apply(ctx) {
|
|
|
2523
2616
|
L.push('')
|
|
2524
2617
|
L.push('本所正在对下列对象发起共识验证:')
|
|
2525
2618
|
L.push(' 对象:' + vs.target + '(类型:' + kindLabel2(vs.kind) + ')')
|
|
2526
|
-
if (vs.statement)
|
|
2619
|
+
if (vs.statement) {
|
|
2620
|
+
// task-24: this line feeds the member prompt. Cutting it with a bare `.slice(0, 800)` hid the tail of a
|
|
2621
|
+
// long statement with no trace; make the cut visible instead (the full text stays in the source card).
|
|
2622
|
+
const _st = String(vs.statement)
|
|
2623
|
+
L.push(' 陈述:' + (_st.length > 800 ? _st.slice(0, 800) + '…(已截断;完整陈述见源卡片 ' + String(vs.rel || vs.target || '') + ')' : _st))
|
|
2624
|
+
}
|
|
2527
2625
|
L.push('')
|
|
2528
2626
|
L.push('请给出你**诚实独立的判断**:')
|
|
2529
2627
|
L.push(' verdict = 1 表示你认为该对象**绝对为真**;')
|
|
@@ -2556,6 +2654,7 @@ export function apply(ctx) {
|
|
|
2556
2654
|
L.push('本所宁可留下未定论,也不要一个骗人的结论。')
|
|
2557
2655
|
mathPushLine(L)
|
|
2558
2656
|
paperPushLine(L)
|
|
2657
|
+
feedbackPushLine(L)
|
|
2559
2658
|
L.push('')
|
|
2560
2659
|
L.push('------------')
|
|
2561
2660
|
L.push(stateBlock(member))
|
|
@@ -2963,7 +3062,7 @@ export function apply(ctx) {
|
|
|
2963
3062
|
for (const j of leanJobs.values()) if (j.state === 'queued' && (!best || (j.enqueuedAt || 0) < (best.enqueuedAt || 0))) best = j
|
|
2964
3063
|
return best
|
|
2965
3064
|
}
|
|
2966
|
-
// The absolute VibeMath root handed to
|
|
3065
|
+
// The absolute VibeMath root handed to `-R` (the compile runs with cwd=instRoot()).
|
|
2967
3066
|
function leanSearchRootView() { return vibeRoot().replace(/\\/g, '/') }
|
|
2968
3067
|
async function leanSearchRoot() {
|
|
2969
3068
|
const root = vibeRoot()
|
|
@@ -3536,14 +3635,194 @@ export function apply(ctx) {
|
|
|
3536
3635
|
// is the "off means zero mention" discipline).
|
|
3537
3636
|
function mathPushLine(L) { const b = mathPromptBlock('zh'); if (b) L.push(b) }
|
|
3538
3637
|
|
|
3539
|
-
// task-9 2b: the LaTeX-missing guidance (four bounded steps + three hard boundaries).
|
|
3540
|
-
//
|
|
3638
|
+
// task-9 2b: the LaTeX-missing guidance (four bounded steps + three hard boundaries). Those four
|
|
3639
|
+
// steps and three boundaries are the shared contract; task-28 (v5 PILOT) additionally names the
|
|
3640
|
+
// "installed but NOT on PATH" case and TeX Live's documented positions — v4/v2/v3 adopt the same
|
|
3641
|
+
// text once v5 is verified, so the wording deliberately leads on v5.
|
|
3541
3642
|
function paperHintBlock(lang) {
|
|
3542
3643
|
return lang === 'en'
|
|
3543
|
-
? '\nWhen no LaTeX engine is detected: (1) probe only the documented common TeX roots and PATH (e.g. where xelatex, latexmk --version) - never scan whole drives; (2) once the absolute path is found, write it into paperLatexCommand, re-detect, then continue; (3) if it is still missing,
|
|
3544
|
-
: '\n检测不到 LaTeX 引擎时:① 只在文档化的常见 TeX 根与 PATH 上做有界核查(如 where xelatex、latexmk --version
|
|
3644
|
+
? '\nWhen no LaTeX engine is detected: (1) probe only the documented common TeX roots and PATH (e.g. where xelatex, latexmk --version) - never scan whole drives; the MOST COMMON case is an engine that IS installed but NOT on PATH — a DRIVE-ROOT Windows TeX Live lives under `texlive\\<year>\\bin\\windows` on that drive, Unix under the standard system paths `/usr/local/texlive/<year>/bin/*` or `/opt/texlive/<year>/bin/*`, macOS at `/Library/TeX/texbin`; (2) once the absolute path is found, write it into paperLatexCommand, re-detect, then continue; (3) if it is still missing, REPORT IT TO THE OFFICE (or the group chat) and let the OFFICE confirm with the user (installing TeX requires the user\'s explicit approval); (4) with no answer yet, degrade exactly as today (deliver paper.tex and paper.md only). Hard boundaries: never auto-install; never write outside the workspace; never treat "not detected" as a failure.'
|
|
3645
|
+
: '\n检测不到 LaTeX 引擎时:① 只在文档化的常见 TeX 根与 PATH 上做有界核查(如 where xelatex、latexmk --version),不要全盘扫描——**最常见的情形是引擎装了但不在 PATH**:TeX Live 若装在 Windows 的某个**盘根**下,看该盘根里的 `texlive\\<年份>\\bin\\windows`;类 Unix 看标准系统路径 `/usr/local/texlive/<年份>/bin/*`、`/opt/texlive/<年份>/bin/*`;macOS 看 `/Library/TeX/texbin`;② 找到绝对路径后写入 paperLatexCommand 并重新检测,再继续;③ 仍找不到就**如实上报所办(或群聊)**,由**所办**向用户确认(安装 TeX 需用户明确同意);④ 尚无回应则照旧降级(只交付 paper.tex 与 paper.md)。硬边界:绝不自动安装;绝不写工作区之外;绝不把"未检测到"当失败。'
|
|
3646
|
+
}
|
|
3647
|
+
function paperPushLine(L) {
|
|
3648
|
+
const b = paperHintBlock('zh')
|
|
3649
|
+
// Iron-rules fix (P4): this block only matters while the member is writing/compiling the paper, yet it was
|
|
3650
|
+
// pushed into EVERY wake — founding, verify and meeting prompts included — where it reads as unexplained
|
|
3651
|
+
// noise ("why is LaTeX guidance in my first round?"). Keep the guidance (the paper builders do NOT emit it,
|
|
3652
|
+
// so removing it here would lose it entirely) but SAY WHEN IT APPLIES. The block itself stays byte-identical
|
|
3653
|
+
// across presets; only this per-preset wrapper changes.
|
|
3654
|
+
if (b) L.push('\n(**以下这段仅在你参与论文写作或编译时适用**;其他阶段可忽略。)' + b)
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
// ---- feedback library (Shared/Feedback/): methodology / collaboration, never research -----------
|
|
3658
|
+
// Entries live in the DURABLE `feedback` array and are written through the fold (patchInstitute with
|
|
3659
|
+
// an UPDATER), so two concurrent adds/updates cannot lose one. The human-readable mirror (one file per
|
|
3660
|
+
// category + a JSON index) is WRITE-ONLY like Shared/TaskBoard.md; the state file stays authoritative.
|
|
3661
|
+
function feedbackOn() { return String(params.feedback || 'on') !== 'off' }
|
|
3662
|
+
function feedbackEntries() { const s = inst(); if (!Array.isArray(s.feedback)) s.feedback = []; return s.feedback }
|
|
3663
|
+
function feedbackCounts() {
|
|
3664
|
+
const all = feedbackEntries()
|
|
3665
|
+
const byCategory = {}, byRoute = {}
|
|
3666
|
+
for (const c of FEEDBACK_CATEGORIES) byCategory[c] = 0
|
|
3667
|
+
for (const r of FEEDBACK_ROUTES) byRoute[r] = 0
|
|
3668
|
+
let open = 0
|
|
3669
|
+
for (const e of all) {
|
|
3670
|
+
if (byCategory[e.category] !== undefined) byCategory[e.category] += 1
|
|
3671
|
+
if (byRoute[e.route] !== undefined) byRoute[e.route] += 1
|
|
3672
|
+
if (FEEDBACK_OPEN_STATUSES.indexOf(e.status) !== -1) open += 1
|
|
3673
|
+
}
|
|
3674
|
+
return { total: all.length, open, closed: all.length - open, byCategory, byRoute }
|
|
3675
|
+
}
|
|
3676
|
+
function feedbackView(e) {
|
|
3677
|
+
return { id: e.id, at: e.at, by: e.by, category: e.category, route: e.route, status: e.status,
|
|
3678
|
+
context: e.context || '', phenomenon: e.phenomenon || '', impact: e.impact || '',
|
|
3679
|
+
action: e.action || '', assessment: e.assessment || '', outcome: e.outcome || '',
|
|
3680
|
+
note: e.note || '', updatedAt: e.updatedAt || '', updatedBy: e.updatedBy || '' }
|
|
3681
|
+
}
|
|
3682
|
+
/** The human-readable mirror + structured index. A failure is REPORTED (mirror:false), never thrown
|
|
3683
|
+
* and never logged: it must not disturb the existing write-failure accounting (F6). */
|
|
3684
|
+
async function feedbackMirror() {
|
|
3685
|
+
const all = feedbackEntries()
|
|
3686
|
+
let ok = true
|
|
3687
|
+
for (const c of FEEDBACK_CATEGORIES) {
|
|
3688
|
+
const rows = all.filter((e) => e.category === c)
|
|
3689
|
+
const md = ['# 反馈库 · ' + FEEDBACK_CATEGORY_LABELS[c] + '(' + c + ')', '',
|
|
3690
|
+
'> 方法论/协作层记录:怎么一起工作(组织、流程、合作、障碍、摩擦)。**不是研究结论**——研究进展见 Progress/,已确立结论见 Verified/。', '']
|
|
3691
|
+
for (const e of rows) {
|
|
3692
|
+
md.push('## ' + e.id + '|' + e.route + '|' + e.status + '|' + (e.at || ''), '',
|
|
3693
|
+
'- 发起人:' + (e.by || '') + '|类别:' + c + '|路由:' + e.route + '|状态:' + e.status,
|
|
3694
|
+
'- 情境:' + (e.context || '(未写)'),
|
|
3695
|
+
'- 现象:' + (e.phenomenon || ''),
|
|
3696
|
+
'- 影响:' + (e.impact || ''),
|
|
3697
|
+
'- 已做的调整或建议:' + (e.action || ''))
|
|
3698
|
+
if (e.route === 'interpersonal') md.push('- 评估(要不要改/改了是否真更优):' + (e.assessment || ''))
|
|
3699
|
+
if (e.outcome) md.push('- 事后验证/结果:' + e.outcome)
|
|
3700
|
+
if (e.note) md.push('- 备注:' + e.note)
|
|
3701
|
+
if (e.updatedAt) md.push('- 最近更新:' + e.updatedAt + '(' + (e.updatedBy || '') + ')')
|
|
3702
|
+
md.push('')
|
|
3703
|
+
}
|
|
3704
|
+
if (!rows.length) md.push('(本类暂无条目)', '')
|
|
3705
|
+
try { if (!(await writeTextRel(FEEDBACK_DIR + '/' + c + '.md', md.join('\n')))) ok = false } catch (err) { ok = false }
|
|
3706
|
+
}
|
|
3707
|
+
try {
|
|
3708
|
+
if (!(await writeTextRel(FEEDBACK_DIR + '/index.json', JSON.stringify({ counts: feedbackCounts(), entries: all }, null, 2) + '\n'))) ok = false
|
|
3709
|
+
} catch (err) { ok = false }
|
|
3710
|
+
return ok
|
|
3711
|
+
}
|
|
3712
|
+
async function feedbackTool(caller, a) {
|
|
3713
|
+
const op = String((a && a.op) || '').trim()
|
|
3714
|
+
// `off` is NEVER a silent write: the call is refused BY NAME with the reason and the remedy.
|
|
3715
|
+
if (!feedbackOn()) {
|
|
3716
|
+
return { ok: false, code: 'V5_FEEDBACK_DISABLED', message: FEEDBACK_DISABLED_MESSAGE,
|
|
3717
|
+
next: { kind: 'note', tool: 'vibe_v5_set', hint: '由所办把 feedback 设回 \'on\' 后重试(本条没有被静默写入)' } }
|
|
3718
|
+
}
|
|
3719
|
+
if (op === 'add') {
|
|
3720
|
+
const category = String(a.category || '').trim()
|
|
3721
|
+
const route = String(a.route || '').trim()
|
|
3722
|
+
if (FEEDBACK_CATEGORIES.indexOf(category) < 0) {
|
|
3723
|
+
return { ok: false, code: 'V5_FEEDBACK_BAD_CATEGORY', message: 'category 必须是 ' + FEEDBACK_CATEGORIES.join('|'),
|
|
3724
|
+
next: { kind: 'note', hint: '合作=cooperation|管理=management|流程=process|障碍=obstacle|矛盾=conflict' } }
|
|
3725
|
+
}
|
|
3726
|
+
if (FEEDBACK_ROUTES.indexOf(route) < 0) {
|
|
3727
|
+
return { ok: false, code: 'V5_FEEDBACK_BAD_ROUTE', message: 'route 必须是 ' + FEEDBACK_ROUTES.join('|'),
|
|
3728
|
+
next: { kind: 'note', hint: 'self=自己的做法(自己调整即可)|team=组织/工作流(自我调节,无需审批)|interpersonal=别人造成的、要别人改变(须评估+事后回填)' } }
|
|
3729
|
+
}
|
|
3730
|
+
const phenomenon = String(a.phenomenon || '').trim()
|
|
3731
|
+
const impact = String(a.impact || '').trim()
|
|
3732
|
+
const action = String(a.action || '').trim()
|
|
3733
|
+
if (!phenomenon || !impact || !action) {
|
|
3734
|
+
return { ok: false, code: 'V5_FEEDBACK_INCOMPLETE', message: 'add 需要 phenomenon/impact/action(现象、影响、已做的调整或建议)',
|
|
3735
|
+
next: { kind: 'note', hint: '写清"看到了什么/造成什么/我已经或打算怎么调整"' } }
|
|
3736
|
+
}
|
|
3737
|
+
const assessment = String(a.assessment || '').trim()
|
|
3738
|
+
// ONLY the interpersonal route gates on the careful evaluation: self/team are self-regulation
|
|
3739
|
+
// (the user ruling: no approval step, adjust as soon as it looks wrong).
|
|
3740
|
+
if (route === 'interpersonal' && !assessment) {
|
|
3741
|
+
return { ok: false, code: 'V5_FEEDBACK_NEEDS_ASSESSMENT', message: 'interpersonal 路由必须带 assessment:先想清楚"要不要让别人改、改了是否真的更优"',
|
|
3742
|
+
next: { kind: 'note', hint: '若发现只能靠自己调整,请改记 self;确实需要别人改变时,写完评估再跟进事后验证' } }
|
|
3743
|
+
}
|
|
3744
|
+
const base = { at: now(), by: caller, category, route, status: 'open',
|
|
3745
|
+
context: String(a.context || ''), phenomenon, impact, action, assessment,
|
|
3746
|
+
outcome: '', note: String(a.note || ''), updatedAt: '', updatedBy: '' }
|
|
3747
|
+
let entry = null
|
|
3748
|
+
await patchInstitute({ feedback: (cur) => {
|
|
3749
|
+
const list = Array.isArray(cur) ? cur.slice() : []
|
|
3750
|
+
entry = Object.assign({ id: 'fb-' + (list.length + 1) }, base)
|
|
3751
|
+
list.push(entry)
|
|
3752
|
+
return list
|
|
3753
|
+
} })
|
|
3754
|
+
const mirror = await feedbackMirror()
|
|
3755
|
+
return { ok: true, entry: feedbackView(entry), mirror, counts: feedbackCounts(),
|
|
3756
|
+
routeHint: route === 'interpersonal'
|
|
3757
|
+
? 'interpersonal:已记录。请按评估结论处理,并用 update 回填 outcome(事后验证)才算闭环。'
|
|
3758
|
+
: 'self/team:无需任何人采纳——自己调整即可,并用 update 把状态/结果记下来(self/team 的记录本身就是闭环凭据)。' }
|
|
3759
|
+
}
|
|
3760
|
+
if (op === 'update') {
|
|
3761
|
+
const id = String(a.id || '').trim()
|
|
3762
|
+
if (!id) return { ok: false, code: 'V5_FEEDBACK_NO_ID', message: 'update 需要 id', next: { kind: 'note', hint: '先用 op=list(默认只看未闭环)确认 id(形如 fb-1)' } }
|
|
3763
|
+
const cur = feedbackEntries().find((x) => x.id === id)
|
|
3764
|
+
if (!cur) return { ok: false, code: 'V5_FEEDBACK_NOT_FOUND', message: '没有条目 ' + id, next: { kind: 'note', hint: '先用 op=list 确认 id' } }
|
|
3765
|
+
// Permission: the INITIATOR updates their own entry; the OFFICE may update any (mark it resolved /
|
|
3766
|
+
// write the result back). Anyone else is refused BY NAME — never silently ignored.
|
|
3767
|
+
if (caller !== 'office' && caller !== cur.by) {
|
|
3768
|
+
return { ok: false, code: 'V5_FEEDBACK_FORBIDDEN', message: '只有发起人(' + cur.by + ')或所办可以更新 ' + id,
|
|
3769
|
+
next: { kind: 'note', hint: '别人的问题请自己另记一条(你只能更新自己发起的条目)' } }
|
|
3770
|
+
}
|
|
3771
|
+
const status = a.status === undefined ? '' : String(a.status).trim()
|
|
3772
|
+
if (status && FEEDBACK_STATUSES.indexOf(status) < 0) {
|
|
3773
|
+
return { ok: false, code: 'V5_FEEDBACK_BAD_STATUS', message: 'status 必须是 ' + FEEDBACK_STATUSES.join('|'),
|
|
3774
|
+
next: { kind: 'note', hint: 'open=未处理|adjusted=已自我调整|closed=已回填结果|dropped=评估后决定不改(需理由)' } }
|
|
3775
|
+
}
|
|
3776
|
+
const outcome = a.outcome === undefined ? '' : String(a.outcome).trim()
|
|
3777
|
+
const note = a.note === undefined ? '' : String(a.note).trim()
|
|
3778
|
+
// The interpersonal route is the ONLY one that must PROVE the change helped: closing it without
|
|
3779
|
+
// the written-back verification is refused (that is exactly why the route exists).
|
|
3780
|
+
if (cur.route === 'interpersonal' && status === 'closed' && !outcome) {
|
|
3781
|
+
return { ok: false, code: 'V5_FEEDBACK_NEEDS_OUTCOME', message: 'interpersonal 条目闭环必须回填 outcome(事后验证:改了什么、结果如何)',
|
|
3782
|
+
next: { kind: 'note', hint: '若评估结论是"不需要别人改",请用 status=dropped + note 写明理由' } }
|
|
3783
|
+
}
|
|
3784
|
+
if (status === 'dropped' && !outcome && !note) {
|
|
3785
|
+
return { ok: false, code: 'V5_FEEDBACK_NEEDS_REASON', message: 'status=dropped 需要 note 或 outcome 说明"为什么不改"',
|
|
3786
|
+
next: { kind: 'note', hint: '评估结论也要留痕,否则下一轮会重新提同一件事' } }
|
|
3787
|
+
}
|
|
3788
|
+
const changes = { updatedAt: now(), updatedBy: caller }
|
|
3789
|
+
if (status) changes.status = status
|
|
3790
|
+
if (outcome) changes.outcome = outcome
|
|
3791
|
+
if (a.action !== undefined) changes.action = String(a.action)
|
|
3792
|
+
if (a.assessment !== undefined) changes.assessment = String(a.assessment)
|
|
3793
|
+
if (a.note !== undefined) changes.note = note
|
|
3794
|
+
await patchInstitute({ feedback: (list) => (Array.isArray(list) ? list : []).map((x) => (x.id === id ? Object.assign({}, x, changes) : x)) })
|
|
3795
|
+
const after = feedbackEntries().find((x) => x.id === id) || cur
|
|
3796
|
+
const mirror = await feedbackMirror()
|
|
3797
|
+
return { ok: true, entry: feedbackView(after), mirror, counts: feedbackCounts() }
|
|
3798
|
+
}
|
|
3799
|
+
if (op === 'list') {
|
|
3800
|
+
const category = a.category === undefined ? '' : String(a.category).trim()
|
|
3801
|
+
const route = a.route === undefined ? '' : String(a.route).trim()
|
|
3802
|
+
const all = a.all === true
|
|
3803
|
+
const rows = feedbackEntries()
|
|
3804
|
+
.filter((e) => (!category || e.category === category) && (!route || e.route === route))
|
|
3805
|
+
.filter((e) => all || FEEDBACK_OPEN_STATUSES.indexOf(e.status) !== -1)
|
|
3806
|
+
.map(feedbackView)
|
|
3807
|
+
return { ok: true, openOnly: !all, count: rows.length, entries: rows, counts: feedbackCounts(),
|
|
3808
|
+
note: all ? '全部条目(含已闭环)' : '默认只列未闭环(open|adjusted);要看全部用 all:true' }
|
|
3809
|
+
}
|
|
3810
|
+
if (op === 'summary') return { ok: true, counts: feedbackCounts() }
|
|
3811
|
+
return { ok: false, code: 'V5_FEEDBACK_BAD_OP', message: 'op 必须是 add|update|list|summary',
|
|
3812
|
+
next: { kind: 'note', hint: 'add=写一条;update=状态流转+回填;list=看未闭环;summary=按类别/路由计数' } }
|
|
3813
|
+
}
|
|
3814
|
+
/** The per-round line. Deliberately SHORT (P4's lesson: every wake already carries the tool/paper
|
|
3815
|
+
* tails) and it answers WHY / WHAT / the three routes / what happens next, so recording is explained
|
|
3816
|
+
* rather than commanded. */
|
|
3817
|
+
function feedbackPushLine(L) {
|
|
3818
|
+
if (!feedbackOn()) return
|
|
3819
|
+
L.push(''
|
|
3820
|
+
+ '\n【工作经验/流程反馈(鼓励积极记录)】' + FEEDBACK_DIR + '/'
|
|
3821
|
+
+ '\n· 为什么记:怎么一起工作(组织、流程、合作、障碍、摩擦)属于**方法论层**的自我调节;研究结论请进 Progress/ 与 Verified/,这里不记结论。'
|
|
3822
|
+
+ '\n· 记什么:类别(合作/管理/流程/障碍/矛盾)+路由+情境/现象/影响+你**已做的调整或建议**;写到多细由你把控。'
|
|
3823
|
+
+ '\n· 三条路由:self=你自己的做法⇒**自己调整,无需谁采纳**;team=组织/工作流/团队运行的调整⇒**同样无需审批**,觉得不好就及时调整;interpersonal=**别人造成的、要别人改变才能解决**⇒只有这条要先**缜密评估**"要不要改、改了是否真更优",并**事后回填验证**。'
|
|
3824
|
+
+ '\n· 记了之后:未闭环条目会出现在汇报与总览的计数里;用 vibe_v5_feedback 的 update 回填结果即闭环(interpersonal 必须回填 outcome)。')
|
|
3545
3825
|
}
|
|
3546
|
-
function paperPushLine(L) { const b = paperHintBlock('zh'); if (b) L.push(b) }
|
|
3547
3826
|
|
|
3548
3827
|
function formalPromptBlock(target) {
|
|
3549
3828
|
if (!formalOn()) return ''
|
|
@@ -4168,7 +4447,7 @@ export function apply(ctx) {
|
|
|
4168
4447
|
const expected = Number(args.expected_revision !== undefined ? args.expected_revision : args.expectedRevision)
|
|
4169
4448
|
if (!Number.isFinite(expected)) return { ok: false, code: 'V5_INVALID_ARGUMENT', message: 'expected_revision is required' }
|
|
4170
4449
|
if (expected !== task.revision) {
|
|
4171
|
-
// F6 (deep-review 5): this message is routed into Chinese frames (
|
|
4450
|
+
// F6 (deep-review 5): this message is routed into Chinese frames (`【研究所提示】…`), where a
|
|
4172
4451
|
// raw English diagnostic reads as noise. The CODE stays (`V5_TASK_STALE_REVISION`) and the
|
|
4173
4452
|
// revision numbers stay, but the sentence is Chinese now for the member reading it.
|
|
4174
4453
|
return { ok: false, code: 'V5_TASK_STALE_REVISION', message: '任务 ' + id + ' 当前 revision 是 ' + task.revision + ',不是 ' + expected + ' —— 请先用 vibe_v5_task_get 重新读取(code: V5_TASK_STALE_REVISION)' }
|
|
@@ -4445,7 +4724,17 @@ export function apply(ctx) {
|
|
|
4445
4724
|
if (!rel) return { rel: null, statement: '' }
|
|
4446
4725
|
const text = (await readTextRel(rel)) || ''
|
|
4447
4726
|
const m = /##\s*陈述\s*\n([\s\S]*?)(?:\n##\s|$)/.exec(text)
|
|
4448
|
-
|
|
4727
|
+
const full = String(m ? m[1] : text).trim()
|
|
4728
|
+
// task-24 (real-host R2, D5'): this used to be `.slice(0, 1200)` with NO marker, so a long statement was
|
|
4729
|
+
// cut silently — and the cut text is exactly what voters read and what the verified record carries
|
|
4730
|
+
// (a real host registered statements >1200 chars). Keep a cap as generous as the paper sections
|
|
4731
|
+
// (20000) and, when a cut DOES happen, say so in-band so the reader knows to open the source file.
|
|
4732
|
+
const cap = 20000
|
|
4733
|
+
if (full.length <= cap) return { rel, statement: full }
|
|
4734
|
+
return {
|
|
4735
|
+
rel, truncated: true,
|
|
4736
|
+
statement: full.slice(0, cap) + '\n(注意:陈述超过 ' + cap + ' 字符已截断,完整文本见 ' + rel + ')',
|
|
4737
|
+
}
|
|
4449
4738
|
}
|
|
4450
4739
|
// Update one `- 字段:` of a source card. Members hand-write cards in two shapes —
|
|
4451
4740
|
// one field per line, or one line with '; '-separated fields — so the anchor may
|
|
@@ -4881,7 +5170,9 @@ export function apply(ctx) {
|
|
|
4881
5170
|
const allVoted = need.length > 0 && need.every((id) => votes[id])
|
|
4882
5171
|
if (allVoted) await continueVerifyRound(next)
|
|
4883
5172
|
else await scheduleNext()
|
|
4884
|
-
|
|
5173
|
+
// task-25 (real-host R2): an incomplete electorate used to be reported only as `allVoted:false`, so the
|
|
5174
|
+
// caller could not tell WHICH voter was still missing (members probed repeatedly to find out). Name them.
|
|
5175
|
+
return { ok: true, voted: memberId, verdict: p, allVoted, pendingVoters: need.filter((id) => !votes[id]) }
|
|
4885
5176
|
}
|
|
4886
5177
|
|
|
4887
5178
|
// ---- chat log / meeting plumbing --------------------------------------
|
|
@@ -6115,7 +6406,7 @@ export function apply(ctx) {
|
|
|
6115
6406
|
if (await fileExistsAbs(paperAbs(id, 'paper.pdf'))) files.push('paper.pdf')
|
|
6116
6407
|
const warnings = (p.warnings || []).slice()
|
|
6117
6408
|
if (compile.status === 'failed') warnings.push('LaTeX 编译失败(已保留 paper.tex 与 paper.md,不阻塞定稿):' + (compile.attempts || []).map((a) => a.engine + '/' + a.label + ' exit=' + a.exitCode + (a.message ? '(' + a.message + ')' : '')).join(';'))
|
|
6118
|
-
if (compile.status === 'not-detected') warnings.push('未检测到 LaTeX 引擎(' + (compile.reason || '') + '):只交付 paper.tex 与 paper.md。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand
|
|
6409
|
+
if (compile.status === 'not-detected') warnings.push('未检测到 LaTeX 引擎(' + (compile.reason || '') + '):只交付 paper.tex 与 paper.md。已探测 PATH 与文档化的常见 TeX 根;可用 paperLatexCommand 指定绝对路径。最常见的情形是引擎已安装但不在 PATH(Windows 盘根下的 texlive\\<年>\\bin\\windows、类 Unix /usr/local|/opt/texlive/<年>/bin/*、macOS /Library/TeX/texbin 已纳入有界探测):把引擎的绝对路径写进 paperLatexCommand 再探测一次;仍找不到就如实上报所办(由所办向用户确认安装),随后照旧安全降级。')
|
|
6119
6410
|
// F6: the failed REQUIRED writes are named ONCE (for this finalisation), before the meta is built,
|
|
6120
6411
|
// so the warning lands in `meta.warnings`, in the flow log, in the state and in the return value.
|
|
6121
6412
|
if (writeFailures.length) warnings.push(paperWriteFailureWarning(writeFailures))
|
|
@@ -6184,8 +6475,8 @@ export function apply(ctx) {
|
|
|
6184
6475
|
const perCap = Math.max(1, Math.floor(Number(params.maxTempPerMember) || 3))
|
|
6185
6476
|
const totalCap = Math.max(1, Math.floor(Number(params.maxTempTotal) || 12))
|
|
6186
6477
|
const mine = employedTemps().filter((m) => m.hiredBy === (office ? 'office' : callerId)).length
|
|
6187
|
-
if (mine >= perCap) return { ok: false, code: 'V5_MEMBER_LIMIT', message: '你名下同时最多 ' + perCap + ' 名临时工(先在册 ' + mine + ' 名);请先解雇不再需要的' }
|
|
6188
|
-
if (employedTemps().length >= totalCap) return { ok: false, code: 'V5_MEMBER_LIMIT', message: '全所同时在册临时工已达上限 ' + totalCap }
|
|
6478
|
+
if (mine >= perCap) return { ok: false, code: 'V5_MEMBER_LIMIT', scope: 'per-member', message: '你名下同时最多 ' + perCap + ' 名临时工(先在册 ' + mine + ' 名);请先解雇不再需要的' }
|
|
6479
|
+
if (employedTemps().length >= totalCap) return { ok: false, code: 'V5_MEMBER_LIMIT', scope: 'institute', message: '全所同时在册临时工已达上限 ' + totalCap }
|
|
6189
6480
|
const member = await newMember('temp', {
|
|
6190
6481
|
direction: purpose, hiredBy: office ? 'office' : callerId, term: String(args.term || ''), provider: pickProvider(),
|
|
6191
6482
|
})
|
|
@@ -6954,7 +7245,7 @@ export function apply(ctx) {
|
|
|
6954
7245
|
'compactThreshold', 'compactAfterRounds', 'maxParallel', 'activityTimeoutMs', 'stallAutoMeetingMs',
|
|
6955
7246
|
'chatDigestMs', 'chatDigestMax', 'meetingKeepEvery', 'leanTimeoutMs']
|
|
6956
7247
|
const bools = ['academician', 'academicianLeads', 'memberMayRejectAssign', 'finalPaper', 'paperCompilePdf', 'leanAsync']
|
|
6957
|
-
const strs = ['quorumMode', 'provider', 'model', 'staffPersona', 'formalVerify', 'leanCommand',
|
|
7248
|
+
const strs = ['feedback', 'quorumMode', 'provider', 'model', 'staffPersona', 'formalVerify', 'leanCommand',
|
|
6958
7249
|
'paperFormat', 'paperLanguage', 'paperEditor', 'paperLatexCommand', 'leanInitiative',
|
|
6959
7250
|
'mathComputation', 'mathMode', 'mathInstallScope']
|
|
6960
7251
|
const arrs = ['toolAllow', 'toolDeny', 'tempToolAllow', 'tempToolDeny', 'leanArgs', 'leanSearchPaths',
|
|
@@ -6990,6 +7281,11 @@ export function apply(ctx) {
|
|
|
6990
7281
|
if (out.leanInitiative !== undefined) {
|
|
6991
7282
|
out.leanInitiative = ['off', 'normal', 'eager'].indexOf(String(out.leanInitiative)) !== -1 ? String(out.leanInitiative) : 'normal'
|
|
6992
7283
|
}
|
|
7284
|
+
// `feedback` is a CLOSED enum too: a typo must keep the documented default ('on') rather than
|
|
7285
|
+
// becoming an unreachable third mode that silently disables the recording hint.
|
|
7286
|
+
if (out.feedback !== undefined) {
|
|
7287
|
+
out.feedback = ['on', 'off'].indexOf(String(out.feedback).trim()) !== -1 ? String(out.feedback).trim() : 'on'
|
|
7288
|
+
}
|
|
6993
7289
|
// Concurrency floor, same discipline as quorumCap/verdictMaxRounds.
|
|
6994
7290
|
if (out.leanJobsMaxParallel !== undefined && out.leanJobsMaxParallel < 1) out.leanJobsMaxParallel = 1
|
|
6995
7291
|
// The six math_computation keys are normalised by the SHARED module (one implementation
|
|
@@ -7070,6 +7366,17 @@ export function apply(ctx) {
|
|
|
7070
7366
|
async function setParams(input) {
|
|
7071
7367
|
const droppedSet = []
|
|
7072
7368
|
const patch = normalizeParams(input || {}, droppedSet)
|
|
7369
|
+
// task-26 (real-host R2): an out-of-range value is coerced by normalizeParams (documented) and an unknown
|
|
7370
|
+
// key is dropped into `droppedSet` (recorded in diagnostics) — but the RESPONSE carried neither, so the
|
|
7371
|
+
// caller could not tell "changed" from "ignored" without a second `status` read. Report both, additively.
|
|
7372
|
+
const adjusted = {}
|
|
7373
|
+
for (const k of Object.keys(patch)) {
|
|
7374
|
+
const raw = (input || {})[k]
|
|
7375
|
+
if (raw === undefined) continue
|
|
7376
|
+
const before = Array.isArray(raw) ? raw.join(',') : String(raw)
|
|
7377
|
+
const after = Array.isArray(patch[k]) ? patch[k].join(',') : String(patch[k])
|
|
7378
|
+
if (before !== after) adjusted[k] = { from: raw, to: patch[k] }
|
|
7379
|
+
}
|
|
7073
7380
|
reportDroppedStateKeys(inst(), droppedSet, 'set')
|
|
7074
7381
|
const merged = Object.assign({}, params, patch)
|
|
7075
7382
|
await patchInstitute({ params: merged })
|
|
@@ -7081,7 +7388,7 @@ export function apply(ctx) {
|
|
|
7081
7388
|
// activityTimeoutMs (or a raised maxParallel) would not take effect until some
|
|
7082
7389
|
// unrelated event drove a pass. Tuning must apply immediately.
|
|
7083
7390
|
if (running && !autoDone) await scheduleNext()
|
|
7084
|
-
return { ok: true, params: visibleParams() }
|
|
7391
|
+
return { ok: true, params: visibleParams(), adjusted, dropped: droppedSet.slice() }
|
|
7085
7392
|
}
|
|
7086
7393
|
function visibleParams() {
|
|
7087
7394
|
return {
|
|
@@ -7105,6 +7412,8 @@ export function apply(ctx) {
|
|
|
7105
7412
|
mathComputation: params.mathComputation, mathMode: params.mathMode,
|
|
7106
7413
|
mathEngines: (params.mathEngines || []).slice(), mathTimeoutMs: params.mathTimeoutMs,
|
|
7107
7414
|
mathPackages: (params.mathPackages || []).slice(), mathInstallScope: params.mathInstallScope,
|
|
7415
|
+
// 'on' | 'off' — methodology/collaboration feedback (Shared/Feedback/).
|
|
7416
|
+
feedback: params.feedback,
|
|
7108
7417
|
// ── final paper ──────────────────────────────────────────────────────
|
|
7109
7418
|
finalPaper: params.finalPaper, paperFormat: params.paperFormat,
|
|
7110
7419
|
paperLanguage: params.paperLanguage, paperCompilePdf: params.paperCompilePdf,
|
|
@@ -7450,8 +7759,8 @@ export function apply(ctx) {
|
|
|
7450
7759
|
// payload, so a reader (or an operator comparing two sessions) cannot mistake them for
|
|
7451
7760
|
// durable state. Durable = derived from the state file; session = rebuilt on load.
|
|
7452
7761
|
fieldScopes: {
|
|
7453
|
-
session: ['running', 'autoDone(session mirror of phase)', 'leanNotices', 'debug', 'members[].rounds', 'members[].busy', 'members[].contextPct', 'members[].childId', 'meeting', 'parkedMeeting', 'persistence.writeFailures', 'persistence.prematureReads', 'persistence.loadProblem', 'pendingSpawns[].attempts'],
|
|
7454
|
-
durable: ['phase', 'runId', 'quorum', 'members[] (except the three session fields)', 'members[].failReason', 'tasks', 'failedMembers', 'pendingSpawns[] (derived from durable failed members)', 'chat', 'officeRequests', 'verify', 'verifyQueue', 'verified', 'undecided', 'solveVotes', 'formal', 'paper', 'lastProgressAt', 'params'],
|
|
7762
|
+
session: ['running', 'autoDone(session mirror of phase)', 'leanNotices', 'debug', 'members[].rounds', 'members[].busy', 'members[].contextPct', 'members[].childId', 'meeting', 'parkedMeeting', 'persistence.writeFailures', 'persistence.prematureReads', 'persistence.loadProblem', 'pendingSpawns[].attempts', 'diagnostics', 'backend'],
|
|
7763
|
+
durable: ['phase', 'runId', 'quorum', 'members[] (except the three session fields)', 'members[].failReason', 'tasks', 'failedMembers', 'pendingSpawns[] (derived from durable failed members)', 'chat', 'officeRequests', 'verify', 'verifyQueue', 'verified', 'verifiedTrue (derived from verdicts)', 'concludedFalse (derived from verdicts)', 'undecided (derived from verdicts)', 'verdicts', 'solve', 'solveVotes', 'formal', 'paper', 'lastProgressAt', 'params'],
|
|
7455
7764
|
},
|
|
7456
7765
|
backend: backend ? backend.kind : 'uninitialized',
|
|
7457
7766
|
// Skipped/malformed events AND state-file load problems. Without this the two
|
|
@@ -7694,6 +8003,25 @@ export function apply(ctx) {
|
|
|
7694
8003
|
// not a refusal: v5 cannot change the host's fence, and refusing would stop persisting work.
|
|
7695
8004
|
if (sandboxPolicyMismatch) L.push('- ⚠ 沙箱策略根与会话工作目录不一致:策略=' + sandboxPolicyMismatch.policyRoot + '|会话=' + sandboxPolicyMismatch.expected
|
|
7696
8005
|
+ '(v5 用会话工作目录拼所有路径;若宿主按策略根解析写操作,产物可能落在预期之外的那棵树——请核对 sandbox 策略的 workspaceRoot)')
|
|
8006
|
+
// Methodology/collaboration feedback: the numbers an operator (and the academician's periodic
|
|
8007
|
+
// summary) needs to see whether the loop is actually being USED and closed — not just collected.
|
|
8008
|
+
L.push('')
|
|
8009
|
+
L.push('## 反馈库(方法论/协作层;不是研究结论)')
|
|
8010
|
+
{
|
|
8011
|
+
const fc = feedbackCounts()
|
|
8012
|
+
L.push('- 条目 ' + fc.total + '|未闭环 ' + fc.open + '|已闭环 ' + fc.closed
|
|
8013
|
+
+ '|按类别:' + FEEDBACK_CATEGORIES.map((c) => c + '=' + fc.byCategory[c]).join('、')
|
|
8014
|
+
+ '(' + FEEDBACK_CATEGORIES.map((c) => FEEDBACK_CATEGORY_LABELS[c]).join('/') + ')'
|
|
8015
|
+
+ '|按路由:' + FEEDBACK_ROUTES.map((r) => r + '=' + fc.byRoute[r]).join('、'))
|
|
8016
|
+
if (!feedbackOn()) {
|
|
8017
|
+
L.push('- 反馈开关:off(提示词不注入该段;写入请求会被具名拒绝)')
|
|
8018
|
+
} else {
|
|
8019
|
+
const openRows = feedbackEntries().filter((e) => FEEDBACK_OPEN_STATUSES.indexOf(e.status) !== -1)
|
|
8020
|
+
const inter = openRows.filter((e) => e.route === 'interpersonal')
|
|
8021
|
+
L.push('- 未闭环 ' + openRows.length + ' 条' + (inter.length ? '|其中 interpersonal(需评估+事后回填):' + inter.map((e) => e.id + '(by ' + e.by + ')').join('、') : ''))
|
|
8022
|
+
L.push('- 文件镜像:' + FEEDBACK_DIR + '/(按类别分文件)|明细:vibe_v5_feedback {op:\'list\'}')
|
|
8023
|
+
}
|
|
8024
|
+
}
|
|
7697
8025
|
return { ok: true, report: L.join('\n'), quorum: qv }
|
|
7698
8026
|
}
|
|
7699
8027
|
// Adding/removing a PERMANENT researcher is a change to the institute's public
|
|
@@ -7770,6 +8098,8 @@ export function apply(ctx) {
|
|
|
7770
8098
|
maybeQueueVerify, castVerdict, currentVerify, hasVerifyInFlight, startMeeting, quorumM, voterCount,
|
|
7771
8099
|
// final paper (docs/final-paper.md; the phase runs BEFORE finishRun)
|
|
7772
8100
|
startPaper, paperStatus: paperSummary, finalizePaperByOffice,
|
|
8101
|
+
// methodology/collaboration feedback (Shared/Feedback/): the tool handler + the observers
|
|
8102
|
+
feedbackOn, feedbackCounts, feedbackTool,
|
|
7773
8103
|
// authorization helpers (used by tool handlers)
|
|
7774
8104
|
memberIdOfAgent, isOffice, isAcademician, isProvablyOffice, officeCaller, memberById, activeMembers,
|
|
7775
8105
|
// diagnosis helpers (defects 1/2 of the architecture self-test)
|
|
@@ -7893,7 +8223,7 @@ export function apply(ctx) {
|
|
|
7893
8223
|
registerTool('vibe_v5_stop', 'Stop the institute: interrupt every member, clear coordination state, and release their child sessions.', objParams({}), (s, a, x) => withOffice(s, x, 'stop the institute', () => s.initStop()))
|
|
7894
8224
|
registerTool('vibe_v5_status', 'Machine-readable institute status (members, tasks, quorum, meetings, verification, mail).', objParams({}), (s) => s.status())
|
|
7895
8225
|
registerTool('vibe_v5_report', 'Human-readable institute report (staffing, tasks, consensus, meetings, file locations).', objParams({}), (s) => s.report())
|
|
7896
|
-
registerTool('vibe_v5_set', 'Tune institute parameters (persisted in State/<institute>.v5state.json). provider/model override staff LLM routes (empty = inherit the office route). toolAllow/toolDeny restrict PERMANENT staff tools; tempToolAllow/tempToolDeny restrict temp workers. quorumCap sets m = min(quorumCap, voters); an m-vote passes only when at least m Boolean votes (exactly 1 or exactly 0) exist AND no voter returns an opposing Boolean, so with the default roster it degenerates to unanimity among the current voters; only current voters count (a dismissed member\'s earlier ballot is dropped). quorumMode "m-unanimous" (v5) or "all-unanimous" (v4 legacy). formalVerify: "off" (default, no extra requirement) | "encourage" (agents decide by implementation difficulty whether to formalize in Lean; a passing Lean run turns the vote into a FIDELITY review of the Lean statements) | "require" (same, plus a gate: a true/false verdict is withheld as undecided until the object is Lean-passed or has an explicit reasoned blocker record). LEAN TOOLCHAIN: leanCommand names the Lean executable (e.g. "lake" with leanArgs ["env","lean"]); leanArgs are inserted before the file name (the framework appends
|
|
8226
|
+
registerTool('vibe_v5_set', 'Tune institute parameters (persisted in State/<institute>.v5state.json). provider/model override staff LLM routes (empty = inherit the office route). toolAllow/toolDeny restrict PERMANENT staff tools; tempToolAllow/tempToolDeny restrict temp workers. quorumCap sets m = min(quorumCap, voters); an m-vote passes only when at least m Boolean votes (exactly 1 or exactly 0) exist AND no voter returns an opposing Boolean, so with the default roster it degenerates to unanimity among the current voters; only current voters count (a dismissed member\'s earlier ballot is dropped). quorumMode "m-unanimous" (v5) or "all-unanimous" (v4 legacy). formalVerify: "off" (default, no extra requirement) | "encourage" (agents decide by implementation difficulty whether to formalize in Lean; a passing Lean run turns the vote into a FIDELITY review of the Lean statements) | "require" (same, plus a gate: a true/false verdict is withheld as undecided until the object is Lean-passed or has an explicit reasoned blocker record). LEAN TOOLCHAIN: leanCommand names the Lean executable (e.g. "lake" with leanArgs ["env","lean"]); leanArgs are inserted before the file name (the framework appends -R <VibeMath root> unless leanArgs already sets one); leanTimeoutMs is the per-run budget in ms (>=1000, and the per-job budget of the async queue). FINAL PAPER: finalPaper (default true) writes the final paper when the run concludes — the paper phase runs BEFORE the run is marked complete, the permanent staff write their own part, cross-review each other, and the editor named by paperEditor finalises; paperFormat "both"|"md"|"tex"; paperLanguage "zh"|"en"; paperCompilePdf compiles a PDF when a LaTeX engine is detected; paperEditor "academician" (default, the only editor an unattended run can reach) | "office" (manual /v5 paper only — the office must first consult the whole institute: >=1 office message AND >=1 meeting, recorded in the finalisation note); paperLatexCommand forces one engine command instead of auto-detection (empty = auto: xelatex -> latexmk -> pdflatex -> lualatex -> tectonic, English prefers pdflatex). LEAN ASYNC: leanAsync (default true) compiles on a per-session background queue (vibe_v5_lean_run / vibe_v5_lean_archive run=true enqueue and return immediately; inspect them with vibe_v5_lean_job or vibe_v5_lean_lib.jobs and wait with vibe_v5_lean_job {jobId,waitMs}); leanAsync=false restores the previous synchronous behaviour. Only a settled job (exit 0, unchanged content hash AND the same build context) may mark an object passed; a job id is the content+build-context digest. leanInitiative "off"|"normal" (default)|"eager" separates DAILY eagerness about formalizing from formalVerify (which stays the verdict-time requirement). leanSearchPaths (string[]) adds extra compiler search roots before the automatic VibeMath root (deduped; an explicit -R/--root in leanArgs wins). leanJobsMaxParallel (default 1) caps simultaneous background compiles. MATH COMPUTATION: mathComputation "off"|"auto" (default)|"on" gates the math_computation tool; mathMode "typed+shell" (default: the host shell may be used as a fallback, but a shell run carries no receipt and its conclusion must be marked 未经工具归档/not tool-archived) | "typed" (never mention the shell; engine=cli is refused); mathEngines lists the allowed engines (cli is on by default, SageMath is a later phase); mathTimeoutMs is the per-run budget (>=1000); mathPackages are packages a computation may require; mathInstallScope "user" (default) | "system" (per call only, never remembered). Installs are two-step (plan then confirm-token) and commercial engines are never installed. Unknown spellings of these enums fall back to the documented default. feedback (default "on") = the methodology/collaboration feedback library (Shared/Feedback/): "on" records entries and injects a short per-round hint; "off" injects nothing and refuses every write BY NAME (a switch, not a severity).', objParams({
|
|
7897
8227
|
academician: B, academicianLeads: B, memberMayRejectAssign: B, researcherCount: I,
|
|
7898
8228
|
quorumCap: I, quorumMode: S, verdictMaxRounds: I,
|
|
7899
8229
|
maxTempPerMember: I, maxTempTotal: I,
|
|
@@ -7907,6 +8237,7 @@ export function apply(ctx) {
|
|
|
7907
8237
|
mathMode: { type: 'string', enum: ['typed', 'typed+shell'] },
|
|
7908
8238
|
mathEngines: SA, mathTimeoutMs: I, mathPackages: SA,
|
|
7909
8239
|
mathInstallScope: { type: 'string', enum: ['user', 'system'] },
|
|
8240
|
+
feedback: { type: 'string', enum: ['on', 'off'] },
|
|
7910
8241
|
finalPaper: B, paperFormat: { type: 'string', enum: ['both', 'md', 'tex'] },
|
|
7911
8242
|
paperLanguage: { type: 'string', enum: ['zh', 'en'] },
|
|
7912
8243
|
paperCompilePdf: B, paperEditor: { type: 'string', enum: ['office', 'academician'] }, paperLatexCommand: S,
|
|
@@ -7984,6 +8315,7 @@ export function apply(ctx) {
|
|
|
7984
8315
|
})
|
|
7985
8316
|
registerTool('vibe_v5_record_progress', '(member) Append to YOUR progress.md — your research log. Include what you tried, the routes and their obstacles, your current state, your plans, and failed/dead ends (they save the institute from repeating them).', objParams({ content: S }, ['content']), (s, a, x) => s.publishProgress(s.memberIdOfAgent(x), a.content))
|
|
7986
8317
|
registerTool('vibe_v5_record_proposition', '(member) Record a proposition/lemma in your library. REQUIRES value (价值程度), motive (动机用途计划) and p (your probability that it is true).', objParams({ id: S, title: S, statement: S, value: N, motive: S, p: N }, ['statement', 'value', 'motive', 'p']), (s, a, x) => s.recordCard(s.memberIdOfAgent(x), 'proposition', a))
|
|
8318
|
+
registerTool('vibe_v5_feedback', '(member) 工作经验/流程反馈库(Shared/Feedback/,方法论/协作层——不是研究结论)。op=add(写一条,需 category/route/phenomenon/impact/action;route=interpersonal 还必须带 assessment)| update(状态流转+回填结果:id/status/outcome/note)| list(默认只看未闭环,可用 category/route 过滤,all:true 看全部)| summary(按类别/路由计数)。类别:cooperation 合作|management 管理|process 流程|obstacle 障碍|conflict 矛盾。路由:self 自己调整即可|team 组织/工作流同样无需审批|interpersonal 只有这条必须先评估、再事后回填验证。权限:成员/临时工可 add 且只能更新自己发起的条目;所办可更新任何条目。', objParams({ op: S, id: S, category: S, route: S, context: S, phenomenon: S, impact: S, action: S, assessment: S, outcome: S, status: S, note: S, all: B }, ['op']), (s, a, x) => withCaller(s, x, '使用反馈库', (caller) => s.feedbackTool(caller, a)))
|
|
7987
8319
|
registerTool('vibe_v5_record_method', '(member) Record a theory/method/tool in your library. REQUIRES value, motive and p.', objParams({ id: S, title: S, type: S, content: S, notation: S, value: N, motive: S, p: N }, ['content', 'value', 'motive', 'p']), (s, a, x) => s.recordCard(s.memberIdOfAgent(x), 'method', a))
|
|
7988
8320
|
registerTool('vibe_v5_record_subproblem', '(member) Record a sub-problem in your library. REQUIRES value, motive and p.', objParams({ id: S, title: S, statement: S, value: N, motive: S, p: N }, ['statement', 'value', 'motive', 'p']), (s, a, x) => s.recordCard(s.memberIdOfAgent(x), 'subproblem', a))
|
|
7989
8321
|
registerTool('vibe_v5_read_library', '(member) Read anyone\'s library (read-only): their progress and recorded cards. Omit member to read everyone.', objParams({ member: S, kind: S, id: S }), (s, a) => s.readLibrary(a))
|
|
@@ -8029,6 +8361,19 @@ export function apply(ctx) {
|
|
|
8029
8361
|
if (todo.length) parts.push('- 形式化待办(定论被搁置):' + todo.map((t) => t.id).join('、') + '(见 Formal/TODO.md)')
|
|
8030
8362
|
parts.push('- 可复用库:vibe_v5_lean_lib 可列出 Formal/Lib 与 Formal/Proved')
|
|
8031
8363
|
}
|
|
8364
|
+
// Methodology/collaboration feedback: the academician's periodic summary needs the SAME numbers the
|
|
8365
|
+
// report shows, so "what did we learn about working together" is one glance away (and open
|
|
8366
|
+
// interpersonal entries — the only route that needs a written-back verification — are named).
|
|
8367
|
+
parts.push('## 反馈库(方法论/协作层)')
|
|
8368
|
+
{
|
|
8369
|
+
const fc = s.feedbackCounts()
|
|
8370
|
+
parts.push('- 条目 ' + fc.total + '|未闭环 ' + fc.open + '|已闭环 ' + fc.closed
|
|
8371
|
+
+ '|按类别:' + Object.keys(fc.byCategory).map((k) => k + '=' + fc.byCategory[k]).join('、')
|
|
8372
|
+
+ '|按路由:' + Object.keys(fc.byRoute).map((k) => k + '=' + fc.byRoute[k]).join('、'))
|
|
8373
|
+
parts.push(s.feedbackOn()
|
|
8374
|
+
? '- 未闭环明细用 vibe_v5_feedback {op:\'list\'};interpersonal 条目必须回填 outcome 才算闭环'
|
|
8375
|
+
: '- 反馈开关:off(提示词不注入该段;写入请求会被具名拒绝)')
|
|
8376
|
+
}
|
|
8032
8377
|
parts.push('## 停滞提示')
|
|
8033
8378
|
const idleFor = Date.now() - st.lastProgressAt
|
|
8034
8379
|
parts.push('- 距上次实质进展:' + Math.round(idleFor / 1000) + ' 秒')
|
|
@@ -8043,7 +8388,7 @@ export function apply(ctx) {
|
|
|
8043
8388
|
// dynamic registration would depend on a runtime knob and break the effect discipline),
|
|
8044
8389
|
// while the MODE only decides whether the framework TELLS members about them. In 'off'
|
|
8045
8390
|
// mode they still work if a human or agent calls them deliberately.
|
|
8046
|
-
registerTool('vibe_v5_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Parameters: file (required; `Formal/x.lean` or the full CWD-relative path — both name the same file), target (optional object id to record the run against), timeout_ms (optional per-run budget). There is NO run/run=false switch here — run-vs-archive are separate tools; use vibe_v5_lean_archive {run:false} to archive WITHOUT compiling. With leanAsync (default true) the compile is ENQUEUED and this returns immediately with async:{jobId,state} — nothing is compiled yet; the result is durable at Formal/Jobs/<jobId>.json (vibe_v5_lean_job {jobId} reads it), is listed by vibe_v5_lean_lib.jobs, and the initiator also gets ONE 【形式化结果】 announcement in its next round. With leanAsync=false it blocks and returns the compiler output (exitCode/stdout/stderr). Never throws: a host with no subprocess service returns NO_SUBPROCESS and a missing toolchain returns LEAN_NOT_FOUND (in both cases the code can still be written down with vibe_v5_lean_archive), a timeout terminates the process and returns LEAN_TIMEOUT. The framework appends
|
|
8391
|
+
registerTool('vibe_v5_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Parameters: file (required; `Formal/x.lean` or the full CWD-relative path — both name the same file), target (optional object id to record the run against), timeout_ms (optional per-run budget). There is NO run/run=false switch here — run-vs-archive are separate tools; use vibe_v5_lean_archive {run:false} to archive WITHOUT compiling. With leanAsync (default true) the compile is ENQUEUED and this returns immediately with async:{jobId,state} — nothing is compiled yet; the result is durable at Formal/Jobs/<jobId>.json (vibe_v5_lean_job {jobId} reads it), is listed by vibe_v5_lean_lib.jobs, and the initiator also gets ONE 【形式化结果】 announcement in its next round. With leanAsync=false it blocks and returns the compiler output (exitCode/stdout/stderr). Never throws: a host with no subprocess service returns NO_SUBPROCESS and a missing toolchain returns LEAN_NOT_FOUND (in both cases the code can still be written down with vibe_v5_lean_archive), a timeout terminates the process and returns LEAN_TIMEOUT. The framework appends `-R <VibeMath root>` before the file name (unless leanArgs already sets a search root).', objParams({ file: S, target: S, timeout_ms: I }, ['file']), (s, a, x) => withCaller(s, x, 'a Lean run', (caller) => s.leanRunTool(caller, a)))
|
|
8047
8392
|
registerTool('vibe_v5_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (only once the queued compile settles ok) also Verified/Lean/<target>.lean, marking the object Lean-passed. Re-archiving IDENTICAL content is de-duplicated (deduped:true, no rewrite/recompile). kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required). Optional `run` (default true): whether to COMPILE after writing — `run:false` archives the text only (useful when this host has no toolchain; the object then stays `attempted`/unset until something compiles it).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: S, target: S, content: S, from: S, note: S, run: B }, ['kind']), (s, a, x) => withCaller(s, x, 'a Lean archive', (caller) => s.leanArchive(caller, a)))
|
|
8048
8393
|
registerTool('vibe_v5_lean_lib', '(member) List (and by default rebuild) the Lean reuse library: your institute\'s Formal/Index.md (with an import-dependency column), plus the global cross-project Formal/Lib and Formal/Proved indexes, the background-compile jobs (jobs) and the injected search path (paths.searchPath). Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: B }), async (s, a) => {
|
|
8049
8394
|
const r = a && a.refresh === false ? { lib: null, proved: null, objects: Object.keys(s.formalRecords()).length } : await s.rebuildLeanLibIndexes()
|