vigthoria-cli 1.6.39 → 1.6.41

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.
@@ -55,8 +55,9 @@ export declare class ChatCommand {
55
55
  private isBrowserTaskPrompt;
56
56
  /**
57
57
  * Returns true when a prompt can be answered directly without the full
58
- * BMAD operator workflow. Matches analysis/lookup questions.
59
- * Infrastructure action verbs are already filtered at the routing level.
58
+ * BMAD operator workflow. Matches analysis/lookup questions as well as
59
+ * short echo/smoke-test prompts that don't require code generation,
60
+ * file scanning, or infrastructure changes.
60
61
  */
61
62
  private isOperatorDirectAnswerable;
62
63
  private inferAgentTaskType;
@@ -92,6 +93,13 @@ export declare class ChatCommand {
92
93
  private formatWorkflowTargetResult;
93
94
  private runWorkflowTargetPrompt;
94
95
  private runOperatorTurn;
96
+ /**
97
+ * Fast-path for operator prompts that don't need the full BMAD workflow.
98
+ * Uses the regular chat API with a strong operator system message so
99
+ * the user gets an instant, grounded answer instead of waiting for the
100
+ * BMAD orchestrator to scan the workspace.
101
+ */
102
+ private runOperatorDirectAnswer;
95
103
  private runSimplePrompt;
96
104
  private runAgentTurn;
97
105
  private runLocalAgentLoop;
@@ -82,8 +82,8 @@ class ChatCommand {
82
82
  return;
83
83
  }
84
84
  if (enabledMode === 'operator') {
85
- if (this.currentModel === 'agent' || !this.currentModel) {
86
- this.currentModel = 'code-8b';
85
+ if (this.currentModel === 'agent' || this.currentModel === 'code-8b' || !this.currentModel) {
86
+ this.currentModel = 'code';
87
87
  }
88
88
  return;
89
89
  }
@@ -108,10 +108,12 @@ class ChatCommand {
108
108
  resolveInitialModel(options) {
109
109
  const requestedModel = String(options.model || '').trim();
110
110
  if (requestedModel) {
111
- return requestedModel;
111
+ return options.operator === true && requestedModel === 'code-8b'
112
+ ? 'code'
113
+ : requestedModel;
112
114
  }
113
115
  if (options.operator === true) {
114
- return 'code-8b';
116
+ return 'code';
115
117
  }
116
118
  if (options.agent === true) {
117
119
  return 'agent';
@@ -208,11 +210,17 @@ class ChatCommand {
208
210
  }
209
211
  /**
210
212
  * Returns true when a prompt can be answered directly without the full
211
- * BMAD operator workflow. Matches analysis/lookup questions.
212
- * Infrastructure action verbs are already filtered at the routing level.
213
+ * BMAD operator workflow. Matches analysis/lookup questions as well as
214
+ * short echo/smoke-test prompts that don't require code generation,
215
+ * file scanning, or infrastructure changes.
213
216
  */
214
217
  isOperatorDirectAnswerable(prompt) {
215
- return this.isAnalysisLookupPrompt(prompt.trim());
218
+ const trimmed = prompt.trim();
219
+ // Short prompts (under 200 chars) that do not contain action verbs
220
+ // targeting infrastructure are almost always direct-answerable.
221
+ const isShortNonAction = trimmed.length < 200
222
+ && !/\b(deploy|provision|scale|migrate|rollback|restart|install|configure|create|build|scaffold|generate|set up|tear down|spin up|initialize|bootstrap)\b/i.test(trimmed);
223
+ return this.isAnalysisLookupPrompt(trimmed) || isShortNonAction;
216
224
  }
217
225
  inferAgentTaskType(prompt) {
218
226
  if (this.isDiagnosticPrompt(prompt))
@@ -228,7 +236,7 @@ class ChatCommand {
228
236
  instructions.push('Platform: Windows. Use list_dir, glob, read_file, and the grep tool for searching.', 'The grep tool handles Windows automatically — do not use bash to call grep, findstr, or Select-String manually.', 'Do not use bash for Unix commands (cat, head, tail, awk, sed, wc).', 'Use read_file to inspect file contents instead of shell commands.', 'All file paths use forward slashes internally.');
229
237
  }
230
238
  if (this.isDiagnosticPrompt(prompt)) {
231
- instructions.push('Diagnostic mode is active.', 'Treat this as a debugging task, not a generic code review or feature request.', 'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.', 'If log files exist, inspect them before proposing fixes.', 'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.', 'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.', 'Prefer grep plus read_file around the exact references involved in the failure.', 'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.', 'Do not suggest speculative fixes when the current evidence contradicts them.');
239
+ instructions.push('Diagnostic mode is active.', 'Treat this as a debugging task, not a generic code review or feature request.', 'Start with concrete evidence: logs, runtime errors, config, launch files, and exact symbol references.', 'If log files exist, inspect them before proposing fixes.', 'Do not claim a file, definition, asset, or symbol is missing until you verify that with tools.', 'If a prior diagnosis mentioned a missing symbol or YAML entry, re-check the actual files before repeating it.', 'Prefer grep plus read_file around the exact references involved in the failure.', 'Separate your reasoning into: Evidence, Confirmed Cause, and Remaining Hypotheses.', 'Do not suggest speculative fixes when the current evidence contradicts them.', 'CRITICAL GROUNDING RULE: Every key name, variable name, symbol, or identifier you mention in your final answer MUST appear verbatim in the tool output you received. If a key/symbol does NOT appear in tool output, you MUST NOT mention it as involved in any conflict or issue.', 'CROSS-FILE ATTRIBUTION: When reporting conflicts between two files, a key/symbol is conflicting ONLY if it appears in BOTH files. Read each file carefully and list only the exact keys that appear in the relevant handler/function of EACH file. Do not assume that because one file handles a key, the other file does too.', 'When reporting conflicts between files, cite the exact file name, line number, and the exact string/key from the tool output. Do not paraphrase or substitute key names.', 'Before concluding, re-check: (1) does every key/symbol in my answer actually appear in the evidence I gathered? (2) for each claimed conflict, did I verify the key appears in BOTH files? If not, correct your answer.');
232
240
  }
233
241
  if (this.isBrowserTaskPrompt(prompt)) {
234
242
  instructions.push('Browser-debug mode is active.', 'Prefer concrete browser evidence such as console errors, network failures, DOM state, and websocket behavior.', 'If a DevTools Bridge is available, use it as the primary browser observability path.');
@@ -673,13 +681,6 @@ class ChatCommand {
673
681
  this.logger.error(this.operatorAccessMessage());
674
682
  return;
675
683
  }
676
- // Smart routing: infrastructure action verbs always need the BMAD
677
- // workflow. Everything else can be answered with a direct chat.
678
- const isInfraAction = /(deploy|provision|scale|replicas|rollback|roll back|migrate|tear.?down|restart|stop\s+\w+|start\s+\w+|configure|set.?up|upgrade|pipeline)/i.test(prompt.trim());
679
- if (!isInfraAction && (this.isSimpleDirectPrompt(prompt) || this.isOperatorDirectAnswerable(prompt))) {
680
- await this.runSimplePrompt(prompt);
681
- return;
682
- }
683
684
  await this.runOperatorTurn(prompt);
684
685
  return;
685
686
  }
@@ -788,37 +789,20 @@ class ChatCommand {
788
789
  this.logger.error(this.operatorAccessMessage());
789
790
  return;
790
791
  }
792
+ // ── Fast-path: direct-answerable prompts skip the full BMAD workflow ──
793
+ // The BMAD orchestrator scans the entire workspace (can be 1000+ files)
794
+ // even for trivial prompts like "Reply with exactly: OK". Route these
795
+ // through the regular chat API with a strong operator system message
796
+ // so the user gets an instant, grounded answer.
797
+ if (this.isOperatorDirectAnswerable(prompt)) {
798
+ await this.runOperatorDirectAnswer(prompt);
799
+ return;
800
+ }
791
801
  (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'operator', model: this.currentModel });
792
802
  const runtimeContext = await this.getPromptRuntimeContext(prompt);
793
803
  const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Thinking like an operator...', spinner: 'clock' }).start();
794
- const isLookup = this.isAnalysisLookupPrompt(prompt);
795
- const workflowType = this.isDiagnosticPrompt(prompt) || isLookup
796
- ? 'analysis_only'
797
- : 'full_autonomy';
798
- // For lightweight lookup tasks, prepend a grounding constraint so
799
- // the operator returns a concise answer rather than a verbose report.
800
- let executionPrompt = this.buildExecutionPrompt(prompt);
801
- if (isLookup) {
802
- // Inject workspace file listing into the prompt so the operator
803
- // can answer "which file defines X" without a full agent loop.
804
- let fileListing = '';
805
- try {
806
- const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
807
- if (snapshot && snapshot.paths.length > 0) {
808
- fileListing = '\n\nWorkspace file listing:\n' + snapshot.paths.slice(0, 80).join('\n');
809
- }
810
- }
811
- catch { /* ignore */ }
812
- executionPrompt = [
813
- 'INSTRUCTION: This is a lightweight lookup task. Return ONLY the concise answer the user asked for.',
814
- 'Do NOT produce a certification report, audit, or analysis blob.',
815
- 'Do NOT return files_analyzed counts or summary statistics.',
816
- 'Just answer the question directly.',
817
- fileListing,
818
- '',
819
- executionPrompt,
820
- ].join('\n');
821
- }
804
+ const workflowType = 'full';
805
+ const executionPrompt = this.buildExecutionPrompt(prompt);
822
806
  try {
823
807
  const response = await this.api.runOperatorWorkflow(executionPrompt, {
824
808
  workspacePath: this.currentProjectPath,
@@ -838,14 +822,12 @@ class ChatCommand {
838
822
  spinner.stop();
839
823
  }
840
824
  // Guard: if the operator workflow returned a useless policy
841
- // acknowledgement instead of a real answer, fall through to
842
- // simple prompt with operator grounding.
825
+ // acknowledgement instead of a real grounded answer, fail closed.
843
826
  const responseText = (response.content || '').trim();
844
827
  const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(responseText)
845
828
  || (responseText.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(responseText));
846
829
  if (isPolicyAck) {
847
- await this.runSimplePrompt(prompt);
848
- return;
830
+ throw new api_js_1.CLIError('Operator workflow returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
849
831
  }
850
832
  if (this.jsonOutput) {
851
833
  console.log(JSON.stringify({
@@ -891,6 +873,83 @@ class ChatCommand {
891
873
  }
892
874
  }
893
875
  }
876
+ /**
877
+ * Fast-path for operator prompts that don't need the full BMAD workflow.
878
+ * Uses the regular chat API with a strong operator system message so
879
+ * the user gets an instant, grounded answer instead of waiting for the
880
+ * BMAD orchestrator to scan the workspace.
881
+ */
882
+ async runOperatorDirectAnswer(prompt) {
883
+ (0, bridge_client_js_1.getBridgeClient)()?.emitPrompt({ prompt, mode: 'operator-direct', model: this.currentModel });
884
+ const spinner = this.jsonOutput ? null : (0, logger_js_1.createSpinner)({ text: 'Operator (direct)...', spinner: 'clock' }).start();
885
+ try {
886
+ let operatorGrounding = [
887
+ 'You are Vigthoria Operator, a DevOps and infrastructure analysis assistant.',
888
+ `Workspace: ${this.currentProjectPath}`,
889
+ 'Answer the user\'s question directly with a concrete, actionable answer.',
890
+ 'Do NOT acknowledge instructions. Do NOT say "provide your next instruction".',
891
+ 'If asked to produce exact output, produce exactly that output with no preamble.',
892
+ ].join('\n');
893
+ try {
894
+ const snapshot = this.api.getAgentWorkspaceSnapshot(this.currentProjectPath);
895
+ if (snapshot && snapshot.paths.length > 0) {
896
+ const listing = snapshot.paths.slice(0, 80).join('\n');
897
+ operatorGrounding += `\n\nProject file listing (${snapshot.fileCount} files):\n${listing}`;
898
+ if (snapshot.fileCount > 80) {
899
+ operatorGrounding += `\n... and ${snapshot.fileCount - 80} more files.`;
900
+ }
901
+ }
902
+ }
903
+ catch { /* ignore */ }
904
+ const chatMessages = [
905
+ { role: 'system', content: operatorGrounding },
906
+ ...this.getMessagesForModel().filter(m => m.role !== 'system'),
907
+ { role: 'user', content: prompt },
908
+ ];
909
+ const response = await this.api.chat(chatMessages, this.currentModel);
910
+ if (spinner)
911
+ spinner.stop();
912
+ const content = (response.message || '').trim();
913
+ const isPolicyAck = /^(i will follow|i understand|i('ll| will) adhere|understood[.,!]|sure[.,!]|provide your|waiting for|i('ll| will) proceed)/i.test(content)
914
+ || (content.length < 200 && /follow the instructions|next instruction|ready to assist/i.test(content));
915
+ if (isPolicyAck || !content) {
916
+ throw new api_js_1.CLIError('Operator returned a non-actionable acknowledgement instead of a grounded result.', 'model_backend');
917
+ }
918
+ if (this.jsonOutput) {
919
+ console.log(JSON.stringify({
920
+ success: true,
921
+ mode: 'operator',
922
+ content,
923
+ metadata: { source: 'operator-direct', mode: 'operator' },
924
+ }, null, 2));
925
+ }
926
+ else {
927
+ console.log(content);
928
+ }
929
+ this.messages.push({ role: 'assistant', content });
930
+ this.saveSession();
931
+ }
932
+ catch (error) {
933
+ if (spinner)
934
+ spinner.stop();
935
+ const cliErr = error instanceof api_js_1.CLIError ? error : (0, api_js_1.classifyError)(error);
936
+ const errorMsg = (0, api_js_1.formatCLIError)(cliErr);
937
+ if (this.jsonOutput) {
938
+ process.exitCode = 1;
939
+ console.log(JSON.stringify({
940
+ success: false,
941
+ mode: 'operator',
942
+ content: '',
943
+ error: errorMsg,
944
+ errorCategory: cliErr.category,
945
+ }, null, 2));
946
+ }
947
+ else {
948
+ this.logger.error('Operator direct answer failed');
949
+ this.logger.error(errorMsg);
950
+ }
951
+ }
952
+ }
894
953
  async runSimplePrompt(prompt) {
895
954
  this.lastActionableUserInput = prompt;
896
955
  // For direct --prompt mode with simple prompts, use a minimal system
@@ -1661,6 +1720,8 @@ class ChatCommand {
1661
1720
  'When diagnosing failures, prefer evidence-backed conclusions over broad repo scans.',
1662
1721
  'If the user corrects you, treat that as evidence that your previous assumption was wrong and re-check with tools.',
1663
1722
  'Do not stop after a partial answer when more direct inspection is possible.',
1723
+ 'EVIDENCE-GROUNDING RULE: Every identifier, key name, variable, or symbol you cite in your final answer MUST appear verbatim in tool output you received during this session. Never invent or guess identifiers. If you read a file and saw "KeyA" and "KeyS" on specific lines, cite those exact strings — do not substitute "KeyR" or any other key that was not in the output.',
1724
+ 'CROSS-FILE RULE: When comparing two or more files, only claim a key/symbol is conflicting or shared if it literally appears in the relevant function/handler of EACH file you read. Finding a key in File A does not mean it also exists in File B — verify each file independently. Use grep to confirm a key exists in a file before claiming that file handles it.',
1664
1725
  'When you need a tool, emit only one or more tool wrappers in this exact format:',
1665
1726
  '<tool_call>',
1666
1727
  '{"tool":"read_file","args":{"path":"relative/path.ext"}}',
@@ -1844,6 +1905,8 @@ class ChatCommand {
1844
1905
  'Do not declare success until the exact user question has been answered with tool-backed evidence.',
1845
1906
  'If a user is asking which file is correct or most recent, keep inspecting until you can justify the answer from actual results.',
1846
1907
  diagnosticMode ? 'Because this is a debugging task, prefer logs, runtime evidence, and exact symbol references over generic fixes.' : 'Keep working from concrete tool results.',
1908
+ 'GROUNDING CHECK: Before writing your final answer, verify that every identifier, key name, or symbol you mention actually appeared in tool output above. Do not invent identifiers that were not in the evidence. If you saw "KeyA" and "KeyS" in the file contents, use those exact names — never substitute a different key. For cross-file comparisons, confirm each claimed conflict key appears in the handler/function of BOTH files — not just one.',
1909
+ 'VERIFICATION PROTOCOL for cross-file comparisons: If your answer claims a key/identifier appears in multiple files, you MUST first use grep to search for that exact identifier in each file BEFORE including it in your answer. Only include keys/identifiers that grep confirms exist in each file. Example: to verify "KeyW" is in InputManager.js, use grep for "KeyW" in that file. If grep finds no match, do NOT claim that file handles "KeyW".',
1847
1910
  'If the request is already satisfied, return a concise completion summary and no tool calls.',
1848
1911
  'If more work is required, continue with only the next minimal tool calls needed to finish it.',
1849
1912
  'Do not ask follow-up questions or drift into unrelated tasks.',
package/dist/index.js CHANGED
@@ -284,7 +284,7 @@ async function main() {
284
284
  .command('operator')
285
285
  .alias('op')
286
286
  .description('Start BMAD operator mode for infrastructure and system workflows')
287
- .option('-m, --model <model>', 'Select operator model (code-8b, code, agent)')
287
+ .option('-m, --model <model>', 'Select operator model (code, agent, cloud)')
288
288
  .option('-p, --project <path>', 'Set project context path')
289
289
  .option('--new-project [name]', 'Create or use a managed local workspace folder when no --project path is given')
290
290
  .option('--prompt <text>', 'Run a single operator prompt directly and exit')
package/dist/utils/api.js CHANGED
@@ -2958,7 +2958,7 @@ document.addEventListener('DOMContentLoaded', () => {
2958
2958
  workspace: { path: workspacePath },
2959
2959
  workspace_path: workspacePath,
2960
2960
  workspace_summary: workspaceSummary,
2961
- model: this.resolveModelId(executionContext.model || 'code-8b'),
2961
+ model: this.resolveModelId(executionContext.model || 'code'),
2962
2962
  history: executionContext.history || [],
2963
2963
  executionSurface: executionContext.executionSurface || 'cli',
2964
2964
  clientSurface: executionContext.clientSurface || 'cli',
@@ -2969,7 +2969,7 @@ document.addEventListener('DOMContentLoaded', () => {
2969
2969
  rawPrompt: executionContext.rawPrompt || null,
2970
2970
  requestStartedAt: executionContext.requestStartedAt,
2971
2971
  },
2972
- workflow_type: executionContext.workflowType || 'analysis_only',
2972
+ workflow_type: executionContext.workflowType || 'full',
2973
2973
  options: {
2974
2974
  stream: true,
2975
2975
  save_to_vigflow: executionContext.savePlanToVigFlow === true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vigthoria-cli",
3
- "version": "1.6.39",
3
+ "version": "1.6.41",
4
4
  "description": "Vigthoria Coder CLI - AI-powered terminal coding assistant",
5
5
  "main": "dist/index.js",
6
6
  "files": [