@tyroneross/build-loop 0.30.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/plugins/marketplace.json +10 -0
- package/.claude-plugin/marketplace.json +38 -0
- package/.claude-plugin/plugin.json +30 -0
- package/.codex-plugin/plugin.json +41 -0
- package/.cursor/rules/build-loop-surface.mdc +22 -0
- package/AGENTS.md +498 -0
- package/LICENSE +202 -0
- package/README.md +403 -0
- package/agents/alignment-checker.md +124 -0
- package/agents/api-assessor.md +136 -0
- package/agents/architecture-scout.md +185 -0
- package/agents/assessment-orchestrator.md +163 -0
- package/agents/build-orchestrator.md +198 -0
- package/agents/database-assessor.md +133 -0
- package/agents/design-contract-specialist.md +239 -0
- package/agents/fact-checker.md +69 -0
- package/agents/fix-critique.md +161 -0
- package/agents/frontend-assessor.md +136 -0
- package/agents/implementer.md +185 -0
- package/agents/independent-auditor.md +143 -0
- package/agents/mock-scanner.md +78 -0
- package/agents/optimize-runner.md +112 -0
- package/agents/overfitting-reviewer.md +96 -0
- package/agents/performance-assessor.md +159 -0
- package/agents/plan-critic.md +85 -0
- package/agents/promotion-reviewer.md +121 -0
- package/agents/recurring-pattern-detector.md +248 -0
- package/agents/retrospective-synthesizer.md +109 -0
- package/agents/root-cause-investigator.md +285 -0
- package/agents/scope-auditor.md +121 -0
- package/agents/security-reviewer.md +151 -0
- package/agents/self-improvement-architect.md +125 -0
- package/agents/synthesis-critic.md +99 -0
- package/agents/transcript-pattern-miner.md +165 -0
- package/agents/ui-validator.md +193 -0
- package/codex-skills/build-loop/SKILL.md +20 -0
- package/codex-skills/debug-loop/SKILL.md +18 -0
- package/codex-skills/handoff/SKILL.md +22 -0
- package/codex-skills/knowledge/SKILL.md +21 -0
- package/codex-skills/optimize/SKILL.md +18 -0
- package/codex-skills/research/SKILL.md +18 -0
- package/commands/agent-rally-point.md +301 -0
- package/commands/assess.md +41 -0
- package/commands/debug.md +20 -0
- package/commands/debugger-detail.md +34 -0
- package/commands/debugger-scan.md +15 -0
- package/commands/debugger-status.md +20 -0
- package/commands/debugger.md +32 -0
- package/commands/handoff.md +113 -0
- package/commands/knowledge-review.md +16 -0
- package/commands/optimize.md +31 -0
- package/commands/plan-verify.md +39 -0
- package/commands/promote-experiment.md +129 -0
- package/commands/research.md +22 -0
- package/commands/run.md +12 -0
- package/commands/self-review.md +51 -0
- package/commands/start-prd.md +57 -0
- package/commands/test.md +34 -0
- package/dist/src/assessment-orchestrator.d.ts +78 -0
- package/dist/src/assessment-orchestrator.d.ts.map +1 -0
- package/dist/src/assessment-orchestrator.js +304 -0
- package/dist/src/assessment-orchestrator.js.map +1 -0
- package/dist/src/audit-miner.d.ts +21 -0
- package/dist/src/audit-miner.d.ts.map +1 -0
- package/dist/src/audit-miner.js +421 -0
- package/dist/src/audit-miner.js.map +1 -0
- package/dist/src/batch-operations.d.ts +30 -0
- package/dist/src/batch-operations.d.ts.map +1 -0
- package/dist/src/batch-operations.js +339 -0
- package/dist/src/batch-operations.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +176 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/context-engine.d.ts +45 -0
- package/dist/src/context-engine.d.ts.map +1 -0
- package/dist/src/context-engine.js +135 -0
- package/dist/src/context-engine.js.map +1 -0
- package/dist/src/debug-wrapper.d.ts +58 -0
- package/dist/src/debug-wrapper.d.ts.map +1 -0
- package/dist/src/debug-wrapper.js +285 -0
- package/dist/src/debug-wrapper.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +149 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/interactive-verifier.d.ts +26 -0
- package/dist/src/interactive-verifier.d.ts.map +1 -0
- package/dist/src/interactive-verifier.js +435 -0
- package/dist/src/interactive-verifier.js.map +1 -0
- package/dist/src/lessons.d.ts +59 -0
- package/dist/src/lessons.d.ts.map +1 -0
- package/dist/src/lessons.js +172 -0
- package/dist/src/lessons.js.map +1 -0
- package/dist/src/log-reader.d.ts +39 -0
- package/dist/src/log-reader.d.ts.map +1 -0
- package/dist/src/log-reader.js +291 -0
- package/dist/src/log-reader.js.map +1 -0
- package/dist/src/logger.d.ts +59 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +328 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/parallel-retrieval.d.ts +52 -0
- package/dist/src/parallel-retrieval.d.ts.map +1 -0
- package/dist/src/parallel-retrieval.js +309 -0
- package/dist/src/parallel-retrieval.js.map +1 -0
- package/dist/src/pattern-extractor.d.ts +30 -0
- package/dist/src/pattern-extractor.d.ts.map +1 -0
- package/dist/src/pattern-extractor.js +386 -0
- package/dist/src/pattern-extractor.js.map +1 -0
- package/dist/src/result-aggregator.d.ts +44 -0
- package/dist/src/result-aggregator.d.ts.map +1 -0
- package/dist/src/result-aggregator.js +315 -0
- package/dist/src/result-aggregator.js.map +1 -0
- package/dist/src/retrieval.d.ts +139 -0
- package/dist/src/retrieval.d.ts.map +1 -0
- package/dist/src/retrieval.js +724 -0
- package/dist/src/retrieval.js.map +1 -0
- package/dist/src/setup/auto-setup.d.ts +15 -0
- package/dist/src/setup/auto-setup.d.ts.map +1 -0
- package/dist/src/setup/auto-setup.js +157 -0
- package/dist/src/setup/auto-setup.js.map +1 -0
- package/dist/src/setup/configure-hooks.d.ts +8 -0
- package/dist/src/setup/configure-hooks.d.ts.map +1 -0
- package/dist/src/setup/configure-hooks.js +16 -0
- package/dist/src/setup/configure-hooks.js.map +1 -0
- package/dist/src/setup/create-slash-commands.d.ts +8 -0
- package/dist/src/setup/create-slash-commands.d.ts.map +1 -0
- package/dist/src/setup/create-slash-commands.js +101 -0
- package/dist/src/setup/create-slash-commands.js.map +1 -0
- package/dist/src/setup/index.d.ts +8 -0
- package/dist/src/setup/index.d.ts.map +1 -0
- package/dist/src/setup/index.js +17 -0
- package/dist/src/setup/index.js.map +1 -0
- package/dist/src/setup/inject-claude-md.d.ts +10 -0
- package/dist/src/setup/inject-claude-md.d.ts.map +1 -0
- package/dist/src/setup/inject-claude-md.js +150 -0
- package/dist/src/setup/inject-claude-md.js.map +1 -0
- package/dist/src/setup/uninstall.d.ts +22 -0
- package/dist/src/setup/uninstall.d.ts.map +1 -0
- package/dist/src/setup/uninstall.js +235 -0
- package/dist/src/setup/uninstall.js.map +1 -0
- package/dist/src/storage.d.ts +192 -0
- package/dist/src/storage.d.ts.map +1 -0
- package/dist/src/storage.js +1037 -0
- package/dist/src/storage.js.map +1 -0
- package/dist/src/string-similarity.d.ts +8 -0
- package/dist/src/string-similarity.d.ts.map +1 -0
- package/dist/src/string-similarity.js +57 -0
- package/dist/src/string-similarity.js.map +1 -0
- package/dist/src/traces/adapters/browser.d.ts +69 -0
- package/dist/src/traces/adapters/browser.d.ts.map +1 -0
- package/dist/src/traces/adapters/browser.js +280 -0
- package/dist/src/traces/adapters/browser.js.map +1 -0
- package/dist/src/traces/adapters/index.d.ts +23 -0
- package/dist/src/traces/adapters/index.d.ts.map +1 -0
- package/dist/src/traces/adapters/index.js +67 -0
- package/dist/src/traces/adapters/index.js.map +1 -0
- package/dist/src/traces/adapters/langchain.d.ts +42 -0
- package/dist/src/traces/adapters/langchain.d.ts.map +1 -0
- package/dist/src/traces/adapters/langchain.js +151 -0
- package/dist/src/traces/adapters/langchain.js.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts +46 -0
- package/dist/src/traces/adapters/opentelemetry.d.ts.map +1 -0
- package/dist/src/traces/adapters/opentelemetry.js +164 -0
- package/dist/src/traces/adapters/opentelemetry.js.map +1 -0
- package/dist/src/traces/adapters/sentry.d.ts +58 -0
- package/dist/src/traces/adapters/sentry.d.ts.map +1 -0
- package/dist/src/traces/adapters/sentry.js +212 -0
- package/dist/src/traces/adapters/sentry.js.map +1 -0
- package/dist/src/traces/index.d.ts +10 -0
- package/dist/src/traces/index.d.ts.map +1 -0
- package/dist/src/traces/index.js +45 -0
- package/dist/src/traces/index.js.map +1 -0
- package/dist/src/traces/storage.d.ts +78 -0
- package/dist/src/traces/storage.d.ts.map +1 -0
- package/dist/src/traces/storage.js +399 -0
- package/dist/src/traces/storage.js.map +1 -0
- package/dist/src/traces/summarizer.d.ts +83 -0
- package/dist/src/traces/summarizer.d.ts.map +1 -0
- package/dist/src/traces/summarizer.js +247 -0
- package/dist/src/traces/summarizer.js.map +1 -0
- package/dist/src/traces/types.d.ts +365 -0
- package/dist/src/traces/types.d.ts.map +1 -0
- package/dist/src/traces/types.js +14 -0
- package/dist/src/traces/types.js.map +1 -0
- package/dist/src/types.d.ts +495 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +27 -0
- package/dist/src/types.js.map +1 -0
- package/docs/agent-surface-policy.md +45 -0
- package/docs/memory-setup.md +226 -0
- package/hooks/_arch_scan_bg.py +88 -0
- package/hooks/_plugin_drift_check_bg.py +85 -0
- package/hooks/_session_start_lib.sh +42 -0
- package/hooks/git/pre-push +141 -0
- package/hooks/hooks.json +126 -0
- package/hooks/post-commit +30 -0
- package/hooks/pre-commit +29 -0
- package/hooks/pre-edit-apppulse.sh +6 -0
- package/hooks/pre-edit-architecture.sh +106 -0
- package/hooks/pre-edit-rally-point.sh +92 -0
- package/hooks/session-start-apppulse.sh +6 -0
- package/hooks/session-start-architecture.sh +10 -0
- package/hooks/session-start-git-hooks.sh +12 -0
- package/hooks/session-start-plugin-drift.sh +27 -0
- package/hooks/session-start-rally-point.sh +44 -0
- package/hooks/session-start-retrieval.sh +24 -0
- package/hooks/session-start-worktree-gc.sh +105 -0
- package/hooks/test_apppulse_hooks.sh +6 -0
- package/hooks/test_rally_point_hooks.sh +388 -0
- package/hooks/test_sec008_hooks.sh +66 -0
- package/package.json +111 -0
- package/scripts/README.md +301 -0
- package/scripts/_db_url.py +61 -0
- package/scripts/_paths.py +445 -0
- package/scripts/blm.py +162 -0
- package/scripts/blm_api.py +263 -0
- package/scripts/check_cache_sync.py +490 -0
- package/scripts/embed_backend.py +475 -0
- package/scripts/install_memory.py +556 -0
- package/scripts/lessons_index/__init__.py +77 -0
- package/scripts/lessons_index/__main__.py +135 -0
- package/scripts/lessons_index/ingest.py +288 -0
- package/scripts/lessons_index/query.py +285 -0
- package/scripts/lessons_index/schema.py +114 -0
- package/scripts/memory_context/__init__.py +610 -0
- package/scripts/memory_graph/__init__.py +393 -0
- package/scripts/project_resolver.py +172 -0
- package/scripts/prune_codex_plugin_cache.py +23 -0
- package/scripts/prune_plugin_cache.py +359 -0
- package/scripts/semantic_index/__init__.py +482 -0
- package/scripts/semantic_index/_bench_hybrid.py +175 -0
- package/scripts/semantic_index/backfill.py +263 -0
- package/scripts/semantic_index/hybrid.py +174 -0
- package/scripts/semantic_index/test_backfill.py +292 -0
- package/scripts/semantic_index/test_hybrid.py +541 -0
- package/scripts/sync_navgator_lessons.py +666 -0
- package/scripts/sync_plugin_cache.py +388 -0
- package/skills/agent-rally-point/SKILL.md +165 -0
- package/skills/agent-rally-watcher/SKILL.md +83 -0
- package/skills/api-registry-bridge/SKILL.md +59 -0
- package/skills/architecture/dead/SKILL.md +56 -0
- package/skills/architecture/impact/SKILL.md +88 -0
- package/skills/architecture/review/SKILL.md +227 -0
- package/skills/architecture/rules/SKILL.md +72 -0
- package/skills/architecture/scan/SKILL.md +79 -0
- package/skills/architecture/trace/SKILL.md +65 -0
- package/skills/attribution-standard/SKILL.md +122 -0
- package/skills/authentication/SKILL.md +81 -0
- package/skills/authentication/references/better-auth-magic-link.md +135 -0
- package/skills/authentication/references/better-auth-setup.md +224 -0
- package/skills/authentication/references/google-calendar-sync.md +278 -0
- package/skills/authentication/references/google-cloud-console.md +159 -0
- package/skills/authentication/references/google-geocoding-directions.md +126 -0
- package/skills/authentication/references/google-lessons-example-web-app.md +177 -0
- package/skills/authentication/references/google-maps.md +162 -0
- package/skills/authentication/references/google-oauth-setup.md +343 -0
- package/skills/authentication/references/google-places.md +173 -0
- package/skills/authentication/references/lessons-example-web-app-better-auth.md +126 -0
- package/skills/authentication/references/resend-email.md +227 -0
- package/skills/authentication/references/resend-otp-magic-link.md +153 -0
- package/skills/authentication/references/supabase-auth.md +172 -0
- package/skills/auto-decision-capture/SKILL.md +419 -0
- package/skills/build-loop/SKILL.md +516 -0
- package/skills/build-loop/detect-plugins.mjs +82 -0
- package/skills/build-loop/eval-guide.md +65 -0
- package/skills/build-loop/fallbacks.md +549 -0
- package/skills/build-loop/phases/fact-check.md +42 -0
- package/skills/build-loop/phases/ui-validation.md +267 -0
- package/skills/build-loop/references/apple-native-planning.md +439 -0
- package/skills/build-loop/references/capability-routing.md +181 -0
- package/skills/build-loop/references/codex-subagents.md +98 -0
- package/skills/build-loop/references/coordination.md +161 -0
- package/skills/build-loop/references/correction-aware-capture.md +177 -0
- package/skills/build-loop/references/independent-auditor.md +72 -0
- package/skills/build-loop/references/intent-capability-pack.md +170 -0
- package/skills/build-loop/references/intent-exploration-prompts.md +96 -0
- package/skills/build-loop/references/leadership.md +72 -0
- package/skills/build-loop/references/memory.md +284 -0
- package/skills/build-loop/references/modular-systems-pack.md +96 -0
- package/skills/build-loop/references/output-style.md +129 -0
- package/skills/build-loop/references/pay-it-forward-arch.md +98 -0
- package/skills/build-loop/references/phase-1-assess.md +218 -0
- package/skills/build-loop/references/phase-2-plan.md +77 -0
- package/skills/build-loop/references/phase-3-execute.md +47 -0
- package/skills/build-loop/references/phase-4-review.md +310 -0
- package/skills/build-loop/references/phase-5-iterate.md +71 -0
- package/skills/build-loop/references/phase-6-learn.md +56 -0
- package/skills/build-loop/references/recent-design-structures.md +274 -0
- package/skills/build-loop/references/refactor-history/ASSESSMENT.md +85 -0
- package/skills/build-loop/references/refactor-history/STANDALONE_TEST_RUN.md +149 -0
- package/skills/build-loop/references/refactor-history/scenarios/01-simple-bugfix.md +32 -0
- package/skills/build-loop/references/refactor-history/scenarios/02-ui-build-with-iteration.md +48 -0
- package/skills/build-loop/references/refactor-history/scenarios/03-multi-failure-escalation.md +60 -0
- package/skills/build-loop/references/refactor-history/scenarios/04-ui-build-ibr-absent.md +51 -0
- package/skills/build-loop/references/refactor-history/scenarios/05-refactor-navgator-absent.md +71 -0
- package/skills/build-loop/references/refactor-history/scenarios/06-recurring-bug-debugger-absent.md +52 -0
- package/skills/build-loop/references/refactor-history/trace-comparison.md +202 -0
- package/skills/build-loop/references/self-recursive-dev.md +77 -0
- package/skills/build-loop/references/self-review.md +232 -0
- package/skills/build-loop/references/ui-io-contract.md +116 -0
- package/skills/build-loop/scanners/audit-design-rules.mjs +476 -0
- package/skills/build-loop/scanners/require-visual-evidence.mjs +239 -0
- package/skills/build-loop/templates/backlog-item.md +35 -0
- package/skills/build-loop/templates/codex-worker-prompt.md +89 -0
- package/skills/build-loop/templates/ui-subagent-prompt.md +179 -0
- package/skills/build-loop/templates/ux-fix-plan.md +40 -0
- package/skills/building-with-deepagents/SKILL.md +195 -0
- package/skills/building-with-deepagents/references/anti-patterns.md +141 -0
- package/skills/building-with-deepagents/references/api-quick-reference.md +184 -0
- package/skills/capabilities/SKILL.md +91 -0
- package/skills/debug-loop/SKILL.md +278 -0
- package/skills/debug-loop/references/convergence-rules.md +145 -0
- package/skills/debug-loop/references/swiftui-macos-clunky-ui-debugger.html +823 -0
- package/skills/debugging/assess/SKILL.md +111 -0
- package/skills/debugging/debug-loop/SKILL.md +231 -0
- package/skills/debugging/memory/SKILL.md +147 -0
- package/skills/debugging/store/SKILL.md +153 -0
- package/skills/debugging-memory/SKILL.md +440 -0
- package/skills/debugging-memory/examples/incident-example.json +67 -0
- package/skills/debugging-memory/references/incident-documentation.md +280 -0
- package/skills/debugging-memory/references/ios-notification-alarm-playbook.md +300 -0
- package/skills/debugging-memory/references/pattern-extraction.md +163 -0
- package/skills/debugging-memory/references/subagent-integration.md +312 -0
- package/skills/defenseclaw-bridge/SKILL.md +156 -0
- package/skills/defenseclaw-bridge/references/dc-config-mapping.md +162 -0
- package/skills/defenseclaw-bridge/references/output-format.md +108 -0
- package/skills/handoff/SKILL.md +94 -0
- package/skills/ibr-bridge/SKILL.md +68 -0
- package/skills/knowledge/SKILL.md +127 -0
- package/skills/knowledge/references/recall-integration.md +83 -0
- package/skills/knowledge/templates/madr-minimal.md +56 -0
- package/skills/knowledge-review/SKILL.md +112 -0
- package/skills/logging-tracer/SKILL.md +202 -0
- package/skills/logging-tracer/references/log-analysis.md +128 -0
- package/skills/logging-tracer/references/stack-templates.md +274 -0
- package/skills/logging-tracer-bridge/SKILL.md +70 -0
- package/skills/mcp-builder/SKILL.md +278 -0
- package/skills/mcp-builder/references/mcp-security.md +106 -0
- package/skills/model-tiering/SKILL.md +216 -0
- package/skills/native-ax-driver/SKILL.md +220 -0
- package/skills/native-ax-driver/scripts/layout_fill.py +222 -0
- package/skills/native-ax-driver/scripts/native_driver.py +495 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Package.swift +20 -0
- package/skills/native-ax-driver/swift/bl-ax-driver/Sources/main.swift +542 -0
- package/skills/optimize/SKILL.md +202 -0
- package/skills/optimize/profiles.md +61 -0
- package/skills/plan-verify/SKILL.md +115 -0
- package/skills/plan-verify/test-fixtures/example-app-v20-findings.json +27 -0
- package/skills/plan-verify/test-fixtures/example-app-v20.md +42 -0
- package/skills/plan-verify/test-fixtures/example-app-v22-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/example-app-v22.md +1608 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan-findings.json +15 -0
- package/skills/plan-verify/test-fixtures/unrelated-good-plan.md +55 -0
- package/skills/plugin-builder/SKILL.md +329 -0
- package/skills/plugin-builder/references/authoritative-sources.md +104 -0
- package/skills/plugin-builder/references/build-loop-phase-guidance.md +94 -0
- package/skills/plugin-builder/references/components-guide.md +221 -0
- package/skills/plugin-builder/references/distribution.md +317 -0
- package/skills/plugin-builder/references/dual-host-claude-codex.md +220 -0
- package/skills/plugin-builder/references/hooks-reference.md +210 -0
- package/skills/plugin-builder/references/manifest-schema.md +111 -0
- package/skills/plugin-builder/references/plugin-hygiene-lessons.md +307 -0
- package/skills/plugin-tests/SKILL.md +94 -0
- package/skills/prd-bridge/SKILL.md +135 -0
- package/skills/research/SKILL.md +80 -0
- package/skills/security-methodology/SKILL.md +105 -0
- package/skills/security-methodology/references/cross-source-matrix.md +77 -0
- package/skills/security-methodology/references/mitre-atlas-starter.md +83 -0
- package/skills/security-methodology/references/nist-600-1-mapping.md +119 -0
- package/skills/security-methodology/references/owasp-agentic-top-10.md +157 -0
- package/skills/security-methodology/references/owasp-llm-top-10.md +144 -0
- package/skills/security-methodology/references/owasp-web-top-10.md +65 -0
- package/skills/self-improve/SKILL.md +298 -0
- package/skills/spec-writing/SKILL.md +548 -0
- package/skills/spec-writing/scripts/check_checklist.py +478 -0
- package/skills/sync-skills/SKILL.md +132 -0
- package/skills/telemetry/SKILL.md +66 -0
- package/skills/ui-design/SKILL.md +75 -0
- package/skills/ui-design/references/design-patterns-multi.md +165 -0
- package/skills/ui-design/references/design-preferences-from-owned-apps.md +150 -0
- package/skills/ui-design/references/evidence-capture-policy.md +55 -0
- package/skills/ui-design/references/ui-guidance-sources.md +330 -0
- package/skills/ui-design/references/universal-design-principles.alt.md +182 -0
- package/skills/ui-design/references/universal-design-principles.md +419 -0
- package/templates/memory/MEMORY.md.template +55 -0
- package/templates/memory/constitution.md.template +64 -0
- package/templates/memory/manifest.json +47 -0
|
@@ -0,0 +1,1037 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// SPDX-FileCopyrightText: 2025-2026 Tyrone Ross, Jr <46267523+tyroneross@users.noreply.github.com>
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
/**
|
|
5
|
+
* Debugging Memory System - Storage Layer
|
|
6
|
+
*
|
|
7
|
+
* Handles reading/writing incidents and patterns to filesystem.
|
|
8
|
+
* File-based storage for simplicity and portability.
|
|
9
|
+
* Now supports configurable paths (local or shared mode).
|
|
10
|
+
*/
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.storeIncident = storeIncident;
|
|
16
|
+
exports.loadIncident = loadIncident;
|
|
17
|
+
exports.loadAllIncidents = loadAllIncidents;
|
|
18
|
+
exports.storePattern = storePattern;
|
|
19
|
+
exports.loadPattern = loadPattern;
|
|
20
|
+
exports.loadAllPatterns = loadAllPatterns;
|
|
21
|
+
exports.validateIncident = validateIncident;
|
|
22
|
+
exports.generateIncidentId = generateIncidentId;
|
|
23
|
+
exports.generatePatternId = generatePatternId;
|
|
24
|
+
exports.getMemoryStats = getMemoryStats;
|
|
25
|
+
exports.generateIncidentSummary = generateIncidentSummary;
|
|
26
|
+
exports.toCompactIncident = toCompactIncident;
|
|
27
|
+
exports.toCompactPattern = toCompactPattern;
|
|
28
|
+
exports.toCompactIncidents = toCompactIncidents;
|
|
29
|
+
exports.toCompactPatterns = toCompactPatterns;
|
|
30
|
+
exports.estimateCompactIncidentTokens = estimateCompactIncidentTokens;
|
|
31
|
+
exports.estimateCompactPatternTokens = estimateCompactPatternTokens;
|
|
32
|
+
exports.enforceTokenBudget = enforceTokenBudget;
|
|
33
|
+
exports.loadCompactIncidents = loadCompactIncidents;
|
|
34
|
+
exports.loadCompactPatterns = loadCompactPatterns;
|
|
35
|
+
exports.appendToIncidentLog = appendToIncidentLog;
|
|
36
|
+
exports.searchIncidentLog = searchIncidentLog;
|
|
37
|
+
exports.rebuildIndex = rebuildIndex;
|
|
38
|
+
exports.loadIndex = loadIndex;
|
|
39
|
+
exports.buildMemorySummary = buildMemorySummary;
|
|
40
|
+
exports.archiveOldIncidents = archiveOldIncidents;
|
|
41
|
+
exports.compressContext = compressContext;
|
|
42
|
+
exports.updateKeywordIndex = updateKeywordIndex;
|
|
43
|
+
exports.loadKeywordIndex = loadKeywordIndex;
|
|
44
|
+
exports.findCandidatesByKeyword = findCandidatesByKeyword;
|
|
45
|
+
exports.rebuildKeywordIndex = rebuildKeywordIndex;
|
|
46
|
+
exports.updateIndexIncremental = updateIndexIncremental;
|
|
47
|
+
exports.recordOutcome = recordOutcome;
|
|
48
|
+
exports.loadOutcomes = loadOutcomes;
|
|
49
|
+
exports.getOutcomeStats = getOutcomeStats;
|
|
50
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
51
|
+
const path_1 = __importDefault(require("path"));
|
|
52
|
+
const config_1 = require("./config");
|
|
53
|
+
const interactive_verifier_1 = require("./interactive-verifier");
|
|
54
|
+
const logger_1 = require("./logger");
|
|
55
|
+
/**
|
|
56
|
+
* Store an incident in memory
|
|
57
|
+
*/
|
|
58
|
+
async function storeIncident(incident, options = {}) {
|
|
59
|
+
return (0, logger_1.traced)('storage:storeIncident', { id: incident.incident_id }, async () => {
|
|
60
|
+
let finalIncident = incident;
|
|
61
|
+
// Use interactive mode if requested
|
|
62
|
+
if (options.interactive) {
|
|
63
|
+
finalIncident = await (0, interactive_verifier_1.buildIncidentInteractive)(incident);
|
|
64
|
+
}
|
|
65
|
+
// Calculate quality score if not already set
|
|
66
|
+
if (!finalIncident.completeness?.quality_score) {
|
|
67
|
+
const qualityScore = (0, interactive_verifier_1.calculateQualityScore)(finalIncident);
|
|
68
|
+
if (!finalIncident.completeness) {
|
|
69
|
+
finalIncident.completeness = {
|
|
70
|
+
symptom: !!finalIncident.symptom && finalIncident.symptom.length >= 20,
|
|
71
|
+
root_cause: !!finalIncident.root_cause?.description && finalIncident.root_cause.description.length >= 50,
|
|
72
|
+
fix: !!finalIncident.fix?.approach && (finalIncident.fix.changes?.length || 0) > 0,
|
|
73
|
+
verification: finalIncident.verification?.status === 'verified',
|
|
74
|
+
quality_score: qualityScore
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
finalIncident.completeness.quality_score = qualityScore;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Validate if requested
|
|
82
|
+
if (options.validate_schema) {
|
|
83
|
+
const validation = validateIncident(finalIncident);
|
|
84
|
+
if (!validation.valid) {
|
|
85
|
+
throw new Error(`Invalid incident: ${validation.errors.join(', ')}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// Get paths from config
|
|
89
|
+
const paths = (0, config_1.getMemoryPaths)(options.config);
|
|
90
|
+
const INCIDENTS_DIR = paths.incidents;
|
|
91
|
+
// Ensure directory exists
|
|
92
|
+
await promises_1.default.mkdir(INCIDENTS_DIR, { recursive: true });
|
|
93
|
+
// Generate filename
|
|
94
|
+
const filename = `${finalIncident.incident_id}.json`;
|
|
95
|
+
const filepath = path_1.default.join(INCIDENTS_DIR, filename);
|
|
96
|
+
// Write to file
|
|
97
|
+
await promises_1.default.writeFile(filepath, JSON.stringify(finalIncident, null, 2), 'utf-8');
|
|
98
|
+
const qualityEmoji = (finalIncident.completeness.quality_score >= 0.75) ? '🌟' :
|
|
99
|
+
(finalIncident.completeness.quality_score >= 0.5) ? '✅' : '⚠️';
|
|
100
|
+
console.log(`${qualityEmoji} Incident stored: ${finalIncident.incident_id} (quality: ${(finalIncident.completeness.quality_score * 100).toFixed(0)}%)`);
|
|
101
|
+
// Append to JSONL log for fast full-text search
|
|
102
|
+
await appendToIncidentLog(finalIncident, options.config);
|
|
103
|
+
// Incremental index update (avoids loading all incidents)
|
|
104
|
+
await updateIndexIncremental(finalIncident, options.config);
|
|
105
|
+
// Update keyword index
|
|
106
|
+
await updateKeywordIndex(finalIncident, options.config);
|
|
107
|
+
return {
|
|
108
|
+
incident_id: finalIncident.incident_id,
|
|
109
|
+
file_path: filepath
|
|
110
|
+
};
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Validate incident ID format to prevent path traversal attacks
|
|
115
|
+
* Supports both formats: INC_YYYYMMDD_HHMMSS_xxxx and INC_CATEGORY_YYYYMMDD_HHMMSS_xxxx
|
|
116
|
+
*/
|
|
117
|
+
function isValidIncidentId(incident_id) {
|
|
118
|
+
return /^INC_[\w\-]+$/.test(incident_id);
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Load an incident by ID
|
|
122
|
+
*/
|
|
123
|
+
async function loadIncident(incident_id, config) {
|
|
124
|
+
return (0, logger_1.traced)('storage:loadIncident', { id: incident_id }, async () => {
|
|
125
|
+
// Validate incident ID to prevent path traversal
|
|
126
|
+
if (!isValidIncidentId(incident_id)) {
|
|
127
|
+
throw new Error(`Invalid incident ID format: ${incident_id}`);
|
|
128
|
+
}
|
|
129
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
130
|
+
const filename = `${incident_id}.json`;
|
|
131
|
+
const filepath = path_1.default.join(paths.incidents, filename);
|
|
132
|
+
try {
|
|
133
|
+
const content = await promises_1.default.readFile(filepath, 'utf-8');
|
|
134
|
+
return JSON.parse(content);
|
|
135
|
+
}
|
|
136
|
+
catch (error) {
|
|
137
|
+
if (error.code === 'ENOENT') {
|
|
138
|
+
return null; // File not found
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Load all incidents with batched I/O (max 50 concurrent reads)
|
|
146
|
+
*/
|
|
147
|
+
async function loadAllIncidents(config) {
|
|
148
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
149
|
+
const INCIDENTS_DIR = paths.incidents;
|
|
150
|
+
try {
|
|
151
|
+
const files = await promises_1.default.readdir(INCIDENTS_DIR);
|
|
152
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
153
|
+
const incidents = [];
|
|
154
|
+
const BATCH_SIZE = 50;
|
|
155
|
+
for (let i = 0; i < jsonFiles.length; i += BATCH_SIZE) {
|
|
156
|
+
const batch = jsonFiles.slice(i, i + BATCH_SIZE);
|
|
157
|
+
const results = await Promise.all(batch.map(async (file) => {
|
|
158
|
+
try {
|
|
159
|
+
const filepath = path_1.default.join(INCIDENTS_DIR, file);
|
|
160
|
+
const content = await promises_1.default.readFile(filepath, 'utf-8');
|
|
161
|
+
return JSON.parse(content);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}));
|
|
167
|
+
incidents.push(...results.filter((r) => r !== null));
|
|
168
|
+
}
|
|
169
|
+
return incidents;
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
if (error.code === 'ENOENT') {
|
|
173
|
+
return []; // Directory doesn't exist yet
|
|
174
|
+
}
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Store a pattern
|
|
180
|
+
*/
|
|
181
|
+
async function storePattern(pattern, config) {
|
|
182
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
183
|
+
await promises_1.default.mkdir(paths.patterns, { recursive: true });
|
|
184
|
+
const filename = `${pattern.pattern_id}.json`;
|
|
185
|
+
const filepath = path_1.default.join(paths.patterns, filename);
|
|
186
|
+
await promises_1.default.writeFile(filepath, JSON.stringify(pattern, null, 2), 'utf-8');
|
|
187
|
+
console.log(`✅ Pattern stored: ${pattern.pattern_id}`);
|
|
188
|
+
return filepath;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Validate pattern ID format to prevent path traversal attacks
|
|
192
|
+
*/
|
|
193
|
+
function isValidPatternId(pattern_id) {
|
|
194
|
+
// Only allow alphanumeric and underscores (PTN_CATEGORY_NAME format)
|
|
195
|
+
return /^PTN_[\w]+$/.test(pattern_id);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Load a pattern by ID
|
|
199
|
+
*/
|
|
200
|
+
async function loadPattern(pattern_id, config) {
|
|
201
|
+
// Validate pattern ID to prevent path traversal
|
|
202
|
+
if (!isValidPatternId(pattern_id)) {
|
|
203
|
+
throw new Error(`Invalid pattern ID format: ${pattern_id}`);
|
|
204
|
+
}
|
|
205
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
206
|
+
const filename = `${pattern_id}.json`;
|
|
207
|
+
const filepath = path_1.default.join(paths.patterns, filename);
|
|
208
|
+
try {
|
|
209
|
+
const content = await promises_1.default.readFile(filepath, 'utf-8');
|
|
210
|
+
return JSON.parse(content);
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error.code === 'ENOENT') {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
throw error;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Load all patterns with batched I/O (max 50 concurrent reads)
|
|
221
|
+
*/
|
|
222
|
+
async function loadAllPatterns(config) {
|
|
223
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
224
|
+
const PATTERNS_DIR = paths.patterns;
|
|
225
|
+
try {
|
|
226
|
+
const files = await promises_1.default.readdir(PATTERNS_DIR);
|
|
227
|
+
const jsonFiles = files.filter(f => f.endsWith('.json'));
|
|
228
|
+
const patterns = [];
|
|
229
|
+
const BATCH_SIZE = 50;
|
|
230
|
+
for (let i = 0; i < jsonFiles.length; i += BATCH_SIZE) {
|
|
231
|
+
const batch = jsonFiles.slice(i, i + BATCH_SIZE);
|
|
232
|
+
const results = await Promise.all(batch.map(async (file) => {
|
|
233
|
+
try {
|
|
234
|
+
const filepath = path_1.default.join(PATTERNS_DIR, file);
|
|
235
|
+
const content = await promises_1.default.readFile(filepath, 'utf-8');
|
|
236
|
+
return JSON.parse(content);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}));
|
|
242
|
+
patterns.push(...results.filter((r) => r !== null));
|
|
243
|
+
}
|
|
244
|
+
return patterns;
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
if (error.code === 'ENOENT') {
|
|
248
|
+
return [];
|
|
249
|
+
}
|
|
250
|
+
throw error;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Validate incident structure
|
|
255
|
+
*/
|
|
256
|
+
function validateIncident(incident) {
|
|
257
|
+
const errors = [];
|
|
258
|
+
const warnings = [];
|
|
259
|
+
// Required fields
|
|
260
|
+
if (!incident.incident_id)
|
|
261
|
+
errors.push('Missing incident_id');
|
|
262
|
+
if (!incident.timestamp)
|
|
263
|
+
errors.push('Missing timestamp');
|
|
264
|
+
if (!incident.symptom)
|
|
265
|
+
errors.push('Missing symptom');
|
|
266
|
+
if (!incident.root_cause)
|
|
267
|
+
errors.push('Missing root_cause');
|
|
268
|
+
if (!incident.fix)
|
|
269
|
+
errors.push('Missing fix');
|
|
270
|
+
// Check completeness
|
|
271
|
+
if (incident.root_cause && !incident.root_cause.confidence) {
|
|
272
|
+
warnings.push('Root cause missing confidence score');
|
|
273
|
+
}
|
|
274
|
+
if (incident.verification && incident.verification.status === 'unverified') {
|
|
275
|
+
warnings.push('Incident not verified');
|
|
276
|
+
}
|
|
277
|
+
if (!incident.quality_gates || !incident.quality_gates.guardian_validated) {
|
|
278
|
+
warnings.push('Not validated by guardian (expected in current system)');
|
|
279
|
+
}
|
|
280
|
+
return {
|
|
281
|
+
valid: errors.length === 0,
|
|
282
|
+
errors,
|
|
283
|
+
warnings
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Generate incident ID with optional category prefix for self-documenting filenames
|
|
288
|
+
*
|
|
289
|
+
* Without category: INC_20250215_143022_a1b2
|
|
290
|
+
* With category: INC_API_20250215_143022_a1b2
|
|
291
|
+
*/
|
|
292
|
+
function generateIncidentId(category) {
|
|
293
|
+
const now = new Date();
|
|
294
|
+
const dateStr = now.toISOString().slice(0, 10).replace(/-/g, '');
|
|
295
|
+
const timeStr = now.toISOString().slice(11, 19).replace(/:/g, '');
|
|
296
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
297
|
+
if (category) {
|
|
298
|
+
const cleanCat = category.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 12);
|
|
299
|
+
return `INC_${cleanCat}_${dateStr}_${timeStr}_${random}`;
|
|
300
|
+
}
|
|
301
|
+
return `INC_${dateStr}_${timeStr}_${random}`;
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Generate pattern ID
|
|
305
|
+
*/
|
|
306
|
+
function generatePatternId(category, name) {
|
|
307
|
+
const cleanCategory = category.toUpperCase().replace(/[^A-Z]/g, '_');
|
|
308
|
+
const cleanName = name.toUpperCase().replace(/[^A-Z]/g, '_');
|
|
309
|
+
return `PTN_${cleanCategory}_${cleanName}`;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Get memory statistics
|
|
313
|
+
*/
|
|
314
|
+
async function getMemoryStats(config) {
|
|
315
|
+
const incidents = await loadAllIncidents(config);
|
|
316
|
+
const patterns = await loadAllPatterns(config);
|
|
317
|
+
const timestamps = incidents.map(i => i.timestamp).filter(Boolean);
|
|
318
|
+
const oldest = timestamps.length > 0 ? Math.min(...timestamps) : 0;
|
|
319
|
+
const newest = timestamps.length > 0 ? Math.max(...timestamps) : 0;
|
|
320
|
+
// Estimate disk usage (rough)
|
|
321
|
+
const diskUsage = incidents.length * 1 + // ~1KB per incident
|
|
322
|
+
patterns.length * 2; // ~2KB per pattern
|
|
323
|
+
return {
|
|
324
|
+
total_incidents: incidents.length,
|
|
325
|
+
total_patterns: patterns.length,
|
|
326
|
+
oldest_incident: oldest,
|
|
327
|
+
newest_incident: newest,
|
|
328
|
+
disk_usage_kb: diskUsage
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Truncate text to max length with ellipsis
|
|
333
|
+
*/
|
|
334
|
+
function truncate(text, maxLen) {
|
|
335
|
+
if (!text)
|
|
336
|
+
return '';
|
|
337
|
+
if (text.length <= maxLen)
|
|
338
|
+
return text;
|
|
339
|
+
return text.substring(0, maxLen - 3) + '...';
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Generate minimal incident summary (~100 tokens)
|
|
343
|
+
*/
|
|
344
|
+
function generateIncidentSummary(incident) {
|
|
345
|
+
return {
|
|
346
|
+
incident_id: incident.incident_id,
|
|
347
|
+
symptom_preview: truncate(incident.symptom, 80),
|
|
348
|
+
root_cause_preview: truncate(incident.root_cause?.description, 100),
|
|
349
|
+
fix_preview: truncate(incident.fix?.approach, 80),
|
|
350
|
+
category: incident.root_cause?.category || 'unknown',
|
|
351
|
+
confidence: incident.root_cause?.confidence || 0,
|
|
352
|
+
quality: incident.completeness?.quality_score || 0,
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/**
|
|
356
|
+
* Convert full incident to compact format (~200 tokens)
|
|
357
|
+
*
|
|
358
|
+
* Uses short keys to minimize token usage in LLM context.
|
|
359
|
+
* Preserves essential information for debugging assistance.
|
|
360
|
+
*/
|
|
361
|
+
function toCompactIncident(incident) {
|
|
362
|
+
return {
|
|
363
|
+
id: incident.incident_id,
|
|
364
|
+
ts: incident.timestamp,
|
|
365
|
+
sym: truncate(incident.symptom, 80),
|
|
366
|
+
rc: {
|
|
367
|
+
d: truncate(incident.root_cause?.description, 100),
|
|
368
|
+
cat: incident.root_cause?.category || 'unknown',
|
|
369
|
+
conf: incident.root_cause?.confidence || 0,
|
|
370
|
+
},
|
|
371
|
+
fix: {
|
|
372
|
+
a: truncate(incident.fix?.approach, 80),
|
|
373
|
+
n: incident.fix?.changes?.length || 0,
|
|
374
|
+
},
|
|
375
|
+
v: incident.verification?.status === 'verified'
|
|
376
|
+
? 'V'
|
|
377
|
+
: incident.verification?.status === 'partial'
|
|
378
|
+
? 'P'
|
|
379
|
+
: 'U',
|
|
380
|
+
t: (incident.tags || []).slice(0, 5),
|
|
381
|
+
q: incident.completeness?.quality_score || 0,
|
|
382
|
+
sim: incident.similarity_score,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Convert full pattern to compact format (~120 tokens)
|
|
387
|
+
*/
|
|
388
|
+
function toCompactPattern(pattern) {
|
|
389
|
+
return {
|
|
390
|
+
id: pattern.pattern_id,
|
|
391
|
+
n: pattern.usage_history?.total_uses || 1,
|
|
392
|
+
desc: truncate(pattern.description, 100),
|
|
393
|
+
sig: pattern.detection_signature.slice(0, 5),
|
|
394
|
+
fix: truncate(pattern.solution_template, 150),
|
|
395
|
+
sr: pattern.success_rate,
|
|
396
|
+
cat: pattern.tags[0] || 'general',
|
|
397
|
+
t: pattern.tags.slice(0, 5),
|
|
398
|
+
last: pattern.last_used || Date.now(),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Batch convert incidents to compact format
|
|
403
|
+
*/
|
|
404
|
+
function toCompactIncidents(incidents) {
|
|
405
|
+
return incidents.map(toCompactIncident);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Batch convert patterns to compact format
|
|
409
|
+
*/
|
|
410
|
+
function toCompactPatterns(patterns) {
|
|
411
|
+
return patterns.map(toCompactPattern);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Estimate tokens for a compact incident
|
|
415
|
+
*/
|
|
416
|
+
function estimateCompactIncidentTokens(incident) {
|
|
417
|
+
// Rough estimate: JSON structure + field names + values
|
|
418
|
+
const json = JSON.stringify(incident);
|
|
419
|
+
return Math.ceil(json.length / 4);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Estimate tokens for a compact pattern
|
|
423
|
+
*/
|
|
424
|
+
function estimateCompactPatternTokens(pattern) {
|
|
425
|
+
const json = JSON.stringify(pattern);
|
|
426
|
+
return Math.ceil(json.length / 4);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Enforce token budget on results
|
|
430
|
+
*
|
|
431
|
+
* Returns limited incidents and patterns that fit within budget.
|
|
432
|
+
* Patterns get 30% budget, incidents get 60%, metadata gets 10%.
|
|
433
|
+
*/
|
|
434
|
+
function enforceTokenBudget(incidents, patterns, budget = 2500) {
|
|
435
|
+
const patternBudget = Math.floor(budget * 0.3);
|
|
436
|
+
const incidentBudget = Math.floor(budget * 0.6);
|
|
437
|
+
// Estimate tokens per item (using averages from DEFAULT_TOKEN_BUDGET)
|
|
438
|
+
const tokensPerPattern = 120;
|
|
439
|
+
const tokensPerIncident = 200;
|
|
440
|
+
const maxPatterns = Math.floor(patternBudget / tokensPerPattern);
|
|
441
|
+
const maxIncidents = Math.floor(incidentBudget / tokensPerIncident);
|
|
442
|
+
const limitedPatterns = patterns.slice(0, maxPatterns);
|
|
443
|
+
const limitedIncidents = incidents.slice(0, maxIncidents);
|
|
444
|
+
const tokensUsed = limitedPatterns.length * tokensPerPattern +
|
|
445
|
+
limitedIncidents.length * tokensPerIncident +
|
|
446
|
+
Math.floor(budget * 0.1); // metadata overhead
|
|
447
|
+
return {
|
|
448
|
+
limitedPatterns,
|
|
449
|
+
limitedIncidents,
|
|
450
|
+
tokensUsed,
|
|
451
|
+
truncated: {
|
|
452
|
+
incidents: incidents.length - limitedIncidents.length,
|
|
453
|
+
patterns: patterns.length - limitedPatterns.length,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Load incidents and return compact versions
|
|
459
|
+
*/
|
|
460
|
+
async function loadCompactIncidents(config) {
|
|
461
|
+
const incidents = await loadAllIncidents(config);
|
|
462
|
+
return toCompactIncidents(incidents);
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Load patterns and return compact versions
|
|
466
|
+
*/
|
|
467
|
+
async function loadCompactPatterns(config) {
|
|
468
|
+
const patterns = await loadAllPatterns(config);
|
|
469
|
+
return toCompactPatterns(patterns);
|
|
470
|
+
}
|
|
471
|
+
// ============================================================================
|
|
472
|
+
// JSONL APPEND LOG - Fast full-text search without loading individual files
|
|
473
|
+
// ============================================================================
|
|
474
|
+
/**
|
|
475
|
+
* Append incident entry to JSONL log for fast search
|
|
476
|
+
*/
|
|
477
|
+
async function appendToIncidentLog(incident, config) {
|
|
478
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
479
|
+
const logPath = path_1.default.join(paths.root, 'incidents.jsonl');
|
|
480
|
+
const entry = {
|
|
481
|
+
incident_id: incident.incident_id,
|
|
482
|
+
timestamp: incident.timestamp,
|
|
483
|
+
symptom: incident.symptom,
|
|
484
|
+
category: incident.root_cause?.category || 'unknown',
|
|
485
|
+
tags: incident.tags || [],
|
|
486
|
+
quality_score: incident.completeness?.quality_score || 0,
|
|
487
|
+
verification_status: incident.verification?.status || 'unverified',
|
|
488
|
+
files_changed: incident.files_changed || [],
|
|
489
|
+
};
|
|
490
|
+
try {
|
|
491
|
+
await promises_1.default.appendFile(logPath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
// Log file doesn't exist yet, create it
|
|
495
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
496
|
+
await promises_1.default.writeFile(logPath, JSON.stringify(entry) + '\n', 'utf-8');
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* Search JSONL log for fast text matching (avoids loading all incident files)
|
|
501
|
+
*/
|
|
502
|
+
async function searchIncidentLog(query, config) {
|
|
503
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
504
|
+
const logPath = path_1.default.join(paths.root, 'incidents.jsonl');
|
|
505
|
+
try {
|
|
506
|
+
const content = await promises_1.default.readFile(logPath, 'utf-8');
|
|
507
|
+
const queryLower = query.toLowerCase();
|
|
508
|
+
return content
|
|
509
|
+
.split('\n')
|
|
510
|
+
.filter(line => line.trim())
|
|
511
|
+
.map(line => {
|
|
512
|
+
try {
|
|
513
|
+
return JSON.parse(line);
|
|
514
|
+
}
|
|
515
|
+
catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
})
|
|
519
|
+
.filter((entry) => {
|
|
520
|
+
if (!entry)
|
|
521
|
+
return false;
|
|
522
|
+
return (entry.incident_id.toLowerCase().includes(queryLower) ||
|
|
523
|
+
entry.symptom.toLowerCase().includes(queryLower) ||
|
|
524
|
+
entry.category.toLowerCase().includes(queryLower) ||
|
|
525
|
+
entry.tags.some(t => t.toLowerCase().includes(queryLower)));
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
return [];
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
// ============================================================================
|
|
533
|
+
// MEMORY INDEX - O(1) lookups by category, tag, file
|
|
534
|
+
// ============================================================================
|
|
535
|
+
/**
|
|
536
|
+
* Build or rebuild the memory index from all incidents
|
|
537
|
+
*/
|
|
538
|
+
async function rebuildIndex(config) {
|
|
539
|
+
return (0, logger_1.traced)('storage:rebuildIndex', undefined, async () => {
|
|
540
|
+
const incidents = await loadAllIncidents(config);
|
|
541
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
542
|
+
const index = {
|
|
543
|
+
version: 1,
|
|
544
|
+
last_updated: Date.now(),
|
|
545
|
+
stats: {
|
|
546
|
+
total_incidents: incidents.length,
|
|
547
|
+
total_patterns: 0,
|
|
548
|
+
categories: {},
|
|
549
|
+
tags: {},
|
|
550
|
+
quality_distribution: { excellent: 0, good: 0, fair: 0 },
|
|
551
|
+
oldest_timestamp: Infinity,
|
|
552
|
+
newest_timestamp: 0,
|
|
553
|
+
},
|
|
554
|
+
by_category: {},
|
|
555
|
+
by_tag: {},
|
|
556
|
+
by_file: {},
|
|
557
|
+
by_quality: { excellent: [], good: [], fair: [] },
|
|
558
|
+
recent: [],
|
|
559
|
+
};
|
|
560
|
+
// Count patterns
|
|
561
|
+
try {
|
|
562
|
+
const patternFiles = await promises_1.default.readdir(paths.patterns);
|
|
563
|
+
index.stats.total_patterns = patternFiles.filter(f => f.endsWith('.json')).length;
|
|
564
|
+
}
|
|
565
|
+
catch { /* patterns dir may not exist */ }
|
|
566
|
+
// Build indexes
|
|
567
|
+
for (const incident of incidents) {
|
|
568
|
+
const id = incident.incident_id;
|
|
569
|
+
const cat = incident.root_cause?.category || 'unknown';
|
|
570
|
+
const qs = incident.completeness?.quality_score || 0;
|
|
571
|
+
// Category index
|
|
572
|
+
if (!index.by_category[cat])
|
|
573
|
+
index.by_category[cat] = [];
|
|
574
|
+
index.by_category[cat].push(id);
|
|
575
|
+
index.stats.categories[cat] = (index.stats.categories[cat] || 0) + 1;
|
|
576
|
+
// Tag index
|
|
577
|
+
for (const tag of (incident.tags || [])) {
|
|
578
|
+
if (!index.by_tag[tag])
|
|
579
|
+
index.by_tag[tag] = [];
|
|
580
|
+
index.by_tag[tag].push(id);
|
|
581
|
+
index.stats.tags[tag] = (index.stats.tags[tag] || 0) + 1;
|
|
582
|
+
}
|
|
583
|
+
// File index
|
|
584
|
+
for (const file of (incident.files_changed || [])) {
|
|
585
|
+
if (!index.by_file[file])
|
|
586
|
+
index.by_file[file] = [];
|
|
587
|
+
index.by_file[file].push(id);
|
|
588
|
+
}
|
|
589
|
+
// Quality distribution
|
|
590
|
+
if (qs >= 0.75) {
|
|
591
|
+
index.by_quality.excellent.push(id);
|
|
592
|
+
index.stats.quality_distribution.excellent++;
|
|
593
|
+
}
|
|
594
|
+
else if (qs >= 0.5) {
|
|
595
|
+
index.by_quality.good.push(id);
|
|
596
|
+
index.stats.quality_distribution.good++;
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
index.by_quality.fair.push(id);
|
|
600
|
+
index.stats.quality_distribution.fair++;
|
|
601
|
+
}
|
|
602
|
+
// Timestamps
|
|
603
|
+
if (incident.timestamp < index.stats.oldest_timestamp) {
|
|
604
|
+
index.stats.oldest_timestamp = incident.timestamp;
|
|
605
|
+
}
|
|
606
|
+
if (incident.timestamp > index.stats.newest_timestamp) {
|
|
607
|
+
index.stats.newest_timestamp = incident.timestamp;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// Fix infinity if no incidents
|
|
611
|
+
if (index.stats.oldest_timestamp === Infinity)
|
|
612
|
+
index.stats.oldest_timestamp = 0;
|
|
613
|
+
// Recent (newest first, max 20)
|
|
614
|
+
index.recent = incidents
|
|
615
|
+
.sort((a, b) => b.timestamp - a.timestamp)
|
|
616
|
+
.slice(0, 20)
|
|
617
|
+
.map(i => i.incident_id);
|
|
618
|
+
// Write index
|
|
619
|
+
const indexPath = path_1.default.join(paths.root, 'index.json');
|
|
620
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
621
|
+
await promises_1.default.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
622
|
+
return index;
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Load memory index (fast, no incident file reads needed)
|
|
627
|
+
*/
|
|
628
|
+
async function loadIndex(config) {
|
|
629
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
630
|
+
const indexPath = path_1.default.join(paths.root, 'index.json');
|
|
631
|
+
try {
|
|
632
|
+
const content = await promises_1.default.readFile(indexPath, 'utf-8');
|
|
633
|
+
return JSON.parse(content);
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
return null;
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
// ============================================================================
|
|
640
|
+
// MEMORY SUMMARY - Compressed context for LLM cold starts
|
|
641
|
+
// ============================================================================
|
|
642
|
+
/**
|
|
643
|
+
* Build a compact MEMORY_SUMMARY.md for LLM context injection
|
|
644
|
+
* Stays under 150 lines to fit in context windows
|
|
645
|
+
*/
|
|
646
|
+
async function buildMemorySummary(config) {
|
|
647
|
+
const index = await loadIndex(config) || await rebuildIndex(config);
|
|
648
|
+
const patterns = await loadAllPatterns(config);
|
|
649
|
+
const lines = [];
|
|
650
|
+
lines.push('# Debugging Memory Summary');
|
|
651
|
+
lines.push(`> ${index.stats.total_incidents} incidents | ${index.stats.total_patterns} patterns`);
|
|
652
|
+
lines.push(`> Quality: ${index.stats.quality_distribution.excellent} excellent, ${index.stats.quality_distribution.good} good, ${index.stats.quality_distribution.fair} fair`);
|
|
653
|
+
lines.push('');
|
|
654
|
+
// Top categories
|
|
655
|
+
const sortedCats = Object.entries(index.stats.categories)
|
|
656
|
+
.sort(([, a], [, b]) => b - a)
|
|
657
|
+
.slice(0, 10);
|
|
658
|
+
if (sortedCats.length > 0) {
|
|
659
|
+
lines.push('## Categories');
|
|
660
|
+
for (const [cat, count] of sortedCats) {
|
|
661
|
+
lines.push(`- **${cat}**: ${count} incidents`);
|
|
662
|
+
}
|
|
663
|
+
lines.push('');
|
|
664
|
+
}
|
|
665
|
+
// Top tags
|
|
666
|
+
const sortedTags = Object.entries(index.stats.tags)
|
|
667
|
+
.sort(([, a], [, b]) => b - a)
|
|
668
|
+
.slice(0, 15);
|
|
669
|
+
if (sortedTags.length > 0) {
|
|
670
|
+
lines.push('## Common Tags');
|
|
671
|
+
lines.push(sortedTags.map(([tag, count]) => `\`${tag}\`(${count})`).join(', '));
|
|
672
|
+
lines.push('');
|
|
673
|
+
}
|
|
674
|
+
// Patterns
|
|
675
|
+
if (patterns.length > 0) {
|
|
676
|
+
lines.push('## Known Patterns');
|
|
677
|
+
for (const p of patterns.slice(0, 10)) {
|
|
678
|
+
lines.push(`- **${p.name}** (${(p.success_rate * 100).toFixed(0)}% success) - ${p.description.slice(0, 80)}`);
|
|
679
|
+
}
|
|
680
|
+
lines.push('');
|
|
681
|
+
}
|
|
682
|
+
// Recent incidents (from index, no file reads)
|
|
683
|
+
if (index.recent.length > 0) {
|
|
684
|
+
lines.push('## Recent Incident IDs');
|
|
685
|
+
lines.push(index.recent.slice(0, 10).map(id => `\`${id}\``).join(', '));
|
|
686
|
+
lines.push('');
|
|
687
|
+
}
|
|
688
|
+
// Hot files
|
|
689
|
+
const sortedFiles = Object.entries(index.by_file)
|
|
690
|
+
.sort(([, a], [, b]) => b.length - a.length)
|
|
691
|
+
.slice(0, 10);
|
|
692
|
+
if (sortedFiles.length > 0) {
|
|
693
|
+
lines.push('## Frequently Affected Files');
|
|
694
|
+
for (const [file, ids] of sortedFiles) {
|
|
695
|
+
lines.push(`- \`${file}\` (${ids.length} incidents)`);
|
|
696
|
+
}
|
|
697
|
+
lines.push('');
|
|
698
|
+
}
|
|
699
|
+
lines.push(`*Updated: ${new Date().toISOString().slice(0, 19)}*`);
|
|
700
|
+
const content = lines.join('\n');
|
|
701
|
+
// Write to disk
|
|
702
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
703
|
+
const summaryPath = path_1.default.join(paths.root, 'MEMORY_SUMMARY.md');
|
|
704
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
705
|
+
await promises_1.default.writeFile(summaryPath, content, 'utf-8');
|
|
706
|
+
return content;
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Archive incidents older than maxAge days, keeping at most maxActive incidents
|
|
710
|
+
*/
|
|
711
|
+
async function archiveOldIncidents(options = {}, config) {
|
|
712
|
+
const { maxActive = 200, maxAgeDays = 180, dryRun = false } = options;
|
|
713
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
714
|
+
const archivePath = path_1.default.join(paths.root, 'archive');
|
|
715
|
+
const incidents = await loadAllIncidents(config);
|
|
716
|
+
const sorted = incidents.sort((a, b) => b.timestamp - a.timestamp);
|
|
717
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
718
|
+
const toArchive = [];
|
|
719
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
720
|
+
if (i >= maxActive || sorted[i].timestamp < cutoff) {
|
|
721
|
+
toArchive.push(sorted[i]);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (toArchive.length === 0) {
|
|
725
|
+
return { archived: [], kept: incidents.length };
|
|
726
|
+
}
|
|
727
|
+
console.log(`📦 ${dryRun ? '[DRY RUN] Would archive' : 'Archiving'} ${toArchive.length} incidents`);
|
|
728
|
+
if (!dryRun) {
|
|
729
|
+
// Create archive directory with timestamp
|
|
730
|
+
const archiveDir = path_1.default.join(archivePath, `archive_${Date.now()}`);
|
|
731
|
+
await promises_1.default.mkdir(archiveDir, { recursive: true });
|
|
732
|
+
// Move incidents to archive
|
|
733
|
+
for (const incident of toArchive) {
|
|
734
|
+
const srcPath = path_1.default.join(paths.incidents, `${incident.incident_id}.json`);
|
|
735
|
+
const destPath = path_1.default.join(archiveDir, `${incident.incident_id}.json`);
|
|
736
|
+
try {
|
|
737
|
+
await promises_1.default.rename(srcPath, destPath);
|
|
738
|
+
}
|
|
739
|
+
catch {
|
|
740
|
+
// File may already be archived or missing
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// Write manifest
|
|
744
|
+
const manifest = {
|
|
745
|
+
archived_at: Date.now(),
|
|
746
|
+
incident_count: toArchive.length,
|
|
747
|
+
oldest_timestamp: Math.min(...toArchive.map(i => i.timestamp)),
|
|
748
|
+
newest_timestamp: Math.max(...toArchive.map(i => i.timestamp)),
|
|
749
|
+
reason: `Exceeded ${maxActive} active or older than ${maxAgeDays} days`,
|
|
750
|
+
};
|
|
751
|
+
await promises_1.default.writeFile(path_1.default.join(archiveDir, 'manifest.json'), JSON.stringify(manifest, null, 2), 'utf-8');
|
|
752
|
+
// Rebuild index and summary
|
|
753
|
+
await rebuildIndex(config);
|
|
754
|
+
await buildMemorySummary(config);
|
|
755
|
+
}
|
|
756
|
+
return {
|
|
757
|
+
archived: toArchive.map(i => i.incident_id),
|
|
758
|
+
kept: incidents.length - toArchive.length,
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
// ============================================================================
|
|
762
|
+
// CONTEXT COMPRESSION - Compress memory for LLM context injection
|
|
763
|
+
// ============================================================================
|
|
764
|
+
/**
|
|
765
|
+
* Generate compressed context string optimized for LLM consumption
|
|
766
|
+
* Fits within token budget while maximizing information density
|
|
767
|
+
*/
|
|
768
|
+
function compressContext(incidents, patterns, budget = 2500) {
|
|
769
|
+
const { limitedIncidents, limitedPatterns, tokensUsed, truncated } = enforceTokenBudget(incidents, patterns, budget);
|
|
770
|
+
const sections = [];
|
|
771
|
+
// Header
|
|
772
|
+
sections.push(`[Memory: ${limitedIncidents.length}i/${limitedPatterns.length}p, ~${tokensUsed}tok]`);
|
|
773
|
+
// Patterns first (highest value)
|
|
774
|
+
if (limitedPatterns.length > 0) {
|
|
775
|
+
sections.push('PATTERNS:');
|
|
776
|
+
for (const p of limitedPatterns) {
|
|
777
|
+
sections.push(` ${p.id}|${p.cat}|${(p.sr * 100).toFixed(0)}%|${p.desc}`);
|
|
778
|
+
sections.push(` fix: ${p.fix}`);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// Incidents
|
|
782
|
+
if (limitedIncidents.length > 0) {
|
|
783
|
+
sections.push('INCIDENTS:');
|
|
784
|
+
for (const inc of limitedIncidents) {
|
|
785
|
+
const vLabel = inc.v === 'V' ? 'verified' : inc.v === 'P' ? 'partial' : 'unverified';
|
|
786
|
+
sections.push(` ${inc.id}|${inc.rc.cat}|${(inc.rc.conf * 100).toFixed(0)}%|${vLabel}`);
|
|
787
|
+
sections.push(` sym: ${inc.sym}`);
|
|
788
|
+
sections.push(` fix: ${inc.fix.a}`);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
// Truncation notice
|
|
792
|
+
if (truncated.incidents > 0 || truncated.patterns > 0) {
|
|
793
|
+
sections.push(`[+${truncated.incidents}i/${truncated.patterns}p omitted]`);
|
|
794
|
+
}
|
|
795
|
+
return sections.join('\n');
|
|
796
|
+
}
|
|
797
|
+
// ============================================================================
|
|
798
|
+
// KEYWORD INDEX - Inverted index for scalable retrieval
|
|
799
|
+
// ============================================================================
|
|
800
|
+
/**
|
|
801
|
+
* Extract keywords from an incident for indexing
|
|
802
|
+
*/
|
|
803
|
+
function extractIndexKeywords(incident) {
|
|
804
|
+
const stopWords = new Set([
|
|
805
|
+
'the', 'a', 'an', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'for',
|
|
806
|
+
'of', 'with', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
807
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'should',
|
|
808
|
+
'can', 'could', 'may', 'might', 'must', 'this', 'that', 'these', 'those',
|
|
809
|
+
'not', 'from', 'it', 'its', 'when', 'where', 'what', 'which', 'how',
|
|
810
|
+
]);
|
|
811
|
+
const text = [
|
|
812
|
+
incident.symptom || '',
|
|
813
|
+
incident.root_cause?.description || '',
|
|
814
|
+
incident.root_cause?.category || '',
|
|
815
|
+
incident.fix?.approach || '',
|
|
816
|
+
...(incident.tags || []),
|
|
817
|
+
].join(' ');
|
|
818
|
+
return [...new Set(text
|
|
819
|
+
.toLowerCase()
|
|
820
|
+
.replace(/[^\w\s]/g, ' ')
|
|
821
|
+
.split(/\s+/)
|
|
822
|
+
.filter(w => w.length > 2 && !stopWords.has(w)))];
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* Update keyword index with a new incident (incremental, no full rebuild)
|
|
826
|
+
*/
|
|
827
|
+
async function updateKeywordIndex(incident, config) {
|
|
828
|
+
const index = await loadKeywordIndex(config) || {
|
|
829
|
+
version: 1,
|
|
830
|
+
last_updated: Date.now(),
|
|
831
|
+
keywords: {},
|
|
832
|
+
total_incidents: 0,
|
|
833
|
+
total_keywords: 0,
|
|
834
|
+
};
|
|
835
|
+
const keywords = extractIndexKeywords(incident);
|
|
836
|
+
const id = incident.incident_id;
|
|
837
|
+
for (const kw of keywords) {
|
|
838
|
+
if (!index.keywords[kw])
|
|
839
|
+
index.keywords[kw] = [];
|
|
840
|
+
if (!index.keywords[kw].includes(id)) {
|
|
841
|
+
index.keywords[kw].push(id);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
index.last_updated = Date.now();
|
|
845
|
+
index.total_incidents++;
|
|
846
|
+
index.total_keywords = Object.keys(index.keywords).length;
|
|
847
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
848
|
+
const indexPath = path_1.default.join(paths.root, 'keyword-index.json');
|
|
849
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
850
|
+
await promises_1.default.writeFile(indexPath, JSON.stringify(index), 'utf-8');
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Load keyword index from disk
|
|
854
|
+
*/
|
|
855
|
+
async function loadKeywordIndex(config) {
|
|
856
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
857
|
+
const indexPath = path_1.default.join(paths.root, 'keyword-index.json');
|
|
858
|
+
try {
|
|
859
|
+
const content = await promises_1.default.readFile(indexPath, 'utf-8');
|
|
860
|
+
return JSON.parse(content);
|
|
861
|
+
}
|
|
862
|
+
catch {
|
|
863
|
+
return null;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Find candidate incident IDs by keyword intersection
|
|
868
|
+
*/
|
|
869
|
+
function findCandidatesByKeyword(queryWords, index, maxCandidates = 20) {
|
|
870
|
+
const candidateScores = new Map();
|
|
871
|
+
for (const word of queryWords) {
|
|
872
|
+
const ids = index.keywords[word] || [];
|
|
873
|
+
for (const id of ids) {
|
|
874
|
+
candidateScores.set(id, (candidateScores.get(id) || 0) + 1);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
// Sort by number of keyword hits (descending)
|
|
878
|
+
return [...candidateScores.entries()]
|
|
879
|
+
.sort((a, b) => b[1] - a[1])
|
|
880
|
+
.slice(0, maxCandidates)
|
|
881
|
+
.map(([id]) => id);
|
|
882
|
+
}
|
|
883
|
+
/**
|
|
884
|
+
* Rebuild keyword index from scratch
|
|
885
|
+
*/
|
|
886
|
+
async function rebuildKeywordIndex(config) {
|
|
887
|
+
const incidents = await loadAllIncidents(config);
|
|
888
|
+
const index = {
|
|
889
|
+
version: 1,
|
|
890
|
+
last_updated: Date.now(),
|
|
891
|
+
keywords: {},
|
|
892
|
+
total_incidents: incidents.length,
|
|
893
|
+
total_keywords: 0,
|
|
894
|
+
};
|
|
895
|
+
for (const incident of incidents) {
|
|
896
|
+
const keywords = extractIndexKeywords(incident);
|
|
897
|
+
const id = incident.incident_id;
|
|
898
|
+
for (const kw of keywords) {
|
|
899
|
+
if (!index.keywords[kw])
|
|
900
|
+
index.keywords[kw] = [];
|
|
901
|
+
if (!index.keywords[kw].includes(id)) {
|
|
902
|
+
index.keywords[kw].push(id);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
index.total_keywords = Object.keys(index.keywords).length;
|
|
907
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
908
|
+
const indexPath = path_1.default.join(paths.root, 'keyword-index.json');
|
|
909
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
910
|
+
await promises_1.default.writeFile(indexPath, JSON.stringify(index), 'utf-8');
|
|
911
|
+
return index;
|
|
912
|
+
}
|
|
913
|
+
// ============================================================================
|
|
914
|
+
// INCREMENTAL INDEX UPDATE - Avoid full rebuilds on every store
|
|
915
|
+
// ============================================================================
|
|
916
|
+
/**
|
|
917
|
+
* Update index incrementally with a new incident (avoids full loadAllIncidents)
|
|
918
|
+
*/
|
|
919
|
+
async function updateIndexIncremental(incident, config) {
|
|
920
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
921
|
+
const indexPath = path_1.default.join(paths.root, 'index.json');
|
|
922
|
+
let index;
|
|
923
|
+
try {
|
|
924
|
+
const content = await promises_1.default.readFile(indexPath, 'utf-8');
|
|
925
|
+
index = JSON.parse(content);
|
|
926
|
+
}
|
|
927
|
+
catch {
|
|
928
|
+
// No index yet — do a full rebuild
|
|
929
|
+
await rebuildIndex(config);
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const id = incident.incident_id;
|
|
933
|
+
const cat = incident.root_cause?.category || 'unknown';
|
|
934
|
+
const qs = incident.completeness?.quality_score || 0;
|
|
935
|
+
// Stats
|
|
936
|
+
index.stats.total_incidents++;
|
|
937
|
+
index.stats.categories[cat] = (index.stats.categories[cat] || 0) + 1;
|
|
938
|
+
for (const tag of (incident.tags || [])) {
|
|
939
|
+
index.stats.tags[tag] = (index.stats.tags[tag] || 0) + 1;
|
|
940
|
+
}
|
|
941
|
+
if (incident.timestamp < index.stats.oldest_timestamp || index.stats.oldest_timestamp === 0) {
|
|
942
|
+
index.stats.oldest_timestamp = incident.timestamp;
|
|
943
|
+
}
|
|
944
|
+
if (incident.timestamp > index.stats.newest_timestamp) {
|
|
945
|
+
index.stats.newest_timestamp = incident.timestamp;
|
|
946
|
+
}
|
|
947
|
+
// Category index
|
|
948
|
+
if (!index.by_category[cat])
|
|
949
|
+
index.by_category[cat] = [];
|
|
950
|
+
index.by_category[cat].push(id);
|
|
951
|
+
// Tag index
|
|
952
|
+
for (const tag of (incident.tags || [])) {
|
|
953
|
+
if (!index.by_tag[tag])
|
|
954
|
+
index.by_tag[tag] = [];
|
|
955
|
+
index.by_tag[tag].push(id);
|
|
956
|
+
}
|
|
957
|
+
// File index
|
|
958
|
+
for (const file of (incident.files_changed || [])) {
|
|
959
|
+
if (!index.by_file[file])
|
|
960
|
+
index.by_file[file] = [];
|
|
961
|
+
index.by_file[file].push(id);
|
|
962
|
+
}
|
|
963
|
+
// Quality
|
|
964
|
+
if (qs >= 0.75) {
|
|
965
|
+
index.by_quality.excellent.push(id);
|
|
966
|
+
index.stats.quality_distribution.excellent++;
|
|
967
|
+
}
|
|
968
|
+
else if (qs >= 0.5) {
|
|
969
|
+
index.by_quality.good.push(id);
|
|
970
|
+
index.stats.quality_distribution.good++;
|
|
971
|
+
}
|
|
972
|
+
else {
|
|
973
|
+
index.by_quality.fair.push(id);
|
|
974
|
+
index.stats.quality_distribution.fair++;
|
|
975
|
+
}
|
|
976
|
+
// Recent
|
|
977
|
+
index.recent.unshift(id);
|
|
978
|
+
if (index.recent.length > 20)
|
|
979
|
+
index.recent = index.recent.slice(0, 20);
|
|
980
|
+
index.last_updated = Date.now();
|
|
981
|
+
await promises_1.default.writeFile(indexPath, JSON.stringify(index, null, 2), 'utf-8');
|
|
982
|
+
}
|
|
983
|
+
// ============================================================================
|
|
984
|
+
// OUTCOME TRACKING - Did the suggested fix actually work?
|
|
985
|
+
// ============================================================================
|
|
986
|
+
/**
|
|
987
|
+
* Record whether a verdict suggestion actually worked
|
|
988
|
+
*/
|
|
989
|
+
async function recordOutcome(outcome, config) {
|
|
990
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
991
|
+
const outcomesPath = path_1.default.join(paths.root, 'outcomes.jsonl');
|
|
992
|
+
try {
|
|
993
|
+
await promises_1.default.appendFile(outcomesPath, JSON.stringify(outcome) + '\n', 'utf-8');
|
|
994
|
+
}
|
|
995
|
+
catch {
|
|
996
|
+
await promises_1.default.mkdir(paths.root, { recursive: true });
|
|
997
|
+
await promises_1.default.writeFile(outcomesPath, JSON.stringify(outcome) + '\n', 'utf-8');
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
/**
|
|
1001
|
+
* Load all recorded outcomes
|
|
1002
|
+
*/
|
|
1003
|
+
async function loadOutcomes(config) {
|
|
1004
|
+
const paths = (0, config_1.getMemoryPaths)(config);
|
|
1005
|
+
const outcomesPath = path_1.default.join(paths.root, 'outcomes.jsonl');
|
|
1006
|
+
try {
|
|
1007
|
+
const content = await promises_1.default.readFile(outcomesPath, 'utf-8');
|
|
1008
|
+
return content
|
|
1009
|
+
.split('\n')
|
|
1010
|
+
.filter(line => line.trim())
|
|
1011
|
+
.map(line => {
|
|
1012
|
+
try {
|
|
1013
|
+
return JSON.parse(line);
|
|
1014
|
+
}
|
|
1015
|
+
catch {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
})
|
|
1019
|
+
.filter((o) => o !== null);
|
|
1020
|
+
}
|
|
1021
|
+
catch {
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Get outcome statistics for a specific incident
|
|
1027
|
+
*/
|
|
1028
|
+
async function getOutcomeStats(incident_id, config) {
|
|
1029
|
+
const outcomes = await loadOutcomes(config);
|
|
1030
|
+
const relevant = outcomes.filter(o => o.incident_id === incident_id);
|
|
1031
|
+
return {
|
|
1032
|
+
worked: relevant.filter(o => o.outcome === 'worked').length,
|
|
1033
|
+
failed: relevant.filter(o => o.outcome === 'failed').length,
|
|
1034
|
+
modified: relevant.filter(o => o.outcome === 'modified').length,
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
//# sourceMappingURL=storage.js.map
|