psyclaw 0.29.12 → 0.29.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@ PsyClaw 是面向社会科学研究的智能体工作台。它把研究项目、
4
4
 
5
5
  PsyClaw 自有代码使用 MIT 许可证;随 npm 包内置的 Academic Research Skills 位于 `vendor/ars`,保持上游署名并单独遵循 CC BY-NC 4.0,仅限非商业用途。
6
6
 
7
- 当前版本:`0.29.12`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
7
+ 当前版本:`0.29.13`。正式命令、用户配置目录和后续发布统一使用 `psyclaw`。
8
8
 
9
9
  ## 发布流程
10
10
 
@@ -21,19 +21,19 @@ RELEASE_MESSAGE="release: describe the change" pnpm release:push
21
21
  需要 Node.js `>=22.19.0`。官方 npm 源:
22
22
 
23
23
  ```powershell
24
- npm install -g psyclaw@0.29.12
24
+ npm install -g psyclaw@0.29.13
25
25
  ```
26
26
 
27
27
  如果本机 npm 配置把 registry 误写成带有 `~/` 的地址,请显式指定官方源:
28
28
 
29
29
  ```bash
30
- npm install -g psyclaw@0.29.12 --registry=https://registry.npmjs.org/
30
+ npm install -g psyclaw@0.29.13 --registry=https://registry.npmjs.org/
31
31
  ```
32
32
 
33
33
  中国大陆网络较慢或无法访问官方源时:
34
34
 
35
35
  ```powershell
36
- npm install -g psyclaw@0.29.12 --registry=https://registry.npmmirror.com
36
+ npm install -g psyclaw@0.29.13 --registry=https://registry.npmmirror.com
37
37
  ```
38
38
 
39
39
  或安装脚本:
@@ -337,23 +337,41 @@
337
337
  .help-grid {
338
338
  display: grid;
339
339
  grid-template-columns: repeat(3, minmax(0, 1fr));
340
- gap: 12px;
341
- margin-bottom: 22px;
340
+ gap: 14px;
341
+ margin-bottom: 28px;
342
342
  }
343
343
  .help-mode-card {
344
+ position: relative;
344
345
  border: 1px solid var(--border-subtle);
345
- background: var(--bg-surface);
346
- border-radius: var(--radius-md);
347
- padding: 14px 16px;
348
- box-shadow: var(--shadow-xs);
346
+ background: linear-gradient(165deg, #ffffff 0%, var(--brand-primary-light) 140%);
347
+ border-radius: 14px;
348
+ padding: 18px 18px 16px;
349
+ overflow: hidden;
350
+ }
351
+ .help-mode-card::before {
352
+ content: "";
353
+ position: absolute;
354
+ left: 0;
355
+ top: 0;
356
+ bottom: 0;
357
+ width: 3px;
358
+ background: var(--brand-primary);
349
359
  }
350
360
  .help-mode-card .mode-id {
351
361
  font-family: var(--font-mono);
352
- font-size: 11px;
353
- color: var(--accent);
354
- letter-spacing: 0.04em;
362
+ font-size: 10px;
363
+ font-weight: 600;
364
+ color: var(--brand-primary);
365
+ letter-spacing: 0.08em;
355
366
  text-transform: uppercase;
356
- margin-bottom: 6px;
367
+ margin-bottom: 8px;
368
+ }
369
+ .help-mode-card .mode-label {
370
+ font-size: 16px;
371
+ font-weight: 650;
372
+ color: var(--text-main);
373
+ letter-spacing: -0.02em;
374
+ margin: 0 0 8px;
357
375
  }
358
376
  .help-mode-card p {
359
377
  margin: 0;
@@ -361,56 +379,164 @@
361
379
  line-height: 1.55;
362
380
  color: var(--text-secondary);
363
381
  }
364
- .help-block {
382
+ .help-hero {
383
+ display: flex;
384
+ align-items: flex-end;
385
+ justify-content: space-between;
386
+ gap: 16px;
365
387
  margin-bottom: 22px;
388
+ padding: 20px 22px;
389
+ border-radius: 16px;
390
+ border: 1px solid var(--brand-primary-border);
391
+ background:
392
+ radial-gradient(120% 80% at 100% 0%, rgba(13, 118, 110, 0.10), transparent 55%),
393
+ linear-gradient(180deg, #ffffff, #f8fafc);
366
394
  }
367
- .help-block h2 {
368
- margin: 0 0 10px;
369
- font-size: 14px;
370
- font-weight: 650;
395
+ .help-hero h1 {
396
+ margin: 0 0 6px;
397
+ font-size: 22px;
398
+ font-weight: 700;
399
+ letter-spacing: -0.03em;
371
400
  color: var(--text-main);
372
401
  }
402
+ .help-hero p {
403
+ margin: 0;
404
+ color: var(--text-muted);
405
+ font-size: 13px;
406
+ max-width: 42rem;
407
+ line-height: 1.5;
408
+ }
409
+ .help-kbd {
410
+ display: inline-flex;
411
+ align-items: center;
412
+ gap: 6px;
413
+ padding: 6px 10px;
414
+ border-radius: 999px;
415
+ border: 1px solid var(--border-subtle);
416
+ background: var(--bg-surface);
417
+ color: var(--text-secondary);
418
+ font-size: 11px;
419
+ font-family: var(--font-mono);
420
+ white-space: nowrap;
421
+ }
422
+ .help-block {
423
+ margin-bottom: 26px;
424
+ }
425
+ .help-block h2 {
426
+ margin: 0 0 12px;
427
+ font-size: 13px;
428
+ font-weight: 700;
429
+ letter-spacing: 0.04em;
430
+ text-transform: uppercase;
431
+ color: var(--text-muted);
432
+ }
373
433
  .help-steps {
374
434
  margin: 0;
375
- padding-left: 1.2rem;
435
+ padding: 0;
436
+ list-style: none;
437
+ display: grid;
438
+ gap: 8px;
439
+ }
440
+ .help-steps li {
441
+ display: grid;
442
+ grid-template-columns: 28px 1fr;
443
+ gap: 12px;
444
+ align-items: start;
445
+ padding: 12px 14px;
446
+ border-radius: 12px;
447
+ border: 1px solid var(--border-subtle);
448
+ background: var(--bg-surface);
376
449
  color: var(--text-secondary);
377
450
  font-size: 13px;
378
- line-height: 1.7;
451
+ line-height: 1.55;
452
+ }
453
+ .help-steps li::before {
454
+ content: attr(data-step);
455
+ width: 28px;
456
+ height: 28px;
457
+ border-radius: 9px;
458
+ display: grid;
459
+ place-items: center;
460
+ background: var(--brand-primary-light);
461
+ color: var(--brand-primary);
462
+ font-family: var(--font-mono);
463
+ font-size: 12px;
464
+ font-weight: 700;
379
465
  }
380
466
  .help-commands {
467
+ display: grid;
468
+ gap: 18px;
469
+ }
470
+ .help-cmd-group h3 {
471
+ margin: 0 0 8px;
472
+ font-size: 12px;
473
+ font-weight: 650;
474
+ color: var(--text-muted);
475
+ letter-spacing: 0.02em;
476
+ }
477
+ .help-cmd-list {
381
478
  display: grid;
382
479
  grid-template-columns: repeat(2, minmax(0, 1fr));
383
480
  gap: 8px;
384
481
  }
385
482
  .help-cmd {
386
- display: flex;
387
- gap: 10px;
388
- align-items: baseline;
483
+ display: grid;
484
+ grid-template-columns: minmax(7.5rem, auto) 1fr;
485
+ gap: 12px;
486
+ align-items: center;
389
487
  border: 1px solid var(--border-subtle);
390
- border-radius: var(--radius-sm);
391
- padding: 10px 12px;
392
- background: var(--bg-surface-subtle);
488
+ border-radius: 12px;
489
+ padding: 11px 13px;
490
+ background: var(--bg-surface);
491
+ transition: border-color 120ms ease, background 120ms ease;
492
+ }
493
+ .help-cmd:hover {
494
+ border-color: var(--brand-primary-border);
495
+ background: var(--brand-primary-light);
393
496
  }
394
497
  .help-cmd code {
395
498
  font-family: var(--font-mono);
396
499
  font-size: 12px;
397
- color: var(--text-main);
500
+ font-weight: 600;
501
+ color: var(--brand-primary);
398
502
  white-space: nowrap;
399
503
  }
400
504
  .help-cmd span {
401
505
  font-size: 12.5px;
402
- color: var(--text-muted);
403
- line-height: 1.45;
506
+ color: var(--text-secondary);
507
+ line-height: 1.4;
404
508
  }
405
509
  .help-notes {
406
510
  margin: 0;
407
- padding-left: 1.2rem;
511
+ padding: 14px 16px;
512
+ list-style: none;
513
+ display: grid;
514
+ gap: 8px;
515
+ border-radius: 14px;
516
+ border: 1px solid var(--border-subtle);
517
+ background: var(--bg-surface-subtle);
518
+ }
519
+ .help-notes li {
520
+ position: relative;
521
+ padding-left: 14px;
408
522
  color: var(--text-muted);
409
523
  font-size: 12.5px;
410
- line-height: 1.65;
524
+ line-height: 1.55;
525
+ }
526
+ .help-notes li::before {
527
+ content: "";
528
+ position: absolute;
529
+ left: 0;
530
+ top: 0.55em;
531
+ width: 6px;
532
+ height: 6px;
533
+ border-radius: 50%;
534
+ background: var(--brand-primary);
535
+ opacity: 0.55;
411
536
  }
412
537
  @media (max-width: 960px) {
413
- .help-grid, .help-commands { grid-template-columns: 1fr; }
538
+ .help-grid, .help-cmd-list { grid-template-columns: 1fr; }
539
+ .help-hero { flex-direction: column; align-items: flex-start; }
414
540
  }
415
541
 
416
542
  .button {
@@ -1561,9 +1687,9 @@
1561
1687
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
1562
1688
  /plan 方案
1563
1689
  </button>
1564
- <button id="cmd-verify-btn" class="topbar-btn" title="关键字段人机核查清单 (/verify)">
1690
+ <button id="cmd-verify-btn" class="topbar-btn" title="AI 核查清单;人审在此核实 (/crosscheck)">
1565
1691
  <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
1566
- /verify 核查
1692
+ /crosscheck
1567
1693
  </button>
1568
1694
  </div>
1569
1695
  <div class="local-badge">
@@ -1809,11 +1935,12 @@
1809
1935
  </section>
1810
1936
 
1811
1937
  <section id="view-help" class="view">
1812
- <div class="page-head">
1938
+ <div class="help-hero">
1813
1939
  <div>
1814
1940
  <h1 id="help-title">使用速览</h1>
1815
- <p id="help-thinking">加载中…</p>
1941
+ <p id="help-subtitle">三种模式 · AI 交叉核验 · 内置学术工作流</p>
1816
1942
  </div>
1943
+ <div id="help-thinking" class="help-kbd">加载中…</div>
1817
1944
  </div>
1818
1945
  <div class="help-grid" id="help-modes"></div>
1819
1946
  <div class="help-block">
@@ -1821,7 +1948,7 @@
1821
1948
  <ol id="help-steps" class="help-steps"></ol>
1822
1949
  </div>
1823
1950
  <div class="help-block">
1824
- <h2>可用命令</h2>
1951
+ <h2>常用命令</h2>
1825
1952
  <div id="help-commands" class="help-commands"></div>
1826
1953
  </div>
1827
1954
  <div class="help-block">
@@ -1834,11 +1961,11 @@
1834
1961
  <div class="page-head">
1835
1962
  <div>
1836
1963
  <h1>交叉核验清单</h1>
1837
- <p>分析前/后由模型提出项,人在此勾选。可跳过,但会标注「未经核对」。本地写入 <code>.psyclaw/verify-checklist.json</code>。</p>
1964
+ <p>AI <code>/crosscheck</code> 核查字段;<strong>analysis / academic 收尾必须在此点「核实」</strong>(或终端 <code>/crosscheck &lt;id&gt; human</code>)。AI 已核与跳过都不算通过。</p>
1838
1965
  </div>
1839
1966
  <div style="display:flex;gap:8px">
1840
1967
  <button id="refresh-checklist" class="button">刷新</button>
1841
- <button id="skip-checklist" class="button" title="其余未核项标为跳过/未经核对">跳过未核项</button>
1968
+ <button id="skip-checklist" class="button" title="标为跳过;不解除 analysis/academic 人审门禁">跳过未核项</button>
1842
1969
  </div>
1843
1970
  </div>
1844
1971
  <div class="panel section">
@@ -1881,7 +2008,7 @@
1881
2008
  <div id="console-log" class="console-log">
1882
2009
  <div class="log-entry sys">
1883
2010
  <div class="log-sys">
1884
- 💡 <strong>对话已就绪</strong>:与 PsyClaw 智能体及底层学术插件安全连接。支持 <code>/init</code>、<code>/plan</code>、<code>/grill</code>、<code>/verify</code> 等研究指令;随时探讨研究设想或分析数据。
2011
+ 💡 <strong>对话已就绪</strong>:与 PsyClaw 智能体安全连接。支持 <code>/init</code>、<code>/plan</code>、<code>/grill</code>、<code>/crosscheck</code>(AI 核验)等指令。
1885
2012
  </div>
1886
2013
  </div>
1887
2014
  </div>
@@ -1891,9 +2018,9 @@
1891
2018
  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
1892
2019
  /plan status
1893
2020
  </span>
1894
- <span class="chip" data-cmd="/verify list">
2021
+ <span class="chip" data-cmd="/crosscheck list">
1895
2022
  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/></svg>
1896
- /verify list
2023
+ /crosscheck list
1897
2024
  </span>
1898
2025
  <span class="chip" data-cmd="/grill">
1899
2026
  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/></svg>
@@ -2035,16 +2162,31 @@ async function loadHelp() {
2035
2162
  try {
2036
2163
  const doc = await json("/api/help");
2037
2164
  $("help-title").textContent = doc.title || "使用速览";
2165
+ $("help-subtitle").textContent = doc.subtitle || "";
2038
2166
  $("help-thinking").textContent = doc.thinking || "";
2039
2167
  $("help-modes").innerHTML = (doc.modes || []).map(mode => `
2040
2168
  <article class="help-mode-card">
2041
- <div class="mode-id">${esc(mode.label || mode.id)}</div>
2169
+ <div class="mode-id">${esc(mode.hint || mode.id)}</div>
2170
+ <div class="mode-label">${esc(mode.label || mode.id)}</div>
2042
2171
  <p>${esc(mode.blurb)}</p>
2043
2172
  </article>
2044
2173
  `).join("");
2045
- $("help-steps").innerHTML = (doc.steps || []).map(step => `<li>${esc(step)}</li>`).join("");
2046
- $("help-commands").innerHTML = (doc.commands || []).map(row => `
2047
- <div class="help-cmd"><code>${esc(row.cmd)}</code><span>${esc(row.blurb)}</span></div>
2174
+ $("help-steps").innerHTML = (doc.steps || []).map((step, index) =>
2175
+ `<li data-step="${index + 1}">${esc(step)}</li>`
2176
+ ).join("");
2177
+ const groups = new Map();
2178
+ for (const row of (doc.commands || [])) {
2179
+ const key = row.group || "其他";
2180
+ if (!groups.has(key)) groups.set(key, []);
2181
+ groups.get(key).push(row);
2182
+ }
2183
+ $("help-commands").innerHTML = [...groups.entries()].map(([group, rows]) => `
2184
+ <div class="help-cmd-group">
2185
+ <h3>${esc(group)}</h3>
2186
+ <div class="help-cmd-list">
2187
+ ${rows.map(row => `<div class="help-cmd"><code>${esc(row.cmd)}</code><span>${esc(row.blurb)}</span></div>`).join("")}
2188
+ </div>
2189
+ </div>
2048
2190
  `).join("");
2049
2191
  $("help-notes").innerHTML = (doc.notes || []).map(note => `<li>${esc(note)}</li>`).join("");
2050
2192
  } catch (error) {
@@ -2091,7 +2233,7 @@ async function loadChecklist() {
2091
2233
 
2092
2234
  $("refresh-checklist")?.addEventListener("click", loadChecklist);
2093
2235
  $("skip-checklist")?.addEventListener("click", async () => {
2094
- if (!confirm("将把所有未核项标为跳过,并写入「未经核对」。继续?")) return;
2236
+ if (!confirm("将把未核/AI已核项标为跳过。注意:这不会解除 analysis/academic 收尾人审门禁。继续?")) return;
2095
2237
  await json("/api/crosscheck", {
2096
2238
  method: "POST",
2097
2239
  headers: { "content-type": "application/json" },
@@ -33,7 +33,7 @@ import { arsRoot } from "../../ars/pi-panel-executor.js";
33
33
  import { ArsModeEditor, isArsModeEditorText } from "../../ars/mode-editor.js";
34
34
  import { MODE_STATUS, nextSessionMode, parseSessionMode, sessionModePrompt, } from "../../session/modes.js";
35
35
  import { continuouslyWorkWarningText, isContinuouslyWorkEnabled, } from "../../session/continuously-work.js";
36
- import { formatVerifyChecklist, isNaturalPlanConfirm, loadVerifyChecklist, markVerifyItem, skipUnverifiedItems } from "../../verify/checklist.js";
36
+ import { assertHumanVerifyGate, formatVerifyChecklist, isNaturalPlanConfirm, loadVerifyChecklist, markVerifyItem, skipUnverifiedItems } from "../../verify/checklist.js";
37
37
  import { formatSessionHelp, formatSessionHelpBrief } from "../../session/help.js";
38
38
  import { openResearchWorkbench } from "../../panel/workbench.js";
39
39
  const PARADIGMS = new Set([
@@ -1291,40 +1291,48 @@ export default function psyclawExtension(pi) {
1291
1291
  return;
1292
1292
  }
1293
1293
  if (parts[0] === "skip") {
1294
- ctx.ui.notify(formatVerifyChecklist(await skipUnverifiedItems(ctx.cwd)), "warning");
1294
+ ctx.ui.notify(`${formatVerifyChecklist(await skipUnverifiedItems(ctx.cwd))}\n注意:skip 不解除 analysis/academic 人审硬门禁。`, "warning");
1295
1295
  return;
1296
1296
  }
1297
1297
  if (parts[0] === "kind") {
1298
1298
  const kind = (parts[1] ?? "general");
1299
1299
  pi.sendUserMessage([
1300
- "交叉核验任务:请作为助手主动提出核对项,不要等用户自己去「申请确认」。",
1300
+ "交叉核验任务:请作为助手主动提出核对项并执行字段核查,不要等用户自己去「申请确认」。",
1301
1301
  `核验类别:${kind}(citations=引文,format=格式,requirements=研究要求,stats=统计结果,general=综合)。`,
1302
- "输出可勾选清单,并建议用户打开 /panel 核对页逐项确认;若用户跳过,必须在产物中标注「未经核对」。",
1302
+ "核查完成后用 /crosscheck <id> verified 记为 AI 已核(ai-checked),不得自称已过人审。",
1303
+ "analysis / academic 模式下,全文或分析收尾前必须请人在 Panel「核实」或 /crosscheck <id> human;跳过不算通过。",
1303
1304
  "分析前与分析后都要有清单,不能默认跳过。",
1304
1305
  ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1305
- ctx.ui.notify(`已启动 ${kind} 交叉核验;也可 /panel 打开核对清单。`, "info");
1306
+ ctx.ui.notify(`已启动 ${kind} 交叉核验;人审请打开 /panel 核对清单。`, "info");
1306
1307
  return;
1307
1308
  }
1308
1309
  const id = parts[0];
1309
- const status = (parts[1] ?? "verified");
1310
- if (status !== "verified" && status !== "unverified" && status !== "flagged" && status !== "skipped") {
1311
- ctx.ui.notify("Usage: /crosscheck list | skip | kind <citations|format|requirements|stats> | <id> verified|unverified|flagged|skipped [备注]", "info");
1310
+ const statusRaw = (parts[1] ?? "verified").toLowerCase();
1311
+ const allowed = new Set(["verified", "ai-checked", "unverified", "flagged", "skipped", "human"]);
1312
+ if (!allowed.has(statusRaw)) {
1313
+ ctx.ui.notify("Usage: /crosscheck list | skip | kind <citations|format|requirements|stats> | <id> verified|ai-checked|human|unverified|flagged|skipped [备注]", "info");
1312
1314
  return;
1313
1315
  }
1314
1316
  const notes = parts.slice(2).join(" ") || undefined;
1315
- const checklist = await markVerifyItem(ctx.cwd, id, status, notes);
1316
- ctx.ui.notify(formatVerifyChecklist(checklist), "info");
1317
+ const source = statusRaw === "human" ? "human" : "ai";
1318
+ const checklist = await markVerifyItem(ctx.cwd, id, statusRaw, notes, undefined, { source });
1319
+ const tip = statusRaw === "human" || (statusRaw === "verified" && source === "human")
1320
+ ? "已记为人审通过。"
1321
+ : statusRaw === "verified" || statusRaw === "ai-checked"
1322
+ ? "已记为 AI 已核;analysis/academic 收尾前仍须人在 Panel 点「核实」或 /crosscheck <id> human。"
1323
+ : undefined;
1324
+ ctx.ui.notify(tip ? `${formatVerifyChecklist(checklist)}\n${tip}` : formatVerifyChecklist(checklist), "info");
1317
1325
  }
1318
1326
  catch (error) {
1319
1327
  await notifyError(ctx, error);
1320
1328
  }
1321
1329
  };
1322
1330
  pi.registerCommand("crosscheck", {
1323
- description: "交叉核验:/crosscheck list|skip|kind <类型>|<id> verified|…",
1331
+ description: "AI 交叉核验 + 人审:/crosscheck list|skip|kind|<id> verified|human|…",
1324
1332
  handler: async (args, ctx) => handleCrosscheck(args, ctx),
1325
1333
  });
1326
1334
  pi.registerCommand("verify", {
1327
- description: "交叉核验(同 /crosscheck):模型提出项,人勾选或 skip 标注未经核对",
1335
+ description: " /crosscheck;AI 核查后 analysis/academic 须人审",
1328
1336
  handler: async (args, ctx) => handleCrosscheck(args, ctx),
1329
1337
  });
1330
1338
  pi.registerCommand("help", {
@@ -1436,6 +1444,11 @@ export default function psyclawExtension(pi) {
1436
1444
  return;
1437
1445
  }
1438
1446
  if (cmd === "handoff") {
1447
+ const gate = await assertHumanVerifyGate(ctx.cwd, "analysis-complete");
1448
+ if (!gate.ok) {
1449
+ ctx.ui.notify(gate.message, "error");
1450
+ return;
1451
+ }
1439
1452
  const path = await syncHandoffFromAnalysisPlan(ctx.cwd, plan);
1440
1453
  if (plan.status === "running") {
1441
1454
  plan = advanceAnalysisPlan(plan, plan.scriptEntrypoint
@@ -1494,6 +1507,19 @@ export default function psyclawExtension(pi) {
1494
1507
  ctx.ui.notify("已启动学术追问。完成后会先展示文档更新建议,并询问是否写回。", "info");
1495
1508
  },
1496
1509
  });
1510
+ if (!legacyTestApi)
1511
+ pi.registerCommand("brainstorm", {
1512
+ description: "显式启动研究方向头脑风暴(逐题澄清)",
1513
+ handler: async (args, ctx) => {
1514
+ const subject = args.trim();
1515
+ pi.sendUserMessage([
1516
+ "使用 academic-grill Skill 的头脑风暴入口,先提出 2-4 个有理论意义、可回答的研究方向,比较价值、可行性与边界并推荐一个。",
1517
+ "随后按研究者需要逐题澄清,每轮只问一个实质问题,同时给出推荐答案和主要取舍;不要直接替用户执行完整研究或写论文。",
1518
+ subject ? `本次头脑风暴主题:${subject}` : "请根据当前对话和项目材料确定头脑风暴主题;已有信息不要重复询问。",
1519
+ ].join("\n"), ctx.isIdle() ? {} : { deliverAs: "followUp" });
1520
+ ctx.ui.notify("已启动研究方向头脑风暴。完成后可用 /grill 继续严格压力测试。", "info");
1521
+ },
1522
+ });
1497
1523
  if (!legacyTestApi)
1498
1524
  pi.registerCommand("review", {
1499
1525
  description: "并发运行多角色模拟同行评审",
@@ -2215,11 +2241,11 @@ export default function psyclawExtension(pi) {
2215
2241
  executionMode: "sequential",
2216
2242
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
2217
2243
  try {
2218
- // Soft gate: missing /init or verify does not hard-block results.
2244
+ // Soft gate for missing /init only. Analysis/academic completion still requires human verify.
2219
2245
  const project = await readActiveProject(ctx.cwd);
2220
2246
  const softNote = project
2221
2247
  ? undefined
2222
- : "尚未 /init(软门禁):继续执行;关键字段请 AI 核查并由人 /verify。建议先 /init 建仓。";
2248
+ : "尚未 /init(软门禁):继续执行;收尾前须 AI /crosscheck 后人审。建议先 /init 建仓。";
2223
2249
  const request = params.request.trim();
2224
2250
  const combined = `${request} ${params.identifier ?? ""}`;
2225
2251
  const explicitWorkflow = params.workflow?.trim();
@@ -2252,6 +2278,13 @@ export default function psyclawExtension(pi) {
2252
2278
  }
2253
2279
  if (publishIntent) {
2254
2280
  try {
2281
+ const gate = await assertHumanVerifyGate(ctx.cwd, "academic-finalize");
2282
+ if (!gate.ok) {
2283
+ return {
2284
+ content: [{ type: "text", text: gate.message }],
2285
+ details: { workflow: "publish-manuscript", blocked: true, reason: "human-verify-gate" },
2286
+ };
2287
+ }
2255
2288
  const result = await exportAcademicDocument(ctx.cwd);
2256
2289
  return {
2257
2290
  content: [{ type: "text", text: JSON.stringify({