cognitive-modules-cli 2.2.5 → 2.2.8

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 (42) hide show
  1. package/CHANGELOG.md +7 -1
  2. package/README.md +25 -3
  3. package/dist/audit.d.ts +13 -0
  4. package/dist/audit.js +25 -0
  5. package/dist/cli.js +188 -3
  6. package/dist/commands/add.js +232 -7
  7. package/dist/commands/compose.d.ts +2 -0
  8. package/dist/commands/compose.js +60 -1
  9. package/dist/commands/core.d.ts +31 -0
  10. package/dist/commands/core.js +338 -0
  11. package/dist/commands/index.d.ts +1 -0
  12. package/dist/commands/index.js +1 -0
  13. package/dist/commands/pipe.js +45 -2
  14. package/dist/commands/run.d.ts +1 -0
  15. package/dist/commands/run.js +136 -31
  16. package/dist/commands/search.js +13 -3
  17. package/dist/commands/update.js +4 -1
  18. package/dist/errors/index.d.ts +7 -0
  19. package/dist/errors/index.js +48 -40
  20. package/dist/modules/composition.d.ts +15 -2
  21. package/dist/modules/composition.js +16 -6
  22. package/dist/modules/loader.d.ts +10 -0
  23. package/dist/modules/loader.js +168 -0
  24. package/dist/modules/runner.d.ts +10 -6
  25. package/dist/modules/runner.js +130 -16
  26. package/dist/profile.d.ts +8 -0
  27. package/dist/profile.js +59 -0
  28. package/dist/provenance.d.ts +50 -0
  29. package/dist/provenance.js +137 -0
  30. package/dist/registry/assets.d.ts +48 -0
  31. package/dist/registry/assets.js +723 -0
  32. package/dist/registry/client.d.ts +20 -5
  33. package/dist/registry/client.js +87 -30
  34. package/dist/registry/tar.d.ts +8 -0
  35. package/dist/registry/tar.js +353 -0
  36. package/dist/server/http.js +167 -42
  37. package/dist/server/index.d.ts +2 -0
  38. package/dist/server/index.js +1 -0
  39. package/dist/server/sse.d.ts +13 -0
  40. package/dist/server/sse.js +22 -0
  41. package/dist/types.d.ts +31 -0
  42. package/package.json +1 -1
@@ -112,6 +112,13 @@ export interface ErrorEnvelopeOptions {
112
112
  */
113
113
  export declare function makeErrorEnvelope(options: ErrorEnvelopeOptions): CognitiveErrorEnvelope;
114
114
  export declare function attachContext<T extends object>(envelope: T, context?: EnvelopeContext): T & EnvelopeContext;
115
+ /**
116
+ * Map a CEP error code to an HTTP status code.
117
+ *
118
+ * This is used to keep HTTP behavior consistent with the error model while
119
+ * allowing callers to attach context without rebuilding envelopes.
120
+ */
121
+ export declare function httpStatusForErrorCode(code: string): number;
115
122
  /**
116
123
  * Create error envelope for HTTP API responses.
117
124
  *
@@ -191,54 +191,47 @@ export function attachContext(envelope, context) {
191
191
  };
192
192
  }
193
193
  /**
194
- * Create error envelope for HTTP API responses.
194
+ * Map a CEP error code to an HTTP status code.
195
195
  *
196
- * @returns Tuple of [statusCode, body]
196
+ * This is used to keep HTTP behavior consistent with the error model while
197
+ * allowing callers to attach context without rebuilding envelopes.
197
198
  */
198
- export function makeHttpError(options) {
199
- const envelope = attachContext(makeErrorEnvelope(options), options);
200
- const code = normalizeErrorCode(options.code);
201
- // Determine HTTP status code
202
- let statusCode;
203
- const category = code.charAt(1);
199
+ export function httpStatusForErrorCode(code) {
200
+ const normalized = normalizeErrorCode(code);
201
+ const category = normalized.charAt(1);
204
202
  switch (category) {
205
203
  case '1': {
206
204
  // Input errors -> Bad Request (with specific overrides)
207
- if (code === ErrorCodes.INPUT_TOO_LARGE) {
208
- statusCode = 413; // Payload Too Large
209
- }
210
- else {
211
- statusCode = 400;
212
- }
213
- break;
205
+ if (normalized === ErrorCodes.INPUT_TOO_LARGE)
206
+ return 413;
207
+ return 400;
214
208
  }
215
- case '2':
216
- statusCode = 422;
217
- break; // Processing errors -> Unprocessable Entity
218
- case '3':
219
- statusCode = 500;
220
- break; // Output errors -> Internal Server Error
209
+ case '2': return 422; // Processing errors -> Unprocessable Entity
210
+ case '3': return 500; // Output errors -> Internal Server Error
221
211
  case '4': {
222
- // Runtime errors - map to appropriate HTTP status
223
- if (code === ErrorCodes.MODULE_NOT_FOUND ||
224
- code === ErrorCodes.ENDPOINT_NOT_FOUND ||
225
- code === ErrorCodes.RESOURCE_NOT_FOUND) {
226
- statusCode = 404; // Not Found
227
- }
228
- else if (code === ErrorCodes.PERMISSION_DENIED) {
229
- statusCode = 403; // Forbidden
230
- }
231
- else if (code === ErrorCodes.RATE_LIMITED) {
232
- statusCode = 429; // Too Many Requests
212
+ // Runtime errors -> map to appropriate HTTP status
213
+ if (normalized === ErrorCodes.MODULE_NOT_FOUND ||
214
+ normalized === ErrorCodes.ENDPOINT_NOT_FOUND ||
215
+ normalized === ErrorCodes.RESOURCE_NOT_FOUND) {
216
+ return 404;
233
217
  }
234
- else {
235
- statusCode = 500; // Internal Server Error
236
- }
237
- break;
218
+ if (normalized === ErrorCodes.PERMISSION_DENIED)
219
+ return 403;
220
+ if (normalized === ErrorCodes.RATE_LIMITED)
221
+ return 429;
222
+ return 500;
238
223
  }
239
- default: statusCode = 500;
224
+ default: return 500;
240
225
  }
241
- // Add HTTP-specific fields
226
+ }
227
+ /**
228
+ * Create error envelope for HTTP API responses.
229
+ *
230
+ * @returns Tuple of [statusCode, body]
231
+ */
232
+ export function makeHttpError(options) {
233
+ const envelope = attachContext(makeErrorEnvelope(options), options);
234
+ const statusCode = httpStatusForErrorCode(String(options.code));
242
235
  return [statusCode, envelope];
243
236
  }
244
237
  /**
@@ -316,16 +309,31 @@ export function shouldRetry(envelope) {
316
309
  * Use this for consistent success responses across HTTP and MCP layers.
317
310
  */
318
311
  export function makeSuccessEnvelope(options) {
312
+ const explain = (options.explain || 'Operation completed successfully').slice(0, 280);
313
+ // Envelope schema requires data to be an object with at least `rationale`.
314
+ // For non-module operations (list/info), we still emit a conforming envelope by
315
+ // injecting a minimal rationale if missing, or wrapping non-objects.
316
+ const normalizedData = (() => {
317
+ const d = options.data;
318
+ const isPlainObject = typeof d === 'object' && d !== null && !Array.isArray(d);
319
+ if (!isPlainObject) {
320
+ return { result: d, rationale: explain };
321
+ }
322
+ const obj = d;
323
+ if (typeof obj.rationale === 'string')
324
+ return options.data;
325
+ return { ...obj, rationale: explain };
326
+ })();
319
327
  return {
320
328
  ok: true,
321
329
  version: options.version || '2.2',
322
330
  meta: {
323
331
  confidence: options.confidence ?? 1.0,
324
332
  risk: options.risk ?? 'none',
325
- explain: (options.explain || 'Operation completed successfully').slice(0, 280),
333
+ explain,
326
334
  trace_id: options.trace_id,
327
335
  },
328
- data: options.data,
336
+ data: normalizedData,
329
337
  };
330
338
  }
331
339
  /**
@@ -12,7 +12,7 @@
12
12
  * - Timeout handling
13
13
  * - Circular dependency detection
14
14
  */
15
- import type { CognitiveModule, ModuleResult, ModuleInput, Provider } from '../types.js';
15
+ import type { CognitiveModule, ModuleResult, ModuleInput, Provider, ExecutionPolicy } from '../types.js';
16
16
  /** Composition pattern types */
17
17
  export type CompositionPattern = 'sequential' | 'parallel' | 'conditional' | 'iterative';
18
18
  /** Aggregation strategy for combining multiple outputs */
@@ -185,7 +185,16 @@ export declare class CompositionOrchestrator {
185
185
  private provider;
186
186
  private cwd;
187
187
  private searchPaths;
188
- constructor(provider: Provider, cwd?: string);
188
+ private validateInput;
189
+ private validateOutput;
190
+ private enableRepair;
191
+ private policy?;
192
+ constructor(provider: Provider, cwd?: string, enforcement?: {
193
+ validateInput?: boolean;
194
+ validateOutput?: boolean;
195
+ enableRepair?: boolean;
196
+ policy?: ExecutionPolicy;
197
+ });
189
198
  /**
190
199
  * Execute a composed module workflow
191
200
  */
@@ -241,6 +250,10 @@ export declare function executeComposition(moduleName: string, input: ModuleInpu
241
250
  cwd?: string;
242
251
  maxDepth?: number;
243
252
  timeoutMs?: number;
253
+ validateInput?: boolean;
254
+ validateOutput?: boolean;
255
+ enableRepair?: boolean;
256
+ policy?: ExecutionPolicy;
244
257
  }): Promise<CompositionResult>;
245
258
  /**
246
259
  * Validate composition configuration
@@ -577,10 +577,18 @@ export class CompositionOrchestrator {
577
577
  provider;
578
578
  cwd;
579
579
  searchPaths;
580
- constructor(provider, cwd = process.cwd()) {
580
+ validateInput;
581
+ validateOutput;
582
+ enableRepair;
583
+ policy;
584
+ constructor(provider, cwd = process.cwd(), enforcement = {}) {
581
585
  this.provider = provider;
582
586
  this.cwd = cwd;
583
587
  this.searchPaths = getDefaultSearchPaths(cwd);
588
+ this.validateInput = enforcement.validateInput ?? true;
589
+ this.validateOutput = enforcement.validateOutput ?? true;
590
+ this.enableRepair = enforcement.enableRepair ?? true;
591
+ this.policy = enforcement.policy;
584
592
  }
585
593
  /**
586
594
  * Execute a composed module workflow
@@ -1160,9 +1168,11 @@ export class CompositionOrchestrator {
1160
1168
  try {
1161
1169
  const result = await runModule(module, this.provider, {
1162
1170
  input,
1163
- validateInput: true,
1164
- validateOutput: true,
1165
- useV22: true
1171
+ validateInput: this.validateInput,
1172
+ validateOutput: this.validateOutput,
1173
+ useV22: true,
1174
+ enableRepair: this.enableRepair,
1175
+ policy: this.policy,
1166
1176
  });
1167
1177
  const endTime = Date.now();
1168
1178
  trace.push({
@@ -1278,8 +1288,8 @@ export class CompositionOrchestrator {
1278
1288
  * Execute a composed module workflow
1279
1289
  */
1280
1290
  export async function executeComposition(moduleName, input, provider, options = {}) {
1281
- const { cwd = process.cwd(), ...execOptions } = options;
1282
- const orchestrator = new CompositionOrchestrator(provider, cwd);
1291
+ const { cwd = process.cwd(), validateInput, validateOutput, enableRepair, policy, ...execOptions } = options;
1292
+ const orchestrator = new CompositionOrchestrator(provider, cwd, { validateInput, validateOutput, enableRepair, policy });
1283
1293
  return orchestrator.execute(moduleName, input, execOptions);
1284
1294
  }
1285
1295
  /**
@@ -7,6 +7,16 @@ import type { CognitiveModule, ModuleTier, SchemaStrictness } from '../types.js'
7
7
  * Load a Cognitive Module (auto-detects format)
8
8
  */
9
9
  export declare function loadModule(modulePath: string): Promise<CognitiveModule>;
10
+ /**
11
+ * Load a single-file module (Markdown) with optional YAML frontmatter.
12
+ *
13
+ * This enables the "5-minute" path: one file runs, and the runtime generates a loose schema/envelope.
14
+ *
15
+ * File format:
16
+ * - Optional YAML frontmatter between --- ... --- (same as v1 MODULE.md)
17
+ * - Body is treated as prompt
18
+ */
19
+ export declare function loadSingleFileModule(filePath: string): Promise<CognitiveModule>;
10
20
  export declare function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null>;
11
21
  export declare function listModules(searchPaths: string[]): Promise<CognitiveModule[]>;
12
22
  export declare function getDefaultSearchPaths(cwd: string): string[];
@@ -7,6 +7,7 @@ import * as path from 'node:path';
7
7
  import { homedir } from 'node:os';
8
8
  import yaml from 'js-yaml';
9
9
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
10
+ const SAFE_MODULE_NAME_REGEX = /^[a-z0-9][a-z0-9._-]*$/i;
10
11
  /**
11
12
  * Detect module format version
12
13
  */
@@ -333,6 +334,173 @@ export async function loadModule(modulePath) {
333
334
  return loadModuleV0(modulePath);
334
335
  }
335
336
  }
337
+ // =============================================================================
338
+ // Single-File Modules (Ad-hoc)
339
+ // =============================================================================
340
+ function coerceModuleName(name, fallback) {
341
+ const raw = (typeof name === 'string' && name.trim().length > 0) ? name.trim() : fallback;
342
+ const normalized = raw
343
+ .replace(/\s+/g, '-')
344
+ .replace(/[^a-zA-Z0-9._-]/g, '-')
345
+ .replace(/-+/g, '-')
346
+ .replace(/^-|-$/g, '');
347
+ const lower = normalized.toLowerCase();
348
+ if (SAFE_MODULE_NAME_REGEX.test(lower))
349
+ return lower;
350
+ return fallback;
351
+ }
352
+ function defaultMetaSchema() {
353
+ return {
354
+ type: 'object',
355
+ additionalProperties: true,
356
+ required: ['confidence', 'risk', 'explain'],
357
+ properties: {
358
+ confidence: { type: 'number', minimum: 0, maximum: 1 },
359
+ risk: { type: 'string', enum: ['none', 'low', 'medium', 'high'] },
360
+ explain: { type: 'string', maxLength: 280 },
361
+ },
362
+ };
363
+ }
364
+ function defaultDataSchema() {
365
+ // Intentionally loose: the "5-minute" path should not fail on business fields.
366
+ return {
367
+ type: 'object',
368
+ additionalProperties: true,
369
+ };
370
+ }
371
+ function defaultInputSchema() {
372
+ // Matches runtime behavior: args are mapped to either query or code.
373
+ return {
374
+ type: 'object',
375
+ additionalProperties: true,
376
+ properties: {
377
+ query: { type: 'string' },
378
+ code: { type: 'string' },
379
+ args: { type: 'string' },
380
+ },
381
+ };
382
+ }
383
+ function defaultEnvelopeSchema(metaSchema) {
384
+ // Used as a hint for structured output (provider jsonSchema). Validation uses metaSchema/dataSchema separately.
385
+ return {
386
+ type: 'object',
387
+ additionalProperties: true,
388
+ oneOf: [
389
+ {
390
+ type: 'object',
391
+ required: ['ok', 'meta', 'data'],
392
+ properties: {
393
+ ok: { const: true },
394
+ version: { type: 'string' },
395
+ meta: metaSchema,
396
+ data: { type: 'object', additionalProperties: true },
397
+ },
398
+ },
399
+ {
400
+ type: 'object',
401
+ required: ['ok', 'meta', 'error'],
402
+ properties: {
403
+ ok: { const: false },
404
+ version: { type: 'string' },
405
+ meta: metaSchema,
406
+ error: {
407
+ type: 'object',
408
+ additionalProperties: true,
409
+ required: ['code', 'message'],
410
+ properties: {
411
+ code: { type: 'string' },
412
+ message: { type: 'string' },
413
+ recoverable: { type: 'boolean' },
414
+ },
415
+ },
416
+ partial_data: { type: 'object', additionalProperties: true },
417
+ },
418
+ },
419
+ ],
420
+ };
421
+ }
422
+ function looksLikeMarkdownModule(filePath) {
423
+ const ext = path.extname(filePath).toLowerCase();
424
+ return ext === '.md' || ext === '.markdown';
425
+ }
426
+ /**
427
+ * Load a single-file module (Markdown) with optional YAML frontmatter.
428
+ *
429
+ * This enables the "5-minute" path: one file runs, and the runtime generates a loose schema/envelope.
430
+ *
431
+ * File format:
432
+ * - Optional YAML frontmatter between --- ... --- (same as v1 MODULE.md)
433
+ * - Body is treated as prompt
434
+ */
435
+ export async function loadSingleFileModule(filePath) {
436
+ const absPath = path.resolve(filePath);
437
+ if (!looksLikeMarkdownModule(absPath)) {
438
+ throw new Error(`Single-file modules currently support Markdown (.md) only: ${absPath}`);
439
+ }
440
+ const content = await fs.readFile(absPath, 'utf-8');
441
+ let manifest = {};
442
+ let prompt = content;
443
+ const match = content.match(FRONTMATTER_REGEX);
444
+ if (match) {
445
+ try {
446
+ const loaded = yaml.load(match[1]);
447
+ manifest = (loaded && typeof loaded === 'object') ? loaded : {};
448
+ }
449
+ catch {
450
+ manifest = {};
451
+ }
452
+ prompt = (match[2] ?? '').trim();
453
+ }
454
+ const base = path.basename(absPath, path.extname(absPath));
455
+ const name = coerceModuleName(manifest.name, coerceModuleName(base, 'single-file-module'));
456
+ const tier = manifest.tier ?? 'decision';
457
+ const responsibility = manifest.responsibility
458
+ ?? `Ad-hoc single-file module loaded from ${path.basename(absPath)}`;
459
+ const excludes = Array.isArray(manifest.excludes) ? manifest.excludes : [];
460
+ const metaSchema = defaultMetaSchema();
461
+ const dataSchema = defaultDataSchema();
462
+ const inputSchema = defaultInputSchema();
463
+ const envelopeSchema = defaultEnvelopeSchema(metaSchema);
464
+ // Keep validation permissive by default, but still enforce envelope meta shape.
465
+ const schemaStrictness = manifest.schema_strictness ?? 'low';
466
+ return {
467
+ name,
468
+ version: manifest.version ?? '0.1.0',
469
+ responsibility,
470
+ excludes,
471
+ constraints: manifest.constraints,
472
+ policies: manifest.policies,
473
+ tools: manifest.tools,
474
+ output: manifest.output ?? { envelope: true, format: 'json_lenient' },
475
+ failure: manifest.failure,
476
+ runtimeRequirements: manifest.runtime_requirements,
477
+ tier,
478
+ schemaStrictness,
479
+ overflow: {
480
+ enabled: true,
481
+ recoverable: true,
482
+ max_items: 20,
483
+ require_suggested_mapping: false,
484
+ },
485
+ enums: { strategy: 'extensible', unknown_tag: 'custom' },
486
+ compat: { accepts_v21_payload: true, runtime_auto_wrap: true, schema_output_alias: 'data' },
487
+ metaConfig: { risk_rule: 'explicit' },
488
+ composition: undefined,
489
+ context: manifest.context,
490
+ prompt: prompt.length > 0 ? prompt : content.trim(),
491
+ // Schemas:
492
+ // - outputSchema is used as provider jsonSchema hint (we want the full envelope)
493
+ // - metaSchema/dataSchema are used for runtime validation after wrapping/repair
494
+ inputSchema,
495
+ outputSchema: envelopeSchema,
496
+ dataSchema,
497
+ metaSchema,
498
+ errorSchema: undefined,
499
+ location: absPath,
500
+ format: 'v2',
501
+ formatVersion: 'v2.2',
502
+ };
503
+ }
336
504
  /**
337
505
  * Check if a directory contains a valid module
338
506
  */
@@ -3,7 +3,7 @@
3
3
  * v2.2: Envelope format with meta/data separation, risk_rule, repair pass
4
4
  * v2.2.1: Version field, enhanced error taxonomy, observability hooks, streaming
5
5
  */
6
- import type { Provider, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel } from '../types.js';
6
+ import type { Provider, CognitiveModule, ModuleResult, ModuleInput, EnvelopeResponseV22, EnvelopeMeta, RiskLevel, ExecutionPolicy } from '../types.js';
7
7
  /**
8
8
  * Validate data against JSON schema. Returns list of errors.
9
9
  */
@@ -366,16 +366,19 @@ export interface RunOptions {
366
366
  enableRepair?: boolean;
367
367
  traceId?: string;
368
368
  model?: string;
369
+ policy?: ExecutionPolicy;
369
370
  }
370
371
  export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
371
372
  /** Event types emitted during streaming execution */
372
- export type StreamEventType = 'start' | 'chunk' | 'meta' | 'complete' | 'error';
373
+ export type StreamEventType = 'start' | 'delta' | 'meta' | 'end' | 'error';
373
374
  /** Event emitted during streaming execution */
374
375
  export interface StreamEvent {
375
376
  type: StreamEventType;
377
+ version: string;
376
378
  timestamp_ms: number;
377
- module_name: string;
378
- chunk?: string;
379
+ module: string;
380
+ provider?: string;
381
+ delta?: string;
379
382
  meta?: EnvelopeMeta;
380
383
  result?: EnvelopeResponseV22<unknown>;
381
384
  error?: {
@@ -392,15 +395,16 @@ export interface StreamOptions {
392
395
  enableRepair?: boolean;
393
396
  traceId?: string;
394
397
  model?: string;
398
+ policy?: ExecutionPolicy;
395
399
  }
396
400
  /**
397
401
  * Run a cognitive module with streaming output.
398
402
  *
399
403
  * Yields StreamEvent objects as the module executes:
400
404
  * - type="start": Module execution started
401
- * - type="chunk": Incremental data chunk (if LLM supports streaming)
405
+ * - type="delta": Incremental output delta (provider streaming chunk)
402
406
  * - type="meta": Meta information available early
403
- * - type="complete": Final complete result
407
+ * - type="end": Final result envelope (always emitted)
404
408
  * - type="error": Error occurred
405
409
  *
406
410
  * @example