@su-record/vibe 4.2.34 → 4.2.36

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.
Files changed (225) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/dist/cli/common.js +4 -1
  5. package/dist/cli/common.js.map +1 -1
  6. package/dist/cli/heldout.js +10 -4
  7. package/dist/cli/heldout.js.map +1 -1
  8. package/dist/cli/human.js +8 -4
  9. package/dist/cli/human.js.map +1 -1
  10. package/dist/cli/internal.js +37 -24
  11. package/dist/cli/internal.js.map +1 -1
  12. package/dist/cli/ledger.js +67 -0
  13. package/dist/cli/ledger.js.map +1 -0
  14. package/dist/cli/map.js +5 -3
  15. package/dist/cli/map.js.map +1 -1
  16. package/dist/cli/memory.js +104 -74
  17. package/dist/cli/memory.js.map +1 -1
  18. package/dist/cli/risks.js +20 -3
  19. package/dist/cli/risks.js.map +1 -1
  20. package/dist/cli/session.js +4 -2
  21. package/dist/cli/session.js.map +1 -1
  22. package/dist/cli/setup.js +44 -13
  23. package/dist/cli/setup.js.map +1 -1
  24. package/dist/cli/verification.js +1 -1
  25. package/dist/cli/verification.js.map +1 -1
  26. package/dist/cli/work.js +75 -22
  27. package/dist/cli/work.js.map +1 -1
  28. package/dist/cli.js +18 -6
  29. package/dist/cli.js.map +1 -1
  30. package/dist/core/analyze.js +21 -7
  31. package/dist/core/analyze.js.map +1 -1
  32. package/dist/core/artifact-paths.js +14 -3
  33. package/dist/core/artifact-paths.js.map +1 -1
  34. package/dist/core/capabilities.js +69 -15
  35. package/dist/core/capabilities.js.map +1 -1
  36. package/dist/core/check-authorization.js +37 -0
  37. package/dist/core/check-authorization.js.map +1 -0
  38. package/dist/core/check-process.js +12 -4
  39. package/dist/core/check-process.js.map +1 -1
  40. package/dist/core/check-report.js +66 -0
  41. package/dist/core/check-report.js.map +1 -0
  42. package/dist/core/check-selection.js +4 -2
  43. package/dist/core/check-selection.js.map +1 -1
  44. package/dist/core/check.js +66 -130
  45. package/dist/core/check.js.map +1 -1
  46. package/dist/core/checks/changed.js +20 -6
  47. package/dist/core/checks/changed.js.map +1 -1
  48. package/dist/core/checks/eval.js +35 -10
  49. package/dist/core/checks/eval.js.map +1 -1
  50. package/dist/core/checks/file.js +26 -23
  51. package/dist/core/checks/file.js.map +1 -1
  52. package/dist/core/checks/fileRules.js +42 -16
  53. package/dist/core/checks/fileRules.js.map +1 -1
  54. package/dist/core/checks/http.js +35 -34
  55. package/dist/core/checks/http.js.map +1 -1
  56. package/dist/core/checks/packs.js +1 -1
  57. package/dist/core/checks/packs.js.map +1 -1
  58. package/dist/core/checks/review.js +75 -40
  59. package/dist/core/checks/review.js.map +1 -1
  60. package/dist/core/checks/run.js +5 -2
  61. package/dist/core/checks/run.js.map +1 -1
  62. package/dist/core/checks/schema.js +25 -0
  63. package/dist/core/checks/schema.js.map +1 -0
  64. package/dist/core/checks/source.js +11 -6
  65. package/dist/core/checks/source.js.map +1 -1
  66. package/dist/core/config.js +9 -2
  67. package/dist/core/config.js.map +1 -1
  68. package/dist/core/consent.js +24 -16
  69. package/dist/core/consent.js.map +1 -1
  70. package/dist/core/context-notes.js +15 -7
  71. package/dist/core/context-notes.js.map +1 -1
  72. package/dist/core/context.js +24 -12
  73. package/dist/core/context.js.map +1 -1
  74. package/dist/core/conventions.js +22 -9
  75. package/dist/core/conventions.js.map +1 -1
  76. package/dist/core/docs/cfb.js +81 -45
  77. package/dist/core/docs/cfb.js.map +1 -1
  78. package/dist/core/docs/html.js +14 -3
  79. package/dist/core/docs/html.js.map +1 -1
  80. package/dist/core/docs/hwp.js +44 -22
  81. package/dist/core/docs/hwp.js.map +1 -1
  82. package/dist/core/docs/office.js +20 -10
  83. package/dist/core/docs/office.js.map +1 -1
  84. package/dist/core/docs/pdf.js +10 -4
  85. package/dist/core/docs/pdf.js.map +1 -1
  86. package/dist/core/docs/read.js +32 -24
  87. package/dist/core/docs/read.js.map +1 -1
  88. package/dist/core/docs/zip.js +32 -15
  89. package/dist/core/docs/zip.js.map +1 -1
  90. package/dist/core/evidence.js +17 -7
  91. package/dist/core/evidence.js.map +1 -1
  92. package/dist/core/failure-message.js +35 -13
  93. package/dist/core/failure-message.js.map +1 -1
  94. package/dist/core/failure.js +49 -14
  95. package/dist/core/failure.js.map +1 -1
  96. package/dist/core/github.js +3 -1
  97. package/dist/core/github.js.map +1 -1
  98. package/dist/core/handoff.js +20 -7
  99. package/dist/core/handoff.js.map +1 -1
  100. package/dist/core/heldout-run.js +6 -3
  101. package/dist/core/heldout-run.js.map +1 -1
  102. package/dist/core/heldout.js +24 -13
  103. package/dist/core/heldout.js.map +1 -1
  104. package/dist/core/inbox.js +3 -1
  105. package/dist/core/inbox.js.map +1 -1
  106. package/dist/core/inputs.js +76 -0
  107. package/dist/core/inputs.js.map +1 -0
  108. package/dist/core/inspect.js +61 -20
  109. package/dist/core/inspect.js.map +1 -1
  110. package/dist/core/intent.js +50 -12
  111. package/dist/core/intent.js.map +1 -1
  112. package/dist/core/knowledge-find.js +31 -12
  113. package/dist/core/knowledge-find.js.map +1 -1
  114. package/dist/core/knowledge.js +22 -12
  115. package/dist/core/knowledge.js.map +1 -1
  116. package/dist/core/lang.js +5 -2
  117. package/dist/core/lang.js.map +1 -1
  118. package/dist/core/ledger.js +44 -20
  119. package/dist/core/ledger.js.map +1 -1
  120. package/dist/core/locations.js +12 -0
  121. package/dist/core/locations.js.map +1 -0
  122. package/dist/core/map/imports.js +20 -9
  123. package/dist/core/map/imports.js.map +1 -1
  124. package/dist/core/map/index.js +28 -13
  125. package/dist/core/map/index.js.map +1 -1
  126. package/dist/core/map/symbols.js +27 -12
  127. package/dist/core/map/symbols.js.map +1 -1
  128. package/dist/core/output-capture.js +7 -4
  129. package/dist/core/output-capture.js.map +1 -1
  130. package/dist/core/paths.js +2 -1
  131. package/dist/core/paths.js.map +1 -1
  132. package/dist/core/performance.js +43 -17
  133. package/dist/core/performance.js.map +1 -1
  134. package/dist/core/profile.js +6 -3
  135. package/dist/core/profile.js.map +1 -1
  136. package/dist/core/reader.js +32 -16
  137. package/dist/core/reader.js.map +1 -1
  138. package/dist/core/readerSession.js +34 -14
  139. package/dist/core/readerSession.js.map +1 -1
  140. package/dist/core/recent.js +20 -6
  141. package/dist/core/recent.js.map +1 -1
  142. package/dist/core/regress.js +21 -12
  143. package/dist/core/regress.js.map +1 -1
  144. package/dist/core/repair.js +22 -4
  145. package/dist/core/repair.js.map +1 -1
  146. package/dist/core/research-queries.js +85 -0
  147. package/dist/core/research-queries.js.map +1 -0
  148. package/dist/core/research.js +89 -88
  149. package/dist/core/research.js.map +1 -1
  150. package/dist/core/results.js +89 -0
  151. package/dist/core/results.js.map +1 -0
  152. package/dist/core/risk-signals.js +55 -16
  153. package/dist/core/risk-signals.js.map +1 -1
  154. package/dist/core/run-id.js +10 -6
  155. package/dist/core/run-id.js.map +1 -1
  156. package/dist/core/scenarios.js +97 -57
  157. package/dist/core/scenarios.js.map +1 -1
  158. package/dist/core/session.js +3 -2
  159. package/dist/core/session.js.map +1 -1
  160. package/dist/core/size-gate.js +57 -0
  161. package/dist/core/size-gate.js.map +1 -0
  162. package/dist/core/size.js +32 -10
  163. package/dist/core/size.js.map +1 -1
  164. package/dist/core/skills.js +66 -28
  165. package/dist/core/skills.js.map +1 -1
  166. package/dist/core/skillsmp.js +26 -12
  167. package/dist/core/skillsmp.js.map +1 -1
  168. package/dist/core/source-basis.js +21 -9
  169. package/dist/core/source-basis.js.map +1 -1
  170. package/dist/core/status.js +26 -13
  171. package/dist/core/status.js.map +1 -1
  172. package/dist/core/table.js +8 -1
  173. package/dist/core/table.js.map +1 -1
  174. package/dist/core/tokens.js +20 -10
  175. package/dist/core/tokens.js.map +1 -1
  176. package/dist/core/tree.js +26 -16
  177. package/dist/core/tree.js.map +1 -1
  178. package/dist/core/verification-plan.js +26 -10
  179. package/dist/core/verification-plan.js.map +1 -1
  180. package/dist/core/verification.js +14 -7
  181. package/dist/core/verification.js.map +1 -1
  182. package/dist/core/view.js +93 -44
  183. package/dist/core/view.js.map +1 -1
  184. package/dist/install/global.js +37 -18
  185. package/dist/install/global.js.map +1 -1
  186. package/dist/install/hooks.js +17 -8
  187. package/dist/install/hooks.js.map +1 -1
  188. package/dist/install/manifest.js +11 -0
  189. package/dist/install/manifest.js.map +1 -0
  190. package/dist/install/mcpb.js +52 -23
  191. package/dist/install/mcpb.js.map +1 -1
  192. package/dist/install/plugin.js +73 -33
  193. package/dist/install/plugin.js.map +1 -1
  194. package/dist/install/project.js +8 -7
  195. package/dist/install/project.js.map +1 -1
  196. package/dist/install/register.js +83 -48
  197. package/dist/install/register.js.map +1 -1
  198. package/dist/install/tree.js +27 -11
  199. package/dist/install/tree.js.map +1 -1
  200. package/dist/install/update.js +46 -20
  201. package/dist/install/update.js.map +1 -1
  202. package/hooks/code-map.cjs +22 -9
  203. package/hooks/file-size.cjs +83 -0
  204. package/hooks/journal.cjs +28 -18
  205. package/hooks/knowledge-auto.cjs +55 -21
  206. package/hooks/knowledge-journal.cjs +47 -20
  207. package/hooks/knowledge-projects.cjs +50 -21
  208. package/hooks/knowledge-recall.cjs +16 -6
  209. package/hooks/knowledge-upkeep.cjs +22 -5
  210. package/hooks/locations.cjs +35 -0
  211. package/hooks/notify.js +52 -24
  212. package/hooks/obsidian.cjs +17 -7
  213. package/hooks/private-store.cjs +25 -10
  214. package/hooks/retest.cjs +39 -16
  215. package/hooks/session-context.cjs +30 -13
  216. package/hooks/session-files.cjs +44 -21
  217. package/hooks/session-repair.cjs +26 -11
  218. package/hooks/session-state.cjs +78 -38
  219. package/hooks/session.js +19 -8
  220. package/hooks/slice-command.cjs +38 -17
  221. package/hooks/slice-guard.cjs +20 -6
  222. package/hooks/slice-store.cjs +25 -12
  223. package/internal/guides/code.md +17 -0
  224. package/mcpb/server/index.js +40 -12
  225. package/package.json +1 -1
@@ -2,6 +2,15 @@ const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
  const { sliceTargets } = require('./slice-command.cjs');
4
4
  const { readTargets, recordSlice } = require('./slice-store.cjs');
5
+ /** Past this many lines a file only to be understood is better read by `vibe read --ask`. */
6
+ const READ_ADVISE_LINES = 400;
7
+
8
+ const SOURCE_EXTENSION = /\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|c|cpp|h|cs|rb|sh|sql|md|mdx|txt|rst|json|ya?ml|toml|ini|xml|html|css|csv|tsv)$/i;
9
+ const SOURCE_NAME = /^(?:README|Dockerfile|Makefile)(?:\..*)?$/i;
10
+ const PROTECTED_PATH = /(?:^|\/)scenarios\.ya?ml$|(?:^|\/)specs?(?:\/|$)/i;
11
+ const BLOCKED = 'Partial reading of a protected file is blocked.';
12
+ const WARNED = 'Partial source/document reading can omit requirements.';
13
+ const INTENT_FILES = ['.vibe', '.vibe/intent.md'];
5
14
 
6
15
  function within(directory, file) {
7
16
  const relative = path.relative(directory, file);
@@ -13,17 +22,20 @@ function exempt(root, file) {
13
22
  }
14
23
  function protectedFile(root, file, files) {
15
24
  const relative = path.relative(root, file).split(path.sep).join('/');
16
- if (relative === '.vibe' || relative === '.vibe/intent.md' || /(?:^|\/)scenarios\.ya?ml$/i.test(relative) || /(?:^|\/)specs?(?:\/|$)/i.test(relative)) return true;
25
+ if (INTENT_FILES.includes(relative) || PROTECTED_PATH.test(relative)) return true;
17
26
  return files.some(protectedPath => file === protectedPath || within(file, protectedPath));
18
27
  }
19
28
  function sourceFile(file) {
20
- if (/\.(?:[cm]?[jt]sx?|py|go|rs|java|kt|c|cpp|h|cs|rb|sh|sql|md|mdx|txt|rst|json|ya?ml|toml|ini|xml|html|css|csv|tsv)$/i.test(file)) return true;
21
- if (/^(?:README|Dockerfile|Makefile)(?:\..*)?$/i.test(path.basename(file))) return true;
29
+ if (SOURCE_EXTENSION.test(file) || SOURCE_NAME.test(path.basename(file))) return true;
22
30
  try { return fs.lstatSync(file).isDirectory(); } catch { return false; }
23
31
  }
24
32
  function guidance(client) {
25
33
  const tool = client === 'codex' ? 'cat (the entire file)' : 'the Read tool (the entire file)';
26
- return `Open the selected files in full with ${tool}. Read fewer files from vibe state; use grep/rg/find only to locate files or lines. For over 400 lines when not editing, use vibe read --ask.`;
34
+ return [
35
+ `Open the selected files in full with ${tool}.`,
36
+ 'Read fewer files from vibe state; use grep/rg/find only to locate files or lines.',
37
+ `For over ${READ_ADVISE_LINES} lines when not editing, use vibe read --ask.`,
38
+ ].join(' ');
27
39
  }
28
40
  function sliceGuard(root, command, client = 'claude', cwd = root) {
29
41
  try { root = fs.realpathSync(root); cwd = fs.realpathSync(path.resolve(root, cwd)); } catch { return { decision: 'none', unavailable: true }; }
@@ -36,6 +48,8 @@ function sliceGuard(root, command, client = 'claude', cwd = root) {
36
48
  if (decision === 'none') return { decision, unavailable: !mirror.available };
37
49
  const recorded = recordSlice(root, decision);
38
50
  const unavailable = !mirror.available || !recorded;
39
- return { decision, unavailable, recorded, message: `[vibe] ${decision === 'block' ? 'Partial reading of a protected file is blocked.' : 'Partial source/document reading can omit requirements.'} ${guidance(client)}${unavailable ? ' Read-target mirror or count storage unavailable.' : ''}` };
51
+ const headline = decision === 'block' ? BLOCKED : WARNED;
52
+ const storage = unavailable ? ' Read-target mirror or count storage unavailable.' : '';
53
+ return { decision, unavailable, recorded, message: `[vibe] ${headline} ${guidance(client)}${storage}` };
40
54
  }
41
- module.exports = { sliceGuard };
55
+ module.exports = { READ_ADVISE_LINES, sliceGuard };
@@ -2,8 +2,17 @@ const fs = require('node:fs');
2
2
  const path = require('node:path');
3
3
  const { randomBytes } = require('node:crypto');
4
4
  const { privateDirectory, readPrivate, writePrivate } = require('./private-store.cjs');
5
- const { digest, json } = require('./session-files.cjs');
5
+ const { STATE_FILE, INTENT_HASH, digest, json } = require('./session-files.cjs');
6
6
 
7
+ const MAX_TARGETS = 2048;
8
+ const MAX_PATH_CHARS = 4096;
9
+ const TARGETS_MAX_BYTES = 128 * 1024;
10
+ const MARKER_MAX_BYTES = 4096;
11
+ // One tiny file per recorded slice; a directory with more than this many store files is not read.
12
+ const MAX_STORE_FILES = 32768;
13
+ const COUNT_MAX_BYTES = 128;
14
+ const COUNT_SUFFIX_BYTES = 12;
15
+ const targetsName = root => `read-targets-${digest(root)}.json`;
7
16
  const prefix = root => `slice-${digest(root)}-`;
8
17
  const markerName = root => `slice-counts-${digest(root)}.json`;
9
18
  function explicitHome(env) {
@@ -15,20 +24,24 @@ function explicitHome(env) {
15
24
  function recordReadTargets(root, input, env) {
16
25
  try {
17
26
  root = fs.realpathSync(root);
18
- if (!input || !/^[a-f0-9]{16,64}$/.test(input.intentHash) || !Array.isArray(input.files) || input.files.length > 2048 || input.files.some(file => typeof file !== 'string' || file.length >= 4096)) return false;
27
+ if (!input || !INTENT_HASH.test(input.intentHash) || !Array.isArray(input.files) || input.files.length > MAX_TARGETS) return false;
28
+ if (input.files.some(file => typeof file !== 'string' || file.length >= MAX_PATH_CHARS)) return false;
19
29
  const files = [...new Set(input.files.map(file => path.resolve(root, file)))];
20
30
  const body = JSON.stringify({ schemaVersion: 1, root, intentHash: input.intentHash, files });
21
- if (Buffer.byteLength(body) > 131072) return false;
22
- writePrivate(root, `read-targets-${digest(root)}.json`, body, explicitHome(env));
31
+ if (Buffer.byteLength(body) > TARGETS_MAX_BYTES) return false;
32
+ writePrivate(root, targetsName(root), body, explicitHome(env));
23
33
  return true;
24
34
  } catch { return false; }
25
35
  }
26
36
  function readTargets(root) {
27
37
  try {
28
- const raw = readPrivate(root, `read-targets-${digest(root)}.json`, 131072);
38
+ const raw = readPrivate(root, targetsName(root), TARGETS_MAX_BYTES);
29
39
  if (raw === null) return { available: false, files: [] };
30
- const saved = JSON.parse(raw), state = json(path.join(root, '.vibe/state.json'));
31
- if (saved.schemaVersion !== 1 || saved.root !== root || saved.intentHash !== state.intentHash || !Array.isArray(saved.files) || saved.files.length > 2048 || saved.files.some(file => typeof file !== 'string' || !path.isAbsolute(file))) return { available: false, files: [] };
40
+ const saved = JSON.parse(raw), state = json(path.join(root, STATE_FILE));
41
+ const valid = saved.schemaVersion === 1 && saved.root === root && saved.intentHash === state.intentHash
42
+ && Array.isArray(saved.files) && saved.files.length <= MAX_TARGETS
43
+ && saved.files.every(file => typeof file === 'string' && path.isAbsolute(file));
44
+ if (!valid) return { available: false, files: [] };
32
45
  return { available: true, files: saved.files };
33
46
  } catch { return { available: false, files: [] }; }
34
47
  }
@@ -37,7 +50,7 @@ function recordSlice(root, decision) {
37
50
  if (!['block', 'warn'].includes(decision)) return false;
38
51
  if (!initializeSliceCounts(root)) return false;
39
52
  const counts = { blocked: Number(decision === 'block'), warned: Number(decision === 'warn') };
40
- writePrivate(root, `${prefix(root)}${randomBytes(12).toString('hex')}.json`, JSON.stringify(counts));
53
+ writePrivate(root, `${prefix(root)}${randomBytes(COUNT_SUFFIX_BYTES).toString('hex')}.json`, JSON.stringify(counts));
41
54
  return true;
42
55
  } catch { return false; }
43
56
  }
@@ -45,7 +58,7 @@ function initializeSliceCounts(root, env) {
45
58
  try {
46
59
  root = fs.realpathSync(root);
47
60
  const home = explicitHome(env);
48
- const raw = readPrivate(root, markerName(root), 4096, home);
61
+ const raw = readPrivate(root, markerName(root), MARKER_MAX_BYTES, home);
49
62
  if (raw !== null) { const value = JSON.parse(raw); return value.schemaVersion === 1 && value.root === root; }
50
63
  writePrivate(root, markerName(root), JSON.stringify({ schemaVersion: 1, root }), home);
51
64
  return true;
@@ -55,14 +68,14 @@ function readSliceCounts(root, env) {
55
68
  try {
56
69
  root = fs.realpathSync(root);
57
70
  const home = explicitHome(env);
58
- const marker = JSON.parse(readPrivate(root, markerName(root), 4096, home));
71
+ const marker = JSON.parse(readPrivate(root, markerName(root), MARKER_MAX_BYTES, home));
59
72
  if (!marker || marker.schemaVersion !== 1 || marker.root !== root) return null;
60
73
  const directory = privateDirectory(root, false, home);
61
74
  const files = fs.readdirSync(directory);
62
- if (files.length > 32768) return null;
75
+ if (files.length > MAX_STORE_FILES) return null;
63
76
  const counts = { blocked: 0, warned: 0 };
64
77
  for (const file of files.filter(file => file.startsWith(prefix(root)))) {
65
- const value = JSON.parse(readPrivate(root, file, 128, home));
78
+ const value = JSON.parse(readPrivate(root, file, COUNT_MAX_BYTES, home));
66
79
  if (!((value.blocked === 1 && value.warned === 0) || (value.blocked === 0 && value.warned === 1))) return null;
67
80
  counts.blocked += value.blocked; counts.warned += value.warned;
68
81
  }
@@ -12,6 +12,23 @@ Make the smallest change that fully solves the task. After reading the code it t
12
12
 
13
13
  Settle the data shape before the logic: when the right types and structures are chosen, the code that follows is plain. Where the language allows, make an illegal state impossible to write (a union of states instead of independent flags, a parsed type instead of a raw string), parse outside data once at the boundary where it enters, and keep the logic behind it pure and trusting. A script, migration or job that changes state reaches the same end state when run again after a partial run; check before acting rather than assuming a fresh start.
14
14
 
15
+ The principles behind these steps, by their usual names:
16
+
17
+ - YAGNI: build nothing for a future nobody asked for (step 1).
18
+ - DRY: one fact lives in one place. A constant, a pattern or a rule used twice is defined once and imported, never copied into a second file.
19
+ - KISS: the plainest construct that works; no cleverness a reader has to decode.
20
+ - Single responsibility: a function does one thing, a file holds one responsibility. Name it by that responsibility; if the name needs "and", split it.
21
+
22
+ Keep files and functions small. A source file stays at or under 400 lines and a function under about 50. Before adding to a file near or over 400 lines, put the new code in a new module named for its responsibility, or split the file first; never grow a file past 400 lines. The edit hook warns when an edit does, and `vibe check --all` fails on a source file the current work grew past 400 lines. A file that was already over the limit is split when the work touches its structure, not as unrelated cleanup.
23
+
24
+ Write for the next reader:
25
+
26
+ - Fix output where it is produced. Never patch a generated string, file or result afterwards with `.replace` or string surgery; change the code that builds it.
27
+ - Build long text from one item per line (an array joined, or a template over several lines), not one line of hundreds of characters.
28
+ - Keep a function at one level of detail: steps read as named calls, and the details live in the called functions.
29
+ - Return early instead of nesting conditions; avoid boolean flags that switch a function between two jobs.
30
+ - No hardcoding. A number, limit, timeout, path, URL, name or message that means something gets a named constant, defined once where its meaning lives, or comes from configuration when a user or environment may need another value. Never repeat a literal across files, never bury a magic number inside logic, and never hardcode a value the code can read or derive (a version, a path, a list that exists elsewhere). A literal whose meaning is plain where it stands (0, 1, an empty string) may stay.
31
+
15
32
  Be lazy about the solution, never about the change: finish every caller, test and fixture it breaks. Before fixing a bug, find every caller of the function (`vibe callers <name>`) and fix the root cause once in the shared code. New logic with a branch, a loop, a parser, money or security leaves one small test or assert-based self-check; trivial changes need none. Never cut validation at trust boundaries, error handling that prevents data loss, security, accessibility or anything the user asked for. End the report with what was skipped or not checked.
16
33
 
17
34
  Apply these antislop criteria while writing and revising the affected code, without waiting for a separate request:
@@ -17,6 +17,22 @@ import readline from 'node:readline';
17
17
  const PROTOCOL = '2025-06-18';
18
18
  const VERSION = process.env.VIBE_MCPB_VERSION || '0.0.0';
19
19
  const project = process.env.VIBE_PROJECT_DIR || process.cwd();
20
+ const VIBE_CLIENT = 'claude-app';
21
+ const RUN_TIMEOUT_MS = 10 * 60 * 1000; // a check run may build and test the whole project
22
+ const TOOL_PREFIX = 'vibe_';
23
+ const INVALID_PARAMS = -32602;
24
+ const METHOD_NOT_FOUND = -32601;
25
+ const PARSE_ERROR = -32700;
26
+ const INSTALL_HINT = 'npm i -g @su-record/vibe';
27
+ const INSTRUCTIONS_TAIL = [
28
+ 'Use the vibe tool with operation brief first; discover returns an internal operation schema.',
29
+ 'The brief includes the personal FDE entry guidance; no extra model review by default.',
30
+ "Talk to the user in the user's language; write every record in English.",
31
+ ].join(' ');
32
+ const ENTRY_DESCRIPTION = [
33
+ 'Your personal FDE: project context, internal guidance, tools and skill extensions.',
34
+ 'Use discover to inspect an operation before calling it.',
35
+ ].join(' ');
20
36
 
21
37
  const str = (description) => ({ type: 'string', description });
22
38
  const TOOLS = [
@@ -73,8 +89,11 @@ function findVibe() {
73
89
  const VIBE = findVibe();
74
90
 
75
91
  function runVibe(args, input) {
76
- if (!VIBE) return { isError: true, text: 'the vibe CLI was not found — install it with `npm i -g @su-record/vibe`, or set its path in the extension settings (vibe executable)' };
77
- const r = spawnSync(VIBE, [...args, '--json'], { cwd: project, encoding: 'utf-8', input, timeout: 600000, env: { ...process.env, VIBE_CLIENT: 'claude-app', PATH: `${path.dirname(VIBE)}${path.delimiter}${process.env.PATH || ''}` }, shell: process.platform === 'win32' });
92
+ if (!VIBE) {
93
+ return { isError: true, text: `the vibe CLI was not found — install it with \`${INSTALL_HINT}\`, or set its path in the extension settings (vibe executable)` };
94
+ }
95
+ const env = { ...process.env, VIBE_CLIENT, PATH: `${path.dirname(VIBE)}${path.delimiter}${process.env.PATH || ''}` };
96
+ const r = spawnSync(VIBE, [...args, '--json'], { cwd: project, encoding: 'utf-8', input, timeout: RUN_TIMEOUT_MS, env, shell: process.platform === 'win32' });
78
97
  if (r.error) return { isError: true, text: `could not run ${VIBE}: ${r.error.message}` };
79
98
  const text = (r.stdout || '').trim() || (r.stderr || '').trim() || `exit ${r.status}`;
80
99
  return { isError: r.status !== 0, text };
@@ -103,31 +122,40 @@ function handle(msg) {
103
122
  if (msg.id === undefined) return; // notification
104
123
  const { id, method, params = {} } = msg;
105
124
  if (method === 'initialize') {
106
- return respond(id, { protocolVersion: params.protocolVersion || PROTOCOL, capabilities: { tools: {} }, serverInfo: { name: 'vibe', version: VERSION }, instructions: `vibe project: ${project}${fs.existsSync(`${project}/.vibe`) ? '' : ' (no .vibe yet — the first intent draft creates it)'} · CLI ${VIBE || 'not found — npm i -g @su-record/vibe'}. Use the vibe tool with operation brief first; discover returns an internal operation schema. The brief includes the personal FDE entry guidance; no extra model review by default. Talk to the user in the user's language; write every record in English.` });
125
+ const fresh = fs.existsSync(`${project}/.vibe`) ? '' : ' (no .vibe yet — the first intent draft creates it)';
126
+ const instructions = `vibe project: ${project}${fresh} · CLI ${VIBE || `not found — ${INSTALL_HINT}`}. ${INSTRUCTIONS_TAIL}`;
127
+ return respond(id, { protocolVersion: params.protocolVersion || PROTOCOL, capabilities: { tools: {} }, serverInfo: { name: 'vibe', version: VERSION }, instructions });
107
128
  }
108
129
  if (method === 'ping') return respond(id, {});
109
130
  if (method === 'tools/list') {
110
- return respond(id, { tools: [{ name: 'vibe', description: 'Your personal FDE: project context, internal guidance, tools and skill extensions. Use discover to inspect an operation before calling it.', inputSchema: { type: 'object', properties: { operation: { type: 'string', enum: ['discover', ...TOOLS.map(t => t.name.slice(5))] }, arguments: { type: 'object', description: 'Arguments for the selected operation. discover accepts an optional operation name.' } }, required: ['operation'], additionalProperties: false } }] });
131
+ const operations = ['discover', ...TOOLS.map(t => t.name.slice(TOOL_PREFIX.length))];
132
+ const properties = {
133
+ operation: { type: 'string', enum: operations },
134
+ arguments: { type: 'object', description: 'Arguments for the selected operation. discover accepts an optional operation name.' },
135
+ };
136
+ const inputSchema = { type: 'object', properties, required: ['operation'], additionalProperties: false };
137
+ return respond(id, { tools: [{ name: 'vibe', description: ENTRY_DESCRIPTION, inputSchema }] });
111
138
  }
112
139
  if (method === 'tools/call') {
113
140
  const envelope = params.arguments || {};
114
141
  if (params.name === 'vibe' && envelope.operation === 'discover') {
115
142
  const selected = envelope.arguments?.operation;
116
- const entries = selected ? TOOLS.filter(t => t.name === `vibe_${selected}`) : TOOLS;
117
- if (!entries.length) return fail(id, -32602, `unknown operation: ${selected}`);
118
- const result = entries.map(t => ({ operation: t.name.slice(5), description: t.description, ...(selected ? { inputSchema: { type: 'object', properties: t.args, required: t.required || [], additionalProperties: false } } : {}) }));
143
+ const entries = selected ? TOOLS.filter(t => t.name === `${TOOL_PREFIX}${selected}`) : TOOLS;
144
+ if (!entries.length) return fail(id, INVALID_PARAMS, `unknown operation: ${selected}`);
145
+ const schema = (t) => ({ type: 'object', properties: t.args, required: t.required || [], additionalProperties: false });
146
+ const result = entries.map(t => ({ operation: t.name.slice(TOOL_PREFIX.length), description: t.description, ...(selected ? { inputSchema: schema(t) } : {}) }));
119
147
  return respond(id, { content: [{ type: 'text', text: JSON.stringify(result) }] });
120
148
  }
121
- const name = params.name === 'vibe' ? `vibe_${envelope.operation}` : params.name;
149
+ const name = params.name === 'vibe' ? `${TOOL_PREFIX}${envelope.operation}` : params.name;
122
150
  const tool = TOOLS.find((t) => t.name === name);
123
- if (!tool) return fail(id, -32602, `unknown tool: ${params.name}`);
151
+ if (!tool) return fail(id, INVALID_PARAMS, `unknown tool: ${params.name}`);
124
152
  const a = params.name === 'vibe' ? envelope.arguments || {} : envelope;
125
153
  const invalid = invalidArguments(tool, a);
126
- if (invalid) return fail(id, -32602, invalid);
154
+ if (invalid) return fail(id, INVALID_PARAMS, invalid);
127
155
  const r = runVibe(tool.cmd(a), tool.stdin ? tool.stdin(a) : undefined);
128
156
  return respond(id, { content: [{ type: 'text', text: r.text }], isError: r.isError });
129
157
  }
130
- return fail(id, -32601, `method not found: ${method}`);
158
+ return fail(id, METHOD_NOT_FOUND, `method not found: ${method}`);
131
159
  }
132
160
 
133
161
  readline.createInterface({ input: process.stdin }).on('line', (line) => {
@@ -135,6 +163,6 @@ readline.createInterface({ input: process.stdin }).on('line', (line) => {
135
163
  try {
136
164
  handle(JSON.parse(line));
137
165
  } catch (error) {
138
- fail(null, -32700, `parse error: ${error.message}`);
166
+ fail(null, PARSE_ERROR, `parse error: ${error.message}`);
139
167
  }
140
168
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@su-record/vibe",
3
- "version": "4.2.34",
3
+ "version": "4.2.36",
4
4
  "description": "Your personal FDE in Claude and Codex. Understand the work, use the right tools, deliver and retain useful context. One entry: vibe.",
5
5
  "type": "module",
6
6
  "bin": {