@xqyz/xq-cli 0.3.2 → 0.3.4

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 (3) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/package.json +1 -1
  3. package/src/cli.mjs +99 -23
package/CHANGELOG.md CHANGED
@@ -10,6 +10,75 @@
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.3.4 / WorkBuddy Plugin 0.10.10] - 2026-08-19
14
+
15
+ ### 快速模式新任务生成链路修复
16
+
17
+ - 对齐喜鹊 Web:新任务先调用 `/task/preSet` 保存用户配置,其中 `requirement`、`rating`、`plan` 允许为空;随后调用 `/proxy/multiBiddingChoice` 触发后台解析流程。
18
+ - 禁止新任务由 xq-cli 直接调用 `/task/generateByRequirement`,避免抢在 `autoGenerateDirectoryJobHandler` 补齐解析结果之前提交空字段,造成任务停留在大纲解析阶段。
19
+ - 保留历史任务兼容逻辑:仅 `task_status=0` 继续直接调用 `/task/generateByRequirement`。
20
+ - 新增新任务与历史任务两条回归测试。
21
+
22
+ ### WorkBuddy 响应依据完整预览
23
+
24
+ - “选择大纲依据”卡片改为三个页签:融合响应、仅格式要求、仅评分要求。
25
+ - 页签切换只读取后端返回的完整候选目录树,不会提交 `referenceType` 或改变任务状态。
26
+ - 用户只有点击“按当前思路生成大纲”才会调用既有单次提交链路;仍保留重复提交拦截。
27
+
28
+ ## [0.3.3 / WorkBuddy Plugin 0.10.9] - 2026-08-19
29
+
30
+ ### WorkBuddy 响应依据阶段化选择
31
+
32
+ - 首次标书配置卡移除“响应依据”,避免在后端候选目录生成前提前提交 `referenceType`。
33
+ - 新增独立依据选择阶段,展示喜鹊返回的三套真实目录预览;选择后只调用依据提交接口并继续同一 `cid` / `runId`。
34
+ - 增加重复提交保护,重复选择不会重新上传、创建任务或提交不同依据。
35
+ - 同步 WorkBuddy MCP App、Skill、Connector 文档与回归测试。
36
+ - xq-cli 新增 `plan reference-select`,依据选择复用原任务且不重复提交篇幅等基础参数。
37
+
38
+ ## [WorkBuddy Plugin 0.10.8] - 2026-08-18
39
+
40
+ ### 规划模式目录修改安全性
41
+
42
+ - `xique_prepare_directory_update` 改为显式递归目录结构,目录名称使用 `text`,同时兼容并自动归一化旧调用中的 `title` / `name`。
43
+ - 保存前读取后台当前完整目录,自动保留已有节点 ID;遗漏根节点或子节点时默认拒绝,避免局部目录请求被后台解释为删除其他章节。
44
+ - 修复目录修改预览始终显示“根节点数=0”,现在从 `requestPreview.directories` 返回准确的根节点数、总节点数和 ID 保留统计。
45
+ - 保存确认绑定预览时的后台目录签名;确认期间目录变化时拒绝写入。后台保存失败会返回真实原因和“重新查看、重新预览、重新确认”动作,禁止自动重复请求。
46
+ - 保存成功后仍停留在 `directory-review`,不会启动正文;新增覆盖字段归一化、完整树校验、ID 保留、失败单次消费和成功保存的回归测试。
47
+
48
+ ## [WorkBuddy Plugin 0.10.7] - 2026-08-17
49
+
50
+ ### 规划模式目录确认卡
51
+
52
+ - 修复 `xique_directory_confirmation` 未声明 MCP App 面板元数据,WorkBuddy 只显示文本而不生成目录确认卡的问题。
53
+ - 目录确认准备结果改为紧凑摘要,始终显式返回真实一次性 `confirmationId`,避免完整目录树截断后被误用 `cid`。
54
+ - 目录确认卡和正文继续动作按 `runId`、目录签名自动恢复错误的 `cid`,并支持“确认目录并生成正文”作为有效确认语句。
55
+ - 增加目录确认卡展示、ID 恢复、单次确认和正文门禁回归测试。
56
+
57
+ ## [WorkBuddy Plugin 0.10.6] - 2026-08-17
58
+
59
+ ### 规划模式目录审阅
60
+
61
+ - 修复 Connector 将目录命令拼成 `xq-cli directory directory view/update`,导致目录已经生成后报 `Unknown directory action: directory` 并停在目录审阅阶段的问题。
62
+ - `xique_directory_view`、目录修改预览和目录修改保存现在分别正确调用 `directory view` / `directory update`,不会重复顶层命令。
63
+ - 增加目录查看、预览修改、确认保存的参数级回归测试,并用真实任务 `a653d3b6e6274a2c91f29bcb28712690` 验证可读回完整 9 章目录。
64
+
65
+ ## [WorkBuddy Plugin 0.10.5] - 2026-08-17
66
+
67
+ ### 规划模式确认调用
68
+
69
+ - 修复 Connector 调用带子命令的 `xq-cli` 时将 `--json` 放在子命令前,导致 `plan confirm-outline` 被解析为 `plan status` 的问题。
70
+ - 确认智能解读后现在会真正执行 `plan confirm-outline`,从而按前端顺序调用 `setPlanModePhase(1)` 和 `/task/generateDirectoryByCid`。
71
+ - 增加真实参数顺序回归测试;当 CLI 返回错误命令时报告协议错误,不再伪装成后端大纲生成阻塞。
72
+
73
+ ## [WorkBuddy Plugin 0.10.4] - 2026-08-17
74
+
75
+ ### 规划模式确认链路
76
+
77
+ - 修复 WorkBuddy 将 cid 错传为 confirmationId 时无法继续生成大纲的问题。
78
+ - 智能解读确认卡按同一 runId 自动恢复真实的一次性确认记录,并继续调用 `/task/generateDirectoryByCid`。
79
+ - 新增回归测试覆盖错误 ID、单次消费和同一 runId 恢复。
80
+
81
+
13
82
  ## [0.3.2] - 2026-08-17
14
83
 
15
84
  ### xq-cli / WorkBuddy 规划模式
@@ -17,6 +86,7 @@
17
86
  - 规划模式首次 `outline --plan-mode 1` 在解析完成后明确返回“智能解读待审核”,不再误报大纲已完成或提前进入正文。
18
87
  - WorkBuddy 新增智能解读审核、补充/修正暂存和确认续跑流程;确认时才调用喜鹊 `generateDirectoryByCid` 生成大纲。
19
88
  - 新增 CLI 与 WorkBuddy 回归测试,验证同一 `cid` / `runId` 不重复提交。
89
+ - WorkBuddy Plugin `0.10.3` 修正 MCP 初始化指令和内置命令,明确规划模式必须先审核智能解读,再确认生成大纲。
20
90
 
21
91
  ### xq-cli `0.3.1` / WorkBuddy `0.10.1`
22
92
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xqyz/xq-cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "CLI for xique bid-book task workflows",
5
5
  "type": "module",
6
6
  "scripts": {
package/src/cli.mjs CHANGED
@@ -473,6 +473,7 @@ Examples
473
473
  xq-cli plan status --cid <cid> --json
474
474
  xq-cli plan feedback --cid <cid> --text "Add the missing service boundary"
475
475
  xq-cli plan confirm-outline --cid <cid> --yes --wait
476
+ xq-cli plan reference-select --cid <cid> --reference-type 0 --yes --wait
476
477
  xq-cli plan outline-ai-adjust --cid <cid> --prompt "Split chapter 2 into implementation and quality control" --out D:\\output\\
477
478
  xq-cli plan outline-ai-apply --cid <cid> --file D:\\output\\outline-ai-<cid>.json --yes
478
479
  xq-cli plan directory-generate --cid <cid> --yes --wait
@@ -926,6 +927,10 @@ async function handlePlan(args, state) {
926
927
  case 'correction':
927
928
  await handlePlanFeedback(args, state);
928
929
  return;
930
+ case 'reference-select':
931
+ case 'select-reference':
932
+ await handlePlanReferenceSelection(args, state);
933
+ return;
929
934
  case 'confirm-outline':
930
935
  case 'outline-confirm':
931
936
  case 'generate-outline':
@@ -973,14 +978,16 @@ async function handlePlan(args, state) {
973
978
  async function handlePlanStatus(args, state) {
974
979
  const client = createAuthorizedClient(args, state);
975
980
  const cid = resolveCid(args, state);
976
- const [taskResponse, outlineResponse, contentResponse] = await Promise.all([
981
+ const [taskResponse, outlineResponse, contentResponse, referenceResponse] = await Promise.all([
977
982
  getJson(client, `/task/getTaskDetail/${encodeURIComponent(cid)}`, {}, { retries: DEFAULT_RETRY_COUNT }),
978
983
  fetchOutlineDetail(client, cid),
979
984
  fetchContentDetail(client, cid),
985
+ fetchReferenceStatusSafe(client, cid),
980
986
  ]);
981
987
  const taskDetail = taskResponse?.data || {};
982
988
  const outlineDetail = outlineResponse?.data || {};
983
989
  const contentDetail = contentResponse?.data || {};
990
+ const referenceDetail = referenceResponse?.data || {};
984
991
  const phase = normalizePlanModePhase(taskDetail.plan_mode_phase);
985
992
  const payload = {
986
993
  command: 'plan-status',
@@ -991,15 +998,23 @@ async function handlePlanStatus(args, state) {
991
998
  taskDetail,
992
999
  outlineDetail,
993
1000
  contentDetail,
1001
+ referenceDetail,
1002
+ referenceStatus: normalizeReferenceStatus(referenceDetail.status),
1003
+ referenceOptions: buildOutlineReferenceChoices(referenceDetail),
994
1004
  pendingFeedback: normalizePlanFeedback(state.tasks?.[cid]?.planFeedback).length,
995
1005
  };
996
1006
  saveTaskSnapshot(state, cid, pickTaskSnapshot({ ...taskDetail, ...contentDetail }));
1007
+ saveTaskSnapshot(state, cid, {
1008
+ referenceStatus: normalizeReferenceStatus(referenceDetail.status),
1009
+ referenceOptions: buildOutlineReferenceChoices(referenceDetail),
1010
+ });
997
1011
  outputResult(args, payload, [
998
1012
  `plan status: cid=${cid}`,
999
1013
  `planMode: ${describeEnumValue(PLAN_MODE_OPTIONS, normalizePlanModeValue(taskDetail.planMode) ?? taskDetail.planMode)}`,
1000
1014
  `phase: ${formatPlanModePhase(phase)}`,
1001
1015
  `outline: ${outlineDetail.outline_status ? 'ready' : 'not-ready'} (${countOutlineRoots({ outlineDetail })} root chapters)`,
1002
1016
  `directory: task_status=${contentDetail.task_status ?? taskDetail.task_status ?? '-'}, directory_status=${contentDetail.directory_portion_status ?? taskDetail.directory_portion_status ?? '-'}`,
1017
+ `reference: status=${normalizeReferenceStatus(referenceDetail.status) ?? '-'}, selected=${state.tasks?.[cid]?.outlineReferenceType ?? '-'}`,
1003
1018
  `pending feedback: ${payload.pendingFeedback}`,
1004
1019
  ]);
1005
1020
  return payload;
@@ -1040,6 +1055,65 @@ async function handlePlanFeedback(args, state) {
1040
1055
  return payload;
1041
1056
  }
1042
1057
 
1058
+ async function handlePlanReferenceSelection(args, state) {
1059
+ const client = createAuthorizedClient(args, state);
1060
+ const cid = resolveCid(args, state);
1061
+ const referenceType = parseOutlineReferenceType(stringOption(args, 'referenceType'));
1062
+ if (referenceType === null) {
1063
+ throw new Error('plan reference-select requires --reference-type 0, 1, 2, merge, format, or score.');
1064
+ }
1065
+
1066
+ const snapshot = await hydrateTaskSnapshot(client, state, cid);
1067
+ const previous = parseOutlineReferenceType(snapshot.outlineReferenceType);
1068
+ if (previous !== null) {
1069
+ if (previous !== referenceType) {
1070
+ throw new Error('该任务已经提交过其他响应依据,不能二次修改;请继续查询同一 cid。');
1071
+ }
1072
+ const payload = {
1073
+ command: 'plan-reference-select',
1074
+ cid,
1075
+ referenceType,
1076
+ alreadyApplied: true,
1077
+ duplicateRequestPrevented: true,
1078
+ };
1079
+ outputResult(args, payload, [
1080
+ `reference selection already applied: cid=${cid}, referenceType=${referenceType}`,
1081
+ 'duplicate request prevented; continue querying the same task.',
1082
+ ]);
1083
+ return payload;
1084
+ }
1085
+
1086
+ const referenceStatus = await fetchReferenceStatusSafe(client, cid);
1087
+ const referencePhase = normalizeReferenceStatus(referenceStatus?.data?.status);
1088
+ if (referencePhase !== null && referencePhase !== 1) {
1089
+ throw new Error('当前任务不在等待响应依据选择阶段(status=' + referencePhase + '),请先查询 plan status。');
1090
+ }
1091
+ await confirmPlanAction(args, 'select outline reference basis', [
1092
+ `referenceType: ${referenceType}`,
1093
+ 'only the backend reference-selection endpoint will be called; the task will not be recreated.',
1094
+ ]);
1095
+ await submitOutlineReferenceType(client, cid, referenceType);
1096
+ saveTaskSnapshot(state, cid, { outlineReferenceType: referenceType, referenceStatus: 1 });
1097
+
1098
+ const waitResult = booleanOption(args, 'wait')
1099
+ ? await waitForOutlineReady(client, state, cid, snapshot.uuid, { ...args, referenceType: undefined }, referenceType)
1100
+ : null;
1101
+ const payload = {
1102
+ command: 'plan-reference-select',
1103
+ cid,
1104
+ referenceType,
1105
+ responseSubmitted: true,
1106
+ waitResult,
1107
+ };
1108
+ outputResult(args, payload, [
1109
+ `reference selection submitted: cid=${cid}, referenceType=${referenceType}`,
1110
+ waitResult?.phase === 'ready'
1111
+ ? `outline ready: rootChapters=${countOutlineRoots(waitResult)}`
1112
+ : 'outline generation is continuing; query plan status for the same task.',
1113
+ ]);
1114
+ return payload;
1115
+ }
1116
+
1043
1117
  async function handlePlanOutlineConfirmation(args, state) {
1044
1118
  const client = createAuthorizedClient(args, state);
1045
1119
  const cid = resolveCid(args, state);
@@ -1854,28 +1928,30 @@ function buildOutlineParams(snapshot, args) {
1854
1928
  }
1855
1929
 
1856
1930
  async function triggerOutlineGeneration(client, snapshot, params) {
1857
- await postJson(client, '/task/preSet', params);
1858
-
1859
- // task_status=6 means "file parsing" in the Xique backend. It is not a
1860
- // signal that this is a multi-bid/EPC task. Only route through the
1861
- // special-selection endpoint when the caller actually supplied a
1862
- // multi-bid or EPC selection; ordinary tasks must use the normal
1863
- // generation endpoint even while the task is transitioning from parse.
1864
- const hasSpecialSelection = Boolean(
1865
- String(params.multiBidId ?? snapshot.multiBidId ?? '').trim()
1866
- || String(params.epcEngineerType ?? snapshot.epcEngineerType ?? '').trim()
1867
- );
1868
- if (hasSpecialSelection) {
1869
- await getJson(client, '/proxy/multiBiddingChoice', compactObject({
1870
- uuid: snapshot.uuid,
1871
- multiBidId: params.multiBidId ?? snapshot.multiBidId,
1872
- epcEngineerType: normalizeEpcEngineerTypeForProxy(params.epcEngineerType ?? snapshot.epcEngineerType),
1873
- }));
1874
- return 'multiBiddingChoice';
1931
+ // The web flow has two deliberately different paths:
1932
+ //
1933
+ // * legacy tasks (task_status === 0) already contain the parsed
1934
+ // requirement/rating/plan fields, so they go straight to
1935
+ // generateByRequirement;
1936
+ // * newly parsed tasks must only persist the user's configuration with
1937
+ // preSet, then call multiBiddingChoice. That endpoint wakes the async
1938
+ // parser, and autoGenerateDirectoryJobHandler later fills the three
1939
+ // parsed fields before it invokes generateByRequirement.
1940
+ //
1941
+ // Calling generateByRequirement here for a new task races that job and
1942
+ // sends empty parsed fields, which leaves the web task stuck in parsing.
1943
+ if (Number(snapshot.task_status) === 0) {
1944
+ await postJson(client, '/task/generateByRequirement', params);
1945
+ return 'generateByRequirement';
1875
1946
  }
1876
1947
 
1877
- await postJson(client, '/task/generateByRequirement', params);
1878
- return 'generateByRequirement';
1948
+ await postJson(client, '/task/preSet', params);
1949
+ await getJson(client, '/proxy/multiBiddingChoice', compactObject({
1950
+ uuid: snapshot.uuid,
1951
+ multiBidId: params.multiBidId ?? snapshot.multiBidId,
1952
+ epcEngineerType: normalizeEpcEngineerTypeForProxy(params.epcEngineerType ?? snapshot.epcEngineerType),
1953
+ }));
1954
+ return 'multiBiddingChoice';
1879
1955
  }
1880
1956
 
1881
1957
  async function preparePlanningAnalysis(client, params) {
@@ -2226,9 +2302,9 @@ async function pollTaskUntil(client, state, cid, args, isDone, formatProgress) {
2226
2302
  });
2227
2303
  }
2228
2304
 
2229
- async function waitForOutlineReady(client, state, cid, uuid, args) {
2305
+ async function waitForOutlineReady(client, state, cid, uuid, args, initialSubmittedReferenceType = null) {
2230
2306
  const handledIssues = new Set();
2231
- let submittedReferenceType = null;
2307
+ let submittedReferenceType = initialSubmittedReferenceType;
2232
2308
 
2233
2309
  return pollUntil(async () => {
2234
2310
  const [taskDetail, outlineDetail, referenceStatus] = await Promise.all([