crewly 1.8.9 → 1.8.11
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/config/constants.d.ts.map +1 -0
- package/config/index.d.ts.map +1 -0
- package/config/roles/_common/memory-instructions.md +6 -5
- package/config/roles/_common/wiki-instructions.md +49 -0
- package/config/roles/architect/prompt.md +2 -2
- package/config/roles/backend-developer/prompt.md +2 -2
- package/config/roles/designer/prompt.md +2 -2
- package/config/roles/developer/prompt.md +2 -2
- package/config/roles/frontend-developer/prompt.md +2 -2
- package/config/roles/fullstack-dev/prompt.md +2 -2
- package/config/roles/generalist/prompt.md +2 -2
- package/config/roles/ops/prompt.md +2 -2
- package/config/roles/orchestrator/prompt.md +135 -11
- package/config/roles/product-manager/prompt.md +2 -2
- package/config/roles/qa/prompt.md +2 -2
- package/config/roles/qa-engineer/prompt.md +2 -2
- package/config/roles/researcher/prompt.md +15 -6
- package/config/roles/sales/prompt.md +2 -2
- package/config/roles/support/prompt.md +2 -2
- package/config/roles/team-leader/prompt.md +17 -2
- package/config/roles/tpm/prompt.md +2 -2
- package/config/roles/ux-designer/prompt.md +2 -2
- package/config/skills/orchestrator/wiki-cleanup/SKILL.md +89 -0
- package/config/skills/orchestrator/wiki-cleanup/execute.sh +139 -0
- package/config/skills/orchestrator/wiki-lint/SKILL.md +75 -0
- package/config/skills/orchestrator/wiki-lint/execute.sh +66 -0
- package/config/skills/orchestrator/wiki-migrate/SKILL.md +103 -0
- package/config/skills/orchestrator/wiki-migrate/execute.sh +82 -0
- package/config/skills/orchestrator/wiki-process-queue/SKILL.md +9 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +169 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +1779 -0
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +18 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +63 -0
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.js +5 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts +109 -0
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js +418 -4
- package/dist/backend/backend/src/controllers/wiki/wiki.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js +11 -1
- package/dist/backend/backend/src/controllers/wiki/wiki.routes.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +64 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/index.js.orc-bak-20260529 +3130 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts +513 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js +1568 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts +86 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js +147 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts +68 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js +131 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts +130 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js +263 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts +74 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js +140 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts +29 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js +279 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts +340 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js +1176 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts +79 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js +145 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts +79 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js +218 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts +16 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js +16 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts +135 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js +185 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +141 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +310 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts +91 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js +143 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts +103 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js +256 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts +143 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js +264 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts +13 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js +91 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts +135 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js +1937 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +1 -1
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts +429 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js +852 -0
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +171 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js +725 -0
- package/dist/backend/backend/src/services/project/task-tracking.service.js.map +1 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +50 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +45 -0
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js +34 -1
- package/dist/backend/backend/src/services/v3/agent-auto-claim.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts +118 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js +326 -0
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.d.ts +72 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.js +186 -0
- package/dist/backend/backend/src/services/wiki/wiki-backlinks.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +4 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +24 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +74 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +154 -0
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts +160 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js +399 -0
- package/dist/backend/backend/src/services/wiki/wiki-cleanup.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.d.ts +182 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.js +505 -0
- package/dist/backend/backend/src/services/wiki/wiki-lint.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts +232 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js +1416 -0
- package/dist/backend/backend/src/services/wiki/wiki-migrate.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.d.ts +51 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.js +102 -0
- package/dist/backend/backend/src/services/wiki/wiki-recent.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts +84 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js +156 -0
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts +90 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js +190 -0
- package/dist/backend/backend/src/services/wiki/wiki-search.service.js.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts +164 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +675 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/workflow/cron-task.service.js +65 -0
- package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
- package/dist/backend/backend/src/types/auto-assign.types.d.ts +271 -0
- package/dist/backend/backend/src/types/auto-assign.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/auto-assign.types.js +136 -0
- package/dist/backend/backend/src/types/auto-assign.types.js.map +1 -0
- package/dist/backend/backend/src/types/cron-task.types.d.ts +16 -1
- package/dist/backend/backend/src/types/cron-task.types.d.ts.map +1 -1
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts +111 -0
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/esm-require.utils.js +124 -0
- package/dist/backend/backend/src/utils/esm-require.utils.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts +220 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.js +37 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-module.interface.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts +56 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js +91 -0
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js +304 -0
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts +115 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js +215 -0
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js.map +1 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts +78 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.js +179 -0
- package/dist/cli/backend/src/services/memory/embedding-provider.js.map +1 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts +331 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.js +814 -0
- package/dist/cli/backend/src/services/memory/vector-store.service.js.map +1 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts +171 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.js +725 -0
- package/dist/cli/backend/src/services/project/task-tracking.service.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +19 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +45 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/types/auto-assign.types.d.ts +271 -0
- package/dist/cli/backend/src/types/auto-assign.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/auto-assign.types.js +136 -0
- package/dist/cli/backend/src/types/auto-assign.types.js.map +1 -0
- package/dist/cli/cli/src/index.js +0 -0
- package/frontend/dist/assets/{index-db3f5041.css → index-068bb4f6.css} +10 -1
- package/frontend/dist/assets/index-c24ceb15.js +4960 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/config/skills/agent/core/query-knowledge/SKILL.md +0 -87
- package/config/skills/agent/core/query-knowledge/execute.sh +0 -30
- package/config/skills/orchestrator/query-knowledge/SKILL.md +0 -75
- package/config/skills/orchestrator/query-knowledge/execute.sh +0 -30
- package/frontend/dist/assets/index-cc115bb4.js +0 -4926
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WikiWorkItemBridgeService — materialises pending wiki work as claimable WIs.
|
|
3
|
+
*
|
|
4
|
+
* Replaces the wiki-bookkeep / wiki-reflect "shouldFire ≥ threshold" cron
|
|
5
|
+
* model with the same V3 pool the rest of the system uses. Two source
|
|
6
|
+
* streams feed the pool:
|
|
7
|
+
*
|
|
8
|
+
* 1. Pending `wiki-queue` items (one WI per vault that has ≥ 1 pending
|
|
9
|
+
* item; the agent drains the whole vault in that WI).
|
|
10
|
+
* 2. Legacy `.crewly/knowledge/*` migration candidates per project (one
|
|
11
|
+
* WI per project root with > 0 proposed pages).
|
|
12
|
+
*
|
|
13
|
+
* Idle agents claim them through `TaskPoolService.claimFromPool` — no
|
|
14
|
+
* extra cron decisions about who to ping. Failures route through the
|
|
15
|
+
* standard task:failed / reconciler escalation paths (closing the loop
|
|
16
|
+
* with the 2026-05-27 reconciler-escalation fix in
|
|
17
|
+
* `reconciler-data-provider.ts:applyCorrection`).
|
|
18
|
+
*
|
|
19
|
+
* PR-1 routing (conservative): both kinds target the orchestrator
|
|
20
|
+
* session. The wiki skills today live under `config/skills/orchestrator/`,
|
|
21
|
+
* so ORC is the only role with a built-in handler. Broadening to TLs is
|
|
22
|
+
* a follow-up once the skills are promoted to a shared path.
|
|
23
|
+
*
|
|
24
|
+
* Dedupe: at most one non-terminal WI per (kind, vaultPath/projectRoot).
|
|
25
|
+
*
|
|
26
|
+
* @module services/wiki/wiki-workitem-bridge.service
|
|
27
|
+
*/
|
|
28
|
+
import os from 'os';
|
|
29
|
+
import path from 'path';
|
|
30
|
+
import { existsSync, promises as fs } from 'fs';
|
|
31
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
32
|
+
import { TaskPoolService } from '../task-pool/task-pool.service.js';
|
|
33
|
+
import { WikiQueueService } from './wiki-queue.service.js';
|
|
34
|
+
import { WikiMigrateService } from './wiki-migrate.service.js';
|
|
35
|
+
import { WikiCleanupService } from './wiki-cleanup.service.js';
|
|
36
|
+
import { discoverWikiVaults } from './wiki-bookkeep-trigger.service.js';
|
|
37
|
+
import { createWorkItem } from '../../types/v2/work-item.types.js';
|
|
38
|
+
import { atomicWriteJson, safeReadJson, ensureDir } from '../../utils/file-io.utils.js';
|
|
39
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// Constants
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
const DEFAULT_INTERVAL_MS = 10 * 60 * 1000;
|
|
44
|
+
const DEFAULT_TARGET_AGENT = 'crewly-orc';
|
|
45
|
+
/**
|
|
46
|
+
* Hard cap on how many new WIs a single tick can publish. PTY-backed
|
|
47
|
+
* agents (Claude Code) flood their paste buffer when too many
|
|
48
|
+
* `[SYSTEM — TASK RECOVERY]` messages arrive in rapid succession —
|
|
49
|
+
* observed during the 2026-05-27 first boot of the bridge: 11 WIs hit
|
|
50
|
+
* ORC in 0.97s, the PTY buffer choked, and ORC sat "Reticulating…" for
|
|
51
|
+
* 13 min until the reconciler revoked the claim. Two per tick gives
|
|
52
|
+
* the worker breathing room; the next interval will pick up the rest.
|
|
53
|
+
*/
|
|
54
|
+
const DEFAULT_MAX_CREATES_PER_TICK = 2;
|
|
55
|
+
/**
|
|
56
|
+
* Cooldown between creating WIs for the same (kind, vault/project) key.
|
|
57
|
+
* Default 30 min.
|
|
58
|
+
*
|
|
59
|
+
* Why: pool-only dedupe is insufficient when ORC's recovery flow forcibly
|
|
60
|
+
* deletes WIs via `DELETE /api/task-pool/:id?force=1` to escape its own
|
|
61
|
+
* PTY-flood state (observed 2026-05-27 22:46:49, post-session-restart
|
|
62
|
+
* cleanup wiped 10 wiki WIs). A removed WI is no longer in the pool, so
|
|
63
|
+
* the next tick's `snapshotInflight` misses it and re-creates a duplicate
|
|
64
|
+
* with a fresh id. Cooldown blocks that loop by tracking
|
|
65
|
+
* "last attempted create" in-memory regardless of pool state.
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_COOLDOWN_MS = 30 * 60 * 1000;
|
|
68
|
+
/**
|
|
69
|
+
* State-file name (under {@link getCrewlyHomePath}). Used to persist
|
|
70
|
+
* {@link WikiWorkItemBridgeService.lastCreatedAt} across restarts so an
|
|
71
|
+
* operator's cancel decision isn't undone by the next boot tick. Without
|
|
72
|
+
* persistence: cancel a WI → restart → bridge's in-memory cooldown is
|
|
73
|
+
* empty → re-creates the WI within seconds.
|
|
74
|
+
*/
|
|
75
|
+
const STATE_FILE_NAME = 'wiki-bridge-state.json';
|
|
76
|
+
/** Persisted state version — bump on shape change to invalidate old files. */
|
|
77
|
+
const STATE_VERSION = 1;
|
|
78
|
+
/**
|
|
79
|
+
* Cap on entries in the persisted map. Old entries are no-ops anyway
|
|
80
|
+
* (their cooldown has long expired), but pruning keeps the file small.
|
|
81
|
+
*/
|
|
82
|
+
const STATE_MAX_ENTRIES = 1000;
|
|
83
|
+
function emptyState() {
|
|
84
|
+
return { version: STATE_VERSION, lastCreatedAt: {} };
|
|
85
|
+
}
|
|
86
|
+
/** WorkItem.metadata.kind value identifying a wiki queue drain WI. */
|
|
87
|
+
export const META_KIND_WIKI_DRAIN = 'wiki_queue_drain';
|
|
88
|
+
/** WorkItem.metadata.kind value identifying a wiki legacy migration WI. */
|
|
89
|
+
export const META_KIND_WIKI_MIGRATE = 'wiki_legacy_migrate';
|
|
90
|
+
/** WorkItem.metadata.kind value identifying a wiki cleanup (low-quality removal) WI. */
|
|
91
|
+
export const META_KIND_WIKI_CLEANUP = 'wiki_quality_cleanup';
|
|
92
|
+
/**
|
|
93
|
+
* Per-WI chunk size for cleanup. The bridge surfaces at most this many
|
|
94
|
+
* candidate page paths per WorkItem; if a vault has more candidates,
|
|
95
|
+
* the bridge creates one WI per chunk on successive ticks. 10 keeps the
|
|
96
|
+
* agent's per-WI workload small enough to actually read each page
|
|
97
|
+
* before deciding keep/delete.
|
|
98
|
+
*/
|
|
99
|
+
const CLEANUP_CHUNK_SIZE = 10;
|
|
100
|
+
/**
|
|
101
|
+
* Skip cleanup-WI creation until the vault has at least this many
|
|
102
|
+
* candidates. Avoids spawning a WI for a vault that has 1-2 minor
|
|
103
|
+
* blemishes — the bridge has better things to do.
|
|
104
|
+
*/
|
|
105
|
+
const CLEANUP_MIN_CANDIDATES = 10;
|
|
106
|
+
const TERMINAL_STATUSES = new Set([
|
|
107
|
+
'done',
|
|
108
|
+
'verified',
|
|
109
|
+
'cancelled',
|
|
110
|
+
'failed',
|
|
111
|
+
'rejected',
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
114
|
+
* Periodic bridge: pending wiki queue items + migration candidates
|
|
115
|
+
* become claimable WorkItems in the V3 pool.
|
|
116
|
+
*/
|
|
117
|
+
export class WikiWorkItemBridgeService {
|
|
118
|
+
static instance = null;
|
|
119
|
+
logger;
|
|
120
|
+
intervalMs;
|
|
121
|
+
targetAgent;
|
|
122
|
+
maxCreatesPerTick;
|
|
123
|
+
cooldownMs;
|
|
124
|
+
nowFn;
|
|
125
|
+
discoverRoots;
|
|
126
|
+
discoverProjectRoots;
|
|
127
|
+
queue;
|
|
128
|
+
migrate;
|
|
129
|
+
cleanup;
|
|
130
|
+
pool;
|
|
131
|
+
timer = null;
|
|
132
|
+
running = false;
|
|
133
|
+
/** Per-key (kind + path) last-create timestamp. Survives pool deletes. */
|
|
134
|
+
lastCreatedAt = new Map();
|
|
135
|
+
/** Absolute path of the persisted state file (or `null` to disable). */
|
|
136
|
+
statePath;
|
|
137
|
+
/** True after `loadStateFromDisk` has either populated or no-op'd. */
|
|
138
|
+
stateLoaded = false;
|
|
139
|
+
constructor(opts = {}) {
|
|
140
|
+
this.logger = LoggerService.getInstance().createComponentLogger('WikiWorkItemBridge');
|
|
141
|
+
this.intervalMs = opts.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
142
|
+
this.targetAgent = opts.targetAgent ?? DEFAULT_TARGET_AGENT;
|
|
143
|
+
this.maxCreatesPerTick = Math.max(1, opts.maxCreatesPerTick ?? DEFAULT_MAX_CREATES_PER_TICK);
|
|
144
|
+
this.cooldownMs = Math.max(0, opts.cooldownMs ?? DEFAULT_COOLDOWN_MS);
|
|
145
|
+
this.nowFn = opts.now ?? (() => Date.now());
|
|
146
|
+
this.discoverRoots = opts.discoverRoots ?? discoverWikiVaults;
|
|
147
|
+
this.discoverProjectRoots = opts.discoverProjectRoots ?? defaultProjectRoots;
|
|
148
|
+
this.queue = opts.queueService ?? WikiQueueService.getInstance();
|
|
149
|
+
this.migrate = opts.migrateService ?? WikiMigrateService.getInstance();
|
|
150
|
+
this.cleanup = opts.cleanupService ?? WikiCleanupService.getInstance();
|
|
151
|
+
this.pool = opts.taskPool ?? TaskPoolService.getInstance();
|
|
152
|
+
if (opts.statePath === null) {
|
|
153
|
+
this.statePath = null;
|
|
154
|
+
}
|
|
155
|
+
else if (typeof opts.statePath === 'string') {
|
|
156
|
+
this.statePath = opts.statePath;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
this.statePath = path.join(getCrewlyHomePath(), STATE_FILE_NAME);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Hydrate {@link lastCreatedAt} from disk. Idempotent — runs once per
|
|
164
|
+
* service lifetime. Bad/missing/stale-version files reset to empty.
|
|
165
|
+
*/
|
|
166
|
+
async loadStateFromDisk() {
|
|
167
|
+
if (this.stateLoaded)
|
|
168
|
+
return;
|
|
169
|
+
this.stateLoaded = true;
|
|
170
|
+
if (!this.statePath)
|
|
171
|
+
return;
|
|
172
|
+
const state = await safeReadJson(this.statePath, emptyState(), this.logger);
|
|
173
|
+
if (!state || state.version !== STATE_VERSION || typeof state.lastCreatedAt !== 'object') {
|
|
174
|
+
this.logger.debug('Bridge state file missing / wrong version — starting fresh', {
|
|
175
|
+
statePath: this.statePath,
|
|
176
|
+
loadedVersion: state?.version,
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
let loadedCount = 0;
|
|
181
|
+
const now = this.nowFn();
|
|
182
|
+
for (const [key, ts] of Object.entries(state.lastCreatedAt)) {
|
|
183
|
+
if (typeof ts !== 'number')
|
|
184
|
+
continue;
|
|
185
|
+
// Drop entries already past their cooldown — they're no-ops and
|
|
186
|
+
// would just bloat memory.
|
|
187
|
+
if (now - ts >= this.cooldownMs)
|
|
188
|
+
continue;
|
|
189
|
+
this.lastCreatedAt.set(key, ts);
|
|
190
|
+
loadedCount++;
|
|
191
|
+
}
|
|
192
|
+
if (loadedCount > 0) {
|
|
193
|
+
this.logger.info('Loaded bridge cooldown state', {
|
|
194
|
+
statePath: this.statePath,
|
|
195
|
+
activeCooldowns: loadedCount,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Persist {@link lastCreatedAt} to disk. Best-effort — write failures
|
|
201
|
+
* are logged but never throw (the bridge is non-essential infrastructure).
|
|
202
|
+
* Prunes expired entries before writing so the file stays small.
|
|
203
|
+
*/
|
|
204
|
+
async saveStateToDisk() {
|
|
205
|
+
if (!this.statePath)
|
|
206
|
+
return;
|
|
207
|
+
try {
|
|
208
|
+
const now = this.nowFn();
|
|
209
|
+
const fresh = {};
|
|
210
|
+
for (const [key, ts] of this.lastCreatedAt) {
|
|
211
|
+
if (now - ts >= this.cooldownMs)
|
|
212
|
+
continue;
|
|
213
|
+
fresh[key] = ts;
|
|
214
|
+
}
|
|
215
|
+
// Cap the file to STATE_MAX_ENTRIES newest by timestamp. Production
|
|
216
|
+
// expects ~10-50 entries; the cap is paranoia against a future
|
|
217
|
+
// path-explosion bug.
|
|
218
|
+
const entries = Object.entries(fresh);
|
|
219
|
+
if (entries.length > STATE_MAX_ENTRIES) {
|
|
220
|
+
entries.sort((a, b) => b[1] - a[1]);
|
|
221
|
+
entries.length = STATE_MAX_ENTRIES;
|
|
222
|
+
}
|
|
223
|
+
const payload = {
|
|
224
|
+
version: STATE_VERSION,
|
|
225
|
+
lastCreatedAt: Object.fromEntries(entries),
|
|
226
|
+
};
|
|
227
|
+
await ensureDir(path.dirname(this.statePath));
|
|
228
|
+
await atomicWriteJson(this.statePath, payload);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
this.logger.warn('Bridge state persist failed (non-fatal)', {
|
|
232
|
+
statePath: this.statePath,
|
|
233
|
+
error: err.message,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* True iff the key's cooldown is still active (i.e., we created a WI
|
|
239
|
+
* for this key recently). Survives forced pool deletes so an
|
|
240
|
+
* agent-driven cleanup can't trick the bridge into re-creating
|
|
241
|
+
* immediately.
|
|
242
|
+
*/
|
|
243
|
+
isCoolingDown(key) {
|
|
244
|
+
const last = this.lastCreatedAt.get(key);
|
|
245
|
+
if (last == null)
|
|
246
|
+
return false;
|
|
247
|
+
return this.nowFn() - last < this.cooldownMs;
|
|
248
|
+
}
|
|
249
|
+
markCreated(key) {
|
|
250
|
+
this.lastCreatedAt.set(key, this.nowFn());
|
|
251
|
+
}
|
|
252
|
+
static getInstance() {
|
|
253
|
+
return this.instance;
|
|
254
|
+
}
|
|
255
|
+
/** Wire / detach the production singleton. */
|
|
256
|
+
static setInstance(next) {
|
|
257
|
+
if (this.instance && this.instance !== next)
|
|
258
|
+
this.instance.stop();
|
|
259
|
+
this.instance = next;
|
|
260
|
+
}
|
|
261
|
+
/** Start the periodic scan. Idempotent. Fires an immediate tick. */
|
|
262
|
+
start() {
|
|
263
|
+
if (this.timer)
|
|
264
|
+
return;
|
|
265
|
+
void this.tick();
|
|
266
|
+
this.timer = setInterval(() => void this.tick(), this.intervalMs);
|
|
267
|
+
this.logger.info('WikiWorkItemBridge started', {
|
|
268
|
+
intervalMs: this.intervalMs,
|
|
269
|
+
targetAgent: this.targetAgent,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
stop() {
|
|
273
|
+
if (this.timer) {
|
|
274
|
+
clearInterval(this.timer);
|
|
275
|
+
this.timer = null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
/** Single pass — exposed for tests and the initial boot-time fire. */
|
|
279
|
+
async tick() {
|
|
280
|
+
const result = {
|
|
281
|
+
scannedVaults: [],
|
|
282
|
+
createdForVault: [],
|
|
283
|
+
skippedInflightVaults: [],
|
|
284
|
+
scannedProjects: [],
|
|
285
|
+
createdForProject: [],
|
|
286
|
+
skippedInflightProjects: [],
|
|
287
|
+
deferredByThrottle: [],
|
|
288
|
+
skippedByCooldown: [],
|
|
289
|
+
createdCleanupForVault: [],
|
|
290
|
+
};
|
|
291
|
+
if (this.running) {
|
|
292
|
+
// Overlapping ticks would create duplicates; the dedupe pass below
|
|
293
|
+
// also catches it but cheaper to bail early.
|
|
294
|
+
this.logger.debug('Tick already in progress — skipping overlap');
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
this.running = true;
|
|
298
|
+
let createdThisTick = 0;
|
|
299
|
+
try {
|
|
300
|
+
await this.loadStateFromDisk();
|
|
301
|
+
const { inflightVaults, inflightProjects, inflightCleanupVaults } = await this.snapshotInflight();
|
|
302
|
+
// Drains come first — they're cheap, fast, and the most common case.
|
|
303
|
+
const vaults = await this.discoverRoots();
|
|
304
|
+
for (const vaultPath of vaults) {
|
|
305
|
+
result.scannedVaults.push(vaultPath);
|
|
306
|
+
if (inflightVaults.has(vaultPath)) {
|
|
307
|
+
result.skippedInflightVaults.push(vaultPath);
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const key = `${META_KIND_WIKI_DRAIN}:${vaultPath}`;
|
|
311
|
+
if (this.isCoolingDown(key)) {
|
|
312
|
+
result.skippedByCooldown.push(vaultPath);
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
const pending = await this.queue.list({
|
|
317
|
+
vaultPath,
|
|
318
|
+
status: 'pending',
|
|
319
|
+
limit: 200,
|
|
320
|
+
});
|
|
321
|
+
if (pending.length === 0)
|
|
322
|
+
continue;
|
|
323
|
+
if (createdThisTick >= this.maxCreatesPerTick) {
|
|
324
|
+
result.deferredByThrottle.push(vaultPath);
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
await this.createDrainWorkItem(vaultPath, pending.length);
|
|
328
|
+
this.markCreated(key);
|
|
329
|
+
result.createdForVault.push(vaultPath);
|
|
330
|
+
createdThisTick++;
|
|
331
|
+
}
|
|
332
|
+
catch (err) {
|
|
333
|
+
this.logger.debug('queue.list / drain WI create failed (non-fatal)', {
|
|
334
|
+
vaultPath,
|
|
335
|
+
error: err.message,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
const projects = await this.discoverProjectRoots();
|
|
340
|
+
for (const projectRoot of projects) {
|
|
341
|
+
result.scannedProjects.push(projectRoot);
|
|
342
|
+
if (inflightProjects.has(projectRoot)) {
|
|
343
|
+
result.skippedInflightProjects.push(projectRoot);
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
const key = `${META_KIND_WIKI_MIGRATE}:${projectRoot}`;
|
|
347
|
+
if (this.isCoolingDown(key)) {
|
|
348
|
+
result.skippedByCooldown.push(projectRoot);
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const scan = await this.migrate.scan({ projectRoot });
|
|
353
|
+
if (!scan.ok)
|
|
354
|
+
continue;
|
|
355
|
+
if (!scan.legacyDetected)
|
|
356
|
+
continue;
|
|
357
|
+
// Count NET-NEW pages only. A scan keeps already-migrated entries in
|
|
358
|
+
// `proposedPages` tagged with skipReason 'already migrated' (apply-only
|
|
359
|
+
// reasons like write_failed never appear on a scan result). Counting
|
|
360
|
+
// the raw length means a fully-migrated vault still reports
|
|
361
|
+
// proposed > 0, so the gate below never trips and the bridge re-creates
|
|
362
|
+
// a no-op migrate WI every cooldown cycle (churn loop). Only pages with
|
|
363
|
+
// no skipReason represent genuine new work for the migrate agent.
|
|
364
|
+
const proposed = scan.proposedPages.filter((p) => !p.skipReason).length;
|
|
365
|
+
if (proposed === 0)
|
|
366
|
+
continue;
|
|
367
|
+
if (createdThisTick >= this.maxCreatesPerTick) {
|
|
368
|
+
result.deferredByThrottle.push(projectRoot);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
await this.createMigrateWorkItem(projectRoot, proposed);
|
|
372
|
+
this.markCreated(key);
|
|
373
|
+
result.createdForProject.push(projectRoot);
|
|
374
|
+
createdThisTick++;
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
this.logger.debug('migrate.scan / migrate WI create failed (non-fatal)', {
|
|
378
|
+
projectRoot,
|
|
379
|
+
error: err.message,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Third pass: cleanup. Re-walk the same vault list (already scanned
|
|
384
|
+
// above for drain) and ask the cleanup service if it has candidates.
|
|
385
|
+
// Same dedupe + cooldown + throttle apply. Each cleanup WI carries
|
|
386
|
+
// up to CLEANUP_CHUNK_SIZE explicit page paths; if the vault has
|
|
387
|
+
// more, the next bridge tick (post-cooldown) creates a fresh WI
|
|
388
|
+
// for the next chunk — the agent doesn't have to handle all of
|
|
389
|
+
// it in one session.
|
|
390
|
+
for (const vaultPath of vaults) {
|
|
391
|
+
if (inflightCleanupVaults.has(vaultPath))
|
|
392
|
+
continue;
|
|
393
|
+
const cleanupKey = `${META_KIND_WIKI_CLEANUP}:${vaultPath}`;
|
|
394
|
+
if (this.isCoolingDown(cleanupKey)) {
|
|
395
|
+
result.skippedByCooldown.push(`cleanup:${vaultPath}`);
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const scan = await this.cleanup.scan({ vaultPath });
|
|
400
|
+
if (!scan.ok || !('candidates' in scan))
|
|
401
|
+
continue;
|
|
402
|
+
if (scan.candidates.length < CLEANUP_MIN_CANDIDATES)
|
|
403
|
+
continue;
|
|
404
|
+
if (createdThisTick >= this.maxCreatesPerTick) {
|
|
405
|
+
result.deferredByThrottle.push(`cleanup:${vaultPath}`);
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
await this.createCleanupWorkItem(vaultPath, scan.candidates.slice(0, CLEANUP_CHUNK_SIZE), scan.candidates.length);
|
|
409
|
+
this.markCreated(cleanupKey);
|
|
410
|
+
result.createdCleanupForVault.push(vaultPath);
|
|
411
|
+
createdThisTick++;
|
|
412
|
+
}
|
|
413
|
+
catch (err) {
|
|
414
|
+
this.logger.debug('cleanup.scan / cleanup WI create failed (non-fatal)', {
|
|
415
|
+
vaultPath,
|
|
416
|
+
error: err.message,
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (result.deferredByThrottle.length > 0) {
|
|
421
|
+
this.logger.info('WikiWorkItemBridge throttled — deferred to next tick', {
|
|
422
|
+
createdThisTick,
|
|
423
|
+
maxCreatesPerTick: this.maxCreatesPerTick,
|
|
424
|
+
deferredCount: result.deferredByThrottle.length,
|
|
425
|
+
nextTickInMs: this.intervalMs,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
// Persist any state mutations from this tick. One write per tick
|
|
429
|
+
// amortises filesystem cost even if many keys were updated.
|
|
430
|
+
if (createdThisTick > 0) {
|
|
431
|
+
await this.saveStateToDisk();
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
catch (err) {
|
|
435
|
+
this.logger.warn('WikiWorkItemBridge tick failed (non-fatal)', {
|
|
436
|
+
error: err.message,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
finally {
|
|
440
|
+
this.running = false;
|
|
441
|
+
}
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Build the dedupe sets from the pool's current contents. A WI is
|
|
446
|
+
* "in-flight" if its kind metadata matches and its status is not
|
|
447
|
+
* terminal — i.e. it's still queued/claimed/running/etc.
|
|
448
|
+
*/
|
|
449
|
+
async snapshotInflight() {
|
|
450
|
+
const inflightVaults = new Set();
|
|
451
|
+
const inflightProjects = new Set();
|
|
452
|
+
const inflightCleanupVaults = new Set();
|
|
453
|
+
const allItems = await this.pool.getAllItems();
|
|
454
|
+
for (const wi of allItems) {
|
|
455
|
+
if (TERMINAL_STATUSES.has(wi.status))
|
|
456
|
+
continue;
|
|
457
|
+
const meta = wi.metadata ?? {};
|
|
458
|
+
const kind = meta['kind'];
|
|
459
|
+
if (kind === META_KIND_WIKI_DRAIN && typeof meta['vaultPath'] === 'string') {
|
|
460
|
+
inflightVaults.add(meta['vaultPath']);
|
|
461
|
+
}
|
|
462
|
+
else if (kind === META_KIND_WIKI_MIGRATE && typeof meta['projectRoot'] === 'string') {
|
|
463
|
+
inflightProjects.add(meta['projectRoot']);
|
|
464
|
+
}
|
|
465
|
+
else if (kind === META_KIND_WIKI_CLEANUP && typeof meta['vaultPath'] === 'string') {
|
|
466
|
+
inflightCleanupVaults.add(meta['vaultPath']);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return { inflightVaults, inflightProjects, inflightCleanupVaults };
|
|
470
|
+
}
|
|
471
|
+
async createDrainWorkItem(vaultPath, pendingCount) {
|
|
472
|
+
const scope = describeVaultScope(vaultPath);
|
|
473
|
+
const wi = createWorkItem({
|
|
474
|
+
type: 'delegate',
|
|
475
|
+
owner: 'orchestrator',
|
|
476
|
+
target: this.targetAgent,
|
|
477
|
+
title: `Drain ${pendingCount} wiki queue item(s) — ${scope}`,
|
|
478
|
+
description: `Process ${pendingCount} pending wiki queue items in ${vaultPath}.`,
|
|
479
|
+
briefMarkdown: drainBrief(vaultPath, pendingCount),
|
|
480
|
+
maxRetries: 1,
|
|
481
|
+
metadata: {
|
|
482
|
+
kind: META_KIND_WIKI_DRAIN,
|
|
483
|
+
vaultPath,
|
|
484
|
+
pendingCount,
|
|
485
|
+
autoCreated: true,
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
await this.pool.addToPool(wi);
|
|
489
|
+
this.logger.info('Created wiki-drain WorkItem', {
|
|
490
|
+
workItemId: wi.id,
|
|
491
|
+
vaultPath,
|
|
492
|
+
pendingCount,
|
|
493
|
+
target: this.targetAgent,
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
async createCleanupWorkItem(vaultPath, chunk, totalCandidates) {
|
|
497
|
+
const scope = describeVaultScope(vaultPath);
|
|
498
|
+
const wi = createWorkItem({
|
|
499
|
+
type: 'delegate',
|
|
500
|
+
owner: 'orchestrator',
|
|
501
|
+
target: this.targetAgent,
|
|
502
|
+
title: `Cleanup ${chunk.length} of ${totalCandidates} low-quality wiki page(s) — ${scope}`,
|
|
503
|
+
description: `Review ${chunk.length} cleanup candidates in ${vaultPath}; decide keep/delete; call wiki-cleanup --apply with the final list.`,
|
|
504
|
+
briefMarkdown: cleanupBrief(vaultPath, chunk, totalCandidates),
|
|
505
|
+
maxRetries: 1,
|
|
506
|
+
metadata: {
|
|
507
|
+
kind: META_KIND_WIKI_CLEANUP,
|
|
508
|
+
vaultPath,
|
|
509
|
+
chunkSize: chunk.length,
|
|
510
|
+
totalCandidates,
|
|
511
|
+
candidates: chunk.map((c) => ({
|
|
512
|
+
relPath: c.relPath,
|
|
513
|
+
reasons: c.reasons,
|
|
514
|
+
confidence: c.confidence,
|
|
515
|
+
bytes: c.bytes,
|
|
516
|
+
})),
|
|
517
|
+
autoCreated: true,
|
|
518
|
+
},
|
|
519
|
+
});
|
|
520
|
+
await this.pool.addToPool(wi);
|
|
521
|
+
this.logger.info('Created wiki-cleanup WorkItem', {
|
|
522
|
+
workItemId: wi.id,
|
|
523
|
+
vaultPath,
|
|
524
|
+
chunkSize: chunk.length,
|
|
525
|
+
totalCandidates,
|
|
526
|
+
target: this.targetAgent,
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
async createMigrateWorkItem(projectRoot, proposedCount) {
|
|
530
|
+
const wi = createWorkItem({
|
|
531
|
+
type: 'delegate',
|
|
532
|
+
owner: 'orchestrator',
|
|
533
|
+
target: this.targetAgent,
|
|
534
|
+
title: `Migrate ${proposedCount} legacy wiki item(s) — ${path.basename(projectRoot)}`,
|
|
535
|
+
description: `Apply wiki-migrate for ${projectRoot} (${proposedCount} new pages).`,
|
|
536
|
+
briefMarkdown: migrateBrief(projectRoot, proposedCount),
|
|
537
|
+
maxRetries: 1,
|
|
538
|
+
metadata: {
|
|
539
|
+
kind: META_KIND_WIKI_MIGRATE,
|
|
540
|
+
projectRoot,
|
|
541
|
+
proposedCount,
|
|
542
|
+
autoCreated: true,
|
|
543
|
+
},
|
|
544
|
+
});
|
|
545
|
+
await this.pool.addToPool(wi);
|
|
546
|
+
this.logger.info('Created wiki-migrate WorkItem', {
|
|
547
|
+
workItemId: wi.id,
|
|
548
|
+
projectRoot,
|
|
549
|
+
proposedCount,
|
|
550
|
+
target: this.targetAgent,
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
// ---------------------------------------------------------------------------
|
|
555
|
+
// Helpers
|
|
556
|
+
// ---------------------------------------------------------------------------
|
|
557
|
+
/**
|
|
558
|
+
* Best-effort label for a vault path that surfaces in UI/list views.
|
|
559
|
+
* Order matters: check global before the generic project tail.
|
|
560
|
+
*/
|
|
561
|
+
export function describeVaultScope(vaultPath) {
|
|
562
|
+
if (vaultPath.endsWith('global-wiki'))
|
|
563
|
+
return 'global';
|
|
564
|
+
const homeTeams = path.join(os.homedir(), '.crewly/teams');
|
|
565
|
+
if (vaultPath.startsWith(homeTeams + path.sep)) {
|
|
566
|
+
const remainder = vaultPath.slice(homeTeams.length + 1);
|
|
567
|
+
const teamId = remainder.split(path.sep)[0] ?? '?';
|
|
568
|
+
return `team ${teamId}`;
|
|
569
|
+
}
|
|
570
|
+
const parent = path.dirname(path.dirname(vaultPath));
|
|
571
|
+
return `project ${path.basename(parent)}`;
|
|
572
|
+
}
|
|
573
|
+
function drainBrief(vaultPath, pendingCount) {
|
|
574
|
+
return [
|
|
575
|
+
'# Wiki Queue Drain',
|
|
576
|
+
'',
|
|
577
|
+
'**This is a bridge-auto maintenance WorkItem.** See the "Bridge-auto',
|
|
578
|
+
'WorkItems" rule in your prompt — process via the skill, do not delete.',
|
|
579
|
+
'',
|
|
580
|
+
`**Vault:** \`${vaultPath}\``,
|
|
581
|
+
`**Pending items:** ${pendingCount}`,
|
|
582
|
+
'',
|
|
583
|
+
`**Partial progress is the norm** — drain whatever you can in this turn`,
|
|
584
|
+
`(5-20 items is a healthy chunk; you do not need to clear all ${pendingCount}).`,
|
|
585
|
+
'When you mark this WI `done`, the bridge cooldown (30 min) expires and a',
|
|
586
|
+
'fresh WI appears with whatever remains. Iterative drain over multiple',
|
|
587
|
+
'sessions is the design.',
|
|
588
|
+
'',
|
|
589
|
+
'```bash',
|
|
590
|
+
`bash {{ORCHESTRATOR_SKILLS_PATH}}/wiki-process-queue/execute.sh --vault ${vaultPath}`,
|
|
591
|
+
'```',
|
|
592
|
+
'',
|
|
593
|
+
'Each invocation claims one item, lets you read context, then commits via `POST /queue/<id>/process` (or `/skip`). Repeat 5-20 times per WI, then mark `done` via `complete-task` with a one-line summary of what you drained.',
|
|
594
|
+
].join('\n');
|
|
595
|
+
}
|
|
596
|
+
function cleanupBrief(vaultPath, chunk, totalCandidates) {
|
|
597
|
+
const lines = [
|
|
598
|
+
'# Wiki Quality Cleanup',
|
|
599
|
+
'',
|
|
600
|
+
'**This is a bridge-auto maintenance WorkItem.** See the "Bridge-auto',
|
|
601
|
+
'WorkItems" rule in your prompt — process via the skill, do not delete.',
|
|
602
|
+
'',
|
|
603
|
+
`**Vault:** \`${vaultPath}\``,
|
|
604
|
+
`**Candidates in this WI:** ${chunk.length}`,
|
|
605
|
+
`**Total candidates in vault:** ${totalCandidates}`,
|
|
606
|
+
'',
|
|
607
|
+
`**Partial progress is the norm** — review the ${chunk.length} pages in this WI, decide keep/delete for each, then call \`wiki-cleanup --apply --pages "..."\` with ONLY the relPaths you decided to delete. The next bridge tick (30 min cooldown) creates a fresh WI with the next ${chunk.length}. No need to drain everything in one session.`,
|
|
608
|
+
'',
|
|
609
|
+
'## Candidates',
|
|
610
|
+
'',
|
|
611
|
+
];
|
|
612
|
+
for (const c of chunk) {
|
|
613
|
+
const conf = c.confidence === null ? 'unknown' : c.confidence.toFixed(2);
|
|
614
|
+
lines.push(`- \`${c.relPath}\` (confidence=${conf}, ${c.bytes} bytes) — ${c.reasons.join('; ')}`);
|
|
615
|
+
}
|
|
616
|
+
lines.push('', '## Workflow', '', '1. For each candidate, run `wiki-page-read` (or grep the file directly) to check whether it has any real value despite the flag. A `confidence=0.3` page might still be the only documentation of a critical gotcha.', '2. Build a comma-separated list of relPaths you want to delete.', '3. Run apply:', '', '```bash', `bash {{ORCHESTRATOR_SKILLS_PATH}}/wiki-cleanup/execute.sh --vault ${vaultPath} --apply --pages "<rel1>,<rel2>,..."`, '```', '', '4. Archive lives at `<vault>/.wiki-cleanup-archive.json` — every deleted page\'s body + frontmatter is preserved, so cleanup is reversible.', '5. Mark this WI done via `complete-task` with a one-line summary (`deleted N of ' + chunk.length + ', kept M, archive grew by ...`).');
|
|
617
|
+
return lines.join('\n');
|
|
618
|
+
}
|
|
619
|
+
function migrateBrief(projectRoot, proposedCount) {
|
|
620
|
+
return [
|
|
621
|
+
'# Wiki Legacy Migration',
|
|
622
|
+
'',
|
|
623
|
+
'**This is a bridge-auto maintenance WorkItem.** See the "Bridge-auto',
|
|
624
|
+
'WorkItems" rule in your prompt — process via the skill, do not delete.',
|
|
625
|
+
'',
|
|
626
|
+
`**Project root:** \`${projectRoot}\``,
|
|
627
|
+
`**Proposed new pages:** ${proposedCount}`,
|
|
628
|
+
'',
|
|
629
|
+
`**Partial progress is the norm** — ${proposedCount} sounds like a lot,`,
|
|
630
|
+
'but the migrate service is idempotent: every successful page lands in',
|
|
631
|
+
'the manifest and is skipped on the next run. So you can run `--apply`,',
|
|
632
|
+
'let it work for a few minutes, mark this WI `done` whatever it migrated,',
|
|
633
|
+
'and the next bridge tick (30 min cooldown) creates a fresh WI for what',
|
|
634
|
+
'remains. No need to wait for the whole batch to complete.',
|
|
635
|
+
'',
|
|
636
|
+
'1. Dry-run first to sanity-check the proposal:',
|
|
637
|
+
'```bash',
|
|
638
|
+
`bash {{ORCHESTRATOR_SKILLS_PATH}}/wiki-migrate/execute.sh --project-root ${projectRoot}`,
|
|
639
|
+
'```',
|
|
640
|
+
'2. Apply (will resume from the manifest on subsequent runs):',
|
|
641
|
+
'```bash',
|
|
642
|
+
`bash {{ORCHESTRATOR_SKILLS_PATH}}/wiki-migrate/execute.sh --project-root ${projectRoot} --apply`,
|
|
643
|
+
'```',
|
|
644
|
+
'3. Report `applied`/`skipped` counts in `complete-task` output and mark `done`. The bridge will detect remaining items on its next tick.',
|
|
645
|
+
].join('\n');
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Default project-root discovery used by the production bridge. Mirrors
|
|
649
|
+
* `discoverWikiVaults`'s project sources but returns roots, not vault paths.
|
|
650
|
+
*/
|
|
651
|
+
export async function defaultProjectRoots() {
|
|
652
|
+
const roots = [];
|
|
653
|
+
const cwd = process.cwd();
|
|
654
|
+
if (existsSync(path.join(cwd, '.crewly')))
|
|
655
|
+
roots.push(cwd);
|
|
656
|
+
const projectsJsonPath = path.join(os.homedir(), '.crewly/projects.json');
|
|
657
|
+
if (existsSync(projectsJsonPath)) {
|
|
658
|
+
try {
|
|
659
|
+
const raw = await fs.readFile(projectsJsonPath, 'utf8');
|
|
660
|
+
const parsed = JSON.parse(raw);
|
|
661
|
+
if (Array.isArray(parsed)) {
|
|
662
|
+
for (const entry of parsed) {
|
|
663
|
+
if (entry && typeof entry.path === 'string' && path.isAbsolute(entry.path)) {
|
|
664
|
+
roots.push(entry.path);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
catch {
|
|
670
|
+
// malformed projects.json — partial discovery is fine
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
return Array.from(new Set(roots));
|
|
674
|
+
}
|
|
675
|
+
//# sourceMappingURL=wiki-workitem-bridge.service.js.map
|