cognitive-runtime 0.3.0 → 0.4.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/dist/cli.js CHANGED
@@ -10,7 +10,7 @@
10
10
  import { parseArgs } from 'node:util';
11
11
  import { getProvider, listProviders } from './providers/index.js';
12
12
  import { run, list, pipe, init } from './commands/index.js';
13
- const VERSION = '0.3.0';
13
+ const VERSION = '0.4.0';
14
14
  async function main() {
15
15
  const args = process.argv.slice(2);
16
16
  const command = args[0];
@@ -36,8 +36,6 @@ export async function pipe(ctx, options) {
36
36
  const result = await runModule(module, ctx.provider, {
37
37
  args: inputData ? undefined : input,
38
38
  input: inputData,
39
- validateInput: !options.noValidate,
40
- validateOutput: !options.noValidate,
41
39
  });
42
40
  // Output JSON to stdout
43
41
  console.log(JSON.stringify(result.output));
@@ -30,8 +30,6 @@ export async function run(moduleName, ctx, options = {}) {
30
30
  const result = await runModule(module, ctx.provider, {
31
31
  args: options.args,
32
32
  input: inputData,
33
- validateInput: !options.noValidate,
34
- validateOutput: !options.noValidate,
35
33
  verbose: options.verbose || ctx.verbose,
36
34
  });
37
35
  return {
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Exports all public APIs for programmatic use.
5
5
  */
6
- export type { Provider, InvokeParams, InvokeResult, Message, CognitiveModule, ModuleResult, CommandContext, CommandResult, } from './types.js';
6
+ export type { Provider, InvokeParams, InvokeResult, Message, CognitiveModule, ModuleResult, ModuleInput, ModuleConstraints, ToolsPolicy, OutputContract, FailureContract, CommandContext, CommandResult, } from './types.js';
7
7
  export { getProvider, listProviders, GeminiProvider, OpenAIProvider, AnthropicProvider, BaseProvider, } from './providers/index.js';
8
8
  export { loadModule, findModule, listModules, getDefaultSearchPaths, runModule, } from './modules/index.js';
9
9
  export { run, list, pipe } from './commands/index.js';
@@ -1,7 +1,11 @@
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
4
  */
4
5
  import type { CognitiveModule } from '../types.js';
6
+ /**
7
+ * Load a Cognitive Module (auto-detects format)
8
+ */
5
9
  export declare function loadModule(modulePath: string): Promise<CognitiveModule>;
6
10
  export declare function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null>;
7
11
  export declare function listModules(searchPaths: string[]): Promise<CognitiveModule[]>;
@@ -1,11 +1,78 @@
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
4
  */
4
5
  import * as fs from 'node:fs/promises';
5
6
  import * as path from 'node:path';
6
7
  import yaml from 'js-yaml';
7
8
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
8
- export async function loadModule(modulePath) {
9
+ /**
10
+ * Detect module format version
11
+ */
12
+ async function detectFormat(modulePath) {
13
+ const v2Manifest = path.join(modulePath, 'module.yaml');
14
+ try {
15
+ await fs.access(v2Manifest);
16
+ return 'v2';
17
+ }
18
+ catch {
19
+ return 'v1';
20
+ }
21
+ }
22
+ /**
23
+ * Load v2 format module (module.yaml + prompt.md)
24
+ */
25
+ async function loadModuleV2(modulePath) {
26
+ const manifestFile = path.join(modulePath, 'module.yaml');
27
+ const promptFile = path.join(modulePath, 'prompt.md');
28
+ const schemaFile = path.join(modulePath, 'schema.json');
29
+ // Read module.yaml
30
+ const manifestContent = await fs.readFile(manifestFile, 'utf-8');
31
+ const manifest = yaml.load(manifestContent);
32
+ // Read prompt.md
33
+ let prompt = '';
34
+ try {
35
+ prompt = await fs.readFile(promptFile, 'utf-8');
36
+ }
37
+ catch {
38
+ // prompt.md is optional, manifest may include inline prompt
39
+ }
40
+ // Read schema.json
41
+ let inputSchema;
42
+ let outputSchema;
43
+ let errorSchema;
44
+ try {
45
+ const schemaContent = await fs.readFile(schemaFile, 'utf-8');
46
+ const schema = JSON.parse(schemaContent);
47
+ inputSchema = schema.input;
48
+ outputSchema = schema.output;
49
+ errorSchema = schema.error;
50
+ }
51
+ catch {
52
+ // Schema file is optional but recommended
53
+ }
54
+ return {
55
+ name: manifest.name || path.basename(modulePath),
56
+ version: manifest.version || '1.0.0',
57
+ responsibility: manifest.responsibility || '',
58
+ excludes: manifest.excludes || [],
59
+ constraints: manifest.constraints,
60
+ tools: manifest.tools,
61
+ output: manifest.output,
62
+ failure: manifest.failure,
63
+ context: manifest.context,
64
+ prompt,
65
+ inputSchema,
66
+ outputSchema,
67
+ errorSchema,
68
+ location: modulePath,
69
+ format: 'v2',
70
+ };
71
+ }
72
+ /**
73
+ * Load v1 format module (MODULE.md with frontmatter)
74
+ */
75
+ async function loadModuleV1(modulePath) {
9
76
  const moduleFile = path.join(modulePath, 'MODULE.md');
10
77
  const schemaFile = path.join(modulePath, 'schema.json');
11
78
  // Read MODULE.md
@@ -29,29 +96,66 @@ export async function loadModule(modulePath) {
29
96
  catch {
30
97
  // Schema file is optional
31
98
  }
99
+ // Extract constraints from v1 format
100
+ const constraints = {};
101
+ const v1Constraints = frontmatter.constraints;
102
+ if (v1Constraints) {
103
+ constraints.no_network = v1Constraints.no_network;
104
+ constraints.no_side_effects = v1Constraints.no_side_effects;
105
+ constraints.no_inventing_data = v1Constraints.no_inventing_data;
106
+ }
32
107
  return {
33
108
  name: frontmatter.name || path.basename(modulePath),
34
109
  version: frontmatter.version || '1.0.0',
35
110
  responsibility: frontmatter.responsibility || '',
36
111
  excludes: frontmatter.excludes || [],
112
+ constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
37
113
  context: frontmatter.context,
38
114
  prompt,
39
115
  inputSchema,
40
116
  outputSchema,
41
117
  location: modulePath,
118
+ format: 'v1',
42
119
  };
43
120
  }
121
+ /**
122
+ * Load a Cognitive Module (auto-detects format)
123
+ */
124
+ export async function loadModule(modulePath) {
125
+ const format = await detectFormat(modulePath);
126
+ if (format === 'v2') {
127
+ return loadModuleV2(modulePath);
128
+ }
129
+ else {
130
+ return loadModuleV1(modulePath);
131
+ }
132
+ }
133
+ /**
134
+ * Check if a directory contains a valid module
135
+ */
136
+ async function isValidModule(modulePath) {
137
+ const v2Manifest = path.join(modulePath, 'module.yaml');
138
+ const v1Module = path.join(modulePath, 'MODULE.md');
139
+ try {
140
+ await fs.access(v2Manifest);
141
+ return true;
142
+ }
143
+ catch {
144
+ try {
145
+ await fs.access(v1Module);
146
+ return true;
147
+ }
148
+ catch {
149
+ return false;
150
+ }
151
+ }
152
+ }
44
153
  export async function findModule(name, searchPaths) {
45
154
  for (const basePath of searchPaths) {
46
155
  const modulePath = path.join(basePath, name);
47
- const moduleFile = path.join(modulePath, 'MODULE.md');
48
- try {
49
- await fs.access(moduleFile);
156
+ if (await isValidModule(modulePath)) {
50
157
  return await loadModule(modulePath);
51
158
  }
52
- catch {
53
- // Module not found in this path, continue
54
- }
55
159
  }
56
160
  return null;
57
161
  }
@@ -63,14 +167,14 @@ export async function listModules(searchPaths) {
63
167
  for (const entry of entries) {
64
168
  if (entry.isDirectory()) {
65
169
  const modulePath = path.join(basePath, entry.name);
66
- const moduleFile = path.join(modulePath, 'MODULE.md');
67
- try {
68
- await fs.access(moduleFile);
69
- const module = await loadModule(modulePath);
70
- modules.push(module);
71
- }
72
- catch {
73
- // Not a valid module, skip
170
+ if (await isValidModule(modulePath)) {
171
+ try {
172
+ const module = await loadModule(modulePath);
173
+ modules.push(module);
174
+ }
175
+ catch {
176
+ // Skip invalid modules
177
+ }
74
178
  }
75
179
  }
76
180
  }
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
- import type { Provider, CognitiveModule, ModuleResult } from '../types.js';
5
+ import type { Provider, CognitiveModule, ModuleResult, ModuleInput } from '../types.js';
5
6
  export interface RunOptions {
7
+ input?: ModuleInput;
6
8
  args?: string;
7
- input?: Record<string, unknown>;
8
- validateInput?: boolean;
9
- validateOutput?: boolean;
10
9
  verbose?: boolean;
11
10
  }
12
11
  export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
@@ -1,43 +1,68 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
5
  export async function runModule(module, provider, options = {}) {
5
6
  const { args, input, verbose = false } = options;
6
- // Build input data
7
- let inputData = input || {};
8
- if (args) {
9
- inputData = { $ARGUMENTS: args, query: args };
7
+ // Build clean input data (v2 style: no $ARGUMENTS pollution)
8
+ const inputData = input || {};
9
+ // Map legacy --args to clean input
10
+ if (args && !inputData.code && !inputData.query) {
11
+ // Determine if args looks like code or natural language
12
+ if (looksLikeCode(args)) {
13
+ inputData.code = args;
14
+ }
15
+ else {
16
+ inputData.query = args;
17
+ }
10
18
  }
11
- // Build prompt
19
+ // Build prompt with clean substitution
12
20
  const prompt = buildPrompt(module, inputData);
13
21
  if (verbose) {
22
+ console.error('--- Module ---');
23
+ console.error(`Name: ${module.name} (${module.format})`);
24
+ console.error(`Responsibility: ${module.responsibility}`);
25
+ console.error('--- Input ---');
26
+ console.error(JSON.stringify(inputData, null, 2));
14
27
  console.error('--- Prompt ---');
15
28
  console.error(prompt);
16
- console.error('--- End Prompt ---');
29
+ console.error('--- End ---');
30
+ }
31
+ // Build system message based on module config
32
+ const systemParts = [
33
+ `You are executing the "${module.name}" Cognitive Module.`,
34
+ '',
35
+ `RESPONSIBILITY: ${module.responsibility}`,
36
+ ];
37
+ if (module.excludes.length > 0) {
38
+ systemParts.push('', 'YOU MUST NOT:');
39
+ module.excludes.forEach(e => systemParts.push(`- ${e}`));
40
+ }
41
+ if (module.constraints) {
42
+ systemParts.push('', 'CONSTRAINTS:');
43
+ if (module.constraints.no_network)
44
+ systemParts.push('- No network access');
45
+ if (module.constraints.no_side_effects)
46
+ systemParts.push('- No side effects');
47
+ if (module.constraints.no_file_write)
48
+ systemParts.push('- No file writes');
49
+ if (module.constraints.no_inventing_data)
50
+ systemParts.push('- Do not invent data');
51
+ }
52
+ if (module.output?.require_behavior_equivalence) {
53
+ systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
54
+ systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
55
+ systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
56
+ }
57
+ systemParts.push('', 'OUTPUT FORMAT:');
58
+ systemParts.push('- Respond with ONLY valid JSON');
59
+ systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
60
+ if (module.output?.require_behavior_equivalence) {
61
+ systemParts.push('- Include "behavior_equivalence" (boolean) field');
17
62
  }
18
- // Build messages
19
63
  const messages = [
20
- {
21
- role: 'system',
22
- content: `You are executing the "${module.name}" Cognitive Module.
23
-
24
- RESPONSIBILITY: ${module.responsibility}
25
-
26
- YOU MUST NOT:
27
- ${module.excludes.map(e => `- ${e}`).join('\n')}
28
-
29
- REQUIRED OUTPUT FORMAT:
30
- You MUST respond with a valid JSON object. Include these fields:
31
- - All fields required by the output schema
32
- - "confidence": a number between 0 and 1
33
- - "rationale": a string explaining your reasoning
34
-
35
- Respond with ONLY valid JSON, no markdown code blocks.`,
36
- },
37
- {
38
- role: 'user',
39
- content: prompt,
40
- },
64
+ { role: 'system', content: systemParts.join('\n') },
65
+ { role: 'user', content: prompt },
41
66
  ];
42
67
  // Invoke provider
43
68
  const result = await provider.invoke({
@@ -53,7 +78,6 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
53
78
  // Parse response
54
79
  let output;
55
80
  try {
56
- // Try to extract JSON from markdown code blocks
57
81
  const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
58
82
  const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
59
83
  output = JSON.parse(jsonStr.trim());
@@ -61,33 +85,65 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
61
85
  catch {
62
86
  throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
63
87
  }
64
- // Extract confidence and rationale
88
+ // Extract standard fields
65
89
  const outputObj = output;
66
90
  const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
67
91
  const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
92
+ const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
93
+ ? outputObj.behavior_equivalence
94
+ : undefined;
68
95
  return {
69
96
  output,
70
97
  confidence,
71
98
  rationale,
99
+ behaviorEquivalence,
72
100
  raw: result.content,
73
101
  };
74
102
  }
75
- function buildPrompt(module, inputData) {
103
+ /**
104
+ * Build prompt with clean variable substitution
105
+ */
106
+ function buildPrompt(module, input) {
76
107
  let prompt = module.prompt;
77
- // Substitute $ARGUMENTS
78
- const argsValue = String(inputData.$ARGUMENTS || inputData.query || '');
108
+ // v2 style: substitute ${variable} placeholders
109
+ for (const [key, value] of Object.entries(input)) {
110
+ const strValue = typeof value === 'string' ? value : JSON.stringify(value);
111
+ prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
112
+ }
113
+ // v1 compatibility: substitute $ARGUMENTS
114
+ const argsValue = input.code || input.query || '';
79
115
  prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
80
- // Substitute $N placeholders
81
- const argsList = argsValue.split(/\s+/);
82
- argsList.forEach((arg, i) => {
83
- prompt = prompt.replace(new RegExp(`\\$${i}`, 'g'), arg);
84
- prompt = prompt.replace(new RegExp(`\\$ARGUMENTS\\[${i}\\]`, 'g'), arg);
85
- });
86
- // Substitute other input fields
87
- for (const [key, value] of Object.entries(inputData)) {
88
- if (key !== '$ARGUMENTS' && key !== 'query') {
89
- prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
116
+ // Substitute $N placeholders (v1 compatibility)
117
+ if (typeof argsValue === 'string') {
118
+ const argsList = argsValue.split(/\s+/);
119
+ argsList.forEach((arg, i) => {
120
+ prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
121
+ });
122
+ }
123
+ // Append input summary if not already in prompt
124
+ if (!prompt.includes(argsValue) && argsValue) {
125
+ prompt += '\n\n## Input\n\n';
126
+ if (input.code) {
127
+ prompt += '```\n' + input.code + '\n```\n';
128
+ }
129
+ if (input.query) {
130
+ prompt += input.query + '\n';
131
+ }
132
+ if (input.language) {
133
+ prompt += `\nLanguage: ${input.language}\n`;
90
134
  }
91
135
  }
92
136
  return prompt;
93
137
  }
138
+ /**
139
+ * Heuristic to detect if input looks like code
140
+ */
141
+ function looksLikeCode(str) {
142
+ const codeIndicators = [
143
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
144
+ /[{};()]/,
145
+ /=>/,
146
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
147
+ ];
148
+ return codeIndicators.some(re => re.test(str));
149
+ }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
  export interface Provider {
5
6
  name: string;
@@ -29,16 +30,41 @@ export interface CognitiveModule {
29
30
  version: string;
30
31
  responsibility: string;
31
32
  excludes: string[];
33
+ constraints?: ModuleConstraints;
34
+ tools?: ToolsPolicy;
35
+ output?: OutputContract;
36
+ failure?: FailureContract;
32
37
  context?: 'fork' | 'main';
33
38
  prompt: string;
34
39
  inputSchema?: object;
35
40
  outputSchema?: object;
41
+ errorSchema?: object;
36
42
  location: string;
43
+ format: 'v1' | 'v2';
44
+ }
45
+ export interface ModuleConstraints {
46
+ no_network?: boolean;
47
+ no_side_effects?: boolean;
48
+ no_file_write?: boolean;
49
+ no_inventing_data?: boolean;
50
+ }
51
+ export interface ToolsPolicy {
52
+ allowed: string[];
53
+ }
54
+ export interface OutputContract {
55
+ mode?: 'json_strict' | 'json_lenient' | 'text';
56
+ require_confidence?: boolean;
57
+ require_rationale?: boolean;
58
+ require_behavior_equivalence?: boolean;
59
+ }
60
+ export interface FailureContract {
61
+ schema?: object;
37
62
  }
38
63
  export interface ModuleResult {
39
64
  output: unknown;
40
65
  confidence: number;
41
66
  rationale: string;
67
+ behaviorEquivalence?: boolean;
42
68
  raw?: string;
43
69
  }
44
70
  export interface CommandContext {
@@ -51,3 +77,10 @@ export interface CommandResult {
51
77
  data?: unknown;
52
78
  error?: string;
53
79
  }
80
+ export interface ModuleInput {
81
+ code?: string;
82
+ query?: string;
83
+ language?: string;
84
+ options?: Record<string, unknown>;
85
+ [key: string]: unknown;
86
+ }
package/dist/types.js CHANGED
@@ -1,4 +1,5 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cognitive-runtime",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Cognitive Runtime - Structured AI Task Execution",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/cli.ts CHANGED
@@ -13,7 +13,7 @@ import { getProvider, listProviders } from './providers/index.js';
13
13
  import { run, list, pipe, init } from './commands/index.js';
14
14
  import type { CommandContext } from './types.js';
15
15
 
16
- const VERSION = '0.3.0';
16
+ const VERSION = '0.4.0';
17
17
 
18
18
  async function main() {
19
19
  const args = process.argv.slice(2);
@@ -52,8 +52,6 @@ export async function pipe(
52
52
  const result = await runModule(module, ctx.provider, {
53
53
  args: inputData ? undefined : input,
54
54
  input: inputData,
55
- validateInput: !options.noValidate,
56
- validateOutput: !options.noValidate,
57
55
  });
58
56
 
59
57
  // Output JSON to stdout
@@ -47,8 +47,6 @@ export async function run(
47
47
  const result = await runModule(module, ctx.provider, {
48
48
  args: options.args,
49
49
  input: inputData,
50
- validateInput: !options.noValidate,
51
- validateOutput: !options.noValidate,
52
50
  verbose: options.verbose || ctx.verbose,
53
51
  });
54
52
 
package/src/index.ts CHANGED
@@ -12,6 +12,11 @@ export type {
12
12
  Message,
13
13
  CognitiveModule,
14
14
  ModuleResult,
15
+ ModuleInput,
16
+ ModuleConstraints,
17
+ ToolsPolicy,
18
+ OutputContract,
19
+ FailureContract,
15
20
  CommandContext,
16
21
  CommandResult,
17
22
  } from './types.js';
@@ -1,15 +1,86 @@
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
4
  */
4
5
 
5
6
  import * as fs from 'node:fs/promises';
6
7
  import * as path from 'node:path';
7
8
  import yaml from 'js-yaml';
8
- import type { CognitiveModule } from '../types.js';
9
+ import type { CognitiveModule, ModuleConstraints, ToolsPolicy, OutputContract, FailureContract } from '../types.js';
9
10
 
10
11
  const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
11
12
 
12
- export async function loadModule(modulePath: string): Promise<CognitiveModule> {
13
+ /**
14
+ * Detect module format version
15
+ */
16
+ async function detectFormat(modulePath: string): Promise<'v1' | 'v2'> {
17
+ const v2Manifest = path.join(modulePath, 'module.yaml');
18
+ try {
19
+ await fs.access(v2Manifest);
20
+ return 'v2';
21
+ } catch {
22
+ return 'v1';
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Load v2 format module (module.yaml + prompt.md)
28
+ */
29
+ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
30
+ const manifestFile = path.join(modulePath, 'module.yaml');
31
+ const promptFile = path.join(modulePath, 'prompt.md');
32
+ const schemaFile = path.join(modulePath, 'schema.json');
33
+
34
+ // Read module.yaml
35
+ const manifestContent = await fs.readFile(manifestFile, 'utf-8');
36
+ const manifest = yaml.load(manifestContent) as Record<string, unknown>;
37
+
38
+ // Read prompt.md
39
+ let prompt = '';
40
+ try {
41
+ prompt = await fs.readFile(promptFile, 'utf-8');
42
+ } catch {
43
+ // prompt.md is optional, manifest may include inline prompt
44
+ }
45
+
46
+ // Read schema.json
47
+ let inputSchema: object | undefined;
48
+ let outputSchema: object | undefined;
49
+ let errorSchema: object | undefined;
50
+
51
+ try {
52
+ const schemaContent = await fs.readFile(schemaFile, 'utf-8');
53
+ const schema = JSON.parse(schemaContent);
54
+ inputSchema = schema.input;
55
+ outputSchema = schema.output;
56
+ errorSchema = schema.error;
57
+ } catch {
58
+ // Schema file is optional but recommended
59
+ }
60
+
61
+ return {
62
+ name: manifest.name as string || path.basename(modulePath),
63
+ version: manifest.version as string || '1.0.0',
64
+ responsibility: manifest.responsibility as string || '',
65
+ excludes: (manifest.excludes as string[]) || [],
66
+ constraints: manifest.constraints as ModuleConstraints | undefined,
67
+ tools: manifest.tools as ToolsPolicy | undefined,
68
+ output: manifest.output as OutputContract | undefined,
69
+ failure: manifest.failure as FailureContract | undefined,
70
+ context: manifest.context as 'fork' | 'main' | undefined,
71
+ prompt,
72
+ inputSchema,
73
+ outputSchema,
74
+ errorSchema,
75
+ location: modulePath,
76
+ format: 'v2',
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Load v1 format module (MODULE.md with frontmatter)
82
+ */
83
+ async function loadModuleV1(modulePath: string): Promise<CognitiveModule> {
13
84
  const moduleFile = path.join(modulePath, 'MODULE.md');
14
85
  const schemaFile = path.join(modulePath, 'schema.json');
15
86
 
@@ -38,29 +109,69 @@ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
38
109
  // Schema file is optional
39
110
  }
40
111
 
112
+ // Extract constraints from v1 format
113
+ const constraints: ModuleConstraints = {};
114
+ const v1Constraints = frontmatter.constraints as Record<string, boolean> | undefined;
115
+ if (v1Constraints) {
116
+ constraints.no_network = v1Constraints.no_network;
117
+ constraints.no_side_effects = v1Constraints.no_side_effects;
118
+ constraints.no_inventing_data = v1Constraints.no_inventing_data;
119
+ }
120
+
41
121
  return {
42
122
  name: frontmatter.name as string || path.basename(modulePath),
43
123
  version: frontmatter.version as string || '1.0.0',
44
124
  responsibility: frontmatter.responsibility as string || '',
45
125
  excludes: (frontmatter.excludes as string[]) || [],
126
+ constraints: Object.keys(constraints).length > 0 ? constraints : undefined,
46
127
  context: frontmatter.context as 'fork' | 'main' | undefined,
47
128
  prompt,
48
129
  inputSchema,
49
130
  outputSchema,
50
131
  location: modulePath,
132
+ format: 'v1',
51
133
  };
52
134
  }
53
135
 
136
+ /**
137
+ * Load a Cognitive Module (auto-detects format)
138
+ */
139
+ export async function loadModule(modulePath: string): Promise<CognitiveModule> {
140
+ const format = await detectFormat(modulePath);
141
+
142
+ if (format === 'v2') {
143
+ return loadModuleV2(modulePath);
144
+ } else {
145
+ return loadModuleV1(modulePath);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Check if a directory contains a valid module
151
+ */
152
+ async function isValidModule(modulePath: string): Promise<boolean> {
153
+ const v2Manifest = path.join(modulePath, 'module.yaml');
154
+ const v1Module = path.join(modulePath, 'MODULE.md');
155
+
156
+ try {
157
+ await fs.access(v2Manifest);
158
+ return true;
159
+ } catch {
160
+ try {
161
+ await fs.access(v1Module);
162
+ return true;
163
+ } catch {
164
+ return false;
165
+ }
166
+ }
167
+ }
168
+
54
169
  export async function findModule(name: string, searchPaths: string[]): Promise<CognitiveModule | null> {
55
170
  for (const basePath of searchPaths) {
56
171
  const modulePath = path.join(basePath, name);
57
- const moduleFile = path.join(modulePath, 'MODULE.md');
58
172
 
59
- try {
60
- await fs.access(moduleFile);
173
+ if (await isValidModule(modulePath)) {
61
174
  return await loadModule(modulePath);
62
- } catch {
63
- // Module not found in this path, continue
64
175
  }
65
176
  }
66
177
 
@@ -77,14 +188,14 @@ export async function listModules(searchPaths: string[]): Promise<CognitiveModul
77
188
  for (const entry of entries) {
78
189
  if (entry.isDirectory()) {
79
190
  const modulePath = path.join(basePath, entry.name);
80
- const moduleFile = path.join(modulePath, 'MODULE.md');
81
191
 
82
- try {
83
- await fs.access(moduleFile);
84
- const module = await loadModule(modulePath);
85
- modules.push(module);
86
- } catch {
87
- // Not a valid module, skip
192
+ if (await isValidModule(modulePath)) {
193
+ try {
194
+ const module = await loadModule(modulePath);
195
+ modules.push(module);
196
+ } catch {
197
+ // Skip invalid modules
198
+ }
88
199
  }
89
200
  }
90
201
  }
@@ -1,14 +1,18 @@
1
1
  /**
2
2
  * Module Runner - Execute Cognitive Modules
3
+ * v2: Clean input mapping, no CLI pollution
3
4
  */
4
5
 
5
- import type { Provider, CognitiveModule, ModuleResult, Message } from '../types.js';
6
+ import type { Provider, CognitiveModule, ModuleResult, Message, ModuleInput } from '../types.js';
6
7
 
7
8
  export interface RunOptions {
9
+ // Clean input (v2 style)
10
+ input?: ModuleInput;
11
+
12
+ // Legacy CLI args (v1 compatibility) - mapped to input.code or input.query
8
13
  args?: string;
9
- input?: Record<string, unknown>;
10
- validateInput?: boolean;
11
- validateOutput?: boolean;
14
+
15
+ // Runtime options
12
16
  verbose?: boolean;
13
17
  }
14
18
 
@@ -19,44 +23,69 @@ export async function runModule(
19
23
  ): Promise<ModuleResult> {
20
24
  const { args, input, verbose = false } = options;
21
25
 
22
- // Build input data
23
- let inputData: Record<string, unknown> = input || {};
24
- if (args) {
25
- inputData = { $ARGUMENTS: args, query: args };
26
+ // Build clean input data (v2 style: no $ARGUMENTS pollution)
27
+ const inputData: ModuleInput = input || {};
28
+
29
+ // Map legacy --args to clean input
30
+ if (args && !inputData.code && !inputData.query) {
31
+ // Determine if args looks like code or natural language
32
+ if (looksLikeCode(args)) {
33
+ inputData.code = args;
34
+ } else {
35
+ inputData.query = args;
36
+ }
26
37
  }
27
38
 
28
- // Build prompt
39
+ // Build prompt with clean substitution
29
40
  const prompt = buildPrompt(module, inputData);
30
41
 
31
42
  if (verbose) {
43
+ console.error('--- Module ---');
44
+ console.error(`Name: ${module.name} (${module.format})`);
45
+ console.error(`Responsibility: ${module.responsibility}`);
46
+ console.error('--- Input ---');
47
+ console.error(JSON.stringify(inputData, null, 2));
32
48
  console.error('--- Prompt ---');
33
49
  console.error(prompt);
34
- console.error('--- End Prompt ---');
50
+ console.error('--- End ---');
51
+ }
52
+
53
+ // Build system message based on module config
54
+ const systemParts: string[] = [
55
+ `You are executing the "${module.name}" Cognitive Module.`,
56
+ '',
57
+ `RESPONSIBILITY: ${module.responsibility}`,
58
+ ];
59
+
60
+ if (module.excludes.length > 0) {
61
+ systemParts.push('', 'YOU MUST NOT:');
62
+ module.excludes.forEach(e => systemParts.push(`- ${e}`));
63
+ }
64
+
65
+ if (module.constraints) {
66
+ systemParts.push('', 'CONSTRAINTS:');
67
+ if (module.constraints.no_network) systemParts.push('- No network access');
68
+ if (module.constraints.no_side_effects) systemParts.push('- No side effects');
69
+ if (module.constraints.no_file_write) systemParts.push('- No file writes');
70
+ if (module.constraints.no_inventing_data) systemParts.push('- Do not invent data');
71
+ }
72
+
73
+ if (module.output?.require_behavior_equivalence) {
74
+ systemParts.push('', 'BEHAVIOR EQUIVALENCE:');
75
+ systemParts.push('- You MUST set behavior_equivalence=true ONLY if the output is functionally identical');
76
+ systemParts.push('- If unsure, set behavior_equivalence=false and explain in rationale');
77
+ }
78
+
79
+ systemParts.push('', 'OUTPUT FORMAT:');
80
+ systemParts.push('- Respond with ONLY valid JSON');
81
+ systemParts.push('- Include "confidence" (0-1) and "rationale" fields');
82
+ if (module.output?.require_behavior_equivalence) {
83
+ systemParts.push('- Include "behavior_equivalence" (boolean) field');
35
84
  }
36
85
 
37
- // Build messages
38
86
  const messages: Message[] = [
39
- {
40
- role: 'system',
41
- content: `You are executing the "${module.name}" Cognitive Module.
42
-
43
- RESPONSIBILITY: ${module.responsibility}
44
-
45
- YOU MUST NOT:
46
- ${module.excludes.map(e => `- ${e}`).join('\n')}
47
-
48
- REQUIRED OUTPUT FORMAT:
49
- You MUST respond with a valid JSON object. Include these fields:
50
- - All fields required by the output schema
51
- - "confidence": a number between 0 and 1
52
- - "rationale": a string explaining your reasoning
53
-
54
- Respond with ONLY valid JSON, no markdown code blocks.`,
55
- },
56
- {
57
- role: 'user',
58
- content: prompt,
59
- },
87
+ { role: 'system', content: systemParts.join('\n') },
88
+ { role: 'user', content: prompt },
60
89
  ];
61
90
 
62
91
  // Invoke provider
@@ -75,7 +104,6 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
75
104
  // Parse response
76
105
  let output: unknown;
77
106
  try {
78
- // Try to extract JSON from markdown code blocks
79
107
  const jsonMatch = result.content.match(/```(?:json)?\s*([\s\S]*?)\s*```/);
80
108
  const jsonStr = jsonMatch ? jsonMatch[1] : result.content;
81
109
  output = JSON.parse(jsonStr.trim());
@@ -83,39 +111,73 @@ Respond with ONLY valid JSON, no markdown code blocks.`,
83
111
  throw new Error(`Failed to parse JSON response: ${result.content.substring(0, 500)}`);
84
112
  }
85
113
 
86
- // Extract confidence and rationale
114
+ // Extract standard fields
87
115
  const outputObj = output as Record<string, unknown>;
88
116
  const confidence = typeof outputObj.confidence === 'number' ? outputObj.confidence : 0.5;
89
117
  const rationale = typeof outputObj.rationale === 'string' ? outputObj.rationale : '';
118
+ const behaviorEquivalence = typeof outputObj.behavior_equivalence === 'boolean'
119
+ ? outputObj.behavior_equivalence
120
+ : undefined;
90
121
 
91
122
  return {
92
123
  output,
93
124
  confidence,
94
125
  rationale,
126
+ behaviorEquivalence,
95
127
  raw: result.content,
96
128
  };
97
129
  }
98
130
 
99
- function buildPrompt(module: CognitiveModule, inputData: Record<string, unknown>): string {
131
+ /**
132
+ * Build prompt with clean variable substitution
133
+ */
134
+ function buildPrompt(module: CognitiveModule, input: ModuleInput): string {
100
135
  let prompt = module.prompt;
101
136
 
102
- // Substitute $ARGUMENTS
103
- const argsValue = String(inputData.$ARGUMENTS || inputData.query || '');
137
+ // v2 style: substitute ${variable} placeholders
138
+ for (const [key, value] of Object.entries(input)) {
139
+ const strValue = typeof value === 'string' ? value : JSON.stringify(value);
140
+ prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), strValue);
141
+ }
142
+
143
+ // v1 compatibility: substitute $ARGUMENTS
144
+ const argsValue = input.code || input.query || '';
104
145
  prompt = prompt.replace(/\$ARGUMENTS/g, argsValue);
105
146
 
106
- // Substitute $N placeholders
107
- const argsList = argsValue.split(/\s+/);
108
- argsList.forEach((arg, i) => {
109
- prompt = prompt.replace(new RegExp(`\\$${i}`, 'g'), arg);
110
- prompt = prompt.replace(new RegExp(`\\$ARGUMENTS\\[${i}\\]`, 'g'), arg);
111
- });
147
+ // Substitute $N placeholders (v1 compatibility)
148
+ if (typeof argsValue === 'string') {
149
+ const argsList = argsValue.split(/\s+/);
150
+ argsList.forEach((arg, i) => {
151
+ prompt = prompt.replace(new RegExp(`\\$${i}\\b`, 'g'), arg);
152
+ });
153
+ }
112
154
 
113
- // Substitute other input fields
114
- for (const [key, value] of Object.entries(inputData)) {
115
- if (key !== '$ARGUMENTS' && key !== 'query') {
116
- prompt = prompt.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), String(value));
155
+ // Append input summary if not already in prompt
156
+ if (!prompt.includes(argsValue) && argsValue) {
157
+ prompt += '\n\n## Input\n\n';
158
+ if (input.code) {
159
+ prompt += '```\n' + input.code + '\n```\n';
160
+ }
161
+ if (input.query) {
162
+ prompt += input.query + '\n';
163
+ }
164
+ if (input.language) {
165
+ prompt += `\nLanguage: ${input.language}\n`;
117
166
  }
118
167
  }
119
168
 
120
169
  return prompt;
121
170
  }
171
+
172
+ /**
173
+ * Heuristic to detect if input looks like code
174
+ */
175
+ function looksLikeCode(str: string): boolean {
176
+ const codeIndicators = [
177
+ /^(def|function|class|const|let|var|import|export|public|private)\s/,
178
+ /[{};()]/,
179
+ /=>/,
180
+ /\.(py|js|ts|go|rs|java|cpp|c|rb)$/,
181
+ ];
182
+ return codeIndicators.some(re => re.test(str));
183
+ }
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * Cognitive Runtime - Core Types
3
+ * Version 2.0 - With tools policy, failure contract, and behavior equivalence
3
4
  */
4
5
 
5
6
  // Provider interface - all LLM providers implement this
@@ -30,23 +31,69 @@ export interface InvokeResult {
30
31
  };
31
32
  }
32
33
 
33
- // Module types
34
+ // Module types (v2)
34
35
  export interface CognitiveModule {
36
+ // Core identity
35
37
  name: string;
36
38
  version: string;
37
39
  responsibility: string;
40
+
41
+ // Constraints
38
42
  excludes: string[];
43
+ constraints?: ModuleConstraints;
44
+
45
+ // Tools policy
46
+ tools?: ToolsPolicy;
47
+
48
+ // Output contract
49
+ output?: OutputContract;
50
+
51
+ // Failure contract
52
+ failure?: FailureContract;
53
+
54
+ // Execution context
39
55
  context?: 'fork' | 'main';
56
+
57
+ // Prompt (from prompt.md or MODULE.md body)
40
58
  prompt: string;
59
+
60
+ // Schemas
41
61
  inputSchema?: object;
42
62
  outputSchema?: object;
63
+ errorSchema?: object;
64
+
65
+ // Metadata
43
66
  location: string;
67
+ format: 'v1' | 'v2'; // v1 = MODULE.md, v2 = module.yaml + prompt.md
68
+ }
69
+
70
+ export interface ModuleConstraints {
71
+ no_network?: boolean;
72
+ no_side_effects?: boolean;
73
+ no_file_write?: boolean;
74
+ no_inventing_data?: boolean;
75
+ }
76
+
77
+ export interface ToolsPolicy {
78
+ allowed: string[];
79
+ }
80
+
81
+ export interface OutputContract {
82
+ mode?: 'json_strict' | 'json_lenient' | 'text';
83
+ require_confidence?: boolean;
84
+ require_rationale?: boolean;
85
+ require_behavior_equivalence?: boolean;
86
+ }
87
+
88
+ export interface FailureContract {
89
+ schema?: object;
44
90
  }
45
91
 
46
92
  export interface ModuleResult {
47
93
  output: unknown;
48
94
  confidence: number;
49
95
  rationale: string;
96
+ behaviorEquivalence?: boolean;
50
97
  raw?: string;
51
98
  }
52
99
 
@@ -62,3 +109,12 @@ export interface CommandResult {
62
109
  data?: unknown;
63
110
  error?: string;
64
111
  }
112
+
113
+ // Module input (clean, no CLI pollution)
114
+ export interface ModuleInput {
115
+ code?: string;
116
+ query?: string;
117
+ language?: string;
118
+ options?: Record<string, unknown>;
119
+ [key: string]: unknown;
120
+ }