@eddacraft/anvil-runtime 0.1.0
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/LICENSE +14 -0
- package/dist/cache/cache-key.d.ts +45 -0
- package/dist/cache/cache-key.d.ts.map +1 -0
- package/dist/cache/cache-key.js +135 -0
- package/dist/cache/index.d.ts +27 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +38 -0
- package/dist/cache/providers/file-cache.d.ts +63 -0
- package/dist/cache/providers/file-cache.d.ts.map +1 -0
- package/dist/cache/providers/file-cache.js +369 -0
- package/dist/cache/providers/memory-cache.d.ts +52 -0
- package/dist/cache/providers/memory-cache.d.ts.map +1 -0
- package/dist/cache/providers/memory-cache.js +197 -0
- package/dist/cache/providers/null-cache.d.ts +26 -0
- package/dist/cache/providers/null-cache.d.ts.map +1 -0
- package/dist/cache/providers/null-cache.js +50 -0
- package/dist/cache/types.d.ts +114 -0
- package/dist/cache/types.d.ts.map +1 -0
- package/dist/cache/types.js +4 -0
- package/dist/concurrency/agent.d.ts +137 -0
- package/dist/concurrency/agent.d.ts.map +1 -0
- package/dist/concurrency/agent.js +440 -0
- package/dist/concurrency/atomic.d.ts +93 -0
- package/dist/concurrency/atomic.d.ts.map +1 -0
- package/dist/concurrency/atomic.js +281 -0
- package/dist/concurrency/git-agent.d.ts +114 -0
- package/dist/concurrency/git-agent.d.ts.map +1 -0
- package/dist/concurrency/git-agent.js +313 -0
- package/dist/concurrency/index.d.ts +95 -0
- package/dist/concurrency/index.d.ts.map +1 -0
- package/dist/concurrency/index.js +127 -0
- package/dist/concurrency/lock-manager.d.ts +170 -0
- package/dist/concurrency/lock-manager.d.ts.map +1 -0
- package/dist/concurrency/lock-manager.js +525 -0
- package/dist/concurrency/queue-manager.d.ts +166 -0
- package/dist/concurrency/queue-manager.d.ts.map +1 -0
- package/dist/concurrency/queue-manager.js +442 -0
- package/dist/concurrency/types.d.ts +382 -0
- package/dist/concurrency/types.d.ts.map +1 -0
- package/dist/concurrency/types.js +204 -0
- package/dist/export/constraint-collector.d.ts +175 -0
- package/dist/export/constraint-collector.d.ts.map +1 -0
- package/dist/export/constraint-collector.js +203 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts +89 -0
- package/dist/export/formatters/llms-txt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/llms-txt-formatter.js +249 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts +186 -0
- package/dist/export/formatters/mcp-resource-formatter.d.ts.map +1 -0
- package/dist/export/formatters/mcp-resource-formatter.js +139 -0
- package/dist/export/formatters/prompt-formatter.d.ts +83 -0
- package/dist/export/formatters/prompt-formatter.d.ts.map +1 -0
- package/dist/export/formatters/prompt-formatter.js +256 -0
- package/dist/export/index.d.ts +10 -0
- package/dist/export/index.d.ts.map +1 -0
- package/dist/export/index.js +9 -0
- package/dist/gate/check.interface.d.ts +15 -0
- package/dist/gate/check.interface.d.ts.map +1 -0
- package/dist/gate/check.interface.js +18 -0
- package/dist/gate/checks/antipattern.check.d.ts +27 -0
- package/dist/gate/checks/antipattern.check.d.ts.map +1 -0
- package/dist/gate/checks/antipattern.check.js +140 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts +33 -0
- package/dist/gate/checks/architecture/circular-detector.d.ts.map +1 -0
- package/dist/gate/checks/architecture/circular-detector.js +71 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts +81 -0
- package/dist/gate/checks/architecture/dependency-analyzer.d.ts.map +1 -0
- package/dist/gate/checks/architecture/dependency-analyzer.js +136 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts +75 -0
- package/dist/gate/checks/architecture/layer-validator.d.ts.map +1 -0
- package/dist/gate/checks/architecture/layer-validator.js +193 -0
- package/dist/gate/checks/architecture.check.d.ts +56 -0
- package/dist/gate/checks/architecture.check.d.ts.map +1 -0
- package/dist/gate/checks/architecture.check.js +394 -0
- package/dist/gate/checks/command-safety.check.d.ts +12 -0
- package/dist/gate/checks/command-safety.check.d.ts.map +1 -0
- package/dist/gate/checks/command-safety.check.js +230 -0
- package/dist/gate/checks/coverage.check.d.ts +9 -0
- package/dist/gate/checks/coverage.check.d.ts.map +1 -0
- package/dist/gate/checks/coverage.check.js +81 -0
- package/dist/gate/checks/dependency.check.d.ts +17 -0
- package/dist/gate/checks/dependency.check.d.ts.map +1 -0
- package/dist/gate/checks/dependency.check.js +342 -0
- package/dist/gate/checks/eslint.check.d.ts +14 -0
- package/dist/gate/checks/eslint.check.d.ts.map +1 -0
- package/dist/gate/checks/eslint.check.js +79 -0
- package/dist/gate/checks/policy.check.d.ts +78 -0
- package/dist/gate/checks/policy.check.d.ts.map +1 -0
- package/dist/gate/checks/policy.check.js +457 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts +44 -0
- package/dist/gate/checks/secret/entropy-detector.d.ts.map +1 -0
- package/dist/gate/checks/secret/entropy-detector.js +76 -0
- package/dist/gate/checks/secret/git-scanner.d.ts +36 -0
- package/dist/gate/checks/secret/git-scanner.d.ts.map +1 -0
- package/dist/gate/checks/secret/git-scanner.js +90 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts +42 -0
- package/dist/gate/checks/secret/secret-patterns.d.ts.map +1 -0
- package/dist/gate/checks/secret/secret-patterns.js +137 -0
- package/dist/gate/checks/secret.check.d.ts +56 -0
- package/dist/gate/checks/secret.check.d.ts.map +1 -0
- package/dist/gate/checks/secret.check.js +245 -0
- package/dist/gate/config/command-safety-config.d.ts +5 -0
- package/dist/gate/config/command-safety-config.d.ts.map +1 -0
- package/dist/gate/config/command-safety-config.js +69 -0
- package/dist/gate/config/index.d.ts +2 -0
- package/dist/gate/config/index.d.ts.map +1 -0
- package/dist/gate/config/index.js +1 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts +10 -0
- package/dist/gate/formatters/command-safety-formatter.d.ts.map +1 -0
- package/dist/gate/formatters/command-safety-formatter.js +64 -0
- package/dist/gate/formatters/index.d.ts +2 -0
- package/dist/gate/formatters/index.d.ts.map +1 -0
- package/dist/gate/formatters/index.js +1 -0
- package/dist/gate/gate-config.d.ts +44 -0
- package/dist/gate/gate-config.d.ts.map +1 -0
- package/dist/gate/gate-config.js +334 -0
- package/dist/gate/gate-runner.d.ts +160 -0
- package/dist/gate/gate-runner.d.ts.map +1 -0
- package/dist/gate/gate-runner.js +531 -0
- package/dist/gate/index.d.ts +20 -0
- package/dist/gate/index.d.ts.map +1 -0
- package/dist/gate/index.js +14 -0
- package/dist/gate/parsers/command-parser.d.ts +18 -0
- package/dist/gate/parsers/command-parser.d.ts.map +1 -0
- package/dist/gate/parsers/command-parser.js +363 -0
- package/dist/gate/parsers/index.d.ts +2 -0
- package/dist/gate/parsers/index.d.ts.map +1 -0
- package/dist/gate/parsers/index.js +1 -0
- package/dist/gate/policy/index.d.ts +12 -0
- package/dist/gate/policy/index.d.ts.map +1 -0
- package/dist/gate/policy/index.js +10 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts +3 -0
- package/dist/gate/rules/default-filesystem-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-filesystem-rules.js +201 -0
- package/dist/gate/rules/default-git-rules.d.ts +3 -0
- package/dist/gate/rules/default-git-rules.d.ts.map +1 -0
- package/dist/gate/rules/default-git-rules.js +192 -0
- package/dist/gate/rules/index.d.ts +5 -0
- package/dist/gate/rules/index.d.ts.map +1 -0
- package/dist/gate/rules/index.js +3 -0
- package/dist/gate/rules/rule-matcher.d.ts +27 -0
- package/dist/gate/rules/rule-matcher.d.ts.map +1 -0
- package/dist/gate/rules/rule-matcher.js +228 -0
- package/dist/gate/rules/types.d.ts +250 -0
- package/dist/gate/rules/types.d.ts.map +1 -0
- package/dist/gate/rules/types.js +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +35 -0
- package/dist/types/gate.types.d.ts +42 -0
- package/dist/types/gate.types.d.ts.map +1 -0
- package/dist/types/gate.types.js +94 -0
- package/dist/watch/debouncer.d.ts +90 -0
- package/dist/watch/debouncer.d.ts.map +1 -0
- package/dist/watch/debouncer.js +135 -0
- package/dist/watch/file-watcher.d.ts +73 -0
- package/dist/watch/file-watcher.d.ts.map +1 -0
- package/dist/watch/file-watcher.js +121 -0
- package/dist/watch/git-status.d.ts +98 -0
- package/dist/watch/git-status.d.ts.map +1 -0
- package/dist/watch/git-status.js +266 -0
- package/dist/watch/index.d.ts +16 -0
- package/dist/watch/index.d.ts.map +1 -0
- package/dist/watch/index.js +15 -0
- package/dist/watch/orchestrator.d.ts +113 -0
- package/dist/watch/orchestrator.d.ts.map +1 -0
- package/dist/watch/orchestrator.js +409 -0
- package/dist/watch/types.d.ts +190 -0
- package/dist/watch/types.d.ts.map +1 -0
- package/dist/watch/types.js +76 -0
- package/package.json +60 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Module
|
|
3
|
+
*
|
|
4
|
+
* File watching functionality for real-time validation and gating.
|
|
5
|
+
*/
|
|
6
|
+
export { WatchConfigSchema, WatchGitConfigSchema, parseWatchConfig, getDefaultWatchConfig, DEFAULT_WATCH_PATTERNS, DEFAULT_EXCLUDE_PATTERNS, } from './types.js';
|
|
7
|
+
export type { WatchConfig, WatchGitConfig, GitFileStatus, WatchChangeEvent, DebouncedChanges, WatchStatusEvent, WatchStatusEventType, WatchActionResult, WatchOrchestratorOptions, MultiAgentConfig, } from './types.js';
|
|
8
|
+
export { GitStatusChecker, createGitStatusChecker, getChangedFiles } from './git-status.js';
|
|
9
|
+
export type { GetChangedFilesOptions } from './git-status.js';
|
|
10
|
+
export { ChangeDebouncer, createDebouncer } from './debouncer.js';
|
|
11
|
+
export type { DebouncerFlushCallback } from './debouncer.js';
|
|
12
|
+
export { FileWatcher, createFileWatcher } from './file-watcher.js';
|
|
13
|
+
export type { FileWatcherOptions, FileWatcherEvents } from './file-watcher.js';
|
|
14
|
+
export { WatchOrchestrator, createWatchOrchestrator } from './orchestrator.js';
|
|
15
|
+
export type { ActionHandler } from './orchestrator.js';
|
|
16
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/watch/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,iBAAiB,EACjB,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,sBAAsB,EACtB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AAEpB,YAAY,EACV,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,wBAAwB,EACxB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAC5F,YAAY,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAG9D,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAG7D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACnE,YAAY,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAG/E,OAAO,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAC/E,YAAY,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Module
|
|
3
|
+
*
|
|
4
|
+
* File watching functionality for real-time validation and gating.
|
|
5
|
+
*/
|
|
6
|
+
// Types and schemas
|
|
7
|
+
export { WatchConfigSchema, WatchGitConfigSchema, parseWatchConfig, getDefaultWatchConfig, DEFAULT_WATCH_PATTERNS, DEFAULT_EXCLUDE_PATTERNS, } from './types.js';
|
|
8
|
+
// Git status checker
|
|
9
|
+
export { GitStatusChecker, createGitStatusChecker, getChangedFiles } from './git-status.js';
|
|
10
|
+
// Debouncer
|
|
11
|
+
export { ChangeDebouncer, createDebouncer } from './debouncer.js';
|
|
12
|
+
// File watcher
|
|
13
|
+
export { FileWatcher, createFileWatcher } from './file-watcher.js';
|
|
14
|
+
// Orchestrator
|
|
15
|
+
export { WatchOrchestrator, createWatchOrchestrator } from './orchestrator.js';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates file watching, git filtering, debouncing, and action dispatch.
|
|
5
|
+
* This is the main entry point for watch mode functionality.
|
|
6
|
+
*
|
|
7
|
+
* Multi-Agent Support:
|
|
8
|
+
* - Acquires workspace-level watch lock to prevent multiple watchers
|
|
9
|
+
* - Coordinates action execution through lock manager
|
|
10
|
+
* - Registers agent and maintains heartbeat
|
|
11
|
+
*/
|
|
12
|
+
import type { WatchOrchestratorOptions, WatchActionResult } from './types.js';
|
|
13
|
+
/**
|
|
14
|
+
* Action handler type
|
|
15
|
+
*/
|
|
16
|
+
export type ActionHandler = (files: string[]) => Promise<WatchActionResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Watch orchestrator
|
|
19
|
+
*
|
|
20
|
+
* Coordinates all watch components and dispatches actions.
|
|
21
|
+
* Supports multi-agent coordination with distributed locking.
|
|
22
|
+
*/
|
|
23
|
+
export declare class WatchOrchestrator {
|
|
24
|
+
private fileWatcher;
|
|
25
|
+
private gitChecker;
|
|
26
|
+
private debouncer;
|
|
27
|
+
private config;
|
|
28
|
+
private workspaceRoot;
|
|
29
|
+
private onEvent?;
|
|
30
|
+
private verbose;
|
|
31
|
+
private isRunning;
|
|
32
|
+
private isGitRepo;
|
|
33
|
+
private multiAgentConfig;
|
|
34
|
+
private concurrencyContext;
|
|
35
|
+
private validateHandler?;
|
|
36
|
+
private gateHandler?;
|
|
37
|
+
private checkHandler?;
|
|
38
|
+
private stats;
|
|
39
|
+
constructor(options: WatchOrchestratorOptions);
|
|
40
|
+
/**
|
|
41
|
+
* Set the validate action handler
|
|
42
|
+
*/
|
|
43
|
+
setValidateHandler(handler: ActionHandler): void;
|
|
44
|
+
/**
|
|
45
|
+
* Set the gate action handler
|
|
46
|
+
*/
|
|
47
|
+
setGateHandler(handler: ActionHandler): void;
|
|
48
|
+
/**
|
|
49
|
+
* Set the check action handler (for source file analysis)
|
|
50
|
+
*/
|
|
51
|
+
setCheckHandler(handler: ActionHandler): void;
|
|
52
|
+
/**
|
|
53
|
+
* Start watching
|
|
54
|
+
*/
|
|
55
|
+
start(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Initialize multi-agent coordination
|
|
58
|
+
*/
|
|
59
|
+
private initializeMultiAgent;
|
|
60
|
+
/**
|
|
61
|
+
* Stop watching
|
|
62
|
+
*/
|
|
63
|
+
stop(): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Get the agent ID (if multi-agent is enabled)
|
|
66
|
+
*/
|
|
67
|
+
getAgentId(): string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* Get current statistics
|
|
70
|
+
*/
|
|
71
|
+
getStats(): typeof this.stats;
|
|
72
|
+
/**
|
|
73
|
+
* Check if orchestrator is running
|
|
74
|
+
*/
|
|
75
|
+
get running(): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Handle file change event from watcher
|
|
78
|
+
*/
|
|
79
|
+
private handleFileChange;
|
|
80
|
+
/**
|
|
81
|
+
* Handle debounced batch of changes
|
|
82
|
+
*/
|
|
83
|
+
private handleDebouncedChanges;
|
|
84
|
+
/**
|
|
85
|
+
* Run the configured action
|
|
86
|
+
*/
|
|
87
|
+
private runAction;
|
|
88
|
+
/**
|
|
89
|
+
* Run action with multi-agent coordination (locking/queuing)
|
|
90
|
+
*/
|
|
91
|
+
private runCoordinatedAction;
|
|
92
|
+
/**
|
|
93
|
+
* Run action directly without coordination
|
|
94
|
+
*/
|
|
95
|
+
private runDirectAction;
|
|
96
|
+
/**
|
|
97
|
+
* Handle watcher ready event
|
|
98
|
+
*/
|
|
99
|
+
private handleReady;
|
|
100
|
+
/**
|
|
101
|
+
* Handle watcher error
|
|
102
|
+
*/
|
|
103
|
+
private handleError;
|
|
104
|
+
/**
|
|
105
|
+
* Emit status event
|
|
106
|
+
*/
|
|
107
|
+
private emitEvent;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Create a watch orchestrator
|
|
111
|
+
*/
|
|
112
|
+
export declare function createWatchOrchestrator(options: WatchOrchestratorOptions): WatchOrchestrator;
|
|
113
|
+
//# sourceMappingURL=orchestrator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orchestrator.d.ts","sourceRoot":"","sources":["../../src/watch/orchestrator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAKH,OAAO,KAAK,EAEV,wBAAwB,EAExB,iBAAiB,EAIlB,MAAM,YAAY,CAAC;AAUpB;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAc5E;;;;;GAKG;AACH,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,SAAS,CAAkB;IACnC,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAC,CAAoC;IACpD,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAS;IAG1B,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,kBAAkB,CAAmC;IAG7D,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,WAAW,CAAC,CAAgB;IACpC,OAAO,CAAC,YAAY,CAAC,CAAgB;IAGrC,OAAO,CAAC,KAAK,CAQX;gBAEU,OAAO,EAAE,wBAAwB;IAkB7C;;OAEG;IACH,kBAAkB,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAIhD;;OAEG;IACH,cAAc,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAI5C;;OAEG;IACH,eAAe,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI;IAI7C;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC5B;;OAEG;YACW,oBAAoB;IAyElC;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAoB3B;;OAEG;IACH,UAAU,IAAI,MAAM,GAAG,SAAS;IAIhC;;OAEG;IACH,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK;IAI7B;;OAEG;IACH,IAAI,OAAO,IAAI,OAAO,CAErB;IAED;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAYxB;;OAEG;YACW,sBAAsB;IAiCpC;;OAEG;YACW,SAAS;IAkCvB;;OAEG;YACW,oBAAoB;IAgDlC;;OAEG;YACW,eAAe;IA8C7B;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;OAEG;IACH,OAAO,CAAC,WAAW;IAInB;;OAEG;IACH,OAAO,CAAC,SAAS;CAGlB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,iBAAiB,CAE5F"}
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates file watching, git filtering, debouncing, and action dispatch.
|
|
5
|
+
* This is the main entry point for watch mode functionality.
|
|
6
|
+
*
|
|
7
|
+
* Multi-Agent Support:
|
|
8
|
+
* - Acquires workspace-level watch lock to prevent multiple watchers
|
|
9
|
+
* - Coordinates action execution through lock manager
|
|
10
|
+
* - Registers agent and maintains heartbeat
|
|
11
|
+
*/
|
|
12
|
+
import { FileWatcher } from './file-watcher.js';
|
|
13
|
+
import { GitStatusChecker } from './git-status.js';
|
|
14
|
+
import { ChangeDebouncer } from './debouncer.js';
|
|
15
|
+
import { createConcurrencyContext, createAgentInfo, } from '../concurrency/index.js';
|
|
16
|
+
import { createDebugger } from '@eddacraft/anvil-core';
|
|
17
|
+
const debug = createDebugger('watch');
|
|
18
|
+
/**
|
|
19
|
+
* Default multi-agent configuration
|
|
20
|
+
*/
|
|
21
|
+
const DEFAULT_MULTI_AGENT_CONFIG = {
|
|
22
|
+
enabled: true,
|
|
23
|
+
exclusiveWatch: true,
|
|
24
|
+
coordinatedActions: true,
|
|
25
|
+
agentId: '',
|
|
26
|
+
waitForLock: true,
|
|
27
|
+
lockWaitTimeoutMs: 30000,
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* Watch orchestrator
|
|
31
|
+
*
|
|
32
|
+
* Coordinates all watch components and dispatches actions.
|
|
33
|
+
* Supports multi-agent coordination with distributed locking.
|
|
34
|
+
*/
|
|
35
|
+
export class WatchOrchestrator {
|
|
36
|
+
fileWatcher;
|
|
37
|
+
gitChecker;
|
|
38
|
+
debouncer;
|
|
39
|
+
config;
|
|
40
|
+
workspaceRoot;
|
|
41
|
+
onEvent;
|
|
42
|
+
verbose;
|
|
43
|
+
isRunning = false;
|
|
44
|
+
isGitRepo = false;
|
|
45
|
+
// Multi-agent support
|
|
46
|
+
multiAgentConfig;
|
|
47
|
+
concurrencyContext = null;
|
|
48
|
+
// Action handlers
|
|
49
|
+
validateHandler;
|
|
50
|
+
gateHandler;
|
|
51
|
+
checkHandler;
|
|
52
|
+
// Statistics
|
|
53
|
+
stats = {
|
|
54
|
+
changesDetected: 0,
|
|
55
|
+
changesFiltered: 0,
|
|
56
|
+
actionsRun: 0,
|
|
57
|
+
actionsPassed: 0,
|
|
58
|
+
actionsFailed: 0,
|
|
59
|
+
actionsQueued: 0,
|
|
60
|
+
lockWaits: 0,
|
|
61
|
+
};
|
|
62
|
+
constructor(options) {
|
|
63
|
+
this.workspaceRoot = options.workspaceRoot;
|
|
64
|
+
this.config = options.config;
|
|
65
|
+
this.onEvent = options.onEvent;
|
|
66
|
+
this.verbose = options.verbose ?? false;
|
|
67
|
+
this.multiAgentConfig = {
|
|
68
|
+
...DEFAULT_MULTI_AGENT_CONFIG,
|
|
69
|
+
...options.multiAgent,
|
|
70
|
+
};
|
|
71
|
+
this.fileWatcher = new FileWatcher();
|
|
72
|
+
this.gitChecker = new GitStatusChecker(options.workspaceRoot);
|
|
73
|
+
this.debouncer = new ChangeDebouncer(options.config.debounceMs, this.handleDebouncedChanges.bind(this));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Set the validate action handler
|
|
77
|
+
*/
|
|
78
|
+
setValidateHandler(handler) {
|
|
79
|
+
this.validateHandler = handler;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Set the gate action handler
|
|
83
|
+
*/
|
|
84
|
+
setGateHandler(handler) {
|
|
85
|
+
this.gateHandler = handler;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Set the check action handler (for source file analysis)
|
|
89
|
+
*/
|
|
90
|
+
setCheckHandler(handler) {
|
|
91
|
+
this.checkHandler = handler;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Start watching
|
|
95
|
+
*/
|
|
96
|
+
async start() {
|
|
97
|
+
debug(`orchestrator start`, { workspace: this.workspaceRoot, action: this.config.action });
|
|
98
|
+
if (this.isRunning) {
|
|
99
|
+
throw new Error('Watch orchestrator is already running');
|
|
100
|
+
}
|
|
101
|
+
// Initialize multi-agent coordination if enabled
|
|
102
|
+
if (this.multiAgentConfig.enabled) {
|
|
103
|
+
debug('orchestrator start: initializing multi-agent coordination');
|
|
104
|
+
await this.initializeMultiAgent();
|
|
105
|
+
}
|
|
106
|
+
// Check if we're in a git repository
|
|
107
|
+
this.isGitRepo = await this.gitChecker.isGitRepository();
|
|
108
|
+
debug(`orchestrator start: isGitRepo=${this.isGitRepo}`);
|
|
109
|
+
if (!this.isGitRepo && this.config.git.unstagedOnly) {
|
|
110
|
+
console.warn('Warning: Not a git repository. Git filtering disabled, watching all file changes.');
|
|
111
|
+
}
|
|
112
|
+
// Set up file watcher events
|
|
113
|
+
this.fileWatcher.on('change', this.handleFileChange.bind(this));
|
|
114
|
+
this.fileWatcher.on('error', this.handleError.bind(this));
|
|
115
|
+
this.fileWatcher.on('ready', this.handleReady.bind(this));
|
|
116
|
+
// Start watching
|
|
117
|
+
await this.fileWatcher.start({
|
|
118
|
+
patterns: this.config.patterns,
|
|
119
|
+
exclude: this.config.exclude,
|
|
120
|
+
cwd: this.workspaceRoot,
|
|
121
|
+
});
|
|
122
|
+
this.isRunning = true;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Initialize multi-agent coordination
|
|
126
|
+
*/
|
|
127
|
+
async initializeMultiAgent() {
|
|
128
|
+
try {
|
|
129
|
+
// Create concurrency context, honouring custom agent ID if provided
|
|
130
|
+
this.concurrencyContext = await createConcurrencyContext({
|
|
131
|
+
workspaceRoot: this.workspaceRoot,
|
|
132
|
+
autoRegister: true,
|
|
133
|
+
autoHeartbeat: true,
|
|
134
|
+
...(this.multiAgentConfig.agentId
|
|
135
|
+
? { agentInfo: createAgentInfo({ id: this.multiAgentConfig.agentId }) }
|
|
136
|
+
: {}),
|
|
137
|
+
});
|
|
138
|
+
// Acquire exclusive watch lock if configured
|
|
139
|
+
if (this.multiAgentConfig.exclusiveWatch) {
|
|
140
|
+
const lockResult = await this.concurrencyContext.queue.waitForLock({
|
|
141
|
+
type: 'watch',
|
|
142
|
+
resource: 'workspace',
|
|
143
|
+
reason: 'Starting file watch mode',
|
|
144
|
+
maxWaitMs: this.multiAgentConfig.waitForLock
|
|
145
|
+
? this.multiAgentConfig.lockWaitTimeoutMs
|
|
146
|
+
: 0,
|
|
147
|
+
onPositionChange: (position, _total) => {
|
|
148
|
+
this.stats.lockWaits++;
|
|
149
|
+
this.emitEvent({
|
|
150
|
+
type: 'lock:waiting',
|
|
151
|
+
resource: 'watch:workspace',
|
|
152
|
+
heldBy: 'another-agent',
|
|
153
|
+
queuePosition: position,
|
|
154
|
+
});
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
if (!lockResult.acquired) {
|
|
158
|
+
// Clean up context
|
|
159
|
+
await this.concurrencyContext.cleanup();
|
|
160
|
+
this.concurrencyContext = null;
|
|
161
|
+
const error = `Cannot start watch mode: ${lockResult.error || 'Lock held by another agent'}`;
|
|
162
|
+
if (lockResult.heldBy) {
|
|
163
|
+
this.emitEvent({
|
|
164
|
+
type: 'lock:denied',
|
|
165
|
+
resource: 'watch:workspace',
|
|
166
|
+
heldBy: lockResult.heldBy.agentId,
|
|
167
|
+
reason: `Lock expires at ${lockResult.heldBy.expiresAt}`,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
throw new Error(error);
|
|
171
|
+
}
|
|
172
|
+
// Start auto-renewal for watch lock
|
|
173
|
+
this.concurrencyContext.locks.startAutoRenewal('watch', 'workspace');
|
|
174
|
+
this.emitEvent({
|
|
175
|
+
type: 'lock:acquired',
|
|
176
|
+
resource: 'watch:workspace',
|
|
177
|
+
agentId: this.concurrencyContext.agent.getAgentId(),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
// Update agent operation
|
|
181
|
+
await this.concurrencyContext.agent.setOperation('watching');
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
// Clean up on error
|
|
185
|
+
if (this.concurrencyContext) {
|
|
186
|
+
await this.concurrencyContext.cleanup();
|
|
187
|
+
this.concurrencyContext = null;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Stop watching
|
|
194
|
+
*/
|
|
195
|
+
async stop() {
|
|
196
|
+
debug(`orchestrator stop: running=${this.isRunning}`);
|
|
197
|
+
if (!this.isRunning) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.debouncer.cancel();
|
|
201
|
+
await this.fileWatcher.stop();
|
|
202
|
+
// Clean up multi-agent coordination
|
|
203
|
+
if (this.concurrencyContext) {
|
|
204
|
+
await this.concurrencyContext.cleanup();
|
|
205
|
+
this.concurrencyContext = null;
|
|
206
|
+
}
|
|
207
|
+
this.isRunning = false;
|
|
208
|
+
this.emitEvent({ type: 'stopped' });
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get the agent ID (if multi-agent is enabled)
|
|
212
|
+
*/
|
|
213
|
+
getAgentId() {
|
|
214
|
+
return this.concurrencyContext?.agent.getAgentId();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Get current statistics
|
|
218
|
+
*/
|
|
219
|
+
getStats() {
|
|
220
|
+
return { ...this.stats };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Check if orchestrator is running
|
|
224
|
+
*/
|
|
225
|
+
get running() {
|
|
226
|
+
return this.isRunning;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Handle file change event from watcher
|
|
230
|
+
*/
|
|
231
|
+
handleFileChange(event) {
|
|
232
|
+
// Only process add and change events (not unlink)
|
|
233
|
+
if (event.type === 'unlink') {
|
|
234
|
+
debug(`orchestrator: ignoring unlink event for ${event.path}`);
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
debug(`orchestrator: file ${event.type} ${event.path}`);
|
|
238
|
+
this.stats.changesDetected++;
|
|
239
|
+
this.debouncer.add(event.path);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Handle debounced batch of changes
|
|
243
|
+
*/
|
|
244
|
+
async handleDebouncedChanges(changes) {
|
|
245
|
+
debug(`orchestrator: debounced batch of ${changes.files.length} files`);
|
|
246
|
+
let filesToProcess = changes.files;
|
|
247
|
+
// Apply git filter if enabled and in git repo
|
|
248
|
+
if (this.isGitRepo && this.config.git.unstagedOnly) {
|
|
249
|
+
filesToProcess = await this.gitChecker.filterUnstaged(changes.files, this.config.git.includeUntracked);
|
|
250
|
+
}
|
|
251
|
+
this.stats.changesFiltered += changes.files.length - filesToProcess.length;
|
|
252
|
+
// Emit change event
|
|
253
|
+
this.emitEvent({
|
|
254
|
+
type: 'change',
|
|
255
|
+
files: changes.files,
|
|
256
|
+
filtered: filesToProcess,
|
|
257
|
+
});
|
|
258
|
+
// Skip if all files were filtered out
|
|
259
|
+
if (filesToProcess.length === 0) {
|
|
260
|
+
if (this.verbose) {
|
|
261
|
+
console.warn('All changed files were filtered out (staged or excluded)');
|
|
262
|
+
}
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
// Run the configured action
|
|
266
|
+
await this.runAction(filesToProcess);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Run the configured action
|
|
270
|
+
*/
|
|
271
|
+
async runAction(files) {
|
|
272
|
+
const action = this.config.action;
|
|
273
|
+
debug(`orchestrator runAction`, { action, files: files.length });
|
|
274
|
+
let handler;
|
|
275
|
+
switch (action) {
|
|
276
|
+
case 'validate':
|
|
277
|
+
handler = this.validateHandler;
|
|
278
|
+
break;
|
|
279
|
+
case 'gate':
|
|
280
|
+
handler = this.gateHandler;
|
|
281
|
+
break;
|
|
282
|
+
case 'check':
|
|
283
|
+
handler = this.checkHandler;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
if (!handler) {
|
|
287
|
+
console.warn(`No handler registered for action: ${action}`);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
// Use coordinated action execution if enabled
|
|
291
|
+
if (this.multiAgentConfig.enabled &&
|
|
292
|
+
this.multiAgentConfig.coordinatedActions &&
|
|
293
|
+
this.concurrencyContext) {
|
|
294
|
+
await this.runCoordinatedAction(action, handler, files);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
await this.runDirectAction(action, handler, files);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Run action with multi-agent coordination (locking/queuing)
|
|
302
|
+
*/
|
|
303
|
+
async runCoordinatedAction(action, handler, files) {
|
|
304
|
+
if (!this.concurrencyContext) {
|
|
305
|
+
return this.runDirectAction(action, handler, files);
|
|
306
|
+
}
|
|
307
|
+
const resource = `action:${action}`;
|
|
308
|
+
// Try to acquire action lock with queuing
|
|
309
|
+
const lockResult = await this.concurrencyContext.queue.waitForLock({
|
|
310
|
+
type: 'action',
|
|
311
|
+
resource,
|
|
312
|
+
reason: `Running ${action} on ${files.length} files`,
|
|
313
|
+
maxWaitMs: 60000, // Wait up to 60s for action lock
|
|
314
|
+
onPositionChange: (position, _total) => {
|
|
315
|
+
this.stats.actionsQueued++;
|
|
316
|
+
this.emitEvent({
|
|
317
|
+
type: 'action:queued',
|
|
318
|
+
action,
|
|
319
|
+
position,
|
|
320
|
+
files,
|
|
321
|
+
});
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
if (!lockResult.acquired) {
|
|
325
|
+
this.emitEvent({
|
|
326
|
+
type: 'action:error',
|
|
327
|
+
error: new Error(`Could not acquire action lock: ${lockResult.error}`),
|
|
328
|
+
files,
|
|
329
|
+
});
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
// Update agent operation
|
|
334
|
+
await this.concurrencyContext.agent.setOperation(`running:${action}`);
|
|
335
|
+
await this.runDirectAction(action, handler, files);
|
|
336
|
+
}
|
|
337
|
+
finally {
|
|
338
|
+
await this.concurrencyContext.locks.release('action', resource);
|
|
339
|
+
await this.concurrencyContext.agent.setOperation('watching');
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Run action directly without coordination
|
|
344
|
+
*/
|
|
345
|
+
async runDirectAction(action, handler, files) {
|
|
346
|
+
debug(`orchestrator runDirectAction`, { action, files: files.length });
|
|
347
|
+
this.stats.actionsRun++;
|
|
348
|
+
this.emitEvent({
|
|
349
|
+
type: 'action:start',
|
|
350
|
+
action,
|
|
351
|
+
files,
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
const startTime = Date.now();
|
|
355
|
+
const result = await handler(files);
|
|
356
|
+
result.executionTimeMs = Date.now() - startTime;
|
|
357
|
+
if (result.success) {
|
|
358
|
+
this.stats.actionsPassed++;
|
|
359
|
+
debug(`orchestrator action passed`, { action, elapsed: result.executionTimeMs });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
this.stats.actionsFailed++;
|
|
363
|
+
debug(`orchestrator action failed`, { action, elapsed: result.executionTimeMs });
|
|
364
|
+
}
|
|
365
|
+
this.emitEvent({
|
|
366
|
+
type: 'action:complete',
|
|
367
|
+
result,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
catch (error) {
|
|
371
|
+
this.stats.actionsFailed++;
|
|
372
|
+
debug(`orchestrator action error: action=${action}`, error instanceof Error ? error : String(error));
|
|
373
|
+
this.emitEvent({
|
|
374
|
+
type: 'action:error',
|
|
375
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
376
|
+
files,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Handle watcher ready event
|
|
382
|
+
*/
|
|
383
|
+
handleReady() {
|
|
384
|
+
this.emitEvent({
|
|
385
|
+
type: 'ready',
|
|
386
|
+
patterns: this.config.patterns,
|
|
387
|
+
gitFilter: this.isGitRepo && this.config.git.unstagedOnly,
|
|
388
|
+
agentId: this.concurrencyContext?.agent.getAgentId(),
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Handle watcher error
|
|
393
|
+
*/
|
|
394
|
+
handleError(error) {
|
|
395
|
+
console.error('Watch error:', error.message);
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Emit status event
|
|
399
|
+
*/
|
|
400
|
+
emitEvent(event) {
|
|
401
|
+
this.onEvent?.(event);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* Create a watch orchestrator
|
|
406
|
+
*/
|
|
407
|
+
export function createWatchOrchestrator(options) {
|
|
408
|
+
return new WatchOrchestrator(options);
|
|
409
|
+
}
|