cognitive-modules-cli 1.1.0 → 1.2.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/README.md +51 -127
- package/dist/modules/loader.js +72 -1
- package/dist/modules/runner.d.ts +3 -1
- package/dist/modules/runner.js +183 -11
- package/dist/types.d.ts +25 -5
- package/dist/types.js +34 -10
- package/package.json +1 -1
- package/src/modules/loader.ts +95 -2
- package/src/modules/runner.ts +226 -15
- package/src/types.ts +53 -10
package/README.md
CHANGED
|
@@ -1,162 +1,86 @@
|
|
|
1
|
-
# Cognitive
|
|
1
|
+
# Cognitive Modules CLI (Node.js)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/cognitive-modules-cli)
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Node.js/TypeScript 版本的 Cognitive Modules CLI。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
> 这是 [cognitive-modules](../../README.md) monorepo 的一部分。
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
12
|
-
┌─────────────────────────────────────┐
|
|
13
|
-
│ Cognitive Runtime │
|
|
14
|
-
│ ┌────────────────────────────────┐ │
|
|
15
|
-
│ │ Module System │ │
|
|
16
|
-
│ │ (load, parse, validate) │ │
|
|
17
|
-
│ └────────────────────────────────┘ │
|
|
18
|
-
│ ┌────────────────────────────────┐ │
|
|
19
|
-
│ │ Execution Engine │ │
|
|
20
|
-
│ │ (prompt, schema, contract) │ │
|
|
21
|
-
│ └────────────────────────────────┘ │
|
|
22
|
-
│ ┌────────────────────────────────┐ │
|
|
23
|
-
│ │ Provider Abstraction │ │
|
|
24
|
-
│ │ (gemini, openai, anthropic, │ │
|
|
25
|
-
│ │ deepseek, minimax, qwen...) │ │
|
|
26
|
-
│ └────────────────────────────────┘ │
|
|
27
|
-
└─────────────────────────────────────┘
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
## Installation
|
|
9
|
+
## 安装
|
|
31
10
|
|
|
32
11
|
```bash
|
|
33
|
-
|
|
34
|
-
|
|
12
|
+
# 全局安装(推荐)
|
|
13
|
+
npm install -g cogn
|
|
35
14
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```bash
|
|
39
|
-
npx cognitive-runtime --help
|
|
15
|
+
# 或使用 npx 零安装
|
|
16
|
+
npx cogn --help
|
|
40
17
|
```
|
|
41
18
|
|
|
42
|
-
##
|
|
43
|
-
|
|
44
|
-
### Run a Module
|
|
19
|
+
## 快速开始
|
|
45
20
|
|
|
46
21
|
```bash
|
|
47
|
-
|
|
48
|
-
|
|
22
|
+
# 配置 LLM
|
|
23
|
+
export LLM_PROVIDER=openai
|
|
24
|
+
export OPENAI_API_KEY=sk-xxx
|
|
49
25
|
|
|
50
|
-
|
|
26
|
+
# 运行模块
|
|
27
|
+
cog run code-reviewer --args "def login(u,p): return db.query(f'SELECT * FROM users WHERE name={u}')"
|
|
51
28
|
|
|
52
|
-
|
|
29
|
+
# 列出模块
|
|
53
30
|
cog list
|
|
54
|
-
```
|
|
55
31
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
```bash
|
|
32
|
+
# 管道模式
|
|
59
33
|
echo "review this code" | cog pipe --module code-reviewer
|
|
60
34
|
```
|
|
61
35
|
|
|
62
|
-
|
|
36
|
+
## 与 Python 版的区别
|
|
63
37
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
38
|
+
| 功能 | Python (`cogn`) | Node.js (`cog`) |
|
|
39
|
+
|------|----------------|-----------------|
|
|
40
|
+
| 包名 | `cognitive-modules` | `cognitive-modules-cli` |
|
|
41
|
+
| 安装 | `pip install` | `npm install -g` |
|
|
42
|
+
| 子代理 | ✅ `@call:module` | ❌ 暂不支持 |
|
|
43
|
+
| MCP Server | ✅ | ❌ 暂不支持 |
|
|
44
|
+
| HTTP Server | ✅ | ❌ 暂不支持 |
|
|
67
45
|
|
|
68
|
-
|
|
46
|
+
两个版本共享相同的模块格式和 v2.2 规范。
|
|
69
47
|
|
|
70
|
-
|
|
48
|
+
## 支持的 Provider
|
|
71
49
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
50
|
+
| Provider | 环境变量 | 别名 |
|
|
51
|
+
|----------|----------|------|
|
|
52
|
+
| OpenAI | `OPENAI_API_KEY` | - |
|
|
53
|
+
| Anthropic | `ANTHROPIC_API_KEY` | - |
|
|
54
|
+
| Gemini | `GEMINI_API_KEY` | - |
|
|
55
|
+
| DeepSeek | `DEEPSEEK_API_KEY` | - |
|
|
56
|
+
| MiniMax | `MINIMAX_API_KEY` | - |
|
|
57
|
+
| Moonshot | `MOONSHOT_API_KEY` | `kimi` |
|
|
58
|
+
| Qwen | `DASHSCOPE_API_KEY` | `tongyi` |
|
|
59
|
+
| Ollama | `OLLAMA_HOST` | `local` |
|
|
81
60
|
|
|
82
|
-
|
|
83
|
-
```yaml
|
|
84
|
-
name: my-module
|
|
85
|
-
version: 2.0.0
|
|
86
|
-
responsibility: What this module does
|
|
87
|
-
constraints:
|
|
88
|
-
no_network: true
|
|
89
|
-
no_side_effects: true
|
|
90
|
-
output:
|
|
91
|
-
mode: json_strict
|
|
92
|
-
require_confidence: true
|
|
93
|
-
require_rationale: true
|
|
94
|
-
require_behavior_equivalence: true
|
|
95
|
-
tools:
|
|
96
|
-
allowed: []
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### v1 (Legacy, still supported)
|
|
100
|
-
|
|
101
|
-
```
|
|
102
|
-
my-module/
|
|
103
|
-
├── MODULE.md # Frontmatter + prompt combined
|
|
104
|
-
└── schema.json
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Providers
|
|
108
|
-
|
|
109
|
-
| Provider | Environment Variable | Default Model |
|
|
110
|
-
|------------|------------------------|----------------------|
|
|
111
|
-
| Gemini | `GEMINI_API_KEY` | `gemini-3-flash` |
|
|
112
|
-
| OpenAI | `OPENAI_API_KEY` | `gpt-5.2` |
|
|
113
|
-
| Anthropic | `ANTHROPIC_API_KEY` | `claude-sonnet-4.5` |
|
|
114
|
-
| DeepSeek | `DEEPSEEK_API_KEY` | `deepseek-v3.2` |
|
|
115
|
-
| MiniMax | `MINIMAX_API_KEY` | `MiniMax-M2.1` |
|
|
116
|
-
| Moonshot | `MOONSHOT_API_KEY` | `kimi-k2.5` |
|
|
117
|
-
| Qwen | `DASHSCOPE_API_KEY` | `qwen3-max` |
|
|
118
|
-
| Ollama | `OLLAMA_HOST` | `llama4` (local) |
|
|
119
|
-
|
|
120
|
-
### Provider Aliases
|
|
61
|
+
## 命令
|
|
121
62
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
2. `./.cognitive/modules/` (project-local, hidden)
|
|
132
|
-
3. `~/.cognitive/modules/` (user-global)
|
|
133
|
-
|
|
134
|
-
## Programmatic API
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
import { getProvider, findModule, runModule } from 'cognitive-runtime';
|
|
138
|
-
|
|
139
|
-
const provider = getProvider('gemini');
|
|
140
|
-
const module = await findModule('code-reviewer', ['./cognitive/modules']);
|
|
141
|
-
|
|
142
|
-
if (module) {
|
|
143
|
-
const result = await runModule(module, provider, {
|
|
144
|
-
args: 'def foo(): pass',
|
|
145
|
-
});
|
|
146
|
-
console.log(result.output);
|
|
147
|
-
}
|
|
63
|
+
```bash
|
|
64
|
+
cog list # 列出模块
|
|
65
|
+
cog run <module> --args "..." # 运行模块
|
|
66
|
+
cog add <url> -m <module> # 从 GitHub 添加模块
|
|
67
|
+
cog update <module> # 更新模块
|
|
68
|
+
cog remove <module> # 删除模块
|
|
69
|
+
cog versions <url> # 查看可用版本
|
|
70
|
+
cog init <name> # 创建新模块
|
|
71
|
+
cog pipe --module <name> # 管道模式
|
|
148
72
|
```
|
|
149
73
|
|
|
150
|
-
##
|
|
74
|
+
## 开发
|
|
151
75
|
|
|
152
76
|
```bash
|
|
153
|
-
#
|
|
77
|
+
# 安装依赖
|
|
154
78
|
npm install
|
|
155
79
|
|
|
156
|
-
#
|
|
80
|
+
# 构建
|
|
157
81
|
npm run build
|
|
158
82
|
|
|
159
|
-
#
|
|
83
|
+
# 开发模式运行
|
|
160
84
|
npm run dev -- run code-reviewer --args "..."
|
|
161
85
|
```
|
|
162
86
|
|
package/dist/modules/loader.js
CHANGED
|
@@ -19,6 +19,18 @@ async function detectFormat(modulePath) {
|
|
|
19
19
|
return 'v1';
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect v2.x sub-version from manifest
|
|
24
|
+
*/
|
|
25
|
+
function detectV2Version(manifest) {
|
|
26
|
+
if (manifest.tier || manifest.overflow || manifest.enums) {
|
|
27
|
+
return 'v2.2';
|
|
28
|
+
}
|
|
29
|
+
if (manifest.policies || manifest.failure) {
|
|
30
|
+
return 'v2.1';
|
|
31
|
+
}
|
|
32
|
+
return 'v2.0';
|
|
33
|
+
}
|
|
22
34
|
/**
|
|
23
35
|
* Load v2 format module (module.yaml + prompt.md)
|
|
24
36
|
*/
|
|
@@ -29,6 +41,8 @@ async function loadModuleV2(modulePath) {
|
|
|
29
41
|
// Read module.yaml
|
|
30
42
|
const manifestContent = await fs.readFile(manifestFile, 'utf-8');
|
|
31
43
|
const manifest = yaml.load(manifestContent);
|
|
44
|
+
// Detect v2.x version
|
|
45
|
+
const formatVersion = detectV2Version(manifest);
|
|
32
46
|
// Read prompt.md
|
|
33
47
|
let prompt = '';
|
|
34
48
|
try {
|
|
@@ -40,17 +54,61 @@ async function loadModuleV2(modulePath) {
|
|
|
40
54
|
// Read schema.json
|
|
41
55
|
let inputSchema;
|
|
42
56
|
let outputSchema;
|
|
57
|
+
let dataSchema;
|
|
58
|
+
let metaSchema;
|
|
43
59
|
let errorSchema;
|
|
44
60
|
try {
|
|
45
61
|
const schemaContent = await fs.readFile(schemaFile, 'utf-8');
|
|
46
62
|
const schema = JSON.parse(schemaContent);
|
|
47
63
|
inputSchema = schema.input;
|
|
48
|
-
|
|
64
|
+
// Support both "data" (v2.2) and "output" (v2.1) aliases
|
|
65
|
+
dataSchema = schema.data || schema.output;
|
|
66
|
+
outputSchema = dataSchema; // Backward compat
|
|
67
|
+
metaSchema = schema.meta;
|
|
49
68
|
errorSchema = schema.error;
|
|
50
69
|
}
|
|
51
70
|
catch {
|
|
52
71
|
// Schema file is optional but recommended
|
|
53
72
|
}
|
|
73
|
+
// Extract v2.2 fields
|
|
74
|
+
const tier = manifest.tier;
|
|
75
|
+
const schemaStrictness = manifest.schema_strictness || 'medium';
|
|
76
|
+
// Determine default max_items based on strictness (SPEC-v2.2)
|
|
77
|
+
const strictnessMaxItems = {
|
|
78
|
+
high: 0,
|
|
79
|
+
medium: 5,
|
|
80
|
+
low: 20
|
|
81
|
+
};
|
|
82
|
+
const defaultMaxItems = strictnessMaxItems[schemaStrictness] ?? 5;
|
|
83
|
+
const defaultEnabled = schemaStrictness !== 'high';
|
|
84
|
+
// Parse overflow config with strictness-based defaults
|
|
85
|
+
const overflowRaw = manifest.overflow || {};
|
|
86
|
+
const overflow = {
|
|
87
|
+
enabled: overflowRaw.enabled ?? defaultEnabled,
|
|
88
|
+
recoverable: overflowRaw.recoverable ?? true,
|
|
89
|
+
max_items: overflowRaw.max_items ?? defaultMaxItems,
|
|
90
|
+
require_suggested_mapping: overflowRaw.require_suggested_mapping ?? true
|
|
91
|
+
};
|
|
92
|
+
// Parse enums config
|
|
93
|
+
const enumsRaw = manifest.enums || {};
|
|
94
|
+
const enums = {
|
|
95
|
+
strategy: enumsRaw.strategy ??
|
|
96
|
+
(tier === 'exec' ? 'strict' : 'extensible'),
|
|
97
|
+
unknown_tag: enumsRaw.unknown_tag ?? 'custom'
|
|
98
|
+
};
|
|
99
|
+
// Parse compat config
|
|
100
|
+
const compatRaw = manifest.compat || {};
|
|
101
|
+
const compat = {
|
|
102
|
+
accepts_v21_payload: compatRaw.accepts_v21_payload ?? true,
|
|
103
|
+
runtime_auto_wrap: compatRaw.runtime_auto_wrap ?? true,
|
|
104
|
+
schema_output_alias: compatRaw.schema_output_alias ?? 'data'
|
|
105
|
+
};
|
|
106
|
+
// Parse meta config (including risk_rule)
|
|
107
|
+
const metaRaw = manifest.meta || {};
|
|
108
|
+
const metaConfig = {
|
|
109
|
+
required: metaRaw.required,
|
|
110
|
+
risk_rule: metaRaw.risk_rule,
|
|
111
|
+
};
|
|
54
112
|
return {
|
|
55
113
|
name: manifest.name || path.basename(modulePath),
|
|
56
114
|
version: manifest.version || '1.0.0',
|
|
@@ -62,13 +120,26 @@ async function loadModuleV2(modulePath) {
|
|
|
62
120
|
output: manifest.output,
|
|
63
121
|
failure: manifest.failure,
|
|
64
122
|
runtimeRequirements: manifest.runtime_requirements,
|
|
123
|
+
// v2.2 fields
|
|
124
|
+
tier,
|
|
125
|
+
schemaStrictness,
|
|
126
|
+
overflow,
|
|
127
|
+
enums,
|
|
128
|
+
compat,
|
|
129
|
+
metaConfig,
|
|
130
|
+
// Context and prompt
|
|
65
131
|
context: manifest.context,
|
|
66
132
|
prompt,
|
|
133
|
+
// Schemas
|
|
67
134
|
inputSchema,
|
|
68
135
|
outputSchema,
|
|
136
|
+
dataSchema,
|
|
137
|
+
metaSchema,
|
|
69
138
|
errorSchema,
|
|
139
|
+
// Metadata
|
|
70
140
|
location: modulePath,
|
|
71
141
|
format: 'v2',
|
|
142
|
+
formatVersion,
|
|
72
143
|
};
|
|
73
144
|
}
|
|
74
145
|
/**
|
package/dist/modules/runner.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Runner - Execute Cognitive Modules
|
|
3
|
-
* v2.
|
|
3
|
+
* v2.2: Envelope format with meta/data separation, risk_rule, repair pass
|
|
4
4
|
*/
|
|
5
5
|
import type { Provider, CognitiveModule, ModuleResult, ModuleInput } from '../types.js';
|
|
6
6
|
export interface RunOptions {
|
|
@@ -8,5 +8,7 @@ export interface RunOptions {
|
|
|
8
8
|
args?: string;
|
|
9
9
|
verbose?: boolean;
|
|
10
10
|
useEnvelope?: boolean;
|
|
11
|
+
useV22?: boolean;
|
|
12
|
+
enableRepair?: boolean;
|
|
11
13
|
}
|
|
12
14
|
export declare function runModule(module: CognitiveModule, provider: Provider, options?: RunOptions): Promise<ModuleResult>;
|
package/dist/modules/runner.js
CHANGED
|
@@ -1,11 +1,116 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Runner - Execute Cognitive Modules
|
|
3
|
-
* v2.
|
|
3
|
+
* v2.2: Envelope format with meta/data separation, risk_rule, repair pass
|
|
4
4
|
*/
|
|
5
|
+
import { aggregateRisk, isV22Envelope } from '../types.js';
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Repair Pass (v2.2)
|
|
8
|
+
// =============================================================================
|
|
9
|
+
/**
|
|
10
|
+
* Attempt to repair envelope format issues without changing semantics.
|
|
11
|
+
*
|
|
12
|
+
* Repairs (lossless only):
|
|
13
|
+
* - Missing meta fields (fill with conservative defaults)
|
|
14
|
+
* - Truncate explain if too long
|
|
15
|
+
* - Trim whitespace from string fields
|
|
16
|
+
*
|
|
17
|
+
* Does NOT repair:
|
|
18
|
+
* - Invalid enum values (treated as validation failure)
|
|
19
|
+
*/
|
|
20
|
+
function repairEnvelope(response, riskRule = 'max_changes_risk', maxExplainLength = 280) {
|
|
21
|
+
const repaired = { ...response };
|
|
22
|
+
// Ensure meta exists
|
|
23
|
+
if (!repaired.meta || typeof repaired.meta !== 'object') {
|
|
24
|
+
repaired.meta = {};
|
|
25
|
+
}
|
|
26
|
+
const meta = repaired.meta;
|
|
27
|
+
const data = (repaired.data ?? {});
|
|
28
|
+
// Repair confidence
|
|
29
|
+
if (typeof meta.confidence !== 'number') {
|
|
30
|
+
meta.confidence = data.confidence ?? 0.5;
|
|
31
|
+
}
|
|
32
|
+
meta.confidence = Math.max(0, Math.min(1, meta.confidence));
|
|
33
|
+
// Repair risk using configurable aggregation rule
|
|
34
|
+
if (!meta.risk) {
|
|
35
|
+
meta.risk = aggregateRisk(data, riskRule);
|
|
36
|
+
}
|
|
37
|
+
// Trim whitespace only (lossless), do NOT invent new values
|
|
38
|
+
if (typeof meta.risk === 'string') {
|
|
39
|
+
meta.risk = meta.risk.trim().toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
// Repair explain
|
|
42
|
+
if (typeof meta.explain !== 'string') {
|
|
43
|
+
const rationale = data.rationale;
|
|
44
|
+
meta.explain = rationale ? String(rationale).slice(0, maxExplainLength) : 'No explanation provided';
|
|
45
|
+
}
|
|
46
|
+
// Trim whitespace (lossless)
|
|
47
|
+
const explainStr = meta.explain;
|
|
48
|
+
meta.explain = explainStr.trim();
|
|
49
|
+
if (meta.explain.length > maxExplainLength) {
|
|
50
|
+
meta.explain = meta.explain.slice(0, maxExplainLength - 3) + '...';
|
|
51
|
+
}
|
|
52
|
+
// Build proper v2.2 response
|
|
53
|
+
const builtMeta = {
|
|
54
|
+
confidence: meta.confidence,
|
|
55
|
+
risk: meta.risk,
|
|
56
|
+
explain: meta.explain
|
|
57
|
+
};
|
|
58
|
+
const result = repaired.ok === false ? {
|
|
59
|
+
ok: false,
|
|
60
|
+
meta: builtMeta,
|
|
61
|
+
error: repaired.error ?? { code: 'UNKNOWN', message: 'Unknown error' },
|
|
62
|
+
partial_data: repaired.partial_data
|
|
63
|
+
} : {
|
|
64
|
+
ok: true,
|
|
65
|
+
meta: builtMeta,
|
|
66
|
+
data: repaired.data
|
|
67
|
+
};
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Wrap v2.1 response to v2.2 format
|
|
72
|
+
*/
|
|
73
|
+
function wrapV21ToV22(response, riskRule = 'max_changes_risk') {
|
|
74
|
+
if (isV22Envelope(response)) {
|
|
75
|
+
return response;
|
|
76
|
+
}
|
|
77
|
+
if (response.ok) {
|
|
78
|
+
const data = (response.data ?? {});
|
|
79
|
+
const confidence = data.confidence ?? 0.5;
|
|
80
|
+
const rationale = data.rationale ?? '';
|
|
81
|
+
return {
|
|
82
|
+
ok: true,
|
|
83
|
+
meta: {
|
|
84
|
+
confidence,
|
|
85
|
+
risk: aggregateRisk(data, riskRule),
|
|
86
|
+
explain: rationale.slice(0, 280) || 'No explanation provided'
|
|
87
|
+
},
|
|
88
|
+
data: data
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const errorMsg = response.error?.message ?? 'Unknown error';
|
|
93
|
+
return {
|
|
94
|
+
ok: false,
|
|
95
|
+
meta: {
|
|
96
|
+
confidence: 0,
|
|
97
|
+
risk: 'high',
|
|
98
|
+
explain: errorMsg.slice(0, 280)
|
|
99
|
+
},
|
|
100
|
+
error: response.error ?? { code: 'UNKNOWN', message: errorMsg },
|
|
101
|
+
partial_data: response.partial_data
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
}
|
|
5
105
|
export async function runModule(module, provider, options = {}) {
|
|
6
|
-
const { args, input, verbose = false, useEnvelope } = options;
|
|
106
|
+
const { args, input, verbose = false, useEnvelope, useV22, enableRepair = true } = options;
|
|
7
107
|
// Determine if we should use envelope format
|
|
8
108
|
const shouldUseEnvelope = useEnvelope ?? (module.output?.envelope === true || module.format === 'v2');
|
|
109
|
+
// Determine if we should use v2.2 format
|
|
110
|
+
const isV22Module = module.tier !== undefined || module.formatVersion === 'v2.2';
|
|
111
|
+
const shouldUseV22 = useV22 ?? (isV22Module || module.compat?.runtime_auto_wrap === true);
|
|
112
|
+
// Get risk_rule from module config
|
|
113
|
+
const riskRule = module.metaConfig?.risk_rule ?? 'max_changes_risk';
|
|
9
114
|
// Build clean input data (v2 style: no $ARGUMENTS pollution)
|
|
10
115
|
const inputData = input || {};
|
|
11
116
|
// Map legacy --args to clean input
|
|
@@ -61,11 +166,21 @@ export async function runModule(module, provider, options = {}) {
|
|
|
61
166
|
}
|
|
62
167
|
// Add envelope format instructions
|
|
63
168
|
if (shouldUseEnvelope) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
169
|
+
if (shouldUseV22) {
|
|
170
|
+
systemParts.push('', 'RESPONSE FORMAT (Envelope v2.2):');
|
|
171
|
+
systemParts.push('- Wrap your response in the v2.2 envelope format with separate meta and data');
|
|
172
|
+
systemParts.push('- Success: { "ok": true, "meta": { "confidence": 0.9, "risk": "low", "explain": "short summary" }, "data": { ...payload... } }');
|
|
173
|
+
systemParts.push('- Error: { "ok": false, "meta": { "confidence": 0.0, "risk": "high", "explain": "error summary" }, "error": { "code": "ERROR_CODE", "message": "..." } }');
|
|
174
|
+
systemParts.push('- meta.explain must be ≤280 characters. data.rationale can be longer for detailed reasoning.');
|
|
175
|
+
systemParts.push('- meta.risk must be one of: "none", "low", "medium", "high"');
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
systemParts.push('', 'RESPONSE FORMAT (Envelope):');
|
|
179
|
+
systemParts.push('- Wrap your response in the envelope format');
|
|
180
|
+
systemParts.push('- Success: { "ok": true, "data": { ...your output... } }');
|
|
181
|
+
systemParts.push('- Error: { "ok": false, "error": { "code": "ERROR_CODE", "message": "..." } }');
|
|
182
|
+
systemParts.push('- Include "confidence" (0-1) and "rationale" in data');
|
|
183
|
+
}
|
|
69
184
|
if (module.output?.require_behavior_equivalence) {
|
|
70
185
|
systemParts.push('- Include "behavior_equivalence" (boolean) in data');
|
|
71
186
|
}
|
|
@@ -105,10 +220,46 @@ export async function runModule(module, provider, options = {}) {
|
|
|
105
220
|
}
|
|
106
221
|
// Handle envelope format
|
|
107
222
|
if (shouldUseEnvelope && isEnvelopeResponse(parsed)) {
|
|
108
|
-
|
|
223
|
+
let response = parseEnvelopeResponse(parsed, result.content);
|
|
224
|
+
// Upgrade to v2.2 if needed
|
|
225
|
+
if (shouldUseV22 && response.ok && !('meta' in response && response.meta)) {
|
|
226
|
+
const upgraded = wrapV21ToV22(parsed, riskRule);
|
|
227
|
+
response = {
|
|
228
|
+
ok: true,
|
|
229
|
+
meta: upgraded.meta,
|
|
230
|
+
data: upgraded.data,
|
|
231
|
+
raw: result.content
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
// Apply repair pass if enabled and response needs it
|
|
235
|
+
if (enableRepair && response.ok && shouldUseV22) {
|
|
236
|
+
const repaired = repairEnvelope(response, riskRule);
|
|
237
|
+
response = {
|
|
238
|
+
ok: true,
|
|
239
|
+
meta: repaired.meta,
|
|
240
|
+
data: repaired.data,
|
|
241
|
+
raw: result.content
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
return response;
|
|
109
245
|
}
|
|
110
246
|
// Handle legacy format (non-envelope)
|
|
111
|
-
|
|
247
|
+
const legacyResult = parseLegacyResponse(parsed, result.content);
|
|
248
|
+
// Upgrade to v2.2 if requested
|
|
249
|
+
if (shouldUseV22 && legacyResult.ok) {
|
|
250
|
+
const data = (legacyResult.data ?? {});
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
meta: {
|
|
254
|
+
confidence: data.confidence ?? 0.5,
|
|
255
|
+
risk: aggregateRisk(data, riskRule),
|
|
256
|
+
explain: (data.rationale ?? '').slice(0, 280) || 'No explanation provided'
|
|
257
|
+
},
|
|
258
|
+
data: legacyResult.data,
|
|
259
|
+
raw: result.content
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
return legacyResult;
|
|
112
263
|
}
|
|
113
264
|
/**
|
|
114
265
|
* Check if response is in envelope format
|
|
@@ -120,11 +271,32 @@ function isEnvelopeResponse(obj) {
|
|
|
120
271
|
return typeof o.ok === 'boolean';
|
|
121
272
|
}
|
|
122
273
|
/**
|
|
123
|
-
* Parse envelope format response
|
|
274
|
+
* Parse envelope format response (supports both v2.1 and v2.2)
|
|
124
275
|
*/
|
|
125
276
|
function parseEnvelopeResponse(response, raw) {
|
|
277
|
+
// Check if v2.2 format (has meta)
|
|
278
|
+
if (isV22Envelope(response)) {
|
|
279
|
+
if (response.ok) {
|
|
280
|
+
return {
|
|
281
|
+
ok: true,
|
|
282
|
+
meta: response.meta,
|
|
283
|
+
data: response.data,
|
|
284
|
+
raw,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
meta: response.meta,
|
|
291
|
+
error: response.error,
|
|
292
|
+
partial_data: response.partial_data,
|
|
293
|
+
raw,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// v2.1 format
|
|
126
298
|
if (response.ok) {
|
|
127
|
-
const data = response.data;
|
|
299
|
+
const data = (response.data ?? {});
|
|
128
300
|
return {
|
|
129
301
|
ok: true,
|
|
130
302
|
data: {
|
package/dist/types.d.ts
CHANGED
|
@@ -33,6 +33,8 @@ export type SchemaStrictness = 'high' | 'medium' | 'low';
|
|
|
33
33
|
export type RiskLevel = 'none' | 'low' | 'medium' | 'high';
|
|
34
34
|
/** Enum extension strategy */
|
|
35
35
|
export type EnumStrategy = 'strict' | 'extensible';
|
|
36
|
+
/** Risk aggregation rule */
|
|
37
|
+
export type RiskRule = 'max_changes_risk' | 'max_issues_risk' | 'explicit';
|
|
36
38
|
export interface CognitiveModule {
|
|
37
39
|
name: string;
|
|
38
40
|
version: string;
|
|
@@ -49,6 +51,7 @@ export interface CognitiveModule {
|
|
|
49
51
|
overflow?: OverflowConfig;
|
|
50
52
|
enums?: EnumConfig;
|
|
51
53
|
compat?: CompatConfig;
|
|
54
|
+
metaConfig?: MetaConfig;
|
|
52
55
|
context?: 'fork' | 'main';
|
|
53
56
|
prompt: string;
|
|
54
57
|
inputSchema?: object;
|
|
@@ -115,6 +118,18 @@ export interface CompatConfig {
|
|
|
115
118
|
runtime_auto_wrap?: boolean;
|
|
116
119
|
schema_output_alias?: 'data' | 'output';
|
|
117
120
|
}
|
|
121
|
+
/** Meta field configuration (v2.2) */
|
|
122
|
+
export interface MetaConfig {
|
|
123
|
+
required?: string[];
|
|
124
|
+
risk_rule?: RiskRule;
|
|
125
|
+
confidence?: {
|
|
126
|
+
min?: number;
|
|
127
|
+
max?: number;
|
|
128
|
+
};
|
|
129
|
+
explain?: {
|
|
130
|
+
max_chars?: number;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
118
133
|
/**
|
|
119
134
|
* Control plane metadata - unified across all modules.
|
|
120
135
|
* Used for routing, logging, UI cards, and middleware decisions.
|
|
@@ -255,10 +270,15 @@ export declare function isV22Envelope<T>(response: EnvelopeResponse<T>): respons
|
|
|
255
270
|
/** Check if response is successful */
|
|
256
271
|
export declare function isEnvelopeSuccess<T>(response: EnvelopeResponse<T>): response is EnvelopeSuccessV22<T> | EnvelopeSuccessV21<T>;
|
|
257
272
|
/** Extract meta from any envelope response */
|
|
258
|
-
export declare function extractMeta<T>(response: EnvelopeResponse<T
|
|
259
|
-
/**
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
273
|
+
export declare function extractMeta<T>(response: EnvelopeResponse<T>, riskRule?: RiskRule): EnvelopeMeta;
|
|
274
|
+
/**
|
|
275
|
+
* Aggregate risk based on configured rule.
|
|
276
|
+
*
|
|
277
|
+
* Rules:
|
|
278
|
+
* - max_changes_risk: max(data.changes[*].risk) - default
|
|
279
|
+
* - max_issues_risk: max(data.issues[*].risk) - for review modules
|
|
280
|
+
* - explicit: return "medium", module should set risk explicitly
|
|
281
|
+
*/
|
|
282
|
+
export declare function aggregateRisk(data: Record<string, unknown>, riskRule?: RiskRule): RiskLevel;
|
|
263
283
|
/** Check if result should be escalated to human review */
|
|
264
284
|
export declare function shouldEscalate<T>(response: EnvelopeResponse<T>, confidenceThreshold?: number): boolean;
|
package/dist/types.js
CHANGED
|
@@ -14,17 +14,17 @@ export function isEnvelopeSuccess(response) {
|
|
|
14
14
|
return response.ok === true;
|
|
15
15
|
}
|
|
16
16
|
/** Extract meta from any envelope response */
|
|
17
|
-
export function extractMeta(response) {
|
|
17
|
+
export function extractMeta(response, riskRule = 'max_changes_risk') {
|
|
18
18
|
if (isV22Envelope(response)) {
|
|
19
19
|
return response.meta;
|
|
20
20
|
}
|
|
21
21
|
// Synthesize meta from v2.1 response
|
|
22
22
|
if (response.ok) {
|
|
23
|
-
const data = response.data;
|
|
23
|
+
const data = (response.data ?? {});
|
|
24
24
|
return {
|
|
25
|
-
confidence: data
|
|
26
|
-
risk:
|
|
27
|
-
explain: (data
|
|
25
|
+
confidence: data.confidence ?? 0.5,
|
|
26
|
+
risk: aggregateRisk(data, riskRule),
|
|
27
|
+
explain: (data.rationale ?? '').slice(0, 280) || 'No explanation',
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
@@ -35,20 +35,44 @@ export function extractMeta(response) {
|
|
|
35
35
|
};
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
/** Aggregate risk from list of
|
|
39
|
-
|
|
38
|
+
/** Aggregate risk from list of items */
|
|
39
|
+
function aggregateRiskFromList(items) {
|
|
40
40
|
const riskLevels = { none: 0, low: 1, medium: 2, high: 3 };
|
|
41
41
|
const riskNames = ['none', 'low', 'medium', 'high'];
|
|
42
|
-
if (!
|
|
42
|
+
if (!items || items.length === 0) {
|
|
43
43
|
return 'medium';
|
|
44
44
|
}
|
|
45
45
|
let maxLevel = 0;
|
|
46
|
-
for (const
|
|
47
|
-
const level = riskLevels[
|
|
46
|
+
for (const item of items) {
|
|
47
|
+
const level = riskLevels[item.risk ?? 'medium'];
|
|
48
48
|
maxLevel = Math.max(maxLevel, level);
|
|
49
49
|
}
|
|
50
50
|
return riskNames[maxLevel];
|
|
51
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Aggregate risk based on configured rule.
|
|
54
|
+
*
|
|
55
|
+
* Rules:
|
|
56
|
+
* - max_changes_risk: max(data.changes[*].risk) - default
|
|
57
|
+
* - max_issues_risk: max(data.issues[*].risk) - for review modules
|
|
58
|
+
* - explicit: return "medium", module should set risk explicitly
|
|
59
|
+
*/
|
|
60
|
+
export function aggregateRisk(data, riskRule = 'max_changes_risk') {
|
|
61
|
+
if (riskRule === 'max_changes_risk') {
|
|
62
|
+
const changes = data.changes ?? [];
|
|
63
|
+
return aggregateRiskFromList(changes);
|
|
64
|
+
}
|
|
65
|
+
else if (riskRule === 'max_issues_risk') {
|
|
66
|
+
const issues = data.issues ?? [];
|
|
67
|
+
return aggregateRiskFromList(issues);
|
|
68
|
+
}
|
|
69
|
+
else if (riskRule === 'explicit') {
|
|
70
|
+
return 'medium'; // Module should override
|
|
71
|
+
}
|
|
72
|
+
// Fallback to changes
|
|
73
|
+
const changes = data.changes ?? [];
|
|
74
|
+
return aggregateRiskFromList(changes);
|
|
75
|
+
}
|
|
52
76
|
/** Check if result should be escalated to human review */
|
|
53
77
|
export function shouldEscalate(response, confidenceThreshold = 0.7) {
|
|
54
78
|
const meta = extractMeta(response);
|
package/package.json
CHANGED
package/src/modules/loader.ts
CHANGED
|
@@ -6,7 +6,21 @@
|
|
|
6
6
|
import * as fs from 'node:fs/promises';
|
|
7
7
|
import * as path from 'node:path';
|
|
8
8
|
import yaml from 'js-yaml';
|
|
9
|
-
import type {
|
|
9
|
+
import type {
|
|
10
|
+
CognitiveModule,
|
|
11
|
+
ModuleConstraints,
|
|
12
|
+
ModulePolicies,
|
|
13
|
+
ToolsPolicy,
|
|
14
|
+
OutputContract,
|
|
15
|
+
FailureContract,
|
|
16
|
+
RuntimeRequirements,
|
|
17
|
+
OverflowConfig,
|
|
18
|
+
EnumConfig,
|
|
19
|
+
CompatConfig,
|
|
20
|
+
MetaConfig,
|
|
21
|
+
ModuleTier,
|
|
22
|
+
SchemaStrictness
|
|
23
|
+
} from '../types.js';
|
|
10
24
|
|
|
11
25
|
const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/;
|
|
12
26
|
|
|
@@ -23,6 +37,19 @@ async function detectFormat(modulePath: string): Promise<'v1' | 'v2'> {
|
|
|
23
37
|
}
|
|
24
38
|
}
|
|
25
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Detect v2.x sub-version from manifest
|
|
42
|
+
*/
|
|
43
|
+
function detectV2Version(manifest: Record<string, unknown>): string {
|
|
44
|
+
if (manifest.tier || manifest.overflow || manifest.enums) {
|
|
45
|
+
return 'v2.2';
|
|
46
|
+
}
|
|
47
|
+
if (manifest.policies || manifest.failure) {
|
|
48
|
+
return 'v2.1';
|
|
49
|
+
}
|
|
50
|
+
return 'v2.0';
|
|
51
|
+
}
|
|
52
|
+
|
|
26
53
|
/**
|
|
27
54
|
* Load v2 format module (module.yaml + prompt.md)
|
|
28
55
|
*/
|
|
@@ -35,6 +62,9 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
|
|
|
35
62
|
const manifestContent = await fs.readFile(manifestFile, 'utf-8');
|
|
36
63
|
const manifest = yaml.load(manifestContent) as Record<string, unknown>;
|
|
37
64
|
|
|
65
|
+
// Detect v2.x version
|
|
66
|
+
const formatVersion = detectV2Version(manifest);
|
|
67
|
+
|
|
38
68
|
// Read prompt.md
|
|
39
69
|
let prompt = '';
|
|
40
70
|
try {
|
|
@@ -46,18 +76,68 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
|
|
|
46
76
|
// Read schema.json
|
|
47
77
|
let inputSchema: object | undefined;
|
|
48
78
|
let outputSchema: object | undefined;
|
|
79
|
+
let dataSchema: object | undefined;
|
|
80
|
+
let metaSchema: object | undefined;
|
|
49
81
|
let errorSchema: object | undefined;
|
|
50
82
|
|
|
51
83
|
try {
|
|
52
84
|
const schemaContent = await fs.readFile(schemaFile, 'utf-8');
|
|
53
85
|
const schema = JSON.parse(schemaContent);
|
|
54
86
|
inputSchema = schema.input;
|
|
55
|
-
|
|
87
|
+
// Support both "data" (v2.2) and "output" (v2.1) aliases
|
|
88
|
+
dataSchema = schema.data || schema.output;
|
|
89
|
+
outputSchema = dataSchema; // Backward compat
|
|
90
|
+
metaSchema = schema.meta;
|
|
56
91
|
errorSchema = schema.error;
|
|
57
92
|
} catch {
|
|
58
93
|
// Schema file is optional but recommended
|
|
59
94
|
}
|
|
60
95
|
|
|
96
|
+
// Extract v2.2 fields
|
|
97
|
+
const tier = manifest.tier as ModuleTier | undefined;
|
|
98
|
+
const schemaStrictness = (manifest.schema_strictness as SchemaStrictness) || 'medium';
|
|
99
|
+
|
|
100
|
+
// Determine default max_items based on strictness (SPEC-v2.2)
|
|
101
|
+
const strictnessMaxItems: Record<SchemaStrictness, number> = {
|
|
102
|
+
high: 0,
|
|
103
|
+
medium: 5,
|
|
104
|
+
low: 20
|
|
105
|
+
};
|
|
106
|
+
const defaultMaxItems = strictnessMaxItems[schemaStrictness] ?? 5;
|
|
107
|
+
const defaultEnabled = schemaStrictness !== 'high';
|
|
108
|
+
|
|
109
|
+
// Parse overflow config with strictness-based defaults
|
|
110
|
+
const overflowRaw = (manifest.overflow as Record<string, unknown>) || {};
|
|
111
|
+
const overflow: OverflowConfig = {
|
|
112
|
+
enabled: (overflowRaw.enabled as boolean) ?? defaultEnabled,
|
|
113
|
+
recoverable: (overflowRaw.recoverable as boolean) ?? true,
|
|
114
|
+
max_items: (overflowRaw.max_items as number) ?? defaultMaxItems,
|
|
115
|
+
require_suggested_mapping: (overflowRaw.require_suggested_mapping as boolean) ?? true
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Parse enums config
|
|
119
|
+
const enumsRaw = (manifest.enums as Record<string, unknown>) || {};
|
|
120
|
+
const enums: EnumConfig = {
|
|
121
|
+
strategy: (enumsRaw.strategy as 'strict' | 'extensible') ??
|
|
122
|
+
(tier === 'exec' ? 'strict' : 'extensible'),
|
|
123
|
+
unknown_tag: (enumsRaw.unknown_tag as string) ?? 'custom'
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Parse compat config
|
|
127
|
+
const compatRaw = (manifest.compat as Record<string, unknown>) || {};
|
|
128
|
+
const compat: CompatConfig = {
|
|
129
|
+
accepts_v21_payload: (compatRaw.accepts_v21_payload as boolean) ?? true,
|
|
130
|
+
runtime_auto_wrap: (compatRaw.runtime_auto_wrap as boolean) ?? true,
|
|
131
|
+
schema_output_alias: (compatRaw.schema_output_alias as 'data' | 'output') ?? 'data'
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Parse meta config (including risk_rule)
|
|
135
|
+
const metaRaw = (manifest.meta as Record<string, unknown>) || {};
|
|
136
|
+
const metaConfig: MetaConfig = {
|
|
137
|
+
required: metaRaw.required as string[] | undefined,
|
|
138
|
+
risk_rule: metaRaw.risk_rule as 'max_changes_risk' | 'max_issues_risk' | 'explicit' | undefined,
|
|
139
|
+
};
|
|
140
|
+
|
|
61
141
|
return {
|
|
62
142
|
name: manifest.name as string || path.basename(modulePath),
|
|
63
143
|
version: manifest.version as string || '1.0.0',
|
|
@@ -69,13 +149,26 @@ async function loadModuleV2(modulePath: string): Promise<CognitiveModule> {
|
|
|
69
149
|
output: manifest.output as OutputContract | undefined,
|
|
70
150
|
failure: manifest.failure as FailureContract | undefined,
|
|
71
151
|
runtimeRequirements: manifest.runtime_requirements as RuntimeRequirements | undefined,
|
|
152
|
+
// v2.2 fields
|
|
153
|
+
tier,
|
|
154
|
+
schemaStrictness,
|
|
155
|
+
overflow,
|
|
156
|
+
enums,
|
|
157
|
+
compat,
|
|
158
|
+
metaConfig,
|
|
159
|
+
// Context and prompt
|
|
72
160
|
context: manifest.context as 'fork' | 'main' | undefined,
|
|
73
161
|
prompt,
|
|
162
|
+
// Schemas
|
|
74
163
|
inputSchema,
|
|
75
164
|
outputSchema,
|
|
165
|
+
dataSchema,
|
|
166
|
+
metaSchema,
|
|
76
167
|
errorSchema,
|
|
168
|
+
// Metadata
|
|
77
169
|
location: modulePath,
|
|
78
170
|
format: 'v2',
|
|
171
|
+
formatVersion,
|
|
79
172
|
};
|
|
80
173
|
}
|
|
81
174
|
|
package/src/modules/runner.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Module Runner - Execute Cognitive Modules
|
|
3
|
-
* v2.
|
|
3
|
+
* v2.2: Envelope format with meta/data separation, risk_rule, repair pass
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type {
|
|
@@ -8,11 +8,17 @@ import type {
|
|
|
8
8
|
CognitiveModule,
|
|
9
9
|
ModuleResult,
|
|
10
10
|
ModuleResultV21,
|
|
11
|
+
ModuleResultV22,
|
|
11
12
|
Message,
|
|
12
13
|
ModuleInput,
|
|
13
14
|
EnvelopeResponse,
|
|
14
|
-
|
|
15
|
+
EnvelopeResponseV22,
|
|
16
|
+
EnvelopeMeta,
|
|
17
|
+
ModuleResultData,
|
|
18
|
+
RiskLevel,
|
|
19
|
+
RiskRule
|
|
15
20
|
} from '../types.js';
|
|
21
|
+
import { aggregateRisk, isV22Envelope } from '../types.js';
|
|
16
22
|
|
|
17
23
|
export interface RunOptions {
|
|
18
24
|
// Clean input (v2 style)
|
|
@@ -26,6 +32,130 @@ export interface RunOptions {
|
|
|
26
32
|
|
|
27
33
|
// Force envelope format (default: auto-detect from module.output.envelope)
|
|
28
34
|
useEnvelope?: boolean;
|
|
35
|
+
|
|
36
|
+
// Force v2.2 format (default: auto-detect from module.tier)
|
|
37
|
+
useV22?: boolean;
|
|
38
|
+
|
|
39
|
+
// Enable repair pass for validation failures (default: true)
|
|
40
|
+
enableRepair?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Repair Pass (v2.2)
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Attempt to repair envelope format issues without changing semantics.
|
|
49
|
+
*
|
|
50
|
+
* Repairs (lossless only):
|
|
51
|
+
* - Missing meta fields (fill with conservative defaults)
|
|
52
|
+
* - Truncate explain if too long
|
|
53
|
+
* - Trim whitespace from string fields
|
|
54
|
+
*
|
|
55
|
+
* Does NOT repair:
|
|
56
|
+
* - Invalid enum values (treated as validation failure)
|
|
57
|
+
*/
|
|
58
|
+
function repairEnvelope(
|
|
59
|
+
response: Record<string, unknown>,
|
|
60
|
+
riskRule: RiskRule = 'max_changes_risk',
|
|
61
|
+
maxExplainLength: number = 280
|
|
62
|
+
): EnvelopeResponseV22<unknown> {
|
|
63
|
+
const repaired = { ...response };
|
|
64
|
+
|
|
65
|
+
// Ensure meta exists
|
|
66
|
+
if (!repaired.meta || typeof repaired.meta !== 'object') {
|
|
67
|
+
repaired.meta = {};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const meta = repaired.meta as Record<string, unknown>;
|
|
71
|
+
const data = (repaired.data ?? {}) as Record<string, unknown>;
|
|
72
|
+
|
|
73
|
+
// Repair confidence
|
|
74
|
+
if (typeof meta.confidence !== 'number') {
|
|
75
|
+
meta.confidence = (data.confidence as number) ?? 0.5;
|
|
76
|
+
}
|
|
77
|
+
meta.confidence = Math.max(0, Math.min(1, meta.confidence as number));
|
|
78
|
+
|
|
79
|
+
// Repair risk using configurable aggregation rule
|
|
80
|
+
if (!meta.risk) {
|
|
81
|
+
meta.risk = aggregateRisk(data, riskRule);
|
|
82
|
+
}
|
|
83
|
+
// Trim whitespace only (lossless), do NOT invent new values
|
|
84
|
+
if (typeof meta.risk === 'string') {
|
|
85
|
+
meta.risk = meta.risk.trim().toLowerCase();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Repair explain
|
|
89
|
+
if (typeof meta.explain !== 'string') {
|
|
90
|
+
const rationale = data.rationale as string | undefined;
|
|
91
|
+
meta.explain = rationale ? String(rationale).slice(0, maxExplainLength) : 'No explanation provided';
|
|
92
|
+
}
|
|
93
|
+
// Trim whitespace (lossless)
|
|
94
|
+
const explainStr = meta.explain as string;
|
|
95
|
+
meta.explain = explainStr.trim();
|
|
96
|
+
if ((meta.explain as string).length > maxExplainLength) {
|
|
97
|
+
meta.explain = (meta.explain as string).slice(0, maxExplainLength - 3) + '...';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Build proper v2.2 response
|
|
101
|
+
const builtMeta: EnvelopeMeta = {
|
|
102
|
+
confidence: meta.confidence as number,
|
|
103
|
+
risk: meta.risk as RiskLevel,
|
|
104
|
+
explain: meta.explain as string
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const result: EnvelopeResponseV22<unknown> = repaired.ok === false ? {
|
|
108
|
+
ok: false,
|
|
109
|
+
meta: builtMeta,
|
|
110
|
+
error: (repaired.error as { code: string; message: string }) ?? { code: 'UNKNOWN', message: 'Unknown error' },
|
|
111
|
+
partial_data: repaired.partial_data
|
|
112
|
+
} : {
|
|
113
|
+
ok: true,
|
|
114
|
+
meta: builtMeta,
|
|
115
|
+
data: repaired.data
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Wrap v2.1 response to v2.2 format
|
|
123
|
+
*/
|
|
124
|
+
function wrapV21ToV22(
|
|
125
|
+
response: EnvelopeResponse<unknown>,
|
|
126
|
+
riskRule: RiskRule = 'max_changes_risk'
|
|
127
|
+
): EnvelopeResponseV22<unknown> {
|
|
128
|
+
if (isV22Envelope(response)) {
|
|
129
|
+
return response;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (response.ok) {
|
|
133
|
+
const data = (response.data ?? {}) as Record<string, unknown>;
|
|
134
|
+
const confidence = (data.confidence as number) ?? 0.5;
|
|
135
|
+
const rationale = (data.rationale as string) ?? '';
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
meta: {
|
|
140
|
+
confidence,
|
|
141
|
+
risk: aggregateRisk(data, riskRule),
|
|
142
|
+
explain: rationale.slice(0, 280) || 'No explanation provided'
|
|
143
|
+
},
|
|
144
|
+
data: data as ModuleResultData
|
|
145
|
+
};
|
|
146
|
+
} else {
|
|
147
|
+
const errorMsg = response.error?.message ?? 'Unknown error';
|
|
148
|
+
return {
|
|
149
|
+
ok: false,
|
|
150
|
+
meta: {
|
|
151
|
+
confidence: 0,
|
|
152
|
+
risk: 'high',
|
|
153
|
+
explain: errorMsg.slice(0, 280)
|
|
154
|
+
},
|
|
155
|
+
error: response.error ?? { code: 'UNKNOWN', message: errorMsg },
|
|
156
|
+
partial_data: response.partial_data
|
|
157
|
+
};
|
|
158
|
+
}
|
|
29
159
|
}
|
|
30
160
|
|
|
31
161
|
export async function runModule(
|
|
@@ -33,10 +163,17 @@ export async function runModule(
|
|
|
33
163
|
provider: Provider,
|
|
34
164
|
options: RunOptions = {}
|
|
35
165
|
): Promise<ModuleResult> {
|
|
36
|
-
const { args, input, verbose = false, useEnvelope } = options;
|
|
166
|
+
const { args, input, verbose = false, useEnvelope, useV22, enableRepair = true } = options;
|
|
37
167
|
|
|
38
168
|
// Determine if we should use envelope format
|
|
39
169
|
const shouldUseEnvelope = useEnvelope ?? (module.output?.envelope === true || module.format === 'v2');
|
|
170
|
+
|
|
171
|
+
// Determine if we should use v2.2 format
|
|
172
|
+
const isV22Module = module.tier !== undefined || module.formatVersion === 'v2.2';
|
|
173
|
+
const shouldUseV22 = useV22 ?? (isV22Module || module.compat?.runtime_auto_wrap === true);
|
|
174
|
+
|
|
175
|
+
// Get risk_rule from module config
|
|
176
|
+
const riskRule: RiskRule = module.metaConfig?.risk_rule ?? 'max_changes_risk';
|
|
40
177
|
|
|
41
178
|
// Build clean input data (v2 style: no $ARGUMENTS pollution)
|
|
42
179
|
const inputData: ModuleInput = input || {};
|
|
@@ -97,11 +234,20 @@ export async function runModule(
|
|
|
97
234
|
|
|
98
235
|
// Add envelope format instructions
|
|
99
236
|
if (shouldUseEnvelope) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
237
|
+
if (shouldUseV22) {
|
|
238
|
+
systemParts.push('', 'RESPONSE FORMAT (Envelope v2.2):');
|
|
239
|
+
systemParts.push('- Wrap your response in the v2.2 envelope format with separate meta and data');
|
|
240
|
+
systemParts.push('- Success: { "ok": true, "meta": { "confidence": 0.9, "risk": "low", "explain": "short summary" }, "data": { ...payload... } }');
|
|
241
|
+
systemParts.push('- Error: { "ok": false, "meta": { "confidence": 0.0, "risk": "high", "explain": "error summary" }, "error": { "code": "ERROR_CODE", "message": "..." } }');
|
|
242
|
+
systemParts.push('- meta.explain must be ≤280 characters. data.rationale can be longer for detailed reasoning.');
|
|
243
|
+
systemParts.push('- meta.risk must be one of: "none", "low", "medium", "high"');
|
|
244
|
+
} else {
|
|
245
|
+
systemParts.push('', 'RESPONSE FORMAT (Envelope):');
|
|
246
|
+
systemParts.push('- Wrap your response in the envelope format');
|
|
247
|
+
systemParts.push('- Success: { "ok": true, "data": { ...your output... } }');
|
|
248
|
+
systemParts.push('- Error: { "ok": false, "error": { "code": "ERROR_CODE", "message": "..." } }');
|
|
249
|
+
systemParts.push('- Include "confidence" (0-1) and "rationale" in data');
|
|
250
|
+
}
|
|
105
251
|
if (module.output?.require_behavior_equivalence) {
|
|
106
252
|
systemParts.push('- Include "behavior_equivalence" (boolean) in data');
|
|
107
253
|
}
|
|
@@ -144,11 +290,55 @@ export async function runModule(
|
|
|
144
290
|
|
|
145
291
|
// Handle envelope format
|
|
146
292
|
if (shouldUseEnvelope && isEnvelopeResponse(parsed)) {
|
|
147
|
-
|
|
293
|
+
let response = parseEnvelopeResponse(parsed, result.content);
|
|
294
|
+
|
|
295
|
+
// Upgrade to v2.2 if needed
|
|
296
|
+
if (shouldUseV22 && response.ok && !('meta' in response && response.meta)) {
|
|
297
|
+
const upgraded = wrapV21ToV22(parsed as EnvelopeResponse<unknown>, riskRule);
|
|
298
|
+
response = {
|
|
299
|
+
ok: true,
|
|
300
|
+
meta: upgraded.meta as EnvelopeMeta,
|
|
301
|
+
data: (upgraded as { data?: ModuleResultData }).data,
|
|
302
|
+
raw: result.content
|
|
303
|
+
} as ModuleResultV22;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Apply repair pass if enabled and response needs it
|
|
307
|
+
if (enableRepair && response.ok && shouldUseV22) {
|
|
308
|
+
const repaired = repairEnvelope(
|
|
309
|
+
response as unknown as Record<string, unknown>,
|
|
310
|
+
riskRule
|
|
311
|
+
);
|
|
312
|
+
response = {
|
|
313
|
+
ok: true,
|
|
314
|
+
meta: repaired.meta as EnvelopeMeta,
|
|
315
|
+
data: (repaired as { data?: ModuleResultData }).data,
|
|
316
|
+
raw: result.content
|
|
317
|
+
} as ModuleResultV22;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return response;
|
|
148
321
|
}
|
|
149
322
|
|
|
150
323
|
// Handle legacy format (non-envelope)
|
|
151
|
-
|
|
324
|
+
const legacyResult = parseLegacyResponse(parsed, result.content);
|
|
325
|
+
|
|
326
|
+
// Upgrade to v2.2 if requested
|
|
327
|
+
if (shouldUseV22 && legacyResult.ok) {
|
|
328
|
+
const data = (legacyResult.data ?? {}) as Record<string, unknown>;
|
|
329
|
+
return {
|
|
330
|
+
ok: true,
|
|
331
|
+
meta: {
|
|
332
|
+
confidence: (data.confidence as number) ?? 0.5,
|
|
333
|
+
risk: aggregateRisk(data, riskRule),
|
|
334
|
+
explain: ((data.rationale as string) ?? '').slice(0, 280) || 'No explanation provided'
|
|
335
|
+
},
|
|
336
|
+
data: legacyResult.data,
|
|
337
|
+
raw: result.content
|
|
338
|
+
} as ModuleResultV22;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return legacyResult;
|
|
152
342
|
}
|
|
153
343
|
|
|
154
344
|
/**
|
|
@@ -161,11 +351,32 @@ function isEnvelopeResponse(obj: unknown): obj is EnvelopeResponse {
|
|
|
161
351
|
}
|
|
162
352
|
|
|
163
353
|
/**
|
|
164
|
-
* Parse envelope format response
|
|
354
|
+
* Parse envelope format response (supports both v2.1 and v2.2)
|
|
165
355
|
*/
|
|
166
|
-
function parseEnvelopeResponse(response: EnvelopeResponse
|
|
356
|
+
function parseEnvelopeResponse(response: EnvelopeResponse<unknown>, raw: string): ModuleResult {
|
|
357
|
+
// Check if v2.2 format (has meta)
|
|
358
|
+
if (isV22Envelope(response)) {
|
|
359
|
+
if (response.ok) {
|
|
360
|
+
return {
|
|
361
|
+
ok: true,
|
|
362
|
+
meta: response.meta,
|
|
363
|
+
data: response.data as ModuleResultData,
|
|
364
|
+
raw,
|
|
365
|
+
} as ModuleResultV22;
|
|
366
|
+
} else {
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
meta: response.meta,
|
|
370
|
+
error: response.error,
|
|
371
|
+
partial_data: response.partial_data,
|
|
372
|
+
raw,
|
|
373
|
+
} as ModuleResultV22;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// v2.1 format
|
|
167
378
|
if (response.ok) {
|
|
168
|
-
const data = response.data as ModuleResultData;
|
|
379
|
+
const data = (response.data ?? {}) as ModuleResultData & { confidence?: number };
|
|
169
380
|
return {
|
|
170
381
|
ok: true,
|
|
171
382
|
data: {
|
|
@@ -175,14 +386,14 @@ function parseEnvelopeResponse(response: EnvelopeResponse, raw: string): ModuleR
|
|
|
175
386
|
behavior_equivalence: data.behavior_equivalence,
|
|
176
387
|
},
|
|
177
388
|
raw,
|
|
178
|
-
};
|
|
389
|
+
} as ModuleResultV21;
|
|
179
390
|
} else {
|
|
180
391
|
return {
|
|
181
392
|
ok: false,
|
|
182
393
|
error: response.error,
|
|
183
394
|
partial_data: response.partial_data,
|
|
184
395
|
raw,
|
|
185
|
-
};
|
|
396
|
+
} as ModuleResultV21;
|
|
186
397
|
}
|
|
187
398
|
}
|
|
188
399
|
|
package/src/types.ts
CHANGED
|
@@ -50,6 +50,9 @@ export type RiskLevel = 'none' | 'low' | 'medium' | 'high';
|
|
|
50
50
|
/** Enum extension strategy */
|
|
51
51
|
export type EnumStrategy = 'strict' | 'extensible';
|
|
52
52
|
|
|
53
|
+
/** Risk aggregation rule */
|
|
54
|
+
export type RiskRule = 'max_changes_risk' | 'max_issues_risk' | 'explicit';
|
|
55
|
+
|
|
53
56
|
// =============================================================================
|
|
54
57
|
// Module Configuration (v2.2)
|
|
55
58
|
// =============================================================================
|
|
@@ -94,6 +97,9 @@ export interface CognitiveModule {
|
|
|
94
97
|
// v2.2: Compatibility configuration
|
|
95
98
|
compat?: CompatConfig;
|
|
96
99
|
|
|
100
|
+
// v2.2: Meta configuration (including risk_rule)
|
|
101
|
+
metaConfig?: MetaConfig;
|
|
102
|
+
|
|
97
103
|
// Execution context
|
|
98
104
|
context?: 'fork' | 'main';
|
|
99
105
|
|
|
@@ -181,6 +187,14 @@ export interface CompatConfig {
|
|
|
181
187
|
schema_output_alias?: 'data' | 'output';
|
|
182
188
|
}
|
|
183
189
|
|
|
190
|
+
/** Meta field configuration (v2.2) */
|
|
191
|
+
export interface MetaConfig {
|
|
192
|
+
required?: string[];
|
|
193
|
+
risk_rule?: RiskRule;
|
|
194
|
+
confidence?: { min?: number; max?: number };
|
|
195
|
+
explain?: { max_chars?: number };
|
|
196
|
+
}
|
|
197
|
+
|
|
184
198
|
// =============================================================================
|
|
185
199
|
// Envelope Types (v2.2)
|
|
186
200
|
// =============================================================================
|
|
@@ -386,18 +400,21 @@ export function isEnvelopeSuccess<T>(
|
|
|
386
400
|
}
|
|
387
401
|
|
|
388
402
|
/** Extract meta from any envelope response */
|
|
389
|
-
export function extractMeta<T>(
|
|
403
|
+
export function extractMeta<T>(
|
|
404
|
+
response: EnvelopeResponse<T>,
|
|
405
|
+
riskRule: RiskRule = 'max_changes_risk'
|
|
406
|
+
): EnvelopeMeta {
|
|
390
407
|
if (isV22Envelope(response)) {
|
|
391
408
|
return response.meta;
|
|
392
409
|
}
|
|
393
410
|
|
|
394
411
|
// Synthesize meta from v2.1 response
|
|
395
412
|
if (response.ok) {
|
|
396
|
-
const data = response.data as Record<string, unknown>;
|
|
413
|
+
const data = (response.data ?? {}) as Record<string, unknown>;
|
|
397
414
|
return {
|
|
398
|
-
confidence: (data
|
|
399
|
-
risk:
|
|
400
|
-
explain: ((data
|
|
415
|
+
confidence: (data.confidence as number) ?? 0.5,
|
|
416
|
+
risk: aggregateRisk(data, riskRule),
|
|
417
|
+
explain: ((data.rationale as string) ?? '').slice(0, 280) || 'No explanation',
|
|
401
418
|
};
|
|
402
419
|
} else {
|
|
403
420
|
return {
|
|
@@ -408,24 +425,50 @@ export function extractMeta<T>(response: EnvelopeResponse<T>): EnvelopeMeta {
|
|
|
408
425
|
}
|
|
409
426
|
}
|
|
410
427
|
|
|
411
|
-
/** Aggregate risk from list of
|
|
412
|
-
|
|
428
|
+
/** Aggregate risk from list of items */
|
|
429
|
+
function aggregateRiskFromList(items: Array<{ risk?: RiskLevel }>): RiskLevel {
|
|
413
430
|
const riskLevels: Record<RiskLevel, number> = { none: 0, low: 1, medium: 2, high: 3 };
|
|
414
431
|
const riskNames: RiskLevel[] = ['none', 'low', 'medium', 'high'];
|
|
415
432
|
|
|
416
|
-
if (!
|
|
433
|
+
if (!items || items.length === 0) {
|
|
417
434
|
return 'medium';
|
|
418
435
|
}
|
|
419
436
|
|
|
420
437
|
let maxLevel = 0;
|
|
421
|
-
for (const
|
|
422
|
-
const level = riskLevels[
|
|
438
|
+
for (const item of items) {
|
|
439
|
+
const level = riskLevels[item.risk ?? 'medium'];
|
|
423
440
|
maxLevel = Math.max(maxLevel, level);
|
|
424
441
|
}
|
|
425
442
|
|
|
426
443
|
return riskNames[maxLevel];
|
|
427
444
|
}
|
|
428
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Aggregate risk based on configured rule.
|
|
448
|
+
*
|
|
449
|
+
* Rules:
|
|
450
|
+
* - max_changes_risk: max(data.changes[*].risk) - default
|
|
451
|
+
* - max_issues_risk: max(data.issues[*].risk) - for review modules
|
|
452
|
+
* - explicit: return "medium", module should set risk explicitly
|
|
453
|
+
*/
|
|
454
|
+
export function aggregateRisk(
|
|
455
|
+
data: Record<string, unknown>,
|
|
456
|
+
riskRule: RiskRule = 'max_changes_risk'
|
|
457
|
+
): RiskLevel {
|
|
458
|
+
if (riskRule === 'max_changes_risk') {
|
|
459
|
+
const changes = (data.changes as Array<{ risk?: RiskLevel }>) ?? [];
|
|
460
|
+
return aggregateRiskFromList(changes);
|
|
461
|
+
} else if (riskRule === 'max_issues_risk') {
|
|
462
|
+
const issues = (data.issues as Array<{ risk?: RiskLevel }>) ?? [];
|
|
463
|
+
return aggregateRiskFromList(issues);
|
|
464
|
+
} else if (riskRule === 'explicit') {
|
|
465
|
+
return 'medium'; // Module should override
|
|
466
|
+
}
|
|
467
|
+
// Fallback to changes
|
|
468
|
+
const changes = (data.changes as Array<{ risk?: RiskLevel }>) ?? [];
|
|
469
|
+
return aggregateRiskFromList(changes);
|
|
470
|
+
}
|
|
471
|
+
|
|
429
472
|
/** Check if result should be escalated to human review */
|
|
430
473
|
export function shouldEscalate<T>(
|
|
431
474
|
response: EnvelopeResponse<T>,
|