Commit1278b1e6Recorded25 Apr 2026Repositorysigil-mcp

Remove sigil-cli dev-tools modules; sigil-mcp is library-only

Message

The 5 dev-tools modules (tools, nrepl, resources, lint, commands) plus their tests are sigil-cli specific (they expose Sigil's docs, lint, format, test, nREPL eval) and have no consumers outside sigil-cli. Moving them out of this repo makes the library/server boundary clean: bureau/courier/folio/minder/tally and any future MCP server author imports the 3 library modules without dragging along the sigil-cli dev-tools surface.

The dev-tools modules are landing in sigil-cli at (sigil cli mcp ...) in a paired monorepo commit. sigil-cli will pin sigil-mcp ^0.13.1 through from-git as part of that commit.

Top-level (sigil mcp) keeps the library re-exports plus the generic load-tool-module! / load-package-tools! discovery helpers (which let any MCP server dynamically pull in package-provided tools).

Removed dependencies: sigil-args, sigil-ansi, sigil-nrepl (only used by the moved dev-tools modules).

Tests: 62 passed, 2 failed (same 2 baseline failures as pre-split and as monorepo master; pre-existing schema-validation tests).

Changed
 README.md                   |  35 ++++---
 package.sgl                 |   3 -
 sigil.lock                  |  15 ---
 src/sigil/mcp.sgl           |  55 +++--------
 src/sigil/mcp/commands.sgl  |  89 ------------------
 src/sigil/mcp/lint.sgl      | 163 --------------------------------
 src/sigil/mcp/nrepl.sgl     | 137 ---------------------------
 src/sigil/mcp/resources.sgl | 369 -------------------------------------------------------------------------
 src/sigil/mcp/tools.sgl     | 832 --------------------------------------------------------------------------------------------------------------------------------------------------------------------
 test/test-nrepl-tools.sgl   |  24 -----
 test/test-tools.sgl         |  65 -------------
 11 files changed, 31 insertions(+), 1756 deletions(-)
Diff
README.mdmodified
@@ -12,32 +12,31 @@ work idiomatically against a live Sigil project.
12
13
## Modules
14
15
| Module | Purpose |
16
|-------------------------|---------------------------------------------------------------|
17
| `(sigil mcp)` | Top-level `mcp-serve` plus re-exports for the bundled server |
18
| `(sigil mcp server)` | Server loop, message routing, handler registration |
19
| `(sigil mcp protocol)` | JSON-RPC 2.0 framing and MCP message shapes |
20
| `(sigil mcp channel)` | Helpers for channel-based server transports |
21
| `(sigil mcp tools)` | Built-in dev-tools (docs, format, test, ...) |
22
| `(sigil mcp nrepl)` | Dev-tools backed by an attached nREPL session |
23
| `(sigil mcp resources)` | Resource providers (docs, package metadata) |
24
| `(sigil mcp lint)` | Code linting surface as an MCP tool |
25
| `(sigil mcp commands)` | `sigil mcp` CLI subcommand registration |
+15
| Module | Purpose |
+16
|------------------------|--------------------------------------------------------|
+17
| `(sigil mcp)` | Top-level re-exports plus package-tool discovery |
+18
| `(sigil mcp server)` | Server loop, message routing, handler registration |
+19
| `(sigil mcp protocol)` | JSON-RPC 2.0 framing and MCP message shapes |
+20
| `(sigil mcp channel)` | Helpers for channel-based server transports |
21
22
## Building your own MCP server
23
24
```scheme
30
(import (sigil mcp server)
31
(sigil mcp protocol))
+25
(import (sigil mcp))
26
33
;; Define your tools, register handlers, then run the server loop.
34
```
+27
(define server
+28
(mcp-server name: "my-server" version: "1.0"))
29
36
## Running the bundled dev-tools server
+30
(mcp-server-register-tool! server
+31
name: "echo"
+32
description: "Echoes its input back."
+33
handler: (lambda (args) ...))
34
+35
(mcp-server-run server)
36
```
39
sigil mcp serve
40
```
+37
+38
For a complete example, see sigil-cli's `(sigil cli mcp)` module or
+39
projects like bureau, courier, folio, and minder.
40
41
## Build and test
42
package.sglmodified
@@ -16,7 +16,4 @@
16
dependencies: (list
17
(from-git url: "codeberg:sigil/sigil" package: "sigil-stdlib" version: "^0.13.1")
18
(from-git url: "codeberg:sigil/sigil-json" version: "^0.13.1")
19
(from-git url: "codeberg:sigil/sigil" package: "sigil-args" version: "^0.13.1")
20
(from-git url: "codeberg:sigil/sigil" package: "sigil-ansi" version: "^0.13.1")
21
(from-git url: "codeberg:sigil/sigil" package: "sigil-nrepl" version: "^0.13.1")
19
(from-git url: "codeberg:sigil/sigil-log" version: "^0.13.0")))
sigil.lockmodified
@@ -10,21 +10,6 @@
10
ref: "^0.13.1"
11
sha: "811b63925399a663e4b7dd25fb0813d59d33f557"
12
version: "0.13.1")
13
(package name: "sigil-args"
14
url: "codeberg:sigil/sigil"
15
ref: "^0.13.1"
16
sha: "1aea1cecd3487bf07d071da2c1e7eb3cf4bbdb21"
17
package-selector: "sigil-args")
18
(package name: "sigil-ansi"
19
url: "codeberg:sigil/sigil"
20
ref: "^0.13.1"
21
sha: "1aea1cecd3487bf07d071da2c1e7eb3cf4bbdb21"
22
package-selector: "sigil-ansi")
23
(package name: "sigil-nrepl"
24
url: "codeberg:sigil/sigil"
25
ref: "^0.13.1"
26
sha: "1aea1cecd3487bf07d071da2c1e7eb3cf4bbdb21"
27
package-selector: "sigil-nrepl")
13
(package name: "sigil-log"
14
url: "codeberg:sigil/sigil-log"
15
ref: "^0.13.0"
src/sigil/mcp.sglmodified
@@ -1,13 +1,14 @@
1
;;; (sigil mcp) - Model Context Protocol server for Sigil.
+1
;;; (sigil mcp) - Library for building Model Context Protocol servers in Sigil.
2
;;;
3
;;; Enables AI agents to write idiomatic Sigil code by providing
4
;;; documentation, procedure discovery, and development tools.
+3
;;; Re-exports the server, protocol, and channel primitives plus
+4
;;; package-tool discovery helpers for assembling MCP servers.
5
;;;
6
;;; ```
7
;;; (import (sigil mcp))
8
;;;
9
;;; ;; Start the MCP server (reads from stdin, writes to stdout)
10
;;; (mcp-serve)
+9
;;; (define server (mcp-server name: "my-server" version: "1.0"))
+10
;;; (mcp-server-register-tool! server ...)
+11
;;; (mcp-server-run server)
12
;;; ```
13
14
(define-library (sigil mcp)
@@ -17,17 +18,13 @@
18
(sigil mcp protocol)
19
(sigil mcp server)
20
(sigil mcp channel)
20
(sigil mcp tools)
21
(sigil mcp nrepl)
22
(sigil mcp resources)
21
(sigil package)
22
(sigil meta))
25
(export mcp-serve
26
mcp-server
+23
(export mcp-server
24
mcp-server?
25
mcp-server-run
26
30
;; Re-export for custom setups
+27
;; Server primitives for custom setups
28
mcp-server-initialized
29
mcp-server-register-tool!
30
mcp-server-register-resource!
@@ -37,7 +34,11 @@
34
;; Channel server helpers
35
mcp-channel-server
36
channel-notify!
40
channel-wait-initialized!)
+37
channel-wait-initialized!
+38
+39
;; Package-tool discovery for dynamic loading
+40
load-tool-module!
+41
load-package-tools!)
42
(begin
43
44
;; ============================================================
@@ -67,32 +68,4 @@
68
(let ((module-name (package-provides-ref (cdr pair) mcp-tools:)))
69
(when module-name
70
(load-tool-module! server module-name))))
70
packages))))))
71
72
;; ============================================================
73
;; Main Entry Point
74
;; ============================================================
75
76
;;; Start the MCP server.
77
;;;
78
;;; Reads JSON-RPC messages from stdin, writes responses to stdout.
79
;;; Resource resolution is handled automatically by (sigil resources),
80
;;; checking project build output, bundled archives, and filesystem.
81
(define (mcp-serve)
82
(let ((server (mcp-server name: "sigil"
83
version: (sigil-version))))
84
85
;; Register core tools
86
(register-tools! server mcp-server-register-tool!)
87
88
;; Register nREPL tools (proxy to running app for live development)
89
(register-nrepl-tools! server mcp-server-register-tool!)
90
91
;; Discover and load package-provided tools
92
(load-package-tools! server)
93
94
;; Register resources
95
(register-resources! server mcp-server-register-resource!)
96
97
;; Run the server loop
98
(mcp-server-run server)))))
+71
packages))))))))
src/sigil/mcp/commands.sgldeleted
@@ -1,89 +0,0 @@
1
;;; (sigil mcp commands) - CLI command for MCP server.
2
;;;
3
;;; Provides the `sigil mcp` command for starting the MCP server.
4
5
(define-library (sigil mcp commands)
6
(import (sigil mcp)
7
(sigil args)
8
(sigil io)
9
(sigil log)
10
(sigil process)
11
(sigil string)
12
(sigil ansi))
13
(export mcp-cmd)
14
(begin
15
16
;;; Handler for 'sigil mcp serve' - start the MCP server
17
(define (mcp-serve-handler opts args)
18
;; Configure logging from --log / --log-level before starting.
19
;; log-configure-from-args! reads directly from (command-line),
20
;; so the flags just need to be on the CLI — they're declared as
21
;; options on mcp-cmd so sigil-args doesn't reject them.
22
(log-configure-from-args!)
23
;; Start the server (blocks until stdin closes)
24
(mcp-serve))
25
26
;;; Handler for 'sigil mcp' without subcommand
27
(define (mcp-handler opts args)
28
(if (null? args)
29
(begin
30
(println "")
31
(println "~a sigil mcp <command>" (a:bold "Usage:"))
32
(println "")
33
(println "~a" (a:bold "Commands:"))
34
(println " serve Start the MCP server (stdio transport)")
35
(println "")
36
(println "~a" (a:bold "Options for serve:"))
37
(println " --log PATH Append logs to PATH (default: stderr)")
38
(println " --log-level LEVEL trace|debug|info|warn|error|fatal")
39
(println "")
40
(println "~a" (a:bold "Example:"))
41
(println " sigil mcp serve")
42
(println " sigil mcp serve --log /tmp/sigil-mcp.log --log-level trace")
43
(println "")
44
(println "The MCP server enables AI agents to write idiomatic Sigil code")
45
(println "by providing documentation, procedure discovery, and dev tools.")
46
(println "")
47
(println "Resource resolution is automatic:")
48
(println " 1. Project build output (if package.sgl found)")
49
(println " 2. Bundled archive (if running from bundle)")
50
(println " 3. Filesystem (library paths)")
51
(println ""))
52
;; Subcommand provided
53
(let ((subcmd (car args)))
54
(cond
55
((string=? subcmd "serve")
56
(mcp-serve-handler opts (cdr args)))
57
(else
58
(eprintln "~a Unknown mcp command: ~a" (a:red "Error:") subcmd)
59
(eprintln "Run 'sigil mcp' for available commands.")
60
(exit 1))))))
61
62
(define log-opt
63
(option
64
name: 'log
65
long: "log"
66
value: "PATH"
67
description: "Append logs to PATH (default: stderr)"))
68
69
(define log-level-opt
70
(option
71
name: 'log-level
72
long: "log-level"
73
value: "LEVEL"
74
description: "Log level: trace|debug|info|warn|error|fatal"))
75
76
;;; MCP command definition
77
(define mcp-cmd
78
(command
79
name: "mcp"
80
description: "Start MCP server for AI-assisted development
81
82
The Model Context Protocol (MCP) server enables AI agents like
83
Claude to write idiomatic Sigil code by providing documentation,
84
procedure discovery, and development tools.
85
86
Subcommands:
87
serve Start the server (reads JSON-RPC from stdin)"
88
options: (list log-opt log-level-opt)
89
handler: mcp-handler))))
src/sigil/mcp/lint.sgldeleted
@@ -1,163 +0,0 @@
1
;;; (sigil mcp lint) - Code linting for Sigil.
2
;;;
3
;;; Provides lint checks for Sigil source code. Currently supports:
4
;;; - unused-imports: Detect imported modules whose exports are not referenced.
5
6
(define-library (sigil mcp lint)
7
(import (sigil core)
8
(sigil string)
9
(sigil io)
10
(sigil struct)
11
(sigil docs search))
12
(export lint-code
13
lint-warning
14
lint-warning?
15
lint-warning-check
16
lint-warning-severity
17
lint-warning-message)
18
(begin
19
20
(define-struct lint-warning
21
(check)
22
(severity)
23
(message))
24
25
;; ============================================================
26
;; Main Entry Point
27
;; ============================================================
28
29
;;; Run lint checks on source code.
30
;;; Returns a list of lint-warning records.
31
(define (lint-code code checks)
32
(let ((run-all? (or (null? checks)
33
(member "all" checks))))
34
(let ((warnings '()))
35
(when (or run-all? (member "unused-imports" checks))
36
(set! warnings (append warnings (check-unused-imports code))))
37
warnings)))
38
39
;; ============================================================
40
;; Unused Imports Check
41
;; ============================================================
42
43
;; Check for imported modules whose exports are never referenced.
44
(define (check-unused-imports code)
45
(guard (e (else '()))
46
(let* ((forms (read-all-forms code))
47
(imports (extract-imports forms))
48
(used-symbols (collect-used-symbols forms)))
49
(filter-map
50
(lambda (mod-name)
51
(let ((exports (get-module-export-names mod-name)))
52
(if (or (null? exports)
53
(any-symbol-used? exports used-symbols))
54
#f
55
(lint-warning
56
check: "unused-imports"
57
severity: "warning"
58
message: (format "Unused import: ~a" mod-name)))))
59
imports))))
60
61
;; Read all top-level forms from source code
62
(define (read-all-forms code)
63
(let ((port (open-input-string code)))
64
(let loop ((forms '()))
65
(let ((form (read port)))
66
(if (eof-object? form)
67
(reverse forms)
68
(loop (cons form forms)))))))
69
70
;; Extract module names from import forms
71
(define (extract-imports forms)
72
(apply append
73
(map extract-imports-from-form forms)))
74
75
;; Extract import module names from a single form
76
(define (extract-imports-from-form form)
77
(cond
78
;; Top-level (import ...)
79
((and (pair? form)
80
(eq? (car form) 'import))
81
(map import-set->module-name (cdr form)))
82
;; (define-library ... (import ...))
83
((and (pair? form)
84
(eq? (car form) 'define-library))
85
(let ((import-clause (find-clause 'import (cddr form))))
86
(if import-clause
87
(map import-set->module-name (cdr import-clause))
88
'())))
89
(else '())))
90
91
;; Convert an import set to a module name string.
92
;; Handles plain module names and prefix/only/except/rename wrappers.
93
(define (import-set->module-name import-set)
94
(cond
95
((and (pair? import-set)
96
(memq (car import-set) '(only except prefix rename)))
97
(import-set->module-name (cadr import-set)))
98
((pair? import-set)
99
(format "~a" import-set))
100
(else
101
(format "~a" import-set))))
102
103
;; Find a named clause in a define-library body
104
(define (find-clause name clauses)
105
(find (lambda (c) (and (pair? c) (eq? (car c) name))) clauses))
106
107
;; Collect all symbols referenced in non-import forms as a list
108
(define (collect-used-symbols forms)
109
(let ((symbols '()))
110
(for-each
111
(lambda (form)
112
(unless (and (pair? form)
113
(memq (car form) '(import define-library)))
114
(set! symbols (walk-collect-symbols form symbols))))
115
forms)
116
symbols))
117
118
;; Walk a form tree collecting symbol references into a list
119
(define (walk-collect-symbols form acc)
120
(cond
121
((symbol? form)
122
(if (memq form acc) acc (cons form acc)))
123
((pair? form)
124
(case (car form)
125
;; Skip quoted data
126
((quote) acc)
127
;; For quasiquote, only collect from unquoted parts
128
((quasiquote)
129
(walk-quasiquote (cadr form) acc))
130
(else
131
(fold-left (lambda (a sub) (walk-collect-symbols sub a))
132
acc form))))
133
(else acc)))
134
135
;; Walk quasiquote, only collecting symbols from unquoted parts
136
(define (walk-quasiquote form acc)
137
(cond
138
((and (pair? form)
139
(or (eq? (car form) 'unquote)
140
(eq? (car form) 'unquote-splicing)))
141
(walk-collect-symbols (cadr form) acc))
142
((pair? form)
143
(walk-quasiquote (cdr form)
144
(walk-quasiquote (car form) acc)))
145
(else acc)))
146
147
;; Get export symbol names for a module
148
(define (get-module-export-names module-name-str)
149
(let ((results (get-module-exports module-name-str)))
150
(map (lambda (r) (string->symbol (search-result-name r)))
151
results)))
152
153
;; Check if any symbol from a list appears in the used-symbols list
154
(define (any-symbol-used? export-syms used-symbols)
155
(any (lambda (sym) (memq sym used-symbols))
156
export-syms))
157
158
;; Check if any element in list satisfies predicate
159
(define (any pred lst)
160
(cond
161
((null? lst) #f)
162
((pred (car lst)) #t)
163
(else (any pred (cdr lst)))))))
src/sigil/mcp/nrepl.sgldeleted
@@ -1,137 +0,0 @@
1
;;; (sigil mcp nrepl) - MCP tools for nREPL interaction
2
;;;
3
;;; Provides MCP tools that proxy to a running Sigil app's nREPL server.
4
;;; Enables AI agents to connect, evaluate code, and inspect module state
5
;;; through the MCP interface.
6
7
(define-library (sigil mcp nrepl)
8
(import (sigil core)
9
(sigil string)
10
(sigil json)
11
(sigil nrepl client))
12
13
(export register-nrepl-tools!)
14
15
(begin
16
17
;; ============================================================
18
;; Tool Schemas
19
;; ============================================================
20
21
(define nrepl-connect-schema
22
'((type . "object")
23
(properties . ((host . ((type . "string")
24
(description . "Host to connect to (default: 127.0.0.1)")))
25
(port . ((type . "integer")
26
(description . "nREPL port to connect to")))))
27
(required . ("port"))))
28
29
(define nrepl-eval-schema
30
'((type . "object")
31
(properties . ((code . ((type . "string")
32
(description . "Sigil code to evaluate in the running app")))
33
(module . ((type . "string")
34
(description . "Module context (e.g. \"(sigil web demo views)\")")))))
35
(required . ("code"))))
36
37
(define nrepl-status-schema
38
'((type . "object")))
39
40
(define nrepl-disconnect-schema
41
'((type . "object")))
42
43
;; ============================================================
44
;; Tool Handlers
45
;; ============================================================
46
47
(define (tool-nrepl-connect args)
48
(let ((host (or (dict-ref args host: #f) "127.0.0.1"))
49
(port (dict-ref args port: #f))
50
(existing (nrepl-lookup 'mcp)))
51
(when (and existing (nrepl-connected? existing))
52
(nrepl-disconnect existing)
53
(nrepl-unregister! 'mcp))
54
(let ((conn (nrepl-connect host port)))
55
(if conn
56
(begin
57
(nrepl-register! 'mcp conn)
58
(format "Connected to nREPL at ~a:~a" host port))
59
(format "Failed to connect to nREPL at ~a:~a" host port)))))
60
61
(define (tool-nrepl-eval args)
62
(let ((conn (nrepl-lookup 'mcp)))
63
(if (not (and conn (nrepl-connected? conn)))
64
"Not connected to nREPL. Use sigil/nrepl-connect first."
65
(let ((code (dict-ref args code: ""))
66
(module (dict-ref args module: #f)))
67
(let ((resp (if module
68
(nrepl-eval conn code module)
69
(nrepl-eval conn code))))
70
(if resp
71
(let ((status (assoc-ref 'status resp))
72
(value (assoc-ref 'value resp))
73
(message (assoc-ref 'message resp))
74
(mod (assoc-ref 'module resp)))
75
(cond
76
((eq? status 'ok)
77
(if mod
78
(format "~a\n[module: ~a]" (or value "ok") mod)
79
(or value "ok")))
80
((eq? status 'error)
81
(format "Error: ~a" (or message value "unknown error")))
82
(else
83
(format "~a" (or value "ok")))))
84
"Error: No response from nREPL (connection may be lost)"))))))
85
86
(define (tool-nrepl-status args)
87
(let ((conn (nrepl-lookup 'mcp)))
88
(if (not conn)
89
"Not connected"
90
(if (not (nrepl-connected? conn))
91
"Connection lost"
92
(let ((resp (nrepl-eval conn "(values)")))
93
(if resp
94
(let ((mod (assoc-ref 'module resp)))
95
(format "Connected\nModule: ~a" (or mod "unknown")))
96
"Connected (no response to ping)"))))))
97
98
(define (tool-nrepl-disconnect args)
99
(let ((conn (nrepl-lookup 'mcp)))
100
(if (not conn)
101
"Not connected"
102
(begin
103
(nrepl-disconnect conn)
104
(nrepl-unregister! 'mcp)
105
"Disconnected"))))
106
107
;; ============================================================
108
;; Registration
109
;; ============================================================
110
111
;;; Register nREPL tools with an MCP server.
112
(define (register-nrepl-tools! server register-tool!)
113
(register-tool! server
114
"sigil/nrepl-connect"
115
"Connect to a running Sigil app's nREPL server for live development"
116
nrepl-connect-schema
117
tool-nrepl-connect)
118
119
(register-tool! server
120
"sigil/nrepl-eval"
121
"Evaluate Sigil code in the running app (redefine handlers, views, etc.)"
122
nrepl-eval-schema
123
tool-nrepl-eval)
124
125
(register-tool! server
126
"sigil/nrepl-status"
127
"Check nREPL connection status and current module"
128
nrepl-status-schema
129
tool-nrepl-status)
130
131
(register-tool! server
132
"sigil/nrepl-disconnect"
133
"Disconnect from the nREPL server"
134
nrepl-disconnect-schema
135
tool-nrepl-disconnect))
136
137
))
src/sigil/mcp/resources.sgldeleted
@@ -1,369 +0,0 @@
1
;;; (sigil mcp resources) - MCP resource providers.
2
;;;
3
;;; Provides resource handlers for serving Sigil documentation:
4
;;; sigil://index, sigil://language/*, sigil://style/*, sigil://stdlib/*.
5
;;;
6
;;; Uses (sigil resources) for unified file resolution across project,
7
;;; bundle, and filesystem sources.
8
9
(define-library (sigil mcp resources)
10
(import (sigil string)
11
(sigil json)
12
(sigil io)
13
(sigil fs)
14
(sigil path)
15
(sigil resources))
16
(export register-resources!
17
;; Project resource handlers (for testing)
18
handle-project-overview
19
handle-project-modules)
20
(begin
21
22
;; ============================================================
23
;; Resource Handlers
24
;; ============================================================
25
26
;;; Read the main documentation index.
27
(define (handle-index uri)
28
(or (read-resource 'docs "reference/index.md")
29
"# Sigil Documentation\n\nDocumentation index not found."))
30
31
;;; Read a language reference document.
32
(define (handle-language uri)
33
;; URI: sigil://language/syntax -> docs/reference/language/syntax.md
34
(let* ((name (uri-last-segment uri))
35
(path (string-append "reference/language/" name ".md")))
36
(or (read-resource 'docs path)
37
(format "# ~a\n\nDocumentation not found." name))))
38
39
;;; Read a style guide document.
40
(define (handle-style uri)
41
;; URI: sigil://style/naming -> docs/reference/style/naming.md
42
(let* ((name (uri-last-segment uri))
43
(path (string-append "reference/style/" name ".md")))
44
(or (read-resource 'docs path)
45
(format "# ~a\n\nDocumentation not found." name))))
46
47
;;; Read the stdlib module index.
48
(define (handle-stdlib-index uri)
49
(let ((content (read-resource 'lib "index.json")))
50
(if content
51
(let ((json (json-decode content)))
52
(format-stdlib-index json))
53
"# Standard Library\n\nModule index not found.")))
54
55
;;; Read documentation for a specific stdlib module.
56
(define (handle-stdlib-module uri)
57
;; URI: sigil://stdlib/sigil/string -> lib/sigil/string.json
58
(let* ((module-path (extract-module-path uri))
59
(json-path (string-append module-path ".json"))
60
(content (read-resource 'lib json-path)))
61
(if content
62
(let ((json (json-decode content)))
63
(format-module-docs json))
64
(format "# ~a\n\nModule documentation not found." module-path))))
65
66
;; ============================================================
67
;; URI Helpers
68
;; ============================================================
69
70
;; Extract the last segment from a URI
71
;; sigil://language/syntax -> syntax
72
(define (uri-last-segment uri)
73
(let ((parts (string-split uri "/")))
74
(if (null? parts)
75
""
76
(last parts))))
77
78
;; Extract module path from stdlib URI
79
;; sigil://stdlib/sigil/string -> sigil/string
80
(define (extract-module-path uri)
81
;; Remove sigil://stdlib/ prefix
82
(let ((prefix "sigil://stdlib/"))
83
(if (string-starts-with? uri prefix)
84
(substring uri (string-length prefix) (string-length uri))
85
uri)))
86
87
(define (last lst)
88
(if (null? (cdr lst))
89
(car lst)
90
(last (cdr lst))))
91
92
;; ============================================================
93
;; Formatting Helpers
94
;; ============================================================
95
96
;;; Format the stdlib index JSON as markdown.
97
(define (format-stdlib-index json)
98
(let ((modules (or (dict-ref json modules: #f) '())))
99
(string-append
100
"# Standard Library Modules\n\n"
101
(string-join
102
(map (lambda (m)
103
(let ((name (or (dict-ref m name: #f) "unknown"))
104
(desc (or (dict-ref m description: #f) "")))
105
(format "- **~a** - ~a" name (first-line desc))))
106
modules)
107
"\n"))))
108
109
;;; Format module documentation JSON as markdown.
110
(define (format-module-docs json)
111
(let ((module-name (or (dict-ref json module: #f) "Unknown"))
112
(description (or (dict-ref json description: #f) ""))
113
(exports (or (dict-ref json exports: #f) '())))
114
(string-append
115
(format "# ~a\n\n" module-name)
116
(if (string-empty? description)
117
""
118
(string-append description "\n\n"))
119
"## Exports\n\n"
120
(string-join
121
(map format-export exports)
122
"\n\n"))))
123
124
;;; Format a single export as markdown.
125
(define (format-export exp)
126
(let ((name (or (dict-ref exp name: #f) "unknown"))
127
(kind (or (dict-ref exp kind: #f) "procedure"))
128
(desc (or (dict-ref exp description: #f) "")))
129
(format "### ~a\n\n~a~a"
130
name
131
(if (equal? kind "syntax") "*syntax*\n\n" "")
132
(if (string-empty? desc) "(No documentation)" desc))))
133
134
;;; Get the first line of a string.
135
(define (first-line str)
136
(let ((lines (string-split str "\n")))
137
(if (null? lines)
138
""
139
(car lines))))
140
141
;; ============================================================
142
;; Project Resources
143
;; ============================================================
144
145
;;; Handle project overview resource.
146
;;; Reads package.sgl and generates a project summary.
147
(define (handle-project-overview uri)
148
(let ((pkg-file (find-package-file)))
149
(if pkg-file
150
(format-project-overview (read-package-sgl pkg-file))
151
"# Project Overview\n\nNo package.sgl found in current directory.")))
152
153
;;; Handle project modules resource.
154
;;; Lists all Sigil modules in the project.
155
(define (handle-project-modules uri)
156
(let ((pkg-file (find-package-file)))
157
(if pkg-file
158
(format-project-modules (path-dirname pkg-file))
159
"# Project Modules\n\nNo package.sgl found in current directory.")))
160
161
;; Find package.sgl in current directory or parents
162
(define (find-package-file)
163
(let ((cwd (current-directory)))
164
(find-package-file-up cwd)))
165
166
(define (find-package-file-up dir)
167
(let ((pkg (path-join dir "package.sgl")))
168
(cond
169
((file-exists? pkg) pkg)
170
((string=? dir "/") #f)
171
(else (find-package-file-up (path-dirname dir))))))
172
173
;; Read and parse package.sgl (basic s-expression reading)
174
(define (read-package-sgl path)
175
(guard (e (else #f))
176
(let ((port (open-input-file path)))
177
(let ((forms (read-all-forms port)))
178
(close-port port)
179
forms))))
180
181
(define (read-all-forms port)
182
(let loop ((forms '()))
183
(let ((form (read port)))
184
(if (eof-object? form)
185
(reverse forms)
186
(loop (cons form forms))))))
187
188
;; Format project overview from package.sgl forms
189
(define (format-project-overview forms)
190
(if (not forms)
191
"# Project Overview\n\nError reading package.sgl."
192
(let* ((pkg-form (find-form 'package forms))
193
(ws-form (find-form 'workspace forms))
194
(name (or (get-prop 'name ws-form) (get-prop 'name pkg-form)))
195
(desc (or (get-prop 'description ws-form) (get-prop 'description pkg-form)))
196
(deps (get-prop 'dependencies pkg-form))
197
(packages-raw (get-prop 'packages ws-form))
198
;; Handle (list "pkg1" "pkg2") form - skip the 'list symbol
199
(packages (if (and packages-raw (pair? packages-raw)
200
(eq? (car packages-raw) 'list))
201
(cdr packages-raw)
202
packages-raw)))
203
(string-append
204
"# Project Overview\n\n"
205
(if name (format "**Name:** ~a\n\n" name) "")
206
(if desc (format "**Description:** ~a\n\n" desc) "")
207
(if packages
208
(format "## Workspace Packages\n\n~a\n\n"
209
(string-join (map (lambda (p) (format "- ~a" p))
210
(filter string? packages))
211
"\n"))
212
"")
213
(if deps
214
(format "## Dependencies\n\n~a\n"
215
(string-join (map (lambda (d) (format "- ~s" d)) deps) "\n"))
216
"")))))
217
218
;; Format project modules listing
219
(define (format-project-modules project-dir)
220
(let ((modules (find-project-modules project-dir)))
221
(string-append
222
"# Project Modules\n\n"
223
(if (null? modules)
224
"No modules found."
225
(string-join
226
(map (lambda (m)
227
(format "- **~a** (`~a`)"
228
(module-name-from-path m project-dir)
229
m))
230
modules)
231
"\n")))))
232
233
;; Find all .sgl files in src/ directories
234
(define (find-project-modules project-dir)
235
(let ((patterns (list
236
(path-join project-dir "src/**/*.sgl")
237
(path-join project-dir "packages/*/src/**/*.sgl"))))
238
(apply append
239
(map (lambda (pat)
240
(guard (e (else '()))
241
(glob pat)))
242
patterns))))
243
244
;; Convert file path to module name
245
;; packages/sigil-foo/src/sigil/bar.sgl -> (sigil bar)
246
;; src/my-app/main.sgl -> (my-app main)
247
(define (module-name-from-path path project-dir)
248
;; Make path relative to project
249
(let* ((rel (if (string-starts-with? path project-dir)
250
(substring path (string-length project-dir) (string-length path))
251
path))
252
(rel (string-trim-left rel "/")))
253
;; Extract module path from src/ directory
254
(let ((src-idx (string-find rel "/src/")))
255
(if src-idx
256
;; Path like packages/sigil-foo/src/sigil/bar.sgl
257
;; Take everything after /src/
258
(let* ((after-src (substring rel (+ src-idx 5) (string-length rel)))
259
(without-ext (string-replace after-src ".sgl" ""))
260
(parts (string-split without-ext "/")))
261
(format "(~a)" (string-join parts " ")))
262
;; Path like src/my-app/main.sgl
263
(let* ((without-ext (string-replace rel ".sgl" ""))
264
(parts (string-split without-ext "/"))
265
;; Skip "src" if present
266
(parts (if (and (pair? parts) (string=? (car parts) "src"))
267
(cdr parts)
268
parts)))
269
(format "(~a)" (string-join parts " ")))))))
270
271
;; Helper to find a form by car
272
(define (find-form name forms)
273
(find (lambda (f)
274
(and (pair? f) (eq? (car f) name)))
275
forms))
276
277
;; Helper to get property from form like (package name: "foo" ...)
278
(define (get-prop key form)
279
(if (not form)
280
#f
281
(let loop ((rest (cdr form)))
282
(cond
283
((null? rest) #f)
284
((and (keyword? (car rest))
285
(eq? (keyword->symbol (car rest)) key)
286
(pair? (cdr rest)))
287
(cadr rest))
288
(else (loop (cdr rest)))))))
289
290
;; Check if x is a keyword
291
(define (keyword? x)
292
(and (symbol? x)
293
(let ((s (symbol->string x)))
294
(and (> (string-length s) 0)
295
(char=? (string-ref s (- (string-length s) 1)) #\:)))))
296
297
;; Convert keyword to symbol
298
(define (keyword->symbol kw)
299
(let ((s (symbol->string kw)))
300
(string->symbol (substring s 0 (- (string-length s) 1)))))
301
302
;; Find first element matching predicate
303
(define (find pred lst)
304
(cond
305
((null? lst) #f)
306
((pred (car lst)) (car lst))
307
(else (find pred (cdr lst)))))
308
309
;; Trim leading character from string
310
(define (string-trim-left str char)
311
(if (and (> (string-length str) 0)
312
(char=? (string-ref str 0) (if (char? char) char (string-ref char 0))))
313
(string-trim-left (substring str 1 (string-length str)) char)
314
str))
315
316
;; ============================================================
317
;; Registration
318
;; ============================================================
319
320
;;; Register all resources with an MCP server.
321
(define (register-resources! server register-resource!)
322
;; Main index
323
(register-resource! server
324
"sigil://index"
325
"Documentation Index"
326
"Entry point for Sigil documentation"
327
handle-index)
328
329
;; Language reference (wildcard)
330
(register-resource! server
331
"sigil://language/*"
332
"Language Reference"
333
"Sigil language syntax and semantics"
334
handle-language)
335
336
;; Style guide (wildcard)
337
(register-resource! server
338
"sigil://style/*"
339
"Style Guide"
340
"Code style conventions and best practices"
341
handle-style)
342
343
;; Stdlib index
344
(register-resource! server
345
"sigil://stdlib/index"
346
"Standard Library Index"
347
"Overview of all standard library modules"
348
handle-stdlib-index)
349
350
;; Stdlib modules (wildcard)
351
(register-resource! server
352
"sigil://stdlib/*"
353
"Standard Library Module"
354
"Documentation for a standard library module"
355
handle-stdlib-module)
356
357
;; Project overview
358
(register-resource! server
359
"sigil://project/overview"
360
"Project Overview"
361
"Current project structure, dependencies, and configuration"
362
handle-project-overview)
363
364
;; Project modules
365
(register-resource! server
366
"sigil://project/modules"
367
"Project Modules"
368
"List of modules in the current project with file paths"
369
handle-project-modules))))
src/sigil/mcp/tools.sgldeleted
@@ -1,832 +0,0 @@
1
;;; (sigil mcp tools) - MCP tool implementations.
2
;;;
3
;;; Provides the core tools for the Sigil MCP server:
4
;;; sigil/eval, sigil/format, sigil/lookup, sigil/search, sigil/exports,
5
;;; sigil/suggest-import, sigil/project-info, sigil/run-file, sigil/lint,
6
;;; sigil/reload.
7
8
(define-library (sigil mcp tools)
9
(import (sigil json)
10
(sigil string)
11
(sigil io)
12
(sigil fs)
13
(sigil path)
14
(sigil format)
15
(sigil inspect)
16
(sigil error)
17
(sigil diagnostic)
18
(sigil spec)
19
(sigil docs search)
20
(sigil docs lookup)
21
(sigil docs types)
22
(sigil docs packages)
23
(sigil package)
24
(sigil process)
25
(sigil mcp lint))
26
(export register-tools!
27
28
;; Tool schemas for reference
29
eval-tool-schema
30
format-tool-schema
31
lookup-tool-schema
32
search-tool-schema
33
exports-tool-schema
34
check-tool-schema
35
test-tool-schema
36
list-package-docs-tool-schema
37
read-package-doc-tool-schema
38
suggest-import-tool-schema
39
project-info-tool-schema
40
run-file-tool-schema
41
lint-tool-schema
42
reload-tool-schema
43
44
;; Tool handlers (for testing/custom use)
45
tool-eval
46
tool-format
47
tool-lookup
48
tool-search
49
tool-exports
50
tool-check
51
tool-test
52
tool-list-package-docs
53
tool-read-package-doc
54
tool-suggest-import
55
tool-project-info
56
tool-run-file
57
tool-lint
58
tool-reload)
59
(begin
60
61
;; ============================================================
62
;; Tool Schemas
63
;; ============================================================
64
65
(define eval-tool-schema
66
'((type . "object")
67
(properties . ((expression . ((type . "string")
68
(description . "Sigil expression to evaluate")))
69
(module . ((type . "string")
70
(description . "Optional module context for imports")))))
71
(required . ("expression"))))
72
73
(define format-tool-schema
74
'((type . "object")
75
(properties . ((code . ((type . "string")
76
(description . "Sigil code to format")))
77
(file . ((type . "string")
78
(description . "Path to file to format (alternative to code)")))))))
79
80
(define lookup-tool-schema
81
'((type . "object")
82
(properties . ((name . ((type . "string")
83
(description . "Name to look up (procedure, syntax, or module)")))))
84
(required . ("name"))))
85
86
(define search-tool-schema
87
'((type . "object")
88
(properties . ((query . ((type . "string")
89
(description . "Search query (name pattern or keywords)")))
90
(module . ((type . "string")
91
(description . "Limit search to specific module")))
92
(type . ((type . "string")
93
(enum . ("procedure" "syntax" "variable" "all"))
94
(description . "Type of binding to search for")))))
95
(required . ("query"))))
96
97
(define exports-tool-schema
98
'((type . "object")
99
(properties . ((module . ((type . "string")
100
(description . "Module name, e.g. '(sigil json)'")))))
101
(required . ("module"))))
102
103
(define check-tool-schema
104
'((type . "object")
105
(properties . ((code . ((type . "string")
106
(description . "Sigil code to check for syntax errors")))
107
(file . ((type . "string")
108
(description . "Path to file to check (alternative to code)")))))
109
(required . ())))
110
111
(define test-tool-schema
112
'((type . "object")
113
(properties . ((pattern . ((type . "string")
114
(description . "Test name pattern to match (optional)")))
115
(file . ((type . "string")
116
(description . "Specific test file to run (optional)")))))
117
(required . ())))
118
119
(define list-package-docs-tool-schema
120
'((type . "object")
121
(properties . ((package . ((type . "string")
122
(description . "Filter to specific package (optional)")))))
123
(required . ())))
124
125
(define read-package-doc-tool-schema
126
'((type . "object")
127
(properties . ((package . ((type . "string")
128
(description . "Package name")))
129
(file . ((type . "string")
130
(description . "Documentation file name (e.g., 'routing.md')")))))
131
(required . ("package" "file"))))
132
133
(define suggest-import-tool-schema
134
'((type . "object")
135
(properties . ((symbol . ((type . "string")
136
(description . "Symbol name to find the module for")))))
137
(required . ("symbol"))))
138
139
(define project-info-tool-schema
140
'((type . "object")
141
(properties . ((dir . ((type . "string")
142
(description . "Project directory (defaults to current directory)")))))
143
(required . ())))
144
145
(define run-file-tool-schema
146
'((type . "object")
147
(properties . ((file . ((type . "string")
148
(description . "Path to Sigil file to run")))
149
(args . ((type . "array")
150
(items . ((type . "string")))
151
(description . "Arguments to pass to the script")))))
152
(required . ("file"))))
153
154
(define lint-tool-schema
155
'((type . "object")
156
(properties . ((code . ((type . "string")
157
(description . "Sigil code to lint")))
158
(file . ((type . "string")
159
(description . "Path to file to lint (alternative to code)")))
160
(checks . ((type . "array")
161
(items . ((type . "string")))
162
(description . "Checks to run (default: all). Available: unused-imports")))))
163
(required . ())))
164
165
(define reload-tool-schema
166
'((type . "object")
167
(properties . ((module . ((type . "string")
168
(description . "Module name to reload, e.g. '(sigil json)'")))))
169
(required . ("module"))))
170
171
;; ============================================================
172
;; Tool Implementations
173
;; ============================================================
174
175
;;; Evaluate a Sigil expression and return the result.
176
(define (tool-eval args)
177
(let ((expr (dict-ref args expression: #f))
178
(module-str (dict-ref args module: #f)))
179
(guard (e (else
180
(json-encode (inspect-error e))))
181
(let ((result (if module-str
182
(eval-string-in-module
183
(string-append "(import " module-str ") " expr)
184
(read (open-input-string module-str)))
185
(eval-string expr))))
186
(json-encode (inspect result))))))
187
188
;;; Format Sigil source code and fix paren issues.
189
(define (tool-format args)
190
(let ((code (dict-ref args code: #f))
191
(file (dict-ref args file: #f)))
192
(guard (e (else
193
(format "Format error: ~a" (exception-message e))))
194
(let ((result (cond
195
(file (format-file file))
196
(code (format-string code "<input>"))
197
(else (error "Provide 'code' or 'file'")))))
198
(let ((output (format-result-output result))
199
(success (format-result-success result)))
200
;; Auto-fix: write output back to file when fixes are available
201
(when (and file output success)
202
(call-with-output-file file
203
(lambda (port) (display output port))))
204
(string-append
205
(format-result->string result)
206
(if output
207
(if file
208
(format "\nFixed file: ~a" file)
209
(string-append "\nFormatted output:\n" output))
210
"")))))))
211
212
;;; Look up documentation for a name.
213
(define (tool-lookup args)
214
(let ((name (dict-ref args name: #f)))
215
(guard (e (else
216
(format "Lookup error: ~a" (exception-message e))))
217
(let ((sym (string->symbol name)))
218
;; Try to find the symbol in the index
219
(let ((results (search-docs name)))
220
(if (null? results)
221
(format "No documentation found for '~a'." name)
222
;; Get detailed docs for first exact match
223
(let* ((result (find-exact-match name results))
224
(mod-name (search-result-module result)))
225
(if mod-name
226
(let ((details (load-module-details (read-module-name mod-name))))
227
(if details
228
(format-export-doc name details)
229
(format-search-result result)))
230
(format-search-result result)))))))))
231
232
;; Find exact name match in results
233
(define (find-exact-match name results)
234
(or (find (lambda (r) (string=? (search-result-name r) name)) results)
235
(car results)))
236
237
;; Parse module name string to list
238
(define (read-module-name str)
239
(if (string? str)
240
(read (open-input-string str))
241
str))
242
243
;; Format a search result as output
244
(define (format-search-result result)
245
(format "~a ~a\n ~a: ~a"
246
(search-result-name result)
247
(search-result-module result)
248
(search-result-kind result)
249
(search-result-summary result)))
250
251
;; Try to get doc from runtime procedure metadata
252
(define (runtime-doc-fallback module-name-str export-name)
253
(guard (e (else #f))
254
(let* ((mod-name (read (open-input-string module-name-str)))
255
(mod (find-module mod-name)))
256
(if (not mod) #f
257
(let ((val (module-ref mod (string->symbol export-name))))
258
(if (not (procedure? val)) #f
259
(let ((meta (procedure-metadata val)))
260
(if (not meta) #f
261
(let ((doc-pair (assq 'doc meta)))
262
(if doc-pair (cdr doc-pair) #f))))))))))
263
264
;; Try to get spec from runtime procedure metadata
265
(define (runtime-spec-fallback module-name-str export-name)
266
(guard (e (else #f))
267
(let* ((mod-name (read (open-input-string module-name-str)))
268
(mod (find-module mod-name)))
269
(if (not mod) #f
270
(let ((val (module-ref mod (string->symbol export-name))))
271
(if (not (procedure? val)) #f
272
(spec->string (procedure-spec val))))))))
273
274
;; Look up docs from the source module's JSON
275
(define (source-module-doc name source-mod-str)
276
(guard (e (else #f))
277
(let* ((mod-name (read (open-input-string source-mod-str)))
278
(details (load-module-details mod-name)))
279
(if (not details) #f
280
(let ((exp (find (lambda (e)
281
(string=? (symbol->string (export-details-name e)) name))
282
(module-details-exports details))))
283
(if exp (export-details-description exp) #f))))))
284
285
;; Format detailed export documentation
286
(define (format-export-doc name details)
287
(let ((export (find (lambda (e)
288
(string=? (symbol->string (export-details-name e)) name))
289
(module-details-exports details))))
290
(if export
291
(let* ((mod-name-str (format "~a" (module-details-name details)))
292
(desc (or (export-details-description export)
293
(and (export-details-source-module export)
294
(source-module-doc name (export-details-source-module export)))
295
(runtime-doc-fallback mod-name-str name)
296
"No description available."))
297
(spec-str (runtime-spec-fallback mod-name-str name)))
298
(if spec-str
299
(format "~a ~a\nSpec: ~a\n\n~a"
300
name mod-name-str spec-str desc)
301
(format "~a ~a\n\n~a"
302
name mod-name-str desc)))
303
(format "Export '~a' not found in module." name))))
304
305
;;; Search for procedures by name or description.
306
(define (tool-search args)
307
(let ((query (dict-ref args query: #f))
308
(module-filter (dict-ref args module: #f))
309
(type-filter (dict-ref args type: #f)))
310
(guard (e (else
311
(format "Search error: ~a" (exception-message e))))
312
(let* ((results (search-docs query))
313
(filtered (filter-results results module-filter type-filter)))
314
(if (null? filtered)
315
(format "No results found for '~a'." query)
316
(format-search-results filtered))))))
317
318
;; Filter results by module and type
319
(define (filter-results results module-filter type-filter)
320
(filter (lambda (r)
321
(and (or (not module-filter)
322
(string=? (search-result-module r) module-filter))
323
(or (not type-filter)
324
(string=? type-filter "all")
325
(string=? (search-result-kind r) type-filter))))
326
results))
327
328
;; Format multiple search results
329
(define (format-search-results results)
330
(string-join
331
(map (lambda (r)
332
(let ((spec-str (runtime-spec-fallback
333
(search-result-module r)
334
(search-result-name r))))
335
(if spec-str
336
(format "~a ~a (~a)\n Spec: ~a\n ~a"
337
(search-result-name r)
338
(search-result-module r)
339
(search-result-kind r)
340
spec-str
341
(search-result-summary r))
342
(format "~a ~a (~a)\n ~a"
343
(search-result-name r)
344
(search-result-module r)
345
(search-result-kind r)
346
(search-result-summary r)))))
347
results)
348
"\n\n"))
349
350
;;; List exports from a module.
351
(define (tool-exports args)
352
(let ((module-str (dict-ref args module: #f)))
353
(guard (e (else
354
(format "Exports error: ~a" (exception-message e))))
355
(let ((exports (get-module-exports module-str)))
356
(if (null? exports)
357
(format "No exports found for module ~a." module-str)
358
(format-exports-list module-str exports))))))
359
360
;; Format exports list
361
(define (format-exports-list module-str exports)
362
(string-append
363
(format "Exports from ~a:\n\n" module-str)
364
(string-join
365
(map (lambda (r)
366
(let ((spec-str (runtime-spec-fallback
367
module-str
368
(search-result-name r))))
369
(if spec-str
370
(format " ~a (~a)\n Spec: ~a\n ~a"
371
(search-result-name r)
372
(search-result-kind r)
373
spec-str
374
(search-result-summary r))
375
(format " ~a (~a) - ~a"
376
(search-result-name r)
377
(search-result-kind r)
378
(search-result-summary r)))))
379
exports)
380
"\n")))
381
382
;;; Check Sigil code for syntax errors.
383
(define (tool-check args)
384
(let ((code (dict-ref args code: #f))
385
(file (dict-ref args file: #f)))
386
(guard (e (else
387
(format "Syntax error: ~a" (exception-message e))))
388
(cond
389
;; Check code string
390
(code
391
(check-code-string code))
392
;; Check file
393
(file
394
(if (file-exists? file)
395
(check-code-string (read-file-string file))
396
(format "File not found: ~a" file)))
397
(else
398
"Error: Either 'code' or 'file' must be provided.")))))
399
400
;; Check code string for syntax errors
401
;; Note: Read/parse errors are hard VM errors, not catchable exceptions.
402
;; When called via MCP, these will be returned as JSON-RPC error responses.
403
(define (check-code-string code)
404
(guard (e (else
405
(format "Syntax error: ~a" (exception-message e))))
406
;; Try to read all expressions - catches runtime errors but not parse errors
407
(let ((port (open-input-string code)))
408
(let loop ((count 0))
409
(let ((expr (read port)))
410
(if (eof-object? expr)
411
(format "OK: ~a expression~a parsed successfully."
412
count
413
(if (= count 1) "" "s"))
414
(loop (+ count 1))))))))
415
416
;;; Run Sigil tests.
417
;;; Note: This tool provides basic test running capability.
418
;;; For full test functionality, use the CLI `sigil test` command.
419
;; Run `sigil test` as a subprocess. Loading test files directly into
420
;; the MCP server process proved fragile: a single test with an infinite
421
;; loop, a blocking I/O call, or an import that tries to initialize heavy
422
;; state would lock up the server for every client. Delegating to the
423
;; `sigil test` CLI keeps the test runner in its own process — if it
424
;; hangs or crashes, the MCP server stays responsive for the next call.
425
(define (tool-test args)
426
(let ((pattern (dict-ref args pattern: #f))
427
(file (dict-ref args file: #f)))
428
(guard (e (else
429
(format "Test error: ~a" (exception-message e))))
430
(let* ((sigil-bin (car (command-line)))
431
(cli-args (append
432
(list "test" "--quiet" "--no-color")
433
(if pattern (list "--filter" pattern) '())
434
(if file (list file) '())))
435
(proc (apply process-spawn sigil-bin cli-args))
436
(stdout-str (read-all-string (process-stdout proc)))
437
(stderr-str (read-all-string (process-stderr proc)))
438
(exit-code (process-wait proc)))
439
(string-append
440
(format "Exit code: ~a\n" exit-code)
441
(if (string-empty? stdout-str)
442
""
443
(format "\n--- stdout ---\n~a" stdout-str))
444
(if (string-empty? stderr-str)
445
""
446
(format "\n--- stderr ---\n~a" stderr-str)))))))
447
448
;; ============================================================
449
;; Package Documentation Tools
450
;; ============================================================
451
452
;;; List all package documentation with summaries.
453
(define (tool-list-package-docs args)
454
(let ((package-filter (dict-ref args package: #f)))
455
(guard (e (else
456
(format "Error listing package docs: ~a" (exception-message e))))
457
(if package-filter
458
;; List docs for specific package
459
(let ((docs (list-package-docs package-filter)))
460
(if (null? docs)
461
(format "No documentation found for package '~a'." package-filter)
462
(format-package-docs-list package-filter docs)))
463
;; List all packages and their docs
464
(let ((packages (list-packages)))
465
(if (null? packages)
466
"No package documentation found."
467
(format-all-packages-docs packages)))))))
468
469
;; Format docs for a single package
470
(define (format-package-docs-list package-name docs)
471
(let ((pkg-info (package-exists? package-name)))
472
(string-append
473
(format "# ~a (~a)\n\n" package-name (if pkg-info (cdr pkg-info) "?"))
474
(string-join
475
(map (lambda (doc)
476
(format "- **~a**: ~a" (car doc) (cdr doc)))
477
docs)
478
"\n"))))
479
480
;; Format docs for all packages
481
(define (format-all-packages-docs packages)
482
(string-join
483
(map (lambda (pkg)
484
(let* ((pkg-name (car pkg))
485
(pkg-version (cdr pkg))
486
(docs (list-package-docs pkg-name)))
487
(string-append
488
(format "## ~a (~a)\n" pkg-name pkg-version)
489
(if (null? docs)
490
" No documentation files.\n"
491
(string-join
492
(map (lambda (doc)
493
(format "- **~a**: ~a" (car doc) (cdr doc)))
494
docs)
495
"\n")))))
496
packages)
497
"\n\n"))
498
499
;;; Read a specific package documentation file.

Showing the first 500 of 834 diff lines for this file. This diff is INCOMPLETE; read the file or clone the repository for the rest.

test/test-nrepl-tools.sgldeleted
@@ -1,24 +0,0 @@
1
(import (sigil test)
2
(sigil string)
3
(sigil json)
4
(sigil mcp nrepl)
5
(sigil mcp server))
6
7
;; ============================================================
8
;; Registration
9
;; ============================================================
10
11
(test-group "register-nrepl-tools!"
12
(test "registers all nREPL tools"
13
(let ((server (mcp-server name: "test" version: "0.1.0"))
14
(tools '()))
15
(register-nrepl-tools! server
16
(lambda (srv name desc schema handler)
17
(set! tools (cons name tools))))
18
(assert-true (member "sigil/nrepl-connect" tools))
19
(assert-true (member "sigil/nrepl-eval" tools))
20
(assert-true (member "sigil/nrepl-status" tools))
21
(assert-true (member "sigil/nrepl-disconnect" tools))
22
(assert-equal 4 (length tools)))))
23
24
(run-tests)
test/test-tools.sgldeleted
@@ -1,65 +0,0 @@
1
(import (sigil test)
2
(sigil json)
3
(sigil string)
4
(sigil mcp tools)
5
(sigil mcp lint))
6
7
;; ============================================================
8
;; sigil/suggest-import
9
;; ============================================================
10
11
(test-group "suggest-import"
12
(test "known symbol returns module"
13
(let ((result (tool-suggest-import (dict symbol: "string-split"))))
14
;; If docs index is loaded, we get module info; if not, we get a "No module" message.
15
;; Both are valid outputs depending on whether docs have been built.
16
(assert-true (or (string-find result "(sigil string)")
17
(string-find result "No module found")))))
18
19
(test "unknown symbol returns not found"
20
(let ((result (tool-suggest-import (dict symbol: "zzz-nonexistent-zzz"))))
21
(assert-true (string-find result "No module found")))))
22
23
;; ============================================================
24
;; sigil/project-info
25
;; ============================================================
26
27
(test-group "project-info"
28
(test "finds sigil workspace"
29
(let ((result (tool-project-info (dict))))
30
(assert-true (string-find result "Workspace:"))
31
(assert-true (string-find result "sigil")))))
32
33
;; ============================================================
34
;; sigil/run-file
35
;; ============================================================
36
37
(test-group "run-file"
38
(test "missing file returns error"
39
(let ((result (tool-run-file (dict file: "/tmp/nonexistent-sigil-test.sgl"))))
40
(assert-true (string-find result "File not found")))))
41
42
;; ============================================================
43
;; sigil/lint
44
;; ============================================================
45
46
(test-group "lint"
47
(test "clean code produces no warnings"
48
(let ((result (tool-lint (dict code: "(define (f x) (+ x 1))"))))
49
(assert-equal "No issues found." result)))
50
51
(test "lint-code returns list of warnings"
52
(let ((warnings (lint-code "(define (f x) (+ x 1))" '("all"))))
53
(assert-true (list? warnings))
54
(assert-equal 0 (length warnings)))))
55
56
;; ============================================================
57
;; sigil/reload
58
;; ============================================================
59
60
(test-group "reload"
61
(test "reload sigil core succeeds"
62
(let ((result (tool-reload (dict module: "(sigil core)"))))
63
(assert-true (string-find result "reloaded successfully")))))
64
65
(run-tests)