cognitive-modules 0.4.0__py3-none-any.whl → 0.5.1__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.1"
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,102 @@ 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
+ # Determine default max_items based on strictness (SPEC-v2.2)
170
+ # high=0 (disabled), medium=5, low=20
171
+ strictness_max_items = {
172
+ "high": 0,
173
+ "medium": 5,
174
+ "low": 20
175
+ }
176
+ default_max_items = strictness_max_items.get(schema_strictness, 5)
177
+ default_enabled = schema_strictness != "high"
178
+
179
+ overflow_raw = manifest.get("overflow", {})
180
+ overflow = {
181
+ "enabled": overflow_raw.get("enabled", default_enabled),
182
+ "recoverable": overflow_raw.get("recoverable", True),
183
+ "max_items": overflow_raw.get("max_items", default_max_items),
184
+ "require_suggested_mapping": overflow_raw.get("require_suggested_mapping", True)
185
+ }
186
+
187
+ enums = manifest.get("enums", {
188
+ "strategy": "extensible" if tier in ("decision", "exploration") else "strict",
189
+ "unknown_tag": "custom"
190
+ })
191
+
192
+ compat = manifest.get("compat", {
193
+ "accepts_v21_payload": True,
194
+ "runtime_auto_wrap": True,
195
+ "schema_output_alias": "data"
196
+ })
197
+
198
+ io_config = manifest.get("io", {})
199
+ tests = manifest.get("tests", [])
200
+
116
201
  return {
202
+ # Core identity
117
203
  "name": manifest.get("name", module_path.name),
118
204
  "version": manifest.get("version", "1.0.0"),
119
205
  "responsibility": manifest.get("responsibility", ""),
120
206
  "excludes": manifest.get("excludes", []),
207
+
208
+ # Path and format info
121
209
  "path": module_path,
122
210
  "format": "v2",
211
+ "format_version": version_str,
212
+
213
+ # Raw manifest
123
214
  "metadata": manifest,
215
+
216
+ # Schemas
124
217
  "input_schema": input_schema,
125
- "output_schema": output_schema,
218
+ "output_schema": output_schema, # v2.1 compat
219
+ "data_schema": data_schema, # v2.2
126
220
  "error_schema": error_schema,
221
+ "meta_schema": meta_schema, # v2.2
222
+ "schema_defs": defs,
223
+
224
+ # Constraints and policies
127
225
  "constraints": constraints,
128
226
  "policies": policies,
129
227
  "tools": tools,
228
+
229
+ # Contracts
130
230
  "output_contract": output_contract,
131
231
  "failure_contract": failure_contract,
232
+
233
+ # Runtime
132
234
  "runtime_requirements": runtime_requirements,
235
+
236
+ # v2.2 specific
237
+ "tier": tier,
238
+ "schema_strictness": schema_strictness,
239
+ "overflow": overflow,
240
+ "enums": enums,
241
+ "compat": compat,
242
+ "io": io_config,
243
+ "tests": tests,
244
+
245
+ # Prompt
133
246
  "prompt": prompt,
134
247
  }
135
248
 
136
249
 
250
+ # =============================================================================
251
+ # v1 Loader (Legacy)
252
+ # =============================================================================
253
+
137
254
  def load_v1_format(module_path: Path) -> dict:
138
255
  """Load module in v1 format (MODULE.md + schema.json)."""
139
256
  # Load MODULE.md
@@ -173,14 +290,26 @@ def load_v1_format(module_path: Path) -> dict:
173
290
  "excludes": metadata.get("excludes", []),
174
291
  "path": module_path,
175
292
  "format": "v1",
293
+ "format_version": "v1.0",
176
294
  "metadata": metadata,
177
295
  "input_schema": input_schema,
178
296
  "output_schema": output_schema,
297
+ "data_schema": output_schema, # Alias for v2.2 compat
179
298
  "constraints": constraints,
180
299
  "prompt": prompt,
300
+ # v2.2 defaults for v1 modules
301
+ "tier": None,
302
+ "schema_strictness": "medium",
303
+ "overflow": {"enabled": False},
304
+ "enums": {"strategy": "strict"},
305
+ "compat": {"accepts_v21_payload": True, "runtime_auto_wrap": True},
181
306
  }
182
307
 
183
308
 
309
+ # =============================================================================
310
+ # v0 Loader (Deprecated)
311
+ # =============================================================================
312
+
184
313
  def load_v0_format(module_path: Path) -> dict:
185
314
  """Load module in v0 format (old 6-file format)."""
186
315
  # Load module.md
@@ -211,14 +340,26 @@ def load_v0_format(module_path: Path) -> dict:
211
340
  "excludes": [],
212
341
  "path": module_path,
213
342
  "format": "v0",
343
+ "format_version": "v0.0",
214
344
  "metadata": metadata,
215
345
  "input_schema": input_schema,
216
346
  "output_schema": output_schema,
347
+ "data_schema": output_schema, # Alias
217
348
  "constraints": constraints,
218
349
  "prompt": prompt,
350
+ # v2.2 defaults
351
+ "tier": None,
352
+ "schema_strictness": "medium",
353
+ "overflow": {"enabled": False},
354
+ "enums": {"strategy": "strict"},
355
+ "compat": {"accepts_v21_payload": True, "runtime_auto_wrap": True},
219
356
  }
220
357
 
221
358
 
359
+ # =============================================================================
360
+ # Main Loader
361
+ # =============================================================================
362
+
222
363
  def load_module(module_path: Path) -> dict:
223
364
  """Load a module, auto-detecting format."""
224
365
  fmt = detect_format(module_path)
@@ -230,6 +371,10 @@ def load_module(module_path: Path) -> dict:
230
371
  return load_v0_format(module_path)
231
372
 
232
373
 
374
+ # =============================================================================
375
+ # Module Discovery
376
+ # =============================================================================
377
+
233
378
  def find_module(name: str, search_paths: list[Path]) -> Optional[dict]:
234
379
  """Find and load a module by name from search paths."""
235
380
  for base_path in search_paths:
@@ -256,3 +401,35 @@ def list_modules(search_paths: list[Path]) -> list[dict]:
256
401
  except FileNotFoundError:
257
402
  continue
258
403
  return modules
404
+
405
+
406
+ # =============================================================================
407
+ # Utility Functions
408
+ # =============================================================================
409
+
410
+ def get_module_tier(module: dict) -> Optional[ModuleTier]:
411
+ """Get module tier (exec, decision, exploration)."""
412
+ return module.get("tier")
413
+
414
+
415
+ def get_schema_strictness(module: dict) -> SchemaStrictness:
416
+ """Get schema strictness level."""
417
+ return module.get("schema_strictness", "medium")
418
+
419
+
420
+ def is_overflow_enabled(module: dict) -> bool:
421
+ """Check if overflow (extensions.insights) is enabled."""
422
+ overflow = module.get("overflow", {})
423
+ return overflow.get("enabled", False)
424
+
425
+
426
+ def get_enum_strategy(module: dict) -> EnumStrategy:
427
+ """Get enum extension strategy."""
428
+ enums = module.get("enums", {})
429
+ return enums.get("strategy", "strict")
430
+
431
+
432
+ def should_auto_wrap(module: dict) -> bool:
433
+ """Check if runtime should auto-wrap v2.1 to v2.2."""
434
+ compat = module.get("compat", {})
435
+ return compat.get("runtime_auto_wrap", True)