opencontext-cli 0.2.1b0__tar.gz → 0.4.0b0__tar.gz
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.
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/PKG-INFO +1 -1
- opencontext_cli-0.4.0b0/opencontext_cli/__main__.py +5 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/ci_check_cmd.py +77 -1
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/config_cmd.py +128 -38
- opencontext_cli-0.4.0b0/opencontext_cli/commands/menu_cmd.py +442 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/plugin_cmd.py +163 -1
- opencontext_cli-0.4.0b0/opencontext_cli/commands/setup_cmd.py +582 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/update_cmd.py +21 -10
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/verify_cmd.py +2 -2
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/main.py +933 -766
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/PKG-INFO +1 -1
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/SOURCES.txt +2 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/pyproject.toml +1 -1
- opencontext_cli-0.2.1b0/opencontext_cli/commands/setup_cmd.py +0 -346
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/LICENSE +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/README.md +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/__init__.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/__init__.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/git_cmd.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/hints_cmd.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/kg_cmd.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/sync_cmd.py +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/dependency_links.txt +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/entry_points.txt +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/requires.txt +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli.egg-info/top_level.txt +0 -0
- {opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/setup.cfg +0 -0
{opencontext_cli-0.2.1b0 → opencontext_cli-0.4.0b0}/opencontext_cli/commands/ci_check_cmd.py
RENAMED
|
@@ -3,17 +3,64 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Any
|
|
7
8
|
|
|
8
9
|
from opencontext_core.dx.console_styles import console
|
|
9
10
|
from opencontext_core.quality.ci_checks import CheckRunner
|
|
10
11
|
|
|
12
|
+
CONTEXTBENCH_WORKFLOW = """\
|
|
13
|
+
# OpenContext ContextBench CI
|
|
14
|
+
# Auto-generated by `opencontext ci-check init`
|
|
15
|
+
name: OpenContext ContextBench
|
|
16
|
+
|
|
17
|
+
on:
|
|
18
|
+
push:
|
|
19
|
+
branches: [main, master]
|
|
20
|
+
pull_request:
|
|
21
|
+
branches: [main, master]
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
contextbench:
|
|
25
|
+
runs-on: ubuntu-latest
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- name: Install OpenContext
|
|
29
|
+
run: pip install opencontext-cli
|
|
30
|
+
- name: Initialize checks
|
|
31
|
+
run: opencontext ci-check init
|
|
32
|
+
- name: Run ContextBench checks
|
|
33
|
+
run: opencontext ci-check run --json > contextbench-report.json
|
|
34
|
+
- name: Upload report
|
|
35
|
+
uses: actions/upload-artifact@v4
|
|
36
|
+
with:
|
|
37
|
+
name: contextbench-report
|
|
38
|
+
path: contextbench-report.json
|
|
39
|
+
- name: Fail on errors
|
|
40
|
+
run: |
|
|
41
|
+
python3 -c "
|
|
42
|
+
import json
|
|
43
|
+
with open('contextbench-report.json') as f:
|
|
44
|
+
report = json.load(f)
|
|
45
|
+
summary = report.get('summary', {})
|
|
46
|
+
if summary.get('failed', 0) > 0:
|
|
47
|
+
print('❌ ContextBench checks failed')
|
|
48
|
+
exit(1)
|
|
49
|
+
print('✅ All ContextBench checks passed')
|
|
50
|
+
"
|
|
51
|
+
"""
|
|
52
|
+
|
|
11
53
|
|
|
12
54
|
def add_ci_check_parser(subparsers: Any) -> None:
|
|
13
55
|
"""Add ci-check command parsers."""
|
|
14
56
|
check_parser = subparsers.add_parser("ci-check", help="CI check management.")
|
|
15
57
|
check_sub = check_parser.add_subparsers(dest="ci_check_command", required=True)
|
|
16
|
-
check_sub.add_parser(
|
|
58
|
+
check_init = check_sub.add_parser(
|
|
59
|
+
"init", help="Initialize checks directory and ContextBench workflow."
|
|
60
|
+
)
|
|
61
|
+
check_init.add_argument(
|
|
62
|
+
"--no-workflow", action="store_true", help="Skip GitHub Actions workflow generation."
|
|
63
|
+
)
|
|
17
64
|
check_sub.add_parser("list", help="List discovered checks.")
|
|
18
65
|
check_run = check_sub.add_parser("run", help="Run all checks.")
|
|
19
66
|
check_run.add_argument("--file", help="Run on specific file only.")
|
|
@@ -21,6 +68,10 @@ def add_ci_check_parser(subparsers: Any) -> None:
|
|
|
21
68
|
check_create = check_sub.add_parser("create", help="Create a new check template.")
|
|
22
69
|
check_create.add_argument("name", help="Check name.")
|
|
23
70
|
check_create.add_argument("--description", default="", help="Check description.")
|
|
71
|
+
check_gh = check_sub.add_parser(
|
|
72
|
+
"github-actions", help="Generate ContextBench GitHub Actions workflow."
|
|
73
|
+
)
|
|
74
|
+
check_gh.add_argument("--force", action="store_true", help="Overwrite existing workflow file.")
|
|
24
75
|
|
|
25
76
|
|
|
26
77
|
def handle_ci_check(args: Any) -> None:
|
|
@@ -35,6 +86,18 @@ def handle_ci_check(args: Any) -> None:
|
|
|
35
86
|
if command == "init":
|
|
36
87
|
path = runner.init_checks_directory()
|
|
37
88
|
console.success(f"Initialized checks directory: {path}")
|
|
89
|
+
skip_workflow = getattr(args, "no_workflow", False)
|
|
90
|
+
if not skip_workflow:
|
|
91
|
+
workflow_path = _generate_contextbench_workflow()
|
|
92
|
+
console.success(f"Generated ContextBench workflow: {workflow_path}")
|
|
93
|
+
elif command == "github-actions":
|
|
94
|
+
force = getattr(args, "force", False)
|
|
95
|
+
workflow_path = Path(".github/workflows/opencontext-contextbench.yml")
|
|
96
|
+
if workflow_path.exists() and not force:
|
|
97
|
+
console.warning(f"Workflow already exists: {workflow_path}. Use --force to overwrite.")
|
|
98
|
+
return
|
|
99
|
+
_write_contextbench_workflow(workflow_path)
|
|
100
|
+
console.success(f"Generated ContextBench workflow: {workflow_path}")
|
|
38
101
|
elif command == "list":
|
|
39
102
|
checks = runner.discover_checks()
|
|
40
103
|
if json_output:
|
|
@@ -113,3 +176,16 @@ def _display_check_report(report: dict[str, Any]) -> None:
|
|
|
113
176
|
console.print(f" [dim]File: {r['file']}:{r.get('line', 'N/A')}[/]")
|
|
114
177
|
if r.get("suggestion"):
|
|
115
178
|
console.print(f" [dim]Suggestion: {r['suggestion']}[/]")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _generate_contextbench_workflow() -> Path:
|
|
182
|
+
"""Generate the ContextBench GitHub Actions workflow as part of init."""
|
|
183
|
+
workflow_path = Path(".github/workflows/opencontext-contextbench.yml")
|
|
184
|
+
_write_contextbench_workflow(workflow_path)
|
|
185
|
+
return workflow_path
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _write_contextbench_workflow(workflow_path: Path) -> None:
|
|
189
|
+
"""Write the ContextBench workflow file."""
|
|
190
|
+
workflow_path.parent.mkdir(parents=True, exist_ok=True)
|
|
191
|
+
workflow_path.write_text(CONTEXTBENCH_WORKFLOW)
|
|
@@ -16,7 +16,6 @@ from opencontext_core.user_prefs import UserConfigStore
|
|
|
16
16
|
from opencontext_core.wizard import (
|
|
17
17
|
reconfigure,
|
|
18
18
|
reset_config,
|
|
19
|
-
run_wizard,
|
|
20
19
|
show_config,
|
|
21
20
|
)
|
|
22
21
|
|
|
@@ -25,7 +24,7 @@ def add_config_parser(subparsers: Any) -> None:
|
|
|
25
24
|
"""Add config command parsers."""
|
|
26
25
|
|
|
27
26
|
config_parser = subparsers.add_parser("config", help="Manage OpenContext configuration.")
|
|
28
|
-
config_sub = config_parser.add_subparsers(dest="config_command"
|
|
27
|
+
config_sub = config_parser.add_subparsers(dest="config_command")
|
|
29
28
|
|
|
30
29
|
# Wizard
|
|
31
30
|
wizard_parser = config_sub.add_parser("wizard", help="Run configuration wizard.")
|
|
@@ -76,10 +75,26 @@ def add_config_parser(subparsers: Any) -> None:
|
|
|
76
75
|
def handle_config(args: Any) -> None:
|
|
77
76
|
"""Handle config commands."""
|
|
78
77
|
|
|
79
|
-
command = args
|
|
78
|
+
command = getattr(args, "config_command", None)
|
|
79
|
+
|
|
80
|
+
if command is None:
|
|
81
|
+
# No subcommand — run the interactive wizard by default
|
|
82
|
+
from opencontext_core.wizard import run_wizard, run_wizard_menu
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
run_wizard_menu()
|
|
86
|
+
except Exception:
|
|
87
|
+
run_wizard(non_interactive=True)
|
|
88
|
+
return
|
|
80
89
|
|
|
81
90
|
if command == "wizard":
|
|
82
|
-
|
|
91
|
+
use_tui = not getattr(args, "non_interactive", False)
|
|
92
|
+
if use_tui:
|
|
93
|
+
from opencontext_core.wizard import run_wizard_menu
|
|
94
|
+
|
|
95
|
+
run_wizard_menu()
|
|
96
|
+
else:
|
|
97
|
+
run_wizard(non_interactive=True)
|
|
83
98
|
elif command == "show":
|
|
84
99
|
show_config()
|
|
85
100
|
elif command == "reset":
|
|
@@ -100,56 +115,131 @@ def handle_config(args: Any) -> None:
|
|
|
100
115
|
_config_cleanup(args.keep_days)
|
|
101
116
|
|
|
102
117
|
|
|
118
|
+
# ── Dot-notation config paths ──────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
# Schema of configurable paths: "path" -> (type, description)
|
|
121
|
+
CONFIG_PATHS: dict[str, tuple[type, str]] = {
|
|
122
|
+
# Flat keys
|
|
123
|
+
"security_mode": (str, "Security mode: private_project, enterprise, or air-gapped"),
|
|
124
|
+
"default_token_budget": (int, "Default token budget per operation"),
|
|
125
|
+
"max_input_tokens": (int, "Maximum input tokens"),
|
|
126
|
+
"reserve_output_tokens": (int, "Reserved output tokens"),
|
|
127
|
+
"check_updates": (bool, "Check for updates automatically"),
|
|
128
|
+
"auto_optimize": (bool, "Auto-optimize token budgets based on usage"),
|
|
129
|
+
"first_run": (bool, "Whether this is the first run"),
|
|
130
|
+
"default_provider": (str, "Default LLM provider"),
|
|
131
|
+
"default_model": (str, "Default LLM model"),
|
|
132
|
+
# Nested: features.*
|
|
133
|
+
"features.knowledge_graph": (bool, "Knowledge Graph (code indexing & search)"),
|
|
134
|
+
"features.call_graph": (bool, "Call Graph (function call analysis)"),
|
|
135
|
+
"features.learning_system": (bool, "Learning System (auto-optimize)"),
|
|
136
|
+
"features.governance": (bool, "Governance (audit trails & policies)"),
|
|
137
|
+
"features.mcp_server": (bool, "MCP Server (agent integration)"),
|
|
138
|
+
"features.git_integration": (bool, "Git Integration"),
|
|
139
|
+
"features.embeddings": (bool, "Embeddings (semantic search)"),
|
|
140
|
+
"features.semantic_search": (bool, "Semantic Search"),
|
|
141
|
+
# Nested: sdd.*
|
|
142
|
+
"sdd.tdd_mode": (str, "TDD mode: ask, strict, or off"),
|
|
143
|
+
"sdd.sdd_model_profile": (str, "SDD model profile: default, cheap, hybrid, premium"),
|
|
144
|
+
"sdd.orchestrator_profile": (
|
|
145
|
+
str,
|
|
146
|
+
"Orchestrator profile: solo-compact, multi-phase, subagent-native",
|
|
147
|
+
),
|
|
148
|
+
# Nested: agents.*
|
|
149
|
+
"agents.default_client": (str, "Default agent client"),
|
|
150
|
+
"agents.active_clients": (list, "Active agent clients (comma-separated)"),
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _resolve_config_path(prefs: Any, dotted: str) -> tuple[Any, str] | None:
|
|
155
|
+
"""Resolve a dotted path to (parent_object, attr_name) or None if invalid.
|
|
156
|
+
|
|
157
|
+
Example: "features.knowledge_graph" -> (prefs.features, "knowledge_graph")
|
|
158
|
+
"""
|
|
159
|
+
parts = dotted.split(".")
|
|
160
|
+
obj = prefs
|
|
161
|
+
for _i, part in enumerate(parts[:-1]):
|
|
162
|
+
if hasattr(obj, part):
|
|
163
|
+
obj = getattr(obj, part)
|
|
164
|
+
else:
|
|
165
|
+
return None
|
|
166
|
+
return (obj, parts[-1])
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _get_all_config_paths() -> list[str]:
|
|
170
|
+
"""Return all available config paths sorted."""
|
|
171
|
+
return sorted(CONFIG_PATHS.keys())
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _coerce_value(value: str, target_type: type) -> object:
|
|
175
|
+
"""Coerce a string value to the target type."""
|
|
176
|
+
if target_type is bool:
|
|
177
|
+
return value.lower() in ("true", "1", "yes", "on")
|
|
178
|
+
elif target_type is int:
|
|
179
|
+
return int(value)
|
|
180
|
+
elif target_type is list:
|
|
181
|
+
import json
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
parsed = json.loads(value)
|
|
185
|
+
if isinstance(parsed, list):
|
|
186
|
+
return parsed
|
|
187
|
+
except (json.JSONDecodeError, TypeError):
|
|
188
|
+
pass
|
|
189
|
+
# Fallback: comma-separated
|
|
190
|
+
return [item.strip() for item in value.split(",") if item.strip()]
|
|
191
|
+
else:
|
|
192
|
+
return value
|
|
193
|
+
|
|
194
|
+
|
|
103
195
|
def _config_set(key: str, value: str) -> None:
|
|
104
|
-
"""Set a config value
|
|
196
|
+
"""Set a config value using dot notation."""
|
|
105
197
|
|
|
106
198
|
store = UserConfigStore()
|
|
107
199
|
prefs = store.load()
|
|
108
200
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
else:
|
|
125
|
-
parsed = value
|
|
126
|
-
setattr(prefs, attr_name, parsed)
|
|
127
|
-
store.save(prefs)
|
|
128
|
-
print(f"Set {key} = {parsed}")
|
|
201
|
+
if key in CONFIG_PATHS:
|
|
202
|
+
_target_type, _description = CONFIG_PATHS[key]
|
|
203
|
+
resolved = _resolve_config_path(prefs, key)
|
|
204
|
+
if resolved is None:
|
|
205
|
+
print(f"Error: Cannot resolve path '{key}'")
|
|
206
|
+
return
|
|
207
|
+
parent, attr = resolved
|
|
208
|
+
try:
|
|
209
|
+
parsed = _coerce_value(value, _target_type)
|
|
210
|
+
setattr(parent, attr, parsed)
|
|
211
|
+
store.save(prefs)
|
|
212
|
+
print(f"Set {key} = {parsed}")
|
|
213
|
+
except (ValueError, TypeError) as exc:
|
|
214
|
+
print(f"Error: Cannot set '{key}' to '{value}': {exc}")
|
|
215
|
+
print(f"Expected type: {_target_type.__name__}")
|
|
129
216
|
else:
|
|
130
217
|
print(f"Unknown key: {key}")
|
|
131
|
-
print(f"Available
|
|
218
|
+
print(f"Available paths ({len(CONFIG_PATHS)}):")
|
|
219
|
+
for path, (typ, desc) in sorted(CONFIG_PATHS.items()):
|
|
220
|
+
print(f" {path} ({typ.__name__}) {desc}")
|
|
132
221
|
|
|
133
222
|
|
|
134
223
|
def _config_get(key: str) -> None:
|
|
135
|
-
"""Get a config value by key."""
|
|
224
|
+
"""Get a config value by dot-notation key."""
|
|
136
225
|
|
|
137
226
|
store = UserConfigStore()
|
|
138
227
|
prefs = store.load()
|
|
139
228
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
if key in key_map:
|
|
150
|
-
print(f"{key} = {key_map[key]}")
|
|
229
|
+
if key in CONFIG_PATHS:
|
|
230
|
+
_target_type, _description = CONFIG_PATHS[key]
|
|
231
|
+
resolved = _resolve_config_path(prefs, key)
|
|
232
|
+
if resolved is None:
|
|
233
|
+
print(f"Error: Cannot resolve path '{key}'")
|
|
234
|
+
return
|
|
235
|
+
parent, attr = resolved
|
|
236
|
+
value = getattr(parent, attr, "<not set>")
|
|
237
|
+
print(f"{key} = {value}")
|
|
151
238
|
else:
|
|
152
239
|
print(f"Unknown key: {key}")
|
|
240
|
+
print(f"Available paths ({len(CONFIG_PATHS)}):")
|
|
241
|
+
for path, (typ, desc) in sorted(CONFIG_PATHS.items()):
|
|
242
|
+
print(f" {path} ({typ.__name__}) {desc}")
|
|
153
243
|
|
|
154
244
|
|
|
155
245
|
def _config_backup() -> None:
|