@codacy/verity-cli 0.35.5 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/bin/verity.js +2244 -218
- package/data/skills/verity-memory/SKILL.md +56 -8
- package/data/skills/verity-reflect/SKILL.md +9 -0
- package/package.json +2 -2
package/bin/verity.js
CHANGED
|
@@ -926,8 +926,8 @@ var require_suggestSimilar = __commonJS2({
|
|
|
926
926
|
if (candidate.length <= 1) return;
|
|
927
927
|
const distance = editDistance(word, candidate);
|
|
928
928
|
const length = Math.max(word.length, candidate.length);
|
|
929
|
-
const
|
|
930
|
-
if (
|
|
929
|
+
const similarity2 = (length - distance) / length;
|
|
930
|
+
if (similarity2 > minSimilarity) {
|
|
931
931
|
if (distance < bestDistance) {
|
|
932
932
|
bestDistance = distance;
|
|
933
933
|
similar = [candidate];
|
|
@@ -14442,11 +14442,61 @@ var {
|
|
|
14442
14442
|
Help
|
|
14443
14443
|
} = import_index.default;
|
|
14444
14444
|
|
|
14445
|
-
// src/
|
|
14446
|
-
var
|
|
14445
|
+
// src/lib/child-process.ts
|
|
14446
|
+
var cp = __toESM2(require("node:child_process"));
|
|
14447
|
+
var import_node_util = require("node:util");
|
|
14448
|
+
function hiddenOptions(options) {
|
|
14449
|
+
return { ...options ?? {}, windowsHide: true };
|
|
14450
|
+
}
|
|
14451
|
+
function argvCall(rest) {
|
|
14452
|
+
let [args2, options, callback] = rest;
|
|
14453
|
+
if (!Array.isArray(args2)) {
|
|
14454
|
+
if (typeof args2 === "function") {
|
|
14455
|
+
callback = args2;
|
|
14456
|
+
options = void 0;
|
|
14457
|
+
} else if (args2 != null && typeof args2 === "object") {
|
|
14458
|
+
callback = options;
|
|
14459
|
+
options = args2;
|
|
14460
|
+
}
|
|
14461
|
+
args2 = [];
|
|
14462
|
+
}
|
|
14463
|
+
if (typeof options === "function") {
|
|
14464
|
+
callback = options;
|
|
14465
|
+
options = void 0;
|
|
14466
|
+
}
|
|
14467
|
+
return { args: args2, options: hiddenOptions(options), callback };
|
|
14468
|
+
}
|
|
14469
|
+
var execSync2 = ((command, options) => cp.execSync(command, hiddenOptions(options)));
|
|
14470
|
+
var execFileSync2 = ((file, ...rest) => {
|
|
14471
|
+
const { args: args2, options } = argvCall(rest);
|
|
14472
|
+
return cp.execFileSync(file, args2, options);
|
|
14473
|
+
});
|
|
14474
|
+
var spawnSync2 = ((command, ...rest) => {
|
|
14475
|
+
const { args: args2, options } = argvCall(rest);
|
|
14476
|
+
return cp.spawnSync(command, args2, options);
|
|
14477
|
+
});
|
|
14478
|
+
var spawn2 = ((command, ...rest) => {
|
|
14479
|
+
const { args: args2, options } = argvCall(rest);
|
|
14480
|
+
return cp.spawn(command, args2, options);
|
|
14481
|
+
});
|
|
14482
|
+
var spawnShown = ((command, ...rest) => {
|
|
14483
|
+
const { args: args2, options } = argvCall(rest);
|
|
14484
|
+
const { windowsHide: _hidden, ...shown } = options;
|
|
14485
|
+
return cp.spawn(command, args2, shown);
|
|
14486
|
+
});
|
|
14487
|
+
var execFile2 = ((file, ...rest) => {
|
|
14488
|
+
const { args: args2, options, callback } = argvCall(rest);
|
|
14489
|
+
return cp.execFile(file, args2, options, callback);
|
|
14490
|
+
});
|
|
14491
|
+
var execFileAsync = (0, import_node_util.promisify)(cp.execFile);
|
|
14492
|
+
Object.defineProperty(execFile2, import_node_util.promisify.custom, {
|
|
14493
|
+
value: (file, ...rest) => {
|
|
14494
|
+
const { args: args2, options } = argvCall(rest);
|
|
14495
|
+
return execFileAsync(file, args2, options);
|
|
14496
|
+
}
|
|
14497
|
+
});
|
|
14447
14498
|
|
|
14448
14499
|
// src/constants.ts
|
|
14449
|
-
var import_node_child_process = require("node:child_process");
|
|
14450
14500
|
var import_node_fs2 = require("node:fs");
|
|
14451
14501
|
var import_node_path2 = require("node:path");
|
|
14452
14502
|
|
|
@@ -14532,7 +14582,7 @@ var _repoRoot = null;
|
|
|
14532
14582
|
function repoRoot() {
|
|
14533
14583
|
if (_repoRoot === null) {
|
|
14534
14584
|
try {
|
|
14535
|
-
_repoRoot = (
|
|
14585
|
+
_repoRoot = execFileSync2("git", ["rev-parse", "--show-toplevel"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
14536
14586
|
} catch {
|
|
14537
14587
|
_repoRoot = process.cwd();
|
|
14538
14588
|
}
|
|
@@ -14547,7 +14597,7 @@ var _mainRoot;
|
|
|
14547
14597
|
function mainWorktreeRoot() {
|
|
14548
14598
|
if (_mainRoot !== void 0) return _mainRoot;
|
|
14549
14599
|
try {
|
|
14550
|
-
const common = (
|
|
14600
|
+
const common = execFileSync2("git", ["rev-parse", "--git-common-dir"], {
|
|
14551
14601
|
encoding: "utf-8",
|
|
14552
14602
|
stdio: ["pipe", "pipe", "pipe"]
|
|
14553
14603
|
}).trim();
|
|
@@ -14824,7 +14874,6 @@ function rotateIfNeeded(file) {
|
|
|
14824
14874
|
// src/lib/credentials.ts
|
|
14825
14875
|
var import_promises = require("node:fs/promises");
|
|
14826
14876
|
var import_node_path3 = require("node:path");
|
|
14827
|
-
var import_node_child_process2 = require("node:child_process");
|
|
14828
14877
|
|
|
14829
14878
|
// src/lib/service-origin.ts
|
|
14830
14879
|
var LEGACY = new URL(LEGACY_PROD_SERVICE_ORIGIN);
|
|
@@ -14860,7 +14909,7 @@ function globalCredentialsPath() {
|
|
|
14860
14909
|
}
|
|
14861
14910
|
function currentRemote() {
|
|
14862
14911
|
try {
|
|
14863
|
-
return (
|
|
14912
|
+
return execFileSync2("git", ["remote", "get-url", "origin"], {
|
|
14864
14913
|
encoding: "utf-8",
|
|
14865
14914
|
stdio: ["pipe", "pipe", "pipe"]
|
|
14866
14915
|
}).trim();
|
|
@@ -15073,15 +15122,64 @@ async function foldLegacyLocalCredential(remoteArg) {
|
|
|
15073
15122
|
}
|
|
15074
15123
|
|
|
15075
15124
|
// src/lib/git.ts
|
|
15076
|
-
var import_node_child_process3 = require("node:child_process");
|
|
15077
15125
|
var import_node_fs4 = require("node:fs");
|
|
15078
15126
|
var import_node_path4 = require("node:path");
|
|
15127
|
+
|
|
15128
|
+
// src/lib/secret-paths.ts
|
|
15129
|
+
var SECRET_SHAPED_PATHS = [
|
|
15130
|
+
// dotenv and direnv
|
|
15131
|
+
/(^|\/)\.env(\.[^/]*)?$/i,
|
|
15132
|
+
/(^|\/)\.envrc$/i,
|
|
15133
|
+
// private keys, keystores, encrypted vaults
|
|
15134
|
+
/\.(pem|key|p8|p12|pfx|jks|keystore|asc|gpg|kdbx|keytab|ovpn)$/i,
|
|
15135
|
+
/(^|\/)id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,
|
|
15136
|
+
// tools that keep a credential in a dotfile
|
|
15137
|
+
/(^|\/)\.(npmrc|netrc|pypirc|pgpass|git-credentials|htpasswd|s3cfg|boto|dockercfg|terraformrc)$/i,
|
|
15138
|
+
/(^|\/)terraform\.rc$/i,
|
|
15139
|
+
// Terraform state and variables carry provider secrets in plain text
|
|
15140
|
+
/\.tfstate(\.backup)?$/i,
|
|
15141
|
+
/\.tfvars(\.json)?$/i,
|
|
15142
|
+
// cloud service-account keys and kubeconfig
|
|
15143
|
+
/(^|\/)[^/]*service[-_]?account[^/]*\.json$/i,
|
|
15144
|
+
/(^|\/)application_default_credentials\.json$/i,
|
|
15145
|
+
/(^|\/)kubeconfig(\.(ya?ml|json|conf))?$/i,
|
|
15146
|
+
// A file NAMED credentials or secret(s), with ANY extension. ⚠ This includes
|
|
15147
|
+
// source modules such as `src/config/secrets.ts` and `credentials.py`: such a
|
|
15148
|
+
// module often holds the literal key, so it is withheld like any other secret.
|
|
15149
|
+
// The cost: the AI reviewer never reads it. Trivy still scans it on the
|
|
15150
|
+
// machine (git.ts filterSecurity), and its path is sent. The agent tools
|
|
15151
|
+
// refused these names before this list was shared (agent-code-tools.test.ts).
|
|
15152
|
+
/(^|\/)(credentials|secrets?)(\.[^/]*)?$/i,
|
|
15153
|
+
// directories that hold nothing but credentials
|
|
15154
|
+
/(^|\/)\.(ssh|aws|gnupg|docker|kube)(\/|$)/i,
|
|
15155
|
+
/(^|\/)\.git(\/|$)/
|
|
15156
|
+
];
|
|
15157
|
+
var SECRET_WITHHELD_REASON = "secret-shaped-path";
|
|
15158
|
+
var SECRET_REFUSED_READ = "shaped like a secret: Verity never sends this file";
|
|
15159
|
+
function isSecretShapedPath(path) {
|
|
15160
|
+
const normalized = path.replace(/\\/g, "/");
|
|
15161
|
+
return SECRET_SHAPED_PATHS.some((pattern) => pattern.test(normalized));
|
|
15162
|
+
}
|
|
15163
|
+
function scannerWitnessedWithheld(excluded, staticFindings) {
|
|
15164
|
+
const norm3 = (p) => p.replace(/\\/g, "/").replace(/^(\.\/)+/, "");
|
|
15165
|
+
const withheld = new Set((excluded ?? []).filter((e) => e.reason === SECRET_WITHHELD_REASON).map((e) => norm3(e.path)));
|
|
15166
|
+
if (withheld.size === 0) return [];
|
|
15167
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
15168
|
+
for (const s of staticFindings ?? []) {
|
|
15169
|
+
if (typeof s.file !== "string" || typeof s.tool !== "string" || s.tool.length === 0) continue;
|
|
15170
|
+
const file = norm3(s.file);
|
|
15171
|
+
if (withheld.has(file)) out2.add(file);
|
|
15172
|
+
}
|
|
15173
|
+
return [...out2];
|
|
15174
|
+
}
|
|
15175
|
+
|
|
15176
|
+
// src/lib/git.ts
|
|
15079
15177
|
function resolveFile(relpath) {
|
|
15080
15178
|
return (0, import_node_fs4.existsSync)(relpath) ? relpath : null;
|
|
15081
15179
|
}
|
|
15082
|
-
function execGit(
|
|
15180
|
+
function execGit(...args2) {
|
|
15083
15181
|
try {
|
|
15084
|
-
return (
|
|
15182
|
+
return execFileSync2("git", args2, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
15085
15183
|
} catch {
|
|
15086
15184
|
return "";
|
|
15087
15185
|
}
|
|
@@ -15122,20 +15220,25 @@ function writeBaselineSha(sha) {
|
|
|
15122
15220
|
}
|
|
15123
15221
|
function commitObjectExists(sha) {
|
|
15124
15222
|
if (!SHA_RE.test(sha)) return false;
|
|
15125
|
-
|
|
15223
|
+
try {
|
|
15224
|
+
execFileSync2("git", ["cat-file", "-e", `${sha}^{commit}`], { stdio: "ignore" });
|
|
15225
|
+
return true;
|
|
15226
|
+
} catch {
|
|
15227
|
+
return false;
|
|
15228
|
+
}
|
|
15126
15229
|
}
|
|
15127
15230
|
function committedSinceRewrite(oldSha) {
|
|
15128
15231
|
if (!SHA_RE.test(oldSha)) return [];
|
|
15129
|
-
const newLocal = new Set(splitLines(execGit(
|
|
15232
|
+
const newLocal = new Set(splitLines(execGit("rev-list", "HEAD", "--not", oldSha, "--remotes")));
|
|
15130
15233
|
if (newLocal.size === 0) return [];
|
|
15131
15234
|
const files = /* @__PURE__ */ new Set();
|
|
15132
|
-
for (const line2 of splitLines(execGit(
|
|
15235
|
+
for (const line2 of splitLines(execGit("cherry", oldSha, "HEAD"))) {
|
|
15133
15236
|
const sp = line2.indexOf(" ");
|
|
15134
15237
|
if (sp < 0) continue;
|
|
15135
15238
|
const mark = line2.slice(0, sp);
|
|
15136
15239
|
const sha = line2.slice(sp + 1).trim();
|
|
15137
15240
|
if (mark !== "+" || !newLocal.has(sha)) continue;
|
|
15138
|
-
for (const f of splitLines(execGit(
|
|
15241
|
+
for (const f of splitLines(execGit("diff-tree", "--no-commit-id", "--name-only", "-r", sha))) files.add(f);
|
|
15139
15242
|
}
|
|
15140
15243
|
return [...files].filter((f) => !isVerityOwnedPath(f));
|
|
15141
15244
|
}
|
|
@@ -15148,13 +15251,13 @@ function isVerityOwnedPath(file) {
|
|
|
15148
15251
|
function getChangedFiles() {
|
|
15149
15252
|
const sets = /* @__PURE__ */ new Set();
|
|
15150
15253
|
let hasRecentCommitFiles = false;
|
|
15151
|
-
for (const f of splitLines(execGit("
|
|
15152
|
-
for (const f of splitLines(execGit("
|
|
15153
|
-
for (const f of splitLines(execGit("
|
|
15254
|
+
for (const f of splitLines(execGit("diff", "--name-only", "HEAD"))) sets.add(f);
|
|
15255
|
+
for (const f of splitLines(execGit("diff", "--name-only", "--cached"))) sets.add(f);
|
|
15256
|
+
for (const f of splitLines(execGit("ls-files", "--others", "--exclude-standard"))) sets.add(f);
|
|
15154
15257
|
const rawBaseline = readRawBaselineSha();
|
|
15155
15258
|
const baseline = readBaselineSha();
|
|
15156
15259
|
if (baseline) {
|
|
15157
|
-
const committed = splitLines(execGit(
|
|
15260
|
+
const committed = splitLines(execGit("diff", "--name-only", `${baseline}..HEAD`));
|
|
15158
15261
|
if (committed.length > 0) {
|
|
15159
15262
|
hasRecentCommitFiles = true;
|
|
15160
15263
|
for (const f of committed) sets.add(f);
|
|
@@ -15167,12 +15270,12 @@ function getChangedFiles() {
|
|
|
15167
15270
|
for (const f of committed) sets.add(f);
|
|
15168
15271
|
}
|
|
15169
15272
|
} else {
|
|
15170
|
-
const headTimestamp = parseInt(execGit("
|
|
15273
|
+
const headTimestamp = parseInt(execGit("log", "-1", "--format=%ct", "HEAD"), 10) || 0;
|
|
15171
15274
|
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
15172
|
-
const hasUnstaged = splitLines(execGit("
|
|
15173
|
-
const hasStaged = splitLines(execGit("
|
|
15275
|
+
const hasUnstaged = splitLines(execGit("diff", "--name-only", "HEAD")).length > 0;
|
|
15276
|
+
const hasStaged = splitLines(execGit("diff", "--name-only", "--cached")).length > 0;
|
|
15174
15277
|
if (commitAge < 120 && !hasUnstaged && !hasStaged) {
|
|
15175
|
-
const recentFiles = splitLines(execGit("
|
|
15278
|
+
const recentFiles = splitLines(execGit("diff", "--name-only", "HEAD~1..HEAD"));
|
|
15176
15279
|
if (recentFiles.length > 0) {
|
|
15177
15280
|
hasRecentCommitFiles = true;
|
|
15178
15281
|
for (const f of recentFiles) sets.add(f);
|
|
@@ -15184,16 +15287,16 @@ function getChangedFiles() {
|
|
|
15184
15287
|
}
|
|
15185
15288
|
function getDirtyFiles() {
|
|
15186
15289
|
const set = /* @__PURE__ */ new Set();
|
|
15187
|
-
for (const f of splitLines(execGit("
|
|
15188
|
-
for (const f of splitLines(execGit("
|
|
15189
|
-
for (const f of splitLines(execGit("
|
|
15290
|
+
for (const f of splitLines(execGit("diff", "--name-only", "HEAD"))) set.add(f);
|
|
15291
|
+
for (const f of splitLines(execGit("diff", "--name-only", "--cached"))) set.add(f);
|
|
15292
|
+
for (const f of splitLines(execGit("ls-files", "--others", "--exclude-standard"))) set.add(f);
|
|
15190
15293
|
return Array.from(set).filter((f) => !isVerityOwnedPath(f));
|
|
15191
15294
|
}
|
|
15192
15295
|
function showContentAtRef(ref, repoRelPath) {
|
|
15193
15296
|
if (!ref || ref === "no-git") return null;
|
|
15194
15297
|
const normalizedPath = repoRelPath.replace(/\\/g, "/");
|
|
15195
15298
|
try {
|
|
15196
|
-
return (
|
|
15299
|
+
return execFileSync2("git", ["show", `${ref}:${normalizedPath}`], {
|
|
15197
15300
|
encoding: "utf-8",
|
|
15198
15301
|
maxBuffer: 64 * 1024 * 1024,
|
|
15199
15302
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -15221,20 +15324,20 @@ function filterReviewable(files) {
|
|
|
15221
15324
|
}
|
|
15222
15325
|
function filterSecurity(files) {
|
|
15223
15326
|
return files.filter(
|
|
15224
|
-
(f) => SECURITY_PATTERNS.some((p) => p.test(f))
|
|
15327
|
+
(f) => SECURITY_PATTERNS.some((p) => p.test(f)) || isSecretShapedPath(f)
|
|
15225
15328
|
);
|
|
15226
15329
|
}
|
|
15227
15330
|
function getCurrentCommit() {
|
|
15228
|
-
return execGit("
|
|
15331
|
+
return execGit("rev-parse", "HEAD") || "no-git";
|
|
15229
15332
|
}
|
|
15230
15333
|
function getCurrentBranch() {
|
|
15231
|
-
const b = execGit("
|
|
15334
|
+
const b = execGit("rev-parse", "--abbrev-ref", "HEAD");
|
|
15232
15335
|
return !b || b === "HEAD" ? null : b;
|
|
15233
15336
|
}
|
|
15234
15337
|
function commitResolves(sha) {
|
|
15235
15338
|
if (!sha) return false;
|
|
15236
15339
|
try {
|
|
15237
|
-
(
|
|
15340
|
+
execFileSync2("git", ["merge-base", "--is-ancestor", sha, "HEAD"], {
|
|
15238
15341
|
stdio: ["pipe", "pipe", "pipe"]
|
|
15239
15342
|
});
|
|
15240
15343
|
return true;
|
|
@@ -15247,7 +15350,7 @@ function commitsSincePaths(sha, paths) {
|
|
|
15247
15350
|
try {
|
|
15248
15351
|
const args2 = ["rev-list", "--count", `${sha}..HEAD`];
|
|
15249
15352
|
if (paths.length > 0) args2.push("--", ...paths.slice(0, 50));
|
|
15250
|
-
const out2 = (
|
|
15353
|
+
const out2 = execFileSync2("git", args2, {
|
|
15251
15354
|
encoding: "utf-8",
|
|
15252
15355
|
stdio: ["pipe", "pipe", "pipe"]
|
|
15253
15356
|
}).trim();
|
|
@@ -15301,8 +15404,8 @@ function parseRemote(raw) {
|
|
|
15301
15404
|
}
|
|
15302
15405
|
function listTrackedFiles() {
|
|
15303
15406
|
const set = /* @__PURE__ */ new Set();
|
|
15304
|
-
for (const f of splitLines(execGit("
|
|
15305
|
-
for (const f of splitLines(execGit("
|
|
15407
|
+
for (const f of splitLines(execGit("ls-files"))) set.add(f);
|
|
15408
|
+
for (const f of splitLines(execGit("ls-files", "--others", "--exclude-standard"))) set.add(f);
|
|
15306
15409
|
return Array.from(set);
|
|
15307
15410
|
}
|
|
15308
15411
|
function sanitizeRemote(remote) {
|
|
@@ -15351,7 +15454,7 @@ function pinRefusalMessage(verdict, targetUrl) {
|
|
|
15351
15454
|
// src/lib/cli-version.ts
|
|
15352
15455
|
function cliVersion() {
|
|
15353
15456
|
try {
|
|
15354
|
-
return true ? "0.
|
|
15457
|
+
return true ? "0.36.0" : "dev";
|
|
15355
15458
|
} catch {
|
|
15356
15459
|
return "dev";
|
|
15357
15460
|
}
|
|
@@ -15730,7 +15833,6 @@ async function maybeHealServiceUrl(resolution, verbose) {
|
|
|
15730
15833
|
var import_node_os2 = require("node:os");
|
|
15731
15834
|
|
|
15732
15835
|
// src/lib/handoff.ts
|
|
15733
|
-
var import_node_child_process4 = require("node:child_process");
|
|
15734
15836
|
function isHttpsPage(url) {
|
|
15735
15837
|
try {
|
|
15736
15838
|
const u = new URL(url);
|
|
@@ -15767,7 +15869,7 @@ function openInBrowser(url, opts = {}) {
|
|
|
15767
15869
|
const cmd = launcher(platform4, url);
|
|
15768
15870
|
if (!cmd) return false;
|
|
15769
15871
|
try {
|
|
15770
|
-
const child = (opts.run ??
|
|
15872
|
+
const child = (opts.run ?? spawnShown)(cmd[0], cmd[1], { stdio: "ignore", detached: true });
|
|
15771
15873
|
child.on("error", () => {
|
|
15772
15874
|
});
|
|
15773
15875
|
child.unref();
|
|
@@ -15791,7 +15893,7 @@ function copyToClipboard(text3, opts = {}) {
|
|
|
15791
15893
|
if (env.SSH_CONNECTION || env.SSH_TTY || env.CI) return false;
|
|
15792
15894
|
for (const [cmd, args2] of clipboards(platform4, env)) {
|
|
15793
15895
|
try {
|
|
15794
|
-
const r = (opts.run ??
|
|
15896
|
+
const r = (opts.run ?? spawnSync2)(cmd, args2, { input: text3, stdio: ["pipe", "ignore", "ignore"], timeout: 2e3 });
|
|
15795
15897
|
if (!r.error && r.status === 0) return true;
|
|
15796
15898
|
} catch {
|
|
15797
15899
|
}
|
|
@@ -16245,7 +16347,7 @@ function registerAuthCommands(program2) {
|
|
|
16245
16347
|
let remote = opts.remote;
|
|
16246
16348
|
if (!remote) {
|
|
16247
16349
|
try {
|
|
16248
|
-
remote = (
|
|
16350
|
+
remote = execFileSync2("git", ["remote", "get-url", "origin"], { encoding: "utf-8" }).trim();
|
|
16249
16351
|
} catch {
|
|
16250
16352
|
printError("No git remote found. Use --remote to specify one.");
|
|
16251
16353
|
process.exit(1);
|
|
@@ -16297,7 +16399,7 @@ function registerAuthCommands(program2) {
|
|
|
16297
16399
|
let remote = opts.remote;
|
|
16298
16400
|
if (!remote) {
|
|
16299
16401
|
try {
|
|
16300
|
-
remote = (
|
|
16402
|
+
remote = execFileSync2("git", ["remote", "get-url", "origin"], { encoding: "utf-8" }).trim();
|
|
16301
16403
|
} catch {
|
|
16302
16404
|
printError("No git remote found. Use --remote to specify one.");
|
|
16303
16405
|
process.exit(1);
|
|
@@ -16963,7 +17065,6 @@ var import_yaml = __toESM2(require_dist());
|
|
|
16963
17065
|
var import_node_path11 = require("node:path");
|
|
16964
17066
|
|
|
16965
17067
|
// src/lib/detect.ts
|
|
16966
|
-
var import_node_child_process6 = require("node:child_process");
|
|
16967
17068
|
var import_node_fs10 = require("node:fs");
|
|
16968
17069
|
var import_node_path10 = require("node:path");
|
|
16969
17070
|
|
|
@@ -17058,8 +17159,12 @@ function collectCodeDelta(files, opts) {
|
|
|
17058
17159
|
let truncationReason = null;
|
|
17059
17160
|
const droppedPaths = [];
|
|
17060
17161
|
const excluded = [];
|
|
17061
|
-
const exclude = (path, reason) => excluded.push({ path, reason, stage: "collectCodeDelta", kind:
|
|
17162
|
+
const exclude = (path, reason, kind2 = "capacity") => excluded.push({ path, reason, stage: "collectCodeDelta", kind: kind2 });
|
|
17062
17163
|
for (const filepath of sorted) {
|
|
17164
|
+
if (isSecretShapedPath(filepath)) {
|
|
17165
|
+
exclude(filepath, SECRET_WITHHELD_REASON, "policy");
|
|
17166
|
+
continue;
|
|
17167
|
+
}
|
|
17063
17168
|
if (result.length >= maxFiles) {
|
|
17064
17169
|
truncationReason ??= "max_files";
|
|
17065
17170
|
droppedPaths.push(filepath);
|
|
@@ -17229,7 +17334,7 @@ function isProjectFile(path) {
|
|
|
17229
17334
|
}
|
|
17230
17335
|
function listProjectFiles(root) {
|
|
17231
17336
|
try {
|
|
17232
|
-
const out2 = (
|
|
17337
|
+
const out2 = execFileSync2("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], {
|
|
17233
17338
|
cwd: root,
|
|
17234
17339
|
encoding: "utf-8",
|
|
17235
17340
|
maxBuffer: 32 * 1024 * 1024,
|
|
@@ -17717,8 +17822,8 @@ var CLI_DECLARATION = {
|
|
|
17717
17822
|
// src/lib/verity-md.ts
|
|
17718
17823
|
var UNPRINTABLE = /[\u0000-\u001f\u007f-\u009f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g;
|
|
17719
17824
|
function markdownPlain(text3, max = 200) {
|
|
17720
|
-
const
|
|
17721
|
-
const bounded =
|
|
17825
|
+
const oneLine3 = text3.replace(UNPRINTABLE, " ").replace(/\s+/g, " ").trim();
|
|
17826
|
+
const bounded = oneLine3.length > max ? `${oneLine3.slice(0, max - 1)}\u2026` : oneLine3;
|
|
17722
17827
|
return bounded.replace(/[\\`*_{}[\]()<>#+!|~]/g, (c) => `\\${c}`);
|
|
17723
17828
|
}
|
|
17724
17829
|
function serviceLink(url) {
|
|
@@ -19183,7 +19288,6 @@ var import_node_fs13 = require("node:fs");
|
|
|
19183
19288
|
var import_node_path14 = require("node:path");
|
|
19184
19289
|
|
|
19185
19290
|
// src/lib/hardened-git.ts
|
|
19186
|
-
var import_node_child_process7 = require("node:child_process");
|
|
19187
19291
|
var HARDENED_CONFIG_ARGS = [
|
|
19188
19292
|
"-c",
|
|
19189
19293
|
"core.fsmonitor=false",
|
|
@@ -19217,7 +19321,7 @@ function classifyError(err2) {
|
|
|
19217
19321
|
}
|
|
19218
19322
|
function runGitSync(cwd, args2, opts = {}) {
|
|
19219
19323
|
try {
|
|
19220
|
-
const stdout = (
|
|
19324
|
+
const stdout = execFileSync2("git", hardenedGitArgv(args2), {
|
|
19221
19325
|
cwd,
|
|
19222
19326
|
env: hardenedGitEnv(),
|
|
19223
19327
|
encoding: "utf-8",
|
|
@@ -19232,7 +19336,7 @@ function runGitSync(cwd, args2, opts = {}) {
|
|
|
19232
19336
|
}
|
|
19233
19337
|
function runGit(cwd, args2, opts = {}) {
|
|
19234
19338
|
return new Promise((resolvePromise) => {
|
|
19235
|
-
(
|
|
19339
|
+
execFile2(
|
|
19236
19340
|
"git",
|
|
19237
19341
|
hardenedGitArgv(args2),
|
|
19238
19342
|
{
|
|
@@ -20094,7 +20198,6 @@ var import_node_crypto10 = require("node:crypto");
|
|
|
20094
20198
|
// src/lib/conversation-buffer.ts
|
|
20095
20199
|
var import_promises5 = require("node:fs/promises");
|
|
20096
20200
|
var import_node_fs19 = require("node:fs");
|
|
20097
|
-
var import_node_child_process8 = require("node:child_process");
|
|
20098
20201
|
var import_node_crypto2 = require("node:crypto");
|
|
20099
20202
|
function stripImageReferences(text3) {
|
|
20100
20203
|
return text3.replace(/\[Image #\d+\]/g, "[screenshot \u2014 not available for review]");
|
|
@@ -20197,8 +20300,9 @@ async function readBufferEntries() {
|
|
|
20197
20300
|
}
|
|
20198
20301
|
function getRecentCommitMessages() {
|
|
20199
20302
|
try {
|
|
20200
|
-
const output = (
|
|
20201
|
-
|
|
20303
|
+
const output = execFileSync2(
|
|
20304
|
+
"git",
|
|
20305
|
+
["log", "--since=30 minutes ago", "--format=%s", "-5"],
|
|
20202
20306
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
20203
20307
|
).trim();
|
|
20204
20308
|
if (!output) return [];
|
|
@@ -21206,7 +21310,6 @@ var import_node_path23 = require("node:path");
|
|
|
21206
21310
|
var import_node_crypto4 = require("node:crypto");
|
|
21207
21311
|
|
|
21208
21312
|
// src/lib/gitignore.ts
|
|
21209
|
-
var import_node_child_process9 = require("node:child_process");
|
|
21210
21313
|
var import_node_fs24 = require("node:fs");
|
|
21211
21314
|
var VERITY_GITIGNORE_MARKER = "# Verity \u2014 machine-local state.";
|
|
21212
21315
|
var SETTINGS_LOCAL_IGNORE_ENTRY = ".claude/settings.local.json";
|
|
@@ -21247,7 +21350,7 @@ var SUPERSEDED_STANDARD_LINE = "!.verity/standard.yaml";
|
|
|
21247
21350
|
var OLD_COMMENT_LINES = /* @__PURE__ */ new Set([SUPERSEDED_COMMENT, ...MEMORY_COMMENT]);
|
|
21248
21351
|
function isIgnored3(path, opts = {}) {
|
|
21249
21352
|
try {
|
|
21250
|
-
(
|
|
21353
|
+
execFileSync2("git", ["check-ignore", "-q", ...opts.noIndex ? ["--no-index"] : [], "--", path], { stdio: "pipe" });
|
|
21251
21354
|
return true;
|
|
21252
21355
|
} catch (err2) {
|
|
21253
21356
|
return err2.status === 1 ? false : null;
|
|
@@ -21266,7 +21369,7 @@ function memoryPosture() {
|
|
|
21266
21369
|
}
|
|
21267
21370
|
function committedMemoryFiles() {
|
|
21268
21371
|
try {
|
|
21269
|
-
return (
|
|
21372
|
+
return execFileSync2("git", ["ls-files", "-z", "--", ".verity/memory"], {
|
|
21270
21373
|
encoding: "utf-8",
|
|
21271
21374
|
stdio: ["pipe", "pipe", "pipe"]
|
|
21272
21375
|
}).split("\0").filter(Boolean);
|
|
@@ -21282,7 +21385,7 @@ function standardPosture() {
|
|
|
21282
21385
|
}
|
|
21283
21386
|
function committedStandardFile() {
|
|
21284
21387
|
try {
|
|
21285
|
-
return (
|
|
21388
|
+
return execFileSync2("git", ["ls-files", "-z", "--", STANDARD_PATH], {
|
|
21286
21389
|
encoding: "utf-8",
|
|
21287
21390
|
stdio: ["pipe", "pipe", "pipe"]
|
|
21288
21391
|
}).split("\0").filter(Boolean).length > 0;
|
|
@@ -21366,7 +21469,7 @@ function ensureVerityGitignore() {
|
|
|
21366
21469
|
function untrackMemory() {
|
|
21367
21470
|
if (committedMemoryFiles().length === 0) return "none";
|
|
21368
21471
|
try {
|
|
21369
|
-
(
|
|
21472
|
+
execFileSync2("git", ["rm", "-r", "--cached", "--quiet", "--", ".verity/memory"], { stdio: "pipe" });
|
|
21370
21473
|
return "untracked";
|
|
21371
21474
|
} catch {
|
|
21372
21475
|
return "failed";
|
|
@@ -21441,7 +21544,7 @@ function untrackStandard() {
|
|
|
21441
21544
|
}
|
|
21442
21545
|
if (!committedStandardFile()) return { ok: true, untracked: false };
|
|
21443
21546
|
try {
|
|
21444
|
-
(
|
|
21547
|
+
execFileSync2("git", ["rm", "--cached", "--quiet", "--", STANDARD_PATH], { stdio: "pipe" });
|
|
21445
21548
|
return { ok: true, untracked: true };
|
|
21446
21549
|
} catch {
|
|
21447
21550
|
restore();
|
|
@@ -21473,7 +21576,7 @@ function keepMemoryTracked() {
|
|
|
21473
21576
|
function committedVerityState() {
|
|
21474
21577
|
let out2 = "";
|
|
21475
21578
|
try {
|
|
21476
|
-
out2 = (
|
|
21579
|
+
out2 = execFileSync2("git", ["ls-files", "-z", "--", ".verity"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
21477
21580
|
} catch {
|
|
21478
21581
|
return [];
|
|
21479
21582
|
}
|
|
@@ -21484,16 +21587,16 @@ function untrackVerityState() {
|
|
|
21484
21587
|
if (tracked.length === 0) return "none";
|
|
21485
21588
|
const standardWasTracked = committedStandardFile();
|
|
21486
21589
|
try {
|
|
21487
|
-
(
|
|
21590
|
+
execFileSync2("git", ["rm", "-r", "--cached", "--quiet", "--", ".verity"], { stdio: "pipe" });
|
|
21488
21591
|
if (standardWasTracked) {
|
|
21489
21592
|
try {
|
|
21490
|
-
(
|
|
21593
|
+
execFileSync2("git", ["add", "-f", "--", STANDARD_PATH], { stdio: "pipe" });
|
|
21491
21594
|
} catch {
|
|
21492
21595
|
}
|
|
21493
21596
|
}
|
|
21494
21597
|
if (memoryPosture() === "committable") {
|
|
21495
21598
|
try {
|
|
21496
|
-
(
|
|
21599
|
+
execFileSync2("git", ["add", "--", ".verity/memory"], { stdio: "pipe" });
|
|
21497
21600
|
} catch {
|
|
21498
21601
|
}
|
|
21499
21602
|
}
|
|
@@ -21637,6 +21740,152 @@ async function buildManifest() {
|
|
|
21637
21740
|
function hashContent(content) {
|
|
21638
21741
|
return `sha256:${(0, import_node_crypto4.createHash)("sha256").update(content).digest("hex").slice(0, 16)}`;
|
|
21639
21742
|
}
|
|
21743
|
+
var hashNodeContent = hashContent;
|
|
21744
|
+
async function writeNodeFile(relPath, content) {
|
|
21745
|
+
const fullPath = resolveInside(memoryDir2(), relPath);
|
|
21746
|
+
if (!fullPath) return false;
|
|
21747
|
+
if (!await ensureDirInRepo((0, import_node_path23.dirname)(fullPath))) return false;
|
|
21748
|
+
return writeInTree(fullPath, content);
|
|
21749
|
+
}
|
|
21750
|
+
async function writeContainedNodeFile(fullPath, content) {
|
|
21751
|
+
if (!await ensureDirInRepo((0, import_node_path23.dirname)(fullPath))) return false;
|
|
21752
|
+
return writeInTree(fullPath, content);
|
|
21753
|
+
}
|
|
21754
|
+
async function removeNodeFile(relPath) {
|
|
21755
|
+
const fullPath = resolveInside(memoryDir2(), relPath);
|
|
21756
|
+
if (!fullPath || !dirInRepo((0, import_node_path23.dirname)(fullPath))) return false;
|
|
21757
|
+
try {
|
|
21758
|
+
await (0, import_promises9.rm)(fullPath, { force: true });
|
|
21759
|
+
return true;
|
|
21760
|
+
} catch {
|
|
21761
|
+
return false;
|
|
21762
|
+
}
|
|
21763
|
+
}
|
|
21764
|
+
function dirInRepo(dir) {
|
|
21765
|
+
try {
|
|
21766
|
+
return isInsideRoot((0, import_node_fs26.realpathSync)(repoRoot()), (0, import_node_fs26.realpathSync)(dir));
|
|
21767
|
+
} catch {
|
|
21768
|
+
return false;
|
|
21769
|
+
}
|
|
21770
|
+
}
|
|
21771
|
+
function memoryDirEscapesRepo() {
|
|
21772
|
+
return (0, import_node_fs26.existsSync)(memoryDir2()) && !dirInRepo(memoryDir2());
|
|
21773
|
+
}
|
|
21774
|
+
async function ensureDirInRepo(dir) {
|
|
21775
|
+
let probe = dir;
|
|
21776
|
+
while (!(0, import_node_fs26.existsSync)(probe) && (0, import_node_path23.dirname)(probe) !== probe) probe = (0, import_node_path23.dirname)(probe);
|
|
21777
|
+
if (!dirInRepo(probe)) return false;
|
|
21778
|
+
await (0, import_promises9.mkdir)(dir, { recursive: true });
|
|
21779
|
+
return dirInRepo(dir);
|
|
21780
|
+
}
|
|
21781
|
+
async function writeFileAtomicInTree(path, content) {
|
|
21782
|
+
const dir = (0, import_node_path23.dirname)(path);
|
|
21783
|
+
if (!await ensureDirInRepo(dir)) return false;
|
|
21784
|
+
const real = (0, import_node_fs26.realpathSync)(dir);
|
|
21785
|
+
const tmp = `.${(0, import_node_path23.basename)(path)}.${process.pid}.${Date.now()}.tmp`;
|
|
21786
|
+
if (!createInRoot(real, tmp, content)) return false;
|
|
21787
|
+
try {
|
|
21788
|
+
await (0, import_promises9.rename)((0, import_node_path23.join)(real, tmp), (0, import_node_path23.join)(real, (0, import_node_path23.basename)(path)));
|
|
21789
|
+
return true;
|
|
21790
|
+
} catch {
|
|
21791
|
+
try {
|
|
21792
|
+
await (0, import_promises9.rm)((0, import_node_path23.join)(real, tmp), { force: true });
|
|
21793
|
+
} catch {
|
|
21794
|
+
}
|
|
21795
|
+
return false;
|
|
21796
|
+
}
|
|
21797
|
+
}
|
|
21798
|
+
async function markNodesSynced(entries) {
|
|
21799
|
+
await mergeSyncBaseline(new Map(entries));
|
|
21800
|
+
}
|
|
21801
|
+
async function forgetSyncedNodes(paths) {
|
|
21802
|
+
if (paths.length === 0 || !(0, import_node_fs26.existsSync)(syncStateFile())) return;
|
|
21803
|
+
try {
|
|
21804
|
+
try {
|
|
21805
|
+
JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
|
|
21806
|
+
} catch {
|
|
21807
|
+
return;
|
|
21808
|
+
}
|
|
21809
|
+
const baseline = await readSyncBaseline();
|
|
21810
|
+
let changed = false;
|
|
21811
|
+
for (const p of paths) changed = baseline.delete(p) || changed;
|
|
21812
|
+
if (!changed) return;
|
|
21813
|
+
const nodes = [...baseline].map(([path, hash]) => ({ path, hash }));
|
|
21814
|
+
await writeFileAtomic(syncStateFile(), JSON.stringify({ schema: 2, nodes }) + "\n");
|
|
21815
|
+
} catch {
|
|
21816
|
+
}
|
|
21817
|
+
}
|
|
21818
|
+
async function refreshMemoryIndex() {
|
|
21819
|
+
await regenerateIndex();
|
|
21820
|
+
}
|
|
21821
|
+
async function appendMemoryLog(lines) {
|
|
21822
|
+
try {
|
|
21823
|
+
const logPath = (0, import_node_path23.join)(memoryDir2(), "log.md");
|
|
21824
|
+
const existing = (0, import_node_fs26.existsSync)(logPath) ? await (0, import_promises9.readFile)(logPath, "utf-8") : "# Memory Log\n\n";
|
|
21825
|
+
writeInTree(logPath, existing + lines.join("\n") + "\n");
|
|
21826
|
+
} catch {
|
|
21827
|
+
}
|
|
21828
|
+
}
|
|
21829
|
+
function uploadableNodeContent(content) {
|
|
21830
|
+
const m = content.match(/^(---\n[\s\S]*?\n---\n)([\s\S]*)$/);
|
|
21831
|
+
if (!m) return content;
|
|
21832
|
+
const lines = m[2].split("\n");
|
|
21833
|
+
let start2 = 0;
|
|
21834
|
+
while (start2 < lines.length && lines[start2].trim() === "") start2++;
|
|
21835
|
+
if (start2 < lines.length && /^# \S/.test(lines[start2])) start2++;
|
|
21836
|
+
let end = lines.length;
|
|
21837
|
+
const related = lines.lastIndexOf("## Related");
|
|
21838
|
+
if (related >= start2) {
|
|
21839
|
+
const tail = lines.slice(related + 1);
|
|
21840
|
+
if (tail.every((l) => l.trim() === "" || /^\*\*[^*]+:\*\*$/.test(l.trim()) || /^- \[\[[^\]]+\]\]$/.test(l.trim()))) {
|
|
21841
|
+
end = related;
|
|
21842
|
+
}
|
|
21843
|
+
}
|
|
21844
|
+
const body2 = lines.slice(start2, end).join("\n").trim();
|
|
21845
|
+
return `${storableFrontmatter(m[1])}
|
|
21846
|
+
${body2}
|
|
21847
|
+
`;
|
|
21848
|
+
}
|
|
21849
|
+
function bareStorableValue(value) {
|
|
21850
|
+
if (value === "" || value !== value.trim() || /[\r\n]/.test(value)) return null;
|
|
21851
|
+
if (/^(null|true|false)$/.test(value) || /^\d+(\.\d+)?$/.test(value)) return null;
|
|
21852
|
+
if (value.startsWith('"') || value.startsWith("[") && value.endsWith("]")) return null;
|
|
21853
|
+
return value;
|
|
21854
|
+
}
|
|
21855
|
+
function storableFrontmatter(block) {
|
|
21856
|
+
const lines = block.split("\n");
|
|
21857
|
+
let inGlobs = false;
|
|
21858
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
21859
|
+
const line2 = lines[i2];
|
|
21860
|
+
if (/^file_globs:\s*$/.test(line2)) {
|
|
21861
|
+
inGlobs = true;
|
|
21862
|
+
continue;
|
|
21863
|
+
}
|
|
21864
|
+
if (inGlobs && /^\s+-\s/.test(line2)) {
|
|
21865
|
+
const q = line2.match(/^(\s+-\s+)("(?:[^"\\]|\\.)*")\s*$/);
|
|
21866
|
+
if (q && q[2].includes("\\")) {
|
|
21867
|
+
const bare = unquote(q[2]);
|
|
21868
|
+
if (bare !== null && !bare.startsWith('"') && bare === bare.trim() && !/[\r\n]/.test(bare)) lines[i2] = `${q[1]}${bare}`;
|
|
21869
|
+
}
|
|
21870
|
+
continue;
|
|
21871
|
+
}
|
|
21872
|
+
inGlobs = false;
|
|
21873
|
+
const t2 = line2.match(/^title:\s*("(?:[^"\\]|\\.)*")\s*$/);
|
|
21874
|
+
if (t2 && t2[1].includes("\\")) {
|
|
21875
|
+
const bare = unquote(t2[1]);
|
|
21876
|
+
const storable = bare === null ? null : bareStorableValue(bare);
|
|
21877
|
+
if (storable !== null) lines[i2] = `title: ${storable}`;
|
|
21878
|
+
}
|
|
21879
|
+
}
|
|
21880
|
+
return lines.join("\n");
|
|
21881
|
+
}
|
|
21882
|
+
function unquote(quoted) {
|
|
21883
|
+
try {
|
|
21884
|
+
return String(JSON.parse(quoted));
|
|
21885
|
+
} catch {
|
|
21886
|
+
return null;
|
|
21887
|
+
}
|
|
21888
|
+
}
|
|
21640
21889
|
async function readOnDiskNodes() {
|
|
21641
21890
|
const out2 = /* @__PURE__ */ new Map();
|
|
21642
21891
|
if (!(0, import_node_fs26.existsSync)(memoryDir2())) return out2;
|
|
@@ -21706,7 +21955,7 @@ async function computeEditedNodeUploads() {
|
|
|
21706
21955
|
} catch {
|
|
21707
21956
|
continue;
|
|
21708
21957
|
}
|
|
21709
|
-
if (hashContent(content) !== prevHash) uploads.push({ path, content });
|
|
21958
|
+
if (hashContent(content) !== prevHash) uploads.push({ path, content: uploadableNodeContent(content) });
|
|
21710
21959
|
}
|
|
21711
21960
|
return uploads;
|
|
21712
21961
|
}
|
|
@@ -21792,10 +22041,14 @@ async function applyOneWrite(write, treePaths, lastServed) {
|
|
|
21792
22041
|
if (existing !== "") {
|
|
21793
22042
|
const served = lastServed?.get(write.path);
|
|
21794
22043
|
if (served == null || served !== hashContent(existing)) {
|
|
21795
|
-
|
|
21796
|
-
|
|
22044
|
+
if (nodeSubstance(existing) !== nodeSubstance(content)) {
|
|
22045
|
+
notes.push(`${write.path}: kept local edit (differs from server)`);
|
|
22046
|
+
return { written: false, syncedHash: null, notes };
|
|
22047
|
+
}
|
|
22048
|
+
notes.push(`${write.path}: updated from server (the local edit is already on the server)`);
|
|
22049
|
+
} else {
|
|
22050
|
+
notes.push(`${write.path}: updated from server (untouched here since the server delivered it)`);
|
|
21797
22051
|
}
|
|
21798
|
-
notes.push(`${write.path}: updated from server (untouched here since the server delivered it)`);
|
|
21799
22052
|
}
|
|
21800
22053
|
}
|
|
21801
22054
|
await (0, import_promises9.mkdir)((0, import_node_path23.dirname)(fullPath), { recursive: true });
|
|
@@ -21805,6 +22058,15 @@ async function applyOneWrite(write, treePaths, lastServed) {
|
|
|
21805
22058
|
}
|
|
21806
22059
|
return { written: true, syncedHash: hashContent(content), notes };
|
|
21807
22060
|
}
|
|
22061
|
+
var SERVER_OWNED_LINE = /^(schema|created_at|updated_at|session|task|run|related|supersedes|superseded_by|contradicts|caused_by|example_of|evidence_run_ids|pattern_ref|source|created_by|confidence):/;
|
|
22062
|
+
function nodeSubstance(content) {
|
|
22063
|
+
const m = uploadableNodeContent(content).match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
22064
|
+
if (!m) return content;
|
|
22065
|
+
const fm = m[1].split("\n").filter((l) => !SERVER_OWNED_LINE.test(l)).join("\n");
|
|
22066
|
+
return `${fm}
|
|
22067
|
+
---
|
|
22068
|
+
${m[2].replace(/\s+$/, "")}`;
|
|
22069
|
+
}
|
|
21808
22070
|
function groundFileGlobs(content, treePaths) {
|
|
21809
22071
|
const globs = pickFrontmatterArray(content, "file_globs");
|
|
21810
22072
|
if (globs.length === 0) return { content, dropped: [] };
|
|
@@ -24339,7 +24601,6 @@ var import_node_crypto11 = require("node:crypto");
|
|
|
24339
24601
|
// src/lib/snapshot.ts
|
|
24340
24602
|
var import_node_fs32 = require("node:fs");
|
|
24341
24603
|
var import_node_path27 = require("node:path");
|
|
24342
|
-
var import_node_child_process10 = require("node:child_process");
|
|
24343
24604
|
function generateSnapshotDiffs(files) {
|
|
24344
24605
|
if (!(0, import_node_fs32.existsSync)(SNAPSHOT_DIR)) {
|
|
24345
24606
|
return { diffs: [], has_snapshots: false };
|
|
@@ -24389,8 +24650,9 @@ function computeDiff(oldContent, newContent, filePath) {
|
|
|
24389
24650
|
(0, import_node_fs32.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
24390
24651
|
(0, import_node_fs32.writeFileSync)(tmpOld, oldContent);
|
|
24391
24652
|
(0, import_node_fs32.writeFileSync)(tmpNew, newContent);
|
|
24392
|
-
const result = (
|
|
24393
|
-
|
|
24653
|
+
const result = execFileSync2(
|
|
24654
|
+
"git",
|
|
24655
|
+
["diff", "--no-index", "--unified=10", "--", tmpOld, tmpNew],
|
|
24394
24656
|
{ encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
24395
24657
|
);
|
|
24396
24658
|
return result || null;
|
|
@@ -25142,7 +25404,7 @@ var EXCLUDED_DIR = /(^|\/)(node_modules|dist|build|bin|out|vendor|coverage|\.ver
|
|
|
25142
25404
|
var TEST_PATH = /(^|\/)(tests?|__tests__|spec|specs|fixtures?|e2e|testdata)\/|\.(test|spec|stories)\.[cm]?[jt]sx?$|_test\.(go|py|rb)$|test_[^/]*\.py$/;
|
|
25143
25405
|
var GENERATED2 = /\.d\.ts$|\.min\.[jt]s$|\.generated\.|\.pb\.|lock\.json$|\.lock$|(^|\/)(integrations\/supabase\/types|database\.types|supabase\.types)\.ts$|(^|\/)__generated__\//;
|
|
25144
25406
|
function isCandidateSource(file) {
|
|
25145
|
-
return SOURCE_EXT.test(file) && !EXCLUDED_DIR.test(file) && !TEST_PATH.test(file) && !GENERATED2.test(file);
|
|
25407
|
+
return SOURCE_EXT.test(file) && !EXCLUDED_DIR.test(file) && !TEST_PATH.test(file) && !GENERATED2.test(file) && !isSecretShapedPath(file);
|
|
25146
25408
|
}
|
|
25147
25409
|
function collectHistory(root, opts = {}) {
|
|
25148
25410
|
const args2 = [
|
|
@@ -27947,7 +28209,6 @@ var import_node_fs58 = require("node:fs");
|
|
|
27947
28209
|
var import_node_path46 = require("node:path");
|
|
27948
28210
|
|
|
27949
28211
|
// src/lib/repo-context.ts
|
|
27950
|
-
var import_node_child_process11 = require("node:child_process");
|
|
27951
28212
|
var import_node_os5 = require("node:os");
|
|
27952
28213
|
function rgInvocations(env = process.env) {
|
|
27953
28214
|
const out2 = [{ cmd: "rg" }];
|
|
@@ -28507,7 +28768,7 @@ function partitionImporters(rgLines, changedPaths, opts) {
|
|
|
28507
28768
|
function runRg(args2, cwd, timeoutMs) {
|
|
28508
28769
|
let res = null;
|
|
28509
28770
|
for (const inv of rgInvocations()) {
|
|
28510
|
-
res = (
|
|
28771
|
+
res = spawnSync2(inv.cmd, args2, {
|
|
28511
28772
|
...inv.argv0 ? { argv0: inv.argv0 } : {},
|
|
28512
28773
|
cwd,
|
|
28513
28774
|
env: ripgrepEnv(),
|
|
@@ -28974,7 +29235,7 @@ function findMomentSegment(command, on) {
|
|
|
28974
29235
|
function classifyCommand(command, on) {
|
|
28975
29236
|
return findMomentSegment(command, on)?.moment ?? null;
|
|
28976
29237
|
}
|
|
28977
|
-
function
|
|
29238
|
+
function unquote2(token) {
|
|
28978
29239
|
if (token.length >= 2) {
|
|
28979
29240
|
const first = token[0];
|
|
28980
29241
|
if ((first === "'" || first === '"') && token.endsWith(first)) return token.slice(1, -1);
|
|
@@ -28994,7 +29255,7 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
|
|
|
28994
29255
|
dir = (0, import_node_os6.homedir)();
|
|
28995
29256
|
continue;
|
|
28996
29257
|
}
|
|
28997
|
-
const raw =
|
|
29258
|
+
const raw = unquote2(m[1]);
|
|
28998
29259
|
if (SHELL_DYNAMIC.test(raw) || raw === "-") {
|
|
28999
29260
|
return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
|
|
29000
29261
|
}
|
|
@@ -29013,7 +29274,7 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
|
|
|
29013
29274
|
let cm;
|
|
29014
29275
|
while ((cm = cRe.exec(optsRegion)) !== null) {
|
|
29015
29276
|
named = true;
|
|
29016
|
-
const raw =
|
|
29277
|
+
const raw = unquote2(cm[1]);
|
|
29017
29278
|
if (SHELL_DYNAMIC.test(raw)) {
|
|
29018
29279
|
return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
|
|
29019
29280
|
}
|
|
@@ -29029,7 +29290,7 @@ function parsePushTarget(segment) {
|
|
|
29029
29290
|
const m = PUSH_RE.exec(segment);
|
|
29030
29291
|
if (!m) return none;
|
|
29031
29292
|
const rest = segment.slice(m.index + m[0].length);
|
|
29032
|
-
const tokens = (rest.match(new RegExp(`'[^']*'|"[^"]*"|\\S+`, "g")) ?? []).map(
|
|
29293
|
+
const tokens = (rest.match(new RegExp(`'[^']*'|"[^"]*"|\\S+`, "g")) ?? []).map(unquote2);
|
|
29033
29294
|
let remote = null;
|
|
29034
29295
|
let refspec = null;
|
|
29035
29296
|
let isDelete = false;
|
|
@@ -29189,7 +29450,7 @@ var COMMIT_VALUE_OPTS = /* @__PURE__ */ new Set([
|
|
|
29189
29450
|
var ADD_HEAD = `git${GIT_GLOBAL_OPTS}\\s+add(?![\\w-])`;
|
|
29190
29451
|
var ADD_RE = new RegExp(`(?:^|[\\s;&|(])${ADD_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${ADD_HEAD}`);
|
|
29191
29452
|
function pathspecOf(word) {
|
|
29192
|
-
const raw =
|
|
29453
|
+
const raw = unquote2(word);
|
|
29193
29454
|
if (!raw) return { reason: "an empty pathspec" };
|
|
29194
29455
|
if (PATHSPEC_UNRESOLVABLE.test(raw)) {
|
|
29195
29456
|
return { reason: `the shell rewrites the pathspec "${raw}" before git sees it` };
|
|
@@ -29306,12 +29567,57 @@ function stagedRange(frame, command) {
|
|
|
29306
29567
|
if (wtVsHead.has(f) && wtVsMerge.has(f) && !isVerityOwnedPath(f)) resolutions.add(f);
|
|
29307
29568
|
}
|
|
29308
29569
|
}
|
|
29309
|
-
|
|
29570
|
+
const parents = [objectId(frame, "HEAD"), objectId(frame, "MERGE_HEAD")];
|
|
29571
|
+
const bases2 = parents.every((p) => p !== null) ? parents : void 0;
|
|
29572
|
+
return { kind: "merge", base: "HEAD", head: "INDEX", via: "merge-resolutions", files: [...resolutions], ...bases2 ? { bases: bases2 } : {} };
|
|
29310
29573
|
}
|
|
29574
|
+
const bases = stagedBases(frame, command);
|
|
29311
29575
|
if (plan.kind === "files") {
|
|
29312
|
-
return { kind: "staged", base: "HEAD", head: "INDEX", via: plan.via, files: plan.files };
|
|
29576
|
+
return { kind: "staged", base: "HEAD", head: "INDEX", via: plan.via, files: plan.files, ...bases ? { bases } : {} };
|
|
29313
29577
|
}
|
|
29314
|
-
return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
|
|
29578
|
+
return { kind: "staged", base: "HEAD", head: "INDEX", via: "index", ...bases ? { bases } : {} };
|
|
29579
|
+
}
|
|
29580
|
+
var OBJECT_ID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
|
|
29581
|
+
function objectId(frame, ref) {
|
|
29582
|
+
const id = frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]);
|
|
29583
|
+
return OBJECT_ID_RE.test(id) ? id : null;
|
|
29584
|
+
}
|
|
29585
|
+
function stagedBases(frame, command) {
|
|
29586
|
+
const head = objectId(frame, "HEAD");
|
|
29587
|
+
if (head === null) return void 0;
|
|
29588
|
+
if (!command || !commitAmends(command)) return [head];
|
|
29589
|
+
const parents = frameGit(frame, ["rev-list", "--parents", "-n", "1", head]).split(" ").slice(1);
|
|
29590
|
+
return parents.length > 0 && parents.every((p) => OBJECT_ID_RE.test(p)) ? parents : void 0;
|
|
29591
|
+
}
|
|
29592
|
+
function commitAmends(command) {
|
|
29593
|
+
const found = findMomentSegment(command, ["commit"]);
|
|
29594
|
+
if (!found) return false;
|
|
29595
|
+
const words = shellWords(splitSegments(command)[found.segmentIndex]);
|
|
29596
|
+
const at = words.indexOf("commit");
|
|
29597
|
+
if (at === -1) return false;
|
|
29598
|
+
let skipValue = false;
|
|
29599
|
+
for (const word of words.slice(at + 1)) {
|
|
29600
|
+
if (skipValue) {
|
|
29601
|
+
skipValue = false;
|
|
29602
|
+
continue;
|
|
29603
|
+
}
|
|
29604
|
+
if (word === "--") break;
|
|
29605
|
+
if (!word.startsWith("--")) {
|
|
29606
|
+
if (word.startsWith("-") && word.length > 1) {
|
|
29607
|
+
for (let i2 = 1; i2 < word.length; i2++) {
|
|
29608
|
+
if (COMMIT_VALUE_OPTS.has(`-${word[i2]}`)) {
|
|
29609
|
+
if (i2 === word.length - 1) skipValue = true;
|
|
29610
|
+
break;
|
|
29611
|
+
}
|
|
29612
|
+
}
|
|
29613
|
+
}
|
|
29614
|
+
continue;
|
|
29615
|
+
}
|
|
29616
|
+
const name2 = word.split("=")[0];
|
|
29617
|
+
if (name2.length >= 4 && "--amend".startsWith(name2)) return true;
|
|
29618
|
+
if (COMMIT_VALUE_OPTS.has(name2) && !word.includes("=")) skipValue = true;
|
|
29619
|
+
}
|
|
29620
|
+
return false;
|
|
29315
29621
|
}
|
|
29316
29622
|
function resolvePushRange(frame, command, on) {
|
|
29317
29623
|
const nothing = (via2) => ({ kind: "nothing", base: null, head: "HEAD", via: via2 });
|
|
@@ -29328,13 +29634,15 @@ function resolvePushRange(frame, command, on) {
|
|
|
29328
29634
|
if (commits.length === 0) return { kind: "nothing", base: null, head, via: "already-published" };
|
|
29329
29635
|
const boundary = frameGit(frame, ["rev-list", head, "--not", remotePattern, "--boundary"]).split("\n").filter((l) => l.startsWith("-")).map((l) => l.slice(1));
|
|
29330
29636
|
const base = boundary.find((s) => SHA_RE2.test(s)) ?? null;
|
|
29637
|
+
const published = boundary.filter((s) => OBJECT_ID_RE.test(s));
|
|
29638
|
+
const bases = published.length > 0 && published.length <= MAX_CHANGE_BASES ? published : void 0;
|
|
29331
29639
|
const files = /* @__PURE__ */ new Set();
|
|
29332
29640
|
for (const sha of commits) {
|
|
29333
29641
|
for (const f of frameGit(frame, ["diff-tree", "--no-commit-id", "--name-only", "-r", sha]).split("\n")) {
|
|
29334
29642
|
if (f && !isVerityOwnedPath(f)) files.add(f);
|
|
29335
29643
|
}
|
|
29336
29644
|
}
|
|
29337
|
-
return { kind: "push", base, head, via, files: [...files], commits };
|
|
29645
|
+
return { kind: "push", base, head, via, files: [...files], commits, ...bases ? { bases } : {} };
|
|
29338
29646
|
}
|
|
29339
29647
|
function rangeFiles(frame, range) {
|
|
29340
29648
|
if (range.files) return range.files.filter((f) => !isVerityOwnedPath(f));
|
|
@@ -29378,6 +29686,226 @@ function rangeChangeSignals(frame, range, paths) {
|
|
|
29378
29686
|
flush();
|
|
29379
29687
|
return out2;
|
|
29380
29688
|
}
|
|
29689
|
+
var MAX_CHANGE_BASES = 8;
|
|
29690
|
+
var CHANGED_LINES_BUDGET_MS = 1500;
|
|
29691
|
+
var MAX_RANGES_PER_FILE = 2e3;
|
|
29692
|
+
var HUNK_RE = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/;
|
|
29693
|
+
function withoutCr(line2) {
|
|
29694
|
+
return line2 !== void 0 && line2.endsWith("\r") ? line2.slice(0, -1) : line2;
|
|
29695
|
+
}
|
|
29696
|
+
function bodyLines(lines) {
|
|
29697
|
+
return lines.length > 0 && lines[lines.length - 1] === "" ? lines.slice(0, -1) : lines;
|
|
29698
|
+
}
|
|
29699
|
+
function parseChangedHunks(diff, sent, base) {
|
|
29700
|
+
const lines = /* @__PURE__ */ new Set();
|
|
29701
|
+
const removedAfter = /* @__PURE__ */ new Set();
|
|
29702
|
+
const text3 = diff.split("\n");
|
|
29703
|
+
const rebuilt = [];
|
|
29704
|
+
let oldPos = 0;
|
|
29705
|
+
let hunks = 0;
|
|
29706
|
+
for (let i2 = 0; i2 < text3.length; i2++) {
|
|
29707
|
+
if (text3[i2].startsWith("Binary files ")) return { reason: "binary" };
|
|
29708
|
+
const h = HUNK_RE.exec(text3[i2]);
|
|
29709
|
+
if (!h) continue;
|
|
29710
|
+
hunks++;
|
|
29711
|
+
const oldStart = Number(h[1]);
|
|
29712
|
+
const oldCount = h[2] === void 0 ? 1 : Number(h[2]);
|
|
29713
|
+
const newStart = Number(h[3]);
|
|
29714
|
+
const newCount = h[4] === void 0 ? 1 : Number(h[4]);
|
|
29715
|
+
if (base) {
|
|
29716
|
+
const copyUntil = oldCount === 0 ? oldStart : oldStart - 1;
|
|
29717
|
+
if (copyUntil < oldPos || copyUntil > base.length) return { reason: "snapshot-differs" };
|
|
29718
|
+
for (; oldPos < copyUntil; oldPos++) rebuilt.push(base[oldPos]);
|
|
29719
|
+
}
|
|
29720
|
+
let seenOld = 0;
|
|
29721
|
+
let seenNew = 0;
|
|
29722
|
+
while (seenOld < oldCount || seenNew < newCount) {
|
|
29723
|
+
i2++;
|
|
29724
|
+
if (i2 >= text3.length) return { reason: "diff-truncated" };
|
|
29725
|
+
const l = text3[i2];
|
|
29726
|
+
if (l.startsWith("\\")) continue;
|
|
29727
|
+
if (l.startsWith("-") && seenOld < oldCount) {
|
|
29728
|
+
if (base) {
|
|
29729
|
+
if (oldPos >= base.length || withoutCr(base[oldPos]) !== withoutCr(l.slice(1))) return { reason: "snapshot-differs" };
|
|
29730
|
+
oldPos++;
|
|
29731
|
+
}
|
|
29732
|
+
seenOld++;
|
|
29733
|
+
continue;
|
|
29734
|
+
}
|
|
29735
|
+
if (l.startsWith("+") && seenNew < newCount) {
|
|
29736
|
+
const at = newStart + seenNew;
|
|
29737
|
+
if (withoutCr(sent[at - 1]) !== withoutCr(l.slice(1))) return { reason: "bytes-differ" };
|
|
29738
|
+
if (base) {
|
|
29739
|
+
rebuilt.push(l.slice(1));
|
|
29740
|
+
if (rebuilt.length !== at) return { reason: "snapshot-differs" };
|
|
29741
|
+
}
|
|
29742
|
+
lines.add(at);
|
|
29743
|
+
seenNew++;
|
|
29744
|
+
continue;
|
|
29745
|
+
}
|
|
29746
|
+
return { reason: "diff-unparsed" };
|
|
29747
|
+
}
|
|
29748
|
+
if (newCount === 0) {
|
|
29749
|
+
if (base && rebuilt.length !== newStart) return { reason: "snapshot-differs" };
|
|
29750
|
+
removedAfter.add(newStart);
|
|
29751
|
+
}
|
|
29752
|
+
}
|
|
29753
|
+
if (hunks === 0) return { reason: "no-hunks" };
|
|
29754
|
+
if (base) {
|
|
29755
|
+
for (; oldPos < base.length; oldPos++) rebuilt.push(base[oldPos]);
|
|
29756
|
+
const want = bodyLines(sent);
|
|
29757
|
+
const got = bodyLines(rebuilt);
|
|
29758
|
+
if (got.length !== want.length || got.some((l, k) => withoutCr(l) !== withoutCr(want[k]))) {
|
|
29759
|
+
return { reason: "snapshot-differs" };
|
|
29760
|
+
}
|
|
29761
|
+
}
|
|
29762
|
+
return { lines, removedAfter };
|
|
29763
|
+
}
|
|
29764
|
+
function intersect(a, b) {
|
|
29765
|
+
return new Set([...a].filter((x) => b.has(x)));
|
|
29766
|
+
}
|
|
29767
|
+
function toRanges(lines) {
|
|
29768
|
+
const sorted = [...lines].sort((x, y) => x - y);
|
|
29769
|
+
const out2 = [];
|
|
29770
|
+
for (const n2 of sorted) {
|
|
29771
|
+
const last = out2[out2.length - 1];
|
|
29772
|
+
if (last && n2 === last[1] + 1) last[1] = n2;
|
|
29773
|
+
else out2.push([n2, n2]);
|
|
29774
|
+
}
|
|
29775
|
+
return out2;
|
|
29776
|
+
}
|
|
29777
|
+
function rangeChangedLines(frame, range, files, budgetMs = CHANGED_LINES_BUDGET_MS) {
|
|
29778
|
+
const out2 = { files: [], omitted: {} };
|
|
29779
|
+
const omit = (why) => {
|
|
29780
|
+
out2.omitted[why] = (out2.omitted[why] ?? 0) + 1;
|
|
29781
|
+
};
|
|
29782
|
+
const bases = range.bases ?? [];
|
|
29783
|
+
if (!frame.worktreeRoot || range.kind === "nothing" || bases.length === 0) {
|
|
29784
|
+
for (const _ of files) omit("no-base");
|
|
29785
|
+
return out2;
|
|
29786
|
+
}
|
|
29787
|
+
const deadline = Date.now() + budgetMs;
|
|
29788
|
+
const blobAt = (rev, path) => {
|
|
29789
|
+
const r = runGitSync(frame.worktreeRoot, ["cat-file", "blob", `${rev}:${path}`], {
|
|
29790
|
+
timeoutMs: Math.max(1, deadline - Date.now())
|
|
29791
|
+
});
|
|
29792
|
+
if (!r.ok && r.error === "timeout") return "deadline";
|
|
29793
|
+
return { text: r.ok ? r.stdout : null };
|
|
29794
|
+
};
|
|
29795
|
+
const merges = [];
|
|
29796
|
+
{
|
|
29797
|
+
const parentSets = [];
|
|
29798
|
+
if (range.kind === "push") {
|
|
29799
|
+
const commits = range.commits ?? [];
|
|
29800
|
+
let readAll = commits.length > 0 && commits.length <= 100;
|
|
29801
|
+
if (readAll) {
|
|
29802
|
+
const r = runGitSync(frame.worktreeRoot, ["show", "-s", "--format=%P", ...commits], {
|
|
29803
|
+
timeoutMs: Math.max(1, deadline - Date.now())
|
|
29804
|
+
});
|
|
29805
|
+
if (r.ok) {
|
|
29806
|
+
for (const line2 of r.stdout.split("\n")) {
|
|
29807
|
+
const ps = line2.trim().split(/\s+/).filter(Boolean);
|
|
29808
|
+
if (ps.length >= 2) parentSets.push(ps);
|
|
29809
|
+
}
|
|
29810
|
+
} else readAll = false;
|
|
29811
|
+
}
|
|
29812
|
+
if (!readAll && bases.length > 1) parentSets.push(bases);
|
|
29813
|
+
} else if (bases.length > 1) {
|
|
29814
|
+
parentSets.push(bases);
|
|
29815
|
+
}
|
|
29816
|
+
for (const parents of parentSets) {
|
|
29817
|
+
const mb = runGitSync(frame.worktreeRoot, ["merge-base", "--octopus", ...parents], {
|
|
29818
|
+
timeoutMs: Math.max(1, deadline - Date.now())
|
|
29819
|
+
});
|
|
29820
|
+
merges.push({ parents, split: mb.ok ? mb.stdout.trim().split("\n")[0] || null : null });
|
|
29821
|
+
}
|
|
29822
|
+
}
|
|
29823
|
+
const mergedByAMerge = (path) => {
|
|
29824
|
+
for (const m of merges) {
|
|
29825
|
+
const split = m.split === null ? void 0 : blobAt(m.split, path);
|
|
29826
|
+
if (split === "deadline") return "deadline";
|
|
29827
|
+
let first;
|
|
29828
|
+
let changed = 0;
|
|
29829
|
+
for (const parent of m.parents) {
|
|
29830
|
+
const b = blobAt(parent, path);
|
|
29831
|
+
if (b === "deadline") return "deadline";
|
|
29832
|
+
if (split === void 0) {
|
|
29833
|
+
if (first === void 0) first = b.text;
|
|
29834
|
+
else if (b.text !== first) return true;
|
|
29835
|
+
} else if (b.text !== split.text && ++changed >= 2) {
|
|
29836
|
+
return true;
|
|
29837
|
+
}
|
|
29838
|
+
}
|
|
29839
|
+
}
|
|
29840
|
+
return false;
|
|
29841
|
+
};
|
|
29842
|
+
for (const file of files) {
|
|
29843
|
+
if (typeof file.content !== "string") {
|
|
29844
|
+
omit("no-content");
|
|
29845
|
+
continue;
|
|
29846
|
+
}
|
|
29847
|
+
const sent = file.content.split("\n");
|
|
29848
|
+
let lines = null;
|
|
29849
|
+
let removedAfter = null;
|
|
29850
|
+
let why = null;
|
|
29851
|
+
const merged = mergedByAMerge(file.path);
|
|
29852
|
+
if (merged !== false) {
|
|
29853
|
+
omit(merged === "deadline" ? "deadline" : "merged-file");
|
|
29854
|
+
continue;
|
|
29855
|
+
}
|
|
29856
|
+
for (const base of bases) {
|
|
29857
|
+
const left = deadline - Date.now();
|
|
29858
|
+
if (left <= 0) {
|
|
29859
|
+
why = "deadline";
|
|
29860
|
+
break;
|
|
29861
|
+
}
|
|
29862
|
+
const res = runGitSync(frame.worktreeRoot, [
|
|
29863
|
+
"--literal-pathspecs",
|
|
29864
|
+
"diff",
|
|
29865
|
+
...DIFF_READ_FLAGS,
|
|
29866
|
+
"--no-color",
|
|
29867
|
+
"--no-renames",
|
|
29868
|
+
"-U0",
|
|
29869
|
+
"--src-prefix=a/",
|
|
29870
|
+
"--dst-prefix=b/",
|
|
29871
|
+
base,
|
|
29872
|
+
"--",
|
|
29873
|
+
file.path
|
|
29874
|
+
], { timeoutMs: left });
|
|
29875
|
+
if (!res.ok) {
|
|
29876
|
+
why = res.error === "timeout" ? "deadline" : "git-failed";
|
|
29877
|
+
break;
|
|
29878
|
+
}
|
|
29879
|
+
const blob = blobAt(base, file.path);
|
|
29880
|
+
if (blob === "deadline") {
|
|
29881
|
+
why = "deadline";
|
|
29882
|
+
break;
|
|
29883
|
+
}
|
|
29884
|
+
const parsed = parseChangedHunks(res.stdout, sent, blob.text === null ? [] : blob.text.split("\n"));
|
|
29885
|
+
if ("reason" in parsed) {
|
|
29886
|
+
why = parsed.reason;
|
|
29887
|
+
break;
|
|
29888
|
+
}
|
|
29889
|
+
lines = lines ? intersect(lines, parsed.lines) : parsed.lines;
|
|
29890
|
+
removedAfter = removedAfter ? intersect(removedAfter, parsed.removedAfter) : parsed.removedAfter;
|
|
29891
|
+
}
|
|
29892
|
+
if (why !== null) {
|
|
29893
|
+
omit(why);
|
|
29894
|
+
continue;
|
|
29895
|
+
}
|
|
29896
|
+
if (!lines || !removedAfter || lines.size === 0 && removedAfter.size === 0) {
|
|
29897
|
+
omit("no-change");
|
|
29898
|
+
continue;
|
|
29899
|
+
}
|
|
29900
|
+
const ranges = toRanges(lines);
|
|
29901
|
+
if (ranges.length + removedAfter.size > MAX_RANGES_PER_FILE) {
|
|
29902
|
+
omit("too-many-ranges");
|
|
29903
|
+
continue;
|
|
29904
|
+
}
|
|
29905
|
+
out2.files.push({ path: file.path, ranges, removed_after: [...removedAfter].sort((x, y) => x - y) });
|
|
29906
|
+
}
|
|
29907
|
+
return out2;
|
|
29908
|
+
}
|
|
29381
29909
|
function rangeMessages(frame, range) {
|
|
29382
29910
|
if (range.kind !== "push" || !range.commits || range.commits.length === 0) return "";
|
|
29383
29911
|
const commits = range.commits.slice(0, 100);
|
|
@@ -30120,7 +30648,6 @@ async function sendSkipBeacon(ctx, reason) {
|
|
|
30120
30648
|
}
|
|
30121
30649
|
|
|
30122
30650
|
// src/lib/static-analysis.ts
|
|
30123
|
-
var import_node_child_process12 = require("node:child_process");
|
|
30124
30651
|
var import_node_module = require("node:module");
|
|
30125
30652
|
var import_node_path37 = require("node:path");
|
|
30126
30653
|
var import_node_fs47 = require("node:fs");
|
|
@@ -30130,9 +30657,9 @@ var import_node_fs46 = require("node:fs");
|
|
|
30130
30657
|
var import_node_path36 = require("node:path");
|
|
30131
30658
|
var TRIVY_PATTERNS = [
|
|
30132
30659
|
"Trivy_secret",
|
|
30133
|
-
"Trivy_malicious_packages",
|
|
30134
30660
|
"Trivy_vulnerability_critical",
|
|
30135
|
-
"Trivy_vulnerability_high"
|
|
30661
|
+
"Trivy_vulnerability_high",
|
|
30662
|
+
"Trivy_malicious_packages"
|
|
30136
30663
|
];
|
|
30137
30664
|
var TRIVY_TOOL = "trivy";
|
|
30138
30665
|
function trivyConfig() {
|
|
@@ -30171,6 +30698,14 @@ var SEVERITY_ORDER = {
|
|
|
30171
30698
|
function isCodacyAvailable() {
|
|
30172
30699
|
return resolveAnalyzer() !== null;
|
|
30173
30700
|
}
|
|
30701
|
+
var ANALYZER_PRIVACY_ENV = {
|
|
30702
|
+
TRIVY_DISABLE_TELEMETRY: "true",
|
|
30703
|
+
TRIVY_OFFLINE_SCAN: "true",
|
|
30704
|
+
CODACY_DISABLE_UPDATE_CHECK: "1"
|
|
30705
|
+
};
|
|
30706
|
+
function analyzerEnv(env = process.env) {
|
|
30707
|
+
return { ...env, ...ANALYZER_PRIVACY_ENV };
|
|
30708
|
+
}
|
|
30174
30709
|
var NO_BUNDLED_ANALYZER_ENV = "VERITY_NO_BUNDLED_ANALYZER";
|
|
30175
30710
|
function bundledAnalyzer(from = __filename) {
|
|
30176
30711
|
try {
|
|
@@ -30199,7 +30734,7 @@ function analyzerCommand(analyzer, argv, execPath = process.execPath) {
|
|
|
30199
30734
|
return { command: analyzer.path, args: argv };
|
|
30200
30735
|
}
|
|
30201
30736
|
function prewarmFile(root = repoRoot()) {
|
|
30202
|
-
const listed = (
|
|
30737
|
+
const listed = spawnSync2("git", ["ls-files", "-z"], { cwd: root, encoding: "utf-8", maxBuffer: 64 * 1024 * 1024 });
|
|
30203
30738
|
if (listed.status !== 0 || typeof listed.stdout !== "string") return null;
|
|
30204
30739
|
for (const file of listed.stdout.split("\0")) {
|
|
30205
30740
|
if (!file) continue;
|
|
@@ -30232,10 +30767,11 @@ function startTrivyPrewarm(opts = {}) {
|
|
|
30232
30767
|
const file = prewarmFile();
|
|
30233
30768
|
if (!file) return false;
|
|
30234
30769
|
const { command, args: args2 } = analyzerCommand(analyzer, buildAnalyzerArgv([file], config));
|
|
30235
|
-
const child = (opts.run ??
|
|
30770
|
+
const child = (opts.run ?? spawn2)(command, args2, {
|
|
30236
30771
|
cwd: repoRoot(),
|
|
30237
30772
|
detached: true,
|
|
30238
|
-
stdio: "ignore"
|
|
30773
|
+
stdio: "ignore",
|
|
30774
|
+
env: analyzerEnv(opts.env ?? process.env)
|
|
30239
30775
|
});
|
|
30240
30776
|
child.on("error", () => {
|
|
30241
30777
|
});
|
|
@@ -30312,10 +30848,11 @@ function runCodacyAnalysis(files, opts = {}) {
|
|
|
30312
30848
|
const resolved = resolveAnalyzer();
|
|
30313
30849
|
const { command, args: args2 } = resolved ? analyzerCommand(resolved, argv) : { command: "codacy-analysis", args: argv };
|
|
30314
30850
|
const timeoutMs = opts.timeoutMs ?? ANALYZER_TIMEOUT_MS;
|
|
30315
|
-
const proc = (
|
|
30851
|
+
const proc = spawnSync2(command, args2, {
|
|
30316
30852
|
encoding: "utf-8",
|
|
30317
30853
|
maxBuffer: 10 * 1024 * 1024,
|
|
30318
|
-
timeout: timeoutMs
|
|
30854
|
+
timeout: timeoutMs,
|
|
30855
|
+
env: analyzerEnv()
|
|
30319
30856
|
});
|
|
30320
30857
|
if (proc.error?.code === "ETIMEDOUT") {
|
|
30321
30858
|
const background = (opts.prewarm ?? startTrivyPrewarm)();
|
|
@@ -30682,6 +31219,7 @@ function discoverSpecs(consulted = []) {
|
|
|
30682
31219
|
const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
|
|
30683
31220
|
if (totalBytes >= totalCap) return false;
|
|
30684
31221
|
if (seen.has(specPath)) return true;
|
|
31222
|
+
if (isSecretShapedPath(specPath)) return true;
|
|
30685
31223
|
if (!(0, import_node_fs48.existsSync)(specPath)) return true;
|
|
30686
31224
|
seen.add(specPath);
|
|
30687
31225
|
const remaining = totalCap - totalBytes;
|
|
@@ -30751,7 +31289,7 @@ function discoverPlans() {
|
|
|
30751
31289
|
continue;
|
|
30752
31290
|
}
|
|
30753
31291
|
for (const f of names2) {
|
|
30754
|
-
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
31292
|
+
if (!f.endsWith(".md") || seen.has(f) || isSecretShapedPath(f)) continue;
|
|
30755
31293
|
seen.add(f);
|
|
30756
31294
|
const relPath = source2.prefix ? (0, import_node_path38.join)(source2.prefix, f) : f;
|
|
30757
31295
|
const stat3 = statRegularInRoot(source2.root, relPath);
|
|
@@ -30787,6 +31325,7 @@ function discoverGuardDocs(rangeFiles2) {
|
|
|
30787
31325
|
for (const path of rangeFiles2) {
|
|
30788
31326
|
if (result.length >= MAX_SPEC_FILES) break;
|
|
30789
31327
|
if (!GUARD_DOC_EXT.test(path)) continue;
|
|
31328
|
+
if (isSecretShapedPath(path)) continue;
|
|
30790
31329
|
if (path.startsWith("/") || path.includes("..")) continue;
|
|
30791
31330
|
if (!(0, import_node_fs48.existsSync)(path)) continue;
|
|
30792
31331
|
const opened = openRegularInRoot(process.cwd(), path);
|
|
@@ -31566,7 +32105,7 @@ async function evidence(run4) {
|
|
|
31566
32105
|
run4,
|
|
31567
32106
|
"No files within size limits to analyze",
|
|
31568
32107
|
"size-limit",
|
|
31569
|
-
codeDelta.excluded.
|
|
32108
|
+
codeDelta.excluded.some((e) => e.kind === "capacity") ? "capacity" : "policy"
|
|
31570
32109
|
);
|
|
31571
32110
|
}
|
|
31572
32111
|
}
|
|
@@ -31671,6 +32210,10 @@ function gatherContextFiles(contextPaths, deltaFiles, opts) {
|
|
|
31671
32210
|
logEvent("context_file_skipped", { path: filePath, reason: "verity_owned" });
|
|
31672
32211
|
continue;
|
|
31673
32212
|
}
|
|
32213
|
+
if (isSecretShapedPath(filePath)) {
|
|
32214
|
+
logEvent("context_file_skipped", { path: filePath, reason: "secret_shaped" });
|
|
32215
|
+
continue;
|
|
32216
|
+
}
|
|
31674
32217
|
const safePath2 = resolveInside(process.cwd(), filePath);
|
|
31675
32218
|
if (!safePath2) {
|
|
31676
32219
|
logEvent("context_file_skipped", { path: filePath, reason: "outside_repo" });
|
|
@@ -31994,7 +32537,7 @@ function definitionsIn(root, spec) {
|
|
|
31994
32537
|
} else if (p.type === "arguments" && moduleCall(p.parent)) {
|
|
31995
32538
|
const call = p.parent;
|
|
31996
32539
|
const route = p.namedChildren[0];
|
|
31997
|
-
const quoted = route && route.id !== n2.id && route.type === "string" ?
|
|
32540
|
+
const quoted = route && route.id !== n2.id && route.type === "string" ? unquote3(route.text) : null;
|
|
31998
32541
|
const path = quoted && /^[\w/:.{}*-]{1,60}$/.test(quoted) ? quoted : null;
|
|
31999
32542
|
const callee = call.childForFieldName("function").text;
|
|
32000
32543
|
add(path ? `${callee} ${path}` : callee, kind3, call.parent, n2, call);
|
|
@@ -32009,7 +32552,7 @@ function moduleCall(call) {
|
|
|
32009
32552
|
const f = call?.type === "call_expression" ? call.childForFieldName("function") : null;
|
|
32010
32553
|
return !!f && (f.type === "identifier" || f.type === "member_expression") && f.text.length <= 40 && !f.text.includes("\n") && call.parent?.type === "expression_statement" && call.parent.parent?.type === "program";
|
|
32011
32554
|
}
|
|
32012
|
-
function
|
|
32555
|
+
function unquote3(s) {
|
|
32013
32556
|
const body2 = s.replace(/^[rRbBuUfF]{0,2}(?=["'`])/, "");
|
|
32014
32557
|
const q3 = body2.startsWith('"""') || body2.startsWith("'''");
|
|
32015
32558
|
return q3 ? body2.slice(3, -3) : body2.slice(1, -1);
|
|
@@ -32032,8 +32575,8 @@ function argsOf(args2, spec) {
|
|
|
32032
32575
|
return list2.slice(0, MAX_ARGS).map((a) => {
|
|
32033
32576
|
let literal = null;
|
|
32034
32577
|
const left = a.type === "binary_expression" ? a.childForFieldName("left") : null;
|
|
32035
|
-
if (spec.strings.has(a.type)) literal =
|
|
32036
|
-
else if (left && spec.strings.has(left.type)) literal = `${
|
|
32578
|
+
if (spec.strings.has(a.type)) literal = unquote3(a.text);
|
|
32579
|
+
else if (left && spec.strings.has(left.type)) literal = `${unquote3(left.text)}${PLACEHOLDER}`;
|
|
32037
32580
|
return { literal: literal === null ? null : cap(literal, LITERAL_CAP), text: cap(a.text, TEXT_CAP) };
|
|
32038
32581
|
});
|
|
32039
32582
|
}
|
|
@@ -32648,7 +33191,7 @@ function accessMap(input) {
|
|
|
32648
33191
|
}
|
|
32649
33192
|
};
|
|
32650
33193
|
consider(fileFns, path, true, state);
|
|
32651
|
-
const
|
|
33194
|
+
const terms2 = [];
|
|
32652
33195
|
let files = 0;
|
|
32653
33196
|
let truncated = false;
|
|
32654
33197
|
if (input.source) {
|
|
@@ -32659,7 +33202,7 @@ function accessMap(input) {
|
|
|
32659
33202
|
truncated = true;
|
|
32660
33203
|
break;
|
|
32661
33204
|
}
|
|
32662
|
-
|
|
33205
|
+
terms2.push(term);
|
|
32663
33206
|
const found = input.source.search(term, { maxHits: 200, timeoutMs: Math.max(100, Math.min(800, input.deadlineAt - Date.now())), sourceOnly: true });
|
|
32664
33207
|
if (!found.ok) {
|
|
32665
33208
|
truncated = true;
|
|
@@ -32706,7 +33249,7 @@ function accessMap(input) {
|
|
|
32706
33249
|
swallowedWrites: [...new Set(effects.filter((e) => e.op === "write" && e.caught === "swallow").map((e) => e.line))]
|
|
32707
33250
|
},
|
|
32708
33251
|
unknownResources: effects.filter((e) => e.resource.name === null || e.resource.confidence === "unknown").length,
|
|
32709
|
-
searched: { terms, files, truncated }
|
|
33252
|
+
searched: { terms: terms2, files, truncated }
|
|
32710
33253
|
};
|
|
32711
33254
|
}
|
|
32712
33255
|
function renderEffect(e) {
|
|
@@ -33203,9 +33746,9 @@ function tsCandidates(spec, fromPath, cfg) {
|
|
|
33203
33746
|
}
|
|
33204
33747
|
const out2 = [];
|
|
33205
33748
|
for (const b of bases) {
|
|
33206
|
-
const
|
|
33749
|
+
const stem2 = b.replace(/\.(?:js|jsx|mjs|cjs)$/, "");
|
|
33207
33750
|
if (languageOf2(b)) out2.push(b);
|
|
33208
|
-
for (const e of TS_EXTS) out2.push(`${
|
|
33751
|
+
for (const e of TS_EXTS) out2.push(`${stem2}${e}`);
|
|
33209
33752
|
for (const e of TS_EXTS) out2.push(`${b}/index${e}`);
|
|
33210
33753
|
}
|
|
33211
33754
|
return [...new Set(out2)];
|
|
@@ -33802,10 +34345,10 @@ var writersOfTool = async (ctx, args2) => {
|
|
|
33802
34345
|
if (name2.length < 2) return { ok: false, error: "resource name too short to search for" };
|
|
33803
34346
|
const deadline = Date.now() + TOOL_MS;
|
|
33804
34347
|
const src = source(ctx);
|
|
33805
|
-
const
|
|
34348
|
+
const terms2 = [.../* @__PURE__ */ new Set([name2, ...searchTerms({ kind: "table", name: name2, confidence: "inferred" })])];
|
|
33806
34349
|
const files = /* @__PURE__ */ new Set();
|
|
33807
34350
|
let truncated = false;
|
|
33808
|
-
for (const t2 of
|
|
34351
|
+
for (const t2 of terms2) {
|
|
33809
34352
|
const found = src.search(t2, { maxHits: 300, timeoutMs: Math.max(100, deadline - Date.now()) });
|
|
33810
34353
|
if (!found.ok) {
|
|
33811
34354
|
truncated = true;
|
|
@@ -33867,17 +34410,38 @@ var TOOL_LIMITS = {
|
|
|
33867
34410
|
diffTimeoutMs: 1e4,
|
|
33868
34411
|
concurrency: 4
|
|
33869
34412
|
};
|
|
33870
|
-
var DENIED_PATHS = [
|
|
33871
|
-
/(^|\/)\.env(\.[^/]*)?$/i,
|
|
33872
|
-
/\.(pem|key|p12|pfx|jks|keystore|asc|gpg|kdbx)$/i,
|
|
33873
|
-
/(^|\/)id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,
|
|
33874
|
-
/(^|\/)\.(npmrc|netrc|pypirc|pgpass|git-credentials)$/i,
|
|
33875
|
-
/(^|\/)(credentials|secrets?)(\.[^/]*)?$/i,
|
|
33876
|
-
/(^|\/)\.(ssh|aws|gnupg|docker)(\/|$)/i,
|
|
33877
|
-
/(^|\/)\.git(\/|$)/
|
|
33878
|
-
];
|
|
33879
34413
|
function isDeniedPath(path) {
|
|
33880
|
-
return
|
|
34414
|
+
return isSecretShapedPath(path) || isVerityOwnedPath(path);
|
|
34415
|
+
}
|
|
34416
|
+
function diffChunkPaths(chunk) {
|
|
34417
|
+
const out2 = /* @__PURE__ */ new Set();
|
|
34418
|
+
const header = /^diff --git a\/(.+?) b\/(.+)$/m.exec(chunk);
|
|
34419
|
+
if (header) {
|
|
34420
|
+
out2.add(header[1]);
|
|
34421
|
+
out2.add(header[2]);
|
|
34422
|
+
}
|
|
34423
|
+
for (const re of [/^--- a\/(.+)$/m, /^\+\+\+ b\/(.+)$/m, /^rename from (.+)$/m, /^rename to (.+)$/m, /^copy from (.+)$/m, /^copy to (.+)$/m]) {
|
|
34424
|
+
const m = re.exec(chunk);
|
|
34425
|
+
if (m) out2.add(m[1]);
|
|
34426
|
+
}
|
|
34427
|
+
return [...out2];
|
|
34428
|
+
}
|
|
34429
|
+
function withoutDeniedDiffs(diff) {
|
|
34430
|
+
const kept = [];
|
|
34431
|
+
const withheld = /* @__PURE__ */ new Set();
|
|
34432
|
+
for (const chunk of diff.split(/^(?=diff --git )/m)) {
|
|
34433
|
+
if (!chunk.startsWith("diff --git ")) {
|
|
34434
|
+
if (chunk.trim()) kept.push(chunk);
|
|
34435
|
+
continue;
|
|
34436
|
+
}
|
|
34437
|
+
const paths = diffChunkPaths(chunk);
|
|
34438
|
+
if (paths.length === 0 || paths.some((p) => isDeniedPath(p))) {
|
|
34439
|
+
withheld.add(paths[paths.length - 1] ?? "(a path that could not be read)");
|
|
34440
|
+
continue;
|
|
34441
|
+
}
|
|
34442
|
+
kept.push(chunk);
|
|
34443
|
+
}
|
|
34444
|
+
return { text: kept.join(""), withheld: [...withheld] };
|
|
33881
34445
|
}
|
|
33882
34446
|
function validateRepoPath(value, { allowRoot = false } = {}) {
|
|
33883
34447
|
if (typeof value !== "string") return { ok: false, error: "path must be a string" };
|
|
@@ -33984,7 +34548,12 @@ var gitDiffTool = async (ctx, args2) => {
|
|
|
33984
34548
|
}
|
|
33985
34549
|
const res = await git(ctx, ["--literal-pathspecs", ...diffArgs, "--", path.value]);
|
|
33986
34550
|
if (!res.ok) return { ok: false, error: gitFailure(res, "diff") };
|
|
33987
|
-
return { ok: true, value:
|
|
34551
|
+
if (!res.stdout.trim()) return { ok: true, value: `no changes to ${path.value} in the change under review` };
|
|
34552
|
+
const { text: text3, withheld } = withoutDeniedDiffs(res.stdout);
|
|
34553
|
+
const note = withheld.length > 0 ? `
|
|
34554
|
+
[not shown: ${withheld.length} file(s) shaped like secrets or owned by Verity: ${withheld.slice(0, 20).join(", ")}]` : "";
|
|
34555
|
+
if (!text3.trim()) return { ok: true, value: `no reviewable changes to ${path.value} in the change under review${note}` };
|
|
34556
|
+
return { ok: true, value: capOutput(text3) + note };
|
|
33988
34557
|
};
|
|
33989
34558
|
var gitLogTool = async (ctx, args2) => {
|
|
33990
34559
|
const path = validateRepoPath(args2.path);
|
|
@@ -34361,23 +34930,23 @@ function deriveSeedNodes(inputs) {
|
|
|
34361
34930
|
const out2 = [];
|
|
34362
34931
|
const ks = inputs.knowledgeSpec;
|
|
34363
34932
|
if (ks.project_name) {
|
|
34364
|
-
const
|
|
34933
|
+
const bodyLines2 = [];
|
|
34365
34934
|
const arch3 = ks.architecture ? `${ks.architecture} ` : "";
|
|
34366
34935
|
const langs = (ks.languages ?? []).filter(Boolean);
|
|
34367
34936
|
const frameworks2 = (ks.frameworks ?? []).filter(Boolean);
|
|
34368
34937
|
const primaryLang = langs[0] ?? "software";
|
|
34369
|
-
|
|
34370
|
-
|
|
34371
|
-
if (langs.length > 0)
|
|
34372
|
-
if (frameworks2.length > 0)
|
|
34373
|
-
if (ks.build_system)
|
|
34374
|
-
if (ks.test_framework)
|
|
34375
|
-
|
|
34376
|
-
|
|
34938
|
+
bodyLines2.push(`**${ks.project_name}** is a ${arch3}${primaryLang} project.`);
|
|
34939
|
+
bodyLines2.push("");
|
|
34940
|
+
if (langs.length > 0) bodyLines2.push(`- **Languages:** ${langs.join(", ")}`);
|
|
34941
|
+
if (frameworks2.length > 0) bodyLines2.push(`- **Frameworks:** ${frameworks2.join(", ")}`);
|
|
34942
|
+
if (ks.build_system) bodyLines2.push(`- **Build system:** ${ks.build_system}`);
|
|
34943
|
+
if (ks.test_framework) bodyLines2.push(`- **Test framework:** ${ks.test_framework}`);
|
|
34944
|
+
bodyLines2.push("");
|
|
34945
|
+
bodyLines2.push("_Generated from the Standard synthesized by `/verity-setup`. Edit this file to add project context._");
|
|
34377
34946
|
out2.push({
|
|
34378
34947
|
kind: "domain",
|
|
34379
34948
|
title: "Project overview",
|
|
34380
|
-
body:
|
|
34949
|
+
body: bodyLines2.join("\n"),
|
|
34381
34950
|
domains: [],
|
|
34382
34951
|
// Broad globs so the overview surfaces on any source file change.
|
|
34383
34952
|
// It's the project's anchor node — cheap to inject, high signal.
|
|
@@ -34457,10 +35026,10 @@ var NODE_DIRS = ["decisions", "quality", "security", "intent", "gotchas", "patte
|
|
|
34457
35026
|
function normalizeTitle(title) {
|
|
34458
35027
|
return title.trim().replace(/\s+/g, " ").toLowerCase();
|
|
34459
35028
|
}
|
|
34460
|
-
async function localSeedTitles(
|
|
35029
|
+
async function localSeedTitles(memoryDir4 = MEMORY_DIR) {
|
|
34461
35030
|
const titles = /* @__PURE__ */ new Set();
|
|
34462
35031
|
for (const dir of NODE_DIRS) {
|
|
34463
|
-
const full = (0, import_node_path42.join)(
|
|
35032
|
+
const full = (0, import_node_path42.join)(memoryDir4, dir);
|
|
34464
35033
|
if (!(0, import_node_fs53.existsSync)(full)) continue;
|
|
34465
35034
|
let files;
|
|
34466
35035
|
try {
|
|
@@ -34506,18 +35075,20 @@ function pickFrontmatterTitle(content) {
|
|
|
34506
35075
|
}
|
|
34507
35076
|
return raw;
|
|
34508
35077
|
}
|
|
34509
|
-
async function serverSeedTitles(opts) {
|
|
34510
|
-
const res = await apiRequest({
|
|
34511
|
-
method: "GET",
|
|
34512
|
-
path: "/compound/memory/nodes?created_by=seed&status=active&limit=100",
|
|
34513
|
-
serviceUrl: opts.serviceUrl,
|
|
34514
|
-
token: opts.token,
|
|
34515
|
-
verbose: opts.verbose,
|
|
34516
|
-
cmd: "memory_seed"
|
|
34517
|
-
});
|
|
34518
|
-
if (!res.ok || !Array.isArray(res.data.nodes)) return null;
|
|
35078
|
+
async function serverSeedTitles(opts, statuses = ["active"]) {
|
|
34519
35079
|
const titles = /* @__PURE__ */ new Set();
|
|
34520
|
-
for (const
|
|
35080
|
+
for (const status of statuses) {
|
|
35081
|
+
const res = await apiRequest({
|
|
35082
|
+
method: "GET",
|
|
35083
|
+
path: `/compound/memory/nodes?created_by=seed&status=${encodeURIComponent(status)}&limit=100`,
|
|
35084
|
+
serviceUrl: opts.serviceUrl,
|
|
35085
|
+
token: opts.token,
|
|
35086
|
+
verbose: opts.verbose,
|
|
35087
|
+
cmd: "memory_seed"
|
|
35088
|
+
});
|
|
35089
|
+
if (!res.ok || !Array.isArray(res.data.nodes)) return null;
|
|
35090
|
+
for (const n2 of res.data.nodes) if (typeof n2?.title === "string") titles.add(normalizeTitle(n2.title));
|
|
35091
|
+
}
|
|
34521
35092
|
return titles;
|
|
34522
35093
|
}
|
|
34523
35094
|
function renderNodeMarkdown(candidate, nodeId, createdAt) {
|
|
@@ -34608,6 +35179,13 @@ async function runSeed(opts) {
|
|
|
34608
35179
|
if (toPost.length === 0) {
|
|
34609
35180
|
return { created: 0, existing: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
|
|
34610
35181
|
}
|
|
35182
|
+
const retired = await serverSeedTitles(opts, ["archived", "superseded"]);
|
|
35183
|
+
if (retired) {
|
|
35184
|
+
toPost = toPost.filter((c) => !retired.has(normalizeTitle(c.title)));
|
|
35185
|
+
if (toPost.length === 0) {
|
|
35186
|
+
return { created: 0, existing: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
|
|
35187
|
+
}
|
|
35188
|
+
}
|
|
34611
35189
|
}
|
|
34612
35190
|
}
|
|
34613
35191
|
}
|
|
@@ -34657,8 +35235,9 @@ async function runSeed(opts) {
|
|
|
34657
35235
|
}
|
|
34658
35236
|
const content = typeof res.data.content === "string" && res.data.content.length > 0 ? res.data.content : renderNodeMarkdown(c, nodeId, createdAt);
|
|
34659
35237
|
try {
|
|
34660
|
-
|
|
34661
|
-
|
|
35238
|
+
if (!await writeContainedNodeFile(targetPath, content)) {
|
|
35239
|
+
throw new Error("refused: a link, or a folder outside the repository");
|
|
35240
|
+
}
|
|
34662
35241
|
if (deduplicated) existing++;
|
|
34663
35242
|
else {
|
|
34664
35243
|
created++;
|
|
@@ -34997,7 +35576,6 @@ function isExplicitlyAutonomous(env = process.env) {
|
|
|
34997
35576
|
}
|
|
34998
35577
|
|
|
34999
35578
|
// src/lib/task-context.ts
|
|
35000
|
-
var import_node_child_process13 = require("node:child_process");
|
|
35001
35579
|
var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
|
|
35002
35580
|
var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
|
|
35003
35581
|
function parseLinkedIssue(sources) {
|
|
@@ -35013,7 +35591,7 @@ function parseLinkedIssue(sources) {
|
|
|
35013
35591
|
}
|
|
35014
35592
|
function safeExec(cmd, timeout) {
|
|
35015
35593
|
try {
|
|
35016
|
-
return (
|
|
35594
|
+
return execSync2(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
|
|
35017
35595
|
} catch {
|
|
35018
35596
|
return "";
|
|
35019
35597
|
}
|
|
@@ -35791,7 +36369,6 @@ function describeRelease(release, input) {
|
|
|
35791
36369
|
}
|
|
35792
36370
|
|
|
35793
36371
|
// src/lib/memory-pull.ts
|
|
35794
|
-
var import_node_child_process14 = require("node:child_process");
|
|
35795
36372
|
var import_promises12 = require("node:fs/promises");
|
|
35796
36373
|
var pullStateFile = () => projectPath(`${VERITY_DIR}/.memory-pull-state.json`);
|
|
35797
36374
|
var PAGE_SIZE = 200;
|
|
@@ -35882,6 +36459,13 @@ async function recordServedFiles(served) {
|
|
|
35882
36459
|
for (const [path, hash] of served) state.served.set(path, hash);
|
|
35883
36460
|
await writePullState(state);
|
|
35884
36461
|
}
|
|
36462
|
+
async function forgetServedFiles(paths) {
|
|
36463
|
+
if (paths.length === 0) return;
|
|
36464
|
+
const state = await readPullState();
|
|
36465
|
+
let changed = false;
|
|
36466
|
+
for (const p of paths) changed = state.served.delete(p) || changed;
|
|
36467
|
+
if (changed) await writePullState(state);
|
|
36468
|
+
}
|
|
35885
36469
|
async function readPullState() {
|
|
35886
36470
|
try {
|
|
35887
36471
|
const parsed = JSON.parse(await (0, import_promises12.readFile)(pullStateFile(), "utf-8"));
|
|
@@ -35914,7 +36498,7 @@ function pullsAtSessionStart(auth3, source2) {
|
|
|
35914
36498
|
}
|
|
35915
36499
|
function spawnBackgroundPull(deps = {}) {
|
|
35916
36500
|
const d = {
|
|
35917
|
-
spawn:
|
|
36501
|
+
spawn: spawn2,
|
|
35918
36502
|
execPath: process.execPath,
|
|
35919
36503
|
script: process.argv[1],
|
|
35920
36504
|
cwd: process.cwd(),
|
|
@@ -36371,12 +36955,10 @@ async function runAnalyze(opts, globals) {
|
|
|
36371
36955
|
|
|
36372
36956
|
// src/lib/prove/lean-background.ts
|
|
36373
36957
|
var import_node_fs63 = require("node:fs");
|
|
36374
|
-
var import_node_child_process17 = require("node:child_process");
|
|
36375
36958
|
var import_node_crypto18 = require("node:crypto");
|
|
36376
36959
|
var import_node_path51 = require("node:path");
|
|
36377
36960
|
|
|
36378
36961
|
// src/lib/prove/runner.ts
|
|
36379
|
-
var import_node_child_process15 = require("node:child_process");
|
|
36380
36962
|
var import_node_os9 = require("node:os");
|
|
36381
36963
|
var import_node_fs61 = require("node:fs");
|
|
36382
36964
|
var import_node_os10 = require("node:os");
|
|
@@ -37114,7 +37696,7 @@ function findToolchain(env = process.env) {
|
|
|
37114
37696
|
const candidates2 = [(0, import_node_path49.join)(managedBin, exe), ...elanHas ? [(0, import_node_path49.join)(elanBin, exe)] : [], exe];
|
|
37115
37697
|
for (const candidate of candidates2) {
|
|
37116
37698
|
try {
|
|
37117
|
-
(
|
|
37699
|
+
execFileSync2(candidate, ["--version"], { env: merged, stdio: ["ignore", "pipe", "pipe"], timeout: 1e4 });
|
|
37118
37700
|
return { lake: candidate, env: merged };
|
|
37119
37701
|
} catch {
|
|
37120
37702
|
}
|
|
@@ -37125,7 +37707,7 @@ var INSTALL_HINT = "Lean 4 is not installed. Run `verity lean install` (official
|
|
|
37125
37707
|
function run2(tc, cwd, args2, timeoutMs) {
|
|
37126
37708
|
const started = Date.now();
|
|
37127
37709
|
try {
|
|
37128
|
-
const stdout = (
|
|
37710
|
+
const stdout = execFileSync2(tc.lake, args2, {
|
|
37129
37711
|
cwd,
|
|
37130
37712
|
env: tc.env,
|
|
37131
37713
|
encoding: "utf-8",
|
|
@@ -37151,7 +37733,7 @@ function runAsync(tc, cwd, args2, timeoutMs) {
|
|
|
37151
37733
|
const MAX = 16 * 1024 * 1024;
|
|
37152
37734
|
return new Promise((resolve10) => {
|
|
37153
37735
|
const posix = (0, import_node_os9.platform)() !== "win32";
|
|
37154
|
-
const child = (
|
|
37736
|
+
const child = spawn2(tc.lake, args2, { cwd, env: tc.env, stdio: ["ignore", "pipe", "pipe"], detached: posix });
|
|
37155
37737
|
let stdout = "";
|
|
37156
37738
|
let stderr = "";
|
|
37157
37739
|
let timedOut = false;
|
|
@@ -37238,18 +37820,21 @@ function listModelFiles(root) {
|
|
|
37238
37820
|
function trimDiagnostics(text3) {
|
|
37239
37821
|
return text3.split("\n").filter((l) => !l.startsWith("trace:")).join("\n").trim().slice(0, 4e3);
|
|
37240
37822
|
}
|
|
37823
|
+
function leanFileArg(rel) {
|
|
37824
|
+
return rel.startsWith("-") ? `./${rel}` : rel;
|
|
37825
|
+
}
|
|
37241
37826
|
function checkModel(tc, root, modelFile, opts = {}) {
|
|
37242
37827
|
const proofsAbs = (0, import_node_path49.join)(root, PROOFS_DIR);
|
|
37243
37828
|
const rel = (0, import_node_path49.relative)(proofsAbs, modelFile);
|
|
37244
37829
|
const problem = proofsWorkspaceProblem(root);
|
|
37245
|
-
const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37830
|
+
const res = problem ? refused(problem) : run2(tc, proofsAbs, ["env", "lean", leanFileArg(rel)], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37246
37831
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37247
37832
|
}
|
|
37248
37833
|
async function checkModelAsync(tc, root, modelFile, opts = {}) {
|
|
37249
37834
|
const proofsAbs = (0, import_node_path49.join)(root, PROOFS_DIR);
|
|
37250
37835
|
const rel = (0, import_node_path49.relative)(proofsAbs, modelFile);
|
|
37251
37836
|
const problem = proofsWorkspaceProblem(root);
|
|
37252
|
-
const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", rel], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37837
|
+
const res = problem ? refused(problem) : await runAsync(tc, proofsAbs, ["env", "lean", leanFileArg(rel)], opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37253
37838
|
return toReport(res, root, modelFile, opts.timeoutMs ?? DEFAULT_CHECK_TIMEOUT_MS);
|
|
37254
37839
|
}
|
|
37255
37840
|
function toReport(res, root, modelFile, timeoutMs) {
|
|
@@ -37303,7 +37888,6 @@ function writeResult(root, report) {
|
|
|
37303
37888
|
// src/lib/prove/lean-install.ts
|
|
37304
37889
|
var import_node_fs62 = require("node:fs");
|
|
37305
37890
|
var import_node_crypto17 = require("node:crypto");
|
|
37306
|
-
var import_node_child_process16 = require("node:child_process");
|
|
37307
37891
|
var import_node_os11 = require("node:os");
|
|
37308
37892
|
var import_node_path50 = require("node:path");
|
|
37309
37893
|
var import_promises13 = require("node:stream/promises");
|
|
@@ -37374,7 +37958,7 @@ function unzip(zip, dest) {
|
|
|
37374
37958
|
const errors = [];
|
|
37375
37959
|
for (const [cmd, args2] of attempts) {
|
|
37376
37960
|
try {
|
|
37377
|
-
(
|
|
37961
|
+
execFileSync2(cmd, args2, { stdio: ["ignore", "ignore", "pipe"], timeout: 15 * 6e4 });
|
|
37378
37962
|
if ((0, import_node_fs62.readdirSync)(dest).length > 0) return { ok: true, via: cmd };
|
|
37379
37963
|
errors.push(`${cmd}: produced nothing`);
|
|
37380
37964
|
} catch (err2) {
|
|
@@ -37577,7 +38161,7 @@ function startBackgroundInstall(opts) {
|
|
|
37577
38161
|
(0, import_node_fs63.mkdirSync)(home, { recursive: true });
|
|
37578
38162
|
const log = (0, import_node_fs63.openSync)((0, import_node_path51.join)(home, LOG_FILE), import_node_fs63.constants.O_WRONLY | import_node_fs63.constants.O_APPEND | import_node_fs63.constants.O_CREAT | (import_node_fs63.constants.O_NOFOLLOW ?? 0), 384);
|
|
37579
38163
|
try {
|
|
37580
|
-
const child = (opts.run ??
|
|
38164
|
+
const child = (opts.run ?? spawn2)(process.execPath, [opts.cli, "lean", "install", "--yes", "--background"], {
|
|
37581
38165
|
detached: true,
|
|
37582
38166
|
stdio: ["ignore", log, log],
|
|
37583
38167
|
env: { ...env, DO_NOT_TRACK: env.DO_NOT_TRACK ?? "1" }
|
|
@@ -38424,6 +39008,7 @@ var deterministicRanker = {
|
|
|
38424
39008
|
}
|
|
38425
39009
|
};
|
|
38426
39010
|
function excerpt(root, file, bytes, readSource, changed, codeMap, codeSource) {
|
|
39011
|
+
if (isSecretShapedPath(file)) return "";
|
|
38427
39012
|
const read2 = readSource ? readSource(file, EXCERPT_SOURCE_CAP_BYTES) : readRegularFileInRoot(root, file, EXCERPT_SOURCE_CAP_BYTES);
|
|
38428
39013
|
if (!read2.ok) return "";
|
|
38429
39014
|
if (changed && changed.length > 0) {
|
|
@@ -39554,7 +40139,7 @@ function isTrackedInRoot(root, file) {
|
|
|
39554
40139
|
}
|
|
39555
40140
|
function buildReplayBrief(root, report, property, modelSource, readSource) {
|
|
39556
40141
|
const sources = (report.header?.abstracts ?? []).map((a) => {
|
|
39557
|
-
const read2 = readSource ? readSource(a.file, MAX_SOURCE_BYTES) : isTrackedInRoot(root, a.file) ? readRegularFileInRoot(root, a.file, MAX_SOURCE_BYTES) : { ok: false, reason: "not tracked by git \u2014 models may only abstract tracked source" };
|
|
40142
|
+
const read2 = isSecretShapedPath(a.file) ? { ok: false, reason: SECRET_REFUSED_READ } : readSource ? readSource(a.file, MAX_SOURCE_BYTES) : isTrackedInRoot(root, a.file) ? readRegularFileInRoot(root, a.file, MAX_SOURCE_BYTES) : { ok: false, reason: "not tracked by git \u2014 models may only abstract tracked source" };
|
|
39558
40143
|
const text3 = read2.ok ? read2.content : "";
|
|
39559
40144
|
return {
|
|
39560
40145
|
file: a.file,
|
|
@@ -40307,10 +40892,20 @@ function isNetworkError(message) {
|
|
|
40307
40892
|
var GENERATE_RESERVE_MS = 4e4;
|
|
40308
40893
|
function workingTreeReader(root) {
|
|
40309
40894
|
return (file, maxBytes) => {
|
|
40895
|
+
if (isSecretShapedPath(file)) return { ok: false, reason: SECRET_REFUSED_READ };
|
|
40310
40896
|
const r = readRegularFileInRoot(root, file, maxBytes);
|
|
40311
40897
|
return r.ok ? { ok: true, content: r.content } : { ok: false, reason: r.reason };
|
|
40312
40898
|
};
|
|
40313
40899
|
}
|
|
40900
|
+
function pushedTipReader(tip) {
|
|
40901
|
+
return (file, maxBytes) => {
|
|
40902
|
+
if (isSecretShapedPath(file)) return { ok: false, reason: SECRET_REFUSED_READ };
|
|
40903
|
+
const content = showContentAtRef(tip, file);
|
|
40904
|
+
if (content === null) return { ok: false, reason: `not at the pushed tip (${tip.slice(0, 12)})` };
|
|
40905
|
+
if (Buffer.byteLength(content) > maxBytes) return { ok: false, reason: "too-large" };
|
|
40906
|
+
return { ok: true, content };
|
|
40907
|
+
};
|
|
40908
|
+
}
|
|
40314
40909
|
function sha256Of(content) {
|
|
40315
40910
|
return (0, import_node_crypto21.createHash)("sha256").update(content).digest("hex");
|
|
40316
40911
|
}
|
|
@@ -41560,12 +42155,7 @@ function startFormalPhase(input) {
|
|
|
41560
42155
|
changedLines = void 0;
|
|
41561
42156
|
}
|
|
41562
42157
|
const tip = input.range.kind === "push" && input.range.head !== "INDEX" ? input.range.head : null;
|
|
41563
|
-
const readSource = tip ? (
|
|
41564
|
-
const content = showContentAtRef(tip, file);
|
|
41565
|
-
if (content === null) return { ok: false, reason: `not at the pushed tip (${tip.slice(0, 12)})` };
|
|
41566
|
-
if (Buffer.byteLength(content) > maxBytes) return { ok: false, reason: "too-large" };
|
|
41567
|
-
return { ok: true, content };
|
|
41568
|
-
} : void 0;
|
|
42158
|
+
const readSource = tip ? pushedTipReader(tip) : void 0;
|
|
41569
42159
|
const run4 = async () => {
|
|
41570
42160
|
const prep = await prepareFormalPhase(input.root, process.env, input.auth, readSource, withoutPaths(tip ? revisionSource(input.root, tip) : worktreeSource(input.root), isDeniedPath), { tier: profile.formal.tier });
|
|
41571
42161
|
if (!prep.ok) return skippedOutcome(prep.skip, prep.detail);
|
|
@@ -41636,7 +42226,8 @@ async function runGuard(opts, globals) {
|
|
|
41636
42226
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
41637
42227
|
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
41638
42228
|
const codeDelta = collectCodeDelta(files, GIT_MOMENT_BUDGET);
|
|
41639
|
-
|
|
42229
|
+
const withheld = codeDelta.excluded.some((e) => e.kind === "policy");
|
|
42230
|
+
if (codeDelta.total_files === 0 && !withheld) process.exit(0);
|
|
41640
42231
|
const statedIntent = extractStatedIntent(
|
|
41641
42232
|
moment,
|
|
41642
42233
|
command,
|
|
@@ -41651,6 +42242,14 @@ async function runGuard(opts, globals) {
|
|
|
41651
42242
|
statedIntent,
|
|
41652
42243
|
buildGuardCoverage(files, codeDelta, frame, range)
|
|
41653
42244
|
);
|
|
42245
|
+
const changedLines = rangeChangedLines(frame, range, codeDelta.files);
|
|
42246
|
+
if (changedLines.files.length > 0) requestBody2.changed_lines = changedLines.files;
|
|
42247
|
+
logEvent("guard_changed_lines", {
|
|
42248
|
+
moment,
|
|
42249
|
+
sent: codeDelta.files.length,
|
|
42250
|
+
with_ranges: changedLines.files.length,
|
|
42251
|
+
omitted: changedLines.omitted
|
|
42252
|
+
});
|
|
41654
42253
|
try {
|
|
41655
42254
|
const ig = loadVerityIgnore();
|
|
41656
42255
|
const reviewedRev = range.kind === "push" && range.head !== "INDEX" ? range.head : null;
|
|
@@ -41733,7 +42332,7 @@ async function runGuard(opts, globals) {
|
|
|
41733
42332
|
timeout: 29e4,
|
|
41734
42333
|
cmd: "guard"
|
|
41735
42334
|
});
|
|
41736
|
-
const agent = startAgentReview({
|
|
42335
|
+
const agent = codeDelta.total_files === 0 ? null : startAgentReview({
|
|
41737
42336
|
serviceUrl: urlResult.data,
|
|
41738
42337
|
token: tokenResult.data.token,
|
|
41739
42338
|
verbose: globals.verbose,
|
|
@@ -41804,7 +42403,11 @@ async function runGuard(opts, globals) {
|
|
|
41804
42403
|
const agentLine = describeAgentOutcome(agentOutcome);
|
|
41805
42404
|
const formalLine = describeFormalOutcome(formalOutcome) || (projectConfig ? formalOffUnverifiedLine(moment, projectConfig) : "");
|
|
41806
42405
|
const covDetail = [coverageBlock(coverage), agentLine, formalLine].filter(Boolean).join("\n ");
|
|
41807
|
-
const witnessed = [
|
|
42406
|
+
const witnessed = [
|
|
42407
|
+
...codeDelta.files.map((f) => f.path),
|
|
42408
|
+
...agentVerdict?.witnessed_files ?? [],
|
|
42409
|
+
...scannerWitnessedWithheld(codeDelta.excluded, requestBody2.static_results?.findings)
|
|
42410
|
+
];
|
|
41808
42411
|
mergeFormalFindings(response, formalOutcome, files);
|
|
41809
42412
|
decision = response.gate_decision ?? "(unrecognised)";
|
|
41810
42413
|
if (formalOutcome) {
|
|
@@ -43014,7 +43617,6 @@ var Rail = class {
|
|
|
43014
43617
|
var import_node_fs74 = require("node:fs");
|
|
43015
43618
|
var import_node_path62 = require("node:path");
|
|
43016
43619
|
var import_node_os13 = require("node:os");
|
|
43017
|
-
var import_node_child_process18 = require("node:child_process");
|
|
43018
43620
|
function createInitUi(opts) {
|
|
43019
43621
|
const term = opts.term ?? detectTerm();
|
|
43020
43622
|
const screen = new Screen(term);
|
|
@@ -43040,7 +43642,7 @@ function markInitSeen(home = process.env.HOME || (0, import_node_os13.homedir)()
|
|
|
43040
43642
|
}
|
|
43041
43643
|
function repoLabel() {
|
|
43042
43644
|
try {
|
|
43043
|
-
const url = (
|
|
43645
|
+
const url = execFileSync2("git", ["remote", "get-url", "origin"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
43044
43646
|
const parsed = parseRemote(url);
|
|
43045
43647
|
if (parsed) return terminalText(`${parsed.owner}/${parsed.repo}`, 120);
|
|
43046
43648
|
} catch {
|
|
@@ -43049,7 +43651,6 @@ function repoLabel() {
|
|
|
43049
43651
|
}
|
|
43050
43652
|
|
|
43051
43653
|
// src/lib/prereqs.ts
|
|
43052
|
-
var import_node_child_process19 = require("node:child_process");
|
|
43053
43654
|
var import_node_fs75 = require("node:fs");
|
|
43054
43655
|
var MIN_NODE_MAJOR = 20;
|
|
43055
43656
|
function which(bin) {
|
|
@@ -43071,7 +43672,7 @@ function checkNode() {
|
|
|
43071
43672
|
function checkGit() {
|
|
43072
43673
|
let detail2 = "";
|
|
43073
43674
|
try {
|
|
43074
|
-
detail2 = (
|
|
43675
|
+
detail2 = execFileSync2("git", ["--version"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
43075
43676
|
} catch {
|
|
43076
43677
|
return {
|
|
43077
43678
|
id: "git",
|
|
@@ -43126,7 +43727,7 @@ function sameFile(a, b) {
|
|
|
43126
43727
|
var INSTALL_TIMEOUT_MS = 12e4;
|
|
43127
43728
|
function run3(command, args2, opts = {}) {
|
|
43128
43729
|
return new Promise((resolve10) => {
|
|
43129
|
-
const child = (
|
|
43730
|
+
const child = spawn2(command, args2, {
|
|
43130
43731
|
stdio: opts.inherit ? "inherit" : "pipe",
|
|
43131
43732
|
timeout: INSTALL_TIMEOUT_MS
|
|
43132
43733
|
});
|
|
@@ -43325,12 +43926,11 @@ async function healLocalTelemetry(root = repoRoot()) {
|
|
|
43325
43926
|
}
|
|
43326
43927
|
|
|
43327
43928
|
// src/lib/telemetry-gate.ts
|
|
43328
|
-
var import_node_child_process20 = require("node:child_process");
|
|
43329
43929
|
var import_node_fs76 = require("node:fs");
|
|
43330
43930
|
var import_node_path65 = require("node:path");
|
|
43331
43931
|
var PROJECT_MARKERS = ["setup.json", "config.json"];
|
|
43332
43932
|
function writtenOnThisMachine(root, rel) {
|
|
43333
|
-
const r = (
|
|
43933
|
+
const r = spawnSync2("git", hardenedGitArgv(["ls-files", "--error-unmatch", "--", rel]), {
|
|
43334
43934
|
cwd: root,
|
|
43335
43935
|
env: hardenedGitEnv(),
|
|
43336
43936
|
encoding: "utf-8",
|
|
@@ -43660,7 +44260,6 @@ function backgroundStep(ui, opts) {
|
|
|
43660
44260
|
var import_promises15 = require("node:fs/promises");
|
|
43661
44261
|
|
|
43662
44262
|
// src/lib/codacy-legacy.ts
|
|
43663
|
-
var import_node_child_process21 = require("node:child_process");
|
|
43664
44263
|
var VERITY_ESLINT_CONFIG_FILE = ".codacy/tools-configs/eslint.config.mjs";
|
|
43665
44264
|
var VERITY_ESLINT_LOCAL_PATH = "./.codacy/tools-configs/eslint.config.mjs";
|
|
43666
44265
|
var VERITY_ESLINT_FIRST_LINE = "// Verity-owned ESLint config for the quality gate.";
|
|
@@ -43682,7 +44281,7 @@ function classifyCodacyConfig(root = repoRoot()) {
|
|
|
43682
44281
|
}
|
|
43683
44282
|
function isTracked(root, path) {
|
|
43684
44283
|
try {
|
|
43685
|
-
(
|
|
44284
|
+
execFileSync2("git", ["ls-files", "--error-unmatch", "--", path], { cwd: root, stdio: "pipe" });
|
|
43686
44285
|
return true;
|
|
43687
44286
|
} catch {
|
|
43688
44287
|
return false;
|
|
@@ -43697,7 +44296,7 @@ function removeVerityCodacyConfig(root = repoRoot()) {
|
|
|
43697
44296
|
untracked.push(f);
|
|
43698
44297
|
continue;
|
|
43699
44298
|
}
|
|
43700
|
-
(
|
|
44299
|
+
execFileSync2("git", ["rm", "--quiet", "--", f], { cwd: root, stdio: "pipe" });
|
|
43701
44300
|
removed.push(f);
|
|
43702
44301
|
}
|
|
43703
44302
|
} catch (err2) {
|
|
@@ -44433,19 +45032,18 @@ function sayChanged(ui, before) {
|
|
|
44433
45032
|
// src/commands/migrate.ts
|
|
44434
45033
|
var import_node_fs79 = require("node:fs");
|
|
44435
45034
|
var import_node_path68 = require("node:path");
|
|
44436
|
-
var import_node_child_process22 = require("node:child_process");
|
|
44437
45035
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
44438
45036
|
function defaultNpmRemover(pkg) {
|
|
44439
45037
|
let listing = "";
|
|
44440
45038
|
try {
|
|
44441
|
-
listing = (
|
|
45039
|
+
listing = execSync2(`npm ls -g --depth=0 --json ${pkg}`, { stdio: "pipe", timeout: 6e4 }).toString();
|
|
44442
45040
|
} catch (err2) {
|
|
44443
45041
|
const stdout = err2.stdout;
|
|
44444
45042
|
listing = stdout ? stdout.toString() : "";
|
|
44445
45043
|
}
|
|
44446
45044
|
const installed2 = parseGlobalListing(listing, pkg);
|
|
44447
45045
|
if (installed2 === false) return "absent";
|
|
44448
|
-
(
|
|
45046
|
+
execSync2(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
44449
45047
|
return installed2 ? "removed" : "unknown";
|
|
44450
45048
|
}
|
|
44451
45049
|
function parseGlobalListing(listing, pkg) {
|
|
@@ -44463,7 +45061,7 @@ function parseGlobalListing(listing, pkg) {
|
|
|
44463
45061
|
return Object.prototype.hasOwnProperty.call(deps, pkg);
|
|
44464
45062
|
}
|
|
44465
45063
|
function git2(cwd, args2) {
|
|
44466
|
-
(
|
|
45064
|
+
execFileSync2("git", hardenedGitArgv(args2), { cwd, stdio: "pipe", env: hardenedGitEnv() });
|
|
44467
45065
|
}
|
|
44468
45066
|
function isGitTracked(cwd, relPath) {
|
|
44469
45067
|
try {
|
|
@@ -44797,7 +45395,6 @@ function registerMigrateCommand(program2) {
|
|
|
44797
45395
|
}
|
|
44798
45396
|
|
|
44799
45397
|
// src/commands/init/sign-in.ts
|
|
44800
|
-
var import_node_child_process23 = require("node:child_process");
|
|
44801
45398
|
async function classifyExistingLogin(serviceUrl, remote, opts) {
|
|
44802
45399
|
const existing = await resolveToken(opts.token);
|
|
44803
45400
|
if (!existing.ok) return { kind: "none" };
|
|
@@ -44823,7 +45420,7 @@ async function classifyExistingLogin(serviceUrl, remote, opts) {
|
|
|
44823
45420
|
}
|
|
44824
45421
|
function gitRemote() {
|
|
44825
45422
|
try {
|
|
44826
|
-
return (
|
|
45423
|
+
return execFileSync2("git", ["remote", "get-url", "origin"], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
44827
45424
|
} catch {
|
|
44828
45425
|
return "";
|
|
44829
45426
|
}
|
|
@@ -45011,7 +45608,7 @@ async function recordAnswers(ui, momentIds, telemetry, completed) {
|
|
|
45011
45608
|
moments: momentIds,
|
|
45012
45609
|
...telemetry ? { telemetry } : {},
|
|
45013
45610
|
// A failed run also clears an earlier stamp: "done" describes this checkout now.
|
|
45014
|
-
init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.
|
|
45611
|
+
init: completed ? { completed_at: (/* @__PURE__ */ new Date()).toISOString(), cli_version: true ? "0.36.0" : "dev" } : void 0
|
|
45015
45612
|
});
|
|
45016
45613
|
} catch (err2) {
|
|
45017
45614
|
ui.rail.line(`Could not record the setup answers: ${terminalText(err2.message, 200)}`, "warn");
|
|
@@ -45714,6 +46311,9 @@ var import_node_fs84 = require("node:fs");
|
|
|
45714
46311
|
// src/lib/reflection-globs.ts
|
|
45715
46312
|
var MAX_GLOBS = 6;
|
|
45716
46313
|
var PATH_TOKEN = /[A-Za-z0-9_./-]*[A-Za-z0-9_-]\.[A-Za-z0-9]{1,8}(?::\d+(?::\d+)?)?/g;
|
|
46314
|
+
var GLOB_TOKEN = /[A-Za-z0-9_.-][A-Za-z0-9_./-]*\/[A-Za-z0-9_./*?-]*[*?][A-Za-z0-9_./*?-]*/g;
|
|
46315
|
+
var DIR_TOKEN = /[A-Za-z0-9_.-][A-Za-z0-9_./-]*\/(?![A-Za-z0-9_.*?/-])/g;
|
|
46316
|
+
var BARE_TOKEN = /`([A-Za-z0-9_][A-Za-z0-9_./-]*)`/g;
|
|
45717
46317
|
function clean(token) {
|
|
45718
46318
|
return token.replace(/:\d+(?::\d+)?$/, "").replace(/^[`'"(\[]+/, "").replace(/[`'"),\].;!?]+$/, "").replace(/^\.\//, "");
|
|
45719
46319
|
}
|
|
@@ -45727,25 +46327,91 @@ function deriveFileGlobs(text3, treePaths) {
|
|
|
45727
46327
|
if (!text3 || treePaths.length === 0) return [];
|
|
45728
46328
|
const out2 = [];
|
|
45729
46329
|
const seen = /* @__PURE__ */ new Set();
|
|
45730
|
-
|
|
45731
|
-
|
|
45732
|
-
|
|
45733
|
-
|
|
45734
|
-
|
|
45735
|
-
|
|
45736
|
-
|
|
46330
|
+
const candidates2 = [];
|
|
46331
|
+
for (const m of text3.matchAll(PATH_TOKEN)) {
|
|
46332
|
+
const token = clean(m[0]);
|
|
46333
|
+
candidates2.push({ at: m.index ?? 0, glob: token ? resolve8(token, treePaths) : null });
|
|
46334
|
+
}
|
|
46335
|
+
for (const m of text3.matchAll(GLOB_TOKEN)) {
|
|
46336
|
+
candidates2.push({ at: m.index ?? 0, glob: resolveGlob(clean(m[0]), treePaths) });
|
|
46337
|
+
}
|
|
46338
|
+
for (const m of text3.matchAll(DIR_TOKEN)) {
|
|
46339
|
+
candidates2.push({ at: m.index ?? 0, glob: resolveDir(m[0], treePaths) });
|
|
46340
|
+
}
|
|
46341
|
+
for (const m of text3.matchAll(BARE_TOKEN)) {
|
|
46342
|
+
candidates2.push({ at: m.index ?? 0, glob: resolveBare(clean(m[1]), treePaths) });
|
|
46343
|
+
}
|
|
46344
|
+
candidates2.sort((a, b) => a.at - b.at);
|
|
46345
|
+
for (const { glob } of candidates2) {
|
|
46346
|
+
if (!glob || seen.has(glob)) continue;
|
|
46347
|
+
seen.add(glob);
|
|
46348
|
+
out2.push(glob);
|
|
45737
46349
|
if (out2.length >= MAX_GLOBS) break;
|
|
45738
46350
|
}
|
|
45739
46351
|
return out2;
|
|
45740
46352
|
}
|
|
46353
|
+
function resolveBare(token, treePaths) {
|
|
46354
|
+
if (!token) return null;
|
|
46355
|
+
if (token.includes("/")) return treePaths.includes(token) ? token : null;
|
|
46356
|
+
if (!/^[A-Z]/.test(token)) return null;
|
|
46357
|
+
return resolve8(token, treePaths);
|
|
46358
|
+
}
|
|
46359
|
+
function resolveGlob(token, treePaths) {
|
|
46360
|
+
if (!token || !/[*?]/.test(token)) return null;
|
|
46361
|
+
return anyPathMatches(token, treePaths) ? token : null;
|
|
46362
|
+
}
|
|
46363
|
+
function resolveDir(token, treePaths) {
|
|
46364
|
+
const dir = token.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
46365
|
+
if (!dir || dir === "." || dir.startsWith("../")) return null;
|
|
46366
|
+
const prefix = `${dir}/`;
|
|
46367
|
+
return treePaths.some((p) => p.startsWith(prefix)) ? `${dir}/**` : null;
|
|
46368
|
+
}
|
|
46369
|
+
|
|
46370
|
+
// src/lib/reflection-title.ts
|
|
46371
|
+
var DERIVED_TITLE_MAX = 120;
|
|
46372
|
+
var TITLE_MAX = 200;
|
|
46373
|
+
function deriveReflectionTitle(text3, max = DERIVED_TITLE_MAX) {
|
|
46374
|
+
const firstLine = text3.split(/\r?\n/).map((l) => l.trim()).find((l) => l.length > 0) ?? "";
|
|
46375
|
+
const line2 = stripMarkup(firstLine);
|
|
46376
|
+
const sentence = firstSentence(line2);
|
|
46377
|
+
return cutAtWord(sentence, max);
|
|
46378
|
+
}
|
|
46379
|
+
function checkExplicitTitle(raw) {
|
|
46380
|
+
const title = raw.replace(/\s+/g, " ").trim();
|
|
46381
|
+
if (!title) return { ok: false, error: "--title is empty." };
|
|
46382
|
+
if (title.length > TITLE_MAX) {
|
|
46383
|
+
return { ok: false, error: `--title is ${title.length} characters; the limit is ${TITLE_MAX}.` };
|
|
46384
|
+
}
|
|
46385
|
+
return { ok: true, title };
|
|
46386
|
+
}
|
|
46387
|
+
function stripMarkup(line2) {
|
|
46388
|
+
let s = line2.replace(/^#{1,6}\s+/, "").replace(/^>\s*/, "").replace(/^(?:[-*+]|\d+[.)])\s+/, "").trim();
|
|
46389
|
+
const wrapped = s.match(/^(\*\*|__|\*|_)(.+)\1$/);
|
|
46390
|
+
if (wrapped) s = wrapped[2].trim();
|
|
46391
|
+
return s.replace(/\s+/g, " ");
|
|
46392
|
+
}
|
|
46393
|
+
function firstSentence(line2) {
|
|
46394
|
+
const m = line2.match(/^(.+?[.!?])(?=\s+[A-Z0-9`"'(\[])/);
|
|
46395
|
+
const s = (m ? m[1] : line2).trim();
|
|
46396
|
+
return s.replace(/[.:;,]+$/, "").trim() || s;
|
|
46397
|
+
}
|
|
46398
|
+
function cutAtWord(s, max) {
|
|
46399
|
+
if (s.length <= max) return s;
|
|
46400
|
+
const room = s.slice(0, max - 1);
|
|
46401
|
+
const lastSpace = room.lastIndexOf(" ");
|
|
46402
|
+
const head = lastSpace > max / 2 ? room.slice(0, lastSpace) : room;
|
|
46403
|
+
return `${head.replace(/[\s,.;:—-]+$/, "")}\u2026`;
|
|
46404
|
+
}
|
|
45741
46405
|
|
|
45742
46406
|
// src/commands/reflect.ts
|
|
45743
46407
|
async function writeNodeToDisk(args2) {
|
|
45744
46408
|
try {
|
|
46409
|
+
const served = /* @__PURE__ */ new Map();
|
|
45745
46410
|
const written = await applyMemoryWrites(
|
|
45746
46411
|
[{ path: args2.filePath, content: args2.content, node_id: args2.nodeId, op: "create" }],
|
|
45747
|
-
{ treePaths: listTrackedFiles(),
|
|
46412
|
+
{ treePaths: listTrackedFiles(), baseline: "written", lastServed: await readServedLedger(), served }
|
|
45748
46413
|
);
|
|
46414
|
+
await recordServedFiles(served);
|
|
45749
46415
|
if (written === 0) return false;
|
|
45750
46416
|
return (0, import_node_fs84.existsSync)(projectPath(`${VERITY_DIR}/memory/${args2.filePath}`));
|
|
45751
46417
|
} catch {
|
|
@@ -45753,13 +46419,26 @@ async function writeNodeToDisk(args2) {
|
|
|
45753
46419
|
}
|
|
45754
46420
|
}
|
|
45755
46421
|
function registerReflectCommand(program2) {
|
|
45756
|
-
program2.command("reflect").description("Capture learnings \u2014 auto-extract or record a reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-dictated text); - reads it from stdin, so the words never pass through shell quoting").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--confirmed", "The USER authored or confirmed these exact words \u2014 records as human-authored (source: user, confidence 1.0)").option("--file-globs <glob...>", "Files this reflection is about. Without any, the paths cited in the text are used; a reflection matching no file never surfaces in a review").option("--autonomous", "(deprecated, ignored) reflections are always recorded without a confirm step").action(async (opts) => {
|
|
46422
|
+
program2.command("reflect").description("Capture learnings \u2014 auto-extract or record a reflection").option("--user-input <text>", "The reflection to record (the agent-drafted or user-dictated text); - reads it from stdin, so the words never pass through shell quoting").option("--kind <kind>", "Node kind (decision, gotcha, pattern, security, quality, intent, domain, integration)", "gotcha").option("--title <text>", "The node title (up to 200 characters). Without it, the first line or sentence of the text is used").option("--task-id <id>", "Task to reflect on (defaults to current task)").option("--confirmed", "The USER authored or confirmed these exact words \u2014 records as human-authored (source: user, confidence 1.0)").option("--file-globs <glob...>", "Files this reflection is about. Without any, the paths cited in the text are used; a reflection matching no file never surfaces in a review").option("--autonomous", "(deprecated, ignored) reflections are always recorded without a confirm step").action(async (opts) => {
|
|
45757
46423
|
const globals = program2.opts();
|
|
45758
46424
|
const input = readTextInput(opts.userInput, "--user-input");
|
|
45759
46425
|
if (!input.ok) {
|
|
45760
46426
|
printError(input.error);
|
|
45761
46427
|
process.exit(1);
|
|
45762
46428
|
}
|
|
46429
|
+
let explicitTitle = null;
|
|
46430
|
+
if (typeof opts.title === "string") {
|
|
46431
|
+
const checked = checkExplicitTitle(opts.title);
|
|
46432
|
+
if (!checked.ok) {
|
|
46433
|
+
printError(checked.error);
|
|
46434
|
+
process.exit(1);
|
|
46435
|
+
}
|
|
46436
|
+
explicitTitle = checked.title;
|
|
46437
|
+
}
|
|
46438
|
+
try {
|
|
46439
|
+
process.chdir(repoRoot());
|
|
46440
|
+
} catch {
|
|
46441
|
+
}
|
|
45763
46442
|
await ensureMemoryDir();
|
|
45764
46443
|
const tokenResult = await resolveToken(globals.token);
|
|
45765
46444
|
if (!tokenResult.ok) {
|
|
@@ -45829,6 +46508,7 @@ function registerReflectCommand(program2) {
|
|
|
45829
46508
|
const explicitGlobs = Array.isArray(opts.fileGlobs) ? opts.fileGlobs.map((g) => g.trim()).filter(Boolean) : [];
|
|
45830
46509
|
const derivedGlobs = explicitGlobs.length > 0 ? [] : deriveFileGlobs(userInput, listTrackedFiles());
|
|
45831
46510
|
const fileGlobs = explicitGlobs.length > 0 ? explicitGlobs : derivedGlobs.length > 0 ? derivedGlobs : inheritedGlobs;
|
|
46511
|
+
const title = explicitTitle ?? deriveReflectionTitle(userInput);
|
|
45832
46512
|
const result = await apiRequest({
|
|
45833
46513
|
method: "POST",
|
|
45834
46514
|
path: "/compound/reflect",
|
|
@@ -45836,7 +46516,7 @@ function registerReflectCommand(program2) {
|
|
|
45836
46516
|
token,
|
|
45837
46517
|
body: {
|
|
45838
46518
|
kind: kind2,
|
|
45839
|
-
title
|
|
46519
|
+
title,
|
|
45840
46520
|
body: userInput,
|
|
45841
46521
|
domains: [],
|
|
45842
46522
|
file_globs: fileGlobs,
|
|
@@ -45871,9 +46551,12 @@ function registerReflectCommand(program2) {
|
|
|
45871
46551
|
printInfo(
|
|
45872
46552
|
synced ? `Recorded ${nodeId} \u2192 .verity/memory/${filePath}` : `Recorded ${nodeId} (syncs to .verity/memory/${filePath} on the next analysis).`
|
|
45873
46553
|
);
|
|
46554
|
+
const storedNode = result.data.node;
|
|
46555
|
+
const storedTitle = typeof storedNode?.title === "string" ? storedNode.title : title;
|
|
46556
|
+
printInfo(` Title: ${terminalText(storedTitle, 200)}`);
|
|
45874
46557
|
if (viewUrl) printInfo(` ${viewUrl}`);
|
|
45875
46558
|
printInfo(
|
|
45876
|
-
recordedAsUser ? " Recorded as yours (source: user)." :
|
|
46559
|
+
recordedAsUser ? " Recorded as yours (source: user)." : ` Auto-recorded from this task \u2014 nobody reviewed it. If it is wrong: verity memory edit ${nodeId} \u2026, or verity memory delete ${nodeId}.`
|
|
45877
46560
|
);
|
|
45878
46561
|
if (recordedSource !== source2) {
|
|
45879
46562
|
printWarn(
|
|
@@ -45917,9 +46600,1353 @@ function registerReflectCommand(program2) {
|
|
|
45917
46600
|
});
|
|
45918
46601
|
}
|
|
45919
46602
|
|
|
46603
|
+
// src/lib/memory-curate.ts
|
|
46604
|
+
var import_promises18 = require("node:fs/promises");
|
|
46605
|
+
var import_node_fs85 = require("node:fs");
|
|
46606
|
+
var import_node_path72 = require("node:path");
|
|
46607
|
+
var memoryDir3 = () => projectPath(`${VERITY_DIR}/memory`);
|
|
46608
|
+
var trashDir = () => projectPath(`${VERITY_DIR}/.memory-trash`);
|
|
46609
|
+
var NODE_DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
46610
|
+
var MAX_NODE_FILE_BYTES2 = 64 * 1024;
|
|
46611
|
+
var NODE_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,199}$/;
|
|
46612
|
+
var SETTABLE_STATUSES = ["active", "archived", "superseded"];
|
|
46613
|
+
function readInRepo(fullPath, max) {
|
|
46614
|
+
const read2 = readRegularFileInRoot(repoRoot(), fullPath, max);
|
|
46615
|
+
return read2.ok ? read2.content : null;
|
|
46616
|
+
}
|
|
46617
|
+
async function listLocalNodes() {
|
|
46618
|
+
const out2 = [];
|
|
46619
|
+
if (!(0, import_node_fs85.existsSync)(memoryDir3()) || memoryDirEscapesRepo()) return out2;
|
|
46620
|
+
for (const domain of NODE_DOMAINS) {
|
|
46621
|
+
const dir = (0, import_node_path72.join)(memoryDir3(), domain);
|
|
46622
|
+
if (!(0, import_node_fs85.existsSync)(dir)) continue;
|
|
46623
|
+
let files;
|
|
46624
|
+
try {
|
|
46625
|
+
files = (await (0, import_promises18.readdir)(dir)).filter((f) => f.endsWith(".md")).sort();
|
|
46626
|
+
} catch {
|
|
46627
|
+
continue;
|
|
46628
|
+
}
|
|
46629
|
+
for (const file of files) {
|
|
46630
|
+
const content = readInRepo((0, import_node_path72.join)(dir, file), MAX_NODE_FILE_BYTES2);
|
|
46631
|
+
if (content === null) continue;
|
|
46632
|
+
const id = readId(content) ?? file.replace(/\.md$/, "");
|
|
46633
|
+
out2.push({ nodeId: id, path: `${domain}/${file}`, content });
|
|
46634
|
+
}
|
|
46635
|
+
}
|
|
46636
|
+
return out2;
|
|
46637
|
+
}
|
|
46638
|
+
async function findLocalNode(nodeId) {
|
|
46639
|
+
if (!NODE_ID_PATTERN.test(nodeId) || memoryDirEscapesRepo()) return null;
|
|
46640
|
+
for (const domain of NODE_DOMAINS) {
|
|
46641
|
+
const rel = `${domain}/${nodeId}.md`;
|
|
46642
|
+
const content = readInRepo((0, import_node_path72.join)(memoryDir3(), rel), MAX_NODE_FILE_BYTES2);
|
|
46643
|
+
if (content !== null && (readId(content) ?? nodeId) === nodeId) return { nodeId, path: rel, content };
|
|
46644
|
+
}
|
|
46645
|
+
return (await listLocalNodes()).find((n2) => n2.nodeId === nodeId) ?? null;
|
|
46646
|
+
}
|
|
46647
|
+
function readId(content) {
|
|
46648
|
+
const id = pickFrontmatter(frontmatterOf2(content) ?? "", "id");
|
|
46649
|
+
return id && NODE_ID_PATTERN.test(id) ? id : null;
|
|
46650
|
+
}
|
|
46651
|
+
function readTitle(content) {
|
|
46652
|
+
const fm = frontmatterOf2(content);
|
|
46653
|
+
const line2 = fm?.split("\n").find((l) => l.startsWith("title:"));
|
|
46654
|
+
if (!line2) return null;
|
|
46655
|
+
const raw = line2.slice("title:".length).trim();
|
|
46656
|
+
if (raw.startsWith('"')) {
|
|
46657
|
+
try {
|
|
46658
|
+
return String(JSON.parse(raw));
|
|
46659
|
+
} catch {
|
|
46660
|
+
return raw.replace(/^"|"$/g, "");
|
|
46661
|
+
}
|
|
46662
|
+
}
|
|
46663
|
+
return raw || null;
|
|
46664
|
+
}
|
|
46665
|
+
function readStatus(content) {
|
|
46666
|
+
return pickFrontmatter(frontmatterOf2(content) ?? "", "status") ?? "active";
|
|
46667
|
+
}
|
|
46668
|
+
function readGlobs(content) {
|
|
46669
|
+
const fm = frontmatterOf2(content) ?? "";
|
|
46670
|
+
const lines = fm.split("\n");
|
|
46671
|
+
const at = lines.findIndex((l) => /^file_globs:/.test(l));
|
|
46672
|
+
if (at < 0) return [];
|
|
46673
|
+
const inline = lines[at].slice("file_globs:".length).trim();
|
|
46674
|
+
if (inline.startsWith("[")) {
|
|
46675
|
+
try {
|
|
46676
|
+
const parsed = JSON.parse(inline);
|
|
46677
|
+
return Array.isArray(parsed) ? parsed.map(String) : [];
|
|
46678
|
+
} catch {
|
|
46679
|
+
return pickFrontmatterArray(fm, "file_globs");
|
|
46680
|
+
}
|
|
46681
|
+
}
|
|
46682
|
+
const out2 = [];
|
|
46683
|
+
for (let j = at + 1; j < lines.length && /^\s+-\s/.test(lines[j]); j++) {
|
|
46684
|
+
const item = lines[j].replace(/^\s+-\s+/, "").trim();
|
|
46685
|
+
let value = item;
|
|
46686
|
+
if (item.startsWith('"')) {
|
|
46687
|
+
try {
|
|
46688
|
+
value = String(JSON.parse(item));
|
|
46689
|
+
} catch {
|
|
46690
|
+
value = item.replace(/^"|"$/g, "");
|
|
46691
|
+
}
|
|
46692
|
+
}
|
|
46693
|
+
if (value) out2.push(value);
|
|
46694
|
+
}
|
|
46695
|
+
return out2;
|
|
46696
|
+
}
|
|
46697
|
+
function frontmatterOf2(content) {
|
|
46698
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
46699
|
+
return m ? `${m[1]}
|
|
46700
|
+
` : null;
|
|
46701
|
+
}
|
|
46702
|
+
function hasChanges(c) {
|
|
46703
|
+
return c.title !== void 0 || c.fileGlobs !== void 0 || c.status !== void 0 || c.body !== void 0;
|
|
46704
|
+
}
|
|
46705
|
+
function applyNodeChanges(content, changes) {
|
|
46706
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
46707
|
+
if (!m) return null;
|
|
46708
|
+
let fm = m[1];
|
|
46709
|
+
let rest = m[2];
|
|
46710
|
+
if (changes.title !== void 0) {
|
|
46711
|
+
fm = setFrontmatterLine(fm, "title", JSON.stringify(changes.title));
|
|
46712
|
+
const restLines = rest.split("\n");
|
|
46713
|
+
const first = restLines.findIndex((l) => l.trim() !== "");
|
|
46714
|
+
if (first >= 0 && /^# /.test(restLines[first])) {
|
|
46715
|
+
restLines[first] = `# ${changes.title}`;
|
|
46716
|
+
rest = restLines.join("\n");
|
|
46717
|
+
}
|
|
46718
|
+
}
|
|
46719
|
+
if (changes.status !== void 0) fm = setFrontmatterLine(fm, "status", changes.status);
|
|
46720
|
+
if (changes.fileGlobs !== void 0) {
|
|
46721
|
+
const block = changes.fileGlobs.length > 0 ? "file_globs:\n" + changes.fileGlobs.map((g) => ` - ${JSON.stringify(g)}`).join("\n") : "file_globs: []";
|
|
46722
|
+
fm = /^file_globs:/m.test(fm) ? replaceFileGlobsBlock(fm, block) : `${fm}
|
|
46723
|
+
${block}`;
|
|
46724
|
+
}
|
|
46725
|
+
if (changes.body !== void 0) rest = replaceBody(rest, changes.body);
|
|
46726
|
+
return `---
|
|
46727
|
+
${fm}
|
|
46728
|
+
---
|
|
46729
|
+
${rest}`;
|
|
46730
|
+
}
|
|
46731
|
+
function setFrontmatterLine(fm, key, value) {
|
|
46732
|
+
const re = new RegExp(`^${key}:.*$`, "m");
|
|
46733
|
+
return re.test(fm) ? fm.replace(re, `${key}: ${value}`) : `${fm}
|
|
46734
|
+
${key}: ${value}`;
|
|
46735
|
+
}
|
|
46736
|
+
function replaceBody(rest, body2) {
|
|
46737
|
+
const lines = rest.split("\n");
|
|
46738
|
+
let i2 = 0;
|
|
46739
|
+
while (i2 < lines.length && lines[i2].trim() === "") i2++;
|
|
46740
|
+
const heading = i2 < lines.length && /^# \S/.test(lines[i2]) ? lines[i2] : null;
|
|
46741
|
+
const related = lines.lastIndexOf("## Related");
|
|
46742
|
+
const relatedBlock = related > i2 ? lines.slice(related).join("\n").trimEnd() : null;
|
|
46743
|
+
const parts2 = [""];
|
|
46744
|
+
if (heading) parts2.push(heading, "");
|
|
46745
|
+
parts2.push(body2.trim());
|
|
46746
|
+
if (relatedBlock) parts2.push("", relatedBlock);
|
|
46747
|
+
return parts2.join("\n") + "\n";
|
|
46748
|
+
}
|
|
46749
|
+
async function fetchServiceNode(api, nodeId) {
|
|
46750
|
+
const res = await apiRequest({
|
|
46751
|
+
method: "GET",
|
|
46752
|
+
path: `/compound/memory/nodes/${encodeURIComponent(nodeId)}`,
|
|
46753
|
+
serviceUrl: api.serviceUrl,
|
|
46754
|
+
token: api.token,
|
|
46755
|
+
verbose: api.verbose,
|
|
46756
|
+
cmd: "memory-curate-read"
|
|
46757
|
+
});
|
|
46758
|
+
if (!res.ok) return { ok: false, error: res.error, notFound: res.http_status === 404 };
|
|
46759
|
+
return { ok: true, node: res.data };
|
|
46760
|
+
}
|
|
46761
|
+
async function setServiceStatus(api, nodeId, status) {
|
|
46762
|
+
const res = await apiRequest({
|
|
46763
|
+
method: "PATCH",
|
|
46764
|
+
path: `/compound/memory/nodes/${encodeURIComponent(nodeId)}`,
|
|
46765
|
+
serviceUrl: api.serviceUrl,
|
|
46766
|
+
token: api.token,
|
|
46767
|
+
body: { status },
|
|
46768
|
+
verbose: api.verbose,
|
|
46769
|
+
cmd: "memory-curate-status"
|
|
46770
|
+
});
|
|
46771
|
+
if (!res.ok) return { ok: false, error: res.error, notFound: res.http_status === 404 };
|
|
46772
|
+
if (res.data.status !== status) {
|
|
46773
|
+
return { ok: false, error: `the service answered with status "${String(res.data.status)}", not "${status}"` };
|
|
46774
|
+
}
|
|
46775
|
+
return { ok: true };
|
|
46776
|
+
}
|
|
46777
|
+
function uploadRefusal(content) {
|
|
46778
|
+
if (!readId(content) || !pickFrontmatter(frontmatterOf2(content) ?? "", "kind") || !readTitle(content)) {
|
|
46779
|
+
return "the file has no id, kind or title in its frontmatter, so the service would skip it";
|
|
46780
|
+
}
|
|
46781
|
+
const bodyLength = uploadBodyLength(content);
|
|
46782
|
+
if (bodyLength > SERVICE_BODY_MAX) {
|
|
46783
|
+
return `the body is ${bodyLength} characters; the service keeps ${SERVICE_BODY_MAX}, so it would be cut. Shorten it`;
|
|
46784
|
+
}
|
|
46785
|
+
const unstorable = unstorableField(content);
|
|
46786
|
+
if (unstorable) {
|
|
46787
|
+
return `the ${unstorable} would not be stored as written (it starts with a quote, is a bare number, true, false or null, or holds a line break). Change it`;
|
|
46788
|
+
}
|
|
46789
|
+
return null;
|
|
46790
|
+
}
|
|
46791
|
+
async function uploadNode(api, path, content, opts = {}) {
|
|
46792
|
+
const refusal = uploadRefusal(content);
|
|
46793
|
+
if (refusal) return { ok: false, error: refusal, unchanged: true };
|
|
46794
|
+
const nodeId = readId(content);
|
|
46795
|
+
const prior = await fetchServiceNode(api, nodeId);
|
|
46796
|
+
if (!prior.ok && !prior.notFound) return { ok: false, error: `could not read the node first: ${prior.error}` };
|
|
46797
|
+
const row2 = prior.ok ? prior.node : null;
|
|
46798
|
+
if (opts.expect !== void 0 && (!row2 || !sameEditableFields(row2, opts.expect))) {
|
|
46799
|
+
return { ok: false, stale: true, error: `the service copy of ${nodeId} is not the one expected, so it was not overwritten` };
|
|
46800
|
+
}
|
|
46801
|
+
const base = opts.base ?? content;
|
|
46802
|
+
if (row2 && !opts.force && serviceIsNewer(row2, base) && !sameEditableFields(row2, base)) {
|
|
46803
|
+
return {
|
|
46804
|
+
ok: false,
|
|
46805
|
+
stale: true,
|
|
46806
|
+
error: `the service has a newer, different version of ${nodeId} (updated ${String(row2.updated_at)}) than the file here. Run \`verity memory pull\` and edit again, or pass --force to overwrite it`
|
|
46807
|
+
};
|
|
46808
|
+
}
|
|
46809
|
+
const notes = [];
|
|
46810
|
+
if (row2) {
|
|
46811
|
+
const changed = ["source", "created_by", "confidence"].filter((k) => {
|
|
46812
|
+
const v = pickFrontmatter(frontmatterOf2(content) ?? "", k);
|
|
46813
|
+
return v !== null && v !== String(row2[k]);
|
|
46814
|
+
});
|
|
46815
|
+
if (changed.length > 0) notes.push(`${changed.join(", ")} ${changed.length > 1 ? "are" : "is"} kept from the service; the file's change to ${changed.length > 1 ? "them" : "it"} was not uploaded`);
|
|
46816
|
+
}
|
|
46817
|
+
const upload = row2 ? withRowFields(uploadableNodeContent(content), row2) : uploadableNodeContent(content);
|
|
46818
|
+
const res = await apiRequest({
|
|
46819
|
+
method: "POST",
|
|
46820
|
+
path: "/compound/memory/sync",
|
|
46821
|
+
serviceUrl: api.serviceUrl,
|
|
46822
|
+
token: api.token,
|
|
46823
|
+
body: { files: [{ path, content: upload }] },
|
|
46824
|
+
verbose: api.verbose,
|
|
46825
|
+
cmd: "memory-curate-upload"
|
|
46826
|
+
});
|
|
46827
|
+
if (!res.ok) return { ok: false, error: res.error };
|
|
46828
|
+
const back = await fetchServiceNode(api, nodeId);
|
|
46829
|
+
if (!back.ok) return { ok: false, error: `uploaded, but could not read it back: ${back.error}` };
|
|
46830
|
+
const mismatched = [];
|
|
46831
|
+
if (back.node.title !== readTitle(content)) mismatched.push("title");
|
|
46832
|
+
if ((back.node.status ?? "active") !== readStatus(content)) mismatched.push("status");
|
|
46833
|
+
const stored = Array.isArray(back.node.file_globs) ? back.node.file_globs.map(String) : [];
|
|
46834
|
+
if (!sameSet(stored, readGlobs(content))) mismatched.push("file_globs");
|
|
46835
|
+
if (back.node.body !== nodeBody(upload)) mismatched.push("body");
|
|
46836
|
+
return { ok: true, mismatched, final: adoptRowFields(content, back.node), notes };
|
|
46837
|
+
}
|
|
46838
|
+
function unstorableField(content) {
|
|
46839
|
+
const title = readTitle(content) ?? "";
|
|
46840
|
+
if (/["\\]/.test(title) && bareStorableValue(title) === null) return "title";
|
|
46841
|
+
if (/[\r\n]/.test(title)) return "title";
|
|
46842
|
+
for (const g of readGlobs(content)) {
|
|
46843
|
+
if (/["\\]/.test(g) && (g.startsWith('"') || g !== g.trim())) return "file_globs";
|
|
46844
|
+
if (/[\r\n]/.test(g)) return "file_globs";
|
|
46845
|
+
}
|
|
46846
|
+
return null;
|
|
46847
|
+
}
|
|
46848
|
+
function timeOf(value) {
|
|
46849
|
+
const t2 = typeof value === "string" ? Date.parse(value) : NaN;
|
|
46850
|
+
return Number.isFinite(t2) ? t2 : null;
|
|
46851
|
+
}
|
|
46852
|
+
function serviceIsNewer(row2, content) {
|
|
46853
|
+
const server = timeOf(row2.updated_at);
|
|
46854
|
+
const local = timeOf(pickFrontmatter(frontmatterOf2(content) ?? "", "updated_at"));
|
|
46855
|
+
return server !== null && local !== null && server - local > 1e3;
|
|
46856
|
+
}
|
|
46857
|
+
function sameEditableFields(row2, content) {
|
|
46858
|
+
const globs = Array.isArray(row2.file_globs) ? row2.file_globs.map(String) : [];
|
|
46859
|
+
return row2.title === readTitle(content) && (row2.status ?? "active") === readStatus(content) && sameSet(globs, readGlobs(content)) && row2.body === nodeBody(uploadableNodeContent(content));
|
|
46860
|
+
}
|
|
46861
|
+
function storableScalar(value) {
|
|
46862
|
+
if (/[\r\n]/.test(value)) return null;
|
|
46863
|
+
return /["\\]/.test(value) ? bareStorableValue(value) : JSON.stringify(value);
|
|
46864
|
+
}
|
|
46865
|
+
function withRowFields(content, row2) {
|
|
46866
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
46867
|
+
if (!m) return content;
|
|
46868
|
+
const lines = [];
|
|
46869
|
+
const evidence2 = Array.isArray(row2.evidence_run_ids) ? row2.evidence_run_ids.filter((x) => typeof x === "string") : [];
|
|
46870
|
+
if (evidence2.length > 0) lines.push(`evidence_run_ids: ${JSON.stringify(evidence2)}`);
|
|
46871
|
+
if (typeof row2.pattern_ref === "string" && row2.pattern_ref) {
|
|
46872
|
+
const v = storableScalar(row2.pattern_ref);
|
|
46873
|
+
if (v !== null) lines.push(`pattern_ref: ${v}`);
|
|
46874
|
+
}
|
|
46875
|
+
if (typeof row2.source === "string") {
|
|
46876
|
+
const v = storableScalar(row2.source);
|
|
46877
|
+
if (v !== null) lines.push(`source: ${v}`);
|
|
46878
|
+
}
|
|
46879
|
+
if (typeof row2.created_by === "string") {
|
|
46880
|
+
const v = storableScalar(row2.created_by);
|
|
46881
|
+
if (v !== null) lines.push(`created_by: ${v}`);
|
|
46882
|
+
}
|
|
46883
|
+
if (typeof row2.confidence === "number" && Number.isFinite(row2.confidence)) lines.push(`confidence: ${row2.confidence}`);
|
|
46884
|
+
const replaced = new Set(lines.map((l) => l.slice(0, l.indexOf(":"))));
|
|
46885
|
+
const fm = m[1].split("\n").filter((l) => !replaced.has(l.slice(0, l.indexOf(":"))));
|
|
46886
|
+
return `---
|
|
46887
|
+
${[...fm, ...lines].join("\n")}
|
|
46888
|
+
---
|
|
46889
|
+
${m[2]}`;
|
|
46890
|
+
}
|
|
46891
|
+
function adoptRowFields(content, row2) {
|
|
46892
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
46893
|
+
if (!m) return content;
|
|
46894
|
+
let fm = m[1];
|
|
46895
|
+
const set = (key, value) => {
|
|
46896
|
+
const re = new RegExp(`^${key}:.*$`, "m");
|
|
46897
|
+
fm = re.test(fm) ? fm.replace(re, () => `${key}: ${value}`) : `${fm}
|
|
46898
|
+
${key}: ${value}`;
|
|
46899
|
+
};
|
|
46900
|
+
if (typeof row2.updated_at === "string" && timeOf(row2.updated_at) !== null) set("updated_at", row2.updated_at);
|
|
46901
|
+
if (typeof row2.source === "string" && /^[a-z_]+$/.test(row2.source)) set("source", row2.source);
|
|
46902
|
+
if (typeof row2.created_by === "string" && !/[\r\n]/.test(row2.created_by)) set("created_by", /^[A-Za-z0-9@._/-]+$/.test(row2.created_by) ? row2.created_by : JSON.stringify(row2.created_by));
|
|
46903
|
+
if (typeof row2.confidence === "number" && Number.isFinite(row2.confidence)) set("confidence", String(row2.confidence));
|
|
46904
|
+
return `---
|
|
46905
|
+
${fm}
|
|
46906
|
+
---
|
|
46907
|
+
${m[2]}`;
|
|
46908
|
+
}
|
|
46909
|
+
var SERVICE_BODY_MAX = 8192;
|
|
46910
|
+
function nodeBody(content) {
|
|
46911
|
+
const m = content.match(/^---\n[\s\S]*?\n---\n([\s\S]*)$/);
|
|
46912
|
+
return (m ? m[1] : content).trim();
|
|
46913
|
+
}
|
|
46914
|
+
function uploadBodyLength(content) {
|
|
46915
|
+
return nodeBody(uploadableNodeContent(content)).length;
|
|
46916
|
+
}
|
|
46917
|
+
function sameSet(a, b) {
|
|
46918
|
+
return a.length === b.length && a.every((x) => b.includes(x));
|
|
46919
|
+
}
|
|
46920
|
+
async function recordSynced(path, content) {
|
|
46921
|
+
const entry = /* @__PURE__ */ new Map([[path, hashNodeContent(content)]]);
|
|
46922
|
+
await markNodesSynced(entry);
|
|
46923
|
+
await recordServedFiles(entry);
|
|
46924
|
+
}
|
|
46925
|
+
var MAX_TRASH_BYTES = 4 * MAX_NODE_FILE_BYTES2;
|
|
46926
|
+
async function readTrash(nodeId) {
|
|
46927
|
+
if (!NODE_ID_PATTERN.test(nodeId)) return null;
|
|
46928
|
+
const text3 = readInRepo((0, import_node_path72.join)(trashDir(), `${nodeId}.json`), MAX_TRASH_BYTES);
|
|
46929
|
+
if (text3 === null) return null;
|
|
46930
|
+
try {
|
|
46931
|
+
const parsed = JSON.parse(text3);
|
|
46932
|
+
return parsed?.node_id === nodeId ? parsed : null;
|
|
46933
|
+
} catch {
|
|
46934
|
+
return null;
|
|
46935
|
+
}
|
|
46936
|
+
}
|
|
46937
|
+
async function writeTrash(record2) {
|
|
46938
|
+
const ok = await writeFileAtomicInTree((0, import_node_path72.join)(trashDir(), `${record2.node_id}.json`), JSON.stringify(record2, null, 2) + "\n");
|
|
46939
|
+
if (!ok) throw new Error(".verity/.memory-trash/ is a link, or a folder outside the repository");
|
|
46940
|
+
}
|
|
46941
|
+
async function removeTrash(nodeId) {
|
|
46942
|
+
if (!(0, import_node_fs85.existsSync)(trashDir()) || !dirInRepo(trashDir())) return;
|
|
46943
|
+
try {
|
|
46944
|
+
await (0, import_promises18.rm)((0, import_node_path72.join)(trashDir(), `${nodeId}.json`), { force: true });
|
|
46945
|
+
} catch {
|
|
46946
|
+
}
|
|
46947
|
+
}
|
|
46948
|
+
async function editNode(api, node, next, opts = {}) {
|
|
46949
|
+
const refusal = uploadRefusal(next);
|
|
46950
|
+
if (refusal) return { ok: false, error: refusal, unchanged: true };
|
|
46951
|
+
if (next !== node.content && !await writeNodeFile(node.path, next)) {
|
|
46952
|
+
return { ok: false, error: `could not write .verity/memory/${node.path} (a link, or a folder outside the repository)`, unchanged: true };
|
|
46953
|
+
}
|
|
46954
|
+
const up = await uploadNode(api, node.path, next, { force: opts.force, base: opts.base ?? node.content, expect: opts.expect });
|
|
46955
|
+
if (!up.ok) {
|
|
46956
|
+
if (up.stale && next !== node.content) await writeNodeFile(node.path, node.content);
|
|
46957
|
+
return up;
|
|
46958
|
+
}
|
|
46959
|
+
if (up.mismatched.length > 0) return up;
|
|
46960
|
+
const final = up.final !== next && await writeNodeFile(node.path, up.final) ? up.final : next;
|
|
46961
|
+
await recordSynced(node.path, final);
|
|
46962
|
+
await refreshMemoryIndex();
|
|
46963
|
+
await appendMemoryLog([`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Uploaded ${node.nodeId} by hand (${node.path})`]);
|
|
46964
|
+
return { ...up, final };
|
|
46965
|
+
}
|
|
46966
|
+
async function deleteNode(api, nodeId, opts = {}) {
|
|
46967
|
+
const local = await findLocalNode(nodeId);
|
|
46968
|
+
const status = opts.status ?? "archived";
|
|
46969
|
+
const previous = await readTrash(nodeId);
|
|
46970
|
+
try {
|
|
46971
|
+
await writeTrash({
|
|
46972
|
+
node_id: nodeId,
|
|
46973
|
+
path: local?.path ?? null,
|
|
46974
|
+
content: local?.content ?? null,
|
|
46975
|
+
status_before: local ? readStatus(local.content) : "active",
|
|
46976
|
+
reason: opts.reason ?? null,
|
|
46977
|
+
deleted_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
46978
|
+
via: opts.via ?? "delete"
|
|
46979
|
+
});
|
|
46980
|
+
} catch (err2) {
|
|
46981
|
+
return { ok: false, error: `could not save the local record in .verity/.memory-trash/ first (${err2.message}); nothing was changed` };
|
|
46982
|
+
}
|
|
46983
|
+
const set = await setServiceStatus(api, nodeId, status);
|
|
46984
|
+
if (!set.ok && !(set.notFound && local)) {
|
|
46985
|
+
if (previous) await writeTrash(previous).catch(() => {
|
|
46986
|
+
});
|
|
46987
|
+
else await removeTrash(nodeId);
|
|
46988
|
+
return set;
|
|
46989
|
+
}
|
|
46990
|
+
const localOnly = !set.ok;
|
|
46991
|
+
let fileKept = false;
|
|
46992
|
+
if (local) {
|
|
46993
|
+
if (await removeNodeFile(local.path)) {
|
|
46994
|
+
await forgetSyncedNodes([local.path]);
|
|
46995
|
+
await forgetServedFiles([local.path]);
|
|
46996
|
+
await refreshMemoryIndex();
|
|
46997
|
+
} else {
|
|
46998
|
+
fileKept = true;
|
|
46999
|
+
}
|
|
47000
|
+
}
|
|
47001
|
+
const why = opts.reason ? `: ${opts.reason}` : "";
|
|
47002
|
+
await appendMemoryLog([`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 ${localOnly ? "Removed local-only" : `Set ${status}`} ${nodeId}${local ? ` (${local.path})` : ""} via ${opts.via ?? "delete"}${why}`]);
|
|
47003
|
+
return { ok: true, path: local?.path ?? null, localOnly, fileKept };
|
|
47004
|
+
}
|
|
47005
|
+
async function restoreNode(api, nodeId, fallback) {
|
|
47006
|
+
const trash = await readTrash(nodeId);
|
|
47007
|
+
const path = trash?.path ?? fallback?.path ?? null;
|
|
47008
|
+
const content = trash?.content ?? fallback?.content ?? null;
|
|
47009
|
+
const set = await setServiceStatus(api, nodeId, "active");
|
|
47010
|
+
const localOnly = !set.ok && !!set.notFound && path !== null && content !== null;
|
|
47011
|
+
if (!set.ok && !localOnly) return set;
|
|
47012
|
+
let restored = false;
|
|
47013
|
+
if (path && content !== null) {
|
|
47014
|
+
const next = readStatus(content) === "active" ? content : applyNodeChanges(content, { status: "active" }) ?? content;
|
|
47015
|
+
if (!await writeNodeFile(path, next)) {
|
|
47016
|
+
return { ok: false, error: `${localOnly ? "the service has no such node, and" : "active on the service again, but"} .verity/memory/${path} could not be written (a link, or a folder outside the repository). The text is kept in .verity/.memory-trash/${nodeId}.json` };
|
|
47017
|
+
}
|
|
47018
|
+
if (!localOnly) await recordSynced(path, next);
|
|
47019
|
+
restored = true;
|
|
47020
|
+
}
|
|
47021
|
+
await removeTrash(nodeId);
|
|
47022
|
+
await refreshMemoryIndex();
|
|
47023
|
+
await appendMemoryLog([`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Restored ${nodeId}${path ? ` (${path})` : ""}${localOnly ? " (local only)" : ""}`]);
|
|
47024
|
+
return { ok: true, path: restored ? path : null, fromTrash: restored, localOnly };
|
|
47025
|
+
}
|
|
47026
|
+
|
|
47027
|
+
// src/commands/memory-curate.ts
|
|
47028
|
+
function cleanReason(raw) {
|
|
47029
|
+
if (raw === void 0) return { ok: true, reason: null };
|
|
47030
|
+
const reason = String(raw).replace(/[\u0000-\u001f\u007f]+/g, " ").replace(/\s+/g, " ").trim();
|
|
47031
|
+
if (reason.length === 0 || reason.length > 500) return { ok: false, error: "--reason is 1 to 500 characters." };
|
|
47032
|
+
return { ok: true, reason };
|
|
47033
|
+
}
|
|
47034
|
+
async function resolveApi(program2) {
|
|
47035
|
+
const globals = program2.opts();
|
|
47036
|
+
const tokenResult = await resolveToken(globals.token);
|
|
47037
|
+
if (!tokenResult.ok) {
|
|
47038
|
+
printError(tokenResult.error);
|
|
47039
|
+
process.exit(1);
|
|
47040
|
+
}
|
|
47041
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
47042
|
+
if (!urlResult.ok) {
|
|
47043
|
+
printError(urlResult.error);
|
|
47044
|
+
process.exit(1);
|
|
47045
|
+
}
|
|
47046
|
+
return { serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose };
|
|
47047
|
+
}
|
|
47048
|
+
function refuseLinkedMemoryDir() {
|
|
47049
|
+
if (memoryDirEscapesRepo()) {
|
|
47050
|
+
printError(".verity/memory is a link (or under one) that leads out of this repository. Refusing to read or change it.");
|
|
47051
|
+
process.exit(1);
|
|
47052
|
+
}
|
|
47053
|
+
}
|
|
47054
|
+
function checkNodeId(nodeId) {
|
|
47055
|
+
if (!NODE_ID_PATTERN.test(nodeId)) {
|
|
47056
|
+
printError('A node id looks like n123-some-slug. `verity memory ask "<topic>"` and .verity/memory/index.md list them.');
|
|
47057
|
+
process.exit(1);
|
|
47058
|
+
}
|
|
47059
|
+
}
|
|
47060
|
+
function registerMemoryCurationCommands(memory2, program2) {
|
|
47061
|
+
memory2.command("edit <node-id>").description("Change a knowledge node and upload it now. With no change option, uploads the file as it is on disk").option("--title <text>", "New title (up to 200 characters)").option("--file-globs <glob...>", "Replace the files this node is about. Each glob must match a file in the repository").option("--status <status>", `New status: ${SETTABLE_STATUSES.join(", ")}`).option("--body <text>", "Replace the body; - reads it from stdin").option("--force", "Upload even when the service has a newer, different version of the node (it is overwritten)").action(async (nodeId, opts) => {
|
|
47062
|
+
checkNodeId(nodeId);
|
|
47063
|
+
const body2 = opts.body === void 0 ? null : readTextInput(opts.body, "--body");
|
|
47064
|
+
if (body2 && !body2.ok) {
|
|
47065
|
+
printError(body2.error);
|
|
47066
|
+
process.exit(1);
|
|
47067
|
+
}
|
|
47068
|
+
try {
|
|
47069
|
+
process.chdir(repoRoot());
|
|
47070
|
+
} catch {
|
|
47071
|
+
}
|
|
47072
|
+
refuseLinkedMemoryDir();
|
|
47073
|
+
const changes = {};
|
|
47074
|
+
if (opts.title !== void 0) {
|
|
47075
|
+
const t2 = checkExplicitTitle(opts.title);
|
|
47076
|
+
if (!t2.ok) {
|
|
47077
|
+
printError(t2.error);
|
|
47078
|
+
process.exit(1);
|
|
47079
|
+
}
|
|
47080
|
+
changes.title = t2.title;
|
|
47081
|
+
}
|
|
47082
|
+
if (opts.status !== void 0) {
|
|
47083
|
+
if (!SETTABLE_STATUSES.includes(opts.status)) {
|
|
47084
|
+
printError(`--status is one of: ${SETTABLE_STATUSES.join(", ")}.`);
|
|
47085
|
+
process.exit(1);
|
|
47086
|
+
}
|
|
47087
|
+
changes.status = opts.status;
|
|
47088
|
+
}
|
|
47089
|
+
if (opts.fileGlobs !== void 0) {
|
|
47090
|
+
const globs = [...new Set(opts.fileGlobs.map((g) => g.trim()).filter(Boolean))];
|
|
47091
|
+
const tree2 = listTrackedFiles();
|
|
47092
|
+
const unmatched = globs.filter((g) => !anyPathMatches(g, tree2));
|
|
47093
|
+
if (globs.length === 0 || unmatched.length > 0) {
|
|
47094
|
+
printError(globs.length === 0 ? "--file-globs needs at least one glob. A node with none never surfaces in a review." : `No file in this repository matches: ${unmatched.join(", ")}. Paths are relative to the repository root.`);
|
|
47095
|
+
process.exit(1);
|
|
47096
|
+
}
|
|
47097
|
+
changes.fileGlobs = globs;
|
|
47098
|
+
}
|
|
47099
|
+
if (body2) changes.body = body2.text;
|
|
47100
|
+
const api = await resolveApi(program2);
|
|
47101
|
+
let node = await findLocalNode(nodeId);
|
|
47102
|
+
if (!node) {
|
|
47103
|
+
try {
|
|
47104
|
+
await pullRepoMemory({ serviceUrl: api.serviceUrl, token: api.token, verbose: api.verbose });
|
|
47105
|
+
} catch {
|
|
47106
|
+
}
|
|
47107
|
+
node = await findLocalNode(nodeId);
|
|
47108
|
+
}
|
|
47109
|
+
if (!node) {
|
|
47110
|
+
printError(`No node ${nodeId} in .verity/memory/. \`verity memory pull\` fetches the graph; \`verity memory ask "<topic>"\` finds node ids.`);
|
|
47111
|
+
process.exit(1);
|
|
47112
|
+
}
|
|
47113
|
+
const next = hasChanges(changes) ? applyNodeChanges(node.content, changes) : node.content;
|
|
47114
|
+
if (next === null) {
|
|
47115
|
+
printError(`.verity/memory/${terminalText(node.path)} has no frontmatter to change.`);
|
|
47116
|
+
process.exit(1);
|
|
47117
|
+
}
|
|
47118
|
+
const bodyLength = uploadBodyLength(next);
|
|
47119
|
+
if (bodyLength > SERVICE_BODY_MAX) {
|
|
47120
|
+
printError(`The body is ${bodyLength} characters; the service keeps ${SERVICE_BODY_MAX}, so it would be cut. Shorten it.`);
|
|
47121
|
+
process.exit(1);
|
|
47122
|
+
}
|
|
47123
|
+
if (changes.status !== void 0 && Object.keys(changes).length === 1) {
|
|
47124
|
+
const set = await setServiceStatus(api, nodeId, changes.status);
|
|
47125
|
+
if (!set.ok) {
|
|
47126
|
+
printError(`Could not change the status of ${nodeId}: ${set.error}`);
|
|
47127
|
+
process.exit(1);
|
|
47128
|
+
}
|
|
47129
|
+
const written = await writeNodeFile(node.path, next);
|
|
47130
|
+
if (written) {
|
|
47131
|
+
const entry = /* @__PURE__ */ new Map([[node.path, hashNodeContent(next)]]);
|
|
47132
|
+
await markNodesSynced(entry);
|
|
47133
|
+
await recordServedFiles(entry);
|
|
47134
|
+
await refreshMemoryIndex();
|
|
47135
|
+
}
|
|
47136
|
+
printInfo(`${nodeId} is ${changes.status} on the service (.verity/memory/${terminalText(node.path)}).`);
|
|
47137
|
+
if (!written) {
|
|
47138
|
+
printError(` But .verity/memory/${terminalText(node.path)} could not be written (a link, or a folder outside the repository). Its status line still says ${readStatus(node.content)}.`);
|
|
47139
|
+
process.exit(1);
|
|
47140
|
+
}
|
|
47141
|
+
if (changes.status !== "active") printInfo(" It no longer surfaces in reviews. `verity memory delete` also removes the file here.");
|
|
47142
|
+
process.exit(0);
|
|
47143
|
+
}
|
|
47144
|
+
const result = await editNode(api, node, next, { force: !!opts.force });
|
|
47145
|
+
if (!result.ok) {
|
|
47146
|
+
printError(`Could not upload ${nodeId}: ${result.error}`);
|
|
47147
|
+
if (result.stale || result.unchanged) printWarn(" Nothing was changed, here or on the service.");
|
|
47148
|
+
else if (next !== node.content) printWarn(` The change is saved in .verity/memory/${terminalText(node.path)}. Run the same command again to upload it.`);
|
|
47149
|
+
process.exit(1);
|
|
47150
|
+
}
|
|
47151
|
+
if (result.mismatched.length > 0) {
|
|
47152
|
+
printError(`The service did not take the change to ${nodeId}: ${result.mismatched.join(", ")} differ from the file.`);
|
|
47153
|
+
process.exit(1);
|
|
47154
|
+
}
|
|
47155
|
+
const changed = Object.keys(changes);
|
|
47156
|
+
printInfo(changed.length > 0 ? `Updated ${nodeId} (${changed.map((k) => k === "fileGlobs" ? "file_globs" : k).join(", ")}) and uploaded it \u2192 .verity/memory/${terminalText(node.path)}` : `Uploaded ${nodeId} as it is on disk \u2192 .verity/memory/${terminalText(node.path)}`);
|
|
47157
|
+
printInfo(" The service holds this version now. The next analysis will not send it again.");
|
|
47158
|
+
for (const note of result.notes) printWarn(` Note: ${note}.`);
|
|
47159
|
+
process.exit(0);
|
|
47160
|
+
});
|
|
47161
|
+
memory2.command("delete <node-id>").description("Archive a knowledge node on the service and remove its file here. `verity memory restore` undoes it").option("--reason <text>", "Why the node is wrong or no longer applies (1\u2013500 characters, kept on this machine); - reads it from stdin").action(async (nodeId, opts) => {
|
|
47162
|
+
checkNodeId(nodeId);
|
|
47163
|
+
const rawReason = opts.reason === void 0 ? null : readTextInput(opts.reason, "--reason");
|
|
47164
|
+
if (rawReason && !rawReason.ok) {
|
|
47165
|
+
printError(rawReason.error);
|
|
47166
|
+
process.exit(1);
|
|
47167
|
+
}
|
|
47168
|
+
const reason = cleanReason(rawReason ? rawReason.text : void 0);
|
|
47169
|
+
if (!reason.ok) {
|
|
47170
|
+
printError(reason.error);
|
|
47171
|
+
process.exit(1);
|
|
47172
|
+
}
|
|
47173
|
+
try {
|
|
47174
|
+
process.chdir(repoRoot());
|
|
47175
|
+
} catch {
|
|
47176
|
+
}
|
|
47177
|
+
refuseLinkedMemoryDir();
|
|
47178
|
+
const api = await resolveApi(program2);
|
|
47179
|
+
const result = await deleteNode(api, nodeId, { reason: reason.reason, via: "delete" });
|
|
47180
|
+
if (!result.ok) {
|
|
47181
|
+
printError(result.notFound ? `No node ${nodeId} on the service, and no file for it here.` : `Could not archive ${nodeId}: ${result.error}`);
|
|
47182
|
+
process.exit(1);
|
|
47183
|
+
}
|
|
47184
|
+
if (result.localOnly) {
|
|
47185
|
+
printInfo(`The service has no node ${nodeId}. Removed the file that existed only here (.verity/memory/${terminalText(result.path)}).`);
|
|
47186
|
+
} else {
|
|
47187
|
+
printInfo(`Archived ${nodeId} on the service${result.path ? ` and removed .verity/memory/${terminalText(result.path)}` : ""}.`);
|
|
47188
|
+
printInfo(" It no longer surfaces in reviews, here or for your team.");
|
|
47189
|
+
}
|
|
47190
|
+
if (result.fileKept) {
|
|
47191
|
+
printWarn(` But .verity/memory/${terminalText(result.path)} could not be removed here. Delete it by hand; the service no longer serves it.`);
|
|
47192
|
+
}
|
|
47193
|
+
if (reason.reason) printInfo(" The reason is kept in .verity/.memory-trash/ and .verity/memory/log.md on this machine.");
|
|
47194
|
+
printInfo(` Undo: verity memory restore ${nodeId}`);
|
|
47195
|
+
process.exit(0);
|
|
47196
|
+
});
|
|
47197
|
+
memory2.command("restore <node-id>").description("Undo `verity memory delete`: make the node active on the service again and put its file back").action(async (nodeId) => {
|
|
47198
|
+
checkNodeId(nodeId);
|
|
47199
|
+
try {
|
|
47200
|
+
process.chdir(repoRoot());
|
|
47201
|
+
} catch {
|
|
47202
|
+
}
|
|
47203
|
+
refuseLinkedMemoryDir();
|
|
47204
|
+
const api = await resolveApi(program2);
|
|
47205
|
+
const trash = await readTrash(nodeId);
|
|
47206
|
+
const result = await restoreNode(api, nodeId);
|
|
47207
|
+
if (!result.ok) {
|
|
47208
|
+
printError(result.notFound ? `No node ${nodeId} on the service.` : `Could not restore ${nodeId}: ${result.error}`);
|
|
47209
|
+
process.exit(1);
|
|
47210
|
+
}
|
|
47211
|
+
if (result.fromTrash) {
|
|
47212
|
+
printInfo(result.localOnly ? `Restored ${nodeId} here: back in .verity/memory/${terminalText(result.path)}. The service never had it, so it is still only on this machine.` : `Restored ${nodeId}: active on the service, and back in .verity/memory/${terminalText(result.path)}.`);
|
|
47213
|
+
process.exit(0);
|
|
47214
|
+
}
|
|
47215
|
+
let fetched = false;
|
|
47216
|
+
try {
|
|
47217
|
+
const pull = await pullRepoMemory({ serviceUrl: api.serviceUrl, token: api.token, verbose: api.verbose, force: true });
|
|
47218
|
+
fetched = pull.ok && !!await findLocalNode(nodeId);
|
|
47219
|
+
} catch {
|
|
47220
|
+
}
|
|
47221
|
+
printInfo(fetched ? `Restored ${nodeId}: active on the service, and pulled back into .verity/memory/.` : `Restored ${nodeId} on the service. Its file arrives with the next \`verity memory pull\`.`);
|
|
47222
|
+
if (trash?.reason) printInfo(` It was deleted with the reason: ${terminalText(trash.reason, 500)}`);
|
|
47223
|
+
process.exit(0);
|
|
47224
|
+
});
|
|
47225
|
+
}
|
|
47226
|
+
|
|
47227
|
+
// src/commands/memory-ask.ts
|
|
47228
|
+
var import_node_fs86 = require("node:fs");
|
|
47229
|
+
|
|
47230
|
+
// src/lib/memory-ask.ts
|
|
47231
|
+
var K1 = 1.2;
|
|
47232
|
+
var B = 0.75;
|
|
47233
|
+
var TITLE_WEIGHT = 3;
|
|
47234
|
+
var PATH_BONUS = 2;
|
|
47235
|
+
var RELATIVE_FLOOR = 0.25;
|
|
47236
|
+
var ANSWER_LINES = 3;
|
|
47237
|
+
var MIN_SENTENCE = 20;
|
|
47238
|
+
function terms(text3) {
|
|
47239
|
+
return text3.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2 && !STOPWORDS.has(w)).map(stem);
|
|
47240
|
+
}
|
|
47241
|
+
function stem(w) {
|
|
47242
|
+
if (w.length > 5 && w.endsWith("ies")) return `${w.slice(0, -3)}y`;
|
|
47243
|
+
if (w.length > 5 && w.endsWith("ing")) return w.slice(0, -3);
|
|
47244
|
+
if (w.length > 4 && w.endsWith("ed")) return w.slice(0, -2);
|
|
47245
|
+
if (w.length > 3 && w.endsWith("s") && !w.endsWith("ss")) return w.slice(0, -1);
|
|
47246
|
+
return w;
|
|
47247
|
+
}
|
|
47248
|
+
function globTerms(globs) {
|
|
47249
|
+
return terms(globs.join(" ").replace(/[/*._-]+/g, " "));
|
|
47250
|
+
}
|
|
47251
|
+
function askMemory(question, nodes, opts = {}) {
|
|
47252
|
+
const limit = Math.max(1, opts.limit ?? 5);
|
|
47253
|
+
const qTerms = [...new Set(terms(question))];
|
|
47254
|
+
const empty = { question, terms: qTerms, hits: [], answer: [] };
|
|
47255
|
+
if (nodes.length === 0 || qTerms.length === 0 && !opts.citedPaths?.length) return empty;
|
|
47256
|
+
const docs = nodes.map((n2) => {
|
|
47257
|
+
const words = [
|
|
47258
|
+
...Array.from({ length: TITLE_WEIGHT }, () => terms(n2.title)).flat(),
|
|
47259
|
+
...terms(n2.body),
|
|
47260
|
+
...globTerms(n2.fileGlobs)
|
|
47261
|
+
];
|
|
47262
|
+
const tf = /* @__PURE__ */ new Map();
|
|
47263
|
+
for (const w of words) tf.set(w, (tf.get(w) ?? 0) + 1);
|
|
47264
|
+
return { node: n2, tf, len: words.length };
|
|
47265
|
+
});
|
|
47266
|
+
const avgLen = docs.reduce((s, d) => s + d.len, 0) / docs.length || 1;
|
|
47267
|
+
const df = /* @__PURE__ */ new Map();
|
|
47268
|
+
for (const t2 of qTerms) df.set(t2, docs.filter((d) => d.tf.has(t2)).length);
|
|
47269
|
+
const idf = (t2) => {
|
|
47270
|
+
const n2 = df.get(t2) ?? 0;
|
|
47271
|
+
return Math.log(1 + (docs.length - n2 + 0.5) / (n2 + 0.5));
|
|
47272
|
+
};
|
|
47273
|
+
const cited = (opts.citedPaths ?? []).filter(Boolean);
|
|
47274
|
+
const scored = docs.map((d) => {
|
|
47275
|
+
let score = 0;
|
|
47276
|
+
for (const t2 of qTerms) {
|
|
47277
|
+
const f = d.tf.get(t2) ?? 0;
|
|
47278
|
+
if (f === 0) continue;
|
|
47279
|
+
score += idf(t2) * (f * (K1 + 1)) / (f + K1 * (1 - B + B * d.len / avgLen));
|
|
47280
|
+
}
|
|
47281
|
+
if (cited.length > 0 && d.node.fileGlobs.some((g) => matchesAny(g, cited))) score += PATH_BONUS;
|
|
47282
|
+
return { node: d.node, score };
|
|
47283
|
+
}).filter((s) => s.score > 0);
|
|
47284
|
+
scored.sort((a, b) => b.score - a.score || a.node.nodeId.localeCompare(b.node.nodeId));
|
|
47285
|
+
const top = scored[0]?.score ?? 0;
|
|
47286
|
+
const hits = scored.filter((s) => s.score >= top * RELATIVE_FLOOR).slice(0, limit).map((s) => ({
|
|
47287
|
+
...s.node,
|
|
47288
|
+
score: Math.round(s.score * 100) / 100,
|
|
47289
|
+
excerpt: bestSentences(s.node.body, qTerms, idf, 2)
|
|
47290
|
+
}));
|
|
47291
|
+
return { question, terms: qTerms, hits, answer: composeAnswer(hits, qTerms, idf) };
|
|
47292
|
+
}
|
|
47293
|
+
function matchesAny(glob, paths) {
|
|
47294
|
+
let re;
|
|
47295
|
+
try {
|
|
47296
|
+
re = globToRegex(glob);
|
|
47297
|
+
} catch {
|
|
47298
|
+
return false;
|
|
47299
|
+
}
|
|
47300
|
+
return paths.some((p) => re.test(p) || p === glob || glob.startsWith(`${p}/`));
|
|
47301
|
+
}
|
|
47302
|
+
function sentences(body2) {
|
|
47303
|
+
const prose = body2.replace(/```[\s\S]*?```/g, " ").split("\n").filter((l) => !/^\s*(#|- \[\[|\*\*[^*]+:\*\*\s*$|>)/.test(l)).join("\n");
|
|
47304
|
+
return prose.split(/(?<=[.!?])\s+(?=[A-Z0-9`"'(\[])|\n{2,}/).map((s) => s.replace(/\s+/g, " ").trim()).filter((s) => s.length >= MIN_SENTENCE);
|
|
47305
|
+
}
|
|
47306
|
+
function sentenceScore(sentence, qTerms, idf) {
|
|
47307
|
+
const own = new Set(terms(sentence));
|
|
47308
|
+
let s = 0;
|
|
47309
|
+
for (const t2 of qTerms) if (own.has(t2)) s += idf(t2);
|
|
47310
|
+
return s;
|
|
47311
|
+
}
|
|
47312
|
+
function bestSentences(body2, qTerms, idf, n2) {
|
|
47313
|
+
return sentences(body2).map((text3, i2) => ({ text: text3, i: i2, s: sentenceScore(text3, qTerms, idf) })).filter((x) => x.s > 0).sort((a, b) => b.s - a.s || a.i - b.i).slice(0, n2).map((x) => x.text);
|
|
47314
|
+
}
|
|
47315
|
+
function composeAnswer(hits, qTerms, idf) {
|
|
47316
|
+
const out2 = [];
|
|
47317
|
+
const seen = /* @__PURE__ */ new Set();
|
|
47318
|
+
const pool = hits.slice(0, 3);
|
|
47319
|
+
for (const round of [0, 1]) {
|
|
47320
|
+
for (const h of pool) {
|
|
47321
|
+
const text3 = h.excerpt[round];
|
|
47322
|
+
if (!text3 || seen.has(text3) || sentenceScore(text3, qTerms, idf) === 0) continue;
|
|
47323
|
+
seen.add(text3);
|
|
47324
|
+
out2.push({ text: text3, nodeId: h.nodeId });
|
|
47325
|
+
if (out2.length >= ANSWER_LINES) return out2;
|
|
47326
|
+
}
|
|
47327
|
+
}
|
|
47328
|
+
return out2;
|
|
47329
|
+
}
|
|
47330
|
+
|
|
47331
|
+
// src/commands/memory-ask.ts
|
|
47332
|
+
var BODY_JSON_MAX = 4e3;
|
|
47333
|
+
async function loadAskNodes(opts = {}) {
|
|
47334
|
+
const out2 = [];
|
|
47335
|
+
for (const n2 of await listLocalNodes()) {
|
|
47336
|
+
const status = readStatus(n2.content);
|
|
47337
|
+
if (!opts.includeInactive && status !== "active") continue;
|
|
47338
|
+
const fm = n2.content.match(/^---\n([\s\S]*?)\n---\n/);
|
|
47339
|
+
out2.push({
|
|
47340
|
+
nodeId: n2.nodeId,
|
|
47341
|
+
path: n2.path,
|
|
47342
|
+
kind: pickFrontmatter(fm?.[1] ?? "", "kind") ?? n2.path.split("/")[0],
|
|
47343
|
+
title: readTitle(n2.content) ?? n2.nodeId,
|
|
47344
|
+
body: n2.content.slice(fm ? fm[0].length : 0).trim(),
|
|
47345
|
+
fileGlobs: readGlobs(n2.content),
|
|
47346
|
+
status,
|
|
47347
|
+
scope: "repo"
|
|
47348
|
+
});
|
|
47349
|
+
}
|
|
47350
|
+
try {
|
|
47351
|
+
for (const c of await readOrgMirror()) {
|
|
47352
|
+
out2.push({
|
|
47353
|
+
nodeId: c.tag_id,
|
|
47354
|
+
path: null,
|
|
47355
|
+
kind: c.kind,
|
|
47356
|
+
title: c.title,
|
|
47357
|
+
body: c.body,
|
|
47358
|
+
fileGlobs: [],
|
|
47359
|
+
status: "active",
|
|
47360
|
+
scope: "org",
|
|
47361
|
+
origin: c.origin
|
|
47362
|
+
});
|
|
47363
|
+
}
|
|
47364
|
+
} catch {
|
|
47365
|
+
}
|
|
47366
|
+
return out2;
|
|
47367
|
+
}
|
|
47368
|
+
function printText(result, full) {
|
|
47369
|
+
const lines = [];
|
|
47370
|
+
if (result.answer.length > 0) {
|
|
47371
|
+
lines.push("Answer, quoted from the nodes (read the nodes before you rely on it):");
|
|
47372
|
+
for (const a of result.answer) lines.push(` - ${a.text} [${a.nodeId}]`);
|
|
47373
|
+
lines.push("");
|
|
47374
|
+
}
|
|
47375
|
+
lines.push(`${result.hits.length} node(s), best first:`);
|
|
47376
|
+
result.hits.forEach((h, i2) => {
|
|
47377
|
+
const where2 = h.scope === "org" ? `org claim ${h.origin ?? ""}`.trim() : `.verity/memory/${h.path}`;
|
|
47378
|
+
const status = h.status !== "active" ? ` \xB7 ${h.status}` : "";
|
|
47379
|
+
lines.push("");
|
|
47380
|
+
lines.push(`${i2 + 1}. ${h.nodeId} \xB7 ${h.kind}${status} \xB7 score ${h.score}`);
|
|
47381
|
+
lines.push(` ${h.title}`);
|
|
47382
|
+
lines.push(` ${where2}${h.fileGlobs.length > 0 ? ` \xB7 scope: ${h.fileGlobs.slice(0, 3).join(", ")}` : ""}`);
|
|
47383
|
+
if (full) {
|
|
47384
|
+
for (const l of h.body.split("\n")) lines.push(` ${l}`);
|
|
47385
|
+
} else {
|
|
47386
|
+
for (const e of h.excerpt) lines.push(` "${e}"`);
|
|
47387
|
+
}
|
|
47388
|
+
});
|
|
47389
|
+
console.log(terminalText(lines.join("\n"), Number.POSITIVE_INFINITY));
|
|
47390
|
+
}
|
|
47391
|
+
function registerMemoryAskCommand(memory2) {
|
|
47392
|
+
memory2.command("ask <question...>").alias("recall").description("Ask the knowledge graph a question. Prints the matching nodes with their ids, and the sentences that answer it. Local: nothing is sent").option("--limit <n>", "At most this many nodes (default 5)", "5").option("--json", "Print JSON: the quoted answer, and each node with its id, path, score and body").option("--full", "Print each node's whole body instead of the matching sentences").option("--all", "Include archived and superseded nodes still on disk").action(async (words, opts) => {
|
|
47393
|
+
const fromStdin = words.length === 1 && words[0] === "-";
|
|
47394
|
+
const stdin = fromStdin ? readTextInput("-", "the question") : null;
|
|
47395
|
+
if (stdin && !stdin.ok) {
|
|
47396
|
+
printError(stdin.error);
|
|
47397
|
+
process.exit(1);
|
|
47398
|
+
}
|
|
47399
|
+
const question = (stdin ? stdin.text ?? "" : words.join(" ")).replace(/\s+/g, " ").trim();
|
|
47400
|
+
const limit = Number.parseInt(opts.limit, 10);
|
|
47401
|
+
if (!question) {
|
|
47402
|
+
printError('Ask a question: verity memory ask "why do we \u2026"');
|
|
47403
|
+
process.exit(1);
|
|
47404
|
+
}
|
|
47405
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 50) {
|
|
47406
|
+
printError("--limit is 1 to 50.");
|
|
47407
|
+
process.exit(1);
|
|
47408
|
+
}
|
|
47409
|
+
try {
|
|
47410
|
+
process.chdir(repoRoot());
|
|
47411
|
+
} catch {
|
|
47412
|
+
}
|
|
47413
|
+
refuseLinkedMemoryDir();
|
|
47414
|
+
const nodes = await loadAskNodes({ includeInactive: !!opts.all });
|
|
47415
|
+
let tree2 = [];
|
|
47416
|
+
try {
|
|
47417
|
+
tree2 = listTrackedFiles();
|
|
47418
|
+
} catch {
|
|
47419
|
+
}
|
|
47420
|
+
const result = askMemory(question, nodes, { limit, citedPaths: deriveFileGlobs(question, tree2) });
|
|
47421
|
+
if (opts.json) {
|
|
47422
|
+
console.log(JSON.stringify({
|
|
47423
|
+
question: result.question,
|
|
47424
|
+
answer: result.answer.map((a) => ({ text: a.text, node_id: a.nodeId })),
|
|
47425
|
+
nodes: result.hits.map((h) => ({
|
|
47426
|
+
node_id: h.nodeId,
|
|
47427
|
+
kind: h.kind,
|
|
47428
|
+
title: h.title,
|
|
47429
|
+
status: h.status,
|
|
47430
|
+
scope: h.scope,
|
|
47431
|
+
...h.scope === "org" ? { origin: h.origin ?? null } : { path: `.verity/memory/${h.path}` },
|
|
47432
|
+
file_globs: h.fileGlobs,
|
|
47433
|
+
score: h.score,
|
|
47434
|
+
excerpt: h.excerpt,
|
|
47435
|
+
body: h.body.length > BODY_JSON_MAX ? `${h.body.slice(0, BODY_JSON_MAX)}\u2026` : h.body
|
|
47436
|
+
}))
|
|
47437
|
+
}, null, 2));
|
|
47438
|
+
process.exit(0);
|
|
47439
|
+
}
|
|
47440
|
+
if (nodes.length === 0) {
|
|
47441
|
+
const hasGraph = (0, import_node_fs86.existsSync)(projectPath(`${VERITY_DIR}/memory`));
|
|
47442
|
+
console.log(hasGraph ? "The knowledge graph here has no nodes yet." : "No knowledge graph in this repository yet. `verity memory pull` fetches it.");
|
|
47443
|
+
process.exit(0);
|
|
47444
|
+
}
|
|
47445
|
+
if (result.hits.length === 0) {
|
|
47446
|
+
console.log(`No node matches "${terminalText(question, 200)}". Try other words, or \`verity memory pull\` if the graph here is old.`);
|
|
47447
|
+
process.exit(0);
|
|
47448
|
+
}
|
|
47449
|
+
printText(result, !!opts.full);
|
|
47450
|
+
process.exit(0);
|
|
47451
|
+
});
|
|
47452
|
+
}
|
|
47453
|
+
|
|
47454
|
+
// src/commands/memory-dream.ts
|
|
47455
|
+
var import_promises19 = require("node:fs/promises");
|
|
47456
|
+
var import_node_fs87 = require("node:fs");
|
|
47457
|
+
var import_node_path73 = require("node:path");
|
|
47458
|
+
|
|
47459
|
+
// src/lib/memory-dream.ts
|
|
47460
|
+
var MERGE_AT = 0.6;
|
|
47461
|
+
var REVIEW_AT = 0.4;
|
|
47462
|
+
var REVIEW_MAX = 40;
|
|
47463
|
+
function toDreamNode(file) {
|
|
47464
|
+
if (readStatus(file.content) !== "active") return null;
|
|
47465
|
+
const m = file.content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
47466
|
+
const fm = m?.[1] ?? "";
|
|
47467
|
+
const supersededBy = pickFrontmatter(fm, "superseded_by");
|
|
47468
|
+
const confidence = Number.parseFloat(pickFrontmatter(fm, "confidence") ?? "");
|
|
47469
|
+
return {
|
|
47470
|
+
nodeId: file.nodeId,
|
|
47471
|
+
path: file.path,
|
|
47472
|
+
kind: pickFrontmatter(fm, "kind") ?? file.path.split("/")[0],
|
|
47473
|
+
title: readTitle(file.content) ?? file.nodeId,
|
|
47474
|
+
body: ownText(m?.[2] ?? ""),
|
|
47475
|
+
fileGlobs: readGlobs(file.content),
|
|
47476
|
+
source: pickFrontmatter(fm, "source") ?? "extractor",
|
|
47477
|
+
confidence: Number.isFinite(confidence) ? confidence : 0.5,
|
|
47478
|
+
createdAt: pickFrontmatter(fm, "created_at") ?? "",
|
|
47479
|
+
createdBy: pickFrontmatter(fm, "created_by") ?? "",
|
|
47480
|
+
supersededBy: supersededBy && supersededBy !== "null" ? supersededBy : null,
|
|
47481
|
+
contradicts: pickFrontmatterArray(fm, "contradicts")
|
|
47482
|
+
};
|
|
47483
|
+
}
|
|
47484
|
+
function ownText(rest) {
|
|
47485
|
+
return rest.replace(/^\s*# .*\n/, "").replace(/\n## Related\n[\s\S]*$/, "").trim();
|
|
47486
|
+
}
|
|
47487
|
+
var STUB_SENTENCES = [
|
|
47488
|
+
/This project uses \*\*[^*\n]+\*\*\.?/g,
|
|
47489
|
+
/Capture gotchas and integration-specific decisions here via `\/verity-reflect` as they come up during work\.?/g
|
|
47490
|
+
];
|
|
47491
|
+
function isPlaceholder(n2) {
|
|
47492
|
+
let own = n2.body;
|
|
47493
|
+
let template = false;
|
|
47494
|
+
for (const re of STUB_SENTENCES) {
|
|
47495
|
+
const stripped = own.replace(re, " ");
|
|
47496
|
+
if (stripped !== own) template = true;
|
|
47497
|
+
own = stripped;
|
|
47498
|
+
}
|
|
47499
|
+
const ownTerms = terms(own);
|
|
47500
|
+
if (template) return ownTerms.length === 0;
|
|
47501
|
+
return ownTerms.length === 0 && terms(n2.title).length === 0;
|
|
47502
|
+
}
|
|
47503
|
+
function similarity(a, b) {
|
|
47504
|
+
const all = jaccard(new Set(terms(`${a.title} ${a.body}`)), new Set(terms(`${b.title} ${b.body}`)));
|
|
47505
|
+
const title = jaccard(new Set(terms(a.title)), new Set(terms(b.title)));
|
|
47506
|
+
return Math.max(all, (all + title) / 2);
|
|
47507
|
+
}
|
|
47508
|
+
function jaccard(a, b) {
|
|
47509
|
+
if (a.size === 0 || b.size === 0) return 0;
|
|
47510
|
+
let inter = 0;
|
|
47511
|
+
for (const x of a) if (b.has(x)) inter++;
|
|
47512
|
+
return inter / (a.size + b.size - inter);
|
|
47513
|
+
}
|
|
47514
|
+
var SOURCE_RANK = { user: 3, agent: 2, extractor: 1 };
|
|
47515
|
+
function compareStrength(a, b) {
|
|
47516
|
+
return (SOURCE_RANK[b.source] ?? 0) - (SOURCE_RANK[a.source] ?? 0) || b.confidence - a.confidence || b.fileGlobs.length - a.fileGlobs.length || a.createdAt.localeCompare(b.createdAt) || a.nodeId.localeCompare(b.nodeId);
|
|
47517
|
+
}
|
|
47518
|
+
var isConfirmed = (n2) => n2.source === "user";
|
|
47519
|
+
var pairKey = (a, b) => [a, b].sort().join("|");
|
|
47520
|
+
var NEGATION = /\b(?:not|no|never|none|nor|neither|without|avoid|stop|forbid(?:den)?|disallow(?:ed)?|cannot|can't|cant|don't|dont|doesn't|doesnt|didn't|isn't|aren't|wasn't|won't|wont|shouldn't|mustn't|needn't)\b/g;
|
|
47521
|
+
function negations(text3) {
|
|
47522
|
+
return (text3.toLowerCase().replace(/[’`]/g, "'").match(NEGATION) ?? []).length;
|
|
47523
|
+
}
|
|
47524
|
+
function negatedTerms(text3) {
|
|
47525
|
+
const lower = text3.toLowerCase().replace(/[’`]/g, "'");
|
|
47526
|
+
const out2 = [];
|
|
47527
|
+
for (const m of lower.matchAll(NEGATION)) {
|
|
47528
|
+
const after = lower.slice((m.index ?? 0) + m[0].length, (m.index ?? 0) + m[0].length + 60);
|
|
47529
|
+
out2.push(terms(after)[0] ?? "\u2205");
|
|
47530
|
+
}
|
|
47531
|
+
return out2.sort();
|
|
47532
|
+
}
|
|
47533
|
+
function samePolarity(a, b) {
|
|
47534
|
+
if (negations(a.title) > 0 !== negations(b.title) > 0) return false;
|
|
47535
|
+
const na = negatedTerms(`${a.title}. ${a.body}`);
|
|
47536
|
+
const nb = negatedTerms(`${b.title}. ${b.body}`);
|
|
47537
|
+
return na.length === nb.length && na.every((t2, i2) => t2 === nb[i2]);
|
|
47538
|
+
}
|
|
47539
|
+
var round2 = (x) => Math.round(x * 100) / 100;
|
|
47540
|
+
function planDream(input, opts = {}) {
|
|
47541
|
+
const mergeAt = opts.mergeAt ?? MERGE_AT;
|
|
47542
|
+
const reviewAt = Math.min(opts.reviewAt ?? REVIEW_AT, mergeAt);
|
|
47543
|
+
const byId = new Map(input.map((n2) => [n2.nodeId, n2]));
|
|
47544
|
+
const changes = [];
|
|
47545
|
+
const review = [];
|
|
47546
|
+
const touched = /* @__PURE__ */ new Set();
|
|
47547
|
+
const reviewed = /* @__PURE__ */ new Set();
|
|
47548
|
+
const addReview = (a, b, why, sim) => {
|
|
47549
|
+
const key = pairKey(a.nodeId, b.nodeId);
|
|
47550
|
+
if (reviewed.has(key)) return;
|
|
47551
|
+
reviewed.add(key);
|
|
47552
|
+
review.push({ nodes: [a.nodeId, b.nodeId], titles: [a.title, b.title], why, ...sim !== void 0 && { similarity: round2(sim) } });
|
|
47553
|
+
};
|
|
47554
|
+
for (const n2 of input) {
|
|
47555
|
+
if (isConfirmed(n2) || !isPlaceholder(n2)) continue;
|
|
47556
|
+
changes.push({ type: "prune", nodeId: n2.nodeId, title: n2.title, why: "a placeholder with no content of its own" });
|
|
47557
|
+
touched.add(n2.nodeId);
|
|
47558
|
+
}
|
|
47559
|
+
for (const n2 of input) {
|
|
47560
|
+
if (touched.has(n2.nodeId) || !n2.supersededBy) continue;
|
|
47561
|
+
const by = byId.get(n2.supersededBy);
|
|
47562
|
+
if (!by || touched.has(by.nodeId)) continue;
|
|
47563
|
+
if (isConfirmed(n2)) {
|
|
47564
|
+
addReview(n2, by, "marked superseded, but a person confirmed it");
|
|
47565
|
+
continue;
|
|
47566
|
+
}
|
|
47567
|
+
changes.push({ type: "supersede", nodeId: n2.nodeId, title: n2.title, by: by.nodeId, why: `its superseded_by names ${by.nodeId}` });
|
|
47568
|
+
touched.add(n2.nodeId);
|
|
47569
|
+
}
|
|
47570
|
+
for (const a of input) {
|
|
47571
|
+
for (const id of a.contradicts) {
|
|
47572
|
+
const b = byId.get(id);
|
|
47573
|
+
if (!b || touched.has(a.nodeId) || touched.has(b.nodeId)) continue;
|
|
47574
|
+
if (isConfirmed(a) !== isConfirmed(b)) {
|
|
47575
|
+
const [keep, drop] = isConfirmed(a) ? [a, b] : [b, a];
|
|
47576
|
+
changes.push({ type: "supersede", nodeId: drop.nodeId, title: drop.title, by: keep.nodeId, why: `contradicts ${keep.nodeId}, which a person confirmed` });
|
|
47577
|
+
touched.add(drop.nodeId);
|
|
47578
|
+
} else {
|
|
47579
|
+
addReview(a, b, "they contradict each other, and nothing in the graph says which is right");
|
|
47580
|
+
}
|
|
47581
|
+
}
|
|
47582
|
+
}
|
|
47583
|
+
const contradicting = /* @__PURE__ */ new Set();
|
|
47584
|
+
for (const a of input) for (const id of a.contradicts) contradicting.add(pairKey(a.nodeId, id));
|
|
47585
|
+
const pool = input.filter((n2) => !touched.has(n2.nodeId)).sort(compareStrength);
|
|
47586
|
+
const merged = /* @__PURE__ */ new Set();
|
|
47587
|
+
for (let i2 = 0; i2 < pool.length; i2++) {
|
|
47588
|
+
const keep = pool[i2];
|
|
47589
|
+
if (merged.has(keep.nodeId)) continue;
|
|
47590
|
+
for (let j = i2 + 1; j < pool.length; j++) {
|
|
47591
|
+
const other = pool[j];
|
|
47592
|
+
if (merged.has(other.nodeId) || other.kind !== keep.kind) continue;
|
|
47593
|
+
if (contradicting.has(pairKey(keep.nodeId, other.nodeId))) continue;
|
|
47594
|
+
const sim = similarity(keep, other);
|
|
47595
|
+
if (sim < reviewAt) continue;
|
|
47596
|
+
if (sim < mergeAt) {
|
|
47597
|
+
addReview(keep, other, "they may say the same thing", sim);
|
|
47598
|
+
continue;
|
|
47599
|
+
}
|
|
47600
|
+
if (!samePolarity(keep, other)) {
|
|
47601
|
+
addReview(keep, other, "one says not or never where the other does not: they may say opposite things", sim);
|
|
47602
|
+
continue;
|
|
47603
|
+
}
|
|
47604
|
+
if (isConfirmed(other)) {
|
|
47605
|
+
addReview(keep, other, "a near-duplicate, but a person confirmed both or the weaker one", sim);
|
|
47606
|
+
continue;
|
|
47607
|
+
}
|
|
47608
|
+
const addGlobs = other.fileGlobs.filter((g) => !keep.fileGlobs.includes(g));
|
|
47609
|
+
if (isConfirmed(keep) && addGlobs.length > 0) {
|
|
47610
|
+
addReview(keep, other, `a near-duplicate, but merging would widen the scope of ${keep.nodeId}, which a person confirmed`, sim);
|
|
47611
|
+
continue;
|
|
47612
|
+
}
|
|
47613
|
+
changes.push({
|
|
47614
|
+
type: "merge",
|
|
47615
|
+
nodeId: other.nodeId,
|
|
47616
|
+
title: other.title,
|
|
47617
|
+
into: keep.nodeId,
|
|
47618
|
+
similarity: round2(sim),
|
|
47619
|
+
addGlobs,
|
|
47620
|
+
why: `a near-duplicate of ${keep.nodeId}`
|
|
47621
|
+
});
|
|
47622
|
+
merged.add(other.nodeId);
|
|
47623
|
+
}
|
|
47624
|
+
}
|
|
47625
|
+
review.sort((a, b) => (b.similarity ?? 1) - (a.similarity ?? 1));
|
|
47626
|
+
return {
|
|
47627
|
+
scanned: input.length,
|
|
47628
|
+
changes,
|
|
47629
|
+
review: review.slice(0, REVIEW_MAX),
|
|
47630
|
+
reviewOverflow: Math.max(0, review.length - REVIEW_MAX)
|
|
47631
|
+
};
|
|
47632
|
+
}
|
|
47633
|
+
function oneLine2(title, max = 90) {
|
|
47634
|
+
const flat = title.replace(/\s+/g, " ").trim();
|
|
47635
|
+
return flat.length <= max ? flat : `${flat.slice(0, max - 1).replace(/\s+\S*$/, "")}\u2026`;
|
|
47636
|
+
}
|
|
47637
|
+
function renderPlan(plan, heading) {
|
|
47638
|
+
const lines = [`# ${heading}`, "", `Scanned ${plan.scanned} active node(s).`, ""];
|
|
47639
|
+
const of = (t2) => plan.changes.filter((c) => c.type === t2);
|
|
47640
|
+
const section = (title, items, line2) => {
|
|
47641
|
+
if (items.length === 0) return;
|
|
47642
|
+
lines.push(`## ${title} (${items.length})`, "");
|
|
47643
|
+
for (const c of items) lines.push(line2(c));
|
|
47644
|
+
lines.push("");
|
|
47645
|
+
};
|
|
47646
|
+
section("Merge near-duplicates", of("merge"), (c) => {
|
|
47647
|
+
const m = c;
|
|
47648
|
+
return `- ${m.nodeId} \u2192 into ${m.into} (overlap ${m.similarity})${m.addGlobs.length ? `, scope +${m.addGlobs.join(", ")}` : ""} \u2014 ${oneLine2(m.title)}`;
|
|
47649
|
+
});
|
|
47650
|
+
section("Retire superseded or contradicted", of("supersede"), (c) => `- ${c.nodeId} \u2014 ${c.why} \u2014 ${oneLine2(c.title)}`);
|
|
47651
|
+
section("Prune placeholders", of("prune"), (c) => `- ${c.nodeId} \u2014 ${oneLine2(c.title)}`);
|
|
47652
|
+
if (plan.changes.length === 0) lines.push("Nothing to change.", "");
|
|
47653
|
+
if (plan.review.length > 0) {
|
|
47654
|
+
lines.push(`## For a person to decide (${plan.review.length + plan.reviewOverflow}, not applied)`, "");
|
|
47655
|
+
for (const r of plan.review) {
|
|
47656
|
+
lines.push(`- ${r.nodes[0]} / ${r.nodes[1]}${r.similarity !== void 0 ? ` (overlap ${r.similarity})` : ""} \u2014 ${r.why}`);
|
|
47657
|
+
lines.push(` "${oneLine2(r.titles[0], 70)}" / "${oneLine2(r.titles[1], 70)}"`);
|
|
47658
|
+
}
|
|
47659
|
+
if (plan.reviewOverflow > 0) lines.push(`- \u2026 and ${plan.reviewOverflow} more`);
|
|
47660
|
+
lines.push("", 'To act on one: verity memory delete <id> --reason "duplicate of <id>"', "");
|
|
47661
|
+
}
|
|
47662
|
+
return terminalText(lines.join("\n"), Number.POSITIVE_INFINITY);
|
|
47663
|
+
}
|
|
47664
|
+
|
|
47665
|
+
// src/commands/memory-dream.ts
|
|
47666
|
+
var dreamsDir = () => projectPath(`${VERITY_DIR}/dreams`);
|
|
47667
|
+
var DREAM_ID = /^dream-\d{8}-\d{6}(?:-\d{3})?$/;
|
|
47668
|
+
var MAX_RECORD_BYTES2 = 8 * 1024 * 1024;
|
|
47669
|
+
function newDreamId(now = Date.now()) {
|
|
47670
|
+
for (let t2 = now; ; t2++) {
|
|
47671
|
+
const iso = new Date(t2).toISOString();
|
|
47672
|
+
const id = `dream-${iso.slice(0, 10).replace(/-/g, "")}-${iso.slice(11, 19).replace(/:/g, "")}-${iso.slice(20, 23)}`;
|
|
47673
|
+
if (!(0, import_node_fs87.existsSync)((0, import_node_path73.join)(dreamsDir(), `${id}.json`))) return id;
|
|
47674
|
+
}
|
|
47675
|
+
}
|
|
47676
|
+
async function saveRecord(record2) {
|
|
47677
|
+
const outcome = record2.results.length > 0 ? ["## Outcome", "", ...record2.results.map((r) => `- ${r.ok ? "done" : "FAILED"}: ${r.type} ${r.nodeId}${r.error ? ` \u2014 ${r.error}` : ""}`), ""] : [];
|
|
47678
|
+
const footer = [`Status: ${record2.status}.`, "", `Undo: verity memory dream --revert ${record2.id}`, ""];
|
|
47679
|
+
const saved = await writeFileAtomicInTree((0, import_node_path73.join)(dreamsDir(), `${record2.id}.json`), JSON.stringify(record2, null, 2) + "\n") && await writeFileAtomicInTree((0, import_node_path73.join)(dreamsDir(), `${record2.id}.md`), [renderPlan(record2.plan, record2.id), ...outcome, ...footer].join("\n"));
|
|
47680
|
+
if (!saved) throw new Error(".verity/dreams/ is a link, or a folder outside the repository");
|
|
47681
|
+
}
|
|
47682
|
+
async function readRecord(id) {
|
|
47683
|
+
if (!DREAM_ID.test(id)) return null;
|
|
47684
|
+
const read2 = readRegularFileInRoot(repoRoot(), (0, import_node_path73.join)(dreamsDir(), `${id}.json`), MAX_RECORD_BYTES2);
|
|
47685
|
+
if (!read2.ok) return null;
|
|
47686
|
+
try {
|
|
47687
|
+
const parsed = JSON.parse(read2.content);
|
|
47688
|
+
return isDreamRecord(parsed) && parsed.id === id ? parsed : null;
|
|
47689
|
+
} catch {
|
|
47690
|
+
return null;
|
|
47691
|
+
}
|
|
47692
|
+
}
|
|
47693
|
+
function isDreamRecord(v) {
|
|
47694
|
+
const r = v;
|
|
47695
|
+
const str4 = (x) => typeof x === "string";
|
|
47696
|
+
if (!r || !str4(r.id) || !["applying", "applied", "partial", "reverted"].includes(r.status)) return false;
|
|
47697
|
+
if (!r.plan || !Array.isArray(r.plan.changes) || !r.plan.changes.every((c) => !!c && str4(c.nodeId) && str4(c.type))) return false;
|
|
47698
|
+
if (!Array.isArray(r.results) || !r.results.every((x) => !!x && str4(x.nodeId) && str4(x.type) && typeof x.ok === "boolean")) return false;
|
|
47699
|
+
if (!r.before || typeof r.before !== "object" || Array.isArray(r.before)) return false;
|
|
47700
|
+
const before = r.before;
|
|
47701
|
+
if (!Object.values(before).every((b) => !!b && str4(b.path) && str4(b.content))) return false;
|
|
47702
|
+
if (r.after !== void 0) {
|
|
47703
|
+
if (!r.after || typeof r.after !== "object" || Array.isArray(r.after)) return false;
|
|
47704
|
+
if (!Object.values(r.after).every((b) => !!b && str4(b.path) && str4(b.content))) return false;
|
|
47705
|
+
}
|
|
47706
|
+
return r.results.every((x) => !x.ok || Object.prototype.hasOwnProperty.call(before, x.nodeId));
|
|
47707
|
+
}
|
|
47708
|
+
async function resolveApi2(program2) {
|
|
47709
|
+
const globals = program2.opts();
|
|
47710
|
+
const tokenResult = await resolveToken(globals.token);
|
|
47711
|
+
if (!tokenResult.ok) {
|
|
47712
|
+
printError(tokenResult.error);
|
|
47713
|
+
process.exit(1);
|
|
47714
|
+
}
|
|
47715
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
47716
|
+
if (!urlResult.ok) {
|
|
47717
|
+
printError(urlResult.error);
|
|
47718
|
+
process.exit(1);
|
|
47719
|
+
}
|
|
47720
|
+
return { serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose };
|
|
47721
|
+
}
|
|
47722
|
+
async function currentNodes() {
|
|
47723
|
+
return (await listLocalNodes()).map(toDreamNode).filter((n2) => n2 !== null);
|
|
47724
|
+
}
|
|
47725
|
+
async function applyDreamPlan(api, plan) {
|
|
47726
|
+
const record2 = { id: newDreamId(), created_at: (/* @__PURE__ */ new Date()).toISOString(), status: "applying", plan, before: {}, results: [] };
|
|
47727
|
+
const keepers = new Set(plan.changes.filter((c) => c.type === "merge" && c.addGlobs.length > 0).map((c) => c.into));
|
|
47728
|
+
for (const id of [...plan.changes.map((c) => c.nodeId), ...keepers]) {
|
|
47729
|
+
const local = await findLocalNode(id);
|
|
47730
|
+
if (local) record2.before[id] = { path: local.path, content: local.content };
|
|
47731
|
+
}
|
|
47732
|
+
await saveRecord(record2);
|
|
47733
|
+
const addGlobs = /* @__PURE__ */ new Map();
|
|
47734
|
+
for (const c of plan.changes) {
|
|
47735
|
+
if (c.type === "merge" && c.addGlobs.length > 0 && record2.before[c.nodeId]) {
|
|
47736
|
+
addGlobs.set(c.into, [...addGlobs.get(c.into) ?? [], ...c.addGlobs]);
|
|
47737
|
+
}
|
|
47738
|
+
}
|
|
47739
|
+
const keeperFailed = /* @__PURE__ */ new Set();
|
|
47740
|
+
for (const [id, globs] of addGlobs) {
|
|
47741
|
+
const keeper = await findLocalNode(id);
|
|
47742
|
+
let error;
|
|
47743
|
+
if (!keeper || !record2.before[id]) error = "the file is not here";
|
|
47744
|
+
else {
|
|
47745
|
+
const before = record2.before[id];
|
|
47746
|
+
const next = applyNodeChanges(keeper.content, { fileGlobs: [.../* @__PURE__ */ new Set([...readGlobs(keeper.content), ...globs])] });
|
|
47747
|
+
const result = next === null ? { ok: false, error: "no frontmatter" } : await editNode(api, keeper, next);
|
|
47748
|
+
if (result.ok && result.mismatched.length === 0) {
|
|
47749
|
+
record2.after = { ...record2.after ?? {}, [id]: { path: before.path, content: result.final } };
|
|
47750
|
+
} else if (result.ok && next !== null) {
|
|
47751
|
+
error = `the service did not take: ${result.mismatched.join(", ")}`;
|
|
47752
|
+
const back = await editNode(api, { ...keeper, content: next }, before.content, { force: true });
|
|
47753
|
+
if (back.ok && back.mismatched.length === 0) error = `${error}; put back as it was`;
|
|
47754
|
+
else {
|
|
47755
|
+
const held = back.ok ? back.final : result.final;
|
|
47756
|
+
record2.after = { ...record2.after ?? {}, [id]: { path: before.path, content: held } };
|
|
47757
|
+
error = `${error}; and putting it back failed (${back.ok ? back.mismatched.join(", ") : back.error}): --revert retries`;
|
|
47758
|
+
}
|
|
47759
|
+
} else {
|
|
47760
|
+
error = result.ok ? void 0 : result.error;
|
|
47761
|
+
if (!await writeNodeFile(before.path, before.content)) {
|
|
47762
|
+
error = `${error}; and the file here could not be put back, so it still has the wider scope (--revert puts it back)`;
|
|
47763
|
+
}
|
|
47764
|
+
}
|
|
47765
|
+
}
|
|
47766
|
+
if (error) keeperFailed.add(id);
|
|
47767
|
+
record2.results.push({ nodeId: id, type: "scope", ok: !error, ...error && { error } });
|
|
47768
|
+
await saveRecord(record2);
|
|
47769
|
+
}
|
|
47770
|
+
for (const change of plan.changes) {
|
|
47771
|
+
if (!record2.before[change.nodeId]) {
|
|
47772
|
+
record2.results.push({ nodeId: change.nodeId, type: change.type, ok: false, error: "its file is gone, so its text could not be saved; not changed" });
|
|
47773
|
+
await saveRecord(record2);
|
|
47774
|
+
continue;
|
|
47775
|
+
}
|
|
47776
|
+
if (change.type === "merge" && keeperFailed.has(change.into)) {
|
|
47777
|
+
record2.results.push({ nodeId: change.nodeId, type: change.type, ok: false, error: `${change.into} could not take its scope; not merged` });
|
|
47778
|
+
await saveRecord(record2);
|
|
47779
|
+
continue;
|
|
47780
|
+
}
|
|
47781
|
+
const result = await deleteNode(api, change.nodeId, {
|
|
47782
|
+
status: change.type === "prune" ? "archived" : "superseded",
|
|
47783
|
+
via: "dream",
|
|
47784
|
+
reason: `${record2.id}: ${change.why}`
|
|
47785
|
+
});
|
|
47786
|
+
record2.results.push({
|
|
47787
|
+
nodeId: change.nodeId,
|
|
47788
|
+
type: change.type,
|
|
47789
|
+
ok: result.ok,
|
|
47790
|
+
...!result.ok && { error: result.error },
|
|
47791
|
+
...result.ok && result.fileKept && { error: "done on the service, but the file here could not be removed" }
|
|
47792
|
+
});
|
|
47793
|
+
await saveRecord(record2);
|
|
47794
|
+
}
|
|
47795
|
+
record2.status = record2.results.every((r) => r.ok) ? "applied" : "partial";
|
|
47796
|
+
await saveRecord(record2);
|
|
47797
|
+
return record2;
|
|
47798
|
+
}
|
|
47799
|
+
async function revert(api, record2) {
|
|
47800
|
+
const out2 = [];
|
|
47801
|
+
if (record2.status === "applying") {
|
|
47802
|
+
const seen = new Set(record2.results.map((r) => r.nodeId));
|
|
47803
|
+
for (const c of record2.plan.changes) {
|
|
47804
|
+
if (!seen.has(c.nodeId) && record2.before[c.nodeId]) record2.results.push({ nodeId: c.nodeId, type: c.type, ok: true });
|
|
47805
|
+
}
|
|
47806
|
+
const planned = new Set(record2.plan.changes.map((c) => c.nodeId));
|
|
47807
|
+
for (const id of Object.keys(record2.before)) {
|
|
47808
|
+
if (!planned.has(id) && !seen.has(id)) record2.results.push({ nodeId: id, type: "scope", ok: true });
|
|
47809
|
+
}
|
|
47810
|
+
}
|
|
47811
|
+
const changedOnService = record2.results.filter((x) => x.type === "scope" && (x.ok || record2.after?.[x.nodeId]));
|
|
47812
|
+
for (const r of changedOnService) {
|
|
47813
|
+
const before = record2.before[r.nodeId];
|
|
47814
|
+
if (!before) {
|
|
47815
|
+
out2.push({ ...r, ok: false, error: "no saved text" });
|
|
47816
|
+
continue;
|
|
47817
|
+
}
|
|
47818
|
+
const current = await findLocalNode(r.nodeId) ?? { nodeId: r.nodeId, path: before.path, content: "" };
|
|
47819
|
+
const after = record2.after?.[r.nodeId]?.content;
|
|
47820
|
+
let result = await editNode(api, current, before.content, after !== void 0 ? { base: after } : {});
|
|
47821
|
+
if (!result.ok && result.stale && after !== void 0) {
|
|
47822
|
+
result = await editNode(api, current, before.content, { force: true, expect: before.content });
|
|
47823
|
+
}
|
|
47824
|
+
const error = !result.ok ? result.stale ? "changed on the service since this dream; not reverted. Fix it with `verity memory edit`" : result.error : result.mismatched.length > 0 ? `the service did not take: ${result.mismatched.join(", ")}` : void 0;
|
|
47825
|
+
out2.push({ nodeId: r.nodeId, type: "scope", ok: !error, ...error && { error } });
|
|
47826
|
+
}
|
|
47827
|
+
for (const r of record2.results.filter((x) => !x.ok && x.type === "scope" && !record2.after?.[x.nodeId])) {
|
|
47828
|
+
const before = record2.before[r.nodeId];
|
|
47829
|
+
const current = await findLocalNode(r.nodeId);
|
|
47830
|
+
if (!before || current?.content === before.content) continue;
|
|
47831
|
+
const ok = await writeNodeFile(before.path, before.content);
|
|
47832
|
+
out2.push({ nodeId: r.nodeId, type: "scope", ok, ...!ok && { error: "the file here could not be written back" } });
|
|
47833
|
+
}
|
|
47834
|
+
for (const r of [...record2.results].reverse().filter((x) => x.ok && x.type !== "scope")) {
|
|
47835
|
+
const result = await restoreNode(api, r.nodeId, record2.before[r.nodeId]);
|
|
47836
|
+
out2.push({ nodeId: r.nodeId, type: r.type, ok: result.ok, ...!result.ok && { error: result.error } });
|
|
47837
|
+
}
|
|
47838
|
+
return out2;
|
|
47839
|
+
}
|
|
47840
|
+
function registerMemoryDreamCommand(memory2, program2) {
|
|
47841
|
+
memory2.command("dream").description("Consolidate the knowledge graph: merge near-duplicates, retire superseded nodes, prune empty placeholders. Prints the plan; --apply makes the changes and saves a report that --revert undoes").option("--apply", "Make the changes, on the service and here, and save a report in .verity/dreams/").option("--revert <dream-id>", "Undo an applied dream").option("--list", "List the dreams applied on this machine").option("--json", "Print the plan, or the outcome, as JSON").action(async (opts) => {
|
|
47842
|
+
try {
|
|
47843
|
+
process.chdir(repoRoot());
|
|
47844
|
+
} catch {
|
|
47845
|
+
}
|
|
47846
|
+
refuseLinkedMemoryDir();
|
|
47847
|
+
if ([opts.apply, opts.revert !== void 0, opts.list].filter(Boolean).length > 1) {
|
|
47848
|
+
printError("Use one of --apply, --revert and --list.");
|
|
47849
|
+
process.exit(1);
|
|
47850
|
+
}
|
|
47851
|
+
if (opts.list) {
|
|
47852
|
+
const files = (0, import_node_fs87.existsSync)(dreamsDir()) ? (await (0, import_promises19.readdir)(dreamsDir())).filter((f) => f.endsWith(".json") && DREAM_ID.test(f.slice(0, -5))).sort() : [];
|
|
47853
|
+
if (files.length === 0) {
|
|
47854
|
+
console.log("No dream has been applied on this machine.");
|
|
47855
|
+
process.exit(0);
|
|
47856
|
+
}
|
|
47857
|
+
for (const f of files) {
|
|
47858
|
+
const r = await readRecord(f.slice(0, -5));
|
|
47859
|
+
if (r) console.log(terminalText(`${r.id} ${r.status.padEnd(8)} ${r.results.filter((x) => x.ok).length} change(s) .verity/dreams/${r.id}.md`));
|
|
47860
|
+
}
|
|
47861
|
+
process.exit(0);
|
|
47862
|
+
}
|
|
47863
|
+
if (opts.revert !== void 0) {
|
|
47864
|
+
if (!DREAM_ID.test(opts.revert)) {
|
|
47865
|
+
printError("A dream id looks like dream-20261112-101500. `verity memory dream --list` lists them.");
|
|
47866
|
+
process.exit(1);
|
|
47867
|
+
}
|
|
47868
|
+
const record3 = await readRecord(opts.revert);
|
|
47869
|
+
if (!record3) {
|
|
47870
|
+
printError(`No valid record .verity/dreams/${opts.revert}.json on this machine (missing, or not a record this command wrote).`);
|
|
47871
|
+
process.exit(1);
|
|
47872
|
+
}
|
|
47873
|
+
if (record3.status === "reverted") {
|
|
47874
|
+
printInfo(`${record3.id} is already reverted.`);
|
|
47875
|
+
process.exit(0);
|
|
47876
|
+
}
|
|
47877
|
+
const api2 = await resolveApi2(program2);
|
|
47878
|
+
const results = await revert(api2, record3);
|
|
47879
|
+
const failed2 = results.filter((r) => !r.ok);
|
|
47880
|
+
if (failed2.length === 0) {
|
|
47881
|
+
record3.status = "reverted";
|
|
47882
|
+
record3.reverted_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
47883
|
+
try {
|
|
47884
|
+
await saveRecord(record3);
|
|
47885
|
+
} catch (err2) {
|
|
47886
|
+
printWarn(`Reverted, but the record could not be marked as reverted: ${err2.message}.`);
|
|
47887
|
+
}
|
|
47888
|
+
}
|
|
47889
|
+
if (opts.json) {
|
|
47890
|
+
console.log(JSON.stringify({ id: record3.id, status: record3.status, results }, null, 2));
|
|
47891
|
+
process.exit(failed2.length ? 1 : 0);
|
|
47892
|
+
}
|
|
47893
|
+
if (failed2.length === 0) printInfo(`Reverted ${record3.id}: ${results.length} node(s) back as they were.`);
|
|
47894
|
+
else {
|
|
47895
|
+
printError(`Reverted ${results.length - failed2.length} of ${results.length}. Not reverted:`);
|
|
47896
|
+
for (const f of failed2) printWarn(` ${f.nodeId}: ${f.error}`);
|
|
47897
|
+
printWarn(` Run the same command again to retry.`);
|
|
47898
|
+
}
|
|
47899
|
+
process.exit(failed2.length ? 1 : 0);
|
|
47900
|
+
}
|
|
47901
|
+
const nodes = await currentNodes();
|
|
47902
|
+
if (nodes.length === 0) {
|
|
47903
|
+
console.log("No active nodes in .verity/memory/. `verity memory pull` fetches the graph.");
|
|
47904
|
+
process.exit(0);
|
|
47905
|
+
}
|
|
47906
|
+
const plan = planDream(nodes);
|
|
47907
|
+
if (!opts.apply) {
|
|
47908
|
+
if (opts.json) console.log(JSON.stringify({ applied: false, ...plan }, null, 2));
|
|
47909
|
+
else {
|
|
47910
|
+
console.log(renderPlan(plan, "Dream plan (nothing changed yet)"));
|
|
47911
|
+
if (plan.changes.length > 0) console.log("Apply it: verity memory dream --apply");
|
|
47912
|
+
}
|
|
47913
|
+
process.exit(0);
|
|
47914
|
+
}
|
|
47915
|
+
if (plan.changes.length === 0) {
|
|
47916
|
+
if (opts.json) console.log(JSON.stringify({ applied: false, ...plan }, null, 2));
|
|
47917
|
+
else console.log(renderPlan(plan, "Dream plan: nothing to change"));
|
|
47918
|
+
process.exit(0);
|
|
47919
|
+
}
|
|
47920
|
+
const api = await resolveApi2(program2);
|
|
47921
|
+
let record2;
|
|
47922
|
+
try {
|
|
47923
|
+
record2 = await applyDreamPlan(api, plan);
|
|
47924
|
+
} catch (err2) {
|
|
47925
|
+
printError(`Could not save the dream record: ${err2.message}. Changes stop here; the record on disk says what was done.`);
|
|
47926
|
+
process.exit(1);
|
|
47927
|
+
}
|
|
47928
|
+
const failed = record2.results.filter((r) => !r.ok);
|
|
47929
|
+
if (opts.json) {
|
|
47930
|
+
console.log(JSON.stringify({ applied: true, id: record2.id, status: record2.status, report: `.verity/dreams/${record2.id}.md`, results: record2.results, review: plan.review }, null, 2));
|
|
47931
|
+
process.exit(failed.length ? 1 : 0);
|
|
47932
|
+
}
|
|
47933
|
+
console.log(renderPlan(plan, `${record2.id} (applied)`));
|
|
47934
|
+
if (failed.length > 0) {
|
|
47935
|
+
printWarn(`${failed.length} change(s) failed:`);
|
|
47936
|
+
for (const f of failed) printWarn(` ${f.type} ${f.nodeId}: ${f.error}`);
|
|
47937
|
+
}
|
|
47938
|
+
printInfo(`Report: .verity/dreams/${record2.id}.md`);
|
|
47939
|
+
printInfo(`Undo: verity memory dream --revert ${record2.id}`);
|
|
47940
|
+
process.exit(failed.length ? 1 : 0);
|
|
47941
|
+
});
|
|
47942
|
+
}
|
|
47943
|
+
|
|
45920
47944
|
// src/commands/memory.ts
|
|
45921
47945
|
function registerMemoryCommand(program2) {
|
|
45922
47946
|
const memory2 = program2.command("memory").description("Manage the project knowledge base");
|
|
47947
|
+
registerMemoryCurationCommands(memory2, program2);
|
|
47948
|
+
registerMemoryAskCommand(memory2);
|
|
47949
|
+
registerMemoryDreamCommand(memory2, program2);
|
|
45923
47950
|
memory2.command("seed").description("Seed .verity/memory/ from what /verity-setup already extracted (knowledge_spec, README, CLAUDE.md)").option("--force", "Re-seed even if the memory dir already has seeded nodes").option("--dry-run", "Print the candidate nodes without writing or syncing").action(async (opts) => {
|
|
45924
47951
|
const globals = program2.opts();
|
|
45925
47952
|
const tokenResult = await resolveToken(globals.token);
|
|
@@ -46184,7 +48211,7 @@ function registerRunCommand(program2) {
|
|
|
46184
48211
|
}
|
|
46185
48212
|
|
|
46186
48213
|
// src/commands/telemetry.ts
|
|
46187
|
-
var
|
|
48214
|
+
var import_node_fs88 = require("node:fs");
|
|
46188
48215
|
var SCOPE = "every Claude Code session on this machine";
|
|
46189
48216
|
function declineHeaders(why) {
|
|
46190
48217
|
process.stderr.write(`Verity telemetry is off here: ${why} Nothing is exported from this session.
|
|
@@ -46192,7 +48219,7 @@ function declineHeaders(why) {
|
|
|
46192
48219
|
process.exitCode = 1;
|
|
46193
48220
|
}
|
|
46194
48221
|
async function recordTelemetryChoice(choice) {
|
|
46195
|
-
if (!(0,
|
|
48222
|
+
if (!(0, import_node_fs88.existsSync)(projectPath(SETUP_STATE_FILE))) return;
|
|
46196
48223
|
try {
|
|
46197
48224
|
if ((await readSetupState())?.telemetry === choice) return;
|
|
46198
48225
|
await writeSetupState({ telemetry: choice });
|
|
@@ -46300,12 +48327,12 @@ function registerTelemetryCommands(program2) {
|
|
|
46300
48327
|
}
|
|
46301
48328
|
|
|
46302
48329
|
// src/commands/prove.ts
|
|
46303
|
-
var
|
|
46304
|
-
var
|
|
48330
|
+
var import_node_fs90 = require("node:fs");
|
|
48331
|
+
var import_node_path75 = require("node:path");
|
|
46305
48332
|
|
|
46306
48333
|
// src/lib/prove/on-demand-run.ts
|
|
46307
|
-
var
|
|
46308
|
-
var
|
|
48334
|
+
var import_node_fs89 = require("node:fs");
|
|
48335
|
+
var import_node_path74 = require("node:path");
|
|
46309
48336
|
var ON_DEMAND_RUN = `${PROOFS_DIR}/.results/.formal-run.json`;
|
|
46310
48337
|
var ON_DEMAND_RUN_REUSE_MS = 18 * 6e4;
|
|
46311
48338
|
function isLocalState2(root) {
|
|
@@ -46327,7 +48354,7 @@ function forgetOnDemandRun(root) {
|
|
|
46327
48354
|
setFormalRun(null);
|
|
46328
48355
|
if (!isLocalState2(root)) return;
|
|
46329
48356
|
try {
|
|
46330
|
-
(0,
|
|
48357
|
+
(0, import_node_fs89.unlinkSync)((0, import_node_path74.join)(root, ON_DEMAND_RUN));
|
|
46331
48358
|
} catch {
|
|
46332
48359
|
}
|
|
46333
48360
|
}
|
|
@@ -46504,7 +48531,7 @@ function registerProveCommand(program2) {
|
|
|
46504
48531
|
process.exit(2);
|
|
46505
48532
|
}
|
|
46506
48533
|
const limit = Math.max(1, Math.min(50, Number(opts.limit) || 10));
|
|
46507
|
-
let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0,
|
|
48534
|
+
let signals = collectRiskSignals(root, { since: opts.since, limit, paths: paths.map((p) => (0, import_node_path75.relative)(root, (0, import_node_path75.resolve)(p)) || ".") });
|
|
46508
48535
|
if (opts.hint && paths.length === 0) {
|
|
46509
48536
|
const hit = hintMatches(root, opts.hint);
|
|
46510
48537
|
if (hit.files.length > 0) {
|
|
@@ -46558,7 +48585,7 @@ function registerProveCommand(program2) {
|
|
|
46558
48585
|
});
|
|
46559
48586
|
prove.command("check [model]").description("Elaborate a model (or every model) with the local Lean toolchain and report each property").option("--timeout <ms>", "Per-model timeout in ms", "120000").option("--json", "Machine-readable output").action((model, opts) => {
|
|
46560
48587
|
const root = repoRoot();
|
|
46561
|
-
if (!(0,
|
|
48588
|
+
if (!(0, import_node_fs90.existsSync)((0, import_node_path75.join)(root, PROOFS_DIR, "lakefile.toml"))) {
|
|
46562
48589
|
printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
|
|
46563
48590
|
process.exit(2);
|
|
46564
48591
|
}
|
|
@@ -46582,7 +48609,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
|
|
|
46582
48609
|
const reports = [];
|
|
46583
48610
|
for (const f of files) {
|
|
46584
48611
|
const report = checkModel(tc, root, f, { timeoutMs: Math.max(1e3, Number(opts.timeout) || 12e4) });
|
|
46585
|
-
report.file = (0,
|
|
48612
|
+
report.file = (0, import_node_path75.relative)(root, f);
|
|
46586
48613
|
reports.push(report);
|
|
46587
48614
|
try {
|
|
46588
48615
|
writeResult(root, report);
|
|
@@ -46620,7 +48647,7 @@ ${build.stderr.split("\n").filter((l) => !l.startsWith("trace:")).slice(0, 20).j
|
|
|
46620
48647
|
printError("Not logged in \u2014 replays run on the Verity service. Run `verity login` first.");
|
|
46621
48648
|
process.exit(2);
|
|
46622
48649
|
}
|
|
46623
|
-
if (!(0,
|
|
48650
|
+
if (!(0, import_node_fs90.existsSync)((0, import_node_path75.join)(root, PROOFS_DIR, "lakefile.toml"))) {
|
|
46624
48651
|
printError(`No ${PROOFS_DIR}/ project here. Run \`verity prove init\` first.`);
|
|
46625
48652
|
process.exit(2);
|
|
46626
48653
|
}
|
|
@@ -46659,7 +48686,7 @@ ${report.error ?? ""}`);
|
|
|
46659
48686
|
process.exit(2);
|
|
46660
48687
|
}
|
|
46661
48688
|
}
|
|
46662
|
-
const modelSource = readRegularFileInRoot(root, (0,
|
|
48689
|
+
const modelSource = readRegularFileInRoot(root, (0, import_node_path75.relative)(root, file), 512 * 1024);
|
|
46663
48690
|
if (!opts.json) printInfo(`replaying ${targets.length} counterexample(s) of ${model} in network-less containers (${Math.min(targets.length, REPLAY_CONCURRENCY)} at a time)\u2026`);
|
|
46664
48691
|
const outcomes = await mapPool(targets, REPLAY_CONCURRENCY, async (p) => {
|
|
46665
48692
|
const brief = buildReplayBrief(root, report, p, modelSource.ok ? modelSource.content : "");
|
|
@@ -46680,7 +48707,7 @@ ${report.error ?? ""}`);
|
|
|
46680
48707
|
});
|
|
46681
48708
|
prove.command("auto [files...]").description("The formal phase the pre-push guard runs: select one risky symbol among changed files, generate a model, check it, replay counterexamples (default files: the commits ahead of upstream)").option("--deadline <ms>", "Budget for the whole phase", "170000").option("--json", "Machine-readable output").action(async (paths, opts) => {
|
|
46682
48709
|
const root = repoRoot();
|
|
46683
|
-
let files = paths.map((p) => (0,
|
|
48710
|
+
let files = paths.map((p) => (0, import_node_path75.relative)(root, (0, import_node_path75.resolve)(p)));
|
|
46684
48711
|
if (files.length === 0) {
|
|
46685
48712
|
const ahead = runGitSync(root, ["diff", "--name-only", "@{upstream}...HEAD"]);
|
|
46686
48713
|
const fallback = ahead.ok ? ahead : runGitSync(root, ["diff", "--name-only", "HEAD~1..HEAD"]);
|
|
@@ -46754,7 +48781,7 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
|
|
|
46754
48781
|
let revise;
|
|
46755
48782
|
if (opts.revise) {
|
|
46756
48783
|
const existing = resolveModelFile(root, opts.revise);
|
|
46757
|
-
const src = existing ? readRegularFileInRoot(root, (0,
|
|
48784
|
+
const src = existing ? readRegularFileInRoot(root, (0, import_node_path75.relative)(root, existing), 64 * 1024) : null;
|
|
46758
48785
|
if (!src || !src.ok || !opts.note) {
|
|
46759
48786
|
printError("--revise needs an existing model under Models/ and --note - with what the replay showed on stdin");
|
|
46760
48787
|
process.exit(2);
|
|
@@ -46803,8 +48830,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
|
|
|
46803
48830
|
});
|
|
46804
48831
|
prove.command("waive <file>").description("Dismiss the remembered formal finding(s) on a file for its current bytes \u2014 the accepted-risk path for a formal block").requiredOption("--reason <text>", "The human disposition this records (reviewer finding, ADR, \u2026)").action((file, opts) => {
|
|
46805
48832
|
const root = repoRoot();
|
|
46806
|
-
const rel0 = (0,
|
|
46807
|
-
const rel = rel0.startsWith("..") || (0,
|
|
48833
|
+
const rel0 = (0, import_node_path75.relative)(root, (0, import_node_path75.isAbsolute)(file) ? file : (0, import_node_path75.join)(root, file)).replace(/\\/g, "/");
|
|
48834
|
+
const rel = rel0.startsWith("..") || (0, import_node_path75.isAbsolute)(rel0) ? null : rel0;
|
|
46808
48835
|
if (!rel) {
|
|
46809
48836
|
printError(`${file} is not inside this repository`);
|
|
46810
48837
|
process.exit(2);
|
|
@@ -46820,8 +48847,8 @@ ${f.reason ? ` ${terminalText(f.reason, 2e3)}
|
|
|
46820
48847
|
});
|
|
46821
48848
|
prove.command("hash <file>").description("Print the sha256 of a file, for a model header (Verity.model \u2026 sha256). Path is REPO-relative, as in the header").action((file) => {
|
|
46822
48849
|
const root = repoRoot();
|
|
46823
|
-
const rel = (0,
|
|
46824
|
-
const digest = rel.startsWith("..") || (0,
|
|
48850
|
+
const rel = (0, import_node_path75.relative)(root, (0, import_node_path75.isAbsolute)(file) ? file : (0, import_node_path75.join)(root, file));
|
|
48851
|
+
const digest = rel.startsWith("..") || (0, import_node_path75.isAbsolute)(rel) ? null : sha256InRoot(root, rel);
|
|
46825
48852
|
if (!digest) {
|
|
46826
48853
|
printError(`Cannot hash ${file}: it must be a regular file inside this repository (not a symlink)`);
|
|
46827
48854
|
process.exit(2);
|
|
@@ -46855,10 +48882,9 @@ function refreshScaffold(root) {
|
|
|
46855
48882
|
}
|
|
46856
48883
|
|
|
46857
48884
|
// src/commands/lean.ts
|
|
46858
|
-
var
|
|
46859
|
-
var import_node_child_process24 = require("node:child_process");
|
|
48885
|
+
var import_node_fs91 = require("node:fs");
|
|
46860
48886
|
var import_node_os16 = require("node:os");
|
|
46861
|
-
var
|
|
48887
|
+
var import_node_path76 = require("node:path");
|
|
46862
48888
|
function registerLeanCommand(program2) {
|
|
46863
48889
|
const lean = program2.command("lean").description("The Lean 4 toolchain formal verification needs (managed download, pinned by this CLI)");
|
|
46864
48890
|
lean.command("status").option("--json", "Machine-readable output").description("Which toolchain would run, and whether it matches the pinned version").action((opts) => {
|
|
@@ -46866,7 +48892,7 @@ function registerLeanCommand(program2) {
|
|
|
46866
48892
|
let version = null;
|
|
46867
48893
|
if (tc) {
|
|
46868
48894
|
try {
|
|
46869
|
-
version = (
|
|
48895
|
+
version = execFileSync2(tc.lake.replace(/lake(\.exe)?$/, "lean$1"), ["--version"], { env: tc.env, encoding: "utf-8", timeout: 1e4 }).trim();
|
|
46870
48896
|
} catch {
|
|
46871
48897
|
version = null;
|
|
46872
48898
|
}
|
|
@@ -46877,7 +48903,7 @@ function registerLeanCommand(program2) {
|
|
|
46877
48903
|
const bg = readInstallState();
|
|
46878
48904
|
const background = bg ? { ...bg, running: bg.state === "running" && installRunning() } : null;
|
|
46879
48905
|
if (opts.json) {
|
|
46880
|
-
printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0,
|
|
48906
|
+
printJson({ pinned: LEAN_TOOLCHAIN, found: tc?.lake ?? null, version, matches_pin: matches, managed_dir: managed, managed_installed: (0, import_node_fs91.existsSync)((0, import_node_path76.join)(managed, "bin")), asset: leanAssetName(), background });
|
|
46881
48907
|
return;
|
|
46882
48908
|
}
|
|
46883
48909
|
printInfo(`pinned: ${LEAN_TOOLCHAIN}`);
|
|
@@ -46898,7 +48924,7 @@ function registerLeanCommand(program2) {
|
|
|
46898
48924
|
process.exit(2);
|
|
46899
48925
|
}
|
|
46900
48926
|
const dir = pinnedVersionDir();
|
|
46901
|
-
if ((0,
|
|
48927
|
+
if ((0, import_node_fs91.existsSync)((0, import_node_path76.join)(dir, "bin", "lake")) && !opts.force) {
|
|
46902
48928
|
printInfo(`already installed at ${dir}`);
|
|
46903
48929
|
return;
|
|
46904
48930
|
}
|
|
@@ -46934,20 +48960,20 @@ function registerLeanCommand(program2) {
|
|
|
46934
48960
|
});
|
|
46935
48961
|
lean.command("remove").description("Delete the Verity-managed toolchain for the pinned version").action(() => {
|
|
46936
48962
|
const dir = pinnedVersionDir();
|
|
46937
|
-
if (!(0,
|
|
48963
|
+
if (!(0, import_node_fs91.existsSync)(dir)) {
|
|
46938
48964
|
printInfo("nothing to remove");
|
|
46939
48965
|
return;
|
|
46940
48966
|
}
|
|
46941
|
-
(0,
|
|
48967
|
+
(0, import_node_fs91.rmSync)(dir, { recursive: true, force: true });
|
|
46942
48968
|
printInfo(`removed ${dir}`);
|
|
46943
48969
|
});
|
|
46944
48970
|
}
|
|
46945
48971
|
|
|
46946
48972
|
// src/cli.ts
|
|
46947
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
48973
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.36.0").option("--token <token>", "Override authentication token").option("--service-url <url>", "Override service URL").option("--verbose", "Log HTTP requests/responses to stderr").hook("preAction", async (_thisCommand, actionCommand) => {
|
|
46948
48974
|
const isTelemetryHelper = actionCommand.name() === "headers" && actionCommand.parent?.name() === "telemetry";
|
|
46949
48975
|
if (!isTelemetryHelper) {
|
|
46950
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.
|
|
48976
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.36.0");
|
|
46951
48977
|
}
|
|
46952
48978
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
46953
48979
|
try {
|