@dsalazardev/ai-hub-cli 0.1.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 +83 -0
- package/dist/catalog.js +350 -0
- package/dist/cli/args.js +1 -0
- package/dist/cli/context.js +5 -0
- package/dist/cli/doctor.js +105 -14
- package/dist/cli/items.d.ts +1 -1
- package/dist/cli/items.js +1 -1
- package/dist/cli/maintenance.js +105 -51
- package/dist/cli/memory.js +9 -0
- package/dist/cli/onboarding.d.ts +33 -1
- package/dist/cli/onboarding.js +450 -54
- package/dist/cli/run.d.ts +3 -1
- package/dist/cli/run.js +10 -7
- package/dist/cli/types.d.ts +3 -0
- package/dist/concurrency.d.ts +5 -0
- package/dist/concurrency.js +23 -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 +154 -15
- package/dist/harness/memory/ledger.d.ts +6 -0
- package/dist/harness/memory/ledger.js +44 -2
- package/dist/manifest/manifest.d.ts +20 -1
- package/dist/manifest/manifest.js +35 -11
- package/dist/presets/materialize.d.ts +14 -1
- package/dist/presets/materialize.js +132 -11
- 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 +35 -1
- package/dist/sync/sync.d.ts +4 -0
- package/dist/sync/sync.js +68 -25
- package/dist/sync/targets.d.ts +13 -0
- package/dist/sync/targets.js +5 -0
- package/engine/engine.yaml +65 -31
- package/manifest.json +28 -47
- package/package.json +2 -1
- package/presets/angular-express/preset.yaml +2 -4
- package/presets/base/preset.yaml +1 -3
- package/presets/generic-ts/preset.yaml +1 -3
- package/presets/python/preset.yaml +1 -3
- package/rules/skill-quality/skill-behavior-rubric.md +63 -0
- package/rules/skill-quality/skill-harden-rubric.md +102 -0
- package/rules/skill-quality/skill-migration-brief.md +200 -0
- package/rules/skill-quality/skill-rubric.md +40 -0
- package/schema/engine.schema.json +22 -3
- package/schema/manifest.schema.json +22 -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/dist/cli/doctor.js
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
4
5
|
import { readAiHubRc } from '../paths.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { auditSelection } from '../catalog.js';
|
|
7
|
+
import { ENGINE_COUNTS, ENGINE_LANES } from '../engine/engine.js';
|
|
8
|
+
import { scanCoupling, scanLegacyArtifacts, scanReservedNamespace, scanRetiredStates } from '../engine/gates.js';
|
|
7
9
|
import { checkInventory, loadManifestContext, } from '../manifest/manifest.js';
|
|
8
10
|
import { checkLock, runSync } from '../sync/sync.js';
|
|
9
11
|
import { CONTEXT_RELATIVE, contextHash } from './context.js';
|
|
10
12
|
import { TARGETS, TOOL_IDS, isToolId } from '../sync/targets.js';
|
|
11
13
|
import { sourceCheckout } from './source.js';
|
|
14
|
+
import { speckitDoctorChecks, speckitNotApplicable } from '../speckit/doctor.js';
|
|
12
15
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
13
16
|
const COUPLING_FILE = path.join(__dirname, '..', '..', 'engine', 'coupling.json');
|
|
14
17
|
async function couplingRules() {
|
|
@@ -24,6 +27,45 @@ async function couplingRules() {
|
|
|
24
27
|
async function exists(file) {
|
|
25
28
|
return fs.access(file).then(() => true, () => false);
|
|
26
29
|
}
|
|
30
|
+
function gitOutput(root, args) {
|
|
31
|
+
const result = spawnSync('git', args, {
|
|
32
|
+
cwd: root,
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
shell: false,
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
status: result.status ?? 1,
|
|
38
|
+
text: `${result.stdout ?? ''}${result.stderr ?? ''}`.trim(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function versionedDestinations(root) {
|
|
42
|
+
const inside = gitOutput(root, ['rev-parse', '--is-inside-work-tree']);
|
|
43
|
+
if (inside.status !== 0 || inside.text !== 'true') {
|
|
44
|
+
return { applicable: false, tracked: [] };
|
|
45
|
+
}
|
|
46
|
+
const listed = gitOutput(root, [
|
|
47
|
+
'ls-files',
|
|
48
|
+
'--',
|
|
49
|
+
'.claude',
|
|
50
|
+
'.opencode',
|
|
51
|
+
'.pi',
|
|
52
|
+
'.cursor',
|
|
53
|
+
'.github/skills',
|
|
54
|
+
'.github/agents',
|
|
55
|
+
'.github/instructions',
|
|
56
|
+
'.mcp.json',
|
|
57
|
+
'.cursor/mcp.json',
|
|
58
|
+
'.vscode/mcp.json',
|
|
59
|
+
'opencode.json',
|
|
60
|
+
]);
|
|
61
|
+
if (listed.status !== 0) {
|
|
62
|
+
return { applicable: false, tracked: [] };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
applicable: true,
|
|
66
|
+
tracked: listed.text.split(/\r?\n/).map((line) => line.trim()).filter((line) => line !== ''),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
27
69
|
export async function diagnose(ctx) {
|
|
28
70
|
const checks = [];
|
|
29
71
|
const add = (id, ok, detail) => {
|
|
@@ -32,16 +74,18 @@ export async function diagnose(ctx) {
|
|
|
32
74
|
const source = await sourceCheckout(ctx.root);
|
|
33
75
|
let manifest = null;
|
|
34
76
|
let engine = null;
|
|
77
|
+
let preset = null;
|
|
35
78
|
try {
|
|
36
79
|
const context = await loadManifestContext(ctx.root);
|
|
37
80
|
manifest = context.manifest;
|
|
38
81
|
engine = context.engine;
|
|
82
|
+
preset = context.preset;
|
|
39
83
|
add('manifest', true, `manifest.yaml válido (proyecto ${manifest.project.id})`);
|
|
40
84
|
}
|
|
41
85
|
catch (error) {
|
|
42
86
|
add('manifest', false, error instanceof Error ? error.message : String(error));
|
|
43
87
|
}
|
|
44
|
-
if (manifest && engine) {
|
|
88
|
+
if (manifest && engine && preset) {
|
|
45
89
|
add('preset', true, `${manifest.preset.id}@${manifest.preset.version} (${manifest.preset.source})`);
|
|
46
90
|
add('engine:roles', engine.roles.length === ENGINE_COUNTS.roles, `${engine.roles.length}/${ENGINE_COUNTS.roles}`);
|
|
47
91
|
add('engine:commands', engine.commands.length === ENGINE_COUNTS.commands, `${engine.commands.length}/${ENGINE_COUNTS.commands}`);
|
|
@@ -60,20 +104,49 @@ export async function diagnose(ctx) {
|
|
|
60
104
|
add('policy:optOuts', true, manifest.policy.optOuts.length === 0
|
|
61
105
|
? 'ninguno'
|
|
62
106
|
: `opt-outs activos: ${manifest.policy.optOuts.join(', ')}`);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
107
|
+
const audit = await auditSelection(ctx.root, manifest, preset, engine);
|
|
108
|
+
const missingProjectSkills = audit.missing
|
|
109
|
+
.filter((item) => item.startsWith('skill:'))
|
|
110
|
+
.map((item) => item.slice('skill:'.length));
|
|
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(', '));
|
|
114
|
+
const missingAgents = audit.missing
|
|
115
|
+
.filter((item) => item.startsWith('agent:'))
|
|
116
|
+
.map((item) => item.slice('agent:'.length));
|
|
117
|
+
add('agents', missingAgents.length === 0, missingAgents.length === 0
|
|
118
|
+
? `${manifest.agents.publish.length} agentes (${audit.modes.agents})`
|
|
119
|
+
: `faltan: ${missingAgents.join(', ')}`);
|
|
120
|
+
const missingRules = audit.missing
|
|
121
|
+
.filter((item) => item.startsWith('rule:'))
|
|
122
|
+
.map((item) => item.slice('rule:'.length));
|
|
123
|
+
add('rules', missingRules.length === 0, missingRules.length === 0
|
|
124
|
+
? `${manifest.rules.publish.length + 1} reglas universales (${audit.modes.rules})`
|
|
125
|
+
: `faltan: ${missingRules.join(', ')}`);
|
|
126
|
+
const primaryError = audit.errors.find((error) => error.includes('publish editado')) ?? audit.errors[0];
|
|
127
|
+
add('completeness', audit.errors.length === 0, audit.errors.length === 0
|
|
128
|
+
? `skills=${audit.modes.skills} agents=${audit.modes.agents} rules=${audit.modes.rules}`
|
|
129
|
+
: primaryError);
|
|
130
|
+
if (Object.values(audit.modes).includes('full')) {
|
|
131
|
+
add('full', true, audit.warnings.length > 0 ? audit.warnings.join('; ') : 'catálogo al día');
|
|
68
132
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
if (!manifest.mcp) {
|
|
134
|
+
add('mcp', true, 'MCP: no configurado');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const direct = path.join(ctx.root, manifest.mcp.source);
|
|
138
|
+
const project = path.join(ctx.root, '.ai-hub', path.basename(manifest.mcp.source));
|
|
139
|
+
const present = (await exists(direct)) || (await exists(project));
|
|
140
|
+
add('mcp', present, present ? `MCP configurado (${manifest.mcp.source})` : `MCP: falta ${manifest.mcp.source}`);
|
|
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);
|
|
73
147
|
}
|
|
74
|
-
add('skills', missingSkills.length === 0, missingSkills.length === 0 ? '17 skills nativas + extras' : missingSkills.join(', '));
|
|
75
148
|
if (source.isSource) {
|
|
76
|
-
const required = ['dist', 'schema', 'manifest.json', 'engine', 'presets', 'agents', 'skills'];
|
|
149
|
+
const required = ['dist', 'schema', 'manifest.json', 'engine', 'presets', 'rules', 'agents', 'skills'];
|
|
77
150
|
const missing = required.filter((entry) => !source.files.includes(entry));
|
|
78
151
|
add('package:files', missing.length === 0, missing.length === 0 ? required.join(', ') : `faltan: ${missing.join(', ')}`);
|
|
79
152
|
add('package:entry', await exists(path.join(ctx.root, 'core', 'dist', 'bin', 'ai-hub.js')), 'core/dist/bin/ai-hub.js');
|
|
@@ -94,6 +167,10 @@ export async function diagnose(ctx) {
|
|
|
94
167
|
}
|
|
95
168
|
add('hooks', missingHooks.length === 0, missingHooks.length === 0 ? 'superficies presentes' : missingHooks.join(', '));
|
|
96
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})` : ''));
|
|
97
174
|
const legacy = await scanLegacyArtifacts(ctx.root);
|
|
98
175
|
add('engine:no-legacy-prefix', legacy.length === 0, legacy.length === 0
|
|
99
176
|
? source.isSource
|
|
@@ -169,8 +246,22 @@ export async function diagnose(ctx) {
|
|
|
169
246
|
const ignored = ignore.split(/\r?\n/).includes('.worktrees/');
|
|
170
247
|
add('worktrees:ignored', ignored, ignored ? '.worktrees/ ignorado' : 'añade .worktrees/ a .gitignore');
|
|
171
248
|
}
|
|
249
|
+
const versioning = versionedDestinations(ctx.root);
|
|
250
|
+
if (!versioning.applicable) {
|
|
251
|
+
add('versionado', true, 'no aplica (sin repositorio git)');
|
|
252
|
+
}
|
|
253
|
+
else if (versioning.tracked.length === 0) {
|
|
254
|
+
add('versionado', true, 'sin destinos generados versionados');
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
add('versionado', true, `aviso: destinos generados versionados: ${versioning.tracked.slice(0, 5).join(', ')}${versioning.tracked.length > 5 ? ` (+${versioning.tracked.length - 5})` : ''}`);
|
|
258
|
+
}
|
|
172
259
|
const coupling = await couplingRules();
|
|
173
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})` : ''));
|
|
174
265
|
const findings = await scanCoupling(ctx.root, coupling.terms);
|
|
175
266
|
add('engine:no-coupling', findings.length === 0, findings.length === 0
|
|
176
267
|
? 'sin términos de dominio en el motor'
|
package/dist/cli/items.d.ts
CHANGED
package/dist/cli/items.js
CHANGED
|
@@ -3,7 +3,7 @@ export function splitItem(args) {
|
|
|
3
3
|
if (!first) {
|
|
4
4
|
return { kind: null, id: null };
|
|
5
5
|
}
|
|
6
|
-
if (first === 'skill' || first === 'agent') {
|
|
6
|
+
if (first === 'skill' || first === 'agent' || first === 'rule') {
|
|
7
7
|
return { kind: first, id: second ?? null };
|
|
8
8
|
}
|
|
9
9
|
return { kind: null, id: first };
|
package/dist/cli/maintenance.js
CHANGED
|
@@ -5,15 +5,29 @@ 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,
|
|
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 { catalogIndex, catalogSeal, hasFullDimension } from '../catalog.js';
|
|
10
11
|
import { readLock } from '../sync/lock.js';
|
|
11
12
|
import { runSync } from '../sync/sync.js';
|
|
12
|
-
import { cliVersion, writeManifest } from './onboarding.js';
|
|
13
|
+
import { cliVersion, cmdInstall, writeManifest } from './onboarding.js';
|
|
13
14
|
import { splitItem } from './items.js';
|
|
14
15
|
async function exists(file) {
|
|
15
16
|
return fs.access(file).then(() => true, () => false);
|
|
16
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
|
+
}
|
|
22
|
+
function markCustom(manifest, dimension) {
|
|
23
|
+
manifest[dimension].selection = 'custom';
|
|
24
|
+
if (hasFullDimension(manifest)) {
|
|
25
|
+
manifest.catalog = catalogSeal(manifest);
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
delete manifest.catalog;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
17
31
|
export async function cmdList(args, ctx) {
|
|
18
32
|
let context;
|
|
19
33
|
try {
|
|
@@ -24,11 +38,18 @@ export async function cmdList(args, ctx) {
|
|
|
24
38
|
return 1;
|
|
25
39
|
}
|
|
26
40
|
const { manifest, engine, preset } = context;
|
|
41
|
+
const mode = manifest.skills.selection ?? 'preset';
|
|
27
42
|
const data = {
|
|
28
43
|
project: manifest.project,
|
|
29
44
|
preset: { id: preset.id, version: preset.version, chain: preset.chain },
|
|
30
45
|
targets: manifest.targets,
|
|
46
|
+
selection: {
|
|
47
|
+
skills: manifest.skills.selection ?? 'preset',
|
|
48
|
+
agents: manifest.agents.selection ?? 'preset',
|
|
49
|
+
rules: manifest.rules.selection ?? 'preset',
|
|
50
|
+
},
|
|
31
51
|
commands: engine.commands.map((command) => ({ id: command.id, lane: command.lane, role: command.role })),
|
|
52
|
+
speckitCommands: engine.commands.map((command) => command.speckit),
|
|
32
53
|
skills: manifest.skills.publish,
|
|
33
54
|
agents: manifest.agents.publish,
|
|
34
55
|
rules: manifest.rules.publish,
|
|
@@ -40,10 +61,11 @@ export async function cmdList(args, ctx) {
|
|
|
40
61
|
ctx.io.out(`project: ${manifest.project.id} (${manifest.project.name})`);
|
|
41
62
|
ctx.io.out(`preset: ${preset.id}@${preset.version} [${preset.chain.join(' -> ')}]`);
|
|
42
63
|
ctx.io.out(`targets: ${manifest.targets.join(', ')}`);
|
|
64
|
+
ctx.io.out(`selection: skills=${data.selection.skills} agents=${data.selection.agents} rules=${data.selection.rules}`);
|
|
43
65
|
ctx.io.out(`commands (${data.commands.length}): ${data.commands.map((command) => command.id).join(', ')}`);
|
|
44
|
-
ctx.io.out(`skills
|
|
66
|
+
ctx.io.out(`skills: ${data.skills.length} de proyecto (${mode}) + ${data.speckitCommands.length} comandos Spec Kit`);
|
|
45
67
|
ctx.io.out(`agents (${data.agents.length}): ${data.agents.join(', ')}`);
|
|
46
|
-
ctx.io.out(`rules
|
|
68
|
+
ctx.io.out(`rules: ${data.rules.length + 1} universales (${data.rules.length} materializadas + preflight)`);
|
|
47
69
|
return 0;
|
|
48
70
|
}
|
|
49
71
|
export async function cmdAdd(args, ctx) {
|
|
@@ -52,6 +74,10 @@ export async function cmdAdd(args, ctx) {
|
|
|
52
74
|
ctx.io.err('uso: ai-hub add <skill|agent> <id>');
|
|
53
75
|
return 2;
|
|
54
76
|
}
|
|
77
|
+
if (parsed.kind === 'rule') {
|
|
78
|
+
ctx.io.err('las reglas universales se seleccionan con --exclude rule:<id> en init; no se añaden con add');
|
|
79
|
+
return 2;
|
|
80
|
+
}
|
|
55
81
|
let context;
|
|
56
82
|
try {
|
|
57
83
|
context = await loadManifestContext(ctx.root);
|
|
@@ -62,18 +88,31 @@ export async function cmdAdd(args, ctx) {
|
|
|
62
88
|
}
|
|
63
89
|
const { manifest, preset } = context;
|
|
64
90
|
const id = parsed.id;
|
|
65
|
-
|
|
66
|
-
|
|
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);
|
|
96
|
+
const catalogAgent = await exists(path.join(ENGINE_AGENTS, `${id}.md`));
|
|
67
97
|
const kind = parsed.kind ??
|
|
68
|
-
(preset.skills.includes(id) ||
|
|
98
|
+
(preset.skills.includes(id) || catalogSkill
|
|
99
|
+
? 'skill'
|
|
100
|
+
: catalogAgent || preset.agents.includes(id)
|
|
101
|
+
? 'agent'
|
|
102
|
+
: null);
|
|
103
|
+
if (kind === null) {
|
|
104
|
+
ctx.io.err(`no existe "${id}" en el preset ${preset.id} ni en el catálogo del motor`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
69
107
|
if (kind === 'skill') {
|
|
70
|
-
if (engineSkill) {
|
|
71
|
-
ctx.io.out(`"${id}" ya forma parte de las 17 skills del motor`);
|
|
72
|
-
return 0;
|
|
73
|
-
}
|
|
74
108
|
const fromPreset = preset.skills.includes(id)
|
|
75
109
|
? await findPresetAsset(preset.chain, `skills/${id}`)
|
|
76
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
|
+
}
|
|
77
116
|
const source = fromPreset ?? path.join(ENGINE_SKILLS_DIR, id);
|
|
78
117
|
if (!(await exists(path.join(source, 'SKILL.md')))) {
|
|
79
118
|
ctx.io.err(`no existe la skill "${id}" en ${preset.id} ni en el catálogo del motor`);
|
|
@@ -84,29 +123,25 @@ export async function cmdAdd(args, ctx) {
|
|
|
84
123
|
ctx.io.out(JSON.stringify({ dryRun: true, action: 'add', kind, id, destination }, null, 2));
|
|
85
124
|
return 0;
|
|
86
125
|
}
|
|
87
|
-
|
|
88
|
-
await fs.cp(source, destination, { recursive: true });
|
|
89
|
-
}
|
|
126
|
+
await materializeSkill(ctx.root, id, source, fromPreset ? `@preset/${preset.id}/skills/${id}` : `@engine/skills/shared/${id}`);
|
|
90
127
|
manifest.skills.publish = [...new Set([...manifest.skills.publish, id])];
|
|
91
128
|
}
|
|
92
129
|
else {
|
|
93
|
-
if (!preset.agents.includes(id)) {
|
|
94
|
-
ctx.io.err(`el agente "${id}" no pertenece al preset ${preset.id}`);
|
|
95
|
-
return 1;
|
|
96
|
-
}
|
|
97
130
|
const fromPreset = await findPresetAsset(preset.chain, `agents/${id}.md`);
|
|
98
131
|
const source = fromPreset ?? path.join(ENGINE_AGENTS, `${id}.md`);
|
|
132
|
+
if (!(await exists(source))) {
|
|
133
|
+
ctx.io.err(`no existe el agente "${id}" en el catálogo ${ENGINE_AGENTS}`);
|
|
134
|
+
return 1;
|
|
135
|
+
}
|
|
99
136
|
const destination = path.join(ctx.root, manifest.agents.source, `${id}.md`);
|
|
100
137
|
if (args.flags['dry-run']) {
|
|
101
138
|
ctx.io.out(JSON.stringify({ dryRun: true, action: 'add', kind, id, destination }, null, 2));
|
|
102
139
|
return 0;
|
|
103
140
|
}
|
|
104
|
-
|
|
105
|
-
await fs.mkdir(path.dirname(destination), { recursive: true });
|
|
106
|
-
await fs.copyFile(source, destination);
|
|
107
|
-
}
|
|
141
|
+
await materializeAgent(ctx.root, id, source, fromPreset ? `@preset/${preset.id}/agents/${id}.md` : `@engine/agents/${id}.md`);
|
|
108
142
|
manifest.agents.publish = [...new Set([...manifest.agents.publish, id])];
|
|
109
143
|
}
|
|
144
|
+
markCustom(manifest, kind === 'agent' ? 'agents' : 'skills');
|
|
110
145
|
await writeManifest(ctx.root, manifest);
|
|
111
146
|
const sync = await runSync(ctx.root, { check: false });
|
|
112
147
|
await writeInventory(ctx.root);
|
|
@@ -116,7 +151,7 @@ export async function cmdAdd(args, ctx) {
|
|
|
116
151
|
export async function cmdUninstall(args, ctx) {
|
|
117
152
|
const parsed = splitItem(args);
|
|
118
153
|
if (!parsed.id) {
|
|
119
|
-
ctx.io.err('uso: ai-hub uninstall <skill|agent> <id>');
|
|
154
|
+
ctx.io.err('uso: ai-hub uninstall <skill|agent|rule> <id>');
|
|
120
155
|
return 2;
|
|
121
156
|
}
|
|
122
157
|
let context;
|
|
@@ -129,9 +164,20 @@ export async function cmdUninstall(args, ctx) {
|
|
|
129
164
|
}
|
|
130
165
|
const { manifest } = context;
|
|
131
166
|
const id = parsed.id;
|
|
132
|
-
const kind = parsed.kind ??
|
|
133
|
-
|
|
134
|
-
|
|
167
|
+
const kind = parsed.kind ??
|
|
168
|
+
(manifest.skills.publish.includes(id)
|
|
169
|
+
? 'skill'
|
|
170
|
+
: manifest.agents.publish.includes(id)
|
|
171
|
+
? 'agent'
|
|
172
|
+
: manifest.rules.publish.includes(id) || id === 'skill-quality'
|
|
173
|
+
? 'rule'
|
|
174
|
+
: 'agent');
|
|
175
|
+
if (isSpeckitCommandName(id)) {
|
|
176
|
+
ctx.io.err(`"${id}" lo provee Spec Kit; ai-hub no añade ni desinstala sus comandos`);
|
|
177
|
+
return 1;
|
|
178
|
+
}
|
|
179
|
+
if (kind === 'rule' && (id === 'core' || id === 'preflight')) {
|
|
180
|
+
ctx.io.err(`la regla base "${id}" no se puede desinstalar`);
|
|
135
181
|
return 1;
|
|
136
182
|
}
|
|
137
183
|
if (args.flags['dry-run']) {
|
|
@@ -142,10 +188,22 @@ export async function cmdUninstall(args, ctx) {
|
|
|
142
188
|
if (kind === 'skill') {
|
|
143
189
|
manifest.skills.publish = manifest.skills.publish.filter((skill) => skill !== id);
|
|
144
190
|
await removeOwned(ctx.root, path.join(manifest.skills.source, id), true);
|
|
191
|
+
markCustom(manifest, 'skills');
|
|
145
192
|
}
|
|
146
|
-
else {
|
|
193
|
+
else if (kind === 'agent') {
|
|
147
194
|
manifest.agents.publish = manifest.agents.publish.filter((agent) => agent !== id);
|
|
148
195
|
await removeOwned(ctx.root, path.join(manifest.agents.source, `${id}.md`), false);
|
|
196
|
+
markCustom(manifest, 'agents');
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
const removed = id === 'skill-quality'
|
|
200
|
+
? manifest.rules.publish.filter((rule) => rule.startsWith('skill-quality/'))
|
|
201
|
+
: manifest.rules.publish.filter((rule) => rule === id);
|
|
202
|
+
manifest.rules.publish = manifest.rules.publish.filter((rule) => !removed.includes(rule));
|
|
203
|
+
for (const rule of removed) {
|
|
204
|
+
await removeOwned(ctx.root, path.join(manifest.rules.source, `${rule}.md`), false);
|
|
205
|
+
}
|
|
206
|
+
markCustom(manifest, 'rules');
|
|
149
207
|
}
|
|
150
208
|
await writeManifest(ctx.root, manifest);
|
|
151
209
|
const sync = await runSync(ctx.root, { check: false });
|
|
@@ -153,30 +211,6 @@ export async function cmdUninstall(args, ctx) {
|
|
|
153
211
|
ctx.io.out(`uninstall: ${kind} "${id}" · ${sync.removed.length} archivos eliminados`);
|
|
154
212
|
return 0;
|
|
155
213
|
}
|
|
156
|
-
async function removeOwned(root, relative, directory) {
|
|
157
|
-
const absolute = path.join(root, relative);
|
|
158
|
-
if (!(await exists(absolute))) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
const record = await readScaffold(root);
|
|
162
|
-
if (directory) {
|
|
163
|
-
const files = record.files;
|
|
164
|
-
const prefix = `${relative.split(path.sep).join('/')}/`;
|
|
165
|
-
const owned = Object.entries(files).filter(([name]) => name.startsWith(prefix));
|
|
166
|
-
if (owned.length === 0) {
|
|
167
|
-
const marker = await fs.readFile(path.join(absolute, 'SKILL.md'), 'utf8').catch(() => '');
|
|
168
|
-
if (!marker.includes('GENERADO por ai-hub')) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
await fs.rm(absolute, { recursive: true, force: true });
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const text = await fs.readFile(absolute, 'utf8').catch(() => null);
|
|
176
|
-
if (text === null || text.includes('GENERADO por ai-hub')) {
|
|
177
|
-
await fs.rm(absolute, { force: true });
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
214
|
export async function cmdBackups(args, ctx) {
|
|
181
215
|
const snapshots = await listBackups(ctx.root);
|
|
182
216
|
if (args.flags.json) {
|
|
@@ -256,6 +290,26 @@ export async function cmdUpgrade(args, ctx) {
|
|
|
256
290
|
return 1;
|
|
257
291
|
}
|
|
258
292
|
}
|
|
293
|
+
let context;
|
|
294
|
+
try {
|
|
295
|
+
context = await loadManifestContext(ctx.root);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
ctx.io.err(error.message);
|
|
299
|
+
return 1;
|
|
300
|
+
}
|
|
301
|
+
const refreshFull = Boolean(args.flags.full) ||
|
|
302
|
+
(hasFullDimension(context.manifest) && !args.flags['preset-only']);
|
|
303
|
+
if (refreshFull) {
|
|
304
|
+
const code = await cmdInstall({
|
|
305
|
+
positionals: [],
|
|
306
|
+
flags: args.flags.full ? { full: true } : {},
|
|
307
|
+
}, ctx);
|
|
308
|
+
if (code === 0) {
|
|
309
|
+
ctx.io.out('upgrade: dimensiones full actualizadas y sello refrescado');
|
|
310
|
+
}
|
|
311
|
+
return code;
|
|
312
|
+
}
|
|
259
313
|
const result = await runSync(ctx.root, { check: false });
|
|
260
314
|
ctx.io.out(`upgrade: ${result.written.length} archivos regenerados`);
|
|
261
315
|
return 0;
|
package/dist/cli/memory.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { appendLedger, queryLedger, readLedger, resumeLedger, } from '../harness/memory/ledger.js';
|
|
2
|
+
import { isEngineCheckout } from '../source-checkout.js';
|
|
2
3
|
import { flagList, flagString } from './args.js';
|
|
3
4
|
function numberFlag(args, name) {
|
|
4
5
|
const raw = flagString(args, name);
|
|
@@ -9,6 +10,10 @@ function numberFlag(args, name) {
|
|
|
9
10
|
return Number.isFinite(value) ? value : null;
|
|
10
11
|
}
|
|
11
12
|
export async function cmdMemory(args, ctx) {
|
|
13
|
+
if (!(await isEngineCheckout(ctx.root))) {
|
|
14
|
+
ctx.io.out('memory: no aplica en instalación');
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
12
17
|
const sub = args.positionals[0] ?? 'list';
|
|
13
18
|
if (sub === 'add') {
|
|
14
19
|
const sessionId = flagString(args, 'session') ?? process.env.AI_HUB_SESSION ?? 'local';
|
|
@@ -74,6 +79,10 @@ export async function cmdMemory(args, ctx) {
|
|
|
74
79
|
ctx.io.out(`cambio activo: ${summary.activeChange ?? 'ninguno'}`);
|
|
75
80
|
ctx.io.out(`fase: ${summary.phase}`);
|
|
76
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
|
+
}
|
|
77
86
|
ctx.io.out(last === null
|
|
78
87
|
? 'ledger: sin eventos'
|
|
79
88
|
: `último evento: ${last.event} ${last.timestamps.at} ${last.sessionId} (${last.branch ?? '-'})`);
|
package/dist/cli/onboarding.d.ts
CHANGED
|
@@ -1,15 +1,47 @@
|
|
|
1
1
|
import { type ProjectManifest } from '../manifest/manifest.js';
|
|
2
|
+
import { type SelectionMode } from '../catalog.js';
|
|
2
3
|
import type { CliContext, ParsedArgs } from './types.js';
|
|
3
4
|
export declare function cliVersion(): Promise<string>;
|
|
4
|
-
export interface
|
|
5
|
+
export interface AssetPlan {
|
|
6
|
+
selection: SelectionMode;
|
|
7
|
+
selections: {
|
|
8
|
+
skills: SelectionMode;
|
|
9
|
+
agents: SelectionMode;
|
|
10
|
+
rules: SelectionMode;
|
|
11
|
+
};
|
|
12
|
+
skills: string[];
|
|
13
|
+
agents: string[];
|
|
14
|
+
rules: string[];
|
|
15
|
+
destinations: number;
|
|
16
|
+
files: number;
|
|
17
|
+
bytes: number;
|
|
18
|
+
}
|
|
19
|
+
export interface Plan extends AssetPlan {
|
|
5
20
|
command: 'init';
|
|
6
21
|
project: string;
|
|
7
22
|
preset: string;
|
|
8
23
|
targets: string[];
|
|
24
|
+
skillCount: number;
|
|
25
|
+
speckitCommands: number;
|
|
26
|
+
agentCount: number;
|
|
27
|
+
ruleCount: number;
|
|
28
|
+
mcp: boolean;
|
|
9
29
|
migrate: boolean;
|
|
10
30
|
write: string[];
|
|
31
|
+
speckit: SpeckitPlan;
|
|
11
32
|
hash: string;
|
|
12
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
|
+
}
|
|
13
45
|
export declare function writeManifest(root: string, manifest: ProjectManifest): Promise<void>;
|
|
14
46
|
export declare function cmdInit(args: ParsedArgs, ctx: CliContext): Promise<number>;
|
|
15
47
|
export declare function cmdInstall(args: ParsedArgs, ctx: CliContext): Promise<number>;
|