vigthoria-cli 1.13.20 → 1.13.23
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 +63 -128
- 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 +3 -42
- package/dist/commands/chat.js +378 -1123
- package/dist/commands/coding-registration.d.ts +4 -0
- package/dist/commands/coding-registration.js +24 -0
- package/dist/commands/config.js +23 -29
- 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 +207 -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 +244 -198
- 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 +368 -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 +124 -1589
- 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 +44 -266
- package/dist/utils/api.js +647 -3180
- 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 +220 -94
- 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 +5 -6
- package/dist/utils/fastAgentRouter.js +17 -25
- 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 +15 -0
- package/dist/utils/preview-screenshot-adapter.js +51 -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/runtime-temp.d.ts +76 -0
- package/dist/utils/runtime-temp.js +492 -0
- package/dist/utils/secret-policy.d.ts +20 -0
- package/dist/utils/secret-policy.js +160 -0
- package/dist/utils/session.d.ts +17 -3
- 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 +226 -512
- 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 +115 -142
- package/install.sh +112 -81
- package/package.json +43 -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 +45 -0
- package/scripts/release/validate-live-service-gates.sh +93 -0
- package/scripts/release/validate-no-go-gates.sh +145 -174
- 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
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;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
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:
|
|
@@ -16,19 +17,6 @@ Example: {"path":"template-instant","task_kind":"web-build","fast_path":"templat
|
|
|
16
17
|
const INFERENCE_MODEL_IDS = [
|
|
17
18
|
'Vigthoria-v4-Assistant-9B',
|
|
18
19
|
];
|
|
19
|
-
function normalizePath(value) {
|
|
20
|
-
const p = String(value || '').trim().toLowerCase();
|
|
21
|
-
if (p === 'template-instant' || p === 'template' || p === 'html-template') {
|
|
22
|
-
return 'template-instant';
|
|
23
|
-
}
|
|
24
|
-
if (p === 'analysis-only' || p === 'analysis') {
|
|
25
|
-
return 'analysis-only';
|
|
26
|
-
}
|
|
27
|
-
if (p === 'v3-agent' || p === 'v3' || p === 'agent') {
|
|
28
|
-
return 'v3-agent';
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
20
|
export function parseBalancedRouterJson(raw) {
|
|
33
21
|
const text = String(raw || '').trim();
|
|
34
22
|
if (!text)
|
|
@@ -94,15 +82,15 @@ function modelIdsFromPayload(payload) {
|
|
|
94
82
|
}).filter(Boolean);
|
|
95
83
|
}
|
|
96
84
|
function includesBalancedRouterModel(modelIds) {
|
|
97
|
-
const allowed = new Set(INFERENCE_MODEL_IDS);
|
|
85
|
+
const allowed = new Set(INFERENCE_MODEL_IDS.map((id) => id.toLowerCase()));
|
|
98
86
|
return modelIds.some((id) => allowed.has(String(id).trim().toLowerCase().replace(/:latest$/, '')));
|
|
99
87
|
}
|
|
100
|
-
async function probeBalancedRouterBase(base, timeoutMs) {
|
|
88
|
+
async function probeBalancedRouterBase(base, timeoutMs, fetchImpl) {
|
|
101
89
|
const fetchJson = async (path) => {
|
|
102
90
|
const controller = new AbortController();
|
|
103
91
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
104
92
|
try {
|
|
105
|
-
const response = await
|
|
93
|
+
const response = await fetchImpl(`${base}${path}`, { signal: controller.signal });
|
|
106
94
|
const payload = await response.json().catch(() => null);
|
|
107
95
|
return { ok: response.ok, payload };
|
|
108
96
|
}
|
|
@@ -124,33 +112,37 @@ async function probeBalancedRouterBase(base, timeoutMs) {
|
|
|
124
112
|
const catalog = await fetchJson('/v1/models');
|
|
125
113
|
return catalog.ok && includesBalancedRouterModel(modelIdsFromPayload(catalog.payload));
|
|
126
114
|
}
|
|
127
|
-
async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs) {
|
|
115
|
+
async function compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl) {
|
|
128
116
|
const bases = getInferenceRouterBaseUrls(selfHostedUrl);
|
|
129
117
|
const key = bases.join('|');
|
|
130
118
|
const now = Date.now();
|
|
131
|
-
if (
|
|
119
|
+
if (fetchImpl === globalThis.fetch
|
|
120
|
+
&&
|
|
121
|
+
inferenceHealthCache
|
|
132
122
|
&& inferenceHealthCache.key === key
|
|
133
123
|
&& now - inferenceHealthCache.checkedAt < 60_000) {
|
|
134
124
|
return inferenceHealthCache.compatibleBases;
|
|
135
125
|
}
|
|
136
126
|
const compatibleBases = [];
|
|
137
127
|
for (const base of bases) {
|
|
138
|
-
if (await probeBalancedRouterBase(base, timeoutMs)) {
|
|
128
|
+
if (await probeBalancedRouterBase(base, timeoutMs, fetchImpl)) {
|
|
139
129
|
compatibleBases.push(base);
|
|
140
130
|
}
|
|
141
131
|
}
|
|
142
|
-
|
|
132
|
+
if (fetchImpl === globalThis.fetch) {
|
|
133
|
+
inferenceHealthCache = { key, compatibleBases, checkedAt: now };
|
|
134
|
+
}
|
|
143
135
|
return compatibleBases;
|
|
144
136
|
}
|
|
145
|
-
export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400) {
|
|
137
|
+
export async function isFastInferenceRouterAvailable(selfHostedUrl, timeoutMs = 400, fetchImpl = globalThis.fetch) {
|
|
146
138
|
if (process.env.VIGTHORIA_AGENT_LLM_ROUTER === '0') {
|
|
147
139
|
return false;
|
|
148
140
|
}
|
|
149
|
-
return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs)).length > 0;
|
|
141
|
+
return (await compatibleInferenceRouterBases(selfHostedUrl, timeoutMs, fetchImpl)).length > 0;
|
|
150
142
|
}
|
|
151
143
|
export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
|
|
152
144
|
const timeoutMs = options.timeoutMs ?? Number.parseInt(process.env.VIGTHORIA_AGENT_ROUTER_TIMEOUT_MS || '8000', 10);
|
|
153
|
-
const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500));
|
|
145
|
+
const bases = await compatibleInferenceRouterBases(selfHostedUrl, Math.min(timeoutMs, 1500), options.fetchImpl || globalThis.fetch);
|
|
154
146
|
let lastError = 'Inference router unreachable';
|
|
155
147
|
if (bases.length === 0) {
|
|
156
148
|
return { ok: false, error: 'No compatible Balanced 4B inference endpoint is available' };
|
|
@@ -161,14 +153,14 @@ export async function routeWithBalanced4b(prompt, selfHostedUrl, options = {}) {
|
|
|
161
153
|
const controller = new AbortController();
|
|
162
154
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
163
155
|
try {
|
|
164
|
-
const response = await fetch(`${base}/v1/chat/completions`, {
|
|
156
|
+
const response = await (options.fetchImpl || globalThis.fetch)(`${base}/v1/chat/completions`, {
|
|
165
157
|
method: 'POST',
|
|
166
158
|
headers: { 'Content-Type': 'application/json' },
|
|
167
159
|
body: JSON.stringify({
|
|
168
160
|
model,
|
|
169
161
|
messages: [
|
|
170
162
|
{ role: 'system', content: ROUTER_SYSTEM },
|
|
171
|
-
{ role: 'user', content: String(prompt || '').slice(0, 1200) },
|
|
163
|
+
{ role: 'user', content: redactSensitiveText(String(prompt || '')).slice(0, 1200) },
|
|
172
164
|
],
|
|
173
165
|
max_tokens: 96,
|
|
174
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)
|