lemmafit 0.0.1 → 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +91 -4
- package/blank-template/README.md +3 -0
- package/blank-template/SPEC.yaml +1 -0
- package/blank-template/index.html +12 -0
- package/blank-template/lemmafit/.vibe/config.json +5 -0
- package/blank-template/lemmafit/dafny/Domain.dfy +5 -0
- package/blank-template/lemmafit/dafny/Replay.dfy +147 -0
- package/blank-template/package.json +25 -0
- package/blank-template/src/App.css +3 -0
- package/blank-template/src/App.tsx +10 -0
- package/blank-template/src/dafny/.gitkeep +0 -0
- package/blank-template/src/index.css +29 -0
- package/blank-template/src/main.tsx +10 -0
- package/blank-template/src/vite-env.d.ts +6 -0
- package/blank-template/template.gitignore +3 -0
- package/blank-template/tsconfig.json +21 -0
- package/blank-template/tsconfig.node.json +11 -0
- package/blank-template/vite.config.js +9 -0
- package/cli/context-hook.js +103 -0
- package/cli/daemon.js +24 -0
- package/cli/download-dafny2js.js +136 -0
- package/cli/generate-guarantees-md.js +223 -0
- package/cli/lemmafit.js +247 -0
- package/cli/session-hook.js +74 -0
- package/cli/sync.js +168 -0
- package/cli/verify-hook.js +221 -0
- package/commands/guarantees.md +138 -0
- package/docs/CLAUDE_INSTRUCTIONS.md +103 -0
- package/kernels/Replay.dfy +147 -0
- package/lib/daemon-client.js +54 -0
- package/lib/daemon.js +916 -0
- package/lib/download-dafny.js +130 -0
- package/lib/log.js +32 -0
- package/lib/spawn-claude.js +51 -0
- package/package.json +49 -5
- package/skills/lemmafit-dafny/SKILL.md +101 -0
- package/skills/lemmafit-post-react-audit/SKILL.md +46 -0
- package/skills/lemmafit-pre-react-audits/SKILL.md +67 -0
- package/skills/lemmafit-proofs/SKILL.md +24 -0
- package/skills/lemmafit-react-pattern/SKILL.md +62 -0
- package/skills/lemmafit-spec/SKILL.md +71 -0
- package/index.js +0 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 midspiral
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,9 +1,96 @@
|
|
|
1
1
|
# lemmafit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Make agents **prove** that their code is correct.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Lemmafit integrates [Dafny](https://dafny.org/) formal verification into your development workflow via [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Business logic, state machines, and other logic is written in Dafny, mathematically verified, then auto-compiled to TypeScript for use in your React app.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Quick Start
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
```bash
|
|
10
|
+
# Install lemmafit globally
|
|
11
|
+
npm install -g lemmafit
|
|
12
|
+
|
|
13
|
+
# Create a new project
|
|
14
|
+
lemmafit init PROJECT_NAME
|
|
15
|
+
cd PROJECT_NAME
|
|
16
|
+
|
|
17
|
+
# Install deps (downloads Dafny automatically)
|
|
18
|
+
npm install
|
|
19
|
+
|
|
20
|
+
# In one terminal, start the verification daemon
|
|
21
|
+
npm run daemon
|
|
22
|
+
|
|
23
|
+
# In another terminal, start the Vite dev server
|
|
24
|
+
npm run dev
|
|
25
|
+
|
|
26
|
+
# In a third terminal, open Claude Code
|
|
27
|
+
claude
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Use Cases / Considerations
|
|
31
|
+
|
|
32
|
+
- lemmafit works with greenfield projects only. You must begin a project with lemmafit. Support for existing codebases is in the pipeline.
|
|
33
|
+
|
|
34
|
+
- lemmafit compiles Dafny to Typescript which then hooks into a React app. In the future, we will support other languages and frameworks.
|
|
35
|
+
|
|
36
|
+
- lemmafit is optimized to work with Claude Code. In the future, lemmafit will be agent-agnostic.
|
|
37
|
+
|
|
38
|
+
## How It Works
|
|
39
|
+
|
|
40
|
+
1. Prompt Claude Code as you normally would. You may use a simple starting prompt or a structured prompting system.
|
|
41
|
+
**Example: "Create a pomodoro app I can use personally and locally."**
|
|
42
|
+
2. The agent will write a `SPEC.yaml` and write verified logic in `lemmafit/dafny/Domain.dfy`
|
|
43
|
+
3. The **daemon** watches `.dfy` files, runs `dafny verify`, and on success compiles to `src/dafny/Domain.cjs` + `src/dafny/app.ts`
|
|
44
|
+
4. The agent will hook the generated TypeScript API into a React app — the logic is proven correct
|
|
45
|
+
5. After proofs complete, run custom command in Claude Code `/guarantees` to activate claimcheck and generate a guarantees report
|
|
46
|
+
|
|
47
|
+
## Project Structure
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
my-app/
|
|
51
|
+
├── SPEC.yaml # Your requirements
|
|
52
|
+
├── lemmafit/
|
|
53
|
+
│ ├── dafny/
|
|
54
|
+
│ │ └── Domain.dfy # Your verified Dafny logic
|
|
55
|
+
│ │ └── Replay.dfy # Generic Replay kernel
|
|
56
|
+
│ ├── .vibe/
|
|
57
|
+
│ │ ├── config.json # Project config
|
|
58
|
+
│ │ ├── status.json # Verification status (generated)
|
|
59
|
+
│ │ └── claims.json # Proof obligations (generated)
|
|
60
|
+
│ └── reports/
|
|
61
|
+
│ └── guarantees.md # Guarantee report (generated)
|
|
62
|
+
├── src/
|
|
63
|
+
│ ├── dafny/
|
|
64
|
+
│ │ ├── Domain.cjs # Compiled JS (generated)
|
|
65
|
+
│ │ └── app.ts # TypeScript API (generated - DO NOT EDIT)
|
|
66
|
+
│ ├── App.tsx # Your React app
|
|
67
|
+
│ └── main.tsx
|
|
68
|
+
├── .claude/ # Hooks & settings (managed by lemmafit)
|
|
69
|
+
└── package.json
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
lemmafit init [dir] # Create project from template
|
|
76
|
+
lemmafit sync [dir] # Re-sync system files (.claude/, hooks)
|
|
77
|
+
lemmafit daemon [dir] # Run verification daemon standalone
|
|
78
|
+
lemmafit logs [dir] # View daemon log
|
|
79
|
+
lemmafit logs --clear [dir] # Clear daemon log
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Updating
|
|
83
|
+
|
|
84
|
+
System files sync automatically on install:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm update lemmafit
|
|
88
|
+
# postinstall re-syncs .claude/settings.json, hooks, and instructions
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Requirements
|
|
92
|
+
|
|
93
|
+
- Node.js 18+
|
|
94
|
+
- [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI
|
|
95
|
+
|
|
96
|
+
Dafny and dafny2js are downloaded automatically during `npm install` to `~/.lemmafit/`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
entries: []
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Verified App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
abstract module {:compile false} Domain {
|
|
2
|
+
type Model
|
|
3
|
+
type Action
|
|
4
|
+
|
|
5
|
+
ghost predicate Inv(m: Model)
|
|
6
|
+
|
|
7
|
+
function Init(): Model
|
|
8
|
+
function Apply(m: Model, a: Action): Model
|
|
9
|
+
requires Inv(m)
|
|
10
|
+
function Normalize(m: Model): Model
|
|
11
|
+
|
|
12
|
+
lemma InitSatisfiesInv()
|
|
13
|
+
ensures Inv(Init())
|
|
14
|
+
|
|
15
|
+
lemma StepPreservesInv(m: Model, a: Action)
|
|
16
|
+
requires Inv(m)
|
|
17
|
+
ensures Inv(Normalize(Apply(m,a)))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
abstract module {:compile false} Kernel {
|
|
21
|
+
import D : Domain
|
|
22
|
+
|
|
23
|
+
function Step(m: D.Model, a: D.Action): D.Model
|
|
24
|
+
requires D.Inv(m)
|
|
25
|
+
{
|
|
26
|
+
D.Normalize(D.Apply(m, a))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function InitHistory(): History {
|
|
30
|
+
History([], D.Init(), [])
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
datatype History =
|
|
34
|
+
History(past: seq<D.Model>, present: D.Model, future: seq<D.Model>)
|
|
35
|
+
|
|
36
|
+
function Do(h: History, a: D.Action): History
|
|
37
|
+
requires D.Inv(h.present)
|
|
38
|
+
{
|
|
39
|
+
History(h.past + [h.present], Step(h.present, a), [])
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Apply action without recording to history (for live preview during drag)
|
|
43
|
+
function Preview(h: History, a: D.Action): History
|
|
44
|
+
requires D.Inv(h.present)
|
|
45
|
+
{
|
|
46
|
+
History(h.past, Step(h.present, a), h.future)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Commit current state, recording baseline to history (for end of drag)
|
|
50
|
+
function CommitFrom(h: History, baseline: D.Model): History {
|
|
51
|
+
History(h.past + [baseline], h.present, [])
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Undo(h: History): History {
|
|
55
|
+
if |h.past| == 0 then h
|
|
56
|
+
else
|
|
57
|
+
var i := |h.past| - 1;
|
|
58
|
+
History(h.past[..i], h.past[i], [h.present] + h.future)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function Redo(h: History): History {
|
|
62
|
+
if |h.future| == 0 then h
|
|
63
|
+
else
|
|
64
|
+
History(h.past + [h.present], h.future[0], h.future[1..])
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
lemma DoPreservesInv(h: History, a: D.Action)
|
|
68
|
+
requires D.Inv(h.present)
|
|
69
|
+
ensures D.Inv(Do(h, a).present)
|
|
70
|
+
{
|
|
71
|
+
D.StepPreservesInv(h.present, a);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
ghost predicate HistInv(h: History) {
|
|
75
|
+
(forall i | 0 <= i < |h.past| :: D.Inv(h.past[i])) &&
|
|
76
|
+
D.Inv(h.present) &&
|
|
77
|
+
(forall j | 0 <= j < |h.future| :: D.Inv(h.future[j]))
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
lemma InitHistorySatisfiesInv()
|
|
81
|
+
ensures HistInv(InitHistory())
|
|
82
|
+
{
|
|
83
|
+
D.InitSatisfiesInv();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
lemma UndoPreservesHistInv(h: History)
|
|
87
|
+
requires HistInv(h)
|
|
88
|
+
ensures HistInv(Undo(h))
|
|
89
|
+
{
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
lemma RedoPreservesHistInv(h: History)
|
|
93
|
+
requires HistInv(h)
|
|
94
|
+
ensures HistInv(Redo(h))
|
|
95
|
+
{
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
lemma DoPreservesHistInv(h: History, a: D.Action)
|
|
99
|
+
requires HistInv(h)
|
|
100
|
+
ensures HistInv(Do(h, a))
|
|
101
|
+
{
|
|
102
|
+
D.StepPreservesInv(h.present, a);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
lemma PreviewPreservesHistInv(h: History, a: D.Action)
|
|
106
|
+
requires HistInv(h)
|
|
107
|
+
ensures HistInv(Preview(h, a))
|
|
108
|
+
{
|
|
109
|
+
D.StepPreservesInv(h.present, a);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
lemma CommitFromPreservesHistInv(h: History, baseline: D.Model)
|
|
113
|
+
requires HistInv(h)
|
|
114
|
+
requires D.Inv(baseline)
|
|
115
|
+
ensures HistInv(CommitFrom(h, baseline))
|
|
116
|
+
{
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// proxy for linear undo: after a new action, there is no redo branch
|
|
120
|
+
lemma DoHasNoRedoBranch(h: History, a: D.Action)
|
|
121
|
+
requires HistInv(h)
|
|
122
|
+
ensures Redo(Do(h, a)) == Do(h, a)
|
|
123
|
+
{
|
|
124
|
+
}
|
|
125
|
+
// round-tripping properties
|
|
126
|
+
lemma UndoRedoRoundTrip(h: History)
|
|
127
|
+
requires |h.past| > 0
|
|
128
|
+
ensures Redo(Undo(h)) == h
|
|
129
|
+
{
|
|
130
|
+
}
|
|
131
|
+
lemma RedoUndoRoundTrip(h: History)
|
|
132
|
+
requires |h.future| > 0
|
|
133
|
+
ensures Undo(Redo(h)) == h
|
|
134
|
+
{
|
|
135
|
+
}
|
|
136
|
+
// idempotence at boundaries
|
|
137
|
+
lemma UndoAtBeginningIsNoOp(h: History)
|
|
138
|
+
requires |h.past| == 0
|
|
139
|
+
ensures Undo(h) == h
|
|
140
|
+
{
|
|
141
|
+
}
|
|
142
|
+
lemma RedoAtEndIsNoOp(h: History)
|
|
143
|
+
requires |h.future| == 0
|
|
144
|
+
ensures Redo(h) == h
|
|
145
|
+
{
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "verified-app",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"daemon": "lemmafit-daemon",
|
|
9
|
+
"build": "tsc && vite build",
|
|
10
|
+
"preview": "vite preview"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"lemmafit": "^0.1.0",
|
|
14
|
+
"bignumber.js": "^9.1.2",
|
|
15
|
+
"react": "^18.2.0",
|
|
16
|
+
"react-dom": "^18.2.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/react": "^18.2.0",
|
|
20
|
+
"@types/react-dom": "^18.2.0",
|
|
21
|
+
"@vitejs/plugin-react": "^4.2.0",
|
|
22
|
+
"typescript": "^5.3.0",
|
|
23
|
+
"vite": "^5.0.0"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
3
|
+
line-height: 1.5;
|
|
4
|
+
font-weight: 400;
|
|
5
|
+
|
|
6
|
+
color-scheme: light dark;
|
|
7
|
+
color: rgba(255, 255, 255, 0.87);
|
|
8
|
+
background-color: #242424;
|
|
9
|
+
|
|
10
|
+
font-synthesis: none;
|
|
11
|
+
text-rendering: optimizeLegibility;
|
|
12
|
+
-webkit-font-smoothing: antialiased;
|
|
13
|
+
-moz-osx-font-smoothing: grayscale;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
margin: 0;
|
|
18
|
+
display: flex;
|
|
19
|
+
place-items: center;
|
|
20
|
+
min-width: 320px;
|
|
21
|
+
min-height: 100vh;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#root {
|
|
25
|
+
max-width: 1280px;
|
|
26
|
+
margin: 0 auto;
|
|
27
|
+
padding: 2rem;
|
|
28
|
+
text-align: center;
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true
|
|
18
|
+
},
|
|
19
|
+
"include": ["src"],
|
|
20
|
+
"references": [{ "path": "./tsconfig.node.json" }]
|
|
21
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code UserPromptSubmit hook for lemmafit.
|
|
4
|
+
*
|
|
5
|
+
* Reads lemmafit/.vibe/status.json and writes it to stdout so Claude
|
|
6
|
+
* sees the current verification status before processing every prompt.
|
|
7
|
+
*
|
|
8
|
+
* Hook receives JSON on stdin with { "cwd": "..." }
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const { initLog, log } = require('../lib/log');
|
|
14
|
+
|
|
15
|
+
async function readStdin() {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for await (const chunk of process.stdin) {
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
}
|
|
20
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function findProjectRoot(dir) {
|
|
24
|
+
let current = dir;
|
|
25
|
+
while (current !== path.dirname(current)) {
|
|
26
|
+
if (fs.existsSync(path.join(current, 'lemmafit'))) {
|
|
27
|
+
return current;
|
|
28
|
+
}
|
|
29
|
+
current = path.dirname(current);
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
const input = await readStdin();
|
|
36
|
+
|
|
37
|
+
let hookData;
|
|
38
|
+
try {
|
|
39
|
+
hookData = JSON.parse(input);
|
|
40
|
+
} catch {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const cwd = hookData.cwd;
|
|
45
|
+
if (!cwd) {
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const projectDir = findProjectRoot(cwd);
|
|
50
|
+
if (!projectDir) {
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
initLog(projectDir);
|
|
55
|
+
log('context', 'Injecting status into prompt');
|
|
56
|
+
|
|
57
|
+
const statusPath = path.join(projectDir, 'lemmafit', '.vibe', 'status.json');
|
|
58
|
+
let status;
|
|
59
|
+
try {
|
|
60
|
+
status = JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
61
|
+
} catch {
|
|
62
|
+
log('context', 'status.json missing or unreadable');
|
|
63
|
+
const context = `<lemmafit-status>
|
|
64
|
+
{ "state": "unavailable", "error": "status.json missing or unreadable — is the daemon running?" }
|
|
65
|
+
</lemmafit-status>`;
|
|
66
|
+
console.log(JSON.stringify({
|
|
67
|
+
hookSpecificOutput: {
|
|
68
|
+
hookEventName: 'UserPromptSubmit',
|
|
69
|
+
additionalContext: context,
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Inject status with full spec queue items so Claude can act on them directly
|
|
76
|
+
const summary = {
|
|
77
|
+
state: status.state,
|
|
78
|
+
compiled: status.compiled,
|
|
79
|
+
lastCompiled: status.lastCompiled,
|
|
80
|
+
timestamp: status.timestamp,
|
|
81
|
+
files: status.files,
|
|
82
|
+
axioms: status.axioms,
|
|
83
|
+
compileError: status.compileError || undefined,
|
|
84
|
+
specQueue: status.specQueue || [],
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const context = `<lemmafit-status>
|
|
88
|
+
${JSON.stringify(summary, null, 2)}
|
|
89
|
+
</lemmafit-status>
|
|
90
|
+
<lemmafit-status-file>${statusPath}</lemmafit-status-file>`;
|
|
91
|
+
|
|
92
|
+
console.log(JSON.stringify({
|
|
93
|
+
hookSpecificOutput: {
|
|
94
|
+
hookEventName: 'UserPromptSubmit',
|
|
95
|
+
additionalContext: context,
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main().catch((err) => {
|
|
101
|
+
console.error('Context hook error:', err.message);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
});
|
package/cli/daemon.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Lemmafit daemon CLI - watches and verifies Dafny files.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* lemmafit-daemon [project-dir] [--once]
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { Daemon } = require('../lib/daemon');
|
|
11
|
+
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const projectDir = args.find(a => !a.startsWith('-')) || '.';
|
|
14
|
+
const once = args.includes('--once');
|
|
15
|
+
|
|
16
|
+
const daemon = new Daemon(projectDir);
|
|
17
|
+
|
|
18
|
+
if (once) {
|
|
19
|
+
daemon.runOnce().then((result) => {
|
|
20
|
+
process.exit(result.verified && result.compiled ? 0 : 1);
|
|
21
|
+
});
|
|
22
|
+
} else {
|
|
23
|
+
daemon.watch();
|
|
24
|
+
}
|