@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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/dist/cli/common.js +4 -1
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/heldout.js +10 -4
- package/dist/cli/heldout.js.map +1 -1
- package/dist/cli/human.js +8 -4
- package/dist/cli/human.js.map +1 -1
- package/dist/cli/internal.js +37 -24
- package/dist/cli/internal.js.map +1 -1
- package/dist/cli/ledger.js +67 -0
- package/dist/cli/ledger.js.map +1 -0
- package/dist/cli/map.js +5 -3
- package/dist/cli/map.js.map +1 -1
- package/dist/cli/memory.js +104 -74
- package/dist/cli/memory.js.map +1 -1
- package/dist/cli/risks.js +20 -3
- package/dist/cli/risks.js.map +1 -1
- package/dist/cli/session.js +4 -2
- package/dist/cli/session.js.map +1 -1
- package/dist/cli/setup.js +44 -13
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/verification.js +1 -1
- package/dist/cli/verification.js.map +1 -1
- package/dist/cli/work.js +75 -22
- package/dist/cli/work.js.map +1 -1
- package/dist/cli.js +18 -6
- package/dist/cli.js.map +1 -1
- package/dist/core/analyze.js +21 -7
- package/dist/core/analyze.js.map +1 -1
- package/dist/core/artifact-paths.js +14 -3
- package/dist/core/artifact-paths.js.map +1 -1
- package/dist/core/capabilities.js +69 -15
- package/dist/core/capabilities.js.map +1 -1
- package/dist/core/check-authorization.js +37 -0
- package/dist/core/check-authorization.js.map +1 -0
- package/dist/core/check-process.js +12 -4
- package/dist/core/check-process.js.map +1 -1
- package/dist/core/check-report.js +66 -0
- package/dist/core/check-report.js.map +1 -0
- package/dist/core/check-selection.js +4 -2
- package/dist/core/check-selection.js.map +1 -1
- package/dist/core/check.js +66 -130
- package/dist/core/check.js.map +1 -1
- package/dist/core/checks/changed.js +20 -6
- package/dist/core/checks/changed.js.map +1 -1
- package/dist/core/checks/eval.js +35 -10
- package/dist/core/checks/eval.js.map +1 -1
- package/dist/core/checks/file.js +26 -23
- package/dist/core/checks/file.js.map +1 -1
- package/dist/core/checks/fileRules.js +42 -16
- package/dist/core/checks/fileRules.js.map +1 -1
- package/dist/core/checks/http.js +35 -34
- package/dist/core/checks/http.js.map +1 -1
- package/dist/core/checks/packs.js +1 -1
- package/dist/core/checks/packs.js.map +1 -1
- package/dist/core/checks/review.js +75 -40
- package/dist/core/checks/review.js.map +1 -1
- package/dist/core/checks/run.js +5 -2
- package/dist/core/checks/run.js.map +1 -1
- package/dist/core/checks/schema.js +25 -0
- package/dist/core/checks/schema.js.map +1 -0
- package/dist/core/checks/source.js +11 -6
- package/dist/core/checks/source.js.map +1 -1
- package/dist/core/config.js +9 -2
- package/dist/core/config.js.map +1 -1
- package/dist/core/consent.js +24 -16
- package/dist/core/consent.js.map +1 -1
- package/dist/core/context-notes.js +15 -7
- package/dist/core/context-notes.js.map +1 -1
- package/dist/core/context.js +24 -12
- package/dist/core/context.js.map +1 -1
- package/dist/core/conventions.js +22 -9
- package/dist/core/conventions.js.map +1 -1
- package/dist/core/docs/cfb.js +81 -45
- package/dist/core/docs/cfb.js.map +1 -1
- package/dist/core/docs/html.js +14 -3
- package/dist/core/docs/html.js.map +1 -1
- package/dist/core/docs/hwp.js +44 -22
- package/dist/core/docs/hwp.js.map +1 -1
- package/dist/core/docs/office.js +20 -10
- package/dist/core/docs/office.js.map +1 -1
- package/dist/core/docs/pdf.js +10 -4
- package/dist/core/docs/pdf.js.map +1 -1
- package/dist/core/docs/read.js +32 -24
- package/dist/core/docs/read.js.map +1 -1
- package/dist/core/docs/zip.js +32 -15
- package/dist/core/docs/zip.js.map +1 -1
- package/dist/core/evidence.js +17 -7
- package/dist/core/evidence.js.map +1 -1
- package/dist/core/failure-message.js +35 -13
- package/dist/core/failure-message.js.map +1 -1
- package/dist/core/failure.js +49 -14
- package/dist/core/failure.js.map +1 -1
- package/dist/core/github.js +3 -1
- package/dist/core/github.js.map +1 -1
- package/dist/core/handoff.js +20 -7
- package/dist/core/handoff.js.map +1 -1
- package/dist/core/heldout-run.js +6 -3
- package/dist/core/heldout-run.js.map +1 -1
- package/dist/core/heldout.js +24 -13
- package/dist/core/heldout.js.map +1 -1
- package/dist/core/inbox.js +3 -1
- package/dist/core/inbox.js.map +1 -1
- package/dist/core/inputs.js +76 -0
- package/dist/core/inputs.js.map +1 -0
- package/dist/core/inspect.js +61 -20
- package/dist/core/inspect.js.map +1 -1
- package/dist/core/intent.js +50 -12
- package/dist/core/intent.js.map +1 -1
- package/dist/core/knowledge-find.js +31 -12
- package/dist/core/knowledge-find.js.map +1 -1
- package/dist/core/knowledge.js +22 -12
- package/dist/core/knowledge.js.map +1 -1
- package/dist/core/lang.js +5 -2
- package/dist/core/lang.js.map +1 -1
- package/dist/core/ledger.js +44 -20
- package/dist/core/ledger.js.map +1 -1
- package/dist/core/locations.js +12 -0
- package/dist/core/locations.js.map +1 -0
- package/dist/core/map/imports.js +20 -9
- package/dist/core/map/imports.js.map +1 -1
- package/dist/core/map/index.js +28 -13
- package/dist/core/map/index.js.map +1 -1
- package/dist/core/map/symbols.js +27 -12
- package/dist/core/map/symbols.js.map +1 -1
- package/dist/core/output-capture.js +7 -4
- package/dist/core/output-capture.js.map +1 -1
- package/dist/core/paths.js +2 -1
- package/dist/core/paths.js.map +1 -1
- package/dist/core/performance.js +43 -17
- package/dist/core/performance.js.map +1 -1
- package/dist/core/profile.js +6 -3
- package/dist/core/profile.js.map +1 -1
- package/dist/core/reader.js +32 -16
- package/dist/core/reader.js.map +1 -1
- package/dist/core/readerSession.js +34 -14
- package/dist/core/readerSession.js.map +1 -1
- package/dist/core/recent.js +20 -6
- package/dist/core/recent.js.map +1 -1
- package/dist/core/regress.js +21 -12
- package/dist/core/regress.js.map +1 -1
- package/dist/core/repair.js +22 -4
- package/dist/core/repair.js.map +1 -1
- package/dist/core/research-queries.js +85 -0
- package/dist/core/research-queries.js.map +1 -0
- package/dist/core/research.js +89 -88
- package/dist/core/research.js.map +1 -1
- package/dist/core/results.js +89 -0
- package/dist/core/results.js.map +1 -0
- package/dist/core/risk-signals.js +55 -16
- package/dist/core/risk-signals.js.map +1 -1
- package/dist/core/run-id.js +10 -6
- package/dist/core/run-id.js.map +1 -1
- package/dist/core/scenarios.js +97 -57
- package/dist/core/scenarios.js.map +1 -1
- package/dist/core/session.js +3 -2
- package/dist/core/session.js.map +1 -1
- package/dist/core/size-gate.js +57 -0
- package/dist/core/size-gate.js.map +1 -0
- package/dist/core/size.js +32 -10
- package/dist/core/size.js.map +1 -1
- package/dist/core/skills.js +66 -28
- package/dist/core/skills.js.map +1 -1
- package/dist/core/skillsmp.js +26 -12
- package/dist/core/skillsmp.js.map +1 -1
- package/dist/core/source-basis.js +21 -9
- package/dist/core/source-basis.js.map +1 -1
- package/dist/core/status.js +26 -13
- package/dist/core/status.js.map +1 -1
- package/dist/core/table.js +8 -1
- package/dist/core/table.js.map +1 -1
- package/dist/core/tokens.js +20 -10
- package/dist/core/tokens.js.map +1 -1
- package/dist/core/tree.js +26 -16
- package/dist/core/tree.js.map +1 -1
- package/dist/core/verification-plan.js +26 -10
- package/dist/core/verification-plan.js.map +1 -1
- package/dist/core/verification.js +14 -7
- package/dist/core/verification.js.map +1 -1
- package/dist/core/view.js +93 -44
- package/dist/core/view.js.map +1 -1
- package/dist/install/global.js +37 -18
- package/dist/install/global.js.map +1 -1
- package/dist/install/hooks.js +17 -8
- package/dist/install/hooks.js.map +1 -1
- package/dist/install/manifest.js +11 -0
- package/dist/install/manifest.js.map +1 -0
- package/dist/install/mcpb.js +52 -23
- package/dist/install/mcpb.js.map +1 -1
- package/dist/install/plugin.js +73 -33
- package/dist/install/plugin.js.map +1 -1
- package/dist/install/project.js +8 -7
- package/dist/install/project.js.map +1 -1
- package/dist/install/register.js +83 -48
- package/dist/install/register.js.map +1 -1
- package/dist/install/tree.js +27 -11
- package/dist/install/tree.js.map +1 -1
- package/dist/install/update.js +46 -20
- package/dist/install/update.js.map +1 -1
- package/hooks/code-map.cjs +22 -9
- package/hooks/file-size.cjs +83 -0
- package/hooks/journal.cjs +28 -18
- package/hooks/knowledge-auto.cjs +55 -21
- package/hooks/knowledge-journal.cjs +47 -20
- package/hooks/knowledge-projects.cjs +50 -21
- package/hooks/knowledge-recall.cjs +16 -6
- package/hooks/knowledge-upkeep.cjs +22 -5
- package/hooks/locations.cjs +35 -0
- package/hooks/notify.js +52 -24
- package/hooks/obsidian.cjs +17 -7
- package/hooks/private-store.cjs +25 -10
- package/hooks/retest.cjs +39 -16
- package/hooks/session-context.cjs +30 -13
- package/hooks/session-files.cjs +44 -21
- package/hooks/session-repair.cjs +26 -11
- package/hooks/session-state.cjs +78 -38
- package/hooks/session.js +19 -8
- package/hooks/slice-command.cjs +38 -17
- package/hooks/slice-guard.cjs +20 -6
- package/hooks/slice-store.cjs +25 -12
- package/internal/guides/code.md +17 -0
- package/mcpb/server/index.js +40 -12
- package/package.json +1 -1
package/hooks/slice-guard.cjs
CHANGED
|
@@ -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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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 };
|
package/hooks/slice-store.cjs
CHANGED
|
@@ -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 ||
|
|
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) >
|
|
22
|
-
writePrivate(root,
|
|
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,
|
|
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,
|
|
31
|
-
|
|
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(
|
|
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),
|
|
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),
|
|
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 >
|
|
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,
|
|
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
|
}
|
package/internal/guides/code.md
CHANGED
|
@@ -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:
|
package/mcpb/server/index.js
CHANGED
|
@@ -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)
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
117
|
-
if (!entries.length) return fail(id,
|
|
118
|
-
const
|
|
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' ?
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
166
|
+
fail(null, PARSE_ERROR, `parse error: ${error.message}`);
|
|
139
167
|
}
|
|
140
168
|
});
|
package/package.json
CHANGED