@su-record/vibe 4.2.19 → 4.2.21

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.
@@ -15,19 +15,25 @@ export function knowledgeDir(root) {
15
15
  const defaultHome = () => process.env['VIBE_HOME_DIR'] ?? os.homedir();
16
16
  /** Conversation turns without a file change before a knowledge turn; 0 keeps only the file-change trigger. */
17
17
  export const DEFAULT_KNOWLEDGE_TURNS = 5;
18
+ /** Changed wiki pages, and days with a changed page, before a knowledge turn also reviews them; 0 pages turns review off. */
19
+ export const DEFAULT_REVIEW_PAGES = 10;
20
+ export const DEFAULT_REVIEW_DAYS = 14;
18
21
  export function knowledgeSettingsFile(home = defaultHome()) {
19
22
  return path.join(home, '.config', 'vibe', 'knowledge.json');
20
23
  }
21
24
  export function knowledgeSettings(home = defaultHome()) {
22
25
  const raw = readJson(knowledgeSettingsFile(home)) ?? {};
23
- const { dir, turns, exclude } = raw;
26
+ const { dir, turns, exclude, review, reviewDays } = raw;
27
+ const count = (value, fallback) => (Number.isInteger(value) && value >= 0 ? value : fallback);
24
28
  return {
25
29
  auto: raw['auto'] === true,
26
30
  ...(typeof dir === 'string' && path.isAbsolute(dir) ? { dir } : {}),
27
- turns: Number.isInteger(turns) && turns >= 0 ? turns : DEFAULT_KNOWLEDGE_TURNS,
31
+ turns: count(turns, DEFAULT_KNOWLEDGE_TURNS),
28
32
  skills: raw['skills'] === 'create' ? 'create' : 'propose',
29
33
  exclude: Array.isArray(exclude) ? exclude.filter((e) => typeof e === 'string' && path.isAbsolute(e)) : [],
30
34
  journal: raw['journal'] !== false,
35
+ review: count(review, DEFAULT_REVIEW_PAGES),
36
+ reviewDays: count(reviewDays, DEFAULT_REVIEW_DAYS),
31
37
  };
32
38
  }
33
39
  function absolute(flag, value) {
@@ -38,8 +44,10 @@ function absolute(flag, value) {
38
44
  /** Apply the person's change; a folder must be absolute, and what the change leaves out is kept. */
39
45
  export function setKnowledgeSettings(change, home = defaultHome()) {
40
46
  const current = knowledgeSettings(home);
41
- if (change.turns !== undefined && !(Number.isInteger(change.turns) && change.turns >= 0))
42
- throw usage('--turns takes a whole number, 0 or more');
47
+ for (const [flag, value] of [['turns', change.turns], ['review', change.review], ['review-days', change.reviewDays]]) {
48
+ if (value !== undefined && !(Number.isInteger(value) && value >= 0))
49
+ throw usage(`--${flag} takes a whole number, 0 or more`);
50
+ }
43
51
  if (change.skills !== undefined && !['propose', 'create'].includes(change.skills))
44
52
  throw usage('--skills takes propose | create');
45
53
  if (change.journal !== undefined && !['on', 'off'].includes(change.journal))
@@ -53,6 +61,8 @@ export function setKnowledgeSettings(change, home = defaultHome()) {
53
61
  skills: change.skills ?? current.skills,
54
62
  exclude: [...new Set([...current.exclude, ...(exclude ? [exclude] : [])])].filter(folder => folder !== include),
55
63
  journal: change.journal === undefined ? current.journal : change.journal === 'on',
64
+ review: change.review ?? current.review,
65
+ reviewDays: change.reviewDays ?? current.reviewDays,
56
66
  };
57
67
  writeJson(knowledgeSettingsFile(home), next);
58
68
  return next;
@@ -1 +1 @@
1
- {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/core/knowledge.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"knowledge.js","sourceRoot":"","sources":["../../src/core/knowledge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import os from 'node:os';
2
+ import { createRequire } from 'node:module';
3
+ import { usage } from './errors.js';
4
+ import { writeJson } from './store.js';
5
+ // Shared with the Bash hooks, which keep the gate's memory; the CLI reads it and writes the person's setting.
6
+ const gate = createRequire(import.meta.url)('../../hooks/retest.cjs');
7
+ const defaultHome = () => process.env['VIBE_HOME_DIR'] ?? os.homedir();
8
+ /** What the retest gate did in this work tree over 30 days, with the person's setting. */
9
+ export function retestSummary(cwd, home = defaultHome()) {
10
+ return gate.summary(cwd, home);
11
+ }
12
+ export function setRetestMode(value, home = defaultHome()) {
13
+ if (!gate.MODES.includes(value))
14
+ throw usage(`retest takes ${gate.MODES.join(' | ')}`);
15
+ writeJson(gate.settingsFile(home), { mode: value });
16
+ return gate.mode(home);
17
+ }
18
+ export const { testCommand } = gate;
19
+ //# sourceMappingURL=retest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retest.js","sourceRoot":"","sources":["../../src/core/retest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAWvC,8GAA8G;AAC9G,MAAM,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,wBAAwB,CAMnE,CAAC;AAEF,MAAM,WAAW,GAAG,GAAW,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;AAE/E,0FAA0F;AAC1F,MAAM,UAAU,aAAa,CAAC,GAAW,EAAE,OAAe,WAAW,EAAE;IACrE,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa,EAAE,OAAe,WAAW,EAAE;IACvE,IAAI,CAAE,IAAI,CAAC,KAAkB,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,MAAM,KAAK,CAAC,gBAAgB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACrG,SAAS,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,IAAI,CAAC"}
@@ -12,7 +12,7 @@ import { pluginTree } from './tree.js';
12
12
  * manifest, the single entry skill and the notification hooks.
13
13
  */
14
14
  const SKILL_NAMES = ['vibe'];
15
- const HOOK_FILES = ['notify.js', 'session.js', 'private-store.cjs', 'session-files.cjs', 'session-state.cjs', 'session-context.cjs', 'session-repair.cjs', 'knowledge-auto.cjs', 'knowledge-projects.cjs', 'knowledge-journal.cjs', 'journal.cjs', 'obsidian.cjs', 'slice-command.cjs', 'slice-store.cjs', 'slice-guard.cjs'];
15
+ const HOOK_FILES = ['notify.js', 'session.js', 'private-store.cjs', 'session-files.cjs', 'session-state.cjs', 'session-context.cjs', 'session-repair.cjs', 'knowledge-auto.cjs', 'knowledge-projects.cjs', 'knowledge-lint.cjs', 'knowledge-upkeep.cjs', 'knowledge-journal.cjs', 'retest.cjs', 'journal.cjs', 'obsidian.cjs', 'slice-command.cjs', 'slice-store.cjs', 'slice-guard.cjs'];
16
16
  export const MARKETPLACE_NAME = 'vibe-local';
17
17
  /** The tree lives under ~/.config/vibe so that `.vibe/` keeps one meaning: a project's records. ≤ 4.1.7 put it at ~/.vibe/plugin. */
18
18
  export function pluginPaths(home = os.homedir()) {
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/install/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/install/plugin.ts"],"names":[],"mappings":"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"}
package/hooks/journal.cjs CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  // UserPromptSubmit (`prompt`) and PostToolUse(Bash) (`tool`): one line in the session-knowledge journal, printing
3
- // nothing. It reads the person's settings before loading anything else, so while session knowledge or its journal
4
- // is off a prompt or a shell call costs only the start of node.
3
+ // nothing, and the retest gate's memory of a passing test command. It reads the person's settings and the command
4
+ // before loading anything else, so while session knowledge or its journal is off a prompt or a non-test shell call
5
+ // costs only the start of node.
5
6
  const fs = require('node:fs');
6
7
  const os = require('node:os');
7
8
  const path = require('node:path');
@@ -28,11 +29,19 @@ function npmOwns() {
28
29
  return false;
29
30
  }
30
31
 
31
- let input = '';
32
- try { input = fs.readFileSync(0, 'utf8'); } catch { /* no payload */ }
33
- if (journalOn() && !(process.argv.includes('--plugin') && npmOwns())) {
34
- try {
35
- const payload = JSON.parse(input);
36
- if (payload && typeof payload === 'object' && !Array.isArray(payload)) require('./knowledge-auto.cjs').observe(kind, payload, home);
37
- } catch { /* the work goes on without a journal line */ }
32
+ let payload = null;
33
+ try {
34
+ const value = JSON.parse(fs.readFileSync(0, 'utf8'));
35
+ if (value && typeof value === 'object' && !Array.isArray(value)) payload = value;
36
+ } catch { /* no payload */ }
37
+ // The retest gate remembers a passing test whatever the journal setting; it loads only for a test command.
38
+ const testLike = kind === 'tool' && /\b(?:test|check|vitest|jest|mocha|ava|pytest|unittest|rspec|phpunit|nextest|verify|cypress|playwright)\b/.test(JSON.stringify(payload?.tool_input?.command ?? ''));
39
+ const journaling = payload !== null && journalOn();
40
+ if ((testLike || journaling) && !(process.argv.includes('--plugin') && npmOwns())) {
41
+ if (testLike) {
42
+ try { require('./retest.cjs').after(payload, home); } catch { /* nothing remembered */ }
43
+ }
44
+ if (journaling) {
45
+ try { require('./knowledge-auto.cjs').observe(kind, payload, home); } catch { /* the work goes on without a journal line */ }
46
+ }
38
47
  }
@@ -11,7 +11,8 @@ const { inside, readPrivate, writePrivate } = require('./private-store.cjs');
11
11
  const { optional, digest, revision, projectFingerprint } = require('./session-files.cjs');
12
12
  const { identity, hostRoot } = require('./session-state.cjs');
13
13
  const { connectVault } = require('./obsidian.cjs');
14
- const { projectName, linkProject, indexProject } = require('./knowledge-projects.cjs');
14
+ const { projectName, linkProject, indexProject, projectPage } = require('./knowledge-projects.cjs');
15
+ const upkeep = require('./knowledge-upkeep.cjs');
15
16
  const journal = require('./knowledge-journal.cjs');
16
17
 
17
18
  const defaultHome = () => process.env.VIBE_HOME_DIR || os.homedir();
@@ -28,6 +29,8 @@ function settings(home) {
28
29
  skills: raw.skills === 'create' ? 'create' : 'propose',
29
30
  exclude: Array.isArray(raw.exclude) ? raw.exclude.filter(e => typeof e === 'string' && path.isAbsolute(e)) : [],
30
31
  journal: raw.journal !== false,
32
+ review: Number.isInteger(raw.review) && raw.review >= 0 ? raw.review : 10,
33
+ reviewDays: Number.isInteger(raw.reviewDays) && raw.reviewDays >= 0 ? raw.reviewDays : 14,
31
34
  };
32
35
  } catch { return { auto: false }; }
33
36
  }
@@ -105,7 +108,7 @@ const REASON = (places, skills, observed) => `[vibe] Knowledge turn: the user tu
105
108
 
106
109
  /** Stop: after a changed turn, or `turns` conversation turns, log the fact and give the model one knowledge turn. */
107
110
  function stop(payload, home = defaultHome()) {
108
- const { auto, dir, turns: every, skills, exclude, journal: on } = settings(home);
111
+ const { auto, dir, turns: every, skills, exclude, journal: on, review, reviewDays } = settings(home);
109
112
  if (!auto || payload.stop_hook_active === true) return {};
110
113
  try {
111
114
  const now = snapshot(payload, home, exclude);
@@ -118,11 +121,13 @@ function stop(payload, home = defaultHome()) {
118
121
  }
119
122
  fs.mkdirSync(dir, { recursive: true });
120
123
  const name = projectName(now.root, home);
124
+ projectPage(dir, name, now.root);
121
125
  fs.appendFileSync(path.join(dir, 'log.md'), logLine(now.root, changed ? 'files changed' : 'conversation', name));
126
+ const wiki = upkeep.tidy(dir, home) + upkeep.review(dir, home, review, reviewDays);
122
127
  // A wiki inside the project changes the files it was just compared with; look again so that is not seen as work.
123
128
  const files = inside(now.root, dir) ? projectFingerprint(now.root) : now.files;
124
129
  writePrivate(now.root, entry(now.root, now.id), JSON.stringify({ files, turns: 0 }), home);
125
- return { decision: 'block', reason: REASON(PLACES(dir, now.root, name), skills, on ? observed(now, home) : '') };
130
+ return { decision: 'block', reason: REASON(PLACES(dir, now.root, name), skills, on ? observed(now, home) : '') + wiki };
126
131
  } catch { return {}; }
127
132
  }
128
133
 
@@ -28,10 +28,32 @@ const CORRECTION = /^\s*(?:no\b|nope\b|wrong\b|not that\b|that'?s not\b|don'?t\b
28
28
  // Tools whose first word says little; their subcommand is the command's meaning.
29
29
  const WITH_SUBCOMMAND = new Set(['git', 'npm', 'npx', 'pnpm', 'yarn', 'gh', 'docker', 'kubectl', 'cargo', 'go', 'make', 'pip', 'pip3', 'uv', 'vibe', 'node', 'python', 'python3', 'deno', 'bun', 'terraform', 'aws', 'gcloud', 'az']);
30
30
 
31
+ /** The commands of a shell line, split at `&&`, `||`, `;`, `|` and line ends outside quotes. */
32
+ function segments(command) {
33
+ const text = Array.isArray(command) ? command.join(' ') : String(command ?? '');
34
+ const out = [];
35
+ let current = '', quote = null;
36
+ for (let i = 0; i < text.length; i++) {
37
+ const c = text[i];
38
+ if (c === '\\' && quote !== "'") { current += c + (text[++i] ?? ''); continue; }
39
+ if (quote) { if (c === quote) quote = null; current += c; continue; }
40
+ if (c === '"' || c === "'") { quote = c; current += c; continue; }
41
+ const pair = text.slice(i, i + 2);
42
+ if (pair === '&&' || pair === '||' || c === ';' || c === '|' || c === '\n') {
43
+ out.push(current); current = '';
44
+ if (pair === '&&' || pair === '||') i++;
45
+ continue;
46
+ }
47
+ current += c;
48
+ }
49
+ out.push(current);
50
+ return out.map(s => s.trim()).filter(Boolean);
51
+ }
52
+
31
53
  /** The command words of a shell line, `git push` or `npm run`, without arguments, values or paths. */
32
54
  function commandHeads(command) {
33
55
  const heads = [];
34
- for (const segment of String(command).split(/&&|\|\||;|\||\n/)) {
56
+ for (const segment of segments(command)) {
35
57
  const words = segment.trim().split(/\s+/).filter(word => !/^[A-Za-z_][A-Za-z0-9_]*=/.test(word));
36
58
  const first = (words[0] ?? '').split('/').pop();
37
59
  if (!/^[A-Za-z][\w.+-]{0,40}$/.test(first)) continue;
@@ -42,12 +64,32 @@ function commandHeads(command) {
42
64
  return heads.slice(0, 4);
43
65
  }
44
66
 
67
+ const EXIT_TEXT = /(?:exit code|exited with code|exit status)[:\s]+(-?\d+)/i;
68
+ const exitText = text => { const m = EXIT_TEXT.exec(text); return m ? Number(m[1]) === 0 : null; };
69
+
45
70
  /** Whether a shell call worked, from the fields clients report; null when the response does not say. */
46
71
  function outcome(response) {
72
+ if (typeof response === 'string') return exitText(response);
47
73
  if (!response || typeof response !== 'object') return null;
48
- for (const key of ['exit_code', 'exitCode', 'returnCode', 'status']) if (Number.isInteger(response[key])) return response[key] === 0;
74
+ for (const source of [response, response.metadata ?? {}]) {
75
+ for (const key of ['exit_code', 'exitCode', 'returnCode', 'status']) if (Number.isInteger(source[key])) return source[key] === 0;
76
+ }
49
77
  if (response.interrupted === true || response.is_error === true || response.isError === true) return false;
50
- return null;
78
+ return typeof response.output === 'string' ? exitText(response.output) : null;
79
+ }
80
+
81
+ function clientOf(payload, env = process.env) {
82
+ const transcript = typeof payload.transcript_path === 'string' ? payload.transcript_path.replaceAll('\\', '/') : '';
83
+ if (transcript.includes('/.codex/')) return 'codex';
84
+ if (transcript.includes('/.claude/')) return 'claude';
85
+ if (env.VIBE_CLIENT) return ['codex', 'chatgpt'].includes(env.VIBE_CLIENT) ? 'codex' : 'claude';
86
+ return env.CODEX_THREAD_ID ? 'codex' : 'claude';
87
+ }
88
+
89
+ /** A PostToolUse call's outcome. Claude Code never sends a failed command to PostToolUse (seen 2026-10-18), so there its silence means success. */
90
+ function runOutcome(payload, env = process.env) {
91
+ const said = outcome(payload.tool_response);
92
+ return said !== null ? said : clientOf(payload, env) === 'claude' ? true : null;
51
93
  }
52
94
 
53
95
  /** The journal line for a hook event, or null when the event carries nothing worth keeping. */
@@ -61,7 +103,7 @@ function entryFor(kind, payload, session, now = Date.now()) {
61
103
  if (kind === 'tool') {
62
104
  const heads = commandHeads(payload.tool_input?.command ?? '');
63
105
  if (!heads.length) return null;
64
- return { ...base, k: 'shell', heads, ok: outcome(payload.tool_response) };
106
+ return { ...base, k: 'shell', heads, ok: runOutcome(payload) };
65
107
  }
66
108
  return kind === 'turn' ? { ...base, k: 'turn' } : null;
67
109
  }
@@ -157,4 +199,4 @@ function brief(summary) {
157
199
  return `; the journal holds ${r.prompts.length} prompts and ${r.corrections.length} corrections since the last knowledge turn and ${repeats} repeats over ${summary.days} days: read them with \`vibe knowledge journal\` and work from them rather than from memory`;
158
200
  }
159
201
 
160
- module.exports = { mask, commandHeads, outcome, entryFor, read, append, summarize, brief, fileName };
202
+ module.exports = { mask, segments, commandHeads, outcome, runOutcome, entryFor, read, append, summarize, brief, fileName };
@@ -0,0 +1,150 @@
1
+ // The wiki's structure, checked and kept without a model: broken links, orphan pages, long pages, unknown types and
2
+ // pages sharing a title. `fixWiki` settles what needs no judgement (an orphan joins the index as a marked line, a
3
+ // marked line whose page is gone is removed, a project page log.md links to is created); the knowledge turn names
4
+ // the rest. Shared by `vibe knowledge lint` and the session hook, which runs it within the client's Stop time limit.
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { readNames, projectPage, writeIfChanged } = require('./knowledge-projects.cjs');
8
+
9
+ const MAX_PAGES = 2000;
10
+ const LONG_LINES = 200;
11
+ /** Pages that are entry points or append-only, so nothing has to link to them. */
12
+ const ROOTS = new Set(['index', 'log']);
13
+ /** What a page is, so the model knows whether to follow it, reuse it, avoid it or just know it; the wiki guide names them. */
14
+ const TYPES = ['decision', 'method', 'problem', 'fact', 'project', 'research', 'skill', 'proposal', 'source'];
15
+ /** Ends an index line vibe wrote, so vibe removes only its own lines; Obsidian's reading view hides it. */
16
+ const AUTO = '<!-- vibe:auto -->';
17
+
18
+ /**
19
+ * The wiki's pages, and the pages of each project's shared knowledge it shows through a folder link
20
+ * (`projects/<name>/`). Those are kept in the project, so they resolve links and are reported as unlinked, not orphans.
21
+ */
22
+ function wikiPages(dir) {
23
+ const files = [];
24
+ const shared = new Set();
25
+ const seen = new Set();
26
+ let partial = false;
27
+ const walk = (current, linked) => {
28
+ if (seen.has(fs.realpathSync(current))) return;
29
+ seen.add(fs.realpathSync(current));
30
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
31
+ if (entry.name.startsWith('.')) continue;
32
+ if (files.length >= MAX_PAGES) { partial = true; return; }
33
+ const file = path.join(current, entry.name);
34
+ if (entry.isDirectory()) walk(file, linked);
35
+ else if (entry.isSymbolicLink() && fs.statSync(file, { throwIfNoEntry: false })?.isDirectory()) walk(file, true);
36
+ else if (entry.isFile() && entry.name.endsWith('.md')) { files.push(file); if (linked) shared.add(file); }
37
+ }
38
+ };
39
+ if (fs.existsSync(dir)) walk(dir, false);
40
+ return { files: files.sort(), shared, partial };
41
+ }
42
+
43
+ /** `[[target]]`, `[[target|label]]` and `[[target#heading]]` give `target`; links inside code are still counted. */
44
+ function wikiLinks(text) {
45
+ return [...text.matchAll(/\[\[([^\]|#\n]+)(?:[#|][^\]\n]*)?\]\]/g)].map(m => m[1].trim()).filter(Boolean);
46
+ }
47
+
48
+ /** The `title` property, else the first `# heading`, else the file name. */
49
+ function pageTitle(text, file) {
50
+ const front = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
51
+ const stated = front && /^title:\s*["']?(.+?)["']?\s*$/m.exec(front[1])?.[1];
52
+ const heading = /^#\s+(.+?)\s*$/m.exec(front ? text.slice(front[0].length) : text)?.[1];
53
+ return stated || heading || path.basename(file, '.md');
54
+ }
55
+
56
+ /** Each page's name as a link target, and every name a link may use for it (path from the root or file name alone). */
57
+ function names(dir, files) {
58
+ const name = file => path.relative(dir, file).split(path.sep).join('/').replace(/\.md$/, '');
59
+ const known = new Map();
60
+ for (const file of files) {
61
+ known.set(name(file).toLowerCase(), name(file));
62
+ known.set(path.basename(file, '.md').toLowerCase(), name(file));
63
+ }
64
+ return { name, resolve: target => known.get(target.replace(/\.md$/, '').toLowerCase()) };
65
+ }
66
+
67
+ /**
68
+ * Broken links, pages nothing links to, pages too long to read in one pass, unknown types and pages sharing a
69
+ * title. A link resolves the way Obsidian resolves it; a link from log.md finds a page but does not make it reachable.
70
+ */
71
+ function lintWiki(dir) {
72
+ const { files, shared, partial } = wikiPages(dir);
73
+ const { name, resolve } = names(dir, files);
74
+ const broken = [], long = [], badType = [];
75
+ const linked = new Set();
76
+ const titles = new Map();
77
+ for (const file of files) {
78
+ const text = fs.readFileSync(file, 'utf8');
79
+ const lines = text.split('\n').length;
80
+ const type = /^---\r?\n(?:[^\n]*\n)*?type:\s*["']?([^"'\n]+?)["']?\s*\n(?:[^\n]*\n)*?---/.exec(text)?.[1];
81
+ // Source material (a transcript, a web clipping, a video analysis) is long by nature and never split.
82
+ if (lines > LONG_LINES && type?.toLowerCase() !== 'source') long.push({ page: name(file), lines });
83
+ if (type !== undefined && !TYPES.includes(type.toLowerCase())) badType.push({ page: name(file), type });
84
+ if (!shared.has(file) && !ROOTS.has(name(file))) {
85
+ const title = pageTitle(text, file).toLowerCase();
86
+ titles.set(title, [...(titles.get(title) ?? []), name(file)]);
87
+ }
88
+ for (const target of wikiLinks(text)) {
89
+ const page = resolve(target);
90
+ if (page === undefined) broken.push({ page: name(file), target });
91
+ else if (page !== name(file) && name(file) !== 'log') linked.add(page);
92
+ }
93
+ }
94
+ const orphans = files.filter(file => !shared.has(file)).map(name).filter(page => !linked.has(page) && !ROOTS.has(page));
95
+ const unlinked = files.filter(file => shared.has(file)).map(name).filter(page => !linked.has(page));
96
+ const duplicates = [...titles].filter(([, pages]) => pages.length > 1).map(([, pages]) => pages);
97
+ return { dir, pages: files.length, broken, orphans, unlinked, long, badType, duplicates, partial };
98
+ }
99
+
100
+ /**
101
+ * Settle what needs no judgement: create each project page log.md links to that is missing, add every orphan to
102
+ * index.md as a marked line, and remove marked lines whose page is gone. A person's line is never touched; its
103
+ * broken link stays in the lint. Returns what changed and the lint after it.
104
+ */
105
+ function fixWiki(dir, home) {
106
+ const created = [];
107
+ const log = path.join(dir, 'log.md');
108
+ if (fs.existsSync(log)) {
109
+ const { files } = wikiPages(dir);
110
+ const { resolve } = names(dir, files);
111
+ const roots = readNames(home);
112
+ for (const target of new Set(wikiLinks(fs.readFileSync(log, 'utf8')))) {
113
+ const project = /^projects\/([^/]+)$/.exec(target.replace(/\.md$/, ''))?.[1];
114
+ if (project && resolve(target) === undefined && projectPage(dir, project, roots[project])) created.push(`projects/${project}`);
115
+ }
116
+ }
117
+ const { files, shared } = wikiPages(dir);
118
+ const { name, resolve } = names(dir, files);
119
+ const index = path.join(dir, 'index.md');
120
+ const text = fs.existsSync(index) ? fs.readFileSync(index, 'utf8') : '# Index\n';
121
+ const removed = [];
122
+ const kept = text.split('\n').filter(line => {
123
+ const target = line.includes(AUTO) ? wikiLinks(line)[0] : undefined;
124
+ if (target === undefined || resolve(target) !== undefined) return true;
125
+ removed.push(target);
126
+ return false;
127
+ });
128
+ const added = lintWiki(dir).orphans.map(page => {
129
+ const file = files.find(f => !shared.has(f) && name(f) === page);
130
+ return { page, title: pageTitle(fs.readFileSync(file, 'utf8'), file).replace(/\s+/g, ' ') };
131
+ });
132
+ if (added.length || removed.length) {
133
+ const body = kept.join('\n').trimEnd();
134
+ writeIfChanged(index, `${body}\n${added.map(a => `- [[${a.page}]] — ${a.title} ${AUTO}\n`).join('')}`);
135
+ }
136
+ return { created, added, removed, lint: lintWiki(dir) };
137
+ }
138
+
139
+ /** Pages changed since `since` (index and log aside) and the pages they link to, for a review turn. */
140
+ function reviewScope(dir, since) {
141
+ const { files } = wikiPages(dir);
142
+ const { name, resolve } = names(dir, files);
143
+ const changed = files.filter(file => !ROOTS.has(name(file)) && fs.statSync(file).mtimeMs > since);
144
+ const linked = new Set();
145
+ for (const file of changed) for (const target of wikiLinks(fs.readFileSync(file, 'utf8'))) linked.add(resolve(target));
146
+ const pages = changed.map(name);
147
+ return { changed: pages, linked: [...linked].filter(page => page && !pages.includes(page) && !ROOTS.has(page)).sort() };
148
+ }
149
+
150
+ module.exports = { TYPES, AUTO, wikiPages, wikiLinks, pageTitle, lintWiki, fixWiki, reviewScope };
@@ -110,4 +110,15 @@ function indexProject(root, wiki, name) {
110
110
  }
111
111
  }
112
112
 
113
- module.exports = { projectName, linkProject, indexProject };
113
+ /**
114
+ * Create the project's page when it is missing, so a log line's `[[projects/<name>]]` always resolves. Returns
115
+ * whether it created one; an existing page is left as it is.
116
+ */
117
+ function projectPage(wiki, name, root) {
118
+ const page = path.join(wiki, 'projects', `${name}.md`);
119
+ if (fs.existsSync(page)) return false;
120
+ writeIfChanged(page, `---\ntype: project\n---\n# ${name}\n${typeof root === 'string' ? `\nFolder: \`${root}\`\n` : ''}`);
121
+ return true;
122
+ }
123
+
124
+ module.exports = { readNames, projectName, linkProject, indexProject, projectPage, writeIfChanged };
@@ -0,0 +1,49 @@
1
+ // What a knowledge turn adds about the wiki itself: the lint fix runs first, and the reason names only what it could
2
+ // not settle; after enough changed pages or days, the turn also reviews those pages and the pages they link.
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { fixWiki, reviewScope } = require('./knowledge-lint.cjs');
6
+ const { writeIfChanged } = require('./knowledge-projects.cjs');
7
+
8
+ const DAY = 86400000;
9
+ const SHOWN = 3;
10
+ const LISTED = 20;
11
+
12
+ const few = (items, show) => `${items.slice(0, SHOWN).map(show).join(', ')}${items.length > SHOWN ? ` and ${items.length - SHOWN} more` : ''}`;
13
+
14
+ /** Run the fix, then a clause naming what needs judgement, or '' when nothing is left. */
15
+ function tidy(dir, home) {
16
+ try {
17
+ const { lint } = fixWiki(dir, home);
18
+ const left = [
19
+ lint.broken.length ? `broken links ${few(lint.broken, b => `${b.page} → [[${b.target}]]`)}` : '',
20
+ lint.long.length ? `long pages ${few(lint.long, l => `${l.page} (${l.lines} lines)`)}` : '',
21
+ lint.badType.length ? `unknown types ${few(lint.badType, b => `${b.page} "${b.type}"`)}` : '',
22
+ lint.duplicates.length ? `pages sharing a title ${few(lint.duplicates, pages => pages.join(' = '))}` : '',
23
+ ].filter(Boolean);
24
+ return left.length ? ` The wiki lint fixed what needs no judgement; left for you: ${left.join('; ')}. Settle them as the guide's "Keep the connections" says (\`vibe knowledge lint\` lists all).` : '';
25
+ } catch { return ''; }
26
+ }
27
+
28
+ const stateFile = home => path.join(home, '.config', 'vibe', 'knowledge-review.json');
29
+
30
+ /**
31
+ * A review clause when `pages` pages changed since the last review, or one did and `days` days passed; '' otherwise.
32
+ * The first call only sets the starting point, so turning this on never reviews the whole wiki at once.
33
+ */
34
+ function review(dir, home, pages, days, now = Date.now()) {
35
+ if (!pages) return '';
36
+ try {
37
+ let since = null;
38
+ try { since = JSON.parse(fs.readFileSync(stateFile(home), 'utf8')).at; } catch { /* first review */ }
39
+ if (!Number.isFinite(since)) { writeIfChanged(stateFile(home), JSON.stringify({ at: now })); return ''; }
40
+ const scope = reviewScope(dir, since);
41
+ const due = scope.changed.length >= pages || (scope.changed.length > 0 && days > 0 && now - since >= days * DAY);
42
+ if (!due) return '';
43
+ writeIfChanged(stateFile(home), JSON.stringify({ at: now }));
44
+ const list = items => `${items.slice(0, LISTED).map(p => `[[${p}]]`).join(', ')}${items.length > LISTED ? ` and ${items.length - LISTED} more` : ''}`;
45
+ return ` This is also a review turn (${scope.changed.length} pages changed since ${new Date(since).toISOString().slice(0, 10)}): read ${list(scope.changed)}${scope.linked.length ? ` and the pages they link, ${list(scope.linked)},` : ''} for claims that contradict each other, dated claims likely out of date and decisions a later one replaced, and correct them as the guide's "Review" says.`;
46
+ } catch { return ''; }
47
+ }
48
+
49
+ module.exports = { tidy, review, stateFile };
package/hooks/notify.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * pre PreToolUse(Bash): an irreversible command with no recent authorize record is blocked (exit 2) under the
7
7
  * strict and irreversible token policies, and only warned about under off.
8
8
  * Partial Bash reads of protected files are blocked; other source/document slices are warned.
9
+ * A test command that already passed on unchanged code is blocked or warned about (retest.cjs).
9
10
  * PreToolUse(Read): when the file is over READ_ADVISE_LINES, tells the model that `vibe read <file> --ask`
10
11
  * lets a low-reasoning model read it — advice only, the read goes ahead.
11
12
  *
@@ -17,6 +18,7 @@ import os from 'node:os';
17
18
  import path from 'node:path';
18
19
  import { fileURLToPath } from 'node:url';
19
20
  import knowledgeAuto from './knowledge-auto.cjs';
21
+ import retest from './retest.cjs';
20
22
  import sessionRuntime from './session-state.cjs';
21
23
  import sessionFiles from './session-files.cjs';
22
24
  import sliceRuntime from './slice-guard.cjs';
@@ -160,13 +162,21 @@ if (mode === 'pre') {
160
162
  if (fs.existsSync(path.join(root, '.vibe'))) adviseRead(payload);
161
163
  process.exit(0);
162
164
  }
163
- const command = String((payload.tool_input && payload.tool_input.command) || '');
165
+ const raw = payload.tool_input && payload.tool_input.command;
166
+ const command = Array.isArray(raw) ? raw.join(' ') : String(raw || '');
164
167
  const cwd = payload.tool_input?.cwd ?? payload.tool_input?.workdir ?? payload.cwd ?? process.cwd();
165
- if (personal && tokenPolicy() === 'off') process.exit(0);
168
+ // One additionalContext output per call: warnings collect here and leave with the exit.
169
+ const notes = [];
170
+ const leave = (code) => { if (notes.length) emitContext(notes.join('\n')); process.exit(code); };
171
+ let gate = null;
172
+ try { gate = retest.before(payload, process.env.VIBE_HOME_DIR || os.homedir()); } catch { /* the command runs */ }
173
+ if (gate?.decision === 'block') { process.stderr.write(`${gate.message}\n`); process.exit(2); }
174
+ if (gate?.decision === 'warn') notes.push(gate.message);
175
+ if (personal && tokenPolicy() === 'off') leave(0);
166
176
  const slice = personal ? { decision: 'allow' } : sliceRuntime.sliceGuard(root, command, sliceClient(), cwd);
167
177
  if (slice.decision === 'block') { process.stderr.write(`${slice.message}\n`); process.exit(2); }
168
- if (slice.decision === 'warn') emitContext(slice.message);
169
- if (!fs.existsSync(path.join(root, '.vibe'))) process.exit(0);
178
+ if (slice.decision === 'warn') notes.push(slice.message);
179
+ if (!fs.existsSync(path.join(root, '.vibe'))) leave(0);
170
180
  // Every segment is judged — `echo x && git push`, `ls; rm -rf build`, `cat x | git push` carry the action in a later one
171
181
  // Vibe gates its own execution; its approval and inbox commands must remain reachable.
172
182
  const segments = command.split(/&&|\|\||;|\||\n/).map((s) => s.trim()).filter((s) => s && !READS_ONLY.test(s) && !/^vibe(?:\s|$)/.test(s));
@@ -175,10 +185,11 @@ if (mode === 'pre') {
175
185
  if (segments.some((s) => re.test(s)) && !recentAuthorize(action)) {
176
186
  const blocking = tokenPolicy() !== 'off';
177
187
  process.stderr.write(`[vibe] "${action}" is irreversible and no authorize record exists in the last 10 minutes — ${blocking ? 'blocked: ' : ''}get a human token with \`vibe ask --needs authorize:${action}\` and run \`vibe authorize\` first, as its own command: the gate reads the ledger before this command runs, so an authorize chained in front of the action is not seen\n`);
178
- process.exit(blocking ? 2 : 0);
188
+ if (blocking) process.exit(2);
189
+ leave(0);
179
190
  }
180
191
  }
181
- process.exit(0);
192
+ leave(0);
182
193
  }
183
194
 
184
195
  // Inside the npm package dist/cli.js sits next to us; inside a plugin tree it does not, so fall back to `vibe` on PATH.