@njeybe/nativ 2.4.1 → 2.5.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 (139) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +26 -0
  3. package/dist/commands/db/db-formatter.d.ts +5 -0
  4. package/dist/commands/db/db-formatter.js +116 -0
  5. package/dist/commands/db/db-introspect.d.ts +11 -0
  6. package/dist/commands/db/db-introspect.js +84 -0
  7. package/dist/commands/db.js +4 -206
  8. package/dist/commands/profile.d.ts +3 -0
  9. package/dist/commands/profile.js +39 -0
  10. package/dist/commands/task/task-add.d.ts +11 -0
  11. package/dist/commands/task/task-add.js +169 -0
  12. package/dist/commands/task/task-common.d.ts +57 -0
  13. package/dist/commands/task/task-common.js +45 -0
  14. package/dist/commands/task/task-escalate.d.ts +7 -0
  15. package/dist/commands/task/task-escalate.js +119 -0
  16. package/dist/commands/task/task-lifecycle.d.ts +5 -0
  17. package/dist/commands/task/task-lifecycle.js +252 -0
  18. package/dist/commands/task/task-query.d.ts +10 -0
  19. package/dist/commands/task/task-query.js +269 -0
  20. package/dist/commands/task.d.ts +9 -87
  21. package/dist/commands/task.js +8 -850
  22. package/dist/commands/triage/triage-store.d.ts +56 -0
  23. package/dist/commands/triage/triage-store.js +108 -0
  24. package/dist/commands/triage/triage-ui.d.ts +14 -0
  25. package/dist/commands/triage/triage-ui.js +104 -0
  26. package/dist/commands/triage.d.ts +3 -52
  27. package/dist/commands/triage.js +8 -216
  28. package/dist/core/enforcement.d.ts +1 -0
  29. package/dist/core/enforcement.js +19 -0
  30. package/dist/core/human-gate.d.ts +2 -1
  31. package/dist/core/human-gate.js +9 -2
  32. package/dist/core/profiles.d.ts +14 -0
  33. package/dist/core/profiles.js +82 -0
  34. package/dist/core/setup/config-merger.d.ts +59 -0
  35. package/dist/core/setup/config-merger.js +107 -0
  36. package/dist/core/setup/session-context.d.ts +4 -0
  37. package/dist/core/setup/session-context.js +65 -0
  38. package/dist/core/setup/setup-planner.d.ts +14 -0
  39. package/dist/core/setup/setup-planner.js +180 -0
  40. package/dist/core/setup/template-planner.d.ts +8 -0
  41. package/dist/core/setup/template-planner.js +32 -0
  42. package/dist/core/setup/types.d.ts +65 -0
  43. package/dist/core/setup/types.js +55 -0
  44. package/dist/core/setup-assets.d.ts +8 -119
  45. package/dist/core/setup-assets.js +7 -437
  46. package/dist/core/tier1-liaison.d.ts +2 -0
  47. package/dist/core/tier1-liaison.js +64 -3
  48. package/dist/core/verifier/executor.d.ts +5 -0
  49. package/dist/core/verifier/executor.js +62 -0
  50. package/dist/core/verifier/phases.d.ts +5 -0
  51. package/dist/core/verifier/phases.js +71 -0
  52. package/dist/core/verifier/task-verifier.d.ts +37 -0
  53. package/dist/core/verifier/task-verifier.js +233 -0
  54. package/dist/core/verifier/types.d.ts +51 -0
  55. package/dist/core/verifier/types.js +1 -0
  56. package/dist/core/verifier.d.ts +5 -93
  57. package/dist/core/verifier.js +5 -359
  58. package/dist/db/introspector.js +59 -59
  59. package/dist/governor/evaluator.d.ts +4 -64
  60. package/dist/governor/evaluator.js +21 -686
  61. package/dist/governor/patch-applier.d.ts +8 -0
  62. package/dist/governor/patch-applier.js +121 -0
  63. package/dist/governor/self-healing.d.ts +24 -0
  64. package/dist/governor/self-healing.js +351 -0
  65. package/dist/governor/test-integrity.d.ts +41 -0
  66. package/dist/governor/test-integrity.js +237 -0
  67. package/dist/index.js +8 -0
  68. package/dist/mcp/capture.d.ts +4 -0
  69. package/dist/mcp/capture.js +33 -0
  70. package/dist/mcp/resources.d.ts +39 -0
  71. package/dist/mcp/resources.js +44 -0
  72. package/dist/mcp/server.d.ts +11 -0
  73. package/dist/mcp/server.js +14 -89
  74. package/dist/mcp/tools.d.ts +1 -0
  75. package/dist/mcp/tools.js +1 -0
  76. package/dist/runner/agent-supervisor.d.ts +8 -302
  77. package/dist/runner/agent-supervisor.js +41 -1030
  78. package/dist/runner/cli-config.d.ts +84 -0
  79. package/dist/runner/cli-config.js +215 -0
  80. package/dist/runner/native-driver.d.ts +18 -0
  81. package/dist/runner/native-driver.js +208 -0
  82. package/dist/runner/native-engine.d.ts +27 -0
  83. package/dist/runner/native-engine.js +184 -0
  84. package/dist/runner/native-tools.d.ts +31 -0
  85. package/dist/runner/native-tools.js +241 -0
  86. package/dist/runner/process-utils.d.ts +12 -0
  87. package/dist/runner/process-utils.js +96 -0
  88. package/dist/runner/run-lifecycle.d.ts +6 -0
  89. package/dist/runner/run-lifecycle.js +42 -0
  90. package/dist/runner/types.d.ts +215 -0
  91. package/dist/runner/types.js +41 -0
  92. package/dist/scanner/detector.js +42 -663
  93. package/dist/scanner/detectors/ecosystem-detector.d.ts +16 -0
  94. package/dist/scanner/detectors/ecosystem-detector.js +354 -0
  95. package/dist/scanner/detectors/orm-detector.d.ts +5 -0
  96. package/dist/scanner/detectors/orm-detector.js +59 -0
  97. package/dist/scanner/detectors/workspace-detector.d.ts +19 -0
  98. package/dist/scanner/detectors/workspace-detector.js +266 -0
  99. package/dist/server/cli-runner.d.ts +15 -0
  100. package/dist/server/cli-runner.js +43 -0
  101. package/dist/server/event-hub.d.ts +42 -0
  102. package/dist/server/event-hub.js +169 -0
  103. package/dist/server/http-utils.d.ts +15 -0
  104. package/dist/server/http-utils.js +56 -0
  105. package/dist/server/plan-utils.d.ts +9 -0
  106. package/dist/server/plan-utils.js +31 -0
  107. package/dist/server/routes/db-routes.d.ts +57 -0
  108. package/dist/server/routes/db-routes.js +484 -0
  109. package/dist/server/routes/task-routes.d.ts +266 -0
  110. package/dist/server/routes/task-routes.js +285 -0
  111. package/dist/server/routes/telemetry-routes.d.ts +128 -0
  112. package/dist/server/routes/telemetry-routes.js +179 -0
  113. package/dist/server/routes/triage-routes.d.ts +81 -0
  114. package/dist/server/routes/triage-routes.js +244 -0
  115. package/dist/server/routes/worktree-routes.d.ts +31 -0
  116. package/dist/server/routes/worktree-routes.js +144 -0
  117. package/dist/server/studio-server.d.ts +4 -11
  118. package/dist/server/studio-server.js +34 -1578
  119. package/dist/server/studio-ui/shell/pipeline-state.js +2 -0
  120. package/dist/server/studio-ui/shell/triage.js +10 -0
  121. package/package.json +2 -2
  122. package/plugin/.claude-plugin/plugin.json +1 -1
  123. package/plugin/agents/architect.md +4 -2
  124. package/plugin/agents/verifier.md +1 -1
  125. package/plugin/agents/worker.md +7 -4
  126. package/plugin/skills/nativ/SKILL.md +1 -0
  127. package/templates/.claudeignore +62 -45
  128. package/templates/AGENTS.md +1 -0
  129. package/templates/CLAUDE.md +13 -0
  130. package/templates/claude-agents/architect.md +4 -2
  131. package/templates/claude-agents/verifier.md +1 -1
  132. package/templates/claude-agents/worker.md +7 -4
  133. package/templates/dot-ai/api_contracts.json +66 -66
  134. package/templates/dot-ai/context.md +38 -38
  135. package/templates/dot-ai/db_schema.json +39 -39
  136. package/dist/commands/session.d.ts +0 -22
  137. package/dist/commands/session.js +0 -106
  138. package/dist/core/session-memory.d.ts +0 -42
  139. package/dist/core/session-memory.js +0 -121
@@ -1,361 +1,7 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
- import { exec } from 'node:child_process';
4
- import { checkCodeShape, formatCodeShapeReport } from './code-shape.js';
5
- import { parseJsonLoose } from './enforcement.js';
6
- import { resolveMainRoot } from './root-resolver.js';
7
- export function loadMasterPlan(targetDir) {
8
- const planPath = path.join(targetDir, '.ai', 'master_plan.json');
9
- if (!fs.existsSync(planPath)) {
10
- return { planPath, plan: null, error: `No .ai/master_plan.json found at: ${planPath}` };
11
- }
12
- try {
13
- const raw = fs.readFileSync(planPath, 'utf8');
14
- const plan = JSON.parse(raw);
15
- return { planPath, plan };
16
- }
17
- catch (err) {
18
- return { planPath, plan: null, error: `Failed to parse .ai/master_plan.json: ${err.message}` };
19
- }
20
- }
21
1
  /**
22
- * Execute a verification command in the workspace directory with timeout and output capture.
2
+ * Re-exports the modular verifier engine components.
23
3
  */
24
- export async function executeVerification(command, cwd, timeoutMs = 120_000) {
25
- const trimmed = (command || '').trim();
26
- // Treat empty, 'none', or 'skip' as an informational pass
27
- if (!trimmed || trimmed.toLowerCase() === 'none' || trimmed.toLowerCase() === 'skip') {
28
- return {
29
- success: true,
30
- exitCode: 0,
31
- command: trimmed || 'none',
32
- stdout: '',
33
- stderr: '',
34
- durationMs: 0,
35
- skipped: true,
36
- };
37
- }
38
- // Cross-platform compatibility for common test commands on Windows cmd.exe
39
- let finalCommand = trimmed;
40
- if (process.platform === 'win32') {
41
- if (trimmed === 'true')
42
- finalCommand = 'exit 0';
43
- else if (trimmed === 'false')
44
- finalCommand = 'exit 1';
45
- }
46
- const start = Date.now();
47
- return new Promise((resolve) => {
48
- exec(finalCommand, {
49
- cwd,
50
- timeout: timeoutMs,
51
- maxBuffer: 10 * 1024 * 1024,
52
- env: process.env,
53
- }, (err, stdout, stderr) => {
54
- const durationMs = Date.now() - start;
55
- const out = (stdout || '').toString();
56
- const errOut = (stderr || '').toString();
57
- if (err) {
58
- const exitCode = typeof err.code === 'number' ? err.code : 1;
59
- resolve({
60
- success: false,
61
- exitCode,
62
- command: trimmed,
63
- stdout: out,
64
- stderr: errOut || err.message,
65
- durationMs,
66
- error: err.killed ? `Command timed out after ${timeoutMs}ms` : err.message,
67
- });
68
- }
69
- else {
70
- resolve({
71
- success: true,
72
- exitCode: 0,
73
- command: trimmed,
74
- stdout: out,
75
- stderr: errOut,
76
- durationMs,
77
- });
78
- }
79
- });
80
- });
81
- }
82
- /**
83
- * Run the code-shape check on a task's target files and print a short summary to stderr.
84
- * In 'warn' mode (default) the result is untouched; in 'block' mode issues fail it with a reason.
85
- */
86
- export function applyCodeShape(result, targetDir, task) {
87
- if (result.skipped || !result.success)
88
- return result;
89
- const report = checkCodeShape(targetDir, task.targetFiles || []);
90
- if (report.mode === 'off')
91
- return result;
92
- result.codeShape = report;
93
- const summary = formatCodeShapeReport(report);
94
- if (summary)
95
- console.error(summary);
96
- if (report.mode === 'block' && report.issues.length > 0) {
97
- result.success = false;
98
- result.exitCode = 1;
99
- result.error = `Code shape check failed: ${report.issues.length} issue(s) (codeStyle.mode is "block")`;
100
- }
101
- return result;
102
- }
103
- /** Reads the file, retrying once: antivirus or an editor saving can briefly lock it on Windows. */
104
- function readConfigText(file) {
105
- for (let attempt = 0;; attempt++) {
106
- try {
107
- return fs.readFileSync(file, 'utf8');
108
- }
109
- catch (err) {
110
- if (err?.code === 'ENOENT')
111
- return null;
112
- if (attempt >= 1)
113
- throw err;
114
- }
115
- }
116
- }
117
- /** `verifyPhases` from the main checkout's `.nativ/config.json`, never from a worktree's own copy. */
118
- export function loadVerifyPhases(configDir) {
119
- let raw;
120
- try {
121
- raw = readConfigText(path.join(resolveMainRoot(configDir), '.nativ', 'config.json'));
122
- }
123
- catch (err) {
124
- return { phases: [], error: `.nativ/config.json cannot be read (${err?.message})` };
125
- }
126
- if (raw === null)
127
- return { phases: [] };
128
- if (!raw.trim())
129
- return { phases: [], error: '.nativ/config.json is empty' };
130
- let phases;
131
- try {
132
- phases = parseJsonLoose(raw)?.verifyPhases;
133
- }
134
- catch (err) {
135
- return { phases: [], error: `.nativ/config.json is not valid JSON (${err?.message})` };
136
- }
137
- if (phases === undefined || phases === null)
138
- return { phases: [] };
139
- // An empty or null "run" switches a phase off; a missing or mistyped field is a mistake.
140
- const isEntry = (p) => typeof p?.name === 'string' && p.name.trim()
141
- && (p.run === null || typeof p.run === 'string');
142
- if (!Array.isArray(phases) || !phases.every(isEntry)) {
143
- return { phases: [], error: 'verifyPhases must be a list of { "name": "...", "run": "..." } entries' };
144
- }
145
- const active = phases.filter((p) => p.run && p.run.trim());
146
- return { phases: active.map((p) => ({ name: p.name.trim(), run: p.run.trim() })) };
147
- }
148
- function configFailure(message) {
149
- const error = `${message}, so the configured checks could not run. Fix it and verify again.`;
150
- const command = '.nativ/config.json';
151
- return { success: false, exitCode: 1, command, stdout: '', stderr: error, durationMs: 0, error };
152
- }
153
- /** Runs the configured phases in order and stops at the first failure. */
154
- export async function runVerifyPhases(cwd, configDir, timeout) {
155
- const config = loadVerifyPhases(configDir);
156
- if (config.error)
157
- return { reports: [], failure: configFailure(config.error) };
158
- const reports = [];
159
- for (const phase of config.phases) {
160
- const res = await executeVerification(phase.run, cwd, timeout);
161
- reports.push({ name: phase.name, command: res.command, success: res.success, durationMs: res.durationMs });
162
- if (!res.success) {
163
- const cause = res.error ? `: ${res.error.trim()}` : '';
164
- return { reports, failure: { ...res, phases: reports, error: `Phase "${phase.name}" failed${cause}` } };
165
- }
166
- }
167
- return { reports };
168
- }
169
- /**
170
- * The one verification every path shares: the configured phases in order, stopping at the
171
- * first failure, then the task's own command, then the code-shape check.
172
- */
173
- export async function runTaskVerification(task, options) {
174
- const configDir = options.configDir ?? options.cwd;
175
- const outcome = options.phases ?? (await runVerifyPhases(options.cwd, configDir, options.timeout));
176
- // In a batch the shared phase time is counted once, by the batch, not again for every task.
177
- if (outcome.failure) {
178
- return { ...outcome.failure, phases: outcome.reports, ...(options.phases ? { durationMs: 0 } : {}) };
179
- }
180
- const raw = await executeVerification(task.verificationCommand, options.cwd, options.timeout);
181
- if (outcome.reports.length) {
182
- raw.phases = outcome.reports;
183
- raw.durationMs += options.phases ? 0 : outcome.reports.reduce((sum, r) => sum + r.durationMs, 0);
184
- raw.skipped = false;
185
- }
186
- return applyCodeShape(raw, options.cwd, task);
187
- }
188
- /**
189
- * Run verification for a single specific task in the plan.
190
- */
191
- export async function verifyTask(taskId, targetDirArg, options = {}) {
192
- const targetDir = path.resolve(targetDirArg || process.cwd());
193
- const { plan, error } = loadMasterPlan(targetDir);
194
- if (!plan) {
195
- return {
196
- taskId,
197
- title: 'Unknown',
198
- milestoneId: 'unknown',
199
- milestoneName: 'Unknown',
200
- status: 'unknown',
201
- result: {
202
- success: false,
203
- exitCode: 1,
204
- command: '',
205
- stdout: '',
206
- stderr: error || 'Failed to load master plan',
207
- durationMs: 0,
208
- error: error || 'Failed to load master plan',
209
- },
210
- };
211
- }
212
- let foundTask = null;
213
- let foundMilestone = null;
214
- for (const m of plan.milestones) {
215
- const t = m.tasks.find((item) => item.id === taskId);
216
- if (t) {
217
- foundTask = t;
218
- foundMilestone = m;
219
- break;
220
- }
221
- }
222
- if (!foundTask || !foundMilestone) {
223
- return {
224
- taskId,
225
- title: 'Not Found',
226
- milestoneId: 'unknown',
227
- milestoneName: 'Unknown',
228
- status: 'unknown',
229
- result: {
230
- success: false,
231
- exitCode: 1,
232
- command: '',
233
- stdout: '',
234
- stderr: `Task [${taskId}] not found in .ai/master_plan.json`,
235
- durationMs: 0,
236
- error: `Task [${taskId}] not found`,
237
- },
238
- };
239
- }
240
- const result = await runTaskVerification(foundTask, { cwd: targetDir, timeout: options.timeout });
241
- return {
242
- taskId: foundTask.id,
243
- title: foundTask.title,
244
- milestoneId: foundMilestone.id,
245
- milestoneName: foundMilestone.name,
246
- status: foundTask.status,
247
- result,
248
- };
249
- }
250
- /**
251
- * Verify a batch of tasks (by milestone, all completed tasks, or the active in-progress task).
252
- */
253
- export async function verifyBatch(targetDirArg, options = {}) {
254
- const targetDir = path.resolve(targetDirArg || process.cwd());
255
- const { plan, error } = loadMasterPlan(targetDir);
256
- if (!plan) {
257
- return {
258
- projectName: 'unknown',
259
- total: 0,
260
- passed: 0,
261
- failed: 1,
262
- skipped: 0,
263
- durationMs: 0,
264
- tasks: [
265
- {
266
- taskId: 'init',
267
- title: 'Plan Loader',
268
- milestoneId: 'unknown',
269
- milestoneName: 'Unknown',
270
- status: 'unknown',
271
- result: {
272
- success: false,
273
- exitCode: 1,
274
- command: '',
275
- stdout: '',
276
- stderr: error || 'Plan not found',
277
- durationMs: 0,
278
- error: error || 'Plan not found',
279
- },
280
- },
281
- ],
282
- };
283
- }
284
- // Filter tasks to run
285
- const candidates = [];
286
- for (const m of plan.milestones) {
287
- for (const t of m.tasks) {
288
- if (options.taskId && t.id === options.taskId) {
289
- candidates.push({ task: t, milestone: m });
290
- }
291
- else if (options.milestoneId && (m.id === options.milestoneId || m.name.toLowerCase() === options.milestoneId.toLowerCase())) {
292
- candidates.push({ task: t, milestone: m });
293
- }
294
- else if (options.all) {
295
- // All tasks with verification commands (prefer completed or in_progress, but include all)
296
- candidates.push({ task: t, milestone: m });
297
- }
298
- }
299
- }
300
- // If no filters specified, find the current in_progress task, or next ready task
301
- if (!options.taskId && !options.milestoneId && !options.all) {
302
- let activeTask = null;
303
- for (const m of plan.milestones) {
304
- const inProgress = m.tasks.find((t) => t.status === 'in_progress');
305
- if (inProgress) {
306
- activeTask = { task: inProgress, milestone: m };
307
- break;
308
- }
309
- }
310
- if (!activeTask) {
311
- for (const m of plan.milestones) {
312
- const pending = m.tasks.find((t) => t.status === 'pending');
313
- if (pending) {
314
- activeTask = { task: pending, milestone: m };
315
- break;
316
- }
317
- }
318
- }
319
- if (activeTask) {
320
- candidates.push(activeTask);
321
- }
322
- }
323
- const results = [];
324
- let passed = 0;
325
- let failed = 0;
326
- let skipped = 0;
327
- let totalDuration = 0;
328
- // The phases check the whole project, so one run serves every task in the batch.
329
- const phases = candidates.length ? await runVerifyPhases(targetDir, targetDir, options.timeout) : { reports: [] };
330
- totalDuration += phases.reports.reduce((sum, r) => sum + r.durationMs, 0);
331
- for (const { task, milestone } of candidates) {
332
- const vResult = await runTaskVerification(task, { cwd: targetDir, timeout: options.timeout, phases });
333
- totalDuration += vResult.durationMs;
334
- if (vResult.skipped) {
335
- skipped++;
336
- }
337
- else if (vResult.success) {
338
- passed++;
339
- }
340
- else {
341
- failed++;
342
- }
343
- results.push({
344
- taskId: task.id,
345
- title: task.title,
346
- milestoneId: milestone.id,
347
- milestoneName: milestone.name,
348
- status: task.status,
349
- result: vResult,
350
- });
351
- }
352
- return {
353
- projectName: plan.projectName,
354
- total: candidates.length,
355
- passed,
356
- failed,
357
- skipped,
358
- durationMs: totalDuration,
359
- tasks: results,
360
- };
361
- }
4
+ export * from './verifier/types.js';
5
+ export { executeVerification } from './verifier/executor.js';
6
+ export { loadVerifyPhases, runVerifyPhases } from './verifier/phases.js';
7
+ export { loadMasterPlan, applyCodeShape, runTaskVerification, verifyTask, verifyBatch, } from './verifier/task-verifier.js';
@@ -81,30 +81,30 @@ async function createPostgresDriver(url) {
81
81
  },
82
82
  async tables() {
83
83
  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'
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'
88
88
  ORDER BY t.table_name`);
89
89
  for (const row of tables.rows) {
90
90
  const table = builder.get(row.table_name);
91
91
  if (row.description)
92
92
  table.description = row.description;
93
93
  }
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
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
99
99
  WHERE tc.table_schema = current_schema() AND tc.constraint_type = 'PRIMARY KEY'`);
100
100
  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()
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()
108
108
  ORDER BY c.table_name, c.ordinal_position`);
109
109
  for (const row of columns.rows) {
110
110
  builder.get(row.table_name).columns.push({
@@ -116,31 +116,31 @@ async function createPostgresDriver(url) {
116
116
  });
117
117
  }
118
118
  // 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')
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')
129
129
  ORDER BY t.relname, i.relname`);
130
130
  for (const row of indexes.rows) {
131
131
  builder.get(row.table_name).indexes.push({ name: row.index_name, columns: row.columns ?? [], unique: row.is_unique });
132
132
  }
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'
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'
144
144
  ORDER BY cl.relname, con.conname`);
145
145
  for (const row of fks.rows) {
146
146
  builder.get(row.table_name).foreignKeys.push({
@@ -177,22 +177,22 @@ async function createMysqlDriver(url) {
177
177
  },
178
178
  async tables() {
179
179
  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'
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'
184
184
  ORDER BY TABLE_NAME`)) {
185
185
  const table = builder.get(row.table_name);
186
186
  if (row.table_comment)
187
187
  table.description = row.table_comment;
188
188
  }
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()
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()
196
196
  ORDER BY c.TABLE_NAME, c.ORDINAL_POSITION`)) {
197
197
  builder.get(row.table_name).columns.push({
198
198
  name: row.column_name,
@@ -203,10 +203,10 @@ async function createMysqlDriver(url) {
203
203
  });
204
204
  }
205
205
  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'
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'
210
210
  ORDER BY TABLE_NAME, INDEX_NAME, SEQ_IN_INDEX`)) {
211
211
  const key = `${row.table_name}.${row.index_name}`;
212
212
  let entry = indexMap.get(key);
@@ -219,13 +219,13 @@ async function createMysqlDriver(url) {
219
219
  }
220
220
  for (const { table, index } of indexMap.values())
221
221
  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
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
229
229
  ORDER BY k.TABLE_NAME, k.CONSTRAINT_NAME, k.ORDINAL_POSITION`)) {
230
230
  builder.get(row.table_name).foreignKeys.push({
231
231
  name: row.constraint_name,
@@ -1,67 +1,7 @@
1
- import { ContractPatch, GovernorVerdict, RuleEvaluationResult } from './types.js';
2
- import { type CandidatePatch, type ContractPatchProposal, type SelfHealingProposal, type TestRestorationProposal, type VerificationProof } from './circuit-breaker.js';
3
- export declare function getContractHash(filePath: string): string | null;
4
- /**
5
- * Replays a candidate against a throwaway copy of the contract in the OS temp
6
- * directory: governor rules, apply, re-parse, and proof that it changes
7
- * something while the live `.ai/` file stays byte-identical.
8
- */
9
- export declare function verifyCandidateInSandbox(targetDir: string, taskId: string, candidate: CandidatePatch): VerificationProof;
10
- /**
11
- * Builds the first backward-compatible candidate that survives an isolated
12
- * replay. Returns null when no candidate can be proven (a human decides).
13
- */
14
- export declare function generateSelfHealingProposal(targetDir: string, patch: ContractPatch, rule: RuleEvaluationResult): ContractPatchProposal | null;
15
- export interface ProposalApplication {
16
- applied: boolean;
17
- ruleId: string;
18
- message: string;
19
- }
20
- /**
21
- * Applies an operator-approved proposal. Contract patches are re-checked
22
- * against the live contract and refused if it moved since the proof was taken.
23
- * Human-approved patches do not count against the task's patch budget.
24
- */
25
- export declare function applySelfHealingProposal(targetDir: string, taskId: string, proposal: SelfHealingProposal): ProposalApplication;
26
- export type TestIntegrityRule = 'TEST_SUITE_DELETED' | 'TEST_ASSERTIONS_REMOVED' | 'TEST_CASES_REMOVED' | 'TESTS_SKIPPED' | 'TESTS_FOCUSED' | 'TEST_SCRIPT_WEAKENED';
27
- export interface TestIntegrityFinding {
28
- rule: TestIntegrityRule;
29
- /** Path relative to the project root. */
30
- path: string;
31
- detail: string;
32
- }
33
- export interface TestIntegrityReport extends RuleEvaluationResult {
34
- /** Resolved commit the working tree was compared against; null when the check was skipped. */
35
- baselineRef: string | null;
36
- /** True when the baseline came from `nativ task start` rather than falling back to HEAD. */
37
- baselineRecorded: boolean;
38
- findings: TestIntegrityFinding[];
39
- skippedReason?: string;
40
- }
41
- export interface TestSuiteMetrics {
42
- assertions: number;
43
- testCases: number;
44
- skipped: number;
45
- focused: number;
46
- }
47
- export declare function isTestFile(file: string): boolean;
48
- /** Heuristic, language-agnostic counts; only deltas against the baseline matter. */
49
- export declare function measureTestSuite(source: string): TestSuiteMetrics;
50
- /** Current commit of the checkout at `targetDir`, or null outside git / before the first commit. */
51
- export declare function resolveGitHead(targetDir: string): string | null;
52
- /**
53
- * Enforces that a task never deletes or weakens the test suites that existed
54
- * when it started: no deleted test files, no fewer assertions or cases, no new
55
- * skip/only markers, and no test files dropped from package.json test scripts.
56
- */
57
- export declare class TestIntegrityGuard {
58
- static evaluate(targetDir: string, taskId: string): TestIntegrityReport;
59
- /**
60
- * Proposal to restore every flagged file from the baseline, with proof that
61
- * each baseline blob exists and what the restored suite measures.
62
- */
63
- static proposeRestoration(targetDir: string, report: TestIntegrityReport): TestRestorationProposal | null;
64
- }
1
+ import { ContractPatch, GovernorVerdict } from './types.js';
2
+ export * from './patch-applier.js';
3
+ export * from './self-healing.js';
4
+ export * from './test-integrity.js';
65
5
  export declare class ContractGovernor {
66
6
  static evaluate(targetDir: string, patch: ContractPatch): GovernorVerdict;
67
7
  }