@stardustlc/dsh-review 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/LICENSE +9 -0
- package/README.en.md +43 -0
- package/README.md +120 -2
- package/cordis.patch.yml +15 -0
- package/docs/DESIGN.md +76 -0
- package/docs/INTEGRATION.md +68 -0
- package/docs/VALIDATION.md +39 -0
- package/docs/review-policy.example.json +10 -0
- package/lib/check.d.ts +23 -0
- package/lib/check.js +17 -0
- package/lib/cli.d.ts +82 -0
- package/lib/cli.js +59 -0
- package/lib/config.d.ts +33 -0
- package/lib/config.js +69 -0
- package/lib/diff.d.ts +38 -0
- package/lib/diff.js +136 -0
- package/lib/dispatch.d.ts +58 -0
- package/lib/dispatch.js +50 -0
- package/lib/eval.d.ts +71 -0
- package/lib/eval.js +83 -0
- package/lib/exec.d.ts +26 -0
- package/lib/exec.js +82 -0
- package/lib/fingerprint.d.ts +12 -0
- package/lib/fingerprint.js +14 -0
- package/lib/gate.d.ts +81 -0
- package/lib/gate.js +135 -0
- package/lib/github.d.ts +2 -0
- package/lib/github.js +53 -0
- package/lib/index.d.ts +36 -0
- package/lib/index.js +56 -0
- package/lib/ledger.d.ts +140 -0
- package/lib/ledger.js +125 -0
- package/lib/lenses.d.ts +43 -0
- package/lib/lenses.js +68 -0
- package/lib/modes.d.ts +30 -0
- package/lib/modes.js +62 -0
- package/lib/policy.d.ts +54 -0
- package/lib/policy.js +42 -0
- package/lib/preexisting.d.ts +8 -0
- package/lib/preexisting.js +39 -0
- package/lib/rules.d.ts +32 -0
- package/lib/rules.js +101 -0
- package/lib/run.d.ts +36 -0
- package/lib/run.js +277 -0
- package/lib/scope.d.ts +23 -0
- package/lib/scope.js +50 -0
- package/lib/target.d.ts +40 -0
- package/lib/target.js +134 -0
- package/lib/tools.d.ts +28 -0
- package/lib/tools.js +187 -0
- package/lib/verdict.d.ts +22 -0
- package/lib/verdict.js +25 -0
- package/package.json +67 -4
package/lib/tools.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { classifyRisk, modeForRisk, planForMode, requiredApprovals } from './modes.js';
|
|
5
|
+
import { Ledger } from './ledger.js';
|
|
6
|
+
import { captureTarget } from './target.js';
|
|
7
|
+
import { runCheck } from './check.js';
|
|
8
|
+
import { externalDispatcher } from './dispatch.js';
|
|
9
|
+
import { startRun, executeRun, submitResults, assertCurrent, runView } from './run.js';
|
|
10
|
+
import { evaluation } from './eval.js';
|
|
11
|
+
import { buildIssueTriagePrompt } from './lenses.js';
|
|
12
|
+
import { automatedVerdict } from './verdict.js';
|
|
13
|
+
function parameters(spec) {
|
|
14
|
+
const properties = {}, required = [];
|
|
15
|
+
for (const [key, { required: needed, ...schema }] of Object.entries(spec)) {
|
|
16
|
+
properties[key] = schema;
|
|
17
|
+
if (needed)
|
|
18
|
+
required.push(key);
|
|
19
|
+
}
|
|
20
|
+
return { type: 'object', properties, additionalProperties: false, ...(required.length ? { required } : {}) };
|
|
21
|
+
}
|
|
22
|
+
function rootFor(exec) {
|
|
23
|
+
const cwd = exec.agent?.session?.header?.cwd;
|
|
24
|
+
return realpathSync(typeof cwd === 'string' && cwd ? cwd : process.cwd());
|
|
25
|
+
}
|
|
26
|
+
function text(value, name) {
|
|
27
|
+
if (typeof value !== 'string' || !value.trim())
|
|
28
|
+
throw new Error(name + ' must be a non-empty string');
|
|
29
|
+
return value.trim();
|
|
30
|
+
}
|
|
31
|
+
function getRun(ledger, args, root) {
|
|
32
|
+
const run = ledger.snapshot().runs[text(args.runId, 'runId')];
|
|
33
|
+
if (!run || run.target.root !== root)
|
|
34
|
+
throw new Error('Unknown run in this workspace');
|
|
35
|
+
if (args.targetId !== undefined && args.targetId !== run.targetId)
|
|
36
|
+
throw new Error('Wrong targetId');
|
|
37
|
+
return run;
|
|
38
|
+
}
|
|
39
|
+
function modeFor(target, requested) {
|
|
40
|
+
if (requested !== undefined && !['quick', 'standard', 'deep'].includes(String(requested)))
|
|
41
|
+
throw new Error('Invalid review mode');
|
|
42
|
+
const risk = classifyRisk(target.allPaths, target.rules.sensitivePaths);
|
|
43
|
+
return risk.tier === 'high' ? 'deep' : (requested ?? target.rules.mode ?? modeForRisk(risk.tier, target.files.length));
|
|
44
|
+
}
|
|
45
|
+
const targetSpec = {
|
|
46
|
+
base: { type: 'string', description: 'Compare the merge base of this ref with HEAD; omit for working tree.' },
|
|
47
|
+
staged: { type: 'boolean', description: 'Review index snapshots, independently of the working tree.' },
|
|
48
|
+
includeUntracked: { type: 'boolean', description: 'Include untracked files; default true for working-tree reviews.' },
|
|
49
|
+
mode: { type: 'string', enum: ['quick', 'standard', 'deep'], description: 'Sensitive paths always require deep.' },
|
|
50
|
+
};
|
|
51
|
+
const runSpec = { runId: { type: 'string', required: true }, targetId: { type: 'string', required: true } };
|
|
52
|
+
function render(_args, value) {
|
|
53
|
+
const result = value;
|
|
54
|
+
if (result.runId)
|
|
55
|
+
return [{ type: 'text', text: 'Review ' + result.runId + ' · ' + (result.status ?? (result.ok ? 'automated checks passed' : 'blocked')) + '\n' + JSON.stringify(result, null, 2) }];
|
|
56
|
+
return [{ type: 'text', text: JSON.stringify(value, null, 2) }];
|
|
57
|
+
}
|
|
58
|
+
export function buildReviewTools(config, options = {}) {
|
|
59
|
+
function tool(name, description, spec, execute, timeoutMs = 120000) {
|
|
60
|
+
return { name, description, parameters: parameters(spec), output: { schema: { type: 'object', additionalProperties: true }, render }, timeoutMs,
|
|
61
|
+
async execute(raw, context) {
|
|
62
|
+
const exec = (context ?? {});
|
|
63
|
+
exec.signal?.throwIfAborted();
|
|
64
|
+
const root = rootFor(exec), ledger = new Ledger(root, config.stateDir);
|
|
65
|
+
const value = await execute((raw ?? {}), exec, root, ledger);
|
|
66
|
+
// DSH uses lossless JSON: omit absent optional fields, never emit undefined.
|
|
67
|
+
return JSON.parse(JSON.stringify(value));
|
|
68
|
+
} };
|
|
69
|
+
}
|
|
70
|
+
return [
|
|
71
|
+
tool('review_diff', 'Capture an immutable diff and source snapshot, then plan independent review. This is a plan, never an approval.', targetSpec, async (args, exec, root, ledger) => {
|
|
72
|
+
const target = await captureTarget(root, args, config.maxDiffBytes, config.stateDir, exec.signal);
|
|
73
|
+
const risk = classifyRisk(target.allPaths, target.rules.sensitivePaths), mode = modeFor(target, args.mode);
|
|
74
|
+
const record = ledger.snapshot().heads[target.series];
|
|
75
|
+
return { targetId: target.id, head: target.head, base: target.base, kind: target.kind, mode, risk, suggestedHumanApprovals: requiredApprovals(risk.tier),
|
|
76
|
+
files: target.files.map(f => ({ path: f.path, oldPath: f.oldPath, status: f.status, binary: f.binary, added: f.addedLines.length, removed: f.removedLines.length })),
|
|
77
|
+
skipped: target.skipped, rules: target.rules, plan: planForMode(mode), completedRounds: record?.round ?? 0, roundCap: config.roundCap, maxCalls: config.maxCalls,
|
|
78
|
+
advice: 'Fresh-context bug finders, independent verification and two risk classifications. No merge or human approval is granted.' };
|
|
79
|
+
}),
|
|
80
|
+
tool('review_run', 'Run fresh-context finders, two risk classifiers, independent verifiers and deep-mode arbitration. Resume with runId; dryRun returns task envelopes and never passes the automatic gate.', { ...targetSpec, runId: { type: 'string', description: 'Resume this run; omit to start a new snapshot.' }, dryRun: { type: 'boolean' }, abandon: { type: 'boolean', description: 'Explicitly stop this run; no approval survives.' } }, async (args, exec, root, ledger) => {
|
|
81
|
+
if (args.abandon === true) {
|
|
82
|
+
const run = getRun(ledger, args, root);
|
|
83
|
+
if (run.lease)
|
|
84
|
+
throw new Error('Cancel the active tool call first and wait for its workers to stop before abandoning it');
|
|
85
|
+
if (run.status === 'complete')
|
|
86
|
+
throw new Error('Completed reviews cannot be abandoned; changing code invalidates their target');
|
|
87
|
+
ledger.transaction(state => { state.runs[run.id].status = 'cancelled'; state.runs[run.id].errors.push('Explicitly abandoned by caller'); });
|
|
88
|
+
return runView(ledger.snapshot().runs[run.id]);
|
|
89
|
+
}
|
|
90
|
+
const signal = exec.signal ? AbortSignal.any([exec.signal, AbortSignal.timeout(config.runTimeoutMs)]) : AbortSignal.timeout(config.runTimeoutMs);
|
|
91
|
+
const dispatcher = args.dryRun === true ? externalDispatcher('Manual task book: independence cannot be verified, so the automatic gate stays blocked.') : options.dispatcher?.(exec, root, signal) ?? externalDispatcher();
|
|
92
|
+
let run;
|
|
93
|
+
if (args.runId)
|
|
94
|
+
run = getRun(ledger, args, root);
|
|
95
|
+
else {
|
|
96
|
+
const target = await captureTarget(root, args, config.maxDiffBytes, config.stateDir, signal);
|
|
97
|
+
run = startRun(target, modeFor(target, args.mode), config, ledger, dispatcher.kind === 'subagents');
|
|
98
|
+
}
|
|
99
|
+
try {
|
|
100
|
+
const updated = await executeRun(ledger, run.id, config, dispatcher, signal);
|
|
101
|
+
return { ...runView(updated), dispatcher: dispatcher.kind, ...(dispatcher.reason ? { reason: dispatcher.reason } : {}) };
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (ledger.snapshot().runs[run.id].lease)
|
|
105
|
+
throw error;
|
|
106
|
+
ledger.transaction(state => { const r = state.runs[run.id]; if (!['stale', 'complete', 'cancelled'].includes(r.status))
|
|
107
|
+
r.status = signal.aborted ? 'cancelled' : 'incomplete'; r.errors.push(error instanceof Error ? error.message : String(error)); });
|
|
108
|
+
return { ...runView(ledger.snapshot().runs[run.id]), dispatcher: dispatcher.kind };
|
|
109
|
+
}
|
|
110
|
+
}, 3600000),
|
|
111
|
+
tool('review_report', 'Submit identity-bound JSON for pending external tasks and advance to the next stage. This cannot certify independent execution or grant automatic approval.', { ...runSpec, results: { type: 'array', required: true, items: { type: 'object', additionalProperties: true } } }, async (args, exec, root, ledger) => {
|
|
112
|
+
const run = getRun(ledger, args, root);
|
|
113
|
+
if (run.independent)
|
|
114
|
+
throw new Error('Native worker results cannot be overwritten by manual reports');
|
|
115
|
+
if (!Array.isArray(args.results) || !args.results.length)
|
|
116
|
+
throw new Error('results must be a non-empty array');
|
|
117
|
+
await assertCurrent(run, ledger, config, exec.signal);
|
|
118
|
+
submitResults(ledger, run.id, run.targetId, args.results, config);
|
|
119
|
+
const signal = exec.signal ?? new AbortController().signal;
|
|
120
|
+
return runView(await executeRun(ledger, run.id, config, externalDispatcher(), signal));
|
|
121
|
+
}),
|
|
122
|
+
tool('review_check', 'Run evidence through the host controlled Bash tool with the caller permissions and cancellation. Records a receipt tied to this exact target; no direct process escape.', { ...runSpec, command: { type: 'string', required: true }, cwd: { type: 'string', description: 'Workspace-relative directory.' }, timeoutMs: { type: 'integer', minimum: 1000, maximum: 600000 } }, async (args, exec, root, ledger) => {
|
|
123
|
+
const run = getRun(ledger, args, root);
|
|
124
|
+
await assertCurrent(run, ledger, config, exec.signal);
|
|
125
|
+
const result = await runCheck(root, text(args.command, 'command'), args.timeoutMs ?? config.commandTimeoutMs, args.cwd ? path.resolve(root, text(args.cwd, 'cwd')) : root, options.runner, exec);
|
|
126
|
+
// A check may itself edit files. Do not attach it as evidence for an obsolete snapshot.
|
|
127
|
+
await assertCurrent(run, ledger, config, exec.signal);
|
|
128
|
+
const id = randomUUID(), output = result.output.slice(0, 65536), truncated = result.truncated || result.output.length > output.length;
|
|
129
|
+
ledger.transaction(state => { state.receipts[id] = { id, runId: run.id, targetId: run.targetId, command: result.command, cwd: result.cwd, exitCode: result.exitCode, timedOut: result.timedOut, aborted: result.aborted, truncated, output, at: new Date().toISOString() }; });
|
|
130
|
+
return { ...result, output, truncated, ok: result.ok && !truncated, receiptId: id, runId: run.id, targetId: run.targetId };
|
|
131
|
+
}, 610000),
|
|
132
|
+
tool('review_gate', 'Check completed independent review of the current snapshot and unresolved findings. This is only the AI review layer; CI and exact-commit human approvals remain separate blocking requirements.', { runId: { type: 'string' }, ...targetSpec }, async (args, exec, root, ledger) => {
|
|
133
|
+
let run;
|
|
134
|
+
if (args.runId)
|
|
135
|
+
run = getRun(ledger, args, root);
|
|
136
|
+
else {
|
|
137
|
+
const current = await captureTarget(root, args, config.maxDiffBytes, config.stateDir, exec.signal);
|
|
138
|
+
const state = ledger.snapshot(), latest = state.heads[current.series]?.latestRunId;
|
|
139
|
+
if (latest)
|
|
140
|
+
run = state.runs[latest];
|
|
141
|
+
if (run?.targetId !== current.id)
|
|
142
|
+
run = undefined;
|
|
143
|
+
}
|
|
144
|
+
if (!run)
|
|
145
|
+
return { ok: false, exitCode: 1, reasons: ['No review of this exact current snapshot'], boundary: 'AI review only; CI and human approval are separate.' };
|
|
146
|
+
await assertCurrent(run, ledger, config, exec.signal);
|
|
147
|
+
return automatedVerdict(ledger.snapshot(), run.id);
|
|
148
|
+
}),
|
|
149
|
+
tool('review_respond', 'Record a reasoned disposition for a verified finding. fixed requires independent re-verification after the fix; accepted exceptions never count as a human approval.', { ...runSpec, fingerprint: { type: 'string', required: true }, disposition: { type: 'string', required: true, enum: ['fixed', 'wrong', 'intentional', 'wontfix'] }, note: { type: 'string', required: true } }, async (args, exec, root, ledger) => {
|
|
150
|
+
const run = getRun(ledger, args, root), note = text(args.note, 'note'), disposition = args.disposition;
|
|
151
|
+
if (!['fixed', 'wrong', 'intentional', 'wontfix'].includes(disposition) || note.length < 20)
|
|
152
|
+
throw new Error('A valid disposition and a specific reason of at least 20 characters are required');
|
|
153
|
+
await assertCurrent(run, ledger, config, exec.signal);
|
|
154
|
+
const fingerprint = text(args.fingerprint, 'fingerprint');
|
|
155
|
+
ledger.transaction(state => {
|
|
156
|
+
const record = state.heads[run.series], finding = record.posted[fingerprint];
|
|
157
|
+
if (!finding || record.latestRunId !== run.id)
|
|
158
|
+
throw new Error('Finding is not in the latest review series');
|
|
159
|
+
if (disposition === 'fixed' && finding.disposition !== 'fixed')
|
|
160
|
+
throw new Error('A fix must be independently re-reviewed; edit the code and run another review to clear this finding');
|
|
161
|
+
finding.disposition = disposition;
|
|
162
|
+
finding.note = note;
|
|
163
|
+
});
|
|
164
|
+
ledger.event('disposition', { runId: run.id, targetId: run.targetId, fingerprint, disposition, note, actor: 'tool-caller' });
|
|
165
|
+
return { ok: true, fingerprint, disposition, note, humanApprovalGranted: false };
|
|
166
|
+
}),
|
|
167
|
+
tool('review_status', 'Inspect completed, incomplete, cancelled and stale runs, dispositions and measured review noise. Missing measurements stay null.', { head: { type: 'string' } }, async (args, _exec, _root, ledger) => {
|
|
168
|
+
const status = ledger.status(args.head), findings = Object.values(ledger.snapshot().heads).flatMap(h => Object.values(h.posted));
|
|
169
|
+
const lenses = [...new Set(findings.map(f => f.lens ?? 'unknown'))].map(lens => {
|
|
170
|
+
const rows = findings.filter(f => (f.lens ?? 'unknown') === lens), answered = rows.filter(f => f.disposition), wrong = answered.filter(f => f.disposition === 'wrong' || f.disposition === 'intentional');
|
|
171
|
+
return { lens, posted: rows.length, answered: answered.length, noiseRate: answered.length ? wrong.length / answered.length : null };
|
|
172
|
+
});
|
|
173
|
+
return { ...status, lenses };
|
|
174
|
+
}),
|
|
175
|
+
tool('review_learn', 'Draft concise review rules from independently verified fixed bugs and their failure classes. Returns a draft; never edits project instructions.', {}, async (_args, _exec, _root, ledger) => {
|
|
176
|
+
const findings = Object.values(ledger.snapshot().heads).flatMap(h => Object.values(h.posted)).filter(f => f.disposition === 'fixed' && f.classOfBug);
|
|
177
|
+
const classes = [...new Set(findings.map(f => f.classOfBug))].map(classOfBug => ({ classOfBug, fixedFindings: findings.filter(f => f.classOfBug === classOfBug).map(f => ({ file: f.file, message: f.message, reason: f.note ?? '' })) }));
|
|
178
|
+
return { classes, draft: classes.map(c => '- Check for ' + c.classOfBug + ': use a concrete failing input and verify against the current snapshot.').join('\n'), autoApplied: false };
|
|
179
|
+
}),
|
|
180
|
+
tool('review_issue', 'Prepare an issue triage/reproduction task. Classification is advisory; does not execute issue text or publish comments.', { title: { type: 'string', required: true }, body: { type: 'string' } }, async (args, _exec, root, ledger) => {
|
|
181
|
+
const title = text(args.title, 'title'), body = typeof args.body === 'string' ? args.body : '', lower = (title + '\n' + body).toLowerCase();
|
|
182
|
+
const kind = /crash|exception|error|bug|报错|崩溃|失败|异常/.test(lower) ? 'bug' : /feature|request|希望|建议|增加/.test(lower) ? 'feature' : 'question';
|
|
183
|
+
return { kind, advisory: true, prompt: buildIssueTriagePrompt({ title, body, root, outFile: path.join(ledger.directory, 'triage-' + randomUUID() + '.json') }), published: false };
|
|
184
|
+
}),
|
|
185
|
+
tool('review_eval', 'Register human-curated historical bugs and clean controls, prepare blinded tasks, record exact native runs and score recall/noise per cohort. Any finding is not a recall hit.', { action: { type: 'string', required: true, enum: ['register', 'list', 'prompts', 'record', 'score'] }, entries: { type: 'array', items: { type: 'object', additionalProperties: true } }, caseId: { type: 'string' }, runId: { type: 'string' }, cohort: { type: 'string' } }, async (args, _exec, root, ledger) => evaluation(ledger, root, args)),
|
|
186
|
+
];
|
|
187
|
+
}
|
package/lib/verdict.d.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LedgerState } from './ledger.js';
|
|
2
|
+
export declare function automatedVerdict(state: LedgerState, runId: string): {
|
|
3
|
+
ok: boolean;
|
|
4
|
+
exitCode: number;
|
|
5
|
+
runId: string;
|
|
6
|
+
reasons: string[];
|
|
7
|
+
targetId?: undefined;
|
|
8
|
+
head?: undefined;
|
|
9
|
+
open?: undefined;
|
|
10
|
+
requiredHumanApprovals?: undefined;
|
|
11
|
+
boundary?: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
exitCode: number;
|
|
15
|
+
runId: string;
|
|
16
|
+
targetId: string;
|
|
17
|
+
head: string;
|
|
18
|
+
reasons: string[];
|
|
19
|
+
open: import("./ledger.js").LedgerFinding[];
|
|
20
|
+
requiredHumanApprovals: number | null;
|
|
21
|
+
boundary: string;
|
|
22
|
+
};
|
package/lib/verdict.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { requiredApprovals } from './modes.js';
|
|
2
|
+
export function automatedVerdict(state, runId) {
|
|
3
|
+
const run = state.runs[runId], reasons = [];
|
|
4
|
+
if (!run)
|
|
5
|
+
return { ok: false, exitCode: 1, runId, reasons: ['Unknown review run'] };
|
|
6
|
+
const record = state.heads[run.series];
|
|
7
|
+
if (run.status !== 'complete' || run.stage !== 'done')
|
|
8
|
+
reasons.push('Review did not complete: ' + run.status);
|
|
9
|
+
if (!run.independent)
|
|
10
|
+
reasons.push('External task submissions cannot prove independent execution');
|
|
11
|
+
if (!run.risk)
|
|
12
|
+
reasons.push('Missing independent risk classifications');
|
|
13
|
+
if (run.unverified)
|
|
14
|
+
reasons.push('Unverified findings: ' + run.unverified);
|
|
15
|
+
if (run.target.files.some(f => f.binary))
|
|
16
|
+
reasons.push('Binary changes require a separate human/domain review; text review cannot certify their content');
|
|
17
|
+
if (record?.latestRunId !== run.id || record.targetId !== run.targetId)
|
|
18
|
+
reasons.push('This is not the latest review of the target');
|
|
19
|
+
const open = Object.values(record?.posted ?? {}).filter(f => !f.disposition);
|
|
20
|
+
if (open.length)
|
|
21
|
+
reasons.push('Unresolved findings (including published nits/pre-existing): ' + open.length);
|
|
22
|
+
return { ok: reasons.length === 0, exitCode: reasons.length ? 1 : 0, runId, targetId: run.targetId, head: run.head, reasons, open,
|
|
23
|
+
requiredHumanApprovals: run.risk ? Math.min(2, requiredApprovals(run.risk.tier) + (run.risk.extraHuman ? 1 : 0)) : null,
|
|
24
|
+
boundary: 'AI review layer only. Required CI checks and independent human approvals of this exact commit must be enforced separately. No merge approval is granted.' };
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,69 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stardustlc/dsh-review",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "以证据为准的代码审阅插件:审 diff、验证结论、记录噪音,让 agent 先读、人只读结论。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"bin": {
|
|
9
|
+
"dsh-review-gate": "lib/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./lib/index.d.ts",
|
|
14
|
+
"default": "./lib/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"lib",
|
|
21
|
+
"cordis.patch.yml",
|
|
22
|
+
"README.md",
|
|
23
|
+
"README.en.md",
|
|
24
|
+
"docs",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsc -p tsconfig.json",
|
|
29
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
30
|
+
"test": "npm run build && node --test \"test/*.test.mjs\"",
|
|
31
|
+
"test:host": "npm run build && node scripts/host-acceptance.mjs",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
},
|
|
34
|
+
"dsh": {
|
|
35
|
+
"bundle": {
|
|
36
|
+
"patch": "./cordis.patch.yml"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"dsh",
|
|
41
|
+
"dsh-plugin",
|
|
42
|
+
"deepseek-harness",
|
|
43
|
+
"plugin",
|
|
44
|
+
"code-review",
|
|
45
|
+
"pull-request",
|
|
46
|
+
"review",
|
|
47
|
+
"agent"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": "^22.19.0 || >=24.0.0"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^24.19.2",
|
|
58
|
+
"typescript": "^5.9.2"
|
|
59
|
+
},
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/STARDUSTLC666/dsh-review.git"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/STARDUSTLC666/dsh-review#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/STARDUSTLC666/dsh-review/issues"
|
|
67
|
+
},
|
|
68
|
+
"author": "STARDUSTLC666"
|
|
69
|
+
}
|