@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
|
@@ -90,6 +90,62 @@ function loadContracts(root) {
|
|
|
90
90
|
uiSpecs: read('ui_specs.md') ?? undefined,
|
|
91
91
|
};
|
|
92
92
|
}
|
|
93
|
+
export function loadTriageConfig(root) {
|
|
94
|
+
const configFile = path.join(root, '.nativ', 'config.json');
|
|
95
|
+
try {
|
|
96
|
+
if (!fs.existsSync(configFile))
|
|
97
|
+
return {};
|
|
98
|
+
const parsed = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
99
|
+
if (parsed && typeof parsed === 'object' && parsed.triage) {
|
|
100
|
+
const res = {};
|
|
101
|
+
if (typeof parsed.triage.autoTriageEnabled === 'boolean') {
|
|
102
|
+
res.autoTriageEnabled = parsed.triage.autoTriageEnabled;
|
|
103
|
+
}
|
|
104
|
+
if (parsed.triage.riskThreshold === 'safe_contracts_only' || parsed.triage.riskThreshold === 'all_non_destructive') {
|
|
105
|
+
res.riskThreshold = parsed.triage.riskThreshold;
|
|
106
|
+
}
|
|
107
|
+
if (typeof parsed.triage.model === 'string' && parsed.triage.model.trim()) {
|
|
108
|
+
res.model = parsed.triage.model.trim();
|
|
109
|
+
}
|
|
110
|
+
return res;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Ignore corrupt or unreadable config file
|
|
115
|
+
}
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
export function saveTriageConfig(root, update) {
|
|
119
|
+
const nativDir = path.join(root, '.nativ');
|
|
120
|
+
const configFile = path.join(nativDir, 'config.json');
|
|
121
|
+
try {
|
|
122
|
+
if (!fs.existsSync(nativDir))
|
|
123
|
+
fs.mkdirSync(nativDir, { recursive: true });
|
|
124
|
+
let existing = {};
|
|
125
|
+
if (fs.existsSync(configFile)) {
|
|
126
|
+
try {
|
|
127
|
+
existing = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
existing = {};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const currentTriage = (existing.triage && typeof existing.triage === 'object') ? existing.triage : {};
|
|
134
|
+
if (typeof update.autoTriageEnabled === 'boolean')
|
|
135
|
+
currentTriage.autoTriageEnabled = update.autoTriageEnabled;
|
|
136
|
+
if (update.riskThreshold)
|
|
137
|
+
currentTriage.riskThreshold = update.riskThreshold;
|
|
138
|
+
if (update.model)
|
|
139
|
+
currentTriage.model = update.model;
|
|
140
|
+
existing.triage = currentTriage;
|
|
141
|
+
const tmp = `${configFile}.${process.pid}.${Date.now()}.tmp`;
|
|
142
|
+
fs.writeFileSync(tmp, JSON.stringify(existing, null, 2) + '\n', 'utf8');
|
|
143
|
+
fs.renameSync(tmp, configFile);
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Ignore write failures gracefully
|
|
147
|
+
}
|
|
148
|
+
}
|
|
93
149
|
/**
|
|
94
150
|
* Tier 1 AI Strategist Liaison instance managing escalation evaluation,
|
|
95
151
|
* auto-patch application, and configuration.
|
|
@@ -106,11 +162,12 @@ export class Tier1Liaison {
|
|
|
106
162
|
constructor(root, options = {}) {
|
|
107
163
|
this.root = root;
|
|
108
164
|
this.options = options;
|
|
165
|
+
const saved = loadTriageConfig(root);
|
|
109
166
|
this.config = {
|
|
110
167
|
// Off by default: even a fix classified as safe waits for confirmation unless auto-triage is enabled.
|
|
111
|
-
autoTriageEnabled: options.config?.autoTriageEnabled ?? false,
|
|
112
|
-
riskThreshold: options.config?.riskThreshold ?? 'safe_contracts_only',
|
|
113
|
-
model: options.model ?? DEFAULT_MODEL,
|
|
168
|
+
autoTriageEnabled: options.config?.autoTriageEnabled ?? saved.autoTriageEnabled ?? false,
|
|
169
|
+
riskThreshold: options.config?.riskThreshold ?? saved.riskThreshold ?? 'safe_contracts_only',
|
|
170
|
+
model: options.model ?? saved.model ?? DEFAULT_MODEL,
|
|
114
171
|
};
|
|
115
172
|
}
|
|
116
173
|
/** Provider options after the legacy `apiKey` and `model` hints are folded in. */
|
|
@@ -155,6 +212,10 @@ export class Tier1Liaison {
|
|
|
155
212
|
if (body.riskThreshold === 'safe_contracts_only' || body.riskThreshold === 'all_non_destructive') {
|
|
156
213
|
this.config.riskThreshold = body.riskThreshold;
|
|
157
214
|
}
|
|
215
|
+
if (typeof body.model === 'string' && body.model.trim()) {
|
|
216
|
+
this.config.model = body.model.trim();
|
|
217
|
+
}
|
|
218
|
+
saveTriageConfig(this.root, this.config);
|
|
158
219
|
return { ok: true, config: { ...this.config } };
|
|
159
220
|
}
|
|
160
221
|
async evaluate(escalationId) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { VerificationResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a verification command in the workspace directory with timeout and output capture.
|
|
4
|
+
*/
|
|
5
|
+
export declare function executeVerification(command: string, cwd: string, timeoutMs?: number): Promise<VerificationResult>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* Execute a verification command in the workspace directory with timeout and output capture.
|
|
4
|
+
*/
|
|
5
|
+
export async function executeVerification(command, cwd, timeoutMs = 120_000) {
|
|
6
|
+
const trimmed = (command || '').trim();
|
|
7
|
+
// Treat empty, 'none', or 'skip' as an informational pass
|
|
8
|
+
if (!trimmed || trimmed.toLowerCase() === 'none' || trimmed.toLowerCase() === 'skip') {
|
|
9
|
+
return {
|
|
10
|
+
success: true,
|
|
11
|
+
exitCode: 0,
|
|
12
|
+
command: trimmed || 'none',
|
|
13
|
+
stdout: '',
|
|
14
|
+
stderr: '',
|
|
15
|
+
durationMs: 0,
|
|
16
|
+
skipped: true,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
// Cross-platform compatibility for common test commands on Windows cmd.exe
|
|
20
|
+
let finalCommand = trimmed;
|
|
21
|
+
if (process.platform === 'win32') {
|
|
22
|
+
if (trimmed === 'true')
|
|
23
|
+
finalCommand = 'exit 0';
|
|
24
|
+
else if (trimmed === 'false')
|
|
25
|
+
finalCommand = 'exit 1';
|
|
26
|
+
}
|
|
27
|
+
const start = Date.now();
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
exec(finalCommand, {
|
|
30
|
+
cwd,
|
|
31
|
+
timeout: timeoutMs,
|
|
32
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
33
|
+
env: process.env,
|
|
34
|
+
}, (err, stdout, stderr) => {
|
|
35
|
+
const durationMs = Date.now() - start;
|
|
36
|
+
const out = (stdout || '').toString();
|
|
37
|
+
const errOut = (stderr || '').toString();
|
|
38
|
+
if (err) {
|
|
39
|
+
const exitCode = typeof err.code === 'number' ? err.code : 1;
|
|
40
|
+
resolve({
|
|
41
|
+
success: false,
|
|
42
|
+
exitCode,
|
|
43
|
+
command: trimmed,
|
|
44
|
+
stdout: out,
|
|
45
|
+
stderr: errOut || err.message,
|
|
46
|
+
durationMs,
|
|
47
|
+
error: err.killed ? `Command timed out after ${timeoutMs}ms` : err.message,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
resolve({
|
|
52
|
+
success: true,
|
|
53
|
+
exitCode: 0,
|
|
54
|
+
command: trimmed,
|
|
55
|
+
stdout: out,
|
|
56
|
+
stderr: errOut,
|
|
57
|
+
durationMs,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { PhasesOutcome, VerifyPhasesConfig } from './types.js';
|
|
2
|
+
/** `verifyPhases` from the main checkout's `.nativ/config.json`, never from a worktree's own copy. */
|
|
3
|
+
export declare function loadVerifyPhases(configDir: string): VerifyPhasesConfig;
|
|
4
|
+
/** Runs the configured phases in order and stops at the first failure. */
|
|
5
|
+
export declare function runVerifyPhases(cwd: string, configDir: string, timeout?: number): Promise<PhasesOutcome>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseJsonLoose } from '../enforcement.js';
|
|
4
|
+
import { resolveMainRoot } from '../root-resolver.js';
|
|
5
|
+
import { executeVerification } from './executor.js';
|
|
6
|
+
/** Reads the file, retrying once: antivirus or an editor saving can briefly lock it on Windows. */
|
|
7
|
+
function readConfigText(file) {
|
|
8
|
+
for (let attempt = 0;; attempt++) {
|
|
9
|
+
try {
|
|
10
|
+
return fs.readFileSync(file, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
catch (err) {
|
|
13
|
+
if (err?.code === 'ENOENT')
|
|
14
|
+
return null;
|
|
15
|
+
if (attempt >= 1)
|
|
16
|
+
throw err;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** `verifyPhases` from the main checkout's `.nativ/config.json`, never from a worktree's own copy. */
|
|
21
|
+
export function loadVerifyPhases(configDir) {
|
|
22
|
+
let raw;
|
|
23
|
+
try {
|
|
24
|
+
raw = readConfigText(path.join(resolveMainRoot(configDir), '.nativ', 'config.json'));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
return { phases: [], error: `.nativ/config.json cannot be read (${err?.message})` };
|
|
28
|
+
}
|
|
29
|
+
if (raw === null)
|
|
30
|
+
return { phases: [] };
|
|
31
|
+
if (!raw.trim())
|
|
32
|
+
return { phases: [], error: '.nativ/config.json is empty' };
|
|
33
|
+
let phases;
|
|
34
|
+
try {
|
|
35
|
+
phases = parseJsonLoose(raw)?.verifyPhases;
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
return { phases: [], error: `.nativ/config.json is not valid JSON (${err?.message})` };
|
|
39
|
+
}
|
|
40
|
+
if (phases === undefined || phases === null)
|
|
41
|
+
return { phases: [] };
|
|
42
|
+
// An empty or null "run" switches a phase off; a missing or mistyped field is a mistake.
|
|
43
|
+
const isEntry = (p) => typeof p?.name === 'string' && p.name.trim()
|
|
44
|
+
&& (p.run === null || typeof p.run === 'string');
|
|
45
|
+
if (!Array.isArray(phases) || !phases.every(isEntry)) {
|
|
46
|
+
return { phases: [], error: 'verifyPhases must be a list of { "name": "...", "run": "..." } entries' };
|
|
47
|
+
}
|
|
48
|
+
const active = phases.filter((p) => p.run && p.run.trim());
|
|
49
|
+
return { phases: active.map((p) => ({ name: p.name.trim(), run: p.run.trim() })) };
|
|
50
|
+
}
|
|
51
|
+
function configFailure(message) {
|
|
52
|
+
const error = `${message}, so the configured checks could not run. Fix it and verify again.`;
|
|
53
|
+
const command = '.nativ/config.json';
|
|
54
|
+
return { success: false, exitCode: 1, command, stdout: '', stderr: error, durationMs: 0, error };
|
|
55
|
+
}
|
|
56
|
+
/** Runs the configured phases in order and stops at the first failure. */
|
|
57
|
+
export async function runVerifyPhases(cwd, configDir, timeout) {
|
|
58
|
+
const config = loadVerifyPhases(configDir);
|
|
59
|
+
if (config.error)
|
|
60
|
+
return { reports: [], failure: configFailure(config.error) };
|
|
61
|
+
const reports = [];
|
|
62
|
+
for (const phase of config.phases) {
|
|
63
|
+
const res = await executeVerification(phase.run, cwd, timeout);
|
|
64
|
+
reports.push({ name: phase.name, command: res.command, success: res.success, durationMs: res.durationMs });
|
|
65
|
+
if (!res.success) {
|
|
66
|
+
const cause = res.error ? `: ${res.error.trim()}` : '';
|
|
67
|
+
return { reports, failure: { ...res, phases: reports, error: `Phase "${phase.name}" failed${cause}` } };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { reports };
|
|
71
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { MasterPlan, MasterPlanTask } from '../../scanner/types.js';
|
|
2
|
+
import type { BatchVerificationResult, PhasesOutcome, TaskVerificationResult, VerificationResult } from './types.js';
|
|
3
|
+
export declare function loadMasterPlan(targetDir: string): {
|
|
4
|
+
planPath: string;
|
|
5
|
+
plan: MasterPlan | null;
|
|
6
|
+
error?: string;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Run the code-shape check on a task's target files and print a short summary to stderr.
|
|
10
|
+
* In 'warn' mode (default) the result is untouched; in 'block' mode issues fail it with a reason.
|
|
11
|
+
*/
|
|
12
|
+
export declare function applyCodeShape(result: VerificationResult, targetDir: string, task: MasterPlanTask): VerificationResult;
|
|
13
|
+
/**
|
|
14
|
+
* The one verification every path shares: the configured phases in order, stopping at the
|
|
15
|
+
* first failure, then the task's own command, then the code-shape check.
|
|
16
|
+
*/
|
|
17
|
+
export declare function runTaskVerification(task: MasterPlanTask, options: {
|
|
18
|
+
cwd: string;
|
|
19
|
+
configDir?: string;
|
|
20
|
+
timeout?: number;
|
|
21
|
+
phases?: PhasesOutcome;
|
|
22
|
+
}): Promise<VerificationResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Run verification for a single specific task in the plan.
|
|
25
|
+
*/
|
|
26
|
+
export declare function verifyTask(taskId: string, targetDirArg?: string, options?: {
|
|
27
|
+
timeout?: number;
|
|
28
|
+
}): Promise<TaskVerificationResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Verify a batch of tasks (by milestone, all completed tasks, or the active in-progress task).
|
|
31
|
+
*/
|
|
32
|
+
export declare function verifyBatch(targetDirArg?: string, options?: {
|
|
33
|
+
taskId?: string;
|
|
34
|
+
milestoneId?: string;
|
|
35
|
+
all?: boolean;
|
|
36
|
+
timeout?: number;
|
|
37
|
+
}): Promise<BatchVerificationResult>;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { checkCodeShape, formatCodeShapeReport } from '../code-shape.js';
|
|
4
|
+
import { executeVerification } from './executor.js';
|
|
5
|
+
import { runVerifyPhases } from './phases.js';
|
|
6
|
+
export function loadMasterPlan(targetDir) {
|
|
7
|
+
const planPath = path.join(targetDir, '.ai', 'master_plan.json');
|
|
8
|
+
if (!fs.existsSync(planPath)) {
|
|
9
|
+
return { planPath, plan: null, error: `No .ai/master_plan.json found at: ${planPath}` };
|
|
10
|
+
}
|
|
11
|
+
try {
|
|
12
|
+
const raw = fs.readFileSync(planPath, 'utf8');
|
|
13
|
+
const plan = JSON.parse(raw);
|
|
14
|
+
return { planPath, plan };
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
return { planPath, plan: null, error: `Failed to parse .ai/master_plan.json: ${err.message}` };
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run the code-shape check on a task's target files and print a short summary to stderr.
|
|
22
|
+
* In 'warn' mode (default) the result is untouched; in 'block' mode issues fail it with a reason.
|
|
23
|
+
*/
|
|
24
|
+
export function applyCodeShape(result, targetDir, task) {
|
|
25
|
+
if (result.skipped || !result.success)
|
|
26
|
+
return result;
|
|
27
|
+
const report = checkCodeShape(targetDir, task.targetFiles || []);
|
|
28
|
+
if (report.mode === 'off')
|
|
29
|
+
return result;
|
|
30
|
+
result.codeShape = report;
|
|
31
|
+
const summary = formatCodeShapeReport(report);
|
|
32
|
+
if (summary)
|
|
33
|
+
console.error(summary);
|
|
34
|
+
if (report.mode === 'block' && report.issues.length > 0) {
|
|
35
|
+
result.success = false;
|
|
36
|
+
result.exitCode = 1;
|
|
37
|
+
result.error = `Code shape check failed: ${report.issues.length} issue(s) (codeStyle.mode is "block")`;
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The one verification every path shares: the configured phases in order, stopping at the
|
|
43
|
+
* first failure, then the task's own command, then the code-shape check.
|
|
44
|
+
*/
|
|
45
|
+
export async function runTaskVerification(task, options) {
|
|
46
|
+
const configDir = options.configDir ?? options.cwd;
|
|
47
|
+
const outcome = options.phases ?? (await runVerifyPhases(options.cwd, configDir, options.timeout));
|
|
48
|
+
// In a batch the shared phase time is counted once, by the batch, not again for every task.
|
|
49
|
+
if (outcome.failure) {
|
|
50
|
+
return { ...outcome.failure, phases: outcome.reports, ...(options.phases ? { durationMs: 0 } : {}) };
|
|
51
|
+
}
|
|
52
|
+
const raw = await executeVerification(task.verificationCommand, options.cwd, options.timeout);
|
|
53
|
+
if (outcome.reports.length) {
|
|
54
|
+
raw.phases = outcome.reports;
|
|
55
|
+
raw.durationMs += options.phases ? 0 : outcome.reports.reduce((sum, r) => sum + r.durationMs, 0);
|
|
56
|
+
raw.skipped = false;
|
|
57
|
+
}
|
|
58
|
+
return applyCodeShape(raw, options.cwd, task);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Run verification for a single specific task in the plan.
|
|
62
|
+
*/
|
|
63
|
+
export async function verifyTask(taskId, targetDirArg, options = {}) {
|
|
64
|
+
const targetDir = path.resolve(targetDirArg || process.cwd());
|
|
65
|
+
const { plan, error } = loadMasterPlan(targetDir);
|
|
66
|
+
if (!plan) {
|
|
67
|
+
return {
|
|
68
|
+
taskId,
|
|
69
|
+
title: 'Unknown',
|
|
70
|
+
milestoneId: 'unknown',
|
|
71
|
+
milestoneName: 'Unknown',
|
|
72
|
+
status: 'unknown',
|
|
73
|
+
result: {
|
|
74
|
+
success: false,
|
|
75
|
+
exitCode: 1,
|
|
76
|
+
command: '',
|
|
77
|
+
stdout: '',
|
|
78
|
+
stderr: error || 'Failed to load master plan',
|
|
79
|
+
durationMs: 0,
|
|
80
|
+
error: error || 'Failed to load master plan',
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
let foundTask = null;
|
|
85
|
+
let foundMilestone = null;
|
|
86
|
+
for (const m of plan.milestones) {
|
|
87
|
+
const t = m.tasks.find((item) => item.id === taskId);
|
|
88
|
+
if (t) {
|
|
89
|
+
foundTask = t;
|
|
90
|
+
foundMilestone = m;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (!foundTask || !foundMilestone) {
|
|
95
|
+
return {
|
|
96
|
+
taskId,
|
|
97
|
+
title: 'Not Found',
|
|
98
|
+
milestoneId: 'unknown',
|
|
99
|
+
milestoneName: 'Unknown',
|
|
100
|
+
status: 'unknown',
|
|
101
|
+
result: {
|
|
102
|
+
success: false,
|
|
103
|
+
exitCode: 1,
|
|
104
|
+
command: '',
|
|
105
|
+
stdout: '',
|
|
106
|
+
stderr: `Task [${taskId}] not found in .ai/master_plan.json`,
|
|
107
|
+
durationMs: 0,
|
|
108
|
+
error: `Task [${taskId}] not found`,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const result = await runTaskVerification(foundTask, { cwd: targetDir, timeout: options.timeout });
|
|
113
|
+
return {
|
|
114
|
+
taskId: foundTask.id,
|
|
115
|
+
title: foundTask.title,
|
|
116
|
+
milestoneId: foundMilestone.id,
|
|
117
|
+
milestoneName: foundMilestone.name,
|
|
118
|
+
status: foundTask.status,
|
|
119
|
+
result,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Verify a batch of tasks (by milestone, all completed tasks, or the active in-progress task).
|
|
124
|
+
*/
|
|
125
|
+
export async function verifyBatch(targetDirArg, options = {}) {
|
|
126
|
+
const targetDir = path.resolve(targetDirArg || process.cwd());
|
|
127
|
+
const { plan, error } = loadMasterPlan(targetDir);
|
|
128
|
+
if (!plan) {
|
|
129
|
+
return {
|
|
130
|
+
projectName: 'unknown',
|
|
131
|
+
total: 0,
|
|
132
|
+
passed: 0,
|
|
133
|
+
failed: 1,
|
|
134
|
+
skipped: 0,
|
|
135
|
+
durationMs: 0,
|
|
136
|
+
tasks: [
|
|
137
|
+
{
|
|
138
|
+
taskId: 'init',
|
|
139
|
+
title: 'Plan Loader',
|
|
140
|
+
milestoneId: 'unknown',
|
|
141
|
+
milestoneName: 'Unknown',
|
|
142
|
+
status: 'unknown',
|
|
143
|
+
result: {
|
|
144
|
+
success: false,
|
|
145
|
+
exitCode: 1,
|
|
146
|
+
command: '',
|
|
147
|
+
stdout: '',
|
|
148
|
+
stderr: error || 'Plan not found',
|
|
149
|
+
durationMs: 0,
|
|
150
|
+
error: error || 'Plan not found',
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
// Filter tasks to run
|
|
157
|
+
const candidates = [];
|
|
158
|
+
for (const m of plan.milestones) {
|
|
159
|
+
for (const t of m.tasks) {
|
|
160
|
+
if (options.taskId && t.id === options.taskId) {
|
|
161
|
+
candidates.push({ task: t, milestone: m });
|
|
162
|
+
}
|
|
163
|
+
else if (options.milestoneId && (m.id === options.milestoneId || m.name.toLowerCase() === options.milestoneId.toLowerCase())) {
|
|
164
|
+
candidates.push({ task: t, milestone: m });
|
|
165
|
+
}
|
|
166
|
+
else if (options.all) {
|
|
167
|
+
// All tasks with verification commands (prefer completed or in_progress, but include all)
|
|
168
|
+
candidates.push({ task: t, milestone: m });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// If no filters specified, find the current in_progress task, or next ready task
|
|
173
|
+
if (!options.taskId && !options.milestoneId && !options.all) {
|
|
174
|
+
let activeTask = null;
|
|
175
|
+
for (const m of plan.milestones) {
|
|
176
|
+
const inProgress = m.tasks.find((t) => t.status === 'in_progress');
|
|
177
|
+
if (inProgress) {
|
|
178
|
+
activeTask = { task: inProgress, milestone: m };
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (!activeTask) {
|
|
183
|
+
for (const m of plan.milestones) {
|
|
184
|
+
const pending = m.tasks.find((t) => t.status === 'pending');
|
|
185
|
+
if (pending) {
|
|
186
|
+
activeTask = { task: pending, milestone: m };
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (activeTask) {
|
|
192
|
+
candidates.push(activeTask);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const results = [];
|
|
196
|
+
let passed = 0;
|
|
197
|
+
let failed = 0;
|
|
198
|
+
let skipped = 0;
|
|
199
|
+
let totalDuration = 0;
|
|
200
|
+
// The phases check the whole project, so one run serves every task in the batch.
|
|
201
|
+
const phases = candidates.length ? await runVerifyPhases(targetDir, targetDir, options.timeout) : { reports: [] };
|
|
202
|
+
totalDuration += phases.reports.reduce((sum, r) => sum + r.durationMs, 0);
|
|
203
|
+
for (const { task, milestone } of candidates) {
|
|
204
|
+
const vResult = await runTaskVerification(task, { cwd: targetDir, timeout: options.timeout, phases });
|
|
205
|
+
totalDuration += vResult.durationMs;
|
|
206
|
+
if (vResult.skipped) {
|
|
207
|
+
skipped++;
|
|
208
|
+
}
|
|
209
|
+
else if (vResult.success) {
|
|
210
|
+
passed++;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
failed++;
|
|
214
|
+
}
|
|
215
|
+
results.push({
|
|
216
|
+
taskId: task.id,
|
|
217
|
+
title: task.title,
|
|
218
|
+
milestoneId: milestone.id,
|
|
219
|
+
milestoneName: milestone.name,
|
|
220
|
+
status: task.status,
|
|
221
|
+
result: vResult,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
return {
|
|
225
|
+
projectName: plan.projectName,
|
|
226
|
+
total: candidates.length,
|
|
227
|
+
passed,
|
|
228
|
+
failed,
|
|
229
|
+
skipped,
|
|
230
|
+
durationMs: totalDuration,
|
|
231
|
+
tasks: results,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { CodeShapeReport } from '../code-shape.js';
|
|
2
|
+
export interface VerificationResult {
|
|
3
|
+
success: boolean;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
command: string;
|
|
6
|
+
stdout: string;
|
|
7
|
+
stderr: string;
|
|
8
|
+
durationMs: number;
|
|
9
|
+
error?: string;
|
|
10
|
+
skipped?: boolean;
|
|
11
|
+
codeShape?: CodeShapeReport;
|
|
12
|
+
/** Results of the configured `verifyPhases`, when any ran. */
|
|
13
|
+
phases?: PhaseResult[];
|
|
14
|
+
}
|
|
15
|
+
export interface PhaseResult {
|
|
16
|
+
name: string;
|
|
17
|
+
command: string;
|
|
18
|
+
success: boolean;
|
|
19
|
+
durationMs: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TaskVerificationResult {
|
|
22
|
+
taskId: string;
|
|
23
|
+
title: string;
|
|
24
|
+
milestoneId: string;
|
|
25
|
+
milestoneName: string;
|
|
26
|
+
status: string;
|
|
27
|
+
result: VerificationResult;
|
|
28
|
+
}
|
|
29
|
+
export interface BatchVerificationResult {
|
|
30
|
+
projectName: string;
|
|
31
|
+
total: number;
|
|
32
|
+
passed: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
skipped: number;
|
|
35
|
+
durationMs: number;
|
|
36
|
+
tasks: TaskVerificationResult[];
|
|
37
|
+
}
|
|
38
|
+
export interface VerifyPhase {
|
|
39
|
+
name: string;
|
|
40
|
+
run: string;
|
|
41
|
+
}
|
|
42
|
+
/** Configured phases, or an error when the config exists but cannot be trusted to say what they are. */
|
|
43
|
+
export interface VerifyPhasesConfig {
|
|
44
|
+
phases: VerifyPhase[];
|
|
45
|
+
error?: string;
|
|
46
|
+
}
|
|
47
|
+
/** Outcome of running the configured phases once; shared by every task in a batch. */
|
|
48
|
+
export interface PhasesOutcome {
|
|
49
|
+
reports: PhaseResult[];
|
|
50
|
+
failure?: VerificationResult;
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/core/verifier.d.ts
CHANGED
|
@@ -1,95 +1,7 @@
|
|
|
1
|
-
import { MasterPlan, MasterPlanTask } from '../scanner/types.js';
|
|
2
|
-
import { type CodeShapeReport } from './code-shape.js';
|
|
3
|
-
export interface VerificationResult {
|
|
4
|
-
success: boolean;
|
|
5
|
-
exitCode: number;
|
|
6
|
-
command: string;
|
|
7
|
-
stdout: string;
|
|
8
|
-
stderr: string;
|
|
9
|
-
durationMs: number;
|
|
10
|
-
error?: string;
|
|
11
|
-
skipped?: boolean;
|
|
12
|
-
codeShape?: CodeShapeReport;
|
|
13
|
-
/** Results of the configured `verifyPhases`, when any ran. */
|
|
14
|
-
phases?: PhaseResult[];
|
|
15
|
-
}
|
|
16
|
-
export interface PhaseResult {
|
|
17
|
-
name: string;
|
|
18
|
-
command: string;
|
|
19
|
-
success: boolean;
|
|
20
|
-
durationMs: number;
|
|
21
|
-
}
|
|
22
|
-
export interface TaskVerificationResult {
|
|
23
|
-
taskId: string;
|
|
24
|
-
title: string;
|
|
25
|
-
milestoneId: string;
|
|
26
|
-
milestoneName: string;
|
|
27
|
-
status: string;
|
|
28
|
-
result: VerificationResult;
|
|
29
|
-
}
|
|
30
|
-
export interface BatchVerificationResult {
|
|
31
|
-
projectName: string;
|
|
32
|
-
total: number;
|
|
33
|
-
passed: number;
|
|
34
|
-
failed: number;
|
|
35
|
-
skipped: number;
|
|
36
|
-
durationMs: number;
|
|
37
|
-
tasks: TaskVerificationResult[];
|
|
38
|
-
}
|
|
39
|
-
export declare function loadMasterPlan(targetDir: string): {
|
|
40
|
-
planPath: string;
|
|
41
|
-
plan: MasterPlan | null;
|
|
42
|
-
error?: string;
|
|
43
|
-
};
|
|
44
1
|
/**
|
|
45
|
-
*
|
|
2
|
+
* Re-exports the modular verifier engine components.
|
|
46
3
|
*/
|
|
47
|
-
export
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
*/
|
|
52
|
-
export declare function applyCodeShape(result: VerificationResult, targetDir: string, task: MasterPlanTask): VerificationResult;
|
|
53
|
-
export interface VerifyPhase {
|
|
54
|
-
name: string;
|
|
55
|
-
run: string;
|
|
56
|
-
}
|
|
57
|
-
/** Configured phases, or an error when the config exists but cannot be trusted to say what they are. */
|
|
58
|
-
export interface VerifyPhasesConfig {
|
|
59
|
-
phases: VerifyPhase[];
|
|
60
|
-
error?: string;
|
|
61
|
-
}
|
|
62
|
-
/** `verifyPhases` from the main checkout's `.nativ/config.json`, never from a worktree's own copy. */
|
|
63
|
-
export declare function loadVerifyPhases(configDir: string): VerifyPhasesConfig;
|
|
64
|
-
/** Outcome of running the configured phases once; shared by every task in a batch. */
|
|
65
|
-
export interface PhasesOutcome {
|
|
66
|
-
reports: PhaseResult[];
|
|
67
|
-
failure?: VerificationResult;
|
|
68
|
-
}
|
|
69
|
-
/** Runs the configured phases in order and stops at the first failure. */
|
|
70
|
-
export declare function runVerifyPhases(cwd: string, configDir: string, timeout?: number): Promise<PhasesOutcome>;
|
|
71
|
-
/**
|
|
72
|
-
* The one verification every path shares: the configured phases in order, stopping at the
|
|
73
|
-
* first failure, then the task's own command, then the code-shape check.
|
|
74
|
-
*/
|
|
75
|
-
export declare function runTaskVerification(task: MasterPlanTask, options: {
|
|
76
|
-
cwd: string;
|
|
77
|
-
configDir?: string;
|
|
78
|
-
timeout?: number;
|
|
79
|
-
phases?: PhasesOutcome;
|
|
80
|
-
}): Promise<VerificationResult>;
|
|
81
|
-
/**
|
|
82
|
-
* Run verification for a single specific task in the plan.
|
|
83
|
-
*/
|
|
84
|
-
export declare function verifyTask(taskId: string, targetDirArg?: string, options?: {
|
|
85
|
-
timeout?: number;
|
|
86
|
-
}): Promise<TaskVerificationResult>;
|
|
87
|
-
/**
|
|
88
|
-
* Verify a batch of tasks (by milestone, all completed tasks, or the active in-progress task).
|
|
89
|
-
*/
|
|
90
|
-
export declare function verifyBatch(targetDirArg?: string, options?: {
|
|
91
|
-
taskId?: string;
|
|
92
|
-
milestoneId?: string;
|
|
93
|
-
all?: boolean;
|
|
94
|
-
timeout?: number;
|
|
95
|
-
}): Promise<BatchVerificationResult>;
|
|
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';
|