@ancleto/spec 0.7.1 → 0.7.2
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 +35 -1
- package/package.json +1 -1
- package/src/cli/index.js +164 -39
package/README.md
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
- [Características principales](#características-principales)
|
|
24
24
|
- [Requisitos](#requisitos)
|
|
25
25
|
- [Instalación](#instalación)
|
|
26
|
+
- [Hosts soportados y layouts por agente](#hosts-soportados-y-layouts-por-agente)
|
|
26
27
|
- [Guía de .gitignore](#guía-de-gitignore)
|
|
27
28
|
- [Configuración y tiers de costo](#configuración-y-tiers-de-costo)
|
|
28
29
|
- [Uso rápido](#uso-rápido)
|
|
@@ -39,7 +40,7 @@
|
|
|
39
40
|
|
|
40
41
|
**Ancleto** (alias de CLI: `aspec`) es un orquestador ligero para desarrollo de software asistido por IA bajo el paradigma **SDD (Spec-Driven Development)**.
|
|
41
42
|
|
|
42
|
-
Vive dentro de tu entorno de desarrollo (OpenCode, Cursor, VS Code, Roo, etc.) y automatiza tres cosas que normalmente hacés a mano:
|
|
43
|
+
Vive dentro de tu entorno de desarrollo (OpenCode, Claude, Cursor, VS Code, Roo, etc.) y automatiza tres cosas que normalmente hacés a mano:
|
|
43
44
|
|
|
44
45
|
- **Especificar** — escritura guiada de especificaciones y planes de implementación.
|
|
45
46
|
- **Descubrir** — relevamiento topológico del repositorio y empaquetado de contexto.
|
|
@@ -125,6 +126,39 @@ Para medir el consumo **real** por sesión (no estimado): `ancleto stats` lee la
|
|
|
125
126
|
|
|
126
127
|
---
|
|
127
128
|
|
|
129
|
+
## Hosts soportados y layouts por agente
|
|
130
|
+
|
|
131
|
+
`--agent <nombre>` selecciona **el layout nativo a materializar** (el directorio y la convención de
|
|
132
|
+
nombre de cada asset) y el **pipeline de adaptación de frontmatter** de ese host. No arbitra qué
|
|
133
|
+
descubre cada host: si dos hosts leen el mismo directorio, el descubrimiento compartido es un efecto
|
|
134
|
+
del host (el CLI no deduplica ni elige un "ganador").
|
|
135
|
+
|
|
136
|
+
| Host (`--agent`) | skills | agents | commands |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| `opencode` (default) | `.opencode/skills/<n>/SKILL.md` | `.opencode/agents/<n>.md` | `.opencode/commands/<n>.md` |
|
|
139
|
+
| `claude` | `.claude/skills/<n>/SKILL.md` | `.claude/agents/<n>.md` (adaptado) | `.claude/commands/<n>.md` |
|
|
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` | — (no soportado) | — (no soportado) |
|
|
142
|
+
| `cursor` | `.cursor/skills/<n>/SKILL.md` | — | — |
|
|
143
|
+
| `roo` | `.roo/skills/<n>/SKILL.md` | — | — |
|
|
144
|
+
|
|
145
|
+
Cada host recibe sus skills en **su** directorio nativo. **`.agents/skills` es un punto de lectura
|
|
146
|
+
compartido** —lo leen opencode, Cursor, VS Code y Antigravity—, no un destino universal: Claude lee
|
|
147
|
+
`.claude/skills` y Roo, `.roo/skills`. Por eso el CLI no escribe todas las skills en `.agents/skills`.
|
|
148
|
+
|
|
149
|
+
Los assets que un host no soporta (por ejemplo `agents`/`commands` de `antigravity`, `cursor` y `roo`)
|
|
150
|
+
**no se escriben** y el instalador avisa por `stderr`
|
|
151
|
+
(`skip agents: not supported by host 'antigravity'`) sin abortar. La instalación es **aditiva y no
|
|
152
|
+
destructiva**: instalar con otro agente escribe en su base y preserva los layouts previos, y el
|
|
153
|
+
manifiesto `.ancletorc.installedPaths` acumula la unión de los destinos realmente escritos.
|
|
154
|
+
|
|
155
|
+
La **configuración MCP específica de host** está fuera de alcance: el CLI no genera ni transforma
|
|
156
|
+
`.mcp.json`, `mcp_config.json` ni `.vscode/mcp.json`, y no pisa config MCP preexistente. `claude` se
|
|
157
|
+
soporta con adaptación de frontmatter obligatoria para `agents` (se omiten `model`/`tools` sin
|
|
158
|
+
equivalencia verificada y las claves `mode`/`color`/`temperature`/`permission`), sin MCP propio.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
128
162
|
## Guía de .gitignore
|
|
129
163
|
|
|
130
164
|
El framework **no gestiona tu `.gitignore`**: es una decisión del proyecto. Como referencia, esta es la combinación que funciona bien con Ancleto:
|
package/package.json
CHANGED
package/src/cli/index.js
CHANGED
|
@@ -35,7 +35,11 @@ Uso:
|
|
|
35
35
|
ancleto install --tier <nivel> normal | minimo | gratis (wizard interactivo en TTY)
|
|
36
36
|
ancleto install --lang <codigo> auto | es | en | pt (idioma de los artifacts; auto = idioma de la conversacion)
|
|
37
37
|
ancleto install --exclude <globs> Paths excluidos del discovery, coma-separados (ej: "**/*.png,docs")
|
|
38
|
-
ancleto install --agent <nombre> opencode | vscode | antigravity | cursor | roo (wizard si no esta guardado)
|
|
38
|
+
ancleto install --agent <nombre> opencode | claude | vscode | antigravity | cursor | roo (wizard si no esta guardado)
|
|
39
|
+
Elige el layout nativo a materializar (directorio + convencion de
|
|
40
|
+
nombre) y el adapter de frontmatter del host. NO arbitra el
|
|
41
|
+
descubrimiento entre hosts: si dos leen el mismo directorio, eso es
|
|
42
|
+
efecto del host (no se deduplica ni se elige "ganador").
|
|
39
43
|
ancleto update [--project <dir>] Alias de install (re-instala sobre lo existente)
|
|
40
44
|
Parado en un proyecto con .ancletorc opera sobre ese proyecto;
|
|
41
45
|
usa --global para forzar el alcance global
|
|
@@ -591,13 +595,13 @@ async function applyTier(agentsDir, tier, models = null) {
|
|
|
591
595
|
}
|
|
592
596
|
}
|
|
593
597
|
|
|
594
|
-
const SUPPORTED_AGENTS = ['opencode', 'vscode', 'antigravity', 'cursor', 'roo']
|
|
598
|
+
const SUPPORTED_AGENTS = ['opencode', 'claude', 'vscode', 'antigravity', 'cursor', 'roo']
|
|
595
599
|
const DEFAULT_AGENT = 'opencode'
|
|
596
600
|
|
|
597
601
|
function askAgent() {
|
|
598
602
|
return new Promise((resolve) => {
|
|
599
603
|
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
600
|
-
rl.question(
|
|
604
|
+
rl.question(`Agente/IDE [${SUPPORTED_AGENTS.join('/')}] (default: ${DEFAULT_AGENT}): `, (a) => {
|
|
601
605
|
rl.close()
|
|
602
606
|
const t = a.trim().toLowerCase()
|
|
603
607
|
resolve(SUPPORTED_AGENTS.includes(t) ? t : DEFAULT_AGENT)
|
|
@@ -732,32 +736,145 @@ async function copyAssets(dest) {
|
|
|
732
736
|
}
|
|
733
737
|
}
|
|
734
738
|
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
739
|
+
// D1: destino nativo por host. Cada asset declara su directorio y su convencion
|
|
740
|
+
// de nombre (ext); null = asset no soportado por ese host. Fuente unica de rutas.
|
|
741
|
+
const MD_EXT = '.md'
|
|
742
|
+
const AGENT_TARGETS = {
|
|
743
|
+
opencode: {
|
|
744
|
+
skills: { dir: '.opencode/skills' },
|
|
745
|
+
agents: { dir: '.opencode/agents', ext: MD_EXT },
|
|
746
|
+
commands: { dir: '.opencode/commands', ext: MD_EXT }
|
|
747
|
+
},
|
|
748
|
+
claude: {
|
|
749
|
+
skills: { dir: '.claude/skills' },
|
|
750
|
+
agents: { dir: '.claude/agents', ext: MD_EXT },
|
|
751
|
+
commands: { dir: '.claude/commands', ext: MD_EXT }
|
|
752
|
+
},
|
|
753
|
+
vscode: {
|
|
754
|
+
skills: { dir: '.github/skills' },
|
|
755
|
+
agents: { dir: '.github/agents', ext: '.agent.md' },
|
|
756
|
+
commands: { dir: '.github/prompts', ext: '.prompt.md' }
|
|
757
|
+
},
|
|
758
|
+
// Antigravity, Cursor y Roo: solo skills (D3/D7). .agents/skills es punto de
|
|
759
|
+
// lectura compartido (D2), no destino universal.
|
|
760
|
+
antigravity: { skills: { dir: '.agents/skills' }, agents: null, commands: null },
|
|
761
|
+
cursor: { skills: { dir: '.cursor/skills' }, agents: null, commands: null },
|
|
762
|
+
roo: { skills: { dir: '.roo/skills' }, agents: null, commands: null }
|
|
741
763
|
}
|
|
742
764
|
|
|
743
765
|
const ANCLETO_SKILLS = ['ancleto-new', 'ancleto-propose', 'ancleto-apply', 'ancleto-verify', 'ancleto-archive', 'ancleto-bulk-archive', 'ancleto-continue', 'ancleto-explore', 'ancleto-ff', 'ancleto-onboard', 'ancleto-workflow']
|
|
744
766
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
767
|
+
// Frontmatter: parser propio sin dependencias. Cada entrada conserva su clave y
|
|
768
|
+
// sus lineas crudas (incluye block scalars y mapas anidados), preservando orden.
|
|
769
|
+
function parseFrontmatter(content) {
|
|
770
|
+
const m = /^(---\r?\n)([\s\S]*?)(\r?\n---)(\r?\n?)([\s\S]*)$/.exec(content)
|
|
771
|
+
if (!m) return { hasFrontmatter: false, content }
|
|
772
|
+
const entries = []
|
|
773
|
+
for (const line of m[2].split(/\r?\n/)) {
|
|
774
|
+
if (/^[A-Za-z0-9_-]+:/.test(line)) {
|
|
775
|
+
entries.push({ key: line.slice(0, line.indexOf(':')), lines: [line] })
|
|
776
|
+
} else if (entries.length) {
|
|
777
|
+
entries[entries.length - 1].lines.push(line)
|
|
778
|
+
} else {
|
|
779
|
+
entries.push({ key: null, lines: [line] })
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return { hasFrontmatter: true, open: m[1], entries, close: m[3], nl: m[4], body: m[5] }
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function serializeFrontmatter(parsed) {
|
|
786
|
+
if (!parsed.hasFrontmatter) return parsed.content
|
|
787
|
+
const inner = parsed.entries.flatMap((e) => e.lines).join('\n')
|
|
788
|
+
return `${parsed.open}${inner}${parsed.close}${parsed.nl}${parsed.body}`
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// D4/D5: hosts cuyo formato de agents difiere del origen requieren adaptacion
|
|
792
|
+
// (hoy Claude y VS Code). El resto es identidad (preservacion verbatim). Nunca se
|
|
793
|
+
// inventan equivalencias de model/tools.
|
|
794
|
+
const AGENT_ADAPTER_HOSTS = new Set(['claude', 'vscode'])
|
|
795
|
+
const AGENT_ADAPTER_DROP = new Set(['mode', 'color', 'temperature', 'permission', 'model', 'tools'])
|
|
796
|
+
|
|
797
|
+
function adaptFrontmatter(content, host, assetKind) {
|
|
798
|
+
if (assetKind !== 'agents' || !AGENT_ADAPTER_HOSTS.has(host)) return content
|
|
799
|
+
const parsed = parseFrontmatter(content)
|
|
800
|
+
if (!parsed.hasFrontmatter) return content
|
|
801
|
+
parsed.entries = parsed.entries.filter((e) => !(e.key && AGENT_ADAPTER_DROP.has(e.key)))
|
|
802
|
+
return serializeFrontmatter(parsed)
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// D12: asset no soportado -> no se escribe y se avisa a stderr (no bloqueante).
|
|
806
|
+
function warnUnsupportedAsset(asset, agent) {
|
|
807
|
+
console.error(`skip ${asset}: not supported by host '${agent}'`)
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async function copyDirTransformed(srcDir, destDir, host, assetKind) {
|
|
811
|
+
await mkdir(destDir, { recursive: true })
|
|
812
|
+
for (const e of await readdir(srcDir, { withFileTypes: true })) {
|
|
813
|
+
const src = join(srcDir, e.name)
|
|
814
|
+
const dest = join(destDir, e.name)
|
|
815
|
+
if (e.isDirectory()) {
|
|
816
|
+
await copyDirTransformed(src, dest, host, assetKind)
|
|
817
|
+
} else if (e.isFile() && e.name.endsWith(MD_EXT)) {
|
|
818
|
+
await writeFile(dest, adaptFrontmatter(await readFile(src, 'utf8'), host, assetKind))
|
|
819
|
+
} else if (e.isFile()) {
|
|
820
|
+
await cp(src, dest)
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
async function installAssetFiles(cat, projectDir, spec, host) {
|
|
826
|
+
const destDir = join(projectDir, spec.dir)
|
|
827
|
+
await mkdir(destDir, { recursive: true })
|
|
828
|
+
for (const e of await readdir(join(ROOT, cat), { withFileTypes: true })) {
|
|
829
|
+
if (!e.isFile() || !e.name.endsWith(MD_EXT)) continue
|
|
830
|
+
const base = e.name.slice(0, -MD_EXT.length)
|
|
831
|
+
const content = await readFile(join(ROOT, cat, e.name), 'utf8')
|
|
832
|
+
await writeFile(join(destDir, `${base}${spec.ext}`), adaptFrontmatter(content, host, cat))
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
// Dueno unico de la instalacion de assets por proyecto: materializa skills,
|
|
837
|
+
// agents y commands en el destino nativo del host y devuelve las rutas escritas.
|
|
838
|
+
async function installAgentAssets(projectDir, agent) {
|
|
839
|
+
const model = AGENT_TARGETS[agent] || AGENT_TARGETS[DEFAULT_AGENT]
|
|
840
|
+
const written = { agents: [], commands: [], skills: [] }
|
|
841
|
+
|
|
842
|
+
if (model.skills) {
|
|
843
|
+
await copyDirTransformed(join(ROOT, 'skills'), join(projectDir, model.skills.dir), agent, 'skills')
|
|
844
|
+
written.skills.push(model.skills.dir)
|
|
845
|
+
} else {
|
|
846
|
+
warnUnsupportedAsset('skills', agent)
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
for (const cat of ['agents', 'commands']) {
|
|
850
|
+
const spec = model[cat]
|
|
851
|
+
if (!spec) {
|
|
852
|
+
warnUnsupportedAsset(cat, agent)
|
|
753
853
|
continue
|
|
754
854
|
}
|
|
755
|
-
await
|
|
855
|
+
await installAssetFiles(cat, projectDir, spec, agent)
|
|
856
|
+
written[cat].push(spec.dir)
|
|
857
|
+
}
|
|
858
|
+
return written
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// D10: installedPaths es la union de destinos realmente escritos; un asset
|
|
862
|
+
// omitido (null) no deja rastro. No se borran rutas previas (no destructivo).
|
|
863
|
+
function unionInstalledPaths(existing, next) {
|
|
864
|
+
const out = {}
|
|
865
|
+
for (const k of ['templates', 'agents', 'commands', 'skills']) {
|
|
866
|
+
out[k] = [...new Set([...(existing?.[k] || []), ...(next?.[k] || [])])]
|
|
756
867
|
}
|
|
757
|
-
|
|
758
|
-
|
|
868
|
+
return out
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
// Convencion de nombre esperada en un directorio instalado (para `check`).
|
|
872
|
+
function installedExtFor(cat, dirRel) {
|
|
873
|
+
for (const t of Object.values(AGENT_TARGETS)) {
|
|
874
|
+
const spec = t[cat]
|
|
875
|
+
if (spec && spec.dir === dirRel && spec.ext) return spec.ext
|
|
759
876
|
}
|
|
760
|
-
return
|
|
877
|
+
return null
|
|
761
878
|
}
|
|
762
879
|
|
|
763
880
|
const CHANGES_ROOT = 'aspec'
|
|
@@ -864,9 +981,9 @@ function findInsertAnchor(local, source, name) {
|
|
|
864
981
|
return local.length
|
|
865
982
|
}
|
|
866
983
|
|
|
984
|
+
// Solo mergea los templates raiz (AGENTS.md/PRODUCT.md). Los assets por host
|
|
985
|
+
// los materializa installAgentAssets (dueno unico).
|
|
867
986
|
async function copyTemplates(projectDir) {
|
|
868
|
-
const dest = join(projectDir, '.opencode')
|
|
869
|
-
await copyAssets(dest)
|
|
870
987
|
for (const t of TEMPLATES) {
|
|
871
988
|
const target = join(projectDir, t)
|
|
872
989
|
const source = await readFile(join(ROOT, 'templates', t), 'utf8')
|
|
@@ -965,18 +1082,16 @@ async function install(args) {
|
|
|
965
1082
|
await migrateLegacyOpenspec(projectDir)
|
|
966
1083
|
await scaffoldAspec(projectDir)
|
|
967
1084
|
await refreshWorkingContext(projectDir)
|
|
968
|
-
const
|
|
1085
|
+
const written = await installAgentAssets(projectDir, agent)
|
|
969
1086
|
await writeManifest(projectDir, {
|
|
970
1087
|
agent,
|
|
971
1088
|
language,
|
|
972
1089
|
...(excludeChoice !== null ? { discovery: { ...existingDiscovery, exclude: excludeChoice } } : {}),
|
|
973
1090
|
...(gratisModelChoice ? { gratisModel: gratisModelChoice } : {}),
|
|
974
|
-
installedPaths: {
|
|
1091
|
+
installedPaths: unionInstalledPaths(existingRc?.installedPaths, {
|
|
975
1092
|
templates: ['AGENTS.md', 'PRODUCT.md'],
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
skills: [agentSkillsDir]
|
|
979
|
-
}
|
|
1093
|
+
...written
|
|
1094
|
+
})
|
|
980
1095
|
})
|
|
981
1096
|
} else {
|
|
982
1097
|
await copyAssets(target)
|
|
@@ -984,6 +1099,9 @@ async function install(args) {
|
|
|
984
1099
|
|
|
985
1100
|
const models = tierModels(tier, TIERS, gratisModelChoice)
|
|
986
1101
|
await applyTier(join(target, 'agents'), tier, models)
|
|
1102
|
+
// El estado de tier vive en el base de opencode; puede no existir si el host
|
|
1103
|
+
// resuelto ubica sus assets en otro directorio (p. ej. cursor -> .cursor/).
|
|
1104
|
+
await mkdir(target, { recursive: true })
|
|
987
1105
|
await writeFile(tierStatePath(target), tier + '\n')
|
|
988
1106
|
|
|
989
1107
|
if (projectDir) await registerProject(projectDir, { agent, tier })
|
|
@@ -999,7 +1117,7 @@ async function install(args) {
|
|
|
999
1117
|
|
|
1000
1118
|
const res = await mergeMcp(target, mcpMap)
|
|
1001
1119
|
const loc = projectDir
|
|
1002
|
-
? `${projectDir} (
|
|
1120
|
+
? `${projectDir} (layout ${agent} + templates en la raiz)`
|
|
1003
1121
|
: `${target} (disponible en todos tus proyectos)`
|
|
1004
1122
|
console.log(`ancleto: instalado en ${loc}`)
|
|
1005
1123
|
console.log(`ancleto: tier de costo de agents: ${tier}`)
|
|
@@ -1020,20 +1138,19 @@ async function upgradeCmd(args) {
|
|
|
1020
1138
|
await migrateLegacyOpenspec(projectDir)
|
|
1021
1139
|
const agent = await resolveAgent(args, rc.agent)
|
|
1022
1140
|
await copyTemplates(projectDir)
|
|
1023
|
-
const
|
|
1141
|
+
const written = await installAgentAssets(projectDir, agent)
|
|
1024
1142
|
const wentContext = await refreshWorkingContext(projectDir)
|
|
1025
1143
|
const manifest = await writeManifest(projectDir, {
|
|
1026
1144
|
agent,
|
|
1027
|
-
installedPaths: {
|
|
1145
|
+
installedPaths: unionInstalledPaths(rc.installedPaths, {
|
|
1028
1146
|
templates: ['AGENTS.md', 'PRODUCT.md'],
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
skills: [agentSkillsDir]
|
|
1032
|
-
}
|
|
1147
|
+
...written
|
|
1148
|
+
})
|
|
1033
1149
|
})
|
|
1150
|
+
const skillsDir = written.skills[0] || '.opencode/skills'
|
|
1034
1151
|
console.log(`ancleto: upgrade completo (v${manifest.version}, agente: ${agent})`)
|
|
1035
1152
|
console.log(' ✔ templates re-aplicados (bloques LOCKED actualizados, EXTENSIBLE intacto)')
|
|
1036
|
-
console.log(` ✔ skills actualizadas en ${
|
|
1153
|
+
console.log(` ✔ skills actualizadas en ${skillsDir} (${ANCLETO_SKILLS.length} skills)`)
|
|
1037
1154
|
console.log(wentContext ? ' ✔ working-context.md regenerado' : ' ✔ working-context.md sin reglas activas (vacio)')
|
|
1038
1155
|
console.log(' ✔ manifiesto .ancletorc actualizado')
|
|
1039
1156
|
}
|
|
@@ -1100,12 +1217,17 @@ async function initProject(args) {
|
|
|
1100
1217
|
const persisted = isKnownGratisModel(existing?.gratisModel) ? existing.gratisModel : null
|
|
1101
1218
|
gratisModelChoice = persisted || gratisModel()
|
|
1102
1219
|
}
|
|
1220
|
+
const written = await installAgentAssets(projectDir, agent)
|
|
1103
1221
|
const manifest = await writeManifest(projectDir, {
|
|
1104
1222
|
azure,
|
|
1105
1223
|
discovery,
|
|
1106
1224
|
agent,
|
|
1107
1225
|
language,
|
|
1108
|
-
...(gratisModelChoice ? { gratisModel: gratisModelChoice } : {})
|
|
1226
|
+
...(gratisModelChoice ? { gratisModel: gratisModelChoice } : {}),
|
|
1227
|
+
installedPaths: unionInstalledPaths(existing?.installedPaths, {
|
|
1228
|
+
templates: ['AGENTS.md', 'PRODUCT.md'],
|
|
1229
|
+
...written
|
|
1230
|
+
})
|
|
1109
1231
|
})
|
|
1110
1232
|
await copyTemplates(projectDir)
|
|
1111
1233
|
if (tier) {
|
|
@@ -1591,7 +1713,10 @@ async function checkCommand() {
|
|
|
1591
1713
|
missing++
|
|
1592
1714
|
continue
|
|
1593
1715
|
}
|
|
1594
|
-
|
|
1716
|
+
// La convencion de nombre del host (p. ej. vscode: .agent.md/.prompt.md)
|
|
1717
|
+
// se deriva del mismo modelo de destino; en skills no cambia el nombre.
|
|
1718
|
+
const ext = installedExtFor(cat, dirRel)
|
|
1719
|
+
const expected = (await readdir(join(ROOT, cat))).sort().map((f) => (ext ? f.replace(/\.md$/, ext) : f))
|
|
1595
1720
|
const actual = (await readdir(destDir)).sort()
|
|
1596
1721
|
const missingFiles = expected.filter((f) => !actual.includes(f))
|
|
1597
1722
|
const orphanFiles = actual.filter((f) => !expected.includes(f))
|