@cloud411716/fancy-webnovel 2.2.0 → 3.0.3

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 (212) hide show
  1. package/README.md +65 -0
  2. package/client.js +201 -15
  3. package/cordis.patch.yml +5 -1
  4. package/docs/douyin-plugin.md +118 -0
  5. package/docs/material-library/README.md +404 -0
  6. package/docs/material-library/import.js +437 -0
  7. package/docs/material-library/prompt.md +370 -0
  8. package/docs/material-library/queries.surql +94 -0
  9. package/docs/material-library/sample.jsonl +14 -0
  10. package/docs/material-library/schema.surql +46 -0
  11. package/docs/mcp-profile.example.yml +24 -0
  12. package/docs/story-graph-design.md +602 -0
  13. package/docs/story-graph-validation.md +81 -0
  14. package/docs/workspace-mcp.md +284 -0
  15. package/index.js +12 -7
  16. package/lib/atomic-write.js +109 -0
  17. package/lib/chapter-order.js +100 -0
  18. package/lib/confirmation.js +80 -0
  19. package/lib/db-init.js +123 -0
  20. package/lib/deployed-state.js +11 -11
  21. package/lib/direction-labels.js +265 -0
  22. package/lib/error-codes.js +31 -31
  23. package/lib/infra.js +29 -4
  24. package/lib/material-db.js +149 -0
  25. package/lib/path-guard.js +150 -0
  26. package/lib/projects.js +400 -0
  27. package/lib/scraper-utils.js +140 -0
  28. package/lib/surreal-server.js +338 -0
  29. package/lib/topic-decision.js +860 -0
  30. package/lib/topic-fields.js +251 -0
  31. package/lib/topic-schema.js +225 -0
  32. package/lib/workspace.js +501 -0
  33. package/package.json +31 -7
  34. package/plugins/fancy-analyze/analyze-definitions.md +519 -0
  35. package/plugins/fancy-analyze/index.js +1128 -77
  36. package/plugins/fancy-analyze/ingest-coverage.js +107 -0
  37. package/plugins/fancy-analyze/schema-analyze.md +355 -107
  38. package/plugins/fancy-analyze/scripts/append-summary-chapter.js +612 -0
  39. package/plugins/fancy-analyze/summary-progress.js +71 -7
  40. package/plugins/fancy-analyze/templates.js +437 -139
  41. package/plugins/fancy-bootstrap/index.js +276 -131
  42. package/plugins/fancy-bootstrap/scripts/init.js +59 -25
  43. package/plugins/fancy-bootstrap/templates.js +35 -18
  44. package/plugins/fancy-db/index.js +172 -0
  45. package/plugins/fancy-douyin/deploy.js +134 -0
  46. package/plugins/fancy-douyin/index.js +551 -0
  47. package/plugins/fancy-douyin/prompts/01-analysis-prompt.md +84 -0
  48. package/plugins/fancy-douyin/scripts/crawl-note.cjs +194 -0
  49. package/plugins/fancy-douyin/scripts/lib.cjs +197 -0
  50. package/plugins/fancy-douyin/scripts/scan-account.cjs +128 -0
  51. package/plugins/fancy-douyin/scripts/start-chrome.cjs +203 -0
  52. package/plugins/fancy-douyin/state.js +197 -0
  53. package/plugins/fancy-douyin/templates.js +127 -0
  54. package/plugins/fancy-export/exporter.js +805 -0
  55. package/plugins/fancy-export/index.js +63 -0
  56. package/plugins/fancy-import/import-driver.js +29 -6
  57. package/plugins/fancy-import/index.js +13 -5
  58. package/plugins/fancy-import/prompts/01-import-prompt.md +39 -14
  59. package/plugins/fancy-import/templates.js +17 -8
  60. package/plugins/fancy-material/exporter.js +153 -0
  61. package/plugins/fancy-material/genres.js +256 -0
  62. package/plugins/fancy-material/index.js +835 -0
  63. package/plugins/fancy-material/inventory.js +145 -0
  64. package/plugins/fancy-material/kinds.js +313 -0
  65. package/plugins/fancy-material/queue.js +55 -0
  66. package/plugins/fancy-material/scripts/query.js +254 -0
  67. package/plugins/fancy-material/templates.js +258 -0
  68. package/plugins/fancy-material/tool-deploy.js +138 -0
  69. package/plugins/fancy-material/validate.js +149 -0
  70. package/plugins/fancy-project/index.js +133 -0
  71. package/plugins/fancy-scan/existing-block.js +100 -0
  72. package/plugins/fancy-scan/index.js +410 -21
  73. package/plugins/fancy-scan/prompts/01-analysis-prompt.md +302 -0
  74. package/plugins/fancy-scan/scripts/append-block.js +725 -0
  75. package/plugins/fancy-scan/scripts/report-validate.js +824 -0
  76. package/plugins/fancy-scan/scripts/run-scan.js +169 -22
  77. package/plugins/fancy-scan/scripts/scrapers/fanqie-rank-scraper.js +145 -16
  78. package/plugins/fancy-scan/scripts/scrapers/jjwxc-rank-scraper.js +179 -49
  79. package/plugins/fancy-scan/scripts/scrapers/qimao-rank-scraper.js +38 -20
  80. package/plugins/fancy-scan/templates/blocks.js +155 -0
  81. package/plugins/fancy-scan/templates/platforms.js +372 -0
  82. package/plugins/fancy-scan/templates/report-schema.js +90 -0
  83. package/plugins/fancy-scan/templates.js +545 -146
  84. package/plugins/fancy-scan/tool-deploy.js +335 -0
  85. package/plugins/fancy-scan/wait-turn.js +46 -0
  86. package/plugins/fancy-story-builder/db/README.md +106 -0
  87. package/plugins/fancy-story-builder/db/client.js +228 -0
  88. package/plugins/fancy-story-builder/db/commands.js +145 -0
  89. package/plugins/fancy-story-builder/db/ids.js +39 -0
  90. package/plugins/fancy-story-builder/db/import.js +798 -0
  91. package/plugins/fancy-story-builder/db/project.js +299 -0
  92. package/plugins/fancy-story-builder/db/query.js +361 -0
  93. package/plugins/fancy-story-builder/db/schema.js +417 -0
  94. package/plugins/fancy-story-builder/db/stage-sync.js +238 -0
  95. package/plugins/fancy-story-builder/flesh-output.js +19 -6
  96. package/plugins/fancy-story-builder/index.js +4105 -396
  97. package/plugins/fancy-story-builder/patch-driver.js +390 -0
  98. package/plugins/fancy-story-builder/prompts/01-skeleton-prompt.md +29 -19
  99. package/plugins/fancy-story-builder/prompts/02-flesh-prompt.md +72 -10
  100. package/plugins/fancy-story-builder/prompts/03-rhythm-prompt.md +40 -5
  101. package/plugins/fancy-story-builder/prompts/04-writing-prompt.md +37 -0
  102. package/plugins/fancy-story-builder/purpose-template.md +42 -66
  103. package/plugins/fancy-story-builder/references/formulas-skeleton.json +229 -56
  104. package/plugins/fancy-story-builder/references/genre-profiles.json +3514 -0
  105. package/plugins/fancy-story-builder/references/glossary.md +15 -6
  106. package/plugins/fancy-story-builder/references/index.md +97 -6
  107. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/00-index.md +1 -1
  108. package/plugins/fancy-story-builder/references//350/212/202/345/245/217/346/214/207/345/257/274/07-audit.md +25 -7
  109. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/01-character.md +5 -1
  110. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/03-relations.md +6 -2
  111. package/plugins/fancy-story-builder/references//350/241/200/350/202/211/346/214/207/345/257/274/05-audit.md +15 -5
  112. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/00-index.md +1 -1
  113. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/01-positioning.md +2 -1
  114. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/03-entities.md +1 -1
  115. package/plugins/fancy-story-builder/references//351/252/250/346/236/266/346/214/207/345/257/274/06-audit.md +19 -1
  116. package/plugins/fancy-story-builder/schema-setting.json +1100 -0
  117. package/plugins/fancy-story-builder/schema-setting.md +201 -293
  118. package/plugins/fancy-story-builder/scripts/brief-validate.js +164 -0
  119. package/plugins/fancy-story-builder/scripts/content-quality.js +4 -1
  120. package/plugins/fancy-story-builder/scripts/flesh-validate.js +135 -12
  121. package/plugins/fancy-story-builder/scripts/graph-utils.js +146 -47
  122. package/plugins/fancy-story-builder/scripts/reference-log.js +110 -0
  123. package/plugins/fancy-story-builder/scripts/rhythm-validate.js +100 -4
  124. package/plugins/fancy-story-builder/scripts/schema-pages.js +2668 -0
  125. package/plugins/fancy-story-builder/scripts/semantic-lint.js +5 -1
  126. package/plugins/fancy-story-builder/scripts/skeleton-validate.js +158 -17
  127. package/plugins/fancy-story-builder/scripts/stage-batches.js +36 -2
  128. package/plugins/fancy-story-builder/scripts/stage-contract.js +12 -0
  129. package/plugins/fancy-story-builder/scripts/story-artifacts.js +933 -0
  130. package/plugins/fancy-story-builder/scripts/upstream-query.js +1 -1
  131. package/plugins/fancy-story-builder/scripts/v2/baseline.js +112 -0
  132. package/plugins/fancy-story-builder/scripts/v2/cast.js +517 -0
  133. package/plugins/fancy-story-builder/scripts/v2/context.js +434 -0
  134. package/plugins/fancy-story-builder/scripts/v2/contracts.js +493 -0
  135. package/plugins/fancy-story-builder/scripts/v2/core.js +419 -0
  136. package/plugins/fancy-story-builder/scripts/v2/engine.js +1068 -0
  137. package/plugins/fancy-story-builder/scripts/v2/export-markdown.js +794 -0
  138. package/plugins/fancy-story-builder/scripts/v2/genre.js +195 -0
  139. package/plugins/fancy-story-builder/scripts/v2/knowledge.js +321 -0
  140. package/plugins/fancy-story-builder/scripts/v2/macro.js +409 -0
  141. package/plugins/fancy-story-builder/scripts/v2/manifest.js +538 -0
  142. package/plugins/fancy-story-builder/scripts/v2/migrate.js +137 -0
  143. package/plugins/fancy-story-builder/scripts/v2/prompts.js +1335 -0
  144. package/plugins/fancy-story-builder/scripts/v2/rhythm.js +618 -0
  145. package/plugins/fancy-story-builder/scripts/v2/scale.js +843 -0
  146. package/plugins/fancy-story-builder/scripts/v2/snapshot.js +121 -0
  147. package/plugins/fancy-story-builder/scripts/v2/stages.js +273 -0
  148. package/plugins/fancy-story-builder/scripts/v2/writing.js +216 -0
  149. package/plugins/fancy-story-builder/storydb/README.md +271 -0
  150. package/plugins/fancy-story-builder/storydb/cli.js +458 -0
  151. package/plugins/fancy-story-builder/storydb/client.js +351 -0
  152. package/plugins/fancy-story-builder/storydb/compile.js +452 -0
  153. package/plugins/fancy-story-builder/storydb/context.js +288 -0
  154. package/plugins/fancy-story-builder/storydb/definitions.js +699 -0
  155. package/plugins/fancy-story-builder/storydb/driver.js +106 -0
  156. package/plugins/fancy-story-builder/storydb/dsh-tools.js +313 -0
  157. package/plugins/fancy-story-builder/storydb/export.js +175 -0
  158. package/plugins/fancy-story-builder/storydb/ids.js +31 -0
  159. package/plugins/fancy-story-builder/storydb/impact.js +170 -0
  160. package/plugins/fancy-story-builder/storydb/import.js +559 -0
  161. package/plugins/fancy-story-builder/storydb/mcp.js +99 -0
  162. package/plugins/fancy-story-builder/storydb/prompts.js +952 -0
  163. package/plugins/fancy-story-builder/storydb/stage-command.js +379 -0
  164. package/plugins/fancy-story-builder/storydb/stage.js +426 -0
  165. package/plugins/fancy-story-builder/storydb/store.js +891 -0
  166. package/plugins/fancy-story-builder/storydb/tools.js +592 -0
  167. package/plugins/fancy-story-builder/storydb/validation.js +183 -0
  168. package/plugins/fancy-story-builder/templates/v2/10-scale/scale-template.json +144 -0
  169. package/plugins/fancy-story-builder/templates/v2/20-macro/emotion-template.json +15 -0
  170. package/plugins/fancy-story-builder/templates/v2/20-macro/endings-template.json +16 -0
  171. package/plugins/fancy-story-builder/templates/v2/20-macro/lines-template.json +28 -0
  172. package/plugins/fancy-story-builder/templates/v2/20-macro/roles-template.json +10 -0
  173. package/plugins/fancy-story-builder/templates/v2/20-macro/story-template.json +19 -0
  174. package/plugins/fancy-story-builder/templates/v2/20-macro/volumes-template.json +24 -0
  175. package/plugins/fancy-story-builder/templates/v2/20-macro/world-template.json +24 -0
  176. package/plugins/fancy-story-builder/templates/v2/30-cast/characters-template.json +49 -0
  177. package/plugins/fancy-story-builder/templates/v2/30-cast/climaxes-template.json +14 -0
  178. package/plugins/fancy-story-builder/templates/v2/30-cast/conflicts-template.json +30 -0
  179. package/plugins/fancy-story-builder/templates/v2/30-cast/factions-template.json +18 -0
  180. package/plugins/fancy-story-builder/templates/v2/30-cast/foreshadows-template.json +29 -0
  181. package/plugins/fancy-story-builder/templates/v2/30-cast/information-template.json +29 -0
  182. package/plugins/fancy-story-builder/templates/v2/30-cast/items-template.json +13 -0
  183. package/plugins/fancy-story-builder/templates/v2/30-cast/locations-template.json +12 -0
  184. package/plugins/fancy-story-builder/templates/v2/30-cast/payoffs-template.json +16 -0
  185. package/plugins/fancy-story-builder/templates/v2/30-cast/relations-template.json +44 -0
  186. package/plugins/fancy-story-builder/templates/v2/40-rhythm/chapter-map-template.json +81 -0
  187. package/plugins/fancy-story-builder/templates/v2/40-rhythm/outline-template.json +162 -0
  188. package/plugins/fancy-story-builder/templates/v2/50-writing/ledger-template.json +46 -0
  189. package/plugins/fancy-story-builder/templates/v2/60-deslop/report-template.json +10 -0
  190. package/plugins/fancy-story-builder/templates//350/212/202/345/245/217//346/227/266/345/272/217/346/230/240/345/260/204/346/250/241/346/235/277.json +267 -44
  191. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//344/270/226/347/225/214/350/247/202/350/247/204/345/210/231/346/250/241/346/235/277.json +121 -19
  192. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/263/351/224/256/351/201/223/345/205/267/346/250/241/346/235/277.json +129 -21
  193. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/205/266/344/273/226/345/256/236/344/275/223/346/250/241/346/235/277.json +62 -0
  194. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/206/262/347/252/201/346/250/241/346/235/277.json +76 -17
  195. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/211/247/346/203/205/346/250/241/346/235/277.json +56 -8
  196. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/212/277/345/212/233/346/250/241/346/235/277.json +63 -13
  197. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//345/234/260/347/202/271/346/250/241/346/235/277.json +34 -6
  198. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//347/273/223/345/261/200/346/250/241/346/235/277.json +81 -18
  199. package/plugins/fancy-story-builder/templates//350/241/200/350/202/211//350/247/222/350/211/262/346/250/241/346/235/277.json +202 -42
  200. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/205/263/347/263/273/346/250/241/346/235/277.json +160 -27
  201. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//345/256/236/344/275/223/346/250/241/346/235/277.json +217 -41
  202. package/plugins/fancy-story-builder/templates//351/252/250/346/236/266//346/241/206/346/236/266/346/250/241/346/235/277.json +215 -41
  203. package/plugins/fancy-story-builder/templates.js +272 -74
  204. package/plugins/fancy-story-builder/wiki-references.js +429 -93
  205. package/plugins/fancy-story-builder/writing-driver.js +336 -9
  206. package/plugins/fancy-topic/index.js +414 -61
  207. package/plugins/fancy-topic/prompts/01-exploration-prompt.md +115 -0
  208. package/plugins/fancy-topic/scripts/save-suggestion.js +219 -0
  209. package/plugins/fancy-topic/scripts/select-topic.js +106 -63
  210. package/plugins/fancy-topic/templates.js +225 -122
  211. package/plugins/fancy-topic/tool-deploy.js +200 -0
  212. package/lib/safe-rm.js +0 -94
package/lib/db-init.js ADDED
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Workspace/project database initialization.
3
+ *
4
+ * This is the single place that creates schema, validation functions, registry
5
+ * metadata, and the per-project database. The `/fancy-db init` command and the
6
+ * MCP `fancy_db_init` tool both call it.
7
+ */
8
+
9
+ import {
10
+ initWorkspace,
11
+ readWorkspace,
12
+ workspacePaths,
13
+ } from './workspace.js';
14
+ import {
15
+ getProject,
16
+ listProjects,
17
+ withRegistry,
18
+ } from './projects.js';
19
+ import { startServer, serverStatus } from './surreal-server.js';
20
+ import { ensureMaterialDatabase, materialDatabaseName } from './material-db.js';
21
+ import { SCHEMA_VERSION, VALIDATION_VERSION } from '../plugins/fancy-story-builder/storydb/validation.js';
22
+
23
+ export async function initWorkspaceDatabase(root, opts = {}) {
24
+ const init = initWorkspace(root, opts);
25
+ // Always verify that the server on this workspace's endpoint is actually
26
+ // ours. A stale server from a deleted workspace may pass /health but reject
27
+ // our root password; startServer then moves us to a free port.
28
+ const started = await startServer(init.root);
29
+
30
+ await withRegistry(init.root, async (db, workspace) => {
31
+ await db.query(
32
+ `UPSERT fancy_meta:main SET
33
+ schema_version = $schema_version,
34
+ validation_version = $validation_version,
35
+ updated_at = time::now();`,
36
+ { schema_version: SCHEMA_VERSION, validation_version: VALIDATION_VERSION },
37
+ );
38
+ return workspace;
39
+ });
40
+
41
+ const material = await ensureMaterialDatabase(init.root);
42
+
43
+ return {
44
+ ok: true,
45
+ workspace: init.root,
46
+ created: init.created,
47
+ server: await serverStatus(init.root),
48
+ started,
49
+ schema_version: SCHEMA_VERSION,
50
+ validation_version: VALIDATION_VERSION,
51
+ material,
52
+ };
53
+ }
54
+
55
+ export async function initProjectDatabase(root, nameOrId, opts = {}) {
56
+ const project = await getProject(root, nameOrId);
57
+ if (!project) throw new Error('未找到项目:' + nameOrId);
58
+ const { openStoryGraph } = await import('../plugins/fancy-story-builder/storydb/client.js');
59
+ const sdb = await openStoryGraph(project.dir, { project, admin: true });
60
+ try {
61
+ await sdb.query(
62
+ `UPSERT fancy_meta:main SET
63
+ schema_version = $schema_version,
64
+ validation_version = $validation_version,
65
+ updated_at = time::now();`,
66
+ { schema_version: SCHEMA_VERSION, validation_version: VALIDATION_VERSION },
67
+ );
68
+ const info = await sdb.query('INFO FOR DB;');
69
+ const db = Array.isArray(info) && info[0] ? info[0] : info;
70
+ return {
71
+ ok: true,
72
+ project: { id: project.project_id, name: project.name, database: project.db_name },
73
+ schema_version: SCHEMA_VERSION,
74
+ validation_version: VALIDATION_VERSION,
75
+ tables: Object.keys((db && db.tables) || {}).length,
76
+ functions: Object.keys((db && db.functions) || {}),
77
+ users: Object.keys((db && db.users) || {}),
78
+ };
79
+ } finally {
80
+ await sdb.close();
81
+ }
82
+ }
83
+
84
+ export async function initAllProjectDatabases(root, opts = {}) {
85
+ const projects = await listProjects(root);
86
+ const results = [];
87
+ for (const project of projects) {
88
+ try {
89
+ results.push(await initProjectDatabase(root, project.project_id, opts));
90
+ } catch (err) {
91
+ results.push({
92
+ ok: false,
93
+ project: { id: project.project_id, name: project.name },
94
+ error: err && err.message ? err.message : String(err),
95
+ });
96
+ }
97
+ }
98
+ return results;
99
+ }
100
+
101
+ export async function initDatabases(root, opts = {}) {
102
+ const workspace = await initWorkspaceDatabase(root, opts);
103
+ let projects = [];
104
+ if (opts.project) {
105
+ projects = [await initProjectDatabase(root, opts.project, opts)];
106
+ } else if (opts.all !== false) {
107
+ projects = await initAllProjectDatabases(root, opts);
108
+ }
109
+ return { ...workspace, projects };
110
+ }
111
+
112
+ export function databaseLayout(root) {
113
+ const paths = workspacePaths(root);
114
+ const workspace = readWorkspace(root);
115
+ return {
116
+ workspace: paths.root,
117
+ server_data: paths.surrealData,
118
+ server_log: paths.surrealLog,
119
+ registry_database: workspace.surreal?.registry_database || 'fancy_registry',
120
+ material_database: materialDatabaseName(workspace),
121
+ projects: paths.projects,
122
+ };
123
+ }
@@ -97,18 +97,18 @@ export function deployedStateProblemText(state, opts = {}) {
97
97
  const projectRoot = opts.projectRoot || '';
98
98
 
99
99
  if (state.status === DEPLOYED_STATUS.MISSING) {
100
- return '❌ 当前目录尚未初始化,请先运行 /fancy-bootstrap 进行初始化。';
100
+ return '❌ 当前目录不是已初始化的 fancy-webnovel 工作区。请先运行 /fancy-bootstrap 初始化工作区。';
101
101
  }
102
102
 
103
103
  const head = state.status === DEPLOYED_STATUS.CORRUPT
104
104
  ? '❌ 状态文件 .fancy-deployed 损坏(不是合法 JSON),无法确定项目状态。'
105
105
  : state.status === DEPLOYED_STATUS.NO_PHASE
106
- ? '❌ 状态文件 .fancy-deployed 缺少 phase 字段,无法确定项目当前阶段。'
107
- : `❌ 状态文件 .fancy-deployed 的 phase 值不认识("${state.phase}"),无法确定项目当前阶段。`;
106
+ ? '❌ 状态文件 .fancy-deployed 缺少 phase 字段,无法确定当前工作区阶段。'
107
+ : `❌ 状态文件 .fancy-deployed 的 phase 值不在允许范围内("${state.phase}"),无法确定当前工作区阶段。`;
108
108
 
109
109
  const phaseHelp = state.status === DEPLOYED_STATUS.CORRUPT
110
- ? `3. 如果整个文件不是合法 JSON,用下面的模板重建 .fancy-deployed,
111
- 并把原来的 llm_wiki_token / reference_id 等字段值填回去:
110
+ ? `3. 如果整个文件不是合法 JSON,请使用下面的模板重建 .fancy-deployed,
111
+ 并将原有的 llm_wiki_token / reference_id 等字段值填回:
112
112
 
113
113
  {
114
114
  "deployed_at": "<ISO 时间>",
@@ -122,20 +122,20 @@ export function deployedStateProblemText(state, opts = {}) {
122
122
  }
123
123
  `
124
124
  : `3. 把 phase 改成合法值之一:${PHASE_ORDER.join(' / ')}。
125
- 拿不准就填 "initialized" —— 它允许流程从头再走一遍。`;
125
+ 无法确定时填写 "initialized",即可允许流程从头重新执行。`;
126
126
 
127
127
  return `${head}${state.error ? `\n原因:${state.error}` : ''}
128
128
 
129
- 这条消息不能靠重跑命令解决(重跑只会再撞一次同样的问题),需要手工修复:
129
+ 重复运行命令无法解决该问题,需要手动修复状态文件:
130
130
 
131
131
  1. 打开项目根目录下的 .fancy-deployed
132
132
  2. ${state.status === DEPLOYED_STATUS.CORRUPT
133
- ? '确认文件内容是不是被截断了(写入过程中断电/被编辑器改坏都会这样)'
133
+ ? '确认文件是否被截断或意外修改(写入过程中断电、编辑器异常退出等均可能导致)'
134
134
  : `找到 "phase" 那一行${state.phase ? `(当前值 "${state.phase}")` : ''},改成下面任一合法值`}
135
135
  ${phaseHelp}
136
- 4. 修完重新执行刚才的命令
136
+ 4. 修复完成后重新执行原命令
137
137
 
138
- 另一条路(会丢配置,慎用):
138
+ 备选方案(会丢失配置,请谨慎使用):
139
139
  5. 删除 .fancy-deployed 后重新运行 /fancy-bootstrap 重建。
140
- 注意:这会丢掉 llm_wiki_token / reference_id / setup_id 等字段,需要重新填。`;
140
+ 注意:该操作会丢失 llm_wiki_token / reference_id / setup_id 等字段,需要重新填写。`;
141
141
  }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * direction-fields —— 选题方向的 11 项设计(**单一事实来源**)
3
+ *
4
+ * ## 为什么要有这个文件(2026-10-29 提示词评审 F2 / 11.2)
5
+ *
6
+ * 这 11 项同时出现在五处:
7
+ *
8
+ * 1. fancy-scan 的方向模板(模型照抄的 11 行);
9
+ * 2. fancy-scan 的写入后校验脚本(查每个方向的标签齐不齐);
10
+ * 3. fancy-topic 的 11 个独立字段(探索建议 + setup 弹框);
11
+ * 4. fancy-topic 的字段校验(查 11 项齐不齐、内容够不够厚);
12
+ * 5. fancy-story-builder 的任务输入快照(渲染给骨架/血肉/节奏阶段)。
13
+ *
14
+ * 现在只定义一次,五处都从它生成。`label` 是扫榜报告里的中文标签,
15
+ * `id` 是 JSON 里的字段名 —— 两边靠这张表一一对应,不再手写第二份。
16
+ *
17
+ * 2026-10-30:`direction_design` 从一个大字符串拆成 11 个平级字段。
18
+ * JSON 结构化后,弹框可以逐项展示,story-builder 也不用再解析文本。
19
+ * 其中「定位」与原来的 `core_summary` 合并为 `positioning`。
20
+ */
21
+ export const DIRECTION_FIELDS = [
22
+ {
23
+ id: 'positioning',
24
+ label: '定位',
25
+ topicLabel: '一句话定位',
26
+ description: '题材、主角、核心卖点,一句话说清这本书是什么',
27
+ sample: '以家族为经营单元的长线修仙群像文,用几十代人的接力写一个小家族如何变成庞然大物',
28
+ },
29
+ {
30
+ id: 'reader_value',
31
+ label: '读者与情绪价值',
32
+ description: '目标读者是谁、提供什么情绪价值;尽量引用榜上同类书',
33
+ sample: '面向喜欢慢热经营与设定自洽的成年读者,提供「看着家底一点点变大」的踏实感与归属感',
34
+ },
35
+ {
36
+ id: 'golden_finger',
37
+ label: '金手指',
38
+ description: '能力、代价、限制,三者都要写',
39
+ sample: '铜镜或残魂式的家族传承,只给家族少数人有限度的启发与预警,代价绑定血脉与代际更替',
40
+ },
41
+ {
42
+ id: 'core_setting',
43
+ label: '核心设定',
44
+ description: '世界、规则、主线;要能自洽,不是副本',
45
+ sample: '完整的修真文明,规则包含灵气、境界、功法与家族长幼秩序,主线是家族从村落到一方势力的数代经营史',
46
+ },
47
+ {
48
+ id: 'payoff_model',
49
+ label: '爽点模式',
50
+ description: '前 10 章、每 3 章、长线各给什么爽点',
51
+ sample: '前 10 章立家族账目与人际矛盾,每 3 章一次资源或关系突破,长线以代际交替给出大尺度成就感',
52
+ },
53
+ {
54
+ id: 'emotion_curve',
55
+ label: '情绪价值曲线',
56
+ description: '压抑—释放—升级的节奏',
57
+ sample: '前期压抑在资源匮乏与族内分歧,中期靠经营突破释放,后期用代际传承升级成就感',
58
+ },
59
+ {
60
+ id: 'opening_hook',
61
+ label: '开篇切入点',
62
+ description: '第 1 章的场景、冲突与钩子',
63
+ sample: '第一章让传承物与外部威胁同时出现,主角在家族账目与生存压力之间做第一个选择',
64
+ },
65
+ {
66
+ id: 'pacing_plan',
67
+ label: '节奏规划',
68
+ description: '开篇 / 前期 / 中期 / 后期各阶段的推进方式',
69
+ sample: '开篇 10 章立设定,前期 30 章完成第一次资源积累,中期以卷为单位扩张,后期进入代际交替',
70
+ },
71
+ {
72
+ id: 'character_design',
73
+ label: '人物设定',
74
+ description: '主角、对手、关键配角与关系网',
75
+ sample: '主角是负责账目的旁支子弟,对手是同族嫡系与外部势力,关键配角覆盖族老、商路伙伴与叛徒',
76
+ },
77
+ {
78
+ id: 'differentiation',
79
+ label: '差异化策略',
80
+ description: '与榜上同类的具体区别,不能只说“更爽”',
81
+ sample: '同类多为个人修仙,本书把经营单位改成家族,用代际接力替代单主角升级',
82
+ },
83
+ {
84
+ id: 'risk_validation',
85
+ label: '风险与验证',
86
+ description: '主要风险与可验证指标',
87
+ sample: '风险是代际切换掉读者,验证指标看前 20 章追读、家族成员辨识度与每 3 章突破是否兑现',
88
+ },
89
+ ];
90
+
91
+ /** 11 项设计标签 —— 顺序即模型照抄的顺序 */
92
+ export const DIRECTION_LABELS = DIRECTION_FIELDS.map(f => f.label);
93
+
94
+ /** 标签 → 字段 ID(旧 direction_design 文本迁移用) */
95
+ export const DIRECTION_FIELD_BY_LABEL = new Map(
96
+ DIRECTION_FIELDS.map(f => [f.label, f])
97
+ );
98
+
99
+ /** 字段 ID → 定义 */
100
+ export const DIRECTION_FIELD_BY_ID = new Map(
101
+ DIRECTION_FIELDS.map(f => [f.id, f])
102
+ );
103
+
104
+ /** 校验用的标签正则(加粗形式) */
105
+ export const DIRECTION_LABEL_RE = new RegExp(
106
+ '^-\\s*\\*\\*(?:' + DIRECTION_LABELS.join('|') + ')\\*\\*\\s*[::]',
107
+ 'm'
108
+ );
109
+
110
+ /** 方向模板(模型照抄这 11 行) */
111
+ export function directionTemplateBlock() {
112
+ return DIRECTION_LABELS.map(l => `- **${l}**:...`).join('\n');
113
+ }
114
+
115
+ /**
116
+ * 生成 python 源码里的标签列表(校验脚本用)。
117
+ *
118
+ * 校验脚本必须拿**同一份**清单去查 —— 手写一遍就等于埋了一个新的漂移点。
119
+ *
120
+ * @param {number} [perLine=4] 每行放几个标签
121
+ * @returns {string[]} python 源码行(第一行带 `labels = [`,后续行对齐)
122
+ */
123
+ export function directionLabelsPythonLines(perLine = 4) {
124
+ const head = 'labels = [';
125
+ const pad = ' '.repeat(head.length);
126
+ const out = [];
127
+ for (let i = 0; i < DIRECTION_LABELS.length; i += perLine) {
128
+ const chunk = DIRECTION_LABELS.slice(i, i + perLine).map(l => `'${l}'`);
129
+ const prefix = i === 0 ? head : pad;
130
+ const suffix = i + perLine >= DIRECTION_LABELS.length ? ']' : ',';
131
+ out.push(prefix + chunk.join(', ') + suffix);
132
+ }
133
+ return out;
134
+ }
135
+
136
+ /** 正则里要转义的字符 */
137
+ function escapeRe(s) {
138
+ return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
139
+ }
140
+
141
+ /** 数汉字(CJK 统一表意文字,含扩展 A) */
142
+ function cjkCount(text) {
143
+ let n = 0;
144
+ for (const ch of String(text || '')) {
145
+ const c = ch.codePointAt(0);
146
+ if ((c >= 0x4e00 && c <= 0x9fff) || (c >= 0x3400 && c <= 0x4dbf)) n++;
147
+ }
148
+ return n;
149
+ }
150
+
151
+ /**
152
+ * 校验一份 `direction_design` 文本:11 项标签是否齐全、每项内容是否够厚。
153
+ *
154
+ * 与 scan 的 `validateReportBody` 用同一份 `DIRECTION_LABELS`,
155
+ * 但这里只查 `direction_design` 这一个字段(topic 侧写盘前用)。
156
+ *
157
+ * @param {string} text
158
+ * @param {{minChars?: number}} [opts]
159
+ * @returns {{ok: boolean, missing: string[], short: Array<{label: string, chars: number}>}}
160
+ */
161
+ export function inspectDirectionDesign(text, opts = {}) {
162
+ const minChars = Number.isInteger(opts.minChars) ? opts.minChars : 20;
163
+ const lines = String(text || '').split('\n');
164
+ const missing = [];
165
+ const short = [];
166
+
167
+ for (const label of DIRECTION_LABELS) {
168
+ const re = new RegExp(
169
+ '^\\s*(?:[-*+]\\s+)?\\*{0,2}' + escapeRe(label) + '\\*{0,2}\\s*[::]\\s*(.*)$'
170
+ );
171
+ const hit = lines.map(l => re.exec(l)).find(Boolean);
172
+ if (!hit) {
173
+ missing.push(label);
174
+ continue;
175
+ }
176
+ const chars = cjkCount(hit[1]);
177
+ if (chars < minChars) short.push({ label, chars });
178
+ }
179
+
180
+ return { ok: missing.length === 0 && short.length === 0, missing, short };
181
+ }
182
+
183
+ /**
184
+ * 把旧的 `direction_design` 文本解析成 11 个字段。
185
+ *
186
+ * 旧格式每项一行:`- **定位**:内容`。只取标签后第一行的内容;
187
+ * 老产物本来就没有跨行条目。
188
+ *
189
+ * @param {string} text
190
+ * @returns {Record<string, string>} 字段 ID → 内容(没写到的项不出现)
191
+ */
192
+ export function parseDirectionDesign(text) {
193
+ const out = {};
194
+ const lines = String(text || '').split('\n');
195
+ for (const field of DIRECTION_FIELDS) {
196
+ const re = new RegExp(
197
+ '^\\s*(?:[-*+]\\s+)?\\*{0,2}' + escapeRe(field.label) + '\\*{0,2}\\s*[::]\\s*(.*)$'
198
+ );
199
+ const hit = lines.map(l => re.exec(l)).find(Boolean);
200
+ if (hit && hit[1].trim()) out[field.id] = hit[1].trim();
201
+ }
202
+ return out;
203
+ }
204
+
205
+ /**
206
+ * 校验 11 个设计字段:每一项都要有内容,且汉字数够。
207
+ *
208
+ * @param {Record<string, unknown>} data
209
+ * @param {{minChars?: number}} [opts]
210
+ * @returns {{ok: boolean, missing: string[], short: Array<{id: string, label: string, chars: number}>}}
211
+ */
212
+ export function inspectDirectionFields(data, opts = {}) {
213
+ const minChars = Number.isInteger(opts.minChars) ? opts.minChars : 20;
214
+ const missing = [];
215
+ const short = [];
216
+ const d = data && typeof data === 'object' ? data : {};
217
+
218
+ for (const field of DIRECTION_FIELDS) {
219
+ const v = d[field.id];
220
+ if (typeof v !== 'string' || !v.trim()) {
221
+ missing.push(field.topicLabel || field.label);
222
+ continue;
223
+ }
224
+ const chars = cjkCount(v);
225
+ if (chars < minChars) {
226
+ short.push({ id: field.id, label: field.topicLabel || field.label, chars });
227
+ }
228
+ }
229
+
230
+ return { ok: missing.length === 0 && short.length === 0, missing, short };
231
+ }
232
+
233
+ /**
234
+ * 找出正文里**缺标签的方向**(插件侧兜底)。
235
+ *
236
+ * 下游 story-builder 拿到的是残缺输入。插件这边能自己查一遍,
237
+ * 就多一道防线(提示词是"请求",插件检查才是"保证")。
238
+ *
239
+ * 只做**告警**,不判块失败:格式差异不该把一整块报告判死。
240
+ *
241
+ * @param {string} text 块正文
242
+ * @returns {string[]} 每个缺标签方向的说明(没有则空数组)
243
+ */
244
+ export function missingDirectionLabels(text) {
245
+ const lines = String(text || '').split('\n');
246
+ const dirRe = /^###\s*方向\s*\d+\s*[::]/;
247
+ const pos = [];
248
+ lines.forEach((l, i) => { if (dirRe.test(l)) pos.push(i); });
249
+
250
+ const out = [];
251
+ pos.forEach((start, k) => {
252
+ const end = k + 1 < pos.length ? pos[k + 1] : lines.length;
253
+ const section = lines.slice(start, end);
254
+ const missing = DIRECTION_LABELS.filter(lab => {
255
+ const re = new RegExp(
256
+ '^\\s*(?:[-*+]\\s+)?\\*{0,2}' + escapeRe(lab) + '\\*{0,2}\\s*[::]'
257
+ );
258
+ return !section.some(l => re.test(l));
259
+ });
260
+ if (missing.length) {
261
+ out.push(`方向 ${k + 1}(缺 ${missing.length} 项:${missing.join('、')})`);
262
+ }
263
+ });
264
+ return out;
265
+ }
@@ -26,49 +26,49 @@
26
26
 
27
27
  export const EC = {
28
28
  // ─── 001-019: bootstrap ───────────────────────────────────────
29
- EC001_INVALID_PATH: { code: 1, message: '路径不合法(不能含空格、.. 或特殊字符)' },
30
- EC002_ALREADY_INITIALIZED: { code: 2, message: '项目已存在,不能重复 init' },
31
- EC003_INVALID_PROJECT_ROOT:{ code: 3, message: '项目根路径不是绝对路径或不存在' },
32
- EC017_INIT_FAILED: { code: 17, message: '初始化失败' },
33
- EC018_DIR_NOT_FOUND: { code: 18, message: '目录不存在' },
34
- EC019_SENSITIVE_PATH: { code: 19, message: '拒绝访问敏感系统路径' },
29
+ EC001_INVALID_PATH: { code: 1, message: '路径不合法(不能包含 .. 或 shell 特殊字符)。' },
30
+ EC002_ALREADY_INITIALIZED: { code: 2, message: '项目已初始化,不能重复执行 init。' },
31
+ EC003_INVALID_PROJECT_ROOT:{ code: 3, message: '项目根路径不是绝对路径,或不存在。' },
32
+ EC017_INIT_FAILED: { code: 17, message: '初始化失败。' },
33
+ EC018_DIR_NOT_FOUND: { code: 18, message: '目录不存在。' },
34
+ EC019_SENSITIVE_PATH: { code: 19, message: '已拒绝访问系统受保护路径。' },
35
35
 
36
36
  // ─── 020-039: scan ────────────────────────────────────────────
37
37
  EC020_BROWSER_LAUNCH: { code: 20, message: '浏览器启动失败' },
38
- EC021_BROWSER_DISCONNECTED: { code: 21, message: '浏览器在采集过程中断开' },
39
- EC022_PAGE_GOTO_TIMEOUT: { code: 22, message: '页面导航超时' },
40
- EC023_WAIT_RENDER_TIMEOUT: { code: 23, message: '等待榜单渲染超时' },
41
- EC024_SCRAPER_TIMEOUT: { code: 24, message: 'scraper 子进程超时' },
42
- EC025_SCRAPER_NON_JSON: { code: 25, message: 'scraper 输出非 JSON' },
43
- EC026_OCR_FAILED: { code: 26, message: 'OCR 识别失败' },
44
- EC027_NO_OUTPUT_FILES: { code: 27, message: '未找到采集输出文件' },
45
- EC028_INVALID_RANK: { code: 28, message: '未知或非法的榜单 ID' },
46
- EC029_INVALID_PLATFORM_SCAN:{ code: 29, message: '不支持的平台' },
38
+ EC021_BROWSER_DISCONNECTED: { code: 21, message: '浏览器在采集过程中断开。' },
39
+ EC022_PAGE_GOTO_TIMEOUT: { code: 22, message: '页面导航超时。' },
40
+ EC023_WAIT_RENDER_TIMEOUT: { code: 23, message: '等待榜单渲染超时。' },
41
+ EC024_SCRAPER_TIMEOUT: { code: 24, message: '采集子进程执行超时。' },
42
+ EC025_SCRAPER_NON_JSON: { code: 25, message: '采集进程输出不是合法 JSON。' },
43
+ EC026_OCR_FAILED: { code: 26, message: 'OCR 识别失败。' },
44
+ EC027_NO_OUTPUT_FILES: { code: 27, message: '未找到采集输出文件。' },
45
+ EC028_INVALID_RANK: { code: 28, message: '榜单 ID 无效或不受支持。' },
46
+ EC029_INVALID_PLATFORM_SCAN:{ code: 29, message: '平台不受支持。' },
47
47
 
48
48
  // ─── 040-059: topic ───────────────────────────────────────────
49
- EC040_INVALID_PLATFORM: { code: 40, message: 'platform 参数非法' },
50
- EC041_INVALID_GENRE: { code: 41, message: 'genre 参数非法' },
51
- EC042_MISSING_REQUIRED: { code: 42, message: '必填参数缺失' },
52
- EC043_PHASE_NOT_TOPIC: { code: 43, message: '当前 phase 不允许 fancy-topic' },
53
- EC044_TOPIC_STATE_MISSING: { code: 44, message: '_story_state.json 不存在' },
54
- EC045_TOPIC_WRITE_FAILED: { code: 45, message: 'fancy-topic 写盘失败' },
55
- EC046_INVALID_WORDS: { code: 46, message: '目标字数不合法(必须为正整数)' },
56
- EC047_INVALID_CHAPTERS: { code: 47, message: '目标章数不合法(必须为正整数)' },
57
- EC048_PLATFORM_NOT_FOUND: { code: 48, message: 'topic_decision 中找不到该平台的内容块' },
49
+ EC040_INVALID_PLATFORM: { code: 40, message: 'platform 参数无效。' },
50
+ EC041_INVALID_GENRE: { code: 41, message: 'genre 参数无效。' },
51
+ EC042_MISSING_REQUIRED: { code: 42, message: '缺少必填参数。' },
52
+ EC043_PHASE_NOT_TOPIC: { code: 43, message: '当前工作区阶段不允许执行 fancy-topic。' },
53
+ EC044_TOPIC_STATE_MISSING: { code: 44, message: '未找到 _story_state.json。' },
54
+ EC045_TOPIC_WRITE_FAILED: { code: 45, message: 'fancy-topic 写入文件失败。' },
55
+ EC046_INVALID_WORDS: { code: 46, message: '目标字数无效,必须为正整数。' },
56
+ EC047_INVALID_CHAPTERS: { code: 47, message: '目标章数无效,必须为正整数。' },
57
+ EC048_PLATFORM_NOT_FOUND: { code: 48, message: 'topic_decision 中未找到该平台的内容块。' },
58
58
 
59
59
  // ─── 120-139: shared ──────────────────────────────────────────
60
- EC120_ATOMIC_WRITE_FAIL: { code: 120, message: '原子写文件失败' },
61
- EC121_PHASE_ADVANCE_FAIL: { code: 121, message: 'phase 推进失败' },
62
- EC122_PERMISSION_DENIED: { code: 122, message: '权限不足' },
63
- EC123_JSON_PARSE_FAIL: { code: 123, message: 'JSON 解析失败' },
60
+ EC120_ATOMIC_WRITE_FAIL: { code: 120, message: '原子写入文件失败。' },
61
+ EC121_PHASE_ADVANCE_FAIL: { code: 121, message: '阶段状态推进失败。' },
62
+ EC122_PERMISSION_DENIED: { code: 122, message: '权限不足。' },
63
+ EC123_JSON_PARSE_FAIL: { code: 123, message: 'JSON 解析失败。' },
64
64
  // "项目未初始化"与 EC002(项目已存在,不能重复 init)语义相反,
65
65
  // 不能复用。此前 run-scan.js / select-topic.js 都把"未初始化"报成 EC002,
66
66
  // 导致 code_name 与 message 自相矛盾(退出码 2 实际表示"未初始化")。
67
- EC124_PROJECT_NOT_INITIALIZED: { code: 124, message: '项目未初始化(.fancy-deployed 不存在)' },
67
+ EC124_PROJECT_NOT_INITIALIZED: { code: 124, message: '项目尚未初始化(.fancy-deployed 不存在)。' },
68
68
 
69
69
  // ─── 200+: system ─────────────────────────────────────────────
70
- EC200_SPAWN_FAIL: { code: 200, message: '子进程启动失败' },
71
- EC201_SCRIPT_TIMEOUT: { code: 201, message: '脚本执行超时' },
70
+ EC200_SPAWN_FAIL: { code: 200, message: '子进程启动失败。' },
71
+ EC201_SCRIPT_TIMEOUT: { code: 201, message: '脚本执行超时。' },
72
72
  };
73
73
 
74
74
  /**
package/lib/infra.js CHANGED
@@ -84,6 +84,7 @@
84
84
 
85
85
  import { spawn } from 'child_process';
86
86
  import { randomUUID } from 'crypto';
87
+ import { join, resolve } from 'path';
87
88
 
88
89
  // --------------------------------------------------------------------------
89
90
  // ANSI escape cleanup
@@ -288,7 +289,29 @@ export function buildUserMessage(text) {
288
289
  * @param {string|object|null|undefined} content - 提示词正文
289
290
  * @returns {{ok: boolean, error: string|null}}
290
291
  */
291
- export function llmFollowup(invocation, content) {
292
+ /**
293
+ * 给所有插件提示词追加统一的临时文件规则。
294
+ *
295
+ * 绝对路径来自会话 cwd(工作区目录),不依赖宿主进程的 process.cwd()。
296
+ */
297
+ export function tempDirInstruction(invocation) {
298
+ const cwd = invocation && invocation.agent && invocation.agent.session
299
+ && invocation.agent.session.header
300
+ && invocation.agent.session.header.cwd;
301
+ if (typeof cwd !== 'string' || !cwd.trim()) return '';
302
+ const tempDir = join(resolve(cwd), '.tmp');
303
+ return [
304
+ '',
305
+ '【临时文件规则】',
306
+ '如果需要创建临时文件,必须创建在以下绝对路径目录中:',
307
+ tempDir,
308
+ '创建目录可用:mkdir -p ' + tempDir,
309
+ '不要把临时文件直接放在工作区目录、/tmp 或其他位置。',
310
+ '临时文件创建后不允许删除(任务结束也不要清理);需要重写时直接覆盖同一个文件。',
311
+ ].join('\n');
312
+ }
313
+
314
+ export function llmFollowup(invocation, content, options) {
292
315
  let prompt;
293
316
  try {
294
317
  if (typeof content === 'string') prompt = content;
@@ -310,7 +333,9 @@ export function llmFollowup(invocation, content) {
310
333
  return { ok: false, error: reason };
311
334
  }
312
335
  try {
313
- const message = buildUserMessage(prompt);
336
+ const appendTempRule = !options || options.appendTempRule !== false;
337
+ const finalPrompt = appendTempRule ? prompt + tempDirInstruction(invocation) : prompt;
338
+ const message = buildUserMessage(finalPrompt);
314
339
  const agent = invocation.agent;
315
340
  // DSH 对两者的语义区分得很明确:
316
341
  // followup() — "排一个普通的后续 turn 并唤醒驱动器,
@@ -422,7 +447,7 @@ export async function broadcastResult(invocation, pluginName, result) {
422
447
  return { activated: false, reason: 'already_dispatched' };
423
448
  }
424
449
  const text = buildBroadcastText(pluginName, result);
425
- const follow = await llmFollowup(invocation, text);
450
+ const follow = await llmFollowup(invocation, text, { appendTempRule: false });
426
451
  return follow.ok === true
427
452
  ? { activated: true, reason: 'activated' }
428
453
  : { activated: false, reason: 'dispatch_failed', error: follow.error };
@@ -447,7 +472,7 @@ export async function broadcastResult(invocation, pluginName, result) {
447
472
  * spawnScript 以 detached:true 起子进程,proc.pid 即新进程组的组长,
448
473
  * process.kill(-pid, sig) 会把整组一起收掉 —— 其中包含 playwright 拉起的
449
474
  * chromium。此前只调 proc.kill() 杀直接子进程,chromium 会变成孤儿进程,
450
- * 继续占 CPU/内存并往项目目录写盘。
475
+ * 继续占 CPU/内存并往工作区目录写盘。
451
476
  *
452
477
  * Windows 的 process.kill 不支持负 pid,此处退化为只杀直接子进程
453
478
  * (与改动前行为一致,不劣化)。