@hivehub/rulebook 1.2.0
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/LICENSE +191 -0
- package/README.md +539 -0
- package/dist/agents/claude-code.d.ts +69 -0
- package/dist/agents/claude-code.d.ts.map +1 -0
- package/dist/agents/claude-code.js +180 -0
- package/dist/agents/claude-code.js.map +1 -0
- package/dist/agents/cursor-agent.d.ts +184 -0
- package/dist/agents/cursor-agent.d.ts.map +1 -0
- package/dist/agents/cursor-agent.js +299 -0
- package/dist/agents/cursor-agent.js.map +1 -0
- package/dist/agents/gemini-cli.d.ts +69 -0
- package/dist/agents/gemini-cli.d.ts.map +1 -0
- package/dist/agents/gemini-cli.js +180 -0
- package/dist/agents/gemini-cli.js.map +1 -0
- package/dist/cli/commands.d.ts +57 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +1370 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/docs-prompts.d.ts +3 -0
- package/dist/cli/docs-prompts.d.ts.map +1 -0
- package/dist/cli/docs-prompts.js +45 -0
- package/dist/cli/docs-prompts.js.map +1 -0
- package/dist/cli/prompts.d.ts +6 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +376 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/core/agent-manager.d.ts +89 -0
- package/dist/core/agent-manager.d.ts.map +1 -0
- package/dist/core/agent-manager.js +546 -0
- package/dist/core/agent-manager.js.map +1 -0
- package/dist/core/auto-fixer.d.ts +14 -0
- package/dist/core/auto-fixer.d.ts.map +1 -0
- package/dist/core/auto-fixer.js +207 -0
- package/dist/core/auto-fixer.js.map +1 -0
- package/dist/core/changelog-generator.d.ts +44 -0
- package/dist/core/changelog-generator.d.ts.map +1 -0
- package/dist/core/changelog-generator.js +222 -0
- package/dist/core/changelog-generator.js.map +1 -0
- package/dist/core/cli-bridge.d.ts +113 -0
- package/dist/core/cli-bridge.d.ts.map +1 -0
- package/dist/core/cli-bridge.js +1094 -0
- package/dist/core/cli-bridge.js.map +1 -0
- package/dist/core/config-manager.d.ts +65 -0
- package/dist/core/config-manager.d.ts.map +1 -0
- package/dist/core/config-manager.js +266 -0
- package/dist/core/config-manager.js.map +1 -0
- package/dist/core/coverage-checker.d.ts +14 -0
- package/dist/core/coverage-checker.d.ts.map +1 -0
- package/dist/core/coverage-checker.js +176 -0
- package/dist/core/coverage-checker.js.map +1 -0
- package/dist/core/custom-templates.d.ts +27 -0
- package/dist/core/custom-templates.d.ts.map +1 -0
- package/dist/core/custom-templates.js +122 -0
- package/dist/core/custom-templates.js.map +1 -0
- package/dist/core/dependency-checker.d.ts +21 -0
- package/dist/core/dependency-checker.d.ts.map +1 -0
- package/dist/core/dependency-checker.js +247 -0
- package/dist/core/dependency-checker.js.map +1 -0
- package/dist/core/detector.d.ts +3 -0
- package/dist/core/detector.d.ts.map +1 -0
- package/dist/core/detector.js +1443 -0
- package/dist/core/detector.js.map +1 -0
- package/dist/core/docs-generator.d.ts +9 -0
- package/dist/core/docs-generator.d.ts.map +1 -0
- package/dist/core/docs-generator.js +531 -0
- package/dist/core/docs-generator.js.map +1 -0
- package/dist/core/generator.d.ts +16 -0
- package/dist/core/generator.d.ts.map +1 -0
- package/dist/core/generator.js +561 -0
- package/dist/core/generator.js.map +1 -0
- package/dist/core/gitignore-generator.d.ts +13 -0
- package/dist/core/gitignore-generator.d.ts.map +1 -0
- package/dist/core/gitignore-generator.js +307 -0
- package/dist/core/gitignore-generator.js.map +1 -0
- package/dist/core/health-scorer.d.ts +22 -0
- package/dist/core/health-scorer.d.ts.map +1 -0
- package/dist/core/health-scorer.js +395 -0
- package/dist/core/health-scorer.js.map +1 -0
- package/dist/core/logger.d.ts +116 -0
- package/dist/core/logger.d.ts.map +1 -0
- package/dist/core/logger.js +289 -0
- package/dist/core/logger.js.map +1 -0
- package/dist/core/merger.d.ts +6 -0
- package/dist/core/merger.d.ts.map +1 -0
- package/dist/core/merger.js +131 -0
- package/dist/core/merger.js.map +1 -0
- package/dist/core/migrator.d.ts +19 -0
- package/dist/core/migrator.d.ts.map +1 -0
- package/dist/core/migrator.js +102 -0
- package/dist/core/migrator.js.map +1 -0
- package/dist/core/minimal-scaffolder.d.ts +8 -0
- package/dist/core/minimal-scaffolder.d.ts.map +1 -0
- package/dist/core/minimal-scaffolder.js +51 -0
- package/dist/core/minimal-scaffolder.js.map +1 -0
- package/dist/core/modern-console-new.d.ts +81 -0
- package/dist/core/modern-console-new.d.ts.map +1 -0
- package/dist/core/modern-console-new.js +340 -0
- package/dist/core/modern-console-new.js.map +1 -0
- package/dist/core/modern-console.d.ts +99 -0
- package/dist/core/modern-console.d.ts.map +1 -0
- package/dist/core/modern-console.js +568 -0
- package/dist/core/modern-console.js.map +1 -0
- package/dist/core/openspec-manager.d.ts +133 -0
- package/dist/core/openspec-manager.d.ts.map +1 -0
- package/dist/core/openspec-manager.js +605 -0
- package/dist/core/openspec-manager.js.map +1 -0
- package/dist/core/openspec-migrator.d.ts +27 -0
- package/dist/core/openspec-migrator.d.ts.map +1 -0
- package/dist/core/openspec-migrator.js +255 -0
- package/dist/core/openspec-migrator.js.map +1 -0
- package/dist/core/task-manager.d.ts +65 -0
- package/dist/core/task-manager.d.ts.map +1 -0
- package/dist/core/task-manager.js +318 -0
- package/dist/core/task-manager.js.map +1 -0
- package/dist/core/test-task-manager.d.ts +49 -0
- package/dist/core/test-task-manager.d.ts.map +1 -0
- package/dist/core/test-task-manager.js +121 -0
- package/dist/core/test-task-manager.js.map +1 -0
- package/dist/core/validator.d.ts +21 -0
- package/dist/core/validator.d.ts.map +1 -0
- package/dist/core/validator.js +177 -0
- package/dist/core/validator.js.map +1 -0
- package/dist/core/version-bumper.d.ts +19 -0
- package/dist/core/version-bumper.d.ts.map +1 -0
- package/dist/core/version-bumper.js +180 -0
- package/dist/core/version-bumper.js.map +1 -0
- package/dist/core/watcher.d.ts +9 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +22 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/core/workflow-generator.d.ts +10 -0
- package/dist/core/workflow-generator.d.ts.map +1 -0
- package/dist/core/workflow-generator.js +279 -0
- package/dist/core/workflow-generator.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +159 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/handlers/archive-task.d.ts +17 -0
- package/dist/mcp/handlers/archive-task.d.ts.map +1 -0
- package/dist/mcp/handlers/archive-task.js +36 -0
- package/dist/mcp/handlers/archive-task.js.map +1 -0
- package/dist/mcp/handlers/create-task.d.ts +17 -0
- package/dist/mcp/handlers/create-task.d.ts.map +1 -0
- package/dist/mcp/handlers/create-task.js +56 -0
- package/dist/mcp/handlers/create-task.js.map +1 -0
- package/dist/mcp/handlers/list-tasks.d.ts +22 -0
- package/dist/mcp/handlers/list-tasks.d.ts.map +1 -0
- package/dist/mcp/handlers/list-tasks.js +42 -0
- package/dist/mcp/handlers/list-tasks.js.map +1 -0
- package/dist/mcp/handlers/show-task.d.ts +25 -0
- package/dist/mcp/handlers/show-task.d.ts.map +1 -0
- package/dist/mcp/handlers/show-task.js +43 -0
- package/dist/mcp/handlers/show-task.js.map +1 -0
- package/dist/mcp/handlers/update-task.d.ts +17 -0
- package/dist/mcp/handlers/update-task.d.ts.map +1 -0
- package/dist/mcp/handlers/update-task.js +35 -0
- package/dist/mcp/handlers/update-task.js.map +1 -0
- package/dist/mcp/handlers/validate-task.d.ts +15 -0
- package/dist/mcp/handlers/validate-task.d.ts.map +1 -0
- package/dist/mcp/handlers/validate-task.js +27 -0
- package/dist/mcp/handlers/validate-task.js.map +1 -0
- package/dist/mcp/rulebook-config.d.ts +22 -0
- package/dist/mcp/rulebook-config.d.ts.map +1 -0
- package/dist/mcp/rulebook-config.js +65 -0
- package/dist/mcp/rulebook-config.js.map +1 -0
- package/dist/mcp/rulebook-server.d.ts +4 -0
- package/dist/mcp/rulebook-server.d.ts.map +1 -0
- package/dist/mcp/rulebook-server.js +246 -0
- package/dist/mcp/rulebook-server.js.map +1 -0
- package/dist/types.d.ts +190 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/file-system.d.ts +9 -0
- package/dist/utils/file-system.d.ts.map +1 -0
- package/dist/utils/file-system.js +51 -0
- package/dist/utils/file-system.js.map +1 -0
- package/dist/utils/git-hooks.d.ts +8 -0
- package/dist/utils/git-hooks.d.ts.map +1 -0
- package/dist/utils/git-hooks.js +440 -0
- package/dist/utils/git-hooks.js.map +1 -0
- package/dist/utils/rulesignore.d.ts +9 -0
- package/dist/utils/rulesignore.d.ts.map +1 -0
- package/dist/utils/rulesignore.js +42 -0
- package/dist/utils/rulesignore.js.map +1 -0
- package/package.json +106 -0
- package/templates/cli/AIDER.md +49 -0
- package/templates/cli/AMAZON_Q.md +25 -0
- package/templates/cli/AUGGIE.md +32 -0
- package/templates/cli/CLAUDE.md +32 -0
- package/templates/cli/CLAUDE_CODE.md +35 -0
- package/templates/cli/CLINE.md +32 -0
- package/templates/cli/CODEBUDDY.md +20 -0
- package/templates/cli/CODEIUM.md +20 -0
- package/templates/cli/CODEX.md +21 -0
- package/templates/cli/CONTINUE.md +34 -0
- package/templates/cli/CURSOR_CLI.md +28 -0
- package/templates/cli/FACTORY.md +18 -0
- package/templates/cli/GEMINI.md +35 -0
- package/templates/cli/KILOCODE.md +18 -0
- package/templates/cli/OPENCODE.md +18 -0
- package/templates/cli/_GENERIC_TEMPLATE.md +29 -0
- package/templates/commands/rulebook-task-apply.md +67 -0
- package/templates/commands/rulebook-task-archive.md +70 -0
- package/templates/commands/rulebook-task-create.md +93 -0
- package/templates/commands/rulebook-task-list.md +42 -0
- package/templates/commands/rulebook-task-show.md +52 -0
- package/templates/commands/rulebook-task-validate.md +53 -0
- package/templates/core/AGENT_AUTOMATION.md +184 -0
- package/templates/core/DAG.md +304 -0
- package/templates/core/DOCUMENTATION_RULES.md +37 -0
- package/templates/core/QUALITY_ENFORCEMENT.md +68 -0
- package/templates/core/RULEBOOK.md +1874 -0
- package/templates/frameworks/ANGULAR.md +36 -0
- package/templates/frameworks/DJANGO.md +83 -0
- package/templates/frameworks/ELECTRON.md +147 -0
- package/templates/frameworks/FLASK.md +38 -0
- package/templates/frameworks/FLUTTER.md +55 -0
- package/templates/frameworks/JQUERY.md +32 -0
- package/templates/frameworks/LARAVEL.md +38 -0
- package/templates/frameworks/NESTJS.md +43 -0
- package/templates/frameworks/NEXTJS.md +127 -0
- package/templates/frameworks/NUXT.md +40 -0
- package/templates/frameworks/RAILS.md +66 -0
- package/templates/frameworks/REACT.md +38 -0
- package/templates/frameworks/REACT_NATIVE.md +47 -0
- package/templates/frameworks/SPRING.md +39 -0
- package/templates/frameworks/SYMFONY.md +36 -0
- package/templates/frameworks/VUE.md +36 -0
- package/templates/frameworks/ZEND.md +35 -0
- package/templates/git/CI_CD_PATTERNS.md +661 -0
- package/templates/git/GITHUB_ACTIONS.md +728 -0
- package/templates/git/GITLAB_CI.md +730 -0
- package/templates/git/GIT_WORKFLOW.md +1157 -0
- package/templates/git/SECRETS_MANAGEMENT.md +585 -0
- package/templates/hooks/COMMIT_MSG.md +530 -0
- package/templates/hooks/POST_CHECKOUT.md +546 -0
- package/templates/hooks/PREPARE_COMMIT_MSG.md +619 -0
- package/templates/hooks/PRE_COMMIT.md +414 -0
- package/templates/hooks/PRE_PUSH.md +601 -0
- package/templates/hooks/csharp-pre-commit.sh +23 -0
- package/templates/hooks/csharp-pre-push.sh +23 -0
- package/templates/hooks/dart-pre-commit.sh +30 -0
- package/templates/hooks/dart-pre-push.sh +25 -0
- package/templates/hooks/elixir-pre-commit.sh +32 -0
- package/templates/hooks/elixir-pre-push.sh +31 -0
- package/templates/hooks/erlang-pre-commit.sh +30 -0
- package/templates/hooks/erlang-pre-push.sh +37 -0
- package/templates/hooks/go-pre-commit.sh +40 -0
- package/templates/hooks/go-pre-push.sh +31 -0
- package/templates/hooks/haskell-pre-commit.sh +41 -0
- package/templates/hooks/haskell-pre-push.sh +37 -0
- package/templates/hooks/java-pre-commit.sh +34 -0
- package/templates/hooks/java-pre-push.sh +24 -0
- package/templates/hooks/kotlin-pre-commit.sh +32 -0
- package/templates/hooks/kotlin-pre-push.sh +16 -0
- package/templates/hooks/php-pre-commit.sh +36 -0
- package/templates/hooks/php-pre-push.sh +26 -0
- package/templates/hooks/python-pre-commit.sh +51 -0
- package/templates/hooks/python-pre-push.sh +25 -0
- package/templates/hooks/ruby-pre-commit.sh +33 -0
- package/templates/hooks/ruby-pre-push.sh +32 -0
- package/templates/hooks/rust-pre-commit.sh +30 -0
- package/templates/hooks/rust-pre-push.sh +30 -0
- package/templates/hooks/scala-pre-commit.sh +32 -0
- package/templates/hooks/scala-pre-push.sh +24 -0
- package/templates/hooks/swift-pre-commit.sh +25 -0
- package/templates/hooks/swift-pre-push.sh +23 -0
- package/templates/hooks/typescript-pre-commit.sh +37 -0
- package/templates/hooks/typescript-pre-push.sh +36 -0
- package/templates/ides/COPILOT.md +37 -0
- package/templates/ides/CURSOR.md +43 -0
- package/templates/ides/JETBRAINS_AI.md +35 -0
- package/templates/ides/REPLIT.md +36 -0
- package/templates/ides/TABNINE.md +29 -0
- package/templates/ides/VSCODE.md +40 -0
- package/templates/ides/WINDSURF.md +36 -0
- package/templates/ides/ZED.md +32 -0
- package/templates/languages/ADA.md +58 -0
- package/templates/languages/C.md +333 -0
- package/templates/languages/CPP.md +743 -0
- package/templates/languages/CSHARP.md +417 -0
- package/templates/languages/DART.md +332 -0
- package/templates/languages/ELIXIR.md +454 -0
- package/templates/languages/ERLANG.md +361 -0
- package/templates/languages/GO.md +645 -0
- package/templates/languages/HASKELL.md +177 -0
- package/templates/languages/JAVA.md +607 -0
- package/templates/languages/JAVASCRIPT.md +631 -0
- package/templates/languages/JULIA.md +97 -0
- package/templates/languages/KOTLIN.md +511 -0
- package/templates/languages/LISP.md +100 -0
- package/templates/languages/LUA.md +74 -0
- package/templates/languages/OBJECTIVEC.md +90 -0
- package/templates/languages/PHP.md +416 -0
- package/templates/languages/PYTHON.md +682 -0
- package/templates/languages/R.md +350 -0
- package/templates/languages/RUBY.md +421 -0
- package/templates/languages/RUST.md +477 -0
- package/templates/languages/SAS.md +73 -0
- package/templates/languages/SCALA.md +348 -0
- package/templates/languages/SOLIDITY.md +580 -0
- package/templates/languages/SQL.md +137 -0
- package/templates/languages/SWIFT.md +466 -0
- package/templates/languages/TYPESCRIPT.md +591 -0
- package/templates/languages/ZIG.md +265 -0
- package/templates/modules/ATLASSIAN.md +255 -0
- package/templates/modules/CONTEXT7.md +54 -0
- package/templates/modules/FIGMA.md +267 -0
- package/templates/modules/GITHUB_MCP.md +64 -0
- package/templates/modules/GRAFANA.md +328 -0
- package/templates/modules/NOTION.md +247 -0
- package/templates/modules/PLAYWRIGHT.md +90 -0
- package/templates/modules/RULEBOOK_MCP.md +156 -0
- package/templates/modules/SERENA.md +337 -0
- package/templates/modules/SUPABASE.md +223 -0
- package/templates/modules/SYNAP.md +69 -0
- package/templates/modules/VECTORIZER.md +63 -0
- package/templates/services/AZURE_BLOB.md +184 -0
- package/templates/services/CASSANDRA.md +239 -0
- package/templates/services/DYNAMODB.md +308 -0
- package/templates/services/ELASTICSEARCH.md +347 -0
- package/templates/services/GCS.md +178 -0
- package/templates/services/INFLUXDB.md +265 -0
- package/templates/services/KAFKA.md +341 -0
- package/templates/services/MARIADB.md +183 -0
- package/templates/services/MEMCACHED.md +242 -0
- package/templates/services/MINIO.md +201 -0
- package/templates/services/MONGODB.md +268 -0
- package/templates/services/MYSQL.md +358 -0
- package/templates/services/NEO4J.md +247 -0
- package/templates/services/ORACLE.md +290 -0
- package/templates/services/POSTGRESQL.md +326 -0
- package/templates/services/RABBITMQ.md +286 -0
- package/templates/services/REDIS.md +292 -0
- package/templates/services/S3.md +298 -0
- package/templates/services/SQLITE.md +294 -0
- package/templates/services/SQLSERVER.md +294 -0
- package/templates/workflows/codespell.yml +31 -0
- package/templates/workflows/cpp-lint.yml +47 -0
- package/templates/workflows/cpp-publish.yml +119 -0
- package/templates/workflows/cpp-test.yml +77 -0
- package/templates/workflows/dotnet-lint.yml +29 -0
- package/templates/workflows/dotnet-publish.yml +40 -0
- package/templates/workflows/dotnet-test.yml +41 -0
- package/templates/workflows/elixir-lint.yml +45 -0
- package/templates/workflows/elixir-publish.yml +49 -0
- package/templates/workflows/elixir-test.yml +54 -0
- package/templates/workflows/erlang-lint.yml +47 -0
- package/templates/workflows/erlang-test.yml +62 -0
- package/templates/workflows/go-lint.yml +39 -0
- package/templates/workflows/go-publish.yml +95 -0
- package/templates/workflows/go-test.yml +59 -0
- package/templates/workflows/java-lint.yml +60 -0
- package/templates/workflows/java-publish.yml +120 -0
- package/templates/workflows/java-test.yml +85 -0
- package/templates/workflows/kotlin-lint.yml +34 -0
- package/templates/workflows/kotlin-publish.yml +56 -0
- package/templates/workflows/kotlin-test.yml +48 -0
- package/templates/workflows/php-lint.yml +39 -0
- package/templates/workflows/php-publish.yml +50 -0
- package/templates/workflows/php-test.yml +54 -0
- package/templates/workflows/python-lint.yml +47 -0
- package/templates/workflows/python-publish.yml +91 -0
- package/templates/workflows/python-test.yml +59 -0
- package/templates/workflows/rust-lint.yml +54 -0
- package/templates/workflows/rust-publish.yml +66 -0
- package/templates/workflows/rust-test.yml +75 -0
- package/templates/workflows/solidity-lint.yml +41 -0
- package/templates/workflows/solidity-test.yml +47 -0
- package/templates/workflows/swift-lint.yml +32 -0
- package/templates/workflows/swift-publish.yml +58 -0
- package/templates/workflows/swift-test.yml +44 -0
- package/templates/workflows/typescript-lint.yml +61 -0
- package/templates/workflows/typescript-publish.yml +60 -0
- package/templates/workflows/typescript-test.yml +73 -0
- package/templates/workflows/zig-lint.yml +27 -0
- package/templates/workflows/zig-test.yml +40 -0
|
@@ -0,0 +1,1094 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { CursorAgentStreamParser, parseStreamLine } from '../agents/cursor-agent.js';
|
|
3
|
+
import { ClaudeCodeStreamParser, parseClaudeCodeLine } from '../agents/claude-code.js';
|
|
4
|
+
import { GeminiStreamParser, parseGeminiLine } from '../agents/gemini-cli.js';
|
|
5
|
+
import { appendFile, mkdir } from 'fs/promises';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { existsSync } from 'fs';
|
|
8
|
+
export class CLIBridge {
|
|
9
|
+
logger;
|
|
10
|
+
config;
|
|
11
|
+
activeProcesses = new Map();
|
|
12
|
+
onLog;
|
|
13
|
+
debugLogFile;
|
|
14
|
+
debugLogInitialized = false;
|
|
15
|
+
constructor(logger, config) {
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Initialize debug logging (called once after singleton creation)
|
|
21
|
+
*/
|
|
22
|
+
initializeDebugLog() {
|
|
23
|
+
if (this.debugLogInitialized) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.debugLogInitialized = true;
|
|
27
|
+
// Skip debug logging during tests
|
|
28
|
+
if (process.env.NODE_ENV === 'test' || process.env.VITEST) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Create logs directory if it doesn't exist
|
|
32
|
+
const logsDir = join(process.cwd(), 'logs');
|
|
33
|
+
if (!existsSync(logsDir)) {
|
|
34
|
+
mkdir(logsDir, { recursive: true }).catch(() => {
|
|
35
|
+
// Ignore mkdir errors
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
// Create debug log file with timestamp in logs directory
|
|
39
|
+
const logTimestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
40
|
+
this.debugLogFile = join(logsDir, `debug-agent-${logTimestamp}.log`);
|
|
41
|
+
this.debugLog(`=== DEBUG LOG START ===`);
|
|
42
|
+
this.debugLog(`Session started at: ${new Date().toISOString()}`);
|
|
43
|
+
this.debugLog(`Working directory: ${process.cwd()}`);
|
|
44
|
+
this.debugLog(`Node version: ${process.version}`);
|
|
45
|
+
this.debugLog(`Platform: ${process.platform}`);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Write to debug log file
|
|
49
|
+
*/
|
|
50
|
+
async debugLog(message) {
|
|
51
|
+
if (!this.debugLogFile)
|
|
52
|
+
return;
|
|
53
|
+
try {
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
await appendFile(this.debugLogFile, `[${timestamp}] ${message}\n`);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Ignore errors in debug logging
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Set callback for logging (used by watcher UI)
|
|
63
|
+
*/
|
|
64
|
+
setLogCallback(callback) {
|
|
65
|
+
this.onLog = callback;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Detect available CLI tools
|
|
69
|
+
*/
|
|
70
|
+
async detectCLITools() {
|
|
71
|
+
this.initializeDebugLog(); // Initialize debug log on first real use
|
|
72
|
+
const tools = [
|
|
73
|
+
{ name: 'cursor-agent', command: 'cursor-agent', available: false },
|
|
74
|
+
// { name: 'claude-code', command: 'claude', available: false }, // Temporarily disabled for v0.10.0
|
|
75
|
+
// { name: 'gemini-cli', command: 'gemini', available: false }, // Temporarily disabled for v0.10.0
|
|
76
|
+
];
|
|
77
|
+
for (const tool of tools) {
|
|
78
|
+
try {
|
|
79
|
+
const proc = spawn(tool.command, ['--version']);
|
|
80
|
+
let stdout = '';
|
|
81
|
+
if (proc.stdout) {
|
|
82
|
+
proc.stdout.setEncoding('utf8');
|
|
83
|
+
proc.stdout.on('data', (data) => {
|
|
84
|
+
stdout += data;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const exitCode = await new Promise((resolve, reject) => {
|
|
88
|
+
const timeout = setTimeout(() => {
|
|
89
|
+
proc.kill('SIGTERM');
|
|
90
|
+
reject(new Error('Timeout'));
|
|
91
|
+
}, 5000);
|
|
92
|
+
proc.on('exit', (code) => {
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
resolve(code ?? 1);
|
|
95
|
+
});
|
|
96
|
+
proc.on('error', (error) => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
reject(error);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
if (exitCode === 0) {
|
|
102
|
+
tool.available = true;
|
|
103
|
+
tool.version = stdout.trim();
|
|
104
|
+
this.logger.info(`Detected CLI tool: ${tool.name}`, { version: tool.version });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
this.logger.debug(`CLI tool not available: ${tool.name}`, { error: String(error) });
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return tools.filter((tool) => tool.available);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Send command to CLI tool
|
|
115
|
+
*/
|
|
116
|
+
async sendCommandToCLI(toolName, command, options = {}) {
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
// Check for deprecated tools
|
|
119
|
+
const deprecatedTools = ['cursor-cli', 'claude-cli', 'gemini-cli-legacy'];
|
|
120
|
+
if (deprecatedTools.includes(toolName)) {
|
|
121
|
+
const duration = Date.now() - startTime;
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
output: '',
|
|
125
|
+
error: `Tool '${toolName}' is deprecated and not supported. Please use 'cursor-agent', 'claude-code', or 'gemini-cli' instead.`,
|
|
126
|
+
duration,
|
|
127
|
+
exitCode: 1,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// Check for supported tools
|
|
131
|
+
const supportedTools = ['cursor-agent']; // Temporarily only cursor-agent for v0.10.0
|
|
132
|
+
if (!supportedTools.includes(toolName)) {
|
|
133
|
+
const duration = Date.now() - startTime;
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
output: '',
|
|
137
|
+
error: `Tool '${toolName}' is not supported. Supported tools are: ${supportedTools.join(', ')}`,
|
|
138
|
+
duration,
|
|
139
|
+
exitCode: 1,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
// cursor-agent needs more time to connect to remote server and process
|
|
143
|
+
// Set to 30 minutes for long-running tasks
|
|
144
|
+
const defaultTimeout = toolName === 'cursor-agent' ? 1800000 : 30000; // 30 minutes for cursor-agent
|
|
145
|
+
const timeout = options.timeout || this.config.timeouts?.cliResponse || defaultTimeout;
|
|
146
|
+
this.logger.cliCommand(command, toolName);
|
|
147
|
+
try {
|
|
148
|
+
if (toolName === 'cursor-agent') {
|
|
149
|
+
// cursor-agent expects: cursor-agent -p --force --approve-mcps --output-format stream-json --stream-partial-output "PROMPT"
|
|
150
|
+
// Using -p (print mode) for non-interactive use with all tools enabled
|
|
151
|
+
// --force: Allow commands unless explicitly denied
|
|
152
|
+
// --approve-mcps: Allow all MCP servers without asking
|
|
153
|
+
// --output-format stream-json: Stream output in JSON format
|
|
154
|
+
// --stream-partial-output: Stream partial output as individual text deltas
|
|
155
|
+
const args = [
|
|
156
|
+
'-p',
|
|
157
|
+
'--force',
|
|
158
|
+
'--approve-mcps',
|
|
159
|
+
'--output-format',
|
|
160
|
+
'stream-json',
|
|
161
|
+
'--stream-partial-output',
|
|
162
|
+
command,
|
|
163
|
+
];
|
|
164
|
+
await this.debugLog(`\n=== CURSOR-AGENT COMMAND START ===`);
|
|
165
|
+
await this.debugLog(`Command: ${command}`);
|
|
166
|
+
await this.debugLog(`Full command line: cursor-agent ${args.join(' ')}`);
|
|
167
|
+
await this.debugLog(`Timestamp: ${new Date().toISOString()}`);
|
|
168
|
+
await this.debugLog(`===================================\n`);
|
|
169
|
+
const proc = spawn(toolName, args, {
|
|
170
|
+
cwd: options.workingDirectory,
|
|
171
|
+
env: {
|
|
172
|
+
...process.env,
|
|
173
|
+
...options.env,
|
|
174
|
+
// Disable Node.js buffering for immediate output
|
|
175
|
+
NODE_NO_READLINE: '1',
|
|
176
|
+
},
|
|
177
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
178
|
+
shell: false,
|
|
179
|
+
});
|
|
180
|
+
if (this.onLog) {
|
|
181
|
+
this.onLog('info', '🔗 Connecting to cursor-agent...');
|
|
182
|
+
this.onLog('info', ' (This may take 30-60 seconds to connect to remote server)');
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
console.log('🔗 Connecting to cursor-agent...');
|
|
186
|
+
console.log(' (This may take 30-60 seconds to connect to remote server)');
|
|
187
|
+
}
|
|
188
|
+
// Progress indicator (silent in watcher mode)
|
|
189
|
+
let dots = 0;
|
|
190
|
+
const progressInterval = setInterval(() => {
|
|
191
|
+
dots = (dots + 1) % 4;
|
|
192
|
+
if (!this.onLog) {
|
|
193
|
+
process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
|
|
194
|
+
}
|
|
195
|
+
}, 500);
|
|
196
|
+
// Stream output in real-time with parser
|
|
197
|
+
let hasOutput = false;
|
|
198
|
+
let stdout = '';
|
|
199
|
+
let stderr = '';
|
|
200
|
+
const parser = new CursorAgentStreamParser();
|
|
201
|
+
// Promise to resolve when parser completes
|
|
202
|
+
let resolveCompletion;
|
|
203
|
+
const completionPromise = new Promise((resolve) => {
|
|
204
|
+
resolveCompletion = resolve;
|
|
205
|
+
});
|
|
206
|
+
// Set completion callback
|
|
207
|
+
parser.onComplete(() => {
|
|
208
|
+
// Silent completion - no debug logs
|
|
209
|
+
if (resolveCompletion) {
|
|
210
|
+
resolveCompletion();
|
|
211
|
+
}
|
|
212
|
+
// Give a small delay for any remaining output, then kill
|
|
213
|
+
setTimeout(() => {
|
|
214
|
+
if (!proc.killed) {
|
|
215
|
+
proc.kill('SIGTERM');
|
|
216
|
+
}
|
|
217
|
+
}, 500);
|
|
218
|
+
});
|
|
219
|
+
// Set event callback to forward to onLog or console
|
|
220
|
+
parser.onEvent((type, message) => {
|
|
221
|
+
if (this.onLog) {
|
|
222
|
+
// Watcher mode - send to UI
|
|
223
|
+
const logType = type === 'completion' ? 'success' : 'info';
|
|
224
|
+
this.onLog(logType, message);
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// CLI mode - print to console with colors
|
|
228
|
+
if (type === 'tool') {
|
|
229
|
+
// Tool calls in cyan
|
|
230
|
+
console.log(` \x1b[36m${message}\x1b[0m`);
|
|
231
|
+
}
|
|
232
|
+
else if (type === 'text') {
|
|
233
|
+
// Text generation in gray
|
|
234
|
+
console.log(` \x1b[90m${message}\x1b[0m`);
|
|
235
|
+
}
|
|
236
|
+
else if (type === 'completion') {
|
|
237
|
+
// Completion in green
|
|
238
|
+
console.log(`\n\x1b[32m${message}\x1b[0m`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
if (proc.stdout) {
|
|
243
|
+
proc.stdout.setEncoding('utf8');
|
|
244
|
+
let buffer = '';
|
|
245
|
+
proc.stdout.on('data', (data) => {
|
|
246
|
+
if (!hasOutput) {
|
|
247
|
+
clearInterval(progressInterval);
|
|
248
|
+
if (this.onLog) {
|
|
249
|
+
this.onLog('success', '✅ Received first response from cursor-agent!');
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log('\n✅ Received first response from cursor-agent!');
|
|
253
|
+
console.log(''); // Empty line for better formatting
|
|
254
|
+
}
|
|
255
|
+
hasOutput = true;
|
|
256
|
+
this.debugLog('First response received from cursor-agent');
|
|
257
|
+
}
|
|
258
|
+
stdout += data;
|
|
259
|
+
buffer += data;
|
|
260
|
+
// Log raw output to debug file
|
|
261
|
+
this.debugLog(`STDOUT RAW: ${data}`);
|
|
262
|
+
// Process complete lines
|
|
263
|
+
const lines = buffer.split('\n');
|
|
264
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
265
|
+
for (const line of lines) {
|
|
266
|
+
if (line.trim()) {
|
|
267
|
+
// Parse and process each JSON event
|
|
268
|
+
this.debugLog(`STDOUT LINE: ${line}`);
|
|
269
|
+
const event = parseStreamLine(line);
|
|
270
|
+
if (event) {
|
|
271
|
+
this.debugLog(`EVENT PARSED: type=${event.type}`);
|
|
272
|
+
parser.processEvent(event);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
this.debugLog(`EVENT PARSE FAILED: ${line.substring(0, 100)}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
if (proc.stderr) {
|
|
282
|
+
proc.stderr.setEncoding('utf8');
|
|
283
|
+
proc.stderr.on('data', (data) => {
|
|
284
|
+
if (!hasOutput) {
|
|
285
|
+
clearInterval(progressInterval);
|
|
286
|
+
hasOutput = true;
|
|
287
|
+
}
|
|
288
|
+
stderr += data;
|
|
289
|
+
// Only write to stderr in CLI mode, not watcher mode
|
|
290
|
+
if (!this.onLog) {
|
|
291
|
+
process.stderr.write('⚠️ ' + data);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
// Store active process
|
|
296
|
+
const processId = `${toolName}-${Date.now()}`;
|
|
297
|
+
this.activeProcesses.set(processId, proc);
|
|
298
|
+
// Wait for process to complete with timeout or completion
|
|
299
|
+
const result = await new Promise((resolve, reject) => {
|
|
300
|
+
const timeoutId = setTimeout(() => {
|
|
301
|
+
proc.kill('SIGTERM');
|
|
302
|
+
reject(new Error(`Command timed out after ${timeout} milliseconds`));
|
|
303
|
+
}, timeout);
|
|
304
|
+
// Resolve when parser detects completion
|
|
305
|
+
completionPromise.then(async () => {
|
|
306
|
+
await this.debugLog('Parser completion event received');
|
|
307
|
+
await this.debugLog(`Process PID: ${proc.pid}, killed: ${proc.killed}, exitCode: ${proc.exitCode}`);
|
|
308
|
+
clearInterval(progressInterval);
|
|
309
|
+
clearTimeout(timeoutId);
|
|
310
|
+
// FORCE KILL IMMEDIATELY after completion
|
|
311
|
+
if (!proc.killed && proc.exitCode === null) {
|
|
312
|
+
await this.debugLog(`Forcing process kill immediately after completion`);
|
|
313
|
+
try {
|
|
314
|
+
proc.kill('SIGTERM');
|
|
315
|
+
// Give 100ms for graceful shutdown, then SIGKILL
|
|
316
|
+
setTimeout(() => {
|
|
317
|
+
if (!proc.killed && proc.exitCode === null) {
|
|
318
|
+
proc.kill('SIGKILL');
|
|
319
|
+
}
|
|
320
|
+
}, 100);
|
|
321
|
+
}
|
|
322
|
+
catch (error) {
|
|
323
|
+
await this.debugLog(`Error killing process: ${error}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// Wait max 500ms for process to exit
|
|
327
|
+
const checkExit = setInterval(async () => {
|
|
328
|
+
if (proc.killed || proc.exitCode !== null) {
|
|
329
|
+
await this.debugLog(`Process exited with code: ${proc.exitCode}`);
|
|
330
|
+
clearInterval(checkExit);
|
|
331
|
+
resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
|
|
332
|
+
}
|
|
333
|
+
}, 50);
|
|
334
|
+
// Force resolve after 500ms
|
|
335
|
+
setTimeout(async () => {
|
|
336
|
+
clearInterval(checkExit);
|
|
337
|
+
if (!proc.killed) {
|
|
338
|
+
await this.debugLog(`Process STILL alive after 500ms, forcing SIGKILL`);
|
|
339
|
+
try {
|
|
340
|
+
proc.kill('SIGKILL');
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
await this.debugLog(`Error force killing: ${error}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
resolve({ exitCode: 0, stdout, stderr });
|
|
347
|
+
}, 500);
|
|
348
|
+
});
|
|
349
|
+
proc.on('exit', async (code, signal) => {
|
|
350
|
+
await this.debugLog(`Process 'exit' event: code=${code}, signal=${signal}`);
|
|
351
|
+
clearInterval(progressInterval);
|
|
352
|
+
clearTimeout(timeoutId);
|
|
353
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
354
|
+
});
|
|
355
|
+
proc.on('error', async (error) => {
|
|
356
|
+
await this.debugLog(`Process 'error' event: ${error.message}`);
|
|
357
|
+
clearInterval(progressInterval);
|
|
358
|
+
clearTimeout(timeoutId);
|
|
359
|
+
reject(error);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
const duration = Date.now() - startTime;
|
|
363
|
+
// Debug logging for cursor-agent completion
|
|
364
|
+
await this.debugLog(`\n=== CURSOR-AGENT COMMAND END ===`);
|
|
365
|
+
await this.debugLog(`Duration: ${duration}ms`);
|
|
366
|
+
await this.debugLog(`Exit code: ${result.exitCode}`);
|
|
367
|
+
await this.debugLog(`Total stdout length: ${stdout.length} chars`);
|
|
368
|
+
await this.debugLog(`Total stderr length: ${stderr.length} chars`);
|
|
369
|
+
await this.debugLog(`Parser completed: ${parser.isCompleted()}`);
|
|
370
|
+
await this.debugLog(`Active processes remaining: ${this.activeProcesses.size}`);
|
|
371
|
+
await this.debugLog(`=================================\n`);
|
|
372
|
+
// Remove from active processes
|
|
373
|
+
this.activeProcesses.delete(processId);
|
|
374
|
+
// Get parsed result from stream parser
|
|
375
|
+
const parsedResult = parser.getResult();
|
|
376
|
+
const response = {
|
|
377
|
+
success: result.exitCode === 0,
|
|
378
|
+
output: parsedResult.text || result.stdout, // Use parsed text if available
|
|
379
|
+
error: result.stderr,
|
|
380
|
+
duration,
|
|
381
|
+
exitCode: result.exitCode,
|
|
382
|
+
};
|
|
383
|
+
// Log summary
|
|
384
|
+
if (this.onLog) {
|
|
385
|
+
this.onLog('info', `Summary: ${parsedResult.text.length} chars, ${parsedResult.toolCalls.length} tools, ${Math.round(duration / 1000)}s`);
|
|
386
|
+
this.onLog('info', `Debug log: ${this.debugLogFile}`);
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
console.log('\n📊 Summary:');
|
|
390
|
+
console.log(` Text generated: ${parsedResult.text.length} chars`);
|
|
391
|
+
console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
|
|
392
|
+
console.log(` Duration: ${Math.round(duration / 1000)}s`);
|
|
393
|
+
}
|
|
394
|
+
this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
|
|
395
|
+
return response;
|
|
396
|
+
}
|
|
397
|
+
else if (toolName === 'claude-code') {
|
|
398
|
+
// claude-code expects: claude --headless "PROMPT"
|
|
399
|
+
const args = ['--headless', command];
|
|
400
|
+
const proc = spawn('claude', args, {
|
|
401
|
+
cwd: options.workingDirectory,
|
|
402
|
+
env: {
|
|
403
|
+
...process.env,
|
|
404
|
+
...options.env,
|
|
405
|
+
},
|
|
406
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
407
|
+
shell: false,
|
|
408
|
+
});
|
|
409
|
+
if (this.onLog) {
|
|
410
|
+
this.onLog('info', '🔗 Connecting to claude-code...');
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
console.log('🔗 Connecting to claude-code...');
|
|
414
|
+
}
|
|
415
|
+
// Progress indicator (silent in watcher mode)
|
|
416
|
+
let dots = 0;
|
|
417
|
+
const progressInterval = setInterval(() => {
|
|
418
|
+
dots = (dots + 1) % 4;
|
|
419
|
+
if (!this.onLog) {
|
|
420
|
+
process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
|
|
421
|
+
}
|
|
422
|
+
}, 500);
|
|
423
|
+
// Stream output in real-time with parser
|
|
424
|
+
let hasOutput = false;
|
|
425
|
+
let stdout = '';
|
|
426
|
+
let stderr = '';
|
|
427
|
+
const parser = new ClaudeCodeStreamParser();
|
|
428
|
+
// Promise to resolve when parser completes
|
|
429
|
+
let resolveCompletion;
|
|
430
|
+
const completionPromise = new Promise((resolve) => {
|
|
431
|
+
resolveCompletion = resolve;
|
|
432
|
+
});
|
|
433
|
+
// Set completion callback
|
|
434
|
+
parser.onComplete(() => {
|
|
435
|
+
console.log('\n✅ claude-code completed, terminating process...');
|
|
436
|
+
if (resolveCompletion) {
|
|
437
|
+
resolveCompletion();
|
|
438
|
+
}
|
|
439
|
+
// Give a small delay for any remaining output, then kill
|
|
440
|
+
setTimeout(() => {
|
|
441
|
+
if (!proc.killed) {
|
|
442
|
+
proc.kill('SIGTERM');
|
|
443
|
+
}
|
|
444
|
+
}, 500);
|
|
445
|
+
});
|
|
446
|
+
if (proc.stdout) {
|
|
447
|
+
proc.stdout.setEncoding('utf8');
|
|
448
|
+
let buffer = '';
|
|
449
|
+
proc.stdout.on('data', (data) => {
|
|
450
|
+
if (!hasOutput) {
|
|
451
|
+
clearInterval(progressInterval);
|
|
452
|
+
console.log('\n✅ Received first response from claude-code!');
|
|
453
|
+
console.log(''); // Empty line for better formatting
|
|
454
|
+
hasOutput = true;
|
|
455
|
+
}
|
|
456
|
+
stdout += data;
|
|
457
|
+
buffer += data;
|
|
458
|
+
// Process complete lines
|
|
459
|
+
const lines = buffer.split('\n');
|
|
460
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
461
|
+
for (const line of lines) {
|
|
462
|
+
if (line.trim()) {
|
|
463
|
+
// Parse and process each line
|
|
464
|
+
const event = parseClaudeCodeLine(line);
|
|
465
|
+
if (event) {
|
|
466
|
+
parser.processEvent(event);
|
|
467
|
+
// Check if parser completed
|
|
468
|
+
if (parser.isCompleted()) {
|
|
469
|
+
console.log('✅ Parser reports completed!');
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
if (proc.stderr) {
|
|
477
|
+
proc.stderr.setEncoding('utf8');
|
|
478
|
+
proc.stderr.on('data', (data) => {
|
|
479
|
+
if (!hasOutput) {
|
|
480
|
+
clearInterval(progressInterval);
|
|
481
|
+
hasOutput = true;
|
|
482
|
+
}
|
|
483
|
+
stderr += data;
|
|
484
|
+
// Only write to stderr in CLI mode, not watcher mode
|
|
485
|
+
if (!this.onLog) {
|
|
486
|
+
process.stderr.write('⚠️ ' + data);
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
// Store active process
|
|
491
|
+
const processId = `${toolName}-${Date.now()}`;
|
|
492
|
+
this.activeProcesses.set(processId, proc);
|
|
493
|
+
// Wait for process to complete with timeout or completion
|
|
494
|
+
const result = await new Promise((resolve, reject) => {
|
|
495
|
+
const timeoutId = setTimeout(() => {
|
|
496
|
+
proc.kill('SIGTERM');
|
|
497
|
+
reject(new Error(`Command timed out after ${timeout} milliseconds`));
|
|
498
|
+
}, timeout);
|
|
499
|
+
// Resolve when parser detects completion
|
|
500
|
+
completionPromise.then(() => {
|
|
501
|
+
console.log('✅ completionPromise resolved!');
|
|
502
|
+
clearInterval(progressInterval);
|
|
503
|
+
clearTimeout(timeoutId);
|
|
504
|
+
// Process might still be alive, wait a bit for graceful exit
|
|
505
|
+
const checkExit = setInterval(() => {
|
|
506
|
+
if (proc.killed || proc.exitCode !== null) {
|
|
507
|
+
clearInterval(checkExit);
|
|
508
|
+
resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
|
|
509
|
+
}
|
|
510
|
+
}, 100);
|
|
511
|
+
// Force resolve after 2 seconds if still hanging
|
|
512
|
+
setTimeout(() => {
|
|
513
|
+
clearInterval(checkExit);
|
|
514
|
+
if (!proc.killed) {
|
|
515
|
+
proc.kill('SIGKILL');
|
|
516
|
+
}
|
|
517
|
+
resolve({ exitCode: 0, stdout, stderr });
|
|
518
|
+
}, 2000);
|
|
519
|
+
});
|
|
520
|
+
proc.on('exit', (code, _signal) => {
|
|
521
|
+
clearInterval(progressInterval);
|
|
522
|
+
clearTimeout(timeoutId);
|
|
523
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
524
|
+
});
|
|
525
|
+
proc.on('error', (error) => {
|
|
526
|
+
clearInterval(progressInterval);
|
|
527
|
+
clearTimeout(timeoutId);
|
|
528
|
+
reject(error);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
const duration = Date.now() - startTime;
|
|
532
|
+
// Remove from active processes
|
|
533
|
+
this.activeProcesses.delete(processId);
|
|
534
|
+
// Get parsed result from stream parser
|
|
535
|
+
const parsedResult = parser.getResult();
|
|
536
|
+
const response = {
|
|
537
|
+
success: result.exitCode === 0,
|
|
538
|
+
output: parsedResult.text || result.stdout, // Use parsed text if available
|
|
539
|
+
error: result.stderr,
|
|
540
|
+
duration,
|
|
541
|
+
exitCode: result.exitCode,
|
|
542
|
+
};
|
|
543
|
+
// Log summary
|
|
544
|
+
console.log('\n📊 Summary:');
|
|
545
|
+
console.log(` Text generated: ${parsedResult.text.length} chars`);
|
|
546
|
+
console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
|
|
547
|
+
console.log(` Duration: ${Math.round(duration / 1000)}s`);
|
|
548
|
+
this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
|
|
549
|
+
return response;
|
|
550
|
+
}
|
|
551
|
+
else if (toolName === 'gemini-cli') {
|
|
552
|
+
// gemini-cli expects: gemini "PROMPT"
|
|
553
|
+
const args = [command];
|
|
554
|
+
const proc = spawn('gemini', args, {
|
|
555
|
+
cwd: options.workingDirectory,
|
|
556
|
+
env: {
|
|
557
|
+
...process.env,
|
|
558
|
+
...options.env,
|
|
559
|
+
},
|
|
560
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
561
|
+
shell: false,
|
|
562
|
+
});
|
|
563
|
+
if (this.onLog) {
|
|
564
|
+
this.onLog('info', '🔗 Connecting to gemini-cli...');
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
console.log('🔗 Connecting to gemini-cli...');
|
|
568
|
+
}
|
|
569
|
+
// Progress indicator (silent in watcher mode)
|
|
570
|
+
let dots = 0;
|
|
571
|
+
const progressInterval = setInterval(() => {
|
|
572
|
+
dots = (dots + 1) % 4;
|
|
573
|
+
if (!this.onLog) {
|
|
574
|
+
process.stdout.write('\r⏳ Waiting' + '.'.repeat(dots) + ' '.repeat(3 - dots));
|
|
575
|
+
}
|
|
576
|
+
}, 500);
|
|
577
|
+
// Stream output in real-time with parser
|
|
578
|
+
let hasOutput = false;
|
|
579
|
+
let stdout = '';
|
|
580
|
+
let stderr = '';
|
|
581
|
+
const parser = new GeminiStreamParser();
|
|
582
|
+
// Promise to resolve when parser completes
|
|
583
|
+
let resolveCompletion;
|
|
584
|
+
const completionPromise = new Promise((resolve) => {
|
|
585
|
+
resolveCompletion = resolve;
|
|
586
|
+
});
|
|
587
|
+
// Set completion callback
|
|
588
|
+
parser.onComplete(() => {
|
|
589
|
+
console.log('\n✅ gemini-cli completed, terminating process...');
|
|
590
|
+
if (resolveCompletion) {
|
|
591
|
+
resolveCompletion();
|
|
592
|
+
}
|
|
593
|
+
// Give a small delay for any remaining output, then kill
|
|
594
|
+
setTimeout(() => {
|
|
595
|
+
if (!proc.killed) {
|
|
596
|
+
proc.kill('SIGTERM');
|
|
597
|
+
}
|
|
598
|
+
}, 500);
|
|
599
|
+
});
|
|
600
|
+
if (proc.stdout) {
|
|
601
|
+
proc.stdout.setEncoding('utf8');
|
|
602
|
+
let buffer = '';
|
|
603
|
+
proc.stdout.on('data', (data) => {
|
|
604
|
+
if (!hasOutput) {
|
|
605
|
+
clearInterval(progressInterval);
|
|
606
|
+
console.log('\n✅ Received first response from gemini-cli!');
|
|
607
|
+
console.log(''); // Empty line for better formatting
|
|
608
|
+
hasOutput = true;
|
|
609
|
+
}
|
|
610
|
+
stdout += data;
|
|
611
|
+
buffer += data;
|
|
612
|
+
// Process complete lines
|
|
613
|
+
const lines = buffer.split('\n');
|
|
614
|
+
buffer = lines.pop() || ''; // Keep incomplete line in buffer
|
|
615
|
+
for (const line of lines) {
|
|
616
|
+
if (line.trim()) {
|
|
617
|
+
// Parse and process each line
|
|
618
|
+
const event = parseGeminiLine(line);
|
|
619
|
+
if (event) {
|
|
620
|
+
parser.processEvent(event);
|
|
621
|
+
// Check if parser completed
|
|
622
|
+
if (parser.isCompleted()) {
|
|
623
|
+
console.log('✅ Parser reports completed!');
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
if (proc.stderr) {
|
|
631
|
+
proc.stderr.setEncoding('utf8');
|
|
632
|
+
proc.stderr.on('data', (data) => {
|
|
633
|
+
if (!hasOutput) {
|
|
634
|
+
clearInterval(progressInterval);
|
|
635
|
+
hasOutput = true;
|
|
636
|
+
}
|
|
637
|
+
stderr += data;
|
|
638
|
+
// Only write to stderr in CLI mode, not watcher mode
|
|
639
|
+
if (!this.onLog) {
|
|
640
|
+
process.stderr.write('⚠️ ' + data);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
// Store active process
|
|
645
|
+
const processId = `${toolName}-${Date.now()}`;
|
|
646
|
+
this.activeProcesses.set(processId, proc);
|
|
647
|
+
// Wait for process to complete with timeout or completion
|
|
648
|
+
const result = await new Promise((resolve, reject) => {
|
|
649
|
+
const timeoutId = setTimeout(() => {
|
|
650
|
+
proc.kill('SIGTERM');
|
|
651
|
+
reject(new Error(`Command timed out after ${timeout} milliseconds`));
|
|
652
|
+
}, timeout);
|
|
653
|
+
// Resolve when parser detects completion
|
|
654
|
+
completionPromise.then(() => {
|
|
655
|
+
console.log('✅ completionPromise resolved!');
|
|
656
|
+
clearInterval(progressInterval);
|
|
657
|
+
clearTimeout(timeoutId);
|
|
658
|
+
// Process might still be alive, wait a bit for graceful exit
|
|
659
|
+
const checkExit = setInterval(() => {
|
|
660
|
+
if (proc.killed || proc.exitCode !== null) {
|
|
661
|
+
clearInterval(checkExit);
|
|
662
|
+
resolve({ exitCode: proc.exitCode ?? 0, stdout, stderr });
|
|
663
|
+
}
|
|
664
|
+
}, 100);
|
|
665
|
+
// Force resolve after 2 seconds if still hanging
|
|
666
|
+
setTimeout(() => {
|
|
667
|
+
clearInterval(checkExit);
|
|
668
|
+
if (!proc.killed) {
|
|
669
|
+
proc.kill('SIGKILL');
|
|
670
|
+
}
|
|
671
|
+
resolve({ exitCode: 0, stdout, stderr });
|
|
672
|
+
}, 2000);
|
|
673
|
+
});
|
|
674
|
+
proc.on('exit', (code, _signal) => {
|
|
675
|
+
clearInterval(progressInterval);
|
|
676
|
+
clearTimeout(timeoutId);
|
|
677
|
+
resolve({ exitCode: code ?? 0, stdout, stderr });
|
|
678
|
+
});
|
|
679
|
+
proc.on('error', (error) => {
|
|
680
|
+
clearInterval(progressInterval);
|
|
681
|
+
clearTimeout(timeoutId);
|
|
682
|
+
reject(error);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
const duration = Date.now() - startTime;
|
|
686
|
+
// Remove from active processes
|
|
687
|
+
this.activeProcesses.delete(processId);
|
|
688
|
+
// Get parsed result from stream parser
|
|
689
|
+
const parsedResult = parser.getResult();
|
|
690
|
+
const response = {
|
|
691
|
+
success: result.exitCode === 0,
|
|
692
|
+
output: parsedResult.text || result.stdout, // Use parsed text if available
|
|
693
|
+
error: result.stderr,
|
|
694
|
+
duration,
|
|
695
|
+
exitCode: result.exitCode,
|
|
696
|
+
};
|
|
697
|
+
// Log summary
|
|
698
|
+
console.log('\n📊 Summary:');
|
|
699
|
+
console.log(` Text generated: ${parsedResult.text.length} chars`);
|
|
700
|
+
console.log(` Tool calls: ${parsedResult.toolCalls.length}`);
|
|
701
|
+
console.log(` Duration: ${Math.round(duration / 1000)}s`);
|
|
702
|
+
this.logger.cliResponse(toolName, parsedResult.text || result.stdout, duration);
|
|
703
|
+
return response;
|
|
704
|
+
}
|
|
705
|
+
else {
|
|
706
|
+
// For other tools, keep using execa (need to import it)
|
|
707
|
+
throw new Error(`Tool ${toolName} not yet implemented with spawn`);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
const duration = Date.now() - startTime;
|
|
712
|
+
const response = {
|
|
713
|
+
success: false,
|
|
714
|
+
output: '',
|
|
715
|
+
error: error instanceof Error ? error.message : String(error),
|
|
716
|
+
duration,
|
|
717
|
+
exitCode: error.exitCode || 1,
|
|
718
|
+
};
|
|
719
|
+
this.logger.error(`CLI command failed: ${command}`, {
|
|
720
|
+
tool: toolName,
|
|
721
|
+
error: error instanceof Error ? error.message : String(error),
|
|
722
|
+
duration,
|
|
723
|
+
});
|
|
724
|
+
return response;
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
/**
|
|
728
|
+
* Wait for CLI completion with timeout handling
|
|
729
|
+
*/
|
|
730
|
+
async waitForCompletion(toolName, command, timeout) {
|
|
731
|
+
const response = await this.sendCommandToCLI(toolName, command, { timeout });
|
|
732
|
+
if (!response.success && response.error?.includes('timeout')) {
|
|
733
|
+
this.logger.warn(`CLI timeout detected for ${toolName}`, { command });
|
|
734
|
+
return await this.handleTimeout(toolName, command);
|
|
735
|
+
}
|
|
736
|
+
return response;
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Handle CLI timeout by sending continue command
|
|
740
|
+
*/
|
|
741
|
+
async handleTimeout(toolName, originalCommand) {
|
|
742
|
+
this.logger.info(`Handling timeout for ${toolName}`, { originalCommand });
|
|
743
|
+
// Send continue command
|
|
744
|
+
const continueResponse = await this.sendCommandToCLI(toolName, 'continue', {
|
|
745
|
+
timeout: this.config.timeouts.cliResponse,
|
|
746
|
+
});
|
|
747
|
+
if (continueResponse.success) {
|
|
748
|
+
this.logger.info(`Continue command successful for ${toolName}`);
|
|
749
|
+
return continueResponse;
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
this.logger.error(`Continue command failed for ${toolName}`, {
|
|
753
|
+
error: continueResponse.error,
|
|
754
|
+
});
|
|
755
|
+
return continueResponse;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Send task implementation command
|
|
760
|
+
*/
|
|
761
|
+
async sendTaskCommand(toolName, task) {
|
|
762
|
+
let command;
|
|
763
|
+
if (toolName === 'cursor-agent') {
|
|
764
|
+
command = `Implement task: ${task.title}. Description: ${task.description}`;
|
|
765
|
+
}
|
|
766
|
+
else if (toolName === 'claude-code') {
|
|
767
|
+
command = `Implement the following task: ${task.title}. Description: ${task.description}`;
|
|
768
|
+
}
|
|
769
|
+
else if (toolName === 'gemini-cli') {
|
|
770
|
+
command = `Please implement this task: ${task.title}. Description: ${task.description}`;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
command = `Implement task "${task.title}" from OpenSpec. Description: ${task.description}`;
|
|
774
|
+
}
|
|
775
|
+
// cursor-agent needs extra time for complex tasks (30 minutes)
|
|
776
|
+
const timeout = toolName === 'cursor-agent' ? 1800000 : undefined;
|
|
777
|
+
return await this.sendCommandToCLI(toolName, command, { timeout });
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Send continue implementation command
|
|
781
|
+
*/
|
|
782
|
+
async sendContinueCommand(toolName, iterations = 10) {
|
|
783
|
+
let command;
|
|
784
|
+
if (toolName === 'cursor-agent') {
|
|
785
|
+
command = `Continue implementation ${iterations} times`;
|
|
786
|
+
}
|
|
787
|
+
else if (toolName === 'claude-code') {
|
|
788
|
+
command = `Continue the implementation ${iterations} more times`;
|
|
789
|
+
}
|
|
790
|
+
else if (toolName === 'gemini-cli') {
|
|
791
|
+
command = `Please continue the implementation ${iterations} times`;
|
|
792
|
+
}
|
|
793
|
+
else {
|
|
794
|
+
command = `Continue implementation ${iterations}x`;
|
|
795
|
+
}
|
|
796
|
+
// cursor-agent needs extra time (30 minutes)
|
|
797
|
+
const timeout = toolName === 'cursor-agent' ? 1800000 : undefined;
|
|
798
|
+
return await this.sendCommandToCLI(toolName, command, { timeout });
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Send test command
|
|
802
|
+
*/
|
|
803
|
+
async sendTestCommand(toolName) {
|
|
804
|
+
let command;
|
|
805
|
+
if (toolName === 'cursor-agent') {
|
|
806
|
+
command = `Execute comprehensive test suite with automatic error correction:
|
|
807
|
+
|
|
808
|
+
1. Run the project's test command (npm test, cargo test, pytest, go test, etc.)
|
|
809
|
+
2. Analyze ALL test failures and errors in detail
|
|
810
|
+
3. If there are ANY test failures:
|
|
811
|
+
a. Read the failing test files and implementation code
|
|
812
|
+
b. Identify the root cause of each failure
|
|
813
|
+
c. Fix the implementation code OR fix the test if it's incorrect
|
|
814
|
+
d. Run tests again to verify fixes
|
|
815
|
+
e. Repeat steps a-d until ALL tests pass (100%)
|
|
816
|
+
4. Check test coverage:
|
|
817
|
+
a. Run coverage command (npm run test:coverage, cargo llvm-cov, pytest --cov, etc.)
|
|
818
|
+
b. If coverage is below threshold (95%): Add more tests to increase coverage
|
|
819
|
+
c. Run coverage again until threshold is met
|
|
820
|
+
5. If all tests pass with adequate coverage: Report success with coverage percentage
|
|
821
|
+
6. Maximum 5 correction attempts
|
|
822
|
+
|
|
823
|
+
CRITICAL: ALL tests must pass (100%) and coverage must meet threshold (95%+) before proceeding.`;
|
|
824
|
+
}
|
|
825
|
+
else if (toolName === 'claude-code') {
|
|
826
|
+
command =
|
|
827
|
+
'Run all tests and ensure 100% passing. If tests fail, fix the issues and rerun. Check coverage meets 95%+ threshold. Maximum 5 attempts.';
|
|
828
|
+
}
|
|
829
|
+
else if (toolName === 'gemini-cli') {
|
|
830
|
+
command = 'Please run tests, fix failures until all pass, and ensure coverage is 95%+.';
|
|
831
|
+
}
|
|
832
|
+
else {
|
|
833
|
+
command = 'Run tests, fix failures, ensure all pass and coverage is 95%+';
|
|
834
|
+
}
|
|
835
|
+
return await this.sendCommandToCLI(toolName, command, { timeout: 600000 }); // 10 minutes for multiple test runs
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Send lint command
|
|
839
|
+
*/
|
|
840
|
+
async sendLintCommand(toolName) {
|
|
841
|
+
let command;
|
|
842
|
+
if (toolName === 'cursor-agent') {
|
|
843
|
+
command = `Execute linting quality checks with automatic error correction:
|
|
844
|
+
|
|
845
|
+
1. Run the project's lint command (npm run lint, cargo clippy, ruff check, etc.)
|
|
846
|
+
2. Analyze ALL lint errors and warnings carefully
|
|
847
|
+
3. If there are ANY lint issues:
|
|
848
|
+
a. Read the files with lint errors
|
|
849
|
+
b. Fix ALL issues according to the linter's suggestions
|
|
850
|
+
c. Run lint again to verify fixes
|
|
851
|
+
d. Repeat steps a-c until linting passes with ZERO warnings
|
|
852
|
+
4. If linting passes: Report success
|
|
853
|
+
5. Maximum 3 correction attempts
|
|
854
|
+
|
|
855
|
+
CRITICAL: Do NOT proceed until linting passes with 0 warnings.`;
|
|
856
|
+
}
|
|
857
|
+
else if (toolName === 'claude-code') {
|
|
858
|
+
command =
|
|
859
|
+
'Run linting checks and fix all issues. Keep fixing until linting passes with 0 warnings. Maximum 3 attempts.';
|
|
860
|
+
}
|
|
861
|
+
else if (toolName === 'gemini-cli') {
|
|
862
|
+
command =
|
|
863
|
+
'Please run lint checks, fix any issues found, and repeat until passing with 0 warnings.';
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
command = 'Run lint checks and fix any issues until passing with 0 warnings';
|
|
867
|
+
}
|
|
868
|
+
return await this.sendCommandToCLI(toolName, command, { timeout: 300000 }); // 5 minutes for multiple attempts
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Send format command
|
|
872
|
+
*/
|
|
873
|
+
async sendFormatCommand(toolName) {
|
|
874
|
+
let command;
|
|
875
|
+
if (toolName === 'cursor-agent') {
|
|
876
|
+
command = `Apply code formatting according to project standards:
|
|
877
|
+
|
|
878
|
+
1. Run the project's format command (npm run format, cargo fmt, black ., gofmt -w ., etc.)
|
|
879
|
+
2. If there are any formatting issues:
|
|
880
|
+
a. Apply automatic formatting fixes
|
|
881
|
+
b. Verify formatting is correct by running format check again
|
|
882
|
+
3. Ensure all files pass formatting standards
|
|
883
|
+
4. Report success when formatting is complete
|
|
884
|
+
|
|
885
|
+
This is typically automatic and should complete quickly.`;
|
|
886
|
+
}
|
|
887
|
+
else if (toolName === 'claude-code') {
|
|
888
|
+
command = 'Format all code according to project standards using the project formatter.';
|
|
889
|
+
}
|
|
890
|
+
else if (toolName === 'gemini-cli') {
|
|
891
|
+
command = 'Please format the code according to project standards.';
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
command = 'Format code according to project standards';
|
|
895
|
+
}
|
|
896
|
+
return await this.sendCommandToCLI(toolName, command, { timeout: 120000 }); // 2 minutes
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Send commit command
|
|
900
|
+
*/
|
|
901
|
+
async sendCommitCommand(toolName, message) {
|
|
902
|
+
let command;
|
|
903
|
+
if (toolName === 'cursor-agent') {
|
|
904
|
+
command = `Commit all changes to Git with proper commit message:
|
|
905
|
+
|
|
906
|
+
1. Stage all changes: git add .
|
|
907
|
+
2. Create commit with this EXACT message:
|
|
908
|
+
"${message}"
|
|
909
|
+
3. Verify commit was created successfully
|
|
910
|
+
4. DO NOT push (user will push manually)
|
|
911
|
+
|
|
912
|
+
Report the commit hash when complete.`;
|
|
913
|
+
}
|
|
914
|
+
else if (toolName === 'claude-code') {
|
|
915
|
+
command = `Stage all changes (git add .) and commit with message: "${message}". Do not push.`;
|
|
916
|
+
}
|
|
917
|
+
else if (toolName === 'gemini-cli') {
|
|
918
|
+
command = `Please commit changes with message: "${message}". Do not push.`;
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
command = `Commit changes with message: ${message}`;
|
|
922
|
+
}
|
|
923
|
+
return await this.sendCommandToCLI(toolName, command, { timeout: 30000 });
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Check if CLI tool is responsive
|
|
927
|
+
*/
|
|
928
|
+
async checkCLIHealth(toolName) {
|
|
929
|
+
try {
|
|
930
|
+
const response = await this.sendCommandToCLI(toolName, 'ping', { timeout: 5000 });
|
|
931
|
+
return response.success;
|
|
932
|
+
}
|
|
933
|
+
catch {
|
|
934
|
+
return false;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Get CLI tool capabilities
|
|
939
|
+
*/
|
|
940
|
+
async getCLICapabilities(toolName) {
|
|
941
|
+
try {
|
|
942
|
+
// Use very short timeout in test environment to avoid hanging on Windows
|
|
943
|
+
const timeout = process.env.NODE_ENV === 'test' || process.env.VITEST ? 100 : 10000;
|
|
944
|
+
const response = await this.sendCommandToCLI(toolName, 'capabilities', { timeout });
|
|
945
|
+
if (response.success) {
|
|
946
|
+
return response.output.split('\n').filter((line) => line.trim());
|
|
947
|
+
}
|
|
948
|
+
return [];
|
|
949
|
+
}
|
|
950
|
+
catch {
|
|
951
|
+
return [];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Execute workflow step
|
|
956
|
+
*/
|
|
957
|
+
async executeWorkflowStep(toolName, step, context) {
|
|
958
|
+
const startTime = Date.now();
|
|
959
|
+
this.logger.info(`Executing workflow step: ${step}`, { tool: toolName, context });
|
|
960
|
+
let response;
|
|
961
|
+
switch (step) {
|
|
962
|
+
case 'implement':
|
|
963
|
+
if (context?.task) {
|
|
964
|
+
response = await this.sendTaskCommand(toolName, context.task);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
response = await this.sendCommandToCLI(toolName, 'Implement current task');
|
|
968
|
+
}
|
|
969
|
+
break;
|
|
970
|
+
case 'test':
|
|
971
|
+
response = await this.sendTestCommand(toolName);
|
|
972
|
+
break;
|
|
973
|
+
case 'lint':
|
|
974
|
+
response = await this.sendLintCommand(toolName);
|
|
975
|
+
break;
|
|
976
|
+
case 'format':
|
|
977
|
+
response = await this.sendFormatCommand(toolName);
|
|
978
|
+
break;
|
|
979
|
+
case 'commit': {
|
|
980
|
+
const message = context?.message || 'Auto-commit from rulebook agent';
|
|
981
|
+
response = await this.sendCommitCommand(toolName, message);
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
default:
|
|
985
|
+
throw new Error(`Unknown workflow step: ${step}`);
|
|
986
|
+
}
|
|
987
|
+
const duration = Date.now() - startTime;
|
|
988
|
+
this.logger.info(`Workflow step completed: ${step}`, {
|
|
989
|
+
tool: toolName,
|
|
990
|
+
success: response.success,
|
|
991
|
+
duration,
|
|
992
|
+
});
|
|
993
|
+
return response;
|
|
994
|
+
}
|
|
995
|
+
/**
|
|
996
|
+
* Kill all active processes (cleanup)
|
|
997
|
+
*/
|
|
998
|
+
async killAllProcesses() {
|
|
999
|
+
if (this.activeProcesses.size === 0) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
await this.debugLog(`\n=== KILLING ALL ACTIVE PROCESSES ===`);
|
|
1003
|
+
await this.debugLog(`Active processes: ${this.activeProcesses.size}`);
|
|
1004
|
+
const killPromises = [];
|
|
1005
|
+
for (const [processId, proc] of this.activeProcesses.entries()) {
|
|
1006
|
+
killPromises.push((async () => {
|
|
1007
|
+
try {
|
|
1008
|
+
if (!proc.killed && proc.exitCode === null) {
|
|
1009
|
+
await this.debugLog(`Killing process ${processId} (PID: ${proc.pid})`);
|
|
1010
|
+
proc.kill('SIGKILL');
|
|
1011
|
+
// Wait for exit
|
|
1012
|
+
await new Promise((resolve) => {
|
|
1013
|
+
const timeout = setTimeout(() => {
|
|
1014
|
+
resolve();
|
|
1015
|
+
}, 500);
|
|
1016
|
+
proc.once('exit', () => {
|
|
1017
|
+
clearTimeout(timeout);
|
|
1018
|
+
resolve();
|
|
1019
|
+
});
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
this.activeProcesses.delete(processId);
|
|
1023
|
+
}
|
|
1024
|
+
catch (error) {
|
|
1025
|
+
await this.debugLog(`Error killing process ${processId}: ${error}`);
|
|
1026
|
+
}
|
|
1027
|
+
})());
|
|
1028
|
+
}
|
|
1029
|
+
await Promise.all(killPromises);
|
|
1030
|
+
await this.debugLog(`All processes killed. Remaining: ${this.activeProcesses.size}`);
|
|
1031
|
+
await this.debugLog(`===================================\n`);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Smart continue detection based on CLI output patterns
|
|
1035
|
+
*/
|
|
1036
|
+
async smartContinueDetection(toolName, lastOutput) {
|
|
1037
|
+
// Patterns that indicate CLI is still processing
|
|
1038
|
+
const processingPatterns = [
|
|
1039
|
+
/thinking/i,
|
|
1040
|
+
/processing/i,
|
|
1041
|
+
/analyzing/i,
|
|
1042
|
+
/generating/i,
|
|
1043
|
+
/working/i,
|
|
1044
|
+
/please wait/i,
|
|
1045
|
+
/\.\.\./g,
|
|
1046
|
+
/loading/i,
|
|
1047
|
+
];
|
|
1048
|
+
// Patterns that indicate CLI has stopped
|
|
1049
|
+
const stoppedPatterns = [
|
|
1050
|
+
/ready/i,
|
|
1051
|
+
/done/i,
|
|
1052
|
+
/complete/i,
|
|
1053
|
+
/finished/i,
|
|
1054
|
+
/awaiting/i,
|
|
1055
|
+
/waiting for/i,
|
|
1056
|
+
/next command/i,
|
|
1057
|
+
];
|
|
1058
|
+
// Check for processing patterns
|
|
1059
|
+
const isProcessing = processingPatterns.some((pattern) => pattern.test(lastOutput));
|
|
1060
|
+
if (isProcessing) {
|
|
1061
|
+
this.logger.debug(`CLI appears to be processing: ${toolName}`);
|
|
1062
|
+
return false; // Don't send continue
|
|
1063
|
+
}
|
|
1064
|
+
// Check for stopped patterns
|
|
1065
|
+
const isStopped = stoppedPatterns.some((pattern) => pattern.test(lastOutput));
|
|
1066
|
+
if (isStopped) {
|
|
1067
|
+
this.logger.debug(`CLI appears to be stopped: ${toolName}`);
|
|
1068
|
+
return true; // Send continue
|
|
1069
|
+
}
|
|
1070
|
+
// Default behavior: send continue if no clear indication
|
|
1071
|
+
this.logger.debug(`Unclear CLI state, defaulting to continue: ${toolName}`);
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Singleton instance of CLIBridge
|
|
1077
|
+
*/
|
|
1078
|
+
let cliBridgeInstance = null;
|
|
1079
|
+
/**
|
|
1080
|
+
* Create CLI bridge instance (singleton)
|
|
1081
|
+
*/
|
|
1082
|
+
export function createCLIBridge(logger, config) {
|
|
1083
|
+
if (!cliBridgeInstance) {
|
|
1084
|
+
cliBridgeInstance = new CLIBridge(logger, config);
|
|
1085
|
+
}
|
|
1086
|
+
return cliBridgeInstance;
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Reset singleton (for testing only)
|
|
1090
|
+
*/
|
|
1091
|
+
export function resetCLIBridge() {
|
|
1092
|
+
cliBridgeInstance = null;
|
|
1093
|
+
}
|
|
1094
|
+
//# sourceMappingURL=cli-bridge.js.map
|