@zwbigi/ink-xy 0.1.2 → 0.1.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 (598) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +2 -2
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/server/app/_global-error.html +1 -1
  5. package/.next/server/app/_global-error.rsc +1 -1
  6. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +1 -1
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  15. package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  16. package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  18. package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  19. package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  20. package/.next/server/app/api/inkos/route.js.nft.json +1 -1
  21. package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
  22. package/.next/server/app/api/skills/search/route.js.nft.json +1 -1
  23. package/.next/server/app/index.html +1 -1
  24. package/.next/server/app/index.rsc +1 -1
  25. package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  26. package/.next/server/app/index.segments/_full.segment.rsc +1 -1
  27. package/.next/server/app/index.segments/_head.segment.rsc +1 -1
  28. package/.next/server/app/index.segments/_index.segment.rsc +1 -1
  29. package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  30. package/.next/server/app-paths-manifest.json +2 -2
  31. package/.next/server/chunks/162.js +1 -1
  32. package/.next/server/middleware-build-manifest.js +1 -1
  33. package/.next/server/pages/404.html +1 -1
  34. package/.next/server/pages/500.html +1 -1
  35. package/.next/trace +4 -4
  36. package/.next/trace-build +1 -1
  37. package/bin/pi-web.js +4 -1
  38. package/inkos/.env.example +20 -0
  39. package/inkos/.node-version +1 -0
  40. package/inkos/.nvmrc +1 -0
  41. package/inkos/CHANGELOG.md +787 -0
  42. package/inkos/CONTRIBUTING.md +89 -0
  43. package/inkos/LICENSE +661 -0
  44. package/inkos/README.en.md +483 -0
  45. package/inkos/README.ja.md +461 -0
  46. package/inkos/README.md +272 -0
  47. package/inkos/assets/15qun.jpg +0 -0
  48. package/inkos/assets/41777702961_.pic.jpg +0 -0
  49. package/inkos/assets/inkos-short-demo-cover.png +0 -0
  50. package/inkos/assets/inkos-text.svg +40 -0
  51. package/inkos/assets/logo.svg +47 -0
  52. package/inkos/assets/screenshot-chapters.png +0 -0
  53. package/inkos/assets/screenshot-pipeline.png +0 -0
  54. package/inkos/assets/screenshot-state.png +0 -0
  55. package/inkos/assets/screenshot-terminal.png +0 -0
  56. package/inkos/assets/wechat-group-v8.jpg +0 -0
  57. package/inkos/package.json +42 -0
  58. package/inkos/packages/cli/package.json +74 -0
  59. package/inkos/packages/cli/src/__tests__/analytics.test.ts +154 -0
  60. package/inkos/packages/cli/src/__tests__/cli-integration.test.ts +1031 -0
  61. package/inkos/packages/cli/src/__tests__/daemon.test.ts +93 -0
  62. package/inkos/packages/cli/src/__tests__/doctor.test.ts +36 -0
  63. package/inkos/packages/cli/src/__tests__/interact-command.test.ts +142 -0
  64. package/inkos/packages/cli/src/__tests__/interaction-tools.test.ts +107 -0
  65. package/inkos/packages/cli/src/__tests__/llm-overrides.test.ts +25 -0
  66. package/inkos/packages/cli/src/__tests__/localization.test.ts +121 -0
  67. package/inkos/packages/cli/src/__tests__/progress-text.test.ts +92 -0
  68. package/inkos/packages/cli/src/__tests__/project-bootstrap.test.ts +71 -0
  69. package/inkos/packages/cli/src/__tests__/publish-package.test.ts +272 -0
  70. package/inkos/packages/cli/src/__tests__/revision-command.test.ts +82 -0
  71. package/inkos/packages/cli/src/__tests__/runtime-requirements.test.ts +89 -0
  72. package/inkos/packages/cli/src/__tests__/short-fiction-command.test.ts +48 -0
  73. package/inkos/packages/cli/src/__tests__/studio-runtime.test.ts +142 -0
  74. package/inkos/packages/cli/src/__tests__/studio.test.ts +87 -0
  75. package/inkos/packages/cli/src/__tests__/tui-activity-state.test.ts +20 -0
  76. package/inkos/packages/cli/src/__tests__/tui-agent-session.test.ts +213 -0
  77. package/inkos/packages/cli/src/__tests__/tui-chat-depth.test.ts +27 -0
  78. package/inkos/packages/cli/src/__tests__/tui-chat-draft.test.ts +44 -0
  79. package/inkos/packages/cli/src/__tests__/tui-command.test.ts +86 -0
  80. package/inkos/packages/cli/src/__tests__/tui-composer-caret.test.ts +46 -0
  81. package/inkos/packages/cli/src/__tests__/tui-composer-display.test.ts +40 -0
  82. package/inkos/packages/cli/src/__tests__/tui-dashboard.test.tsx +219 -0
  83. package/inkos/packages/cli/src/__tests__/tui-effects-i18n.test.ts +29 -0
  84. package/inkos/packages/cli/src/__tests__/tui-i18n.test.ts +22 -0
  85. package/inkos/packages/cli/src/__tests__/tui-input-chrome.test.ts +10 -0
  86. package/inkos/packages/cli/src/__tests__/tui-input-history.test.ts +40 -0
  87. package/inkos/packages/cli/src/__tests__/tui-layout.test.ts +55 -0
  88. package/inkos/packages/cli/src/__tests__/tui-local-commands.test.ts +47 -0
  89. package/inkos/packages/cli/src/__tests__/tui-session-store.test.ts +81 -0
  90. package/inkos/packages/cli/src/__tests__/tui-setup-i18n.test.ts +31 -0
  91. package/inkos/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts +33 -0
  92. package/inkos/packages/cli/src/commands/agent.ts +65 -0
  93. package/inkos/packages/cli/src/commands/analytics.ts +77 -0
  94. package/inkos/packages/cli/src/commands/audit.ts +52 -0
  95. package/inkos/packages/cli/src/commands/book.ts +260 -0
  96. package/inkos/packages/cli/src/commands/compose.ts +50 -0
  97. package/inkos/packages/cli/src/commands/config.ts +328 -0
  98. package/inkos/packages/cli/src/commands/consolidate.ts +50 -0
  99. package/inkos/packages/cli/src/commands/daemon.ts +121 -0
  100. package/inkos/packages/cli/src/commands/detect.ts +125 -0
  101. package/inkos/packages/cli/src/commands/doctor.ts +391 -0
  102. package/inkos/packages/cli/src/commands/draft.ts +43 -0
  103. package/inkos/packages/cli/src/commands/eval.ts +217 -0
  104. package/inkos/packages/cli/src/commands/export.ts +45 -0
  105. package/inkos/packages/cli/src/commands/fanfic.ts +183 -0
  106. package/inkos/packages/cli/src/commands/genre.ts +160 -0
  107. package/inkos/packages/cli/src/commands/import.ts +158 -0
  108. package/inkos/packages/cli/src/commands/init.ts +47 -0
  109. package/inkos/packages/cli/src/commands/interact.ts +109 -0
  110. package/inkos/packages/cli/src/commands/plan.ts +54 -0
  111. package/inkos/packages/cli/src/commands/radar.ts +60 -0
  112. package/inkos/packages/cli/src/commands/review.ts +253 -0
  113. package/inkos/packages/cli/src/commands/revise.ts +58 -0
  114. package/inkos/packages/cli/src/commands/short-fiction.ts +294 -0
  115. package/inkos/packages/cli/src/commands/status.ts +138 -0
  116. package/inkos/packages/cli/src/commands/studio.ts +194 -0
  117. package/inkos/packages/cli/src/commands/style.ts +99 -0
  118. package/inkos/packages/cli/src/commands/tui.ts +18 -0
  119. package/inkos/packages/cli/src/commands/update.ts +45 -0
  120. package/inkos/packages/cli/src/commands/write.ts +324 -0
  121. package/inkos/packages/cli/src/index.ts +5 -0
  122. package/inkos/packages/cli/src/interaction/tools.ts +49 -0
  123. package/inkos/packages/cli/src/localization.ts +215 -0
  124. package/inkos/packages/cli/src/program.ts +106 -0
  125. package/inkos/packages/cli/src/progress-text.ts +85 -0
  126. package/inkos/packages/cli/src/project-bootstrap.ts +175 -0
  127. package/inkos/packages/cli/src/runtime-requirements.ts +135 -0
  128. package/inkos/packages/cli/src/tui/__tests__/markdown.test.ts +64 -0
  129. package/inkos/packages/cli/src/tui/activity-state.ts +41 -0
  130. package/inkos/packages/cli/src/tui/agent-input.ts +264 -0
  131. package/inkos/packages/cli/src/tui/ansi.ts +72 -0
  132. package/inkos/packages/cli/src/tui/app.ts +130 -0
  133. package/inkos/packages/cli/src/tui/chat-depth.ts +22 -0
  134. package/inkos/packages/cli/src/tui/chat-draft.ts +42 -0
  135. package/inkos/packages/cli/src/tui/composer-caret.ts +22 -0
  136. package/inkos/packages/cli/src/tui/composer-display.ts +26 -0
  137. package/inkos/packages/cli/src/tui/dashboard-model.ts +164 -0
  138. package/inkos/packages/cli/src/tui/dashboard.tsx +544 -0
  139. package/inkos/packages/cli/src/tui/effects.ts +542 -0
  140. package/inkos/packages/cli/src/tui/i18n.ts +278 -0
  141. package/inkos/packages/cli/src/tui/input-history.ts +69 -0
  142. package/inkos/packages/cli/src/tui/local-commands.ts +55 -0
  143. package/inkos/packages/cli/src/tui/markdown.ts +64 -0
  144. package/inkos/packages/cli/src/tui/session-store.ts +6 -0
  145. package/inkos/packages/cli/src/tui/setup.ts +397 -0
  146. package/inkos/packages/cli/src/tui/slash-autocomplete.ts +62 -0
  147. package/inkos/packages/cli/src/tui/theme.ts +17 -0
  148. package/inkos/packages/cli/src/utils.ts +222 -0
  149. package/inkos/packages/cli/tsconfig.json +9 -0
  150. package/inkos/packages/core/genres/cozy.md +43 -0
  151. package/inkos/packages/core/genres/cultivation.md +42 -0
  152. package/inkos/packages/core/genres/dungeon-core.md +40 -0
  153. package/inkos/packages/core/genres/horror.md +51 -0
  154. package/inkos/packages/core/genres/isekai.md +43 -0
  155. package/inkos/packages/core/genres/litrpg.md +43 -0
  156. package/inkos/packages/core/genres/other.md +24 -0
  157. package/inkos/packages/core/genres/progression.md +41 -0
  158. package/inkos/packages/core/genres/romantasy.md +45 -0
  159. package/inkos/packages/core/genres/sci-fi.md +42 -0
  160. package/inkos/packages/core/genres/system-apocalypse.md +40 -0
  161. package/inkos/packages/core/genres/tower-climber.md +41 -0
  162. package/inkos/packages/core/genres/urban.md +53 -0
  163. package/inkos/packages/core/genres/xianxia.md +46 -0
  164. package/inkos/packages/core/genres/xuanhuan.md +64 -0
  165. package/inkos/packages/core/package.json +61 -0
  166. package/inkos/packages/core/src/__tests__/agent-max-tokens-policy.test.ts +29 -0
  167. package/inkos/packages/core/src/__tests__/agent-session.test.ts +866 -0
  168. package/inkos/packages/core/src/__tests__/agent-system-prompt.test.ts +167 -0
  169. package/inkos/packages/core/src/__tests__/agent-tools-params.test.ts +197 -0
  170. package/inkos/packages/core/src/__tests__/agent-tools.test.ts +421 -0
  171. package/inkos/packages/core/src/__tests__/ai-tells.test.ts +90 -0
  172. package/inkos/packages/core/src/__tests__/architect-phase5-consolidated.test.ts +445 -0
  173. package/inkos/packages/core/src/__tests__/architect-phase5.test.ts +455 -0
  174. package/inkos/packages/core/src/__tests__/architect-phase7.test.ts +210 -0
  175. package/inkos/packages/core/src/__tests__/architect.test.ts +859 -0
  176. package/inkos/packages/core/src/__tests__/audit-parse.test.ts +78 -0
  177. package/inkos/packages/core/src/__tests__/book-id.test.ts +26 -0
  178. package/inkos/packages/core/src/__tests__/book-session-store.test.ts +447 -0
  179. package/inkos/packages/core/src/__tests__/book-session.test.ts +113 -0
  180. package/inkos/packages/core/src/__tests__/chapter-analyzer.test.ts +574 -0
  181. package/inkos/packages/core/src/__tests__/chapter-memo-parser.test.ts +247 -0
  182. package/inkos/packages/core/src/__tests__/chapter-persistence.test.ts +198 -0
  183. package/inkos/packages/core/src/__tests__/chapter-review-cycle.test.ts +294 -0
  184. package/inkos/packages/core/src/__tests__/chapter-splitter.test.ts +156 -0
  185. package/inkos/packages/core/src/__tests__/chapter-state-recovery.test.ts +235 -0
  186. package/inkos/packages/core/src/__tests__/chapter-truth-validation.test.ts +253 -0
  187. package/inkos/packages/core/src/__tests__/composer.test.ts +627 -0
  188. package/inkos/packages/core/src/__tests__/config-loader.test.ts +325 -0
  189. package/inkos/packages/core/src/__tests__/config-migration.test.ts +102 -0
  190. package/inkos/packages/core/src/__tests__/consolidator.test.ts +32 -0
  191. package/inkos/packages/core/src/__tests__/context-filter.test.ts +60 -0
  192. package/inkos/packages/core/src/__tests__/context-transform.test.ts +108 -0
  193. package/inkos/packages/core/src/__tests__/continuity.test.ts +391 -0
  194. package/inkos/packages/core/src/__tests__/detection-insights.test.ts +59 -0
  195. package/inkos/packages/core/src/__tests__/detector.test.ts +86 -0
  196. package/inkos/packages/core/src/__tests__/draft-directive-parser.test.ts +386 -0
  197. package/inkos/packages/core/src/__tests__/edit-controller.test.ts +190 -0
  198. package/inkos/packages/core/src/__tests__/effective-llm-config.test.ts +486 -0
  199. package/inkos/packages/core/src/__tests__/fanfic-dimensions.test.ts +58 -0
  200. package/inkos/packages/core/src/__tests__/fanfic-models.test.ts +69 -0
  201. package/inkos/packages/core/src/__tests__/governed-working-set.test.ts +155 -0
  202. package/inkos/packages/core/src/__tests__/hook-arbiter.test.ts +124 -0
  203. package/inkos/packages/core/src/__tests__/hook-governance.test.ts +228 -0
  204. package/inkos/packages/core/src/__tests__/hook-health.test.ts +166 -0
  205. package/inkos/packages/core/src/__tests__/hook-ledger-validator.test.ts +236 -0
  206. package/inkos/packages/core/src/__tests__/hook-promotion.test.ts +192 -0
  207. package/inkos/packages/core/src/__tests__/hook-stale-detection.test.ts +136 -0
  208. package/inkos/packages/core/src/__tests__/index-notify-lazy.test.ts +20 -0
  209. package/inkos/packages/core/src/__tests__/interaction-chat-tokens.test.ts +170 -0
  210. package/inkos/packages/core/src/__tests__/interaction-models.test.ts +155 -0
  211. package/inkos/packages/core/src/__tests__/interaction-nl-router.test.ts +223 -0
  212. package/inkos/packages/core/src/__tests__/interaction-runtime.test.ts +633 -0
  213. package/inkos/packages/core/src/__tests__/interaction-tools.test.ts +343 -0
  214. package/inkos/packages/core/src/__tests__/length-metrics.test.ts +82 -0
  215. package/inkos/packages/core/src/__tests__/length-normalizer.test.ts +331 -0
  216. package/inkos/packages/core/src/__tests__/list-models.test.ts +109 -0
  217. package/inkos/packages/core/src/__tests__/llm-env.test.ts +31 -0
  218. package/inkos/packages/core/src/__tests__/logger.test.ts +175 -0
  219. package/inkos/packages/core/src/__tests__/long-span-fatigue.test.ts +160 -0
  220. package/inkos/packages/core/src/__tests__/memory-retrieval.test.ts +1303 -0
  221. package/inkos/packages/core/src/__tests__/models.test.ts +918 -0
  222. package/inkos/packages/core/src/__tests__/outline-paths.test.ts +97 -0
  223. package/inkos/packages/core/src/__tests__/path-safety.test.ts +22 -0
  224. package/inkos/packages/core/src/__tests__/persisted-governed-plan.test.ts +134 -0
  225. package/inkos/packages/core/src/__tests__/phase5-cleanup.test.ts +393 -0
  226. package/inkos/packages/core/src/__tests__/phase5-hotfix.test.ts +288 -0
  227. package/inkos/packages/core/src/__tests__/phase7-hotfix.test.ts +614 -0
  228. package/inkos/packages/core/src/__tests__/pipeline-agent.test.ts +354 -0
  229. package/inkos/packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts +317 -0
  230. package/inkos/packages/core/src/__tests__/pipeline-runner.test.ts +5200 -0
  231. package/inkos/packages/core/src/__tests__/planner-context.test.ts +137 -0
  232. package/inkos/packages/core/src/__tests__/planner-prompts-ratio.test.ts +11 -0
  233. package/inkos/packages/core/src/__tests__/planner-prompts.test.ts +171 -0
  234. package/inkos/packages/core/src/__tests__/planner.test.ts +362 -0
  235. package/inkos/packages/core/src/__tests__/planning-materials.test.ts +90 -0
  236. package/inkos/packages/core/src/__tests__/polisher.test.ts +189 -0
  237. package/inkos/packages/core/src/__tests__/post-write-validator.test.ts +291 -0
  238. package/inkos/packages/core/src/__tests__/probe.test.ts +77 -0
  239. package/inkos/packages/core/src/__tests__/project-interaction.test.ts +241 -0
  240. package/inkos/packages/core/src/__tests__/provider.test.ts +953 -0
  241. package/inkos/packages/core/src/__tests__/providers-group.test.ts +34 -0
  242. package/inkos/packages/core/src/__tests__/providers-lookup.test.ts +81 -0
  243. package/inkos/packages/core/src/__tests__/providers-schema.test.ts +158 -0
  244. package/inkos/packages/core/src/__tests__/proxy-fetch.test.ts +75 -0
  245. package/inkos/packages/core/src/__tests__/revise-foundation.test.ts +514 -0
  246. package/inkos/packages/core/src/__tests__/reviser.test.ts +859 -0
  247. package/inkos/packages/core/src/__tests__/runtime-state-store.test.ts +388 -0
  248. package/inkos/packages/core/src/__tests__/scheduler.test.ts +123 -0
  249. package/inkos/packages/core/src/__tests__/secrets-migration.test.ts +71 -0
  250. package/inkos/packages/core/src/__tests__/secrets.test.ts +95 -0
  251. package/inkos/packages/core/src/__tests__/sensitive-words.test.ts +88 -0
  252. package/inkos/packages/core/src/__tests__/service-presets-regression.test.ts +73 -0
  253. package/inkos/packages/core/src/__tests__/service-resolver-regression.test.ts +75 -0
  254. package/inkos/packages/core/src/__tests__/service-resolver.test.ts +228 -0
  255. package/inkos/packages/core/src/__tests__/session-transcript-restore.test.ts +1311 -0
  256. package/inkos/packages/core/src/__tests__/session-transcript.test.ts +195 -0
  257. package/inkos/packages/core/src/__tests__/settler-delta-parser.test.ts +133 -0
  258. package/inkos/packages/core/src/__tests__/short-fiction-public.test.ts +241 -0
  259. package/inkos/packages/core/src/__tests__/spot-fix-patches.test.ts +104 -0
  260. package/inkos/packages/core/src/__tests__/state-manager.test.ts +1298 -0
  261. package/inkos/packages/core/src/__tests__/state-projections.test.ts +130 -0
  262. package/inkos/packages/core/src/__tests__/state-reducer.test.ts +372 -0
  263. package/inkos/packages/core/src/__tests__/state-validator-agent.test.ts +165 -0
  264. package/inkos/packages/core/src/__tests__/state-validator.test.ts +122 -0
  265. package/inkos/packages/core/src/__tests__/style-analyzer.test.ts +61 -0
  266. package/inkos/packages/core/src/__tests__/temperature-constraints.test.ts +57 -0
  267. package/inkos/packages/core/src/__tests__/v13-hotfix-round4.test.ts +343 -0
  268. package/inkos/packages/core/src/__tests__/verify-service.test.ts +77 -0
  269. package/inkos/packages/core/src/__tests__/webhook.test.ts +91 -0
  270. package/inkos/packages/core/src/__tests__/writer-parser.test.ts +348 -0
  271. package/inkos/packages/core/src/__tests__/writer-prompts.test.ts +269 -0
  272. package/inkos/packages/core/src/__tests__/writer.test.ts +1360 -0
  273. package/inkos/packages/core/src/agent/agent-session.ts +737 -0
  274. package/inkos/packages/core/src/agent/agent-system-prompt.ts +199 -0
  275. package/inkos/packages/core/src/agent/agent-tools.ts +835 -0
  276. package/inkos/packages/core/src/agent/context-transform.ts +85 -0
  277. package/inkos/packages/core/src/agent/index.ts +14 -0
  278. package/inkos/packages/core/src/agents/ai-tells.ts +161 -0
  279. package/inkos/packages/core/src/agents/architect.ts +1291 -0
  280. package/inkos/packages/core/src/agents/base.ts +100 -0
  281. package/inkos/packages/core/src/agents/chapter-analyzer.ts +634 -0
  282. package/inkos/packages/core/src/agents/composer.ts +469 -0
  283. package/inkos/packages/core/src/agents/consolidator.ts +218 -0
  284. package/inkos/packages/core/src/agents/continuity.ts +824 -0
  285. package/inkos/packages/core/src/agents/detection-insights.ts +72 -0
  286. package/inkos/packages/core/src/agents/detector.ts +224 -0
  287. package/inkos/packages/core/src/agents/en-prompt-sections.ts +129 -0
  288. package/inkos/packages/core/src/agents/fanfic-canon-importer.ts +146 -0
  289. package/inkos/packages/core/src/agents/fanfic-dimensions.ts +87 -0
  290. package/inkos/packages/core/src/agents/fanfic-prompt-sections.ts +109 -0
  291. package/inkos/packages/core/src/agents/foundation-reviewer.ts +204 -0
  292. package/inkos/packages/core/src/agents/length-normalizer.ts +218 -0
  293. package/inkos/packages/core/src/agents/observer-prompts.ts +127 -0
  294. package/inkos/packages/core/src/agents/planner-context.ts +297 -0
  295. package/inkos/packages/core/src/agents/planner-prompts.ts +404 -0
  296. package/inkos/packages/core/src/agents/planner.ts +783 -0
  297. package/inkos/packages/core/src/agents/polisher.ts +153 -0
  298. package/inkos/packages/core/src/agents/post-write-validator.ts +873 -0
  299. package/inkos/packages/core/src/agents/radar-source.ts +123 -0
  300. package/inkos/packages/core/src/agents/radar.ts +120 -0
  301. package/inkos/packages/core/src/agents/reviser.ts +701 -0
  302. package/inkos/packages/core/src/agents/rules-reader.ts +155 -0
  303. package/inkos/packages/core/src/agents/sensitive-words.ts +142 -0
  304. package/inkos/packages/core/src/agents/settler-delta-parser.ts +53 -0
  305. package/inkos/packages/core/src/agents/settler-parser.ts +38 -0
  306. package/inkos/packages/core/src/agents/settler-prompts.ts +230 -0
  307. package/inkos/packages/core/src/agents/short-fiction.ts +429 -0
  308. package/inkos/packages/core/src/agents/state-validator.ts +322 -0
  309. package/inkos/packages/core/src/agents/style-analyzer.ts +93 -0
  310. package/inkos/packages/core/src/agents/writer-parser.ts +178 -0
  311. package/inkos/packages/core/src/agents/writer-prompts.ts +899 -0
  312. package/inkos/packages/core/src/agents/writer.ts +1450 -0
  313. package/inkos/packages/core/src/index.ts +392 -0
  314. package/inkos/packages/core/src/interaction/book-session-store.ts +226 -0
  315. package/inkos/packages/core/src/interaction/draft-directive-parser.ts +266 -0
  316. package/inkos/packages/core/src/interaction/edit-controller.ts +270 -0
  317. package/inkos/packages/core/src/interaction/events.ts +41 -0
  318. package/inkos/packages/core/src/interaction/export-artifact.ts +151 -0
  319. package/inkos/packages/core/src/interaction/intents.ts +63 -0
  320. package/inkos/packages/core/src/interaction/modes.ts +13 -0
  321. package/inkos/packages/core/src/interaction/nl-router.ts +258 -0
  322. package/inkos/packages/core/src/interaction/project-control.ts +150 -0
  323. package/inkos/packages/core/src/interaction/project-session-store.ts +81 -0
  324. package/inkos/packages/core/src/interaction/project-tools.ts +704 -0
  325. package/inkos/packages/core/src/interaction/request-router.ts +5 -0
  326. package/inkos/packages/core/src/interaction/runtime.ts +1167 -0
  327. package/inkos/packages/core/src/interaction/session-transcript-legacy.ts +113 -0
  328. package/inkos/packages/core/src/interaction/session-transcript-restore.ts +607 -0
  329. package/inkos/packages/core/src/interaction/session-transcript-schema.ts +76 -0
  330. package/inkos/packages/core/src/interaction/session-transcript.ts +189 -0
  331. package/inkos/packages/core/src/interaction/session.ts +226 -0
  332. package/inkos/packages/core/src/interaction/truth-authority.ts +45 -0
  333. package/inkos/packages/core/src/llm/config-migration.ts +58 -0
  334. package/inkos/packages/core/src/llm/cover-providers.ts +45 -0
  335. package/inkos/packages/core/src/llm/provider.ts +1331 -0
  336. package/inkos/packages/core/src/llm/providers/endpoints/ai360.ts +42 -0
  337. package/inkos/packages/core/src/llm/providers/endpoints/anthropic.ts +82 -0
  338. package/inkos/packages/core/src/llm/providers/endpoints/astronCodingPlan.ts +30 -0
  339. package/inkos/packages/core/src/llm/providers/endpoints/baichuan.ts +28 -0
  340. package/inkos/packages/core/src/llm/providers/endpoints/bailian.ts +65 -0
  341. package/inkos/packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts +30 -0
  342. package/inkos/packages/core/src/llm/providers/endpoints/custom.ts +22 -0
  343. package/inkos/packages/core/src/llm/providers/endpoints/deepseek.ts +35 -0
  344. package/inkos/packages/core/src/llm/providers/endpoints/giteeai.ts +41 -0
  345. package/inkos/packages/core/src/llm/providers/endpoints/githubCopilot.ts +43 -0
  346. package/inkos/packages/core/src/llm/providers/endpoints/glmCodingPlan.ts +28 -0
  347. package/inkos/packages/core/src/llm/providers/endpoints/google.ts +51 -0
  348. package/inkos/packages/core/src/llm/providers/endpoints/hunyuan.ts +42 -0
  349. package/inkos/packages/core/src/llm/providers/endpoints/infiniai.ts +72 -0
  350. package/inkos/packages/core/src/llm/providers/endpoints/internlm.ts +28 -0
  351. package/inkos/packages/core/src/llm/providers/endpoints/kimiCode.ts +23 -0
  352. package/inkos/packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts +24 -0
  353. package/inkos/packages/core/src/llm/providers/endpoints/kkaiapi.ts +56 -0
  354. package/inkos/packages/core/src/llm/providers/endpoints/longcat.ts +25 -0
  355. package/inkos/packages/core/src/llm/providers/endpoints/minimax.ts +39 -0
  356. package/inkos/packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts +28 -0
  357. package/inkos/packages/core/src/llm/providers/endpoints/mistral.ts +40 -0
  358. package/inkos/packages/core/src/llm/providers/endpoints/modelscope.ts +30 -0
  359. package/inkos/packages/core/src/llm/providers/endpoints/moonshot.ts +39 -0
  360. package/inkos/packages/core/src/llm/providers/endpoints/newapi.ts +21 -0
  361. package/inkos/packages/core/src/llm/providers/endpoints/ollama.ts +73 -0
  362. package/inkos/packages/core/src/llm/providers/endpoints/openai.ts +77 -0
  363. package/inkos/packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts +30 -0
  364. package/inkos/packages/core/src/llm/providers/endpoints/openrouter.ts +87 -0
  365. package/inkos/packages/core/src/llm/providers/endpoints/ppio.ts +86 -0
  366. package/inkos/packages/core/src/llm/providers/endpoints/qiniu.ts +32 -0
  367. package/inkos/packages/core/src/llm/providers/endpoints/sensenova.ts +45 -0
  368. package/inkos/packages/core/src/llm/providers/endpoints/siliconcloud.ts +126 -0
  369. package/inkos/packages/core/src/llm/providers/endpoints/spark.ts +33 -0
  370. package/inkos/packages/core/src/llm/providers/endpoints/stepfun.ts +35 -0
  371. package/inkos/packages/core/src/llm/providers/endpoints/tencentcloud.ts +25 -0
  372. package/inkos/packages/core/src/llm/providers/endpoints/volcengine.ts +52 -0
  373. package/inkos/packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts +42 -0
  374. package/inkos/packages/core/src/llm/providers/endpoints/wenxin.ts +106 -0
  375. package/inkos/packages/core/src/llm/providers/endpoints/xai.ts +34 -0
  376. package/inkos/packages/core/src/llm/providers/endpoints/xiaomimimo.ts +26 -0
  377. package/inkos/packages/core/src/llm/providers/endpoints/zeroone.ts +34 -0
  378. package/inkos/packages/core/src/llm/providers/endpoints/zhipu.ts +61 -0
  379. package/inkos/packages/core/src/llm/providers/index.ts +71 -0
  380. package/inkos/packages/core/src/llm/providers/lookup.ts +70 -0
  381. package/inkos/packages/core/src/llm/providers/probe.ts +35 -0
  382. package/inkos/packages/core/src/llm/providers/types.ts +89 -0
  383. package/inkos/packages/core/src/llm/providers/verify.ts +104 -0
  384. package/inkos/packages/core/src/llm/secrets.ts +77 -0
  385. package/inkos/packages/core/src/llm/service-presets.ts +215 -0
  386. package/inkos/packages/core/src/llm/service-resolver.ts +91 -0
  387. package/inkos/packages/core/src/models/book-rules.ts +126 -0
  388. package/inkos/packages/core/src/models/book.ts +70 -0
  389. package/inkos/packages/core/src/models/chapter.ts +42 -0
  390. package/inkos/packages/core/src/models/detection.ts +25 -0
  391. package/inkos/packages/core/src/models/genre-profile.ts +36 -0
  392. package/inkos/packages/core/src/models/input-governance.ts +99 -0
  393. package/inkos/packages/core/src/models/length-governance.ts +46 -0
  394. package/inkos/packages/core/src/models/project.ts +161 -0
  395. package/inkos/packages/core/src/models/runtime-state.ts +144 -0
  396. package/inkos/packages/core/src/models/state.ts +52 -0
  397. package/inkos/packages/core/src/models/style-profile.ts +15 -0
  398. package/inkos/packages/core/src/notify/dispatcher.ts +96 -0
  399. package/inkos/packages/core/src/notify/feishu.ts +34 -0
  400. package/inkos/packages/core/src/notify/telegram.ts +25 -0
  401. package/inkos/packages/core/src/notify/webhook.ts +58 -0
  402. package/inkos/packages/core/src/notify/wechat-work.ts +22 -0
  403. package/inkos/packages/core/src/pipeline/agent.ts +691 -0
  404. package/inkos/packages/core/src/pipeline/chapter-persistence.ts +79 -0
  405. package/inkos/packages/core/src/pipeline/chapter-review-cycle.ts +324 -0
  406. package/inkos/packages/core/src/pipeline/chapter-state-recovery.ts +236 -0
  407. package/inkos/packages/core/src/pipeline/chapter-truth-validation.ts +145 -0
  408. package/inkos/packages/core/src/pipeline/detection-runner.ts +164 -0
  409. package/inkos/packages/core/src/pipeline/persisted-governed-plan.ts +216 -0
  410. package/inkos/packages/core/src/pipeline/runner.ts +3438 -0
  411. package/inkos/packages/core/src/pipeline/scheduler.ts +411 -0
  412. package/inkos/packages/core/src/pipeline/short-fiction-runner.ts +801 -0
  413. package/inkos/packages/core/src/prompts/index.ts +1 -0
  414. package/inkos/packages/core/src/prompts/short-fiction.ts +273 -0
  415. package/inkos/packages/core/src/state/manager.ts +560 -0
  416. package/inkos/packages/core/src/state/memory-db.ts +359 -0
  417. package/inkos/packages/core/src/state/runtime-state-store.ts +164 -0
  418. package/inkos/packages/core/src/state/state-bootstrap.ts +657 -0
  419. package/inkos/packages/core/src/state/state-projections.ts +255 -0
  420. package/inkos/packages/core/src/state/state-reducer.ts +260 -0
  421. package/inkos/packages/core/src/state/state-validator.ts +117 -0
  422. package/inkos/packages/core/src/utils/analytics.ts +92 -0
  423. package/inkos/packages/core/src/utils/book-id.ts +31 -0
  424. package/inkos/packages/core/src/utils/cadence-policy.ts +46 -0
  425. package/inkos/packages/core/src/utils/chapter-cadence.ts +211 -0
  426. package/inkos/packages/core/src/utils/chapter-memo-parser.ts +157 -0
  427. package/inkos/packages/core/src/utils/chapter-splitter.ts +80 -0
  428. package/inkos/packages/core/src/utils/config-loader.ts +29 -0
  429. package/inkos/packages/core/src/utils/context-assembly.ts +98 -0
  430. package/inkos/packages/core/src/utils/context-filter.ts +190 -0
  431. package/inkos/packages/core/src/utils/effective-llm-config.ts +529 -0
  432. package/inkos/packages/core/src/utils/governed-context.ts +101 -0
  433. package/inkos/packages/core/src/utils/governed-working-set.ts +395 -0
  434. package/inkos/packages/core/src/utils/hook-arbiter.ts +332 -0
  435. package/inkos/packages/core/src/utils/hook-governance.ts +199 -0
  436. package/inkos/packages/core/src/utils/hook-health.ts +189 -0
  437. package/inkos/packages/core/src/utils/hook-ledger-validator.ts +277 -0
  438. package/inkos/packages/core/src/utils/hook-lifecycle.ts +224 -0
  439. package/inkos/packages/core/src/utils/hook-policy.ts +115 -0
  440. package/inkos/packages/core/src/utils/hook-promotion.ts +313 -0
  441. package/inkos/packages/core/src/utils/hook-stale-detection.ts +168 -0
  442. package/inkos/packages/core/src/utils/length-metrics.ts +123 -0
  443. package/inkos/packages/core/src/utils/llm-endpoint-auth.ts +40 -0
  444. package/inkos/packages/core/src/utils/llm-env.ts +74 -0
  445. package/inkos/packages/core/src/utils/logger.ts +123 -0
  446. package/inkos/packages/core/src/utils/long-span-fatigue.ts +545 -0
  447. package/inkos/packages/core/src/utils/memory-retrieval.ts +527 -0
  448. package/inkos/packages/core/src/utils/narrative-control.ts +177 -0
  449. package/inkos/packages/core/src/utils/outline-paths.ts +275 -0
  450. package/inkos/packages/core/src/utils/path-safety.ts +11 -0
  451. package/inkos/packages/core/src/utils/planning-materials.ts +185 -0
  452. package/inkos/packages/core/src/utils/pov-filter.ts +149 -0
  453. package/inkos/packages/core/src/utils/proxy-fetch.ts +44 -0
  454. package/inkos/packages/core/src/utils/runtime-writer.ts +41 -0
  455. package/inkos/packages/core/src/utils/spot-fix-patches.ts +189 -0
  456. package/inkos/packages/core/src/utils/story-markdown.ts +346 -0
  457. package/inkos/packages/core/src/utils/web-search.ts +82 -0
  458. package/inkos/packages/core/src/utils/writing-methodology.ts +164 -0
  459. package/inkos/packages/core/tsconfig.json +8 -0
  460. package/inkos/packages/core/vitest.config.ts +7 -0
  461. package/inkos/packages/studio/components.json +25 -0
  462. package/inkos/packages/studio/index.html +13 -0
  463. package/inkos/packages/studio/package.json +72 -0
  464. package/inkos/packages/studio/postcss.config.js +3 -0
  465. package/inkos/packages/studio/src/App.test.ts +25 -0
  466. package/inkos/packages/studio/src/App.tsx +280 -0
  467. package/inkos/packages/studio/src/api/__tests__/normalize-base-url.test.ts +40 -0
  468. package/inkos/packages/studio/src/api/book-create.test.ts +104 -0
  469. package/inkos/packages/studio/src/api/book-create.ts +94 -0
  470. package/inkos/packages/studio/src/api/errors.ts +17 -0
  471. package/inkos/packages/studio/src/api/index.ts +30 -0
  472. package/inkos/packages/studio/src/api/lib/run-store.ts +177 -0
  473. package/inkos/packages/studio/src/api/lib/sse.ts +50 -0
  474. package/inkos/packages/studio/src/api/phase5-hotfix.test.ts +335 -0
  475. package/inkos/packages/studio/src/api/safety.ts +6 -0
  476. package/inkos/packages/studio/src/api/server.test.ts +3162 -0
  477. package/inkos/packages/studio/src/api/server.ts +3666 -0
  478. package/inkos/packages/studio/src/api/v13-hotfix-round4.test.ts +226 -0
  479. package/inkos/packages/studio/src/app-state.test.ts +8 -0
  480. package/inkos/packages/studio/src/app-state.ts +1 -0
  481. package/inkos/packages/studio/src/components/ConfirmDialog.tsx +95 -0
  482. package/inkos/packages/studio/src/components/ServiceConfigSourceCard.tsx +139 -0
  483. package/inkos/packages/studio/src/components/ServiceQuickLinks.tsx +65 -0
  484. package/inkos/packages/studio/src/components/Sidebar.tsx +652 -0
  485. package/inkos/packages/studio/src/components/ai-elements/code-block.tsx +562 -0
  486. package/inkos/packages/studio/src/components/ai-elements/confirmation.tsx +174 -0
  487. package/inkos/packages/studio/src/components/ai-elements/message.tsx +360 -0
  488. package/inkos/packages/studio/src/components/ai-elements/prompt-input.tsx +1457 -0
  489. package/inkos/packages/studio/src/components/ai-elements/reasoning.tsx +226 -0
  490. package/inkos/packages/studio/src/components/ai-elements/shimmer.tsx +77 -0
  491. package/inkos/packages/studio/src/components/ai-elements/tool.tsx +173 -0
  492. package/inkos/packages/studio/src/components/chat/BookSidebar.tsx +291 -0
  493. package/inkos/packages/studio/src/components/chat/ChatMessage.tsx +39 -0
  494. package/inkos/packages/studio/src/components/chat/QuickActions.tsx +73 -0
  495. package/inkos/packages/studio/src/components/chat/ToolExecutionSteps.tsx +320 -0
  496. package/inkos/packages/studio/src/components/chat/__tests__/ToolExecutionSteps.test.ts +114 -0
  497. package/inkos/packages/studio/src/components/chat-utils.ts +56 -0
  498. package/inkos/packages/studio/src/components/chatbar-state.test.ts +69 -0
  499. package/inkos/packages/studio/src/components/sidebar/ChaptersSection.tsx +66 -0
  500. package/inkos/packages/studio/src/components/sidebar/CharacterSection.tsx +129 -0
  501. package/inkos/packages/studio/src/components/sidebar/FoundationSection.tsx +61 -0
  502. package/inkos/packages/studio/src/components/sidebar/ProgressSection.tsx +124 -0
  503. package/inkos/packages/studio/src/components/sidebar/SidebarCard.tsx +30 -0
  504. package/inkos/packages/studio/src/components/sidebar/SummarySection.tsx +89 -0
  505. package/inkos/packages/studio/src/components/ui/alert.tsx +76 -0
  506. package/inkos/packages/studio/src/components/ui/badge.tsx +52 -0
  507. package/inkos/packages/studio/src/components/ui/button-group.tsx +87 -0
  508. package/inkos/packages/studio/src/components/ui/button.tsx +58 -0
  509. package/inkos/packages/studio/src/components/ui/collapsible.tsx +19 -0
  510. package/inkos/packages/studio/src/components/ui/command.tsx +194 -0
  511. package/inkos/packages/studio/src/components/ui/dialog.tsx +158 -0
  512. package/inkos/packages/studio/src/components/ui/dropdown-menu.tsx +266 -0
  513. package/inkos/packages/studio/src/components/ui/hover-card.tsx +51 -0
  514. package/inkos/packages/studio/src/components/ui/input-group.tsx +158 -0
  515. package/inkos/packages/studio/src/components/ui/input.tsx +20 -0
  516. package/inkos/packages/studio/src/components/ui/select.tsx +199 -0
  517. package/inkos/packages/studio/src/components/ui/separator.tsx +23 -0
  518. package/inkos/packages/studio/src/components/ui/spinner.tsx +10 -0
  519. package/inkos/packages/studio/src/components/ui/textarea.tsx +18 -0
  520. package/inkos/packages/studio/src/components/ui/tooltip.tsx +66 -0
  521. package/inkos/packages/studio/src/constants/service-groups.ts +29 -0
  522. package/inkos/packages/studio/src/hooks/use-api.test.ts +93 -0
  523. package/inkos/packages/studio/src/hooks/use-api.ts +189 -0
  524. package/inkos/packages/studio/src/hooks/use-book-activity.test.ts +129 -0
  525. package/inkos/packages/studio/src/hooks/use-book-activity.ts +180 -0
  526. package/inkos/packages/studio/src/hooks/use-colors.ts +27 -0
  527. package/inkos/packages/studio/src/hooks/use-hash-route.test.ts +101 -0
  528. package/inkos/packages/studio/src/hooks/use-hash-route.ts +89 -0
  529. package/inkos/packages/studio/src/hooks/use-i18n.ts +289 -0
  530. package/inkos/packages/studio/src/hooks/use-session-events.ts +64 -0
  531. package/inkos/packages/studio/src/hooks/use-sse.test.ts +52 -0
  532. package/inkos/packages/studio/src/hooks/use-sse.ts +92 -0
  533. package/inkos/packages/studio/src/hooks/use-theme.test.ts +31 -0
  534. package/inkos/packages/studio/src/hooks/use-theme.ts +75 -0
  535. package/inkos/packages/studio/src/index.css +323 -0
  536. package/inkos/packages/studio/src/lib/error-copy.test.ts +34 -0
  537. package/inkos/packages/studio/src/lib/error-copy.ts +37 -0
  538. package/inkos/packages/studio/src/lib/utils.ts +6 -0
  539. package/inkos/packages/studio/src/main.tsx +10 -0
  540. package/inkos/packages/studio/src/pages/Analytics.tsx +80 -0
  541. package/inkos/packages/studio/src/pages/BookCreate.tsx +895 -0
  542. package/inkos/packages/studio/src/pages/BookDetail.tsx +652 -0
  543. package/inkos/packages/studio/src/pages/ChapterReader.tsx +266 -0
  544. package/inkos/packages/studio/src/pages/ChatPage.tsx +521 -0
  545. package/inkos/packages/studio/src/pages/DaemonControl.tsx +116 -0
  546. package/inkos/packages/studio/src/pages/Dashboard.tsx +379 -0
  547. package/inkos/packages/studio/src/pages/DoctorView.tsx +82 -0
  548. package/inkos/packages/studio/src/pages/GenreManager.tsx +464 -0
  549. package/inkos/packages/studio/src/pages/ImportManager.tsx +216 -0
  550. package/inkos/packages/studio/src/pages/LanguageSelector.tsx +74 -0
  551. package/inkos/packages/studio/src/pages/LogViewer.tsx +82 -0
  552. package/inkos/packages/studio/src/pages/RadarView.tsx +157 -0
  553. package/inkos/packages/studio/src/pages/ServiceDetailPage.tsx +393 -0
  554. package/inkos/packages/studio/src/pages/ServiceListPage.tsx +463 -0
  555. package/inkos/packages/studio/src/pages/StyleManager.tsx +225 -0
  556. package/inkos/packages/studio/src/pages/TruthFiles.tsx +194 -0
  557. package/inkos/packages/studio/src/pages/chat-page-state.test.ts +206 -0
  558. package/inkos/packages/studio/src/pages/chat-page-state.ts +112 -0
  559. package/inkos/packages/studio/src/pages/page-state.test.ts +258 -0
  560. package/inkos/packages/studio/src/pages/service-detail-state.test.ts +294 -0
  561. package/inkos/packages/studio/src/pages/service-detail-state.ts +234 -0
  562. package/inkos/packages/studio/src/pages/style-manager-state.test.ts +22 -0
  563. package/inkos/packages/studio/src/pages/truth-files-state.test.ts +61 -0
  564. package/inkos/packages/studio/src/shared/contracts.ts +143 -0
  565. package/inkos/packages/studio/src/store/chat/__tests__/message-parts.test.ts +172 -0
  566. package/inkos/packages/studio/src/store/chat/index.ts +3 -0
  567. package/inkos/packages/studio/src/store/chat/initialState.ts +8 -0
  568. package/inkos/packages/studio/src/store/chat/message-policy.test.ts +16 -0
  569. package/inkos/packages/studio/src/store/chat/message-policy.ts +5 -0
  570. package/inkos/packages/studio/src/store/chat/parts-builder.ts +187 -0
  571. package/inkos/packages/studio/src/store/chat/selectors.ts +13 -0
  572. package/inkos/packages/studio/src/store/chat/slices/create/action.ts +10 -0
  573. package/inkos/packages/studio/src/store/chat/slices/create/initialState.ts +9 -0
  574. package/inkos/packages/studio/src/store/chat/slices/message/action.ts +417 -0
  575. package/inkos/packages/studio/src/store/chat/slices/message/initialState.ts +10 -0
  576. package/inkos/packages/studio/src/store/chat/slices/message/runtime.test.ts +21 -0
  577. package/inkos/packages/studio/src/store/chat/slices/message/runtime.ts +233 -0
  578. package/inkos/packages/studio/src/store/chat/slices/message/stream-events.ts +272 -0
  579. package/inkos/packages/studio/src/store/chat/store.ts +11 -0
  580. package/inkos/packages/studio/src/store/chat/types.ts +169 -0
  581. package/inkos/packages/studio/src/store/service/index.ts +2 -0
  582. package/inkos/packages/studio/src/store/service/store.ts +123 -0
  583. package/inkos/packages/studio/src/store/service/types.ts +50 -0
  584. package/inkos/packages/studio/tsconfig.json +24 -0
  585. package/inkos/packages/studio/tsconfig.server.json +11 -0
  586. package/inkos/packages/studio/vite.config.ts +34 -0
  587. package/inkos/packages/studio/vitest.config.ts +14 -0
  588. package/inkos/pnpm-lock.yaml +9569 -0
  589. package/inkos/pnpm-workspace.yaml +2 -0
  590. package/inkos/scripts/prepare-package-for-publish.mjs +135 -0
  591. package/inkos/scripts/restore-package-json.mjs +31 -0
  592. package/inkos/scripts/set-package-versions.mjs +74 -0
  593. package/inkos/scripts/verify-no-workspace-protocol.mjs +140 -0
  594. package/inkos/skills/SKILL.md +654 -0
  595. package/inkos/tsconfig.json +19 -0
  596. package/package.json +4 -3
  597. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_buildManifest.js +0 -0
  598. /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_ssgManifest.js +0 -0
@@ -0,0 +1,189 @@
1
+ import type { AuditIssue } from "../agents/continuity.js";
2
+ import type { HookRecord, RuntimeStateDelta } from "../models/runtime-state.js";
3
+ import { classifyHookDisposition, collectStaleHookDebt } from "./hook-governance.js";
4
+ import { describeHookLifecycle, localizeHookPayoffTiming } from "./hook-lifecycle.js";
5
+ import { HOOK_HEALTH_DEFAULTS } from "./hook-policy.js";
6
+
7
+ export function analyzeHookHealth(params: {
8
+ readonly language: "zh" | "en";
9
+ readonly chapterNumber: number;
10
+ readonly targetChapters?: number;
11
+ readonly hooks: ReadonlyArray<HookRecord>;
12
+ readonly delta?: Pick<RuntimeStateDelta, "chapter" | "hookOps">;
13
+ readonly existingHookIds?: ReadonlyArray<string>;
14
+ readonly maxActiveHooks?: number;
15
+ readonly staleAfterChapters?: number;
16
+ readonly noAdvanceWindow?: number;
17
+ readonly newHookBurstThreshold?: number;
18
+ }): AuditIssue[] {
19
+ const maxActiveHooks = params.maxActiveHooks ?? HOOK_HEALTH_DEFAULTS.maxActiveHooks;
20
+ const staleAfterChapters = params.staleAfterChapters ?? HOOK_HEALTH_DEFAULTS.staleAfterChapters;
21
+ const noAdvanceWindow = params.noAdvanceWindow ?? HOOK_HEALTH_DEFAULTS.noAdvanceWindow;
22
+ const newHookBurstThreshold = params.newHookBurstThreshold ?? HOOK_HEALTH_DEFAULTS.newHookBurstThreshold;
23
+ const issues: AuditIssue[] = [];
24
+
25
+ const activeHooks = params.hooks.filter((hook) => hook.status !== "resolved");
26
+ const lifecycleEntries = activeHooks.map((hook) => ({
27
+ hook,
28
+ lifecycle: describeHookLifecycle({
29
+ payoffTiming: hook.payoffTiming,
30
+ expectedPayoff: hook.expectedPayoff,
31
+ notes: hook.notes,
32
+ startChapter: hook.startChapter,
33
+ lastAdvancedChapter: hook.lastAdvancedChapter,
34
+ status: hook.status,
35
+ chapterNumber: params.chapterNumber,
36
+ targetChapters: params.targetChapters,
37
+ }),
38
+ }));
39
+
40
+ if (activeHooks.length > maxActiveHooks) {
41
+ issues.push(warning(
42
+ params.language,
43
+ params.language === "en"
44
+ ? `There are ${activeHooks.length} active hooks, above the recommended cap of ${maxActiveHooks}.`
45
+ : `当前有 ${activeHooks.length} 个活跃伏笔,已经高于建议上限 ${maxActiveHooks} 个。`,
46
+ params.language === "en"
47
+ ? "Prefer advancing, resolving, or deferring existing debt before opening more hooks."
48
+ : "优先推进、回收或延后已有伏笔,再继续开新伏笔。",
49
+ ));
50
+ }
51
+
52
+ const staleHookIds = new Set(collectStaleHookDebt({
53
+ hooks: activeHooks,
54
+ chapterNumber: params.chapterNumber,
55
+ targetChapters: params.targetChapters,
56
+ staleAfterChapters,
57
+ }).map((hook) => hook.hookId));
58
+ const pressuredHooks = lifecycleEntries.filter(({ hook, lifecycle }) =>
59
+ staleHookIds.has(hook.hookId)
60
+ || lifecycle.readyToResolve
61
+ || lifecycle.overdue,
62
+ );
63
+ const unresolvedPressure = pressuredHooks.filter(({ hook }) => {
64
+ if (!params.delta) {
65
+ return true;
66
+ }
67
+
68
+ const disposition = classifyHookDisposition({
69
+ hookId: hook.hookId,
70
+ delta: params.delta,
71
+ });
72
+ return disposition === "none" || disposition === "mention";
73
+ });
74
+ if (unresolvedPressure.length > 0) {
75
+ issues.push(warning(
76
+ params.language,
77
+ buildPressureDescription({
78
+ language: params.language,
79
+ entries: unresolvedPressure,
80
+ mentionsCurrentChapter: Boolean(params.delta),
81
+ }),
82
+ params.language === "en"
83
+ ? "Move one pressured hook with a real payoff, escalation, or explicit defer before opening adjacent debt."
84
+ : "先让一个已进入压力区的伏笔发生真实推进、回收或明确延后,再继续扩展同类债务。",
85
+ ));
86
+ } else {
87
+ const latestRealAdvance = activeHooks.reduce(
88
+ (max, hook) => Math.max(max, hook.lastAdvancedChapter),
89
+ 0,
90
+ );
91
+ if (
92
+ params.noAdvanceWindow !== undefined
93
+ && activeHooks.length > 0
94
+ && params.chapterNumber - latestRealAdvance >= noAdvanceWindow
95
+ ) {
96
+ issues.push(warning(
97
+ params.language,
98
+ params.language === "en"
99
+ ? `No real hook advancement has landed for ${params.chapterNumber - latestRealAdvance} chapters.`
100
+ : `已经连续 ${params.chapterNumber - latestRealAdvance} 章没有真实伏笔推进。`,
101
+ params.language === "en"
102
+ ? "Schedule one old hook for real movement instead of opening parallel restatements."
103
+ : "下一章优先让一个旧伏笔发生真实推进,而不是继续平行重述。",
104
+ ));
105
+ }
106
+ }
107
+
108
+ if (params.delta) {
109
+ const existingHookIds = new Set(params.existingHookIds ?? []);
110
+ const resultingHookIds = new Set(params.hooks.map((hook) => hook.hookId));
111
+ const newHookIds = params.delta.hookOps.upsert
112
+ .map((hook) => hook.hookId)
113
+ .filter((hookId) => !existingHookIds.has(hookId) && resultingHookIds.has(hookId));
114
+
115
+ if (newHookIds.length >= newHookBurstThreshold && params.delta.hookOps.resolve.length === 0) {
116
+ issues.push(warning(
117
+ params.language,
118
+ params.language === "en"
119
+ ? `Opened ${newHookIds.length} new hooks without resolving any older debt.`
120
+ : `本章新开了 ${newHookIds.length} 个伏笔,但没有回收任何旧债。`,
121
+ params.language === "en"
122
+ ? "Keep the hook table from ballooning by pairing new openings with old payoffs."
123
+ : "控制伏笔膨胀,新开伏笔时尽量配套回收旧伏笔。",
124
+ ));
125
+ }
126
+ }
127
+
128
+ return issues;
129
+ }
130
+
131
+ function buildPressureDescription(params: {
132
+ readonly language: "zh" | "en";
133
+ readonly entries: ReadonlyArray<{
134
+ readonly hook: HookRecord;
135
+ readonly lifecycle: ReturnType<typeof describeHookLifecycle>;
136
+ }>;
137
+ readonly mentionsCurrentChapter: boolean;
138
+ }): string {
139
+ const summarized = params.entries
140
+ .slice(0, 3)
141
+ .map(({ hook, lifecycle }) => {
142
+ const timing = localizeHookPayoffTiming(lifecycle.timing, params.language);
143
+ const pressure = localizePressureLabel(lifecycle, params.language);
144
+ return params.language === "en"
145
+ ? `${hook.hookId} (${timing}, ${pressure})`
146
+ : `${hook.hookId}(${timing},${pressure})`;
147
+ });
148
+ const suffix = params.entries.length > summarized.length
149
+ ? params.language === "en"
150
+ ? `, +${params.entries.length - summarized.length} more`
151
+ : `,另有 ${params.entries.length - summarized.length} 条`
152
+ : "";
153
+
154
+ if (params.language === "en") {
155
+ return params.mentionsCurrentChapter
156
+ ? `Hooks are already under payoff pressure but this chapter left them untouched: ${summarized.join(", ")}${suffix}.`
157
+ : `Hooks are already under payoff pressure without recent movement: ${summarized.join(", ")}${suffix}.`;
158
+ }
159
+
160
+ return params.mentionsCurrentChapter
161
+ ? `这些伏笔已经进入回收/推进压力,但本章没有真正处理:${summarized.join("、")}${suffix}。`
162
+ : `这些伏笔已经进入回收/推进压力,但近期没有真实推进:${summarized.join("、")}${suffix}。`;
163
+ }
164
+
165
+ function localizePressureLabel(
166
+ lifecycle: ReturnType<typeof describeHookLifecycle>,
167
+ language: "zh" | "en",
168
+ ): string {
169
+ if (lifecycle.overdue) {
170
+ return language === "en" ? "overdue" : "已逾期";
171
+ }
172
+ if (lifecycle.readyToResolve) {
173
+ return language === "en" ? "ready to pay off" : "可回收";
174
+ }
175
+ return language === "en" ? "stale" : "陈旧";
176
+ }
177
+
178
+ function warning(
179
+ language: "zh" | "en",
180
+ description: string,
181
+ suggestion: string,
182
+ ): AuditIssue {
183
+ return {
184
+ severity: "warning",
185
+ category: language === "en" ? "Hook Debt" : "伏笔债务",
186
+ description,
187
+ suggestion,
188
+ };
189
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * Phase 9-3: hard gate that a chapter draft actually acts on the hook ledger
3
+ * the planner declared in the memo's "## 本章 hook 账" / "## Hook ledger for
4
+ * this chapter" section.
5
+ *
6
+ * The planner commits, per chapter, to:
7
+ * - advance: <hook_id> "name" → state-change
8
+ * - resolve: <hook_id> "name" → action
9
+ *
10
+ * The validator parses those two lists and checks that every committed hook
11
+ * has observable evidence in the draft. "Evidence" means the draft mentions
12
+ * at least one keyword from the ledger line's descriptor (hook name, key
13
+ * noun, etc.). We deliberately do NOT require the draft to repeat the raw
14
+ * hook_id like "H007" — writers don't embed IDs in prose.
15
+ */
16
+
17
+ export interface HookLedgerViolation {
18
+ readonly severity: "critical";
19
+ readonly category: string;
20
+ readonly description: string;
21
+ readonly suggestion: string;
22
+ }
23
+
24
+ export interface HookLedgerEntry {
25
+ readonly id: string;
26
+ /** Raw text of the ledger line after the hook_id. */
27
+ readonly descriptor: string;
28
+ /** 2+ char CJK sequences and 3+ letter ASCII words extracted from descriptor. */
29
+ readonly keywords: ReadonlyArray<string>;
30
+ }
31
+
32
+ export interface HookLedger {
33
+ readonly open: ReadonlyArray<HookLedgerEntry>;
34
+ readonly advance: ReadonlyArray<HookLedgerEntry>;
35
+ readonly resolve: ReadonlyArray<HookLedgerEntry>;
36
+ readonly defer: ReadonlyArray<HookLedgerEntry>;
37
+ /**
38
+ * Count of `[new] ...` placeholder lines in the `open:` subsection. These
39
+ * are brand-new hooks declared by the planner that have no pre-existing
40
+ * hook_id (extractLedgerEntry rejects them because they carry no id to
41
+ * match downstream), but they still count as "a new hook opened" for the
42
+ * 揭 1 埋 1 floor check.
43
+ */
44
+ readonly newOpenCount: number;
45
+ }
46
+
47
+ const LEDGER_HEADING_PATTERNS = [
48
+ /^#{2,3}\s*本章\s*hook\s*账\s*$/im,
49
+ /^#{2,3}\s*Hook\s+ledger\s+for\s+this\s+chapter\s*$/im,
50
+ ];
51
+
52
+ const SUBSECTION_KEYS: ReadonlyArray<keyof HookLedger> = ["open", "advance", "resolve", "defer"];
53
+
54
+ /**
55
+ * Tokens that look like hook_ids but are placeholders meaning "no hooks in
56
+ * this slot". Writers sometimes emit "- 无" or "- none" under an empty slot
57
+ * instead of leaving it blank.
58
+ */
59
+ const PLACEHOLDER_TOKENS = /^(无|空|none|nil|null|暂无|n\/a|na|n-a|tbd|todo|待定)$/i;
60
+
61
+ /** Subsection heading words that must not be parsed as hook_ids. */
62
+ const SUBSECTION_WORDS = /^(open|advance|resolve|defer|new)$/i;
63
+
64
+ export function parseHookLedger(memoBody: string): HookLedger {
65
+ const section = extractLedgerSection(memoBody);
66
+ if (!section) {
67
+ return { open: [], advance: [], resolve: [], defer: [], newOpenCount: 0 };
68
+ }
69
+
70
+ type Subsection = "open" | "advance" | "resolve" | "defer";
71
+ const result: Record<Subsection, HookLedgerEntry[]> = {
72
+ open: [],
73
+ advance: [],
74
+ resolve: [],
75
+ defer: [],
76
+ };
77
+ let newOpenCount = 0;
78
+
79
+ let current: Subsection | null = null;
80
+ for (const rawLine of section.split(/\r?\n/)) {
81
+ const line = rawLine.trim();
82
+ if (line.length === 0) continue;
83
+
84
+ const subHeadingMatch = line.match(/^(open|advance|resolve|defer)\s*[::]?\s*$/i);
85
+ if (subHeadingMatch) {
86
+ current = subHeadingMatch[1]!.toLowerCase() as Subsection;
87
+ continue;
88
+ }
89
+
90
+ if (!current) continue;
91
+ if (!line.startsWith("-")) continue;
92
+
93
+ // `[new]` placeholder lines have no hook_id but still count as a new hook
94
+ // opened (揭 1 埋 1 floor check). extractLedgerEntry filters them out for
95
+ // advance/resolve evidence matching; we tally them separately here.
96
+ const cleaned = line.replace(/^-+\s*/, "").trim();
97
+ if (current === "open" && /^\[new\]/i.test(cleaned)) {
98
+ newOpenCount += 1;
99
+ continue;
100
+ }
101
+
102
+ const entry = extractLedgerEntry(line);
103
+ if (entry) result[current].push(entry);
104
+ }
105
+
106
+ return { ...result, newOpenCount };
107
+ }
108
+
109
+ /**
110
+ * Enforce: every hook declared under advance / resolve must have observable
111
+ * evidence in the draft text. We do NOT validate `open` (new hooks don't have
112
+ * a pre-existing id/descriptor to echo) or `defer` (deferred = deliberately
113
+ * not touched).
114
+ *
115
+ * Additionally enforces the "揭 1 埋 1" hard floor (Xu Er Jia De Mao, 番茄文章
116
+ * 10): whenever a chapter resolves one or more hooks, it must open at least
117
+ * as many new hooks in the same memo. "Resolve without opening" leaves the
118
+ * reader feeling "解完即索然无味" — the story loses forward pull. The softer
119
+ * "揭 1 埋 2" rule is a planner-prompt recommendation, not a hard gate here,
120
+ * because enforcing ×2 would conflict with the "≤ 2 new hooks per chapter"
121
+ * cap on the planner side when resolve=2.
122
+ */
123
+ export function validateHookLedger(
124
+ memoBody: string,
125
+ draftContent: string,
126
+ ): ReadonlyArray<HookLedgerViolation> {
127
+ const ledger = parseHookLedger(memoBody);
128
+ const violations: HookLedgerViolation[] = [];
129
+
130
+ // Evidence check for everything the memo committed to land in prose.
131
+ const committed = dedupeById([...ledger.advance, ...ledger.resolve]);
132
+ for (const entry of committed) {
133
+ if (!draftEchoesEntry(draftContent, entry)) {
134
+ violations.push({
135
+ severity: "critical",
136
+ category: "hook 账未兑现",
137
+ description: `memo 在 advance/resolve 里声明要处理 ${entry.id},但正文没有对应的落地动作`,
138
+ suggestion: `在正文中加入对 ${entry.id} 的具体情节推进(动作、对话、环境变化),或把它从 hook 账里移到 defer 并给出理由`,
139
+ });
140
+ }
141
+ }
142
+
143
+ // "揭 1 埋 1" hard floor: when anything was resolved, at least the same
144
+ // number of new hooks must have been opened. We count both `[new]`
145
+ // placeholder lines (newOpenCount — the normal way planners declare fresh
146
+ // hooks without an id) and any id-bearing lines under `open:` (rare, but
147
+ // legal if a planner re-opens a previously paused hook).
148
+ const resolvedCount = ledger.resolve.length;
149
+ const openedCount = ledger.open.length + ledger.newOpenCount;
150
+ if (resolvedCount > 0 && openedCount < resolvedCount) {
151
+ violations.push({
152
+ severity: "critical",
153
+ category: "hook 账揭 1 埋 1 违规",
154
+ description: `本章 resolve 了 ${resolvedCount} 个钩子,但 open 只有 ${openedCount} 个新钩子。只揭不埋会让读者豁然开朗后索然无味,本书的前进拉力被削弱。`,
155
+ suggestion: `在 memo 的 open 段下至少再埋 ${resolvedCount - openedCount} 个与本章已揭钩子相关的新钩子(番茄老师徐二家的猫:"掀开一个伏笔的同时,再埋两个伏笔")。新钩子最好与已揭钩子彼此关联,不要凭空冒出来。`,
156
+ });
157
+ }
158
+
159
+ return violations;
160
+ }
161
+
162
+ function extractLedgerSection(memoBody: string): string | undefined {
163
+ for (const pattern of LEDGER_HEADING_PATTERNS) {
164
+ const match = memoBody.match(pattern);
165
+ if (!match || match.index === undefined) continue;
166
+ const start = match.index + match[0].length;
167
+ const rest = memoBody.slice(start);
168
+ const nextHeading = rest.match(/\n#{2,3}\s/);
169
+ const end = nextHeading ? nextHeading.index ?? rest.length : rest.length;
170
+ return rest.slice(0, end);
171
+ }
172
+ return undefined;
173
+ }
174
+
175
+ function extractLedgerEntry(line: string): HookLedgerEntry | undefined {
176
+ const cleaned = line.replace(/^-+\s*/, "").trim();
177
+ if (cleaned.startsWith("[new]") || cleaned.startsWith("[NEW]")) return undefined;
178
+
179
+ // Reject whole-line placeholders first — "- 无", "- n/a", "- none" etc.
180
+ const firstWord = cleaned.split(/\s+/)[0] ?? "";
181
+ if (PLACEHOLDER_TOKENS.test(firstWord)) return undefined;
182
+
183
+ const idMatch = cleaned.match(/^([A-Za-z\u4e00-\u9fff][A-Za-z0-9_\-\u4e00-\u9fff]{0,19})/);
184
+ if (!idMatch) return undefined;
185
+
186
+ const candidate = idMatch[1]!;
187
+ if (SUBSECTION_WORDS.test(candidate)) return undefined;
188
+ if (PLACEHOLDER_TOKENS.test(candidate)) return undefined;
189
+
190
+ const descriptor = cleaned.slice(candidate.length).trim();
191
+ return { id: candidate, descriptor, keywords: extractKeywords(descriptor) };
192
+ }
193
+
194
+ /**
195
+ * Extract content-matching tokens from a ledger line's descriptor.
196
+ *
197
+ * Priority 1: quoted hook name — `H007 "胖虎借条" → ...` — this is the most
198
+ * informative token the planner attached, and it's what the writer should
199
+ * echo. We split compound CJK names into leading/trailing 2-grams so
200
+ * partial echoes still count.
201
+ *
202
+ * Priority 2: if no quoted name, fall back to the descriptor text UP TO the
203
+ * first state-transition arrow (→ or ->), same CJK/ASCII splitting. Anything
204
+ * AFTER the arrow describes new state, not the hook itself, and risks
205
+ * character-name false positives.
206
+ */
207
+ function extractKeywords(descriptor: string): ReadonlyArray<string> {
208
+ if (!descriptor) return [];
209
+
210
+ // Try the quoted-name anchor first — matches "..." or "..." quotes.
211
+ const quotedMatch = descriptor.match(/[""]([^""\n]+)[""]/);
212
+ const source = quotedMatch ? quotedMatch[1]! : descriptor.split(/[→]|->/, 1)[0]!;
213
+
214
+ const cjkRuns = source.match(/[\u4e00-\u9fff]{2,}/g) ?? [];
215
+ const cjkTokens: string[] = [];
216
+ for (const run of cjkRuns) {
217
+ cjkTokens.push(run);
218
+ if (run.length >= 3) {
219
+ for (let index = 0; index <= run.length - 2; index++) {
220
+ cjkTokens.push(run.slice(index, index + 2));
221
+ }
222
+ }
223
+ if (run.length >= 4) {
224
+ cjkTokens.push(run.slice(0, 3));
225
+ cjkTokens.push(run.slice(-3));
226
+ }
227
+ }
228
+ const ascii = (source.match(/[A-Za-z]{3,}/g) ?? []).map((w) => w.toLowerCase());
229
+ return dedupeStrings([...cjkTokens, ...ascii].filter((tok) => !ASCII_STOPWORDS.has(tok)));
230
+ }
231
+
232
+ const ASCII_STOPWORDS = new Set([
233
+ "and", "the", "for", "with", "from", "that", "into", "then",
234
+ "open", "close", "advance", "resolve", "defer", "new",
235
+ "planted", "pressured", "near", "payoff", "ready", "stale",
236
+ ]);
237
+
238
+ function draftEchoesEntry(draft: string, entry: HookLedgerEntry): boolean {
239
+ if (entry.keywords.length > 0) {
240
+ const draftLower = draft.toLowerCase();
241
+ return entry.keywords.some((kw) => {
242
+ // ASCII keywords are already lowercased; CJK keywords case doesn't matter.
243
+ return /^[a-z]/.test(kw) ? draftLower.includes(kw) : draft.includes(kw);
244
+ });
245
+ }
246
+ // Bare-id ledger line with no descriptor — fall back to ID match.
247
+ if (/^[A-Za-z0-9_-]+$/.test(entry.id)) {
248
+ return new RegExp(`\\b${escapeRegex(entry.id)}\\b`).test(draft);
249
+ }
250
+ return draft.includes(entry.id);
251
+ }
252
+
253
+ function dedupeById(entries: ReadonlyArray<HookLedgerEntry>): HookLedgerEntry[] {
254
+ const seen = new Set<string>();
255
+ const result: HookLedgerEntry[] = [];
256
+ for (const entry of entries) {
257
+ if (seen.has(entry.id)) continue;
258
+ seen.add(entry.id);
259
+ result.push(entry);
260
+ }
261
+ return result;
262
+ }
263
+
264
+ function dedupeStrings(values: ReadonlyArray<string>): string[] {
265
+ return [...new Set(values)];
266
+ }
267
+
268
+ function escapeRegex(value: string): string {
269
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
270
+ }
271
+
272
+ export const INTERNAL = {
273
+ SUBSECTION_KEYS,
274
+ extractLedgerSection,
275
+ extractLedgerEntry,
276
+ extractKeywords,
277
+ };
@@ -0,0 +1,224 @@
1
+ import type { HookPayoffTiming } from "../models/runtime-state.js";
2
+ import type { StoredHook } from "../state/memory-db.js";
3
+ import {
4
+ HOOK_ACTIVITY_THRESHOLDS,
5
+ HOOK_PHASE_THRESHOLDS,
6
+ HOOK_PHASE_WEIGHT,
7
+ HOOK_PRESSURE_WEIGHTS,
8
+ HOOK_TIMING_PROFILES,
9
+ type HookPhase,
10
+ } from "./hook-policy.js";
11
+
12
+ export const DEFAULT_HOOK_LOOKAHEAD_CHAPTERS = 3;
13
+
14
+ function normalizeStoredHookStatus(status: string): "resolved" | "deferred" | "progressing" | "open" {
15
+ if (/^(resolved|closed|done|已回收|已解决)$/i.test(status.trim())) return "resolved";
16
+ if (/^(deferred|paused|hold|延后|延期|搁置|暂缓)$/i.test(status.trim())) return "deferred";
17
+ if (/^(progressing|advanced|重大推进|持续推进)$/i.test(status.trim())) return "progressing";
18
+ return "open";
19
+ }
20
+
21
+ export function filterActiveHooks(hooks: ReadonlyArray<StoredHook>): StoredHook[] {
22
+ return hooks.filter((hook) => normalizeStoredHookStatus(hook.status) !== "resolved");
23
+ }
24
+
25
+ export function isFuturePlannedHook(
26
+ hook: StoredHook,
27
+ chapterNumber: number,
28
+ lookahead: number = DEFAULT_HOOK_LOOKAHEAD_CHAPTERS,
29
+ ): boolean {
30
+ return hook.lastAdvancedChapter <= 0 && hook.startChapter > chapterNumber + lookahead;
31
+ }
32
+
33
+ export function isHookWithinChapterWindow(
34
+ hook: StoredHook,
35
+ chapterNumber: number,
36
+ recentWindow: number = 5,
37
+ lookahead: number = DEFAULT_HOOK_LOOKAHEAD_CHAPTERS,
38
+ ): boolean {
39
+ const recentCutoff = Math.max(0, chapterNumber - recentWindow);
40
+
41
+ if (hook.lastAdvancedChapter > 0 && hook.lastAdvancedChapter >= recentCutoff) {
42
+ return true;
43
+ }
44
+
45
+ if (hook.lastAdvancedChapter > 0) {
46
+ return false;
47
+ }
48
+
49
+ if (hook.startChapter <= 0) {
50
+ return true;
51
+ }
52
+
53
+ if (hook.startChapter >= recentCutoff && hook.startChapter <= chapterNumber) {
54
+ return true;
55
+ }
56
+
57
+ return hook.startChapter > chapterNumber && hook.startChapter <= chapterNumber + lookahead;
58
+ }
59
+
60
+ const LABELS: Record<"zh" | "en", Record<HookPayoffTiming, string>> = {
61
+ en: {
62
+ immediate: "immediate",
63
+ "near-term": "near-term",
64
+ "mid-arc": "mid-arc",
65
+ "slow-burn": "slow-burn",
66
+ endgame: "endgame",
67
+ },
68
+ zh: {
69
+ immediate: "立即",
70
+ "near-term": "近期",
71
+ "mid-arc": "中程",
72
+ "slow-burn": "慢烧",
73
+ endgame: "终局",
74
+ },
75
+ };
76
+
77
+ const TIMING_ALIASES: Array<[HookPayoffTiming, RegExp]> = [
78
+ ["immediate", /^(?:立即|马上|当章|本章|下一章|immediate|instant|next(?:\s+chapter|\s+beat)?|right\s+away)$/i],
79
+ ["near-term", /^(?:近期|近几章|短线|soon|short(?:\s+run)?|near(?:\s*-\s*|\s+)term|current\s+sequence)$/i],
80
+ ["mid-arc", /^(?:中程|中期|卷中|mid(?:\s*-\s*|\s+)arc|mid(?:\s*-\s*|\s+)book|middle)$/i],
81
+ ["slow-burn", /^(?:慢烧|长线|后续|later|late(?:r)?|long(?:\s*-\s*|\s+)arc|slow(?:\s*-\s*|\s+)burn)$/i],
82
+ ["endgame", /^(?:终局|终章|大结局|最终|climax|finale|endgame|late\s+book)$/i],
83
+ ];
84
+
85
+ const SIGNAL_PATTERNS: Array<[HookPayoffTiming, RegExp]> = [
86
+ ["endgame", /(终局|终章|大结局|最终揭晓|最终摊牌|climax|finale|endgame|final reveal|last act)/i],
87
+ ["immediate", /(当章|本章|下一章|马上|立刻|即刻|immediate|next chapter|right away|at once)/i],
88
+ ["near-term", /(近期|近几章|很快|短线|soon|near-term|short run|current sequence)/i],
89
+ ["mid-arc", /(中期|卷中|本卷中段|mid-book|mid arc|middle of the arc)/i],
90
+ ["slow-burn", /(长线|慢烧|后续发酵|慢慢揭开|later|slow burn|long arc|long tail)/i],
91
+ ];
92
+
93
+ export function normalizeHookPayoffTiming(value: string | undefined | null): HookPayoffTiming | undefined {
94
+ const normalized = value?.trim();
95
+ if (!normalized) return undefined;
96
+
97
+ for (const [timing, pattern] of TIMING_ALIASES) {
98
+ if (pattern.test(normalized)) {
99
+ return timing;
100
+ }
101
+ }
102
+
103
+ return undefined;
104
+ }
105
+
106
+ export function inferHookPayoffTiming(params: {
107
+ readonly expectedPayoff?: string;
108
+ readonly notes?: string;
109
+ }): HookPayoffTiming {
110
+ const combined = [params.expectedPayoff, params.notes]
111
+ .filter((value): value is string => Boolean(value && value.trim()))
112
+ .join(" ")
113
+ .trim();
114
+ if (!combined) return "mid-arc";
115
+
116
+ for (const [timing, pattern] of SIGNAL_PATTERNS) {
117
+ if (pattern.test(combined)) {
118
+ return timing;
119
+ }
120
+ }
121
+
122
+ return "mid-arc";
123
+ }
124
+
125
+ export function resolveHookPayoffTiming(params: {
126
+ readonly payoffTiming?: string | null;
127
+ readonly expectedPayoff?: string;
128
+ readonly notes?: string;
129
+ }): HookPayoffTiming {
130
+ return normalizeHookPayoffTiming(params.payoffTiming)
131
+ ?? inferHookPayoffTiming({
132
+ expectedPayoff: params.expectedPayoff,
133
+ notes: params.notes,
134
+ });
135
+ }
136
+
137
+ export function localizeHookPayoffTiming(
138
+ timing: HookPayoffTiming,
139
+ language: "zh" | "en",
140
+ ): string {
141
+ return LABELS[language][timing];
142
+ }
143
+
144
+ export function describeHookLifecycle(params: {
145
+ readonly payoffTiming?: string | null;
146
+ readonly expectedPayoff?: string;
147
+ readonly notes?: string;
148
+ readonly startChapter: number;
149
+ readonly lastAdvancedChapter: number;
150
+ readonly status: string;
151
+ readonly chapterNumber: number;
152
+ readonly targetChapters?: number;
153
+ }): {
154
+ readonly timing: HookPayoffTiming;
155
+ readonly phase: HookPhase;
156
+ readonly age: number;
157
+ readonly dormancy: number;
158
+ readonly readyToResolve: boolean;
159
+ readonly stale: boolean;
160
+ readonly overdue: boolean;
161
+ readonly advancePressure: number;
162
+ readonly resolvePressure: number;
163
+ } {
164
+ const timing = resolveHookPayoffTiming(params);
165
+ const profile = HOOK_TIMING_PROFILES[timing];
166
+ const phase = resolveHookPhase(params.chapterNumber, params.targetChapters);
167
+ const age = Math.max(0, params.chapterNumber - Math.max(1, params.startChapter));
168
+ const lastTouchChapter = Math.max(params.startChapter, params.lastAdvancedChapter);
169
+ const dormancy = Math.max(0, params.chapterNumber - Math.max(1, lastTouchChapter));
170
+ const explicitProgressing = /^(progressing|advanced|重大推进|持续推进)$/i.test(params.status.trim());
171
+ const phaseReady = HOOK_PHASE_WEIGHT[phase] >= HOOK_PHASE_WEIGHT[profile.minimumPhase];
172
+ const recentlyTouched = dormancy <= HOOK_ACTIVITY_THRESHOLDS.recentlyTouchedDormancy;
173
+ const overdue = phaseReady && age >= profile.overdueAge;
174
+ const cadenceReady = timing === "slow-burn"
175
+ ? phase === "late" || overdue
176
+ : timing === "endgame"
177
+ ? phase === "late"
178
+ : true;
179
+ const momentum = explicitProgressing || recentlyTouched;
180
+ const stale = phaseReady && (
181
+ dormancy >= profile.staleDormancy
182
+ || (overdue && !momentum)
183
+ );
184
+ const readyToResolve = phaseReady
185
+ && cadenceReady
186
+ && age >= profile.earliestResolveAge
187
+ && (momentum || (overdue && explicitProgressing));
188
+
189
+ return {
190
+ timing,
191
+ phase,
192
+ age,
193
+ dormancy,
194
+ readyToResolve,
195
+ stale,
196
+ overdue,
197
+ advancePressure: age
198
+ + dormancy
199
+ + (stale ? HOOK_PRESSURE_WEIGHTS.staleAdvanceBonus : 0)
200
+ + (overdue ? HOOK_PRESSURE_WEIGHTS.overdueAdvanceBonus : 0),
201
+ resolvePressure: readyToResolve
202
+ ? profile.resolveBias * HOOK_PRESSURE_WEIGHTS.resolveBiasMultiplier
203
+ + (explicitProgressing ? HOOK_PRESSURE_WEIGHTS.progressingResolveBonus : 0)
204
+ + Math.min(
205
+ HOOK_PRESSURE_WEIGHTS.maxDormancyResolveBonus,
206
+ dormancy * HOOK_PRESSURE_WEIGHTS.dormancyResolveMultiplier,
207
+ )
208
+ + (overdue ? HOOK_PRESSURE_WEIGHTS.overdueResolveBonus : 0)
209
+ : 0,
210
+ };
211
+ }
212
+
213
+ function resolveHookPhase(chapterNumber: number, targetChapters?: number): HookPhase {
214
+ if (targetChapters && targetChapters > 0) {
215
+ const progress = chapterNumber / targetChapters;
216
+ if (progress >= HOOK_PHASE_THRESHOLDS.lateProgress) return "late";
217
+ if (progress >= HOOK_PHASE_THRESHOLDS.middleProgress) return "middle";
218
+ return "opening";
219
+ }
220
+
221
+ if (chapterNumber >= HOOK_PHASE_THRESHOLDS.lateChapter) return "late";
222
+ if (chapterNumber >= HOOK_PHASE_THRESHOLDS.middleChapter) return "middle";
223
+ return "opening";
224
+ }