@vreko/cli 3.0.1
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/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/CeremonyView-LQS7FTMK.js +134 -0
- package/dist/CeremonyView-LQS7FTMK.js.map +1 -0
- package/dist/InitApp-7K5DTYSW.js +1479 -0
- package/dist/InitApp-7K5DTYSW.js.map +1 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js +7 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js.map +1 -0
- package/dist/TuiApp-FX23XQBK.js +8 -0
- package/dist/TuiApp-FX23XQBK.js.map +1 -0
- package/dist/analysis-ABEO6RTN.js +8 -0
- package/dist/analysis-ABEO6RTN.js.map +1 -0
- package/dist/auth-XNBEBNPY.js +7669 -0
- package/dist/auth-XNBEBNPY.js.map +1 -0
- package/dist/ceremony-M7CXVBVA.js +45 -0
- package/dist/ceremony-M7CXVBVA.js.map +1 -0
- package/dist/chunk-A3QSZJPD.js +3147 -0
- package/dist/chunk-A3QSZJPD.js.map +1 -0
- package/dist/chunk-ASGZ5B6C.js +3969 -0
- package/dist/chunk-ASGZ5B6C.js.map +1 -0
- package/dist/chunk-DMXC2JTC.js +58 -0
- package/dist/chunk-DMXC2JTC.js.map +1 -0
- package/dist/chunk-EEBSK2IH.js +161 -0
- package/dist/chunk-EEBSK2IH.js.map +1 -0
- package/dist/chunk-EWOJGXRX.js +22 -0
- package/dist/chunk-EWOJGXRX.js.map +1 -0
- package/dist/chunk-F7GEJLP7.js +2389 -0
- package/dist/chunk-F7GEJLP7.js.map +1 -0
- package/dist/chunk-GOYL3F4T.js +605 -0
- package/dist/chunk-GOYL3F4T.js.map +1 -0
- package/dist/chunk-GRMRYWYS.js +17 -0
- package/dist/chunk-GRMRYWYS.js.map +1 -0
- package/dist/chunk-GSUGROXB.js +1951 -0
- package/dist/chunk-GSUGROXB.js.map +1 -0
- package/dist/chunk-H7773ONB.js +50 -0
- package/dist/chunk-H7773ONB.js.map +1 -0
- package/dist/chunk-HFQHU5LC.js +445 -0
- package/dist/chunk-HFQHU5LC.js.map +1 -0
- package/dist/chunk-IVHUBLJD.js +318 -0
- package/dist/chunk-IVHUBLJD.js.map +1 -0
- package/dist/chunk-KJWKY4L4.js +14 -0
- package/dist/chunk-KJWKY4L4.js.map +1 -0
- package/dist/chunk-MJVY2XUN.js +1793 -0
- package/dist/chunk-MJVY2XUN.js.map +1 -0
- package/dist/chunk-QWZVCJII.js +1797 -0
- package/dist/chunk-QWZVCJII.js.map +1 -0
- package/dist/chunk-VTSNRV3V.js +3237 -0
- package/dist/chunk-VTSNRV3V.js.map +1 -0
- package/dist/chunk-W5B4GTXR.js +1466 -0
- package/dist/chunk-W5B4GTXR.js.map +1 -0
- package/dist/chunk-WZEZLVOW.js +4995 -0
- package/dist/chunk-WZEZLVOW.js.map +1 -0
- package/dist/chunk-YPTTIXKC.js +199 -0
- package/dist/chunk-YPTTIXKC.js.map +1 -0
- package/dist/chunk-Z55UGM6X.js +6360 -0
- package/dist/chunk-Z55UGM6X.js.map +1 -0
- package/dist/chunk-ZIIRQODJ.js +110 -0
- package/dist/chunk-ZIIRQODJ.js.map +1 -0
- package/dist/chunk-ZSUQ4FMB.js +77 -0
- package/dist/chunk-ZSUQ4FMB.js.map +1 -0
- package/dist/client-JMTSZS3V.js +10 -0
- package/dist/client-JMTSZS3V.js.map +1 -0
- package/dist/deprecated-snap.js +19 -0
- package/dist/deprecated-snap.js.map +1 -0
- package/dist/dist-2KWBZFLA.js +14 -0
- package/dist/dist-2KWBZFLA.js.map +1 -0
- package/dist/dist-5ZYKNNU3.js +7 -0
- package/dist/dist-5ZYKNNU3.js.map +1 -0
- package/dist/dist-CP3RFHPI.js +11 -0
- package/dist/dist-CP3RFHPI.js.map +1 -0
- package/dist/gecko-53ITAGG6.js +56 -0
- package/dist/gecko-53ITAGG6.js.map +1 -0
- package/dist/guards-QAFC64NO.js +7 -0
- package/dist/guards-QAFC64NO.js.map +1 -0
- package/dist/index.js +57785 -0
- package/dist/index.js.map +1 -0
- package/dist/init-command-246JIVXM.js +7 -0
- package/dist/init-command-246JIVXM.js.map +1 -0
- package/dist/init-core-KAI7LCXZ.js +12 -0
- package/dist/init-core-KAI7LCXZ.js.map +1 -0
- package/dist/init-scan-RZNYDTUV.js +1919 -0
- package/dist/init-scan-RZNYDTUV.js.map +1 -0
- package/dist/local-service-adapter-6KNN6WQL.js +8 -0
- package/dist/local-service-adapter-6KNN6WQL.js.map +1 -0
- package/dist/secure-credentials-JXWAQLS2.js +306 -0
- package/dist/secure-credentials-JXWAQLS2.js.map +1 -0
- package/dist/tui-TPJPUS2R.js +111 -0
- package/dist/tui-TPJPUS2R.js.map +1 -0
- package/dist/vreko-dir-O3RLG7PI.js +8 -0
- package/dist/vreko-dir-O3RLG7PI.js.map +1 -0
- package/package.json +132 -0
- package/scripts/check-banned-words.ts +152 -0
- package/scripts/hooks/posttooluse-file-notify.sh +108 -0
- package/scripts/hooks/pretooluse-fragile-guard.sh +82 -0
- package/scripts/post-install-notice.js +24 -0
- package/scripts/postinstall.mjs +84 -0
- package/scripts/preuninstall.mjs +34 -0
- package/scripts/verify-jsx-transform.mjs +55 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Banned Words Check
|
|
3
|
+
*
|
|
4
|
+
* Scans CLI source files for banned words in user-facing strings.
|
|
5
|
+
* Prevents brand terminology drift (snapback, daemon) in terminal output.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node scripts/check-banned-words.ts
|
|
8
|
+
*
|
|
9
|
+
* @module scripts/check-banned-words
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const cliSrcDir = join(__dirname, "../src");
|
|
18
|
+
|
|
19
|
+
// Banned words with their replacements
|
|
20
|
+
const BANNED_WORDS = [
|
|
21
|
+
{ word: "snapback", replacement: "vreko", description: "Brand name" },
|
|
22
|
+
{ word: "daemon", replacement: "service", description: "Service terminology" },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// Patterns to exclude (internal code, not user-facing)
|
|
26
|
+
const EXCLUDE_PATTERNS = [
|
|
27
|
+
// File paths (directory names, log files)
|
|
28
|
+
/\.vreko\/daemon/,
|
|
29
|
+
/daemon\.log/,
|
|
30
|
+
/join\(.*daemon/,
|
|
31
|
+
/homedir\(\).*daemon/,
|
|
32
|
+
// Variable names
|
|
33
|
+
/daemonResult/,
|
|
34
|
+
/getDaemonPid/,
|
|
35
|
+
/isDaemon/,
|
|
36
|
+
/connectToDaemon/,
|
|
37
|
+
/getDaemonClient/,
|
|
38
|
+
/registerWithDaemon/,
|
|
39
|
+
/tryAutoStartDaemon/,
|
|
40
|
+
// Object properties
|
|
41
|
+
/result\.daemon/,
|
|
42
|
+
/client\.daemon/,
|
|
43
|
+
/daemonIcon/,
|
|
44
|
+
/daemonMajor/,
|
|
45
|
+
/daemonError/,
|
|
46
|
+
// Command names (the actual CLI command - users type this)
|
|
47
|
+
/program\.command\("daemon"\)/,
|
|
48
|
+
/command\("daemon"\)/,
|
|
49
|
+
/"daemon",/,
|
|
50
|
+
/process\.argv.*daemon/,
|
|
51
|
+
/vreko daemon start/,
|
|
52
|
+
// API methods (internal protocol)
|
|
53
|
+
/"daemon\/status"/,
|
|
54
|
+
/"daemon\/ping"/,
|
|
55
|
+
/"daemon\/start"/,
|
|
56
|
+
/"daemon\/shutdown"/,
|
|
57
|
+
// Help table command names (the command itself, not description)
|
|
58
|
+
/\{ name: "daemon"/,
|
|
59
|
+
// Test files
|
|
60
|
+
/__tests__/,
|
|
61
|
+
// Comments
|
|
62
|
+
/\/\/.*daemon/,
|
|
63
|
+
/\/\/.*snapback/,
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
interface Violation {
|
|
67
|
+
file: string;
|
|
68
|
+
line: number;
|
|
69
|
+
word: string;
|
|
70
|
+
match: string;
|
|
71
|
+
replacement: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const violations: Violation[] = [];
|
|
75
|
+
|
|
76
|
+
function scanFile(filePath: string): void {
|
|
77
|
+
const content = readFileSync(filePath, "utf-8");
|
|
78
|
+
const lines = content.split("\n");
|
|
79
|
+
|
|
80
|
+
for (let i = 0; i < lines.length; i++) {
|
|
81
|
+
const line = lines[i];
|
|
82
|
+
const lineNum = i + 1;
|
|
83
|
+
|
|
84
|
+
// Skip if line contains any exclude pattern
|
|
85
|
+
if (EXCLUDE_PATTERNS.some((pattern) => pattern.test(line))) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Check for banned words in string literals
|
|
90
|
+
for (const { word, replacement } of BANNED_WORDS) {
|
|
91
|
+
// Match word boundaries in string literals
|
|
92
|
+
const stringPattern = new RegExp(`["\`]\\b${word}\\b["\`]`, "gi");
|
|
93
|
+
const matches = line.match(stringPattern);
|
|
94
|
+
|
|
95
|
+
if (matches) {
|
|
96
|
+
for (const match of matches) {
|
|
97
|
+
violations.push({
|
|
98
|
+
file: filePath.replace(cliSrcDir, "src"),
|
|
99
|
+
line: lineNum,
|
|
100
|
+
word,
|
|
101
|
+
match,
|
|
102
|
+
replacement,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function scanDirectory(dir: string): void {
|
|
111
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
112
|
+
|
|
113
|
+
for (const entry of entries) {
|
|
114
|
+
const fullPath = join(dir, entry.name);
|
|
115
|
+
|
|
116
|
+
if (entry.isDirectory()) {
|
|
117
|
+
// Recursively scan subdirectories
|
|
118
|
+
scanDirectory(fullPath);
|
|
119
|
+
} else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
120
|
+
// Scan TypeScript files
|
|
121
|
+
scanFile(fullPath);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Scan relevant directories
|
|
127
|
+
const targetDirs = ["commands", "ui", "services"];
|
|
128
|
+
|
|
129
|
+
for (const targetDir of targetDirs) {
|
|
130
|
+
const dirPath = join(cliSrcDir, targetDir);
|
|
131
|
+
try {
|
|
132
|
+
scanDirectory(dirPath);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// Directory might not exist, skip
|
|
135
|
+
void error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Report results
|
|
140
|
+
if (violations.length === 0) {
|
|
141
|
+
console.log("✅ No banned words found in user-facing strings");
|
|
142
|
+
process.exit(0);
|
|
143
|
+
} else {
|
|
144
|
+
console.error(`❌ Found ${violations.length} banned word violation(s):\n`);
|
|
145
|
+
|
|
146
|
+
for (const violation of violations) {
|
|
147
|
+
console.error(` ${violation.file}:${violation.line} - "${violation.match}" → use "${violation.replacement}"`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.error("\nFix these violations before committing.");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PostToolUse hook - file-notify IPC bridge
|
|
3
|
+
# Fires after every Edit/Write/MultiEdit and notifies the vrekod daemon via
|
|
4
|
+
# intelligence/file-modified IPC so fileIndex in workspace.json stays
|
|
5
|
+
# current for Claude Code users.
|
|
6
|
+
#
|
|
7
|
+
# Hook contract:
|
|
8
|
+
# stdin - JSON: { tool: { name, input: { file_path?, edits?, paths? } } }
|
|
9
|
+
# stdout - reserved (not written by this hook)
|
|
10
|
+
# stderr - surfaced to the model; kept silent on happy path
|
|
11
|
+
# exit 0 - always (PostToolUse hooks must never block)
|
|
12
|
+
#
|
|
13
|
+
# Degraded-state breadcrumb (R-FIX-3):
|
|
14
|
+
# The *success* path stays fire-and-forget for latency. But a missing
|
|
15
|
+
# dependency (jq/socat/nc) or an unreachable socket would otherwise make every
|
|
16
|
+
# agent edit vanish before reaching the daemon - silently - which is the
|
|
17
|
+
# phantom-action failure mode this platform exists to detect. So degraded
|
|
18
|
+
# conditions leave a breadcrumb at ~/.vreko/daemon/ingress-degraded that
|
|
19
|
+
# `vr status` reads and surfaces. Asserting on silence asserts on nothing.
|
|
20
|
+
|
|
21
|
+
# ---- Degraded-state breadcrumb -------------------------------------------
|
|
22
|
+
# Writes (or refreshes) a single-line marker describing why ingress degraded.
|
|
23
|
+
# Best-effort: the breadcrumb itself must never block or fail the hook.
|
|
24
|
+
DEGRADED_MARKER="$HOME/.vreko/daemon/ingress-degraded"
|
|
25
|
+
write_degraded_breadcrumb() {
|
|
26
|
+
reason="$1"
|
|
27
|
+
marker_dir="$(dirname "$DEGRADED_MARKER")"
|
|
28
|
+
mkdir -p "$marker_dir" 2>/dev/null || return 0
|
|
29
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || echo unknown)"
|
|
30
|
+
printf 'ingress-degraded reason=%s at=%s\n' "$reason" "$ts" >"$DEGRADED_MARKER" 2>/dev/null || true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# ---- Gate: jq is required to parse the hook payload ----------------------
|
|
34
|
+
# Without jq the payload cannot be parsed and the edit cannot be forwarded.
|
|
35
|
+
# This is a degraded condition, not a no-op.
|
|
36
|
+
if ! command -v jq >/dev/null 2>&1; then
|
|
37
|
+
write_degraded_breadcrumb "missing-dependency:jq"
|
|
38
|
+
exit 0
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# ---- Parse stdin ---------------------------------------------------------
|
|
42
|
+
INPUT=$(cat)
|
|
43
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool.name // empty' 2>/dev/null || echo "")
|
|
44
|
+
|
|
45
|
+
# ---- Gate: only act on file-modifying tools ------------------------------
|
|
46
|
+
case "$TOOL_NAME" in
|
|
47
|
+
Edit|Write|MultiEdit) ;;
|
|
48
|
+
*) exit 0 ;;
|
|
49
|
+
esac
|
|
50
|
+
|
|
51
|
+
# ---- Extract target file -------------------------------------------------
|
|
52
|
+
# MultiEdit: .tool.input.edits[0].file_path takes priority
|
|
53
|
+
# Edit/Write: .tool.input.file_path
|
|
54
|
+
# Fallback: .tool.input.paths[0]
|
|
55
|
+
TARGET_FILE=$(printf '%s' "$INPUT" | jq -r '
|
|
56
|
+
.tool.input.edits[0].file_path //
|
|
57
|
+
.tool.input.file_path //
|
|
58
|
+
.tool.input.paths[0] //
|
|
59
|
+
empty
|
|
60
|
+
' 2>/dev/null || echo "")
|
|
61
|
+
|
|
62
|
+
if [ -z "$TARGET_FILE" ]; then
|
|
63
|
+
exit 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# ---- Resolve workspace ---------------------------------------------------
|
|
67
|
+
# CLAUDE_PROJECT_DIR is set by Claude Code to the current project root.
|
|
68
|
+
WORKSPACE="${CLAUDE_PROJECT_DIR:-$(pwd)}"
|
|
69
|
+
|
|
70
|
+
# ---- Build JSON-RPC payload ----------------------------------------------
|
|
71
|
+
# NOTE: `aiTool` is the AI *client* identity, which the daemon's
|
|
72
|
+
# intelligence/file-modified schema constrains to a fixed enum
|
|
73
|
+
# (cursor|copilot|claude|windsurf|burst-pattern). This hook runs under Claude
|
|
74
|
+
# Code, so the identity is "claude". The Claude Code *tool* name ($TOOL_NAME,
|
|
75
|
+
# e.g. Edit/Write/MultiEdit) is NOT a valid `aiTool` value - sending it makes the
|
|
76
|
+
# daemon reject the payload with -32602 and, because this hook is fire-and-forget,
|
|
77
|
+
# the rejection would be silent. `aiAttributed:true` already marks the change as
|
|
78
|
+
# AI-driven; `aiTool:"claude"` records which assistant.
|
|
79
|
+
SOCK="$HOME/.vreko/service.sock"
|
|
80
|
+
PAYLOAD=$(printf '{"jsonrpc":"2.0","method":"intelligence/file-modified","params":{"workspace":"%s","path":"%s","aiAttributed":true,"aiTool":"claude"},"id":1}\n' \
|
|
81
|
+
"$WORKSPACE" "$TARGET_FILE")
|
|
82
|
+
|
|
83
|
+
# ---- Gate: a transport (socat or nc) is required -------------------------
|
|
84
|
+
# Without either, the edit cannot reach the daemon - degraded, not silent.
|
|
85
|
+
if ! command -v socat >/dev/null 2>&1 && ! command -v nc >/dev/null 2>&1; then
|
|
86
|
+
write_degraded_breadcrumb "missing-dependency:socat-or-nc"
|
|
87
|
+
exit 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
# ---- Gate: the daemon socket must exist ----------------------------------
|
|
91
|
+
# A missing socket means the daemon is unreachable. Detecting it here (cheap,
|
|
92
|
+
# non-blocking) lets us leave a breadcrumb instead of firing into the void.
|
|
93
|
+
if [ ! -S "$SOCK" ]; then
|
|
94
|
+
write_degraded_breadcrumb "socket-unreachable"
|
|
95
|
+
exit 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
# ---- Fire-and-forget IPC (2s timeout, latency-preserving) ----------------
|
|
99
|
+
# Deps present and socket exists: stay fire-and-forget on the success path.
|
|
100
|
+
if command -v socat >/dev/null 2>&1; then
|
|
101
|
+
{ printf '%s' "$PAYLOAD" | socat -t2 UNIX-CONNECT:"$SOCK" -; } &>/dev/null &
|
|
102
|
+
disown 2>/dev/null || true
|
|
103
|
+
elif command -v nc >/dev/null 2>&1; then
|
|
104
|
+
{ timeout 2 nc -U "$SOCK" <<< "$PAYLOAD" || true; } &>/dev/null &
|
|
105
|
+
disown 2>/dev/null || true
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
exit 0
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PreToolUse hook - fragile-file guard
|
|
3
|
+
# Reads stdin JSON, calls `vreko pulse`, emits stderr banner if target is fragile.
|
|
4
|
+
# Exit 0 = allow (advisory); Exit 2 = block (blocking mode only).
|
|
5
|
+
# Hook contract: stdout reserved for structured responses; stderr surfaces to model.
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
# --- Parse stdin (Claude Code hook contract) -----------------------------
|
|
10
|
+
INPUT=$(cat)
|
|
11
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | jq -r '.tool.name // empty' 2>/dev/null || echo "")
|
|
12
|
+
TARGET_FILE=$(printf '%s' "$INPUT" | jq -r '.tool.input.file_path // empty' 2>/dev/null || echo "")
|
|
13
|
+
|
|
14
|
+
# Only act on Edit/Write/MultiEdit operations
|
|
15
|
+
case "$TOOL_NAME" in
|
|
16
|
+
Edit|Write|MultiEdit) ;;
|
|
17
|
+
*) exit 0 ;;
|
|
18
|
+
esac
|
|
19
|
+
|
|
20
|
+
# Require a target file
|
|
21
|
+
if [ -z "$TARGET_FILE" ]; then
|
|
22
|
+
exit 0
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# --- Daemon presence check (graceful degradation) ------------------------
|
|
26
|
+
if ! vreko daemon status --quiet 2>/dev/null; then
|
|
27
|
+
exit 0
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
# --- Pulse query (HOOK-T-03: bounded timeout) ----------------------------
|
|
31
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
32
|
+
PULSE=$(timeout 5 vreko pulse --format json --focus "$TARGET_FILE" 2>/dev/null || echo '{}')
|
|
33
|
+
else
|
|
34
|
+
PULSE=$(vreko pulse --format json --focus "$TARGET_FILE" 2>/dev/null || echo '{}')
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
# --- Determine fragility verdict ----------------------------------------
|
|
38
|
+
# Since --focus already filters to the target file, a non-empty fragileFiles
|
|
39
|
+
# array means the target IS fragile. Avoids path-matching ambiguity.
|
|
40
|
+
IS_FRAGILE=$(printf '%s' "$PULSE" | jq -r '(.fragileFiles | length) > 0' \
|
|
41
|
+
2>/dev/null || echo "false")
|
|
42
|
+
|
|
43
|
+
if [ "$IS_FRAGILE" != "true" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# --- Compute provenance fields (HOOK-04) --------------------------------
|
|
48
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
49
|
+
SHA=$(shasum -a 256 "$TARGET_FILE" 2>/dev/null | cut -c1-16 || echo "unknown")
|
|
50
|
+
elif command -v openssl >/dev/null 2>&1; then
|
|
51
|
+
SHA=$(openssl sha256 -r "$TARGET_FILE" 2>/dev/null | cut -c1-16 || echo "unknown")
|
|
52
|
+
else
|
|
53
|
+
SHA="unknown"
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
CONFIDENCE=$(printf '%s' "$PULSE" | jq -r '.fragileFiles[0]?.fragility // 0' \
|
|
57
|
+
2>/dev/null || echo "0")
|
|
58
|
+
|
|
59
|
+
HINT=$(printf '%s' "$PULSE" | jq -r '.llmHint // empty' 2>/dev/null || echo "")
|
|
60
|
+
|
|
61
|
+
# --- Mode resolution (HOOK-T-04: case literal compare, no eval) ---------
|
|
62
|
+
MODE=$(vreko config get hooks.claude-code.mode 2>/dev/null || echo "advisory")
|
|
63
|
+
case "$MODE" in
|
|
64
|
+
blocking) MODE="blocking" ;;
|
|
65
|
+
advisory|"") MODE="advisory" ;;
|
|
66
|
+
*) MODE="advisory" ;;
|
|
67
|
+
esac
|
|
68
|
+
|
|
69
|
+
# --- Emit user-visible output to stderr ---------------------------------
|
|
70
|
+
{
|
|
71
|
+
printf '⚠ Vreko: %s is flagged fragile.\n' "$TARGET_FILE"
|
|
72
|
+
printf ' SHA: %s Confidence: %s\n' "$SHA" "$CONFIDENCE"
|
|
73
|
+
if [ -n "$HINT" ]; then
|
|
74
|
+
printf '%s\n' "$HINT"
|
|
75
|
+
fi
|
|
76
|
+
} >&2
|
|
77
|
+
|
|
78
|
+
# --- Exit code ----------------------------------------------------------
|
|
79
|
+
if [ "$MODE" = "blocking" ]; then
|
|
80
|
+
exit 2
|
|
81
|
+
fi
|
|
82
|
+
exit 0
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install notice for CLI binary name change
|
|
5
|
+
*
|
|
6
|
+
* Informs users about the snap → vr migration
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const chalk = require("chalk");
|
|
10
|
+
|
|
11
|
+
console.log(chalk.cyan.bold("\n📦 Vreko CLI Installed!\n"));
|
|
12
|
+
console.log(chalk.yellow("⚠️ IMPORTANT: Command name change"));
|
|
13
|
+
console.log(chalk.gray(" Old: snap <command>"));
|
|
14
|
+
console.log(chalk.green(" New: vr <command> (recommended)"));
|
|
15
|
+
console.log(chalk.gray(" Or: vreko <command>"));
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.dim('The "snap" command still works but will be removed in v4.0.0'));
|
|
18
|
+
console.log(chalk.dim("Update your scripts and aliases now to avoid disruption."));
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(chalk.cyan("Quick start:"));
|
|
21
|
+
console.log(chalk.gray(" vr init # Initialize workspace"));
|
|
22
|
+
console.log(chalk.gray(" vr --help # View all commands"));
|
|
23
|
+
console.log();
|
|
24
|
+
console.log(chalk.dim("Migration guide: https://docs.vreko.dev/cli/migration\n"));
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Post-install script for @vreko/cli
|
|
5
|
+
* Runs after npm install to:
|
|
6
|
+
* 1. Emit the cli_installed analytics event (idempotent, once per machine).
|
|
7
|
+
* 2. Install the OS supervisor so the daemon auto-restarts on crash.
|
|
8
|
+
*
|
|
9
|
+
* CLI-EVENTS-02: Emits the `cli_installed` pioneer event idempotently.
|
|
10
|
+
* Uses a ~/.vreko/installed flag file to ensure the event fires only once
|
|
11
|
+
* per machine, regardless of how many times the package is installed.
|
|
12
|
+
*
|
|
13
|
+
* RULES: This script must ALWAYS exit 0. A postinstall failure breaks
|
|
14
|
+
* `npm install -g` entirely. Every code path must end with success.
|
|
15
|
+
* No telemetry is added beyond the existing cli_installed event.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
20
|
+
import { homedir, platform } from "node:os";
|
|
21
|
+
import { join } from "node:path";
|
|
22
|
+
|
|
23
|
+
// Idempotent cli_installed emit - fire once, then never again on this machine.
|
|
24
|
+
try {
|
|
25
|
+
const globalDir = join(homedir(), ".vreko");
|
|
26
|
+
const flagFile = join(globalDir, "installed");
|
|
27
|
+
|
|
28
|
+
if (!existsSync(flagFile)) {
|
|
29
|
+
// Ensure ~/.vreko/ exists
|
|
30
|
+
mkdirSync(globalDir, { recursive: true });
|
|
31
|
+
// Write timestamp flag before emitting so a crash/timeout doesn't re-emit
|
|
32
|
+
writeFileSync(flagFile, new Date().toISOString(), "utf8");
|
|
33
|
+
|
|
34
|
+
// Fire-and-forget POST - never await, never throw
|
|
35
|
+
const endpoint = process.env.VREKO_API_URL ?? "https://api.vreko.dev";
|
|
36
|
+
fetch(`${endpoint}/v1/analytics/events`, {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify({ event: "cli_installed" }),
|
|
40
|
+
}).catch(() => {
|
|
41
|
+
// Silently discard - postinstall must never fail due to analytics
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Non-fatal: postinstall must always succeed regardless of analytics errors
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Supervisor install - runs after the analytics section.
|
|
49
|
+
// Always exits 0. Never throws. Never fails the install.
|
|
50
|
+
try {
|
|
51
|
+
const currentPlatform = platform();
|
|
52
|
+
|
|
53
|
+
if (currentPlatform === "win32") {
|
|
54
|
+
// Windows is preview-only; supervisor install is manual.
|
|
55
|
+
process.stdout.write("vreko: Windows is preview; run `vreko service install` manually\n");
|
|
56
|
+
} else if (currentPlatform === "darwin" || currentPlatform === "linux") {
|
|
57
|
+
// Refuse to run supervisor install as root - launchd agents must be
|
|
58
|
+
// installed as the user who will own them, not as root.
|
|
59
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
60
|
+
if (isRoot) {
|
|
61
|
+
const user = process.env.SUDO_USER || process.env.USER || "your-user";
|
|
62
|
+
process.stdout.write(
|
|
63
|
+
`vreko: installed as root. Skipping supervisor install.\n` +
|
|
64
|
+
` Run: sudo -u ${user} vreko service install\n`,
|
|
65
|
+
);
|
|
66
|
+
} else {
|
|
67
|
+
// npm creates bin symlinks before running postinstall, so `vreko` is on PATH.
|
|
68
|
+
// Use "pipe" so subprocess output doesn't interleave with our one-line status.
|
|
69
|
+
const result = spawnSync("vreko", ["service", "install"], {
|
|
70
|
+
stdio: "pipe",
|
|
71
|
+
shell: false,
|
|
72
|
+
});
|
|
73
|
+
if (result.status !== 0 || result.error) {
|
|
74
|
+
// Non-fatal: log one line, do not propagate error.
|
|
75
|
+
process.stderr.write("vreko: supervisor install failed; run `vreko service install` manually\n");
|
|
76
|
+
} else {
|
|
77
|
+
process.stdout.write("vreko: supervisor installed\n");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
// Any unexpected error is non-fatal - postinstall must always succeed.
|
|
83
|
+
process.stderr.write("vreko: supervisor install failed; run `vreko service install` manually\n");
|
|
84
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-uninstall script for @vreko/cli
|
|
5
|
+
* Runs before npm uninstall to remove the OS supervisor agent/unit.
|
|
6
|
+
*
|
|
7
|
+
* RULES: This script must ALWAYS exit 0. A preuninstall failure blocks
|
|
8
|
+
* `npm uninstall -g`. Every code path must end with success.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { spawnSync } from "node:child_process";
|
|
12
|
+
import { platform } from "node:os";
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const currentPlatform = platform();
|
|
16
|
+
|
|
17
|
+
if (currentPlatform === "win32") {
|
|
18
|
+
// Windows preview: no supervisor to uninstall.
|
|
19
|
+
} else if (currentPlatform === "darwin" || currentPlatform === "linux") {
|
|
20
|
+
const isRoot = typeof process.getuid === "function" && process.getuid() === 0;
|
|
21
|
+
if (!isRoot) {
|
|
22
|
+
const result = spawnSync("vreko", ["service", "uninstall"], {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
shell: false,
|
|
25
|
+
});
|
|
26
|
+
if (result.status !== 0 || result.error) {
|
|
27
|
+
process.stderr.write("vreko: supervisor uninstall failed; run `vreko service uninstall` manually\n");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// Any unexpected error is non-fatal.
|
|
33
|
+
process.stderr.write("vreko: supervisor uninstall failed; run `vreko service uninstall` manually\n");
|
|
34
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Post-build guard: asserts the CLI dist is using the automatic JSX transform.
|
|
3
|
+
*
|
|
4
|
+
* WHY THIS EXISTS:
|
|
5
|
+
* When tsconfig.base.json has "emitDecoratorMetadata": true, tsup switches from
|
|
6
|
+
* esbuild to SWC for transpilation. SWC's default JSX transform is classic
|
|
7
|
+
* (React.createElement) WITHOUT injecting "import React from 'react'". Combined
|
|
8
|
+
* with splitting:true, this produces ESM chunks where React is referenced but
|
|
9
|
+
* never imported → ReferenceError: React is not defined at runtime.
|
|
10
|
+
*
|
|
11
|
+
* The fix lives in tsup.config.ts: swc.jsc.transform.react.runtime = "automatic"
|
|
12
|
+
* This script verifies the fix is in effect after every build.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
|
+
import { dirname, join } from "node:path";
|
|
17
|
+
import { fileURLToPath } from "node:url";
|
|
18
|
+
|
|
19
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const distIndex = join(__dirname, "../dist/index.js");
|
|
21
|
+
const distDir = join(__dirname, "../dist");
|
|
22
|
+
|
|
23
|
+
if (!existsSync(distIndex)) {
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const { readdirSync } = await import("node:fs");
|
|
28
|
+
const jsFiles = readdirSync(distDir).filter((f) => f.endsWith(".js"));
|
|
29
|
+
const fileContents = jsFiles.map((file) => ({
|
|
30
|
+
file,
|
|
31
|
+
content: readFileSync(join(distDir, file), "utf-8"),
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// In split bundles, the automatic runtime import can live in a chunk rather than index.js.
|
|
35
|
+
const hasAutomaticJsxRuntime = fileContents.some(({ content }) => content.includes("react/jsx-runtime"));
|
|
36
|
+
if (!hasAutomaticJsxRuntime) {
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Guard against classic JSX output without React in scope.
|
|
41
|
+
for (const { content } of fileContents) {
|
|
42
|
+
const hasClassicJsx = content.includes("React.createElement");
|
|
43
|
+
const hasReactImport =
|
|
44
|
+
content.includes("import React") ||
|
|
45
|
+
content.includes('from "react"') ||
|
|
46
|
+
content.includes("from 'react'") ||
|
|
47
|
+
content.includes('import("react")') ||
|
|
48
|
+
content.includes("import('react')") ||
|
|
49
|
+
content.includes('require("react")') ||
|
|
50
|
+
content.includes("require('react')");
|
|
51
|
+
|
|
52
|
+
if (hasClassicJsx && !hasReactImport) {
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
}
|