@a3t/rapid-core 0.1.12 → 0.1.14

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/dist/index.d.ts CHANGED
@@ -10,6 +10,14 @@ interface RapidConfig {
10
10
  agents: AgentsConfig;
11
11
  context?: ContextConfig;
12
12
  mcp?: McpConfig;
13
+ lima?: LimaConfig;
14
+ }
15
+ /**
16
+ * Lima VM configuration
17
+ */
18
+ interface LimaConfig {
19
+ /** Install GitHub CLI (gh) in the VM (default: true) */
20
+ installGh?: boolean;
13
21
  }
14
22
  interface ContainerConfig {
15
23
  devcontainer?: string;
@@ -90,14 +98,26 @@ interface McpConfig {
90
98
  configFile?: string;
91
99
  servers?: Record<string, McpServerConfig>;
92
100
  }
101
+ /**
102
+ * MCP transport types following the MCP specification.
103
+ * - 'stdio': Local subprocess communication (MCP spec standard)
104
+ * - 'streamable-http': Remote HTTP communication (MCP spec standard, replaces deprecated SSE)
105
+ * - 'remote': Alias for 'streamable-http' (user-friendly, backwards compatible)
106
+ */
107
+ type McpTransportType = 'stdio' | 'streamable-http' | 'remote';
93
108
  interface McpServerConfig {
94
109
  /** Enable this MCP server (default: true) */
95
110
  enabled?: boolean;
96
- /** Server type: remote HTTP or local stdio */
97
- type?: 'remote' | 'stdio';
98
- /** URL for remote servers */
111
+ /**
112
+ * Server transport type (MCP spec naming):
113
+ * - 'stdio': Local subprocess (command + args)
114
+ * - 'streamable-http': Remote HTTP server (url + headers)
115
+ * - 'remote': Alias for 'streamable-http' (backwards compatible)
116
+ */
117
+ type?: McpTransportType;
118
+ /** URL for remote/streamable-http servers */
99
119
  url?: string;
100
- /** HTTP headers for remote servers */
120
+ /** HTTP headers for remote/streamable-http servers */
101
121
  headers?: Record<string, string>;
102
122
  /** Command for stdio servers */
103
123
  command?: string;
@@ -212,6 +232,8 @@ declare function buildAgentArgs(agent: AgentDefinition, options?: {
212
232
  compactPrompt?: boolean;
213
233
  /** Custom system prompt to use instead of default RAPID methodology */
214
234
  customPrompt?: string;
235
+ /** Additional context content to append to the system prompt */
236
+ contextContent?: string;
215
237
  }): string[];
216
238
  /**
217
239
  * Check if an agent reads instruction files from the filesystem.
@@ -639,7 +661,7 @@ declare function getSecretReferences(templateNames: string[]): Record<string, st
639
661
  */
640
662
  interface McpServerDefinition extends McpServerConfig {
641
663
  enabled?: boolean;
642
- type?: 'remote' | 'stdio';
664
+ type?: 'remote' | 'stdio' | 'streamable-http';
643
665
  url?: string;
644
666
  headers?: Record<string, string>;
645
667
  command?: string;
@@ -648,6 +670,7 @@ interface McpServerDefinition extends McpServerConfig {
648
670
  }
649
671
  /**
650
672
  * MCP server info for display
673
+ * Note: 'streamable-http' is normalized to 'remote' for display purposes
651
674
  */
652
675
  interface McpServerInfo {
653
676
  name: string;
@@ -691,10 +714,11 @@ interface OpenCodeConfig {
691
714
  instructions?: string[];
692
715
  }
693
716
  /**
694
- * OpenCode MCP entry format
717
+ * OpenCode MCP entry format.
718
+ * OpenCode uses 'local' for stdio and 'remote' for HTTP servers.
695
719
  */
696
720
  interface OpenCodeMcpEntry {
697
- type: 'remote' | 'stdio';
721
+ type: 'local' | 'remote';
698
722
  url?: string | undefined;
699
723
  headers?: Record<string, string> | undefined;
700
724
  command?: string | undefined;
@@ -730,11 +754,22 @@ declare function enableMcpServer(config: RapidConfig, name: string): RapidConfig
730
754
  */
731
755
  declare function disableMcpServer(config: RapidConfig, name: string): RapidConfig;
732
756
  /**
733
- * Generate .mcp.json config from rapid.json mcp section
757
+ * Generate .mcp.json config from rapid.json mcp section.
758
+ *
759
+ * Output format follows Claude Code conventions:
760
+ * - 'stdio' servers use type: 'stdio' with command/args/env
761
+ * - Remote servers (type: 'remote' or 'streamable-http') use type: 'http' with url/headers
762
+ *
763
+ * Note: Claude Code uses 'http' instead of MCP spec's 'streamable-http' for simplicity.
734
764
  */
735
765
  declare function generateMcpConfig(config: RapidConfig): GeneratedMcpConfig;
736
766
  /**
737
- * Generate opencode.json config format
767
+ * Generate opencode.json config format.
768
+ *
769
+ * Output format follows OpenCode conventions:
770
+ * - 'stdio' servers use type: 'local' with command/args/env
771
+ * - Remote servers (type: 'remote' or 'streamable-http') use type: 'remote' with url/headers
772
+ * - Environment variables use {env:VAR} format instead of ${VAR}
738
773
  */
739
774
  declare function generateOpenCodeConfig(config: RapidConfig): OpenCodeConfig;
740
775
  /**
@@ -758,4 +793,97 @@ declare function readMcpConfig(rootDir: string, config?: RapidConfig): Promise<G
758
793
  */
759
794
  declare function getMcpConfigPath(rootDir: string, config?: RapidConfig): string;
760
795
 
761
- export { type AgentDefinition, type AgentStatus, type AgentsConfig, type AuthStatus, CODE_EDITING_GUIDELINES, COMMUNICATION_GUIDELINES, type ContainerConfig, type ContainerStatus, type ContextConfig, DEBUGGING_GUIDELINES, type DetectedCredential, type DevcontainerConfig, type DotenvConfig, type EnvironmentStatus, type EnvrcConfig, type ExternalAuthConfig, type ExternalAuthSource, GIT_GUIDELINES, type GeneratedMcpConfig, type LoadedConfig, type LogLevel, MCP_SERVER_TEMPLATES, MCP_USAGE_GUIDELINES, type McpConfig, type McpServerConfig, type McpServerDefinition, type McpServerEntry, type McpServerInfo, type McpServerStatus, type McpServerTemplate, type OpAuthStatus, type OpenCodeConfig, type OpenCodeMcpEntry, RAPID_METHODOLOGY, RAPID_METHODOLOGY_COMPACT, RAPID_PHASES, type RapidConfig, type RapidPhase, type SecretStatus, type SecretsConfig, type SecretsStatus, addMcpServer, addMcpServerFromTemplate, agentReadsInstructionFiles, agentSupportsRuntimeInjection, buildAgentArgs, checkAgentAvailable, checkAllAgents, detectAiderAuth, detectAllCredentials, detectClaudeCodeAuth, detectCodexAuth, detectEnvAuth, detectGeminiAuth, disableMcpServer, enableMcpServer, execInContainer, formatAuthStatus, generateEnvrc, generateFullSystemPrompt, generateMcpConfig, generateOpenCodeConfig, generateRapidMethodology, getAgent, getAuthEnvironment, getAuthStatus, getContainerName, getContainerStatus, getCredentialsForProvider, getDefaultAgent, getDefaultConfig, getDevcontainerPath, getEasySetupTemplates, getLogLevel, getMcpConfigPath, getMcpServerStatus, getMcpServers, getMcpTemplate, getMcpTemplateNames, getOpAuthStatus, getProviderInfo, getRequiredSecrets, getSecretReferences, getStandardAgentInstructions, hasDevcontainerCli, hasDocker, hasEnvrc, hasMcpConfig, hasOpCli, hasOpServiceAccountToken, hasVaultCli, isOpAuthenticated, isVaultAuthenticated, launchAgent, loadConfig, loadConfigFromFile, loadDevcontainerConfig, loadSecrets, logger, mergeWithDefaults, readEnvrc, readMcpConfig, readOpSecret, readVaultSecret, removeMcpServer, setLogLevel, startContainer, stopContainer, verifySecret, verifySecrets, writeEnvrc, writeMcpConfig, writeOpenCodeConfig };
796
+ /**
797
+ * JSON formatting utilities using Prettier
798
+ */
799
+ /**
800
+ * Format JSON using Prettier for consistent output
801
+ * @param data - The data to format as JSON
802
+ * @returns Formatted JSON string with trailing newline
803
+ */
804
+ declare function formatJson(data: unknown): Promise<string>;
805
+ /**
806
+ * Synchronous JSON formatting (fallback to JSON.stringify if prettier fails)
807
+ * @param data - The data to format as JSON
808
+ * @returns Formatted JSON string with trailing newline
809
+ */
810
+ declare function formatJsonSync(data: unknown): string;
811
+
812
+ /**
813
+ * Context file assembly for AI agents
814
+ *
815
+ * Reads and assembles context from files and directories specified in rapid.json,
816
+ * then formats it for injection into agent system prompts.
817
+ */
818
+
819
+ /**
820
+ * Result of reading a context file
821
+ */
822
+ interface ContextFileResult {
823
+ path: string;
824
+ relativePath: string;
825
+ content: string;
826
+ size: number;
827
+ truncated: boolean;
828
+ }
829
+ /**
830
+ * Information about a skipped file
831
+ */
832
+ interface SkippedFile {
833
+ path: string;
834
+ reason: 'missing' | 'binary' | 'too-large' | 'excluded' | 'directory' | 'error';
835
+ error?: string;
836
+ }
837
+ /**
838
+ * Result of assembling all context files
839
+ */
840
+ interface AssembledContext {
841
+ files: ContextFileResult[];
842
+ totalSize: number;
843
+ skippedFiles: SkippedFile[];
844
+ content: string;
845
+ }
846
+ /**
847
+ * Options for context assembly
848
+ */
849
+ interface ContextAssemblyOptions {
850
+ /** Maximum size per file in bytes (default: 100KB) */
851
+ maxFileSize?: number;
852
+ /** Maximum total size in bytes (default: 500KB) */
853
+ maxTotalSize?: number;
854
+ /** Whether to include file headers with paths (default: true) */
855
+ includeHeaders?: boolean;
856
+ /** File extensions to treat as binary (in addition to built-in list) */
857
+ binaryExtensions?: string[];
858
+ }
859
+ /**
860
+ * Check if a file path matches any exclude patterns
861
+ */
862
+ declare function matchesExcludePattern(relativePath: string, excludePatterns: string[]): boolean;
863
+ /**
864
+ * Check if a file is likely binary based on extension
865
+ */
866
+ declare function isBinaryFile(filepath: string, additionalExtensions?: string[]): boolean;
867
+ /**
868
+ * Read a single context file with safety checks
869
+ */
870
+ declare function readContextFile(filepath: string, rootDir: string, options?: ContextAssemblyOptions): Promise<ContextFileResult | SkippedFile>;
871
+ /**
872
+ * Recursively read files from a directory
873
+ */
874
+ declare function readContextDirectory(dirPath: string, rootDir: string, excludePatterns: string[], options?: ContextAssemblyOptions): Promise<{
875
+ files: ContextFileResult[];
876
+ skipped: SkippedFile[];
877
+ }>;
878
+ /**
879
+ * Assemble all context files from configuration
880
+ */
881
+ declare function assembleContext(rootDir: string, config: ContextConfig, options?: ContextAssemblyOptions): Promise<AssembledContext>;
882
+ /**
883
+ * Format assembled context as a string for injection
884
+ */
885
+ declare function formatContextContent(files: ContextFileResult[], options?: {
886
+ includeHeaders?: boolean;
887
+ }): string;
888
+
889
+ export { type AgentDefinition, type AgentStatus, type AgentsConfig, type AssembledContext, type AuthStatus, CODE_EDITING_GUIDELINES, COMMUNICATION_GUIDELINES, type ContainerConfig, type ContainerStatus, type ContextAssemblyOptions, type ContextConfig, type ContextFileResult, DEBUGGING_GUIDELINES, type DetectedCredential, type DevcontainerConfig, type DotenvConfig, type EnvironmentStatus, type EnvrcConfig, type ExternalAuthConfig, type ExternalAuthSource, GIT_GUIDELINES, type GeneratedMcpConfig, type LoadedConfig, type LogLevel, MCP_SERVER_TEMPLATES, MCP_USAGE_GUIDELINES, type McpConfig, type McpServerConfig, type McpServerDefinition, type McpServerEntry, type McpServerInfo, type McpServerStatus, type McpServerTemplate, type McpTransportType, type OpAuthStatus, type OpenCodeConfig, type OpenCodeMcpEntry, RAPID_METHODOLOGY, RAPID_METHODOLOGY_COMPACT, RAPID_PHASES, type RapidConfig, type RapidPhase, type SecretStatus, type SecretsConfig, type SecretsStatus, type SkippedFile, addMcpServer, addMcpServerFromTemplate, agentReadsInstructionFiles, agentSupportsRuntimeInjection, assembleContext, buildAgentArgs, checkAgentAvailable, checkAllAgents, detectAiderAuth, detectAllCredentials, detectClaudeCodeAuth, detectCodexAuth, detectEnvAuth, detectGeminiAuth, disableMcpServer, enableMcpServer, execInContainer, formatAuthStatus, formatContextContent, formatJson, formatJsonSync, generateEnvrc, generateFullSystemPrompt, generateMcpConfig, generateOpenCodeConfig, generateRapidMethodology, getAgent, getAuthEnvironment, getAuthStatus, getContainerName, getContainerStatus, getCredentialsForProvider, getDefaultAgent, getDefaultConfig, getDevcontainerPath, getEasySetupTemplates, getLogLevel, getMcpConfigPath, getMcpServerStatus, getMcpServers, getMcpTemplate, getMcpTemplateNames, getOpAuthStatus, getProviderInfo, getRequiredSecrets, getSecretReferences, getStandardAgentInstructions, hasDevcontainerCli, hasDocker, hasEnvrc, hasMcpConfig, hasOpCli, hasOpServiceAccountToken, hasVaultCli, isBinaryFile, isOpAuthenticated, isVaultAuthenticated, launchAgent, loadConfig, loadConfigFromFile, loadDevcontainerConfig, loadSecrets, logger, matchesExcludePattern, mergeWithDefaults, readContextDirectory, readContextFile, readEnvrc, readMcpConfig, readOpSecret, readVaultSecret, removeMcpServer, setLogLevel, startContainer, stopContainer, verifySecret, verifySecrets, writeEnvrc, writeMcpConfig, writeOpenCodeConfig };
package/dist/index.js CHANGED
@@ -74,6 +74,9 @@ function getDefaultConfig() {
74
74
  files: ["README.md"],
75
75
  dirs: ["docs/"],
76
76
  generateAgentFiles: true
77
+ },
78
+ lima: {
79
+ installGh: true
77
80
  }
78
81
  };
79
82
  }
@@ -97,6 +100,10 @@ function mergeWithDefaults(config) {
97
100
  context: {
98
101
  ...defaults.context,
99
102
  ...config.context
103
+ },
104
+ lima: {
105
+ ...defaults.lima,
106
+ ...config.lima
100
107
  }
101
108
  };
102
109
  }
@@ -1003,7 +1010,10 @@ function buildAgentArgs(agent, options) {
1003
1010
  if (options?.compactPrompt) {
1004
1011
  instructionOptions.compact = true;
1005
1012
  }
1006
- const promptContent = options?.customPrompt ?? getStandardAgentInstructions(instructionOptions);
1013
+ let promptContent = options?.customPrompt ?? getStandardAgentInstructions(instructionOptions);
1014
+ if (options?.contextContent) {
1015
+ promptContent = promptContent + "\n\n" + options.contextContent;
1016
+ }
1007
1017
  const pattern = agent.systemPromptArg;
1008
1018
  if (pattern.includes("{prompt}")) {
1009
1019
  const parts = pattern.split(/\s+/);
@@ -1675,7 +1685,24 @@ function getSecretReferences(templateNames) {
1675
1685
  return refs;
1676
1686
  }
1677
1687
 
1688
+ // src/format.ts
1689
+ import prettier from "prettier";
1690
+ async function formatJson(data) {
1691
+ const json = JSON.stringify(data);
1692
+ return prettier.format(json, {
1693
+ parser: "json",
1694
+ printWidth: 80,
1695
+ tabWidth: 2
1696
+ });
1697
+ }
1698
+ function formatJsonSync(data) {
1699
+ return JSON.stringify(data, null, 2) + "\n";
1700
+ }
1701
+
1678
1702
  // src/mcp.ts
1703
+ function isRemoteType(type) {
1704
+ return type === "remote" || type === "streamable-http";
1705
+ }
1679
1706
  function getMcpServers(config) {
1680
1707
  const servers = [];
1681
1708
  if (!config.mcp?.servers) {
@@ -1687,10 +1714,12 @@ function getMcpServers(config) {
1687
1714
  }
1688
1715
  const def = serverConfig;
1689
1716
  const template = getMcpTemplate(name);
1717
+ const rawType = def.type ?? template?.type ?? "stdio";
1718
+ const displayType = isRemoteType(rawType) ? "remote" : "stdio";
1690
1719
  servers.push({
1691
1720
  name,
1692
1721
  enabled: def.enabled !== false,
1693
- type: def.type ?? template?.type ?? "stdio",
1722
+ type: displayType,
1694
1723
  url: def.url ?? template?.url,
1695
1724
  command: def.command ?? template?.command,
1696
1725
  template: template ? name : void 0
@@ -1812,7 +1841,7 @@ function generateMcpConfig(config) {
1812
1841
  const template = getMcpTemplate(name);
1813
1842
  const entry = {};
1814
1843
  const type = def.type ?? template?.type ?? "stdio";
1815
- if (type === "remote") {
1844
+ if (isRemoteType(type)) {
1816
1845
  entry.type = "http";
1817
1846
  entry.url = def.url ?? template?.url;
1818
1847
  entry.headers = def.headers ?? template?.headers;
@@ -1850,10 +1879,11 @@ function generateOpenCodeConfig(config) {
1850
1879
  }
1851
1880
  const template = getMcpTemplate(name);
1852
1881
  const type = def.type ?? template?.type ?? "stdio";
1882
+ const openCodeType = isRemoteType(type) ? "remote" : "local";
1853
1883
  const entry = {
1854
- type
1884
+ type: openCodeType
1855
1885
  };
1856
- if (type === "remote") {
1886
+ if (isRemoteType(type)) {
1857
1887
  entry.url = def.url ?? template?.url;
1858
1888
  const headers = def.headers ?? template?.headers;
1859
1889
  if (headers) {
@@ -1878,12 +1908,12 @@ async function writeMcpConfig(rootDir, config) {
1878
1908
  const mcpConfig = generateMcpConfig(config);
1879
1909
  const configFile = config.mcp?.configFile ?? ".mcp.json";
1880
1910
  const configPath = isAbsolute(configFile) ? configFile : join4(rootDir, configFile);
1881
- await writeFile2(configPath, JSON.stringify(mcpConfig, null, 2) + "\n", "utf-8");
1911
+ await writeFile2(configPath, await formatJson(mcpConfig), "utf-8");
1882
1912
  }
1883
1913
  async function writeOpenCodeConfig(rootDir, config) {
1884
1914
  const openCodeConfig = generateOpenCodeConfig(config);
1885
1915
  const configPath = join4(rootDir, "opencode.json");
1886
- await writeFile2(configPath, JSON.stringify(openCodeConfig, null, 2) + "\n", "utf-8");
1916
+ await writeFile2(configPath, await formatJson(openCodeConfig), "utf-8");
1887
1917
  }
1888
1918
  async function hasMcpConfig(rootDir, config) {
1889
1919
  const configFile = config?.mcp?.configFile ?? ".mcp.json";
@@ -1909,6 +1939,286 @@ function getMcpConfigPath(rootDir, config) {
1909
1939
  const configFile = config?.mcp?.configFile ?? ".mcp.json";
1910
1940
  return isAbsolute(configFile) ? configFile : join4(rootDir, configFile);
1911
1941
  }
1942
+
1943
+ // src/context.ts
1944
+ import { readFile as readFile6, readdir, access as access4, stat } from "fs/promises";
1945
+ import { join as join5, relative, extname } from "path";
1946
+ import { minimatch } from "minimatch";
1947
+ var DEFAULT_MAX_FILE_SIZE = 100 * 1024;
1948
+ var DEFAULT_MAX_TOTAL_SIZE = 500 * 1024;
1949
+ var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([
1950
+ // Images
1951
+ ".png",
1952
+ ".jpg",
1953
+ ".jpeg",
1954
+ ".gif",
1955
+ ".bmp",
1956
+ ".ico",
1957
+ ".webp",
1958
+ ".svg",
1959
+ ".tiff",
1960
+ ".tif",
1961
+ // Documents
1962
+ ".pdf",
1963
+ ".doc",
1964
+ ".docx",
1965
+ ".xls",
1966
+ ".xlsx",
1967
+ ".ppt",
1968
+ ".pptx",
1969
+ ".odt",
1970
+ ".ods",
1971
+ ".odp",
1972
+ // Archives
1973
+ ".zip",
1974
+ ".tar",
1975
+ ".gz",
1976
+ ".bz2",
1977
+ ".7z",
1978
+ ".rar",
1979
+ ".xz",
1980
+ // Executables
1981
+ ".exe",
1982
+ ".dll",
1983
+ ".so",
1984
+ ".dylib",
1985
+ ".bin",
1986
+ ".app",
1987
+ // Fonts
1988
+ ".woff",
1989
+ ".woff2",
1990
+ ".ttf",
1991
+ ".eot",
1992
+ ".otf",
1993
+ // Media
1994
+ ".mp3",
1995
+ ".mp4",
1996
+ ".wav",
1997
+ ".avi",
1998
+ ".mov",
1999
+ ".mkv",
2000
+ ".webm",
2001
+ ".ogg",
2002
+ ".flac",
2003
+ // Data
2004
+ ".db",
2005
+ ".sqlite",
2006
+ ".sqlite3",
2007
+ // Compiled
2008
+ ".wasm",
2009
+ ".pyc",
2010
+ ".pyo",
2011
+ ".class",
2012
+ ".o",
2013
+ ".obj",
2014
+ // Lock files (often binary-ish)
2015
+ ".lock",
2016
+ // Package manager
2017
+ "package-lock.json",
2018
+ "pnpm-lock.yaml",
2019
+ "yarn.lock"
2020
+ ]);
2021
+ function matchesExcludePattern(relativePath, excludePatterns) {
2022
+ for (const pattern of excludePatterns) {
2023
+ if (minimatch(relativePath, pattern, { dot: true })) {
2024
+ return true;
2025
+ }
2026
+ }
2027
+ return false;
2028
+ }
2029
+ function isBinaryFile(filepath, additionalExtensions) {
2030
+ const ext = extname(filepath).toLowerCase();
2031
+ const filename = filepath.split("/").pop() ?? "";
2032
+ if (BINARY_EXTENSIONS.has(ext)) {
2033
+ return true;
2034
+ }
2035
+ if (BINARY_EXTENSIONS.has(filename)) {
2036
+ return true;
2037
+ }
2038
+ if (additionalExtensions) {
2039
+ for (const addExt of additionalExtensions) {
2040
+ if (ext === addExt || filename === addExt) {
2041
+ return true;
2042
+ }
2043
+ }
2044
+ }
2045
+ return false;
2046
+ }
2047
+ async function readContextFile(filepath, rootDir, options = {}) {
2048
+ const maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
2049
+ const absolutePath = filepath.startsWith("/") ? filepath : join5(rootDir, filepath);
2050
+ const relativePath = relative(rootDir, absolutePath);
2051
+ try {
2052
+ await access4(absolutePath);
2053
+ const stats = await stat(absolutePath);
2054
+ if (stats.isDirectory()) {
2055
+ return { path: absolutePath, reason: "directory" };
2056
+ }
2057
+ if (stats.size > maxFileSize) {
2058
+ logger.debug(
2059
+ `Skipping ${relativePath}: exceeds max file size (${stats.size} > ${maxFileSize})`
2060
+ );
2061
+ return { path: absolutePath, reason: "too-large" };
2062
+ }
2063
+ if (isBinaryFile(absolutePath, options.binaryExtensions)) {
2064
+ logger.debug(`Skipping ${relativePath}: binary file extension`);
2065
+ return { path: absolutePath, reason: "binary" };
2066
+ }
2067
+ const content = await readFile6(absolutePath, "utf-8");
2068
+ if (content.includes("\0")) {
2069
+ logger.debug(`Skipping ${relativePath}: contains null bytes`);
2070
+ return { path: absolutePath, reason: "binary" };
2071
+ }
2072
+ return {
2073
+ path: absolutePath,
2074
+ relativePath,
2075
+ content,
2076
+ size: stats.size,
2077
+ truncated: false
2078
+ };
2079
+ } catch (error) {
2080
+ if (error.code === "ENOENT") {
2081
+ logger.debug(`Skipping ${relativePath}: file not found`);
2082
+ return { path: absolutePath, reason: "missing" };
2083
+ }
2084
+ logger.debug(
2085
+ `Skipping ${relativePath}: ${error instanceof Error ? error.message : String(error)}`
2086
+ );
2087
+ return {
2088
+ path: absolutePath,
2089
+ reason: "error",
2090
+ error: error instanceof Error ? error.message : String(error)
2091
+ };
2092
+ }
2093
+ }
2094
+ async function readContextDirectory(dirPath, rootDir, excludePatterns, options = {}) {
2095
+ const files = [];
2096
+ const skipped = [];
2097
+ const absolutePath = dirPath.startsWith("/") ? dirPath : join5(rootDir, dirPath);
2098
+ try {
2099
+ await access4(absolutePath);
2100
+ const stats = await stat(absolutePath);
2101
+ if (!stats.isDirectory()) {
2102
+ const result = await readContextFile(absolutePath, rootDir, options);
2103
+ if ("content" in result) {
2104
+ files.push(result);
2105
+ } else {
2106
+ skipped.push(result);
2107
+ }
2108
+ return { files, skipped };
2109
+ }
2110
+ const entries = await readdir(absolutePath, { withFileTypes: true });
2111
+ for (const entry of entries) {
2112
+ const entryPath = join5(absolutePath, entry.name);
2113
+ const relativePath = relative(rootDir, entryPath);
2114
+ if (matchesExcludePattern(relativePath, excludePatterns)) {
2115
+ skipped.push({ path: entryPath, reason: "excluded" });
2116
+ continue;
2117
+ }
2118
+ if (entry.isDirectory()) {
2119
+ const subResult = await readContextDirectory(entryPath, rootDir, excludePatterns, options);
2120
+ files.push(...subResult.files);
2121
+ skipped.push(...subResult.skipped);
2122
+ } else if (entry.isFile()) {
2123
+ const result = await readContextFile(entryPath, rootDir, options);
2124
+ if ("content" in result) {
2125
+ files.push(result);
2126
+ } else {
2127
+ skipped.push(result);
2128
+ }
2129
+ }
2130
+ }
2131
+ } catch (error) {
2132
+ if (error.code === "ENOENT") {
2133
+ logger.debug(`Directory not found: ${absolutePath}`);
2134
+ } else {
2135
+ logger.debug(
2136
+ `Error reading directory ${absolutePath}: ${error instanceof Error ? error.message : String(error)}`
2137
+ );
2138
+ }
2139
+ }
2140
+ return { files, skipped };
2141
+ }
2142
+ async function assembleContext(rootDir, config, options = {}) {
2143
+ const maxTotalSize = options.maxTotalSize ?? DEFAULT_MAX_TOTAL_SIZE;
2144
+ const excludePatterns = config.exclude ?? [];
2145
+ const allFiles = [];
2146
+ const skippedFiles = [];
2147
+ let totalSize = 0;
2148
+ if (config.files && config.files.length > 0) {
2149
+ for (const filePath of config.files) {
2150
+ const relativePath = filePath;
2151
+ if (matchesExcludePattern(relativePath, excludePatterns)) {
2152
+ skippedFiles.push({ path: filePath, reason: "excluded" });
2153
+ continue;
2154
+ }
2155
+ const result = await readContextFile(filePath, rootDir, options);
2156
+ if ("content" in result) {
2157
+ if (totalSize + result.size > maxTotalSize) {
2158
+ logger.debug(`Skipping ${filePath}: would exceed total size limit`);
2159
+ skippedFiles.push({ path: filePath, reason: "too-large" });
2160
+ continue;
2161
+ }
2162
+ totalSize += result.size;
2163
+ allFiles.push(result);
2164
+ } else {
2165
+ skippedFiles.push(result);
2166
+ }
2167
+ }
2168
+ }
2169
+ if (config.dirs && config.dirs.length > 0) {
2170
+ for (const dirPath of config.dirs) {
2171
+ const { files, skipped } = await readContextDirectory(
2172
+ dirPath,
2173
+ rootDir,
2174
+ excludePatterns,
2175
+ options
2176
+ );
2177
+ for (const file of files) {
2178
+ if (totalSize + file.size > maxTotalSize) {
2179
+ logger.debug(`Skipping ${file.relativePath}: would exceed total size limit`);
2180
+ skippedFiles.push({ path: file.path, reason: "too-large" });
2181
+ continue;
2182
+ }
2183
+ totalSize += file.size;
2184
+ allFiles.push(file);
2185
+ }
2186
+ skippedFiles.push(...skipped);
2187
+ }
2188
+ }
2189
+ const content = formatContextContent(allFiles, options);
2190
+ return {
2191
+ files: allFiles,
2192
+ totalSize,
2193
+ skippedFiles,
2194
+ content
2195
+ };
2196
+ }
2197
+ function formatContextContent(files, options = {}) {
2198
+ const includeHeaders = options.includeHeaders ?? true;
2199
+ if (files.length === 0) {
2200
+ return "";
2201
+ }
2202
+ const sections = [
2203
+ "## Project Context Files",
2204
+ "",
2205
+ "The following files provide additional context about the project:",
2206
+ ""
2207
+ ];
2208
+ for (const file of files) {
2209
+ if (includeHeaders) {
2210
+ sections.push(`### ${file.relativePath}`);
2211
+ sections.push("```");
2212
+ sections.push(file.content.trim());
2213
+ sections.push("```");
2214
+ sections.push("");
2215
+ } else {
2216
+ sections.push(file.content.trim());
2217
+ sections.push("");
2218
+ }
2219
+ }
2220
+ return sections.join("\n");
2221
+ }
1912
2222
  export {
1913
2223
  CODE_EDITING_GUIDELINES,
1914
2224
  COMMUNICATION_GUIDELINES,
@@ -1923,6 +2233,7 @@ export {
1923
2233
  addMcpServerFromTemplate,
1924
2234
  agentReadsInstructionFiles,
1925
2235
  agentSupportsRuntimeInjection,
2236
+ assembleContext,
1926
2237
  buildAgentArgs,
1927
2238
  checkAgentAvailable,
1928
2239
  checkAllAgents,
@@ -1936,6 +2247,9 @@ export {
1936
2247
  enableMcpServer,
1937
2248
  execInContainer,
1938
2249
  formatAuthStatus,
2250
+ formatContextContent,
2251
+ formatJson,
2252
+ formatJsonSync,
1939
2253
  generateEnvrc,
1940
2254
  generateFullSystemPrompt,
1941
2255
  generateMcpConfig,
@@ -1969,6 +2283,7 @@ export {
1969
2283
  hasOpCli,
1970
2284
  hasOpServiceAccountToken,
1971
2285
  hasVaultCli,
2286
+ isBinaryFile,
1972
2287
  isOpAuthenticated,
1973
2288
  isVaultAuthenticated,
1974
2289
  launchAgent,
@@ -1977,7 +2292,10 @@ export {
1977
2292
  loadDevcontainerConfig,
1978
2293
  loadSecrets,
1979
2294
  logger,
2295
+ matchesExcludePattern,
1980
2296
  mergeWithDefaults,
2297
+ readContextDirectory,
2298
+ readContextFile,
1981
2299
  readEnvrc,
1982
2300
  readMcpConfig,
1983
2301
  readOpSecret,