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 +1 -1
- cognitive/cli.py +173 -18
- cognitive/loader.py +180 -14
- cognitive/mcp_server.py +245 -0
- cognitive/migrate.py +624 -0
- cognitive/runner.py +409 -80
- cognitive/server.py +294 -0
- cognitive/validator.py +380 -122
- {cognitive_modules-0.4.0.dist-info → cognitive_modules-0.5.0.dist-info}/METADATA +179 -176
- cognitive_modules-0.5.0.dist-info/RECORD +18 -0
- cognitive_modules-0.4.0.dist-info/RECORD +0 -15
- {cognitive_modules-0.4.0.dist-info → cognitive_modules-0.5.0.dist-info}/WHEEL +0 -0
- {cognitive_modules-0.4.0.dist-info → cognitive_modules-0.5.0.dist-info}/entry_points.txt +0 -0
- {cognitive_modules-0.4.0.dist-info → cognitive_modules-0.5.0.dist-info}/licenses/LICENSE +0 -0
- {cognitive_modules-0.4.0.dist-info → cognitive_modules-0.5.0.dist-info}/top_level.txt +0 -0
cognitive/__init__.py
CHANGED
cognitive/cli.py
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Cognitive CLI - Main entry point for the
|
|
2
|
+
Cognitive CLI - Main entry point for the cogn command.
|
|
3
3
|
|
|
4
4
|
Commands:
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
"""
|
|
165
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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)
|