@iloom/cli 0.3.1 → 0.3.3
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/README.md +8 -6
- package/dist/{BranchNamingService-OMWKUYMM.js → BranchNamingService-A77VI6AI.js} +2 -2
- package/dist/ClaudeContextManager-BN7RE5ZQ.js +15 -0
- package/dist/ClaudeService-DLYLJUPA.js +14 -0
- package/dist/{GitHubService-EBOETDIW.js → GitHubService-FZHHBOFG.js} +3 -3
- package/dist/{LoomLauncher-JF7JZMTZ.js → LoomLauncher-ZV3ZZIBA.js} +40 -26
- package/dist/LoomLauncher-ZV3ZZIBA.js.map +1 -0
- package/dist/{PromptTemplateManager-A52RUAMS.js → PromptTemplateManager-6HH3PVXV.js} +2 -2
- package/dist/README.md +8 -6
- package/dist/{SettingsManager-ZCWJ56WP.js → SettingsManager-I2LRCW2A.js} +2 -2
- package/dist/{SettingsMigrationManager-AGIIIPDQ.js → SettingsMigrationManager-TJ7UWZG5.js} +3 -3
- package/dist/agents/iloom-issue-complexity-evaluator.md +18 -3
- package/dist/agents/iloom-issue-enhancer.md +1 -1
- package/dist/{chunk-TSKY3JI7.js → chunk-2CXREBLZ.js} +2 -2
- package/dist/{chunk-HBYZH6GD.js → chunk-2IJEMXOB.js} +431 -128
- package/dist/chunk-2IJEMXOB.js.map +1 -0
- package/dist/{chunk-IXKLYTWO.js → chunk-2MAIX45J.js} +8 -8
- package/dist/{chunk-4BGK7T6X.js → chunk-5Q3NDNNV.js} +48 -8
- package/dist/chunk-5Q3NDNNV.js.map +1 -0
- package/dist/{chunk-JQFO7QQN.js → chunk-5VK4NRSF.js} +3 -3
- package/dist/{chunk-JQFO7QQN.js.map → chunk-5VK4NRSF.js.map} +1 -1
- package/dist/{chunk-XPKDPZ5D.js → chunk-AKUJXDNW.js} +2 -2
- package/dist/{chunk-O5OH5MRX.js → chunk-CDZERT7Z.js} +23 -11
- package/dist/chunk-CDZERT7Z.js.map +1 -0
- package/dist/{chunk-JKXJ7BGL.js → chunk-CE26YH2U.js} +42 -3
- package/dist/chunk-CE26YH2U.js.map +1 -0
- package/dist/{chunk-ZZZWQGTS.js → chunk-CFFQ2Z7A.js} +74 -75
- package/dist/chunk-CFFQ2Z7A.js.map +1 -0
- package/dist/{chunk-RO26VS3W.js → chunk-DLHA5VQ3.js} +174 -5
- package/dist/chunk-DLHA5VQ3.js.map +1 -0
- package/dist/{chunk-ZBQVSHVT.js → chunk-IFB4Z76W.js} +35 -10
- package/dist/chunk-IFB4Z76W.js.map +1 -0
- package/dist/{chunk-G2IEYOLQ.js → chunk-M7JJCX53.js} +17 -2
- package/dist/chunk-M7JJCX53.js.map +1 -0
- package/dist/{chunk-KLBYVHPK.js → chunk-OSCLCMDG.js} +2 -2
- package/dist/chunk-OXAM2WVC.js +68 -0
- package/dist/chunk-OXAM2WVC.js.map +1 -0
- package/dist/{chunk-ZWFBBPJI.js → chunk-OYF4VIFI.js} +5 -3
- package/dist/chunk-OYF4VIFI.js.map +1 -0
- package/dist/{chunk-U5QDY7ZD.js → chunk-PGPI5LR4.js} +8 -8
- package/dist/{chunk-WEN5C5DM.js → chunk-RIEO2WML.js} +4 -1
- package/dist/chunk-RIEO2WML.js.map +1 -0
- package/dist/{chunk-ZE74H5BR.js → chunk-RW54ZMBM.js} +26 -20
- package/dist/chunk-RW54ZMBM.js.map +1 -0
- package/dist/{chunk-INW24J2W.js → chunk-SUOXY5WJ.js} +2 -2
- package/dist/{init-L55Q73H4.js → chunk-UAN4A3YU.js} +345 -45
- package/dist/chunk-UAN4A3YU.js.map +1 -0
- package/dist/{chunk-IP7SMKIF.js → chunk-UJL4HI2R.js} +59 -60
- package/dist/chunk-UJL4HI2R.js.map +1 -0
- package/dist/{claude-LUZ35IMK.js → claude-W52VKI6L.js} +4 -2
- package/dist/{cleanup-3MONU4PU.js → cleanup-H4VXU3C3.js} +19 -17
- package/dist/{cleanup-3MONU4PU.js.map → cleanup-H4VXU3C3.js.map} +1 -1
- package/dist/cli.js +347 -114
- package/dist/cli.js.map +1 -1
- package/dist/{color-ZVALX37U.js → color-F7RU6B6Z.js} +10 -4
- package/dist/{contribute-UWJAGIG7.js → contribute-Y7IQV5QY.js} +4 -3
- package/dist/{contribute-UWJAGIG7.js.map → contribute-Y7IQV5QY.js.map} +1 -1
- package/dist/{feedback-W3BXTGIM.js → feedback-XTUCKJNT.js} +16 -12
- package/dist/{feedback-W3BXTGIM.js.map → feedback-XTUCKJNT.js.map} +1 -1
- package/dist/{git-34Z6QVDS.js → git-IYA53VIC.js} +9 -2
- package/dist/{ignite-KVJEFXNO.js → ignite-T74RYXCA.js} +25 -75
- package/dist/ignite-T74RYXCA.js.map +1 -0
- package/dist/index.d.ts +71 -14
- package/dist/index.js +407 -377
- package/dist/index.js.map +1 -1
- package/dist/init-4FHTAM3F.js +19 -0
- package/dist/mcp/issue-management-server.js +8 -1
- package/dist/mcp/issue-management-server.js.map +1 -1
- package/dist/{neon-helpers-WPUACUVC.js → neon-helpers-77PBPGJ5.js} +3 -3
- package/dist/{open-LNRZL3UU.js → open-UMXANW5S.js} +27 -14
- package/dist/open-UMXANW5S.js.map +1 -0
- package/dist/{prompt-7INJ7YRU.js → prompt-QALMYTVC.js} +4 -2
- package/dist/prompts/init-prompt.txt +89 -9
- package/dist/prompts/issue-prompt.txt +18 -11
- package/dist/{rebase-C4WNCVGM.js → rebase-VJ2VKR6R.js} +15 -13
- package/dist/rebase-VJ2VKR6R.js.map +1 -0
- package/dist/{run-IOGNIOYN.js → run-MJYY4PUT.js} +27 -14
- package/dist/run-MJYY4PUT.js.map +1 -0
- package/dist/schema/settings.schema.json +22 -4
- package/dist/{test-git-J7I5MFYH.js → test-git-IT5EWQ5C.js} +5 -5
- package/dist/{test-prefix-ZCONBCBX.js → test-prefix-NPWDPUUH.js} +5 -5
- package/dist/{test-tabs-RXDBZ6J7.js → test-tabs-PRMRSHKI.js} +3 -2
- package/dist/{test-tabs-RXDBZ6J7.js.map → test-tabs-PRMRSHKI.js.map} +1 -1
- package/package.json +2 -1
- package/dist/ClaudeContextManager-3VXA6UPR.js +0 -13
- package/dist/ClaudeService-6CPK43N4.js +0 -12
- package/dist/LoomLauncher-JF7JZMTZ.js.map +0 -1
- package/dist/chunk-4BGK7T6X.js.map +0 -1
- package/dist/chunk-4E4LD3QR.js +0 -302
- package/dist/chunk-4E4LD3QR.js.map +0 -1
- package/dist/chunk-G2IEYOLQ.js.map +0 -1
- package/dist/chunk-HBYZH6GD.js.map +0 -1
- package/dist/chunk-IP7SMKIF.js.map +0 -1
- package/dist/chunk-JKXJ7BGL.js.map +0 -1
- package/dist/chunk-O5OH5MRX.js.map +0 -1
- package/dist/chunk-RO26VS3W.js.map +0 -1
- package/dist/chunk-WEN5C5DM.js.map +0 -1
- package/dist/chunk-ZBQVSHVT.js.map +0 -1
- package/dist/chunk-ZE74H5BR.js.map +0 -1
- package/dist/chunk-ZWFBBPJI.js.map +0 -1
- package/dist/chunk-ZZZWQGTS.js.map +0 -1
- package/dist/ignite-KVJEFXNO.js.map +0 -1
- package/dist/init-L55Q73H4.js.map +0 -1
- package/dist/open-LNRZL3UU.js.map +0 -1
- package/dist/rebase-C4WNCVGM.js.map +0 -1
- package/dist/run-IOGNIOYN.js.map +0 -1
- package/dist/terminal-BIRBZ4AZ.js +0 -16
- /package/dist/{BranchNamingService-OMWKUYMM.js.map → BranchNamingService-A77VI6AI.js.map} +0 -0
- /package/dist/{ClaudeContextManager-3VXA6UPR.js.map → ClaudeContextManager-BN7RE5ZQ.js.map} +0 -0
- /package/dist/{ClaudeService-6CPK43N4.js.map → ClaudeService-DLYLJUPA.js.map} +0 -0
- /package/dist/{GitHubService-EBOETDIW.js.map → GitHubService-FZHHBOFG.js.map} +0 -0
- /package/dist/{PromptTemplateManager-A52RUAMS.js.map → PromptTemplateManager-6HH3PVXV.js.map} +0 -0
- /package/dist/{SettingsManager-ZCWJ56WP.js.map → SettingsManager-I2LRCW2A.js.map} +0 -0
- /package/dist/{SettingsMigrationManager-AGIIIPDQ.js.map → SettingsMigrationManager-TJ7UWZG5.js.map} +0 -0
- /package/dist/{chunk-TSKY3JI7.js.map → chunk-2CXREBLZ.js.map} +0 -0
- /package/dist/{chunk-IXKLYTWO.js.map → chunk-2MAIX45J.js.map} +0 -0
- /package/dist/{chunk-XPKDPZ5D.js.map → chunk-AKUJXDNW.js.map} +0 -0
- /package/dist/{chunk-KLBYVHPK.js.map → chunk-OSCLCMDG.js.map} +0 -0
- /package/dist/{chunk-U5QDY7ZD.js.map → chunk-PGPI5LR4.js.map} +0 -0
- /package/dist/{chunk-INW24J2W.js.map → chunk-SUOXY5WJ.js.map} +0 -0
- /package/dist/{claude-LUZ35IMK.js.map → claude-W52VKI6L.js.map} +0 -0
- /package/dist/{color-ZVALX37U.js.map → color-F7RU6B6Z.js.map} +0 -0
- /package/dist/{git-34Z6QVDS.js.map → git-IYA53VIC.js.map} +0 -0
- /package/dist/{neon-helpers-WPUACUVC.js.map → init-4FHTAM3F.js.map} +0 -0
- /package/dist/{prompt-7INJ7YRU.js.map → neon-helpers-77PBPGJ5.js.map} +0 -0
- /package/dist/{terminal-BIRBZ4AZ.js.map → prompt-QALMYTVC.js.map} +0 -0
- /package/dist/{test-git-J7I5MFYH.js.map → test-git-IT5EWQ5C.js.map} +0 -0
- /package/dist/{test-prefix-ZCONBCBX.js.map → test-prefix-NPWDPUUH.js.map} +0 -0
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
MIN_COLOR_DISTANCE,
|
|
3
4
|
calculateForegroundColor,
|
|
5
|
+
colorDistance,
|
|
4
6
|
generateColorFromBranchName,
|
|
5
7
|
getColorPalette,
|
|
6
8
|
hexToRgb,
|
|
7
9
|
lightenColor,
|
|
8
10
|
rgbToHex,
|
|
9
|
-
saturateColor
|
|
10
|
-
|
|
11
|
+
saturateColor,
|
|
12
|
+
selectDistinctColor
|
|
13
|
+
} from "./chunk-CFFQ2Z7A.js";
|
|
11
14
|
import "./chunk-GEHQXLEI.js";
|
|
12
15
|
export {
|
|
16
|
+
MIN_COLOR_DISTANCE,
|
|
13
17
|
calculateForegroundColor,
|
|
18
|
+
colorDistance,
|
|
14
19
|
generateColorFromBranchName,
|
|
15
20
|
getColorPalette,
|
|
16
21
|
hexToRgb,
|
|
17
22
|
lightenColor,
|
|
18
23
|
rgbToHex,
|
|
19
|
-
saturateColor
|
|
24
|
+
saturateColor,
|
|
25
|
+
selectDistinctColor
|
|
20
26
|
};
|
|
21
|
-
//# sourceMappingURL=color-
|
|
27
|
+
//# sourceMappingURL=color-F7RU6B6Z.js.map
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
executeGitCommand
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5Q3NDNNV.js";
|
|
5
|
+
import "./chunk-CDZERT7Z.js";
|
|
5
6
|
import {
|
|
6
7
|
checkGhAuth,
|
|
7
8
|
executeGhCommand
|
|
8
9
|
} from "./chunk-3RUPPQRG.js";
|
|
9
10
|
import {
|
|
10
11
|
promptInput
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-CE26YH2U.js";
|
|
12
13
|
import {
|
|
13
14
|
logger
|
|
14
15
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -256,4 +257,4 @@ export {
|
|
|
256
257
|
validateDirectoryName,
|
|
257
258
|
validateDirectoryPath
|
|
258
259
|
};
|
|
259
|
-
//# sourceMappingURL=contribute-
|
|
260
|
+
//# sourceMappingURL=contribute-Y7IQV5QY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/contribute.ts"],"sourcesContent":["import { logger } from '../utils/logger.js'\nimport { checkGhAuth, executeGhCommand } from '../utils/github.js'\nimport { executeGitCommand } from '../utils/git.js'\nimport { promptInput } from '../utils/prompt.js'\nimport { existsSync, accessSync, constants } from 'fs'\nimport { writeFile, mkdir } from 'fs/promises'\nimport path from 'path'\nimport { InitCommand } from './init.js'\nimport chalk from 'chalk'\n\nconst ILOOM_REPO = 'iloom-ai/iloom-cli'\nconst UPSTREAM_URL = 'https://github.com/iloom-ai/iloom-cli.git'\n\n// Maximum path length for most file systems\nconst MAX_PATH_LENGTH = 255\n\n// Reserved names on Windows (also avoid on all platforms for portability)\nconst RESERVED_NAMES = [\n\t'CON', 'PRN', 'AUX', 'NUL',\n\t'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',\n\t'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',\n]\n\n// Invalid characters for directory names (cross-platform)\n// eslint-disable-next-line no-control-regex\nconst INVALID_CHARS_PATTERN = /[<>:\"|?*\\x00-\\x1f]/\n\n\n/**\n * Validation result for directory input\n */\ninterface DirectoryValidationResult {\n\tisValid: boolean\n\terror?: string\n}\n\n/**\n * Validate directory name format\n * @param directoryName - The directory name (not full path)\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryName(directoryName: string): DirectoryValidationResult {\n\t// Check for empty or whitespace-only\n\tif (!directoryName || directoryName.trim() === '') {\n\t\treturn { isValid: false, error: 'Directory name cannot be empty' }\n\t}\n\n\tconst trimmed = directoryName.trim()\n\tconst baseName = path.basename(trimmed)\n\n\t// Check for invalid characters\n\tif (INVALID_CHARS_PATTERN.test(baseName)) {\n\t\treturn { isValid: false, error: 'Directory name contains invalid characters (<>:\"|?*)' }\n\t}\n\n\t// Check for reserved names (case-insensitive)\n\tif (RESERVED_NAMES.includes(baseName.toUpperCase())) {\n\t\treturn { isValid: false, error: `\"${baseName}\" is a reserved name and cannot be used` }\n\t}\n\n\t// Check for names that start/end with dots or spaces (problematic on some systems)\n\tif (baseName.startsWith('.') && baseName === '.') {\n\t\treturn { isValid: false, error: 'Directory name cannot be just a dot' }\n\t}\n\tif (baseName.endsWith('.') || baseName.endsWith(' ')) {\n\t\treturn { isValid: false, error: 'Directory name cannot end with a dot or space' }\n\t}\n\n\treturn { isValid: true }\n}\n\n/**\n * Validate full directory path\n * @param directoryPath - The full directory path\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryPath(directoryPath: string): DirectoryValidationResult {\n\t// First validate the directory name component\n\tconst nameValidation = validateDirectoryName(directoryPath)\n\tif (!nameValidation.isValid) {\n\t\treturn nameValidation\n\t}\n\n\tconst trimmed = directoryPath.trim()\n\tconst absolutePath = path.resolve(trimmed)\n\n\t// Check path length\n\tif (absolutePath.length > MAX_PATH_LENGTH) {\n\t\treturn {\n\t\t\tisValid: false,\n\t\t\terror: `Path is too long (${absolutePath.length} characters). Maximum is ${MAX_PATH_LENGTH} characters.`\n\t\t}\n\t}\n\n\t// Check if directory already exists\n\tif (existsSync(absolutePath)) {\n\t\treturn { isValid: false, error: `Directory already exists: ${trimmed}` }\n\t}\n\n\t// Check if parent directory exists\n\tconst parentDir = path.dirname(absolutePath)\n\tif (!existsSync(parentDir)) {\n\t\treturn { isValid: false, error: `Parent directory does not exist: ${parentDir}` }\n\t}\n\n\t// Check if parent directory is writable\n\ttry {\n\t\taccessSync(parentDir, constants.W_OK)\n\t} catch {\n\t\treturn { isValid: false, error: `Parent directory is not writable: ${parentDir}` }\n\t}\n\n\treturn { isValid: true }\n}\n\n\n/**\n * ContributeCommand - Set up local development environment for contributing to iloom\n * Implements issue #220: streamlined contributor onboarding workflow\n */\nexport class ContributeCommand {\n\tconstructor(_initCommand?: InitCommand) {}\n\n\t/**\n\t * Main entry point for the contribute command\n\t * Automates fork creation, cloning, and upstream configuration\n\t */\n\tpublic async execute(): Promise<void> {\n\t\tlogger.info(chalk.bold('Setting up iloom contributor environment...'))\n\n\t\t// Step 1: Verify gh CLI authenticated\n\t\tconst username = await this.getAuthenticatedUsername()\n\t\tlogger.success(`Authenticated as ${chalk.cyan(username)}`)\n\n\t\t// Step 2: Check for existing fork\n\t\tconst hasFork = await this.forkExists(username)\n\n\t\t// Step 3: Create fork if needed\n\t\tif (!hasFork) {\n\t\t\tlogger.info('Creating fork of iloom-ai/iloom-cli...')\n\t\t\tawait this.createFork()\n\t\t\tlogger.success('Fork created successfully')\n\t\t} else {\n\t\t\tlogger.info('Using existing fork')\n\t\t}\n\n\t\t// Step 4: Prompt for directory with validation and retry loop\n\t\tconst directory = await this.promptForDirectory()\n\n\t\t// Handle cancelled input\n\t\tif (!directory) {\n\t\t\tlogger.info('Setup cancelled by user')\n\t\t\tprocess.exit(0)\n\t\t}\n\n\t\tconst absolutePath = path.resolve(directory)\n\n\t\t// Step 5: Clone repository (gh CLI handles SSH/HTTPS automatically based on git config)\n\t\tlogger.info(`Cloning repository to ${directory}...`)\n\t\tawait this.cloneRepository(username, directory)\n\t\tlogger.success('Repository cloned successfully')\n\n\t\t// Step 6: Add upstream remote if it doesn't exist\n\t\tawait this.addUpstreamRemote(absolutePath)\n\n\t\t// Step 7: Configure settings\n\t\tlogger.info('Configuring iloom settings...')\n\t\tawait this.configureSettings(absolutePath)\n\t\tlogger.success('Settings configured')\n\n\t\tlogger.success(chalk.bold.green('\\nContributor environment setup complete!'))\n\t\tlogger.info(`\\nNext steps:`)\n\t\tlogger.info(` 1. cd ${directory}`)\n\t\tlogger.info(` 2. pnpm install`)\n\t\tlogger.info(` 3. iloom start <issue_number>`)\n\t\tlogger.info(`\\nHappy contributing!`)\n\t}\n\n\t/**\n\t * Get authenticated GitHub username\n\t * @throws Error if not authenticated\n\t */\n\tprivate async getAuthenticatedUsername(): Promise<string> {\n\t\tconst authStatus = await checkGhAuth()\n\n\t\tif (!authStatus.hasAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t'GitHub CLI is not authenticated. Please run: gh auth login'\n\t\t\t)\n\t\t}\n\n\t\tif (!authStatus.username) {\n\t\t\t// Try to fetch username from gh api if not in auth status\n\t\t\ttry {\n\t\t\t\tconst user = await executeGhCommand<{ login: string }>(['api', 'user', '--json', 'login'])\n\t\t\t\treturn user.login\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error)\n\t\t\t\tthrow new Error(`Unable to determine GitHub username: ${message}`)\n\t\t\t}\n\t\t}\n\n\t\treturn authStatus.username\n\t}\n\n\t/**\n\t * Check if user already has a fork of iloom-cli\n\t */\n\tprivate async forkExists(username: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait executeGhCommand(['api', `repos/${username}/iloom-cli`])\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\t// 404 means no fork exists\n\t\t\tif (error instanceof Error && error.message.includes('Not Found')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Create a fork of iloom-cli without cloning\n\t */\n\tprivate async createFork(): Promise<void> {\n\t\tawait executeGhCommand(['repo', 'fork', ILOOM_REPO, '--clone=false'])\n\t}\n\n\n\t/**\n\t * Clone the repository using simplified gh CLI approach\n\t */\n\tprivate async cloneRepository(\n\t\tusername: string,\n\t\tdirectory: string\n\t): Promise<void> {\n\t\tconst repoIdentifier = `${username}/iloom-cli`\n\t\t// Always use gh repo clone - it handles SSH/HTTPS based on user's git config\n\t\tawait executeGhCommand(['repo', 'clone', repoIdentifier, directory])\n\t}\n\n\t/**\n\t * Add upstream remote if it doesn't already exist\n\t */\n\tprivate async addUpstreamRemote(directory: string): Promise<void> {\n\t\ttry {\n\t\t\t// Check if upstream remote exists\n\t\t\tawait executeGitCommand(['remote', 'get-url', 'upstream'], { cwd: directory })\n\t\t\tlogger.info('Upstream remote already configured')\n\t\t} catch {\n\t\t\t// Upstream doesn't exist, add it\n\t\t\tlogger.info('Adding upstream remote...')\n\t\t\tawait executeGitCommand(\n\t\t\t\t['remote', 'add', 'upstream', UPSTREAM_URL],\n\t\t\t\t{ cwd: directory }\n\t\t\t)\n\t\t\tlogger.success('Upstream remote configured')\n\t\t}\n\t}\n\n\t/**\n\t * Prompt for directory with validation and retry loop\n\t * @returns The validated directory path, or null if user cancels\n\t */\n\tprivate async promptForDirectory(): Promise<string | null> {\n\t\tconst maxRetries = 3\n\t\tlet attempts = 0\n\n\t\twhile (attempts < maxRetries) {\n\t\t\tconst directory = await promptInput(\n\t\t\t\t'Where should the repository be cloned?',\n\t\t\t\t'./iloom-cli'\n\t\t\t)\n\n\t\t\t// Handle empty input (user cancelled by entering empty string after exhausting default)\n\t\t\tif (!directory || directory.trim() === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst trimmed = directory.trim()\n\n\t\t\t// Validate the directory path\n\t\t\tconst validation = validateDirectoryPath(trimmed)\n\t\t\tif (validation.isValid) {\n\t\t\t\treturn trimmed\n\t\t\t}\n\n\t\t\t// Show error and increment attempts\n\t\t\tattempts++\n\t\t\tif (attempts < maxRetries) {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.info(`Please try again (${maxRetries - attempts} attempts remaining)`)\n\t\t\t} else {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.error('Maximum retry attempts reached')\n\t\t\t\tthrow new Error(`Invalid directory after ${maxRetries} attempts: ${validation.error}`)\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n\n\n\t/**\n\t * Configure .iloom/settings.json with upstream remote\n\t */\n\tprivate async configureSettings(directory: string): Promise<void> {\n\t\tconst iloomDir = path.join(directory, '.iloom')\n\t\tconst settingsPath = path.join(iloomDir, 'settings.local.json')\n\n\t\t// Create .iloom directory\n\t\tawait mkdir(iloomDir, { recursive: true })\n\n\t\t// Create settings.json with upstream remote configuration and github-pr mode\n\t\tconst settings = {\n\t\t\tissueManagement: {\n\t\t\t\tgithub: {\n\t\t\t\t\tremote: 'upstream',\n\t\t\t\t},\n\t\t\t},\n\t\t\tmergeBehavior: {\n\t\t\t\tmode: 'github-pr',\n\t\t\t},\n\t\t}\n\n\t\tawait writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\\n')\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/contribute.ts"],"sourcesContent":["import { logger } from '../utils/logger.js'\nimport { checkGhAuth, executeGhCommand } from '../utils/github.js'\nimport { executeGitCommand } from '../utils/git.js'\nimport { promptInput } from '../utils/prompt.js'\nimport { existsSync, accessSync, constants } from 'fs'\nimport { writeFile, mkdir } from 'fs/promises'\nimport path from 'path'\nimport { InitCommand } from './init.js'\nimport chalk from 'chalk'\n\nconst ILOOM_REPO = 'iloom-ai/iloom-cli'\nconst UPSTREAM_URL = 'https://github.com/iloom-ai/iloom-cli.git'\n\n// Maximum path length for most file systems\nconst MAX_PATH_LENGTH = 255\n\n// Reserved names on Windows (also avoid on all platforms for portability)\nconst RESERVED_NAMES = [\n\t'CON', 'PRN', 'AUX', 'NUL',\n\t'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9',\n\t'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9',\n]\n\n// Invalid characters for directory names (cross-platform)\n// eslint-disable-next-line no-control-regex\nconst INVALID_CHARS_PATTERN = /[<>:\"|?*\\x00-\\x1f]/\n\n\n/**\n * Validation result for directory input\n */\ninterface DirectoryValidationResult {\n\tisValid: boolean\n\terror?: string\n}\n\n/**\n * Validate directory name format\n * @param directoryName - The directory name (not full path)\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryName(directoryName: string): DirectoryValidationResult {\n\t// Check for empty or whitespace-only\n\tif (!directoryName || directoryName.trim() === '') {\n\t\treturn { isValid: false, error: 'Directory name cannot be empty' }\n\t}\n\n\tconst trimmed = directoryName.trim()\n\tconst baseName = path.basename(trimmed)\n\n\t// Check for invalid characters\n\tif (INVALID_CHARS_PATTERN.test(baseName)) {\n\t\treturn { isValid: false, error: 'Directory name contains invalid characters (<>:\"|?*)' }\n\t}\n\n\t// Check for reserved names (case-insensitive)\n\tif (RESERVED_NAMES.includes(baseName.toUpperCase())) {\n\t\treturn { isValid: false, error: `\"${baseName}\" is a reserved name and cannot be used` }\n\t}\n\n\t// Check for names that start/end with dots or spaces (problematic on some systems)\n\tif (baseName.startsWith('.') && baseName === '.') {\n\t\treturn { isValid: false, error: 'Directory name cannot be just a dot' }\n\t}\n\tif (baseName.endsWith('.') || baseName.endsWith(' ')) {\n\t\treturn { isValid: false, error: 'Directory name cannot end with a dot or space' }\n\t}\n\n\treturn { isValid: true }\n}\n\n/**\n * Validate full directory path\n * @param directoryPath - The full directory path\n * @returns Validation result with error message if invalid\n */\nexport function validateDirectoryPath(directoryPath: string): DirectoryValidationResult {\n\t// First validate the directory name component\n\tconst nameValidation = validateDirectoryName(directoryPath)\n\tif (!nameValidation.isValid) {\n\t\treturn nameValidation\n\t}\n\n\tconst trimmed = directoryPath.trim()\n\tconst absolutePath = path.resolve(trimmed)\n\n\t// Check path length\n\tif (absolutePath.length > MAX_PATH_LENGTH) {\n\t\treturn {\n\t\t\tisValid: false,\n\t\t\terror: `Path is too long (${absolutePath.length} characters). Maximum is ${MAX_PATH_LENGTH} characters.`\n\t\t}\n\t}\n\n\t// Check if directory already exists\n\tif (existsSync(absolutePath)) {\n\t\treturn { isValid: false, error: `Directory already exists: ${trimmed}` }\n\t}\n\n\t// Check if parent directory exists\n\tconst parentDir = path.dirname(absolutePath)\n\tif (!existsSync(parentDir)) {\n\t\treturn { isValid: false, error: `Parent directory does not exist: ${parentDir}` }\n\t}\n\n\t// Check if parent directory is writable\n\ttry {\n\t\taccessSync(parentDir, constants.W_OK)\n\t} catch {\n\t\treturn { isValid: false, error: `Parent directory is not writable: ${parentDir}` }\n\t}\n\n\treturn { isValid: true }\n}\n\n\n/**\n * ContributeCommand - Set up local development environment for contributing to iloom\n * Implements issue #220: streamlined contributor onboarding workflow\n */\nexport class ContributeCommand {\n\tconstructor(_initCommand?: InitCommand) {}\n\n\t/**\n\t * Main entry point for the contribute command\n\t * Automates fork creation, cloning, and upstream configuration\n\t */\n\tpublic async execute(): Promise<void> {\n\t\tlogger.info(chalk.bold('Setting up iloom contributor environment...'))\n\n\t\t// Step 1: Verify gh CLI authenticated\n\t\tconst username = await this.getAuthenticatedUsername()\n\t\tlogger.success(`Authenticated as ${chalk.cyan(username)}`)\n\n\t\t// Step 2: Check for existing fork\n\t\tconst hasFork = await this.forkExists(username)\n\n\t\t// Step 3: Create fork if needed\n\t\tif (!hasFork) {\n\t\t\tlogger.info('Creating fork of iloom-ai/iloom-cli...')\n\t\t\tawait this.createFork()\n\t\t\tlogger.success('Fork created successfully')\n\t\t} else {\n\t\t\tlogger.info('Using existing fork')\n\t\t}\n\n\t\t// Step 4: Prompt for directory with validation and retry loop\n\t\tconst directory = await this.promptForDirectory()\n\n\t\t// Handle cancelled input\n\t\tif (!directory) {\n\t\t\tlogger.info('Setup cancelled by user')\n\t\t\tprocess.exit(0)\n\t\t}\n\n\t\tconst absolutePath = path.resolve(directory)\n\n\t\t// Step 5: Clone repository (gh CLI handles SSH/HTTPS automatically based on git config)\n\t\tlogger.info(`Cloning repository to ${directory}...`)\n\t\tawait this.cloneRepository(username, directory)\n\t\tlogger.success('Repository cloned successfully')\n\n\t\t// Step 6: Add upstream remote if it doesn't exist\n\t\tawait this.addUpstreamRemote(absolutePath)\n\n\t\t// Step 7: Configure settings\n\t\tlogger.info('Configuring iloom settings...')\n\t\tawait this.configureSettings(absolutePath)\n\t\tlogger.success('Settings configured')\n\n\t\tlogger.success(chalk.bold.green('\\nContributor environment setup complete!'))\n\t\tlogger.info(`\\nNext steps:`)\n\t\tlogger.info(` 1. cd ${directory}`)\n\t\tlogger.info(` 2. pnpm install`)\n\t\tlogger.info(` 3. iloom start <issue_number>`)\n\t\tlogger.info(`\\nHappy contributing!`)\n\t}\n\n\t/**\n\t * Get authenticated GitHub username\n\t * @throws Error if not authenticated\n\t */\n\tprivate async getAuthenticatedUsername(): Promise<string> {\n\t\tconst authStatus = await checkGhAuth()\n\n\t\tif (!authStatus.hasAuth) {\n\t\t\tthrow new Error(\n\t\t\t\t'GitHub CLI is not authenticated. Please run: gh auth login'\n\t\t\t)\n\t\t}\n\n\t\tif (!authStatus.username) {\n\t\t\t// Try to fetch username from gh api if not in auth status\n\t\t\ttry {\n\t\t\t\tconst user = await executeGhCommand<{ login: string }>(['api', 'user', '--json', 'login'])\n\t\t\t\treturn user.login\n\t\t\t} catch (error) {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error)\n\t\t\t\tthrow new Error(`Unable to determine GitHub username: ${message}`)\n\t\t\t}\n\t\t}\n\n\t\treturn authStatus.username\n\t}\n\n\t/**\n\t * Check if user already has a fork of iloom-cli\n\t */\n\tprivate async forkExists(username: string): Promise<boolean> {\n\t\ttry {\n\t\t\tawait executeGhCommand(['api', `repos/${username}/iloom-cli`])\n\t\t\treturn true\n\t\t} catch (error) {\n\t\t\t// 404 means no fork exists\n\t\t\tif (error instanceof Error && error.message.includes('Not Found')) {\n\t\t\t\treturn false\n\t\t\t}\n\t\t\t// Re-throw unexpected errors\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Create a fork of iloom-cli without cloning\n\t */\n\tprivate async createFork(): Promise<void> {\n\t\tawait executeGhCommand(['repo', 'fork', ILOOM_REPO, '--clone=false'])\n\t}\n\n\n\t/**\n\t * Clone the repository using simplified gh CLI approach\n\t */\n\tprivate async cloneRepository(\n\t\tusername: string,\n\t\tdirectory: string\n\t): Promise<void> {\n\t\tconst repoIdentifier = `${username}/iloom-cli`\n\t\t// Always use gh repo clone - it handles SSH/HTTPS based on user's git config\n\t\tawait executeGhCommand(['repo', 'clone', repoIdentifier, directory])\n\t}\n\n\t/**\n\t * Add upstream remote if it doesn't already exist\n\t */\n\tprivate async addUpstreamRemote(directory: string): Promise<void> {\n\t\ttry {\n\t\t\t// Check if upstream remote exists\n\t\t\tawait executeGitCommand(['remote', 'get-url', 'upstream'], { cwd: directory })\n\t\t\tlogger.info('Upstream remote already configured')\n\t\t} catch {\n\t\t\t// Upstream doesn't exist, add it\n\t\t\tlogger.info('Adding upstream remote...')\n\t\t\tawait executeGitCommand(\n\t\t\t\t['remote', 'add', 'upstream', UPSTREAM_URL],\n\t\t\t\t{ cwd: directory }\n\t\t\t)\n\t\t\tlogger.success('Upstream remote configured')\n\t\t}\n\t}\n\n\t/**\n\t * Prompt for directory with validation and retry loop\n\t * @returns The validated directory path, or null if user cancels\n\t */\n\tprivate async promptForDirectory(): Promise<string | null> {\n\t\tconst maxRetries = 3\n\t\tlet attempts = 0\n\n\t\twhile (attempts < maxRetries) {\n\t\t\tconst directory = await promptInput(\n\t\t\t\t'Where should the repository be cloned?',\n\t\t\t\t'./iloom-cli'\n\t\t\t)\n\n\t\t\t// Handle empty input (user cancelled by entering empty string after exhausting default)\n\t\t\tif (!directory || directory.trim() === '') {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst trimmed = directory.trim()\n\n\t\t\t// Validate the directory path\n\t\t\tconst validation = validateDirectoryPath(trimmed)\n\t\t\tif (validation.isValid) {\n\t\t\t\treturn trimmed\n\t\t\t}\n\n\t\t\t// Show error and increment attempts\n\t\t\tattempts++\n\t\t\tif (attempts < maxRetries) {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.info(`Please try again (${maxRetries - attempts} attempts remaining)`)\n\t\t\t} else {\n\t\t\t\tlogger.error(`${validation.error}`)\n\t\t\t\tlogger.error('Maximum retry attempts reached')\n\t\t\t\tthrow new Error(`Invalid directory after ${maxRetries} attempts: ${validation.error}`)\n\t\t\t}\n\t\t}\n\n\t\treturn null\n\t}\n\n\n\t/**\n\t * Configure .iloom/settings.json with upstream remote\n\t */\n\tprivate async configureSettings(directory: string): Promise<void> {\n\t\tconst iloomDir = path.join(directory, '.iloom')\n\t\tconst settingsPath = path.join(iloomDir, 'settings.local.json')\n\n\t\t// Create .iloom directory\n\t\tawait mkdir(iloomDir, { recursive: true })\n\n\t\t// Create settings.json with upstream remote configuration and github-pr mode\n\t\tconst settings = {\n\t\t\tissueManagement: {\n\t\t\t\tgithub: {\n\t\t\t\t\tremote: 'upstream',\n\t\t\t\t},\n\t\t\t},\n\t\t\tmergeBehavior: {\n\t\t\t\tmode: 'github-pr',\n\t\t\t},\n\t\t}\n\n\t\tawait writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\\n')\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
-
IssueEnhancementService
|
|
4
|
-
|
|
3
|
+
IssueEnhancementService,
|
|
4
|
+
capitalizeFirstLetter
|
|
5
|
+
} from "./chunk-IFB4Z76W.js";
|
|
6
|
+
import "./chunk-YETJNRQM.js";
|
|
5
7
|
import {
|
|
6
8
|
AgentManager
|
|
7
9
|
} from "./chunk-OC4H6HJD.js";
|
|
8
|
-
import "./chunk-YETJNRQM.js";
|
|
9
|
-
import {
|
|
10
|
-
getClaudeVersion
|
|
11
|
-
} from "./chunk-ZWFBBPJI.js";
|
|
12
10
|
import {
|
|
13
11
|
ProjectCapabilityDetector
|
|
14
12
|
} from "./chunk-EBISESAP.js";
|
|
15
13
|
import "./chunk-2ZPFJQ3B.js";
|
|
16
14
|
import {
|
|
17
15
|
SettingsManager
|
|
18
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-CDZERT7Z.js";
|
|
19
17
|
import {
|
|
20
18
|
GitHubService
|
|
21
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-M7JJCX53.js";
|
|
22
20
|
import "./chunk-3RUPPQRG.js";
|
|
23
|
-
import "./chunk-
|
|
21
|
+
import "./chunk-CE26YH2U.js";
|
|
22
|
+
import {
|
|
23
|
+
getClaudeVersion
|
|
24
|
+
} from "./chunk-OYF4VIFI.js";
|
|
25
|
+
import "./chunk-RW54ZMBM.js";
|
|
26
|
+
import "./chunk-UJL4HI2R.js";
|
|
24
27
|
import {
|
|
25
28
|
logger
|
|
26
29
|
} from "./chunk-GEHQXLEI.js";
|
|
@@ -132,13 +135,14 @@ var FeedbackCommand = class {
|
|
|
132
135
|
* 5. Return issue number
|
|
133
136
|
*/
|
|
134
137
|
async execute(input) {
|
|
135
|
-
const
|
|
138
|
+
const description = capitalizeFirstLetter(input.description);
|
|
139
|
+
const body = input.options.body ? capitalizeFirstLetter(input.options.body) : void 0;
|
|
136
140
|
if (!description || !this.enhancementService.validateDescription(description)) {
|
|
137
141
|
throw new Error("Description is required and must be more than 30 characters with at least 3 words");
|
|
138
142
|
}
|
|
139
143
|
const diagnostics = await gatherDiagnosticInfo();
|
|
140
144
|
const diagnosticsMarkdown = formatDiagnosticsAsMarkdown(diagnostics);
|
|
141
|
-
const userBody =
|
|
145
|
+
const userBody = body ?? description;
|
|
142
146
|
const enhancedBody = `${diagnosticsMarkdown}
|
|
143
147
|
|
|
144
148
|
${userBody}`;
|
|
@@ -156,4 +160,4 @@ ${userBody}`;
|
|
|
156
160
|
export {
|
|
157
161
|
FeedbackCommand
|
|
158
162
|
};
|
|
159
|
-
//# sourceMappingURL=feedback-
|
|
163
|
+
//# sourceMappingURL=feedback-XTUCKJNT.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/diagnostics.ts","../src/commands/feedback.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { join, dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport { platform, release, arch } from 'os'\nimport { logger } from './logger'\nimport { ProjectCapabilityDetector } from '../lib/ProjectCapabilityDetector.js'\nimport type { ProjectCapability } from '../types/loom.js'\nimport { getClaudeVersion } from './claude.js'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * Diagnostic information gathered from the system\n */\nexport interface DiagnosticInfo {\n\tcliVersion: string\n\tnodeVersion: string\n\tosType: string\n\tosVersion: string\n\tarchitecture: string\n\tcapabilities: ProjectCapability[]\n\tclaudeVersion: string | null\n}\n\n/**\n * Gathers diagnostic information about the CLI environment.\n * Fails gracefully with fallback messages if any information cannot be gathered.\n */\nexport async function gatherDiagnosticInfo(): Promise<DiagnosticInfo> {\n\t// Instantiate detector for current directory\n\tconst detector = new ProjectCapabilityDetector()\n\tconst { capabilities } = await detector.detectCapabilities(process.cwd())\n\n\t// Get Claude CLI version (returns null if not available)\n\tconst claudeVersion = await getClaudeVersion()\n\n\tconst diagnostics: DiagnosticInfo = {\n\t\tcliVersion: await getCliVersion(),\n\t\tnodeVersion: getNodeVersion(),\n\t\tosType: getOsType(),\n\t\tosVersion: getOsVersion(),\n\t\tarchitecture: getArchitecture(),\n\t\tcapabilities,\n\t\tclaudeVersion,\n\t}\n\n\treturn diagnostics\n}\n\n/**\n * Formats diagnostic information as markdown for inclusion in GitHub issues\n */\nexport function formatDiagnosticsAsMarkdown(diagnostics: DiagnosticInfo, includeMarker = true): string {\n\tconst marker = includeMarker ? `<!-- CLI GENERATED FEEDBACK v${diagnostics.cliVersion} -->\\n` : ''\n\n\t// Format capabilities as comma-separated string or \"none\"\n\tconst capabilitiesDisplay = diagnostics.capabilities.length > 0\n\t\t? diagnostics.capabilities.join(', ')\n\t\t: 'none'\n\n\t// Format Claude version with fallback for null\n\tconst claudeVersionDisplay = diagnostics.claudeVersion ?? 'not available'\n\n\treturn `${marker}\n<details>\n<summary>Diagnostic Information</summary>\n\n| Property | Value |\n|----------|-------|\n| CLI Version | ${diagnostics.cliVersion} |\n| Node.js Version | ${diagnostics.nodeVersion} |\n| OS | ${diagnostics.osType} |\n| OS Version | ${diagnostics.osVersion} |\n| Architecture | ${diagnostics.architecture} |\n| Capabilities | ${capabilitiesDisplay} |\n| Claude CLI Version | ${claudeVersionDisplay} |\n\n</details>\n`\n}\n\n/**\n * Gets the CLI version from package.json.\n * Falls back to \"unknown\" if package.json cannot be read.\n */\nasync function getCliVersion(): Promise<string> {\n\ttry {\n\t\t// Navigate up from dist/ to root directory (same as cli.ts does)\n\t\tconst packageJsonPath = join(__dirname, '..', 'package.json')\n\t\tconst packageJson = await readFile(packageJsonPath, 'utf-8')\n\t\tconst parsed = JSON.parse(packageJson)\n\t\treturn parsed.version ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read CLI version from package.json: ', error)\n\t\treturn 'unknown (failed to read package.json)'\n\t}\n}\n\n/**\n * Gets the Node.js version.\n * Falls back to \"unknown\" if process.version is not available.\n */\nfunction getNodeVersion(): string {\n\ttry {\n\t\treturn process.version ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read Node.js version:', error)\n\t\treturn 'unknown (failed to read Node.js version)'\n\t}\n}\n\n/**\n * Gets the operating system type.\n * Falls back to \"unknown\" if os.platform() fails.\n */\nfunction getOsType(): string {\n\ttry {\n\t\treturn platform() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read OS type:', error)\n\t\treturn 'unknown (failed to detect OS)'\n\t}\n}\n\n/**\n * Gets the operating system version.\n * Falls back to \"unknown\" if os.release() fails.\n */\nfunction getOsVersion(): string {\n\ttry {\n\t\treturn release() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read OS version:', error)\n\t\treturn 'unknown (failed to detect OS version)'\n\t}\n}\n\n/**\n * Gets the system architecture.\n * Falls back to \"unknown\" if os.arch() fails.\n */\nfunction getArchitecture(): string {\n\ttry {\n\t\treturn arch() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read system architecture:', error)\n\t\treturn 'unknown (failed to detect architecture)'\n\t}\n}\n","import type { FeedbackOptions } from '../types/index.js'\nimport { IssueEnhancementService } from '../lib/IssueEnhancementService.js'\nimport { GitHubService } from '../lib/GitHubService.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { gatherDiagnosticInfo, formatDiagnosticsAsMarkdown } from '../utils/diagnostics.js'\n\n// Hardcoded target repository for feedback\nconst FEEDBACK_REPOSITORY = 'iloom-ai/iloom-cli'\n\n/**\n * Input structure for FeedbackCommand\n */\nexport interface FeedbackCommandInput {\n\tdescription: string\n\toptions: FeedbackOptions\n}\n\n/**\n * Command to submit feedback/bug reports to the iloom-cli repository.\n * Mirrors add-issue command but targets iloom-ai/iloom-cli repo.\n */\nexport class FeedbackCommand {\n\tprivate enhancementService: IssueEnhancementService\n\n\tconstructor(enhancementService?: IssueEnhancementService) {\n\t\t// Use provided service or create default\n\t\tthis.enhancementService = enhancementService ?? new IssueEnhancementService(\n\t\t\tnew GitHubService(),\n\t\t\tnew AgentManager(),\n\t\t\tnew SettingsManager()\n\t\t)\n\t}\n\n\t/**\n\t * Execute the feedback command workflow:\n\t * 1. Validate description format\n\t * 2. Gather diagnostic information\n\t * 3. Create GitHub issue in iloom-ai/iloom-cli with CLI marker and diagnostics\n\t * 4. Wait for keypress and open browser for review\n\t * 5. Return issue number\n\t */\n\tpublic async execute(input: FeedbackCommandInput): Promise<string | number> {\n\t\tconst { description } = input\n\n\t\t// Step 1: Validate description format\n\t\tif (!description || !this.enhancementService.validateDescription(description)) {\n\t\t\tthrow new Error('Description is required and must be more than 30 characters with at least 3 words')\n\t\t}\n\n\t\t// Step 2: Gather diagnostic information\n\t\tconst diagnostics = await gatherDiagnosticInfo()\n\t\tconst diagnosticsMarkdown = formatDiagnosticsAsMarkdown(diagnostics)\n\n\t\t// Step 3: Create enhanced issue body with marker and diagnostics\n\t\tconst userBody = input.options.body ?? description\n\t\tconst enhancedBody = `${diagnosticsMarkdown}\n\n${userBody}`\n\n\t\t// Step 4: Create GitHub issue in iloom-cli repo (no label needed)\n\t\t// The GitHub Action workflow will detect the CLI marker and enhance the issue\n\t\tconst result = await this.enhancementService.createEnhancedIssue(\n\t\t\tdescription,\n\t\t\tenhancedBody,\n\t\t\tFEEDBACK_REPOSITORY,\n\t\t\tundefined // No labels needed\n\t\t)\n\n\t\t// Step 5: Wait for keypress and open issue in browser for review\n\t\tawait this.enhancementService.waitForReviewAndOpen(result.number, false, FEEDBACK_REPOSITORY)\n\n\t\t// Step 6: Return issue number for reference\n\t\treturn result.number\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/diagnostics.ts","../src/commands/feedback.ts"],"sourcesContent":["import { readFile } from 'fs/promises'\nimport { join, dirname } from 'path'\nimport { fileURLToPath } from 'url'\nimport { platform, release, arch } from 'os'\nimport { logger } from './logger'\nimport { ProjectCapabilityDetector } from '../lib/ProjectCapabilityDetector.js'\nimport type { ProjectCapability } from '../types/loom.js'\nimport { getClaudeVersion } from './claude.js'\n\nconst __filename = fileURLToPath(import.meta.url)\nconst __dirname = dirname(__filename)\n\n/**\n * Diagnostic information gathered from the system\n */\nexport interface DiagnosticInfo {\n\tcliVersion: string\n\tnodeVersion: string\n\tosType: string\n\tosVersion: string\n\tarchitecture: string\n\tcapabilities: ProjectCapability[]\n\tclaudeVersion: string | null\n}\n\n/**\n * Gathers diagnostic information about the CLI environment.\n * Fails gracefully with fallback messages if any information cannot be gathered.\n */\nexport async function gatherDiagnosticInfo(): Promise<DiagnosticInfo> {\n\t// Instantiate detector for current directory\n\tconst detector = new ProjectCapabilityDetector()\n\tconst { capabilities } = await detector.detectCapabilities(process.cwd())\n\n\t// Get Claude CLI version (returns null if not available)\n\tconst claudeVersion = await getClaudeVersion()\n\n\tconst diagnostics: DiagnosticInfo = {\n\t\tcliVersion: await getCliVersion(),\n\t\tnodeVersion: getNodeVersion(),\n\t\tosType: getOsType(),\n\t\tosVersion: getOsVersion(),\n\t\tarchitecture: getArchitecture(),\n\t\tcapabilities,\n\t\tclaudeVersion,\n\t}\n\n\treturn diagnostics\n}\n\n/**\n * Formats diagnostic information as markdown for inclusion in GitHub issues\n */\nexport function formatDiagnosticsAsMarkdown(diagnostics: DiagnosticInfo, includeMarker = true): string {\n\tconst marker = includeMarker ? `<!-- CLI GENERATED FEEDBACK v${diagnostics.cliVersion} -->\\n` : ''\n\n\t// Format capabilities as comma-separated string or \"none\"\n\tconst capabilitiesDisplay = diagnostics.capabilities.length > 0\n\t\t? diagnostics.capabilities.join(', ')\n\t\t: 'none'\n\n\t// Format Claude version with fallback for null\n\tconst claudeVersionDisplay = diagnostics.claudeVersion ?? 'not available'\n\n\treturn `${marker}\n<details>\n<summary>Diagnostic Information</summary>\n\n| Property | Value |\n|----------|-------|\n| CLI Version | ${diagnostics.cliVersion} |\n| Node.js Version | ${diagnostics.nodeVersion} |\n| OS | ${diagnostics.osType} |\n| OS Version | ${diagnostics.osVersion} |\n| Architecture | ${diagnostics.architecture} |\n| Capabilities | ${capabilitiesDisplay} |\n| Claude CLI Version | ${claudeVersionDisplay} |\n\n</details>\n`\n}\n\n/**\n * Gets the CLI version from package.json.\n * Falls back to \"unknown\" if package.json cannot be read.\n */\nasync function getCliVersion(): Promise<string> {\n\ttry {\n\t\t// Navigate up from dist/ to root directory (same as cli.ts does)\n\t\tconst packageJsonPath = join(__dirname, '..', 'package.json')\n\t\tconst packageJson = await readFile(packageJsonPath, 'utf-8')\n\t\tconst parsed = JSON.parse(packageJson)\n\t\treturn parsed.version ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read CLI version from package.json: ', error)\n\t\treturn 'unknown (failed to read package.json)'\n\t}\n}\n\n/**\n * Gets the Node.js version.\n * Falls back to \"unknown\" if process.version is not available.\n */\nfunction getNodeVersion(): string {\n\ttry {\n\t\treturn process.version ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read Node.js version:', error)\n\t\treturn 'unknown (failed to read Node.js version)'\n\t}\n}\n\n/**\n * Gets the operating system type.\n * Falls back to \"unknown\" if os.platform() fails.\n */\nfunction getOsType(): string {\n\ttry {\n\t\treturn platform() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read OS type:', error)\n\t\treturn 'unknown (failed to detect OS)'\n\t}\n}\n\n/**\n * Gets the operating system version.\n * Falls back to \"unknown\" if os.release() fails.\n */\nfunction getOsVersion(): string {\n\ttry {\n\t\treturn release() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read OS version:', error)\n\t\treturn 'unknown (failed to detect OS version)'\n\t}\n}\n\n/**\n * Gets the system architecture.\n * Falls back to \"unknown\" if os.arch() fails.\n */\nfunction getArchitecture(): string {\n\ttry {\n\t\treturn arch() ?? 'unknown'\n\t} catch (error) {\n\t\tlogger.debug('Failed to read system architecture:', error)\n\t\treturn 'unknown (failed to detect architecture)'\n\t}\n}\n","import type { FeedbackOptions } from '../types/index.js'\nimport { IssueEnhancementService } from '../lib/IssueEnhancementService.js'\nimport { GitHubService } from '../lib/GitHubService.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { gatherDiagnosticInfo, formatDiagnosticsAsMarkdown } from '../utils/diagnostics.js'\nimport { capitalizeFirstLetter } from '../utils/text.js'\n\n// Hardcoded target repository for feedback\nconst FEEDBACK_REPOSITORY = 'iloom-ai/iloom-cli'\n\n/**\n * Input structure for FeedbackCommand\n */\nexport interface FeedbackCommandInput {\n\tdescription: string\n\toptions: FeedbackOptions\n}\n\n/**\n * Command to submit feedback/bug reports to the iloom-cli repository.\n * Mirrors add-issue command but targets iloom-ai/iloom-cli repo.\n */\nexport class FeedbackCommand {\n\tprivate enhancementService: IssueEnhancementService\n\n\tconstructor(enhancementService?: IssueEnhancementService) {\n\t\t// Use provided service or create default\n\t\tthis.enhancementService = enhancementService ?? new IssueEnhancementService(\n\t\t\tnew GitHubService(),\n\t\t\tnew AgentManager(),\n\t\t\tnew SettingsManager()\n\t\t)\n\t}\n\n\t/**\n\t * Execute the feedback command workflow:\n\t * 1. Validate description format\n\t * 2. Gather diagnostic information\n\t * 3. Create GitHub issue in iloom-ai/iloom-cli with CLI marker and diagnostics\n\t * 4. Wait for keypress and open browser for review\n\t * 5. Return issue number\n\t */\n\tpublic async execute(input: FeedbackCommandInput): Promise<string | number> {\n\t\t// Apply first-letter capitalization to title (description) and body\n\t\tconst description = capitalizeFirstLetter(input.description)\n\t\tconst body = input.options.body ? capitalizeFirstLetter(input.options.body) : undefined\n\n\t\t// Step 1: Validate description format\n\t\tif (!description || !this.enhancementService.validateDescription(description)) {\n\t\t\tthrow new Error('Description is required and must be more than 30 characters with at least 3 words')\n\t\t}\n\n\t\t// Step 2: Gather diagnostic information\n\t\tconst diagnostics = await gatherDiagnosticInfo()\n\t\tconst diagnosticsMarkdown = formatDiagnosticsAsMarkdown(diagnostics)\n\n\t\t// Step 3: Create enhanced issue body with marker and diagnostics\n\t\tconst userBody = body ?? description\n\t\tconst enhancedBody = `${diagnosticsMarkdown}\n\n${userBody}`\n\n\t\t// Step 4: Create GitHub issue in iloom-cli repo (no label needed)\n\t\t// The GitHub Action workflow will detect the CLI marker and enhance the issue\n\t\tconst result = await this.enhancementService.createEnhancedIssue(\n\t\t\tdescription,\n\t\t\tenhancedBody,\n\t\t\tFEEDBACK_REPOSITORY,\n\t\t\tundefined // No labels needed\n\t\t)\n\n\t\t// Step 5: Wait for keypress and open issue in browser for review\n\t\tawait this.enhancementService.waitForReviewAndOpen(result.number, false, FEEDBACK_REPOSITORY)\n\n\t\t// Step 6: Return issue number for reference\n\t\treturn result.number\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,gBAAgB;AACzB,SAAS,MAAM,eAAe;AAC9B,SAAS,qBAAqB;AAC9B,SAAS,UAAU,SAAS,YAAY;AAMxC,IAAM,aAAa,cAAc,YAAY,GAAG;AAChD,IAAM,YAAY,QAAQ,UAAU;AAmBpC,eAAsB,uBAAgD;AAErE,QAAM,WAAW,IAAI,0BAA0B;AAC/C,QAAM,EAAE,aAAa,IAAI,MAAM,SAAS,mBAAmB,QAAQ,IAAI,CAAC;AAGxE,QAAM,gBAAgB,MAAM,iBAAiB;AAE7C,QAAM,cAA8B;AAAA,IACnC,YAAY,MAAM,cAAc;AAAA,IAChC,aAAa,eAAe;AAAA,IAC5B,QAAQ,UAAU;AAAA,IAClB,WAAW,aAAa;AAAA,IACxB,cAAc,gBAAgB;AAAA,IAC9B;AAAA,IACA;AAAA,EACD;AAEA,SAAO;AACR;AAKO,SAAS,4BAA4B,aAA6B,gBAAgB,MAAc;AACtG,QAAM,SAAS,gBAAgB,gCAAgC,YAAY,UAAU;AAAA,IAAW;AAGhG,QAAM,sBAAsB,YAAY,aAAa,SAAS,IAC3D,YAAY,aAAa,KAAK,IAAI,IAClC;AAGH,QAAM,uBAAuB,YAAY,iBAAiB;AAE1D,SAAO,GAAG,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAMC,YAAY,UAAU;AAAA,sBAClB,YAAY,WAAW;AAAA,SACpC,YAAY,MAAM;AAAA,iBACV,YAAY,SAAS;AAAA,mBACnB,YAAY,YAAY;AAAA,mBACxB,mBAAmB;AAAA,yBACb,oBAAoB;AAAA;AAAA;AAAA;AAI7C;AAMA,eAAe,gBAAiC;AAC/C,MAAI;AAEH,UAAM,kBAAkB,KAAK,WAAW,MAAM,cAAc;AAC5D,UAAM,cAAc,MAAM,SAAS,iBAAiB,OAAO;AAC3D,UAAM,SAAS,KAAK,MAAM,WAAW;AACrC,WAAO,OAAO,WAAW;AAAA,EAC1B,SAAS,OAAO;AACf,WAAO,MAAM,kDAAkD,KAAK;AACpE,WAAO;AAAA,EACR;AACD;AAMA,SAAS,iBAAyB;AACjC,MAAI;AACH,WAAO,QAAQ,WAAW;AAAA,EAC3B,SAAS,OAAO;AACf,WAAO,MAAM,mCAAmC,KAAK;AACrD,WAAO;AAAA,EACR;AACD;AAMA,SAAS,YAAoB;AAC5B,MAAI;AACH,WAAO,SAAS,KAAK;AAAA,EACtB,SAAS,OAAO;AACf,WAAO,MAAM,2BAA2B,KAAK;AAC7C,WAAO;AAAA,EACR;AACD;AAMA,SAAS,eAAuB;AAC/B,MAAI;AACH,WAAO,QAAQ,KAAK;AAAA,EACrB,SAAS,OAAO;AACf,WAAO,MAAM,8BAA8B,KAAK;AAChD,WAAO;AAAA,EACR;AACD;AAMA,SAAS,kBAA0B;AAClC,MAAI;AACH,WAAO,KAAK,KAAK;AAAA,EAClB,SAAS,OAAO;AACf,WAAO,MAAM,uCAAuC,KAAK;AACzD,WAAO;AAAA,EACR;AACD;;;AC5IA,IAAM,sBAAsB;AAcrB,IAAM,kBAAN,MAAsB;AAAA,EAG5B,YAAY,oBAA8C;AAEzD,SAAK,qBAAqB,sBAAsB,IAAI;AAAA,MACnD,IAAI,cAAc;AAAA,MAClB,IAAI,aAAa;AAAA,MACjB,IAAI,gBAAgB;AAAA,IACrB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAa,QAAQ,OAAuD;AAE3E,UAAM,cAAc,sBAAsB,MAAM,WAAW;AAC3D,UAAM,OAAO,MAAM,QAAQ,OAAO,sBAAsB,MAAM,QAAQ,IAAI,IAAI;AAG9E,QAAI,CAAC,eAAe,CAAC,KAAK,mBAAmB,oBAAoB,WAAW,GAAG;AAC9E,YAAM,IAAI,MAAM,mFAAmF;AAAA,IACpG;AAGA,UAAM,cAAc,MAAM,qBAAqB;AAC/C,UAAM,sBAAsB,4BAA4B,WAAW;AAGnE,UAAM,WAAW,QAAQ;AACzB,UAAM,eAAe,GAAG,mBAAmB;AAAA;AAAA,EAE3C,QAAQ;AAIR,UAAM,SAAS,MAAM,KAAK,mBAAmB;AAAA,MAC5C;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA;AAAA,IACD;AAGA,UAAM,KAAK,mBAAmB,qBAAqB,OAAO,QAAQ,OAAO,mBAAmB;AAG5F,WAAO,OAAO;AAAA,EACf;AACD;","names":[]}
|
|
@@ -12,14 +12,18 @@ import {
|
|
|
12
12
|
getCurrentBranch,
|
|
13
13
|
getDefaultBranch,
|
|
14
14
|
getRepoRoot,
|
|
15
|
+
getWorktreeRoot,
|
|
15
16
|
hasUncommittedChanges,
|
|
16
17
|
isEmptyRepository,
|
|
18
|
+
isFileGitignored,
|
|
19
|
+
isFileTrackedByGit,
|
|
17
20
|
isPRBranch,
|
|
18
21
|
isValidGitRepo,
|
|
19
22
|
isWorktreePath,
|
|
20
23
|
parseWorktreeList,
|
|
21
24
|
pushBranchToRemote
|
|
22
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-5Q3NDNNV.js";
|
|
26
|
+
import "./chunk-CDZERT7Z.js";
|
|
23
27
|
import "./chunk-GEHQXLEI.js";
|
|
24
28
|
export {
|
|
25
29
|
branchExists,
|
|
@@ -34,12 +38,15 @@ export {
|
|
|
34
38
|
getCurrentBranch,
|
|
35
39
|
getDefaultBranch,
|
|
36
40
|
getRepoRoot,
|
|
41
|
+
getWorktreeRoot,
|
|
37
42
|
hasUncommittedChanges,
|
|
38
43
|
isEmptyRepository,
|
|
44
|
+
isFileGitignored,
|
|
45
|
+
isFileTrackedByGit,
|
|
39
46
|
isPRBranch,
|
|
40
47
|
isValidGitRepo,
|
|
41
48
|
isWorktreePath,
|
|
42
49
|
parseWorktreeList,
|
|
43
50
|
pushBranchToRemote
|
|
44
51
|
};
|
|
45
|
-
//# sourceMappingURL=git-
|
|
52
|
+
//# sourceMappingURL=git-IYA53VIC.js.map
|
|
@@ -1,91 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
FirstRunManager,
|
|
3
4
|
IssueTrackerFactory,
|
|
4
5
|
generateIssueManagementMcpConfig
|
|
5
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-DLHA5VQ3.js";
|
|
6
7
|
import {
|
|
7
8
|
AgentManager
|
|
8
9
|
} from "./chunk-OC4H6HJD.js";
|
|
9
10
|
import {
|
|
10
11
|
GitWorktreeManager
|
|
11
|
-
} from "./chunk-
|
|
12
|
-
import {
|
|
13
|
-
launchClaude
|
|
14
|
-
} from "./chunk-ZWFBBPJI.js";
|
|
12
|
+
} from "./chunk-2CXREBLZ.js";
|
|
15
13
|
import {
|
|
16
14
|
PromptTemplateManager
|
|
17
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-RIEO2WML.js";
|
|
18
16
|
import {
|
|
19
17
|
extractSettingsOverrides
|
|
20
18
|
} from "./chunk-GYCR2LOU.js";
|
|
21
|
-
import {
|
|
22
|
-
SettingsManager
|
|
23
|
-
} from "./chunk-O5OH5MRX.js";
|
|
24
19
|
import {
|
|
25
20
|
extractIssueNumber
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import
|
|
21
|
+
} from "./chunk-5Q3NDNNV.js";
|
|
22
|
+
import {
|
|
23
|
+
SettingsManager
|
|
24
|
+
} from "./chunk-CDZERT7Z.js";
|
|
25
|
+
import "./chunk-M7JJCX53.js";
|
|
28
26
|
import "./chunk-3RUPPQRG.js";
|
|
29
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-CE26YH2U.js";
|
|
28
|
+
import {
|
|
29
|
+
launchClaude
|
|
30
|
+
} from "./chunk-OYF4VIFI.js";
|
|
31
|
+
import "./chunk-RW54ZMBM.js";
|
|
32
|
+
import "./chunk-UJL4HI2R.js";
|
|
30
33
|
import {
|
|
31
34
|
logger
|
|
32
35
|
} from "./chunk-GEHQXLEI.js";
|
|
33
36
|
|
|
34
37
|
// src/commands/ignite.ts
|
|
35
|
-
import path2 from "path";
|
|
36
|
-
|
|
37
|
-
// src/utils/FirstRunManager.ts
|
|
38
|
-
import os from "os";
|
|
39
38
|
import path from "path";
|
|
40
|
-
import fs from "fs-extra";
|
|
41
|
-
var FirstRunManager = class {
|
|
42
|
-
constructor(feature = "spin") {
|
|
43
|
-
const configDir = path.join(os.homedir(), ".config", "iloom-ai");
|
|
44
|
-
this.markerFilePath = path.join(configDir, `${feature}-first-run`);
|
|
45
|
-
logger.debug("FirstRunManager initialized", {
|
|
46
|
-
feature,
|
|
47
|
-
markerFilePath: this.markerFilePath
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Check if this is the first run of the feature
|
|
52
|
-
* Returns true if marker file doesn't exist
|
|
53
|
-
* Handles errors gracefully by returning true (treat as first-run on error)
|
|
54
|
-
*/
|
|
55
|
-
async isFirstRun() {
|
|
56
|
-
logger.debug("isFirstRun: Checking for marker file", { markerFilePath: this.markerFilePath });
|
|
57
|
-
try {
|
|
58
|
-
const exists = await fs.pathExists(this.markerFilePath);
|
|
59
|
-
logger.debug(`isFirstRun: Marker file exists=${exists}`);
|
|
60
|
-
return !exists;
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.debug(`isFirstRun: Error checking marker file, treating as first-run: ${error}`);
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/**
|
|
67
|
-
* Mark the feature as having been run
|
|
68
|
-
* Creates the marker file in config directory
|
|
69
|
-
* Handles errors gracefully without throwing
|
|
70
|
-
*/
|
|
71
|
-
async markAsRun() {
|
|
72
|
-
logger.debug("markAsRun: Attempting to create marker file", { markerFilePath: this.markerFilePath });
|
|
73
|
-
try {
|
|
74
|
-
const configDir = path.dirname(this.markerFilePath);
|
|
75
|
-
logger.debug(`markAsRun: Ensuring config directory exists: ${configDir}`);
|
|
76
|
-
await fs.ensureDir(configDir);
|
|
77
|
-
const markerContent = {
|
|
78
|
-
firstRun: (/* @__PURE__ */ new Date()).toISOString()
|
|
79
|
-
};
|
|
80
|
-
await fs.writeFile(this.markerFilePath, JSON.stringify(markerContent, null, 2), "utf8");
|
|
81
|
-
logger.debug("markAsRun: Marker file created successfully");
|
|
82
|
-
} catch (error) {
|
|
83
|
-
logger.debug(`markAsRun: Failed to create marker file: ${error}`);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
// src/commands/ignite.ts
|
|
89
39
|
import { readFile } from "fs/promises";
|
|
90
40
|
var IgniteCommand = class {
|
|
91
41
|
constructor(templateManager, gitWorktreeManager, agentManager, settingsManager, firstRunManager) {
|
|
@@ -292,7 +242,7 @@ var IgniteCommand = class {
|
|
|
292
242
|
*/
|
|
293
243
|
async detectWorkspaceContext() {
|
|
294
244
|
const workspacePath = process.cwd();
|
|
295
|
-
const currentDir =
|
|
245
|
+
const currentDir = path.basename(workspacePath);
|
|
296
246
|
const prPattern = /_pr_(\d+)$/;
|
|
297
247
|
const prMatch = currentDir.match(prPattern);
|
|
298
248
|
if (prMatch == null ? void 0 : prMatch[1]) {
|
|
@@ -430,15 +380,15 @@ var IgniteCommand = class {
|
|
|
430
380
|
*/
|
|
431
381
|
async loadReadmeContent() {
|
|
432
382
|
try {
|
|
433
|
-
let currentDir =
|
|
434
|
-
while (currentDir !==
|
|
435
|
-
const readmePath =
|
|
383
|
+
let currentDir = path.dirname(new URL(import.meta.url).pathname);
|
|
384
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
385
|
+
const readmePath = path.join(currentDir, "README.md");
|
|
436
386
|
try {
|
|
437
387
|
const content = await readFile(readmePath, "utf-8");
|
|
438
388
|
logger.debug("Loaded README.md for first-time user", { readmePath });
|
|
439
389
|
return content;
|
|
440
390
|
} catch {
|
|
441
|
-
currentDir =
|
|
391
|
+
currentDir = path.dirname(currentDir);
|
|
442
392
|
}
|
|
443
393
|
}
|
|
444
394
|
logger.debug("README.md not found, returning empty string");
|
|
@@ -454,15 +404,15 @@ var IgniteCommand = class {
|
|
|
454
404
|
*/
|
|
455
405
|
async loadSettingsSchemaContent() {
|
|
456
406
|
try {
|
|
457
|
-
let currentDir =
|
|
458
|
-
while (currentDir !==
|
|
459
|
-
const schemaPath =
|
|
407
|
+
let currentDir = path.dirname(new URL(import.meta.url).pathname);
|
|
408
|
+
while (currentDir !== path.dirname(currentDir)) {
|
|
409
|
+
const schemaPath = path.join(currentDir, ".iloom", "README.md");
|
|
460
410
|
try {
|
|
461
411
|
const content = await readFile(schemaPath, "utf-8");
|
|
462
412
|
logger.debug("Loaded .iloom/README.md for first-time user", { schemaPath });
|
|
463
413
|
return content;
|
|
464
414
|
} catch {
|
|
465
|
-
currentDir =
|
|
415
|
+
currentDir = path.dirname(currentDir);
|
|
466
416
|
}
|
|
467
417
|
}
|
|
468
418
|
logger.debug(".iloom/README.md not found, returning empty string");
|
|
@@ -476,4 +426,4 @@ var IgniteCommand = class {
|
|
|
476
426
|
export {
|
|
477
427
|
IgniteCommand
|
|
478
428
|
};
|
|
479
|
-
//# sourceMappingURL=ignite-
|
|
429
|
+
//# sourceMappingURL=ignite-T74RYXCA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/ignite.ts"],"sourcesContent":["import path from 'path'\nimport { logger } from '../utils/logger.js'\nimport { ClaudeWorkflowOptions } from '../lib/ClaudeService.js'\nimport { GitWorktreeManager } from '../lib/GitWorktreeManager.js'\nimport { launchClaude, ClaudeCliOptions } from '../utils/claude.js'\nimport { PromptTemplateManager, TemplateVariables } from '../lib/PromptTemplateManager.js'\nimport { generateIssueManagementMcpConfig } from '../utils/mcp.js'\nimport { AgentManager } from '../lib/AgentManager.js'\nimport { IssueTrackerFactory } from '../lib/IssueTrackerFactory.js'\nimport { SettingsManager } from '../lib/SettingsManager.js'\nimport { extractSettingsOverrides } from '../utils/cli-overrides.js'\nimport { FirstRunManager } from '../utils/FirstRunManager.js'\nimport { extractIssueNumber } from '../utils/git.js'\nimport { readFile } from 'fs/promises'\n\n/**\n * IgniteCommand: Auto-detect workspace context and launch Claude\n *\n * This command:\n * 1. Auto-detects context from current directory and git branch\n * 2. Loads appropriate prompt template with variable substitution\n * 3. Launches Claude with existing agent system (NO changes to agent loading)\n * 4. Executes in current terminal (not opening a new window)\n *\n * CRITICAL: This command works with agents exactly as they currently function.\n * NO modifications to agent loading mechanisms.\n */\nexport class IgniteCommand {\n\tprivate templateManager: PromptTemplateManager\n\tprivate gitWorktreeManager: GitWorktreeManager\n\tprivate agentManager: AgentManager\n\tprivate settingsManager: SettingsManager\n\tprivate firstRunManager: FirstRunManager\n\tprivate settings?: import('../lib/SettingsManager.js').IloomSettings\n\n\tconstructor(\n\t\ttemplateManager?: PromptTemplateManager,\n\t\tgitWorktreeManager?: GitWorktreeManager,\n\t\tagentManager?: AgentManager,\n\t\tsettingsManager?: SettingsManager,\n\t\tfirstRunManager?: FirstRunManager\n\t) {\n\t\tthis.templateManager = templateManager ?? new PromptTemplateManager()\n\t\tthis.gitWorktreeManager = gitWorktreeManager ?? new GitWorktreeManager()\n\t\tthis.agentManager = agentManager ?? new AgentManager()\n\t\tthis.settingsManager = settingsManager ?? new SettingsManager()\n\t\tthis.firstRunManager = firstRunManager ?? new FirstRunManager('spin')\n\t}\n\n\t/**\n\t * Main entry point for spin command\n\t */\n\tasync execute(oneShot: import('../types/index.js').OneShotMode = 'default'): Promise<void> {\n\t\ttry {\n\t\t\tlogger.info('🚀 Your loom is spinning up, please wait...')\n\n\t\t\t// Step 0.5: Check if this is first-time user\n\t\t\tconst isFirstRun = await this.firstRunManager.isFirstRun()\n\t\t\tif (isFirstRun) {\n\t\t\t\tlogger.success('Welcome to iloom! Preparing first-time experience...')\n\t\t\t}\n\n\t\t\t// Step 1: Auto-detect workspace context\n\t\t\tconst context = await this.detectWorkspaceContext()\n\n\t\t\tlogger.debug('Auto-detected workspace context', { context })\n\n\t\t\t// Inform user what context was detected\n\t\t\tthis.logDetectedContext(context)\n\n\t\t\tlogger.info('📝 Loading prompt template and preparing Claude...')\n\n\t\t\t// Step 2: Get prompt template with variable substitution\n\t\t\tconst variables = this.buildTemplateVariables(context, oneShot)\n\n\t\t\t// Step 2.5: Add first-time user context if needed\n\t\t\tif (isFirstRun) {\n\t\t\t\tvariables.FIRST_TIME_USER = true\n\t\t\t\tvariables.README_CONTENT = await this.loadReadmeContent()\n\t\t\t\tvariables.SETTINGS_SCHEMA_CONTENT = await this.loadSettingsSchemaContent()\n\t\t\t}\n\n\t\t\tconst systemInstructions = await this.templateManager.getPrompt(context.type, variables)\n\n\t\t\t// User prompt to trigger the workflow (includes one-shot bypass instructions if needed)\n\t\t\tconst userPrompt = this.buildUserPrompt(oneShot)\n\n\t\t\t// Step 2.5: Load settings if not cached with CLI overrides\n\t\t\t// Settings are pre-validated at CLI startup, so no error handling needed here\n\t\t\tif (!this.settings) {\n\t\t\t\tconst cliOverrides = extractSettingsOverrides()\n\t\t\t\tthis.settings = await this.settingsManager.loadSettings(undefined, cliOverrides)\n\t\t\t}\n\n\t\t\t// Step 3: Determine model and permission mode based on workflow type\n\t\t\tconst model = this.getModelForWorkflow(context.type)\n\t\t\tlet permissionMode = this.getPermissionModeForWorkflow(context.type)\n\n\t\t\t// Override permission mode if bypassPermissions oneShot mode\n\t\t\tif (oneShot === 'bypassPermissions') {\n\t\t\t\tpermissionMode = 'bypassPermissions'\n\t\t\t}\n\n\t\t\t// Display warning if bypassPermissions is used\n\t\t\tif (permissionMode === 'bypassPermissions') {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t'⚠️ WARNING: Using bypassPermissions mode - Claude will execute all tool calls without confirmation. ' +\n\t\t\t\t\t\t'This can be dangerous. Use with caution.'\n\t\t\t\t)\n\t\t\t}\n\n\t\t\t// Step 4: Build Claude CLI options\n\t\t\tconst claudeOptions: ClaudeCliOptions = {\n\t\t\t\theadless: false, // Enable stdio: 'inherit' for current terminal\n\t\t\t\taddDir: context.workspacePath,\n\t\t\t}\n\n\t\t\t// Add optional model if present\n\t\t\tif (model !== undefined) {\n\t\t\t\tclaudeOptions.model = model\n\t\t\t}\n\n\t\t\t// Add permission mode if not default\n\t\t\tif (permissionMode !== undefined && permissionMode !== 'default') {\n\t\t\t\tclaudeOptions.permissionMode = permissionMode\n\t\t\t}\n\n\t\t\t// Add optional branch name for context\n\t\t\tif (context.branchName !== undefined) {\n\t\t\t\tclaudeOptions.branchName = context.branchName\n\t\t\t}\n\n\t\t\t// Step 4.5: Generate MCP config and tool filtering for issue/PR workflows\n\t\t\tlet mcpConfig: Record<string, unknown>[] | undefined\n\t\t\tlet allowedTools: string[] | undefined\n\t\t\tlet disallowedTools: string[] | undefined\n\n\t\t\tif (context.type === 'issue' || context.type === 'pr') {\n\t\t\t\ttry {\n\t\t\t\t\tconst provider = this.settings ? IssueTrackerFactory.getProviderName(this.settings) : 'github'\n\t\t\t\t\tmcpConfig = await generateIssueManagementMcpConfig(context.type, undefined, provider, this.settings)\n\t\t\t\t\tlogger.debug('Generated MCP configuration for issue management', { provider })\n\n\t\t\t\t\t// Configure tool filtering for issue/PR workflows\n\t\t\t\t\tallowedTools = [\n\t\t\t\t\t\t'mcp__issue_management__get_issue',\n\t\t\t\t\t\t'mcp__issue_management__get_comment',\n\t\t\t\t\t\t'mcp__issue_management__create_comment',\n\t\t\t\t\t\t'mcp__issue_management__update_comment',\n\t\t\t\t\t]\n\t\t\t\t\tdisallowedTools = ['Bash(gh api:*), Bash(gh issue view:*), Bash(gh pr view:*), Bash(gh issue comment:*)']\n\n\t\t\t\t\tlogger.debug('Configured tool filtering for issue/PR workflow', { allowedTools, disallowedTools })\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Log warning but continue without MCP\n\t\t\t\t\tlogger.warn(`Failed to generate MCP config: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Step 4.6: Load agent configurations using cached settings\n\t\t\tlet agents: Record<string, unknown> | undefined\n\t\t\ttry {\n\t\t\t\t// Use cached settings from Step 2.5\n\t\t\t\tif (this.settings?.agents && Object.keys(this.settings.agents).length > 0) {\n\t\t\t\t\tlogger.debug('Loaded project settings', {\n\t\t\t\t\t\tagentOverrides: Object.keys(this.settings.agents),\n\t\t\t\t\t})\n\t\t\t\t}\n\n\t\t\t\t// Load agents with settings overrides\n\t\t\t\tconst loadedAgents = await this.agentManager.loadAgents(this.settings)\n\t\t\t\tagents = this.agentManager.formatForCli(loadedAgents)\n\t\t\t\tlogger.debug('Loaded agent configurations', {\n\t\t\t\t\tagentCount: Object.keys(agents).length,\n\t\t\t\t\tagentNames: Object.keys(agents),\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\t// Log warning but continue without agents\n\t\t\t\tlogger.warn(`Failed to load agents: ${error instanceof Error ? error.message : 'Unknown error'}`)\n\t\t\t}\n\n\t\t\tlogger.debug('Launching Claude in current terminal', {\n\t\t\t\ttype: context.type,\n\t\t\t\tmodel,\n\t\t\t\tpermissionMode,\n\t\t\t\tworkspacePath: context.workspacePath,\n\t\t\t\thasMcpConfig: !!mcpConfig,\n\t\t\t})\n\n\t\t\tlogger.info('✨ Launching Claude in current terminal...')\n\n\t\t\t// Step 5: Launch Claude with system instructions appended and user prompt\n\t\t\tawait launchClaude(userPrompt, {\n\t\t\t\t...claudeOptions,\n\t\t\t\tappendSystemPrompt: systemInstructions,\n\t\t\t\t...(mcpConfig && { mcpConfig }),\n\t\t\t\t...(allowedTools && { allowedTools }),\n\t\t\t\t...(disallowedTools && { disallowedTools }),\n\t\t\t\t...(agents && { agents }),\n\t\t\t})\n\n\t\t\t// Step 6: Mark as run after successful launch\n\t\t\tif (isFirstRun) {\n\t\t\t\tawait this.firstRunManager.markAsRun()\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : 'Unknown error'\n\t\t\tlogger.error(`Failed to launch Claude: ${errorMessage}`)\n\t\t\tthrow error\n\t\t}\n\t}\n\n\t/**\n\t * Log user-friendly information about detected context\n\t */\n\tprivate logDetectedContext(context: ClaudeWorkflowOptions): void {\n\t\tif (context.type === 'issue') {\n\t\t\tlogger.info(`🎯 Detected issue workflow: Issue #${context.issueNumber}`)\n\t\t} else if (context.type === 'pr') {\n\t\t\tlogger.info(`🔄 Detected PR workflow: PR #${context.prNumber}`)\n\t\t} else {\n\t\t\tlogger.info('🌟 Detected regular workflow')\n\t\t}\n\n\t\tif (context.branchName) {\n\t\t\tlogger.info(`🌿 Working on branch: ${context.branchName}`)\n\t\t}\n\n\t\tif (context.port) {\n\t\t\tlogger.info(`🌐 Development server port: ${context.port}`)\n\t\t}\n\t}\n\n\t/**\n\t * Build template variables from context\n\t */\n\tprivate buildTemplateVariables(context: ClaudeWorkflowOptions, oneShot: import('../types/index.js').OneShotMode): TemplateVariables {\n\t\tconst variables: TemplateVariables = {\n\t\t\tWORKSPACE_PATH: context.workspacePath,\n\t\t}\n\n\t\tif (context.issueNumber !== undefined) {\n\t\t\tvariables.ISSUE_NUMBER = context.issueNumber\n\t\t}\n\n\t\tif (context.prNumber !== undefined) {\n\t\t\tvariables.PR_NUMBER = context.prNumber\n\t\t}\n\n\t\tif (context.title !== undefined) {\n\t\t\tif (context.type === 'issue') {\n\t\t\t\tvariables.ISSUE_TITLE = context.title\n\t\t\t} else if (context.type === 'pr') {\n\t\t\t\tvariables.PR_TITLE = context.title\n\t\t\t}\n\t\t}\n\n\t\tif (context.port !== undefined) {\n\t\t\tvariables.PORT = context.port\n\t\t}\n\n\t\t// Set ONE_SHOT_MODE flag for template conditional sections\n\t\tif (oneShot === 'noReview' || oneShot === 'bypassPermissions') {\n\t\t\tvariables.ONE_SHOT_MODE = true\n\t\t}\n\n\t\treturn variables\n\t}\n\n\t/**\n\t * Get the appropriate model for a workflow type\n\t * Same logic as ClaudeService.getModelForWorkflow()\n\t */\n\tprivate getModelForWorkflow(type: 'issue' | 'pr' | 'regular'): string | undefined {\n\t\t// Issue workflows use claude-sonnet-4-20250514\n\t\tif (type === 'issue') {\n\t\t\treturn 'claude-sonnet-4-20250514'\n\t\t}\n\t\t// For PR and regular workflows, use Claude's default model\n\t\treturn undefined\n\t}\n\n\t/**\n\t * Get the appropriate permission mode for a workflow type\n\t * Same logic as ClaudeService.getPermissionModeForWorkflow()\n\t */\n\tprivate getPermissionModeForWorkflow(\n\t\ttype: 'issue' | 'pr' | 'regular'\n\t): ClaudeCliOptions['permissionMode'] {\n\t\t// Check settings for configured permission mode\n\t\tif (this.settings?.workflows) {\n\t\t\tconst workflowConfig =\n\t\t\t\ttype === 'issue'\n\t\t\t\t\t? this.settings.workflows.issue\n\t\t\t\t\t: type === 'pr'\n\t\t\t\t\t\t? this.settings.workflows.pr\n\t\t\t\t\t\t: this.settings.workflows.regular\n\n\t\t\tif (workflowConfig?.permissionMode) {\n\t\t\t\treturn workflowConfig.permissionMode\n\t\t\t}\n\t\t}\n\n\t\t// Fall back to current defaults\n\t\tif (type === 'issue') {\n\t\t\treturn 'acceptEdits'\n\t\t}\n\t\t// For PR and regular workflows, use default permissions\n\t\treturn 'default'\n\t}\n\n\t/**\n\t * Auto-detect workspace context from current directory and git branch\n\t *\n\t * Detection priority:\n\t * 1. Directory name patterns (_pr_N, issue-N)\n\t * 2. Git branch name patterns\n\t * 3. Fallback to 'regular' workflow\n\t *\n\t * This leverages the same logic as FinishCommand.autoDetectFromCurrentDirectory()\n\t */\n\tprivate async detectWorkspaceContext(): Promise<ClaudeWorkflowOptions> {\n\t\tconst workspacePath = process.cwd()\n\t\tconst currentDir = path.basename(workspacePath)\n\n\t\t// Check for PR worktree pattern: _pr_N suffix\n\t\t// Pattern: /.*_pr_(\\d+)$/\n\t\tconst prPattern = /_pr_(\\d+)$/\n\t\tconst prMatch = currentDir.match(prPattern)\n\n\t\tif (prMatch?.[1]) {\n\t\t\tconst prNumber = parseInt(prMatch[1], 10)\n\t\t\tlogger.debug(`Auto-detected PR #${prNumber} from directory: ${currentDir}`)\n\n\t\t\treturn this.buildContextForPR(prNumber, workspacePath)\n\t\t}\n\n\t\t// Check for issue pattern in directory name\n\t\tconst issueNumber = extractIssueNumber(currentDir)\n\n\t\tif (issueNumber !== null) {\n\t\t\tlogger.debug(`Auto-detected issue #${issueNumber} from directory: ${currentDir}`)\n\n\t\t\treturn this.buildContextForIssue(issueNumber, workspacePath)\n\t\t}\n\n\t\t// Fallback: Try to extract from git branch name\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tconst currentBranch = repoInfo.currentBranch\n\n\t\t\tif (currentBranch) {\n\t\t\t\t// Try to extract issue from branch name\n\t\t\t\tconst branchIssueNumber = extractIssueNumber(currentBranch)\n\t\t\t\tif (branchIssueNumber !== null) {\n\t\t\t\t\tlogger.debug(`Auto-detected issue #${branchIssueNumber} from branch: ${currentBranch}`)\n\n\t\t\t\t\treturn this.buildContextForIssue(branchIssueNumber, workspacePath, currentBranch)\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\t// Git command failed - not a git repo or other git error\n\t\t\tlogger.debug('Could not detect from git branch', { error })\n\t\t}\n\n\t\t// Last resort: use regular workflow\n\t\tlogger.debug('No specific context detected, using regular workflow')\n\t\treturn this.buildContextForRegular(workspacePath)\n\t}\n\n\t/**\n\t * Build context for issue workflow\n\t */\n\tprivate async buildContextForIssue(\n\t\tissueNumber: string | number,\n\t\tworkspacePath: string,\n\t\tbranchName?: string\n\t): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name if not provided\n\t\tif (!branchName) {\n\t\t\ttry {\n\t\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t\t} catch {\n\t\t\t\t// Ignore git errors\n\t\t\t}\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'issue',\n\t\t\tissueNumber,\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Build context for PR workflow\n\t */\n\tprivate async buildContextForPR(\n\t\tprNumber: number,\n\t\tworkspacePath: string\n\t): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name\n\t\tlet branchName: string | undefined\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t} catch {\n\t\t\t// Ignore git errors\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'pr',\n\t\t\tprNumber,\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Build context for regular workflow\n\t */\n\tprivate async buildContextForRegular(workspacePath: string): Promise<ClaudeWorkflowOptions> {\n\t\t// Get branch name\n\t\tlet branchName: string | undefined\n\t\ttry {\n\t\t\tconst repoInfo = await this.gitWorktreeManager.getRepoInfo()\n\t\t\tbranchName = repoInfo.currentBranch ?? undefined\n\t\t} catch {\n\t\t\t// Ignore git errors\n\t\t}\n\n\t\tconst port = this.getPortFromEnv()\n\t\tconst context: ClaudeWorkflowOptions = {\n\t\t\ttype: 'regular',\n\t\t\tworkspacePath,\n\t\t\theadless: false, // Interactive mode\n\t\t}\n\n\t\tif (port !== undefined) {\n\t\t\tcontext.port = port\n\t\t}\n\n\t\tif (branchName !== undefined) {\n\t\t\tcontext.branchName = branchName\n\t\t}\n\n\t\treturn context\n\t}\n\n\t/**\n\t * Get PORT from environment variables\n\t * Returns undefined if PORT is not set or invalid\n\t */\n\tprivate getPortFromEnv(): number | undefined {\n\t\tconst portStr = process.env.PORT\n\t\tif (!portStr) {\n\t\t\treturn undefined\n\t\t}\n\n\t\tconst port = parseInt(portStr, 10)\n\t\tif (isNaN(port)) {\n\t\t\tlogger.warn(`Invalid PORT environment variable: ${portStr}`)\n\t\t\treturn undefined\n\t\t}\n\n\t\treturn port\n\t}\n\n\n\t/**\n\t * Build user prompt based on one-shot mode\n\t */\n\tprivate buildUserPrompt(oneShot: import('../types/index.js').OneShotMode = 'default'): string {\n\t\t// For one-shot modes, add bypass instructions to override template approval requirements\n\t\tif (oneShot === 'noReview' || oneShot === 'bypassPermissions') {\n\t\t\treturn 'Guide the user through the iloom workflow! The user has requested you move through the workflow without awaiting confirmation. This supersedes any other guidance.'\n\t\t}\n\n\t\t// Default mode: simple \"Go!\" prompt\n\t\treturn 'Guide the user through the iloom workflow!'\n\t}\n\n\t/**\n\t * Load README.md content for first-time users\n\t * Walks up from dist directory to find README.md in project root\n\t */\n\tprivate async loadReadmeContent(): Promise<string> {\n\t\ttry {\n\t\t\t// Walk up from current file location to find README.md\n\t\t\t// Use same pattern as PromptTemplateManager for finding files\n\t\t\tlet currentDir = path.dirname(new URL(import.meta.url).pathname)\n\n\t\t\t// Walk up to find README.md\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst readmePath = path.join(currentDir, 'README.md')\n\t\t\t\ttry {\n\t\t\t\t\tconst content = await readFile(readmePath, 'utf-8')\n\t\t\t\t\tlogger.debug('Loaded README.md for first-time user', { readmePath })\n\t\t\t\t\treturn content\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlogger.debug('README.md not found, returning empty string')\n\t\t\treturn ''\n\t\t} catch (error) {\n\t\t\t// Graceful degradation - return empty string on error\n\t\t\tlogger.debug(`Failed to load README.md: ${error}`)\n\t\t\treturn ''\n\t\t}\n\t}\n\n\t/**\n\t * Load settings schema content for first-time users\n\t * Walks up from dist directory to find .iloom/README.md\n\t */\n\tprivate async loadSettingsSchemaContent(): Promise<string> {\n\t\ttry {\n\t\t\t// Walk up from current file location to find .iloom/README.md\n\t\t\tlet currentDir = path.dirname(new URL(import.meta.url).pathname)\n\n\t\t\t// Walk up to find .iloom/README.md\n\t\t\twhile (currentDir !== path.dirname(currentDir)) {\n\t\t\t\tconst schemaPath = path.join(currentDir, '.iloom', 'README.md')\n\t\t\t\ttry {\n\t\t\t\t\tconst content = await readFile(schemaPath, 'utf-8')\n\t\t\t\t\tlogger.debug('Loaded .iloom/README.md for first-time user', { schemaPath })\n\t\t\t\t\treturn content\n\t\t\t\t} catch {\n\t\t\t\t\tcurrentDir = path.dirname(currentDir)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlogger.debug('.iloom/README.md not found, returning empty string')\n\t\t\treturn ''\n\t\t} catch (error) {\n\t\t\t// Graceful degradation - return empty string on error\n\t\t\tlogger.debug(`Failed to load .iloom/README.md: ${error}`)\n\t\t\treturn ''\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|