@yixinkj/inquiry-opening-coach-cli 0.3.4 → 0.3.6

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/changelog.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "//": "随包发布的更新日志。启动器用它决定「这次更新要不要打断调用方」,并把 summary 打给模型看(只写 stderr,不要求转述给终端用户)。",
3
+ "//impact": "output = 只影响怎么读结果,本次命令仍然有效,照常执行;invocation = 影响怎么发命令,调用方手上那条命令可能已经失效,必须停下重读。缺省和写坏一律按 invocation 处理。发版时填不出 summary,多半说明这次压根不该动 SKILL.md 正文。",
4
+ "entries": [
5
+ {
6
+ "version": "0.3.6",
7
+ "impact": "output",
8
+ "summary": "版本更新不再每次打断:只有会影响命令怎么发的改动才要求重跑,其余照常执行并提示重读"
9
+ },
10
+ {
11
+ "version": "0.3.5",
12
+ "impact": "invocation",
13
+ "summary": "档案缺业务员英文名或公司英文名时,先问用户补齐再生成,不再直接用 [Your Name] 占位交付;存回档案要先读后合并,避免覆盖已有资料"
14
+ },
15
+ {
16
+ "version": "0.3.4",
17
+ "impact": "output",
18
+ "summary": "汇总改成纯文本加粗,默认只给推荐那一版全文,飞书钉钉里不再刷屏"
19
+ },
20
+ {
21
+ "version": "0.3.3",
22
+ "impact": "output",
23
+ "summary": "同一浏览器开着多个 Profile 时会自动认出哪个登着阿里;认不出时会列出各 Profile 开着哪些站点"
24
+ },
25
+ {
26
+ "version": "0.3.2",
27
+ "impact": "output",
28
+ "summary": "今天进来、之前没起草过的询盘都会写开场白,不再因为消息条数多而跳过"
29
+ }
30
+ ]
31
+ }
package/index.js CHANGED
@@ -57,6 +57,9 @@ const REPOSITORY_SKILL_PATH = path.join(
57
57
  const BUNDLED_SKILL_PATH = fs.existsSync(PACKED_SKILL_PATH)
58
58
  ? PACKED_SKILL_PATH
59
59
  : REPOSITORY_SKILL_PATH;
60
+ // 更新日志随包发布,启动器据此判断「这次更新要不要打断调用方」。
61
+ // 缺文件、读不出、区间为空时一律按最保守的 invocation 处理,见 core.readChangelog。
62
+ const CHANGELOG_PATH = path.join(PACKAGE_ROOT, 'changelog.json');
60
63
  const SKILL_VERSION_MARKER = `.${SKILL_ID}-version.json`;
61
64
  // 注意:SKILL_ROOT 下面除了 runtime/ 还住着用户的记忆库(banks/、config.json)。
62
65
  // 那是用户从 0.2 起累积的资料,任何发布都不许动它——本文件只写 runtime/ 这一层。
@@ -242,7 +245,10 @@ function bundledAssetDirNames(bundledSkillRoot) {
242
245
  * @param {object} [options] 测试注入的内置文件路径。
243
246
  * @returns {object} 同步结论与版本信息。
244
247
  */
245
- function syncSkillInstallation(skillPath, { bundledSkillPath = BUNDLED_SKILL_PATH } = {}) {
248
+ function syncSkillInstallation(
249
+ skillPath,
250
+ { bundledSkillPath = BUNDLED_SKILL_PATH, changelogPath = CHANGELOG_PATH } = {}
251
+ ) {
246
252
  const resolvedSkillPath = core.resolveSkillMdPath(skillPath);
247
253
  const { content: bundledSkill, metadata: bundledMetadata } =
248
254
  core.loadBundledSkill(bundledSkillPath);
@@ -347,8 +353,17 @@ function syncSkillInstallation(skillPath, { bundledSkillPath = BUNDLED_SKILL_PAT
347
353
  }
348
354
  });
349
355
 
356
+ // 判据是**正文**变没变,不是文件变没变:frontmatter 的版本号每次发布必然变,
357
+ // 拿整份文件比对等于「每次发布都打断一次」。附带素材(references/ 那棵树)算进正文——
358
+ // 它们是创作细则,变了同样要重读。
359
+ const bodyChanged =
360
+ core.skillBody(currentSkill) !== core.skillBody(bundledSkill) || changedAssets.length > 0;
361
+ const changelog = core.readChangelog(changelogPath, currentVersion, SKILL_VERSION);
362
+ const blocking = bodyChanged && changelog.impact !== 'output';
350
363
  return {
351
- action: skillChanged || changedAssets.length > 0 ? 'updated' : 'registry_updated',
364
+ action: blocking ? 'updated' : 'synced',
365
+ bodyChanged,
366
+ changelog,
352
367
  skillPath: resolvedSkillPath,
353
368
  currentVersion,
354
369
  targetVersion: SKILL_VERSION,
@@ -357,6 +372,22 @@ function syncSkillInstallation(skillPath, { bundledSkillPath = BUNDLED_SKILL_PAT
357
372
  };
358
373
  }
359
374
 
375
+ /// 把「已更新、请重读」这件事说给调用方听,但**不打断本次执行**。
376
+ ///
377
+ /// 只写 stderr:stdout 是产物 JSON,混进去会让调用方解析失败。也刻意不要求转述给终端
378
+ /// 用户——重读 SKILL.md 是模型自己的事,业务员不需要每次更新都被告知一遍。
379
+ function reportSkillSynced(sync) {
380
+ if (!sync || !sync.bodyChanged) return;
381
+ const entries = (sync.changelog && sync.changelog.entries) || [];
382
+ console.error(
383
+ `[${SKILL_ID}] 已更新到 ${sync.targetVersion}。本次结果请按新版说明解读,`
384
+ + `下一步动作前重新读一次 SKILL.md。`
385
+ );
386
+ for (const entry of entries) {
387
+ if (entry && entry.summary) console.error(`[${SKILL_ID}]  ${entry.summary}`);
388
+ }
389
+ }
390
+
360
391
  /** 帮助文案是本仓的命令面,不进共享内核。 */
361
392
  function printHelp() {
362
393
  process.stdout.write(`${SKILL_DISPLAY_NAME} ${SKILL_VERSION}(Runtime ${RUNTIME_VERSION})
@@ -438,17 +469,23 @@ async function main() {
438
469
  }
439
470
  emitStatus(
440
471
  'skill_updated',
441
- `${SKILL_DISPLAY_NAME}已更新至 ${SKILL_VERSION}。请重新试一次,本次不执行任何命令。`,
472
+ `${SKILL_DISPLAY_NAME}已更新至 ${SKILL_VERSION}。本次改动会影响命令怎么发,`
473
+ + `请重新读一次 SKILL.md 再按新版执行;本次不执行任何命令。`,
442
474
  {
443
475
  skill_path: gate.sync.skillPath,
444
476
  previous_version: gate.sync.currentVersion || null,
445
477
  synced_assets: gate.sync.syncedAssets || [],
446
478
  skills_registry: gate.sync.registryPath || null,
447
- restart_required: true
479
+ restart_required: true,
480
+ changes: ((gate.sync.changelog && gate.sync.changelog.entries) || [])
481
+ .map((entry) => entry.summary)
482
+ .filter(Boolean)
448
483
  }
449
484
  );
450
485
  return;
451
486
  }
487
+ // 放行的更新只提示、不打断:正文没变,或者改动只影响怎么读结果。
488
+ reportSkillSynced(gate.sync);
452
489
 
453
490
  const runtimeOverride = process.env.INQUIRY_OPENING_COACH_RUNTIME_BINARY;
454
491
  if (runtimeOverride) {
package/launcher-core.js CHANGED
@@ -629,7 +629,7 @@ function resolveSkillGate(skillPath, { syncSkill, discoverSkillPaths, discovered
629
629
  }
630
630
  if (skillPath) {
631
631
  const sync = syncSkill(skillPath);
632
- return { allowRun: sync.action === 'current', sync };
632
+ return { allowRun: isRunnableSyncAction(sync.action), sync };
633
633
  }
634
634
  const candidates = discoverSkillPaths();
635
635
  if (candidates.length !== 1) {
@@ -643,12 +643,76 @@ function resolveSkillGate(skillPath, { syncSkill, discoverSkillPaths, discovered
643
643
  if (discoveredPathPolicy === 'require_argument') {
644
644
  return {
645
645
  allowRun: false,
646
- sync: { ...sync, action: 'skill_path_required', candidates, synced: sync.action !== 'current' }
646
+ sync: {
647
+ ...sync,
648
+ action: 'skill_path_required',
649
+ candidates,
650
+ synced: !isRunnableSyncAction(sync.action)
651
+ }
647
652
  };
648
653
  }
649
654
  return { allowRun: false, sync: { ...sync, action: 'restart_required' } };
650
655
  }
651
656
 
657
+ /// 同步之后仍然可以继续执行本次命令吗。
658
+ ///
659
+ /// `current` 是什么都没变;`synced` 是变了但**不影响本次这条命令**——版本号单独升、
660
+ /// 或者改动只影响「怎么读结果」。两者都放行,调用方读到提示后自己重读 SKILL.md 即可。
661
+ ///
662
+ /// 真机数据:一周里 4 次更新有 2 次正文一个字没动,只是 frontmatter 的版本号跟着升了,
663
+ /// 那 2 次全都把调用方挡回去重跑一遍,换来零信息。
664
+ ///
665
+ /// 写成函数而不是模块级 Set,是为了不动内核那条「模块级只允许一个策略常量」的守卫——
666
+ /// 那条守卫是 SKILL_ID 事故留下的,不该为了少写两个字就把它放宽。
667
+ function isRunnableSyncAction(action) {
668
+ return action === 'current' || action === 'synced';
669
+ }
670
+
671
+ /// 剥掉 frontmatter 里的 `version:` 行,用来判断**正文**变没变。
672
+ ///
673
+ /// 版本号每次发布必然变,拿整份文件比对等于「每次发布都算正文变了」。而真正决定要不要
674
+ /// 打断调用方的,是那些指导它怎么做的文字有没有变。
675
+ function skillBody(content) {
676
+ return String(content ?? '').replace(/^version:.*$/m, '');
677
+ }
678
+
679
+ /// 取 `(fromVersion, toVersion]` 区间内的更新日志条目。
680
+ ///
681
+ /// `impact` 决定要不要打断本次调用,只有两个取值:
682
+ /// - `output` —— 只影响「怎么读结果」。本次命令仍然有效,照常执行,事后重读即可。
683
+ /// - `invocation` —— 影响「怎么发命令」。调用方是读着旧说明构造的这条命令,它可能已经
684
+ /// 失效,必须停下重来。
685
+ ///
686
+ /// **缺省和读不出来一律按 `invocation`。** 真机上栽过一次:SKILL.md 从「`--mark` 只配
687
+ /// `--start`」改成「首响心跳也必须带 `--mark`」,按旧说明构造的命令没有 `--mark`,
688
+ /// 放行执行的结果是水位不记账、同一批新询盘每轮重复推送——不报错,只是结果是错的。
689
+ /// 默认成放行的话,漏标一次就是这种静默出错,而且没人会发现。
690
+ function readChangelog(changelogPath, fromVersion, toVersion) {
691
+ const fallback = { impact: 'invocation', entries: [] };
692
+ if (!changelogPath || !fs.existsSync(changelogPath)) return fallback;
693
+ let data;
694
+ try {
695
+ data = JSON.parse(fs.readFileSync(changelogPath, 'utf8'));
696
+ } catch {
697
+ return fallback;
698
+ }
699
+ const all = Array.isArray(data) ? data : Array.isArray(data.entries) ? data.entries : null;
700
+ if (!all) return fallback;
701
+ const entries = all.filter((entry) => {
702
+ const version = entry && typeof entry.version === 'string' ? entry.version : '';
703
+ if (!version) return false;
704
+ // 左开右闭:本地已经是 from 那一版,它的改动不必再说一遍。
705
+ return (
706
+ (!fromVersion || compareVersions(version, fromVersion) > 0) &&
707
+ compareVersions(version, toVersion) <= 0
708
+ );
709
+ });
710
+ if (!entries.length) return fallback;
711
+ // 区间里只要有一条影响调用方式,整段就必须按 invocation 处理。
712
+ const impact = entries.some((entry) => entry.impact !== 'output') ? 'invocation' : 'output';
713
+ return { impact, entries };
714
+ }
715
+
652
716
  /* ------------------------------------------------------------------ *
653
717
  * 六、平台识别与下载源
654
718
  * ------------------------------------------------------------------ */
@@ -1439,6 +1503,8 @@ export {
1439
1503
  reportPrunedRuntimeVersions,
1440
1504
  resolveReleaseBaseUrls,
1441
1505
  resolveSkillGate,
1506
+ readChangelog,
1507
+ skillBody,
1442
1508
  resolveSkillMdPath,
1443
1509
  restoreFile,
1444
1510
  restoreTree,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yixinkj/inquiry-opening-coach-cli",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
4
4
  "description": "询盘开场白创意教练独立启动器:只读读取询盘详情页并召回本机记忆库,不发送任何消息。",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  "files": [
15
15
  "index.js",
16
16
  "launcher-core.js",
17
+ "changelog.json",
17
18
  "skills.json",
18
19
  "skill"
19
20
  ],
package/skill/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: create-inquiry-openers
3
3
  description: 为阿里巴巴国际站业务员创作新询盘的首次响应(英文开场白),可直接从询盘详情页读取买家国家、需求卡片和买家原话,也可由用户口述信息生成;结合本机记忆库的行为约束与长期经验,固定生成热情自然、幽默记忆、专业创意三种版本,并沉淀一条经验。同时管理这个本机记忆库和业务档案:通过纯 Rust CLI 写入、查看、修改、停用和删除记忆,并把累积的经验归纳为长期心智模型。Use when 用户给出询盘详情页链接、要求给某条新询盘写第一次回复、要求批量处理首响待办清单或看看有哪些新询盘要回、提到询盘开场白、客户第一句话、开发信开头、根据客户国家和产品写英文开场、修改或优化三套开场白;也 use when 用户说记住这个、保存公司资料、保存产品能力、维护 FAQ、设置禁止承诺、个人记忆点、查看或整理记忆库、把对话附件沉淀为长期资料、归纳历史询盘经验。
4
- version: "0.3.4"
4
+ version: "0.3.6"
5
5
  ---
6
6
 
7
7
  # 询盘开场白创意教练
@@ -19,6 +19,9 @@ npx -y @yixinkj/inquiry-opening-coach-cli@latest \
19
19
  - **每一条命令都要带 `--skill-path`**,值是你本次实际读取的这份 `SKILL.md` 的绝对路径。缺了它本次不执行,启动器会直接把该带的参数和路径告诉你——那不是可以重试的错误,原样重跑一万次也不会成功。
20
20
  - 不要固定路径、目录名或版本号,也不要去找宿主 PATH 里的 `inquiry-opening-coach`、插件缓存或任何本地二进制。原生运行时由启动器自己下载并校验 SHA256。
21
21
  - 返回 `skill_updated` 时说明本机 Skill 刚被同步到新版:**本次不执行**,请重新读一次 SKILL.md 再按新版执行。
22
+ - stderr 出现「已更新到 x.y.z,本次结果请按新版说明解读」时,**本次命令照常执行完了,结果有效**。
23
+ 下一步动作之前重新读一次 SKILL.md 即可,不要重跑这条命令。这类更新不影响命令怎么发,
24
+ 只影响怎么读结果;真正会让命令失效的更新走上面那条 `skill_updated`,那才要停。
22
25
  - 返回 `skill_version_conflict` 时说明本机 Skill 版本高于 npm 包,启动器拒绝降级;如实告诉用户,不要绕过。
23
26
 
24
27
  CLI 不含 LLM。它只负责抓取、召回、校验和落盘,文案由你直接创作。
@@ -243,14 +246,25 @@ npx -y @yixinkj/inquiry-opening-coach-cli@latest \
243
246
  - **`profileGapsAction`** —— **有值就照做,这是动作不是提示。**它只在缺少署名字段
244
247
  (业务员英文名、公司英文名)时出现,因为那两项缺了会在开场白正文里留下
245
248
  `[Your Name]` / `[Your Company]`,业务员要么手工替换、要么把占位符发给客户。
246
- **先用 ask_user 问用户补齐**,拿到后用 `profile set` 存进档案——一次性,以后不再问。
249
+ **先用 ask_user 问用户补齐**,然后按下面「存回档案」的三步存进去——一次性,以后不再问。
247
250
  用户明确说先不填时才用占位符继续,并在交付时点名哪几处需要他手工替换。
248
251
  - **`profileGaps`** —— 产品优势、个人记忆点这类空缺,只会让开场白平淡一些,
249
252
  不影响能不能直接用。**不要为此打断生成**,交付后用一句话提一次即可。
250
253
 
251
- 用户想改已有资料时同样用 ask_user 问清要改哪一项、改成什么,再 `profile set`;
254
+ 用户想改已有资料时同样用 ask_user 问清要改哪一项、改成什么,再按下面三步存回;
252
255
  不要凭一句「换个公司名」就自行猜写。
253
256
 
257
+ #### 存回档案:三步,不能跳
258
+
259
+ **`profile save` 是整份覆盖,不是合并。**只把新问到的两个字段写进去,会丢掉主营产品、
260
+ 公司名和已有的一切。所以必须:
261
+
262
+ 1. `profile show --json` 拿到当前完整档案;
263
+ 2. 在那份 JSON 上**改动要改的字段**,其余原样保留;
264
+ 3. 写成临时文件,`profile save --file <文件> --json`。
265
+
266
+ 没有 `profile set` 这个子命令,`profile` 只有 `show` 和 `save` 两个。
267
+
254
268
  `budget.droppedCount` 大于 0 表示有记忆因预算未注入,`droppedTitles` 列出了前几条。若本次询盘明显需要更多背景,用 `--budget 12000` 重新召回一次。
255
269
 
256
270
  `warnings` 非空表示 `memories.jsonl` 有行损坏、已被跳过。**照常完成本次生成**,交付结果后再用一句话提醒用户哪一行需要修——此时写入类命令会失败,所以生成后的经验沉淀这次会跳过,不要反复重试。
package/skills.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.3.3",
2
+ "runtimeVersion": "0.3.4",
3
3
  "platforms": {
4
4
  "darwin-arm64": {
5
5
  "id": "mac-arm64",
@@ -24,7 +24,7 @@
24
24
  "skill": {
25
25
  "name": "create-inquiry-openers",
26
26
  "id": "inquiry-opening-coach",
27
- "version": "0.3.4",
27
+ "version": "0.3.6",
28
28
  "bin": "inquiry-opening-coach-cli"
29
29
  }
30
30
  }