vigthoria-cli 1.12.3 → 1.13.6

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.
@@ -23,6 +23,7 @@ export class CodebaseIndexer {
23
23
  '.DS_Store', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml',
24
24
  '.vsix', '.map', '.min.js', '.min.css', 'coverage',
25
25
  '.cache', '.tmp', 'tmp', 'temp', '.idea', '.vscode',
26
+ '.vigthoria',
26
27
  ];
27
28
  supportedExtensions = new Set([
28
29
  '.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rs',
@@ -96,10 +97,23 @@ export class CodebaseIndexer {
96
97
  for (const filePath of files) {
97
98
  this.indexFile(filePath);
98
99
  }
99
- const indexHash = crypto.createHash('sha256')
100
- .update(`${this.indexedFileCount}:${this.totalChunks}:${files.length}`)
101
- .digest('hex')
102
- .slice(0, 16);
100
+ // The synchronization contract is content-addressed. Stable file and
101
+ // chunk counts must never hide a source modification from the Brain.
102
+ const contentHasher = crypto.createHash('sha256');
103
+ const contentEntries = Array.from(this.fileHashes.entries())
104
+ .map(([filePath, fileHash]) => [
105
+ path.relative(this.workspaceRoot, filePath).split(path.sep).join('/'),
106
+ fileHash,
107
+ ])
108
+ .sort(([left], [right]) => left.localeCompare(right));
109
+ for (const [relativePath, fileHash] of contentEntries) {
110
+ contentHasher.update(relativePath, 'utf8');
111
+ contentHasher.update('\0', 'utf8');
112
+ contentHasher.update(fileHash, 'ascii');
113
+ contentHasher.update('\n', 'utf8');
114
+ }
115
+ // Empty workspaces still receive a deterministic, valid SHA-256 value.
116
+ const indexHash = contentHasher.digest('hex');
103
117
  const topFiles = files.slice(0, 12).map((filePath) => path.relative(this.workspaceRoot, filePath));
104
118
  const meta = {
105
119
  indexedFileCount: this.indexedFileCount,
@@ -308,7 +322,16 @@ export class CodebaseIndexer {
308
322
  shouldIgnore(name, fullPath) {
309
323
  if (name.startsWith('.') && name !== '.env')
310
324
  return true;
311
- const lowerPath = fullPath.toLowerCase();
312
- return this.ignorePatterns.some((pattern) => lowerPath.includes(pattern));
325
+ const relativePath = path.relative(this.workspaceRoot, fullPath);
326
+ const segments = relativePath.split(path.sep).map((segment) => segment.toLowerCase());
327
+ const fileName = segments.at(-1) || '';
328
+ const suffixPatterns = new Set(['.vsix', '.map', '.min.js', '.min.css']);
329
+ return this.ignorePatterns.some((rawPattern) => {
330
+ const pattern = rawPattern.toLowerCase();
331
+ if (suffixPatterns.has(pattern)) {
332
+ return fileName.endsWith(pattern);
333
+ }
334
+ return segments.includes(pattern);
335
+ });
313
336
  }
314
337
  }
@@ -0,0 +1,11 @@
1
+ import type { Command } from 'commander';
2
+ export type CommandFamily = 'Vigthoria Repo Management' | 'Agent Mode' | 'System & Configuration';
3
+ export interface CommandCatalogEntry {
4
+ path: string;
5
+ description: string;
6
+ family: CommandFamily;
7
+ command: Command;
8
+ }
9
+ export declare function buildCommandCatalog(program: Command): CommandCatalogEntry[];
10
+ export declare function renderDynamicHelp(program: Command): string;
11
+ export declare function selectInteractiveCommand(program: Command): Promise<string[] | null>;
@@ -0,0 +1,97 @@
1
+ import inquirer from 'inquirer';
2
+ const AGENT_COMMANDS = new Set([
3
+ 'agent', 'chat', 'chat-resume', 'operator', 'legion', 'workflow',
4
+ 'history', 'replay', 'fork', 'cancel',
5
+ ]);
6
+ function familyFor(commandPath) {
7
+ const root = commandPath.split(' ')[0];
8
+ if (root === 'repo')
9
+ return 'Vigthoria Repo Management';
10
+ if (AGENT_COMMANDS.has(root))
11
+ return 'Agent Mode';
12
+ return 'System & Configuration';
13
+ }
14
+ export function buildCommandCatalog(program) {
15
+ const entries = [];
16
+ const visit = (command, parents) => {
17
+ const commandPath = [...parents, command.name()].join(' ');
18
+ entries.push({
19
+ path: commandPath,
20
+ description: command.description() || '',
21
+ family: familyFor(commandPath),
22
+ command,
23
+ });
24
+ for (const child of command.commands)
25
+ visit(child, [...parents, command.name()]);
26
+ };
27
+ for (const command of program.commands)
28
+ visit(command, []);
29
+ return entries.sort((left, right) => left.family.localeCompare(right.family) || left.path.localeCompare(right.path));
30
+ }
31
+ export function renderDynamicHelp(program) {
32
+ const catalog = buildCommandCatalog(program);
33
+ const lines = ['Commands:'];
34
+ const families = [
35
+ 'Vigthoria Repo Management',
36
+ 'Agent Mode',
37
+ 'System & Configuration',
38
+ ];
39
+ for (const family of families) {
40
+ lines.push('', `${family}:`);
41
+ for (const entry of catalog.filter((candidate) => candidate.family === family)) {
42
+ const aliases = entry.command.aliases();
43
+ const displayPath = aliases.length
44
+ ? `${entry.path} (${aliases.join(', ')})`
45
+ : entry.path;
46
+ lines.push(` ${displayPath.padEnd(30)} ${entry.description}`);
47
+ }
48
+ }
49
+ return lines.join('\n');
50
+ }
51
+ async function collectRequiredArguments(entry) {
52
+ const values = [];
53
+ for (const argument of entry.command.registeredArguments) {
54
+ if (!argument.required)
55
+ continue;
56
+ const answer = await inquirer.prompt([{
57
+ type: 'input',
58
+ name: 'value',
59
+ message: `${argument.name()}:`,
60
+ validate: (value) => value.trim().length > 0 || `${argument.name()} is required`,
61
+ }]);
62
+ values.push(String(answer.value).trim());
63
+ }
64
+ return values;
65
+ }
66
+ export async function selectInteractiveCommand(program) {
67
+ const catalog = buildCommandCatalog(program);
68
+ const familyAnswer = await inquirer.prompt([{
69
+ type: 'list',
70
+ name: 'family',
71
+ message: 'Select a command family:',
72
+ choices: [
73
+ 'Vigthoria Repo Management',
74
+ 'Agent Mode',
75
+ 'System & Configuration',
76
+ new inquirer.Separator(),
77
+ { name: 'Exit', value: null },
78
+ ],
79
+ }]);
80
+ if (!familyAnswer.family)
81
+ return null;
82
+ const entries = catalog.filter((entry) => entry.family === familyAnswer.family && entry.command.commands.length === 0);
83
+ const commandAnswer = await inquirer.prompt([{
84
+ type: 'list',
85
+ name: 'path',
86
+ message: `${familyAnswer.family}:`,
87
+ pageSize: 20,
88
+ choices: entries.map((entry) => ({
89
+ name: `${entry.path.padEnd(24)} ${entry.description}`,
90
+ value: entry.path,
91
+ })),
92
+ }]);
93
+ const selected = entries.find((entry) => entry.path === commandAnswer.path);
94
+ if (!selected)
95
+ return null;
96
+ return [...selected.path.split(' '), ...await collectRequiredArguments(selected)];
97
+ }
@@ -14,7 +14,13 @@ export function emitDeckEvent(event) {
14
14
  ...event,
15
15
  ts: event.ts ?? Date.now(),
16
16
  };
17
- process.stdout.write(`${DECK_PREFIX}${JSON.stringify(payload)}\n`);
17
+ // Force the marker onto its own fresh line. Callers may have just updated an
18
+ // in-place spinner (e.g. ora) which redraws via "\r" and never emits a
19
+ // trailing "\n", so without this leading newline the marker can land stuck
20
+ // mid-line after visible text (e.g. "Executing plan...@vigthoria-deck:{...}")
21
+ // and the Workbench's line-start prefix check won't strip it, leaking raw
22
+ // JSON into the terminal.
23
+ process.stdout.write(`\n${DECK_PREFIX}${JSON.stringify(payload)}\n`);
18
24
  }
19
25
  export function parseDeckLine(line) {
20
26
  const trimmed = line.trim();
@@ -9,6 +9,8 @@ export type IntentContext = {
9
9
  export declare function stripExecutionShaping(prompt: string): string;
10
10
  export declare function hasBuildVerb(prompt: string): boolean;
11
11
  export declare function hasArtifactNoun(prompt: string): boolean;
12
+ export type GameEngineHint = 'babylon' | 'three' | 'canvas-2d';
13
+ export declare function inferGameEngine(prompt: string): GameEngineHint;
12
14
  export declare function hasGameIntent(prompt: string): boolean;
13
15
  /** True when the CLI already expanded a /continue-style follow-up prompt. */
14
16
  export declare function isBuiltContinuePrompt(prompt: string): boolean;
@@ -52,4 +54,6 @@ export declare function buildExecutionHints(agentTaskType: string, prompt: strin
52
54
  classify_from: string;
53
55
  workflow_type: 'analysis_only' | 'full';
54
56
  fast_path?: string;
57
+ game_engine?: GameEngineHint;
58
+ render_transport?: 'client-state';
55
59
  };
@@ -7,7 +7,7 @@ const READONLY_SHAPING_BLOCK = /(?:^|\n\n)(?:Read-only analysis mode is active\.
7
7
  const BUILD_VERBS = /\b(build|create|make|implement|complete|fix|repair|edit|modify|write|generate|add|finish|scaffold|develop|update|change|refactor|want|need|require|looking for|let's|let us|we need|we should|erstelle|erstellen|schreib|schreibe|bearbeite)\b/i;
8
8
  const ARTIFACT_NOUNS = /\b(file|files|project|game|spiel|app|website|html5|frontend|component|feature|code|workspace|repo|clone|replica|remake|page|site|screen|button|popup|modal|alert|canvas|sprite|level|levels|enemy|enemies|character|npc|world|scene|script|module|api|database|config|landing|dashboard|platformer|arcade|playable|collectible|scoreboard|leaderboard|pitfall|pacman|rogue|html|css|javascript|typescript)\b/i;
9
9
  /** Canvas/playable games only — bare "html5" or "website" must NOT match. */
10
- const GAME_INTENT = /\b(game|spiel|playable|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
10
+ const GAME_INTENT = /\b(game|spiel|playable|babylon(?:\.js|js)?|vge|webgl|3d\s+(?:game|runner|prototype|world|experience)|html5\s+(?:game|canvas)|html5\s+game|canvas\s+game|game\s+canvas|arcade|platformer|side[- ]?scroller|pitfall|pac[- ]?man|tetris|snake|breakout|pong|roguelike|metroidvania|tower\s+defense|clone\s+of\s+(?:a\s+)?(?:game|pitfall|pac|mario|zelda|arcade)|gameplay|collectible|boss\s+fight|level\s+design|sprite\s+sheet|wild\s+(?:boar|pig|forest)|forest\s+pig)\b/i;
11
11
  const WEB_PAGE_INTENT = /\b(website|web\s*site|webpage|web\s*page|landing\s+page|home\s*page|index\.html|html\s+page|static\s+page|single\s+page|popup|alert|modal|hello\s+world)\b/i;
12
12
  const CLONE_BUILD = /\bclone\s+of\b|\b(build|create|make)\s+(?:a|an|the|us|me)?\s*(?:clone|replica|remake|port)\b/i;
13
13
  const READ_ONLY_INTENT = /\b(analy[sz]e|analyse|analysis|audit|review|inspect|proof|understand|summari[sz]e|scan|read[\s-]?only|where we left|what is|how does|show me|tell me|identify\s+gaps?|gap\s+analysis|production\s+blockers?)\b/i;
@@ -34,6 +34,16 @@ export function hasArtifactNoun(prompt) {
34
34
  }
35
35
  /** Game clones require a game target — generic "clone of X" must not imply game-build. */
36
36
  const GAME_CLONE_TARGET = /\bclone\s+of\s+(?:a\s+)?(?:the\s+)?(?:game|pitfall|pac[- ]?man|mario|zelda|tetris|snake|arcade|spiel)\b/i;
37
+ export function inferGameEngine(prompt) {
38
+ const text = stripExecutionShaping(prompt);
39
+ if (/\bthree(?:\.js|js)?\b/i.test(text))
40
+ return 'three';
41
+ if (/\bbabylon(?:\.js|js)?\b|\bvge\b|vigthoria gaming engine/i.test(text))
42
+ return 'babylon';
43
+ if (/\b3d\s+(?:game|runner|prototype|world|experience)\b|\bwebgl\b/i.test(text))
44
+ return 'babylon';
45
+ return 'canvas-2d';
46
+ }
37
47
  export function hasGameIntent(prompt) {
38
48
  const text = stripExecutionShaping(prompt);
39
49
  if (GAME_INTENT.test(text)) {
@@ -370,6 +380,11 @@ export function buildExecutionHints(agentTaskType, prompt, options = {}) {
370
380
  classify_from: String(prompt || ''),
371
381
  workflow_type: workflowType,
372
382
  };
383
+ if (kind === 'game-build' || hasGameIntent(prompt)) {
384
+ hints.game_engine = inferGameEngine(prompt);
385
+ if (hints.game_engine === 'babylon')
386
+ hints.render_transport = 'client-state';
387
+ }
373
388
  if (options.fastPath) {
374
389
  hints.fast_path = options.fastPath;
375
390
  }
@@ -2104,6 +2104,24 @@ export class AgenticTools {
2104
2104
  result = await response.json();
2105
2105
  if (response.ok && result.success)
2106
2106
  break;
2107
+ if (result.code === 'REPOSITORY_SECURITY_REVIEW') {
2108
+ return {
2109
+ success: false,
2110
+ error: result.error || 'Repository security review required',
2111
+ metadata: {
2112
+ code: result.code,
2113
+ review: {
2114
+ reviewId: result.reviewId,
2115
+ status: result.status,
2116
+ riskScore: result.riskScore,
2117
+ findings: result.findings || [],
2118
+ actionItems: result.actionItems || [],
2119
+ retryCommand: result.retryCommand,
2120
+ },
2121
+ },
2122
+ suggestion: `Inspect with "vigthoria repo review ${result.reviewId}" and retry corrected content with "${result.retryCommand || `vigthoria repo push --retry ${result.reviewId}`}".`,
2123
+ };
2124
+ }
2107
2125
  lastError = result.error || result.message || `Status ${response.status}`;
2108
2126
  result = null;
2109
2127
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.12.3",
3
+ "version": "1.13.6",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -60,10 +60,12 @@
60
60
  "test:package:hygiene": "npm run build && node scripts/verify-package-hygiene.js",
61
61
  "test:v3:platforms": "npm run build && node scripts/test-v3-platforms-e2e.js",
62
62
  "test:repo:live": "npm run build && node scripts/test-live-repo-e2e.js",
63
+ "test:repo:auth-fallback": "node scripts/test-community-repo-auth-fallback.mjs",
64
+ "test:repo:payload-fallback": "node scripts/test-repo-payload-fallback.mjs",
63
65
  "test:game:live": "npm run build && node scripts/test-live-game-push.js",
64
66
  "proof:agent": "npm run build && node scripts/proof-agent-mode.js",
65
67
  "test:fresh-install": "node scripts/test-fresh-install.js",
66
- "prepublishOnly": "npm run build && npm run validate:no-go && node scripts/verify-package-hygiene.js && node scripts/test-fresh-install.js",
68
+ "prepublishOnly": "npm run build && npm run validate:no-go && npm run test:repo:auth-fallback && npm run test:repo:payload-fallback && node scripts/verify-package-hygiene.js && node scripts/test-fresh-install.js",
67
69
  "precheck:services": "node scripts/precheck-local-services.js",
68
70
  "test:model:governance": "npm run build && node scripts/test-model-governance-no-agent.js",
69
71
  "validate:no-go": "bash scripts/release/validate-no-go-gates.sh",
@@ -73,7 +75,8 @@
73
75
  "test:session:project-match": "npm run build && node scripts/test-session-project-match.mjs",
74
76
  "test:v3-stream-mutation": "npm run build && node scripts/test-v3-stream-mutation.js",
75
77
  "test:context:budget": "npm run build && node scripts/test-context-budget.js",
76
- "test:pitfall:context": "npm run build && node scripts/test-pitfall-context-smoke.js"
78
+ "test:pitfall:context": "npm run build && node scripts/test-pitfall-context-smoke.js",
79
+ "test:game:command": "node scripts/test-game-command.mjs"
77
80
  },
78
81
  "keywords": [
79
82
  "ai",