@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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +2 -2
- package/.next/build-manifest.json +2 -2
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +1 -1
- package/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/api/inkos/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/install/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/search/route.js.nft.json +1 -1
- package/.next/server/app/index.html +1 -1
- package/.next/server/app/index.rsc +1 -1
- package/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/index.segments/_full.segment.rsc +1 -1
- package/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/.next/server/app/index.segments/_index.segment.rsc +1 -1
- package/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/server/app-paths-manifest.json +2 -2
- package/.next/server/chunks/162.js +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/trace +4 -4
- package/.next/trace-build +1 -1
- package/bin/pi-web.js +4 -1
- package/inkos/.env.example +20 -0
- package/inkos/.node-version +1 -0
- package/inkos/.nvmrc +1 -0
- package/inkos/CHANGELOG.md +787 -0
- package/inkos/CONTRIBUTING.md +89 -0
- package/inkos/LICENSE +661 -0
- package/inkos/README.en.md +483 -0
- package/inkos/README.ja.md +461 -0
- package/inkos/README.md +272 -0
- package/inkos/assets/15qun.jpg +0 -0
- package/inkos/assets/41777702961_.pic.jpg +0 -0
- package/inkos/assets/inkos-short-demo-cover.png +0 -0
- package/inkos/assets/inkos-text.svg +40 -0
- package/inkos/assets/logo.svg +47 -0
- package/inkos/assets/screenshot-chapters.png +0 -0
- package/inkos/assets/screenshot-pipeline.png +0 -0
- package/inkos/assets/screenshot-state.png +0 -0
- package/inkos/assets/screenshot-terminal.png +0 -0
- package/inkos/assets/wechat-group-v8.jpg +0 -0
- package/inkos/package.json +42 -0
- package/inkos/packages/cli/package.json +74 -0
- package/inkos/packages/cli/src/__tests__/analytics.test.ts +154 -0
- package/inkos/packages/cli/src/__tests__/cli-integration.test.ts +1031 -0
- package/inkos/packages/cli/src/__tests__/daemon.test.ts +93 -0
- package/inkos/packages/cli/src/__tests__/doctor.test.ts +36 -0
- package/inkos/packages/cli/src/__tests__/interact-command.test.ts +142 -0
- package/inkos/packages/cli/src/__tests__/interaction-tools.test.ts +107 -0
- package/inkos/packages/cli/src/__tests__/llm-overrides.test.ts +25 -0
- package/inkos/packages/cli/src/__tests__/localization.test.ts +121 -0
- package/inkos/packages/cli/src/__tests__/progress-text.test.ts +92 -0
- package/inkos/packages/cli/src/__tests__/project-bootstrap.test.ts +71 -0
- package/inkos/packages/cli/src/__tests__/publish-package.test.ts +272 -0
- package/inkos/packages/cli/src/__tests__/revision-command.test.ts +82 -0
- package/inkos/packages/cli/src/__tests__/runtime-requirements.test.ts +89 -0
- package/inkos/packages/cli/src/__tests__/short-fiction-command.test.ts +48 -0
- package/inkos/packages/cli/src/__tests__/studio-runtime.test.ts +142 -0
- package/inkos/packages/cli/src/__tests__/studio.test.ts +87 -0
- package/inkos/packages/cli/src/__tests__/tui-activity-state.test.ts +20 -0
- package/inkos/packages/cli/src/__tests__/tui-agent-session.test.ts +213 -0
- package/inkos/packages/cli/src/__tests__/tui-chat-depth.test.ts +27 -0
- package/inkos/packages/cli/src/__tests__/tui-chat-draft.test.ts +44 -0
- package/inkos/packages/cli/src/__tests__/tui-command.test.ts +86 -0
- package/inkos/packages/cli/src/__tests__/tui-composer-caret.test.ts +46 -0
- package/inkos/packages/cli/src/__tests__/tui-composer-display.test.ts +40 -0
- package/inkos/packages/cli/src/__tests__/tui-dashboard.test.tsx +219 -0
- package/inkos/packages/cli/src/__tests__/tui-effects-i18n.test.ts +29 -0
- package/inkos/packages/cli/src/__tests__/tui-i18n.test.ts +22 -0
- package/inkos/packages/cli/src/__tests__/tui-input-chrome.test.ts +10 -0
- package/inkos/packages/cli/src/__tests__/tui-input-history.test.ts +40 -0
- package/inkos/packages/cli/src/__tests__/tui-layout.test.ts +55 -0
- package/inkos/packages/cli/src/__tests__/tui-local-commands.test.ts +47 -0
- package/inkos/packages/cli/src/__tests__/tui-session-store.test.ts +81 -0
- package/inkos/packages/cli/src/__tests__/tui-setup-i18n.test.ts +31 -0
- package/inkos/packages/cli/src/__tests__/tui-slash-autocomplete.test.ts +33 -0
- package/inkos/packages/cli/src/commands/agent.ts +65 -0
- package/inkos/packages/cli/src/commands/analytics.ts +77 -0
- package/inkos/packages/cli/src/commands/audit.ts +52 -0
- package/inkos/packages/cli/src/commands/book.ts +260 -0
- package/inkos/packages/cli/src/commands/compose.ts +50 -0
- package/inkos/packages/cli/src/commands/config.ts +328 -0
- package/inkos/packages/cli/src/commands/consolidate.ts +50 -0
- package/inkos/packages/cli/src/commands/daemon.ts +121 -0
- package/inkos/packages/cli/src/commands/detect.ts +125 -0
- package/inkos/packages/cli/src/commands/doctor.ts +391 -0
- package/inkos/packages/cli/src/commands/draft.ts +43 -0
- package/inkos/packages/cli/src/commands/eval.ts +217 -0
- package/inkos/packages/cli/src/commands/export.ts +45 -0
- package/inkos/packages/cli/src/commands/fanfic.ts +183 -0
- package/inkos/packages/cli/src/commands/genre.ts +160 -0
- package/inkos/packages/cli/src/commands/import.ts +158 -0
- package/inkos/packages/cli/src/commands/init.ts +47 -0
- package/inkos/packages/cli/src/commands/interact.ts +109 -0
- package/inkos/packages/cli/src/commands/plan.ts +54 -0
- package/inkos/packages/cli/src/commands/radar.ts +60 -0
- package/inkos/packages/cli/src/commands/review.ts +253 -0
- package/inkos/packages/cli/src/commands/revise.ts +58 -0
- package/inkos/packages/cli/src/commands/short-fiction.ts +294 -0
- package/inkos/packages/cli/src/commands/status.ts +138 -0
- package/inkos/packages/cli/src/commands/studio.ts +194 -0
- package/inkos/packages/cli/src/commands/style.ts +99 -0
- package/inkos/packages/cli/src/commands/tui.ts +18 -0
- package/inkos/packages/cli/src/commands/update.ts +45 -0
- package/inkos/packages/cli/src/commands/write.ts +324 -0
- package/inkos/packages/cli/src/index.ts +5 -0
- package/inkos/packages/cli/src/interaction/tools.ts +49 -0
- package/inkos/packages/cli/src/localization.ts +215 -0
- package/inkos/packages/cli/src/program.ts +106 -0
- package/inkos/packages/cli/src/progress-text.ts +85 -0
- package/inkos/packages/cli/src/project-bootstrap.ts +175 -0
- package/inkos/packages/cli/src/runtime-requirements.ts +135 -0
- package/inkos/packages/cli/src/tui/__tests__/markdown.test.ts +64 -0
- package/inkos/packages/cli/src/tui/activity-state.ts +41 -0
- package/inkos/packages/cli/src/tui/agent-input.ts +264 -0
- package/inkos/packages/cli/src/tui/ansi.ts +72 -0
- package/inkos/packages/cli/src/tui/app.ts +130 -0
- package/inkos/packages/cli/src/tui/chat-depth.ts +22 -0
- package/inkos/packages/cli/src/tui/chat-draft.ts +42 -0
- package/inkos/packages/cli/src/tui/composer-caret.ts +22 -0
- package/inkos/packages/cli/src/tui/composer-display.ts +26 -0
- package/inkos/packages/cli/src/tui/dashboard-model.ts +164 -0
- package/inkos/packages/cli/src/tui/dashboard.tsx +544 -0
- package/inkos/packages/cli/src/tui/effects.ts +542 -0
- package/inkos/packages/cli/src/tui/i18n.ts +278 -0
- package/inkos/packages/cli/src/tui/input-history.ts +69 -0
- package/inkos/packages/cli/src/tui/local-commands.ts +55 -0
- package/inkos/packages/cli/src/tui/markdown.ts +64 -0
- package/inkos/packages/cli/src/tui/session-store.ts +6 -0
- package/inkos/packages/cli/src/tui/setup.ts +397 -0
- package/inkos/packages/cli/src/tui/slash-autocomplete.ts +62 -0
- package/inkos/packages/cli/src/tui/theme.ts +17 -0
- package/inkos/packages/cli/src/utils.ts +222 -0
- package/inkos/packages/cli/tsconfig.json +9 -0
- package/inkos/packages/core/genres/cozy.md +43 -0
- package/inkos/packages/core/genres/cultivation.md +42 -0
- package/inkos/packages/core/genres/dungeon-core.md +40 -0
- package/inkos/packages/core/genres/horror.md +51 -0
- package/inkos/packages/core/genres/isekai.md +43 -0
- package/inkos/packages/core/genres/litrpg.md +43 -0
- package/inkos/packages/core/genres/other.md +24 -0
- package/inkos/packages/core/genres/progression.md +41 -0
- package/inkos/packages/core/genres/romantasy.md +45 -0
- package/inkos/packages/core/genres/sci-fi.md +42 -0
- package/inkos/packages/core/genres/system-apocalypse.md +40 -0
- package/inkos/packages/core/genres/tower-climber.md +41 -0
- package/inkos/packages/core/genres/urban.md +53 -0
- package/inkos/packages/core/genres/xianxia.md +46 -0
- package/inkos/packages/core/genres/xuanhuan.md +64 -0
- package/inkos/packages/core/package.json +61 -0
- package/inkos/packages/core/src/__tests__/agent-max-tokens-policy.test.ts +29 -0
- package/inkos/packages/core/src/__tests__/agent-session.test.ts +866 -0
- package/inkos/packages/core/src/__tests__/agent-system-prompt.test.ts +167 -0
- package/inkos/packages/core/src/__tests__/agent-tools-params.test.ts +197 -0
- package/inkos/packages/core/src/__tests__/agent-tools.test.ts +421 -0
- package/inkos/packages/core/src/__tests__/ai-tells.test.ts +90 -0
- package/inkos/packages/core/src/__tests__/architect-phase5-consolidated.test.ts +445 -0
- package/inkos/packages/core/src/__tests__/architect-phase5.test.ts +455 -0
- package/inkos/packages/core/src/__tests__/architect-phase7.test.ts +210 -0
- package/inkos/packages/core/src/__tests__/architect.test.ts +859 -0
- package/inkos/packages/core/src/__tests__/audit-parse.test.ts +78 -0
- package/inkos/packages/core/src/__tests__/book-id.test.ts +26 -0
- package/inkos/packages/core/src/__tests__/book-session-store.test.ts +447 -0
- package/inkos/packages/core/src/__tests__/book-session.test.ts +113 -0
- package/inkos/packages/core/src/__tests__/chapter-analyzer.test.ts +574 -0
- package/inkos/packages/core/src/__tests__/chapter-memo-parser.test.ts +247 -0
- package/inkos/packages/core/src/__tests__/chapter-persistence.test.ts +198 -0
- package/inkos/packages/core/src/__tests__/chapter-review-cycle.test.ts +294 -0
- package/inkos/packages/core/src/__tests__/chapter-splitter.test.ts +156 -0
- package/inkos/packages/core/src/__tests__/chapter-state-recovery.test.ts +235 -0
- package/inkos/packages/core/src/__tests__/chapter-truth-validation.test.ts +253 -0
- package/inkos/packages/core/src/__tests__/composer.test.ts +627 -0
- package/inkos/packages/core/src/__tests__/config-loader.test.ts +325 -0
- package/inkos/packages/core/src/__tests__/config-migration.test.ts +102 -0
- package/inkos/packages/core/src/__tests__/consolidator.test.ts +32 -0
- package/inkos/packages/core/src/__tests__/context-filter.test.ts +60 -0
- package/inkos/packages/core/src/__tests__/context-transform.test.ts +108 -0
- package/inkos/packages/core/src/__tests__/continuity.test.ts +391 -0
- package/inkos/packages/core/src/__tests__/detection-insights.test.ts +59 -0
- package/inkos/packages/core/src/__tests__/detector.test.ts +86 -0
- package/inkos/packages/core/src/__tests__/draft-directive-parser.test.ts +386 -0
- package/inkos/packages/core/src/__tests__/edit-controller.test.ts +190 -0
- package/inkos/packages/core/src/__tests__/effective-llm-config.test.ts +486 -0
- package/inkos/packages/core/src/__tests__/fanfic-dimensions.test.ts +58 -0
- package/inkos/packages/core/src/__tests__/fanfic-models.test.ts +69 -0
- package/inkos/packages/core/src/__tests__/governed-working-set.test.ts +155 -0
- package/inkos/packages/core/src/__tests__/hook-arbiter.test.ts +124 -0
- package/inkos/packages/core/src/__tests__/hook-governance.test.ts +228 -0
- package/inkos/packages/core/src/__tests__/hook-health.test.ts +166 -0
- package/inkos/packages/core/src/__tests__/hook-ledger-validator.test.ts +236 -0
- package/inkos/packages/core/src/__tests__/hook-promotion.test.ts +192 -0
- package/inkos/packages/core/src/__tests__/hook-stale-detection.test.ts +136 -0
- package/inkos/packages/core/src/__tests__/index-notify-lazy.test.ts +20 -0
- package/inkos/packages/core/src/__tests__/interaction-chat-tokens.test.ts +170 -0
- package/inkos/packages/core/src/__tests__/interaction-models.test.ts +155 -0
- package/inkos/packages/core/src/__tests__/interaction-nl-router.test.ts +223 -0
- package/inkos/packages/core/src/__tests__/interaction-runtime.test.ts +633 -0
- package/inkos/packages/core/src/__tests__/interaction-tools.test.ts +343 -0
- package/inkos/packages/core/src/__tests__/length-metrics.test.ts +82 -0
- package/inkos/packages/core/src/__tests__/length-normalizer.test.ts +331 -0
- package/inkos/packages/core/src/__tests__/list-models.test.ts +109 -0
- package/inkos/packages/core/src/__tests__/llm-env.test.ts +31 -0
- package/inkos/packages/core/src/__tests__/logger.test.ts +175 -0
- package/inkos/packages/core/src/__tests__/long-span-fatigue.test.ts +160 -0
- package/inkos/packages/core/src/__tests__/memory-retrieval.test.ts +1303 -0
- package/inkos/packages/core/src/__tests__/models.test.ts +918 -0
- package/inkos/packages/core/src/__tests__/outline-paths.test.ts +97 -0
- package/inkos/packages/core/src/__tests__/path-safety.test.ts +22 -0
- package/inkos/packages/core/src/__tests__/persisted-governed-plan.test.ts +134 -0
- package/inkos/packages/core/src/__tests__/phase5-cleanup.test.ts +393 -0
- package/inkos/packages/core/src/__tests__/phase5-hotfix.test.ts +288 -0
- package/inkos/packages/core/src/__tests__/phase7-hotfix.test.ts +614 -0
- package/inkos/packages/core/src/__tests__/pipeline-agent.test.ts +354 -0
- package/inkos/packages/core/src/__tests__/pipeline-runner-memory-sync.test.ts +317 -0
- package/inkos/packages/core/src/__tests__/pipeline-runner.test.ts +5200 -0
- package/inkos/packages/core/src/__tests__/planner-context.test.ts +137 -0
- package/inkos/packages/core/src/__tests__/planner-prompts-ratio.test.ts +11 -0
- package/inkos/packages/core/src/__tests__/planner-prompts.test.ts +171 -0
- package/inkos/packages/core/src/__tests__/planner.test.ts +362 -0
- package/inkos/packages/core/src/__tests__/planning-materials.test.ts +90 -0
- package/inkos/packages/core/src/__tests__/polisher.test.ts +189 -0
- package/inkos/packages/core/src/__tests__/post-write-validator.test.ts +291 -0
- package/inkos/packages/core/src/__tests__/probe.test.ts +77 -0
- package/inkos/packages/core/src/__tests__/project-interaction.test.ts +241 -0
- package/inkos/packages/core/src/__tests__/provider.test.ts +953 -0
- package/inkos/packages/core/src/__tests__/providers-group.test.ts +34 -0
- package/inkos/packages/core/src/__tests__/providers-lookup.test.ts +81 -0
- package/inkos/packages/core/src/__tests__/providers-schema.test.ts +158 -0
- package/inkos/packages/core/src/__tests__/proxy-fetch.test.ts +75 -0
- package/inkos/packages/core/src/__tests__/revise-foundation.test.ts +514 -0
- package/inkos/packages/core/src/__tests__/reviser.test.ts +859 -0
- package/inkos/packages/core/src/__tests__/runtime-state-store.test.ts +388 -0
- package/inkos/packages/core/src/__tests__/scheduler.test.ts +123 -0
- package/inkos/packages/core/src/__tests__/secrets-migration.test.ts +71 -0
- package/inkos/packages/core/src/__tests__/secrets.test.ts +95 -0
- package/inkos/packages/core/src/__tests__/sensitive-words.test.ts +88 -0
- package/inkos/packages/core/src/__tests__/service-presets-regression.test.ts +73 -0
- package/inkos/packages/core/src/__tests__/service-resolver-regression.test.ts +75 -0
- package/inkos/packages/core/src/__tests__/service-resolver.test.ts +228 -0
- package/inkos/packages/core/src/__tests__/session-transcript-restore.test.ts +1311 -0
- package/inkos/packages/core/src/__tests__/session-transcript.test.ts +195 -0
- package/inkos/packages/core/src/__tests__/settler-delta-parser.test.ts +133 -0
- package/inkos/packages/core/src/__tests__/short-fiction-public.test.ts +241 -0
- package/inkos/packages/core/src/__tests__/spot-fix-patches.test.ts +104 -0
- package/inkos/packages/core/src/__tests__/state-manager.test.ts +1298 -0
- package/inkos/packages/core/src/__tests__/state-projections.test.ts +130 -0
- package/inkos/packages/core/src/__tests__/state-reducer.test.ts +372 -0
- package/inkos/packages/core/src/__tests__/state-validator-agent.test.ts +165 -0
- package/inkos/packages/core/src/__tests__/state-validator.test.ts +122 -0
- package/inkos/packages/core/src/__tests__/style-analyzer.test.ts +61 -0
- package/inkos/packages/core/src/__tests__/temperature-constraints.test.ts +57 -0
- package/inkos/packages/core/src/__tests__/v13-hotfix-round4.test.ts +343 -0
- package/inkos/packages/core/src/__tests__/verify-service.test.ts +77 -0
- package/inkos/packages/core/src/__tests__/webhook.test.ts +91 -0
- package/inkos/packages/core/src/__tests__/writer-parser.test.ts +348 -0
- package/inkos/packages/core/src/__tests__/writer-prompts.test.ts +269 -0
- package/inkos/packages/core/src/__tests__/writer.test.ts +1360 -0
- package/inkos/packages/core/src/agent/agent-session.ts +737 -0
- package/inkos/packages/core/src/agent/agent-system-prompt.ts +199 -0
- package/inkos/packages/core/src/agent/agent-tools.ts +835 -0
- package/inkos/packages/core/src/agent/context-transform.ts +85 -0
- package/inkos/packages/core/src/agent/index.ts +14 -0
- package/inkos/packages/core/src/agents/ai-tells.ts +161 -0
- package/inkos/packages/core/src/agents/architect.ts +1291 -0
- package/inkos/packages/core/src/agents/base.ts +100 -0
- package/inkos/packages/core/src/agents/chapter-analyzer.ts +634 -0
- package/inkos/packages/core/src/agents/composer.ts +469 -0
- package/inkos/packages/core/src/agents/consolidator.ts +218 -0
- package/inkos/packages/core/src/agents/continuity.ts +824 -0
- package/inkos/packages/core/src/agents/detection-insights.ts +72 -0
- package/inkos/packages/core/src/agents/detector.ts +224 -0
- package/inkos/packages/core/src/agents/en-prompt-sections.ts +129 -0
- package/inkos/packages/core/src/agents/fanfic-canon-importer.ts +146 -0
- package/inkos/packages/core/src/agents/fanfic-dimensions.ts +87 -0
- package/inkos/packages/core/src/agents/fanfic-prompt-sections.ts +109 -0
- package/inkos/packages/core/src/agents/foundation-reviewer.ts +204 -0
- package/inkos/packages/core/src/agents/length-normalizer.ts +218 -0
- package/inkos/packages/core/src/agents/observer-prompts.ts +127 -0
- package/inkos/packages/core/src/agents/planner-context.ts +297 -0
- package/inkos/packages/core/src/agents/planner-prompts.ts +404 -0
- package/inkos/packages/core/src/agents/planner.ts +783 -0
- package/inkos/packages/core/src/agents/polisher.ts +153 -0
- package/inkos/packages/core/src/agents/post-write-validator.ts +873 -0
- package/inkos/packages/core/src/agents/radar-source.ts +123 -0
- package/inkos/packages/core/src/agents/radar.ts +120 -0
- package/inkos/packages/core/src/agents/reviser.ts +701 -0
- package/inkos/packages/core/src/agents/rules-reader.ts +155 -0
- package/inkos/packages/core/src/agents/sensitive-words.ts +142 -0
- package/inkos/packages/core/src/agents/settler-delta-parser.ts +53 -0
- package/inkos/packages/core/src/agents/settler-parser.ts +38 -0
- package/inkos/packages/core/src/agents/settler-prompts.ts +230 -0
- package/inkos/packages/core/src/agents/short-fiction.ts +429 -0
- package/inkos/packages/core/src/agents/state-validator.ts +322 -0
- package/inkos/packages/core/src/agents/style-analyzer.ts +93 -0
- package/inkos/packages/core/src/agents/writer-parser.ts +178 -0
- package/inkos/packages/core/src/agents/writer-prompts.ts +899 -0
- package/inkos/packages/core/src/agents/writer.ts +1450 -0
- package/inkos/packages/core/src/index.ts +392 -0
- package/inkos/packages/core/src/interaction/book-session-store.ts +226 -0
- package/inkos/packages/core/src/interaction/draft-directive-parser.ts +266 -0
- package/inkos/packages/core/src/interaction/edit-controller.ts +270 -0
- package/inkos/packages/core/src/interaction/events.ts +41 -0
- package/inkos/packages/core/src/interaction/export-artifact.ts +151 -0
- package/inkos/packages/core/src/interaction/intents.ts +63 -0
- package/inkos/packages/core/src/interaction/modes.ts +13 -0
- package/inkos/packages/core/src/interaction/nl-router.ts +258 -0
- package/inkos/packages/core/src/interaction/project-control.ts +150 -0
- package/inkos/packages/core/src/interaction/project-session-store.ts +81 -0
- package/inkos/packages/core/src/interaction/project-tools.ts +704 -0
- package/inkos/packages/core/src/interaction/request-router.ts +5 -0
- package/inkos/packages/core/src/interaction/runtime.ts +1167 -0
- package/inkos/packages/core/src/interaction/session-transcript-legacy.ts +113 -0
- package/inkos/packages/core/src/interaction/session-transcript-restore.ts +607 -0
- package/inkos/packages/core/src/interaction/session-transcript-schema.ts +76 -0
- package/inkos/packages/core/src/interaction/session-transcript.ts +189 -0
- package/inkos/packages/core/src/interaction/session.ts +226 -0
- package/inkos/packages/core/src/interaction/truth-authority.ts +45 -0
- package/inkos/packages/core/src/llm/config-migration.ts +58 -0
- package/inkos/packages/core/src/llm/cover-providers.ts +45 -0
- package/inkos/packages/core/src/llm/provider.ts +1331 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ai360.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/anthropic.ts +82 -0
- package/inkos/packages/core/src/llm/providers/endpoints/astronCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/baichuan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/bailian.ts +65 -0
- package/inkos/packages/core/src/llm/providers/endpoints/bailianCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/custom.ts +22 -0
- package/inkos/packages/core/src/llm/providers/endpoints/deepseek.ts +35 -0
- package/inkos/packages/core/src/llm/providers/endpoints/giteeai.ts +41 -0
- package/inkos/packages/core/src/llm/providers/endpoints/githubCopilot.ts +43 -0
- package/inkos/packages/core/src/llm/providers/endpoints/glmCodingPlan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/google.ts +51 -0
- package/inkos/packages/core/src/llm/providers/endpoints/hunyuan.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/infiniai.ts +72 -0
- package/inkos/packages/core/src/llm/providers/endpoints/internlm.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kimiCode.ts +23 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kimiCodingPlan.ts +24 -0
- package/inkos/packages/core/src/llm/providers/endpoints/kkaiapi.ts +56 -0
- package/inkos/packages/core/src/llm/providers/endpoints/longcat.ts +25 -0
- package/inkos/packages/core/src/llm/providers/endpoints/minimax.ts +39 -0
- package/inkos/packages/core/src/llm/providers/endpoints/minimaxCodingPlan.ts +28 -0
- package/inkos/packages/core/src/llm/providers/endpoints/mistral.ts +40 -0
- package/inkos/packages/core/src/llm/providers/endpoints/modelscope.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/moonshot.ts +39 -0
- package/inkos/packages/core/src/llm/providers/endpoints/newapi.ts +21 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ollama.ts +73 -0
- package/inkos/packages/core/src/llm/providers/endpoints/openai.ts +77 -0
- package/inkos/packages/core/src/llm/providers/endpoints/opencodeCodingPlan.ts +30 -0
- package/inkos/packages/core/src/llm/providers/endpoints/openrouter.ts +87 -0
- package/inkos/packages/core/src/llm/providers/endpoints/ppio.ts +86 -0
- package/inkos/packages/core/src/llm/providers/endpoints/qiniu.ts +32 -0
- package/inkos/packages/core/src/llm/providers/endpoints/sensenova.ts +45 -0
- package/inkos/packages/core/src/llm/providers/endpoints/siliconcloud.ts +126 -0
- package/inkos/packages/core/src/llm/providers/endpoints/spark.ts +33 -0
- package/inkos/packages/core/src/llm/providers/endpoints/stepfun.ts +35 -0
- package/inkos/packages/core/src/llm/providers/endpoints/tencentcloud.ts +25 -0
- package/inkos/packages/core/src/llm/providers/endpoints/volcengine.ts +52 -0
- package/inkos/packages/core/src/llm/providers/endpoints/volcengineCodingPlan.ts +42 -0
- package/inkos/packages/core/src/llm/providers/endpoints/wenxin.ts +106 -0
- package/inkos/packages/core/src/llm/providers/endpoints/xai.ts +34 -0
- package/inkos/packages/core/src/llm/providers/endpoints/xiaomimimo.ts +26 -0
- package/inkos/packages/core/src/llm/providers/endpoints/zeroone.ts +34 -0
- package/inkos/packages/core/src/llm/providers/endpoints/zhipu.ts +61 -0
- package/inkos/packages/core/src/llm/providers/index.ts +71 -0
- package/inkos/packages/core/src/llm/providers/lookup.ts +70 -0
- package/inkos/packages/core/src/llm/providers/probe.ts +35 -0
- package/inkos/packages/core/src/llm/providers/types.ts +89 -0
- package/inkos/packages/core/src/llm/providers/verify.ts +104 -0
- package/inkos/packages/core/src/llm/secrets.ts +77 -0
- package/inkos/packages/core/src/llm/service-presets.ts +215 -0
- package/inkos/packages/core/src/llm/service-resolver.ts +91 -0
- package/inkos/packages/core/src/models/book-rules.ts +126 -0
- package/inkos/packages/core/src/models/book.ts +70 -0
- package/inkos/packages/core/src/models/chapter.ts +42 -0
- package/inkos/packages/core/src/models/detection.ts +25 -0
- package/inkos/packages/core/src/models/genre-profile.ts +36 -0
- package/inkos/packages/core/src/models/input-governance.ts +99 -0
- package/inkos/packages/core/src/models/length-governance.ts +46 -0
- package/inkos/packages/core/src/models/project.ts +161 -0
- package/inkos/packages/core/src/models/runtime-state.ts +144 -0
- package/inkos/packages/core/src/models/state.ts +52 -0
- package/inkos/packages/core/src/models/style-profile.ts +15 -0
- package/inkos/packages/core/src/notify/dispatcher.ts +96 -0
- package/inkos/packages/core/src/notify/feishu.ts +34 -0
- package/inkos/packages/core/src/notify/telegram.ts +25 -0
- package/inkos/packages/core/src/notify/webhook.ts +58 -0
- package/inkos/packages/core/src/notify/wechat-work.ts +22 -0
- package/inkos/packages/core/src/pipeline/agent.ts +691 -0
- package/inkos/packages/core/src/pipeline/chapter-persistence.ts +79 -0
- package/inkos/packages/core/src/pipeline/chapter-review-cycle.ts +324 -0
- package/inkos/packages/core/src/pipeline/chapter-state-recovery.ts +236 -0
- package/inkos/packages/core/src/pipeline/chapter-truth-validation.ts +145 -0
- package/inkos/packages/core/src/pipeline/detection-runner.ts +164 -0
- package/inkos/packages/core/src/pipeline/persisted-governed-plan.ts +216 -0
- package/inkos/packages/core/src/pipeline/runner.ts +3438 -0
- package/inkos/packages/core/src/pipeline/scheduler.ts +411 -0
- package/inkos/packages/core/src/pipeline/short-fiction-runner.ts +801 -0
- package/inkos/packages/core/src/prompts/index.ts +1 -0
- package/inkos/packages/core/src/prompts/short-fiction.ts +273 -0
- package/inkos/packages/core/src/state/manager.ts +560 -0
- package/inkos/packages/core/src/state/memory-db.ts +359 -0
- package/inkos/packages/core/src/state/runtime-state-store.ts +164 -0
- package/inkos/packages/core/src/state/state-bootstrap.ts +657 -0
- package/inkos/packages/core/src/state/state-projections.ts +255 -0
- package/inkos/packages/core/src/state/state-reducer.ts +260 -0
- package/inkos/packages/core/src/state/state-validator.ts +117 -0
- package/inkos/packages/core/src/utils/analytics.ts +92 -0
- package/inkos/packages/core/src/utils/book-id.ts +31 -0
- package/inkos/packages/core/src/utils/cadence-policy.ts +46 -0
- package/inkos/packages/core/src/utils/chapter-cadence.ts +211 -0
- package/inkos/packages/core/src/utils/chapter-memo-parser.ts +157 -0
- package/inkos/packages/core/src/utils/chapter-splitter.ts +80 -0
- package/inkos/packages/core/src/utils/config-loader.ts +29 -0
- package/inkos/packages/core/src/utils/context-assembly.ts +98 -0
- package/inkos/packages/core/src/utils/context-filter.ts +190 -0
- package/inkos/packages/core/src/utils/effective-llm-config.ts +529 -0
- package/inkos/packages/core/src/utils/governed-context.ts +101 -0
- package/inkos/packages/core/src/utils/governed-working-set.ts +395 -0
- package/inkos/packages/core/src/utils/hook-arbiter.ts +332 -0
- package/inkos/packages/core/src/utils/hook-governance.ts +199 -0
- package/inkos/packages/core/src/utils/hook-health.ts +189 -0
- package/inkos/packages/core/src/utils/hook-ledger-validator.ts +277 -0
- package/inkos/packages/core/src/utils/hook-lifecycle.ts +224 -0
- package/inkos/packages/core/src/utils/hook-policy.ts +115 -0
- package/inkos/packages/core/src/utils/hook-promotion.ts +313 -0
- package/inkos/packages/core/src/utils/hook-stale-detection.ts +168 -0
- package/inkos/packages/core/src/utils/length-metrics.ts +123 -0
- package/inkos/packages/core/src/utils/llm-endpoint-auth.ts +40 -0
- package/inkos/packages/core/src/utils/llm-env.ts +74 -0
- package/inkos/packages/core/src/utils/logger.ts +123 -0
- package/inkos/packages/core/src/utils/long-span-fatigue.ts +545 -0
- package/inkos/packages/core/src/utils/memory-retrieval.ts +527 -0
- package/inkos/packages/core/src/utils/narrative-control.ts +177 -0
- package/inkos/packages/core/src/utils/outline-paths.ts +275 -0
- package/inkos/packages/core/src/utils/path-safety.ts +11 -0
- package/inkos/packages/core/src/utils/planning-materials.ts +185 -0
- package/inkos/packages/core/src/utils/pov-filter.ts +149 -0
- package/inkos/packages/core/src/utils/proxy-fetch.ts +44 -0
- package/inkos/packages/core/src/utils/runtime-writer.ts +41 -0
- package/inkos/packages/core/src/utils/spot-fix-patches.ts +189 -0
- package/inkos/packages/core/src/utils/story-markdown.ts +346 -0
- package/inkos/packages/core/src/utils/web-search.ts +82 -0
- package/inkos/packages/core/src/utils/writing-methodology.ts +164 -0
- package/inkos/packages/core/tsconfig.json +8 -0
- package/inkos/packages/core/vitest.config.ts +7 -0
- package/inkos/packages/studio/components.json +25 -0
- package/inkos/packages/studio/index.html +13 -0
- package/inkos/packages/studio/package.json +72 -0
- package/inkos/packages/studio/postcss.config.js +3 -0
- package/inkos/packages/studio/src/App.test.ts +25 -0
- package/inkos/packages/studio/src/App.tsx +280 -0
- package/inkos/packages/studio/src/api/__tests__/normalize-base-url.test.ts +40 -0
- package/inkos/packages/studio/src/api/book-create.test.ts +104 -0
- package/inkos/packages/studio/src/api/book-create.ts +94 -0
- package/inkos/packages/studio/src/api/errors.ts +17 -0
- package/inkos/packages/studio/src/api/index.ts +30 -0
- package/inkos/packages/studio/src/api/lib/run-store.ts +177 -0
- package/inkos/packages/studio/src/api/lib/sse.ts +50 -0
- package/inkos/packages/studio/src/api/phase5-hotfix.test.ts +335 -0
- package/inkos/packages/studio/src/api/safety.ts +6 -0
- package/inkos/packages/studio/src/api/server.test.ts +3162 -0
- package/inkos/packages/studio/src/api/server.ts +3666 -0
- package/inkos/packages/studio/src/api/v13-hotfix-round4.test.ts +226 -0
- package/inkos/packages/studio/src/app-state.test.ts +8 -0
- package/inkos/packages/studio/src/app-state.ts +1 -0
- package/inkos/packages/studio/src/components/ConfirmDialog.tsx +95 -0
- package/inkos/packages/studio/src/components/ServiceConfigSourceCard.tsx +139 -0
- package/inkos/packages/studio/src/components/ServiceQuickLinks.tsx +65 -0
- package/inkos/packages/studio/src/components/Sidebar.tsx +652 -0
- package/inkos/packages/studio/src/components/ai-elements/code-block.tsx +562 -0
- package/inkos/packages/studio/src/components/ai-elements/confirmation.tsx +174 -0
- package/inkos/packages/studio/src/components/ai-elements/message.tsx +360 -0
- package/inkos/packages/studio/src/components/ai-elements/prompt-input.tsx +1457 -0
- package/inkos/packages/studio/src/components/ai-elements/reasoning.tsx +226 -0
- package/inkos/packages/studio/src/components/ai-elements/shimmer.tsx +77 -0
- package/inkos/packages/studio/src/components/ai-elements/tool.tsx +173 -0
- package/inkos/packages/studio/src/components/chat/BookSidebar.tsx +291 -0
- package/inkos/packages/studio/src/components/chat/ChatMessage.tsx +39 -0
- package/inkos/packages/studio/src/components/chat/QuickActions.tsx +73 -0
- package/inkos/packages/studio/src/components/chat/ToolExecutionSteps.tsx +320 -0
- package/inkos/packages/studio/src/components/chat/__tests__/ToolExecutionSteps.test.ts +114 -0
- package/inkos/packages/studio/src/components/chat-utils.ts +56 -0
- package/inkos/packages/studio/src/components/chatbar-state.test.ts +69 -0
- package/inkos/packages/studio/src/components/sidebar/ChaptersSection.tsx +66 -0
- package/inkos/packages/studio/src/components/sidebar/CharacterSection.tsx +129 -0
- package/inkos/packages/studio/src/components/sidebar/FoundationSection.tsx +61 -0
- package/inkos/packages/studio/src/components/sidebar/ProgressSection.tsx +124 -0
- package/inkos/packages/studio/src/components/sidebar/SidebarCard.tsx +30 -0
- package/inkos/packages/studio/src/components/sidebar/SummarySection.tsx +89 -0
- package/inkos/packages/studio/src/components/ui/alert.tsx +76 -0
- package/inkos/packages/studio/src/components/ui/badge.tsx +52 -0
- package/inkos/packages/studio/src/components/ui/button-group.tsx +87 -0
- package/inkos/packages/studio/src/components/ui/button.tsx +58 -0
- package/inkos/packages/studio/src/components/ui/collapsible.tsx +19 -0
- package/inkos/packages/studio/src/components/ui/command.tsx +194 -0
- package/inkos/packages/studio/src/components/ui/dialog.tsx +158 -0
- package/inkos/packages/studio/src/components/ui/dropdown-menu.tsx +266 -0
- package/inkos/packages/studio/src/components/ui/hover-card.tsx +51 -0
- package/inkos/packages/studio/src/components/ui/input-group.tsx +158 -0
- package/inkos/packages/studio/src/components/ui/input.tsx +20 -0
- package/inkos/packages/studio/src/components/ui/select.tsx +199 -0
- package/inkos/packages/studio/src/components/ui/separator.tsx +23 -0
- package/inkos/packages/studio/src/components/ui/spinner.tsx +10 -0
- package/inkos/packages/studio/src/components/ui/textarea.tsx +18 -0
- package/inkos/packages/studio/src/components/ui/tooltip.tsx +66 -0
- package/inkos/packages/studio/src/constants/service-groups.ts +29 -0
- package/inkos/packages/studio/src/hooks/use-api.test.ts +93 -0
- package/inkos/packages/studio/src/hooks/use-api.ts +189 -0
- package/inkos/packages/studio/src/hooks/use-book-activity.test.ts +129 -0
- package/inkos/packages/studio/src/hooks/use-book-activity.ts +180 -0
- package/inkos/packages/studio/src/hooks/use-colors.ts +27 -0
- package/inkos/packages/studio/src/hooks/use-hash-route.test.ts +101 -0
- package/inkos/packages/studio/src/hooks/use-hash-route.ts +89 -0
- package/inkos/packages/studio/src/hooks/use-i18n.ts +289 -0
- package/inkos/packages/studio/src/hooks/use-session-events.ts +64 -0
- package/inkos/packages/studio/src/hooks/use-sse.test.ts +52 -0
- package/inkos/packages/studio/src/hooks/use-sse.ts +92 -0
- package/inkos/packages/studio/src/hooks/use-theme.test.ts +31 -0
- package/inkos/packages/studio/src/hooks/use-theme.ts +75 -0
- package/inkos/packages/studio/src/index.css +323 -0
- package/inkos/packages/studio/src/lib/error-copy.test.ts +34 -0
- package/inkos/packages/studio/src/lib/error-copy.ts +37 -0
- package/inkos/packages/studio/src/lib/utils.ts +6 -0
- package/inkos/packages/studio/src/main.tsx +10 -0
- package/inkos/packages/studio/src/pages/Analytics.tsx +80 -0
- package/inkos/packages/studio/src/pages/BookCreate.tsx +895 -0
- package/inkos/packages/studio/src/pages/BookDetail.tsx +652 -0
- package/inkos/packages/studio/src/pages/ChapterReader.tsx +266 -0
- package/inkos/packages/studio/src/pages/ChatPage.tsx +521 -0
- package/inkos/packages/studio/src/pages/DaemonControl.tsx +116 -0
- package/inkos/packages/studio/src/pages/Dashboard.tsx +379 -0
- package/inkos/packages/studio/src/pages/DoctorView.tsx +82 -0
- package/inkos/packages/studio/src/pages/GenreManager.tsx +464 -0
- package/inkos/packages/studio/src/pages/ImportManager.tsx +216 -0
- package/inkos/packages/studio/src/pages/LanguageSelector.tsx +74 -0
- package/inkos/packages/studio/src/pages/LogViewer.tsx +82 -0
- package/inkos/packages/studio/src/pages/RadarView.tsx +157 -0
- package/inkos/packages/studio/src/pages/ServiceDetailPage.tsx +393 -0
- package/inkos/packages/studio/src/pages/ServiceListPage.tsx +463 -0
- package/inkos/packages/studio/src/pages/StyleManager.tsx +225 -0
- package/inkos/packages/studio/src/pages/TruthFiles.tsx +194 -0
- package/inkos/packages/studio/src/pages/chat-page-state.test.ts +206 -0
- package/inkos/packages/studio/src/pages/chat-page-state.ts +112 -0
- package/inkos/packages/studio/src/pages/page-state.test.ts +258 -0
- package/inkos/packages/studio/src/pages/service-detail-state.test.ts +294 -0
- package/inkos/packages/studio/src/pages/service-detail-state.ts +234 -0
- package/inkos/packages/studio/src/pages/style-manager-state.test.ts +22 -0
- package/inkos/packages/studio/src/pages/truth-files-state.test.ts +61 -0
- package/inkos/packages/studio/src/shared/contracts.ts +143 -0
- package/inkos/packages/studio/src/store/chat/__tests__/message-parts.test.ts +172 -0
- package/inkos/packages/studio/src/store/chat/index.ts +3 -0
- package/inkos/packages/studio/src/store/chat/initialState.ts +8 -0
- package/inkos/packages/studio/src/store/chat/message-policy.test.ts +16 -0
- package/inkos/packages/studio/src/store/chat/message-policy.ts +5 -0
- package/inkos/packages/studio/src/store/chat/parts-builder.ts +187 -0
- package/inkos/packages/studio/src/store/chat/selectors.ts +13 -0
- package/inkos/packages/studio/src/store/chat/slices/create/action.ts +10 -0
- package/inkos/packages/studio/src/store/chat/slices/create/initialState.ts +9 -0
- package/inkos/packages/studio/src/store/chat/slices/message/action.ts +417 -0
- package/inkos/packages/studio/src/store/chat/slices/message/initialState.ts +10 -0
- package/inkos/packages/studio/src/store/chat/slices/message/runtime.test.ts +21 -0
- package/inkos/packages/studio/src/store/chat/slices/message/runtime.ts +233 -0
- package/inkos/packages/studio/src/store/chat/slices/message/stream-events.ts +272 -0
- package/inkos/packages/studio/src/store/chat/store.ts +11 -0
- package/inkos/packages/studio/src/store/chat/types.ts +169 -0
- package/inkos/packages/studio/src/store/service/index.ts +2 -0
- package/inkos/packages/studio/src/store/service/store.ts +123 -0
- package/inkos/packages/studio/src/store/service/types.ts +50 -0
- package/inkos/packages/studio/tsconfig.json +24 -0
- package/inkos/packages/studio/tsconfig.server.json +11 -0
- package/inkos/packages/studio/vite.config.ts +34 -0
- package/inkos/packages/studio/vitest.config.ts +14 -0
- package/inkos/pnpm-lock.yaml +9569 -0
- package/inkos/pnpm-workspace.yaml +2 -0
- package/inkos/scripts/prepare-package-for-publish.mjs +135 -0
- package/inkos/scripts/restore-package-json.mjs +31 -0
- package/inkos/scripts/set-package-versions.mjs +74 -0
- package/inkos/scripts/verify-no-workspace-protocol.mjs +140 -0
- package/inkos/skills/SKILL.md +654 -0
- package/inkos/tsconfig.json +19 -0
- package/package.json +4 -3
- /package/.next/static/{F2hMZMf1IyCVAWpkbtRz7 → -3vIrBZXdQ0rp7Wa3Kz40}/_buildManifest.js +0 -0
- /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
|
+
}
|