@codacy/verity-cli 0.30.0 → 0.30.1-experimental.cbeb697
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/bin/verity.js +1679 -716
- package/data/skills/verity-setup/SKILL.md +42 -0
- package/package.json +1 -1
package/bin/verity.js
CHANGED
|
@@ -10342,6 +10342,10 @@ function repoRoot() {
|
|
|
10342
10342
|
}
|
|
10343
10343
|
return _repoRoot;
|
|
10344
10344
|
}
|
|
10345
|
+
function _resetRepoRoot() {
|
|
10346
|
+
_repoRoot = null;
|
|
10347
|
+
_mainRoot = void 0;
|
|
10348
|
+
}
|
|
10345
10349
|
var _mainRoot;
|
|
10346
10350
|
function mainWorktreeRoot() {
|
|
10347
10351
|
if (_mainRoot !== void 0) return _mainRoot;
|
|
@@ -10383,6 +10387,7 @@ var CLAUDE_SETTINGS_FILE = ".claude/settings.json";
|
|
|
10383
10387
|
var STANDARD_FILE = `${VERITY_DIR}/standard.yaml`;
|
|
10384
10388
|
var MEMORY_DIR = `${VERITY_DIR}/memory`;
|
|
10385
10389
|
var CODACY_CONFIG_FILE = ".codacy/codacy.config.json";
|
|
10390
|
+
var VERITYIGNORE_FILE = ".verityignore";
|
|
10386
10391
|
function projectPath(relativePath) {
|
|
10387
10392
|
return (0, import_node_path.join)(repoRoot(), relativePath);
|
|
10388
10393
|
}
|
|
@@ -10473,7 +10478,8 @@ var REVIEWABLE_FILENAMES = /* @__PURE__ */ new Set([
|
|
|
10473
10478
|
"Makefile",
|
|
10474
10479
|
"Dockerfile",
|
|
10475
10480
|
"Jenkinsfile",
|
|
10476
|
-
"Vagrantfile"
|
|
10481
|
+
"Vagrantfile",
|
|
10482
|
+
".verityignore"
|
|
10477
10483
|
]);
|
|
10478
10484
|
var REVIEWABLE_PATH_PATTERNS = [
|
|
10479
10485
|
/\.circleci\//,
|
|
@@ -10493,7 +10499,7 @@ var SECURITY_PATTERNS = [
|
|
|
10493
10499
|
/Dockerfile/
|
|
10494
10500
|
];
|
|
10495
10501
|
var PROD_SERVICE_URL = "https://ofcamwrjwrkazqvdchko.supabase.co/functions/v1";
|
|
10496
|
-
var DEFAULT_SERVICE_URL = "".length > 0 ? "" : PROD_SERVICE_URL;
|
|
10502
|
+
var DEFAULT_SERVICE_URL = "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1".length > 0 ? "https://wukeddyzpijoegyajtnc.supabase.co/functions/v1" : PROD_SERVICE_URL;
|
|
10497
10503
|
var GITHUB_CLIENT_ID = "Iv23li88HxAi3ZrbYzWh";
|
|
10498
10504
|
var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
10499
10505
|
var GITHUB_ACCESS_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
@@ -10503,6 +10509,7 @@ function githubAppInstallUrl(accountId) {
|
|
|
10503
10509
|
return accountId != null ? `https://github.com/apps/${GITHUB_APP_SLUG}/installations/new/permissions?target_id=${accountId}` : GITHUB_APP_INSTALL_URL;
|
|
10504
10510
|
}
|
|
10505
10511
|
var ADVISORY_EPISODE_FILE = `${VERITY_DIR}/.advisory-episode`;
|
|
10512
|
+
var IGNORE_DECLARATION_FILE = `${VERITY_DIR}/.ignore-declaration`;
|
|
10506
10513
|
|
|
10507
10514
|
// src/lib/output.ts
|
|
10508
10515
|
var RED = "\x1B[0;31m";
|
|
@@ -10804,13 +10811,6 @@ function execGit(cmd) {
|
|
|
10804
10811
|
return "";
|
|
10805
10812
|
}
|
|
10806
10813
|
}
|
|
10807
|
-
function execGitArgs(args) {
|
|
10808
|
-
try {
|
|
10809
|
-
return (0, import_node_child_process3.execFileSync)("git", args, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
10810
|
-
} catch {
|
|
10811
|
-
return "";
|
|
10812
|
-
}
|
|
10813
|
-
}
|
|
10814
10814
|
function splitLines(s) {
|
|
10815
10815
|
return s.split("\n").filter((l) => l.length > 0);
|
|
10816
10816
|
}
|
|
@@ -10877,9 +10877,6 @@ function getChangedFiles() {
|
|
|
10877
10877
|
const filtered = Array.from(sets).filter((f) => !isVerityOwnedPath(f));
|
|
10878
10878
|
return { files: filtered, hasRecentCommitFiles };
|
|
10879
10879
|
}
|
|
10880
|
-
function getStagedFiles() {
|
|
10881
|
-
return splitLines(execGit("git diff --cached --name-only")).filter((f) => !isVerityOwnedPath(f));
|
|
10882
|
-
}
|
|
10883
10880
|
function getDirtyFiles() {
|
|
10884
10881
|
const set = /* @__PURE__ */ new Set();
|
|
10885
10882
|
for (const f of splitLines(execGit("git diff --name-only HEAD"))) set.add(f);
|
|
@@ -10900,33 +10897,6 @@ function showContentAtRef(ref, repoRelPath) {
|
|
|
10900
10897
|
return null;
|
|
10901
10898
|
}
|
|
10902
10899
|
}
|
|
10903
|
-
function getPushRangeFiles() {
|
|
10904
|
-
const diff = (range) => splitLines(execGitArgs(["diff", "--name-only", range])).filter((f) => !f.startsWith(".verity/") && !f.startsWith(".verity\\"));
|
|
10905
|
-
const resolvers = [
|
|
10906
|
-
() => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{push}"]) ? "@{push}..HEAD" : null,
|
|
10907
|
-
() => execGitArgs(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]) ? "@{upstream}..HEAD" : null,
|
|
10908
|
-
() => {
|
|
10909
|
-
const branch = execGitArgs(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
10910
|
-
return branch && branch !== "HEAD" && execGitArgs(["rev-parse", "--verify", "-q", `origin/${branch}`]) ? `origin/${branch}..HEAD` : null;
|
|
10911
|
-
}
|
|
10912
|
-
];
|
|
10913
|
-
for (const resolve3 of resolvers) {
|
|
10914
|
-
const range = resolve3();
|
|
10915
|
-
if (range) return { files: diff(range), range };
|
|
10916
|
-
}
|
|
10917
|
-
const baseline = readBaselineSha();
|
|
10918
|
-
if (baseline) {
|
|
10919
|
-
const files = diff(`${baseline}..HEAD`);
|
|
10920
|
-
if (files.length > 0) return { files, range: `${baseline}..HEAD` };
|
|
10921
|
-
}
|
|
10922
|
-
const last = diff("HEAD~1..HEAD");
|
|
10923
|
-
return { files: last, range: last.length > 0 ? "HEAD~1..HEAD" : null };
|
|
10924
|
-
}
|
|
10925
|
-
function getPushRangeMessages() {
|
|
10926
|
-
const { range } = getPushRangeFiles();
|
|
10927
|
-
if (!range) return "";
|
|
10928
|
-
return execGitArgs(["log", range, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
10929
|
-
}
|
|
10930
10900
|
function filterAnalyzable(files) {
|
|
10931
10901
|
return files.filter((f) => {
|
|
10932
10902
|
const ext = (0, import_node_path3.extname)(f).slice(1);
|
|
@@ -11417,7 +11387,7 @@ var readline = __toESM(require("node:readline/promises"));
|
|
|
11417
11387
|
var import_node_os = require("node:os");
|
|
11418
11388
|
|
|
11419
11389
|
// src/lib/provider-auth.ts
|
|
11420
|
-
var sleep = (ms) => new Promise((
|
|
11390
|
+
var sleep = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
11421
11391
|
var form = (fields) => new URLSearchParams(fields).toString();
|
|
11422
11392
|
async function githubAccountId(owner) {
|
|
11423
11393
|
try {
|
|
@@ -13563,6 +13533,42 @@ var LEGACY_MD_END = "<!-- gate-memory:end -->";
|
|
|
13563
13533
|
var LEGACY_PRESERVE_START = "<!-- gate-memory:preserve -->";
|
|
13564
13534
|
var LEGACY_PRESERVE_END = "<!-- /gate-memory:preserve -->";
|
|
13565
13535
|
var CLAUDE_MD_PROSE = [
|
|
13536
|
+
"## Project Memory",
|
|
13537
|
+
"",
|
|
13538
|
+
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
13539
|
+
"non-trivial work, scan `.verity/memory/index.md` for decisions, gotchas, and patterns",
|
|
13540
|
+
"that may apply to the change you are about to make. Open specific node files via",
|
|
13541
|
+
"the Read tool when the title or scope suggests relevance.",
|
|
13542
|
+
"",
|
|
13543
|
+
"The graph is auto-maintained by Verity. Files at `.verity/memory/_archive/` are",
|
|
13544
|
+
"superseded \u2014 ignore them unless investigating history.",
|
|
13545
|
+
"",
|
|
13546
|
+
"> Durable, hand-curated guidance goes in the preserve region below (it survives",
|
|
13547
|
+
"> regeneration) or anywhere OUTSIDE these markers. Everything else between the",
|
|
13548
|
+
"> markers is tool-owned and overwritten on each run.",
|
|
13549
|
+
"",
|
|
13550
|
+
"## Housekeeping Turns",
|
|
13551
|
+
"",
|
|
13552
|
+
"When a turn will be pure housekeeping \u2014 pulling, installing dependencies,",
|
|
13553
|
+
"rebasing, a formatting sweep you are not authoring \u2014 declare it BEFORE doing it:",
|
|
13554
|
+
"",
|
|
13555
|
+
"```bash",
|
|
13556
|
+
'verity ignore --turn --agent --reason "pulling latest before starting"',
|
|
13557
|
+
"```",
|
|
13558
|
+
"",
|
|
13559
|
+
"This skips the review for that turn, which saves the turn Verity would",
|
|
13560
|
+
"otherwise spend saying it had nothing to say. Use `--for 30m` instead of",
|
|
13561
|
+
"`--turn` when a single piece of housekeeping spans several turns.",
|
|
13562
|
+
"",
|
|
13563
|
+
"**It is a claim about the turn, not a way to silence review.** The declaration",
|
|
13564
|
+
"is checked against what the turn actually did: if anything is authored \u2014 by you,",
|
|
13565
|
+
"by a subagent, or by a shell command that can write files \u2014 it voids, the review",
|
|
13566
|
+
"runs anyway, and the broken declaration is reported. So declare housekeeping you",
|
|
13567
|
+
"are about to do, never work you have already done, and never as a way to get past",
|
|
13568
|
+
"a finding. Declarations are budgeted per session and every one is recorded with",
|
|
13569
|
+
"its reason."
|
|
13570
|
+
].join("\n");
|
|
13571
|
+
var CLAUDE_MD_PROSE_PRE_IGNORE = [
|
|
13566
13572
|
"## Project Memory",
|
|
13567
13573
|
"",
|
|
13568
13574
|
"This project has a knowledge graph maintained at `.verity/memory/`. Before starting",
|
|
@@ -13687,7 +13693,7 @@ function extractPreserveContent(interior) {
|
|
|
13687
13693
|
}
|
|
13688
13694
|
function stripKnownProse(interior) {
|
|
13689
13695
|
const trimmed = interior.replace(/^\n+/, "");
|
|
13690
|
-
for (const prose of [CLAUDE_MD_PROSE, CLAUDE_MD_PROSE_LEGACY]) {
|
|
13696
|
+
for (const prose of [CLAUDE_MD_PROSE, CLAUDE_MD_PROSE_PRE_IGNORE, CLAUDE_MD_PROSE_LEGACY]) {
|
|
13691
13697
|
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
13692
13698
|
}
|
|
13693
13699
|
return trimmed;
|
|
@@ -13747,6 +13753,160 @@ var import_node_fs14 = require("node:fs");
|
|
|
13747
13753
|
var import_node_crypto7 = require("node:crypto");
|
|
13748
13754
|
var import_node_path13 = require("node:path");
|
|
13749
13755
|
|
|
13756
|
+
// src/lib/analysis-mode.ts
|
|
13757
|
+
var DEBUG_PHRASES = [
|
|
13758
|
+
"not working",
|
|
13759
|
+
"doesn't work",
|
|
13760
|
+
"doesn't work",
|
|
13761
|
+
"does not work",
|
|
13762
|
+
"isn't working",
|
|
13763
|
+
"is not working",
|
|
13764
|
+
"can't figure out",
|
|
13765
|
+
"stack trace"
|
|
13766
|
+
];
|
|
13767
|
+
var DEBUG_WORDS = [
|
|
13768
|
+
"fix",
|
|
13769
|
+
"bug",
|
|
13770
|
+
"broken",
|
|
13771
|
+
"crash",
|
|
13772
|
+
"crashing",
|
|
13773
|
+
"failing",
|
|
13774
|
+
"debug",
|
|
13775
|
+
"debugging",
|
|
13776
|
+
"investigate",
|
|
13777
|
+
"troubleshoot",
|
|
13778
|
+
"regression",
|
|
13779
|
+
"wrong"
|
|
13780
|
+
];
|
|
13781
|
+
var DEBUG_PATTERN = new RegExp(
|
|
13782
|
+
[
|
|
13783
|
+
...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
13784
|
+
...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
|
|
13785
|
+
].join("|"),
|
|
13786
|
+
"i"
|
|
13787
|
+
);
|
|
13788
|
+
var FALSE_POSITIVE_PATTERNS = [
|
|
13789
|
+
/\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
|
|
13790
|
+
/\berror\s+handling\b/i,
|
|
13791
|
+
/\berror\s+boundar(?:y|ies)\b/i,
|
|
13792
|
+
/\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
|
|
13793
|
+
/\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
|
|
13794
|
+
];
|
|
13795
|
+
function hasDebugIntent(prompt) {
|
|
13796
|
+
if (!DEBUG_PATTERN.test(prompt)) return false;
|
|
13797
|
+
for (const fp of FALSE_POSITIVE_PATTERNS) {
|
|
13798
|
+
if (fp.test(prompt)) return false;
|
|
13799
|
+
}
|
|
13800
|
+
return true;
|
|
13801
|
+
}
|
|
13802
|
+
var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
|
|
13803
|
+
var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
|
|
13804
|
+
function isGitOnlyPrompt(prompt) {
|
|
13805
|
+
if (!GIT_ONLY_PATTERN.test(prompt)) return false;
|
|
13806
|
+
if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
|
|
13807
|
+
return true;
|
|
13808
|
+
}
|
|
13809
|
+
function reconcileAnalysisMode(predictedMode, signals) {
|
|
13810
|
+
const mode2 = resolveAnalysisMode(predictedMode, signals);
|
|
13811
|
+
if (mode2 !== "skip") return mode2;
|
|
13812
|
+
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
13813
|
+
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
13814
|
+
return mode2;
|
|
13815
|
+
}
|
|
13816
|
+
function resolveAnalysisMode(predictedMode, signals) {
|
|
13817
|
+
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
13818
|
+
return detectAnalysisMode(
|
|
13819
|
+
signals.noFilesChanged,
|
|
13820
|
+
signals.assistantResponse,
|
|
13821
|
+
signals.conversationPrompts,
|
|
13822
|
+
signals.actionSummary,
|
|
13823
|
+
signals.sessionAuthoredCode
|
|
13824
|
+
);
|
|
13825
|
+
}
|
|
13826
|
+
const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
|
|
13827
|
+
const agentInvestigated = didAgentInvestigate(signals.actionSummary);
|
|
13828
|
+
switch (predictedMode) {
|
|
13829
|
+
case "skip":
|
|
13830
|
+
if (agentAuthoredCode) return "standard";
|
|
13831
|
+
return "skip";
|
|
13832
|
+
case "plan":
|
|
13833
|
+
if (agentAuthoredCode) return "standard";
|
|
13834
|
+
return "plan";
|
|
13835
|
+
case "debug":
|
|
13836
|
+
return "debug";
|
|
13837
|
+
case "standard":
|
|
13838
|
+
if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
|
|
13839
|
+
return agentInvestigated ? "plan" : "skip";
|
|
13840
|
+
}
|
|
13841
|
+
return "standard";
|
|
13842
|
+
}
|
|
13843
|
+
}
|
|
13844
|
+
function didAgentInvestigate(summary) {
|
|
13845
|
+
if (!summary) return false;
|
|
13846
|
+
return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
|
|
13847
|
+
}
|
|
13848
|
+
function isValidMode(mode2) {
|
|
13849
|
+
return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
|
|
13850
|
+
}
|
|
13851
|
+
function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
|
|
13852
|
+
const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
|
|
13853
|
+
if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
|
|
13854
|
+
if (!agentAuthoredCode) return "skip";
|
|
13855
|
+
}
|
|
13856
|
+
if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
|
|
13857
|
+
return "plan";
|
|
13858
|
+
}
|
|
13859
|
+
if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
|
|
13860
|
+
return didAgentInvestigate(actionSummary) ? "plan" : "skip";
|
|
13861
|
+
}
|
|
13862
|
+
for (const prompt of conversationPrompts) {
|
|
13863
|
+
if (hasDebugIntent(prompt)) {
|
|
13864
|
+
return "debug";
|
|
13865
|
+
}
|
|
13866
|
+
}
|
|
13867
|
+
return "standard";
|
|
13868
|
+
}
|
|
13869
|
+
var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
|
|
13870
|
+
var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
|
|
13871
|
+
var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
|
|
13872
|
+
var CHAIN_RE = /&&|\||;|\$\(|\x60/;
|
|
13873
|
+
function isNonAuthoringCommand(cmd) {
|
|
13874
|
+
if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
|
|
13875
|
+
if (FILE_MUTATE_RE.test(cmd)) return false;
|
|
13876
|
+
if (CHAIN_RE.test(cmd)) return false;
|
|
13877
|
+
return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
|
|
13878
|
+
}
|
|
13879
|
+
function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
|
|
13880
|
+
if (!actionSummary) return sessionAuthoredCode;
|
|
13881
|
+
if ((actionSummary.subagents ?? 0) > 0) return true;
|
|
13882
|
+
if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
|
|
13883
|
+
const commands = actionSummary.commands ?? [];
|
|
13884
|
+
if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
|
|
13885
|
+
if (sessionAuthoredCode) {
|
|
13886
|
+
const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
|
|
13887
|
+
if (!allSafe) return true;
|
|
13888
|
+
}
|
|
13889
|
+
return false;
|
|
13890
|
+
}
|
|
13891
|
+
function scopeToAuthored(files, actionSummary) {
|
|
13892
|
+
if (!actionSummary) return { files, signal: "no-transcript" };
|
|
13893
|
+
const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
|
|
13894
|
+
if (touched.length === 0) return { files: [], signal: "none-authored" };
|
|
13895
|
+
return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
|
|
13896
|
+
}
|
|
13897
|
+
function narrowToAgentAuthored(files, actionSummary) {
|
|
13898
|
+
if (!actionSummary) return files;
|
|
13899
|
+
const touched = [
|
|
13900
|
+
...actionSummary.files_edited,
|
|
13901
|
+
...actionSummary.files_created
|
|
13902
|
+
];
|
|
13903
|
+
if (touched.length === 0) return files;
|
|
13904
|
+
return files.filter((f) => {
|
|
13905
|
+
const suffix = "/" + f;
|
|
13906
|
+
return touched.some((t) => t === f || t.endsWith(suffix));
|
|
13907
|
+
});
|
|
13908
|
+
}
|
|
13909
|
+
|
|
13750
13910
|
// src/lib/skip-detection.ts
|
|
13751
13911
|
function isBareAckPrompt(prompt) {
|
|
13752
13912
|
if (typeof prompt !== "string") return false;
|
|
@@ -13810,6 +13970,15 @@ function shouldSkipForBareAck(input) {
|
|
|
13810
13970
|
if (input.turnAuthoredCode) return false;
|
|
13811
13971
|
return input.canSeeTurnAuthorship;
|
|
13812
13972
|
}
|
|
13973
|
+
function isCommandOnlyTurn(input) {
|
|
13974
|
+
if (!input.authorshipIsObservable) return false;
|
|
13975
|
+
if (input.userCommandsTruncated) return false;
|
|
13976
|
+
const commands = input.userCommands ?? [];
|
|
13977
|
+
if (commands.length === 0) return false;
|
|
13978
|
+
if (input.agentAuthoredFiles > 0) return false;
|
|
13979
|
+
if (input.agentToolCalls > 0) return false;
|
|
13980
|
+
return commands.every(isNonAuthoringCommand);
|
|
13981
|
+
}
|
|
13813
13982
|
|
|
13814
13983
|
// src/lib/pending-repeat.ts
|
|
13815
13984
|
var STOP = /* @__PURE__ */ new Set([
|
|
@@ -16119,17 +16288,17 @@ async function readHookStdin() {
|
|
|
16119
16288
|
try {
|
|
16120
16289
|
if (process.stdin.isTTY) return {};
|
|
16121
16290
|
const chunks = [];
|
|
16122
|
-
const timeout = new Promise((
|
|
16123
|
-
const read = new Promise((
|
|
16291
|
+
const timeout = new Promise((resolve4) => setTimeout(() => resolve4({}), 500));
|
|
16292
|
+
const read = new Promise((resolve4) => {
|
|
16124
16293
|
process.stdin.on("data", (c) => chunks.push(c));
|
|
16125
16294
|
process.stdin.on("end", () => {
|
|
16126
16295
|
try {
|
|
16127
|
-
|
|
16296
|
+
resolve4(JSON.parse(Buffer.concat(chunks).toString("utf-8").trim() || "{}"));
|
|
16128
16297
|
} catch {
|
|
16129
|
-
|
|
16298
|
+
resolve4({});
|
|
16130
16299
|
}
|
|
16131
16300
|
});
|
|
16132
|
-
process.stdin.on("error", () =>
|
|
16301
|
+
process.stdin.on("error", () => resolve4({}));
|
|
16133
16302
|
process.stdin.resume();
|
|
16134
16303
|
});
|
|
16135
16304
|
return await Promise.race([read, timeout]);
|
|
@@ -16140,7 +16309,145 @@ async function readHookStdin() {
|
|
|
16140
16309
|
|
|
16141
16310
|
// src/commands/standard.ts
|
|
16142
16311
|
var import_promises9 = require("node:fs/promises");
|
|
16312
|
+
var import_node_fs20 = require("node:fs");
|
|
16143
16313
|
var import_yaml = __toESM(require_dist());
|
|
16314
|
+
|
|
16315
|
+
// src/lib/verityignore.ts
|
|
16316
|
+
var import_node_fs19 = require("node:fs");
|
|
16317
|
+
var EMPTY = { rules: [], securityOverlap: [], problems: [] };
|
|
16318
|
+
var SECURITY_PROBES = [
|
|
16319
|
+
".env",
|
|
16320
|
+
".env.local",
|
|
16321
|
+
".env.production",
|
|
16322
|
+
"config/.env",
|
|
16323
|
+
"services/api/.env",
|
|
16324
|
+
"package-lock.json",
|
|
16325
|
+
"yarn.lock",
|
|
16326
|
+
"pnpm-lock.yaml",
|
|
16327
|
+
"Cargo.lock",
|
|
16328
|
+
"go.sum",
|
|
16329
|
+
"Gemfile.lock",
|
|
16330
|
+
"Dockerfile",
|
|
16331
|
+
"docker/Dockerfile"
|
|
16332
|
+
];
|
|
16333
|
+
function isSecuritySensitive(path) {
|
|
16334
|
+
return SECURITY_PATTERNS.some((p) => p.test(path));
|
|
16335
|
+
}
|
|
16336
|
+
function compile(pattern) {
|
|
16337
|
+
let p = pattern;
|
|
16338
|
+
const dirOnly = p.endsWith("/");
|
|
16339
|
+
if (dirOnly) p = p.slice(0, -1);
|
|
16340
|
+
const anchored = p.includes("/");
|
|
16341
|
+
if (p.startsWith("/")) p = p.slice(1);
|
|
16342
|
+
const base = anchored ? p : `**/${p}`;
|
|
16343
|
+
const forms = dirOnly ? [`${base}/**/*`] : [base, `${base}/**`];
|
|
16344
|
+
const regexes = [];
|
|
16345
|
+
for (const f of forms) {
|
|
16346
|
+
try {
|
|
16347
|
+
regexes.push(globToRegex(f));
|
|
16348
|
+
} catch {
|
|
16349
|
+
}
|
|
16350
|
+
}
|
|
16351
|
+
if (regexes.length === 0) return () => false;
|
|
16352
|
+
return (path) => regexes.some((r) => r.test(path));
|
|
16353
|
+
}
|
|
16354
|
+
function parseVerityIgnore(content) {
|
|
16355
|
+
const rules = [];
|
|
16356
|
+
const problems = [];
|
|
16357
|
+
const lines = content.split("\n");
|
|
16358
|
+
for (let i = 0; i < lines.length; i++) {
|
|
16359
|
+
const lineNo = i + 1;
|
|
16360
|
+
let raw = lines[i];
|
|
16361
|
+
raw = raw.replace(/(?<!\\)\s+$/, "");
|
|
16362
|
+
if (raw.length === 0) continue;
|
|
16363
|
+
if (raw.startsWith("#")) continue;
|
|
16364
|
+
let negated = false;
|
|
16365
|
+
if (raw.startsWith("!")) {
|
|
16366
|
+
negated = true;
|
|
16367
|
+
raw = raw.slice(1);
|
|
16368
|
+
} else if (raw.startsWith("\\!") || raw.startsWith("\\#")) {
|
|
16369
|
+
raw = raw.slice(1);
|
|
16370
|
+
}
|
|
16371
|
+
if (raw.length === 0) {
|
|
16372
|
+
problems.push(`line ${lineNo}: "!" with no pattern after it`);
|
|
16373
|
+
continue;
|
|
16374
|
+
}
|
|
16375
|
+
if (raw === "**" || raw === "*" || raw === "/" || raw === "**/*") {
|
|
16376
|
+
problems.push(
|
|
16377
|
+
`line ${lineNo}: "${raw}" would exclude the whole repository from review \u2014 refused. List the directories you mean instead.`
|
|
16378
|
+
);
|
|
16379
|
+
continue;
|
|
16380
|
+
}
|
|
16381
|
+
rules.push({ raw, line: lineNo, negated, test: compile(raw) });
|
|
16382
|
+
}
|
|
16383
|
+
const byRule = /* @__PURE__ */ new Map();
|
|
16384
|
+
for (const probe of SECURITY_PROBES) {
|
|
16385
|
+
let responsible = null;
|
|
16386
|
+
for (const rule of rules) {
|
|
16387
|
+
if (rule.test(probe)) responsible = rule.negated ? null : rule;
|
|
16388
|
+
}
|
|
16389
|
+
if (!responsible) continue;
|
|
16390
|
+
const entry = byRule.get(responsible.line) ?? { raw: responsible.raw, line: responsible.line, hides: [] };
|
|
16391
|
+
entry.hides.push(probe);
|
|
16392
|
+
byRule.set(responsible.line, entry);
|
|
16393
|
+
}
|
|
16394
|
+
const securityOverlap = [...byRule.values()].sort((a, b) => a.line - b.line);
|
|
16395
|
+
return { rules, securityOverlap, problems };
|
|
16396
|
+
}
|
|
16397
|
+
function decide(rules, path) {
|
|
16398
|
+
let excluded = false;
|
|
16399
|
+
for (const rule of rules) {
|
|
16400
|
+
if (rule.test(path)) excluded = !rule.negated;
|
|
16401
|
+
}
|
|
16402
|
+
return excluded;
|
|
16403
|
+
}
|
|
16404
|
+
function isIgnored(ig, path) {
|
|
16405
|
+
return decide(ig.rules, path);
|
|
16406
|
+
}
|
|
16407
|
+
function loadVerityIgnore() {
|
|
16408
|
+
const file = projectPath(VERITYIGNORE_FILE);
|
|
16409
|
+
if (!(0, import_node_fs19.existsSync)(file)) return EMPTY;
|
|
16410
|
+
try {
|
|
16411
|
+
return parseVerityIgnore((0, import_node_fs19.readFileSync)(file, "utf-8"));
|
|
16412
|
+
} catch {
|
|
16413
|
+
return EMPTY;
|
|
16414
|
+
}
|
|
16415
|
+
}
|
|
16416
|
+
function partitionIgnored(paths, ig) {
|
|
16417
|
+
const suspended = paths.some((p) => p === VERITYIGNORE_FILE);
|
|
16418
|
+
if (suspended || ig.rules.length === 0) {
|
|
16419
|
+
return { rules: ig.rules.length, kept: [...paths], ignored: [], securityExcluded: [], suspended };
|
|
16420
|
+
}
|
|
16421
|
+
const kept = [];
|
|
16422
|
+
const ignored = [];
|
|
16423
|
+
for (const p of paths) {
|
|
16424
|
+
if (p !== VERITYIGNORE_FILE && isIgnored(ig, p)) ignored.push(p);
|
|
16425
|
+
else kept.push(p);
|
|
16426
|
+
}
|
|
16427
|
+
return {
|
|
16428
|
+
rules: ig.rules.length,
|
|
16429
|
+
kept,
|
|
16430
|
+
ignored,
|
|
16431
|
+
securityExcluded: ignored.filter(isSecuritySensitive),
|
|
16432
|
+
suspended: false
|
|
16433
|
+
};
|
|
16434
|
+
}
|
|
16435
|
+
function ignoreShare(kept, ignored) {
|
|
16436
|
+
const total = kept + ignored;
|
|
16437
|
+
if (total === 0) return 0;
|
|
16438
|
+
return Math.round(ignored / total * 1e3) / 1e3;
|
|
16439
|
+
}
|
|
16440
|
+
function describeSecurityOverlap(ig) {
|
|
16441
|
+
if (ig.securityOverlap.length === 0) return null;
|
|
16442
|
+
const lines = ig.securityOverlap.map(
|
|
16443
|
+
(o) => ` line ${o.line}: "${o.raw}" would hide ${o.hides.slice(0, 3).join(", ")}` + (o.hides.length > 3 ? ` (+${o.hides.length - 3} more)` : "")
|
|
16444
|
+
);
|
|
16445
|
+
return `.verityignore: ${ig.securityOverlap.length} pattern(s) can hide security-sensitive files from review:
|
|
16446
|
+
${lines.join("\n")}
|
|
16447
|
+
These are still excluded \u2014 this is a warning, not a refusal. Add a \`!\` rule to keep one in scope, e.g. \`!.env*\`.`;
|
|
16448
|
+
}
|
|
16449
|
+
|
|
16450
|
+
// src/commands/standard.ts
|
|
16144
16451
|
function registerStandardCommands(program2) {
|
|
16145
16452
|
const standard = program2.command("standard").description("Manage the project Standard");
|
|
16146
16453
|
standard.command("push").description("Upload the project Standard to the service").option("--file <path>", "Path to standard YAML file", STANDARD_FILE).option("--created-by <name>", "Who created this version", "claude-code").action(async (opts) => {
|
|
@@ -16169,12 +16476,25 @@ function registerStandardCommands(program2) {
|
|
|
16169
16476
|
printError(`Invalid YAML in ${opts.file}: ${err.message}`);
|
|
16170
16477
|
process.exit(1);
|
|
16171
16478
|
}
|
|
16479
|
+
let ignoreRaw = null;
|
|
16480
|
+
const ignorePath = projectPath(VERITYIGNORE_FILE);
|
|
16481
|
+
if ((0, import_node_fs20.existsSync)(ignorePath)) {
|
|
16482
|
+
try {
|
|
16483
|
+
ignoreRaw = (0, import_node_fs20.readFileSync)(ignorePath, "utf-8");
|
|
16484
|
+
const overlap = describeSecurityOverlap(parseVerityIgnore(ignoreRaw));
|
|
16485
|
+
if (overlap) printWarn(overlap);
|
|
16486
|
+
} catch {
|
|
16487
|
+
}
|
|
16488
|
+
}
|
|
16172
16489
|
const result = await apiRequest({
|
|
16173
16490
|
method: "POST",
|
|
16174
16491
|
path: "/standards",
|
|
16175
16492
|
serviceUrl: urlResult.data,
|
|
16176
16493
|
token: tokenResult.data.token,
|
|
16177
|
-
body: {
|
|
16494
|
+
body: {
|
|
16495
|
+
content: ignoreRaw === null ? content : { ...content, verityignore: ignoreRaw },
|
|
16496
|
+
created_by: opts.createdBy
|
|
16497
|
+
},
|
|
16178
16498
|
verbose: globals.verbose
|
|
16179
16499
|
});
|
|
16180
16500
|
if (!result.ok) {
|
|
@@ -16392,27 +16712,330 @@ function formatRunDetail(run) {
|
|
|
16392
16712
|
return lines;
|
|
16393
16713
|
}
|
|
16394
16714
|
|
|
16395
|
-
// src/
|
|
16396
|
-
|
|
16397
|
-
|
|
16398
|
-
|
|
16399
|
-
|
|
16400
|
-
|
|
16401
|
-
|
|
16402
|
-
if (
|
|
16403
|
-
|
|
16404
|
-
return `${days}d ago`;
|
|
16715
|
+
// src/lib/ignore-declaration.ts
|
|
16716
|
+
var import_node_fs22 = require("node:fs");
|
|
16717
|
+
|
|
16718
|
+
// src/lib/debounce.ts
|
|
16719
|
+
var import_node_fs21 = require("node:fs");
|
|
16720
|
+
var import_node_crypto10 = require("node:crypto");
|
|
16721
|
+
function scopedFile(base, sessionId) {
|
|
16722
|
+
if (!sessionId) return base;
|
|
16723
|
+
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
16405
16724
|
}
|
|
16406
|
-
function
|
|
16407
|
-
|
|
16408
|
-
|
|
16409
|
-
|
|
16410
|
-
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
|
|
16415
|
-
|
|
16725
|
+
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
16726
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16727
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16728
|
+
try {
|
|
16729
|
+
const lastTs = parseInt((0, import_node_fs21.readFileSync)(file, "utf-8").trim(), 10);
|
|
16730
|
+
const nowTs = Math.floor(Date.now() / 1e3);
|
|
16731
|
+
const elapsed = nowTs - lastTs;
|
|
16732
|
+
if (elapsed < debounceSeconds) {
|
|
16733
|
+
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
16734
|
+
}
|
|
16735
|
+
} catch {
|
|
16736
|
+
}
|
|
16737
|
+
return null;
|
|
16738
|
+
}
|
|
16739
|
+
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
16740
|
+
if (bypassForRecentCommits) return null;
|
|
16741
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16742
|
+
if (!(0, import_node_fs21.existsSync)(file)) return null;
|
|
16743
|
+
let debounceTime;
|
|
16744
|
+
try {
|
|
16745
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16746
|
+
} catch {
|
|
16747
|
+
return null;
|
|
16748
|
+
}
|
|
16749
|
+
for (const f of files) {
|
|
16750
|
+
const resolved = resolveFile(f);
|
|
16751
|
+
if (!resolved) continue;
|
|
16752
|
+
try {
|
|
16753
|
+
const stat3 = (0, import_node_fs21.statSync)(resolved);
|
|
16754
|
+
if (stat3.mtimeMs > debounceTime) {
|
|
16755
|
+
return null;
|
|
16756
|
+
}
|
|
16757
|
+
} catch {
|
|
16758
|
+
continue;
|
|
16759
|
+
}
|
|
16760
|
+
}
|
|
16761
|
+
return "No files modified since last analysis";
|
|
16762
|
+
}
|
|
16763
|
+
function computeContentHash(files) {
|
|
16764
|
+
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
16765
|
+
const sorted = [...files].sort();
|
|
16766
|
+
for (const f of sorted) {
|
|
16767
|
+
const resolved = resolveFile(f) ?? f;
|
|
16768
|
+
try {
|
|
16769
|
+
if ((0, import_node_fs21.existsSync)(resolved)) {
|
|
16770
|
+
hash.update((0, import_node_fs21.readFileSync)(resolved));
|
|
16771
|
+
}
|
|
16772
|
+
} catch {
|
|
16773
|
+
}
|
|
16774
|
+
}
|
|
16775
|
+
return hash.digest("hex");
|
|
16776
|
+
}
|
|
16777
|
+
function checkContentHash(files, sessionId) {
|
|
16778
|
+
const hash = computeContentHash(files);
|
|
16779
|
+
const file = scopedFile(HASH_FILE, sessionId);
|
|
16780
|
+
if ((0, import_node_fs21.existsSync)(file)) {
|
|
16781
|
+
try {
|
|
16782
|
+
const storedHash = (0, import_node_fs21.readFileSync)(file, "utf-8").trim();
|
|
16783
|
+
if (hash === storedHash) {
|
|
16784
|
+
return { skip: "No source changes since last analysis", hash };
|
|
16785
|
+
}
|
|
16786
|
+
} catch {
|
|
16787
|
+
}
|
|
16788
|
+
}
|
|
16789
|
+
return { skip: null, hash };
|
|
16790
|
+
}
|
|
16791
|
+
function recordAnalysisStart(sessionId) {
|
|
16792
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16793
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
16794
|
+
}
|
|
16795
|
+
function recordPassHash(hash, sessionId) {
|
|
16796
|
+
(0, import_node_fs21.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
16797
|
+
}
|
|
16798
|
+
function narrowToRecent(files, sessionId) {
|
|
16799
|
+
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
16800
|
+
if (!(0, import_node_fs21.existsSync)(file)) return files;
|
|
16801
|
+
let debounceTime;
|
|
16802
|
+
try {
|
|
16803
|
+
debounceTime = (0, import_node_fs21.statSync)(file).mtimeMs;
|
|
16804
|
+
} catch {
|
|
16805
|
+
return files;
|
|
16806
|
+
}
|
|
16807
|
+
const recent = files.filter((f) => {
|
|
16808
|
+
try {
|
|
16809
|
+
return (0, import_node_fs21.existsSync)(f) && (0, import_node_fs21.statSync)(f).mtimeMs > debounceTime;
|
|
16810
|
+
} catch {
|
|
16811
|
+
return false;
|
|
16812
|
+
}
|
|
16813
|
+
});
|
|
16814
|
+
return recent.length > 0 ? recent : files;
|
|
16815
|
+
}
|
|
16816
|
+
function readIteration(currentCommit, _contentHash) {
|
|
16817
|
+
return Math.max(1, readBlockState(currentCommit).attempts);
|
|
16818
|
+
}
|
|
16819
|
+
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
16820
|
+
function readBlockState(currentCommit, opts) {
|
|
16821
|
+
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
16822
|
+
if (!(0, import_node_fs21.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
16823
|
+
try {
|
|
16824
|
+
const stored = (0, import_node_fs21.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
16825
|
+
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
16826
|
+
if (!parsed) return NO_BLOCKS;
|
|
16827
|
+
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
16828
|
+
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
16829
|
+
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
16830
|
+
} catch {
|
|
16831
|
+
return NO_BLOCKS;
|
|
16832
|
+
}
|
|
16833
|
+
}
|
|
16834
|
+
function parseJsonState(raw) {
|
|
16835
|
+
const o = JSON.parse(raw);
|
|
16836
|
+
const attempts = typeof o.attempts === "number" ? o.attempts : NaN;
|
|
16837
|
+
if (isNaN(attempts)) return null;
|
|
16838
|
+
return {
|
|
16839
|
+
attempts,
|
|
16840
|
+
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
16841
|
+
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
16842
|
+
commit: typeof o.commit === "string" ? o.commit : "",
|
|
16843
|
+
ts: typeof o.ts === "number" ? o.ts : 0
|
|
16844
|
+
};
|
|
16845
|
+
}
|
|
16846
|
+
function parseLegacyState(raw) {
|
|
16847
|
+
const parts = raw.split(":");
|
|
16848
|
+
const n = parseInt(parts[0], 10);
|
|
16849
|
+
if (isNaN(n)) return null;
|
|
16850
|
+
return {
|
|
16851
|
+
attempts: n,
|
|
16852
|
+
// The old file has no separate block count; the old counter is the closest
|
|
16853
|
+
// honest answer, and it errs toward releasing sooner rather than later.
|
|
16854
|
+
blocks: n,
|
|
16855
|
+
fingerprint: parts.slice(3).join(":") || null,
|
|
16856
|
+
commit: parts[1] ?? "",
|
|
16857
|
+
ts: parseInt(parts[2] ?? "0", 10)
|
|
16858
|
+
};
|
|
16859
|
+
}
|
|
16860
|
+
function findingsFingerprint(findings) {
|
|
16861
|
+
const keys = findings.map((f) => `${String(f.pattern_id ?? "?")}|${String(f.file ?? "?")}`).filter((k) => k !== "?|?");
|
|
16862
|
+
return [...new Set(keys)].sort().join(",");
|
|
16863
|
+
}
|
|
16864
|
+
function isSameProblem(previous, current) {
|
|
16865
|
+
if (!previous || !current) return false;
|
|
16866
|
+
const prev = new Set(previous.split(","));
|
|
16867
|
+
return current.split(",").some((k) => prev.has(k));
|
|
16868
|
+
}
|
|
16869
|
+
function writeBlockState(commit, state) {
|
|
16870
|
+
(0, import_node_fs21.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
16871
|
+
(0, import_node_fs21.writeFileSync)(
|
|
16872
|
+
ITERATION_FILE,
|
|
16873
|
+
JSON.stringify({
|
|
16874
|
+
v: 2,
|
|
16875
|
+
attempts: state.attempts,
|
|
16876
|
+
blocks: state.blocks,
|
|
16877
|
+
commit,
|
|
16878
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
16879
|
+
fingerprint: state.fingerprint ?? void 0
|
|
16880
|
+
})
|
|
16881
|
+
);
|
|
16882
|
+
}
|
|
16883
|
+
function resetBlockState(commit) {
|
|
16884
|
+
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
16885
|
+
}
|
|
16886
|
+
|
|
16887
|
+
// src/lib/ignore-declaration.ts
|
|
16888
|
+
var IGNORE_BUDGET = 3;
|
|
16889
|
+
var MAX_WINDOW_SECONDS = 60 * 60;
|
|
16890
|
+
var TURN_FUSE_SECONDS = 10 * 60;
|
|
16891
|
+
function parseDuration(input) {
|
|
16892
|
+
const trimmed = input.trim().toLowerCase();
|
|
16893
|
+
const m = /^(\d+)(s|m|h)?$/.exec(trimmed);
|
|
16894
|
+
if (!m) {
|
|
16895
|
+
return { ok: false, error: `Could not read "${input}" as a duration. Use 30m, 45s, or 1h.` };
|
|
16896
|
+
}
|
|
16897
|
+
const n = parseInt(m[1], 10);
|
|
16898
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
16899
|
+
return { ok: false, error: `A duration must be a positive number of seconds, minutes or hours \u2014 got "${input}".` };
|
|
16900
|
+
}
|
|
16901
|
+
const unit = m[2] ?? "m";
|
|
16902
|
+
const seconds = unit === "s" ? n : unit === "h" ? n * 3600 : n * 60;
|
|
16903
|
+
if (seconds > MAX_WINDOW_SECONDS) {
|
|
16904
|
+
return {
|
|
16905
|
+
ok: false,
|
|
16906
|
+
error: `${input} is longer than the ${MAX_WINDOW_SECONDS / 60}-minute maximum. An ignore is meant to cover one piece of housekeeping, not a sitting \u2014 declare it again when you need it.`
|
|
16907
|
+
};
|
|
16908
|
+
}
|
|
16909
|
+
return { ok: true, seconds };
|
|
16910
|
+
}
|
|
16911
|
+
function resolveActive(state, now) {
|
|
16912
|
+
if (!state?.active) return null;
|
|
16913
|
+
if (state.active.expires <= now) return null;
|
|
16914
|
+
return state.active;
|
|
16915
|
+
}
|
|
16916
|
+
function verifyDeclaration(input) {
|
|
16917
|
+
const d = input.declaration;
|
|
16918
|
+
if (!d) return { honoured: false, void: false };
|
|
16919
|
+
if (!input.authorshipIsObservable) {
|
|
16920
|
+
return {
|
|
16921
|
+
honoured: false,
|
|
16922
|
+
void: true,
|
|
16923
|
+
why: "the transcript window could not show what this turn did, so the declaration could not be checked"
|
|
16924
|
+
};
|
|
16925
|
+
}
|
|
16926
|
+
if (input.commandRecordTruncated) {
|
|
16927
|
+
return {
|
|
16928
|
+
honoured: false,
|
|
16929
|
+
void: true,
|
|
16930
|
+
why: "the record of commands run this turn was incomplete, so the declaration could not be checked"
|
|
16931
|
+
};
|
|
16932
|
+
}
|
|
16933
|
+
if (input.agentAuthoredFiles > 0) {
|
|
16934
|
+
return {
|
|
16935
|
+
honoured: false,
|
|
16936
|
+
void: true,
|
|
16937
|
+
why: `${input.agentAuthoredFiles} file${input.agentAuthoredFiles === 1 ? " was" : "s were"} authored during it`
|
|
16938
|
+
};
|
|
16939
|
+
}
|
|
16940
|
+
if (input.subagents > 0) {
|
|
16941
|
+
return {
|
|
16942
|
+
honoured: false,
|
|
16943
|
+
void: true,
|
|
16944
|
+
why: "work was delegated to a subagent during it, whose authorship this turn cannot account for"
|
|
16945
|
+
};
|
|
16946
|
+
}
|
|
16947
|
+
const authoring = [...input.agentCommands ?? [], ...input.userCommands ?? []].filter(
|
|
16948
|
+
(c) => !isNonAuthoringCommand(c)
|
|
16949
|
+
);
|
|
16950
|
+
if (authoring.length > 0) {
|
|
16951
|
+
return {
|
|
16952
|
+
honoured: false,
|
|
16953
|
+
void: true,
|
|
16954
|
+
why: `a command that can write files ran during it (${authoring[0]})`
|
|
16955
|
+
};
|
|
16956
|
+
}
|
|
16957
|
+
return { honoured: true };
|
|
16958
|
+
}
|
|
16959
|
+
function stateFile(sessionId) {
|
|
16960
|
+
return projectPath(scopedFile(IGNORE_DECLARATION_FILE, sessionId));
|
|
16961
|
+
}
|
|
16962
|
+
function ignoreStateKeys(token, sessionId) {
|
|
16963
|
+
const scoped = sessionScopeKey(token, sessionId);
|
|
16964
|
+
const userOnly = sessionScopeKey(token, void 0);
|
|
16965
|
+
return scoped === userOnly ? [scoped] : [scoped, userOnly];
|
|
16966
|
+
}
|
|
16967
|
+
function resolveIgnoreState(keys) {
|
|
16968
|
+
for (const key of keys) {
|
|
16969
|
+
const state = readIgnoreState(key);
|
|
16970
|
+
if (state) return { state, key };
|
|
16971
|
+
}
|
|
16972
|
+
return null;
|
|
16973
|
+
}
|
|
16974
|
+
function readIgnoreState(sessionId) {
|
|
16975
|
+
const file = stateFile(sessionId);
|
|
16976
|
+
if (!(0, import_node_fs22.existsSync)(file)) return null;
|
|
16977
|
+
try {
|
|
16978
|
+
const o = JSON.parse((0, import_node_fs22.readFileSync)(file, "utf-8")) ?? {};
|
|
16979
|
+
const spent = typeof o.spent === "number" ? o.spent : 0;
|
|
16980
|
+
const raw = o.active;
|
|
16981
|
+
let active = null;
|
|
16982
|
+
if (raw && typeof raw === "object") {
|
|
16983
|
+
const scope2 = raw.scope === "window" ? "window" : raw.scope === "turn" ? "turn" : null;
|
|
16984
|
+
const expires = typeof raw.expires === "number" ? raw.expires : null;
|
|
16985
|
+
if (scope2 && expires != null) {
|
|
16986
|
+
active = {
|
|
16987
|
+
scope: scope2,
|
|
16988
|
+
origin: raw.origin === "agent" ? "agent" : "user",
|
|
16989
|
+
reason: typeof raw.reason === "string" ? raw.reason : "",
|
|
16990
|
+
at: typeof raw.at === "number" ? raw.at : 0,
|
|
16991
|
+
expires
|
|
16992
|
+
};
|
|
16993
|
+
}
|
|
16994
|
+
}
|
|
16995
|
+
return { v: 1, active, spent };
|
|
16996
|
+
} catch {
|
|
16997
|
+
return null;
|
|
16998
|
+
}
|
|
16999
|
+
}
|
|
17000
|
+
function writeIgnoreState(state, sessionId) {
|
|
17001
|
+
try {
|
|
17002
|
+
(0, import_node_fs22.mkdirSync)(projectPath(VERITY_DIR), { recursive: true });
|
|
17003
|
+
(0, import_node_fs22.writeFileSync)(stateFile(sessionId), JSON.stringify({ v: 1, active: state.active, spent: state.spent }));
|
|
17004
|
+
} catch {
|
|
17005
|
+
}
|
|
17006
|
+
}
|
|
17007
|
+
function clearActiveDeclaration(sessionId) {
|
|
17008
|
+
const prev = readIgnoreState(sessionId);
|
|
17009
|
+
if (!prev) return;
|
|
17010
|
+
writeIgnoreState({ v: 1, active: null, spent: prev.spent }, sessionId);
|
|
17011
|
+
}
|
|
17012
|
+
function describeRemaining(d, now) {
|
|
17013
|
+
const left = Math.max(0, d.expires - now);
|
|
17014
|
+
if (left >= 90) return `${Math.round(left / 60)}m left`;
|
|
17015
|
+
return `${left}s left`;
|
|
17016
|
+
}
|
|
17017
|
+
|
|
17018
|
+
// src/commands/status.ts
|
|
17019
|
+
function timeAgo(isoDate) {
|
|
17020
|
+
const ms = Date.now() - new Date(isoDate).getTime();
|
|
17021
|
+
const mins = Math.floor(ms / 6e4);
|
|
17022
|
+
if (mins < 1) return "just now";
|
|
17023
|
+
if (mins < 60) return `${mins}m ago`;
|
|
17024
|
+
const hrs = Math.floor(mins / 60);
|
|
17025
|
+
if (hrs < 24) return `${hrs}h ago`;
|
|
17026
|
+
const days = Math.floor(hrs / 24);
|
|
17027
|
+
return `${days}d ago`;
|
|
17028
|
+
}
|
|
17029
|
+
function registerStatusCommand(program2) {
|
|
17030
|
+
program2.command("status").description("Show project quality status").option("--history", "Include recent run history").option("--limit <n>", "Number of history entries", "5").option("--json", "Output raw JSON").action(async (opts) => {
|
|
17031
|
+
const globals = program2.opts();
|
|
17032
|
+
const tokenResult = await resolveToken(globals.token);
|
|
17033
|
+
if (!tokenResult.ok) {
|
|
17034
|
+
printError(tokenResult.error);
|
|
17035
|
+
process.exit(1);
|
|
17036
|
+
}
|
|
17037
|
+
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
17038
|
+
if (!urlResult.ok) {
|
|
16416
17039
|
printError(urlResult.error);
|
|
16417
17040
|
process.exit(1);
|
|
16418
17041
|
}
|
|
@@ -16500,6 +17123,39 @@ function registerStatusCommand(program2) {
|
|
|
16500
17123
|
printInfo(`Trend: ${r.trend}`);
|
|
16501
17124
|
printInfo(`Runs: ${r.count} recorded`);
|
|
16502
17125
|
}
|
|
17126
|
+
{
|
|
17127
|
+
const tokenForScope = tokenResult.data.token;
|
|
17128
|
+
const found = resolveIgnoreState(
|
|
17129
|
+
ignoreStateKeys(tokenForScope, process.env.CLAUDE_SESSION_ID || void 0)
|
|
17130
|
+
);
|
|
17131
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
17132
|
+
const active = resolveActive(found?.state ?? null, now);
|
|
17133
|
+
const spent = found?.state.spent ?? 0;
|
|
17134
|
+
if (active) {
|
|
17135
|
+
printInfo("");
|
|
17136
|
+
printInfo("--- Ignore Declared ---");
|
|
17137
|
+
printInfo(`Scope: ${active.scope === "turn" ? "the next turn" : `a window, ${describeRemaining(active, now)}`}`);
|
|
17138
|
+
printInfo(`Reason: ${active.reason}`);
|
|
17139
|
+
printInfo(`Declared by: ${active.origin === "agent" ? "the agent" : "you"}`);
|
|
17140
|
+
printInfo(`Budget: ${spent}/${IGNORE_BUDGET} declarations this session`);
|
|
17141
|
+
printInfo('Turns that author anything are still reviewed \u2014 the declaration voids. "verity ignore clear" cancels it.');
|
|
17142
|
+
} else if (spent > 0) {
|
|
17143
|
+
printInfo("");
|
|
17144
|
+
printInfo(`Ignore budget: ${spent}/${IGNORE_BUDGET} declarations used this session.`);
|
|
17145
|
+
}
|
|
17146
|
+
}
|
|
17147
|
+
{
|
|
17148
|
+
const ig = loadVerityIgnore();
|
|
17149
|
+
if (ig.rules.length > 0 || ig.problems.length > 0) {
|
|
17150
|
+
printInfo("");
|
|
17151
|
+
printInfo("--- .verityignore ---");
|
|
17152
|
+
printInfo(`Rules: ${ig.rules.length}`);
|
|
17153
|
+
for (const problem of ig.problems) printWarn(` ${problem}`);
|
|
17154
|
+
const overlap = describeSecurityOverlap(ig);
|
|
17155
|
+
if (overlap) printWarn(overlap);
|
|
17156
|
+
printInfo("An edit to .verityignore suspends every rule for that turn \u2014 the edit is always reviewed.");
|
|
17157
|
+
}
|
|
17158
|
+
}
|
|
16503
17159
|
if (mem.pending_items && mem.pending_items.length > 0) {
|
|
16504
17160
|
printInfo("");
|
|
16505
17161
|
printInfo("--- Pending Items ---");
|
|
@@ -16647,6 +17303,7 @@ function createRun(opts, globals) {
|
|
|
16647
17303
|
phaseReached: "",
|
|
16648
17304
|
phasesCompleted: [],
|
|
16649
17305
|
skipReason: null,
|
|
17306
|
+
treeFrame: null,
|
|
16650
17307
|
turnId: "",
|
|
16651
17308
|
// The value `resolveReachability` itself returns when every rung declines.
|
|
16652
17309
|
reachability: { human_reachable: "unknown", human_reachable_source: "inferred", rung: "none" },
|
|
@@ -16662,6 +17319,7 @@ function createRun(opts, globals) {
|
|
|
16662
17319
|
allChanged: [],
|
|
16663
17320
|
hasRecentCommitFiles: false,
|
|
16664
17321
|
changedUniverse: [],
|
|
17322
|
+
verityIgnored: { rules: 0, kept: [], ignored: [], securityExcluded: [], suspended: false },
|
|
16665
17323
|
analyzable: [],
|
|
16666
17324
|
reviewable: [],
|
|
16667
17325
|
securityFiles: [],
|
|
@@ -16695,6 +17353,7 @@ function createRun(opts, globals) {
|
|
|
16695
17353
|
deletedNodePaths: [],
|
|
16696
17354
|
editedUploads: [],
|
|
16697
17355
|
autoSeedNotice: null,
|
|
17356
|
+
voidedIgnoreNotice: null,
|
|
16698
17357
|
foldResult: null,
|
|
16699
17358
|
foldConservation: null,
|
|
16700
17359
|
memorySession: null,
|
|
@@ -16715,7 +17374,7 @@ function createRun(opts, globals) {
|
|
|
16715
17374
|
}
|
|
16716
17375
|
|
|
16717
17376
|
// src/lib/stderr-log.ts
|
|
16718
|
-
var
|
|
17377
|
+
var import_node_fs23 = require("node:fs");
|
|
16719
17378
|
var TOKEN_RE2 = /verity_[0-9a-f]{16,}/g;
|
|
16720
17379
|
var ANSI_RE = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
16721
17380
|
function scrub(s) {
|
|
@@ -16728,9 +17387,9 @@ function append(text) {
|
|
|
16728
17387
|
try {
|
|
16729
17388
|
const dir = projectPath(DEBUG_LOG_DIR);
|
|
16730
17389
|
const file = projectPath(STDERR_LOG_FILE);
|
|
16731
|
-
(0,
|
|
17390
|
+
(0, import_node_fs23.mkdirSync)(dir, { recursive: true });
|
|
16732
17391
|
rotateIfNeeded(file);
|
|
16733
|
-
(0,
|
|
17392
|
+
(0, import_node_fs23.appendFileSync)(file, text);
|
|
16734
17393
|
} catch {
|
|
16735
17394
|
}
|
|
16736
17395
|
}
|
|
@@ -16786,6 +17445,10 @@ function formatRunEvidence(run, startedAt) {
|
|
|
16786
17445
|
`;
|
|
16787
17446
|
out += row("turn", `${run.turnId || "(unminted)"}${run.sessionId ? ` \xB7 session ${run.sessionId}` : ""}`);
|
|
16788
17447
|
out += row("reached", `${run.phaseReached || "(none)"}${run.skipReason ? ` \xB7 SKIPPED: ${run.skipReason}` : ""} \xB7 ${ms}ms`);
|
|
17448
|
+
if (run.treeFrame) {
|
|
17449
|
+
const f = run.treeFrame;
|
|
17450
|
+
out += row("tree", f.worktreeRoot ? `${f.worktreeRoot}${f.isLinkedWorktree ? " \xB7 linked worktree" : ""}${f.branch ? ` \xB7 branch ${f.branch}` : " \xB7 detached"}` : `(unresolved: ${f.refusal ?? "unknown"})`);
|
|
17451
|
+
}
|
|
16789
17452
|
out += row("changed", `${run.changedUniverse.length} from git \xB7 analyzable ${run.analyzable.length} \xB7 reviewable ${run.reviewable.length} \xB7 security ${run.securityFiles.length} \xB7 forReview ${run.allForReview.length}`);
|
|
16790
17453
|
const done = (phase) => run.phasesCompleted.includes(phase);
|
|
16791
17454
|
const ifDone = (phase, value) => done(phase) ? value : "?";
|
|
@@ -16882,17 +17545,312 @@ function installRunEvidence(run) {
|
|
|
16882
17545
|
});
|
|
16883
17546
|
}
|
|
16884
17547
|
|
|
16885
|
-
// src/lib/
|
|
16886
|
-
|
|
16887
|
-
|
|
16888
|
-
|
|
16889
|
-
|
|
17548
|
+
// src/lib/git-frame.ts
|
|
17549
|
+
var import_node_child_process7 = require("node:child_process");
|
|
17550
|
+
var import_node_fs24 = require("node:fs");
|
|
17551
|
+
var import_node_os3 = require("node:os");
|
|
17552
|
+
var import_node_path18 = require("node:path");
|
|
17553
|
+
var import_node_path19 = require("node:path");
|
|
17554
|
+
var VALUE_TOKEN = `(?:'[^']*'|"[^"]*"|\\S+)`;
|
|
17555
|
+
var GIT_GLOBAL_OPTS = `(?:\\s+(?:-[Cc]\\s+${VALUE_TOKEN}|--?[\\w-]+(?:=\\S+)?))*`;
|
|
17556
|
+
var COMMIT_HEAD = `git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`;
|
|
17557
|
+
var PUSH_HEAD = `git${GIT_GLOBAL_OPTS}\\s+push\\b`;
|
|
17558
|
+
var GH_PR_HEAD = `gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`;
|
|
17559
|
+
var COMMIT_RE = new RegExp(`(?:^|[\\s;&|(])${COMMIT_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${COMMIT_HEAD}`);
|
|
17560
|
+
var PUSH_RE = new RegExp(`(?:^|[\\s;&|(])${PUSH_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${PUSH_HEAD}`);
|
|
17561
|
+
var GH_PR_RE = new RegExp(`(?:^|[\\s;&|(])${GH_PR_HEAD}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${GH_PR_HEAD}`);
|
|
17562
|
+
function splitSegments(command) {
|
|
17563
|
+
return (command ?? "").split(/&&|\|\||;|\n/);
|
|
17564
|
+
}
|
|
17565
|
+
function findMomentSegment(command, on) {
|
|
17566
|
+
const segments = splitSegments(command);
|
|
17567
|
+
let commitIdx = -1;
|
|
17568
|
+
let pushIdx = -1;
|
|
17569
|
+
for (let i = 0; i < segments.length; i++) {
|
|
17570
|
+
const seg = segments[i];
|
|
17571
|
+
if (/--dry-run\b/.test(seg)) continue;
|
|
17572
|
+
if (commitIdx === -1 && COMMIT_RE.test(seg)) commitIdx = i;
|
|
17573
|
+
if (pushIdx === -1 && (PUSH_RE.test(seg) || GH_PR_RE.test(seg))) pushIdx = i;
|
|
16890
17574
|
}
|
|
16891
|
-
if (
|
|
16892
|
-
|
|
17575
|
+
if (commitIdx !== -1 && on.includes("commit")) return { moment: "pre-commit", segmentIndex: commitIdx };
|
|
17576
|
+
if (pushIdx !== -1 && on.includes("push")) return { moment: "pre-push", segmentIndex: pushIdx };
|
|
17577
|
+
return null;
|
|
17578
|
+
}
|
|
17579
|
+
function classifyCommand(command, on) {
|
|
17580
|
+
return findMomentSegment(command, on)?.moment ?? null;
|
|
17581
|
+
}
|
|
17582
|
+
function unquote(token) {
|
|
17583
|
+
if (token.length >= 2) {
|
|
17584
|
+
const first = token[0];
|
|
17585
|
+
if ((first === "'" || first === '"') && token.endsWith(first)) return token.slice(1, -1);
|
|
17586
|
+
}
|
|
17587
|
+
return token;
|
|
17588
|
+
}
|
|
17589
|
+
var SHELL_DYNAMIC = /[$`\\]/;
|
|
17590
|
+
function extractCommandTarget(command, segmentIndex, baseDir) {
|
|
17591
|
+
const segments = splitSegments(command);
|
|
17592
|
+
let dir = baseDir;
|
|
17593
|
+
let named = false;
|
|
17594
|
+
for (let i = 0; i < segmentIndex; i++) {
|
|
17595
|
+
const m = segments[i].match(new RegExp(`^\\s*cd(?:\\s+(${VALUE_TOKEN}))?\\s*$`));
|
|
17596
|
+
if (!m) continue;
|
|
17597
|
+
named = true;
|
|
17598
|
+
if (m[1] === void 0) {
|
|
17599
|
+
dir = (0, import_node_os3.homedir)();
|
|
17600
|
+
continue;
|
|
17601
|
+
}
|
|
17602
|
+
const raw = unquote(m[1]);
|
|
17603
|
+
if (SHELL_DYNAMIC.test(raw) || raw === "-") {
|
|
17604
|
+
return { dir: null, named: true, unresolvable: `cd target not statically resolvable: ${raw}` };
|
|
17605
|
+
}
|
|
17606
|
+
const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
|
|
17607
|
+
dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
|
|
17608
|
+
}
|
|
17609
|
+
const seg = segments[segmentIndex];
|
|
17610
|
+
const overrideMatch = /--(?:git-dir|work-tree)(?:=|\s)|\bGIT_(?:DIR|WORK_TREE|INDEX_FILE)=/.exec(seg);
|
|
17611
|
+
if (overrideMatch) {
|
|
17612
|
+
return { dir: null, named: true, unresolvable: `git-dir/work-tree/index override in command: ${overrideMatch[0].trim()}` };
|
|
17613
|
+
}
|
|
17614
|
+
const gitMatch = seg.match(new RegExp(`(?:^|[\\s;&|(])(?:[^\\s;&|()'"]*\\/)?git(${GIT_GLOBAL_OPTS})\\s`));
|
|
17615
|
+
if (gitMatch) {
|
|
17616
|
+
const optsRegion = gitMatch[1] ?? "";
|
|
17617
|
+
const cRe = new RegExp(`-C\\s+(${VALUE_TOKEN})`, "g");
|
|
17618
|
+
let cm;
|
|
17619
|
+
while ((cm = cRe.exec(optsRegion)) !== null) {
|
|
17620
|
+
named = true;
|
|
17621
|
+
const raw = unquote(cm[1]);
|
|
17622
|
+
if (SHELL_DYNAMIC.test(raw)) {
|
|
17623
|
+
return { dir: null, named: true, unresolvable: `-C target not statically resolvable: ${raw}` };
|
|
17624
|
+
}
|
|
17625
|
+
const expanded = raw === "~" ? (0, import_node_os3.homedir)() : raw.startsWith("~/") ? (0, import_node_path19.join)((0, import_node_os3.homedir)(), raw.slice(2)) : raw;
|
|
17626
|
+
dir = (0, import_node_path18.isAbsolute)(expanded) ? expanded : (0, import_node_path18.resolve)(dir, expanded);
|
|
17627
|
+
}
|
|
17628
|
+
}
|
|
17629
|
+
return { dir, named, unresolvable: null };
|
|
17630
|
+
}
|
|
17631
|
+
var PUSH_VALUE_FLAGS = /* @__PURE__ */ new Set(["-o", "--push-option", "--receive-pack", "--exec"]);
|
|
17632
|
+
function parsePushTarget(segment) {
|
|
17633
|
+
const none = { remote: null, srcRef: null, dstRef: null, isDelete: false };
|
|
17634
|
+
const m = PUSH_RE.exec(segment);
|
|
17635
|
+
if (!m) return none;
|
|
17636
|
+
const rest = segment.slice(m.index + m[0].length);
|
|
17637
|
+
const tokens = (rest.match(new RegExp(`'[^']*'|"[^"]*"|\\S+`, "g")) ?? []).map(unquote);
|
|
17638
|
+
let remote = null;
|
|
17639
|
+
let refspec = null;
|
|
17640
|
+
let isDelete = false;
|
|
17641
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
17642
|
+
const t = tokens[i];
|
|
17643
|
+
if (t === "-d" || t === "--delete") {
|
|
17644
|
+
isDelete = true;
|
|
17645
|
+
continue;
|
|
17646
|
+
}
|
|
17647
|
+
if (t.startsWith("--repo=")) {
|
|
17648
|
+
remote = t.slice("--repo=".length);
|
|
17649
|
+
continue;
|
|
17650
|
+
}
|
|
17651
|
+
if (t === "--repo") {
|
|
17652
|
+
remote = tokens[++i] ?? null;
|
|
17653
|
+
continue;
|
|
17654
|
+
}
|
|
17655
|
+
if (PUSH_VALUE_FLAGS.has(t)) {
|
|
17656
|
+
i++;
|
|
17657
|
+
continue;
|
|
17658
|
+
}
|
|
17659
|
+
if (t.startsWith("-")) continue;
|
|
17660
|
+
if (SHELL_DYNAMIC.test(t)) return none;
|
|
17661
|
+
if (remote === null) {
|
|
17662
|
+
remote = t;
|
|
17663
|
+
continue;
|
|
17664
|
+
}
|
|
17665
|
+
if (refspec === null) {
|
|
17666
|
+
refspec = t;
|
|
17667
|
+
continue;
|
|
17668
|
+
}
|
|
17669
|
+
break;
|
|
16893
17670
|
}
|
|
16894
|
-
if (
|
|
16895
|
-
|
|
17671
|
+
if (refspec === null) return { remote, srcRef: null, dstRef: null, isDelete };
|
|
17672
|
+
const spec = refspec.startsWith("+") ? refspec.slice(1) : refspec;
|
|
17673
|
+
const colon = spec.indexOf(":");
|
|
17674
|
+
if (colon === -1) return { remote, srcRef: spec, dstRef: null, isDelete };
|
|
17675
|
+
const src = spec.slice(0, colon);
|
|
17676
|
+
const dst = spec.slice(colon + 1);
|
|
17677
|
+
if (src === "") return { remote, srcRef: null, dstRef: dst || null, isDelete: true };
|
|
17678
|
+
return { remote, srcRef: src, dstRef: dst || null, isDelete };
|
|
17679
|
+
}
|
|
17680
|
+
function gitAt(dir, args) {
|
|
17681
|
+
try {
|
|
17682
|
+
return (0, import_node_child_process7.execFileSync)("git", args, { cwd: dir, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
17683
|
+
} catch {
|
|
17684
|
+
return "";
|
|
17685
|
+
}
|
|
17686
|
+
}
|
|
17687
|
+
function realpathOr(p) {
|
|
17688
|
+
try {
|
|
17689
|
+
return import_node_fs24.realpathSync.native(p);
|
|
17690
|
+
} catch {
|
|
17691
|
+
return (0, import_node_path18.resolve)(p);
|
|
17692
|
+
}
|
|
17693
|
+
}
|
|
17694
|
+
function resolveFrame(input) {
|
|
17695
|
+
const found = findMomentSegment(input.command, input.on);
|
|
17696
|
+
const hookDirUsable = !!input.hookCwd && (0, import_node_fs24.existsSync)(input.hookCwd);
|
|
17697
|
+
const baseDir = hookDirUsable ? input.hookCwd : process.cwd();
|
|
17698
|
+
let anchor = hookDirUsable ? "hook-cwd" : "process-cwd";
|
|
17699
|
+
const refuse = (refusal) => ({
|
|
17700
|
+
moment: found?.moment ?? null,
|
|
17701
|
+
frame: { worktreeRoot: null, gitDir: null, commonDir: null, isLinkedWorktree: false, branch: null, anchor, refusal }
|
|
17702
|
+
});
|
|
17703
|
+
let dir = baseDir;
|
|
17704
|
+
if (found) {
|
|
17705
|
+
const segments = splitSegments(input.command);
|
|
17706
|
+
const kindRes = found.moment === "pre-commit" ? [COMMIT_RE] : [PUSH_RE, GH_PR_RE];
|
|
17707
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
17708
|
+
for (let i = 0; i < segments.length; i++) {
|
|
17709
|
+
if (/--dry-run\b/.test(segments[i])) continue;
|
|
17710
|
+
if (!kindRes.some((re) => re.test(segments[i]))) continue;
|
|
17711
|
+
const target = extractCommandTarget(input.command, i, baseDir);
|
|
17712
|
+
if (target.named) anchor = "command-target";
|
|
17713
|
+
if (target.unresolvable) return refuse(`target:${target.unresolvable}`);
|
|
17714
|
+
dirs.add(target.dir ?? baseDir);
|
|
17715
|
+
}
|
|
17716
|
+
if (dirs.size > 1) return refuse(`target:multiple ${found.moment} targets in one command`);
|
|
17717
|
+
const targetDir = dirs.size === 1 ? [...dirs][0] : baseDir;
|
|
17718
|
+
if (targetDir !== baseDir) {
|
|
17719
|
+
if (!(0, import_node_fs24.existsSync)(targetDir)) return refuse(`target:directory does not exist: ${targetDir}`);
|
|
17720
|
+
dir = targetDir;
|
|
17721
|
+
}
|
|
17722
|
+
}
|
|
17723
|
+
const toplevel = gitAt(dir, ["rev-parse", "--show-toplevel"]);
|
|
17724
|
+
if (!toplevel) {
|
|
17725
|
+
return refuse(anchor === "command-target" ? `target:not a git repository: ${dir}` : `anchor:not a git repository: ${dir}`);
|
|
17726
|
+
}
|
|
17727
|
+
const gitDirRaw = gitAt(dir, ["rev-parse", "--absolute-git-dir"]);
|
|
17728
|
+
const commonRaw = gitAt(dir, ["rev-parse", "--git-common-dir"]);
|
|
17729
|
+
const gitDir = gitDirRaw ? realpathOr(gitDirRaw) : null;
|
|
17730
|
+
const commonDir = commonRaw ? realpathOr((0, import_node_path18.isAbsolute)(commonRaw) ? commonRaw : (0, import_node_path18.resolve)(dir, commonRaw)) : null;
|
|
17731
|
+
const branchRaw = gitAt(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
17732
|
+
return {
|
|
17733
|
+
moment: found?.moment ?? null,
|
|
17734
|
+
frame: {
|
|
17735
|
+
worktreeRoot: realpathOr(toplevel),
|
|
17736
|
+
gitDir,
|
|
17737
|
+
commonDir,
|
|
17738
|
+
// The one honest definition: a linked worktree's own git dir differs from
|
|
17739
|
+
// the shared one. No path convention involved — see the header.
|
|
17740
|
+
isLinkedWorktree: !!gitDir && !!commonDir && gitDir !== commonDir,
|
|
17741
|
+
branch: !branchRaw || branchRaw === "HEAD" ? null : branchRaw,
|
|
17742
|
+
anchor,
|
|
17743
|
+
refusal: null
|
|
17744
|
+
}
|
|
17745
|
+
};
|
|
17746
|
+
}
|
|
17747
|
+
function frameGit(frame, args) {
|
|
17748
|
+
if (!frame.worktreeRoot) return "";
|
|
17749
|
+
return gitAt(frame.worktreeRoot, args);
|
|
17750
|
+
}
|
|
17751
|
+
function refResolves(frame, ref) {
|
|
17752
|
+
return frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]) !== "";
|
|
17753
|
+
}
|
|
17754
|
+
var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
17755
|
+
function baselineShaAt(frame) {
|
|
17756
|
+
if (!frame.worktreeRoot) return null;
|
|
17757
|
+
try {
|
|
17758
|
+
const sha = (0, import_node_fs24.readFileSync)((0, import_node_path19.join)(frame.worktreeRoot, BASELINE_SHA_FILE), "utf-8").trim();
|
|
17759
|
+
if (!SHA_RE2.test(sha)) return null;
|
|
17760
|
+
return refResolves(frame, sha) ? sha : null;
|
|
17761
|
+
} catch {
|
|
17762
|
+
return null;
|
|
17763
|
+
}
|
|
17764
|
+
}
|
|
17765
|
+
function stagedRange() {
|
|
17766
|
+
return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
|
|
17767
|
+
}
|
|
17768
|
+
function resolvePushRange(frame, command, on) {
|
|
17769
|
+
const nothing = (via) => ({ kind: "nothing", base: null, head: "HEAD", via });
|
|
17770
|
+
if (!frame.worktreeRoot) return nothing("refused");
|
|
17771
|
+
const found = findMomentSegment(command, on);
|
|
17772
|
+
const segment = found ? splitSegments(command)[found.segmentIndex] : "";
|
|
17773
|
+
const target = parsePushTarget(segment);
|
|
17774
|
+
if (target.isDelete) return nothing("deletion");
|
|
17775
|
+
const head = target.srcRef ?? "HEAD";
|
|
17776
|
+
if (!refResolves(frame, head)) return nothing(`src-unresolvable:${head}`);
|
|
17777
|
+
const srcName = target.srcRef;
|
|
17778
|
+
const branchForRemote = srcName ?? frame.branch;
|
|
17779
|
+
const candidates = [];
|
|
17780
|
+
if (target.remote && (target.dstRef ?? srcName)) {
|
|
17781
|
+
const dstName = (target.dstRef ?? srcName).replace(/^refs\/heads\//, "");
|
|
17782
|
+
candidates.push({ ref: `refs/remotes/${target.remote}/${dstName}`, via: `refspec:${target.remote}/${dstName}` });
|
|
17783
|
+
}
|
|
17784
|
+
if (target.remote && !srcName && !target.dstRef && frame.branch) {
|
|
17785
|
+
candidates.push({ ref: `refs/remotes/${target.remote}/${frame.branch}`, via: `remote:${target.remote}/${frame.branch}` });
|
|
17786
|
+
}
|
|
17787
|
+
candidates.push({ ref: srcName ? `${srcName}@{push}` : "@{push}", via: "@{push}" });
|
|
17788
|
+
candidates.push({ ref: srcName ? `${srcName}@{upstream}` : "@{upstream}", via: "@{upstream}" });
|
|
17789
|
+
if (branchForRemote) {
|
|
17790
|
+
candidates.push({
|
|
17791
|
+
ref: `refs/remotes/origin/${branchForRemote.replace(/^refs\/heads\//, "")}`,
|
|
17792
|
+
via: `origin/${branchForRemote.replace(/^refs\/heads\//, "")}`
|
|
17793
|
+
});
|
|
17794
|
+
}
|
|
17795
|
+
for (const c of candidates) {
|
|
17796
|
+
if (!refResolves(frame, c.ref)) continue;
|
|
17797
|
+
const mergeBase = frameGit(frame, ["merge-base", c.ref, head]);
|
|
17798
|
+
if (SHA_RE2.test(mergeBase)) return { kind: "push", base: mergeBase, head, via: c.via };
|
|
17799
|
+
}
|
|
17800
|
+
const baseline = baselineShaAt(frame);
|
|
17801
|
+
if (baseline) return { kind: "baseline", base: baseline, head, via: "review-baseline" };
|
|
17802
|
+
if (refResolves(frame, `${head}~1`)) return { kind: "last-commit", base: `${head}~1`, head, via: `${head}~1` };
|
|
17803
|
+
return nothing("no-parent");
|
|
17804
|
+
}
|
|
17805
|
+
function rangeFiles(frame, range) {
|
|
17806
|
+
let out;
|
|
17807
|
+
switch (range.kind) {
|
|
17808
|
+
case "staged":
|
|
17809
|
+
out = frameGit(frame, ["diff", "--cached", "--name-only"]);
|
|
17810
|
+
break;
|
|
17811
|
+
case "push":
|
|
17812
|
+
case "baseline":
|
|
17813
|
+
case "last-commit":
|
|
17814
|
+
out = frameGit(frame, ["diff", "--name-only", range.base, range.head === "INDEX" ? "HEAD" : range.head]);
|
|
17815
|
+
break;
|
|
17816
|
+
case "nothing":
|
|
17817
|
+
return [];
|
|
17818
|
+
}
|
|
17819
|
+
return out.split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
|
|
17820
|
+
}
|
|
17821
|
+
function rangeMessages(frame, range) {
|
|
17822
|
+
if (range.kind === "staged" || range.kind === "nothing" || !range.base) return "";
|
|
17823
|
+
return frameGit(frame, ["log", `${range.base}..${range.head === "INDEX" ? "HEAD" : range.head}`, "--format=%B%x00"]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
17824
|
+
}
|
|
17825
|
+
function frameTelemetry(frame, range, divergence) {
|
|
17826
|
+
const t = {
|
|
17827
|
+
anchor: frame.anchor,
|
|
17828
|
+
linked_worktree: frame.isLinkedWorktree,
|
|
17829
|
+
range_via: range?.via ?? null,
|
|
17830
|
+
refusal: frame.refusal
|
|
17831
|
+
};
|
|
17832
|
+
if (divergence) {
|
|
17833
|
+
t.root_differs = !!frame.worktreeRoot && !!divergence.actualRoot && realpathOr(frame.worktreeRoot) !== realpathOr(divergence.actualRoot);
|
|
17834
|
+
const a = [...divergence.actualFiles].sort().join("\n");
|
|
17835
|
+
const b = [...divergence.frameFiles].sort().join("\n");
|
|
17836
|
+
t.files_differ = a !== b;
|
|
17837
|
+
t.frame_file_count = divergence.frameFiles.length;
|
|
17838
|
+
t.actual_file_count = divergence.actualFiles.length;
|
|
17839
|
+
}
|
|
17840
|
+
return t;
|
|
17841
|
+
}
|
|
17842
|
+
|
|
17843
|
+
// src/lib/reachability.ts
|
|
17844
|
+
function resolveReachability(input = {}) {
|
|
17845
|
+
const env = input.env ?? {};
|
|
17846
|
+
if (input.autonomousFlag === true) {
|
|
17847
|
+
return { human_reachable: "no", human_reachable_source: "declared", rung: "flag" };
|
|
17848
|
+
}
|
|
17849
|
+
if (truthy(env.VERITY_AUTONOMOUS)) {
|
|
17850
|
+
return { human_reachable: "no", human_reachable_source: "declared", rung: "env" };
|
|
17851
|
+
}
|
|
17852
|
+
if (truthy(env.CI) || truthy(env.GITHUB_ACTIONS) || truthy(env.BUILDKITE) || truthy(env.JENKINS_URL)) {
|
|
17853
|
+
return { human_reachable: "no", human_reachable_source: "declared", rung: "env" };
|
|
16896
17854
|
}
|
|
16897
17855
|
if (truthy(env.VERITY_INTERACTIVE)) {
|
|
16898
17856
|
return { human_reachable: "yes", human_reachable_source: "declared", rung: "env" };
|
|
@@ -16910,7 +17868,7 @@ function truthy(v) {
|
|
|
16910
17868
|
}
|
|
16911
17869
|
|
|
16912
17870
|
// src/lib/transcript.ts
|
|
16913
|
-
var
|
|
17871
|
+
var import_node_fs25 = require("node:fs");
|
|
16914
17872
|
var MAX_READ_BYTES = 256 * 1024;
|
|
16915
17873
|
var SMALL_FILE_BYTES = 64 * 1024;
|
|
16916
17874
|
var MAX_FILES_LIST = 20;
|
|
@@ -16920,6 +17878,8 @@ var MAX_COMMAND_CHARS = 80;
|
|
|
16920
17878
|
var MAX_TOOL_BLOCKS = 200;
|
|
16921
17879
|
var MAX_SUMMARY_BYTES = 4096;
|
|
16922
17880
|
var HOME = process.env.HOME ?? "";
|
|
17881
|
+
var BASH_INPUT_RE = /^\s*<bash-input>([\s\S]*?)<\/bash-input>/;
|
|
17882
|
+
var BASH_ECHO_RE = /^\s*<bash-(?:stdout|stderr)>/;
|
|
16923
17883
|
async function extractActionSummary(transcriptPath) {
|
|
16924
17884
|
try {
|
|
16925
17885
|
const read = readTurnLines(transcriptPath);
|
|
@@ -16934,7 +17894,7 @@ async function extractActionSummary(transcriptPath) {
|
|
|
16934
17894
|
function readTurnLines(transcriptPath) {
|
|
16935
17895
|
let size;
|
|
16936
17896
|
try {
|
|
16937
|
-
size = (0,
|
|
17897
|
+
size = (0, import_node_fs25.statSync)(transcriptPath).size;
|
|
16938
17898
|
} catch {
|
|
16939
17899
|
return null;
|
|
16940
17900
|
}
|
|
@@ -16942,7 +17902,7 @@ function readTurnLines(transcriptPath) {
|
|
|
16942
17902
|
let raw;
|
|
16943
17903
|
let windowed = false;
|
|
16944
17904
|
if (size <= SMALL_FILE_BYTES) {
|
|
16945
|
-
raw = (0,
|
|
17905
|
+
raw = (0, import_node_fs25.readFileSync)(transcriptPath, "utf-8");
|
|
16946
17906
|
} else {
|
|
16947
17907
|
windowed = true;
|
|
16948
17908
|
const buf = Buffer.alloc(Math.min(MAX_READ_BYTES, size));
|
|
@@ -16983,7 +17943,7 @@ function isRealUserMessage(parsed) {
|
|
|
16983
17943
|
const message = parsed.message;
|
|
16984
17944
|
if (!message) return false;
|
|
16985
17945
|
const content = message.content;
|
|
16986
|
-
if (typeof content === "string") return
|
|
17946
|
+
if (typeof content === "string") return !BASH_ECHO_RE.test(content);
|
|
16987
17947
|
if (Array.isArray(content)) {
|
|
16988
17948
|
return content.some((b) => {
|
|
16989
17949
|
if (typeof b !== "object" || b === null) return false;
|
|
@@ -16998,6 +17958,9 @@ function buildSummary(lines) {
|
|
|
16998
17958
|
const filesEdited = /* @__PURE__ */ new Set();
|
|
16999
17959
|
const filesCreated = /* @__PURE__ */ new Set();
|
|
17000
17960
|
const commands = [];
|
|
17961
|
+
const userCommands = [];
|
|
17962
|
+
let userCommandsTruncated = false;
|
|
17963
|
+
let commandsTruncated = false;
|
|
17001
17964
|
let searches = 0;
|
|
17002
17965
|
let subagents = 0;
|
|
17003
17966
|
let webFetches = 0;
|
|
@@ -17015,6 +17978,17 @@ function buildSummary(lines) {
|
|
|
17015
17978
|
if (entry.type === "user" && !firstTimestamp) {
|
|
17016
17979
|
firstTimestamp = entry.timestamp ?? null;
|
|
17017
17980
|
}
|
|
17981
|
+
if (entry.type === "user") {
|
|
17982
|
+
const typed = userTypedCommands(entry);
|
|
17983
|
+
if (typed.truncated) userCommandsTruncated = true;
|
|
17984
|
+
for (const cmd of typed.commands) {
|
|
17985
|
+
if (userCommands.length >= MAX_COMMANDS) {
|
|
17986
|
+
userCommandsTruncated = true;
|
|
17987
|
+
break;
|
|
17988
|
+
}
|
|
17989
|
+
userCommands.push(cmd);
|
|
17990
|
+
}
|
|
17991
|
+
}
|
|
17018
17992
|
if (entry.type !== "assistant") continue;
|
|
17019
17993
|
turnMessages++;
|
|
17020
17994
|
lastTimestamp = entry.timestamp ?? lastTimestamp;
|
|
@@ -17027,7 +18001,10 @@ function buildSummary(lines) {
|
|
|
17027
18001
|
totalToolCalls++;
|
|
17028
18002
|
const toolName = block.name ?? "unknown";
|
|
17029
18003
|
toolCounts[toolName] = (toolCounts[toolName] ?? 0) + 1;
|
|
17030
|
-
if (totalToolCalls > MAX_TOOL_BLOCKS)
|
|
18004
|
+
if (totalToolCalls > MAX_TOOL_BLOCKS) {
|
|
18005
|
+
if (toolName === "Bash") commandsTruncated = true;
|
|
18006
|
+
continue;
|
|
18007
|
+
}
|
|
17031
18008
|
const input = block.input ?? {};
|
|
17032
18009
|
switch (toolName) {
|
|
17033
18010
|
case "Read":
|
|
@@ -17047,9 +18024,11 @@ function buildSummary(lines) {
|
|
|
17047
18024
|
addPath(filesEdited, input.file_path);
|
|
17048
18025
|
break;
|
|
17049
18026
|
case "Bash": {
|
|
17050
|
-
const cmd =
|
|
17051
|
-
if (
|
|
17052
|
-
|
|
18027
|
+
const { cmd, lost } = sanitizeCommandWithLoss(input.command);
|
|
18028
|
+
if (lost) commandsTruncated = true;
|
|
18029
|
+
if (cmd) {
|
|
18030
|
+
if (commands.length < MAX_COMMANDS) commands.push(cmd);
|
|
18031
|
+
else commandsTruncated = true;
|
|
17053
18032
|
}
|
|
17054
18033
|
break;
|
|
17055
18034
|
}
|
|
@@ -17092,6 +18071,9 @@ function buildSummary(lines) {
|
|
|
17092
18071
|
],
|
|
17093
18072
|
searches,
|
|
17094
18073
|
commands,
|
|
18074
|
+
...commandsTruncated ? { commands_truncated: true } : {},
|
|
18075
|
+
user_commands: userCommands,
|
|
18076
|
+
...userCommandsTruncated ? { user_commands_truncated: true } : {},
|
|
17095
18077
|
subagents,
|
|
17096
18078
|
web_fetches: webFetches,
|
|
17097
18079
|
total_tool_calls: totalToolCalls,
|
|
@@ -17100,6 +18082,9 @@ function buildSummary(lines) {
|
|
|
17100
18082
|
};
|
|
17101
18083
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
17102
18084
|
summary.commands = [];
|
|
18085
|
+
summary.commands_truncated = true;
|
|
18086
|
+
summary.user_commands = [];
|
|
18087
|
+
summary.user_commands_truncated = true;
|
|
17103
18088
|
if (JSON.stringify(summary).length > MAX_SUMMARY_BYTES) {
|
|
17104
18089
|
summary.files_read = summary.files_read.slice(0, 10);
|
|
17105
18090
|
summary.files_edited = summary.files_edited.slice(0, 10);
|
|
@@ -17117,12 +18102,38 @@ function addPath(set, rawPath) {
|
|
|
17117
18102
|
if (p.length > 200) p = p.slice(0, 200);
|
|
17118
18103
|
set.add(p);
|
|
17119
18104
|
}
|
|
18105
|
+
function userTypedCommands(entry) {
|
|
18106
|
+
const none = { commands: [], truncated: false };
|
|
18107
|
+
const message = entry.message;
|
|
18108
|
+
const content = message?.content;
|
|
18109
|
+
if (typeof content !== "string") return none;
|
|
18110
|
+
const m = BASH_INPUT_RE.exec(content);
|
|
18111
|
+
if (!m) return none;
|
|
18112
|
+
const commands = [];
|
|
18113
|
+
let truncated = false;
|
|
18114
|
+
for (const line of m[1].split("\n")) {
|
|
18115
|
+
if (line.length > MAX_COMMAND_CHARS) truncated = true;
|
|
18116
|
+
const cmd = sanitizeCommand(line);
|
|
18117
|
+
if (cmd) commands.push(cmd);
|
|
18118
|
+
}
|
|
18119
|
+
return { commands, truncated };
|
|
18120
|
+
}
|
|
18121
|
+
function sanitizeCommandWithLoss(rawCmd) {
|
|
18122
|
+
if (typeof rawCmd !== "string" || !rawCmd) return { cmd: null, lost: false };
|
|
18123
|
+
const lines = rawCmd.split("\n");
|
|
18124
|
+
const lost = lines.slice(1).some((l) => l.trim().length > 0);
|
|
18125
|
+
const first = lines[0];
|
|
18126
|
+
const cmd = sanitizeCommand(first);
|
|
18127
|
+
const hadSeparator = SEPARATORS.some((sep2) => first.indexOf(sep2) > 0);
|
|
18128
|
+
return { cmd, lost: lost || !hadSeparator && first.length > MAX_COMMAND_CHARS };
|
|
18129
|
+
}
|
|
18130
|
+
var SEPARATORS = [" | ", " > ", " >> ", " 2>", " && ", " ; "];
|
|
17120
18131
|
function sanitizeCommand(rawCmd) {
|
|
17121
18132
|
if (typeof rawCmd !== "string" || !rawCmd) return null;
|
|
17122
18133
|
let cmd = rawCmd.split("\n")[0];
|
|
17123
18134
|
let cut = -1;
|
|
17124
18135
|
let marker = "";
|
|
17125
|
-
for (const sep2 of
|
|
18136
|
+
for (const sep2 of SEPARATORS) {
|
|
17126
18137
|
const idx = cmd.indexOf(sep2);
|
|
17127
18138
|
if (idx > 0 && (cut === -1 || idx < cut)) {
|
|
17128
18139
|
cut = idx;
|
|
@@ -17150,28 +18161,28 @@ async function readStopHookStdin() {
|
|
|
17150
18161
|
try {
|
|
17151
18162
|
if (process.stdin.isTTY) return empty;
|
|
17152
18163
|
const chunks = [];
|
|
17153
|
-
const timeout = new Promise((
|
|
17154
|
-
const read = new Promise((
|
|
18164
|
+
const timeout = new Promise((resolve4) => setTimeout(() => resolve4(empty), 500));
|
|
18165
|
+
const read = new Promise((resolve4) => {
|
|
17155
18166
|
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
17156
18167
|
process.stdin.on("end", () => {
|
|
17157
18168
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
17158
18169
|
if (!raw) {
|
|
17159
|
-
|
|
18170
|
+
resolve4(empty);
|
|
17160
18171
|
return;
|
|
17161
18172
|
}
|
|
17162
18173
|
try {
|
|
17163
18174
|
const data = JSON.parse(raw);
|
|
17164
|
-
|
|
18175
|
+
resolve4({
|
|
17165
18176
|
assistantMessage: typeof data.last_assistant_message === "string" ? data.last_assistant_message : null,
|
|
17166
18177
|
stopReason: typeof data.stop_reason === "string" ? data.stop_reason : null,
|
|
17167
18178
|
transcriptPath: typeof data.transcript_path === "string" ? data.transcript_path : null,
|
|
17168
18179
|
sessionId: typeof data.session_id === "string" ? data.session_id : null
|
|
17169
18180
|
});
|
|
17170
18181
|
} catch {
|
|
17171
|
-
|
|
18182
|
+
resolve4(empty);
|
|
17172
18183
|
}
|
|
17173
18184
|
});
|
|
17174
|
-
process.stdin.on("error", () =>
|
|
18185
|
+
process.stdin.on("error", () => resolve4(empty));
|
|
17175
18186
|
process.stdin.resume();
|
|
17176
18187
|
});
|
|
17177
18188
|
return await Promise.race([read, timeout]);
|
|
@@ -17185,6 +18196,7 @@ async function bootstrap(run) {
|
|
|
17185
18196
|
process.chdir(repoRoot());
|
|
17186
18197
|
} catch {
|
|
17187
18198
|
}
|
|
18199
|
+
run.treeFrame = resolveFrame({ command: "", on: [], hookCwd: null }).frame;
|
|
17188
18200
|
const turnId = `t-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
17189
18201
|
let reachability = resolveReachability({
|
|
17190
18202
|
autonomousFlag: process.env.VERITY_AUTONOMOUS === "1" || opts.mode === "autonomous",
|
|
@@ -17350,7 +18362,7 @@ function truncateToCap(text) {
|
|
|
17350
18362
|
function buildHookOutput(gateDecision, systemMessage, agentContext) {
|
|
17351
18363
|
return {
|
|
17352
18364
|
gate_decision: gateDecision,
|
|
17353
|
-
systemMessage,
|
|
18365
|
+
...systemMessage === null ? {} : { systemMessage },
|
|
17354
18366
|
...agentContext ? {
|
|
17355
18367
|
hookSpecificOutput: {
|
|
17356
18368
|
hookEventName: "Stop",
|
|
@@ -17371,7 +18383,7 @@ function channelSilence(input) {
|
|
|
17371
18383
|
// src/lib/cli-version.ts
|
|
17372
18384
|
function cliVersion() {
|
|
17373
18385
|
try {
|
|
17374
|
-
return true ? "0.30.
|
|
18386
|
+
return true ? "0.30.1-experimental.cbeb697" : "dev";
|
|
17375
18387
|
} catch {
|
|
17376
18388
|
return "dev";
|
|
17377
18389
|
}
|
|
@@ -17411,8 +18423,8 @@ async function sendSkipBeacon(ctx, reason) {
|
|
|
17411
18423
|
}
|
|
17412
18424
|
|
|
17413
18425
|
// src/lib/static-analysis.ts
|
|
17414
|
-
var
|
|
17415
|
-
var
|
|
18426
|
+
var import_node_child_process8 = require("node:child_process");
|
|
18427
|
+
var import_node_fs26 = require("node:fs");
|
|
17416
18428
|
var SEVERITY_ORDER = {
|
|
17417
18429
|
Error: 0,
|
|
17418
18430
|
Critical: 0,
|
|
@@ -17424,7 +18436,7 @@ var SEVERITY_ORDER = {
|
|
|
17424
18436
|
};
|
|
17425
18437
|
function isCodacyAvailable() {
|
|
17426
18438
|
try {
|
|
17427
|
-
(0,
|
|
18439
|
+
(0, import_node_child_process8.execSync)("which codacy-analysis", { stdio: "pipe" });
|
|
17428
18440
|
return true;
|
|
17429
18441
|
} catch {
|
|
17430
18442
|
return false;
|
|
@@ -17460,13 +18472,13 @@ function runCodacyAnalysis(files) {
|
|
|
17460
18472
|
if (files.length === 0) return empty;
|
|
17461
18473
|
const existingFiles = files.filter((f) => {
|
|
17462
18474
|
try {
|
|
17463
|
-
return (0,
|
|
18475
|
+
return (0, import_node_fs26.existsSync)(f);
|
|
17464
18476
|
} catch {
|
|
17465
18477
|
return false;
|
|
17466
18478
|
}
|
|
17467
18479
|
});
|
|
17468
18480
|
if (existingFiles.length === 0) return empty;
|
|
17469
|
-
const proc = (0,
|
|
18481
|
+
const proc = (0, import_node_child_process8.spawnSync)("codacy-analysis", buildAnalyzerArgv(existingFiles), {
|
|
17470
18482
|
encoding: "utf-8",
|
|
17471
18483
|
maxBuffer: 10 * 1024 * 1024
|
|
17472
18484
|
});
|
|
@@ -17657,6 +18669,8 @@ async function passAndExit(run, reason, skip, kindOverride) {
|
|
|
17657
18669
|
"verity-command",
|
|
17658
18670
|
"bare-acknowledgment",
|
|
17659
18671
|
"reflection-prompt",
|
|
18672
|
+
"command-only-turn",
|
|
18673
|
+
"declared-ignore",
|
|
17660
18674
|
"skip-mode",
|
|
17661
18675
|
"zero-increment",
|
|
17662
18676
|
"debounce",
|
|
@@ -17675,10 +18689,12 @@ async function passAndExit(run, reason, skip, kindOverride) {
|
|
|
17675
18689
|
}
|
|
17676
18690
|
const AGENT_SILENT_SKIPS = /* @__PURE__ */ new Set([]);
|
|
17677
18691
|
const agentNote = AGENT_SILENT_SKIPS.has(skip) ? null : note;
|
|
18692
|
+
const HUMAN_SILENT_SKIPS = /* @__PURE__ */ new Set(["command-only-turn"]);
|
|
18693
|
+
const humanNote = HUMAN_SILENT_SKIPS.has(skip) ? null : `Verity: ${reason}`;
|
|
17678
18694
|
printJsonCompact(
|
|
17679
18695
|
buildHookOutput(
|
|
17680
18696
|
verdict,
|
|
17681
|
-
|
|
18697
|
+
humanNote,
|
|
17682
18698
|
// `additionalContext` is the agent's ONLY input. Writing only
|
|
17683
18699
|
// `systemMessage` — the human's field — tells the agent nothing at all,
|
|
17684
18700
|
// which is what sixteen of the nineteen terminating paths used to do.
|
|
@@ -17694,20 +18710,38 @@ async function scope(run) {
|
|
|
17694
18710
|
const { files: allChanged, hasRecentCommitFiles } = getChangedFiles();
|
|
17695
18711
|
run.changedUniverse = allChanged;
|
|
17696
18712
|
const { kept: external } = partitionVerityOwned(allChanged);
|
|
17697
|
-
const
|
|
17698
|
-
const
|
|
17699
|
-
const
|
|
18713
|
+
const verityIgnore = loadVerityIgnore();
|
|
18714
|
+
const ignored = partitionIgnored(external, verityIgnore);
|
|
18715
|
+
for (const problem of verityIgnore.problems) {
|
|
18716
|
+
process.stderr.write(`Verity: .verityignore ${problem}
|
|
18717
|
+
`);
|
|
18718
|
+
}
|
|
18719
|
+
if (ignored.securityExcluded.length > 0) {
|
|
18720
|
+
process.stderr.write(
|
|
18721
|
+
`Verity: .verityignore excluded ${ignored.securityExcluded.length} security-sensitive file(s) from review this run: ${ignored.securityExcluded.slice(0, 5).join(", ")}. Add a \`!\` rule to keep them in scope.
|
|
18722
|
+
`
|
|
18723
|
+
);
|
|
18724
|
+
}
|
|
18725
|
+
if (ignored.suspended) {
|
|
18726
|
+
process.stderr.write(
|
|
18727
|
+
"Verity: .verityignore changed this turn \u2014 its rules are suspended for this run, so nothing is excluded by them. They take effect once this turn has been reviewed.\n"
|
|
18728
|
+
);
|
|
18729
|
+
}
|
|
18730
|
+
const inScope = ignored.kept;
|
|
18731
|
+
const analyzable = filterAnalyzable(inScope);
|
|
18732
|
+
const reviewable = filterReviewable(inScope);
|
|
18733
|
+
const securityFiles = filterSecurity(inScope);
|
|
17700
18734
|
const noFilesChanged = analyzable.length === 0 && reviewable.length === 0 && securityFiles.length === 0;
|
|
17701
18735
|
if (noFilesChanged && !assistantResponse) {
|
|
17702
18736
|
await passAndExit(run, "No analyzable files changed", "no-analyzable-files");
|
|
17703
18737
|
}
|
|
17704
18738
|
const allForReview = Array.from(/* @__PURE__ */ new Set([...analyzable, ...reviewable]));
|
|
17705
|
-
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles });
|
|
18739
|
+
Object.assign(run, { allChanged, allForReview, analyzable, hasRecentCommitFiles, noFilesChanged, reviewable, securityFiles, verityIgnored: ignored });
|
|
17706
18740
|
}
|
|
17707
18741
|
|
|
17708
18742
|
// src/lib/specs.ts
|
|
17709
|
-
var
|
|
17710
|
-
var
|
|
18743
|
+
var import_node_fs27 = require("node:fs");
|
|
18744
|
+
var import_node_path20 = require("node:path");
|
|
17711
18745
|
var SPEC_CANDIDATES = [
|
|
17712
18746
|
"CLAUDE.md",
|
|
17713
18747
|
"AGENTS.md",
|
|
@@ -17738,16 +18772,16 @@ function discoverSpecs(consulted = []) {
|
|
|
17738
18772
|
const totalCap = relevant ? MAX_TOTAL_SPEC_BYTES : UNCONSULTED_TOTAL_BYTES;
|
|
17739
18773
|
if (totalBytes >= totalCap) return false;
|
|
17740
18774
|
if (seen.has(specPath)) return true;
|
|
17741
|
-
if (!(0,
|
|
18775
|
+
if (!(0, import_node_fs27.existsSync)(specPath)) return true;
|
|
17742
18776
|
seen.add(specPath);
|
|
17743
18777
|
const remaining = totalCap - totalBytes;
|
|
17744
18778
|
const fileCap = relevant ? MAX_SPEC_FILE_BYTES : UNCONSULTED_FILE_BYTES;
|
|
17745
18779
|
const readBytes = Math.min(fileCap, remaining);
|
|
17746
18780
|
try {
|
|
17747
18781
|
const buf = Buffer.alloc(readBytes);
|
|
17748
|
-
const fd = (0,
|
|
17749
|
-
const bytesRead = (0,
|
|
17750
|
-
(0,
|
|
18782
|
+
const fd = (0, import_node_fs27.openSync)(specPath, "r");
|
|
18783
|
+
const bytesRead = (0, import_node_fs27.readSync)(fd, buf, 0, readBytes, 0);
|
|
18784
|
+
(0, import_node_fs27.closeSync)(fd);
|
|
17751
18785
|
const content = buf.slice(0, bytesRead).toString("utf-8");
|
|
17752
18786
|
if (!content) return true;
|
|
17753
18787
|
result.push({ path: specPath, content });
|
|
@@ -17763,7 +18797,7 @@ function discoverSpecs(consulted = []) {
|
|
|
17763
18797
|
if (!addSpec(candidate)) break;
|
|
17764
18798
|
}
|
|
17765
18799
|
for (const dir of ["spec", "docs"]) {
|
|
17766
|
-
if (!(0,
|
|
18800
|
+
if (!(0, import_node_fs27.existsSync)(dir)) continue;
|
|
17767
18801
|
try {
|
|
17768
18802
|
const mdFiles = findMdFiles(dir, 2).sort();
|
|
17769
18803
|
for (const mdFile of mdFiles) {
|
|
@@ -17778,9 +18812,9 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
17778
18812
|
if (depth >= maxDepth) return [];
|
|
17779
18813
|
const result = [];
|
|
17780
18814
|
try {
|
|
17781
|
-
const entries = (0,
|
|
18815
|
+
const entries = (0, import_node_fs27.readdirSync)(dir, { withFileTypes: true });
|
|
17782
18816
|
for (const entry of entries) {
|
|
17783
|
-
const fullPath = (0,
|
|
18817
|
+
const fullPath = (0, import_node_path20.join)(dir, entry.name);
|
|
17784
18818
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
17785
18819
|
result.push(fullPath);
|
|
17786
18820
|
} else if (entry.isDirectory() && depth < maxDepth - 1) {
|
|
@@ -17792,19 +18826,19 @@ function findMdFiles(dir, maxDepth, depth = 0) {
|
|
|
17792
18826
|
return result;
|
|
17793
18827
|
}
|
|
17794
18828
|
function discoverPlans() {
|
|
17795
|
-
const homePlansDir = (0,
|
|
18829
|
+
const homePlansDir = (0, import_node_path20.join)(process.env.HOME ?? "", ".claude", "plans");
|
|
17796
18830
|
const localPlansDir = ".claude/plans";
|
|
17797
18831
|
const candidates = [];
|
|
17798
18832
|
const seen = /* @__PURE__ */ new Set();
|
|
17799
18833
|
for (const plansDir of [localPlansDir, homePlansDir]) {
|
|
17800
|
-
if (!(0,
|
|
18834
|
+
if (!(0, import_node_fs27.existsSync)(plansDir)) continue;
|
|
17801
18835
|
try {
|
|
17802
|
-
for (const f of (0,
|
|
18836
|
+
for (const f of (0, import_node_fs27.readdirSync)(plansDir)) {
|
|
17803
18837
|
if (!f.endsWith(".md") || seen.has(f)) continue;
|
|
17804
18838
|
seen.add(f);
|
|
17805
|
-
const fullPath = (0,
|
|
18839
|
+
const fullPath = (0, import_node_path20.join)(plansDir, f);
|
|
17806
18840
|
try {
|
|
17807
|
-
const stat3 = (0,
|
|
18841
|
+
const stat3 = (0, import_node_fs27.statSync)(fullPath);
|
|
17808
18842
|
candidates.push({ name: f, path: fullPath, mtime: stat3.mtimeMs, size: stat3.size });
|
|
17809
18843
|
} catch {
|
|
17810
18844
|
}
|
|
@@ -17817,7 +18851,7 @@ function discoverPlans() {
|
|
|
17817
18851
|
for (const entry of candidates.slice(0, MAX_PLAN_FILES)) {
|
|
17818
18852
|
if (entry.size > MAX_PLAN_FILE_BYTES) continue;
|
|
17819
18853
|
try {
|
|
17820
|
-
const content = (0,
|
|
18854
|
+
const content = (0, import_node_fs27.readFileSync)(entry.path, "utf-8");
|
|
17821
18855
|
result.push({ name: entry.name, content });
|
|
17822
18856
|
} catch {
|
|
17823
18857
|
}
|
|
@@ -17828,6 +18862,59 @@ function discoverPlans() {
|
|
|
17828
18862
|
// src/commands/analyze/phases/03-intent-inputs.ts
|
|
17829
18863
|
async function intentInputs(run) {
|
|
17830
18864
|
const { actionSummary, allForReview, assistantResponse, baseline, baselineSessionId } = run;
|
|
18865
|
+
if (isCommandOnlyTurn({
|
|
18866
|
+
userCommands: actionSummary?.user_commands,
|
|
18867
|
+
userCommandsTruncated: actionSummary?.user_commands_truncated,
|
|
18868
|
+
agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
|
|
18869
|
+
agentToolCalls: actionSummary?.total_tool_calls ?? 0,
|
|
18870
|
+
authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
|
|
18871
|
+
})) {
|
|
18872
|
+
await passAndExit(run, "User command only \u2014 skipping analysis", "command-only-turn");
|
|
18873
|
+
}
|
|
18874
|
+
{
|
|
18875
|
+
const ignoreKeys = ignoreStateKeys(
|
|
18876
|
+
run.tokenResult.ok ? run.tokenResult.data.token : void 0,
|
|
18877
|
+
null
|
|
18878
|
+
);
|
|
18879
|
+
const found = resolveIgnoreState([baselineSessionId, ...ignoreKeys]);
|
|
18880
|
+
const declaration = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
18881
|
+
if (declaration) {
|
|
18882
|
+
const commandRecordTruncated = actionSummary?.commands_truncated === true || actionSummary?.user_commands_truncated === true || // The derived signal, and the only one an older client's summary can
|
|
18883
|
+
// give: `tool_counts` keeps counting Bash calls past every cap that
|
|
18884
|
+
// stops the list growing, so a mismatch IS a loss. See
|
|
18885
|
+
// `ActionSummary.commands_truncated`.
|
|
18886
|
+
(actionSummary?.tool_counts?.Bash ?? 0) > (actionSummary?.commands?.length ?? 0);
|
|
18887
|
+
const outcome = verifyDeclaration({
|
|
18888
|
+
declaration,
|
|
18889
|
+
agentAuthoredFiles: (actionSummary?.files_edited.length ?? 0) + (actionSummary?.files_created.length ?? 0),
|
|
18890
|
+
subagents: actionSummary?.subagents ?? 0,
|
|
18891
|
+
agentCommands: actionSummary?.commands,
|
|
18892
|
+
userCommands: actionSummary?.user_commands,
|
|
18893
|
+
commandRecordTruncated,
|
|
18894
|
+
authorshipIsObservable: !!actionSummary && actionSummary.transcript_windowed !== "orphaned"
|
|
18895
|
+
});
|
|
18896
|
+
if (outcome.honoured) {
|
|
18897
|
+
if (declaration.scope === "turn" && found) clearActiveDeclaration(found.key);
|
|
18898
|
+
logEvent("ignore_honoured", { scope: declaration.scope, origin: declaration.origin });
|
|
18899
|
+
await passAndExit(
|
|
18900
|
+
run,
|
|
18901
|
+
`skipping this turn \u2014 declared housekeeping ("${declaration.reason}")`,
|
|
18902
|
+
"declared-ignore"
|
|
18903
|
+
);
|
|
18904
|
+
} else if (outcome.void) {
|
|
18905
|
+
if (found) clearActiveDeclaration(found.key);
|
|
18906
|
+
logEvent("ignore_voided", {
|
|
18907
|
+
scope: declaration.scope,
|
|
18908
|
+
origin: declaration.origin,
|
|
18909
|
+
why: outcome.why
|
|
18910
|
+
});
|
|
18911
|
+
const notice = `Verity: the ignore declared for this window ("${declaration.reason}") was voided \u2014 ${outcome.why}. Reviewing normally.`;
|
|
18912
|
+
process.stderr.write(`${notice}
|
|
18913
|
+
`);
|
|
18914
|
+
run.voidedIgnoreNotice = notice;
|
|
18915
|
+
}
|
|
18916
|
+
}
|
|
18917
|
+
}
|
|
17831
18918
|
const conversation = await readAndClearConversationBuffer(baselineSessionId);
|
|
17832
18919
|
const specs = discoverSpecs(actionSummary?.files_read ?? []);
|
|
17833
18920
|
const plans = discoverPlans();
|
|
@@ -17868,156 +18955,6 @@ async function connect(run) {
|
|
|
17868
18955
|
Object.assign(run, { urlResult, serviceUrl: urlResult.data, token: tokenResult.data.token });
|
|
17869
18956
|
}
|
|
17870
18957
|
|
|
17871
|
-
// src/lib/analysis-mode.ts
|
|
17872
|
-
var DEBUG_PHRASES = [
|
|
17873
|
-
"not working",
|
|
17874
|
-
"doesn't work",
|
|
17875
|
-
"doesn't work",
|
|
17876
|
-
"does not work",
|
|
17877
|
-
"isn't working",
|
|
17878
|
-
"is not working",
|
|
17879
|
-
"can't figure out",
|
|
17880
|
-
"stack trace"
|
|
17881
|
-
];
|
|
17882
|
-
var DEBUG_WORDS = [
|
|
17883
|
-
"fix",
|
|
17884
|
-
"bug",
|
|
17885
|
-
"broken",
|
|
17886
|
-
"crash",
|
|
17887
|
-
"crashing",
|
|
17888
|
-
"failing",
|
|
17889
|
-
"debug",
|
|
17890
|
-
"debugging",
|
|
17891
|
-
"investigate",
|
|
17892
|
-
"troubleshoot",
|
|
17893
|
-
"regression",
|
|
17894
|
-
"wrong"
|
|
17895
|
-
];
|
|
17896
|
-
var DEBUG_PATTERN = new RegExp(
|
|
17897
|
-
[
|
|
17898
|
-
...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
17899
|
-
...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
|
|
17900
|
-
].join("|"),
|
|
17901
|
-
"i"
|
|
17902
|
-
);
|
|
17903
|
-
var FALSE_POSITIVE_PATTERNS = [
|
|
17904
|
-
/\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
|
|
17905
|
-
/\berror\s+handling\b/i,
|
|
17906
|
-
/\berror\s+boundar(?:y|ies)\b/i,
|
|
17907
|
-
/\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
|
|
17908
|
-
/\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
|
|
17909
|
-
];
|
|
17910
|
-
function hasDebugIntent(prompt) {
|
|
17911
|
-
if (!DEBUG_PATTERN.test(prompt)) return false;
|
|
17912
|
-
for (const fp of FALSE_POSITIVE_PATTERNS) {
|
|
17913
|
-
if (fp.test(prompt)) return false;
|
|
17914
|
-
}
|
|
17915
|
-
return true;
|
|
17916
|
-
}
|
|
17917
|
-
var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
|
|
17918
|
-
var CODE_AUTHORING_PATTERN = /\b(add|create|implement|build|write|fix|update|change|refactor|modify|remove|delete|move|rename)\b.*\b(function|component|feature|endpoint|test|file|module|class|type|interface|hook|page|route|style|migration|code|bug|error|issue)\b/i;
|
|
17919
|
-
function isGitOnlyPrompt(prompt) {
|
|
17920
|
-
if (!GIT_ONLY_PATTERN.test(prompt)) return false;
|
|
17921
|
-
if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
|
|
17922
|
-
return true;
|
|
17923
|
-
}
|
|
17924
|
-
function reconcileAnalysisMode(predictedMode, signals) {
|
|
17925
|
-
const mode2 = resolveAnalysisMode(predictedMode, signals);
|
|
17926
|
-
if (mode2 !== "skip") return mode2;
|
|
17927
|
-
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
17928
|
-
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
17929
|
-
return mode2;
|
|
17930
|
-
}
|
|
17931
|
-
function resolveAnalysisMode(predictedMode, signals) {
|
|
17932
|
-
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
17933
|
-
return detectAnalysisMode(
|
|
17934
|
-
signals.noFilesChanged,
|
|
17935
|
-
signals.assistantResponse,
|
|
17936
|
-
signals.conversationPrompts,
|
|
17937
|
-
signals.actionSummary,
|
|
17938
|
-
signals.sessionAuthoredCode
|
|
17939
|
-
);
|
|
17940
|
-
}
|
|
17941
|
-
const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
|
|
17942
|
-
const agentInvestigated = didAgentInvestigate(signals.actionSummary);
|
|
17943
|
-
switch (predictedMode) {
|
|
17944
|
-
case "skip":
|
|
17945
|
-
if (agentAuthoredCode) return "standard";
|
|
17946
|
-
return "skip";
|
|
17947
|
-
case "plan":
|
|
17948
|
-
if (agentAuthoredCode) return "standard";
|
|
17949
|
-
return "plan";
|
|
17950
|
-
case "debug":
|
|
17951
|
-
return "debug";
|
|
17952
|
-
case "standard":
|
|
17953
|
-
if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
|
|
17954
|
-
return agentInvestigated ? "plan" : "skip";
|
|
17955
|
-
}
|
|
17956
|
-
return "standard";
|
|
17957
|
-
}
|
|
17958
|
-
}
|
|
17959
|
-
function didAgentInvestigate(summary) {
|
|
17960
|
-
if (!summary) return false;
|
|
17961
|
-
return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
|
|
17962
|
-
}
|
|
17963
|
-
function isValidMode(mode2) {
|
|
17964
|
-
return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
|
|
17965
|
-
}
|
|
17966
|
-
function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
|
|
17967
|
-
const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
|
|
17968
|
-
if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
|
|
17969
|
-
if (!agentAuthoredCode) return "skip";
|
|
17970
|
-
}
|
|
17971
|
-
if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
|
|
17972
|
-
return "plan";
|
|
17973
|
-
}
|
|
17974
|
-
if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
|
|
17975
|
-
return didAgentInvestigate(actionSummary) ? "plan" : "skip";
|
|
17976
|
-
}
|
|
17977
|
-
for (const prompt of conversationPrompts) {
|
|
17978
|
-
if (hasDebugIntent(prompt)) {
|
|
17979
|
-
return "debug";
|
|
17980
|
-
}
|
|
17981
|
-
}
|
|
17982
|
-
return "standard";
|
|
17983
|
-
}
|
|
17984
|
-
var FILE_MUTATE_RE = /(?:^|[\s|&;(`])(?:sed\s+-i|perl\s+-i|awk\b|tee\b|dd\b|cp\b|mv\b|ln\b|install\b|touch\b|patch\b|git\s+(?:apply|am)\b|cargo\s+build|go\s+generate|make\b|--write\b|--fix\b|--in-place\b)|>>?(?![&>])/i;
|
|
17985
|
-
var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
|
|
17986
|
-
var READ_ONLY_RE = /^\s*(?:git\s+(?:status|diff|log|show|branch|remote|config|rev-parse|ls-files|blame|describe)|ls|cat|head|tail|less|grep|rg|find|pwd|echo|printf|wc|which|type|tree|stat|file|env|printenv|date|whoami)\b/i;
|
|
17987
|
-
var CHAIN_RE = /&&|\||;|\$\(|\x60/;
|
|
17988
|
-
function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
|
|
17989
|
-
if (!actionSummary) return sessionAuthoredCode;
|
|
17990
|
-
if ((actionSummary.subagents ?? 0) > 0) return true;
|
|
17991
|
-
if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
|
|
17992
|
-
const commands = actionSummary.commands ?? [];
|
|
17993
|
-
if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
|
|
17994
|
-
if (sessionAuthoredCode) {
|
|
17995
|
-
const allSafe = commands.length > 0 && commands.every(
|
|
17996
|
-
(c) => !CHAIN_RE.test(c) && (GIT_PLUMBING_RE.test(c) || READ_ONLY_RE.test(c))
|
|
17997
|
-
);
|
|
17998
|
-
if (!allSafe) return true;
|
|
17999
|
-
}
|
|
18000
|
-
return false;
|
|
18001
|
-
}
|
|
18002
|
-
function scopeToAuthored(files, actionSummary) {
|
|
18003
|
-
if (!actionSummary) return { files, signal: "no-transcript" };
|
|
18004
|
-
const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
|
|
18005
|
-
if (touched.length === 0) return { files: [], signal: "none-authored" };
|
|
18006
|
-
return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
|
|
18007
|
-
}
|
|
18008
|
-
function narrowToAgentAuthored(files, actionSummary) {
|
|
18009
|
-
if (!actionSummary) return files;
|
|
18010
|
-
const touched = [
|
|
18011
|
-
...actionSummary.files_edited,
|
|
18012
|
-
...actionSummary.files_created
|
|
18013
|
-
];
|
|
18014
|
-
if (touched.length === 0) return files;
|
|
18015
|
-
return files.filter((f) => {
|
|
18016
|
-
const suffix = "/" + f;
|
|
18017
|
-
return touched.some((t) => t === f || t.endsWith(suffix));
|
|
18018
|
-
});
|
|
18019
|
-
}
|
|
18020
|
-
|
|
18021
18958
|
// src/commands/analyze/phases/05-mode.ts
|
|
18022
18959
|
async function mode(run) {
|
|
18023
18960
|
const { opts, globals } = run;
|
|
@@ -18038,231 +18975,62 @@ async function mode(run) {
|
|
|
18038
18975
|
});
|
|
18039
18976
|
if (memoryResult.ok) {
|
|
18040
18977
|
if (Array.isArray(memoryResult.data.context_files)) {
|
|
18041
|
-
contextFilePaths = memoryResult.data.context_files;
|
|
18042
|
-
}
|
|
18043
|
-
const rawMode = memoryResult.data.predicted_mode;
|
|
18044
|
-
if (rawMode && ["standard", "plan", "debug", "skip"].includes(rawMode)) {
|
|
18045
|
-
predictedMode = rawMode;
|
|
18046
|
-
}
|
|
18047
|
-
}
|
|
18048
|
-
} catch {
|
|
18049
|
-
logEvent("memory_fetch_failed", { reason: "exception" });
|
|
18050
|
-
}
|
|
18051
|
-
const conversationPrompts = (conversation?.prompts ?? []).map((p) => p.prompt);
|
|
18052
|
-
let analysisMode;
|
|
18053
|
-
const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18054
|
-
const modeOverride = opts.mode;
|
|
18055
|
-
const forced = !!modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride);
|
|
18056
|
-
if (forced) {
|
|
18057
|
-
analysisMode = modeOverride;
|
|
18058
|
-
} else {
|
|
18059
|
-
analysisMode = reconcileAnalysisMode(
|
|
18060
|
-
predictedMode,
|
|
18061
|
-
{ noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
|
|
18062
|
-
);
|
|
18063
|
-
}
|
|
18064
|
-
const investigated = didAgentInvestigate(actionSummary);
|
|
18065
|
-
run.modeDecision = {
|
|
18066
|
-
predicted: predictedMode ?? null,
|
|
18067
|
-
resolved: analysisMode,
|
|
18068
|
-
authored: turnAuthoredCode,
|
|
18069
|
-
investigated,
|
|
18070
|
-
forced
|
|
18071
|
-
};
|
|
18072
|
-
logEvent("mode_resolved", {
|
|
18073
|
-
predicted: predictedMode ?? null,
|
|
18074
|
-
resolved: analysisMode,
|
|
18075
|
-
forced,
|
|
18076
|
-
authored: turnAuthoredCode,
|
|
18077
|
-
investigated,
|
|
18078
|
-
// The two counters that decide `investigated`, so a false reading is
|
|
18079
|
-
// traceable to the tool that was not recognised.
|
|
18080
|
-
subagents: actionSummary?.subagents ?? null,
|
|
18081
|
-
files_read: actionSummary?.files_read.length ?? null
|
|
18082
|
-
});
|
|
18083
|
-
if (analysisMode === "skip") {
|
|
18084
|
-
await passAndExit(
|
|
18085
|
-
run,
|
|
18086
|
-
"Skip mode \u2014 no code work to analyze",
|
|
18087
|
-
"skip-mode",
|
|
18088
|
-
turnAuthoredCode ? "capacity" : void 0
|
|
18089
|
-
);
|
|
18090
|
-
}
|
|
18091
|
-
Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
|
|
18092
|
-
}
|
|
18093
|
-
|
|
18094
|
-
// src/lib/debounce.ts
|
|
18095
|
-
var import_node_fs23 = require("node:fs");
|
|
18096
|
-
var import_node_crypto10 = require("node:crypto");
|
|
18097
|
-
function scopedFile(base, sessionId) {
|
|
18098
|
-
if (!sessionId) return base;
|
|
18099
|
-
return `${base}.${(0, import_node_crypto10.createHash)("sha1").update(sessionId).digest("hex").slice(0, 12)}`;
|
|
18100
|
-
}
|
|
18101
|
-
function checkDebounce(debounceSeconds = DEBOUNCE_SECONDS, sessionId) {
|
|
18102
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18103
|
-
if (!(0, import_node_fs23.existsSync)(file)) return null;
|
|
18104
|
-
try {
|
|
18105
|
-
const lastTs = parseInt((0, import_node_fs23.readFileSync)(file, "utf-8").trim(), 10);
|
|
18106
|
-
const nowTs = Math.floor(Date.now() / 1e3);
|
|
18107
|
-
const elapsed = nowTs - lastTs;
|
|
18108
|
-
if (elapsed < debounceSeconds) {
|
|
18109
|
-
return `Debounced \u2014 last analysis was ${elapsed}s ago`;
|
|
18110
|
-
}
|
|
18111
|
-
} catch {
|
|
18112
|
-
}
|
|
18113
|
-
return null;
|
|
18114
|
-
}
|
|
18115
|
-
function checkMtime(files, bypassForRecentCommits, sessionId) {
|
|
18116
|
-
if (bypassForRecentCommits) return null;
|
|
18117
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18118
|
-
if (!(0, import_node_fs23.existsSync)(file)) return null;
|
|
18119
|
-
let debounceTime;
|
|
18120
|
-
try {
|
|
18121
|
-
debounceTime = (0, import_node_fs23.statSync)(file).mtimeMs;
|
|
18122
|
-
} catch {
|
|
18123
|
-
return null;
|
|
18124
|
-
}
|
|
18125
|
-
for (const f of files) {
|
|
18126
|
-
const resolved = resolveFile(f);
|
|
18127
|
-
if (!resolved) continue;
|
|
18128
|
-
try {
|
|
18129
|
-
const stat3 = (0, import_node_fs23.statSync)(resolved);
|
|
18130
|
-
if (stat3.mtimeMs > debounceTime) {
|
|
18131
|
-
return null;
|
|
18132
|
-
}
|
|
18133
|
-
} catch {
|
|
18134
|
-
continue;
|
|
18135
|
-
}
|
|
18136
|
-
}
|
|
18137
|
-
return "No files modified since last analysis";
|
|
18138
|
-
}
|
|
18139
|
-
function computeContentHash(files) {
|
|
18140
|
-
const hash = (0, import_node_crypto10.createHash)("sha1");
|
|
18141
|
-
const sorted = [...files].sort();
|
|
18142
|
-
for (const f of sorted) {
|
|
18143
|
-
const resolved = resolveFile(f) ?? f;
|
|
18144
|
-
try {
|
|
18145
|
-
if ((0, import_node_fs23.existsSync)(resolved)) {
|
|
18146
|
-
hash.update((0, import_node_fs23.readFileSync)(resolved));
|
|
18978
|
+
contextFilePaths = memoryResult.data.context_files;
|
|
18147
18979
|
}
|
|
18148
|
-
|
|
18149
|
-
|
|
18150
|
-
|
|
18151
|
-
return hash.digest("hex");
|
|
18152
|
-
}
|
|
18153
|
-
function checkContentHash(files, sessionId) {
|
|
18154
|
-
const hash = computeContentHash(files);
|
|
18155
|
-
const file = scopedFile(HASH_FILE, sessionId);
|
|
18156
|
-
if ((0, import_node_fs23.existsSync)(file)) {
|
|
18157
|
-
try {
|
|
18158
|
-
const storedHash = (0, import_node_fs23.readFileSync)(file, "utf-8").trim();
|
|
18159
|
-
if (hash === storedHash) {
|
|
18160
|
-
return { skip: "No source changes since last analysis", hash };
|
|
18980
|
+
const rawMode = memoryResult.data.predicted_mode;
|
|
18981
|
+
if (rawMode && ["standard", "plan", "debug", "skip"].includes(rawMode)) {
|
|
18982
|
+
predictedMode = rawMode;
|
|
18161
18983
|
}
|
|
18162
|
-
} catch {
|
|
18163
18984
|
}
|
|
18164
|
-
}
|
|
18165
|
-
return { skip: null, hash };
|
|
18166
|
-
}
|
|
18167
|
-
function recordAnalysisStart(sessionId) {
|
|
18168
|
-
(0, import_node_fs23.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
18169
|
-
(0, import_node_fs23.writeFileSync)(scopedFile(DEBOUNCE_FILE, sessionId), String(Math.floor(Date.now() / 1e3)));
|
|
18170
|
-
}
|
|
18171
|
-
function recordPassHash(hash, sessionId) {
|
|
18172
|
-
(0, import_node_fs23.writeFileSync)(scopedFile(HASH_FILE, sessionId), hash);
|
|
18173
|
-
}
|
|
18174
|
-
function narrowToRecent(files, sessionId) {
|
|
18175
|
-
const file = scopedFile(DEBOUNCE_FILE, sessionId);
|
|
18176
|
-
if (!(0, import_node_fs23.existsSync)(file)) return files;
|
|
18177
|
-
let debounceTime;
|
|
18178
|
-
try {
|
|
18179
|
-
debounceTime = (0, import_node_fs23.statSync)(file).mtimeMs;
|
|
18180
18985
|
} catch {
|
|
18181
|
-
|
|
18986
|
+
logEvent("memory_fetch_failed", { reason: "exception" });
|
|
18182
18987
|
}
|
|
18183
|
-
const
|
|
18184
|
-
|
|
18185
|
-
|
|
18186
|
-
|
|
18187
|
-
|
|
18188
|
-
|
|
18189
|
-
|
|
18190
|
-
|
|
18191
|
-
|
|
18192
|
-
|
|
18193
|
-
|
|
18194
|
-
|
|
18195
|
-
var NO_BLOCKS = { attempts: 0, blocks: 0, fingerprint: null };
|
|
18196
|
-
function readBlockState(currentCommit, opts) {
|
|
18197
|
-
if (opts?.newUserPrompt) return NO_BLOCKS;
|
|
18198
|
-
if (!(0, import_node_fs23.existsSync)(ITERATION_FILE)) return NO_BLOCKS;
|
|
18199
|
-
try {
|
|
18200
|
-
const stored = (0, import_node_fs23.readFileSync)(ITERATION_FILE, "utf-8").trim();
|
|
18201
|
-
const parsed = stored.startsWith("{") ? parseJsonState(stored) : parseLegacyState(stored);
|
|
18202
|
-
if (!parsed) return NO_BLOCKS;
|
|
18203
|
-
if (parsed.commit !== currentCommit) return NO_BLOCKS;
|
|
18204
|
-
if (parsed.ts > 0 && Math.floor(Date.now() / 1e3) - parsed.ts > 600) return NO_BLOCKS;
|
|
18205
|
-
return { attempts: parsed.attempts, blocks: parsed.blocks, fingerprint: parsed.fingerprint };
|
|
18206
|
-
} catch {
|
|
18207
|
-
return NO_BLOCKS;
|
|
18988
|
+
const conversationPrompts = (conversation?.prompts ?? []).map((p) => p.prompt);
|
|
18989
|
+
let analysisMode;
|
|
18990
|
+
const sessionAuthoredCode = !!baseline && allForReview.some((f) => changedSinceBaseline(f, baseline));
|
|
18991
|
+
const modeOverride = opts.mode;
|
|
18992
|
+
const forced = !!modeOverride && ["standard", "plan", "debug", "skip"].includes(modeOverride);
|
|
18993
|
+
if (forced) {
|
|
18994
|
+
analysisMode = modeOverride;
|
|
18995
|
+
} else {
|
|
18996
|
+
analysisMode = reconcileAnalysisMode(
|
|
18997
|
+
predictedMode,
|
|
18998
|
+
{ noFilesChanged, assistantResponse, actionSummary, conversationPrompts, sessionAuthoredCode }
|
|
18999
|
+
);
|
|
18208
19000
|
}
|
|
18209
|
-
|
|
18210
|
-
|
|
18211
|
-
|
|
18212
|
-
|
|
18213
|
-
|
|
18214
|
-
|
|
18215
|
-
|
|
18216
|
-
blocks: typeof o.blocks === "number" ? o.blocks : attempts,
|
|
18217
|
-
fingerprint: typeof o.fingerprint === "string" && o.fingerprint ? o.fingerprint : null,
|
|
18218
|
-
commit: typeof o.commit === "string" ? o.commit : "",
|
|
18219
|
-
ts: typeof o.ts === "number" ? o.ts : 0
|
|
18220
|
-
};
|
|
18221
|
-
}
|
|
18222
|
-
function parseLegacyState(raw) {
|
|
18223
|
-
const parts = raw.split(":");
|
|
18224
|
-
const n = parseInt(parts[0], 10);
|
|
18225
|
-
if (isNaN(n)) return null;
|
|
18226
|
-
return {
|
|
18227
|
-
attempts: n,
|
|
18228
|
-
// The old file has no separate block count; the old counter is the closest
|
|
18229
|
-
// honest answer, and it errs toward releasing sooner rather than later.
|
|
18230
|
-
blocks: n,
|
|
18231
|
-
fingerprint: parts.slice(3).join(":") || null,
|
|
18232
|
-
commit: parts[1] ?? "",
|
|
18233
|
-
ts: parseInt(parts[2] ?? "0", 10)
|
|
19001
|
+
const investigated = didAgentInvestigate(actionSummary);
|
|
19002
|
+
run.modeDecision = {
|
|
19003
|
+
predicted: predictedMode ?? null,
|
|
19004
|
+
resolved: analysisMode,
|
|
19005
|
+
authored: turnAuthoredCode,
|
|
19006
|
+
investigated,
|
|
19007
|
+
forced
|
|
18234
19008
|
};
|
|
18235
|
-
|
|
18236
|
-
|
|
18237
|
-
|
|
18238
|
-
|
|
18239
|
-
|
|
18240
|
-
|
|
18241
|
-
|
|
18242
|
-
|
|
18243
|
-
|
|
18244
|
-
|
|
18245
|
-
|
|
18246
|
-
(
|
|
18247
|
-
|
|
18248
|
-
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18255
|
-
fingerprint: state.fingerprint ?? void 0
|
|
18256
|
-
})
|
|
18257
|
-
);
|
|
18258
|
-
}
|
|
18259
|
-
function resetBlockState(commit) {
|
|
18260
|
-
writeBlockState(commit, { attempts: 0, blocks: 0, fingerprint: null });
|
|
19009
|
+
logEvent("mode_resolved", {
|
|
19010
|
+
predicted: predictedMode ?? null,
|
|
19011
|
+
resolved: analysisMode,
|
|
19012
|
+
forced,
|
|
19013
|
+
authored: turnAuthoredCode,
|
|
19014
|
+
investigated,
|
|
19015
|
+
// The two counters that decide `investigated`, so a false reading is
|
|
19016
|
+
// traceable to the tool that was not recognised.
|
|
19017
|
+
subagents: actionSummary?.subagents ?? null,
|
|
19018
|
+
files_read: actionSummary?.files_read.length ?? null
|
|
19019
|
+
});
|
|
19020
|
+
if (analysisMode === "skip") {
|
|
19021
|
+
await passAndExit(
|
|
19022
|
+
run,
|
|
19023
|
+
"Skip mode \u2014 no code work to analyze",
|
|
19024
|
+
"skip-mode",
|
|
19025
|
+
turnAuthoredCode ? "capacity" : void 0
|
|
19026
|
+
);
|
|
19027
|
+
}
|
|
19028
|
+
Object.assign(run, { analysisMode, contextFilePaths, sessionAuthoredCode, sessionIdForMemory });
|
|
18261
19029
|
}
|
|
18262
19030
|
|
|
18263
19031
|
// src/lib/fold.ts
|
|
18264
|
-
var
|
|
18265
|
-
var
|
|
19032
|
+
var import_node_fs28 = require("node:fs");
|
|
19033
|
+
var import_node_path21 = require("node:path");
|
|
18266
19034
|
var KNOWN_RECORD_TYPES = /* @__PURE__ */ new Set([
|
|
18267
19035
|
"user",
|
|
18268
19036
|
"assistant",
|
|
@@ -18300,7 +19068,7 @@ var COMMAND_CLASSES = [
|
|
|
18300
19068
|
[/\btsc\b|\bmypy\b|\btypecheck\b/, "typecheck"],
|
|
18301
19069
|
[/^git\s/, "git"]
|
|
18302
19070
|
];
|
|
18303
|
-
function
|
|
19071
|
+
function classifyCommand2(cmd) {
|
|
18304
19072
|
for (const [re, cls] of COMMAND_CLASSES) if (re.test(cmd)) return cls;
|
|
18305
19073
|
return "other";
|
|
18306
19074
|
}
|
|
@@ -18399,7 +19167,7 @@ function candidateRoots(repoRoot2) {
|
|
|
18399
19167
|
const norm = repoRoot2.replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18400
19168
|
const out = [norm];
|
|
18401
19169
|
try {
|
|
18402
|
-
const real =
|
|
19170
|
+
const real = import_node_fs28.realpathSync.native(norm).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
18403
19171
|
if (real !== norm) out.push(real);
|
|
18404
19172
|
} catch {
|
|
18405
19173
|
}
|
|
@@ -18487,31 +19255,31 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18487
19255
|
}
|
|
18488
19256
|
};
|
|
18489
19257
|
try {
|
|
18490
|
-
if (!(0,
|
|
18491
|
-
ingest((0,
|
|
19258
|
+
if (!(0, import_node_fs28.existsSync)(transcriptPath)) return result;
|
|
19259
|
+
ingest((0, import_node_fs28.readFileSync)(transcriptPath, "utf8"), "agent");
|
|
18492
19260
|
result.coverage.complete = true;
|
|
18493
19261
|
} catch {
|
|
18494
19262
|
return result;
|
|
18495
19263
|
}
|
|
18496
19264
|
try {
|
|
18497
|
-
const sidecarDir = (0,
|
|
18498
|
-
(0,
|
|
18499
|
-
(0,
|
|
19265
|
+
const sidecarDir = (0, import_node_path21.join)(
|
|
19266
|
+
(0, import_node_path21.dirname)(transcriptPath),
|
|
19267
|
+
(0, import_node_path21.basename)(transcriptPath).replace(/\.jsonl$/, ""),
|
|
18500
19268
|
"subagents"
|
|
18501
19269
|
);
|
|
18502
|
-
if ((0,
|
|
19270
|
+
if ((0, import_node_fs28.existsSync)(sidecarDir)) {
|
|
18503
19271
|
const maxFiles = opts.maxSidecars ?? 200;
|
|
18504
19272
|
const maxBytes = opts.maxSidecarBytes ?? 16 * 1024 * 1024;
|
|
18505
19273
|
const found = [];
|
|
18506
19274
|
const walk = (d, depth) => {
|
|
18507
19275
|
if (depth > 4) return;
|
|
18508
|
-
for (const e of (0,
|
|
18509
|
-
const p = (0,
|
|
19276
|
+
for (const e of (0, import_node_fs28.readdirSync)(d, { withFileTypes: true })) {
|
|
19277
|
+
const p = (0, import_node_path21.join)(d, e.name);
|
|
18510
19278
|
if (e.isDirectory()) {
|
|
18511
19279
|
walk(p, depth + 1);
|
|
18512
19280
|
} else if (e.name.startsWith("agent-") && e.name.endsWith(".jsonl")) {
|
|
18513
19281
|
try {
|
|
18514
|
-
const st = (0,
|
|
19282
|
+
const st = (0, import_node_fs28.statSync)(p);
|
|
18515
19283
|
found.push({ path: p, size: st.size, mtimeMs: st.mtimeMs });
|
|
18516
19284
|
} catch {
|
|
18517
19285
|
result.coverage.malformed++;
|
|
@@ -18528,7 +19296,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18528
19296
|
continue;
|
|
18529
19297
|
}
|
|
18530
19298
|
try {
|
|
18531
|
-
ingest((0,
|
|
19299
|
+
ingest((0, import_node_fs28.readFileSync)(f.path, "utf8"), "subagent");
|
|
18532
19300
|
bytes += f.size;
|
|
18533
19301
|
result.coverage.subagentFiles++;
|
|
18534
19302
|
} catch {
|
|
@@ -18563,7 +19331,7 @@ function fold(transcriptPath, opts = {}) {
|
|
|
18563
19331
|
}
|
|
18564
19332
|
function classifyUnobserved(path) {
|
|
18565
19333
|
try {
|
|
18566
|
-
const st = (0,
|
|
19334
|
+
const st = (0, import_node_fs28.statSync)(path);
|
|
18567
19335
|
if (!st.isFile()) return "unreadable";
|
|
18568
19336
|
} catch {
|
|
18569
19337
|
return "unreadable";
|
|
@@ -18629,7 +19397,7 @@ function collectFromRecord(record, owner, byPath, commandStats, pendingByToolUse
|
|
|
18629
19397
|
if (name === "Bash") {
|
|
18630
19398
|
const cmd = typeof input.command === "string" ? input.command : "";
|
|
18631
19399
|
if (cmd) {
|
|
18632
|
-
const cls =
|
|
19400
|
+
const cls = classifyCommand2(cmd);
|
|
18633
19401
|
const prev = commandStats.get(cls) ?? { last_status: null, runs: 0, head: "" };
|
|
18634
19402
|
commandStats.set(cls, {
|
|
18635
19403
|
// UNKNOWN until this command's OWN result arrives. Inheriting the
|
|
@@ -18867,20 +19635,20 @@ async function evidence(run) {
|
|
|
18867
19635
|
}
|
|
18868
19636
|
|
|
18869
19637
|
// src/lib/cache-cleanup.ts
|
|
18870
|
-
var
|
|
18871
|
-
var
|
|
19638
|
+
var import_node_fs29 = require("node:fs");
|
|
19639
|
+
var import_node_path22 = require("node:path");
|
|
18872
19640
|
var CACHE_TTL_DAYS = 7;
|
|
18873
19641
|
function pruneStaleCache() {
|
|
18874
19642
|
try {
|
|
18875
19643
|
const dir = projectPath(CACHE_DIR);
|
|
18876
19644
|
const cutoff = Date.now() - CACHE_TTL_DAYS * 24 * 3600 * 1e3;
|
|
18877
|
-
for (const entry of (0,
|
|
19645
|
+
for (const entry of (0, import_node_fs29.readdirSync)(dir)) {
|
|
18878
19646
|
if (!entry.startsWith("pending-")) continue;
|
|
18879
|
-
const path = (0,
|
|
19647
|
+
const path = (0, import_node_path22.join)(dir, entry);
|
|
18880
19648
|
try {
|
|
18881
|
-
const stat3 = (0,
|
|
19649
|
+
const stat3 = (0, import_node_fs29.statSync)(path);
|
|
18882
19650
|
if (stat3.mtimeMs < cutoff) {
|
|
18883
|
-
(0,
|
|
19651
|
+
(0, import_node_fs29.unlinkSync)(path);
|
|
18884
19652
|
logEvent("cache_entry_pruned", {
|
|
18885
19653
|
path: entry,
|
|
18886
19654
|
age_days: Math.round((Date.now() - stat3.mtimeMs) / 864e5)
|
|
@@ -18894,7 +19662,7 @@ function pruneStaleCache() {
|
|
|
18894
19662
|
}
|
|
18895
19663
|
|
|
18896
19664
|
// src/lib/context-files.ts
|
|
18897
|
-
var
|
|
19665
|
+
var import_node_fs30 = require("node:fs");
|
|
18898
19666
|
var MAX_CONTEXT_FILES = 10;
|
|
18899
19667
|
var MAX_CONTEXT_FILE_BYTES = 10240;
|
|
18900
19668
|
var MAX_CONTEXT_TOTAL_BYTES = 51200;
|
|
@@ -18915,7 +19683,7 @@ function gatherContextFiles(contextPaths, deltaFiles) {
|
|
|
18915
19683
|
continue;
|
|
18916
19684
|
}
|
|
18917
19685
|
try {
|
|
18918
|
-
const content = (0,
|
|
19686
|
+
const content = (0, import_node_fs30.readFileSync)(safePath, "utf8");
|
|
18919
19687
|
const bytes = Buffer.byteLength(content);
|
|
18920
19688
|
if (bytes > MAX_CONTEXT_FILE_BYTES) {
|
|
18921
19689
|
logEvent("context_file_skipped", { path: filePath, reason: "too_large", bytes });
|
|
@@ -18977,8 +19745,8 @@ async function contextFiles(run) {
|
|
|
18977
19745
|
|
|
18978
19746
|
// src/lib/seed-runner.ts
|
|
18979
19747
|
var import_promises11 = require("node:fs/promises");
|
|
18980
|
-
var
|
|
18981
|
-
var
|
|
19748
|
+
var import_node_fs31 = require("node:fs");
|
|
19749
|
+
var import_node_path23 = require("node:path");
|
|
18982
19750
|
var import_yaml2 = __toESM(require_dist());
|
|
18983
19751
|
|
|
18984
19752
|
// src/lib/seed.ts
|
|
@@ -19217,7 +19985,7 @@ function renderNodeMarkdown(candidate, nodeId, createdAt) {
|
|
|
19217
19985
|
return fm;
|
|
19218
19986
|
}
|
|
19219
19987
|
async function runSeed(opts) {
|
|
19220
|
-
if (!(0,
|
|
19988
|
+
if (!(0, import_node_fs31.existsSync)(STANDARD_FILE)) {
|
|
19221
19989
|
return { created: 0, failed: 0, skipped: "no_standard", candidates: [] };
|
|
19222
19990
|
}
|
|
19223
19991
|
let standardDoc;
|
|
@@ -19229,7 +19997,7 @@ async function runSeed(opts) {
|
|
|
19229
19997
|
}
|
|
19230
19998
|
const knowledgeSpec = standardDoc.knowledge_spec ?? {};
|
|
19231
19999
|
let readmeContent;
|
|
19232
|
-
if ((0,
|
|
20000
|
+
if ((0, import_node_fs31.existsSync)("README.md")) {
|
|
19233
20001
|
try {
|
|
19234
20002
|
readmeContent = await (0, import_promises11.readFile)("README.md", "utf-8");
|
|
19235
20003
|
} catch {
|
|
@@ -19237,7 +20005,7 @@ async function runSeed(opts) {
|
|
|
19237
20005
|
}
|
|
19238
20006
|
let claudeMdContent;
|
|
19239
20007
|
for (const p of ["CLAUDE.md", ".claude/CLAUDE.md"]) {
|
|
19240
|
-
if ((0,
|
|
20008
|
+
if ((0, import_node_fs31.existsSync)(p)) {
|
|
19241
20009
|
try {
|
|
19242
20010
|
claudeMdContent = await (0, import_promises11.readFile)(p, "utf-8");
|
|
19243
20011
|
break;
|
|
@@ -19260,8 +20028,8 @@ async function runSeed(opts) {
|
|
|
19260
20028
|
if (candidates.length === 0) {
|
|
19261
20029
|
return { created: 0, failed: 0, skipped: "no_candidates", candidates: [] };
|
|
19262
20030
|
}
|
|
19263
|
-
const overviewPath = (0,
|
|
19264
|
-
if ((0,
|
|
20031
|
+
const overviewPath = (0, import_node_path23.join)(MEMORY_DIR, "domain", "project-overview.md");
|
|
20032
|
+
if ((0, import_node_fs31.existsSync)(overviewPath) && !opts.force) {
|
|
19265
20033
|
return { created: 0, failed: 0, skipped: "already_seeded", candidates };
|
|
19266
20034
|
}
|
|
19267
20035
|
if (opts.dryRun) {
|
|
@@ -19303,7 +20071,7 @@ async function runSeed(opts) {
|
|
|
19303
20071
|
continue;
|
|
19304
20072
|
}
|
|
19305
20073
|
try {
|
|
19306
|
-
await (0, import_promises11.mkdir)((0,
|
|
20074
|
+
await (0, import_promises11.mkdir)((0, import_node_path23.dirname)(targetPath), { recursive: true });
|
|
19307
20075
|
await (0, import_promises11.writeFile)(targetPath, renderNodeMarkdown(c, nodeId, createdAt));
|
|
19308
20076
|
created++;
|
|
19309
20077
|
opts.onCreated?.(nodeId, filePathRel, c);
|
|
@@ -19316,8 +20084,8 @@ async function runSeed(opts) {
|
|
|
19316
20084
|
}
|
|
19317
20085
|
|
|
19318
20086
|
// src/commands/analyze/phases/08-memory-manifest.ts
|
|
19319
|
-
var
|
|
19320
|
-
var
|
|
20087
|
+
var import_node_fs32 = require("node:fs");
|
|
20088
|
+
var import_node_path24 = require("node:path");
|
|
19321
20089
|
async function memoryManifest(run) {
|
|
19322
20090
|
const { globals } = run;
|
|
19323
20091
|
const { serviceUrl, token } = run;
|
|
@@ -19327,9 +20095,9 @@ async function memoryManifest(run) {
|
|
|
19327
20095
|
let autoSeedNotice = null;
|
|
19328
20096
|
try {
|
|
19329
20097
|
await ensureMemoryDir();
|
|
19330
|
-
const seedMarker = (0,
|
|
19331
|
-
const hasStandard = (0,
|
|
19332
|
-
const alreadyTried = (0,
|
|
20098
|
+
const seedMarker = (0, import_node_path24.join)(VERITY_DIR, ".seeded");
|
|
20099
|
+
const hasStandard = (0, import_node_fs32.existsSync)(STANDARD_FILE);
|
|
20100
|
+
const alreadyTried = (0, import_node_fs32.existsSync)(seedMarker);
|
|
19333
20101
|
if (hasStandard && !alreadyTried) {
|
|
19334
20102
|
const preManifest = await buildManifest();
|
|
19335
20103
|
if (preManifest.nodes.length === 0) {
|
|
@@ -19342,7 +20110,7 @@ async function memoryManifest(run) {
|
|
|
19342
20110
|
dryRun: false
|
|
19343
20111
|
});
|
|
19344
20112
|
if (seedResult.created > 0) {
|
|
19345
|
-
(0,
|
|
20113
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} created=${seedResult.created}
|
|
19346
20114
|
`);
|
|
19347
20115
|
autoSeedNotice = `Seeded ${seedResult.created} knowledge node(s) from your existing Standard (one-time).`;
|
|
19348
20116
|
logEvent("auto_seed_ran", {
|
|
@@ -19350,7 +20118,7 @@ async function memoryManifest(run) {
|
|
|
19350
20118
|
failed: seedResult.failed
|
|
19351
20119
|
});
|
|
19352
20120
|
} else if (seedResult.skipped === "already_seeded") {
|
|
19353
|
-
(0,
|
|
20121
|
+
(0, import_node_fs32.writeFileSync)(seedMarker, `${(/* @__PURE__ */ new Date()).toISOString()} skipped=already_seeded
|
|
19354
20122
|
`);
|
|
19355
20123
|
} else {
|
|
19356
20124
|
logEvent("auto_seed_noop", {
|
|
@@ -19445,7 +20213,7 @@ function computeIncrement(reviewedPaths, hashOf, priorAuthored) {
|
|
|
19445
20213
|
}
|
|
19446
20214
|
|
|
19447
20215
|
// src/commands/analyze/phases/10-working-memory.ts
|
|
19448
|
-
var
|
|
20216
|
+
var import_node_path25 = require("node:path");
|
|
19449
20217
|
async function workingMemory(run) {
|
|
19450
20218
|
const { opts } = run;
|
|
19451
20219
|
const { allForReview, baseline, conversation, foldResult, sessionId, token, transcriptPath } = run;
|
|
@@ -19457,7 +20225,7 @@ async function workingMemory(run) {
|
|
|
19457
20225
|
const priorState = foldForMarks(memorySession.d);
|
|
19458
20226
|
incrementReport = computeIncrement(
|
|
19459
20227
|
allForReview,
|
|
19460
|
-
(p) => fileHash((0,
|
|
20228
|
+
(p) => fileHash((0, import_node_path25.join)(repoRoot(), p)),
|
|
19461
20229
|
priorState.authored_all.map((a) => ({
|
|
19462
20230
|
path: a.path,
|
|
19463
20231
|
hash_at_last_verdict: a.hash_at_last_verdict
|
|
@@ -19539,7 +20307,7 @@ async function workingMemory(run) {
|
|
|
19539
20307
|
}
|
|
19540
20308
|
|
|
19541
20309
|
// src/lib/note-budget.ts
|
|
19542
|
-
var
|
|
20310
|
+
var import_node_fs33 = require("node:fs");
|
|
19543
20311
|
var ADVISORY_BUDGET = { PASS: 1, WARN: 2 };
|
|
19544
20312
|
var EPISODE_STALE_SECONDS = 30 * 60;
|
|
19545
20313
|
var FRESH = { delivered: 0, tasksCompleted: 0, ts: 0 };
|
|
@@ -19561,9 +20329,9 @@ function advisoryBudgetSpent(episode, rawDecision) {
|
|
|
19561
20329
|
}
|
|
19562
20330
|
function readAdvisoryEpisode(sessionId) {
|
|
19563
20331
|
const file = scopedFile(ADVISORY_EPISODE_FILE, sessionId);
|
|
19564
|
-
if (!(0,
|
|
20332
|
+
if (!(0, import_node_fs33.existsSync)(file)) return null;
|
|
19565
20333
|
try {
|
|
19566
|
-
const o = JSON.parse((0,
|
|
20334
|
+
const o = JSON.parse((0, import_node_fs33.readFileSync)(file, "utf-8")) ?? {};
|
|
19567
20335
|
const delivered = typeof o.delivered === "number" ? o.delivered : NaN;
|
|
19568
20336
|
if (isNaN(delivered)) return null;
|
|
19569
20337
|
return {
|
|
@@ -19577,8 +20345,8 @@ function readAdvisoryEpisode(sessionId) {
|
|
|
19577
20345
|
}
|
|
19578
20346
|
function writeAdvisoryEpisode(episode, sessionId) {
|
|
19579
20347
|
try {
|
|
19580
|
-
(0,
|
|
19581
|
-
(0,
|
|
20348
|
+
(0, import_node_fs33.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20349
|
+
(0, import_node_fs33.writeFileSync)(
|
|
19582
20350
|
scopedFile(ADVISORY_EPISODE_FILE, sessionId),
|
|
19583
20351
|
JSON.stringify({ v: 1, ...episode })
|
|
19584
20352
|
);
|
|
@@ -19608,7 +20376,7 @@ function isExplicitlyAutonomous(env = process.env) {
|
|
|
19608
20376
|
}
|
|
19609
20377
|
|
|
19610
20378
|
// src/lib/task-context.ts
|
|
19611
|
-
var
|
|
20379
|
+
var import_node_child_process9 = require("node:child_process");
|
|
19612
20380
|
var CLOSING_RE = /\b(close[sd]?|fix(?:e[sd])?|resolve[sd]?)\b[\s:]*#(\d+)/i;
|
|
19613
20381
|
var BRANCH_RE = /(?:^|[/_-])(?:issue|gh|fix)[-_/]?(\d+)\b/i;
|
|
19614
20382
|
function parseLinkedIssue(sources) {
|
|
@@ -19624,7 +20392,7 @@ function parseLinkedIssue(sources) {
|
|
|
19624
20392
|
}
|
|
19625
20393
|
function safeExec(cmd, timeout) {
|
|
19626
20394
|
try {
|
|
19627
|
-
return (0,
|
|
20395
|
+
return (0, import_node_child_process9.execSync)(cmd, { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout }).trim();
|
|
19628
20396
|
} catch {
|
|
19629
20397
|
return "";
|
|
19630
20398
|
}
|
|
@@ -19685,7 +20453,16 @@ async function buildRequest(run) {
|
|
|
19685
20453
|
// the state, so the number is one turn lagged by construction. The
|
|
19686
20454
|
// degenerate win for the budget is a dead channel that looks like clean
|
|
19687
20455
|
// code; this is what makes "did delivery rate collapse" a query.
|
|
19688
|
-
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0
|
|
20456
|
+
advisory_delivered_prior: readAdvisoryEpisode(run.baselineSessionId)?.delivered ?? 0,
|
|
20457
|
+
// `.verityignore` — see CoverageTelemetry.verityignore for why the SHARE is
|
|
20458
|
+
// the number that matters and why no paths travel with it.
|
|
20459
|
+
verityignore: {
|
|
20460
|
+
rules: run.verityIgnored.rules,
|
|
20461
|
+
excluded: run.verityIgnored.ignored.length,
|
|
20462
|
+
share: ignoreShare(run.verityIgnored.kept.length, run.verityIgnored.ignored.length),
|
|
20463
|
+
security_excluded: run.verityIgnored.securityExcluded.length,
|
|
20464
|
+
suspended: run.verityIgnored.suspended
|
|
20465
|
+
}
|
|
19689
20466
|
};
|
|
19690
20467
|
const requestBody = {
|
|
19691
20468
|
coverage_telemetry: coverageTelemetry,
|
|
@@ -19870,7 +20647,10 @@ async function buildRequest(run) {
|
|
|
19870
20647
|
}
|
|
19871
20648
|
if (specs.length > 0) intentContext.specs = specs;
|
|
19872
20649
|
if (plans.length > 0) intentContext.plans = plans;
|
|
19873
|
-
if (actionSummary)
|
|
20650
|
+
if (actionSummary) {
|
|
20651
|
+
const { user_commands: _uc, user_commands_truncated: _uct, ...onTheWire } = actionSummary;
|
|
20652
|
+
intentContext.action_summary = onTheWire;
|
|
20653
|
+
}
|
|
19874
20654
|
for (const key of Object.keys(intentContext)) {
|
|
19875
20655
|
if (intentContext[key] == null) delete intentContext[key];
|
|
19876
20656
|
}
|
|
@@ -19880,14 +20660,14 @@ async function buildRequest(run) {
|
|
|
19880
20660
|
}
|
|
19881
20661
|
|
|
19882
20662
|
// src/lib/offline.ts
|
|
19883
|
-
var
|
|
20663
|
+
var import_node_fs34 = require("node:fs");
|
|
19884
20664
|
var import_node_crypto11 = require("node:crypto");
|
|
19885
20665
|
function cacheRequest(body) {
|
|
19886
20666
|
try {
|
|
19887
|
-
(0,
|
|
20667
|
+
(0, import_node_fs34.mkdirSync)(CACHE_DIR, { recursive: true });
|
|
19888
20668
|
const suffix = (0, import_node_crypto11.randomBytes)(4).toString("hex");
|
|
19889
20669
|
const filename = `pending-${Math.floor(Date.now() / 1e3)}-${suffix}.json`;
|
|
19890
|
-
(0,
|
|
20670
|
+
(0, import_node_fs34.writeFileSync)(`${CACHE_DIR}/${filename}`, JSON.stringify(redactRequest(body)));
|
|
19891
20671
|
} catch {
|
|
19892
20672
|
}
|
|
19893
20673
|
}
|
|
@@ -20006,8 +20786,8 @@ async function transmit(run) {
|
|
|
20006
20786
|
}
|
|
20007
20787
|
|
|
20008
20788
|
// src/commands/analyze/phases/13-reconcile.ts
|
|
20009
|
-
var
|
|
20010
|
-
var
|
|
20789
|
+
var import_node_fs35 = require("node:fs");
|
|
20790
|
+
var import_node_path26 = require("node:path");
|
|
20011
20791
|
async function reconcile(run) {
|
|
20012
20792
|
const { actionSummary, allChanged, analyzable, baseline, baselineSessionId, codeDelta, contentHash, conversation, decision, foldResult, memory, memorySession, response, reviewable, securityFiles, turnId } = run;
|
|
20013
20793
|
const sentPaths = codeDelta.files.map((f) => f.path);
|
|
@@ -20017,7 +20797,7 @@ async function reconcile(run) {
|
|
|
20017
20797
|
const st = foldDossier(memorySession.d);
|
|
20018
20798
|
openElsewhere = openBlockingElsewhere(st.statements, sentPaths, (file, line) => {
|
|
20019
20799
|
try {
|
|
20020
|
-
const src = (0,
|
|
20800
|
+
const src = (0, import_node_fs35.readFileSync)((0, import_node_path26.join)(repoRoot(), file), "utf8").split("\n");
|
|
20021
20801
|
const at = src[line - 1];
|
|
20022
20802
|
return at === void 0 ? null : lineSha(at);
|
|
20023
20803
|
} catch {
|
|
@@ -20078,6 +20858,25 @@ async function reconcile(run) {
|
|
|
20078
20858
|
stage: "self-scope",
|
|
20079
20859
|
kind: "policy"
|
|
20080
20860
|
})),
|
|
20861
|
+
// ⚠ WHAT THE TEAM'S OWN `.verityignore` REMOVED — every path, named
|
|
20862
|
+
// (VRT-135 · 3/3). The 0.30 principle: an exclusion the ledger cannot see
|
|
20863
|
+
// is a scoping decision nobody can audit, and this is the one exclusion
|
|
20864
|
+
// stage a user can edit, so it is the one that most needs to be visible.
|
|
20865
|
+
//
|
|
20866
|
+
// `policy`, by D2's bar — no version of this product would have reviewed a
|
|
20867
|
+
// file the project declared out of scope, so it must not downgrade PASS to
|
|
20868
|
+
// WARN. That is also why it is not narrated per-turn by `describeCoverage`
|
|
20869
|
+
// (policy exclusions are recorded, not announced): repeating "your dist/
|
|
20870
|
+
// went unreviewed" on every turn is how a channel gets muted. The SHARE
|
|
20871
|
+
// below is the signal that replaces the noise.
|
|
20872
|
+
//
|
|
20873
|
+
// Taken from the run, not recomputed — see context.ts `verityIgnored`.
|
|
20874
|
+
...run.verityIgnored.ignored.map((path) => ({
|
|
20875
|
+
path,
|
|
20876
|
+
reason: "verityignore",
|
|
20877
|
+
stage: "verityignore",
|
|
20878
|
+
kind: "policy"
|
|
20879
|
+
})),
|
|
20081
20880
|
// The extension allowlist. POLICY: a changed README was never going to be
|
|
20082
20881
|
// reviewed, and calling that a coverage gap would downgrade nearly every
|
|
20083
20882
|
// PASS to WARN until WARN meant nothing. Recorded so the ledger balances and
|
|
@@ -20307,7 +21106,7 @@ function agentContextFor(response, intentRepeat = 0, priorPendingFingerprints =
|
|
|
20307
21106
|
}
|
|
20308
21107
|
async function render(run) {
|
|
20309
21108
|
const { opts, globals } = run;
|
|
20310
|
-
const { actionSummary, assistantResponse, autoSeedNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run;
|
|
21109
|
+
const { actionSummary, assistantResponse, autoSeedNotice, voidedIgnoreNotice, baselineSessionId, codeDelta, contentHash, conversation, currentCommit, decision, intentRepeatCount, memory, openElsewhere, priorPendingFingerprints, response, reviewCoverage, serviceUrl, sessionIdForMemory, silenced, token, watermarkHash, watermarkIsPartial } = run;
|
|
20311
21110
|
let { iteration } = run;
|
|
20312
21111
|
const metadata = response.metadata ?? {};
|
|
20313
21112
|
const intentAmbiguity = metadata.intent_ambiguity;
|
|
@@ -20559,6 +21358,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20559
21358
|
const viewUrl = response.view_url ?? "";
|
|
20560
21359
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20561
21360
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21361
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20562
21362
|
userSummary += loginNudge + grantNudge;
|
|
20563
21363
|
emitVerdict({
|
|
20564
21364
|
proposed: "PASS",
|
|
@@ -20580,6 +21380,7 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20580
21380
|
const viewUrl = response.view_url ?? "";
|
|
20581
21381
|
if (viewUrl) userSummary += ` Report: ${viewUrl}`;
|
|
20582
21382
|
if (autoSeedNotice) userSummary = `${autoSeedNotice} ${userSummary}`;
|
|
21383
|
+
if (voidedIgnoreNotice) userSummary = `${voidedIgnoreNotice} ${userSummary}`;
|
|
20583
21384
|
userSummary += loginNudge + grantNudge;
|
|
20584
21385
|
emitVerdict({
|
|
20585
21386
|
proposed: "WARN",
|
|
@@ -20595,7 +21396,11 @@ ${YELLOW}${grantNudge.trim()}${NC}
|
|
|
20595
21396
|
}
|
|
20596
21397
|
default: {
|
|
20597
21398
|
const raw = String(decision ?? "(missing)");
|
|
20598
|
-
const msg =
|
|
21399
|
+
const msg = [
|
|
21400
|
+
voidedIgnoreNotice,
|
|
21401
|
+
autoSeedNotice,
|
|
21402
|
+
"Verity: unrecognised verdict \u2014 treating as WARN"
|
|
21403
|
+
].filter(Boolean).join(" ") + loginNudge + grantNudge;
|
|
20599
21404
|
process.stderr.write(
|
|
20600
21405
|
`Verity: server returned an unrecognised gate_decision (${raw}). Rendering WARN rather than PASS. Update the CLI: npm i -g @codacy/verity-cli
|
|
20601
21406
|
`
|
|
@@ -20672,7 +21477,7 @@ async function runAnalyze(opts, globals) {
|
|
|
20672
21477
|
}
|
|
20673
21478
|
|
|
20674
21479
|
// src/commands/baseline.ts
|
|
20675
|
-
var
|
|
21480
|
+
var import_node_fs36 = require("node:fs");
|
|
20676
21481
|
function registerBaselineCommands(program2) {
|
|
20677
21482
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
20678
21483
|
baseline.command("capture").description("Snapshot the working tree at task start (used by SessionStart hook)").option("--session-id <id>", "Session id (overrides any value from stdin)").option("--source <source>", "Lifecycle hint: startup|resume|clear|compact").action(async (opts) => {
|
|
@@ -20681,7 +21486,7 @@ function registerBaselineCommands(program2) {
|
|
|
20681
21486
|
process.chdir(repoRoot());
|
|
20682
21487
|
} catch {
|
|
20683
21488
|
}
|
|
20684
|
-
if (!(0,
|
|
21489
|
+
if (!(0, import_node_fs36.existsSync)(VERITY_DIR)) {
|
|
20685
21490
|
process.exit(0);
|
|
20686
21491
|
}
|
|
20687
21492
|
let sessionId = opts.sessionId;
|
|
@@ -20721,7 +21526,7 @@ async function readStdin() {
|
|
|
20721
21526
|
}
|
|
20722
21527
|
|
|
20723
21528
|
// src/commands/review.ts
|
|
20724
|
-
var
|
|
21529
|
+
var import_node_fs37 = require("node:fs");
|
|
20725
21530
|
function registerReviewCommand(program2) {
|
|
20726
21531
|
program2.command("review").description("Run on-demand Verity analysis (advisory, never blocks)").requiredOption("--files <paths>", "Comma-separated file list").option("--changed <paths>", "Subset of --files that were modified").option("--intent <text>", "User intent description (max 2000 chars)").option("--specs <paths>", "Comma-separated spec file paths").option("--json", "Output raw JSON response").action(async (opts) => {
|
|
20727
21532
|
const globals = program2.opts();
|
|
@@ -20740,7 +21545,7 @@ async function runReview(opts, globals) {
|
|
|
20740
21545
|
const securityFiles = filterSecurity(allFiles);
|
|
20741
21546
|
let staticResults;
|
|
20742
21547
|
if (isCodacyAvailable()) {
|
|
20743
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0,
|
|
21548
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).filter((f) => (0, import_node_fs37.existsSync)(f) || resolveFile(f) !== null);
|
|
20744
21549
|
staticResults = runCodacyAnalysis(scannable);
|
|
20745
21550
|
} else {
|
|
20746
21551
|
staticResults = {
|
|
@@ -20766,10 +21571,10 @@ async function runReview(opts, globals) {
|
|
|
20766
21571
|
const specPaths = opts.specs.split(",").map((f) => f.trim()).filter(Boolean);
|
|
20767
21572
|
specs = [];
|
|
20768
21573
|
for (const p of specPaths) {
|
|
20769
|
-
if (!(0,
|
|
21574
|
+
if (!(0, import_node_fs37.existsSync)(p)) continue;
|
|
20770
21575
|
try {
|
|
20771
|
-
const { readFileSync:
|
|
20772
|
-
const content =
|
|
21576
|
+
const { readFileSync: readFileSync23 } = await import("node:fs");
|
|
21577
|
+
const content = readFileSync23(p, "utf-8");
|
|
20773
21578
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
20774
21579
|
} catch {
|
|
20775
21580
|
}
|
|
@@ -20826,15 +21631,15 @@ async function runReview(opts, globals) {
|
|
|
20826
21631
|
}
|
|
20827
21632
|
|
|
20828
21633
|
// src/commands/guard.ts
|
|
20829
|
-
var
|
|
20830
|
-
var
|
|
21634
|
+
var import_node_fs38 = require("node:fs");
|
|
21635
|
+
var import_node_path27 = require("node:path");
|
|
20831
21636
|
var GUARD_BLOCK_CAP = 2;
|
|
20832
|
-
var GUARD_ITER_FILE = (0,
|
|
21637
|
+
var GUARD_ITER_FILE = (0, import_node_path27.join)(VERITY_DIR, ".guard-iteration");
|
|
20833
21638
|
function readPreToolUseStdin() {
|
|
20834
21639
|
const empty = { command: "", cwd: null, sessionId: null };
|
|
20835
|
-
return new Promise((
|
|
21640
|
+
return new Promise((resolve4) => {
|
|
20836
21641
|
try {
|
|
20837
|
-
if (process.stdin.isTTY) return
|
|
21642
|
+
if (process.stdin.isTTY) return resolve4(empty);
|
|
20838
21643
|
const chunks = [];
|
|
20839
21644
|
let timer;
|
|
20840
21645
|
let settled = false;
|
|
@@ -20847,7 +21652,7 @@ function readPreToolUseStdin() {
|
|
|
20847
21652
|
process.stdin.removeListener("end", onEnd);
|
|
20848
21653
|
process.stdin.removeListener("error", onError);
|
|
20849
21654
|
process.stdin.pause();
|
|
20850
|
-
|
|
21655
|
+
resolve4(value);
|
|
20851
21656
|
};
|
|
20852
21657
|
const onEnd = () => {
|
|
20853
21658
|
try {
|
|
@@ -20868,32 +21673,13 @@ function readPreToolUseStdin() {
|
|
|
20868
21673
|
process.stdin.on("error", onError);
|
|
20869
21674
|
process.stdin.resume();
|
|
20870
21675
|
} catch {
|
|
20871
|
-
|
|
21676
|
+
resolve4(empty);
|
|
20872
21677
|
}
|
|
20873
21678
|
});
|
|
20874
21679
|
}
|
|
20875
|
-
function buildCommandRe(head) {
|
|
20876
|
-
return new RegExp(`(?:^|[\\s;&|(])${head}|(?:^|[;&|(])\\s*[^\\s;&|()'"]*\\/${head}`);
|
|
20877
|
-
}
|
|
20878
|
-
var GIT_GLOBAL_OPTS = "(?:\\s+(?:-[Cc]\\s+\\S+|--?[\\w-]+(?:=\\S+)?))*";
|
|
20879
|
-
var COMMIT_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+commit(?![\\w-])`);
|
|
20880
|
-
var PUSH_RE = buildCommandRe(`git${GIT_GLOBAL_OPTS}\\s+push\\b`);
|
|
20881
|
-
var GH_PR_RE = buildCommandRe(`gh${GIT_GLOBAL_OPTS}\\s+pr\\s+create\\b`);
|
|
20882
|
-
function classifyCommand2(command, on) {
|
|
20883
|
-
let commit = false;
|
|
20884
|
-
let push = false;
|
|
20885
|
-
for (const seg of (command ?? "").split(/&&|\|\||;|\n/)) {
|
|
20886
|
-
if (/--dry-run\b/.test(seg)) continue;
|
|
20887
|
-
if (COMMIT_RE.test(seg)) commit = true;
|
|
20888
|
-
if (PUSH_RE.test(seg) || GH_PR_RE.test(seg)) push = true;
|
|
20889
|
-
}
|
|
20890
|
-
if (commit && on.includes("commit")) return "pre-commit";
|
|
20891
|
-
if (push && on.includes("push")) return "pre-push";
|
|
20892
|
-
return null;
|
|
20893
|
-
}
|
|
20894
21680
|
function readIterMap() {
|
|
20895
21681
|
try {
|
|
20896
|
-
const raw = JSON.parse((0,
|
|
21682
|
+
const raw = JSON.parse((0, import_node_fs38.readFileSync)(GUARD_ITER_FILE, "utf-8"));
|
|
20897
21683
|
if (raw && typeof raw === "object") {
|
|
20898
21684
|
if (typeof raw.moment === "string" && typeof raw.count === "number") {
|
|
20899
21685
|
return { [raw.moment]: raw.count };
|
|
@@ -20913,10 +21699,10 @@ function readIter(moment) {
|
|
|
20913
21699
|
}
|
|
20914
21700
|
function writeIter(moment, count) {
|
|
20915
21701
|
try {
|
|
20916
|
-
(0,
|
|
21702
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
20917
21703
|
const map = readIterMap();
|
|
20918
21704
|
map[moment] = count;
|
|
20919
|
-
(0,
|
|
21705
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
20920
21706
|
} catch {
|
|
20921
21707
|
}
|
|
20922
21708
|
}
|
|
@@ -20926,10 +21712,10 @@ function resetIter(moment) {
|
|
|
20926
21712
|
if (!(moment in map)) return;
|
|
20927
21713
|
delete map[moment];
|
|
20928
21714
|
if (Object.keys(map).length === 0) {
|
|
20929
|
-
if ((0,
|
|
21715
|
+
if ((0, import_node_fs38.existsSync)(GUARD_ITER_FILE)) (0, import_node_fs38.unlinkSync)(GUARD_ITER_FILE);
|
|
20930
21716
|
} else {
|
|
20931
|
-
(0,
|
|
20932
|
-
(0,
|
|
21717
|
+
(0, import_node_fs38.mkdirSync)(VERITY_DIR, { recursive: true });
|
|
21718
|
+
(0, import_node_fs38.writeFileSync)(GUARD_ITER_FILE, JSON.stringify(map));
|
|
20933
21719
|
}
|
|
20934
21720
|
} catch {
|
|
20935
21721
|
}
|
|
@@ -20944,8 +21730,14 @@ function registerGuardCommand(program2) {
|
|
|
20944
21730
|
}
|
|
20945
21731
|
});
|
|
20946
21732
|
}
|
|
20947
|
-
function
|
|
20948
|
-
return moment === "pre-commit" ?
|
|
21733
|
+
function resolveMomentRange(moment, frame, command, on) {
|
|
21734
|
+
return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
|
|
21735
|
+
}
|
|
21736
|
+
function describeRange(range) {
|
|
21737
|
+
if (range.kind === "staged") return "staged";
|
|
21738
|
+
if (range.kind === "nothing" || !range.base) return null;
|
|
21739
|
+
const base = /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
|
|
21740
|
+
return `${base}..${range.head} via ${range.via}`;
|
|
20949
21741
|
}
|
|
20950
21742
|
function matchFlagValue(command, flags) {
|
|
20951
21743
|
const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
|
|
@@ -20980,26 +21772,25 @@ function isSubstantiveIntent(text) {
|
|
|
20980
21772
|
if (SHELL_PLUMBING.test(t)) return false;
|
|
20981
21773
|
return true;
|
|
20982
21774
|
}
|
|
20983
|
-
function extractStatedIntent(moment, command) {
|
|
20984
|
-
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ??
|
|
21775
|
+
function extractStatedIntent(moment, command, pushedMessages = null) {
|
|
21776
|
+
const text = moment === "pre-commit" ? parseCommitMessage(command) : parsePrIntent(command) ?? pushedMessages;
|
|
20985
21777
|
return isSubstantiveIntent(text) ? text : null;
|
|
20986
21778
|
}
|
|
20987
|
-
function hasBlockingFinding(response) {
|
|
21779
|
+
function hasBlockingFinding(response, sentFiles) {
|
|
20988
21780
|
const findings = response.findings ?? [];
|
|
20989
|
-
|
|
21781
|
+
const sent = sentFiles ? new Set(sentFiles.map((p) => p.replace(/\\/g, "/"))) : null;
|
|
21782
|
+
return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()) && (sent === null || typeof f.file === "string" && sent.has(f.file.replace(/\\/g, "/"))));
|
|
20990
21783
|
}
|
|
20991
|
-
function buildGuardRequest(moment, files, iter, sessionId,
|
|
21784
|
+
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
20992
21785
|
const analyzable = filterAnalyzable(files);
|
|
20993
21786
|
const securityFiles = filterSecurity(files);
|
|
20994
21787
|
let staticResults;
|
|
20995
21788
|
if (isCodacyAvailable()) {
|
|
20996
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
21789
|
+
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0, import_node_fs38.existsSync)(f) ? f : resolveFile(f)).filter((f) => f !== null);
|
|
20997
21790
|
staticResults = runCodacyAnalysis(scannable);
|
|
20998
21791
|
} else {
|
|
20999
21792
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
21000
21793
|
}
|
|
21001
|
-
const codeDelta = collectCodeDelta(files);
|
|
21002
|
-
if (codeDelta.total_files === 0) return null;
|
|
21003
21794
|
const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
|
|
21004
21795
|
const requestBody = {
|
|
21005
21796
|
static_results: staticResults,
|
|
@@ -21016,9 +21807,9 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
|
21016
21807
|
iteration: iter + 1
|
|
21017
21808
|
}
|
|
21018
21809
|
};
|
|
21810
|
+
if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
|
|
21019
21811
|
const specs = discoverSpecs();
|
|
21020
21812
|
const plans = discoverPlans();
|
|
21021
|
-
const statedIntent = extractStatedIntent(moment, command);
|
|
21022
21813
|
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
21023
21814
|
const intentContext = {};
|
|
21024
21815
|
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
@@ -21028,6 +21819,39 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
|
21028
21819
|
}
|
|
21029
21820
|
return requestBody;
|
|
21030
21821
|
}
|
|
21822
|
+
function buildGuardCoverage(files, codeDelta, frame, frameRange) {
|
|
21823
|
+
const byReason = {};
|
|
21824
|
+
for (const e of codeDelta.excluded) byReason[e.reason] = (byReason[e.reason] ?? 0) + 1;
|
|
21825
|
+
return {
|
|
21826
|
+
changed_all: files.length,
|
|
21827
|
+
analyzable: filterAnalyzable(files).length,
|
|
21828
|
+
reviewable: filterReviewable(files).length,
|
|
21829
|
+
security: filterSecurity(files).length,
|
|
21830
|
+
for_review: files.length,
|
|
21831
|
+
sent: codeDelta.total_files,
|
|
21832
|
+
capped_out: codeDelta.truncated?.dropped ?? 0,
|
|
21833
|
+
excluded: codeDelta.excluded.length,
|
|
21834
|
+
excluded_by_reason: byReason,
|
|
21835
|
+
transcript_windowed: null,
|
|
21836
|
+
guard_frame: frameTelemetry(frame, frameRange)
|
|
21837
|
+
};
|
|
21838
|
+
}
|
|
21839
|
+
function coverageSummary(c) {
|
|
21840
|
+
const range = c.range ? ` @ ${c.range}` : "";
|
|
21841
|
+
return `reviewed ${c.sent.length} file(s)${range}`;
|
|
21842
|
+
}
|
|
21843
|
+
function coverageBlock(c) {
|
|
21844
|
+
const lines = [];
|
|
21845
|
+
const tree = c.root ? `${c.root}${c.linked ? " (linked worktree)" : ""}${c.branch ? ` \xB7 branch ${c.branch}` : ""}` : "(no tree resolved)";
|
|
21846
|
+
lines.push(`Reviewed (${c.moment}): ${c.sent.length} file(s)${c.range ? ` @ ${c.range}` : ""}`);
|
|
21847
|
+
lines.push(` Tree: ${tree}`);
|
|
21848
|
+
for (const f of c.sent) lines.push(` - ${f}`);
|
|
21849
|
+
if (c.excluded.length > 0) {
|
|
21850
|
+
lines.push(` Excluded (${c.excluded.length}):`);
|
|
21851
|
+
for (const e of c.excluded) lines.push(` - ${e.path} (${e.reason})`);
|
|
21852
|
+
}
|
|
21853
|
+
return lines.join("\n");
|
|
21854
|
+
}
|
|
21031
21855
|
function emitAllowNotice(userMsg, agentMsg) {
|
|
21032
21856
|
process.stdout.write(JSON.stringify({
|
|
21033
21857
|
systemMessage: userMsg,
|
|
@@ -21038,15 +21862,24 @@ function emitAllowNotice(userMsg, agentMsg) {
|
|
|
21038
21862
|
async function runGuard(opts, globals) {
|
|
21039
21863
|
const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
|
|
21040
21864
|
const { command, cwd, sessionId } = await readPreToolUseStdin();
|
|
21041
|
-
|
|
21042
|
-
try {
|
|
21043
|
-
process.chdir(cwd);
|
|
21044
|
-
} catch {
|
|
21045
|
-
}
|
|
21046
|
-
}
|
|
21047
|
-
const moment = classifyCommand2(command, on);
|
|
21865
|
+
const moment = classifyCommand(command, on);
|
|
21048
21866
|
if (!moment) process.exit(0);
|
|
21049
21867
|
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
21868
|
+
const { frame } = resolveFrame({ command, on, hookCwd: cwd });
|
|
21869
|
+
if (frame.refusal || !frame.worktreeRoot) {
|
|
21870
|
+
logEvent("guard_frame", { moment, ...frameTelemetry(frame, null) });
|
|
21871
|
+
if ((frame.refusal ?? "").startsWith("anchor:")) process.exit(0);
|
|
21872
|
+
emitAllowNotice(
|
|
21873
|
+
`\u26A0 Verity ${moment}: could not resolve the tree this ${verb} targets \u2014 ${verb}ed WITHOUT review`,
|
|
21874
|
+
`Verity ${moment}: the target tree could not be resolved (${frame.refusal}); the ${verb} was allowed WITHOUT a Verity review.`
|
|
21875
|
+
);
|
|
21876
|
+
}
|
|
21877
|
+
try {
|
|
21878
|
+
process.chdir(frame.worktreeRoot);
|
|
21879
|
+
} catch {
|
|
21880
|
+
process.exit(0);
|
|
21881
|
+
}
|
|
21882
|
+
_resetRepoRoot();
|
|
21050
21883
|
const iter = readIter(moment);
|
|
21051
21884
|
if (iter >= GUARD_BLOCK_CAP) {
|
|
21052
21885
|
resetIter(moment);
|
|
@@ -21055,13 +21888,39 @@ async function runGuard(opts, globals) {
|
|
|
21055
21888
|
`Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
|
|
21056
21889
|
);
|
|
21057
21890
|
}
|
|
21058
|
-
const
|
|
21891
|
+
const range = resolveMomentRange(moment, frame, command, on);
|
|
21892
|
+
const files = rangeFiles(frame, range);
|
|
21059
21893
|
if (files.length === 0) process.exit(0);
|
|
21060
21894
|
const tokenResult = await resolveToken(globals.token);
|
|
21061
21895
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
21062
21896
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
21063
|
-
|
|
21064
|
-
|
|
21897
|
+
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
21898
|
+
const codeDelta = collectCodeDelta(files);
|
|
21899
|
+
if (codeDelta.total_files === 0) process.exit(0);
|
|
21900
|
+
const statedIntent = extractStatedIntent(
|
|
21901
|
+
moment,
|
|
21902
|
+
command,
|
|
21903
|
+
moment === "pre-push" ? rangeMessages(frame, range) || null : null
|
|
21904
|
+
);
|
|
21905
|
+
const requestBody = buildGuardRequest(
|
|
21906
|
+
moment,
|
|
21907
|
+
files,
|
|
21908
|
+
codeDelta,
|
|
21909
|
+
iter,
|
|
21910
|
+
sessionId,
|
|
21911
|
+
statedIntent,
|
|
21912
|
+
buildGuardCoverage(files, codeDelta, frame, range)
|
|
21913
|
+
);
|
|
21914
|
+
const coverage = {
|
|
21915
|
+
moment,
|
|
21916
|
+
root: frame.worktreeRoot,
|
|
21917
|
+
branch: frame.branch,
|
|
21918
|
+
linked: frame.isLinkedWorktree,
|
|
21919
|
+
range: describeRange(range),
|
|
21920
|
+
sent: codeDelta.files.map((f) => f.path),
|
|
21921
|
+
excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
|
|
21922
|
+
};
|
|
21923
|
+
logToFileOnly(coverageBlock(coverage));
|
|
21065
21924
|
const result = await analyzeRequest({
|
|
21066
21925
|
serviceUrl: urlResult.data,
|
|
21067
21926
|
token: tokenResult.data.token,
|
|
@@ -21082,31 +21941,39 @@ async function runGuard(opts, globals) {
|
|
|
21082
21941
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
21083
21942
|
const viewUrl = response.view_url ?? "";
|
|
21084
21943
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21085
|
-
|
|
21944
|
+
const covLine = coverageSummary(coverage);
|
|
21945
|
+
const covDetail = coverageBlock(coverage);
|
|
21946
|
+
if (decision === "FAIL" && hasBlockingFinding(response, codeDelta.files.map((f) => f.path))) {
|
|
21086
21947
|
writeIter(moment, iter + 1);
|
|
21087
|
-
writeBlockMessage(moment, response);
|
|
21948
|
+
writeBlockMessage(moment, response, covDetail);
|
|
21088
21949
|
process.exit(2);
|
|
21089
21950
|
}
|
|
21090
21951
|
resetIter(moment);
|
|
21091
21952
|
if (decision === "FAIL") {
|
|
21092
21953
|
const narrative = response.assessment?.narrative ?? "";
|
|
21093
21954
|
emitAllowNotice(
|
|
21094
|
-
`\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding${link}`,
|
|
21095
|
-
`Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
|
|
21955
|
+
`\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding (${covLine})${link}`,
|
|
21956
|
+
`Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
|
|
21957
|
+
${covDetail}${viewUrl ? `
|
|
21958
|
+
Report: ${viewUrl}` : ""}`
|
|
21096
21959
|
);
|
|
21097
21960
|
}
|
|
21098
21961
|
if (decision === "WARN") {
|
|
21099
21962
|
emitAllowNotice(
|
|
21100
|
-
`\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
|
|
21101
|
-
`Verity ${moment} review: WARN (proceeding)
|
|
21963
|
+
`\u26A0 Verity ${moment}: WARN \u2014 proceeding (${covLine})${link}`,
|
|
21964
|
+
`Verity ${moment} review: WARN (proceeding).
|
|
21965
|
+
${covDetail}${viewUrl ? `
|
|
21966
|
+
Report: ${viewUrl}` : ""}`
|
|
21102
21967
|
);
|
|
21103
21968
|
}
|
|
21104
21969
|
emitAllowNotice(
|
|
21105
|
-
`\u2713 Verity ${moment}: PASS${link}`,
|
|
21106
|
-
`Verity ${moment} review: PASS
|
|
21970
|
+
`\u2713 Verity ${moment}: PASS (${covLine})${link}`,
|
|
21971
|
+
`Verity ${moment} review: PASS.
|
|
21972
|
+
${covDetail}${viewUrl ? `
|
|
21973
|
+
Report: ${viewUrl}` : ""}`
|
|
21107
21974
|
);
|
|
21108
21975
|
}
|
|
21109
|
-
function writeBlockMessage(moment, response) {
|
|
21976
|
+
function writeBlockMessage(moment, response, covDetail) {
|
|
21110
21977
|
const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
|
|
21111
21978
|
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
21112
21979
|
const assessment = response.assessment;
|
|
@@ -21136,6 +22003,9 @@ function writeBlockMessage(moment, response) {
|
|
|
21136
22003
|
`);
|
|
21137
22004
|
}
|
|
21138
22005
|
}
|
|
22006
|
+
process.stderr.write(`${DIM}${covDetail}${NC}
|
|
22007
|
+
|
|
22008
|
+
`);
|
|
21139
22009
|
if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
|
|
21140
22010
|
|
|
21141
22011
|
`);
|
|
@@ -21143,17 +22013,109 @@ function writeBlockMessage(moment, response) {
|
|
|
21143
22013
|
`);
|
|
21144
22014
|
}
|
|
21145
22015
|
|
|
22016
|
+
// src/commands/ignore.ts
|
|
22017
|
+
function registerIgnoreCommand(program2) {
|
|
22018
|
+
const ignore = program2.command("ignore").description("Declare the next turn (or a short window) as housekeeping \u2014 no review needed").option("--turn", "Cover the next turn only (the default)").option("--for <duration>", "Cover a window: 30m, 45s, 1h (max 60m)").option("--reason <reason>", "Why this window needs no review \u2014 required, and recorded").option("--agent", "Mark the declaration as agent-invoked (a ledger label, not a permission)").option("--session-id <id>", "Session id (defaults to $CLAUDE_SESSION_ID)").option("--json", "Output raw JSON").action(async (opts) => {
|
|
22019
|
+
const globals = program2.opts();
|
|
22020
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22021
|
+
const reason = opts.reason?.trim();
|
|
22022
|
+
if (!reason) {
|
|
22023
|
+
printError(
|
|
22024
|
+
'A reason is required: verity ignore --turn --reason "pulling latest before starting".\nIt is recorded with the declaration and is the only trace a skipped turn leaves.'
|
|
22025
|
+
);
|
|
22026
|
+
process.exit(1);
|
|
22027
|
+
}
|
|
22028
|
+
if (opts.for && opts.turn) {
|
|
22029
|
+
printError("Use either --turn or --for, not both \u2014 they are two different windows.");
|
|
22030
|
+
process.exit(1);
|
|
22031
|
+
}
|
|
22032
|
+
let scope2 = "turn";
|
|
22033
|
+
let ttl = TURN_FUSE_SECONDS;
|
|
22034
|
+
if (opts.for) {
|
|
22035
|
+
const parsed = parseDuration(opts.for);
|
|
22036
|
+
if (!parsed.ok) {
|
|
22037
|
+
printError(parsed.error);
|
|
22038
|
+
process.exit(1);
|
|
22039
|
+
}
|
|
22040
|
+
scope2 = "window";
|
|
22041
|
+
ttl = parsed.seconds;
|
|
22042
|
+
}
|
|
22043
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22044
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22045
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22046
|
+
const keys = ignoreStateKeys(token, sessionId);
|
|
22047
|
+
const existing = resolveIgnoreState(keys);
|
|
22048
|
+
const spent = existing?.state.spent ?? 0;
|
|
22049
|
+
const writeKey = existing?.key ?? keys[0];
|
|
22050
|
+
if (spent >= IGNORE_BUDGET) {
|
|
22051
|
+
const msg = `Ignore budget spent for this session (${spent}/${IGNORE_BUDGET} declarations). The next turn will be reviewed normally. The budget is per session \u2014 it is what stops an ignore from becoming a standing mute.`;
|
|
22052
|
+
if (opts.json) {
|
|
22053
|
+
printJson({ declared: false, reason_refused: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22054
|
+
} else {
|
|
22055
|
+
printWarn(msg);
|
|
22056
|
+
}
|
|
22057
|
+
logEvent("ignore_refused", { why: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22058
|
+
process.exit(0);
|
|
22059
|
+
}
|
|
22060
|
+
const declaration = {
|
|
22061
|
+
scope: scope2,
|
|
22062
|
+
origin: opts.agent ? "agent" : "user",
|
|
22063
|
+
reason,
|
|
22064
|
+
at: now,
|
|
22065
|
+
expires: now + ttl
|
|
22066
|
+
};
|
|
22067
|
+
writeIgnoreState({ v: 1, active: declaration, spent: spent + 1 }, writeKey);
|
|
22068
|
+
logEvent("ignore_declared", {
|
|
22069
|
+
scope: scope2,
|
|
22070
|
+
origin: declaration.origin,
|
|
22071
|
+
ttl_seconds: ttl,
|
|
22072
|
+
spent: spent + 1,
|
|
22073
|
+
budget: IGNORE_BUDGET
|
|
22074
|
+
});
|
|
22075
|
+
if (opts.json) {
|
|
22076
|
+
printJson({
|
|
22077
|
+
declared: true,
|
|
22078
|
+
scope: scope2,
|
|
22079
|
+
origin: declaration.origin,
|
|
22080
|
+
reason,
|
|
22081
|
+
expires_at: new Date(declaration.expires * 1e3).toISOString(),
|
|
22082
|
+
spent: spent + 1,
|
|
22083
|
+
budget: IGNORE_BUDGET
|
|
22084
|
+
});
|
|
22085
|
+
return;
|
|
22086
|
+
}
|
|
22087
|
+
const window = scope2 === "turn" ? "the next turn" : `the next ${describeRemaining(declaration, now).replace(" left", "")}`;
|
|
22088
|
+
printInfo(`Verity will skip ${window} \u2014 "${reason}" (${spent + 1}/${IGNORE_BUDGET} this session).`);
|
|
22089
|
+
printInfo("It covers turns that author nothing. If anything is written, the declaration voids and the review runs.");
|
|
22090
|
+
});
|
|
22091
|
+
ignore.command("clear").description("Cancel the active declaration (the spent budget is not refunded)").option("--session-id <id>", "Session id (defaults to $CLAUDE_SESSION_ID)").action(async (opts) => {
|
|
22092
|
+
const globals = program2.opts();
|
|
22093
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22094
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22095
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22096
|
+
const found = resolveIgnoreState(ignoreStateKeys(token, sessionId));
|
|
22097
|
+
const active = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
22098
|
+
if (!found || !active) {
|
|
22099
|
+
printInfo("No active ignore declaration.");
|
|
22100
|
+
return;
|
|
22101
|
+
}
|
|
22102
|
+
clearActiveDeclaration(found.key);
|
|
22103
|
+
logEvent("ignore_cleared", { scope: active.scope, origin: active.origin });
|
|
22104
|
+
printInfo(`Cleared: "${active.reason}". The next turn will be reviewed normally.`);
|
|
22105
|
+
});
|
|
22106
|
+
}
|
|
22107
|
+
|
|
21146
22108
|
// src/commands/init.ts
|
|
21147
|
-
var
|
|
22109
|
+
var import_node_fs40 = require("node:fs");
|
|
21148
22110
|
var import_promises13 = require("node:fs/promises");
|
|
21149
|
-
var
|
|
21150
|
-
var
|
|
22111
|
+
var import_node_path29 = require("node:path");
|
|
22112
|
+
var import_node_child_process11 = require("node:child_process");
|
|
21151
22113
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
21152
22114
|
|
|
21153
22115
|
// src/commands/migrate.ts
|
|
21154
|
-
var
|
|
21155
|
-
var
|
|
21156
|
-
var
|
|
22116
|
+
var import_node_fs39 = require("node:fs");
|
|
22117
|
+
var import_node_path28 = require("node:path");
|
|
22118
|
+
var import_node_child_process10 = require("node:child_process");
|
|
21157
22119
|
|
|
21158
22120
|
// src/lib/telemetry.ts
|
|
21159
22121
|
var import_promises12 = require("node:fs/promises");
|
|
@@ -21248,11 +22210,11 @@ async function uninstallTelemetry() {
|
|
|
21248
22210
|
// src/commands/migrate.ts
|
|
21249
22211
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
21250
22212
|
function defaultNpmRemover(pkg) {
|
|
21251
|
-
(0,
|
|
22213
|
+
(0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
21252
22214
|
}
|
|
21253
22215
|
function isGitTracked(cwd, relPath) {
|
|
21254
22216
|
try {
|
|
21255
|
-
(0,
|
|
22217
|
+
(0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
21256
22218
|
return true;
|
|
21257
22219
|
} catch {
|
|
21258
22220
|
return false;
|
|
@@ -21260,7 +22222,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
21260
22222
|
}
|
|
21261
22223
|
function isGitRepo(cwd) {
|
|
21262
22224
|
try {
|
|
21263
|
-
(0,
|
|
22225
|
+
(0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
21264
22226
|
return true;
|
|
21265
22227
|
} catch {
|
|
21266
22228
|
return false;
|
|
@@ -21281,12 +22243,12 @@ async function runMigration(opts = {}) {
|
|
|
21281
22243
|
return { actions, migrated: actions.length > 0 };
|
|
21282
22244
|
}
|
|
21283
22245
|
function migrateProjectDir(root, actions) {
|
|
21284
|
-
const gateDir = (0,
|
|
21285
|
-
const verityDir = (0,
|
|
21286
|
-
if ((0,
|
|
22246
|
+
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22247
|
+
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22248
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) {
|
|
21287
22249
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
21288
22250
|
}
|
|
21289
|
-
if ((0,
|
|
22251
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
21290
22252
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
21291
22253
|
}
|
|
21292
22254
|
return false;
|
|
@@ -21300,20 +22262,20 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
21300
22262
|
);
|
|
21301
22263
|
}
|
|
21302
22264
|
try {
|
|
21303
|
-
(0,
|
|
22265
|
+
(0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
21304
22266
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
21305
22267
|
moved = true;
|
|
21306
22268
|
} catch {
|
|
21307
22269
|
}
|
|
21308
22270
|
}
|
|
21309
22271
|
if (moved) {
|
|
21310
|
-
if ((0,
|
|
22272
|
+
if ((0, import_node_fs39.existsSync)(gateDir)) {
|
|
21311
22273
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
21312
22274
|
if (carried > 0) {
|
|
21313
22275
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
21314
22276
|
}
|
|
21315
22277
|
try {
|
|
21316
|
-
(0,
|
|
22278
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21317
22279
|
} catch {
|
|
21318
22280
|
}
|
|
21319
22281
|
}
|
|
@@ -21329,18 +22291,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
21329
22291
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
21330
22292
|
}
|
|
21331
22293
|
try {
|
|
21332
|
-
(0,
|
|
22294
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21333
22295
|
} catch {
|
|
21334
22296
|
}
|
|
21335
22297
|
return carried > 0;
|
|
21336
22298
|
}
|
|
21337
22299
|
function migrateGlobalCredentials(home, actions) {
|
|
21338
22300
|
if (!home) return;
|
|
21339
|
-
const gateCreds = (0,
|
|
21340
|
-
const verityCreds = (0,
|
|
21341
|
-
if (!(0,
|
|
21342
|
-
if (!(0,
|
|
21343
|
-
(0,
|
|
22301
|
+
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
22302
|
+
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
22303
|
+
if (!(0, import_node_fs39.existsSync)(gateCreds)) return;
|
|
22304
|
+
if (!(0, import_node_fs39.existsSync)(verityCreds)) {
|
|
22305
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
21344
22306
|
moveFile(gateCreds, verityCreds);
|
|
21345
22307
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
21346
22308
|
return;
|
|
@@ -21362,8 +22324,8 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21362
22324
|
}
|
|
21363
22325
|
}
|
|
21364
22326
|
async function migrateClaudeMd(root, actions) {
|
|
21365
|
-
const claudeMd = (0,
|
|
21366
|
-
const hadLegacyBlock = (0,
|
|
22327
|
+
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22328
|
+
const hadLegacyBlock = (0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21367
22329
|
if (!hadLegacyBlock) return;
|
|
21368
22330
|
try {
|
|
21369
22331
|
await ensureClaudeMdPointer(root);
|
|
@@ -21373,13 +22335,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21373
22335
|
}
|
|
21374
22336
|
}
|
|
21375
22337
|
function migrateStandardFile(root, actions) {
|
|
21376
|
-
const gateMd = (0,
|
|
21377
|
-
const verityMd = (0,
|
|
21378
|
-
if (!(0,
|
|
22338
|
+
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
22339
|
+
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
22340
|
+
if (!(0, import_node_fs39.existsSync)(gateMd) || (0, import_node_fs39.existsSync)(verityMd)) return;
|
|
21379
22341
|
let moved = false;
|
|
21380
22342
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21381
22343
|
try {
|
|
21382
|
-
(0,
|
|
22344
|
+
(0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
21383
22345
|
moved = true;
|
|
21384
22346
|
} catch {
|
|
21385
22347
|
}
|
|
@@ -21387,12 +22349,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21387
22349
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21388
22350
|
const content = readFileSyncSafe(verityMd);
|
|
21389
22351
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21390
|
-
if (refreshed !== content) (0,
|
|
22352
|
+
if (refreshed !== content) (0, import_node_fs39.writeFileSync)(verityMd, refreshed);
|
|
21391
22353
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21392
22354
|
}
|
|
21393
22355
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21394
|
-
const file = (0,
|
|
21395
|
-
if (!(0,
|
|
22356
|
+
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
22357
|
+
if (!(0, import_node_fs39.existsSync)(file)) return;
|
|
21396
22358
|
let settings;
|
|
21397
22359
|
try {
|
|
21398
22360
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21440,21 +22402,21 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21440
22402
|
}
|
|
21441
22403
|
if (toAppend.length > 0) {
|
|
21442
22404
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21443
|
-
(0,
|
|
22405
|
+
(0, import_node_fs39.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
21444
22406
|
}
|
|
21445
|
-
(0,
|
|
22407
|
+
(0, import_node_fs39.rmSync)(gateCreds, { force: true });
|
|
21446
22408
|
return toAppend.length;
|
|
21447
22409
|
}
|
|
21448
22410
|
function readFileSyncSafe(path) {
|
|
21449
22411
|
try {
|
|
21450
|
-
return (0,
|
|
22412
|
+
return (0, import_node_fs39.readFileSync)(path, "utf-8");
|
|
21451
22413
|
} catch {
|
|
21452
22414
|
return "";
|
|
21453
22415
|
}
|
|
21454
22416
|
}
|
|
21455
22417
|
function hasStagedChanges(root) {
|
|
21456
22418
|
try {
|
|
21457
|
-
(0,
|
|
22419
|
+
(0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
21458
22420
|
return false;
|
|
21459
22421
|
} catch {
|
|
21460
22422
|
return true;
|
|
@@ -21462,35 +22424,35 @@ function hasStagedChanges(root) {
|
|
|
21462
22424
|
}
|
|
21463
22425
|
function moveDir(from, to) {
|
|
21464
22426
|
try {
|
|
21465
|
-
(0,
|
|
22427
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21466
22428
|
} catch (err) {
|
|
21467
22429
|
if (err.code !== "EXDEV") throw err;
|
|
21468
|
-
(0,
|
|
21469
|
-
(0,
|
|
22430
|
+
(0, import_node_fs39.cpSync)(from, to, { recursive: true });
|
|
22431
|
+
(0, import_node_fs39.rmSync)(from, { recursive: true, force: true });
|
|
21470
22432
|
}
|
|
21471
22433
|
}
|
|
21472
22434
|
function moveFile(from, to) {
|
|
21473
22435
|
try {
|
|
21474
|
-
(0,
|
|
22436
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21475
22437
|
} catch (err) {
|
|
21476
22438
|
if (err.code !== "EXDEV") throw err;
|
|
21477
|
-
(0,
|
|
21478
|
-
(0,
|
|
22439
|
+
(0, import_node_fs39.cpSync)(from, to);
|
|
22440
|
+
(0, import_node_fs39.rmSync)(from, { force: true });
|
|
21479
22441
|
}
|
|
21480
22442
|
}
|
|
21481
22443
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21482
22444
|
let copied = 0;
|
|
21483
22445
|
const walk = (relDir) => {
|
|
21484
|
-
const srcDir = (0,
|
|
21485
|
-
for (const entry of (0,
|
|
21486
|
-
const rel = relDir ? (0,
|
|
21487
|
-
const src = (0,
|
|
21488
|
-
const dest = (0,
|
|
21489
|
-
if ((0,
|
|
22446
|
+
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
22447
|
+
for (const entry of (0, import_node_fs39.readdirSync)(srcDir)) {
|
|
22448
|
+
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
22449
|
+
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
22450
|
+
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
22451
|
+
if ((0, import_node_fs39.statSync)(src).isDirectory()) {
|
|
21490
22452
|
walk(rel);
|
|
21491
|
-
} else if (!(0,
|
|
21492
|
-
(0,
|
|
21493
|
-
(0,
|
|
22453
|
+
} else if (!(0, import_node_fs39.existsSync)(dest)) {
|
|
22454
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22455
|
+
(0, import_node_fs39.cpSync)(src, dest);
|
|
21494
22456
|
copied++;
|
|
21495
22457
|
}
|
|
21496
22458
|
}
|
|
@@ -21499,22 +22461,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21499
22461
|
return copied;
|
|
21500
22462
|
}
|
|
21501
22463
|
async function needsMigration(root = repoRoot()) {
|
|
21502
|
-
const gateDir = (0,
|
|
21503
|
-
const verityDir = (0,
|
|
21504
|
-
if ((0,
|
|
21505
|
-
if ((0,
|
|
21506
|
-
if ((0,
|
|
22464
|
+
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22465
|
+
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22466
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) return true;
|
|
22467
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
22468
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(gateDir, "credentials")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(verityDir, "credentials"))) {
|
|
21507
22469
|
return true;
|
|
21508
22470
|
}
|
|
21509
|
-
if ((0,
|
|
22471
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(gateDir, "memory")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(verityDir, "memory"))) {
|
|
21510
22472
|
return true;
|
|
21511
22473
|
}
|
|
21512
22474
|
}
|
|
21513
|
-
const claudeMd = (0,
|
|
21514
|
-
if ((0,
|
|
22475
|
+
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22476
|
+
if ((0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21515
22477
|
return true;
|
|
21516
22478
|
}
|
|
21517
|
-
if ((0,
|
|
22479
|
+
if ((0, import_node_fs39.existsSync)((0, import_node_path28.join)(root, "GATE.md")) && !(0, import_node_fs39.existsSync)((0, import_node_path28.join)(root, "VERITY.md"))) {
|
|
21518
22480
|
return true;
|
|
21519
22481
|
}
|
|
21520
22482
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -21607,7 +22569,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
21607
22569
|
}
|
|
21608
22570
|
let remote = "";
|
|
21609
22571
|
try {
|
|
21610
|
-
remote = (0,
|
|
22572
|
+
remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
21611
22573
|
} catch {
|
|
21612
22574
|
}
|
|
21613
22575
|
if (!healed) {
|
|
@@ -21650,15 +22612,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
21650
22612
|
}
|
|
21651
22613
|
function resolveDataDir() {
|
|
21652
22614
|
const candidates = [
|
|
21653
|
-
(0,
|
|
22615
|
+
(0, import_node_path29.join)(__dirname, "..", "data"),
|
|
21654
22616
|
// installed: node_modules/@codacy/verity-cli/data
|
|
21655
|
-
(0,
|
|
22617
|
+
(0, import_node_path29.join)(__dirname, "..", "..", "data"),
|
|
21656
22618
|
// edge case: nested resolution
|
|
21657
|
-
(0,
|
|
22619
|
+
(0, import_node_path29.join)(process.cwd(), "cli", "data")
|
|
21658
22620
|
// local dev: running from repo root
|
|
21659
22621
|
];
|
|
21660
22622
|
for (const candidate of candidates) {
|
|
21661
|
-
if ((0,
|
|
22623
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
21662
22624
|
return candidate;
|
|
21663
22625
|
}
|
|
21664
22626
|
}
|
|
@@ -21674,7 +22636,7 @@ function registerInitCommand(program2) {
|
|
|
21674
22636
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
21675
22637
|
const force = opts.force ?? false;
|
|
21676
22638
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
21677
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22639
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs40.existsSync)(m));
|
|
21678
22640
|
if (!isProject) {
|
|
21679
22641
|
printError("No project detected in the current directory.");
|
|
21680
22642
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -21702,30 +22664,30 @@ function registerInitCommand(program2) {
|
|
|
21702
22664
|
}
|
|
21703
22665
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
21704
22666
|
try {
|
|
21705
|
-
const gitVersion = (0,
|
|
22667
|
+
const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
21706
22668
|
printInfo(` ${gitVersion} \u2713`);
|
|
21707
22669
|
} catch {
|
|
21708
22670
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
21709
22671
|
process.exit(1);
|
|
21710
22672
|
}
|
|
21711
22673
|
try {
|
|
21712
|
-
(0,
|
|
22674
|
+
(0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
|
|
21713
22675
|
printInfo(" Claude Code \u2713");
|
|
21714
22676
|
} catch {
|
|
21715
22677
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
21716
22678
|
}
|
|
21717
22679
|
try {
|
|
21718
|
-
(0,
|
|
22680
|
+
(0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
21719
22681
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
21720
22682
|
} catch {
|
|
21721
22683
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
21722
22684
|
try {
|
|
21723
|
-
(0,
|
|
22685
|
+
(0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
21724
22686
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
21725
22687
|
} catch {
|
|
21726
22688
|
try {
|
|
21727
22689
|
printWarn(" Retrying with sudo...");
|
|
21728
|
-
(0,
|
|
22690
|
+
(0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
21729
22691
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
21730
22692
|
} catch {
|
|
21731
22693
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -21737,21 +22699,21 @@ function registerInitCommand(program2) {
|
|
|
21737
22699
|
console.log("");
|
|
21738
22700
|
printInfo("Installing skills...");
|
|
21739
22701
|
const dataDir = resolveDataDir();
|
|
21740
|
-
const skillsSource = (0,
|
|
22702
|
+
const skillsSource = (0, import_node_path29.join)(dataDir, "skills");
|
|
21741
22703
|
const skillsDest = ".claude/skills";
|
|
21742
22704
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
21743
22705
|
let skillsInstalled = 0;
|
|
21744
22706
|
for (const skill of skills) {
|
|
21745
|
-
const src = (0,
|
|
21746
|
-
const dest = (0,
|
|
21747
|
-
if (!(0,
|
|
22707
|
+
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22708
|
+
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22709
|
+
if (!(0, import_node_fs40.existsSync)(src)) {
|
|
21748
22710
|
printWarn(` Skill data not found: ${skill}`);
|
|
21749
22711
|
continue;
|
|
21750
22712
|
}
|
|
21751
|
-
if ((0,
|
|
21752
|
-
const srcSkill = (0,
|
|
21753
|
-
const destSkill = (0,
|
|
21754
|
-
if ((0,
|
|
22713
|
+
if ((0, import_node_fs40.existsSync)(dest) && !force) {
|
|
22714
|
+
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22715
|
+
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22716
|
+
if ((0, import_node_fs40.existsSync)(destSkill)) {
|
|
21755
22717
|
try {
|
|
21756
22718
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
21757
22719
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -21794,7 +22756,7 @@ function registerInitCommand(program2) {
|
|
|
21794
22756
|
} catch (err) {
|
|
21795
22757
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
21796
22758
|
}
|
|
21797
|
-
const globalVerityDir = (0,
|
|
22759
|
+
const globalVerityDir = (0, import_node_path29.join)(process.env.HOME ?? "", ".verity");
|
|
21798
22760
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
21799
22761
|
console.log("");
|
|
21800
22762
|
try {
|
|
@@ -21829,8 +22791,8 @@ function registerInitCommand(program2) {
|
|
|
21829
22791
|
}
|
|
21830
22792
|
|
|
21831
22793
|
// src/commands/uninstall.ts
|
|
21832
|
-
var
|
|
21833
|
-
var
|
|
22794
|
+
var import_node_fs41 = require("node:fs");
|
|
22795
|
+
var import_node_path30 = require("node:path");
|
|
21834
22796
|
var SKILL_NAMES = [
|
|
21835
22797
|
"verity-setup",
|
|
21836
22798
|
"verity-analyze",
|
|
@@ -21849,11 +22811,11 @@ function registerUninstallCommand(program2) {
|
|
|
21849
22811
|
const actions = [];
|
|
21850
22812
|
const skillsRoot = projectPath(".claude/skills");
|
|
21851
22813
|
for (const name of SKILL_NAMES) {
|
|
21852
|
-
const dir = (0,
|
|
21853
|
-
if ((0,
|
|
22814
|
+
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22815
|
+
if ((0, import_node_fs41.existsSync)(dir)) {
|
|
21854
22816
|
actions.push({
|
|
21855
22817
|
label: `Remove .claude/skills/${name}/`,
|
|
21856
|
-
apply: () => (0,
|
|
22818
|
+
apply: () => (0, import_node_fs41.rmSync)(dir, { recursive: true, force: true })
|
|
21857
22819
|
});
|
|
21858
22820
|
}
|
|
21859
22821
|
}
|
|
@@ -21867,24 +22829,24 @@ function registerUninstallCommand(program2) {
|
|
|
21867
22829
|
});
|
|
21868
22830
|
}
|
|
21869
22831
|
const verityDir = projectPath(VERITY_DIR);
|
|
21870
|
-
if ((0,
|
|
22832
|
+
if ((0, import_node_fs41.existsSync)(verityDir)) {
|
|
21871
22833
|
actions.push({
|
|
21872
22834
|
label: `Remove ${VERITY_DIR}/`,
|
|
21873
|
-
apply: () => (0,
|
|
22835
|
+
apply: () => (0, import_node_fs41.rmSync)(verityDir, { recursive: true, force: true })
|
|
21874
22836
|
});
|
|
21875
22837
|
}
|
|
21876
22838
|
if (!keepVerityMd) {
|
|
21877
22839
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
21878
|
-
if ((0,
|
|
22840
|
+
if ((0, import_node_fs41.existsSync)(verityMd)) {
|
|
21879
22841
|
actions.push({
|
|
21880
22842
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
21881
|
-
apply: () => (0,
|
|
22843
|
+
apply: () => (0, import_node_fs41.rmSync)(verityMd, { force: true })
|
|
21882
22844
|
});
|
|
21883
22845
|
}
|
|
21884
22846
|
}
|
|
21885
22847
|
const cleanupEmptyDir = (path) => {
|
|
21886
|
-
if ((0,
|
|
21887
|
-
(0,
|
|
22848
|
+
if ((0, import_node_fs41.existsSync)(path) && (0, import_node_fs41.statSync)(path).isDirectory() && (0, import_node_fs41.readdirSync)(path).length === 0) {
|
|
22849
|
+
(0, import_node_fs41.rmdirSync)(path);
|
|
21888
22850
|
}
|
|
21889
22851
|
};
|
|
21890
22852
|
actions.push({
|
|
@@ -21895,11 +22857,11 @@ function registerUninstallCommand(program2) {
|
|
|
21895
22857
|
}
|
|
21896
22858
|
});
|
|
21897
22859
|
const home = process.env.HOME ?? "";
|
|
21898
|
-
const globalVerityDir = (0,
|
|
21899
|
-
if (purgeGlobal && (0,
|
|
22860
|
+
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22861
|
+
if (purgeGlobal && (0, import_node_fs41.existsSync)(globalVerityDir)) {
|
|
21900
22862
|
actions.push({
|
|
21901
22863
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
21902
|
-
apply: () => (0,
|
|
22864
|
+
apply: () => (0, import_node_fs41.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
21903
22865
|
});
|
|
21904
22866
|
}
|
|
21905
22867
|
if (actions.length === 0) {
|
|
@@ -22093,8 +23055,8 @@ function registerTaskCommands(program2) {
|
|
|
22093
23055
|
}
|
|
22094
23056
|
|
|
22095
23057
|
// src/commands/reset.ts
|
|
22096
|
-
var
|
|
22097
|
-
var
|
|
23058
|
+
var import_node_fs42 = require("node:fs");
|
|
23059
|
+
var import_node_path31 = require("node:path");
|
|
22098
23060
|
function registerResetCommand(program2) {
|
|
22099
23061
|
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) => {
|
|
22100
23062
|
const globals = program2.opts();
|
|
@@ -22131,11 +23093,11 @@ function registerResetCommand(program2) {
|
|
|
22131
23093
|
}
|
|
22132
23094
|
const cacheDir = projectPath(CACHE_DIR);
|
|
22133
23095
|
let purged = 0;
|
|
22134
|
-
if ((0,
|
|
22135
|
-
for (const entry of (0,
|
|
23096
|
+
if ((0, import_node_fs42.existsSync)(cacheDir)) {
|
|
23097
|
+
for (const entry of (0, import_node_fs42.readdirSync)(cacheDir)) {
|
|
22136
23098
|
if (entry.startsWith("pending-")) {
|
|
22137
23099
|
try {
|
|
22138
|
-
(0,
|
|
23100
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
22139
23101
|
purged++;
|
|
22140
23102
|
} catch {
|
|
22141
23103
|
}
|
|
@@ -22150,19 +23112,19 @@ function registerResetCommand(program2) {
|
|
|
22150
23112
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
22151
23113
|
];
|
|
22152
23114
|
for (const file of filesToClear) {
|
|
22153
|
-
if ((0,
|
|
23115
|
+
if ((0, import_node_fs42.existsSync)(file)) {
|
|
22154
23116
|
try {
|
|
22155
|
-
(0,
|
|
23117
|
+
(0, import_node_fs42.writeFileSync)(file, "");
|
|
22156
23118
|
} catch {
|
|
22157
23119
|
}
|
|
22158
23120
|
}
|
|
22159
23121
|
}
|
|
22160
23122
|
if (opts.all) {
|
|
22161
23123
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
22162
|
-
if ((0,
|
|
22163
|
-
for (const entry of (0,
|
|
23124
|
+
if ((0, import_node_fs42.existsSync)(logsDir)) {
|
|
23125
|
+
for (const entry of (0, import_node_fs42.readdirSync)(logsDir)) {
|
|
22164
23126
|
try {
|
|
22165
|
-
(0,
|
|
23127
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
22166
23128
|
} catch {
|
|
22167
23129
|
}
|
|
22168
23130
|
}
|
|
@@ -22470,8 +23432,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22470
23432
|
}
|
|
22471
23433
|
|
|
22472
23434
|
// src/cli.ts
|
|
22473
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.30.
|
|
22474
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.
|
|
23435
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.30.1-experimental.cbeb697").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) => {
|
|
23436
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.1-experimental.cbeb697");
|
|
22475
23437
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22476
23438
|
try {
|
|
22477
23439
|
await foldLegacyLocalCredential();
|
|
@@ -22494,6 +23456,7 @@ registerAnalyzeCommand(program);
|
|
|
22494
23456
|
registerBaselineCommands(program);
|
|
22495
23457
|
registerReviewCommand(program);
|
|
22496
23458
|
registerGuardCommand(program);
|
|
23459
|
+
registerIgnoreCommand(program);
|
|
22497
23460
|
registerInitCommand(program);
|
|
22498
23461
|
registerUninstallCommand(program);
|
|
22499
23462
|
registerTaskCommands(program);
|