antigravity-ai-kit 3.2.0 → 3.4.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🚀 Antigravity AI Kit
2
2
 
3
- ![version](https://img.shields.io/badge/version-3.2.0-blue)
3
+ ![version](https://img.shields.io/badge/version-3.3.1-blue)
4
4
  ![license](https://img.shields.io/badge/license-MIT-green)
5
5
  ![AI Agents](https://img.shields.io/badge/AI%20Agents-19-purple)
6
6
  ![Skills](https://img.shields.io/badge/Skills-32-orange)
@@ -100,22 +100,14 @@ Creates a new project with `.agent/` pre-configured. Templates: `minimal`, `node
100
100
  npx antigravity-ai-kit init
101
101
  ```
102
102
 
103
- This automatically copies the `.agent/` folder to your project.
104
-
105
- ### Option 3: Manual Installation
103
+ ### 🔄 Updating
106
104
 
107
105
  ```bash
108
- # 1. Clone the repository
109
- git clone https://github.com/besync-labs/antigravity-ai-kit.git
110
-
111
- # 2. Copy .agent/ to your project
112
- cp -r antigravity-ai-kit/.agent/ your-project/.agent/
113
-
114
- # 3. Start your session
115
- /status
106
+ ag-kit update # Non-destructive preserves your customizations
107
+ ag-kit update --dry-run # Preview changes without applying
116
108
  ```
117
109
 
118
- That's it! The kit is now active and ready to accelerate your development.
110
+ > **Prefer `ag-kit update` over `ag-kit init --force`**. The update command preserves your session data, ADRs, learning contexts, and customizations. Use `init --force` only for clean reinstalls.
119
111
 
120
112
  ### ✅ Verify Installation
121
113
 
@@ -124,6 +116,31 @@ ag-kit verify # Manifest integrity check
124
116
  ag-kit scan # Security scan
125
117
  ```
126
118
 
119
+ ### 🛡️ Safety Guarantees
120
+
121
+ Antigravity AI Kit is designed to **never touch your project files**. All operations are scoped to the `.agent/` directory.
122
+
123
+ | Your Project Files | Safe? | Details |
124
+ |:---|:---|:---|
125
+ | Source code (`src/`, `lib/`, `app/`) | ✅ Never touched | Init/update only operates on `.agent/` |
126
+ | Config files (`.env`, `package.json`) | ✅ Never touched | No project config is read or written |
127
+ | Documentation (`docs/`, `README.md`) | ✅ Never touched | Only `.agent/` docs are managed |
128
+ | Tests (`tests/`, `__tests__/`) | ✅ Never touched | Kit tests are internal to the package |
129
+ | Platform files (`android/`, `ios/`) | ✅ Never touched | No platform-specific operations |
130
+
131
+ **`init --force` safety features (v3.3.1+):**
132
+ - 🔒 **Auto-backup**: Creates timestamped backup of existing `.agent/` before overwriting
133
+ - ⚛️ **Atomic copy**: Uses temp directory + rename to prevent corruption on failure
134
+ - 🔗 **Symlink guard**: Skips symbolic links to prevent path traversal attacks
135
+ - ⚠️ **Session warning**: Alerts if active work-in-progress would be destroyed
136
+ - 🔍 **Dry-run preview**: `--dry-run --force` shows exactly which user files would be overwritten
137
+
138
+ **`update` preserved files:**
139
+ - `session-context.md` — Your active session notes
140
+ - `session-state.json` — Your session metadata
141
+ - `decisions/` — Your Architecture Decision Records
142
+ - `contexts/` — Your learning data and plan quality logs
143
+
127
144
  ---
128
145
 
129
146
  ## 🏗️ Architecture Overview
package/bin/ag-kit.js CHANGED
@@ -16,6 +16,7 @@ const path = require('path');
16
16
 
17
17
  const VERSION = require('../package.json').version;
18
18
  const AGENT_FOLDER = '.agent';
19
+ const { safeCopyDirSync, readJsonSafe } = require('../lib/io');
19
20
 
20
21
  // ANSI colors
21
22
  const colors = {
@@ -49,7 +50,7 @@ ${colors.reset}
49
50
  ${colors.green}🚀 Antigravity AI Kit v${VERSION}${colors.reset}
50
51
  ${colors.yellow} Transform Your IDE into an Autonomous Engineering Team${colors.reset}
51
52
 
52
- • 19 AI Agents • 31 Skills
53
+ • 19 AI Agents • 32 Skills
53
54
  • 31 Commands • 14 Workflows
54
55
  • Runtime Engine • Error Budget
55
56
  `);
@@ -95,23 +96,28 @@ ${colors.bright}IDE Reference:${colors.reset}
95
96
  `);
96
97
  }
97
98
 
98
- function copyFolderSync(src, dest) {
99
- if (!fs.existsSync(dest)) {
100
- fs.mkdirSync(dest, { recursive: true });
101
- }
102
-
103
- const entries = fs.readdirSync(src, { withFileTypes: true });
104
-
105
- for (const entry of entries) {
106
- const srcPath = path.join(src, entry.name);
107
- const destPath = path.join(dest, entry.name);
108
-
109
- if (entry.isDirectory()) {
110
- copyFolderSync(srcPath, destPath);
111
- } else {
112
- fs.copyFileSync(srcPath, destPath);
113
- }
114
- }
99
+ /**
100
+ * Creates a timestamped backup of a directory.
101
+ * @param {string} dirPath - Directory to back up
102
+ * @returns {string} Path to the backup directory
103
+ */
104
+ function backupDirectory(dirPath) {
105
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
106
+ const backupPath = `${dirPath}.backup-${timestamp}`;
107
+ safeCopyDirSync(dirPath, backupPath);
108
+ return backupPath;
109
+ }
110
+
111
+ /**
112
+ * Checks if there is an active session with in-progress work.
113
+ * @param {string} agentPath - Path to existing .agent directory
114
+ * @returns {boolean} True if session-state indicates active work
115
+ */
116
+ function hasActiveSession(agentPath) {
117
+ const statePath = path.join(agentPath, 'session-state.json');
118
+ const state = readJsonSafe(statePath, null);
119
+ if (!state) return false;
120
+ return state.currentTask || state.inProgress || state.activeWorkflow;
115
121
  }
116
122
 
117
123
  function countItems(dir, type = 'dir') {
@@ -146,24 +152,76 @@ function initCommand(options) {
146
152
  // Check if already exists
147
153
  if (fs.existsSync(agentPath) && !options.force) {
148
154
  log(`\n⚠️ ${AGENT_FOLDER} folder already exists!`, 'yellow');
149
- log(' Use --force to overwrite\n', 'yellow');
155
+ log(' Use --force to overwrite', 'yellow');
156
+ log(' Use ag-kit update for non-destructive updates\n', 'yellow');
150
157
  process.exit(1);
151
158
  }
152
159
 
160
+ // M-3: Active session warning for --force
161
+ if (options.force && fs.existsSync(agentPath) && hasActiveSession(agentPath)) {
162
+ log('\n⚠️ Active session detected! Force-overwrite will destroy in-progress work.', 'yellow');
163
+ log(' Consider using ag-kit update instead.\n', 'yellow');
164
+ }
165
+
153
166
  if (options.dryRun) {
154
167
  log('\n🔍 Dry run mode - no changes will be made\n', 'cyan');
155
168
  log(` Would copy: ${sourcePath}`, 'cyan');
156
169
  log(` To: ${agentPath}\n`, 'cyan');
170
+ // M-2: Show force damage preview
171
+ if (options.force && fs.existsSync(agentPath)) {
172
+ log(' ⚠️ --force would overwrite these user files:', 'yellow');
173
+ const userFiles = ['session-context.md', 'session-state.json'];
174
+ const userDirs = ['decisions', 'contexts'];
175
+ for (const f of userFiles) {
176
+ if (fs.existsSync(path.join(agentPath, f))) {
177
+ log(` • ${f}`, 'yellow');
178
+ }
179
+ }
180
+ for (const d of userDirs) {
181
+ if (fs.existsSync(path.join(agentPath, d))) {
182
+ log(` • ${d}/ (directory)`, 'yellow');
183
+ }
184
+ }
185
+ log('', 'reset');
186
+ }
157
187
  return;
158
188
  }
159
189
 
160
- // Copy .agent folder
161
- logStep('1/3', 'Copying .agent folder...');
190
+ // C-2: Auto-backup before force-overwrite
191
+ if (options.force && fs.existsSync(agentPath)) {
192
+ logStep('1/4', 'Backing up existing .agent folder...');
193
+ try {
194
+ const backupPath = backupDirectory(agentPath);
195
+ log(` ✓ Backup created: ${path.basename(backupPath)}`, 'green');
196
+ } catch (err) {
197
+ log(` ⚠️ Backup failed: ${err.message}`, 'yellow');
198
+ log(' Proceeding without backup...', 'yellow');
199
+ }
200
+ }
201
+
202
+ // C-3: Atomic copy via temp directory
203
+ const stepPrefix = options.force && fs.existsSync(agentPath) ? '2/4' : '1/3';
204
+ logStep(stepPrefix, 'Copying .agent folder...');
162
205
 
206
+ const tempPath = `${agentPath}.tmp-${Date.now()}`;
163
207
  try {
164
- copyFolderSync(sourcePath, agentPath);
208
+ safeCopyDirSync(sourcePath, tempPath);
209
+ // Remove old directory if force-overwriting
210
+ if (fs.existsSync(agentPath)) {
211
+ fs.rmSync(agentPath, { recursive: true, force: true });
212
+ }
213
+ // Atomic rename: move temp to final
214
+ fs.renameSync(tempPath, agentPath);
165
215
  log(' ✓ Copied successfully', 'green');
166
216
  } catch (err) {
217
+ // Cleanup temp directory on failure
218
+ try {
219
+ if (fs.existsSync(tempPath)) {
220
+ fs.rmSync(tempPath, { recursive: true, force: true });
221
+ }
222
+ } catch {
223
+ // Cleanup failure is non-critical
224
+ }
167
225
  log(` ✗ Failed to copy: ${err.message}`, 'red');
168
226
  process.exit(1);
169
227
  }
@@ -366,6 +424,13 @@ function updateCommand(updateOptions) {
366
424
  const updater = require('../lib/updater');
367
425
  const isDryRun = updateOptions.dryRun;
368
426
 
427
+ // M-1: Show version transition
428
+ const currentManifest = readJsonSafe(path.join(agentPath, 'manifest.json'), {});
429
+ const currentVersion = currentManifest.kitVersion || 'unknown';
430
+ if (currentVersion !== VERSION) {
431
+ log(`\n 📦 Upgrading from v${currentVersion} → v${VERSION}`, 'cyan');
432
+ }
433
+
369
434
  if (isDryRun) {
370
435
  log('\n🔍 Dry run mode — no changes will be made\n', 'cyan');
371
436
  }
package/lib/io.js CHANGED
@@ -68,7 +68,44 @@ function readJsonSafe(filePath, defaultValue = null) {
68
68
  }
69
69
  }
70
70
 
71
+ /**
72
+ * Recursively copies a directory, skipping symbolic links for security.
73
+ *
74
+ * Symlinks are skipped because they could point outside the intended
75
+ * scope (e.g., outside .agent/), enabling path traversal attacks.
76
+ *
77
+ * @param {string} src - Source directory path
78
+ * @param {string} dest - Destination directory path
79
+ * @returns {void}
80
+ */
81
+ function safeCopyDirSync(src, dest) {
82
+ if (!fs.existsSync(dest)) {
83
+ fs.mkdirSync(dest, { recursive: true });
84
+ }
85
+
86
+ const entries = fs.readdirSync(src, { withFileTypes: true });
87
+
88
+ for (const entry of entries) {
89
+ const srcPath = path.join(src, entry.name);
90
+ const destPath = path.join(dest, entry.name);
91
+
92
+ // Security: skip symlinks to prevent path traversal
93
+ const stat = fs.lstatSync(srcPath);
94
+ if (stat.isSymbolicLink()) {
95
+ log.debug('Skipping symlink for security', { path: srcPath });
96
+ continue;
97
+ }
98
+
99
+ if (entry.isDirectory()) {
100
+ safeCopyDirSync(srcPath, destPath);
101
+ } else {
102
+ fs.copyFileSync(srcPath, destPath);
103
+ }
104
+ }
105
+ }
106
+
71
107
  module.exports = {
72
108
  writeJsonAtomic,
73
109
  readJsonSafe,
110
+ safeCopyDirSync,
74
111
  };
@@ -16,7 +16,7 @@ const fs = require('fs');
16
16
  const path = require('path');
17
17
 
18
18
  const { AGENT_DIR, ENGINE_DIR, PLUGINS_DIR, HOOKS_DIR } = require('./constants');
19
- const { writeJsonAtomic } = require('./io');
19
+ const { writeJsonAtomic, safeCopyDirSync } = require('./io');
20
20
  const { createLogger } = require('./logger');
21
21
  const log = createLogger('plugin-system');
22
22
  const PLUGINS_REGISTRY = 'plugins-registry.json';
@@ -289,7 +289,7 @@ function installPlugin(pluginPath, projectRoot) {
289
289
  for (const skillDir of (manifest.skills || [])) {
290
290
  const src = path.join(pluginPath, 'skills', skillDir);
291
291
  const dest = path.join(projectRoot, AGENT_DIR, 'skills', skillDir);
292
- copyDirSync(src, dest);
292
+ safeCopyDirSync(src, dest);
293
293
  installed.skills++;
294
294
  }
295
295
 
@@ -593,31 +593,7 @@ function copyFileSync(src, dest) {
593
593
  fs.copyFileSync(src, dest);
594
594
  }
595
595
 
596
- /**
597
- * Recursively copies a directory.
598
- *
599
- * @param {string} src - Source directory
600
- * @param {string} dest - Destination directory
601
- * @returns {void}
602
- */
603
- function copyDirSync(src, dest) {
604
- if (!fs.existsSync(dest)) {
605
- fs.mkdirSync(dest, { recursive: true });
606
- }
607
-
608
- const entries = fs.readdirSync(src, { withFileTypes: true });
609
596
 
610
- for (const entry of entries) {
611
- const srcPath = path.join(src, entry.name);
612
- const destPath = path.join(dest, entry.name);
613
-
614
- if (entry.isDirectory()) {
615
- copyDirSync(srcPath, destPath);
616
- } else {
617
- fs.copyFileSync(srcPath, destPath);
618
- }
619
- }
620
- }
621
597
 
622
598
  module.exports = {
623
599
  validatePlugin,
@@ -262,6 +262,12 @@ function collectAllFiles(dirPath) {
262
262
  for (const entry of entries) {
263
263
  const fullPath = path.join(dirPath, entry.name);
264
264
 
265
+ // Security: skip symlinks to prevent path traversal
266
+ const stat = fs.lstatSync(fullPath);
267
+ if (stat.isSymbolicLink()) {
268
+ continue;
269
+ }
270
+
265
271
  if (entry.isDirectory() && entry.name !== 'node_modules' && entry.name !== '.git') {
266
272
  files.push(...collectAllFiles(fullPath));
267
273
  } else if (entry.isFile()) {
package/lib/updater.js CHANGED
@@ -27,6 +27,7 @@ const PRESERVED_FILES = new Set([
27
27
  /** Directories whose contents should never be overwritten */
28
28
  const PRESERVED_DIRS = new Set([
29
29
  'decisions',
30
+ 'contexts',
30
31
  ]);
31
32
 
32
33
  /**
package/lib/verify.js CHANGED
@@ -189,6 +189,32 @@ function runAllChecks(projectRoot) {
189
189
  results.push(checkJsonFile(path.join(agentDir, ENGINE_DIR, engineFile), `engine:${engineFile}`));
190
190
  }
191
191
 
192
+ // --- Check 10a: Rule files exist ---
193
+ const manifestRules = manifest.capabilities?.rules?.items || [];
194
+ for (const rule of manifestRules) {
195
+ const rulePath = path.join(agentDir, rule.file);
196
+ const exists = fs.existsSync(rulePath);
197
+ results.push({
198
+ name: `rule-file:${rule.name}`,
199
+ status: exists ? 'pass' : 'fail',
200
+ message: exists ? `Rule exists: ${rule.name}` : `Missing rule file: ${rule.file}`,
201
+ });
202
+ }
203
+
204
+ // --- Check 10b: Rule count matches ---
205
+ const ruleCountManifest = manifest.capabilities?.rules?.count || 0;
206
+ const ruleCountFS = fs.existsSync(path.join(agentDir, 'rules'))
207
+ ? fs.readdirSync(path.join(agentDir, 'rules')).filter((f) => f.endsWith('.md') && f !== 'README.md').length
208
+ : 0;
209
+ results.push({
210
+ name: 'rule-count',
211
+ status: ruleCountManifest === ruleCountFS ? 'pass' : 'fail',
212
+ message:
213
+ ruleCountManifest === ruleCountFS
214
+ ? `Rule count matches: ${ruleCountFS}`
215
+ : `Rule count mismatch: manifest=${ruleCountManifest}, filesystem=${ruleCountFS}`,
216
+ });
217
+
192
218
  // --- Check 11: Hooks file valid ---
193
219
  results.push(checkJsonFile(path.join(agentDir, HOOKS_DIR, 'hooks.json'), 'hooks-json'));
194
220
 
@@ -212,6 +238,19 @@ function runAllChecks(projectRoot) {
212
238
  });
213
239
  }
214
240
  }
241
+ // --- Check 13: Cross-reference — alwaysLoadRules files exist ---
242
+ const mandatoryRules = loadingRules.planningMandates?.alwaysLoadRules || [];
243
+ for (const ruleName of mandatoryRules) {
244
+ const rulePath = path.join(agentDir, 'rules', `${ruleName}.md`);
245
+ const ruleExists = fs.existsSync(rulePath);
246
+ results.push({
247
+ name: `xref:mandatory-rule:${ruleName}`,
248
+ status: ruleExists ? 'pass' : 'fail',
249
+ message: ruleExists
250
+ ? `Mandatory rule "${ruleName}" exists`
251
+ : `Mandatory rule "${ruleName}" referenced in alwaysLoadRules but file missing: rules/${ruleName}.md`,
252
+ });
253
+ }
215
254
  } catch {
216
255
  results.push({ name: 'xref:loading-rules', status: 'warn', message: 'Could not parse loading-rules.json for cross-reference check' });
217
256
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "antigravity-ai-kit",
3
- "version": "3.2.0",
4
- "description": "🚀 Trust-Grade AI development framework with a 29-module runtime engine — 19 Agents, 32 Skills, 31 Commands, 14 Workflows, 327 Tests. Workflow enforcement, task governance, agent reputation, self-healing, and skill marketplace.",
3
+ "version": "3.4.0",
4
+ "description": "🚀 Trust-Grade AI development framework with a 29-module runtime engine — 19 Agents, 32 Skills, 31 Commands, 14 Workflows, 8 Rules, 330 Tests. Workflow enforcement, task governance, agent reputation, self-healing, and skill marketplace.",
5
5
  "main": "bin/ag-kit.js",
6
6
  "bin": {
7
7
  "ag-kit": "./bin/ag-kit.js"