@mrciphersmith/keryx 0.3.113 → 0.3.114
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/README.md +4 -0
- package/dist/cli.js +354 -112
- package/dist/core.js +3 -1
- package/package.json +1 -1
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +8 -8
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +13 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/templates/review-report.md +22 -0
package/README.md
CHANGED
|
@@ -1148,6 +1148,10 @@ package. See
|
|
|
1148
1148
|
|
|
1149
1149
|
Every managed review start, attach or ingest (and any `dispatch-check` or `blast-radius` given `--package`) refreshes the graph, the wiki and the memory index for the PR head and writes `layers.json`, with **no model call**; `wiki enrich` is the only model step and is never automatic (`keryx review layers refresh --package <dir> [--write-wiki]`). Blast-radius files beyond the 40-file cap are reviewed in chunks (`keryx review chunks estimate|next|record|merge`; the ceiling is 25 passes of 40 files, `reviewChunks.maxPasses` in `routing.config.json`), `keryx review coverage` counts what is covered, omitted by name, and uncovered, and `keryx review complete` refuses while a file is uncovered unless you pass `--waive-coverage "<your words>"`. `/reviews` and `/review layers` in the shell show it.
|
|
1150
1150
|
|
|
1151
|
+
### Gates that keep a review report honest
|
|
1152
|
+
|
|
1153
|
+
`keryx review complete` on a pull-request package refuses a merged PR or a moved head until the first line of `report.md` names the reviewed SHA and says so, and refuses a report with no `Evidence level:` line (`static only — did not run: <what>` or `ran — <what>`; a package ingested before the field existed only warns). `keryx review ingest` reads each finding's quote at its `path` at the reviewed SHA and refuses one that does not resolve there, adds a `citation not found` line to a rebuttal that cites a rule or document missing from the tree (the finding is never deleted), and a finding can close as `narrowed` only with a "what remains" text. See [review with a record](docs/docs/guides/review-with-a-record.md).
|
|
1154
|
+
|
|
1151
1155
|
### Review bot on pull requests
|
|
1152
1156
|
|
|
1153
1157
|
A GitHub Action reviews each same-repository pull request, has a second model
|
package/dist/cli.js
CHANGED
|
@@ -23150,7 +23150,7 @@ var package_default;
|
|
|
23150
23150
|
var init_package = __esm(() => {
|
|
23151
23151
|
package_default = {
|
|
23152
23152
|
name: "@mrciphersmith/keryx",
|
|
23153
|
-
version: "0.3.
|
|
23153
|
+
version: "0.3.114",
|
|
23154
23154
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
23155
23155
|
private: false,
|
|
23156
23156
|
publishConfig: {
|
|
@@ -68818,10 +68818,12 @@ function researchErrors(value, findings) {
|
|
|
68818
68818
|
ids.add(id);
|
|
68819
68819
|
if (!nonempty(o.source) || !nonempty(o.question))
|
|
68820
68820
|
errors.push(`${id}: source and question are required`);
|
|
68821
|
-
if (!["finding", "refuted", "out-of-scope"].includes(String(o.status)))
|
|
68821
|
+
if (!["finding", "refuted", "out-of-scope", "narrowed"].includes(String(o.status)))
|
|
68822
68822
|
errors.push(`${id}: unresolved research (${String(o.status)})`);
|
|
68823
68823
|
if (!nonempty(o.evidence) || !nonempty(o.reason))
|
|
68824
68824
|
errors.push(`${id}: closure needs evidence and reason`);
|
|
68825
|
+
if (o.status === "narrowed" && !nonempty(o.remains))
|
|
68826
|
+
errors.push(`${id}: narrowed needs a "what remains" text`);
|
|
68825
68827
|
if (o.status === "finding") {
|
|
68826
68828
|
const matches = findings.filter((f) => f && typeof f === "object" && (f.id === o.finding || f.global_id === o.finding));
|
|
68827
68829
|
if (!nonempty(o.finding) || matches.length !== 1)
|
|
@@ -78919,6 +78921,127 @@ function arbiterSession() {
|
|
|
78919
78921
|
}
|
|
78920
78922
|
var session;
|
|
78921
78923
|
|
|
78924
|
+
// src/review/rebuttal-citation.ts
|
|
78925
|
+
function citedPaths(text) {
|
|
78926
|
+
const found = new Set;
|
|
78927
|
+
for (const match of text.replace(URL2, " ").matchAll(CITED)) {
|
|
78928
|
+
const cited = match[1];
|
|
78929
|
+
if (cited.includes("/") || ROOT_DOCUMENT.test(cited))
|
|
78930
|
+
found.add(cited);
|
|
78931
|
+
}
|
|
78932
|
+
return [...found];
|
|
78933
|
+
}
|
|
78934
|
+
async function flagMissingCitations(findings, exists) {
|
|
78935
|
+
let added = 0;
|
|
78936
|
+
for (const finding of findings) {
|
|
78937
|
+
const disposition = finding.disposition;
|
|
78938
|
+
if (disposition === undefined || typeof disposition.evidence !== "string")
|
|
78939
|
+
continue;
|
|
78940
|
+
const missing = [];
|
|
78941
|
+
for (const cited of citedPaths(disposition.evidence)) {
|
|
78942
|
+
if (disposition.evidence.includes(marker(cited)))
|
|
78943
|
+
continue;
|
|
78944
|
+
if (!await exists(cited))
|
|
78945
|
+
missing.push(cited);
|
|
78946
|
+
}
|
|
78947
|
+
if (missing.length === 0)
|
|
78948
|
+
continue;
|
|
78949
|
+
disposition.evidence = `${disposition.evidence}
|
|
78950
|
+
${missing.map(marker).join(`
|
|
78951
|
+
`)}`;
|
|
78952
|
+
added += missing.length;
|
|
78953
|
+
}
|
|
78954
|
+
return added;
|
|
78955
|
+
}
|
|
78956
|
+
var URL2, CITED, ROOT_DOCUMENT, marker = (cited) => `citation not found: ${cited}`;
|
|
78957
|
+
var init_rebuttal_citation = __esm(() => {
|
|
78958
|
+
URL2 = /\bhttps?:\/\/\S+/gi;
|
|
78959
|
+
CITED = /(?<![\w./-])((?:\.\.?\/)*(?:[\w.-]+\/)*[\w.-]+\.(?:mdc|mdx|md|txt|json|ya?ml|toml))(?![\w-])/g;
|
|
78960
|
+
ROOT_DOCUMENT = /^[A-Z][A-Z0-9_-]*\.(?:md|mdx|txt)$/;
|
|
78961
|
+
});
|
|
78962
|
+
|
|
78963
|
+
// src/review/complete-head-gate.ts
|
|
78964
|
+
async function completeHeadGate(input) {
|
|
78965
|
+
const { target } = input.manifest;
|
|
78966
|
+
if (target.kind !== "pr")
|
|
78967
|
+
return { warnings: [] };
|
|
78968
|
+
let pr;
|
|
78969
|
+
try {
|
|
78970
|
+
pr = await input.lookup(target.ref);
|
|
78971
|
+
} catch (error) {
|
|
78972
|
+
return { warnings: [`could not read the PR state for ${target.ref} (${error instanceof Error ? error.message : String(error)}); the head and merge check was skipped`] };
|
|
78973
|
+
}
|
|
78974
|
+
if (pr === null || !pr.exists)
|
|
78975
|
+
return { warnings: [`could not read the PR state for ${target.ref}; the head and merge check was skipped`] };
|
|
78976
|
+
const recorded = target.head ?? null;
|
|
78977
|
+
const reviewed = recorded !== null && USABLE_SHA.test(recorded) ? recorded : null;
|
|
78978
|
+
const unusable = recorded !== null && reviewed === null ? [`the reviewed head recorded for ${target.ref} ("${recorded}") is not a commit SHA; the moved-head check was skipped`] : [];
|
|
78979
|
+
const merged = typeof pr.state === "string" && pr.state.toUpperCase() === "MERGED";
|
|
78980
|
+
const prHead = typeof pr.headSha === "string" && pr.headSha !== "" ? pr.headSha : null;
|
|
78981
|
+
const moved = reviewed !== null && prHead !== null && !sameCommit(reviewed, prHead);
|
|
78982
|
+
if (!merged && !moved)
|
|
78983
|
+
return { warnings: unusable };
|
|
78984
|
+
const what = [merged ? "the PR is merged" : undefined, moved ? `the PR head moved (reviewed ${reviewed.slice(0, 7)}, now ${prHead.slice(0, 7)})` : undefined].filter(Boolean).join(" and ");
|
|
78985
|
+
const first = input.report.split(/\r?\n/).find((line) => line.trim() !== "" && !/^\s*<!--.*-->\s*$/.test(line)) ?? "";
|
|
78986
|
+
const names = reviewed === null ? /\b[0-9a-f]{7,40}\b/i.test(first) : first.toLowerCase().includes(reviewed.slice(0, 7).toLowerCase());
|
|
78987
|
+
if (names && SAYS_SO.test(first))
|
|
78988
|
+
return { warnings: unusable };
|
|
78989
|
+
throw new Error(`Cannot complete managed review; ${what}. The report's first line must name the reviewed SHA${reviewed === null ? "" : ` (${reviewed.slice(0, 7)})`} and say the PR moved, for example: "Reviewed ${(reviewed ?? "<sha>").slice(0, 7)} \u2014 the PR has since ${merged ? "merged" : "moved on"}."`);
|
|
78990
|
+
}
|
|
78991
|
+
var MOVE_WORD = "(?:merged|moved|advanced|superseded|closed|force-pushed)", PR_WORD = "(?:pr|pull request|head)", SAYS_SO, USABLE_SHA, sameCommit = (a, b) => {
|
|
78992
|
+
const [x, y] = [a.toLowerCase(), b.toLowerCase()];
|
|
78993
|
+
return x.startsWith(y) || y.startsWith(x);
|
|
78994
|
+
};
|
|
78995
|
+
var init_complete_head_gate = __esm(() => {
|
|
78996
|
+
SAYS_SO = new RegExp(`\\b${PR_WORD}\\b.{0,40}\\b${MOVE_WORD}\\b|\\b${MOVE_WORD}\\b.{0,40}\\b${PR_WORD}\\b`, "i");
|
|
78997
|
+
USABLE_SHA = /^[0-9a-f]{7,40}$/i;
|
|
78998
|
+
});
|
|
78999
|
+
|
|
79000
|
+
// src/review/evidence-level.ts
|
|
79001
|
+
function itemsOf(text) {
|
|
79002
|
+
return text.split(/[,;]/).map((item) => item.trim()).filter((item) => item.length > 0 && !PLACEHOLDER.test(item));
|
|
79003
|
+
}
|
|
79004
|
+
function withoutFences(report) {
|
|
79005
|
+
let fence = null;
|
|
79006
|
+
const kept = [];
|
|
79007
|
+
for (const line of report.split(/\r?\n/)) {
|
|
79008
|
+
const marker = /^\s*(```+|~~~+)/.exec(line)?.[1];
|
|
79009
|
+
if (marker !== undefined) {
|
|
79010
|
+
if (fence === null)
|
|
79011
|
+
fence = marker[0];
|
|
79012
|
+
else if (marker[0] === fence)
|
|
79013
|
+
fence = null;
|
|
79014
|
+
continue;
|
|
79015
|
+
}
|
|
79016
|
+
if (fence === null)
|
|
79017
|
+
kept.push(line);
|
|
79018
|
+
}
|
|
79019
|
+
return kept;
|
|
79020
|
+
}
|
|
79021
|
+
function parseEvidenceLevel(report) {
|
|
79022
|
+
const line = withoutFences(report).map((l) => LINE.exec(l)).find((m) => m !== null);
|
|
79023
|
+
if (!line)
|
|
79024
|
+
return { ok: false, reason: "the report has no `Evidence level:` line" };
|
|
79025
|
+
const value = line[1].replace(/(?:\*\*|__)\s*$/, "").trim();
|
|
79026
|
+
const staticOnly = /^static[ -]only\b/i.exec(value);
|
|
79027
|
+
if (staticOnly) {
|
|
79028
|
+
const items = itemsOf(value.slice(staticOnly[0].length).replace(SEPARATORS, "").replace(/^(?:did not run|not run|did not)\s*:?\s*/i, ""));
|
|
79029
|
+
return items.length > 0 ? { ok: true, level: "static-only", items } : { ok: false, reason: "`static only` must list what did not run" };
|
|
79030
|
+
}
|
|
79031
|
+
const ran = /^ran\b/i.exec(value);
|
|
79032
|
+
if (ran) {
|
|
79033
|
+
const items = itemsOf(value.slice(ran[0].length).replace(SEPARATORS, ""));
|
|
79034
|
+
return items.length > 0 ? { ok: true, level: "ran", items } : { ok: false, reason: "`ran` must list what ran" };
|
|
79035
|
+
}
|
|
79036
|
+
return { ok: false, reason: "the evidence level must start with `static only` or `ran`" };
|
|
79037
|
+
}
|
|
79038
|
+
var REPORT_FIELD_EVIDENCE_LEVEL = "evidence-level", LINE, SEPARATORS, PLACEHOLDER;
|
|
79039
|
+
var init_evidence_level = __esm(() => {
|
|
79040
|
+
LINE = /^\s*(?:[-*+]\s+)?(?:\*\*|__)?evidence level\s*:?\s*(?:\*\*|__)?\s*:?\s*(.*)$/i;
|
|
79041
|
+
SEPARATORS = /^[\s:\u2014\u2013-]+/;
|
|
79042
|
+
PLACEHOLDER = /^(?:none|nothing|n\/a|na|nil|-|\u2014|\u2013)\.?$/i;
|
|
79043
|
+
});
|
|
79044
|
+
|
|
78922
79045
|
// src/review/verification.ts
|
|
78923
79046
|
function verificationClaims(source) {
|
|
78924
79047
|
if (source === undefined) {
|
|
@@ -79868,7 +79991,7 @@ var init_repair = __esm(() => {
|
|
|
79868
79991
|
});
|
|
79869
79992
|
|
|
79870
79993
|
// src/review/managed.ts
|
|
79871
|
-
import { mkdir as mkdir52, readFile as readFile98, readdir as readdir33, realpath as realpath18, rm as rm13, stat as stat27 } from "fs/promises";
|
|
79994
|
+
import { lstat as lstat12, mkdir as mkdir52, readFile as readFile98, readdir as readdir33, realpath as realpath18, rm as rm13, stat as stat27 } from "fs/promises";
|
|
79872
79995
|
import path188 from "path";
|
|
79873
79996
|
function reviewsRoot(cwd) {
|
|
79874
79997
|
return path188.join(cwd, ".metaproject", "reviews");
|
|
@@ -79937,6 +80060,7 @@ async function createManagedReviewPackage(input) {
|
|
|
79937
80060
|
const repairs = repairMechanicalOmissions(reported);
|
|
79938
80061
|
const head = await resolveTargetHead(input);
|
|
79939
80062
|
await anchorFindings(reported, input, head);
|
|
80063
|
+
refuseUnresolvedLocators(reported, input, head);
|
|
79940
80064
|
assignGlobalIds(reported, reviewId);
|
|
79941
80065
|
const merged = mergeVerifications(reported, input.verifications ?? [], {
|
|
79942
80066
|
mode: input.verificationMode ?? DEFAULT_VERIFICATION_MODE
|
|
@@ -79955,6 +80079,7 @@ async function createManagedReviewPackage(input) {
|
|
|
79955
80079
|
...roundDismissed
|
|
79956
80080
|
];
|
|
79957
80081
|
assignGlobalIds(findings, reviewId);
|
|
80082
|
+
await flagMissingCitations(findings, (cited) => citationExists(input.cwd, head, cited));
|
|
79958
80083
|
const carried = input.scope === undefined ? await readPreFilterScopeBlock(packageDir) : undefined;
|
|
79959
80084
|
const concurrency = concurrencyPlan(input, coverage);
|
|
79960
80085
|
const caps = {
|
|
@@ -80150,6 +80275,18 @@ async function completeManagedReview(cwd, ref, options = {}) {
|
|
|
80150
80275
|
if (missing.length > 0) {
|
|
80151
80276
|
throw new Error(`Cannot complete managed review; missing artifacts: ${missing.join(", ")}`);
|
|
80152
80277
|
}
|
|
80278
|
+
const warnings = [];
|
|
80279
|
+
const reportText = await readFile98(path188.join(packageDir, "report.md"), "utf8");
|
|
80280
|
+
if (options.prLookup !== undefined)
|
|
80281
|
+
warnings.push(...(await completeHeadGate({ manifest, report: reportText, lookup: options.prLookup })).warnings);
|
|
80282
|
+
const evidence = parseEvidenceLevel(reportText);
|
|
80283
|
+
if (!evidence.ok) {
|
|
80284
|
+
const problem = `the report's evidence level is missing or invalid: ${evidence.reason}`;
|
|
80285
|
+
if (manifest.report_fields?.includes(REPORT_FIELD_EVIDENCE_LEVEL)) {
|
|
80286
|
+
throw new Error(`Cannot complete managed review; ${problem}. Add a line \`Evidence level: static only \u2014 did not run: <what>\` or \`Evidence level: ran \u2014 <what>\` to report.md`);
|
|
80287
|
+
}
|
|
80288
|
+
warnings.push(`${problem} (this package predates the field, so it is a warning)`);
|
|
80289
|
+
}
|
|
80153
80290
|
const researchFindings = JSON.parse(await readFile98(path188.join(packageDir, "findings.json"), "utf8"));
|
|
80154
80291
|
const researchPath = path188.join(packageDir, "research.json");
|
|
80155
80292
|
const researchBefore = options.waiveCoverage === undefined ? undefined : await readFile98(researchPath, "utf8").catch(() => {
|
|
@@ -80193,7 +80330,7 @@ async function completeManagedReview(cwd, ref, options = {}) {
|
|
|
80193
80330
|
packagePath: path188.relative(cwd, packageDir),
|
|
80194
80331
|
...options.now === undefined ? {} : { now: options.now }
|
|
80195
80332
|
});
|
|
80196
|
-
return { manifest: updated, reviewNotes, dispositions: tallyDispositions(recorded), ...coverageWaiver === undefined ? {} : { coverageWaiver } };
|
|
80333
|
+
return { manifest: updated, reviewNotes, dispositions: tallyDispositions(recorded), warnings, ...coverageWaiver === undefined ? {} : { coverageWaiver } };
|
|
80197
80334
|
}
|
|
80198
80335
|
async function validateManagedReviewManifest(cwd, manifest) {
|
|
80199
80336
|
const errors = [];
|
|
@@ -80283,6 +80420,7 @@ function buildManifest(args) {
|
|
|
80283
80420
|
coverage: args.coverage,
|
|
80284
80421
|
filter_stats: args.filterStats,
|
|
80285
80422
|
...args.repairs.length === 0 ? {} : { repairs: args.repairs },
|
|
80423
|
+
report_fields: [REPORT_FIELD_EVIDENCE_LEVEL],
|
|
80286
80424
|
...args.input.cost === undefined ? {} : { cost: args.input.cost },
|
|
80287
80425
|
...crossFamily === undefined ? {} : { cross_family_review: crossFamily },
|
|
80288
80426
|
...args.input.automatic === true ? { automatic: true } : {},
|
|
@@ -80358,7 +80496,7 @@ function triage(findings, args) {
|
|
|
80358
80496
|
}
|
|
80359
80497
|
async function anchorFindings(findings, input, head) {
|
|
80360
80498
|
const read = input.readTreeFile ?? (head === null ? readFromWorkingTree(input) : async (relative) => {
|
|
80361
|
-
const contained =
|
|
80499
|
+
const contained = lexicalRelativePath(relative);
|
|
80362
80500
|
return contained === null ? null : readFileAtCommit(input.cwd, head, contained);
|
|
80363
80501
|
});
|
|
80364
80502
|
for (const finding of findings) {
|
|
@@ -80384,17 +80522,42 @@ async function anchorFindings(findings, input, head) {
|
|
|
80384
80522
|
};
|
|
80385
80523
|
}
|
|
80386
80524
|
}
|
|
80387
|
-
|
|
80388
|
-
|
|
80389
|
-
|
|
80390
|
-
|
|
80391
|
-
|
|
80525
|
+
function refuseUnresolvedLocators(findings, input, head) {
|
|
80526
|
+
if (head === null || input.readTreeFile !== undefined) {
|
|
80527
|
+
return;
|
|
80528
|
+
}
|
|
80529
|
+
const unresolved = findings.filter((finding) => finding.source !== "external" && finding.disposition === undefined && finding.locator?.state === "unlocatable");
|
|
80530
|
+
if (unresolved.length === 0) {
|
|
80531
|
+
return;
|
|
80532
|
+
}
|
|
80533
|
+
const lines = unresolved.map((finding) => {
|
|
80534
|
+
const locator = finding.locator?.state === "unlocatable" ? finding.locator : undefined;
|
|
80535
|
+
const line = locator?.reported_line ?? null;
|
|
80536
|
+
return ` - ${finding.id ?? finding.summary} (${finding.file}${line === null ? "" : `:${line}`}): ${locator?.reason ?? ""}`;
|
|
80537
|
+
});
|
|
80538
|
+
throw new Error(`Cannot ingest review; ${unresolved.length} finding(s) cite a quote that does not resolve at the reviewed SHA ${head.slice(0, 12)}:
|
|
80539
|
+
${lines.join(`
|
|
80540
|
+
`)}
|
|
80541
|
+
Causes: the quote is not in the file at that commit, appears in it more than once, or the file or the commit is not in this checkout (check \`git cat-file -e ${head.slice(0, 12)}\`). Re-quote one distinct line from \`git show ${head.slice(0, 7)}:<path>\` or dismiss the finding with evidence.`);
|
|
80542
|
+
}
|
|
80543
|
+
async function citationExists(cwd, head, cited) {
|
|
80544
|
+
const relative = lexicalRelativePath(cited);
|
|
80545
|
+
if (relative === null) {
|
|
80546
|
+
return false;
|
|
80547
|
+
}
|
|
80548
|
+
if (head !== null && await readFileAtCommit(cwd, head, relative) !== null) {
|
|
80549
|
+
return true;
|
|
80550
|
+
}
|
|
80551
|
+
return lstat12(path188.resolve(cwd, relative)).then(() => true, () => false);
|
|
80552
|
+
}
|
|
80553
|
+
function lexicalRelativePath(relative) {
|
|
80554
|
+
const normalised = path188.posix.normalize(relative.replace(/\\/g, "/"));
|
|
80555
|
+
if (normalised === "." || normalised === ".." || normalised.startsWith("../") || path188.posix.isAbsolute(normalised)) {
|
|
80392
80556
|
return null;
|
|
80393
80557
|
}
|
|
80394
|
-
return
|
|
80558
|
+
return normalised;
|
|
80395
80559
|
}
|
|
80396
|
-
async function readFileAtCommit(cwd, commit,
|
|
80397
|
-
const relative = path188.relative(await realpath18(cwd).catch(() => path188.resolve(cwd)), absolute);
|
|
80560
|
+
async function readFileAtCommit(cwd, commit, relative) {
|
|
80398
80561
|
try {
|
|
80399
80562
|
const proc = Bun.spawn(["git", "show", `${commit}:${relative}`], {
|
|
80400
80563
|
cwd,
|
|
@@ -81091,6 +81254,9 @@ async function loadDocpackSchema(cwd) {
|
|
|
81091
81254
|
var REQUIRED_ARTIFACTS, BLAST_RADIUS_ARTIFACT = "blast-radius.json", EMBEDDED_FINDINGS_FENCE, SEVERITY_PREFIX, NOT_RECORDED = (field) => `not recorded: derived from a markdown review report, which carried no ${field} field`, LABEL_LINE, HEADING_MARKER, FINDING_IDENTIFIER, TITLE_SEPARATOR, REFERENCE_PUNCTUATION, PRE_FILTER_SCOPE_BLOCK, BLAST_RADIUS_RECORD_SOURCE, MAX_SAME_DAY_ROUNDS = 99;
|
|
81092
81255
|
var init_managed = __esm(() => {
|
|
81093
81256
|
init_coverage_complete();
|
|
81257
|
+
init_rebuttal_citation();
|
|
81258
|
+
init_complete_head_gate();
|
|
81259
|
+
init_evidence_level();
|
|
81094
81260
|
init_research();
|
|
81095
81261
|
init_validator();
|
|
81096
81262
|
init_contracts();
|
|
@@ -88980,7 +89146,7 @@ function sha256Hex5(bytes) {
|
|
|
88980
89146
|
var init_checksum = () => {};
|
|
88981
89147
|
|
|
88982
89148
|
// src/bundle/external.ts
|
|
88983
|
-
import { mkdir as mkdir54, lstat as
|
|
89149
|
+
import { mkdir as mkdir54, lstat as lstat13, readFile as readFile108, readdir as readdir40, rename as rename11, stat as stat29, unlink, writeFile as writeFile33 } from "fs/promises";
|
|
88984
89150
|
import { createHmac as createHmac4, randomBytes as randomBytes10, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
88985
89151
|
import path215 from "path";
|
|
88986
89152
|
function emptyRegistry() {
|
|
@@ -89024,7 +89190,7 @@ async function loadIntegrityKey(env, homeDir) {
|
|
|
89024
89190
|
const keyPath = integrityKeyPathFor(env, homeDir);
|
|
89025
89191
|
let st;
|
|
89026
89192
|
try {
|
|
89027
|
-
st = await
|
|
89193
|
+
st = await lstat13(keyPath);
|
|
89028
89194
|
} catch (err) {
|
|
89029
89195
|
if (err?.code === "ENOENT")
|
|
89030
89196
|
return { ok: false, reason: "missing" };
|
|
@@ -89464,7 +89630,7 @@ var init_external = __esm(() => {
|
|
|
89464
89630
|
|
|
89465
89631
|
// src/gdskills/governance/scout.ts
|
|
89466
89632
|
import { existsSync as existsSync32, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
|
|
89467
|
-
import { lstat as
|
|
89633
|
+
import { lstat as lstat14, mkdtemp as mkdtemp2, readdir as readdir41, readFile as readFile109 } from "fs/promises";
|
|
89468
89634
|
import { tmpdir as tmpdir3 } from "os";
|
|
89469
89635
|
import path216 from "path";
|
|
89470
89636
|
function stemLite(token) {
|
|
@@ -89690,7 +89856,7 @@ async function scoutImports(query, opts = {}) {
|
|
|
89690
89856
|
async function collectSkillDirectorySnapshot(dir, limits = DEFAULT_SNAPSHOT_VETTING_LIMITS) {
|
|
89691
89857
|
let rootStat;
|
|
89692
89858
|
try {
|
|
89693
|
-
rootStat = await
|
|
89859
|
+
rootStat = await lstat14(dir);
|
|
89694
89860
|
} catch (error) {
|
|
89695
89861
|
return { ok: false, reason: "unreadable-dir", message: `cannot read ${dir}: ${error instanceof Error ? error.message : String(error)}` };
|
|
89696
89862
|
}
|
|
@@ -89722,7 +89888,7 @@ async function collectSkillDirectorySnapshot(dir, limits = DEFAULT_SNAPSHOT_VETT
|
|
|
89722
89888
|
const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
|
|
89723
89889
|
let entryStat;
|
|
89724
89890
|
try {
|
|
89725
|
-
entryStat = await
|
|
89891
|
+
entryStat = await lstat14(abs);
|
|
89726
89892
|
} catch (error) {
|
|
89727
89893
|
return { ok: false, reason: "unreadable-file", message: `cannot stat ${rel}: ${error instanceof Error ? error.message : String(error)}` };
|
|
89728
89894
|
}
|
|
@@ -94092,7 +94258,7 @@ var init_slate_lifecycle = __esm(() => {
|
|
|
94092
94258
|
|
|
94093
94259
|
// src/harness/tool/builtin/slate-memory-tools.ts
|
|
94094
94260
|
import { createReadStream } from "fs";
|
|
94095
|
-
import { lstat as
|
|
94261
|
+
import { lstat as lstat15, realpath as realpath19 } from "fs/promises";
|
|
94096
94262
|
import path223 from "path";
|
|
94097
94263
|
import { createInterface } from "readline";
|
|
94098
94264
|
function failure3(message) {
|
|
@@ -94110,7 +94276,7 @@ async function confinedFile(sessionDir, candidate, within) {
|
|
|
94110
94276
|
try {
|
|
94111
94277
|
const root = await realpath19(within === undefined ? sessionDir : path223.join(sessionDir, within));
|
|
94112
94278
|
const sessionReal = await realpath19(sessionDir);
|
|
94113
|
-
const info = await
|
|
94279
|
+
const info = await lstat15(path223.resolve(candidate));
|
|
94114
94280
|
if (info.isSymbolicLink() || !info.isFile())
|
|
94115
94281
|
return null;
|
|
94116
94282
|
const real = await realpath19(path223.resolve(candidate));
|
|
@@ -99580,7 +99746,7 @@ async function resolveGitHooksRoot(projectRoot) {
|
|
|
99580
99746
|
var init_git_hooks = () => {};
|
|
99581
99747
|
|
|
99582
99748
|
// src/lib/managed-git-hook.ts
|
|
99583
|
-
import { chmod as chmod9, lstat as
|
|
99749
|
+
import { chmod as chmod9, lstat as lstat16, mkdir as mkdir57, readFile as readFile112, realpath as realpath20, writeFile as writeFile35 } from "fs/promises";
|
|
99584
99750
|
import path230 from "path";
|
|
99585
99751
|
function escapeRegExp6(value) {
|
|
99586
99752
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
@@ -99611,13 +99777,13 @@ async function resolveContainedHookPath(projectRoot, hookName) {
|
|
|
99611
99777
|
const commonDirReal = await realpath20(path230.dirname(hooksRoot)).catch(() => path230.dirname(hooksRoot));
|
|
99612
99778
|
const projectRootReal = await realpath20(projectRoot).catch(() => projectRoot);
|
|
99613
99779
|
const acceptedContainers = [commonDirReal, projectRootReal];
|
|
99614
|
-
const hooksRootStat = await
|
|
99780
|
+
const hooksRootStat = await lstat16(hooksRoot).catch(() => null);
|
|
99615
99781
|
const hooksRootReal = await realpath20(hooksRoot).catch(() => hooksRoot);
|
|
99616
99782
|
if (hooksRootStat?.isSymbolicLink()) {
|
|
99617
99783
|
await assertResolvesInside(hooksRoot, acceptedContainers, "the git hooks directory");
|
|
99618
99784
|
}
|
|
99619
99785
|
const hookPath = path230.join(hooksRoot, hookName);
|
|
99620
|
-
const hookStat = await
|
|
99786
|
+
const hookStat = await lstat16(hookPath).catch(() => null);
|
|
99621
99787
|
if (hookStat?.isSymbolicLink()) {
|
|
99622
99788
|
await assertResolvesInside(hookPath, [hooksRootReal, projectRootReal], "the git hook file");
|
|
99623
99789
|
}
|
|
@@ -107924,7 +108090,7 @@ var init_registry3 = __esm(() => {
|
|
|
107924
108090
|
// src/harness/tool/builtin/spawn-subagent-tool.ts
|
|
107925
108091
|
import { createHash as createHash74, randomUUID as randomUUID28 } from "crypto";
|
|
107926
108092
|
import { execFile as execFile7 } from "child_process";
|
|
107927
|
-
import { lstat as
|
|
108093
|
+
import { lstat as lstat17, mkdir as mkdir60, mkdtemp as mkdtemp3, readFile as readFile114, realpath as realpath21, rm as rm16, stat as stat31, writeFile as writeFile38 } from "fs/promises";
|
|
107928
108094
|
import { tmpdir as tmpdir7 } from "os";
|
|
107929
108095
|
import path243 from "path";
|
|
107930
108096
|
import { promisify as promisify5 } from "util";
|
|
@@ -108232,7 +108398,7 @@ async function verifyWorktreeCandidate(candidate, commonDir, worktreesDir) {
|
|
|
108232
108398
|
try {
|
|
108233
108399
|
const realCandidate = await realpath21(candidate);
|
|
108234
108400
|
const dotGit = path243.join(realCandidate, ".git");
|
|
108235
|
-
const dotGitStat = await
|
|
108401
|
+
const dotGitStat = await lstat17(dotGit);
|
|
108236
108402
|
if (dotGitStat.isDirectory()) {
|
|
108237
108403
|
return await realpath21(dotGit) === commonDir ? realCandidate : undefined;
|
|
108238
108404
|
}
|
|
@@ -126701,6 +126867,12 @@ function layersLockPath(packageDir) {
|
|
|
126701
126867
|
function packageMemoryDir(packageDir) {
|
|
126702
126868
|
return path275.join(packageDir, LAYERS_DIR, MEMORY_DIR);
|
|
126703
126869
|
}
|
|
126870
|
+
function failedRecently(existing, now) {
|
|
126871
|
+
if (existing.graph.status !== "failed")
|
|
126872
|
+
return false;
|
|
126873
|
+
const age = now.getTime() - Date.parse(existing.graph.failedAt ?? existing.refreshedAt);
|
|
126874
|
+
return age >= 0 && age < FAILED_RETRY_WINDOW_MS;
|
|
126875
|
+
}
|
|
126704
126876
|
function layersRefreshInFlight(packageDir) {
|
|
126705
126877
|
return isLockHeld(layersLockPath(packageDir), LOCK_STALE_MS2);
|
|
126706
126878
|
}
|
|
@@ -126842,20 +127014,35 @@ async function sweepStaleWorktrees(deps, projectRoot) {
|
|
|
126842
127014
|
await rm19(worktree, { recursive: true, force: true }).catch(() => {});
|
|
126843
127015
|
}
|
|
126844
127016
|
}
|
|
126845
|
-
async function
|
|
126846
|
-
const
|
|
126847
|
-
const cleanup = async () => {
|
|
126848
|
-
await cleanupExtra().catch(() => {});
|
|
126849
|
-
await deps.git(input.projectRoot, ["worktree", "remove", "--force", "--end-of-options", worktree]);
|
|
126850
|
-
await rm19(worktree, { recursive: true, force: true }).catch(() => {});
|
|
126851
|
-
};
|
|
127017
|
+
async function withSignalCleanup(packageDir, body) {
|
|
127018
|
+
const cleanups = new Set;
|
|
126852
127019
|
const onSignal = (signal) => {
|
|
126853
127020
|
for (const name of SIGNALS)
|
|
126854
127021
|
process.off(name, onSignal);
|
|
126855
|
-
|
|
127022
|
+
const settled = Promise.all([...cleanups].map((cleanup) => cleanup().catch(() => {})));
|
|
127023
|
+
const bound = new Promise((resolve) => setTimeout(resolve, SIGNAL_CLEANUP_MAX_MS).unref());
|
|
127024
|
+
Promise.race([settled, bound]).then(() => rm19(layersLockPath(packageDir), { recursive: true, force: true })).catch(() => {}).finally(() => process.kill(process.pid, signal));
|
|
126856
127025
|
};
|
|
126857
127026
|
for (const name of SIGNALS)
|
|
126858
127027
|
process.on(name, onSignal);
|
|
127028
|
+
try {
|
|
127029
|
+
return await body((cleanup) => {
|
|
127030
|
+
cleanups.add(cleanup);
|
|
127031
|
+
return () => void cleanups.delete(cleanup);
|
|
127032
|
+
});
|
|
127033
|
+
} finally {
|
|
127034
|
+
for (const name of SIGNALS)
|
|
127035
|
+
process.off(name, onSignal);
|
|
127036
|
+
}
|
|
127037
|
+
}
|
|
127038
|
+
async function withDetachedWorktree(deps, input, rev, cleanupExtra, register, use) {
|
|
127039
|
+
const worktree = await mkdtemp6(path275.join(tmpdir12(), WORKTREE_PREFIX));
|
|
127040
|
+
const cleanup = async () => {
|
|
127041
|
+
await cleanupExtra().catch(() => {});
|
|
127042
|
+
await deps.git(input.projectRoot, ["worktree", "remove", "--force", "--end-of-options", worktree]);
|
|
127043
|
+
await rm19(worktree, { recursive: true, force: true }).catch(() => {});
|
|
127044
|
+
};
|
|
127045
|
+
const unregister = register(cleanup);
|
|
126859
127046
|
try {
|
|
126860
127047
|
const add = ["-c", "core.hooksPath=/dev/null", "-c", "core.fsmonitor=false", "worktree", "add", "--detach", "--force", "--end-of-options", worktree, rev];
|
|
126861
127048
|
let added = await deps.git(input.projectRoot, add);
|
|
@@ -126867,19 +127054,22 @@ async function withDetachedWorktree(deps, input, rev, cleanupExtra, use) {
|
|
|
126867
127054
|
throw new Error(`could not check out ${rev} to build its graph`);
|
|
126868
127055
|
return await use(worktree, await deps.git(worktree, ["rev-parse", "HEAD"]));
|
|
126869
127056
|
} finally {
|
|
126870
|
-
|
|
126871
|
-
|
|
126872
|
-
|
|
127057
|
+
try {
|
|
127058
|
+
await cleanup();
|
|
127059
|
+
} finally {
|
|
127060
|
+
unregister();
|
|
127061
|
+
}
|
|
126873
127062
|
}
|
|
126874
127063
|
}
|
|
126875
|
-
async function buildPackageGraph(deps, input, prHead) {
|
|
127064
|
+
async function buildPackageGraph(deps, input, prHead, register) {
|
|
126876
127065
|
const failed = (error) => ({
|
|
126877
127066
|
status: "failed",
|
|
126878
127067
|
commit: null,
|
|
126879
127068
|
builtAt: null,
|
|
126880
127069
|
nodes: 0,
|
|
126881
127070
|
edges: 0,
|
|
126882
|
-
error
|
|
127071
|
+
error,
|
|
127072
|
+
failedAt: deps.now().toISOString()
|
|
126883
127073
|
});
|
|
126884
127074
|
if (prHead === null)
|
|
126885
127075
|
return failed("no commit to build the graph at");
|
|
@@ -126887,7 +127077,7 @@ async function buildPackageGraph(deps, input, prHead) {
|
|
|
126887
127077
|
const staging = `${dataDir}.${randomUUID35()}`;
|
|
126888
127078
|
const dropStaging = () => rm19(staging, { recursive: true, force: true }).catch(() => {});
|
|
126889
127079
|
try {
|
|
126890
|
-
return await withDetachedWorktree(deps, input, prHead, dropStaging, async (worktree, builtFrom) => {
|
|
127080
|
+
return await withDetachedWorktree(deps, input, prHead, dropStaging, register, async (worktree, builtFrom) => {
|
|
126891
127081
|
if (builtFrom !== prHead) {
|
|
126892
127082
|
return failed(`graph would be built at ${builtFrom ?? "an unknown commit"}, not the PR head ${prHead}`);
|
|
126893
127083
|
}
|
|
@@ -127005,7 +127195,7 @@ async function refreshLayers(input) {
|
|
|
127005
127195
|
base = fromTarget ?? `${prHead}~1`;
|
|
127006
127196
|
}
|
|
127007
127197
|
await writeSelfIgnoringDir(path275.join(input.packageDir, LAYERS_DIR));
|
|
127008
|
-
return withFileLock(layersLockPath(input.packageDir), async () => {
|
|
127198
|
+
return withFileLock(layersLockPath(input.packageDir), () => withSignalCleanup(input.packageDir, async (register) => {
|
|
127009
127199
|
await recoverGraphDirectory(input.packageDir);
|
|
127010
127200
|
await sweepStaleWorktrees(deps, input.projectRoot);
|
|
127011
127201
|
const existing = await readLayers(input.packageDir);
|
|
@@ -127013,8 +127203,10 @@ async function refreshLayers(input) {
|
|
|
127013
127203
|
let graph;
|
|
127014
127204
|
if (input.force !== true && sameHead && await layersCurrent(input.packageDir, existing, prHead)) {
|
|
127015
127205
|
graph = existing.graph;
|
|
127206
|
+
} else if (input.force !== true && sameHead && failedRecently(existing, deps.now())) {
|
|
127207
|
+
graph = existing.graph;
|
|
127016
127208
|
} else {
|
|
127017
|
-
graph = await buildPackageGraph(deps, input, prHead);
|
|
127209
|
+
graph = await buildPackageGraph(deps, input, prHead, register);
|
|
127018
127210
|
if (graph.status === "failed" && sameHead && await layersCurrent(input.packageDir, existing, prHead)) {
|
|
127019
127211
|
graph = existing.graph;
|
|
127020
127212
|
}
|
|
@@ -127032,10 +127224,10 @@ async function refreshLayers(input) {
|
|
|
127032
127224
|
wiki,
|
|
127033
127225
|
memory
|
|
127034
127226
|
};
|
|
127035
|
-
await
|
|
127227
|
+
await deps.writeLayers(layersFilePath(input.packageDir), `${JSON.stringify(layers, null, 2)}
|
|
127036
127228
|
`);
|
|
127037
127229
|
return layers;
|
|
127038
|
-
}, { timeoutMs: LOCK_WAIT_MS, staleMs: LOCK_STALE_MS2 });
|
|
127230
|
+
}), { timeoutMs: LOCK_WAIT_MS, staleMs: LOCK_STALE_MS2 });
|
|
127039
127231
|
}
|
|
127040
127232
|
async function loadPackageGraph(packageDir, prHead) {
|
|
127041
127233
|
if (await layersRefreshInFlight(packageDir)) {
|
|
@@ -127045,7 +127237,8 @@ async function loadPackageGraph(packageDir, prHead) {
|
|
|
127045
127237
|
if (layers === null)
|
|
127046
127238
|
throw new PackageGraphError(`no ${LAYERS_FILE} in ${packageDir}; run \`keryx review layers refresh --package ${shellWord(packageDir)}\``);
|
|
127047
127239
|
if (layers.graph.status !== "ok") {
|
|
127048
|
-
|
|
127240
|
+
const retry = layers.graph.status === "failed" ? `; a failed build is not retried for ${FAILED_RETRY_WINDOW_MS / 60000} minutes at the same head, run \`keryx review layers refresh --package ${shellWord(packageDir)} --force\` to retry now` : "";
|
|
127241
|
+
throw new PackageGraphError(`the package graph is ${layers.graph.status}${layers.graph.error ? `: ${layers.graph.error}` : ""}${retry}`);
|
|
127049
127242
|
}
|
|
127050
127243
|
if (!await graphIntact(packageDir, layers)) {
|
|
127051
127244
|
throw new PackageGraphError(`the package graph directory is missing or does not match ${LAYERS_FILE}; run \`keryx review layers refresh --package ${shellWord(packageDir)}\``);
|
|
@@ -127057,7 +127250,7 @@ async function loadPackageGraph(packageDir, prHead) {
|
|
|
127057
127250
|
const dataDir = packageGraphDir(packageDir);
|
|
127058
127251
|
return { graph: await loadGraph(packageDir, dataDir), dataDir, layers };
|
|
127059
127252
|
}
|
|
127060
|
-
var execFileAsync9, LAYERS_FILE = "layers.json", LAYERS_VERSION = 1, LAYERS_DIR = "layers", GRAPH_DIR = "graph", GRAPH_BUILD_FILE = "build.json", MEMORY_DIR = "memory", LOCK_NAME = "refresh.lock", MANIFEST_FILE2 = "manifest.json", LOCK_STALE_MS2 = 60000, LOCK_WAIT_MS, WORKTREE_PREFIX = "keryx-layers-", WORKTREE_SWEEP_AGE_MS, SIGNALS, WIKI_PATH_PREFIX = ".metaproject/wiki", MEMORY_PATH_PREFIX = ".metaproject/memory", defaultLayerDeps, COMMIT_ID, PackageGraphError;
|
|
127253
|
+
var execFileAsync9, LAYERS_FILE = "layers.json", LAYERS_VERSION = 1, LAYERS_DIR = "layers", GRAPH_DIR = "graph", GRAPH_BUILD_FILE = "build.json", MEMORY_DIR = "memory", LOCK_NAME = "refresh.lock", MANIFEST_FILE2 = "manifest.json", LOCK_STALE_MS2 = 60000, LOCK_WAIT_MS, WORKTREE_PREFIX = "keryx-layers-", WORKTREE_SWEEP_AGE_MS, FAILED_RETRY_WINDOW_MS, SIGNALS, SIGNAL_CLEANUP_MAX_MS = 5000, WIKI_PATH_PREFIX = ".metaproject/wiki", MEMORY_PATH_PREFIX = ".metaproject/memory", defaultLayerDeps, COMMIT_ID, PackageGraphError;
|
|
127061
127254
|
var init_layers = __esm(() => {
|
|
127062
127255
|
init_build();
|
|
127063
127256
|
init_fs();
|
|
@@ -127071,6 +127264,7 @@ var init_layers = __esm(() => {
|
|
|
127071
127264
|
execFileAsync9 = promisify9(execFile11);
|
|
127072
127265
|
LOCK_WAIT_MS = 16 * 60000;
|
|
127073
127266
|
WORKTREE_SWEEP_AGE_MS = 30 * 60000;
|
|
127267
|
+
FAILED_RETRY_WINDOW_MS = 5 * 60000;
|
|
127074
127268
|
SIGNALS = ["SIGINT", "SIGTERM"];
|
|
127075
127269
|
defaultLayerDeps = {
|
|
127076
127270
|
async git(cwd, args) {
|
|
@@ -127090,6 +127284,7 @@ var init_layers = __esm(() => {
|
|
|
127090
127284
|
async indexMemory(cwd, indexDir) {
|
|
127091
127285
|
return createMemoryService().index({ cwd, embeddings: false, indexDir });
|
|
127092
127286
|
},
|
|
127287
|
+
writeLayers: writeFileAtomic,
|
|
127093
127288
|
now: () => new Date
|
|
127094
127289
|
};
|
|
127095
127290
|
COMMIT_ID = /^[0-9a-f]{7,64}$/i;
|
|
@@ -127119,7 +127314,7 @@ function layersSummaryLine(layers) {
|
|
|
127119
127314
|
function layersDetailLines(layers) {
|
|
127120
127315
|
const lines = [];
|
|
127121
127316
|
const graph = layers.graph;
|
|
127122
|
-
lines.push(graph.status === "ok" ? `graph: ok @${shortSha(graph.commit)} = ${graphHeadState(layers)} (PR head ${shortSha(layers.prHead)}), ${graph.nodes} nodes, ${graph.edges} edges, built ${graph.builtAt ?? "?"}` : `graph: ${graph.status}${graph.error ? ` \u2014 ${graph.error}` : ""}`);
|
|
127317
|
+
lines.push(graph.status === "ok" ? `graph: ok @${shortSha(graph.commit)} = ${graphHeadState(layers)} (PR head ${shortSha(layers.prHead)}), ${graph.nodes} nodes, ${graph.edges} edges, built ${graph.builtAt ?? "?"}` : `graph: ${graph.status}${graph.error ? ` \u2014 ${graph.error}` : ""}${graph.status === "failed" ? " (not retried automatically for a few minutes at the same head; refresh --force retries now)" : ""}`);
|
|
127123
127318
|
const wiki = layers.wiki;
|
|
127124
127319
|
lines.push(`wiki: ${wiki.status} @${shortSha(wiki.commit)}, divergence ${divergenceText(wiki.divergence)}, ${wiki.pagesRefreshed} page(s) ${wiki.written ? "refreshed" : "would refresh (dry run)"}, ` + `${wiki.pagesStaleProse === null ? "stale prose unknown" : `${wiki.pagesStaleProse} page(s) with stale prose`}${wiki.error ? ` \u2014 ${wiki.error}` : ""}`);
|
|
127125
127320
|
const memory = layers.memory;
|
|
@@ -127458,7 +127653,7 @@ var init_review_panel = __esm(() => {
|
|
|
127458
127653
|
// src/tui/review-layers-actions.ts
|
|
127459
127654
|
import { execFile as execFile12 } from "child_process";
|
|
127460
127655
|
function layersRefreshArgv(entry, packageDir) {
|
|
127461
|
-
return [...SAFE_BUN_SPAWN_ARGS, entry, "review", "layers", "refresh", "--package", packageDir, "--json"];
|
|
127656
|
+
return [...SAFE_BUN_SPAWN_ARGS, entry, "review", "layers", "refresh", "--package", packageDir, "--force", "--json"];
|
|
127462
127657
|
}
|
|
127463
127658
|
function killLiveChildren() {
|
|
127464
127659
|
for (const child of liveChildren)
|
|
@@ -127474,8 +127669,14 @@ function makeLayersRefreshRunner(entry, timeoutMs = REFRESH_TIMEOUT_MS) {
|
|
|
127474
127669
|
exitHookInstalled = true;
|
|
127475
127670
|
process.once("exit", killLiveChildren);
|
|
127476
127671
|
}
|
|
127672
|
+
const startedAt = Date.now();
|
|
127477
127673
|
const child = execFile12(process.execPath, layersRefreshArgv(entry, packageDir), { cwd: projectRoot, timeout: timeoutMs, maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
127478
127674
|
liveChildren.delete(child);
|
|
127675
|
+
if (error?.killed === true || /maxBuffer/i.test(error?.message ?? "")) {
|
|
127676
|
+
const timedOut = error?.killed === true && Date.now() - startedAt >= timeoutMs - 100;
|
|
127677
|
+
reject(new Error(timedOut ? `the layers refresh did not finish within ${Math.round(timeoutMs / 1000)}s and was stopped` : "the layers refresh printed more output than the limit allows and was stopped"));
|
|
127678
|
+
return;
|
|
127679
|
+
}
|
|
127479
127680
|
try {
|
|
127480
127681
|
resolve(JSON.parse(stdout));
|
|
127481
127682
|
} catch {
|
|
@@ -137684,7 +137885,7 @@ function protectJsonIds(text) {
|
|
|
137684
137885
|
const head = text.trim();
|
|
137685
137886
|
if (head.length === 0 || head[0] !== "[" && head[0] !== "{")
|
|
137686
137887
|
return;
|
|
137687
|
-
if (text.includes(
|
|
137888
|
+
if (text.includes(PLACEHOLDER2))
|
|
137688
137889
|
return;
|
|
137689
137890
|
try {
|
|
137690
137891
|
JSON.parse(head);
|
|
@@ -137694,7 +137895,7 @@ function protectJsonIds(text) {
|
|
|
137694
137895
|
const hidden = [];
|
|
137695
137896
|
const hide = (value) => {
|
|
137696
137897
|
hidden.push(value);
|
|
137697
|
-
return `${
|
|
137898
|
+
return `${PLACEHOLDER2}${letters(hidden.length - 1)}x`;
|
|
137698
137899
|
};
|
|
137699
137900
|
let out = "";
|
|
137700
137901
|
let i = 0;
|
|
@@ -137724,13 +137925,13 @@ function protectJsonIds(text) {
|
|
|
137724
137925
|
}
|
|
137725
137926
|
return {
|
|
137726
137927
|
text: out,
|
|
137727
|
-
restore: (scrubbed) => scrubbed.replace(new RegExp(`${
|
|
137928
|
+
restore: (scrubbed) => scrubbed.replace(new RegExp(`${PLACEHOLDER2}([a-j]+)x`, "g"), (whole, index) => {
|
|
137728
137929
|
const at = Number(index.replace(/[a-j]/g, (c) => String(c.charCodeAt(0) - 97)));
|
|
137729
137930
|
return hidden[at] ?? whole;
|
|
137730
137931
|
})
|
|
137731
137932
|
};
|
|
137732
137933
|
}
|
|
137733
|
-
var
|
|
137934
|
+
var PLACEHOLDER2 = "kxjsonid", GITHUB_URL_PREFIX, GITHUB_URL_REST, SENSITIVE_KEY, NUMBER2;
|
|
137734
137935
|
var init_granted_json_ids = __esm(() => {
|
|
137735
137936
|
GITHUB_URL_PREFIX = /^https:\/\/(?:api\.)?github\.com\/(?:repos\/)?[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+\//;
|
|
137736
137937
|
GITHUB_URL_REST = /^(?:actions\/runs|pull|issues)\/\d+(?:\/[A-Za-z0-9_-]+)*(?:#[A-Za-z_-]+-?\d+)?$/;
|
|
@@ -181120,7 +181321,7 @@ var init_retired_rules = __esm(() => {
|
|
|
181120
181321
|
});
|
|
181121
181322
|
|
|
181122
181323
|
// src/gdskills/guarded-fs-ops.ts
|
|
181123
|
-
import { cp as cp2, lstat as
|
|
181324
|
+
import { cp as cp2, lstat as lstat18, readFile as readFile171, readdir as readdir59, realpath as realpath25, unlink as unlink3 } from "fs/promises";
|
|
181124
181325
|
import { existsSync as existsSync63 } from "fs";
|
|
181125
181326
|
import { createHash as createHash84 } from "crypto";
|
|
181126
181327
|
import path340 from "path";
|
|
@@ -181211,7 +181412,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink3 })
|
|
|
181211
181412
|
continue;
|
|
181212
181413
|
const installedPath = path340.join(installedDir, entryName);
|
|
181213
181414
|
try {
|
|
181214
|
-
const stats = await
|
|
181415
|
+
const stats = await lstat18(installedPath);
|
|
181215
181416
|
if (!stats.isFile()) {
|
|
181216
181417
|
outcomes.push({ path: installedPath, action: "kept-not-regular-file" });
|
|
181217
181418
|
continue;
|
|
@@ -181230,7 +181431,7 @@ async function removeStaleRuntimeBuilds(skillsRoot, fsOps = { unlink: unlink3 })
|
|
|
181230
181431
|
}
|
|
181231
181432
|
async function resolveSweepableDir(dir) {
|
|
181232
181433
|
try {
|
|
181233
|
-
const stats = await
|
|
181434
|
+
const stats = await lstat18(dir);
|
|
181234
181435
|
if (stats.isSymbolicLink() || !stats.isDirectory()) {
|
|
181235
181436
|
return {
|
|
181236
181437
|
kind: "blocked",
|
|
@@ -181305,7 +181506,7 @@ async function removeUnmodifiedRetiredRules(rulesTarget, fsOps = { unlink: unlin
|
|
|
181305
181506
|
const installedPath = path340.join(rulesTarget, retired.fileName);
|
|
181306
181507
|
let stage = "lstat";
|
|
181307
181508
|
try {
|
|
181308
|
-
const stats = await
|
|
181509
|
+
const stats = await lstat18(installedPath);
|
|
181309
181510
|
if (!stats.isFile()) {
|
|
181310
181511
|
outcomes.push({ fileName: retired.fileName, action: "kept-not-regular-file" });
|
|
181311
181512
|
continue;
|
|
@@ -181376,7 +181577,7 @@ var init_guarded_fs_ops = __esm(() => {
|
|
|
181376
181577
|
});
|
|
181377
181578
|
|
|
181378
181579
|
// src/gdskills/install.ts
|
|
181379
|
-
import { lstat as
|
|
181580
|
+
import { lstat as lstat19, readFile as readFile172, readlink as readlink2 } from "fs/promises";
|
|
181380
181581
|
import { existsSync as existsSync64 } from "fs";
|
|
181381
181582
|
import path341 from "path";
|
|
181382
181583
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
@@ -181489,7 +181690,7 @@ async function installBundledSharedSkills(skillsRoot, metaprojectRoot) {
|
|
|
181489
181690
|
async function checkInstallDestination(destination, displayPath) {
|
|
181490
181691
|
let stats;
|
|
181491
181692
|
try {
|
|
181492
|
-
stats = await
|
|
181693
|
+
stats = await lstat19(destination);
|
|
181493
181694
|
} catch (error) {
|
|
181494
181695
|
if (isErrnoException(error) && error.code === "ENOENT") {
|
|
181495
181696
|
return { usable: true };
|
|
@@ -181572,7 +181773,7 @@ async function installContracts(contractsRoot, metaprojectRoot) {
|
|
|
181572
181773
|
async function checkInstallFile(destination, displayPath) {
|
|
181573
181774
|
let stats;
|
|
181574
181775
|
try {
|
|
181575
|
-
stats = await
|
|
181776
|
+
stats = await lstat19(destination);
|
|
181576
181777
|
} catch (error) {
|
|
181577
181778
|
if (isMissingPathError(error)) {
|
|
181578
181779
|
return { usable: true };
|
|
@@ -189768,7 +189969,7 @@ var init_plan = __esm(() => {
|
|
|
189768
189969
|
});
|
|
189769
189970
|
|
|
189770
189971
|
// src/gdskills/manifest/state.ts
|
|
189771
|
-
import { lstat as
|
|
189972
|
+
import { lstat as lstat20, readFile as readFile182 } from "fs/promises";
|
|
189772
189973
|
import path363 from "path";
|
|
189773
189974
|
function isValidInstallState(value) {
|
|
189774
189975
|
return validateAgainstSchemaObject(INSTALL_STATE_SCHEMA, value).valid;
|
|
@@ -189811,7 +190012,7 @@ async function hasSymlinkSegment(repoRoot, posixRel) {
|
|
|
189811
190012
|
current = path363.join(current, segment);
|
|
189812
190013
|
let stat;
|
|
189813
190014
|
try {
|
|
189814
|
-
stat = await
|
|
190015
|
+
stat = await lstat20(current);
|
|
189815
190016
|
} catch {
|
|
189816
190017
|
return false;
|
|
189817
190018
|
}
|
|
@@ -190390,7 +190591,7 @@ var init_sync2 = __esm(() => {
|
|
|
190390
190591
|
});
|
|
190391
190592
|
|
|
190392
190593
|
// src/gdskills/import-skills.ts
|
|
190393
|
-
import { lstat as
|
|
190594
|
+
import { lstat as lstat21, readdir as readdir64, readFile as readFile185, stat as stat41 } from "fs/promises";
|
|
190394
190595
|
import { homedir as homedir15 } from "os";
|
|
190395
190596
|
import path368 from "path";
|
|
190396
190597
|
function emptyOnlyError(label) {
|
|
@@ -191401,7 +191602,7 @@ async function decideReferencedRules(options, sources, refusedPackages) {
|
|
|
191401
191602
|
async function listTemplateFiles(sourcePath) {
|
|
191402
191603
|
const root = path368.join(path368.dirname(sourcePath), TEMPLATES_DIR);
|
|
191403
191604
|
try {
|
|
191404
|
-
if (!(await
|
|
191605
|
+
if (!(await lstat21(root)).isDirectory())
|
|
191405
191606
|
return [];
|
|
191406
191607
|
} catch {
|
|
191407
191608
|
return [];
|
|
@@ -191823,7 +192024,7 @@ var init_import_skills = __esm(() => {
|
|
|
191823
192024
|
});
|
|
191824
192025
|
|
|
191825
192026
|
// src/gdskills/remove-skill.ts
|
|
191826
|
-
import { lstat as
|
|
192027
|
+
import { lstat as lstat22, readdir as readdir65, readFile as readFile186, realpath as realpath26 } from "fs/promises";
|
|
191827
192028
|
import path369 from "path";
|
|
191828
192029
|
async function removeProjectSkill(projectRoot, options) {
|
|
191829
192030
|
if (!await pathExists(path369.join(projectRoot, ".metaproject"))) {
|
|
@@ -192018,7 +192219,7 @@ function normalizeSkillKey(input) {
|
|
|
192018
192219
|
}
|
|
192019
192220
|
async function lstatOrMissing(target) {
|
|
192020
192221
|
try {
|
|
192021
|
-
return await
|
|
192222
|
+
return await lstat22(target);
|
|
192022
192223
|
} catch (error) {
|
|
192023
192224
|
if (isMissingPathError(error))
|
|
192024
192225
|
return;
|
|
@@ -192083,7 +192284,7 @@ async function locateRewritable(root, relativePath) {
|
|
|
192083
192284
|
if (absolute === root || !isPathInside(root, absolute)) {
|
|
192084
192285
|
throw new Error(`${LABEL}: ${relativePath} resolves to ${absolute}, outside the project. Refusing to rewrite a file the project does not own. ${UNCHANGED}`);
|
|
192085
192286
|
}
|
|
192086
|
-
if (!(await
|
|
192287
|
+
if (!(await lstat22(absolute)).isFile()) {
|
|
192087
192288
|
throw new Error(`${LABEL}: ${relativePath} is not a regular file. ${UNCHANGED}`);
|
|
192088
192289
|
}
|
|
192089
192290
|
const resolvedPath = toPosix(path369.relative(root, absolute));
|
|
@@ -197025,7 +197226,7 @@ function buildLedger(input) {
|
|
|
197025
197226
|
continue;
|
|
197026
197227
|
}
|
|
197027
197228
|
obligations.push({ ...base, status: "unresolved" });
|
|
197028
|
-
gaps.push({ detail: `${id}: raw finding is not in the canonical findings and has no closure`, next: `add a disposition {"id":"${id}","status":"finding|refuted|out-of-scope","evidence":\u2026,"reason":\u2026} to --dispositions` });
|
|
197229
|
+
gaps.push({ detail: `${id}: raw finding is not in the canonical findings and has no closure`, next: `add a disposition {"id":"${id}","status":"finding|refuted|out-of-scope|narrowed","evidence":\u2026,"reason":\u2026,"remains":\u2026 (narrowed only)} to --dispositions` });
|
|
197029
197230
|
}
|
|
197030
197231
|
}
|
|
197031
197232
|
for (const given of input.dispositions) {
|
|
@@ -197053,7 +197254,9 @@ function closeWith(given, canonical) {
|
|
|
197053
197254
|
return;
|
|
197054
197255
|
if (given.status === "finding" && !(text4(given.finding) && canonical.filter((f) => f.id === given.finding || f.global_id === given.finding).length === 1))
|
|
197055
197256
|
return;
|
|
197056
|
-
|
|
197257
|
+
if (given.status === "narrowed" && !text4(given.remains))
|
|
197258
|
+
return;
|
|
197259
|
+
return { status: given.status, evidence: given.evidence, reason: given.reason, ...given.status === "finding" ? { finding: given.finding } : {}, ...given.status === "narrowed" ? { remains: given.remains } : {} };
|
|
197057
197260
|
}
|
|
197058
197261
|
async function ledgerBlockers(ledger, canonical, options) {
|
|
197059
197262
|
const dispatch = dispatchErrors(ledger.dispatch);
|
|
@@ -197069,7 +197272,7 @@ var init_ledger2 = __esm(() => {
|
|
|
197069
197272
|
init_slice();
|
|
197070
197273
|
init_waves();
|
|
197071
197274
|
FINISHED = new Set(["DONE", "DONE_WITH_CONCERNS"]);
|
|
197072
|
-
CLOSED = new Set(["finding", "refuted", "out-of-scope"]);
|
|
197275
|
+
CLOSED = new Set(["finding", "refuted", "out-of-scope", "narrowed"]);
|
|
197073
197276
|
OBJECT_START = /\{\s*["}]/y;
|
|
197074
197277
|
RESULT_KEYS = new Set(["status", "reviewer", "findings"]);
|
|
197075
197278
|
KEY_COLON = /\s*:/y;
|
|
@@ -197101,8 +197304,8 @@ needs_context and every slice its path gate requires covered by its dispatched p
|
|
|
197101
197304
|
complete run (source-only, executionRequired false); anything else \u2014 NEEDS_CONTEXT, BLOCKED,
|
|
197102
197305
|
INCOMPLETE, missing slices, no result \u2014 stays incomplete or not-run, never declared complete.
|
|
197103
197306
|
Each raw finding becomes an obligation, closed only by a matching canonical finding or a
|
|
197104
|
-
valid --dispositions entry ({id, status finding|refuted|out-of-scope, evidence, reason,
|
|
197105
|
-
finding?}; default: the obligations already in research.json). scopeReviewed and rawReconciled
|
|
197307
|
+
valid --dispositions entry ({id, status finding|refuted|out-of-scope|narrowed, evidence, reason,
|
|
197308
|
+
finding?, remains? \u2014 required for narrowed: what of the finding still stands}; default: the obligations already in research.json). scopeReviewed and rawReconciled
|
|
197106
197309
|
are true only when established.
|
|
197107
197310
|
|
|
197108
197311
|
Slices are dispatched in waves bounded by the manifest's wave budget; a reviewer that covered
|
|
@@ -197348,24 +197551,36 @@ import path380 from "path";
|
|
|
197348
197551
|
function sizeOf(block) {
|
|
197349
197552
|
return Buffer.byteLength(JSON.stringify(block), "utf8");
|
|
197350
197553
|
}
|
|
197554
|
+
function reasonOf(error) {
|
|
197555
|
+
const first = (error instanceof Error ? error.message : String(error)).split(`
|
|
197556
|
+
`)[0] ?? "unknown error";
|
|
197557
|
+
return first.length > MAX_REASON_CHARS ? `${first.slice(0, MAX_REASON_CHARS)}\u2026` : first;
|
|
197558
|
+
}
|
|
197351
197559
|
function unique2(values) {
|
|
197352
197560
|
return [...new Set(values)].sort();
|
|
197353
197561
|
}
|
|
197354
197562
|
function affectedFor(graph, files, depth) {
|
|
197355
197563
|
const own = new Set(files);
|
|
197356
197564
|
const found = new Set;
|
|
197565
|
+
const failures = [];
|
|
197357
197566
|
for (const file of files) {
|
|
197358
197567
|
let result;
|
|
197359
197568
|
try {
|
|
197360
197569
|
result = computeAffected(graph, file, { depth });
|
|
197361
|
-
} catch {
|
|
197570
|
+
} catch (error) {
|
|
197571
|
+
failures.push(`${file}: ${reasonOf(error)}`);
|
|
197362
197572
|
continue;
|
|
197363
197573
|
}
|
|
197364
197574
|
for (const dependent of result.dependents)
|
|
197365
197575
|
if (!own.has(dependent))
|
|
197366
197576
|
found.add(dependent);
|
|
197367
197577
|
}
|
|
197368
|
-
|
|
197578
|
+
const named = failures.slice(0, MAX_NAMED_FAILURES).join("; ");
|
|
197579
|
+
const more = failures.length > MAX_NAMED_FAILURES ? ` (and ${failures.length - MAX_NAMED_FAILURES} more)` : "";
|
|
197580
|
+
return {
|
|
197581
|
+
files: [...found].sort(),
|
|
197582
|
+
unavailable: failures.length === 0 ? null : `not computed for ${failures.length} of ${files.length} file(s) \u2014 ${named}${more}`
|
|
197583
|
+
};
|
|
197369
197584
|
}
|
|
197370
197585
|
function symbolsFor(graph, files) {
|
|
197371
197586
|
const symbols = graph.symbols ?? [];
|
|
@@ -197415,29 +197630,34 @@ function relatedTestsFor(graph, files, affected) {
|
|
|
197415
197630
|
found.add(dependent);
|
|
197416
197631
|
return [...found].sort();
|
|
197417
197632
|
}
|
|
197418
|
-
async function wikiFor(projectRoot, graph, files) {
|
|
197633
|
+
async function wikiFor(projectRoot, graph, files, readPage = (absolutePath) => readFile196(absolutePath, "utf8")) {
|
|
197419
197634
|
let pages;
|
|
197420
197635
|
try {
|
|
197421
197636
|
pages = await collectPages(projectRoot);
|
|
197422
|
-
} catch {
|
|
197423
|
-
return [];
|
|
197637
|
+
} catch (error) {
|
|
197638
|
+
return { items: [], unavailable: `the wiki could not be read \u2014 ${reasonOf(error)}` };
|
|
197424
197639
|
}
|
|
197425
197640
|
const knownPaths = new Set(graph.nodes.map((node) => node.path));
|
|
197426
197641
|
const keyFilesIndex = computeModuleKeyFiles(graph);
|
|
197427
197642
|
const wanted = new Set(files);
|
|
197428
197643
|
const out = [];
|
|
197644
|
+
const unreadable = [];
|
|
197429
197645
|
for (const page of pages) {
|
|
197430
197646
|
let content;
|
|
197431
197647
|
try {
|
|
197432
|
-
content = await
|
|
197648
|
+
content = await readPage(page.absolutePath);
|
|
197433
197649
|
} catch {
|
|
197650
|
+
unreadable.push(page.relativePath);
|
|
197434
197651
|
continue;
|
|
197435
197652
|
}
|
|
197436
197653
|
const set = resolveDescribeSet({ page, content, knownPaths, keyFilesIndex });
|
|
197437
197654
|
if (set.paths.some((candidate) => wanted.has(candidate)))
|
|
197438
197655
|
out.push({ page: page.relativePath, title: page.title });
|
|
197439
197656
|
}
|
|
197440
|
-
return
|
|
197657
|
+
return {
|
|
197658
|
+
items: out.sort((a, b) => a.page.localeCompare(b.page)),
|
|
197659
|
+
unavailable: unreadable.length === 0 ? null : `${unreadable.length} of ${pages.length} wiki page(s) could not be read \u2014 ${unreadable.slice(0, MAX_NAMED_FAILURES).join(", ")}`
|
|
197660
|
+
};
|
|
197441
197661
|
}
|
|
197442
197662
|
function modulesOf(file) {
|
|
197443
197663
|
const parts = file.split("/").filter(Boolean);
|
|
@@ -197451,8 +197671,8 @@ async function memoryFor(projectRoot, files) {
|
|
|
197451
197671
|
let entries;
|
|
197452
197672
|
try {
|
|
197453
197673
|
entries = await collectEntries(projectRoot);
|
|
197454
|
-
} catch {
|
|
197455
|
-
return [];
|
|
197674
|
+
} catch (error) {
|
|
197675
|
+
return { items: [], unavailable: `the memory could not be read \u2014 ${reasonOf(error)}` };
|
|
197456
197676
|
}
|
|
197457
197677
|
const modules = new Set(files.flatMap(modulesOf));
|
|
197458
197678
|
const wanted = new Set(files);
|
|
@@ -197466,7 +197686,7 @@ async function memoryFor(projectRoot, files) {
|
|
|
197466
197686
|
if (byModule || byFile)
|
|
197467
197687
|
out.push({ path: entry.relativePath, title: entry.title, status: entry.status, module: module ?? "" });
|
|
197468
197688
|
}
|
|
197469
|
-
return out.sort((a, b) => a.path.localeCompare(b.path));
|
|
197689
|
+
return { items: out.sort((a, b) => a.path.localeCompare(b.path)), unavailable: null };
|
|
197470
197690
|
}
|
|
197471
197691
|
function fetchCommand(section, files, packageDir, projectRoot) {
|
|
197472
197692
|
const first = files[0] ?? "<file>";
|
|
@@ -197496,18 +197716,28 @@ async function buildLayersBlock(input) {
|
|
|
197496
197716
|
const { graph, layers } = await loadPackageGraph(input.packageDir);
|
|
197497
197717
|
const base = async (depth) => {
|
|
197498
197718
|
const affected = affectedFor(graph, files, depth);
|
|
197719
|
+
const wiki = await wikiFor(input.projectRoot, graph, files, input.readPage);
|
|
197720
|
+
const memory = await memoryFor(input.projectRoot, files);
|
|
197721
|
+
const unavailable = [];
|
|
197722
|
+
if (affected.unavailable !== null)
|
|
197723
|
+
unavailable.push({ section: "affected", reason: affected.unavailable });
|
|
197724
|
+
if (wiki.unavailable !== null)
|
|
197725
|
+
unavailable.push({ section: "wiki", reason: wiki.unavailable });
|
|
197726
|
+
if (memory.unavailable !== null)
|
|
197727
|
+
unavailable.push({ section: "memory", reason: memory.unavailable });
|
|
197499
197728
|
return {
|
|
197500
197729
|
version: LAYERS_BLOCK_VERSION,
|
|
197501
197730
|
files,
|
|
197502
197731
|
prHead: layers.prHead,
|
|
197503
197732
|
graphCommit: layers.graphCommit,
|
|
197504
197733
|
maxBytes,
|
|
197505
|
-
affected: { depth, files: affected },
|
|
197734
|
+
affected: { depth, files: affected.files },
|
|
197506
197735
|
symbols: symbolsFor(graph, files),
|
|
197507
|
-
relatedTests: relatedTestsFor(graph, files, affected),
|
|
197508
|
-
wiki:
|
|
197509
|
-
memory:
|
|
197510
|
-
truncated: []
|
|
197736
|
+
relatedTests: relatedTestsFor(graph, files, affected.files),
|
|
197737
|
+
wiki: wiki.items,
|
|
197738
|
+
memory: memory.items,
|
|
197739
|
+
truncated: [],
|
|
197740
|
+
unavailable
|
|
197511
197741
|
};
|
|
197512
197742
|
};
|
|
197513
197743
|
let block = await base(1);
|
|
@@ -197577,11 +197807,13 @@ function layersBlockLines(block) {
|
|
|
197577
197807
|
` wiki pages: ${block.wiki.length}`,
|
|
197578
197808
|
` memory entries: ${block.memory.length}`
|
|
197579
197809
|
];
|
|
197810
|
+
for (const gap of block.unavailable)
|
|
197811
|
+
lines.push(` ${gap.section}: unavailable (${gap.reason})`);
|
|
197580
197812
|
for (const cut of block.truncated)
|
|
197581
197813
|
lines.push(` truncated ${cut.section}: ${cut.shown}/${cut.total} \u2014 ${cut.command}`);
|
|
197582
197814
|
return lines;
|
|
197583
197815
|
}
|
|
197584
|
-
var LAYERS_BLOCK_VERSION = 1, DEFAULT_LAYERS_BLOCK_MAX_BYTES = 8192, MAX_SYMBOLS_PER_FILE = 6, MAX_NEIGHBOURS_PER_SYMBOL = 4, MAX_BLOCK_FILES = 100, LAYERS_BLOCK_SECTIONS, CONTROL_CHARACTER;
|
|
197816
|
+
var LAYERS_BLOCK_VERSION = 1, DEFAULT_LAYERS_BLOCK_MAX_BYTES = 8192, MAX_SYMBOLS_PER_FILE = 6, MAX_NEIGHBOURS_PER_SYMBOL = 4, MAX_REASON_CHARS = 160, MAX_BLOCK_FILES = 100, LAYERS_BLOCK_SECTIONS, MAX_NAMED_FAILURES = 3, CONTROL_CHARACTER;
|
|
197585
197817
|
var init_layers_payload = __esm(() => {
|
|
197586
197818
|
init_affected();
|
|
197587
197819
|
init_shell_quote();
|
|
@@ -197602,7 +197834,7 @@ function printLayersHelp() {
|
|
|
197602
197834
|
|
|
197603
197835
|
keryx review layers refresh --package <dir> [--head <sha>] [--base <ref>] [--write-wiki] [--force] [--json]
|
|
197604
197836
|
keryx review layers status --package <dir> [--json]
|
|
197605
|
-
keryx review layers block --package <dir> --files <a,b,...> [--max-bytes <n>] [--json]
|
|
197837
|
+
keryx review layers block --package <dir> --files <a,b,...> [--max-bytes <n>] [--force] [--json]
|
|
197606
197838
|
|
|
197607
197839
|
\`block\` prints the bounded \`layers\` block a reviewer payload carries for those files: files
|
|
197608
197840
|
the graph says are affected (depth 1, then 2 while under the cap), symbol neighbours, related
|
|
@@ -197618,7 +197850,8 @@ The graph is built at the PR head into <dir>/layers/graph; the tracked graph art
|
|
|
197618
197850
|
the working tree are not touched. The wiki and the memory are read from the project's
|
|
197619
197851
|
working copy, and layers.json marks how far that is from the PR head. The wiki refresh is a
|
|
197620
197852
|
dry run unless --write-wiki is given. A graph that is already built at this head and intact is
|
|
197621
|
-
kept (the wiki and the memory are still refreshed); --force rebuilds it
|
|
197853
|
+
kept (the wiki and the memory are still refreshed); --force rebuilds it. A graph that failed at
|
|
197854
|
+
this head is not rebuilt for 5 minutes after the failure; --force or a new head rebuilds it.`);
|
|
197622
197855
|
}
|
|
197623
197856
|
function wantsHelp2(args) {
|
|
197624
197857
|
return args.includes("--help") || args.includes("-h");
|
|
@@ -197649,7 +197882,8 @@ async function ensurePackageLayers(packageDir, options = {}) {
|
|
|
197649
197882
|
projectRoot,
|
|
197650
197883
|
packageDir,
|
|
197651
197884
|
...head === undefined ? {} : { prHead: head },
|
|
197652
|
-
...options.base === undefined ? {} : { base: options.base }
|
|
197885
|
+
...options.base === undefined ? {} : { base: options.base },
|
|
197886
|
+
...options.force === true ? { force: true } : {}
|
|
197653
197887
|
});
|
|
197654
197888
|
}
|
|
197655
197889
|
function print(layers, json) {
|
|
@@ -197708,7 +197942,7 @@ async function runLayers(args, deps) {
|
|
|
197708
197942
|
if (maxBytes !== undefined && (!Number.isInteger(maxBytes) || maxBytes < 256))
|
|
197709
197943
|
throw new Error("--max-bytes must be an integer of at least 256; a block with every list empty needs more than that, and the refusal names how much.");
|
|
197710
197944
|
const projectRoot = resolveProjectRoot2(process.cwd());
|
|
197711
|
-
await ensurePackageLayers(packageDir, { projectRoot });
|
|
197945
|
+
await ensurePackageLayers(packageDir, { projectRoot, force: rest.includes("--force") });
|
|
197712
197946
|
const block = await buildLayersBlock({ projectRoot, packageDir, files, ...maxBytes === undefined ? {} : { maxBytes } });
|
|
197713
197947
|
console.log(rest.includes("--json") ? JSON.stringify(block, null, 2) : layersBlockLines(block).join(`
|
|
197714
197948
|
`));
|
|
@@ -197727,7 +197961,7 @@ var init_review_layers = __esm(() => {
|
|
|
197727
197961
|
init_layers_payload();
|
|
197728
197962
|
LAYERS_REFRESH_FLAGS = ["--package", "--head", "--base", "--write-wiki", "--force", "--json", "--help", "-h"];
|
|
197729
197963
|
LAYERS_STATUS_FLAGS = ["--package", "--json", "--help", "-h"];
|
|
197730
|
-
LAYERS_BLOCK_FLAGS = ["--package", "--files", "--max-bytes", "--json", "--help", "-h"];
|
|
197964
|
+
LAYERS_BLOCK_FLAGS = ["--package", "--files", "--max-bytes", "--force", "--json", "--help", "-h"];
|
|
197731
197965
|
});
|
|
197732
197966
|
|
|
197733
197967
|
// src/commands/review-chunks.ts
|
|
@@ -199687,6 +199921,11 @@ function rejectUnknownFlags(args, allowed, usage) {
|
|
|
199687
199921
|
throw new Error(`Unknown option${unknown.length > 1 ? "s" : ""} for \`keryx review ${usage}\`: ${[...new Set(unknown)].join(", ")}. Accepted: ${allowed.join(", ")}. Refused rather than ignored \u2014 a flag that is silently dropped writes nothing and reports success.`);
|
|
199688
199922
|
}
|
|
199689
199923
|
}
|
|
199924
|
+
async function trackerPrLookup(ref) {
|
|
199925
|
+
if (!await githubAdapter.detect())
|
|
199926
|
+
return null;
|
|
199927
|
+
return githubAdapter.prStatus(ref);
|
|
199928
|
+
}
|
|
199690
199929
|
async function reviewCommand(args, seams) {
|
|
199691
199930
|
const command = args[0];
|
|
199692
199931
|
if (!command || command === "--help" || command === "-h") {
|
|
@@ -201667,8 +201906,11 @@ async function runComplete3(args, seams) {
|
|
|
201667
201906
|
manifest,
|
|
201668
201907
|
reviewNotes,
|
|
201669
201908
|
dispositions: tally,
|
|
201670
|
-
coverageWaiver
|
|
201671
|
-
|
|
201909
|
+
coverageWaiver,
|
|
201910
|
+
warnings
|
|
201911
|
+
} = await completeManagedReview(process.cwd(), ref, { dispositions, prLookup: seams?.prLookup ?? trackerPrLookup, ...waiver?.value === undefined ? {} : { waiveCoverage: waiver.value } });
|
|
201912
|
+
for (const warning of warnings)
|
|
201913
|
+
console.error(`warning: ${warning}`);
|
|
201672
201914
|
if (coverageWaiver !== undefined) {
|
|
201673
201915
|
const shown = coverageWaiver.files.slice(0, FIRST_FILES).join(", ");
|
|
201674
201916
|
console.log(`coverage waived by the operator (${coverageWaiver.time}): "${coverageWaiver.operatorQuote}"; ${coverageWaiver.files.length} file(s) not reviewed: ${shown}${coverageWaiver.files.length > FIRST_FILES ? ", \u2026" : ""}`);
|
|
@@ -201927,7 +202169,7 @@ Usage:
|
|
|
201927
202169
|
[--package <review-dir>] [--json | --brief] [--out <file>]
|
|
201928
202170
|
keryx review layers refresh --package <review-dir> [--head <sha>] [--base <ref>] [--write-wiki] [--force] [--json]
|
|
201929
202171
|
keryx review layers status --package <review-dir> [--json]
|
|
201930
|
-
keryx review layers block --package <review-dir> --files a,b [--max-bytes <n>] [--json]
|
|
202172
|
+
keryx review layers block --package <review-dir> --files a,b [--max-bytes <n>] [--force] [--json]
|
|
201931
202173
|
Graph, wiki and memory for one review; no model is called. An intact graph at
|
|
201932
202174
|
the same head is reused (--force rebuilds); a second refresh waits up to 16 min.
|
|
201933
202175
|
keryx review chunks plan|estimate|next|record|merge --package <review-dir>
|
|
@@ -208130,7 +208372,7 @@ var init_tools3 = __esm(() => {
|
|
|
208130
208372
|
|
|
208131
208373
|
// src/mcp/resources.ts
|
|
208132
208374
|
import path396 from "path";
|
|
208133
|
-
import { lstat as
|
|
208375
|
+
import { lstat as lstat23, readdir as readdir76, realpath as realpath28, stat as stat44 } from "fs/promises";
|
|
208134
208376
|
function mimeForPath(filePath) {
|
|
208135
208377
|
if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
|
|
208136
208378
|
return "application/json";
|
|
@@ -208153,7 +208395,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
|
|
|
208153
208395
|
if (!await pathExists(root)) {
|
|
208154
208396
|
return [];
|
|
208155
208397
|
}
|
|
208156
|
-
const ownerInfo = await
|
|
208398
|
+
const ownerInfo = await lstat23(ownerRoot).catch(() => null);
|
|
208157
208399
|
if (!ownerInfo || !ownerInfo.isDirectory() || ownerInfo.isSymbolicLink())
|
|
208158
208400
|
return [];
|
|
208159
208401
|
const ownerReal = await realpath28(ownerRoot).catch(() => null);
|
|
@@ -224400,7 +224642,7 @@ function describeAvailability(entry, availability) {
|
|
|
224400
224642
|
const range = verdict.state === "in-range" ? "within the recorded range" : verdict.state === "below-min" ? `below the recorded minimum ${verdict.min} \u2014 parsing may drift` : verdict.state === "above-max" ? `above the recorded maximum ${verdict.max} \u2014 parsing may drift` : "version banner not recognised";
|
|
224401
224643
|
return `installed, ${version} (${range}); login not verified \u2014 keryx cannot know`;
|
|
224402
224644
|
}
|
|
224403
|
-
function
|
|
224645
|
+
function marker2(availability) {
|
|
224404
224646
|
if (availability.state === "available")
|
|
224405
224647
|
return style.cyan("\u25CF");
|
|
224406
224648
|
if (availability.state === "binary-missing")
|
|
@@ -224440,7 +224682,7 @@ function renderExternalAgents(rows, capability) {
|
|
|
224440
224682
|
lines.push(capability.available ? "capability: enabled" : `capability: unavailable \u2014 ${capability.reason ?? "no reason given"}`);
|
|
224441
224683
|
lines.push("");
|
|
224442
224684
|
for (const row of rows) {
|
|
224443
|
-
lines.push(` ${
|
|
224685
|
+
lines.push(` ${marker2(row.availability)} ${row.entry.id} ${row.entry.label}`);
|
|
224444
224686
|
lines.push(` ${describeAvailability(row.entry, row.availability)}`);
|
|
224445
224687
|
lines.push(` transport: ${transportOf(row.entry)} sandbox: ${row.entry.sandboxModes.join(", ")} streaming: ${row.entry.streamingInput} ` + `resumable: ${row.entry.resumable} reports cost: ${row.entry.reportsCost}`);
|
|
224446
224688
|
}
|
|
@@ -230794,7 +231036,7 @@ var init_types13 = __esm(() => {
|
|
|
230794
231036
|
});
|
|
230795
231037
|
|
|
230796
231038
|
// src/bundle/paths.ts
|
|
230797
|
-
import { lstat as
|
|
231039
|
+
import { lstat as lstat24, readFile as readFile214 } from "fs/promises";
|
|
230798
231040
|
import path423 from "path";
|
|
230799
231041
|
function scopeRoot(scope, ctx) {
|
|
230800
231042
|
if (scope === "user") {
|
|
@@ -230988,7 +231230,7 @@ async function resolveContainedPathForWrite(root, relPath) {
|
|
|
230988
231230
|
async function refuseSymlinkChain(root, relPath) {
|
|
230989
231231
|
const rootResolved = path423.resolve(root);
|
|
230990
231232
|
try {
|
|
230991
|
-
const rootStat = await
|
|
231233
|
+
const rootStat = await lstat24(rootResolved);
|
|
230992
231234
|
if (rootStat.isSymbolicLink()) {
|
|
230993
231235
|
return {
|
|
230994
231236
|
ok: false,
|
|
@@ -231001,7 +231243,7 @@ async function refuseSymlinkChain(root, relPath) {
|
|
|
231001
231243
|
for (const segment of segments) {
|
|
231002
231244
|
current = path423.join(current, segment);
|
|
231003
231245
|
try {
|
|
231004
|
-
const stat = await
|
|
231246
|
+
const stat = await lstat24(current);
|
|
231005
231247
|
if (stat.isSymbolicLink()) {
|
|
231006
231248
|
return {
|
|
231007
231249
|
ok: false,
|
|
@@ -231051,7 +231293,7 @@ var init_paths5 = __esm(() => {
|
|
|
231051
231293
|
});
|
|
231052
231294
|
|
|
231053
231295
|
// src/bundle/archive.ts
|
|
231054
|
-
import { readdir as readdir79, readFile as readFile215, lstat as
|
|
231296
|
+
import { readdir as readdir79, readFile as readFile215, lstat as lstat25 } from "fs/promises";
|
|
231055
231297
|
import path424 from "path";
|
|
231056
231298
|
import { gzipSync, gunzipSync } from "zlib";
|
|
231057
231299
|
function resolveLimits(limits) {
|
|
@@ -231259,7 +231501,7 @@ async function openBundle(bundlePath, limits) {
|
|
|
231259
231501
|
const resolved = resolveLimits(limits);
|
|
231260
231502
|
let stat;
|
|
231261
231503
|
try {
|
|
231262
|
-
stat = await
|
|
231504
|
+
stat = await lstat25(bundlePath);
|
|
231263
231505
|
} catch {
|
|
231264
231506
|
return { ok: false, refusal: { reason: BUNDLE_REFUSAL.notABundle, message: `no such path: ${bundlePath}` } };
|
|
231265
231507
|
}
|
|
@@ -231273,7 +231515,7 @@ async function openBundle(bundlePath, limits) {
|
|
|
231273
231515
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
231274
231516
|
const abs = path424.join(dir, entry.name);
|
|
231275
231517
|
const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
|
|
231276
|
-
const entryStat = await
|
|
231518
|
+
const entryStat = await lstat25(abs);
|
|
231277
231519
|
if (entryStat.isSymbolicLink()) {
|
|
231278
231520
|
return { reason: BUNDLE_REFUSAL.symlinkRefused, path: rel, message: `${rel} is a symlink; bundles may not contain symlinks` };
|
|
231279
231521
|
}
|
|
@@ -232148,7 +232390,7 @@ var init_learned_pattern_schema = __esm(() => {
|
|
|
232148
232390
|
|
|
232149
232391
|
// src/bundle/export.ts
|
|
232150
232392
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
232151
|
-
import { mkdir as mkdir89, readdir as readdir80, readFile as readFile216, writeFile as writeFile63, lstat as
|
|
232393
|
+
import { mkdir as mkdir89, readdir as readdir80, readFile as readFile216, writeFile as writeFile63, lstat as lstat26 } from "fs/promises";
|
|
232152
232394
|
import { existsSync as existsSync78 } from "fs";
|
|
232153
232395
|
import path425 from "path";
|
|
232154
232396
|
function globToRegExp6(glob) {
|
|
@@ -232235,7 +232477,7 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
|
|
|
232235
232477
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
232236
232478
|
const abs = path425.join(dir, entry.name);
|
|
232237
232479
|
const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
|
|
232238
|
-
const st = await
|
|
232480
|
+
const st = await lstat26(abs);
|
|
232239
232481
|
if (st.isSymbolicLink()) {
|
|
232240
232482
|
skipped.push({ path: rel, reason: "symlink" });
|
|
232241
232483
|
continue;
|
|
@@ -232283,7 +232525,7 @@ async function collectAgents(root, skipped) {
|
|
|
232283
232525
|
const candidates = [];
|
|
232284
232526
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
232285
232527
|
const abs = path425.join(base, entry.name);
|
|
232286
|
-
const st = await
|
|
232528
|
+
const st = await lstat26(abs);
|
|
232287
232529
|
if (st.isSymbolicLink()) {
|
|
232288
232530
|
skipped.push({ path: `agents/${entry.name}`, reason: "symlink" });
|
|
232289
232531
|
continue;
|
|
@@ -232308,7 +232550,7 @@ async function collectMemory(root, skipped) {
|
|
|
232308
232550
|
}
|
|
232309
232551
|
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
232310
232552
|
const abs = path425.join(base, entry.name);
|
|
232311
|
-
const st = await
|
|
232553
|
+
const st = await lstat26(abs);
|
|
232312
232554
|
if (st.isSymbolicLink()) {
|
|
232313
232555
|
skipped.push({ path: `memory/${folder}/${entry.name}`, reason: "symlink" });
|
|
232314
232556
|
continue;
|
|
@@ -232324,7 +232566,7 @@ async function collectHookConfig(root) {
|
|
|
232324
232566
|
const abs = path425.join(root, "hooks.json");
|
|
232325
232567
|
if (!existsSync78(abs))
|
|
232326
232568
|
return [];
|
|
232327
|
-
const st = await
|
|
232569
|
+
const st = await lstat26(abs);
|
|
232328
232570
|
if (st.isSymbolicLink() || !st.isFile())
|
|
232329
232571
|
return [];
|
|
232330
232572
|
return [{ bundlePath: "hooks.json", kind: "hook-config", absolutePath: abs }];
|
|
@@ -232705,7 +232947,7 @@ var init_applied_state = __esm(() => {
|
|
|
232705
232947
|
});
|
|
232706
232948
|
|
|
232707
232949
|
// src/lib/private-dir.ts
|
|
232708
|
-
import { lstat as
|
|
232950
|
+
import { lstat as lstat27, mkdir as mkdir90, open as open12, readFile as readFile218 } from "fs/promises";
|
|
232709
232951
|
import path427 from "path";
|
|
232710
232952
|
async function checkPrivateDirGitignore(dir, root) {
|
|
232711
232953
|
if (root !== undefined) {
|
|
@@ -232724,7 +232966,7 @@ async function checkPrivateDirGitignore(dir, root) {
|
|
|
232724
232966
|
}
|
|
232725
232967
|
let dirStats;
|
|
232726
232968
|
try {
|
|
232727
|
-
dirStats = await
|
|
232969
|
+
dirStats = await lstat27(dir);
|
|
232728
232970
|
} catch (error) {
|
|
232729
232971
|
if (isNotFound(error)) {
|
|
232730
232972
|
return { ok: true, action: "create" };
|
|
@@ -232745,7 +232987,7 @@ async function checkPrivateDirGitignore(dir, root) {
|
|
|
232745
232987
|
const gitignorePath = path427.join(dir, ".gitignore");
|
|
232746
232988
|
let stats;
|
|
232747
232989
|
try {
|
|
232748
|
-
stats = await
|
|
232990
|
+
stats = await lstat27(gitignorePath);
|
|
232749
232991
|
} catch (error) {
|
|
232750
232992
|
if (isNotFound(error)) {
|
|
232751
232993
|
return { ok: true, action: "create" };
|
package/dist/core.js
CHANGED
|
@@ -28739,10 +28739,12 @@ function researchErrors(value, findings) {
|
|
|
28739
28739
|
ids.add(id);
|
|
28740
28740
|
if (!nonempty(o.source) || !nonempty(o.question))
|
|
28741
28741
|
errors.push(`${id}: source and question are required`);
|
|
28742
|
-
if (!["finding", "refuted", "out-of-scope"].includes(String(o.status)))
|
|
28742
|
+
if (!["finding", "refuted", "out-of-scope", "narrowed"].includes(String(o.status)))
|
|
28743
28743
|
errors.push(`${id}: unresolved research (${String(o.status)})`);
|
|
28744
28744
|
if (!nonempty(o.evidence) || !nonempty(o.reason))
|
|
28745
28745
|
errors.push(`${id}: closure needs evidence and reason`);
|
|
28746
|
+
if (o.status === "narrowed" && !nonempty(o.remains))
|
|
28747
|
+
errors.push(`${id}: narrowed needs a "what remains" text`);
|
|
28746
28748
|
if (o.status === "finding") {
|
|
28747
28749
|
const matches = findings.filter((f) => f && typeof f === "object" && (f.id === o.finding || f.global_id === o.finding));
|
|
28748
28750
|
if (!nonempty(o.finding) || matches.length !== 1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrciphersmith/keryx",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.114",
|
|
4
4
|
"description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"publishConfig": {
|
|
@@ -207,8 +207,7 @@ keryx review complete <review-id-or-path>
|
|
|
207
207
|
[--finding <id> --disposition <state> --evidence <ref>]...
|
|
208
208
|
```
|
|
209
209
|
|
|
210
|
-
**An unrecognised option is refused, not ignored** — a misspelled flag exits
|
|
211
|
-
non-zero rather than printing `status: closed` and writing nothing at all.
|
|
210
|
+
**An unrecognised option is refused, not ignored** — a misspelled flag exits non-zero rather than printing `status: closed` and writing nothing at all.
|
|
212
211
|
|
|
213
212
|
`--verifications` takes what `review-verifier` returned. `--scope` takes the
|
|
214
213
|
whole `--json` output of `keryx review scope`, so the package records what the
|
|
@@ -234,9 +233,7 @@ Two channels, and both work:
|
|
|
234
233
|
it: it is copied into the package it screened and removed from the slot, so a
|
|
235
234
|
later round cannot be screened against a set nobody recomputed.
|
|
236
235
|
|
|
237
|
-
Do **not** write the record inside the package directory before the ingest. A
|
|
238
|
-
round that names no `--review-id` has not allocated that directory yet, and
|
|
239
|
-
creating it makes the ingest take the next free name instead.
|
|
236
|
+
Do **not** write the record inside the package directory before the ingest. A round that names no `--review-id` has not allocated that directory yet, and creating it makes the ingest take the next free name instead.
|
|
240
237
|
|
|
241
238
|
`--refuted` takes the findings this round **raised and then dismissed**, in the
|
|
242
239
|
same finding shape, each carrying `disposition: {state, evidence}` with one of
|
|
@@ -252,9 +249,12 @@ Everything except `unknown` must cite where the outcome is written down — the
|
|
|
252
249
|
commit, the test, the decision. A recorded state and its citation cannot be
|
|
253
250
|
overwritten by a later close; record a correction as a new round.
|
|
254
251
|
|
|
255
|
-
|
|
256
|
-
`
|
|
257
|
-
|
|
252
|
+
`review complete` on a PR-bound package refuses a merged PR or a moved head unless `report.md`'s first line names the reviewed SHA and says so; a package with no PR binding is unaffected.
|
|
253
|
+
`review complete` refuses a report with no `Evidence level:` line (`static only — did not run: <what>` or `ran — <what>`); a package ingested before the field existed only warns.
|
|
254
|
+
`review ingest` refuses an internal finding whose `quote` is not at its `path` at the reviewed SHA, and adds a `citation not found` line, never a deletion, to a rebuttal citing a rule or document that is not in the tree.
|
|
255
|
+
A finding may close as `narrowed` (research obligation `"status":"narrowed","remains":"<what remains>"`); it counts as closed only with that text, and the report keeps it under "Refuted and narrowed".
|
|
256
|
+
|
|
257
|
+
**Closing a fix round without dispositions leaves every finding reading `unknown`.** That is not "the reviewers were right"; it is "nobody wrote down what happened", and it is the single reason the precision figure cannot be read.
|
|
258
258
|
|
|
259
259
|
Managed modes:
|
|
260
260
|
|
|
@@ -259,6 +259,19 @@
|
|
|
259
259
|
},
|
|
260
260
|
"additionalProperties": false
|
|
261
261
|
}
|
|
262
|
+
},
|
|
263
|
+
"unavailable": {
|
|
264
|
+
"type": "array",
|
|
265
|
+
"description": "Sections that could not be read, with the reason. An empty section that is not listed here was read and held nothing.",
|
|
266
|
+
"items": {
|
|
267
|
+
"type": "object",
|
|
268
|
+
"required": ["section", "reason"],
|
|
269
|
+
"properties": {
|
|
270
|
+
"section": { "enum": ["affected", "wiki", "memory"] },
|
|
271
|
+
"reason": { "type": "string" }
|
|
272
|
+
},
|
|
273
|
+
"additionalProperties": false
|
|
274
|
+
}
|
|
262
275
|
}
|
|
263
276
|
},
|
|
264
277
|
"additionalProperties": false
|
|
@@ -34,8 +34,10 @@ what a reader must act on; everything else is folded.
|
|
|
34
34
|
|
|
35
35
|
```markdown
|
|
36
36
|
<!-- slot:pre-verdict -->
|
|
37
|
+
<only when the PR merged or its head moved: "Reviewed `<sha>` — the PR has since merged / moved on to `<new sha>`." as the very first line>
|
|
37
38
|
**Changes requested — 1 Blocker, 2 Major, 4 Minor, 1 question.** <what holds, and where the risk is — one sentence.>
|
|
38
39
|
Reviewed `<head>` against `<base>` · round N · <files> files, +<add>/−<del> · CI <green | red: check names>.
|
|
40
|
+
Evidence level: <static only — did not run: tests, build | ran — the commands that ran>
|
|
39
41
|
<!-- slot:after-verdict -->
|
|
40
42
|
|
|
41
43
|
### Prior findings (fix rounds only)
|
|
@@ -43,6 +45,7 @@ Reviewed `<head>` against `<base>` · round N · <files> files, +<add>/−<del>
|
|
|
43
45
|
### Major
|
|
44
46
|
### Minor
|
|
45
47
|
### Questions
|
|
48
|
+
### Refuted and narrowed (only when a finding was dismissed or narrowed)
|
|
46
49
|
|
|
47
50
|
<details><summary><b>Pre-existing (not blocking)</b></summary> … </details>
|
|
48
51
|
<details><summary><b>Scope</b></summary> … </details> (only when a pr-process note exists)
|
|
@@ -68,6 +71,25 @@ author answer, rendered under Questions, not as a finding.
|
|
|
68
71
|
The second line is the context line: head, base, round, size, CI. If head moved
|
|
69
72
|
during the round, add `· unchecked: <commits>`.
|
|
70
73
|
|
|
74
|
+
The third line is the **evidence level** and is required: `Evidence level: static
|
|
75
|
+
only — did not run: <what>` when nothing was executed, or `Evidence level: ran —
|
|
76
|
+
<what ran>`. `keryx review complete` refuses a report without it.
|
|
77
|
+
|
|
78
|
+
When the PR was merged or its head moved after the review, the report's first line
|
|
79
|
+
says so and names the reviewed SHA, or `keryx review complete` refuses.
|
|
80
|
+
|
|
81
|
+
### Refuted and narrowed
|
|
82
|
+
|
|
83
|
+
One line per finding that did not stand as reported, kept with its evidence so a
|
|
84
|
+
reader sees what was claimed and why it was set aside:
|
|
85
|
+
|
|
86
|
+
```markdown
|
|
87
|
+
- **F-007. Dismissed** — <the author's rebuttal, quoted>. <citation not found: …> where the cited rule is not in the tree.
|
|
88
|
+
- **F-009. Narrowed** — <what the finding claimed>. **Remains:** <what is still open>. Evidence: <what was run>.
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
A narrowed finding counts as closed only with its "Remains" text.
|
|
92
|
+
|
|
71
93
|
## One finding
|
|
72
94
|
|
|
73
95
|
Blocker and Major as a paragraph, visible:
|