index.mjs 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright (c) 未来飞马
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this
  5. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
  6. //
  7. // Trademark Notice:
  8. // The MPL-2.0 license grants copyright permissions for source code only.
  9. // It does NOT grant any rights to use trademarks including "未来飞马",
  10. // "Harness Loop", "RSI", and associated slogan "让AI进化提前发生,让AI落地快人一步".
  11. // Any use of these trademarks requires separate written permission.
  12. export const VERSION = '2.1.0';
  13. export const SKILL_NAME = 'skill-heterarchy';
  14. // ---------------------------------------------------------------------------
  15. // v2.1 dispatch model — 判断0 硬门槛 + 四判断决策流(对齐 SKILL.md §一 / §二)
  16. //
  17. // v2.0 失效根因(2026-10-08 实测):用户给出 git 项目路径并明说"项目级任务",
  18. // 主控仍自己读写改文件。原因是「四判断流」全是软描述,且判断 2 的 ≤2 调用预算
  19. // 被解读成"每一步",主控边走边看逐步自我授权;git 项目的映射规则又藏在判断 2
  20. // 之后,判断 2 一旦误判"自做",映射表根本不会被查到。
  21. //
  22. // v2.1 修正:
  23. // ① 新增 hardTrigger()——判断 0,在判断 2 之前执行,命中即收回"自做"裁量权。
  24. // ② 判断 2 预算改为「整条请求累计」而非单步(见 cumulativeToolCalls 选项)。
  25. // ③ git 仓库 / 技能仓任务 → claude -p(需项目根 + CLAUDE.md + rules)。
  26. // ④ 导出 signatures 保持向后兼容:isConversation / canSelfExecute /
  27. // selectProfile / selectMechanism / planDispatch 全部保留。
  28. // ---------------------------------------------------------------------------
  29. /** 判断 1:沟通(问候/确认/简单回执/已知信息问答)还是干活。 */
  30. export function isConversation(text = '') {
  31. // 去掉尾部标点/空白;注意 CJK 后不能用 \b(JS \b 仅识别 ASCII 词边界)
  32. const t = String(text).trim().replace(/[!!。.,,、~~\s]+$/g, '');
  33. if (!t) return true;
  34. if (t.length > 20) return false;
  35. return /^(你好|您好|hi|hello|在吗|在么|收到|好的|好|嗯+|谢谢|thanks|thank you|ok|okk?|早|晚安)$/i.test(t);
  36. }
  37. // ---------------------------------------------------------------------------
  38. // 判断 0:硬门槛(★ v2.1 新增,最先执行,命中即派)
  39. // ---------------------------------------------------------------------------
  40. /** 硬触发规则表(SKILL.md §一)。命中任一 → 必须派发,不允许主控自做。 */
  41. export const HARD_TRIGGERS = [
  42. {
  43. id: 'H1',
  44. label: 'git 仓库路径',
  45. // 绝对/相对路径里出现 git-repos / repos/ / git- 前缀 / .git
  46. re: /(^|[\s"'`(])(\S*\/git-repos\/|\S*\/repos\/|\S*git-[a-z0-9_-]+|\.git\b)/i,
  47. profile: 'fullstack',
  48. mechanism: 'claude-p',
  49. },
  50. {
  51. id: 'H2',
  52. label: 'git 操作',
  53. re: /(git\s+clone|git\s+commit|git\s+push|git\s+checkout\s+-b|建仓|打\s*tag|发版|发布\s*release)/i,
  54. profile: 'fullstack',
  55. mechanism: 'claude-p',
  56. },
  57. {
  58. id: 'H4',
  59. label: '技能开发/安装',
  60. re: /(开发\s*skill|skill\s*开发|改\s*SKILL\.md|修改\s*SKILL\.md|\/opt\/data\/skills\/|安装技能|npx\s+.*skill)/i,
  61. profile: 'fullstack',
  62. mechanism: 'claude-p',
  63. },
  64. // H5 排在 H3 之前:用户显式声明比泛化的"项目级"词更具体(否则 "项目级任务" 会误报为 H3)
  65. {
  66. id: 'H5',
  67. label: '用户显式声明',
  68. re: /(项目级任务|项目任务|派\s*(CC|FmodeCode\s*Code)|交给\s*FmodeCode\s*Code|用\s*CC\s*做)/i,
  69. profile: null, // 由文本再判
  70. mechanism: 'delegate-task',
  71. },
  72. {
  73. id: 'H3',
  74. label: '项目级动词',
  75. re: /(build|构建|拆分|重构|部署|发布|上线|\bCI\b|多文件|全栈|实现功能|项目级)/i,
  76. profile: 'fullstack',
  77. mechanism: 'delegate-task',
  78. },
  79. {
  80. id: 'H6',
  81. label: '批量对象',
  82. re: /(全舰队|所有容器|逐台|批量|\d+\s*(台|个|套)\s*(容器|服务器|机器|节点)?)/i,
  83. profile: 'devops',
  84. mechanism: 'delegate-task',
  85. },
  86. ];
  87. /**
  88. * 判断 0:硬门槛。命中即返回命中的规则,未命中返回 null。
  89. * @returns {{id:string,label:string,profile:string|null,mechanism:string}|null}
  90. */
  91. export function hardTrigger(text = '') {
  92. const t = String(text);
  93. for (const rule of HARD_TRIGGERS) {
  94. if (rule.re.test(t)) return { ...rule };
  95. }
  96. return null;
  97. }
  98. // ---------------------------------------------------------------------------
  99. // 判断 2:是否主控直接执行(整条请求累计 ≤2 个简单工具调用)
  100. // ---------------------------------------------------------------------------
  101. /**
  102. * 判断 2:主控自做判定。
  103. *
  104. * v2.0 的坑:预算被读成"每一步"。v2.1 明确按「整条用户请求累计」计算——
  105. * 调用方应把当前已累计的调用数传进 `cumulativeToolCalls`,而非"这一步要几个"。
  106. *
  107. * @param {object} plan
  108. * @param {boolean} [plan.known] 已知信息、无需工具即可回答
  109. * @param {number} [plan.toolCalls] 本步预计调用数(兼容旧签名)
  110. * @param {number} [plan.cumulativeToolCalls] 整条请求累计调用数(v2.1 推荐)
  111. */
  112. export function canSelfExecute(plan) {
  113. if (!plan || typeof plan !== 'object') return false; // 无计划 → 不臆断为自做
  114. if (plan.known === true) return true;
  115. const budget =
  116. typeof plan.cumulativeToolCalls === 'number' ? plan.cumulativeToolCalls : plan.toolCalls;
  117. if (typeof budget === 'number') return budget <= 2;
  118. return false;
  119. }
  120. // 判断 3:任务类型 → Profile 映射表(SKILL.md §三 的内置速查)
  121. const PROFILE_MAP = [
  122. { profile: 'writer', re: /(小说|章节|剧本|文案|报告|拉片|文章|内容|html|ppt|讲述)/i },
  123. { profile: 'researcher', re: /(调研|检索|分析|研究|选型|竞品|资料|查证)/i },
  124. { profile: 'fullstack', re: /(开发|实现|功能|项目|全栈|前端|后端|重构|代码|skill|技能)/i },
  125. { profile: 'devops', re: /(运维|部署|发布|容器|docker|k8s|批量|上线|舰队|服务器)/i },
  126. ];
  127. /** 判断 3:根据任务描述自动选 Profile;不要求用户显式指定。 */
  128. export function selectProfile(task = '') {
  129. const t = String(task);
  130. for (const { profile, re } of PROFILE_MAP) {
  131. if (re.test(t)) return profile;
  132. }
  133. return 'fullstack'; // 兜底:通用实现
  134. }
  135. /** 判断 4:选派发机制。项目上下文(git 项目/需读 CLAUDE.md)优先 claude -p。 */
  136. export function selectMechanism(opts = {}) {
  137. if (opts.needsProjectContext === true) return 'claude-p';
  138. return 'delegate-task';
  139. }
  140. /**
  141. * 完整决策:硬门槛(判断0)→ 沟通(判断1)→ 自做(判断2)→ Profile(判断3)→ 机制(判断4)。
  142. *
  143. * @returns {{action:'reply'|'self'|'dispatch', hit?:string, label?:string,
  144. * profile?:string, mechanism?:string, parallel?:boolean}}
  145. */
  146. export function planDispatch(text = '', opts = {}) {
  147. // ── 判断 0:硬门槛——命中即派,跳过判断 2 的自做裁量 ──────────────────
  148. const hit = hardTrigger(text);
  149. if (hit) {
  150. const profile = hit.profile || selectProfile(text);
  151. // git 项目 / 技能仓 → claude -p;其余按规则表给的机制
  152. const mechanism =
  153. hit.mechanism === 'claude-p' || opts.needsProjectContext === true
  154. ? 'claude-p'
  155. : selectMechanism(opts);
  156. return { action: 'dispatch', hit: hit.id, label: hit.label, profile, mechanism, parallel: opts.parallel === true };
  157. }
  158. // ── 判断 1:沟通 / 干活 ─────────────────────────────────────────────
  159. if (isConversation(text)) return { action: 'reply' };
  160. // ── 判断 2:自做 / 派发(整条请求累计 ≤2 才允许自做)──────────────────
  161. if (canSelfExecute(opts.plan)) return { action: 'self' };
  162. // ── 判断 3 / 4:Profile + 机制 ──────────────────────────────────────
  163. const profile = selectProfile(text);
  164. const mechanism = selectMechanism(opts);
  165. return { action: 'dispatch', profile, mechanism, parallel: opts.parallel === true };
  166. }
  167. /**
  168. * 教训库条目构造器(SKILL.md §十一)。发现"该派未派"时调用,
  169. * 返回一行 Markdown 表格记录,追加到 LESSONS.md。
  170. *
  171. * @param {{date?:string, hit:string, action:string, should:string, cause:string}} e
  172. */
  173. export function lessonEntry(e = {}) {
  174. const date = e.date || new Date().toISOString().slice(0, 10);
  175. const esc = (s) => String(s ?? '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
  176. return `| ${esc(date)} | ${esc(e.hit)} | ${esc(e.action)} | ${esc(e.should)} | ${esc(e.cause)} |`;
  177. }