antigravity-ai-kit 3.1.1 → 3.2.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/.agent/agents/planner.md +205 -62
- package/.agent/contexts/plan-quality-log.md +30 -0
- package/.agent/engine/loading-rules.json +37 -3
- package/.agent/hooks/hooks.json +10 -0
- package/.agent/manifest.json +4 -3
- package/.agent/skills/plan-validation/SKILL.md +192 -0
- package/.agent/skills/plan-writing/SKILL.md +47 -8
- package/.agent/skills/plan-writing/domain-enhancers.md +114 -0
- package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
- package/.agent/skills/plan-writing/plan-schema.md +119 -0
- package/.agent/workflows/plan.md +49 -5
- package/README.md +30 -29
- package/bin/ag-kit.js +26 -5
- package/lib/agent-registry.js +17 -3
- package/lib/agent-reputation.js +3 -11
- package/lib/circuit-breaker.js +195 -0
- package/lib/cli-commands.js +88 -1
- package/lib/config-validator.js +274 -0
- package/lib/conflict-detector.js +29 -22
- package/lib/constants.js +35 -0
- package/lib/engineering-manager.js +9 -27
- package/lib/error-budget.js +105 -29
- package/lib/hook-system.js +8 -4
- package/lib/identity.js +22 -27
- package/lib/io.js +74 -0
- package/lib/loading-engine.js +248 -35
- package/lib/logger.js +118 -0
- package/lib/marketplace.js +43 -20
- package/lib/plugin-system.js +55 -31
- package/lib/plugin-verifier.js +197 -0
- package/lib/rate-limiter.js +113 -0
- package/lib/security-scanner.js +1 -4
- package/lib/self-healing.js +58 -24
- package/lib/session-manager.js +51 -48
- package/lib/skill-sandbox.js +1 -1
- package/lib/task-governance.js +10 -11
- package/lib/task-model.js +42 -27
- package/lib/updater.js +1 -1
- package/lib/verify.js +4 -4
- package/lib/workflow-engine.js +88 -68
- package/lib/workflow-events.js +166 -0
- package/lib/workflow-persistence.js +19 -19
- package/package.json +2 -2
package/lib/error-budget.js
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
const fs = require('fs');
|
|
16
16
|
const path = require('path');
|
|
17
17
|
|
|
18
|
-
const AGENT_DIR = '
|
|
19
|
-
const
|
|
18
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
19
|
+
const { writeJsonAtomic } = require('./io');
|
|
20
|
+
const { createLogger } = require('./logger');
|
|
21
|
+
const log = createLogger('error-budget');
|
|
20
22
|
const RELIABILITY_CONFIG = 'reliability-config.json';
|
|
21
23
|
const METRICS_FILE = 'metrics.json';
|
|
22
24
|
|
|
@@ -133,12 +135,13 @@ function createEmptyMetrics() {
|
|
|
133
135
|
*/
|
|
134
136
|
function writeMetrics(projectRoot, metrics) {
|
|
135
137
|
const metricsPath = resolveMetricsPath(projectRoot);
|
|
136
|
-
const tempPath = `${metricsPath}.tmp`;
|
|
137
138
|
|
|
138
|
-
|
|
139
|
+
const updatedMetrics = {
|
|
140
|
+
...metrics,
|
|
141
|
+
lastUpdated: new Date().toISOString(),
|
|
142
|
+
};
|
|
139
143
|
|
|
140
|
-
|
|
141
|
-
fs.renameSync(tempPath, metricsPath);
|
|
144
|
+
writeJsonAtomic(metricsPath, updatedMetrics);
|
|
142
145
|
}
|
|
143
146
|
|
|
144
147
|
/**
|
|
@@ -151,9 +154,11 @@ function writeMetrics(projectRoot, metrics) {
|
|
|
151
154
|
*/
|
|
152
155
|
function recordTestResult(projectRoot, passed, failed) {
|
|
153
156
|
const metrics = loadMetrics(projectRoot);
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
writeMetrics(projectRoot, {
|
|
158
|
+
...metrics,
|
|
159
|
+
testsPassed: metrics.testsPassed + passed,
|
|
160
|
+
testsFailed: metrics.testsFailed + failed,
|
|
161
|
+
});
|
|
157
162
|
}
|
|
158
163
|
|
|
159
164
|
/**
|
|
@@ -165,14 +170,11 @@ function recordTestResult(projectRoot, passed, failed) {
|
|
|
165
170
|
*/
|
|
166
171
|
function recordBuildResult(projectRoot, success) {
|
|
167
172
|
const metrics = loadMetrics(projectRoot);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
metrics.buildsSucceeded
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
writeMetrics(projectRoot, metrics);
|
|
173
|
+
writeMetrics(projectRoot, {
|
|
174
|
+
...metrics,
|
|
175
|
+
buildsSucceeded: metrics.buildsSucceeded + (success ? 1 : 0),
|
|
176
|
+
buildsFailed: metrics.buildsFailed + (success ? 0 : 1),
|
|
177
|
+
});
|
|
176
178
|
}
|
|
177
179
|
|
|
178
180
|
/**
|
|
@@ -185,16 +187,11 @@ function recordBuildResult(projectRoot, success) {
|
|
|
185
187
|
*/
|
|
186
188
|
function recordDeployResult(projectRoot, success, rolledBack = false) {
|
|
187
189
|
const metrics = loadMetrics(projectRoot);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
metrics.deploysSucceeded
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (rolledBack) {
|
|
194
|
-
metrics.deploysRolledBack += 1;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
writeMetrics(projectRoot, metrics);
|
|
190
|
+
writeMetrics(projectRoot, {
|
|
191
|
+
...metrics,
|
|
192
|
+
deploysSucceeded: metrics.deploysSucceeded + (success && !rolledBack ? 1 : 0),
|
|
193
|
+
deploysRolledBack: metrics.deploysRolledBack + (rolledBack ? 1 : 0),
|
|
194
|
+
});
|
|
198
195
|
}
|
|
199
196
|
|
|
200
197
|
/**
|
|
@@ -273,14 +270,91 @@ function getBudgetReport(projectRoot) {
|
|
|
273
270
|
};
|
|
274
271
|
}
|
|
275
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Archives current metrics before resetting.
|
|
275
|
+
* Preserves historical data for trend analysis.
|
|
276
|
+
*
|
|
277
|
+
* @param {string} projectRoot - Root directory of the project
|
|
278
|
+
* @returns {{ archived: boolean, archivePath: string | null }}
|
|
279
|
+
*/
|
|
280
|
+
function archiveMetrics(projectRoot) {
|
|
281
|
+
const metrics = loadMetrics(projectRoot);
|
|
282
|
+
const hasData = metrics.testsPassed + metrics.testsFailed +
|
|
283
|
+
metrics.buildsSucceeded + metrics.buildsFailed +
|
|
284
|
+
metrics.deploysSucceeded + metrics.deploysRolledBack > 0;
|
|
285
|
+
|
|
286
|
+
if (!hasData) {
|
|
287
|
+
return { archived: false, archivePath: null };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const archiveDir = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, 'metrics-archive');
|
|
291
|
+
if (!fs.existsSync(archiveDir)) {
|
|
292
|
+
fs.mkdirSync(archiveDir, { recursive: true });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
296
|
+
const archivePath = path.join(archiveDir, `metrics-${timestamp}.json`);
|
|
297
|
+
|
|
298
|
+
const report = getBudgetReport(projectRoot);
|
|
299
|
+
const archiveEntry = {
|
|
300
|
+
...metrics,
|
|
301
|
+
archivedAt: new Date().toISOString(),
|
|
302
|
+
budgetStatus: report.status,
|
|
303
|
+
rates: report.rates,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
fs.writeFileSync(archivePath, JSON.stringify(archiveEntry, null, 2) + '\n', 'utf-8');
|
|
307
|
+
|
|
308
|
+
return { archived: true, archivePath };
|
|
309
|
+
}
|
|
310
|
+
|
|
276
311
|
/**
|
|
277
312
|
* Resets metrics for a new tracking period.
|
|
313
|
+
* Automatically archives previous period's data before resetting.
|
|
278
314
|
*
|
|
279
315
|
* @param {string} projectRoot - Root directory of the project
|
|
280
|
-
* @
|
|
316
|
+
* @param {object} [options] - Reset options
|
|
317
|
+
* @param {boolean} [options.skipArchive=false] - Skip archival
|
|
318
|
+
* @returns {{ archived: boolean, archivePath: string | null }}
|
|
281
319
|
*/
|
|
282
|
-
function resetMetrics(projectRoot) {
|
|
320
|
+
function resetMetrics(projectRoot, options = {}) {
|
|
321
|
+
let archiveResult = { archived: false, archivePath: null };
|
|
322
|
+
|
|
323
|
+
if (!options.skipArchive) {
|
|
324
|
+
archiveResult = archiveMetrics(projectRoot);
|
|
325
|
+
}
|
|
326
|
+
|
|
283
327
|
writeMetrics(projectRoot, createEmptyMetrics());
|
|
328
|
+
return archiveResult;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Returns historical metrics from the archive.
|
|
333
|
+
*
|
|
334
|
+
* @param {string} projectRoot - Root directory of the project
|
|
335
|
+
* @param {number} [limit=10] - Maximum entries to return
|
|
336
|
+
* @returns {object[]}
|
|
337
|
+
*/
|
|
338
|
+
function getMetricsHistory(projectRoot, limit = 10) {
|
|
339
|
+
const archiveDir = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, 'metrics-archive');
|
|
340
|
+
|
|
341
|
+
if (!fs.existsSync(archiveDir)) {
|
|
342
|
+
return [];
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const files = fs.readdirSync(archiveDir)
|
|
346
|
+
.filter((f) => f.startsWith('metrics-') && f.endsWith('.json'))
|
|
347
|
+
.sort()
|
|
348
|
+
.reverse()
|
|
349
|
+
.slice(0, limit);
|
|
350
|
+
|
|
351
|
+
return files.map((file) => {
|
|
352
|
+
try {
|
|
353
|
+
return JSON.parse(fs.readFileSync(path.join(archiveDir, file), 'utf-8'));
|
|
354
|
+
} catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}).filter(Boolean);
|
|
284
358
|
}
|
|
285
359
|
|
|
286
360
|
module.exports = {
|
|
@@ -291,4 +365,6 @@ module.exports = {
|
|
|
291
365
|
recordDeployResult,
|
|
292
366
|
getBudgetReport,
|
|
293
367
|
resetMetrics,
|
|
368
|
+
archiveMetrics,
|
|
369
|
+
getMetricsHistory,
|
|
294
370
|
};
|
package/lib/hook-system.js
CHANGED
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
16
|
|
|
17
|
-
const AGENT_DIR = '
|
|
18
|
-
|
|
17
|
+
const { AGENT_DIR, ENGINE_DIR, HOOKS_DIR } = require('./constants');
|
|
18
|
+
|
|
19
19
|
const HOOKS_FILE = 'hooks.json';
|
|
20
20
|
|
|
21
21
|
/**
|
|
@@ -50,7 +50,11 @@ function loadHooks(projectRoot) {
|
|
|
50
50
|
return { hooks: [] };
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
|
|
53
|
+
try {
|
|
54
|
+
return JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
|
|
55
|
+
} catch {
|
|
56
|
+
return { hooks: [] };
|
|
57
|
+
}
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
/**
|
|
@@ -146,7 +150,7 @@ function evaluateAction(action, context) {
|
|
|
146
150
|
|
|
147
151
|
if (actionLower.includes('loading-rules.json') || actionLower.includes('workflow-state.json')) {
|
|
148
152
|
const targetFile = actionLower.includes('loading-rules.json') ? 'loading-rules.json' : 'workflow-state.json';
|
|
149
|
-
const filePath = path.join(projectRoot, AGENT_DIR,
|
|
153
|
+
const filePath = path.join(projectRoot, AGENT_DIR, ENGINE_DIR, targetFile);
|
|
150
154
|
const exists = fs.existsSync(filePath);
|
|
151
155
|
|
|
152
156
|
return {
|
package/lib/identity.js
CHANGED
|
@@ -16,8 +16,8 @@ const path = require('path');
|
|
|
16
16
|
const crypto = require('crypto');
|
|
17
17
|
const { execSync } = require('child_process');
|
|
18
18
|
|
|
19
|
-
const AGENT_DIR = '
|
|
20
|
-
const
|
|
19
|
+
const { AGENT_DIR, ENGINE_DIR } = require('./constants');
|
|
20
|
+
const { writeJsonAtomic } = require('./io');
|
|
21
21
|
const IDENTITY_FILE = 'identity.json';
|
|
22
22
|
|
|
23
23
|
/** @type {readonly string[]} */
|
|
@@ -86,15 +86,7 @@ function loadIdentityRegistry(projectRoot) {
|
|
|
86
86
|
*/
|
|
87
87
|
function writeIdentityRegistry(projectRoot, registry) {
|
|
88
88
|
const identityPath = resolveIdentityPath(projectRoot);
|
|
89
|
-
|
|
90
|
-
const dir = path.dirname(identityPath);
|
|
91
|
-
|
|
92
|
-
if (!fs.existsSync(dir)) {
|
|
93
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2) + '\n', 'utf-8');
|
|
97
|
-
fs.renameSync(tempPath, identityPath);
|
|
89
|
+
writeJsonAtomic(identityPath, registry);
|
|
98
90
|
}
|
|
99
91
|
|
|
100
92
|
/**
|
|
@@ -136,33 +128,36 @@ function registerIdentity(projectRoot, { name, email, role }) {
|
|
|
136
128
|
const existingIndex = registry.developers.findIndex((d) => d.id === developerId);
|
|
137
129
|
|
|
138
130
|
if (existingIndex !== -1) {
|
|
139
|
-
// Update last active
|
|
140
|
-
registry.developers[existingIndex]
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
131
|
+
// Update last active (immutable)
|
|
132
|
+
const updatedDev = { ...registry.developers[existingIndex], lastActiveAt: now, name };
|
|
133
|
+
const updatedRegistry = {
|
|
134
|
+
...registry,
|
|
135
|
+
developers: registry.developers.map((d, i) => (i === existingIndex ? updatedDev : d)),
|
|
136
|
+
activeId: developerId,
|
|
137
|
+
};
|
|
138
|
+
writeIdentityRegistry(projectRoot, updatedRegistry);
|
|
139
|
+
return { success: true, identity: updatedDev, isNew: false };
|
|
145
140
|
}
|
|
146
141
|
|
|
142
|
+
// First developer becomes owner automatically
|
|
143
|
+
const assignedRole = registry.developers.length === 0 ? 'owner' : identityRole;
|
|
144
|
+
|
|
147
145
|
/** @type {Identity} */
|
|
148
146
|
const identity = {
|
|
149
147
|
id: developerId,
|
|
150
148
|
name,
|
|
151
149
|
email: email.toLowerCase().trim(),
|
|
152
|
-
role:
|
|
150
|
+
role: assignedRole,
|
|
153
151
|
registeredAt: now,
|
|
154
152
|
lastActiveAt: now,
|
|
155
153
|
};
|
|
156
154
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
registry.activeId = developerId;
|
|
165
|
-
writeIdentityRegistry(projectRoot, registry);
|
|
155
|
+
const updatedRegistry = {
|
|
156
|
+
...registry,
|
|
157
|
+
developers: [...registry.developers, identity],
|
|
158
|
+
activeId: developerId,
|
|
159
|
+
};
|
|
160
|
+
writeIdentityRegistry(projectRoot, updatedRegistry);
|
|
166
161
|
|
|
167
162
|
return { success: true, identity, isNew: true };
|
|
168
163
|
}
|
package/lib/io.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Antigravity AI Kit — Shared I/O Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic file write operations and safe JSON parsing
|
|
5
|
+
* used across all runtime modules. Single point for error
|
|
6
|
+
* handling around filesystem operations.
|
|
7
|
+
*
|
|
8
|
+
* @module lib/io
|
|
9
|
+
* @author Emre Dursun
|
|
10
|
+
* @since v3.2.0
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const { createLogger } = require('./logger');
|
|
18
|
+
const log = createLogger('io');
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Writes JSON data to a file atomically (temp file + rename).
|
|
22
|
+
* Creates parent directories if they don't exist.
|
|
23
|
+
*
|
|
24
|
+
* @param {string} filePath - Target file path
|
|
25
|
+
* @param {object} data - Data to serialize as JSON
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
function writeJsonAtomic(filePath, data) {
|
|
29
|
+
const dir = path.dirname(filePath);
|
|
30
|
+
if (!fs.existsSync(dir)) {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const tempPath = `${filePath}.tmp`;
|
|
35
|
+
try {
|
|
36
|
+
fs.writeFileSync(tempPath, JSON.stringify(data, null, 2) + '\n', 'utf-8');
|
|
37
|
+
fs.renameSync(tempPath, filePath);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
// Clean up temp file on failure
|
|
40
|
+
try {
|
|
41
|
+
if (fs.existsSync(tempPath)) {
|
|
42
|
+
fs.unlinkSync(tempPath);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Cleanup failure is non-critical
|
|
46
|
+
}
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Safely parses a JSON file, returning a default value on failure.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} filePath - Path to JSON file
|
|
55
|
+
* @param {*} defaultValue - Value to return if file doesn't exist or is invalid
|
|
56
|
+
* @returns {*} Parsed JSON or default value
|
|
57
|
+
*/
|
|
58
|
+
function readJsonSafe(filePath, defaultValue = null) {
|
|
59
|
+
if (!fs.existsSync(filePath)) {
|
|
60
|
+
return defaultValue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
log.debug('Failed to parse JSON file, returning default', { filePath, error: error.message });
|
|
67
|
+
return defaultValue;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
writeJsonAtomic,
|
|
73
|
+
readJsonSafe,
|
|
74
|
+
};
|