mixdog 0.7.1
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/.claude-plugin/marketplace.json +31 -0
- package/.claude-plugin/plugin.json +20 -0
- package/.gitattributes +34 -0
- package/.mcp.json +14 -0
- package/ARCHITECTURE.md +77 -0
- package/CHANGELOG.md +7 -0
- package/CONTRIBUTING.md +45 -0
- package/DATA-FLOW.md +79 -0
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/SECURITY.md +138 -0
- package/UNINSTALL.md +112 -0
- package/agents/maintenance.md +5 -0
- package/agents/memory-classification.md +30 -0
- package/agents/scheduler-task.md +18 -0
- package/agents/webhook-handler.md +27 -0
- package/agents/worker.md +24 -0
- package/bin/bridge +133 -0
- package/bin/statusline-launcher.mjs +78 -0
- package/bin/statusline-lib.mjs +550 -0
- package/bin/statusline.mjs +607 -0
- package/bun.lock +802 -0
- package/commands/config.md +16 -0
- package/commands/doctor.md +13 -0
- package/commands/setup.md +17 -0
- package/defaults/cycle3-review-prompt.md +90 -0
- package/defaults/hidden-roles.json +65 -0
- package/defaults/memory-chunk-prompt.md +63 -0
- package/defaults/memory-promote-prompt.md +135 -0
- package/defaults/mixdog-config.template.json +27 -0
- package/defaults/user-workflow.json +8 -0
- package/defaults/user-workflow.md +12 -0
- package/hooks/hooks.json +73 -0
- package/hooks/lib/active-instance.cjs +77 -0
- package/hooks/lib/permission-evaluator.cjs +411 -0
- package/hooks/lib/permission-route.cjs +63 -0
- package/hooks/lib/permission-rules.cjs +170 -0
- package/hooks/lib/settings-loader.cjs +116 -0
- package/hooks/post-tool-use.cjs +84 -0
- package/hooks/pre-mcp-sandbox.cjs +158 -0
- package/hooks/pre-tool-subagent.cjs +253 -0
- package/hooks/session-start.cjs +1372 -0
- package/hooks/turn-timer.cjs +82 -0
- package/lib/claude-md-writer.cjs +386 -0
- package/lib/config-cjs.cjs +61 -0
- package/lib/hook-pipe-path.cjs +10 -0
- package/lib/keychain-cjs.cjs +263 -0
- package/lib/plugin-paths.cjs +61 -0
- package/lib/rules-builder.cjs +241 -0
- package/lib/text-utils.cjs +61 -0
- package/native/README.md +117 -0
- package/native/prebuilt/linux-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/linux-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/macos-aarch64/mixdog-shim +0 -0
- package/native/prebuilt/macos-x86_64/mixdog-shim +0 -0
- package/native/prebuilt/windows-x86_64/mixdog-shim.exe +0 -0
- package/package.json +107 -0
- package/prompts/code-review.txt +16 -0
- package/prompts/security-audit.txt +17 -0
- package/rules/bridge/00-common.md +39 -0
- package/rules/bridge/20-skip-protocol.md +18 -0
- package/rules/bridge/30-explorer.md +33 -0
- package/rules/bridge/40-cycle1-agent.md +52 -0
- package/rules/bridge/41-cycle2-agent.md +62 -0
- package/rules/bridge/42-cycle3-agent.md +44 -0
- package/rules/lead/00-tool-lead.md +61 -0
- package/rules/lead/01-general.md +23 -0
- package/rules/lead/02-channels.md +49 -0
- package/rules/lead/03-team.md +27 -0
- package/rules/lead/04-workflow.md +20 -0
- package/rules/shared/00-language.md +14 -0
- package/rules/shared/01-tool.md +138 -0
- package/scripts/bootstrap.mjs +184 -0
- package/scripts/bridge-unify-smoke.mjs +308 -0
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/builtin-utils-smoke.mjs +392 -0
- package/scripts/check-json.mjs +45 -0
- package/scripts/check-syntax-changed.mjs +102 -0
- package/scripts/check-syntax.mjs +58 -0
- package/scripts/code-graph-batch.test.mjs +33 -0
- package/scripts/config-preserve-smoke.mjs +180 -0
- package/scripts/doctor.mjs +484 -0
- package/scripts/edit-normalize-fuzz.mjs +130 -0
- package/scripts/edit-normalize-smoke.mjs +401 -0
- package/scripts/edit-operation-smoke.mjs +369 -0
- package/scripts/edit2-smoke.mjs +63 -0
- package/scripts/fuzzy-e2e.mjs +28 -0
- package/scripts/fuzzy-smoke.mjs +26 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/guard-smoke.mjs +66 -0
- package/scripts/hidden-role-schema-smoke.mjs +162 -0
- package/scripts/hook-routing-smoke.mjs +29 -0
- package/scripts/inject-input.ps1 +204 -0
- package/scripts/io-complex-smoke.mjs +667 -0
- package/scripts/io-explore-bench.mjs +424 -0
- package/scripts/io-guardrails-smoke.mjs +205 -0
- package/scripts/io-mini-bench-baseline.json +11 -0
- package/scripts/io-mini-bench.mjs +216 -0
- package/scripts/io-route-harness.mjs +933 -0
- package/scripts/io-telemetry-report.mjs +691 -0
- package/scripts/mutation-bench.mjs +564 -0
- package/scripts/mutation-io-smoke.mjs +1081 -0
- package/scripts/native-patch-bridge-smoke.mjs +288 -0
- package/scripts/native-patch-smoke.mjs +304 -0
- package/scripts/patch-interior-context-smoke.mjs +49 -0
- package/scripts/patch-newline-utf8-smoke.mjs +157 -0
- package/scripts/perf-hook-smoke.mjs +71 -0
- package/scripts/permission-eval-smoke.mjs +426 -0
- package/scripts/prep-patch.mjs +53 -0
- package/scripts/prep-shim.mjs +96 -0
- package/scripts/provider-cache-smoke.mjs +687 -0
- package/scripts/report-runtime-health.mjs +132 -0
- package/scripts/run-mcp.mjs +1547 -0
- package/scripts/salvage-v4a-shatter.test.mjs +58 -0
- package/scripts/scoped-cache-io-smoke.mjs +103 -0
- package/scripts/shell-policy-round3-smoke.mjs +46 -0
- package/scripts/smoke-runtime-negative.ps1 +100 -0
- package/scripts/smoke-runtime-negative.sh +95 -0
- package/scripts/stall-policy-smoke.mjs +50 -0
- package/scripts/start-memory-worker.mjs +23 -0
- package/scripts/statusline-launcher-smoke.mjs +82 -0
- package/scripts/stress-atomic-write.mjs +1028 -0
- package/scripts/test-config-rmw-restore.mjs +122 -0
- package/scripts/test-fault-inject.mjs +164 -0
- package/scripts/test-large-file.mjs +174 -0
- package/scripts/tool-edge-smoke.mjs +209 -0
- package/scripts/uninstall.mjs +201 -0
- package/scripts/webhook-selfheal-smoke.mjs +29 -0
- package/scripts/write-overwrite-guard-smoke.mjs +56 -0
- package/server-main.mjs +3055 -0
- package/server.mjs +468 -0
- package/setup/config-merge.mjs +254 -0
- package/setup/install.mjs +120 -0
- package/setup/launch-core.mjs +507 -0
- package/setup/launch.mjs +101 -0
- package/setup/setup-server.mjs +3206 -0
- package/setup/setup.html +3693 -0
- package/skills/retro-skill-proposer/SKILL.md +92 -0
- package/skills/schedule-add/SKILL.md +77 -0
- package/skills/setup/SKILL.md +346 -0
- package/skills/webhook-add/SKILL.md +81 -0
- package/src/agent/bridge-stall-watchdog.mjs +337 -0
- package/src/agent/index.mjs +2138 -0
- package/src/agent/orchestrator/activity-bus.mjs +38 -0
- package/src/agent/orchestrator/ai-wrapped-dispatch.mjs +1010 -0
- package/src/agent/orchestrator/bridge-retry.mjs +220 -0
- package/src/agent/orchestrator/bridge-trace.mjs +583 -0
- package/src/agent/orchestrator/cache-mtime.mjs +58 -0
- package/src/agent/orchestrator/config.mjs +358 -0
- package/src/agent/orchestrator/context/collect.mjs +651 -0
- package/src/agent/orchestrator/dispatch-persist.mjs +549 -0
- package/src/agent/orchestrator/drain-registry.mjs +50 -0
- package/src/agent/orchestrator/explore-validator.mjs +8 -0
- package/src/agent/orchestrator/internal-roles.mjs +118 -0
- package/src/agent/orchestrator/internal-tools.mjs +88 -0
- package/src/agent/orchestrator/jobs.mjs +116 -0
- package/src/agent/orchestrator/mcp/client.mjs +364 -0
- package/src/agent/orchestrator/providers/anthropic-betas.mjs +21 -0
- package/src/agent/orchestrator/providers/anthropic-oauth.mjs +1745 -0
- package/src/agent/orchestrator/providers/anthropic.mjs +437 -0
- package/src/agent/orchestrator/providers/gemini.mjs +1175 -0
- package/src/agent/orchestrator/providers/grok-oauth.mjs +782 -0
- package/src/agent/orchestrator/providers/model-catalog.mjs +241 -0
- package/src/agent/orchestrator/providers/openai-compat.mjs +1467 -0
- package/src/agent/orchestrator/providers/openai-oauth-ws.mjs +1890 -0
- package/src/agent/orchestrator/providers/openai-oauth.mjs +1307 -0
- package/src/agent/orchestrator/providers/openai-ws.mjs +104 -0
- package/src/agent/orchestrator/providers/registry.mjs +192 -0
- package/src/agent/orchestrator/providers/retry-classifier.mjs +325 -0
- package/src/agent/orchestrator/session/abort-lookup.mjs +13 -0
- package/src/agent/orchestrator/session/cache/post-edit-marks.mjs +42 -0
- package/src/agent/orchestrator/session/cache/prefetch-cache.mjs +142 -0
- package/src/agent/orchestrator/session/cache/read-cache.mjs +319 -0
- package/src/agent/orchestrator/session/cache/scoped-cache-outcome.mjs +11 -0
- package/src/agent/orchestrator/session/cache/scoped-cache.mjs +361 -0
- package/src/agent/orchestrator/session/cache/util.mjs +49 -0
- package/src/agent/orchestrator/session/loop.mjs +1478 -0
- package/src/agent/orchestrator/session/manager.mjs +1975 -0
- package/src/agent/orchestrator/session/read-dedup.mjs +6 -0
- package/src/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/agent/orchestrator/session/save-session-worker.mjs +18 -0
- package/src/agent/orchestrator/session/store.mjs +624 -0
- package/src/agent/orchestrator/session/stream-watchdog.mjs +130 -0
- package/src/agent/orchestrator/session/tool-result-offload.mjs +166 -0
- package/src/agent/orchestrator/session/trim.mjs +491 -0
- package/src/agent/orchestrator/smart-bridge/CACHE-SHARD.md +115 -0
- package/src/agent/orchestrator/smart-bridge/bridge-llm.mjs +327 -0
- package/src/agent/orchestrator/smart-bridge/cache-obs.mjs +150 -0
- package/src/agent/orchestrator/smart-bridge/cache-strategy.mjs +228 -0
- package/src/agent/orchestrator/smart-bridge/index.mjs +215 -0
- package/src/agent/orchestrator/smart-bridge/profiles.mjs +37 -0
- package/src/agent/orchestrator/smart-bridge/registry.mjs +348 -0
- package/src/agent/orchestrator/smart-bridge/session-builder.mjs +116 -0
- package/src/agent/orchestrator/stall-policy.mjs +195 -0
- package/src/agent/orchestrator/tool-loop-guard.mjs +75 -0
- package/src/agent/orchestrator/tools/bash-policy-scan.mjs +77 -0
- package/src/agent/orchestrator/tools/bash-session.mjs +721 -0
- package/src/agent/orchestrator/tools/builtin/advisory-lock.mjs +171 -0
- package/src/agent/orchestrator/tools/builtin/arg-guard.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/atomic-write.mjs +236 -0
- package/src/agent/orchestrator/tools/builtin/bash-tool.mjs +480 -0
- package/src/agent/orchestrator/tools/builtin/binary-file.mjs +76 -0
- package/src/agent/orchestrator/tools/builtin/builtin-tools.mjs +256 -0
- package/src/agent/orchestrator/tools/builtin/cache-layers.mjs +386 -0
- package/src/agent/orchestrator/tools/builtin/cwd-utils.mjs +37 -0
- package/src/agent/orchestrator/tools/builtin/device-paths.mjs +154 -0
- package/src/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +292 -0
- package/src/agent/orchestrator/tools/builtin/diff-utils.mjs +109 -0
- package/src/agent/orchestrator/tools/builtin/edit-base-guard.mjs +58 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-plan.mjs +240 -0
- package/src/agent/orchestrator/tools/builtin/edit-byte-utils.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/edit-commit.mjs +74 -0
- package/src/agent/orchestrator/tools/builtin/edit-context-utils.mjs +242 -0
- package/src/agent/orchestrator/tools/builtin/edit-diagnostics.mjs +211 -0
- package/src/agent/orchestrator/tools/builtin/edit-engine.mjs +1364 -0
- package/src/agent/orchestrator/tools/builtin/edit-failure-context.mjs +126 -0
- package/src/agent/orchestrator/tools/builtin/edit-hint.mjs +141 -0
- package/src/agent/orchestrator/tools/builtin/edit-match-utils.mjs +194 -0
- package/src/agent/orchestrator/tools/builtin/edit-partial-write.mjs +60 -0
- package/src/agent/orchestrator/tools/builtin/edit-stale-refresh.mjs +168 -0
- package/src/agent/orchestrator/tools/builtin/edit-tool.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/edit-utf8-guard.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fs-reachability.mjs +48 -0
- package/src/agent/orchestrator/tools/builtin/fuzzy-match.mjs +99 -0
- package/src/agent/orchestrator/tools/builtin/glob-walk.mjs +170 -0
- package/src/agent/orchestrator/tools/builtin/grep-formatting.mjs +113 -0
- package/src/agent/orchestrator/tools/builtin/hash-utils.mjs +6 -0
- package/src/agent/orchestrator/tools/builtin/list-formatting.mjs +7 -0
- package/src/agent/orchestrator/tools/builtin/list-tool.mjs +593 -0
- package/src/agent/orchestrator/tools/builtin/native-edit-runner.mjs +89 -0
- package/src/agent/orchestrator/tools/builtin/notebook-edit-tool.mjs +300 -0
- package/src/agent/orchestrator/tools/builtin/open-config-tool.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/path-diagnostics.mjs +152 -0
- package/src/agent/orchestrator/tools/builtin/path-locks.mjs +35 -0
- package/src/agent/orchestrator/tools/builtin/path-utils.mjs +201 -0
- package/src/agent/orchestrator/tools/builtin/read-args.mjs +103 -0
- package/src/agent/orchestrator/tools/builtin/read-batch.mjs +172 -0
- package/src/agent/orchestrator/tools/builtin/read-constants.mjs +40 -0
- package/src/agent/orchestrator/tools/builtin/read-formatting.mjs +118 -0
- package/src/agent/orchestrator/tools/builtin/read-image-resize.mjs +189 -0
- package/src/agent/orchestrator/tools/builtin/read-image.mjs +88 -0
- package/src/agent/orchestrator/tools/builtin/read-lines.mjs +12 -0
- package/src/agent/orchestrator/tools/builtin/read-mode-tool.mjs +455 -0
- package/src/agent/orchestrator/tools/builtin/read-open.mjs +190 -0
- package/src/agent/orchestrator/tools/builtin/read-range-index.mjs +271 -0
- package/src/agent/orchestrator/tools/builtin/read-ranges.mjs +26 -0
- package/src/agent/orchestrator/tools/builtin/read-single-tool.mjs +728 -0
- package/src/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +173 -0
- package/src/agent/orchestrator/tools/builtin/read-special-files.mjs +268 -0
- package/src/agent/orchestrator/tools/builtin/read-streaming.mjs +602 -0
- package/src/agent/orchestrator/tools/builtin/read-tool.mjs +530 -0
- package/src/agent/orchestrator/tools/builtin/read-windows.mjs +107 -0
- package/src/agent/orchestrator/tools/builtin/rename-tool.mjs +196 -0
- package/src/agent/orchestrator/tools/builtin/rg-runner.mjs +422 -0
- package/src/agent/orchestrator/tools/builtin/search-builders.mjs +158 -0
- package/src/agent/orchestrator/tools/builtin/search-tool.mjs +869 -0
- package/src/agent/orchestrator/tools/builtin/shell-analysis.mjs +653 -0
- package/src/agent/orchestrator/tools/builtin/shell-jobs.mjs +936 -0
- package/src/agent/orchestrator/tools/builtin/shell-output.mjs +36 -0
- package/src/agent/orchestrator/tools/builtin/shell-runtime.mjs +214 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-helpers.mjs +143 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-store.mjs +206 -0
- package/src/agent/orchestrator/tools/builtin/snapshot-validation.mjs +98 -0
- package/src/agent/orchestrator/tools/builtin/text-stats.mjs +69 -0
- package/src/agent/orchestrator/tools/builtin/windows-roots.mjs +23 -0
- package/src/agent/orchestrator/tools/builtin/write-tool.mjs +401 -0
- package/src/agent/orchestrator/tools/builtin.mjs +500 -0
- package/src/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +39 -0
- package/src/agent/orchestrator/tools/code-graph-tool-defs.mjs +24 -0
- package/src/agent/orchestrator/tools/code-graph.mjs +4095 -0
- package/src/agent/orchestrator/tools/cwd-tool.mjs +298 -0
- package/src/agent/orchestrator/tools/destructive-warning.mjs +323 -0
- package/src/agent/orchestrator/tools/edit-normalize.mjs +603 -0
- package/src/agent/orchestrator/tools/env-scrub.mjs +100 -0
- package/src/agent/orchestrator/tools/graph-binary-fetcher.mjs +144 -0
- package/src/agent/orchestrator/tools/graph-manifest.json +26 -0
- package/src/agent/orchestrator/tools/host-input.mjs +204 -0
- package/src/agent/orchestrator/tools/mutation-content-cache.mjs +67 -0
- package/src/agent/orchestrator/tools/mutation-planner.mjs +75 -0
- package/src/agent/orchestrator/tools/next-call-utils.mjs +48 -0
- package/src/agent/orchestrator/tools/patch-binary-fetcher.mjs +133 -0
- package/src/agent/orchestrator/tools/patch-manifest.json +26 -0
- package/src/agent/orchestrator/tools/patch-tool-defs.mjs +20 -0
- package/src/agent/orchestrator/tools/patch.mjs +2754 -0
- package/src/agent/orchestrator/tools/progress-message.mjs +118 -0
- package/src/agent/orchestrator/tools/result-compression.mjs +279 -0
- package/src/agent/orchestrator/tools/shell-command.mjs +865 -0
- package/src/agent/orchestrator/tools/shell-exec-policy.mjs +89 -0
- package/src/agent/orchestrator/tools/shell-policy-danger-target.mjs +27 -0
- package/src/agent/orchestrator/tools/shell-policy-imports.mjs +7 -0
- package/src/agent/orchestrator/tools/shell-policy.mjs +345 -0
- package/src/agent/orchestrator/tools/shell-snapshot.mjs +313 -0
- package/src/agent/orchestrator/workflow-store.mjs +93 -0
- package/src/agent/tool-defs.mjs +103 -0
- package/src/channels/backends/discord.mjs +784 -0
- package/src/channels/data/voice-runtime-manifest.json +138 -0
- package/src/channels/index.mjs +3229 -0
- package/src/channels/lib/cli-worker-host.mjs +12 -0
- package/src/channels/lib/config-lock.mjs +13 -0
- package/src/channels/lib/config.mjs +292 -0
- package/src/channels/lib/drop-trace.mjs +71 -0
- package/src/channels/lib/event-pipeline.mjs +81 -0
- package/src/channels/lib/event-queue.mjs +345 -0
- package/src/channels/lib/executor.mjs +168 -0
- package/src/channels/lib/format.mjs +188 -0
- package/src/channels/lib/holidays.mjs +138 -0
- package/src/channels/lib/hook-pipe-server.mjs +802 -0
- package/src/channels/lib/interaction-workflows.mjs +184 -0
- package/src/channels/lib/memory-client.mjs +149 -0
- package/src/channels/lib/output-forwarder.mjs +765 -0
- package/src/channels/lib/runtime-paths.mjs +479 -0
- package/src/channels/lib/scheduler.mjs +723 -0
- package/src/channels/lib/session-control.mjs +36 -0
- package/src/channels/lib/session-discovery.mjs +103 -0
- package/src/channels/lib/settings.mjs +11 -0
- package/src/channels/lib/state-file.mjs +68 -0
- package/src/channels/lib/status-snapshot.mjs +219 -0
- package/src/channels/lib/tool-format.mjs +140 -0
- package/src/channels/lib/transcript-discovery.mjs +195 -0
- package/src/channels/lib/voice-runtime-fetcher.mjs +734 -0
- package/src/channels/lib/webhook.mjs +1179 -0
- package/src/channels/lib/whisper-server.mjs +477 -0
- package/src/channels/tool-defs.mjs +170 -0
- package/src/daemon/host.mjs +118 -0
- package/src/daemon/mcp-transport.mjs +47 -0
- package/src/daemon/session.mjs +100 -0
- package/src/daemon/thin-client.mjs +71 -0
- package/src/daemon/transport.mjs +163 -0
- package/src/memory/data/runtime-manifest.json +40 -0
- package/src/memory/index.mjs +3305 -0
- package/src/memory/lib/agent-ipc.mjs +93 -0
- package/src/memory/lib/bridge-trace-queries.mjs +120 -0
- package/src/memory/lib/core-memory-store.mjs +330 -0
- package/src/memory/lib/embedding-provider.mjs +269 -0
- package/src/memory/lib/embedding-worker.mjs +323 -0
- package/src/memory/lib/llm-worker-host.mjs +17 -0
- package/src/memory/lib/memory-cycle.mjs +11 -0
- package/src/memory/lib/memory-cycle1.mjs +641 -0
- package/src/memory/lib/memory-cycle2.mjs +1284 -0
- package/src/memory/lib/memory-cycle3.mjs +540 -0
- package/src/memory/lib/memory-embed.mjs +299 -0
- package/src/memory/lib/memory-extraction.mjs +5 -0
- package/src/memory/lib/memory-maintenance-store.mjs +32 -0
- package/src/memory/lib/memory-ops-policy.mjs +190 -0
- package/src/memory/lib/memory-recall-id-patch.mjs +15 -0
- package/src/memory/lib/memory-recall-read-query.mjs +7 -0
- package/src/memory/lib/memory-recall-scope-filter.mjs +63 -0
- package/src/memory/lib/memory-recall-store.mjs +621 -0
- package/src/memory/lib/memory-retrievers.mjs +112 -0
- package/src/memory/lib/memory-score.mjs +71 -0
- package/src/memory/lib/memory-text-utils.mjs +58 -0
- package/src/memory/lib/memory.mjs +412 -0
- package/src/memory/lib/model-profile.mjs +85 -0
- package/src/memory/lib/pg/adapter.mjs +308 -0
- package/src/memory/lib/pg/process.mjs +360 -0
- package/src/memory/lib/pg/supervisor.mjs +396 -0
- package/src/memory/lib/project-id-resolver.mjs +86 -0
- package/src/memory/lib/runtime-fetcher.mjs +442 -0
- package/src/memory/lib/trace-store.mjs +728 -0
- package/src/memory/tool-defs.mjs +79 -0
- package/src/search/index.mjs +1173 -0
- package/src/search/lib/backends/anthropic-oauth.mjs +98 -0
- package/src/search/lib/backends/exa.mjs +50 -0
- package/src/search/lib/backends/firecrawl.mjs +61 -0
- package/src/search/lib/backends/gemini-api.mjs +83 -0
- package/src/search/lib/backends/grok-oauth.mjs +86 -0
- package/src/search/lib/backends/index.mjs +150 -0
- package/src/search/lib/backends/openai-api.mjs +144 -0
- package/src/search/lib/backends/openai-oauth.mjs +98 -0
- package/src/search/lib/backends/openai-web-search.mjs +76 -0
- package/src/search/lib/backends/tavily.mjs +55 -0
- package/src/search/lib/backends/xai-api.mjs +113 -0
- package/src/search/lib/cache.mjs +131 -0
- package/src/search/lib/config.mjs +192 -0
- package/src/search/lib/formatter.mjs +115 -0
- package/src/search/lib/provider-usage.mjs +67 -0
- package/src/search/lib/providers.mjs +47 -0
- package/src/search/lib/search-intent.mjs +109 -0
- package/src/search/lib/setup-handler.mjs +261 -0
- package/src/search/lib/state.mjs +201 -0
- package/src/search/lib/web-tools.mjs +1207 -0
- package/src/search/tool-defs.mjs +83 -0
- package/src/setup/defender-exclusion.mjs +183 -0
- package/src/shared/abort-controller.mjs +15 -0
- package/src/shared/atomic-file.mjs +420 -0
- package/src/shared/config.mjs +350 -0
- package/src/shared/daemon-recycle.mjs +108 -0
- package/src/shared/disable-claude-builtins.mjs +88 -0
- package/src/shared/err-text.mjs +12 -0
- package/src/shared/llm/cost.mjs +66 -0
- package/src/shared/llm/http-agent.mjs +123 -0
- package/src/shared/llm/index.mjs +41 -0
- package/src/shared/llm/pid-cleanup.mjs +27 -0
- package/src/shared/llm/usage-log.mjs +47 -0
- package/src/shared/plugin-paths.mjs +58 -0
- package/src/shared/schedules-store.mjs +70 -0
- package/src/shared/seed.mjs +119 -0
- package/src/shared/user-cwd.mjs +213 -0
- package/src/shared/user-data-guard.mjs +238 -0
- package/src/status/aggregator.mjs +584 -0
- package/src/status/server.mjs +413 -0
- package/tools.json +1653 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
// Cycle 3 — user-curated core memory review.
|
|
2
|
+
//
|
|
3
|
+
// Walks every row in core_entries (via listCore('*')), retrieves the related
|
|
4
|
+
// current memory for each row using searchRelevantHybrid, packs both into a
|
|
5
|
+
// {{CORE_REVIEW}} block for defaults/cycle3-review-prompt.md, then asks the
|
|
6
|
+
// maintenance-preset LLM for one verdict per id. By default Cycle3 performs
|
|
7
|
+
// conservative cleanup: safe compression updates and strict duplicate merges
|
|
8
|
+
// are applied, while deletes stay proposals unless explicitly confirmed.
|
|
9
|
+
//
|
|
10
|
+
// Verdict line grammar (mirrors parseUnifiedFormat in memory-cycle2.mjs):
|
|
11
|
+
// <id>|keep
|
|
12
|
+
// <id>|update|<element>|<summary>
|
|
13
|
+
// <id>|merge|<target_id>|<source_ids_csv>
|
|
14
|
+
// <id>|delete
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from 'fs'
|
|
17
|
+
import { join } from 'path'
|
|
18
|
+
import { resolveMaintenancePreset } from '../../shared/llm/index.mjs'
|
|
19
|
+
import { callBridgeLlm } from './agent-ipc.mjs'
|
|
20
|
+
import { listCore, editCore, deleteCore, CORE_SUMMARY_MAX } from './core-memory-store.mjs'
|
|
21
|
+
import { loadCurrentRulesDigest } from './memory-cycle2.mjs'
|
|
22
|
+
import { embedText } from './embedding-provider.mjs'
|
|
23
|
+
import { searchRelevantHybrid } from './memory-recall-store.mjs'
|
|
24
|
+
|
|
25
|
+
function resourceDir() {
|
|
26
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) return process.env.CLAUDE_PLUGIN_ROOT
|
|
27
|
+
throw new Error('CLAUDE_PLUGIN_ROOT env var required for prompt loading')
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function invokeLlm(prompt, mode, preset, timeout, llmCall = callBridgeLlm) {
|
|
31
|
+
return await llmCall({
|
|
32
|
+
role: 'cycle3-agent',
|
|
33
|
+
taskType: 'maintenance',
|
|
34
|
+
mode,
|
|
35
|
+
preset,
|
|
36
|
+
timeout,
|
|
37
|
+
cwd: null,
|
|
38
|
+
}, prompt)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function throwIfAborted(signal) {
|
|
42
|
+
if (signal?.aborted) throw signal.reason ?? new Error('aborted')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function resolveApplyMode(config, options = {}) {
|
|
46
|
+
if (options?.apply === true) return 'confirmed'
|
|
47
|
+
if (options?.apply === false) return 'proposal'
|
|
48
|
+
const raw = String(options?.applyMode || config?.cycle3?.applyMode || 'conservative').trim().toLowerCase()
|
|
49
|
+
if (raw === 'proposal' || raw === 'dry-run' || raw === 'dryrun') return 'proposal'
|
|
50
|
+
return 'conservative'
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function normalizeComparable(value) {
|
|
54
|
+
return String(value ?? '')
|
|
55
|
+
.toLowerCase()
|
|
56
|
+
.replace(/[|`"'“”‘’()[\]{}<>]/g, ' ')
|
|
57
|
+
.replace(/\s+/g, ' ')
|
|
58
|
+
.trim()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function compactComparable(value) {
|
|
62
|
+
return normalizeComparable(value).replace(/\s+/g, '')
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function charDice(a, b) {
|
|
66
|
+
const aa = compactComparable(a)
|
|
67
|
+
const bb = compactComparable(b)
|
|
68
|
+
if (!aa || !bb) return 0
|
|
69
|
+
if (aa === bb) return 1
|
|
70
|
+
if (aa.length < 3 || bb.length < 3) return aa === bb ? 1 : 0
|
|
71
|
+
const grams = (s) => {
|
|
72
|
+
const m = new Map()
|
|
73
|
+
for (let i = 0; i <= s.length - 3; i++) {
|
|
74
|
+
const g = s.slice(i, i + 3)
|
|
75
|
+
m.set(g, (m.get(g) || 0) + 1)
|
|
76
|
+
}
|
|
77
|
+
return m
|
|
78
|
+
}
|
|
79
|
+
const ga = grams(aa)
|
|
80
|
+
const gb = grams(bb)
|
|
81
|
+
let overlap = 0
|
|
82
|
+
for (const [g, n] of ga) overlap += Math.min(n, gb.get(g) || 0)
|
|
83
|
+
const total = [...ga.values()].reduce((s, n) => s + n, 0) + [...gb.values()].reduce((s, n) => s + n, 0)
|
|
84
|
+
return total > 0 ? (2 * overlap) / total : 0
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function coreText(core) {
|
|
88
|
+
return `${core?.element || ''}\n${core?.summary || ''}`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isSafeConservativeUpdate(current, action) {
|
|
92
|
+
if (!current || !action?.element || !action?.summary) return { ok: false, reason: 'missing text' }
|
|
93
|
+
const newElement = normalizeComparable(action.element)
|
|
94
|
+
const newSummary = normalizeComparable(action.summary)
|
|
95
|
+
if (!newElement || !newSummary) return { ok: false, reason: 'empty rewrite' }
|
|
96
|
+
if (newSummary.length > CORE_SUMMARY_MAX) return { ok: false, reason: 'summary too long' }
|
|
97
|
+
|
|
98
|
+
const oldText = coreText(current)
|
|
99
|
+
const newText = `${action.element}\n${action.summary}`
|
|
100
|
+
const oldLen = normalizeComparable(oldText).length
|
|
101
|
+
const newLen = normalizeComparable(newText).length
|
|
102
|
+
if (oldLen > 0 && newLen > oldLen + 20) return { ok: false, reason: 'rewrite expands entry' }
|
|
103
|
+
|
|
104
|
+
const sim = charDice(oldText, newText)
|
|
105
|
+
if (sim < 0.28) return { ok: false, reason: `rewrite drift sim=${sim.toFixed(2)}` }
|
|
106
|
+
return { ok: true, reason: 'safe compression' }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function findElementConflict(coreById, currentId, element, projectId) {
|
|
110
|
+
const nextElement = String(element ?? '').trim()
|
|
111
|
+
if (!nextElement) return null
|
|
112
|
+
for (const [id, row] of coreById) {
|
|
113
|
+
if (Number(id) === Number(currentId)) continue
|
|
114
|
+
if ((row.project_id ?? null) !== (projectId ?? null)) continue
|
|
115
|
+
if (String(row.element ?? '') === nextElement) return Number(id)
|
|
116
|
+
}
|
|
117
|
+
return null
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function isStrictDuplicate(a, b) {
|
|
121
|
+
if (!a || !b) return false
|
|
122
|
+
const ae = compactComparable(a.element)
|
|
123
|
+
const be = compactComparable(b.element)
|
|
124
|
+
const as = compactComparable(a.summary)
|
|
125
|
+
const bs = compactComparable(b.summary)
|
|
126
|
+
if (as && bs && as === bs) return true
|
|
127
|
+
if (ae && be && ae === be && charDice(a.summary, b.summary) >= 0.65) return true
|
|
128
|
+
const sim = charDice(coreText(a), coreText(b))
|
|
129
|
+
return sim >= 0.78
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function formatRelatedRow(r) {
|
|
133
|
+
const tag = r.project_id ? r.project_id : 'COMMON'
|
|
134
|
+
const stat = r.status ? `[${r.status}]` : '[?]'
|
|
135
|
+
const el = r.element ? `el:${r.element} ` : ''
|
|
136
|
+
const sm = String(r.summary || r.content || '').replace(/\s+/g, ' ').slice(0, 160)
|
|
137
|
+
return ` - id:${r.id} ${stat} ${tag} ${r.category ?? '?'} ${el}sm:${sm}`
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function formatCoreBlock(core, related) {
|
|
141
|
+
const tag = core.project_id ? core.project_id : 'COMMON'
|
|
142
|
+
const head = `## CORE id:${core.id} ${tag} ${core.category}`
|
|
143
|
+
const el = ` element: ${core.element}`
|
|
144
|
+
const sm = ` summary: ${String(core.summary || '').replace(/\s+/g, ' ')}`
|
|
145
|
+
const rel = related && related.length
|
|
146
|
+
? ` related current memory (top ${related.length}):\n` + related.map(formatRelatedRow).join('\n')
|
|
147
|
+
: ` related current memory: (none found)`
|
|
148
|
+
return [head, el, sm, rel].join('\n')
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Parse cycle3 verdict lines. Returns { actions } where each action is one of
|
|
152
|
+
// { id, verb:'keep' } | { id, verb:'update', element, summary }
|
|
153
|
+
// | { id, verb:'merge', targetId, sourceIds:[...] } | { id, verb:'delete' }.
|
|
154
|
+
function parseVerdicts(raw, idSet) {
|
|
155
|
+
if (raw == null) return null
|
|
156
|
+
const text = String(raw).trim()
|
|
157
|
+
if (!text) return { actions: [] }
|
|
158
|
+
const lines = text.split('\n')
|
|
159
|
+
const actions = []
|
|
160
|
+
let sawValid = false
|
|
161
|
+
for (const rawLine of lines) {
|
|
162
|
+
const line = rawLine.trim().replace(/^\d+[\.)]?\s+(?=\d+\|)/, '')
|
|
163
|
+
if (!line) continue
|
|
164
|
+
if (line.startsWith('//') || line.startsWith('#')) continue
|
|
165
|
+
if (line.startsWith('```')) continue
|
|
166
|
+
const parts = line.split('|')
|
|
167
|
+
if (parts.length < 2) continue
|
|
168
|
+
const id = Number(parts[0].trim())
|
|
169
|
+
const verb = parts[1].trim().toLowerCase()
|
|
170
|
+
if (!Number.isFinite(id) || !verb) continue
|
|
171
|
+
if (!idSet.has(id)) continue
|
|
172
|
+
sawValid = true
|
|
173
|
+
if (verb === 'keep') {
|
|
174
|
+
actions.push({ id, verb: 'keep' })
|
|
175
|
+
} else if (verb === 'update') {
|
|
176
|
+
const element = (parts[2] ?? '').trim()
|
|
177
|
+
const summary = parts.slice(3).join('|').trim()
|
|
178
|
+
if (!element || !summary) continue
|
|
179
|
+
actions.push({ id, verb: 'update', element, summary })
|
|
180
|
+
} else if (verb === 'merge') {
|
|
181
|
+
const targetId = Number((parts[2] ?? '').trim())
|
|
182
|
+
const sourceIds = [...new Set((parts[3] ?? '')
|
|
183
|
+
.split(',')
|
|
184
|
+
.map(s => Number(String(s).trim()))
|
|
185
|
+
.filter(n => Number.isFinite(n) && idSet.has(n)))]
|
|
186
|
+
if (!Number.isFinite(targetId) || !idSet.has(targetId)) {
|
|
187
|
+
process.stderr.write(`[cycle3] merge rejected: id=${id} invalid target\n`)
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
if (sourceIds.length === 0) {
|
|
191
|
+
process.stderr.write(`[cycle3] merge rejected: id=${id} invalid sources\n`)
|
|
192
|
+
continue
|
|
193
|
+
}
|
|
194
|
+
if (targetId !== id && !sourceIds.includes(id)) {
|
|
195
|
+
process.stderr.write(
|
|
196
|
+
`[cycle3] merge rejected: id=${id} must be target or listed source (target=${targetId} sources=${sourceIds.join(',')})\n`,
|
|
197
|
+
)
|
|
198
|
+
continue
|
|
199
|
+
}
|
|
200
|
+
actions.push({ id, verb: 'merge', targetId, sourceIds })
|
|
201
|
+
} else if (verb === 'delete') {
|
|
202
|
+
actions.push({ id, verb: 'delete' })
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (!sawValid) return null
|
|
206
|
+
return { actions }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const _runCycle3InFlight = new WeakMap()
|
|
210
|
+
|
|
211
|
+
export async function runCycle3(db, config, dataDir, options = {}) {
|
|
212
|
+
const signal = options?.signal
|
|
213
|
+
throwIfAborted(signal)
|
|
214
|
+
const applyMode = resolveApplyMode(config, options)
|
|
215
|
+
const partial = {
|
|
216
|
+
reviewed: 0, kept: 0, updated: 0, merged: 0, deleted: 0,
|
|
217
|
+
proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
|
|
218
|
+
held: { updated: 0, merged: 0, deleted: 0 },
|
|
219
|
+
applied: applyMode !== 'proposal',
|
|
220
|
+
applyMode,
|
|
221
|
+
details: [],
|
|
222
|
+
}
|
|
223
|
+
if (_runCycle3InFlight.has(db)) {
|
|
224
|
+
process.stderr.write('[cycle3] skipped: already in flight for this db\n')
|
|
225
|
+
return { ...partial, skippedInFlight: true }
|
|
226
|
+
}
|
|
227
|
+
const client = await db._pool.connect()
|
|
228
|
+
let gotLock = false
|
|
229
|
+
try {
|
|
230
|
+
const r = await client.query(`SELECT pg_try_advisory_lock(hashtext($1)) AS got`, ['mixdog.cycle3'])
|
|
231
|
+
gotLock = r.rows[0]?.got === true
|
|
232
|
+
} catch (err) {
|
|
233
|
+
client.release()
|
|
234
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
235
|
+
process.stderr.write(`[cycle3] advisory lock query failed: ${err.message}\n`)
|
|
236
|
+
return { ...partial, skippedInFlight: true }
|
|
237
|
+
}
|
|
238
|
+
if (!gotLock) {
|
|
239
|
+
client.release()
|
|
240
|
+
process.stderr.write('[cycle3] skipped: advisory lock held by another worker\n')
|
|
241
|
+
return { ...partial, skippedInFlight: true }
|
|
242
|
+
}
|
|
243
|
+
const promise = (async () => {
|
|
244
|
+
try {
|
|
245
|
+
return await _runCycle3Impl(db, config, dataDir, options)
|
|
246
|
+
} finally {
|
|
247
|
+
try { await client.query(`SELECT pg_advisory_unlock(hashtext($1))`, ['mixdog.cycle3']) } catch {}
|
|
248
|
+
client.release()
|
|
249
|
+
}
|
|
250
|
+
})()
|
|
251
|
+
_runCycle3InFlight.set(db, promise)
|
|
252
|
+
try { return await promise }
|
|
253
|
+
finally { _runCycle3InFlight.delete(db) }
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function _runCycle3Impl(db, config, dataDir, options = {}) {
|
|
257
|
+
const signal = options?.signal
|
|
258
|
+
const applyMode = resolveApplyMode(config, options)
|
|
259
|
+
const confirmed = applyMode === 'confirmed'
|
|
260
|
+
const conservative = applyMode === 'conservative'
|
|
261
|
+
const mutate = confirmed || conservative
|
|
262
|
+
throwIfAborted(signal)
|
|
263
|
+
if (!dataDir) throw new Error('runCycle3: dataDir required')
|
|
264
|
+
|
|
265
|
+
const cores = await listCore(dataDir, '*')
|
|
266
|
+
throwIfAborted(signal)
|
|
267
|
+
if (!cores || cores.length === 0) {
|
|
268
|
+
process.stderr.write(`[cycle3] reviewed=0 kept=0 updated=0 merged=0 deleted=0 mode=${applyMode} (no core_entries)\n`)
|
|
269
|
+
return {
|
|
270
|
+
reviewed: 0, kept: 0, updated: 0, merged: 0, deleted: 0,
|
|
271
|
+
proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
|
|
272
|
+
held: { updated: 0, merged: 0, deleted: 0 },
|
|
273
|
+
applied: mutate,
|
|
274
|
+
applyMode,
|
|
275
|
+
details: [],
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Per-core related-memory recall.
|
|
280
|
+
const blocks = []
|
|
281
|
+
for (const core of cores) {
|
|
282
|
+
throwIfAborted(signal)
|
|
283
|
+
const queryText = `${core.element}\n${String(core.summary || '')}`.trim()
|
|
284
|
+
let related = []
|
|
285
|
+
try {
|
|
286
|
+
const scope = core.project_id ? String(core.project_id) : 'common'
|
|
287
|
+
let queryVector = null
|
|
288
|
+
try {
|
|
289
|
+
queryVector = await embedText(queryText)
|
|
290
|
+
} catch (err) {
|
|
291
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
292
|
+
process.stderr.write(`[cycle3] embedding failed for core id=${core.id}: ${err.message}\n`)
|
|
293
|
+
}
|
|
294
|
+
related = await searchRelevantHybrid(db, queryText, {
|
|
295
|
+
limit: 8,
|
|
296
|
+
projectScope: scope,
|
|
297
|
+
includeMembers: false,
|
|
298
|
+
queryVector: Array.isArray(queryVector) ? queryVector : undefined,
|
|
299
|
+
})
|
|
300
|
+
} catch (err) {
|
|
301
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
302
|
+
process.stderr.write(`[cycle3] recall failed for core id=${core.id}: ${err.message}\n`)
|
|
303
|
+
related = []
|
|
304
|
+
}
|
|
305
|
+
throwIfAborted(signal)
|
|
306
|
+
blocks.push(formatCoreBlock(core, related))
|
|
307
|
+
}
|
|
308
|
+
const coreReview = blocks.join('\n\n')
|
|
309
|
+
|
|
310
|
+
// Load + fill prompt template.
|
|
311
|
+
const promptPath = join(resourceDir(), 'defaults', 'cycle3-review-prompt.md')
|
|
312
|
+
if (!existsSync(promptPath)) {
|
|
313
|
+
throw new Error(`runCycle3: prompt file missing at ${promptPath}`)
|
|
314
|
+
}
|
|
315
|
+
const template = readFileSync(promptPath, 'utf8')
|
|
316
|
+
const rulesDigest = loadCurrentRulesDigest() || '(no current rules digest available)'
|
|
317
|
+
const prompt = template
|
|
318
|
+
.replace('{{CORE_REVIEW}}', coreReview)
|
|
319
|
+
.replace('{{CURRENT_RULES}}', rulesDigest)
|
|
320
|
+
|
|
321
|
+
const preset = resolveMaintenancePreset('cycle3')
|
|
322
|
+
const timeout = Number(config?.cycle3?.timeout ?? 600000)
|
|
323
|
+
const mode = 'cycle3-review'
|
|
324
|
+
|
|
325
|
+
process.stderr.write(`[cycle3-diag] prompt=${prompt.length} bytes; cores=${cores.length}\n`)
|
|
326
|
+
|
|
327
|
+
let raw
|
|
328
|
+
try {
|
|
329
|
+
throwIfAborted(signal)
|
|
330
|
+
raw = await invokeLlm(prompt, mode, preset, timeout, options.callLlm)
|
|
331
|
+
} catch (err) {
|
|
332
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
333
|
+
process.stderr.write(`[cycle3] LLM error: ${err.message}\n`)
|
|
334
|
+
return {
|
|
335
|
+
reviewed: cores.length, kept: 0, updated: 0, merged: 0, deleted: 0,
|
|
336
|
+
proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
|
|
337
|
+
held: { updated: 0, merged: 0, deleted: 0 },
|
|
338
|
+
applied: mutate,
|
|
339
|
+
applyMode,
|
|
340
|
+
details: [], error: err.message,
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
throwIfAborted(signal)
|
|
344
|
+
|
|
345
|
+
const idSet = new Set(cores.map(c => Number(c.id)))
|
|
346
|
+
const coreById = new Map(cores.map(c => [Number(c.id), c]))
|
|
347
|
+
const parsed = parseVerdicts(raw, idSet)
|
|
348
|
+
if (!parsed) {
|
|
349
|
+
process.stderr.write(
|
|
350
|
+
`[cycle3] unparseable response — skipping (${String(raw ?? '').replace(/\s+/g, ' ').slice(0, 200)})\n`,
|
|
351
|
+
)
|
|
352
|
+
return {
|
|
353
|
+
reviewed: cores.length, kept: 0, updated: 0, merged: 0, deleted: 0,
|
|
354
|
+
proposed: { kept: 0, updated: 0, merged: 0, deleted: 0 },
|
|
355
|
+
held: { updated: 0, merged: 0, deleted: 0 },
|
|
356
|
+
applied: mutate,
|
|
357
|
+
applyMode,
|
|
358
|
+
details: [], error: 'unparseable',
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
const seenVerdictIds = new Set()
|
|
362
|
+
const dedupedActions = []
|
|
363
|
+
for (const action of parsed.actions) {
|
|
364
|
+
const vid = Number(action.id)
|
|
365
|
+
if (seenVerdictIds.has(vid)) {
|
|
366
|
+
process.stderr.write(`[cycle3] duplicate verdict rejected: id=${vid} verb=${action.verb}\n`)
|
|
367
|
+
continue
|
|
368
|
+
}
|
|
369
|
+
seenVerdictIds.add(vid)
|
|
370
|
+
dedupedActions.push(action)
|
|
371
|
+
}
|
|
372
|
+
parsed.actions = dedupedActions
|
|
373
|
+
const actionIds = new Set(parsed.actions.map(a => Number(a.id)).filter(n => Number.isFinite(n)))
|
|
374
|
+
for (const id of idSet) {
|
|
375
|
+
if (!actionIds.has(id)) parsed.actions.push({ id, verb: 'keep' })
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
let kept = 0, updated = 0, merged = 0, deleted = 0
|
|
379
|
+
const proposed = { kept: 0, updated: 0, merged: 0, deleted: 0 }
|
|
380
|
+
const held = { updated: 0, merged: 0, deleted: 0 }
|
|
381
|
+
const details = []
|
|
382
|
+
const touched = new Set() // ids already acted on this cycle — avoid double action
|
|
383
|
+
|
|
384
|
+
// Core-store edit/delete calls are the mutation unit; checkpoints sit before
|
|
385
|
+
// each action/source and after each awaited unit, not inside one file-store write.
|
|
386
|
+
for (const a of parsed.actions) {
|
|
387
|
+
throwIfAborted(signal)
|
|
388
|
+
if (touched.has(a.id)) continue
|
|
389
|
+
if (a.verb === 'keep') {
|
|
390
|
+
kept++
|
|
391
|
+
proposed.kept++
|
|
392
|
+
details.push({ id: a.id, verb: 'keep' })
|
|
393
|
+
touched.add(a.id)
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
if (a.verb === 'update') {
|
|
397
|
+
proposed.updated++
|
|
398
|
+
const safety = conservative ? isSafeConservativeUpdate(coreById.get(a.id), a) : { ok: true, reason: 'confirmed' }
|
|
399
|
+
const conflictId = conservative
|
|
400
|
+
? findElementConflict(coreById, a.id, a.element, coreById.get(a.id)?.project_id ?? null)
|
|
401
|
+
: null
|
|
402
|
+
if (!mutate || (conservative && (!safety.ok || conflictId != null))) {
|
|
403
|
+
held.updated++
|
|
404
|
+
details.push({
|
|
405
|
+
id: a.id, verb: 'update', element: a.element, summary: a.summary,
|
|
406
|
+
applied: false, held: true,
|
|
407
|
+
reason: !mutate ? 'proposal mode' : (conflictId != null ? `element conflicts with core id=${conflictId}` : safety.reason),
|
|
408
|
+
})
|
|
409
|
+
touched.add(a.id)
|
|
410
|
+
continue
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
await editCore(dataDir, a.id, { element: a.element, summary: a.summary })
|
|
414
|
+
updated++
|
|
415
|
+
details.push({ id: a.id, verb: 'update', element: a.element, summary: a.summary, applied: true })
|
|
416
|
+
touched.add(a.id)
|
|
417
|
+
} catch (err) {
|
|
418
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
419
|
+
process.stderr.write(`[cycle3] update failed id=${a.id}: ${err.message}\n`)
|
|
420
|
+
details.push({ id: a.id, verb: 'update', error: err.message })
|
|
421
|
+
}
|
|
422
|
+
continue
|
|
423
|
+
}
|
|
424
|
+
if (a.verb === 'delete') {
|
|
425
|
+
proposed.deleted++
|
|
426
|
+
if (!confirmed) {
|
|
427
|
+
held.deleted++
|
|
428
|
+
details.push({
|
|
429
|
+
id: a.id, verb: 'delete', applied: false, held: true,
|
|
430
|
+
reason: conservative ? 'delete requires APPLY CYCLE3' : 'proposal mode',
|
|
431
|
+
})
|
|
432
|
+
touched.add(a.id)
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
try {
|
|
436
|
+
await deleteCore(dataDir, a.id)
|
|
437
|
+
deleted++
|
|
438
|
+
details.push({ id: a.id, verb: 'delete', applied: true })
|
|
439
|
+
touched.add(a.id)
|
|
440
|
+
} catch (err) {
|
|
441
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
442
|
+
process.stderr.write(`[cycle3] delete failed id=${a.id}: ${err.message}\n`)
|
|
443
|
+
details.push({ id: a.id, verb: 'delete', error: err.message })
|
|
444
|
+
}
|
|
445
|
+
continue
|
|
446
|
+
}
|
|
447
|
+
if (a.verb === 'merge') {
|
|
448
|
+
if (a.targetId !== a.id && !a.sourceIds.includes(a.id)) {
|
|
449
|
+
process.stderr.write(
|
|
450
|
+
`[cycle3] merge rejected: id=${a.id} must be target or listed source (target=${a.targetId} sources=${a.sourceIds.join(',')})\n`,
|
|
451
|
+
)
|
|
452
|
+
touched.add(a.id)
|
|
453
|
+
touched.add(a.targetId)
|
|
454
|
+
for (const sid of a.sourceIds) touched.add(sid)
|
|
455
|
+
details.push({ id: a.id, verb: 'merge', error: 'id must be target or listed source' })
|
|
456
|
+
continue
|
|
457
|
+
}
|
|
458
|
+
// Only merge within the same project pool. Survivor = targetId.
|
|
459
|
+
const target = coreById.get(a.targetId)
|
|
460
|
+
if (!target) {
|
|
461
|
+
details.push({ id: a.id, verb: 'merge', error: `target ${a.targetId} not found` })
|
|
462
|
+
touched.add(a.id)
|
|
463
|
+
continue
|
|
464
|
+
}
|
|
465
|
+
const validSources = []
|
|
466
|
+
for (const sid of a.sourceIds) {
|
|
467
|
+
throwIfAborted(signal)
|
|
468
|
+
if (sid === a.targetId) continue
|
|
469
|
+
if (touched.has(sid)) continue
|
|
470
|
+
const src = coreById.get(sid)
|
|
471
|
+
if (!src) continue
|
|
472
|
+
if ((src.project_id ?? null) !== (target.project_id ?? null)) {
|
|
473
|
+
process.stderr.write(`[cycle3] merge skipped src=${sid} target=${a.targetId} (project pool mismatch)\n`)
|
|
474
|
+
continue
|
|
475
|
+
}
|
|
476
|
+
validSources.push(sid)
|
|
477
|
+
}
|
|
478
|
+
if (validSources.length === 0) {
|
|
479
|
+
details.push({ id: a.id, verb: 'merge', error: 'no valid sources' })
|
|
480
|
+
continue
|
|
481
|
+
}
|
|
482
|
+
// Refresh target via editCore so summary/element reflect the merged form.
|
|
483
|
+
// The verdict carries no rewritten text → fall back to the target's
|
|
484
|
+
// existing element/summary unmodified; the LLM expressed merge intent
|
|
485
|
+
// alone. editCore requires a change, so when no text drift is supplied
|
|
486
|
+
// we skip the target update and just absorb sources.
|
|
487
|
+
proposed.merged++
|
|
488
|
+
const safeSources = conservative
|
|
489
|
+
? validSources.filter(sid => isStrictDuplicate(target, coreById.get(sid)))
|
|
490
|
+
: validSources
|
|
491
|
+
const mergedDetail = {
|
|
492
|
+
id: a.id, verb: 'merge', targetId: a.targetId, sourceIds: validSources,
|
|
493
|
+
removed: [], applied: false, applyMode,
|
|
494
|
+
}
|
|
495
|
+
if (!mutate || safeSources.length === 0) {
|
|
496
|
+
held.merged++
|
|
497
|
+
mergedDetail.held = true
|
|
498
|
+
mergedDetail.reason = !mutate ? 'proposal mode' : 'no strict duplicate source'
|
|
499
|
+
details.push(mergedDetail)
|
|
500
|
+
touched.add(a.targetId)
|
|
501
|
+
validSources.forEach(sid => touched.add(sid))
|
|
502
|
+
continue
|
|
503
|
+
}
|
|
504
|
+
for (const sid of safeSources) {
|
|
505
|
+
throwIfAborted(signal)
|
|
506
|
+
try {
|
|
507
|
+
await deleteCore(dataDir, sid)
|
|
508
|
+
mergedDetail.removed.push(sid)
|
|
509
|
+
touched.add(sid)
|
|
510
|
+
} catch (err) {
|
|
511
|
+
if (signal?.aborted) throw signal.reason ?? err
|
|
512
|
+
process.stderr.write(`[cycle3] merge delete src=${sid} failed: ${err.message}\n`)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
if (mergedDetail.removed.length > 0) {
|
|
516
|
+
merged++
|
|
517
|
+
mergedDetail.applied = true
|
|
518
|
+
if (safeSources.length < validSources.length) {
|
|
519
|
+
held.merged++
|
|
520
|
+
mergedDetail.heldSources = validSources.filter(sid => !safeSources.includes(sid))
|
|
521
|
+
}
|
|
522
|
+
touched.add(a.targetId)
|
|
523
|
+
}
|
|
524
|
+
details.push(mergedDetail)
|
|
525
|
+
continue
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
throwIfAborted(signal)
|
|
530
|
+
|
|
531
|
+
process.stderr.write(
|
|
532
|
+
`[cycle3] reviewed=${cores.length} kept=${kept}` +
|
|
533
|
+
` proposed_update=${proposed.updated} proposed_merge=${proposed.merged} proposed_delete=${proposed.deleted}` +
|
|
534
|
+
` applied_update=${updated} applied_merge=${merged} applied_delete=${deleted}` +
|
|
535
|
+
` held_update=${held.updated} held_merge=${held.merged} held_delete=${held.deleted}` +
|
|
536
|
+
` mode=${applyMode}\n`,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
return { reviewed: cores.length, kept, updated, merged, deleted, proposed, held, applied: mutate, applyMode, details }
|
|
540
|
+
}
|