@astrosheep/keiyaku 2.9.5 → 2.9.6
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/build/.tsbuildinfo +1 -1
- package/build/agents/harness/outcome.js +10 -4
- package/build/agents/harness/projection.js +8 -7
- package/build/agents/harness/router.js +2 -1
- package/build/agents/providers/claude-agent-sdk/adapter.js +13 -1
- package/build/agents/providers/claude-agent-sdk/registration.js +2 -1
- package/build/agents/providers/codex-app-server/adapter.js +11 -10
- package/build/agents/providers/codex-app-server/registration.js +2 -1
- package/build/agents/providers/opencode-sdk/adapter.js +7 -0
- package/build/agents/providers/opencode-sdk/registration.js +2 -1
- package/build/agents/providers/pi/adapter.js +10 -0
- package/build/agents/providers/pi/registration.js +3 -1
- package/build/cli/completion.js +9 -2
- package/build/cli/flags.js +3 -2
- package/build/cli/render/errors.js +18 -1
- package/build/cli/render/format.js +5 -2
- package/build/core/audit/candidate.js +48 -0
- package/build/core/audit/coordinates.js +180 -0
- package/build/core/audit/evidence.js +158 -0
- package/build/core/audit/facade.js +42 -0
- package/build/core/audit/readiness.js +4 -0
- package/build/core/audit/report.js +38 -0
- package/build/core/audit.js +1 -650
- package/build/core/task/query.js +65 -23
- package/build/generated/version.js +2 -2
- package/package.json +1 -1
- package/skills/keiyaku-workflow/SKILL.md +6 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { FlowError, asMessage } from "../../flow-error.js";
|
|
2
|
+
import { scopePathEvidence } from "../scope.js";
|
|
3
|
+
import { verificationExecutor, resolveVerificationCommands, runVerificationCommand } from "../settlement/verification.js";
|
|
4
|
+
function importLine(line) {
|
|
5
|
+
return /^(?:import\b|export\s+(?:\*|\{)|(?:const|let|var)\s+[^=]+\s*=\s*require\s*\(|[^=]+\brequire\s*\(|[^=]+\bimport\s*\()/.test(line.content.trim());
|
|
6
|
+
}
|
|
7
|
+
function identifierRename(before, after) {
|
|
8
|
+
const left = before.match(/[A-Za-z_$][\w$]*/g) ?? [];
|
|
9
|
+
const right = after.match(/[A-Za-z_$][\w$]*/g) ?? [];
|
|
10
|
+
if (left.length !== right.length)
|
|
11
|
+
return null;
|
|
12
|
+
const differences = left.flatMap((value, index) => value === right[index] ? [] : [{ from: value, to: right[index] }]);
|
|
13
|
+
if (differences.length !== 1)
|
|
14
|
+
return null;
|
|
15
|
+
const rename = differences[0];
|
|
16
|
+
const expression = new RegExp(`\\b${rename.from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "g");
|
|
17
|
+
return before.replace(expression, rename.to) === after ? rename : null;
|
|
18
|
+
}
|
|
19
|
+
function explainFile(file, matcher) {
|
|
20
|
+
const explanations = [];
|
|
21
|
+
const explained = new Set();
|
|
22
|
+
if (file.status === "R" && file.oldPath)
|
|
23
|
+
explanations.push({ kind: "file-rename", from: file.oldPath, to: file.path, similarity: file.similarity ?? 0 });
|
|
24
|
+
for (const hunk of file.hunks) {
|
|
25
|
+
const changed = hunk.lines.filter((line) => line.kind !== "context");
|
|
26
|
+
for (const line of changed) {
|
|
27
|
+
if (!importLine(line))
|
|
28
|
+
continue;
|
|
29
|
+
explanations.push({ kind: "import", hunk: hunk.index, oldLine: line.oldLine, newLine: line.newLine });
|
|
30
|
+
explained.add(`${hunk.index}:${line.kind}:${line.oldLine}:${line.newLine}`);
|
|
31
|
+
}
|
|
32
|
+
const deletions = changed.filter((line) => line.kind === "deletion" && !importLine(line));
|
|
33
|
+
const additions = changed.filter((line) => line.kind === "addition" && !importLine(line));
|
|
34
|
+
if (deletions.length === additions.length)
|
|
35
|
+
deletions.forEach((deletion, index) => {
|
|
36
|
+
const addition = additions[index];
|
|
37
|
+
const rename = identifierRename(deletion.content, addition.content);
|
|
38
|
+
if (!rename)
|
|
39
|
+
return;
|
|
40
|
+
explanations.push({ kind: "symbol-rename", ...rename, hunk: hunk.index, oldLine: deletion.oldLine, newLine: addition.newLine });
|
|
41
|
+
explained.add(`${hunk.index}:${deletion.kind}:${deletion.oldLine}:${deletion.newLine}`);
|
|
42
|
+
explained.add(`${hunk.index}:${addition.kind}:${addition.oldLine}:${addition.newLine}`);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const residue = file.binary ? [{ hunk: -1, oldLine: null, newLine: null, kind: "binary", content: "binary content changed" }] : [];
|
|
46
|
+
for (const hunk of file.hunks)
|
|
47
|
+
for (const line of hunk.lines) {
|
|
48
|
+
if (line.kind === "context")
|
|
49
|
+
continue;
|
|
50
|
+
const key = `${hunk.index}:${line.kind}:${line.oldLine}:${line.newLine}`;
|
|
51
|
+
if (!explained.has(key))
|
|
52
|
+
residue.push({ hunk: hunk.index, oldLine: line.oldLine, newLine: line.newLine, kind: line.kind, content: line.content });
|
|
53
|
+
}
|
|
54
|
+
return { path: file.path, ...(file.oldPath ? { oldPath: file.oldPath } : {}), matcher, explanations, residue, class: matcher.matched ? "in-scope" : residue.length === 0 && explanations.length > 0 ? "downgraded" : "violated" };
|
|
55
|
+
}
|
|
56
|
+
export async function evaluateScope(input) {
|
|
57
|
+
const matchers = await scopePathEvidence(input.cwd, input.frozen.scope, input.diff.files.map((file) => file.path));
|
|
58
|
+
return input.diff.files.map((file, index) => explainFile(file, matchers[index]));
|
|
59
|
+
}
|
|
60
|
+
function rawHunk(file, hunk) {
|
|
61
|
+
return [`${file.status} ${file.oldPath ? `${file.oldPath} -> ` : ""}${file.path} · +${file.insertions} -${file.deletions}`, hunk.header, ...hunk.lines.map((line) => `${line.kind === "addition" ? "+" : line.kind === "deletion" ? "-" : " "}${line.content}`)].join("\n");
|
|
62
|
+
}
|
|
63
|
+
function rawFile(file) { return `${file.status} ${file.oldPath ? `${file.oldPath} -> ` : ""}${file.path} · +${file.insertions} -${file.deletions}`; }
|
|
64
|
+
function moduleFor(pathname) { const segments = pathname.split("/"); return segments.length > 1 ? segments.slice(0, -1).join("/") : "."; }
|
|
65
|
+
function byteLength(content) { return new TextEncoder().encode(content).byteLength; }
|
|
66
|
+
function isAuthorityBoundary(pathname) { return /(^|\/)(auth(?:entication|orization)?|schema|schemas|migrations?|api|config|ci|\.github|permissions?|concurrency)(\/|$)|(^|\/)(package|tsconfig|vite|webpack|eslint|prettier)\.(json|ya?ml|[cm]?[jt]s)$/.test(pathname); }
|
|
67
|
+
function isGeneratedOrLowPriority(pathname, file) { return file.binary || /(^|\/)(vendor|generated|dist|build)(\/|$)|(^|\/)([^/]+\.)?(lock|min)\.(json|ya?ml|js|css)$|(?:^|\/)(package-lock\.json|pnpm-lock\.yaml|yarn\.lock)$/.test(pathname); }
|
|
68
|
+
function isSource(pathname) { return /\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|rb|php|cs|swift)$/.test(pathname) && !/(^|\/)(test|tests|__tests__|spec)(\/|\.)/.test(pathname); }
|
|
69
|
+
function hasRelatedTest(file, all) { const stem = file.path.replace(/\.[^.]+$/, "").replace(/\/index$/, ""); return all.some((candidate) => candidate.path !== file.path && /(test|tests|__tests__|spec)/.test(candidate.path) && candidate.path.replace(/\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|rb|php|cs|swift)$/, "").includes(stem.split("/").at(-1) ?? "")); }
|
|
70
|
+
function portfolioTier(file, scope, all) {
|
|
71
|
+
if (scope.class === "violated")
|
|
72
|
+
return { tier: 0, mandatory: true };
|
|
73
|
+
if (isAuthorityBoundary(file.path))
|
|
74
|
+
return { tier: 1, mandatory: true };
|
|
75
|
+
if (/(^|\/)(package\.json|workspace|pnpm-workspace|turbo|nx)\b/.test(file.path))
|
|
76
|
+
return { tier: 2, mandatory: false };
|
|
77
|
+
if (isGeneratedOrLowPriority(file.path, file))
|
|
78
|
+
return { tier: 6, mandatory: false };
|
|
79
|
+
if (isSource(file.path) && !hasRelatedTest(file, all))
|
|
80
|
+
return { tier: 3, mandatory: false };
|
|
81
|
+
if (file.status === "R" || scope.class === "downgraded")
|
|
82
|
+
return { tier: 5, mandatory: false };
|
|
83
|
+
return { tier: 4, mandatory: false };
|
|
84
|
+
}
|
|
85
|
+
function stablePortfolioOrder(left, right) { if (left.tier !== right.tier)
|
|
86
|
+
return left.tier - right.tier; const ratio = (1000 - right.tier * 100) / Math.max(right.bytes, 1) - (1000 - left.tier * 100) / Math.max(left.bytes, 1); return ratio || left.path.localeCompare(right.path) || (left.hunk ?? -1) - (right.hunk ?? -1); }
|
|
87
|
+
function retrieval(from, to, pathname, hunk) { return { kind: "git-coordinate", from, to, argv: ["git", "diff", "--no-color", "--no-ext-diff", "--unified=3", `${from}..${to}`, "--", pathname], path: pathname, hunk }; }
|
|
88
|
+
export function presentDiff(input) {
|
|
89
|
+
const { diff, scope, budget } = input;
|
|
90
|
+
const candidates = [];
|
|
91
|
+
for (let fileIndex = 0; fileIndex < diff.files.length; fileIndex += 1) {
|
|
92
|
+
const file = diff.files[fileIndex];
|
|
93
|
+
const policy = portfolioTier(file, scope[fileIndex], diff.files);
|
|
94
|
+
const chunks = file.hunks.length === 0 ? [{ hunk: null, content: rawFile(file) }] : file.hunks.map((hunk) => ({ hunk: hunk.index, content: rawHunk(file, hunk) }));
|
|
95
|
+
for (const chunk of chunks) {
|
|
96
|
+
const bytes = byteLength(chunk.content);
|
|
97
|
+
candidates.push({ path: file.path, hunk: chunk.hunk, content: chunk.content, bytes, cost: bytes, tier: policy.tier, mandatory: policy.mandatory, module: moduleFor(file.path) });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const selected = [];
|
|
101
|
+
const selectedKeys = new Set();
|
|
102
|
+
let used = 0;
|
|
103
|
+
const keyFor = (candidate) => `${candidate.path}\0${candidate.hunk ?? "file"}`;
|
|
104
|
+
const take = (candidate) => { const key = keyFor(candidate); if (selectedKeys.has(key))
|
|
105
|
+
return; selectedKeys.add(key); selected.push(candidate); used += candidate.cost; };
|
|
106
|
+
for (const candidate of [...candidates].filter((item) => item.mandatory).sort(stablePortfolioOrder))
|
|
107
|
+
if (used + candidate.cost <= budget)
|
|
108
|
+
take(candidate);
|
|
109
|
+
for (const module of [...new Set(candidates.map((candidate) => candidate.module))].sort()) {
|
|
110
|
+
const representative = candidates.filter((candidate) => candidate.module === module && !selectedKeys.has(keyFor(candidate))).sort(stablePortfolioOrder)[0];
|
|
111
|
+
if (representative && used + representative.cost <= budget)
|
|
112
|
+
take(representative);
|
|
113
|
+
}
|
|
114
|
+
for (const candidate of [...candidates].sort(stablePortfolioOrder))
|
|
115
|
+
if (!selectedKeys.has(keyFor(candidate)) && used + candidate.cost <= budget)
|
|
116
|
+
take(candidate);
|
|
117
|
+
const omissions = candidates.filter((candidate) => !selectedKeys.has(keyFor(candidate))).sort((left, right) => left.path.localeCompare(right.path) || (left.hunk ?? -1) - (right.hunk ?? -1)).map((candidate) => ({ path: candidate.path, hunk: candidate.hunk, bytes: candidate.bytes, reason: candidate.hunk === null ? "no-raw-hunks" : used + candidate.cost > budget ? "budget" : "not-selected", retrieval: retrieval(input.retrievalCoordinates.from, input.retrievalCoordinates.to, candidate.path, candidate.hunk) }));
|
|
118
|
+
return { budget: { limit: budget, used }, selected: selected.sort((left, right) => left.path.localeCompare(right.path) || (left.hunk ?? -1) - (right.hunk ?? -1)), omissions };
|
|
119
|
+
}
|
|
120
|
+
function fileFacts(diff, scope) { return diff.files.map((file, index) => { const row = scope[index]; return { path: file.path, ...(file.oldPath ? { oldPath: file.oldPath } : {}), status: file.status, stats: { insertions: file.insertions, deletions: file.deletions, binary: file.binary }, matcher: row.matcher, explanations: row.explanations, residue: row.residue, class: row.class }; }); }
|
|
121
|
+
export async function evaluateVerification(cwd, head) {
|
|
122
|
+
let commands;
|
|
123
|
+
try {
|
|
124
|
+
commands = await resolveVerificationCommands(cwd);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) }, results: [] };
|
|
128
|
+
}
|
|
129
|
+
if (commands.length === 0)
|
|
130
|
+
return { status: "not_configured", results: [] };
|
|
131
|
+
const results = [];
|
|
132
|
+
for (const command of commands) {
|
|
133
|
+
const argv = [command.command, ...command.args];
|
|
134
|
+
const known = verificationExecutor(command);
|
|
135
|
+
if ("cause" in known) {
|
|
136
|
+
results.push({ status: "unverifiable", label: command.label, command: command.command, argv, head, cause: known.cause });
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
const execution = await runVerificationCommand(cwd, command);
|
|
141
|
+
results.push({ status: "executed", label: command.label, executor: known.executor, body: known.body, command: command.command, argv, head, exitCode: execution.code, durationMs: execution.durationMs, stdout: execution.stdout, stderr: execution.stderr });
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
return { status: "unknown", cause: { code: error instanceof FlowError ? error.code : "INTERNAL_STATE", message: asMessage(error) }, results: [] };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { status: "evaluated", results };
|
|
148
|
+
}
|
|
149
|
+
export async function collectEvidence(input) {
|
|
150
|
+
const candidate = input.candidateResult.candidate;
|
|
151
|
+
if (!candidate)
|
|
152
|
+
return null;
|
|
153
|
+
if (!input.diff)
|
|
154
|
+
return null;
|
|
155
|
+
const diff = input.diff;
|
|
156
|
+
const scope = await evaluateScope({ cwd: input.candidateResult.cwd, frozen: input.frozen, diff });
|
|
157
|
+
return { diff, scope, presentation: presentDiff({ diff, scope, budget: input.diffBudgetBytes, retrievalCoordinates: { from: input.frozen.base, to: input.frozen.coordinates.delivery.head } }), files: fileFacts(diff, scope), verification: await evaluateVerification(input.candidateResult.cwd, candidate.head), candidate };
|
|
158
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createAuditCandidate, disposeAuditCandidate, readAuditCandidateDiff } from "./candidate.js";
|
|
2
|
+
import { initialCoordinateRace, pinAuditCoordinates, pinAuditSnapshot, rereadCoordinateRace } from "./coordinates.js";
|
|
3
|
+
import { collectEvidence } from "./evidence.js";
|
|
4
|
+
import { collectFindings, emptyRaceReport } from "./report.js";
|
|
5
|
+
import { readinessFromFacts } from "./readiness.js";
|
|
6
|
+
export async function auditContract(input) {
|
|
7
|
+
const contractId = input.address.binding.commissionId;
|
|
8
|
+
let frozen;
|
|
9
|
+
try {
|
|
10
|
+
frozen = await pinAuditCoordinates({ cwd: input.cwd, address: input.address });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
const fallback = await pinAuditSnapshot({ address: input.address }).catch(() => null);
|
|
14
|
+
if (!fallback)
|
|
15
|
+
throw error;
|
|
16
|
+
return emptyRaceReport({ contractId, coordinates: fallback.coordinates, terms: { criteria: fallback.criteria, scope: fallback.scope, appendScope: fallback.appendScope }, detail: error instanceof Error ? error.message : String(error), race: initialCoordinateRace({ frozen: fallback, observed: error instanceof Error ? error.message : String(error) }) });
|
|
17
|
+
}
|
|
18
|
+
const candidateResult = await createAuditCandidate({ root: input.address.repo.stableRoot, contractId, frozen });
|
|
19
|
+
let evidence = null;
|
|
20
|
+
let verification;
|
|
21
|
+
try {
|
|
22
|
+
const diff = await readAuditCandidateDiff(candidateResult);
|
|
23
|
+
evidence = await collectEvidence({ candidateResult, frozen, diff, diffBudgetBytes: input.diffBudgetBytes ?? 32_000 });
|
|
24
|
+
verification = evidence?.verification ?? { status: "not_evaluated", reason: candidateResult.rehearsal.status === "conflict" ? "renew_conflict" : "renew_unknown", results: [] };
|
|
25
|
+
await input.afterEvidence?.();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await disposeAuditCandidate(candidateResult);
|
|
29
|
+
}
|
|
30
|
+
const race = await rereadCoordinateRace({ cwd: input.cwd, address: input.address, frozen });
|
|
31
|
+
if (race) {
|
|
32
|
+
const detail = typeof race.observed === "string"
|
|
33
|
+
? `final coordinate reread failed: ${race.observed}`
|
|
34
|
+
: race.which === "delivery-worktree"
|
|
35
|
+
? "delivery worktree bytes or HEAD changed"
|
|
36
|
+
: `audit coordinates changed while evidence was collected: ${race.which}`;
|
|
37
|
+
return emptyRaceReport({ contractId, coordinates: frozen.coordinates, terms: { criteria: frozen.criteria, scope: frozen.scope, appendScope: frozen.appendScope }, detail, race });
|
|
38
|
+
}
|
|
39
|
+
const readiness = readinessFromFacts(frozen.readiness);
|
|
40
|
+
const findings = collectFindings({ contractId, deliveryCwd: frozen.deliveryCwd, rehearsal: candidateResult.rehearsal, scope: evidence?.scope ?? null, verification, readiness });
|
|
41
|
+
return { contract: contractId, coordinates: frozen.coordinates, terms: { criteria: frozen.criteria, scope: frozen.scope, appendScope: frozen.appendScope }, rehearsal: candidateResult.rehearsal, candidate: evidence ? (evidence.candidate.rewritten ? { kind: "renew-rehearsal", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree, deliveryHead: frozen.coordinates.delivery.head, targetHead: frozen.coordinates.target.head } : { kind: "delivery", base: evidence.candidate.base, head: evidence.candidate.head, tree: evidence.candidate.tree }) : null, diff: evidence?.diff ?? null, presentation: evidence?.presentation ?? null, files: evidence?.files ?? null, scope: evidence?.scope ?? null, verification, readiness, findings, counts: { files: evidence?.files.length ?? 0, selected: evidence?.presentation.selected.length ?? 0, omitted: evidence?.presentation.omissions.length ?? 0, inScope: evidence?.scope.filter((row) => row.class === "in-scope").length ?? 0, downgraded: evidence?.scope.filter((row) => row.class === "downgraded").length ?? 0, violated: evidence?.scope.filter((row) => row.class === "violated").length ?? 0, findings: findings.length } };
|
|
42
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function collectFindings(input) {
|
|
2
|
+
const findings = [];
|
|
3
|
+
const selector = `keiyaku @${input.contractId}`;
|
|
4
|
+
if (input.rehearsal.status === "clean")
|
|
5
|
+
findings.push({ code: "BASE_DRIFT", detail: `${input.rehearsal.oldBase} -> ${input.rehearsal.newBase}`, nextCommand: `${selector} renew` });
|
|
6
|
+
if (input.rehearsal.status === "conflict")
|
|
7
|
+
findings.push({ code: "RENEW_CONFLICT", detail: input.rehearsal.paths.join(", "), nextCommand: `${selector} renew` });
|
|
8
|
+
if (input.rehearsal.status === "unknown")
|
|
9
|
+
findings.push({ code: "RENEW_UNKNOWN", detail: `${input.rehearsal.cause.code}: ${input.rehearsal.cause.message}`, nextCommand: `${selector} audit` });
|
|
10
|
+
for (const row of input.scope?.filter((item) => item.class === "violated") ?? [])
|
|
11
|
+
findings.push({ code: "SCOPE_RESIDUE", detail: `${row.path}: ${row.residue.length} unexplained changes`, nextCommand: `${selector} amend --scope-add ${JSON.stringify(row.path)}` });
|
|
12
|
+
if (input.verification.status === "evaluated") {
|
|
13
|
+
for (const result of input.verification.results) {
|
|
14
|
+
if (result.status === "executed" && result.exitCode !== 0)
|
|
15
|
+
findings.push({ code: "VERIFICATION_FAILED", detail: `${result.label} exited ${result.exitCode}`, nextCommand: result.command });
|
|
16
|
+
if (result.status === "unverifiable")
|
|
17
|
+
findings.push({ code: "VERIFICATION_UNVERIFIABLE", detail: `${result.label}: ${result.cause}`, nextCommand: result.command });
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const residue = input.readiness.deliveryResidue;
|
|
21
|
+
if (residue.tracked.length + residue.staged.length + residue.unstaged.length + residue.untracked.length > 0)
|
|
22
|
+
findings.push({ code: "DELIVERY_RESIDUE", detail: `registered delivery worktree has uncommitted residue at ${input.deliveryCwd}`, nextCommand: `git -C ${JSON.stringify(input.deliveryCwd)} status --short` });
|
|
23
|
+
if (input.readiness.arc.max > 0 && input.readiness.arc.open === null && input.readiness.arc.looseCommits.length > 0)
|
|
24
|
+
findings.push({ code: "LOOSE_COMMITS", detail: `${input.readiness.arc.looseCommits.length} loose commits after fence`, nextCommand: `printf '# Continue\\n\\n## Objective\\nReceipt loose commits.\\n\\n## Brief\\nContinue delivery.\\n' | ${selector} arc` });
|
|
25
|
+
if (input.readiness.renewSession.open)
|
|
26
|
+
findings.push({ code: "RENEW_SESSION_OPEN", detail: "renew recovery session is open", nextCommand: `${selector} renew --continue` });
|
|
27
|
+
if (input.readiness.rewriteGuard.liveProjection)
|
|
28
|
+
findings.push({ code: "LIVE_REWRITE_GUARD", detail: "a live projection owns the delivery worktree", nextCommand: "keiyaku status" });
|
|
29
|
+
for (const dependency of input.readiness.dependencies.filter((item) => !item.satisfied))
|
|
30
|
+
findings.push({ code: "DEPENDENCY_OPEN", detail: `dependency ${dependency.id} is ${dependency.state}`, nextCommand: `keiyaku @${dependency.id} log` });
|
|
31
|
+
if (input.readiness.petition.state === "petitioned" && input.readiness.petition.queue?.position !== 1)
|
|
32
|
+
findings.push({ code: "PETITION_QUEUE", detail: `petition queue position ${input.readiness.petition.queue?.position ?? "unknown"}`, nextCommand: "keiyaku status" });
|
|
33
|
+
return findings;
|
|
34
|
+
}
|
|
35
|
+
export function emptyRaceReport(input) {
|
|
36
|
+
const findings = [{ code: "COORDINATE_RACE", detail: input.detail, nextCommand: `keiyaku @${input.contractId} audit`, race: input.race }];
|
|
37
|
+
return { contract: input.contractId, coordinates: input.coordinates, terms: input.terms, rehearsal: { status: "unknown", cause: { code: "COORDINATE_RACE", message: input.detail } }, candidate: null, diff: null, presentation: null, files: null, scope: null, verification: { status: "not_evaluated", reason: "coordinate_race", results: [] }, readiness: null, findings, counts: { files: 0, selected: 0, omitted: 0, inScope: 0, downgraded: 0, violated: 0, findings: 1 } };
|
|
38
|
+
}
|