feat(ai): support a project-level prompt template

ai.prompt_template is now prepended to every generated prompt.txt,
ahead of the task description, as a 'Project conventions:' block.
Threaded through ai_select()/build_prompt_only_txt() in selector.py,
run_ai_selection() in cli_ai.py, and the --prompt call site in cli.py.
This commit is contained in:
akadeepesh
2026-08-26 14:44:13 +05:30
parent e65e7b19c7
commit 2dedeb48dc
2 changed files with 38 additions and 11 deletions
+33 -11
View File
@@ -33,6 +33,7 @@ def ai_select(
ecosystem: str,
api_key: str,
max_files: int | None = None,
prompt_template: str = "",
) -> tuple[list[Path], str, str]:
"""
Select the minimum relevant files for *prompt* from *resolved.included*.
@@ -41,6 +42,10 @@ def ai_select(
project's `ai.max_files` preference (.contextzip/config.json). None
falls back to each backend's own built-in default cap.
*prompt_template*, if non-empty, is a project's `ai.prompt_template`
preference — house conventions prepended to the generated prompt.txt
ahead of the task description.
Returns
-------
(selected_paths, prompt_txt, method)
@@ -78,14 +83,20 @@ def ai_select(
}
selected_paths = [rel_to_abs[rel] for rel in selected_rel if rel in rel_to_abs]
prompt_txt = _build_prompt_txt(prompt, selected_rel, ecosystem, method)
prompt_txt = _build_prompt_txt(
prompt, selected_rel, ecosystem, method, prompt_template=prompt_template
)
return selected_paths, prompt_txt, method
def build_prompt_only_txt(prompt: str, ecosystem: str) -> str:
def build_prompt_only_txt(
prompt: str, ecosystem: str, prompt_template: str = ""
) -> str:
"""Build a minimal prompt.txt when no AI selection was performed."""
return _build_prompt_txt(prompt, [], ecosystem, USED_GEMINI)
return _build_prompt_txt(
prompt, [], ecosystem, USED_GEMINI, prompt_template=prompt_template
)
# ---------------------------------------------------------------------------
@@ -125,12 +136,14 @@ def _build_prompt_txt(
selected_rel: list[str],
ecosystem: str,
method: str,
prompt_template: str = "",
) -> str:
"""
Build the prompt.txt to include inside the ZIP.
Any AI tool that receives the ZIP immediately sees the task description,
the framework, and exactly which files were selected and why.
Any AI tool that receives the ZIP immediately sees the project's own
conventions (if any are configured), the task description, the
framework, and exactly which files were selected and why.
"""
selector_label = (
@@ -139,12 +152,21 @@ def _build_prompt_txt(
else "contextzip (keyword heuristic — Gemini was rate limited)"
)
lines: list[str] = [
f"Task: {prompt}",
"",
f"Framework: {ecosystem}",
"",
]
lines: list[str] = []
if prompt_template:
lines.append("Project conventions:")
lines.append(prompt_template)
lines.append("")
lines.extend(
[
f"Task: {prompt}",
"",
f"Framework: {ecosystem}",
"",
]
)
if selected_rel:
lines.append(f"Files selected by {selector_label}:")
+5
View File
@@ -61,6 +61,7 @@ def run_ai_selection(
ecosystem: str,
api_key: str,
max_files: int | None = None,
prompt_template: str = "",
con: Console = console,
) -> tuple[list[Path], str]:
"""
@@ -70,6 +71,9 @@ def run_ai_selection(
project's `ai.max_files` preference (.contextzip/config.json). None
falls back to each backend's own built-in default.
*prompt_template*, if non-empty, is a project's `ai.prompt_template`
preference, prepended to the generated prompt.txt.
Handles progress display, heuristic-fallback warning, and Gemini
errors. Raises SystemExit(1) on hard failure.
"""
@@ -86,6 +90,7 @@ def run_ai_selection(
ecosystem=ecosystem,
api_key=api_key,
max_files=max_files,
prompt_template=prompt_template,
)
except GeminiError as exc:
con.print(