antigravity-ai-kit 2.1.0 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/README.md +4 -4
- package/.agent/agents/README.md +16 -12
- package/.agent/agents/architect.md +1 -0
- package/.agent/agents/backend-specialist.md +11 -0
- package/.agent/agents/code-reviewer.md +1 -0
- package/.agent/agents/database-architect.md +11 -0
- package/.agent/agents/devops-engineer.md +11 -0
- package/.agent/agents/e2e-runner.md +1 -0
- package/.agent/agents/explorer-agent.md +11 -0
- package/.agent/agents/frontend-specialist.md +11 -0
- package/.agent/agents/mobile-developer.md +11 -0
- package/.agent/agents/performance-optimizer.md +11 -0
- package/.agent/agents/planner.md +1 -0
- package/.agent/agents/refactor-cleaner.md +1 -0
- package/.agent/agents/reliability-engineer.md +11 -0
- package/.agent/agents/security-reviewer.md +1 -0
- package/.agent/agents/sprint-orchestrator.md +10 -0
- package/.agent/agents/tdd-guide.md +1 -0
- package/.agent/commands/code-review.md +1 -0
- package/.agent/commands/debug.md +1 -0
- package/.agent/commands/deploy.md +1 -0
- package/.agent/commands/help.md +252 -31
- package/.agent/commands/plan.md +1 -0
- package/.agent/commands/status.md +1 -0
- package/.agent/commands/tdd.md +1 -0
- package/.agent/contexts/brainstorm.md +26 -0
- package/.agent/contexts/debug.md +28 -0
- package/.agent/contexts/implement.md +29 -0
- package/.agent/contexts/review.md +27 -0
- package/.agent/contexts/ship.md +28 -0
- package/.agent/engine/identity.json +13 -0
- package/.agent/engine/loading-rules.json +23 -1
- package/.agent/engine/marketplace-index.json +29 -0
- package/.agent/engine/reliability-config.json +14 -0
- package/.agent/engine/sdlc-map.json +44 -0
- package/.agent/engine/workflow-state.json +28 -2
- package/.agent/hooks/hooks.json +27 -25
- package/.agent/manifest.json +12 -4
- package/.agent/rules.md +2 -1
- package/.agent/skills/README.md +10 -5
- package/.agent/skills/i18n-localization/SKILL.md +191 -0
- package/.agent/skills/mcp-integration/SKILL.md +224 -0
- package/.agent/skills/parallel-agents/SKILL.md +1 -1
- package/.agent/skills/shell-conventions/SKILL.md +92 -0
- package/.agent/skills/ui-ux-pro-max/SKILL.md +557 -0
- package/.agent/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/skills/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/skills/ui-ux-pro-max/data/styles.csv +68 -0
- package/.agent/skills/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/skills/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/skills/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/skills/ui-ux-pro-max/scripts/core.py +253 -0
- package/.agent/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.agent/templates/adr-template.md +32 -0
- package/.agent/templates/bug-report.md +37 -0
- package/.agent/templates/feature-request.md +32 -0
- package/.agent/workflows/README.md +92 -78
- package/.agent/workflows/brainstorm.md +154 -100
- package/.agent/workflows/create.md +142 -75
- package/.agent/workflows/debug.md +157 -98
- package/.agent/workflows/deploy.md +195 -144
- package/.agent/workflows/enhance.md +157 -65
- package/.agent/workflows/orchestrate.md +171 -114
- package/.agent/workflows/plan.md +147 -72
- package/.agent/workflows/preview.md +140 -83
- package/.agent/workflows/quality-gate.md +196 -0
- package/.agent/workflows/retrospective.md +197 -0
- package/.agent/workflows/review.md +188 -0
- package/.agent/workflows/status.md +142 -91
- package/.agent/workflows/test.md +168 -95
- package/.agent/workflows/ui-ux-pro-max.md +181 -127
- package/README.md +215 -78
- package/bin/ag-kit.js +344 -10
- package/lib/agent-registry.js +214 -0
- package/lib/agent-reputation.js +351 -0
- package/lib/cli-commands.js +235 -0
- package/lib/conflict-detector.js +245 -0
- package/lib/engineering-manager.js +354 -0
- package/lib/error-budget.js +294 -0
- package/lib/hook-system.js +252 -0
- package/lib/identity.js +245 -0
- package/lib/loading-engine.js +208 -0
- package/lib/marketplace.js +298 -0
- package/lib/plugin-system.js +604 -0
- package/lib/security-scanner.js +309 -0
- package/lib/self-healing.js +434 -0
- package/lib/session-manager.js +261 -0
- package/lib/skill-sandbox.js +244 -0
- package/lib/task-governance.js +523 -0
- package/lib/task-model.js +317 -0
- package/lib/updater.js +201 -0
- package/lib/verify.js +240 -0
- package/lib/workflow-engine.js +353 -0
- package/lib/workflow-persistence.js +160 -0
- package/package.json +7 -3
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity AI Kit — Self-Healing Pipeline
|
|
3
|
+
*
|
|
4
|
+
* Detects CI failures, diagnoses root causes, generates
|
|
5
|
+
* JSON fix patches, and applies with confirmation.
|
|
6
|
+
*
|
|
7
|
+
* @module lib/self-healing
|
|
8
|
+
* @author Emre Dursun
|
|
9
|
+
* @since v3.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
|
|
18
|
+
const AGENT_DIR = '.agent';
|
|
19
|
+
const ENGINE_DIR = 'engine';
|
|
20
|
+
const HEALING_LOG_FILE = 'healing-log.json';
|
|
21
|
+
const LAST_CI_OUTPUT_FILE = 'last-ci-output.txt';
|
|
22
|
+
|
|
23
|
+
/** Maximum healing log entries before pruning */
|
|
24
|
+
const MAX_LOG_ENTRIES = 100;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {object} FailureDetection
|
|
28
|
+
* @property {string} type - Failure type: 'test' | 'build' | 'dependency' | 'lint'
|
|
29
|
+
* @property {string} message - Failure message
|
|
30
|
+
* @property {string | null} file - Affected file (if detectable)
|
|
31
|
+
* @property {number | null} line - Line number (if detectable)
|
|
32
|
+
* @property {string} severity - 'critical' | 'high' | 'medium' | 'low'
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @typedef {object} Diagnosis
|
|
37
|
+
* @property {string} category - Root cause: 'syntax' | 'import' | 'type' | 'config' | 'assertion' | 'unknown'
|
|
38
|
+
* @property {string} explanation - Human-readable diagnosis
|
|
39
|
+
* @property {boolean} autoFixable - Whether auto-fix is possible
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @typedef {object} FixPatch
|
|
44
|
+
* @property {string} patchId - Unique patch ID
|
|
45
|
+
* @property {string} file - Target file path
|
|
46
|
+
* @property {'insert' | 'replace' | 'delete'} type - Patch operation type
|
|
47
|
+
* @property {number | null} line - Target line number
|
|
48
|
+
* @property {string} original - Original content (for replace/delete)
|
|
49
|
+
* @property {string} replacement - New content (for insert/replace)
|
|
50
|
+
* @property {'high' | 'medium' | 'low'} confidence - Fix confidence level
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves the healing log file path.
|
|
55
|
+
*
|
|
56
|
+
* @param {string} projectRoot - Root directory
|
|
57
|
+
* @returns {string}
|
|
58
|
+
*/
|
|
59
|
+
function resolveHealingLogPath(projectRoot) {
|
|
60
|
+
return path.join(projectRoot, AGENT_DIR, ENGINE_DIR, HEALING_LOG_FILE);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Loads the healing log from disk.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} projectRoot - Root directory
|
|
67
|
+
* @returns {{ entries: object[] }}
|
|
68
|
+
*/
|
|
69
|
+
function loadHealingLog(projectRoot) {
|
|
70
|
+
const filePath = resolveHealingLogPath(projectRoot);
|
|
71
|
+
|
|
72
|
+
if (!fs.existsSync(filePath)) {
|
|
73
|
+
return { entries: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
78
|
+
} catch {
|
|
79
|
+
return { entries: [] };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Writes the healing log atomically with pruning.
|
|
85
|
+
*
|
|
86
|
+
* @param {string} projectRoot - Root directory
|
|
87
|
+
* @param {{ entries: object[] }} data
|
|
88
|
+
* @returns {void}
|
|
89
|
+
*/
|
|
90
|
+
function writeHealingLog(projectRoot, data) {
|
|
91
|
+
const filePath = resolveHealingLogPath(projectRoot);
|
|
92
|
+
const dir = path.dirname(filePath);
|
|
93
|
+
|
|
94
|
+
if (!fs.existsSync(dir)) {
|
|
95
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Prune to last MAX_LOG_ENTRIES
|
|
99
|
+
if (data.entries.length > MAX_LOG_ENTRIES) {
|
|
100
|
+
data.entries = data.entries.slice(-MAX_LOG_ENTRIES);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const tempPath = `${filePath}.tmp`;
|
|
104
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
105
|
+
fs.renameSync(tempPath, filePath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ═══════════════════════════════════════════════════
|
|
109
|
+
// Failure Detection Patterns
|
|
110
|
+
// ═══════════════════════════════════════════════════
|
|
111
|
+
|
|
112
|
+
/** @type {{ pattern: RegExp, type: string, severity: string }[]} */
|
|
113
|
+
const FAILURE_PATTERNS = [
|
|
114
|
+
// Test failures
|
|
115
|
+
{
|
|
116
|
+
pattern: /FAIL\s+([\w./\\-]+)\s*>/,
|
|
117
|
+
type: 'test',
|
|
118
|
+
severity: 'high',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
pattern: /AssertionError:\s*(.+)/,
|
|
122
|
+
type: 'test',
|
|
123
|
+
severity: 'high',
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
pattern: /Expected\s*(.+)\s*to\s*(equal|be|match)/i,
|
|
127
|
+
type: 'test',
|
|
128
|
+
severity: 'high',
|
|
129
|
+
},
|
|
130
|
+
// Build failures
|
|
131
|
+
{
|
|
132
|
+
pattern: /error TS(\d+):\s*(.+)/,
|
|
133
|
+
type: 'build',
|
|
134
|
+
severity: 'critical',
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
pattern: /SyntaxError:\s*(.+)/,
|
|
138
|
+
type: 'build',
|
|
139
|
+
severity: 'critical',
|
|
140
|
+
},
|
|
141
|
+
// Import/dependency issues
|
|
142
|
+
{
|
|
143
|
+
pattern: /Cannot find module '([^']+)'/,
|
|
144
|
+
type: 'dependency',
|
|
145
|
+
severity: 'high',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
pattern: /Module not found:\s*(.+)/,
|
|
149
|
+
type: 'dependency',
|
|
150
|
+
severity: 'high',
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
pattern: /ERR_MODULE_NOT_FOUND/,
|
|
154
|
+
type: 'dependency',
|
|
155
|
+
severity: 'high',
|
|
156
|
+
},
|
|
157
|
+
// Lint errors
|
|
158
|
+
{
|
|
159
|
+
pattern: /(\d+):(\d+)\s+error\s+(.+?)\s+([\w/-]+)$/m,
|
|
160
|
+
type: 'lint',
|
|
161
|
+
severity: 'medium',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
pattern: /eslint.*error/i,
|
|
165
|
+
type: 'lint',
|
|
166
|
+
severity: 'medium',
|
|
167
|
+
},
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Detects failures from raw CI output text.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} ciOutput - Raw CI log text
|
|
174
|
+
* @returns {FailureDetection[]}
|
|
175
|
+
*/
|
|
176
|
+
function detectFailure(ciOutput) {
|
|
177
|
+
if (!ciOutput || typeof ciOutput !== 'string') {
|
|
178
|
+
return [];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** @type {FailureDetection[]} */
|
|
182
|
+
const failures = [];
|
|
183
|
+
const lines = ciOutput.split('\n');
|
|
184
|
+
|
|
185
|
+
for (const line of lines) {
|
|
186
|
+
for (const { pattern, type, severity } of FAILURE_PATTERNS) {
|
|
187
|
+
const match = line.match(pattern);
|
|
188
|
+
if (match) {
|
|
189
|
+
// Try to extract file and line from context
|
|
190
|
+
const fileMatch = line.match(/([\w./\\-]+\.(js|ts|jsx|tsx|json))/);
|
|
191
|
+
const lineMatch = line.match(/:(\d+):/);
|
|
192
|
+
|
|
193
|
+
failures.push({
|
|
194
|
+
type,
|
|
195
|
+
message: match[0].trim(),
|
|
196
|
+
file: fileMatch ? fileMatch[1] : null,
|
|
197
|
+
line: lineMatch ? parseInt(lineMatch[1], 10) : null,
|
|
198
|
+
severity,
|
|
199
|
+
});
|
|
200
|
+
break; // One match per line
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return failures;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ═══════════════════════════════════════════════════
|
|
209
|
+
// Failure Diagnosis
|
|
210
|
+
// ═══════════════════════════════════════════════════
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Diagnoses the root cause of a detected failure.
|
|
214
|
+
*
|
|
215
|
+
* @param {FailureDetection} failure - Detected failure
|
|
216
|
+
* @returns {Diagnosis}
|
|
217
|
+
*/
|
|
218
|
+
function diagnoseFailure(failure) {
|
|
219
|
+
const message = failure.message.toLowerCase();
|
|
220
|
+
|
|
221
|
+
// Import/module issues
|
|
222
|
+
if (failure.type === 'dependency' || message.includes('cannot find module') || message.includes('module not found')) {
|
|
223
|
+
return {
|
|
224
|
+
category: 'import',
|
|
225
|
+
explanation: `Missing module or incorrect import path: ${failure.message}`,
|
|
226
|
+
autoFixable: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Syntax errors
|
|
231
|
+
if (failure.type === 'build' && message.includes('syntaxerror')) {
|
|
232
|
+
return {
|
|
233
|
+
category: 'syntax',
|
|
234
|
+
explanation: `Syntax error in source code: ${failure.message}`,
|
|
235
|
+
autoFixable: false,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// TypeScript type errors
|
|
240
|
+
if (failure.type === 'build' && message.includes('error ts')) {
|
|
241
|
+
return {
|
|
242
|
+
category: 'type',
|
|
243
|
+
explanation: `TypeScript type error: ${failure.message}`,
|
|
244
|
+
autoFixable: false,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Test assertion failures
|
|
249
|
+
if (failure.type === 'test') {
|
|
250
|
+
return {
|
|
251
|
+
category: 'assertion',
|
|
252
|
+
explanation: `Test assertion failed — requires manual review: ${failure.message}`,
|
|
253
|
+
autoFixable: false,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Lint errors
|
|
258
|
+
if (failure.type === 'lint') {
|
|
259
|
+
return {
|
|
260
|
+
category: 'config',
|
|
261
|
+
explanation: `Lint rule violation: ${failure.message}`,
|
|
262
|
+
autoFixable: true,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
category: 'unknown',
|
|
268
|
+
explanation: `Unclassified failure: ${failure.message}`,
|
|
269
|
+
autoFixable: false,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// ═══════════════════════════════════════════════════
|
|
274
|
+
// Fix Patch Generation
|
|
275
|
+
// ═══════════════════════════════════════════════════
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Generates a fix patch for a diagnosed failure.
|
|
279
|
+
* Only generates patches for auto-fixable issues.
|
|
280
|
+
*
|
|
281
|
+
* @param {FailureDetection} failure - Detected failure
|
|
282
|
+
* @param {Diagnosis} diagnosis - Diagnosis result
|
|
283
|
+
* @returns {FixPatch | null} Generated patch, or null if not auto-fixable
|
|
284
|
+
*/
|
|
285
|
+
function generateFixPatch(failure, diagnosis) {
|
|
286
|
+
if (!diagnosis.autoFixable) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const patchId = `HEAL-${crypto.randomUUID().slice(0, 8).toUpperCase()}`;
|
|
291
|
+
|
|
292
|
+
// Missing import → suggest adding import
|
|
293
|
+
if (diagnosis.category === 'import') {
|
|
294
|
+
const moduleMatch = failure.message.match(/(?:Cannot find module|Module not found)[:\s]*'?([^'"\s]+)/i);
|
|
295
|
+
const moduleName = moduleMatch ? moduleMatch[1] : 'unknown-module';
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
patchId,
|
|
299
|
+
file: failure.file || 'unknown',
|
|
300
|
+
type: 'insert',
|
|
301
|
+
line: 1,
|
|
302
|
+
original: '',
|
|
303
|
+
replacement: `const ${moduleName.replace(/[^a-zA-Z]/g, '')} = require('${moduleName}');`,
|
|
304
|
+
confidence: 'medium',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Lint fix → suggest formatting change
|
|
309
|
+
if (diagnosis.category === 'config') {
|
|
310
|
+
return {
|
|
311
|
+
patchId,
|
|
312
|
+
file: failure.file || 'unknown',
|
|
313
|
+
type: 'replace',
|
|
314
|
+
line: failure.line,
|
|
315
|
+
original: failure.message,
|
|
316
|
+
replacement: `// TODO: Fix lint rule — ${failure.message}`,
|
|
317
|
+
confidence: 'low',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Applies a fix patch with confirmation safeguard.
|
|
326
|
+
* Dry-run by default — requires explicit opt-in for file writes.
|
|
327
|
+
*
|
|
328
|
+
* @param {string} projectRoot - Root directory
|
|
329
|
+
* @param {FixPatch} patch - Patch to apply
|
|
330
|
+
* @param {object} [options] - Apply options
|
|
331
|
+
* @param {boolean} [options.dryRun] - If true (default), just preview
|
|
332
|
+
* @returns {{ applied: boolean, preview: string, patchId: string }}
|
|
333
|
+
*/
|
|
334
|
+
function applyFixWithConfirmation(projectRoot, patch, options = {}) {
|
|
335
|
+
const dryRun = options.dryRun !== false; // Default: true
|
|
336
|
+
|
|
337
|
+
const logEntry = {
|
|
338
|
+
patchId: patch.patchId,
|
|
339
|
+
file: patch.file,
|
|
340
|
+
type: patch.type,
|
|
341
|
+
applied: false,
|
|
342
|
+
dryRun,
|
|
343
|
+
timestamp: new Date().toISOString(),
|
|
344
|
+
rollbackData: {
|
|
345
|
+
original: patch.original,
|
|
346
|
+
line: patch.line,
|
|
347
|
+
},
|
|
348
|
+
};
|
|
349
|
+
|
|
350
|
+
const preview = [
|
|
351
|
+
`Patch: ${patch.patchId}`,
|
|
352
|
+
`File: ${patch.file}`,
|
|
353
|
+
`Type: ${patch.type}`,
|
|
354
|
+
`Line: ${patch.line || 'N/A'}`,
|
|
355
|
+
`Confidence: ${patch.confidence}`,
|
|
356
|
+
`Original: ${patch.original || '(empty)'}`,
|
|
357
|
+
`Replacement: ${patch.replacement}`,
|
|
358
|
+
dryRun ? '[DRY RUN — no changes applied]' : '[APPLIED]',
|
|
359
|
+
].join('\n');
|
|
360
|
+
|
|
361
|
+
if (!dryRun && patch.file !== 'unknown') {
|
|
362
|
+
const targetPath = path.join(projectRoot, patch.file);
|
|
363
|
+
|
|
364
|
+
if (fs.existsSync(targetPath)) {
|
|
365
|
+
try {
|
|
366
|
+
const content = fs.readFileSync(targetPath, 'utf-8');
|
|
367
|
+
const lines = content.split('\n');
|
|
368
|
+
|
|
369
|
+
if (patch.type === 'insert' && patch.line !== null) {
|
|
370
|
+
const insertIndex = Math.max(0, (patch.line || 1) - 1);
|
|
371
|
+
lines.splice(insertIndex, 0, patch.replacement);
|
|
372
|
+
} else if (patch.type === 'replace' && patch.line !== null) {
|
|
373
|
+
const replaceIndex = (patch.line || 1) - 1;
|
|
374
|
+
if (replaceIndex >= 0 && replaceIndex < lines.length) {
|
|
375
|
+
lines[replaceIndex] = patch.replacement;
|
|
376
|
+
}
|
|
377
|
+
} else if (patch.type === 'delete' && patch.line !== null) {
|
|
378
|
+
const deleteIndex = (patch.line || 1) - 1;
|
|
379
|
+
if (deleteIndex >= 0 && deleteIndex < lines.length) {
|
|
380
|
+
lines.splice(deleteIndex, 1);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const tempPath = `${targetPath}.tmp`;
|
|
385
|
+
fs.writeFileSync(tempPath, lines.join('\n'), 'utf-8');
|
|
386
|
+
fs.renameSync(tempPath, targetPath);
|
|
387
|
+
|
|
388
|
+
logEntry.applied = true;
|
|
389
|
+
} catch {
|
|
390
|
+
logEntry.applied = false;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Log the action
|
|
396
|
+
const log = loadHealingLog(projectRoot);
|
|
397
|
+
log.entries.push(logEntry);
|
|
398
|
+
writeHealingLog(projectRoot, log);
|
|
399
|
+
|
|
400
|
+
return {
|
|
401
|
+
applied: logEntry.applied,
|
|
402
|
+
preview,
|
|
403
|
+
patchId: patch.patchId,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Returns the healing report: recent heal activities and stats.
|
|
409
|
+
*
|
|
410
|
+
* @param {string} projectRoot - Root directory
|
|
411
|
+
* @returns {{ totalHeals: number, successRate: number, recentEntries: object[], pendingPatches: number }}
|
|
412
|
+
*/
|
|
413
|
+
function getHealingReport(projectRoot) {
|
|
414
|
+
const log = loadHealingLog(projectRoot);
|
|
415
|
+
const entries = log.entries || [];
|
|
416
|
+
|
|
417
|
+
const applied = entries.filter((e) => e.applied).length;
|
|
418
|
+
const dryRuns = entries.filter((e) => e.dryRun && !e.applied).length;
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
totalHeals: entries.length,
|
|
422
|
+
successRate: entries.length > 0 ? Math.round((applied / entries.length) * 100) : 0,
|
|
423
|
+
recentEntries: entries.slice(-5),
|
|
424
|
+
pendingPatches: dryRuns,
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
module.exports = {
|
|
429
|
+
detectFailure,
|
|
430
|
+
diagnoseFailure,
|
|
431
|
+
generateFixPatch,
|
|
432
|
+
applyFixWithConfirmation,
|
|
433
|
+
getHealingReport,
|
|
434
|
+
};
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity AI Kit — Session Manager
|
|
3
|
+
*
|
|
4
|
+
* Automates session-state.json updates so it is no longer
|
|
5
|
+
* a blank template. Tracks active sessions, tasks, and
|
|
6
|
+
* repository state.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/session-manager
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.0.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const { execSync } = require('child_process');
|
|
19
|
+
|
|
20
|
+
const AGENT_DIR = '.agent';
|
|
21
|
+
const STATE_FILENAME = 'session-state.json';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolves the absolute path to session-state.json.
|
|
25
|
+
*
|
|
26
|
+
* @param {string} projectRoot - Root directory of the project
|
|
27
|
+
* @returns {string} Absolute path to session-state.json
|
|
28
|
+
*/
|
|
29
|
+
function resolveStatePath(projectRoot) {
|
|
30
|
+
return path.join(projectRoot, AGENT_DIR, STATE_FILENAME);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Loads session state from disk.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} projectRoot - Root directory of the project
|
|
37
|
+
* @returns {object} Parsed session state
|
|
38
|
+
*/
|
|
39
|
+
function loadSessionState(projectRoot) {
|
|
40
|
+
const filePath = resolveStatePath(projectRoot);
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(filePath)) {
|
|
43
|
+
throw new Error(`Session state file not found: ${filePath}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
47
|
+
return JSON.parse(raw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Writes session state to disk atomically.
|
|
52
|
+
*
|
|
53
|
+
* @param {string} projectRoot - Root directory of the project
|
|
54
|
+
* @param {object} state - Session state object to write
|
|
55
|
+
* @returns {void}
|
|
56
|
+
*/
|
|
57
|
+
function writeSessionState(projectRoot, state) {
|
|
58
|
+
const filePath = resolveStatePath(projectRoot);
|
|
59
|
+
const tempPath = `${filePath}.tmp`;
|
|
60
|
+
|
|
61
|
+
state.lastUpdated = new Date().toISOString();
|
|
62
|
+
|
|
63
|
+
fs.writeFileSync(tempPath, JSON.stringify(state, null, 2) + '\n', 'utf-8');
|
|
64
|
+
fs.renameSync(tempPath, filePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Retrieves current Git branch name safely.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} projectRoot - Root directory of the project
|
|
71
|
+
* @returns {string | null} Branch name or null if not a git repo
|
|
72
|
+
*/
|
|
73
|
+
function getGitBranch(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const result = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
76
|
+
cwd: projectRoot,
|
|
77
|
+
encoding: 'utf-8',
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
80
|
+
});
|
|
81
|
+
return result.trim() || null;
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Retrieves the last Git commit SHA safely.
|
|
89
|
+
*
|
|
90
|
+
* @param {string} projectRoot - Root directory of the project
|
|
91
|
+
* @returns {string | null} Commit SHA or null
|
|
92
|
+
*/
|
|
93
|
+
function getGitLastCommit(projectRoot) {
|
|
94
|
+
try {
|
|
95
|
+
const result = execSync('git log -1 --format=%H', {
|
|
96
|
+
cwd: projectRoot,
|
|
97
|
+
encoding: 'utf-8',
|
|
98
|
+
timeout: 5000,
|
|
99
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
100
|
+
});
|
|
101
|
+
return result.trim() || null;
|
|
102
|
+
} catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Starts a new session. Generates a session ID, populates
|
|
109
|
+
* date, Git info, and sets status to "active".
|
|
110
|
+
*
|
|
111
|
+
* @param {string} projectRoot - Root directory of the project
|
|
112
|
+
* @param {string} [focus] - Optional session focus description
|
|
113
|
+
* @returns {{ sessionId: string, state: object }}
|
|
114
|
+
*/
|
|
115
|
+
function startSession(projectRoot, focus) {
|
|
116
|
+
const state = loadSessionState(projectRoot);
|
|
117
|
+
const sessionId = crypto.randomUUID();
|
|
118
|
+
|
|
119
|
+
state.session = {
|
|
120
|
+
id: sessionId,
|
|
121
|
+
date: new Date().toISOString(),
|
|
122
|
+
focus: focus || null,
|
|
123
|
+
status: 'active',
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
state.repository = {
|
|
127
|
+
currentBranch: getGitBranch(projectRoot),
|
|
128
|
+
lastCommit: getGitLastCommit(projectRoot),
|
|
129
|
+
remoteSynced: false,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
writeSessionState(projectRoot, state);
|
|
133
|
+
|
|
134
|
+
return { sessionId, state };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Ends the current session. Sets status to "completed".
|
|
139
|
+
*
|
|
140
|
+
* @param {string} projectRoot - Root directory of the project
|
|
141
|
+
* @returns {{ success: boolean, sessionId: string | null }}
|
|
142
|
+
*/
|
|
143
|
+
function endSession(projectRoot) {
|
|
144
|
+
const state = loadSessionState(projectRoot);
|
|
145
|
+
|
|
146
|
+
if (!state.session || !state.session.id) {
|
|
147
|
+
return { success: false, sessionId: null };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const sessionId = state.session.id;
|
|
151
|
+
state.session.status = 'completed';
|
|
152
|
+
|
|
153
|
+
// Archive completed tasks count
|
|
154
|
+
state.notes = `Session ${sessionId} completed at ${new Date().toISOString()}`;
|
|
155
|
+
|
|
156
|
+
writeSessionState(projectRoot, state);
|
|
157
|
+
|
|
158
|
+
return { success: true, sessionId };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Adds a task to the open tasks list.
|
|
163
|
+
*
|
|
164
|
+
* @param {string} projectRoot - Root directory of the project
|
|
165
|
+
* @param {string} title - Task title
|
|
166
|
+
* @param {string} [description] - Optional description
|
|
167
|
+
* @returns {{ taskId: string }}
|
|
168
|
+
*/
|
|
169
|
+
function addTask(projectRoot, title, description) {
|
|
170
|
+
const state = loadSessionState(projectRoot);
|
|
171
|
+
const taskId = `TASK-${Date.now().toString(36).toUpperCase()}`;
|
|
172
|
+
|
|
173
|
+
const task = {
|
|
174
|
+
id: taskId,
|
|
175
|
+
title,
|
|
176
|
+
description: description || null,
|
|
177
|
+
createdAt: new Date().toISOString(),
|
|
178
|
+
status: 'open',
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
if (!Array.isArray(state.openTasks)) {
|
|
182
|
+
state.openTasks = [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
state.openTasks.push(task);
|
|
186
|
+
state.currentTask = taskId;
|
|
187
|
+
|
|
188
|
+
writeSessionState(projectRoot, state);
|
|
189
|
+
|
|
190
|
+
return { taskId };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Marks a task as completed, moving it from openTasks to completedTasks.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} projectRoot - Root directory of the project
|
|
197
|
+
* @param {string} taskId - ID of the task to complete
|
|
198
|
+
* @returns {{ success: boolean }}
|
|
199
|
+
*/
|
|
200
|
+
function completeTask(projectRoot, taskId) {
|
|
201
|
+
const state = loadSessionState(projectRoot);
|
|
202
|
+
|
|
203
|
+
if (!Array.isArray(state.openTasks)) {
|
|
204
|
+
return { success: false };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const taskIndex = state.openTasks.findIndex((task) => task.id === taskId);
|
|
208
|
+
|
|
209
|
+
if (taskIndex === -1) {
|
|
210
|
+
return { success: false };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const [task] = state.openTasks.splice(taskIndex, 1);
|
|
214
|
+
task.status = 'completed';
|
|
215
|
+
task.completedAt = new Date().toISOString();
|
|
216
|
+
|
|
217
|
+
if (!Array.isArray(state.completedTasks)) {
|
|
218
|
+
state.completedTasks = [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
state.completedTasks.push(task);
|
|
222
|
+
|
|
223
|
+
// Update currentTask
|
|
224
|
+
if (state.currentTask === taskId) {
|
|
225
|
+
state.currentTask = state.openTasks.length > 0 ? state.openTasks[0].id : null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
writeSessionState(projectRoot, state);
|
|
229
|
+
|
|
230
|
+
return { success: true };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Returns a summary of the current session state.
|
|
235
|
+
*
|
|
236
|
+
* @param {string} projectRoot - Root directory of the project
|
|
237
|
+
* @returns {object} Session summary
|
|
238
|
+
*/
|
|
239
|
+
function getSessionSummary(projectRoot) {
|
|
240
|
+
const state = loadSessionState(projectRoot);
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
sessionId: state.session?.id || null,
|
|
244
|
+
status: state.session?.status || 'new',
|
|
245
|
+
focus: state.session?.focus || null,
|
|
246
|
+
branch: state.repository?.currentBranch || null,
|
|
247
|
+
openTaskCount: Array.isArray(state.openTasks) ? state.openTasks.length : 0,
|
|
248
|
+
completedTaskCount: Array.isArray(state.completedTasks) ? state.completedTasks.length : 0,
|
|
249
|
+
currentTask: state.currentTask || null,
|
|
250
|
+
lastUpdated: state.lastUpdated || null,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
module.exports = {
|
|
255
|
+
loadSessionState,
|
|
256
|
+
startSession,
|
|
257
|
+
endSession,
|
|
258
|
+
addTask,
|
|
259
|
+
completeTask,
|
|
260
|
+
getSessionSummary,
|
|
261
|
+
};
|