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.
Files changed (43) hide show
  1. package/.agent/agents/planner.md +205 -62
  2. package/.agent/contexts/plan-quality-log.md +30 -0
  3. package/.agent/engine/loading-rules.json +37 -3
  4. package/.agent/hooks/hooks.json +10 -0
  5. package/.agent/manifest.json +4 -3
  6. package/.agent/skills/plan-validation/SKILL.md +192 -0
  7. package/.agent/skills/plan-writing/SKILL.md +47 -8
  8. package/.agent/skills/plan-writing/domain-enhancers.md +114 -0
  9. package/.agent/skills/plan-writing/plan-retrospective.md +116 -0
  10. package/.agent/skills/plan-writing/plan-schema.md +119 -0
  11. package/.agent/workflows/plan.md +49 -5
  12. package/README.md +30 -29
  13. package/bin/ag-kit.js +26 -5
  14. package/lib/agent-registry.js +17 -3
  15. package/lib/agent-reputation.js +3 -11
  16. package/lib/circuit-breaker.js +195 -0
  17. package/lib/cli-commands.js +88 -1
  18. package/lib/config-validator.js +274 -0
  19. package/lib/conflict-detector.js +29 -22
  20. package/lib/constants.js +35 -0
  21. package/lib/engineering-manager.js +9 -27
  22. package/lib/error-budget.js +105 -29
  23. package/lib/hook-system.js +8 -4
  24. package/lib/identity.js +22 -27
  25. package/lib/io.js +74 -0
  26. package/lib/loading-engine.js +248 -35
  27. package/lib/logger.js +118 -0
  28. package/lib/marketplace.js +43 -20
  29. package/lib/plugin-system.js +55 -31
  30. package/lib/plugin-verifier.js +197 -0
  31. package/lib/rate-limiter.js +113 -0
  32. package/lib/security-scanner.js +1 -4
  33. package/lib/self-healing.js +58 -24
  34. package/lib/session-manager.js +51 -48
  35. package/lib/skill-sandbox.js +1 -1
  36. package/lib/task-governance.js +10 -11
  37. package/lib/task-model.js +42 -27
  38. package/lib/updater.js +1 -1
  39. package/lib/verify.js +4 -4
  40. package/lib/workflow-engine.js +88 -68
  41. package/lib/workflow-events.js +166 -0
  42. package/lib/workflow-persistence.js +19 -19
  43. package/package.json +2 -2
@@ -15,8 +15,10 @@
15
15
  const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
- const AGENT_DIR = '.agent';
19
- const ENGINE_DIR = 'engine';
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
- metrics.lastUpdated = new Date().toISOString();
139
+ const updatedMetrics = {
140
+ ...metrics,
141
+ lastUpdated: new Date().toISOString(),
142
+ };
139
143
 
140
- fs.writeFileSync(tempPath, JSON.stringify(metrics, null, 2) + '\n', 'utf-8');
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
- metrics.testsPassed += passed;
155
- metrics.testsFailed += failed;
156
- writeMetrics(projectRoot, metrics);
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
- if (success) {
170
- metrics.buildsSucceeded += 1;
171
- } else {
172
- metrics.buildsFailed += 1;
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
- if (success && !rolledBack) {
190
- metrics.deploysSucceeded += 1;
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
- * @returns {void}
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
  };
@@ -14,8 +14,8 @@
14
14
  const fs = require('fs');
15
15
  const path = require('path');
16
16
 
17
- const AGENT_DIR = '.agent';
18
- const HOOKS_DIR = 'hooks';
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
- return JSON.parse(fs.readFileSync(hooksPath, 'utf-8'));
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, 'engine', targetFile);
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 = '.agent';
20
- const ENGINE_DIR = 'engine';
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
- const tempPath = `${identityPath}.tmp`;
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].lastActiveAt = now;
141
- registry.developers[existingIndex].name = name;
142
- registry.activeId = developerId;
143
- writeIdentityRegistry(projectRoot, registry);
144
- return { success: true, identity: registry.developers[existingIndex], isNew: false };
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: identityRole,
150
+ role: assignedRole,
153
151
  registeredAt: now,
154
152
  lastActiveAt: now,
155
153
  };
156
154
 
157
- registry.developers.push(identity);
158
-
159
- // First developer becomes owner automatically
160
- if (registry.developers.length === 1) {
161
- identity.role = 'owner';
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
+ };