Skip to content

feat(PILOT-50): add AI-aware output target selector - #51

Merged
e-Garcia merged 1 commit into
mainfrom
feature/pilot-50_ai-aware-context-output
Nov 22, 2025
Merged

feat(PILOT-50): add AI-aware output target selector#51
e-Garcia merged 1 commit into
mainfrom
feature/pilot-50_ai-aware-context-output

Conversation

@e-Garcia

@e-Garcia e-Garcia commented Nov 22, 2025

Copy link
Copy Markdown
Owner

• Summary

  • add an AI Repository Context Target selector to the PromptPilot tool window so contributors can route the generated repo-context file to .promptpilot, .github/copilot-instructions.md, .cursor/rules, or a custom location
  • persist the selected preset/custom paths in PropertiesComponent, auto-create missing directories/files, and expose a live destination preview/status in the UI
  • document the workflow in README.md, AGENTS.md, and CLAUDE.md, plus add ContextOutputTarget.kt to define the presets

Details

  • ContextFileManager now resolves output paths via ContextOutputTarget, creates directories on demand, and exposes helper accessors (currentOutputTarget, currentOutputLocation)
  • PromptPilotToolWindowFactory adds the dropdown, custom fields, hint text, save button behavior, and live status labels
  • new constants/string resources for preset names, Copilot/Cursor filenames, and output-target messaging

Testing

  • Pick an AI provider from the dropdown, select one or more files inside of the source context directory and create or update repo context file. Validate that the proper file was created and it being referenced by the AI tool in any new conversations.
Screenshot 2025-11-22 at 11 12 00 AM

@e-Garcia e-Garcia added this to the 1.2 milestone Nov 22, 2025
@e-Garcia
e-Garcia requested a review from Copilot November 22, 2025 13:52
@e-Garcia e-Garcia self-assigned this Nov 22, 2025
@e-Garcia e-Garcia added the enhancement New feature or request label Nov 22, 2025
@e-Garcia e-Garcia linked an issue Nov 22, 2025 that may be closed by this pull request

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds an AI-aware output target selector to the PromptPilot IntelliJ plugin, enabling users to route generated repository context files to different AI tool locations (GitHub Copilot, Cursor, or custom paths). The implementation introduces a dropdown UI component, persistence layer, and dynamic path resolution with automatic directory creation.

Key Changes:

  • New dropdown UI component for selecting output targets (PromptPilot default, GitHub Copilot, Cursor, or Custom)
  • Output target persistence using PropertiesComponent with backward compatibility for existing custom paths
  • Live destination preview showing the resolved path and directory existence status

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/main/kotlin/com/github/egarcia/promptpilot/file/ContextFileManager.kt Refactored output path resolution to use ContextOutputTarget enum, added backward compatibility logic for custom paths
src/main/kotlin/com/github/egarcia/promptpilot/toolWindow/PromptPilotToolWindowFactory.kt Added AI Output Target dropdown UI, custom field controls, live preview labels, and save functionality
src/main/kotlin/com/github/egarcia/promptpilot/Constants.kt Added OUTPUT_TARGET_KEY and new file constants for GitHub Copilot and Cursor paths
src/main/kotlin/com/github/egarcia/promptpilot/resources/StringKeys.kt Added string key constants for all new output target labels and messages
src/main/resources/messages/MyBundle.properties Added localized strings for output target UI, fixed typo in files.label
README.md Added "AI Output Targets" documentation section with feature table
gradle.properties Bumped version from 0.1.0 to 0.1.2
.github/labeler.yml Added refactor label pattern

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (2)

src/main/kotlin/com/github/egarcia/promptpilot/file/ContextFileManager.kt:81

  • The new output target resolution logic in ContextFileManager lacks test coverage. Consider adding tests for:
  • hasCustomOverrides() detection logic
  • resolveOutputLocation() for each target type
  • Path resolution with custom and preset targets
  • Directory creation behavior via ensureDirectoriesExist()
  • Backward compatibility with existing custom settings
    private val selectedTarget: ContextOutputTarget
        get() {
            val valueSet = properties.isValueSet(SettingsKeys.OUTPUT_TARGET_KEY)
            val storedTarget = properties.getValue(SettingsKeys.OUTPUT_TARGET_KEY)?.let { fromId(it) }
            if (storedTarget != null) return storedTarget

            if (!valueSet && hasCustomOverrides()) {
                return ContextOutputTarget.CUSTOM
            }
            return ContextOutputTarget.PROMPT_PILOT
        }

    private fun hasCustomOverrides(): Boolean {
        val customDir = properties.getValue(SettingsKeys.CUSTOM_OUTPUT_DIR)
        val customFile = properties.getValue(SettingsKeys.CUSTOM_OUTPUT_FILENAME)
        val dirDiffers = !customDir.isNullOrBlank() && customDir != FileConstants.OUTPUT_DIR
        val fileDiffers = !customFile.isNullOrBlank() && customFile != FileConstants.REPO_CONTEXT_FILENAME
        return dirDiffers || fileDiffers
    }

    private fun resolveOutputLocation(): OutputLocation {
        val target = selectedTarget
        val location = target.defaultLocation()
        val relativeDir = when {
            target.isCustom -> properties.getValue(SettingsKeys.CUSTOM_OUTPUT_DIR)?.takeUnless { it.isBlank() }
                ?: FileConstants.OUTPUT_DIR
            location != null -> location.relativeDir
            else -> FileConstants.OUTPUT_DIR
        }

        val filename = when {
            target.isCustom -> properties.getValue(SettingsKeys.CUSTOM_OUTPUT_FILENAME)?.takeUnless { it.isBlank() }
                ?: FileConstants.REPO_CONTEXT_FILENAME
            location != null -> location.filename
            else -> FileConstants.REPO_CONTEXT_FILENAME
        }

        return OutputLocation(relativeDir, filename)
    }

    fun ensureDirectoriesExist() {
        val location = resolveOutputLocation()
        var lastAttempted = FileConstants.SOURCE_CONTEXT_DIR
        runCatching {
            ensureDirectoryExists(sourceDir)
            lastAttempted = location.relativeDir
            val outputDir = Paths.get(basePath, location.relativeDir).normalize().toFile()
            ensureDirectoryExists(outputDir)
        }.onFailure { e ->
            throw IllegalStateException(
                MyBundle.message(
                    Strings.ERROR_CREATING_OUTPUT_DIRECTORY,
                    lastAttempted,
                    e.message ?: MyBundle.message(Strings.ERROR_UNKNOWN)
                )
            )
        }
    }

src/main/kotlin/com/github/egarcia/promptpilot/toolWindow/PromptPilotToolWindowFactory.kt:457

  • The save button action listener calls fileManager.ensureDirectoriesExist() but does not handle potential exceptions. If directory creation fails (e.g., due to permissions issues), the user will see no feedback in the UI, and the success message will still be displayed. Consider wrapping this call in a try-catch block and showing an error message if directory creation fails.
        saveButton.addActionListener {
            val target = selectedTarget()
            properties.setValue(SettingsKeys.OUTPUT_TARGET_KEY, target.id)
            if (target.isCustom) {
                properties.setValue(SettingsKeys.CUSTOM_OUTPUT_DIR, customOutputDirField.text.trim())
                properties.setValue(
                    SettingsKeys.CUSTOM_OUTPUT_FILENAME,
                    customOutputFileField.text.trim()
                )
            }
            fileManager.ensureDirectoriesExist()
            updateDestinationLabels()
            Messages.showInfoMessage(
                project,
                MyBundle.message(Strings.SAVE_OUTPUT_SETTINGS_SUCCESS),
                MyBundle.message(Strings.TOOL_WINDOW_TITLE)
            )
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread README.md
Comment thread gradle.properties Outdated
Comment thread src/main/kotlin/com/github/egarcia/promptpilot/file/ContextOutputTarget.kt Outdated
Comment thread src/main/kotlin/com/github/egarcia/promptpilot/Constants.kt
Comment thread README.md
@github-actions

github-actions Bot commented Nov 22, 2025

Copy link
Copy Markdown

Qodana Community for JVM

It seems all right 👌

No new problems were found according to the checks applied

💡 Qodana analysis was run in the pull request mode: only the changed files were checked

View the detailed Qodana report

To be able to view the detailed Qodana report, you can either:

To get *.log files or any other Qodana artifacts, run the action with upload-result option set to true,
so that the action will upload the files as the job artifacts:

      - name: 'Qodana Scan'
        uses: JetBrains/qodana-action@v2024.2.5
        with:
          upload-result: true
Contact Qodana team

Contact us at qodana-support@jetbrains.com

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

properties.setValue(
SettingsKeys.CUSTOM_OUTPUT_FILENAME,
customOutputFileField.text.trim()
)

Copilot AI Nov 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a non-CUSTOM target is selected and the save button is clicked, the custom output directory and filename properties are not cleared. This could lead to confusion if a user switches back to CUSTOM later, as they might see stale values. Consider clearing these properties when saving a non-CUSTOM target, or document this behavior if it's intentional.

Suggested change
)
)
} else {
properties.unsetValue(SettingsKeys.CUSTOM_OUTPUT_DIR)
properties.unsetValue(SettingsKeys.CUSTOM_OUTPUT_FILENAME)

Copilot uses AI. Check for mistakes.
output.target.github.copilot.label=GitHub Copilot (.github/copilot-instructions.md)
output.target.cursor.label=Cursor (.cursor/rules)
output.target.custom.label=Custom (choose directory + file)
output.target.custom.hint=Custom paths are resolved relative to the project root unless absolute.

Copilot AI Nov 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation in MyBundle.properties and the hint label claim that custom paths are resolved relative to the project root "unless absolute", but there's no explicit handling of absolute paths in resolveOutputLocation() or getOutputFile(). Paths.get(basePath, location.relativeDir) will treat an absolute path as relative if it doesn't start with the filesystem root. Consider either:

  1. Adding explicit logic to detect and handle absolute paths correctly
  2. Updating the documentation to clarify that paths should always be relative
Suggested change
output.target.custom.hint=Custom paths are resolved relative to the project root unless absolute.
output.target.custom.hint=Custom paths must be relative to the project root.

Copilot uses AI. Check for mistakes.
@e-Garcia
e-Garcia force-pushed the feature/pilot-50_ai-aware-context-output branch from 45a85e7 to 080fd56 Compare November 22, 2025 14:53
@e-Garcia e-Garcia modified the milestones: 1.2, 1.1 Nov 22, 2025
@e-Garcia
e-Garcia force-pushed the feature/pilot-50_ai-aware-context-output branch from 080fd56 to 45505b7 Compare November 22, 2025 15:09
@e-Garcia
e-Garcia requested a review from Copilot November 22, 2025 15:09

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread CHANGELOG.md Outdated
if (!fsOps.exists(outputDir)) fsOps.createDirectories(outputDir.toPath())
ensureDirectoryExists(sourceDir)
lastAttempted = location.relativeDir
val outputDir = Paths.get(basePath, location.relativeDir).normalize().toFile()

Copilot AI Nov 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom directory path from user input is used without validation for path traversal attempts. A malicious or accidental input like ../../etc could write files outside the project directory. Consider adding validation to ensure the resolved path after normalization is still within the project root, or at minimum warning the user when an absolute path or path traversal is detected.

Suggested change
val outputDir = Paths.get(basePath, location.relativeDir).normalize().toFile()
val outputDir = Paths.get(basePath, location.relativeDir).normalize().toFile()
// Validate that outputDir is within basePath to prevent path traversal
val baseCanonical = File(basePath).canonicalFile
val outputCanonical = outputDir.canonicalFile
if (!outputCanonical.path.startsWith(baseCanonical.path)) {
throw IllegalArgumentException(
MyBundle.message(
Strings.ERROR_CREATING_OUTPUT_DIRECTORY,
lastAttempted,
"Output directory is outside the project root: ${outputCanonical.path}"
)
)
}

Copilot uses AI. Check for mistakes.
Comment thread CHANGELOG.md
# PromptPilot Changelog

## [Unreleased]
## 1.1.0

Copilot AI Nov 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CHANGELOG.md indicates version "1.1.0" but gradle.properties sets the version to "0.1.1". These version numbers should be consistent - either both should use "0.1.1" (indicating a pre-1.0 release) or both should use "1.1.0" (indicating a post-1.0 release following the existing 1.0.0 entry in the changelog).

Suggested change
## 1.1.0
## 0.1.1

Copilot uses AI. Check for mistakes.
Comment thread src/main/kotlin/com/github/egarcia/promptpilot/file/ContextOutputTarget.kt Outdated
fileName
)
val dirFile = Paths.get(basePath, relativeDir).normalize().toFile()
if (dirFile.exists()) {

Copilot AI Nov 22, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the issue in ContextFileManager, the custom directory path from customOutputDirField.text is used directly without validation for path traversal. Consider validating that the resolved path stays within the project boundaries or displaying a warning when absolute paths or parent directory references are used.

Suggested change
if (dirFile.exists()) {
val baseDirFile = Paths.get(basePath).normalize().toFile()
val isWithinProject = try {
val dirCanonical = dirFile.canonicalPath
val baseCanonical = baseDirFile.canonicalPath
dirCanonical.startsWith(baseCanonical)
} catch (e: Exception) {
false
}
if (!isWithinProject) {
destinationStatusLabel.text =
MyBundle.message(Strings.OUTPUT_TARGET_STATUS_INVALID_PATH, relativeDir)
destinationStatusLabel.foreground = JBColor.RED
} else if (dirFile.exists()) {

Copilot uses AI. Check for mistakes.
@e-Garcia
e-Garcia force-pushed the feature/pilot-50_ai-aware-context-output branch 3 times, most recently from 257bc5e to 0a063e7 Compare November 22, 2025 15:37
   Description:

   - add AI Output Target dropdown with Copilot, Cursor, default, and custom presets
   - persist selections, auto-create target directories, and update docs to cover the new workflow

   Closes #50
@e-Garcia
e-Garcia force-pushed the feature/pilot-50_ai-aware-context-output branch from 0a063e7 to 4a7d2c0 Compare November 22, 2025 16:11
@e-Garcia
e-Garcia merged commit a1c336c into main Nov 22, 2025
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add AI picker dropdown

2 participants