@codacy/verity-cli 0.30.0 → 0.30.1-experimental.67b8921
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 +1675 -713
- 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.67b8921" : "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,24 @@ 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
21779
|
function hasBlockingFinding(response) {
|
|
20988
21780
|
const findings = response.findings ?? [];
|
|
20989
21781
|
return findings.some((f) => f.scope !== "pre-existing" && ["critical", "high"].includes((f.severity ?? "").toLowerCase()));
|
|
20990
21782
|
}
|
|
20991
|
-
function buildGuardRequest(moment, files, iter, sessionId,
|
|
21783
|
+
function buildGuardRequest(moment, files, codeDelta, iter, sessionId, statedIntent, coverageTelemetry) {
|
|
20992
21784
|
const analyzable = filterAnalyzable(files);
|
|
20993
21785
|
const securityFiles = filterSecurity(files);
|
|
20994
21786
|
let staticResults;
|
|
20995
21787
|
if (isCodacyAvailable()) {
|
|
20996
|
-
const scannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles])).map((f) => (0,
|
|
21788
|
+
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
21789
|
staticResults = runCodacyAnalysis(scannable);
|
|
20998
21790
|
} else {
|
|
20999
21791
|
staticResults = { tool: "@codacy/analysis-cli", findings: [], summary: { total_findings: 0, by_severity: {}, tools_run: [] } };
|
|
21000
21792
|
}
|
|
21001
|
-
const codeDelta = collectCodeDelta(files);
|
|
21002
|
-
if (codeDelta.total_files === 0) return null;
|
|
21003
21793
|
const trigger = moment === "pre-commit" ? "hook:pre-commit" : "hook:pre-push";
|
|
21004
21794
|
const requestBody = {
|
|
21005
21795
|
static_results: staticResults,
|
|
@@ -21016,9 +21806,9 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
|
21016
21806
|
iteration: iter + 1
|
|
21017
21807
|
}
|
|
21018
21808
|
};
|
|
21809
|
+
if (coverageTelemetry) requestBody.coverage_telemetry = coverageTelemetry;
|
|
21019
21810
|
const specs = discoverSpecs();
|
|
21020
21811
|
const plans = discoverPlans();
|
|
21021
|
-
const statedIntent = extractStatedIntent(moment, command);
|
|
21022
21812
|
if (specs.length > 0 || plans.length > 0 || statedIntent) {
|
|
21023
21813
|
const intentContext = {};
|
|
21024
21814
|
if (statedIntent) intentContext.user_prompt = statedIntent;
|
|
@@ -21028,6 +21818,39 @@ function buildGuardRequest(moment, files, iter, sessionId, command) {
|
|
|
21028
21818
|
}
|
|
21029
21819
|
return requestBody;
|
|
21030
21820
|
}
|
|
21821
|
+
function buildGuardCoverage(files, codeDelta, frame, frameRange) {
|
|
21822
|
+
const byReason = {};
|
|
21823
|
+
for (const e of codeDelta.excluded) byReason[e.reason] = (byReason[e.reason] ?? 0) + 1;
|
|
21824
|
+
return {
|
|
21825
|
+
changed_all: files.length,
|
|
21826
|
+
analyzable: filterAnalyzable(files).length,
|
|
21827
|
+
reviewable: filterReviewable(files).length,
|
|
21828
|
+
security: filterSecurity(files).length,
|
|
21829
|
+
for_review: files.length,
|
|
21830
|
+
sent: codeDelta.total_files,
|
|
21831
|
+
capped_out: codeDelta.truncated?.dropped ?? 0,
|
|
21832
|
+
excluded: codeDelta.excluded.length,
|
|
21833
|
+
excluded_by_reason: byReason,
|
|
21834
|
+
transcript_windowed: null,
|
|
21835
|
+
guard_frame: frameTelemetry(frame, frameRange)
|
|
21836
|
+
};
|
|
21837
|
+
}
|
|
21838
|
+
function coverageSummary(c) {
|
|
21839
|
+
const range = c.range ? ` @ ${c.range}` : "";
|
|
21840
|
+
return `reviewed ${c.sent.length} file(s)${range}`;
|
|
21841
|
+
}
|
|
21842
|
+
function coverageBlock(c) {
|
|
21843
|
+
const lines = [];
|
|
21844
|
+
const tree = c.root ? `${c.root}${c.linked ? " (linked worktree)" : ""}${c.branch ? ` \xB7 branch ${c.branch}` : ""}` : "(no tree resolved)";
|
|
21845
|
+
lines.push(`Reviewed (${c.moment}): ${c.sent.length} file(s)${c.range ? ` @ ${c.range}` : ""}`);
|
|
21846
|
+
lines.push(` Tree: ${tree}`);
|
|
21847
|
+
for (const f of c.sent) lines.push(` - ${f}`);
|
|
21848
|
+
if (c.excluded.length > 0) {
|
|
21849
|
+
lines.push(` Excluded (${c.excluded.length}):`);
|
|
21850
|
+
for (const e of c.excluded) lines.push(` - ${e.path} (${e.reason})`);
|
|
21851
|
+
}
|
|
21852
|
+
return lines.join("\n");
|
|
21853
|
+
}
|
|
21031
21854
|
function emitAllowNotice(userMsg, agentMsg) {
|
|
21032
21855
|
process.stdout.write(JSON.stringify({
|
|
21033
21856
|
systemMessage: userMsg,
|
|
@@ -21038,15 +21861,24 @@ function emitAllowNotice(userMsg, agentMsg) {
|
|
|
21038
21861
|
async function runGuard(opts, globals) {
|
|
21039
21862
|
const on = opts.on.split(",").map((s) => s.trim()).filter((s) => s === "commit" || s === "push");
|
|
21040
21863
|
const { command, cwd, sessionId } = await readPreToolUseStdin();
|
|
21041
|
-
|
|
21042
|
-
try {
|
|
21043
|
-
process.chdir(cwd);
|
|
21044
|
-
} catch {
|
|
21045
|
-
}
|
|
21046
|
-
}
|
|
21047
|
-
const moment = classifyCommand2(command, on);
|
|
21864
|
+
const moment = classifyCommand(command, on);
|
|
21048
21865
|
if (!moment) process.exit(0);
|
|
21049
21866
|
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
21867
|
+
const { frame } = resolveFrame({ command, on, hookCwd: cwd });
|
|
21868
|
+
if (frame.refusal || !frame.worktreeRoot) {
|
|
21869
|
+
logEvent("guard_frame", { moment, ...frameTelemetry(frame, null) });
|
|
21870
|
+
if ((frame.refusal ?? "").startsWith("anchor:")) process.exit(0);
|
|
21871
|
+
emitAllowNotice(
|
|
21872
|
+
`\u26A0 Verity ${moment}: could not resolve the tree this ${verb} targets \u2014 ${verb}ed WITHOUT review`,
|
|
21873
|
+
`Verity ${moment}: the target tree could not be resolved (${frame.refusal}); the ${verb} was allowed WITHOUT a Verity review.`
|
|
21874
|
+
);
|
|
21875
|
+
}
|
|
21876
|
+
try {
|
|
21877
|
+
process.chdir(frame.worktreeRoot);
|
|
21878
|
+
} catch {
|
|
21879
|
+
process.exit(0);
|
|
21880
|
+
}
|
|
21881
|
+
_resetRepoRoot();
|
|
21050
21882
|
const iter = readIter(moment);
|
|
21051
21883
|
if (iter >= GUARD_BLOCK_CAP) {
|
|
21052
21884
|
resetIter(moment);
|
|
@@ -21055,13 +21887,39 @@ async function runGuard(opts, globals) {
|
|
|
21055
21887
|
`Verity ${moment}: review-cycle cap (${GUARD_BLOCK_CAP}) reached; the ${verb} was allowed without a further block.`
|
|
21056
21888
|
);
|
|
21057
21889
|
}
|
|
21058
|
-
const
|
|
21890
|
+
const range = resolveMomentRange(moment, frame, command, on);
|
|
21891
|
+
const files = rangeFiles(frame, range);
|
|
21059
21892
|
if (files.length === 0) process.exit(0);
|
|
21060
21893
|
const tokenResult = await resolveToken(globals.token);
|
|
21061
21894
|
const urlResult = await resolveServiceUrl(globals.serviceUrl);
|
|
21062
21895
|
if (!tokenResult.ok || !urlResult.ok) process.exit(0);
|
|
21063
|
-
|
|
21064
|
-
|
|
21896
|
+
logEvent("guard_frame", { moment, ...frameTelemetry(frame, range) });
|
|
21897
|
+
const codeDelta = collectCodeDelta(files);
|
|
21898
|
+
if (codeDelta.total_files === 0) process.exit(0);
|
|
21899
|
+
const statedIntent = extractStatedIntent(
|
|
21900
|
+
moment,
|
|
21901
|
+
command,
|
|
21902
|
+
moment === "pre-push" ? rangeMessages(frame, range) || null : null
|
|
21903
|
+
);
|
|
21904
|
+
const requestBody = buildGuardRequest(
|
|
21905
|
+
moment,
|
|
21906
|
+
files,
|
|
21907
|
+
codeDelta,
|
|
21908
|
+
iter,
|
|
21909
|
+
sessionId,
|
|
21910
|
+
statedIntent,
|
|
21911
|
+
buildGuardCoverage(files, codeDelta, frame, range)
|
|
21912
|
+
);
|
|
21913
|
+
const coverage = {
|
|
21914
|
+
moment,
|
|
21915
|
+
root: frame.worktreeRoot,
|
|
21916
|
+
branch: frame.branch,
|
|
21917
|
+
linked: frame.isLinkedWorktree,
|
|
21918
|
+
range: describeRange(range),
|
|
21919
|
+
sent: codeDelta.files.map((f) => f.path),
|
|
21920
|
+
excluded: codeDelta.excluded.map((e) => ({ path: e.path, reason: e.reason }))
|
|
21921
|
+
};
|
|
21922
|
+
logToFileOnly(coverageBlock(coverage));
|
|
21065
21923
|
const result = await analyzeRequest({
|
|
21066
21924
|
serviceUrl: urlResult.data,
|
|
21067
21925
|
token: tokenResult.data.token,
|
|
@@ -21082,31 +21940,39 @@ async function runGuard(opts, globals) {
|
|
|
21082
21940
|
const decision = response.gate_decision ?? "(unrecognised)";
|
|
21083
21941
|
const viewUrl = response.view_url ?? "";
|
|
21084
21942
|
const link = viewUrl ? ` \u2014 ${viewUrl}` : "";
|
|
21943
|
+
const covLine = coverageSummary(coverage);
|
|
21944
|
+
const covDetail = coverageBlock(coverage);
|
|
21085
21945
|
if (decision === "FAIL" && hasBlockingFinding(response)) {
|
|
21086
21946
|
writeIter(moment, iter + 1);
|
|
21087
|
-
writeBlockMessage(moment, response);
|
|
21947
|
+
writeBlockMessage(moment, response, covDetail);
|
|
21088
21948
|
process.exit(2);
|
|
21089
21949
|
}
|
|
21090
21950
|
resetIter(moment);
|
|
21091
21951
|
if (decision === "FAIL") {
|
|
21092
21952
|
const narrative = response.assessment?.narrative ?? "";
|
|
21093
21953
|
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 : ""}
|
|
21954
|
+
`\u26A0 Verity ${moment}: the ${verb} may not match its stated purpose \u2014 proceeding (${covLine})${link}`,
|
|
21955
|
+
`Verity ${moment}: intent-alignment WARNING (not blocked).${narrative ? " " + narrative : ""}
|
|
21956
|
+
${covDetail}${viewUrl ? `
|
|
21957
|
+
Report: ${viewUrl}` : ""}`
|
|
21096
21958
|
);
|
|
21097
21959
|
}
|
|
21098
21960
|
if (decision === "WARN") {
|
|
21099
21961
|
emitAllowNotice(
|
|
21100
|
-
`\u26A0 Verity ${moment}: WARN \u2014 proceeding${link}`,
|
|
21101
|
-
`Verity ${moment} review: WARN (proceeding)
|
|
21962
|
+
`\u26A0 Verity ${moment}: WARN \u2014 proceeding (${covLine})${link}`,
|
|
21963
|
+
`Verity ${moment} review: WARN (proceeding).
|
|
21964
|
+
${covDetail}${viewUrl ? `
|
|
21965
|
+
Report: ${viewUrl}` : ""}`
|
|
21102
21966
|
);
|
|
21103
21967
|
}
|
|
21104
21968
|
emitAllowNotice(
|
|
21105
|
-
`\u2713 Verity ${moment}: PASS${link}`,
|
|
21106
|
-
`Verity ${moment} review: PASS
|
|
21969
|
+
`\u2713 Verity ${moment}: PASS (${covLine})${link}`,
|
|
21970
|
+
`Verity ${moment} review: PASS.
|
|
21971
|
+
${covDetail}${viewUrl ? `
|
|
21972
|
+
Report: ${viewUrl}` : ""}`
|
|
21107
21973
|
);
|
|
21108
21974
|
}
|
|
21109
|
-
function writeBlockMessage(moment, response) {
|
|
21975
|
+
function writeBlockMessage(moment, response, covDetail) {
|
|
21110
21976
|
const label2 = moment === "pre-commit" ? "pre-commit" : "pre-push";
|
|
21111
21977
|
const verb = moment === "pre-commit" ? "commit" : "push";
|
|
21112
21978
|
const assessment = response.assessment;
|
|
@@ -21136,6 +22002,9 @@ function writeBlockMessage(moment, response) {
|
|
|
21136
22002
|
`);
|
|
21137
22003
|
}
|
|
21138
22004
|
}
|
|
22005
|
+
process.stderr.write(`${DIM}${covDetail}${NC}
|
|
22006
|
+
|
|
22007
|
+
`);
|
|
21139
22008
|
if (viewUrl) process.stderr.write(`${CYAN}Full report: ${viewUrl}${NC}
|
|
21140
22009
|
|
|
21141
22010
|
`);
|
|
@@ -21143,17 +22012,109 @@ function writeBlockMessage(moment, response) {
|
|
|
21143
22012
|
`);
|
|
21144
22013
|
}
|
|
21145
22014
|
|
|
22015
|
+
// src/commands/ignore.ts
|
|
22016
|
+
function registerIgnoreCommand(program2) {
|
|
22017
|
+
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) => {
|
|
22018
|
+
const globals = program2.opts();
|
|
22019
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
22020
|
+
const reason = opts.reason?.trim();
|
|
22021
|
+
if (!reason) {
|
|
22022
|
+
printError(
|
|
22023
|
+
'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.'
|
|
22024
|
+
);
|
|
22025
|
+
process.exit(1);
|
|
22026
|
+
}
|
|
22027
|
+
if (opts.for && opts.turn) {
|
|
22028
|
+
printError("Use either --turn or --for, not both \u2014 they are two different windows.");
|
|
22029
|
+
process.exit(1);
|
|
22030
|
+
}
|
|
22031
|
+
let scope2 = "turn";
|
|
22032
|
+
let ttl = TURN_FUSE_SECONDS;
|
|
22033
|
+
if (opts.for) {
|
|
22034
|
+
const parsed = parseDuration(opts.for);
|
|
22035
|
+
if (!parsed.ok) {
|
|
22036
|
+
printError(parsed.error);
|
|
22037
|
+
process.exit(1);
|
|
22038
|
+
}
|
|
22039
|
+
scope2 = "window";
|
|
22040
|
+
ttl = parsed.seconds;
|
|
22041
|
+
}
|
|
22042
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22043
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22044
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22045
|
+
const keys = ignoreStateKeys(token, sessionId);
|
|
22046
|
+
const existing = resolveIgnoreState(keys);
|
|
22047
|
+
const spent = existing?.state.spent ?? 0;
|
|
22048
|
+
const writeKey = existing?.key ?? keys[0];
|
|
22049
|
+
if (spent >= IGNORE_BUDGET) {
|
|
22050
|
+
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.`;
|
|
22051
|
+
if (opts.json) {
|
|
22052
|
+
printJson({ declared: false, reason_refused: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22053
|
+
} else {
|
|
22054
|
+
printWarn(msg);
|
|
22055
|
+
}
|
|
22056
|
+
logEvent("ignore_refused", { why: "budget-spent", spent, budget: IGNORE_BUDGET });
|
|
22057
|
+
process.exit(0);
|
|
22058
|
+
}
|
|
22059
|
+
const declaration = {
|
|
22060
|
+
scope: scope2,
|
|
22061
|
+
origin: opts.agent ? "agent" : "user",
|
|
22062
|
+
reason,
|
|
22063
|
+
at: now,
|
|
22064
|
+
expires: now + ttl
|
|
22065
|
+
};
|
|
22066
|
+
writeIgnoreState({ v: 1, active: declaration, spent: spent + 1 }, writeKey);
|
|
22067
|
+
logEvent("ignore_declared", {
|
|
22068
|
+
scope: scope2,
|
|
22069
|
+
origin: declaration.origin,
|
|
22070
|
+
ttl_seconds: ttl,
|
|
22071
|
+
spent: spent + 1,
|
|
22072
|
+
budget: IGNORE_BUDGET
|
|
22073
|
+
});
|
|
22074
|
+
if (opts.json) {
|
|
22075
|
+
printJson({
|
|
22076
|
+
declared: true,
|
|
22077
|
+
scope: scope2,
|
|
22078
|
+
origin: declaration.origin,
|
|
22079
|
+
reason,
|
|
22080
|
+
expires_at: new Date(declaration.expires * 1e3).toISOString(),
|
|
22081
|
+
spent: spent + 1,
|
|
22082
|
+
budget: IGNORE_BUDGET
|
|
22083
|
+
});
|
|
22084
|
+
return;
|
|
22085
|
+
}
|
|
22086
|
+
const window = scope2 === "turn" ? "the next turn" : `the next ${describeRemaining(declaration, now).replace(" left", "")}`;
|
|
22087
|
+
printInfo(`Verity will skip ${window} \u2014 "${reason}" (${spent + 1}/${IGNORE_BUDGET} this session).`);
|
|
22088
|
+
printInfo("It covers turns that author nothing. If anything is written, the declaration voids and the review runs.");
|
|
22089
|
+
});
|
|
22090
|
+
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) => {
|
|
22091
|
+
const globals = program2.opts();
|
|
22092
|
+
const tokenResult = await resolveToken(globals.token);
|
|
22093
|
+
const token = tokenResult.ok ? tokenResult.data.token : void 0;
|
|
22094
|
+
const sessionId = opts.sessionId || process.env.CLAUDE_SESSION_ID || void 0;
|
|
22095
|
+
const found = resolveIgnoreState(ignoreStateKeys(token, sessionId));
|
|
22096
|
+
const active = resolveActive(found?.state ?? null, Math.floor(Date.now() / 1e3));
|
|
22097
|
+
if (!found || !active) {
|
|
22098
|
+
printInfo("No active ignore declaration.");
|
|
22099
|
+
return;
|
|
22100
|
+
}
|
|
22101
|
+
clearActiveDeclaration(found.key);
|
|
22102
|
+
logEvent("ignore_cleared", { scope: active.scope, origin: active.origin });
|
|
22103
|
+
printInfo(`Cleared: "${active.reason}". The next turn will be reviewed normally.`);
|
|
22104
|
+
});
|
|
22105
|
+
}
|
|
22106
|
+
|
|
21146
22107
|
// src/commands/init.ts
|
|
21147
|
-
var
|
|
22108
|
+
var import_node_fs40 = require("node:fs");
|
|
21148
22109
|
var import_promises13 = require("node:fs/promises");
|
|
21149
|
-
var
|
|
21150
|
-
var
|
|
22110
|
+
var import_node_path29 = require("node:path");
|
|
22111
|
+
var import_node_child_process11 = require("node:child_process");
|
|
21151
22112
|
var readline2 = __toESM(require("node:readline/promises"));
|
|
21152
22113
|
|
|
21153
22114
|
// src/commands/migrate.ts
|
|
21154
|
-
var
|
|
21155
|
-
var
|
|
21156
|
-
var
|
|
22115
|
+
var import_node_fs39 = require("node:fs");
|
|
22116
|
+
var import_node_path28 = require("node:path");
|
|
22117
|
+
var import_node_child_process10 = require("node:child_process");
|
|
21157
22118
|
|
|
21158
22119
|
// src/lib/telemetry.ts
|
|
21159
22120
|
var import_promises12 = require("node:fs/promises");
|
|
@@ -21248,11 +22209,11 @@ async function uninstallTelemetry() {
|
|
|
21248
22209
|
// src/commands/migrate.ts
|
|
21249
22210
|
var LEGACY_NPM_PACKAGE = "@codacy/gate-cli";
|
|
21250
22211
|
function defaultNpmRemover(pkg) {
|
|
21251
|
-
(0,
|
|
22212
|
+
(0, import_node_child_process10.execSync)(`npm rm -g ${pkg}`, { stdio: "pipe", timeout: 12e4 });
|
|
21252
22213
|
}
|
|
21253
22214
|
function isGitTracked(cwd, relPath) {
|
|
21254
22215
|
try {
|
|
21255
|
-
(0,
|
|
22216
|
+
(0, import_node_child_process10.execSync)(`git ls-files --error-unmatch ${relPath}`, { cwd, stdio: "pipe" });
|
|
21256
22217
|
return true;
|
|
21257
22218
|
} catch {
|
|
21258
22219
|
return false;
|
|
@@ -21260,7 +22221,7 @@ function isGitTracked(cwd, relPath) {
|
|
|
21260
22221
|
}
|
|
21261
22222
|
function isGitRepo(cwd) {
|
|
21262
22223
|
try {
|
|
21263
|
-
(0,
|
|
22224
|
+
(0, import_node_child_process10.execSync)("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
21264
22225
|
return true;
|
|
21265
22226
|
} catch {
|
|
21266
22227
|
return false;
|
|
@@ -21281,12 +22242,12 @@ async function runMigration(opts = {}) {
|
|
|
21281
22242
|
return { actions, migrated: actions.length > 0 };
|
|
21282
22243
|
}
|
|
21283
22244
|
function migrateProjectDir(root, actions) {
|
|
21284
|
-
const gateDir = (0,
|
|
21285
|
-
const verityDir = (0,
|
|
21286
|
-
if ((0,
|
|
22245
|
+
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22246
|
+
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22247
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) {
|
|
21287
22248
|
return migrateProjectDirRename(root, gateDir, verityDir, actions);
|
|
21288
22249
|
}
|
|
21289
|
-
if ((0,
|
|
22250
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
21290
22251
|
return migrateProjectDirCarry(gateDir, verityDir, actions);
|
|
21291
22252
|
}
|
|
21292
22253
|
return false;
|
|
@@ -21300,20 +22261,20 @@ function migrateProjectDirRename(root, gateDir, verityDir, actions) {
|
|
|
21300
22261
|
);
|
|
21301
22262
|
}
|
|
21302
22263
|
try {
|
|
21303
|
-
(0,
|
|
22264
|
+
(0, import_node_child_process10.execSync)("git mv .gate .verity", { cwd: root, stdio: "pipe" });
|
|
21304
22265
|
actions.push("Moved .gate/ \u2192 .verity/ (git mv, staged)");
|
|
21305
22266
|
moved = true;
|
|
21306
22267
|
} catch {
|
|
21307
22268
|
}
|
|
21308
22269
|
}
|
|
21309
22270
|
if (moved) {
|
|
21310
|
-
if ((0,
|
|
22271
|
+
if ((0, import_node_fs39.existsSync)(gateDir)) {
|
|
21311
22272
|
const carried = carryLegacyContents(gateDir, verityDir);
|
|
21312
22273
|
if (carried > 0) {
|
|
21313
22274
|
actions.push(`Carried ${carried} untracked legacy file(s) from .gate/ into .verity/`);
|
|
21314
22275
|
}
|
|
21315
22276
|
try {
|
|
21316
|
-
(0,
|
|
22277
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21317
22278
|
} catch {
|
|
21318
22279
|
}
|
|
21319
22280
|
}
|
|
@@ -21329,18 +22290,18 @@ function migrateProjectDirCarry(gateDir, verityDir, actions) {
|
|
|
21329
22290
|
actions.push(`Carried ${carried} legacy file(s) from .gate/ into .verity/`);
|
|
21330
22291
|
}
|
|
21331
22292
|
try {
|
|
21332
|
-
(0,
|
|
22293
|
+
(0, import_node_fs39.rmSync)(gateDir, { recursive: true, force: true });
|
|
21333
22294
|
} catch {
|
|
21334
22295
|
}
|
|
21335
22296
|
return carried > 0;
|
|
21336
22297
|
}
|
|
21337
22298
|
function migrateGlobalCredentials(home, actions) {
|
|
21338
22299
|
if (!home) return;
|
|
21339
|
-
const gateCreds = (0,
|
|
21340
|
-
const verityCreds = (0,
|
|
21341
|
-
if (!(0,
|
|
21342
|
-
if (!(0,
|
|
21343
|
-
(0,
|
|
22300
|
+
const gateCreds = (0, import_node_path28.join)(home, ".gate", "credentials");
|
|
22301
|
+
const verityCreds = (0, import_node_path28.join)(home, ".verity", "credentials");
|
|
22302
|
+
if (!(0, import_node_fs39.existsSync)(gateCreds)) return;
|
|
22303
|
+
if (!(0, import_node_fs39.existsSync)(verityCreds)) {
|
|
22304
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.join)(home, ".verity"), { recursive: true });
|
|
21344
22305
|
moveFile(gateCreds, verityCreds);
|
|
21345
22306
|
actions.push("Moved ~/.gate/credentials \u2192 ~/.verity/credentials");
|
|
21346
22307
|
return;
|
|
@@ -21362,8 +22323,8 @@ async function migrateLegacyHooks(root, actions) {
|
|
|
21362
22323
|
}
|
|
21363
22324
|
}
|
|
21364
22325
|
async function migrateClaudeMd(root, actions) {
|
|
21365
|
-
const claudeMd = (0,
|
|
21366
|
-
const hadLegacyBlock = (0,
|
|
22326
|
+
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22327
|
+
const hadLegacyBlock = (0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd));
|
|
21367
22328
|
if (!hadLegacyBlock) return;
|
|
21368
22329
|
try {
|
|
21369
22330
|
await ensureClaudeMdPointer(root);
|
|
@@ -21373,13 +22334,13 @@ async function migrateClaudeMd(root, actions) {
|
|
|
21373
22334
|
}
|
|
21374
22335
|
}
|
|
21375
22336
|
function migrateStandardFile(root, actions) {
|
|
21376
|
-
const gateMd = (0,
|
|
21377
|
-
const verityMd = (0,
|
|
21378
|
-
if (!(0,
|
|
22337
|
+
const gateMd = (0, import_node_path28.join)(root, "GATE.md");
|
|
22338
|
+
const verityMd = (0, import_node_path28.join)(root, "VERITY.md");
|
|
22339
|
+
if (!(0, import_node_fs39.existsSync)(gateMd) || (0, import_node_fs39.existsSync)(verityMd)) return;
|
|
21379
22340
|
let moved = false;
|
|
21380
22341
|
if (isGitRepo(root) && isGitTracked(root, "GATE.md")) {
|
|
21381
22342
|
try {
|
|
21382
|
-
(0,
|
|
22343
|
+
(0, import_node_child_process10.execSync)("git mv GATE.md VERITY.md", { cwd: root, stdio: "pipe" });
|
|
21383
22344
|
moved = true;
|
|
21384
22345
|
} catch {
|
|
21385
22346
|
}
|
|
@@ -21387,12 +22348,12 @@ function migrateStandardFile(root, actions) {
|
|
|
21387
22348
|
if (!moved) moveFile(gateMd, verityMd);
|
|
21388
22349
|
const content = readFileSyncSafe(verityMd);
|
|
21389
22350
|
const refreshed = content.split("GATE.md").join("VERITY.md");
|
|
21390
|
-
if (refreshed !== content) (0,
|
|
22351
|
+
if (refreshed !== content) (0, import_node_fs39.writeFileSync)(verityMd, refreshed);
|
|
21391
22352
|
actions.push("Renamed GATE.md \u2192 VERITY.md");
|
|
21392
22353
|
}
|
|
21393
22354
|
async function migrateTelemetryHeaders(root, actions) {
|
|
21394
|
-
const file = (0,
|
|
21395
|
-
if (!(0,
|
|
22355
|
+
const file = (0, import_node_path28.join)(root, ".claude", "settings.local.json");
|
|
22356
|
+
if (!(0, import_node_fs39.existsSync)(file)) return;
|
|
21396
22357
|
let settings;
|
|
21397
22358
|
try {
|
|
21398
22359
|
settings = JSON.parse(readFileSyncSafe(file) || "{}");
|
|
@@ -21440,21 +22401,21 @@ function mergeGlobalCredentials(gateCreds, verityCreds) {
|
|
|
21440
22401
|
}
|
|
21441
22402
|
if (toAppend.length > 0) {
|
|
21442
22403
|
const sep2 = verityContent.endsWith("\n") || verityContent === "" ? "" : "\n";
|
|
21443
|
-
(0,
|
|
22404
|
+
(0, import_node_fs39.writeFileSync)(verityCreds, verityContent + sep2 + toAppend.join("\n") + "\n");
|
|
21444
22405
|
}
|
|
21445
|
-
(0,
|
|
22406
|
+
(0, import_node_fs39.rmSync)(gateCreds, { force: true });
|
|
21446
22407
|
return toAppend.length;
|
|
21447
22408
|
}
|
|
21448
22409
|
function readFileSyncSafe(path) {
|
|
21449
22410
|
try {
|
|
21450
|
-
return (0,
|
|
22411
|
+
return (0, import_node_fs39.readFileSync)(path, "utf-8");
|
|
21451
22412
|
} catch {
|
|
21452
22413
|
return "";
|
|
21453
22414
|
}
|
|
21454
22415
|
}
|
|
21455
22416
|
function hasStagedChanges(root) {
|
|
21456
22417
|
try {
|
|
21457
|
-
(0,
|
|
22418
|
+
(0, import_node_child_process10.execSync)("git diff --cached --quiet", { cwd: root, stdio: "pipe" });
|
|
21458
22419
|
return false;
|
|
21459
22420
|
} catch {
|
|
21460
22421
|
return true;
|
|
@@ -21462,35 +22423,35 @@ function hasStagedChanges(root) {
|
|
|
21462
22423
|
}
|
|
21463
22424
|
function moveDir(from, to) {
|
|
21464
22425
|
try {
|
|
21465
|
-
(0,
|
|
22426
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21466
22427
|
} catch (err) {
|
|
21467
22428
|
if (err.code !== "EXDEV") throw err;
|
|
21468
|
-
(0,
|
|
21469
|
-
(0,
|
|
22429
|
+
(0, import_node_fs39.cpSync)(from, to, { recursive: true });
|
|
22430
|
+
(0, import_node_fs39.rmSync)(from, { recursive: true, force: true });
|
|
21470
22431
|
}
|
|
21471
22432
|
}
|
|
21472
22433
|
function moveFile(from, to) {
|
|
21473
22434
|
try {
|
|
21474
|
-
(0,
|
|
22435
|
+
(0, import_node_fs39.renameSync)(from, to);
|
|
21475
22436
|
} catch (err) {
|
|
21476
22437
|
if (err.code !== "EXDEV") throw err;
|
|
21477
|
-
(0,
|
|
21478
|
-
(0,
|
|
22438
|
+
(0, import_node_fs39.cpSync)(from, to);
|
|
22439
|
+
(0, import_node_fs39.rmSync)(from, { force: true });
|
|
21479
22440
|
}
|
|
21480
22441
|
}
|
|
21481
22442
|
function carryLegacyContents(gateDir, verityDir) {
|
|
21482
22443
|
let copied = 0;
|
|
21483
22444
|
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,
|
|
22445
|
+
const srcDir = (0, import_node_path28.join)(gateDir, relDir);
|
|
22446
|
+
for (const entry of (0, import_node_fs39.readdirSync)(srcDir)) {
|
|
22447
|
+
const rel = relDir ? (0, import_node_path28.join)(relDir, entry) : entry;
|
|
22448
|
+
const src = (0, import_node_path28.join)(gateDir, rel);
|
|
22449
|
+
const dest = (0, import_node_path28.join)(verityDir, rel);
|
|
22450
|
+
if ((0, import_node_fs39.statSync)(src).isDirectory()) {
|
|
21490
22451
|
walk(rel);
|
|
21491
|
-
} else if (!(0,
|
|
21492
|
-
(0,
|
|
21493
|
-
(0,
|
|
22452
|
+
} else if (!(0, import_node_fs39.existsSync)(dest)) {
|
|
22453
|
+
(0, import_node_fs39.mkdirSync)((0, import_node_path28.dirname)(dest), { recursive: true });
|
|
22454
|
+
(0, import_node_fs39.cpSync)(src, dest);
|
|
21494
22455
|
copied++;
|
|
21495
22456
|
}
|
|
21496
22457
|
}
|
|
@@ -21499,22 +22460,22 @@ function carryLegacyContents(gateDir, verityDir) {
|
|
|
21499
22460
|
return copied;
|
|
21500
22461
|
}
|
|
21501
22462
|
async function needsMigration(root = repoRoot()) {
|
|
21502
|
-
const gateDir = (0,
|
|
21503
|
-
const verityDir = (0,
|
|
21504
|
-
if ((0,
|
|
21505
|
-
if ((0,
|
|
21506
|
-
if ((0,
|
|
22463
|
+
const gateDir = (0, import_node_path28.join)(root, ".gate");
|
|
22464
|
+
const verityDir = (0, import_node_path28.join)(root, ".verity");
|
|
22465
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && !(0, import_node_fs39.existsSync)(verityDir)) return true;
|
|
22466
|
+
if ((0, import_node_fs39.existsSync)(gateDir) && (0, import_node_fs39.existsSync)(verityDir)) {
|
|
22467
|
+
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
22468
|
return true;
|
|
21508
22469
|
}
|
|
21509
|
-
if ((0,
|
|
22470
|
+
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
22471
|
return true;
|
|
21511
22472
|
}
|
|
21512
22473
|
}
|
|
21513
|
-
const claudeMd = (0,
|
|
21514
|
-
if ((0,
|
|
22474
|
+
const claudeMd = (0, import_node_path28.join)(root, "CLAUDE.md");
|
|
22475
|
+
if ((0, import_node_fs39.existsSync)(claudeMd) && hasLegacyMemoryBlock(readFileSyncSafe(claudeMd))) {
|
|
21515
22476
|
return true;
|
|
21516
22477
|
}
|
|
21517
|
-
if ((0,
|
|
22478
|
+
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
22479
|
return true;
|
|
21519
22480
|
}
|
|
21520
22481
|
if (await hasLegacyHooksAt(root)) return true;
|
|
@@ -21607,7 +22568,7 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
21607
22568
|
}
|
|
21608
22569
|
let remote = "";
|
|
21609
22570
|
try {
|
|
21610
|
-
remote = (0,
|
|
22571
|
+
remote = (0, import_node_child_process11.execSync)("git remote get-url origin", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
21611
22572
|
} catch {
|
|
21612
22573
|
}
|
|
21613
22574
|
if (!healed) {
|
|
@@ -21650,15 +22611,15 @@ async function runOptionalAuth(resolution, opts = {}) {
|
|
|
21650
22611
|
}
|
|
21651
22612
|
function resolveDataDir() {
|
|
21652
22613
|
const candidates = [
|
|
21653
|
-
(0,
|
|
22614
|
+
(0, import_node_path29.join)(__dirname, "..", "data"),
|
|
21654
22615
|
// installed: node_modules/@codacy/verity-cli/data
|
|
21655
|
-
(0,
|
|
22616
|
+
(0, import_node_path29.join)(__dirname, "..", "..", "data"),
|
|
21656
22617
|
// edge case: nested resolution
|
|
21657
|
-
(0,
|
|
22618
|
+
(0, import_node_path29.join)(process.cwd(), "cli", "data")
|
|
21658
22619
|
// local dev: running from repo root
|
|
21659
22620
|
];
|
|
21660
22621
|
for (const candidate of candidates) {
|
|
21661
|
-
if ((0,
|
|
22622
|
+
if ((0, import_node_fs40.existsSync)((0, import_node_path29.join)(candidate, "skills"))) {
|
|
21662
22623
|
return candidate;
|
|
21663
22624
|
}
|
|
21664
22625
|
}
|
|
@@ -21674,7 +22635,7 @@ function registerInitCommand(program2) {
|
|
|
21674
22635
|
program2.command("init").description("Initialize Verity in the current project").option("--force", "Overwrite existing skills and hooks").action(async (opts) => {
|
|
21675
22636
|
const force = opts.force ?? false;
|
|
21676
22637
|
const projectMarkers = [".git", "package.json", "pyproject.toml", "go.mod", "Cargo.toml", "Gemfile", "pom.xml", "build.gradle"];
|
|
21677
|
-
const isProject = projectMarkers.some((m) => (0,
|
|
22638
|
+
const isProject = projectMarkers.some((m) => (0, import_node_fs40.existsSync)(m));
|
|
21678
22639
|
if (!isProject) {
|
|
21679
22640
|
printError("No project detected in the current directory.");
|
|
21680
22641
|
printInfo('Run "verity init" from your project root.');
|
|
@@ -21702,30 +22663,30 @@ function registerInitCommand(program2) {
|
|
|
21702
22663
|
}
|
|
21703
22664
|
printInfo(` Node.js ${nodeVersion} \u2713`);
|
|
21704
22665
|
try {
|
|
21705
|
-
const gitVersion = (0,
|
|
22666
|
+
const gitVersion = (0, import_node_child_process11.execSync)("git --version", { encoding: "utf-8" }).trim();
|
|
21706
22667
|
printInfo(` ${gitVersion} \u2713`);
|
|
21707
22668
|
} catch {
|
|
21708
22669
|
printError("git is required but not installed. Install from https://git-scm.com");
|
|
21709
22670
|
process.exit(1);
|
|
21710
22671
|
}
|
|
21711
22672
|
try {
|
|
21712
|
-
(0,
|
|
22673
|
+
(0, import_node_child_process11.execSync)("which claude", { encoding: "utf-8" });
|
|
21713
22674
|
printInfo(" Claude Code \u2713");
|
|
21714
22675
|
} catch {
|
|
21715
22676
|
printWarn(" Claude Code not found \u2014 hooks will be configured but need Claude Code to run.");
|
|
21716
22677
|
}
|
|
21717
22678
|
try {
|
|
21718
|
-
(0,
|
|
22679
|
+
(0, import_node_child_process11.execSync)("which codacy-analysis", { encoding: "utf-8", stdio: "pipe" });
|
|
21719
22680
|
printInfo(" @codacy/analysis-cli \u2713");
|
|
21720
22681
|
} catch {
|
|
21721
22682
|
printInfo(" Installing @codacy/analysis-cli...");
|
|
21722
22683
|
try {
|
|
21723
|
-
(0,
|
|
22684
|
+
(0, import_node_child_process11.execSync)("npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "pipe", timeout: 12e4 });
|
|
21724
22685
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
21725
22686
|
} catch {
|
|
21726
22687
|
try {
|
|
21727
22688
|
printWarn(" Retrying with sudo...");
|
|
21728
|
-
(0,
|
|
22689
|
+
(0, import_node_child_process11.execSync)("sudo npm install -g @codacy/analysis-cli", { encoding: "utf-8", stdio: "inherit", timeout: 12e4 });
|
|
21729
22690
|
printInfo(" @codacy/analysis-cli installed \u2713");
|
|
21730
22691
|
} catch {
|
|
21731
22692
|
printWarn(" Could not install @codacy/analysis-cli automatically.");
|
|
@@ -21737,21 +22698,21 @@ function registerInitCommand(program2) {
|
|
|
21737
22698
|
console.log("");
|
|
21738
22699
|
printInfo("Installing skills...");
|
|
21739
22700
|
const dataDir = resolveDataDir();
|
|
21740
|
-
const skillsSource = (0,
|
|
22701
|
+
const skillsSource = (0, import_node_path29.join)(dataDir, "skills");
|
|
21741
22702
|
const skillsDest = ".claude/skills";
|
|
21742
22703
|
const skills = ["verity-setup", "verity-analyze", "verity-status", "verity-feedback", "verity-learn", "verity-memory", "verity-insights", "verity-reflect"];
|
|
21743
22704
|
let skillsInstalled = 0;
|
|
21744
22705
|
for (const skill of skills) {
|
|
21745
|
-
const src = (0,
|
|
21746
|
-
const dest = (0,
|
|
21747
|
-
if (!(0,
|
|
22706
|
+
const src = (0, import_node_path29.join)(skillsSource, skill);
|
|
22707
|
+
const dest = (0, import_node_path29.join)(skillsDest, skill);
|
|
22708
|
+
if (!(0, import_node_fs40.existsSync)(src)) {
|
|
21748
22709
|
printWarn(` Skill data not found: ${skill}`);
|
|
21749
22710
|
continue;
|
|
21750
22711
|
}
|
|
21751
|
-
if ((0,
|
|
21752
|
-
const srcSkill = (0,
|
|
21753
|
-
const destSkill = (0,
|
|
21754
|
-
if ((0,
|
|
22712
|
+
if ((0, import_node_fs40.existsSync)(dest) && !force) {
|
|
22713
|
+
const srcSkill = (0, import_node_path29.join)(src, "SKILL.md");
|
|
22714
|
+
const destSkill = (0, import_node_path29.join)(dest, "SKILL.md");
|
|
22715
|
+
if ((0, import_node_fs40.existsSync)(destSkill)) {
|
|
21755
22716
|
try {
|
|
21756
22717
|
const srcContent = await (0, import_promises13.readFile)(srcSkill, "utf-8");
|
|
21757
22718
|
const destContent = await (0, import_promises13.readFile)(destSkill, "utf-8");
|
|
@@ -21794,7 +22755,7 @@ function registerInitCommand(program2) {
|
|
|
21794
22755
|
} catch (err) {
|
|
21795
22756
|
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
21796
22757
|
}
|
|
21797
|
-
const globalVerityDir = (0,
|
|
22758
|
+
const globalVerityDir = (0, import_node_path29.join)(process.env.HOME ?? "", ".verity");
|
|
21798
22759
|
await (0, import_promises13.mkdir)(globalVerityDir, { recursive: true });
|
|
21799
22760
|
console.log("");
|
|
21800
22761
|
try {
|
|
@@ -21829,8 +22790,8 @@ function registerInitCommand(program2) {
|
|
|
21829
22790
|
}
|
|
21830
22791
|
|
|
21831
22792
|
// src/commands/uninstall.ts
|
|
21832
|
-
var
|
|
21833
|
-
var
|
|
22793
|
+
var import_node_fs41 = require("node:fs");
|
|
22794
|
+
var import_node_path30 = require("node:path");
|
|
21834
22795
|
var SKILL_NAMES = [
|
|
21835
22796
|
"verity-setup",
|
|
21836
22797
|
"verity-analyze",
|
|
@@ -21849,11 +22810,11 @@ function registerUninstallCommand(program2) {
|
|
|
21849
22810
|
const actions = [];
|
|
21850
22811
|
const skillsRoot = projectPath(".claude/skills");
|
|
21851
22812
|
for (const name of SKILL_NAMES) {
|
|
21852
|
-
const dir = (0,
|
|
21853
|
-
if ((0,
|
|
22813
|
+
const dir = (0, import_node_path30.join)(skillsRoot, name);
|
|
22814
|
+
if ((0, import_node_fs41.existsSync)(dir)) {
|
|
21854
22815
|
actions.push({
|
|
21855
22816
|
label: `Remove .claude/skills/${name}/`,
|
|
21856
|
-
apply: () => (0,
|
|
22817
|
+
apply: () => (0, import_node_fs41.rmSync)(dir, { recursive: true, force: true })
|
|
21857
22818
|
});
|
|
21858
22819
|
}
|
|
21859
22820
|
}
|
|
@@ -21867,24 +22828,24 @@ function registerUninstallCommand(program2) {
|
|
|
21867
22828
|
});
|
|
21868
22829
|
}
|
|
21869
22830
|
const verityDir = projectPath(VERITY_DIR);
|
|
21870
|
-
if ((0,
|
|
22831
|
+
if ((0, import_node_fs41.existsSync)(verityDir)) {
|
|
21871
22832
|
actions.push({
|
|
21872
22833
|
label: `Remove ${VERITY_DIR}/`,
|
|
21873
|
-
apply: () => (0,
|
|
22834
|
+
apply: () => (0, import_node_fs41.rmSync)(verityDir, { recursive: true, force: true })
|
|
21874
22835
|
});
|
|
21875
22836
|
}
|
|
21876
22837
|
if (!keepVerityMd) {
|
|
21877
22838
|
const verityMd = projectPath(VERITY_MD_FILE);
|
|
21878
|
-
if ((0,
|
|
22839
|
+
if ((0, import_node_fs41.existsSync)(verityMd)) {
|
|
21879
22840
|
actions.push({
|
|
21880
22841
|
label: `Remove ${VERITY_MD_FILE}`,
|
|
21881
|
-
apply: () => (0,
|
|
22842
|
+
apply: () => (0, import_node_fs41.rmSync)(verityMd, { force: true })
|
|
21882
22843
|
});
|
|
21883
22844
|
}
|
|
21884
22845
|
}
|
|
21885
22846
|
const cleanupEmptyDir = (path) => {
|
|
21886
|
-
if ((0,
|
|
21887
|
-
(0,
|
|
22847
|
+
if ((0, import_node_fs41.existsSync)(path) && (0, import_node_fs41.statSync)(path).isDirectory() && (0, import_node_fs41.readdirSync)(path).length === 0) {
|
|
22848
|
+
(0, import_node_fs41.rmdirSync)(path);
|
|
21888
22849
|
}
|
|
21889
22850
|
};
|
|
21890
22851
|
actions.push({
|
|
@@ -21895,11 +22856,11 @@ function registerUninstallCommand(program2) {
|
|
|
21895
22856
|
}
|
|
21896
22857
|
});
|
|
21897
22858
|
const home = process.env.HOME ?? "";
|
|
21898
|
-
const globalVerityDir = (0,
|
|
21899
|
-
if (purgeGlobal && (0,
|
|
22859
|
+
const globalVerityDir = (0, import_node_path30.join)(home, ".verity");
|
|
22860
|
+
if (purgeGlobal && (0, import_node_fs41.existsSync)(globalVerityDir)) {
|
|
21900
22861
|
actions.push({
|
|
21901
22862
|
label: `Remove ~/.verity/ (global credentials \u2014 reconnect requires re-registration)`,
|
|
21902
|
-
apply: () => (0,
|
|
22863
|
+
apply: () => (0, import_node_fs41.rmSync)(globalVerityDir, { recursive: true, force: true })
|
|
21903
22864
|
});
|
|
21904
22865
|
}
|
|
21905
22866
|
if (actions.length === 0) {
|
|
@@ -22093,8 +23054,8 @@ function registerTaskCommands(program2) {
|
|
|
22093
23054
|
}
|
|
22094
23055
|
|
|
22095
23056
|
// src/commands/reset.ts
|
|
22096
|
-
var
|
|
22097
|
-
var
|
|
23057
|
+
var import_node_fs42 = require("node:fs");
|
|
23058
|
+
var import_node_path31 = require("node:path");
|
|
22098
23059
|
function registerResetCommand(program2) {
|
|
22099
23060
|
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
23061
|
const globals = program2.opts();
|
|
@@ -22131,11 +23092,11 @@ function registerResetCommand(program2) {
|
|
|
22131
23092
|
}
|
|
22132
23093
|
const cacheDir = projectPath(CACHE_DIR);
|
|
22133
23094
|
let purged = 0;
|
|
22134
|
-
if ((0,
|
|
22135
|
-
for (const entry of (0,
|
|
23095
|
+
if ((0, import_node_fs42.existsSync)(cacheDir)) {
|
|
23096
|
+
for (const entry of (0, import_node_fs42.readdirSync)(cacheDir)) {
|
|
22136
23097
|
if (entry.startsWith("pending-")) {
|
|
22137
23098
|
try {
|
|
22138
|
-
(0,
|
|
23099
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(cacheDir, entry));
|
|
22139
23100
|
purged++;
|
|
22140
23101
|
} catch {
|
|
22141
23102
|
}
|
|
@@ -22150,19 +23111,19 @@ function registerResetCommand(program2) {
|
|
|
22150
23111
|
projectPath(`${VERITY_DIR}/.last-analysis`)
|
|
22151
23112
|
];
|
|
22152
23113
|
for (const file of filesToClear) {
|
|
22153
|
-
if ((0,
|
|
23114
|
+
if ((0, import_node_fs42.existsSync)(file)) {
|
|
22154
23115
|
try {
|
|
22155
|
-
(0,
|
|
23116
|
+
(0, import_node_fs42.writeFileSync)(file, "");
|
|
22156
23117
|
} catch {
|
|
22157
23118
|
}
|
|
22158
23119
|
}
|
|
22159
23120
|
}
|
|
22160
23121
|
if (opts.all) {
|
|
22161
23122
|
const logsDir = projectPath(`${VERITY_DIR}/.logs`);
|
|
22162
|
-
if ((0,
|
|
22163
|
-
for (const entry of (0,
|
|
23123
|
+
if ((0, import_node_fs42.existsSync)(logsDir)) {
|
|
23124
|
+
for (const entry of (0, import_node_fs42.readdirSync)(logsDir)) {
|
|
22164
23125
|
try {
|
|
22165
|
-
(0,
|
|
23126
|
+
(0, import_node_fs42.unlinkSync)((0, import_node_path31.join)(logsDir, entry));
|
|
22166
23127
|
} catch {
|
|
22167
23128
|
}
|
|
22168
23129
|
}
|
|
@@ -22470,8 +23431,8 @@ function registerTelemetryCommands(program2) {
|
|
|
22470
23431
|
}
|
|
22471
23432
|
|
|
22472
23433
|
// 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.
|
|
23434
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.30.1-experimental.67b8921").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) => {
|
|
23435
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.30.1-experimental.67b8921");
|
|
22475
23436
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
22476
23437
|
try {
|
|
22477
23438
|
await foldLegacyLocalCredential();
|
|
@@ -22494,6 +23455,7 @@ registerAnalyzeCommand(program);
|
|
|
22494
23455
|
registerBaselineCommands(program);
|
|
22495
23456
|
registerReviewCommand(program);
|
|
22496
23457
|
registerGuardCommand(program);
|
|
23458
|
+
registerIgnoreCommand(program);
|
|
22497
23459
|
registerInitCommand(program);
|
|
22498
23460
|
registerUninstallCommand(program);
|
|
22499
23461
|
registerTaskCommands(program);
|