agentsys 5.0.2 → 5.1.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/.claude-plugin/marketplace.json +21 -14
- package/.claude-plugin/plugin.json +1 -1
- package/AGENTS.md +2 -1
- package/CHANGELOG.md +24 -1
- package/README.md +7 -6
- package/adapters/codex/skills/agnix/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-agents/SKILL.md +0 -1
- package/adapters/codex/skills/audit-project-github/SKILL.md +0 -1
- package/adapters/codex/skills/consult/SKILL.md +133 -59
- package/adapters/codex/skills/debate/SKILL.md +214 -0
- package/adapters/codex/skills/delivery-approval/SKILL.md +0 -1
- package/adapters/codex/skills/deslop/SKILL.md +0 -1
- package/adapters/codex/skills/drift-detect/SKILL.md +0 -1
- package/adapters/codex/skills/enhance/SKILL.md +0 -1
- package/adapters/codex/skills/learn/SKILL.md +0 -1
- package/adapters/codex/skills/next-task/SKILL.md +0 -1
- package/adapters/codex/skills/perf/SKILL.md +0 -1
- package/adapters/codex/skills/repo-map/SKILL.md +0 -1
- package/adapters/codex/skills/ship/SKILL.md +0 -1
- package/adapters/codex/skills/ship-ci-review-loop/SKILL.md +0 -1
- package/adapters/codex/skills/ship-deployment/SKILL.md +0 -1
- package/adapters/codex/skills/ship-error-handling/SKILL.md +0 -1
- package/adapters/codex/skills/sync-docs/SKILL.md +0 -1
- package/adapters/opencode/agents/agent-enhancer.md +0 -1
- package/adapters/opencode/agents/agnix-agent.md +0 -1
- package/adapters/opencode/agents/ci-fixer.md +0 -1
- package/adapters/opencode/agents/ci-monitor.md +0 -1
- package/adapters/opencode/agents/claudemd-enhancer.md +0 -1
- package/adapters/opencode/agents/consult-agent.md +123 -31
- package/adapters/opencode/agents/cross-file-enhancer.md +0 -1
- package/adapters/opencode/agents/debate-orchestrator.md +169 -0
- package/adapters/opencode/agents/delivery-validator.md +0 -1
- package/adapters/opencode/agents/deslop-agent.md +0 -1
- package/adapters/opencode/agents/docs-enhancer.md +0 -1
- package/adapters/opencode/agents/exploration-agent.md +0 -1
- package/adapters/opencode/agents/hooks-enhancer.md +0 -1
- package/adapters/opencode/agents/implementation-agent.md +0 -1
- package/adapters/opencode/agents/learn-agent.md +0 -1
- package/adapters/opencode/agents/map-validator.md +0 -1
- package/adapters/opencode/agents/perf-analyzer.md +0 -1
- package/adapters/opencode/agents/perf-code-paths.md +0 -1
- package/adapters/opencode/agents/perf-investigation-logger.md +0 -1
- package/adapters/opencode/agents/perf-orchestrator.md +0 -1
- package/adapters/opencode/agents/perf-theory-gatherer.md +0 -1
- package/adapters/opencode/agents/perf-theory-tester.md +0 -1
- package/adapters/opencode/agents/plan-synthesizer.md +0 -1
- package/adapters/opencode/agents/planning-agent.md +0 -1
- package/adapters/opencode/agents/plugin-enhancer.md +0 -1
- package/adapters/opencode/agents/prompt-enhancer.md +0 -1
- package/adapters/opencode/agents/simple-fixer.md +0 -1
- package/adapters/opencode/agents/skills-enhancer.md +0 -1
- package/adapters/opencode/agents/sync-docs-agent.md +0 -1
- package/adapters/opencode/agents/task-discoverer.md +0 -1
- package/adapters/opencode/agents/test-coverage-checker.md +0 -1
- package/adapters/opencode/agents/worktree-manager.md +0 -1
- package/adapters/opencode/commands/agnix.md +0 -1
- package/adapters/opencode/commands/audit-project-agents.md +0 -1
- package/adapters/opencode/commands/audit-project-github.md +0 -1
- package/adapters/opencode/commands/audit-project.md +0 -1
- package/adapters/opencode/commands/consult.md +134 -59
- package/adapters/opencode/commands/debate.md +224 -0
- package/adapters/opencode/commands/delivery-approval.md +0 -1
- package/adapters/opencode/commands/deslop.md +0 -1
- package/adapters/opencode/commands/drift-detect.md +0 -1
- package/adapters/opencode/commands/enhance.md +0 -1
- package/adapters/opencode/commands/learn.md +0 -1
- package/adapters/opencode/commands/next-task.md +0 -1
- package/adapters/opencode/commands/perf.md +0 -1
- package/adapters/opencode/commands/repo-map.md +0 -1
- package/adapters/opencode/commands/ship-ci-review-loop.md +0 -1
- package/adapters/opencode/commands/ship-deployment.md +0 -1
- package/adapters/opencode/commands/ship-error-handling.md +0 -1
- package/adapters/opencode/commands/ship.md +0 -1
- package/adapters/opencode/commands/sync-docs.md +0 -1
- package/adapters/opencode/skills/agnix/SKILL.md +1 -2
- package/adapters/opencode/skills/consult/SKILL.md +41 -27
- package/adapters/opencode/skills/debate/SKILL.md +245 -0
- package/adapters/opencode/skills/deslop/SKILL.md +1 -2
- package/adapters/opencode/skills/discover-tasks/SKILL.md +1 -2
- package/adapters/opencode/skills/drift-analysis/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-agent-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-claude-memory/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-cross-file/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-hooks/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-orchestrator/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-plugins/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-prompts/SKILL.md +1 -2
- package/adapters/opencode/skills/enhance-skills/SKILL.md +1 -2
- package/adapters/opencode/skills/learn/SKILL.md +1 -2
- package/adapters/opencode/skills/orchestrate-review/SKILL.md +0 -1
- package/adapters/opencode/skills/perf-analyzer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-baseline-manager/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-benchmarker/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-code-paths/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-investigation-logger/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-profiler/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-gatherer/SKILL.md +1 -2
- package/adapters/opencode/skills/perf-theory-tester/SKILL.md +1 -2
- package/adapters/opencode/skills/repo-mapping/SKILL.md +1 -2
- package/adapters/opencode/skills/sync-docs/SKILL.md +1 -2
- package/adapters/opencode/skills/validate-delivery/SKILL.md +1 -2
- package/lib/adapter-transforms.js +24 -4
- package/package.json +1 -1
- package/plugins/agnix/.claude-plugin/plugin.json +1 -1
- package/plugins/agnix/skills/agnix/SKILL.md +1 -1
- package/plugins/audit-project/.claude-plugin/plugin.json +1 -1
- package/plugins/audit-project/lib/adapter-transforms.js +24 -4
- package/plugins/consult/.claude-plugin/plugin.json +1 -1
- package/plugins/consult/agents/consult-agent.md +123 -30
- package/plugins/consult/commands/consult.md +136 -60
- package/plugins/consult/skills/consult/SKILL.md +39 -24
- package/plugins/debate/.claude-plugin/plugin.json +21 -0
- package/plugins/debate/agents/debate-orchestrator.md +175 -0
- package/plugins/debate/commands/debate.md +221 -0
- package/plugins/debate/lib/adapter-transforms.js +298 -0
- package/plugins/debate/lib/collectors/codebase.js +392 -0
- package/plugins/debate/lib/collectors/docs-patterns.js +713 -0
- package/plugins/debate/lib/collectors/documentation.js +219 -0
- package/plugins/debate/lib/collectors/github.js +330 -0
- package/plugins/debate/lib/collectors/index.js +126 -0
- package/plugins/debate/lib/config/index.js +14 -0
- package/plugins/debate/lib/cross-platform/index.js +539 -0
- package/plugins/debate/lib/discovery/index.js +352 -0
- package/plugins/debate/lib/drift-detect/collectors.js +37 -0
- package/plugins/debate/lib/enhance/agent-analyzer.js +421 -0
- package/plugins/debate/lib/enhance/agent-patterns.js +571 -0
- package/plugins/debate/lib/enhance/auto-suppression.js +622 -0
- package/plugins/debate/lib/enhance/benchmark.js +417 -0
- package/plugins/debate/lib/enhance/cross-file-analyzer.js +930 -0
- package/plugins/debate/lib/enhance/cross-file-patterns.js +370 -0
- package/plugins/debate/lib/enhance/docs-analyzer.js +325 -0
- package/plugins/debate/lib/enhance/docs-patterns.js +671 -0
- package/plugins/debate/lib/enhance/fixer.js +721 -0
- package/plugins/debate/lib/enhance/hook-analyzer.js +135 -0
- package/plugins/debate/lib/enhance/hook-patterns.js +40 -0
- package/plugins/debate/lib/enhance/index.js +127 -0
- package/plugins/debate/lib/enhance/plugin-analyzer.js +402 -0
- package/plugins/debate/lib/enhance/plugin-patterns.js +326 -0
- package/plugins/debate/lib/enhance/projectmemory-analyzer.js +551 -0
- package/plugins/debate/lib/enhance/projectmemory-patterns.js +617 -0
- package/plugins/debate/lib/enhance/prompt-analyzer.js +457 -0
- package/plugins/debate/lib/enhance/prompt-patterns.js +1484 -0
- package/plugins/debate/lib/enhance/reporter.js +1348 -0
- package/plugins/debate/lib/enhance/security-patterns.js +284 -0
- package/plugins/debate/lib/enhance/skill-analyzer.js +182 -0
- package/plugins/debate/lib/enhance/skill-patterns.js +147 -0
- package/plugins/debate/lib/enhance/suppression.js +352 -0
- package/plugins/debate/lib/enhance/tool-patterns.js +373 -0
- package/plugins/debate/lib/index.js +270 -0
- package/plugins/debate/lib/patterns/cli-enhancers.js +611 -0
- package/plugins/debate/lib/patterns/pipeline.js +948 -0
- package/plugins/debate/lib/patterns/review-patterns.js +558 -0
- package/plugins/debate/lib/patterns/slop-analyzers.js +2305 -0
- package/plugins/debate/lib/patterns/slop-patterns.js +1187 -0
- package/plugins/debate/lib/perf/analyzer/index.js +22 -0
- package/plugins/debate/lib/perf/argument-parser.js +105 -0
- package/plugins/debate/lib/perf/baseline-comparator.js +50 -0
- package/plugins/debate/lib/perf/baseline-store.js +127 -0
- package/plugins/debate/lib/perf/benchmark-runner.js +404 -0
- package/plugins/debate/lib/perf/breaking-point-finder.js +52 -0
- package/plugins/debate/lib/perf/breaking-point-runner.js +60 -0
- package/plugins/debate/lib/perf/checkpoint.js +123 -0
- package/plugins/debate/lib/perf/code-paths.js +86 -0
- package/plugins/debate/lib/perf/consolidation.js +37 -0
- package/plugins/debate/lib/perf/constraint-runner.js +71 -0
- package/plugins/debate/lib/perf/experiment-runner.js +32 -0
- package/plugins/debate/lib/perf/index.js +41 -0
- package/plugins/debate/lib/perf/investigation-state.js +874 -0
- package/plugins/debate/lib/perf/optimization-runner.js +79 -0
- package/plugins/debate/lib/perf/profilers/go.js +22 -0
- package/plugins/debate/lib/perf/profilers/index.js +46 -0
- package/plugins/debate/lib/perf/profilers/java.js +23 -0
- package/plugins/debate/lib/perf/profilers/node.js +27 -0
- package/plugins/debate/lib/perf/profilers/python.js +23 -0
- package/plugins/debate/lib/perf/profilers/rust.js +23 -0
- package/plugins/debate/lib/perf/profiling-runner.js +75 -0
- package/plugins/debate/lib/perf/schemas.js +140 -0
- package/plugins/debate/lib/platform/detect-platform.js +413 -0
- package/plugins/debate/lib/platform/detection-configs.js +93 -0
- package/plugins/debate/lib/platform/state-dir.js +132 -0
- package/plugins/debate/lib/platform/verify-tools.js +182 -0
- package/plugins/debate/lib/repo-map/cache.js +152 -0
- package/plugins/debate/lib/repo-map/concurrency.js +29 -0
- package/plugins/debate/lib/repo-map/index.js +222 -0
- package/plugins/debate/lib/repo-map/installer.js +212 -0
- package/plugins/debate/lib/repo-map/queries/go.js +27 -0
- package/plugins/debate/lib/repo-map/queries/index.js +100 -0
- package/plugins/debate/lib/repo-map/queries/java.js +38 -0
- package/plugins/debate/lib/repo-map/queries/javascript.js +55 -0
- package/plugins/debate/lib/repo-map/queries/python.js +24 -0
- package/plugins/debate/lib/repo-map/queries/rust.js +73 -0
- package/plugins/debate/lib/repo-map/queries/typescript.js +38 -0
- package/plugins/debate/lib/repo-map/runner.js +1364 -0
- package/plugins/debate/lib/repo-map/updater.js +562 -0
- package/plugins/debate/lib/repo-map/usage-analyzer.js +407 -0
- package/plugins/debate/lib/schemas/plugin-manifest.schema.json +57 -0
- package/plugins/debate/lib/schemas/validator.js +247 -0
- package/plugins/debate/lib/sources/custom-handler.js +199 -0
- package/plugins/debate/lib/sources/policy-questions.js +246 -0
- package/plugins/debate/lib/sources/source-cache.js +165 -0
- package/plugins/debate/lib/state/workflow-state.js +576 -0
- package/plugins/debate/lib/types/agent-frontmatter.d.ts +134 -0
- package/plugins/debate/lib/types/command-frontmatter.d.ts +107 -0
- package/plugins/debate/lib/types/hook-frontmatter.d.ts +115 -0
- package/plugins/debate/lib/types/index.d.ts +84 -0
- package/plugins/debate/lib/types/plugin-manifest.d.ts +102 -0
- package/plugins/debate/lib/types/skill-frontmatter.d.ts +89 -0
- package/plugins/debate/lib/utils/atomic-write.js +94 -0
- package/plugins/debate/lib/utils/cache-manager.js +159 -0
- package/plugins/debate/lib/utils/command-parser.js +0 -0
- package/plugins/debate/lib/utils/context-optimizer.js +300 -0
- package/plugins/debate/lib/utils/deprecation.js +37 -0
- package/plugins/debate/lib/utils/shell-escape.js +88 -0
- package/plugins/debate/lib/utils/state-helpers.js +61 -0
- package/plugins/debate/skills/debate/SKILL.md +264 -0
- package/plugins/deslop/.claude-plugin/plugin.json +1 -1
- package/plugins/deslop/lib/adapter-transforms.js +24 -4
- package/plugins/deslop/skills/deslop/SKILL.md +1 -1
- package/plugins/drift-detect/.claude-plugin/plugin.json +1 -1
- package/plugins/drift-detect/lib/adapter-transforms.js +24 -4
- package/plugins/drift-detect/skills/drift-analysis/SKILL.md +1 -1
- package/plugins/enhance/.claude-plugin/plugin.json +1 -1
- package/plugins/enhance/lib/adapter-transforms.js +24 -4
- package/plugins/enhance/skills/enhance-agent-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-claude-memory/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-cross-file/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-docs/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-hooks/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-orchestrator/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-plugins/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-prompts/SKILL.md +1 -1
- package/plugins/enhance/skills/enhance-skills/SKILL.md +1 -1
- package/plugins/learn/.claude-plugin/plugin.json +1 -1
- package/plugins/learn/agents/learn-agent.md +1 -1
- package/plugins/learn/lib/adapter-transforms.js +24 -4
- package/plugins/learn/skills/learn/SKILL.md +1 -1
- package/plugins/next-task/.claude-plugin/plugin.json +1 -1
- package/plugins/next-task/agents/exploration-agent.md +1 -1
- package/plugins/next-task/lib/adapter-transforms.js +24 -4
- package/plugins/next-task/skills/discover-tasks/SKILL.md +1 -1
- package/plugins/next-task/skills/validate-delivery/SKILL.md +1 -1
- package/plugins/perf/.claude-plugin/plugin.json +1 -1
- package/plugins/perf/lib/adapter-transforms.js +24 -4
- package/plugins/perf/skills/perf-analyzer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-baseline-manager/SKILL.md +1 -1
- package/plugins/perf/skills/perf-benchmarker/SKILL.md +1 -1
- package/plugins/perf/skills/perf-code-paths/SKILL.md +1 -1
- package/plugins/perf/skills/perf-investigation-logger/SKILL.md +1 -1
- package/plugins/perf/skills/perf-profiler/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-gatherer/SKILL.md +1 -1
- package/plugins/perf/skills/perf-theory-tester/SKILL.md +1 -1
- package/plugins/repo-map/.claude-plugin/plugin.json +1 -1
- package/plugins/repo-map/lib/adapter-transforms.js +24 -4
- package/plugins/ship/.claude-plugin/plugin.json +1 -1
- package/plugins/ship/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/.claude-plugin/plugin.json +1 -1
- package/plugins/sync-docs/lib/adapter-transforms.js +24 -4
- package/plugins/sync-docs/skills/sync-docs/SKILL.md +1 -1
- package/scripts/gen-adapters.js +6 -7
- package/scripts/generate-docs.js +4 -2
- package/scripts/plugins.txt +1 -0
- package/site/content.json +6 -6
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Platform Detection Infrastructure
|
|
4
|
+
* Auto-detects project configuration for zero-config slash commands
|
|
5
|
+
*
|
|
6
|
+
* Usage: node lib/platform/detect-platform.js
|
|
7
|
+
* Output: JSON with detected platform information
|
|
8
|
+
*
|
|
9
|
+
* @author Avi Fenesh
|
|
10
|
+
* @license MIT
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const { exec } = require('child_process');
|
|
16
|
+
const { promisify } = require('util');
|
|
17
|
+
|
|
18
|
+
const execAsync = promisify(exec);
|
|
19
|
+
const fsPromises = fs.promises;
|
|
20
|
+
|
|
21
|
+
// Import shared utilities
|
|
22
|
+
const { CacheManager } = require('../utils/cache-manager');
|
|
23
|
+
const {
|
|
24
|
+
CI_CONFIGS,
|
|
25
|
+
DEPLOYMENT_CONFIGS,
|
|
26
|
+
PACKAGE_MANAGER_CONFIGS
|
|
27
|
+
} = require('./detection-configs');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default timeout for async operations (5 seconds)
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_ASYNC_TIMEOUT_MS = 5000;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Maximum JSON file size to parse (1MB) - prevents DoS via large files
|
|
36
|
+
*/
|
|
37
|
+
const MAX_JSON_SIZE_BYTES = 1024 * 1024;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Safely parse JSON content with size limit
|
|
41
|
+
* @param {string} content - JSON string to parse
|
|
42
|
+
* @param {string} filename - Filename for error messages
|
|
43
|
+
* @returns {Object|null} Parsed object or null if invalid/too large
|
|
44
|
+
*/
|
|
45
|
+
function safeJSONParse(content, filename = 'unknown') {
|
|
46
|
+
if (!content || typeof content !== 'string') {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (content.length > MAX_JSON_SIZE_BYTES) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.parse(content);
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Wrap a promise with a timeout
|
|
61
|
+
* @param {Promise} promise - Promise to wrap
|
|
62
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
63
|
+
* @param {string} operation - Operation name for error message
|
|
64
|
+
* @returns {Promise} Promise that rejects on timeout
|
|
65
|
+
*/
|
|
66
|
+
function withTimeout(promise, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS, operation = 'operation') {
|
|
67
|
+
let timeoutId;
|
|
68
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
69
|
+
timeoutId = setTimeout(() => {
|
|
70
|
+
reject(new Error(`${operation} timed out after ${timeoutMs}ms`));
|
|
71
|
+
}, timeoutMs);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return Promise.race([promise, timeoutPromise]).finally(() => {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Execute a command with timeout protection
|
|
81
|
+
* @param {string} cmd - Command to execute
|
|
82
|
+
* @param {Object} options - exec options
|
|
83
|
+
* @param {number} timeoutMs - Timeout in milliseconds
|
|
84
|
+
* @returns {Promise<{stdout: string, stderr: string}>}
|
|
85
|
+
*/
|
|
86
|
+
async function execWithTimeout(cmd, options = {}, timeoutMs = DEFAULT_ASYNC_TIMEOUT_MS) {
|
|
87
|
+
return withTimeout(execAsync(cmd, options), timeoutMs, `exec: ${cmd.substring(0, 50)}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Maximum cached file size constant
|
|
91
|
+
const MAX_CACHED_FILE_SIZE = 64 * 1024; // 64KB max per cached file
|
|
92
|
+
|
|
93
|
+
// Cache instances using CacheManager abstraction
|
|
94
|
+
const _detectionCache = new CacheManager({ maxSize: 1, ttl: 60000 });
|
|
95
|
+
const _fileCache = new CacheManager({ maxSize: 100, ttl: 60000, maxValueSize: MAX_CACHED_FILE_SIZE });
|
|
96
|
+
const _existsCache = new CacheManager({ maxSize: 100, ttl: 60000 });
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generic file-based detector
|
|
100
|
+
* @param {Array} configs - Array of {file, platform} objects
|
|
101
|
+
* @param {Function} existsChecker - Async function to check file existence
|
|
102
|
+
* @returns {Promise<string|null>} Detected platform or null
|
|
103
|
+
*/
|
|
104
|
+
async function detectFromFiles(configs, existsChecker) {
|
|
105
|
+
try {
|
|
106
|
+
const checks = await Promise.all(
|
|
107
|
+
configs.map(({ file }) => existsChecker(file).catch(() => false))
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
for (let i = 0; i < checks.length; i++) {
|
|
111
|
+
if (checks[i]) {
|
|
112
|
+
return configs[i].platform;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
} catch (error) {
|
|
117
|
+
// Log unexpected errors but return null to allow detection to continue
|
|
118
|
+
if (process.env.DEBUG) {
|
|
119
|
+
console.error(`[detect-platform] detectFromFiles error: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if a file exists (cached)
|
|
127
|
+
* @param {string} filepath - Path to check
|
|
128
|
+
* @returns {Promise<boolean>}
|
|
129
|
+
*/
|
|
130
|
+
async function existsCached(filepath) {
|
|
131
|
+
const cached = _existsCache.get(filepath);
|
|
132
|
+
if (cached !== undefined) {
|
|
133
|
+
return cached;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
await fsPromises.access(filepath);
|
|
137
|
+
_existsCache.set(filepath, true);
|
|
138
|
+
return true;
|
|
139
|
+
} catch {
|
|
140
|
+
_existsCache.set(filepath, false);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Read file contents (cached)
|
|
147
|
+
* Only caches files smaller than MAX_CACHED_FILE_SIZE to prevent memory bloat
|
|
148
|
+
* Optimized: normalizes filepath to prevent cache pollution from variant paths
|
|
149
|
+
* @param {string} filepath - Path to read
|
|
150
|
+
* @returns {Promise<string|null>}
|
|
151
|
+
*/
|
|
152
|
+
async function readFileCached(filepath) {
|
|
153
|
+
const normalizedPath = path.resolve(filepath);
|
|
154
|
+
|
|
155
|
+
const cached = _fileCache.get(normalizedPath);
|
|
156
|
+
if (cached !== undefined) {
|
|
157
|
+
return cached;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const content = await fsPromises.readFile(normalizedPath, 'utf8');
|
|
161
|
+
_fileCache.set(normalizedPath, content);
|
|
162
|
+
return content;
|
|
163
|
+
} catch {
|
|
164
|
+
_fileCache.set(normalizedPath, null);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Detects CI platform by scanning for configuration files
|
|
171
|
+
* @returns {Promise<string|null>} CI platform name or null if not detected
|
|
172
|
+
*/
|
|
173
|
+
async function detectCI() {
|
|
174
|
+
return detectFromFiles(CI_CONFIGS, existsCached);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Detects deployment platform by scanning for platform-specific files
|
|
179
|
+
* @returns {Promise<string|null>} Deployment platform name or null if not detected
|
|
180
|
+
*/
|
|
181
|
+
async function detectDeployment() {
|
|
182
|
+
return detectFromFiles(DEPLOYMENT_CONFIGS, existsCached);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Detects project type by scanning for language-specific files
|
|
187
|
+
* @returns {Promise<string>} Project type identifier
|
|
188
|
+
*/
|
|
189
|
+
async function detectProjectType() {
|
|
190
|
+
try {
|
|
191
|
+
const checks = await Promise.all([
|
|
192
|
+
existsCached('package.json').catch(() => false),
|
|
193
|
+
existsCached('requirements.txt').catch(() => false),
|
|
194
|
+
existsCached('pyproject.toml').catch(() => false),
|
|
195
|
+
existsCached('setup.py').catch(() => false),
|
|
196
|
+
existsCached('Cargo.toml').catch(() => false),
|
|
197
|
+
existsCached('go.mod').catch(() => false),
|
|
198
|
+
existsCached('pom.xml').catch(() => false),
|
|
199
|
+
existsCached('build.gradle').catch(() => false)
|
|
200
|
+
]);
|
|
201
|
+
|
|
202
|
+
if (checks[0]) return 'nodejs';
|
|
203
|
+
if (checks[1] || checks[2] || checks[3]) return 'python';
|
|
204
|
+
if (checks[4]) return 'rust';
|
|
205
|
+
if (checks[5]) return 'go';
|
|
206
|
+
if (checks[6] || checks[7]) return 'java';
|
|
207
|
+
return 'unknown';
|
|
208
|
+
} catch (error) {
|
|
209
|
+
if (process.env.DEBUG) {
|
|
210
|
+
console.error(`[detect-platform] detectProjectType error: ${error.message}`);
|
|
211
|
+
}
|
|
212
|
+
return 'unknown';
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Detects package manager by scanning for lockfiles
|
|
218
|
+
* @returns {Promise<string|null>} Package manager name or null if not detected
|
|
219
|
+
*/
|
|
220
|
+
async function detectPackageManager() {
|
|
221
|
+
return detectFromFiles(
|
|
222
|
+
PACKAGE_MANAGER_CONFIGS.map(({ file, manager }) => ({ file, platform: manager })),
|
|
223
|
+
existsCached
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Detects branch strategy (single-branch vs multi-branch with dev+prod)
|
|
229
|
+
* @returns {Promise<string>} 'single-branch' or 'multi-branch'
|
|
230
|
+
*/
|
|
231
|
+
async function detectBranchStrategy() {
|
|
232
|
+
try {
|
|
233
|
+
const [localResult, remoteResult] = await Promise.all([
|
|
234
|
+
execWithTimeout('git branch', { encoding: 'utf8' }).catch(() => ({ stdout: '' })),
|
|
235
|
+
execWithTimeout('git branch -r', { encoding: 'utf8' }).catch(() => ({ stdout: '' }))
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
const allBranches = (localResult.stdout || '') + (remoteResult.stdout || '');
|
|
239
|
+
|
|
240
|
+
const hasStable = allBranches.includes('stable');
|
|
241
|
+
const hasProduction = allBranches.includes('production') || allBranches.includes('prod');
|
|
242
|
+
|
|
243
|
+
if (hasStable || hasProduction) {
|
|
244
|
+
return 'multi-branch';
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (await existsCached('railway.json')) {
|
|
248
|
+
try {
|
|
249
|
+
const content = await readFileCached('railway.json');
|
|
250
|
+
if (content) {
|
|
251
|
+
const config = safeJSONParse(content, 'railway.json');
|
|
252
|
+
if (config &&
|
|
253
|
+
typeof config === 'object' &&
|
|
254
|
+
typeof config.environments === 'object' &&
|
|
255
|
+
config.environments !== null &&
|
|
256
|
+
Object.keys(config.environments).length > 1) {
|
|
257
|
+
return 'multi-branch';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} catch { /* config parse error - ignore */ }
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return 'single-branch';
|
|
264
|
+
} catch {
|
|
265
|
+
return 'single-branch';
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Detects the main branch name
|
|
271
|
+
* @returns {Promise<string>} Main branch name ('main' or 'master')
|
|
272
|
+
*/
|
|
273
|
+
async function detectMainBranch() {
|
|
274
|
+
try {
|
|
275
|
+
const { stdout } = await execWithTimeout('git symbolic-ref refs/remotes/origin/HEAD', { encoding: 'utf8' });
|
|
276
|
+
return stdout.trim().replace('refs/remotes/origin/', '');
|
|
277
|
+
} catch {
|
|
278
|
+
try {
|
|
279
|
+
await execWithTimeout('git rev-parse --verify main', { encoding: 'utf8' });
|
|
280
|
+
return 'main';
|
|
281
|
+
} catch {
|
|
282
|
+
return 'master';
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Main detection function - aggregates all platform information
|
|
289
|
+
* Uses Promise.all for parallel execution and caching
|
|
290
|
+
* @param {boolean} forceRefresh - Force cache refresh
|
|
291
|
+
* @returns {Promise<Object>} Platform configuration object
|
|
292
|
+
*/
|
|
293
|
+
async function detect(forceRefresh = false) {
|
|
294
|
+
if (!forceRefresh) {
|
|
295
|
+
const cached = _detectionCache.get('detection');
|
|
296
|
+
if (cached !== undefined) {
|
|
297
|
+
return cached;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const [
|
|
303
|
+
ci,
|
|
304
|
+
deployment,
|
|
305
|
+
projectType,
|
|
306
|
+
packageManager,
|
|
307
|
+
branchStrategy,
|
|
308
|
+
mainBranch,
|
|
309
|
+
hasPlanFile,
|
|
310
|
+
hasTechDebtFile
|
|
311
|
+
] = await Promise.all([
|
|
312
|
+
detectCI().catch((err) => {
|
|
313
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectCI failed: ${err.message}`);
|
|
314
|
+
return null;
|
|
315
|
+
}),
|
|
316
|
+
detectDeployment().catch((err) => {
|
|
317
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectDeployment failed: ${err.message}`);
|
|
318
|
+
return null;
|
|
319
|
+
}),
|
|
320
|
+
detectProjectType().catch((err) => {
|
|
321
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectProjectType failed: ${err.message}`);
|
|
322
|
+
return 'unknown';
|
|
323
|
+
}),
|
|
324
|
+
detectPackageManager().catch((err) => {
|
|
325
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectPackageManager failed: ${err.message}`);
|
|
326
|
+
return null;
|
|
327
|
+
}),
|
|
328
|
+
detectBranchStrategy().catch((err) => {
|
|
329
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectBranchStrategy failed: ${err.message}`);
|
|
330
|
+
return 'single-branch';
|
|
331
|
+
}),
|
|
332
|
+
detectMainBranch().catch((err) => {
|
|
333
|
+
if (process.env.DEBUG) console.error(`[detect-platform] detectMainBranch failed: ${err.message}`);
|
|
334
|
+
return 'main';
|
|
335
|
+
}),
|
|
336
|
+
existsCached('PLAN.md').catch(() => false),
|
|
337
|
+
existsCached('TECHNICAL_DEBT.md').catch(() => false)
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
const detection = {
|
|
341
|
+
ci,
|
|
342
|
+
deployment,
|
|
343
|
+
projectType,
|
|
344
|
+
packageManager,
|
|
345
|
+
branchStrategy,
|
|
346
|
+
mainBranch,
|
|
347
|
+
hasPlanFile,
|
|
348
|
+
hasTechDebtFile,
|
|
349
|
+
timestamp: new Date().toISOString()
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
_detectionCache.set('detection', detection);
|
|
353
|
+
return detection;
|
|
354
|
+
} catch (error) {
|
|
355
|
+
// Return sensible defaults if detection fails entirely
|
|
356
|
+
const fallback = {
|
|
357
|
+
ci: null,
|
|
358
|
+
deployment: null,
|
|
359
|
+
projectType: 'unknown',
|
|
360
|
+
packageManager: null,
|
|
361
|
+
branchStrategy: 'single-branch',
|
|
362
|
+
mainBranch: 'main',
|
|
363
|
+
hasPlanFile: false,
|
|
364
|
+
hasTechDebtFile: false,
|
|
365
|
+
timestamp: new Date().toISOString(),
|
|
366
|
+
error: error.message
|
|
367
|
+
};
|
|
368
|
+
if (process.env.DEBUG) {
|
|
369
|
+
console.error(`[detect-platform] detect() failed, returning fallback: ${error.message}`);
|
|
370
|
+
}
|
|
371
|
+
return fallback;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Invalidate all detection caches
|
|
377
|
+
* Call this after making changes that affect platform detection
|
|
378
|
+
*/
|
|
379
|
+
function invalidateCache() {
|
|
380
|
+
_detectionCache.clear();
|
|
381
|
+
_fileCache.clear();
|
|
382
|
+
_existsCache.clear();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// When run directly, output JSON
|
|
386
|
+
if (require.main === module) {
|
|
387
|
+
(async () => {
|
|
388
|
+
try {
|
|
389
|
+
const result = await detect();
|
|
390
|
+
const indent = process.stdout.isTTY ? 2 : 0;
|
|
391
|
+
console.log(JSON.stringify(result, null, indent));
|
|
392
|
+
} catch (error) {
|
|
393
|
+
const indent = process.stderr.isTTY ? 2 : 0;
|
|
394
|
+
console.error(JSON.stringify({
|
|
395
|
+
error: error.message,
|
|
396
|
+
timestamp: new Date().toISOString()
|
|
397
|
+
}, null, indent));
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
})();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Export for use as module
|
|
404
|
+
module.exports = {
|
|
405
|
+
detect,
|
|
406
|
+
invalidateCache,
|
|
407
|
+
detectCI,
|
|
408
|
+
detectDeployment,
|
|
409
|
+
detectProjectType,
|
|
410
|
+
detectPackageManager,
|
|
411
|
+
detectBranchStrategy,
|
|
412
|
+
detectMainBranch
|
|
413
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detection Configurations
|
|
3
|
+
* Centralized config for platform detection logic
|
|
4
|
+
*
|
|
5
|
+
* @module lib/platform/detection-configs
|
|
6
|
+
* @author Avi Fenesh
|
|
7
|
+
* @license MIT
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CI platform detection configuration
|
|
12
|
+
* Order matters - first match wins
|
|
13
|
+
*/
|
|
14
|
+
const CI_CONFIGS = [
|
|
15
|
+
{ file: '.github/workflows', platform: 'github-actions' },
|
|
16
|
+
{ file: '.gitlab-ci.yml', platform: 'gitlab-ci' },
|
|
17
|
+
{ file: '.circleci/config.yml', platform: 'circleci' },
|
|
18
|
+
{ file: 'Jenkinsfile', platform: 'jenkins' },
|
|
19
|
+
{ file: '.travis.yml', platform: 'travis' }
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Deployment platform detection configuration
|
|
24
|
+
* Order matters - first match wins
|
|
25
|
+
*/
|
|
26
|
+
const DEPLOYMENT_CONFIGS = [
|
|
27
|
+
{ file: 'railway.json', platform: 'railway' },
|
|
28
|
+
{ file: 'railway.toml', platform: 'railway' }, // Legacy Railway marker
|
|
29
|
+
{ file: 'vercel.json', platform: 'vercel' },
|
|
30
|
+
{ file: 'netlify.toml', platform: 'netlify' },
|
|
31
|
+
{ file: '.netlify', platform: 'netlify' }, // Legacy Netlify marker
|
|
32
|
+
{ file: 'fly.toml', platform: 'fly' },
|
|
33
|
+
{ file: '.platform.app.yaml', platform: 'platformsh' },
|
|
34
|
+
{ file: 'render.yaml', platform: 'render' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Project type detection configuration
|
|
39
|
+
* Checks package.json for framework indicators
|
|
40
|
+
*/
|
|
41
|
+
const PROJECT_TYPE_CONFIGS = {
|
|
42
|
+
dependencies: [
|
|
43
|
+
{ name: 'next', type: 'nextjs' },
|
|
44
|
+
{ name: 'react', type: 'react' },
|
|
45
|
+
{ name: 'vue', type: 'vue' },
|
|
46
|
+
{ name: '@angular/core', type: 'angular' },
|
|
47
|
+
{ name: 'svelte', type: 'svelte' },
|
|
48
|
+
{ name: 'express', type: 'express' },
|
|
49
|
+
{ name: '@nestjs/core', type: 'nestjs' },
|
|
50
|
+
{ name: 'gatsby', type: 'gatsby' },
|
|
51
|
+
{ name: '@remix-run/react', type: 'remix' },
|
|
52
|
+
{ name: 'astro', type: 'astro' }
|
|
53
|
+
]
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Package manager detection configuration
|
|
58
|
+
* Lock files indicate package manager used
|
|
59
|
+
* Order matters - first match wins (prioritize pnpm > yarn > bun > npm for Node.js)
|
|
60
|
+
*/
|
|
61
|
+
const PACKAGE_MANAGER_CONFIGS = [
|
|
62
|
+
{ file: 'pnpm-lock.yaml', manager: 'pnpm' },
|
|
63
|
+
{ file: 'yarn.lock', manager: 'yarn' },
|
|
64
|
+
{ file: 'bun.lockb', manager: 'bun' },
|
|
65
|
+
{ file: 'package-lock.json', manager: 'npm' },
|
|
66
|
+
{ file: 'poetry.lock', manager: 'poetry' },
|
|
67
|
+
{ file: 'Pipfile.lock', manager: 'pipenv' },
|
|
68
|
+
{ file: 'Cargo.lock', manager: 'cargo' },
|
|
69
|
+
{ file: 'go.sum', manager: 'go' }
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Branch strategy patterns
|
|
74
|
+
*/
|
|
75
|
+
const BRANCH_STRATEGIES = {
|
|
76
|
+
gitflow: ['develop', 'main', 'master'],
|
|
77
|
+
githubflow: ['main'],
|
|
78
|
+
trunkbased: ['main', 'trunk']
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Main branch candidates (in priority order)
|
|
83
|
+
*/
|
|
84
|
+
const MAIN_BRANCH_CANDIDATES = ['main', 'master', 'trunk', 'develop'];
|
|
85
|
+
|
|
86
|
+
module.exports = {
|
|
87
|
+
CI_CONFIGS,
|
|
88
|
+
DEPLOYMENT_CONFIGS,
|
|
89
|
+
PROJECT_TYPE_CONFIGS,
|
|
90
|
+
PACKAGE_MANAGER_CONFIGS,
|
|
91
|
+
BRANCH_STRATEGIES,
|
|
92
|
+
MAIN_BRANCH_CANDIDATES
|
|
93
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Platform-aware state directory detection
|
|
3
|
+
*
|
|
4
|
+
* Determines the appropriate state directory based on the AI coding assistant
|
|
5
|
+
* being used (Claude Code, OpenCode, or Codex CLI).
|
|
6
|
+
*
|
|
7
|
+
* @module lib/platform/state-dir
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Cached state directory name (relative, without leading dot handling),
|
|
15
|
+
* scoped by resolved base path.
|
|
16
|
+
* @type {Map<string, string>}
|
|
17
|
+
*/
|
|
18
|
+
const _cachedStateDirs = new Map();
|
|
19
|
+
|
|
20
|
+
function isDirectory(targetPath) {
|
|
21
|
+
try {
|
|
22
|
+
return fs.statSync(targetPath).isDirectory();
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Detect which AI coding assistant is running and return appropriate state directory
|
|
30
|
+
*
|
|
31
|
+
* Detection order:
|
|
32
|
+
* 1. AI_STATE_DIR env var (user override)
|
|
33
|
+
* 2. OpenCode detection (OPENCODE_CONFIG env or .opencode/ exists)
|
|
34
|
+
* 3. Codex detection (CODEX_HOME env or .codex/ exists)
|
|
35
|
+
* 4. Default to .claude (Claude Code or unknown)
|
|
36
|
+
*
|
|
37
|
+
* @param {string} [basePath=process.cwd()] - Base path to check for project directories
|
|
38
|
+
* @returns {string} State directory name (e.g., '.claude', '.opencode', '.codex')
|
|
39
|
+
*/
|
|
40
|
+
function getStateDir(basePath = process.cwd()) {
|
|
41
|
+
// Check user override first
|
|
42
|
+
if (process.env.AI_STATE_DIR) {
|
|
43
|
+
return process.env.AI_STATE_DIR;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const cacheKey = path.resolve(basePath);
|
|
47
|
+
const cached = _cachedStateDirs.get(cacheKey);
|
|
48
|
+
if (cached) {
|
|
49
|
+
return cached;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// OpenCode detection
|
|
53
|
+
if (process.env.OPENCODE_CONFIG || process.env.OPENCODE_CONFIG_DIR) {
|
|
54
|
+
_cachedStateDirs.set(cacheKey, '.opencode');
|
|
55
|
+
return '.opencode';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Check for .opencode directory in project
|
|
59
|
+
try {
|
|
60
|
+
const opencodePath = path.join(basePath, '.opencode');
|
|
61
|
+
if (isDirectory(opencodePath)) {
|
|
62
|
+
_cachedStateDirs.set(cacheKey, '.opencode');
|
|
63
|
+
return '.opencode';
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Ignore errors, continue detection
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Codex detection
|
|
70
|
+
if (process.env.CODEX_HOME) {
|
|
71
|
+
_cachedStateDirs.set(cacheKey, '.codex');
|
|
72
|
+
return '.codex';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check for .codex directory in project
|
|
76
|
+
try {
|
|
77
|
+
const codexPath = path.join(basePath, '.codex');
|
|
78
|
+
if (isDirectory(codexPath)) {
|
|
79
|
+
_cachedStateDirs.set(cacheKey, '.codex');
|
|
80
|
+
return '.codex';
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Ignore errors, continue detection
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Default to Claude Code
|
|
87
|
+
_cachedStateDirs.set(cacheKey, '.claude');
|
|
88
|
+
return '.claude';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get the full path to the state directory
|
|
93
|
+
* @param {string} [basePath=process.cwd()] - Base path
|
|
94
|
+
* @returns {string} Full path to state directory
|
|
95
|
+
*/
|
|
96
|
+
function getStateDirPath(basePath = process.cwd()) {
|
|
97
|
+
return path.join(basePath, getStateDir(basePath));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Get the detected platform name
|
|
102
|
+
* @param {string} [basePath=process.cwd()] - Base path
|
|
103
|
+
* @returns {string} Platform name ('claude', 'opencode', 'codex', or 'custom')
|
|
104
|
+
*/
|
|
105
|
+
function getPlatformName(basePath = process.cwd()) {
|
|
106
|
+
const stateDir = getStateDir(basePath);
|
|
107
|
+
|
|
108
|
+
if (process.env.AI_STATE_DIR) {
|
|
109
|
+
return 'custom';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
switch (stateDir) {
|
|
113
|
+
case '.opencode': return 'opencode';
|
|
114
|
+
case '.codex': return 'codex';
|
|
115
|
+
case '.claude': return 'claude';
|
|
116
|
+
default: return 'unknown';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Clear the cached state directory (useful for testing)
|
|
122
|
+
*/
|
|
123
|
+
function clearCache() {
|
|
124
|
+
_cachedStateDirs.clear();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
module.exports = {
|
|
128
|
+
getStateDir,
|
|
129
|
+
getStateDirPath,
|
|
130
|
+
getPlatformName,
|
|
131
|
+
clearCache
|
|
132
|
+
};
|