vigthoria-cli 1.10.47 → 1.10.49
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/dist/commands/agent-session-menu.js +2 -8
- package/dist/commands/auth.js +51 -68
- package/dist/commands/bridge.js +42 -22
- package/dist/commands/cancel.js +15 -22
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.js +326 -295
- package/dist/commands/config.js +33 -73
- package/dist/commands/deploy.js +83 -123
- package/dist/commands/device.js +21 -61
- package/dist/commands/edit.js +32 -39
- package/dist/commands/explain.js +18 -25
- package/dist/commands/fork.d.ts +17 -0
- package/dist/commands/fork.js +164 -0
- package/dist/commands/generate.js +37 -44
- package/dist/commands/history.d.ts +17 -0
- package/dist/commands/history.js +113 -0
- package/dist/commands/hub.js +95 -102
- package/dist/commands/index.js +41 -46
- package/dist/commands/legion.js +146 -186
- package/dist/commands/preview.d.ts +55 -0
- package/dist/commands/preview.js +467 -0
- package/dist/commands/replay.d.ts +18 -0
- package/dist/commands/replay.js +156 -0
- package/dist/commands/repo.d.ts +97 -0
- package/dist/commands/repo.js +773 -0
- package/dist/commands/review.js +29 -36
- package/dist/commands/security.js +5 -12
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +201 -0
- package/dist/commands/wallet.js +28 -35
- package/dist/commands/workflow.js +13 -20
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1652 -0
- package/dist/utils/api.d.ts +544 -0
- package/dist/utils/api.js +5486 -0
- package/dist/utils/brain-hub-client.js +1 -5
- package/dist/utils/bridge-client.js +11 -52
- package/dist/utils/cli-state.d.ts +54 -0
- package/dist/utils/cli-state.js +185 -0
- package/dist/utils/codebase-indexer.js +4 -41
- package/dist/utils/config.d.ts +82 -0
- package/dist/utils/config.js +269 -0
- package/dist/utils/context-ranker.js +15 -21
- package/dist/utils/desktop-bridge-client.d.ts +12 -0
- package/dist/utils/desktop-bridge-client.js +30 -0
- package/dist/utils/files.js +5 -42
- package/dist/utils/logger.js +42 -50
- package/dist/utils/persona.js +3 -8
- package/dist/utils/post-write-validator.js +26 -33
- package/dist/utils/project-memory.js +16 -23
- package/dist/utils/session.d.ts +118 -0
- package/dist/utils/session.js +423 -0
- package/dist/utils/task-display.js +13 -20
- package/dist/utils/tools.d.ts +269 -0
- package/dist/utils/tools.js +3450 -0
- package/dist/utils/workspace-brain-service.js +8 -45
- package/dist/utils/workspace-cache.js +18 -26
- package/dist/utils/workspace-stream.js +21 -63
- package/package.json +2 -1
- package/scripts/release/validate-no-go-gates.sh +7 -4
package/dist/utils/logger.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Logger utility for Vigthoria CLI
|
|
4
3
|
*/
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.Logger = exports.CH = void 0;
|
|
10
|
-
exports.createSpinner = createSpinner;
|
|
11
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
12
|
-
const ora_1 = __importDefault(require("ora"));
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import ora from 'ora';
|
|
13
6
|
/**
|
|
14
7
|
* Platform-aware character map.
|
|
15
8
|
*
|
|
@@ -21,7 +14,7 @@ const ora_1 = __importDefault(require("ora"));
|
|
|
21
14
|
* monospace rendering can break.
|
|
22
15
|
*/
|
|
23
16
|
const isWin = process.platform === 'win32';
|
|
24
|
-
|
|
17
|
+
export const CH = {
|
|
25
18
|
info: 'ℹ',
|
|
26
19
|
warn: '⚠',
|
|
27
20
|
error: '✗',
|
|
@@ -64,67 +57,67 @@ exports.CH = {
|
|
|
64
57
|
* Prefer: spinner.stop() then Logger.error(msg) — which writes to
|
|
65
58
|
* stdout — instead of spinner.fail(msg).
|
|
66
59
|
*/
|
|
67
|
-
function createSpinner(textOrOpts) {
|
|
60
|
+
export function createSpinner(textOrOpts) {
|
|
68
61
|
const opts = typeof textOrOpts === 'string' ? { text: textOrOpts } : textOrOpts;
|
|
69
62
|
// Suppress spinner animation when stderr is not a TTY (piped output,
|
|
70
63
|
// CI, non-interactive terminals). The spinner object still works — its
|
|
71
64
|
// .start()/.stop()/.succeed() methods are no-ops — so callers don't
|
|
72
65
|
// need conditional logic.
|
|
73
66
|
const isSilent = !process.stderr.isTTY;
|
|
74
|
-
return (
|
|
67
|
+
return ora({ ...opts, stream: process.stderr, isSilent });
|
|
75
68
|
}
|
|
76
|
-
class Logger {
|
|
69
|
+
export class Logger {
|
|
77
70
|
verbose = false;
|
|
78
71
|
setVerbose(verbose) {
|
|
79
72
|
this.verbose = verbose;
|
|
80
73
|
}
|
|
81
74
|
debug(...args) {
|
|
82
75
|
if (this.verbose) {
|
|
83
|
-
console.log(
|
|
76
|
+
console.log(chalk.gray('[DEBUG]'), ...args);
|
|
84
77
|
}
|
|
85
78
|
}
|
|
86
79
|
info(...args) {
|
|
87
|
-
console.log(
|
|
80
|
+
console.log(chalk.blue(CH.info), ...args);
|
|
88
81
|
}
|
|
89
82
|
warn(...args) {
|
|
90
|
-
console.log(
|
|
83
|
+
console.log(chalk.yellow(CH.warn), ...args);
|
|
91
84
|
}
|
|
92
85
|
error(...args) {
|
|
93
86
|
// Write error messages to stdout (not stderr) to avoid triggering
|
|
94
87
|
// PowerShell NativeCommandError styling. The red ✗ prefix already
|
|
95
88
|
// signals an error visually; stderr redirection is unnecessary.
|
|
96
|
-
console.log(
|
|
89
|
+
console.log(chalk.red(CH.error), ...args);
|
|
97
90
|
}
|
|
98
91
|
success(...args) {
|
|
99
|
-
console.log(
|
|
92
|
+
console.log(chalk.green(CH.success), ...args);
|
|
100
93
|
}
|
|
101
94
|
// AI response formatting
|
|
102
95
|
ai(message) {
|
|
103
|
-
console.log(
|
|
96
|
+
console.log(chalk.cyan(CH.ai), message);
|
|
104
97
|
}
|
|
105
98
|
// User input formatting
|
|
106
99
|
user(message) {
|
|
107
|
-
console.log(
|
|
100
|
+
console.log(chalk.white(CH.user), message);
|
|
108
101
|
}
|
|
109
102
|
// Code block
|
|
110
103
|
code(code, language) {
|
|
111
|
-
console.log(
|
|
112
|
-
console.log(
|
|
113
|
-
console.log(
|
|
104
|
+
console.log(chalk.gray(CH.hLine.repeat(60)));
|
|
105
|
+
console.log(chalk.yellow(code));
|
|
106
|
+
console.log(chalk.gray(CH.hLine.repeat(60)));
|
|
114
107
|
}
|
|
115
108
|
// Diff output - enhanced with syntax-highlighted unified diff
|
|
116
109
|
diff(added, removed) {
|
|
117
|
-
removed.forEach(line => console.log(
|
|
118
|
-
added.forEach(line => console.log(
|
|
110
|
+
removed.forEach(line => console.log(chalk.red(`- ${line}`)));
|
|
111
|
+
added.forEach(line => console.log(chalk.green(`+ ${line}`)));
|
|
119
112
|
}
|
|
120
113
|
// Unified diff display with file context
|
|
121
114
|
unifiedDiff(filePath, oldText, newText) {
|
|
122
115
|
const oldLines = oldText.split('\n');
|
|
123
116
|
const newLines = newText.split('\n');
|
|
124
117
|
const fileName = filePath.split('/').pop() || filePath;
|
|
125
|
-
console.log(
|
|
126
|
-
console.log(
|
|
127
|
-
console.log(
|
|
118
|
+
console.log(chalk.bold.white(`\n ${CH.hLine.repeat(3)} ${fileName} ${CH.hLine.repeat(Math.max(1, 50 - fileName.length))}`));
|
|
119
|
+
console.log(chalk.gray(` --- a/${filePath}`));
|
|
120
|
+
console.log(chalk.gray(` +++ b/${filePath}`));
|
|
128
121
|
// Find diff ranges with context
|
|
129
122
|
const contextLines = 3;
|
|
130
123
|
let i = 0;
|
|
@@ -140,18 +133,18 @@ class Logger {
|
|
|
140
133
|
const startNew = Math.max(0, j - contextLines);
|
|
141
134
|
// Context before
|
|
142
135
|
for (let c = startOld; c < i; c++) {
|
|
143
|
-
console.log(
|
|
136
|
+
console.log(chalk.gray(` ${String(c + 1).padStart(4)} │ ${oldLines[c] || ''}`));
|
|
144
137
|
}
|
|
145
138
|
// Removed lines
|
|
146
139
|
while (i < oldLines.length && (j >= newLines.length || oldLines[i] !== newLines[j])) {
|
|
147
|
-
console.log(
|
|
140
|
+
console.log(chalk.red(` ${String(i + 1).padStart(4)} │-${oldLines[i]}`));
|
|
148
141
|
i++;
|
|
149
142
|
if (j < newLines.length && (i >= oldLines.length || oldLines[i] === newLines[j]))
|
|
150
143
|
break;
|
|
151
144
|
}
|
|
152
145
|
// Added lines
|
|
153
146
|
while (j < newLines.length && (i >= oldLines.length || oldLines[i] !== newLines[j])) {
|
|
154
|
-
console.log(
|
|
147
|
+
console.log(chalk.green(` ${String(j + 1).padStart(4)} │+${newLines[j]}`));
|
|
155
148
|
j++;
|
|
156
149
|
if (i < oldLines.length && oldLines[i] === newLines[j])
|
|
157
150
|
break;
|
|
@@ -159,23 +152,23 @@ class Logger {
|
|
|
159
152
|
// Context after
|
|
160
153
|
const endCtx = Math.min(oldLines.length, i + contextLines);
|
|
161
154
|
for (let c = i; c < endCtx && c < oldLines.length && j < newLines.length && oldLines[c] === newLines[j]; c++) {
|
|
162
|
-
console.log(
|
|
155
|
+
console.log(chalk.gray(` ${String(c + 1).padStart(4)} │ ${oldLines[c] || ''}`));
|
|
163
156
|
i = c + 1;
|
|
164
157
|
j++;
|
|
165
158
|
}
|
|
166
|
-
console.log(
|
|
159
|
+
console.log(chalk.gray(` ${CH.hLine.repeat(55)}`));
|
|
167
160
|
}
|
|
168
161
|
console.log();
|
|
169
162
|
}
|
|
170
163
|
// Section header
|
|
171
164
|
section(title) {
|
|
172
165
|
console.log();
|
|
173
|
-
console.log(
|
|
166
|
+
console.log(chalk.bold.cyan(`${CH.hDouble.repeat(3)} ${title} ${CH.hDouble.repeat(3)}`));
|
|
174
167
|
console.log();
|
|
175
168
|
}
|
|
176
169
|
// Progress
|
|
177
170
|
progress(message) {
|
|
178
|
-
process.stdout.write(
|
|
171
|
+
process.stdout.write(chalk.gray(`${message}\r`));
|
|
179
172
|
}
|
|
180
173
|
// Clear line
|
|
181
174
|
clearLine() {
|
|
@@ -188,18 +181,18 @@ class Logger {
|
|
|
188
181
|
const stripAnsi = (str) => str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, '');
|
|
189
182
|
const maxLen = Math.max(...lines.map(l => stripAnsi(l).length), title?.length || 0);
|
|
190
183
|
const width = maxLen + 4;
|
|
191
|
-
console.log(
|
|
184
|
+
console.log(chalk.cyan(CH.tl + CH.hLine.repeat(width - 2) + CH.tr));
|
|
192
185
|
if (title) {
|
|
193
|
-
console.log(
|
|
194
|
-
console.log(
|
|
186
|
+
console.log(chalk.cyan(CH.vLine + ' ') + chalk.bold.white(title.padEnd(width - 4)) + chalk.cyan(' ' + CH.vLine));
|
|
187
|
+
console.log(chalk.cyan(CH.teeR + CH.hLine.repeat(width - 2) + CH.teeL));
|
|
195
188
|
}
|
|
196
189
|
lines.forEach(line => {
|
|
197
190
|
// Calculate visible length and pad accordingly
|
|
198
191
|
const visibleLen = stripAnsi(line).length;
|
|
199
192
|
const padding = ' '.repeat(Math.max(0, width - 4 - visibleLen));
|
|
200
|
-
console.log(
|
|
193
|
+
console.log(chalk.cyan(CH.vLine + ' ') + line + padding + chalk.cyan(' ' + CH.vLine));
|
|
201
194
|
});
|
|
202
|
-
console.log(
|
|
195
|
+
console.log(chalk.cyan(CH.bl + CH.hLine.repeat(width - 2) + CH.br));
|
|
203
196
|
}
|
|
204
197
|
// Table output
|
|
205
198
|
table(headers, rows) {
|
|
@@ -208,19 +201,18 @@ class Logger {
|
|
|
208
201
|
return Math.max(h.length, maxData);
|
|
209
202
|
});
|
|
210
203
|
// Header
|
|
211
|
-
console.log(
|
|
212
|
-
headers.map((h, i) =>
|
|
213
|
-
|
|
204
|
+
console.log(chalk.gray(CH.vLine + ' ') +
|
|
205
|
+
headers.map((h, i) => chalk.bold(h.padEnd(colWidths[i]))).join(chalk.gray(' ' + CH.vLine + ' ')) +
|
|
206
|
+
chalk.gray(' ' + CH.vLine));
|
|
214
207
|
// Separator
|
|
215
|
-
console.log(
|
|
216
|
-
colWidths.map(w =>
|
|
217
|
-
|
|
208
|
+
console.log(chalk.gray(CH.teeR + CH.hLine) +
|
|
209
|
+
colWidths.map(w => CH.hLine.repeat(w)).join(chalk.gray(CH.hLine + CH.cross + CH.hLine)) +
|
|
210
|
+
chalk.gray(CH.hLine + CH.teeL));
|
|
218
211
|
// Rows
|
|
219
212
|
rows.forEach(row => {
|
|
220
|
-
console.log(
|
|
221
|
-
row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(
|
|
222
|
-
|
|
213
|
+
console.log(chalk.gray(CH.vLine + ' ') +
|
|
214
|
+
row.map((cell, i) => (cell || '').padEnd(colWidths[i])).join(chalk.gray(' ' + CH.vLine + ' ')) +
|
|
215
|
+
chalk.gray(' ' + CH.vLine));
|
|
223
216
|
});
|
|
224
217
|
}
|
|
225
218
|
}
|
|
226
|
-
exports.Logger = Logger;
|
package/dist/utils/persona.js
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizePersonaMode = normalizePersonaMode;
|
|
4
|
-
exports.isToneDownPrompt = isToneDownPrompt;
|
|
5
|
-
exports.buildPersonaOverlay = buildPersonaOverlay;
|
|
6
1
|
const WIENER_GRANT_PROMPT = [
|
|
7
2
|
'Optional persona overlay: Wiener Grantler mode.',
|
|
8
3
|
'You remain Vigthoria: technically precise, helpful, safe, and comprehensive.',
|
|
@@ -18,7 +13,7 @@ const TONE_DOWN_PROMPT = [
|
|
|
18
13
|
'Tone-down rule active: this request appears safety-sensitive, destructive, auth/billing-related, security-related, legal/medical/financial, or production-critical.',
|
|
19
14
|
'Use only a very light flavor line if appropriate, then prioritize plain clarity, caution, and exact steps.',
|
|
20
15
|
].join('\n');
|
|
21
|
-
function normalizePersonaMode(value) {
|
|
16
|
+
export function normalizePersonaMode(value) {
|
|
22
17
|
const normalized = String(value || '').trim().toLowerCase().replace(/_/g, '-');
|
|
23
18
|
if (!normalized || normalized === 'default' || normalized === 'off' || normalized === 'none')
|
|
24
19
|
return 'default';
|
|
@@ -26,10 +21,10 @@ function normalizePersonaMode(value) {
|
|
|
26
21
|
return 'wiener_grant';
|
|
27
22
|
return null;
|
|
28
23
|
}
|
|
29
|
-
function isToneDownPrompt(prompt) {
|
|
24
|
+
export function isToneDownPrompt(prompt) {
|
|
30
25
|
return /\b(delete|drop|destroy|wipe|rm\s+-rf|format|credential|secret|token|auth|login|billing|payment|wallet|security|vulnerability|exploit|incident|production|prod|deploy|legal|medical|financial|bank|tax)\b/i.test(prompt);
|
|
31
26
|
}
|
|
32
|
-
function buildPersonaOverlay(mode, prompt = '') {
|
|
27
|
+
export function buildPersonaOverlay(mode, prompt = '') {
|
|
33
28
|
if (mode !== 'wiener_grant')
|
|
34
29
|
return '';
|
|
35
30
|
const parts = [WIENER_GRANT_PROMPT];
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Vigthoria CLI — Post-Write Validator / Self-Healing Layer
|
|
4
3
|
*
|
|
@@ -9,18 +8,12 @@
|
|
|
9
8
|
*
|
|
10
9
|
* Used by runSelfHealingCycle in api.ts to send targeted correction prompts.
|
|
11
10
|
*/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.runPostWriteValidation = runPostWriteValidation;
|
|
17
|
-
exports.formatValidationErrors = formatValidationErrors;
|
|
18
|
-
const node_child_process_1 = require("node:child_process");
|
|
19
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
20
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
import { execFileSync } from 'node:child_process';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
import path from 'node:path';
|
|
21
14
|
function commandExists(cmd, args = ['--version']) {
|
|
22
15
|
try {
|
|
23
|
-
|
|
16
|
+
execFileSync(cmd, args, { stdio: 'pipe', timeout: 4000, windowsHide: true });
|
|
24
17
|
return true;
|
|
25
18
|
}
|
|
26
19
|
catch {
|
|
@@ -28,34 +21,34 @@ function commandExists(cmd, args = ['--version']) {
|
|
|
28
21
|
}
|
|
29
22
|
}
|
|
30
23
|
function hasTsConfig(dir) {
|
|
31
|
-
return
|
|
24
|
+
return fs.existsSync(path.join(dir, 'tsconfig.json'));
|
|
32
25
|
}
|
|
33
26
|
/**
|
|
34
27
|
* Resolve the best available tsc binary for the project.
|
|
35
28
|
* Prefers local node_modules/.bin/tsc over npx to avoid network fetches.
|
|
36
29
|
*/
|
|
37
30
|
function resolveTscBin(workspacePath) {
|
|
38
|
-
const localTsc =
|
|
39
|
-
if (
|
|
31
|
+
const localTsc = path.join(workspacePath, 'node_modules', '.bin', 'tsc');
|
|
32
|
+
if (fs.existsSync(localTsc))
|
|
40
33
|
return { cmd: localTsc, args: ["--noEmit", "--skipLibCheck"] };
|
|
41
34
|
try {
|
|
42
|
-
|
|
43
|
-
return { cmd: "npx", args: ["tsc", "--noEmit", "--skipLibCheck"] };
|
|
44
|
-
}
|
|
45
|
-
catch { /* npx not available */ }
|
|
46
|
-
try {
|
|
47
|
-
(0, node_child_process_1.execFileSync)("tsc", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true });
|
|
35
|
+
execFileSync("tsc", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true });
|
|
48
36
|
return { cmd: "tsc", args: ["--noEmit", "--skipLibCheck"] };
|
|
49
37
|
}
|
|
50
38
|
catch { /* tsc not available */ }
|
|
39
|
+
try {
|
|
40
|
+
execFileSync("npx", ["--version"], { stdio: "pipe", timeout: 4000, windowsHide: true });
|
|
41
|
+
return { cmd: "npx", args: ["--yes", "tsc", "--noEmit", "--skipLibCheck"] };
|
|
42
|
+
}
|
|
43
|
+
catch { /* npx not available */ }
|
|
51
44
|
return null;
|
|
52
45
|
}
|
|
53
46
|
function hasRealTestScript(dir) {
|
|
54
47
|
try {
|
|
55
|
-
const pkgPath =
|
|
56
|
-
if (!
|
|
48
|
+
const pkgPath = path.join(dir, 'package.json');
|
|
49
|
+
if (!fs.existsSync(pkgPath))
|
|
57
50
|
return false;
|
|
58
|
-
const pkg = JSON.parse(
|
|
51
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
59
52
|
const script = pkg.scripts?.test ?? '';
|
|
60
53
|
return typeof script === 'string' && script.length > 0
|
|
61
54
|
&& !script.startsWith('echo "Error: no test specified"');
|
|
@@ -68,8 +61,8 @@ function hasRealTestScript(dir) {
|
|
|
68
61
|
* Run post-write validation in the project directory.
|
|
69
62
|
* Each check is time-bounded to avoid blocking the CLI.
|
|
70
63
|
*/
|
|
71
|
-
async function runPostWriteValidation(workspacePath) {
|
|
72
|
-
if (!workspacePath || !
|
|
64
|
+
export async function runPostWriteValidation(workspacePath) {
|
|
65
|
+
if (!workspacePath || !fs.existsSync(workspacePath))
|
|
73
66
|
return [];
|
|
74
67
|
const results = [];
|
|
75
68
|
const opts = { cwd: workspacePath, stdio: 'pipe', windowsHide: true };
|
|
@@ -77,7 +70,7 @@ async function runPostWriteValidation(workspacePath) {
|
|
|
77
70
|
const tscBin = hasTsConfig(workspacePath) ? resolveTscBin(workspacePath) : null;
|
|
78
71
|
if (tscBin) {
|
|
79
72
|
try {
|
|
80
|
-
|
|
73
|
+
execFileSync(tscBin.cmd, tscBin.args, { ...opts, timeout: 45_000 });
|
|
81
74
|
results.push({ ran: true, tool: 'tsc', passed: true, output: '' });
|
|
82
75
|
}
|
|
83
76
|
catch (err) {
|
|
@@ -88,7 +81,7 @@ async function runPostWriteValidation(workspacePath) {
|
|
|
88
81
|
// 2. npm test
|
|
89
82
|
if (hasRealTestScript(workspacePath) && commandExists('npm')) {
|
|
90
83
|
try {
|
|
91
|
-
|
|
84
|
+
execFileSync('npm', ['test', '--', '--passWithNoTests'], { ...opts, timeout: 90_000 });
|
|
92
85
|
results.push({ ran: true, tool: 'npm test', passed: true, output: '' });
|
|
93
86
|
}
|
|
94
87
|
catch (err) {
|
|
@@ -97,13 +90,13 @@ async function runPostWriteValidation(workspacePath) {
|
|
|
97
90
|
}
|
|
98
91
|
}
|
|
99
92
|
// 3. Python syntax check
|
|
100
|
-
const hasPy =
|
|
101
|
-
||
|
|
102
|
-
||
|
|
93
|
+
const hasPy = fs.existsSync(path.join(workspacePath, 'requirements.txt'))
|
|
94
|
+
|| fs.existsSync(path.join(workspacePath, 'setup.py'))
|
|
95
|
+
|| fs.existsSync(path.join(workspacePath, 'pyproject.toml'));
|
|
103
96
|
if (hasPy && commandExists('python3', ['-c', 'import ast'])) {
|
|
104
97
|
const pyFiles = [];
|
|
105
98
|
try {
|
|
106
|
-
const entries =
|
|
99
|
+
const entries = fs.readdirSync(workspacePath, { withFileTypes: true });
|
|
107
100
|
for (const e of entries) {
|
|
108
101
|
if (e.isFile() && e.name.endsWith('.py'))
|
|
109
102
|
pyFiles.push(e.name);
|
|
@@ -114,7 +107,7 @@ async function runPostWriteValidation(workspacePath) {
|
|
|
114
107
|
let pyOut = '';
|
|
115
108
|
for (const pyFile of pyFiles.slice(0, 20)) {
|
|
116
109
|
try {
|
|
117
|
-
|
|
110
|
+
execFileSync('python3', ['-m', 'py_compile', pyFile], { ...opts, timeout: 10_000 });
|
|
118
111
|
}
|
|
119
112
|
catch (err) {
|
|
120
113
|
pyPassed = false;
|
|
@@ -130,7 +123,7 @@ async function runPostWriteValidation(workspacePath) {
|
|
|
130
123
|
/**
|
|
131
124
|
* Build a clear error summary string from failed validation results.
|
|
132
125
|
*/
|
|
133
|
-
function formatValidationErrors(results) {
|
|
126
|
+
export function formatValidationErrors(results) {
|
|
134
127
|
return results
|
|
135
128
|
.filter((r) => r.ran && !r.passed)
|
|
136
129
|
.map((r) => `[${r.tool}]\n${r.output.trim()}`)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Vigthoria Project Memory Service
|
|
4
3
|
*
|
|
@@ -6,14 +5,9 @@
|
|
|
6
5
|
* compact facts that can be retrieved for follow-up prompts without replaying
|
|
7
6
|
* full chat history or noisy tool traces.
|
|
8
7
|
*/
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
-
exports.ProjectMemoryService = void 0;
|
|
14
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
15
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
16
|
-
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
import fs from 'node:fs';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
17
11
|
const MAX_ITEMS = 240;
|
|
18
12
|
const MAX_TEXT_LENGTH = 320;
|
|
19
13
|
const MAX_CONTEXT_ITEMS = 14;
|
|
@@ -35,7 +29,7 @@ function clampText(value, max = MAX_TEXT_LENGTH) {
|
|
|
35
29
|
return normalized.length > max ? `${normalized.slice(0, max - 3)}...` : normalized;
|
|
36
30
|
}
|
|
37
31
|
function slugHash(value) {
|
|
38
|
-
return
|
|
32
|
+
return createHash('sha1').update(value).digest('hex').slice(0, 12);
|
|
39
33
|
}
|
|
40
34
|
function tokenize(value) {
|
|
41
35
|
return Array.from(new Set(String(value || '')
|
|
@@ -65,22 +59,22 @@ function inferTags(text) {
|
|
|
65
59
|
tags.add('validation');
|
|
66
60
|
return Array.from(tags).slice(0, 8);
|
|
67
61
|
}
|
|
68
|
-
class ProjectMemoryService {
|
|
62
|
+
export class ProjectMemoryService {
|
|
69
63
|
workspacePath;
|
|
70
64
|
memoryDir;
|
|
71
65
|
brainPath;
|
|
72
66
|
constructor(workspacePath = process.cwd()) {
|
|
73
|
-
this.workspacePath =
|
|
74
|
-
this.memoryDir =
|
|
75
|
-
this.brainPath =
|
|
67
|
+
this.workspacePath = path.resolve(workspacePath || process.cwd());
|
|
68
|
+
this.memoryDir = path.join(this.workspacePath, '.vigthoria', 'memory');
|
|
69
|
+
this.brainPath = path.join(this.memoryDir, 'brain.json');
|
|
76
70
|
}
|
|
77
71
|
getMemoryDir() {
|
|
78
72
|
return this.memoryDir;
|
|
79
73
|
}
|
|
80
74
|
loadBrain() {
|
|
81
75
|
try {
|
|
82
|
-
if (
|
|
83
|
-
const parsed = JSON.parse(
|
|
76
|
+
if (fs.existsSync(this.brainPath)) {
|
|
77
|
+
const parsed = JSON.parse(fs.readFileSync(this.brainPath, 'utf8'));
|
|
84
78
|
if (parsed && parsed.version === 1 && Array.isArray(parsed.items)) {
|
|
85
79
|
return parsed;
|
|
86
80
|
}
|
|
@@ -91,21 +85,21 @@ class ProjectMemoryService {
|
|
|
91
85
|
}
|
|
92
86
|
return {
|
|
93
87
|
version: 1,
|
|
94
|
-
workspaceName:
|
|
88
|
+
workspaceName: path.basename(this.workspacePath),
|
|
95
89
|
updatedAt: nowIso(),
|
|
96
90
|
items: [],
|
|
97
91
|
};
|
|
98
92
|
}
|
|
99
93
|
saveBrain(brain) {
|
|
100
94
|
try {
|
|
101
|
-
|
|
95
|
+
fs.mkdirSync(this.memoryDir, { recursive: true });
|
|
102
96
|
const normalized = {
|
|
103
97
|
...brain,
|
|
104
|
-
workspaceName: brain.workspaceName ||
|
|
98
|
+
workspaceName: brain.workspaceName || path.basename(this.workspacePath),
|
|
105
99
|
updatedAt: nowIso(),
|
|
106
100
|
items: this.dedupeAndTrim(brain.items || []),
|
|
107
101
|
};
|
|
108
|
-
|
|
102
|
+
fs.writeFileSync(this.brainPath, JSON.stringify(normalized, null, 2) + '\n', 'utf8');
|
|
109
103
|
this.writeMarkdownViews(normalized);
|
|
110
104
|
}
|
|
111
105
|
catch {
|
|
@@ -276,10 +270,10 @@ class ProjectMemoryService {
|
|
|
276
270
|
continue;
|
|
277
271
|
const heading = this.headingForType(type);
|
|
278
272
|
const lines = [`# ${heading}`, '', ...items.map((item) => `- ${item.text}`)];
|
|
279
|
-
|
|
273
|
+
fs.writeFileSync(path.join(this.memoryDir, `${type}s.md`), lines.join('\n') + '\n', 'utf8');
|
|
280
274
|
allLines.push(`## ${heading}`, '', ...items.slice(-40).map((item) => `- ${item.text}`), '');
|
|
281
275
|
}
|
|
282
|
-
|
|
276
|
+
fs.writeFileSync(path.join(this.memoryDir, 'README.md'), allLines.join('\n').trimEnd() + '\n', 'utf8');
|
|
283
277
|
}
|
|
284
278
|
headingForType(type) {
|
|
285
279
|
switch (type) {
|
|
@@ -293,4 +287,3 @@ class ProjectMemoryService {
|
|
|
293
287
|
}
|
|
294
288
|
}
|
|
295
289
|
}
|
|
296
|
-
exports.ProjectMemoryService = ProjectMemoryService;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager - Persist and resume conversations
|
|
3
|
+
* Similar to Vigthoria's session persistence
|
|
4
|
+
*/
|
|
5
|
+
import { ChatMessage } from './api.js';
|
|
6
|
+
export type AuthState = {
|
|
7
|
+
token: string | null;
|
|
8
|
+
expiresAt: number | null;
|
|
9
|
+
isValid: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type CliError = {
|
|
12
|
+
code: string;
|
|
13
|
+
message: string;
|
|
14
|
+
details?: any;
|
|
15
|
+
isCritical: boolean;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Validate persisted authentication state without assuming any field is present.
|
|
19
|
+
*
|
|
20
|
+
* Production hygiene: by default this helper is SILENT. Earlier revisions
|
|
21
|
+
* wrote ``console.warn`` for every missing-token branch, which polluted
|
|
22
|
+
* stderr on PowerShell (NativeCommandError styling) and surfaced as noise
|
|
23
|
+
* on first-run installs. Pass ``{ silent: false }`` only when the caller
|
|
24
|
+
* actually wants the warning visible.
|
|
25
|
+
*/
|
|
26
|
+
export declare function validateSession(session: AuthState, options?: {
|
|
27
|
+
silent?: boolean;
|
|
28
|
+
}): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Load persisted authentication state and normalize nullable token fields safely.
|
|
31
|
+
*
|
|
32
|
+
* Production behaviour: first-run users (no session file yet) get a clean
|
|
33
|
+
* ``{ token: null, expiresAt: null, isValid: false }`` instead of an
|
|
34
|
+
* exception. Callers that need a hard failure can inspect ``isValid``.
|
|
35
|
+
*/
|
|
36
|
+
export declare function loadSession(): Promise<AuthState>;
|
|
37
|
+
export interface Session {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
project: string;
|
|
41
|
+
model: string;
|
|
42
|
+
messages: ChatMessage[];
|
|
43
|
+
memorySummary?: string;
|
|
44
|
+
compactedAt?: string | null;
|
|
45
|
+
createdAt: string;
|
|
46
|
+
updatedAt: string;
|
|
47
|
+
agentMode: boolean;
|
|
48
|
+
operatorMode?: boolean;
|
|
49
|
+
}
|
|
50
|
+
export declare class SessionManager {
|
|
51
|
+
private sessionsDir;
|
|
52
|
+
private readonly compactThreshold;
|
|
53
|
+
private readonly retainRecentMessages;
|
|
54
|
+
/** Keep at most this many session files on disk per CLI install. */
|
|
55
|
+
private readonly maxRetainedSessions;
|
|
56
|
+
/** Sessions older than this are pruned (90 days). */
|
|
57
|
+
private readonly retentionWindowMs;
|
|
58
|
+
/** Avoid running prune on every save — at most once per CLI process. */
|
|
59
|
+
private prunedThisProcess;
|
|
60
|
+
constructor();
|
|
61
|
+
private ensureDir;
|
|
62
|
+
/**
|
|
63
|
+
* Prune sessions older than the retention window AND cap the number of
|
|
64
|
+
* retained files. This is best-effort: any unreadable file is treated
|
|
65
|
+
* as stale and removed. Pruning is bounded to once per process.
|
|
66
|
+
*/
|
|
67
|
+
private pruneIfNeeded;
|
|
68
|
+
/**
|
|
69
|
+
* Generate unique session ID
|
|
70
|
+
*/
|
|
71
|
+
private generateId;
|
|
72
|
+
/**
|
|
73
|
+
* Create a new session
|
|
74
|
+
*/
|
|
75
|
+
create(project: string, model: string, agentMode?: boolean, operatorMode?: boolean): Session;
|
|
76
|
+
/**
|
|
77
|
+
* Save session to disk — atomic + 0600 mode so transcripts cannot be
|
|
78
|
+
* read by other local users on a shared POSIX host.
|
|
79
|
+
*/
|
|
80
|
+
save(session: Session): void;
|
|
81
|
+
/**
|
|
82
|
+
* Load session by ID
|
|
83
|
+
*/
|
|
84
|
+
load(id: string): Session | null;
|
|
85
|
+
/**
|
|
86
|
+
* Get the most recent session for a project
|
|
87
|
+
*/
|
|
88
|
+
getLatest(project: string): Session | null;
|
|
89
|
+
/**
|
|
90
|
+
* List all sessions (metadata only) — corrupt or unreadable files are
|
|
91
|
+
* skipped silently unless VIGTHORIA_DEBUG=1.
|
|
92
|
+
*/
|
|
93
|
+
list(): Omit<Session, 'messages'>[];
|
|
94
|
+
/**
|
|
95
|
+
* Delete a session
|
|
96
|
+
*/
|
|
97
|
+
delete(id: string): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Clear all sessions
|
|
100
|
+
*/
|
|
101
|
+
clearAll(): number;
|
|
102
|
+
/**
|
|
103
|
+
* Add message to session
|
|
104
|
+
*/
|
|
105
|
+
addMessage(session: Session, message: ChatMessage): void;
|
|
106
|
+
compactInMemory(session: Session): Session;
|
|
107
|
+
buildMemoryContext(session: Session | null): string;
|
|
108
|
+
private compactSession;
|
|
109
|
+
private isPersistentSystemMessage;
|
|
110
|
+
private mergePersistentAndRecent;
|
|
111
|
+
private mergeSummaries;
|
|
112
|
+
private summarizeMessages;
|
|
113
|
+
private normalizeMessageContent;
|
|
114
|
+
/**
|
|
115
|
+
* Get session summary for display
|
|
116
|
+
*/
|
|
117
|
+
getSummary(session: Session): string;
|
|
118
|
+
}
|