@gaia-ai/core 0.5.4 → 0.6.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.
Files changed (34) hide show
  1. package/dist/src/cli/commands.d.ts +27 -0
  2. package/dist/src/cli/commands.js +1 -0
  3. package/dist/src/cli/gaia-dir.d.ts +51 -0
  4. package/dist/src/cli/gaia-dir.js +152 -0
  5. package/dist/src/cli/load-gaia-config.d.ts +25 -0
  6. package/dist/src/cli/load-gaia-config.js +110 -0
  7. package/dist/src/cli/machine-context.d.ts +24 -0
  8. package/dist/src/cli/machine-context.js +45 -0
  9. package/dist/src/cli/paths.d.ts +11 -0
  10. package/dist/src/cli/paths.js +31 -0
  11. package/dist/src/cli/resolve-module.d.ts +6 -0
  12. package/dist/src/cli/resolve-module.js +24 -0
  13. package/dist/src/conductor-registry/index.d.ts +23 -0
  14. package/dist/src/conductor-registry/index.js +59 -0
  15. package/dist/src/index.d.ts +11 -0
  16. package/dist/src/index.js +14 -0
  17. package/dist/src/plugins/auth/basic.d.ts +1 -0
  18. package/dist/src/plugins/auth/basic.js +3 -1
  19. package/dist/src/plugins/builtins-preset.d.ts +5 -0
  20. package/dist/src/plugins/builtins-preset.js +32 -0
  21. package/dist/src/plugins/discover-addons.d.ts +22 -0
  22. package/dist/src/plugins/discover-addons.js +228 -0
  23. package/dist/src/plugins/executor/executor.d.ts +13 -8
  24. package/dist/src/plugins/preset.d.ts +76 -0
  25. package/dist/src/plugins/preset.js +42 -0
  26. package/dist/src/plugins/remote/drupal.d.ts +5 -0
  27. package/dist/src/plugins/remote/drupal.js +27 -1
  28. package/dist/src/plugins/remote/fake.d.ts +4 -0
  29. package/dist/src/plugins/remote/fake.js +5 -0
  30. package/dist/src/plugins/remote/remote.d.ts +12 -0
  31. package/dist/src/types.d.ts +14 -1
  32. package/dist/src/workflow/step-contract.d.ts +119 -0
  33. package/dist/src/workflow/step-contract.js +430 -0
  34. package/package.json +10 -4
@@ -0,0 +1,119 @@
1
+ /** The workflow states an agent works — the legal `step` values. */
2
+ export declare const WORKFLOW_STEPS: readonly ["qualification", "spec", "diagnose", "coding", "review", "pre_deployment", "post_deployment", "verifying", "summary"];
3
+ export type WorkflowStep = (typeof WORKFLOW_STEPS)[number];
4
+ /** Typed defect raised on the first contract violation. */
5
+ export declare class StepContractError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ /**
9
+ * A normalized `when` clause value: a concrete list of allowed values, the
10
+ * wildcard `'*'` (matches anything), or `undefined` (key omitted — also any).
11
+ */
12
+ export type WhenValue = string[] | '*' | undefined;
13
+ /** A skill's `when` triple. Any of the three keys may be omitted (= any). */
14
+ export interface SkillWhen {
15
+ work_type: WhenValue;
16
+ workflow: WhenValue;
17
+ step: WhenValue;
18
+ }
19
+ /** One declared input: a value key with a short description and an optional default. */
20
+ export interface InputDecl {
21
+ /** The value key the skill reads (e.g. `test.command`). */
22
+ key: string;
23
+ /** A short human description of what the value is. */
24
+ description?: string;
25
+ /** The default used when `WORKFLOW.md` supplies no override for this key. */
26
+ default?: string;
27
+ }
28
+ /** The parsed contract of a single skill's frontmatter. */
29
+ export interface SkillContract {
30
+ /** Skill name from frontmatter. */
31
+ name: string;
32
+ /**
33
+ * The `when` triple, or `undefined` when the skill declares no `when` — a
34
+ * helper/capability skill that owns no step and routes nothing.
35
+ */
36
+ when?: SkillWhen;
37
+ /** The inputs the skill declares (each: key + description + optional default). */
38
+ inputs: InputDecl[];
39
+ /**
40
+ * A **bundle** skill carries its own `## Loaded skills` section in its markdown
41
+ * **body** — the same list a `WORKFLOW.md` carries. `loads` holds those member
42
+ * names in declared order. Nothing in the frontmatter marks a bundle; the
43
+ * section is the marker. Present only on bundles.
44
+ */
45
+ loads?: string[];
46
+ /**
47
+ * A bundle's member values: the inline `key: value` overrides written under a
48
+ * member's bullet in the bundle body, keyed by member skill name. Present only
49
+ * on bundles.
50
+ */
51
+ values?: StepValues;
52
+ }
53
+ /**
54
+ * A concrete `(work_type, workflow, step)` the project can produce. `work_type`
55
+ * is `null` for a non-work-typed step (qualification, spec, deployment,
56
+ * verification), a `work:*` label (without the prefix) otherwise.
57
+ */
58
+ export interface Triple {
59
+ work_type: string | null;
60
+ workflow: string;
61
+ step: WorkflowStep;
62
+ }
63
+ /** Per-skill values, keyed by skill `name` (the `@gaia/` prefix stripped). */
64
+ export type StepValues = Record<string, Record<string, string>>;
65
+ /**
66
+ * Read the per-skill value **overrides** from a `WORKFLOW.md`'s `## Loaded skills`
67
+ * section. A project overrides a skill default **inline under that skill's bullet**
68
+ * (indented `key: value` YAML, block scalars allowed), not in one global block.
69
+ * Returns a map keyed by skill `name` (the `@gaia/` prefix stripped); a skill with no
70
+ * overrides maps to `{}` (it runs on its declared defaults).
71
+ */
72
+ export declare function parseStepValues(workflowMd: string): StepValues;
73
+ /**
74
+ * Parse a skill's frontmatter into its `SkillContract` — the `when` triple (when
75
+ * present) and the declared `inputs`. Malformed frontmatter raises `StepContractError`.
76
+ */
77
+ export declare function readSkillWhen(skillMd: string): SkillContract;
78
+ /** What a load expands to: the flat leaf skills plus the values that reached them. */
79
+ export interface ExpandedLoad {
80
+ /** The leaf skills (step-owners + helpers), deduplicated, in first-seen order. */
81
+ skills: SkillContract[];
82
+ /** The effective per-skill values, keyed by skill name. */
83
+ values: StepValues;
84
+ }
85
+ /**
86
+ * Read a `WORKFLOW.md` and expand what it loads into the flat, deduplicated list of
87
+ * **leaf** skills (step-owners + helpers) the project effectively runs, together with
88
+ * the values that reach each leaf. One call is the whole load step: parse, expand,
89
+ * resolve — hand the result straight to `validateLoad`.
90
+ *
91
+ * A **bundle** — a skill whose markdown body carries its own `## Loaded skills`
92
+ * section, so its contract carries `loads` — is recursed into, in its declared
93
+ * member order; a step-owner or helper is emitted as itself. Leaves are
94
+ * deduplicated **by name, first occurrence wins**, so the result is deterministic
95
+ * and idempotent — listing the same bundle twice, or a bundle plus one of its
96
+ * members as an explicit bullet, yields each leaf exactly once in first-seen order.
97
+ *
98
+ * **Value precedence**, narrowest wins: the project's own inline override in
99
+ * `WORKFLOW.md` beats a bundle's inline value for that member, which beats the
100
+ * skill's declared `default` (resolved later, in `validateLoad`). Between nested
101
+ * bundles the outermost wins, matching first-occurrence dedup.
102
+ *
103
+ * A root or member name absent from `byName`, or a cycle in the bundle graph,
104
+ * raises a typed `StepContractError`. Bundles may nest (cycle-safe); gaia ships one
105
+ * flat level. This never dispatches or routes.
106
+ */
107
+ export declare function expandLoad(workflowMd: string, byName: Map<string, SkillContract>): ExpandedLoad;
108
+ /**
109
+ * Validate a loaded set of skills against the project's triple set and per-skill values.
110
+ *
111
+ * For every `(work_type, workflow, step)` the project can produce, **exactly one**
112
+ * loaded skill's `when` must match — a triple with no match (gap) or two matches
113
+ * (collision) is a configuration error. Every `input` a matched skill declares must
114
+ * resolve to a value: the `values` map (as resolved by `expandLoad`) or the input's
115
+ * own declared `default`.
116
+ *
117
+ * Reports the first defect as a typed `StepContractError`. Never dispatches or routes.
118
+ */
119
+ export declare function validateLoad(skills: SkillContract[], values: StepValues, projectTriples: Triple[]): void;
@@ -0,0 +1,430 @@
1
+ /**
2
+ * GAIA-204 — workflow step contract (pure library).
3
+ *
4
+ * "Everything is a skill; `WORKFLOW.md` is a pure loader." Each step-owning skill
5
+ * self-declares one `when:` over three variables — `work_type`, `workflow`, `step` —
6
+ * and owns that step's whole flow in prose. Each skill declares its `inputs` (a short
7
+ * description + a default per value); a project overrides a value inline under the
8
+ * skill's bullet in `WORKFLOW.md`'s `## Loaded skills` list. Effective value =
9
+ * override ?? default.
10
+ *
11
+ * A **bundle** skill carries a `## Loaded skills` section in its own body and thereby
12
+ * loads further skills — the same section, the same bullet syntax, the same parser as
13
+ * `WORKFLOW.md`. Nothing in the frontmatter marks it; the section is the marker.
14
+ *
15
+ * This module is the mechanical enforcement of that model. It PARSES, EXPANDS along
16
+ * a fixed value precedence, and VALIDATES — it never dispatches or routes.
17
+ * Its only consumers are the contract test suite and `@gaia/upgrade-project` /
18
+ * `@gaia/initialize-project` load-time validation.
19
+ */
20
+ import { parse as parseYaml } from 'yaml';
21
+ /** The workflow states an agent works — the legal `step` values. */
22
+ export const WORKFLOW_STEPS = [
23
+ 'qualification',
24
+ 'spec',
25
+ 'diagnose',
26
+ 'coding',
27
+ 'review',
28
+ 'pre_deployment',
29
+ 'post_deployment',
30
+ 'verifying',
31
+ 'summary',
32
+ ];
33
+ /** Typed defect raised on the first contract violation. */
34
+ export class StepContractError extends Error {
35
+ constructor(message) {
36
+ super(message);
37
+ this.name = 'StepContractError';
38
+ }
39
+ }
40
+ const LOADED_SKILLS_HEADING = /^##\s+Loaded skills\s*$/;
41
+ const BULLET = /^\s*-\s+@gaia\/([a-z0-9-]+)\s*$/;
42
+ /**
43
+ * Locate the `## Loaded skills` section in a markdown document: the `[start, end)`
44
+ * line range of its body, or `null` when the document carries no such section.
45
+ *
46
+ * The section is the **only** marker of a loader — `WORKFLOW.md` carries one, and so
47
+ * does a **bundle** skill whose body loads further skills. Nothing else distinguishes
48
+ * the two.
49
+ */
50
+ function loadedSkillsRange(lines) {
51
+ const start = lines.findIndex((l) => LOADED_SKILLS_HEADING.test(l));
52
+ if (start === -1)
53
+ return null;
54
+ let end = lines.length;
55
+ for (let i = start + 1; i < lines.length; i++) {
56
+ if (/^##\s+/.test(lines[i] ?? '')) {
57
+ end = i;
58
+ break;
59
+ }
60
+ }
61
+ return [start, end];
62
+ }
63
+ /**
64
+ * Read the per-skill value **overrides** from a `WORKFLOW.md`'s `## Loaded skills`
65
+ * section. A project overrides a skill default **inline under that skill's bullet**
66
+ * (indented `key: value` YAML, block scalars allowed), not in one global block.
67
+ * Returns a map keyed by skill `name` (the `@gaia/` prefix stripped); a skill with no
68
+ * overrides maps to `{}` (it runs on its declared defaults).
69
+ */
70
+ export function parseStepValues(workflowMd) {
71
+ return parseLoadedSection(workflowMd).values;
72
+ }
73
+ /**
74
+ * Scan a `## Loaded skills` section once and return both what it lists and what it
75
+ * sets. Order and values come from the same pass, so they can never disagree — the
76
+ * reason there is no separate name-only parser.
77
+ *
78
+ * Works on any document carrying the section: a project's `WORKFLOW.md` and a
79
+ * **bundle** skill's markdown body are read by the exact same call.
80
+ */
81
+ function parseLoadedSection(workflowMd) {
82
+ const lines = workflowMd.split('\n');
83
+ const range = loadedSkillsRange(lines);
84
+ if (range === null) {
85
+ throw new StepContractError('WORKFLOW.md has no `## Loaded skills` section; a pure loader must carry one.');
86
+ }
87
+ const [start, end] = range;
88
+ const names = [];
89
+ const values = {};
90
+ let current = null;
91
+ let childIndent = null;
92
+ let buffer = [];
93
+ const flush = () => {
94
+ if (current === null)
95
+ return;
96
+ const yamlText = buffer.join('\n').trim();
97
+ if (yamlText === '') {
98
+ values[current] = {};
99
+ }
100
+ else {
101
+ let parsed;
102
+ try {
103
+ parsed = parseYaml(yamlText);
104
+ }
105
+ catch (cause) {
106
+ throw new StepContractError(`inline values for \`${current}\` are not valid YAML: ${cause.message}`);
107
+ }
108
+ if (parsed == null ||
109
+ typeof parsed !== 'object' ||
110
+ Array.isArray(parsed)) {
111
+ throw new StepContractError(`inline values for \`${current}\` must be a map of value keys to values.`);
112
+ }
113
+ const inner = {};
114
+ for (const [k, v] of Object.entries(parsed)) {
115
+ if (typeof v !== 'string') {
116
+ throw new StepContractError(`inline value \`${current}.${k}\` must be a string.`);
117
+ }
118
+ inner[k] = v;
119
+ }
120
+ values[current] = inner;
121
+ }
122
+ buffer = [];
123
+ childIndent = null;
124
+ };
125
+ for (const raw of lines.slice(start + 1, end)) {
126
+ const bullet = BULLET.exec(raw);
127
+ if (bullet) {
128
+ flush();
129
+ current = bullet[1] ?? null;
130
+ if (current !== null)
131
+ names.push(current);
132
+ continue;
133
+ }
134
+ if (current === null)
135
+ continue; // prose before the first bullet
136
+ if (raw.trim() === '') {
137
+ buffer.push('');
138
+ continue;
139
+ }
140
+ const indent = raw.length - raw.trimStart().length;
141
+ if (indent === 0) {
142
+ // dedented prose after the list ends the current skill's values
143
+ flush();
144
+ current = null;
145
+ continue;
146
+ }
147
+ if (childIndent === null)
148
+ childIndent = indent;
149
+ buffer.push(raw.slice(Math.min(childIndent, indent)));
150
+ }
151
+ flush();
152
+ return { names, values };
153
+ }
154
+ /** Split leading YAML frontmatter (`---` … `---`) from a skill markdown body. */
155
+ function readFrontmatter(skillMd) {
156
+ const match = /^---\n([\s\S]*?)\n---\s*(?:\n|$)/.exec(skillMd);
157
+ if (!match) {
158
+ throw new StepContractError('skill markdown has no YAML frontmatter.');
159
+ }
160
+ let parsed;
161
+ try {
162
+ parsed = parseYaml(match[1] ?? '');
163
+ }
164
+ catch (cause) {
165
+ throw new StepContractError(`skill frontmatter is not valid YAML: ${cause.message}`);
166
+ }
167
+ if (parsed == null || typeof parsed !== 'object' || Array.isArray(parsed)) {
168
+ throw new StepContractError('skill frontmatter must be a YAML map.');
169
+ }
170
+ return parsed;
171
+ }
172
+ /** Normalize a raw `when` value (string / list / `*` / omitted) to a `WhenValue`. */
173
+ function normalizeWhenValue(raw, key) {
174
+ if (raw === undefined || raw === null)
175
+ return undefined;
176
+ if (raw === '*')
177
+ return '*';
178
+ if (typeof raw === 'string')
179
+ return [raw];
180
+ if (Array.isArray(raw)) {
181
+ if (raw.some((v) => typeof v !== 'string')) {
182
+ throw new StepContractError(`\`when.${key}\` list must contain only strings.`);
183
+ }
184
+ if (raw.length === 0) {
185
+ throw new StepContractError(`\`when.${key}\` list must not be empty.`);
186
+ }
187
+ return raw;
188
+ }
189
+ throw new StepContractError(`\`when.${key}\` must be a string, a list of strings, \`*\`, or omitted.`);
190
+ }
191
+ /**
192
+ * Parse a skill's frontmatter into its `SkillContract` — the `when` triple (when
193
+ * present) and the declared `inputs`. Malformed frontmatter raises `StepContractError`.
194
+ */
195
+ export function readSkillWhen(skillMd) {
196
+ const fm = readFrontmatter(skillMd);
197
+ const name = fm.name;
198
+ if (typeof name !== 'string' || name.length === 0) {
199
+ throw new StepContractError('skill frontmatter must declare a string `name`.');
200
+ }
201
+ let when;
202
+ if (fm.when !== undefined) {
203
+ if (fm.when === null ||
204
+ typeof fm.when !== 'object' ||
205
+ Array.isArray(fm.when)) {
206
+ throw new StepContractError(`\`${name}\`: \`when\` must be a map.`);
207
+ }
208
+ const raw = fm.when;
209
+ const allowed = new Set(['work_type', 'workflow', 'step']);
210
+ for (const k of Object.keys(raw)) {
211
+ if (!allowed.has(k)) {
212
+ throw new StepContractError(`\`${name}\`: \`when.${k}\` is not a recognized key (work_type, workflow, step).`);
213
+ }
214
+ }
215
+ const step = normalizeWhenValue(raw.step, 'step');
216
+ if (Array.isArray(step)) {
217
+ for (const s of step) {
218
+ if (!WORKFLOW_STEPS.includes(s)) {
219
+ throw new StepContractError(`\`${name}\`: \`when.step\` value \`${s}\` is not a known workflow step.`);
220
+ }
221
+ }
222
+ }
223
+ when = {
224
+ work_type: normalizeWhenValue(raw.work_type, 'work_type'),
225
+ workflow: normalizeWhenValue(raw.workflow, 'workflow'),
226
+ step,
227
+ };
228
+ }
229
+ const isBundle = loadedSkillsRange(skillMd.split('\n')) !== null;
230
+ if (isBundle) {
231
+ if (when !== undefined) {
232
+ throw new StepContractError(`\`${name}\`: a bundle loads skills in its body and owns no step — it must declare no \`when\`.`);
233
+ }
234
+ if (fm.inputs !== undefined) {
235
+ throw new StepContractError(`\`${name}\`: a bundle declares no \`inputs\` — it carries values for its members, it takes none itself.`);
236
+ }
237
+ }
238
+ const inputs = readInputs(fm.inputs, name);
239
+ const contract = { name, inputs };
240
+ if (when !== undefined)
241
+ contract.when = when;
242
+ if (isBundle) {
243
+ const section = parseLoadedSection(skillMd);
244
+ if (section.names.length === 0) {
245
+ throw new StepContractError(`\`${name}\`: the \`## Loaded skills\` section lists no skill.`);
246
+ }
247
+ contract.loads = section.names;
248
+ contract.values = section.values;
249
+ }
250
+ return contract;
251
+ }
252
+ /**
253
+ * Parse the `inputs` declaration. Each input is a value key with a short
254
+ * `description` and an optional `default` (used when `WORKFLOW.md` supplies no
255
+ * override). The map form is canonical:
256
+ *
257
+ * ```yaml
258
+ * inputs:
259
+ * test.command:
260
+ * description: shell command that runs the e2e suite
261
+ * default: ddev gaia-test-e2e
262
+ * ```
263
+ *
264
+ * A bare string value is shorthand for `{ description: <string> }`, and a plain
265
+ * list of keys (`inputs: [a, b]`) is accepted for description-less inputs.
266
+ */
267
+ function readInputs(raw, name) {
268
+ if (raw === undefined)
269
+ return [];
270
+ if (Array.isArray(raw)) {
271
+ return raw.map((k) => {
272
+ if (typeof k !== 'string') {
273
+ throw new StepContractError(`\`${name}\`: \`inputs\` list items must be strings.`);
274
+ }
275
+ return { key: k };
276
+ });
277
+ }
278
+ if (raw === null || typeof raw !== 'object') {
279
+ throw new StepContractError(`\`${name}\`: \`inputs\` must be a map of key → { description, default } (or a list of keys).`);
280
+ }
281
+ const decls = [];
282
+ for (const [key, spec] of Object.entries(raw)) {
283
+ if (typeof spec === 'string') {
284
+ decls.push({ key, description: spec });
285
+ continue;
286
+ }
287
+ if (spec === null || typeof spec !== 'object' || Array.isArray(spec)) {
288
+ throw new StepContractError(`\`${name}\`: input \`${key}\` must be a string or a { description, default } map.`);
289
+ }
290
+ const s = spec;
291
+ if (s.description !== undefined && typeof s.description !== 'string') {
292
+ throw new StepContractError(`\`${name}\`: input \`${key}\`.description must be a string.`);
293
+ }
294
+ if (s.default !== undefined && typeof s.default !== 'string') {
295
+ throw new StepContractError(`\`${name}\`: input \`${key}\`.default must be a string.`);
296
+ }
297
+ const decl = { key };
298
+ if (typeof s.description === 'string')
299
+ decl.description = s.description;
300
+ if (typeof s.default === 'string')
301
+ decl.default = s.default;
302
+ decls.push(decl);
303
+ }
304
+ return decls;
305
+ }
306
+ /** Does a normalized `when` value match a triple component? */
307
+ function componentMatches(whenVal, tripleVal) {
308
+ if (whenVal === undefined || whenVal === '*')
309
+ return true;
310
+ if (tripleVal === null)
311
+ return false;
312
+ return whenVal.includes(tripleVal);
313
+ }
314
+ /** Does a skill's `when` triple match a concrete project triple? */
315
+ function matchesTriple(when, triple) {
316
+ return (componentMatches(when.work_type, triple.work_type) &&
317
+ componentMatches(when.workflow, triple.workflow) &&
318
+ componentMatches(when.step, triple.step));
319
+ }
320
+ function tripleLabel(triple) {
321
+ return `(${triple.work_type ?? '—'}, ${triple.workflow}, ${triple.step})`;
322
+ }
323
+ /**
324
+ * Read a `WORKFLOW.md` and expand what it loads into the flat, deduplicated list of
325
+ * **leaf** skills (step-owners + helpers) the project effectively runs, together with
326
+ * the values that reach each leaf. One call is the whole load step: parse, expand,
327
+ * resolve — hand the result straight to `validateLoad`.
328
+ *
329
+ * A **bundle** — a skill whose markdown body carries its own `## Loaded skills`
330
+ * section, so its contract carries `loads` — is recursed into, in its declared
331
+ * member order; a step-owner or helper is emitted as itself. Leaves are
332
+ * deduplicated **by name, first occurrence wins**, so the result is deterministic
333
+ * and idempotent — listing the same bundle twice, or a bundle plus one of its
334
+ * members as an explicit bullet, yields each leaf exactly once in first-seen order.
335
+ *
336
+ * **Value precedence**, narrowest wins: the project's own inline override in
337
+ * `WORKFLOW.md` beats a bundle's inline value for that member, which beats the
338
+ * skill's declared `default` (resolved later, in `validateLoad`). Between nested
339
+ * bundles the outermost wins, matching first-occurrence dedup.
340
+ *
341
+ * A root or member name absent from `byName`, or a cycle in the bundle graph,
342
+ * raises a typed `StepContractError`. Bundles may nest (cycle-safe); gaia ships one
343
+ * flat level. This never dispatches or routes.
344
+ */
345
+ export function expandLoad(workflowMd, byName) {
346
+ const { names: rootNames, values: workflowValues } = parseLoadedSection(workflowMd);
347
+ const skills = [];
348
+ const emitted = new Set();
349
+ const values = {};
350
+ /** Record values for `name` without overwriting a narrower source already set. */
351
+ const contribute = (name, from) => {
352
+ values[name] ??= {};
353
+ const target = values[name];
354
+ for (const [k, v] of Object.entries(from)) {
355
+ if (!(k in target))
356
+ target[k] = v;
357
+ }
358
+ };
359
+ const walk = (name, stack) => {
360
+ const skill = byName.get(name);
361
+ if (skill === undefined) {
362
+ const via = stack.length > 0
363
+ ? `bundle \`${stack[stack.length - 1]}\` loads unknown skill \`${name}\``
364
+ : `loaded skill \`${name}\` not found`;
365
+ throw new StepContractError(`${via}.`);
366
+ }
367
+ if (skill.loads !== undefined) {
368
+ if (stack.includes(name)) {
369
+ throw new StepContractError(`cyclic skill bundle: ${[...stack, name].join(' → ')}.`);
370
+ }
371
+ if (Object.keys(workflowValues[name] ?? {}).length > 0) {
372
+ throw new StepContractError(`bundle \`${name}\` takes no inline values of its own — write the value under the member skill's own bullet.`);
373
+ }
374
+ // The bundle's own inline values reach its members, but never beat an
375
+ // override the project wrote in WORKFLOW.md (contributed first, below).
376
+ for (const [member, memberValues] of Object.entries(skill.values ?? {})) {
377
+ contribute(member, memberValues);
378
+ }
379
+ for (const member of skill.loads)
380
+ walk(member, [...stack, name]);
381
+ return;
382
+ }
383
+ if (emitted.has(name))
384
+ return;
385
+ emitted.add(name);
386
+ skills.push(skill);
387
+ };
388
+ // WORKFLOW.md's own overrides are contributed first, so they win every tie.
389
+ for (const [name, own] of Object.entries(workflowValues))
390
+ contribute(name, own);
391
+ for (const name of rootNames)
392
+ walk(name, []);
393
+ return { skills, values };
394
+ }
395
+ /**
396
+ * Validate a loaded set of skills against the project's triple set and per-skill values.
397
+ *
398
+ * For every `(work_type, workflow, step)` the project can produce, **exactly one**
399
+ * loaded skill's `when` must match — a triple with no match (gap) or two matches
400
+ * (collision) is a configuration error. Every `input` a matched skill declares must
401
+ * resolve to a value: the `values` map (as resolved by `expandLoad`) or the input's
402
+ * own declared `default`.
403
+ *
404
+ * Reports the first defect as a typed `StepContractError`. Never dispatches or routes.
405
+ */
406
+ export function validateLoad(skills, values, projectTriples) {
407
+ const owners = skills.filter((s) => s.when !== undefined);
408
+ for (const triple of projectTriples) {
409
+ const matched = owners.filter((s) => matchesTriple(s.when, triple));
410
+ if (matched.length === 0) {
411
+ throw new StepContractError(`no skill matches triple ${tripleLabel(triple)} — coverage gap.`);
412
+ }
413
+ if (matched.length > 1) {
414
+ throw new StepContractError(`${matched.length} skills match triple ${tripleLabel(triple)} — collision: ${matched
415
+ .map((s) => s.name)
416
+ .join(', ')}.`);
417
+ }
418
+ const owner = matched[0];
419
+ if (owner.inputs.length === 0)
420
+ continue;
421
+ const overrides = values[owner.name];
422
+ for (const input of owner.inputs) {
423
+ const effective = overrides?.[input.key] ?? input.default;
424
+ if (effective === undefined) {
425
+ const desc = input.description ? ` (${input.description})` : '';
426
+ throw new StepContractError(`skill \`${owner.name}\` input \`${input.key}\`${desc} has no value: WORKFLOW.md sets no override and the skill declares no default (needed for triple ${tripleLabel(triple)}).`);
427
+ }
428
+ }
429
+ }
430
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gaia-ai/core",
3
- "version": "0.5.4",
3
+ "version": "0.6.0",
4
4
  "description": "GAIA global contract: plugin API, built-in remotes/workspaces/auth, shared primitives.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -10,10 +10,13 @@
10
10
  "exports": {
11
11
  ".": "./dist/src/index.js",
12
12
  "./plugins": "./dist/src/plugins-index.js",
13
+ "./builtins": "./dist/src/plugins/builtins-preset.js",
14
+ "./conductor-registry": "./dist/src/conductor-registry/index.js",
13
15
  "./package.json": "./package.json"
14
16
  },
15
17
  "files": [
16
18
  "dist/src",
19
+ "gaia.config.js",
17
20
  "README.md",
18
21
  "LICENSE"
19
22
  ],
@@ -23,11 +26,14 @@
23
26
  "repository": {
24
27
  "type": "git",
25
28
  "url": "git+https://git.key-tec.de/keytec/gaia.git",
26
- "directory": "conductor/packages/core"
29
+ "directory": "conductor/core"
27
30
  },
28
31
  "dependencies": {
29
- "dropsh": "^0.5.7",
32
+ "@dropsh/plugin-oauth2": "^0.5.7",
33
+ "commander": "^12.1.0",
34
+ "dropsh": "^0.5.8",
30
35
  "pino": "^9.6.0",
31
- "pino-pretty": "^13.0.0"
36
+ "pino-pretty": "^13.0.0",
37
+ "yaml": "^2.7.0"
32
38
  }
33
39
  }