@renseiai/agentfactory 0.8.7 → 0.8.8

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 (119) hide show
  1. package/dist/src/config/repository-config.d.ts +14 -0
  2. package/dist/src/config/repository-config.d.ts.map +1 -1
  3. package/dist/src/config/repository-config.js +20 -0
  4. package/dist/src/governor/event-types.d.ts +18 -1
  5. package/dist/src/governor/event-types.d.ts.map +1 -1
  6. package/dist/src/governor/event-types.js +4 -0
  7. package/dist/src/merge-queue/adapters/github-native.d.ts +22 -0
  8. package/dist/src/merge-queue/adapters/github-native.d.ts.map +1 -0
  9. package/dist/src/merge-queue/adapters/github-native.js +243 -0
  10. package/dist/src/merge-queue/adapters/github-native.test.d.ts +2 -0
  11. package/dist/src/merge-queue/adapters/github-native.test.d.ts.map +1 -0
  12. package/dist/src/merge-queue/adapters/github-native.test.js +384 -0
  13. package/dist/src/merge-queue/index.d.ts +18 -0
  14. package/dist/src/merge-queue/index.d.ts.map +1 -0
  15. package/dist/src/merge-queue/index.js +28 -0
  16. package/dist/src/merge-queue/merge-queue.integration.test.d.ts +2 -0
  17. package/dist/src/merge-queue/merge-queue.integration.test.d.ts.map +1 -0
  18. package/dist/src/merge-queue/merge-queue.integration.test.js +128 -0
  19. package/dist/src/merge-queue/types.d.ts +48 -0
  20. package/dist/src/merge-queue/types.d.ts.map +1 -0
  21. package/dist/src/merge-queue/types.js +8 -0
  22. package/dist/src/orchestrator/artifact-tracker.d.ts +93 -0
  23. package/dist/src/orchestrator/artifact-tracker.d.ts.map +1 -0
  24. package/dist/src/orchestrator/artifact-tracker.js +235 -0
  25. package/dist/src/orchestrator/artifact-tracker.test.d.ts +2 -0
  26. package/dist/src/orchestrator/artifact-tracker.test.d.ts.map +1 -0
  27. package/dist/src/orchestrator/artifact-tracker.test.js +189 -0
  28. package/dist/src/orchestrator/context-manager.d.ts +72 -0
  29. package/dist/src/orchestrator/context-manager.d.ts.map +1 -0
  30. package/dist/src/orchestrator/context-manager.js +120 -0
  31. package/dist/src/orchestrator/context-manager.test.d.ts +2 -0
  32. package/dist/src/orchestrator/context-manager.test.d.ts.map +1 -0
  33. package/dist/src/orchestrator/context-manager.test.js +137 -0
  34. package/dist/src/orchestrator/index.d.ts +8 -2
  35. package/dist/src/orchestrator/index.d.ts.map +1 -1
  36. package/dist/src/orchestrator/index.js +8 -1
  37. package/dist/src/orchestrator/orchestrator.d.ts +12 -0
  38. package/dist/src/orchestrator/orchestrator.d.ts.map +1 -1
  39. package/dist/src/orchestrator/orchestrator.js +258 -2
  40. package/dist/src/orchestrator/state-recovery.d.ts +21 -2
  41. package/dist/src/orchestrator/state-recovery.d.ts.map +1 -1
  42. package/dist/src/orchestrator/state-recovery.js +54 -2
  43. package/dist/src/orchestrator/state-recovery.test.js +106 -2
  44. package/dist/src/orchestrator/state-types.d.ts +62 -0
  45. package/dist/src/orchestrator/state-types.d.ts.map +1 -1
  46. package/dist/src/orchestrator/state-types.js +5 -1
  47. package/dist/src/orchestrator/summary-builder.d.ts +47 -0
  48. package/dist/src/orchestrator/summary-builder.d.ts.map +1 -0
  49. package/dist/src/orchestrator/summary-builder.js +240 -0
  50. package/dist/src/orchestrator/summary-builder.test.d.ts +2 -0
  51. package/dist/src/orchestrator/summary-builder.test.d.ts.map +1 -0
  52. package/dist/src/orchestrator/summary-builder.test.js +236 -0
  53. package/dist/src/orchestrator/types.d.ts +2 -0
  54. package/dist/src/orchestrator/types.d.ts.map +1 -1
  55. package/dist/src/orchestrator/work-types.d.ts +1 -1
  56. package/dist/src/orchestrator/work-types.d.ts.map +1 -1
  57. package/dist/src/templates/registry.test.js +2 -2
  58. package/dist/src/templates/types.d.ts +2 -0
  59. package/dist/src/templates/types.d.ts.map +1 -1
  60. package/dist/src/templates/types.js +1 -0
  61. package/dist/src/workflow/branching-router.d.ts +38 -0
  62. package/dist/src/workflow/branching-router.d.ts.map +1 -0
  63. package/dist/src/workflow/branching-router.js +52 -0
  64. package/dist/src/workflow/branching-router.test.d.ts +2 -0
  65. package/dist/src/workflow/branching-router.test.d.ts.map +1 -0
  66. package/dist/src/workflow/branching-router.test.js +209 -0
  67. package/dist/src/workflow/duration.d.ts +28 -0
  68. package/dist/src/workflow/duration.d.ts.map +1 -0
  69. package/dist/src/workflow/duration.js +57 -0
  70. package/dist/src/workflow/duration.test.d.ts +2 -0
  71. package/dist/src/workflow/duration.test.d.ts.map +1 -0
  72. package/dist/src/workflow/duration.test.js +74 -0
  73. package/dist/src/workflow/expression/ast.d.ts +53 -0
  74. package/dist/src/workflow/expression/ast.d.ts.map +1 -0
  75. package/dist/src/workflow/expression/ast.js +8 -0
  76. package/dist/src/workflow/expression/context.d.ts +40 -0
  77. package/dist/src/workflow/expression/context.d.ts.map +1 -0
  78. package/dist/src/workflow/expression/context.js +37 -0
  79. package/dist/src/workflow/expression/evaluator.d.ts +28 -0
  80. package/dist/src/workflow/expression/evaluator.d.ts.map +1 -0
  81. package/dist/src/workflow/expression/evaluator.js +165 -0
  82. package/dist/src/workflow/expression/evaluator.test.d.ts +2 -0
  83. package/dist/src/workflow/expression/evaluator.test.d.ts.map +1 -0
  84. package/dist/src/workflow/expression/evaluator.test.js +792 -0
  85. package/dist/src/workflow/expression/expression.test.d.ts +2 -0
  86. package/dist/src/workflow/expression/expression.test.d.ts.map +1 -0
  87. package/dist/src/workflow/expression/expression.test.js +516 -0
  88. package/dist/src/workflow/expression/helpers.d.ts +21 -0
  89. package/dist/src/workflow/expression/helpers.d.ts.map +1 -0
  90. package/dist/src/workflow/expression/helpers.js +56 -0
  91. package/dist/src/workflow/expression/index.d.ts +55 -0
  92. package/dist/src/workflow/expression/index.d.ts.map +1 -0
  93. package/dist/src/workflow/expression/index.js +71 -0
  94. package/dist/src/workflow/expression/lexer.d.ts +37 -0
  95. package/dist/src/workflow/expression/lexer.d.ts.map +1 -0
  96. package/dist/src/workflow/expression/lexer.js +166 -0
  97. package/dist/src/workflow/expression/parser.d.ts +23 -0
  98. package/dist/src/workflow/expression/parser.d.ts.map +1 -0
  99. package/dist/src/workflow/expression/parser.js +181 -0
  100. package/dist/src/workflow/index.d.ts +10 -3
  101. package/dist/src/workflow/index.d.ts.map +1 -1
  102. package/dist/src/workflow/index.js +6 -1
  103. package/dist/src/workflow/retry-resolver.d.ts +51 -0
  104. package/dist/src/workflow/retry-resolver.d.ts.map +1 -0
  105. package/dist/src/workflow/retry-resolver.js +70 -0
  106. package/dist/src/workflow/retry-resolver.test.d.ts +2 -0
  107. package/dist/src/workflow/retry-resolver.test.d.ts.map +1 -0
  108. package/dist/src/workflow/retry-resolver.test.js +149 -0
  109. package/dist/src/workflow/transition-engine.d.ts +3 -1
  110. package/dist/src/workflow/transition-engine.d.ts.map +1 -1
  111. package/dist/src/workflow/transition-engine.js +14 -7
  112. package/dist/src/workflow/transition-engine.test.js +123 -11
  113. package/dist/src/workflow/workflow-registry.d.ts +41 -0
  114. package/dist/src/workflow/workflow-registry.d.ts.map +1 -1
  115. package/dist/src/workflow/workflow-registry.js +66 -0
  116. package/dist/src/workflow/workflow-types.d.ts +181 -8
  117. package/dist/src/workflow/workflow-types.d.ts.map +1 -1
  118. package/dist/src/workflow/workflow-types.js +31 -6
  119. package/package.json +2 -2
@@ -0,0 +1,93 @@
1
+ import type { AgentEvent } from '../providers/types.js';
2
+ /**
3
+ * Extended file action type for the artifact tracker.
4
+ * Includes 'searched' beyond what StructuredSummary uses.
5
+ */
6
+ export type TrackedFileAction = 'read' | 'created' | 'modified' | 'deleted' | 'searched';
7
+ /**
8
+ * A tracked file with its operation history
9
+ */
10
+ export interface TrackedFile {
11
+ /** Absolute file path */
12
+ path: string;
13
+ /** Path relative to worktree root */
14
+ relativePath: string;
15
+ /** Chronological list of operations performed */
16
+ actions: TrackedFileAction[];
17
+ /** Unix timestamp when file was first encountered */
18
+ firstSeenAt: number;
19
+ /** Unix timestamp of most recent operation */
20
+ lastTouchedAt: number;
21
+ /** Number of lines read (from Read tool) */
22
+ linesRead?: number;
23
+ /** Number of lines modified (from Edit/Write tools) */
24
+ linesModified?: number;
25
+ }
26
+ /**
27
+ * The full artifact index
28
+ */
29
+ export interface ArtifactIndex {
30
+ /** Map of absolute path to tracked file info */
31
+ files: Record<string, TrackedFile>;
32
+ /** Total read operations performed */
33
+ totalReads: number;
34
+ /** Total write operations performed */
35
+ totalWrites: number;
36
+ /** Unix timestamp of last index update */
37
+ lastUpdatedAt: number;
38
+ }
39
+ /**
40
+ * Tracks files read/modified during an agent session.
41
+ * Parses tool_use events to extract file operations and maintains
42
+ * a persistent index for context window management.
43
+ */
44
+ export declare class ArtifactTracker {
45
+ private index;
46
+ private worktreeRoot;
47
+ constructor(worktreeRoot: string, existingIndex?: ArtifactIndex);
48
+ /**
49
+ * Process an agent event to extract file operations.
50
+ * Only processes tool_use events (not tool_result).
51
+ */
52
+ trackEvent(event: AgentEvent): void;
53
+ /**
54
+ * Process a tool_use event to extract file operations
55
+ */
56
+ private trackToolUseEvent;
57
+ /**
58
+ * Parse bash commands for file operation heuristics
59
+ */
60
+ private parseBashCommand;
61
+ /**
62
+ * Record a file action in the index
63
+ */
64
+ private recordAction;
65
+ /**
66
+ * Get the current artifact index
67
+ */
68
+ getIndex(): ArtifactIndex;
69
+ /**
70
+ * Get files matching optional filters
71
+ */
72
+ getFiles(filter?: {
73
+ action?: TrackedFileAction;
74
+ glob?: string;
75
+ }): TrackedFile[];
76
+ /**
77
+ * Persist the index to .agent/artifacts.json atomically
78
+ */
79
+ persist(): void;
80
+ /**
81
+ * Load a persisted artifact index from .agent/artifacts.json
82
+ */
83
+ static load(worktreeRoot: string): ArtifactTracker;
84
+ /**
85
+ * Generate a compact summary string for context injection
86
+ */
87
+ toContextString(): string;
88
+ /**
89
+ * Summarize action counts for a file
90
+ */
91
+ private summarizeActions;
92
+ }
93
+ //# sourceMappingURL=artifact-tracker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-tracker.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/artifact-tracker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAqB,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAG1E;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,SAAS,GAAG,UAAU,CAAA;AAExF;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,YAAY,EAAE,MAAM,CAAA;IACpB,iDAAiD;IACjD,OAAO,EAAE,iBAAiB,EAAE,CAAA;IAC5B,qDAAqD;IACrD,WAAW,EAAE,MAAM,CAAA;IACnB,8CAA8C;IAC9C,aAAa,EAAE,MAAM,CAAA;IACrB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,uDAAuD;IACvD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IAClC,sCAAsC;IACtC,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAA;IACnB,0CAA0C;IAC1C,aAAa,EAAE,MAAM,CAAA;CACtB;AAYD;;;;GAIG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAAe;IAC5B,OAAO,CAAC,YAAY,CAAQ;gBAEhB,YAAY,EAAE,MAAM,EAAE,aAAa,CAAC,EAAE,aAAa;IAU/D;;;OAGG;IACH,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAKnC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkDzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAiBxB;;OAEG;IACH,OAAO,CAAC,YAAY;IAoBpB;;OAEG;IACH,QAAQ,IAAI,aAAa;IAIzB;;OAEG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,iBAAiB,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,EAAE;IAyB/E;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,eAAe;IAoBlD;;OAEG;IACH,eAAe,IAAI,MAAM;IAqCzB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CASzB"}
@@ -0,0 +1,235 @@
1
+ import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
2
+ import { resolve, relative } from 'path';
3
+ import { initializeAgentDir, getAgentDir } from './state-recovery.js';
4
+ /**
5
+ * Tracks files read/modified during an agent session.
6
+ * Parses tool_use events to extract file operations and maintains
7
+ * a persistent index for context window management.
8
+ */
9
+ export class ArtifactTracker {
10
+ index;
11
+ worktreeRoot;
12
+ constructor(worktreeRoot, existingIndex) {
13
+ this.worktreeRoot = worktreeRoot;
14
+ this.index = existingIndex ?? {
15
+ files: {},
16
+ totalReads: 0,
17
+ totalWrites: 0,
18
+ lastUpdatedAt: Date.now(),
19
+ };
20
+ }
21
+ /**
22
+ * Process an agent event to extract file operations.
23
+ * Only processes tool_use events (not tool_result).
24
+ */
25
+ trackEvent(event) {
26
+ if (event.type !== 'tool_use')
27
+ return;
28
+ this.trackToolUseEvent(event);
29
+ }
30
+ /**
31
+ * Process a tool_use event to extract file operations
32
+ */
33
+ trackToolUseEvent(event) {
34
+ const { toolName, input } = event;
35
+ const now = Date.now();
36
+ switch (toolName) {
37
+ case 'Read': {
38
+ const filePath = input.file_path;
39
+ if (filePath) {
40
+ this.recordAction(filePath, 'read', now);
41
+ this.index.totalReads++;
42
+ }
43
+ break;
44
+ }
45
+ case 'Write': {
46
+ const filePath = input.file_path;
47
+ if (filePath) {
48
+ // Check if file exists to determine created vs modified
49
+ const action = existsSync(filePath) ? 'modified' : 'created';
50
+ this.recordAction(filePath, action, now);
51
+ this.index.totalWrites++;
52
+ }
53
+ break;
54
+ }
55
+ case 'Edit': {
56
+ const filePath = input.file_path;
57
+ if (filePath) {
58
+ this.recordAction(filePath, 'modified', now);
59
+ this.index.totalWrites++;
60
+ }
61
+ break;
62
+ }
63
+ case 'Glob': {
64
+ // Glob results are in tool_result, not tool_use; we track the search pattern
65
+ // We don't track individual files from glob since results come in tool_result
66
+ break;
67
+ }
68
+ case 'Grep': {
69
+ // Similar to Glob — results come in tool_result
70
+ break;
71
+ }
72
+ case 'Bash': {
73
+ const command = input.command;
74
+ if (command) {
75
+ this.parseBashCommand(command, now);
76
+ }
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ /**
82
+ * Parse bash commands for file operation heuristics
83
+ */
84
+ parseBashCommand(command, timestamp) {
85
+ // Detect rm commands
86
+ const rmMatch = command.match(/\brm\s+(?:-[rRfv]+\s+)*([^\s|;&]+)/);
87
+ if (rmMatch && rmMatch[1]) {
88
+ this.recordAction(rmMatch[1], 'deleted', timestamp);
89
+ this.index.totalWrites++;
90
+ }
91
+ // Detect mv commands (source deleted, dest created)
92
+ const mvMatch = command.match(/\bmv\s+(?:-[fv]+\s+)*([^\s|;&]+)\s+([^\s|;&]+)/);
93
+ if (mvMatch && mvMatch[1] && mvMatch[2]) {
94
+ this.recordAction(mvMatch[1], 'deleted', timestamp);
95
+ this.recordAction(mvMatch[2], 'created', timestamp);
96
+ this.index.totalWrites += 2;
97
+ }
98
+ }
99
+ /**
100
+ * Record a file action in the index
101
+ */
102
+ recordAction(filePath, action, timestamp) {
103
+ const absPath = resolve(this.worktreeRoot, filePath);
104
+ const relPath = relative(this.worktreeRoot, absPath);
105
+ const existing = this.index.files[absPath];
106
+ if (existing) {
107
+ existing.actions.push(action);
108
+ existing.lastTouchedAt = timestamp;
109
+ }
110
+ else {
111
+ this.index.files[absPath] = {
112
+ path: absPath,
113
+ relativePath: relPath,
114
+ actions: [action],
115
+ firstSeenAt: timestamp,
116
+ lastTouchedAt: timestamp,
117
+ };
118
+ }
119
+ this.index.lastUpdatedAt = timestamp;
120
+ }
121
+ /**
122
+ * Get the current artifact index
123
+ */
124
+ getIndex() {
125
+ return this.index;
126
+ }
127
+ /**
128
+ * Get files matching optional filters
129
+ */
130
+ getFiles(filter) {
131
+ let files = Object.values(this.index.files);
132
+ if (filter?.action) {
133
+ files = files.filter(f => f.actions.includes(filter.action));
134
+ }
135
+ if (filter?.glob) {
136
+ const pattern = filter.glob;
137
+ files = files.filter(f => {
138
+ // Simple glob matching: * matches anything except /, ** matches anything
139
+ const regex = new RegExp('^' + pattern
140
+ .replace(/\*\*/g, '___DOUBLESTAR___')
141
+ .replace(/\*/g, '[^/]*')
142
+ .replace(/___DOUBLESTAR___/g, '.*')
143
+ .replace(/\?/g, '[^/]') + '$');
144
+ return regex.test(f.relativePath);
145
+ });
146
+ }
147
+ return files;
148
+ }
149
+ /**
150
+ * Persist the index to .agent/artifacts.json atomically
151
+ */
152
+ persist() {
153
+ const agentDir = getAgentDir(this.worktreeRoot);
154
+ initializeAgentDir(this.worktreeRoot);
155
+ const artifactsPath = resolve(agentDir, 'artifacts.json');
156
+ const tempPath = artifactsPath + '.tmp';
157
+ const serialized = {
158
+ files: this.index.files,
159
+ totalReads: this.index.totalReads,
160
+ totalWrites: this.index.totalWrites,
161
+ lastUpdatedAt: this.index.lastUpdatedAt,
162
+ };
163
+ writeFileSync(tempPath, JSON.stringify(serialized, null, 2));
164
+ renameSync(tempPath, artifactsPath);
165
+ }
166
+ /**
167
+ * Load a persisted artifact index from .agent/artifacts.json
168
+ */
169
+ static load(worktreeRoot) {
170
+ const artifactsPath = resolve(getAgentDir(worktreeRoot), 'artifacts.json');
171
+ try {
172
+ if (!existsSync(artifactsPath)) {
173
+ return new ArtifactTracker(worktreeRoot);
174
+ }
175
+ const content = readFileSync(artifactsPath, 'utf-8');
176
+ const data = JSON.parse(content);
177
+ const index = {
178
+ files: data.files ?? {},
179
+ totalReads: data.totalReads ?? 0,
180
+ totalWrites: data.totalWrites ?? 0,
181
+ lastUpdatedAt: data.lastUpdatedAt ?? Date.now(),
182
+ };
183
+ return new ArtifactTracker(worktreeRoot, index);
184
+ }
185
+ catch {
186
+ return new ArtifactTracker(worktreeRoot);
187
+ }
188
+ }
189
+ /**
190
+ * Generate a compact summary string for context injection
191
+ */
192
+ toContextString() {
193
+ const files = Object.values(this.index.files);
194
+ if (files.length === 0)
195
+ return '## Files Tracked (0 files)\nNo files tracked yet.';
196
+ const modified = files.filter(f => f.actions.some(a => a === 'created' || a === 'modified' || a === 'deleted'));
197
+ const readOnly = files.filter(f => !f.actions.some(a => a === 'created' || a === 'modified' || a === 'deleted') && f.actions.includes('read'));
198
+ const searchedOnly = files.filter(f => f.actions.every(a => a === 'searched'));
199
+ const lines = [];
200
+ lines.push(`## Files Tracked (${files.length} files)`);
201
+ if (modified.length > 0) {
202
+ lines.push(`### Modified (${modified.length}):`);
203
+ for (const f of modified) {
204
+ const actionCounts = this.summarizeActions(f.actions);
205
+ lines.push(`- ${f.relativePath} (${actionCounts})`);
206
+ }
207
+ }
208
+ if (readOnly.length > 0) {
209
+ lines.push(`### Read-only (${readOnly.length}):`);
210
+ for (const f of readOnly) {
211
+ const readCount = f.actions.filter(a => a === 'read').length;
212
+ lines.push(`- ${f.relativePath} (read ${readCount}x)`);
213
+ }
214
+ }
215
+ if (searchedOnly.length > 0) {
216
+ lines.push(`### Searched (${searchedOnly.length}):`);
217
+ for (const f of searchedOnly) {
218
+ lines.push(`- ${f.relativePath}`);
219
+ }
220
+ }
221
+ return lines.join('\n');
222
+ }
223
+ /**
224
+ * Summarize action counts for a file
225
+ */
226
+ summarizeActions(actions) {
227
+ const counts = {};
228
+ for (const action of actions) {
229
+ counts[action] = (counts[action] ?? 0) + 1;
230
+ }
231
+ return Object.entries(counts)
232
+ .map(([action, count]) => `${action} ${count}x`)
233
+ .join(', ');
234
+ }
235
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=artifact-tracker.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"artifact-tracker.test.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/artifact-tracker.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,189 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ vi.mock('fs', () => ({
3
+ existsSync: vi.fn(),
4
+ readFileSync: vi.fn(),
5
+ writeFileSync: vi.fn(),
6
+ renameSync: vi.fn(),
7
+ mkdirSync: vi.fn(),
8
+ }));
9
+ import { existsSync, readFileSync, writeFileSync, renameSync } from 'fs';
10
+ import { resolve } from 'path';
11
+ import { ArtifactTracker } from './artifact-tracker.js';
12
+ const WORKTREE = '/tmp/test-worktree';
13
+ function makeToolUse(toolName, input) {
14
+ return {
15
+ type: 'tool_use',
16
+ toolName,
17
+ input,
18
+ raw: {},
19
+ };
20
+ }
21
+ beforeEach(() => {
22
+ vi.clearAllMocks();
23
+ // By default, existsSync returns true (for file existence checks in Write tool)
24
+ vi.mocked(existsSync).mockReturnValue(true);
25
+ });
26
+ describe('ArtifactTracker — trackEvent', () => {
27
+ it('tracks Read tool events', () => {
28
+ const tracker = new ArtifactTracker(WORKTREE);
29
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
30
+ const index = tracker.getIndex();
31
+ expect(Object.keys(index.files)).toHaveLength(1);
32
+ expect(index.totalReads).toBe(1);
33
+ const file = Object.values(index.files)[0];
34
+ expect(file.relativePath).toBe('src/foo.ts');
35
+ expect(file.actions).toEqual(['read']);
36
+ });
37
+ it('tracks Write tool events as modified when file exists', () => {
38
+ vi.mocked(existsSync).mockReturnValue(true);
39
+ const tracker = new ArtifactTracker(WORKTREE);
40
+ tracker.trackEvent(makeToolUse('Write', { file_path: '/tmp/test-worktree/src/bar.ts' }));
41
+ const index = tracker.getIndex();
42
+ expect(index.totalWrites).toBe(1);
43
+ const file = Object.values(index.files)[0];
44
+ expect(file.actions).toEqual(['modified']);
45
+ });
46
+ it('tracks Write tool events as created when file does not exist', () => {
47
+ vi.mocked(existsSync).mockReturnValue(false);
48
+ const tracker = new ArtifactTracker(WORKTREE);
49
+ tracker.trackEvent(makeToolUse('Write', { file_path: '/tmp/test-worktree/src/new.ts' }));
50
+ const file = Object.values(tracker.getIndex().files)[0];
51
+ expect(file.actions).toEqual(['created']);
52
+ });
53
+ it('tracks Edit tool events as modified', () => {
54
+ const tracker = new ArtifactTracker(WORKTREE);
55
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/edit.ts' }));
56
+ const file = Object.values(tracker.getIndex().files)[0];
57
+ expect(file.actions).toEqual(['modified']);
58
+ expect(tracker.getIndex().totalWrites).toBe(1);
59
+ });
60
+ it('accumulates actions for the same file', () => {
61
+ const tracker = new ArtifactTracker(WORKTREE);
62
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
63
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/foo.ts' }));
64
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
65
+ const file = Object.values(tracker.getIndex().files)[0];
66
+ expect(file.actions).toEqual(['read', 'modified', 'read']);
67
+ expect(tracker.getIndex().totalReads).toBe(2);
68
+ expect(tracker.getIndex().totalWrites).toBe(1);
69
+ });
70
+ it('ignores non-tool_use events', () => {
71
+ const tracker = new ArtifactTracker(WORKTREE);
72
+ tracker.trackEvent({ type: 'assistant_text', text: 'hello', raw: {} });
73
+ tracker.trackEvent({ type: 'system', subtype: 'status', raw: {} });
74
+ expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
75
+ });
76
+ it('parses bash rm commands', () => {
77
+ const tracker = new ArtifactTracker(WORKTREE);
78
+ tracker.trackEvent(makeToolUse('Bash', { command: 'rm -f /tmp/test-worktree/src/old.ts' }));
79
+ const files = Object.values(tracker.getIndex().files);
80
+ expect(files).toHaveLength(1);
81
+ expect(files[0].actions).toEqual(['deleted']);
82
+ });
83
+ it('parses bash mv commands', () => {
84
+ const tracker = new ArtifactTracker(WORKTREE);
85
+ tracker.trackEvent(makeToolUse('Bash', { command: 'mv /tmp/test-worktree/src/a.ts /tmp/test-worktree/src/b.ts' }));
86
+ const files = Object.values(tracker.getIndex().files);
87
+ expect(files).toHaveLength(2);
88
+ });
89
+ it('ignores tool_use events with missing file_path', () => {
90
+ const tracker = new ArtifactTracker(WORKTREE);
91
+ tracker.trackEvent(makeToolUse('Read', {}));
92
+ tracker.trackEvent(makeToolUse('Write', {}));
93
+ tracker.trackEvent(makeToolUse('Edit', {}));
94
+ expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
95
+ });
96
+ });
97
+ describe('ArtifactTracker — getFiles', () => {
98
+ it('returns all files when no filter', () => {
99
+ const tracker = new ArtifactTracker(WORKTREE);
100
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/a.ts' }));
101
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/b.ts' }));
102
+ expect(tracker.getFiles()).toHaveLength(2);
103
+ });
104
+ it('filters by action', () => {
105
+ const tracker = new ArtifactTracker(WORKTREE);
106
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/a.ts' }));
107
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/b.ts' }));
108
+ expect(tracker.getFiles({ action: 'read' })).toHaveLength(1);
109
+ expect(tracker.getFiles({ action: 'modified' })).toHaveLength(1);
110
+ });
111
+ it('filters by glob pattern', () => {
112
+ const tracker = new ArtifactTracker(WORKTREE);
113
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
114
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/test/bar.ts' }));
115
+ const srcFiles = tracker.getFiles({ glob: 'src/**' });
116
+ expect(srcFiles).toHaveLength(1);
117
+ expect(srcFiles[0].relativePath).toBe('src/foo.ts');
118
+ });
119
+ });
120
+ describe('ArtifactTracker — persistence', () => {
121
+ it('persists index atomically', () => {
122
+ vi.mocked(existsSync).mockReturnValue(true);
123
+ const tracker = new ArtifactTracker(WORKTREE);
124
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
125
+ tracker.persist();
126
+ const artifactsPath = resolve(WORKTREE, '.agent', 'artifacts.json');
127
+ expect(writeFileSync).toHaveBeenCalledWith(artifactsPath + '.tmp', expect.any(String));
128
+ expect(renameSync).toHaveBeenCalledWith(artifactsPath + '.tmp', artifactsPath);
129
+ });
130
+ it('loads persisted index', () => {
131
+ const savedIndex = {
132
+ files: {
133
+ '/tmp/test-worktree/src/foo.ts': {
134
+ path: '/tmp/test-worktree/src/foo.ts',
135
+ relativePath: 'src/foo.ts',
136
+ actions: ['read', 'modified'],
137
+ firstSeenAt: 1000,
138
+ lastTouchedAt: 2000,
139
+ }
140
+ },
141
+ totalReads: 1,
142
+ totalWrites: 1,
143
+ lastUpdatedAt: 2000,
144
+ };
145
+ vi.mocked(existsSync).mockReturnValue(true);
146
+ vi.mocked(readFileSync).mockReturnValue(JSON.stringify(savedIndex));
147
+ const tracker = ArtifactTracker.load(WORKTREE);
148
+ const index = tracker.getIndex();
149
+ expect(Object.keys(index.files)).toHaveLength(1);
150
+ expect(index.totalReads).toBe(1);
151
+ expect(index.totalWrites).toBe(1);
152
+ });
153
+ it('returns empty tracker when no persisted file exists', () => {
154
+ vi.mocked(existsSync).mockReturnValue(false);
155
+ const tracker = ArtifactTracker.load(WORKTREE);
156
+ expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
157
+ });
158
+ it('returns empty tracker when persisted file is invalid JSON', () => {
159
+ vi.mocked(existsSync).mockReturnValue(true);
160
+ vi.mocked(readFileSync).mockReturnValue('not json');
161
+ const tracker = ArtifactTracker.load(WORKTREE);
162
+ expect(Object.keys(tracker.getIndex().files)).toHaveLength(0);
163
+ });
164
+ });
165
+ describe('ArtifactTracker — toContextString', () => {
166
+ it('returns empty message when no files tracked', () => {
167
+ const tracker = new ArtifactTracker(WORKTREE);
168
+ expect(tracker.toContextString()).toContain('0 files');
169
+ });
170
+ it('categorizes files into modified, read-only, and searched', () => {
171
+ const tracker = new ArtifactTracker(WORKTREE);
172
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/modified.ts' }));
173
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/readonly.ts' }));
174
+ const output = tracker.toContextString();
175
+ expect(output).toContain('Modified (1)');
176
+ expect(output).toContain('Read-only (1)');
177
+ expect(output).toContain('src/modified.ts');
178
+ expect(output).toContain('src/readonly.ts');
179
+ });
180
+ it('shows action counts for modified files', () => {
181
+ const tracker = new ArtifactTracker(WORKTREE);
182
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
183
+ tracker.trackEvent(makeToolUse('Edit', { file_path: '/tmp/test-worktree/src/foo.ts' }));
184
+ tracker.trackEvent(makeToolUse('Read', { file_path: '/tmp/test-worktree/src/foo.ts' }));
185
+ const output = tracker.toContextString();
186
+ expect(output).toContain('read 2x');
187
+ expect(output).toContain('modified 1x');
188
+ });
189
+ });
@@ -0,0 +1,72 @@
1
+ import type { AgentEvent } from '../providers/types.js';
2
+ import type { StructuredSummary } from './state-types.js';
3
+ import type { ArtifactIndex } from './artifact-tracker.js';
4
+ /**
5
+ * Configuration for the ContextManager
6
+ */
7
+ export interface ContextManagerConfig {
8
+ /** Root path of the worktree for file resolution and persistence */
9
+ worktreeRoot: string;
10
+ /** Maximum events to buffer between compaction boundaries */
11
+ maxEventBufferSize?: number;
12
+ }
13
+ /**
14
+ * Coordinates ArtifactTracker, SummaryBuilder, and StructuredSummary
15
+ * for context window management during an agent session.
16
+ *
17
+ * Processes all agent events, tracks file operations, and handles
18
+ * compaction boundaries by generating incremental summaries.
19
+ */
20
+ export declare class ContextManager {
21
+ private artifactTracker;
22
+ private summaryBuilder;
23
+ private currentSummary;
24
+ private eventBuffer;
25
+ private worktreeRoot;
26
+ private maxEventBufferSize;
27
+ private lastPersistAt;
28
+ constructor(config: ContextManagerConfig);
29
+ /**
30
+ * Process an agent event. Called for every event in the stream.
31
+ * Feeds tool events to ArtifactTracker and buffers events for compaction.
32
+ */
33
+ processEvent(event: AgentEvent): void;
34
+ /**
35
+ * Handle a compaction boundary event from the provider.
36
+ * Generates an incremental summary from buffered events and merges
37
+ * it with the existing persisted summary.
38
+ */
39
+ handleCompaction(): void;
40
+ /**
41
+ * Get the current context injection string for the agent prompt.
42
+ * Returns empty string if no summary exists yet.
43
+ */
44
+ getContextSection(): string;
45
+ /**
46
+ * Get the current structured summary (if any)
47
+ */
48
+ getSummary(): StructuredSummary | null;
49
+ /**
50
+ * Get the current artifact index
51
+ */
52
+ getArtifactIndex(): ArtifactIndex;
53
+ /**
54
+ * Persist all state (summary + artifacts) to disk.
55
+ * Uses atomic writes to survive crashes.
56
+ */
57
+ persist(): void;
58
+ /**
59
+ * Persist if enough time has passed since last persist (debounced).
60
+ * Call this periodically (e.g., on every tool event) to ensure
61
+ * state is saved without excessive I/O.
62
+ *
63
+ * @param intervalMs - Minimum interval between persists (default: 30000ms)
64
+ */
65
+ persistIfStale(intervalMs?: number): void;
66
+ /**
67
+ * Load a ContextManager from persisted state (for session resume).
68
+ * Loads both summary.json and artifacts.json from the .agent/ directory.
69
+ */
70
+ static load(worktreeRoot: string): ContextManager;
71
+ }
72
+ //# sourceMappingURL=context-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-manager.d.ts","sourceRoot":"","sources":["../../../src/orchestrator/context-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAI1D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oEAAoE;IACpE,YAAY,EAAE,MAAM,CAAA;IACpB,6DAA6D;IAC7D,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,eAAe,CAAiB;IACxC,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,YAAY,CAAQ;IAC5B,OAAO,CAAC,kBAAkB,CAAQ;IAClC,OAAO,CAAC,aAAa,CAAQ;gBAEjB,MAAM,EAAE,oBAAoB;IAUxC;;;OAGG;IACH,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAkBrC;;;;OAIG;IACH,gBAAgB,IAAI,IAAI;IAyBxB;;;OAGG;IACH,iBAAiB,IAAI,MAAM;IAQ3B;;OAEG;IACH,UAAU,IAAI,iBAAiB,GAAG,IAAI;IAItC;;OAEG;IACH,gBAAgB,IAAI,aAAa;IAIjC;;;OAGG;IACH,OAAO,IAAI,IAAI;IAQf;;;;;;OAMG;IACH,cAAc,CAAC,UAAU,GAAE,MAAc,GAAG,IAAI;IAOhD;;;OAGG;IACH,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc;CAMlD"}