musubi-sdd 5.0.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.
Files changed (232) hide show
  1. package/README.ja.md +106 -48
  2. package/README.md +110 -32
  3. package/bin/musubi-analyze.js +74 -67
  4. package/bin/musubi-browser.js +27 -26
  5. package/bin/musubi-change.js +48 -47
  6. package/bin/musubi-checkpoint.js +10 -7
  7. package/bin/musubi-convert.js +25 -25
  8. package/bin/musubi-costs.js +27 -10
  9. package/bin/musubi-gui.js +52 -46
  10. package/bin/musubi-init.js +1952 -10
  11. package/bin/musubi-orchestrate.js +327 -239
  12. package/bin/musubi-remember.js +69 -56
  13. package/bin/musubi-resolve.js +53 -45
  14. package/bin/musubi-trace.js +51 -22
  15. package/bin/musubi-validate.js +39 -30
  16. package/bin/musubi-workflow.js +33 -34
  17. package/bin/musubi.js +39 -2
  18. package/package.json +1 -1
  19. package/src/agents/agent-loop.js +94 -95
  20. package/src/agents/agentic/code-generator.js +119 -109
  21. package/src/agents/agentic/code-reviewer.js +105 -108
  22. package/src/agents/agentic/index.js +4 -4
  23. package/src/agents/browser/action-executor.js +13 -13
  24. package/src/agents/browser/ai-comparator.js +11 -10
  25. package/src/agents/browser/context-manager.js +6 -6
  26. package/src/agents/browser/index.js +5 -5
  27. package/src/agents/browser/nl-parser.js +31 -46
  28. package/src/agents/browser/screenshot.js +2 -2
  29. package/src/agents/browser/test-generator.js +6 -4
  30. package/src/agents/function-tool.js +71 -65
  31. package/src/agents/index.js +7 -7
  32. package/src/agents/schema-generator.js +98 -94
  33. package/src/analyzers/ast-extractor.js +164 -145
  34. package/src/analyzers/codegraph-auto-update.js +858 -0
  35. package/src/analyzers/complexity-analyzer.js +536 -0
  36. package/src/analyzers/context-optimizer.js +247 -125
  37. package/src/analyzers/impact-analyzer.js +1 -1
  38. package/src/analyzers/large-project-analyzer.js +766 -0
  39. package/src/analyzers/repository-map.js +83 -80
  40. package/src/analyzers/security-analyzer.js +19 -11
  41. package/src/analyzers/stuck-detector.js +19 -17
  42. package/src/converters/index.js +78 -57
  43. package/src/converters/ir/types.js +12 -12
  44. package/src/converters/parsers/musubi-parser.js +134 -126
  45. package/src/converters/parsers/openapi-parser.js +70 -53
  46. package/src/converters/parsers/speckit-parser.js +239 -175
  47. package/src/converters/writers/musubi-writer.js +123 -118
  48. package/src/converters/writers/speckit-writer.js +124 -113
  49. package/src/generators/rust-migration-generator.js +512 -0
  50. package/src/gui/public/index.html +1365 -1211
  51. package/src/gui/server.js +41 -40
  52. package/src/gui/services/file-watcher.js +23 -8
  53. package/src/gui/services/project-scanner.js +26 -20
  54. package/src/gui/services/replanning-service.js +27 -23
  55. package/src/gui/services/traceability-service.js +8 -8
  56. package/src/gui/services/workflow-service.js +14 -7
  57. package/src/index.js +151 -0
  58. package/src/integrations/cicd.js +90 -104
  59. package/src/integrations/codegraph-mcp.js +643 -0
  60. package/src/integrations/documentation.js +142 -103
  61. package/src/integrations/examples.js +95 -80
  62. package/src/integrations/github-client.js +17 -17
  63. package/src/integrations/index.js +5 -5
  64. package/src/integrations/mcp/index.js +21 -21
  65. package/src/integrations/mcp/mcp-context-provider.js +76 -78
  66. package/src/integrations/mcp/mcp-discovery.js +74 -72
  67. package/src/integrations/mcp/mcp-tool-registry.js +99 -94
  68. package/src/integrations/mcp-connector.js +70 -66
  69. package/src/integrations/platforms.js +50 -49
  70. package/src/integrations/tool-discovery.js +37 -31
  71. package/src/llm-providers/anthropic-provider.js +11 -11
  72. package/src/llm-providers/base-provider.js +16 -18
  73. package/src/llm-providers/copilot-provider.js +22 -19
  74. package/src/llm-providers/index.js +26 -25
  75. package/src/llm-providers/ollama-provider.js +11 -11
  76. package/src/llm-providers/openai-provider.js +12 -12
  77. package/src/managers/agent-memory.js +36 -24
  78. package/src/managers/checkpoint-manager.js +4 -8
  79. package/src/managers/delta-spec.js +19 -19
  80. package/src/managers/index.js +13 -4
  81. package/src/managers/memory-condenser.js +35 -45
  82. package/src/managers/repo-skill-manager.js +57 -31
  83. package/src/managers/skill-loader.js +25 -22
  84. package/src/managers/skill-tools.js +36 -72
  85. package/src/managers/workflow.js +30 -22
  86. package/src/monitoring/cost-tracker.js +53 -44
  87. package/src/monitoring/incident-manager.js +123 -103
  88. package/src/monitoring/index.js +144 -134
  89. package/src/monitoring/observability.js +82 -59
  90. package/src/monitoring/quality-dashboard.js +51 -39
  91. package/src/monitoring/release-manager.js +70 -50
  92. package/src/orchestration/agent-skill-binding.js +39 -47
  93. package/src/orchestration/error-handler.js +65 -107
  94. package/src/orchestration/guardrails/base-guardrail.js +26 -24
  95. package/src/orchestration/guardrails/guardrail-rules.js +50 -64
  96. package/src/orchestration/guardrails/index.js +5 -5
  97. package/src/orchestration/guardrails/input-guardrail.js +58 -45
  98. package/src/orchestration/guardrails/output-guardrail.js +104 -81
  99. package/src/orchestration/guardrails/safety-check.js +79 -79
  100. package/src/orchestration/index.js +38 -55
  101. package/src/orchestration/mcp-tool-adapters.js +96 -99
  102. package/src/orchestration/orchestration-engine.js +21 -21
  103. package/src/orchestration/pattern-registry.js +60 -45
  104. package/src/orchestration/patterns/auto.js +34 -47
  105. package/src/orchestration/patterns/group-chat.js +59 -65
  106. package/src/orchestration/patterns/handoff.js +67 -65
  107. package/src/orchestration/patterns/human-in-loop.js +51 -72
  108. package/src/orchestration/patterns/nested.js +25 -40
  109. package/src/orchestration/patterns/sequential.js +35 -34
  110. package/src/orchestration/patterns/swarm.js +63 -56
  111. package/src/orchestration/patterns/triage.js +150 -109
  112. package/src/orchestration/reasoning/index.js +9 -9
  113. package/src/orchestration/reasoning/planning-engine.js +143 -140
  114. package/src/orchestration/reasoning/reasoning-engine.js +206 -144
  115. package/src/orchestration/reasoning/self-correction.js +121 -128
  116. package/src/orchestration/replanning/adaptive-goal-modifier.js +107 -112
  117. package/src/orchestration/replanning/alternative-generator.js +37 -42
  118. package/src/orchestration/replanning/config.js +63 -59
  119. package/src/orchestration/replanning/goal-progress-tracker.js +98 -100
  120. package/src/orchestration/replanning/index.js +24 -20
  121. package/src/orchestration/replanning/plan-evaluator.js +49 -50
  122. package/src/orchestration/replanning/plan-monitor.js +32 -28
  123. package/src/orchestration/replanning/proactive-path-optimizer.js +175 -178
  124. package/src/orchestration/replanning/replan-history.js +33 -26
  125. package/src/orchestration/replanning/replanning-engine.js +106 -108
  126. package/src/orchestration/skill-executor.js +107 -109
  127. package/src/orchestration/skill-registry.js +85 -89
  128. package/src/orchestration/workflow-examples.js +228 -231
  129. package/src/orchestration/workflow-executor.js +65 -68
  130. package/src/orchestration/workflow-orchestrator.js +72 -73
  131. package/src/phase4-integration.js +47 -40
  132. package/src/phase5-integration.js +89 -30
  133. package/src/reporters/coverage-report.js +82 -30
  134. package/src/reporters/hierarchical-reporter.js +498 -0
  135. package/src/reporters/traceability-matrix-report.js +29 -20
  136. package/src/resolvers/issue-resolver.js +43 -31
  137. package/src/steering/advanced-validation.js +133 -124
  138. package/src/steering/auto-updater.js +60 -73
  139. package/src/steering/index.js +6 -6
  140. package/src/steering/quality-metrics.js +41 -35
  141. package/src/steering/steering-auto-update.js +83 -86
  142. package/src/steering/steering-validator.js +98 -106
  143. package/src/steering/template-constraints.js +53 -54
  144. package/src/templates/agents/claude-code/CLAUDE.md +32 -32
  145. package/src/templates/agents/claude-code/skills/agent-assistant/SKILL.md +13 -5
  146. package/src/templates/agents/claude-code/skills/ai-ml-engineer/mlops-guide.md +23 -23
  147. package/src/templates/agents/claude-code/skills/ai-ml-engineer/model-card-template.md +60 -41
  148. package/src/templates/agents/claude-code/skills/api-designer/api-patterns.md +27 -19
  149. package/src/templates/agents/claude-code/skills/api-designer/openapi-template.md +11 -7
  150. package/src/templates/agents/claude-code/skills/bug-hunter/SKILL.md +4 -3
  151. package/src/templates/agents/claude-code/skills/bug-hunter/root-cause-analysis.md +37 -15
  152. package/src/templates/agents/claude-code/skills/change-impact-analyzer/dependency-graph-patterns.md +36 -42
  153. package/src/templates/agents/claude-code/skills/change-impact-analyzer/impact-analysis-template.md +69 -60
  154. package/src/templates/agents/claude-code/skills/cloud-architect/aws-patterns.md +31 -38
  155. package/src/templates/agents/claude-code/skills/cloud-architect/azure-patterns.md +28 -23
  156. package/src/templates/agents/claude-code/skills/code-reviewer/SKILL.md +61 -0
  157. package/src/templates/agents/claude-code/skills/code-reviewer/best-practices.md +27 -0
  158. package/src/templates/agents/claude-code/skills/code-reviewer/review-checklist.md +29 -10
  159. package/src/templates/agents/claude-code/skills/code-reviewer/review-standards.md +29 -24
  160. package/src/templates/agents/claude-code/skills/constitution-enforcer/SKILL.md +8 -6
  161. package/src/templates/agents/claude-code/skills/constitution-enforcer/constitutional-articles.md +62 -26
  162. package/src/templates/agents/claude-code/skills/constitution-enforcer/phase-minus-one-gates.md +35 -16
  163. package/src/templates/agents/claude-code/skills/database-administrator/backup-recovery.md +27 -17
  164. package/src/templates/agents/claude-code/skills/database-administrator/tuning-guide.md +25 -20
  165. package/src/templates/agents/claude-code/skills/database-schema-designer/schema-patterns.md +39 -22
  166. package/src/templates/agents/claude-code/skills/devops-engineer/ci-cd-templates.md +25 -22
  167. package/src/templates/agents/claude-code/skills/issue-resolver/SKILL.md +24 -21
  168. package/src/templates/agents/claude-code/skills/orchestrator/SKILL.md +148 -63
  169. package/src/templates/agents/claude-code/skills/orchestrator/patterns.md +35 -16
  170. package/src/templates/agents/claude-code/skills/orchestrator/selection-matrix.md +69 -64
  171. package/src/templates/agents/claude-code/skills/performance-engineer/optimization-playbook.md +47 -47
  172. package/src/templates/agents/claude-code/skills/performance-optimizer/SKILL.md +69 -0
  173. package/src/templates/agents/claude-code/skills/performance-optimizer/benchmark-template.md +63 -45
  174. package/src/templates/agents/claude-code/skills/performance-optimizer/optimization-patterns.md +33 -35
  175. package/src/templates/agents/claude-code/skills/project-manager/SKILL.md +7 -6
  176. package/src/templates/agents/claude-code/skills/project-manager/agile-ceremonies.md +47 -28
  177. package/src/templates/agents/claude-code/skills/project-manager/project-templates.md +94 -78
  178. package/src/templates/agents/claude-code/skills/quality-assurance/SKILL.md +20 -17
  179. package/src/templates/agents/claude-code/skills/quality-assurance/qa-plan-template.md +63 -49
  180. package/src/templates/agents/claude-code/skills/release-coordinator/SKILL.md +5 -5
  181. package/src/templates/agents/claude-code/skills/release-coordinator/feature-flag-guide.md +30 -26
  182. package/src/templates/agents/claude-code/skills/release-coordinator/release-plan-template.md +67 -35
  183. package/src/templates/agents/claude-code/skills/requirements-analyst/ears-format.md +54 -42
  184. package/src/templates/agents/claude-code/skills/requirements-analyst/validation-rules.md +36 -33
  185. package/src/templates/agents/claude-code/skills/security-auditor/SKILL.md +77 -19
  186. package/src/templates/agents/claude-code/skills/security-auditor/audit-checklists.md +24 -24
  187. package/src/templates/agents/claude-code/skills/security-auditor/owasp-top-10.md +61 -20
  188. package/src/templates/agents/claude-code/skills/security-auditor/vulnerability-patterns.md +43 -11
  189. package/src/templates/agents/claude-code/skills/site-reliability-engineer/SKILL.md +1 -0
  190. package/src/templates/agents/claude-code/skills/site-reliability-engineer/incident-response-template.md +55 -25
  191. package/src/templates/agents/claude-code/skills/site-reliability-engineer/observability-patterns.md +78 -68
  192. package/src/templates/agents/claude-code/skills/site-reliability-engineer/slo-sli-guide.md +73 -53
  193. package/src/templates/agents/claude-code/skills/software-developer/solid-principles.md +83 -37
  194. package/src/templates/agents/claude-code/skills/software-developer/test-first-workflow.md +38 -31
  195. package/src/templates/agents/claude-code/skills/steering/SKILL.md +1 -0
  196. package/src/templates/agents/claude-code/skills/steering/auto-update-rules.md +31 -0
  197. package/src/templates/agents/claude-code/skills/system-architect/adr-template.md +25 -7
  198. package/src/templates/agents/claude-code/skills/system-architect/c4-model-guide.md +74 -61
  199. package/src/templates/agents/claude-code/skills/technical-writer/doc-templates/documentation-templates.md +70 -52
  200. package/src/templates/agents/claude-code/skills/test-engineer/SKILL.md +2 -0
  201. package/src/templates/agents/claude-code/skills/test-engineer/ears-test-mapping.md +75 -71
  202. package/src/templates/agents/claude-code/skills/test-engineer/test-types.md +85 -63
  203. package/src/templates/agents/claude-code/skills/traceability-auditor/coverage-matrix-template.md +39 -36
  204. package/src/templates/agents/claude-code/skills/traceability-auditor/gap-detection-rules.md +22 -17
  205. package/src/templates/agents/claude-code/skills/ui-ux-designer/SKILL.md +1 -0
  206. package/src/templates/agents/claude-code/skills/ui-ux-designer/accessibility-guidelines.md +49 -75
  207. package/src/templates/agents/claude-code/skills/ui-ux-designer/design-system-components.md +71 -59
  208. package/src/templates/agents/codex/AGENTS.md +74 -42
  209. package/src/templates/agents/cursor/AGENTS.md +74 -42
  210. package/src/templates/agents/gemini-cli/GEMINI.md +74 -42
  211. package/src/templates/agents/github-copilot/AGENTS.md +83 -51
  212. package/src/templates/agents/qwen-code/QWEN.md +74 -42
  213. package/src/templates/agents/windsurf/AGENTS.md +74 -42
  214. package/src/templates/architectures/README.md +41 -0
  215. package/src/templates/architectures/clean-architecture/README.md +113 -0
  216. package/src/templates/architectures/event-driven/README.md +162 -0
  217. package/src/templates/architectures/hexagonal/README.md +130 -0
  218. package/src/templates/index.js +6 -1
  219. package/src/templates/locale-manager.js +16 -16
  220. package/src/templates/shared/delta-spec-template.md +20 -13
  221. package/src/templates/shared/github-actions/musubi-issue-resolver.yml +5 -5
  222. package/src/templates/shared/github-actions/musubi-security-check.yml +3 -3
  223. package/src/templates/shared/github-actions/musubi-validate.yml +4 -4
  224. package/src/templates/shared/steering/structure.md +95 -0
  225. package/src/templates/skills/browser-agent.md +21 -16
  226. package/src/templates/skills/web-gui.md +8 -0
  227. package/src/templates/template-constraints.js +50 -53
  228. package/src/validators/advanced-validation.js +30 -36
  229. package/src/validators/constitutional-validator.js +77 -73
  230. package/src/validators/critic-system.js +49 -59
  231. package/src/validators/delta-format.js +59 -55
  232. 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
+ };