@njeybe/nativ 2.4.1 → 2.5.1
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 +26 -0
- package/README.md +26 -0
- package/dist/commands/db/db-formatter.d.ts +5 -0
- package/dist/commands/db/db-formatter.js +116 -0
- package/dist/commands/db/db-introspect.d.ts +11 -0
- package/dist/commands/db/db-introspect.js +84 -0
- package/dist/commands/db.js +4 -206
- package/dist/commands/profile.d.ts +3 -0
- package/dist/commands/profile.js +39 -0
- package/dist/commands/task/task-add.d.ts +11 -0
- package/dist/commands/task/task-add.js +169 -0
- package/dist/commands/task/task-common.d.ts +57 -0
- package/dist/commands/task/task-common.js +45 -0
- package/dist/commands/task/task-escalate.d.ts +7 -0
- package/dist/commands/task/task-escalate.js +119 -0
- package/dist/commands/task/task-lifecycle.d.ts +5 -0
- package/dist/commands/task/task-lifecycle.js +252 -0
- package/dist/commands/task/task-query.d.ts +10 -0
- package/dist/commands/task/task-query.js +269 -0
- package/dist/commands/task.d.ts +9 -87
- package/dist/commands/task.js +8 -850
- package/dist/commands/triage/triage-store.d.ts +56 -0
- package/dist/commands/triage/triage-store.js +108 -0
- package/dist/commands/triage/triage-ui.d.ts +14 -0
- package/dist/commands/triage/triage-ui.js +104 -0
- package/dist/commands/triage.d.ts +3 -52
- package/dist/commands/triage.js +8 -216
- package/dist/core/enforcement.d.ts +1 -0
- package/dist/core/enforcement.js +19 -0
- package/dist/core/human-gate.d.ts +2 -1
- package/dist/core/human-gate.js +9 -2
- package/dist/core/profiles.d.ts +14 -0
- package/dist/core/profiles.js +82 -0
- package/dist/core/setup/config-merger.d.ts +59 -0
- package/dist/core/setup/config-merger.js +107 -0
- package/dist/core/setup/session-context.d.ts +4 -0
- package/dist/core/setup/session-context.js +65 -0
- package/dist/core/setup/setup-planner.d.ts +14 -0
- package/dist/core/setup/setup-planner.js +180 -0
- package/dist/core/setup/template-planner.d.ts +8 -0
- package/dist/core/setup/template-planner.js +32 -0
- package/dist/core/setup/types.d.ts +65 -0
- package/dist/core/setup/types.js +55 -0
- package/dist/core/setup-assets.d.ts +8 -119
- package/dist/core/setup-assets.js +7 -437
- package/dist/core/tier1-liaison.d.ts +2 -0
- package/dist/core/tier1-liaison.js +64 -3
- package/dist/core/verifier/executor.d.ts +5 -0
- package/dist/core/verifier/executor.js +62 -0
- package/dist/core/verifier/phases.d.ts +5 -0
- package/dist/core/verifier/phases.js +71 -0
- package/dist/core/verifier/task-verifier.d.ts +37 -0
- package/dist/core/verifier/task-verifier.js +233 -0
- package/dist/core/verifier/types.d.ts +51 -0
- package/dist/core/verifier/types.js +1 -0
- package/dist/core/verifier.d.ts +5 -93
- package/dist/core/verifier.js +5 -359
- package/dist/db/introspector.js +59 -59
- package/dist/governor/evaluator.d.ts +4 -64
- package/dist/governor/evaluator.js +21 -686
- package/dist/governor/patch-applier.d.ts +8 -0
- package/dist/governor/patch-applier.js +121 -0
- package/dist/governor/self-healing.d.ts +24 -0
- package/dist/governor/self-healing.js +351 -0
- package/dist/governor/test-integrity.d.ts +41 -0
- package/dist/governor/test-integrity.js +237 -0
- package/dist/index.js +8 -0
- package/dist/mcp/capture.d.ts +4 -0
- package/dist/mcp/capture.js +33 -0
- package/dist/mcp/resources.d.ts +39 -0
- package/dist/mcp/resources.js +44 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +14 -89
- package/dist/mcp/tools.d.ts +1 -0
- package/dist/mcp/tools.js +1 -0
- package/dist/runner/agent-supervisor.d.ts +8 -302
- package/dist/runner/agent-supervisor.js +41 -1030
- package/dist/runner/cli-config.d.ts +84 -0
- package/dist/runner/cli-config.js +215 -0
- package/dist/runner/native-driver.d.ts +18 -0
- package/dist/runner/native-driver.js +208 -0
- package/dist/runner/native-engine.d.ts +27 -0
- package/dist/runner/native-engine.js +184 -0
- package/dist/runner/native-tools.d.ts +31 -0
- package/dist/runner/native-tools.js +241 -0
- package/dist/runner/process-utils.d.ts +12 -0
- package/dist/runner/process-utils.js +96 -0
- package/dist/runner/run-lifecycle.d.ts +6 -0
- package/dist/runner/run-lifecycle.js +42 -0
- package/dist/runner/types.d.ts +215 -0
- package/dist/runner/types.js +41 -0
- package/dist/scanner/detector.js +42 -663
- package/dist/scanner/detectors/ecosystem-detector.d.ts +16 -0
- package/dist/scanner/detectors/ecosystem-detector.js +354 -0
- package/dist/scanner/detectors/orm-detector.d.ts +5 -0
- package/dist/scanner/detectors/orm-detector.js +59 -0
- package/dist/scanner/detectors/workspace-detector.d.ts +19 -0
- package/dist/scanner/detectors/workspace-detector.js +266 -0
- package/dist/server/cli-runner.d.ts +15 -0
- package/dist/server/cli-runner.js +43 -0
- package/dist/server/event-hub.d.ts +42 -0
- package/dist/server/event-hub.js +169 -0
- package/dist/server/http-utils.d.ts +15 -0
- package/dist/server/http-utils.js +56 -0
- package/dist/server/plan-utils.d.ts +9 -0
- package/dist/server/plan-utils.js +31 -0
- package/dist/server/routes/db-routes.d.ts +57 -0
- package/dist/server/routes/db-routes.js +484 -0
- package/dist/server/routes/task-routes.d.ts +266 -0
- package/dist/server/routes/task-routes.js +285 -0
- package/dist/server/routes/telemetry-routes.d.ts +128 -0
- package/dist/server/routes/telemetry-routes.js +179 -0
- package/dist/server/routes/triage-routes.d.ts +81 -0
- package/dist/server/routes/triage-routes.js +244 -0
- package/dist/server/routes/worktree-routes.d.ts +31 -0
- package/dist/server/routes/worktree-routes.js +144 -0
- package/dist/server/studio-server.d.ts +4 -11
- package/dist/server/studio-server.js +34 -1578
- package/dist/server/studio-ui/shell/pipeline-state.js +2 -0
- package/dist/server/studio-ui/shell/triage.js +10 -0
- package/package.json +2 -2
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/agents/architect.md +4 -2
- package/plugin/agents/verifier.md +1 -1
- package/plugin/agents/worker.md +7 -4
- package/plugin/skills/nativ/SKILL.md +1 -0
- package/templates/.claudeignore +62 -45
- package/templates/AGENTS.md +1 -0
- package/templates/CLAUDE.md +13 -0
- package/templates/claude-agents/architect.md +4 -2
- package/templates/claude-agents/verifier.md +1 -1
- package/templates/claude-agents/worker.md +7 -4
- 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 +54 -0
- package/dist/commands/session.d.ts +0 -22
- package/dist/commands/session.js +0 -106
- package/dist/core/session-memory.d.ts +0 -42
- package/dist/core/session-memory.js +0 -121
package/dist/core/verifier.js
CHANGED
|
@@ -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
|
-
*
|
|
2
|
+
* Re-exports the modular verifier engine components.
|
|
23
3
|
*/
|
|
24
|
-
export
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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';
|
package/dist/db/introspector.js
CHANGED
|
@@ -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
|
|
2
|
-
|
|
3
|
-
export
|
|
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
|
}
|