cognitive-modules 0.4.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/__init__.py CHANGED
@@ -9,4 +9,4 @@ Usage:
9
9
  cog doctor # Check environment setup
10
10
  """
11
11
 
12
- __version__ = "0.1.0"
12
+ __version__ = "0.5.0"
cognitive/cli.py CHANGED
@@ -1,20 +1,22 @@
1
1
  """
2
- Cognitive CLI - Main entry point for the cog command.
2
+ Cognitive CLI - Main entry point for the cogn command.
3
3
 
4
4
  Commands:
5
- cog list List installed modules
6
- cog run <module> <input> Run a module
7
- cog validate <module> Validate module structure
8
- cog add <url> --module <name> Add module from GitHub (recommended)
9
- cog update <module> Update module to latest version
10
- cog versions <url> List available versions for a repo
11
- cog install <source> Install module from git/local/registry
12
- cog remove <module> Remove an installed module
13
- cog uninstall <module> Remove an installed module (alias)
14
- cog init <name> Create a new module from template
15
- cog search <query> Search the public registry
16
- cog doctor Check environment setup
17
- cog info <module> Show module details
5
+ cogn list List installed modules
6
+ cogn run <module> <input> Run a module
7
+ cogn validate <module> Validate module structure
8
+ cogn add <url> --module <name> Add module from GitHub (recommended)
9
+ cogn update <module> Update module to latest version
10
+ cogn versions <url> List available versions for a repo
11
+ cogn install <source> Install module from git/local/registry
12
+ cogn remove <module> Remove an installed module
13
+ cogn uninstall <module> Remove an installed module (alias)
14
+ cogn init <name> Create a new module from template
15
+ cogn search <query> Search the public registry
16
+ cogn doctor Check environment setup
17
+ cogn info <module> Show module details
18
+ cogn serve Start HTTP API server
19
+ cogn mcp Start MCP server for Claude/Cursor
18
20
  """
19
21
 
20
22
  import json
@@ -47,6 +49,7 @@ from .runner import run_module
47
49
  from .subagent import run_with_subagents
48
50
  from .validator import validate_module
49
51
  from .templates import create_module
52
+ from .migrate import migrate_module, migrate_all_modules
50
53
  from .providers import check_provider_status
51
54
 
52
55
  app = typer.Typer(
@@ -160,11 +163,19 @@ def run_cmd(
160
163
  @app.command("validate")
161
164
  def validate_cmd(
162
165
  module: str = typer.Argument(..., help="Module name or path"),
166
+ v22: bool = typer.Option(False, "--v22", help="Validate v2.2 format requirements"),
163
167
  ):
164
- """Validate a cognitive module's structure and examples."""
165
- rprint(f"[cyan]→[/cyan] Validating module: [bold]{module}[/bold]\n")
168
+ """
169
+ Validate a cognitive module's structure and examples.
170
+
171
+ Examples:
172
+ cogn validate code-reviewer
173
+ cogn validate code-reviewer --v22 # Check v2.2 requirements
174
+ """
175
+ mode_str = " [dim](v2.2 strict)[/dim]" if v22 else ""
176
+ rprint(f"[cyan]→[/cyan] Validating module: [bold]{module}[/bold]{mode_str}\n")
166
177
 
167
- is_valid, errors, warnings = validate_module(module)
178
+ is_valid, errors, warnings = validate_module(module, v22=v22)
168
179
 
169
180
  if warnings:
170
181
  rprint(f"[yellow]⚠ Warnings ({len(warnings)}):[/yellow]")
@@ -173,7 +184,10 @@ def validate_cmd(
173
184
  print()
174
185
 
175
186
  if is_valid:
176
- rprint(f"[green]✓ Module '{module}' is valid[/green]")
187
+ if v22:
188
+ rprint(f"[green]✓ Module '{module}' is valid v2.2 format[/green]")
189
+ else:
190
+ rprint(f"[green]✓ Module '{module}' is valid[/green]")
177
191
  else:
178
192
  rprint(f"[red]✗ Validation failed ({len(errors)} errors):[/red]")
179
193
  for e in errors:
@@ -181,6 +195,83 @@ def validate_cmd(
181
195
  raise typer.Exit(1)
182
196
 
183
197
 
198
+ @app.command("migrate")
199
+ def migrate_cmd(
200
+ module: Optional[str] = typer.Argument(None, help="Module name or path (omit for --all)"),
201
+ all_modules: bool = typer.Option(False, "--all", "-a", help="Migrate all installed modules"),
202
+ dry_run: bool = typer.Option(False, "--dry-run", "-n", help="Show what would be done without making changes"),
203
+ no_backup: bool = typer.Option(False, "--no-backup", help="Skip creating backup before migration"),
204
+ ):
205
+ """
206
+ Migrate modules from v1/v2.1 to v2.2 format.
207
+
208
+ Examples:
209
+ cogn migrate code-reviewer # Migrate single module
210
+ cogn migrate code-reviewer --dry-run # Preview changes
211
+ cogn migrate --all # Migrate all modules
212
+ """
213
+ if not module and not all_modules:
214
+ rprint("[red]Error: Provide module name or use --all[/red]")
215
+ raise typer.Exit(1)
216
+
217
+ if all_modules:
218
+ rprint(f"[cyan]→[/cyan] Migrating all modules to v2.2...")
219
+ if dry_run:
220
+ rprint("[dim](dry run - no changes will be made)[/dim]")
221
+ print()
222
+
223
+ results = migrate_all_modules(dry_run=dry_run, backup=not no_backup)
224
+
225
+ success_count = 0
226
+ for name, success, changes, warnings in results:
227
+ if success:
228
+ success_count += 1
229
+ rprint(f"[green]✓[/green] {name}")
230
+ for c in changes:
231
+ rprint(f" {c}")
232
+ else:
233
+ rprint(f"[red]✗[/red] {name}")
234
+ for w in warnings:
235
+ rprint(f" [yellow]{w}[/yellow]")
236
+
237
+ print()
238
+ rprint(f"[green]Migrated: {success_count}/{len(results)}[/green]")
239
+
240
+ else:
241
+ mode_str = " [dim](dry run)[/dim]" if dry_run else ""
242
+ rprint(f"[cyan]→[/cyan] Migrating module: [bold]{module}[/bold]{mode_str}\n")
243
+
244
+ success, changes, warnings = migrate_module(
245
+ module,
246
+ dry_run=dry_run,
247
+ backup=not no_backup
248
+ )
249
+
250
+ if changes:
251
+ rprint(f"[cyan]Changes:[/cyan]")
252
+ for c in changes:
253
+ rprint(f" - {c}")
254
+ print()
255
+
256
+ if warnings:
257
+ rprint(f"[yellow]⚠ Warnings:[/yellow]")
258
+ for w in warnings:
259
+ rprint(f" - {w}")
260
+ print()
261
+
262
+ if success:
263
+ if dry_run:
264
+ rprint(f"[green]✓ Migration preview complete[/green]")
265
+ rprint(f" Run without --dry-run to apply changes")
266
+ else:
267
+ rprint(f"[green]✓ Module '{module}' migrated to v2.2[/green]")
268
+ rprint(f"\nValidate with:")
269
+ rprint(f" [cyan]cogn validate {module} --v22[/cyan]")
270
+ else:
271
+ rprint(f"[red]✗ Migration failed[/red]")
272
+ raise typer.Exit(1)
273
+
274
+
184
275
  @app.command("install")
185
276
  def install_cmd(
186
277
  source: str = typer.Argument(..., help="Source: github:org/repo/path, registry:name, or local path"),
@@ -607,6 +698,70 @@ def info_cmd(
607
698
  rprint(f"\n Update with: [cyan]cog update {meta.get('name', module)}[/cyan]")
608
699
 
609
700
 
701
+ @app.command("serve")
702
+ def serve_cmd(
703
+ host: str = typer.Option("0.0.0.0", "--host", "-h", help="Host to bind"),
704
+ port: int = typer.Option(8000, "--port", "-p", help="Port to bind"),
705
+ ):
706
+ """
707
+ Start HTTP API server for workflow integration.
708
+
709
+ Example:
710
+ cogn serve --port 8000
711
+
712
+ Then use:
713
+ curl -X POST http://localhost:8000/run \\
714
+ -H "Content-Type: application/json" \\
715
+ -d '{"module": "code-reviewer", "args": "your code"}'
716
+ """
717
+ try:
718
+ import uvicorn
719
+ except ImportError:
720
+ rprint("[red]Error:[/red] Server dependencies not installed.")
721
+ rprint("\nInstall with:")
722
+ rprint(" [cyan]pip install cognitive-modules[server][/cyan]")
723
+ raise typer.Exit(1)
724
+
725
+ rprint(f"[green]Starting Cognitive API server...[/green]")
726
+ rprint(f" Host: {host}")
727
+ rprint(f" Port: {port}")
728
+ rprint(f" Docs: http://{host if host != '0.0.0.0' else 'localhost'}:{port}/docs")
729
+ rprint()
730
+
731
+ uvicorn.run("cognitive.server:app", host=host, port=port, reload=False)
732
+
733
+
734
+ @app.command("mcp")
735
+ def mcp_cmd():
736
+ """
737
+ Start MCP (Model Context Protocol) server.
738
+
739
+ Enables Claude Code, Cursor, and other MCP-compatible tools to use Cognitive Modules.
740
+
741
+ Example:
742
+ cogn mcp
743
+
744
+ Configure in Claude Desktop (claude_desktop_config.json):
745
+ {
746
+ "mcpServers": {
747
+ "cognitive": {
748
+ "command": "cogn",
749
+ "args": ["mcp"]
750
+ }
751
+ }
752
+ }
753
+ """
754
+ try:
755
+ from .mcp_server import serve
756
+ except ImportError:
757
+ rprint("[red]Error:[/red] MCP dependencies not installed.")
758
+ rprint("\nInstall with:")
759
+ rprint(" [cyan]pip install cognitive-modules[mcp][/cyan]")
760
+ raise typer.Exit(1)
761
+
762
+ serve()
763
+
764
+
610
765
  @app.callback(invoke_without_command=True)
611
766
  def main(
612
767
  ctx: typer.Context,
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)