@ancleto/spec 0.7.2 → 0.8.0
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 +27 -7
- package/package.json +1 -1
- package/src/cli/index.js +251 -18
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ del host (el CLI no deduplica ni elige un "ganador").
|
|
|
138
138
|
| `opencode` (default) | `.opencode/skills/<n>/SKILL.md` | `.opencode/agents/<n>.md` | `.opencode/commands/<n>.md` |
|
|
139
139
|
| `claude` | `.claude/skills/<n>/SKILL.md` | `.claude/agents/<n>.md` (adaptado) | `.claude/commands/<n>.md` |
|
|
140
140
|
| `vscode` (VS Code / Copilot) | `.github/skills/<n>/SKILL.md` | `.github/agents/<n>.agent.md` | `.github/prompts/<n>.prompt.md` |
|
|
141
|
-
| `antigravity` | `.agents/skills/<n>/SKILL.md` |
|
|
141
|
+
| `antigravity` | `.agents/skills/<n>/SKILL.md` | `.agents/agents/<n>.md` (adaptado) | `.agents/skills/<n>/SKILL.md` (como skill) |
|
|
142
142
|
| `cursor` | `.cursor/skills/<n>/SKILL.md` | — | — |
|
|
143
143
|
| `roo` | `.roo/skills/<n>/SKILL.md` | — | — |
|
|
144
144
|
|
|
@@ -146,16 +146,36 @@ Cada host recibe sus skills en **su** directorio nativo. **`.agents/skills` es u
|
|
|
146
146
|
compartido** —lo leen opencode, Cursor, VS Code y Antigravity—, no un destino universal: Claude lee
|
|
147
147
|
`.claude/skills` y Roo, `.roo/skills`. Por eso el CLI no escribe todas las skills en `.agents/skills`.
|
|
148
148
|
|
|
149
|
-
|
|
149
|
+
**Antigravity** no tiene directorio de commands de proyecto; sus `/cleto-*` se materializan como
|
|
150
|
+
**command-skills** en `.agents/skills/<n>/SKILL.md` (los Workflows están deprecados a favor de Agent
|
|
151
|
+
Skills), por eso `installedPaths.commands` queda vacío para este host. Sus `agents` se escriben en
|
|
152
|
+
`.agents/agents/<n>.md` con el frontmatter adaptado: `name` inyectado, `tools` como **lista** de ids
|
|
153
|
+
verificados de Antigravity (`read`→`view_file`, `edit`→`replace_file_content`, `grep`→`grep_search`,
|
|
154
|
+
`bash`→`run_command`, `todowrite`→`manage_task`), `mode`→`mainAgent`/`subagent`,
|
|
155
|
+
`commandExecutionPolicy: sandbox` y `model: inherit` (el tier no reescribe modelos de Antigravity). Una
|
|
156
|
+
tool de opencode sin id verificado se **omite con aviso** (`skip tool '<k>': no verified Antigravity id
|
|
157
|
+
for agent '<n>'`): un id inexistente cuelga el subagent. Los ids que sólo existen en el SDK
|
|
158
|
+
(`find_file`, `edit_file`, `search_web`, `read_url_content`, …), los de comunidad (`write_to_file`,
|
|
159
|
+
`call_mcp_tool`, `multi_replace_file_content`, …) y los de delegación (`invoke_subagent`,
|
|
160
|
+
`start_subagent`, `define_subagent`) **no** se emiten; el uso de MCP se expresa por
|
|
161
|
+
`mcpServers`/`.agents/mcp_config.json`, nunca como tool id. El campo `skill` no es una tool: se cubre
|
|
162
|
+
con el surfaceo automático de skills de Antigravity.
|
|
163
|
+
|
|
164
|
+
Los assets que un host no soporta (por ejemplo `agents`/`commands` de `cursor` y `roo`)
|
|
150
165
|
**no se escriben** y el instalador avisa por `stderr`
|
|
151
|
-
(`skip agents: not supported by host '
|
|
166
|
+
(`skip agents: not supported by host 'cursor'`) sin abortar. La instalación es **aditiva y no
|
|
152
167
|
destructiva**: instalar con otro agente escribe en su base y preserva los layouts previos, y el
|
|
153
168
|
manifiesto `.ancletorc.installedPaths` acumula la unión de los destinos realmente escritos.
|
|
154
169
|
|
|
155
|
-
La **configuración MCP específica de host** está fuera de alcance
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
170
|
+
La **configuración MCP específica de host** está fuera de alcance con **una excepción**: para
|
|
171
|
+
`agent: antigravity` el CLI genera o mergea (no destructivo) el MCP de workspace
|
|
172
|
+
`.agents/mcp_config.json` con esquema `{ "mcpServers": { "<n>": { "command", "args", "env" } } }`,
|
|
173
|
+
incluyendo el servidor de memoria `ancleto-memory`; preserva `mcpServers` preexistentes y no pisa
|
|
174
|
+
homónimos. `.mcp.json` (Claude) y `.vscode/mcp.json` (VS Code) siguen fuera de alcance, al igual que el
|
|
175
|
+
MCP global de Antigravity. `claude` se soporta con adaptación de frontmatter obligatoria para `agents`
|
|
176
|
+
(se omiten `model`/`tools` sin equivalencia verificada y las claves
|
|
177
|
+
`mode`/`color`/`temperature`/`permission`), sin MCP propio. Tanto `init` como `install --project`
|
|
178
|
+
configuran el MCP del host en la misma ejecución; `--no-mcp` lo omite.
|
|
159
179
|
|
|
160
180
|
---
|
|
161
181
|
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -40,12 +40,14 @@ Uso:
|
|
|
40
40
|
nombre) y el adapter de frontmatter del host. NO arbitra el
|
|
41
41
|
descubrimiento entre hosts: si dos leen el mismo directorio, eso es
|
|
42
42
|
efecto del host (no se deduplica ni se elige "ganador").
|
|
43
|
+
antigravity instala skills, agents adaptados y commands como skills
|
|
44
|
+
(.agents/*) y mergea el MCP de workspace .agents/mcp_config.json.
|
|
43
45
|
ancleto update [--project <dir>] Alias de install (re-instala sobre lo existente)
|
|
44
46
|
Parado en un proyecto con .ancletorc opera sobre ese proyecto;
|
|
45
47
|
usa --global para forzar el alcance global
|
|
46
48
|
ancleto upgrade [--agent <nombre>] Re-aplica templates (LOCKED) y skills sobre el proyecto actual
|
|
47
|
-
ancleto init [--with-azure] [--agent <nombre>] [--tier <nivel>] [--lang <codigo>] [--exclude <globs>]
|
|
48
|
-
Crea .ancletorc en el repositorio actual
|
|
49
|
+
ancleto init [--with-azure] [--agent <nombre>] [--tier <nivel>] [--lang <codigo>] [--exclude <globs>] [--no-mcp]
|
|
50
|
+
Crea .ancletorc en el repositorio actual y configura el MCP del host
|
|
49
51
|
(interactivo en TTY: banner + menu; Azure desactivado por defecto)
|
|
50
52
|
ancleto discovery --check Estado del seed (READY/STALE/PARTIAL/MISSING)
|
|
51
53
|
ancleto discovery [--compress] [--include G] [--ignore G] [--token-budget N]
|
|
@@ -151,12 +153,28 @@ async function writeProjectsRegistry(reg) {
|
|
|
151
153
|
return file
|
|
152
154
|
}
|
|
153
155
|
|
|
156
|
+
// D9: directorios locales de agents derivados del host/manifiesto, no de
|
|
157
|
+
// `.opencode/agents` fijo. Un proyecto antigravity usa `.agents/agents`.
|
|
158
|
+
function localAgentDirs(rc, agent) {
|
|
159
|
+
const dirs = new Set(rc?.installedPaths?.agents || [])
|
|
160
|
+
const spec = AGENT_TARGETS[agent || rc?.agent]?.agents
|
|
161
|
+
if (spec?.dir) dirs.add(spec.dir)
|
|
162
|
+
return [...dirs]
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function hasLocalAgents(baseDir, rc, agent) {
|
|
166
|
+
for (const d of localAgentDirs(rc, agent)) {
|
|
167
|
+
if (await exists(join(baseDir, d))) return true
|
|
168
|
+
}
|
|
169
|
+
return false
|
|
170
|
+
}
|
|
171
|
+
|
|
154
172
|
async function registerProject(projectDir, { agent, tier } = {}) {
|
|
155
173
|
const abs = resolve(projectDir)
|
|
156
174
|
const rc = await readAncletorc(abs)
|
|
157
175
|
const tierFile = join(abs, '.opencode', '.ancleto-tier')
|
|
158
176
|
const localTier = (await exists(tierFile)) ? (await readFile(tierFile, 'utf8')).trim() : null
|
|
159
|
-
const hasScopedAgents = await
|
|
177
|
+
const hasScopedAgents = await hasLocalAgents(abs, rc, agent)
|
|
160
178
|
const entry = {
|
|
161
179
|
path: abs.replace(/\\/g, '/'),
|
|
162
180
|
version: await packageVersion(),
|
|
@@ -231,7 +249,7 @@ async function projectStatus(entry) {
|
|
|
231
249
|
if (!rc) return { ...entry, exists: false, reason: 'sin .ancletorc' }
|
|
232
250
|
const tierFile = join(abs, '.opencode', '.ancleto-tier')
|
|
233
251
|
const tier = (await exists(tierFile)) ? (await readFile(tierFile, 'utf8')).trim() : null
|
|
234
|
-
const scoped = await
|
|
252
|
+
const scoped = await hasLocalAgents(abs, rc, entry.agent)
|
|
235
253
|
return {
|
|
236
254
|
...entry,
|
|
237
255
|
exists: true,
|
|
@@ -729,6 +747,47 @@ async function mergeMcp(configDir, mcpMap) {
|
|
|
729
747
|
return { file: targets.join(', '), added: [...new Set(added)] }
|
|
730
748
|
}
|
|
731
749
|
|
|
750
|
+
// D6: MCP de workspace de Antigravity (`.agents/mcp_config.json`). Merge NO
|
|
751
|
+
// destructivo: se preservan `mcpServers` y demas claves top-level, no se pisa un
|
|
752
|
+
// servidor homonimo, y un JSON invalido se avisa sin sobrescribir. Esquema del host
|
|
753
|
+
// `{ command, args, env }` (se descartan `type`/`enabled` de opencode).
|
|
754
|
+
async function mergeAntigravityMcp(projectDir, mcpMap) {
|
|
755
|
+
if (Object.keys(mcpMap).length === 0) return { file: null, added: [] }
|
|
756
|
+
const file = join(projectDir, '.agents', 'mcp_config.json')
|
|
757
|
+
let cfg = {}
|
|
758
|
+
if (await exists(file)) {
|
|
759
|
+
try {
|
|
760
|
+
cfg = JSON.parse((await readFile(file, 'utf8')).replace(/^\uFEFF/, ''))
|
|
761
|
+
} catch {
|
|
762
|
+
console.warn(`ancleto: no se pudo leer ${basename(file)} como JSON; MCP de Antigravity no se modifico`)
|
|
763
|
+
return { file, added: [] }
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
cfg.mcpServers = cfg.mcpServers || {}
|
|
767
|
+
const added = []
|
|
768
|
+
for (const [name, def] of Object.entries(mcpMap)) {
|
|
769
|
+
if (cfg.mcpServers[name]) continue
|
|
770
|
+
const command = Array.isArray(def.command) ? def.command : [def.command]
|
|
771
|
+
const server = { command: command[0], args: command.slice(1) }
|
|
772
|
+
if (def.env) server.env = def.env
|
|
773
|
+
cfg.mcpServers[name] = server
|
|
774
|
+
added.push(name)
|
|
775
|
+
}
|
|
776
|
+
await mkdir(dirname(file), { recursive: true })
|
|
777
|
+
await writeFile(file, JSON.stringify(cfg, null, 2) + '\n')
|
|
778
|
+
return { file, added }
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// D6: dueno unico del MCP de host. Antigravity mergea `.agents/mcp_config.json`;
|
|
782
|
+
// el resto conserva `mergeMcp` sobre opencode.json (targetDir = .opencode del
|
|
783
|
+
// proyecto o config global).
|
|
784
|
+
async function setupHostMcp(targetDir, mcpMap, { projectDir = null, agent = null } = {}) {
|
|
785
|
+
if (Object.keys(mcpMap).length === 0) return { file: null, added: [] }
|
|
786
|
+
if (agent === 'antigravity' && projectDir) return mergeAntigravityMcp(projectDir, mcpMap)
|
|
787
|
+
await mkdir(targetDir, { recursive: true })
|
|
788
|
+
return mergeMcp(targetDir, mcpMap)
|
|
789
|
+
}
|
|
790
|
+
|
|
732
791
|
async function copyAssets(dest) {
|
|
733
792
|
await mkdir(dest, { recursive: true })
|
|
734
793
|
for (const d of ASSETS) {
|
|
@@ -755,9 +814,17 @@ const AGENT_TARGETS = {
|
|
|
755
814
|
agents: { dir: '.github/agents', ext: '.agent.md' },
|
|
756
815
|
commands: { dir: '.github/prompts', ext: '.prompt.md' }
|
|
757
816
|
},
|
|
758
|
-
// Antigravity
|
|
759
|
-
//
|
|
760
|
-
|
|
817
|
+
// Antigravity: agents adaptados en `.agents/agents` y commands materializados
|
|
818
|
+
// como skills dentro de `.agents/skills` (Antigravity no tiene dir de commands;
|
|
819
|
+
// los Workflows estan deprecados a favor de Agent Skills). `package: 'skill-dir'`
|
|
820
|
+
// marca que el asset se empaqueta como `<dir>/<n>/SKILL.md`, no como archivo plano.
|
|
821
|
+
antigravity: {
|
|
822
|
+
skills: { dir: '.agents/skills' },
|
|
823
|
+
agents: { dir: '.agents/agents', ext: MD_EXT },
|
|
824
|
+
commands: { dir: '.agents/skills', package: 'skill-dir' }
|
|
825
|
+
},
|
|
826
|
+
// Cursor y Roo: solo skills (D3/D7). .agents/skills es punto de lectura
|
|
827
|
+
// compartido (D2), no destino universal.
|
|
761
828
|
cursor: { skills: { dir: '.cursor/skills' }, agents: null, commands: null },
|
|
762
829
|
roo: { skills: { dir: '.roo/skills' }, agents: null, commands: null }
|
|
763
830
|
}
|
|
@@ -788,14 +855,119 @@ function serializeFrontmatter(parsed) {
|
|
|
788
855
|
return `${parsed.open}${inner}${parsed.close}${parsed.nl}${parsed.body}`
|
|
789
856
|
}
|
|
790
857
|
|
|
791
|
-
// D4/D5: hosts cuyo formato de agents difiere del origen requieren adaptacion
|
|
792
|
-
//
|
|
793
|
-
//
|
|
794
|
-
const AGENT_ADAPTER_HOSTS = new Set(['claude', 'vscode'])
|
|
858
|
+
// D4/D5: hosts cuyo formato de agents difiere del origen requieren adaptacion.
|
|
859
|
+
// Claude/VS Code eliminan las claves de opencode; Antigravity usa una
|
|
860
|
+
// transformacion propia (D2). El resto es identidad (preservacion verbatim).
|
|
861
|
+
const AGENT_ADAPTER_HOSTS = new Set(['claude', 'vscode', 'antigravity'])
|
|
795
862
|
const AGENT_ADAPTER_DROP = new Set(['mode', 'color', 'temperature', 'permission', 'model', 'tools'])
|
|
796
863
|
|
|
797
|
-
|
|
864
|
+
// D3: tabla unica (fijada contra la tabla oficial de frontmatter de Custom
|
|
865
|
+
// Subagents) de ids de Antigravity. Conjunto CERRADO: solo se emiten estos ids.
|
|
866
|
+
// Los ids solo-SDK (`find_file`, `edit_file`, `search_web`, `read_url_content`,
|
|
867
|
+
// ...), los de comunidad (`write_to_file`, `call_mcp_tool`,
|
|
868
|
+
// `multi_replace_file_content`, ...) y los de delegacion (`invoke_subagent`,
|
|
869
|
+
// `start_subagent`, `define_subagent`) NO se emiten: no estan confirmados en el
|
|
870
|
+
// frontmatter y un id inexistente cuelga el subagent (Known Issue). Una clave no
|
|
871
|
+
// listada aqui se omite con aviso a stderr, nunca en silencio (D5).
|
|
872
|
+
const ANTIGRAVITY_TOOL_MAP = {
|
|
873
|
+
read: 'view_file',
|
|
874
|
+
edit: 'replace_file_content',
|
|
875
|
+
grep: 'grep_search',
|
|
876
|
+
bash: 'run_command',
|
|
877
|
+
todowrite: 'manage_task'
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Hijos directos del mapa `tools:` con su valor crudo (p. ej. `read: true`).
|
|
881
|
+
function parseToolFlags(entry) {
|
|
882
|
+
const flags = []
|
|
883
|
+
if (!entry) return flags
|
|
884
|
+
let childIndent = null
|
|
885
|
+
for (const line of entry.lines.slice(1)) {
|
|
886
|
+
const m = /^(\s+)([A-Za-z0-9_-]+):\s*(.*?)\s*$/.exec(line)
|
|
887
|
+
if (!m) continue
|
|
888
|
+
if (childIndent === null) childIndent = m[1].length
|
|
889
|
+
if (m[1].length !== childIndent) continue
|
|
890
|
+
flags.push({ key: m[2], value: m[3] })
|
|
891
|
+
}
|
|
892
|
+
return flags
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function frontmatterValue(entry, fallback = null) {
|
|
896
|
+
if (!entry) return fallback
|
|
897
|
+
const m = /^[A-Za-z0-9_-]+:\s*(.*?)\s*$/.exec(entry.lines[0] || '')
|
|
898
|
+
return m ? m[1] : fallback
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// D2/D3/D5/D8: adapta el frontmatter de un agent de opencode al de Antigravity.
|
|
902
|
+
// `name` (requerido por el host) se inyecta desde el nombre del archivo. `tools` se
|
|
903
|
+
// emite siempre como lista (el default del host es la lista vacia). Nunca se inventa
|
|
904
|
+
// un id: una clave sin id verificado se omite con aviso (un id inexistente cuelga el
|
|
905
|
+
// subagent).
|
|
906
|
+
function adaptAntigravityFrontmatter(content, name) {
|
|
907
|
+
const parsed = parseFrontmatter(content)
|
|
908
|
+
if (!parsed.hasFrontmatter) return content
|
|
909
|
+
const byKey = (k) => parsed.entries.find((e) => e.key === k)
|
|
910
|
+
// Claves que el adaptador gestiona: las dropeadas (AGENT_ADAPTER_DROP), las que
|
|
911
|
+
// re-emite (name/tools/mainAgent/subagent/model/commandExecutionPolicy) y los campos
|
|
912
|
+
// que preserva del origen (description/skills/mcpServers). Se filtran TODAS del
|
|
913
|
+
// conjunto conservado para que ninguna pueda quedar duplicada, y se re-emiten una
|
|
914
|
+
// sola vez en orden determinista, sin importar lo que declare la fuente.
|
|
915
|
+
const managed = new Set([
|
|
916
|
+
...AGENT_ADAPTER_DROP,
|
|
917
|
+
'name',
|
|
918
|
+
'description',
|
|
919
|
+
'mainAgent',
|
|
920
|
+
'subagent',
|
|
921
|
+
'commandExecutionPolicy',
|
|
922
|
+
'mcpServers',
|
|
923
|
+
'skills'
|
|
924
|
+
])
|
|
925
|
+
// Se conservan las claves extra (no gestionadas); los campos preservados se
|
|
926
|
+
// re-emiten abajo desde su primera ocurrencia en el origen.
|
|
927
|
+
const kept = parsed.entries.filter((e) => e.key && !managed.has(e.key))
|
|
928
|
+
|
|
929
|
+
const toolIds = []
|
|
930
|
+
for (const { key, value } of parseToolFlags(byKey('tools'))) {
|
|
931
|
+
if (value === 'false') continue
|
|
932
|
+
if (key === 'skill') continue // no es tool: se cubre por `skills`/surfaceo automatico (OQ2)
|
|
933
|
+
const id = ANTIGRAVITY_TOOL_MAP[key]
|
|
934
|
+
if (id) {
|
|
935
|
+
toolIds.push(id)
|
|
936
|
+
continue
|
|
937
|
+
}
|
|
938
|
+
// D3/D5: regla dura. Un id fuera del frontmatter confirmado (exista o no en el
|
|
939
|
+
// SDK) no se emite: un id inexistente cuelga el subagent. Se omite con aviso,
|
|
940
|
+
// nunca en silencio. El uso de MCP NO se infiere desde claves desconocidas del
|
|
941
|
+
// mapa `tools`: se expresa por `mcpServers`/`.agents/mcp_config.json` (D6).
|
|
942
|
+
console.error(`skip tool '${key}': no verified Antigravity id for agent '${name}'`)
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
const mode = frontmatterValue(byKey('mode'))
|
|
946
|
+
const mainAgent = mode === 'subagent' ? 'false' : 'true'
|
|
947
|
+
const subagent = mode === 'primary' ? 'false' : 'true'
|
|
948
|
+
|
|
949
|
+
// Orden determinista: name, description, tools, mainAgent, subagent, model,
|
|
950
|
+
// commandExecutionPolicy, mcpServers, skills, y luego las claves extra conservadas.
|
|
951
|
+
const out = [{ key: 'name', lines: [`name: ${name}`] }]
|
|
952
|
+
const description = byKey('description')
|
|
953
|
+
if (description) out.push(description)
|
|
954
|
+
out.push({ key: 'tools', lines: [`tools: [${toolIds.join(', ')}]`] })
|
|
955
|
+
out.push({ key: 'mainAgent', lines: [`mainAgent: ${mainAgent}`] })
|
|
956
|
+
out.push({ key: 'subagent', lines: [`subagent: ${subagent}`] })
|
|
957
|
+
out.push({ key: 'model', lines: [`model: inherit`] })
|
|
958
|
+
out.push({ key: 'commandExecutionPolicy', lines: [`commandExecutionPolicy: sandbox`] })
|
|
959
|
+
const mcpServers = byKey('mcpServers')
|
|
960
|
+
if (mcpServers) out.push(mcpServers)
|
|
961
|
+
const skills = byKey('skills')
|
|
962
|
+
if (skills) out.push(skills)
|
|
963
|
+
out.push(...kept)
|
|
964
|
+
parsed.entries = out
|
|
965
|
+
return serializeFrontmatter(parsed)
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
function adaptFrontmatter(content, host, assetKind, name) {
|
|
798
969
|
if (assetKind !== 'agents' || !AGENT_ADAPTER_HOSTS.has(host)) return content
|
|
970
|
+
if (host === 'antigravity') return adaptAntigravityFrontmatter(content, name)
|
|
799
971
|
const parsed = parseFrontmatter(content)
|
|
800
972
|
if (!parsed.hasFrontmatter) return content
|
|
801
973
|
parsed.entries = parsed.entries.filter((e) => !(e.key && AGENT_ADAPTER_DROP.has(e.key)))
|
|
@@ -829,7 +1001,30 @@ async function installAssetFiles(cat, projectDir, spec, host) {
|
|
|
829
1001
|
if (!e.isFile() || !e.name.endsWith(MD_EXT)) continue
|
|
830
1002
|
const base = e.name.slice(0, -MD_EXT.length)
|
|
831
1003
|
const content = await readFile(join(ROOT, cat, e.name), 'utf8')
|
|
832
|
-
await writeFile(join(destDir, `${base}${spec.ext}`), adaptFrontmatter(content, host, cat))
|
|
1004
|
+
await writeFile(join(destDir, `${base}${spec.ext}`), adaptFrontmatter(content, host, cat, base))
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// D4: un command de ancleto convertido en skill (`name` + `description` del origen,
|
|
1009
|
+
// body verbatim). Antigravity no tiene dir de commands: sus `/cleto-*` viven como
|
|
1010
|
+
// command-skills dentro de `.agents/skills`.
|
|
1011
|
+
function commandToSkill(content, name) {
|
|
1012
|
+
const parsed = parseFrontmatter(content)
|
|
1013
|
+
const desc = parsed.hasFrontmatter ? parsed.entries.find((e) => e.key === 'description') : null
|
|
1014
|
+
const head = ['---', `name: ${name}`, ...(desc ? desc.lines : []), '---'].join('\n')
|
|
1015
|
+
const body = parsed.hasFrontmatter ? parsed.body : content
|
|
1016
|
+
return `${head}\n${body}`
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Materializa `commands/*.md` como `.agents/skills/<n>/SKILL.md` (asset empaquetado).
|
|
1020
|
+
async function installCommandSkills(projectDir, spec) {
|
|
1021
|
+
const destBase = join(projectDir, spec.dir)
|
|
1022
|
+
for (const e of await readdir(join(ROOT, 'commands'), { withFileTypes: true })) {
|
|
1023
|
+
if (!e.isFile() || !e.name.endsWith(MD_EXT)) continue
|
|
1024
|
+
const base = e.name.slice(0, -MD_EXT.length)
|
|
1025
|
+
const content = await readFile(join(ROOT, 'commands', e.name), 'utf8')
|
|
1026
|
+
await mkdir(join(destBase, base), { recursive: true })
|
|
1027
|
+
await writeFile(join(destBase, base, 'SKILL.md'), commandToSkill(content, base))
|
|
833
1028
|
}
|
|
834
1029
|
}
|
|
835
1030
|
|
|
@@ -852,6 +1047,12 @@ async function installAgentAssets(projectDir, agent) {
|
|
|
852
1047
|
warnUnsupportedAsset(cat, agent)
|
|
853
1048
|
continue
|
|
854
1049
|
}
|
|
1050
|
+
// D4: un asset empaquetado (commands de Antigravity) vive dentro del layout de
|
|
1051
|
+
// skills; no se registra un directorio de commands propio en installedPaths.
|
|
1052
|
+
if (spec.package === 'skill-dir') {
|
|
1053
|
+
await installCommandSkills(projectDir, spec)
|
|
1054
|
+
continue
|
|
1055
|
+
}
|
|
855
1056
|
await installAssetFiles(cat, projectDir, spec, agent)
|
|
856
1057
|
written[cat].push(spec.dir)
|
|
857
1058
|
}
|
|
@@ -877,6 +1078,16 @@ function installedExtFor(cat, dirRel) {
|
|
|
877
1078
|
return null
|
|
878
1079
|
}
|
|
879
1080
|
|
|
1081
|
+
// D9: hosts realmente instalados derivados del manifiesto (union de destinos
|
|
1082
|
+
// escritos), no de un unico `rc.agent`. Un proyecto multi-host debe sumar los
|
|
1083
|
+
// esperados de todos los hosts para no reportar huerfanos falsos.
|
|
1084
|
+
function installedHostsFromPaths(ip) {
|
|
1085
|
+
const dirs = new Set([...(ip?.skills || []), ...(ip?.agents || []), ...(ip?.commands || [])])
|
|
1086
|
+
return Object.entries(AGENT_TARGETS)
|
|
1087
|
+
.filter(([, t]) => ['skills', 'agents', 'commands'].some((cat) => t[cat]?.dir && dirs.has(t[cat].dir)))
|
|
1088
|
+
.map(([host]) => host)
|
|
1089
|
+
}
|
|
1090
|
+
|
|
880
1091
|
const CHANGES_ROOT = 'aspec'
|
|
881
1092
|
|
|
882
1093
|
const DEFAULT_ASPEC_CONFIG = `# aspec project configuration
|
|
@@ -1115,7 +1326,7 @@ async function install(args) {
|
|
|
1115
1326
|
}
|
|
1116
1327
|
}
|
|
1117
1328
|
|
|
1118
|
-
const res = await
|
|
1329
|
+
const res = await setupHostMcp(target, mcpMap, { projectDir, agent })
|
|
1119
1330
|
const loc = projectDir
|
|
1120
1331
|
? `${projectDir} (layout ${agent} + templates en la raiz)`
|
|
1121
1332
|
: `${target} (disponible en todos tus proyectos)`
|
|
@@ -1158,6 +1369,7 @@ async function upgradeCmd(args) {
|
|
|
1158
1369
|
async function initProject(args) {
|
|
1159
1370
|
const projectDir = process.cwd()
|
|
1160
1371
|
const withAzure = args.includes('--with-azure')
|
|
1372
|
+
const withMcp = !args.includes('--no-mcp')
|
|
1161
1373
|
const agentFlag = scanAgentFlag(args)
|
|
1162
1374
|
const tierFlag = scanTierFlag(args)
|
|
1163
1375
|
const langFlag = scanLangFlag(args)
|
|
@@ -1237,7 +1449,12 @@ async function initProject(args) {
|
|
|
1237
1449
|
await scaffoldAspec(projectDir)
|
|
1238
1450
|
await refreshWorkingContext(projectDir)
|
|
1239
1451
|
await registerProject(projectDir, { agent, tier: tier || null })
|
|
1452
|
+
// D7: init configura el MCP del host igual que install (mismo dueno, sin rama
|
|
1453
|
+
// especial por host); --no-mcp conserva el escape.
|
|
1454
|
+
const mcpMap = withMcp ? buildDefaultMcp({}) : {}
|
|
1455
|
+
const mcpRes = await setupHostMcp(join(projectDir, '.opencode'), mcpMap, { projectDir, agent })
|
|
1240
1456
|
if (azure.enabled) console.log(AZURE_MCP_NOTICE)
|
|
1457
|
+
if (mcpRes.added.length) console.log(`ancleto: MCP configurados: ${mcpRes.added.join(', ')} en ${mcpRes.file}`)
|
|
1241
1458
|
console.log(`ancleto: .ancletorc actualizado en ${projectDir} (v${manifest.version})${azure.enabled ? ' (Azure habilitado)' : ' (Azure desactivado)'} (Agente: ${agent})${tier ? ` (Tier: ${tier})` : ''} (Idioma: ${language})`)
|
|
1242
1459
|
}
|
|
1243
1460
|
|
|
@@ -1693,6 +1910,8 @@ async function checkCommand() {
|
|
|
1693
1910
|
process.exit(1)
|
|
1694
1911
|
}
|
|
1695
1912
|
const ip = rc.installedPaths
|
|
1913
|
+
// D9: la union de hosts instalados se lee del manifiesto, no de un unico `rc.agent`.
|
|
1914
|
+
const installedHosts = installedHostsFromPaths(ip)
|
|
1696
1915
|
let missing = 0
|
|
1697
1916
|
let orphans = 0
|
|
1698
1917
|
|
|
@@ -1716,7 +1935,18 @@ async function checkCommand() {
|
|
|
1716
1935
|
// La convencion de nombre del host (p. ej. vscode: .agent.md/.prompt.md)
|
|
1717
1936
|
// se deriva del mismo modelo de destino; en skills no cambia el nombre.
|
|
1718
1937
|
const ext = installedExtFor(cat, dirRel)
|
|
1719
|
-
|
|
1938
|
+
let expected = (await readdir(join(ROOT, cat))).sort().map((f) => (ext ? f.replace(/\.md$/, ext) : f))
|
|
1939
|
+
// D9: los command-skills de Antigravity viven dentro de `.agents/skills`; el
|
|
1940
|
+
// conjunto esperado de ese dir suma el catalogo de skills y los commands de
|
|
1941
|
+
// cualquier host instalado que empaquete commands ahi (union, no rc.agent).
|
|
1942
|
+
const packagesCommandsHere = cat === 'skills' && installedHosts.some((h) => {
|
|
1943
|
+
const cs = AGENT_TARGETS[h].commands
|
|
1944
|
+
return cs?.package === 'skill-dir' && cs.dir === dirRel
|
|
1945
|
+
})
|
|
1946
|
+
if (packagesCommandsHere) {
|
|
1947
|
+
const cmds = (await readdir(join(ROOT, 'commands'))).filter((f) => f.endsWith(MD_EXT)).map((f) => f.slice(0, -MD_EXT.length))
|
|
1948
|
+
expected = [...new Set([...expected, ...cmds])].sort()
|
|
1949
|
+
}
|
|
1720
1950
|
const actual = (await readdir(destDir)).sort()
|
|
1721
1951
|
const missingFiles = expected.filter((f) => !actual.includes(f))
|
|
1722
1952
|
const orphanFiles = actual.filter((f) => !expected.includes(f))
|
|
@@ -1749,10 +1979,13 @@ async function checkTierOrphan(cwd) {
|
|
|
1749
1979
|
if (!(await exists(tierFile))) return warnings
|
|
1750
1980
|
const tier = (await readFile(tierFile, 'utf8')).trim()
|
|
1751
1981
|
if (!TIERS[tier]) return warnings
|
|
1752
|
-
|
|
1982
|
+
// D9: se cuentan los agents locales del host realmente instalados, no
|
|
1983
|
+
// `.opencode/agents` fijo (un proyecto antigravity usa `.agents/agents`).
|
|
1984
|
+
const rc = await readAncletorc(cwd)
|
|
1753
1985
|
let count = 0
|
|
1754
|
-
|
|
1755
|
-
|
|
1986
|
+
for (const d of localAgentDirs(rc)) {
|
|
1987
|
+
const p = join(cwd, d)
|
|
1988
|
+
if (await exists(p)) count += (await readdir(p)).filter((f) => f.endsWith(MD_EXT)).length
|
|
1756
1989
|
}
|
|
1757
1990
|
if (count === 0) {
|
|
1758
1991
|
warnings.push(`tier "${tier}" sin agentes locales donde aplicarlo — este proyecto usa los agentes globales. Corre 'ancleto install --project .' para que el tier aplique, o borra .opencode/.ancleto-tier.`)
|