cognitive-modules 0.3.0__py3-none-any.whl → 0.5.0__py3-none-any.whl

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.
cognitive/loader.py CHANGED
@@ -1,7 +1,13 @@
1
1
  """
2
2
  Module Loader - Load cognitive modules in all formats.
3
3
 
4
- Format v2 (recommended):
4
+ Format v2.2 (latest):
5
+ - module.yaml (machine-readable manifest with tier, overflow, enums, compat)
6
+ - prompt.md (human-readable prompt)
7
+ - schema.json (meta + input + data + error)
8
+ - tests/ (golden tests)
9
+
10
+ Format v2/v2.1 (supported):
5
11
  - module.yaml (machine-readable manifest)
6
12
  - prompt.md (human-readable prompt)
7
13
  - schema.json (input + output + error)
@@ -21,11 +27,24 @@ Format v0 (old, deprecated):
21
27
 
22
28
  import json
23
29
  from pathlib import Path
24
- from typing import Optional
30
+ from typing import Optional, Literal
25
31
 
26
32
  import yaml
27
33
 
28
34
 
35
+ # =============================================================================
36
+ # Type Definitions (v2.2)
37
+ # =============================================================================
38
+
39
+ ModuleTier = Literal["exec", "decision", "exploration"]
40
+ SchemaStrictness = Literal["high", "medium", "low"]
41
+ EnumStrategy = Literal["strict", "extensible"]
42
+
43
+
44
+ # =============================================================================
45
+ # Format Detection
46
+ # =============================================================================
47
+
29
48
  def detect_format(module_path: Path) -> str:
30
49
  """Detect module format: 'v2', 'v1', or 'v0'."""
31
50
  if (module_path / "module.yaml").exists():
@@ -38,6 +57,22 @@ def detect_format(module_path: Path) -> str:
38
57
  raise FileNotFoundError(f"No module.yaml, MODULE.md, or module.md found in {module_path}")
39
58
 
40
59
 
60
+ def detect_v2_version(manifest: dict) -> str:
61
+ """Detect v2.x version from manifest content."""
62
+ # v2.2 indicators
63
+ if manifest.get("tier") or manifest.get("overflow") or manifest.get("enums"):
64
+ return "v2.2"
65
+ # v2.1 indicators
66
+ if manifest.get("policies") or manifest.get("failure"):
67
+ return "v2.1"
68
+ # Default v2.0
69
+ return "v2.0"
70
+
71
+
72
+ # =============================================================================
73
+ # Frontmatter Parsing
74
+ # =============================================================================
75
+
41
76
  def parse_frontmatter(content: str) -> tuple[dict, str]:
42
77
  """Parse YAML frontmatter from markdown content."""
43
78
  if not content.startswith('---'):
@@ -52,12 +87,19 @@ def parse_frontmatter(content: str) -> tuple[dict, str]:
52
87
  return frontmatter, body
53
88
 
54
89
 
90
+ # =============================================================================
91
+ # v2.2 Loader
92
+ # =============================================================================
93
+
55
94
  def load_v2_format(module_path: Path) -> dict:
56
- """Load module in v2 format (module.yaml + prompt.md + schema.json)."""
95
+ """Load module in v2.x format (module.yaml + prompt.md + schema.json)."""
57
96
  # Load module.yaml
58
97
  with open(module_path / "module.yaml", 'r', encoding='utf-8') as f:
59
98
  manifest = yaml.safe_load(f)
60
99
 
100
+ # Detect version
101
+ version_str = detect_v2_version(manifest)
102
+
61
103
  # Load prompt.md
62
104
  prompt_path = module_path / "prompt.md"
63
105
  if prompt_path.exists():
@@ -71,13 +113,28 @@ def load_v2_format(module_path: Path) -> dict:
71
113
  if schema_path.exists():
72
114
  with open(schema_path, 'r', encoding='utf-8') as f:
73
115
  schema = json.load(f)
116
+
74
117
  input_schema = schema.get("input", {})
75
- output_schema = schema.get("output", {})
118
+
119
+ # Support both "data" (v2.2) and "output" (v2.1) aliases
120
+ compat = manifest.get("compat", {})
121
+ if compat.get("schema_output_alias") == "data" or "data" in schema:
122
+ data_schema = schema.get("data", schema.get("output", {}))
123
+ output_schema = data_schema # Keep for backward compat
124
+ else:
125
+ output_schema = schema.get("output", {})
126
+ data_schema = output_schema
127
+
76
128
  error_schema = schema.get("error", {})
129
+ meta_schema = schema.get("meta", {})
130
+ defs = schema.get("$defs", {})
77
131
  else:
78
132
  input_schema = {}
79
133
  output_schema = {}
134
+ data_schema = {}
80
135
  error_schema = {}
136
+ meta_schema = {}
137
+ defs = {}
81
138
 
82
139
  # Extract constraints (supports both old and new format)
83
140
  constraints_raw = manifest.get("constraints", {})
@@ -98,42 +155,91 @@ def load_v2_format(module_path: Path) -> dict:
98
155
  "behavior_equivalence_false_max_confidence": constraints_raw.get("behavior_equivalence_false_max_confidence", 0.7),
99
156
  }
100
157
 
101
- # Extract policies (v2.1)
158
+ # Extract v2.1 fields
102
159
  policies = manifest.get("policies", {})
103
-
104
- # Extract tools policy
105
160
  tools = manifest.get("tools", {})
106
-
107
- # Extract output contract
108
161
  output_contract = manifest.get("output", {})
109
-
110
- # Extract failure contract
111
162
  failure_contract = manifest.get("failure", {})
112
-
113
- # Extract runtime requirements
114
163
  runtime_requirements = manifest.get("runtime_requirements", {})
115
164
 
165
+ # Extract v2.2 fields
166
+ tier: Optional[ModuleTier] = manifest.get("tier")
167
+ schema_strictness: SchemaStrictness = manifest.get("schema_strictness", "medium")
168
+
169
+ overflow = manifest.get("overflow", {
170
+ "enabled": False,
171
+ "recoverable": True,
172
+ "max_items": 5,
173
+ "require_suggested_mapping": True
174
+ })
175
+
176
+ enums = manifest.get("enums", {
177
+ "strategy": "extensible" if tier in ("decision", "exploration") else "strict",
178
+ "unknown_tag": "custom"
179
+ })
180
+
181
+ compat = manifest.get("compat", {
182
+ "accepts_v21_payload": True,
183
+ "runtime_auto_wrap": True,
184
+ "schema_output_alias": "data"
185
+ })
186
+
187
+ io_config = manifest.get("io", {})
188
+ tests = manifest.get("tests", [])
189
+
116
190
  return {
191
+ # Core identity
117
192
  "name": manifest.get("name", module_path.name),
118
193
  "version": manifest.get("version", "1.0.0"),
119
194
  "responsibility": manifest.get("responsibility", ""),
120
195
  "excludes": manifest.get("excludes", []),
196
+
197
+ # Path and format info
121
198
  "path": module_path,
122
199
  "format": "v2",
200
+ "format_version": version_str,
201
+
202
+ # Raw manifest
123
203
  "metadata": manifest,
204
+
205
+ # Schemas
124
206
  "input_schema": input_schema,
125
- "output_schema": output_schema,
207
+ "output_schema": output_schema, # v2.1 compat
208
+ "data_schema": data_schema, # v2.2
126
209
  "error_schema": error_schema,
210
+ "meta_schema": meta_schema, # v2.2
211
+ "schema_defs": defs,
212
+
213
+ # Constraints and policies
127
214
  "constraints": constraints,
128
215
  "policies": policies,
129
216
  "tools": tools,
217
+
218
+ # Contracts
130
219
  "output_contract": output_contract,
131
220
  "failure_contract": failure_contract,
221
+
222
+ # Runtime
132
223
  "runtime_requirements": runtime_requirements,
224
+
225
+ # v2.2 specific
226
+ "tier": tier,
227
+ "schema_strictness": schema_strictness,
228
+ "overflow": overflow,
229
+ "enums": enums,
230
+ "compat": compat,
231
+ "io": io_config,
232
+ "tests": tests,
233
+
234
+ # Prompt
133
235
  "prompt": prompt,
134
236
  }
135
237
 
136
238
 
239
+ # =============================================================================
240
+ # v1 Loader (Legacy)
241
+ # =============================================================================
242
+
137
243
  def load_v1_format(module_path: Path) -> dict:
138
244
  """Load module in v1 format (MODULE.md + schema.json)."""
139
245
  # Load MODULE.md
@@ -173,14 +279,26 @@ def load_v1_format(module_path: Path) -> dict:
173
279
  "excludes": metadata.get("excludes", []),
174
280
  "path": module_path,
175
281
  "format": "v1",
282
+ "format_version": "v1.0",
176
283
  "metadata": metadata,
177
284
  "input_schema": input_schema,
178
285
  "output_schema": output_schema,
286
+ "data_schema": output_schema, # Alias for v2.2 compat
179
287
  "constraints": constraints,
180
288
  "prompt": prompt,
289
+ # v2.2 defaults for v1 modules
290
+ "tier": None,
291
+ "schema_strictness": "medium",
292
+ "overflow": {"enabled": False},
293
+ "enums": {"strategy": "strict"},
294
+ "compat": {"accepts_v21_payload": True, "runtime_auto_wrap": True},
181
295
  }
182
296
 
183
297
 
298
+ # =============================================================================
299
+ # v0 Loader (Deprecated)
300
+ # =============================================================================
301
+
184
302
  def load_v0_format(module_path: Path) -> dict:
185
303
  """Load module in v0 format (old 6-file format)."""
186
304
  # Load module.md
@@ -211,14 +329,26 @@ def load_v0_format(module_path: Path) -> dict:
211
329
  "excludes": [],
212
330
  "path": module_path,
213
331
  "format": "v0",
332
+ "format_version": "v0.0",
214
333
  "metadata": metadata,
215
334
  "input_schema": input_schema,
216
335
  "output_schema": output_schema,
336
+ "data_schema": output_schema, # Alias
217
337
  "constraints": constraints,
218
338
  "prompt": prompt,
339
+ # v2.2 defaults
340
+ "tier": None,
341
+ "schema_strictness": "medium",
342
+ "overflow": {"enabled": False},
343
+ "enums": {"strategy": "strict"},
344
+ "compat": {"accepts_v21_payload": True, "runtime_auto_wrap": True},
219
345
  }
220
346
 
221
347
 
348
+ # =============================================================================
349
+ # Main Loader
350
+ # =============================================================================
351
+
222
352
  def load_module(module_path: Path) -> dict:
223
353
  """Load a module, auto-detecting format."""
224
354
  fmt = detect_format(module_path)
@@ -230,6 +360,10 @@ def load_module(module_path: Path) -> dict:
230
360
  return load_v0_format(module_path)
231
361
 
232
362
 
363
+ # =============================================================================
364
+ # Module Discovery
365
+ # =============================================================================
366
+
233
367
  def find_module(name: str, search_paths: list[Path]) -> Optional[dict]:
234
368
  """Find and load a module by name from search paths."""
235
369
  for base_path in search_paths:
@@ -256,3 +390,35 @@ def list_modules(search_paths: list[Path]) -> list[dict]:
256
390
  except FileNotFoundError:
257
391
  continue
258
392
  return modules
393
+
394
+
395
+ # =============================================================================
396
+ # Utility Functions
397
+ # =============================================================================
398
+
399
+ def get_module_tier(module: dict) -> Optional[ModuleTier]:
400
+ """Get module tier (exec, decision, exploration)."""
401
+ return module.get("tier")
402
+
403
+
404
+ def get_schema_strictness(module: dict) -> SchemaStrictness:
405
+ """Get schema strictness level."""
406
+ return module.get("schema_strictness", "medium")
407
+
408
+
409
+ def is_overflow_enabled(module: dict) -> bool:
410
+ """Check if overflow (extensions.insights) is enabled."""
411
+ overflow = module.get("overflow", {})
412
+ return overflow.get("enabled", False)
413
+
414
+
415
+ def get_enum_strategy(module: dict) -> EnumStrategy:
416
+ """Get enum extension strategy."""
417
+ enums = module.get("enums", {})
418
+ return enums.get("strategy", "strict")
419
+
420
+
421
+ def should_auto_wrap(module: dict) -> bool:
422
+ """Check if runtime should auto-wrap v2.1 to v2.2."""
423
+ compat = module.get("compat", {})
424
+ return compat.get("runtime_auto_wrap", True)
@@ -0,0 +1,245 @@
1
+ """
2
+ Cognitive Modules MCP Server
3
+
4
+ 提供 MCP (Model Context Protocol) 接口,让 Claude Code、Cursor 等工具可以使用 Cognitive Modules。
5
+
6
+ 启动方式:
7
+ cogn mcp
8
+
9
+ 或直接运行:
10
+ python -m cognitive.mcp_server
11
+ """
12
+
13
+ from typing import Optional, Any
14
+ import json
15
+ import os
16
+
17
+ try:
18
+ from mcp.server.fastmcp import FastMCP
19
+ except ImportError:
20
+ raise ImportError(
21
+ "MCP dependencies not installed. "
22
+ "Install with: pip install cognitive-modules[mcp]"
23
+ )
24
+
25
+ from .registry import list_modules, find_module
26
+ from .loader import load_module
27
+ from .runner import run_module as execute_module
28
+
29
+ # ============================================================
30
+ # MCP Server Setup
31
+ # ============================================================
32
+
33
+ mcp = FastMCP(
34
+ "Cognitive Modules",
35
+ description="可验证的结构化 AI 任务规范 - 提供代码审查、任务排序等模块",
36
+ )
37
+
38
+
39
+ # ============================================================
40
+ # Tools
41
+ # ============================================================
42
+
43
+ @mcp.tool()
44
+ def cognitive_run(
45
+ module: str,
46
+ args: str,
47
+ provider: Optional[str] = None,
48
+ model: Optional[str] = None,
49
+ ) -> dict[str, Any]:
50
+ """
51
+ 运行 Cognitive Module,获取结构化的 AI 分析结果。
52
+
53
+ Args:
54
+ module: 模块名称,如 "code-reviewer", "task-prioritizer"
55
+ args: 输入参数,如代码片段或任务列表
56
+ provider: LLM 提供商(可选),如 "openai", "anthropic"
57
+ model: 模型名称(可选),如 "gpt-4o", "claude-3-5-sonnet-20241022"
58
+
59
+ Returns:
60
+ 结构化结果,包含分析内容、confidence(置信度)和 rationale(推理过程)
61
+
62
+ Example:
63
+ cognitive_run("code-reviewer", "def login(u,p): return db.query(f'SELECT * FROM users WHERE name={u}')")
64
+
65
+ 返回:
66
+ {
67
+ "ok": true,
68
+ "data": {
69
+ "issues": [{"type": "security", "severity": "critical", "description": "SQL 注入"}],
70
+ "confidence": 0.95,
71
+ "rationale": "检测到字符串拼接 SQL"
72
+ }
73
+ }
74
+ """
75
+ # 检查模块是否存在
76
+ module_path = find_module(module)
77
+ if not module_path:
78
+ return {"ok": False, "error": f"Module '{module}' not found"}
79
+
80
+ # 设置 provider(如果指定)
81
+ original_provider = os.environ.get("LLM_PROVIDER")
82
+ original_model = os.environ.get("LLM_MODEL")
83
+
84
+ try:
85
+ if provider:
86
+ os.environ["LLM_PROVIDER"] = provider
87
+ if model:
88
+ os.environ["LLM_MODEL"] = model
89
+
90
+ # 执行模块
91
+ result = execute_module(module, args=args)
92
+
93
+ return {"ok": True, "data": result, "module": module}
94
+ except Exception as e:
95
+ return {"ok": False, "error": str(e), "module": module}
96
+ finally:
97
+ # 恢复原始环境变量
98
+ if original_provider:
99
+ os.environ["LLM_PROVIDER"] = original_provider
100
+ elif "LLM_PROVIDER" in os.environ and provider:
101
+ del os.environ["LLM_PROVIDER"]
102
+
103
+ if original_model:
104
+ os.environ["LLM_MODEL"] = original_model
105
+ elif "LLM_MODEL" in os.environ and model:
106
+ del os.environ["LLM_MODEL"]
107
+
108
+
109
+ @mcp.tool()
110
+ def cognitive_list() -> dict[str, Any]:
111
+ """
112
+ 列出所有已安装的 Cognitive Modules。
113
+
114
+ Returns:
115
+ 模块列表,包含名称、位置和格式信息
116
+
117
+ Example:
118
+ cognitive_list()
119
+
120
+ 返回:
121
+ {
122
+ "modules": [
123
+ {"name": "code-reviewer", "location": "builtin", "format": "v1"},
124
+ {"name": "task-prioritizer", "location": "builtin", "format": "v1"}
125
+ ],
126
+ "count": 2
127
+ }
128
+ """
129
+ modules = list_modules()
130
+ return {
131
+ "modules": [
132
+ {"name": m["name"], "location": m["location"], "format": m["format"]}
133
+ for m in modules
134
+ ],
135
+ "count": len(modules),
136
+ }
137
+
138
+
139
+ @mcp.tool()
140
+ def cognitive_info(module: str) -> dict[str, Any]:
141
+ """
142
+ 获取 Cognitive Module 的详细信息。
143
+
144
+ Args:
145
+ module: 模块名称
146
+
147
+ Returns:
148
+ 模块详情,包含名称、描述、输入输出格式等
149
+
150
+ Example:
151
+ cognitive_info("code-reviewer")
152
+
153
+ 返回:
154
+ {
155
+ "name": "code-reviewer",
156
+ "version": "1.0.0",
157
+ "description": "代码安全和质量审查",
158
+ "responsibility": "分析代码并识别潜在问题"
159
+ }
160
+ """
161
+ module_path = find_module(module)
162
+ if not module_path:
163
+ return {"ok": False, "error": f"Module '{module}' not found"}
164
+
165
+ try:
166
+ m = load_module(module)
167
+ meta = m.get("meta", {})
168
+
169
+ return {
170
+ "ok": True,
171
+ "name": meta.get("name", module),
172
+ "version": meta.get("version"),
173
+ "description": meta.get("description") or meta.get("responsibility"),
174
+ "responsibility": meta.get("responsibility"),
175
+ "input_schema": m.get("input_schema"),
176
+ "output_schema": m.get("output_schema"),
177
+ }
178
+ except Exception as e:
179
+ return {"ok": False, "error": str(e)}
180
+
181
+
182
+ # ============================================================
183
+ # Resources
184
+ # ============================================================
185
+
186
+ @mcp.resource("cognitive://modules")
187
+ def get_modules_resource() -> str:
188
+ """获取所有模块列表(资源形式)"""
189
+ modules = list_modules()
190
+ return json.dumps([m["name"] for m in modules], indent=2)
191
+
192
+
193
+ @mcp.resource("cognitive://module/{name}")
194
+ def get_module_resource(name: str) -> str:
195
+ """获取单个模块的 prompt 内容"""
196
+ module_path = find_module(name)
197
+ if not module_path:
198
+ return f"Module '{name}' not found"
199
+
200
+ try:
201
+ m = load_module(name)
202
+ return m.get("prompt", "")
203
+ except Exception as e:
204
+ return f"Error: {e}"
205
+
206
+
207
+ # ============================================================
208
+ # Prompts
209
+ # ============================================================
210
+
211
+ @mcp.prompt()
212
+ def code_review_prompt(code: str) -> str:
213
+ """生成代码审查提示"""
214
+ return f"""请使用 cognitive_run 工具审查以下代码:
215
+
216
+ ```
217
+ {code}
218
+ ```
219
+
220
+ 调用: cognitive_run("code-reviewer", "{code[:100]}...")
221
+ """
222
+
223
+
224
+ @mcp.prompt()
225
+ def task_prioritize_prompt(tasks: str) -> str:
226
+ """生成任务排序提示"""
227
+ return f"""请使用 cognitive_run 工具对以下任务进行优先级排序:
228
+
229
+ {tasks}
230
+
231
+ 调用: cognitive_run("task-prioritizer", "{tasks}")
232
+ """
233
+
234
+
235
+ # ============================================================
236
+ # 启动入口
237
+ # ============================================================
238
+
239
+ def serve():
240
+ """启动 MCP 服务器"""
241
+ mcp.run(transport="stdio")
242
+
243
+
244
+ if __name__ == "__main__":
245
+ serve()