musubi-sdd 5.1.0 → 5.6.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/README.ja.md +106 -48
- package/README.md +110 -32
- package/bin/musubi-analyze.js +74 -67
- package/bin/musubi-browser.js +27 -26
- package/bin/musubi-change.js +48 -47
- package/bin/musubi-checkpoint.js +10 -7
- package/bin/musubi-convert.js +25 -25
- package/bin/musubi-costs.js +27 -10
- package/bin/musubi-gui.js +52 -46
- package/bin/musubi-init.js +1952 -10
- package/bin/musubi-orchestrate.js +327 -239
- package/bin/musubi-remember.js +69 -56
- package/bin/musubi-resolve.js +53 -45
- package/bin/musubi-trace.js +51 -22
- package/bin/musubi-validate.js +39 -30
- package/bin/musubi-workflow.js +33 -34
- package/bin/musubi.js +39 -2
- package/package.json +1 -1
- package/src/agents/agent-loop.js +94 -95
- package/src/agents/agentic/code-generator.js +119 -109
- package/src/agents/agentic/code-reviewer.js +105 -108
- package/src/agents/agentic/index.js +4 -4
- package/src/agents/browser/action-executor.js +13 -13
- package/src/agents/browser/ai-comparator.js +11 -10
- package/src/agents/browser/context-manager.js +6 -6
- package/src/agents/browser/index.js +5 -5
- package/src/agents/browser/nl-parser.js +31 -46
- package/src/agents/browser/screenshot.js +2 -2
- package/src/agents/browser/test-generator.js +6 -4
- package/src/agents/function-tool.js +71 -65
- package/src/agents/index.js +7 -7
- package/src/agents/schema-generator.js +98 -94
- package/src/analyzers/ast-extractor.js +158 -146
- package/src/analyzers/codegraph-auto-update.js +858 -0
- package/src/analyzers/complexity-analyzer.js +536 -0
- package/src/analyzers/context-optimizer.js +241 -126
- package/src/analyzers/impact-analyzer.js +1 -1
- package/src/analyzers/large-project-analyzer.js +766 -0
- package/src/analyzers/repository-map.js +77 -81
- package/src/analyzers/security-analyzer.js +19 -11
- package/src/analyzers/stuck-detector.js +19 -17
- package/src/converters/index.js +78 -57
- package/src/converters/ir/types.js +12 -12
- package/src/converters/parsers/musubi-parser.js +134 -126
- package/src/converters/parsers/openapi-parser.js +70 -53
- package/src/converters/parsers/speckit-parser.js +239 -175
- package/src/converters/writers/musubi-writer.js +123 -118
- package/src/converters/writers/speckit-writer.js +124 -113
- package/src/generators/rust-migration-generator.js +512 -0
- package/src/gui/public/index.html +1365 -1211
- package/src/gui/server.js +41 -40
- package/src/gui/services/file-watcher.js +23 -8
- package/src/gui/services/project-scanner.js +26 -20
- package/src/gui/services/replanning-service.js +27 -23
- package/src/gui/services/traceability-service.js +8 -8
- package/src/gui/services/workflow-service.js +14 -7
- package/src/index.js +151 -0
- package/src/integrations/cicd.js +90 -104
- package/src/integrations/codegraph-mcp.js +643 -0
- package/src/integrations/documentation.js +142 -103
- package/src/integrations/examples.js +95 -80
- package/src/integrations/github-client.js +17 -17
- package/src/integrations/index.js +5 -5
- package/src/integrations/mcp/index.js +21 -21
- package/src/integrations/mcp/mcp-context-provider.js +76 -78
- package/src/integrations/mcp/mcp-discovery.js +74 -72
- package/src/integrations/mcp/mcp-tool-registry.js +99 -94
- package/src/integrations/mcp-connector.js +70 -66
- package/src/integrations/platforms.js +50 -49
- package/src/integrations/tool-discovery.js +37 -31
- package/src/llm-providers/anthropic-provider.js +11 -11
- package/src/llm-providers/base-provider.js +16 -18
- package/src/llm-providers/copilot-provider.js +22 -19
- package/src/llm-providers/index.js +26 -25
- package/src/llm-providers/ollama-provider.js +11 -11
- package/src/llm-providers/openai-provider.js +12 -12
- package/src/managers/agent-memory.js +36 -24
- package/src/managers/checkpoint-manager.js +4 -8
- package/src/managers/delta-spec.js +19 -19
- package/src/managers/index.js +13 -4
- package/src/managers/memory-condenser.js +35 -45
- package/src/managers/repo-skill-manager.js +57 -31
- package/src/managers/skill-loader.js +25 -22
- package/src/managers/skill-tools.js +36 -72
- package/src/managers/workflow.js +30 -22
- package/src/monitoring/cost-tracker.js +48 -46
- package/src/monitoring/incident-manager.js +116 -106
- package/src/monitoring/index.js +144 -134
- package/src/monitoring/observability.js +75 -62
- package/src/monitoring/quality-dashboard.js +45 -41
- package/src/monitoring/release-manager.js +63 -53
- package/src/orchestration/agent-skill-binding.js +39 -47
- package/src/orchestration/error-handler.js +65 -107
- package/src/orchestration/guardrails/base-guardrail.js +26 -24
- package/src/orchestration/guardrails/guardrail-rules.js +50 -64
- package/src/orchestration/guardrails/index.js +5 -5
- package/src/orchestration/guardrails/input-guardrail.js +58 -45
- package/src/orchestration/guardrails/output-guardrail.js +104 -81
- package/src/orchestration/guardrails/safety-check.js +79 -79
- package/src/orchestration/index.js +38 -55
- package/src/orchestration/mcp-tool-adapters.js +96 -99
- package/src/orchestration/orchestration-engine.js +21 -21
- package/src/orchestration/pattern-registry.js +60 -45
- package/src/orchestration/patterns/auto.js +34 -47
- package/src/orchestration/patterns/group-chat.js +59 -65
- package/src/orchestration/patterns/handoff.js +67 -65
- package/src/orchestration/patterns/human-in-loop.js +51 -72
- package/src/orchestration/patterns/nested.js +25 -40
- package/src/orchestration/patterns/sequential.js +35 -34
- package/src/orchestration/patterns/swarm.js +63 -56
- package/src/orchestration/patterns/triage.js +150 -109
- package/src/orchestration/reasoning/index.js +9 -9
- package/src/orchestration/reasoning/planning-engine.js +143 -140
- package/src/orchestration/reasoning/reasoning-engine.js +206 -144
- package/src/orchestration/reasoning/self-correction.js +121 -128
- package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
- package/src/orchestration/replanning/alternative-generator.js +37 -42
- package/src/orchestration/replanning/config.js +63 -59
- package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
- package/src/orchestration/replanning/index.js +24 -20
- package/src/orchestration/replanning/plan-evaluator.js +49 -50
- package/src/orchestration/replanning/plan-monitor.js +32 -28
- package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
- package/src/orchestration/replanning/replan-history.js +33 -26
- package/src/orchestration/replanning/replanning-engine.js +106 -108
- package/src/orchestration/skill-executor.js +107 -109
- package/src/orchestration/skill-registry.js +85 -89
- package/src/orchestration/workflow-examples.js +228 -231
- package/src/orchestration/workflow-executor.js +65 -68
- package/src/orchestration/workflow-orchestrator.js +72 -73
- package/src/phase4-integration.js +47 -40
- package/src/phase5-integration.js +89 -30
- package/src/reporters/coverage-report.js +82 -30
- package/src/reporters/hierarchical-reporter.js +498 -0
- package/src/reporters/traceability-matrix-report.js +29 -20
- package/src/resolvers/issue-resolver.js +43 -31
- package/src/steering/advanced-validation.js +133 -124
- package/src/steering/auto-updater.js +60 -73
- package/src/steering/index.js +6 -6
- package/src/steering/quality-metrics.js +41 -35
- package/src/steering/steering-auto-update.js +83 -86
- package/src/steering/steering-validator.js +98 -106
- package/src/steering/template-constraints.js +53 -54
- package/src/templates/agents/claude-code/CLAUDE.md +32 -32
- package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
- package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
- package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
- package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
- package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
- package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
- package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
- package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
- package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
- package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
- package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
- package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
- package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
- package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
- package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
- package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
- package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
- package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
- package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
- package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
- package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
- package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
- package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
- package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
- package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
- package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
- package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
- package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
- package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
- package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
- package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
- package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
- package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
- package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
- package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
- package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
- package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
- package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
- package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
- package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
- package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
- package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
- package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
- package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
- package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
- package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
- package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
- package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
- package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
- package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
- package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
- package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
- package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
- package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
- package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
- package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
- package/src/templates/agents/codex/AGENTS.md +74 -42
- package/src/templates/agents/cursor/AGENTS.md +74 -42
- package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
- package/src/templates/agents/github-copilot/AGENTS.md +83 -51
- package/src/templates/agents/qwen-code/QWEN.md +74 -42
- package/src/templates/agents/windsurf/AGENTS.md +74 -42
- package/src/templates/architectures/README.md +41 -0
- package/src/templates/architectures/clean-architecture/README.md +113 -0
- package/src/templates/architectures/event-driven/README.md +162 -0
- package/src/templates/architectures/hexagonal/README.md +130 -0
- package/src/templates/index.js +6 -1
- package/src/templates/locale-manager.js +16 -16
- package/src/templates/shared/delta-spec-template.md +20 -13
- package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
- package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
- package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
- package/src/templates/shared/steering/structure.md +95 -0
- package/src/templates/skills/browser-agent.md +21 -16
- package/src/templates/skills/web-gui.md +8 -0
- package/src/templates/template-constraints.js +50 -53
- package/src/validators/advanced-validation.js +30 -36
- package/src/validators/constitutional-validator.js +77 -73
- package/src/validators/critic-system.js +49 -59
- package/src/validators/delta-format.js +59 -55
- package/src/validators/traceability-validator.js +7 -11
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file codegraph-auto-update.js
|
|
3
|
+
* @description Automatic CodeGraph and MCP Index update engine
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*
|
|
6
|
+
* Part of MUSUBI v5.1.0 - Codebase Intelligence Auto-Update
|
|
7
|
+
*
|
|
8
|
+
* @trace REQ-P4-001
|
|
9
|
+
* @requirement REQ-P4-001 Repository Map Generation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { EventEmitter } = require('events');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Update trigger types for CodeGraph
|
|
21
|
+
* @enum {string}
|
|
22
|
+
*/
|
|
23
|
+
const TRIGGER = {
|
|
24
|
+
FILE_CHANGE: 'file-change',
|
|
25
|
+
GIT_COMMIT: 'git-commit',
|
|
26
|
+
BRANCH_SWITCH: 'branch-switch',
|
|
27
|
+
DEPENDENCY_INSTALL: 'dependency-install',
|
|
28
|
+
MCP_CONFIG_CHANGE: 'mcp-config-change',
|
|
29
|
+
MANUAL: 'manual',
|
|
30
|
+
SCHEDULED: 'scheduled',
|
|
31
|
+
STARTUP: 'startup',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Update target types
|
|
36
|
+
* @enum {string}
|
|
37
|
+
*/
|
|
38
|
+
const TARGET = {
|
|
39
|
+
REPOSITORY_MAP: 'repository-map',
|
|
40
|
+
SYMBOL_INDEX: 'symbol-index',
|
|
41
|
+
DEPENDENCY_GRAPH: 'dependency-graph',
|
|
42
|
+
MCP_REGISTRY: 'mcp-registry',
|
|
43
|
+
CONTEXT_CACHE: 'context-cache',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @typedef {Object} FileHash
|
|
48
|
+
* @property {string} path - File path
|
|
49
|
+
* @property {string} hash - Content hash
|
|
50
|
+
* @property {number} mtime - Modification time
|
|
51
|
+
* @property {number} size - File size
|
|
52
|
+
*/
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @typedef {Object} UpdateResult
|
|
56
|
+
* @property {string} id - Update ID
|
|
57
|
+
* @property {string} target - Updated target
|
|
58
|
+
* @property {string} trigger - Trigger type
|
|
59
|
+
* @property {boolean} success - Success status
|
|
60
|
+
* @property {number} filesUpdated - Number of files updated
|
|
61
|
+
* @property {number} duration - Duration in ms
|
|
62
|
+
* @property {Date} timestamp - Update timestamp
|
|
63
|
+
* @property {string[]} changes - List of changes
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @typedef {Object} CodeGraphAutoUpdateOptions
|
|
68
|
+
* @property {string} [cacheDir='.musubi/cache'] - Cache directory
|
|
69
|
+
* @property {number} [debounceMs=500] - Debounce time for file changes
|
|
70
|
+
* @property {boolean} [watchFiles=true] - Enable file watching
|
|
71
|
+
* @property {boolean} [watchMcp=true] - Enable MCP config watching
|
|
72
|
+
* @property {string[]} [ignorePatterns] - Patterns to ignore
|
|
73
|
+
* @property {number} [maxCacheAge=3600000] - Max cache age in ms (1 hour)
|
|
74
|
+
*/
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Default ignore patterns
|
|
78
|
+
*/
|
|
79
|
+
const DEFAULT_IGNORE = [
|
|
80
|
+
'node_modules',
|
|
81
|
+
'.git',
|
|
82
|
+
'dist',
|
|
83
|
+
'build',
|
|
84
|
+
'coverage',
|
|
85
|
+
'.musubi/cache',
|
|
86
|
+
'*.log',
|
|
87
|
+
'*.tmp',
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* CodeGraph Auto-Update Engine
|
|
92
|
+
* @extends EventEmitter
|
|
93
|
+
*/
|
|
94
|
+
class CodeGraphAutoUpdate extends EventEmitter {
|
|
95
|
+
/**
|
|
96
|
+
* @param {CodeGraphAutoUpdateOptions} [options={}]
|
|
97
|
+
*/
|
|
98
|
+
constructor(options = {}) {
|
|
99
|
+
super();
|
|
100
|
+
|
|
101
|
+
this.cacheDir = options.cacheDir || '.musubi/cache';
|
|
102
|
+
this.debounceMs = options.debounceMs || 500;
|
|
103
|
+
this.watchFiles = options.watchFiles !== false;
|
|
104
|
+
this.watchMcp = options.watchMcp !== false;
|
|
105
|
+
this.ignorePatterns = options.ignorePatterns || DEFAULT_IGNORE;
|
|
106
|
+
this.maxCacheAge = options.maxCacheAge || 3600000;
|
|
107
|
+
|
|
108
|
+
// State
|
|
109
|
+
/** @type {Map<string, FileHash>} */
|
|
110
|
+
this.fileHashes = new Map();
|
|
111
|
+
|
|
112
|
+
/** @type {Map<string, Object>} */
|
|
113
|
+
this.cache = new Map();
|
|
114
|
+
|
|
115
|
+
/** @type {Map<string, number>} */
|
|
116
|
+
this.cacheTimestamps = new Map();
|
|
117
|
+
|
|
118
|
+
/** @type {Set<string>} */
|
|
119
|
+
this.pendingUpdates = new Set();
|
|
120
|
+
|
|
121
|
+
/** @type {UpdateResult[]} */
|
|
122
|
+
this.updateHistory = [];
|
|
123
|
+
|
|
124
|
+
/** @type {NodeJS.Timeout|null} */
|
|
125
|
+
this.debounceTimer = null;
|
|
126
|
+
|
|
127
|
+
/** @type {fs.FSWatcher|null} */
|
|
128
|
+
this.fileWatcher = null;
|
|
129
|
+
|
|
130
|
+
/** @type {fs.FSWatcher|null} */
|
|
131
|
+
this.mcpWatcher = null;
|
|
132
|
+
|
|
133
|
+
this.updateCounter = 0;
|
|
134
|
+
this.isUpdating = false;
|
|
135
|
+
this.projectRoot = process.cwd();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Initialize the auto-update engine
|
|
140
|
+
* @param {string} [projectRoot] - Project root directory
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
143
|
+
async initialize(projectRoot) {
|
|
144
|
+
this.projectRoot = projectRoot || process.cwd();
|
|
145
|
+
|
|
146
|
+
// Ensure cache directory exists
|
|
147
|
+
const cacheFullPath = path.join(this.projectRoot, this.cacheDir);
|
|
148
|
+
await this._ensureDir(cacheFullPath);
|
|
149
|
+
|
|
150
|
+
// Load existing cache
|
|
151
|
+
await this._loadCache();
|
|
152
|
+
|
|
153
|
+
// Start watchers
|
|
154
|
+
if (this.watchFiles) {
|
|
155
|
+
this._startFileWatcher();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (this.watchMcp) {
|
|
159
|
+
this._startMcpWatcher();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Initial scan
|
|
163
|
+
await this.triggerUpdate(TRIGGER.STARTUP, {
|
|
164
|
+
targets: [TARGET.REPOSITORY_MAP, TARGET.MCP_REGISTRY],
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
this.emit('initialized', { projectRoot: this.projectRoot });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Trigger an update
|
|
172
|
+
* @param {string} trigger - Trigger type
|
|
173
|
+
* @param {Object} [context={}] - Update context
|
|
174
|
+
* @returns {Promise<UpdateResult[]>}
|
|
175
|
+
*/
|
|
176
|
+
async triggerUpdate(trigger, context = {}) {
|
|
177
|
+
const targets = context.targets || this._getTargetsForTrigger(trigger);
|
|
178
|
+
const results = [];
|
|
179
|
+
|
|
180
|
+
this.isUpdating = true;
|
|
181
|
+
this.emit('update-start', { trigger, targets });
|
|
182
|
+
|
|
183
|
+
for (const target of targets) {
|
|
184
|
+
const startTime = Date.now();
|
|
185
|
+
const updateId = `update-${++this.updateCounter}`;
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const result = await this._updateTarget(target, trigger, context);
|
|
189
|
+
|
|
190
|
+
const updateResult = {
|
|
191
|
+
id: updateId,
|
|
192
|
+
target,
|
|
193
|
+
trigger,
|
|
194
|
+
success: true,
|
|
195
|
+
filesUpdated: result.filesUpdated || 0,
|
|
196
|
+
duration: Date.now() - startTime,
|
|
197
|
+
timestamp: new Date(),
|
|
198
|
+
changes: result.changes || [],
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
results.push(updateResult);
|
|
202
|
+
this.updateHistory.push(updateResult);
|
|
203
|
+
this.emit('update-complete', updateResult);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
const updateResult = {
|
|
206
|
+
id: updateId,
|
|
207
|
+
target,
|
|
208
|
+
trigger,
|
|
209
|
+
success: false,
|
|
210
|
+
filesUpdated: 0,
|
|
211
|
+
duration: Date.now() - startTime,
|
|
212
|
+
timestamp: new Date(),
|
|
213
|
+
changes: [],
|
|
214
|
+
error: error.message,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
results.push(updateResult);
|
|
218
|
+
this.updateHistory.push(updateResult);
|
|
219
|
+
this.emit('update-error', { ...updateResult, error });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
this.isUpdating = false;
|
|
224
|
+
this.emit('update-batch-complete', { trigger, results });
|
|
225
|
+
|
|
226
|
+
// Persist cache
|
|
227
|
+
await this._saveCache();
|
|
228
|
+
|
|
229
|
+
return results;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Get targets for a specific trigger
|
|
234
|
+
* @private
|
|
235
|
+
*/
|
|
236
|
+
_getTargetsForTrigger(trigger) {
|
|
237
|
+
switch (trigger) {
|
|
238
|
+
case TRIGGER.FILE_CHANGE:
|
|
239
|
+
return [TARGET.REPOSITORY_MAP, TARGET.SYMBOL_INDEX];
|
|
240
|
+
|
|
241
|
+
case TRIGGER.GIT_COMMIT:
|
|
242
|
+
case TRIGGER.BRANCH_SWITCH:
|
|
243
|
+
return [TARGET.REPOSITORY_MAP, TARGET.SYMBOL_INDEX, TARGET.DEPENDENCY_GRAPH];
|
|
244
|
+
|
|
245
|
+
case TRIGGER.DEPENDENCY_INSTALL:
|
|
246
|
+
return [TARGET.DEPENDENCY_GRAPH, TARGET.SYMBOL_INDEX];
|
|
247
|
+
|
|
248
|
+
case TRIGGER.MCP_CONFIG_CHANGE:
|
|
249
|
+
return [TARGET.MCP_REGISTRY];
|
|
250
|
+
|
|
251
|
+
case TRIGGER.STARTUP:
|
|
252
|
+
case TRIGGER.MANUAL:
|
|
253
|
+
return [TARGET.REPOSITORY_MAP, TARGET.SYMBOL_INDEX, TARGET.MCP_REGISTRY];
|
|
254
|
+
|
|
255
|
+
case TRIGGER.SCHEDULED:
|
|
256
|
+
return [TARGET.REPOSITORY_MAP, TARGET.CONTEXT_CACHE];
|
|
257
|
+
|
|
258
|
+
default:
|
|
259
|
+
return [TARGET.REPOSITORY_MAP];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update a specific target
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
267
|
+
async _updateTarget(target, trigger, context) {
|
|
268
|
+
switch (target) {
|
|
269
|
+
case TARGET.REPOSITORY_MAP:
|
|
270
|
+
return this._updateRepositoryMap(context);
|
|
271
|
+
|
|
272
|
+
case TARGET.SYMBOL_INDEX:
|
|
273
|
+
return this._updateSymbolIndex(context);
|
|
274
|
+
|
|
275
|
+
case TARGET.DEPENDENCY_GRAPH:
|
|
276
|
+
return this._updateDependencyGraph(context);
|
|
277
|
+
|
|
278
|
+
case TARGET.MCP_REGISTRY:
|
|
279
|
+
return this._updateMcpRegistry(context);
|
|
280
|
+
|
|
281
|
+
case TARGET.CONTEXT_CACHE:
|
|
282
|
+
return this._updateContextCache(context);
|
|
283
|
+
|
|
284
|
+
default:
|
|
285
|
+
return { filesUpdated: 0, changes: [] };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update repository map incrementally
|
|
291
|
+
* @private
|
|
292
|
+
*/
|
|
293
|
+
async _updateRepositoryMap(context) {
|
|
294
|
+
const changes = [];
|
|
295
|
+
let filesUpdated = 0;
|
|
296
|
+
|
|
297
|
+
// Get changed files since last update
|
|
298
|
+
const changedFiles = context.changedFiles || (await this._detectChangedFiles());
|
|
299
|
+
|
|
300
|
+
if (changedFiles.added.length > 0) {
|
|
301
|
+
changes.push(`Added ${changedFiles.added.length} files`);
|
|
302
|
+
filesUpdated += changedFiles.added.length;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if (changedFiles.modified.length > 0) {
|
|
306
|
+
changes.push(`Modified ${changedFiles.modified.length} files`);
|
|
307
|
+
filesUpdated += changedFiles.modified.length;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (changedFiles.deleted.length > 0) {
|
|
311
|
+
changes.push(`Deleted ${changedFiles.deleted.length} files`);
|
|
312
|
+
filesUpdated += changedFiles.deleted.length;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Update file hashes
|
|
316
|
+
for (const file of [...changedFiles.added, ...changedFiles.modified]) {
|
|
317
|
+
const hash = await this._computeFileHash(file);
|
|
318
|
+
this.fileHashes.set(file, hash);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (const file of changedFiles.deleted) {
|
|
322
|
+
this.fileHashes.delete(file);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Update cache
|
|
326
|
+
this.cache.set(TARGET.REPOSITORY_MAP, {
|
|
327
|
+
files: Array.from(this.fileHashes.entries()),
|
|
328
|
+
updatedAt: new Date().toISOString(),
|
|
329
|
+
});
|
|
330
|
+
this.cacheTimestamps.set(TARGET.REPOSITORY_MAP, Date.now());
|
|
331
|
+
|
|
332
|
+
return { filesUpdated, changes };
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Update symbol index incrementally
|
|
337
|
+
* @private
|
|
338
|
+
*/
|
|
339
|
+
async _updateSymbolIndex(context) {
|
|
340
|
+
const changes = [];
|
|
341
|
+
let filesUpdated = 0;
|
|
342
|
+
|
|
343
|
+
const changedFiles = context.changedFiles || (await this._detectChangedFiles());
|
|
344
|
+
const codeFiles = [...changedFiles.added, ...changedFiles.modified].filter(f =>
|
|
345
|
+
this._isCodeFile(f)
|
|
346
|
+
);
|
|
347
|
+
|
|
348
|
+
for (const file of codeFiles) {
|
|
349
|
+
const symbols = await this._extractSymbols(file);
|
|
350
|
+
if (symbols.length > 0) {
|
|
351
|
+
changes.push(`Indexed ${symbols.length} symbols in ${path.basename(file)}`);
|
|
352
|
+
filesUpdated++;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Remove symbols from deleted files
|
|
357
|
+
for (const file of changedFiles.deleted) {
|
|
358
|
+
if (this._isCodeFile(file)) {
|
|
359
|
+
changes.push(`Removed symbols from ${path.basename(file)}`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
this.cacheTimestamps.set(TARGET.SYMBOL_INDEX, Date.now());
|
|
364
|
+
|
|
365
|
+
return { filesUpdated, changes };
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Update dependency graph
|
|
370
|
+
* @private
|
|
371
|
+
*/
|
|
372
|
+
async _updateDependencyGraph(_context) {
|
|
373
|
+
const changes = [];
|
|
374
|
+
let filesUpdated = 0;
|
|
375
|
+
|
|
376
|
+
// Check package.json for dependency changes
|
|
377
|
+
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
378
|
+
|
|
379
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
380
|
+
const currentHash = await this._computeFileHash(packageJsonPath);
|
|
381
|
+
const cachedHash = this.fileHashes.get(packageJsonPath);
|
|
382
|
+
|
|
383
|
+
if (!cachedHash || cachedHash.hash !== currentHash.hash) {
|
|
384
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
385
|
+
const deps = Object.keys(pkg.dependencies || {});
|
|
386
|
+
const devDeps = Object.keys(pkg.devDependencies || {});
|
|
387
|
+
|
|
388
|
+
changes.push(`Dependencies: ${deps.length} production, ${devDeps.length} dev`);
|
|
389
|
+
filesUpdated = 1;
|
|
390
|
+
|
|
391
|
+
this.cache.set(TARGET.DEPENDENCY_GRAPH, {
|
|
392
|
+
dependencies: deps,
|
|
393
|
+
devDependencies: devDeps,
|
|
394
|
+
updatedAt: new Date().toISOString(),
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
this.fileHashes.set(packageJsonPath, currentHash);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
this.cacheTimestamps.set(TARGET.DEPENDENCY_GRAPH, Date.now());
|
|
402
|
+
|
|
403
|
+
return { filesUpdated, changes };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Update MCP registry
|
|
408
|
+
* @private
|
|
409
|
+
*/
|
|
410
|
+
async _updateMcpRegistry(_context) {
|
|
411
|
+
const changes = [];
|
|
412
|
+
let filesUpdated = 0;
|
|
413
|
+
|
|
414
|
+
const mcpConfigs = ['.mcp/config.json', '.mcp.json', 'mcp.config.json'];
|
|
415
|
+
|
|
416
|
+
const servers = [];
|
|
417
|
+
|
|
418
|
+
for (const configFile of mcpConfigs) {
|
|
419
|
+
const configPath = path.join(this.projectRoot, configFile);
|
|
420
|
+
|
|
421
|
+
if (fs.existsSync(configPath)) {
|
|
422
|
+
try {
|
|
423
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
424
|
+
const mcpServers = config.mcpServers || config.servers || {};
|
|
425
|
+
|
|
426
|
+
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
427
|
+
servers.push({ name, ...serverConfig });
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
changes.push(`Loaded ${Object.keys(mcpServers).length} servers from ${configFile}`);
|
|
431
|
+
filesUpdated++;
|
|
432
|
+
} catch (error) {
|
|
433
|
+
changes.push(`Error parsing ${configFile}: ${error.message}`);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.cache.set(TARGET.MCP_REGISTRY, {
|
|
439
|
+
servers,
|
|
440
|
+
updatedAt: new Date().toISOString(),
|
|
441
|
+
});
|
|
442
|
+
this.cacheTimestamps.set(TARGET.MCP_REGISTRY, Date.now());
|
|
443
|
+
|
|
444
|
+
this.emit('mcp-registry-updated', { servers });
|
|
445
|
+
|
|
446
|
+
return { filesUpdated, changes };
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Update context cache
|
|
451
|
+
* @private
|
|
452
|
+
*/
|
|
453
|
+
async _updateContextCache(_context) {
|
|
454
|
+
const changes = [];
|
|
455
|
+
let filesUpdated = 0;
|
|
456
|
+
|
|
457
|
+
// Invalidate old cache entries
|
|
458
|
+
const now = Date.now();
|
|
459
|
+
|
|
460
|
+
for (const [key, timestamp] of this.cacheTimestamps.entries()) {
|
|
461
|
+
if (now - timestamp > this.maxCacheAge) {
|
|
462
|
+
this.cache.delete(key);
|
|
463
|
+
this.cacheTimestamps.delete(key);
|
|
464
|
+
changes.push(`Invalidated cache: ${key}`);
|
|
465
|
+
filesUpdated++;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
return { filesUpdated, changes };
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Detect changed files since last update
|
|
474
|
+
* @private
|
|
475
|
+
*/
|
|
476
|
+
async _detectChangedFiles() {
|
|
477
|
+
const added = [];
|
|
478
|
+
const modified = [];
|
|
479
|
+
const deleted = [];
|
|
480
|
+
|
|
481
|
+
const currentFiles = await this._scanDirectory(this.projectRoot);
|
|
482
|
+
const knownFiles = new Set(this.fileHashes.keys());
|
|
483
|
+
|
|
484
|
+
for (const file of currentFiles) {
|
|
485
|
+
if (this._shouldIgnore(file)) continue;
|
|
486
|
+
|
|
487
|
+
const relativePath = path.relative(this.projectRoot, file);
|
|
488
|
+
|
|
489
|
+
if (!knownFiles.has(relativePath)) {
|
|
490
|
+
added.push(relativePath);
|
|
491
|
+
} else {
|
|
492
|
+
const currentHash = await this._computeFileHash(relativePath);
|
|
493
|
+
const cachedHash = this.fileHashes.get(relativePath);
|
|
494
|
+
|
|
495
|
+
if (cachedHash && cachedHash.hash !== currentHash.hash) {
|
|
496
|
+
modified.push(relativePath);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
knownFiles.delete(relativePath);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Remaining known files are deleted
|
|
504
|
+
for (const file of knownFiles) {
|
|
505
|
+
deleted.push(file);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
return { added, modified, deleted };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Scan directory recursively
|
|
513
|
+
* @private
|
|
514
|
+
*/
|
|
515
|
+
async _scanDirectory(dir, files = []) {
|
|
516
|
+
if (!fs.existsSync(dir)) return files;
|
|
517
|
+
|
|
518
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
519
|
+
|
|
520
|
+
for (const entry of entries) {
|
|
521
|
+
const fullPath = path.join(dir, entry.name);
|
|
522
|
+
|
|
523
|
+
if (this._shouldIgnore(fullPath)) continue;
|
|
524
|
+
|
|
525
|
+
if (entry.isDirectory()) {
|
|
526
|
+
await this._scanDirectory(fullPath, files);
|
|
527
|
+
} else if (entry.isFile()) {
|
|
528
|
+
files.push(fullPath);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
return files;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Compute file hash
|
|
537
|
+
* @private
|
|
538
|
+
*/
|
|
539
|
+
async _computeFileHash(filePath) {
|
|
540
|
+
const fullPath = path.isAbsolute(filePath) ? filePath : path.join(this.projectRoot, filePath);
|
|
541
|
+
|
|
542
|
+
if (!fs.existsSync(fullPath)) {
|
|
543
|
+
return null;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const stats = fs.statSync(fullPath);
|
|
547
|
+
const content = fs.readFileSync(fullPath);
|
|
548
|
+
const hash = crypto.createHash('md5').update(content).digest('hex');
|
|
549
|
+
|
|
550
|
+
return {
|
|
551
|
+
path: filePath,
|
|
552
|
+
hash,
|
|
553
|
+
mtime: stats.mtimeMs,
|
|
554
|
+
size: stats.size,
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Extract symbols from a code file
|
|
560
|
+
* @private
|
|
561
|
+
*/
|
|
562
|
+
async _extractSymbols(filePath) {
|
|
563
|
+
const symbols = [];
|
|
564
|
+
const fullPath = path.join(this.projectRoot, filePath);
|
|
565
|
+
|
|
566
|
+
if (!fs.existsSync(fullPath)) return symbols;
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
const content = fs.readFileSync(fullPath, 'utf-8');
|
|
570
|
+
|
|
571
|
+
// Extract function declarations
|
|
572
|
+
const funcMatches = content.matchAll(/(?:function|const|let|var)\s+(\w+)\s*[=(]/g);
|
|
573
|
+
for (const match of funcMatches) {
|
|
574
|
+
symbols.push({ name: match[1], type: 'function', file: filePath });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Extract class declarations
|
|
578
|
+
const classMatches = content.matchAll(/class\s+(\w+)/g);
|
|
579
|
+
for (const match of classMatches) {
|
|
580
|
+
symbols.push({ name: match[1], type: 'class', file: filePath });
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Extract exports
|
|
584
|
+
const exportMatches = content.matchAll(/module\.exports\s*=\s*{([^}]+)}/g);
|
|
585
|
+
for (const match of exportMatches) {
|
|
586
|
+
const exports = match[1].split(',').map(e => e.trim().split(':')[0].trim());
|
|
587
|
+
for (const exp of exports) {
|
|
588
|
+
if (exp) symbols.push({ name: exp, type: 'export', file: filePath });
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
} catch (error) {
|
|
592
|
+
// Ignore parse errors
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
return symbols;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
/**
|
|
599
|
+
* Check if file is a code file
|
|
600
|
+
* @private
|
|
601
|
+
*/
|
|
602
|
+
_isCodeFile(filePath) {
|
|
603
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
604
|
+
return ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java'].includes(ext);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Check if path should be ignored
|
|
609
|
+
* @private
|
|
610
|
+
*/
|
|
611
|
+
_shouldIgnore(filePath) {
|
|
612
|
+
const relativePath = path.relative(this.projectRoot, filePath);
|
|
613
|
+
|
|
614
|
+
for (const pattern of this.ignorePatterns) {
|
|
615
|
+
if (pattern.includes('*')) {
|
|
616
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
617
|
+
if (regex.test(relativePath)) return true;
|
|
618
|
+
} else {
|
|
619
|
+
if (relativePath.includes(pattern)) return true;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* Start file watcher
|
|
628
|
+
* @private
|
|
629
|
+
*/
|
|
630
|
+
_startFileWatcher() {
|
|
631
|
+
try {
|
|
632
|
+
this.fileWatcher = fs.watch(this.projectRoot, { recursive: true }, (eventType, filename) => {
|
|
633
|
+
if (!filename || this._shouldIgnore(filename)) return;
|
|
634
|
+
|
|
635
|
+
this.pendingUpdates.add(filename);
|
|
636
|
+
this._debouncedUpdate(TRIGGER.FILE_CHANGE);
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
this.emit('watcher-started', { type: 'file' });
|
|
640
|
+
} catch (error) {
|
|
641
|
+
this.emit('watcher-error', { type: 'file', error });
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Start MCP config watcher
|
|
647
|
+
* @private
|
|
648
|
+
*/
|
|
649
|
+
_startMcpWatcher() {
|
|
650
|
+
const mcpDir = path.join(this.projectRoot, '.mcp');
|
|
651
|
+
|
|
652
|
+
if (!fs.existsSync(mcpDir)) {
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
this.mcpWatcher = fs.watch(mcpDir, (eventType, filename) => {
|
|
658
|
+
if (filename && filename.endsWith('.json')) {
|
|
659
|
+
this._debouncedUpdate(TRIGGER.MCP_CONFIG_CHANGE);
|
|
660
|
+
}
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
this.emit('watcher-started', { type: 'mcp' });
|
|
664
|
+
} catch (error) {
|
|
665
|
+
this.emit('watcher-error', { type: 'mcp', error });
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/**
|
|
670
|
+
* Debounced update trigger
|
|
671
|
+
* @private
|
|
672
|
+
*/
|
|
673
|
+
_debouncedUpdate(trigger) {
|
|
674
|
+
if (this.debounceTimer) {
|
|
675
|
+
clearTimeout(this.debounceTimer);
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
this.debounceTimer = setTimeout(async () => {
|
|
679
|
+
const changedFiles = Array.from(this.pendingUpdates);
|
|
680
|
+
this.pendingUpdates.clear();
|
|
681
|
+
|
|
682
|
+
await this.triggerUpdate(trigger, {
|
|
683
|
+
changedFiles: {
|
|
684
|
+
added: [],
|
|
685
|
+
modified: changedFiles,
|
|
686
|
+
deleted: [],
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
}, this.debounceMs);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Load cache from disk
|
|
694
|
+
* @private
|
|
695
|
+
*/
|
|
696
|
+
async _loadCache() {
|
|
697
|
+
const cacheFile = path.join(this.projectRoot, this.cacheDir, 'codegraph-cache.json');
|
|
698
|
+
|
|
699
|
+
if (fs.existsSync(cacheFile)) {
|
|
700
|
+
try {
|
|
701
|
+
const data = JSON.parse(fs.readFileSync(cacheFile, 'utf-8'));
|
|
702
|
+
|
|
703
|
+
if (data.fileHashes) {
|
|
704
|
+
this.fileHashes = new Map(data.fileHashes);
|
|
705
|
+
}
|
|
706
|
+
if (data.cache) {
|
|
707
|
+
this.cache = new Map(Object.entries(data.cache));
|
|
708
|
+
}
|
|
709
|
+
if (data.cacheTimestamps) {
|
|
710
|
+
this.cacheTimestamps = new Map(Object.entries(data.cacheTimestamps));
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
this.emit('cache-loaded', { entries: this.cache.size });
|
|
714
|
+
} catch (error) {
|
|
715
|
+
this.emit('cache-error', { action: 'load', error });
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
/**
|
|
721
|
+
* Save cache to disk
|
|
722
|
+
* @private
|
|
723
|
+
*/
|
|
724
|
+
async _saveCache() {
|
|
725
|
+
const cacheFile = path.join(this.projectRoot, this.cacheDir, 'codegraph-cache.json');
|
|
726
|
+
|
|
727
|
+
try {
|
|
728
|
+
await this._ensureDir(path.dirname(cacheFile));
|
|
729
|
+
|
|
730
|
+
const data = {
|
|
731
|
+
fileHashes: Array.from(this.fileHashes.entries()),
|
|
732
|
+
cache: Object.fromEntries(this.cache),
|
|
733
|
+
cacheTimestamps: Object.fromEntries(this.cacheTimestamps),
|
|
734
|
+
savedAt: new Date().toISOString(),
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
fs.writeFileSync(cacheFile, JSON.stringify(data, null, 2));
|
|
738
|
+
this.emit('cache-saved', { entries: this.cache.size });
|
|
739
|
+
} catch (error) {
|
|
740
|
+
this.emit('cache-error', { action: 'save', error });
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Ensure directory exists
|
|
746
|
+
* @private
|
|
747
|
+
*/
|
|
748
|
+
async _ensureDir(dir) {
|
|
749
|
+
if (!fs.existsSync(dir)) {
|
|
750
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Get cache entry
|
|
756
|
+
* @param {string} target - Cache target
|
|
757
|
+
* @returns {Object|null}
|
|
758
|
+
*/
|
|
759
|
+
getCache(target) {
|
|
760
|
+
return this.cache.get(target) || null;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
/**
|
|
764
|
+
* Get MCP servers from cache
|
|
765
|
+
* @returns {Object[]}
|
|
766
|
+
*/
|
|
767
|
+
getMcpServers() {
|
|
768
|
+
const registry = this.cache.get(TARGET.MCP_REGISTRY);
|
|
769
|
+
return registry?.servers || [];
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Get update statistics
|
|
774
|
+
* @returns {Object}
|
|
775
|
+
*/
|
|
776
|
+
getStatistics() {
|
|
777
|
+
const recentUpdates = this.updateHistory.slice(-100);
|
|
778
|
+
|
|
779
|
+
return {
|
|
780
|
+
totalUpdates: this.updateHistory.length,
|
|
781
|
+
successRate: recentUpdates.filter(u => u.success).length / Math.max(recentUpdates.length, 1),
|
|
782
|
+
averageDuration:
|
|
783
|
+
recentUpdates.reduce((sum, u) => sum + u.duration, 0) / Math.max(recentUpdates.length, 1),
|
|
784
|
+
cacheSize: this.cache.size,
|
|
785
|
+
filesTracked: this.fileHashes.size,
|
|
786
|
+
lastUpdate: recentUpdates[recentUpdates.length - 1]?.timestamp || null,
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Force full refresh of all targets
|
|
792
|
+
* @returns {Promise<UpdateResult[]>}
|
|
793
|
+
*/
|
|
794
|
+
async forceRefresh() {
|
|
795
|
+
// Clear all caches
|
|
796
|
+
this.fileHashes.clear();
|
|
797
|
+
this.cache.clear();
|
|
798
|
+
this.cacheTimestamps.clear();
|
|
799
|
+
|
|
800
|
+
return this.triggerUpdate(TRIGGER.MANUAL, {
|
|
801
|
+
targets: Object.values(TARGET),
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Start watchers (alias for initialize without initial scan)
|
|
807
|
+
* Used by Phase5Integration for consistent API
|
|
808
|
+
*/
|
|
809
|
+
start() {
|
|
810
|
+
if (this.watchFiles && !this.fileWatcher) {
|
|
811
|
+
this._startFileWatcher();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
if (this.watchMcp && !this.mcpWatcher) {
|
|
815
|
+
this._startMcpWatcher();
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
this.emit('started');
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Stop all watchers and cleanup
|
|
823
|
+
*/
|
|
824
|
+
stop() {
|
|
825
|
+
if (this.fileWatcher) {
|
|
826
|
+
this.fileWatcher.close();
|
|
827
|
+
this.fileWatcher = null;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (this.mcpWatcher) {
|
|
831
|
+
this.mcpWatcher.close();
|
|
832
|
+
this.mcpWatcher = null;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
if (this.debounceTimer) {
|
|
836
|
+
clearTimeout(this.debounceTimer);
|
|
837
|
+
this.debounceTimer = null;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
this.emit('stopped');
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Create CodeGraph auto-update instance
|
|
846
|
+
* @param {CodeGraphAutoUpdateOptions} [options={}]
|
|
847
|
+
* @returns {CodeGraphAutoUpdate}
|
|
848
|
+
*/
|
|
849
|
+
function createCodeGraphAutoUpdate(options = {}) {
|
|
850
|
+
return new CodeGraphAutoUpdate(options);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
module.exports = {
|
|
854
|
+
CodeGraphAutoUpdate,
|
|
855
|
+
createCodeGraphAutoUpdate,
|
|
856
|
+
TRIGGER,
|
|
857
|
+
TARGET,
|
|
858
|
+
};
|