@bakers-build/bakers 0.0.0-stage → 0.1.0-next.42
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/LICENSE +21 -0
- package/README.md +76 -2
- package/bin/bakers +5 -0
- package/dist/src/config.d.ts +14 -0
- package/dist/src/config.js +523 -0
- package/dist/src/host.d.ts +30 -0
- package/dist/src/host.js +239 -0
- package/dist/src/plugins-barrel.d.ts +4 -0
- package/dist/src/plugins-barrel.js +16 -0
- package/dist/src/update.d.ts +15 -0
- package/dist/src/update.js +59 -0
- package/dist/src/validate.d.ts +78 -0
- package/dist/src/validate.js +272 -0
- package/package.json +67 -4
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type CommandDescriptor } from '@bakers-build/core';
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
/** The built-in command registry. `$BB_COMMANDS` (JSON array of descriptors)
|
|
4
|
+
* overrides it wholesale. */
|
|
5
|
+
export declare const DEFAULT_COMMANDS: CommandDescriptor[];
|
|
6
|
+
/** Resolve the registry: `$BB_COMMANDS` override else the built-in set. */
|
|
7
|
+
export declare function resolveCommands(): CommandDescriptor[];
|
|
8
|
+
export { resolveCliVersion } from '@bakers-build/core';
|
|
9
|
+
/** Host bases for ESLint-style sibling resolution: the meta install → cwd. */
|
|
10
|
+
export declare function hostBases(): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Value-aware argv pre-scan: find the first positional token that names a
|
|
13
|
+
* registered command. Skips the host's value-taking global (`--conductor <v>`)
|
|
14
|
+
* and maps `help <name>` → `<name>`. Returns undefined for `--help`, an unknown
|
|
15
|
+
* first positional, or a bare invocation.
|
|
16
|
+
*/
|
|
17
|
+
export declare function scanTarget(argv: string[], names: string[]): string | undefined;
|
|
18
|
+
/** Build the top-level host program, mounting only the target command (the rest
|
|
19
|
+
* are describe-only stubs). */
|
|
20
|
+
export declare function buildHostProgram(argv: string[], opts?: {
|
|
21
|
+
forceTarget?: string;
|
|
22
|
+
bases?: string[];
|
|
23
|
+
}): Promise<Command>;
|
|
24
|
+
/** Parse argv against the host program, mapping commander's clean exits. */
|
|
25
|
+
export declare function runHost(argv: string[], opts?: {
|
|
26
|
+
forceTarget?: string;
|
|
27
|
+
bases?: string[];
|
|
28
|
+
}): Promise<void>;
|
|
29
|
+
/** The `bakers` entrypoint: drop node + script path, run the host. */
|
|
30
|
+
export declare function main(argv: string[]): Promise<void>;
|
package/dist/src/host.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
import { commands as agentsCommands } from '@bakers-build/addon-agents/preset';
|
|
3
|
+
import { commands as deploymentCommands } from '@bakers-build/addon-deployment/preset';
|
|
4
|
+
import { commands as dropshCommands } from '@bakers-build/addon-dropsh/preset';
|
|
5
|
+
import { commands as infoCommands } from '@bakers-build/addon-info/preset';
|
|
6
|
+
import { commands as portsCommands } from '@bakers-build/addon-ports/preset';
|
|
7
|
+
import { commands as trackCommands } from '@bakers-build/addon-track/preset';
|
|
8
|
+
import { commands as conductorCommands } from '@bakers-build/conductor/preset';
|
|
9
|
+
import { resolveCliVersion, resolveConfigPath, resolveModuleEslintStyle, } from '@bakers-build/core';
|
|
10
|
+
import { commands as uiCommands } from '@bakers-build/ui/preset';
|
|
11
|
+
import { Command } from 'commander';
|
|
12
|
+
import { registerConfig } from './config.js';
|
|
13
|
+
import { registerUpdate } from './update.js';
|
|
14
|
+
import { registerValidate } from './validate.js';
|
|
15
|
+
// BB-201: the `bakers` plugin HOST. The meta package `@bakers-build/bakers` owns the
|
|
16
|
+
// CLI entrypoint (AC-1); ui / conductor / dropsh / deployment are declared,
|
|
17
|
+
// ESLint-style resolved, LAZILY mounted command plugins (AC-2). The host
|
|
18
|
+
// discovers + mounts; it hard-wires nothing. Only the invoked subcommand's module
|
|
19
|
+
// is imported — `bakers --help` lists all four via describe-only stubs and imports
|
|
20
|
+
// none; `bakers oven …` imports only the conductor module.
|
|
21
|
+
//
|
|
22
|
+
// BB-215: the registry is DERIVED from the command surface — the command
|
|
23
|
+
// addons' preset `commands` accumulators — rather than a hand-written list.
|
|
24
|
+
// Those presets are LIGHT (they import no runtime, only return descriptors), so
|
|
25
|
+
// composing them at boot keeps `bakers --help` import-light. `$BB_COMMANDS`
|
|
26
|
+
// still overrides the whole registry. BB-224 (Finding 7) added the fourth
|
|
27
|
+
// command surface, `@bakers-build/addon-deployment` (`bakers deployment …`), which the
|
|
28
|
+
// `conductor` command plugin used to register as a side effect.
|
|
29
|
+
/** Compose the built-in command registry from the command addons' presets. The
|
|
30
|
+
* accumulators are synchronous descriptor builders (light presets), threaded in
|
|
31
|
+
* the conductor → ui → dropsh → deployment order. */
|
|
32
|
+
function composeCommandSurface() {
|
|
33
|
+
const accumulators = [
|
|
34
|
+
conductorCommands,
|
|
35
|
+
uiCommands,
|
|
36
|
+
dropshCommands,
|
|
37
|
+
deploymentCommands,
|
|
38
|
+
agentsCommands,
|
|
39
|
+
infoCommands,
|
|
40
|
+
portsCommands,
|
|
41
|
+
trackCommands,
|
|
42
|
+
];
|
|
43
|
+
let acc = [];
|
|
44
|
+
for (const fn of accumulators) {
|
|
45
|
+
if (typeof fn === 'function')
|
|
46
|
+
acc = fn(acc, undefined);
|
|
47
|
+
}
|
|
48
|
+
return acc;
|
|
49
|
+
}
|
|
50
|
+
/** The built-in command registry. `$BB_COMMANDS` (JSON array of descriptors)
|
|
51
|
+
* overrides it wholesale. */
|
|
52
|
+
export const DEFAULT_COMMANDS = composeCommandSurface();
|
|
53
|
+
/** Resolve the registry: `$BB_COMMANDS` override else the built-in set. */
|
|
54
|
+
export function resolveCommands() {
|
|
55
|
+
const override = process.env.BB_COMMANDS;
|
|
56
|
+
if (override && override.trim() !== '') {
|
|
57
|
+
const parsed = JSON.parse(override);
|
|
58
|
+
if (!Array.isArray(parsed)) {
|
|
59
|
+
throw new Error('$BB_COMMANDS must be a JSON array of command descriptors');
|
|
60
|
+
}
|
|
61
|
+
return parsed;
|
|
62
|
+
}
|
|
63
|
+
return DEFAULT_COMMANDS;
|
|
64
|
+
}
|
|
65
|
+
export { resolveCliVersion } from '@bakers-build/core';
|
|
66
|
+
/** Host bases for ESLint-style sibling resolution: the meta install → cwd. */
|
|
67
|
+
export function hostBases() {
|
|
68
|
+
return [import.meta.url, pathToFileURL(`${process.cwd()}/`).href];
|
|
69
|
+
}
|
|
70
|
+
/** Load a command descriptor's `BbCommandPlugin` (default export, or the named
|
|
71
|
+
* `export`), resolving the module over the host bases. */
|
|
72
|
+
async function loadCommandPlugin(entry, bases) {
|
|
73
|
+
const resolved = resolveModuleEslintStyle(entry.plugin, bases);
|
|
74
|
+
if (resolved === undefined) {
|
|
75
|
+
throw new Error(`bakers: cannot resolve command plugin '${entry.plugin}' (command '${entry.name}')`);
|
|
76
|
+
}
|
|
77
|
+
const mod = (await import(pathToFileURL(resolved).href));
|
|
78
|
+
const factory = entry.export ? mod[entry.export] : mod.default;
|
|
79
|
+
if (factory === undefined || factory === null) {
|
|
80
|
+
throw new Error(`bakers: command plugin '${entry.plugin}' has no ${entry.export ?? 'default'} export`);
|
|
81
|
+
}
|
|
82
|
+
return factory;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Value-aware argv pre-scan: find the first positional token that names a
|
|
86
|
+
* registered command. Skips the host's value-taking global (`--conductor <v>`)
|
|
87
|
+
* and maps `help <name>` → `<name>`. Returns undefined for `--help`, an unknown
|
|
88
|
+
* first positional, or a bare invocation.
|
|
89
|
+
*/
|
|
90
|
+
export function scanTarget(argv, names) {
|
|
91
|
+
for (let i = 0; i < argv.length; i++) {
|
|
92
|
+
const a = argv[i];
|
|
93
|
+
if (a === '--conductor') {
|
|
94
|
+
i++; // skip its value
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (a === 'help') {
|
|
98
|
+
const next = argv[i + 1];
|
|
99
|
+
return next !== undefined && names.includes(next) ? next : undefined;
|
|
100
|
+
}
|
|
101
|
+
if (a.startsWith('-'))
|
|
102
|
+
continue; // any other flag
|
|
103
|
+
return names.includes(a) ? a : undefined;
|
|
104
|
+
}
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
/** Add a describe-only stub for a not-loaded command so `--help` stays complete.
|
|
108
|
+
* If it is ever dispatched (a mis-scan), its action self-loads the real plugin
|
|
109
|
+
* and re-runs the host forcing that target. */
|
|
110
|
+
function addStub(program, entry, bases, argv) {
|
|
111
|
+
program
|
|
112
|
+
.command(entry.name)
|
|
113
|
+
.description(pluginDescribe(entry))
|
|
114
|
+
.allowUnknownOption(true)
|
|
115
|
+
.allowExcessArguments(true)
|
|
116
|
+
.helpOption(false)
|
|
117
|
+
.action(async () => {
|
|
118
|
+
await runHost(argv, { forceTarget: entry.name, bases });
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
/** Best-effort describe text for a stub without importing the plugin. BB-215:
|
|
122
|
+
* the descriptor now carries its own `describe` (from the addon's preset); this
|
|
123
|
+
* falls back only for a `$BB_COMMANDS` entry that omits it. */
|
|
124
|
+
function pluginDescribe(entry) {
|
|
125
|
+
if (typeof entry.describe === 'string' && entry.describe.trim() !== '') {
|
|
126
|
+
return entry.describe;
|
|
127
|
+
}
|
|
128
|
+
switch (entry.name) {
|
|
129
|
+
case 'oven':
|
|
130
|
+
return 'node-agent lifecycle + local registry';
|
|
131
|
+
case 'ui':
|
|
132
|
+
return 'interactive terminal UI: dashboard, projects, tickets';
|
|
133
|
+
case 'dropsh':
|
|
134
|
+
return 'JSON:API shell (dropsh) bound to the bakers.build connection config';
|
|
135
|
+
case 'deployment':
|
|
136
|
+
return 'release / deployment helpers';
|
|
137
|
+
default:
|
|
138
|
+
return `${entry.name} command`;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Build the top-level host program, mounting only the target command (the rest
|
|
142
|
+
* are describe-only stubs). */
|
|
143
|
+
export async function buildHostProgram(argv, opts = {}) {
|
|
144
|
+
const bases = opts.bases ?? hostBases();
|
|
145
|
+
const commands = resolveCommands();
|
|
146
|
+
const names = commands.map((c) => c.name);
|
|
147
|
+
const target = opts.forceTarget ?? scanTarget(argv, names);
|
|
148
|
+
const projectContext = target === 'ui'
|
|
149
|
+
? await import('@bakers-build/conductor/project-context')
|
|
150
|
+
: undefined;
|
|
151
|
+
const host = {
|
|
152
|
+
resolveBases: bases,
|
|
153
|
+
workspacePlugin: async (workspaceId) => {
|
|
154
|
+
const { workspacePlugin } = await import('@bakers-build/addon-workspace');
|
|
155
|
+
return workspacePlugin({ workspace_id: workspaceId });
|
|
156
|
+
},
|
|
157
|
+
...(projectContext
|
|
158
|
+
? {
|
|
159
|
+
projectContext: {
|
|
160
|
+
resolveConfigPath: (cwd) => resolveConfigPath(undefined, cwd),
|
|
161
|
+
readWorkspace: (configPath) => projectContext.readBbProjectSelection(configPath).workspace,
|
|
162
|
+
resolveContext: async (configPath, connection) => {
|
|
163
|
+
const { createBbConnectionClient } = await import('@bakers-build/core');
|
|
164
|
+
const { resolveBbProjectContext } = await import('@bakers-build/conductor/contract');
|
|
165
|
+
const selection = projectContext.readBbProjectSelection(configPath);
|
|
166
|
+
return resolveBbProjectContext(await createBbConnectionClient(connection), selection);
|
|
167
|
+
},
|
|
168
|
+
writeWorkspace: async (configPath, name) => {
|
|
169
|
+
await projectContext.writeBbWorkspaceSelection(projectContext.bakersProjectOwner(configPath), name, `Set bakers.workspace in ${configPath} to switch manually.`);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
: {}),
|
|
174
|
+
};
|
|
175
|
+
const program = new Command();
|
|
176
|
+
// exitOverride so commander surfaces help/version/unknown-command as thrown
|
|
177
|
+
// CommanderErrors (mapped in runHost) instead of calling process.exit —
|
|
178
|
+
// otherwise an unknown command would kill the host process directly.
|
|
179
|
+
program.exitOverride();
|
|
180
|
+
program
|
|
181
|
+
.name('bakers')
|
|
182
|
+
.description('bakers.build conductor + client CLI')
|
|
183
|
+
.option('--conductor <name>', 'select a conductor by stem (conductor→conductor.config.js, else <name>.conductor.config.js; env $BB_CONDUCTOR)');
|
|
184
|
+
// The global --conductor flag threads into $BB_CONDUCTOR (flag wins over env)
|
|
185
|
+
// so every command selects the named engine config without per-command wiring.
|
|
186
|
+
program.hook('preAction', () => {
|
|
187
|
+
const name = program.opts().conductor;
|
|
188
|
+
if (typeof name === 'string' && name !== '') {
|
|
189
|
+
process.env.BB_CONDUCTOR = name;
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
const cliVersion = resolveCliVersion();
|
|
193
|
+
program.version(cliVersion); // -V, --version
|
|
194
|
+
program
|
|
195
|
+
.command('version')
|
|
196
|
+
.description('output the bakers CLI version')
|
|
197
|
+
.action(() => {
|
|
198
|
+
console.log(cliVersion);
|
|
199
|
+
});
|
|
200
|
+
registerConfig(program, { host });
|
|
201
|
+
registerUpdate(program);
|
|
202
|
+
registerValidate(program);
|
|
203
|
+
for (const entry of commands) {
|
|
204
|
+
if (entry.name === target) {
|
|
205
|
+
const plugin = await loadCommandPlugin(entry, bases);
|
|
206
|
+
await plugin.register(program, host);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
addStub(program, entry, bases, argv);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return program;
|
|
213
|
+
}
|
|
214
|
+
/** Parse argv against the host program, mapping commander's clean exits. */
|
|
215
|
+
export async function runHost(argv, opts = {}) {
|
|
216
|
+
const program = await buildHostProgram(argv, opts);
|
|
217
|
+
try {
|
|
218
|
+
await program.parseAsync(argv, { from: 'user' });
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
const e = err;
|
|
222
|
+
if (e.code === 'commander.helpDisplayed' ||
|
|
223
|
+
e.code === 'commander.version') {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (typeof e.code === 'string' && e.code.startsWith('commander.')) {
|
|
227
|
+
if (!process.exitCode)
|
|
228
|
+
process.exitCode = 1;
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
process.stderr.write(`${e.message ?? String(err)}\n`);
|
|
232
|
+
if (!process.exitCode)
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
/** The `bakers` entrypoint: drop node + script path, run the host. */
|
|
237
|
+
export async function main(argv) {
|
|
238
|
+
await runHost(argv.slice(2));
|
|
239
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { basicAuthProvider } from '@bakers-build/addon-auth-basic';
|
|
2
|
+
export { DrupalBbRemote, drupalRemote, } from '@bakers-build/addon-remote-drupal';
|
|
3
|
+
export { GitWorkspace, gitWorkspace, loadInstructions, } from '@bakers-build/addon-workspace-git';
|
|
4
|
+
export { type AgentCandidate, type ResolvedAgent, selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from '@bakers-build/conductor/contract';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// BB-224 (Finding 6, decision 11): the `@bakers-build/bakers/plugins` BACK-COMPAT
|
|
2
|
+
// barrel. It used to be a single `export * from '@bakers-build/core/plugins'`; that
|
|
3
|
+
// core barrel is deleted, because core no longer ships plugin implementations.
|
|
4
|
+
// The barrel itself stays (published configs may still name
|
|
5
|
+
// `{ plugin: '@bakers-build/bakers/plugins', export: 'drupalRemote' }`), but it now
|
|
6
|
+
// aggregates the CONTRIBUTIONS from where they actually live: the impl addons +
|
|
7
|
+
// the conductor-surface selectors. "plugins" is the right word for a
|
|
8
|
+
// contribution barrel (decision 16) — only the *package* prefix became `addon-`.
|
|
9
|
+
//
|
|
10
|
+
// The fakes (`fakeRemote`/`fakeWorkspace`) are deliberately NOT re-exported:
|
|
11
|
+
// they moved into `@bakers-build/addon-fake`, a workspace-only test fixture that is
|
|
12
|
+
// never published, so the published meta package must not depend on it.
|
|
13
|
+
export { basicAuthProvider } from '@bakers-build/addon-auth-basic';
|
|
14
|
+
export { DrupalBbRemote, drupalRemote, } from '@bakers-build/addon-remote-drupal';
|
|
15
|
+
export { GitWorkspace, gitWorkspace, loadInstructions, } from '@bakers-build/addon-workspace-git';
|
|
16
|
+
export { selectAgent, selectAgents, selectExecutor, selectRemote, selectWorkspace, } from '@bakers-build/conductor/contract';
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
export type SpawnUpdate = (cmd: string, args: string[]) => Promise<number>;
|
|
3
|
+
export type SpawnCapture = (cmd: string, args: string[]) => Promise<{
|
|
4
|
+
code: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
}>;
|
|
7
|
+
export interface UpdateOptions {
|
|
8
|
+
out?: (line: string) => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function runUpdate(run?: SpawnUpdate, query?: SpawnCapture, options?: UpdateOptions): Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
before: string | null;
|
|
13
|
+
after: string | null;
|
|
14
|
+
}>;
|
|
15
|
+
export declare function registerUpdate(program: Command): void;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { BB_PACKAGE } from '@bakers-build/core';
|
|
3
|
+
const spawnUpdate = (cmd, args) => new Promise((resolve) => {
|
|
4
|
+
const child = spawn(cmd, args, { stdio: 'inherit', shell: false });
|
|
5
|
+
child.on('error', () => resolve(1));
|
|
6
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
7
|
+
});
|
|
8
|
+
const capture = (cmd, args) => new Promise((resolve) => {
|
|
9
|
+
let stdout = '';
|
|
10
|
+
const child = spawn(cmd, args, {
|
|
11
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
12
|
+
shell: false,
|
|
13
|
+
});
|
|
14
|
+
child.stdout?.on('data', (chunk) => {
|
|
15
|
+
stdout += String(chunk);
|
|
16
|
+
});
|
|
17
|
+
child.on('error', () => resolve({ code: 1, stdout }));
|
|
18
|
+
child.on('close', (code) => resolve({ code: code ?? 1, stdout }));
|
|
19
|
+
});
|
|
20
|
+
async function installedVersion(run) {
|
|
21
|
+
try {
|
|
22
|
+
const result = await run('npm', [
|
|
23
|
+
'ls',
|
|
24
|
+
'-g',
|
|
25
|
+
BB_PACKAGE,
|
|
26
|
+
'--json',
|
|
27
|
+
'--depth',
|
|
28
|
+
'0',
|
|
29
|
+
]);
|
|
30
|
+
if (result.code !== 0)
|
|
31
|
+
return null;
|
|
32
|
+
const parsed = JSON.parse(result.stdout);
|
|
33
|
+
const version = parsed.dependencies?.[BB_PACKAGE]?.version;
|
|
34
|
+
return typeof version === 'string' ? version : null;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function runUpdate(run = spawnUpdate, query = capture, options = {}) {
|
|
41
|
+
const before = await installedVersion(query);
|
|
42
|
+
if ((await run('npm', ['install', '-g', `${BB_PACKAGE}@latest`])) !== 0)
|
|
43
|
+
return { ok: false, before, after: before };
|
|
44
|
+
const after = await installedVersion(query);
|
|
45
|
+
if (after === null)
|
|
46
|
+
return { ok: false, before, after };
|
|
47
|
+
(options.out ?? console.log)(`${before ?? 'unknown'} → ${after}`);
|
|
48
|
+
return { ok: true, before, after };
|
|
49
|
+
}
|
|
50
|
+
export function registerUpdate(program) {
|
|
51
|
+
program
|
|
52
|
+
.command('update')
|
|
53
|
+
.description('install the latest bakers CLI')
|
|
54
|
+
.action(async () => {
|
|
55
|
+
const result = await runUpdate();
|
|
56
|
+
if (!result.ok)
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { type EmptyLoaderShape, type SkillContract, type StepContractErrorCode } from '@bakers-build/core';
|
|
2
|
+
import type { Command } from 'commander';
|
|
3
|
+
/** A `SKILL.md` whose frontmatter is not a readable skill contract. */
|
|
4
|
+
export interface SkillReadFailure {
|
|
5
|
+
path: string;
|
|
6
|
+
reason: string;
|
|
7
|
+
}
|
|
8
|
+
/** What the skill scan found: the name index, plus what it could not read. */
|
|
9
|
+
export interface SkillIndex {
|
|
10
|
+
byName: Map<string, SkillContract>;
|
|
11
|
+
/** Paths that parsed, in scan order — for the green summary's count. */
|
|
12
|
+
indexed: string[];
|
|
13
|
+
/** A later `SKILL.md` re-declaring an already-indexed name (first wins). */
|
|
14
|
+
shadowed: {
|
|
15
|
+
name: string;
|
|
16
|
+
path: string;
|
|
17
|
+
}[];
|
|
18
|
+
failures: SkillReadFailure[];
|
|
19
|
+
}
|
|
20
|
+
/** The verdict: `ok`, or the first contract defect with its message. */
|
|
21
|
+
export interface ValidateReport {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
workflowPath: string;
|
|
24
|
+
roots: string[];
|
|
25
|
+
index: SkillIndex;
|
|
26
|
+
/** Loaded leaf skills — owners plus helpers — when the load got that far. */
|
|
27
|
+
loaded?: {
|
|
28
|
+
owners: string[];
|
|
29
|
+
helpers: string[];
|
|
30
|
+
};
|
|
31
|
+
tripleCount?: number;
|
|
32
|
+
error?: string;
|
|
33
|
+
/**
|
|
34
|
+
* The contract defect's classification, when core tagged one. BB-436: this is
|
|
35
|
+
* the seam `formatReport` branches on — it replaced a regex over core's message
|
|
36
|
+
* text, which matched only one of the two throw sites that need a hint and would
|
|
37
|
+
* have broken silently the next time a message was reworded.
|
|
38
|
+
*/
|
|
39
|
+
errorCode?: StepContractErrorCode;
|
|
40
|
+
/** For `unresolved_skill`: the skill name the load could not resolve. */
|
|
41
|
+
unresolvedSkill?: string;
|
|
42
|
+
/**
|
|
43
|
+
* For `empty_loader`: what the `## Loaded skills` section actually holds. Core
|
|
44
|
+
* raises that code for three shapes and its message can only describe one of
|
|
45
|
+
* them, so the shape is measured here and `formatReport` prints the remedy that
|
|
46
|
+
* fits the document rather than the one that fits the common case.
|
|
47
|
+
*/
|
|
48
|
+
emptyLoader?: EmptyLoaderShape;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Index every skill reachable from `roots` by its declared `name` — the key a
|
|
52
|
+
* `WORKFLOW.md` bullet resolves on, whatever namespace prefix the bullet wears.
|
|
53
|
+
*
|
|
54
|
+
* A `SKILL.md` that is not a readable bakers.build skill contract is RECORDED rather than
|
|
55
|
+
* thrown on: a project's skill tree legitimately holds third-party skills that
|
|
56
|
+
* declare no bakers.build frontmatter, and failing the whole validation over one of them
|
|
57
|
+
* would make the command useless exactly where it is most needed. The record is
|
|
58
|
+
* printed only when a bullet then fails to resolve, where it is the likely cause.
|
|
59
|
+
*/
|
|
60
|
+
export declare function indexSkills(roots: string[]): SkillIndex;
|
|
61
|
+
export interface ValidateOptions {
|
|
62
|
+
/** A project directory, or a loader document (`WORKFLOW.md`) directly. */
|
|
63
|
+
path?: string | undefined;
|
|
64
|
+
/** Project root for default skill-root discovery; defaults from `path`. */
|
|
65
|
+
project?: string | undefined;
|
|
66
|
+
/** Extra skill roots, in precedence order before the defaults. */
|
|
67
|
+
skills?: string[] | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Validate a project's loader: read `WORKFLOW.md`, index the skills it can reach,
|
|
71
|
+
* expand the load, derive the project's triple set, and enforce the contract.
|
|
72
|
+
*
|
|
73
|
+
* Returns the verdict rather than throwing, so the caller owns the exit code.
|
|
74
|
+
*/
|
|
75
|
+
export declare function runValidate(options?: ValidateOptions): ValidateReport;
|
|
76
|
+
/** Render the report for a terminal. Returns the lines, so tests can read them. */
|
|
77
|
+
export declare function formatReport(report: ValidateReport): string[];
|
|
78
|
+
export declare function registerValidate(program: Command): void;
|