[OCaml] High Intensity Training Online
chore rework FASOC into FRACAS with dedicated MCP tools
Rename the skill to FRACAS (Feature Requests As a Constant Agent-monitored Stream). Add fracas-tools.el, which registers dedicated Emacs MCP tools for capture, status, completion, refile, and verification. The generic eval-elisp tool routes code through the Emacs security form walker, which prompts for each file function. A registered MCP tool runs through the tool dispatch path, which does not use the walker, so the operations run without repeated prompts. Read-only tools carry a readOnlyHint annotation for client auto-approval. Update the vocabulary from ENHANCEMENTS.org to REQUESTS.org.
Changed files
.kiro/skills/fasoc/SKILL.md
@@ -1,233 +0,0 @@
1
Removed:
---
2
Removed:
name: fasoc
3
Removed:
description: Capture each feature request in ENHANCEMENTS.org with Emacs MCP. FASOC means Feature as Stream of Consciousness. Never implement requests.
4
Removed:
---
5
Removed:
6
Removed:
# FASOC — Feature as Stream of Consciousness
7
Removed:
8
Removed:
Use this skill when the user asks to capture a feature request or track a
9
Removed:
feature in `ENHANCEMENTS.org`.
10
Removed:
11
Removed:
Keep the session open across prompts. Treat each prompt as a separate feature
12
Removed:
request. Capture each request under `* Dispatch`. Do not merge requests. Do not
13
Removed:
build, test, commit, or start an agent.
14
Removed:
15
Removed:
The current prompt is the feature request:
16
Removed:
17
Removed:
$ARGUMENTS
18
Removed:
19
Removed:
If the prompt is empty, ask for one line that describes the feature
20
Removed:
request. Then stop. Capture the next prompt as the next request.
21
Removed:
22
Removed:
## Use the Emacs MCP server
23
Removed:
24
Removed:
Use the Emacs Model Context Protocol (MCP) server for all Org mode
25
Removed:
operations. Do not edit `ENHANCEMENTS.org` as raw text. Do not use
26
Removed:
shell or file tools to capture, refile, change status, or verify an
27
Removed:
entry.
28
Removed:
29
Removed:
Use these Emacs MCP tools:
30
Removed:
31
Removed:
- Use `org-get-node` or `org-search` to inspect headings, keywords,
32
Removed:
and content.
33
Removed:
- Use `org-list-templates` and `org-capture` to inspect templates and
34
Removed:
create the `Dispatch` heading when needed.
35
Removed:
- Use the Emacs MCP `eval-elisp` tool to run the required code
36
Removed:
snippets below.
37
Removed:
- Use `org-get-node` after each change to verify the result.
38
Removed:
39
Removed:
Use the file and outline path that the Emacs MCP server returns. If an
40
Removed:
Emacs MCP operation cannot perform the action, stop and report the
41
Removed:
problem. Do not replace the operation with a raw file edit.
42
Removed:
43
Removed:
## Required Emacs code
44
Removed:
45
Removed:
Run each snippet in this section with the Emacs MCP `eval-elisp`
46
Removed:
tool. Replace `ID`, `TITLE`, `BODY`, and `TARGET` with values that you
47
Removed:
observed through the Emacs MCP server. Run the inspection snippet
48
Removed:
before you change the file. Run the capture, status, completion, and
49
Removed:
refile snippets when needed. Run the verification expression after
50
Removed:
each change.
51
Removed:
52
Removed:
Inspect the file and its TODO sequence with this expression:
53
Removed:
54
Removed:
```elisp
55
Removed:
(let ((file (expand-file-name "ENHANCEMENTS.org" default-directory)))
56
Removed:
(with-current-buffer (find-file-noselect file)
57
Removed:
(org-mode)
58
Removed:
(org-with-wide-buffer
59
Removed:
(list :todo-keywords org-todo-keywords-1
60
Removed:
:dispatch (org-find-exact-headline-in-buffer "Dispatch")))))
61
Removed:
```
62
Removed:
63
Removed:
Run this expression to capture an entry under `Dispatch`. Create
64
Removed:
`Dispatch` with the Emacs MCP `org-capture` tool first if it does not
65
Removed:
exist.
66
Removed:
67
Removed:
```elisp
68
Removed:
(let* ((file (expand-file-name "ENHANCEMENTS.org" default-directory))
69
Removed:
(title "TITLE")
70
Removed:
(body "BODY")
71
Removed:
(captured-at (format-time-string "[%Y-%m-%d %a %H:%M]"))
72
Removed:
(org-capture-templates
73
Removed:
`(("e" "Enhancement" entry
74
Removed:
(file+headline ,file "Dispatch")
75
Removed:
"%i")))
76
Removed:
(org-capture-string
77
Removed:
(format "** TODO %s\nSCHEDULED: %s\n%s\n" title captured-at body)
78
Removed:
"e")
79
Removed:
(org-capture-finalize))
80
Removed:
```
81
Removed:
82
Removed:
Run this expression to change an open entry's keyword. Replace `STATE`
83
Removed:
with one keyword from the observed `#+TODO:` sequence.
84
Removed:
85
Removed:
```elisp
86
Removed:
(let ((file (expand-file-name "ENHANCEMENTS.org" default-directory))
87
Removed:
(id "ID")
88
Removed:
(state "STATE"))
89
Removed:
(with-current-buffer (find-file-noselect file)
90
Removed:
(org-mode)
91
Removed:
(let ((marker (org-id-find id 'marker)))
92
Removed:
(unless marker
93
Removed:
(user-error "Org ID not found: %s" id))
94
Removed:
(goto-char marker))
95
Removed:
(org-todo state)
96
Removed:
(save-buffer)))
97
Removed:
```
98
Removed:
99
Removed:
Run this expression to set `DONE` and verify the completion
100
Removed:
timestamp. Do not insert a `CLOSED` line yourself. The normal Org TODO
101
Removed:
transition must insert it.
102
Removed:
103
Removed:
```elisp
104
Removed:
(let ((file (expand-file-name "ENHANCEMENTS.org" default-directory))
105
Removed:
(id "ID"))
106
Removed:
(with-current-buffer (find-file-noselect file)
107
Removed:
(org-mode)
108
Removed:
(hack-local-variables)
109
Removed:
(unless (eq org-log-done 'time)
110
Removed:
(user-error "org-log-done is not set to time"))
111
Removed:
(let ((marker (org-id-find id 'marker)))
112
Removed:
(unless marker
113
Removed:
(user-error "Org ID not found: %s" id))
114
Removed:
(goto-char marker))
115
Removed:
(org-todo "DONE")
116
Removed:
(save-buffer)
117
Removed:
(unless (org-entry-get nil "CLOSED")
118
Removed:
(user-error "Org did not record a CLOSED timestamp"))
119
Removed:
(org-entry-get nil "CLOSED")))
120
Removed:
```
121
Removed:
122
Removed:
Run this expression to refile the completed entry under a top-level
123
Removed:
section. Replace `TARGET` with the observed top-level heading name.
124
Removed:
125
Removed:
```elisp
126
Removed:
(let ((file (expand-file-name "ENHANCEMENTS.org" default-directory))
127
Removed:
(id "ID")
128
Removed:
(target "TARGET"))
129
Removed:
(with-current-buffer (find-file-noselect file)
130
Removed:
(org-mode)
131
Removed:
(let ((marker (org-id-find id 'marker)))
132
Removed:
(unless marker
133
Removed:
(user-error "Org ID not found: %s" id))
134
Removed:
(goto-char marker))
135
Removed:
(let ((source-position (point))
136
Removed:
(target-position
137
Removed:
(with-current-buffer (find-file-noselect file)
138
Removed:
(org-mode)
139
Removed:
(goto-char (point-min))
140
Removed:
(let ((target-marker
141
Removed:
(org-find-exact-headline-in-buffer target)))
142
Removed:
(unless target-marker
143
Removed:
(user-error "Org target not found: %s" target))
144
Removed:
(marker-position target-marker)))))
145
Removed:
(goto-char source-position)
146
Removed:
(org-refile nil nil (list target file nil target-position))
147
Removed:
(save-buffer))))
148
Removed:
```
149
Removed:
150
Removed:
Run this expression to verify the entry after a mutation:
151
Removed:
152
Removed:
```elisp
153
Removed:
(let ((file (expand-file-name "ENHANCEMENTS.org" default-directory))
154
Removed:
(id "ID"))
155
Removed:
(with-current-buffer (find-file-noselect file)
156
Removed:
(org-mode)
157
Removed:
(let ((marker (org-id-find id 'marker)))
158
Removed:
(unless marker
159
Removed:
(user-error "Org ID not found: %s" id))
160
Removed:
(goto-char marker))
161
Removed:
(list (org-get-heading t t t t)
162
Removed:
(org-get-todo-state)
163
Removed:
(org-entry-get nil "CLOSED")
164
Removed:
(org-get-outline-path))))
165
Removed:
```
166
Removed:
167
Removed:
## Step 1 — Capture under Dispatch
168
Removed:
169
Removed:
Inspect `ENHANCEMENTS.org` with `org-search` or `org-get-node` before
170
Removed:
writing. Find the top-level `* Dispatch` heading. If it does not
171
Removed:
exist, create it at the top of the file.
172
Removed:
173
Removed:
Add the request as a level-2 heading under `* Dispatch`. Run
174
Removed:
`org-list-templates` first. Select a matching `org-capture`
175
Removed:
template. If no template matches, use `org-capture` direct mode with
176
Removed:
the file, title, outline path, and body. Then run the capture snippet.
177
Removed:
178
Removed:
Create this entry:
179
Removed:
180
Removed:
```
181
Removed:
** TODO <imperative title under 60 chars>
182
Removed:
SCHEDULED: [YYYY-MM-DD Day HH:MM]
183
Removed:
<the full idea text, formatted as a list if text contains separate ideas>
184
Removed:
```
185
Removed:
186
Removed:
The inactive timestamp records the capture time. Use a short imperative
187
Removed:
title. Keep the full request text unchanged. Do not rewrite it. Keep
188
Removed:
the file well-formed. Verify the new heading with `org-get-node`. Report
189
Removed:
its outline path.
190
Removed:
191
Removed:
## Step 2 — Refile on completion
192
Removed:
193
Removed:
Keep the request under `* Dispatch` while its status is open,
194
Removed:
`BLOCKED`, or `CANCELLED`. When its status becomes `DONE`, move the
195
Removed:
entire heading out of `* Dispatch`.
196
Removed:
197
Removed:
Inspect the top-level (`*`) headings with `org-search` or
198
Removed:
`org-get-node`. Select the section that best matches the request. If
199
Removed:
no section matches, create a short, consistent top-level section with
200
Removed:
`org-capture` through the Emacs MCP server.
201
Removed:
202
Removed:
Run the refile snippet. The snippet calls `org-refile`. Verify the new
203
Removed:
location with `org-get-node`. Report the new outline path.
204
Removed:
205
Removed:
## Step 3 — Track the status
206
Removed:
207
Removed:
Read the file's `#+TODO:` header and the current heading with
208
Removed:
`org-get-node` before changing a keyword. Use the keyword sequence in
209
Removed:
the header. Run the status snippet for every status change. The
210
Removed:
snippet calls `org-todo`. Do not make another status change after the
211
Removed:
snippet.
212
Removed:
213
Removed:
- `TODO` — Capture the request. Do not start work.
214
Removed:
- `IN-PROGRESS` — Start work.
215
Removed:
- `TESTING` — Run tests. The tests do not pass.
216
Removed:
- `TESTED` — Tests pass. No commit exists.
217
Removed:
- `DONE` — The build passes. Tests pass. The files have the required
218
Removed:
format. A commit exists. Run the completion snippet. The snippet
219
Removed:
sets `DONE`, uses the configured `org-log-done` behavior, and
220
Removed:
verifies the completion timestamp. Do not add or edit the timestamp
221
Removed:
manually. Refile the heading under Step 2.
222
Removed:
- `BLOCKED` — Work cannot continue. Mention the cause of the block.
223
Removed:
- `CANCELLED` — Stop work intentionally.
224
Removed:
225
Removed:
Change the keyword only after you observe the current status. Do not
226
Removed:
set `DONE` unless you observe a commit and passing tests. After each
227
Removed:
status snippet, use `org-get-node` to verify the keyword. When the
228
Removed:
keyword becomes `DONE`, verify the completion timestamp before you
229
Removed:
refile the heading. Let Org's configured completion logging add the
230
Removed:
timestamp. Add no other field.
231
Removed:
232
Removed:
Record every prompt as its own request. If you cannot complete a
233
Removed:
request, record the request and the failure.
.kiro/skills/fracas/SKILL.md
@@ -0,0 +1,157 @@
1
Added:
---
2
Added:
name: fracas
3
Added:
description: Manage feature requests with Emacs MCP tools.
4
Added:
---
5
Added:
6
Added:
# FRACAS — Feature Requests As a Constant Agent-monitored Stream
7
Added:
8
Added:
Use this skill when the user asks to capture feature requests or to
9
Added:
track a feature in `REQUESTS.org`.
10
Added:
11
Added:
Keep the session open across prompts. Treat each prompt as a separate
12
Added:
feature request. Capture each request under `* Dispatch`. Do not merge
13
Added:
requests. Do not build, test, commit, or start an agent.
14
Added:
15
Added:
The current prompt is the feature request:
16
Added:
17
Added:
$ARGUMENTS
18
Added:
19
Added:
If the prompt is empty, ask for one line that describes the feature
20
Added:
request. Then stop. Capture the next prompt as the next request.
21
Added:
22
Added:
## Use the dedicated FRACAS MCP tools
23
Added:
24
Added:
This skill provides dedicated Emacs Model Context Protocol (MCP)
25
Added:
tools. Use them for every interaction with `REQUESTS.org`. Do not edit
26
Added:
the file as raw text. Do not use shell or file tools to capture,
27
Added:
refile, change status, or verify an entry.
28
Added:
29
Added:
The dedicated tools avoid a security prompt. The generic `eval-elisp`
30
Added:
tool passes its code through the Emacs security form walker. The
31
Added:
walker prompts for each file function, such as `find-file-noselect`
32
Added:
and `save-buffer`. The FRACAS tools register as normal MCP tools. They
33
Added:
run through the tool dispatch path, which does not use the walker. The
34
Added:
read-only tools also carry a `readOnlyHint` annotation. The MCP client
35
Added:
can approve a read-only tool without a prompt.
36
Added:
37
Added:
Each FRACAS tool takes a `root` argument. Set `root` to the absolute
38
Added:
path of the project directory that contains `REQUESTS.org`. The tools
39
Added:
operate only on that one file.
40
Added:
41
Added:
### Install the tools once per session
42
Added:
43
Added:
Load the tool file one time in the running Emacs before you use the
44
Added:
tools. Run this with the Emacs MCP `eval-elisp` tool:
45
Added:
46
Added:
```elisp
47
Added:
(load "/home/blendux/git/hito/.kiro/skills/fracas/fracas-tools.el" nil t)
48
Added:
```
49
Added:
50
Added:
Confirm the tools are present. This expression returns the six FRACAS
51
Added:
tool names:
52
Added:
53
Added:
```elisp
54
Added:
(seq-filter (lambda (name) (string-prefix-p "fracas-" name))
55
Added:
(mapcar (lambda (tool) (alist-get 'name tool))
56
Added:
(mcp-server-tools-list)))
57
Added:
```
58
Added:
59
Added:
If the load fails, stop and report the problem. Do not fall back to
60
Added:
raw file edits.
61
Added:
62
Added:
### The FRACAS tools
63
Added:
64
Added:
- `fracas-inspect` — return the file's TODO keyword sequence and
65
Added:
whether a `Dispatch` heading exists. Read-only.
66
Added:
- `fracas-capture` — create a TODO request under `Dispatch`. Records
67
Added:
the capture time as an inactive `SCHEDULED` timestamp and assigns an
68
Added:
Org ID.
69
Added:
- `fracas-set-status` — set the TODO keyword of an entry. Rejects
70
Added:
`DONE`.
71
Added:
- `fracas-complete` — set an entry to `DONE`. Confirms Org inserted
72
Added:
the `CLOSED` timestamp. Never writes the timestamp itself.
73
Added:
- `fracas-refile` — move a completed entry under a top-level heading.
74
Added:
- `fracas-verify` — return the heading, TODO state, `CLOSED`
75
Added:
timestamp, and outline path of an entry. Read-only.
76
Added:
77
Added:
Use the Emacs MCP `org-*` tools for tasks the FRACAS tools do not
78
Added:
cover:
79
Added:
80
Added:
- Use `org-get-node` or `org-search` to read headings and content.
81
Added:
- Use `org-capture` to create the `Dispatch` heading, or a top-level
82
Added:
section, when it does not exist.
83
Added:
84
Added:
## Step 1 — Capture under Dispatch
85
Added:
86
Added:
Run `fracas-inspect` first. Read the TODO keyword sequence. Check
87
Added:
whether a `Dispatch` heading exists.
88
Added:
89
Added:
If `Dispatch` does not exist, create it with the Emacs MCP
90
Added:
`org-capture` tool. Add `* Dispatch` as a top-level heading at the top
91
Added:
of the file.
92
Added:
93
Added:
Run `fracas-capture` with these arguments:
94
Added:
95
Added:
- `root` — the project directory.
96
Added:
- `title` — a short imperative title under 60 characters.
97
Added:
- `body` — the full request text.
98
Added:
99
Added:
Keep the request text unchanged. If the text holds separate ideas,
100
Added:
format the body as a list.
101
Added:
102
Added:
The tool records the capture time as an inactive `SCHEDULED` timestamp
103
Added:
and returns the new entry's Org ID and outline path. Report the
104
Added:
outline path.
105
Added:
106
Added:
## Step 2 — Track the status
107
Added:
108
Added:
Read the current status with `fracas-verify` before you change a
109
Added:
keyword. Use one keyword from the sequence that `fracas-inspect`
110
Added:
returned.
111
Added:
112
Added:
Run `fracas-set-status` for a status change to any open keyword:
113
Added:
114
Added:
- `TODO` — Capture the request. Do not start work.
115
Added:
- `IN-PROGRESS` — Start work.
116
Added:
- `TESTING` — Run tests. The tests do not pass.
117
Added:
- `TESTED` — Tests pass. No commit exists.
118
Added:
- `BLOCKED` — Work cannot continue. State the cause of the block.
119
Added:
- `CANCELLED` — Stop work on purpose.
120
Added:
121
Added:
Change the keyword only after you observe the current status. Run
122
Added:
`fracas-verify` after the change to confirm the keyword.
123
Added:
124
Added:
For `DONE`, use `fracas-complete`, not `fracas-set-status`. Set `DONE`
125
Added:
only when all of these are true:
126
Added:
127
Added:
- The build passes.
128
Added:
- Tests pass.
129
Added:
- The files have the required format.
130
Added:
- A commit exists.
131
Added:
132
Added:
`fracas-complete` sets `DONE`, lets Org's configured logging add the
133
Added:
`CLOSED` timestamp, and confirms the timestamp. Do not add or edit the
134
Added:
timestamp yourself. Refile the entry next, under Step 3.
135
Added:
136
Added:
## Step 3 — Refile on completion
137
Added:
138
Added:
Keep the request under `* Dispatch` while its status is open,
139
Added:
`BLOCKED`, or `CANCELLED`. When its status becomes `DONE`, move the
140
Added:
entry out of `Dispatch`.
141
Added:
142
Added:
Read the top-level headings with `org-search` or
143
Added:
`org-get-node`. Select the section that best matches the request. If
144
Added:
no section matches, create a short, consistent top-level section with
145
Added:
the Emacs MCP `org-capture` tool.
146
Added:
147
Added:
Run `fracas-refile` with these arguments:
148
Added:
149
Added:
- `root` — the project directory.
150
Added:
- `id` — the Org ID of the entry.
151
Added:
- `target` — the top-level heading name.
152
Added:
153
Added:
Run `fracas-verify` to confirm the new location. Report the new
154
Added:
outline path.
155
Added:
156
Added:
Record each prompt as its own request. If you cannot complete a
157
Added:
request, record the request and the failure.
.kiro/skills/fracas/fracas-tools.el
@@ -0,0 +1,314 @@
1
Added:
;;; fracas-tools.el --- FRACAS MCP tools for REQUESTS.org -*- lexical-binding: t; -*-
2
Added:
3
Added:
;; Copyright (C) 2025
4
Added:
5
Added:
;; This file is NOT part of GNU Emacs.
6
Added:
7
Added:
;;; Commentary:
8
Added:
9
Added:
;; FRACAS means "Feature Requests As a Constant Agent-monitored Stream".
10
Added:
;;
11
Added:
;; This file registers dedicated Emacs MCP tools for the FRACAS skill so the
12
Added:
;; agent does not run raw `eval-elisp' snippets for each capture, status
13
Added:
;; change, refile, or verification. Each operation becomes a named tool.
14
Added:
;;
15
Added:
;; Why dedicated tools instead of `eval-elisp'?
16
Added:
;;
17
Added:
;; The `eval-elisp' tool routes its argument through
18
Added:
;; `mcp-server-security-safe-eval', whose form walker blocks or prompts for
19
Added:
;; "dangerous" functions such as `find-file-noselect', `write-file', and
20
Added:
;; `save-buffer'. With `mcp-server-security-prompt-for-permissions' set to t,
21
Added:
;; every FRACAS snippet triggers a minibuffer prompt.
22
Added:
;;
23
Added:
;; A registered MCP tool runs through `mcp-server-tools-call', which calls the
24
Added:
;; handler function directly and does NOT pass through the form walker. The
25
Added:
;; handlers below therefore run without the repeated security prompt. Each
26
Added:
;; tool also carries MCP `annotations' so the MCP client can auto-approve the
27
Added:
;; read-only tools.
28
Added:
;;
29
Added:
;; The tools operate only on the file "REQUESTS.org" under a caller-
30
Added:
;; supplied project ROOT. They never touch any other file.
31
Added:
;;
32
Added:
;; Install (run once per Emacs session):
33
Added:
;;
34
Added:
;; (load "/home/blendux/git/hito/.kiro/skills/fasoc/fracas-tools.el")
35
Added:
;;
36
Added:
;; The tools self-register on load. With `mcp-server-emacs-tools-enabled' set
37
Added:
;; to `all' (the default), they appear in the MCP tool list immediately.
38
Added:
39
Added:
;;; Code:
40
Added:
41
Added:
(require 'mcp-server-tools)
42
Added:
(require 'org)
43
Added:
(require 'org-id)
44
Added:
(require 'org-capture)
45
Added:
(require 'org-refile)
46
Added:
(require 'json)
47
Added:
48
Added:
;;; Helpers
49
Added:
50
Added:
(defun fracas-tools--file (args)
51
Added:
"Return the absolute REQUESTS.org path for ARGS.
52
Added:
ARGS must contain a `root' string naming the project directory.
53
Added:
Signal an error when `root' is missing or is not a directory."
54
Added:
(let ((root (alist-get 'root args)))
55
Added:
(unless (and (stringp root) (> (length root) 0))
56
Added:
(error "Missing required `root' (project directory)"))
57
Added:
(let ((dir (expand-file-name root)))
58
Added:
(unless (file-directory-p dir)
59
Added:
(error "Not a directory: %s" dir))
60
Added:
(expand-file-name "REQUESTS.org" dir))))
61
Added:
62
Added:
(defun fracas-tools--buffer (file)
63
Added:
"Return an org-mode buffer visiting FILE, creating it as needed."
64
Added:
(let ((buf (find-file-noselect file)))
65
Added:
(with-current-buffer buf
66
Added:
(unless (derived-mode-p 'org-mode)
67
Added:
(org-mode)))
68
Added:
buf))
69
Added:
70
Added:
(defun fracas-tools--goto-id (id)
71
Added:
"Move point to the heading with Org ID in the current buffer.
72
Added:
Signal an error when ID is not found."
73
Added:
(let ((marker (org-id-find id 'marker)))
74
Added:
(unless marker
75
Added:
(error "Org ID not found: %s" id))
76
Added:
(goto-char marker)))
77
Added:
78
Added:
(defmacro fracas-tools--json (&rest body)
79
Added:
"Evaluate BODY and return its value as a JSON string.
80
Added:
Catch any error and return a JSON object with an `error' field."
81
Added:
(declare (indent 0))
82
Added:
`(condition-case err
83
Added:
(json-encode (progn ,@body))
84
Added:
(error (json-encode `((error . ,(error-message-string err)))))))
85
Added:
86
Added:
;;; inspect (read-only)
87
Added:
88
Added:
(defun fracas-tools--inspect-handler (args)
89
Added:
"Report the TODO sequence and Dispatch presence for REQUESTS.org."
90
Added:
(fracas-tools--json
91
Added:
(let ((file (fracas-tools--file args)))
92
Added:
(with-current-buffer (fracas-tools--buffer file)
93
Added:
(org-with-wide-buffer
94
Added:
`((file . ,file)
95
Added:
(todo_keywords . ,(vconcat org-todo-keywords-1))
96
Added:
(dispatch . ,(if (org-find-exact-headline-in-buffer "Dispatch")
97
Added:
t :false))))))))
98
Added:
99
Added:
(mcp-server-register-tool
100
Added:
(make-mcp-server-tool
101
Added:
:name "fracas-inspect"
102
Added:
:title "FRACAS Inspect"
103
Added:
:description "Inspect REQUESTS.org: return its TODO keyword sequence and whether a top-level `Dispatch' heading exists. Read-only."
104
Added:
:input-schema '((type . "object")
105
Added:
(properties . ((root . ((type . "string")
106
Added:
(description . "Absolute path to the project directory containing REQUESTS.org")))))
107
Added:
(required . ["root"]))
108
Added:
:function #'fracas-tools--inspect-handler
109
Added:
:annotations '((readOnlyHint . t)
110
Added:
(destructiveHint . :false)
111
Added:
(idempotentHint . t)
112
Added:
(openWorldHint . :false))))
113
Added:
114
Added:
;;; capture
115
Added:
116
Added:
(defun fracas-tools--capture-handler (args)
117
Added:
"Capture a TODO entry under `Dispatch' in REQUESTS.org.
118
Added:
ARGS keys: `root', `title', `body' (optional). The Dispatch heading must
119
Added:
already exist; create it with the org-capture MCP tool first when absent.
120
Added:
Record the capture time as an inactive SCHEDULED timestamp."
121
Added:
(fracas-tools--json
122
Added:
(let* ((file (fracas-tools--file args))
123
Added:
(title (or (alist-get 'title args) (error "Missing `title'")))
124
Added:
(body (or (alist-get 'body args) ""))
125
Added:
(captured-at (format-time-string "[%Y-%m-%d %a %H:%M]"))
126
Added:
(org-capture-templates
127
Added:
`(("e" "Request" entry
128
Added:
(file+headline ,file "Dispatch")
129
Added:
"%i" :immediate-finish t))))
130
Added:
(with-current-buffer (fracas-tools--buffer file)
131
Added:
(unless (org-find-exact-headline-in-buffer "Dispatch")
132
Added:
(error "No `Dispatch' heading in %s; create it first" file)))
133
Added:
(org-capture-string
134
Added:
(format "** TODO %s\nSCHEDULED: %s\n%s\n" title captured-at body)
135
Added:
"e")
136
Added:
(with-current-buffer (fracas-tools--buffer file)
137
Added:
(let ((marker org-capture-last-stored-marker))
138
Added:
(goto-char marker)
139
Added:
(let ((id (org-id-get-create)))
140
Added:
(when (buffer-modified-p) (save-buffer))
141
Added:
`((id . ,id)
142
Added:
(file . ,file)
143
Added:
(heading . ,(org-get-heading t t t t))
144
Added:
(outline_path . ,(vconcat (org-get-outline-path t))))))))))
145
Added:
146
Added:
(mcp-server-register-tool
147
Added:
(make-mcp-server-tool
148
Added:
:name "fracas-capture"
149
Added:
:title "FRACAS Capture"
150
Added:
:description "Capture a TODO feature request under the `Dispatch' heading in REQUESTS.org. Records the capture time as an inactive SCHEDULED timestamp and assigns an Org ID. The `Dispatch' heading must exist first."
151
Added:
:input-schema '((type . "object")
152
Added:
(properties . ((root . ((type . "string")
153
Added:
(description . "Absolute path to the project directory")))
154
Added:
(title . ((type . "string")
155
Added:
(description . "Imperative title under 60 chars")))
156
Added:
(body . ((type . "string")
157
Added:
(description . "Full request text, verbatim")))))
158
Added:
(required . ["root" "title"]))
159
Added:
:function #'fracas-tools--capture-handler
160
Added:
:annotations '((readOnlyHint . :false)
161
Added:
(destructiveHint . :false)
162
Added:
(idempotentHint . :false)
163
Added:
(openWorldHint . :false))))
164
Added:
165
Added:
;;; set-status
166
Added:
167
Added:
(defun fracas-tools--set-status-handler (args)
168
Added:
"Change the TODO keyword of an entry.
169
Added:
ARGS keys: `root', `id', `state'. STATE must be one keyword from the
170
Added:
file's own #+TODO sequence, and must not be DONE (use fracas-complete)."
171
Added:
(fracas-tools--json
172
Added:
(let ((file (fracas-tools--file args))
173
Added:
(id (or (alist-get 'id args) (error "Missing `id'")))
174
Added:
(state (or (alist-get 'state args) (error "Missing `state'"))))
175
Added:
(when (string-equal state "DONE")
176
Added:
(error "Use fracas-complete for DONE, not fracas-set-status"))
177
Added:
(with-current-buffer (fracas-tools--buffer file)
178
Added:
(fracas-tools--goto-id id)
179
Added:
(org-todo state)
180
Added:
(when (buffer-modified-p) (save-buffer))
181
Added:
`((id . ,id)
182
Added:
(state . ,(org-get-todo-state)))))))
183
Added:
184
Added:
(mcp-server-register-tool
185
Added:
(make-mcp-server-tool
186
Added:
:name "fracas-set-status"
187
Added:
:title "FRACAS Set Status"
188
Added:
:description "Set the TODO keyword of a FRACAS entry (for example IN-PROGRESS, TESTING, TESTED, BLOCKED, CANCELLED). Use one keyword from the file's own #+TODO sequence. Does not accept DONE; use fracas-complete for that."
189
Added:
:input-schema '((type . "object")
190
Added:
(properties . ((root . ((type . "string")))
191
Added:
(id . ((type . "string")
192
Added:
(description . "Org ID of the entry")))
193
Added:
(state . ((type . "string")
194
Added:
(description . "TODO keyword from the file's #+TODO sequence")))))
195
Added:
(required . ["root" "id" "state"]))
196
Added:
:function #'fracas-tools--set-status-handler
197
Added:
:annotations '((readOnlyHint . :false)
198
Added:
(destructiveHint . :false)
199
Added:
(idempotentHint . t)
200
Added:
(openWorldHint . :false))))
201
Added:
202
Added:
;;; complete
203
Added:
204
Added:
(defun fracas-tools--complete-handler (args)
205
Added:
"Set an entry to DONE and confirm Org recorded a CLOSED timestamp.
206
Added:
ARGS keys: `root', `id'. Requires `org-log-done' to be `time' so the
207
Added:
normal Org transition inserts CLOSED; this handler never writes CLOSED."
208
Added:
(fracas-tools--json
209
Added:
(let ((file (fracas-tools--file args))
210
Added:
(id (or (alist-get 'id args) (error "Missing `id'"))))
211
Added:
(with-current-buffer (fracas-tools--buffer file)
212
Added:
(hack-local-variables)
213
Added:
(unless (eq org-log-done 'time)
214
Added:
(error "org-log-done is not set to time"))
215
Added:
(fracas-tools--goto-id id)
216
Added:
(org-todo "DONE")
217
Added:
(when (buffer-modified-p) (save-buffer))
218
Added:
(let ((closed (org-entry-get nil "CLOSED")))
219
Added:
(unless closed
220
Added:
(error "Org did not record a CLOSED timestamp"))
221
Added:
`((id . ,id)
222
Added:
(state . ,(org-get-todo-state))
223
Added:
(closed . ,closed)))))))
224
Added:
225
Added:
(mcp-server-register-tool
226
Added:
(make-mcp-server-tool
227
Added:
:name "fracas-complete"
228
Added:
:title "FRACAS Complete"
229
Added:
:description "Set a FRACAS entry to DONE and confirm Org inserted a CLOSED timestamp. Requires org-log-done set to time. Never writes the timestamp itself. Refile with fracas-refile afterward."
230
Added:
:input-schema '((type . "object")
231
Added:
(properties . ((root . ((type . "string")))
232
Added:
(id . ((type . "string")))))
233
Added:
(required . ["root" "id"]))
234
Added:
:function #'fracas-tools--complete-handler
235
Added:
:annotations '((readOnlyHint . :false)
236
Added:
(destructiveHint . :false)
237
Added:
(idempotentHint . t)
238
Added:
(openWorldHint . :false))))
239
Added:
240
Added:
;;; refile
241
Added:
242
Added:
(defun fracas-tools--refile-handler (args)
243
Added:
"Refile the entry with `id' under the top-level heading `target'.
244
Added:
ARGS keys: `root', `id', `target'. Both entry and target are in
245
Added:
REQUESTS.org."
246
Added:
(fracas-tools--json
247
Added:
(let ((file (fracas-tools--file args))
248
Added:
(id (or (alist-get 'id args) (error "Missing `id'")))
249
Added:
(target (or (alist-get 'target args) (error "Missing `target'"))))
250
Added:
(with-current-buffer (fracas-tools--buffer file)
251
Added:
(org-with-wide-buffer
252
Added:
(let ((target-position
253
Added:
(let ((tm (org-find-exact-headline-in-buffer target)))
254
Added:
(unless tm (error "Org target not found: %s" target))
255
Added:
(marker-position tm))))
256
Added:
(fracas-tools--goto-id id)
257
Added:
(org-refile nil nil (list target file nil target-position))
258
Added:
(when (buffer-modified-p) (save-buffer))
259
Added:
(fracas-tools--goto-id id)
260
Added:
`((id . ,id)
261
Added:
(outline_path . ,(vconcat (org-get-outline-path t))))))))))
262
Added:
263
Added:
(mcp-server-register-tool
264
Added:
(make-mcp-server-tool
265
Added:
:name "fracas-refile"
266
Added:
:title "FRACAS Refile"
267
Added:
:description "Move a completed FRACAS entry out of `Dispatch' and under a top-level `target' heading in REQUESTS.org."
268
Added:
:input-schema '((type . "object")
269
Added:
(properties . ((root . ((type . "string")))
270
Added:
(id . ((type . "string")))
271
Added:
(target . ((type . "string")
272
Added:
(description . "Top-level heading to receive the entry")))))
273
Added:
(required . ["root" "id" "target"]))
274
Added:
:function #'fracas-tools--refile-handler
275
Added:
:annotations '((readOnlyHint . :false)
276
Added:
(destructiveHint . :false)
277
Added:
(idempotentHint . :false)
278
Added:
(openWorldHint . :false))))
279
Added:
280
Added:
;;; verify (read-only)
281
Added:
282
Added:
(defun fracas-tools--verify-handler (args)
283
Added:
"Return the current state of the entry with `id'.
284
Added:
ARGS keys: `root', `id'. Read-only."
285
Added:
(fracas-tools--json
286
Added:
(let ((file (fracas-tools--file args))
287
Added:
(id (or (alist-get 'id args) (error "Missing `id'"))))
288
Added:
(with-current-buffer (fracas-tools--buffer file)
289
Added:
(org-with-wide-buffer
290
Added:
(fracas-tools--goto-id id)
291
Added:
`((id . ,id)
292
Added:
(heading . ,(org-get-heading t t t t))
293
Added:
(state . ,(org-get-todo-state))
294
Added:
(closed . ,(or (org-entry-get nil "CLOSED") :null))
295
Added:
(outline_path . ,(vconcat (org-get-outline-path t)))))))))
296
Added:
297
Added:
(mcp-server-register-tool
298
Added:
(make-mcp-server-tool
299
Added:
:name "fracas-verify"
300
Added:
:title "FRACAS Verify"
301
Added:
:description "Return the heading, TODO state, CLOSED timestamp, and outline path of a FRACAS entry. Read-only."
302
Added:
:input-schema '((type . "object")
303
Added:
(properties . ((root . ((type . "string")))
304
Added:
(id . ((type . "string")))))
305
Added:
(required . ["root" "id"]))
306
Added:
:function #'fracas-tools--verify-handler
307
Added:
:annotations '((readOnlyHint . t)
308
Added:
(destructiveHint . :false)
309
Added:
(idempotentHint . t)
310
Added:
(openWorldHint . :false))))
311
Added:
312
Added:
(provide 'fracas-tools)
313
Added:
314
Added:
;;; fracas-tools.el ends here