@codacy/verity-cli 0.31.2 → 0.31.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/bin/verity.js +560 -506
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -24,6 +24,16 @@ installs and updates it for you.
|
|
|
24
24
|
plugin is present it owns the hooks, and the settings.json copy stands down.
|
|
25
25
|
`verity init` detects the plugin and removes the duplicate wiring.
|
|
26
26
|
|
|
27
|
+
### 🚪 A project that was never set up is left alone
|
|
28
|
+
|
|
29
|
+
Every hook now asks the same question the same way. `verity analyze` used to run
|
|
30
|
+
in a project that had never seen `verity init`, create `.verity/` there, and then —
|
|
31
|
+
with no baseline to compare against — review nothing. It now stands down like the
|
|
32
|
+
other hooks always have, so installing Verity no longer scatters state through
|
|
33
|
+
directories you merely opened. On the first session in such a project, Claude is
|
|
34
|
+
told once that the gate is off here and can offer `/verity:setup` (or `verity init`
|
|
35
|
+
on an npm install). Works correctly from linked git worktrees.
|
|
36
|
+
|
|
27
37
|
### ⚙️ Git-moment gating moved into the project
|
|
28
38
|
|
|
29
39
|
`verity guard` now reads `.verity/config.json` instead of taking its moments from
|
|
@@ -107,6 +117,26 @@ with one entry point.
|
|
|
107
117
|
terminal (Ctrl+D, a pty that ends) falls back to the default instead of
|
|
108
118
|
crashing init between copying the skills and wiring the hooks.
|
|
109
119
|
|
|
120
|
+
## [0.31.3] — 2026-09-03
|
|
121
|
+
|
|
122
|
+
**After a rebase, Verity reviewed the wrong code.** When a branch was rebased
|
|
123
|
+
onto an updated main, the gate could treat all of the upstream changes as the
|
|
124
|
+
session's own work and block the push on findings in code the session never
|
|
125
|
+
touched. The cause was that the review baseline was trusted by whether its commit
|
|
126
|
+
still existed rather than whether it was still part of the current history, so a
|
|
127
|
+
rewritten baseline kept anchoring every diff.
|
|
128
|
+
|
|
129
|
+
- **The push gate reviews exactly what the push publishes** — the commits the
|
|
130
|
+
remote does not yet have — so a rebase, a force-push, a first push, or
|
|
131
|
+
re-pushing an already-published commit each review only their real new work.
|
|
132
|
+
- **The stop-hook baseline is trusted only while it is an ancestor of HEAD.** A
|
|
133
|
+
rewritten baseline is discarded and re-derived from the commits actually made
|
|
134
|
+
since, so upstream changes are never attributed to the session.
|
|
135
|
+
- **A commit made during a merge reviews only the files you resolved**, not the
|
|
136
|
+
whole incoming branch.
|
|
137
|
+
- **A slash command such as `/login` is no longer treated as a task or a goal**,
|
|
138
|
+
so the review is graded against what you were actually working on.
|
|
139
|
+
|
|
110
140
|
## [0.31.1] — 2026-08-27
|
|
111
141
|
|
|
112
142
|
**Verity stops repeating itself.** A finding it had already closed could keep
|
package/bin/verity.js
CHANGED
|
@@ -10833,16 +10833,9 @@ function splitLines(s) {
|
|
|
10833
10833
|
}
|
|
10834
10834
|
var SHA_RE = /^[0-9a-f]{40}$/;
|
|
10835
10835
|
function readBaselineSha() {
|
|
10836
|
-
|
|
10837
|
-
|
|
10838
|
-
|
|
10839
|
-
sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10840
|
-
} catch {
|
|
10841
|
-
return null;
|
|
10842
|
-
}
|
|
10843
|
-
if (!SHA_RE.test(sha)) return null;
|
|
10844
|
-
const reachable = execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10845
|
-
if (!reachable) {
|
|
10836
|
+
const sha = readRawBaselineSha();
|
|
10837
|
+
if (sha === null) return null;
|
|
10838
|
+
if (!commitResolves(sha)) {
|
|
10846
10839
|
try {
|
|
10847
10840
|
(0, import_node_fs3.unlinkSync)(BASELINE_SHA_FILE);
|
|
10848
10841
|
} catch {
|
|
@@ -10851,6 +10844,16 @@ function readBaselineSha() {
|
|
|
10851
10844
|
}
|
|
10852
10845
|
return sha;
|
|
10853
10846
|
}
|
|
10847
|
+
function readRawBaselineSha() {
|
|
10848
|
+
if (!(0, import_node_fs3.existsSync)(BASELINE_SHA_FILE)) return null;
|
|
10849
|
+
let sha;
|
|
10850
|
+
try {
|
|
10851
|
+
sha = (0, import_node_fs3.readFileSync)(BASELINE_SHA_FILE, "utf-8").trim();
|
|
10852
|
+
} catch {
|
|
10853
|
+
return null;
|
|
10854
|
+
}
|
|
10855
|
+
return SHA_RE.test(sha) ? sha : null;
|
|
10856
|
+
}
|
|
10854
10857
|
function writeBaselineSha(sha) {
|
|
10855
10858
|
if (!SHA_RE.test(sha)) return;
|
|
10856
10859
|
try {
|
|
@@ -10859,6 +10862,25 @@ function writeBaselineSha(sha) {
|
|
|
10859
10862
|
} catch {
|
|
10860
10863
|
}
|
|
10861
10864
|
}
|
|
10865
|
+
function commitObjectExists(sha) {
|
|
10866
|
+
if (!SHA_RE.test(sha)) return false;
|
|
10867
|
+
return execGit(`git cat-file -e ${sha}^{commit} 2>/dev/null && echo ok`) === "ok";
|
|
10868
|
+
}
|
|
10869
|
+
function committedSinceRewrite(oldSha) {
|
|
10870
|
+
if (!SHA_RE.test(oldSha)) return [];
|
|
10871
|
+
const newLocal = new Set(splitLines(execGit(`git rev-list HEAD --not ${oldSha} --remotes`)));
|
|
10872
|
+
if (newLocal.size === 0) return [];
|
|
10873
|
+
const files = /* @__PURE__ */ new Set();
|
|
10874
|
+
for (const line of splitLines(execGit(`git cherry ${oldSha} HEAD`))) {
|
|
10875
|
+
const sp = line.indexOf(" ");
|
|
10876
|
+
if (sp < 0) continue;
|
|
10877
|
+
const mark = line.slice(0, sp);
|
|
10878
|
+
const sha = line.slice(sp + 1).trim();
|
|
10879
|
+
if (mark !== "+" || !newLocal.has(sha)) continue;
|
|
10880
|
+
for (const f of splitLines(execGit(`git diff-tree --no-commit-id --name-only -r ${sha}`))) files.add(f);
|
|
10881
|
+
}
|
|
10882
|
+
return [...files].filter((f) => !isVerityOwnedPath(f));
|
|
10883
|
+
}
|
|
10862
10884
|
var VERITY_OWNED_PREFIXES = [".verity/", ".gate/", ".codacy/"];
|
|
10863
10885
|
var VERITY_OWNED_FILES = ["VERITY.md"];
|
|
10864
10886
|
function isVerityOwnedPath(file) {
|
|
@@ -10871,6 +10893,7 @@ function getChangedFiles() {
|
|
|
10871
10893
|
for (const f of splitLines(execGit("git diff --name-only HEAD"))) sets.add(f);
|
|
10872
10894
|
for (const f of splitLines(execGit("git diff --name-only --cached"))) sets.add(f);
|
|
10873
10895
|
for (const f of splitLines(execGit("git ls-files --others --exclude-standard"))) sets.add(f);
|
|
10896
|
+
const rawBaseline = readRawBaselineSha();
|
|
10874
10897
|
const baseline = readBaselineSha();
|
|
10875
10898
|
if (baseline) {
|
|
10876
10899
|
const committed = splitLines(execGit(`git diff --name-only ${baseline}..HEAD`));
|
|
@@ -10878,6 +10901,13 @@ function getChangedFiles() {
|
|
|
10878
10901
|
hasRecentCommitFiles = true;
|
|
10879
10902
|
for (const f of committed) sets.add(f);
|
|
10880
10903
|
}
|
|
10904
|
+
} else if (rawBaseline && commitObjectExists(rawBaseline)) {
|
|
10905
|
+
const committed = committedSinceRewrite(rawBaseline);
|
|
10906
|
+
logEvent("baseline_rewritten", { recovered: committed.length });
|
|
10907
|
+
if (committed.length > 0) {
|
|
10908
|
+
hasRecentCommitFiles = true;
|
|
10909
|
+
for (const f of committed) sets.add(f);
|
|
10910
|
+
}
|
|
10881
10911
|
} else {
|
|
10882
10912
|
const headTimestamp = parseInt(execGit("git log -1 --format=%ct HEAD"), 10) || 0;
|
|
10883
10913
|
const commitAge = Math.floor(Date.now() / 1e3) - headTimestamp;
|
|
@@ -13083,6 +13113,242 @@ function getRecentCommitMessages() {
|
|
|
13083
13113
|
}
|
|
13084
13114
|
}
|
|
13085
13115
|
|
|
13116
|
+
// src/lib/analysis-mode.ts
|
|
13117
|
+
var DEBUG_PHRASES = [
|
|
13118
|
+
"not working",
|
|
13119
|
+
"doesn't work",
|
|
13120
|
+
"doesn't work",
|
|
13121
|
+
"does not work",
|
|
13122
|
+
"isn't working",
|
|
13123
|
+
"is not working",
|
|
13124
|
+
"can't figure out",
|
|
13125
|
+
"stack trace"
|
|
13126
|
+
];
|
|
13127
|
+
var DEBUG_WORDS = [
|
|
13128
|
+
"fix",
|
|
13129
|
+
"bug",
|
|
13130
|
+
"broken",
|
|
13131
|
+
"crash",
|
|
13132
|
+
"crashing",
|
|
13133
|
+
"failing",
|
|
13134
|
+
"debug",
|
|
13135
|
+
"debugging",
|
|
13136
|
+
"investigate",
|
|
13137
|
+
"troubleshoot",
|
|
13138
|
+
"regression",
|
|
13139
|
+
"wrong"
|
|
13140
|
+
];
|
|
13141
|
+
var DEBUG_PATTERN = new RegExp(
|
|
13142
|
+
[
|
|
13143
|
+
...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
13144
|
+
...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
|
|
13145
|
+
].join("|"),
|
|
13146
|
+
"i"
|
|
13147
|
+
);
|
|
13148
|
+
var FALSE_POSITIVE_PATTERNS = [
|
|
13149
|
+
/\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
|
|
13150
|
+
/\berror\s+handling\b/i,
|
|
13151
|
+
/\berror\s+boundar(?:y|ies)\b/i,
|
|
13152
|
+
/\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
|
|
13153
|
+
/\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
|
|
13154
|
+
];
|
|
13155
|
+
function hasDebugIntent(prompt) {
|
|
13156
|
+
if (!DEBUG_PATTERN.test(prompt)) return false;
|
|
13157
|
+
for (const fp of FALSE_POSITIVE_PATTERNS) {
|
|
13158
|
+
if (fp.test(prompt)) return false;
|
|
13159
|
+
}
|
|
13160
|
+
return true;
|
|
13161
|
+
}
|
|
13162
|
+
var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
|
|
13163
|
+
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;
|
|
13164
|
+
function isGitOnlyPrompt(prompt) {
|
|
13165
|
+
if (!GIT_ONLY_PATTERN.test(prompt)) return false;
|
|
13166
|
+
if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
|
|
13167
|
+
return true;
|
|
13168
|
+
}
|
|
13169
|
+
function reconcileAnalysisMode(predictedMode, signals) {
|
|
13170
|
+
const mode2 = resolveAnalysisMode(predictedMode, signals);
|
|
13171
|
+
if (mode2 !== "skip") return mode2;
|
|
13172
|
+
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
13173
|
+
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
13174
|
+
return mode2;
|
|
13175
|
+
}
|
|
13176
|
+
function resolveAnalysisMode(predictedMode, signals) {
|
|
13177
|
+
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
13178
|
+
return detectAnalysisMode(
|
|
13179
|
+
signals.noFilesChanged,
|
|
13180
|
+
signals.assistantResponse,
|
|
13181
|
+
signals.conversationPrompts,
|
|
13182
|
+
signals.actionSummary,
|
|
13183
|
+
signals.sessionAuthoredCode
|
|
13184
|
+
);
|
|
13185
|
+
}
|
|
13186
|
+
const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
|
|
13187
|
+
const agentInvestigated = didAgentInvestigate(signals.actionSummary);
|
|
13188
|
+
switch (predictedMode) {
|
|
13189
|
+
case "skip":
|
|
13190
|
+
if (agentAuthoredCode) return "standard";
|
|
13191
|
+
return "skip";
|
|
13192
|
+
case "plan":
|
|
13193
|
+
if (agentAuthoredCode) return "standard";
|
|
13194
|
+
return "plan";
|
|
13195
|
+
case "debug":
|
|
13196
|
+
return "debug";
|
|
13197
|
+
case "standard":
|
|
13198
|
+
if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
|
|
13199
|
+
return agentInvestigated ? "plan" : "skip";
|
|
13200
|
+
}
|
|
13201
|
+
return "standard";
|
|
13202
|
+
}
|
|
13203
|
+
}
|
|
13204
|
+
function didAgentInvestigate(summary) {
|
|
13205
|
+
if (!summary) return false;
|
|
13206
|
+
return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
|
|
13207
|
+
}
|
|
13208
|
+
function isValidMode(mode2) {
|
|
13209
|
+
return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
|
|
13210
|
+
}
|
|
13211
|
+
function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
|
|
13212
|
+
const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
|
|
13213
|
+
if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
|
|
13214
|
+
if (!agentAuthoredCode) return "skip";
|
|
13215
|
+
}
|
|
13216
|
+
if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
|
|
13217
|
+
return "plan";
|
|
13218
|
+
}
|
|
13219
|
+
if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
|
|
13220
|
+
return didAgentInvestigate(actionSummary) ? "plan" : "skip";
|
|
13221
|
+
}
|
|
13222
|
+
for (const prompt of conversationPrompts) {
|
|
13223
|
+
if (hasDebugIntent(prompt)) {
|
|
13224
|
+
return "debug";
|
|
13225
|
+
}
|
|
13226
|
+
}
|
|
13227
|
+
return "standard";
|
|
13228
|
+
}
|
|
13229
|
+
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;
|
|
13230
|
+
var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
|
|
13231
|
+
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;
|
|
13232
|
+
var CHAIN_RE = /&&|\||;|\$\(|\x60/;
|
|
13233
|
+
function isNonAuthoringCommand(cmd) {
|
|
13234
|
+
if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
|
|
13235
|
+
if (FILE_MUTATE_RE.test(cmd)) return false;
|
|
13236
|
+
if (CHAIN_RE.test(cmd)) return false;
|
|
13237
|
+
return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
|
|
13238
|
+
}
|
|
13239
|
+
function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
|
|
13240
|
+
if (!actionSummary) return sessionAuthoredCode;
|
|
13241
|
+
if ((actionSummary.subagents ?? 0) > 0) return true;
|
|
13242
|
+
if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
|
|
13243
|
+
const commands = actionSummary.commands ?? [];
|
|
13244
|
+
if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
|
|
13245
|
+
if (sessionAuthoredCode) {
|
|
13246
|
+
const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
|
|
13247
|
+
if (!allSafe) return true;
|
|
13248
|
+
}
|
|
13249
|
+
return false;
|
|
13250
|
+
}
|
|
13251
|
+
function scopeToAuthored(files, actionSummary) {
|
|
13252
|
+
if (!actionSummary) return { files, signal: "no-transcript" };
|
|
13253
|
+
const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
|
|
13254
|
+
if (touched.length === 0) return { files: [], signal: "none-authored" };
|
|
13255
|
+
return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
|
|
13256
|
+
}
|
|
13257
|
+
function chooseReviewScope(input) {
|
|
13258
|
+
if (input.narrowingIsTrustworthy) return input.scopedFiles;
|
|
13259
|
+
if (input.recoveredScope.length > 0) return input.recoveredScope;
|
|
13260
|
+
return input.authorshipWasObservable ? [] : input.allForReview;
|
|
13261
|
+
}
|
|
13262
|
+
function narrowToAgentAuthored(files, actionSummary) {
|
|
13263
|
+
if (!actionSummary) return files;
|
|
13264
|
+
const touched = [
|
|
13265
|
+
...actionSummary.files_edited,
|
|
13266
|
+
...actionSummary.files_created
|
|
13267
|
+
];
|
|
13268
|
+
if (touched.length === 0) return files;
|
|
13269
|
+
return files.filter((f) => {
|
|
13270
|
+
const suffix = "/" + f;
|
|
13271
|
+
return touched.some((t) => t === f || t.endsWith(suffix));
|
|
13272
|
+
});
|
|
13273
|
+
}
|
|
13274
|
+
|
|
13275
|
+
// src/lib/skip-detection.ts
|
|
13276
|
+
function isBareAckPrompt(prompt) {
|
|
13277
|
+
if (typeof prompt !== "string") return false;
|
|
13278
|
+
const trimmed = prompt.trim();
|
|
13279
|
+
if (trimmed.length === 0) return false;
|
|
13280
|
+
if (trimmed.length > 20) return false;
|
|
13281
|
+
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
13282
|
+
return bareAckPattern.test(trimmed);
|
|
13283
|
+
}
|
|
13284
|
+
function isContinuationPrompt(prompt) {
|
|
13285
|
+
if (typeof prompt !== "string") return false;
|
|
13286
|
+
const trimmed = prompt.trim();
|
|
13287
|
+
if (trimmed.length === 0) return false;
|
|
13288
|
+
if (trimmed.length > 24) return false;
|
|
13289
|
+
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
13290
|
+
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
13291
|
+
}
|
|
13292
|
+
function isSlashCommand(prompt) {
|
|
13293
|
+
if (typeof prompt !== "string") return false;
|
|
13294
|
+
return /^\s*\/[A-Za-z][\w-]*(\s|$)/.test(prompt);
|
|
13295
|
+
}
|
|
13296
|
+
function resolveGoalPrompt(prompts) {
|
|
13297
|
+
if (prompts.length === 0) return null;
|
|
13298
|
+
const latest = prompts[prompts.length - 1];
|
|
13299
|
+
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
13300
|
+
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
13301
|
+
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
13302
|
+
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
13303
|
+
}
|
|
13304
|
+
}
|
|
13305
|
+
return { entry: latest, turnsBack: 0 };
|
|
13306
|
+
}
|
|
13307
|
+
function isReflectionQuestion(response) {
|
|
13308
|
+
if (!response || typeof response !== "string") return false;
|
|
13309
|
+
const markers = [
|
|
13310
|
+
/reflection\s+for\s+future\s+agents/i,
|
|
13311
|
+
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
13312
|
+
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
13313
|
+
/quick\s+reflection\s+question/i,
|
|
13314
|
+
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
13315
|
+
// interactive, asks the user to confirm/correct before recording. That
|
|
13316
|
+
// turn authors no code either, so it's still a reflection turn.
|
|
13317
|
+
/reflection\s+draft/i,
|
|
13318
|
+
/confirm,?\s+correct,?\s+or\s+add/i
|
|
13319
|
+
];
|
|
13320
|
+
return markers.some((m) => m.test(response));
|
|
13321
|
+
}
|
|
13322
|
+
function isMetaTaskLabel(label2) {
|
|
13323
|
+
if (label2 === null || label2 === void 0) return false;
|
|
13324
|
+
if (typeof label2 !== "string") return false;
|
|
13325
|
+
const trimmed = label2.trim();
|
|
13326
|
+
if (trimmed.length === 0) return true;
|
|
13327
|
+
const metaPatterns = [
|
|
13328
|
+
/^verity\s+[\w-]+\s+response$/i,
|
|
13329
|
+
// "Verity reflect response"
|
|
13330
|
+
/^simple user response$/i,
|
|
13331
|
+
/^verity\s+command$/i,
|
|
13332
|
+
// "Verity command"
|
|
13333
|
+
/^user\s+(question|reply|response|ack)$/i
|
|
13334
|
+
];
|
|
13335
|
+
return metaPatterns.some((p) => p.test(trimmed));
|
|
13336
|
+
}
|
|
13337
|
+
function shouldSkipForBareAck(input) {
|
|
13338
|
+
if (!isBareAckPrompt(input.prompt)) return false;
|
|
13339
|
+
if (input.turnAuthoredCode) return false;
|
|
13340
|
+
return input.canSeeTurnAuthorship;
|
|
13341
|
+
}
|
|
13342
|
+
function isCommandOnlyTurn(input) {
|
|
13343
|
+
if (!input.authorshipIsObservable) return false;
|
|
13344
|
+
if (input.userCommandsTruncated) return false;
|
|
13345
|
+
const commands = input.userCommands ?? [];
|
|
13346
|
+
if (commands.length === 0) return false;
|
|
13347
|
+
if (input.agentAuthoredFiles > 0) return false;
|
|
13348
|
+
if (input.agentToolCalls > 0) return false;
|
|
13349
|
+
return commands.every(isNonAuthoringCommand);
|
|
13350
|
+
}
|
|
13351
|
+
|
|
13086
13352
|
// src/lib/context-identity.ts
|
|
13087
13353
|
var import_node_crypto2 = require("node:crypto");
|
|
13088
13354
|
var import_node_fs8 = require("node:fs");
|
|
@@ -14023,386 +14289,159 @@ function* scanLines(text) {
|
|
|
14023
14289
|
offset += line.length + 1;
|
|
14024
14290
|
}
|
|
14025
14291
|
}
|
|
14026
|
-
function findMarker(text, marker, from = 0) {
|
|
14027
|
-
for (const { line, start, inFence } of scanLines(text)) {
|
|
14028
|
-
if (!inFence && start >= from && line.startsWith(marker)) {
|
|
14029
|
-
return start;
|
|
14030
|
-
}
|
|
14031
|
-
}
|
|
14032
|
-
return -1;
|
|
14033
|
-
}
|
|
14034
|
-
function hasLegacyMemoryBlock(text) {
|
|
14035
|
-
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
14036
|
-
}
|
|
14037
|
-
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
14038
|
-
const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
|
|
14039
|
-
let existing = "";
|
|
14040
|
-
if ((0, import_node_fs12.existsSync)(claudeMdPath)) {
|
|
14041
|
-
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
14042
|
-
}
|
|
14043
|
-
let startTag = CLAUDE_MD_START;
|
|
14044
|
-
let endTag = CLAUDE_MD_END;
|
|
14045
|
-
let startIdx = findMarker(existing, CLAUDE_MD_START);
|
|
14046
|
-
let endIdx = startIdx === -1 ? -1 : findMarker(existing, CLAUDE_MD_END, startIdx + CLAUDE_MD_START.length);
|
|
14047
|
-
if (startIdx === -1 || endIdx === -1) {
|
|
14048
|
-
const legacyStart = findMarker(existing, LEGACY_MD_START);
|
|
14049
|
-
const legacyEnd = legacyStart === -1 ? -1 : findMarker(existing, LEGACY_MD_END, legacyStart + LEGACY_MD_START.length);
|
|
14050
|
-
if (legacyStart !== -1 && legacyEnd !== -1) {
|
|
14051
|
-
startTag = LEGACY_MD_START;
|
|
14052
|
-
endTag = LEGACY_MD_END;
|
|
14053
|
-
startIdx = legacyStart;
|
|
14054
|
-
endIdx = legacyEnd;
|
|
14055
|
-
} else if (startIdx !== -1 && endIdx === -1 || legacyStart !== -1 && legacyEnd === -1) {
|
|
14056
|
-
printWarn(
|
|
14057
|
-
"CLAUDE.md: found an unterminated memory marker (a start marker with no matching end marker). Leaving the file untouched to avoid erasing your text \u2014 please close or remove the stray marker so Verity can manage the block."
|
|
14058
|
-
);
|
|
14059
|
-
return;
|
|
14060
|
-
}
|
|
14061
|
-
}
|
|
14062
|
-
const hasBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
14063
|
-
let preserved = PRESERVE_PLACEHOLDER;
|
|
14064
|
-
if (hasBlock) {
|
|
14065
|
-
const interior = existing.slice(startIdx + startTag.length, endIdx);
|
|
14066
|
-
const captured = extractPreserveContent(interior);
|
|
14067
|
-
if (captured !== null) {
|
|
14068
|
-
preserved = captured;
|
|
14069
|
-
} else {
|
|
14070
|
-
const rescued = stripKnownProse(interior).trim();
|
|
14071
|
-
if (rescued) {
|
|
14072
|
-
preserved = rescued;
|
|
14073
|
-
printWarn(
|
|
14074
|
-
`CLAUDE.md: moved ${rescued.split("\n").length} line(s) of hand-edited text from inside the verity-memory markers into a preserve region (it now survives regeneration). Put durable guidance OUTSIDE the markers to keep it fully under your control.`
|
|
14075
|
-
);
|
|
14076
|
-
}
|
|
14077
|
-
}
|
|
14078
|
-
}
|
|
14079
|
-
const block = [
|
|
14080
|
-
CLAUDE_MD_START,
|
|
14081
|
-
CLAUDE_MD_PROSE,
|
|
14082
|
-
"",
|
|
14083
|
-
PRESERVE_START,
|
|
14084
|
-
preserved,
|
|
14085
|
-
PRESERVE_END,
|
|
14086
|
-
CLAUDE_MD_END
|
|
14087
|
-
].join("\n");
|
|
14088
|
-
let next;
|
|
14089
|
-
if (hasBlock) {
|
|
14090
|
-
next = existing.slice(0, startIdx) + block + existing.slice(endIdx + endTag.length);
|
|
14091
|
-
} else if (existing.trim() === "") {
|
|
14092
|
-
next = block + "\n";
|
|
14093
|
-
} else {
|
|
14094
|
-
next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
|
|
14095
|
-
}
|
|
14096
|
-
if (next === existing) return;
|
|
14097
|
-
await (0, import_promises8.writeFile)(claudeMdPath, next);
|
|
14098
|
-
}
|
|
14099
|
-
function extractPreserveContent(interior) {
|
|
14100
|
-
for (const [start, end] of [
|
|
14101
|
-
[PRESERVE_START, PRESERVE_END],
|
|
14102
|
-
[LEGACY_PRESERVE_START, LEGACY_PRESERVE_END]
|
|
14103
|
-
]) {
|
|
14104
|
-
const s = interior.indexOf(start);
|
|
14105
|
-
const e = interior.indexOf(end);
|
|
14106
|
-
if (s !== -1 && e !== -1 && e >= s) {
|
|
14107
|
-
return interior.slice(s + start.length, e).replace(/^\n+|\n+$/g, "");
|
|
14108
|
-
}
|
|
14109
|
-
}
|
|
14110
|
-
return null;
|
|
14111
|
-
}
|
|
14112
|
-
function stripKnownProse(interior) {
|
|
14113
|
-
const trimmed = interior.replace(/^\n+/, "");
|
|
14114
|
-
for (const prose of [
|
|
14115
|
-
CLAUDE_MD_PROSE,
|
|
14116
|
-
CLAUDE_MD_PROSE_PRE_REFLECT,
|
|
14117
|
-
CLAUDE_MD_PROSE_PRE_WAIVE,
|
|
14118
|
-
CLAUDE_MD_PROSE_PRE_IGNORE,
|
|
14119
|
-
CLAUDE_MD_PROSE_LEGACY
|
|
14120
|
-
]) {
|
|
14121
|
-
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
14122
|
-
}
|
|
14123
|
-
return trimmed;
|
|
14124
|
-
}
|
|
14125
|
-
var SCHEMA_TEMPLATE = `# Memory Graph Schema (v1)
|
|
14126
|
-
|
|
14127
|
-
## Node format
|
|
14128
|
-
|
|
14129
|
-
Each node is a markdown file with YAML frontmatter:
|
|
14130
|
-
|
|
14131
|
-
\`\`\`yaml
|
|
14132
|
-
---
|
|
14133
|
-
schema: 1
|
|
14134
|
-
id: n001-slug
|
|
14135
|
-
kind: decision | quality | security | intent | gotcha | pattern | domain | integration
|
|
14136
|
-
title: "Short title (\u2264200 chars)"
|
|
14137
|
-
domains: [tag1, tag2]
|
|
14138
|
-
file_globs: ["src/auth/**"]
|
|
14139
|
-
confidence: 0.5-1.0
|
|
14140
|
-
status: active | archived | superseded | orphan_flagged
|
|
14141
|
-
source: extractor | user | imported
|
|
14142
|
-
# ... (see full schema in MEMORY-GRAPH-PRD \xA76.3)
|
|
14143
|
-
---
|
|
14144
|
-
|
|
14145
|
-
# Title
|
|
14146
|
-
|
|
14147
|
-
Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
14148
|
-
\`\`\`
|
|
14149
|
-
|
|
14150
|
-
## Edge types
|
|
14151
|
-
|
|
14152
|
-
| Edge | Meaning |
|
|
14153
|
-
|------|---------|
|
|
14154
|
-
| related | Loose association |
|
|
14155
|
-
| supersedes | A replaces B |
|
|
14156
|
-
| contradicts | A and B disagree |
|
|
14157
|
-
| caused_by | Something in B led to A |
|
|
14158
|
-
| example_of | A is an instance of B |
|
|
14159
|
-
|
|
14160
|
-
## Domains
|
|
14161
|
-
|
|
14162
|
-
| Directory | Purpose |
|
|
14163
|
-
|-----------|---------|
|
|
14164
|
-
| decisions/ | Architectural choices (ADR-style) |
|
|
14165
|
-
| quality/ | Quality patterns |
|
|
14166
|
-
| security/ | Security constraints |
|
|
14167
|
-
| intent/ | Intent templates |
|
|
14168
|
-
| gotchas/ | Footguns and surprises |
|
|
14169
|
-
| patterns/ | Code conventions |
|
|
14170
|
-
| domain/ | Business logic concepts |
|
|
14171
|
-
| integrations/ | External system knowledge |
|
|
14172
|
-
| _archive/ | Superseded nodes |
|
|
14173
|
-
`;
|
|
14174
|
-
|
|
14175
|
-
// src/lib/dossier-session.ts
|
|
14176
|
-
var import_node_fs17 = require("node:fs");
|
|
14177
|
-
var import_node_crypto7 = require("node:crypto");
|
|
14178
|
-
var import_node_path13 = require("node:path");
|
|
14179
|
-
|
|
14180
|
-
// src/lib/analysis-mode.ts
|
|
14181
|
-
var DEBUG_PHRASES = [
|
|
14182
|
-
"not working",
|
|
14183
|
-
"doesn't work",
|
|
14184
|
-
"doesn't work",
|
|
14185
|
-
"does not work",
|
|
14186
|
-
"isn't working",
|
|
14187
|
-
"is not working",
|
|
14188
|
-
"can't figure out",
|
|
14189
|
-
"stack trace"
|
|
14190
|
-
];
|
|
14191
|
-
var DEBUG_WORDS = [
|
|
14192
|
-
"fix",
|
|
14193
|
-
"bug",
|
|
14194
|
-
"broken",
|
|
14195
|
-
"crash",
|
|
14196
|
-
"crashing",
|
|
14197
|
-
"failing",
|
|
14198
|
-
"debug",
|
|
14199
|
-
"debugging",
|
|
14200
|
-
"investigate",
|
|
14201
|
-
"troubleshoot",
|
|
14202
|
-
"regression",
|
|
14203
|
-
"wrong"
|
|
14204
|
-
];
|
|
14205
|
-
var DEBUG_PATTERN = new RegExp(
|
|
14206
|
-
[
|
|
14207
|
-
...DEBUG_PHRASES.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")),
|
|
14208
|
-
...DEBUG_WORDS.map((w) => `\\b${w}\\b`)
|
|
14209
|
-
].join("|"),
|
|
14210
|
-
"i"
|
|
14211
|
-
);
|
|
14212
|
-
var FALSE_POSITIVE_PATTERNS = [
|
|
14213
|
-
/\b(?:add|create|implement|write|build|design|set\s*up)\b.{0,20}\berror\b/i,
|
|
14214
|
-
/\berror\s+handling\b/i,
|
|
14215
|
-
/\berror\s+boundar(?:y|ies)\b/i,
|
|
14216
|
-
/\berror\s+(?:type|class|page|component|message|code|enum)\b/i,
|
|
14217
|
-
/\b(?:add|create|implement|write|build)\b.{0,20}\b(?:fix|debug|issue)\b/i
|
|
14218
|
-
];
|
|
14219
|
-
function hasDebugIntent(prompt) {
|
|
14220
|
-
if (!DEBUG_PATTERN.test(prompt)) return false;
|
|
14221
|
-
for (const fp of FALSE_POSITIVE_PATTERNS) {
|
|
14222
|
-
if (fp.test(prompt)) return false;
|
|
14223
|
-
}
|
|
14224
|
-
return true;
|
|
14225
|
-
}
|
|
14226
|
-
var GIT_ONLY_PATTERN = /\b(commit|push|deploy|merge|rebase|tag|release|publish|ship)\b/i;
|
|
14227
|
-
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;
|
|
14228
|
-
function isGitOnlyPrompt(prompt) {
|
|
14229
|
-
if (!GIT_ONLY_PATTERN.test(prompt)) return false;
|
|
14230
|
-
if (CODE_AUTHORING_PATTERN.test(prompt)) return false;
|
|
14231
|
-
return true;
|
|
14232
|
-
}
|
|
14233
|
-
function reconcileAnalysisMode(predictedMode, signals) {
|
|
14234
|
-
const mode2 = resolveAnalysisMode(predictedMode, signals);
|
|
14235
|
-
if (mode2 !== "skip") return mode2;
|
|
14236
|
-
const windowIsOrphaned = signals.actionSummary?.transcript_windowed === "orphaned";
|
|
14237
|
-
if (windowIsOrphaned && !signals.sessionAuthoredCode) return "standard";
|
|
14238
|
-
return mode2;
|
|
14239
|
-
}
|
|
14240
|
-
function resolveAnalysisMode(predictedMode, signals) {
|
|
14241
|
-
if (!predictedMode || !isValidMode(predictedMode)) {
|
|
14242
|
-
return detectAnalysisMode(
|
|
14243
|
-
signals.noFilesChanged,
|
|
14244
|
-
signals.assistantResponse,
|
|
14245
|
-
signals.conversationPrompts,
|
|
14246
|
-
signals.actionSummary,
|
|
14247
|
-
signals.sessionAuthoredCode
|
|
14248
|
-
);
|
|
14249
|
-
}
|
|
14250
|
-
const agentAuthoredCode = !!(signals.actionSummary && (signals.actionSummary.files_edited.length > 0 || signals.actionSummary.files_created.length > 0)) || !!signals.sessionAuthoredCode;
|
|
14251
|
-
const agentInvestigated = didAgentInvestigate(signals.actionSummary);
|
|
14252
|
-
switch (predictedMode) {
|
|
14253
|
-
case "skip":
|
|
14254
|
-
if (agentAuthoredCode) return "standard";
|
|
14255
|
-
return "skip";
|
|
14256
|
-
case "plan":
|
|
14257
|
-
if (agentAuthoredCode) return "standard";
|
|
14258
|
-
return "plan";
|
|
14259
|
-
case "debug":
|
|
14260
|
-
return "debug";
|
|
14261
|
-
case "standard":
|
|
14262
|
-
if (!!signals.actionSummary && !agentAuthoredCode && !!signals.assistantResponse) {
|
|
14263
|
-
return agentInvestigated ? "plan" : "skip";
|
|
14264
|
-
}
|
|
14265
|
-
return "standard";
|
|
14266
|
-
}
|
|
14267
|
-
}
|
|
14268
|
-
function didAgentInvestigate(summary) {
|
|
14269
|
-
if (!summary) return false;
|
|
14270
|
-
return summary.files_read.length > 0 || summary.searches > 0 || summary.commands.length > 0 || summary.subagents > 0 || summary.web_fetches > 0;
|
|
14271
|
-
}
|
|
14272
|
-
function isValidMode(mode2) {
|
|
14273
|
-
return mode2 === "standard" || mode2 === "plan" || mode2 === "debug" || mode2 === "skip";
|
|
14274
|
-
}
|
|
14275
|
-
function detectAnalysisMode(noFilesChanged, assistantResponse, conversationPrompts, actionSummary, sessionAuthoredCode) {
|
|
14276
|
-
const agentAuthoredCode = !!(actionSummary && (actionSummary.files_edited.length > 0 || actionSummary.files_created.length > 0)) || !!sessionAuthoredCode;
|
|
14277
|
-
if (conversationPrompts.length > 0 && conversationPrompts.every(isGitOnlyPrompt)) {
|
|
14278
|
-
if (!agentAuthoredCode) return "skip";
|
|
14279
|
-
}
|
|
14280
|
-
if (noFilesChanged && !!assistantResponse && !agentAuthoredCode) {
|
|
14281
|
-
return "plan";
|
|
14282
|
-
}
|
|
14283
|
-
if (!!actionSummary && !agentAuthoredCode && !!assistantResponse) {
|
|
14284
|
-
return didAgentInvestigate(actionSummary) ? "plan" : "skip";
|
|
14285
|
-
}
|
|
14286
|
-
for (const prompt of conversationPrompts) {
|
|
14287
|
-
if (hasDebugIntent(prompt)) {
|
|
14288
|
-
return "debug";
|
|
14289
|
-
}
|
|
14290
|
-
}
|
|
14291
|
-
return "standard";
|
|
14292
|
-
}
|
|
14293
|
-
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;
|
|
14294
|
-
var GIT_PLUMBING_RE = /^\s*git\s+(?:merge|rebase|stash|cherry-pick|revert|pull|fetch|checkout|switch|reset|restore|clean)\b/i;
|
|
14295
|
-
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;
|
|
14296
|
-
var CHAIN_RE = /&&|\||;|\$\(|\x60/;
|
|
14297
|
-
function isNonAuthoringCommand(cmd) {
|
|
14298
|
-
if (typeof cmd !== "string" || cmd.trim().length === 0) return false;
|
|
14299
|
-
if (FILE_MUTATE_RE.test(cmd)) return false;
|
|
14300
|
-
if (CHAIN_RE.test(cmd)) return false;
|
|
14301
|
-
return GIT_PLUMBING_RE.test(cmd) || READ_ONLY_RE.test(cmd);
|
|
14302
|
-
}
|
|
14303
|
-
function hasNonEditAuthorship(actionSummary, sessionAuthoredCode) {
|
|
14304
|
-
if (!actionSummary) return sessionAuthoredCode;
|
|
14305
|
-
if ((actionSummary.subagents ?? 0) > 0) return true;
|
|
14306
|
-
if (Object.keys(actionSummary.tool_counts ?? {}).some((t) => t.startsWith("mcp__"))) return true;
|
|
14307
|
-
const commands = actionSummary.commands ?? [];
|
|
14308
|
-
if (commands.some((c) => FILE_MUTATE_RE.test(c))) return true;
|
|
14309
|
-
if (sessionAuthoredCode) {
|
|
14310
|
-
const allSafe = commands.length > 0 && commands.every(isNonAuthoringCommand);
|
|
14311
|
-
if (!allSafe) return true;
|
|
14312
|
-
}
|
|
14313
|
-
return false;
|
|
14314
|
-
}
|
|
14315
|
-
function scopeToAuthored(files, actionSummary) {
|
|
14316
|
-
if (!actionSummary) return { files, signal: "no-transcript" };
|
|
14317
|
-
const touched = [...actionSummary.files_edited ?? [], ...actionSummary.files_created ?? []];
|
|
14318
|
-
if (touched.length === 0) return { files: [], signal: "none-authored" };
|
|
14319
|
-
return { files: narrowToAgentAuthored(files, actionSummary), signal: "authored" };
|
|
14320
|
-
}
|
|
14321
|
-
function narrowToAgentAuthored(files, actionSummary) {
|
|
14322
|
-
if (!actionSummary) return files;
|
|
14323
|
-
const touched = [
|
|
14324
|
-
...actionSummary.files_edited,
|
|
14325
|
-
...actionSummary.files_created
|
|
14326
|
-
];
|
|
14327
|
-
if (touched.length === 0) return files;
|
|
14328
|
-
return files.filter((f) => {
|
|
14329
|
-
const suffix = "/" + f;
|
|
14330
|
-
return touched.some((t) => t === f || t.endsWith(suffix));
|
|
14331
|
-
});
|
|
14332
|
-
}
|
|
14333
|
-
|
|
14334
|
-
// src/lib/skip-detection.ts
|
|
14335
|
-
function isBareAckPrompt(prompt) {
|
|
14336
|
-
if (typeof prompt !== "string") return false;
|
|
14337
|
-
const trimmed = prompt.trim();
|
|
14338
|
-
if (trimmed.length === 0) return false;
|
|
14339
|
-
if (trimmed.length > 20) return false;
|
|
14340
|
-
const bareAckPattern = /^(\d{1,2}|y|n|yes|no|yep|nope|ok(ay)?|sure|skip|cancel|stop|done|noted|got\s+it|sounds\s+good|thanks|thank\s+you|thx)[.!?]*$/i;
|
|
14341
|
-
return bareAckPattern.test(trimmed);
|
|
14342
|
-
}
|
|
14343
|
-
function isContinuationPrompt(prompt) {
|
|
14344
|
-
if (typeof prompt !== "string") return false;
|
|
14345
|
-
const trimmed = prompt.trim();
|
|
14346
|
-
if (trimmed.length === 0) return false;
|
|
14347
|
-
if (trimmed.length > 24) return false;
|
|
14348
|
-
const continuation = /^(let['’]?s\s+(go|do\s+it|start|continue)|go|go\s+ahead|go\s+on|proceed|continue|carry\s+on|keep\s+going|do\s+it|make\s+it\s+so|next|start|begin|ship\s+it|yes\s+please|please\s+continue|perfect|great|nice|excellent|agreed)[.!]*$/i;
|
|
14349
|
-
return continuation.test(trimmed) || isBareAckPrompt(trimmed);
|
|
14350
|
-
}
|
|
14351
|
-
function resolveGoalPrompt(prompts) {
|
|
14352
|
-
if (prompts.length === 0) return null;
|
|
14353
|
-
const latest = prompts[prompts.length - 1];
|
|
14354
|
-
if (!isContinuationPrompt(latest.prompt)) return { entry: latest, turnsBack: 0 };
|
|
14355
|
-
for (let i = prompts.length - 2; i >= 0; i--) {
|
|
14356
|
-
if (!isContinuationPrompt(prompts[i].prompt)) {
|
|
14357
|
-
return { entry: prompts[i], turnsBack: prompts.length - 1 - i };
|
|
14292
|
+
function findMarker(text, marker, from = 0) {
|
|
14293
|
+
for (const { line, start, inFence } of scanLines(text)) {
|
|
14294
|
+
if (!inFence && start >= from && line.startsWith(marker)) {
|
|
14295
|
+
return start;
|
|
14358
14296
|
}
|
|
14359
14297
|
}
|
|
14360
|
-
return
|
|
14298
|
+
return -1;
|
|
14361
14299
|
}
|
|
14362
|
-
function
|
|
14363
|
-
|
|
14364
|
-
const markers = [
|
|
14365
|
-
/reflection\s+for\s+future\s+agents/i,
|
|
14366
|
-
/what(?:'s|\s+is)\s+one\s+thing\s+you\s+learned/i,
|
|
14367
|
-
/say\s+['"]?skip['"]?\s+to\s+skip/i,
|
|
14368
|
-
/quick\s+reflection\s+question/i,
|
|
14369
|
-
// Post-flip (VRT-21): the agent drafts the reflection itself and, when
|
|
14370
|
-
// interactive, asks the user to confirm/correct before recording. That
|
|
14371
|
-
// turn authors no code either, so it's still a reflection turn.
|
|
14372
|
-
/reflection\s+draft/i,
|
|
14373
|
-
/confirm,?\s+correct,?\s+or\s+add/i
|
|
14374
|
-
];
|
|
14375
|
-
return markers.some((m) => m.test(response));
|
|
14300
|
+
function hasLegacyMemoryBlock(text) {
|
|
14301
|
+
return findMarker(text, LEGACY_MD_START) !== -1;
|
|
14376
14302
|
}
|
|
14377
|
-
function
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14390
|
-
|
|
14303
|
+
async function ensureClaudeMdPointer(cwd = repoRoot()) {
|
|
14304
|
+
const claudeMdPath = (0, import_node_path10.join)(cwd, "CLAUDE.md");
|
|
14305
|
+
let existing = "";
|
|
14306
|
+
if ((0, import_node_fs12.existsSync)(claudeMdPath)) {
|
|
14307
|
+
existing = await (0, import_promises8.readFile)(claudeMdPath, "utf-8");
|
|
14308
|
+
}
|
|
14309
|
+
let startTag = CLAUDE_MD_START;
|
|
14310
|
+
let endTag = CLAUDE_MD_END;
|
|
14311
|
+
let startIdx = findMarker(existing, CLAUDE_MD_START);
|
|
14312
|
+
let endIdx = startIdx === -1 ? -1 : findMarker(existing, CLAUDE_MD_END, startIdx + CLAUDE_MD_START.length);
|
|
14313
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
14314
|
+
const legacyStart = findMarker(existing, LEGACY_MD_START);
|
|
14315
|
+
const legacyEnd = legacyStart === -1 ? -1 : findMarker(existing, LEGACY_MD_END, legacyStart + LEGACY_MD_START.length);
|
|
14316
|
+
if (legacyStart !== -1 && legacyEnd !== -1) {
|
|
14317
|
+
startTag = LEGACY_MD_START;
|
|
14318
|
+
endTag = LEGACY_MD_END;
|
|
14319
|
+
startIdx = legacyStart;
|
|
14320
|
+
endIdx = legacyEnd;
|
|
14321
|
+
} else if (startIdx !== -1 && endIdx === -1 || legacyStart !== -1 && legacyEnd === -1) {
|
|
14322
|
+
printWarn(
|
|
14323
|
+
"CLAUDE.md: found an unterminated memory marker (a start marker with no matching end marker). Leaving the file untouched to avoid erasing your text \u2014 please close or remove the stray marker so Verity can manage the block."
|
|
14324
|
+
);
|
|
14325
|
+
return;
|
|
14326
|
+
}
|
|
14327
|
+
}
|
|
14328
|
+
const hasBlock = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx;
|
|
14329
|
+
let preserved = PRESERVE_PLACEHOLDER;
|
|
14330
|
+
if (hasBlock) {
|
|
14331
|
+
const interior = existing.slice(startIdx + startTag.length, endIdx);
|
|
14332
|
+
const captured = extractPreserveContent(interior);
|
|
14333
|
+
if (captured !== null) {
|
|
14334
|
+
preserved = captured;
|
|
14335
|
+
} else {
|
|
14336
|
+
const rescued = stripKnownProse(interior).trim();
|
|
14337
|
+
if (rescued) {
|
|
14338
|
+
preserved = rescued;
|
|
14339
|
+
printWarn(
|
|
14340
|
+
`CLAUDE.md: moved ${rescued.split("\n").length} line(s) of hand-edited text from inside the verity-memory markers into a preserve region (it now survives regeneration). Put durable guidance OUTSIDE the markers to keep it fully under your control.`
|
|
14341
|
+
);
|
|
14342
|
+
}
|
|
14343
|
+
}
|
|
14344
|
+
}
|
|
14345
|
+
const block = [
|
|
14346
|
+
CLAUDE_MD_START,
|
|
14347
|
+
CLAUDE_MD_PROSE,
|
|
14348
|
+
"",
|
|
14349
|
+
PRESERVE_START,
|
|
14350
|
+
preserved,
|
|
14351
|
+
PRESERVE_END,
|
|
14352
|
+
CLAUDE_MD_END
|
|
14353
|
+
].join("\n");
|
|
14354
|
+
let next;
|
|
14355
|
+
if (hasBlock) {
|
|
14356
|
+
next = existing.slice(0, startIdx) + block + existing.slice(endIdx + endTag.length);
|
|
14357
|
+
} else if (existing.trim() === "") {
|
|
14358
|
+
next = block + "\n";
|
|
14359
|
+
} else {
|
|
14360
|
+
next = existing.replace(/\n*$/, "") + "\n\n" + block + "\n";
|
|
14361
|
+
}
|
|
14362
|
+
if (next === existing) return;
|
|
14363
|
+
await (0, import_promises8.writeFile)(claudeMdPath, next);
|
|
14391
14364
|
}
|
|
14392
|
-
function
|
|
14393
|
-
|
|
14394
|
-
|
|
14395
|
-
|
|
14365
|
+
function extractPreserveContent(interior) {
|
|
14366
|
+
for (const [start, end] of [
|
|
14367
|
+
[PRESERVE_START, PRESERVE_END],
|
|
14368
|
+
[LEGACY_PRESERVE_START, LEGACY_PRESERVE_END]
|
|
14369
|
+
]) {
|
|
14370
|
+
const s = interior.indexOf(start);
|
|
14371
|
+
const e = interior.indexOf(end);
|
|
14372
|
+
if (s !== -1 && e !== -1 && e >= s) {
|
|
14373
|
+
return interior.slice(s + start.length, e).replace(/^\n+|\n+$/g, "");
|
|
14374
|
+
}
|
|
14375
|
+
}
|
|
14376
|
+
return null;
|
|
14396
14377
|
}
|
|
14397
|
-
function
|
|
14398
|
-
|
|
14399
|
-
|
|
14400
|
-
|
|
14401
|
-
|
|
14402
|
-
|
|
14403
|
-
|
|
14404
|
-
|
|
14378
|
+
function stripKnownProse(interior) {
|
|
14379
|
+
const trimmed = interior.replace(/^\n+/, "");
|
|
14380
|
+
for (const prose of [
|
|
14381
|
+
CLAUDE_MD_PROSE,
|
|
14382
|
+
CLAUDE_MD_PROSE_PRE_REFLECT,
|
|
14383
|
+
CLAUDE_MD_PROSE_PRE_WAIVE,
|
|
14384
|
+
CLAUDE_MD_PROSE_PRE_IGNORE,
|
|
14385
|
+
CLAUDE_MD_PROSE_LEGACY
|
|
14386
|
+
]) {
|
|
14387
|
+
if (trimmed.startsWith(prose)) return trimmed.slice(prose.length);
|
|
14388
|
+
}
|
|
14389
|
+
return trimmed;
|
|
14405
14390
|
}
|
|
14391
|
+
var SCHEMA_TEMPLATE = `# Memory Graph Schema (v1)
|
|
14392
|
+
|
|
14393
|
+
## Node format
|
|
14394
|
+
|
|
14395
|
+
Each node is a markdown file with YAML frontmatter:
|
|
14396
|
+
|
|
14397
|
+
\`\`\`yaml
|
|
14398
|
+
---
|
|
14399
|
+
schema: 1
|
|
14400
|
+
id: n001-slug
|
|
14401
|
+
kind: decision | quality | security | intent | gotcha | pattern | domain | integration
|
|
14402
|
+
title: "Short title (\u2264200 chars)"
|
|
14403
|
+
domains: [tag1, tag2]
|
|
14404
|
+
file_globs: ["src/auth/**"]
|
|
14405
|
+
confidence: 0.5-1.0
|
|
14406
|
+
status: active | archived | superseded | orphan_flagged
|
|
14407
|
+
source: extractor | user | imported
|
|
14408
|
+
# ... (see full schema in MEMORY-GRAPH-PRD \xA76.3)
|
|
14409
|
+
---
|
|
14410
|
+
|
|
14411
|
+
# Title
|
|
14412
|
+
|
|
14413
|
+
Body content (\u22648KB). Use [[node-id]] wikilinks for cross-references.
|
|
14414
|
+
\`\`\`
|
|
14415
|
+
|
|
14416
|
+
## Edge types
|
|
14417
|
+
|
|
14418
|
+
| Edge | Meaning |
|
|
14419
|
+
|------|---------|
|
|
14420
|
+
| related | Loose association |
|
|
14421
|
+
| supersedes | A replaces B |
|
|
14422
|
+
| contradicts | A and B disagree |
|
|
14423
|
+
| caused_by | Something in B led to A |
|
|
14424
|
+
| example_of | A is an instance of B |
|
|
14425
|
+
|
|
14426
|
+
## Domains
|
|
14427
|
+
|
|
14428
|
+
| Directory | Purpose |
|
|
14429
|
+
|-----------|---------|
|
|
14430
|
+
| decisions/ | Architectural choices (ADR-style) |
|
|
14431
|
+
| quality/ | Quality patterns |
|
|
14432
|
+
| security/ | Security constraints |
|
|
14433
|
+
| intent/ | Intent templates |
|
|
14434
|
+
| gotchas/ | Footguns and surprises |
|
|
14435
|
+
| patterns/ | Code conventions |
|
|
14436
|
+
| domain/ | Business logic concepts |
|
|
14437
|
+
| integrations/ | External system knowledge |
|
|
14438
|
+
| _archive/ | Superseded nodes |
|
|
14439
|
+
`;
|
|
14440
|
+
|
|
14441
|
+
// src/lib/dossier-session.ts
|
|
14442
|
+
var import_node_fs17 = require("node:fs");
|
|
14443
|
+
var import_node_crypto7 = require("node:crypto");
|
|
14444
|
+
var import_node_path13 = require("node:path");
|
|
14406
14445
|
|
|
14407
14446
|
// src/lib/pending-repeat.ts
|
|
14408
14447
|
var STOP = /* @__PURE__ */ new Set([
|
|
@@ -15863,15 +15902,18 @@ function registerIntentCommands(program2) {
|
|
|
15863
15902
|
if (deferredToPlugin("intent capture", event.session_id ?? process.env.CLAUDE_SESSION_ID ?? null)) {
|
|
15864
15903
|
process.exit(0);
|
|
15865
15904
|
}
|
|
15905
|
+
const isPrimitive = isSlashCommand(prompt);
|
|
15866
15906
|
const authForScope = await resolveToken(program2.opts().token);
|
|
15867
15907
|
const scopeToken = authForScope.ok ? authForScope.data.token : void 0;
|
|
15868
15908
|
const scopeSession = event.session_id || process.env.CLAUDE_SESSION_ID || "";
|
|
15869
|
-
await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
|
|
15870
|
-
|
|
15871
|
-
|
|
15872
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15909
|
+
if (!isPrimitive) await appendToConversationBuffer(prompt, sessionScopeKey(scopeToken, scopeSession));
|
|
15910
|
+
if (!isPrimitive) {
|
|
15911
|
+
try {
|
|
15912
|
+
const tok = await resolveToken(globals.token);
|
|
15913
|
+
const session = sessionDossier(tok.ok ? tok.data.token : null, event.session_id ?? null);
|
|
15914
|
+
if (session) recordGoal(session.d, prompt);
|
|
15915
|
+
} catch {
|
|
15916
|
+
}
|
|
15875
15917
|
}
|
|
15876
15918
|
try {
|
|
15877
15919
|
await ensureMemoryDir();
|
|
@@ -17807,6 +17849,9 @@ function createRun(opts, globals) {
|
|
|
17807
17849
|
};
|
|
17808
17850
|
}
|
|
17809
17851
|
|
|
17852
|
+
// src/commands/analyze/index.ts
|
|
17853
|
+
var import_node_fs38 = require("node:fs");
|
|
17854
|
+
|
|
17810
17855
|
// src/lib/repo-context.ts
|
|
17811
17856
|
var import_node_child_process7 = require("node:child_process");
|
|
17812
17857
|
var import_node_os3 = require("node:os");
|
|
@@ -18831,21 +18876,19 @@ function refResolves(frame, ref) {
|
|
|
18831
18876
|
return frameGit(frame, ["rev-parse", "--verify", "-q", `${ref}^{commit}`]) !== "";
|
|
18832
18877
|
}
|
|
18833
18878
|
var SHA_RE2 = /^[0-9a-f]{40}$/;
|
|
18834
|
-
function
|
|
18835
|
-
if (!frame.worktreeRoot) return null;
|
|
18836
|
-
|
|
18837
|
-
|
|
18838
|
-
|
|
18839
|
-
|
|
18840
|
-
|
|
18841
|
-
return
|
|
18879
|
+
function stagedRange(frame) {
|
|
18880
|
+
if (!frame.worktreeRoot) return { kind: "nothing", base: null, head: "INDEX", via: "refused" };
|
|
18881
|
+
const mergeHead = frame.gitDir ? (0, import_node_path19.join)(frame.gitDir, "MERGE_HEAD") : null;
|
|
18882
|
+
if (mergeHead && (0, import_node_fs26.existsSync)(mergeHead)) {
|
|
18883
|
+
const vsHead = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "HEAD"]).split("\n").filter(Boolean));
|
|
18884
|
+
const vsMerge = new Set(frameGit(frame, ["diff", "--cached", "--name-only", "MERGE_HEAD"]).split("\n").filter(Boolean));
|
|
18885
|
+
const resolutions = [...vsHead].filter((f) => vsMerge.has(f) && !isVerityOwnedPath(f));
|
|
18886
|
+
return { kind: "merge", base: "HEAD", head: "INDEX", via: "merge-resolutions", files: resolutions };
|
|
18842
18887
|
}
|
|
18843
|
-
}
|
|
18844
|
-
function stagedRange() {
|
|
18845
18888
|
return { kind: "staged", base: "HEAD", head: "INDEX", via: "index" };
|
|
18846
18889
|
}
|
|
18847
18890
|
function resolvePushRange(frame, command, on) {
|
|
18848
|
-
const nothing = (
|
|
18891
|
+
const nothing = (via2) => ({ kind: "nothing", base: null, head: "HEAD", via: via2 });
|
|
18849
18892
|
if (!frame.worktreeRoot) return nothing("refused");
|
|
18850
18893
|
const found = findMomentSegment(command, on);
|
|
18851
18894
|
const segment = found ? splitSegments(command)[found.segmentIndex] : "";
|
|
@@ -18853,54 +18896,32 @@ function resolvePushRange(frame, command, on) {
|
|
|
18853
18896
|
if (target.isDelete) return nothing("deletion");
|
|
18854
18897
|
const head = target.srcRef ?? "HEAD";
|
|
18855
18898
|
if (!refResolves(frame, head)) return nothing(`src-unresolvable:${head}`);
|
|
18856
|
-
const
|
|
18857
|
-
const
|
|
18858
|
-
const
|
|
18859
|
-
if (
|
|
18860
|
-
|
|
18861
|
-
|
|
18862
|
-
|
|
18863
|
-
|
|
18864
|
-
|
|
18865
|
-
|
|
18866
|
-
|
|
18867
|
-
candidates.push({ ref: srcName ? `${srcName}@{upstream}` : "@{upstream}", via: "@{upstream}" });
|
|
18868
|
-
if (branchForRemote) {
|
|
18869
|
-
candidates.push({
|
|
18870
|
-
ref: `refs/remotes/origin/${branchForRemote.replace(/^refs\/heads\//, "")}`,
|
|
18871
|
-
via: `origin/${branchForRemote.replace(/^refs\/heads\//, "")}`
|
|
18872
|
-
});
|
|
18873
|
-
}
|
|
18874
|
-
for (const c of candidates) {
|
|
18875
|
-
if (!refResolves(frame, c.ref)) continue;
|
|
18876
|
-
const mergeBase = frameGit(frame, ["merge-base", c.ref, head]);
|
|
18877
|
-
if (SHA_RE2.test(mergeBase)) return { kind: "push", base: mergeBase, head, via: c.via };
|
|
18899
|
+
const remotePattern = target.remote ? `--remotes=${target.remote}` : "--remotes";
|
|
18900
|
+
const via = target.remote ? `publication:${target.remote}` : "publication";
|
|
18901
|
+
const commits = frameGit(frame, ["rev-list", head, "--not", remotePattern]).split("\n").filter(Boolean);
|
|
18902
|
+
if (commits.length === 0) return { kind: "nothing", base: null, head, via: "already-published" };
|
|
18903
|
+
const boundary = frameGit(frame, ["rev-list", head, "--not", remotePattern, "--boundary"]).split("\n").filter((l) => l.startsWith("-")).map((l) => l.slice(1));
|
|
18904
|
+
const base = boundary.find((s) => SHA_RE2.test(s)) ?? null;
|
|
18905
|
+
const files = /* @__PURE__ */ new Set();
|
|
18906
|
+
for (const sha of commits) {
|
|
18907
|
+
for (const f of frameGit(frame, ["diff-tree", "--no-commit-id", "--name-only", "-r", sha]).split("\n")) {
|
|
18908
|
+
if (f && !isVerityOwnedPath(f)) files.add(f);
|
|
18909
|
+
}
|
|
18878
18910
|
}
|
|
18879
|
-
|
|
18880
|
-
if (baseline) return { kind: "baseline", base: baseline, head, via: "review-baseline" };
|
|
18881
|
-
if (refResolves(frame, `${head}~1`)) return { kind: "last-commit", base: `${head}~1`, head, via: `${head}~1` };
|
|
18882
|
-
return nothing("no-parent");
|
|
18911
|
+
return { kind: "push", base, head, via, files: [...files], commits };
|
|
18883
18912
|
}
|
|
18884
18913
|
function rangeFiles(frame, range) {
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
|
|
18888
|
-
out = frameGit(frame, ["diff", "--cached", "--name-only"]);
|
|
18889
|
-
break;
|
|
18890
|
-
case "push":
|
|
18891
|
-
case "baseline":
|
|
18892
|
-
case "last-commit":
|
|
18893
|
-
out = frameGit(frame, ["diff", "--name-only", range.base, range.head === "INDEX" ? "HEAD" : range.head]);
|
|
18894
|
-
break;
|
|
18895
|
-
case "nothing":
|
|
18896
|
-
return [];
|
|
18914
|
+
if (range.files) return range.files.filter((f) => !isVerityOwnedPath(f));
|
|
18915
|
+
if (range.kind === "staged") {
|
|
18916
|
+
return frameGit(frame, ["diff", "--cached", "--name-only"]).split("\n").filter((l) => l.length > 0).filter((f) => !isVerityOwnedPath(f));
|
|
18897
18917
|
}
|
|
18898
|
-
return
|
|
18918
|
+
return [];
|
|
18899
18919
|
}
|
|
18900
18920
|
function rangeChangeSignals(frame, range, paths) {
|
|
18901
18921
|
const out = /* @__PURE__ */ new Map();
|
|
18902
18922
|
if (range.kind === "nothing" || paths.length === 0) return out;
|
|
18903
|
-
|
|
18923
|
+
if (range.kind === "push" && !range.base) return out;
|
|
18924
|
+
const args = range.kind === "staged" || range.kind === "merge" ? ["diff", "--cached", "--unified=0"] : ["diff", "--unified=0", range.base, range.head === "INDEX" ? "HEAD" : range.head];
|
|
18904
18925
|
const diff = frameGit(frame, [...args, "--", ...paths]);
|
|
18905
18926
|
let current = null;
|
|
18906
18927
|
let oldSide = null;
|
|
@@ -18932,8 +18953,9 @@ function rangeChangeSignals(frame, range, paths) {
|
|
|
18932
18953
|
return out;
|
|
18933
18954
|
}
|
|
18934
18955
|
function rangeMessages(frame, range) {
|
|
18935
|
-
if (range.kind
|
|
18936
|
-
|
|
18956
|
+
if (range.kind !== "push" || !range.commits || range.commits.length === 0) return "";
|
|
18957
|
+
const commits = range.commits.slice(0, 100);
|
|
18958
|
+
return frameGit(frame, ["show", "-s", "--format=%B%x00", ...commits]).split("\0").map((s) => s.trim()).filter(Boolean).join("\n\n");
|
|
18937
18959
|
}
|
|
18938
18960
|
function frameTelemetry(frame, range, divergence) {
|
|
18939
18961
|
const t = {
|
|
@@ -19515,7 +19537,7 @@ function channelSilence(input) {
|
|
|
19515
19537
|
// src/lib/cli-version.ts
|
|
19516
19538
|
function cliVersion() {
|
|
19517
19539
|
try {
|
|
19518
|
-
return true ? "0.31.
|
|
19540
|
+
return true ? "0.31.4" : "dev";
|
|
19519
19541
|
} catch {
|
|
19520
19542
|
return "dev";
|
|
19521
19543
|
}
|
|
@@ -20734,7 +20756,20 @@ async function evidence(run2) {
|
|
|
20734
20756
|
widened_to: allForReview.length
|
|
20735
20757
|
});
|
|
20736
20758
|
}
|
|
20737
|
-
const
|
|
20759
|
+
const authorshipWasObservable = scoped.signal === "authored" && actionSummary?.transcript_windowed !== "orphaned";
|
|
20760
|
+
if (!narrowingIsTrustworthy && recoveredScope.length === 0 && authorshipWasObservable) {
|
|
20761
|
+
logEvent("authored_nothing_reviewable", {
|
|
20762
|
+
touched: (actionSummary?.files_edited?.length ?? 0) + (actionSummary?.files_created?.length ?? 0),
|
|
20763
|
+
candidates: allForReview.length
|
|
20764
|
+
});
|
|
20765
|
+
}
|
|
20766
|
+
const baseForReview = chooseReviewScope({
|
|
20767
|
+
narrowingIsTrustworthy,
|
|
20768
|
+
scopedFiles: scoped.files,
|
|
20769
|
+
recoveredScope,
|
|
20770
|
+
allForReview,
|
|
20771
|
+
authorshipWasObservable
|
|
20772
|
+
});
|
|
20738
20773
|
const recentForReview = narrowToRecent(baseForReview, baselineSessionId);
|
|
20739
20774
|
if (!opts.skipStatic && isCodacyAvailable()) {
|
|
20740
20775
|
let allScannable = Array.from(/* @__PURE__ */ new Set([...analyzable, ...securityFiles]));
|
|
@@ -22700,6 +22735,10 @@ function registerAnalyzeCommand(program2) {
|
|
|
22700
22735
|
}
|
|
22701
22736
|
var tracing = () => process.env.VERITY_TRACE_PHASES === "1";
|
|
22702
22737
|
async function runAnalyze(opts, globals) {
|
|
22738
|
+
if (!verityConfigured()) {
|
|
22739
|
+
(0, import_node_fs38.writeSync)(2, '[verity] not set up in this project \u2014 run "verity init" first.\n');
|
|
22740
|
+
process.exit(0);
|
|
22741
|
+
}
|
|
22703
22742
|
const run2 = createRun(opts, globals);
|
|
22704
22743
|
installRunEvidence(run2);
|
|
22705
22744
|
for (const [name, phase] of PIPELINE) {
|
|
@@ -22718,7 +22757,6 @@ async function runAnalyze(opts, globals) {
|
|
|
22718
22757
|
}
|
|
22719
22758
|
|
|
22720
22759
|
// src/commands/baseline.ts
|
|
22721
|
-
var import_node_fs38 = require("node:fs");
|
|
22722
22760
|
function registerBaselineCommands(program2) {
|
|
22723
22761
|
const baseline = program2.command("baseline").description("Manage the task-start working-tree baseline");
|
|
22724
22762
|
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) => {
|
|
@@ -22727,7 +22765,16 @@ function registerBaselineCommands(program2) {
|
|
|
22727
22765
|
process.chdir(repoRoot());
|
|
22728
22766
|
} catch {
|
|
22729
22767
|
}
|
|
22730
|
-
if (!(
|
|
22768
|
+
if (!verityConfigured()) {
|
|
22769
|
+
const how = isPluginInvocation() ? "Offer to run /verity:setup for the user." : "Offer to run `verity init` (or /verity-setup) for the user.";
|
|
22770
|
+
process.stdout.write(
|
|
22771
|
+
JSON.stringify({
|
|
22772
|
+
hookSpecificOutput: {
|
|
22773
|
+
hookEventName: "SessionStart",
|
|
22774
|
+
additionalContext: `Verity is installed but this project is not set up yet, so the quality gate will not review anything here. ${how}`
|
|
22775
|
+
}
|
|
22776
|
+
}) + "\n"
|
|
22777
|
+
);
|
|
22731
22778
|
process.exit(0);
|
|
22732
22779
|
}
|
|
22733
22780
|
let sessionId = opts.sessionId;
|
|
@@ -22817,8 +22864,8 @@ async function runReview(opts, globals) {
|
|
|
22817
22864
|
for (const p of specPaths) {
|
|
22818
22865
|
if (!(0, import_node_fs39.existsSync)(p)) continue;
|
|
22819
22866
|
try {
|
|
22820
|
-
const { readFileSync:
|
|
22821
|
-
const content =
|
|
22867
|
+
const { readFileSync: readFileSync26 } = await import("node:fs");
|
|
22868
|
+
const content = readFileSync26(p, "utf-8");
|
|
22822
22869
|
specs.push({ path: p, content: content.slice(0, 10240) });
|
|
22823
22870
|
} catch {
|
|
22824
22871
|
}
|
|
@@ -22975,13 +23022,14 @@ function registerGuardCommand(program2) {
|
|
|
22975
23022
|
});
|
|
22976
23023
|
}
|
|
22977
23024
|
function resolveMomentRange(moment, frame, command, on) {
|
|
22978
|
-
return moment === "pre-commit" ? stagedRange() : resolvePushRange(frame, command, on);
|
|
23025
|
+
return moment === "pre-commit" ? stagedRange(frame) : resolvePushRange(frame, command, on);
|
|
22979
23026
|
}
|
|
22980
23027
|
function describeRange(range) {
|
|
22981
23028
|
if (range.kind === "staged") return "staged";
|
|
22982
|
-
if (range.kind === "
|
|
22983
|
-
|
|
22984
|
-
|
|
23029
|
+
if (range.kind === "merge") return `merge (${range.via})`;
|
|
23030
|
+
if (range.kind === "nothing") return range.via === "already-published" ? "already published" : null;
|
|
23031
|
+
const base = range.base && /^[0-9a-f]{40}$/.test(range.base) ? range.base.slice(0, 7) : range.base;
|
|
23032
|
+
return base ? `${base}..${range.head} via ${range.via}` : `via ${range.via}`;
|
|
22985
23033
|
}
|
|
22986
23034
|
function matchFlagValue(command, flags) {
|
|
22987
23035
|
const re = new RegExp(`(?<![\\w-])(?:${flags})(?:=|\\s+)('((?:[^'\\\\]|\\\\.)*)'|"((?:[^"\\\\]|\\\\.)*)"|([^\\s'"-][^\\s]*))`);
|
|
@@ -24645,6 +24693,61 @@ async function reconcileOwnWiring(moments) {
|
|
|
24645
24693
|
printWarn(" Enable one: verity hooks install --moments stop");
|
|
24646
24694
|
}
|
|
24647
24695
|
}
|
|
24696
|
+
async function checkPrerequisites(step) {
|
|
24697
|
+
step("Checking prerequisites");
|
|
24698
|
+
const prereqs = await checkPrereqs({ install: true });
|
|
24699
|
+
for (const c of prereqs.checks) {
|
|
24700
|
+
if (c.status === "ok") {
|
|
24701
|
+
if (c.justInstalled) continue;
|
|
24702
|
+
printInfo(` ${c.label} ${c.detail} \u2713`);
|
|
24703
|
+
} else {
|
|
24704
|
+
printWarn(` ${c.label}: ${c.detail}`);
|
|
24705
|
+
if (c.remedy) printWarn(` ${c.remedy}`);
|
|
24706
|
+
}
|
|
24707
|
+
}
|
|
24708
|
+
if (prereqs.blocked) {
|
|
24709
|
+
printError("A required prerequisite is missing \u2014 cannot continue.");
|
|
24710
|
+
process.exit(1);
|
|
24711
|
+
}
|
|
24712
|
+
return prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
|
|
24713
|
+
}
|
|
24714
|
+
async function scaffoldProject(step, defaultsOnly) {
|
|
24715
|
+
step("Knowledge base, .gitignore and CLAUDE.md");
|
|
24716
|
+
await (0, import_promises14.mkdir)(VERITY_DIR, { recursive: true });
|
|
24717
|
+
await ensureMemoryDir();
|
|
24718
|
+
const ignoreResult = ensureVerityGitignore();
|
|
24719
|
+
if (ignoreResult === "failed") {
|
|
24720
|
+
printWarn(" .gitignore: could not write the Verity block \u2014 add it manually");
|
|
24721
|
+
printWarn(" (.verity/ holds copies of analyzed files, including any secret the gate flagged)");
|
|
24722
|
+
} else if (ignoreResult === "conflict") {
|
|
24723
|
+
printWarn(" .gitignore: the Verity block is in place but git still ignores .verity/standard.yaml");
|
|
24724
|
+
printWarn(" Something outside this file covers it \u2014 a global (~/.gitignore) or nested");
|
|
24725
|
+
printWarn(" .gitignore, or a pattern we do not recognise. Check: git check-ignore -v .verity/standard.yaml");
|
|
24726
|
+
printWarn(" Until it is fixed, the Standard and the knowledge graph cannot be committed.");
|
|
24727
|
+
} else if (ignoreResult === "repaired") {
|
|
24728
|
+
printInfo(" .gitignore: rewrote `.verity/` to `.verity/*` so the standard stays committable \u2713");
|
|
24729
|
+
} else {
|
|
24730
|
+
printInfo(` .gitignore: Verity block ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
|
|
24731
|
+
}
|
|
24732
|
+
const tracked = committedVerityState();
|
|
24733
|
+
if (tracked.length > 0) {
|
|
24734
|
+
printWarn(` ${tracked.length} Verity state file(s) are tracked in git (e.g. ${tracked[0]}).`);
|
|
24735
|
+
const untrack = defaultsOnly ? false : await promptYes(" Untrack them now (files stay on disk)? [Y/n] ", { nonInteractive: false });
|
|
24736
|
+
if (untrack) {
|
|
24737
|
+
const result = untrackVerityState();
|
|
24738
|
+
if (result === "untracked") printInfo(" Untracked (staged) \u2014 commit to finish \u2713");
|
|
24739
|
+
else if (result === "failed") printWarn(' Could not untrack \u2014 run "git rm -r --cached .verity" manually');
|
|
24740
|
+
} else {
|
|
24741
|
+
printWarn(" Left tracked. Fix with: git rm -r --cached .verity && git add .verity/standard.yaml .verity/memory");
|
|
24742
|
+
}
|
|
24743
|
+
}
|
|
24744
|
+
try {
|
|
24745
|
+
await ensureClaudeMdPointer();
|
|
24746
|
+
printInfo(" CLAUDE.md instructions \u2713");
|
|
24747
|
+
} catch (err) {
|
|
24748
|
+
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
24749
|
+
}
|
|
24750
|
+
}
|
|
24648
24751
|
function registerInitCommand(program2) {
|
|
24649
24752
|
program2.command("init").alias("setup").description("Set up Verity in the current project (asks the setup questions, then hands off to /verity-setup)").option("--force", "Reinstall the skills even when they are already up to date (hooks are always reconciled)").option("-y, --yes", "Take the recommended answer for every question (no prompts)").option("--no-setup", "Skip the /verity-setup handoff at the end").option(
|
|
24650
24753
|
"--plugin-mode",
|
|
@@ -24686,22 +24789,7 @@ function registerInitCommand(program2) {
|
|
|
24686
24789
|
}
|
|
24687
24790
|
console.log("");
|
|
24688
24791
|
}
|
|
24689
|
-
step
|
|
24690
|
-
const prereqs = await checkPrereqs({ install: true });
|
|
24691
|
-
for (const c of prereqs.checks) {
|
|
24692
|
-
if (c.status === "ok") {
|
|
24693
|
-
if (c.justInstalled) continue;
|
|
24694
|
-
printInfo(` ${c.label} ${c.detail} \u2713`);
|
|
24695
|
-
} else {
|
|
24696
|
-
printWarn(` ${c.label}: ${c.detail}`);
|
|
24697
|
-
if (c.remedy) printWarn(` ${c.remedy}`);
|
|
24698
|
-
}
|
|
24699
|
-
}
|
|
24700
|
-
if (prereqs.blocked) {
|
|
24701
|
-
printError("A required prerequisite is missing \u2014 cannot continue.");
|
|
24702
|
-
process.exit(1);
|
|
24703
|
-
}
|
|
24704
|
-
const claudeInstalled = prereqs.checks.some((c) => c.id === "claude" && c.status === "ok");
|
|
24792
|
+
const claudeInstalled = await checkPrerequisites(step);
|
|
24705
24793
|
console.log("");
|
|
24706
24794
|
if (pluginMode) {
|
|
24707
24795
|
printInfo("Skipping skills \u2014 the Verity plugin provides them, namespaced as /verity:<name>.");
|
|
@@ -24715,41 +24803,7 @@ function registerInitCommand(program2) {
|
|
|
24715
24803
|
if (defaultsOnly) {
|
|
24716
24804
|
printInfo(` intensity: ${intensity} \xB7 moments: ${moments.join(", ") || "none"} (no questions asked)`);
|
|
24717
24805
|
}
|
|
24718
|
-
step
|
|
24719
|
-
await (0, import_promises14.mkdir)(VERITY_DIR, { recursive: true });
|
|
24720
|
-
await ensureMemoryDir();
|
|
24721
|
-
const ignoreResult = ensureVerityGitignore();
|
|
24722
|
-
if (ignoreResult === "failed") {
|
|
24723
|
-
printWarn(" .gitignore: could not write the Verity block \u2014 add it manually");
|
|
24724
|
-
printWarn(" (.verity/ holds copies of analyzed files, including any secret the gate flagged)");
|
|
24725
|
-
} else if (ignoreResult === "conflict") {
|
|
24726
|
-
printWarn(" .gitignore: the Verity block is in place but git still ignores .verity/standard.yaml");
|
|
24727
|
-
printWarn(" Something outside this file covers it \u2014 a global (~/.gitignore) or nested");
|
|
24728
|
-
printWarn(" .gitignore, or a pattern we do not recognise. Check: git check-ignore -v .verity/standard.yaml");
|
|
24729
|
-
printWarn(" Until it is fixed, the Standard and the knowledge graph cannot be committed.");
|
|
24730
|
-
} else if (ignoreResult === "repaired") {
|
|
24731
|
-
printInfo(" .gitignore: rewrote `.verity/` to `.verity/*` so the standard stays committable \u2713");
|
|
24732
|
-
} else {
|
|
24733
|
-
printInfo(` .gitignore: Verity block ${ignoreResult === "added" ? "added" : "already covered"} \u2713`);
|
|
24734
|
-
}
|
|
24735
|
-
const tracked = committedVerityState();
|
|
24736
|
-
if (tracked.length > 0) {
|
|
24737
|
-
printWarn(` ${tracked.length} Verity state file(s) are tracked in git (e.g. ${tracked[0]}).`);
|
|
24738
|
-
const untrack = defaultsOnly ? false : await promptYes(" Untrack them now (files stay on disk)? [Y/n] ", { nonInteractive: false });
|
|
24739
|
-
if (untrack) {
|
|
24740
|
-
const result = untrackVerityState();
|
|
24741
|
-
if (result === "untracked") printInfo(" Untracked (staged) \u2014 commit to finish \u2713");
|
|
24742
|
-
else if (result === "failed") printWarn(' Could not untrack \u2014 run "git rm -r --cached .verity" manually');
|
|
24743
|
-
} else {
|
|
24744
|
-
printWarn(" Left tracked. Fix with: git rm -r --cached .verity && git add .verity/standard.yaml .verity/memory");
|
|
24745
|
-
}
|
|
24746
|
-
}
|
|
24747
|
-
try {
|
|
24748
|
-
await ensureClaudeMdPointer();
|
|
24749
|
-
printInfo(" CLAUDE.md instructions \u2713");
|
|
24750
|
-
} catch (err) {
|
|
24751
|
-
printWarn(` Could not update CLAUDE.md: ${err.message}`);
|
|
24752
|
-
}
|
|
24806
|
+
await scaffoldProject(step, defaultsOnly);
|
|
24753
24807
|
const globalVerityDir = (0, import_node_path29.join)(process.env.HOME ?? "", ".verity");
|
|
24754
24808
|
await (0, import_promises14.mkdir)(globalVerityDir, { recursive: true });
|
|
24755
24809
|
console.log("");
|
|
@@ -24811,7 +24865,7 @@ function registerInitCommand(program2) {
|
|
|
24811
24865
|
...telemetryChoice ? { telemetry: telemetryChoice } : {},
|
|
24812
24866
|
init: {
|
|
24813
24867
|
completed_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
24814
|
-
cli_version: true ? "0.31.
|
|
24868
|
+
cli_version: true ? "0.31.4" : "dev"
|
|
24815
24869
|
}
|
|
24816
24870
|
});
|
|
24817
24871
|
} catch (err) {
|
|
@@ -25480,8 +25534,8 @@ function registerTelemetryCommands(program2) {
|
|
|
25480
25534
|
}
|
|
25481
25535
|
|
|
25482
25536
|
// src/cli.ts
|
|
25483
|
-
program.name("verity").description("CLI for Verity quality gate service").version("0.31.
|
|
25484
|
-
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.
|
|
25537
|
+
program.name("verity").description("CLI for Verity quality gate service").version("0.31.4").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) => {
|
|
25538
|
+
installStderrLog(actionCommand.name(), process.argv.slice(2), "0.31.4");
|
|
25485
25539
|
setUserNamedServiceUrl(program.opts().serviceUrl);
|
|
25486
25540
|
try {
|
|
25487
25541
|
await foldLegacyLocalCredential();
|