@zigrivers/mmr 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +98 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/jobs.d.ts +7 -0
- package/dist/commands/jobs.d.ts.map +1 -0
- package/dist/commands/jobs.js +48 -0
- package/dist/commands/jobs.js.map +1 -0
- package/dist/commands/results.d.ts +9 -0
- package/dist/commands/results.d.ts.map +1 -0
- package/dist/commands/results.js +145 -0
- package/dist/commands/results.js.map +1 -0
- package/dist/commands/review.d.ts +18 -0
- package/dist/commands/review.d.ts.map +1 -0
- package/dist/commands/review.js +203 -0
- package/dist/commands/review.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +56 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/defaults.d.ts +12 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +69 -0
- package/dist/config/defaults.js.map +1 -0
- package/dist/config/loader.d.ts +23 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +100 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/config/schema.d.ts +207 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +38 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/auth.d.ts +17 -0
- package/dist/core/auth.d.ts.map +1 -0
- package/dist/core/auth.js +60 -0
- package/dist/core/auth.js.map +1 -0
- package/dist/core/dispatcher.d.ts +15 -0
- package/dist/core/dispatcher.d.ts.map +1 -0
- package/dist/core/dispatcher.js +107 -0
- package/dist/core/dispatcher.js.map +1 -0
- package/dist/core/job-store.d.ts +46 -0
- package/dist/core/job-store.d.ts.map +1 -0
- package/dist/core/job-store.js +141 -0
- package/dist/core/job-store.js.map +1 -0
- package/dist/core/parser.d.ts +16 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +123 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/prompt.d.ts +26 -0
- package/dist/core/prompt.d.ts.map +1 -0
- package/dist/core/prompt.js +53 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/reconciler.d.ts +17 -0
- package/dist/core/reconciler.d.ts.map +1 -0
- package/dist/core/reconciler.js +84 -0
- package/dist/core/reconciler.js.map +1 -0
- package/dist/formatters/json.d.ts +3 -0
- package/dist/formatters/json.d.ts.map +1 -0
- package/dist/formatters/json.js +4 -0
- package/dist/formatters/json.js.map +1 -0
- package/dist/formatters/markdown.d.ts +3 -0
- package/dist/formatters/markdown.d.ts.map +1 -0
- package/dist/formatters/markdown.js +36 -0
- package/dist/formatters/markdown.js.map +1 -0
- package/dist/formatters/text.d.ts +3 -0
- package/dist/formatters/text.d.ts.map +1 -0
- package/dist/formatters/text.js +31 -0
- package/dist/formatters/text.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +7 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
- package/templates/core-prompt.md +33 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const TERMINAL_STATUSES = new Set([
|
|
5
|
+
'completed',
|
|
6
|
+
'timeout',
|
|
7
|
+
'failed',
|
|
8
|
+
'auth_failed',
|
|
9
|
+
'skipped',
|
|
10
|
+
]);
|
|
11
|
+
/** Check whether a channel status represents a terminal (done) state */
|
|
12
|
+
export function isChannelComplete(status) {
|
|
13
|
+
return TERMINAL_STATUSES.has(status);
|
|
14
|
+
}
|
|
15
|
+
/** Spawn a background process for a review channel and monitor it */
|
|
16
|
+
export async function dispatchChannel(store, jobId, channelName, opts) {
|
|
17
|
+
const jobDir = store.getJobDir(jobId);
|
|
18
|
+
const channelsDir = path.join(jobDir, 'channels');
|
|
19
|
+
// Split multi-word commands (e.g. "claude -p" → ["claude", "-p"])
|
|
20
|
+
const [cmd, ...cmdArgs] = opts.command.split(/\s+/);
|
|
21
|
+
const args = [...cmdArgs, ...opts.flags];
|
|
22
|
+
// Update channel to running
|
|
23
|
+
store.updateChannel(jobId, channelName, {
|
|
24
|
+
status: 'running',
|
|
25
|
+
started_at: new Date().toISOString(),
|
|
26
|
+
});
|
|
27
|
+
// Pipe prompt via stdin to avoid E2BIG on large diffs
|
|
28
|
+
const proc = spawn(cmd, args, {
|
|
29
|
+
detached: true,
|
|
30
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
+
env: { ...process.env, ...opts.env },
|
|
32
|
+
});
|
|
33
|
+
// Write prompt to stdin
|
|
34
|
+
proc.stdin.write(opts.prompt);
|
|
35
|
+
proc.stdin.end();
|
|
36
|
+
// Write PID file
|
|
37
|
+
const pidFile = path.join(channelsDir, `${channelName}.pid`);
|
|
38
|
+
fs.writeFileSync(pidFile, String(proc.pid));
|
|
39
|
+
// Collect stdout and stderr
|
|
40
|
+
let stdout = '';
|
|
41
|
+
let stderr = '';
|
|
42
|
+
proc.stdout.on('data', (chunk) => {
|
|
43
|
+
stdout += chunk.toString();
|
|
44
|
+
});
|
|
45
|
+
proc.stderr.on('data', (chunk) => {
|
|
46
|
+
stderr += chunk.toString();
|
|
47
|
+
});
|
|
48
|
+
// Set up timeout
|
|
49
|
+
const timeoutMs = opts.timeout * 1000;
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
try {
|
|
52
|
+
// Kill the process group (negative PID kills the group)
|
|
53
|
+
if (proc.pid) {
|
|
54
|
+
process.kill(-proc.pid, 'SIGKILL');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Process may have already exited
|
|
59
|
+
}
|
|
60
|
+
const completedAt = new Date().toISOString();
|
|
61
|
+
store.updateChannel(jobId, channelName, {
|
|
62
|
+
status: 'timeout',
|
|
63
|
+
completed_at: completedAt,
|
|
64
|
+
});
|
|
65
|
+
if (stderr) {
|
|
66
|
+
store.saveChannelLog(jobId, channelName, stderr);
|
|
67
|
+
}
|
|
68
|
+
}, timeoutMs);
|
|
69
|
+
// Handle process close
|
|
70
|
+
proc.on('close', (code) => {
|
|
71
|
+
clearTimeout(timer);
|
|
72
|
+
const completedAt = new Date().toISOString();
|
|
73
|
+
const meta = store.loadJob(jobId);
|
|
74
|
+
// If already marked as timeout, don't overwrite
|
|
75
|
+
if (meta.channels[channelName]?.status === 'timeout') {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (code === 0 && stdout) {
|
|
79
|
+
// Always save raw stdout — parser.ts handles format quirks
|
|
80
|
+
store.saveChannelOutput(jobId, channelName, stdout);
|
|
81
|
+
store.updateChannel(jobId, channelName, {
|
|
82
|
+
status: 'completed',
|
|
83
|
+
completed_at: completedAt,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
const errorMsg = stderr || `Process exited with code ${code}`;
|
|
88
|
+
store.saveChannelLog(jobId, channelName, errorMsg);
|
|
89
|
+
store.updateChannel(jobId, channelName, {
|
|
90
|
+
status: 'failed',
|
|
91
|
+
completed_at: completedAt,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Handle spawn errors
|
|
96
|
+
proc.on('error', (err) => {
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
store.updateChannel(jobId, channelName, {
|
|
99
|
+
status: 'failed',
|
|
100
|
+
completed_at: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
store.saveChannelLog(jobId, channelName, err.message);
|
|
103
|
+
});
|
|
104
|
+
// Unref so parent process can exit
|
|
105
|
+
proc.unref();
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=dispatcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { JobMetadata, ChannelJobEntry, Severity, OutputFormat } from '../types.js';
|
|
2
|
+
import type { ReconciledResults } from '../types.js';
|
|
3
|
+
export interface CreateJobOpts {
|
|
4
|
+
fix_threshold: Severity;
|
|
5
|
+
format: OutputFormat;
|
|
6
|
+
channels: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare class JobStore {
|
|
9
|
+
private readonly jobsDir;
|
|
10
|
+
constructor(jobsDir: string);
|
|
11
|
+
/** Generate a unique job ID: mmr-{6 hex chars} */
|
|
12
|
+
private generateId;
|
|
13
|
+
/** Full path to a job directory */
|
|
14
|
+
getJobDir(jobId: string): string;
|
|
15
|
+
/** Create a new job with its directory structure and initial metadata */
|
|
16
|
+
createJob(opts: CreateJobOpts): JobMetadata;
|
|
17
|
+
/** Write job metadata to job.json */
|
|
18
|
+
saveJob(jobId: string, metadata: JobMetadata): void;
|
|
19
|
+
/** Read job metadata from job.json */
|
|
20
|
+
loadJob(jobId: string): JobMetadata;
|
|
21
|
+
/** Save the assembled prompt text */
|
|
22
|
+
savePrompt(jobId: string, prompt: string): void;
|
|
23
|
+
/** Load the assembled prompt text */
|
|
24
|
+
loadPrompt(jobId: string): string;
|
|
25
|
+
/** Save the diff content */
|
|
26
|
+
saveDiff(jobId: string, diff: string): void;
|
|
27
|
+
/** Load the diff content */
|
|
28
|
+
loadDiff(jobId: string): string;
|
|
29
|
+
/** Save parsed channel output as JSON */
|
|
30
|
+
saveChannelOutput(jobId: string, channel: string, output: unknown): void;
|
|
31
|
+
/** Load raw channel output string */
|
|
32
|
+
loadChannelOutput(jobId: string, channel: string): string;
|
|
33
|
+
/** Save raw channel log output */
|
|
34
|
+
saveChannelLog(jobId: string, channel: string, log: string): void;
|
|
35
|
+
/** Update a channel entry and auto-update overall job status */
|
|
36
|
+
updateChannel(jobId: string, channel: string, update: Partial<ChannelJobEntry>): void;
|
|
37
|
+
/** Save reconciled results */
|
|
38
|
+
saveResults(jobId: string, results: ReconciledResults): void;
|
|
39
|
+
/** List all jobs sorted by creation time (newest first) */
|
|
40
|
+
listJobs(): JobMetadata[];
|
|
41
|
+
/** Delete jobs older than retentionDays, return count of pruned jobs */
|
|
42
|
+
pruneJobs(retentionDays: number): number;
|
|
43
|
+
/** Derive overall job status from channel statuses */
|
|
44
|
+
private deriveJobStatus;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=job-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store.d.ts","sourceRoot":"","sources":["../../src/core/job-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,eAAe,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACvF,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpD,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,QAAQ,CAAA;IACvB,MAAM,EAAE,YAAY,CAAA;IACpB,QAAQ,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,OAAO,EAAE,MAAM;IAK3B,kDAAkD;IAClD,OAAO,CAAC,UAAU;IAKlB,mCAAmC;IACnC,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIhC,yEAAyE;IACzE,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,WAAW;IAuB3C,qCAAqC;IACrC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,GAAG,IAAI;IAKnD,sCAAsC;IACtC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW;IAMnC,qCAAqC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAI/C,qCAAqC;IACrC,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAIjC,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI;IAI3C,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAI/B,yCAAyC;IACzC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,IAAI;IAKxE,qCAAqC;IACrC,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM;IAKzD,kCAAkC;IAClC,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAKjE,gEAAgE;IAChE,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,IAAI;IAOrF,8BAA8B;IAC9B,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAO5D,2DAA2D;IAC3D,QAAQ,IAAI,WAAW,EAAE;IAsBzB,wEAAwE;IACxE,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM;IAgBxC,sDAAsD;IACtD,OAAO,CAAC,eAAe;CAUxB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
export class JobStore {
|
|
5
|
+
jobsDir;
|
|
6
|
+
constructor(jobsDir) {
|
|
7
|
+
this.jobsDir = jobsDir;
|
|
8
|
+
fs.mkdirSync(jobsDir, { recursive: true });
|
|
9
|
+
}
|
|
10
|
+
/** Generate a unique job ID: mmr-{6 hex chars} */
|
|
11
|
+
generateId() {
|
|
12
|
+
const hex = crypto.randomBytes(3).toString('hex');
|
|
13
|
+
return `mmr-${hex}`;
|
|
14
|
+
}
|
|
15
|
+
/** Full path to a job directory */
|
|
16
|
+
getJobDir(jobId) {
|
|
17
|
+
return path.join(this.jobsDir, jobId);
|
|
18
|
+
}
|
|
19
|
+
/** Create a new job with its directory structure and initial metadata */
|
|
20
|
+
createJob(opts) {
|
|
21
|
+
const jobId = this.generateId();
|
|
22
|
+
const jobDir = this.getJobDir(jobId);
|
|
23
|
+
fs.mkdirSync(path.join(jobDir, 'channels'), { recursive: true });
|
|
24
|
+
const channels = {};
|
|
25
|
+
for (const ch of opts.channels) {
|
|
26
|
+
channels[ch] = { status: 'dispatched', auth: 'ok' };
|
|
27
|
+
}
|
|
28
|
+
const metadata = {
|
|
29
|
+
job_id: jobId,
|
|
30
|
+
status: 'dispatched',
|
|
31
|
+
fix_threshold: opts.fix_threshold,
|
|
32
|
+
format: opts.format,
|
|
33
|
+
created_at: new Date().toISOString(),
|
|
34
|
+
channels,
|
|
35
|
+
};
|
|
36
|
+
this.saveJob(jobId, metadata);
|
|
37
|
+
return metadata;
|
|
38
|
+
}
|
|
39
|
+
/** Write job metadata to job.json */
|
|
40
|
+
saveJob(jobId, metadata) {
|
|
41
|
+
const jobDir = this.getJobDir(jobId);
|
|
42
|
+
fs.writeFileSync(path.join(jobDir, 'job.json'), JSON.stringify(metadata, null, 2));
|
|
43
|
+
}
|
|
44
|
+
/** Read job metadata from job.json */
|
|
45
|
+
loadJob(jobId) {
|
|
46
|
+
const jobDir = this.getJobDir(jobId);
|
|
47
|
+
const raw = fs.readFileSync(path.join(jobDir, 'job.json'), 'utf-8');
|
|
48
|
+
return JSON.parse(raw);
|
|
49
|
+
}
|
|
50
|
+
/** Save the assembled prompt text */
|
|
51
|
+
savePrompt(jobId, prompt) {
|
|
52
|
+
fs.writeFileSync(path.join(this.getJobDir(jobId), 'prompt.txt'), prompt);
|
|
53
|
+
}
|
|
54
|
+
/** Load the assembled prompt text */
|
|
55
|
+
loadPrompt(jobId) {
|
|
56
|
+
return fs.readFileSync(path.join(this.getJobDir(jobId), 'prompt.txt'), 'utf-8');
|
|
57
|
+
}
|
|
58
|
+
/** Save the diff content */
|
|
59
|
+
saveDiff(jobId, diff) {
|
|
60
|
+
fs.writeFileSync(path.join(this.getJobDir(jobId), 'diff.patch'), diff);
|
|
61
|
+
}
|
|
62
|
+
/** Load the diff content */
|
|
63
|
+
loadDiff(jobId) {
|
|
64
|
+
return fs.readFileSync(path.join(this.getJobDir(jobId), 'diff.patch'), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
/** Save parsed channel output as JSON */
|
|
67
|
+
saveChannelOutput(jobId, channel, output) {
|
|
68
|
+
const filePath = path.join(this.getJobDir(jobId), 'channels', `${channel}.json`);
|
|
69
|
+
fs.writeFileSync(filePath, JSON.stringify(output, null, 2));
|
|
70
|
+
}
|
|
71
|
+
/** Load raw channel output string */
|
|
72
|
+
loadChannelOutput(jobId, channel) {
|
|
73
|
+
const filePath = path.join(this.getJobDir(jobId), 'channels', `${channel}.json`);
|
|
74
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
/** Save raw channel log output */
|
|
77
|
+
saveChannelLog(jobId, channel, log) {
|
|
78
|
+
const filePath = path.join(this.getJobDir(jobId), 'channels', `${channel}.log`);
|
|
79
|
+
fs.writeFileSync(filePath, log);
|
|
80
|
+
}
|
|
81
|
+
/** Update a channel entry and auto-update overall job status */
|
|
82
|
+
updateChannel(jobId, channel, update) {
|
|
83
|
+
const metadata = this.loadJob(jobId);
|
|
84
|
+
metadata.channels[channel] = { ...metadata.channels[channel], ...update };
|
|
85
|
+
metadata.status = this.deriveJobStatus(metadata.channels);
|
|
86
|
+
this.saveJob(jobId, metadata);
|
|
87
|
+
}
|
|
88
|
+
/** Save reconciled results */
|
|
89
|
+
saveResults(jobId, results) {
|
|
90
|
+
fs.writeFileSync(path.join(this.getJobDir(jobId), 'results.json'), JSON.stringify(results, null, 2));
|
|
91
|
+
}
|
|
92
|
+
/** List all jobs sorted by creation time (newest first) */
|
|
93
|
+
listJobs() {
|
|
94
|
+
if (!fs.existsSync(this.jobsDir))
|
|
95
|
+
return [];
|
|
96
|
+
const entries = fs.readdirSync(this.jobsDir, { withFileTypes: true });
|
|
97
|
+
const jobs = [];
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
if (!entry.isDirectory() || !entry.name.startsWith('mmr-'))
|
|
100
|
+
continue;
|
|
101
|
+
const jobJsonPath = path.join(this.jobsDir, entry.name, 'job.json');
|
|
102
|
+
if (!fs.existsSync(jobJsonPath))
|
|
103
|
+
continue;
|
|
104
|
+
try {
|
|
105
|
+
const raw = fs.readFileSync(jobJsonPath, 'utf-8');
|
|
106
|
+
jobs.push(JSON.parse(raw));
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
// Skip malformed job directories
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
jobs.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
|
|
113
|
+
return jobs;
|
|
114
|
+
}
|
|
115
|
+
/** Delete jobs older than retentionDays, return count of pruned jobs */
|
|
116
|
+
pruneJobs(retentionDays) {
|
|
117
|
+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
118
|
+
const jobs = this.listJobs();
|
|
119
|
+
let pruned = 0;
|
|
120
|
+
for (const job of jobs) {
|
|
121
|
+
if (new Date(job.created_at).getTime() < cutoff) {
|
|
122
|
+
const jobDir = this.getJobDir(job.job_id);
|
|
123
|
+
fs.rmSync(jobDir, { recursive: true });
|
|
124
|
+
pruned++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return pruned;
|
|
128
|
+
}
|
|
129
|
+
/** Derive overall job status from channel statuses */
|
|
130
|
+
deriveJobStatus(channels) {
|
|
131
|
+
const statuses = Object.values(channels).map((ch) => ch.status);
|
|
132
|
+
const allTerminal = statuses.every((s) => ['completed', 'failed', 'timeout', 'auth_failed', 'skipped'].includes(s));
|
|
133
|
+
if (allTerminal)
|
|
134
|
+
return 'completed';
|
|
135
|
+
const anyRunning = statuses.some((s) => s === 'running' || s === 'completed');
|
|
136
|
+
if (anyRunning)
|
|
137
|
+
return 'running';
|
|
138
|
+
return 'dispatched';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=job-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-store.js","sourceRoot":"","sources":["../../src/core/job-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Finding } from '../types.js';
|
|
2
|
+
export interface ParsedOutput {
|
|
3
|
+
approved: boolean;
|
|
4
|
+
findings: Finding[];
|
|
5
|
+
summary: string;
|
|
6
|
+
}
|
|
7
|
+
export type Parser = (raw: string) => ParsedOutput;
|
|
8
|
+
/**
|
|
9
|
+
* Returns a parser function by name. Falls back to default if name is unknown.
|
|
10
|
+
*/
|
|
11
|
+
export declare function getParser(name: string): Parser;
|
|
12
|
+
/**
|
|
13
|
+
* Wraps getParser in try/catch, returns error finding on parse failure.
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseChannelOutput(raw: string, parserName: string): ParsedOutput;
|
|
16
|
+
//# sourceMappingURL=parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,MAAM,MAAM,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,YAAY,CAAA;AAsGlD;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,YAAY,CAmBhF"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remove ```json and ``` markdown fence markers from text.
|
|
3
|
+
*/
|
|
4
|
+
function stripMarkdownFences(text) {
|
|
5
|
+
return text.replace(/^```(?:json)?\s*\n?/gm, '').replace(/\n?```\s*$/gm, '');
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Remove trailing commas before `}` and `]`.
|
|
9
|
+
*/
|
|
10
|
+
function fixTrailingCommas(text) {
|
|
11
|
+
return text.replace(/,\s*([}\]])/g, '$1');
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Find first `{`, count brace depth, extract to matching `}`.
|
|
15
|
+
*/
|
|
16
|
+
function extractJson(text) {
|
|
17
|
+
const start = text.indexOf('{');
|
|
18
|
+
if (start === -1)
|
|
19
|
+
throw new Error('No JSON object found in output');
|
|
20
|
+
let depth = 0;
|
|
21
|
+
for (let i = start; i < text.length; i++) {
|
|
22
|
+
if (text[i] === '{')
|
|
23
|
+
depth++;
|
|
24
|
+
else if (text[i] === '}')
|
|
25
|
+
depth--;
|
|
26
|
+
if (depth === 0) {
|
|
27
|
+
return text.slice(start, i + 1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error('Unbalanced braces in JSON output');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Default parser: strips markdown fences, extracts JSON from surrounding text,
|
|
34
|
+
* fixes trailing commas, then JSON.parse.
|
|
35
|
+
*/
|
|
36
|
+
function validateParsedOutput(obj) {
|
|
37
|
+
if (typeof obj !== 'object' || obj === null) {
|
|
38
|
+
throw new Error('Parsed output is not an object');
|
|
39
|
+
}
|
|
40
|
+
const record = obj;
|
|
41
|
+
return {
|
|
42
|
+
approved: typeof record.approved === 'boolean' ? record.approved : false,
|
|
43
|
+
findings: Array.isArray(record.findings) ? record.findings.map(validateFinding) : [],
|
|
44
|
+
summary: typeof record.summary === 'string' ? record.summary : '',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function validateFinding(f) {
|
|
48
|
+
if (typeof f !== 'object' || f === null) {
|
|
49
|
+
return { severity: 'P2', location: 'unknown', description: 'Malformed finding', suggestion: '' };
|
|
50
|
+
}
|
|
51
|
+
const record = f;
|
|
52
|
+
return {
|
|
53
|
+
severity: (['P0', 'P1', 'P2', 'P3'].includes(record.severity)
|
|
54
|
+
? record.severity : 'P2'),
|
|
55
|
+
location: typeof record.location === 'string' ? record.location : 'unknown',
|
|
56
|
+
description: typeof record.description === 'string' ? record.description : String(record.description ?? ''),
|
|
57
|
+
suggestion: typeof record.suggestion === 'string' ? record.suggestion : '',
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function defaultParser(raw) {
|
|
61
|
+
let text = stripMarkdownFences(raw);
|
|
62
|
+
text = extractJson(text);
|
|
63
|
+
text = fixTrailingCommas(text);
|
|
64
|
+
return validateParsedOutput(JSON.parse(text));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Gemini parser: tries to unwrap `{ "response": "..." }` wrapper,
|
|
68
|
+
* then delegates to defaultParser.
|
|
69
|
+
*/
|
|
70
|
+
function geminiParser(raw) {
|
|
71
|
+
// First try to parse the raw text as JSON to check for wrapper
|
|
72
|
+
let text = stripMarkdownFences(raw);
|
|
73
|
+
text = extractJson(text);
|
|
74
|
+
text = fixTrailingCommas(text);
|
|
75
|
+
try {
|
|
76
|
+
const outer = JSON.parse(text);
|
|
77
|
+
if (typeof outer.response === 'string') {
|
|
78
|
+
// Unwrap the response field and parse it with the default parser
|
|
79
|
+
return defaultParser(outer.response);
|
|
80
|
+
}
|
|
81
|
+
// No wrapper — treat as direct ParsedOutput
|
|
82
|
+
return outer;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Fall back to default parser on the original raw input
|
|
86
|
+
return defaultParser(raw);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const parsers = {
|
|
90
|
+
default: defaultParser,
|
|
91
|
+
gemini: geminiParser,
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Returns a parser function by name. Falls back to default if name is unknown.
|
|
95
|
+
*/
|
|
96
|
+
export function getParser(name) {
|
|
97
|
+
return parsers[name] ?? parsers['default'];
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Wraps getParser in try/catch, returns error finding on parse failure.
|
|
101
|
+
*/
|
|
102
|
+
export function parseChannelOutput(raw, parserName) {
|
|
103
|
+
try {
|
|
104
|
+
const parser = getParser(parserName);
|
|
105
|
+
return parser(raw);
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
109
|
+
return {
|
|
110
|
+
approved: false,
|
|
111
|
+
findings: [
|
|
112
|
+
{
|
|
113
|
+
severity: 'P1',
|
|
114
|
+
location: 'output-parser',
|
|
115
|
+
description: `Failed to parse channel output: ${message}`,
|
|
116
|
+
suggestion: 'Check the raw output for unexpected format changes.',
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
summary: 'Output parsing failed.',
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/core/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AssemblePromptOptions {
|
|
2
|
+
/** The unified diff to review */
|
|
3
|
+
diff: string;
|
|
4
|
+
/** Project-specific review criteria lines */
|
|
5
|
+
reviewCriteria?: string[];
|
|
6
|
+
/** Template-level criteria (e.g. from methodology presets) */
|
|
7
|
+
templateCriteria?: string[];
|
|
8
|
+
/** Free-text focus areas for this review */
|
|
9
|
+
focus?: string;
|
|
10
|
+
/** Channel prompt wrapper with {{prompt}} placeholder */
|
|
11
|
+
promptWrapper?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Assemble the full review prompt from layered components.
|
|
15
|
+
*
|
|
16
|
+
* Layers (in order):
|
|
17
|
+
* 1. Core prompt template (severity defs, output format)
|
|
18
|
+
* 2. Project review criteria (if provided)
|
|
19
|
+
* 2b. Template criteria (if provided)
|
|
20
|
+
* 3. Focus areas (if provided)
|
|
21
|
+
* 4. The diff (always last)
|
|
22
|
+
*
|
|
23
|
+
* After assembly, applies the optional prompt wrapper.
|
|
24
|
+
*/
|
|
25
|
+
export declare function assemblePrompt(options: AssemblePromptOptions): string;
|
|
26
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AAgBA,MAAM,WAAW,qBAAqB;IACpC,iCAAiC;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,EAAE,CAAA;IACzB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,4CAA4C;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,yDAAyD;IACzD,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,MAAM,CAwCrE"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
5
|
+
const TEMPLATE_PATH = resolve(__dirname, '../../templates/core-prompt.md');
|
|
6
|
+
let cachedTemplate;
|
|
7
|
+
function loadTemplate() {
|
|
8
|
+
if (cachedTemplate === undefined) {
|
|
9
|
+
cachedTemplate = readFileSync(TEMPLATE_PATH, 'utf-8');
|
|
10
|
+
}
|
|
11
|
+
return cachedTemplate;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Assemble the full review prompt from layered components.
|
|
15
|
+
*
|
|
16
|
+
* Layers (in order):
|
|
17
|
+
* 1. Core prompt template (severity defs, output format)
|
|
18
|
+
* 2. Project review criteria (if provided)
|
|
19
|
+
* 2b. Template criteria (if provided)
|
|
20
|
+
* 3. Focus areas (if provided)
|
|
21
|
+
* 4. The diff (always last)
|
|
22
|
+
*
|
|
23
|
+
* After assembly, applies the optional prompt wrapper.
|
|
24
|
+
*/
|
|
25
|
+
export function assemblePrompt(options) {
|
|
26
|
+
const { diff, reviewCriteria, templateCriteria, focus, promptWrapper } = options;
|
|
27
|
+
const layers = [];
|
|
28
|
+
// Layer 1: Core prompt (always)
|
|
29
|
+
layers.push(loadTemplate());
|
|
30
|
+
// Layer 2: Project review criteria
|
|
31
|
+
if (reviewCriteria && reviewCriteria.length > 0) {
|
|
32
|
+
layers.push('## Project Review Criteria\n' +
|
|
33
|
+
reviewCriteria.map((c) => `- ${c}`).join('\n'));
|
|
34
|
+
}
|
|
35
|
+
// Layer 2b: Template criteria
|
|
36
|
+
if (templateCriteria && templateCriteria.length > 0) {
|
|
37
|
+
layers.push('## Template Criteria\n' +
|
|
38
|
+
templateCriteria.map((c) => `- ${c}`).join('\n'));
|
|
39
|
+
}
|
|
40
|
+
// Layer 3: Focus areas
|
|
41
|
+
if (focus) {
|
|
42
|
+
layers.push(`## Focus Areas\n${focus}`);
|
|
43
|
+
}
|
|
44
|
+
// Layer 4: The diff (always last)
|
|
45
|
+
layers.push(`## Diff\n\`\`\`diff\n${diff}\n\`\`\``);
|
|
46
|
+
let assembled = layers.join('\n\n');
|
|
47
|
+
// Apply prompt wrapper if provided
|
|
48
|
+
if (promptWrapper) {
|
|
49
|
+
assembled = promptWrapper.replace('{{prompt}}', assembled);
|
|
50
|
+
}
|
|
51
|
+
return assembled;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../../src/core/prompt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AACzD,MAAM,aAAa,GAAG,OAAO,CAAC,SAAS,EAAE,gCAAgC,CAAC,CAAA;AAE1E,IAAI,cAAkC,CAAA;AAEtC,SAAS,YAAY;IACnB,IAAI,cAAc,KAAK,SAAS,EAAE,CAAC;QACjC,cAAc,GAAG,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAA;IACvD,CAAC;IACD,OAAO,cAAc,CAAA;AACvB,CAAC;AAeD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,gBAAgB,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,OAAO,CAAA;IAEhF,MAAM,MAAM,GAAa,EAAE,CAAA;IAE3B,gCAAgC;IAChC,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,CAAA;IAE3B,mCAAmC;IACnC,IAAI,cAAc,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChD,MAAM,CAAC,IAAI,CACT,8BAA8B;YAC5B,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACjD,CAAA;IACH,CAAC;IAED,8BAA8B;IAC9B,IAAI,gBAAgB,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,CAAC,IAAI,CACT,wBAAwB;YACtB,gBAAgB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CACnD,CAAA;IACH,CAAC;IAED,uBAAuB;IACvB,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,CAAC,IAAI,CAAC,mBAAmB,KAAK,EAAE,CAAC,CAAA;IACzC,CAAC;IAED,kCAAkC;IAClC,MAAM,CAAC,IAAI,CAAC,wBAAwB,IAAI,UAAU,CAAC,CAAA;IAEnD,IAAI,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAEnC,mCAAmC;IACnC,IAAI,aAAa,EAAE,CAAC;QAClB,SAAS,GAAG,aAAa,CAAC,OAAO,CAAC,YAAY,EAAE,SAAS,CAAC,CAAA;IAC5D,CAAC;IAED,OAAO,SAAS,CAAA;AAClB,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Finding, ReconciledFinding, Severity } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reconcile findings from multiple channels into a unified list with
|
|
4
|
+
* consensus scoring.
|
|
5
|
+
*
|
|
6
|
+
* 1. Flatten all findings with source attribution
|
|
7
|
+
* 2. Group by normalized location
|
|
8
|
+
* 3. For each group, determine agreement, confidence, and effective severity
|
|
9
|
+
* 4. Sort by severity (P0 first)
|
|
10
|
+
*/
|
|
11
|
+
export declare function reconcile(channelFindings: Record<string, Finding[]>): ReconciledFinding[];
|
|
12
|
+
/**
|
|
13
|
+
* Evaluate the quality gate: passes if no finding has severity at or above
|
|
14
|
+
* the threshold (i.e., no finding with SEVERITY_ORDER <= threshold order).
|
|
15
|
+
*/
|
|
16
|
+
export declare function evaluateGate(findings: ReconciledFinding[], threshold: Severity): boolean;
|
|
17
|
+
//# sourceMappingURL=reconciler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciler.d.ts","sourceRoot":"","sources":["../../src/core/reconciler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAyB,MAAM,aAAa,CAAA;AAe9F;;;;;;;;GAQG;AACH,wBAAgB,SAAS,CAAC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC,GAAG,iBAAiB,EAAE,CAgEzF;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,iBAAiB,EAAE,EAAE,SAAS,EAAE,QAAQ,GAAG,OAAO,CAGxF"}
|