cognitive-modules-cli 2.2.0 → 2.2.1

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.
@@ -5,3 +5,5 @@
5
5
  export * from './loader.js';
6
6
  export * from './runner.js';
7
7
  export * from './subagent.js';
8
+ export * from './validator.js';
9
+ export * from './composition.js';
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Module Loader - Load and parse Cognitive Modules
3
- * Supports both v1 (MODULE.md) and v2 (module.yaml + prompt.md) formats
3
+ * Supports v0 (6-file), v1 (MODULE.md) and v2 (module.yaml + prompt.md) formats
4
4
  */
5
5
 
6
6
  import * as fs from 'node:fs/promises';
@@ -19,7 +19,14 @@ import type {
19
19
  CompatConfig,
20
20
  MetaConfig,
21
21
  ModuleTier,
22
- SchemaStrictness
22
+ SchemaStrictness,
23
+ CompositionConfig,
24
+ CompositionPattern,
25
+ DependencyDeclaration,
26
+ DataflowStep,
27
+ RoutingRule,
28
+ AggregationStrategy,
29
+ IterationConfig
23
30
  } from '../types.js';
24
31
 
25
32
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
@@ -27,13 +34,26 @@ const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
27
34
  /**
28
35
  * Detect module format version
29
36
  */
30
- async function detectFormat(modulePath: string): Promise<'v1' | 'v2'> {
37
+ async function detectFormat(modulePath: string): Promise<'v0' | 'v1' | 'v2'> {
31
38
  const v2Manifest = path.join(modulePath, 'module.yaml');
39
+ const v1Module = path.join(modulePath, 'MODULE.md');
40
+ const v0Module = path.join(modulePath, 'module.md');
41
+
32
42
  try {
33
43
  await fs.access(v2Manifest);
34
44
  return 'v2';
35
45
  } catch {
36
- return 'v1';
46
+ try {
47
+ await fs.access(v1Module);
48
+ return 'v1';
49
+ } catch {
50
+ try {
51
+ await fs.access(v0Module);
52
+ return 'v0';
53
+ } catch {
54
+ throw new Error(`No module.yaml, MODULE.md, or module.md found in ${modulePath}`);
55
+ }
56
+ }
37
57
  }
38
58
  }
39
59
 
@@ -144,6 +164,61 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
144
164
  risk_rule: validatedRiskRule,
145
165
  };
146
166
 
167
+ // Parse composition config (v2.2)
168
+ const compositionRaw = manifest.composition as Record<string, unknown> | undefined;
169
+ let composition: CompositionConfig | undefined;
170
+
171
+ if (compositionRaw) {
172
+ // Parse pattern
173
+ const pattern = compositionRaw.pattern as CompositionPattern ?? 'sequential';
174
+
175
+ // Parse requires (dependencies)
176
+ const requiresRaw = compositionRaw.requires as Array<Record<string, unknown>> | undefined;
177
+ const requires: DependencyDeclaration[] | undefined = requiresRaw?.map(dep => ({
178
+ name: dep.name as string,
179
+ version: dep.version as string | undefined,
180
+ optional: dep.optional as boolean | undefined,
181
+ fallback: dep.fallback as string | null | undefined,
182
+ timeout_ms: dep.timeout_ms as number | undefined
183
+ }));
184
+
185
+ // Parse dataflow
186
+ const dataflowRaw = compositionRaw.dataflow as Array<Record<string, unknown>> | undefined;
187
+ const dataflow: DataflowStep[] | undefined = dataflowRaw?.map(step => ({
188
+ from: step.from as string | string[],
189
+ to: step.to as string | string[],
190
+ mapping: step.mapping as Record<string, string> | undefined,
191
+ condition: step.condition as string | undefined,
192
+ aggregate: step.aggregate as AggregationStrategy | undefined,
193
+ aggregator: step.aggregator as string | undefined
194
+ }));
195
+
196
+ // Parse routing rules
197
+ const routingRaw = compositionRaw.routing as Array<Record<string, unknown>> | undefined;
198
+ const routing: RoutingRule[] | undefined = routingRaw?.map(rule => ({
199
+ condition: rule.condition as string,
200
+ next: rule.next as string | null
201
+ }));
202
+
203
+ // Parse iteration config
204
+ const iterationRaw = compositionRaw.iteration as Record<string, unknown> | undefined;
205
+ const iteration: IterationConfig | undefined = iterationRaw ? {
206
+ max_iterations: iterationRaw.max_iterations as number | undefined,
207
+ continue_condition: iterationRaw.continue_condition as string | undefined,
208
+ stop_condition: iterationRaw.stop_condition as string | undefined
209
+ } : undefined;
210
+
211
+ composition = {
212
+ pattern,
213
+ requires,
214
+ dataflow,
215
+ routing,
216
+ max_depth: compositionRaw.max_depth as number | undefined,
217
+ timeout_ms: compositionRaw.timeout_ms as number | undefined,
218
+ iteration
219
+ };
220
+ }
221
+
147
222
  return {
148
223
  name: manifest.name as string || path.basename(modulePath),
149
224
  version: manifest.version as string || '1.0.0',
@@ -162,6 +237,7 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
162
237
  enums,
163
238
  compat,
164
239
  metaConfig,
240
+ composition,
165
241
  // Context and prompt
166
242
  context: manifest.context as 'fork' | 'main' | undefined,
167
243
  prompt,
@@ -234,6 +310,73 @@ async function loadModuleV1(modulePath: string): Promise<CognitiveModule> {
234
310
  };
235
311
  }
236
312
 
313
+ /**
314
+ * Load v0 format module (6-file format - deprecated)
315
+ */
316
+ async function loadModuleV0(modulePath: string): Promise<CognitiveModule> {
317
+ // Read module.md
318
+ const moduleFile = path.join(modulePath, 'module.md');
319
+ const moduleContent = await fs.readFile(moduleFile, 'utf-8');
320
+
321
+ // Parse frontmatter
322
+ const match = moduleContent.match(FRONTMATTER_REGEX);
323
+ if (!match) {
324
+ throw new Error(`Invalid module.md: missing YAML frontmatter in ${moduleFile}`);
325
+ }
326
+
327
+ const metadata = yaml.load(match[1]) as Record<string, unknown>;
328
+
329
+ // Read schemas
330
+ const inputSchemaFile = path.join(modulePath, 'input.schema.json');
331
+ const outputSchemaFile = path.join(modulePath, 'output.schema.json');
332
+ const constraintsFile = path.join(modulePath, 'constraints.yaml');
333
+ const promptFile = path.join(modulePath, 'prompt.txt');
334
+
335
+ const inputSchemaContent = await fs.readFile(inputSchemaFile, 'utf-8');
336
+ const inputSchema = JSON.parse(inputSchemaContent);
337
+
338
+ const outputSchemaContent = await fs.readFile(outputSchemaFile, 'utf-8');
339
+ const outputSchema = JSON.parse(outputSchemaContent);
340
+
341
+ // Load constraints
342
+ const constraintsContent = await fs.readFile(constraintsFile, 'utf-8');
343
+ const constraintsRaw = yaml.load(constraintsContent) as Record<string, unknown>;
344
+
345
+ // Load prompt
346
+ const prompt = await fs.readFile(promptFile, 'utf-8');
347
+
348
+ // Extract constraints
349
+ const constraints: ModuleConstraints = {};
350
+ if (constraintsRaw) {
351
+ const operational = (constraintsRaw.operational as Record<string, boolean>) ?? {};
352
+ constraints.no_network = operational.no_external_network;
353
+ constraints.no_side_effects = operational.no_side_effects;
354
+ constraints.no_file_write = operational.no_file_write;
355
+ constraints.no_inventing_data = operational.no_inventing_data;
356
+ }
357
+
358
+ return {
359
+ name: metadata.name as string || path.basename(modulePath),
360
+ version: metadata.version as string || '1.0.0',
361
+ responsibility: metadata.responsibility as string || '',
362
+ excludes: (metadata.excludes as string[]) || [],
363
+ constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
364
+ prompt,
365
+ inputSchema,
366
+ outputSchema,
367
+ dataSchema: outputSchema, // Alias for v2.2 compat
368
+ // v2.2 defaults for v0 modules
369
+ schemaStrictness: 'medium',
370
+ overflow: { enabled: false },
371
+ enums: { strategy: 'strict' },
372
+ compat: { accepts_v21_payload: true, runtime_auto_wrap: true },
373
+ // Metadata
374
+ location: modulePath,
375
+ format: 'v0',
376
+ formatVersion: 'v0.0',
377
+ };
378
+ }
379
+
237
380
  /**
238
381
  * Load a Cognitive Module (auto-detects format)
239
382
  */
@@ -242,8 +385,10 @@ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
242
385
 
243
386
  if (format === 'v2') {
244
387
  return loadModuleV2(modulePath);
245
- } else {
388
+ } else if (format === 'v1') {
246
389
  return loadModuleV1(modulePath);
390
+ } else {
391
+ return loadModuleV0(modulePath);
247
392
  }
248
393
  }
249
394
 
@@ -253,6 +398,7 @@ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
253
398
  async function isValidModule(modulePath: string): Promise<boolean> {
254
399
  const v2Manifest = path.join(modulePath, 'module.yaml');
255
400
  const v1Module = path.join(modulePath, 'MODULE.md');
401
+ const v0Module = path.join(modulePath, 'module.md');
256
402
 
257
403
  try {
258
404
  await fs.access(v2Manifest);
@@ -262,7 +408,12 @@ async function isValidModule(modulePath: string): Promise<boolean> {
262
408
  await fs.access(v1Module);
263
409
  return true;
264
410
  } catch {
265
- return false;
411
+ try {
412
+ await fs.access(v0Module);
413
+ return true;
414
+ } catch {
415
+ return false;
416
+ }
266
417
  }
267
418
  }
268
419
  }
@@ -316,3 +467,42 @@ export function getDefaultSearchPaths(cwd: string): string[] {
316
467
  path.join(home, '.cognitive', 'modules'),
317
468
  ];
318
469
  }
470
+
471
+ // =============================================================================
472
+ // Utility Functions
473
+ // =============================================================================
474
+
475
+ /**
476
+ * Get module tier (exec, decision, exploration).
477
+ */
478
+ export function getModuleTier(module: CognitiveModule): ModuleTier | undefined {
479
+ return module.tier;
480
+ }
481
+
482
+ /**
483
+ * Get schema strictness level.
484
+ */
485
+ export function getSchemaStrictness(module: CognitiveModule): SchemaStrictness {
486
+ return module.schemaStrictness ?? 'medium';
487
+ }
488
+
489
+ /**
490
+ * Check if overflow (extensions.insights) is enabled.
491
+ */
492
+ export function isOverflowEnabled(module: CognitiveModule): boolean {
493
+ return module.overflow?.enabled ?? false;
494
+ }
495
+
496
+ /**
497
+ * Get enum extension strategy.
498
+ */
499
+ export function getEnumStrategy(module: CognitiveModule): 'strict' | 'extensible' {
500
+ return module.enums?.strategy ?? 'strict';
501
+ }
502
+
503
+ /**
504
+ * Check if runtime should auto-wrap v2.1 to v2.2.
505
+ */
506
+ export function shouldAutoWrap(module: CognitiveModule): boolean {
507
+ return module.compat?.runtime_auto_wrap ?? true;
508
+ }