vigthoria-cli 1.13.18 → 1.13.22
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/completions/_vigthoria +49 -0
- package/completions/vigthoria.bash +6 -0
- package/completions/vigthoria.fish +41 -0
- package/dist/commands/auth.d.ts +1 -0
- package/dist/commands/auth.js +84 -135
- package/dist/commands/background.d.ts +0 -2
- package/dist/commands/background.js +26 -34
- package/dist/commands/bridge.js +45 -41
- package/dist/commands/cancel.d.ts +1 -0
- package/dist/commands/cancel.js +22 -16
- package/dist/commands/chat.d.ts +5 -42
- package/dist/commands/chat.js +462 -1122
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +32 -42
- package/dist/commands/conversation-registration.d.ts +4 -0
- package/dist/commands/conversation-registration.js +122 -0
- package/dist/commands/deploy.d.ts +8 -2
- package/dist/commands/deploy.js +92 -90
- package/dist/commands/device.d.ts +1 -2
- package/dist/commands/device.js +37 -21
- package/dist/commands/doctor-registration.d.ts +4 -0
- package/dist/commands/doctor-registration.js +200 -0
- package/dist/commands/edit.js +22 -33
- package/dist/commands/explain.js +7 -10
- package/dist/commands/fork.d.ts +1 -2
- package/dist/commands/fork.js +72 -22
- package/dist/commands/game.d.ts +1 -1
- package/dist/commands/game.js +17 -6
- package/dist/commands/generate.js +7 -9
- package/dist/commands/history.js +11 -7
- package/dist/commands/hub.d.ts +2 -2
- package/dist/commands/hub.js +34 -35
- package/dist/commands/legion.d.ts +0 -1
- package/dist/commands/legion.js +62 -79
- package/dist/commands/music.d.ts +1 -2
- package/dist/commands/music.js +62 -27
- package/dist/commands/platform-registration.d.ts +4 -0
- package/dist/commands/platform-registration.js +45 -0
- package/dist/commands/preview.d.ts +0 -1
- package/dist/commands/preview.js +82 -35
- package/dist/commands/product-run-registration.d.ts +4 -0
- package/dist/commands/product-run-registration.js +490 -0
- package/dist/commands/replay.d.ts +1 -2
- package/dist/commands/replay.js +15 -11
- package/dist/commands/repo.d.ts +14 -7
- package/dist/commands/repo.js +245 -196
- package/dist/commands/review.js +9 -12
- package/dist/commands/security.d.ts +1 -2
- package/dist/commands/security.js +12 -6
- package/dist/commands/update-registration.d.ts +3 -0
- package/dist/commands/update-registration.js +338 -0
- package/dist/commands/v4-registration.d.ts +4 -0
- package/dist/commands/v4-registration.js +24 -0
- package/dist/commands/v4.d.ts +1 -1
- package/dist/commands/v4.js +46 -8
- package/dist/commands/wallet.d.ts +1 -3
- package/dist/commands/wallet.js +18 -45
- package/dist/commands/workflow.d.ts +2 -0
- package/dist/commands/workflow.js +52 -39
- package/dist/index.js +122 -1498
- package/dist/utils/agent-stream-state.d.ts +27 -0
- package/dist/utils/agent-stream-state.js +93 -0
- package/dist/utils/agentRoute.d.ts +2 -1
- package/dist/utils/agentRoute.js +22 -9
- package/dist/utils/agentRunOutcome.d.ts +16 -0
- package/dist/utils/agentRunOutcome.js +37 -2
- package/dist/utils/api-client-factory.d.ts +8 -0
- package/dist/utils/api-client-factory.js +8 -0
- package/dist/utils/api.d.ts +45 -269
- package/dist/utils/api.js +664 -3210
- package/dist/utils/auth-session.d.ts +41 -0
- package/dist/utils/auth-session.js +224 -0
- package/dist/utils/brain-hub-client.d.ts +0 -4
- package/dist/utils/brain-hub-client.js +6 -3
- package/dist/utils/bridge-client.js +2 -0
- package/dist/utils/capability-health-service.d.ts +48 -0
- package/dist/utils/capability-health-service.js +64 -0
- package/dist/utils/chat-prompt-policy.d.ts +18 -0
- package/dist/utils/chat-prompt-policy.js +61 -0
- package/dist/utils/chat-tool-call-parser.d.ts +11 -0
- package/dist/utils/chat-tool-call-parser.js +71 -0
- package/dist/utils/cli-state.d.ts +22 -4
- package/dist/utils/cli-state.js +105 -47
- package/dist/utils/code-operations-service.d.ts +104 -0
- package/dist/utils/code-operations-service.js +837 -0
- package/dist/utils/codebase-indexer.d.ts +1 -0
- package/dist/utils/codebase-indexer.js +28 -8
- package/dist/utils/command-contract.d.ts +61 -0
- package/dist/utils/command-contract.js +149 -0
- package/dist/utils/command-policy.d.ts +10 -0
- package/dist/utils/command-policy.js +54 -0
- package/dist/utils/config.d.ts +13 -5
- package/dist/utils/config.js +227 -99
- package/dist/utils/context-ranker.js +11 -2
- package/dist/utils/context-state.d.ts +39 -0
- package/dist/utils/context-state.js +52 -0
- package/dist/utils/deckEvents.d.ts +0 -5
- package/dist/utils/deckEvents.js +3 -2
- package/dist/utils/desktop-bridge-client.js +2 -0
- package/dist/utils/direct-output-policy.d.ts +3 -0
- package/dist/utils/direct-output-policy.js +76 -0
- package/dist/utils/durable-json.d.ts +57 -0
- package/dist/utils/durable-json.js +319 -0
- package/dist/utils/fastAgentRouter.d.ts +6 -7
- package/dist/utils/fastAgentRouter.js +21 -30
- package/dist/utils/files.js +21 -13
- package/dist/utils/frontend-preview-service.d.ts +76 -0
- package/dist/utils/frontend-preview-service.js +482 -0
- package/dist/utils/goaEvents.d.ts +0 -4
- package/dist/utils/goaEvents.js +5 -2
- package/dist/utils/logger.d.ts +2 -1
- package/dist/utils/logger.js +27 -20
- package/dist/utils/mcp-context-client.d.ts +24 -0
- package/dist/utils/mcp-context-client.js +58 -0
- package/dist/utils/model-governance.d.ts +18 -0
- package/dist/utils/model-governance.js +75 -0
- package/dist/utils/model-transport-service.d.ts +44 -0
- package/dist/utils/model-transport-service.js +111 -0
- package/dist/utils/mutation-journal.d.ts +42 -0
- package/dist/utils/mutation-journal.js +387 -0
- package/dist/utils/network-policy.d.ts +24 -0
- package/dist/utils/network-policy.js +202 -0
- package/dist/utils/operator-client.d.ts +45 -0
- package/dist/utils/operator-client.js +159 -0
- package/dist/utils/post-write-validator.js +7 -9
- package/dist/utils/preview-screenshot-adapter.d.ts +13 -0
- package/dist/utils/preview-screenshot-adapter.js +36 -0
- package/dist/utils/process-policy.d.ts +23 -0
- package/dist/utils/process-policy.js +284 -0
- package/dist/utils/project-memory.d.ts +13 -3
- package/dist/utils/project-memory.js +70 -17
- package/dist/utils/prompt-workspace-resolver.d.ts +14 -0
- package/dist/utils/prompt-workspace-resolver.js +64 -0
- package/dist/utils/release-install.d.ts +11 -0
- package/dist/utils/release-install.js +155 -0
- package/dist/utils/release-policy.d.ts +64 -0
- package/dist/utils/release-policy.js +293 -0
- package/dist/utils/requestIntent.js +22 -3
- package/dist/utils/runtime-capability.d.ts +8 -0
- package/dist/utils/runtime-capability.js +16 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +34 -2
- package/dist/utils/session.js +354 -110
- package/dist/utils/tool-approval-service.d.ts +17 -0
- package/dist/utils/tool-approval-service.js +78 -0
- package/dist/utils/tool-capability-providers.d.ts +57 -0
- package/dist/utils/tool-capability-providers.js +57 -0
- package/dist/utils/tools.d.ts +2 -23
- package/dist/utils/tools.js +216 -508
- package/dist/utils/update-policy.d.ts +18 -0
- package/dist/utils/update-policy.js +137 -0
- package/dist/utils/v3-agent-client.d.ts +40 -0
- package/dist/utils/v3-agent-client.js +165 -0
- package/dist/utils/v3-workspace-service.d.ts +28 -0
- package/dist/utils/v3-workspace-service.js +279 -0
- package/dist/utils/vigflow-client.d.ts +95 -0
- package/dist/utils/vigflow-client.js +105 -0
- package/dist/utils/workspace-boundary.d.ts +45 -0
- package/dist/utils/workspace-boundary.js +377 -0
- package/dist/utils/workspace-brain-service.d.ts +2 -0
- package/dist/utils/workspace-brain-service.js +11 -1
- package/dist/utils/workspace-cache.js +37 -15
- package/dist/utils/workspace-stream.js +35 -10
- package/install.ps1 +86 -143
- package/install.sh +76 -81
- package/package.json +44 -9
- package/release-policy.json +43 -0
- package/scripts/release/LOCAL_MACHINE_USER_VERIFICATION.md +1 -1
- package/scripts/release/generate-completions.mjs +24 -0
- package/scripts/release/install-release.mjs +10 -0
- package/scripts/release/publish-cli-release.mjs +116 -0
- package/scripts/release/publish-cli-release.sh +2 -70
- package/scripts/release/resolve-release-manifest.mjs +26 -0
- package/scripts/release/test-balanced-model-live.sh +42 -0
- package/scripts/release/validate-live-service-gates.sh +90 -0
- package/scripts/release/validate-no-go-gates.sh +135 -172
- package/scripts/release/verify-runtime-consistency.mjs +4 -1
- package/dist/commands/index.d.ts +0 -12
- package/dist/commands/index.js +0 -194
- package/dist/commands/update.d.ts +0 -9
- package/dist/commands/update.js +0 -301
- package/dist/utils/templateInstantPath.d.ts +0 -18
- package/dist/utils/templateInstantPath.js +0 -228
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export function stripHiddenThoughtBlocks(text) {
|
|
2
|
+
if (!text)
|
|
3
|
+
return text;
|
|
4
|
+
return text
|
|
5
|
+
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
6
|
+
.replace(/<think>[\s\S]*?<\/redacted_thinking>/gi, '')
|
|
7
|
+
.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '')
|
|
8
|
+
.replace(/<\/redacted_thinking>/gi, '')
|
|
9
|
+
.replace(/<\/thinking>/gi, '')
|
|
10
|
+
.replace(/<\|(?:mask_start|mask_end)\|>/g, '')
|
|
11
|
+
.trim();
|
|
12
|
+
}
|
|
13
|
+
export function sanitizeDirectModeOutput(text) {
|
|
14
|
+
let cleaned = stripHiddenThoughtBlocks(text)
|
|
15
|
+
.replace(/<\|mask_start\|>[\s\S]*?<\|mask_end\|>/g, '')
|
|
16
|
+
.replace(/<think>[\s\S]*?<\/think>/gi, '')
|
|
17
|
+
.replace(/<\|(?:mask_start|mask_end)\|>/g, '');
|
|
18
|
+
cleaned = cleaned.replace(/Tool (?:read_file|grep|list_dir|glob|bash|write_file|edit_file|ssh_exec|task|multi_edit|codebase_search) (?:succeeded|FAILED)\.[\s\S]*?(?=\nTool |\n\n|$)/g, '');
|
|
19
|
+
const contaminationPatterns = [
|
|
20
|
+
/^\[Agent recovered from backend failure[^\]]*\]\s*/m,
|
|
21
|
+
/^Evidence gathered before backend failure:?\s*/m,
|
|
22
|
+
/^MANDATORY CROSS-FILE EVIDENCE[^\n]*\n?/m,
|
|
23
|
+
/^CONFIRMED CONFLICTING keys[^\n]*/m,
|
|
24
|
+
/^Keys found ONLY in [^\n]*/m,
|
|
25
|
+
/^CONSTRAINT: Your answer MUST[^\n]*/m,
|
|
26
|
+
/^GROUNDING CHECK:[^\n]*/m,
|
|
27
|
+
/^VERIFICATION PROTOCOL[^\n]*/m,
|
|
28
|
+
/^Quality gate:[^\n]*/m,
|
|
29
|
+
/^Evidence collected:[^\n]*/m,
|
|
30
|
+
/^Warning: \d+ search tool[^\n]*/m,
|
|
31
|
+
/^Tool results received for direct mode[^\n]*/m,
|
|
32
|
+
/^Step \d+ complete\. Task:[^\n]*/m,
|
|
33
|
+
/^Original user request:[^\n]*/m,
|
|
34
|
+
/^Project root boundary:[^\n]*/m,
|
|
35
|
+
/^Do not declare success[^\n]*/m,
|
|
36
|
+
/^Keep working from concrete[^\n]*/m,
|
|
37
|
+
/^Continue with tool calls if more[^\n]*/m,
|
|
38
|
+
/^IMPORTANT: Your response must be ONLY[^\n]*/m,
|
|
39
|
+
/^Because this is a debugging[^\n]*/m,
|
|
40
|
+
/^If a user is asking which file[^\n]*/m,
|
|
41
|
+
/^If the request is already[^\n]*/m,
|
|
42
|
+
/^If more work is required[^\n]*/m,
|
|
43
|
+
/^Do not ask follow-up[^\n]*/m,
|
|
44
|
+
/^CRITICAL GROUNDING RULE:[^\n]*/m,
|
|
45
|
+
/^CROSS-FILE ATTRIBUTION:[^\n]*/m,
|
|
46
|
+
/^EVIDENCE-GROUNDING RULE:[^\n]*/m,
|
|
47
|
+
/^CROSS-FILE RULE:[^\n]*/m,
|
|
48
|
+
/^OUTPUT DISCIPLINE:[^\n]*/m,
|
|
49
|
+
/^Vigthoria CLI agent operating contract\.[^\n]*/m,
|
|
50
|
+
/^You are operating inside the project root:[^\n]*/m,
|
|
51
|
+
/^CRITICAL: Begin working on the user's task[^\n]*/m,
|
|
52
|
+
/^File: \S+\s*$/m,
|
|
53
|
+
/^Search status: \S+\s*$/m,
|
|
54
|
+
/^Output:\s*$/m,
|
|
55
|
+
/^\[\.\.\. ?truncated\]\s*$/m,
|
|
56
|
+
/^---\s*$/m,
|
|
57
|
+
];
|
|
58
|
+
for (const pattern of contaminationPatterns)
|
|
59
|
+
cleaned = cleaned.replace(pattern, '');
|
|
60
|
+
cleaned = cleaned.split(/\n\n+/).filter((paragraph) => {
|
|
61
|
+
const value = paragraph.trim();
|
|
62
|
+
if (!value || /^Tool (?:read_file|grep|list_dir|glob|bash|write_file) /i.test(value))
|
|
63
|
+
return false;
|
|
64
|
+
const lines = value.split('\n');
|
|
65
|
+
if (lines.length > 6) {
|
|
66
|
+
const codeLines = lines.filter((line) => /[{};=]|function |class |const |let |var |import |export |switch |case /.test(line));
|
|
67
|
+
if (codeLines.length / lines.length > 0.4)
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}).join('\n\n');
|
|
72
|
+
return cleaned.replace(/\n{3,}/g, '\n\n').trim() || stripHiddenThoughtBlocks(text);
|
|
73
|
+
}
|
|
74
|
+
export function isDirectModeFollowUpQuestion(text) {
|
|
75
|
+
return /^(would you like me|do you want me|which aspect|what aspect|can you clarify|could you clarify|should i focus on|i will follow|i understand|i('ll| will) adhere|provide your|waiting for)/i.test(text.trim());
|
|
76
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface DurableJsonEnvelope<T> {
|
|
2
|
+
schemaVersion: number;
|
|
3
|
+
revision: number;
|
|
4
|
+
updatedAt: string;
|
|
5
|
+
data: T;
|
|
6
|
+
}
|
|
7
|
+
export interface DurableJsonStoreOptions<T> {
|
|
8
|
+
filePath: string;
|
|
9
|
+
schemaVersion: number;
|
|
10
|
+
defaultValue: () => T;
|
|
11
|
+
validate: (value: unknown) => value is T;
|
|
12
|
+
migrate?: (value: unknown, fromVersion: number) => T;
|
|
13
|
+
mode?: number;
|
|
14
|
+
lockTimeoutMs?: number;
|
|
15
|
+
staleLockMs?: number;
|
|
16
|
+
now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
export declare class DurableStateError extends Error {
|
|
19
|
+
readonly code: 'LOCK_TIMEOUT' | 'INVALID_STATE' | 'MIGRATION_FAILED' | 'WRITE_FAILED';
|
|
20
|
+
readonly filePath: string;
|
|
21
|
+
constructor(message: string, code: 'LOCK_TIMEOUT' | 'INVALID_STATE' | 'MIGRATION_FAILED' | 'WRITE_FAILED', filePath: string, options?: {
|
|
22
|
+
cause?: unknown;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export declare class DurableJsonStore<T> {
|
|
26
|
+
readonly filePath: string;
|
|
27
|
+
readonly backupPath: string;
|
|
28
|
+
readonly lockPath: string;
|
|
29
|
+
private readonly schemaVersion;
|
|
30
|
+
private readonly defaultValue;
|
|
31
|
+
private readonly validate;
|
|
32
|
+
private readonly migrate?;
|
|
33
|
+
private readonly mode;
|
|
34
|
+
private readonly lockTimeoutMs;
|
|
35
|
+
private readonly staleLockMs;
|
|
36
|
+
private readonly now;
|
|
37
|
+
constructor(options: DurableJsonStoreOptions<T>);
|
|
38
|
+
read(): T;
|
|
39
|
+
readEnvelope(): DurableJsonEnvelope<T>;
|
|
40
|
+
write(value: T): DurableJsonEnvelope<T>;
|
|
41
|
+
update(mutator: (current: T) => T): DurableJsonEnvelope<T>;
|
|
42
|
+
remove(): boolean;
|
|
43
|
+
private createEnvelope;
|
|
44
|
+
private readEnvelopeLocked;
|
|
45
|
+
private parseFile;
|
|
46
|
+
private decode;
|
|
47
|
+
private runMigration;
|
|
48
|
+
private preserveMigrationSource;
|
|
49
|
+
private tryRecoverFromBackup;
|
|
50
|
+
private writeEnvelopeLocked;
|
|
51
|
+
private writeBackupEnvelope;
|
|
52
|
+
private applyMode;
|
|
53
|
+
private syncDirectory;
|
|
54
|
+
private withLock;
|
|
55
|
+
private acquireLock;
|
|
56
|
+
private removeStaleLock;
|
|
57
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable, schema-versioned JSON persistence for CLI-owned state.
|
|
3
|
+
*
|
|
4
|
+
* The store deliberately stays synchronous because the existing CLI state APIs
|
|
5
|
+
* are synchronous. Every read that may migrate/recover and every write is
|
|
6
|
+
* serialized by an inter-process directory lock. Writes use a same-directory
|
|
7
|
+
* temporary file, fsync, and atomic rename; the last valid generation is kept
|
|
8
|
+
* as a recovery backup.
|
|
9
|
+
*/
|
|
10
|
+
import * as fs from 'fs';
|
|
11
|
+
import * as path from 'path';
|
|
12
|
+
import { randomUUID } from 'crypto';
|
|
13
|
+
export class DurableStateError extends Error {
|
|
14
|
+
code;
|
|
15
|
+
filePath;
|
|
16
|
+
constructor(message, code, filePath, options) {
|
|
17
|
+
super(message, options);
|
|
18
|
+
this.code = code;
|
|
19
|
+
this.filePath = filePath;
|
|
20
|
+
this.name = 'DurableStateError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function sleepSync(milliseconds) {
|
|
24
|
+
const waitBuffer = new Int32Array(new SharedArrayBuffer(4));
|
|
25
|
+
Atomics.wait(waitBuffer, 0, 0, milliseconds);
|
|
26
|
+
}
|
|
27
|
+
function isEnvelope(value) {
|
|
28
|
+
if (!value || typeof value !== 'object')
|
|
29
|
+
return false;
|
|
30
|
+
const candidate = value;
|
|
31
|
+
return Number.isInteger(candidate.schemaVersion)
|
|
32
|
+
&& Number.isInteger(candidate.revision)
|
|
33
|
+
&& typeof candidate.updatedAt === 'string'
|
|
34
|
+
&& Object.prototype.hasOwnProperty.call(candidate, 'data');
|
|
35
|
+
}
|
|
36
|
+
export class DurableJsonStore {
|
|
37
|
+
filePath;
|
|
38
|
+
backupPath;
|
|
39
|
+
lockPath;
|
|
40
|
+
schemaVersion;
|
|
41
|
+
defaultValue;
|
|
42
|
+
validate;
|
|
43
|
+
migrate;
|
|
44
|
+
mode;
|
|
45
|
+
lockTimeoutMs;
|
|
46
|
+
staleLockMs;
|
|
47
|
+
now;
|
|
48
|
+
constructor(options) {
|
|
49
|
+
if (!Number.isInteger(options.schemaVersion) || options.schemaVersion < 1) {
|
|
50
|
+
throw new Error('Durable JSON schemaVersion must be a positive integer.');
|
|
51
|
+
}
|
|
52
|
+
this.filePath = path.resolve(options.filePath);
|
|
53
|
+
this.backupPath = `${this.filePath}.bak`;
|
|
54
|
+
this.lockPath = `${this.filePath}.lock`;
|
|
55
|
+
this.schemaVersion = options.schemaVersion;
|
|
56
|
+
this.defaultValue = options.defaultValue;
|
|
57
|
+
this.validate = options.validate;
|
|
58
|
+
this.migrate = options.migrate;
|
|
59
|
+
this.mode = options.mode ?? 0o600;
|
|
60
|
+
this.lockTimeoutMs = options.lockTimeoutMs ?? 5000;
|
|
61
|
+
this.staleLockMs = options.staleLockMs ?? 30_000;
|
|
62
|
+
this.now = options.now ?? Date.now;
|
|
63
|
+
}
|
|
64
|
+
read() {
|
|
65
|
+
return this.withLock(() => this.readEnvelopeLocked(true).data);
|
|
66
|
+
}
|
|
67
|
+
readEnvelope() {
|
|
68
|
+
return this.withLock(() => this.readEnvelopeLocked(true));
|
|
69
|
+
}
|
|
70
|
+
write(value) {
|
|
71
|
+
if (!this.validate(value)) {
|
|
72
|
+
throw new DurableStateError('Refusing to write invalid durable state.', 'INVALID_STATE', this.filePath);
|
|
73
|
+
}
|
|
74
|
+
return this.withLock(() => {
|
|
75
|
+
const current = this.readEnvelopeLocked(false);
|
|
76
|
+
const envelope = this.createEnvelope(value, current.revision + 1);
|
|
77
|
+
this.writeEnvelopeLocked(envelope);
|
|
78
|
+
return envelope;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
update(mutator) {
|
|
82
|
+
return this.withLock(() => {
|
|
83
|
+
const current = this.readEnvelopeLocked(true);
|
|
84
|
+
const next = mutator(current.data);
|
|
85
|
+
if (!this.validate(next)) {
|
|
86
|
+
throw new DurableStateError('Durable state update produced an invalid value.', 'INVALID_STATE', this.filePath);
|
|
87
|
+
}
|
|
88
|
+
const envelope = this.createEnvelope(next, current.revision + 1);
|
|
89
|
+
this.writeEnvelopeLocked(envelope);
|
|
90
|
+
return envelope;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
remove() {
|
|
94
|
+
return this.withLock(() => {
|
|
95
|
+
let removed = false;
|
|
96
|
+
for (const candidate of [this.filePath, this.backupPath]) {
|
|
97
|
+
try {
|
|
98
|
+
fs.unlinkSync(candidate);
|
|
99
|
+
removed = true;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error.code !== 'ENOENT')
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
this.syncDirectory();
|
|
107
|
+
return removed;
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
createEnvelope(data, revision) {
|
|
111
|
+
return {
|
|
112
|
+
schemaVersion: this.schemaVersion,
|
|
113
|
+
revision,
|
|
114
|
+
updatedAt: new Date(this.now()).toISOString(),
|
|
115
|
+
data,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
readEnvelopeLocked(repair) {
|
|
119
|
+
if (!fs.existsSync(this.filePath)) {
|
|
120
|
+
return this.createEnvelope(this.defaultValue(), 0);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const parsed = this.parseFile(this.filePath);
|
|
124
|
+
const decoded = this.decode(parsed);
|
|
125
|
+
if (decoded.requiresRewrite && repair) {
|
|
126
|
+
this.preserveMigrationSource();
|
|
127
|
+
this.writeEnvelopeLocked(decoded.envelope);
|
|
128
|
+
}
|
|
129
|
+
return decoded.envelope;
|
|
130
|
+
}
|
|
131
|
+
catch (primaryError) {
|
|
132
|
+
if (repair) {
|
|
133
|
+
const recovered = this.tryRecoverFromBackup(primaryError);
|
|
134
|
+
if (recovered)
|
|
135
|
+
return recovered;
|
|
136
|
+
}
|
|
137
|
+
if (primaryError instanceof DurableStateError)
|
|
138
|
+
throw primaryError;
|
|
139
|
+
throw new DurableStateError('Durable state is unreadable and no valid recovery copy exists.', 'INVALID_STATE', this.filePath, { cause: primaryError });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
parseFile(candidate) {
|
|
143
|
+
const text = fs.readFileSync(candidate, 'utf8');
|
|
144
|
+
if (!text.trim()) {
|
|
145
|
+
throw new Error('State file is empty.');
|
|
146
|
+
}
|
|
147
|
+
return JSON.parse(text);
|
|
148
|
+
}
|
|
149
|
+
decode(parsed) {
|
|
150
|
+
if (isEnvelope(parsed)) {
|
|
151
|
+
if (parsed.schemaVersion > this.schemaVersion) {
|
|
152
|
+
throw new DurableStateError(`State schema ${parsed.schemaVersion} is newer than supported schema ${this.schemaVersion}.`, 'INVALID_STATE', this.filePath);
|
|
153
|
+
}
|
|
154
|
+
if (parsed.schemaVersion === this.schemaVersion && this.validate(parsed.data)) {
|
|
155
|
+
return { envelope: parsed, requiresRewrite: false };
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
envelope: this.createEnvelope(this.runMigration(parsed.data, parsed.schemaVersion), parsed.revision + 1),
|
|
159
|
+
requiresRewrite: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
envelope: this.createEnvelope(this.runMigration(parsed, 0), 1),
|
|
164
|
+
requiresRewrite: true,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
runMigration(value, fromVersion) {
|
|
168
|
+
try {
|
|
169
|
+
const migrated = this.migrate ? this.migrate(value, fromVersion) : value;
|
|
170
|
+
if (!this.validate(migrated))
|
|
171
|
+
throw new Error('Migrated value failed schema validation.');
|
|
172
|
+
return migrated;
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
throw new DurableStateError(`Could not migrate durable state from schema ${fromVersion} to ${this.schemaVersion}.`, 'MIGRATION_FAILED', this.filePath, { cause: error });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
preserveMigrationSource() {
|
|
179
|
+
const migrationBackup = `${this.filePath}.pre-migration.bak`;
|
|
180
|
+
if (!fs.existsSync(migrationBackup)) {
|
|
181
|
+
fs.copyFileSync(this.filePath, migrationBackup, fs.constants.COPYFILE_EXCL);
|
|
182
|
+
this.applyMode(migrationBackup);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
tryRecoverFromBackup(primaryError) {
|
|
186
|
+
if (!fs.existsSync(this.backupPath))
|
|
187
|
+
return null;
|
|
188
|
+
try {
|
|
189
|
+
const recovered = this.decode(this.parseFile(this.backupPath)).envelope;
|
|
190
|
+
const corruptPath = `${this.filePath}.corrupt-${this.now()}-${process.pid}`;
|
|
191
|
+
fs.renameSync(this.filePath, corruptPath);
|
|
192
|
+
this.applyMode(corruptPath);
|
|
193
|
+
this.writeEnvelopeLocked(recovered, false);
|
|
194
|
+
return recovered;
|
|
195
|
+
}
|
|
196
|
+
catch (backupError) {
|
|
197
|
+
throw new DurableStateError('Durable state and its recovery backup are both invalid.', 'INVALID_STATE', this.filePath, { cause: { primaryError, backupError } });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
writeEnvelopeLocked(envelope, rotateBackup = true) {
|
|
201
|
+
const parent = path.dirname(this.filePath);
|
|
202
|
+
fs.mkdirSync(parent, { recursive: true, mode: 0o700 });
|
|
203
|
+
if (process.platform !== 'win32') {
|
|
204
|
+
try {
|
|
205
|
+
fs.chmodSync(parent, 0o700);
|
|
206
|
+
}
|
|
207
|
+
catch { /* filesystem may not support chmod */ }
|
|
208
|
+
}
|
|
209
|
+
const temporary = `${this.filePath}.${process.pid}.${randomUUID()}.tmp`;
|
|
210
|
+
let descriptor;
|
|
211
|
+
try {
|
|
212
|
+
descriptor = fs.openSync(temporary, 'wx', this.mode);
|
|
213
|
+
fs.writeFileSync(descriptor, `${JSON.stringify(envelope, null, 2)}\n`, 'utf8');
|
|
214
|
+
fs.fsyncSync(descriptor);
|
|
215
|
+
fs.closeSync(descriptor);
|
|
216
|
+
descriptor = undefined;
|
|
217
|
+
this.applyMode(temporary);
|
|
218
|
+
if (rotateBackup && fs.existsSync(this.filePath)) {
|
|
219
|
+
try {
|
|
220
|
+
const current = this.decode(this.parseFile(this.filePath)).envelope;
|
|
221
|
+
this.writeBackupEnvelope(current);
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// Never replace a known-good backup with a corrupt current file.
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
fs.renameSync(temporary, this.filePath);
|
|
228
|
+
this.applyMode(this.filePath);
|
|
229
|
+
this.syncDirectory();
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (descriptor !== undefined) {
|
|
233
|
+
try {
|
|
234
|
+
fs.closeSync(descriptor);
|
|
235
|
+
}
|
|
236
|
+
catch { /* already closed */ }
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
fs.unlinkSync(temporary);
|
|
240
|
+
}
|
|
241
|
+
catch { /* absent */ }
|
|
242
|
+
if (error instanceof DurableStateError)
|
|
243
|
+
throw error;
|
|
244
|
+
throw new DurableStateError('Atomic durable-state write failed.', 'WRITE_FAILED', this.filePath, { cause: error });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
writeBackupEnvelope(envelope) {
|
|
248
|
+
const temporary = `${this.backupPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
249
|
+
fs.writeFileSync(temporary, `${JSON.stringify(envelope, null, 2)}\n`, { encoding: 'utf8', mode: this.mode, flag: 'wx' });
|
|
250
|
+
this.applyMode(temporary);
|
|
251
|
+
fs.renameSync(temporary, this.backupPath);
|
|
252
|
+
this.applyMode(this.backupPath);
|
|
253
|
+
}
|
|
254
|
+
applyMode(candidate) {
|
|
255
|
+
if (process.platform === 'win32')
|
|
256
|
+
return;
|
|
257
|
+
try {
|
|
258
|
+
fs.chmodSync(candidate, this.mode);
|
|
259
|
+
}
|
|
260
|
+
catch { /* filesystem may not support chmod */ }
|
|
261
|
+
}
|
|
262
|
+
syncDirectory() {
|
|
263
|
+
if (process.platform === 'win32')
|
|
264
|
+
return;
|
|
265
|
+
let descriptor;
|
|
266
|
+
try {
|
|
267
|
+
descriptor = fs.openSync(path.dirname(this.filePath), 'r');
|
|
268
|
+
fs.fsyncSync(descriptor);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Some filesystems do not allow directory fsync.
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
if (descriptor !== undefined)
|
|
275
|
+
fs.closeSync(descriptor);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
withLock(operation) {
|
|
279
|
+
this.acquireLock();
|
|
280
|
+
try {
|
|
281
|
+
return operation();
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
try {
|
|
285
|
+
fs.rmdirSync(this.lockPath);
|
|
286
|
+
}
|
|
287
|
+
catch { /* stale lock cleanup handles leftovers */ }
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
acquireLock() {
|
|
291
|
+
fs.mkdirSync(path.dirname(this.filePath), { recursive: true, mode: 0o700 });
|
|
292
|
+
const startedAt = this.now();
|
|
293
|
+
while (true) {
|
|
294
|
+
try {
|
|
295
|
+
fs.mkdirSync(this.lockPath, { mode: 0o700 });
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
if (error.code !== 'EEXIST')
|
|
300
|
+
throw error;
|
|
301
|
+
this.removeStaleLock();
|
|
302
|
+
if (this.now() - startedAt >= this.lockTimeoutMs) {
|
|
303
|
+
throw new DurableStateError('Timed out waiting for the durable-state writer lock.', 'LOCK_TIMEOUT', this.filePath);
|
|
304
|
+
}
|
|
305
|
+
sleepSync(10);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
removeStaleLock() {
|
|
310
|
+
try {
|
|
311
|
+
const age = this.now() - fs.statSync(this.lockPath).mtimeMs;
|
|
312
|
+
if (age > this.staleLockMs)
|
|
313
|
+
fs.rmdirSync(this.lockPath);
|
|
314
|
+
}
|
|
315
|
+
catch {
|
|
316
|
+
// Another writer may have released the lock between stat and removal.
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
|
|
4
|
-
*/
|
|
5
|
-
export type AgentRoutePath = 'template-instant' | 'v3-agent' | 'analysis-only';
|
|
1
|
+
/** The CLI has one executable agent transport; specialized behavior is a server-side hint. */
|
|
2
|
+
export type AgentRoutePath = 'v3-agent';
|
|
6
3
|
export interface BalancedRouterJson {
|
|
7
4
|
path?: string;
|
|
8
5
|
task_kind?: string;
|
|
@@ -21,14 +18,16 @@ export interface BalancedRouterResult {
|
|
|
21
18
|
endpoint?: string;
|
|
22
19
|
error?: string;
|
|
23
20
|
}
|
|
21
|
+
export type InferenceFetch = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
24
22
|
export declare function parseBalancedRouterJson(raw: string): BalancedRouterJson | null;
|
|
25
23
|
/**
|
|
26
|
-
* Inference bases for
|
|
24
|
+
* Inference bases for the V4 Assistant routing lane only.
|
|
27
25
|
* Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
|
|
28
26
|
*/
|
|
29
27
|
export declare function getInferenceRouterBaseUrls(selfHostedUrl: string | null): string[];
|
|
30
|
-
export declare function isFastInferenceRouterAvailable(selfHostedUrl: string | null, timeoutMs?: number): Promise<boolean>;
|
|
28
|
+
export declare function isFastInferenceRouterAvailable(selfHostedUrl: string | null, timeoutMs?: number, fetchImpl?: InferenceFetch): Promise<boolean>;
|
|
31
29
|
export declare function routeWithBalanced4b(prompt: string, selfHostedUrl: string | null, options?: {
|
|
32
30
|
timeoutMs?: number;
|
|
31
|
+
fetchImpl?: InferenceFetch;
|
|
33
32
|
}): Promise<BalancedRouterResult>;
|
|
34
33
|
export declare function shouldUseBalancedRouter(prompt: string, regexTaskKind?: string): boolean;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vigthoria
|
|
2
|
+
* Vigthoria v4 Assistant 9B — fast agent path classifier through the governed router.
|
|
3
3
|
* Regex/trivial detection always wins; LLM router refines ambiguous build prompts only.
|
|
4
4
|
*/
|
|
5
|
+
import { redactSensitiveText } from './secret-policy.js';
|
|
5
6
|
const ROUTER_SYSTEM = `You are Vigthoria Agent Router. Reply with ONE JSON object only. No markdown, no HTML, no code fences.
|
|
6
7
|
|
|
7
8
|
Paths:
|
|
@@ -14,22 +15,8 @@ fast_path: "template-instant" or null
|
|
|
14
15
|
|
|
15
16
|
Example: {"path":"template-instant","task_kind":"web-build","fast_path":"template-instant","confidence":0.95,"reason":"trivial html popup page"}`;
|
|
16
17
|
const INFERENCE_MODEL_IDS = [
|
|
17
|
-
'
|
|
18
|
-
'vigthoria-v3-balanced-4b',
|
|
18
|
+
'Vigthoria-v4-Assistant-9B',
|
|
19
19
|
];
|
|
20
|
-
function normalizePath(value) {
|
|
21
|
-
const p = String(value || '').trim().toLowerCase();
|
|
22
|
-
if (p === 'template-instant' || p === 'template' || p === 'html-template') {
|
|
23
|
-
return 'template-instant';
|
|
24
|
-
}
|
|
25
|
-
if (p === 'analysis-only' || p === 'analysis') {
|
|
26
|
-
return 'analysis-only';
|
|
27
|
-
}
|
|
28
|
-
if (p === 'v3-agent' || p === 'v3' || p === 'agent') {
|
|
29
|
-
return 'v3-agent';
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
20
|
export function parseBalancedRouterJson(raw) {
|
|
34
21
|
const text = String(raw || '').trim();
|
|
35
22
|
if (!text)
|
|
@@ -56,7 +43,7 @@ export function parseBalancedRouterJson(raw) {
|
|
|
56
43
|
return null;
|
|
57
44
|
}
|
|
58
45
|
/**
|
|
59
|
-
* Inference bases for
|
|
46
|
+
* Inference bases for the V4 Assistant routing lane only.
|
|
60
47
|
* Local developer machines must opt in (env) — avoids ~1.5s localhost probe on every Windows turn.
|
|
61
48
|
*/
|
|
62
49
|
export function getInferenceRouterBaseUrls(selfHostedUrl) {
|
|
@@ -69,7 +56,7 @@ export function getInferenceRouterBaseUrls(selfHostedUrl) {
|
|
|
69
56
|
|| process.env.VIGTHORIA_AGENT_LLM_ROUTER === '1'
|
|
70
57
|
|| process.platform === 'linux';
|
|
71
58
|
if (allowLocalhost) {
|
|
72
|
-
urls.push('http://127.0.0.1:
|
|
59
|
+
urls.push('http://127.0.0.1:4009');
|
|
73
60
|
}
|
|
74
61
|
return [...new Set(urls)];
|
|
75
62
|
}
|
|
@@ -95,15 +82,15 @@ function modelIdsFromPayload(payload) {
|
|
|
95
82
|
}).filter(Boolean);
|
|
96
83
|
}
|
|
97
84
|
function includesBalancedRouterModel(modelIds) {
|
|
98
|
-
const allowed = new Set(INFERENCE_MODEL_IDS);
|
|
85
|
+
const allowed = new Set(INFERENCE_MODEL_IDS.map((id) => id.toLowerCase()));
|
|
99
86
|
return modelIds.some((id) => allowed.has(String(id).trim().toLowerCase().replace(/:latest$/, '')));
|
|
100
87
|
}
|
|
101
|
-
async function probeBalancedRouterBase(base, timeoutMs) {
|
|
88
|
+
async function probeBalancedRouterBase(base, timeoutMs, fetchImpl) {
|
|
102
89
|
const fetchJson = async (path) => {
|
|
103
90
|
const controller = new AbortController();
|
|
104
91
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
105
92
|
try {
|
|
106
|
-
const response = await
|
|
93
|
+
const response = await fetchImpl(`${base}${path}`, { signal: controller.signal });
|
|
107
94
|
const payload = await response.json().catch(() => null);
|
|
108
95
|
return { ok: response.ok, payload };
|
|
109
96
|
}
|
|
@@ -125,33 +112,37 @@ async function probeBalancedRouterBase(base, timeoutMs) {
|
|
|
125
112
|
const catalog = await fetchJson('/v1/models');
|
|
126
113
|
return catalog.ok && includesBalancedRouterModel(modelIdsFromPayload(catalog.payload));
|
|
127
114
|
}
|
|
128
|
-
async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs) {
|
|
115
|
+
async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl) {
|
|
129
116
|
const bases = getInferenceRouterBaseUrls(selfHostedUrl);
|
|
130
117
|
const key = bases.join('|');
|
|
131
118
|
const now = Date.now();
|
|
132
|
-
if (
|
|
119
|
+
if (fetchImpl === globalThis.fetch
|
|
120
|
+
&&
|
|
121
|
+
inferenceHealthCache
|
|
133
122
|
&& inferenceHealthCache.key === key
|
|
134
123
|
&& now - inferenceHealthCache.checkedAt < 60_000) {
|
|
135
124
|
return inferenceHealthCache.compatibleBases;
|
|
136
125
|
}
|
|
137
126
|
const compatibleBases = [];
|
|
138
127
|
for (const base of bases) {
|
|
139
|
-
if (await probeBalancedRouterBase(base, timeoutMs)) {
|
|
128
|
+
if (await probeBalancedRouterBase(base, timeoutMs, fetchImpl)) {
|
|
140
129
|
compatibleBases.push(base);
|
|
141
130
|
}
|
|
142
131
|
}
|
|
143
|
-
|
|
132
|
+
if (fetchImpl === globalThis.fetch) {
|
|
133
|
+
inferenceHealthCache = { key, compatibleBases, checkedAt: now };
|
|
134
|
+
}
|
|
144
135
|
return compatibleBases;
|
|
145
136
|
}
|
|
146
|
-
export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400) {
|
|
137
|
+
export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400, fetchImpl = globalThis.fetch) {
|
|
147
138
|
if (process.env.VIGTHORIA_AGENT_LLM_ROUTER === '0') {
|
|
148
139
|
return false;
|
|
149
140
|
}
|
|
150
|
-
return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs)).length > 0;
|
|
141
|
+
return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl)).length > 0;
|
|
151
142
|
}
|
|
152
143
|
export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
|
|
153
144
|
const timeoutMs = options.timeoutMs ?? Number.parseInt(process.env.VIGTHORIA_AGENT_ROUTER_TIMEOUT_MS || '8000', 10);
|
|
154
|
-
const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500));
|
|
145
|
+
const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500), options.fetchImpl || globalThis.fetch);
|
|
155
146
|
let lastError = 'Inference router unreachable';
|
|
156
147
|
if (bases.length === 0) {
|
|
157
148
|
return { ok: false, error: 'No compatible Balanced 4B inference endpoint is available' };
|
|
@@ -162,14 +153,14 @@ export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
|
|
|
162
153
|
const controller = new AbortController();
|
|
163
154
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
164
155
|
try {
|
|
165
|
-
const response = await fetch(`${base}/v1/chat/completions`, {
|
|
156
|
+
const response = await (options.fetchImpl || globalThis.fetch)(`${base}/v1/chat/completions`, {
|
|
166
157
|
method: 'POST',
|
|
167
158
|
headers: { 'Content-Type': 'application/json' },
|
|
168
159
|
body: JSON.stringify({
|
|
169
160
|
model,
|
|
170
161
|
messages: [
|
|
171
162
|
{ role: 'system', content: ROUTER_SYSTEM },
|
|
172
|
-
{ role: 'user', content: String(prompt || '').slice(0, 1200) },
|
|
163
|
+
{ role: 'user', content: redactSensitiveText(String(prompt || '')).slice(0, 1200) },
|
|
173
164
|
],
|
|
174
165
|
max_tokens: 96,
|
|
175
166
|
temperature: 0,
|
package/dist/utils/files.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
import * as fs from 'fs';
|
|
5
5
|
import * as path from 'path';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
|
+
import { assertContainedAbsolutePath, resolveWorkspacePath } from './workspace-boundary.js';
|
|
8
|
+
import { containsHighConfidenceSecret, isSensitivePath } from './secret-policy.js';
|
|
7
9
|
export class FileUtils {
|
|
8
10
|
projectRoot;
|
|
9
11
|
ignorePatterns;
|
|
@@ -21,22 +23,19 @@ export class FileUtils {
|
|
|
21
23
|
// Read file with metadata
|
|
22
24
|
readFile(filePath) {
|
|
23
25
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
absolutePath = fs.realpathSync(absolutePath);
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
// Keep original path if realpath fails
|
|
33
|
-
}
|
|
26
|
+
if (isSensitivePath(filePath))
|
|
27
|
+
return null;
|
|
28
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
29
|
+
? assertContainedAbsolutePath(this.projectRoot, filePath)
|
|
30
|
+
: resolveWorkspacePath(this.projectRoot, filePath);
|
|
34
31
|
// Check if it's a regular file (not a directory or special file)
|
|
35
32
|
const stats = fs.statSync(absolutePath);
|
|
36
33
|
if (!stats.isFile()) {
|
|
37
34
|
return null;
|
|
38
35
|
}
|
|
39
36
|
const content = fs.readFileSync(absolutePath, 'utf-8');
|
|
37
|
+
if (containsHighConfidenceSecret(content))
|
|
38
|
+
return null;
|
|
40
39
|
const relativePath = path.relative(this.projectRoot, absolutePath);
|
|
41
40
|
return {
|
|
42
41
|
path: absolutePath,
|
|
@@ -62,8 +61,8 @@ export class FileUtils {
|
|
|
62
61
|
writeFile(filePath, content) {
|
|
63
62
|
try {
|
|
64
63
|
const absolutePath = path.isAbsolute(filePath)
|
|
65
|
-
? filePath
|
|
66
|
-
:
|
|
64
|
+
? assertContainedAbsolutePath(this.projectRoot, filePath, { allowMissing: true })
|
|
65
|
+
: resolveWorkspacePath(this.projectRoot, filePath, { allowMissing: true });
|
|
67
66
|
// Create directory if needed
|
|
68
67
|
const dir = path.dirname(absolutePath);
|
|
69
68
|
if (!fs.existsSync(dir)) {
|
|
@@ -98,8 +97,17 @@ export class FileUtils {
|
|
|
98
97
|
cwd: this.projectRoot,
|
|
99
98
|
ignore: this.ignorePatterns,
|
|
100
99
|
nodir: true,
|
|
100
|
+
follow: false,
|
|
101
|
+
});
|
|
102
|
+
return files.filter((file) => {
|
|
103
|
+
try {
|
|
104
|
+
resolveWorkspacePath(this.projectRoot, file);
|
|
105
|
+
return !isSensitivePath(file);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
101
110
|
});
|
|
102
|
-
return files;
|
|
103
111
|
}
|
|
104
112
|
catch (error) {
|
|
105
113
|
// Return empty array on error (e.g., permission issues on Windows)
|