@codacy/verity-cli 0.32.7 → 0.33.0-experimental.34db3a4
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 +42 -15
- package/bin/verity.js +2154 -628
- package/data/skills/verity-reflect/SKILL.md +55 -31
- package/package.json +2 -2
package/bin/verity.js
CHANGED
|
@@ -6975,10 +6975,10 @@ var require_resolve_block_map = __commonJS({
|
|
|
6975
6975
|
let offset = bm.offset;
|
|
6976
6976
|
let commentEnd = null;
|
|
6977
6977
|
for (const collItem of bm.items) {
|
|
6978
|
-
const { start, key, sep:
|
|
6978
|
+
const { start, key, sep: sep4, value } = collItem;
|
|
6979
6979
|
const keyProps = resolveProps.resolveProps(start, {
|
|
6980
6980
|
indicator: "explicit-key-ind",
|
|
6981
|
-
next: key ??
|
|
6981
|
+
next: key ?? sep4?.[0],
|
|
6982
6982
|
offset,
|
|
6983
6983
|
onError,
|
|
6984
6984
|
parentIndent: bm.indent,
|
|
@@ -6992,7 +6992,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
6992
6992
|
else if ("indent" in key && key.indent !== bm.indent)
|
|
6993
6993
|
onError(offset, "BAD_INDENT", startColMsg);
|
|
6994
6994
|
}
|
|
6995
|
-
if (!keyProps.anchor && !keyProps.tag && !
|
|
6995
|
+
if (!keyProps.anchor && !keyProps.tag && !sep4) {
|
|
6996
6996
|
commentEnd = keyProps.end;
|
|
6997
6997
|
if (keyProps.comment) {
|
|
6998
6998
|
if (map.comment)
|
|
@@ -7016,7 +7016,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
7016
7016
|
ctx.atKey = false;
|
|
7017
7017
|
if (utilMapIncludes.mapIncludes(ctx, map.items, keyNode))
|
|
7018
7018
|
onError(keyStart, "DUPLICATE_KEY", "Map keys must be unique");
|
|
7019
|
-
const valueProps = resolveProps.resolveProps(
|
|
7019
|
+
const valueProps = resolveProps.resolveProps(sep4 ?? [], {
|
|
7020
7020
|
indicator: "map-value-ind",
|
|
7021
7021
|
next: value,
|
|
7022
7022
|
offset: keyNode.range[2],
|
|
@@ -7032,7 +7032,7 @@ var require_resolve_block_map = __commonJS({
|
|
|
7032
7032
|
if (ctx.options.strict && keyProps.start < valueProps.found.offset - 1024)
|
|
7033
7033
|
onError(keyNode.range, "KEY_OVER_1024_CHARS", "The : indicator must be at most 1024 chars after the start of an implicit block mapping key");
|
|
7034
7034
|
}
|
|
7035
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset,
|
|
7035
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : composeEmptyNode(ctx, offset, sep4, null, valueProps, onError);
|
|
7036
7036
|
if (ctx.schema.compat)
|
|
7037
7037
|
utilFlowIndentCheck.flowIndentCheck(bm.indent, value, onError);
|
|
7038
7038
|
offset = valueNode.range[2];
|
|
@@ -7123,7 +7123,7 @@ var require_resolve_end = __commonJS({
|
|
|
7123
7123
|
let comment = "";
|
|
7124
7124
|
if (end) {
|
|
7125
7125
|
let hasSpace = false;
|
|
7126
|
-
let
|
|
7126
|
+
let sep4 = "";
|
|
7127
7127
|
for (const token of end) {
|
|
7128
7128
|
const { source, type } = token;
|
|
7129
7129
|
switch (type) {
|
|
@@ -7137,13 +7137,13 @@ var require_resolve_end = __commonJS({
|
|
|
7137
7137
|
if (!comment)
|
|
7138
7138
|
comment = cb;
|
|
7139
7139
|
else
|
|
7140
|
-
comment +=
|
|
7141
|
-
|
|
7140
|
+
comment += sep4 + cb;
|
|
7141
|
+
sep4 = "";
|
|
7142
7142
|
break;
|
|
7143
7143
|
}
|
|
7144
7144
|
case "newline":
|
|
7145
7145
|
if (comment)
|
|
7146
|
-
|
|
7146
|
+
sep4 += source;
|
|
7147
7147
|
hasSpace = true;
|
|
7148
7148
|
break;
|
|
7149
7149
|
default:
|
|
@@ -7186,18 +7186,18 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
7186
7186
|
let offset = fc.offset + fc.start.source.length;
|
|
7187
7187
|
for (let i = 0; i < fc.items.length; ++i) {
|
|
7188
7188
|
const collItem = fc.items[i];
|
|
7189
|
-
const { start, key, sep:
|
|
7189
|
+
const { start, key, sep: sep4, value } = collItem;
|
|
7190
7190
|
const props = resolveProps.resolveProps(start, {
|
|
7191
7191
|
flow: fcName,
|
|
7192
7192
|
indicator: "explicit-key-ind",
|
|
7193
|
-
next: key ??
|
|
7193
|
+
next: key ?? sep4?.[0],
|
|
7194
7194
|
offset,
|
|
7195
7195
|
onError,
|
|
7196
7196
|
parentIndent: fc.indent,
|
|
7197
7197
|
startOnNewline: false
|
|
7198
7198
|
});
|
|
7199
7199
|
if (!props.found) {
|
|
7200
|
-
if (!props.anchor && !props.tag && !
|
|
7200
|
+
if (!props.anchor && !props.tag && !sep4 && !value) {
|
|
7201
7201
|
if (i === 0 && props.comma)
|
|
7202
7202
|
onError(props.comma, "UNEXPECTED_TOKEN", `Unexpected , in ${fcName}`);
|
|
7203
7203
|
else if (i < fc.items.length - 1)
|
|
@@ -7251,8 +7251,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
7251
7251
|
}
|
|
7252
7252
|
}
|
|
7253
7253
|
}
|
|
7254
|
-
if (!isMap && !
|
|
7255
|
-
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end,
|
|
7254
|
+
if (!isMap && !sep4 && !props.found) {
|
|
7255
|
+
const valueNode = value ? composeNode(ctx, value, props, onError) : composeEmptyNode(ctx, props.end, sep4, null, props, onError);
|
|
7256
7256
|
coll.items.push(valueNode);
|
|
7257
7257
|
offset = valueNode.range[2];
|
|
7258
7258
|
if (isBlock(value))
|
|
@@ -7264,7 +7264,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
7264
7264
|
if (isBlock(key))
|
|
7265
7265
|
onError(keyNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
7266
7266
|
ctx.atKey = false;
|
|
7267
|
-
const valueProps = resolveProps.resolveProps(
|
|
7267
|
+
const valueProps = resolveProps.resolveProps(sep4 ?? [], {
|
|
7268
7268
|
flow: fcName,
|
|
7269
7269
|
indicator: "map-value-ind",
|
|
7270
7270
|
next: value,
|
|
@@ -7275,8 +7275,8 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
7275
7275
|
});
|
|
7276
7276
|
if (valueProps.found) {
|
|
7277
7277
|
if (!isMap && !props.found && ctx.options.strict) {
|
|
7278
|
-
if (
|
|
7279
|
-
for (const st of
|
|
7278
|
+
if (sep4)
|
|
7279
|
+
for (const st of sep4) {
|
|
7280
7280
|
if (st === valueProps.found)
|
|
7281
7281
|
break;
|
|
7282
7282
|
if (st.type === "newline") {
|
|
@@ -7293,7 +7293,7 @@ var require_resolve_flow_collection = __commonJS({
|
|
|
7293
7293
|
else
|
|
7294
7294
|
onError(valueProps.start, "MISSING_CHAR", `Missing , or : between ${fcName} items`);
|
|
7295
7295
|
}
|
|
7296
|
-
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end,
|
|
7296
|
+
const valueNode = value ? composeNode(ctx, value, valueProps, onError) : valueProps.found ? composeEmptyNode(ctx, valueProps.end, sep4, null, valueProps, onError) : null;
|
|
7297
7297
|
if (valueNode) {
|
|
7298
7298
|
if (isBlock(value))
|
|
7299
7299
|
onError(valueNode.range, "BLOCK_IN_FLOW", blockMsg);
|
|
@@ -7473,7 +7473,7 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
7473
7473
|
chompStart = i + 1;
|
|
7474
7474
|
}
|
|
7475
7475
|
let value = "";
|
|
7476
|
-
let
|
|
7476
|
+
let sep4 = "";
|
|
7477
7477
|
let prevMoreIndented = false;
|
|
7478
7478
|
for (let i = 0; i < contentStart; ++i)
|
|
7479
7479
|
value += lines[i][0].slice(trimIndent) + "\n";
|
|
@@ -7490,24 +7490,24 @@ var require_resolve_block_scalar = __commonJS({
|
|
|
7490
7490
|
indent = "";
|
|
7491
7491
|
}
|
|
7492
7492
|
if (type === Scalar.Scalar.BLOCK_LITERAL) {
|
|
7493
|
-
value +=
|
|
7494
|
-
|
|
7493
|
+
value += sep4 + indent.slice(trimIndent) + content;
|
|
7494
|
+
sep4 = "\n";
|
|
7495
7495
|
} else if (indent.length > trimIndent || content[0] === " ") {
|
|
7496
|
-
if (
|
|
7497
|
-
|
|
7498
|
-
else if (!prevMoreIndented &&
|
|
7499
|
-
|
|
7500
|
-
value +=
|
|
7501
|
-
|
|
7496
|
+
if (sep4 === " ")
|
|
7497
|
+
sep4 = "\n";
|
|
7498
|
+
else if (!prevMoreIndented && sep4 === "\n")
|
|
7499
|
+
sep4 = "\n\n";
|
|
7500
|
+
value += sep4 + indent.slice(trimIndent) + content;
|
|
7501
|
+
sep4 = "\n";
|
|
7502
7502
|
prevMoreIndented = true;
|
|
7503
7503
|
} else if (content === "") {
|
|
7504
|
-
if (
|
|
7504
|
+
if (sep4 === "\n")
|
|
7505
7505
|
value += "\n";
|
|
7506
7506
|
else
|
|
7507
|
-
|
|
7507
|
+
sep4 = "\n";
|
|
7508
7508
|
} else {
|
|
7509
|
-
value +=
|
|
7510
|
-
|
|
7509
|
+
value += sep4 + content;
|
|
7510
|
+
sep4 = " ";
|
|
7511
7511
|
prevMoreIndented = false;
|
|
7512
7512
|
}
|
|
7513
7513
|
}
|
|
@@ -7689,25 +7689,25 @@ var require_resolve_flow_scalar = __commonJS({
|
|
|
7689
7689
|
if (!match)
|
|
7690
7690
|
return source;
|
|
7691
7691
|
let res = match[1];
|
|
7692
|
-
let
|
|
7692
|
+
let sep4 = " ";
|
|
7693
7693
|
let pos = first.lastIndex;
|
|
7694
7694
|
line.lastIndex = pos;
|
|
7695
7695
|
while (match = line.exec(source)) {
|
|
7696
7696
|
if (match[1] === "") {
|
|
7697
|
-
if (
|
|
7698
|
-
res +=
|
|
7697
|
+
if (sep4 === "\n")
|
|
7698
|
+
res += sep4;
|
|
7699
7699
|
else
|
|
7700
|
-
|
|
7700
|
+
sep4 = "\n";
|
|
7701
7701
|
} else {
|
|
7702
|
-
res +=
|
|
7703
|
-
|
|
7702
|
+
res += sep4 + match[1];
|
|
7703
|
+
sep4 = " ";
|
|
7704
7704
|
}
|
|
7705
7705
|
pos = line.lastIndex;
|
|
7706
7706
|
}
|
|
7707
7707
|
const last = /[ \t]*(.*)/sy;
|
|
7708
7708
|
last.lastIndex = pos;
|
|
7709
7709
|
match = last.exec(source);
|
|
7710
|
-
return res +
|
|
7710
|
+
return res + sep4 + (match?.[1] ?? "");
|
|
7711
7711
|
}
|
|
7712
7712
|
function doubleQuotedValue(source, onError) {
|
|
7713
7713
|
let res = "";
|
|
@@ -8514,14 +8514,14 @@ var require_cst_stringify = __commonJS({
|
|
|
8514
8514
|
}
|
|
8515
8515
|
}
|
|
8516
8516
|
}
|
|
8517
|
-
function stringifyItem({ start, key, sep:
|
|
8517
|
+
function stringifyItem({ start, key, sep: sep4, value }) {
|
|
8518
8518
|
let res = "";
|
|
8519
8519
|
for (const st of start)
|
|
8520
8520
|
res += st.source;
|
|
8521
8521
|
if (key)
|
|
8522
8522
|
res += stringifyToken(key);
|
|
8523
|
-
if (
|
|
8524
|
-
for (const st of
|
|
8523
|
+
if (sep4)
|
|
8524
|
+
for (const st of sep4)
|
|
8525
8525
|
res += st.source;
|
|
8526
8526
|
if (value)
|
|
8527
8527
|
res += stringifyToken(value);
|
|
@@ -9671,18 +9671,18 @@ var require_parser = __commonJS({
|
|
|
9671
9671
|
if (this.type === "map-value-ind") {
|
|
9672
9672
|
const prev = getPrevProps(this.peek(2));
|
|
9673
9673
|
const start = getFirstKeyStartProps(prev);
|
|
9674
|
-
let
|
|
9674
|
+
let sep4;
|
|
9675
9675
|
if (scalar.end) {
|
|
9676
|
-
|
|
9677
|
-
|
|
9676
|
+
sep4 = scalar.end;
|
|
9677
|
+
sep4.push(this.sourceToken);
|
|
9678
9678
|
delete scalar.end;
|
|
9679
9679
|
} else
|
|
9680
|
-
|
|
9680
|
+
sep4 = [this.sourceToken];
|
|
9681
9681
|
const map = {
|
|
9682
9682
|
type: "block-map",
|
|
9683
9683
|
offset: scalar.offset,
|
|
9684
9684
|
indent: scalar.indent,
|
|
9685
|
-
items: [{ start, key: scalar, sep:
|
|
9685
|
+
items: [{ start, key: scalar, sep: sep4 }]
|
|
9686
9686
|
};
|
|
9687
9687
|
this.onKeyLine = true;
|
|
9688
9688
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -9835,15 +9835,15 @@ var require_parser = __commonJS({
|
|
|
9835
9835
|
} else if (isFlowToken(it.key) && !includesToken(it.sep, "newline")) {
|
|
9836
9836
|
const start2 = getFirstKeyStartProps(it.start);
|
|
9837
9837
|
const key = it.key;
|
|
9838
|
-
const
|
|
9839
|
-
|
|
9838
|
+
const sep4 = it.sep;
|
|
9839
|
+
sep4.push(this.sourceToken);
|
|
9840
9840
|
delete it.key;
|
|
9841
9841
|
delete it.sep;
|
|
9842
9842
|
this.stack.push({
|
|
9843
9843
|
type: "block-map",
|
|
9844
9844
|
offset: this.offset,
|
|
9845
9845
|
indent: this.indent,
|
|
9846
|
-
items: [{ start: start2, key, sep:
|
|
9846
|
+
items: [{ start: start2, key, sep: sep4 }]
|
|
9847
9847
|
});
|
|
9848
9848
|
} else if (start.length > 0) {
|
|
9849
9849
|
it.sep = it.sep.concat(start, this.sourceToken);
|
|
@@ -10037,13 +10037,13 @@ var require_parser = __commonJS({
|
|
|
10037
10037
|
const prev = getPrevProps(parent);
|
|
10038
10038
|
const start = getFirstKeyStartProps(prev);
|
|
10039
10039
|
fixFlowSeqItems(fc);
|
|
10040
|
-
const
|
|
10041
|
-
|
|
10040
|
+
const sep4 = fc.end.splice(1, fc.end.length);
|
|
10041
|
+
sep4.push(this.sourceToken);
|
|
10042
10042
|
const map = {
|
|
10043
10043
|
type: "block-map",
|
|
10044
10044
|
offset: fc.offset,
|
|
10045
10045
|
indent: fc.indent,
|
|
10046
|
-
items: [{ start, key: fc, sep:
|
|
10046
|
+
items: [{ start, key: fc, sep: sep4 }]
|
|
10047
10047
|
};
|
|
10048
10048
|
this.onKeyLine = true;
|
|
10049
10049
|
this.stack[this.stack.length - 1] = map;
|
|
@@ -10413,7 +10413,6 @@ var MAX_ITERATIONS = 2;
|
|
|
10413
10413
|
var MAX_SPEC_FILES = 6;
|
|
10414
10414
|
var MAX_SPEC_FILE_BYTES = 512e3;
|
|
10415
10415
|
var MAX_TOTAL_SPEC_BYTES = 512e3;
|
|
10416
|
-
var MAX_EXPLICIT_SPEC_FILE_BYTES = 10240;
|
|
10417
10416
|
var MAX_PLAN_FILES = 3;
|
|
10418
10417
|
var MAX_PLAN_FILE_BYTES = 512e3;
|
|
10419
10418
|
var MAX_INTENT_CHARS = 2e3;
|
|
@@ -10524,7 +10523,7 @@ var SECURITY_PATTERNS = [
|
|
|
10524
10523
|
/Dockerfile/
|
|
10525
10524
|
];
|
|
10526
10525
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10527
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10526
|
+
var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10528
10527
|
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10529
10528
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10530
10529
|
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
@@ -10970,8 +10969,8 @@ function filterReviewable(files) {
|
|
|
10970
10969
|
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
10971
10970
|
if (ANALYZABLE_EXTENSIONS.has(ext)) return false;
|
|
10972
10971
|
if (REVIEWABLE_EXTENSIONS.has(ext)) return true;
|
|
10973
|
-
const
|
|
10974
|
-
if (REVIEWABLE_FILENAMES.has(
|
|
10972
|
+
const basename4 = f.split("/").pop() ?? "";
|
|
10973
|
+
if (REVIEWABLE_FILENAMES.has(basename4)) return true;
|
|
10975
10974
|
if (REVIEWABLE_PATH_PATTERNS.some((p) => p.test(f))) return true;
|
|
10976
10975
|
return false;
|
|
10977
10976
|
});
|
|
@@ -11534,7 +11533,7 @@ function startSpinner(label2, opts = {}) {
|
|
|
11534
11533
|
}
|
|
11535
11534
|
|
|
11536
11535
|
// src/lib/provider-auth.ts
|
|
11537
|
-
var sleep = (ms) => new Promise((
|
|
11536
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
11538
11537
|
var form = (fields) => new URLSearchParams(fields).toString();
|
|
11539
11538
|
async function githubAccountId(owner) {
|
|
11540
11539
|
try {
|
|
@@ -12801,50 +12800,12 @@ function resolveGuardMoments(explicit) {
|
|
|
12801
12800
|
}
|
|
12802
12801
|
|
|
12803
12802
|
// src/lib/plugin-ownership.ts
|
|
12804
|
-
var
|
|
12805
|
-
|
|
12806
|
-
// src/lib/which.ts
|
|
12807
|
-
var import_node_fs5 = require("node:fs");
|
|
12808
|
-
var import_node_path7 = require("node:path");
|
|
12809
|
-
function executableExtensions(platform, pathext) {
|
|
12810
|
-
if (platform !== "win32") return [""];
|
|
12811
|
-
const raw = (pathext ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
12812
|
-
const out = [];
|
|
12813
|
-
for (const ext of raw) {
|
|
12814
|
-
const lower = ext.toLowerCase();
|
|
12815
|
-
if (!out.includes(lower)) out.push(lower);
|
|
12816
|
-
if (!out.includes(ext)) out.push(ext);
|
|
12817
|
-
}
|
|
12818
|
-
return out;
|
|
12819
|
-
}
|
|
12820
|
-
function whichSync(bin, opts = {}) {
|
|
12821
|
-
const platform = opts.platform ?? process.platform;
|
|
12822
|
-
const rawPath = opts.path ?? process.env.PATH ?? "";
|
|
12823
|
-
if (!rawPath) return null;
|
|
12824
|
-
const exts = executableExtensions(platform, opts.pathext ?? process.env.PATHEXT);
|
|
12825
|
-
const real = opts.real ?? true;
|
|
12826
|
-
for (const dir of rawPath.split(import_node_path7.delimiter)) {
|
|
12827
|
-
if (!dir) continue;
|
|
12828
|
-
for (const ext of exts) {
|
|
12829
|
-
const candidate = (0, import_node_path7.join)(dir, bin + ext);
|
|
12830
|
-
try {
|
|
12831
|
-
if (!(0, import_node_fs5.statSync)(candidate).isFile()) continue;
|
|
12832
|
-
if (platform !== "win32") (0, import_node_fs5.accessSync)(candidate, import_node_fs5.constants.X_OK);
|
|
12833
|
-
return real ? (0, import_node_fs5.realpathSync)(candidate) : candidate;
|
|
12834
|
-
} catch {
|
|
12835
|
-
continue;
|
|
12836
|
-
}
|
|
12837
|
-
}
|
|
12838
|
-
}
|
|
12839
|
-
return null;
|
|
12840
|
-
}
|
|
12841
|
-
|
|
12842
|
-
// src/lib/plugin-ownership.ts
|
|
12803
|
+
var import_node_fs6 = require("node:fs");
|
|
12843
12804
|
var import_node_os2 = require("node:os");
|
|
12844
|
-
var
|
|
12805
|
+
var import_node_path7 = require("node:path");
|
|
12845
12806
|
|
|
12846
12807
|
// src/lib/stderr-log.ts
|
|
12847
|
-
var
|
|
12808
|
+
var import_node_fs5 = require("node:fs");
|
|
12848
12809
|
var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
|
|
12849
12810
|
var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
12850
12811
|
function scrub(s) {
|
|
@@ -12857,9 +12818,9 @@ function append(text) {
|
|
|
12857
12818
|
try {
|
|
12858
12819
|
const dir = projectPath(DEBUG_LOG_DIR);
|
|
12859
12820
|
const file = projectPath(STDERR_LOG_FILE);
|
|
12860
|
-
(0,
|
|
12821
|
+
(0, import_node_fs5.mkdirSync)(dir, { recursive: true });
|
|
12861
12822
|
rotateIfNeeded(file);
|
|
12862
|
-
(0,
|
|
12823
|
+
(0, import_node_fs5.appendFileSync)(file, text);
|
|
12863
12824
|
} catch {
|
|
12864
12825
|
}
|
|
12865
12826
|
}
|
|
@@ -12907,8 +12868,8 @@ function markerPath() {
|
|
|
12907
12868
|
}
|
|
12908
12869
|
function readMarker() {
|
|
12909
12870
|
try {
|
|
12910
|
-
if (!(0,
|
|
12911
|
-
const raw = JSON.parse((0,
|
|
12871
|
+
if (!(0, import_node_fs6.existsSync)(markerPath())) return null;
|
|
12872
|
+
const raw = JSON.parse((0, import_node_fs6.readFileSync)(markerPath(), "utf-8"));
|
|
12912
12873
|
const pluginRoot = typeof raw.plugin_root === "string" ? raw.plugin_root : "";
|
|
12913
12874
|
if (!pluginRoot || CONTROL_CHARS.test(pluginRoot)) return null;
|
|
12914
12875
|
return {
|
|
@@ -12925,23 +12886,23 @@ function recordPluginOwnership(sessionId) {
|
|
|
12925
12886
|
const pluginRoot = process.env.VERITY_PLUGIN_ROOT;
|
|
12926
12887
|
if (!pluginRoot) return;
|
|
12927
12888
|
try {
|
|
12928
|
-
(0,
|
|
12889
|
+
(0, import_node_fs6.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
|
|
12929
12890
|
const marker = {
|
|
12930
12891
|
session_id: sessionId,
|
|
12931
12892
|
plugin_root: pluginRoot,
|
|
12932
12893
|
version: process.env.VERITY_PLUGIN_VERSION || null,
|
|
12933
12894
|
ts: Math.floor(Date.now() / 1e3)
|
|
12934
12895
|
};
|
|
12935
|
-
(0,
|
|
12896
|
+
(0, import_node_fs6.writeFileSync)(markerPath(), JSON.stringify(marker));
|
|
12936
12897
|
} catch {
|
|
12937
12898
|
}
|
|
12938
12899
|
}
|
|
12939
12900
|
function claudeConfigDir() {
|
|
12940
|
-
return process.env.CLAUDE_CONFIG_DIR || (0,
|
|
12901
|
+
return process.env.CLAUDE_CONFIG_DIR || (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".claude");
|
|
12941
12902
|
}
|
|
12942
12903
|
function readJsonFile(path) {
|
|
12943
12904
|
try {
|
|
12944
|
-
const parsed = JSON.parse((0,
|
|
12905
|
+
const parsed = JSON.parse((0, import_node_fs6.readFileSync)(path, "utf-8"));
|
|
12945
12906
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
12946
12907
|
} catch {
|
|
12947
12908
|
return null;
|
|
@@ -12949,9 +12910,9 @@ function readJsonFile(path) {
|
|
|
12949
12910
|
}
|
|
12950
12911
|
function enabledPluginSetting(key) {
|
|
12951
12912
|
const files = [
|
|
12952
|
-
projectPath((0,
|
|
12953
|
-
projectPath((0,
|
|
12954
|
-
(0,
|
|
12913
|
+
projectPath((0, import_node_path7.join)(".claude", "settings.local.json")),
|
|
12914
|
+
projectPath((0, import_node_path7.join)(".claude", "settings.json")),
|
|
12915
|
+
(0, import_node_path7.join)(claudeConfigDir(), "settings.json")
|
|
12955
12916
|
];
|
|
12956
12917
|
for (const file of files) {
|
|
12957
12918
|
const map = readJsonFile(file)?.enabledPlugins;
|
|
@@ -12963,16 +12924,27 @@ function enabledPluginSetting(key) {
|
|
|
12963
12924
|
}
|
|
12964
12925
|
function marketplaceLocations() {
|
|
12965
12926
|
const out = /* @__PURE__ */ new Map();
|
|
12966
|
-
const known = readJsonFile((0,
|
|
12927
|
+
const known = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
|
|
12967
12928
|
if (!known) return out;
|
|
12968
12929
|
for (const [name, entry] of Object.entries(known)) {
|
|
12969
12930
|
const loc2 = entry?.installLocation;
|
|
12970
|
-
if (typeof loc2 === "string" && loc2) out.set(name, (0,
|
|
12931
|
+
if (typeof loc2 === "string" && loc2) out.set(name, (0, import_node_path7.resolve)(loc2));
|
|
12971
12932
|
}
|
|
12972
12933
|
return out;
|
|
12973
12934
|
}
|
|
12974
12935
|
function verityPathEntry() {
|
|
12975
|
-
|
|
12936
|
+
const path = process.env.PATH;
|
|
12937
|
+
if (!path) return null;
|
|
12938
|
+
for (const dir of path.split(import_node_path7.delimiter)) {
|
|
12939
|
+
if (!dir) continue;
|
|
12940
|
+
const candidate = (0, import_node_path7.join)(dir, "verity");
|
|
12941
|
+
try {
|
|
12942
|
+
(0, import_node_fs6.accessSync)(candidate, import_node_fs6.constants.X_OK);
|
|
12943
|
+
return candidate;
|
|
12944
|
+
} catch {
|
|
12945
|
+
}
|
|
12946
|
+
}
|
|
12947
|
+
return null;
|
|
12976
12948
|
}
|
|
12977
12949
|
function verityOnPath() {
|
|
12978
12950
|
return verityPathEntry() !== null;
|
|
@@ -12981,7 +12953,7 @@ function globalVerityVersion() {
|
|
|
12981
12953
|
const entry = verityPathEntry();
|
|
12982
12954
|
if (!entry) return null;
|
|
12983
12955
|
try {
|
|
12984
|
-
const pkg = readJsonFile((0,
|
|
12956
|
+
const pkg = readJsonFile((0, import_node_path7.join)((0, import_node_path7.dirname)((0, import_node_fs6.realpathSync)(entry)), "..", "package.json"));
|
|
12985
12957
|
if (pkg?.name !== "@codacy/verity-cli" || typeof pkg.version !== "string") return null;
|
|
12986
12958
|
return pkg.version;
|
|
12987
12959
|
} catch {
|
|
@@ -12993,7 +12965,7 @@ function pluginCliInvocation() {
|
|
|
12993
12965
|
if (!root) return null;
|
|
12994
12966
|
const onPath = globalVerityVersion();
|
|
12995
12967
|
if (onPath !== null && onPath === activePluginVersion()) return null;
|
|
12996
|
-
return `node ${JSON.stringify((0,
|
|
12968
|
+
return `node ${JSON.stringify((0, import_node_path7.join)(root, "scripts", "verity.mjs"))}`;
|
|
12997
12969
|
}
|
|
12998
12970
|
function cliVersionSkew() {
|
|
12999
12971
|
if (!process.env.VERITY_PLUGIN_ROOT) return null;
|
|
@@ -13012,7 +12984,7 @@ var VERITY_MARKETPLACE_REPO = "codacy/verity";
|
|
|
13012
12984
|
function marketplaceConflict() {
|
|
13013
12985
|
const wanted = VERITY_MARKETPLACE;
|
|
13014
12986
|
for (const file of ["settings.json", "settings.local.json"]) {
|
|
13015
|
-
const path = (0,
|
|
12987
|
+
const path = (0, import_node_path7.join)(claudeConfigDir(), file);
|
|
13016
12988
|
const declared = readJsonFile(path)?.extraKnownMarketplaces ?? null;
|
|
13017
12989
|
const entry2 = declared && typeof declared === "object" ? declared[wanted] : void 0;
|
|
13018
12990
|
if (entry2 && typeof entry2 === "object") {
|
|
@@ -13022,7 +12994,7 @@ function marketplaceConflict() {
|
|
|
13022
12994
|
}
|
|
13023
12995
|
}
|
|
13024
12996
|
}
|
|
13025
|
-
const known = readJsonFile((0,
|
|
12997
|
+
const known = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "known_marketplaces.json"));
|
|
13026
12998
|
const entry = known?.[wanted];
|
|
13027
12999
|
if (entry && typeof entry === "object" && !pointsAtVerity(entry.source)) {
|
|
13028
13000
|
return { name: wanted, declaredAs: describeSource(entry.source) };
|
|
@@ -13043,7 +13015,7 @@ function describeSource(src) {
|
|
|
13043
13015
|
return target ? `${kind} \u2192 ${target}` : kind;
|
|
13044
13016
|
}
|
|
13045
13017
|
function legacyMarketplaceInstall() {
|
|
13046
|
-
const plugins = readJsonFile((0,
|
|
13018
|
+
const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
|
|
13047
13019
|
if (!plugins || typeof plugins !== "object") return null;
|
|
13048
13020
|
for (const key of Object.keys(plugins)) {
|
|
13049
13021
|
const at = key.lastIndexOf("@");
|
|
@@ -13055,13 +13027,13 @@ function legacyMarketplaceInstall() {
|
|
|
13055
13027
|
}
|
|
13056
13028
|
function realpathOr(p) {
|
|
13057
13029
|
try {
|
|
13058
|
-
return
|
|
13030
|
+
return import_node_fs6.realpathSync.native(p);
|
|
13059
13031
|
} catch {
|
|
13060
|
-
return (0,
|
|
13032
|
+
return (0, import_node_path7.resolve)(p);
|
|
13061
13033
|
}
|
|
13062
13034
|
}
|
|
13063
13035
|
function isWithin(want, dir) {
|
|
13064
|
-
return want === dir || want.startsWith(dir +
|
|
13036
|
+
return want === dir || want.startsWith(dir + import_node_path7.sep);
|
|
13065
13037
|
}
|
|
13066
13038
|
function entryAppliesHere(entry, here) {
|
|
13067
13039
|
const e = entry;
|
|
@@ -13071,9 +13043,9 @@ function entryAppliesHere(entry, here) {
|
|
|
13071
13043
|
return forProject === here;
|
|
13072
13044
|
}
|
|
13073
13045
|
function registrySays(pluginRoot) {
|
|
13074
|
-
const plugins = readJsonFile((0,
|
|
13046
|
+
const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
|
|
13075
13047
|
if (!plugins || typeof plugins !== "object") return "unverified";
|
|
13076
|
-
const want = (0,
|
|
13048
|
+
const want = (0, import_node_path7.resolve)(pluginRoot);
|
|
13077
13049
|
const here = realpathOr(repoRoot());
|
|
13078
13050
|
const markets = marketplaceLocations();
|
|
13079
13051
|
for (const [key, value] of Object.entries(plugins)) {
|
|
@@ -13082,15 +13054,15 @@ function registrySays(pluginRoot) {
|
|
|
13082
13054
|
const applicable = (Array.isArray(value) ? value : []).filter((entry) => entryAppliesHere(entry, here));
|
|
13083
13055
|
const claims = applicable.some((entry) => {
|
|
13084
13056
|
const installPath = entry?.installPath;
|
|
13085
|
-
return typeof installPath === "string" && (0,
|
|
13057
|
+
return typeof installPath === "string" && (0, import_node_path7.resolve)(installPath) === want;
|
|
13086
13058
|
}) || source !== void 0 && applicable.length > 0 && isWithin(want, source);
|
|
13087
13059
|
if (claims) {
|
|
13088
13060
|
if (enabledPluginSetting(key) === false) return "gone";
|
|
13089
|
-
return (0,
|
|
13061
|
+
return (0, import_node_fs6.existsSync)(pluginRoot) ? "live" : "gone";
|
|
13090
13062
|
}
|
|
13091
13063
|
}
|
|
13092
|
-
const managed = (0,
|
|
13093
|
-
return want === managed || want.startsWith(managed +
|
|
13064
|
+
const managed = (0, import_node_path7.resolve)((0, import_node_path7.join)(claudeConfigDir(), "plugins", "cache"));
|
|
13065
|
+
return want === managed || want.startsWith(managed + import_node_path7.sep) ? "gone" : "unverified";
|
|
13094
13066
|
}
|
|
13095
13067
|
var _live = /* @__PURE__ */ new Map();
|
|
13096
13068
|
function pluginLiveness(pluginRoot) {
|
|
@@ -13104,7 +13076,7 @@ function clearStalePluginMarker() {
|
|
|
13104
13076
|
const marker = readMarker();
|
|
13105
13077
|
if (!marker || pluginLiveness(marker.plugin_root) !== "gone") return null;
|
|
13106
13078
|
try {
|
|
13107
|
-
(0,
|
|
13079
|
+
(0, import_node_fs6.rmSync)(markerPath(), { force: true });
|
|
13108
13080
|
} catch {
|
|
13109
13081
|
return null;
|
|
13110
13082
|
}
|
|
@@ -13132,7 +13104,7 @@ function pluginActiveHere() {
|
|
|
13132
13104
|
return activePluginInstall() !== null;
|
|
13133
13105
|
}
|
|
13134
13106
|
function registeredVerityPlugin() {
|
|
13135
|
-
const plugins = readJsonFile((0,
|
|
13107
|
+
const plugins = readJsonFile((0, import_node_path7.join)(claudeConfigDir(), "plugins", "installed_plugins.json"))?.plugins;
|
|
13136
13108
|
if (!plugins || typeof plugins !== "object") return null;
|
|
13137
13109
|
const here = realpathOr(repoRoot());
|
|
13138
13110
|
for (const [key, value] of Object.entries(plugins)) {
|
|
@@ -13142,7 +13114,7 @@ function registeredVerityPlugin() {
|
|
|
13142
13114
|
for (const entry of Array.isArray(value) ? value : []) {
|
|
13143
13115
|
const e = entry;
|
|
13144
13116
|
const installPath = typeof e.installPath === "string" ? e.installPath : "";
|
|
13145
|
-
if (!installPath || !(0,
|
|
13117
|
+
if (!installPath || !(0, import_node_fs6.existsSync)(installPath)) continue;
|
|
13146
13118
|
if (!entryAppliesHere(entry, here)) continue;
|
|
13147
13119
|
return { pluginRoot: installPath, version: typeof e.version === "string" ? e.version : null };
|
|
13148
13120
|
}
|
|
@@ -13327,7 +13299,7 @@ var import_node_crypto8 = require("node:crypto");
|
|
|
13327
13299
|
|
|
13328
13300
|
// src/lib/conversation-buffer.ts
|
|
13329
13301
|
var import_promises5 = require("node:fs/promises");
|
|
13330
|
-
var
|
|
13302
|
+
var import_node_fs7 = require("node:fs");
|
|
13331
13303
|
var import_node_child_process5 = require("node:child_process");
|
|
13332
13304
|
var import_node_crypto = require("node:crypto");
|
|
13333
13305
|
function stripImageReferences(text) {
|
|
@@ -13363,7 +13335,7 @@ async function appendToConversationBuffer(prompt, sessionId) {
|
|
|
13363
13335
|
}
|
|
13364
13336
|
async function readAndClearConversationBuffer(currentSessionId) {
|
|
13365
13337
|
try {
|
|
13366
|
-
if ((0,
|
|
13338
|
+
if ((0, import_node_fs7.existsSync)(CONVERSATION_BUFFER_FILE)) {
|
|
13367
13339
|
const entries = await readBufferEntries();
|
|
13368
13340
|
let mine = entries;
|
|
13369
13341
|
let others = [];
|
|
@@ -13387,7 +13359,7 @@ async function readAndClearConversationBuffer(currentSessionId) {
|
|
|
13387
13359
|
};
|
|
13388
13360
|
}
|
|
13389
13361
|
}
|
|
13390
|
-
if ((0,
|
|
13362
|
+
if ((0, import_node_fs7.existsSync)(INTENT_FILE)) {
|
|
13391
13363
|
try {
|
|
13392
13364
|
const content = await (0, import_promises5.readFile)(INTENT_FILE, "utf-8");
|
|
13393
13365
|
await (0, import_promises5.unlink)(INTENT_FILE).catch(() => {
|
|
@@ -13680,9 +13652,9 @@ function isCommandOnlyTurn(input) {
|
|
|
13680
13652
|
|
|
13681
13653
|
// src/lib/context-identity.ts
|
|
13682
13654
|
var import_node_crypto2 = require("node:crypto");
|
|
13683
|
-
var
|
|
13655
|
+
var import_node_fs8 = require("node:fs");
|
|
13684
13656
|
var import_node_os3 = require("node:os");
|
|
13685
|
-
var
|
|
13657
|
+
var import_node_path8 = require("node:path");
|
|
13686
13658
|
var SHARED_SENTINELS = /* @__PURE__ */ new Set([
|
|
13687
13659
|
"",
|
|
13688
13660
|
"-",
|
|
@@ -13720,7 +13692,7 @@ function contextIdentity(input) {
|
|
|
13720
13692
|
if (rawTree && !isSharedSentinel(rawTree)) {
|
|
13721
13693
|
let resolved = rawTree;
|
|
13722
13694
|
try {
|
|
13723
|
-
resolved =
|
|
13695
|
+
resolved = import_node_fs8.realpathSync.native(rawTree);
|
|
13724
13696
|
} catch {
|
|
13725
13697
|
}
|
|
13726
13698
|
treeKey = (0, import_node_crypto2.createHash)("sha256").update(resolved).digest("hex").slice(0, 12);
|
|
@@ -13736,13 +13708,13 @@ function contextIdentity(input) {
|
|
|
13736
13708
|
}
|
|
13737
13709
|
function verityHome() {
|
|
13738
13710
|
const override = process.env.VERITY_HOME;
|
|
13739
|
-
return override && override.trim() ? (0,
|
|
13711
|
+
return override && override.trim() ? (0, import_node_path8.resolve)(override) : (0, import_node_path8.join)((0, import_node_os3.homedir)(), ".verity");
|
|
13740
13712
|
}
|
|
13741
13713
|
function dossierDir(identity) {
|
|
13742
|
-
return (0,
|
|
13714
|
+
return (0, import_node_path8.join)(verityHome(), "sessions", identity.userKey, identity.treeKey, identity.sessionKey);
|
|
13743
13715
|
}
|
|
13744
13716
|
function treeDir(identity) {
|
|
13745
|
-
return (0,
|
|
13717
|
+
return (0, import_node_path8.join)(verityHome(), "sessions", identity.userKey, identity.treeKey);
|
|
13746
13718
|
}
|
|
13747
13719
|
function scopeIdentity(token, sessionId) {
|
|
13748
13720
|
const t = (token ?? "").trim();
|
|
@@ -13757,8 +13729,8 @@ function sessionScopeKey(token, sessionId) {
|
|
|
13757
13729
|
|
|
13758
13730
|
// src/lib/task-context-buffer.ts
|
|
13759
13731
|
var import_promises6 = require("node:fs/promises");
|
|
13760
|
-
var
|
|
13761
|
-
var
|
|
13732
|
+
var import_node_fs9 = require("node:fs");
|
|
13733
|
+
var import_node_path9 = require("node:path");
|
|
13762
13734
|
var TASK_CONTEXT_DIR = `${VERITY_DIR}/.task-context`;
|
|
13763
13735
|
var MAX_BUFFER_BYTES = 500 * 1024;
|
|
13764
13736
|
var MAX_PROMPT_CHARS = 2e3;
|
|
@@ -13797,7 +13769,7 @@ async function appendResponseToTaskBuffer(taskId, assistantResponse, actionSumma
|
|
|
13797
13769
|
}
|
|
13798
13770
|
async function readTaskContextBuffer(taskId) {
|
|
13799
13771
|
const filePath = bufferPath(taskId);
|
|
13800
|
-
if (!(0,
|
|
13772
|
+
if (!(0, import_node_fs9.existsSync)(filePath)) return null;
|
|
13801
13773
|
try {
|
|
13802
13774
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
13803
13775
|
if (!content.trim()) return null;
|
|
@@ -13831,12 +13803,12 @@ async function readTaskContextBuffer(taskId) {
|
|
|
13831
13803
|
}
|
|
13832
13804
|
async function cleanupTaskContextBuffers() {
|
|
13833
13805
|
try {
|
|
13834
|
-
if (!(0,
|
|
13806
|
+
if (!(0, import_node_fs9.existsSync)(TASK_CONTEXT_DIR)) return;
|
|
13835
13807
|
const files = await (0, import_promises6.readdir)(TASK_CONTEXT_DIR);
|
|
13836
13808
|
const cutoffMs = Date.now() - RETENTION_DAYS * 24 * 60 * 60 * 1e3;
|
|
13837
13809
|
for (const file of files) {
|
|
13838
13810
|
if (!file.endsWith(".jsonl")) continue;
|
|
13839
|
-
const filePath = (0,
|
|
13811
|
+
const filePath = (0, import_node_path9.join)(TASK_CONTEXT_DIR, file);
|
|
13840
13812
|
try {
|
|
13841
13813
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
13842
13814
|
if (stats.mtimeMs < cutoffMs) {
|
|
@@ -13850,13 +13822,13 @@ async function cleanupTaskContextBuffers() {
|
|
|
13850
13822
|
}
|
|
13851
13823
|
function bufferPath(taskId) {
|
|
13852
13824
|
const safe = taskId.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
13853
|
-
return (0,
|
|
13825
|
+
return (0, import_node_path9.join)(TASK_CONTEXT_DIR, `${safe}.jsonl`);
|
|
13854
13826
|
}
|
|
13855
13827
|
async function appendEntry(taskId, entry) {
|
|
13856
13828
|
try {
|
|
13857
13829
|
await (0, import_promises6.mkdir)(TASK_CONTEXT_DIR, { recursive: true });
|
|
13858
13830
|
const filePath = bufferPath(taskId);
|
|
13859
|
-
if ((0,
|
|
13831
|
+
if ((0, import_node_fs9.existsSync)(filePath)) {
|
|
13860
13832
|
const stats = await (0, import_promises6.stat)(filePath);
|
|
13861
13833
|
if (stats.size >= MAX_BUFFER_BYTES) {
|
|
13862
13834
|
const content = await (0, import_promises6.readFile)(filePath, "utf-8");
|
|
@@ -13867,21 +13839,213 @@ async function appendEntry(taskId, entry) {
|
|
|
13867
13839
|
}
|
|
13868
13840
|
}
|
|
13869
13841
|
const line = JSON.stringify(entry) + "\n";
|
|
13870
|
-
const existing = (0,
|
|
13842
|
+
const existing = (0, import_node_fs9.existsSync)(filePath) ? await (0, import_promises6.readFile)(filePath, "utf-8") : "";
|
|
13871
13843
|
await (0, import_promises6.writeFile)(filePath, existing + line);
|
|
13872
13844
|
} catch {
|
|
13873
13845
|
}
|
|
13874
13846
|
}
|
|
13875
13847
|
|
|
13876
13848
|
// src/lib/memory-retrieval.ts
|
|
13877
|
-
var
|
|
13849
|
+
var import_promises8 = require("node:fs/promises");
|
|
13878
13850
|
var import_node_fs11 = require("node:fs");
|
|
13879
13851
|
var import_node_path11 = require("node:path");
|
|
13852
|
+
|
|
13853
|
+
// src/lib/org-mirror.ts
|
|
13854
|
+
var import_node_fs10 = require("node:fs");
|
|
13855
|
+
var import_promises7 = require("node:fs/promises");
|
|
13856
|
+
var import_node_path10 = require("node:path");
|
|
13857
|
+
var ORG_KINDS = ["decision", "security", "gotcha", "pattern", "domain", "integration"];
|
|
13858
|
+
var STATE_FILE = ".org-pull-state.json";
|
|
13859
|
+
var BODY_MAX = 8192;
|
|
13860
|
+
function orgMirrorDir(remote = requestRemote()) {
|
|
13861
|
+
const parsed = parseRemote(remote);
|
|
13862
|
+
if (!parsed) return null;
|
|
13863
|
+
return (0, import_node_path10.join)(verityHome(), "orgs", parsed.host, parsed.owner.toLowerCase(), "memory");
|
|
13864
|
+
}
|
|
13865
|
+
function slugify(s) {
|
|
13866
|
+
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
13867
|
+
}
|
|
13868
|
+
function orgNodePath(node) {
|
|
13869
|
+
const kind = ORG_KINDS.includes(node.kind) ? node.kind : "domain";
|
|
13870
|
+
return `${kind}/${slugify(node.title) || "claim"}-${node.tag_id.replace(/-/g, "").slice(0, 8)}.md`;
|
|
13871
|
+
}
|
|
13872
|
+
function renderOrgNode(node, orgName) {
|
|
13873
|
+
const fm = [
|
|
13874
|
+
"---",
|
|
13875
|
+
`id: ${JSON.stringify(node.tag_id)}`,
|
|
13876
|
+
`node_id: ${JSON.stringify(node.node_id)}`,
|
|
13877
|
+
'scope: "org"',
|
|
13878
|
+
`org: ${JSON.stringify(orgName)}`,
|
|
13879
|
+
`kind: ${JSON.stringify(node.kind)}`,
|
|
13880
|
+
`title: ${JSON.stringify(node.title)}`,
|
|
13881
|
+
`origin: ${JSON.stringify(node.origin)}`,
|
|
13882
|
+
`signal: ${JSON.stringify(node.signal)}`,
|
|
13883
|
+
`tier: ${node.tier === null ? "null" : JSON.stringify(node.tier)}`,
|
|
13884
|
+
`applies_to: ${JSON.stringify(node.applies_to)}`,
|
|
13885
|
+
`source_repo_count: ${JSON.stringify(node.source_repo_count)}`,
|
|
13886
|
+
`promoted_at: ${JSON.stringify(node.promoted_at)}`,
|
|
13887
|
+
'status: "active"',
|
|
13888
|
+
"---"
|
|
13889
|
+
];
|
|
13890
|
+
return `${fm.join("\n")}
|
|
13891
|
+
|
|
13892
|
+
# ${node.title}
|
|
13893
|
+
|
|
13894
|
+
${node.body.trim()}
|
|
13895
|
+
`;
|
|
13896
|
+
}
|
|
13897
|
+
function parseOrgNode(content) {
|
|
13898
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
13899
|
+
if (!m) return null;
|
|
13900
|
+
const fm = {};
|
|
13901
|
+
for (const line of m[1].split("\n")) {
|
|
13902
|
+
const kv = line.match(/^([a-z_]+):\s*(.*)$/);
|
|
13903
|
+
if (!kv) continue;
|
|
13904
|
+
try {
|
|
13905
|
+
fm[kv[1]] = JSON.parse(kv[2]);
|
|
13906
|
+
} catch {
|
|
13907
|
+
fm[kv[1]] = kv[2];
|
|
13908
|
+
}
|
|
13909
|
+
}
|
|
13910
|
+
if (fm.scope !== "org" || typeof fm.id !== "string" || typeof fm.title !== "string") return null;
|
|
13911
|
+
if (fm.status && fm.status !== "active") return null;
|
|
13912
|
+
const body = m[2].replace(/^\s*#[^\n]*\n/, "").trim();
|
|
13913
|
+
return {
|
|
13914
|
+
tag_id: fm.id,
|
|
13915
|
+
node_id: typeof fm.node_id === "string" ? fm.node_id : "",
|
|
13916
|
+
kind: typeof fm.kind === "string" ? fm.kind : "domain",
|
|
13917
|
+
title: fm.title,
|
|
13918
|
+
body,
|
|
13919
|
+
origin: typeof fm.origin === "string" ? fm.origin : "",
|
|
13920
|
+
signal: typeof fm.signal === "string" ? fm.signal : "",
|
|
13921
|
+
tier: typeof fm.tier === "number" ? fm.tier : null,
|
|
13922
|
+
applies_to: Array.isArray(fm.applies_to) ? fm.applies_to.filter((v) => typeof v === "string") : [],
|
|
13923
|
+
source_repo_count: typeof fm.source_repo_count === "number" ? fm.source_repo_count : 1,
|
|
13924
|
+
promoted_at: typeof fm.promoted_at === "string" ? fm.promoted_at : ""
|
|
13925
|
+
};
|
|
13926
|
+
}
|
|
13927
|
+
async function readState(dir) {
|
|
13928
|
+
try {
|
|
13929
|
+
const parsed = JSON.parse(await (0, import_promises7.readFile)((0, import_node_path10.join)(dir, STATE_FILE), "utf-8"));
|
|
13930
|
+
return {
|
|
13931
|
+
version: typeof parsed?.version === "string" ? parsed.version : null,
|
|
13932
|
+
organization: parsed?.organization && typeof parsed.organization.id === "string" ? parsed.organization : null,
|
|
13933
|
+
files: Array.isArray(parsed?.files) ? parsed.files.filter((f) => typeof f === "string") : []
|
|
13934
|
+
};
|
|
13935
|
+
} catch {
|
|
13936
|
+
return { version: null, organization: null, files: [] };
|
|
13937
|
+
}
|
|
13938
|
+
}
|
|
13939
|
+
async function writeState(dir, state) {
|
|
13940
|
+
await (0, import_promises7.mkdir)(dir, { recursive: true });
|
|
13941
|
+
await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, STATE_FILE), JSON.stringify({
|
|
13942
|
+
...state.version ? { version: state.version } : {},
|
|
13943
|
+
organization: state.organization,
|
|
13944
|
+
pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
13945
|
+
files: state.files
|
|
13946
|
+
}, null, 2) + "\n");
|
|
13947
|
+
}
|
|
13948
|
+
function renderIndex(orgName, nodes) {
|
|
13949
|
+
const lines = [
|
|
13950
|
+
`# Org Knowledge \u2014 ${orgName}`,
|
|
13951
|
+
"",
|
|
13952
|
+
"*Auto-generated by verity CLI from what this organization's repositories learned. Do not hand-edit; `verity memory pull` overwrites it.*",
|
|
13953
|
+
"",
|
|
13954
|
+
'> These claims hold across the organization\'s repositories, not only this one. Each names where it came from. A claim that is wrong for this repository is demoted with `verity memory demote <id> --reason "\u2026"`, for everyone.',
|
|
13955
|
+
""
|
|
13956
|
+
];
|
|
13957
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
13958
|
+
for (const n of nodes) byKind.set(n.kind, [...byKind.get(n.kind) ?? [], n]);
|
|
13959
|
+
for (const kind of ORG_KINDS) {
|
|
13960
|
+
const list2 = byKind.get(kind);
|
|
13961
|
+
if (!list2?.length) continue;
|
|
13962
|
+
lines.push(`## ${kind}/ (${list2.length})`);
|
|
13963
|
+
for (const n of list2) lines.push(`- [[${orgNodePath(n).split("/")[1].replace(/\.md$/, "")}]] \u2014 ${n.title} \xB7 ${n.origin}`);
|
|
13964
|
+
lines.push("");
|
|
13965
|
+
}
|
|
13966
|
+
if (nodes.length === 0) lines.push("No org knowledge yet.", "");
|
|
13967
|
+
return lines.join("\n");
|
|
13968
|
+
}
|
|
13969
|
+
async function pullOrgKnowledge(opts) {
|
|
13970
|
+
const dir = orgMirrorDir(opts.remote ?? requestRemote());
|
|
13971
|
+
if (!dir) return { ok: true, status: "no_remote", received: 0, dir: null };
|
|
13972
|
+
const state = await readState(dir);
|
|
13973
|
+
const params = new URLSearchParams({ for: "agent" });
|
|
13974
|
+
if (!opts.force && state.version) params.set("if_version", state.version);
|
|
13975
|
+
const res = await apiRequest({
|
|
13976
|
+
method: "GET",
|
|
13977
|
+
path: `/memory/org?${params.toString()}`,
|
|
13978
|
+
serviceUrl: opts.serviceUrl,
|
|
13979
|
+
token: opts.token,
|
|
13980
|
+
verbose: opts.verbose,
|
|
13981
|
+
timeout: opts.timeoutMs ?? 3e4,
|
|
13982
|
+
cmd: "memory-org-pull"
|
|
13983
|
+
});
|
|
13984
|
+
if (!res.ok) return { ok: false, error: res.error, category: res.category };
|
|
13985
|
+
const page = res.data;
|
|
13986
|
+
if (page.unchanged) return { ok: true, status: "unchanged", received: 0, dir };
|
|
13987
|
+
const org = page.organization;
|
|
13988
|
+
const nodes = org ? (page.nodes ?? []).flatMap((n) => typeof n.tag_id === "string" && typeof n.title === "string" && typeof n.body === "string" ? [{
|
|
13989
|
+
tag_id: n.tag_id,
|
|
13990
|
+
node_id: typeof n.node_id === "string" ? n.node_id : "",
|
|
13991
|
+
kind: typeof n.kind === "string" ? n.kind : "domain",
|
|
13992
|
+
title: n.title,
|
|
13993
|
+
body: n.body.slice(0, BODY_MAX),
|
|
13994
|
+
origin: typeof n.origin === "string" ? n.origin : "",
|
|
13995
|
+
signal: typeof n.signal === "string" ? n.signal : "",
|
|
13996
|
+
tier: typeof n.tier === "number" ? n.tier : null,
|
|
13997
|
+
applies_to: Array.isArray(n.applies_to) ? n.applies_to.filter((v) => typeof v === "string") : [],
|
|
13998
|
+
source_repo_count: typeof n.source_repo_count === "number" ? n.source_repo_count : 1,
|
|
13999
|
+
promoted_at: typeof n.promoted_at === "string" ? n.promoted_at : ""
|
|
14000
|
+
}] : []) : [];
|
|
14001
|
+
const written = /* @__PURE__ */ new Set();
|
|
14002
|
+
await (0, import_promises7.mkdir)(dir, { recursive: true });
|
|
14003
|
+
for (const n of nodes) {
|
|
14004
|
+
const rel = orgNodePath(n);
|
|
14005
|
+
if (written.has(rel)) continue;
|
|
14006
|
+
await (0, import_promises7.mkdir)((0, import_node_path10.join)(dir, rel.split("/")[0]), { recursive: true });
|
|
14007
|
+
await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, rel), renderOrgNode(n, org?.name ?? ""));
|
|
14008
|
+
written.add(rel);
|
|
14009
|
+
}
|
|
14010
|
+
for (const rel of state.files) {
|
|
14011
|
+
if (!written.has(rel)) await (0, import_promises7.rm)((0, import_node_path10.join)(dir, rel), { force: true });
|
|
14012
|
+
}
|
|
14013
|
+
await (0, import_promises7.writeFile)((0, import_node_path10.join)(dir, "index.md"), renderIndex(org?.name ?? "no organization", nodes));
|
|
14014
|
+
await writeState(dir, { version: org ? page.version : null, organization: org, files: [...written].sort() });
|
|
14015
|
+
return { ok: true, status: org ? "pulled" : "none", received: nodes.length, dir };
|
|
14016
|
+
}
|
|
14017
|
+
async function readOrgMirror(remote = requestRemote()) {
|
|
14018
|
+
const dir = orgMirrorDir(remote);
|
|
14019
|
+
if (!dir || !(0, import_node_fs10.existsSync)(dir)) return [];
|
|
14020
|
+
const out = [];
|
|
14021
|
+
for (const kind of ORG_KINDS) {
|
|
14022
|
+
const kindDir = (0, import_node_path10.join)(dir, kind);
|
|
14023
|
+
if (!(0, import_node_fs10.existsSync)(kindDir)) continue;
|
|
14024
|
+
let files;
|
|
14025
|
+
try {
|
|
14026
|
+
files = await (0, import_promises7.readdir)(kindDir);
|
|
14027
|
+
} catch {
|
|
14028
|
+
continue;
|
|
14029
|
+
}
|
|
14030
|
+
for (const file of files) {
|
|
14031
|
+
if (!file.endsWith(".md")) continue;
|
|
14032
|
+
try {
|
|
14033
|
+
const node = parseOrgNode(await (0, import_promises7.readFile)((0, import_node_path10.join)(kindDir, file), "utf-8"));
|
|
14034
|
+
if (node) out.push(node);
|
|
14035
|
+
} catch {
|
|
14036
|
+
}
|
|
14037
|
+
}
|
|
14038
|
+
}
|
|
14039
|
+
return out;
|
|
14040
|
+
}
|
|
14041
|
+
|
|
14042
|
+
// src/lib/memory-retrieval.ts
|
|
13880
14043
|
var memoryDir = () => projectPath(`${VERITY_DIR}/memory`);
|
|
13881
14044
|
var DOMAINS = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations"];
|
|
13882
14045
|
var DEFAULT_BUDGET_TOKENS = 2e3;
|
|
13883
14046
|
var MAX_BUDGET_TOKENS = 4e3;
|
|
13884
14047
|
var MIN_SCORE = 0.5;
|
|
14048
|
+
var ORG_MIN_SHARED_TERMS = 2;
|
|
13885
14049
|
function tokenize(text) {
|
|
13886
14050
|
return new Set(
|
|
13887
14051
|
text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((w) => w.length > 2)
|
|
@@ -13894,6 +14058,26 @@ function jaccardKeywords(a, b) {
|
|
|
13894
14058
|
const union = a.size + b.size - inter;
|
|
13895
14059
|
return union === 0 ? 0 : inter / union;
|
|
13896
14060
|
}
|
|
14061
|
+
function sharedTerms(a, b) {
|
|
14062
|
+
let n = 0;
|
|
14063
|
+
for (const w of a) if (b.has(w)) n++;
|
|
14064
|
+
return n;
|
|
14065
|
+
}
|
|
14066
|
+
function orgNodeToLocal(n) {
|
|
14067
|
+
return {
|
|
14068
|
+
path: `org:${n.tag_id}`,
|
|
14069
|
+
nodeId: n.tag_id,
|
|
14070
|
+
kind: n.kind,
|
|
14071
|
+
title: n.title,
|
|
14072
|
+
body: n.body.slice(0, 2e3),
|
|
14073
|
+
fileGlobs: [],
|
|
14074
|
+
confidence: 0.5,
|
|
14075
|
+
citedCount: 0,
|
|
14076
|
+
tokenEstimate: Math.ceil((n.title.length + n.body.length) / 4) + 20,
|
|
14077
|
+
scope: "org",
|
|
14078
|
+
origin: n.origin
|
|
14079
|
+
};
|
|
14080
|
+
}
|
|
13897
14081
|
function globMatch(glob, filePath) {
|
|
13898
14082
|
if (glob === filePath) return true;
|
|
13899
14083
|
const suffixMatch = glob.match(/^\*\*\/\*(.+)$/);
|
|
@@ -13969,19 +14153,19 @@ function parseFrontmatter(content) {
|
|
|
13969
14153
|
return { fm, body: match[2].trim() };
|
|
13970
14154
|
}
|
|
13971
14155
|
async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = DEFAULT_BUDGET_TOKENS) {
|
|
13972
|
-
|
|
14156
|
+
const hasRepoGraph = (0, import_node_fs11.existsSync)(memoryDir());
|
|
13973
14157
|
const budget = Math.min(budgetTokens, MAX_BUDGET_TOKENS);
|
|
13974
14158
|
const promptTokens = tokenize(promptText);
|
|
13975
14159
|
const nodes = [];
|
|
13976
|
-
for (const domain of DOMAINS) {
|
|
14160
|
+
for (const domain of hasRepoGraph ? DOMAINS : []) {
|
|
13977
14161
|
const domainDir = (0, import_node_path11.join)(memoryDir(), domain);
|
|
13978
14162
|
if (!(0, import_node_fs11.existsSync)(domainDir)) continue;
|
|
13979
14163
|
try {
|
|
13980
|
-
const files = await (0,
|
|
14164
|
+
const files = await (0, import_promises8.readdir)(domainDir);
|
|
13981
14165
|
for (const file of files) {
|
|
13982
14166
|
if (!file.endsWith(".md")) continue;
|
|
13983
14167
|
try {
|
|
13984
|
-
const content = await (0,
|
|
14168
|
+
const content = await (0, import_promises8.readFile)((0, import_node_path11.join)(domainDir, file), "utf-8");
|
|
13985
14169
|
const { fm, body } = parseFrontmatter(content);
|
|
13986
14170
|
if (fm.status && fm.status !== "active") continue;
|
|
13987
14171
|
nodes.push({
|
|
@@ -14001,6 +14185,13 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
14001
14185
|
} catch {
|
|
14002
14186
|
}
|
|
14003
14187
|
}
|
|
14188
|
+
try {
|
|
14189
|
+
for (const claim of await readOrgMirror()) {
|
|
14190
|
+
const local = orgNodeToLocal(claim);
|
|
14191
|
+
if (sharedTerms(tokenize(`${local.title} ${local.body}`), promptTokens) >= ORG_MIN_SHARED_TERMS) nodes.push(local);
|
|
14192
|
+
}
|
|
14193
|
+
} catch {
|
|
14194
|
+
}
|
|
14004
14195
|
if (nodes.length === 0) return null;
|
|
14005
14196
|
const scored = nodes.map((n) => ({
|
|
14006
14197
|
...n,
|
|
@@ -14019,12 +14210,13 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
14019
14210
|
const lines = [
|
|
14020
14211
|
"## Project Knowledge (auto-injected)",
|
|
14021
14212
|
"",
|
|
14022
|
-
`*${selected.length} node(s) relevant to your current task. See \`.verity/memory/index.md\` for the full knowledge base.*`,
|
|
14213
|
+
`*${selected.length} node(s) relevant to your current task. See \`.verity/memory/index.md\` for the full knowledge base${selected.some((n) => n.scope === "org") ? ", and `~/.verity/orgs/` for what the organization's other repositories learned" : ""}.*`,
|
|
14023
14214
|
""
|
|
14024
14215
|
];
|
|
14025
14216
|
for (const node of selected) {
|
|
14026
14217
|
lines.push(`### ${node.title}`);
|
|
14027
|
-
lines.push(
|
|
14218
|
+
if (node.scope === "org") lines.push(`*org \xB7 ${node.origin}* \xB7 ${node.kind}`);
|
|
14219
|
+
else lines.push(`*${node.kind}* \xB7 confidence ${Math.round(node.confidence * 100)}%`);
|
|
14028
14220
|
lines.push("");
|
|
14029
14221
|
lines.push(node.body.slice(0, 800));
|
|
14030
14222
|
lines.push("");
|
|
@@ -14039,7 +14231,7 @@ async function retrieveForInjection(promptText, taskFiles = [], budgetTokens = D
|
|
|
14039
14231
|
}
|
|
14040
14232
|
|
|
14041
14233
|
// src/lib/memory-sync.ts
|
|
14042
|
-
var
|
|
14234
|
+
var import_promises9 = require("node:fs/promises");
|
|
14043
14235
|
var import_node_fs14 = require("node:fs");
|
|
14044
14236
|
var import_node_path13 = require("node:path");
|
|
14045
14237
|
var import_node_crypto3 = require("node:crypto");
|
|
@@ -14154,8 +14346,8 @@ function ensureVerityGitignore() {
|
|
|
14154
14346
|
}
|
|
14155
14347
|
let text = next.join("\n");
|
|
14156
14348
|
if (!hasMarker) {
|
|
14157
|
-
const
|
|
14158
|
-
text = text +
|
|
14349
|
+
const sep4 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
14350
|
+
text = text + sep4 + VERITY_GITIGNORE_BLOCK;
|
|
14159
14351
|
}
|
|
14160
14352
|
if (text !== content) (0, import_node_fs12.writeFileSync)(".gitignore", text);
|
|
14161
14353
|
return verified(
|
|
@@ -14185,8 +14377,8 @@ function writeFencedBlock() {
|
|
|
14185
14377
|
lines = fenceMemoryLines(lines);
|
|
14186
14378
|
let text = lines.join("\n");
|
|
14187
14379
|
if (!hasMarker) {
|
|
14188
|
-
const
|
|
14189
|
-
text = text +
|
|
14380
|
+
const sep4 = text === "" ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
14381
|
+
text = text + sep4 + VERITY_GITIGNORE_BLOCK;
|
|
14190
14382
|
}
|
|
14191
14383
|
try {
|
|
14192
14384
|
(0, import_node_fs12.writeFileSync)(".gitignore", text);
|
|
@@ -14236,8 +14428,8 @@ function keepMemoryTracked() {
|
|
|
14236
14428
|
}
|
|
14237
14429
|
if (content.includes(MEMORY_OPT_OUT_MARKER)) return "already";
|
|
14238
14430
|
try {
|
|
14239
|
-
const
|
|
14240
|
-
(0, import_node_fs12.writeFileSync)(".gitignore", content +
|
|
14431
|
+
const sep4 = content === "" ? "" : content.endsWith("\n") ? "\n" : "\n\n";
|
|
14432
|
+
(0, import_node_fs12.writeFileSync)(".gitignore", content + sep4 + MEMORY_OPT_OUT_STANZA);
|
|
14241
14433
|
} catch {
|
|
14242
14434
|
return "failed";
|
|
14243
14435
|
}
|
|
@@ -14299,42 +14491,6 @@ function resolveInside(baseDir, candidate) {
|
|
|
14299
14491
|
}
|
|
14300
14492
|
return full;
|
|
14301
14493
|
}
|
|
14302
|
-
var O_NOFOLLOW = typeof import_node_fs13.constants.O_NOFOLLOW === "number" ? import_node_fs13.constants.O_NOFOLLOW : 0;
|
|
14303
|
-
function readFileInside(baseDir, candidate, maxBytes) {
|
|
14304
|
-
const full = resolveInside(baseDir, candidate);
|
|
14305
|
-
if (!full) return null;
|
|
14306
|
-
let realParent;
|
|
14307
|
-
let realBase;
|
|
14308
|
-
try {
|
|
14309
|
-
realParent = (0, import_node_fs13.realpathSync)((0, import_node_path12.dirname)(full));
|
|
14310
|
-
realBase = (0, import_node_fs13.realpathSync)((0, import_node_path12.resolve)(baseDir));
|
|
14311
|
-
} catch {
|
|
14312
|
-
return null;
|
|
14313
|
-
}
|
|
14314
|
-
const realBaseSep = realBase.endsWith(import_node_path12.sep) ? realBase : realBase + import_node_path12.sep;
|
|
14315
|
-
if (realParent !== realBase && !realParent.startsWith(realBaseSep)) return null;
|
|
14316
|
-
const target = (0, import_node_path12.join)(realParent, (0, import_node_path12.basename)(full));
|
|
14317
|
-
let fd = null;
|
|
14318
|
-
try {
|
|
14319
|
-
fd = (0, import_node_fs13.openSync)(target, import_node_fs13.constants.O_RDONLY | O_NOFOLLOW);
|
|
14320
|
-
const st = (0, import_node_fs13.fstatSync)(fd);
|
|
14321
|
-
if (!st.isFile()) return null;
|
|
14322
|
-
const cap = Math.min(maxBytes, st.size);
|
|
14323
|
-
if (cap <= 0) return "";
|
|
14324
|
-
const buf = Buffer.alloc(cap);
|
|
14325
|
-
const bytesRead = (0, import_node_fs13.readSync)(fd, buf, 0, cap, 0);
|
|
14326
|
-
return buf.subarray(0, bytesRead).toString("utf-8");
|
|
14327
|
-
} catch {
|
|
14328
|
-
return null;
|
|
14329
|
-
} finally {
|
|
14330
|
-
if (fd !== null) {
|
|
14331
|
-
try {
|
|
14332
|
-
(0, import_node_fs13.closeSync)(fd);
|
|
14333
|
-
} catch {
|
|
14334
|
-
}
|
|
14335
|
-
}
|
|
14336
|
-
}
|
|
14337
|
-
}
|
|
14338
14494
|
|
|
14339
14495
|
// src/lib/glob-match.ts
|
|
14340
14496
|
function globToRegex(glob) {
|
|
@@ -14384,6 +14540,14 @@ function globToRegex(glob) {
|
|
|
14384
14540
|
out = out.replace(/(?:\.\*)+/g, ".*");
|
|
14385
14541
|
return new RegExp(`^${out}$`);
|
|
14386
14542
|
}
|
|
14543
|
+
function globMatch2(glob, filePath) {
|
|
14544
|
+
if (glob === filePath) return true;
|
|
14545
|
+
try {
|
|
14546
|
+
return globToRegex(glob).test(filePath);
|
|
14547
|
+
} catch {
|
|
14548
|
+
return false;
|
|
14549
|
+
}
|
|
14550
|
+
}
|
|
14387
14551
|
function anyPathMatches(glob, treePaths) {
|
|
14388
14552
|
let regex;
|
|
14389
14553
|
try {
|
|
@@ -14402,18 +14566,18 @@ var memoryDir2 = () => projectPath(`${VERITY_DIR}/memory`);
|
|
|
14402
14566
|
var DOMAINS2 = ["decisions", "quality", "security", "intent", "gotchas", "patterns", "domain", "integrations", "_archive"];
|
|
14403
14567
|
var syncStateFile = () => projectPath(`${VERITY_DIR}/.memory-sync-state.json`);
|
|
14404
14568
|
async function ensureMemoryDir() {
|
|
14405
|
-
await (0,
|
|
14569
|
+
await (0, import_promises9.mkdir)(memoryDir2(), { recursive: true });
|
|
14406
14570
|
for (const domain of DOMAINS2) {
|
|
14407
|
-
await (0,
|
|
14571
|
+
await (0, import_promises9.mkdir)((0, import_node_path13.join)(memoryDir2(), domain), { recursive: true });
|
|
14408
14572
|
}
|
|
14409
14573
|
if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "SCHEMA.md"))) {
|
|
14410
|
-
await (0,
|
|
14574
|
+
await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "SCHEMA.md"), SCHEMA_TEMPLATE);
|
|
14411
14575
|
}
|
|
14412
14576
|
if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "index.md"))) {
|
|
14413
|
-
await (0,
|
|
14577
|
+
await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "# Project Memory Index\n\nNo nodes yet. Run an analysis to start building the knowledge graph.\n");
|
|
14414
14578
|
}
|
|
14415
14579
|
if (!(0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md"))) {
|
|
14416
|
-
await (0,
|
|
14580
|
+
await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "# Memory Log\n\n");
|
|
14417
14581
|
}
|
|
14418
14582
|
}
|
|
14419
14583
|
async function buildManifest() {
|
|
@@ -14425,13 +14589,13 @@ async function buildManifest() {
|
|
|
14425
14589
|
const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
|
|
14426
14590
|
if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
|
|
14427
14591
|
try {
|
|
14428
|
-
const files = await (0,
|
|
14592
|
+
const files = await (0, import_promises9.readdir)(domainDir);
|
|
14429
14593
|
for (const file of files) {
|
|
14430
14594
|
if (!file.endsWith(".md")) continue;
|
|
14431
14595
|
const filePath = `${domain}/${file}`;
|
|
14432
14596
|
const fullPath = (0, import_node_path13.join)(memoryDir2(), filePath);
|
|
14433
14597
|
try {
|
|
14434
|
-
const content = await (0,
|
|
14598
|
+
const content = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
14435
14599
|
const hash = (0, import_node_crypto3.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
14436
14600
|
nodes.push({ path: filePath, content_hash: `sha256:${hash}` });
|
|
14437
14601
|
} catch {
|
|
@@ -14442,13 +14606,13 @@ async function buildManifest() {
|
|
|
14442
14606
|
}
|
|
14443
14607
|
let indexHash = null;
|
|
14444
14608
|
try {
|
|
14445
|
-
const indexContent = await (0,
|
|
14609
|
+
const indexContent = await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "index.md"), "utf-8");
|
|
14446
14610
|
indexHash = `sha256:${(0, import_node_crypto3.createHash)("sha256").update(indexContent).digest("hex").slice(0, 16)}`;
|
|
14447
14611
|
} catch {
|
|
14448
14612
|
}
|
|
14449
14613
|
let logLength = 0;
|
|
14450
14614
|
try {
|
|
14451
|
-
const logContent = await (0,
|
|
14615
|
+
const logContent = await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8");
|
|
14452
14616
|
logLength = logContent.split("\n").length;
|
|
14453
14617
|
} catch {
|
|
14454
14618
|
}
|
|
@@ -14464,10 +14628,10 @@ async function readOnDiskNodes() {
|
|
|
14464
14628
|
const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
|
|
14465
14629
|
if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
|
|
14466
14630
|
try {
|
|
14467
|
-
for (const file of await (0,
|
|
14631
|
+
for (const file of await (0, import_promises9.readdir)(domainDir)) {
|
|
14468
14632
|
if (!file.endsWith(".md")) continue;
|
|
14469
14633
|
try {
|
|
14470
|
-
out.set(`${domain}/${file}`, hashContent(await (0,
|
|
14634
|
+
out.set(`${domain}/${file}`, hashContent(await (0, import_promises9.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8")));
|
|
14471
14635
|
} catch {
|
|
14472
14636
|
}
|
|
14473
14637
|
}
|
|
@@ -14479,7 +14643,7 @@ async function readOnDiskNodes() {
|
|
|
14479
14643
|
async function readSyncBaseline() {
|
|
14480
14644
|
const out = /* @__PURE__ */ new Map();
|
|
14481
14645
|
try {
|
|
14482
|
-
const parsed = JSON.parse(await (0,
|
|
14646
|
+
const parsed = JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
|
|
14483
14647
|
if (Array.isArray(parsed?.nodes)) {
|
|
14484
14648
|
for (const n of parsed.nodes) if (n?.path) out.set(n.path, n.hash ?? null);
|
|
14485
14649
|
} else if (Array.isArray(parsed?.paths)) {
|
|
@@ -14495,15 +14659,20 @@ async function recordSyncedNodePaths() {
|
|
|
14495
14659
|
const next = JSON.stringify({ schema: 2, nodes }) + "\n";
|
|
14496
14660
|
let existing = "";
|
|
14497
14661
|
try {
|
|
14498
|
-
existing = await (0,
|
|
14662
|
+
existing = await (0, import_promises9.readFile)(syncStateFile(), "utf-8");
|
|
14499
14663
|
} catch {
|
|
14500
14664
|
}
|
|
14501
14665
|
if (existing === next) return;
|
|
14502
|
-
await (
|
|
14503
|
-
await (0, import_promises8.writeFile)(syncStateFile(), next);
|
|
14666
|
+
await writeFileAtomic(syncStateFile(), next);
|
|
14504
14667
|
} catch {
|
|
14505
14668
|
}
|
|
14506
14669
|
}
|
|
14670
|
+
async function writeFileAtomic(path, content) {
|
|
14671
|
+
await (0, import_promises9.mkdir)((0, import_node_path13.dirname)(path), { recursive: true });
|
|
14672
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
14673
|
+
await (0, import_promises9.writeFile)(tmp, content);
|
|
14674
|
+
await (0, import_promises9.rename)(tmp, path);
|
|
14675
|
+
}
|
|
14507
14676
|
async function recordSyncBaseline() {
|
|
14508
14677
|
await recordSyncedNodePaths();
|
|
14509
14678
|
}
|
|
@@ -14517,7 +14686,7 @@ async function computeEditedNodeUploads() {
|
|
|
14517
14686
|
if (!(0, import_node_fs14.existsSync)(full)) continue;
|
|
14518
14687
|
let content;
|
|
14519
14688
|
try {
|
|
14520
|
-
content = await (0,
|
|
14689
|
+
content = await (0, import_promises9.readFile)(full, "utf-8");
|
|
14521
14690
|
} catch {
|
|
14522
14691
|
continue;
|
|
14523
14692
|
}
|
|
@@ -14538,31 +14707,56 @@ async function applyMemoryWrites(writes, opts = {}) {
|
|
|
14538
14707
|
let count = 0;
|
|
14539
14708
|
const notes = [];
|
|
14540
14709
|
const treePaths = opts.treePaths;
|
|
14710
|
+
const synced = /* @__PURE__ */ new Map();
|
|
14541
14711
|
for (const write of writes) {
|
|
14542
|
-
const
|
|
14543
|
-
if (written) count++;
|
|
14544
|
-
|
|
14712
|
+
const outcome = await applyOneWrite(write, treePaths, opts.lastServed);
|
|
14713
|
+
if (outcome.written) count++;
|
|
14714
|
+
if (outcome.syncedHash) {
|
|
14715
|
+
synced.set(write.path, outcome.syncedHash);
|
|
14716
|
+
opts.served?.set(write.path, outcome.syncedHash);
|
|
14717
|
+
}
|
|
14718
|
+
notes.push(...outcome.notes);
|
|
14545
14719
|
}
|
|
14546
14720
|
await regenerateIndex();
|
|
14547
|
-
|
|
14548
|
-
|
|
14549
|
-
|
|
14721
|
+
if (opts.claudeMdPointer !== false) {
|
|
14722
|
+
try {
|
|
14723
|
+
await ensureClaudeMdPointer();
|
|
14724
|
+
} catch {
|
|
14725
|
+
}
|
|
14550
14726
|
}
|
|
14551
14727
|
const logLines = [`- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 16)} \u2014 Applied ${count} write(s) from server`];
|
|
14552
14728
|
for (const n of notes) logLines.push(` - ${n}`);
|
|
14553
14729
|
try {
|
|
14554
|
-
const existing = (0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md")) ? await (0,
|
|
14555
|
-
await (0,
|
|
14730
|
+
const existing = (0, import_node_fs14.existsSync)((0, import_node_path13.join)(memoryDir2(), "log.md")) ? await (0, import_promises9.readFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), "utf-8") : "# Memory Log\n\n";
|
|
14731
|
+
await (0, import_promises9.writeFile)((0, import_node_path13.join)(memoryDir2(), "log.md"), existing + logLines.join("\n") + "\n");
|
|
14556
14732
|
} catch {
|
|
14557
14733
|
}
|
|
14558
|
-
await
|
|
14734
|
+
if (opts.baseline === "written") await mergeSyncBaseline(synced);
|
|
14735
|
+
else if (opts.recordBaseline !== false) await recordSyncedNodePaths();
|
|
14559
14736
|
return count;
|
|
14560
14737
|
}
|
|
14561
|
-
async function
|
|
14738
|
+
async function mergeSyncBaseline(entries) {
|
|
14739
|
+
if (entries.size === 0) return;
|
|
14740
|
+
try {
|
|
14741
|
+
if ((0, import_node_fs14.existsSync)(syncStateFile())) {
|
|
14742
|
+
try {
|
|
14743
|
+
JSON.parse(await (0, import_promises9.readFile)(syncStateFile(), "utf-8"));
|
|
14744
|
+
} catch {
|
|
14745
|
+
return;
|
|
14746
|
+
}
|
|
14747
|
+
}
|
|
14748
|
+
const merged = await readSyncBaseline();
|
|
14749
|
+
for (const [path, hash] of entries) merged.set(path, hash);
|
|
14750
|
+
const nodes = [...merged].map(([path, hash]) => ({ path, hash }));
|
|
14751
|
+
await writeFileAtomic(syncStateFile(), JSON.stringify({ schema: 2, nodes }) + "\n");
|
|
14752
|
+
} catch {
|
|
14753
|
+
}
|
|
14754
|
+
}
|
|
14755
|
+
async function applyOneWrite(write, treePaths, lastServed) {
|
|
14562
14756
|
const fullPath = resolveInside(memoryDir2(), write.path);
|
|
14563
14757
|
const notes = [];
|
|
14564
14758
|
if (!fullPath) {
|
|
14565
|
-
return { written: false, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
|
|
14759
|
+
return { written: false, syncedHash: null, notes: [`${String(write.path)}: rejected \u2014 path escapes the memory directory`] };
|
|
14566
14760
|
}
|
|
14567
14761
|
let content = write.content;
|
|
14568
14762
|
if (treePaths && treePaths.length > 0) {
|
|
@@ -14575,18 +14769,22 @@ async function applyOneWrite(write, treePaths) {
|
|
|
14575
14769
|
if ((0, import_node_fs14.existsSync)(fullPath)) {
|
|
14576
14770
|
let existing = "";
|
|
14577
14771
|
try {
|
|
14578
|
-
existing = await (0,
|
|
14772
|
+
existing = await (0, import_promises9.readFile)(fullPath, "utf-8");
|
|
14579
14773
|
} catch {
|
|
14580
14774
|
}
|
|
14581
|
-
if (existing === content) return { written: false, notes };
|
|
14775
|
+
if (existing === content) return { written: false, syncedHash: hashContent(content), notes };
|
|
14582
14776
|
if (existing !== "") {
|
|
14583
|
-
|
|
14584
|
-
|
|
14777
|
+
const served = lastServed?.get(write.path);
|
|
14778
|
+
if (served == null || served !== hashContent(existing)) {
|
|
14779
|
+
notes.push(`${write.path}: kept local edit (differs from server)`);
|
|
14780
|
+
return { written: false, syncedHash: null, notes };
|
|
14781
|
+
}
|
|
14782
|
+
notes.push(`${write.path}: updated from server (untouched here since the server delivered it)`);
|
|
14585
14783
|
}
|
|
14586
14784
|
}
|
|
14587
|
-
await (0,
|
|
14588
|
-
await (0,
|
|
14589
|
-
return { written: true, notes };
|
|
14785
|
+
await (0, import_promises9.mkdir)((0, import_node_path13.dirname)(fullPath), { recursive: true });
|
|
14786
|
+
await (0, import_promises9.writeFile)(fullPath, content);
|
|
14787
|
+
return { written: true, syncedHash: hashContent(content), notes };
|
|
14590
14788
|
}
|
|
14591
14789
|
function groundFileGlobs(content, treePaths) {
|
|
14592
14790
|
const globs = pickFrontmatterArray(content, "file_globs");
|
|
@@ -14628,7 +14826,7 @@ async function regenerateIndex() {
|
|
|
14628
14826
|
const domainDir = (0, import_node_path13.join)(memoryDir2(), domain);
|
|
14629
14827
|
if (!(0, import_node_fs14.existsSync)(domainDir)) continue;
|
|
14630
14828
|
try {
|
|
14631
|
-
const files = await (0,
|
|
14829
|
+
const files = await (0, import_promises9.readdir)(domainDir);
|
|
14632
14830
|
const mdFiles = files.filter((f) => f.endsWith(".md"));
|
|
14633
14831
|
if (mdFiles.length === 0) continue;
|
|
14634
14832
|
lines.push(`## ${domain}/ (${mdFiles.length})`);
|
|
@@ -14636,7 +14834,7 @@ async function regenerateIndex() {
|
|
|
14636
14834
|
for (const file of mdFiles.sort()) {
|
|
14637
14835
|
const slug = file.replace(/\.md$/, "");
|
|
14638
14836
|
try {
|
|
14639
|
-
const content = await (0,
|
|
14837
|
+
const content = await (0, import_promises9.readFile)((0, import_node_path13.join)(domainDir, file), "utf-8");
|
|
14640
14838
|
const title = pickFrontmatter(content, "title") ?? slug;
|
|
14641
14839
|
const kind = pickFrontmatter(content, "kind") ?? "-";
|
|
14642
14840
|
const confidence = pickFrontmatter(content, "confidence");
|
|
@@ -14663,11 +14861,11 @@ async function regenerateIndex() {
|
|
|
14663
14861
|
const indexPath = (0, import_node_path13.join)(memoryDir2(), "index.md");
|
|
14664
14862
|
let existing = null;
|
|
14665
14863
|
try {
|
|
14666
|
-
existing = await (0,
|
|
14864
|
+
existing = await (0, import_promises9.readFile)(indexPath, "utf-8");
|
|
14667
14865
|
} catch {
|
|
14668
14866
|
}
|
|
14669
14867
|
if (existing === next) return;
|
|
14670
|
-
await (0,
|
|
14868
|
+
await (0, import_promises9.writeFile)(indexPath, next);
|
|
14671
14869
|
}
|
|
14672
14870
|
function pickFrontmatter(content, key) {
|
|
14673
14871
|
const re = new RegExp(`^${key}:\\s*"?([^"\\n]+?)"?\\s*$`, "m");
|
|
@@ -14694,6 +14892,169 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
|
|
|
14694
14892
|
var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
|
|
14695
14893
|
var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
|
|
14696
14894
|
var CLAUDE_MD_PROSE = [
|
|
14895
|
+
"## Project Memory",
|
|
14896
|
+
"",
|
|
14897
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
14898
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
14899
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
14900
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
14901
|
+
"",
|
|
14902
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
14903
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
14904
|
+
"",
|
|
14905
|
+
"Knowledge the organization's other repositories learned is mirrored outside the",
|
|
14906
|
+
"repo at `~/.verity/orgs/<host>/<owner>/memory/` (`verity memory org` lists it).",
|
|
14907
|
+
"Each claim says where it came from; one that is wrong here is demoted for",
|
|
14908
|
+
'everyone with `verity memory demote <id> --reason "\u2026"`.',
|
|
14909
|
+
"",
|
|
14910
|
+
"## Quality gate: accepted risks",
|
|
14911
|
+
"",
|
|
14912
|
+
"When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
|
|
14913
|
+
'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
|
|
14914
|
+
"a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
|
|
14915
|
+
"the user saying so in this conversation. The --reason must cite that source.",
|
|
14916
|
+
"",
|
|
14917
|
+
"Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
|
|
14918
|
+
"binds to the file's current bytes and voids automatically when the file changes,",
|
|
14919
|
+
"and every waive is recorded in the run ledger. For a pattern-level false positive",
|
|
14920
|
+
"use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
|
|
14921
|
+
"",
|
|
14922
|
+
"## Post-task reflection",
|
|
14923
|
+
"",
|
|
14924
|
+
`When a task is complete (you've created a PR, the user says "done" or "ship it",`,
|
|
14925
|
+
"or the work is clearly finished), **draft the reflection yourself** \u2014 1\u20133",
|
|
14926
|
+
"concrete things worth remembering (a decision, a gotcha, or a pattern), each",
|
|
14927
|
+
"cited with the files / PR / commands / error-signatures it came from. Skip",
|
|
14928
|
+
"entirely if nothing non-obvious happened \u2014 that judgement is the ONLY filter,",
|
|
14929
|
+
"because nothing reviews the reflection before it lands.",
|
|
14930
|
+
"",
|
|
14931
|
+
"Then record it straight away. There is no confirm step, in any environment:",
|
|
14932
|
+
"",
|
|
14933
|
+
"```bash",
|
|
14934
|
+
'verity reflect --user-input "<your draft>" --kind <kind>',
|
|
14935
|
+
"```",
|
|
14936
|
+
"",
|
|
14937
|
+
"Add `--confirmed` ONLY when the user authored or dictated the words. Without",
|
|
14938
|
+
"it the node is stored as `source: agent` \u2014 Verity thought this, nobody checked",
|
|
14939
|
+
"it. With it, `source: user` at full confidence \u2014 a person stands behind this.",
|
|
14940
|
+
"Never claim the second for your own draft, however good it is.",
|
|
14941
|
+
"",
|
|
14942
|
+
"**Name the files in the text.** Verity scopes the reflection to the paths it",
|
|
14943
|
+
"cites, and a reflection that names no file in this repo is never retrieved for",
|
|
14944
|
+
"a later review \u2014 it is recorded and then invisible. The command says so when it",
|
|
14945
|
+
'happens; `--file-globs "<path or glob>"` is the fix when the prose cannot carry',
|
|
14946
|
+
"the paths.",
|
|
14947
|
+
"",
|
|
14948
|
+
"Then tell the user, in one line, what you recorded and where: the command",
|
|
14949
|
+
"prints the node id, the path under `.verity/memory/`, and a dashboard link.",
|
|
14950
|
+
"They did not agree to it in advance, so say it happened \u2014 editing or deleting",
|
|
14951
|
+
"that file is how they correct it.",
|
|
14952
|
+
"",
|
|
14953
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
14954
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
14955
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
14956
|
+
"",
|
|
14957
|
+
"## Housekeeping Turns",
|
|
14958
|
+
"",
|
|
14959
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
14960
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
14961
|
+
"",
|
|
14962
|
+
"```bash",
|
|
14963
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
14964
|
+
"```",
|
|
14965
|
+
"",
|
|
14966
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
14967
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
14968
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
14969
|
+
"",
|
|
14970
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
14971
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
14972
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
14973
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
14974
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
14975
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
14976
|
+
"its reason."
|
|
14977
|
+
].join("\n");
|
|
14978
|
+
var CLAUDE_MD_PROSE_PRE_ORG = [
|
|
14979
|
+
"## Project Memory",
|
|
14980
|
+
"",
|
|
14981
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
14982
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
14983
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
14984
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
14985
|
+
"",
|
|
14986
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
14987
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
14988
|
+
"",
|
|
14989
|
+
"## Quality gate: accepted risks",
|
|
14990
|
+
"",
|
|
14991
|
+
"When the Verity pre-commit/pre-push gate FAILs, fix the findings \u2014 that is the",
|
|
14992
|
+
'default. Use `verity waive <pattern-id> --file <path> --reason "\u2026"` ONLY to relay',
|
|
14993
|
+
"a risk a human has explicitly accepted: a named code-review finding, an ADR, or",
|
|
14994
|
+
"the user saying so in this conversation. The --reason must cite that source.",
|
|
14995
|
+
"",
|
|
14996
|
+
"Never waive on your own judgment, to get past a block, or pre-emptively. A waive",
|
|
14997
|
+
"binds to the file's current bytes and voids automatically when the file changes,",
|
|
14998
|
+
"and every waive is recorded in the run ledger. For a pattern-level false positive",
|
|
14999
|
+
"use `verity feedback finding <run-id> <pattern-id> false_positive` instead.",
|
|
15000
|
+
"",
|
|
15001
|
+
"## Post-task reflection",
|
|
15002
|
+
"",
|
|
15003
|
+
`When a task is complete (you've created a PR, the user says "done" or "ship it",`,
|
|
15004
|
+
"or the work is clearly finished), **draft the reflection yourself** \u2014 1\u20133",
|
|
15005
|
+
"concrete things worth remembering (a decision, a gotcha, or a pattern), each",
|
|
15006
|
+
"cited with the files / PR / commands / error-signatures it came from. Skip",
|
|
15007
|
+
"entirely if nothing non-obvious happened \u2014 that judgement is the ONLY filter,",
|
|
15008
|
+
"because nothing reviews the reflection before it lands.",
|
|
15009
|
+
"",
|
|
15010
|
+
"Then record it straight away. There is no confirm step, in any environment:",
|
|
15011
|
+
"",
|
|
15012
|
+
"```bash",
|
|
15013
|
+
'verity reflect --user-input "<your draft>" --kind <kind>',
|
|
15014
|
+
"```",
|
|
15015
|
+
"",
|
|
15016
|
+
"Add `--confirmed` ONLY when the user authored or dictated the words. Without",
|
|
15017
|
+
"it the node is stored as `source: agent` \u2014 Verity thought this, nobody checked",
|
|
15018
|
+
"it. With it, `source: user` at full confidence \u2014 a person stands behind this.",
|
|
15019
|
+
"Never claim the second for your own draft, however good it is.",
|
|
15020
|
+
"",
|
|
15021
|
+
"**Name the files in the text.** Verity scopes the reflection to the paths it",
|
|
15022
|
+
"cites, and a reflection that names no file in this repo is never retrieved for",
|
|
15023
|
+
"a later review \u2014 it is recorded and then invisible. The command says so when it",
|
|
15024
|
+
'happens; `--file-globs "<path or glob>"` is the fix when the prose cannot carry',
|
|
15025
|
+
"the paths.",
|
|
15026
|
+
"",
|
|
15027
|
+
"Then tell the user, in one line, what you recorded and where: the command",
|
|
15028
|
+
"prints the node id, the path under `.verity/memory/`, and a dashboard link.",
|
|
15029
|
+
"They did not agree to it in advance, so say it happened \u2014 editing or deleting",
|
|
15030
|
+
"that file is how they correct it.",
|
|
15031
|
+
"",
|
|
15032
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
15033
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
15034
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
15035
|
+
"",
|
|
15036
|
+
"## Housekeeping Turns",
|
|
15037
|
+
"",
|
|
15038
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
15039
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
15040
|
+
"",
|
|
15041
|
+
"```bash",
|
|
15042
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
15043
|
+
"```",
|
|
15044
|
+
"",
|
|
15045
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
15046
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
15047
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
15048
|
+
"",
|
|
15049
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
15050
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
15051
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
15052
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
15053
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
15054
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
15055
|
+
"its reason."
|
|
15056
|
+
].join("\n");
|
|
15057
|
+
var CLAUDE_MD_PROSE_PRE_AUTORECORD = [
|
|
14697
15058
|
"## Project Memory",
|
|
14698
15059
|
"",
|
|
14699
15060
|
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
@@ -14897,7 +15258,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
|
14897
15258
|
const claudeMdPath = (0, import_node_path13.join)(cwd, "CLAUDE.md");
|
|
14898
15259
|
let existing = "";
|
|
14899
15260
|
if ((0, import_node_fs14.existsSync)(claudeMdPath)) {
|
|
14900
|
-
existing = await (0,
|
|
15261
|
+
existing = await (0, import_promises9.readFile)(claudeMdPath, "utf-8");
|
|
14901
15262
|
}
|
|
14902
15263
|
let startTag = CLAUDE_MD_START;
|
|
14903
15264
|
let endTag = CLAUDE_MD_END;
|
|
@@ -14953,7 +15314,7 @@ async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
|
14953
15314
|
next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
|
|
14954
15315
|
}
|
|
14955
15316
|
if (next === existing) return;
|
|
14956
|
-
await (0,
|
|
15317
|
+
await (0, import_promises9.writeFile)(claudeMdPath, next);
|
|
14957
15318
|
}
|
|
14958
15319
|
function extractPreserveContent(interior) {
|
|
14959
15320
|
for (const [start, end] of [
|
|
@@ -14972,6 +15333,8 @@ function stripKnownProse(interior) {
|
|
|
14972
15333
|
const trimmed = interior.replace(/^\n+/, "");
|
|
14973
15334
|
for (const prose of [
|
|
14974
15335
|
CLAUDE_MD_PROSE,
|
|
15336
|
+
CLAUDE_MD_PROSE_PRE_ORG,
|
|
15337
|
+
CLAUDE_MD_PROSE_PRE_AUTORECORD,
|
|
14975
15338
|
CLAUDE_MD_PROSE_PRE_REFLECT,
|
|
14976
15339
|
CLAUDE_MD_PROSE_PRE_WAIVE,
|
|
14977
15340
|
CLAUDE_MD_PROSE_PRE_IGNORE,
|
|
@@ -16591,21 +16954,112 @@ async function fireClassify(prompt, sessionId, globals) {
|
|
|
16591
16954
|
|
|
16592
16955
|
// src/commands/lifecycle.ts
|
|
16593
16956
|
var import_node_fs23 = require("node:fs");
|
|
16594
|
-
var
|
|
16957
|
+
var import_node_path21 = require("node:path");
|
|
16595
16958
|
|
|
16596
16959
|
// src/lib/baseline.ts
|
|
16597
16960
|
var import_node_fs22 = require("node:fs");
|
|
16598
|
-
var
|
|
16961
|
+
var import_node_path20 = require("node:path");
|
|
16599
16962
|
var import_node_crypto9 = require("node:crypto");
|
|
16600
16963
|
|
|
16601
16964
|
// src/lib/snapshot.ts
|
|
16602
16965
|
var import_node_fs21 = require("node:fs");
|
|
16603
|
-
var
|
|
16966
|
+
var import_node_path19 = require("node:path");
|
|
16604
16967
|
var import_node_child_process7 = require("node:child_process");
|
|
16605
16968
|
|
|
16606
16969
|
// src/lib/files.ts
|
|
16970
|
+
var import_node_path18 = require("node:path");
|
|
16971
|
+
|
|
16972
|
+
// src/lib/safe-read.ts
|
|
16607
16973
|
var import_node_fs20 = require("node:fs");
|
|
16608
16974
|
var import_node_path17 = require("node:path");
|
|
16975
|
+
var FLAGS = import_node_fs20.constants;
|
|
16976
|
+
var NOFOLLOW = FLAGS.O_NOFOLLOW;
|
|
16977
|
+
var NONBLOCK = FLAGS.O_NONBLOCK;
|
|
16978
|
+
function closeOpened(fd) {
|
|
16979
|
+
try {
|
|
16980
|
+
(0, import_node_fs20.closeSync)(fd);
|
|
16981
|
+
} catch {
|
|
16982
|
+
}
|
|
16983
|
+
}
|
|
16984
|
+
function isInsideRoot(realRoot, realPath) {
|
|
16985
|
+
return realPath === realRoot || realPath.startsWith(realRoot.endsWith(import_node_path17.sep) ? realRoot : realRoot + import_node_path17.sep);
|
|
16986
|
+
}
|
|
16987
|
+
function sameOpenedFile(opened, current, nofollowAvailable) {
|
|
16988
|
+
if (!nofollowAvailable && (opened.ino === 0n || current.ino === 0n)) return false;
|
|
16989
|
+
return opened.ino === current.ino && opened.dev === current.dev;
|
|
16990
|
+
}
|
|
16991
|
+
function openRegularInRoot(root, path) {
|
|
16992
|
+
const full = (0, import_node_path17.isAbsolute)(path) ? path : (0, import_node_path17.join)(root, path);
|
|
16993
|
+
let fd;
|
|
16994
|
+
try {
|
|
16995
|
+
fd = (0, import_node_fs20.openSync)(full, import_node_fs20.constants.O_RDONLY | (NOFOLLOW ?? 0) | (NONBLOCK ?? 0));
|
|
16996
|
+
} catch (err) {
|
|
16997
|
+
const code = err.code;
|
|
16998
|
+
if (code === "ELOOP" || code === "EMLINK" || code === "EFTYPE") return { ok: false, reason: "symlink" };
|
|
16999
|
+
if (code === "ENOENT" || code === "ENOTDIR") return { ok: false, reason: "missing" };
|
|
17000
|
+
return { ok: false, reason: "unreadable" };
|
|
17001
|
+
}
|
|
17002
|
+
try {
|
|
17003
|
+
const opened = (0, import_node_fs20.fstatSync)(fd, { bigint: true });
|
|
17004
|
+
if (!opened.isFile()) {
|
|
17005
|
+
closeOpened(fd);
|
|
17006
|
+
return { ok: false, reason: "not-regular" };
|
|
17007
|
+
}
|
|
17008
|
+
const realRoot = import_node_fs20.realpathSync.native(root);
|
|
17009
|
+
const realPath = import_node_fs20.realpathSync.native(full);
|
|
17010
|
+
if (!isInsideRoot(realRoot, realPath)) {
|
|
17011
|
+
closeOpened(fd);
|
|
17012
|
+
return { ok: false, reason: "outside-root" };
|
|
17013
|
+
}
|
|
17014
|
+
const current = (0, import_node_fs20.statSync)(realPath, { bigint: true });
|
|
17015
|
+
if (!sameOpenedFile(opened, current, NOFOLLOW !== void 0)) {
|
|
17016
|
+
closeOpened(fd);
|
|
17017
|
+
return { ok: false, reason: NOFOLLOW === void 0 ? "unreadable" : "outside-root" };
|
|
17018
|
+
}
|
|
17019
|
+
return { ok: true, fd, size: Number(opened.size), mtimeMs: Number(opened.mtimeMs) };
|
|
17020
|
+
} catch {
|
|
17021
|
+
closeOpened(fd);
|
|
17022
|
+
return { ok: false, reason: "unreadable" };
|
|
17023
|
+
}
|
|
17024
|
+
}
|
|
17025
|
+
function statRegularInRoot(root, path) {
|
|
17026
|
+
const opened = openRegularInRoot(root, path);
|
|
17027
|
+
if (!opened.ok) return opened;
|
|
17028
|
+
closeOpened(opened.fd);
|
|
17029
|
+
return { ok: true, size: opened.size, mtimeMs: opened.mtimeMs };
|
|
17030
|
+
}
|
|
17031
|
+
function readOpened(fd, size) {
|
|
17032
|
+
try {
|
|
17033
|
+
const buffer = Buffer.alloc(size);
|
|
17034
|
+
let offset = 0;
|
|
17035
|
+
while (offset < size) {
|
|
17036
|
+
const n = (0, import_node_fs20.readSync)(fd, buffer, offset, size - offset, offset);
|
|
17037
|
+
if (n === 0) break;
|
|
17038
|
+
offset += n;
|
|
17039
|
+
}
|
|
17040
|
+
return buffer.subarray(0, offset);
|
|
17041
|
+
} finally {
|
|
17042
|
+
closeOpened(fd);
|
|
17043
|
+
}
|
|
17044
|
+
}
|
|
17045
|
+
function readOpenedUtf8(fd, size) {
|
|
17046
|
+
return readOpened(fd, size).toString("utf-8");
|
|
17047
|
+
}
|
|
17048
|
+
function readRegularFileInRoot(root, path, maxBytes) {
|
|
17049
|
+
const opened = openRegularInRoot(root, path);
|
|
17050
|
+
if (!opened.ok) return opened;
|
|
17051
|
+
if (opened.size > maxBytes) {
|
|
17052
|
+
closeOpened(opened.fd);
|
|
17053
|
+
return { ok: false, reason: "too-large", size: opened.size };
|
|
17054
|
+
}
|
|
17055
|
+
try {
|
|
17056
|
+
return { ok: true, content: readOpenedUtf8(opened.fd, opened.size), size: opened.size };
|
|
17057
|
+
} catch {
|
|
17058
|
+
return { ok: false, reason: "unreadable" };
|
|
17059
|
+
}
|
|
17060
|
+
}
|
|
17061
|
+
|
|
17062
|
+
// src/lib/files.ts
|
|
16609
17063
|
var LANG_MAP = {
|
|
16610
17064
|
// Analyzable (static analysis + Gemini)
|
|
16611
17065
|
ts: "typescript",
|
|
@@ -16673,19 +17127,14 @@ var LANG_MAP = {
|
|
|
16673
17127
|
mk: "make"
|
|
16674
17128
|
};
|
|
16675
17129
|
function detectLanguage(filepath) {
|
|
16676
|
-
const ext = (0,
|
|
17130
|
+
const ext = (0, import_node_path18.extname)(filepath).slice(1);
|
|
16677
17131
|
return LANG_MAP[ext] ?? ext;
|
|
16678
17132
|
}
|
|
16679
17133
|
function sortByMtime(files) {
|
|
16680
17134
|
const withMtime = files.map((f) => {
|
|
16681
|
-
const
|
|
16682
|
-
if (
|
|
16683
|
-
|
|
16684
|
-
const stat3 = (0, import_node_fs20.statSync)(resolved);
|
|
16685
|
-
return { path: f, resolved, mtime: stat3.mtimeMs };
|
|
16686
|
-
} catch {
|
|
16687
|
-
return null;
|
|
16688
|
-
}
|
|
17135
|
+
const stat3 = statRegularInRoot(process.cwd(), f);
|
|
17136
|
+
if (stat3.ok) return { path: f, mtime: stat3.mtimeMs };
|
|
17137
|
+
return stat3.reason === "missing" ? null : { path: f, mtime: Number.NEGATIVE_INFINITY };
|
|
16689
17138
|
}).filter((x) => x !== null);
|
|
16690
17139
|
withMtime.sort((a, b) => b.mtime - a.mtime);
|
|
16691
17140
|
return withMtime.map((x) => x.path);
|
|
@@ -16707,23 +17156,22 @@ function collectCodeDelta(files, opts) {
|
|
|
16707
17156
|
droppedPaths.push(filepath);
|
|
16708
17157
|
continue;
|
|
16709
17158
|
}
|
|
16710
|
-
const
|
|
16711
|
-
if (!
|
|
16712
|
-
exclude(
|
|
16713
|
-
|
|
16714
|
-
|
|
16715
|
-
|
|
16716
|
-
try {
|
|
16717
|
-
size = (0, import_node_fs20.statSync)(resolved).size;
|
|
16718
|
-
} catch {
|
|
16719
|
-
exclude(filepath, "not-stattable");
|
|
17159
|
+
const opened = openRegularInRoot(process.cwd(), filepath);
|
|
17160
|
+
if (!opened.ok) {
|
|
17161
|
+
exclude(
|
|
17162
|
+
filepath,
|
|
17163
|
+
opened.reason === "symlink" || opened.reason === "outside-root" ? "symlink" : opened.reason === "missing" ? "path-not-resolvable" : "not-stattable"
|
|
17164
|
+
);
|
|
16720
17165
|
continue;
|
|
16721
17166
|
}
|
|
17167
|
+
const size = opened.size;
|
|
16722
17168
|
if (size > maxFileBytes) {
|
|
17169
|
+
closeOpened(opened.fd);
|
|
16723
17170
|
exclude(filepath, `over-file-size-limit-${maxFileBytes}b`);
|
|
16724
17171
|
continue;
|
|
16725
17172
|
}
|
|
16726
17173
|
if (totalSize + size > maxTotalBytes) {
|
|
17174
|
+
closeOpened(opened.fd);
|
|
16727
17175
|
truncationReason ??= "max_total_bytes";
|
|
16728
17176
|
const idx = sorted.indexOf(filepath);
|
|
16729
17177
|
droppedPaths.push(...sorted.slice(idx));
|
|
@@ -16731,7 +17179,7 @@ function collectCodeDelta(files, opts) {
|
|
|
16731
17179
|
}
|
|
16732
17180
|
let content;
|
|
16733
17181
|
try {
|
|
16734
|
-
content = (
|
|
17182
|
+
content = readOpenedUtf8(opened.fd, size);
|
|
16735
17183
|
} catch {
|
|
16736
17184
|
exclude(filepath, "not-readable");
|
|
16737
17185
|
continue;
|
|
@@ -16776,7 +17224,7 @@ function generateSnapshotDiffs(files) {
|
|
|
16776
17224
|
const diffs = [];
|
|
16777
17225
|
for (const file of files) {
|
|
16778
17226
|
if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
|
|
16779
|
-
const snapshotPath = (0,
|
|
17227
|
+
const snapshotPath = (0, import_node_path19.join)(SNAPSHOT_DIR, file.path);
|
|
16780
17228
|
const language = file.language ?? detectLanguage(file.path);
|
|
16781
17229
|
if ((0, import_node_fs21.existsSync)(snapshotPath)) {
|
|
16782
17230
|
const oldContent = (0, import_node_fs21.readFileSync)(snapshotPath, "utf-8");
|
|
@@ -16804,16 +17252,16 @@ function saveSnapshots(files) {
|
|
|
16804
17252
|
const snapshotPaths = /* @__PURE__ */ new Set();
|
|
16805
17253
|
for (const file of files) {
|
|
16806
17254
|
if (!resolveInside(SNAPSHOT_DIR, file.path)) continue;
|
|
16807
|
-
const snapshotPath = (0,
|
|
17255
|
+
const snapshotPath = (0, import_node_path19.join)(SNAPSHOT_DIR, file.path);
|
|
16808
17256
|
snapshotPaths.add(snapshotPath);
|
|
16809
|
-
(0, import_node_fs21.mkdirSync)((0,
|
|
17257
|
+
(0, import_node_fs21.mkdirSync)((0, import_node_path19.dirname)(snapshotPath), { recursive: true });
|
|
16810
17258
|
(0, import_node_fs21.writeFileSync)(snapshotPath, file.content);
|
|
16811
17259
|
}
|
|
16812
17260
|
cleanStaleSnapshots(SNAPSHOT_DIR, snapshotPaths);
|
|
16813
17261
|
}
|
|
16814
17262
|
function computeDiff(oldContent, newContent, filePath) {
|
|
16815
|
-
const tmpOld = (0,
|
|
16816
|
-
const tmpNew = (0,
|
|
17263
|
+
const tmpOld = (0, import_node_path19.join)(SNAPSHOT_DIR, ".diff-old.tmp");
|
|
17264
|
+
const tmpNew = (0, import_node_path19.join)(SNAPSHOT_DIR, ".diff-new.tmp");
|
|
16817
17265
|
try {
|
|
16818
17266
|
(0, import_node_fs21.mkdirSync)(SNAPSHOT_DIR, { recursive: true });
|
|
16819
17267
|
(0, import_node_fs21.writeFileSync)(tmpOld, oldContent);
|
|
@@ -16846,7 +17294,7 @@ function cleanStaleSnapshots(dir, keepSet) {
|
|
|
16846
17294
|
const entries = (0, import_node_fs21.readdirSync)(dir, { withFileTypes: true });
|
|
16847
17295
|
for (const entry of entries) {
|
|
16848
17296
|
if (dir === SNAPSHOT_DIR && (entry.name === ".diff-old.tmp" || entry.name === ".diff-new.tmp")) continue;
|
|
16849
|
-
const fullPath = (0,
|
|
17297
|
+
const fullPath = (0, import_node_path19.join)(dir, entry.name);
|
|
16850
17298
|
if (entry.isDirectory()) {
|
|
16851
17299
|
cleanStaleSnapshots(fullPath, keepSet);
|
|
16852
17300
|
try {
|
|
@@ -16875,13 +17323,13 @@ function sessionKey(sessionId) {
|
|
|
16875
17323
|
return (0, import_node_crypto9.createHash)("sha256").update(sessionId).digest("hex").slice(0, 16);
|
|
16876
17324
|
}
|
|
16877
17325
|
function sessionDir(key) {
|
|
16878
|
-
return (0,
|
|
17326
|
+
return (0, import_node_path20.join)(projectPath(BASELINE_DIR), key);
|
|
16879
17327
|
}
|
|
16880
17328
|
function manifestPath(dir) {
|
|
16881
|
-
return (0,
|
|
17329
|
+
return (0, import_node_path20.join)(dir, "manifest.json");
|
|
16882
17330
|
}
|
|
16883
17331
|
function mirrorPath(dir, repoRelPath) {
|
|
16884
|
-
return (0,
|
|
17332
|
+
return (0, import_node_path20.join)(dir, "files", repoRelPath);
|
|
16885
17333
|
}
|
|
16886
17334
|
var CARRY_FILE = `${BASELINE_DIR}/.carry`;
|
|
16887
17335
|
var CARRY_WINDOW_MS = 12e4;
|
|
@@ -16943,7 +17391,7 @@ function captureBaseline(opts = {}) {
|
|
|
16943
17391
|
(0, import_node_fs22.rmSync)(dir, { recursive: true, force: true });
|
|
16944
17392
|
} catch {
|
|
16945
17393
|
}
|
|
16946
|
-
const filesDir = (0,
|
|
17394
|
+
const filesDir = (0, import_node_path20.join)(dir, "files");
|
|
16947
17395
|
const mirrored = [];
|
|
16948
17396
|
try {
|
|
16949
17397
|
(0, import_node_fs22.mkdirSync)(filesDir, { recursive: true });
|
|
@@ -16953,7 +17401,7 @@ function captureBaseline(opts = {}) {
|
|
|
16953
17401
|
if (content === null) continue;
|
|
16954
17402
|
const dest = mirrorPath(dir, p);
|
|
16955
17403
|
try {
|
|
16956
|
-
(0, import_node_fs22.mkdirSync)((0,
|
|
17404
|
+
(0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(dest), { recursive: true });
|
|
16957
17405
|
(0, import_node_fs22.writeFileSync)(dest, content);
|
|
16958
17406
|
mirrored.push(p);
|
|
16959
17407
|
} catch {
|
|
@@ -17061,7 +17509,7 @@ function absorbIntoBaseline(paths, sessionId) {
|
|
|
17061
17509
|
const content = safeReadForMirror(projectPath(p));
|
|
17062
17510
|
if (content === null) continue;
|
|
17063
17511
|
const dest = mirrorPath(dir, p);
|
|
17064
|
-
(0, import_node_fs22.mkdirSync)((0,
|
|
17512
|
+
(0, import_node_fs22.mkdirSync)((0, import_node_path20.dirname)(dest), { recursive: true });
|
|
17065
17513
|
(0, import_node_fs22.writeFileSync)(dest, content);
|
|
17066
17514
|
dirty.add(p);
|
|
17067
17515
|
adopted++;
|
|
@@ -17109,7 +17557,7 @@ function pruneOldBaselines() {
|
|
|
17109
17557
|
}
|
|
17110
17558
|
const now = Date.now();
|
|
17111
17559
|
for (const name of entries) {
|
|
17112
|
-
const dir = (0,
|
|
17560
|
+
const dir = (0, import_node_path20.join)(root, name);
|
|
17113
17561
|
const manifest = readManifest(dir);
|
|
17114
17562
|
if (!manifest) {
|
|
17115
17563
|
try {
|
|
@@ -17297,7 +17745,7 @@ function buildCompactionContext(session) {
|
|
|
17297
17745
|
commitsSince: commitsSincePaths(watermark, (state.authored ?? []).map((a) => a.path)),
|
|
17298
17746
|
readFileLines: (file) => {
|
|
17299
17747
|
try {
|
|
17300
|
-
const abs = (0,
|
|
17748
|
+
const abs = (0, import_node_path21.join)(root, file);
|
|
17301
17749
|
return (0, import_node_fs23.existsSync)(abs) ? (0, import_node_fs23.readFileSync)(abs, "utf8").split("\n") : null;
|
|
17302
17750
|
} catch {
|
|
17303
17751
|
return null;
|
|
@@ -17334,17 +17782,17 @@ async function readHookStdin() {
|
|
|
17334
17782
|
try {
|
|
17335
17783
|
if (process.stdin.isTTY) return {};
|
|
17336
17784
|
const chunks = [];
|
|
17337
|
-
const timeout = new Promise((
|
|
17338
|
-
const read = new Promise((
|
|
17785
|
+
const timeout = new Promise((resolve6) => setTimeout(() => resolve6({}), 500));
|
|
17786
|
+
const read = new Promise((resolve6) => {
|
|
17339
17787
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
17340
17788
|
process.stdin.on("end", () => {
|
|
17341
17789
|
try {
|
|
17342
|
-
|
|
17790
|
+
resolve6(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
|
|
17343
17791
|
} catch {
|
|
17344
|
-
|
|
17792
|
+
resolve6({});
|
|
17345
17793
|
}
|
|
17346
17794
|
});
|
|
17347
|
-
process.stdin.on("error", () =>
|
|
17795
|
+
process.stdin.on("error", () => resolve6({}));
|
|
17348
17796
|
process.stdin.resume();
|
|
17349
17797
|
});
|
|
17350
17798
|
return await Promise.race([read, timeout]);
|
|
@@ -17354,25 +17802,25 @@ async function readHookStdin() {
|
|
|
17354
17802
|
}
|
|
17355
17803
|
|
|
17356
17804
|
// src/commands/standard.ts
|
|
17357
|
-
var
|
|
17805
|
+
var import_promises13 = require("node:fs/promises");
|
|
17358
17806
|
var import_node_fs30 = require("node:fs");
|
|
17359
17807
|
var import_yaml3 = __toESM(require_dist());
|
|
17360
17808
|
|
|
17361
17809
|
// src/lib/synthesize.ts
|
|
17362
17810
|
var import_node_child_process9 = require("node:child_process");
|
|
17363
17811
|
var import_node_fs26 = require("node:fs");
|
|
17364
|
-
var
|
|
17365
|
-
var
|
|
17812
|
+
var import_promises10 = require("node:fs/promises");
|
|
17813
|
+
var import_node_path24 = require("node:path");
|
|
17366
17814
|
var import_yaml = __toESM(require_dist());
|
|
17367
17815
|
|
|
17368
17816
|
// src/lib/data-dir.ts
|
|
17369
17817
|
var import_node_fs24 = require("node:fs");
|
|
17370
|
-
var
|
|
17818
|
+
var import_node_path22 = require("node:path");
|
|
17371
17819
|
function resolveDataDir() {
|
|
17372
17820
|
const candidates2 = [
|
|
17373
|
-
(0,
|
|
17821
|
+
(0, import_node_path22.join)(__dirname, "..", "data"),
|
|
17374
17822
|
// installed: node_modules/@codacy/verity-cli/data
|
|
17375
|
-
(0,
|
|
17823
|
+
(0, import_node_path22.join)(__dirname, "..", "..", "data"),
|
|
17376
17824
|
// edge case: nested resolution
|
|
17377
17825
|
// THE COMMITTED SOURCE, for a source checkout that has not been built.
|
|
17378
17826
|
// cli/data/skills/ is a BUILD ARTIFACT (scripts/build.js copies client/skills
|
|
@@ -17381,14 +17829,14 @@ function resolveDataDir() {
|
|
|
17381
17829
|
// without this the synthesizer throws "Could not find Verity skill data"
|
|
17382
17830
|
// for every test and every `verity` run from source. Resolved from this
|
|
17383
17831
|
// module's own location, never the cwd: see the warning below.
|
|
17384
|
-
(0,
|
|
17832
|
+
(0, import_node_path22.join)(__dirname, "..", "..", "client"),
|
|
17385
17833
|
// bundled: cli/bin/ → ../../client
|
|
17386
|
-
(0,
|
|
17834
|
+
(0, import_node_path22.join)(__dirname, "..", "..", "..", "client"),
|
|
17387
17835
|
// tsx: cli/src/lib/ → ../../../client
|
|
17388
17836
|
...process.env.VERITY_DEV_DATA_DIR ? [process.env.VERITY_DEV_DATA_DIR] : []
|
|
17389
17837
|
];
|
|
17390
17838
|
for (const candidate of candidates2) {
|
|
17391
|
-
if ((0, import_node_fs24.existsSync)((0,
|
|
17839
|
+
if ((0, import_node_fs24.existsSync)((0, import_node_path22.join)(candidate, "skills"))) {
|
|
17392
17840
|
return candidate;
|
|
17393
17841
|
}
|
|
17394
17842
|
}
|
|
@@ -17397,13 +17845,13 @@ function resolveDataDir() {
|
|
|
17397
17845
|
);
|
|
17398
17846
|
}
|
|
17399
17847
|
function setupDataPath(file) {
|
|
17400
|
-
return (0,
|
|
17848
|
+
return (0, import_node_path22.join)(resolveDataDir(), "skills", "verity-setup", file);
|
|
17401
17849
|
}
|
|
17402
17850
|
|
|
17403
17851
|
// src/lib/detect.ts
|
|
17404
17852
|
var import_node_child_process8 = require("node:child_process");
|
|
17405
17853
|
var import_node_fs25 = require("node:fs");
|
|
17406
|
-
var
|
|
17854
|
+
var import_node_path23 = require("node:path");
|
|
17407
17855
|
var TOOLED_LANGUAGES = /* @__PURE__ */ new Set([
|
|
17408
17856
|
"typescript",
|
|
17409
17857
|
"javascript",
|
|
@@ -17467,18 +17915,18 @@ function walk(root) {
|
|
|
17467
17915
|
for (const entry of entries) {
|
|
17468
17916
|
if (found.length >= WALK_MAX_FILES) return;
|
|
17469
17917
|
if (IGNORED_SEGMENTS.includes(entry.name)) continue;
|
|
17470
|
-
const full = (0,
|
|
17918
|
+
const full = (0, import_node_path23.join)(dir, entry.name);
|
|
17471
17919
|
if (entry.isDirectory()) visit(full, depth + 1);
|
|
17472
|
-
else if (entry.isFile()) found.push((0,
|
|
17920
|
+
else if (entry.isFile()) found.push((0, import_node_path23.relative)(root, full));
|
|
17473
17921
|
}
|
|
17474
17922
|
};
|
|
17475
17923
|
visit(root, 0);
|
|
17476
17924
|
return found;
|
|
17477
17925
|
}
|
|
17478
17926
|
function languageOf(path) {
|
|
17479
|
-
const name = (0,
|
|
17927
|
+
const name = (0, import_node_path23.basename)(path);
|
|
17480
17928
|
if (/^Dockerfile(\..+)?$/i.test(name)) return "dockerfile";
|
|
17481
|
-
if (!(0,
|
|
17929
|
+
if (!(0, import_node_path23.extname)(name)) return null;
|
|
17482
17930
|
const lang = detectLanguage(path);
|
|
17483
17931
|
return lang || null;
|
|
17484
17932
|
}
|
|
@@ -17560,12 +18008,12 @@ function declaredDependencies(root, files) {
|
|
|
17560
18008
|
if (deps && typeof deps === "object") names2.push(...Object.keys(deps));
|
|
17561
18009
|
}
|
|
17562
18010
|
};
|
|
17563
|
-
readPackageJson((0,
|
|
17564
|
-
const nested = files.filter((f) => f.includes("/") && (0,
|
|
17565
|
-
for (const rel of nested) readPackageJson((0,
|
|
18011
|
+
readPackageJson((0, import_node_path23.join)(root, "package.json"));
|
|
18012
|
+
const nested = files.filter((f) => f.includes("/") && (0, import_node_path23.basename)(f) === "package.json").slice(0, NESTED_MANIFEST_LIMIT);
|
|
18013
|
+
for (const rel of nested) readPackageJson((0, import_node_path23.join)(root, rel));
|
|
17566
18014
|
const pythonManifests = [
|
|
17567
|
-
...["pyproject.toml", "requirements.txt", "Pipfile", "setup.py"].map((f) => (0,
|
|
17568
|
-
...files.filter((f) => f.includes("/") && /(^|\/)(pyproject\.toml|requirements\.txt)$/.test(f)).slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0,
|
|
18015
|
+
...["pyproject.toml", "requirements.txt", "Pipfile", "setup.py"].map((f) => (0, import_node_path23.join)(root, f)),
|
|
18016
|
+
...files.filter((f) => f.includes("/") && /(^|\/)(pyproject\.toml|requirements\.txt)$/.test(f)).slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path23.join)(root, f))
|
|
17569
18017
|
];
|
|
17570
18018
|
for (const path of pythonManifests) {
|
|
17571
18019
|
if (!(0, import_node_fs25.existsSync)(path)) continue;
|
|
@@ -17580,8 +18028,8 @@ function declaredDependencies(root, files) {
|
|
|
17580
18028
|
}
|
|
17581
18029
|
}
|
|
17582
18030
|
const goMods = [
|
|
17583
|
-
(0,
|
|
17584
|
-
...files.filter((f) => f.includes("/") && (0,
|
|
18031
|
+
(0, import_node_path23.join)(root, "go.mod"),
|
|
18032
|
+
...files.filter((f) => f.includes("/") && (0, import_node_path23.basename)(f) === "go.mod").slice(0, NESTED_MANIFEST_LIMIT).map((f) => (0, import_node_path23.join)(root, f))
|
|
17585
18033
|
];
|
|
17586
18034
|
for (const path of goMods) {
|
|
17587
18035
|
if (!(0, import_node_fs25.existsSync)(path)) continue;
|
|
@@ -17594,7 +18042,7 @@ function declaredDependencies(root, files) {
|
|
|
17594
18042
|
}
|
|
17595
18043
|
}
|
|
17596
18044
|
for (const file of ["pom.xml", "build.gradle", "build.gradle.kts", "Gemfile", "Cargo.toml"]) {
|
|
17597
|
-
const path = (0,
|
|
18045
|
+
const path = (0, import_node_path23.join)(root, file);
|
|
17598
18046
|
if (!(0, import_node_fs25.existsSync)(path)) continue;
|
|
17599
18047
|
try {
|
|
17600
18048
|
const text = (0, import_node_fs25.readFileSync)(path, "utf-8");
|
|
@@ -17605,7 +18053,7 @@ function declaredDependencies(root, files) {
|
|
|
17605
18053
|
return names2;
|
|
17606
18054
|
}
|
|
17607
18055
|
function detectBuildSystem(root, files) {
|
|
17608
|
-
const has = (f) => (0, import_node_fs25.existsSync)((0,
|
|
18056
|
+
const has = (f) => (0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, f)) || files.some((p) => (0, import_node_path23.basename)(p) === f);
|
|
17609
18057
|
if (has("pnpm-lock.yaml")) return "pnpm";
|
|
17610
18058
|
if (has("yarn.lock")) return "yarn";
|
|
17611
18059
|
if (has("bun.lock") || has("bun.lockb")) return "bun";
|
|
@@ -17622,8 +18070,8 @@ function detectBuildSystem(root, files) {
|
|
|
17622
18070
|
}
|
|
17623
18071
|
function detectArchitecture(root, files) {
|
|
17624
18072
|
const workspaceMarkers = ["lerna.json", "pnpm-workspace.yaml", "nx.json", "turbo.json", "rush.json"];
|
|
17625
|
-
if (workspaceMarkers.some((m) => (0, import_node_fs25.existsSync)((0,
|
|
17626
|
-
const pkg = readJson((0,
|
|
18073
|
+
if (workspaceMarkers.some((m) => (0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, m)))) return "monorepo";
|
|
18074
|
+
const pkg = readJson((0, import_node_path23.join)(root, "package.json"));
|
|
17627
18075
|
if (pkg && "workspaces" in pkg) return "monorepo";
|
|
17628
18076
|
const manifests = files.filter((f) => /(^|\/)(package\.json|go\.mod|pyproject\.toml|Cargo\.toml|pom\.xml)$/.test(f));
|
|
17629
18077
|
const nested = manifests.filter((f) => f.includes("/"));
|
|
@@ -17645,7 +18093,7 @@ function measureAvgFileLength(root, files, languages) {
|
|
|
17645
18093
|
let total = 0;
|
|
17646
18094
|
let counted = 0;
|
|
17647
18095
|
for (let i = 0; i < candidates2.length; i += stride) {
|
|
17648
|
-
const path = (0,
|
|
18096
|
+
const path = (0, import_node_path23.join)(root, candidates2[i]);
|
|
17649
18097
|
try {
|
|
17650
18098
|
if ((0, import_node_fs25.statSync)(path).size > 2 * 1024 * 1024) continue;
|
|
17651
18099
|
total += (0, import_node_fs25.readFileSync)(path, "utf-8").split("\n").length;
|
|
@@ -17678,14 +18126,14 @@ function detectProject(root = repoRoot()) {
|
|
|
17678
18126
|
const existingToolConfigs = [];
|
|
17679
18127
|
for (const [tool, markers] of TOOL_CONFIG_MARKERS) {
|
|
17680
18128
|
for (const marker of markers) {
|
|
17681
|
-
if ((0, import_node_fs25.existsSync)((0,
|
|
18129
|
+
if ((0, import_node_fs25.existsSync)((0, import_node_path23.join)(root, marker))) {
|
|
17682
18130
|
existingToolConfigs.push({ tool, path: `./${marker}` });
|
|
17683
18131
|
break;
|
|
17684
18132
|
}
|
|
17685
18133
|
}
|
|
17686
18134
|
}
|
|
17687
18135
|
return {
|
|
17688
|
-
projectName: (0,
|
|
18136
|
+
projectName: (0, import_node_path23.basename)(root),
|
|
17689
18137
|
languages,
|
|
17690
18138
|
languageCounts,
|
|
17691
18139
|
frameworks: matchAll(dependencies, FRAMEWORK_BY_DEPENDENCY),
|
|
@@ -18129,10 +18577,10 @@ async function correctVerityMdVersion(opts) {
|
|
|
18129
18577
|
origin: { kind: "synthesized", tools: opts.tools }
|
|
18130
18578
|
}));
|
|
18131
18579
|
}
|
|
18132
|
-
async function writeFileTo(
|
|
18133
|
-
const target = projectPath(
|
|
18134
|
-
await (0,
|
|
18135
|
-
await (0,
|
|
18580
|
+
async function writeFileTo(relative2, body) {
|
|
18581
|
+
const target = projectPath(relative2);
|
|
18582
|
+
await (0, import_promises10.mkdir)((0, import_node_path24.dirname)(target), { recursive: true });
|
|
18583
|
+
await (0, import_promises10.writeFile)(target, body);
|
|
18136
18584
|
}
|
|
18137
18585
|
async function deriveConfigForStandard(standard) {
|
|
18138
18586
|
const spec = standard.knowledge_spec ?? {};
|
|
@@ -18189,14 +18637,14 @@ ${validation.detail}`);
|
|
|
18189
18637
|
}
|
|
18190
18638
|
|
|
18191
18639
|
// src/lib/setup-state.ts
|
|
18192
|
-
var
|
|
18640
|
+
var import_promises11 = require("node:fs/promises");
|
|
18193
18641
|
var import_node_fs27 = require("node:fs");
|
|
18194
18642
|
var SETUP_STATE_FILE = `${VERITY_DIR}/setup.json`;
|
|
18195
18643
|
async function readSetupState() {
|
|
18196
18644
|
const path = projectPath(SETUP_STATE_FILE);
|
|
18197
18645
|
if (!(0, import_node_fs27.existsSync)(path)) return null;
|
|
18198
18646
|
try {
|
|
18199
|
-
const parsed = JSON.parse(await (0,
|
|
18647
|
+
const parsed = JSON.parse(await (0, import_promises11.readFile)(path, "utf-8"));
|
|
18200
18648
|
return parsed && typeof parsed === "object" ? parsed : null;
|
|
18201
18649
|
} catch {
|
|
18202
18650
|
return null;
|
|
@@ -18211,7 +18659,7 @@ async function writeSetupState(patch) {
|
|
|
18211
18659
|
|
|
18212
18660
|
// src/lib/push-setup.ts
|
|
18213
18661
|
var import_node_fs29 = require("node:fs");
|
|
18214
|
-
var
|
|
18662
|
+
var import_promises12 = require("node:fs/promises");
|
|
18215
18663
|
var import_yaml2 = __toESM(require_dist());
|
|
18216
18664
|
|
|
18217
18665
|
// src/lib/verityignore.ts
|
|
@@ -18380,7 +18828,7 @@ async function pushStandardAndConfig(globals, what = {}) {
|
|
|
18380
18828
|
const standardPath = projectPath(STANDARD_FILE);
|
|
18381
18829
|
if (pushStandard && (0, import_node_fs29.existsSync)(standardPath)) {
|
|
18382
18830
|
try {
|
|
18383
|
-
const content = (0, import_yaml2.parse)(await (0,
|
|
18831
|
+
const content = (0, import_yaml2.parse)(await (0, import_promises12.readFile)(standardPath, "utf-8"));
|
|
18384
18832
|
const upload = buildStandardUpload(content, readVerityIgnoreRaw());
|
|
18385
18833
|
if (upload.warning) lines.push(upload.warning);
|
|
18386
18834
|
const result = await apiRequest({
|
|
@@ -18405,7 +18853,7 @@ async function pushStandardAndConfig(globals, what = {}) {
|
|
|
18405
18853
|
const configPath = projectPath(CODACY_CONFIG_FILE);
|
|
18406
18854
|
if (pushConfig && (0, import_node_fs29.existsSync)(configPath)) {
|
|
18407
18855
|
try {
|
|
18408
|
-
const content = JSON.parse(await (0,
|
|
18856
|
+
const content = JSON.parse(await (0, import_promises12.readFile)(configPath, "utf-8"));
|
|
18409
18857
|
const result = await apiRequest({
|
|
18410
18858
|
method: "POST",
|
|
18411
18859
|
path: "/analysis-configs",
|
|
@@ -18439,7 +18887,7 @@ function registerStandardCommands(program2) {
|
|
|
18439
18887
|
printError(`No ${STANDARD_FILE} here \u2014 run "verity standard synthesize" to create one.`);
|
|
18440
18888
|
process.exit(1);
|
|
18441
18889
|
}
|
|
18442
|
-
const content = (0, import_yaml3.parse)(await (0,
|
|
18890
|
+
const content = (0, import_yaml3.parse)(await (0, import_promises13.readFile)(standardPath, "utf-8"));
|
|
18443
18891
|
const derived = await deriveConfigForStandard(content);
|
|
18444
18892
|
for (const path of derived.written) printInfo(` ${path} \u2713`);
|
|
18445
18893
|
for (const note of derived.notes) printWarn(` ${note}`);
|
|
@@ -18495,7 +18943,7 @@ function registerStandardCommands(program2) {
|
|
|
18495
18943
|
}
|
|
18496
18944
|
let yamlContent;
|
|
18497
18945
|
try {
|
|
18498
|
-
yamlContent = await (0,
|
|
18946
|
+
yamlContent = await (0, import_promises13.readFile)(opts.file, "utf-8");
|
|
18499
18947
|
} catch {
|
|
18500
18948
|
printError(`Cannot read ${opts.file}`);
|
|
18501
18949
|
process.exit(1);
|
|
@@ -18592,7 +19040,7 @@ function parseIntensity(value) {
|
|
|
18592
19040
|
}
|
|
18593
19041
|
|
|
18594
19042
|
// src/commands/config.ts
|
|
18595
|
-
var
|
|
19043
|
+
var import_promises14 = require("node:fs/promises");
|
|
18596
19044
|
function registerConfigCommands(program2) {
|
|
18597
19045
|
const config = program2.command("config").description("Manage analysis configuration");
|
|
18598
19046
|
config.command("service-url").description("Print the resolved service URL (used by the shipped skills)").action(async () => {
|
|
@@ -18638,7 +19086,7 @@ function registerConfigCommands(program2) {
|
|
|
18638
19086
|
}
|
|
18639
19087
|
let content;
|
|
18640
19088
|
try {
|
|
18641
|
-
const raw = await (0,
|
|
19089
|
+
const raw = await (0, import_promises14.readFile)(opts.file, "utf-8");
|
|
18642
19090
|
content = JSON.parse(raw);
|
|
18643
19091
|
} catch {
|
|
18644
19092
|
printError(`Cannot read or parse ${opts.file}`);
|
|
@@ -18747,7 +19195,9 @@ function formatRunDetail(run2) {
|
|
|
18747
19195
|
if (f.description && f.description !== title) lines.push(` ${f.description}`);
|
|
18748
19196
|
const fix = f.fix?.description ?? f.suggestion;
|
|
18749
19197
|
if (fix) lines.push(` \u21B3 fix: ${fix}`);
|
|
18750
|
-
if (f.scope === "pre-existing")
|
|
19198
|
+
if (f.scope === "pre-existing") {
|
|
19199
|
+
lines.push(f.provenance === "caused-elsewhere" ? " (caused elsewhere \u2014 not by this change)" : " (pre-existing)");
|
|
19200
|
+
}
|
|
18751
19201
|
}
|
|
18752
19202
|
}
|
|
18753
19203
|
const pending = run2.pending_items ?? [];
|
|
@@ -19447,6 +19897,10 @@ function rgInvocations(env = process.env) {
|
|
|
19447
19897
|
out.push({ cmd: `${(0, import_node_os4.homedir)()}/.local/bin/claude`, argv0: "rg" });
|
|
19448
19898
|
return out;
|
|
19449
19899
|
}
|
|
19900
|
+
function ripgrepEnv(base = process.env) {
|
|
19901
|
+
const { RIPGREP_CONFIG_PATH: _dropped, ...rest } = base;
|
|
19902
|
+
return rest;
|
|
19903
|
+
}
|
|
19450
19904
|
var MAX_SYMBOLS = 12;
|
|
19451
19905
|
var MAX_SITES = 24;
|
|
19452
19906
|
var MAX_SITES_PER_FILE = 3;
|
|
@@ -19943,6 +20397,112 @@ function partitionSites(rgLines, symbols, opts) {
|
|
|
19943
20397
|
while (callers.length + tests.length > MAX_SITES) callers.pop();
|
|
19944
20398
|
return { callers, tests, dropped };
|
|
19945
20399
|
}
|
|
20400
|
+
var MAX_IMPORTERS = 12;
|
|
20401
|
+
var MAX_IMPORTERS_PER_FILE = 2;
|
|
20402
|
+
function moduleKey(path) {
|
|
20403
|
+
return path.replace(/\.(ts|tsx|js|jsx|mjs|cjs|mts|cts)$/i, "").replace(/\/index$/, "");
|
|
20404
|
+
}
|
|
20405
|
+
function importSpecifier(text) {
|
|
20406
|
+
const m = /(?:\bfrom|\brequire\s*\(|\bimport\s*\(|^\s*import)\s*['"]([^'"\n]+)['"]/.exec(text);
|
|
20407
|
+
return m?.[1] ?? null;
|
|
20408
|
+
}
|
|
20409
|
+
function resolveSpecifier(fromFile, spec) {
|
|
20410
|
+
if (!spec.startsWith(".")) return null;
|
|
20411
|
+
const dir = fromFile.includes("/") ? fromFile.slice(0, fromFile.lastIndexOf("/")) : "";
|
|
20412
|
+
const out = [];
|
|
20413
|
+
for (const part of (dir ? dir.split("/") : []).concat(spec.split("/"))) {
|
|
20414
|
+
if (part === "" || part === ".") continue;
|
|
20415
|
+
if (part === "..") {
|
|
20416
|
+
if (out.length === 0) return null;
|
|
20417
|
+
out.pop();
|
|
20418
|
+
continue;
|
|
20419
|
+
}
|
|
20420
|
+
out.push(part);
|
|
20421
|
+
}
|
|
20422
|
+
return out.length > 0 ? moduleKey(out.join("/")) : null;
|
|
20423
|
+
}
|
|
20424
|
+
function partitionImporters(rgLines, changedPaths, opts) {
|
|
20425
|
+
const changedByKey = /* @__PURE__ */ new Map();
|
|
20426
|
+
for (const p of changedPaths) changedByKey.set(moduleKey(p), p);
|
|
20427
|
+
const hits = rgLines.map(parseRgLine).filter((h) => h !== null);
|
|
20428
|
+
hits.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : a.line - b.line);
|
|
20429
|
+
const perFile = /* @__PURE__ */ new Map();
|
|
20430
|
+
const out = [];
|
|
20431
|
+
for (const h of hits) {
|
|
20432
|
+
if (out.length >= MAX_IMPORTERS) break;
|
|
20433
|
+
if (opts.sentPaths.has(h.file)) continue;
|
|
20434
|
+
if (opts.isExcluded(h.file)) continue;
|
|
20435
|
+
if (!isCodeSiteFile(h.file)) continue;
|
|
20436
|
+
const spec = importSpecifier(h.text);
|
|
20437
|
+
if (!spec) continue;
|
|
20438
|
+
const resolved = resolveSpecifier(h.file, spec);
|
|
20439
|
+
if (!resolved) continue;
|
|
20440
|
+
const changed = changedByKey.get(resolved);
|
|
20441
|
+
if (!changed) continue;
|
|
20442
|
+
const used = perFile.get(h.file) ?? 0;
|
|
20443
|
+
if (used >= MAX_IMPORTERS_PER_FILE) continue;
|
|
20444
|
+
perFile.set(h.file, used + 1);
|
|
20445
|
+
out.push({ file: h.file, line: h.line, text: h.text.trim().slice(0, SITE_TEXT_MAX), symbol: changed });
|
|
20446
|
+
}
|
|
20447
|
+
return out;
|
|
20448
|
+
}
|
|
20449
|
+
function runRg(args, cwd, timeoutMs) {
|
|
20450
|
+
let res = null;
|
|
20451
|
+
for (const inv of rgInvocations()) {
|
|
20452
|
+
res = (0, import_node_child_process10.spawnSync)(inv.cmd, args, {
|
|
20453
|
+
...inv.argv0 ? { argv0: inv.argv0 } : {},
|
|
20454
|
+
cwd,
|
|
20455
|
+
env: ripgrepEnv(),
|
|
20456
|
+
timeout: timeoutMs,
|
|
20457
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
20458
|
+
encoding: "utf8"
|
|
20459
|
+
});
|
|
20460
|
+
if (res.error?.code !== "ENOENT") break;
|
|
20461
|
+
}
|
|
20462
|
+
if (!res || res.error?.code === "ENOENT") {
|
|
20463
|
+
return { ok: false, reason: "no-tool" };
|
|
20464
|
+
}
|
|
20465
|
+
if (res.error) {
|
|
20466
|
+
const code = res.error.code;
|
|
20467
|
+
return { ok: false, reason: code === "ETIMEDOUT" ? "timeout" : "error" };
|
|
20468
|
+
}
|
|
20469
|
+
if (res.signal) return { ok: false, reason: "timeout" };
|
|
20470
|
+
if (res.status !== 0 && res.status !== 1) return { ok: false, reason: "error" };
|
|
20471
|
+
return { ok: true, lines: (res.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "")).filter(Boolean) };
|
|
20472
|
+
}
|
|
20473
|
+
function findImporters(input) {
|
|
20474
|
+
const basenames = [
|
|
20475
|
+
...new Set(input.changedPaths.map((p) => moduleKey(p).split("/").pop() ?? "").filter((b) => b.length > 0))
|
|
20476
|
+
].slice(0, MAX_SYMBOLS);
|
|
20477
|
+
if (basenames.length === 0) return [];
|
|
20478
|
+
const res = runRg(
|
|
20479
|
+
[
|
|
20480
|
+
"--no-config",
|
|
20481
|
+
"-n",
|
|
20482
|
+
"-w",
|
|
20483
|
+
"-F",
|
|
20484
|
+
"--no-heading",
|
|
20485
|
+
"--color",
|
|
20486
|
+
"never",
|
|
20487
|
+
"-m",
|
|
20488
|
+
"8",
|
|
20489
|
+
"--max-columns",
|
|
20490
|
+
"300",
|
|
20491
|
+
"--max-columns-preview",
|
|
20492
|
+
...basenames.flatMap((b) => ["-e", b]),
|
|
20493
|
+
"-g",
|
|
20494
|
+
"!**/{dist,build,out,vendor,node_modules,.git,coverage,target,__pycache__}/**",
|
|
20495
|
+
"./"
|
|
20496
|
+
],
|
|
20497
|
+
input.cwd,
|
|
20498
|
+
input.timeoutMs
|
|
20499
|
+
);
|
|
20500
|
+
if (!res.ok) return [];
|
|
20501
|
+
return partitionImporters(res.lines, input.changedPaths, {
|
|
20502
|
+
sentPaths: input.sentPaths,
|
|
20503
|
+
isExcluded: input.isExcluded
|
|
20504
|
+
});
|
|
20505
|
+
}
|
|
19946
20506
|
function buildRepoContext(input) {
|
|
19947
20507
|
const started = Date.now();
|
|
19948
20508
|
let signalsByPath;
|
|
@@ -19977,7 +20537,18 @@ function buildRepoContext(input) {
|
|
|
19977
20537
|
const unsupportedExts = [...unsupported].sort().slice(0, 8);
|
|
19978
20538
|
const audit = unsupportedExts.length > 0 ? { unsupported_exts: unsupportedExts } : {};
|
|
19979
20539
|
const symbols = rankSymbols([...new Set(collected)]);
|
|
20540
|
+
const changedPaths = [.../* @__PURE__ */ new Set([...signalsByPath.keys(), ...input.deltaFiles.map((f) => f.path)])];
|
|
20541
|
+
const importers = findImporters({
|
|
20542
|
+
changedPaths,
|
|
20543
|
+
sentPaths: input.sentPaths,
|
|
20544
|
+
isExcluded: input.isExcluded,
|
|
20545
|
+
cwd: input.cwd ?? process.cwd(),
|
|
20546
|
+
timeoutMs: input.timeoutMs ?? RG_TIMEOUT_MS
|
|
20547
|
+
});
|
|
19980
20548
|
if (symbols.length === 0) {
|
|
20549
|
+
if (importers.length > 0) {
|
|
20550
|
+
return { state: "ok", symbols: [], ...audit, callers: [], tests: [], importers, elapsed_ms: Date.now() - started };
|
|
20551
|
+
}
|
|
19981
20552
|
const everySupportedFileFoundNothing = unsupportedExts.length > 0;
|
|
19982
20553
|
return {
|
|
19983
20554
|
state: "absent",
|
|
@@ -19986,6 +20557,9 @@ function buildRepoContext(input) {
|
|
|
19986
20557
|
};
|
|
19987
20558
|
}
|
|
19988
20559
|
const args = [
|
|
20560
|
+
// `--no-config` + RIPGREP_CONFIG_PATH scrubbed below: a config file can add
|
|
20561
|
+
// `--pre=<command>`, which runs a program per searched file.
|
|
20562
|
+
"--no-config",
|
|
19989
20563
|
"-n",
|
|
19990
20564
|
"-w",
|
|
19991
20565
|
"-F",
|
|
@@ -20013,33 +20587,19 @@ function buildRepoContext(input) {
|
|
|
20013
20587
|
"!**/{dist,build,out,vendor,node_modules,.git,coverage,target,__pycache__}/**",
|
|
20014
20588
|
"./"
|
|
20015
20589
|
];
|
|
20016
|
-
|
|
20017
|
-
|
|
20018
|
-
|
|
20019
|
-
...
|
|
20020
|
-
|
|
20021
|
-
|
|
20022
|
-
maxBuffer: 4 * 1024 * 1024,
|
|
20023
|
-
encoding: "utf8"
|
|
20024
|
-
});
|
|
20025
|
-
if (res.error?.code !== "ENOENT") break;
|
|
20026
|
-
}
|
|
20027
|
-
if (!res || res.error?.code === "ENOENT") {
|
|
20028
|
-
return { state: "absent", reason: "no-tool", symbols, ...audit };
|
|
20029
|
-
}
|
|
20030
|
-
if (res.error) {
|
|
20031
|
-
const code = res.error.code;
|
|
20032
|
-
if (code === "ETIMEDOUT") return { state: "absent", reason: "timeout", symbols, ...audit };
|
|
20033
|
-
return { state: "absent", reason: "error", symbols, ...audit };
|
|
20590
|
+
const res = runRg(args, input.cwd ?? process.cwd(), input.timeoutMs ?? RG_TIMEOUT_MS);
|
|
20591
|
+
if (!res.ok) {
|
|
20592
|
+
if (importers.length > 0) {
|
|
20593
|
+
return { state: "ok", symbols, ...audit, callers: [], tests: [], importers, elapsed_ms: Date.now() - started };
|
|
20594
|
+
}
|
|
20595
|
+
return { state: "absent", reason: res.reason, symbols, ...audit };
|
|
20034
20596
|
}
|
|
20035
|
-
|
|
20036
|
-
if (res.status !== 0 && res.status !== 1) return { state: "absent", reason: "error", symbols, ...audit };
|
|
20037
|
-
const lines = (res.stdout ?? "").split("\n").map((l) => l.replace(/^\.\//, "")).filter(Boolean);
|
|
20597
|
+
const lines = res.lines;
|
|
20038
20598
|
const { callers, tests, dropped } = partitionSites(lines, symbols, {
|
|
20039
20599
|
sentPaths: input.sentPaths,
|
|
20040
20600
|
isExcluded: input.isExcluded
|
|
20041
20601
|
});
|
|
20042
|
-
if (callers.length === 0 && tests.length === 0) {
|
|
20602
|
+
if (callers.length === 0 && tests.length === 0 && importers.length === 0) {
|
|
20043
20603
|
return {
|
|
20044
20604
|
state: "absent",
|
|
20045
20605
|
reason: "no-sites",
|
|
@@ -20056,6 +20616,7 @@ function buildRepoContext(input) {
|
|
|
20056
20616
|
...audit,
|
|
20057
20617
|
callers,
|
|
20058
20618
|
tests,
|
|
20619
|
+
...importers.length > 0 ? { importers } : {},
|
|
20059
20620
|
elapsed_ms: Date.now() - started
|
|
20060
20621
|
};
|
|
20061
20622
|
}
|
|
@@ -20123,12 +20684,17 @@ function upgradeToExcerpts(rc, opts) {
|
|
|
20123
20684
|
}
|
|
20124
20685
|
function describeRepoContext(rc) {
|
|
20125
20686
|
if (rc.state !== "ok") {
|
|
20126
|
-
const
|
|
20127
|
-
|
|
20687
|
+
const bits = [
|
|
20688
|
+
rc.symbols?.length ? `searched: ${rc.symbols.join(", ")}` : "",
|
|
20689
|
+
rc.dropped_symbols?.length ? `dropped too-common: ${rc.dropped_symbols.join(", ")}` : "",
|
|
20690
|
+
rc.unsupported_exts?.length ? `no rules for: ${rc.unsupported_exts.join(", ")}` : ""
|
|
20691
|
+
].filter(Boolean);
|
|
20692
|
+
return `absent (${rc.reason ?? "unknown"})${bits.length > 0 ? ` \xB7 ${bits.join(" \xB7 ")}` : ""}`;
|
|
20128
20693
|
}
|
|
20129
20694
|
const parts = [
|
|
20130
20695
|
`${rc.symbols?.length ?? 0} symbol(s) \u2192 ${rc.callers?.length ?? 0} caller(s) \xB7 ${rc.tests?.length ?? 0} test(s)`
|
|
20131
20696
|
];
|
|
20697
|
+
if (rc.importers?.length) parts.push(`${rc.importers.length} importer(s)`);
|
|
20132
20698
|
if (rc.excerpts?.length) parts.push(`${rc.excerpts.length} excerpt(s)`);
|
|
20133
20699
|
if (rc.dropped_symbols?.length) parts.push(`dropped too-common: ${rc.dropped_symbols.join(", ")}`);
|
|
20134
20700
|
if (rc.unsupported_exts?.length) parts.push(`no rules for: ${rc.unsupported_exts.join(", ")}`);
|
|
@@ -20256,11 +20822,85 @@ function installRunEvidence(run2) {
|
|
|
20256
20822
|
}
|
|
20257
20823
|
|
|
20258
20824
|
// src/lib/git-frame.ts
|
|
20259
|
-
var import_node_child_process11 = require("node:child_process");
|
|
20260
20825
|
var import_node_fs33 = require("node:fs");
|
|
20261
20826
|
var import_node_os5 = require("node:os");
|
|
20262
|
-
var import_node_path24 = require("node:path");
|
|
20263
20827
|
var import_node_path25 = require("node:path");
|
|
20828
|
+
var import_node_path26 = require("node:path");
|
|
20829
|
+
|
|
20830
|
+
// src/lib/hardened-git.ts
|
|
20831
|
+
var import_node_child_process11 = require("node:child_process");
|
|
20832
|
+
var HARDENED_CONFIG_ARGS = [
|
|
20833
|
+
"-c",
|
|
20834
|
+
"core.fsmonitor=false",
|
|
20835
|
+
"-c",
|
|
20836
|
+
"core.pager=cat",
|
|
20837
|
+
"-c",
|
|
20838
|
+
"log.showSignature=false"
|
|
20839
|
+
];
|
|
20840
|
+
var DIFF_READ_FLAGS = ["--no-ext-diff", "--no-textconv"];
|
|
20841
|
+
var DEFAULT_GIT_TIMEOUT_MS = 6e4;
|
|
20842
|
+
var DEFAULT_GIT_MAX_BUFFER = 64 * 1024 * 1024;
|
|
20843
|
+
function hardenedGitEnv(base = process.env) {
|
|
20844
|
+
const env = {};
|
|
20845
|
+
for (const [key, value] of Object.entries(base)) {
|
|
20846
|
+
if (key.startsWith("GIT_")) continue;
|
|
20847
|
+
env[key] = value;
|
|
20848
|
+
}
|
|
20849
|
+
env.GIT_OPTIONAL_LOCKS = "0";
|
|
20850
|
+
env.GIT_TERMINAL_PROMPT = "0";
|
|
20851
|
+
return env;
|
|
20852
|
+
}
|
|
20853
|
+
function hardenedGitArgv(args) {
|
|
20854
|
+
return [...HARDENED_CONFIG_ARGS, ...args];
|
|
20855
|
+
}
|
|
20856
|
+
function classifyError(err) {
|
|
20857
|
+
const e = err;
|
|
20858
|
+
const stderr = typeof e?.stderr === "string" ? e.stderr : e?.stderr ? e.stderr.toString("utf-8") : "";
|
|
20859
|
+
if (e?.code === "ETIMEDOUT" || e?.killed && e?.signal === "SIGTERM") return { ok: false, error: "timeout", stderr };
|
|
20860
|
+
if (e?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return { ok: false, error: "too_large", stderr };
|
|
20861
|
+
return { ok: false, error: "failed", stderr };
|
|
20862
|
+
}
|
|
20863
|
+
function runGitSync(cwd, args, opts = {}) {
|
|
20864
|
+
try {
|
|
20865
|
+
const stdout = (0, import_node_child_process11.execFileSync)("git", hardenedGitArgv(args), {
|
|
20866
|
+
cwd,
|
|
20867
|
+
env: hardenedGitEnv(),
|
|
20868
|
+
encoding: "utf-8",
|
|
20869
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
20870
|
+
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
20871
|
+
maxBuffer: opts.maxBuffer ?? DEFAULT_GIT_MAX_BUFFER
|
|
20872
|
+
});
|
|
20873
|
+
return { ok: true, stdout };
|
|
20874
|
+
} catch (err) {
|
|
20875
|
+
return classifyError(err);
|
|
20876
|
+
}
|
|
20877
|
+
}
|
|
20878
|
+
function runGit(cwd, args, opts = {}) {
|
|
20879
|
+
return new Promise((resolvePromise) => {
|
|
20880
|
+
(0, import_node_child_process11.execFile)(
|
|
20881
|
+
"git",
|
|
20882
|
+
hardenedGitArgv(args),
|
|
20883
|
+
{
|
|
20884
|
+
cwd,
|
|
20885
|
+
env: hardenedGitEnv(),
|
|
20886
|
+
encoding: "utf-8",
|
|
20887
|
+
timeout: opts.timeoutMs ?? DEFAULT_GIT_TIMEOUT_MS,
|
|
20888
|
+
maxBuffer: opts.maxBuffer ?? DEFAULT_GIT_MAX_BUFFER
|
|
20889
|
+
},
|
|
20890
|
+
(err, stdout, stderr) => {
|
|
20891
|
+
if (err) {
|
|
20892
|
+
const e = err;
|
|
20893
|
+
e.stderr = stderr;
|
|
20894
|
+
resolvePromise(classifyError(e));
|
|
20895
|
+
return;
|
|
20896
|
+
}
|
|
20897
|
+
resolvePromise({ ok: true, stdout });
|
|
20898
|
+
}
|
|
20899
|
+
);
|
|
20900
|
+
});
|
|
20901
|
+
}
|
|
20902
|
+
|
|
20903
|
+
// src/lib/git-frame.ts
|
|
20264
20904
|
var VALUE_TOKEN = `(?:'[^']*'|"[^"]*"|\\S+)`;
|
|
20265
20905
|
var GIT_GLOBAL_OPTS = `(?:\\s+(?:-[Cc]\\s+${VALUE_TOKEN}|--?[\\w-]+(?:=\\S+)?))*`;
|
|
20266
20906
|
var COMMIT_HEAD = `git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`;
|
|
@@ -20270,7 +20910,41 @@ var COMMIT_RE = new RegExp(`(?:^|[\\s;&|(])${COMMIT_HEAD}|(?:^|[;&|(])\\s*[^\\s;
|
|
|
20270
20910
|
var PUSH_RE = new RegExp(`(?:^|[\\s;&|(])${PUSH_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${PUSH_HEAD}`);
|
|
20271
20911
|
var GH_PR_RE = new RegExp(`(?:^|[\\s;&|(])${GH_PR_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${GH_PR_HEAD}`);
|
|
20272
20912
|
function splitSegments(command) {
|
|
20273
|
-
return (command ?? "").split(/&&|\|\||;|\n/);
|
|
20913
|
+
return stripHeredocBodies(command ?? "").split(/&&|\|\||;|\n/);
|
|
20914
|
+
}
|
|
20915
|
+
var HEREDOC_OPERATOR = /(^|[^<])<<(-?)[ \t]*(?:'([^'\n]+)'|"([^"\n]+)"|\\?([A-Za-z_][A-Za-z0-9_]*))/g;
|
|
20916
|
+
var SHELL_CONSUMER = /(^|[\s;&|(])(bash|sh|zsh|dash|ksh|fish|ssh|eval|exec|sudo|su|xargs|env|nohup|time|docker|kubectl|podman)(\s|$)/;
|
|
20917
|
+
function stripHeredocBodies(command) {
|
|
20918
|
+
const lines = command.split("\n");
|
|
20919
|
+
const out = [];
|
|
20920
|
+
let i = 0;
|
|
20921
|
+
while (i < lines.length) {
|
|
20922
|
+
const line = lines[i];
|
|
20923
|
+
out.push(line);
|
|
20924
|
+
i++;
|
|
20925
|
+
const words = [];
|
|
20926
|
+
HEREDOC_OPERATOR.lastIndex = 0;
|
|
20927
|
+
let m;
|
|
20928
|
+
while ((m = HEREDOC_OPERATOR.exec(line)) !== null) {
|
|
20929
|
+
words.push({ word: m[3] ?? m[4] ?? m[5], dash: m[2] === "-" });
|
|
20930
|
+
}
|
|
20931
|
+
if (words.length === 0 || SHELL_CONSUMER.test(line)) continue;
|
|
20932
|
+
let cursor = i;
|
|
20933
|
+
let end = -1;
|
|
20934
|
+
for (const { word, dash } of words) {
|
|
20935
|
+
let k = cursor;
|
|
20936
|
+
while (k < lines.length && (dash ? lines[k].replace(/^\t+/, "") : lines[k]) !== word) k++;
|
|
20937
|
+
if (k >= lines.length) {
|
|
20938
|
+
end = -1;
|
|
20939
|
+
break;
|
|
20940
|
+
}
|
|
20941
|
+
end = k;
|
|
20942
|
+
cursor = k + 1;
|
|
20943
|
+
}
|
|
20944
|
+
if (end === -1) continue;
|
|
20945
|
+
i = end + 1;
|
|
20946
|
+
}
|
|
20947
|
+
return out.join("\n");
|
|
20274
20948
|
}
|
|
20275
20949
|
function findMomentSegment(command, on) {
|
|
20276
20950
|
const segments = splitSegments(command);
|
|
@@ -20313,8 +20987,8 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
|
|
|
20313
20987
|
if (SHELL_DYNAMIC.test(raw) || raw === "-") {
|
|
20314
20988
|
return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
|
|
20315
20989
|
}
|
|
20316
|
-
const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0,
|
|
20317
|
-
dir = (0,
|
|
20990
|
+
const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path26.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
|
|
20991
|
+
dir = (0, import_node_path25.isAbsolute)(expanded) ? expanded : (0, import_node_path25.resolve)(dir, expanded);
|
|
20318
20992
|
}
|
|
20319
20993
|
const seg = segments[segmentIndex];
|
|
20320
20994
|
const overrideMatch = /--(?:git-dir|work-tree)(?:=|\s)|\bGIT_(?:DIR|WORK_TREE|INDEX_FILE)=/.exec(seg);
|
|
@@ -20332,8 +21006,8 @@ function extractCommandTarget(command, segmentIndex, baseDir) {
|
|
|
20332
21006
|
if (SHELL_DYNAMIC.test(raw)) {
|
|
20333
21007
|
return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
|
|
20334
21008
|
}
|
|
20335
|
-
const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0,
|
|
20336
|
-
dir = (0,
|
|
21009
|
+
const expanded = raw === "~" ? (0, import_node_os5.homedir)() : raw.startsWith("~/") ? (0, import_node_path26.join)((0, import_node_os5.homedir)(), raw.slice(2)) : raw;
|
|
21010
|
+
dir = (0, import_node_path25.isAbsolute)(expanded) ? expanded : (0, import_node_path25.resolve)(dir, expanded);
|
|
20337
21011
|
}
|
|
20338
21012
|
}
|
|
20339
21013
|
return { dir, named, unresolvable: null };
|
|
@@ -20388,17 +21062,14 @@ function parsePushTarget(segment) {
|
|
|
20388
21062
|
return { remote, srcRef: src, dstRef: dst || null, isDelete };
|
|
20389
21063
|
}
|
|
20390
21064
|
function gitAt(dir, args) {
|
|
20391
|
-
|
|
20392
|
-
|
|
20393
|
-
} catch {
|
|
20394
|
-
return "";
|
|
20395
|
-
}
|
|
21065
|
+
const res = runGitSync(dir, args);
|
|
21066
|
+
return res.ok ? res.stdout.trim() : "";
|
|
20396
21067
|
}
|
|
20397
21068
|
function realpathOr2(p) {
|
|
20398
21069
|
try {
|
|
20399
21070
|
return import_node_fs33.realpathSync.native(p);
|
|
20400
21071
|
} catch {
|
|
20401
|
-
return (0,
|
|
21072
|
+
return (0, import_node_path25.resolve)(p);
|
|
20402
21073
|
}
|
|
20403
21074
|
}
|
|
20404
21075
|
function resolveFrame(input) {
|
|
@@ -20437,7 +21108,7 @@ function resolveFrame(input) {
|
|
|
20437
21108
|
const gitDirRaw = gitAt(dir, ["rev-parse", "--absolute-git-dir"]);
|
|
20438
21109
|
const commonRaw = gitAt(dir, ["rev-parse", "--git-common-dir"]);
|
|
20439
21110
|
const gitDir = gitDirRaw ? realpathOr2(gitDirRaw) : null;
|
|
20440
|
-
const commonDir = commonRaw ? realpathOr2((0,
|
|
21111
|
+
const commonDir = commonRaw ? realpathOr2((0, import_node_path25.isAbsolute)(commonRaw) ? commonRaw : (0, import_node_path25.resolve)(dir, commonRaw)) : null;
|
|
20441
21112
|
const branchRaw = gitAt(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
20442
21113
|
return {
|
|
20443
21114
|
moment: found?.moment ?? null,
|
|
@@ -20612,7 +21283,7 @@ function stagedRange(frame, command) {
|
|
|
20612
21283
|
if (plan.kind === "unpredictable") {
|
|
20613
21284
|
return { kind: "staged", base: "HEAD", head: "INDEX", via: "staged-in-command", refusal: plan.reason };
|
|
20614
21285
|
}
|
|
20615
|
-
const mergeHead = frame.gitDir ? (0,
|
|
21286
|
+
const mergeHead = frame.gitDir ? (0, import_node_path26.join)(frame.gitDir, "MERGE_HEAD") : null;
|
|
20616
21287
|
if (mergeHead && (0, import_node_fs33.existsSync)(mergeHead)) {
|
|
20617
21288
|
const vsHead = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "HEAD"]).split("\n").filter(Boolean));
|
|
20618
21289
|
const vsMerge = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "MERGE_HEAD"]).split("\n").filter(Boolean));
|
|
@@ -20665,7 +21336,7 @@ function rangeChangeSignals(frame, range, paths) {
|
|
|
20665
21336
|
const out = /* @__PURE__ */ new Map();
|
|
20666
21337
|
if (range.kind === "nothing" || paths.length === 0) return out;
|
|
20667
21338
|
if (range.kind === "push" && !range.base) return out;
|
|
20668
|
-
const args = range.kind === "staged" || range.kind === "merge" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
|
|
21339
|
+
const args = range.kind === "staged" || range.kind === "merge" ? ["diff", ...DIFF_READ_FLAGS, "--cached", "--unified=0"] : ["diff", ...DIFF_READ_FLAGS, "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
|
|
20669
21340
|
const diff = frameGit(frame, [...args, "--", ...paths]);
|
|
20670
21341
|
let current = null;
|
|
20671
21342
|
let oldSide = null;
|
|
@@ -21028,7 +21699,7 @@ function sanitizeCommandWithLoss(rawCmd) {
|
|
|
21028
21699
|
const lost = lines.slice(1).some((l) => l.trim().length > 0);
|
|
21029
21700
|
const first = lines[0];
|
|
21030
21701
|
const cmd = sanitizeCommand(first);
|
|
21031
|
-
const hadSeparator = SEPARATORS.some((
|
|
21702
|
+
const hadSeparator = SEPARATORS.some((sep4) => first.indexOf(sep4) > 0);
|
|
21032
21703
|
return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
|
|
21033
21704
|
}
|
|
21034
21705
|
var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
|
|
@@ -21037,11 +21708,11 @@ function sanitizeCommand(rawCmd) {
|
|
|
21037
21708
|
let cmd = rawCmd.split("\n")[0];
|
|
21038
21709
|
let cut = -1;
|
|
21039
21710
|
let marker = "";
|
|
21040
|
-
for (const
|
|
21041
|
-
const idx = cmd.indexOf(
|
|
21711
|
+
for (const sep4 of SEPARATORS) {
|
|
21712
|
+
const idx = cmd.indexOf(sep4);
|
|
21042
21713
|
if (idx > 0 && (cut === -1 || idx < cut)) {
|
|
21043
21714
|
cut = idx;
|
|
21044
|
-
marker =
|
|
21715
|
+
marker = sep4.trim();
|
|
21045
21716
|
}
|
|
21046
21717
|
}
|
|
21047
21718
|
if (cut > -1) cmd = cmd.slice(0, cut);
|
|
@@ -21065,28 +21736,28 @@ async function readStopHookStdin() {
|
|
|
21065
21736
|
try {
|
|
21066
21737
|
if (process.stdin.isTTY) return empty;
|
|
21067
21738
|
const chunks = [];
|
|
21068
|
-
const timeout = new Promise((
|
|
21069
|
-
const read = new Promise((
|
|
21739
|
+
const timeout = new Promise((resolve6) => setTimeout(() => resolve6(empty), 500));
|
|
21740
|
+
const read = new Promise((resolve6) => {
|
|
21070
21741
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
21071
21742
|
process.stdin.on("end", () => {
|
|
21072
21743
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
21073
21744
|
if (!raw) {
|
|
21074
|
-
|
|
21745
|
+
resolve6(empty);
|
|
21075
21746
|
return;
|
|
21076
21747
|
}
|
|
21077
21748
|
try {
|
|
21078
21749
|
const data = JSON.parse(raw);
|
|
21079
|
-
|
|
21750
|
+
resolve6({
|
|
21080
21751
|
assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
|
|
21081
21752
|
stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
|
|
21082
21753
|
transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
|
|
21083
21754
|
sessionId: typeof data.session_id === "string" ? data.session_id : null
|
|
21084
21755
|
});
|
|
21085
21756
|
} catch {
|
|
21086
|
-
|
|
21757
|
+
resolve6(empty);
|
|
21087
21758
|
}
|
|
21088
21759
|
});
|
|
21089
|
-
process.stdin.on("error", () =>
|
|
21760
|
+
process.stdin.on("error", () => resolve6(empty));
|
|
21090
21761
|
process.stdin.resume();
|
|
21091
21762
|
});
|
|
21092
21763
|
return await Promise.race([read, timeout]);
|
|
@@ -21290,7 +21961,7 @@ function channelSilence(input) {
|
|
|
21290
21961
|
// src/lib/cli-version.ts
|
|
21291
21962
|
function cliVersion() {
|
|
21292
21963
|
try {
|
|
21293
|
-
return true ? "0.
|
|
21964
|
+
return true ? "0.33.0-experimental.34db3a4" : "dev";
|
|
21294
21965
|
} catch {
|
|
21295
21966
|
return "dev";
|
|
21296
21967
|
}
|
|
@@ -21342,10 +22013,12 @@ var SEVERITY_ORDER = {
|
|
|
21342
22013
|
Low: 3
|
|
21343
22014
|
};
|
|
21344
22015
|
function isCodacyAvailable() {
|
|
21345
|
-
|
|
21346
|
-
}
|
|
21347
|
-
|
|
21348
|
-
|
|
22016
|
+
try {
|
|
22017
|
+
(0, import_node_child_process12.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
22018
|
+
return true;
|
|
22019
|
+
} catch {
|
|
22020
|
+
return false;
|
|
22021
|
+
}
|
|
21349
22022
|
}
|
|
21350
22023
|
function buildAnalyzerArgv(files) {
|
|
21351
22024
|
return [
|
|
@@ -21372,16 +22045,6 @@ function withFailure(kind, detail) {
|
|
|
21372
22045
|
summary: { ...EMPTY_RESULT.summary, failure: { kind, detail: detail.slice(0, 300) } }
|
|
21373
22046
|
};
|
|
21374
22047
|
}
|
|
21375
|
-
function runCodacyAnalysisIfAvailable(files) {
|
|
21376
|
-
if (files.length === 0) return EMPTY_RESULT;
|
|
21377
|
-
if (!isCodacyAvailable()) {
|
|
21378
|
-
return withFailure(
|
|
21379
|
-
"analyzer_unavailable",
|
|
21380
|
-
"@codacy/analysis-cli was not found on PATH \u2014 no static analysis ran"
|
|
21381
|
-
);
|
|
21382
|
-
}
|
|
21383
|
-
return runCodacyAnalysis(files);
|
|
21384
|
-
}
|
|
21385
22048
|
function runCodacyAnalysis(files) {
|
|
21386
22049
|
const empty = EMPTY_RESULT;
|
|
21387
22050
|
if (files.length === 0) return empty;
|
|
@@ -21393,8 +22056,7 @@ function runCodacyAnalysis(files) {
|
|
|
21393
22056
|
}
|
|
21394
22057
|
});
|
|
21395
22058
|
if (existingFiles.length === 0) return empty;
|
|
21396
|
-
const
|
|
21397
|
-
const proc = (0, import_node_child_process12.spawnSync)(analyzer, buildAnalyzerArgv(existingFiles), {
|
|
22059
|
+
const proc = (0, import_node_child_process12.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
|
|
21398
22060
|
encoding: "utf-8",
|
|
21399
22061
|
maxBuffer: 10 * 1024 * 1024
|
|
21400
22062
|
});
|
|
@@ -21562,10 +22224,11 @@ var EMPTY_STATIC = {
|
|
|
21562
22224
|
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
21563
22225
|
};
|
|
21564
22226
|
function runLocalStatic(analyzable, securityFiles, baseline, skipStatic) {
|
|
21565
|
-
if (skipStatic) return EMPTY_STATIC;
|
|
22227
|
+
if (skipStatic || !isCodacyAvailable()) return EMPTY_STATIC;
|
|
21566
22228
|
let scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
21567
22229
|
if (baseline) scannable = scannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
21568
|
-
|
|
22230
|
+
if (scannable.length === 0) return EMPTY_STATIC;
|
|
22231
|
+
return runCodacyAnalysis(scannable);
|
|
21569
22232
|
}
|
|
21570
22233
|
function localOnlyAndExit(staticResults) {
|
|
21571
22234
|
printJsonCompact({
|
|
@@ -21657,7 +22320,7 @@ async function scope(run2) {
|
|
|
21657
22320
|
|
|
21658
22321
|
// src/lib/specs.ts
|
|
21659
22322
|
var import_node_fs36 = require("node:fs");
|
|
21660
|
-
var
|
|
22323
|
+
var import_node_path27 = require("node:path");
|
|
21661
22324
|
var SPEC_CANDIDATES = [
|
|
21662
22325
|
"CLAUDE.md",
|
|
21663
22326
|
"AGENTS.md",
|
|
@@ -21676,16 +22339,6 @@ var SPEC_CANDIDATES = [
|
|
|
21676
22339
|
var DOC_EXT = /\.(md|mdx|ya?ml|txt|rst|adoc)$/i;
|
|
21677
22340
|
var UNCONSULTED_FILE_BYTES = 10240;
|
|
21678
22341
|
var UNCONSULTED_TOTAL_BYTES = 30720;
|
|
21679
|
-
function readSpecFiles(specsOpt, root) {
|
|
21680
|
-
const out = [];
|
|
21681
|
-
for (const raw of specsOpt.split(",").map((f) => f.trim()).filter(Boolean)) {
|
|
21682
|
-
const candidate = (0, import_node_path26.isAbsolute)(raw) ? (0, import_node_path26.relative)(root, raw) : raw;
|
|
21683
|
-
const content = readFileInside(root, candidate, MAX_EXPLICIT_SPEC_FILE_BYTES);
|
|
21684
|
-
if (content === null) continue;
|
|
21685
|
-
out.push({ path: raw, content });
|
|
21686
|
-
}
|
|
21687
|
-
return out;
|
|
21688
|
-
}
|
|
21689
22342
|
function discoverSpecs(consulted = []) {
|
|
21690
22343
|
const result = [];
|
|
21691
22344
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -21703,15 +22356,13 @@ function discoverSpecs(consulted = []) {
|
|
|
21703
22356
|
const remaining = totalCap - totalBytes;
|
|
21704
22357
|
const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
|
|
21705
22358
|
const readBytes = Math.min(fileCap, remaining);
|
|
22359
|
+
const opened = openRegularInRoot(process.cwd(), specPath);
|
|
22360
|
+
if (!opened.ok) return true;
|
|
21706
22361
|
try {
|
|
21707
|
-
const
|
|
21708
|
-
|
|
21709
|
-
|
|
21710
|
-
|
|
21711
|
-
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
21712
|
-
if (!content) return true;
|
|
21713
|
-
result.push({ path: specPath, content });
|
|
21714
|
-
totalBytes += content.length;
|
|
22362
|
+
const bytes = readOpened(opened.fd, Math.min(opened.size, readBytes));
|
|
22363
|
+
if (bytes.length === 0) return true;
|
|
22364
|
+
result.push({ path: specPath, content: bytes.toString("utf-8") });
|
|
22365
|
+
totalBytes += bytes.length;
|
|
21715
22366
|
} catch {
|
|
21716
22367
|
}
|
|
21717
22368
|
return true;
|
|
@@ -21740,7 +22391,7 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
21740
22391
|
try {
|
|
21741
22392
|
const entries = (0, import_node_fs36.readdirSync)(dir, { withFileTypes: true });
|
|
21742
22393
|
for (const entry of entries) {
|
|
21743
|
-
const fullPath = (0,
|
|
22394
|
+
const fullPath = (0, import_node_path27.join)(dir, entry.name);
|
|
21744
22395
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
21745
22396
|
result.push(fullPath);
|
|
21746
22397
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -21752,37 +22403,51 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
21752
22403
|
return result;
|
|
21753
22404
|
}
|
|
21754
22405
|
function discoverPlans() {
|
|
21755
|
-
const
|
|
21756
|
-
const
|
|
22406
|
+
const home = process.env.HOME ?? "";
|
|
22407
|
+
const homePlansDir = (0, import_node_path27.join)(home, ".claude", "plans");
|
|
22408
|
+
const sources = [];
|
|
22409
|
+
if (isRealDirectoryChain(process.cwd(), [".claude", "plans"])) {
|
|
22410
|
+
sources.push({ root: process.cwd(), prefix: (0, import_node_path27.join)(".claude", "plans") });
|
|
22411
|
+
}
|
|
22412
|
+
if (home && (0, import_node_fs36.existsSync)(homePlansDir)) sources.push({ root: homePlansDir, prefix: "" });
|
|
21757
22413
|
const candidates2 = [];
|
|
21758
22414
|
const seen = /* @__PURE__ */ new Set();
|
|
21759
|
-
for (const
|
|
21760
|
-
|
|
22415
|
+
for (const source of sources) {
|
|
22416
|
+
let names2;
|
|
21761
22417
|
try {
|
|
21762
|
-
|
|
21763
|
-
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
21764
|
-
seen.add(f);
|
|
21765
|
-
const fullPath = (0, import_node_path26.join)(plansDir, f);
|
|
21766
|
-
try {
|
|
21767
|
-
const stat3 = (0, import_node_fs36.statSync)(fullPath);
|
|
21768
|
-
candidates2.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
21769
|
-
} catch {
|
|
21770
|
-
}
|
|
21771
|
-
}
|
|
22418
|
+
names2 = (0, import_node_fs36.readdirSync)(source.prefix ? (0, import_node_path27.join)(source.root, source.prefix) : source.root);
|
|
21772
22419
|
} catch {
|
|
22420
|
+
continue;
|
|
22421
|
+
}
|
|
22422
|
+
for (const f of names2) {
|
|
22423
|
+
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
22424
|
+
seen.add(f);
|
|
22425
|
+
const relPath = source.prefix ? (0, import_node_path27.join)(source.prefix, f) : f;
|
|
22426
|
+
const stat3 = statRegularInRoot(source.root, relPath);
|
|
22427
|
+
if (!stat3.ok) continue;
|
|
22428
|
+
candidates2.push({ name: f, root: source.root, relPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
21773
22429
|
}
|
|
21774
22430
|
}
|
|
21775
22431
|
candidates2.sort((a, b) => b.mtime - a.mtime);
|
|
21776
22432
|
const result = [];
|
|
21777
22433
|
for (const entry of candidates2.slice(0, MAX_PLAN_FILES)) {
|
|
21778
22434
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
22435
|
+
const read = readRegularFileInRoot(entry.root, entry.relPath, MAX_PLAN_FILE_BYTES);
|
|
22436
|
+
if (read.ok) result.push({ name: entry.name, content: read.content });
|
|
22437
|
+
}
|
|
22438
|
+
return result;
|
|
22439
|
+
}
|
|
22440
|
+
function isRealDirectoryChain(root, parts) {
|
|
22441
|
+
let current = root;
|
|
22442
|
+
for (const part of parts) {
|
|
22443
|
+
current = (0, import_node_path27.join)(current, part);
|
|
21779
22444
|
try {
|
|
21780
|
-
|
|
21781
|
-
result.push({ name: entry.name, content });
|
|
22445
|
+
if (!(0, import_node_fs36.lstatSync)(current).isDirectory()) return false;
|
|
21782
22446
|
} catch {
|
|
22447
|
+
return false;
|
|
21783
22448
|
}
|
|
21784
22449
|
}
|
|
21785
|
-
return
|
|
22450
|
+
return true;
|
|
21786
22451
|
}
|
|
21787
22452
|
var GUARD_DOC_EXT = /\.(md|mdx|txt|rst|adoc)$/i;
|
|
21788
22453
|
function discoverGuardDocs(rangeFiles2) {
|
|
@@ -21793,16 +22458,21 @@ function discoverGuardDocs(rangeFiles2) {
|
|
|
21793
22458
|
if (!GUARD_DOC_EXT.test(path)) continue;
|
|
21794
22459
|
if (path.startsWith("/") || path.includes("..")) continue;
|
|
21795
22460
|
if (!(0, import_node_fs36.existsSync)(path)) continue;
|
|
22461
|
+
const opened = openRegularInRoot(process.cwd(), path);
|
|
22462
|
+
if (!opened.ok) continue;
|
|
22463
|
+
if (opened.size > MAX_PLAN_FILE_BYTES || totalBytes + opened.size > MAX_TOTAL_SPEC_BYTES) {
|
|
22464
|
+
closeOpened(opened.fd);
|
|
22465
|
+
continue;
|
|
22466
|
+
}
|
|
22467
|
+
let bytes;
|
|
21796
22468
|
try {
|
|
21797
|
-
|
|
21798
|
-
if (stat3.size > MAX_PLAN_FILE_BYTES) continue;
|
|
21799
|
-
if (totalBytes + stat3.size > MAX_TOTAL_SPEC_BYTES) continue;
|
|
21800
|
-
const content = (0, import_node_fs36.readFileSync)(path, "utf-8");
|
|
21801
|
-
if (!content) continue;
|
|
21802
|
-
result.push({ name: path, content });
|
|
21803
|
-
totalBytes += content.length;
|
|
22469
|
+
bytes = readOpened(opened.fd, opened.size);
|
|
21804
22470
|
} catch {
|
|
22471
|
+
continue;
|
|
21805
22472
|
}
|
|
22473
|
+
if (bytes.length === 0) continue;
|
|
22474
|
+
result.push({ name: path, content: bytes.toString("utf-8") });
|
|
22475
|
+
totalBytes += bytes.length;
|
|
21806
22476
|
}
|
|
21807
22477
|
return result;
|
|
21808
22478
|
}
|
|
@@ -21974,7 +22644,7 @@ async function mode(run2) {
|
|
|
21974
22644
|
|
|
21975
22645
|
// src/lib/fold.ts
|
|
21976
22646
|
var import_node_fs37 = require("node:fs");
|
|
21977
|
-
var
|
|
22647
|
+
var import_node_path28 = require("node:path");
|
|
21978
22648
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
21979
22649
|
"user",
|
|
21980
22650
|
"assistant",
|
|
@@ -22206,9 +22876,9 @@ function fold(transcriptPath, opts = {}) {
|
|
|
22206
22876
|
return result;
|
|
22207
22877
|
}
|
|
22208
22878
|
try {
|
|
22209
|
-
const sidecarDir = (0,
|
|
22210
|
-
(0,
|
|
22211
|
-
(0,
|
|
22879
|
+
const sidecarDir = (0, import_node_path28.join)(
|
|
22880
|
+
(0, import_node_path28.dirname)(transcriptPath),
|
|
22881
|
+
(0, import_node_path28.basename)(transcriptPath).replace(/\.jsonl$/, ""),
|
|
22212
22882
|
"subagents"
|
|
22213
22883
|
);
|
|
22214
22884
|
if ((0, import_node_fs37.existsSync)(sidecarDir)) {
|
|
@@ -22218,7 +22888,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
22218
22888
|
const walk2 = (d, depth) => {
|
|
22219
22889
|
if (depth > 4) return;
|
|
22220
22890
|
for (const e of (0, import_node_fs37.readdirSync)(d, { withFileTypes: true })) {
|
|
22221
|
-
const p = (0,
|
|
22891
|
+
const p = (0, import_node_path28.join)(d, e.name);
|
|
22222
22892
|
if (e.isDirectory()) {
|
|
22223
22893
|
walk2(p, depth + 1);
|
|
22224
22894
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
@@ -22542,12 +23212,14 @@ async function evidence(run2) {
|
|
|
22542
23212
|
authorshipWasObservable
|
|
22543
23213
|
});
|
|
22544
23214
|
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
22545
|
-
if (!opts.skipStatic) {
|
|
23215
|
+
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
22546
23216
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
22547
23217
|
if (baseline) {
|
|
22548
23218
|
allScannable = allScannable.filter((f) => changedSinceBaseline(f, baseline));
|
|
22549
23219
|
}
|
|
22550
|
-
|
|
23220
|
+
if (allScannable.length > 0) {
|
|
23221
|
+
staticResults = runCodacyAnalysis(allScannable);
|
|
23222
|
+
}
|
|
22551
23223
|
}
|
|
22552
23224
|
const deltaSet = baseline ? recentForReview.filter((f) => changedSinceBaseline(f, baseline)) : recentForReview;
|
|
22553
23225
|
codeDelta = collectCodeDelta(deltaSet, {
|
|
@@ -22591,7 +23263,7 @@ async function evidence(run2) {
|
|
|
22591
23263
|
|
|
22592
23264
|
// src/lib/cache-cleanup.ts
|
|
22593
23265
|
var import_node_fs38 = require("node:fs");
|
|
22594
|
-
var
|
|
23266
|
+
var import_node_path29 = require("node:path");
|
|
22595
23267
|
var CACHE_TTL_DAYS = 7;
|
|
22596
23268
|
function pruneStaleCache() {
|
|
22597
23269
|
try {
|
|
@@ -22599,7 +23271,7 @@ function pruneStaleCache() {
|
|
|
22599
23271
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
22600
23272
|
for (const entry of (0, import_node_fs38.readdirSync)(dir)) {
|
|
22601
23273
|
if (!entry.startsWith("pending-")) continue;
|
|
22602
|
-
const path = (0,
|
|
23274
|
+
const path = (0, import_node_path29.join)(dir, entry);
|
|
22603
23275
|
try {
|
|
22604
23276
|
const stat3 = (0, import_node_fs38.statSync)(path);
|
|
22605
23277
|
if (stat3.mtimeMs < cutoff) {
|
|
@@ -22760,14 +23432,15 @@ async function repoContext(run2) {
|
|
|
22760
23432
|
dropped: run2.repoContext.dropped_symbols?.length ?? 0,
|
|
22761
23433
|
callers: run2.repoContext.callers?.length ?? 0,
|
|
22762
23434
|
tests: run2.repoContext.tests?.length ?? 0,
|
|
23435
|
+
importers: run2.repoContext.importers?.length ?? 0,
|
|
22763
23436
|
elapsed_ms: run2.repoContext.elapsed_ms ?? null
|
|
22764
23437
|
});
|
|
22765
23438
|
}
|
|
22766
23439
|
|
|
22767
23440
|
// src/lib/seed-runner.ts
|
|
22768
|
-
var
|
|
23441
|
+
var import_promises15 = require("node:fs/promises");
|
|
22769
23442
|
var import_node_fs40 = require("node:fs");
|
|
22770
|
-
var
|
|
23443
|
+
var import_node_path30 = require("node:path");
|
|
22771
23444
|
var import_yaml4 = __toESM(require_dist());
|
|
22772
23445
|
|
|
22773
23446
|
// src/lib/seed.ts
|
|
@@ -22843,7 +23516,7 @@ function classifyClaudeSection(heading) {
|
|
|
22843
23516
|
if (hasAny(["integration", "webhook", "third-party", "third party", "provider"])) return "integration";
|
|
22844
23517
|
return "domain";
|
|
22845
23518
|
}
|
|
22846
|
-
function
|
|
23519
|
+
function slugify2(s) {
|
|
22847
23520
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 60);
|
|
22848
23521
|
}
|
|
22849
23522
|
var FRAMEWORK_GLOBS = [
|
|
@@ -22940,7 +23613,7 @@ _Extracted from README on setup. Update when project direction changes._`,
|
|
|
22940
23613
|
for (const fwRaw of frameworks) {
|
|
22941
23614
|
const fw = fwRaw.trim();
|
|
22942
23615
|
if (!fw) continue;
|
|
22943
|
-
const slug =
|
|
23616
|
+
const slug = slugify2(fw);
|
|
22944
23617
|
if (!slug || seenFrameworkSlugs.has(slug)) continue;
|
|
22945
23618
|
seenFrameworkSlugs.add(slug);
|
|
22946
23619
|
out.push({
|
|
@@ -23011,7 +23684,7 @@ async function runSeed(opts) {
|
|
|
23011
23684
|
}
|
|
23012
23685
|
let standardDoc;
|
|
23013
23686
|
try {
|
|
23014
|
-
const raw = await (0,
|
|
23687
|
+
const raw = await (0, import_promises15.readFile)(STANDARD_FILE, "utf-8");
|
|
23015
23688
|
standardDoc = (0, import_yaml4.parse)(raw);
|
|
23016
23689
|
} catch {
|
|
23017
23690
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
@@ -23020,7 +23693,7 @@ async function runSeed(opts) {
|
|
|
23020
23693
|
let readmeContent;
|
|
23021
23694
|
if ((0, import_node_fs40.existsSync)("README.md")) {
|
|
23022
23695
|
try {
|
|
23023
|
-
readmeContent = await (0,
|
|
23696
|
+
readmeContent = await (0, import_promises15.readFile)("README.md", "utf-8");
|
|
23024
23697
|
} catch {
|
|
23025
23698
|
}
|
|
23026
23699
|
}
|
|
@@ -23028,7 +23701,7 @@ async function runSeed(opts) {
|
|
|
23028
23701
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
23029
23702
|
if ((0, import_node_fs40.existsSync)(p)) {
|
|
23030
23703
|
try {
|
|
23031
|
-
claudeMdContent = await (0,
|
|
23704
|
+
claudeMdContent = await (0, import_promises15.readFile)(p, "utf-8");
|
|
23032
23705
|
break;
|
|
23033
23706
|
} catch {
|
|
23034
23707
|
}
|
|
@@ -23049,7 +23722,7 @@ async function runSeed(opts) {
|
|
|
23049
23722
|
if (candidates2.length === 0) {
|
|
23050
23723
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
23051
23724
|
}
|
|
23052
|
-
const overviewPath = (0,
|
|
23725
|
+
const overviewPath = (0, import_node_path30.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
23053
23726
|
if ((0, import_node_fs40.existsSync)(overviewPath) && !opts.force) {
|
|
23054
23727
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates: candidates2 };
|
|
23055
23728
|
}
|
|
@@ -23092,8 +23765,8 @@ async function runSeed(opts) {
|
|
|
23092
23765
|
continue;
|
|
23093
23766
|
}
|
|
23094
23767
|
try {
|
|
23095
|
-
await (0,
|
|
23096
|
-
await (0,
|
|
23768
|
+
await (0, import_promises15.mkdir)((0, import_node_path30.dirname)(targetPath), { recursive: true });
|
|
23769
|
+
await (0, import_promises15.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
23097
23770
|
created++;
|
|
23098
23771
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
23099
23772
|
} catch (err) {
|
|
@@ -23106,7 +23779,7 @@ async function runSeed(opts) {
|
|
|
23106
23779
|
|
|
23107
23780
|
// src/commands/analyze/phases/08-memory-manifest.ts
|
|
23108
23781
|
var import_node_fs41 = require("node:fs");
|
|
23109
|
-
var
|
|
23782
|
+
var import_node_path31 = require("node:path");
|
|
23110
23783
|
async function memoryManifest(run2) {
|
|
23111
23784
|
const { globals } = run2;
|
|
23112
23785
|
const { serviceUrl, token } = run2;
|
|
@@ -23116,7 +23789,7 @@ async function memoryManifest(run2) {
|
|
|
23116
23789
|
let autoSeedNotice = null;
|
|
23117
23790
|
try {
|
|
23118
23791
|
await ensureMemoryDir();
|
|
23119
|
-
const seedMarker = (0,
|
|
23792
|
+
const seedMarker = (0, import_node_path31.join)(VERITY_DIR, ".seeded");
|
|
23120
23793
|
const hasStandard = (0, import_node_fs41.existsSync)(STANDARD_FILE);
|
|
23121
23794
|
const alreadyTried = (0, import_node_fs41.existsSync)(seedMarker);
|
|
23122
23795
|
if (hasStandard && !alreadyTried) {
|
|
@@ -23234,7 +23907,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
|
|
|
23234
23907
|
}
|
|
23235
23908
|
|
|
23236
23909
|
// src/commands/analyze/phases/10-working-memory.ts
|
|
23237
|
-
var
|
|
23910
|
+
var import_node_path32 = require("node:path");
|
|
23238
23911
|
async function workingMemory(run2) {
|
|
23239
23912
|
const { opts } = run2;
|
|
23240
23913
|
const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run2;
|
|
@@ -23246,7 +23919,7 @@ async function workingMemory(run2) {
|
|
|
23246
23919
|
const priorState = foldForMarks(memorySession.d);
|
|
23247
23920
|
incrementReport = computeIncrement(
|
|
23248
23921
|
allForReview,
|
|
23249
|
-
(p) => fileHash((0,
|
|
23922
|
+
(p) => fileHash((0, import_node_path32.join)(repoRoot(), p)),
|
|
23250
23923
|
priorState.authored_all.map((a) => ({
|
|
23251
23924
|
path: a.path,
|
|
23252
23925
|
hash_at_last_verdict: a.hash_at_last_verdict
|
|
@@ -23383,15 +24056,6 @@ function parseAutonomousEnv(raw) {
|
|
|
23383
24056
|
if (v === "0" || v === "false" || v === "off" || v === "no") return false;
|
|
23384
24057
|
return true;
|
|
23385
24058
|
}
|
|
23386
|
-
function resolveRunMode(inputs = {}) {
|
|
23387
|
-
if (inputs.autonomousFlag === true) return "autonomous";
|
|
23388
|
-
if (inputs.autonomousFlag === false) return "interactive";
|
|
23389
|
-
const env = inputs.env ?? process.env;
|
|
23390
|
-
const envDecision = parseAutonomousEnv(env.VERITY_AUTONOMOUS);
|
|
23391
|
-
if (envDecision !== void 0) return envDecision ? "autonomous" : "interactive";
|
|
23392
|
-
const isTTY = inputs.isTTY ?? Boolean(process.stdin?.isTTY);
|
|
23393
|
-
return isTTY ? "interactive" : "autonomous";
|
|
23394
|
-
}
|
|
23395
24059
|
function isExplicitlyAutonomous(env = process.env) {
|
|
23396
24060
|
return parseAutonomousEnv(env.VERITY_AUTONOMOUS) === true || parseAutonomousEnv(env.CI) === true || parseAutonomousEnv(env.GITHUB_ACTIONS) === true;
|
|
23397
24061
|
}
|
|
@@ -23811,7 +24475,7 @@ async function transmit(run2) {
|
|
|
23811
24475
|
|
|
23812
24476
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
23813
24477
|
var import_node_fs44 = require("node:fs");
|
|
23814
|
-
var
|
|
24478
|
+
var import_node_path33 = require("node:path");
|
|
23815
24479
|
async function reconcile(run2) {
|
|
23816
24480
|
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run2;
|
|
23817
24481
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
@@ -23840,7 +24504,7 @@ async function reconcile(run2) {
|
|
|
23840
24504
|
const st = foldDossier(memorySession.d);
|
|
23841
24505
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
23842
24506
|
try {
|
|
23843
|
-
const src = (0, import_node_fs44.readFileSync)((0,
|
|
24507
|
+
const src = (0, import_node_fs44.readFileSync)((0, import_node_path33.join)(repoRoot(), file), "utf8").split("\n");
|
|
23844
24508
|
const at = src[line - 1];
|
|
23845
24509
|
return at === void 0 ? null : lineSha(at);
|
|
23846
24510
|
} catch {
|
|
@@ -24083,23 +24747,163 @@ function mayBlock(input) {
|
|
|
24083
24747
|
if (input.attempts > input.maxIterations) {
|
|
24084
24748
|
return { block: false, release: "same-problem-cap" };
|
|
24085
24749
|
}
|
|
24086
|
-
if (input.blocks > ceiling) {
|
|
24087
|
-
return { block: false, release: "block-ceiling" };
|
|
24750
|
+
if (input.blocks > ceiling) {
|
|
24751
|
+
return { block: false, release: "block-ceiling" };
|
|
24752
|
+
}
|
|
24753
|
+
return { block: true, release: null };
|
|
24754
|
+
}
|
|
24755
|
+
function describeRelease(release, input) {
|
|
24756
|
+
const ceiling = input.ceiling ?? HARD_BLOCK_CEILING;
|
|
24757
|
+
const open = input.findingCount > 0 ? `${input.findingCount} finding(s) remain OPEN and were NOT fixed. Human review required before deploying.` : "Human review required before deploying.";
|
|
24758
|
+
switch (release) {
|
|
24759
|
+
case "no-code-reviewed":
|
|
24760
|
+
return `Verity: WARN \u2014 NOT BLOCKING: no code was reviewed on this turn, so there is nothing here to fix. Reported as advice instead. ${open}`;
|
|
24761
|
+
case "nothing-moved":
|
|
24762
|
+
return `Verity: WARN \u2014 NOT BLOCKING: nothing has changed since the last verdict, so re-raising it cannot move anything forward. ${open}`;
|
|
24763
|
+
case "same-problem-cap":
|
|
24764
|
+
return `Verity: WARN \u2014 self-healing limit (${input.maxIterations}) reached on the same finding. NO LONGER BLOCKING, but ${open}`;
|
|
24765
|
+
case "block-ceiling":
|
|
24766
|
+
return `Verity: WARN \u2014 ${ceiling} consecutive blocking verdicts reached; releasing the block so this cannot loop. ${open}`;
|
|
24767
|
+
}
|
|
24768
|
+
}
|
|
24769
|
+
|
|
24770
|
+
// src/lib/memory-pull.ts
|
|
24771
|
+
var import_node_child_process14 = require("node:child_process");
|
|
24772
|
+
var import_promises16 = require("node:fs/promises");
|
|
24773
|
+
var pullStateFile = () => projectPath(`${VERITY_DIR}/.memory-pull-state.json`);
|
|
24774
|
+
var PAGE_SIZE = 200;
|
|
24775
|
+
var MAX_PAGES = 500;
|
|
24776
|
+
var memoryIsTracked = () => committedMemoryFiles().length > 0;
|
|
24777
|
+
async function pullRepoMemory(opts) {
|
|
24778
|
+
if (memoryIsTracked()) return { ok: true, status: "tracked", received: 0, written: 0 };
|
|
24779
|
+
const state = await readPullState();
|
|
24780
|
+
const known = opts.force ? null : state.version;
|
|
24781
|
+
let treePaths;
|
|
24782
|
+
try {
|
|
24783
|
+
treePaths = listTrackedFiles();
|
|
24784
|
+
} catch {
|
|
24785
|
+
treePaths = void 0;
|
|
24786
|
+
}
|
|
24787
|
+
const served = /* @__PURE__ */ new Map();
|
|
24788
|
+
let received = 0;
|
|
24789
|
+
let written = 0;
|
|
24790
|
+
let version = null;
|
|
24791
|
+
let cursor = null;
|
|
24792
|
+
let pages = 0;
|
|
24793
|
+
const remember = (complete) => writePullState({
|
|
24794
|
+
version: complete ? version : null,
|
|
24795
|
+
// A complete pull saw every file the server holds, so its ledger replaces the
|
|
24796
|
+
// old one (a path that stopped arriving was archived). A partial one only adds.
|
|
24797
|
+
served: complete ? served : new Map([...state.served, ...served])
|
|
24798
|
+
});
|
|
24799
|
+
do {
|
|
24800
|
+
const params = new URLSearchParams({ limit: String(PAGE_SIZE) });
|
|
24801
|
+
if (cursor) params.set("cursor", cursor);
|
|
24802
|
+
else if (known) params.set("if_version", known);
|
|
24803
|
+
const res = await apiRequest({
|
|
24804
|
+
method: "GET",
|
|
24805
|
+
path: `/memory/files?${params.toString()}`,
|
|
24806
|
+
serviceUrl: opts.serviceUrl,
|
|
24807
|
+
token: opts.token,
|
|
24808
|
+
verbose: opts.verbose,
|
|
24809
|
+
timeout: opts.timeoutMs ?? 3e4,
|
|
24810
|
+
cmd: "memory-pull"
|
|
24811
|
+
});
|
|
24812
|
+
if (!res.ok) {
|
|
24813
|
+
if (pages > 0) await remember(false);
|
|
24814
|
+
return { ok: false, error: res.error, category: res.category };
|
|
24815
|
+
}
|
|
24816
|
+
const page = res.data;
|
|
24817
|
+
if (page.unchanged) return { ok: true, status: "unchanged", received: 0, written: 0 };
|
|
24818
|
+
if (pages === 0) version = page.version;
|
|
24819
|
+
else if (page.version !== version) version = null;
|
|
24820
|
+
const files = (page.files ?? []).map((f) => ({
|
|
24821
|
+
path: f.path,
|
|
24822
|
+
content: f.content,
|
|
24823
|
+
node_id: f.node_id,
|
|
24824
|
+
op: "create"
|
|
24825
|
+
}));
|
|
24826
|
+
received += files.length;
|
|
24827
|
+
try {
|
|
24828
|
+
written += await applyMemoryWrites(files, {
|
|
24829
|
+
treePaths,
|
|
24830
|
+
baseline: "written",
|
|
24831
|
+
lastServed: state.served,
|
|
24832
|
+
served,
|
|
24833
|
+
claudeMdPointer: false
|
|
24834
|
+
});
|
|
24835
|
+
} catch (err) {
|
|
24836
|
+
await remember(false);
|
|
24837
|
+
throw err;
|
|
24838
|
+
}
|
|
24839
|
+
cursor = page.next_cursor;
|
|
24840
|
+
pages++;
|
|
24841
|
+
} while (cursor && pages < MAX_PAGES);
|
|
24842
|
+
await remember(!cursor);
|
|
24843
|
+
return { ok: true, status: "pulled", received, written };
|
|
24844
|
+
}
|
|
24845
|
+
async function readServedLedger() {
|
|
24846
|
+
return (await readPullState()).served;
|
|
24847
|
+
}
|
|
24848
|
+
async function recordServedFiles(served) {
|
|
24849
|
+
if (served.size === 0) return;
|
|
24850
|
+
const state = await readPullState();
|
|
24851
|
+
for (const [path, hash] of served) state.served.set(path, hash);
|
|
24852
|
+
await writePullState(state);
|
|
24853
|
+
}
|
|
24854
|
+
async function readPullState() {
|
|
24855
|
+
try {
|
|
24856
|
+
const parsed = JSON.parse(await (0, import_promises16.readFile)(pullStateFile(), "utf-8"));
|
|
24857
|
+
const served = /* @__PURE__ */ new Map();
|
|
24858
|
+
if (parsed?.served && typeof parsed.served === "object") {
|
|
24859
|
+
for (const [path, hash] of Object.entries(parsed.served)) {
|
|
24860
|
+
if (typeof hash === "string") served.set(path, hash);
|
|
24861
|
+
}
|
|
24862
|
+
}
|
|
24863
|
+
return { version: typeof parsed?.version === "string" ? parsed.version : null, served };
|
|
24864
|
+
} catch {
|
|
24865
|
+
return { version: null, served: /* @__PURE__ */ new Map() };
|
|
24866
|
+
}
|
|
24867
|
+
}
|
|
24868
|
+
async function writePullState(state) {
|
|
24869
|
+
try {
|
|
24870
|
+
await (0, import_promises16.mkdir)(projectPath(VERITY_DIR), { recursive: true });
|
|
24871
|
+
await (0, import_promises16.writeFile)(pullStateFile(), JSON.stringify({
|
|
24872
|
+
...state.version ? { version: state.version } : {},
|
|
24873
|
+
pulled_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24874
|
+
served: Object.fromEntries(state.served)
|
|
24875
|
+
}) + "\n");
|
|
24876
|
+
} catch {
|
|
24088
24877
|
}
|
|
24089
|
-
return { block: true, release: null };
|
|
24090
24878
|
}
|
|
24091
|
-
function
|
|
24092
|
-
|
|
24093
|
-
|
|
24094
|
-
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
|
|
24101
|
-
|
|
24102
|
-
|
|
24879
|
+
function pullsAtSessionStart(auth2, source) {
|
|
24880
|
+
if (source === "compact") return false;
|
|
24881
|
+
if (auth2.source === "env" || auth2.source === "flag") return true;
|
|
24882
|
+
return auth2.userId != null;
|
|
24883
|
+
}
|
|
24884
|
+
function spawnBackgroundPull(deps = {}) {
|
|
24885
|
+
const d = {
|
|
24886
|
+
spawn: import_node_child_process14.spawn,
|
|
24887
|
+
execPath: process.execPath,
|
|
24888
|
+
script: process.argv[1],
|
|
24889
|
+
cwd: process.cwd(),
|
|
24890
|
+
env: process.env,
|
|
24891
|
+
...deps
|
|
24892
|
+
};
|
|
24893
|
+
if (d.env.VERITY_NO_BACKGROUND_PULL === "1" || !d.script) return false;
|
|
24894
|
+
try {
|
|
24895
|
+
const child = d.spawn(d.execPath, [d.script, "memory", "pull", "--quiet"], {
|
|
24896
|
+
cwd: d.cwd,
|
|
24897
|
+
env: d.env,
|
|
24898
|
+
detached: true,
|
|
24899
|
+
stdio: "ignore"
|
|
24900
|
+
});
|
|
24901
|
+
child.on("error", () => {
|
|
24902
|
+
});
|
|
24903
|
+
child.unref();
|
|
24904
|
+
return true;
|
|
24905
|
+
} catch {
|
|
24906
|
+
return false;
|
|
24103
24907
|
}
|
|
24104
24908
|
}
|
|
24105
24909
|
|
|
@@ -24201,7 +25005,9 @@ async function render(run2) {
|
|
|
24201
25005
|
if (memoryWritesRaw && memoryWritesRaw.length > 0) {
|
|
24202
25006
|
try {
|
|
24203
25007
|
const treePaths = listTrackedFiles();
|
|
24204
|
-
|
|
25008
|
+
const served = /* @__PURE__ */ new Map();
|
|
25009
|
+
await applyMemoryWrites(memoryWritesRaw, { treePaths, lastServed: await readServedLedger(), served });
|
|
25010
|
+
await recordServedFiles(served);
|
|
24205
25011
|
} catch (err) {
|
|
24206
25012
|
process.stderr.write(`verity: memory sync write failed: ${err.message}
|
|
24207
25013
|
`);
|
|
@@ -24530,7 +25336,7 @@ var import_node_fs47 = require("node:fs");
|
|
|
24530
25336
|
|
|
24531
25337
|
// src/lib/project-skills.ts
|
|
24532
25338
|
var import_node_fs46 = require("node:fs");
|
|
24533
|
-
var
|
|
25339
|
+
var import_node_path34 = require("node:path");
|
|
24534
25340
|
var PROJECT_SKILL_NAMES = [
|
|
24535
25341
|
"verity-setup",
|
|
24536
25342
|
"verity-analyze",
|
|
@@ -24556,13 +25362,13 @@ var ALL = [...PROJECT_SKILL_NAMES, ...LEGACY_SKILL_NAMES];
|
|
|
24556
25362
|
function staleProjectSkills() {
|
|
24557
25363
|
const root = projectPath(".claude/skills");
|
|
24558
25364
|
if (!(0, import_node_fs46.existsSync)(root)) return [];
|
|
24559
|
-
return ALL.filter((name) => (0, import_node_fs46.existsSync)((0,
|
|
25365
|
+
return ALL.filter((name) => (0, import_node_fs46.existsSync)((0, import_node_path34.join)(root, name)));
|
|
24560
25366
|
}
|
|
24561
25367
|
function removeProjectSkills() {
|
|
24562
25368
|
const root = projectPath(".claude/skills");
|
|
24563
25369
|
const removed = [];
|
|
24564
25370
|
for (const name of staleProjectSkills()) {
|
|
24565
|
-
(0, import_node_fs46.rmSync)((0,
|
|
25371
|
+
(0, import_node_fs46.rmSync)((0, import_node_path34.join)(root, name), { recursive: true, force: true });
|
|
24566
25372
|
removed.push(name);
|
|
24567
25373
|
}
|
|
24568
25374
|
return removed;
|
|
@@ -24688,6 +25494,7 @@ function registerBaselineCommands(program2) {
|
|
|
24688
25494
|
}
|
|
24689
25495
|
const authForScope = await resolveToken(program2.opts().token);
|
|
24690
25496
|
const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
|
|
25497
|
+
if (authForScope.ok && pullsAtSessionStart(authForScope.data, source)) spawnBackgroundPull();
|
|
24691
25498
|
const scopeSession = session;
|
|
24692
25499
|
const result = captureBaseline({ sessionId: sessionScopeKey(scopeToken, scopeSession), source });
|
|
24693
25500
|
logEvent("baseline_capture", {
|
|
@@ -24750,22 +25557,45 @@ async function runReview(opts, globals) {
|
|
|
24750
25557
|
const changedFiles = opts.changed ? opts.changed.split(",").map((f) => f.trim()).filter(Boolean) : allFiles;
|
|
24751
25558
|
const analyzable = filterAnalyzable(allFiles);
|
|
24752
25559
|
const securityFiles = filterSecurity(allFiles);
|
|
24753
|
-
|
|
24754
|
-
|
|
25560
|
+
let staticResults;
|
|
25561
|
+
if (isCodacyAvailable()) {
|
|
25562
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs48.existsSync)(f) || resolveFile(f) !== null);
|
|
25563
|
+
staticResults = runCodacyAnalysis(scannable);
|
|
25564
|
+
} else {
|
|
25565
|
+
staticResults = {
|
|
25566
|
+
tool: "@codacy/analysis-cli",
|
|
25567
|
+
findings: [],
|
|
25568
|
+
summary: { total_findings: 0, by_severity: {}, tools_run: [] }
|
|
25569
|
+
};
|
|
25570
|
+
}
|
|
24755
25571
|
const codeDelta = collectCodeDelta(allFiles);
|
|
24756
25572
|
const tokenResult = await resolveToken(globals.token);
|
|
24757
25573
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
24758
25574
|
if (!tokenResult.ok || !urlResult.ok) {
|
|
24759
|
-
const staticFailure = staticResults.summary.failure;
|
|
24760
25575
|
printJsonCompact({
|
|
24761
|
-
gate_decision: "
|
|
24762
|
-
systemMessage:
|
|
25576
|
+
gate_decision: "PASS",
|
|
25577
|
+
systemMessage: "Verity: not authenticated \u2014 showing local static results only (no deep review, no upload). Run `verity init` to authenticate and unlock the full review.",
|
|
24763
25578
|
unauthenticated: true,
|
|
24764
25579
|
static_results: staticResults
|
|
24765
25580
|
});
|
|
24766
25581
|
process.exit(0);
|
|
24767
25582
|
}
|
|
24768
|
-
|
|
25583
|
+
let specs;
|
|
25584
|
+
if (opts.specs) {
|
|
25585
|
+
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
25586
|
+
specs = [];
|
|
25587
|
+
for (const p of specPaths) {
|
|
25588
|
+
if (!(0, import_node_fs48.existsSync)(p)) continue;
|
|
25589
|
+
try {
|
|
25590
|
+
const { readFileSync: readFileSync26 } = await import("node:fs");
|
|
25591
|
+
const content = readFileSync26(p, "utf-8");
|
|
25592
|
+
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
25593
|
+
} catch {
|
|
25594
|
+
}
|
|
25595
|
+
}
|
|
25596
|
+
} else {
|
|
25597
|
+
specs = discoverSpecs();
|
|
25598
|
+
}
|
|
24769
25599
|
const plans = discoverPlans();
|
|
24770
25600
|
const requestBody = {
|
|
24771
25601
|
static_results: staticResults,
|
|
@@ -24798,10 +25628,9 @@ async function runReview(opts, globals) {
|
|
|
24798
25628
|
});
|
|
24799
25629
|
if (!result.ok) {
|
|
24800
25630
|
printError(`Service error: ${result.error}`);
|
|
24801
|
-
const staticFailure = staticResults.summary.failure;
|
|
24802
25631
|
printJsonCompact({
|
|
24803
|
-
gate_decision: "
|
|
24804
|
-
systemMessage:
|
|
25632
|
+
gate_decision: "PASS",
|
|
25633
|
+
systemMessage: "Verity: Service unavailable \u2014 showing static results only",
|
|
24805
25634
|
offline: true,
|
|
24806
25635
|
static_results: staticResults
|
|
24807
25636
|
});
|
|
@@ -24817,14 +25646,387 @@ async function runReview(opts, globals) {
|
|
|
24817
25646
|
|
|
24818
25647
|
// src/commands/guard.ts
|
|
24819
25648
|
var import_node_fs49 = require("node:fs");
|
|
24820
|
-
var
|
|
25649
|
+
var import_node_path35 = require("node:path");
|
|
25650
|
+
|
|
25651
|
+
// src/lib/terminal-text.ts
|
|
25652
|
+
var CONTROL = /[\x00-\x08\x0b-\x1f\x7f-\x9f\u061c\u200e\u200f\u2028-\u202e\u2066-\u2069]/g;
|
|
25653
|
+
function terminalText(value, max = 4e3) {
|
|
25654
|
+
if (typeof value !== "string") return "";
|
|
25655
|
+
const clean2 = value.replace(CONTROL, "");
|
|
25656
|
+
return clean2.length > max ? `${clean2.slice(0, max - 1)}\u2026` : clean2;
|
|
25657
|
+
}
|
|
25658
|
+
function terminalUrl(value) {
|
|
25659
|
+
const clean2 = terminalText(value, 2e3);
|
|
25660
|
+
return /^https?:\/\/\S+$/.test(clean2) ? clean2 : "";
|
|
25661
|
+
}
|
|
25662
|
+
|
|
25663
|
+
// src/lib/agent-driver.ts
|
|
25664
|
+
var AGENT_DEADLINE_MS = {
|
|
25665
|
+
"pre-commit": 9e4,
|
|
25666
|
+
"pre-push": 18e4
|
|
25667
|
+
};
|
|
25668
|
+
var MIN_AGENT_START_MS = 25e3;
|
|
25669
|
+
var MIN_TURN_MS = 12e3;
|
|
25670
|
+
var FINISH_TIMEOUT_MS = 15e3;
|
|
25671
|
+
var CANCEL_TIMEOUT_MS = 3e3;
|
|
25672
|
+
var MAX_TURNS = 16;
|
|
25673
|
+
var ONE_SHOT_FINDING_FIELDS = ["severity", "category", "file", "line", "title", "description", "scope", "pattern_id"];
|
|
25674
|
+
function summarizeFindings(response) {
|
|
25675
|
+
const findings = Array.isArray(response.findings) ? response.findings : [];
|
|
25676
|
+
return findings.slice(0, 40).map((f) => {
|
|
25677
|
+
const out = {};
|
|
25678
|
+
const finding = f ?? {};
|
|
25679
|
+
for (const key of ONE_SHOT_FINDING_FIELDS) if (finding[key] !== void 0) out[key] = finding[key];
|
|
25680
|
+
return out;
|
|
25681
|
+
});
|
|
25682
|
+
}
|
|
25683
|
+
function finishOneShot(settled) {
|
|
25684
|
+
if (!settled) return { status: "pending" };
|
|
25685
|
+
if (!settled.ok) return { status: "failed" };
|
|
25686
|
+
const r = settled.response;
|
|
25687
|
+
return {
|
|
25688
|
+
status: "arrived",
|
|
25689
|
+
...typeof r.agent_attestation === "string" ? { attestation: r.agent_attestation } : {},
|
|
25690
|
+
...typeof r.run_id === "string" ? { run_id: r.run_id } : {},
|
|
25691
|
+
...typeof r.gate_decision === "string" ? { gate_decision: r.gate_decision } : {}
|
|
25692
|
+
};
|
|
25693
|
+
}
|
|
25694
|
+
async function runAgentReview(options) {
|
|
25695
|
+
const now = options.now ?? Date.now;
|
|
25696
|
+
const remaining = () => options.deadlineMs - (now() - options.startedAt);
|
|
25697
|
+
let settled = null;
|
|
25698
|
+
const oneShotDone = options.oneShot.then(
|
|
25699
|
+
(outcome) => {
|
|
25700
|
+
settled = outcome;
|
|
25701
|
+
},
|
|
25702
|
+
() => {
|
|
25703
|
+
settled = { ok: false };
|
|
25704
|
+
}
|
|
25705
|
+
);
|
|
25706
|
+
if (remaining() < MIN_AGENT_START_MS) return { kind: "skipped", reason: "not enough time left after prep" };
|
|
25707
|
+
const started = await options.http.post(
|
|
25708
|
+
"/agent-review/start",
|
|
25709
|
+
{ ...options.start, remaining_ms: remaining() },
|
|
25710
|
+
Math.max(1, remaining())
|
|
25711
|
+
);
|
|
25712
|
+
if (!started.ok) return { kind: "failed", reason: `start: ${started.error}` };
|
|
25713
|
+
if (!started.data.enabled) return { kind: "disabled", reason: started.data.reason };
|
|
25714
|
+
let state = started.data.state;
|
|
25715
|
+
let calls = started.data.calls ?? [];
|
|
25716
|
+
let done = started.data.done;
|
|
25717
|
+
let sentOneShot = "pending";
|
|
25718
|
+
const cancel = async () => {
|
|
25719
|
+
await options.http.post("/agent-review/cancel", { state }, CANCEL_TIMEOUT_MS);
|
|
25720
|
+
};
|
|
25721
|
+
for (let turn = 0; !done; turn++) {
|
|
25722
|
+
if (turn >= MAX_TURNS || remaining() < MIN_TURN_MS) break;
|
|
25723
|
+
const results = await options.executeTools(calls);
|
|
25724
|
+
let oneShot;
|
|
25725
|
+
const current = settled;
|
|
25726
|
+
if (current && sentOneShot === "pending") {
|
|
25727
|
+
oneShot = current.ok ? { status: "arrived", findings: summarizeFindings(current.response) } : { status: "failed" };
|
|
25728
|
+
sentOneShot = oneShot.status;
|
|
25729
|
+
}
|
|
25730
|
+
const turnRequest = { state, remaining_ms: remaining(), results, ...oneShot ? { one_shot: oneShot } : {} };
|
|
25731
|
+
const next = await options.http.post("/agent-review/turn", turnRequest, Math.max(1, remaining()));
|
|
25732
|
+
if (!next.ok) {
|
|
25733
|
+
await cancel();
|
|
25734
|
+
return { kind: "failed", reason: `turn: ${next.error}` };
|
|
25735
|
+
}
|
|
25736
|
+
state = next.data.state;
|
|
25737
|
+
calls = next.data.calls ?? [];
|
|
25738
|
+
done = next.data.done;
|
|
25739
|
+
}
|
|
25740
|
+
if (!settled) {
|
|
25741
|
+
const wait = Math.max(0, remaining() - FINISH_TIMEOUT_MS);
|
|
25742
|
+
await Promise.race([oneShotDone, new Promise((resolve6) => setTimeout(resolve6, wait))]);
|
|
25743
|
+
}
|
|
25744
|
+
const finished = await options.http.post(
|
|
25745
|
+
"/agent-review/finish",
|
|
25746
|
+
{ state, session_id: options.sessionId, one_shot: finishOneShot(settled) },
|
|
25747
|
+
FINISH_TIMEOUT_MS
|
|
25748
|
+
);
|
|
25749
|
+
if (!finished.ok) {
|
|
25750
|
+
await cancel();
|
|
25751
|
+
return { kind: "failed", reason: `finish: ${finished.error}` };
|
|
25752
|
+
}
|
|
25753
|
+
return { kind: "finished", response: finished.data };
|
|
25754
|
+
}
|
|
25755
|
+
|
|
25756
|
+
// src/lib/agent-tools.ts
|
|
25757
|
+
var TOOL_LIMITS = {
|
|
25758
|
+
outputChars: 16e3,
|
|
25759
|
+
readLines: 400,
|
|
25760
|
+
blameLines: 200,
|
|
25761
|
+
searchHits: 50,
|
|
25762
|
+
searchLineChars: 300,
|
|
25763
|
+
listEntries: 200,
|
|
25764
|
+
logCommits: 8,
|
|
25765
|
+
textChars: 200,
|
|
25766
|
+
pathChars: 400,
|
|
25767
|
+
fileBytes: 4 * 1024 * 1024,
|
|
25768
|
+
gitTimeoutMs: 2e3,
|
|
25769
|
+
diffTimeoutMs: 1e4,
|
|
25770
|
+
concurrency: 4
|
|
25771
|
+
};
|
|
25772
|
+
var DENIED_PATHS = [
|
|
25773
|
+
/(^|\/)\.env(\.[^/]*)?$/i,
|
|
25774
|
+
/\.(pem|key|p12|pfx|jks|keystore|asc|gpg|kdbx)$/i,
|
|
25775
|
+
/(^|\/)id_(rsa|dsa|ecdsa|ed25519)(\.pub)?$/i,
|
|
25776
|
+
/(^|\/)\.(npmrc|netrc|pypirc|pgpass|git-credentials)$/i,
|
|
25777
|
+
/(^|\/)(credentials|secrets?)(\.[^/]*)?$/i,
|
|
25778
|
+
/(^|\/)\.(ssh|aws|gnupg|docker)(\/|$)/i,
|
|
25779
|
+
/(^|\/)\.git(\/|$)/
|
|
25780
|
+
];
|
|
25781
|
+
function isDeniedPath(path) {
|
|
25782
|
+
return DENIED_PATHS.some((pattern) => pattern.test(path)) || isVerityOwnedPath(path);
|
|
25783
|
+
}
|
|
25784
|
+
function validateRepoPath(value, { allowRoot = false } = {}) {
|
|
25785
|
+
if (typeof value !== "string") return { ok: false, error: "path must be a string" };
|
|
25786
|
+
let path = value.replace(/\\/g, "/").replace(/^\.\/+/, "");
|
|
25787
|
+
if (allowRoot && (path === "" || path === ".")) return { ok: true, value: "" };
|
|
25788
|
+
path = path.replace(/\/+$/, "");
|
|
25789
|
+
if (!path || path.length > TOOL_LIMITS.pathChars) return { ok: false, error: "path is empty or too long" };
|
|
25790
|
+
if (/[-]/.test(path)) return { ok: false, error: "path contains control characters" };
|
|
25791
|
+
if (path.startsWith("/") || /^[A-Za-z]:/.test(path)) return { ok: false, error: "path must be relative to the repository root" };
|
|
25792
|
+
if (path.split("/").some((segment) => segment === "..")) return { ok: false, error: "path must not leave the repository" };
|
|
25793
|
+
if (path.startsWith("-") || path.startsWith(":")) return { ok: false, error: 'path must not start with "-" or ":"' };
|
|
25794
|
+
if (isDeniedPath(path)) return { ok: false, error: "reading this path is not allowed" };
|
|
25795
|
+
return { ok: true, value: path };
|
|
25796
|
+
}
|
|
25797
|
+
function lineNumber(value, fallback) {
|
|
25798
|
+
return typeof value === "number" && Number.isInteger(value) && value >= 1 ? value : fallback;
|
|
25799
|
+
}
|
|
25800
|
+
function capOutput(text) {
|
|
25801
|
+
return text.length > TOOL_LIMITS.outputChars ? `${text.slice(0, TOOL_LIMITS.outputChars)}
|
|
25802
|
+
[truncated at ${TOOL_LIMITS.outputChars} characters]` : text;
|
|
25803
|
+
}
|
|
25804
|
+
function git(ctx, args, opts = {}) {
|
|
25805
|
+
return runGit(ctx.root, args, { timeoutMs: opts.timeoutMs ?? TOOL_LIMITS.gitTimeoutMs, maxBuffer: opts.maxBuffer ?? TOOL_LIMITS.fileBytes });
|
|
25806
|
+
}
|
|
25807
|
+
function gitFailure(res, what) {
|
|
25808
|
+
if (res.error === "timeout") return `${what} timed out`;
|
|
25809
|
+
if (res.error === "too_large") return `${what} is too large`;
|
|
25810
|
+
const detail = res.stderr.trim().split("\n")[0]?.slice(0, 160);
|
|
25811
|
+
return detail ? `${what} failed: ${detail}` : `${what} failed`;
|
|
25812
|
+
}
|
|
25813
|
+
var INDEX = Symbol("index");
|
|
25814
|
+
function changeRevision(range) {
|
|
25815
|
+
return range.kind === "push" ? range.head : INDEX;
|
|
25816
|
+
}
|
|
25817
|
+
function baseRevision(range) {
|
|
25818
|
+
return range.kind === "push" ? range.base : "HEAD";
|
|
25819
|
+
}
|
|
25820
|
+
function objectSpec(revision, path) {
|
|
25821
|
+
return revision === INDEX ? `:${path}` : `${revision}:${path}`;
|
|
25822
|
+
}
|
|
25823
|
+
async function readBlob(ctx, revision, path) {
|
|
25824
|
+
const res = await git(ctx, ["cat-file", "blob", objectSpec(revision, path)]);
|
|
25825
|
+
if (!res.ok) {
|
|
25826
|
+
if (res.error === "failed") return { ok: false, error: `${path} is not a file in ${revision === INDEX ? "the staged version" : "that version"}` };
|
|
25827
|
+
return { ok: false, error: gitFailure(res, `reading ${path}`) };
|
|
25828
|
+
}
|
|
25829
|
+
if (res.stdout.slice(0, 8e3).includes("\0")) return { ok: false, error: `${path} is a binary file` };
|
|
25830
|
+
return { ok: true, value: res.stdout };
|
|
25831
|
+
}
|
|
25832
|
+
function numberedRange(content, startLine, endLine, maxLines) {
|
|
25833
|
+
const lines = content.split("\n");
|
|
25834
|
+
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
|
25835
|
+
const total = lines.length;
|
|
25836
|
+
const from = Math.min(Math.max(1, startLine), Math.max(1, total));
|
|
25837
|
+
const to = Math.min(total, Math.max(from, endLine), from + maxLines - 1);
|
|
25838
|
+
const width = String(to).length;
|
|
25839
|
+
const body = lines.slice(from - 1, to).map((line, i) => `${String(from + i).padStart(width, " ")}| ${line}`).join("\n");
|
|
25840
|
+
return `lines ${total === 0 ? 0 : from}-${to} of ${total}
|
|
25841
|
+
${body}`;
|
|
25842
|
+
}
|
|
25843
|
+
async function resolveRevision(ctx, value) {
|
|
25844
|
+
if (value === "head") return { ok: true, value: changeRevision(ctx.range) };
|
|
25845
|
+
if (value === "base") {
|
|
25846
|
+
const base = baseRevision(ctx.range);
|
|
25847
|
+
return base ? { ok: true, value: base } : { ok: false, error: "this change has no base version" };
|
|
25848
|
+
}
|
|
25849
|
+
if (typeof value === "string" && /^[0-9a-f]{7,40}$/.test(value)) {
|
|
25850
|
+
const res = await git(ctx, ["rev-parse", "--verify", "--quiet", "--end-of-options", `${value}^{commit}`]);
|
|
25851
|
+
if (res.ok && res.stdout.trim()) return { ok: true, value: res.stdout.trim() };
|
|
25852
|
+
return { ok: false, error: `unknown commit ${value}` };
|
|
25853
|
+
}
|
|
25854
|
+
return { ok: false, error: 'revision must be "base", "head", or a commit hash from git_log' };
|
|
25855
|
+
}
|
|
25856
|
+
var readFileTool = async (ctx, args) => {
|
|
25857
|
+
const path = validateRepoPath(args.path);
|
|
25858
|
+
if (!path.ok) return path;
|
|
25859
|
+
const blob = await readBlob(ctx, changeRevision(ctx.range), path.value);
|
|
25860
|
+
if (!blob.ok) return blob;
|
|
25861
|
+
const start = lineNumber(args.start_line, 1);
|
|
25862
|
+
const end = lineNumber(args.end_line, start + TOOL_LIMITS.readLines - 1);
|
|
25863
|
+
return { ok: true, value: `${path.value} (${ctx.range.kind === "push" ? "pushed version" : "staged version"}), ${numberedRange(blob.value, start, end, TOOL_LIMITS.readLines)}` };
|
|
25864
|
+
};
|
|
25865
|
+
var gitShowTool = async (ctx, args) => {
|
|
25866
|
+
const path = validateRepoPath(args.path);
|
|
25867
|
+
if (!path.ok) return path;
|
|
25868
|
+
const revision = await resolveRevision(ctx, args.revision);
|
|
25869
|
+
if (!revision.ok) return revision;
|
|
25870
|
+
const blob = await readBlob(ctx, revision.value, path.value);
|
|
25871
|
+
if (!blob.ok) return blob;
|
|
25872
|
+
const start = lineNumber(args.start_line, 1);
|
|
25873
|
+
const end = lineNumber(args.end_line, start + TOOL_LIMITS.readLines - 1);
|
|
25874
|
+
return { ok: true, value: `${path.value} at ${String(args.revision)}, ${numberedRange(blob.value, start, end, TOOL_LIMITS.readLines)}` };
|
|
25875
|
+
};
|
|
25876
|
+
var gitDiffTool = async (ctx, args) => {
|
|
25877
|
+
const path = validateRepoPath(args.path);
|
|
25878
|
+
if (!path.ok) return path;
|
|
25879
|
+
const range = ctx.range;
|
|
25880
|
+
let diffArgs;
|
|
25881
|
+
if (range.kind === "push") {
|
|
25882
|
+
if (!range.base) return { ok: false, error: "this push has no base to diff against" };
|
|
25883
|
+
diffArgs = ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", range.base, range.head];
|
|
25884
|
+
} else {
|
|
25885
|
+
diffArgs = ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", "--cached"];
|
|
25886
|
+
}
|
|
25887
|
+
const res = await git(ctx, ["--literal-pathspecs", ...diffArgs, "--", path.value]);
|
|
25888
|
+
if (!res.ok) return { ok: false, error: gitFailure(res, "diff") };
|
|
25889
|
+
return { ok: true, value: res.stdout.trim() ? capOutput(res.stdout) : `no changes to ${path.value} in the change under review` };
|
|
25890
|
+
};
|
|
25891
|
+
var gitLogTool = async (ctx, args) => {
|
|
25892
|
+
const path = validateRepoPath(args.path);
|
|
25893
|
+
if (!path.ok) return path;
|
|
25894
|
+
const count = Math.min(TOOL_LIMITS.logCommits, lineNumber(args.max_count, TOOL_LIMITS.logCommits));
|
|
25895
|
+
const revision = ctx.range.kind === "push" ? ctx.range.head : "HEAD";
|
|
25896
|
+
const res = await git(ctx, [
|
|
25897
|
+
"--literal-pathspecs",
|
|
25898
|
+
"log",
|
|
25899
|
+
"--no-show-signature",
|
|
25900
|
+
"--no-color",
|
|
25901
|
+
"-n",
|
|
25902
|
+
String(count),
|
|
25903
|
+
"--date=short",
|
|
25904
|
+
"--format=%h %ad %s",
|
|
25905
|
+
revision,
|
|
25906
|
+
"--",
|
|
25907
|
+
path.value
|
|
25908
|
+
]);
|
|
25909
|
+
if (!res.ok) return { ok: false, error: gitFailure(res, "log") };
|
|
25910
|
+
return { ok: true, value: res.stdout.trim() ? capOutput(res.stdout.trim()) : `no commits touch ${path.value}` };
|
|
25911
|
+
};
|
|
25912
|
+
var gitBlameTool = async (ctx, args) => {
|
|
25913
|
+
const path = validateRepoPath(args.path);
|
|
25914
|
+
if (!path.ok) return path;
|
|
25915
|
+
const start = lineNumber(args.start_line, 1);
|
|
25916
|
+
const end = Math.min(lineNumber(args.end_line, start), start + TOOL_LIMITS.blameLines - 1);
|
|
25917
|
+
const revision = ctx.range.kind === "push" ? ctx.range.head : "HEAD";
|
|
25918
|
+
const res = await git(ctx, ["blame", "--no-textconv", "-s", "-L", `${start},${Math.max(start, end)}`, revision, "--", path.value]);
|
|
25919
|
+
if (!res.ok) return { ok: false, error: gitFailure(res, "blame") };
|
|
25920
|
+
return { ok: true, value: capOutput(res.stdout.trimEnd()) };
|
|
25921
|
+
};
|
|
25922
|
+
var listFilesTool = async (ctx, args) => {
|
|
25923
|
+
const dir = validateRepoPath(args.dir, { allowRoot: true });
|
|
25924
|
+
if (!dir.ok) return dir;
|
|
25925
|
+
const glob = typeof args.glob === "string" && args.glob.length > 0 && args.glob.length <= 200 ? args.glob : null;
|
|
25926
|
+
const scope2 = dir.value || ".";
|
|
25927
|
+
const res = ctx.range.kind === "push" ? await git(ctx, ["--literal-pathspecs", "ls-tree", "-r", "--name-only", "-z", ctx.range.head, "--", scope2]) : await git(ctx, ["--literal-pathspecs", "ls-files", "-z", "--cached", "--", scope2]);
|
|
25928
|
+
if (!res.ok) return { ok: false, error: gitFailure(res, "listing") };
|
|
25929
|
+
const paths = res.stdout.split("\0").filter((p) => p && !isDeniedPath(p) && (!glob || globMatch2(glob, p)));
|
|
25930
|
+
const shown = paths.slice(0, TOOL_LIMITS.listEntries);
|
|
25931
|
+
const more = paths.length - shown.length;
|
|
25932
|
+
return { ok: true, value: shown.length ? `${shown.join("\n")}${more > 0 ? `
|
|
25933
|
+
\u2026 ${more} more` : ""}` : "no matching files" };
|
|
25934
|
+
};
|
|
25935
|
+
var searchTool = async (ctx, args) => {
|
|
25936
|
+
const text = args.text;
|
|
25937
|
+
if (typeof text !== "string" || text.length === 0 || text.length > TOOL_LIMITS.textChars || /[\n\r]/.test(text)) {
|
|
25938
|
+
return { ok: false, error: `text must be 1-${TOOL_LIMITS.textChars} characters on one line` };
|
|
25939
|
+
}
|
|
25940
|
+
const glob = typeof args.path_glob === "string" && args.path_glob.length > 0 ? args.path_glob : null;
|
|
25941
|
+
if (glob && (glob.length > 200 || /[-]/.test(glob) || glob.startsWith("-"))) {
|
|
25942
|
+
return { ok: false, error: "path_glob is not a valid glob" };
|
|
25943
|
+
}
|
|
25944
|
+
const pathspec = glob ? [`:(glob)${glob}`] : [];
|
|
25945
|
+
const revision = ctx.range.kind === "push" ? [ctx.range.head] : ["--cached"];
|
|
25946
|
+
const res = await git(ctx, ["grep", "-n", "-I", "-F", "--no-color", ...revision.filter((r) => r === "--cached"), "-e", text, ...revision.filter((r) => r !== "--cached"), "--", ...pathspec]);
|
|
25947
|
+
if (!res.ok) {
|
|
25948
|
+
if (res.error === "failed" && res.stderr.trim() === "") return { ok: true, value: "no matches" };
|
|
25949
|
+
return { ok: false, error: gitFailure(res, "search") };
|
|
25950
|
+
}
|
|
25951
|
+
const prefix = ctx.range.kind === "push" ? `${ctx.range.head}:` : "";
|
|
25952
|
+
const hits = [];
|
|
25953
|
+
for (const raw of res.stdout.split("\n")) {
|
|
25954
|
+
if (!raw) continue;
|
|
25955
|
+
const line = prefix && raw.startsWith(prefix) ? raw.slice(prefix.length) : raw;
|
|
25956
|
+
const file = line.slice(0, line.indexOf(":"));
|
|
25957
|
+
if (isDeniedPath(file)) continue;
|
|
25958
|
+
hits.push(line.length > TOOL_LIMITS.searchLineChars ? `${line.slice(0, TOOL_LIMITS.searchLineChars)}\u2026` : line);
|
|
25959
|
+
if (hits.length >= TOOL_LIMITS.searchHits) break;
|
|
25960
|
+
}
|
|
25961
|
+
return { ok: true, value: hits.length ? hits.join("\n") : "no matches" };
|
|
25962
|
+
};
|
|
25963
|
+
var TOOLS = {
|
|
25964
|
+
read_file: readFileTool,
|
|
25965
|
+
search: searchTool,
|
|
25966
|
+
list_files: listFilesTool,
|
|
25967
|
+
git_log: gitLogTool,
|
|
25968
|
+
git_blame: gitBlameTool,
|
|
25969
|
+
git_show: gitShowTool,
|
|
25970
|
+
git_diff: gitDiffTool
|
|
25971
|
+
};
|
|
25972
|
+
function makeToolExecutor(ctx) {
|
|
25973
|
+
return async (calls) => {
|
|
25974
|
+
const results = new Array(calls.length);
|
|
25975
|
+
let next = 0;
|
|
25976
|
+
const worker = async () => {
|
|
25977
|
+
while (next < calls.length) {
|
|
25978
|
+
const index = next++;
|
|
25979
|
+
const call = calls[index];
|
|
25980
|
+
const tool = TOOLS[call.name];
|
|
25981
|
+
if (!tool) {
|
|
25982
|
+
results[index] = { call_id: call.call_id, ok: false, error: `unknown tool ${call.name}` };
|
|
25983
|
+
continue;
|
|
25984
|
+
}
|
|
25985
|
+
try {
|
|
25986
|
+
const outcome = await tool(ctx, call.arguments ?? {});
|
|
25987
|
+
results[index] = outcome.ok ? { call_id: call.call_id, ok: true, output: capOutput(outcome.value) } : { call_id: call.call_id, ok: false, error: outcome.error };
|
|
25988
|
+
} catch (err) {
|
|
25989
|
+
results[index] = { call_id: call.call_id, ok: false, error: `tool failed: ${String(err?.message ?? err).slice(0, 160)}` };
|
|
25990
|
+
}
|
|
25991
|
+
}
|
|
25992
|
+
};
|
|
25993
|
+
await Promise.all(Array.from({ length: Math.min(TOOL_LIMITS.concurrency, calls.length) }, worker));
|
|
25994
|
+
return results;
|
|
25995
|
+
};
|
|
25996
|
+
}
|
|
25997
|
+
async function buildAgentDiffs(ctx, paths, limits = { perFileChars: 24e3, totalChars: 12e4 }) {
|
|
25998
|
+
const valid = paths.filter((p) => validateRepoPath(p).ok);
|
|
25999
|
+
if (valid.length === 0 || ctx.range.kind === "nothing") return [];
|
|
26000
|
+
if (ctx.range.kind === "push" && !ctx.range.base) return [];
|
|
26001
|
+
const args = ctx.range.kind === "push" ? ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", ctx.range.base, ctx.range.head] : ["diff", ...DIFF_READ_FLAGS, "--no-color", "-U10", "--cached"];
|
|
26002
|
+
const res = await git(ctx, ["--literal-pathspecs", ...args, "--", ...valid], { timeoutMs: TOOL_LIMITS.diffTimeoutMs, maxBuffer: 16 * 1024 * 1024 });
|
|
26003
|
+
if (!res.ok) return [];
|
|
26004
|
+
const out = [];
|
|
26005
|
+
let total = 0;
|
|
26006
|
+
for (const chunk of res.stdout.split(/^(?=diff --git )/m)) {
|
|
26007
|
+
if (!chunk.startsWith("diff --git ")) continue;
|
|
26008
|
+
const plus = /^\+\+\+ b\/(.+)$/m.exec(chunk);
|
|
26009
|
+
const minus = /^--- a\/(.+)$/m.exec(chunk);
|
|
26010
|
+
const path = plus?.[1] ?? minus?.[1] ?? /^diff --git a\/(\S+)/.exec(chunk)?.[1];
|
|
26011
|
+
if (!path || isDeniedPath(path)) continue;
|
|
26012
|
+
const room = Math.min(limits.perFileChars, limits.totalChars - total);
|
|
26013
|
+
if (room <= 0) break;
|
|
26014
|
+
const diff = chunk.length > room ? chunk.slice(0, room) : chunk;
|
|
26015
|
+
total += diff.length;
|
|
26016
|
+
out.push({ path, diff });
|
|
26017
|
+
}
|
|
26018
|
+
return out;
|
|
26019
|
+
}
|
|
26020
|
+
|
|
26021
|
+
// src/commands/guard.ts
|
|
26022
|
+
var EXCERPT_SOURCE_MAX_BYTES = 2 * 1024 * 1024;
|
|
24821
26023
|
var GUARD_BLOCK_CAP = 2;
|
|
24822
|
-
var GUARD_ITER_FILE = (0,
|
|
26024
|
+
var GUARD_ITER_FILE = (0, import_node_path35.join)(VERITY_DIR, ".guard-iteration");
|
|
24823
26025
|
function readPreToolUseStdin() {
|
|
24824
26026
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
24825
|
-
return new Promise((
|
|
26027
|
+
return new Promise((resolve6) => {
|
|
24826
26028
|
try {
|
|
24827
|
-
if (process.stdin.isTTY) return
|
|
26029
|
+
if (process.stdin.isTTY) return resolve6(empty);
|
|
24828
26030
|
const chunks = [];
|
|
24829
26031
|
let timer;
|
|
24830
26032
|
let settled = false;
|
|
@@ -24837,7 +26039,7 @@ function readPreToolUseStdin() {
|
|
|
24837
26039
|
process.stdin.removeListener("end", onEnd);
|
|
24838
26040
|
process.stdin.removeListener("error", onError);
|
|
24839
26041
|
process.stdin.pause();
|
|
24840
|
-
|
|
26042
|
+
resolve6(value);
|
|
24841
26043
|
};
|
|
24842
26044
|
const onEnd = () => {
|
|
24843
26045
|
try {
|
|
@@ -24858,7 +26060,7 @@ function readPreToolUseStdin() {
|
|
|
24858
26060
|
process.stdin.on("error", onError);
|
|
24859
26061
|
process.stdin.resume();
|
|
24860
26062
|
} catch {
|
|
24861
|
-
|
|
26063
|
+
resolve6(empty);
|
|
24862
26064
|
}
|
|
24863
26065
|
});
|
|
24864
26066
|
}
|
|
@@ -24970,8 +26172,13 @@ function hasBlockingFinding(response, sentFiles) {
|
|
|
24970
26172
|
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
24971
26173
|
const analyzable = filterAnalyzable(files);
|
|
24972
26174
|
const securityFiles = filterSecurity(files);
|
|
24973
|
-
|
|
24974
|
-
|
|
26175
|
+
let staticResults;
|
|
26176
|
+
if (isCodacyAvailable()) {
|
|
26177
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs49.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
|
|
26178
|
+
staticResults = runCodacyAnalysis(scannable);
|
|
26179
|
+
} else {
|
|
26180
|
+
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
26181
|
+
}
|
|
24975
26182
|
const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
|
|
24976
26183
|
const requestBody = {
|
|
24977
26184
|
static_results: staticResults,
|
|
@@ -25034,12 +26241,103 @@ function coverageBlock(c) {
|
|
|
25034
26241
|
}
|
|
25035
26242
|
function emitAllowNotice(userMsg, agentMsg) {
|
|
25036
26243
|
process.stdout.write(JSON.stringify({
|
|
25037
|
-
systemMessage: userMsg,
|
|
25038
|
-
hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: agentMsg }
|
|
26244
|
+
systemMessage: terminalText(userMsg, 8e3),
|
|
26245
|
+
hookSpecificOutput: { hookEventName: "PreToolUse", additionalContext: terminalText(agentMsg, 4e4) }
|
|
25039
26246
|
}) + "\n");
|
|
25040
26247
|
process.exit(0);
|
|
25041
26248
|
}
|
|
26249
|
+
var GATES = ["PASS", "WARN", "FAIL"];
|
|
26250
|
+
function worstGate(...gates) {
|
|
26251
|
+
const rank2 = (gate) => {
|
|
26252
|
+
const index = GATES.indexOf(String(gate ?? "").toUpperCase());
|
|
26253
|
+
return index === -1 ? 1 : index;
|
|
26254
|
+
};
|
|
26255
|
+
return GATES[Math.max(...gates.map(rank2))];
|
|
26256
|
+
}
|
|
26257
|
+
function oneShotLostOutcome(input) {
|
|
26258
|
+
return input.agentGate === "FAIL" && input.agentBlocking ? "block" : "allow-without-review";
|
|
26259
|
+
}
|
|
26260
|
+
function withAgentVerdict(oneShot, agent) {
|
|
26261
|
+
if (agent.one_shot_source === "none" && typeof oneShot.gate_decision === "string") {
|
|
26262
|
+
const added = agent.findings.filter((f) => f.agent_added === true);
|
|
26263
|
+
return {
|
|
26264
|
+
...oneShot,
|
|
26265
|
+
gate_decision: worstGate(oneShot.gate_decision, agent.gate_decision ?? "WARN", "WARN"),
|
|
26266
|
+
findings: [...Array.isArray(oneShot.findings) ? oneShot.findings : [], ...added],
|
|
26267
|
+
agent_review: {
|
|
26268
|
+
...agent.agent_review,
|
|
26269
|
+
gate_decision_before: oneShot.gate_decision,
|
|
26270
|
+
one_shot_source: "none",
|
|
26271
|
+
cleared: [],
|
|
26272
|
+
downgraded: []
|
|
26273
|
+
}
|
|
26274
|
+
};
|
|
26275
|
+
}
|
|
26276
|
+
return {
|
|
26277
|
+
...oneShot,
|
|
26278
|
+
gate_decision: agent.gate_decision,
|
|
26279
|
+
findings: agent.findings,
|
|
26280
|
+
agent_review: {
|
|
26281
|
+
...agent.agent_review,
|
|
26282
|
+
gate_decision_before: agent.gate_decision_before,
|
|
26283
|
+
one_shot_source: agent.one_shot_source,
|
|
26284
|
+
cleared: agent.cleared,
|
|
26285
|
+
downgraded: agent.downgraded ?? []
|
|
26286
|
+
}
|
|
26287
|
+
};
|
|
26288
|
+
}
|
|
26289
|
+
function describeAgentOutcome(outcome) {
|
|
26290
|
+
if (!outcome || outcome.kind === "disabled") return null;
|
|
26291
|
+
if (outcome.kind !== "finished") return `Agent review: not used (${outcome.reason})`;
|
|
26292
|
+
const { agent_review: r, gate_decision: after, gate_decision_before: before } = outcome.response;
|
|
26293
|
+
const checks = Number(r.code_runtime_checks ?? 0) + Number(r.container_checks ?? 0);
|
|
26294
|
+
const verdict = before && after && before !== after ? `, verdict changed from ${before} to ${after}` : "";
|
|
26295
|
+
return `Agent review: ${r.outcome}, ${r.turns} turn(s), ${r.local_calls} read(s), ${checks} code check(s), ${r.cleared} cleared, ${Number(r.downgraded ?? 0)} downgraded, ${r.added} added${verdict}`;
|
|
26296
|
+
}
|
|
26297
|
+
function startAgentReview(input) {
|
|
26298
|
+
if (process.env.VERITY_GIT_MOMENT_AGENT === "off") return null;
|
|
26299
|
+
const ctx = { root: input.root, range: input.range };
|
|
26300
|
+
const http = {
|
|
26301
|
+
async post(path, body, timeoutMs) {
|
|
26302
|
+
const res = await apiRequest({
|
|
26303
|
+
method: "POST",
|
|
26304
|
+
path,
|
|
26305
|
+
serviceUrl: input.serviceUrl,
|
|
26306
|
+
token: input.token,
|
|
26307
|
+
body,
|
|
26308
|
+
timeout: timeoutMs,
|
|
26309
|
+
cmd: "guard-agent",
|
|
26310
|
+
verbose: input.verbose,
|
|
26311
|
+
encodeBody: true
|
|
26312
|
+
});
|
|
26313
|
+
return res.ok ? { ok: true, data: res.data } : { ok: false, error: res.error };
|
|
26314
|
+
}
|
|
26315
|
+
};
|
|
26316
|
+
const sentPaths = input.codeDelta.files.map((f) => f.path);
|
|
26317
|
+
const run2 = async () => runAgentReview({
|
|
26318
|
+
http,
|
|
26319
|
+
executeTools: makeToolExecutor(ctx),
|
|
26320
|
+
startedAt: input.hookStartedAt,
|
|
26321
|
+
deadlineMs: AGENT_DEADLINE_MS[input.moment],
|
|
26322
|
+
start: {
|
|
26323
|
+
moment: input.moment,
|
|
26324
|
+
iteration: input.iteration,
|
|
26325
|
+
session_id: input.sessionId,
|
|
26326
|
+
stated_intent: input.statedIntent,
|
|
26327
|
+
changed_files: input.files,
|
|
26328
|
+
sent_paths: sentPaths,
|
|
26329
|
+
diffs: await buildAgentDiffs(ctx, sentPaths),
|
|
26330
|
+
repo_context: input.requestBody.repo_context,
|
|
26331
|
+
static_results: input.requestBody.static_results,
|
|
26332
|
+
one_shot_chars: input.codeDelta.files.reduce((n, f) => n + f.content.length, 0)
|
|
26333
|
+
},
|
|
26334
|
+
oneShot: input.oneShot.then((r) => r.ok ? { ok: true, response: r.data } : { ok: false }),
|
|
26335
|
+
sessionId: input.sessionId
|
|
26336
|
+
});
|
|
26337
|
+
return run2().catch((err) => ({ kind: "failed", reason: String(err?.message ?? err) }));
|
|
26338
|
+
}
|
|
25042
26339
|
async function runGuard(opts, globals) {
|
|
26340
|
+
const hookStartedAt = Date.now();
|
|
25043
26341
|
const on = resolveGuardMoments(opts.on);
|
|
25044
26342
|
if (on.length === 0) process.exit(0);
|
|
25045
26343
|
const { command, cwd, sessionId } = await readPreToolUseStdin();
|
|
@@ -25111,12 +26409,12 @@ async function runGuard(opts, globals) {
|
|
|
25111
26409
|
cwd: frame.worktreeRoot ?? process.cwd()
|
|
25112
26410
|
});
|
|
25113
26411
|
upgradeToExcerpts(repoContext2, {
|
|
26412
|
+
// Rooted, no-follow read from the validated descriptor (safe-read.ts):
|
|
26413
|
+
// site paths come from a search, and a site under a symlinked directory
|
|
26414
|
+
// must not pull a file from outside the tree into the request.
|
|
25114
26415
|
readFile: (rel) => {
|
|
25115
|
-
|
|
25116
|
-
|
|
25117
|
-
} catch {
|
|
25118
|
-
return null;
|
|
25119
|
-
}
|
|
26416
|
+
const read = readRegularFileInRoot(frame.worktreeRoot ?? process.cwd(), rel, EXCERPT_SOURCE_MAX_BYTES);
|
|
26417
|
+
return read.ok ? read.content : null;
|
|
25120
26418
|
}
|
|
25121
26419
|
});
|
|
25122
26420
|
requestBody.repo_context = repoContext2;
|
|
@@ -25127,6 +26425,7 @@ async function runGuard(opts, globals) {
|
|
|
25127
26425
|
symbols: repoContext2.symbols?.length ?? 0,
|
|
25128
26426
|
callers: repoContext2.callers?.length ?? 0,
|
|
25129
26427
|
tests: repoContext2.tests?.length ?? 0,
|
|
26428
|
+
importers: repoContext2.importers?.length ?? 0,
|
|
25130
26429
|
excerpts: repoContext2.excerpts?.length ?? 0,
|
|
25131
26430
|
elapsed_ms: repoContext2.elapsed_ms ?? null
|
|
25132
26431
|
});
|
|
@@ -25145,7 +26444,7 @@ async function runGuard(opts, globals) {
|
|
|
25145
26444
|
};
|
|
25146
26445
|
logToFileOnly(coverageBlock(coverage));
|
|
25147
26446
|
const reviewStart = Date.now();
|
|
25148
|
-
const
|
|
26447
|
+
const oneShot = analyzeRequest({
|
|
25149
26448
|
serviceUrl: urlResult.data,
|
|
25150
26449
|
token: tokenResult.data.token,
|
|
25151
26450
|
body: requestBody,
|
|
@@ -25153,22 +26452,51 @@ async function runGuard(opts, globals) {
|
|
|
25153
26452
|
timeout: 29e4,
|
|
25154
26453
|
cmd: "guard"
|
|
25155
26454
|
});
|
|
26455
|
+
const agent = startAgentReview({
|
|
26456
|
+
serviceUrl: urlResult.data,
|
|
26457
|
+
token: tokenResult.data.token,
|
|
26458
|
+
verbose: globals.verbose,
|
|
26459
|
+
hookStartedAt,
|
|
26460
|
+
moment,
|
|
26461
|
+
root: frame.worktreeRoot,
|
|
26462
|
+
range,
|
|
26463
|
+
iteration: iter + 1,
|
|
26464
|
+
sessionId,
|
|
26465
|
+
statedIntent,
|
|
26466
|
+
files,
|
|
26467
|
+
codeDelta,
|
|
26468
|
+
requestBody,
|
|
26469
|
+
oneShot
|
|
26470
|
+
});
|
|
26471
|
+
const result = await oneShot;
|
|
26472
|
+
const agentOutcome = agent ? await agent : null;
|
|
26473
|
+
const agentVerdict = agentOutcome?.kind === "finished" && agentOutcome.response.gate_decision ? agentOutcome.response : null;
|
|
25156
26474
|
const reviewSecs = Math.max(1, Math.round((Date.now() - reviewStart) / 1e3));
|
|
26475
|
+
const oneShotResponse = result.ok ? result.data : {};
|
|
26476
|
+
const response = agentVerdict ? withAgentVerdict(oneShotResponse, agentVerdict) : oneShotResponse;
|
|
26477
|
+
if (opts.json) process.stderr.write(JSON.stringify(response) + "\n");
|
|
26478
|
+
const decision = response.gate_decision ?? "(unrecognised)";
|
|
26479
|
+
const viewUrl = response.view_url ?? "";
|
|
26480
|
+
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
26481
|
+
const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
|
|
26482
|
+
const agentLine = describeAgentOutcome(agentOutcome);
|
|
26483
|
+
const covDetail = agentLine ? `${coverageBlock(coverage)}
|
|
26484
|
+
${agentLine}` : coverageBlock(coverage);
|
|
26485
|
+
const witnessed = [...codeDelta.files.map((f) => f.path), ...agentVerdict?.witnessed_files ?? []];
|
|
25157
26486
|
if (!result.ok) {
|
|
25158
26487
|
const authRemedy = result.error.startsWith("STALE_VERIFICATION") ? " Your GitHub verification expired \u2014 run `verity login` to re-verify." : result.error.startsWith("FORBIDDEN") ? " No access grant for this repository \u2014 run `verity login` to refresh your grants." : result.error.startsWith("INVALID_TOKEN") ? " Your Verity login expired or was revoked \u2014 run `verity login` to sign in again." : "";
|
|
26488
|
+
if (oneShotLostOutcome({ agentGate: decision, agentBlocking: hasBlockingFinding(response, witnessed) }) === "block") {
|
|
26489
|
+
writeIter(moment, iter + 1);
|
|
26490
|
+
writeBlockMessage(moment, response, covDetail);
|
|
26491
|
+
process.exit(2);
|
|
26492
|
+
}
|
|
26493
|
+
const agentNote = agentLine ? ` ${agentLine}, but it reviewed only part of the change.` : "";
|
|
25159
26494
|
emitAllowNotice(
|
|
25160
|
-
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "
|
|
25161
|
-
`Verity ${moment}:
|
|
26495
|
+
`\u26A0 Verity ${moment}: ${authRemedy ? "not authorized" : "no single-pass review"} \u2014 ${verb}ed WITHOUT review${authRemedy}`,
|
|
26496
|
+
`Verity ${moment}: the single-pass review did not come back (${result.error}); the ${verb} was allowed WITHOUT a Verity review.${agentNote}${authRemedy}`
|
|
25162
26497
|
);
|
|
25163
26498
|
}
|
|
25164
|
-
if (
|
|
25165
|
-
const response = result.data;
|
|
25166
|
-
const decision = response.gate_decision ?? "(unrecognised)";
|
|
25167
|
-
const viewUrl = response.view_url ?? "";
|
|
25168
|
-
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
25169
|
-
const covLine = `${coverageSummary(coverage)} in ${reviewSecs}s`;
|
|
25170
|
-
const covDetail = coverageBlock(coverage);
|
|
25171
|
-
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
26499
|
+
if (decision === "FAIL" && hasBlockingFinding(response, witnessed)) {
|
|
25172
26500
|
writeIter(moment, iter + 1);
|
|
25173
26501
|
writeBlockMessage(moment, response, covDetail);
|
|
25174
26502
|
process.exit(2);
|
|
@@ -25187,12 +26515,16 @@ async function runGuard(opts, globals) {
|
|
|
25187
26515
|
emitAllowNotice(notice.user, notice.agent);
|
|
25188
26516
|
}
|
|
25189
26517
|
function verdictNotice(ctx) {
|
|
25190
|
-
const { moment, verb
|
|
25191
|
-
const
|
|
26518
|
+
const { moment, verb } = ctx;
|
|
26519
|
+
const covLine = terminalText(ctx.covLine, 500);
|
|
26520
|
+
const covDetail = terminalText(ctx.covDetail, 2e4);
|
|
26521
|
+
const link = terminalText(ctx.link, 2100);
|
|
26522
|
+
const viewUrl = terminalUrl(ctx.viewUrl);
|
|
26523
|
+
const decision = ctx.decision == null ? "(unrecognised)" : terminalText(ctx.decision, 60);
|
|
25192
26524
|
const report = viewUrl ? `
|
|
25193
26525
|
Report: ${viewUrl}` : "";
|
|
25194
26526
|
if (decision === "FAIL") {
|
|
25195
|
-
const narrative = ctx.narrative
|
|
26527
|
+
const narrative = terminalText(ctx.narrative, 4e3);
|
|
25196
26528
|
return {
|
|
25197
26529
|
user: `\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding (${covLine})${link}`,
|
|
25198
26530
|
agent: `Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
|
|
@@ -25223,9 +26555,9 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
25223
26555
|
const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
|
|
25224
26556
|
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
25225
26557
|
const assessment = response.assessment;
|
|
25226
|
-
const narrative = assessment?.narrative
|
|
25227
|
-
const findings = response.findings
|
|
25228
|
-
const viewUrl = response.view_url
|
|
26558
|
+
const narrative = terminalText(assessment?.narrative, 4e3);
|
|
26559
|
+
const findings = Array.isArray(response.findings) ? response.findings : [];
|
|
26560
|
+
const viewUrl = terminalUrl(response.view_url);
|
|
25229
26561
|
process.stderr.write(`${RED}${BOLD}\u2501\u2501\u2501 Verity ${label2} gate: FAIL \u2501\u2501\u2501${NC}
|
|
25230
26562
|
|
|
25231
26563
|
`);
|
|
@@ -25238,18 +26570,18 @@ function writeBlockMessage(moment, response, covDetail) {
|
|
|
25238
26570
|
|
|
25239
26571
|
`);
|
|
25240
26572
|
for (const f of agentFindings) {
|
|
25241
|
-
const severity = (f.severity
|
|
26573
|
+
const severity = terminalText(f.severity, 20).toUpperCase();
|
|
25242
26574
|
const fix = f.fix;
|
|
25243
|
-
process.stderr.write(`[${severity}] ${f.title
|
|
26575
|
+
process.stderr.write(`[${severity}] ${terminalText(f.title, 300)}
|
|
25244
26576
|
`);
|
|
25245
|
-
process.stderr.write(` File: ${f.file
|
|
26577
|
+
process.stderr.write(` File: ${terminalText(f.file, 400)}:${terminalText(String(f.line ?? ""), 12)}
|
|
25246
26578
|
`);
|
|
25247
|
-
process.stderr.write(` Fix: ${fix?.description
|
|
26579
|
+
process.stderr.write(` Fix: ${terminalText(fix?.description, 2e3) || "See description"}
|
|
25248
26580
|
|
|
25249
26581
|
`);
|
|
25250
26582
|
}
|
|
25251
26583
|
}
|
|
25252
|
-
process.stderr.write(`${DIM}${covDetail}${NC}
|
|
26584
|
+
process.stderr.write(`${DIM}${terminalText(covDetail, 2e4)}${NC}
|
|
25253
26585
|
|
|
25254
26586
|
`);
|
|
25255
26587
|
if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
|
|
@@ -25410,10 +26742,10 @@ function registerWaiveCommand(program2) {
|
|
|
25410
26742
|
|
|
25411
26743
|
// src/commands/init.ts
|
|
25412
26744
|
var import_node_fs54 = require("node:fs");
|
|
25413
|
-
var
|
|
26745
|
+
var import_promises19 = require("node:fs/promises");
|
|
25414
26746
|
var import_yaml6 = __toESM(require_dist());
|
|
25415
|
-
var
|
|
25416
|
-
var
|
|
26747
|
+
var import_node_path38 = require("node:path");
|
|
26748
|
+
var import_node_child_process17 = require("node:child_process");
|
|
25417
26749
|
|
|
25418
26750
|
// src/lib/banner.ts
|
|
25419
26751
|
var WORDMARK = [
|
|
@@ -25494,10 +26826,15 @@ function printPhase(n, of, title, subtitle) {
|
|
|
25494
26826
|
var import_node_fs51 = require("node:fs");
|
|
25495
26827
|
|
|
25496
26828
|
// src/lib/prereqs.ts
|
|
25497
|
-
var
|
|
26829
|
+
var import_node_child_process15 = require("node:child_process");
|
|
25498
26830
|
var MIN_NODE_MAJOR = 20;
|
|
25499
26831
|
function which(bin) {
|
|
25500
|
-
|
|
26832
|
+
try {
|
|
26833
|
+
const out = (0, import_node_child_process15.execSync)(`command -v ${bin}`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
26834
|
+
return out || null;
|
|
26835
|
+
} catch {
|
|
26836
|
+
return null;
|
|
26837
|
+
}
|
|
25501
26838
|
}
|
|
25502
26839
|
function checkNode() {
|
|
25503
26840
|
const version = process.version;
|
|
@@ -25515,7 +26852,7 @@ function checkNode() {
|
|
|
25515
26852
|
function checkGit() {
|
|
25516
26853
|
let detail = "";
|
|
25517
26854
|
try {
|
|
25518
|
-
detail = (0,
|
|
26855
|
+
detail = (0, import_node_child_process15.execSync)("git --version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
25519
26856
|
} catch {
|
|
25520
26857
|
return {
|
|
25521
26858
|
id: "git",
|
|
@@ -25552,13 +26889,13 @@ function checkAnalysisCli() {
|
|
|
25552
26889
|
}
|
|
25553
26890
|
var INSTALL_TIMEOUT_MS = 12e4;
|
|
25554
26891
|
function run(command, args, opts = {}) {
|
|
25555
|
-
return new Promise((
|
|
25556
|
-
const child = (0,
|
|
26892
|
+
return new Promise((resolve6) => {
|
|
26893
|
+
const child = (0, import_node_child_process15.spawn)(command, args, {
|
|
25557
26894
|
stdio: opts.inherit ? "inherit" : "pipe",
|
|
25558
26895
|
timeout: INSTALL_TIMEOUT_MS
|
|
25559
26896
|
});
|
|
25560
|
-
child.on("error", () =>
|
|
25561
|
-
child.on("close", (code) =>
|
|
26897
|
+
child.on("error", () => resolve6(false));
|
|
26898
|
+
child.on("close", (code) => resolve6(code === 0));
|
|
25562
26899
|
});
|
|
25563
26900
|
}
|
|
25564
26901
|
async function installAnalysisCli() {
|
|
@@ -25602,7 +26939,7 @@ async function checkPrereqs(opts = {}) {
|
|
|
25602
26939
|
}
|
|
25603
26940
|
|
|
25604
26941
|
// src/lib/telemetry.ts
|
|
25605
|
-
var
|
|
26942
|
+
var import_promises17 = require("node:fs/promises");
|
|
25606
26943
|
var SETTINGS_LOCAL_FILE2 = ".claude/settings.local.json";
|
|
25607
26944
|
var GITIGNORE_FILE = ".gitignore";
|
|
25608
26945
|
var GITIGNORE_ENTRY = SETTINGS_LOCAL_IGNORE_ENTRY;
|
|
@@ -25632,7 +26969,7 @@ function buildTelemetryEnv(serviceUrl) {
|
|
|
25632
26969
|
var VERITY_TELEMETRY_KEYS = Object.keys(buildTelemetryEnv(""));
|
|
25633
26970
|
async function readSettingsLocal() {
|
|
25634
26971
|
try {
|
|
25635
|
-
return JSON.parse(await (0,
|
|
26972
|
+
return JSON.parse(await (0, import_promises17.readFile)(projectPath(SETTINGS_LOCAL_FILE2), "utf-8"));
|
|
25636
26973
|
} catch {
|
|
25637
26974
|
return {};
|
|
25638
26975
|
}
|
|
@@ -25644,7 +26981,7 @@ async function ensureGitignore() {
|
|
|
25644
26981
|
const file = projectPath(GITIGNORE_FILE);
|
|
25645
26982
|
let content = "";
|
|
25646
26983
|
try {
|
|
25647
|
-
content = await (0,
|
|
26984
|
+
content = await (0, import_promises17.readFile)(file, "utf-8");
|
|
25648
26985
|
} catch {
|
|
25649
26986
|
}
|
|
25650
26987
|
const lines = content.split("\n").map((l) => l.trim());
|
|
@@ -25653,7 +26990,7 @@ async function ensureGitignore() {
|
|
|
25653
26990
|
}
|
|
25654
26991
|
const block = "# Verity telemetry \u2014 machine-local Claude Code settings\n" + GITIGNORE_ENTRY + "\n";
|
|
25655
26992
|
const next = content ? content + (content.endsWith("\n") ? "" : "\n") + "\n" + block : block;
|
|
25656
|
-
await (0,
|
|
26993
|
+
await (0, import_promises17.writeFile)(file, next);
|
|
25657
26994
|
}
|
|
25658
26995
|
async function installTelemetry(serviceUrl) {
|
|
25659
26996
|
const env = buildTelemetryEnv(serviceUrl);
|
|
@@ -25834,15 +27171,15 @@ function registerDoctorCommand(program2) {
|
|
|
25834
27171
|
|
|
25835
27172
|
// src/commands/migrate.ts
|
|
25836
27173
|
var import_node_fs52 = require("node:fs");
|
|
25837
|
-
var
|
|
25838
|
-
var
|
|
27174
|
+
var import_node_path36 = require("node:path");
|
|
27175
|
+
var import_node_child_process16 = require("node:child_process");
|
|
25839
27176
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
25840
27177
|
function defaultNpmRemover(pkg) {
|
|
25841
|
-
(0,
|
|
27178
|
+
(0, import_node_child_process16.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
25842
27179
|
}
|
|
25843
27180
|
function isGitTracked(cwd, relPath) {
|
|
25844
27181
|
try {
|
|
25845
|
-
(0,
|
|
27182
|
+
(0, import_node_child_process16.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
25846
27183
|
return true;
|
|
25847
27184
|
} catch {
|
|
25848
27185
|
return false;
|
|
@@ -25850,7 +27187,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
25850
27187
|
}
|
|
25851
27188
|
function isGitRepo(cwd) {
|
|
25852
27189
|
try {
|
|
25853
|
-
(0,
|
|
27190
|
+
(0, import_node_child_process16.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
25854
27191
|
return true;
|
|
25855
27192
|
} catch {
|
|
25856
27193
|
return false;
|
|
@@ -25871,8 +27208,8 @@ async function runMigration(opts = {}) {
|
|
|
25871
27208
|
return { actions, migrated: actions.length > 0 };
|
|
25872
27209
|
}
|
|
25873
27210
|
function migrateProjectDir(root, actions) {
|
|
25874
|
-
const gateDir = (0,
|
|
25875
|
-
const verityDir = (0,
|
|
27211
|
+
const gateDir = (0, import_node_path36.join)(root, ".gate");
|
|
27212
|
+
const verityDir = (0, import_node_path36.join)(root, ".verity");
|
|
25876
27213
|
if ((0, import_node_fs52.existsSync)(gateDir) && !(0, import_node_fs52.existsSync)(verityDir)) {
|
|
25877
27214
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
25878
27215
|
}
|
|
@@ -25890,7 +27227,7 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
25890
27227
|
);
|
|
25891
27228
|
}
|
|
25892
27229
|
try {
|
|
25893
|
-
(0,
|
|
27230
|
+
(0, import_node_child_process16.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
25894
27231
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
25895
27232
|
moved = true;
|
|
25896
27233
|
} catch {
|
|
@@ -25926,11 +27263,11 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
25926
27263
|
}
|
|
25927
27264
|
function migrateGlobalCredentials(home, actions) {
|
|
25928
27265
|
if (!home) return;
|
|
25929
|
-
const gateCreds = (0,
|
|
25930
|
-
const verityCreds = (0,
|
|
27266
|
+
const gateCreds = (0, import_node_path36.join)(home, ".gate", "credentials");
|
|
27267
|
+
const verityCreds = (0, import_node_path36.join)(home, ".verity", "credentials");
|
|
25931
27268
|
if (!(0, import_node_fs52.existsSync)(gateCreds)) return;
|
|
25932
27269
|
if (!(0, import_node_fs52.existsSync)(verityCreds)) {
|
|
25933
|
-
(0, import_node_fs52.mkdirSync)((0,
|
|
27270
|
+
(0, import_node_fs52.mkdirSync)((0, import_node_path36.join)(home, ".verity"), { recursive: true });
|
|
25934
27271
|
moveFile(gateCreds, verityCreds);
|
|
25935
27272
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
25936
27273
|
return;
|
|
@@ -25952,7 +27289,7 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
25952
27289
|
}
|
|
25953
27290
|
}
|
|
25954
27291
|
async function migrateClaudeMd(root, actions) {
|
|
25955
|
-
const claudeMd = (0,
|
|
27292
|
+
const claudeMd = (0, import_node_path36.join)(root, "CLAUDE.md");
|
|
25956
27293
|
const hadLegacyBlock = (0, import_node_fs52.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
25957
27294
|
if (!hadLegacyBlock) return;
|
|
25958
27295
|
try {
|
|
@@ -25963,13 +27300,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
25963
27300
|
}
|
|
25964
27301
|
}
|
|
25965
27302
|
function migrateStandardFile(root, actions) {
|
|
25966
|
-
const gateMd = (0,
|
|
25967
|
-
const verityMd = (0,
|
|
27303
|
+
const gateMd = (0, import_node_path36.join)(root, "GATE.md");
|
|
27304
|
+
const verityMd = (0, import_node_path36.join)(root, "VERITY.md");
|
|
25968
27305
|
if (!(0, import_node_fs52.existsSync)(gateMd) || (0, import_node_fs52.existsSync)(verityMd)) return;
|
|
25969
27306
|
let moved = false;
|
|
25970
27307
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
25971
27308
|
try {
|
|
25972
|
-
(0,
|
|
27309
|
+
(0, import_node_child_process16.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
25973
27310
|
moved = true;
|
|
25974
27311
|
} catch {
|
|
25975
27312
|
}
|
|
@@ -25981,7 +27318,7 @@ function migrateStandardFile(root, actions) {
|
|
|
25981
27318
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
25982
27319
|
}
|
|
25983
27320
|
async function migrateTelemetryHeaders(root, actions) {
|
|
25984
|
-
const file = (0,
|
|
27321
|
+
const file = (0, import_node_path36.join)(root, ".claude", "settings.local.json");
|
|
25985
27322
|
if (!(0, import_node_fs52.existsSync)(file)) return;
|
|
25986
27323
|
let settings;
|
|
25987
27324
|
try {
|
|
@@ -26029,8 +27366,8 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
26029
27366
|
toAppend.push(line.replace(/\r$/, ""));
|
|
26030
27367
|
}
|
|
26031
27368
|
if (toAppend.length > 0) {
|
|
26032
|
-
const
|
|
26033
|
-
(0, import_node_fs52.writeFileSync)(verityCreds, verityContent +
|
|
27369
|
+
const sep4 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
27370
|
+
(0, import_node_fs52.writeFileSync)(verityCreds, verityContent + sep4 + toAppend.join("\n") + "\n");
|
|
26034
27371
|
}
|
|
26035
27372
|
(0, import_node_fs52.rmSync)(gateCreds, { force: true });
|
|
26036
27373
|
return toAppend.length;
|
|
@@ -26044,7 +27381,7 @@ function readFileSyncSafe(path) {
|
|
|
26044
27381
|
}
|
|
26045
27382
|
function hasStagedChanges(root) {
|
|
26046
27383
|
try {
|
|
26047
|
-
(0,
|
|
27384
|
+
(0, import_node_child_process16.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
26048
27385
|
return false;
|
|
26049
27386
|
} catch {
|
|
26050
27387
|
return true;
|
|
@@ -26071,15 +27408,15 @@ function moveFile(from, to) {
|
|
|
26071
27408
|
function carryLegacyContents(gateDir, verityDir) {
|
|
26072
27409
|
let copied = 0;
|
|
26073
27410
|
const walk2 = (relDir) => {
|
|
26074
|
-
const srcDir = (0,
|
|
27411
|
+
const srcDir = (0, import_node_path36.join)(gateDir, relDir);
|
|
26075
27412
|
for (const entry of (0, import_node_fs52.readdirSync)(srcDir)) {
|
|
26076
|
-
const rel = relDir ? (0,
|
|
26077
|
-
const src = (0,
|
|
26078
|
-
const dest = (0,
|
|
27413
|
+
const rel = relDir ? (0, import_node_path36.join)(relDir, entry) : entry;
|
|
27414
|
+
const src = (0, import_node_path36.join)(gateDir, rel);
|
|
27415
|
+
const dest = (0, import_node_path36.join)(verityDir, rel);
|
|
26079
27416
|
if ((0, import_node_fs52.statSync)(src).isDirectory()) {
|
|
26080
27417
|
walk2(rel);
|
|
26081
27418
|
} else if (!(0, import_node_fs52.existsSync)(dest)) {
|
|
26082
|
-
(0, import_node_fs52.mkdirSync)((0,
|
|
27419
|
+
(0, import_node_fs52.mkdirSync)((0, import_node_path36.dirname)(dest), { recursive: true });
|
|
26083
27420
|
(0, import_node_fs52.cpSync)(src, dest);
|
|
26084
27421
|
copied++;
|
|
26085
27422
|
}
|
|
@@ -26089,22 +27426,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
26089
27426
|
return copied;
|
|
26090
27427
|
}
|
|
26091
27428
|
async function needsMigration(root = repoRoot()) {
|
|
26092
|
-
const gateDir = (0,
|
|
26093
|
-
const verityDir = (0,
|
|
27429
|
+
const gateDir = (0, import_node_path36.join)(root, ".gate");
|
|
27430
|
+
const verityDir = (0, import_node_path36.join)(root, ".verity");
|
|
26094
27431
|
if ((0, import_node_fs52.existsSync)(gateDir) && !(0, import_node_fs52.existsSync)(verityDir)) return true;
|
|
26095
27432
|
if ((0, import_node_fs52.existsSync)(gateDir) && (0, import_node_fs52.existsSync)(verityDir)) {
|
|
26096
|
-
if ((0, import_node_fs52.existsSync)((0,
|
|
27433
|
+
if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(gateDir, "credentials")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(verityDir, "credentials"))) {
|
|
26097
27434
|
return true;
|
|
26098
27435
|
}
|
|
26099
|
-
if ((0, import_node_fs52.existsSync)((0,
|
|
27436
|
+
if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(gateDir, "memory")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(verityDir, "memory"))) {
|
|
26100
27437
|
return true;
|
|
26101
27438
|
}
|
|
26102
27439
|
}
|
|
26103
|
-
const claudeMd = (0,
|
|
27440
|
+
const claudeMd = (0, import_node_path36.join)(root, "CLAUDE.md");
|
|
26104
27441
|
if ((0, import_node_fs52.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
26105
27442
|
return true;
|
|
26106
27443
|
}
|
|
26107
|
-
if ((0, import_node_fs52.existsSync)((0,
|
|
27444
|
+
if ((0, import_node_fs52.existsSync)((0, import_node_path36.join)(root, "GATE.md")) && !(0, import_node_fs52.existsSync)((0, import_node_path36.join)(root, "VERITY.md"))) {
|
|
26108
27445
|
return true;
|
|
26109
27446
|
}
|
|
26110
27447
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -26233,7 +27570,7 @@ function runSelect(opts) {
|
|
|
26233
27570
|
cursor: initialIdx[0] ?? 0,
|
|
26234
27571
|
chosen: new Set(mode2 === "single" ? [initialIdx[0] ?? 0] : initialIdx)
|
|
26235
27572
|
};
|
|
26236
|
-
return new Promise((
|
|
27573
|
+
return new Promise((resolve6) => {
|
|
26237
27574
|
let painted = 0;
|
|
26238
27575
|
let settled = false;
|
|
26239
27576
|
const draw = () => {
|
|
@@ -26281,7 +27618,7 @@ function runSelect(opts) {
|
|
|
26281
27618
|
if (settled) return;
|
|
26282
27619
|
settled = true;
|
|
26283
27620
|
restore();
|
|
26284
|
-
|
|
27621
|
+
resolve6(value);
|
|
26285
27622
|
};
|
|
26286
27623
|
readline2.emitKeypressEvents(input);
|
|
26287
27624
|
input.setRawMode(true);
|
|
@@ -26302,9 +27639,9 @@ async function askLine(question, io = {}) {
|
|
|
26302
27639
|
output: io.output ?? process.stdout
|
|
26303
27640
|
});
|
|
26304
27641
|
try {
|
|
26305
|
-
return await new Promise((
|
|
26306
|
-
rl.question(question).then((a) =>
|
|
26307
|
-
rl.once("close", () => setImmediate(() =>
|
|
27642
|
+
return await new Promise((resolve6) => {
|
|
27643
|
+
rl.question(question).then((a) => resolve6(a.trim())).catch(() => resolve6(null));
|
|
27644
|
+
rl.once("close", () => setImmediate(() => resolve6(null)));
|
|
26308
27645
|
});
|
|
26309
27646
|
} finally {
|
|
26310
27647
|
rl.close();
|
|
@@ -26386,8 +27723,8 @@ async function promptMultiSelect(question, choices, fallback) {
|
|
|
26386
27723
|
|
|
26387
27724
|
// src/lib/remote-config.ts
|
|
26388
27725
|
var import_node_fs53 = require("node:fs");
|
|
26389
|
-
var
|
|
26390
|
-
var
|
|
27726
|
+
var import_promises18 = require("node:fs/promises");
|
|
27727
|
+
var import_node_path37 = require("node:path");
|
|
26391
27728
|
var import_yaml5 = __toESM(require_dist());
|
|
26392
27729
|
var IGNORE_RIDER = "verityignore";
|
|
26393
27730
|
async function fetchRemoteSetup(opts) {
|
|
@@ -26436,7 +27773,7 @@ async function adoptRemoteSetup(found, opts) {
|
|
|
26436
27773
|
await writeOut(VERITYIGNORE_FILE, rider);
|
|
26437
27774
|
written.push(VERITYIGNORE_FILE);
|
|
26438
27775
|
} else {
|
|
26439
|
-
const local = await (0,
|
|
27776
|
+
const local = await (0, import_promises18.readFile)(localIgnore, "utf-8").catch(() => null);
|
|
26440
27777
|
if (local !== null && local !== rider) {
|
|
26441
27778
|
notes.push(`${VERITYIGNORE_FILE} already exists here and differs from the pushed copy \u2014 kept yours.`);
|
|
26442
27779
|
}
|
|
@@ -26463,10 +27800,10 @@ async function adoptRemoteSetup(found, opts) {
|
|
|
26463
27800
|
written.push(VERITY_MD_FILE);
|
|
26464
27801
|
return { written, notes };
|
|
26465
27802
|
}
|
|
26466
|
-
async function writeOut(
|
|
26467
|
-
const target = projectPath(
|
|
26468
|
-
await (0,
|
|
26469
|
-
await (0,
|
|
27803
|
+
async function writeOut(relative2, body) {
|
|
27804
|
+
const target = projectPath(relative2);
|
|
27805
|
+
await (0, import_promises18.mkdir)((0, import_node_path37.dirname)(target), { recursive: true });
|
|
27806
|
+
await (0, import_promises18.writeFile)(target, body);
|
|
26470
27807
|
}
|
|
26471
27808
|
function describeRemote(found) {
|
|
26472
27809
|
const when = found.standard.createdAt.slice(0, 10);
|
|
@@ -26536,7 +27873,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
26536
27873
|
}
|
|
26537
27874
|
let remote = "";
|
|
26538
27875
|
try {
|
|
26539
|
-
remote = (0,
|
|
27876
|
+
remote = (0, import_node_child_process17.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
26540
27877
|
} catch {
|
|
26541
27878
|
}
|
|
26542
27879
|
if (!healed) {
|
|
@@ -26582,15 +27919,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
26582
27919
|
}
|
|
26583
27920
|
function resolveDataDir2() {
|
|
26584
27921
|
const candidates2 = [
|
|
26585
|
-
(0,
|
|
27922
|
+
(0, import_node_path38.join)(__dirname, "..", "data"),
|
|
26586
27923
|
// installed: node_modules/@codacy/verity-cli/data
|
|
26587
|
-
(0,
|
|
27924
|
+
(0, import_node_path38.join)(__dirname, "..", "..", "data"),
|
|
26588
27925
|
// edge case: nested resolution
|
|
26589
|
-
(0,
|
|
27926
|
+
(0, import_node_path38.join)(process.cwd(), "cli", "data")
|
|
26590
27927
|
// local dev: running from repo root
|
|
26591
27928
|
];
|
|
26592
27929
|
for (const candidate of candidates2) {
|
|
26593
|
-
if ((0, import_node_fs54.existsSync)((0,
|
|
27930
|
+
if ((0, import_node_fs54.existsSync)((0, import_node_path38.join)(candidate, "skills"))) {
|
|
26594
27931
|
return candidate;
|
|
26595
27932
|
}
|
|
26596
27933
|
}
|
|
@@ -26599,8 +27936,8 @@ function resolveDataDir2() {
|
|
|
26599
27936
|
);
|
|
26600
27937
|
}
|
|
26601
27938
|
async function copyDir(src, dest) {
|
|
26602
|
-
await (0,
|
|
26603
|
-
await (0,
|
|
27939
|
+
await (0, import_promises19.mkdir)(dest, { recursive: true });
|
|
27940
|
+
await (0, import_promises19.cp)(src, dest, { recursive: true, force: true });
|
|
26604
27941
|
}
|
|
26605
27942
|
async function skillIsCurrent(src, dest) {
|
|
26606
27943
|
const list2 = (dir) => {
|
|
@@ -26608,7 +27945,7 @@ async function skillIsCurrent(src, dest) {
|
|
|
26608
27945
|
const walk2 = (d, prefix) => {
|
|
26609
27946
|
for (const e of (0, import_node_fs54.readdirSync)(d, { withFileTypes: true })) {
|
|
26610
27947
|
const rel = prefix ? `${prefix}/${e.name}` : e.name;
|
|
26611
|
-
if (e.isDirectory()) walk2((0,
|
|
27948
|
+
if (e.isDirectory()) walk2((0, import_node_path38.join)(d, e.name), rel);
|
|
26612
27949
|
else if (e.isFile()) out.push(rel);
|
|
26613
27950
|
}
|
|
26614
27951
|
};
|
|
@@ -26619,8 +27956,8 @@ async function skillIsCurrent(src, dest) {
|
|
|
26619
27956
|
const shipped = list2(src);
|
|
26620
27957
|
if (JSON.stringify(shipped) !== JSON.stringify(list2(dest))) return false;
|
|
26621
27958
|
for (const rel of shipped) {
|
|
26622
|
-
const a = await (0,
|
|
26623
|
-
const b = await (0,
|
|
27959
|
+
const a = await (0, import_promises19.readFile)((0, import_node_path38.join)(src, rel), "utf-8");
|
|
27960
|
+
const b = await (0, import_promises19.readFile)((0, import_node_path38.join)(dest, rel), "utf-8");
|
|
26624
27961
|
if (a !== b) return false;
|
|
26625
27962
|
}
|
|
26626
27963
|
return true;
|
|
@@ -26807,7 +28144,7 @@ async function healStaleAnalysisConfig(globals) {
|
|
|
26807
28144
|
printWarn(" Your analysis config names pattern ids that no longer resolve \u2014 those tools were");
|
|
26808
28145
|
printWarn(" running silently with nothing enabled. Re-deriving it from your Standard\u2026");
|
|
26809
28146
|
try {
|
|
26810
|
-
const content = (0, import_yaml6.parse)(await (0,
|
|
28147
|
+
const content = (0, import_yaml6.parse)(await (0, import_promises19.readFile)(standardPath, "utf-8"));
|
|
26811
28148
|
const derived = await deriveConfigForStandard(content);
|
|
26812
28149
|
for (const path of derived.written) printInfo(` ${path} \u2713 (re-derived)`);
|
|
26813
28150
|
for (const note of derived.notes) printWarn(` ${note}`);
|
|
@@ -26888,7 +28225,7 @@ async function handoffToSetup(enabled, claudeInstalled) {
|
|
|
26888
28225
|
console.log(" Usually a minute or two. Quit any time \u2014 re-running /verity-setup resumes.");
|
|
26889
28226
|
console.log("");
|
|
26890
28227
|
const startedAt = Date.now();
|
|
26891
|
-
const run2 = (0,
|
|
28228
|
+
const run2 = (0, import_node_child_process17.spawnSync)("claude", ["/verity-setup"], { stdio: "inherit" });
|
|
26892
28229
|
if (run2.error) {
|
|
26893
28230
|
printWarn(`Could not start Claude Code: ${run2.error.message}`);
|
|
26894
28231
|
return instruct("start it yourself and run the skill there");
|
|
@@ -26898,12 +28235,12 @@ async function handoffToSetup(enabled, claudeInstalled) {
|
|
|
26898
28235
|
async function installSkills(force, step) {
|
|
26899
28236
|
step("Installing skills");
|
|
26900
28237
|
const dataDir = resolveDataDir2();
|
|
26901
|
-
const skillsSource = (0,
|
|
28238
|
+
const skillsSource = (0, import_node_path38.join)(dataDir, "skills");
|
|
26902
28239
|
const skillsDest = ".claude/skills";
|
|
26903
28240
|
let skillsInstalled = 0;
|
|
26904
28241
|
for (const skill of SKILLS) {
|
|
26905
|
-
const src = (0,
|
|
26906
|
-
const dest = (0,
|
|
28242
|
+
const src = (0, import_node_path38.join)(skillsSource, skill);
|
|
28243
|
+
const dest = (0, import_node_path38.join)(skillsDest, skill);
|
|
26907
28244
|
if (!(0, import_node_fs54.existsSync)(src)) {
|
|
26908
28245
|
printWarn(` Skill data not found: ${skill}`);
|
|
26909
28246
|
continue;
|
|
@@ -26972,7 +28309,7 @@ async function checkPrerequisites(step) {
|
|
|
26972
28309
|
}
|
|
26973
28310
|
async function scaffoldProject(step, defaultsOnly) {
|
|
26974
28311
|
step("Knowledge base, .gitignore and CLAUDE.md");
|
|
26975
|
-
await (0,
|
|
28312
|
+
await (0, import_promises19.mkdir)(VERITY_DIR, { recursive: true });
|
|
26976
28313
|
await ensureMemoryDir();
|
|
26977
28314
|
const ignoreResult = ensureVerityGitignore();
|
|
26978
28315
|
if (ignoreResult === "failed") {
|
|
@@ -27137,8 +28474,8 @@ function registerInitCommand(program2) {
|
|
|
27137
28474
|
printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
|
|
27138
28475
|
}
|
|
27139
28476
|
await scaffoldProject(step, defaultsOnly);
|
|
27140
|
-
const globalVerityDir = (0,
|
|
27141
|
-
await (0,
|
|
28477
|
+
const globalVerityDir = (0, import_node_path38.join)(process.env.HOME ?? "", ".verity");
|
|
28478
|
+
await (0, import_promises19.mkdir)(globalVerityDir, { recursive: true });
|
|
27142
28479
|
console.log("");
|
|
27143
28480
|
step("Wiring Claude Code hooks");
|
|
27144
28481
|
const gitMoments = [
|
|
@@ -27236,7 +28573,7 @@ function registerInitCommand(program2) {
|
|
|
27236
28573
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
27237
28574
|
init: {
|
|
27238
28575
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27239
|
-
cli_version: true ? "0.
|
|
28576
|
+
cli_version: true ? "0.33.0-experimental.34db3a4" : "dev"
|
|
27240
28577
|
}
|
|
27241
28578
|
});
|
|
27242
28579
|
} catch (err) {
|
|
@@ -27287,7 +28624,7 @@ function registerInitCommand(program2) {
|
|
|
27287
28624
|
|
|
27288
28625
|
// src/commands/uninstall.ts
|
|
27289
28626
|
var import_node_fs55 = require("node:fs");
|
|
27290
|
-
var
|
|
28627
|
+
var import_node_path39 = require("node:path");
|
|
27291
28628
|
function registerUninstallCommand(program2) {
|
|
27292
28629
|
program2.command("uninstall").description("Remove Verity from this project (skills, hooks, .verity/, VERITY.md)").option("--dry-run", "Show what would be removed without doing it").option("--purge-global", "Also remove ~/.verity/ (deletes saved tokens \u2014 reconnect requires re-registration)").option("--keep-verity-md", "Keep the project root VERITY.md file").action(async (opts) => {
|
|
27293
28630
|
const dryRun = opts.dryRun ?? false;
|
|
@@ -27296,7 +28633,7 @@ function registerUninstallCommand(program2) {
|
|
|
27296
28633
|
const actions = [];
|
|
27297
28634
|
const skillsRoot = projectPath(".claude/skills");
|
|
27298
28635
|
for (const name of PROJECT_SKILL_NAMES) {
|
|
27299
|
-
const dir = (0,
|
|
28636
|
+
const dir = (0, import_node_path39.join)(skillsRoot, name);
|
|
27300
28637
|
if ((0, import_node_fs55.existsSync)(dir)) {
|
|
27301
28638
|
actions.push({
|
|
27302
28639
|
label: `Remove .claude/skills/${name}/`,
|
|
@@ -27342,7 +28679,7 @@ function registerUninstallCommand(program2) {
|
|
|
27342
28679
|
}
|
|
27343
28680
|
});
|
|
27344
28681
|
const home = process.env.HOME ?? "";
|
|
27345
|
-
const globalVerityDir = (0,
|
|
28682
|
+
const globalVerityDir = (0, import_node_path39.join)(home, ".verity");
|
|
27346
28683
|
if (purgeGlobal && (0, import_node_fs55.existsSync)(globalVerityDir)) {
|
|
27347
28684
|
actions.push({
|
|
27348
28685
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
@@ -27541,7 +28878,7 @@ function registerTaskCommands(program2) {
|
|
|
27541
28878
|
|
|
27542
28879
|
// src/commands/reset.ts
|
|
27543
28880
|
var import_node_fs56 = require("node:fs");
|
|
27544
|
-
var
|
|
28881
|
+
var import_node_path40 = require("node:path");
|
|
27545
28882
|
function registerResetCommand(program2) {
|
|
27546
28883
|
program2.command("reset").description("Close the current task and clear transient state").option("--keep-task", "Only purge caches; leave the current task open").option("--all", "Also purge diagnostic logs (.verity/.logs/)").action(async (opts) => {
|
|
27547
28884
|
const globals = program2.opts();
|
|
@@ -27582,7 +28919,7 @@ function registerResetCommand(program2) {
|
|
|
27582
28919
|
for (const entry of (0, import_node_fs56.readdirSync)(cacheDir)) {
|
|
27583
28920
|
if (entry.startsWith("pending-")) {
|
|
27584
28921
|
try {
|
|
27585
|
-
(0, import_node_fs56.unlinkSync)((0,
|
|
28922
|
+
(0, import_node_fs56.unlinkSync)((0, import_node_path40.join)(cacheDir, entry));
|
|
27586
28923
|
purged++;
|
|
27587
28924
|
} catch {
|
|
27588
28925
|
}
|
|
@@ -27609,7 +28946,7 @@ function registerResetCommand(program2) {
|
|
|
27609
28946
|
if ((0, import_node_fs56.existsSync)(logsDir)) {
|
|
27610
28947
|
for (const entry of (0, import_node_fs56.readdirSync)(logsDir)) {
|
|
27611
28948
|
try {
|
|
27612
|
-
(0, import_node_fs56.unlinkSync)((0,
|
|
28949
|
+
(0, import_node_fs56.unlinkSync)((0, import_node_path40.join)(logsDir, entry));
|
|
27613
28950
|
} catch {
|
|
27614
28951
|
}
|
|
27615
28952
|
}
|
|
@@ -27621,10 +28958,52 @@ function registerResetCommand(program2) {
|
|
|
27621
28958
|
}
|
|
27622
28959
|
|
|
27623
28960
|
// src/commands/reflect.ts
|
|
28961
|
+
var import_node_fs57 = require("node:fs");
|
|
28962
|
+
|
|
28963
|
+
// src/lib/reflection-globs.ts
|
|
28964
|
+
var MAX_GLOBS = 6;
|
|
28965
|
+
var PATH_TOKEN = /[A-Za-z0-9_./-]*[A-Za-z0-9_-]\.[A-Za-z0-9]{1,8}(?::\d+(?::\d+)?)?/g;
|
|
28966
|
+
function clean(token) {
|
|
28967
|
+
return token.replace(/:\d+(?::\d+)?$/, "").replace(/^[`'"(\[]+/, "").replace(/[`'"),\].;!?]+$/, "").replace(/^\.\//, "");
|
|
28968
|
+
}
|
|
28969
|
+
function resolve5(token, treePaths) {
|
|
28970
|
+
if (treePaths.includes(token)) return token;
|
|
28971
|
+
const suffix = token.startsWith("/") ? token : `/${token}`;
|
|
28972
|
+
const matches = treePaths.filter((p) => p.endsWith(suffix));
|
|
28973
|
+
return matches.length === 1 ? matches[0] : null;
|
|
28974
|
+
}
|
|
28975
|
+
function deriveFileGlobs(text, treePaths) {
|
|
28976
|
+
if (!text || treePaths.length === 0) return [];
|
|
28977
|
+
const out = [];
|
|
28978
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28979
|
+
for (const raw of text.match(PATH_TOKEN) ?? []) {
|
|
28980
|
+
const token = clean(raw);
|
|
28981
|
+
if (!token) continue;
|
|
28982
|
+
const path = resolve5(token, treePaths);
|
|
28983
|
+
if (!path || seen.has(path)) continue;
|
|
28984
|
+
seen.add(path);
|
|
28985
|
+
out.push(path);
|
|
28986
|
+
if (out.length >= MAX_GLOBS) break;
|
|
28987
|
+
}
|
|
28988
|
+
return out;
|
|
28989
|
+
}
|
|
28990
|
+
|
|
28991
|
+
// src/commands/reflect.ts
|
|
28992
|
+
async function writeNodeToDisk(args) {
|
|
28993
|
+
try {
|
|
28994
|
+
const written = await applyMemoryWrites(
|
|
28995
|
+
[{ path: args.filePath, content: args.content, node_id: args.nodeId, op: "create" }],
|
|
28996
|
+
{ treePaths: listTrackedFiles(), recordBaseline: false }
|
|
28997
|
+
);
|
|
28998
|
+
if (written === 0) return false;
|
|
28999
|
+
return (0, import_node_fs57.existsSync)(projectPath(`${VERITY_DIR}/memory/${args.filePath}`));
|
|
29000
|
+
} catch {
|
|
29001
|
+
return false;
|
|
29002
|
+
}
|
|
29003
|
+
}
|
|
27624
29004
|
function registerReflectCommand(program2) {
|
|
27625
|
-
program2.command("reflect").description("Capture learnings \u2014 auto-extract or
|
|
29005
|
+
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)").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) => {
|
|
27626
29006
|
const globals = program2.opts();
|
|
27627
|
-
const mode2 = resolveRunMode({ autonomousFlag: opts.autonomous });
|
|
27628
29007
|
await ensureMemoryDir();
|
|
27629
29008
|
const tokenResult = await resolveToken(globals.token);
|
|
27630
29009
|
if (!tokenResult.ok) {
|
|
@@ -27689,9 +29068,14 @@ function registerReflectCommand(program2) {
|
|
|
27689
29068
|
if (userInput) {
|
|
27690
29069
|
const validKinds = ["decision", "gotcha", "pattern", "security", "quality", "intent", "domain", "integration"];
|
|
27691
29070
|
const kind = validKinds.includes(opts.kind) ? opts.kind : "gotcha";
|
|
29071
|
+
const confirmed = opts.confirmed === true;
|
|
29072
|
+
const source = confirmed ? "user" : "agent";
|
|
29073
|
+
const explicitGlobs = Array.isArray(opts.fileGlobs) ? opts.fileGlobs.map((g) => g.trim()).filter(Boolean) : [];
|
|
29074
|
+
const derivedGlobs = explicitGlobs.length > 0 ? [] : deriveFileGlobs(userInput, listTrackedFiles());
|
|
29075
|
+
const fileGlobs = explicitGlobs.length > 0 ? explicitGlobs : derivedGlobs.length > 0 ? derivedGlobs : inheritedGlobs;
|
|
27692
29076
|
const result = await apiRequest({
|
|
27693
29077
|
method: "POST",
|
|
27694
|
-
path: "/compound/
|
|
29078
|
+
path: "/compound/reflect",
|
|
27695
29079
|
serviceUrl,
|
|
27696
29080
|
token,
|
|
27697
29081
|
body: {
|
|
@@ -27699,14 +29083,23 @@ function registerReflectCommand(program2) {
|
|
|
27699
29083
|
title: userInput.slice(0, 200),
|
|
27700
29084
|
body: userInput,
|
|
27701
29085
|
domains: [],
|
|
27702
|
-
|
|
27703
|
-
|
|
27704
|
-
|
|
27705
|
-
//
|
|
27706
|
-
|
|
29086
|
+
file_globs: fileGlobs,
|
|
29087
|
+
source,
|
|
29088
|
+
created_by: "reflect",
|
|
29089
|
+
// The server VERIFIES this against the project before writing it —
|
|
29090
|
+
// an id it cannot verify is dropped, never trusted and never an
|
|
29091
|
+
// error. Sending it is what lets an auto-recorded reflection carry
|
|
29092
|
+
// the same attribution a dashboard-written one does; without it the
|
|
29093
|
+
// unreviewed node is the one that cannot say where it came from.
|
|
29094
|
+
...taskId && { task_id: taskId },
|
|
29095
|
+
// Server-side default for each source; sent explicitly so an older
|
|
29096
|
+
// server (which knows 'user'/'extractor'/'linter' only, and would
|
|
29097
|
+
// silently coerce an unknown source to 'user' at 1.0) still records
|
|
29098
|
+
// an honest confidence for a draft nobody confirmed.
|
|
29099
|
+
confidence: confirmed ? 1 : 0.7
|
|
27707
29100
|
},
|
|
27708
29101
|
verbose: globals.verbose,
|
|
27709
|
-
cmd: "reflect_user"
|
|
29102
|
+
cmd: confirmed ? "reflect_user" : "reflect_agent"
|
|
27710
29103
|
});
|
|
27711
29104
|
if (!result.ok) {
|
|
27712
29105
|
printError(`Failed to save reflection: ${result.error}`);
|
|
@@ -27714,12 +29107,30 @@ function registerReflectCommand(program2) {
|
|
|
27714
29107
|
}
|
|
27715
29108
|
const nodeId = result.data.node_id;
|
|
27716
29109
|
const filePath = result.data.file_path;
|
|
27717
|
-
|
|
29110
|
+
const viewUrl = typeof result.data.view_url === "string" ? result.data.view_url : null;
|
|
29111
|
+
const recordedSource = typeof result.data.source === "string" ? result.data.source : source;
|
|
29112
|
+
const recordedAsUser = recordedSource === "user";
|
|
29113
|
+
const content = typeof result.data.content === "string" ? result.data.content : null;
|
|
29114
|
+
const synced = content !== null && await writeNodeToDisk({ filePath, content, nodeId });
|
|
29115
|
+
printInfo(
|
|
29116
|
+
synced ? `Recorded ${nodeId} \u2192 .verity/memory/${filePath}` : `Recorded ${nodeId} (syncs to .verity/memory/${filePath} on the next analysis).`
|
|
29117
|
+
);
|
|
29118
|
+
if (viewUrl) printInfo(` ${viewUrl}`);
|
|
27718
29119
|
printInfo(
|
|
27719
|
-
|
|
29120
|
+
recordedAsUser ? " Recorded as yours (source: user)." : " Auto-recorded from this task \u2014 nobody reviewed it. Edit or delete the file if it is wrong."
|
|
27720
29121
|
);
|
|
29122
|
+
if (recordedSource !== source) {
|
|
29123
|
+
printWarn(
|
|
29124
|
+
`Provenance mismatch: sent source "${source}", the service recorded "${recordedSource}". Correct ${nodeId} on the dashboard \u2014 a draft stored as human-authored cannot be told apart later.`
|
|
29125
|
+
);
|
|
29126
|
+
}
|
|
29127
|
+
if (fileGlobs.length === 0) {
|
|
29128
|
+
printWarn(
|
|
29129
|
+
'No files matched \u2014 this reflection will not surface in future reviews. Cite a path in the text, or pass --file-globs "<path or glob>".'
|
|
29130
|
+
);
|
|
29131
|
+
}
|
|
27721
29132
|
if (taskResolution === "none") {
|
|
27722
|
-
printInfo("(No task context \u2014 reflection lives at the project level.)");
|
|
29133
|
+
printInfo(" (No task context \u2014 reflection lives at the project level.)");
|
|
27723
29134
|
}
|
|
27724
29135
|
} else {
|
|
27725
29136
|
if (!taskId) {
|
|
@@ -27806,6 +29217,121 @@ function registerMemoryCommand(program2) {
|
|
|
27806
29217
|
}
|
|
27807
29218
|
process.exit(result.failed === 0 ? 0 : 1);
|
|
27808
29219
|
});
|
|
29220
|
+
memory.command("pull").description("Download this repo's knowledge graph into .verity/memory/ (the session-start hook runs it)").option("--force", "Fetch every node even if nothing changed since the last pull").option("--quiet", "Print nothing and always exit 0 (for the session-start hook)").action(async (opts) => {
|
|
29221
|
+
const globals = program2.opts();
|
|
29222
|
+
const quiet = !!opts.quiet;
|
|
29223
|
+
const fail = (message) => {
|
|
29224
|
+
if (!quiet) printError(message);
|
|
29225
|
+
process.exit(quiet ? 0 : 1);
|
|
29226
|
+
};
|
|
29227
|
+
try {
|
|
29228
|
+
process.chdir(repoRoot());
|
|
29229
|
+
} catch {
|
|
29230
|
+
}
|
|
29231
|
+
const tokenResult = await resolveToken(globals.token);
|
|
29232
|
+
if (!tokenResult.ok) return fail(tokenResult.error);
|
|
29233
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
29234
|
+
if (!urlResult.ok) return fail(urlResult.error);
|
|
29235
|
+
let result;
|
|
29236
|
+
try {
|
|
29237
|
+
result = await pullRepoMemory({
|
|
29238
|
+
serviceUrl: urlResult.data,
|
|
29239
|
+
token: tokenResult.data.token,
|
|
29240
|
+
verbose: globals.verbose,
|
|
29241
|
+
force: !!opts.force
|
|
29242
|
+
});
|
|
29243
|
+
} catch (err) {
|
|
29244
|
+
return fail(`Could not pull the knowledge graph: ${err.message}`);
|
|
29245
|
+
}
|
|
29246
|
+
logEvent("memory_pull", result.ok ? { status: result.status, received: result.received, written: result.written } : { status: "failed", category: result.category ?? null });
|
|
29247
|
+
if (!result.ok) return fail(`Could not pull the knowledge graph: ${result.error}`);
|
|
29248
|
+
if (!quiet) {
|
|
29249
|
+
printInfo(result.status === "unchanged" ? "Knowledge graph is up to date." : result.status === "tracked" ? "Skipped: .verity/memory/ is tracked by git here, and the pull never rewrites tracked files. Run `verity memory untrack` to make the graph machine-local." : `Pulled ${result.received} node(s); ${result.written} new or updated file(s) written to .verity/memory/.`);
|
|
29250
|
+
}
|
|
29251
|
+
let org;
|
|
29252
|
+
try {
|
|
29253
|
+
org = await pullOrgKnowledge({
|
|
29254
|
+
serviceUrl: urlResult.data,
|
|
29255
|
+
token: tokenResult.data.token,
|
|
29256
|
+
verbose: globals.verbose,
|
|
29257
|
+
force: !!opts.force
|
|
29258
|
+
});
|
|
29259
|
+
} catch (err) {
|
|
29260
|
+
org = { ok: false, error: err.message };
|
|
29261
|
+
}
|
|
29262
|
+
logEvent("memory_org_pull", org.ok ? { status: org.status, received: org.received } : { status: "failed", category: org.category ?? null });
|
|
29263
|
+
if (!quiet) {
|
|
29264
|
+
if (!org.ok) printWarn(`Could not refresh the org knowledge mirror: ${org.error}`);
|
|
29265
|
+
else if (org.status === "pulled") printInfo(`Org knowledge: ${org.received} claim(s) mirrored at ${org.dir}.`);
|
|
29266
|
+
else if (org.status === "unchanged") printInfo("Org knowledge mirror is up to date.");
|
|
29267
|
+
}
|
|
29268
|
+
process.exit(0);
|
|
29269
|
+
});
|
|
29270
|
+
memory.command("org").description("List the org knowledge mirrored for this repository's organization").action(async () => {
|
|
29271
|
+
try {
|
|
29272
|
+
process.chdir(repoRoot());
|
|
29273
|
+
} catch {
|
|
29274
|
+
}
|
|
29275
|
+
const dir = orgMirrorDir();
|
|
29276
|
+
if (!dir) {
|
|
29277
|
+
printError("Not a repository with an origin remote, so there is no organization to list.");
|
|
29278
|
+
process.exit(1);
|
|
29279
|
+
}
|
|
29280
|
+
const claims = await readOrgMirror();
|
|
29281
|
+
if (claims.length === 0) {
|
|
29282
|
+
printInfo(`No org knowledge mirrored yet (${dir}). Run \`verity memory pull\`.`);
|
|
29283
|
+
process.exit(0);
|
|
29284
|
+
}
|
|
29285
|
+
for (const c of claims) console.log(`${c.tag_id} ${c.kind.padEnd(11)} ${c.title} \xB7 ${c.origin}`);
|
|
29286
|
+
printInfo(`${claims.length} claim(s) at ${dir}. Demote one with \`verity memory demote <id> --reason "\u2026"\`.`);
|
|
29287
|
+
process.exit(0);
|
|
29288
|
+
});
|
|
29289
|
+
memory.command("demote <claim-id>").description("Demote an org knowledge claim for the whole organization, with a reason").requiredOption("--reason <text>", "Why this claim should no longer be shared (1\u2013500 characters)").action(async (claimId, opts) => {
|
|
29290
|
+
const globals = program2.opts();
|
|
29291
|
+
try {
|
|
29292
|
+
process.chdir(repoRoot());
|
|
29293
|
+
} catch {
|
|
29294
|
+
}
|
|
29295
|
+
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(claimId)) {
|
|
29296
|
+
printError("The claim id is the `id` in the mirrored file, a uuid. `verity memory org` lists them.");
|
|
29297
|
+
process.exit(1);
|
|
29298
|
+
}
|
|
29299
|
+
const reason = opts.reason.trim();
|
|
29300
|
+
if (reason.length === 0 || reason.length > 500) {
|
|
29301
|
+
printError("--reason is 1 to 500 characters.");
|
|
29302
|
+
process.exit(1);
|
|
29303
|
+
}
|
|
29304
|
+
const tokenResult = await resolveToken(globals.token);
|
|
29305
|
+
if (!tokenResult.ok) {
|
|
29306
|
+
printError(tokenResult.error);
|
|
29307
|
+
process.exit(1);
|
|
29308
|
+
}
|
|
29309
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
29310
|
+
if (!urlResult.ok) {
|
|
29311
|
+
printError(urlResult.error);
|
|
29312
|
+
process.exit(1);
|
|
29313
|
+
}
|
|
29314
|
+
const res = await apiRequest({
|
|
29315
|
+
method: "POST",
|
|
29316
|
+
path: `/memory/org/claims/${claimId}/demote`,
|
|
29317
|
+
serviceUrl: urlResult.data,
|
|
29318
|
+
token: tokenResult.data.token,
|
|
29319
|
+
body: { reason },
|
|
29320
|
+
verbose: globals.verbose,
|
|
29321
|
+
cmd: "memory-demote",
|
|
29322
|
+
extraHeaders: { "X-Verity-Via": "cli" }
|
|
29323
|
+
});
|
|
29324
|
+
if (!res.ok) {
|
|
29325
|
+
printError(`Could not demote the claim: ${res.error}`);
|
|
29326
|
+
process.exit(1);
|
|
29327
|
+
}
|
|
29328
|
+
printInfo(`Demoted ${claimId} for the whole organization. Restore it from the dashboard if that was wrong.`);
|
|
29329
|
+
try {
|
|
29330
|
+
await pullOrgKnowledge({ serviceUrl: urlResult.data, token: tokenResult.data.token, verbose: globals.verbose, force: true });
|
|
29331
|
+
} catch {
|
|
29332
|
+
}
|
|
29333
|
+
process.exit(0);
|
|
29334
|
+
});
|
|
27809
29335
|
memory.command("untrack").description("Stop committing .verity/memory/ \u2014 the graph stays on disk and leaves your diffs").action(() => {
|
|
27810
29336
|
try {
|
|
27811
29337
|
process.chdir(repoRoot());
|
|
@@ -27969,8 +29495,8 @@ function registerTelemetryCommands(program2) {
|
|
|
27969
29495
|
}
|
|
27970
29496
|
|
|
27971
29497
|
// src/cli.ts
|
|
27972
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.
|
|
27973
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.
|
|
29498
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.33.0-experimental.34db3a4").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) => {
|
|
29499
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.33.0-experimental.34db3a4");
|
|
27974
29500
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
27975
29501
|
try {
|
|
27976
29502
|
await foldLegacyLocalCredential();
|