delimit-cli 4.7.3 โ 4.7.4
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/bin/delimit-cli.js +152 -1
- package/bin/delimit-setup.js +88 -6
- package/bin/delimit.js +10 -25
- package/gateway/ai/backends/governance_bridge.py +52 -0
- package/gateway/ai/backends/repo_bridge.py +12 -0
- package/gateway/ai/backends/tools_infra.py +43 -1
- package/gateway/ai/cli_contract.py +12 -0
- package/gateway/ai/custom_gemini_repl.py +80 -0
- package/gateway/ai/delimit_daemon.py +8 -0
- package/gateway/ai/gemini_vertex_shim.py +38 -0
- package/gateway/ai/license_core.cpython-310-x86_64-linux-gnu.so +0 -0
- package/gateway/ai/release_sync.py +43 -8
- package/gateway/ai/route_daemon.py +98 -0
- package/gateway/ai/server.py +71 -1
- package/gateway/ai/session_phoenix.py +101 -136
- package/gateway/ai/supabase_sync.py +58 -0
- package/gateway/ai/swarm.py +2 -0
- package/gateway/ai/tui.py +81 -0
- package/gateway/core/ci_formatter.py +89 -61
- package/gateway/core/diff_engine_v2.py +208 -627
- package/gateway/core/explainer.py +67 -34
- package/lib/ai-sbom-engine.js +1 -0
- package/lib/auth-setup.js +10 -1
- package/lib/chat-repl.js +244 -0
- package/lib/cross-model-hooks.js +111 -0
- package/lib/timeline-engine.js +60 -0
- package/lib/wrap-engine.js +67 -11
- package/package.json +1 -1
|
@@ -263,62 +263,95 @@ def _render_pr_comment(ctx: Dict) -> str:
|
|
|
263
263
|
total = ctx["counts"]["total"]
|
|
264
264
|
additive_count = ctx["counts"]["additive"]
|
|
265
265
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
266
|
+
if bc == 0:
|
|
267
|
+
# โโ GREEN PATH โโ
|
|
268
|
+
semver_label = bump.upper() if bump != "none" else "NONE"
|
|
269
|
+
lines.append("## \U0001f6e1\ufe0f Governance Passed")
|
|
270
|
+
lines.append("")
|
|
271
|
+
if total > 0:
|
|
272
|
+
lines.append(
|
|
273
|
+
f"> **No breaking API changes detected.** "
|
|
274
|
+
f"{additive_count} additive change{'s' if additive_count != 1 else ''} "
|
|
275
|
+
f"found \u2014 Semver: **{semver_label}**"
|
|
276
|
+
)
|
|
277
|
+
else:
|
|
278
|
+
lines.append("> **No breaking API changes detected.**")
|
|
279
|
+
lines.append("")
|
|
269
280
|
|
|
270
|
-
|
|
271
|
-
|
|
281
|
+
# Additive changes (collapsed)
|
|
282
|
+
additive = ctx["additive_changes"]
|
|
283
|
+
if additive:
|
|
284
|
+
lines.append("<details>")
|
|
285
|
+
lines.append(f"<summary>\u2705 New additions ({len(additive)})</summary>")
|
|
286
|
+
lines.append("")
|
|
287
|
+
for c in additive:
|
|
288
|
+
lines.append(f"- `{c['path']}` \u2014 {c['message']}")
|
|
289
|
+
lines.append("")
|
|
290
|
+
lines.append("</details>")
|
|
291
|
+
lines.append("")
|
|
272
292
|
else:
|
|
273
|
-
|
|
274
|
-
|
|
293
|
+
# โโ RED PATH โโ
|
|
294
|
+
lines.append("## \U0001f6e1\ufe0f Breaking API Changes Detected")
|
|
295
|
+
lines.append("")
|
|
275
296
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
lines.append(" ยท ".join(parts))
|
|
283
|
-
lines.append("")
|
|
297
|
+
# Summary card
|
|
298
|
+
parts = [f"\U0001f534 **{bc} breaking change{'s' if bc != 1 else ''}**"]
|
|
299
|
+
parts.append("Semver: **MAJOR**")
|
|
300
|
+
separator = " \u00b7 "
|
|
301
|
+
lines.append(f"> {separator.join(parts)}")
|
|
302
|
+
lines.append("")
|
|
284
303
|
|
|
285
|
-
|
|
286
|
-
|
|
304
|
+
# Stats table
|
|
305
|
+
lines.append("| | Count |")
|
|
306
|
+
lines.append("|---|---|")
|
|
307
|
+
lines.append(f"| Total changes | {total} |")
|
|
308
|
+
lines.append(f"| Breaking | {bc} |")
|
|
309
|
+
lines.append(f"| Additive | {additive_count} |")
|
|
310
|
+
lines.append("")
|
|
311
|
+
|
|
312
|
+
# Breaking changes table
|
|
287
313
|
lines.append("### Breaking Changes")
|
|
288
314
|
lines.append("")
|
|
289
|
-
lines.append("|
|
|
290
|
-
lines.append("
|
|
315
|
+
lines.append("| Severity | Change | Location |")
|
|
316
|
+
lines.append("|----------|--------|----------|")
|
|
291
317
|
for c in ctx["breaking_changes"]:
|
|
292
318
|
change_type = c.get("type", "breaking")
|
|
293
319
|
severity = _pr_severity(change_type)
|
|
294
|
-
lines.append(f"| {c['message']} | `{c['path']}` |
|
|
320
|
+
lines.append(f"| {severity} | {c['message']} | `{c['path']}` |")
|
|
295
321
|
lines.append("")
|
|
296
322
|
|
|
297
323
|
# Migration guidance
|
|
298
324
|
lines.append("<details>")
|
|
299
|
-
lines.append("<summary
|
|
325
|
+
lines.append("<summary>\U0001f4cb Migration guide</summary>")
|
|
300
326
|
lines.append("")
|
|
301
327
|
for i, c in enumerate(ctx["breaking_changes"], 1):
|
|
302
|
-
lines.append(f"**{i}. {c['path']}
|
|
303
|
-
lines.append(f"
|
|
328
|
+
lines.append(f"**{i}. `{c['path']}`**")
|
|
329
|
+
lines.append(f"{_pr_migration_hint(c)}")
|
|
304
330
|
lines.append("")
|
|
305
331
|
lines.append("</details>")
|
|
306
332
|
lines.append("")
|
|
307
333
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
334
|
+
# Additive changes
|
|
335
|
+
additive = ctx["additive_changes"]
|
|
336
|
+
if additive:
|
|
337
|
+
lines.append("<details>")
|
|
338
|
+
lines.append(f"<summary>\u2705 New additions ({len(additive)})</summary>")
|
|
339
|
+
lines.append("")
|
|
340
|
+
for c in additive:
|
|
341
|
+
lines.append(f"- `{c['path']}` \u2014 {c['message']}")
|
|
342
|
+
lines.append("")
|
|
343
|
+
lines.append("</details>")
|
|
344
|
+
lines.append("")
|
|
345
|
+
|
|
346
|
+
lines.append("> **Fix locally:** `npx delimit-cli lint`")
|
|
318
347
|
lines.append("")
|
|
319
348
|
|
|
320
349
|
lines.append("---")
|
|
321
|
-
lines.append(
|
|
350
|
+
lines.append(
|
|
351
|
+
"Powered by [Delimit](https://delimit.ai) \u00b7 "
|
|
352
|
+
"[Docs](https://delimit.ai/docs) \u00b7 "
|
|
353
|
+
"[Install](https://github.com/marketplace/actions/delimit-api-governance)"
|
|
354
|
+
)
|
|
322
355
|
return "\n".join(lines)
|
|
323
356
|
|
|
324
357
|
|
package/lib/ai-sbom-engine.js
CHANGED
|
@@ -17,6 +17,7 @@ const KNOWN_MODEL_PROVIDERS = [
|
|
|
17
17
|
{ pattern: /claude|anthropic/i, vendor: 'anthropic', family: 'claude' },
|
|
18
18
|
{ pattern: /openai|gpt-\d|o1|o3/i, vendor: 'openai', family: 'gpt' },
|
|
19
19
|
{ pattern: /gemini|vertex/i, vendor: 'google', family: 'gemini' },
|
|
20
|
+
{ pattern: /antigravity|agy/i, vendor: 'google', family: 'antigravity' },
|
|
20
21
|
{ pattern: /codex/i, vendor: 'openai', family: 'codex' },
|
|
21
22
|
{ pattern: /grok|xai/i, vendor: 'xai', family: 'grok' },
|
|
22
23
|
{ pattern: /llama|mistral/i, vendor: 'meta-or-mistral', family: 'open-weight' },
|
package/lib/auth-setup.js
CHANGED
|
@@ -55,6 +55,7 @@ class DelimitAuthSetup {
|
|
|
55
55
|
'claude': 'Anthropic Claude',
|
|
56
56
|
'openai': 'OpenAI GPT',
|
|
57
57
|
'gemini': 'Google Gemini',
|
|
58
|
+
'antigravity': 'Google Antigravity',
|
|
58
59
|
'codex': 'GitHub Copilot'
|
|
59
60
|
};
|
|
60
61
|
|
|
@@ -223,6 +224,11 @@ class DelimitAuthSetup {
|
|
|
223
224
|
config.projectId = await this.prompt(` GCP Project ID (optional): `, '');
|
|
224
225
|
break;
|
|
225
226
|
|
|
227
|
+
case 'antigravity':
|
|
228
|
+
config.apiKey = await this.promptSecret(` Google AI API key (optional): `);
|
|
229
|
+
config.projectId = await this.prompt(` GCP Project ID (optional): `, '');
|
|
230
|
+
break;
|
|
231
|
+
|
|
226
232
|
case 'codex':
|
|
227
233
|
config.token = await this.promptSecret(` GitHub Copilot token: `);
|
|
228
234
|
break;
|
|
@@ -685,6 +691,9 @@ class DelimitAuthSetup {
|
|
|
685
691
|
if (credentials.gemini?.apiKey) {
|
|
686
692
|
envContent += `export GOOGLE_AI_API_KEY="${credentials.gemini.apiKey}"\n`;
|
|
687
693
|
}
|
|
694
|
+
if (credentials.antigravity?.apiKey) {
|
|
695
|
+
envContent += `export ANTIGRAVITY_API_KEY="${credentials.antigravity.apiKey}"\n`;
|
|
696
|
+
}
|
|
688
697
|
|
|
689
698
|
// Organization
|
|
690
699
|
if (credentials.organization?.policyUrl) {
|
|
@@ -860,7 +869,7 @@ class DelimitAuthSetup {
|
|
|
860
869
|
if (credentials.github.ghToken) console.log(chalk.gray(' - GitHub CLI configured'));
|
|
861
870
|
}
|
|
862
871
|
|
|
863
|
-
for (const tool of ['claude', 'openai', 'gemini', 'codex']) {
|
|
872
|
+
for (const tool of ['claude', 'openai', 'gemini', 'antigravity', 'codex']) {
|
|
864
873
|
if (credentials[tool]) {
|
|
865
874
|
console.log(chalk.white(` โข ${tool}: Configured`));
|
|
866
875
|
}
|
package/lib/chat-repl.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const { spawnSync, execSync } = require('child_process');
|
|
6
|
+
|
|
7
|
+
class DelimitChatREPL {
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.apiFallbackEnabled = options.apiFallback !== undefined ?
|
|
10
|
+
!!options.apiFallback :
|
|
11
|
+
(process.env.DELIMIT_API_FALLBACK === 'true' || false);
|
|
12
|
+
this.failedModels = new Set();
|
|
13
|
+
this.modelsConfig = this.loadModels();
|
|
14
|
+
this.routesConfig = this.loadRoutes();
|
|
15
|
+
this.agentName = 'orchestrator'; // Default agent
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
loadModels() {
|
|
19
|
+
const modelsPath = path.join(os.homedir(), '.delimit', 'models.json');
|
|
20
|
+
if (fs.existsSync(modelsPath)) {
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(fs.readFileSync(modelsPath, 'utf-8'));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
console.error(chalk.red('Failed to parse models.json'));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { fallbacks: { default: [] } };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
loadRoutes() {
|
|
31
|
+
const routesPath = path.join(os.homedir(), '.delimit', 'routes.json');
|
|
32
|
+
if (fs.existsSync(routesPath)) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(routesPath, 'utf-8'));
|
|
35
|
+
} catch (e) {}
|
|
36
|
+
}
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getActiveChain() {
|
|
41
|
+
const chain = this.modelsConfig.fallbacks?.['default'] || [];
|
|
42
|
+
const activeModels = [];
|
|
43
|
+
|
|
44
|
+
for (const provider of chain) {
|
|
45
|
+
if (this.failedModels.has(provider)) continue;
|
|
46
|
+
const p = this.modelsConfig[provider];
|
|
47
|
+
if (!p) continue;
|
|
48
|
+
|
|
49
|
+
if (p.auth_mode === 'chat_login' || p.auth_mode === 'adc') {
|
|
50
|
+
activeModels.push({ id: provider, type: 'subscription' });
|
|
51
|
+
} else if (p.api_key && this.apiFallbackEnabled) {
|
|
52
|
+
activeModels.push({ id: provider, type: 'api' });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return activeModels;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
start() {
|
|
59
|
+
console.log(chalk.magenta.bold(`
|
|
60
|
+
____ ________ ______ _____________
|
|
61
|
+
/ __ \\/ ____/ / / _/ |/ / _/_ __/
|
|
62
|
+
/ / / / __/ / / / // /|_/ // / / /
|
|
63
|
+
/ /_/ / /___/ /____/ // / / // / / /
|
|
64
|
+
/_____/_____/_____/___/_/ /_/___/ /_/
|
|
65
|
+
v4.7.3`));
|
|
66
|
+
|
|
67
|
+
while (true) {
|
|
68
|
+
const chain = this.getActiveChain();
|
|
69
|
+
if (chain.length === 0) {
|
|
70
|
+
console.log(chalk.red('\n Error: No active models available.'));
|
|
71
|
+
if (!this.apiFallbackEnabled) {
|
|
72
|
+
console.log(chalk.yellow(' Auto-Phoenix stalled: All flat-rate subscription models have degraded.'));
|
|
73
|
+
// Ask user to enable API fallback synchronously
|
|
74
|
+
try {
|
|
75
|
+
execSync('read -p " Enable API Fallback to continue using paid tokens? (y/N) " yn && [ "$yn" = "y" ] || [ "$yn" = "Y" ]', {stdio: 'inherit'});
|
|
76
|
+
this.apiFallbackEnabled = true;
|
|
77
|
+
console.log(chalk.green(' API Fallback enabled. Resuming...\n'));
|
|
78
|
+
continue;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
console.log(chalk.gray(' API Fallback declined. Exiting.\n'));
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.red(' Auto-Phoenix stalled: No remaining models in fallback chain.\n'));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const activeModel = chain[0];
|
|
90
|
+
const chainStr = chain.map(m => m.type === 'subscription' ? chalk.green(m.id) : chalk.yellow(m.id)).join(' -> ');
|
|
91
|
+
console.log(`\n [Agent: ${chalk.white(this.agentName)}] [API Fallback: ${this.apiFallbackEnabled ? chalk.green('ON') : chalk.gray('OFF')}]`);
|
|
92
|
+
console.log(` Active Routing: ${chainStr}`);
|
|
93
|
+
console.log(chalk.magenta.bold(` [Delimit] `) + chalk.magenta(`โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ`));
|
|
94
|
+
console.log(chalk.magenta.bold(` [Delimit] `) + chalk.magenta(`<`) + chalk.yellow(`/`) + chalk.magenta(`> `) + chalk.bold(`GOVERNANCE ACTIVE: ${activeModel.id.toUpperCase()}`));
|
|
95
|
+
console.log(chalk.magenta.bold(` [Delimit] `) + chalk.magenta(`โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n`));
|
|
96
|
+
|
|
97
|
+
const shimPath = path.join(os.homedir(), '.delimit', 'shims', activeModel.id);
|
|
98
|
+
if (!fs.existsSync(shimPath)) {
|
|
99
|
+
console.log(chalk.red(` Error: Shim not found for ${activeModel.id} at ${shimPath}`));
|
|
100
|
+
this.failedModels.add(activeModel.id);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Probe model quota/health before entering interactive session
|
|
105
|
+
let isHealthy = true;
|
|
106
|
+
if (activeModel.id === 'claude' || activeModel.id === 'gemini' || activeModel.id === 'gemini_consumer' || activeModel.id === 'antigravity') {
|
|
107
|
+
process.stdout.write(` Probing ${chalk.bold(activeModel.id)} quota... `);
|
|
108
|
+
|
|
109
|
+
// Configure environment for the probe (identical to the run environment)
|
|
110
|
+
const probeEnv = { ...process.env, DELIMIT_QUIET: 'true' };
|
|
111
|
+
const p = this.modelsConfig[activeModel.id];
|
|
112
|
+
if (activeModel.id === 'claude') {
|
|
113
|
+
if (p && p.auth_mode === 'chat_login') {
|
|
114
|
+
delete probeEnv.ANTHROPIC_API_KEY;
|
|
115
|
+
}
|
|
116
|
+
} else if (activeModel.id === 'gemini' || activeModel.id === 'gemini_consumer' || activeModel.id === 'antigravity') {
|
|
117
|
+
if (!p || p.auth_mode === 'chat_login') {
|
|
118
|
+
delete probeEnv.GOOGLE_CLOUD_PROJECT;
|
|
119
|
+
delete probeEnv.GEMINI_USER_GCP_PROJECT;
|
|
120
|
+
delete probeEnv.GEMINI_CLI_USE_COMPUTE_ADC;
|
|
121
|
+
delete probeEnv.GOOGLE_APPLICATION_CREDENTIALS;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Spawn the shim silently with -p "space" (timeout 6000ms)
|
|
126
|
+
const probeResult = spawnSync(shimPath, ['-p', 'space'], { env: probeEnv, timeout: 6000 });
|
|
127
|
+
|
|
128
|
+
const isTimeout = probeResult.error && probeResult.error.code === 'ETIMEDOUT';
|
|
129
|
+
if ((probeResult.error && !isTimeout) || (probeResult.status !== null && probeResult.status !== 0)) {
|
|
130
|
+
console.log(chalk.red('failed (out of quota/limit)'));
|
|
131
|
+
isHealthy = false;
|
|
132
|
+
} else {
|
|
133
|
+
console.log(chalk.green('verified'));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!isHealthy) {
|
|
138
|
+
this.failedModels.add(activeModel.id);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const args = [];
|
|
143
|
+
if (activeModel.id.startsWith('gemini')) {
|
|
144
|
+
const p = this.modelsConfig[activeModel.id];
|
|
145
|
+
if (p && p.model) {
|
|
146
|
+
let modelName = p.model;
|
|
147
|
+
if (modelName.endsWith('-latest') && this.routesConfig[activeModel.id]) {
|
|
148
|
+
const basePrefix = modelName.replace('-latest', '');
|
|
149
|
+
const concrete = this.routesConfig[activeModel.id].find(m => m.startsWith(basePrefix) && m !== modelName);
|
|
150
|
+
if (concrete) modelName = concrete;
|
|
151
|
+
}
|
|
152
|
+
args.unshift('-m', modelName);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const env = { ...process.env };
|
|
157
|
+
// Suppress the shim banner so it feels like a native session
|
|
158
|
+
env.DELIMIT_QUIET = 'true';
|
|
159
|
+
|
|
160
|
+
const p = this.modelsConfig[activeModel.id];
|
|
161
|
+
|
|
162
|
+
// Fix 403 Google Cloud API Error by preventing accidental enterprise routing
|
|
163
|
+
// If the model is using chat_login (consumer Google One plan), we MUST strip
|
|
164
|
+
// any global GCP env vars that would accidentally trigger Code Assist Enterprise mode.
|
|
165
|
+
if (activeModel.id === 'gemini' || activeModel.id === 'gemini_consumer' || activeModel.id === 'antigravity') {
|
|
166
|
+
if (!p || p.auth_mode === 'chat_login') {
|
|
167
|
+
delete env.GOOGLE_CLOUD_PROJECT;
|
|
168
|
+
delete env.GEMINI_USER_GCP_PROJECT;
|
|
169
|
+
delete env.GEMINI_CLI_USE_COMPUTE_ADC;
|
|
170
|
+
delete env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
171
|
+
} else if (p && p.auth_mode === 'adc') {
|
|
172
|
+
if (p.project) {
|
|
173
|
+
env.GOOGLE_CLOUD_PROJECT = p.project;
|
|
174
|
+
env.GEMINI_USER_GCP_PROJECT = p.project;
|
|
175
|
+
}
|
|
176
|
+
if (p.credentials_path) {
|
|
177
|
+
env.GOOGLE_APPLICATION_CREDENTIALS = p.credentials_path;
|
|
178
|
+
}
|
|
179
|
+
env.GEMINI_CLI_USE_COMPUTE_ADC = 'true';
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Force Claude Code to use chat login rather than API keys when auth_mode is chat_login
|
|
184
|
+
if (activeModel.id === 'claude') {
|
|
185
|
+
if (p && p.auth_mode === 'chat_login') {
|
|
186
|
+
delete env.ANTHROPIC_API_KEY;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Execute the model interactively. Ignore SIGINT in parent while child runs
|
|
191
|
+
// to prevent Ctrl+C from killing the parent Node process.
|
|
192
|
+
const sigintHandler = () => {};
|
|
193
|
+
process.on('SIGINT', sigintHandler);
|
|
194
|
+
const result = spawnSync(shimPath, args, { stdio: 'inherit', env });
|
|
195
|
+
process.removeListener('SIGINT', sigintHandler);
|
|
196
|
+
|
|
197
|
+
if (result.status === 0) {
|
|
198
|
+
// Clean exit (user typed /exit)
|
|
199
|
+
console.log(chalk.gray('\n Session saved. Exiting Delimit OS.'));
|
|
200
|
+
process.exit(0);
|
|
201
|
+
} else if (result.signal === 'SIGINT') {
|
|
202
|
+
console.log(chalk.yellow('\n Session interrupted (Ctrl+C).'));
|
|
203
|
+
try {
|
|
204
|
+
execSync('read -p " Migrate to the next fallback model? (Y/n) " yn && [ "$yn" = "n" ] || [ "$yn" = "N" ]', {stdio: 'inherit'});
|
|
205
|
+
console.log(chalk.yellow(` โ ${activeModel.id} interrupted. Auto-Phoenix initiating seamless migration...`));
|
|
206
|
+
this.failedModels.add(activeModel.id);
|
|
207
|
+
|
|
208
|
+
// Capture soul to preserve context before switching
|
|
209
|
+
try {
|
|
210
|
+
const pyCmd = `import sys; sys.path.insert(0, '/home/delimit/delimit-gateway'); from ai.session_phoenix import capture_soul; capture_soul(active_task='Auto-Phoenix migration from ${activeModel.id}')`;
|
|
211
|
+
execSync(`python3 -c "${pyCmd}"`, { stdio: 'ignore' });
|
|
212
|
+
} catch (e) {}
|
|
213
|
+
|
|
214
|
+
if (chain.length > 1) {
|
|
215
|
+
const nextModel = chain[1];
|
|
216
|
+
console.log(chalk.green(` โ Soul captured. Rehydrating into ${chalk.bold(nextModel.id)}...`));
|
|
217
|
+
}
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.log(chalk.gray(' Exiting Delimit OS.\n'));
|
|
220
|
+
process.exit(0);
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// The CLI crashed (e.g. 429 Quota Error or exit code != 0)
|
|
224
|
+
console.log(chalk.red(`\n Execution failed: Model CLI exited with status ${result.status}`));
|
|
225
|
+
console.log(chalk.yellow(` โ ${activeModel.id} degraded. Auto-Phoenix initiating seamless migration...`));
|
|
226
|
+
this.failedModels.add(activeModel.id);
|
|
227
|
+
|
|
228
|
+
// Capture soul to preserve context before switching
|
|
229
|
+
try {
|
|
230
|
+
const pyCmd = `import sys; sys.path.insert(0, '/home/delimit/delimit-gateway'); from ai.session_phoenix import capture_soul; capture_soul(active_task='Auto-Phoenix migration from ${activeModel.id}')`;
|
|
231
|
+
execSync(`python3 -c "${pyCmd}"`, { stdio: 'ignore' });
|
|
232
|
+
} catch (e) {}
|
|
233
|
+
|
|
234
|
+
if (chain.length > 1) {
|
|
235
|
+
const nextModel = chain[1];
|
|
236
|
+
console.log(chalk.green(` โ Soul captured. Rehydrating into ${chalk.bold(nextModel.id)}...`));
|
|
237
|
+
}
|
|
238
|
+
// Loop continues to spawn the next model
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
module.exports = { DelimitChatREPL };
|
package/lib/cross-model-hooks.js
CHANGED
|
@@ -227,6 +227,29 @@ function detectAITools() {
|
|
|
227
227
|
});
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// Antigravity CLI
|
|
231
|
+
const antigravityDir = path.join(getHome(), '.gemini', 'antigravity-cli');
|
|
232
|
+
let hasAntigravity = fs.existsSync(antigravityDir);
|
|
233
|
+
if (!hasAntigravity) {
|
|
234
|
+
try {
|
|
235
|
+
execSync('agy --version 2>/dev/null', { stdio: 'pipe', timeout: 3000 });
|
|
236
|
+
hasAntigravity = true;
|
|
237
|
+
} catch {
|
|
238
|
+
try {
|
|
239
|
+
execSync('antigravity --version 2>/dev/null', { stdio: 'pipe', timeout: 3000 });
|
|
240
|
+
hasAntigravity = true;
|
|
241
|
+
} catch { /* not installed */ }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
if (hasAntigravity) {
|
|
245
|
+
detected.push({
|
|
246
|
+
id: 'antigravity',
|
|
247
|
+
name: 'Antigravity CLI',
|
|
248
|
+
configPath: path.join(antigravityDir, 'settings.json'),
|
|
249
|
+
format: 'antigravity-mcp',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
230
253
|
return detected;
|
|
231
254
|
}
|
|
232
255
|
|
|
@@ -672,6 +695,58 @@ ${getDelimitSection()}
|
|
|
672
695
|
return changes;
|
|
673
696
|
}
|
|
674
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Install hooks for Antigravity CLI.
|
|
700
|
+
*/
|
|
701
|
+
function installAntigravityHooks(tool, hookConfig) {
|
|
702
|
+
const changes = [];
|
|
703
|
+
const antigravityDir = path.dirname(tool.configPath);
|
|
704
|
+
fs.mkdirSync(antigravityDir, { recursive: true });
|
|
705
|
+
|
|
706
|
+
// Update settings.json with custom instructions
|
|
707
|
+
let config = {};
|
|
708
|
+
if (fs.existsSync(tool.configPath)) {
|
|
709
|
+
try {
|
|
710
|
+
config = JSON.parse(fs.readFileSync(tool.configPath, 'utf-8'));
|
|
711
|
+
} catch { config = {}; }
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
const govInstructions = getDelimitSectionCondensed();
|
|
715
|
+
const DELIMIT_MARKER = '<!-- delimit:start';
|
|
716
|
+
|
|
717
|
+
if (!config.customInstructions || !config.customInstructions.includes(DELIMIT_MARKER)) {
|
|
718
|
+
config.customInstructions = govInstructions;
|
|
719
|
+
changes.push('customInstructions');
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
fs.writeFileSync(tool.configPath, JSON.stringify(config, null, 2));
|
|
723
|
+
|
|
724
|
+
// ANTIGRAVITY.md: use the same upsert pattern as CLAUDE.md
|
|
725
|
+
const antigravityMd = path.join(getHome(), 'ANTIGRAVITY.md');
|
|
726
|
+
const managedSection = getDelimitSection();
|
|
727
|
+
if (!fs.existsSync(antigravityMd)) {
|
|
728
|
+
fs.writeFileSync(antigravityMd, managedSection + '\n');
|
|
729
|
+
changes.push('ANTIGRAVITY.md');
|
|
730
|
+
} else {
|
|
731
|
+
const existing = fs.readFileSync(antigravityMd, 'utf-8');
|
|
732
|
+
if (existing.includes(DELIMIT_MARKER) && existing.includes('<!-- delimit:end -->')) {
|
|
733
|
+
const before = existing.substring(0, existing.indexOf(DELIMIT_MARKER));
|
|
734
|
+
const after = existing.substring(existing.indexOf('<!-- delimit:end -->') + '<!-- delimit:end -->'.length);
|
|
735
|
+
const updated = before + managedSection + after;
|
|
736
|
+
if (updated !== existing) {
|
|
737
|
+
fs.writeFileSync(antigravityMd, updated);
|
|
738
|
+
changes.push('ANTIGRAVITY.md');
|
|
739
|
+
}
|
|
740
|
+
} else {
|
|
741
|
+
const sep = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
742
|
+
fs.writeFileSync(antigravityMd, existing + sep + managedSection + '\n');
|
|
743
|
+
changes.push('ANTIGRAVITY.md');
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return changes;
|
|
748
|
+
}
|
|
749
|
+
|
|
675
750
|
/**
|
|
676
751
|
* Install hooks for Gemini CLI.
|
|
677
752
|
* Gemini CLI uses MCP (already handled by setup) but we add governance
|
|
@@ -744,6 +819,8 @@ function installHooksForTool(tool, hookConfig) {
|
|
|
744
819
|
return { tool, changes: installCodexHooks(tool, hookConfig) };
|
|
745
820
|
case 'gemini':
|
|
746
821
|
return { tool, changes: installGeminiHooks(tool, hookConfig) };
|
|
822
|
+
case 'antigravity':
|
|
823
|
+
return { tool, changes: installAntigravityHooks(tool, hookConfig) };
|
|
747
824
|
default:
|
|
748
825
|
return { tool, changes: [] };
|
|
749
826
|
}
|
|
@@ -880,6 +957,35 @@ function removeGeminiHooks() {
|
|
|
880
957
|
return changed;
|
|
881
958
|
}
|
|
882
959
|
|
|
960
|
+
function removeAntigravityHooks() {
|
|
961
|
+
let changed = false;
|
|
962
|
+
|
|
963
|
+
// Remove custom instructions referencing delimit
|
|
964
|
+
const configPath = path.join(getHome(), '.gemini', 'antigravity-cli', 'settings.json');
|
|
965
|
+
if (fs.existsSync(configPath)) {
|
|
966
|
+
try {
|
|
967
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
968
|
+
if (config.customInstructions && config.customInstructions.includes('delimit-cli hook')) {
|
|
969
|
+
delete config.customInstructions;
|
|
970
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
971
|
+
changed = true;
|
|
972
|
+
}
|
|
973
|
+
} catch { /* ignore */ }
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// Remove ANTIGRAVITY.md if it's ours
|
|
977
|
+
const antigravityMd = path.join(getHome(), 'ANTIGRAVITY.md');
|
|
978
|
+
if (fs.existsSync(antigravityMd)) {
|
|
979
|
+
const content = fs.readFileSync(antigravityMd, 'utf-8');
|
|
980
|
+
if (content.includes('Delimit Governance')) {
|
|
981
|
+
fs.unlinkSync(antigravityMd);
|
|
982
|
+
changed = true;
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
return changed;
|
|
987
|
+
}
|
|
988
|
+
|
|
883
989
|
function removeAllHooks() {
|
|
884
990
|
const results = [];
|
|
885
991
|
|
|
@@ -892,6 +998,9 @@ function removeAllHooks() {
|
|
|
892
998
|
if (removeGeminiHooks()) {
|
|
893
999
|
results.push('Gemini CLI');
|
|
894
1000
|
}
|
|
1001
|
+
if (removeAntigravityHooks()) {
|
|
1002
|
+
results.push('Antigravity CLI');
|
|
1003
|
+
}
|
|
895
1004
|
|
|
896
1005
|
return results;
|
|
897
1006
|
}
|
|
@@ -1511,10 +1620,12 @@ module.exports = {
|
|
|
1511
1620
|
installClaudeHooks,
|
|
1512
1621
|
installCodexHooks,
|
|
1513
1622
|
installGeminiHooks,
|
|
1623
|
+
installAntigravityHooks,
|
|
1514
1624
|
removeAllHooks,
|
|
1515
1625
|
removeClaudeHooks,
|
|
1516
1626
|
removeCodexHooks,
|
|
1517
1627
|
removeGeminiHooks,
|
|
1628
|
+
removeAntigravityHooks,
|
|
1518
1629
|
loadHookConfig,
|
|
1519
1630
|
hookSessionStart,
|
|
1520
1631
|
hookBootstrap,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
function generateTimeline(venturePath) {
|
|
6
|
+
const homedir = require('os').homedir();
|
|
7
|
+
const delimitHome = path.join(homedir, '.delimit');
|
|
8
|
+
const ledgerDir = path.join(delimitHome, 'ledger');
|
|
9
|
+
|
|
10
|
+
const events = [];
|
|
11
|
+
|
|
12
|
+
// Read all ledger files
|
|
13
|
+
const files = ['operations.jsonl', 'strategy.jsonl'];
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const filePath = path.join(ledgerDir, file);
|
|
16
|
+
if (fs.existsSync(filePath)) {
|
|
17
|
+
const lines = fs.readFileSync(filePath, 'utf-8').trim().split('\n');
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
if (!line.trim()) continue;
|
|
20
|
+
try {
|
|
21
|
+
const event = JSON.parse(line);
|
|
22
|
+
events.push(event);
|
|
23
|
+
} catch (e) {}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Sort by timestamp (fallback to 0)
|
|
29
|
+
events.sort((a, b) => new Date(a.created_at || a.ts || 0) - new Date(b.created_at || b.ts || 0));
|
|
30
|
+
|
|
31
|
+
if (events.length === 0) {
|
|
32
|
+
return "No history found in ledger.";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let output = chalk.bold.blue("\n Delimit Venture Timeline โ civilization-style retrospective\n\n");
|
|
36
|
+
|
|
37
|
+
let lastDate = "";
|
|
38
|
+
for (const e of events) {
|
|
39
|
+
const ts = e.created_at || e.ts || new Date(0).toISOString();
|
|
40
|
+
const date = ts.split('T')[0];
|
|
41
|
+
if (date !== lastDate) {
|
|
42
|
+
output += chalk.bold.white(`\n --- ${date} ---\n`);
|
|
43
|
+
lastDate = date;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const time = ts.split('T')[1]?.slice(0, 5) || "??:??";
|
|
47
|
+
const priority = e.priority === 'P0' ? chalk.red('P0') : e.priority === 'P1' ? chalk.yellow('P1') : chalk.gray(e.priority || 'n/a');
|
|
48
|
+
const type = e.type === 'strategy' ? chalk.magenta('STR') : chalk.cyan('OPS');
|
|
49
|
+
|
|
50
|
+
output += ` ${chalk.gray(time)} [${type}] [${priority}] ${chalk.white(e.title || e.message || 'Untitled Event')}\n`;
|
|
51
|
+
if (e.status === 'done' || e.status === 'completed') {
|
|
52
|
+
output += ` ${chalk.green('โ COMPLETED')}\n`;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
output += chalk.dim(`\n Total events: ${events.length}\n`);
|
|
57
|
+
return output;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = { generateTimeline };
|