@njeybe/nativ 2.5.1 → 2.7.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/CHANGELOG.md +67 -0
- package/README.md +100 -5
- package/dist/commands/design-check.d.ts +4 -0
- package/dist/commands/design-check.js +76 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +55 -11
- package/dist/commands/notify.d.ts +2 -0
- package/dist/commands/notify.js +25 -0
- package/dist/commands/session.d.ts +22 -0
- package/dist/commands/session.js +106 -0
- package/dist/commands/task/task-add.js +1 -1
- package/dist/commands/task/task-common.js +6 -6
- package/dist/commands/task/task-escalate.js +8 -10
- package/dist/commands/task/task-lifecycle.d.ts +7 -1
- package/dist/commands/task/task-lifecycle.js +30 -5
- package/dist/commands/task/task-query.d.ts +2 -0
- package/dist/commands/task/task-query.js +27 -12
- package/dist/commands/task/task-reclaim.d.ts +20 -0
- package/dist/commands/task/task-reclaim.js +87 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +1 -0
- package/dist/commands/triage.js +3 -3
- package/dist/commands/update.js +46 -0
- package/dist/commands/validate.js +1 -0
- package/dist/commands/verify.js +21 -5
- package/dist/core/design-rules.d.ts +23 -0
- package/dist/core/design-rules.js +115 -0
- package/dist/core/enforcement.js +18 -17
- package/dist/core/harness-integrity.js +26 -7
- package/dist/core/lock-manager.d.ts +6 -0
- package/dist/core/lock-manager.js +50 -41
- package/dist/core/notify.d.ts +38 -0
- package/dist/core/notify.js +133 -0
- package/dist/core/profiles.js +7 -16
- package/dist/core/session-memory.d.ts +42 -0
- package/dist/core/session-memory.js +121 -0
- package/dist/core/setup/pm-playbook.d.ts +5 -0
- package/dist/core/setup/pm-playbook.js +26 -0
- package/dist/core/setup/session-context.js +35 -2
- package/dist/core/setup/setup-planner.js +2 -2
- package/dist/core/setup/types.d.ts +2 -0
- package/dist/core/setup/types.js +5 -1
- package/dist/core/task-claims.d.ts +15 -0
- package/dist/core/task-claims.js +32 -0
- package/dist/core/tier1-liaison.d.ts +1 -0
- package/dist/core/tier1-liaison.js +25 -27
- package/dist/core/verifier/output-digest.d.ts +19 -0
- package/dist/core/verifier/output-digest.js +48 -0
- package/dist/db/data-engine.js +33 -32
- package/dist/db/introspector.js +61 -60
- package/dist/db/sqlite.d.ts +3 -0
- package/dist/db/sqlite.js +10 -0
- package/dist/governor/circuit-breaker.js +12 -0
- package/dist/governor/patch-applier.d.ts +1 -0
- package/dist/governor/patch-applier.js +3 -7
- package/dist/index.js +40 -6
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +53 -14
- package/dist/providers/claude-api.js +3 -0
- package/dist/providers/gemini.js +4 -1
- package/dist/providers/registry.d.ts +6 -2
- package/dist/providers/registry.js +28 -16
- package/dist/providers/types.d.ts +4 -1
- package/dist/runner/agent-supervisor.js +2 -2
- package/dist/runner/cli-config.js +1 -1
- package/dist/scanner/types.d.ts +5 -1
- package/dist/scanner/types.js +7 -0
- package/dist/server/routes/task-routes.d.ts +2 -0
- package/dist/server/routes/triage-routes.js +3 -18
- package/dist/server/studio-ui/pages/benchmarks/styles.js +8 -5
- package/dist/server/studio-ui/pages/database/browser.js +1 -1
- package/dist/server/studio-ui/pages/database/connection.js +1 -1
- package/dist/server/studio-ui/pages/database/data-shell.js +5 -5
- package/dist/server/studio-ui/pages/database/drift.js +1 -1
- package/dist/server/studio-ui/pages/database/explorer.js +2 -2
- package/dist/server/studio-ui/pages/database/styles.js +5 -5
- package/dist/server/studio-ui/pages/flow/styles.js +3 -3
- package/dist/server/studio-ui/pages/home/script.js +110 -91
- package/dist/server/studio-ui/pages/home/styles.js +33 -45
- package/dist/server/studio-ui/pages/tasks/script.js +150 -3
- package/dist/server/studio-ui/pages/tasks/styles.js +63 -6
- package/dist/server/studio-ui/pages/team/script.js +49 -52
- package/dist/server/studio-ui/pages/team/styles.js +21 -20
- package/dist/server/studio-ui/pages/worktrees/script.js +47 -35
- package/dist/server/studio-ui/pages/worktrees/styles.js +12 -10
- package/dist/server/studio-ui/shell/brand.d.ts +4 -0
- package/dist/server/studio-ui/shell/brand.js +16 -0
- package/dist/server/studio-ui/shell/console.js +61 -2
- package/dist/server/studio-ui/shell/core.js +8 -8
- package/dist/server/studio-ui/shell/detail-view.js +1 -1
- package/dist/server/studio-ui/shell/dispatch.js +18 -0
- package/dist/server/studio-ui/shell/events-tools.js +5 -1
- package/dist/server/studio-ui/shell/format.js +5 -6
- package/dist/server/studio-ui/shell/icons.js +23 -15
- package/dist/server/studio-ui/shell/markup-head.js +11 -6
- package/dist/server/studio-ui/shell/markup-tools.js +19 -10
- package/dist/server/studio-ui/shell/paint.js +3 -0
- package/dist/server/studio-ui/shell/pipeline-state.js +1 -1
- package/dist/server/studio-ui/shell/proposals.js +6 -2
- package/dist/server/studio-ui/shell/pulse.d.ts +1 -0
- package/dist/server/studio-ui/shell/pulse.js +67 -0
- package/dist/server/studio-ui/shell/router.js +25 -0
- package/dist/server/studio-ui/shell/sidebar-view.js +6 -3
- package/dist/server/studio-ui/shell/toasts-format.js +4 -4
- package/dist/server/studio-ui/styles/console.js +33 -13
- package/dist/server/studio-ui/styles/dialogs.js +4 -4
- package/dist/server/studio-ui/styles/responsive.js +8 -0
- package/dist/server/studio-ui/styles/shell.js +24 -5
- package/dist/server/studio-ui/styles/strategist.js +10 -10
- package/dist/server/studio-ui/styles/tokens-base.js +23 -23
- package/dist/server/studio-ui.js +2 -0
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +11 -12
- package/plugin/agents/explorer.md +1 -1
- package/plugin/agents/verifier.md +2 -2
- package/plugin/agents/worker.md +5 -7
- package/plugin/skills/nativ/SKILL.md +36 -49
- package/templates/AGENTS.md +36 -49
- package/templates/CLAUDE.md +1 -41
- package/templates/GEMINI.md +3 -2
- package/templates/claude-agents/architect.md +11 -12
- package/templates/claude-agents/explorer.md +1 -1
- package/templates/claude-agents/verifier.md +2 -2
- package/templates/claude-agents/worker.md +5 -7
- package/templates/design/taste/LICENSE +21 -0
- package/templates/design/taste/SOURCE.md +28 -0
- package/templates/design/taste/v1/DIGEST.md +57 -0
- package/templates/design/taste/v1/SKILL.md +226 -0
- package/templates/design/taste/v2/DIGEST.md +62 -0
- package/templates/design/taste/v2/SKILL.md +1206 -0
- package/templates/dot-ai/api_contracts.json +66 -66
- package/templates/dot-ai/context.md +38 -38
- package/templates/dot-ai/db_schema.json +39 -39
- package/templates/dot-ai/subagents/architect.md +6 -0
- package/templates/dot-ai/subagents/flutter-developer.md +2 -11
- package/templates/dot-ai/subagents/frontend.md +47 -44
- package/templates/dot-ai/ui_specs.md +15 -36
package/dist/db/introspector.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { performance } from 'node:perf_hooks';
|
|
2
2
|
import { isNoSqlEngine } from './types.js';
|
|
3
3
|
import { databaseNameFromUrl, detectEngine, maskConnectionString, MASK, parseFirestoreUrl, redactMediaUrl, sqlitePathFromUrl, } from './env-parser.js';
|
|
4
|
+
import { loadSqlite } from './sqlite.js';
|
|
4
5
|
/**
|
|
5
6
|
* Multi-engine schema introspector.
|
|
6
7
|
* Reads structural metadata only (tables, columns, types, nullability, keys, indexes).
|
|
@@ -81,30 +82,30 @@ async function createPostgresDriver(url) {
|
|
|
81
82
|
},
|
|
82
83
|
async tables() {
|
|
83
84
|
const builder = new TableBuilder();
|
|
84
|
-
const tables = await client.query(`
|
|
85
|
-
SELECT t.table_name, obj_description(format('%I.%I', t.table_schema, t.table_name)::regclass, 'pg_class') AS description
|
|
86
|
-
FROM information_schema.tables t
|
|
87
|
-
WHERE t.table_schema = current_schema() AND t.table_type = 'BASE TABLE'
|
|
85
|
+
const tables = await client.query(`
|
|
86
|
+
SELECT t.table_name, obj_description(format('%I.%I', t.table_schema, t.table_name)::regclass, 'pg_class') AS description
|
|
87
|
+
FROM information_schema.tables t
|
|
88
|
+
WHERE t.table_schema = current_schema() AND t.table_type = 'BASE TABLE'
|
|
88
89
|
ORDER BY t.table_name`);
|
|
89
90
|
for (const row of tables.rows) {
|
|
90
91
|
const table = builder.get(row.table_name);
|
|
91
92
|
if (row.description)
|
|
92
93
|
table.description = row.description;
|
|
93
94
|
}
|
|
94
|
-
const pks = await client.query(`
|
|
95
|
-
SELECT kcu.table_name, kcu.column_name
|
|
96
|
-
FROM information_schema.table_constraints tc
|
|
97
|
-
JOIN information_schema.key_column_usage kcu
|
|
98
|
-
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema AND kcu.table_name = tc.table_name
|
|
95
|
+
const pks = await client.query(`
|
|
96
|
+
SELECT kcu.table_name, kcu.column_name
|
|
97
|
+
FROM information_schema.table_constraints tc
|
|
98
|
+
JOIN information_schema.key_column_usage kcu
|
|
99
|
+
ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema AND kcu.table_name = tc.table_name
|
|
99
100
|
WHERE tc.table_schema = current_schema() AND tc.constraint_type = 'PRIMARY KEY'`);
|
|
100
101
|
const pkSet = new Set(pks.rows.map((r) => `${r.table_name}.${r.column_name}`));
|
|
101
|
-
const columns = await client.query(`
|
|
102
|
-
SELECT c.table_name, c.column_name, c.data_type, c.udt_name, c.character_maximum_length,
|
|
103
|
-
c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default
|
|
104
|
-
FROM information_schema.columns c
|
|
105
|
-
JOIN information_schema.tables t
|
|
106
|
-
ON t.table_schema = c.table_schema AND t.table_name = c.table_name AND t.table_type = 'BASE TABLE'
|
|
107
|
-
WHERE c.table_schema = current_schema()
|
|
102
|
+
const columns = await client.query(`
|
|
103
|
+
SELECT c.table_name, c.column_name, c.data_type, c.udt_name, c.character_maximum_length,
|
|
104
|
+
c.numeric_precision, c.numeric_scale, c.is_nullable, c.column_default
|
|
105
|
+
FROM information_schema.columns c
|
|
106
|
+
JOIN information_schema.tables t
|
|
107
|
+
ON t.table_schema = c.table_schema AND t.table_name = c.table_name AND t.table_type = 'BASE TABLE'
|
|
108
|
+
WHERE c.table_schema = current_schema()
|
|
108
109
|
ORDER BY c.table_name, c.ordinal_position`);
|
|
109
110
|
for (const row of columns.rows) {
|
|
110
111
|
builder.get(row.table_name).columns.push({
|
|
@@ -116,31 +117,31 @@ async function createPostgresDriver(url) {
|
|
|
116
117
|
});
|
|
117
118
|
}
|
|
118
119
|
// information_schema has no index view — pg_catalog is the only source.
|
|
119
|
-
const indexes = await client.query(`
|
|
120
|
-
SELECT t.relname AS table_name, i.relname AS index_name, ix.indisunique AS is_unique,
|
|
121
|
-
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
122
|
-
FROM unnest(ix.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)
|
|
123
|
-
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum) AS columns
|
|
124
|
-
FROM pg_index ix
|
|
125
|
-
JOIN pg_class t ON t.oid = ix.indrelid
|
|
126
|
-
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
127
|
-
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
128
|
-
WHERE n.nspname = current_schema() AND NOT ix.indisprimary AND t.relkind IN ('r', 'p')
|
|
120
|
+
const indexes = await client.query(`
|
|
121
|
+
SELECT t.relname AS table_name, i.relname AS index_name, ix.indisunique AS is_unique,
|
|
122
|
+
(SELECT array_agg(a.attname::text ORDER BY k.ord)
|
|
123
|
+
FROM unnest(ix.indkey::int2[]) WITH ORDINALITY AS k(attnum, ord)
|
|
124
|
+
JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = k.attnum) AS columns
|
|
125
|
+
FROM pg_index ix
|
|
126
|
+
JOIN pg_class t ON t.oid = ix.indrelid
|
|
127
|
+
JOIN pg_class i ON i.oid = ix.indexrelid
|
|
128
|
+
JOIN pg_namespace n ON n.oid = t.relnamespace
|
|
129
|
+
WHERE n.nspname = current_schema() AND NOT ix.indisprimary AND t.relkind IN ('r', 'p')
|
|
129
130
|
ORDER BY t.relname, i.relname`);
|
|
130
131
|
for (const row of indexes.rows) {
|
|
131
132
|
builder.get(row.table_name).indexes.push({ name: row.index_name, columns: row.columns ?? [], unique: row.is_unique });
|
|
132
133
|
}
|
|
133
|
-
const fks = await client.query(`
|
|
134
|
-
SELECT con.conname AS constraint_name, cl.relname AS table_name, a.attname AS column_name,
|
|
135
|
-
fcl.relname AS ref_table, fa.attname AS ref_column, con.confdeltype AS on_delete
|
|
136
|
-
FROM pg_constraint con
|
|
137
|
-
JOIN pg_class cl ON cl.oid = con.conrelid
|
|
138
|
-
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
139
|
-
JOIN pg_class fcl ON fcl.oid = con.confrelid
|
|
140
|
-
CROSS JOIN LATERAL unnest(con.conkey, con.confkey) AS k(attnum, fattnum)
|
|
141
|
-
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
142
|
-
JOIN pg_attribute fa ON fa.attrelid = con.confrelid AND fa.attnum = k.fattnum
|
|
143
|
-
WHERE n.nspname = current_schema() AND con.contype = 'f'
|
|
134
|
+
const fks = await client.query(`
|
|
135
|
+
SELECT con.conname AS constraint_name, cl.relname AS table_name, a.attname AS column_name,
|
|
136
|
+
fcl.relname AS ref_table, fa.attname AS ref_column, con.confdeltype AS on_delete
|
|
137
|
+
FROM pg_constraint con
|
|
138
|
+
JOIN pg_class cl ON cl.oid = con.conrelid
|
|
139
|
+
JOIN pg_namespace n ON n.oid = cl.relnamespace
|
|
140
|
+
JOIN pg_class fcl ON fcl.oid = con.confrelid
|
|
141
|
+
CROSS JOIN LATERAL unnest(con.conkey, con.confkey) AS k(attnum, fattnum)
|
|
142
|
+
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
|
|
143
|
+
JOIN pg_attribute fa ON fa.attrelid = con.confrelid AND fa.attnum = k.fattnum
|
|
144
|
+
WHERE n.nspname = current_schema() AND con.contype = 'f'
|
|
144
145
|
ORDER BY cl.relname, con.conname`);
|
|
145
146
|
for (const row of fks.rows) {
|
|
146
147
|
builder.get(row.table_name).foreignKeys.push({
|
|
@@ -177,22 +178,22 @@ async function createMysqlDriver(url) {
|
|
|
177
178
|
},
|
|
178
179
|
async tables() {
|
|
179
180
|
const builder = new TableBuilder();
|
|
180
|
-
for (const row of await rows(`
|
|
181
|
-
SELECT TABLE_NAME AS table_name, TABLE_COMMENT AS table_comment
|
|
182
|
-
FROM information_schema.TABLES
|
|
183
|
-
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'
|
|
181
|
+
for (const row of await rows(`
|
|
182
|
+
SELECT TABLE_NAME AS table_name, TABLE_COMMENT AS table_comment
|
|
183
|
+
FROM information_schema.TABLES
|
|
184
|
+
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_TYPE = 'BASE TABLE'
|
|
184
185
|
ORDER BY TABLE_NAME`)) {
|
|
185
186
|
const table = builder.get(row.table_name);
|
|
186
187
|
if (row.table_comment)
|
|
187
188
|
table.description = row.table_comment;
|
|
188
189
|
}
|
|
189
|
-
for (const row of await rows(`
|
|
190
|
-
SELECT c.TABLE_NAME AS table_name, c.COLUMN_NAME AS column_name, c.COLUMN_TYPE AS column_type,
|
|
191
|
-
c.IS_NULLABLE AS is_nullable, c.COLUMN_DEFAULT AS column_default, c.COLUMN_KEY AS column_key
|
|
192
|
-
FROM information_schema.COLUMNS c
|
|
193
|
-
JOIN information_schema.TABLES t
|
|
194
|
-
ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_TYPE = 'BASE TABLE'
|
|
195
|
-
WHERE c.TABLE_SCHEMA = DATABASE()
|
|
190
|
+
for (const row of await rows(`
|
|
191
|
+
SELECT c.TABLE_NAME AS table_name, c.COLUMN_NAME AS column_name, c.COLUMN_TYPE AS column_type,
|
|
192
|
+
c.IS_NULLABLE AS is_nullable, c.COLUMN_DEFAULT AS column_default, c.COLUMN_KEY AS column_key
|
|
193
|
+
FROM information_schema.COLUMNS c
|
|
194
|
+
JOIN information_schema.TABLES t
|
|
195
|
+
ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME AND t.TABLE_TYPE = 'BASE TABLE'
|
|
196
|
+
WHERE c.TABLE_SCHEMA = DATABASE()
|
|
196
197
|
ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`)) {
|
|
197
198
|
builder.get(row.table_name).columns.push({
|
|
198
199
|
name: row.column_name,
|
|
@@ -203,10 +204,10 @@ async function createMysqlDriver(url) {
|
|
|
203
204
|
});
|
|
204
205
|
}
|
|
205
206
|
const indexMap = new Map();
|
|
206
|
-
for (const row of await rows(`
|
|
207
|
-
SELECT TABLE_NAME AS table_name, INDEX_NAME AS index_name, NON_UNIQUE AS non_unique, COLUMN_NAME AS column_name
|
|
208
|
-
FROM information_schema.STATISTICS
|
|
209
|
-
WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME <> 'PRIMARY'
|
|
207
|
+
for (const row of await rows(`
|
|
208
|
+
SELECT TABLE_NAME AS table_name, INDEX_NAME AS index_name, NON_UNIQUE AS non_unique, COLUMN_NAME AS column_name
|
|
209
|
+
FROM information_schema.STATISTICS
|
|
210
|
+
WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME <> 'PRIMARY'
|
|
210
211
|
ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`)) {
|
|
211
212
|
const key = `${row.table_name}.${row.index_name}`;
|
|
212
213
|
let entry = indexMap.get(key);
|
|
@@ -219,13 +220,13 @@ async function createMysqlDriver(url) {
|
|
|
219
220
|
}
|
|
220
221
|
for (const { table, index } of indexMap.values())
|
|
221
222
|
builder.get(table).indexes.push(index);
|
|
222
|
-
for (const row of await rows(`
|
|
223
|
-
SELECT k.CONSTRAINT_NAME AS constraint_name, k.TABLE_NAME AS table_name, k.COLUMN_NAME AS column_name,
|
|
224
|
-
k.REFERENCED_TABLE_NAME AS ref_table, k.REFERENCED_COLUMN_NAME AS ref_column, r.DELETE_RULE AS delete_rule
|
|
225
|
-
FROM information_schema.KEY_COLUMN_USAGE k
|
|
226
|
-
JOIN information_schema.REFERENTIAL_CONSTRAINTS r
|
|
227
|
-
ON r.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA AND r.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.TABLE_NAME = k.TABLE_NAME
|
|
228
|
-
WHERE k.TABLE_SCHEMA = DATABASE() AND k.REFERENCED_TABLE_NAME IS NOT NULL
|
|
223
|
+
for (const row of await rows(`
|
|
224
|
+
SELECT k.CONSTRAINT_NAME AS constraint_name, k.TABLE_NAME AS table_name, k.COLUMN_NAME AS column_name,
|
|
225
|
+
k.REFERENCED_TABLE_NAME AS ref_table, k.REFERENCED_COLUMN_NAME AS ref_column, r.DELETE_RULE AS delete_rule
|
|
226
|
+
FROM information_schema.KEY_COLUMN_USAGE k
|
|
227
|
+
JOIN information_schema.REFERENTIAL_CONSTRAINTS r
|
|
228
|
+
ON r.CONSTRAINT_SCHEMA = k.CONSTRAINT_SCHEMA AND r.CONSTRAINT_NAME = k.CONSTRAINT_NAME AND r.TABLE_NAME = k.TABLE_NAME
|
|
229
|
+
WHERE k.TABLE_SCHEMA = DATABASE() AND k.REFERENCED_TABLE_NAME IS NOT NULL
|
|
229
230
|
ORDER BY k.TABLE_NAME, k.CONSTRAINT_NAME, k.ORDINAL_POSITION`)) {
|
|
230
231
|
builder.get(row.table_name).foreignKeys.push({
|
|
231
232
|
name: row.constraint_name,
|
|
@@ -247,7 +248,7 @@ function quoteIdent(name) {
|
|
|
247
248
|
return `"${name.replace(/"/g, '""')}"`;
|
|
248
249
|
}
|
|
249
250
|
async function createSqliteDriver(url) {
|
|
250
|
-
const { DatabaseSync } = await
|
|
251
|
+
const { DatabaseSync } = await loadSqlite();
|
|
251
252
|
const filePath = sqlitePathFromUrl(url);
|
|
252
253
|
// readOnly: never create a database file or mutate one while introspecting.
|
|
253
254
|
const db = filePath === ':memory:' ? new DatabaseSync(':memory:') : new DatabaseSync(filePath, { readOnly: true });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** `node:sqlite` ships with Node.js 22.5+; nativ itself supports Node 20, so SQLite projects get a clear message there. */
|
|
2
|
+
export const SQLITE_MIN_NODE = '22.5';
|
|
3
|
+
export async function loadSqlite() {
|
|
4
|
+
try {
|
|
5
|
+
return await import('node:sqlite');
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
throw new Error(`SQLite databases need Node.js ${SQLITE_MIN_NODE} or newer (this is ${process.version}). Upgrade Node.js, or use Postgres, MySQL or MongoDB.`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { withPlanLockSync } from '../core/lock-manager.js';
|
|
3
3
|
import { recordCircuitBreakerTrip } from '../core/telemetry.js';
|
|
4
|
+
import { notifyHuman } from '../core/notify.js';
|
|
4
5
|
import { appendEscalation, loadLedger, mutateLedger } from './store.js';
|
|
5
6
|
const MAX_FAILURE_THRESHOLD = 3;
|
|
6
7
|
const newEntry = () => ({ consecutiveFailures: 0, lastFailureTime: '', patchCount: 0 });
|
|
@@ -38,6 +39,15 @@ function tryMutateLedger(targetDir, mutator) {
|
|
|
38
39
|
// Non-fatal
|
|
39
40
|
}
|
|
40
41
|
}
|
|
42
|
+
/** Fire-and-forget: the verdict is synchronous, and an open request keeps a CLI process alive until it settles. */
|
|
43
|
+
function notifyTrip(targetDir, taskId, escalationId, ruleId) {
|
|
44
|
+
notifyHuman(targetDir, {
|
|
45
|
+
event: 'circuit_breaker_tripped',
|
|
46
|
+
taskId,
|
|
47
|
+
escalationId,
|
|
48
|
+
summary: `Task ${taskId} failed ${MAX_FAILURE_THRESHOLD} times in a row (${ruleId}) and is now blocked`,
|
|
49
|
+
}).catch(() => { });
|
|
50
|
+
}
|
|
41
51
|
export class CircuitBreaker {
|
|
42
52
|
static getStatus(targetDir, taskId) {
|
|
43
53
|
const ledger = loadLedger(targetDir);
|
|
@@ -138,6 +148,7 @@ export class CircuitBreaker {
|
|
|
138
148
|
}));
|
|
139
149
|
this.blockTask(targetDir, taskId, `Circuit breaker tripped by Governor: ${ruleResult.ruleId} (${escalationId ?? 'escalation not recorded'})`);
|
|
140
150
|
recordCircuitBreakerTrip(targetDir, taskId).catch(() => { });
|
|
151
|
+
notifyTrip(targetDir, taskId, escalationId ?? undefined, ruleResult.ruleId);
|
|
141
152
|
return { state, escalationId: escalationId ?? undefined, proposal };
|
|
142
153
|
}
|
|
143
154
|
/** Atomic increment under the ledger lock: concurrent failures are all counted. */
|
|
@@ -192,6 +203,7 @@ export class CircuitBreaker {
|
|
|
192
203
|
// 2. Mark task as blocked in .ai/master_plan.json
|
|
193
204
|
this.blockTask(targetDir, taskId, `Circuit breaker tripped by Governor: ${ruleResult.ruleId} (${escalationId})`);
|
|
194
205
|
recordCircuitBreakerTrip(targetDir, taskId).catch(() => { });
|
|
206
|
+
notifyTrip(targetDir, taskId, escalationId === 'unrecorded' ? undefined : escalationId, ruleResult.ruleId);
|
|
195
207
|
return {
|
|
196
208
|
escalationId,
|
|
197
209
|
taskId,
|
|
@@ -3,6 +3,7 @@ export declare function getContractHash(filePath: string): string | null;
|
|
|
3
3
|
export declare function appendAuditLog(targetDir: string, entry: AuditLogEntry): void;
|
|
4
4
|
export declare function applyDbPatch(schema: any, patch: ContractPatch): any;
|
|
5
5
|
export declare function applyApiPatch(contracts: any, patch: ContractPatch): any;
|
|
6
|
+
/** False when the write failed; the shared helper retries Windows sharing violations before giving up. */
|
|
6
7
|
export declare function atomicWriteJson(filePath: string, data: any): boolean;
|
|
7
8
|
export declare function contractFileFor(target: ContractPatch['target']): string;
|
|
8
9
|
export declare function applyPatch(contract: any, patch: ContractPatch): any;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
|
+
import { writeJsonAtomicSync } from '../core/lock-manager.js';
|
|
4
5
|
export function getContractHash(filePath) {
|
|
5
6
|
if (!fs.existsSync(filePath))
|
|
6
7
|
return null;
|
|
@@ -98,18 +99,13 @@ export function applyApiPatch(contracts, patch) {
|
|
|
98
99
|
contracts.updatedAt = new Date().toISOString();
|
|
99
100
|
return contracts;
|
|
100
101
|
}
|
|
102
|
+
/** False when the write failed; the shared helper retries Windows sharing violations before giving up. */
|
|
101
103
|
export function atomicWriteJson(filePath, data) {
|
|
102
|
-
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
-
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2) + '\n', 'utf8');
|
|
106
|
-
fs.renameSync(tmpPath, filePath);
|
|
105
|
+
writeJsonAtomicSync(filePath, data);
|
|
107
106
|
return true;
|
|
108
107
|
}
|
|
109
108
|
catch {
|
|
110
|
-
if (fs.existsSync(tmpPath)) {
|
|
111
|
-
fs.rmSync(tmpPath, { force: true });
|
|
112
|
-
}
|
|
113
109
|
return false;
|
|
114
110
|
}
|
|
115
111
|
}
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { runInit } from './commands/init.js';
|
|
3
3
|
import { runStatus } from './commands/status.js';
|
|
4
4
|
import { runValidate } from './commands/validate.js';
|
|
5
|
-
import { runTaskList, runTaskNext, runTaskStart, runTaskComplete, runTaskBlock, runTaskEscalate, runTaskAdd, runTaskProposePatch, runTaskUnlock, } from './commands/task.js';
|
|
5
|
+
import { runTaskList, runTaskNext, runTaskStart, runTaskComplete, runTaskBlock, runTaskReclaim, runTaskEscalate, runTaskAdd, runTaskProposePatch, runTaskUnlock, } from './commands/task.js';
|
|
6
6
|
import { runHookCheck, runHookStatus } from './commands/hook.js';
|
|
7
7
|
import { runSetup } from './commands/setup.js';
|
|
8
8
|
import { runDoctor } from './commands/doctor.js';
|
|
@@ -15,8 +15,10 @@ import { runVerify } from './commands/verify.js';
|
|
|
15
15
|
import { runTestGen, TEST_GEN_FRAMEWORK_HELP } from './commands/test-gen.js';
|
|
16
16
|
import { runBench } from './commands/bench.js';
|
|
17
17
|
import { runTriage } from './commands/triage.js';
|
|
18
|
+
import { runNotifyTest } from './commands/notify.js';
|
|
18
19
|
import { runLearnPropose, runLearnDecide, runLearnList } from './commands/learn.js';
|
|
19
20
|
import { runProfileCommand } from './commands/profile.js';
|
|
21
|
+
import { runDesignCheck } from './commands/design-check.js';
|
|
20
22
|
import { packageVersion } from './core/version.js';
|
|
21
23
|
export function createProgram() {
|
|
22
24
|
const program = new Command();
|
|
@@ -28,6 +30,7 @@ export function createProgram() {
|
|
|
28
30
|
.command('init [targetDir]')
|
|
29
31
|
.description('Scaffold the workflow (.ai/, AGENTS.md, directives) and the Claude Code configuration in the target directory')
|
|
30
32
|
.option('-f, --force', 'Overwrite existing specification and directive files')
|
|
33
|
+
.option('--design <profile>', 'Design profile: nativ, taste-v1 or taste-v2')
|
|
31
34
|
.action(async (targetDir, options) => {
|
|
32
35
|
await runInit(targetDir, options);
|
|
33
36
|
});
|
|
@@ -80,7 +83,8 @@ export function createProgram() {
|
|
|
80
83
|
.option('-s, --status <status>', 'Filter tasks by status (pending, in_progress, completed, blocked)')
|
|
81
84
|
.option('-m, --milestone <id>', 'Filter tasks by milestone ID or name')
|
|
82
85
|
.option('--fast-path', 'Show only fast-path tasks')
|
|
83
|
-
.option('--json', 'Output filtered tasks as JSON')
|
|
86
|
+
.option('--json', 'Output filtered tasks as JSON (a summary per task)')
|
|
87
|
+
.option('--full', 'With --json, include every task field')
|
|
84
88
|
.action(async (targetDir, options) => {
|
|
85
89
|
await runTaskList(targetDir, options);
|
|
86
90
|
});
|
|
@@ -121,7 +125,8 @@ export function createProgram() {
|
|
|
121
125
|
.option('-s, --status <status>', 'Filter tasks by status (pending, in_progress, completed, blocked)')
|
|
122
126
|
.option('-m, --milestone <id>', 'Filter tasks by milestone ID or name')
|
|
123
127
|
.option('--fast-path', 'Show only fast-path tasks')
|
|
124
|
-
.option('--json', 'Output filtered tasks as JSON')
|
|
128
|
+
.option('--json', 'Output filtered tasks as JSON (a summary per task)')
|
|
129
|
+
.option('--full', 'With --json, include every task field')
|
|
125
130
|
.action(async (targetDir, options) => {
|
|
126
131
|
await runTaskList(targetDir, options);
|
|
127
132
|
});
|
|
@@ -151,9 +156,11 @@ export function createProgram() {
|
|
|
151
156
|
});
|
|
152
157
|
task
|
|
153
158
|
.command('start <taskId> [targetDir]')
|
|
154
|
-
.description('
|
|
155
|
-
.
|
|
156
|
-
|
|
159
|
+
.description('Claim a pending task whose dependencies are done and mark it in_progress')
|
|
160
|
+
.option('--agent <id>', 'Who claims the task (default: $NATIV_AGENT_ID)')
|
|
161
|
+
.option('--force', 'Human override: start a blocked task or one with unfinished dependencies')
|
|
162
|
+
.action(async (taskId, targetDir, options) => {
|
|
163
|
+
await runTaskStart(taskId, targetDir, { agent: options.agent, force: options.force });
|
|
157
164
|
});
|
|
158
165
|
task
|
|
159
166
|
.command('complete <taskId> [targetDir]')
|
|
@@ -178,6 +185,16 @@ export function createProgram() {
|
|
|
178
185
|
.action(async (taskId, targetDir, options) => {
|
|
179
186
|
await runVerify(taskId, targetDir, options);
|
|
180
187
|
});
|
|
188
|
+
task
|
|
189
|
+
.command('reclaim [taskId] [targetDir]')
|
|
190
|
+
.description('Put in_progress tasks whose agent stopped back to pending (claims older than --older-than hours)')
|
|
191
|
+
.option('--older-than <hours>', 'Age at which a claim counts as abandoned (default 4)', parseFloat)
|
|
192
|
+
.option('--dry-run', 'List what would be reclaimed without changing the plan')
|
|
193
|
+
.option('--force', 'Human override: reclaim the named task even though its claim is fresh')
|
|
194
|
+
.option('--json', 'Output the result as JSON')
|
|
195
|
+
.action(async (taskId, targetDir, options) => {
|
|
196
|
+
await runTaskReclaim(taskId, targetDir, { olderThan: options.olderThan, dryRun: options.dryRun, force: options.force, json: options.json });
|
|
197
|
+
});
|
|
181
198
|
task
|
|
182
199
|
.command('block <taskId> [targetDir]')
|
|
183
200
|
.description('Mark a task as blocked with a required reason')
|
|
@@ -224,6 +241,15 @@ export function createProgram() {
|
|
|
224
241
|
.action(async (taskId, targetDir, options) => {
|
|
225
242
|
await runTaskUnlock(taskId, targetDir, { reason: options.reason, revoke: options.revoke });
|
|
226
243
|
});
|
|
244
|
+
const design = program.command('design').description('Design profile tools');
|
|
245
|
+
design
|
|
246
|
+
.command('check [paths...]')
|
|
247
|
+
.description('Scan source files for design-rule violations (default path: src)')
|
|
248
|
+
.option('--profile <id>', 'Design profile: nativ, taste-v1 or taste-v2')
|
|
249
|
+
.option('--json', 'Output findings as JSON')
|
|
250
|
+
.action((paths, options) => {
|
|
251
|
+
runDesignCheck(paths, options);
|
|
252
|
+
});
|
|
227
253
|
const learn = program
|
|
228
254
|
.command('learn')
|
|
229
255
|
.description('Project lessons the contracts do not capture: agents propose, a human approves');
|
|
@@ -396,6 +422,14 @@ export function createProgram() {
|
|
|
396
422
|
.action(async (escalationId, targetDir, options) => {
|
|
397
423
|
await runTriage(escalationId, targetDir, options);
|
|
398
424
|
});
|
|
425
|
+
program
|
|
426
|
+
.command('notify')
|
|
427
|
+
.description('Human notifications (webhook or command) for decisions and circuit-breaker trips')
|
|
428
|
+
.command('test [targetDir]')
|
|
429
|
+
.description('Send a sample notification through the configured webhook or command')
|
|
430
|
+
.action(async (targetDir) => {
|
|
431
|
+
await runNotifyTest(targetDir);
|
|
432
|
+
});
|
|
399
433
|
program
|
|
400
434
|
.command('mcp [targetDir]')
|
|
401
435
|
.description('Run the native MCP (Model Context Protocol) server over stdio, exposing nativ tools and .ai/ contract resources')
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
export { captureOutput } from './capture.js';
|
|
3
3
|
export { registerMcpResources, MCP_RESOURCES } from './resources.js';
|
|
4
|
-
export
|
|
4
|
+
export { ESCALATION_TYPES } from '../scanner/types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Native MCP (Model Context Protocol) server for Nativ over stdio.
|
|
7
7
|
*
|
package/dist/mcp/server.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
3
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { Writable } from 'node:stream';
|
|
4
5
|
import { z } from 'zod';
|
|
5
|
-
import { runTaskList, runTaskNext, runTaskStart, runTaskComplete, runTaskBlock, runTaskEscalate, runTaskAdd, runTaskProposePatch, } from '../commands/task.js';
|
|
6
|
-
import { SUBAGENT_TYPES } from '../scanner/types.js';
|
|
6
|
+
import { runTaskList, runTaskNext, runTaskStart, runTaskComplete, runTaskBlock, runTaskReclaim, runTaskEscalate, runTaskAdd, runTaskProposePatch, } from '../commands/task.js';
|
|
7
|
+
import { SUBAGENT_TYPES, ESCALATION_TYPES } from '../scanner/types.js';
|
|
7
8
|
import { runInit } from '../commands/init.js';
|
|
9
|
+
import { runTriage } from '../commands/triage.js';
|
|
8
10
|
import { runStatus } from '../commands/status.js';
|
|
9
11
|
import { runDoctor } from '../commands/doctor.js';
|
|
12
|
+
import { runValidate } from '../commands/validate.js';
|
|
10
13
|
import { runWorktreeCreate, runWorktreeList, runWorktreeMerge, runWorktreeRemove, } from '../commands/worktree.js';
|
|
11
14
|
import { runDbStatus, runDbInspect, runDbDiff } from '../commands/db.js';
|
|
12
15
|
import { runVerify } from '../commands/verify.js';
|
|
@@ -19,13 +22,7 @@ import { captureOutput } from './capture.js';
|
|
|
19
22
|
import { registerMcpResources } from './resources.js';
|
|
20
23
|
export { captureOutput } from './capture.js';
|
|
21
24
|
export { registerMcpResources, MCP_RESOURCES } from './resources.js';
|
|
22
|
-
export
|
|
23
|
-
'contract_drift',
|
|
24
|
-
'schema_flaw',
|
|
25
|
-
'missing_credential',
|
|
26
|
-
'dependency_conflict',
|
|
27
|
-
'architectural_ambiguity',
|
|
28
|
-
];
|
|
25
|
+
export { ESCALATION_TYPES } from '../scanner/types.js';
|
|
29
26
|
/**
|
|
30
27
|
* Native MCP (Model Context Protocol) server for Nativ over stdio.
|
|
31
28
|
*
|
|
@@ -46,14 +43,15 @@ export function createMcpServer(targetDirArg) {
|
|
|
46
43
|
inputSchema: {},
|
|
47
44
|
}, () => captureOutput(() => runTaskNext(targetDir, { json: true })));
|
|
48
45
|
registerNativTool('task_list', {
|
|
49
|
-
description: 'List project tasks from .ai/master_plan.json with optional filters (JSON).',
|
|
46
|
+
description: 'List project tasks from .ai/master_plan.json with optional filters (JSON summary; full for every field).',
|
|
50
47
|
inputSchema: {
|
|
51
48
|
available: z.boolean().optional().describe('Only tasks that are unblocked and ready for execution'),
|
|
52
49
|
status: z.enum(['pending', 'in_progress', 'completed', 'blocked']).optional().describe('Filter by task status'),
|
|
53
50
|
milestone: z.string().optional().describe('Filter by milestone ID or name'),
|
|
54
51
|
fastPath: z.boolean().optional().describe('Only fast-path tasks'),
|
|
52
|
+
full: z.boolean().optional().describe('Return every task field; the default is a short summary per task'),
|
|
55
53
|
},
|
|
56
|
-
}, ({ available, status, milestone, fastPath }) => captureOutput(() => runTaskList(targetDir, { available, status, milestone, fastPath, json: true })));
|
|
54
|
+
}, ({ available, status, milestone, fastPath, full }) => captureOutput(() => runTaskList(targetDir, { available, status, milestone, fastPath, json: true, brief: !full })));
|
|
57
55
|
registerNativTool('task_add', {
|
|
58
56
|
description: 'Append a task to .ai/master_plan.json with an auto-incremented ID (JSON). Goes to the given milestone, else the active one; ' +
|
|
59
57
|
'fastPath routes it to the fast-path milestone (created on first use).',
|
|
@@ -84,9 +82,12 @@ export function createMcpServer(targetDirArg) {
|
|
|
84
82
|
json: true,
|
|
85
83
|
})));
|
|
86
84
|
registerNativTool('task_start', {
|
|
87
|
-
description: '
|
|
88
|
-
inputSchema: {
|
|
89
|
-
|
|
85
|
+
description: 'Claim a task and mark it in_progress. Refused when it is blocked, completed, waits on unfinished dependencies, or another agent holds a fresh claim. Calling it again on your own task refreshes the claim.',
|
|
86
|
+
inputSchema: {
|
|
87
|
+
taskId: z.string().min(1).describe('Task ID, e.g. task-12'),
|
|
88
|
+
agent: z.string().optional().describe('Your agent id, so parallel agents do not take the same task'),
|
|
89
|
+
},
|
|
90
|
+
}, ({ taskId, agent }) => captureOutput(() => runTaskStart(taskId, targetDir, { agent })));
|
|
90
91
|
registerNativTool('task_complete', {
|
|
91
92
|
description: "Mark a task as completed (always executes its verificationCommand; verification cannot be skipped over MCP). Advances the milestone when all its tasks are done.",
|
|
92
93
|
inputSchema: {
|
|
@@ -115,6 +116,33 @@ export function createMcpServer(targetDirArg) {
|
|
|
115
116
|
reason: z.string().min(1).describe('Why the task is blocked'),
|
|
116
117
|
},
|
|
117
118
|
}, ({ taskId, reason }) => captureOutput(() => runTaskBlock(taskId, reason, targetDir)));
|
|
119
|
+
registerNativTool('triage', {
|
|
120
|
+
description: 'Self-heal escalations: evaluate pending escalations (or one), apply sandbox-proven additive contract fixes and unblock their tasks. Returns compact JSON; items classified REQUIRE_HUMAN_DECISION carry a humanCard to present to the human.',
|
|
121
|
+
inputSchema: {
|
|
122
|
+
escalationId: z.string().optional().describe('Triage only this escalation, e.g. esc-03; default: every pending one'),
|
|
123
|
+
apply: z.boolean().optional().describe('Write proven safe patches and unblock tasks (default true)'),
|
|
124
|
+
},
|
|
125
|
+
}, ({ escalationId, apply }) => captureOutput(() => runTriage(escalationId, targetDir, {
|
|
126
|
+
all: !escalationId,
|
|
127
|
+
apply: apply ?? true,
|
|
128
|
+
json: true,
|
|
129
|
+
interactive: false,
|
|
130
|
+
// stdout carries the MCP protocol, so triage output is routed into the captured tool result.
|
|
131
|
+
output: new Writable({
|
|
132
|
+
write(chunk, _encoding, done) {
|
|
133
|
+
console.log(String(chunk).replace(/\n$/, ''));
|
|
134
|
+
done();
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
})));
|
|
138
|
+
registerNativTool('task_reclaim', {
|
|
139
|
+
description: 'Put in_progress tasks whose agent stopped (claim older than olderThanHours, default 4) back to pending. Pass taskId to reclaim one task that has a stale or unrecorded claim.',
|
|
140
|
+
inputSchema: {
|
|
141
|
+
taskId: z.string().optional().describe('Reclaim only this task'),
|
|
142
|
+
olderThanHours: z.number().positive().optional().describe('Age at which a claim counts as abandoned'),
|
|
143
|
+
dryRun: z.boolean().optional().describe('Report without changing the plan'),
|
|
144
|
+
},
|
|
145
|
+
}, ({ taskId, olderThanHours, dryRun }) => captureOutput(() => runTaskReclaim(taskId, targetDir, { olderThan: olderThanHours, dryRun, json: true })));
|
|
118
146
|
registerNativTool('task_escalate', {
|
|
119
147
|
description: 'Escalate a contract or architectural blocker to the Architect (writes .ai/escalation.json and blocks the task).',
|
|
120
148
|
inputSchema: {
|
|
@@ -192,6 +220,17 @@ export function createMcpServer(targetDirArg) {
|
|
|
192
220
|
description: 'Summarize execution progress across all milestones and tasks.',
|
|
193
221
|
inputSchema: {},
|
|
194
222
|
}, () => captureOutput(() => runStatus(targetDir)));
|
|
223
|
+
registerNativTool('validate', {
|
|
224
|
+
description: 'Read-only integrity check of the .ai/ contracts, master plan, spec references and role guides. ' +
|
|
225
|
+
'Returns only problems (✖ errors, ⚠ warnings) and the verdict; an error result means a contract is missing or malformed.',
|
|
226
|
+
inputSchema: {},
|
|
227
|
+
}, async () => {
|
|
228
|
+
const result = await captureOutput(() => runValidate(targetDir));
|
|
229
|
+
const text = result.content[0]?.type === 'text' ? result.content[0].text : '';
|
|
230
|
+
const problems = text.split('\n').filter((line) => /[✖⚠]/.test(line) && !/validation failed with errors/i.test(line)).map((line) => line.trim());
|
|
231
|
+
const verdict = result.isError ? 'Validation failed.' : 'All contracts are valid.';
|
|
232
|
+
return { ...result, content: [{ type: 'text', text: [...problems, verdict].join('\n') }] };
|
|
233
|
+
});
|
|
195
234
|
registerNativTool('doctor', {
|
|
196
235
|
description: 'Read-only health check of the Claude Code integration: MCP server, hooks, permissions, agents, enforcement mode and provider availability (JSON). ' +
|
|
197
236
|
'Reports drift but never repairs it; repairing is `nativ doctor --fix` in a terminal.',
|
|
@@ -68,6 +68,9 @@ export function createClaudeApiProvider(deps = {}) {
|
|
|
68
68
|
throw new ProviderError('NO_CREDENTIALS', 'The Anthropic API key was rejected.');
|
|
69
69
|
if (/timeout|timed out/i.test(name + message))
|
|
70
70
|
throw new ProviderError('TIMEOUT', `Claude did not answer within ${timeout}ms.`);
|
|
71
|
+
if ((status !== undefined && status >= 500) || /APIConnectionError|ECONNRESET|ECONNREFUSED|ENOTFOUND|EAI_AGAIN|socket hang up|fetch failed/i.test(name + message)) {
|
|
72
|
+
throw new ProviderError('TRANSIENT', message.slice(0, 300));
|
|
73
|
+
}
|
|
71
74
|
throw new ProviderError('BAD_RESPONSE', message.slice(0, 300));
|
|
72
75
|
}
|
|
73
76
|
},
|
package/dist/providers/gemini.js
CHANGED
|
@@ -52,7 +52,8 @@ export function createGeminiProvider(deps = {}) {
|
|
|
52
52
|
const name = err?.name ?? '';
|
|
53
53
|
if (name === 'TimeoutError' || name === 'AbortError')
|
|
54
54
|
throw new ProviderError('TIMEOUT', `Gemini did not answer within ${timeout}ms.`);
|
|
55
|
-
|
|
55
|
+
// fetch only throws here when the request never got an answer: DNS, refused or reset connection.
|
|
56
|
+
throw new ProviderError('TRANSIENT', err instanceof Error ? err.message : String(err));
|
|
56
57
|
}
|
|
57
58
|
if (res.status === 429) {
|
|
58
59
|
const seconds = Number(res.headers.get('retry-after'));
|
|
@@ -60,6 +61,8 @@ export function createGeminiProvider(deps = {}) {
|
|
|
60
61
|
}
|
|
61
62
|
if (res.status === 401 || res.status === 403)
|
|
62
63
|
throw new ProviderError('NO_CREDENTIALS', 'The Gemini API key was rejected.');
|
|
64
|
+
if (res.status >= 500)
|
|
65
|
+
throw new ProviderError('TRANSIENT', `Gemini responded with HTTP ${res.status}.`);
|
|
63
66
|
if (!res.ok)
|
|
64
67
|
throw new ProviderError('BAD_RESPONSE', `Gemini responded with HTTP ${res.status}.`);
|
|
65
68
|
let body;
|
|
@@ -31,8 +31,12 @@ export declare function providerCooldownRemaining(root: string, id: ProviderId,
|
|
|
31
31
|
export declare function resolveProviderChain(role: ProviderRole, root: string, options?: RegistryOptions): Provider[];
|
|
32
32
|
export declare function resolveProvider(role: ProviderRole, root: string, options?: RegistryOptions): Provider | null;
|
|
33
33
|
/**
|
|
34
|
-
* Runs the request against each provider in turn. A
|
|
35
|
-
*
|
|
34
|
+
* Runs the request against each provider in turn. A TRANSIENT failure (network, 5xx) is retried on the same
|
|
35
|
+
* provider up to MAX_TRANSIENT_RETRIES times with jittered backoff. A rate limit puts that provider on cooldown, and
|
|
36
|
+
* any other ProviderError falls through to the next one. Throws NO_CREDENTIALS when nothing is available and the
|
|
36
37
|
* last error when every provider failed, so the caller can drop to its deterministic path.
|
|
37
38
|
*/
|
|
39
|
+
export declare const MAX_TRANSIENT_RETRIES = 2;
|
|
40
|
+
/** 0.5s, then 1s, each plus up to 50% jitter so parallel agents do not retry in lockstep. */
|
|
41
|
+
export declare function transientBackoffMs(attempt: number, random?: () => number): number;
|
|
38
42
|
export declare function completeWithChain(role: ProviderRole, root: string, request: CompleteRequest, options?: RegistryOptions): Promise<ChainResult>;
|