qcoder 0.5.0a6__tar.gz → 0.5.0a7__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.
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/CHANGELOG.md +12 -0
- {qcoder-0.5.0a6/src/qcoder.egg-info → qcoder-0.5.0a7}/PKG-INFO +12 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/README.md +9 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/pyproject.toml +3 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/__init__.py +1 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/cli.py +54 -11
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/qasm2_regex_parser.py +1 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/explorer/derived_evidence.py +1 -1
- qcoder-0.5.0a7/src/qcoder/mcp/__init__.py +5 -0
- qcoder-0.5.0a7/src/qcoder/mcp/server.py +87 -0
- qcoder-0.5.0a7/src/qcoder/mcp/tools.py +334 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pipelines/analyze.py +1 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pipelines/review.py +1 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7/src/qcoder.egg-info}/PKG-INFO +12 -1
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder.egg-info/SOURCES.txt +3 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/LICENSE +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/MANIFEST.in +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/NOTICE +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/setup.cfg +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/__main__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/context.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/qasm2/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/qasm2/adjoint_eligibility.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/qasm2/mirror_build.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/run_config.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/schema.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/core/share_safe.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/context/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/context/bundle.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/context/markdown.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/cirq_intake.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/pennylane_intake.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/qiskit_intake.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/extractor.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/compute_v0.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/glossary_v0.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/schema_v0.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/ir.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/labeling.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/parsers/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/cut_profile.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/depth.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/entangling_layers.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/gate_set_stats.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/interaction_graph.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/interaction_graph_metrics.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/spans.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/guidance/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/guidance/model_pack.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/guidance/resource.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/guidance/structural_scores.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/profiles/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/profiles/feature_profiles_v0.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/review/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/review/bundle.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/review/counts_v0.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/review/markdown.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/review/qiskit_counts.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/explorer/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/model_packs/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/model_packs/resource_guidance_local_v0.json +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pipelines/batch.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pipelines/context.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pro_preview/__init__.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pro_preview/client.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pro_preview/config.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pro_preview/errors.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/pro_preview/manifest.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/tools/batch.py +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder.egg-info/dependency_links.txt +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder.egg-info/entry_points.txt +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder.egg-info/requires.txt +0 -0
- {qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## 0.5.0a7
|
|
5
|
+
|
|
6
|
+
- Add local read-only qCoder MCP for Cursor-oriented Explorer Launch workflows.
|
|
7
|
+
- Add MCP tools for local circuit analysis, context-pack generation, counts review, bounded findings, claim boundaries, next checks, and share-safe artifact sanitization.
|
|
8
|
+
- Document the Evidence-Grounded Coding Loop and preserve the manual artifact handoff as launch-required even when local MCP is available.
|
|
9
|
+
- Harden Windows-created inputs by accepting UTF-8 BOM in QASM and JSON paths used by local analysis/review.
|
|
10
|
+
- Improve `qcoder review` error guidance for malformed JSON and Qiskit counts input.
|
|
11
|
+
- Add MCP, share-safe, claim-boundary, and Windows/QASM/Qiskit tests plus release-check MCP stdio rehearsal.
|
|
12
|
+
- Preserve launch boundaries: no public hosted MCP claim, no live service calls from MCP, no token requirement, no autonomous code modification, no QPU/simulator submission, and no runtime/fidelity/backend/QPU/correctness/advantage claims.
|
|
13
|
+
- This is release-candidate preparation only until separately published.
|
|
14
|
+
|
|
15
|
+
|
|
4
16
|
## 0.5.0a6
|
|
5
17
|
|
|
6
18
|
- Add share-safe / redacted artifact mode for artifacts intended for ChatGPT, Cursor, email, GitHub issues, or support threads.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qcoder
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.0a7
|
|
4
4
|
Summary: Quantum circuit analysis and structured feature extraction tools.
|
|
5
5
|
Author-email: Quantum Ready Solutions <support@qcoder.ai>
|
|
6
6
|
Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
|
|
@@ -15,6 +15,8 @@ Classifier: Intended Audience :: Science/Research
|
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
20
|
Requires-Python: >=3.11
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE
|
|
@@ -40,6 +42,7 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
|
|
|
40
42
|
- `qcoder batch`
|
|
41
43
|
- `qcoder context`
|
|
42
44
|
- `qcoder review`
|
|
45
|
+
- `qcoder mcp` (local read-only Cursor MCP tools)
|
|
43
46
|
- `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
|
|
44
47
|
- `qcoder student` (temporary compatibility alias for Explorer Beta)
|
|
45
48
|
- `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
|
|
@@ -87,6 +90,14 @@ qcoder review --counts-json counts.json --format qiskit_counts --preflight-json
|
|
|
87
90
|
|
|
88
91
|
Share-safe mode is designed for safer sharing: it redacts local paths and token/header-like strings, adds `share_safe=true`, and marks raw QASM/local paths/tokens as not included. Review artifacts before sharing; this is not a guarantee that all sensitive project content has been removed.
|
|
89
92
|
|
|
93
|
+
Local Cursor MCP:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
qcoder mcp serve
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The MCP server uses explicit user-selected local inputs only. It reads, inspects, explains, bounds, and recommends; it does not execute circuits, modify user code, read arbitrary files, call live services, or require tokens. See [Explorer Evidence-Grounded Coding Loop](examples/08_explorer_evidence_grounded_coding_loop.md) for the manual artifact loop, Cursor MCP setup, and claim boundaries.
|
|
100
|
+
|
|
90
101
|
Explorer Beta compatibility checks and archived Pro bootstrap:
|
|
91
102
|
|
|
92
103
|
```bash
|
|
@@ -10,6 +10,7 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
|
|
|
10
10
|
- `qcoder batch`
|
|
11
11
|
- `qcoder context`
|
|
12
12
|
- `qcoder review`
|
|
13
|
+
- `qcoder mcp` (local read-only Cursor MCP tools)
|
|
13
14
|
- `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
|
|
14
15
|
- `qcoder student` (temporary compatibility alias for Explorer Beta)
|
|
15
16
|
- `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
|
|
@@ -57,6 +58,14 @@ qcoder review --counts-json counts.json --format qiskit_counts --preflight-json
|
|
|
57
58
|
|
|
58
59
|
Share-safe mode is designed for safer sharing: it redacts local paths and token/header-like strings, adds `share_safe=true`, and marks raw QASM/local paths/tokens as not included. Review artifacts before sharing; this is not a guarantee that all sensitive project content has been removed.
|
|
59
60
|
|
|
61
|
+
Local Cursor MCP:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
qcoder mcp serve
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The MCP server uses explicit user-selected local inputs only. It reads, inspects, explains, bounds, and recommends; it does not execute circuits, modify user code, read arbitrary files, call live services, or require tokens. See [Explorer Evidence-Grounded Coding Loop](examples/08_explorer_evidence_grounded_coding_loop.md) for the manual artifact loop, Cursor MCP setup, and claim boundaries.
|
|
68
|
+
|
|
60
69
|
Explorer Beta compatibility checks and archived Pro bootstrap:
|
|
61
70
|
|
|
62
71
|
```bash
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "qcoder"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.0a7"
|
|
8
8
|
description = "Quantum circuit analysis and structured feature extraction tools."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -23,6 +23,8 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Scientific/Engineering",
|
|
24
24
|
"Programming Language :: Python :: 3",
|
|
25
25
|
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
26
28
|
]
|
|
27
29
|
dependencies = ["numpy"]
|
|
28
30
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import argparse
|
|
4
4
|
import json
|
|
5
5
|
import sys
|
|
6
|
+
from json import JSONDecodeError
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from qcoder.pipelines.analyze import analyze_qasm
|
|
@@ -284,14 +285,29 @@ def _cmd_review(argv: list[str]) -> int:
|
|
|
284
285
|
)
|
|
285
286
|
args = p.parse_args(argv)
|
|
286
287
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
try:
|
|
289
|
+
write_execution_review(
|
|
290
|
+
counts_json=args.counts_json,
|
|
291
|
+
counts_format=args.counts_format,
|
|
292
|
+
preflight_json=args.preflight_json,
|
|
293
|
+
out_json=args.out_json,
|
|
294
|
+
out_md=args.out_md,
|
|
295
|
+
share_safe=args.share_safe,
|
|
296
|
+
)
|
|
297
|
+
except JSONDecodeError as exc:
|
|
298
|
+
print(
|
|
299
|
+
f"qcoder review: input JSON could not be parsed at line {exc.lineno}, column {exc.colno}. "
|
|
300
|
+
"Check that the counts/preflight file is UTF-8 JSON.",
|
|
301
|
+
file=sys.stderr,
|
|
302
|
+
)
|
|
303
|
+
return 2
|
|
304
|
+
except ValueError as exc:
|
|
305
|
+
print(
|
|
306
|
+
f"qcoder review: {exc}. For Qiskit counts, use --format qiskit_counts with a JSON object "
|
|
307
|
+
'like {"00": 10, "11": 6} or {"counts": {"00": 10, "11": 6}}.',
|
|
308
|
+
file=sys.stderr,
|
|
309
|
+
)
|
|
310
|
+
return 2
|
|
295
311
|
json_label = "<redacted-local-path>" if args.share_safe else args.out_json
|
|
296
312
|
md_label = "<redacted-local-path>" if args.share_safe else args.out_md
|
|
297
313
|
print(f"Wrote execution review JSON to {json_label}", file=sys.stderr)
|
|
@@ -299,6 +315,30 @@ def _cmd_review(argv: list[str]) -> int:
|
|
|
299
315
|
return 0
|
|
300
316
|
|
|
301
317
|
|
|
318
|
+
def _cmd_mcp(argv: list[str]) -> int:
|
|
319
|
+
p = argparse.ArgumentParser(
|
|
320
|
+
prog="qcoder mcp",
|
|
321
|
+
add_help=True,
|
|
322
|
+
description=(
|
|
323
|
+
"Local read-only qCoder MCP for Cursor. Uses explicit user-selected inputs only; "
|
|
324
|
+
"does not execute circuits, modify code, use tokens, or call live services."
|
|
325
|
+
),
|
|
326
|
+
)
|
|
327
|
+
sub = p.add_subparsers(dest="mcp_command")
|
|
328
|
+
p_serve = sub.add_parser("serve", help="Serve qCoder tools over MCP stdio.")
|
|
329
|
+
p_serve.set_defaults(mcp_command="serve")
|
|
330
|
+
args = p.parse_args(argv)
|
|
331
|
+
if args.mcp_command is None:
|
|
332
|
+
p.print_help()
|
|
333
|
+
return 0
|
|
334
|
+
if args.mcp_command == "serve":
|
|
335
|
+
from qcoder.mcp.server import serve_stdio
|
|
336
|
+
|
|
337
|
+
return serve_stdio()
|
|
338
|
+
p.print_help()
|
|
339
|
+
return 0
|
|
340
|
+
|
|
341
|
+
|
|
302
342
|
def _run_pro_preview_demo_check(
|
|
303
343
|
*,
|
|
304
344
|
base_url_override: str | None,
|
|
@@ -1200,14 +1240,15 @@ def _cmd_pro(argv: list[str]) -> int:
|
|
|
1200
1240
|
|
|
1201
1241
|
def _print_root_help() -> None:
|
|
1202
1242
|
print(
|
|
1203
|
-
"usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,explorer,pro,student} ...\n\n"
|
|
1243
|
+
"usage: qcoder [--version | -V] [-h] {analyze,batch,context,review,mcp,explorer,pro,student} ...\n\n"
|
|
1204
1244
|
"Quantum circuit analysis CLI.\n\n"
|
|
1205
1245
|
"positional arguments:\n"
|
|
1206
|
-
" {analyze,batch,context,review,explorer,pro,student} subcommand\n\n"
|
|
1246
|
+
" {analyze,batch,context,review,mcp,explorer,pro,student} subcommand\n\n"
|
|
1207
1247
|
" analyze Analyze a QASM file (feature extraction + metadata + run config).\n"
|
|
1208
1248
|
" batch Batch extract a directory to JSONL (requires --out).\n"
|
|
1209
1249
|
" context Build deterministic preflight context artifacts.\n"
|
|
1210
1250
|
" review Build deterministic execution review artifacts from counts.\n"
|
|
1251
|
+
" mcp Serve local read-only qCoder tools for Cursor over MCP stdio.\n"
|
|
1211
1252
|
" explorer Explorer Beta status/demo/evidence checks.\n"
|
|
1212
1253
|
" pro Archived Pro client-contract shell (not current public product).\n"
|
|
1213
1254
|
" student Compatibility alias for Explorer Beta checks.\n\n"
|
|
@@ -1239,6 +1280,8 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1239
1280
|
return _cmd_context(rest)
|
|
1240
1281
|
if cmd == "review":
|
|
1241
1282
|
return _cmd_review(rest)
|
|
1283
|
+
if cmd == "mcp":
|
|
1284
|
+
return _cmd_mcp(rest)
|
|
1242
1285
|
if cmd == "explorer":
|
|
1243
1286
|
return _cmd_explorer(rest)
|
|
1244
1287
|
if cmd == "pro":
|
|
@@ -1247,7 +1290,7 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
1247
1290
|
return _cmd_student(rest)
|
|
1248
1291
|
|
|
1249
1292
|
print(
|
|
1250
|
-
f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, explorer, pro, or student)",
|
|
1293
|
+
f"qcoder: unknown subcommand {cmd!r} (expected analyze, batch, context, review, mcp, explorer, pro, or student)",
|
|
1251
1294
|
file=sys.stderr,
|
|
1252
1295
|
)
|
|
1253
1296
|
print("Run `qcoder --help` for usage.", file=sys.stderr)
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/qasm2_regex_parser.py
RENAMED
|
@@ -193,5 +193,5 @@ def parse_qasm2_text(qasm_text: str, *, source_label: str | None = None) -> Circ
|
|
|
193
193
|
|
|
194
194
|
def parse_qasm2_file(path: str) -> CircuitIR:
|
|
195
195
|
p = Path(path)
|
|
196
|
-
text = p.read_text(encoding="utf-8", errors="replace")
|
|
196
|
+
text = p.read_text(encoding="utf-8-sig", errors="replace")
|
|
197
197
|
return parse_qasm2_text(text)
|
|
@@ -71,7 +71,7 @@ def _qcoder_version() -> str:
|
|
|
71
71
|
|
|
72
72
|
def _load_context_json(path: str) -> dict[str, Any]:
|
|
73
73
|
try:
|
|
74
|
-
loaded = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
74
|
+
loaded = json.loads(Path(path).read_text(encoding="utf-8-sig"))
|
|
75
75
|
except json.JSONDecodeError as exc:
|
|
76
76
|
raise ExplorerDerivedEvidenceRequestError("context JSON is not valid JSON") from exc
|
|
77
77
|
if not isinstance(loaded, dict):
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Any, TextIO
|
|
7
|
+
|
|
8
|
+
from qcoder.mcp.tools import call_tool, list_tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
SERVER_INFO = {"name": "qcoder-local-read-only", "version": "0.1.0"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def serve_stdio(*, stdin: TextIO | None = None, stdout: TextIO | None = None) -> int:
|
|
15
|
+
in_stream = stdin or sys.stdin
|
|
16
|
+
out_stream = stdout or sys.stdout
|
|
17
|
+
for raw_line in in_stream:
|
|
18
|
+
line = raw_line.strip()
|
|
19
|
+
if not line:
|
|
20
|
+
continue
|
|
21
|
+
try:
|
|
22
|
+
request = json.loads(line)
|
|
23
|
+
response = handle_request(request)
|
|
24
|
+
except Exception as exc: # MCP server must answer JSON-RPC errors, not crash on bad input.
|
|
25
|
+
response = _error_response(None, -32603, str(exc))
|
|
26
|
+
if response is not None:
|
|
27
|
+
out_stream.write(json.dumps(response, separators=(",", ":"), sort_keys=True) + "\n")
|
|
28
|
+
out_stream.flush()
|
|
29
|
+
return 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def handle_request(request: dict[str, Any]) -> dict[str, Any] | None:
|
|
33
|
+
if not isinstance(request, dict):
|
|
34
|
+
return _error_response(None, -32600, "request must be a JSON object")
|
|
35
|
+
request_id = request.get("id")
|
|
36
|
+
method = request.get("method")
|
|
37
|
+
params = request.get("params") if isinstance(request.get("params"), dict) else {}
|
|
38
|
+
|
|
39
|
+
if method == "initialize":
|
|
40
|
+
return _result_response(
|
|
41
|
+
request_id,
|
|
42
|
+
{
|
|
43
|
+
"protocolVersion": params.get("protocolVersion", "2024-11-05"),
|
|
44
|
+
"capabilities": {"tools": {}},
|
|
45
|
+
"serverInfo": SERVER_INFO,
|
|
46
|
+
"instructions": (
|
|
47
|
+
"Local read-only qCoder MCP. Uses explicit user-selected inputs only; "
|
|
48
|
+
"does not execute circuits, modify code, use tokens, or call live services."
|
|
49
|
+
),
|
|
50
|
+
},
|
|
51
|
+
)
|
|
52
|
+
if method == "notifications/initialized":
|
|
53
|
+
return None
|
|
54
|
+
if method == "tools/list":
|
|
55
|
+
return _result_response(request_id, {"tools": list_tools()})
|
|
56
|
+
if method == "tools/call":
|
|
57
|
+
name = params.get("name")
|
|
58
|
+
arguments = params.get("arguments")
|
|
59
|
+
if not isinstance(name, str):
|
|
60
|
+
return _error_response(request_id, -32602, "tools/call requires a string tool name")
|
|
61
|
+
if arguments is not None and not isinstance(arguments, dict):
|
|
62
|
+
return _error_response(request_id, -32602, "tools/call arguments must be an object")
|
|
63
|
+
try:
|
|
64
|
+
return _result_response(request_id, call_tool(name, arguments or {}))
|
|
65
|
+
except Exception as exc:
|
|
66
|
+
return _error_response(request_id, -32000, str(exc))
|
|
67
|
+
return _error_response(request_id, -32601, f"unknown method: {method}")
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _result_response(request_id: Any, result: dict[str, Any]) -> dict[str, Any]:
|
|
71
|
+
return {"jsonrpc": "2.0", "id": request_id, "result": result}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _error_response(request_id: Any, code: int, message: str) -> dict[str, Any]:
|
|
75
|
+
return {"jsonrpc": "2.0", "id": request_id, "error": {"code": code, "message": message}}
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def main(argv: list[str] | None = None) -> int:
|
|
79
|
+
parser = argparse.ArgumentParser(prog="qcoder mcp serve")
|
|
80
|
+
parser.add_argument("serve", nargs="?", help=argparse.SUPPRESS)
|
|
81
|
+
args = parser.parse_args(argv)
|
|
82
|
+
_ = args
|
|
83
|
+
return serve_stdio()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from qcoder.core.share_safe import make_share_safe_payload
|
|
8
|
+
from qcoder.engines.context.markdown import render_context_markdown
|
|
9
|
+
from qcoder.engines.review.markdown import render_review_markdown
|
|
10
|
+
from qcoder.pipelines.analyze import analyze_qasm_json
|
|
11
|
+
from qcoder.pipelines.context import build_preflight_context
|
|
12
|
+
from qcoder.pipelines.review import build_execution_review
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
MAX_MCP_INPUT_BYTES = 1024 * 1024
|
|
16
|
+
|
|
17
|
+
SAFE_MARKETING_FRAME = (
|
|
18
|
+
"Explorer helps you make your AI assistant better at quantum circuit work by "
|
|
19
|
+
"giving it clean qCoder evidence to reason from."
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
SUPPORTED_CLAIMS = [
|
|
23
|
+
"local deterministic QASM2/OpenQASM 2 structural evidence",
|
|
24
|
+
"local Qiskit counts review",
|
|
25
|
+
"share-safe evidence handoff",
|
|
26
|
+
"plain-English bounded next checks from observed qCoder evidence",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
UNSUPPORTED_CLAIMS = [
|
|
30
|
+
"autonomous circuit repair",
|
|
31
|
+
"runtime prediction",
|
|
32
|
+
"fidelity prediction",
|
|
33
|
+
"backend or QPU ranking",
|
|
34
|
+
"QPU/backend guidance",
|
|
35
|
+
"quantum advantage",
|
|
36
|
+
"correctness proof",
|
|
37
|
+
"raw hosted QASM review",
|
|
38
|
+
"persistent Explorer history",
|
|
39
|
+
"public hosted MCP",
|
|
40
|
+
"productized Claude Code or Codex integration",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def list_tools() -> list[dict[str, Any]]:
|
|
45
|
+
return [
|
|
46
|
+
_tool_schema(
|
|
47
|
+
"qcoder_analyze_circuit",
|
|
48
|
+
"Analyze one explicit local OpenQASM 2 file and return share-safe qCoder evidence.",
|
|
49
|
+
{"qasm_path": {"type": "string"}, "include_guidance": {"type": "boolean"}, "include_profiles": {"type": "boolean"}},
|
|
50
|
+
["qasm_path"],
|
|
51
|
+
),
|
|
52
|
+
_tool_schema(
|
|
53
|
+
"qcoder_generate_context_pack",
|
|
54
|
+
"Build a share-safe preflight context pack for one explicit local OpenQASM 2 file.",
|
|
55
|
+
{"qasm_path": {"type": "string"}, "include_guidance": {"type": "boolean"}, "include_profiles": {"type": "boolean"}},
|
|
56
|
+
["qasm_path"],
|
|
57
|
+
),
|
|
58
|
+
_tool_schema(
|
|
59
|
+
"qcoder_review_counts",
|
|
60
|
+
"Review one explicit local counts JSON file, optionally linked to one preflight context JSON.",
|
|
61
|
+
{
|
|
62
|
+
"counts_json": {"type": "string"},
|
|
63
|
+
"counts_format": {"type": "string", "enum": ["qcoder", "qiskit_counts"]},
|
|
64
|
+
"preflight_json": {"type": "string"},
|
|
65
|
+
},
|
|
66
|
+
["counts_json"],
|
|
67
|
+
),
|
|
68
|
+
_tool_schema(
|
|
69
|
+
"qcoder_explain_findings",
|
|
70
|
+
"Explain bounded findings from a qCoder artifact object supplied directly to the tool.",
|
|
71
|
+
{"artifact": {"type": "object"}},
|
|
72
|
+
["artifact"],
|
|
73
|
+
),
|
|
74
|
+
_tool_schema(
|
|
75
|
+
"qcoder_claim_boundaries",
|
|
76
|
+
"Return qCoder Explorer Launch claim boundaries and unsupported claims.",
|
|
77
|
+
{"artifact": {"type": "object"}},
|
|
78
|
+
[],
|
|
79
|
+
),
|
|
80
|
+
_tool_schema(
|
|
81
|
+
"qcoder_next_checks",
|
|
82
|
+
"Recommend bounded next checks from a qCoder artifact object supplied directly to the tool.",
|
|
83
|
+
{"artifact": {"type": "object"}},
|
|
84
|
+
["artifact"],
|
|
85
|
+
),
|
|
86
|
+
_tool_schema(
|
|
87
|
+
"qcoder_generate_share_safe_artifact",
|
|
88
|
+
"Sanitize a supplied artifact object into a share-safe qCoder artifact.",
|
|
89
|
+
{"artifact": {"type": "object"}},
|
|
90
|
+
["artifact"],
|
|
91
|
+
),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _tool_schema(name: str, description: str, properties: dict[str, Any], required: list[str]) -> dict[str, Any]:
|
|
96
|
+
return {
|
|
97
|
+
"name": name,
|
|
98
|
+
"description": description,
|
|
99
|
+
"inputSchema": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"properties": properties,
|
|
102
|
+
"required": required,
|
|
103
|
+
"additionalProperties": False,
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def call_tool(name: str, arguments: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
109
|
+
args = arguments or {}
|
|
110
|
+
if name == "qcoder_analyze_circuit":
|
|
111
|
+
return _analyze_circuit(args)
|
|
112
|
+
if name == "qcoder_generate_context_pack":
|
|
113
|
+
return _generate_context_pack(args)
|
|
114
|
+
if name == "qcoder_review_counts":
|
|
115
|
+
return _review_counts(args)
|
|
116
|
+
if name == "qcoder_explain_findings":
|
|
117
|
+
return _explain_findings(args)
|
|
118
|
+
if name == "qcoder_claim_boundaries":
|
|
119
|
+
return _claim_boundaries(args)
|
|
120
|
+
if name == "qcoder_next_checks":
|
|
121
|
+
return _next_checks(args)
|
|
122
|
+
if name == "qcoder_generate_share_safe_artifact":
|
|
123
|
+
return _generate_share_safe_artifact(args)
|
|
124
|
+
raise ValueError(f"unknown qCoder MCP tool: {name}")
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _explicit_file_arg(args: dict[str, Any], key: str) -> str:
|
|
128
|
+
value = args.get(key)
|
|
129
|
+
if not isinstance(value, str) or not value.strip():
|
|
130
|
+
raise ValueError(f"{key} is required")
|
|
131
|
+
path = Path(value).expanduser()
|
|
132
|
+
if not path.is_file():
|
|
133
|
+
raise ValueError(f"{key} must be an explicit readable file")
|
|
134
|
+
if path.stat().st_size > MAX_MCP_INPUT_BYTES:
|
|
135
|
+
raise ValueError(f"{key} exceeds the local MCP input size cap of {MAX_MCP_INPUT_BYTES} bytes")
|
|
136
|
+
return str(path)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _artifact_arg(args: dict[str, Any]) -> dict[str, Any]:
|
|
140
|
+
artifact = args.get("artifact")
|
|
141
|
+
if not isinstance(artifact, dict):
|
|
142
|
+
raise ValueError("artifact must be a JSON object supplied directly to the tool")
|
|
143
|
+
return artifact
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _result(payload: dict[str, Any], *, markdown: str | None = None) -> dict[str, Any]:
|
|
147
|
+
text = json.dumps(payload, indent=2, sort_keys=True)
|
|
148
|
+
content = [{"type": "text", "text": text}]
|
|
149
|
+
if markdown is not None:
|
|
150
|
+
content.append({"type": "text", "text": markdown})
|
|
151
|
+
return {"content": content, "structuredContent": payload}
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _analyze_circuit(args: dict[str, Any]) -> dict[str, Any]:
|
|
155
|
+
qasm_path = _explicit_file_arg(args, "qasm_path")
|
|
156
|
+
try:
|
|
157
|
+
payload = analyze_qasm_json(
|
|
158
|
+
qasm_path,
|
|
159
|
+
include_guidance=bool(args.get("include_guidance", True)),
|
|
160
|
+
include_profiles=bool(args.get("include_profiles", True)),
|
|
161
|
+
)
|
|
162
|
+
except NotImplementedError as exc:
|
|
163
|
+
if "OpenQASM 3" in str(exc):
|
|
164
|
+
raise ValueError("local qCoder MCP currently supports OpenQASM 2; QASM3 is deferred") from exc
|
|
165
|
+
raise
|
|
166
|
+
payload = make_share_safe_payload(payload)
|
|
167
|
+
payload["mcp_boundary"] = _mcp_boundary()
|
|
168
|
+
return _result(payload)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _generate_context_pack(args: dict[str, Any]) -> dict[str, Any]:
|
|
172
|
+
qasm_path = _explicit_file_arg(args, "qasm_path")
|
|
173
|
+
try:
|
|
174
|
+
payload = build_preflight_context(
|
|
175
|
+
qasm_path,
|
|
176
|
+
include_guidance=bool(args.get("include_guidance", True)),
|
|
177
|
+
include_profiles=bool(args.get("include_profiles", True)),
|
|
178
|
+
)
|
|
179
|
+
except NotImplementedError as exc:
|
|
180
|
+
if "OpenQASM 3" in str(exc):
|
|
181
|
+
raise ValueError("local qCoder MCP currently supports OpenQASM 2; QASM3 is deferred") from exc
|
|
182
|
+
raise
|
|
183
|
+
payload = make_share_safe_payload(payload)
|
|
184
|
+
payload["evidence_grounded_coding_loop"] = _evidence_loop()
|
|
185
|
+
payload["mcp_boundary"] = _mcp_boundary()
|
|
186
|
+
return _result(payload, markdown=render_context_markdown(payload))
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _review_counts(args: dict[str, Any]) -> dict[str, Any]:
|
|
190
|
+
counts_json = _explicit_file_arg(args, "counts_json")
|
|
191
|
+
counts_format = args.get("counts_format", "qiskit_counts")
|
|
192
|
+
if counts_format not in {"qcoder", "qiskit_counts"}:
|
|
193
|
+
raise ValueError("counts_format must be qcoder or qiskit_counts")
|
|
194
|
+
preflight_json = args.get("preflight_json")
|
|
195
|
+
preflight = _explicit_file_arg(args, "preflight_json") if preflight_json else None
|
|
196
|
+
payload = build_execution_review(
|
|
197
|
+
counts_json=counts_json,
|
|
198
|
+
counts_format=str(counts_format),
|
|
199
|
+
preflight_json=preflight,
|
|
200
|
+
)
|
|
201
|
+
payload = make_share_safe_payload(payload)
|
|
202
|
+
payload["mcp_boundary"] = _mcp_boundary()
|
|
203
|
+
return _result(payload, markdown=render_review_markdown(payload))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _explain_findings(args: dict[str, Any]) -> dict[str, Any]:
|
|
207
|
+
artifact = _artifact_arg(args)
|
|
208
|
+
feature_map = _feature_map_from_artifact(artifact)
|
|
209
|
+
summary = {
|
|
210
|
+
"schema_id": "qcoder.mcp.explain_findings.v0",
|
|
211
|
+
"status": "ok",
|
|
212
|
+
"finding_summary": _finding_lines(feature_map),
|
|
213
|
+
"evidence_basis": "qCoder structural evidence supplied by the user or generated from an explicit local input.",
|
|
214
|
+
"limits": [
|
|
215
|
+
"This is not simulator or hardware execution evidence.",
|
|
216
|
+
"This is not a correctness proof.",
|
|
217
|
+
"This is not runtime, fidelity, backend, QPU, or quantum-advantage prediction.",
|
|
218
|
+
],
|
|
219
|
+
"safe_framing": SAFE_MARKETING_FRAME,
|
|
220
|
+
"mcp_boundary": _mcp_boundary(),
|
|
221
|
+
}
|
|
222
|
+
return _result(make_share_safe_payload(summary))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _claim_boundaries(args: dict[str, Any]) -> dict[str, Any]:
|
|
226
|
+
artifact = args.get("artifact") if isinstance(args.get("artifact"), dict) else {}
|
|
227
|
+
feature_map = _feature_map_from_artifact(artifact) if artifact else {}
|
|
228
|
+
payload = {
|
|
229
|
+
"schema_id": "qcoder.mcp.claim_boundaries.v0",
|
|
230
|
+
"status": "ok",
|
|
231
|
+
"mcp_supported_claims": SUPPORTED_CLAIMS,
|
|
232
|
+
"mcp_unsupported_claims": UNSUPPORTED_CLAIMS,
|
|
233
|
+
"artifact_present": bool(artifact),
|
|
234
|
+
"artifact_summary": {
|
|
235
|
+
"feature_map_present": bool(feature_map),
|
|
236
|
+
"feature_keys_present": sorted(str(key) for key in feature_map)[:12],
|
|
237
|
+
"artifact_type": artifact.get("artifact_type") if isinstance(artifact.get("artifact_type"), str) else None,
|
|
238
|
+
"schema_id": artifact.get("schema_id") if isinstance(artifact.get("schema_id"), str) else None,
|
|
239
|
+
},
|
|
240
|
+
"manual_artifact_loop_launch_required": True,
|
|
241
|
+
"local_cursor_mcp_complements_artifacts": True,
|
|
242
|
+
"oss_boundary": "OSS remains local, no-account, no-token.",
|
|
243
|
+
"explorer_boundary": "Explorer is account-backed guidance over local/share-safe evidence.",
|
|
244
|
+
"pro_boundary": "Pro owns fidelity prediction, QPU/backend guidance, backend ranking, and validated runtime/shot modeling.",
|
|
245
|
+
"mcp_boundary": _mcp_boundary(),
|
|
246
|
+
}
|
|
247
|
+
return _result(make_share_safe_payload(payload))
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _next_checks(args: dict[str, Any]) -> dict[str, Any]:
|
|
251
|
+
artifact = _artifact_arg(args)
|
|
252
|
+
feature_map = _feature_map_from_artifact(artifact)
|
|
253
|
+
checks = [
|
|
254
|
+
"Generate or refresh a share-safe qCoder preflight context artifact.",
|
|
255
|
+
"If you have measurement counts, run qcoder review with --format qiskit_counts or qcoder.",
|
|
256
|
+
"Share only the share-safe summary with Cursor, ChatGPT, teammates, issues, or forums.",
|
|
257
|
+
]
|
|
258
|
+
if _number(feature_map.get("n_measure_ops")) == 0:
|
|
259
|
+
checks.append("Add or verify measurement operations before interpreting counts.")
|
|
260
|
+
if _number(feature_map.get("n_2q_gate_ops")) > 0:
|
|
261
|
+
checks.append("Review two-qubit structure and entangling depth before choosing simulator settings.")
|
|
262
|
+
if _number(feature_map.get("n_param_ops")) > 0:
|
|
263
|
+
checks.append("Confirm parameter binding and sweep assumptions outside qCoder before execution.")
|
|
264
|
+
payload = {
|
|
265
|
+
"schema_id": "qcoder.mcp.next_checks.v0",
|
|
266
|
+
"status": "ok",
|
|
267
|
+
"next_checks": checks,
|
|
268
|
+
"limits": [
|
|
269
|
+
"Recommendations are bounded next checks, not autonomous repair.",
|
|
270
|
+
"No backend, QPU, runtime, fidelity, correctness, or advantage claim is made.",
|
|
271
|
+
],
|
|
272
|
+
"mcp_boundary": _mcp_boundary(),
|
|
273
|
+
}
|
|
274
|
+
return _result(make_share_safe_payload(payload))
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _generate_share_safe_artifact(args: dict[str, Any]) -> dict[str, Any]:
|
|
278
|
+
payload = make_share_safe_payload(_artifact_arg(args))
|
|
279
|
+
payload["mcp_boundary"] = _mcp_boundary()
|
|
280
|
+
return _result(payload)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _feature_map_from_artifact(artifact: dict[str, Any]) -> dict[str, Any]:
|
|
284
|
+
if isinstance(artifact.get("feature_map"), dict):
|
|
285
|
+
return artifact["feature_map"]
|
|
286
|
+
analysis = artifact.get("analysis")
|
|
287
|
+
if isinstance(analysis, dict) and isinstance(analysis.get("feature_map"), dict):
|
|
288
|
+
return analysis["feature_map"]
|
|
289
|
+
derived = artifact.get("derived_analysis")
|
|
290
|
+
if isinstance(derived, dict) and isinstance(derived.get("selected_feature_map"), dict):
|
|
291
|
+
return derived["selected_feature_map"]
|
|
292
|
+
return {}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def _finding_lines(feature_map: dict[str, Any]) -> list[str]:
|
|
296
|
+
if not feature_map:
|
|
297
|
+
return ["No qCoder feature_map was supplied; generate qCoder evidence first."]
|
|
298
|
+
lines: list[str] = []
|
|
299
|
+
for key in ("n_qubits", "n_ops", "real_depth", "entangling_depth", "n_2q_gate_ops", "n_measure_ops"):
|
|
300
|
+
if key in feature_map:
|
|
301
|
+
lines.append(f"{key}: {feature_map[key]}")
|
|
302
|
+
return lines or ["qCoder evidence was supplied, but no launch summary fields were present."]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _number(value: Any) -> float:
|
|
306
|
+
if isinstance(value, bool):
|
|
307
|
+
return 0.0
|
|
308
|
+
if isinstance(value, (int, float)):
|
|
309
|
+
return float(value)
|
|
310
|
+
return 0.0
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def _mcp_boundary() -> dict[str, Any]:
|
|
314
|
+
return {
|
|
315
|
+
"local": True,
|
|
316
|
+
"read_only": True,
|
|
317
|
+
"explicit_user_selected_inputs_only": True,
|
|
318
|
+
"modifies_user_code": False,
|
|
319
|
+
"executes_circuits": False,
|
|
320
|
+
"submits_to_qpu_or_simulator": False,
|
|
321
|
+
"requires_token": False,
|
|
322
|
+
"uses_live_service": False,
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _evidence_loop() -> list[str]:
|
|
327
|
+
return [
|
|
328
|
+
"write/edit circuit",
|
|
329
|
+
"generate qCoder evidence locally",
|
|
330
|
+
"use Explorer to produce plain-English review and next-step guidance",
|
|
331
|
+
"share clean Explorer summary with Cursor/ChatGPT",
|
|
332
|
+
"revise circuit",
|
|
333
|
+
"review again",
|
|
334
|
+
]
|
|
@@ -88,7 +88,7 @@ def analyze_qasm(
|
|
|
88
88
|
if mirror_artifacts_dir:
|
|
89
89
|
from qcoder.core.qasm2.adjoint_eligibility import check_adjoint_eligibility
|
|
90
90
|
|
|
91
|
-
qasm_text = Path(qasm_path).read_text(encoding="utf-8", errors="replace")
|
|
91
|
+
qasm_text = Path(qasm_path).read_text(encoding="utf-8-sig", errors="replace")
|
|
92
92
|
content_hash = hashlib.sha256(qasm_text.encode("utf-8")).hexdigest()
|
|
93
93
|
eligibility = check_adjoint_eligibility(qasm_text, include_mirror_qasm=True)
|
|
94
94
|
mirror_qasm_ref = None
|
|
@@ -20,7 +20,7 @@ def _qcoder_version() -> str:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
def _load_json(path: str) -> dict[str, Any]:
|
|
23
|
-
data = json.loads(Path(path).read_text(encoding="utf-8"))
|
|
23
|
+
data = json.loads(Path(path).read_text(encoding="utf-8-sig"))
|
|
24
24
|
if not isinstance(data, dict):
|
|
25
25
|
raise ValueError(f"JSON root must be an object: {path}")
|
|
26
26
|
return data
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qcoder
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.0a7
|
|
4
4
|
Summary: Quantum circuit analysis and structured feature extraction tools.
|
|
5
5
|
Author-email: Quantum Ready Solutions <support@qcoder.ai>
|
|
6
6
|
Maintainer-email: Quantum Ready Solutions <support@qcoder.ai>
|
|
@@ -15,6 +15,8 @@ Classifier: Intended Audience :: Science/Research
|
|
|
15
15
|
Classifier: Topic :: Scientific/Engineering
|
|
16
16
|
Classifier: Programming Language :: Python :: 3
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
20
|
Requires-Python: >=3.11
|
|
19
21
|
Description-Content-Type: text/markdown
|
|
20
22
|
License-File: LICENSE
|
|
@@ -40,6 +42,7 @@ The current public local path is **qCoder OSS**. OSS commands run locally and do
|
|
|
40
42
|
- `qcoder batch`
|
|
41
43
|
- `qcoder context`
|
|
42
44
|
- `qcoder review`
|
|
45
|
+
- `qcoder mcp` (local read-only Cursor MCP tools)
|
|
43
46
|
- `qcoder explorer` (Explorer Beta account-backed status/demo/evidence checks)
|
|
44
47
|
- `qcoder student` (temporary compatibility alias for Explorer Beta)
|
|
45
48
|
- `qcoder pro` (archived pilot/bootstrap client contract; non-confidential local plumbing only, not a current public product path)
|
|
@@ -87,6 +90,14 @@ qcoder review --counts-json counts.json --format qiskit_counts --preflight-json
|
|
|
87
90
|
|
|
88
91
|
Share-safe mode is designed for safer sharing: it redacts local paths and token/header-like strings, adds `share_safe=true`, and marks raw QASM/local paths/tokens as not included. Review artifacts before sharing; this is not a guarantee that all sensitive project content has been removed.
|
|
89
92
|
|
|
93
|
+
Local Cursor MCP:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
qcoder mcp serve
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The MCP server uses explicit user-selected local inputs only. It reads, inspects, explains, bounds, and recommends; it does not execute circuits, modify user code, read arbitrary files, call live services, or require tokens. See [Explorer Evidence-Grounded Coding Loop](examples/08_explorer_evidence_grounded_coding_loop.md) for the manual artifact loop, Cursor MCP setup, and claim boundaries.
|
|
100
|
+
|
|
90
101
|
Explorer Beta compatibility checks and archived Pro bootstrap:
|
|
91
102
|
|
|
92
103
|
```bash
|
|
@@ -56,6 +56,9 @@ src/qcoder/engines/review/markdown.py
|
|
|
56
56
|
src/qcoder/engines/review/qiskit_counts.py
|
|
57
57
|
src/qcoder/explorer/__init__.py
|
|
58
58
|
src/qcoder/explorer/derived_evidence.py
|
|
59
|
+
src/qcoder/mcp/__init__.py
|
|
60
|
+
src/qcoder/mcp/server.py
|
|
61
|
+
src/qcoder/mcp/tools.py
|
|
59
62
|
src/qcoder/model_packs/__init__.py
|
|
60
63
|
src/qcoder/model_packs/resource_guidance_local_v0.json
|
|
61
64
|
src/qcoder/pipelines/analyze.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/__init__.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/cirq_intake.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/pennylane_intake.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/adapters/qiskit_intake.py
RENAMED
|
File without changes
|
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/compute_v0.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/glossary_v0.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/features/schema_v0.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/entangling_layers.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/gate_set_stats.py
RENAMED
|
File without changes
|
{qcoder-0.5.0a6 → qcoder-0.5.0a7}/src/qcoder/engines/feature_extraction/reps/interaction_graph.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|