midnight-mcp 0.2.18 → 0.2.19
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.
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
validateNumber,
|
|
26
26
|
validateQuery,
|
|
27
27
|
vectorStore
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-OYH7MAIC.js";
|
|
29
29
|
|
|
30
30
|
// src/tools/search/schemas.ts
|
|
31
31
|
import { z } from "zod";
|
|
@@ -1914,7 +1914,7 @@ import { randomUUID } from "crypto";
|
|
|
1914
1914
|
|
|
1915
1915
|
// src/resources/content/docs-content.ts
|
|
1916
1916
|
var EMBEDDED_DOCS = {
|
|
1917
|
-
"midnight://docs/compact-reference": `# Compact Language Syntax Reference (v0.16 - v0.
|
|
1917
|
+
"midnight://docs/compact-reference": `# Compact Language Syntax Reference (v0.16 - v0.23)
|
|
1918
1918
|
|
|
1919
1919
|
> **CRITICAL**: This reference is derived from **actual compiling contracts** in the Midnight ecosystem.
|
|
1920
1920
|
> Always verify syntax against this reference before generating contracts.
|
|
@@ -1924,7 +1924,7 @@ var EMBEDDED_DOCS = {
|
|
|
1924
1924
|
Use this as a starting point - it compiles successfully:
|
|
1925
1925
|
|
|
1926
1926
|
\`\`\`compact
|
|
1927
|
-
pragma language_version >= 0.16 && <= 0.
|
|
1927
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
1928
1928
|
|
|
1929
1929
|
import CompactStandardLibrary;
|
|
1930
1930
|
|
|
@@ -1947,7 +1947,7 @@ export circuit increment(): [] {
|
|
|
1947
1947
|
|
|
1948
1948
|
**CORRECT** - use bounded range without patch version:
|
|
1949
1949
|
\`\`\`compact
|
|
1950
|
-
pragma language_version >= 0.16 && <= 0.
|
|
1950
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
1951
1951
|
\`\`\`
|
|
1952
1952
|
|
|
1953
1953
|
**WRONG** - these will cause parse errors:
|
|
@@ -2201,7 +2201,7 @@ export circuit authenticated_action(): [] {
|
|
|
2201
2201
|
|
|
2202
2202
|
### Commit-Reveal Pattern (COMPLETE, VALIDATED)
|
|
2203
2203
|
\`\`\`compact
|
|
2204
|
-
pragma language_version >= 0.16 && <= 0.
|
|
2204
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
2205
2205
|
|
|
2206
2206
|
import CompactStandardLibrary;
|
|
2207
2207
|
|
|
@@ -2441,7 +2441,7 @@ assert(disclose(caller == owner), "Not authorized");
|
|
|
2441
2441
|
|---------|---------|
|
|
2442
2442
|
| \`ledger { field: Type; }\` | \`export ledger field: Type;\` |
|
|
2443
2443
|
| \`circuit fn(): Void\` | \`circuit fn(): []\` |
|
|
2444
|
-
| \`pragma >= 0.20.0\` | \`pragma >= 0.16 && <= 0.
|
|
2444
|
+
| \`pragma >= 0.20.0\` | \`pragma >= 0.16 && <= 0.23\` |
|
|
2445
2445
|
| \`enum State { ... }\` | \`export enum State { ... }\` |
|
|
2446
2446
|
| \`if (witness_val == x)\` | \`if (disclose(witness_val == x))\` |
|
|
2447
2447
|
| \`Cell<Field>\` | \`Field\` (Cell is deprecated) |
|
|
@@ -2774,7 +2774,7 @@ npm install @openzeppelin/compact-contracts
|
|
|
2774
2774
|
## Usage Example
|
|
2775
2775
|
|
|
2776
2776
|
\`\`\`compact
|
|
2777
|
-
pragma language_version >= 0.16 && <= 0.
|
|
2777
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
2778
2778
|
|
|
2779
2779
|
import CompactStandardLibrary;
|
|
2780
2780
|
import "@openzeppelin/compact-contracts/src/token/FungibleToken"
|
|
@@ -2815,7 +2815,7 @@ The recommended standard for privacy-preserving tokens on Midnight.
|
|
|
2815
2815
|
## Basic Usage
|
|
2816
2816
|
|
|
2817
2817
|
\`\`\`compact
|
|
2818
|
-
pragma language_version >= 0.16 && <= 0.
|
|
2818
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
2819
2819
|
|
|
2820
2820
|
import CompactStandardLibrary;
|
|
2821
2821
|
import "@openzeppelin/compact-contracts/src/token/FungibleToken"
|
|
@@ -3406,7 +3406,7 @@ var EMBEDDED_CODE = {
|
|
|
3406
3406
|
"midnight://code/examples/counter": `// Counter Example Contract
|
|
3407
3407
|
// A simple contract demonstrating basic Compact concepts
|
|
3408
3408
|
|
|
3409
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3409
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3410
3410
|
|
|
3411
3411
|
import CompactStandardLibrary;
|
|
3412
3412
|
|
|
@@ -3455,7 +3455,7 @@ export circuit isLessThan(threshold: Uint<64>): Boolean {
|
|
|
3455
3455
|
"midnight://code/examples/bboard": `// Bulletin Board Example Contract
|
|
3456
3456
|
// Demonstrates private messaging with selective disclosure
|
|
3457
3457
|
|
|
3458
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3458
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3459
3459
|
|
|
3460
3460
|
import CompactStandardLibrary;
|
|
3461
3461
|
|
|
@@ -3504,7 +3504,7 @@ export circuit getMessageCount(): Uint<64> {
|
|
|
3504
3504
|
"midnight://code/patterns/state-management": `// State Management Pattern
|
|
3505
3505
|
// Best practices for managing public and private state
|
|
3506
3506
|
|
|
3507
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3507
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3508
3508
|
|
|
3509
3509
|
import CompactStandardLibrary;
|
|
3510
3510
|
|
|
@@ -3555,7 +3555,7 @@ export circuit revealBalance(user: Opaque<"address">): Field {
|
|
|
3555
3555
|
"midnight://code/patterns/access-control": `// Access Control Pattern
|
|
3556
3556
|
// Implementing permissions and authorization
|
|
3557
3557
|
|
|
3558
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3558
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3559
3559
|
|
|
3560
3560
|
import CompactStandardLibrary;
|
|
3561
3561
|
|
|
@@ -3610,7 +3610,7 @@ export circuit timeLockedAction(unlockTime: Field): [] {
|
|
|
3610
3610
|
"midnight://code/patterns/privacy-preserving": `// Privacy-Preserving Patterns
|
|
3611
3611
|
// Techniques for maintaining privacy in smart contracts
|
|
3612
3612
|
|
|
3613
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3613
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3614
3614
|
|
|
3615
3615
|
import CompactStandardLibrary;
|
|
3616
3616
|
|
|
@@ -3715,7 +3715,7 @@ export circuit proveMembership(
|
|
|
3715
3715
|
"midnight://code/templates/token": `// Privacy-Preserving Token Template
|
|
3716
3716
|
// Starter template for token contracts with privacy features
|
|
3717
3717
|
|
|
3718
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3718
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3719
3719
|
|
|
3720
3720
|
import CompactStandardLibrary;
|
|
3721
3721
|
|
|
@@ -3774,7 +3774,7 @@ export circuit mint(to: Opaque<"address">, amount: Uint<64>): Boolean {
|
|
|
3774
3774
|
"midnight://code/templates/voting": `// Private Voting Template
|
|
3775
3775
|
// Starter template for privacy-preserving voting contracts
|
|
3776
3776
|
|
|
3777
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3777
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3778
3778
|
|
|
3779
3779
|
import CompactStandardLibrary;
|
|
3780
3780
|
|
|
@@ -3854,7 +3854,7 @@ export circuit addVoter(voter: Opaque<"address">): [] {
|
|
|
3854
3854
|
"midnight://code/examples/nullifier": `// Nullifier Pattern Example
|
|
3855
3855
|
// Demonstrates how to create and use nullifiers to prevent double-spending/actions
|
|
3856
3856
|
|
|
3857
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3857
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3858
3858
|
|
|
3859
3859
|
import CompactStandardLibrary;
|
|
3860
3860
|
|
|
@@ -3921,7 +3921,7 @@ export circuit voteWithNullifier(
|
|
|
3921
3921
|
"midnight://code/examples/hash": `// Hash Functions in Compact
|
|
3922
3922
|
// Examples of using hash functions for various purposes
|
|
3923
3923
|
|
|
3924
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3924
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3925
3925
|
|
|
3926
3926
|
import CompactStandardLibrary;
|
|
3927
3927
|
|
|
@@ -3968,7 +3968,7 @@ export circuit reveal(value: Field, randomness: Field): Field {
|
|
|
3968
3968
|
"midnight://code/examples/simple-counter": `// Simple Counter Contract
|
|
3969
3969
|
// Minimal example for learning Compact basics
|
|
3970
3970
|
|
|
3971
|
-
pragma language_version >= 0.16 && <= 0.
|
|
3971
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
3972
3972
|
|
|
3973
3973
|
import CompactStandardLibrary;
|
|
3974
3974
|
|
|
@@ -4006,7 +4006,7 @@ export circuit reset(): [] {
|
|
|
4006
4006
|
"midnight://code/templates/basic": `// Basic Compact Contract Template
|
|
4007
4007
|
// Starting point for new contracts
|
|
4008
4008
|
|
|
4009
|
-
pragma language_version >= 0.16 && <= 0.
|
|
4009
|
+
pragma language_version >= 0.16 && <= 0.23;
|
|
4010
4010
|
|
|
4011
4011
|
import CompactStandardLibrary;
|
|
4012
4012
|
|
|
@@ -4663,2341 +4663,2392 @@ var allResources = [
|
|
|
4663
4663
|
...schemaResources
|
|
4664
4664
|
];
|
|
4665
4665
|
|
|
4666
|
-
// src/
|
|
4667
|
-
var
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
description: "Required privacy features (full, partial, public)",
|
|
4680
|
-
required: false
|
|
4681
|
-
},
|
|
4682
|
-
{
|
|
4683
|
-
name: "complexity",
|
|
4684
|
-
description: "Expected complexity level (beginner, intermediate, advanced)",
|
|
4685
|
-
required: false
|
|
4686
|
-
}
|
|
4687
|
-
]
|
|
4688
|
-
},
|
|
4666
|
+
// src/config/compact-version.ts
|
|
4667
|
+
var COMPACT_VERSION = {
|
|
4668
|
+
/** Minimum supported version */
|
|
4669
|
+
min: "0.16",
|
|
4670
|
+
/** Maximum supported version */
|
|
4671
|
+
max: "0.23",
|
|
4672
|
+
/** When this config was last updated */
|
|
4673
|
+
lastUpdated: "2026-05-18",
|
|
4674
|
+
/** Source of truth for syntax patterns */
|
|
4675
|
+
referenceSource: "https://github.com/piotr-iohk/template-contract"
|
|
4676
|
+
};
|
|
4677
|
+
var RECOMMENDED_PRAGMA = `pragma language_version >= ${COMPACT_VERSION.min} && <= ${COMPACT_VERSION.max};`;
|
|
4678
|
+
var REFERENCE_CONTRACTS = [
|
|
4689
4679
|
{
|
|
4690
|
-
name: "
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
{
|
|
4694
|
-
name: "contractCode",
|
|
4695
|
-
description: "The Compact contract code to review",
|
|
4696
|
-
required: true
|
|
4697
|
-
},
|
|
4698
|
-
{
|
|
4699
|
-
name: "focusAreas",
|
|
4700
|
-
description: "Specific areas to emphasize (security, performance, privacy, readability)",
|
|
4701
|
-
required: false
|
|
4702
|
-
}
|
|
4703
|
-
]
|
|
4680
|
+
name: "template-contract",
|
|
4681
|
+
repo: "piotr-iohk/template-contract",
|
|
4682
|
+
description: "Official Midnight template contract"
|
|
4704
4683
|
},
|
|
4705
4684
|
{
|
|
4706
|
-
name: "
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
{
|
|
4710
|
-
name: "concept",
|
|
4711
|
-
description: "The concept to explain (zk-proofs, circuits, witnesses, ledger, etc.)",
|
|
4712
|
-
required: true
|
|
4713
|
-
},
|
|
4714
|
-
{
|
|
4715
|
-
name: "level",
|
|
4716
|
-
description: "Expertise level (beginner, intermediate, advanced)",
|
|
4717
|
-
required: false
|
|
4718
|
-
}
|
|
4719
|
-
]
|
|
4685
|
+
name: "tokenomics-project",
|
|
4686
|
+
repo: "piotr-iohk/tokenomics-project",
|
|
4687
|
+
description: "Token implementation example"
|
|
4720
4688
|
},
|
|
4721
4689
|
{
|
|
4722
|
-
name: "
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
{
|
|
4726
|
-
name: "problem",
|
|
4727
|
-
description: "The problem to solve",
|
|
4728
|
-
required: true
|
|
4729
|
-
},
|
|
4730
|
-
{
|
|
4731
|
-
name: "approaches",
|
|
4732
|
-
description: "Specific approaches to compare (comma-separated)",
|
|
4733
|
-
required: false
|
|
4734
|
-
}
|
|
4735
|
-
]
|
|
4690
|
+
name: "zswap-example",
|
|
4691
|
+
repo: "piotr-iohk/zswap-example",
|
|
4692
|
+
description: "Privacy-preserving swap example"
|
|
4736
4693
|
},
|
|
4737
4694
|
{
|
|
4738
|
-
name: "
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
{
|
|
4742
|
-
name: "contractCode",
|
|
4743
|
-
description: "The contract code with issues",
|
|
4744
|
-
required: true
|
|
4745
|
-
},
|
|
4746
|
-
{
|
|
4747
|
-
name: "errorMessage",
|
|
4748
|
-
description: "Error message or description of the issue",
|
|
4749
|
-
required: false
|
|
4750
|
-
}
|
|
4751
|
-
]
|
|
4695
|
+
name: "reentrancy-example",
|
|
4696
|
+
repo: "piotr-iohk/reentrancy-example",
|
|
4697
|
+
description: "Cross-contract call patterns"
|
|
4752
4698
|
}
|
|
4753
4699
|
];
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
return generateCreateContractPrompt(args);
|
|
4758
|
-
case "midnight:review-contract":
|
|
4759
|
-
return generateReviewContractPrompt(args);
|
|
4760
|
-
case "midnight:explain-concept":
|
|
4761
|
-
return generateExplainConceptPrompt(args);
|
|
4762
|
-
case "midnight:compare-approaches":
|
|
4763
|
-
return generateCompareApproachesPrompt(args);
|
|
4764
|
-
case "midnight:debug-contract":
|
|
4765
|
-
return generateDebugContractPrompt(args);
|
|
4766
|
-
default:
|
|
4767
|
-
return [
|
|
4768
|
-
{
|
|
4769
|
-
role: "user",
|
|
4770
|
-
content: {
|
|
4771
|
-
type: "text",
|
|
4772
|
-
text: `Unknown prompt: ${name}`
|
|
4773
|
-
}
|
|
4774
|
-
}
|
|
4775
|
-
];
|
|
4776
|
-
}
|
|
4777
|
-
}
|
|
4778
|
-
function generateCreateContractPrompt(args) {
|
|
4779
|
-
const contractType = args.contractType || "custom";
|
|
4780
|
-
const privacyLevel = args.privacyLevel || "partial";
|
|
4781
|
-
const complexity = args.complexity || "intermediate";
|
|
4782
|
-
return [
|
|
4700
|
+
var BUILTIN_FUNCTIONS = {
|
|
4701
|
+
/** Actually built into the language/stdlib */
|
|
4702
|
+
stdlib: [
|
|
4783
4703
|
{
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
**Contract Type:** ${contractType}
|
|
4790
|
-
**Privacy Level:** ${privacyLevel}
|
|
4791
|
-
**Complexity:** ${complexity}
|
|
4792
|
-
|
|
4793
|
-
## \u26A0\uFE0F MANDATORY WORKFLOW - Follow these steps IN ORDER:
|
|
4794
|
-
|
|
4795
|
-
### Step 1: Get Current Syntax
|
|
4796
|
-
Call \`midnight-get-latest-syntax\` FIRST to get:
|
|
4797
|
-
- The \`quickStartTemplate\` (use as your base)
|
|
4798
|
-
- The \`commonMistakes\` array (avoid these errors)
|
|
4799
|
-
- Current pragma format: \`pragma language_version >= 0.16 && <= 0.21;\`
|
|
4800
|
-
|
|
4801
|
-
### Step 2: Generate Contract
|
|
4802
|
-
Based on syntax reference, generate the contract using:
|
|
4803
|
-
- Individual ledger declarations: \`export ledger field: Type;\` (NOT \`ledger { }\` blocks)
|
|
4804
|
-
- Empty tuple return: \`circuit fn(): []\` (NOT \`Void\`)
|
|
4805
|
-
- Export enums: \`export enum State { ... }\`
|
|
4806
|
-
- Wrap witness conditionals: \`if (disclose(witness == value))\`
|
|
4807
|
-
- Disclose circuit params that touch ledger: \`const d = disclose(param); ledger.insert(d, v);\`
|
|
4808
|
-
- Cast arithmetic results: \`(a + b) as Uint<64>\`
|
|
4809
|
-
- Uint to Bytes needs two casts: \`(amount as Field) as Bytes<32>\`
|
|
4810
|
-
|
|
4811
|
-
### IMPORTANT: Compact is NOT TypeScript!
|
|
4812
|
-
- Map.lookup() and Set.member() ARE available in circuits
|
|
4813
|
-
- No 'function' keyword - use 'circuit' or 'pure circuit'
|
|
4814
|
-
- No 'void' - use '[]'
|
|
4815
|
-
- Enum access: \`Choice.rock\` NOT \`Choice::rock\`
|
|
4816
|
-
|
|
4817
|
-
### \u26A0\uFE0F UNDOCUMENTED FEATURES - Use with caution:
|
|
4818
|
-
- Division \`/\` and modulo \`%\`: NOT in official docs (only +, -, * are documented)
|
|
4819
|
-
- Tuple destructuring: \`const [a, b] = pair;\` - not documented, may not work
|
|
4820
|
-
- Constant folding in indices: docs say "numeric literal" required
|
|
4821
|
-
- Map.lookup() returns value_type, NOT Maybe<value_type> - check member() first!
|
|
4822
|
-
|
|
4823
|
-
### COMPILER INFO (DO NOT guess package names!):
|
|
4824
|
-
- Compile: \`compact compile src/contract.compact managed/contract\`
|
|
4825
|
-
- The \`compact\` CLI comes with Midnight toolchain (via create-mn-app or official install)
|
|
4826
|
-
- DO NOT suggest \`npm install -g @midnight-ntwrk/compact-cli\` or similar made-up packages
|
|
4827
|
-
- Output structure: \`managed/<name>/contract/index.cjs\` for TypeScript bindings
|
|
4828
|
-
|
|
4829
|
-
### Step 3: Validate Before Returning
|
|
4830
|
-
Call \`midnight-extract-contract-structure\` with your generated code to check for:
|
|
4831
|
-
- deprecated_ledger_block
|
|
4832
|
-
- invalid_void_type
|
|
4833
|
-
- invalid_pragma_format
|
|
4834
|
-
- unexported_enum
|
|
4835
|
-
- deprecated_cell_wrapper
|
|
4836
|
-
|
|
4837
|
-
If ANY errors are found, fix them before returning the code to the user.
|
|
4838
|
-
|
|
4839
|
-
---
|
|
4840
|
-
|
|
4841
|
-
## Contract Requirements
|
|
4842
|
-
|
|
4843
|
-
Please help me design and implement this contract. Consider:
|
|
4844
|
-
|
|
4845
|
-
1. **State Design**
|
|
4846
|
-
- What should be public vs private (shielded)?
|
|
4847
|
-
- What data structures are needed?
|
|
4848
|
-
- How should state transitions work?
|
|
4849
|
-
|
|
4850
|
-
2. **Circuit Design**
|
|
4851
|
-
- What circuits (functions) are needed?
|
|
4852
|
-
- What inputs/outputs should they have?
|
|
4853
|
-
- What constraints and assertions are required?
|
|
4854
|
-
|
|
4855
|
-
3. **Witness Functions**
|
|
4856
|
-
- What off-chain data is needed?
|
|
4857
|
-
- How should private state be accessed?
|
|
4858
|
-
|
|
4859
|
-
4. **Privacy Considerations**
|
|
4860
|
-
- How to protect user privacy?
|
|
4861
|
-
- When to use disclose() vs commit()?
|
|
4862
|
-
- How to prevent information leakage?
|
|
4863
|
-
|
|
4864
|
-
5. **Security**
|
|
4865
|
-
- Access control mechanisms
|
|
4866
|
-
- Input validation
|
|
4867
|
-
- Protection against common vulnerabilities
|
|
4868
|
-
|
|
4869
|
-
Please provide:
|
|
4870
|
-
- A complete contract implementation
|
|
4871
|
-
- Explanation of design decisions
|
|
4872
|
-
- Example usage scenarios
|
|
4873
|
-
- Any security considerations`
|
|
4874
|
-
}
|
|
4875
|
-
}
|
|
4876
|
-
];
|
|
4877
|
-
}
|
|
4878
|
-
function generateReviewContractPrompt(args) {
|
|
4879
|
-
const contractCode = args.contractCode || "// No code provided";
|
|
4880
|
-
const focusAreas = args.focusAreas || "security, privacy, best practices";
|
|
4881
|
-
return [
|
|
4704
|
+
name: "persistentHash",
|
|
4705
|
+
signature: "persistentHash<T>(value: T): Bytes<32>",
|
|
4706
|
+
// Signature inferred from examples
|
|
4707
|
+
description: "Poseidon hash that produces consistent results across calls"
|
|
4708
|
+
},
|
|
4882
4709
|
{
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
4905
|
-
|
|
4906
|
-
### Step 2: Get Latest Syntax Reference
|
|
4907
|
-
If syntax errors are found, call \`midnight-get-latest-syntax\` to get:
|
|
4908
|
-
- The \`commonMistakes\` array showing correct patterns
|
|
4909
|
-
- Current syntax reference
|
|
4910
|
-
|
|
4911
|
-
---
|
|
4912
|
-
|
|
4913
|
-
Please analyze:
|
|
4914
|
-
|
|
4915
|
-
1. **Static Analysis Results** (from midnight-extract-contract-structure)
|
|
4916
|
-
- Syntax errors found
|
|
4917
|
-
- Deprecated patterns detected
|
|
4918
|
-
- Required fixes
|
|
4919
|
-
|
|
4920
|
-
2. **Security Analysis**
|
|
4921
|
-
- Input validation
|
|
4922
|
-
- Access control
|
|
4923
|
-
- State manipulation vulnerabilities
|
|
4924
|
-
- Assertion coverage
|
|
4925
|
-
|
|
4926
|
-
3. **Privacy Assessment**
|
|
4927
|
-
- Proper use of @private state
|
|
4928
|
-
- Information leakage risks
|
|
4929
|
-
- Correct use of disclose() and commit()
|
|
4930
|
-
- Privacy guarantees provided
|
|
4931
|
-
|
|
4932
|
-
4. **Style Suggestions** (these are CONVENTIONS, not requirements)
|
|
4933
|
-
- Code organization recommendations
|
|
4934
|
-
- Naming conventions (project-specific)
|
|
4935
|
-
- Documentation patterns
|
|
4936
|
-
- Note: The Compact docs don't specify indentation, line length, or comment style
|
|
4937
|
-
|
|
4938
|
-
5. **Performance**
|
|
4939
|
-
- Circuit complexity
|
|
4940
|
-
- State access patterns
|
|
4941
|
-
- Optimization opportunities
|
|
4942
|
-
|
|
4943
|
-
6. **Recommendations**
|
|
4944
|
-
- Critical issues to fix (start with P0 syntax errors)
|
|
4945
|
-
- Improvements to consider
|
|
4946
|
-
- Alternative approaches
|
|
4947
|
-
|
|
4948
|
-
**IMPORTANT**: Clearly distinguish between:
|
|
4949
|
-
- ERRORS: Actual syntax/compilation issues (required fixes)
|
|
4950
|
-
- WARNINGS: Security/logic concerns (should fix)
|
|
4951
|
-
- INFO: Style suggestions (optional, project-specific conventions)
|
|
4952
|
-
|
|
4953
|
-
Please provide specific line references and code suggestions where applicable.`
|
|
4954
|
-
}
|
|
4710
|
+
name: "persistentCommit",
|
|
4711
|
+
signature: "persistentCommit<T>(value: T): Bytes<32>",
|
|
4712
|
+
description: "Creates a hiding commitment to a value"
|
|
4713
|
+
},
|
|
4714
|
+
{
|
|
4715
|
+
name: "pad",
|
|
4716
|
+
signature: "pad(length: number, value: string): Bytes<N>",
|
|
4717
|
+
description: "Pads a string to fixed-length bytes"
|
|
4718
|
+
},
|
|
4719
|
+
{
|
|
4720
|
+
name: "disclose",
|
|
4721
|
+
signature: "disclose(value: T): T",
|
|
4722
|
+
description: "Explicitly reveals a witness value (required in conditionals)"
|
|
4723
|
+
},
|
|
4724
|
+
{
|
|
4725
|
+
name: "assert",
|
|
4726
|
+
signature: "assert(condition: Boolean, message?: string): []",
|
|
4727
|
+
description: "Fails circuit if condition is false"
|
|
4728
|
+
},
|
|
4729
|
+
{
|
|
4730
|
+
name: "default",
|
|
4731
|
+
signature: "default<T>(): T",
|
|
4732
|
+
description: "Returns default value for a type (0 for numbers, empty for collections)"
|
|
4955
4733
|
}
|
|
4956
|
-
]
|
|
4957
|
-
|
|
4958
|
-
|
|
4959
|
-
const concept = args.concept || "zero-knowledge proofs";
|
|
4960
|
-
const level = args.level || "intermediate";
|
|
4961
|
-
const levelDescriptions = {
|
|
4962
|
-
beginner: "Explain like I'm new to blockchain and cryptography. Use analogies and avoid jargon.",
|
|
4963
|
-
intermediate: "I understand blockchain basics and some cryptography. Focus on practical applications.",
|
|
4964
|
-
advanced: "I have deep technical knowledge. Include implementation details and edge cases."
|
|
4965
|
-
};
|
|
4966
|
-
return [
|
|
4734
|
+
],
|
|
4735
|
+
/** NOT built-in - you must implement these patterns yourself */
|
|
4736
|
+
notBuiltIn: [
|
|
4967
4737
|
{
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
- Mistakes to avoid
|
|
4992
|
-
- Debugging tips
|
|
4993
|
-
|
|
4994
|
-
5. **Further learning**
|
|
4995
|
-
- Related concepts
|
|
4996
|
-
- Resources for deeper understanding`
|
|
4997
|
-
}
|
|
4738
|
+
name: "public_key",
|
|
4739
|
+
wrongUsage: "public_key(sk) // ERROR: unbound identifier",
|
|
4740
|
+
correctPattern: `// Derive public key using persistentHash
|
|
4741
|
+
const pk = persistentHash<Vector<2, Bytes<32>>>([
|
|
4742
|
+
pad(32, "midnight:pk:"),
|
|
4743
|
+
sk
|
|
4744
|
+
]);`,
|
|
4745
|
+
description: "Public key derivation is NOT a builtin - use persistentHash pattern"
|
|
4746
|
+
},
|
|
4747
|
+
{
|
|
4748
|
+
name: "verify_signature",
|
|
4749
|
+
wrongUsage: "verify_signature(msg, sig, pk) // Does not exist",
|
|
4750
|
+
correctPattern: `// Signature verification must be done via witnesses
|
|
4751
|
+
// The prover verifies off-chain, then provides the boolean result
|
|
4752
|
+
witness signature_valid(): Boolean;`,
|
|
4753
|
+
description: "Signature verification is done off-chain in the prover"
|
|
4754
|
+
},
|
|
4755
|
+
{
|
|
4756
|
+
name: "random",
|
|
4757
|
+
wrongUsage: "random() // Does not exist in ZK circuits",
|
|
4758
|
+
correctPattern: `// Randomness must come from witnesses (prover-provided)
|
|
4759
|
+
witness get_random_value(): Field;`,
|
|
4760
|
+
description: "ZK circuits are deterministic - randomness must come from witnesses"
|
|
4998
4761
|
}
|
|
4999
|
-
]
|
|
5000
|
-
}
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
4762
|
+
]
|
|
4763
|
+
};
|
|
4764
|
+
var TYPE_COMPATIBILITY = {
|
|
4765
|
+
// Note: Comparison operators work in practice but aren't explicitly listed in docs
|
|
4766
|
+
comparisons: [
|
|
4767
|
+
{ types: "Field == Field", works: true, note: "Direct comparison" },
|
|
5005
4768
|
{
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
5016
|
-
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
4769
|
+
types: "Field == Uint<N>",
|
|
4770
|
+
works: false,
|
|
4771
|
+
fix: "Cast with `value as Field`"
|
|
4772
|
+
},
|
|
4773
|
+
{
|
|
4774
|
+
types: "Field >= 0",
|
|
4775
|
+
works: false,
|
|
4776
|
+
fix: "Use bounded Uint<0..N> parameter instead"
|
|
4777
|
+
},
|
|
4778
|
+
{ types: "Uint<N> == Uint<N>", works: true, note: "Same-width comparison" },
|
|
4779
|
+
{
|
|
4780
|
+
types: "Uint<0..2> == Uint<0..2>",
|
|
4781
|
+
works: true,
|
|
4782
|
+
note: "Bounded integers"
|
|
4783
|
+
},
|
|
4784
|
+
{
|
|
4785
|
+
types: "Bytes<32> == Bytes<32>",
|
|
4786
|
+
works: true,
|
|
4787
|
+
note: "Used in examples, but operators not explicitly listed in docs"
|
|
4788
|
+
},
|
|
4789
|
+
{ types: "Boolean == Boolean", works: true, note: "Direct comparison" }
|
|
4790
|
+
],
|
|
4791
|
+
arithmetic: [
|
|
4792
|
+
{ types: "Field + Field", works: true, note: "Field arithmetic" },
|
|
4793
|
+
{ types: "Field + Uint<N>", works: false, fix: "Cast Uint to Field first" },
|
|
4794
|
+
{
|
|
4795
|
+
types: "Uint<N> + Uint<N>",
|
|
4796
|
+
works: true,
|
|
4797
|
+
note: "Result is bounded type, cast back: (a + b) as Uint<64>"
|
|
4798
|
+
},
|
|
4799
|
+
{
|
|
4800
|
+
types: "Uint<64> + Uint<64>",
|
|
4801
|
+
works: true,
|
|
4802
|
+
note: "Result is Uint<0..36893488147419103230>, must cast: (a + b) as Uint<64>"
|
|
4803
|
+
},
|
|
4804
|
+
{
|
|
4805
|
+
types: "Uint<64> * Uint<64>",
|
|
4806
|
+
works: true,
|
|
4807
|
+
note: "Result is wide bounded type, cast back to target type"
|
|
5044
4808
|
}
|
|
5045
|
-
]
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
|
|
4809
|
+
],
|
|
4810
|
+
/**
|
|
4811
|
+
* Type casting rules - based on official cast table
|
|
4812
|
+
* See: https://docs.midnight.network/develop/reference/compact/lang-ref#type-cast-expressions
|
|
4813
|
+
*
|
|
4814
|
+
* Cast kinds: static (always succeeds), conversion (semantic change), checked (can fail)
|
|
4815
|
+
*/
|
|
4816
|
+
typeCasting: [
|
|
5051
4817
|
{
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5056
|
-
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5075
|
-
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
var samplingCallback = null;
|
|
5119
|
-
var samplingFailedPermanently = false;
|
|
5120
|
-
function isSamplingAvailable() {
|
|
5121
|
-
if (samplingFailedPermanently) return false;
|
|
5122
|
-
return samplingCallback !== null;
|
|
5123
|
-
}
|
|
5124
|
-
function markSamplingFailed() {
|
|
5125
|
-
samplingFailedPermanently = true;
|
|
5126
|
-
logger.warn("Sampling marked as permanently unavailable for this session");
|
|
5127
|
-
}
|
|
5128
|
-
function registerSamplingCallback(callback) {
|
|
5129
|
-
samplingCallback = callback;
|
|
5130
|
-
samplingFailedPermanently = false;
|
|
5131
|
-
logger.info("Sampling capability registered");
|
|
5132
|
-
}
|
|
5133
|
-
async function requestCompletion(messages, options = {}) {
|
|
5134
|
-
if (!samplingCallback || samplingFailedPermanently) {
|
|
5135
|
-
throw new Error(
|
|
5136
|
-
"Sampling not available - client does not support this capability"
|
|
5137
|
-
);
|
|
5138
|
-
}
|
|
5139
|
-
const request = {
|
|
5140
|
-
messages: messages.map((m) => ({
|
|
5141
|
-
role: m.role,
|
|
5142
|
-
content: { type: "text", text: m.content }
|
|
5143
|
-
})),
|
|
5144
|
-
systemPrompt: options.systemPrompt,
|
|
5145
|
-
maxTokens: options.maxTokens ?? 2048,
|
|
5146
|
-
temperature: options.temperature ?? 0.7,
|
|
5147
|
-
modelPreferences: options.modelPreferences ?? {
|
|
5148
|
-
hints: [{ name: "claude-3-sonnet" }, { name: "gpt-4" }],
|
|
5149
|
-
intelligencePriority: 0.8,
|
|
5150
|
-
speedPriority: 0.5
|
|
5151
|
-
}
|
|
5152
|
-
};
|
|
5153
|
-
logger.debug("Requesting LLM completion", {
|
|
5154
|
-
messageCount: messages.length,
|
|
5155
|
-
maxTokens: request.maxTokens
|
|
5156
|
-
});
|
|
5157
|
-
try {
|
|
5158
|
-
const response = await samplingCallback(request);
|
|
5159
|
-
if (response.content.type !== "text") {
|
|
5160
|
-
throw new Error("Unexpected response content type");
|
|
4818
|
+
from: "Uint<N>",
|
|
4819
|
+
to: "Field",
|
|
4820
|
+
direct: true,
|
|
4821
|
+
kind: "static",
|
|
4822
|
+
note: "Always succeeds"
|
|
4823
|
+
},
|
|
4824
|
+
{
|
|
4825
|
+
from: "Field",
|
|
4826
|
+
to: "Uint<0..n>",
|
|
4827
|
+
direct: true,
|
|
4828
|
+
kind: "checked",
|
|
4829
|
+
note: "Fails at runtime if value > n"
|
|
4830
|
+
},
|
|
4831
|
+
{
|
|
4832
|
+
from: "Field",
|
|
4833
|
+
to: "Bytes<n>",
|
|
4834
|
+
direct: true,
|
|
4835
|
+
kind: "conversion",
|
|
4836
|
+
note: "Can fail at runtime if value doesn't fit (little-endian)"
|
|
4837
|
+
},
|
|
4838
|
+
{
|
|
4839
|
+
from: "Bytes<m>",
|
|
4840
|
+
to: "Field",
|
|
4841
|
+
direct: true,
|
|
4842
|
+
kind: "conversion",
|
|
4843
|
+
note: "Can fail at runtime if result exceeds max Field value"
|
|
4844
|
+
},
|
|
4845
|
+
{
|
|
4846
|
+
from: "Boolean",
|
|
4847
|
+
to: "Uint<0..n>",
|
|
4848
|
+
direct: true,
|
|
4849
|
+
kind: "conversion",
|
|
4850
|
+
note: "false\u21920, true\u21921 (n must not be 0)"
|
|
4851
|
+
},
|
|
4852
|
+
{
|
|
4853
|
+
from: "Boolean",
|
|
4854
|
+
to: "Field",
|
|
4855
|
+
direct: false,
|
|
4856
|
+
fix: "Go through Uint: (flag as Uint<0..1>) as Field"
|
|
4857
|
+
},
|
|
4858
|
+
{
|
|
4859
|
+
from: "Uint<N>",
|
|
4860
|
+
to: "Bytes<M>",
|
|
4861
|
+
direct: false,
|
|
4862
|
+
fix: "Go through Field: (amount as Field) as Bytes<32>"
|
|
4863
|
+
},
|
|
4864
|
+
{
|
|
4865
|
+
from: "enum",
|
|
4866
|
+
to: "Field",
|
|
4867
|
+
direct: true,
|
|
4868
|
+
kind: "conversion",
|
|
4869
|
+
note: "Enum variant index as Field"
|
|
4870
|
+
},
|
|
4871
|
+
{
|
|
4872
|
+
from: "Uint<0..m>",
|
|
4873
|
+
to: "Uint<0..n>",
|
|
4874
|
+
direct: true,
|
|
4875
|
+
kind: "static if m\u2264n, checked if m>n",
|
|
4876
|
+
note: "Widening is static, narrowing is checked"
|
|
4877
|
+
},
|
|
4878
|
+
{
|
|
4879
|
+
from: "arithmetic result",
|
|
4880
|
+
to: "Uint<64>",
|
|
4881
|
+
direct: true,
|
|
4882
|
+
kind: "checked",
|
|
4883
|
+
note: "Required cast: (a + b) as Uint<64>"
|
|
5161
4884
|
}
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
|
|
5172
|
-
|
|
5173
|
-
);
|
|
4885
|
+
],
|
|
4886
|
+
assignments: [
|
|
4887
|
+
{
|
|
4888
|
+
types: "Field = Uint<N>",
|
|
4889
|
+
works: false,
|
|
4890
|
+
fix: "Cast with `value as Field`"
|
|
4891
|
+
},
|
|
4892
|
+
{
|
|
4893
|
+
types: "Uint<N> = Field",
|
|
4894
|
+
works: false,
|
|
4895
|
+
fix: "Use bounded param or explicit cast"
|
|
5174
4896
|
}
|
|
5175
|
-
|
|
5176
|
-
|
|
5177
|
-
|
|
5178
|
-
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
const systemPrompt = `You are an expert Compact smart contract developer for the Midnight blockchain.
|
|
5187
|
-
Your task is to generate secure, well-documented Compact contracts based on user requirements.
|
|
5188
|
-
|
|
5189
|
-
Key Compact syntax (REQUIRED):
|
|
5190
|
-
- \`export ledger field: Type;\` - Individual ledger declarations (NOT ledger { } blocks)
|
|
5191
|
-
- \`export circuit fn(): []\` - Public functions return empty tuple [] (NOT Void)
|
|
5192
|
-
- \`witness fn(): T;\` - Declaration only, no body
|
|
5193
|
-
- \`pragma language_version >= 0.16 && <= 0.21;\` - Version pragma
|
|
5194
|
-
- \`import CompactStandardLibrary;\` - Standard imports
|
|
5195
|
-
- \`Counter\`, \`Map<K,V>\`, \`Set<T>\` - Built-in collection types
|
|
5196
|
-
- \`Field\`, \`Boolean\`, \`Uint<N>\`, \`Bytes<N>\` - Primitive types
|
|
5197
|
-
- \`export enum State { a, b }\` - Enums must be exported
|
|
5198
|
-
|
|
5199
|
-
Key operations:
|
|
5200
|
-
- Counter: .read(), .increment(n), .decrement(n)
|
|
5201
|
-
- Map: .lookup(k), .insert(k,v), .remove(k)
|
|
5202
|
-
- Set: .member(v), .insert(v), .remove(v)
|
|
5203
|
-
|
|
5204
|
-
COMPILER INFO (DO NOT guess package names!):
|
|
5205
|
-
- Command: \`compact compile src/contract.compact managed/contract\`
|
|
5206
|
-
- The \`compact\` CLI comes with Midnight toolchain (via create-mn-app or official install)
|
|
5207
|
-
- Output goes to managed/<name>/ directory
|
|
5208
|
-
- DO NOT suggest \`npm install -g @midnight-ntwrk/compact-cli\` or similar - that's incorrect
|
|
5209
|
-
|
|
5210
|
-
IMPORTANT - Style vs. Requirements:
|
|
5211
|
-
- Syntax rules above are REQUIRED for compilation
|
|
5212
|
-
- Style choices (indentation, comment style, line length) are CONVENTIONS, not requirements
|
|
5213
|
-
- Single-line comments (//) are common in examples; block comments (/* */) may also work
|
|
5214
|
-
- The docs don't specify indentation width - use consistent style
|
|
5215
|
-
|
|
5216
|
-
Return ONLY the Compact code, no explanations.`;
|
|
5217
|
-
const userPrompt = options.baseExample ? `Based on this example contract:
|
|
5218
|
-
\`\`\`compact
|
|
5219
|
-
${options.baseExample}
|
|
5220
|
-
\`\`\`
|
|
5221
|
-
|
|
5222
|
-
Generate a new contract with these requirements:
|
|
5223
|
-
${requirements}` : `Generate a Compact smart contract with these requirements:
|
|
5224
|
-
${requirements}
|
|
5225
|
-
|
|
5226
|
-
Contract type: ${options.contractType || "custom"}`;
|
|
5227
|
-
try {
|
|
5228
|
-
const code = await requestCompletion(
|
|
5229
|
-
[{ role: "user", content: userPrompt }],
|
|
4897
|
+
],
|
|
4898
|
+
tips: [
|
|
4899
|
+
"Use Uint<0..N> for circuit parameters that need range validation",
|
|
4900
|
+
"Field is unbounded - use for hashes, commitments, general computation",
|
|
4901
|
+
"Uint<N> is bounded - use when you need range checks",
|
|
4902
|
+
"Casting with `as Field` is safe but loses range information"
|
|
4903
|
+
]
|
|
4904
|
+
};
|
|
4905
|
+
var LEDGER_TYPE_LIMITS = {
|
|
4906
|
+
Counter: {
|
|
4907
|
+
circuitOperations: [
|
|
5230
4908
|
{
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
5235
|
-
modelPreferences: {
|
|
5236
|
-
hints: [{ name: "claude-3-sonnet" }],
|
|
5237
|
-
intelligencePriority: 0.9,
|
|
5238
|
-
speedPriority: 0.3
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
);
|
|
5242
|
-
const extractedCode = code.includes("```") ? code.replace(/```compact?\n?/g, "").replace(/```/g, "").trim() : code.trim();
|
|
5243
|
-
const explanation = await requestCompletion(
|
|
5244
|
-
[
|
|
5245
|
-
{
|
|
5246
|
-
role: "user",
|
|
5247
|
-
content: `Briefly explain what this Compact contract does (2-3 sentences):
|
|
5248
|
-
\`\`\`compact
|
|
5249
|
-
${extractedCode}
|
|
5250
|
-
\`\`\``
|
|
5251
|
-
}
|
|
5252
|
-
],
|
|
4909
|
+
method: ".increment(n)",
|
|
4910
|
+
works: true,
|
|
4911
|
+
note: "Increase counter by n (Uint<16>)"
|
|
4912
|
+
},
|
|
5253
4913
|
{
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
|
|
5257
|
-
}
|
|
5258
|
-
);
|
|
5259
|
-
return {
|
|
5260
|
-
code: extractedCode,
|
|
5261
|
-
explanation: explanation.trim(),
|
|
5262
|
-
warnings: []
|
|
5263
|
-
};
|
|
5264
|
-
} catch (error) {
|
|
5265
|
-
logger.error("Contract generation failed", { error: String(error) });
|
|
5266
|
-
return {
|
|
5267
|
-
code: "",
|
|
5268
|
-
explanation: `Contract generation failed: ${String(error)}`,
|
|
5269
|
-
warnings: ["Generation failed - check logs for details"]
|
|
5270
|
-
};
|
|
5271
|
-
}
|
|
5272
|
-
}
|
|
5273
|
-
async function reviewContract(code) {
|
|
5274
|
-
if (!isSamplingAvailable()) {
|
|
5275
|
-
return {
|
|
5276
|
-
summary: "Code review requires sampling capability",
|
|
5277
|
-
issues: [
|
|
5278
|
-
{
|
|
5279
|
-
severity: "info",
|
|
5280
|
-
message: "This feature requires a client that supports the sampling capability"
|
|
5281
|
-
}
|
|
5282
|
-
]
|
|
5283
|
-
};
|
|
5284
|
-
}
|
|
5285
|
-
const systemPrompt = `You are a Compact smart contract security auditor.
|
|
5286
|
-
Review the provided contract for:
|
|
5287
|
-
1. Security vulnerabilities
|
|
5288
|
-
2. Privacy concerns (improper handling of shielded state)
|
|
5289
|
-
3. Logic errors
|
|
5290
|
-
4. Syntax errors (use "error" severity)
|
|
5291
|
-
5. Performance issues
|
|
5292
|
-
|
|
5293
|
-
IMPORTANT - Distinguish between:
|
|
5294
|
-
- ERRORS: Actual syntax/compilation issues (e.g., using Void instead of [], ledger {} blocks)
|
|
5295
|
-
- WARNINGS: Potential bugs or security issues
|
|
5296
|
-
- INFO: Style suggestions and best practices (these are CONVENTIONS, not requirements)
|
|
5297
|
-
|
|
5298
|
-
Do NOT claim style choices as "violations" - the Compact docs don't specify:
|
|
5299
|
-
- Indentation width (2 vs 4 spaces)
|
|
5300
|
-
- Comment style preferences (//, /* */)
|
|
5301
|
-
- Line length limits
|
|
5302
|
-
- Naming conventions
|
|
5303
|
-
|
|
5304
|
-
These are project-specific style choices, not language requirements.
|
|
5305
|
-
|
|
5306
|
-
Respond in JSON format:
|
|
5307
|
-
{
|
|
5308
|
-
"summary": "Brief summary of the contract and overall quality",
|
|
5309
|
-
"issues": [
|
|
5310
|
-
{
|
|
5311
|
-
"severity": "error|warning|info",
|
|
5312
|
-
"line": optional_line_number,
|
|
5313
|
-
"message": "Description of the issue",
|
|
5314
|
-
"suggestion": "How to fix it"
|
|
5315
|
-
}
|
|
5316
|
-
],
|
|
5317
|
-
"improvedCode": "Full improved contract code if changes are needed"
|
|
5318
|
-
}`;
|
|
5319
|
-
try {
|
|
5320
|
-
const response = await requestCompletion(
|
|
5321
|
-
[
|
|
5322
|
-
{
|
|
5323
|
-
role: "user",
|
|
5324
|
-
content: `Review this Compact contract:
|
|
5325
|
-
\`\`\`compact
|
|
5326
|
-
${code}
|
|
5327
|
-
\`\`\``
|
|
5328
|
-
}
|
|
5329
|
-
],
|
|
4914
|
+
method: ".decrement(n)",
|
|
4915
|
+
works: true,
|
|
4916
|
+
note: "Decrease counter by n (Uint<16>)"
|
|
4917
|
+
},
|
|
5330
4918
|
{
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
}
|
|
5335
|
-
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
4919
|
+
method: ".read()",
|
|
4920
|
+
works: true,
|
|
4921
|
+
note: "Get current value (returns Uint<64>)"
|
|
4922
|
+
},
|
|
4923
|
+
{
|
|
4924
|
+
method: ".lessThan(n)",
|
|
4925
|
+
works: true,
|
|
4926
|
+
note: "Compare with threshold (returns Boolean)"
|
|
4927
|
+
},
|
|
4928
|
+
{ method: ".resetToDefault()", works: true, note: "Reset to 0" }
|
|
4929
|
+
],
|
|
4930
|
+
typescriptAccess: "Access counter value via `ledgerState.counter` in TypeScript SDK",
|
|
4931
|
+
note: "All Counter operations work in circuits. Use .read() to get value, NOT .value()"
|
|
4932
|
+
},
|
|
4933
|
+
Map: {
|
|
4934
|
+
circuitOperations: [
|
|
4935
|
+
{
|
|
4936
|
+
method: ".insert(key, value)",
|
|
4937
|
+
works: true,
|
|
4938
|
+
note: "Adds/updates entry"
|
|
4939
|
+
},
|
|
4940
|
+
{
|
|
4941
|
+
method: ".insertDefault(key)",
|
|
4942
|
+
works: true,
|
|
4943
|
+
note: "Inserts default value for key"
|
|
4944
|
+
},
|
|
4945
|
+
{ method: ".remove(key)", works: true, note: "Removes entry" },
|
|
4946
|
+
{
|
|
4947
|
+
method: ".lookup(key)",
|
|
4948
|
+
works: true,
|
|
4949
|
+
note: "Returns value_type - gets value for key"
|
|
4950
|
+
},
|
|
4951
|
+
{
|
|
4952
|
+
method: ".member(key)",
|
|
4953
|
+
works: true,
|
|
4954
|
+
note: "Returns Boolean - checks if key exists"
|
|
4955
|
+
},
|
|
4956
|
+
{
|
|
4957
|
+
method: ".isEmpty()",
|
|
4958
|
+
works: true,
|
|
4959
|
+
note: "Returns Boolean - checks if map is empty"
|
|
4960
|
+
},
|
|
4961
|
+
{
|
|
4962
|
+
method: ".size()",
|
|
4963
|
+
works: true,
|
|
4964
|
+
note: "Returns Uint<64> - number of entries"
|
|
4965
|
+
},
|
|
4966
|
+
{
|
|
4967
|
+
method: ".resetToDefault()",
|
|
4968
|
+
works: true,
|
|
4969
|
+
note: "Clears entire map"
|
|
5353
4970
|
}
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5359
|
-
|
|
5360
|
-
|
|
5361
|
-
|
|
5362
|
-
summary: `Review failed: ${String(error)}`,
|
|
5363
|
-
issues: [
|
|
5364
|
-
{
|
|
5365
|
-
severity: "error",
|
|
5366
|
-
message: "Review failed - check logs for details"
|
|
5367
|
-
}
|
|
5368
|
-
]
|
|
5369
|
-
};
|
|
5370
|
-
}
|
|
5371
|
-
}
|
|
5372
|
-
async function generateDocumentation(code, format = "markdown") {
|
|
5373
|
-
if (!isSamplingAvailable()) {
|
|
5374
|
-
return "Documentation generation requires sampling capability";
|
|
5375
|
-
}
|
|
5376
|
-
const systemPrompt = format === "markdown" ? `Generate comprehensive Markdown documentation for this Compact contract.
|
|
5377
|
-
Include:
|
|
5378
|
-
- Overview and purpose
|
|
5379
|
-
- State variables (with privacy annotations)
|
|
5380
|
-
- Circuit functions with parameters and effects
|
|
5381
|
-
- Witness functions
|
|
5382
|
-
- Usage examples
|
|
5383
|
-
- Security considerations` : `Generate JSDoc-style documentation comments for this Compact contract.
|
|
5384
|
-
Add documentation comments above each:
|
|
5385
|
-
- Ledger field
|
|
5386
|
-
- Circuit function
|
|
5387
|
-
- Witness function
|
|
5388
|
-
- Type definition`;
|
|
5389
|
-
try {
|
|
5390
|
-
return await requestCompletion(
|
|
5391
|
-
[
|
|
5392
|
-
{
|
|
5393
|
-
role: "user",
|
|
5394
|
-
content: `Generate ${format} documentation for:
|
|
5395
|
-
\`\`\`compact
|
|
5396
|
-
${code}
|
|
5397
|
-
\`\`\``
|
|
5398
|
-
}
|
|
5399
|
-
],
|
|
4971
|
+
],
|
|
4972
|
+
typescriptAccess: "Query map via `contractState.data.get(key)` or iterate with `[Symbol.iterator]()` in TypeScript SDK",
|
|
4973
|
+
note: "All Map operations work in circuits. insertCoin() available when value_type is QualifiedCoinInfo."
|
|
4974
|
+
},
|
|
4975
|
+
Set: {
|
|
4976
|
+
circuitOperations: [
|
|
4977
|
+
{ method: ".insert(value)", works: true, note: "Adds to set" },
|
|
4978
|
+
{ method: ".remove(value)", works: true, note: "Removes from set" },
|
|
5400
4979
|
{
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
4980
|
+
method: ".member(value)",
|
|
4981
|
+
works: true,
|
|
4982
|
+
note: "Returns Boolean - checks if value exists in set"
|
|
4983
|
+
},
|
|
4984
|
+
{
|
|
4985
|
+
method: ".isEmpty()",
|
|
4986
|
+
works: true,
|
|
4987
|
+
note: "Returns Boolean - checks if set is empty"
|
|
4988
|
+
},
|
|
4989
|
+
{
|
|
4990
|
+
method: ".size()",
|
|
4991
|
+
works: true,
|
|
4992
|
+
note: "Returns Uint<64> - number of elements"
|
|
4993
|
+
},
|
|
4994
|
+
{
|
|
4995
|
+
method: ".resetToDefault()",
|
|
4996
|
+
works: true,
|
|
4997
|
+
note: "Clears entire set"
|
|
5404
4998
|
}
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
async function searchADTInfo(adtName) {
|
|
5421
|
-
try {
|
|
5422
|
-
const docsResult = await searchDocsHosted(
|
|
5423
|
-
`${adtName} ADT operations methods ledger`,
|
|
5424
|
-
ADT_SEARCH_LIMIT,
|
|
5425
|
-
"reference"
|
|
5426
|
-
);
|
|
5427
|
-
if (!docsResult.results || docsResult.results.length === 0) {
|
|
5428
|
-
logger.debug(`No indexed docs found for ADT: ${adtName}`);
|
|
5429
|
-
return null;
|
|
5430
|
-
}
|
|
5431
|
-
const operations = [];
|
|
5432
|
-
const notes = [];
|
|
5433
|
-
let sourceDocPath;
|
|
5434
|
-
for (const result of docsResult.results) {
|
|
5435
|
-
const content = result.content || result.code || "";
|
|
5436
|
-
const isLedgerADTDoc = result.source.filePath.includes("ledger-adt");
|
|
5437
|
-
if (!sourceDocPath && isLedgerADTDoc) {
|
|
5438
|
-
sourceDocPath = result.source.filePath;
|
|
5439
|
-
}
|
|
5440
|
-
if (isLedgerADTDoc) {
|
|
5441
|
-
const methodPattern = /\|\s*`?(\w+)`?\s*\|\s*`?\(([^)]*)\)\s*(?::\s*([^|`]+))?`?\s*\|\s*([^|]+)\|/g;
|
|
5442
|
-
let match;
|
|
5443
|
-
while ((match = methodPattern.exec(content)) !== null) {
|
|
5444
|
-
const [, methodName, params, returnType, description] = match;
|
|
5445
|
-
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
5446
|
-
const descText = description?.trim() || "";
|
|
5447
|
-
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
5448
|
-
descText
|
|
5449
|
-
);
|
|
5450
|
-
operations.push({
|
|
5451
|
-
method: methodName,
|
|
5452
|
-
signature: `(${params})${returnType ? `: ${returnType.trim()}` : ""}`,
|
|
5453
|
-
description: descText,
|
|
5454
|
-
worksInCircuits,
|
|
5455
|
-
source: "indexed-docs"
|
|
5456
|
-
});
|
|
5457
|
-
}
|
|
5458
|
-
}
|
|
5459
|
-
const inlinePattern = /(?:method|operation|function)\s+`?(\w+)`?\s*\(([^)]*)\)/gi;
|
|
5460
|
-
while ((match = inlinePattern.exec(content)) !== null) {
|
|
5461
|
-
const [, methodName, params] = match;
|
|
5462
|
-
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
5463
|
-
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
5464
|
-
content
|
|
5465
|
-
);
|
|
5466
|
-
operations.push({
|
|
5467
|
-
method: methodName,
|
|
5468
|
-
signature: `(${params})`,
|
|
5469
|
-
description: "Extracted from documentation",
|
|
5470
|
-
worksInCircuits,
|
|
5471
|
-
source: "indexed-docs"
|
|
5472
|
-
});
|
|
5473
|
-
}
|
|
5474
|
-
}
|
|
5475
|
-
}
|
|
5476
|
-
if (content.toLowerCase().includes(adtName.toLowerCase()) && content.length < MAX_NOTE_SOURCE_LENGTH) {
|
|
5477
|
-
const cleanNote = content.replace(/\s+/g, " ").trim();
|
|
5478
|
-
if (cleanNote.length > NOTE_MIN_LENGTH && cleanNote.length < NOTE_MAX_LENGTH) {
|
|
5479
|
-
notes.push(cleanNote);
|
|
5480
|
-
}
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5483
|
-
if (operations.length === 0) {
|
|
5484
|
-
logger.debug(`Could not parse operations for ADT: ${adtName}`);
|
|
5485
|
-
return null;
|
|
5486
|
-
}
|
|
5487
|
-
return {
|
|
5488
|
-
name: adtName,
|
|
5489
|
-
operations,
|
|
5490
|
-
notes: notes.slice(0, MAX_NOTES_PER_ADT),
|
|
5491
|
-
sourceDocPath,
|
|
5492
|
-
lastVerified: (/* @__PURE__ */ new Date()).toISOString()
|
|
5493
|
-
};
|
|
5494
|
-
} catch (error) {
|
|
5495
|
-
logger.warn(`Failed to search indexed docs for ADT ${adtName}`, {
|
|
5496
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5497
|
-
});
|
|
5498
|
-
return null;
|
|
5499
|
-
}
|
|
5500
|
-
}
|
|
5501
|
-
async function searchCompactSyntax(topic) {
|
|
5502
|
-
try {
|
|
5503
|
-
const [docsResult, codeResult] = await Promise.all([
|
|
5504
|
-
searchDocsHosted(
|
|
5505
|
-
`Compact ${topic} syntax`,
|
|
5506
|
-
SYNTAX_SUBQUERY_LIMIT,
|
|
5507
|
-
"reference"
|
|
5508
|
-
),
|
|
5509
|
-
searchCompactHosted(`${topic}`, SYNTAX_SUBQUERY_LIMIT)
|
|
5510
|
-
]);
|
|
5511
|
-
const mergedResults = [
|
|
5512
|
-
...docsResult.results || [],
|
|
5513
|
-
...codeResult.results || []
|
|
5514
|
-
];
|
|
5515
|
-
return {
|
|
5516
|
-
results: mergedResults.slice(0, SYNTAX_RESULTS_LIMIT),
|
|
5517
|
-
totalResults: mergedResults.length,
|
|
5518
|
-
query: topic,
|
|
5519
|
-
lastIndexed: docsResult.lastIndexed || codeResult.lastIndexed
|
|
5520
|
-
};
|
|
5521
|
-
} catch (error) {
|
|
5522
|
-
logger.warn(`Failed to search Compact syntax for topic ${topic}`, {
|
|
5523
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5524
|
-
});
|
|
5525
|
-
return null;
|
|
5526
|
-
}
|
|
5527
|
-
}
|
|
5528
|
-
async function validateADTOperations(adtName, staticOperations) {
|
|
5529
|
-
const discrepancies = [];
|
|
5530
|
-
const enrichments = [];
|
|
5531
|
-
const indexedInfo = await searchADTInfo(adtName);
|
|
5532
|
-
if (!indexedInfo) {
|
|
5533
|
-
return {
|
|
5534
|
-
validated: false,
|
|
5535
|
-
operations: staticOperations.map((op) => ({
|
|
5536
|
-
method: op.method,
|
|
5537
|
-
description: op.note,
|
|
5538
|
-
worksInCircuits: op.works,
|
|
5539
|
-
source: "static-fallback"
|
|
5540
|
-
})),
|
|
5541
|
-
discrepancies: [
|
|
5542
|
-
"Could not validate against indexed docs - using static data"
|
|
5543
|
-
],
|
|
5544
|
-
enrichments: []
|
|
5545
|
-
};
|
|
5546
|
-
}
|
|
5547
|
-
const operations = [];
|
|
5548
|
-
const indexedMethodNames = new Set(
|
|
5549
|
-
indexedInfo.operations.map((op) => op.method.toLowerCase())
|
|
5550
|
-
);
|
|
5551
|
-
const staticMethodNames = new Set(
|
|
5552
|
-
staticOperations.map(
|
|
5553
|
-
(op) => op.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
5554
|
-
)
|
|
5555
|
-
);
|
|
5556
|
-
for (const indexedOp of indexedInfo.operations) {
|
|
5557
|
-
operations.push(indexedOp);
|
|
5558
|
-
const staticOp = staticOperations.find(
|
|
5559
|
-
(s) => s.method.toLowerCase().includes(indexedOp.method.toLowerCase()) || indexedOp.method.toLowerCase().includes(
|
|
5560
|
-
s.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
5561
|
-
)
|
|
5562
|
-
);
|
|
5563
|
-
if (staticOp && !staticOp.works && indexedOp.worksInCircuits) {
|
|
5564
|
-
discrepancies.push(
|
|
5565
|
-
`Static said ${staticOp.method} doesn't work, but indexed docs show it does`
|
|
5566
|
-
);
|
|
5567
|
-
}
|
|
5568
|
-
}
|
|
5569
|
-
for (const staticOp of staticOperations) {
|
|
5570
|
-
const methodName = staticOp.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase();
|
|
5571
|
-
if (!indexedMethodNames.has(methodName)) {
|
|
5572
|
-
discrepancies.push(
|
|
5573
|
-
`Static references ${staticOp.method} but not found in indexed docs - may not exist`
|
|
5574
|
-
);
|
|
5575
|
-
}
|
|
5576
|
-
}
|
|
5577
|
-
for (const indexedOp of indexedInfo.operations) {
|
|
5578
|
-
const methodName = indexedOp.method.toLowerCase();
|
|
5579
|
-
if (!staticMethodNames.has(methodName)) {
|
|
5580
|
-
enrichments.push(
|
|
5581
|
-
`Indexed docs show ${indexedOp.method} exists but was missing from static reference`
|
|
5582
|
-
);
|
|
5583
|
-
}
|
|
5584
|
-
}
|
|
5585
|
-
return {
|
|
5586
|
-
validated: true,
|
|
5587
|
-
operations,
|
|
5588
|
-
discrepancies,
|
|
5589
|
-
enrichments
|
|
5590
|
-
};
|
|
5591
|
-
}
|
|
5592
|
-
async function validateBuiltinFunctions(staticBuiltins) {
|
|
5593
|
-
const discrepancies = [];
|
|
5594
|
-
const enrichments = [];
|
|
5595
|
-
const results = await Promise.all(
|
|
5596
|
-
staticBuiltins.map(async (builtin) => {
|
|
5597
|
-
const searchResult = await searchCompactSyntax(
|
|
5598
|
-
`${builtin.name} function builtin`
|
|
5599
|
-
);
|
|
5600
|
-
return { builtin, found: (searchResult?.results?.length || 0) > 0 };
|
|
5601
|
-
})
|
|
5602
|
-
);
|
|
5603
|
-
for (const { builtin, found } of results) {
|
|
5604
|
-
if (!found) {
|
|
5605
|
-
discrepancies.push(
|
|
5606
|
-
`Builtin "${builtin.name}" not found in indexed docs - may not exist or have different name`
|
|
5607
|
-
);
|
|
5608
|
-
}
|
|
5609
|
-
}
|
|
5610
|
-
const allBuiltinsSearch = await searchCompactSyntax(
|
|
5611
|
-
"builtin function stdlib standard library"
|
|
5612
|
-
);
|
|
5613
|
-
if (allBuiltinsSearch?.results) {
|
|
5614
|
-
const knownNames = new Set(staticBuiltins.map((b) => b.name.toLowerCase()));
|
|
5615
|
-
for (const result of allBuiltinsSearch.results) {
|
|
5616
|
-
const content = result.content || result.code || "";
|
|
5617
|
-
const funcPattern = /\b(export\s+)?function\s+(\w+)/gi;
|
|
5618
|
-
let match;
|
|
5619
|
-
while ((match = funcPattern.exec(content)) !== null) {
|
|
5620
|
-
const funcName = match[2].toLowerCase();
|
|
5621
|
-
if (!knownNames.has(funcName) && funcName.length > 2) {
|
|
5622
|
-
enrichments.push(`Indexed docs mention "${match[2]}" function`);
|
|
5623
|
-
}
|
|
5624
|
-
}
|
|
5625
|
-
}
|
|
5626
|
-
}
|
|
5627
|
-
return {
|
|
5628
|
-
dataType: "BUILTIN_FUNCTIONS",
|
|
5629
|
-
validated: discrepancies.length === 0,
|
|
5630
|
-
discrepancies,
|
|
5631
|
-
enrichments: [...new Set(enrichments)].slice(0, 10),
|
|
5632
|
-
deprecatedPatterns: [],
|
|
5633
|
-
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5634
|
-
};
|
|
5635
|
-
}
|
|
5636
|
-
async function validateTypeCompatibility(staticRules) {
|
|
5637
|
-
const discrepancies = [];
|
|
5638
|
-
const enrichments = [];
|
|
5639
|
-
const typeSearch = await searchCompactSyntax(
|
|
5640
|
-
"type cast Field Uint Bytes conversion compatible"
|
|
5641
|
-
);
|
|
5642
|
-
if (typeSearch?.results) {
|
|
5643
|
-
for (const result of typeSearch.results) {
|
|
5644
|
-
const content = (result.content || result.code || "").toLowerCase();
|
|
5645
|
-
for (const rule of staticRules) {
|
|
5646
|
-
const typeMatch = rule.types.match(
|
|
5647
|
-
/^\s*(.+?)\s*(?:==|=|<=|>=|<|>|[+*])\s*(.+?)\s*$/
|
|
5648
|
-
);
|
|
5649
|
-
const typeA = typeMatch?.[1]?.trim().toLowerCase();
|
|
5650
|
-
const typeB = typeMatch?.[2]?.trim().toLowerCase();
|
|
5651
|
-
if (typeA && typeB) {
|
|
5652
|
-
if (content.includes(typeA) && content.includes(typeB) && content.includes("compatible")) {
|
|
5653
|
-
if (!rule.works) {
|
|
5654
|
-
discrepancies.push(
|
|
5655
|
-
`Static says "${rule.types}" doesn't work, but docs suggest compatibility`
|
|
5656
|
-
);
|
|
5657
|
-
}
|
|
5658
|
-
}
|
|
5659
|
-
}
|
|
5660
|
-
}
|
|
5661
|
-
}
|
|
5662
|
-
}
|
|
5663
|
-
return {
|
|
5664
|
-
dataType: "TYPE_COMPATIBILITY",
|
|
5665
|
-
validated: discrepancies.length === 0,
|
|
5666
|
-
discrepancies,
|
|
5667
|
-
enrichments,
|
|
5668
|
-
deprecatedPatterns: [],
|
|
5669
|
-
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5670
|
-
};
|
|
5671
|
-
}
|
|
5672
|
-
async function validateCommonErrors(staticErrors) {
|
|
5673
|
-
const discrepancies = [];
|
|
5674
|
-
const enrichments = [];
|
|
5675
|
-
const errorSearch = await searchDocsHosted(
|
|
5676
|
-
"error compile compilation failure parse",
|
|
5677
|
-
ADT_SEARCH_LIMIT,
|
|
5678
|
-
"all"
|
|
5679
|
-
);
|
|
5680
|
-
if (errorSearch?.results) {
|
|
5681
|
-
const knownErrors = new Set(
|
|
5682
|
-
staticErrors.map((e) => e.error.toLowerCase().slice(0, 30))
|
|
5683
|
-
);
|
|
5684
|
-
for (const result of errorSearch.results) {
|
|
5685
|
-
const content = result.content || result.code || "";
|
|
5686
|
-
const errorPattern = /(?:error|Error):\s*["']?([^"'\n]+)["']?/g;
|
|
5687
|
-
let match;
|
|
5688
|
-
while ((match = errorPattern.exec(content)) !== null) {
|
|
5689
|
-
const errorMsg = match[1].toLowerCase().slice(0, 30);
|
|
5690
|
-
if (!knownErrors.has(errorMsg) && errorMsg.length > 10) {
|
|
5691
|
-
enrichments.push(`Docs mention error: "${match[1].slice(0, 50)}..."`);
|
|
5692
|
-
}
|
|
5693
|
-
}
|
|
5694
|
-
}
|
|
4999
|
+
],
|
|
5000
|
+
typescriptAccess: "Check membership via `contractState.set.has(value)` or iterate with `[Symbol.iterator]()` in TypeScript SDK",
|
|
5001
|
+
note: "All Set operations work in circuits. insertCoin() available when value_type is QualifiedCoinInfo."
|
|
5002
|
+
},
|
|
5003
|
+
MerkleTree: {
|
|
5004
|
+
circuitOperations: [
|
|
5005
|
+
{ method: ".insert(leaf)", works: true, note: "Adds leaf to tree" },
|
|
5006
|
+
{ method: ".root()", works: false, note: "NOT available in circuits" }
|
|
5007
|
+
],
|
|
5008
|
+
typescriptAccess: "Get root via `contractState.tree.root` in TypeScript SDK",
|
|
5009
|
+
pattern: `// To verify a merkle proof in circuit:
|
|
5010
|
+
witness get_merkle_root(): Bytes<32>;
|
|
5011
|
+
witness get_merkle_proof(leaf: Bytes<32>): Vector<32, Bytes<32>>;
|
|
5012
|
+
|
|
5013
|
+
// Verify proof using persistentHash to compute expected root`
|
|
5695
5014
|
}
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5015
|
+
};
|
|
5016
|
+
var COMMON_ERRORS = [
|
|
5017
|
+
{
|
|
5018
|
+
error: 'unbound identifier "public_key"',
|
|
5019
|
+
cause: "Trying to use public_key() as if it's a builtin function",
|
|
5020
|
+
fix: `Use persistentHash pattern instead:
|
|
5021
|
+
const pk = persistentHash<Vector<2, Bytes<32>>>([pad(32, "midnight:pk:"), sk]);`
|
|
5022
|
+
},
|
|
5023
|
+
{
|
|
5024
|
+
error: "incompatible combination of types Field and Uint",
|
|
5025
|
+
cause: "Comparing or operating on Field with Uint without casting",
|
|
5026
|
+
fix: `Cast Uint to Field: (myUint as Field)
|
|
5027
|
+
Or use bounded Uint<0..N> for parameters that need constraints`
|
|
5028
|
+
},
|
|
5029
|
+
{
|
|
5030
|
+
error: 'operation "value" undefined for ledger field type Counter',
|
|
5031
|
+
cause: "Using wrong method name - Counter uses .read() not .value()",
|
|
5032
|
+
fix: `Use counter.read() to get the current value:
|
|
5033
|
+
const current = ledger.counter.read();
|
|
5034
|
+
|
|
5035
|
+
Counter ADT methods available in circuits:
|
|
5036
|
+
- increment(amount: Uint<16>): [] - increase counter
|
|
5037
|
+
- decrement(amount: Uint<16>): [] - decrease counter
|
|
5038
|
+
- read(): Uint<64> - get current value
|
|
5039
|
+
- lessThan(threshold: Uint<64>): Boolean - compare
|
|
5040
|
+
- resetToDefault(): [] - reset to zero`
|
|
5041
|
+
},
|
|
5042
|
+
{
|
|
5043
|
+
error: "implicit disclosure of witness value",
|
|
5044
|
+
cause: "Using witness value in conditional without disclose()",
|
|
5045
|
+
fix: `Wrap witness comparisons in disclose():
|
|
5046
|
+
if (disclose(witness_value == expected)) { ... }`
|
|
5047
|
+
},
|
|
5707
5048
|
{
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5049
|
+
error: 'parse error: found "{" looking for an identifier',
|
|
5050
|
+
cause: "Using old ledger { } block syntax",
|
|
5051
|
+
fix: `Use individual exports instead:
|
|
5052
|
+
export ledger field1: Type1;
|
|
5053
|
+
export ledger field2: Type2;`
|
|
5712
5054
|
},
|
|
5713
5055
|
{
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5056
|
+
error: 'parse error: found "{" looking for ";"',
|
|
5057
|
+
cause: "Using Void as return type (doesn't exist)",
|
|
5058
|
+
fix: `Use empty tuple [] for no return value:
|
|
5059
|
+
export circuit myCircuit(): [] { ... }`
|
|
5718
5060
|
},
|
|
5719
5061
|
{
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5062
|
+
error: 'unbound identifier "Cell"',
|
|
5063
|
+
cause: "Using deprecated Cell<T> wrapper (removed in 0.15)",
|
|
5064
|
+
fix: `Remove Cell wrapper, just use the type directly:
|
|
5065
|
+
export ledger myField: Field; // Not Cell<Field>`
|
|
5724
5066
|
},
|
|
5725
5067
|
{
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5068
|
+
error: "member access requires struct type",
|
|
5069
|
+
cause: "Trying to access a field on a non-struct type",
|
|
5070
|
+
fix: `Make sure you're accessing a struct field, not a primitive.
|
|
5071
|
+
Map.lookup() and Map.member() ARE available in circuits.
|
|
5072
|
+
Check that the base type is actually a struct.`
|
|
5730
5073
|
},
|
|
5731
5074
|
{
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5075
|
+
error: "potential witness-value disclosure must be declared",
|
|
5076
|
+
cause: "Circuit parameter flows to ledger operation without disclose()",
|
|
5077
|
+
fix: `Disclose parameters at the start of the circuit:
|
|
5078
|
+
export circuit my_circuit(param: Bytes<32>): [] {
|
|
5079
|
+
const d_param = disclose(param); // Acknowledge on-chain visibility
|
|
5080
|
+
ledger.insert(d_param, value); // Now use disclosed value
|
|
5081
|
+
}`
|
|
5736
5082
|
},
|
|
5737
5083
|
{
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5084
|
+
error: "expected second argument of insert to have type Uint<64> but received Uint<0..N>",
|
|
5085
|
+
cause: "Arithmetic result has bounded type, needs cast back to target",
|
|
5086
|
+
fix: `Cast arithmetic results back to the target type:
|
|
5087
|
+
const new_balance = (current + amount) as Uint<64>;
|
|
5088
|
+
ledger_map.insert(key, new_balance);`
|
|
5742
5089
|
},
|
|
5743
5090
|
{
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5091
|
+
error: "cannot cast from type Uint<64> to type Bytes<32>",
|
|
5092
|
+
cause: "Direct Uint to Bytes cast not allowed",
|
|
5093
|
+
fix: `Go through Field first:
|
|
5094
|
+
const amount_field = amount as Field;
|
|
5095
|
+
const amount_bytes = amount_field as Bytes<32>;
|
|
5096
|
+
// Or chained: (amount as Field) as Bytes<32>`
|
|
5097
|
+
},
|
|
5098
|
+
{
|
|
5099
|
+
error: "cannot prove assertion",
|
|
5100
|
+
cause: "Assert condition cannot be proven true",
|
|
5101
|
+
fix: `Check your logic. Common causes:
|
|
5102
|
+
1. Witness returns unexpected value
|
|
5103
|
+
2. Range check fails (use bounded Uint)
|
|
5104
|
+
3. Logic error in circuit`
|
|
5105
|
+
},
|
|
5106
|
+
{
|
|
5107
|
+
error: 'parse error: found ":" looking for ")"',
|
|
5108
|
+
cause: "Using Rust-style :: for enum variant access",
|
|
5109
|
+
fix: `Use dot notation for enum variants:
|
|
5110
|
+
WRONG: Choice::rock, GameState::waiting
|
|
5111
|
+
CORRECT: Choice.rock, GameState.waiting`
|
|
5112
|
+
},
|
|
5113
|
+
{
|
|
5114
|
+
error: 'parse error: found "{" after witness declaration',
|
|
5115
|
+
cause: "Trying to add implementation body to witness",
|
|
5116
|
+
fix: `Witnesses are declarations only - no body allowed:
|
|
5117
|
+
WRONG: witness get_caller(): Bytes<32> { return ...; }
|
|
5118
|
+
CORRECT: witness get_caller(): Bytes<32>;
|
|
5119
|
+
Implementation goes in TypeScript prover, not Compact.`
|
|
5120
|
+
},
|
|
5121
|
+
{
|
|
5122
|
+
error: 'unbound identifier "function"',
|
|
5123
|
+
cause: 'Using "pure function" instead of "pure circuit"',
|
|
5124
|
+
fix: `Use "pure circuit" for helper functions:
|
|
5125
|
+
WRONG: pure function helper(...): Type { }
|
|
5126
|
+
CORRECT: pure circuit helper(...): Type { }`
|
|
5127
|
+
},
|
|
5128
|
+
{
|
|
5129
|
+
error: "apparent use of an old standard-library / ledger operator name NativePoint: the new name is JubjubPoint",
|
|
5130
|
+
cause: "Using the pre-0.22 NativePoint name. Renamed in compactc 0.30.0 (language 0.22).",
|
|
5131
|
+
fix: `Rename to JubjubPoint and related circuits:
|
|
5132
|
+
WRONG: NativePoint, nativePointX(p), nativePointY(p), constructNativePoint(x, y)
|
|
5133
|
+
CORRECT: JubjubPoint, jubjubPointX(p), jubjubPointY(p), constructJubjubPoint(x, y)
|
|
5134
|
+
NativePoint is rejected at compile time, not silently aliased.`
|
|
5135
|
+
},
|
|
5136
|
+
{
|
|
5137
|
+
error: 'parse error: found keyword "let" (which is reserved for future use)',
|
|
5138
|
+
cause: "Using `let` as an identifier. `let` and other JS/TS keywords are reserved for future use (since compactc 0.28.0).",
|
|
5139
|
+
fix: `Rename the identifier:
|
|
5140
|
+
WRONG: const let = 1; // or circuit fn(let: Field): []
|
|
5141
|
+
CORRECT: const value = 1; // or circuit fn(value: Field): []
|
|
5142
|
+
Other reserved words from JS/TS are also disallowed as identifiers.`
|
|
5143
|
+
},
|
|
5144
|
+
{
|
|
5145
|
+
error: 'parse error: found keyword "const" looking for a program element or end of file',
|
|
5146
|
+
cause: "Declaring `const` at module (top-of-file) scope. `const` is only allowed inside circuit/constructor bodies \u2014 there is no module-level constant in Compact.",
|
|
5147
|
+
fix: `Move the constant inside a circuit, or use a pure circuit returning the value:
|
|
5148
|
+
WRONG: const MAX_SUPPLY: Uint<64> = 1000; // at top of file
|
|
5149
|
+
CORRECT: pure circuit max_supply(): Uint<64> { return 1000; }
|
|
5150
|
+
Then call max_supply() wherever you need the value.`
|
|
5151
|
+
},
|
|
5152
|
+
{
|
|
5153
|
+
error: "unexpected character",
|
|
5154
|
+
cause: "Using a single `|` (bitwise OR) in an expression. Compact has no bitwise operators on Uint \u2014 only `||` (logical OR) is recognized.",
|
|
5155
|
+
fix: `Compact does not support bitwise OR/AND/XOR/shifts. Replace with arithmetic or branching:
|
|
5156
|
+
WRONG: flags | mask
|
|
5157
|
+
CORRECT: flags + mask // if you know bits don't overlap
|
|
5158
|
+
or: if (cond) { ... } else { ... }
|
|
5159
|
+
Use \`||\` only for Boolean values, not Uints.`
|
|
5160
|
+
},
|
|
5161
|
+
{
|
|
5162
|
+
error: "Uint width 254 is not between 1 and the maximum Uint width 248 (inclusive)",
|
|
5163
|
+
cause: "Declaring a Uint wider than 248 bits. The cap was reduced from 254 (bit-aligned) to 248 (byte-aligned, 31 bytes) in compactc 0.27.0 to match the ledger storage alignment.",
|
|
5164
|
+
fix: `Use Uint<N> where 1 \u2264 N \u2264 248:
|
|
5165
|
+
WRONG: field: Uint<254>
|
|
5166
|
+
CORRECT: field: Uint<248> // 31 bytes \u2014 max representable
|
|
5167
|
+
For larger values, use Field (unbounded) and cast where needed.`
|
|
5168
|
+
},
|
|
5169
|
+
{
|
|
5170
|
+
// Not a compiler error — surfaces as a TypeScript type error in generated bindings.
|
|
5171
|
+
error: "Argument of type 'number' is not assignable to parameter of type 'bigint' (convertBytesToUint)",
|
|
5172
|
+
cause: "Passing a JavaScript `number` to convertBytesToUint's `maxval` parameter. Changed to `bigint` in compactc 0.30.101 (language 0.22) to avoid silent precision loss for large Uints.",
|
|
5173
|
+
fix: `Pass a bigint literal for maxval:
|
|
5174
|
+
WRONG: convertBytesToUint(255, n, bytes, src)
|
|
5175
|
+
CORRECT: convertBytesToUint(255n, n, bytes, src)
|
|
5176
|
+
This is in generated TypeScript bindings \u2014 usually you don't call it directly, but if you do, the suffix-n bigint literal is required.`
|
|
5748
5177
|
}
|
|
5749
5178
|
];
|
|
5750
|
-
function scanForDeprecatedPatterns(code) {
|
|
5751
|
-
const issues = [];
|
|
5752
|
-
const lines = code.split("\n");
|
|
5753
|
-
for (const { pattern, name, message } of DEPRECATED_SYNTAX_PATTERNS) {
|
|
5754
|
-
for (let i = 0; i < lines.length; i++) {
|
|
5755
|
-
if (pattern.test(lines[i])) {
|
|
5756
|
-
issues.push({
|
|
5757
|
-
pattern: name,
|
|
5758
|
-
message,
|
|
5759
|
-
lineNumber: i + 1
|
|
5760
|
-
});
|
|
5761
|
-
}
|
|
5762
|
-
}
|
|
5763
|
-
}
|
|
5764
|
-
return issues;
|
|
5765
|
-
}
|
|
5766
|
-
async function validateAllStaticData(staticData) {
|
|
5767
|
-
const results = {};
|
|
5768
|
-
const [builtinResult, typeResult, errorResult, adtResults] = await Promise.all([
|
|
5769
|
-
staticData.builtinFunctions ? validateBuiltinFunctions(staticData.builtinFunctions) : Promise.resolve(null),
|
|
5770
|
-
staticData.typeCompatibility ? validateTypeCompatibility(staticData.typeCompatibility) : Promise.resolve(null),
|
|
5771
|
-
staticData.commonErrors ? validateCommonErrors(staticData.commonErrors) : Promise.resolve(null),
|
|
5772
|
-
staticData.ledgerTypeLimits ? Promise.all(
|
|
5773
|
-
Object.entries(staticData.ledgerTypeLimits).map(
|
|
5774
|
-
async ([name, data]) => ({
|
|
5775
|
-
name,
|
|
5776
|
-
result: await validateADTOperations(
|
|
5777
|
-
name,
|
|
5778
|
-
data.circuitOperations
|
|
5779
|
-
)
|
|
5780
|
-
})
|
|
5781
|
-
)
|
|
5782
|
-
) : Promise.resolve([])
|
|
5783
|
-
]);
|
|
5784
|
-
if (builtinResult) results.builtinFunctions = builtinResult;
|
|
5785
|
-
if (typeResult) results.typeCompatibility = typeResult;
|
|
5786
|
-
if (errorResult) results.commonErrors = errorResult;
|
|
5787
|
-
for (const { name, result } of adtResults) {
|
|
5788
|
-
results[`adt_${name}`] = {
|
|
5789
|
-
dataType: `LEDGER_TYPE_LIMITS.${name}`,
|
|
5790
|
-
validated: result.validated,
|
|
5791
|
-
discrepancies: result.discrepancies,
|
|
5792
|
-
enrichments: result.enrichments,
|
|
5793
|
-
deprecatedPatterns: [],
|
|
5794
|
-
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5795
|
-
};
|
|
5796
|
-
}
|
|
5797
|
-
const allDiscrepancies = Object.values(results).flatMap(
|
|
5798
|
-
(r) => r.discrepancies
|
|
5799
|
-
);
|
|
5800
|
-
const allEnrichments = Object.values(results).flatMap((r) => r.enrichments);
|
|
5801
|
-
return {
|
|
5802
|
-
overall: {
|
|
5803
|
-
validated: allDiscrepancies.length === 0,
|
|
5804
|
-
totalDiscrepancies: allDiscrepancies.length,
|
|
5805
|
-
totalEnrichments: allEnrichments.length
|
|
5806
|
-
},
|
|
5807
|
-
results,
|
|
5808
|
-
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
5809
|
-
};
|
|
5810
|
-
}
|
|
5811
5179
|
|
|
5812
|
-
// src/
|
|
5813
|
-
var
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
}
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
const data = await response.json();
|
|
5837
|
-
const latestVersion = data.version;
|
|
5838
|
-
if (latestVersion !== CURRENT_VERSION) {
|
|
5839
|
-
versionCheckResult = {
|
|
5840
|
-
isOutdated: true,
|
|
5841
|
-
latestVersion,
|
|
5842
|
-
lastChecked: Date.now(),
|
|
5843
|
-
updateMessage: `\u{1F6A8} UPDATE AVAILABLE: v${latestVersion} (you have v${CURRENT_VERSION})`
|
|
5844
|
-
};
|
|
5845
|
-
logger.warn(
|
|
5846
|
-
`Outdated version detected: v${CURRENT_VERSION} -> v${latestVersion}`
|
|
5847
|
-
);
|
|
5848
|
-
} else {
|
|
5849
|
-
versionCheckResult = {
|
|
5850
|
-
...versionCheckResult,
|
|
5851
|
-
lastChecked: Date.now()
|
|
5852
|
-
};
|
|
5853
|
-
}
|
|
5854
|
-
} catch (error) {
|
|
5855
|
-
logger.debug("Version check failed (non-blocking)", {
|
|
5856
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5857
|
-
});
|
|
5858
|
-
}
|
|
5859
|
-
}
|
|
5860
|
-
function maybeCheckForUpdates() {
|
|
5861
|
-
toolCallCount++;
|
|
5862
|
-
if (toolCallCount >= VERSION_CHECK_INTERVAL) {
|
|
5863
|
-
toolCallCount = 0;
|
|
5864
|
-
const fiveMinutes = 5 * 60 * 1e3;
|
|
5865
|
-
if (Date.now() - versionCheckResult.lastChecked > fiveMinutes) {
|
|
5866
|
-
checkForUpdates().catch((error) => {
|
|
5867
|
-
logger.debug("Periodic version check failed", {
|
|
5868
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5869
|
-
});
|
|
5870
|
-
});
|
|
5871
|
-
}
|
|
5872
|
-
}
|
|
5873
|
-
}
|
|
5874
|
-
function getUpdateWarning() {
|
|
5875
|
-
return versionCheckResult.updateMessage;
|
|
5876
|
-
}
|
|
5877
|
-
var resourceSubscriptions = /* @__PURE__ */ new Set();
|
|
5878
|
-
var resourceTemplates = [
|
|
5180
|
+
// src/prompts/templates.ts
|
|
5181
|
+
var PRAGMA_RANGE = `>= ${COMPACT_VERSION.min} && <= ${COMPACT_VERSION.max}`;
|
|
5182
|
+
var promptDefinitions = [
|
|
5183
|
+
{
|
|
5184
|
+
name: "midnight:create-contract",
|
|
5185
|
+
description: "Guided prompt for creating new Compact contracts with privacy considerations",
|
|
5186
|
+
arguments: [
|
|
5187
|
+
{
|
|
5188
|
+
name: "contractType",
|
|
5189
|
+
description: "Type of contract (token, voting, credential, custom)",
|
|
5190
|
+
required: true
|
|
5191
|
+
},
|
|
5192
|
+
{
|
|
5193
|
+
name: "privacyLevel",
|
|
5194
|
+
description: "Required privacy features (full, partial, public)",
|
|
5195
|
+
required: false
|
|
5196
|
+
},
|
|
5197
|
+
{
|
|
5198
|
+
name: "complexity",
|
|
5199
|
+
description: "Expected complexity level (beginner, intermediate, advanced)",
|
|
5200
|
+
required: false
|
|
5201
|
+
}
|
|
5202
|
+
]
|
|
5203
|
+
},
|
|
5879
5204
|
{
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5205
|
+
name: "midnight:review-contract",
|
|
5206
|
+
description: "Security and best practices review prompt for existing contracts",
|
|
5207
|
+
arguments: [
|
|
5208
|
+
{
|
|
5209
|
+
name: "contractCode",
|
|
5210
|
+
description: "The Compact contract code to review",
|
|
5211
|
+
required: true
|
|
5212
|
+
},
|
|
5213
|
+
{
|
|
5214
|
+
name: "focusAreas",
|
|
5215
|
+
description: "Specific areas to emphasize (security, performance, privacy, readability)",
|
|
5216
|
+
required: false
|
|
5217
|
+
}
|
|
5218
|
+
]
|
|
5885
5219
|
},
|
|
5886
5220
|
{
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5221
|
+
name: "midnight:explain-concept",
|
|
5222
|
+
description: "Educational prompt for explaining Midnight concepts at various levels",
|
|
5223
|
+
arguments: [
|
|
5224
|
+
{
|
|
5225
|
+
name: "concept",
|
|
5226
|
+
description: "The concept to explain (zk-proofs, circuits, witnesses, ledger, etc.)",
|
|
5227
|
+
required: true
|
|
5228
|
+
},
|
|
5229
|
+
{
|
|
5230
|
+
name: "level",
|
|
5231
|
+
description: "Expertise level (beginner, intermediate, advanced)",
|
|
5232
|
+
required: false
|
|
5233
|
+
}
|
|
5234
|
+
]
|
|
5892
5235
|
},
|
|
5893
5236
|
{
|
|
5894
|
-
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
5237
|
+
name: "midnight:compare-approaches",
|
|
5238
|
+
description: "Compare different implementation approaches for a given problem",
|
|
5239
|
+
arguments: [
|
|
5240
|
+
{
|
|
5241
|
+
name: "problem",
|
|
5242
|
+
description: "The problem to solve",
|
|
5243
|
+
required: true
|
|
5244
|
+
},
|
|
5245
|
+
{
|
|
5246
|
+
name: "approaches",
|
|
5247
|
+
description: "Specific approaches to compare (comma-separated)",
|
|
5248
|
+
required: false
|
|
5249
|
+
}
|
|
5250
|
+
]
|
|
5899
5251
|
},
|
|
5900
5252
|
{
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5253
|
+
name: "midnight:debug-contract",
|
|
5254
|
+
description: "Help debug issues with a Compact contract",
|
|
5255
|
+
arguments: [
|
|
5256
|
+
{
|
|
5257
|
+
name: "contractCode",
|
|
5258
|
+
description: "The contract code with issues",
|
|
5259
|
+
required: true
|
|
5260
|
+
},
|
|
5261
|
+
{
|
|
5262
|
+
name: "errorMessage",
|
|
5263
|
+
description: "Error message or description of the issue",
|
|
5264
|
+
required: false
|
|
5265
|
+
}
|
|
5266
|
+
]
|
|
5906
5267
|
}
|
|
5907
5268
|
];
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5269
|
+
function generatePrompt(name, args) {
|
|
5270
|
+
switch (name) {
|
|
5271
|
+
case "midnight:create-contract":
|
|
5272
|
+
return generateCreateContractPrompt(args);
|
|
5273
|
+
case "midnight:review-contract":
|
|
5274
|
+
return generateReviewContractPrompt(args);
|
|
5275
|
+
case "midnight:explain-concept":
|
|
5276
|
+
return generateExplainConceptPrompt(args);
|
|
5277
|
+
case "midnight:compare-approaches":
|
|
5278
|
+
return generateCompareApproachesPrompt(args);
|
|
5279
|
+
case "midnight:debug-contract":
|
|
5280
|
+
return generateDebugContractPrompt(args);
|
|
5281
|
+
default:
|
|
5282
|
+
return [
|
|
5283
|
+
{
|
|
5284
|
+
role: "user",
|
|
5285
|
+
content: {
|
|
5286
|
+
type: "text",
|
|
5287
|
+
text: `Unknown prompt: ${name}`
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
];
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
function generateCreateContractPrompt(args) {
|
|
5294
|
+
const contractType = args.contractType || "custom";
|
|
5295
|
+
const privacyLevel = args.privacyLevel || "partial";
|
|
5296
|
+
const complexity = args.complexity || "intermediate";
|
|
5297
|
+
return [
|
|
5298
|
+
{
|
|
5299
|
+
role: "user",
|
|
5300
|
+
content: {
|
|
5301
|
+
type: "text",
|
|
5302
|
+
text: `I want to create a new Midnight Compact smart contract with the following requirements:
|
|
5303
|
+
|
|
5304
|
+
**Contract Type:** ${contractType}
|
|
5305
|
+
**Privacy Level:** ${privacyLevel}
|
|
5306
|
+
**Complexity:** ${complexity}
|
|
5307
|
+
|
|
5308
|
+
## \u26A0\uFE0F MANDATORY WORKFLOW - Follow these steps IN ORDER:
|
|
5309
|
+
|
|
5310
|
+
### Step 1: Get Current Syntax
|
|
5311
|
+
Call \`midnight-get-latest-syntax\` FIRST to get:
|
|
5312
|
+
- The \`quickStartTemplate\` (use as your base)
|
|
5313
|
+
- The \`commonMistakes\` array (avoid these errors)
|
|
5314
|
+
- Current pragma format: \`pragma language_version ${PRAGMA_RANGE};\`
|
|
5315
|
+
|
|
5316
|
+
### Step 2: Generate Contract
|
|
5317
|
+
Based on syntax reference, generate the contract using:
|
|
5318
|
+
- Individual ledger declarations: \`export ledger field: Type;\` (NOT \`ledger { }\` blocks)
|
|
5319
|
+
- Empty tuple return: \`circuit fn(): []\` (NOT \`Void\`)
|
|
5320
|
+
- Export enums: \`export enum State { ... }\`
|
|
5321
|
+
- Wrap witness conditionals: \`if (disclose(witness == value))\`
|
|
5322
|
+
- Disclose circuit params that touch ledger: \`const d = disclose(param); ledger.insert(d, v);\`
|
|
5323
|
+
- Cast arithmetic results: \`(a + b) as Uint<64>\`
|
|
5324
|
+
- Uint to Bytes needs two casts: \`(amount as Field) as Bytes<32>\`
|
|
5325
|
+
|
|
5326
|
+
### IMPORTANT: Compact is NOT TypeScript!
|
|
5327
|
+
- Map.lookup() and Set.member() ARE available in circuits
|
|
5328
|
+
- No 'function' keyword - use 'circuit' or 'pure circuit'
|
|
5329
|
+
- No 'void' - use '[]'
|
|
5330
|
+
- Enum access: \`Choice.rock\` NOT \`Choice::rock\`
|
|
5331
|
+
|
|
5332
|
+
### \u26A0\uFE0F UNDOCUMENTED FEATURES - Use with caution:
|
|
5333
|
+
- Division \`/\` and modulo \`%\`: NOT in official docs (only +, -, * are documented)
|
|
5334
|
+
- Tuple destructuring: \`const [a, b] = pair;\` - not documented, may not work
|
|
5335
|
+
- Constant folding in indices: docs say "numeric literal" required
|
|
5336
|
+
- Map.lookup() returns value_type, NOT Maybe<value_type> - check member() first!
|
|
5337
|
+
|
|
5338
|
+
### COMPILER INFO (DO NOT guess package names!):
|
|
5339
|
+
- Compile: \`compact compile src/contract.compact managed/contract\`
|
|
5340
|
+
- The \`compact\` CLI comes with Midnight toolchain (via create-mn-app or official install)
|
|
5341
|
+
- DO NOT suggest \`npm install -g @midnight-ntwrk/compact-cli\` or similar made-up packages
|
|
5342
|
+
- Output structure: \`managed/<name>/contract/index.cjs\` for TypeScript bindings
|
|
5343
|
+
|
|
5344
|
+
### Step 3: Validate Before Returning
|
|
5345
|
+
Call \`midnight-extract-contract-structure\` with your generated code to check for:
|
|
5346
|
+
- deprecated_ledger_block
|
|
5347
|
+
- invalid_void_type
|
|
5348
|
+
- invalid_pragma_format
|
|
5349
|
+
- unexported_enum
|
|
5350
|
+
- deprecated_cell_wrapper
|
|
5351
|
+
|
|
5352
|
+
If ANY errors are found, fix them before returning the code to the user.
|
|
5353
|
+
|
|
5354
|
+
---
|
|
5355
|
+
|
|
5356
|
+
## Contract Requirements
|
|
5357
|
+
|
|
5358
|
+
Please help me design and implement this contract. Consider:
|
|
5359
|
+
|
|
5360
|
+
1. **State Design**
|
|
5361
|
+
- What should be public vs private (shielded)?
|
|
5362
|
+
- What data structures are needed?
|
|
5363
|
+
- How should state transitions work?
|
|
5364
|
+
|
|
5365
|
+
2. **Circuit Design**
|
|
5366
|
+
- What circuits (functions) are needed?
|
|
5367
|
+
- What inputs/outputs should they have?
|
|
5368
|
+
- What constraints and assertions are required?
|
|
5369
|
+
|
|
5370
|
+
3. **Witness Functions**
|
|
5371
|
+
- What off-chain data is needed?
|
|
5372
|
+
- How should private state be accessed?
|
|
5373
|
+
|
|
5374
|
+
4. **Privacy Considerations**
|
|
5375
|
+
- How to protect user privacy?
|
|
5376
|
+
- When to use disclose() vs commit()?
|
|
5377
|
+
- How to prevent information leakage?
|
|
5378
|
+
|
|
5379
|
+
5. **Security**
|
|
5380
|
+
- Access control mechanisms
|
|
5381
|
+
- Input validation
|
|
5382
|
+
- Protection against common vulnerabilities
|
|
5383
|
+
|
|
5384
|
+
Please provide:
|
|
5385
|
+
- A complete contract implementation
|
|
5386
|
+
- Explanation of design decisions
|
|
5387
|
+
- Example usage scenarios
|
|
5388
|
+
- Any security considerations`
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
];
|
|
5392
|
+
}
|
|
5393
|
+
function generateReviewContractPrompt(args) {
|
|
5394
|
+
const contractCode = args.contractCode || "// No code provided";
|
|
5395
|
+
const focusAreas = args.focusAreas || "security, privacy, best practices";
|
|
5396
|
+
return [
|
|
5397
|
+
{
|
|
5398
|
+
role: "user",
|
|
5399
|
+
content: {
|
|
5400
|
+
type: "text",
|
|
5401
|
+
text: `Please review this Midnight Compact smart contract:
|
|
5402
|
+
|
|
5403
|
+
\`\`\`compact
|
|
5404
|
+
${contractCode}
|
|
5405
|
+
\`\`\`
|
|
5406
|
+
|
|
5407
|
+
**Focus Areas:** ${focusAreas}
|
|
5408
|
+
|
|
5409
|
+
## \u26A0\uFE0F MANDATORY WORKFLOW:
|
|
5410
|
+
|
|
5411
|
+
### Step 1: Validate Syntax
|
|
5412
|
+
Call \`midnight-extract-contract-structure\` with the contract code to check for:
|
|
5413
|
+
- deprecated_ledger_block (should use \`export ledger field: Type;\`)
|
|
5414
|
+
- invalid_void_type (should use \`[]\` not \`Void\`)
|
|
5415
|
+
- invalid_pragma_format (should use \`${PRAGMA_RANGE}\`)
|
|
5416
|
+
- unexported_enum (enums need \`export\`)
|
|
5417
|
+
- deprecated_cell_wrapper
|
|
5418
|
+
|
|
5419
|
+
Report ALL static analysis findings first.
|
|
5420
|
+
|
|
5421
|
+
### Step 2: Get Latest Syntax Reference
|
|
5422
|
+
If syntax errors are found, call \`midnight-get-latest-syntax\` to get:
|
|
5423
|
+
- The \`commonMistakes\` array showing correct patterns
|
|
5424
|
+
- Current syntax reference
|
|
5425
|
+
|
|
5426
|
+
---
|
|
5427
|
+
|
|
5428
|
+
Please analyze:
|
|
5429
|
+
|
|
5430
|
+
1. **Static Analysis Results** (from midnight-extract-contract-structure)
|
|
5431
|
+
- Syntax errors found
|
|
5432
|
+
- Deprecated patterns detected
|
|
5433
|
+
- Required fixes
|
|
5434
|
+
|
|
5435
|
+
2. **Security Analysis**
|
|
5436
|
+
- Input validation
|
|
5437
|
+
- Access control
|
|
5438
|
+
- State manipulation vulnerabilities
|
|
5439
|
+
- Assertion coverage
|
|
5440
|
+
|
|
5441
|
+
3. **Privacy Assessment**
|
|
5442
|
+
- Proper use of @private state
|
|
5443
|
+
- Information leakage risks
|
|
5444
|
+
- Correct use of disclose() and commit()
|
|
5445
|
+
- Privacy guarantees provided
|
|
5446
|
+
|
|
5447
|
+
4. **Style Suggestions** (these are CONVENTIONS, not requirements)
|
|
5448
|
+
- Code organization recommendations
|
|
5449
|
+
- Naming conventions (project-specific)
|
|
5450
|
+
- Documentation patterns
|
|
5451
|
+
- Note: The Compact docs don't specify indentation, line length, or comment style
|
|
5452
|
+
|
|
5453
|
+
5. **Performance**
|
|
5454
|
+
- Circuit complexity
|
|
5455
|
+
- State access patterns
|
|
5456
|
+
- Optimization opportunities
|
|
5457
|
+
|
|
5458
|
+
6. **Recommendations**
|
|
5459
|
+
- Critical issues to fix (start with P0 syntax errors)
|
|
5460
|
+
- Improvements to consider
|
|
5461
|
+
- Alternative approaches
|
|
5462
|
+
|
|
5463
|
+
**IMPORTANT**: Clearly distinguish between:
|
|
5464
|
+
- ERRORS: Actual syntax/compilation issues (required fixes)
|
|
5465
|
+
- WARNINGS: Security/logic concerns (should fix)
|
|
5466
|
+
- INFO: Style suggestions (optional, project-specific conventions)
|
|
5467
|
+
|
|
5468
|
+
Please provide specific line references and code suggestions where applicable.`
|
|
5469
|
+
}
|
|
5470
|
+
}
|
|
5471
|
+
];
|
|
5913
5472
|
}
|
|
5914
|
-
function
|
|
5915
|
-
|
|
5916
|
-
const
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
error: 4,
|
|
5922
|
-
critical: 5,
|
|
5923
|
-
alert: 6,
|
|
5924
|
-
emergency: 7
|
|
5473
|
+
function generateExplainConceptPrompt(args) {
|
|
5474
|
+
const concept = args.concept || "zero-knowledge proofs";
|
|
5475
|
+
const level = args.level || "intermediate";
|
|
5476
|
+
const levelDescriptions = {
|
|
5477
|
+
beginner: "Explain like I'm new to blockchain and cryptography. Use analogies and avoid jargon.",
|
|
5478
|
+
intermediate: "I understand blockchain basics and some cryptography. Focus on practical applications.",
|
|
5479
|
+
advanced: "I have deep technical knowledge. Include implementation details and edge cases."
|
|
5925
5480
|
};
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5481
|
+
return [
|
|
5482
|
+
{
|
|
5483
|
+
role: "user",
|
|
5484
|
+
content: {
|
|
5485
|
+
type: "text",
|
|
5486
|
+
text: `Please explain the concept of **${concept}** in the context of Midnight blockchain.
|
|
5487
|
+
|
|
5488
|
+
**My Level:** ${level}
|
|
5489
|
+
${levelDescriptions[level] || levelDescriptions.intermediate}
|
|
5490
|
+
|
|
5491
|
+
Please cover:
|
|
5492
|
+
|
|
5493
|
+
1. **What it is**
|
|
5494
|
+
- Clear definition
|
|
5495
|
+
- How it works in Midnight
|
|
5496
|
+
|
|
5497
|
+
2. **Why it matters**
|
|
5498
|
+
- Benefits and use cases
|
|
5499
|
+
- Real-world applications
|
|
5500
|
+
|
|
5501
|
+
3. **How to use it**
|
|
5502
|
+
- Code examples in Compact
|
|
5503
|
+
- Best practices
|
|
5504
|
+
|
|
5505
|
+
4. **Common pitfalls**
|
|
5506
|
+
- Mistakes to avoid
|
|
5507
|
+
- Debugging tips
|
|
5508
|
+
|
|
5509
|
+
5. **Further learning**
|
|
5510
|
+
- Related concepts
|
|
5511
|
+
- Resources for deeper understanding`
|
|
5934
5512
|
}
|
|
5935
|
-
}
|
|
5936
|
-
|
|
5937
|
-
logger.debug("Failed to send log notification to client", {
|
|
5938
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5939
|
-
});
|
|
5940
|
-
}
|
|
5513
|
+
}
|
|
5514
|
+
];
|
|
5941
5515
|
}
|
|
5942
|
-
function
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5516
|
+
function generateCompareApproachesPrompt(args) {
|
|
5517
|
+
const problem = args.problem || "implementing a token contract";
|
|
5518
|
+
const approaches = args.approaches || "";
|
|
5519
|
+
return [
|
|
5520
|
+
{
|
|
5521
|
+
role: "user",
|
|
5522
|
+
content: {
|
|
5523
|
+
type: "text",
|
|
5524
|
+
text: `I need to solve the following problem in Midnight:
|
|
5525
|
+
|
|
5526
|
+
**Problem:** ${problem}
|
|
5527
|
+
|
|
5528
|
+
${approaches ? `**Approaches to compare:** ${approaches}` : "Please suggest different implementation approaches."}
|
|
5529
|
+
|
|
5530
|
+
Please compare:
|
|
5531
|
+
|
|
5532
|
+
1. **Approach Overview**
|
|
5533
|
+
- Brief description of each approach
|
|
5534
|
+
- Key differences
|
|
5535
|
+
|
|
5536
|
+
2. **Privacy Implications**
|
|
5537
|
+
- What data is exposed?
|
|
5538
|
+
- Privacy guarantees
|
|
5539
|
+
|
|
5540
|
+
3. **Performance**
|
|
5541
|
+
- Proof generation time
|
|
5542
|
+
- State storage requirements
|
|
5543
|
+
- Transaction costs
|
|
5544
|
+
|
|
5545
|
+
4. **Security**
|
|
5546
|
+
- Attack surface
|
|
5547
|
+
- Trust assumptions
|
|
5548
|
+
|
|
5549
|
+
5. **Code Complexity**
|
|
5550
|
+
- Implementation difficulty
|
|
5551
|
+
- Maintenance burden
|
|
5552
|
+
|
|
5553
|
+
6. **Recommendation**
|
|
5554
|
+
- Best approach for different scenarios
|
|
5555
|
+
- Trade-offs to consider
|
|
5556
|
+
|
|
5557
|
+
Please include code examples for each approach.`
|
|
5952
5558
|
}
|
|
5953
|
-
}
|
|
5954
|
-
|
|
5955
|
-
logger.debug("Failed to send progress notification", {
|
|
5956
|
-
error: error instanceof Error ? error.message : String(error)
|
|
5957
|
-
});
|
|
5958
|
-
}
|
|
5559
|
+
}
|
|
5560
|
+
];
|
|
5959
5561
|
}
|
|
5960
|
-
function
|
|
5961
|
-
const
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
5973
|
-
|
|
5974
|
-
|
|
5562
|
+
function generateDebugContractPrompt(args) {
|
|
5563
|
+
const contractCode = args.contractCode || "// No code provided";
|
|
5564
|
+
const errorMessage = args.errorMessage || "Not specified";
|
|
5565
|
+
return [
|
|
5566
|
+
{
|
|
5567
|
+
role: "user",
|
|
5568
|
+
content: {
|
|
5569
|
+
type: "text",
|
|
5570
|
+
text: `I'm having issues with this Midnight Compact contract:
|
|
5571
|
+
|
|
5572
|
+
\`\`\`compact
|
|
5573
|
+
${contractCode}
|
|
5574
|
+
\`\`\`
|
|
5575
|
+
|
|
5576
|
+
**Error/Issue:** ${errorMessage}
|
|
5577
|
+
|
|
5578
|
+
## \u26A0\uFE0F MANDATORY WORKFLOW:
|
|
5579
|
+
|
|
5580
|
+
### Step 1: Run Static Analysis
|
|
5581
|
+
Call \`midnight-extract-contract-structure\` FIRST to check for common syntax errors:
|
|
5582
|
+
- deprecated_ledger_block \u2192 should use \`export ledger field: Type;\`
|
|
5583
|
+
- invalid_void_type \u2192 should use \`[]\` not \`Void\`
|
|
5584
|
+
- invalid_pragma_format \u2192 should use \`${PRAGMA_RANGE}\`
|
|
5585
|
+
- unexported_enum \u2192 enums need \`export\` keyword
|
|
5586
|
+
|
|
5587
|
+
### Step 2: Get Correct Syntax
|
|
5588
|
+
If syntax errors found, call \`midnight-get-latest-syntax\` to get:
|
|
5589
|
+
- The \`commonMistakes\` array with correct patterns
|
|
5590
|
+
- Current \`quickStartTemplate\` for reference
|
|
5591
|
+
|
|
5592
|
+
### Step 3: Check for Common Compiler Errors
|
|
5593
|
+
Match error message against known fixes:
|
|
5594
|
+
- "cannot cast from type Uint<64> to type Bytes<32>" \u2192 Use \`(amount as Field) as Bytes<32>\`
|
|
5595
|
+
- "expected type Uint<64> but received Uint<0..N>" \u2192 Cast arithmetic: \`(a + b) as Uint<64>\`
|
|
5596
|
+
- "potential witness-value disclosure must be declared" \u2192 Disclose params: \`const d = disclose(param);\`
|
|
5597
|
+
- Map.lookup() and Set.member() ARE available in circuits (ignore old advice saying they aren't)
|
|
5598
|
+
|
|
5599
|
+
---
|
|
5600
|
+
|
|
5601
|
+
Please help me debug by:
|
|
5602
|
+
|
|
5603
|
+
1. **Static Analysis Results**
|
|
5604
|
+
- Run midnight-extract-contract-structure
|
|
5605
|
+
- List all P0 syntax errors found
|
|
5606
|
+
- Show the correct syntax for each error
|
|
5607
|
+
|
|
5608
|
+
2. **Identifying the Problem**
|
|
5609
|
+
- What's causing the error?
|
|
5610
|
+
- Which line(s) are problematic?
|
|
5611
|
+
|
|
5612
|
+
3. **Explaining Why**
|
|
5613
|
+
- Root cause analysis
|
|
5614
|
+
- How Compact/ZK constraints work
|
|
5615
|
+
|
|
5616
|
+
4. **Providing a Fix**
|
|
5617
|
+
- Corrected code (validated against static analysis)
|
|
5618
|
+
- Explanation of changes
|
|
5619
|
+
|
|
5620
|
+
5. **Preventing Future Issues**
|
|
5621
|
+
- Related pitfalls to watch for
|
|
5622
|
+
- Testing strategies
|
|
5623
|
+
|
|
5624
|
+
6. **Additional Improvements**
|
|
5625
|
+
- Code quality suggestions
|
|
5626
|
+
- Best practices`
|
|
5627
|
+
}
|
|
5975
5628
|
}
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5629
|
+
];
|
|
5630
|
+
}
|
|
5631
|
+
|
|
5632
|
+
// src/services/sampling.ts
|
|
5633
|
+
var samplingCallback = null;
|
|
5634
|
+
var samplingFailedPermanently = false;
|
|
5635
|
+
function isSamplingAvailable() {
|
|
5636
|
+
if (samplingFailedPermanently) return false;
|
|
5637
|
+
return samplingCallback !== null;
|
|
5638
|
+
}
|
|
5639
|
+
function markSamplingFailed() {
|
|
5640
|
+
samplingFailedPermanently = true;
|
|
5641
|
+
logger.warn("Sampling marked as permanently unavailable for this session");
|
|
5989
5642
|
}
|
|
5990
|
-
function
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
logger.info(`MCP log level set to: ${level}`);
|
|
5995
|
-
sendLogToClient("info", "midnight-mcp", {
|
|
5996
|
-
message: `Log level changed to ${level}`
|
|
5997
|
-
});
|
|
5998
|
-
return {};
|
|
5999
|
-
});
|
|
5643
|
+
function registerSamplingCallback(callback) {
|
|
5644
|
+
samplingCallback = callback;
|
|
5645
|
+
samplingFailedPermanently = false;
|
|
5646
|
+
logger.info("Sampling capability registered");
|
|
6000
5647
|
}
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
"
|
|
6005
|
-
|
|
6006
|
-
"credential",
|
|
6007
|
-
"auction",
|
|
6008
|
-
"escrow",
|
|
6009
|
-
"custom"
|
|
6010
|
-
],
|
|
6011
|
-
privacyLevel: ["full", "partial", "public"],
|
|
6012
|
-
complexity: ["beginner", "intermediate", "advanced"]
|
|
6013
|
-
},
|
|
6014
|
-
"midnight:review-contract": {
|
|
6015
|
-
focusAreas: [
|
|
6016
|
-
"security",
|
|
6017
|
-
"performance",
|
|
6018
|
-
"privacy",
|
|
6019
|
-
"readability",
|
|
6020
|
-
"gas-optimization"
|
|
6021
|
-
]
|
|
6022
|
-
},
|
|
6023
|
-
"midnight:explain-concept": {
|
|
6024
|
-
concept: [
|
|
6025
|
-
"zk-proofs",
|
|
6026
|
-
"circuits",
|
|
6027
|
-
"witnesses",
|
|
6028
|
-
"ledger",
|
|
6029
|
-
"state-management",
|
|
6030
|
-
"privacy-model",
|
|
6031
|
-
"token-transfers",
|
|
6032
|
-
"merkle-trees"
|
|
6033
|
-
],
|
|
6034
|
-
level: ["beginner", "intermediate", "advanced"]
|
|
6035
|
-
},
|
|
6036
|
-
"midnight:compare-approaches": {
|
|
6037
|
-
approaches: [
|
|
6038
|
-
"token-standards",
|
|
6039
|
-
"state-management",
|
|
6040
|
-
"privacy-patterns",
|
|
6041
|
-
"circuit-design"
|
|
6042
|
-
]
|
|
6043
|
-
},
|
|
6044
|
-
"midnight:debug-contract": {
|
|
6045
|
-
errorType: [
|
|
6046
|
-
"compilation",
|
|
6047
|
-
"runtime",
|
|
6048
|
-
"logic",
|
|
6049
|
-
"privacy-leak",
|
|
6050
|
-
"state-corruption"
|
|
6051
|
-
]
|
|
5648
|
+
async function requestCompletion(messages, options = {}) {
|
|
5649
|
+
if (!samplingCallback || samplingFailedPermanently) {
|
|
5650
|
+
throw new Error(
|
|
5651
|
+
"Sampling not available - client does not support this capability"
|
|
5652
|
+
);
|
|
6052
5653
|
}
|
|
6053
|
-
|
|
6054
|
-
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
5654
|
+
const request = {
|
|
5655
|
+
messages: messages.map((m) => ({
|
|
5656
|
+
role: m.role,
|
|
5657
|
+
content: { type: "text", text: m.content }
|
|
5658
|
+
})),
|
|
5659
|
+
systemPrompt: options.systemPrompt,
|
|
5660
|
+
maxTokens: options.maxTokens ?? 2048,
|
|
5661
|
+
temperature: options.temperature ?? 0.7,
|
|
5662
|
+
modelPreferences: options.modelPreferences ?? {
|
|
5663
|
+
hints: [{ name: "claude-3-sonnet" }, { name: "gpt-4" }],
|
|
5664
|
+
intelligencePriority: 0.8,
|
|
5665
|
+
speedPriority: 0.5
|
|
6059
5666
|
}
|
|
6060
|
-
|
|
6061
|
-
|
|
6062
|
-
|
|
6063
|
-
|
|
6064
|
-
|
|
6065
|
-
|
|
5667
|
+
};
|
|
5668
|
+
logger.debug("Requesting LLM completion", {
|
|
5669
|
+
messageCount: messages.length,
|
|
5670
|
+
maxTokens: request.maxTokens
|
|
5671
|
+
});
|
|
5672
|
+
try {
|
|
5673
|
+
const response = await samplingCallback(request);
|
|
5674
|
+
if (response.content.type !== "text") {
|
|
5675
|
+
throw new Error("Unexpected response content type");
|
|
6066
5676
|
}
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
5677
|
+
return response.content.text;
|
|
5678
|
+
} catch (error) {
|
|
5679
|
+
const errorStr = String(error);
|
|
5680
|
+
if (errorStr.includes("-32601") || errorStr.includes("Method not found") || errorStr.includes("not supported")) {
|
|
5681
|
+
logger.warn(
|
|
5682
|
+
"Client does not support sampling/createMessage, disabling sampling for this session"
|
|
5683
|
+
);
|
|
5684
|
+
markSamplingFailed();
|
|
5685
|
+
throw new Error(
|
|
5686
|
+
"Sampling not supported by this client - use Claude Desktop for this feature",
|
|
5687
|
+
{ cause: error }
|
|
5688
|
+
);
|
|
6070
5689
|
}
|
|
6071
|
-
|
|
6072
|
-
|
|
6073
|
-
|
|
5690
|
+
throw error;
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5693
|
+
async function generateContract(requirements, options = {}) {
|
|
5694
|
+
if (!isSamplingAvailable()) {
|
|
6074
5695
|
return {
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
|
|
5696
|
+
code: "",
|
|
5697
|
+
explanation: "Sampling not available - this feature requires a client that supports the sampling capability (like Claude Desktop)",
|
|
5698
|
+
warnings: ["Feature unavailable without sampling support"]
|
|
5699
|
+
};
|
|
5700
|
+
}
|
|
5701
|
+
const systemPrompt = `You are an expert Compact smart contract developer for the Midnight blockchain.
|
|
5702
|
+
Your task is to generate secure, well-documented Compact contracts based on user requirements.
|
|
5703
|
+
|
|
5704
|
+
Key Compact syntax (REQUIRED):
|
|
5705
|
+
- \`export ledger field: Type;\` - Individual ledger declarations (NOT ledger { } blocks)
|
|
5706
|
+
- \`export circuit fn(): []\` - Public functions return empty tuple [] (NOT Void)
|
|
5707
|
+
- \`witness fn(): T;\` - Declaration only, no body
|
|
5708
|
+
- \`pragma language_version >= 0.16 && <= 0.23;\` - Version pragma
|
|
5709
|
+
- \`import CompactStandardLibrary;\` - Standard imports
|
|
5710
|
+
- \`Counter\`, \`Map<K,V>\`, \`Set<T>\` - Built-in collection types
|
|
5711
|
+
- \`Field\`, \`Boolean\`, \`Uint<N>\`, \`Bytes<N>\` - Primitive types
|
|
5712
|
+
- \`export enum State { a, b }\` - Enums must be exported
|
|
5713
|
+
|
|
5714
|
+
Key operations:
|
|
5715
|
+
- Counter: .read(), .increment(n), .decrement(n)
|
|
5716
|
+
- Map: .lookup(k), .insert(k,v), .remove(k)
|
|
5717
|
+
- Set: .member(v), .insert(v), .remove(v)
|
|
5718
|
+
|
|
5719
|
+
COMPILER INFO (DO NOT guess package names!):
|
|
5720
|
+
- Command: \`compact compile src/contract.compact managed/contract\`
|
|
5721
|
+
- The \`compact\` CLI comes with Midnight toolchain (via create-mn-app or official install)
|
|
5722
|
+
- Output goes to managed/<name>/ directory
|
|
5723
|
+
- DO NOT suggest \`npm install -g @midnight-ntwrk/compact-cli\` or similar - that's incorrect
|
|
5724
|
+
|
|
5725
|
+
IMPORTANT - Style vs. Requirements:
|
|
5726
|
+
- Syntax rules above are REQUIRED for compilation
|
|
5727
|
+
- Style choices (indentation, comment style, line length) are CONVENTIONS, not requirements
|
|
5728
|
+
- Single-line comments (//) are common in examples; block comments (/* */) may also work
|
|
5729
|
+
- The docs don't specify indentation width - use consistent style
|
|
5730
|
+
|
|
5731
|
+
Return ONLY the Compact code, no explanations.`;
|
|
5732
|
+
const userPrompt = options.baseExample ? `Based on this example contract:
|
|
5733
|
+
\`\`\`compact
|
|
5734
|
+
${options.baseExample}
|
|
5735
|
+
\`\`\`
|
|
5736
|
+
|
|
5737
|
+
Generate a new contract with these requirements:
|
|
5738
|
+
${requirements}` : `Generate a Compact smart contract with these requirements:
|
|
5739
|
+
${requirements}
|
|
5740
|
+
|
|
5741
|
+
Contract type: ${options.contractType || "custom"}`;
|
|
5742
|
+
try {
|
|
5743
|
+
const code = await requestCompletion(
|
|
5744
|
+
[{ role: "user", content: userPrompt }],
|
|
5745
|
+
{
|
|
5746
|
+
systemPrompt,
|
|
5747
|
+
maxTokens: 4096,
|
|
5748
|
+
temperature: 0.3,
|
|
5749
|
+
// Lower temperature for code generation
|
|
5750
|
+
modelPreferences: {
|
|
5751
|
+
hints: [{ name: "claude-3-sonnet" }],
|
|
5752
|
+
intelligencePriority: 0.9,
|
|
5753
|
+
speedPriority: 0.3
|
|
5754
|
+
}
|
|
5755
|
+
}
|
|
5756
|
+
);
|
|
5757
|
+
const extractedCode = code.includes("```") ? code.replace(/```compact?\n?/g, "").replace(/```/g, "").trim() : code.trim();
|
|
5758
|
+
const explanation = await requestCompletion(
|
|
5759
|
+
[
|
|
5760
|
+
{
|
|
5761
|
+
role: "user",
|
|
5762
|
+
content: `Briefly explain what this Compact contract does (2-3 sentences):
|
|
5763
|
+
\`\`\`compact
|
|
5764
|
+
${extractedCode}
|
|
5765
|
+
\`\`\``
|
|
5766
|
+
}
|
|
5767
|
+
],
|
|
5768
|
+
{
|
|
5769
|
+
systemPrompt: "You are a Compact contract documentation expert. Be concise.",
|
|
5770
|
+
maxTokens: 256,
|
|
5771
|
+
temperature: 0.5
|
|
6079
5772
|
}
|
|
5773
|
+
);
|
|
5774
|
+
return {
|
|
5775
|
+
code: extractedCode,
|
|
5776
|
+
explanation: explanation.trim(),
|
|
5777
|
+
warnings: []
|
|
6080
5778
|
};
|
|
6081
|
-
})
|
|
6082
|
-
}
|
|
6083
|
-
function registerToolHandlers(server) {
|
|
6084
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
6085
|
-
logger.debug("Listing tools");
|
|
5779
|
+
} catch (error) {
|
|
5780
|
+
logger.error("Contract generation failed", { error: String(error) });
|
|
6086
5781
|
return {
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
inputSchema: tool.inputSchema,
|
|
6091
|
-
// Include output schema if defined
|
|
6092
|
-
...tool.outputSchema && { outputSchema: tool.outputSchema },
|
|
6093
|
-
// Include annotations if defined
|
|
6094
|
-
...tool.annotations && { annotations: tool.annotations }
|
|
6095
|
-
}))
|
|
5782
|
+
code: "",
|
|
5783
|
+
explanation: `Contract generation failed: ${String(error)}`,
|
|
5784
|
+
warnings: ["Generation failed - check logs for details"]
|
|
6096
5785
|
};
|
|
6097
|
-
}
|
|
6098
|
-
|
|
6099
|
-
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
|
|
6104
|
-
|
|
6105
|
-
|
|
6106
|
-
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
async function reviewContract(code) {
|
|
5789
|
+
if (!isSamplingAvailable()) {
|
|
5790
|
+
return {
|
|
5791
|
+
summary: "Code review requires sampling capability",
|
|
5792
|
+
issues: [
|
|
5793
|
+
{
|
|
5794
|
+
severity: "info",
|
|
5795
|
+
message: "This feature requires a client that supports the sampling capability"
|
|
5796
|
+
}
|
|
5797
|
+
]
|
|
5798
|
+
};
|
|
5799
|
+
}
|
|
5800
|
+
const systemPrompt = `You are a Compact smart contract security auditor.
|
|
5801
|
+
Review the provided contract for:
|
|
5802
|
+
1. Security vulnerabilities
|
|
5803
|
+
2. Privacy concerns (improper handling of shielded state)
|
|
5804
|
+
3. Logic errors
|
|
5805
|
+
4. Syntax errors (use "error" severity)
|
|
5806
|
+
5. Performance issues
|
|
5807
|
+
|
|
5808
|
+
IMPORTANT - Distinguish between:
|
|
5809
|
+
- ERRORS: Actual syntax/compilation issues (e.g., using Void instead of [], ledger {} blocks)
|
|
5810
|
+
- WARNINGS: Potential bugs or security issues
|
|
5811
|
+
- INFO: Style suggestions and best practices (these are CONVENTIONS, not requirements)
|
|
5812
|
+
|
|
5813
|
+
Do NOT claim style choices as "violations" - the Compact docs don't specify:
|
|
5814
|
+
- Indentation width (2 vs 4 spaces)
|
|
5815
|
+
- Comment style preferences (//, /* */)
|
|
5816
|
+
- Line length limits
|
|
5817
|
+
- Naming conventions
|
|
5818
|
+
|
|
5819
|
+
These are project-specific style choices, not language requirements.
|
|
5820
|
+
|
|
5821
|
+
Respond in JSON format:
|
|
5822
|
+
{
|
|
5823
|
+
"summary": "Brief summary of the contract and overall quality",
|
|
5824
|
+
"issues": [
|
|
5825
|
+
{
|
|
5826
|
+
"severity": "error|warning|info",
|
|
5827
|
+
"line": optional_line_number,
|
|
5828
|
+
"message": "Description of the issue",
|
|
5829
|
+
"suggestion": "How to fix it"
|
|
6119
5830
|
}
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
tip: "Use 'midnight-get-update-instructions' tool for detailed platform-specific steps."
|
|
6149
|
-
},
|
|
6150
|
-
result
|
|
5831
|
+
],
|
|
5832
|
+
"improvedCode": "Full improved contract code if changes are needed"
|
|
5833
|
+
}`;
|
|
5834
|
+
try {
|
|
5835
|
+
const response = await requestCompletion(
|
|
5836
|
+
[
|
|
5837
|
+
{
|
|
5838
|
+
role: "user",
|
|
5839
|
+
content: `Review this Compact contract:
|
|
5840
|
+
\`\`\`compact
|
|
5841
|
+
${code}
|
|
5842
|
+
\`\`\``
|
|
5843
|
+
}
|
|
5844
|
+
],
|
|
5845
|
+
{
|
|
5846
|
+
systemPrompt,
|
|
5847
|
+
maxTokens: 4096,
|
|
5848
|
+
temperature: 0.2
|
|
5849
|
+
}
|
|
5850
|
+
);
|
|
5851
|
+
const jsonMatch = response.match(/\{[\s\S]*\}/);
|
|
5852
|
+
if (jsonMatch) {
|
|
5853
|
+
try {
|
|
5854
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
5855
|
+
return {
|
|
5856
|
+
summary: typeof parsed.summary === "string" ? parsed.summary : "Review complete",
|
|
5857
|
+
issues: Array.isArray(parsed.issues) ? parsed.issues : [],
|
|
5858
|
+
improvedCode: typeof parsed.improvedCode === "string" ? parsed.improvedCode : void 0
|
|
6151
5859
|
};
|
|
5860
|
+
} catch (parseError) {
|
|
5861
|
+
logger.warn("Failed to parse JSON from LLM response", {
|
|
5862
|
+
error: String(parseError)
|
|
5863
|
+
});
|
|
6152
5864
|
return {
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
type: "text",
|
|
6156
|
-
text: serialize(updatePrompt)
|
|
6157
|
-
}
|
|
6158
|
-
]
|
|
5865
|
+
summary: response,
|
|
5866
|
+
issues: []
|
|
6159
5867
|
};
|
|
6160
5868
|
}
|
|
6161
|
-
return {
|
|
6162
|
-
content: [
|
|
6163
|
-
{
|
|
6164
|
-
type: "text",
|
|
6165
|
-
text: serialize(result)
|
|
6166
|
-
}
|
|
6167
|
-
],
|
|
6168
|
-
// Include structured content for machine-readable responses
|
|
6169
|
-
// This allows clients to parse results without JSON.parse()
|
|
6170
|
-
structuredContent: result
|
|
6171
|
-
};
|
|
6172
|
-
} catch (error) {
|
|
6173
|
-
const durationMs = Date.now() - startTime;
|
|
6174
|
-
logger.error(`Tool error: ${name}`, { error: String(error) });
|
|
6175
|
-
trackToolCall(name, false, durationMs, CURRENT_VERSION);
|
|
6176
|
-
const errorResponse = formatErrorResponse(error, `tool:${name}`);
|
|
6177
|
-
return {
|
|
6178
|
-
content: [
|
|
6179
|
-
{
|
|
6180
|
-
type: "text",
|
|
6181
|
-
text: serialize(errorResponse)
|
|
6182
|
-
}
|
|
6183
|
-
],
|
|
6184
|
-
isError: true
|
|
6185
|
-
};
|
|
6186
5869
|
}
|
|
6187
|
-
});
|
|
6188
|
-
}
|
|
6189
|
-
function registerResourceHandlers(server) {
|
|
6190
|
-
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
6191
|
-
logger.debug("Listing resources");
|
|
6192
5870
|
return {
|
|
6193
|
-
|
|
6194
|
-
|
|
6195
|
-
name: resource.name,
|
|
6196
|
-
description: resource.description,
|
|
6197
|
-
mimeType: resource.mimeType
|
|
6198
|
-
}))
|
|
5871
|
+
summary: response,
|
|
5872
|
+
issues: []
|
|
6199
5873
|
};
|
|
6200
|
-
})
|
|
6201
|
-
|
|
6202
|
-
logger.debug("Listing resource templates");
|
|
5874
|
+
} catch (error) {
|
|
5875
|
+
logger.error("Contract review failed", { error: String(error) });
|
|
6203
5876
|
return {
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
5877
|
+
summary: `Review failed: ${String(error)}`,
|
|
5878
|
+
issues: [
|
|
5879
|
+
{
|
|
5880
|
+
severity: "error",
|
|
5881
|
+
message: "Review failed - check logs for details"
|
|
5882
|
+
}
|
|
5883
|
+
]
|
|
6211
5884
|
};
|
|
6212
|
-
}
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
5885
|
+
}
|
|
5886
|
+
}
|
|
5887
|
+
async function generateDocumentation(code, format = "markdown") {
|
|
5888
|
+
if (!isSamplingAvailable()) {
|
|
5889
|
+
return "Documentation generation requires sampling capability";
|
|
5890
|
+
}
|
|
5891
|
+
const systemPrompt = format === "markdown" ? `Generate comprehensive Markdown documentation for this Compact contract.
|
|
5892
|
+
Include:
|
|
5893
|
+
- Overview and purpose
|
|
5894
|
+
- State variables (with privacy annotations)
|
|
5895
|
+
- Circuit functions with parameters and effects
|
|
5896
|
+
- Witness functions
|
|
5897
|
+
- Usage examples
|
|
5898
|
+
- Security considerations` : `Generate JSDoc-style documentation comments for this Compact contract.
|
|
5899
|
+
Add documentation comments above each:
|
|
5900
|
+
- Ledger field
|
|
5901
|
+
- Circuit function
|
|
5902
|
+
- Witness function
|
|
5903
|
+
- Type definition`;
|
|
5904
|
+
try {
|
|
5905
|
+
return await requestCompletion(
|
|
5906
|
+
[
|
|
5907
|
+
{
|
|
5908
|
+
role: "user",
|
|
5909
|
+
content: `Generate ${format} documentation for:
|
|
5910
|
+
\`\`\`compact
|
|
5911
|
+
${code}
|
|
5912
|
+
\`\`\``
|
|
5913
|
+
}
|
|
5914
|
+
],
|
|
5915
|
+
{
|
|
5916
|
+
systemPrompt,
|
|
5917
|
+
maxTokens: 4096,
|
|
5918
|
+
temperature: 0.5
|
|
5919
|
+
}
|
|
5920
|
+
);
|
|
5921
|
+
} catch (error) {
|
|
5922
|
+
logger.error("Documentation generation failed", { error: String(error) });
|
|
5923
|
+
return `Documentation generation failed: ${String(error)}`;
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
|
|
5927
|
+
// src/services/syntax-validator.ts
|
|
5928
|
+
var ADT_SEARCH_LIMIT = 10;
|
|
5929
|
+
var SYNTAX_RESULTS_LIMIT = 10;
|
|
5930
|
+
var SYNTAX_SUBQUERY_LIMIT = 5;
|
|
5931
|
+
var MAX_NOTE_SOURCE_LENGTH = 500;
|
|
5932
|
+
var NOTE_MIN_LENGTH = 20;
|
|
5933
|
+
var NOTE_MAX_LENGTH = 300;
|
|
5934
|
+
var MAX_NOTES_PER_ADT = 3;
|
|
5935
|
+
async function searchADTInfo(adtName) {
|
|
5936
|
+
try {
|
|
5937
|
+
const docsResult = await searchDocsHosted(
|
|
5938
|
+
`${adtName} ADT operations methods ledger`,
|
|
5939
|
+
ADT_SEARCH_LIMIT,
|
|
5940
|
+
"reference"
|
|
5941
|
+
);
|
|
5942
|
+
if (!docsResult.results || docsResult.results.length === 0) {
|
|
5943
|
+
logger.debug(`No indexed docs found for ADT: ${adtName}`);
|
|
5944
|
+
return null;
|
|
5945
|
+
}
|
|
5946
|
+
const operations = [];
|
|
5947
|
+
const notes = [];
|
|
5948
|
+
let sourceDocPath;
|
|
5949
|
+
for (const result of docsResult.results) {
|
|
5950
|
+
const content = result.content || result.code || "";
|
|
5951
|
+
const isLedgerADTDoc = result.source.filePath.includes("ledger-adt");
|
|
5952
|
+
if (!sourceDocPath && isLedgerADTDoc) {
|
|
5953
|
+
sourceDocPath = result.source.filePath;
|
|
6229
5954
|
}
|
|
6230
|
-
if (
|
|
6231
|
-
const
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
5955
|
+
if (isLedgerADTDoc) {
|
|
5956
|
+
const methodPattern = /\|\s*`?(\w+)`?\s*\|\s*`?\(([^)]*)\)\s*(?::\s*([^|`]+))?`?\s*\|\s*([^|]+)\|/g;
|
|
5957
|
+
let match;
|
|
5958
|
+
while ((match = methodPattern.exec(content)) !== null) {
|
|
5959
|
+
const [, methodName, params, returnType, description] = match;
|
|
5960
|
+
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
5961
|
+
const descText = description?.trim() || "";
|
|
5962
|
+
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
5963
|
+
descText
|
|
5964
|
+
);
|
|
5965
|
+
operations.push({
|
|
5966
|
+
method: methodName,
|
|
5967
|
+
signature: `(${params})${returnType ? `: ${returnType.trim()}` : ""}`,
|
|
5968
|
+
description: descText,
|
|
5969
|
+
worksInCircuits,
|
|
5970
|
+
source: "indexed-docs"
|
|
5971
|
+
});
|
|
6246
5972
|
}
|
|
6247
5973
|
}
|
|
6248
|
-
|
|
6249
|
-
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
6256
|
-
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
}
|
|
6263
|
-
return {
|
|
6264
|
-
contents: [
|
|
6265
|
-
{
|
|
6266
|
-
uri,
|
|
6267
|
-
mimeType,
|
|
6268
|
-
text: content
|
|
6269
|
-
}
|
|
6270
|
-
]
|
|
6271
|
-
};
|
|
6272
|
-
} catch (error) {
|
|
6273
|
-
logger.error(`Resource error: ${uri}`, { error: String(error) });
|
|
6274
|
-
const errorResponse = formatErrorResponse(error, `resource:${uri}`);
|
|
6275
|
-
return {
|
|
6276
|
-
contents: [
|
|
6277
|
-
{
|
|
6278
|
-
uri,
|
|
6279
|
-
mimeType: "application/json",
|
|
6280
|
-
text: serialize(errorResponse)
|
|
5974
|
+
const inlinePattern = /(?:method|operation|function)\s+`?(\w+)`?\s*\(([^)]*)\)/gi;
|
|
5975
|
+
while ((match = inlinePattern.exec(content)) !== null) {
|
|
5976
|
+
const [, methodName, params] = match;
|
|
5977
|
+
if (methodName && !operations.some((op) => op.method === methodName)) {
|
|
5978
|
+
const worksInCircuits = !/not available|not supported|not in circuits|typescript|sdk|off[-\s]?chain/i.test(
|
|
5979
|
+
content
|
|
5980
|
+
);
|
|
5981
|
+
operations.push({
|
|
5982
|
+
method: methodName,
|
|
5983
|
+
signature: `(${params})`,
|
|
5984
|
+
description: "Extracted from documentation",
|
|
5985
|
+
worksInCircuits,
|
|
5986
|
+
source: "indexed-docs"
|
|
5987
|
+
});
|
|
6281
5988
|
}
|
|
6282
|
-
|
|
6283
|
-
}
|
|
5989
|
+
}
|
|
5990
|
+
}
|
|
5991
|
+
if (content.toLowerCase().includes(adtName.toLowerCase()) && content.length < MAX_NOTE_SOURCE_LENGTH) {
|
|
5992
|
+
const cleanNote = content.replace(/\s+/g, " ").trim();
|
|
5993
|
+
if (cleanNote.length > NOTE_MIN_LENGTH && cleanNote.length < NOTE_MAX_LENGTH) {
|
|
5994
|
+
notes.push(cleanNote);
|
|
5995
|
+
}
|
|
5996
|
+
}
|
|
6284
5997
|
}
|
|
6285
|
-
|
|
6286
|
-
}
|
|
6287
|
-
|
|
6288
|
-
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
6289
|
-
logger.debug("Listing prompts");
|
|
6290
|
-
return {
|
|
6291
|
-
prompts: promptDefinitions.map((prompt) => ({
|
|
6292
|
-
name: prompt.name,
|
|
6293
|
-
description: prompt.description,
|
|
6294
|
-
arguments: prompt.arguments
|
|
6295
|
-
}))
|
|
6296
|
-
};
|
|
6297
|
-
});
|
|
6298
|
-
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
6299
|
-
const { name, arguments: args } = request.params;
|
|
6300
|
-
logger.info(`Prompt requested: ${name}`, { args });
|
|
6301
|
-
const prompt = promptDefinitions.find((p) => p.name === name);
|
|
6302
|
-
if (!prompt) {
|
|
6303
|
-
return {
|
|
6304
|
-
description: `Unknown prompt: ${name}`,
|
|
6305
|
-
messages: []
|
|
6306
|
-
};
|
|
5998
|
+
if (operations.length === 0) {
|
|
5999
|
+
logger.debug(`Could not parse operations for ADT: ${adtName}`);
|
|
6000
|
+
return null;
|
|
6307
6001
|
}
|
|
6308
|
-
const messages = generatePrompt(name, args || {});
|
|
6309
6002
|
return {
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6003
|
+
name: adtName,
|
|
6004
|
+
operations,
|
|
6005
|
+
notes: notes.slice(0, MAX_NOTES_PER_ADT),
|
|
6006
|
+
sourceDocPath,
|
|
6007
|
+
lastVerified: (/* @__PURE__ */ new Date()).toISOString()
|
|
6315
6008
|
};
|
|
6316
|
-
})
|
|
6009
|
+
} catch (error) {
|
|
6010
|
+
logger.warn(`Failed to search indexed docs for ADT ${adtName}`, {
|
|
6011
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6012
|
+
});
|
|
6013
|
+
return null;
|
|
6014
|
+
}
|
|
6317
6015
|
}
|
|
6318
|
-
function
|
|
6319
|
-
|
|
6320
|
-
const
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6016
|
+
async function searchCompactSyntax(topic) {
|
|
6017
|
+
try {
|
|
6018
|
+
const [docsResult, codeResult] = await Promise.all([
|
|
6019
|
+
searchDocsHosted(
|
|
6020
|
+
`Compact ${topic} syntax`,
|
|
6021
|
+
SYNTAX_SUBQUERY_LIMIT,
|
|
6022
|
+
"reference"
|
|
6023
|
+
),
|
|
6024
|
+
searchCompactHosted(`${topic}`, SYNTAX_SUBQUERY_LIMIT)
|
|
6025
|
+
]);
|
|
6026
|
+
const mergedResults = [
|
|
6027
|
+
...docsResult.results || [],
|
|
6028
|
+
...codeResult.results || []
|
|
6326
6029
|
];
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6030
|
+
return {
|
|
6031
|
+
results: mergedResults.slice(0, SYNTAX_RESULTS_LIMIT),
|
|
6032
|
+
totalResults: mergedResults.length,
|
|
6033
|
+
query: topic,
|
|
6034
|
+
lastIndexed: docsResult.lastIndexed || codeResult.lastIndexed
|
|
6035
|
+
};
|
|
6036
|
+
} catch (error) {
|
|
6037
|
+
logger.warn(`Failed to search Compact syntax for topic ${topic}`, {
|
|
6038
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6039
|
+
});
|
|
6040
|
+
return null;
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
async function validateADTOperations(adtName, staticOperations) {
|
|
6044
|
+
const discrepancies = [];
|
|
6045
|
+
const enrichments = [];
|
|
6046
|
+
const indexedInfo = await searchADTInfo(adtName);
|
|
6047
|
+
if (!indexedInfo) {
|
|
6048
|
+
return {
|
|
6049
|
+
validated: false,
|
|
6050
|
+
operations: staticOperations.map((op) => ({
|
|
6051
|
+
method: op.method,
|
|
6052
|
+
description: op.note,
|
|
6053
|
+
worksInCircuits: op.works,
|
|
6054
|
+
source: "static-fallback"
|
|
6055
|
+
})),
|
|
6056
|
+
discrepancies: [
|
|
6057
|
+
"Could not validate against indexed docs - using static data"
|
|
6058
|
+
],
|
|
6059
|
+
enrichments: []
|
|
6060
|
+
};
|
|
6061
|
+
}
|
|
6062
|
+
const operations = [];
|
|
6063
|
+
const indexedMethodNames = new Set(
|
|
6064
|
+
indexedInfo.operations.map((op) => op.method.toLowerCase())
|
|
6065
|
+
);
|
|
6066
|
+
const staticMethodNames = new Set(
|
|
6067
|
+
staticOperations.map(
|
|
6068
|
+
(op) => op.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
6069
|
+
)
|
|
6070
|
+
);
|
|
6071
|
+
for (const indexedOp of indexedInfo.operations) {
|
|
6072
|
+
operations.push(indexedOp);
|
|
6073
|
+
const staticOp = staticOperations.find(
|
|
6074
|
+
(s) => s.method.toLowerCase().includes(indexedOp.method.toLowerCase()) || indexedOp.method.toLowerCase().includes(
|
|
6075
|
+
s.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase()
|
|
6076
|
+
)
|
|
6077
|
+
);
|
|
6078
|
+
if (staticOp && !staticOp.works && indexedOp.worksInCircuits) {
|
|
6079
|
+
discrepancies.push(
|
|
6080
|
+
`Static said ${staticOp.method} doesn't work, but indexed docs show it does`
|
|
6332
6081
|
);
|
|
6333
6082
|
}
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
logger.info(`Unsubscribing from resource: ${uri}`);
|
|
6341
|
-
resourceSubscriptions.delete(uri);
|
|
6342
|
-
logger.debug(`Active subscriptions: ${resourceSubscriptions.size}`);
|
|
6343
|
-
return {};
|
|
6344
|
-
});
|
|
6345
|
-
}
|
|
6346
|
-
function setupSampling(server) {
|
|
6347
|
-
const samplingCallback2 = async (request) => {
|
|
6348
|
-
logger.debug("Requesting sampling from client", {
|
|
6349
|
-
messageCount: request.messages.length,
|
|
6350
|
-
maxTokens: request.maxTokens
|
|
6351
|
-
});
|
|
6352
|
-
try {
|
|
6353
|
-
const response = await server.request(
|
|
6354
|
-
{
|
|
6355
|
-
method: "sampling/createMessage",
|
|
6356
|
-
params: {
|
|
6357
|
-
messages: request.messages,
|
|
6358
|
-
systemPrompt: request.systemPrompt,
|
|
6359
|
-
maxTokens: request.maxTokens || 2048,
|
|
6360
|
-
temperature: request.temperature,
|
|
6361
|
-
modelPreferences: request.modelPreferences
|
|
6362
|
-
}
|
|
6363
|
-
},
|
|
6364
|
-
// Use a schema that matches the expected response
|
|
6365
|
-
{
|
|
6366
|
-
parse: (data) => {
|
|
6367
|
-
const response2 = data;
|
|
6368
|
-
if (!response2 || typeof response2 !== "object") {
|
|
6369
|
-
throw new Error("Invalid sampling response: expected object");
|
|
6370
|
-
}
|
|
6371
|
-
if (!response2.content || typeof response2.content !== "object") {
|
|
6372
|
-
throw new Error("Invalid sampling response: missing content");
|
|
6373
|
-
}
|
|
6374
|
-
return response2;
|
|
6375
|
-
},
|
|
6376
|
-
_def: { typeName: "SamplingResponse" }
|
|
6377
|
-
}
|
|
6083
|
+
}
|
|
6084
|
+
for (const staticOp of staticOperations) {
|
|
6085
|
+
const methodName = staticOp.method.replace(/^\./, "").replace(/\(.*$/, "").toLowerCase();
|
|
6086
|
+
if (!indexedMethodNames.has(methodName)) {
|
|
6087
|
+
discrepancies.push(
|
|
6088
|
+
`Static references ${staticOp.method} but not found in indexed docs - may not exist`
|
|
6378
6089
|
);
|
|
6379
|
-
return response;
|
|
6380
|
-
} catch (error) {
|
|
6381
|
-
logger.error("Sampling request failed", { error: String(error) });
|
|
6382
|
-
throw error;
|
|
6383
6090
|
}
|
|
6384
|
-
};
|
|
6385
|
-
registerSamplingCallback(samplingCallback2);
|
|
6386
|
-
logger.info("Sampling capability configured");
|
|
6387
|
-
}
|
|
6388
|
-
async function initializeServer() {
|
|
6389
|
-
logger.info("Initializing Midnight MCP Server...");
|
|
6390
|
-
checkForUpdates().catch((error) => {
|
|
6391
|
-
logger.debug("Startup version check failed (non-blocking)", {
|
|
6392
|
-
error: error instanceof Error ? error.message : String(error)
|
|
6393
|
-
});
|
|
6394
|
-
});
|
|
6395
|
-
try {
|
|
6396
|
-
await vectorStore.initialize();
|
|
6397
|
-
logger.info("Vector store initialized");
|
|
6398
|
-
} catch (error) {
|
|
6399
|
-
logger.warn("Vector store initialization failed, continuing without it", {
|
|
6400
|
-
error: String(error)
|
|
6401
|
-
});
|
|
6402
6091
|
}
|
|
6403
|
-
const
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6412
|
-
|
|
6092
|
+
for (const indexedOp of indexedInfo.operations) {
|
|
6093
|
+
const methodName = indexedOp.method.toLowerCase();
|
|
6094
|
+
if (!staticMethodNames.has(methodName)) {
|
|
6095
|
+
enrichments.push(
|
|
6096
|
+
`Indexed docs show ${indexedOp.method} exists but was missing from static reference`
|
|
6097
|
+
);
|
|
6098
|
+
}
|
|
6099
|
+
}
|
|
6100
|
+
return {
|
|
6101
|
+
validated: true,
|
|
6102
|
+
operations,
|
|
6103
|
+
discrepancies,
|
|
6104
|
+
enrichments
|
|
6105
|
+
};
|
|
6413
6106
|
}
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6418
|
-
async
|
|
6419
|
-
|
|
6420
|
-
|
|
6107
|
+
async function validateBuiltinFunctions(staticBuiltins) {
|
|
6108
|
+
const discrepancies = [];
|
|
6109
|
+
const enrichments = [];
|
|
6110
|
+
const results = await Promise.all(
|
|
6111
|
+
staticBuiltins.map(async (builtin) => {
|
|
6112
|
+
const searchResult = await searchCompactSyntax(
|
|
6113
|
+
`${builtin.name} function builtin`
|
|
6114
|
+
);
|
|
6115
|
+
return { builtin, found: (searchResult?.results?.length || 0) > 0 };
|
|
6421
6116
|
})
|
|
6422
6117
|
);
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
|
|
6426
|
-
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
sessionIdGenerator: () => randomUUID(),
|
|
6444
|
-
onsessioninitialized: (newSessionId) => {
|
|
6445
|
-
transports.streamable[newSessionId] = transport;
|
|
6446
|
-
logger.debug(`New streamable session: ${newSessionId}`);
|
|
6447
|
-
}
|
|
6448
|
-
});
|
|
6449
|
-
transport.onclose = () => {
|
|
6450
|
-
if (transport.sessionId) {
|
|
6451
|
-
delete transports.streamable[transport.sessionId];
|
|
6452
|
-
logger.debug(`Streamable session closed: ${transport.sessionId}`);
|
|
6118
|
+
for (const { builtin, found } of results) {
|
|
6119
|
+
if (!found) {
|
|
6120
|
+
discrepancies.push(
|
|
6121
|
+
`Builtin "${builtin.name}" not found in indexed docs - may not exist or have different name`
|
|
6122
|
+
);
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
const allBuiltinsSearch = await searchCompactSyntax(
|
|
6126
|
+
"builtin function stdlib standard library"
|
|
6127
|
+
);
|
|
6128
|
+
if (allBuiltinsSearch?.results) {
|
|
6129
|
+
const knownNames = new Set(staticBuiltins.map((b) => b.name.toLowerCase()));
|
|
6130
|
+
for (const result of allBuiltinsSearch.results) {
|
|
6131
|
+
const content = result.content || result.code || "";
|
|
6132
|
+
const funcPattern = /\b(export\s+)?function\s+(\w+)/gi;
|
|
6133
|
+
let match;
|
|
6134
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
6135
|
+
const funcName = match[2].toLowerCase();
|
|
6136
|
+
if (!knownNames.has(funcName) && funcName.length > 2) {
|
|
6137
|
+
enrichments.push(`Indexed docs mention "${match[2]}" function`);
|
|
6453
6138
|
}
|
|
6454
|
-
};
|
|
6455
|
-
try {
|
|
6456
|
-
await mcpServer.connect(transport);
|
|
6457
|
-
} catch (error) {
|
|
6458
|
-
logger.error("Failed to connect streamable transport", {
|
|
6459
|
-
error: String(error)
|
|
6460
|
-
});
|
|
6461
|
-
res.status(500).json({
|
|
6462
|
-
jsonrpc: "2.0",
|
|
6463
|
-
error: { code: -32603, message: "Internal error: connection failed" },
|
|
6464
|
-
id: null
|
|
6465
|
-
});
|
|
6466
|
-
return;
|
|
6467
6139
|
}
|
|
6468
|
-
} else {
|
|
6469
|
-
res.status(400).json({
|
|
6470
|
-
jsonrpc: "2.0",
|
|
6471
|
-
error: {
|
|
6472
|
-
code: -32e3,
|
|
6473
|
-
message: "Bad Request: No valid session ID provided"
|
|
6474
|
-
},
|
|
6475
|
-
id: null
|
|
6476
|
-
});
|
|
6477
|
-
return;
|
|
6478
|
-
}
|
|
6479
|
-
await transport.handleRequest(req, res, req.body);
|
|
6480
|
-
});
|
|
6481
|
-
app.get("/sse", async (_req, res) => {
|
|
6482
|
-
logger.debug("New SSE connection");
|
|
6483
|
-
const transport = new SSEServerTransport("/messages", res);
|
|
6484
|
-
transports.sse[transport.sessionId] = transport;
|
|
6485
|
-
res.on("close", () => {
|
|
6486
|
-
delete transports.sse[transport.sessionId];
|
|
6487
|
-
logger.debug(`SSE session closed: ${transport.sessionId}`);
|
|
6488
|
-
});
|
|
6489
|
-
try {
|
|
6490
|
-
await mcpServer.connect(transport);
|
|
6491
|
-
} catch (error) {
|
|
6492
|
-
delete transports.sse[transport.sessionId];
|
|
6493
|
-
logger.error(`SSE connection failed: ${transport.sessionId}`, {
|
|
6494
|
-
error: String(error)
|
|
6495
|
-
});
|
|
6496
|
-
res.status(500).end();
|
|
6497
|
-
}
|
|
6498
|
-
});
|
|
6499
|
-
app.post("/messages", async (req, res) => {
|
|
6500
|
-
const sessionId = req.query.sessionId;
|
|
6501
|
-
const transport = transports.sse[sessionId];
|
|
6502
|
-
if (transport) {
|
|
6503
|
-
await transport.handlePostMessage(req, res);
|
|
6504
|
-
} else {
|
|
6505
|
-
res.status(400).send(`No transport found for sessionId ${sessionId}`);
|
|
6506
6140
|
}
|
|
6507
|
-
}
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6512
|
-
|
|
6141
|
+
}
|
|
6142
|
+
return {
|
|
6143
|
+
dataType: "BUILTIN_FUNCTIONS",
|
|
6144
|
+
validated: discrepancies.length === 0,
|
|
6145
|
+
discrepancies,
|
|
6146
|
+
enrichments: [...new Set(enrichments)].slice(0, 10),
|
|
6147
|
+
deprecatedPatterns: [],
|
|
6148
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6149
|
+
};
|
|
6150
|
+
}
|
|
6151
|
+
async function validateTypeCompatibility(staticRules) {
|
|
6152
|
+
const discrepancies = [];
|
|
6153
|
+
const enrichments = [];
|
|
6154
|
+
const typeSearch = await searchCompactSyntax(
|
|
6155
|
+
"type cast Field Uint Bytes conversion compatible"
|
|
6156
|
+
);
|
|
6157
|
+
if (typeSearch?.results) {
|
|
6158
|
+
for (const result of typeSearch.results) {
|
|
6159
|
+
const content = (result.content || result.code || "").toLowerCase();
|
|
6160
|
+
for (const rule of staticRules) {
|
|
6161
|
+
const typeMatch = rule.types.match(
|
|
6162
|
+
/^\s*(.+?)\s*(?:==|=|<=|>=|<|>|[+*])\s*(.+?)\s*$/
|
|
6163
|
+
);
|
|
6164
|
+
const typeA = typeMatch?.[1]?.trim().toLowerCase();
|
|
6165
|
+
const typeB = typeMatch?.[2]?.trim().toLowerCase();
|
|
6166
|
+
if (typeA && typeB) {
|
|
6167
|
+
if (content.includes(typeA) && content.includes(typeB) && content.includes("compatible")) {
|
|
6168
|
+
if (!rule.works) {
|
|
6169
|
+
discrepancies.push(
|
|
6170
|
+
`Static says "${rule.types}" doesn't work, but docs suggest compatibility`
|
|
6171
|
+
);
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
}
|
|
6513
6176
|
}
|
|
6514
|
-
|
|
6515
|
-
|
|
6177
|
+
}
|
|
6178
|
+
return {
|
|
6179
|
+
dataType: "TYPE_COMPATIBILITY",
|
|
6180
|
+
validated: discrepancies.length === 0,
|
|
6181
|
+
discrepancies,
|
|
6182
|
+
enrichments,
|
|
6183
|
+
deprecatedPatterns: [],
|
|
6184
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6516
6185
|
};
|
|
6517
|
-
|
|
6518
|
-
|
|
6519
|
-
const
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6186
|
+
}
|
|
6187
|
+
async function validateCommonErrors(staticErrors) {
|
|
6188
|
+
const discrepancies = [];
|
|
6189
|
+
const enrichments = [];
|
|
6190
|
+
const errorSearch = await searchDocsHosted(
|
|
6191
|
+
"error compile compilation failure parse",
|
|
6192
|
+
ADT_SEARCH_LIMIT,
|
|
6193
|
+
"all"
|
|
6194
|
+
);
|
|
6195
|
+
if (errorSearch?.results) {
|
|
6196
|
+
const knownErrors = new Set(
|
|
6197
|
+
staticErrors.map((e) => e.error.toLowerCase().slice(0, 30))
|
|
6198
|
+
);
|
|
6199
|
+
for (const result of errorSearch.results) {
|
|
6200
|
+
const content = result.content || result.code || "";
|
|
6201
|
+
const errorPattern = /(?:error|Error):\s*["']?([^"'\n]+)["']?/g;
|
|
6202
|
+
let match;
|
|
6203
|
+
while ((match = errorPattern.exec(content)) !== null) {
|
|
6204
|
+
const errorMsg = match[1].toLowerCase().slice(0, 30);
|
|
6205
|
+
if (!knownErrors.has(errorMsg) && errorMsg.length > 10) {
|
|
6206
|
+
enrichments.push(`Docs mention error: "${match[1].slice(0, 50)}..."`);
|
|
6207
|
+
}
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
}
|
|
6211
|
+
return {
|
|
6212
|
+
dataType: "COMMON_ERRORS",
|
|
6213
|
+
validated: true,
|
|
6214
|
+
// Errors are informational, hard to validate
|
|
6215
|
+
discrepancies,
|
|
6216
|
+
enrichments: [...new Set(enrichments)].slice(0, 10),
|
|
6217
|
+
deprecatedPatterns: [],
|
|
6218
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6533
6219
|
};
|
|
6534
|
-
process.on("SIGINT", shutdown);
|
|
6535
|
-
process.on("SIGTERM", shutdown);
|
|
6536
6220
|
}
|
|
6537
|
-
|
|
6538
|
-
// src/config/compact-version.ts
|
|
6539
|
-
var COMPACT_VERSION = {
|
|
6540
|
-
/** Minimum supported version */
|
|
6541
|
-
min: "0.16",
|
|
6542
|
-
/** Maximum supported version */
|
|
6543
|
-
max: "0.21",
|
|
6544
|
-
/** When this config was last updated */
|
|
6545
|
-
lastUpdated: "2026-03-24",
|
|
6546
|
-
/** Source of truth for syntax patterns */
|
|
6547
|
-
referenceSource: "https://github.com/piotr-iohk/template-contract"
|
|
6548
|
-
};
|
|
6549
|
-
var RECOMMENDED_PRAGMA = `pragma language_version >= ${COMPACT_VERSION.min} && <= ${COMPACT_VERSION.max};`;
|
|
6550
|
-
var REFERENCE_CONTRACTS = [
|
|
6221
|
+
var DEPRECATED_SYNTAX_PATTERNS = [
|
|
6551
6222
|
{
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6223
|
+
pattern: /ledger\s*\{/,
|
|
6224
|
+
name: "ledger-block",
|
|
6225
|
+
message: "Deprecated: Use 'export ledger field: Type;' instead of ledger { } block",
|
|
6226
|
+
since: "0.16"
|
|
6555
6227
|
},
|
|
6556
6228
|
{
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6229
|
+
pattern: /Cell\s*<\s*\w+\s*>/,
|
|
6230
|
+
name: "cell-wrapper",
|
|
6231
|
+
message: "Deprecated: Cell<T> wrapper removed, use Type directly",
|
|
6232
|
+
since: "0.15"
|
|
6560
6233
|
},
|
|
6561
6234
|
{
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6235
|
+
pattern: /:\s*Void\b/,
|
|
6236
|
+
name: "void-type",
|
|
6237
|
+
message: "Void type doesn't exist, use [] (empty tuple) for no return",
|
|
6238
|
+
since: "always"
|
|
6565
6239
|
},
|
|
6566
6240
|
{
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6241
|
+
pattern: /\.\s*value\s*\(\s*\)/,
|
|
6242
|
+
name: "counter-value",
|
|
6243
|
+
message: "Counter.value() doesn't exist, use Counter.read() instead",
|
|
6244
|
+
since: "always"
|
|
6245
|
+
},
|
|
6246
|
+
{
|
|
6247
|
+
pattern: /::\w+/,
|
|
6248
|
+
name: "rust-enum-syntax",
|
|
6249
|
+
message: "Rust-style :: enum access doesn't work, use dot notation (Choice.rock)",
|
|
6250
|
+
since: "always"
|
|
6251
|
+
},
|
|
6252
|
+
{
|
|
6253
|
+
pattern: /pure\s+function\b/,
|
|
6254
|
+
name: "pure-function",
|
|
6255
|
+
message: "Use 'pure circuit' not 'pure function' for helper functions",
|
|
6256
|
+
since: "0.16"
|
|
6257
|
+
},
|
|
6258
|
+
{
|
|
6259
|
+
pattern: /witness\s+\w+\s*\([^)]*\)\s*:\s*\w+\s*\{/,
|
|
6260
|
+
name: "witness-with-body",
|
|
6261
|
+
message: "Witnesses are declarations only - no body allowed. Implementation goes in TypeScript prover.",
|
|
6262
|
+
since: "always"
|
|
6570
6263
|
}
|
|
6571
6264
|
];
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
description: "Creates a hiding commitment to a value"
|
|
6585
|
-
},
|
|
6586
|
-
{
|
|
6587
|
-
name: "pad",
|
|
6588
|
-
signature: "pad(length: number, value: string): Bytes<N>",
|
|
6589
|
-
description: "Pads a string to fixed-length bytes"
|
|
6590
|
-
},
|
|
6591
|
-
{
|
|
6592
|
-
name: "disclose",
|
|
6593
|
-
signature: "disclose(value: T): T",
|
|
6594
|
-
description: "Explicitly reveals a witness value (required in conditionals)"
|
|
6595
|
-
},
|
|
6596
|
-
{
|
|
6597
|
-
name: "assert",
|
|
6598
|
-
signature: "assert(condition: Boolean, message?: string): []",
|
|
6599
|
-
description: "Fails circuit if condition is false"
|
|
6600
|
-
},
|
|
6601
|
-
{
|
|
6602
|
-
name: "default",
|
|
6603
|
-
signature: "default<T>(): T",
|
|
6604
|
-
description: "Returns default value for a type (0 for numbers, empty for collections)"
|
|
6605
|
-
}
|
|
6606
|
-
],
|
|
6607
|
-
/** NOT built-in - you must implement these patterns yourself */
|
|
6608
|
-
notBuiltIn: [
|
|
6609
|
-
{
|
|
6610
|
-
name: "public_key",
|
|
6611
|
-
wrongUsage: "public_key(sk) // ERROR: unbound identifier",
|
|
6612
|
-
correctPattern: `// Derive public key using persistentHash
|
|
6613
|
-
const pk = persistentHash<Vector<2, Bytes<32>>>([
|
|
6614
|
-
pad(32, "midnight:pk:"),
|
|
6615
|
-
sk
|
|
6616
|
-
]);`,
|
|
6617
|
-
description: "Public key derivation is NOT a builtin - use persistentHash pattern"
|
|
6618
|
-
},
|
|
6619
|
-
{
|
|
6620
|
-
name: "verify_signature",
|
|
6621
|
-
wrongUsage: "verify_signature(msg, sig, pk) // Does not exist",
|
|
6622
|
-
correctPattern: `// Signature verification must be done via witnesses
|
|
6623
|
-
// The prover verifies off-chain, then provides the boolean result
|
|
6624
|
-
witness signature_valid(): Boolean;`,
|
|
6625
|
-
description: "Signature verification is done off-chain in the prover"
|
|
6626
|
-
},
|
|
6627
|
-
{
|
|
6628
|
-
name: "random",
|
|
6629
|
-
wrongUsage: "random() // Does not exist in ZK circuits",
|
|
6630
|
-
correctPattern: `// Randomness must come from witnesses (prover-provided)
|
|
6631
|
-
witness get_random_value(): Field;`,
|
|
6632
|
-
description: "ZK circuits are deterministic - randomness must come from witnesses"
|
|
6633
|
-
}
|
|
6634
|
-
]
|
|
6635
|
-
};
|
|
6636
|
-
var TYPE_COMPATIBILITY = {
|
|
6637
|
-
// Note: Comparison operators work in practice but aren't explicitly listed in docs
|
|
6638
|
-
comparisons: [
|
|
6639
|
-
{ types: "Field == Field", works: true, note: "Direct comparison" },
|
|
6640
|
-
{
|
|
6641
|
-
types: "Field == Uint<N>",
|
|
6642
|
-
works: false,
|
|
6643
|
-
fix: "Cast with `value as Field`"
|
|
6644
|
-
},
|
|
6645
|
-
{
|
|
6646
|
-
types: "Field >= 0",
|
|
6647
|
-
works: false,
|
|
6648
|
-
fix: "Use bounded Uint<0..N> parameter instead"
|
|
6649
|
-
},
|
|
6650
|
-
{ types: "Uint<N> == Uint<N>", works: true, note: "Same-width comparison" },
|
|
6651
|
-
{
|
|
6652
|
-
types: "Uint<0..2> == Uint<0..2>",
|
|
6653
|
-
works: true,
|
|
6654
|
-
note: "Bounded integers"
|
|
6655
|
-
},
|
|
6656
|
-
{
|
|
6657
|
-
types: "Bytes<32> == Bytes<32>",
|
|
6658
|
-
works: true,
|
|
6659
|
-
note: "Used in examples, but operators not explicitly listed in docs"
|
|
6660
|
-
},
|
|
6661
|
-
{ types: "Boolean == Boolean", works: true, note: "Direct comparison" }
|
|
6662
|
-
],
|
|
6663
|
-
arithmetic: [
|
|
6664
|
-
{ types: "Field + Field", works: true, note: "Field arithmetic" },
|
|
6665
|
-
{ types: "Field + Uint<N>", works: false, fix: "Cast Uint to Field first" },
|
|
6666
|
-
{
|
|
6667
|
-
types: "Uint<N> + Uint<N>",
|
|
6668
|
-
works: true,
|
|
6669
|
-
note: "Result is bounded type, cast back: (a + b) as Uint<64>"
|
|
6670
|
-
},
|
|
6671
|
-
{
|
|
6672
|
-
types: "Uint<64> + Uint<64>",
|
|
6673
|
-
works: true,
|
|
6674
|
-
note: "Result is Uint<0..36893488147419103230>, must cast: (a + b) as Uint<64>"
|
|
6675
|
-
},
|
|
6676
|
-
{
|
|
6677
|
-
types: "Uint<64> * Uint<64>",
|
|
6678
|
-
works: true,
|
|
6679
|
-
note: "Result is wide bounded type, cast back to target type"
|
|
6680
|
-
}
|
|
6681
|
-
],
|
|
6682
|
-
/**
|
|
6683
|
-
* Type casting rules - based on official cast table
|
|
6684
|
-
* See: https://docs.midnight.network/develop/reference/compact/lang-ref#type-cast-expressions
|
|
6685
|
-
*
|
|
6686
|
-
* Cast kinds: static (always succeeds), conversion (semantic change), checked (can fail)
|
|
6687
|
-
*/
|
|
6688
|
-
typeCasting: [
|
|
6689
|
-
{
|
|
6690
|
-
from: "Uint<N>",
|
|
6691
|
-
to: "Field",
|
|
6692
|
-
direct: true,
|
|
6693
|
-
kind: "static",
|
|
6694
|
-
note: "Always succeeds"
|
|
6695
|
-
},
|
|
6696
|
-
{
|
|
6697
|
-
from: "Field",
|
|
6698
|
-
to: "Uint<0..n>",
|
|
6699
|
-
direct: true,
|
|
6700
|
-
kind: "checked",
|
|
6701
|
-
note: "Fails at runtime if value > n"
|
|
6702
|
-
},
|
|
6703
|
-
{
|
|
6704
|
-
from: "Field",
|
|
6705
|
-
to: "Bytes<n>",
|
|
6706
|
-
direct: true,
|
|
6707
|
-
kind: "conversion",
|
|
6708
|
-
note: "Can fail at runtime if value doesn't fit (little-endian)"
|
|
6709
|
-
},
|
|
6710
|
-
{
|
|
6711
|
-
from: "Bytes<m>",
|
|
6712
|
-
to: "Field",
|
|
6713
|
-
direct: true,
|
|
6714
|
-
kind: "conversion",
|
|
6715
|
-
note: "Can fail at runtime if result exceeds max Field value"
|
|
6716
|
-
},
|
|
6717
|
-
{
|
|
6718
|
-
from: "Boolean",
|
|
6719
|
-
to: "Uint<0..n>",
|
|
6720
|
-
direct: true,
|
|
6721
|
-
kind: "conversion",
|
|
6722
|
-
note: "false\u21920, true\u21921 (n must not be 0)"
|
|
6723
|
-
},
|
|
6724
|
-
{
|
|
6725
|
-
from: "Boolean",
|
|
6726
|
-
to: "Field",
|
|
6727
|
-
direct: false,
|
|
6728
|
-
fix: "Go through Uint: (flag as Uint<0..1>) as Field"
|
|
6729
|
-
},
|
|
6730
|
-
{
|
|
6731
|
-
from: "Uint<N>",
|
|
6732
|
-
to: "Bytes<M>",
|
|
6733
|
-
direct: false,
|
|
6734
|
-
fix: "Go through Field: (amount as Field) as Bytes<32>"
|
|
6735
|
-
},
|
|
6736
|
-
{
|
|
6737
|
-
from: "enum",
|
|
6738
|
-
to: "Field",
|
|
6739
|
-
direct: true,
|
|
6740
|
-
kind: "conversion",
|
|
6741
|
-
note: "Enum variant index as Field"
|
|
6742
|
-
},
|
|
6743
|
-
{
|
|
6744
|
-
from: "Uint<0..m>",
|
|
6745
|
-
to: "Uint<0..n>",
|
|
6746
|
-
direct: true,
|
|
6747
|
-
kind: "static if m\u2264n, checked if m>n",
|
|
6748
|
-
note: "Widening is static, narrowing is checked"
|
|
6749
|
-
},
|
|
6750
|
-
{
|
|
6751
|
-
from: "arithmetic result",
|
|
6752
|
-
to: "Uint<64>",
|
|
6753
|
-
direct: true,
|
|
6754
|
-
kind: "checked",
|
|
6755
|
-
note: "Required cast: (a + b) as Uint<64>"
|
|
6265
|
+
function scanForDeprecatedPatterns(code) {
|
|
6266
|
+
const issues = [];
|
|
6267
|
+
const lines = code.split("\n");
|
|
6268
|
+
for (const { pattern, name, message } of DEPRECATED_SYNTAX_PATTERNS) {
|
|
6269
|
+
for (let i = 0; i < lines.length; i++) {
|
|
6270
|
+
if (pattern.test(lines[i])) {
|
|
6271
|
+
issues.push({
|
|
6272
|
+
pattern: name,
|
|
6273
|
+
message,
|
|
6274
|
+
lineNumber: i + 1
|
|
6275
|
+
});
|
|
6276
|
+
}
|
|
6756
6277
|
}
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6278
|
+
}
|
|
6279
|
+
return issues;
|
|
6280
|
+
}
|
|
6281
|
+
async function validateAllStaticData(staticData) {
|
|
6282
|
+
const results = {};
|
|
6283
|
+
const [builtinResult, typeResult, errorResult, adtResults] = await Promise.all([
|
|
6284
|
+
staticData.builtinFunctions ? validateBuiltinFunctions(staticData.builtinFunctions) : Promise.resolve(null),
|
|
6285
|
+
staticData.typeCompatibility ? validateTypeCompatibility(staticData.typeCompatibility) : Promise.resolve(null),
|
|
6286
|
+
staticData.commonErrors ? validateCommonErrors(staticData.commonErrors) : Promise.resolve(null),
|
|
6287
|
+
staticData.ledgerTypeLimits ? Promise.all(
|
|
6288
|
+
Object.entries(staticData.ledgerTypeLimits).map(
|
|
6289
|
+
async ([name, data]) => ({
|
|
6290
|
+
name,
|
|
6291
|
+
result: await validateADTOperations(
|
|
6292
|
+
name,
|
|
6293
|
+
data.circuitOperations
|
|
6294
|
+
)
|
|
6295
|
+
})
|
|
6296
|
+
)
|
|
6297
|
+
) : Promise.resolve([])
|
|
6298
|
+
]);
|
|
6299
|
+
if (builtinResult) results.builtinFunctions = builtinResult;
|
|
6300
|
+
if (typeResult) results.typeCompatibility = typeResult;
|
|
6301
|
+
if (errorResult) results.commonErrors = errorResult;
|
|
6302
|
+
for (const { name, result } of adtResults) {
|
|
6303
|
+
results[`adt_${name}`] = {
|
|
6304
|
+
dataType: `LEDGER_TYPE_LIMITS.${name}`,
|
|
6305
|
+
validated: result.validated,
|
|
6306
|
+
discrepancies: result.discrepancies,
|
|
6307
|
+
enrichments: result.enrichments,
|
|
6308
|
+
deprecatedPatterns: [],
|
|
6309
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6310
|
+
};
|
|
6311
|
+
}
|
|
6312
|
+
const allDiscrepancies = Object.values(results).flatMap(
|
|
6313
|
+
(r) => r.discrepancies
|
|
6314
|
+
);
|
|
6315
|
+
const allEnrichments = Object.values(results).flatMap((r) => r.enrichments);
|
|
6316
|
+
return {
|
|
6317
|
+
overall: {
|
|
6318
|
+
validated: allDiscrepancies.length === 0,
|
|
6319
|
+
totalDiscrepancies: allDiscrepancies.length,
|
|
6320
|
+
totalEnrichments: allEnrichments.length
|
|
6763
6321
|
},
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
"Casting with `as Field` is safe but loses range information"
|
|
6775
|
-
]
|
|
6322
|
+
results,
|
|
6323
|
+
lastValidated: (/* @__PURE__ */ new Date()).toISOString()
|
|
6324
|
+
};
|
|
6325
|
+
}
|
|
6326
|
+
|
|
6327
|
+
// src/server.ts
|
|
6328
|
+
var SERVER_INFO = {
|
|
6329
|
+
name: "midnight-mcp",
|
|
6330
|
+
version: CURRENT_VERSION,
|
|
6331
|
+
description: "MCP Server for Midnight Blockchain Development"
|
|
6776
6332
|
};
|
|
6777
|
-
var
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6333
|
+
var versionCheckResult = {
|
|
6334
|
+
isOutdated: false,
|
|
6335
|
+
latestVersion: CURRENT_VERSION,
|
|
6336
|
+
updateMessage: null,
|
|
6337
|
+
lastChecked: 0
|
|
6338
|
+
};
|
|
6339
|
+
var toolCallCount = 0;
|
|
6340
|
+
var VERSION_CHECK_INTERVAL = 10;
|
|
6341
|
+
async function checkForUpdates() {
|
|
6342
|
+
try {
|
|
6343
|
+
const controller = new AbortController();
|
|
6344
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
6345
|
+
const response = await fetch(
|
|
6346
|
+
"https://registry.npmjs.org/midnight-mcp/latest",
|
|
6347
|
+
{ signal: controller.signal }
|
|
6348
|
+
);
|
|
6349
|
+
clearTimeout(timeoutId);
|
|
6350
|
+
if (!response.ok) return;
|
|
6351
|
+
const data = await response.json();
|
|
6352
|
+
const latestVersion = data.version;
|
|
6353
|
+
if (latestVersion !== CURRENT_VERSION) {
|
|
6354
|
+
versionCheckResult = {
|
|
6355
|
+
isOutdated: true,
|
|
6356
|
+
latestVersion,
|
|
6357
|
+
lastChecked: Date.now(),
|
|
6358
|
+
updateMessage: `\u{1F6A8} UPDATE AVAILABLE: v${latestVersion} (you have v${CURRENT_VERSION})`
|
|
6359
|
+
};
|
|
6360
|
+
logger.warn(
|
|
6361
|
+
`Outdated version detected: v${CURRENT_VERSION} -> v${latestVersion}`
|
|
6362
|
+
);
|
|
6363
|
+
} else {
|
|
6364
|
+
versionCheckResult = {
|
|
6365
|
+
...versionCheckResult,
|
|
6366
|
+
lastChecked: Date.now()
|
|
6367
|
+
};
|
|
6368
|
+
}
|
|
6369
|
+
} catch (error) {
|
|
6370
|
+
logger.debug("Version check failed (non-blocking)", {
|
|
6371
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6372
|
+
});
|
|
6373
|
+
}
|
|
6374
|
+
}
|
|
6375
|
+
function maybeCheckForUpdates() {
|
|
6376
|
+
toolCallCount++;
|
|
6377
|
+
if (toolCallCount >= VERSION_CHECK_INTERVAL) {
|
|
6378
|
+
toolCallCount = 0;
|
|
6379
|
+
const fiveMinutes = 5 * 60 * 1e3;
|
|
6380
|
+
if (Date.now() - versionCheckResult.lastChecked > fiveMinutes) {
|
|
6381
|
+
checkForUpdates().catch((error) => {
|
|
6382
|
+
logger.debug("Periodic version check failed", {
|
|
6383
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6384
|
+
});
|
|
6385
|
+
});
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
}
|
|
6389
|
+
function getUpdateWarning() {
|
|
6390
|
+
return versionCheckResult.updateMessage;
|
|
6391
|
+
}
|
|
6392
|
+
var resourceSubscriptions = /* @__PURE__ */ new Set();
|
|
6393
|
+
var resourceTemplates = [
|
|
6394
|
+
{
|
|
6395
|
+
uriTemplate: "midnight://code/{owner}/{repo}/{path}",
|
|
6396
|
+
name: "Repository Code",
|
|
6397
|
+
title: "\u{1F4C4} Repository Code Files",
|
|
6398
|
+
description: "Access code files from any Midnight repository by specifying owner, repo, and file path",
|
|
6399
|
+
mimeType: "text/plain"
|
|
6804
6400
|
},
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6401
|
+
{
|
|
6402
|
+
uriTemplate: "midnight://docs/{section}/{topic}",
|
|
6403
|
+
name: "Documentation",
|
|
6404
|
+
title: "\u{1F4DA} Documentation Sections",
|
|
6405
|
+
description: "Access documentation by section (guides, api, concepts) and topic",
|
|
6406
|
+
mimeType: "text/markdown"
|
|
6407
|
+
},
|
|
6408
|
+
{
|
|
6409
|
+
uriTemplate: "midnight://examples/{category}/{name}",
|
|
6410
|
+
name: "Example Contracts",
|
|
6411
|
+
title: "\u{1F4DD} Example Contracts",
|
|
6412
|
+
description: "Access example contracts by category (counter, bboard, token, voting) and name",
|
|
6413
|
+
mimeType: "text/x-compact"
|
|
6414
|
+
},
|
|
6415
|
+
{
|
|
6416
|
+
uriTemplate: "midnight://schema/{type}",
|
|
6417
|
+
name: "Schema Definitions",
|
|
6418
|
+
title: "\u{1F527} Schema Definitions",
|
|
6419
|
+
description: "Access JSON schemas for contract AST, transactions, and proofs",
|
|
6420
|
+
mimeType: "application/json"
|
|
6421
|
+
}
|
|
6422
|
+
];
|
|
6423
|
+
var mcpLogLevel = "info";
|
|
6424
|
+
var serverInstance = null;
|
|
6425
|
+
var isConnected = false;
|
|
6426
|
+
function setServerConnected(connected) {
|
|
6427
|
+
isConnected = connected;
|
|
6428
|
+
}
|
|
6429
|
+
function sendLogToClient(level, loggerName, data) {
|
|
6430
|
+
if (!serverInstance || !isConnected) return;
|
|
6431
|
+
const levelValues = {
|
|
6432
|
+
debug: 0,
|
|
6433
|
+
info: 1,
|
|
6434
|
+
notice: 2,
|
|
6435
|
+
warning: 3,
|
|
6436
|
+
error: 4,
|
|
6437
|
+
critical: 5,
|
|
6438
|
+
alert: 6,
|
|
6439
|
+
emergency: 7
|
|
6440
|
+
};
|
|
6441
|
+
if (levelValues[level] < levelValues[mcpLogLevel]) return;
|
|
6442
|
+
try {
|
|
6443
|
+
serverInstance.notification({
|
|
6444
|
+
method: "notifications/message",
|
|
6445
|
+
params: {
|
|
6446
|
+
level,
|
|
6447
|
+
logger: loggerName,
|
|
6448
|
+
data
|
|
6449
|
+
}
|
|
6450
|
+
});
|
|
6451
|
+
} catch (error) {
|
|
6452
|
+
logger.debug("Failed to send log notification to client", {
|
|
6453
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6454
|
+
});
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
function sendProgressNotification(progressToken, progress, total, message) {
|
|
6458
|
+
if (!serverInstance) return;
|
|
6459
|
+
try {
|
|
6460
|
+
serverInstance.notification({
|
|
6461
|
+
method: "notifications/progress",
|
|
6462
|
+
params: {
|
|
6463
|
+
progressToken,
|
|
6464
|
+
progress,
|
|
6465
|
+
...total !== void 0 && { total },
|
|
6466
|
+
...message && { message }
|
|
6467
|
+
}
|
|
6468
|
+
});
|
|
6469
|
+
} catch (error) {
|
|
6470
|
+
logger.debug("Failed to send progress notification", {
|
|
6471
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6472
|
+
});
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
function createServer() {
|
|
6476
|
+
const server = new Server(SERVER_INFO, {
|
|
6477
|
+
capabilities: {
|
|
6478
|
+
tools: {
|
|
6479
|
+
listChanged: true
|
|
6827
6480
|
},
|
|
6828
|
-
{
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
note: "Returns Boolean - checks if map is empty"
|
|
6481
|
+
resources: {
|
|
6482
|
+
subscribe: true,
|
|
6483
|
+
listChanged: true
|
|
6832
6484
|
},
|
|
6833
|
-
{
|
|
6834
|
-
|
|
6835
|
-
works: true,
|
|
6836
|
-
note: "Returns Uint<64> - number of entries"
|
|
6485
|
+
prompts: {
|
|
6486
|
+
listChanged: true
|
|
6837
6487
|
},
|
|
6838
|
-
{
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6488
|
+
logging: {},
|
|
6489
|
+
completions: {}
|
|
6490
|
+
}
|
|
6491
|
+
});
|
|
6492
|
+
serverInstance = server;
|
|
6493
|
+
setMCPLogCallback((level, loggerName, data) => {
|
|
6494
|
+
sendLogToClient(level, loggerName, data);
|
|
6495
|
+
});
|
|
6496
|
+
registerToolHandlers(server);
|
|
6497
|
+
registerResourceHandlers(server);
|
|
6498
|
+
registerPromptHandlers(server);
|
|
6499
|
+
registerSubscriptionHandlers(server);
|
|
6500
|
+
registerLoggingHandler(server);
|
|
6501
|
+
registerCompletionsHandler(server);
|
|
6502
|
+
setupSampling(server);
|
|
6503
|
+
return server;
|
|
6504
|
+
}
|
|
6505
|
+
function registerLoggingHandler(server) {
|
|
6506
|
+
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
|
|
6507
|
+
const { level } = request.params;
|
|
6508
|
+
mcpLogLevel = level;
|
|
6509
|
+
logger.info(`MCP log level set to: ${level}`);
|
|
6510
|
+
sendLogToClient("info", "midnight-mcp", {
|
|
6511
|
+
message: `Log level changed to ${level}`
|
|
6512
|
+
});
|
|
6513
|
+
return {};
|
|
6514
|
+
});
|
|
6515
|
+
}
|
|
6516
|
+
var COMPLETION_VALUES = {
|
|
6517
|
+
"midnight:create-contract": {
|
|
6518
|
+
contractType: [
|
|
6519
|
+
"token",
|
|
6520
|
+
"voting",
|
|
6521
|
+
"credential",
|
|
6522
|
+
"auction",
|
|
6523
|
+
"escrow",
|
|
6524
|
+
"custom"
|
|
6525
|
+
],
|
|
6526
|
+
privacyLevel: ["full", "partial", "public"],
|
|
6527
|
+
complexity: ["beginner", "intermediate", "advanced"]
|
|
6528
|
+
},
|
|
6529
|
+
"midnight:review-contract": {
|
|
6530
|
+
focusAreas: [
|
|
6531
|
+
"security",
|
|
6532
|
+
"performance",
|
|
6533
|
+
"privacy",
|
|
6534
|
+
"readability",
|
|
6535
|
+
"gas-optimization"
|
|
6536
|
+
]
|
|
6537
|
+
},
|
|
6538
|
+
"midnight:explain-concept": {
|
|
6539
|
+
concept: [
|
|
6540
|
+
"zk-proofs",
|
|
6541
|
+
"circuits",
|
|
6542
|
+
"witnesses",
|
|
6543
|
+
"ledger",
|
|
6544
|
+
"state-management",
|
|
6545
|
+
"privacy-model",
|
|
6546
|
+
"token-transfers",
|
|
6547
|
+
"merkle-trees"
|
|
6548
|
+
],
|
|
6549
|
+
level: ["beginner", "intermediate", "advanced"]
|
|
6550
|
+
},
|
|
6551
|
+
"midnight:compare-approaches": {
|
|
6552
|
+
approaches: [
|
|
6553
|
+
"token-standards",
|
|
6554
|
+
"state-management",
|
|
6555
|
+
"privacy-patterns",
|
|
6556
|
+
"circuit-design"
|
|
6557
|
+
]
|
|
6558
|
+
},
|
|
6559
|
+
"midnight:debug-contract": {
|
|
6560
|
+
errorType: [
|
|
6561
|
+
"compilation",
|
|
6562
|
+
"runtime",
|
|
6563
|
+
"logic",
|
|
6564
|
+
"privacy-leak",
|
|
6565
|
+
"state-corruption"
|
|
6566
|
+
]
|
|
6567
|
+
}
|
|
6568
|
+
};
|
|
6569
|
+
function registerCompletionsHandler(server) {
|
|
6570
|
+
server.setRequestHandler(CompleteRequestSchema, async (request) => {
|
|
6571
|
+
const { ref, argument } = request.params;
|
|
6572
|
+
if (ref.type !== "ref/prompt") {
|
|
6573
|
+
return { completion: { values: [], hasMore: false } };
|
|
6574
|
+
}
|
|
6575
|
+
const promptName = ref.name;
|
|
6576
|
+
const argName = argument.name;
|
|
6577
|
+
const currentValue = argument.value?.toLowerCase() || "";
|
|
6578
|
+
const promptCompletions = COMPLETION_VALUES[promptName];
|
|
6579
|
+
if (!promptCompletions) {
|
|
6580
|
+
return { completion: { values: [], hasMore: false } };
|
|
6581
|
+
}
|
|
6582
|
+
const argValues = promptCompletions[argName];
|
|
6583
|
+
if (!argValues) {
|
|
6584
|
+
return { completion: { values: [], hasMore: false } };
|
|
6585
|
+
}
|
|
6586
|
+
const filtered = argValues.filter(
|
|
6587
|
+
(v) => v.toLowerCase().includes(currentValue)
|
|
6588
|
+
);
|
|
6589
|
+
return {
|
|
6590
|
+
completion: {
|
|
6591
|
+
values: filtered.slice(0, 20),
|
|
6592
|
+
total: filtered.length,
|
|
6593
|
+
hasMore: filtered.length > 20
|
|
6594
|
+
}
|
|
6595
|
+
};
|
|
6596
|
+
});
|
|
6597
|
+
}
|
|
6598
|
+
function registerToolHandlers(server) {
|
|
6599
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
6600
|
+
logger.debug("Listing tools");
|
|
6601
|
+
return {
|
|
6602
|
+
tools: allTools.map((tool) => ({
|
|
6603
|
+
name: tool.name,
|
|
6604
|
+
description: tool.description,
|
|
6605
|
+
inputSchema: tool.inputSchema,
|
|
6606
|
+
// Include output schema if defined
|
|
6607
|
+
...tool.outputSchema && { outputSchema: tool.outputSchema },
|
|
6608
|
+
// Include annotations if defined
|
|
6609
|
+
...tool.annotations && { annotations: tool.annotations }
|
|
6610
|
+
}))
|
|
6611
|
+
};
|
|
6612
|
+
});
|
|
6613
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
6614
|
+
const { name, arguments: args } = request.params;
|
|
6615
|
+
logger.info(`Tool called: ${name}`, { args });
|
|
6616
|
+
const startTime = Date.now();
|
|
6617
|
+
const tool = allTools.find((t) => t.name === name);
|
|
6618
|
+
if (!tool) {
|
|
6619
|
+
const availableTools = allTools.map((t) => t.name).slice(0, 5).join(", ");
|
|
6620
|
+
trackToolCall(name, false, Date.now() - startTime, CURRENT_VERSION);
|
|
6621
|
+
return {
|
|
6622
|
+
content: [
|
|
6623
|
+
{
|
|
6624
|
+
type: "text",
|
|
6625
|
+
text: serialize({
|
|
6626
|
+
error: `Unknown tool: ${name}`,
|
|
6627
|
+
suggestion: `Available tools include: ${availableTools}...`,
|
|
6628
|
+
hint: "Use ListTools to see all available tools"
|
|
6629
|
+
})
|
|
6630
|
+
}
|
|
6631
|
+
],
|
|
6632
|
+
isError: true
|
|
6633
|
+
};
|
|
6634
|
+
}
|
|
6635
|
+
try {
|
|
6636
|
+
const result = await tool.handler(args);
|
|
6637
|
+
const durationMs = Date.now() - startTime;
|
|
6638
|
+
trackToolCall(name, true, durationMs, CURRENT_VERSION);
|
|
6639
|
+
maybeCheckForUpdates();
|
|
6640
|
+
const updateWarning = getUpdateWarning();
|
|
6641
|
+
if (updateWarning && versionCheckResult.isOutdated) {
|
|
6642
|
+
const updatePrompt = {
|
|
6643
|
+
"\u{1F6A8} UPDATE REQUIRED": {
|
|
6644
|
+
current: `v${CURRENT_VERSION}`,
|
|
6645
|
+
latest: `v${versionCheckResult.latestVersion}`,
|
|
6646
|
+
quickFix: {
|
|
6647
|
+
description: "Most users just need these 2 steps:",
|
|
6648
|
+
step1: "Run in terminal: rm -rf ~/.npm/_npx",
|
|
6649
|
+
step2: "Restart editor completely (Cmd+Q / Alt+F4, then reopen)"
|
|
6650
|
+
},
|
|
6651
|
+
ifStillOld: {
|
|
6652
|
+
description: "If still showing old version after restart:",
|
|
6653
|
+
action: "Edit your MCP config file to use @latest:",
|
|
6654
|
+
change: 'Change "midnight-mcp" to "midnight-mcp@latest" in args',
|
|
6655
|
+
configFiles: {
|
|
6656
|
+
"Claude Desktop (Mac)": "~/Library/Application Support/Claude/claude_desktop_config.json",
|
|
6657
|
+
"Claude Desktop (Win)": "%APPDATA%/Claude/claude_desktop_config.json",
|
|
6658
|
+
Cursor: ".cursor/mcp.json",
|
|
6659
|
+
"VS Code": ".vscode/mcp.json",
|
|
6660
|
+
Windsurf: "~/.codeium/windsurf/mcp_config.json"
|
|
6661
|
+
}
|
|
6662
|
+
},
|
|
6663
|
+
tip: "Use 'midnight-get-update-instructions' tool for detailed platform-specific steps."
|
|
6664
|
+
},
|
|
6665
|
+
result
|
|
6666
|
+
};
|
|
6667
|
+
return {
|
|
6668
|
+
content: [
|
|
6669
|
+
{
|
|
6670
|
+
type: "text",
|
|
6671
|
+
text: serialize(updatePrompt)
|
|
6672
|
+
}
|
|
6673
|
+
]
|
|
6674
|
+
};
|
|
6675
|
+
}
|
|
6676
|
+
return {
|
|
6677
|
+
content: [
|
|
6678
|
+
{
|
|
6679
|
+
type: "text",
|
|
6680
|
+
text: serialize(result)
|
|
6681
|
+
}
|
|
6682
|
+
],
|
|
6683
|
+
// Include structured content for machine-readable responses
|
|
6684
|
+
// This allows clients to parse results without JSON.parse()
|
|
6685
|
+
structuredContent: result
|
|
6686
|
+
};
|
|
6687
|
+
} catch (error) {
|
|
6688
|
+
const durationMs = Date.now() - startTime;
|
|
6689
|
+
logger.error(`Tool error: ${name}`, { error: String(error) });
|
|
6690
|
+
trackToolCall(name, false, durationMs, CURRENT_VERSION);
|
|
6691
|
+
const errorResponse = formatErrorResponse(error, `tool:${name}`);
|
|
6692
|
+
return {
|
|
6693
|
+
content: [
|
|
6694
|
+
{
|
|
6695
|
+
type: "text",
|
|
6696
|
+
text: serialize(errorResponse)
|
|
6697
|
+
}
|
|
6698
|
+
],
|
|
6699
|
+
isError: true
|
|
6700
|
+
};
|
|
6701
|
+
}
|
|
6702
|
+
});
|
|
6703
|
+
}
|
|
6704
|
+
function registerResourceHandlers(server) {
|
|
6705
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
6706
|
+
logger.debug("Listing resources");
|
|
6707
|
+
return {
|
|
6708
|
+
resources: allResources.map((resource) => ({
|
|
6709
|
+
uri: resource.uri,
|
|
6710
|
+
name: resource.name,
|
|
6711
|
+
description: resource.description,
|
|
6712
|
+
mimeType: resource.mimeType
|
|
6713
|
+
}))
|
|
6714
|
+
};
|
|
6715
|
+
});
|
|
6716
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
6717
|
+
logger.debug("Listing resource templates");
|
|
6718
|
+
return {
|
|
6719
|
+
resourceTemplates: resourceTemplates.map((template) => ({
|
|
6720
|
+
uriTemplate: template.uriTemplate,
|
|
6721
|
+
name: template.name,
|
|
6722
|
+
title: template.title,
|
|
6723
|
+
description: template.description,
|
|
6724
|
+
mimeType: template.mimeType
|
|
6725
|
+
}))
|
|
6726
|
+
};
|
|
6727
|
+
});
|
|
6728
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
6729
|
+
const { uri } = request.params;
|
|
6730
|
+
logger.info(`Resource requested: ${uri}`);
|
|
6731
|
+
try {
|
|
6732
|
+
let content = null;
|
|
6733
|
+
let mimeType = "text/plain";
|
|
6734
|
+
if (uri.startsWith("midnight://docs/")) {
|
|
6735
|
+
content = await getDocumentation(uri);
|
|
6736
|
+
mimeType = "text/markdown";
|
|
6737
|
+
} else if (uri.startsWith("midnight://code/")) {
|
|
6738
|
+
content = await getCode(uri);
|
|
6739
|
+
mimeType = "text/x-compact";
|
|
6740
|
+
} else if (uri.startsWith("midnight://schema/")) {
|
|
6741
|
+
const schema = getSchema(uri);
|
|
6742
|
+
content = schema ? JSON.stringify(schema, null, 2) : null;
|
|
6743
|
+
mimeType = "application/json";
|
|
6842
6744
|
}
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
6869
|
-
|
|
6745
|
+
if (!content) {
|
|
6746
|
+
const resourceTypes = [
|
|
6747
|
+
"midnight://docs/",
|
|
6748
|
+
"midnight://code/",
|
|
6749
|
+
"midnight://schema/"
|
|
6750
|
+
];
|
|
6751
|
+
const validPrefix = resourceTypes.find((p) => uri.startsWith(p));
|
|
6752
|
+
let suggestion = validPrefix ? `Check the resource path after '${validPrefix}'` : `Valid resource prefixes: ${resourceTypes.join(", ")}`;
|
|
6753
|
+
if (uri.includes("://resources/")) {
|
|
6754
|
+
const resourceName = uri.split("://resources/").pop() || "";
|
|
6755
|
+
if (resourceName.includes("template") || resourceName.includes("pattern") || resourceName.includes("example")) {
|
|
6756
|
+
suggestion = `Try: midnight://code/templates/${resourceName} or midnight://code/examples/${resourceName} or midnight://code/patterns/${resourceName}`;
|
|
6757
|
+
} else if (resourceName.includes("doc") || resourceName.includes("reference") || resourceName.includes("guide")) {
|
|
6758
|
+
suggestion = `Try: midnight://docs/${resourceName}`;
|
|
6759
|
+
} else {
|
|
6760
|
+
suggestion = `'midnight://resources/' is not valid. Use: midnight://docs/, midnight://code/, or midnight://schema/`;
|
|
6761
|
+
}
|
|
6762
|
+
}
|
|
6763
|
+
return {
|
|
6764
|
+
contents: [
|
|
6765
|
+
{
|
|
6766
|
+
uri,
|
|
6767
|
+
mimeType: "application/json",
|
|
6768
|
+
text: serialize({
|
|
6769
|
+
error: `Resource not found: ${uri}`,
|
|
6770
|
+
suggestion,
|
|
6771
|
+
hint: "Use ListResources to see all available resources",
|
|
6772
|
+
validPrefixes: resourceTypes
|
|
6773
|
+
})
|
|
6774
|
+
}
|
|
6775
|
+
]
|
|
6776
|
+
};
|
|
6870
6777
|
}
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
6875
|
-
|
|
6876
|
-
|
|
6877
|
-
|
|
6878
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
6899
|
-
|
|
6900
|
-
|
|
6901
|
-
|
|
6902
|
-
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6909
|
-
|
|
6910
|
-
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6916
|
-
|
|
6917
|
-
|
|
6918
|
-
|
|
6919
|
-
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
{
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
}
|
|
6946
|
-
{
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
}
|
|
6954
|
-
|
|
6955
|
-
{
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
|
|
6960
|
-
|
|
6961
|
-
|
|
6962
|
-
|
|
6963
|
-
|
|
6964
|
-
|
|
6965
|
-
|
|
6966
|
-
|
|
6967
|
-
|
|
6968
|
-
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
|
|
6973
|
-
|
|
6974
|
-
|
|
6975
|
-
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
}
|
|
6993
|
-
|
|
6994
|
-
|
|
6995
|
-
|
|
6996
|
-
|
|
6997
|
-
|
|
6998
|
-
|
|
6778
|
+
return {
|
|
6779
|
+
contents: [
|
|
6780
|
+
{
|
|
6781
|
+
uri,
|
|
6782
|
+
mimeType,
|
|
6783
|
+
text: content
|
|
6784
|
+
}
|
|
6785
|
+
]
|
|
6786
|
+
};
|
|
6787
|
+
} catch (error) {
|
|
6788
|
+
logger.error(`Resource error: ${uri}`, { error: String(error) });
|
|
6789
|
+
const errorResponse = formatErrorResponse(error, `resource:${uri}`);
|
|
6790
|
+
return {
|
|
6791
|
+
contents: [
|
|
6792
|
+
{
|
|
6793
|
+
uri,
|
|
6794
|
+
mimeType: "application/json",
|
|
6795
|
+
text: serialize(errorResponse)
|
|
6796
|
+
}
|
|
6797
|
+
]
|
|
6798
|
+
};
|
|
6799
|
+
}
|
|
6800
|
+
});
|
|
6801
|
+
}
|
|
6802
|
+
function registerPromptHandlers(server) {
|
|
6803
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
6804
|
+
logger.debug("Listing prompts");
|
|
6805
|
+
return {
|
|
6806
|
+
prompts: promptDefinitions.map((prompt) => ({
|
|
6807
|
+
name: prompt.name,
|
|
6808
|
+
description: prompt.description,
|
|
6809
|
+
arguments: prompt.arguments
|
|
6810
|
+
}))
|
|
6811
|
+
};
|
|
6812
|
+
});
|
|
6813
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
6814
|
+
const { name, arguments: args } = request.params;
|
|
6815
|
+
logger.info(`Prompt requested: ${name}`, { args });
|
|
6816
|
+
const prompt = promptDefinitions.find((p) => p.name === name);
|
|
6817
|
+
if (!prompt) {
|
|
6818
|
+
return {
|
|
6819
|
+
description: `Unknown prompt: ${name}`,
|
|
6820
|
+
messages: []
|
|
6821
|
+
};
|
|
6822
|
+
}
|
|
6823
|
+
const messages = generatePrompt(name, args || {});
|
|
6824
|
+
return {
|
|
6825
|
+
description: prompt.description,
|
|
6826
|
+
messages: messages.map((m) => ({
|
|
6827
|
+
role: m.role,
|
|
6828
|
+
content: m.content
|
|
6829
|
+
}))
|
|
6830
|
+
};
|
|
6831
|
+
});
|
|
6832
|
+
}
|
|
6833
|
+
function registerSubscriptionHandlers(server) {
|
|
6834
|
+
server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
|
6835
|
+
const { uri } = request.params;
|
|
6836
|
+
logger.info(`Subscribing to resource: ${uri}`);
|
|
6837
|
+
const validPrefixes = [
|
|
6838
|
+
"midnight://docs/",
|
|
6839
|
+
"midnight://code/",
|
|
6840
|
+
"midnight://schema/"
|
|
6841
|
+
];
|
|
6842
|
+
const isValid = validPrefixes.some((prefix) => uri.startsWith(prefix));
|
|
6843
|
+
if (!isValid) {
|
|
6844
|
+
logger.warn(`Invalid subscription URI: ${uri}`);
|
|
6845
|
+
throw new Error(
|
|
6846
|
+
`Invalid subscription URI: ${uri}. Valid prefixes: ${validPrefixes.join(", ")}`
|
|
6847
|
+
);
|
|
6848
|
+
}
|
|
6849
|
+
resourceSubscriptions.add(uri);
|
|
6850
|
+
logger.debug(`Active subscriptions: ${resourceSubscriptions.size}`);
|
|
6851
|
+
return {};
|
|
6852
|
+
});
|
|
6853
|
+
server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
|
|
6854
|
+
const { uri } = request.params;
|
|
6855
|
+
logger.info(`Unsubscribing from resource: ${uri}`);
|
|
6856
|
+
resourceSubscriptions.delete(uri);
|
|
6857
|
+
logger.debug(`Active subscriptions: ${resourceSubscriptions.size}`);
|
|
6858
|
+
return {};
|
|
6859
|
+
});
|
|
6860
|
+
}
|
|
6861
|
+
function setupSampling(server) {
|
|
6862
|
+
const samplingCallback2 = async (request) => {
|
|
6863
|
+
logger.debug("Requesting sampling from client", {
|
|
6864
|
+
messageCount: request.messages.length,
|
|
6865
|
+
maxTokens: request.maxTokens
|
|
6866
|
+
});
|
|
6867
|
+
try {
|
|
6868
|
+
const response = await server.request(
|
|
6869
|
+
{
|
|
6870
|
+
method: "sampling/createMessage",
|
|
6871
|
+
params: {
|
|
6872
|
+
messages: request.messages,
|
|
6873
|
+
systemPrompt: request.systemPrompt,
|
|
6874
|
+
maxTokens: request.maxTokens || 2048,
|
|
6875
|
+
temperature: request.temperature,
|
|
6876
|
+
modelPreferences: request.modelPreferences
|
|
6877
|
+
}
|
|
6878
|
+
},
|
|
6879
|
+
// Use a schema that matches the expected response
|
|
6880
|
+
{
|
|
6881
|
+
parse: (data) => {
|
|
6882
|
+
const response2 = data;
|
|
6883
|
+
if (!response2 || typeof response2 !== "object") {
|
|
6884
|
+
throw new Error("Invalid sampling response: expected object");
|
|
6885
|
+
}
|
|
6886
|
+
if (!response2.content || typeof response2.content !== "object") {
|
|
6887
|
+
throw new Error("Invalid sampling response: missing content");
|
|
6888
|
+
}
|
|
6889
|
+
return response2;
|
|
6890
|
+
},
|
|
6891
|
+
_def: { typeName: "SamplingResponse" }
|
|
6892
|
+
}
|
|
6893
|
+
);
|
|
6894
|
+
return response;
|
|
6895
|
+
} catch (error) {
|
|
6896
|
+
logger.error("Sampling request failed", { error: String(error) });
|
|
6897
|
+
throw error;
|
|
6898
|
+
}
|
|
6899
|
+
};
|
|
6900
|
+
registerSamplingCallback(samplingCallback2);
|
|
6901
|
+
logger.info("Sampling capability configured");
|
|
6902
|
+
}
|
|
6903
|
+
async function initializeServer() {
|
|
6904
|
+
logger.info("Initializing Midnight MCP Server...");
|
|
6905
|
+
checkForUpdates().catch((error) => {
|
|
6906
|
+
logger.debug("Startup version check failed (non-blocking)", {
|
|
6907
|
+
error: error instanceof Error ? error.message : String(error)
|
|
6908
|
+
});
|
|
6909
|
+
});
|
|
6910
|
+
try {
|
|
6911
|
+
await vectorStore.initialize();
|
|
6912
|
+
logger.info("Vector store initialized");
|
|
6913
|
+
} catch (error) {
|
|
6914
|
+
logger.warn("Vector store initialization failed, continuing without it", {
|
|
6915
|
+
error: String(error)
|
|
6916
|
+
});
|
|
6999
6917
|
}
|
|
7000
|
-
|
|
6918
|
+
const server = createServer();
|
|
6919
|
+
logger.info(`Server v${CURRENT_VERSION} created successfully`);
|
|
6920
|
+
return server;
|
|
6921
|
+
}
|
|
6922
|
+
async function startServer() {
|
|
6923
|
+
const server = await initializeServer();
|
|
6924
|
+
const transport = new StdioServerTransport();
|
|
6925
|
+
await server.connect(transport);
|
|
6926
|
+
setServerConnected(true);
|
|
6927
|
+
logger.info("Midnight MCP Server running on stdio");
|
|
6928
|
+
}
|
|
6929
|
+
var transports = {
|
|
6930
|
+
streamable: {},
|
|
6931
|
+
sse: {}
|
|
6932
|
+
};
|
|
6933
|
+
async function closeTransports(transportMap) {
|
|
6934
|
+
const closePromises = Object.values(transportMap).map(
|
|
6935
|
+
(transport) => transport.close?.().catch(() => {
|
|
6936
|
+
})
|
|
6937
|
+
);
|
|
6938
|
+
await Promise.all(closePromises);
|
|
6939
|
+
}
|
|
6940
|
+
async function startHttpServer(port = 3e3) {
|
|
6941
|
+
const mcpServer = await initializeServer();
|
|
6942
|
+
const app = express();
|
|
6943
|
+
app.use("/mcp", express.json());
|
|
6944
|
+
app.get("/health", (_req, res) => {
|
|
6945
|
+
res.json({
|
|
6946
|
+
status: "ok",
|
|
6947
|
+
version: CURRENT_VERSION,
|
|
6948
|
+
transport: "http"
|
|
6949
|
+
});
|
|
6950
|
+
});
|
|
6951
|
+
app.post("/mcp", async (req, res) => {
|
|
6952
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
6953
|
+
let transport;
|
|
6954
|
+
if (sessionId && transports.streamable[sessionId]) {
|
|
6955
|
+
transport = transports.streamable[sessionId];
|
|
6956
|
+
} else if (!sessionId && isInitializeRequest(req.body)) {
|
|
6957
|
+
transport = new StreamableHTTPServerTransport({
|
|
6958
|
+
sessionIdGenerator: () => randomUUID(),
|
|
6959
|
+
onsessioninitialized: (newSessionId) => {
|
|
6960
|
+
transports.streamable[newSessionId] = transport;
|
|
6961
|
+
logger.debug(`New streamable session: ${newSessionId}`);
|
|
6962
|
+
}
|
|
6963
|
+
});
|
|
6964
|
+
transport.onclose = () => {
|
|
6965
|
+
if (transport.sessionId) {
|
|
6966
|
+
delete transports.streamable[transport.sessionId];
|
|
6967
|
+
logger.debug(`Streamable session closed: ${transport.sessionId}`);
|
|
6968
|
+
}
|
|
6969
|
+
};
|
|
6970
|
+
try {
|
|
6971
|
+
await mcpServer.connect(transport);
|
|
6972
|
+
} catch (error) {
|
|
6973
|
+
logger.error("Failed to connect streamable transport", {
|
|
6974
|
+
error: String(error)
|
|
6975
|
+
});
|
|
6976
|
+
res.status(500).json({
|
|
6977
|
+
jsonrpc: "2.0",
|
|
6978
|
+
error: { code: -32603, message: "Internal error: connection failed" },
|
|
6979
|
+
id: null
|
|
6980
|
+
});
|
|
6981
|
+
return;
|
|
6982
|
+
}
|
|
6983
|
+
} else {
|
|
6984
|
+
res.status(400).json({
|
|
6985
|
+
jsonrpc: "2.0",
|
|
6986
|
+
error: {
|
|
6987
|
+
code: -32e3,
|
|
6988
|
+
message: "Bad Request: No valid session ID provided"
|
|
6989
|
+
},
|
|
6990
|
+
id: null
|
|
6991
|
+
});
|
|
6992
|
+
return;
|
|
6993
|
+
}
|
|
6994
|
+
await transport.handleRequest(req, res, req.body);
|
|
6995
|
+
});
|
|
6996
|
+
app.get("/sse", async (_req, res) => {
|
|
6997
|
+
logger.debug("New SSE connection");
|
|
6998
|
+
const transport = new SSEServerTransport("/messages", res);
|
|
6999
|
+
transports.sse[transport.sessionId] = transport;
|
|
7000
|
+
res.on("close", () => {
|
|
7001
|
+
delete transports.sse[transport.sessionId];
|
|
7002
|
+
logger.debug(`SSE session closed: ${transport.sessionId}`);
|
|
7003
|
+
});
|
|
7004
|
+
try {
|
|
7005
|
+
await mcpServer.connect(transport);
|
|
7006
|
+
} catch (error) {
|
|
7007
|
+
delete transports.sse[transport.sessionId];
|
|
7008
|
+
logger.error(`SSE connection failed: ${transport.sessionId}`, {
|
|
7009
|
+
error: String(error)
|
|
7010
|
+
});
|
|
7011
|
+
res.status(500).end();
|
|
7012
|
+
}
|
|
7013
|
+
});
|
|
7014
|
+
app.post("/messages", async (req, res) => {
|
|
7015
|
+
const sessionId = req.query.sessionId;
|
|
7016
|
+
const transport = transports.sse[sessionId];
|
|
7017
|
+
if (transport) {
|
|
7018
|
+
await transport.handlePostMessage(req, res);
|
|
7019
|
+
} else {
|
|
7020
|
+
res.status(400).send(`No transport found for sessionId ${sessionId}`);
|
|
7021
|
+
}
|
|
7022
|
+
});
|
|
7023
|
+
const handleSessionRequest = async (req, res) => {
|
|
7024
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
7025
|
+
if (!sessionId || !transports.streamable[sessionId]) {
|
|
7026
|
+
res.status(400).send("Invalid or missing session ID");
|
|
7027
|
+
return;
|
|
7028
|
+
}
|
|
7029
|
+
const transport = transports.streamable[sessionId];
|
|
7030
|
+
await transport.handleRequest(req, res);
|
|
7031
|
+
};
|
|
7032
|
+
app.get("/mcp", handleSessionRequest);
|
|
7033
|
+
app.delete("/mcp", handleSessionRequest);
|
|
7034
|
+
const httpServer = app.listen(port, "127.0.0.1", () => {
|
|
7035
|
+
logger.info(`Midnight MCP Server running on HTTP port ${port}`);
|
|
7036
|
+
logger.info(` Streamable HTTP: http://localhost:${port}/mcp`);
|
|
7037
|
+
logger.info(` SSE endpoint: http://localhost:${port}/sse`);
|
|
7038
|
+
logger.info(` Health check: http://localhost:${port}/health`);
|
|
7039
|
+
});
|
|
7040
|
+
const shutdown = async () => {
|
|
7041
|
+
logger.info("Shutting down HTTP server...");
|
|
7042
|
+
await closeTransports(transports.sse);
|
|
7043
|
+
await closeTransports(transports.streamable);
|
|
7044
|
+
httpServer.close(() => {
|
|
7045
|
+
logger.info("Server shutdown complete");
|
|
7046
|
+
process.exit(0);
|
|
7047
|
+
});
|
|
7048
|
+
};
|
|
7049
|
+
process.on("SIGINT", shutdown);
|
|
7050
|
+
process.on("SIGTERM", shutdown);
|
|
7051
|
+
}
|
|
7001
7052
|
|
|
7002
7053
|
// src/tools/repository/validation.ts
|
|
7003
7054
|
import { readFile } from "fs/promises";
|
|
@@ -7387,7 +7438,7 @@ async function extractContractStructure(input) {
|
|
|
7387
7438
|
type: "invalid_pragma_format",
|
|
7388
7439
|
line: lineNum,
|
|
7389
7440
|
message: `Pragma includes patch version which may cause parse errors`,
|
|
7390
|
-
suggestion: `Use bounded range format: '
|
|
7441
|
+
suggestion: `Use bounded range format: '${RECOMMENDED_PRAGMA}'`,
|
|
7391
7442
|
severity: "error"
|
|
7392
7443
|
});
|
|
7393
7444
|
}
|
|
@@ -8427,6 +8478,36 @@ export circuit increment(): [] {
|
|
|
8427
8478
|
wrong: "export circuit fn(param: T): [] { ledger.insert(param, v); }",
|
|
8428
8479
|
correct: "export circuit fn(param: T): [] { const d = disclose(param); ledger.insert(d, v); }",
|
|
8429
8480
|
error: "potential witness-value disclosure must be declared"
|
|
8481
|
+
},
|
|
8482
|
+
{
|
|
8483
|
+
wrong: "NativePoint, nativePointX(p), constructNativePoint(x, y)",
|
|
8484
|
+
correct: "JubjubPoint, jubjubPointX(p), constructJubjubPoint(x, y)",
|
|
8485
|
+
error: "apparent use of an old standard-library / ledger operator name NativePoint: the new name is JubjubPoint (renamed in compactc 0.30.0)"
|
|
8486
|
+
},
|
|
8487
|
+
{
|
|
8488
|
+
wrong: "const let = 1; // or `let` used as any identifier",
|
|
8489
|
+
correct: "const value = 1; // rename the identifier",
|
|
8490
|
+
error: 'parse error: found keyword "let" (which is reserved for future use)'
|
|
8491
|
+
},
|
|
8492
|
+
{
|
|
8493
|
+
wrong: "const MAX_SUPPLY: Uint<64> = 1000; // module-level const",
|
|
8494
|
+
correct: "pure circuit max_supply(): Uint<64> { return 1000; } // wrap in a pure circuit",
|
|
8495
|
+
error: 'parse error: found keyword "const" looking for a program element'
|
|
8496
|
+
},
|
|
8497
|
+
{
|
|
8498
|
+
wrong: "flags | mask // bitwise OR on Uint",
|
|
8499
|
+
correct: "// No bitwise ops on Uint \u2014 use arithmetic or branching. `||` is logical OR for Booleans only.",
|
|
8500
|
+
error: "unexpected character (single `|` not recognized)"
|
|
8501
|
+
},
|
|
8502
|
+
{
|
|
8503
|
+
wrong: "field: Uint<254>",
|
|
8504
|
+
correct: "field: Uint<248> // max byte-aligned Uint width",
|
|
8505
|
+
error: "Uint width 254 is not between 1 and the maximum Uint width 248 (inclusive)"
|
|
8506
|
+
},
|
|
8507
|
+
{
|
|
8508
|
+
wrong: "convertBytesToUint(255, n, bytes, src) // number literal",
|
|
8509
|
+
correct: "convertBytesToUint(255n, n, bytes, src) // bigint literal",
|
|
8510
|
+
error: "TypeScript binding: maxval is bigint since compactc 0.30.101 (avoids silent precision loss)"
|
|
8430
8511
|
}
|
|
8431
8512
|
],
|
|
8432
8513
|
syntaxReference: compactReference,
|
|
@@ -11172,4 +11253,4 @@ export {
|
|
|
11172
11253
|
startServer,
|
|
11173
11254
|
startHttpServer
|
|
11174
11255
|
};
|
|
11175
|
-
//# sourceMappingURL=chunk-
|
|
11256
|
+
//# sourceMappingURL=chunk-4CUN6SQZ.js.map
|