@dsalazardev/ai-hub-cli 0.2.0 → 0.3.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 +63 -4
- package/dist/backup/snapshots.js +12 -0
- package/dist/catalog.d.ts +8 -1
- package/dist/catalog.js +38 -9
- package/dist/cli/args.js +1 -0
- package/dist/cli/doctor.js +20 -11
- package/dist/cli/maintenance.js +21 -39
- package/dist/cli/memory.js +4 -0
- package/dist/cli/onboarding.d.ts +13 -0
- package/dist/cli/onboarding.js +128 -14
- package/dist/cli/run.d.ts +3 -1
- package/dist/cli/run.js +5 -4
- package/dist/cli/types.d.ts +3 -0
- package/dist/engine/engine.d.ts +19 -1
- package/dist/engine/engine.js +52 -0
- package/dist/engine/gates.d.ts +16 -0
- package/dist/engine/gates.js +102 -1
- package/dist/harness/memory/ledger.d.ts +6 -0
- package/dist/harness/memory/ledger.js +44 -2
- package/dist/manifest/manifest.d.ts +3 -1
- package/dist/manifest/manifest.js +16 -10
- package/dist/presets/materialize.d.ts +5 -0
- package/dist/presets/materialize.js +30 -0
- package/dist/speckit/binding.d.ts +15 -0
- package/dist/speckit/binding.js +66 -0
- package/dist/speckit/commands.d.ts +46 -0
- package/dist/speckit/commands.js +131 -0
- package/dist/speckit/doctor.d.ts +14 -0
- package/dist/speckit/doctor.js +103 -0
- package/dist/speckit/runner.d.ts +19 -0
- package/dist/speckit/runner.js +22 -0
- package/dist/speckit/seal.d.ts +12 -0
- package/dist/speckit/seal.js +47 -0
- package/dist/speckit/settings.d.ts +36 -0
- package/dist/speckit/settings.js +75 -0
- package/dist/speckit/state.d.ts +59 -0
- package/dist/speckit/state.js +279 -0
- package/dist/speckit/version.d.ts +8 -0
- package/dist/speckit/version.js +53 -0
- package/dist/sync/config.js +3 -2
- package/dist/sync/render.js +30 -0
- package/dist/sync/sync.d.ts +4 -0
- package/dist/sync/sync.js +48 -5
- package/dist/sync/targets.d.ts +13 -0
- package/dist/sync/targets.js +5 -0
- package/engine/engine.yaml +65 -31
- package/manifest.json +20 -48
- package/package.json +1 -1
- package/schema/engine.schema.json +22 -3
- package/schema/manifest.schema.json +10 -0
- package/skills/shared/analyze/SKILL.md +2 -1
- package/skills/shared/clarify/SKILL.md +2 -1
- package/skills/shared/implement/SKILL.md +2 -1
- package/skills/shared/plan/SKILL.md +2 -1
- package/skills/shared/review/SKILL.md +2 -1
- package/skills/shared/sdd/SKILL.md +2 -1
- package/skills/shared/sdd-init/SKILL.md +2 -1
- package/skills/shared/ship/SKILL.md +2 -1
- package/skills/shared/specify/SKILL.md +2 -1
- package/skills/shared/tasks/SKILL.md +2 -1
- package/skills/shared/verify/SKILL.md +2 -1
- package/skills/shared/speckit-analyze/SKILL.md +0 -35
- package/skills/shared/speckit-assess-decide/SKILL.md +0 -36
- package/skills/shared/speckit-assess-define/SKILL.md +0 -36
- package/skills/shared/speckit-assess-intake/SKILL.md +0 -38
- package/skills/shared/speckit-assess-research/SKILL.md +0 -36
- package/skills/shared/speckit-assess-shape/SKILL.md +0 -36
- package/skills/shared/speckit-bug-assess/SKILL.md +0 -38
- package/skills/shared/speckit-bug-fix/SKILL.md +0 -37
- package/skills/shared/speckit-bug-test/SKILL.md +0 -37
- package/skills/shared/speckit-checklist/SKILL.md +0 -35
- package/skills/shared/speckit-clarify/SKILL.md +0 -32
- package/skills/shared/speckit-constitution/SKILL.md +0 -35
- package/skills/shared/speckit-converge/SKILL.md +0 -36
- package/skills/shared/speckit-implement/SKILL.md +0 -32
- package/skills/shared/speckit-plan/SKILL.md +0 -37
- package/skills/shared/speckit-specify/SKILL.md +0 -36
- package/skills/shared/speckit-tasks/SKILL.md +0 -34
package/README.md
CHANGED
|
@@ -7,6 +7,11 @@ el proyecto ya usa, sin acoplarse a un lenguaje ni a un dominio concreto.
|
|
|
7
7
|
## Requisitos
|
|
8
8
|
|
|
9
9
|
- Node.js >= 22
|
|
10
|
+
- [uv](https://docs.astral.sh/uv/getting-started/installation/) con `uvx` en el PATH: `init` e `install`
|
|
11
|
+
ejecutan Spec Kit con `uvx --from specify-cli==<versión fijada>`, sin `uv tool install`.
|
|
12
|
+
- `bash` para los scripts `sh` de Spec Kit (por defecto en todas las plataformas). En Windows,
|
|
13
|
+
Git for Windows (Git Bash) en el PATH; el lanzador `bash` de WSL sin distribución no sirve.
|
|
14
|
+
Alternativa: `speckit.script: ps` en `manifest.yaml` (requiere PowerShell).
|
|
10
15
|
|
|
11
16
|
## Uso rápido
|
|
12
17
|
|
|
@@ -19,8 +24,40 @@ npx @dsalazardev/ai-hub-cli doctor
|
|
|
19
24
|
```
|
|
20
25
|
|
|
21
26
|
`init` detecta el preset según el stack del repositorio y materializa `.ai-hub/`,
|
|
22
|
-
que es obligatoria
|
|
23
|
-
declarados en `targets
|
|
27
|
+
que es obligatoria, e instala Spec Kit en la raíz con las extensiones `bug` y `assess`.
|
|
28
|
+
`install` proyecta reglas, agentes y skills a los destinos declarados en `targets` y
|
|
29
|
+
repite la instalación de Spec Kit. `doctor` valida motor, preset, manifiesto, deriva y
|
|
30
|
+
Spec Kit.
|
|
31
|
+
|
|
32
|
+
## Spec Kit
|
|
33
|
+
|
|
34
|
+
Spec Kit ejecuta los 17 comandos de la metodología; AI-Hub define roles, estados y
|
|
35
|
+
etiquetas. El paquete fija la versión de `specify-cli`, la de las extensiones y una
|
|
36
|
+
fecha `--exclude-newer` para las dependencias de `uvx`. Esa fecha se mueve solo en un
|
|
37
|
+
release, nunca automáticamente. `manifest.yaml` puede restringir la versión dentro del rango
|
|
38
|
+
soportado (`speckit.version`), elegir el script (`speckit.script: sh | ps`) y la integración
|
|
39
|
+
por defecto (`speckit.defaultTarget`), pero no ampliar la versión ni cambiar las extensiones.
|
|
40
|
+
|
|
41
|
+
- Spec Kit se instala en `claude`, `opencode`, `pi` y `cursor` (`cursor-agent`). **Copilot
|
|
42
|
+
no recibe Spec Kit**: recibe agentes, reglas y la regla `ai-hub-speckit`.
|
|
43
|
+
- Sin `uv`, sin red y sin caché para la versión fijada, o sin `bash` con `script: sh`,
|
|
44
|
+
`init` sale con código 2 **antes de escribir nada** y dice qué falta.
|
|
45
|
+
- `init --no-speckit` instala sin Spec Kit y deja el arnés **degradado** (`doctor` sale 1).
|
|
46
|
+
Un `ai-hub install` posterior sin la bandera lo reactiva.
|
|
47
|
+
- `.specify/` y `specs/` se versionan; las copias de los comandos en las carpetas de los
|
|
48
|
+
asistentes no, y `install` las regenera en un clon nuevo.
|
|
49
|
+
|
|
50
|
+
### CI
|
|
51
|
+
|
|
52
|
+
`doctor` no necesita Python, pero un clon nuevo no tiene las copias de los comandos de
|
|
53
|
+
Spec Kit en las carpetas de los asistentes, que están ignoradas. El job instala `uv`,
|
|
54
|
+
regenera con `install` y verifica:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- uses: astral-sh/setup-uv@v6
|
|
58
|
+
- run: npx @dsalazardev/ai-hub-cli install
|
|
59
|
+
- run: npx @dsalazardev/ai-hub-cli doctor
|
|
60
|
+
```
|
|
24
61
|
|
|
25
62
|
Si la detección es ambigua, el preset se elige explícitamente:
|
|
26
63
|
|
|
@@ -48,14 +85,36 @@ npx @dsalazardev/ai-hub-cli install --targets claude
|
|
|
48
85
|
| `context` | Regenera `.planning/context-next.md` |
|
|
49
86
|
| `purge` | Retira los destinos generados, con snapshot previo |
|
|
50
87
|
|
|
88
|
+
## Checks de Spec Kit en `doctor`
|
|
89
|
+
|
|
90
|
+
`doctor` verifica la instalación de Spec Kit leyendo `.specify/` y calculando hashes; no
|
|
91
|
+
ejecuta Python ni `uvx`. En el checkout del motor todos informan `no aplica`.
|
|
92
|
+
|
|
93
|
+
| Check | Criterio | Reparación |
|
|
94
|
+
|---|---|---|
|
|
95
|
+
| `speckit:managed` | `speckit.managed` no es `false`; un arnés con `--no-speckit` es **degradado** y `doctor` sale 1 | `ai-hub install` (sin `--no-speckit`) |
|
|
96
|
+
| `speckit:installed` | Existe `.specify/init-options.json` | `ai-hub install` |
|
|
97
|
+
| `speckit:version` | `speckit_version` coincide con la versión fijada (o la que restrinja `speckit.version`) | `ai-hub install` |
|
|
98
|
+
| `speckit:shell` | El shell del script declarado ejecuta `echo ok` (`bash` para `sh`, PowerShell para `ps`) | Instalar Git Bash/bash, o declarar `speckit.script: ps` |
|
|
99
|
+
| `speckit:integrations` | `.specify/integration.json` tiene exactamente las integraciones de los destinos con Spec Kit, sin `copilot`, y la integración por defecto declarada | `ai-hub install`; si aparece `copilot`, `specify integration uninstall copilot` |
|
|
100
|
+
| `speckit:targets` | Informativo: `copilot: sin Spec Kit` (recibe agentes y reglas, no comandos) | — |
|
|
101
|
+
| `speckit:extensions` | `bug` y `assess` instalados en la versión fijada, registrados y presentes en cada destino con Spec Kit | `ai-hub install` |
|
|
102
|
+
| `speckit:integrity` | Cada archivo de `.specify/integrations/*.manifest.json` existe; si su hash difiere solo avisa (el próximo `init` lo sobrescribe) | `ai-hub install`; personalizar con overrides de Spec Kit, no editando plantillas |
|
|
103
|
+
| `speckit:collision` | Ninguna ruta de `sync.lock.json` pertenece a Spec Kit | `ai-hub sync`; no publicar skills con nombre `speckit-*` |
|
|
104
|
+
| `speckit:constitution` | Aviso si `.specify/memory/constitution.md` sigue siendo la plantilla | Ejecutar `/speckit.constitution` (o `/speckit-constitution`) |
|
|
105
|
+
| `speckit:versioning` | En un repositorio git no se ignoran la constitución, `init-options.json` ni `specs/` | Quitar esas rutas de `.gitignore` |
|
|
106
|
+
|
|
51
107
|
## Estructura instalada
|
|
52
108
|
|
|
53
109
|
```
|
|
54
110
|
.ai-hub/ preset materializado, manifiesto y configuración (obligatoria)
|
|
55
|
-
.
|
|
56
|
-
|
|
111
|
+
.specify/ Spec Kit: constitución, plantillas, scripts, assessments y bugs (versionado)
|
|
112
|
+
specs/ features de Spec Kit: spec.md, plan.md, tasks.md (versionado)
|
|
113
|
+
.claude/ .opencode/ .pi/ .cursor/ .github/ destinos generados según targets (ignorados)
|
|
57
114
|
```
|
|
58
115
|
|
|
116
|
+
Una instalación no crea `.planning/`: es estado del checkout de desarrollo del motor.
|
|
117
|
+
|
|
59
118
|
## Procedencia de activos
|
|
60
119
|
|
|
61
120
|
Ver `NOTICE.md` para el origen de skills y agentes reutilizados.
|
package/dist/backup/snapshots.js
CHANGED
|
@@ -38,6 +38,11 @@ async function walkRelative(root, relative) {
|
|
|
38
38
|
}
|
|
39
39
|
return files;
|
|
40
40
|
}
|
|
41
|
+
const SPECKIT_CONFIG_FILES = [
|
|
42
|
+
'.specify/init-options.json',
|
|
43
|
+
'.specify/integration.json',
|
|
44
|
+
'.specify/extensions.yml',
|
|
45
|
+
];
|
|
41
46
|
async function managedFiles(root) {
|
|
42
47
|
const files = new Set([
|
|
43
48
|
'sync.lock.json',
|
|
@@ -52,6 +57,13 @@ async function managedFiles(root) {
|
|
|
52
57
|
for (const rel of await walkRelative(root, '.ai-hub')) {
|
|
53
58
|
files.add(rel);
|
|
54
59
|
}
|
|
60
|
+
// Estado de Spec Kit que un nuevo `specify init` podría tocar: constitución y configuración.
|
|
61
|
+
for (const rel of await walkRelative(root, '.specify/memory')) {
|
|
62
|
+
files.add(rel);
|
|
63
|
+
}
|
|
64
|
+
for (const rel of SPECKIT_CONFIG_FILES) {
|
|
65
|
+
files.add(rel);
|
|
66
|
+
}
|
|
55
67
|
const result = [];
|
|
56
68
|
for (const rel of [...files].sort()) {
|
|
57
69
|
if (await exists(path.join(root, rel))) {
|
package/dist/catalog.d.ts
CHANGED
|
@@ -16,11 +16,18 @@ export interface SelectionScope {
|
|
|
16
16
|
}
|
|
17
17
|
export interface CatalogIndex {
|
|
18
18
|
skills: string[];
|
|
19
|
+
/** Skills del paquete que declaran `runtime: false`: siguen publicadas pero no son instalables. */
|
|
20
|
+
retiredSkills: string[];
|
|
19
21
|
agents: string[];
|
|
20
22
|
skillFiles: Record<string, string[]>;
|
|
21
23
|
ruleFiles: Record<string, string[]>;
|
|
22
24
|
walkCount: number;
|
|
23
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Índice de las skills instalables de un directorio de catálogo. Las skills con
|
|
28
|
+
* `runtime: false` siguen en el paquete pero no se listan, materializan ni proyectan.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildCatalogIndexFrom(skillsDir: string): Promise<Pick<CatalogIndex, 'skills' | 'skillFiles' | 'retiredSkills'>>;
|
|
24
31
|
/**
|
|
25
32
|
* Índice memoizado por proceso. En tests, `AI_HUB_CATALOG_INDEX_FILE` apunta al
|
|
26
33
|
* índice que construye `globalSetup`, de modo que todos los workers comparten un
|
|
@@ -31,7 +38,7 @@ export declare function catalogIndex(): Promise<CatalogIndex>;
|
|
|
31
38
|
export declare function rebuildCatalogIndex(): Promise<CatalogIndex>;
|
|
32
39
|
export declare function catalogWalkCount(): number;
|
|
33
40
|
export declare function resetCatalogIndex(): void;
|
|
34
|
-
/** Skills
|
|
41
|
+
/** Skills instalables del catálogo; los 17 comandos los provee Spec Kit, no el catálogo. */
|
|
35
42
|
export declare function listCatalogSkills(engine: EngineManifest): Promise<string[]>;
|
|
36
43
|
/** Agentes de catálogo publicados en el paquete. */
|
|
37
44
|
export declare function listCatalogAgents(): Promise<string[]>;
|
package/dist/catalog.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { ENGINE_SKILLS_DIR } from './engine/engine.js';
|
|
5
5
|
import { PACKAGE_VERSION } from './package-info.js';
|
|
6
6
|
import { sha256 } from './sync/lock.js';
|
|
7
|
+
import { splitFrontmatter } from './frontmatter.js';
|
|
7
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
9
|
/** Directorio de agentes dentro del paquete (core/agents). */
|
|
9
10
|
export const ENGINE_AGENTS_DIR = path.join(__dirname, '..', 'agents');
|
|
@@ -38,16 +39,43 @@ async function walk(dir) {
|
|
|
38
39
|
}
|
|
39
40
|
return files.sort();
|
|
40
41
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
42
|
+
/** `true` si el frontmatter de la skill declara `runtime: false` (no instalable). */
|
|
43
|
+
async function isRuntimeSkill(skillDir) {
|
|
44
|
+
const text = await fs.readFile(path.join(skillDir, 'SKILL.md'), 'utf8').catch(() => null);
|
|
45
|
+
if (text === null) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
return splitFrontmatter(text, skillDir).data.runtime !== false;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Índice de las skills instalables de un directorio de catálogo. Las skills con
|
|
57
|
+
* `runtime: false` siguen en el paquete pero no se listan, materializan ni proyectan.
|
|
58
|
+
*/
|
|
59
|
+
export async function buildCatalogIndexFrom(skillsDir) {
|
|
60
|
+
const entries = await fs.readdir(skillsDir, { withFileTypes: true }).catch(() => []);
|
|
44
61
|
const skillFiles = {};
|
|
45
|
-
|
|
62
|
+
const retiredSkills = [];
|
|
63
|
+
for (const entry of entries.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))) {
|
|
46
64
|
if (!entry.isDirectory()) {
|
|
47
65
|
continue;
|
|
48
66
|
}
|
|
49
|
-
|
|
67
|
+
const dir = path.join(skillsDir, entry.name);
|
|
68
|
+
if (!(await isRuntimeSkill(dir))) {
|
|
69
|
+
retiredSkills.push(entry.name);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
skillFiles[entry.name] = await walk(dir);
|
|
50
73
|
}
|
|
74
|
+
return { skills: Object.keys(skillFiles).sort(), skillFiles, retiredSkills: retiredSkills.sort() };
|
|
75
|
+
}
|
|
76
|
+
async function buildCatalogIndex() {
|
|
77
|
+
walkCount += 1;
|
|
78
|
+
const { skillFiles, retiredSkills } = await buildCatalogIndexFrom(ENGINE_SKILLS_DIR);
|
|
51
79
|
const ruleFiles = {};
|
|
52
80
|
for (const id of RUBRIC_RULE_IDS) {
|
|
53
81
|
const file = path.join(ENGINE_RULES_DIR, `${id}.md`);
|
|
@@ -60,6 +88,7 @@ async function buildCatalogIndex() {
|
|
|
60
88
|
.filter((file) => file.endsWith('.md'))
|
|
61
89
|
.map((file) => path.basename(file, '.md'))
|
|
62
90
|
.sort(),
|
|
91
|
+
retiredSkills,
|
|
63
92
|
skillFiles,
|
|
64
93
|
ruleFiles,
|
|
65
94
|
walkCount,
|
|
@@ -78,6 +107,7 @@ async function readIndexFile(file) {
|
|
|
78
107
|
}
|
|
79
108
|
return {
|
|
80
109
|
skills: parsed.skills,
|
|
110
|
+
retiredSkills: Array.isArray(parsed.retiredSkills) ? parsed.retiredSkills : [],
|
|
81
111
|
agents: parsed.agents,
|
|
82
112
|
skillFiles: parsed.skillFiles,
|
|
83
113
|
ruleFiles: parsed.ruleFiles,
|
|
@@ -121,11 +151,10 @@ export function resetCatalogIndex() {
|
|
|
121
151
|
indexPromise = null;
|
|
122
152
|
walkCount = 0;
|
|
123
153
|
}
|
|
124
|
-
/** Skills
|
|
154
|
+
/** Skills instalables del catálogo; los 17 comandos los provee Spec Kit, no el catálogo. */
|
|
125
155
|
export async function listCatalogSkills(engine) {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
return index.skills.filter((skill) => !engineSkills.has(skill));
|
|
156
|
+
void engine;
|
|
157
|
+
return (await catalogIndex()).skills;
|
|
129
158
|
}
|
|
130
159
|
/** Agentes de catálogo publicados en el paquete. */
|
|
131
160
|
export async function listCatalogAgents() {
|
package/dist/cli/args.js
CHANGED
package/dist/cli/doctor.js
CHANGED
|
@@ -4,13 +4,14 @@ import { spawnSync } from 'node:child_process';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { readAiHubRc } from '../paths.js';
|
|
6
6
|
import { auditSelection } from '../catalog.js';
|
|
7
|
-
import { ENGINE_COUNTS, ENGINE_LANES
|
|
8
|
-
import { scanCoupling, scanLegacyArtifacts } from '../engine/gates.js';
|
|
7
|
+
import { ENGINE_COUNTS, ENGINE_LANES } from '../engine/engine.js';
|
|
8
|
+
import { scanCoupling, scanLegacyArtifacts, scanReservedNamespace, scanRetiredStates } from '../engine/gates.js';
|
|
9
9
|
import { checkInventory, loadManifestContext, } from '../manifest/manifest.js';
|
|
10
10
|
import { checkLock, runSync } from '../sync/sync.js';
|
|
11
11
|
import { CONTEXT_RELATIVE, contextHash } from './context.js';
|
|
12
12
|
import { TARGETS, TOOL_IDS, isToolId } from '../sync/targets.js';
|
|
13
13
|
import { sourceCheckout } from './source.js';
|
|
14
|
+
import { speckitDoctorChecks, speckitNotApplicable } from '../speckit/doctor.js';
|
|
14
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
15
16
|
const COUPLING_FILE = path.join(__dirname, '..', '..', 'engine', 'coupling.json');
|
|
16
17
|
async function couplingRules() {
|
|
@@ -103,19 +104,13 @@ export async function diagnose(ctx) {
|
|
|
103
104
|
add('policy:optOuts', true, manifest.policy.optOuts.length === 0
|
|
104
105
|
? 'ninguno'
|
|
105
106
|
: `opt-outs activos: ${manifest.policy.optOuts.join(', ')}`);
|
|
106
|
-
const missingEngineSkills = [];
|
|
107
|
-
for (const command of engine.commands) {
|
|
108
|
-
if (!(await exists(path.join(ENGINE_SKILLS_DIR, command.skill, 'SKILL.md')))) {
|
|
109
|
-
missingEngineSkills.push(`${command.id} -> ${command.skill}`);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
107
|
const audit = await auditSelection(ctx.root, manifest, preset, engine);
|
|
113
108
|
const missingProjectSkills = audit.missing
|
|
114
109
|
.filter((item) => item.startsWith('skill:'))
|
|
115
110
|
.map((item) => item.slice('skill:'.length));
|
|
116
|
-
add('skills',
|
|
117
|
-
?
|
|
118
|
-
:
|
|
111
|
+
add('skills', missingProjectSkills.length === 0, missingProjectSkills.length === 0
|
|
112
|
+
? `${manifest.skills.publish.length} skills de proyecto + ${engine.commands.length} comandos Spec Kit (${audit.modes.skills})`
|
|
113
|
+
: missingProjectSkills.map((id) => `${id} -> ${manifest.skills.source}`).join(', '));
|
|
119
114
|
const missingAgents = audit.missing
|
|
120
115
|
.filter((item) => item.startsWith('agent:'))
|
|
121
116
|
.map((item) => item.slice('agent:'.length));
|
|
@@ -144,6 +139,12 @@ export async function diagnose(ctx) {
|
|
|
144
139
|
const present = (await exists(direct)) || (await exists(project));
|
|
145
140
|
add('mcp', present, present ? `MCP configurado (${manifest.mcp.source})` : `MCP: falta ${manifest.mcp.source}`);
|
|
146
141
|
}
|
|
142
|
+
const speckitChecks = source.isSource
|
|
143
|
+
? speckitNotApplicable()
|
|
144
|
+
: await speckitDoctorChecks(ctx.root, manifest, engine, ctx.exec);
|
|
145
|
+
for (const check of speckitChecks) {
|
|
146
|
+
add(check.id, check.ok, check.detail);
|
|
147
|
+
}
|
|
147
148
|
if (source.isSource) {
|
|
148
149
|
const required = ['dist', 'schema', 'manifest.json', 'engine', 'presets', 'rules', 'agents', 'skills'];
|
|
149
150
|
const missing = required.filter((entry) => !source.files.includes(entry));
|
|
@@ -166,6 +167,10 @@ export async function diagnose(ctx) {
|
|
|
166
167
|
}
|
|
167
168
|
add('hooks', missingHooks.length === 0, missingHooks.length === 0 ? 'superficies presentes' : missingHooks.join(', '));
|
|
168
169
|
}
|
|
170
|
+
const reserved = await scanReservedNamespace();
|
|
171
|
+
add('catalog:reserved-namespace', reserved.length === 0, reserved.length === 0
|
|
172
|
+
? 'ningún activo del catálogo usa el espacio speckit-*/speckit.* de Spec Kit'
|
|
173
|
+
: reserved.slice(0, 5).join('; ') + (reserved.length > 5 ? ` (+${reserved.length - 5})` : ''));
|
|
169
174
|
const legacy = await scanLegacyArtifacts(ctx.root);
|
|
170
175
|
add('engine:no-legacy-prefix', legacy.length === 0, legacy.length === 0
|
|
171
176
|
? source.isSource
|
|
@@ -253,6 +258,10 @@ export async function diagnose(ctx) {
|
|
|
253
258
|
}
|
|
254
259
|
const coupling = await couplingRules();
|
|
255
260
|
if (source.isSource) {
|
|
261
|
+
const retired = await scanRetiredStates(undefined, ctx.root);
|
|
262
|
+
add('engine:retired-states', retired.length === 0, retired.length === 0
|
|
263
|
+
? 'sin vocabulario de estados retirado (ROOT_CAUSE, FIXED, READY…)'
|
|
264
|
+
: retired.slice(0, 5).join('; ') + (retired.length > 5 ? ` (+${retired.length - 5})` : ''));
|
|
256
265
|
const findings = await scanCoupling(ctx.root, coupling.terms);
|
|
257
266
|
add('engine:no-coupling', findings.length === 0, findings.length === 0
|
|
258
267
|
? 'sin términos de dominio en el motor'
|
package/dist/cli/maintenance.js
CHANGED
|
@@ -5,9 +5,9 @@ import { createBackup, listBackups, purgeGenerated, restoreBackup } from '../bac
|
|
|
5
5
|
import { readAiHubRc } from '../paths.js';
|
|
6
6
|
import { loadManifestContext, writeInventory } from '../manifest/manifest.js';
|
|
7
7
|
import { findPresetAsset } from '../presets/resolve.js';
|
|
8
|
-
import { ENGINE_AGENTS, materializeAgent, materializeSkill,
|
|
8
|
+
import { ENGINE_AGENTS, materializeAgent, materializeSkill, removeOwned, SCAFFOLD_RELATIVE, } from '../presets/materialize.js';
|
|
9
9
|
import { ENGINE_SKILLS_DIR } from '../engine/engine.js';
|
|
10
|
-
import { catalogSeal, hasFullDimension } from '../catalog.js';
|
|
10
|
+
import { catalogIndex, catalogSeal, hasFullDimension } from '../catalog.js';
|
|
11
11
|
import { readLock } from '../sync/lock.js';
|
|
12
12
|
import { runSync } from '../sync/sync.js';
|
|
13
13
|
import { cliVersion, cmdInstall, writeManifest } from './onboarding.js';
|
|
@@ -15,6 +15,10 @@ import { splitItem } from './items.js';
|
|
|
15
15
|
async function exists(file) {
|
|
16
16
|
return fs.access(file).then(() => true, () => false);
|
|
17
17
|
}
|
|
18
|
+
/** Nombres del espacio de Spec Kit: sus comandos no se añaden ni se desinstalan desde ai-hub. */
|
|
19
|
+
function isSpeckitCommandName(id) {
|
|
20
|
+
return id.startsWith('speckit-') || id.startsWith('speckit.');
|
|
21
|
+
}
|
|
18
22
|
function markCustom(manifest, dimension) {
|
|
19
23
|
manifest[dimension].selection = 'custom';
|
|
20
24
|
if (hasFullDimension(manifest)) {
|
|
@@ -45,7 +49,7 @@ export async function cmdList(args, ctx) {
|
|
|
45
49
|
rules: manifest.rules.selection ?? 'preset',
|
|
46
50
|
},
|
|
47
51
|
commands: engine.commands.map((command) => ({ id: command.id, lane: command.lane, role: command.role })),
|
|
48
|
-
|
|
52
|
+
speckitCommands: engine.commands.map((command) => command.speckit),
|
|
49
53
|
skills: manifest.skills.publish,
|
|
50
54
|
agents: manifest.agents.publish,
|
|
51
55
|
rules: manifest.rules.publish,
|
|
@@ -59,7 +63,7 @@ export async function cmdList(args, ctx) {
|
|
|
59
63
|
ctx.io.out(`targets: ${manifest.targets.join(', ')}`);
|
|
60
64
|
ctx.io.out(`selection: skills=${data.selection.skills} agents=${data.selection.agents} rules=${data.selection.rules}`);
|
|
61
65
|
ctx.io.out(`commands (${data.commands.length}): ${data.commands.map((command) => command.id).join(', ')}`);
|
|
62
|
-
ctx.io.out(`skills:
|
|
66
|
+
ctx.io.out(`skills: ${data.skills.length} de proyecto (${mode}) + ${data.speckitCommands.length} comandos Spec Kit`);
|
|
63
67
|
ctx.io.out(`agents (${data.agents.length}): ${data.agents.join(', ')}`);
|
|
64
68
|
ctx.io.out(`rules: ${data.rules.length + 1} universales (${data.rules.length} materializadas + preflight)`);
|
|
65
69
|
return 0;
|
|
@@ -84,11 +88,14 @@ export async function cmdAdd(args, ctx) {
|
|
|
84
88
|
}
|
|
85
89
|
const { manifest, preset } = context;
|
|
86
90
|
const id = parsed.id;
|
|
87
|
-
|
|
88
|
-
|
|
91
|
+
if (isSpeckitCommandName(id)) {
|
|
92
|
+
ctx.io.err(`"${id}" lo provee Spec Kit; ai-hub no añade ni desinstala sus comandos`);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
const catalogSkill = (await catalogIndex()).skills.includes(id);
|
|
89
96
|
const catalogAgent = await exists(path.join(ENGINE_AGENTS, `${id}.md`));
|
|
90
97
|
const kind = parsed.kind ??
|
|
91
|
-
(preset.skills.includes(id) ||
|
|
98
|
+
(preset.skills.includes(id) || catalogSkill
|
|
92
99
|
? 'skill'
|
|
93
100
|
: catalogAgent || preset.agents.includes(id)
|
|
94
101
|
? 'agent'
|
|
@@ -98,13 +105,14 @@ export async function cmdAdd(args, ctx) {
|
|
|
98
105
|
return 1;
|
|
99
106
|
}
|
|
100
107
|
if (kind === 'skill') {
|
|
101
|
-
if (engineSkill) {
|
|
102
|
-
ctx.io.out(`"${id}" ya forma parte de las 17 skills del motor`);
|
|
103
|
-
return 0;
|
|
104
|
-
}
|
|
105
108
|
const fromPreset = preset.skills.includes(id)
|
|
106
109
|
? await findPresetAsset(preset.chain, `skills/${id}`)
|
|
107
110
|
: null;
|
|
111
|
+
if (fromPreset === null && !catalogSkill) {
|
|
112
|
+
// Una skill con `runtime: false` sigue en el paquete pero no es instalable.
|
|
113
|
+
ctx.io.err(`la skill "${id}" no pertenece al catálogo instalable (no existe o declara runtime: false)`);
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
108
116
|
const source = fromPreset ?? path.join(ENGINE_SKILLS_DIR, id);
|
|
109
117
|
if (!(await exists(path.join(source, 'SKILL.md')))) {
|
|
110
118
|
ctx.io.err(`no existe la skill "${id}" en ${preset.id} ni en el catálogo del motor`);
|
|
@@ -164,8 +172,8 @@ export async function cmdUninstall(args, ctx) {
|
|
|
164
172
|
: manifest.rules.publish.includes(id) || id === 'skill-quality'
|
|
165
173
|
? 'rule'
|
|
166
174
|
: 'agent');
|
|
167
|
-
if (
|
|
168
|
-
ctx.io.err(`"${id}"
|
|
175
|
+
if (isSpeckitCommandName(id)) {
|
|
176
|
+
ctx.io.err(`"${id}" lo provee Spec Kit; ai-hub no añade ni desinstala sus comandos`);
|
|
169
177
|
return 1;
|
|
170
178
|
}
|
|
171
179
|
if (kind === 'rule' && (id === 'core' || id === 'preflight')) {
|
|
@@ -203,32 +211,6 @@ export async function cmdUninstall(args, ctx) {
|
|
|
203
211
|
ctx.io.out(`uninstall: ${kind} "${id}" · ${sync.removed.length} archivos eliminados`);
|
|
204
212
|
return 0;
|
|
205
213
|
}
|
|
206
|
-
async function removeOwned(root, relative, directory) {
|
|
207
|
-
const absolute = path.join(root, relative);
|
|
208
|
-
if (!(await exists(absolute))) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
const record = await readScaffold(root);
|
|
212
|
-
const relativePosix = relative.split(path.sep).join('/');
|
|
213
|
-
if (directory) {
|
|
214
|
-
const files = record.files;
|
|
215
|
-
const prefix = `${relativePosix}/`;
|
|
216
|
-
const owned = Object.entries(files).filter(([name]) => name.startsWith(prefix));
|
|
217
|
-
if (owned.length === 0) {
|
|
218
|
-
const marker = await fs.readFile(path.join(absolute, 'SKILL.md'), 'utf8').catch(() => '');
|
|
219
|
-
if (!marker.includes('GENERADO por ai-hub')) {
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
await fs.rm(absolute, { recursive: true, force: true });
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const text = await fs.readFile(absolute, 'utf8').catch(() => null);
|
|
227
|
-
const owned = record.files[relativePosix] !== undefined;
|
|
228
|
-
if (text === null || owned || text.includes('GENERADO por ai-hub')) {
|
|
229
|
-
await fs.rm(absolute, { force: true });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
214
|
export async function cmdBackups(args, ctx) {
|
|
233
215
|
const snapshots = await listBackups(ctx.root);
|
|
234
216
|
if (args.flags.json) {
|
package/dist/cli/memory.js
CHANGED
|
@@ -79,6 +79,10 @@ export async function cmdMemory(args, ctx) {
|
|
|
79
79
|
ctx.io.out(`cambio activo: ${summary.activeChange ?? 'ninguno'}`);
|
|
80
80
|
ctx.io.out(`fase: ${summary.phase}`);
|
|
81
81
|
ctx.io.out(`tareas: ${summary.tasks.done}/${summary.tasks.total}`);
|
|
82
|
+
if (summary.feature !== null) {
|
|
83
|
+
ctx.io.out(`feature Spec Kit: ${summary.feature.dir} · ${summary.feature.pending} pendientes` +
|
|
84
|
+
(summary.feature.source === 'local' ? ' (puntero local .specify/feature.json, no versionado)' : ''));
|
|
85
|
+
}
|
|
82
86
|
ctx.io.out(last === null
|
|
83
87
|
? 'ledger: sin eventos'
|
|
84
88
|
: `último evento: ${last.event} ${last.timestamps.at} ${last.sessionId} (${last.branch ?? '-'})`);
|
package/dist/cli/onboarding.d.ts
CHANGED
|
@@ -22,13 +22,26 @@ export interface Plan extends AssetPlan {
|
|
|
22
22
|
preset: string;
|
|
23
23
|
targets: string[];
|
|
24
24
|
skillCount: number;
|
|
25
|
+
speckitCommands: number;
|
|
25
26
|
agentCount: number;
|
|
26
27
|
ruleCount: number;
|
|
27
28
|
mcp: boolean;
|
|
28
29
|
migrate: boolean;
|
|
29
30
|
write: string[];
|
|
31
|
+
speckit: SpeckitPlan;
|
|
30
32
|
hash: string;
|
|
31
33
|
}
|
|
34
|
+
/** Intención de Spec Kit dentro del hash del plan; el entorno (uv, shell) queda fuera. */
|
|
35
|
+
export interface SpeckitPlan {
|
|
36
|
+
managed: boolean;
|
|
37
|
+
package: string;
|
|
38
|
+
version: string;
|
|
39
|
+
excludeNewer: string;
|
|
40
|
+
script: string;
|
|
41
|
+
defaultIntegration: string | null;
|
|
42
|
+
integrations: string[];
|
|
43
|
+
extensions: Record<string, string>;
|
|
44
|
+
}
|
|
32
45
|
export declare function writeManifest(root: string, manifest: ProjectManifest): Promise<void>;
|
|
33
46
|
export declare function cmdInit(args: ParsedArgs, ctx: CliContext): Promise<number>;
|
|
34
47
|
export declare function cmdInstall(args: ParsedArgs, ctx: CliContext): Promise<number>;
|