@mingmaple/dsh-handover 0.9.22 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +117 -113
  2. package/docs/migrate.md +52 -41
  3. package/docs/releases/0.9.12.md +2 -3
  4. package/docs/releases/0.9.14.md +1 -5
  5. package/docs/releases/0.9.22.md +1 -1
  6. package/docs/releases/1.0.0.md +33 -0
  7. package/docs/troubleshoot.md +221 -61
  8. package/package.json +5 -2
  9. package/scripts/behavior.mjs +398 -0
  10. package/scripts/blank-dsh.mjs +12 -2
  11. package/scripts/blank-smoke.mjs +95 -44
  12. package/scripts/check-docs.mjs +7 -0
  13. package/scripts/portability.mjs +71 -0
  14. package/scripts/style-gate.mjs +571 -0
  15. package/src/client.js +1028 -994
  16. package/src/index.mjs +273 -109
  17. package/src/lib/absrefs.mjs +1 -1
  18. package/src/lib/api.mjs +9 -3
  19. package/src/lib/archive.mjs +1 -1
  20. package/src/lib/brief.mjs +8 -6
  21. package/src/lib/config.mjs +21 -6
  22. package/src/lib/docmap.mjs +5 -8
  23. package/src/lib/drift.mjs +1 -1
  24. package/src/lib/events.mjs +1 -1
  25. package/src/lib/facts.mjs +19 -12
  26. package/src/lib/gates.mjs +12 -12
  27. package/src/lib/global-task.mjs +69 -0
  28. package/src/lib/guide.mjs +80 -75
  29. package/src/lib/handover-io.mjs +69 -6
  30. package/src/lib/identity.mjs +330 -0
  31. package/src/lib/legacy.mjs +89 -0
  32. package/src/lib/lint.mjs +14 -11
  33. package/src/lib/manual.mjs +117 -80
  34. package/src/lib/mover.mjs +18 -14
  35. package/src/lib/office.mjs +42 -2
  36. package/src/lib/pack-ops.mjs +10 -10
  37. package/src/lib/pathkey.mjs +24 -0
  38. package/src/lib/phase.mjs +8 -13
  39. package/src/lib/profile.mjs +1 -1
  40. package/src/lib/reminder.mjs +110 -0
  41. package/src/lib/role.mjs +1 -1
  42. package/src/lib/scaffold.mjs +30 -10
  43. package/src/lib/schedule.mjs +5 -5
  44. package/src/lib/session-create.mjs +2 -2
  45. package/src/lib/session-ops.mjs +76 -47
  46. package/src/lib/sessions.mjs +40 -2
  47. package/src/lib/sevenzip.mjs +10 -1
  48. package/src/lib/status.mjs +337 -336
  49. package/src/lib/succession.mjs +14 -14
  50. package/src/lib/system.mjs +364 -0
  51. package/src/templates/Changelog.skeleton.md +3 -3
  52. package/src/templates/Charter.skeleton.md +13 -9
  53. package/src/templates/Handover.global.skeleton.md +20 -13
  54. package/src/templates/Handover.template.md +89 -0
  55. package/src/templates/Plan.skeleton.md +34 -0
  56. package/src/templates/Prompt_Templates.md +91 -10
  57. package/src/templates/Runtime_Manual.md +254 -139
  58. package/src/templates/System.skeleton.md +31 -0
  59. package/src/templates/Systems.global.skeleton.md +13 -0
  60. package/src/templates/rules/Delegation_Rules.md +45 -0
  61. package/src/templates/rules/Handover_Rules.md +46 -24
  62. package/src/templates/rules/Migration_Rules.md +7 -7
  63. package/src/templates/rules/Misc_Task_Standard.md +17 -10
  64. package/src/templates/rules/Writing_Rules.md +33 -16
  65. package/src/templates/standards/README.md +19 -7
  66. package/src/templates/standards//345/206/231/344/275/234/350/247/204/350/214/203/README.md +120 -0
  67. package/src/templates/standards//345/206/231/344/275/234/350/247/204/350/214/203//344/276/235/346/215/256_/350/256/272/346/226/207.md +389 -0
  68. package/src/tools/ho_backup.mjs +8 -8
  69. package/src/tools/ho_changelog.mjs +4 -4
  70. package/src/tools/ho_charter.mjs +7 -6
  71. package/src/tools/ho_checkpoint.mjs +34 -31
  72. package/src/tools/ho_docmap.mjs +135 -49
  73. package/src/tools/ho_gate.mjs +9 -4
  74. package/src/tools/ho_handover.mjs +61 -35
  75. package/src/tools/ho_office.mjs +45 -4
  76. package/src/tools/ho_profile.mjs +3 -3
  77. package/src/tools/ho_sessions.mjs +43 -22
  78. package/src/tools/ho_status.mjs +14 -9
  79. package/src/tools/ho_succeed.mjs +3 -3
  80. package/src/tools/ho_workspace.mjs +83 -51
  81. package/src/tools/peer.mjs +219 -8
@@ -99,7 +99,7 @@ export function renderAbsoluteRefs(res, opts) {
99
99
  if (!res.totalHits) {
100
100
  out.push(' 未发现写死的本机绝对路径');
101
101
  } else {
102
- out.push(' 发现 ' + res.hits.length + ' 个文件里有 ' + res.totalHits + ' 处绝对路径(**插件不改写这些内容**):');
102
+ out.push(' 发现 ' + res.hits.length + ' 个文件中有 ' + res.totalHits + ' 处绝对路径(插件不改写这些内容):');
103
103
  for (const h of res.hits.slice(0, o.max || 10)) {
104
104
  out.push(' ' + h.path);
105
105
  for (const it of h.items) out.push(' L' + it.line + ' [' + it.kind + '] ' + it.sample);
package/src/lib/api.mjs CHANGED
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import fs from 'node:fs';
7
7
  import path from 'node:path';
8
+ import { pathKey } from './pathkey.mjs';
8
9
  import { isDir } from './text.mjs';
9
10
  import { buildStatus } from './status.mjs';
10
11
  import { createGlobalWorkspace } from './scaffold.mjs';
@@ -79,8 +80,12 @@ async function createGlobalSession(env, rootPath, title) {
79
80
  try {
80
81
  const reg = env.registry && env.registry();
81
82
  const list = reg && typeof reg.list === 'function' ? (reg.list() || []) : [];
82
- const norm = (s) => String(s || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
83
- const hit = list.find((w) => w && norm(w.path) === norm(rootPath));
83
+ const norm = (s) => pathKey(String(s || ''));
84
+ const same = (a, b) => {
85
+ if (norm(a) === norm(b)) return true;
86
+ try { return norm(fs.realpathSync(String(a))) === norm(fs.realpathSync(String(b))); } catch { return false; }
87
+ };
88
+ const hit = list.find((w) => w && same(w.path, rootPath));
84
89
  if (hit) workspaceId = String(hit.id || '');
85
90
  } catch { workspaceId = ''; }
86
91
  const made = await newSessionIn(env, {
@@ -157,7 +162,7 @@ export function createApi(env, info) {
157
162
  if (!st || !st.isDirectory()) return bad('bad-request', '目录不存在或不是目录:' + target);
158
163
  const reg = env.registry && env.registry();
159
164
  if (!reg || typeof reg.create !== 'function') {
160
- return { ok: false, error: { code: 'no-registry', message: 'workspaceRegistry 不可用', details: { hint: '请手工把这个路径粘进 DSH 的新建工作区:' + target } }, http: 501 };
165
+ return { ok: false, error: { code: 'no-registry', message: 'workspaceRegistry 不可用', details: { hint: '请手工将该路径粘贴到 DSH 的新建工作区:' + target } }, http: 501 };
161
166
  }
162
167
  const title = String(p.title || '').trim() || target.split(/[\\/]/).filter(Boolean).pop() || target;
163
168
  try {
@@ -203,6 +208,7 @@ export function createApi(env, info) {
203
208
  project: p.project,
204
209
  task: p.task,
205
210
  wake: p.wake !== false,
211
+ migrate: p.migrate === 'direct' ? 'direct' : 'auto',
206
212
  });
207
213
  env.log('会话角色:' + (r.ok ? (r.role + ' · 存活=' + r.live + ' · 已投递=' + r.delivered) : ('失败 ' + r.error)));
208
214
  invalidateStatus(env);
@@ -188,7 +188,7 @@ export function extractTarGz(file, destDir) {
188
188
  dirs++;
189
189
  return;
190
190
  }
191
- if (e.type === '1' || e.type === '2') { skipped.push('链接条目不解:' + rel); return; }
191
+ if (e.type === '1' || e.type === '2') { skipped.push('链接条目不处理:' + rel); return; }
192
192
  if (e.type !== '0' && e.type !== '') { skipped.push('未知条目类型 ' + e.type + ':' + rel); return; }
193
193
  fs.mkdirSync(path.dirname(dest), { recursive: true });
194
194
  fs.writeFileSync(dest, e.body);
package/src/lib/brief.mjs CHANGED
@@ -41,13 +41,15 @@ export function composeBrief(input) {
41
41
  out.push('水位 无法读取,需人工判断');
42
42
  } else {
43
43
  const pct = (input.selfUsed / input.selfWin) * 100;
44
- const warn = cfg.warnRatio === undefined ? 0.32 : cfg.warnRatio;
45
- out.push("水位 " + pct.toFixed(1) + "%(" + thousands(input.selfUsed) + " / " + thousands(input.selfWin) + ")→ " + (pct >= warn * 100 ? "已过阈值,该写交接" : "可继续"));
44
+ const r1 = (cfg.remindRatio === undefined ? 0.35 : cfg.remindRatio) * 100;
45
+ const r2 = (cfg.remind2Ratio === undefined ? 0.5 : cfg.remind2Ratio) * 100;
46
+ const tail = pct >= r2 ? '已过 50% 档,完成当前小步骤后停止' : pct >= r1 ? '已过 35% 档,应收束并刷新交接' : '可继续';
47
+ out.push("水位 " + pct.toFixed(1) + "%(" + thousands(input.selfUsed) + " / " + thousands(input.selfWin) + ")→ " + tail);
46
48
  }
47
49
  const gate = input.gate;
48
50
  if (!gate || gate.error) {
49
51
  out.push('闸门 ' + (gate && gate.error ? gate.error : '读不到') + ' · 退出码 ? · ?');
50
- if (gate && gate.noWorkspace) out.push(' 首次安装?把工作区接入即可:ho_workspace(action="create", path="<绝对目录>", title="全局"),再开一个标题含「全局」的会话。');
52
+ if (gate && gate.noWorkspace) out.push(' 尚未接入工作区:用 ho_workspace(action="create", path="<绝对目录>", title="全局") 接入,再新建一个标题含「全局」的会话。');
51
53
  } else {
52
54
  out.push("闸门 " + gate.total + " 库 · 正文 " + gate.bodyLines + " 行 · 退出码 " + (gate.ok ? 0 : 10) + " · 判定:" + gate.verdict);
53
55
  }
@@ -58,8 +60,8 @@ export function composeBrief(input) {
58
60
  const notifProjects = new Set(scan.notify.map((r) => r.project));
59
61
  const skipProjects = new Set(scan.skip.map((r) => r.project));
60
62
  out.push("活跃 " + notifProjects.size + " 个项目,需要通知 " + scan.notify.length + " 个会话:" + (scan.notify.length ? scan.notify.map((r) => r.project + "(" + sidShort(r.sid) + ",在任)").join("、") : "无"));
61
- out.push("沉默 " + skipProjects.size + " 个项目,跳过 " + scan.skip.length + " 个会话(不唤醒,省 token)");
62
- if (scan.hot.length) out.push("预警 " + scan.hot.length + " 个会话已进预警区(≥" + Math.round(scan.warn) + "%)—— 全为已交出的旧会话则可忽略");
63
+ out.push("沉默 " + skipProjects.size + " 个项目,跳过 " + scan.skip.length + " 个会话(不唤醒,以节省 token)");
64
+ if (scan.hot.length) out.push("预警 " + scan.hot.length + " 个会话已过 35% 线,全为已交出的旧会话时可忽略");
63
65
  }
64
66
  if (input.drift) {
65
67
  out.push('Δ交接 ' + driftVerdict(input.drift));
@@ -69,6 +71,6 @@ export function composeBrief(input) {
69
71
  out.push('Δ交接 读不到(会话日志不可用)');
70
72
  }
71
73
  if (input.advice && input.advice.text) out.push('阶段 ' + input.advice.text);
72
- out.push('提示 ' + (input.ok ? '按「活跃」名单逐个用 peer(action="send")广播本会话 id 并记回 §3;可正常开始作业' : '本会话已过阈值或闸门异常,应先收齐交接,再开展新任务'));
74
+ out.push('提示 ' + (input.ok ? '按「活跃」名单逐个用 peer(action="send")广播本会话 id,并记回 §3;可正常开始作业' : '本会话已过 35% 线或闸门异常,应先收齐交接,再开展新任务'));
73
75
  return out;
74
76
  }
@@ -1,8 +1,12 @@
1
1
  /** dsh-handover 配置:全部可被 profile patch 的 config 覆盖。 */
2
2
  export const DEFAULTS = {
3
3
  workspaceRoot: '', // 留空则从会话 cwd 推导(向上找 _Control_Center 目录)
4
- warnRatio: 0.32,
5
- handoverRatio: 0.35,
4
+ remindRatio: 0.35, // 35% 一次性后台提醒(不再设 32% 提醒线)
5
+ remind2Ratio: 0.50, // 50% 一次性后台提醒
6
+ margin1: 0.45, // 35% 档判断余量:完成剩余流程后不超过 45%
7
+ margin2: 0.55, // 50% 档判断余量:完成剩余流程后不超过 55%
8
+ pureWindowRatio: 0.40, // 纯窗口线:繁重任务预计超过 40% 时引入子智能体
9
+ handoverRatio: 0.35, // 35% 后可压缩;更早仅助手建议或用户主动并授权
6
10
  hardRatio: 0.85,
7
11
  maxCompactions: 3,
8
12
  autoResume: true,
@@ -11,8 +15,19 @@ export const DEFAULTS = {
11
15
  rolesPath: '', // 留空 = ~/.dsh/storages/dsh-handover.roles.json
12
16
  // 续跑提示:{n} {max} {handover} {role} 会被替换成真值;{handover} 是本会话自己的交接绝对路径。
13
17
  // 只在助手登记压缩时选 resume=continue(默认)才投递;resume=wait 表示已收尾,不唤醒。
14
- resumePrompt: '【dsh-handover 自动续跑·第 {n}/{max} 次压缩】压缩已完成,历史已被摘要替换。第一步:ho_handover(action=read) 取回本会话的交接({handover});然后按 §2「下一步」继续(若 §2 已无待办,向当前用户一句话收尾并停止)。不要复述交接内容,不要重做已完成的事。完整流程:ho_charter(doc="runtime")。',
15
- resumePromptUnknown: '【dsh-handover 自动续跑·第 {n}/{max} 次压缩】压缩已完成,历史已被摘要替换。本会话是普通会话,不参与交接;如需继续,请由全局会话为其指定角色。',
18
+ resumePrompt: [
19
+ '【dsh-handover 自动续跑·第 {n}/{max} 次压缩】压缩已完成,历史已由摘要替换。',
20
+ '按以下顺序执行:',
21
+ '一、调用 ho_handover(action=read),取回本会话交接({handover})。',
22
+ '二、调用 ho_docmap(action="plan", mode="read"),读取行动计划;未建立计划时跳过本步。',
23
+ '三、按交接 §2 与行动计划继续执行;§2 无待办时,向当前用户作一句总结后收尾并停止。',
24
+ '约束:不复述交接内容;不重做已完成事项。',
25
+ '完整流程:ho_charter(doc="runtime")。',
26
+ ].join(String.fromCharCode(10)),
27
+ resumePromptUnknown: [
28
+ '【dsh-handover 自动续跑·第 {n}/{max} 次压缩】压缩已完成,历史已由摘要替换。',
29
+ '本会话为普通会话,不参与交接;如需继续,由全局会话为其指定角色。',
30
+ ].join(String.fromCharCode(10)),
16
31
  compactTimeoutMs: 120000,
17
32
  compactAttempts: 3,
18
33
  waitIdleMs: 180000, // 等会话空闲的上限(超过就放弃并回告)
@@ -20,10 +35,10 @@ export const DEFAULTS = {
20
35
  wakeAuthority: 'on', // 会话守卫硬拦:on / off(off=连 peer 的 new 动作也不拦)
21
36
  directPeer: 'on', // 会话之间直接 peer(action="send"):on 允许(不经全局转述);off 退回「只能向全局报告」的旧口径
22
37
  roleGating: 'on', // 按角色裁剪工具与提示段:on / off(off=所有会话都看得见全部工具)
23
- reuseCheck: true, // 建新会话前查在任会话水位(低于 warnRatio 拒绝)
38
+ reuseCheck: true, // 建新会话前查在任会话水位(低于 handoverRatio 拒绝,建议复用)
24
39
  prepackMinAgeMin: 90, // 打包前要求会话静止分钟数
25
40
  packFormat: 'auto', // 归档格式:auto(有 7-Zip 用 7z,否则内置 tar.gz)、7z、tar
26
- sevenZipPath: '', // 留空则自动探测 C:\Program Files\7-Zip\7z.exe
41
+ sevenZipPath: '', // 留空则自动探测:常见安装位置(Windows、macOS、Linux)与 PATH 上的 7z
27
42
  };
28
43
 
29
44
  export function resolveConfig(raw) {
@@ -1,9 +1,6 @@
1
1
  /**
2
- * 文档定位 map(docmap):每个会话(全局、每个项目、每个杂项任务)一份文件,写「本类别有哪些文档、各自一句话介绍、什么时候该读」。
3
- *
4
- * 定位:只指路,不规定读多少。是否读正文由被注入的会话自己判断;
5
- * 空 map 不注入,提示段其余文字一字不动。文件放在 _Control_Center/records/docmap/,
6
- * 随工作区迁移;写入者必须是在任会话(由 tools/ho_docmap.mjs 用在任登记表判定)。
2
+ * 旧版文档定位(docmap)解析:1.0 起只由迁移兼容模块 `legacy.mjs` 使用,用于把旧表并入体系注册。
3
+ * 新写入一律走 `system.mjs`;本模块不再参与提示段注入。
7
4
  */
8
5
  import fs from 'node:fs';
9
6
  import path from 'node:path';
@@ -81,7 +78,7 @@ export function renderDocmap(info, entries) {
81
78
  const lines = ['# 文档定位(' + label + ')', '', '| ' + COLUMNS.join(' | ') + ' |', '|' + COLUMNS.map(() => '---').join('|') + '|'];
82
79
  for (const e of entries) lines.push('| `' + e.path + '` | ' + e.about + ' | ' + (e.when || '') + ' |');
83
80
  lines.push('');
84
- lines.push('只指路:是否读正文由你判断,不要无目的通读。');
81
+ lines.push('只指路:是否读正文由会话判断,不得无目的通读。');
85
82
  return lines.join(NL) + NL;
86
83
  }
87
84
 
@@ -92,7 +89,7 @@ export function renderDocmap(info, entries) {
92
89
  export function docmapBlock(ws, info) {
93
90
  const map = readDocmap(ws, info);
94
91
  if (!map.entries.length) return '';
95
- const lines = ['文档定位(' + lineageLabel(info) + ';只指路,是否读正文由你判断,不要无目的通读)'];
92
+ const lines = ['文档定位(' + lineageLabel(info) + ';只指路,是否读正文由会话判断,不得无目的通读)'];
96
93
  for (const e of map.entries) {
97
94
  lines.push('- ' + e.path + (e.about ? ' —— ' + e.about : '') + (e.when ? '(' + e.when + ')' : ''));
98
95
  }
@@ -103,7 +100,7 @@ export function docmapBlock(ws, info) {
103
100
  export function pathProblem(ws, p) {
104
101
  const raw = String(p || '').trim();
105
102
  if (!raw) return '路径为空';
106
- if (/^[a-zA-Z]:/.test(raw) || raw.startsWith('/') || raw.startsWith('\\')) return '要写工作区相对路径,不写绝对路径';
103
+ if (/^[a-zA-Z]:/.test(raw) || raw.startsWith('/') || raw.startsWith('\\')) return '须写工作区相对路径,不得写绝对路径';
107
104
  const norm = path.normalize(raw).replace(/\\/g, '/');
108
105
  if (norm === '..' || norm.startsWith('../')) return '不得指向工作区之外';
109
106
  const abs = path.join(String(ws || ''), norm);
package/src/lib/drift.mjs CHANGED
@@ -61,5 +61,5 @@ export function computeDrift(opts) {
61
61
  export function driftVerdict(res) {
62
62
  return '判定:' + res.fresh + ' 个已最新、' + res.need + ' 个需刷交接' +
63
63
  (res.unknown ? '、' + res.unknown + ' 个需人工确认' : '') +
64
- (res.need || res.unknown ? ';先让需刷的会话写交接,再搬' : ';可以直接搬');
64
+ (res.need || res.unknown ? ';先令需刷交接的会话写交接,再迁移' : ';可直接迁移');
65
65
  }
@@ -85,5 +85,5 @@ export function stalePressureNote(sessionId) {
85
85
  if (!st.lastSummaryMs) return '';
86
86
  const m = projCacheMtimeOf(sessionId);
87
87
  if (m === null || m >= st.lastSummaryMs) return '';
88
- return '(压缩刚完成,投影缓存尚未刷新,水位读数滞后一轮;下一次请求后自动正常)';
88
+ return '(压缩刚完成,投影缓存尚未刷新,水位读数滞后一轮;下一次请求后恢复正常)';
89
89
  }
package/src/lib/facts.mjs CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { DEFAULTS } from './config.mjs';
7
7
 
8
- /** 比例转百分比文本:0.32 → "32%"。 */
8
+ /** 比例转百分比文本:0.35 → "35%"。 */
9
9
  function pctOf(ratio) {
10
10
  const n = Math.round(Number(ratio) * 1000) / 10;
11
11
  return (Number.isInteger(n) ? String(n) : n.toFixed(1)) + '%';
@@ -14,24 +14,31 @@ function pctOf(ratio) {
14
14
  /** 水位与压缩口径:全部由配置推导。 */
15
15
  export function watermark(cfg) {
16
16
  const c = cfg || DEFAULTS;
17
- const warn = pctOf(c.warnRatio !== undefined ? c.warnRatio : DEFAULTS.warnRatio);
18
- const handover = pctOf(c.handoverRatio !== undefined ? c.handoverRatio : DEFAULTS.handoverRatio);
19
- const hard = pctOf(c.hardRatio !== undefined ? c.hardRatio : DEFAULTS.hardRatio);
20
- const max = c.maxCompactions !== undefined ? c.maxCompactions : DEFAULTS.maxCompactions;
21
- return { warn, handover, hard, max, warnValue: Math.round((c.warnRatio !== undefined ? c.warnRatio : DEFAULTS.warnRatio) * 100) };
17
+ const pick = (k) => (c[k] !== undefined ? c[k] : DEFAULTS[k]);
18
+ const remind = pctOf(pick('remindRatio'));
19
+ const remind2 = pctOf(pick('remind2Ratio'));
20
+ const handover = pctOf(pick('handoverRatio'));
21
+ const hard = pctOf(pick('hardRatio'));
22
+ const max = pick('maxCompactions');
23
+ return {
24
+ remind, remind2, handover, hard, max,
25
+ remindValue: Math.round(pick('remindRatio') * 100),
26
+ remind2Value: Math.round(pick('remind2Ratio') * 100),
27
+ reuseValue: Math.round(pick('handoverRatio') * 100),
28
+ };
22
29
  }
23
30
 
24
31
  /** 一句式交接规则(全局与项目提示段共用)。 */
25
32
  export function handoverRuleText(cfg) {
26
33
  const w = watermark(cfg);
27
- return '水位 ' + w.warn + ' 提醒,' + w.handover + ' 交接线,' + w.hard + ' 硬上限;至多压缩 '
34
+ return w.handover + ' 后可压缩,' + w.hard + ' 为硬上限;至多压缩 '
28
35
  + w.max + ' 次,第 ' + (w.max + 1) + ' 次换任。';
29
36
  }
30
37
 
31
38
  /** 设置页使用说明里的紧写形式。 */
32
39
  export function compactSummaryText(cfg) {
33
40
  const w = watermark(cfg);
34
- return w.warn + ' 提醒、' + w.handover + ' 交接线、' + w.hard + ' 硬上限;同一会话最多压缩 ' + w.max + ' 次';
41
+ return w.remind + ' 与 ' + w.remind2 + ' 各提醒一次,' + w.handover + ' 后可压缩,' + w.hard + ' 硬上限;同一会话最多压缩 ' + w.max + ' 次';
35
42
  }
36
43
 
37
44
  /** 提示段与工具描述的加权 token 估算:CJK 与全角标点每字符 1 token,其余每 4 字符 1 token。 */
@@ -63,9 +70,9 @@ function isWide(cp) {
63
70
  * 提示词效果优先于字数,实测值超出参考值时如实展示即可。
64
71
  */
65
72
  export const TOKEN_BUDGET = {
66
- globalTokens: 320,
67
- globalWorstTokens: 360,
68
- projectTokens: 300,
69
- projectWorstTokens: 340,
73
+ globalTokens: 900,
74
+ globalWorstTokens: 1000,
75
+ projectTokens: 800,
76
+ projectWorstTokens: 900,
70
77
  toolDescTokens: 70,
71
78
  };
package/src/lib/gates.mjs CHANGED
@@ -4,7 +4,7 @@ import { readUtf8, isFile, isDir } from './text.mjs';
4
4
  import fs from 'node:fs';
5
5
  import { charterPath, canonicalBlock, TITLE_KEY, WATER_KEY } from './charter.mjs';
6
6
  import { readProfile, listProfiles } from './profile.mjs';
7
- import { checkDocmaps } from './docmap.mjs';
7
+ import { checkRegistries } from './system.mjs';
8
8
 
9
9
  /**
10
10
  * 读 Registry.md 的「形态」列 → { 项目名: 形态 }。
@@ -87,7 +87,7 @@ export function syncCheck(ws) {
87
87
  builtin = true;
88
88
  } else {
89
89
  return {
90
- error: '这个实例还没接入工作区:读不到 ' + charter,
90
+ error: '本实例尚未接入工作区:读不到 ' + charter,
91
91
  path: charter,
92
92
  ok: false,
93
93
  noWorkspace: true,
@@ -95,7 +95,7 @@ export function syncCheck(ws) {
95
95
  }
96
96
  const blk = canonicalBlock(text);
97
97
  if (!blk || !blk.body || blk.body.length === 0) {
98
- return { error: '无法获取 §8.1.1.4 的正文(结构可能变了)', path: charter, ok: false };
98
+ return { error: '无法取得 §8.1.1.4 的正文(结构可能已变更)', path: charter, ok: false };
99
99
  }
100
100
  const rows = [];
101
101
  let bad = 0;
@@ -119,19 +119,19 @@ export function syncCheck(ws) {
119
119
  verdict: bad === 0
120
120
 
121
121
  ? "全部落位。"
122
- : bad + ' 个库有缺 —— 向对应项目会话派发(由它自己写进本库 RULES.md,全局不得改项目库内文件)。',
122
+ : bad + ' 个库有缺,向对应项目会话派发(由该会话写进本库 RULES.md;全局不得修改项目库内文件)。',
123
123
  };
124
124
  }
125
125
 
126
126
  export function renderSyncCheck(res) {
127
127
  const out = [];
128
128
  if (res.error) { out.push(res.error + ':' + res.path); return out; }
129
- out.push('通用约束条同步复核 库数 ' + res.total + ' 正文 ' + res.bodyLines + ' 行 来源 §8.1.1.4' + (res.builtin ? '(工作区没有 Workspace_Charter.md,用插件内置口径)' : ''));
129
+ out.push('通用约束条同步复核 库数 ' + res.total + ' 正文 ' + res.bodyLines + ' 行 来源 §8.1.1.4' + (res.builtin ? '(工作区无 Workspace_Charter.md,采用插件内置口径)' : ''));
130
130
  for (const r of res.libraries) {
131
131
  const flag = r.ok ? '通过' : '未通过';
132
132
  out.push(' ' + r.name + ' 标题 ' + (r.titleOk ? '通过' : '缺') + ' 正文 ' + (r.total - r.missing.length) + '/' + r.total +
133
133
  ' 水位自查 ' + (r.waterOk ? '通过' : '缺') + ' ' + flag);
134
- for (const m of r.missing.slice(0, 3)) out.push(" 缺: " + m.slice(0, 70));
134
+ for (const m of r.missing.slice(0, 3)) out.push(" 缺:" + m.slice(0, 70));
135
135
  }
136
136
  const ps = res.profiles || [];
137
137
  if (ps.length) {
@@ -148,25 +148,25 @@ export function renderSyncCheck(res) {
148
148
  return out;
149
149
  }
150
150
 
151
- /** 文档定位 map:只检查表内路径是否仍然存在(不判字数、不判内容)。 */
151
+ /** 体系注册:只检查表内路径是否仍然存在(不判字数、不判内容)。 */
152
152
  export function docmapReport(ws) {
153
- return checkDocmaps(ws);
153
+ return checkRegistries(ws);
154
154
  }
155
155
 
156
156
  export function renderDocmapCheck(res) {
157
157
  const out = [];
158
- out.push('文档定位 map 目录 ' + res.dir);
158
+ out.push('体系注册 任务 ' + res.rows.length + ' 个');
159
159
  if (!res.rows.length) {
160
- out.push(' 还没有任何 map(空 map 不注入提示段,属正常)');
160
+ out.push(' 尚无任何任务注册(空注册不注入正文,属正常)');
161
161
  out.push('判定:无需检查');
162
162
  return out;
163
163
  }
164
164
  for (const r of res.rows) {
165
- out.push(' ' + r.name + ' ' + r.label + ' ' + r.entries + ' 条 ' + (r.bad.length ? '失效 ' + r.bad.length + ' 条' : '路径全部存在'));
165
+ out.push(' ' + r.label + ' ' + r.rel + ' ' + (r.exists ? r.entries + ' 条' : '未设置') + ' ' + (r.bad.length ? '失效 ' + r.bad.length + ' 条' : '路径全部存在'));
166
166
  for (const b of r.bad.slice(0, 3)) out.push(' 失效:' + b.path + ' —— ' + b.problem);
167
167
  }
168
168
  out.push('判定:' + (res.missing === 0
169
169
  ? '全部落位。'
170
- : res.missing + ' 条路径失效 —— 由该类别在任会话用 ho_docmap(action="write") 更新。'));
170
+ : res.missing + ' 条路径失效,由该类别在任会话用 ho_docmap(action="write") 更新。'));
171
171
  return out;
172
172
  }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * 全局任务库:`_Control_Center/tasks/<任务>/`。
3
+ * 全局可同时承担多个任务,每个任务有独立交接、体系注册与计划;任务可整体独立为项目或杂项会话。
4
+ */
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { NL, readUtf8, isFile, isDir } from './text.mjs';
8
+ import { assertTaskName } from './role.mjs';
9
+ import { renderRegistry, syncGlobalRefs } from './system.mjs';
10
+
11
+ const HANDOVER = [
12
+ '# 交接(全局任务)',
13
+ '',
14
+ '## 1. 正在做的事',
15
+ '',
16
+ '未开始;由全局会话在任务启动时补写。',
17
+ '',
18
+ '## 2. 下一步',
19
+ '',
20
+ '- 待定。',
21
+ '',
22
+ ].join(NL);
23
+
24
+ function copyIf(src, dst) {
25
+ if (!isFile(src) || fs.existsSync(dst)) return false;
26
+ fs.mkdirSync(path.dirname(dst), { recursive: true });
27
+ fs.copyFileSync(src, dst);
28
+ return true;
29
+ }
30
+
31
+ /**
32
+ * 建立全局任务库:目录、交接、体系注册、规则与模板副本;随后重建全局总注册。
33
+ * @returns {{ dir: string, created: string[] }}
34
+ */
35
+ export function createGlobalTask(ws, task) {
36
+ const name = String(task || '').trim();
37
+ assertTaskName(name);
38
+ const dir = path.join(String(ws || ''), '_Control_Center', 'tasks', name);
39
+ if (isDir(dir) && fs.readdirSync(dir).length) throw new Error('全局任务已存在且非空:' + dir);
40
+ fs.mkdirSync(path.join(dir, '_archive'), { recursive: true });
41
+ const created = [];
42
+ const files = [
43
+ ['Handover.md', HANDOVER],
44
+ ['System.md', renderRegistry({ role: 'global-task', task: name }, { structure: [], files: [], plan: '' })],
45
+ ];
46
+ for (const [rel, body] of files) {
47
+ const dst = path.join(dir, rel);
48
+ if (fs.existsSync(dst)) continue;
49
+ fs.writeFileSync(dst, body, 'utf8');
50
+ created.push(rel);
51
+ }
52
+ // 暂任规范与交接规范随任务库独立,避免暂任方跨库读取全局规则。
53
+ const rules = path.join(String(ws || ''), '_Control_Center', 'rules');
54
+ for (const rel of ['Delegation_Rules.md', 'Handover_Rules.md']) {
55
+ if (copyIf(path.join(rules, rel), path.join(dir, rel))) created.push(rel);
56
+ }
57
+ const tpl = path.join(String(ws || ''), '_Control_Center', 'templates');
58
+ for (const rel of ['Plan.skeleton.md', 'System.skeleton.md']) {
59
+ if (copyIf(path.join(tpl, rel), path.join(dir, rel))) created.push(rel);
60
+ }
61
+ syncGlobalRefs(ws);
62
+ return { dir, created };
63
+ }
64
+
65
+ /** 读全局任务交接正文(不存在返回空串)。 */
66
+ export function readTaskHandover(ws, task) {
67
+ const file = path.join(String(ws || ''), '_Control_Center', 'tasks', String(task || ''), 'Handover.md');
68
+ try { return readUtf8(file); } catch { return ''; }
69
+ }