helixlife-v5-cli 1.2.8 → 1.2.9

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 (46) hide show
  1. package/README.md +33 -58
  2. package/helix-cli.js +7 -2
  3. package/lib/.cli.runtime.generated.json +10 -1
  4. package/lib/automation-file-chooser-guard.js +182 -0
  5. package/lib/automation-overlay.init.js +332 -68
  6. package/lib/browser-css-cache-guard.init-page.js +49 -0
  7. package/lib/browser-window-maximize.init-page.js +64 -0
  8. package/lib/cli.js +316 -14
  9. package/lib/helix-reload-bypass-cache.run-code.js +7 -0
  10. package/lib/helix-styles-guard.js +77 -0
  11. package/lib/hide-nested-skills.js +120 -0
  12. package/lib/native-mode-patch.js +288 -0
  13. package/lib/postinstall.js +39 -0
  14. package/lib/profile-lock-guard.js +187 -0
  15. package/lib/runtime-config.js +90 -0
  16. package/lib/token-file.js +189 -0
  17. package/lib/visual-page-wrap.js +311 -0
  18. package/lib/visual.js +255 -86
  19. package/package.json +9 -21
  20. package/scripts/README.md +81 -0
  21. package/scripts/{analysis-detail-upload-file.js → analysis/detail-upload-file.js} +1 -1
  22. package/scripts/{jigsaw-inspect-dom.js → analysis/jigsaw-inspect-dom.js} +15 -15
  23. package/scripts/{jigsaw-verify-enumerate.js → analysis/jigsaw-verify-enumerate.js} +1 -1
  24. package/scripts/common/.gitkeep +0 -0
  25. package/scripts/{chrome-pdf-enumerate-a11y.js → edu/chrome-pdf-enumerate-a11y.js} +1 -1
  26. package/scripts/{chrome-pdf-goto-page.js → edu/chrome-pdf-goto-page.js} +1 -1
  27. package/scripts/{chrome-pdf-zoom-out.js → edu/chrome-pdf-zoom-out.js} +1 -1
  28. package/scripts/{hover-partial-chapter-match.js → edu/hover-partial-chapter-match.js} +1 -1
  29. package/scripts/maintainer/strip-extra-skills.ps1 +27 -0
  30. package/scripts/workbench/jova/outline-edit-root-title.js +33 -0
  31. package/scripts/workbench/jova/outline-keyword-edit.js +122 -0
  32. package/scripts/workbench/jova/outline-ops.js +344 -0
  33. package/scripts/workbench/jova/outline-probe-hover.js +56 -0
  34. package/scripts/workbench/jova/proofread-ai-rewrite-section.js +94 -0
  35. package/scripts/workbench/jova/proofread-ops.js +261 -0
  36. package/scripts/workbench/jova/writing-ops.js +376 -0
  37. package/scripts/workbench/jova/writing-proofread-abstract.js +103 -0
  38. package/lib/cli.runtime.config.json +0 -5
  39. package/references/vip.helixlife.cn.site-ops.md +0 -1469
  40. package/scripts/workbench-jova-outline-regenerate-node.js +0 -173
  41. package/scripts/workbench-jova-proofread-section.js +0 -262
  42. package/scripts/workbench-jova-writing-tool.js +0 -308
  43. /package/scripts/{analysis-detail-upload-both.js → analysis/detail-upload-both.js} +0 -0
  44. /package/scripts/{jigsaw-verify-bio-tools.js → analysis/jigsaw-verify-bio-tools.js} +0 -0
  45. /package/scripts/{jigsaw-verify-drag-tool.js → analysis/jigsaw-verify-drag-tool.js} +0 -0
  46. /package/scripts/{jigsaw-verify-reference-lines.js → analysis/jigsaw-verify-reference-lines.js} +0 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * 综述工作台 · 初稿校对 · 单段 AI 改写并替换原文
3
+ *
4
+ * 运行:
5
+ * helixlife-v5-cli run-code --filename=scripts/workbench/jova/proofread-ai-rewrite-section.js
6
+ */
7
+ async page => {
8
+ const CONFIG = {
9
+ navTitle: '2. 蛋白质异戊二烯化:法尼基化和香叶基香叶基化',
10
+ h2Partial: '2. 蛋白质异戊二烯化',
11
+ aiExtraNeed: '',
12
+ runReplace: true,
13
+ };
14
+
15
+ const wait = ms => page.waitForTimeout(ms);
16
+ const pathname = await page.evaluate(() => location.pathname);
17
+ if (pathname !== '/workbench/jova/proofread') {
18
+ return { ok: false, reason: '当前不在初稿校对页', pathname };
19
+ }
20
+
21
+ const HINT = {
22
+ unproofIdle: '请校对本段内容',
23
+ unproofHover: '点击本段进入编辑校对',
24
+ proofIdle: '本段完成校对',
25
+ proofHover: '重新编辑',
26
+ };
27
+
28
+ const nav = page
29
+ .locator('main span.truncate.flex-1')
30
+ .filter({ hasText: CONFIG.navTitle, exact: true })
31
+ .first();
32
+ if ((await nav.count()) > 0) {
33
+ await nav.click();
34
+ await wait(800);
35
+ }
36
+
37
+ const block = page
38
+ .locator('main .slate-block > div.group')
39
+ .filter({ has: page.locator('h2').filter({ hasText: CONFIG.h2Partial }) })
40
+ .first();
41
+ if ((await block.count()) === 0) {
42
+ return { ok: false, reason: `未找到 h2 含「${CONFIG.h2Partial}」的段落块` };
43
+ }
44
+
45
+ await block.scrollIntoViewIfNeeded();
46
+ await block.hover();
47
+ await wait(450);
48
+ const hint = block.getByText(
49
+ new RegExp(
50
+ `${HINT.unproofIdle}|${HINT.unproofHover}|${HINT.proofIdle}|${HINT.proofHover}`,
51
+ ),
52
+ );
53
+ await hint.first().click();
54
+ await wait(550);
55
+
56
+ await block.getByRole('button', { name: 'AI 改写' }).click();
57
+ await wait(700);
58
+
59
+ if (CONFIG.aiExtraNeed) {
60
+ await page.getByRole('textbox', { name: '额外需求' }).fill(CONFIG.aiExtraNeed);
61
+ }
62
+ await page.getByRole('button', { name: '一键重写' }).click();
63
+
64
+ let elapsed = 0;
65
+ let rewriteDone = false;
66
+ while (elapsed < 120000) {
67
+ const generating = page.getByRole('button', { name: '生成中' });
68
+ if ((await generating.count()) === 0) {
69
+ const copy = page.getByRole('button', { name: '一键复制' });
70
+ if ((await copy.count()) > 0 && (await copy.isEnabled())) {
71
+ rewriteDone = true;
72
+ break;
73
+ }
74
+ }
75
+ await wait(1500);
76
+ elapsed += 1500;
77
+ }
78
+
79
+ if (!rewriteDone) {
80
+ return { ok: false, reason: 'AI 改写超时或失败', target: CONFIG.navTitle };
81
+ }
82
+
83
+ if (CONFIG.runReplace) {
84
+ await page.getByRole('button', { name: '替换原文' }).click();
85
+ await wait(400);
86
+ await page.getByRole('button', { name: '确 定' }).click();
87
+ await wait(800);
88
+ }
89
+
90
+ const closeImg = page.getByRole('img', { name: 'close' });
91
+ if ((await closeImg.count()) > 0) await closeImg.last().click();
92
+
93
+ return { ok: true, target: CONFIG.navTitle, replaced: CONFIG.runReplace };
94
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * 综述工作台 · 初稿校对(/workbench/jova/proofread)· 全流程标准化脚本
3
+ *
4
+ * 运行(须已在初稿校对页且全文已生成完毕):
5
+ * helixlife-v5-cli run-code --filename=scripts/workbench/jova/proofread-ops.js
6
+ *
7
+ * 默认仅做结构探测与只读验收(不改动段落校对状态、不替换原文)。
8
+ * 在 CONFIG 中开启对应开关后,可执行确认校对 / AI 改写 / 替换原文等写操作。
9
+ *
10
+ * 覆盖链路(与用户规格 100% 对齐,线上文案以实测为准):
11
+ * A. 左侧目录 span.truncate.flex-1 → 点击后正文滚动至对应段落块
12
+ * B. 未校对段落:请校对本段内容 → hover 点击本段进入编辑校对 → 点击提示条 → 确认校对 | AI 改写
13
+ * C. 已校对段落:本段完成校对 → hover 重新编辑 → 点击提示条 → 同 B
14
+ * D. AI 改写右侧面板:原文内容 / 一键重写 / 额外需求 / 生成结果 / 一键复制 / 替换原文
15
+ * E. 替换原文二次确认:Popover「是否将本次生成结果替换原文?」→ 取 消 | 确 定
16
+ * F. 底栏:返回提纲 / 下载文档 / 完成校对(完成校对默认不执行,避免擅自进入精修)
17
+ */
18
+ async page => {
19
+ const CONFIG = {
20
+ /** 左侧目录探测项(点击后验收对应 h2/h3 进入视口) */
21
+ navProbe: '1.1 棕榈酰化的生化基础和调控机制',
22
+ /** 未校对段落(h2 标题,部分匹配) */
23
+ unproofreadH2: '2. 蛋白质异戊二烯化',
24
+ /** 已校对段落(h2 标题,部分匹配) */
25
+ proofreadH2: '前言',
26
+ /** 写操作开关(默认 false) */
27
+ runConfirmProofread: false,
28
+ runAiRewrite: false,
29
+ runReplaceConfirm: false,
30
+ aiExtraNeed: '将表述改得更学术简洁',
31
+ /** 完成校对 → 精修(默认禁止,勿擅自推进) */
32
+ runCompleteProofread: false,
33
+ };
34
+
35
+ const wait = ms => page.waitForTimeout(ms);
36
+ const pathname = await page.evaluate(() => location.pathname);
37
+ if (pathname !== '/workbench/jova/proofread') {
38
+ return { ok: false, reason: '当前不在初稿校对页', pathname };
39
+ }
40
+
41
+ const HINT = {
42
+ unproofIdle: '请校对本段内容',
43
+ unproofHover: '点击本段进入编辑校对', // 线上实测文案(非「进行」)
44
+ proofIdle: '本段完成校对',
45
+ proofHover: '重新编辑',
46
+ };
47
+
48
+ const blocks = () => page.locator('main .slate-block > div.group');
49
+ const blockByH2 = partial =>
50
+ blocks().filter({ has: page.locator('h2').filter({ hasText: partial }) }).first();
51
+ const outlineItem = text =>
52
+ page.locator('main span.truncate.flex-1').filter({ hasText: text, exact: true }).first();
53
+
54
+ const readVisibleHint = async block => {
55
+ for (const t of [HINT.proofHover, HINT.unproofHover, HINT.proofIdle, HINT.unproofIdle]) {
56
+ if ((await block.getByText(t, { exact: true }).count()) > 0) return t;
57
+ }
58
+ if ((await block.getByRole('button', { name: '确认校对' }).count()) > 0) return 'actions-visible';
59
+ return null;
60
+ };
61
+
62
+ const hoverBlock = async block => {
63
+ await block.scrollIntoViewIfNeeded();
64
+ await block.hover();
65
+ await wait(450);
66
+ return readVisibleHint(block);
67
+ };
68
+
69
+ /** 点击段落提示条以展开「确认校对 / AI 改写」(须先 hover 使提示条可点) */
70
+ const activateBlock = async block => {
71
+ await hoverBlock(block);
72
+ const hint = block.getByText(
73
+ new RegExp(`${HINT.unproofIdle}|${HINT.unproofHover}|${HINT.proofIdle}|${HINT.proofHover}`),
74
+ );
75
+ await hint.first().click();
76
+ await wait(550);
77
+ return {
78
+ confirm: (await block.getByRole('button', { name: '确认校对' }).count()) > 0,
79
+ ai: (await block.getByRole('button', { name: 'AI 改写' }).count()) > 0,
80
+ };
81
+ };
82
+
83
+ const closeRewritePanel = async () => {
84
+ const closeImg = page.getByRole('img', { name: 'close' });
85
+ if ((await closeImg.count()) > 0) {
86
+ await closeImg.last().click();
87
+ await wait(300);
88
+ }
89
+ };
90
+
91
+ const dismissReplacePopover = async () => {
92
+ const cancel = page.getByRole('button', { name: '取 消' });
93
+ if ((await cancel.count()) > 0 && (await cancel.isVisible())) {
94
+ await cancel.click();
95
+ await wait(300);
96
+ }
97
+ };
98
+
99
+ const waitRewriteDone = async (timeoutMs = 120000) => {
100
+ let elapsed = 0;
101
+ while (elapsed < timeoutMs) {
102
+ const generating = page.getByRole('button', { name: '生成中' });
103
+ if ((await generating.count()) === 0) {
104
+ const copy = page.getByRole('button', { name: '一键复制' });
105
+ if ((await copy.count()) > 0 && (await copy.isEnabled())) return true;
106
+ }
107
+ await wait(1500);
108
+ elapsed += 1500;
109
+ }
110
+ return false;
111
+ };
112
+
113
+ const inspectRewritePanel = async () => ({
114
+ title: (await page.getByText('段落重写', { exact: true }).count()) > 0,
115
+ originalLabel: (await page.getByText('原文内容', { exact: true }).count()) > 0,
116
+ oneClickRewrite: (await page.getByRole('button', { name: '一键重写' }).count()) > 0,
117
+ extraNeed: (await page.getByRole('textbox', { name: '额外需求' }).count()) > 0,
118
+ resultLabel: (await page.getByText('生成结果', { exact: true }).count()) > 0,
119
+ copyBtn: (await page.getByRole('button', { name: '一键复制' }).count()) > 0,
120
+ replaceBtn: (await page.getByRole('button', { name: '替换原文' }).count()) > 0,
121
+ });
122
+
123
+ const results = { pathname, hints: HINT };
124
+
125
+ // --- A. 左侧目录点击 → 滚动 ---
126
+ const nav = outlineItem(CONFIG.navProbe);
127
+ if ((await nav.count()) === 0) {
128
+ results.navScroll = { ok: false, reason: `目录无「${CONFIG.navProbe}」` };
129
+ } else {
130
+ await nav.click();
131
+ await wait(800);
132
+ const target = page.locator('main h3').filter({ hasText: CONFIG.navProbe }).first();
133
+ const inView = await target.evaluate(el => {
134
+ const r = el.getBoundingClientRect();
135
+ return r.top >= 0 && r.top < window.innerHeight * 0.85;
136
+ }).catch(() => false);
137
+ results.navScroll = { ok: inView, target: CONFIG.navProbe };
138
+ }
139
+
140
+ // --- B. 未校对段落 ---
141
+ const unBlock = blockByH2(CONFIG.unproofreadH2);
142
+ if ((await unBlock.count()) === 0) {
143
+ results.unproofread = { ok: false, reason: `未找到 h2 含「${CONFIG.unproofreadH2}」的段落块` };
144
+ } else {
145
+ const idleHint = await readVisibleHint(unBlock);
146
+ const hoverHint = await hoverBlock(unBlock);
147
+ const clickActions = await activateBlock(unBlock);
148
+ results.unproofread = {
149
+ idleHint,
150
+ hoverHint,
151
+ clickActions,
152
+ idleOk: idleHint === HINT.unproofIdle,
153
+ hoverOk: hoverHint === HINT.unproofHover,
154
+ actionsOk: clickActions.confirm && clickActions.ai,
155
+ };
156
+
157
+ if (CONFIG.runAiRewrite && clickActions.ai) {
158
+ await unBlock.getByRole('button', { name: 'AI 改写' }).click();
159
+ await wait(700);
160
+ results.rewritePanel = await inspectRewritePanel();
161
+
162
+ await page.getByRole('textbox', { name: '额外需求' }).fill(CONFIG.aiExtraNeed);
163
+ await page.getByRole('button', { name: '一键重写' }).click();
164
+ results.rewriteDone = await waitRewriteDone();
165
+
166
+ if (results.rewriteDone) {
167
+ await page.getByRole('button', { name: '一键复制' }).click();
168
+ results.copyToast = (await page.getByText('复制成功').count()) > 0;
169
+
170
+ await page.getByRole('button', { name: '替换原文' }).click();
171
+ await wait(400);
172
+ results.replacePopover =
173
+ (await page.getByText('是否将本次生成结果替换原文?').count()) > 0;
174
+
175
+ if (CONFIG.runReplaceConfirm) {
176
+ await page.getByRole('button', { name: '确 定' }).click();
177
+ await wait(500);
178
+ results.replaced = true;
179
+ } else {
180
+ await dismissReplacePopover();
181
+ results.replaced = false;
182
+ }
183
+ }
184
+ await closeRewritePanel();
185
+ } else if (clickActions.ai) {
186
+ await unBlock.getByRole('button', { name: 'AI 改写' }).click();
187
+ await wait(600);
188
+ results.rewritePanel = await inspectRewritePanel();
189
+ await closeRewritePanel();
190
+ }
191
+
192
+ if (CONFIG.runConfirmProofread && clickActions.confirm) {
193
+ if (!(await unBlock.getByRole('button', { name: '确认校对' }).count())) {
194
+ await activateBlock(unBlock);
195
+ }
196
+ await unBlock.getByRole('button', { name: '确认校对' }).click();
197
+ await wait(600);
198
+ results.unproofread.afterConfirm = await readVisibleHint(unBlock);
199
+ results.unproofread.confirmed = results.unproofread.afterConfirm === HINT.proofIdle;
200
+ } else {
201
+ await page.keyboard.press('Escape');
202
+ await wait(200);
203
+ }
204
+ }
205
+
206
+ // --- C. 已校对段落(与 B 点击后逻辑一致) ---
207
+ const proofBlock = blockByH2(CONFIG.proofreadH2);
208
+ if ((await proofBlock.count()) === 0) {
209
+ results.proofread = { ok: false, reason: `未找到 h2 含「${CONFIG.proofreadH2}」的段落块` };
210
+ } else {
211
+ const idleHint = await readVisibleHint(proofBlock);
212
+ const hoverHint = await hoverBlock(proofBlock);
213
+ const clickActions = await activateBlock(proofBlock);
214
+ results.proofread = {
215
+ idleHint,
216
+ hoverHint,
217
+ clickActions,
218
+ idleOk: idleHint === HINT.proofIdle,
219
+ hoverOk: hoverHint === HINT.proofHover,
220
+ actionsOk: clickActions.confirm && clickActions.ai,
221
+ };
222
+ await page.keyboard.press('Escape');
223
+ await wait(200);
224
+ }
225
+
226
+ // --- F. 底栏(只探测,默认不点完成校对) ---
227
+ results.footer = {
228
+ returnOutline: (await page.getByRole('button', { name: '返回提纲' }).count()) > 0,
229
+ download: (await page.getByRole('button', { name: '下载文档' }).count()) > 0,
230
+ complete: (await page.getByRole('button', { name: '完成校对' }).count()) > 0,
231
+ };
232
+
233
+ if (CONFIG.runCompleteProofread) {
234
+ await page.getByRole('button', { name: '完成校对' }).click();
235
+ await wait(500);
236
+ const dialog =
237
+ (await page.getByText('是否确认完成所有段落内容校对,进入全文编辑模式').count()) > 0;
238
+ results.completeDialog = dialog;
239
+ // 默认仍不确认,避免进入精修;若需全链路回归可在此扩展
240
+ }
241
+
242
+ results.ok =
243
+ results.navScroll?.ok !== false &&
244
+ results.unproofread?.idleOk &&
245
+ results.unproofread?.hoverOk &&
246
+ results.unproofread?.actionsOk &&
247
+ results.proofread?.idleOk &&
248
+ results.proofread?.hoverOk &&
249
+ results.proofread?.actionsOk &&
250
+ results.rewritePanel?.title !== false;
251
+
252
+ results.summary = {
253
+ blockLocator: 'main .slate-block > div.group',
254
+ outlineLocator: 'main span.truncate.flex-1',
255
+ activate: 'hover 段落块 → click 提示条 → 确认校对 | AI 改写',
256
+ rewritePanel: '段落重写 · 原文内容 · 一键重写 · 额外需求 · 生成结果 · 一键复制 · 替换原文',
257
+ replaceConfirm: '是否将本次生成结果替换原文? → 取 消 | 确 定',
258
+ };
259
+
260
+ return results;
261
+ }