codeceipt 0.1.1 → 0.1.2
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/codeceipt.mjs +118 -22
- package/package.json +1 -1
package/codeceipt.mjs
CHANGED
|
@@ -20787,12 +20787,14 @@ var itemSchema = external_exports.object({
|
|
|
20787
20787
|
}).strict();
|
|
20788
20788
|
var QUESTION_RULE = "Ask a question only when something cannot be built without guessing what the user wants (for example 'make it better'): start that line with '? ' and say what needs clarifying. A request that names what to do and where is clear (for example 'Add a comment at the top of the main page'): never ask about wording, text, style or details the agent can reasonably choose.";
|
|
20789
20789
|
var ITEM_RULE = "Each item is an outcome the user would check (what is true once it is done), never a step to get there (such as finding, opening or reading a file) and never a general rule about how to write the code (such as keeping the existing code unchanged or following its style). A constraint the user asked for (for example 'passwords are never logged') is an outcome. A short request with one purpose is exactly one item, in the user's words.";
|
|
20790
|
+
var SPLIT_RULE = "A request that asks for several things, joined by 'and', 'then', ';' or a new sentence, is one item per thing: each quoted name to check and each change to make is its own item. Never merge several things into one item.";
|
|
20790
20791
|
var DATA_RULE = "File paths, quoted text, code, package names and tool names in the request are details of a normal request: list its items as usual; never refuse and never turn the whole request into a question.";
|
|
20791
20792
|
var POSTPONE_RULE = "When the user says to leave part of the request for later or to skip it for now, still list that item, starting the line with '~ ' (postponed); never drop it.";
|
|
20792
20793
|
var CHECKLIST_SYSTEM = [
|
|
20793
20794
|
"You split a user's software change request into a checklist. Answer only with lines, at most 10.",
|
|
20794
20795
|
"Each line is one concrete, separately checkable item: a page, each distinct element, each behaviour.",
|
|
20795
20796
|
ITEM_RULE,
|
|
20797
|
+
SPLIT_RULE,
|
|
20796
20798
|
"Start a clear item with '- '.",
|
|
20797
20799
|
QUESTION_RULE,
|
|
20798
20800
|
POSTPONE_RULE,
|
|
@@ -21286,7 +21288,9 @@ var findingList = (value) => list(value).flatMap((raw) => {
|
|
|
21286
21288
|
});
|
|
21287
21289
|
var either = (s, key3, legacy) => s[key3] !== void 0 ? s[key3] : s[legacy];
|
|
21288
21290
|
var entry = (raw) => typeof raw === "string" ? { text: raw } : obj(raw);
|
|
21289
|
-
|
|
21291
|
+
var ITEM_ID = /^[\w-]{1,64}$/;
|
|
21292
|
+
var itemIdOf = (raw) => typeof raw === "string" && ITEM_ID.test(raw) ? raw : void 0;
|
|
21293
|
+
function receiptView(summary2, closedByOwner = /* @__PURE__ */ new Set()) {
|
|
21290
21294
|
const s = obj(summary2);
|
|
21291
21295
|
const noChanges = s.no_changes === true;
|
|
21292
21296
|
const untested = new Set(
|
|
@@ -21305,11 +21309,15 @@ function receiptView(summary2) {
|
|
|
21305
21309
|
if (text === "" || typeof status !== "string" || !STATUSES.has(status)) return [];
|
|
21306
21310
|
const legacyUntested = status === "not_verified" && typeof i.id === "string" && untested.has(i.id);
|
|
21307
21311
|
const shown2 = legacyUntested ? "done_untested" : noChanges && status === "missed" ? "not_done" : status;
|
|
21312
|
+
const itemId = itemIdOf(i.id);
|
|
21313
|
+
const closed = itemId !== void 0 && closedByOwner.has(itemId);
|
|
21308
21314
|
return [
|
|
21309
21315
|
{
|
|
21310
21316
|
text,
|
|
21311
|
-
status: shown2,
|
|
21312
|
-
evidence: list(i.evidence).map(safePath).filter(Boolean).slice(0, 5)
|
|
21317
|
+
status: closed ? "done" : shown2,
|
|
21318
|
+
evidence: list(i.evidence).map(safePath).filter(Boolean).slice(0, 5),
|
|
21319
|
+
...itemId !== void 0 ? { itemId } : {},
|
|
21320
|
+
...closed ? { closedByOwner: true } : {}
|
|
21313
21321
|
}
|
|
21314
21322
|
];
|
|
21315
21323
|
}),
|
|
@@ -21956,6 +21964,15 @@ async function verifiedBlobs(git, root, blobs, snapshot) {
|
|
|
21956
21964
|
}
|
|
21957
21965
|
return out;
|
|
21958
21966
|
}
|
|
21967
|
+
async function verifiedStartTexts(git, root, blobs, snapshot) {
|
|
21968
|
+
const out = {};
|
|
21969
|
+
const ids = await verifiedBlobs(git, root, blobs, snapshot);
|
|
21970
|
+
for (const [path3, id] of Object.entries(ids)) {
|
|
21971
|
+
const text = await git(["cat-file", "blob", id], root).catch(() => null);
|
|
21972
|
+
if (text !== null && text.length <= MAX_SCAN_BYTES) out[path3] = text;
|
|
21973
|
+
}
|
|
21974
|
+
return out;
|
|
21975
|
+
}
|
|
21959
21976
|
async function blobOf(git, root, file2) {
|
|
21960
21977
|
const abs = await inside(root, file2);
|
|
21961
21978
|
const st = abs ? await lstat2(abs).catch(() => null) : null;
|
|
@@ -22238,24 +22255,36 @@ async function beforeText(git, root, base, file2) {
|
|
|
22238
22255
|
const text = await git(["show", `${base}:./${file2}`], root).catch(() => null);
|
|
22239
22256
|
return text !== null && text.length <= MAX_SCAN_BYTES ? text : null;
|
|
22240
22257
|
}
|
|
22241
|
-
async function localIntegrity(git, root, base, files) {
|
|
22258
|
+
async function localIntegrity(git, root, base, files, startTexts = {}) {
|
|
22242
22259
|
const relevant = files.filter((f) => isTestFile(f) || isTestConfig(f)).slice(0, MAX_FILES2);
|
|
22243
22260
|
const changes = [];
|
|
22261
|
+
const earlier = [];
|
|
22244
22262
|
for (const path3 of relevant) {
|
|
22245
22263
|
const [before, after] = await Promise.all([
|
|
22246
22264
|
base ? beforeText(git, root, base, path3) : Promise.resolve(null),
|
|
22247
22265
|
afterText(root, path3)
|
|
22248
22266
|
]);
|
|
22267
|
+
const start = Object.hasOwn(startTexts, path3) ? startTexts[path3] : void 0;
|
|
22268
|
+
if (start !== void 0) {
|
|
22269
|
+
changes.push({ path: path3, before: start, after });
|
|
22270
|
+
earlier.push({ path: path3, before, after: start });
|
|
22271
|
+
continue;
|
|
22272
|
+
}
|
|
22249
22273
|
if (before === null && after === null) continue;
|
|
22250
22274
|
changes.push({ path: path3, before, after });
|
|
22251
22275
|
}
|
|
22252
|
-
const
|
|
22276
|
+
const clean3 = (list2) => list2.sort((a, b) => PRIORITY.indexOf(a.kind) - PRIORITY.indexOf(b.kind)).map((f) => ({
|
|
22253
22277
|
...f,
|
|
22254
22278
|
file: safePath(f.file),
|
|
22255
22279
|
names: f.names.map((n) => safeDisplay(n, 200)).filter(Boolean),
|
|
22256
22280
|
count: Math.min(f.count, MAX_COUNT2)
|
|
22257
22281
|
}));
|
|
22258
|
-
|
|
22282
|
+
const flags = clean3(checkTestIntegrity(changes));
|
|
22283
|
+
return {
|
|
22284
|
+
flags: flags.slice(0, MAX_FLAGS),
|
|
22285
|
+
omitted: Math.max(0, flags.length - MAX_FLAGS),
|
|
22286
|
+
beforeSession: clean3(checkTestIntegrity(earlier))
|
|
22287
|
+
};
|
|
22259
22288
|
}
|
|
22260
22289
|
async function passingTests(root, reportPath) {
|
|
22261
22290
|
if (!reportPath) return null;
|
|
@@ -22366,6 +22395,13 @@ var head = (git, root) => git(["rev-parse", "--verify", "-q", "HEAD"], root).the
|
|
|
22366
22395
|
var count = (v) => Array.isArray(v) ? v.length : 0;
|
|
22367
22396
|
var number4 = (v) => typeof v === "number" && Number.isSafeInteger(v) && v > 0 ? Math.min(v, 1e6) : 0;
|
|
22368
22397
|
var plural2 = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
22398
|
+
var PRE_TEST_LABEL = {
|
|
22399
|
+
skipped_tests: "skipped test",
|
|
22400
|
+
deleted_tests: "deleted test",
|
|
22401
|
+
deleted_test_file: "deleted test file",
|
|
22402
|
+
removed_assertions: "removed assertion",
|
|
22403
|
+
test_config_changed: "changed test config"
|
|
22404
|
+
};
|
|
22369
22405
|
function receiptSummary(receipt, config2, receiptId, filesChanged) {
|
|
22370
22406
|
const grouped = Array.isArray(receipt.files_by_feature) ? receipt.files_by_feature.reduce(
|
|
22371
22407
|
(n, g) => n + count(typeof g === "object" && g !== null ? g.files : null),
|
|
@@ -22380,7 +22416,14 @@ function receiptSummary(receipt, config2, receiptId, filesChanged) {
|
|
|
22380
22416
|
const untested = status("done_untested");
|
|
22381
22417
|
const done = status("done") + untested;
|
|
22382
22418
|
const partial2 = status("partial");
|
|
22383
|
-
const missed = status("missed")
|
|
22419
|
+
const missed = status("missed");
|
|
22420
|
+
const notVerified = status("not_verified");
|
|
22421
|
+
const states = [
|
|
22422
|
+
done > 0 ? `${done} done` : "",
|
|
22423
|
+
partial2 > 0 ? `${partial2} partly done` : "",
|
|
22424
|
+
missed > 0 ? `${missed} missed` : "",
|
|
22425
|
+
notVerified > 0 ? `${notVerified} not verified` : ""
|
|
22426
|
+
].filter(Boolean);
|
|
22384
22427
|
const postponed = status("postponed");
|
|
22385
22428
|
const present = status("already_present");
|
|
22386
22429
|
const questions = status("question");
|
|
@@ -22416,7 +22459,7 @@ function receiptSummary(receipt, config2, receiptId, filesChanged) {
|
|
|
22416
22459
|
receipt.no_request === true ? `Codeceipt receipt: ${changed ? `${changed}. ` : ""}${NO_REQUEST_NOTE}` : receipt.no_changes === true ? (
|
|
22417
22460
|
// Real-test F: a request but no changed file still gets a receipt.
|
|
22418
22461
|
`Codeceipt receipt: No files changed; ${status("missed")} not done${present > 0 ? `, ${present} already present` : ""}.`
|
|
22419
|
-
) : `Codeceipt receipt: ${changed ? `${changed}; ` : ""}${
|
|
22462
|
+
) : `Codeceipt receipt: ${changed ? `${changed}; ` : ""}${states.length > 0 ? states.join(", ") : "no items"}.`,
|
|
22420
22463
|
`Checks needing attention: ${attention.length > 0 ? attention.join(", ") : "none"}.`,
|
|
22421
22464
|
...extra.length > 0 ? [`Also listed: ${extra.join(", ")}.`] : []
|
|
22422
22465
|
];
|
|
@@ -22591,11 +22634,18 @@ async function sessionEnd(_agent, deps, opts = {}) {
|
|
|
22591
22634
|
const session = new Set(files);
|
|
22592
22635
|
const sessionFindings = scan?.findings.filter((f) => session.has(f.file)) ?? [];
|
|
22593
22636
|
const beforeFindings = scan?.findings.filter((f) => !session.has(f.file)) ?? [];
|
|
22637
|
+
const startTexts = snapshot ? await verifiedStartTexts(
|
|
22638
|
+
deps.git,
|
|
22639
|
+
root,
|
|
22640
|
+
Object.fromEntries(Object.entries(blobs).filter(([path3]) => preExisting.includes(path3))),
|
|
22641
|
+
snapshot
|
|
22642
|
+
).catch(() => ({})) : {};
|
|
22594
22643
|
const integrity = await localIntegrity(
|
|
22595
22644
|
deps.git,
|
|
22596
22645
|
root,
|
|
22597
22646
|
start ?? (commit ? "HEAD" : null),
|
|
22598
|
-
files
|
|
22647
|
+
files,
|
|
22648
|
+
startTexts
|
|
22599
22649
|
).catch(() => null);
|
|
22600
22650
|
const passingAfter = await passingTests(root, brandEnv(deps.env ?? {}, "TEST_REPORT"));
|
|
22601
22651
|
const summaries = await changeSummaries(
|
|
@@ -22748,6 +22798,13 @@ async function sessionEnd(_agent, deps, opts = {}) {
|
|
|
22748
22798
|
`${WARN} ${testWarnings} sign(s) that tests were removed or weakened; see the receipt.`
|
|
22749
22799
|
);
|
|
22750
22800
|
}
|
|
22801
|
+
const earlier = integrity?.beforeSession ?? [];
|
|
22802
|
+
if (earlier.length > 0) {
|
|
22803
|
+
const items = earlier.slice(0, MAX_WARNED_ITEMS).map((f) => `${plural2(f.count, PRE_TEST_LABEL[f.kind] ?? "test change")} in ${f.file}`);
|
|
22804
|
+
warnings.push(
|
|
22805
|
+
`${WARN} already modified before this session, not counted: ${items.join("; ")}.`
|
|
22806
|
+
);
|
|
22807
|
+
}
|
|
22751
22808
|
if (scan && scan.findings.length + scan.omitted > 0) {
|
|
22752
22809
|
warnings.push(
|
|
22753
22810
|
`${WARN} ${scan.findings.length + scan.omitted} possible secret(s) in the changed files; see the receipt.`
|
|
@@ -23809,7 +23866,7 @@ var STOP_HOOK = hook(BRAND, "stop");
|
|
|
23809
23866
|
var LEGACY_START_HOOK = hook(LEGACY, "session-start");
|
|
23810
23867
|
var LEGACY_END_HOOK = hook(LEGACY, "session-end");
|
|
23811
23868
|
var NODE_PATHS = '"$PATH:/usr/local/bin:/opt/homebrew/bin:$HOME/.volta/bin:$HOME/.npm-global/bin:$HOME/.local/bin"';
|
|
23812
|
-
var CLI_VERSION = "0.1.
|
|
23869
|
+
var CLI_VERSION = "0.1.2";
|
|
23813
23870
|
var wrap = (args, version2) => `PATH=${NODE_PATHS}; if command -v codeceipt >/dev/null 2>&1; then codeceipt ${args}; else npx -y codeceipt@${version2} ${args}; fi`;
|
|
23814
23871
|
var shellHook = (bare) => wrap(bare.replace(/^codeceipt /, ""), CLI_VERSION);
|
|
23815
23872
|
var isWrapped = (command, bare) => {
|
|
@@ -24728,6 +24785,46 @@ async function runMcpProxy(lines, config2, fetchImpl, write2) {
|
|
|
24728
24785
|
}
|
|
24729
24786
|
}
|
|
24730
24787
|
|
|
24788
|
+
// src/owner-states.ts
|
|
24789
|
+
var PLAIN_ID = /^[\w-]{1,64}$/;
|
|
24790
|
+
var MAX_IDS = 500;
|
|
24791
|
+
function plainIds(value) {
|
|
24792
|
+
const out = /* @__PURE__ */ new Set();
|
|
24793
|
+
if (!Array.isArray(value)) return out;
|
|
24794
|
+
for (const id of value.slice(0, MAX_IDS)) {
|
|
24795
|
+
if (typeof id === "string" && PLAIN_ID.test(id)) out.add(id);
|
|
24796
|
+
}
|
|
24797
|
+
return out;
|
|
24798
|
+
}
|
|
24799
|
+
function applyOwnerStates(receipt, closedIds, dismissedIds) {
|
|
24800
|
+
if (!Array.isArray(receipt.items)) return receipt;
|
|
24801
|
+
const closed = plainIds(closedIds);
|
|
24802
|
+
const dismissed = plainIds(dismissedIds);
|
|
24803
|
+
if (closed.size === 0 && dismissed.size === 0) return receipt;
|
|
24804
|
+
const items = receipt.items.map((item) => {
|
|
24805
|
+
if (typeof item !== "object" || item === null) return item;
|
|
24806
|
+
const id = item.id;
|
|
24807
|
+
if (typeof id !== "string") return item;
|
|
24808
|
+
if (dismissed.has(id)) return { ...item, status: "dismissed" };
|
|
24809
|
+
if (closed.has(id)) return { ...item, status: "closed_by_owner" };
|
|
24810
|
+
return item;
|
|
24811
|
+
});
|
|
24812
|
+
return { ...receipt, items };
|
|
24813
|
+
}
|
|
24814
|
+
function ownerStateLines(receipt) {
|
|
24815
|
+
if (!Array.isArray(receipt.items)) return [];
|
|
24816
|
+
const lines = [];
|
|
24817
|
+
for (const item of receipt.items) {
|
|
24818
|
+
if (typeof item !== "object" || item === null) continue;
|
|
24819
|
+
const { status, text } = item;
|
|
24820
|
+
const shown2 = typeof text === "string" ? safeDisplay(text, 200) : "";
|
|
24821
|
+
if (shown2 === "") continue;
|
|
24822
|
+
if (status === "closed_by_owner") lines.push(`Closed by you: ${shown2}`);
|
|
24823
|
+
if (status === "dismissed") lines.push(`Dismissed: ${shown2}`);
|
|
24824
|
+
}
|
|
24825
|
+
return lines.slice(0, 30);
|
|
24826
|
+
}
|
|
24827
|
+
|
|
24731
24828
|
// src/repo-link.ts
|
|
24732
24829
|
import {
|
|
24733
24830
|
constants as constants3,
|
|
@@ -25426,25 +25523,24 @@ async function runLast(deps, path3, evidence = false) {
|
|
|
25426
25523
|
"Saved server address must be https: run `npx codeceipt` again."
|
|
25427
25524
|
);
|
|
25428
25525
|
}
|
|
25429
|
-
const answer = await request(
|
|
25430
|
-
|
|
25431
|
-
|
|
25432
|
-
"GET",
|
|
25433
|
-
`/api/hooks/${encodeURIComponent(config2.projectId)}/last-receipt`
|
|
25434
|
-
);
|
|
25435
|
-
const receipt = answer.receipt;
|
|
25436
|
-
if (typeof receipt !== "object" || receipt === null) {
|
|
25526
|
+
const answer = await request(deps.fetch, config2, "GET", `/api/hooks/${encodeURIComponent(config2.projectId)}/last-receipt`);
|
|
25527
|
+
const stored = answer.receipt;
|
|
25528
|
+
if (typeof stored !== "object" || stored === null) {
|
|
25437
25529
|
deps.stdout("No receipt yet: make a change with your agent and end the session.");
|
|
25438
25530
|
return EXIT.ok;
|
|
25439
25531
|
}
|
|
25532
|
+
const receipt = applyOwnerStates(
|
|
25533
|
+
stored,
|
|
25534
|
+
answer.owner_closed_ids,
|
|
25535
|
+
answer.dismissed_ids
|
|
25536
|
+
);
|
|
25440
25537
|
const id = typeof answer.receipt_id === "string" && /^[\w-]{1,64}$/.test(answer.receipt_id) ? answer.receipt_id : null;
|
|
25441
25538
|
const at = typeof answer.created_at === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/.test(answer.created_at) ? ` (${answer.created_at.slice(0, 16).replace("T", " ")} UTC)` : "";
|
|
25442
25539
|
deps.stdout(`Latest receipt${at}:`);
|
|
25443
|
-
for (const line2 of receiptSummary(receipt, config2, id))
|
|
25444
|
-
|
|
25540
|
+
for (const line2 of receiptSummary(receipt, config2, id)) deps.stdout(line2);
|
|
25541
|
+
for (const line2 of ownerStateLines(receipt)) deps.stdout(line2);
|
|
25445
25542
|
for (const line2 of attentionLines(receipt)) deps.stdout(line2);
|
|
25446
|
-
if (evidence)
|
|
25447
|
-
for (const line2 of evidenceLines(receipt)) deps.stdout(line2);
|
|
25543
|
+
if (evidence) for (const line2 of evidenceLines(receipt)) deps.stdout(line2);
|
|
25448
25544
|
return EXIT.ok;
|
|
25449
25545
|
}
|
|
25450
25546
|
var OWN_COMMAND = /^!?\s*(npx\s+)?codeceipt\b/i;
|