Skip to content

Commit 7fc5b23

Browse files
exengstfeldEric EngstfeldCopilot
authored
feat: Add Alquimia AI integration (#2734)
* Add alquimia-ai as new integration: https://alquimia.ai * Fix test cases for alquimia-ai integration. Add alquimia-ai to workflow.yml * Add install url to alquimia-ai integration * Renamed alquimia-ai to alquimia (cli native denomination) * Fix unit tests for alquimia integration * Minor fix in alquimia integration * Fix typos and copilot findings * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Update tests cases and lint formatting * Final fixes * Rename alquimia_ai to alquimia module integration * Make cli optional for alquimiia integration * resolve review comments * Fix copilot review * Minor fixes: naming, remove unused code * Update tests cases. Fix issues * Fix unit tests * Add alquimia context to default agent-context extension. Update cli requirment to support workflows * Fix hints (suggestion) * Add Alquimia AI as agent in github issue template. Fix unit tests * Address review comments. Update docs * Update test cases --------- Co-authored-by: Eric Engstfeld <ericengstfeld@Erics-MacBook-Pro.local> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 118062e commit 7fc5b23

11 files changed

Lines changed: 792 additions & 2 deletions

File tree

.github/ISSUE_TEMPLATE/agent_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ body:
88
value: |
99
Thanks for requesting a new agent! Before submitting, please check if the agent is already supported.
1010
11-
**Currently supported agents**: Amp, Antigravity, Auggie CLI, Claude Code, Cline, CodeBuddy, Codex CLI, Cursor, Devin for Terminal, Factory Droid, Firebender, Forge, Gemini CLI, GitHub Copilot, Goose, Grok Build, Hermes Agent, IBM Bob, Junie, Kilo Code, Kimi Code, Kiro CLI, Lingma, Mistral Vibe, Oh My Pi, opencode, Pi Coding Agent, Qoder CLI, Qwen Code, RovoDev ACLI, SHAI, Tabnine CLI, Trae, ZCode, Zed
11+
**Currently supported agents**: Alquimia AI, Amp, Antigravity, Auggie CLI, Claude Code, Cline, CodeBuddy, Codex CLI, Cursor, Devin for Terminal, Factory Droid, Firebender, Forge, Gemini CLI, GitHub Copilot, Goose, Grok Build, Hermes Agent, IBM Bob, Junie, Kilo Code, Kimi Code, Kiro CLI, Lingma, Mistral Vibe, Oh My Pi, opencode, Pi Coding Agent, Qoder CLI, Qwen Code, RovoDev ACLI, SHAI, Tabnine CLI, Trae, ZCode, Zed
1212
1313
- type: input
1414
id: agent-name

.github/ISSUE_TEMPLATE/bug_report.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ body:
6262
label: AI Agent
6363
description: Which AI agent are you using?
6464
options:
65+
- Alquimia AI
6566
- Amp
6667
- Antigravity
6768
- Auggie CLI

.github/ISSUE_TEMPLATE/feature_request.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ body:
5656
description: Does this feature relate to a specific AI agent?
5757
options:
5858
- All agents
59+
- Alquimia AI
5960
- Amp
6061
- Antigravity
6162
- Auggie CLI

docs/reference/integrations.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ The Specify CLI supports a wide range of AI coding agents. When you run `specify
66

77
| Agent | Key | Notes |
88
| ------------------------------------------------------------------------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
9+
| [Alquimia AI](https://docs.alquimia.ai) | `alquimia` | Skills-based integration; installs skills into `.alquimia/skills` and invokes them as `/speckit-<command>` |
910
| [Amp](https://ampcode.com/) | `amp` | |
1011
| [Antigravity (agy)](https://antigravity.google/) | `agy` | Skills-based integration; skills are installed automatically |
1112
| [Auggie CLI](https://docs.augmentcode.com/cli/overview) | `auggie` | |
@@ -271,6 +272,7 @@ The currently declared multi-install safe integrations are:
271272

272273
| Key | Command directory |
273274
| --- | ----------------- |
275+
| `alquimia` | `.alquimia/skills` |
274276
| `auggie` | `.augment/commands` |
275277
| `claude` | `.claude/skills` |
276278
| `cline` | `.clinerules/workflows` |

extensions/agent-context/agent-context-defaults.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"_comment": "Default coding agent context file per integration, owned by the agent-context extension. Used to self-seed agent-context-config.yml when it declares no context_file/context_files. Keyed by the Spec Kit integration key recorded in .specify/init-options.json. This mapping is independent of the Specify CLI by design.",
33
"agents": {
44
"agy": "AGENTS.md",
5+
"alquimia": "ALQUIMIA.md",
56
"amp": "AGENTS.md",
67
"auggie": ".augment/rules/specify-rules.md",
78
"bob": "AGENTS.md",

integrations/catalog.json

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
{
22
"schema_version": "1.0",
3-
"updated_at": "2026-07-17T00:00:00Z",
3+
"updated_at": "2026-07-27T00:00:00Z",
44
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/integrations/catalog.json",
55
"integrations": {
6+
"alquimia": {
7+
"id": "alquimia",
8+
"name": "Alquimia AI",
9+
"version": "1.0.0",
10+
"description": "Alquimia AI CLI integration",
11+
"author": "spec-kit-core",
12+
"repository": "https://github.com/github/spec-kit",
13+
"tags": ["alquimia"]
14+
},
615
"claude": {
716
"id": "claude",
817
"name": "Claude Code",

src/specify_cli/integrations/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def _register_builtins() -> None:
4848
"""
4949
# -- Imports (alphabetical) -------------------------------------------
5050
from .agy import AgyIntegration
51+
from .alquimia import AlquimiaAIIntegration
5152
from .amp import AmpIntegration
5253
from .auggie import AuggieIntegration
5354
from .bob import BobIntegration
@@ -86,6 +87,7 @@ def _register_builtins() -> None:
8687

8788
# -- Registration (alphabetical) --------------------------------------
8889
_register(AgyIntegration())
90+
_register(AlquimiaAIIntegration())
8991
_register(AmpIntegration())
9092
_register(AuggieIntegration())
9193
_register(BobIntegration())
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
"""Alquimia AI integration."""
2+
3+
from __future__ import annotations
4+
5+
from typing import Any
6+
7+
from ..._utils import dump_frontmatter
8+
from ..base import SkillsIntegration
9+
10+
# Mapping of command template stem → argument-hint text shown inline
11+
# when a user invokes the slash command in Alquimia AI.
12+
ARGUMENT_HINTS: dict[str, str] = {
13+
"specify": "Describe the feature you want to specify",
14+
"plan": "Optional guidance for the planning phase",
15+
"tasks": "Optional task generation constraints",
16+
"implement": "Optional implementation guidance or task filter",
17+
"analyze": "Optional focus areas for analysis",
18+
"clarify": "Optional areas to clarify in the spec",
19+
"constitution": "Principles or values for the project constitution",
20+
"checklist": "Domain or focus area for the checklist",
21+
"taskstoissues": "Optional filter or label for GitHub issues",
22+
}
23+
24+
25+
class AlquimiaAIIntegration(SkillsIntegration):
26+
"""Integration for Alquimia AI skills."""
27+
28+
key = "alquimia"
29+
config = {
30+
"name": "Alquimia AI",
31+
"folder": ".alquimia/",
32+
"commands_subdir": "skills",
33+
"install_url": "https://docs.alquimia.ai",
34+
"requires_cli": True,
35+
}
36+
registrar_config = {
37+
"dir": ".alquimia/skills",
38+
"format": "markdown",
39+
"args": "$ARGUMENTS",
40+
"extension": "/SKILL.md",
41+
}
42+
multi_install_safe = True
43+
44+
def _render_skill(
45+
self, template_name: str, frontmatter: dict[str, Any], body: str
46+
) -> str:
47+
"""Render a processed command template as an Alquimia skill."""
48+
skill_name = f"speckit-{template_name.replace('.', '-')}"
49+
description = frontmatter.get(
50+
"description",
51+
f"Spec-kit workflow command: {template_name}",
52+
)
53+
skill_frontmatter = self._build_skill_fm(
54+
skill_name, description, f"templates/commands/{template_name}.md"
55+
)
56+
frontmatter_text = dump_frontmatter(skill_frontmatter)
57+
return f"---\n{frontmatter_text}\n---\n\n{body.strip()}\n"
58+
59+
def _build_skill_fm(self, name: str, description: str, source: str) -> dict:
60+
from specify_cli.agents import CommandRegistrar
61+
62+
return CommandRegistrar.build_skill_frontmatter(
63+
self.key, name, description, source
64+
)
65+
66+
@staticmethod
67+
def inject_argument_hint(content: str, hint: str) -> str:
68+
"""Insert ``argument-hint`` after the first ``description:`` in YAML frontmatter.
69+
70+
Skips injection if ``argument-hint:`` already exists in the
71+
frontmatter to avoid duplicate keys.
72+
"""
73+
lines = content.splitlines(keepends=True)
74+
75+
# Pre-scan: bail out if argument-hint already present in frontmatter
76+
dash_count = 0
77+
for line in lines:
78+
stripped = line.rstrip("\n\r")
79+
if stripped == "---":
80+
dash_count += 1
81+
if dash_count == 2:
82+
break
83+
continue
84+
if dash_count == 1 and stripped.startswith("argument-hint:"):
85+
return content # already present
86+
87+
out: list[str] = []
88+
in_fm = False
89+
dash_count = 0
90+
injected = False
91+
for line in lines:
92+
stripped = line.rstrip("\n\r")
93+
if stripped == "---":
94+
dash_count += 1
95+
in_fm = dash_count == 1
96+
out.append(line)
97+
continue
98+
if in_fm and not injected and stripped.startswith("description:"):
99+
out.append(line)
100+
# Preserve the exact line-ending style (\r\n vs \n)
101+
if line.endswith("\r\n"):
102+
eol = "\r\n"
103+
elif line.endswith("\n"):
104+
eol = "\n"
105+
else:
106+
eol = ""
107+
escaped = hint.replace("\\", "\\\\").replace('"', '\\"')
108+
out.append(f'argument-hint: "{escaped}"{eol}')
109+
injected = True
110+
continue
111+
out.append(line)
112+
return "".join(out)
113+
114+
@staticmethod
115+
def _inject_frontmatter_flag(content: str, key: str, value: str = "true") -> str:
116+
"""Insert ``key: value`` before the closing ``---`` if not already present."""
117+
lines = content.splitlines(keepends=True)
118+
119+
# Pre-scan: bail out if already present in frontmatter
120+
dash_count = 0
121+
for line in lines:
122+
stripped = line.rstrip("\n\r")
123+
if stripped == "---":
124+
dash_count += 1
125+
if dash_count == 2:
126+
break
127+
continue
128+
if dash_count == 1 and stripped.startswith(f"{key}:"):
129+
return content
130+
131+
# Inject before the closing --- of frontmatter
132+
out: list[str] = []
133+
dash_count = 0
134+
injected = False
135+
for line in lines:
136+
stripped = line.rstrip("\n\r")
137+
if stripped == "---":
138+
dash_count += 1
139+
if dash_count == 2 and not injected:
140+
if line.endswith("\r\n"):
141+
eol = "\r\n"
142+
elif line.endswith("\n"):
143+
eol = "\n"
144+
else:
145+
eol = ""
146+
out.append(f"{key}: {value}{eol}")
147+
injected = True
148+
out.append(line)
149+
return "".join(out)
150+
151+
def post_process_skill_content(self, content: str) -> str:
152+
"""Inject Alquimia-specific frontmatter flags, hints and hook notes."""
153+
updated = super().post_process_skill_content(content)
154+
updated = self._inject_frontmatter_flag(updated, "user-invocable")
155+
updated = self._inject_frontmatter_flag(
156+
updated, "disable-model-invocation", "false"
157+
)
158+
for line in updated.splitlines():
159+
if line.startswith("name:"):
160+
name = line.removeprefix("name:").strip().strip("\"'")
161+
hint = ARGUMENT_HINTS.get(name.removeprefix("speckit-"))
162+
if hint:
163+
updated = self.inject_argument_hint(updated, hint)
164+
break
165+
return updated

0 commit comments

Comments
 (0)