@balpal4495/quorum 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/.github/copilot-instructions.md +94 -0
- package/CLAUDE.md +86 -0
- package/GEMINI.md +73 -0
- package/LICENSE +21 -0
- package/README.md +202 -0
- package/SETUP.md +256 -0
- package/bin/init.js +366 -0
- package/modules/AGENTS.md +66 -0
- package/modules/CLAUDE.md +64 -0
- package/modules/README.md +251 -0
- package/modules/council/advisors.ts +68 -0
- package/modules/council/chairman.ts +112 -0
- package/modules/council/deliberate.ts +106 -0
- package/modules/council/frame.ts +54 -0
- package/modules/council/index.ts +4 -0
- package/modules/council/personas.ts +57 -0
- package/modules/council/reviewers.ts +81 -0
- package/modules/council/types.ts +45 -0
- package/modules/jury/evaluate.ts +112 -0
- package/modules/jury/index.ts +3 -0
- package/modules/jury/schema.ts +15 -0
- package/modules/jury/types.ts +31 -0
- package/modules/oracle/adapters/lance-db.ts +81 -0
- package/modules/oracle/adapters/xenova-embedder.ts +43 -0
- package/modules/oracle/bm25.ts +92 -0
- package/modules/oracle/index.ts +36 -0
- package/modules/oracle/log.ts +15 -0
- package/modules/oracle/propose.ts +148 -0
- package/modules/oracle/query.ts +145 -0
- package/modules/oracle/summary.ts +115 -0
- package/modules/oracle/types.ts +32 -0
- package/modules/sentinel/assert.ts +95 -0
- package/modules/sentinel/coverage.ts +106 -0
- package/modules/sentinel/drift.ts +159 -0
- package/modules/sentinel/index.ts +6 -0
- package/modules/sentinel/review.ts +207 -0
- package/modules/setup.ts +153 -0
- package/modules/shared/types.ts +148 -0
- package/package.json +47 -0
package/SETUP.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Quorum — Bootstrap Setup
|
|
2
|
+
|
|
3
|
+
You are reading this because Quorum is being set up in a codebase.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Automated setup (preferred)
|
|
8
|
+
|
|
9
|
+
If you have Node.js 18+ available, run this from the project root:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @balpal4495/quorum@latest init
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This handles all steps below automatically — copying modules, merging instruction files, updating `package.json`, `.gitignore`, and creating the Chronicle directory.
|
|
16
|
+
|
|
17
|
+
After it completes, run `npm install` and wire `setup()` into your entry point (see Step 6 below).
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Manual setup (AI-assisted fallback)
|
|
22
|
+
|
|
23
|
+
If the automated script is not available or fails, follow every step below in order.
|
|
24
|
+
Do not skip steps. Do not ask for confirmation between steps — complete the full sequence and then report what was done.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Step 1 — Read before touching anything
|
|
29
|
+
|
|
30
|
+
Read these files in full before proceeding:
|
|
31
|
+
|
|
32
|
+
- `quorum/modules/README.md` — module overview and quick-start
|
|
33
|
+
- `quorum/modules/AGENTS.md` — file ownership and invariants
|
|
34
|
+
- `quorum/.github/copilot-instructions.md` — workflow rules for AI agents
|
|
35
|
+
|
|
36
|
+
These are your operating instructions for everything that follows.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Step 2 — Audit the host project
|
|
41
|
+
|
|
42
|
+
Check what already exists at the project root. Record what you find — you will need this in Step 4.
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
Does .github/copilot-instructions.md exist? yes / no
|
|
46
|
+
Does AGENTS.md exist at the project root? yes / no
|
|
47
|
+
Does CLAUDE.md exist at the project root? yes / no
|
|
48
|
+
Does package.json exist? yes / no
|
|
49
|
+
Does .gitignore exist? yes / no
|
|
50
|
+
Does .chronicle/ exist? yes / no
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Step 3 — Install dependencies
|
|
56
|
+
|
|
57
|
+
Open `package.json` at the project root. Add the following to `dependencies` if they are not already present:
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
"zod": "^3.23.0",
|
|
61
|
+
"vectordb": "^0.4.0",
|
|
62
|
+
"@xenova/transformers": "^2.17.0"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then run:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm install
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
If the project uses `yarn` or `pnpm`, use the appropriate installer instead.
|
|
72
|
+
|
|
73
|
+
> `zod` is required for all structured LLM output validation.
|
|
74
|
+
> `vectordb` is the LanceDB adapter (swappable — see `quorum/modules/oracle/adapters/`).
|
|
75
|
+
> `@xenova/transformers` is the local ONNX embedder (swappable — see `quorum/modules/oracle/adapters/xenova-embedder.ts`).
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Step 4 — Merge AI instruction files
|
|
80
|
+
|
|
81
|
+
### 4a. `.github/copilot-instructions.md`
|
|
82
|
+
|
|
83
|
+
**If it does not exist:**
|
|
84
|
+
Copy `quorum/.github/copilot-instructions.md` to `.github/copilot-instructions.md`.
|
|
85
|
+
|
|
86
|
+
**If it already exists:**
|
|
87
|
+
Append the entire contents of `quorum/.github/copilot-instructions.md` to the existing file, preceded by this separator:
|
|
88
|
+
|
|
89
|
+
```markdown
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
<!-- Quorum: appended by setup -->
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Do not replace or overwrite existing content.
|
|
96
|
+
|
|
97
|
+
### 4b. `AGENTS.md`
|
|
98
|
+
|
|
99
|
+
**If it does not exist:**
|
|
100
|
+
Create `AGENTS.md` at the project root with this content:
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
# Agent Instructions
|
|
104
|
+
|
|
105
|
+
See [quorum/modules/AGENTS.md](quorum/modules/AGENTS.md) for Quorum module internals.
|
|
106
|
+
See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**If it already exists:**
|
|
110
|
+
Append to it:
|
|
111
|
+
|
|
112
|
+
```markdown
|
|
113
|
+
|
|
114
|
+
## Quorum modules
|
|
115
|
+
|
|
116
|
+
See [quorum/modules/AGENTS.md](quorum/modules/AGENTS.md) for Oracle, Jury, and Council internals.
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4c. `CLAUDE.md`
|
|
120
|
+
|
|
121
|
+
**If it does not exist:**
|
|
122
|
+
Create `CLAUDE.md` at the project root with this content:
|
|
123
|
+
|
|
124
|
+
```markdown
|
|
125
|
+
# Claude Instructions
|
|
126
|
+
|
|
127
|
+
See [quorum/modules/CLAUDE.md](quorum/modules/CLAUDE.md) for Quorum module internals.
|
|
128
|
+
See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
**If it already exists:**
|
|
132
|
+
Append to it:
|
|
133
|
+
|
|
134
|
+
```markdown
|
|
135
|
+
|
|
136
|
+
## Quorum modules
|
|
137
|
+
|
|
138
|
+
See [quorum/modules/CLAUDE.md](quorum/modules/CLAUDE.md) for Oracle, Jury, and Council internals.
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Step 5 — Update .gitignore
|
|
144
|
+
|
|
145
|
+
**If `.gitignore` does not exist**, create it.
|
|
146
|
+
|
|
147
|
+
Add the following block if it is not already present:
|
|
148
|
+
|
|
149
|
+
```gitignore
|
|
150
|
+
# Quorum — Chronicle
|
|
151
|
+
# entries/ is a LanceDB binary vector store — do not commit
|
|
152
|
+
.chronicle/entries/
|
|
153
|
+
|
|
154
|
+
# proposals/ contains pending human-approval writes — commit these
|
|
155
|
+
# (remove the line above if you want to ignore the whole store)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Step 6 — Wire setup() into the project
|
|
161
|
+
|
|
162
|
+
Find the application entry point (e.g. `index.ts`, `server.ts`, `app.ts`, or equivalent).
|
|
163
|
+
|
|
164
|
+
Add the following import and call at startup, **before** any agent or workflow code runs:
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { setup } from "./quorum/modules/setup"
|
|
168
|
+
|
|
169
|
+
const { oracle, evaluate, deliberate } = await setup({
|
|
170
|
+
llm: yourLLMProvider, // replace with your project's LLM provider function
|
|
171
|
+
})
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`setup()` creates `.chronicle/` directories, warms the embedder, and wires all module dependencies.
|
|
175
|
+
It must be called once before any `oracle.query()`, `evaluate()`, or `deliberate()` call.
|
|
176
|
+
|
|
177
|
+
If no entry point exists yet, note that `setup()` must be called before first use — do not inline it.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Step 7 — Verify Chronicle is created
|
|
182
|
+
|
|
183
|
+
Run the project (or call `setup()` in isolation). Confirm that `.chronicle/proposals/` exists after startup.
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ls .chronicle/
|
|
187
|
+
# expected: proposals/
|
|
188
|
+
# entries/ will appear after the first oracle.commit()
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
If the directory is not created, re-check that `setup()` is being awaited correctly.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Step 8 — Run module tests
|
|
196
|
+
|
|
197
|
+
Confirm the modules are working in this environment:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
npx vitest run quorum/modules/
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
All tests should pass. If they fail due to missing dependencies, re-run Step 3.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Step 9 — Report what was done
|
|
208
|
+
|
|
209
|
+
Once all steps are complete, report:
|
|
210
|
+
|
|
211
|
+
1. Which files were created vs appended
|
|
212
|
+
2. Which dependencies were added (if any were already present, note that)
|
|
213
|
+
3. Whether tests passed
|
|
214
|
+
4. The path to `setup()` in the entry point, and the LLM provider that was wired (or a note if it was left as a placeholder)
|
|
215
|
+
5. Any step that could not be completed and why
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Optional: Step 10 — Gemini CLI integration
|
|
220
|
+
|
|
221
|
+
Skip this step if you do not have Google Gemini CLI installed. Quorum is fully functional without it.
|
|
222
|
+
|
|
223
|
+
If you do have it (or want to add it later), this enables Claude Code to delegate large-context
|
|
224
|
+
analysis to Gemini — useful when a task requires surveying the whole codebase at once.
|
|
225
|
+
|
|
226
|
+
**10a. Install Gemini CLI** (if not already installed — requires Node.js 18+):
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm install -g @google/gemini-cli
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**10b. Get an API key** from Google AI Studio and add to your shell profile:
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
export GEMINI_API_KEY="your-key-here"
|
|
236
|
+
export GEMINI_CLI_TRUST_WORKSPACE=true
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**10c. Create `GEMINI.md`** at the project root so Gemini understands the codebase.
|
|
240
|
+
Copy `quorum/modules/AGENTS.md` content as a starting point, or write a brief description of
|
|
241
|
+
the project and the Quorum architecture. The `GEMINI.md` in the Quorum repo itself is a
|
|
242
|
+
working example.
|
|
243
|
+
|
|
244
|
+
Once the key is set and `gemini -p "hello"` responds, Claude Code will automatically detect
|
|
245
|
+
Gemini and use it for large-context tasks.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## After setup
|
|
250
|
+
|
|
251
|
+
You are now operating under Quorum. The rules in `quorum/modules/AGENTS.md` and `.github/copilot-instructions.md` apply to all subsequent work.
|
|
252
|
+
|
|
253
|
+
Key reminders:
|
|
254
|
+
- **Query Oracle before proposing anything.** `oracle.query("what you're about to do")` first.
|
|
255
|
+
- **Never call `oracle.commit()` autonomously.** Only `oracle.propose()`. A human commits.
|
|
256
|
+
- **Chronicle entries are ground truth.** Respect `refuted` entries — do not retry what has already failed.
|
package/bin/init.js
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* quorum init
|
|
4
|
+
*
|
|
5
|
+
* Drops Quorum into an existing Node.js project.
|
|
6
|
+
* Run from the target project root:
|
|
7
|
+
*
|
|
8
|
+
* npx github:balpal4495/Quorum init
|
|
9
|
+
*
|
|
10
|
+
* Zero external dependencies — uses only Node.js built-ins.
|
|
11
|
+
* Requires Node.js 18+.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { promises as fs } from "fs"
|
|
15
|
+
import path from "path"
|
|
16
|
+
import { fileURLToPath } from "url"
|
|
17
|
+
import { execSync } from "child_process"
|
|
18
|
+
|
|
19
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
20
|
+
const QUORUM_ROOT = path.resolve(__dirname, "..")
|
|
21
|
+
const TARGET = process.cwd()
|
|
22
|
+
|
|
23
|
+
// ── Deps Quorum requires in the host project ───────────────────────────────
|
|
24
|
+
|
|
25
|
+
const DEPS = {
|
|
26
|
+
zod: "^3.23.0",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const OPTIONAL_DEPS = {
|
|
30
|
+
vectordb: "^0.4.0",
|
|
31
|
+
"@xenova/transformers": "^2.17.0",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ── Logging ────────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const c = {
|
|
37
|
+
bold: (s) => `\x1b[1m${s}\x1b[0m`,
|
|
38
|
+
green: (s) => `\x1b[32m${s}\x1b[0m`,
|
|
39
|
+
blue: (s) => `\x1b[34m${s}\x1b[0m`,
|
|
40
|
+
dim: (s) => `\x1b[90m${s}\x1b[0m`,
|
|
41
|
+
yellow:(s) => `\x1b[33m${s}\x1b[0m`,
|
|
42
|
+
red: (s) => `\x1b[31m${s}\x1b[0m`,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const log = {
|
|
46
|
+
section: (title) => console.log(`\n${c.bold(title)}`),
|
|
47
|
+
created: (file) => console.log(` ${c.green("+ created ")} ${file}`),
|
|
48
|
+
appended:(file) => console.log(` ${c.blue("~ appended")} ${file}`),
|
|
49
|
+
skipped: (file) => console.log(` ${c.dim("· skipped ")} ${file}`),
|
|
50
|
+
warn: (msg) => console.log(` ${c.yellow("⚠ " + msg)}`),
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Helpers ────────────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
async function exists(p) {
|
|
56
|
+
return fs.access(p).then(() => true).catch(() => false)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function readJson(p) {
|
|
60
|
+
return JSON.parse(await fs.readFile(p, "utf8"))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function geminiAvailable() {
|
|
64
|
+
try {
|
|
65
|
+
execSync("which gemini", { stdio: "ignore" })
|
|
66
|
+
return true
|
|
67
|
+
} catch {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ── Steps ──────────────────────────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
async function guardAlreadyInitialized() {
|
|
75
|
+
if (await exists(path.join(TARGET, "quorum", "modules"))) {
|
|
76
|
+
console.log(c.yellow("\nQuorum is already initialized in this project."))
|
|
77
|
+
console.log("Remove quorum/ first if you want to reinitialize.\n")
|
|
78
|
+
process.exit(0)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function copyModules() {
|
|
83
|
+
log.section("Copying modules")
|
|
84
|
+
|
|
85
|
+
const src = path.join(QUORUM_ROOT, "modules")
|
|
86
|
+
const dest = path.join(TARGET, "quorum", "modules")
|
|
87
|
+
await fs.cp(src, dest, {
|
|
88
|
+
recursive: true,
|
|
89
|
+
filter: (src) =>
|
|
90
|
+
!src.includes("__tests__") &&
|
|
91
|
+
!src.includes(".test.ts") &&
|
|
92
|
+
!src.includes(".spec.ts"),
|
|
93
|
+
})
|
|
94
|
+
log.created("quorum/modules/")
|
|
95
|
+
|
|
96
|
+
await fs.copyFile(
|
|
97
|
+
path.join(QUORUM_ROOT, "SETUP.md"),
|
|
98
|
+
path.join(TARGET, "quorum", "SETUP.md"),
|
|
99
|
+
)
|
|
100
|
+
log.created("quorum/SETUP.md")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function mergeCopilotInstructions() {
|
|
104
|
+
log.section("Merging AI instruction files")
|
|
105
|
+
|
|
106
|
+
const src = path.join(QUORUM_ROOT, ".github", "copilot-instructions.md")
|
|
107
|
+
const dest = path.join(TARGET, ".github", "copilot-instructions.md")
|
|
108
|
+
const content = await fs.readFile(src, "utf8")
|
|
109
|
+
|
|
110
|
+
await fs.mkdir(path.join(TARGET, ".github"), { recursive: true })
|
|
111
|
+
|
|
112
|
+
if (await exists(dest)) {
|
|
113
|
+
const existing = await fs.readFile(dest, "utf8")
|
|
114
|
+
if (existing.includes("<!-- quorum -->")) {
|
|
115
|
+
log.skipped(".github/copilot-instructions.md (already present)")
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
await fs.appendFile(dest, `\n\n---\n\n<!-- quorum -->\n${content}`, "utf8")
|
|
119
|
+
log.appended(".github/copilot-instructions.md")
|
|
120
|
+
} else {
|
|
121
|
+
await fs.writeFile(dest, content, "utf8")
|
|
122
|
+
log.created(".github/copilot-instructions.md")
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function mergeAgentsMd() {
|
|
127
|
+
const dest = path.join(TARGET, "AGENTS.md")
|
|
128
|
+
const section = [
|
|
129
|
+
"",
|
|
130
|
+
"## Quorum modules",
|
|
131
|
+
"",
|
|
132
|
+
"See [quorum/modules/AGENTS.md](quorum/modules/AGENTS.md) for Oracle, Jury, and Council internals.",
|
|
133
|
+
"See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.",
|
|
134
|
+
"",
|
|
135
|
+
].join("\n")
|
|
136
|
+
|
|
137
|
+
if (await exists(dest)) {
|
|
138
|
+
const existing = await fs.readFile(dest, "utf8")
|
|
139
|
+
if (existing.includes("quorum/modules/AGENTS.md")) {
|
|
140
|
+
log.skipped("AGENTS.md (already present)")
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
await fs.appendFile(dest, section, "utf8")
|
|
144
|
+
log.appended("AGENTS.md")
|
|
145
|
+
} else {
|
|
146
|
+
await fs.writeFile(dest, `# Agent Instructions\n${section}`, "utf8")
|
|
147
|
+
log.created("AGENTS.md")
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async function mergeClaudeMd() {
|
|
152
|
+
const dest = path.join(TARGET, "CLAUDE.md")
|
|
153
|
+
const section = `
|
|
154
|
+
## Quorum modules
|
|
155
|
+
|
|
156
|
+
See [quorum/modules/CLAUDE.md](quorum/modules/CLAUDE.md) for Oracle, Jury, and Council internals.
|
|
157
|
+
See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.
|
|
158
|
+
|
|
159
|
+
## Gemini CLI (optional assistant)
|
|
160
|
+
|
|
161
|
+
Before attempting any Gemini call, check availability:
|
|
162
|
+
|
|
163
|
+
\`\`\`bash
|
|
164
|
+
which gemini 2>/dev/null
|
|
165
|
+
\`\`\`
|
|
166
|
+
|
|
167
|
+
If the command returns empty, skip this section entirely. The project is fully functional
|
|
168
|
+
without Gemini. Never try to install it or ask the user to install it mid-task.
|
|
169
|
+
|
|
170
|
+
If Gemini is available, use it as a large-context assistant for tasks that require
|
|
171
|
+
surveying many files at once — it can hold the entire codebase in a single context window.
|
|
172
|
+
|
|
173
|
+
\`\`\`bash
|
|
174
|
+
# The Bash tool does not auto-source shell profiles — always prefix with source:
|
|
175
|
+
source ~/.zshrc && gemini -p "Summarise the public API across all modules"
|
|
176
|
+
source ~/.zshrc && gemini -p "I'm about to change X. What should I watch out for?"
|
|
177
|
+
\`\`\`
|
|
178
|
+
|
|
179
|
+
You reason about Gemini's output — it assists, you decide. Never pass its response to the
|
|
180
|
+
user unfiltered. If Gemini contradicts what you know from reading the code, trust your reading.
|
|
181
|
+
`
|
|
182
|
+
|
|
183
|
+
if (await exists(dest)) {
|
|
184
|
+
const existing = await fs.readFile(dest, "utf8")
|
|
185
|
+
if (existing.includes("quorum/modules/CLAUDE.md")) {
|
|
186
|
+
log.skipped("CLAUDE.md (already present)")
|
|
187
|
+
return
|
|
188
|
+
}
|
|
189
|
+
await fs.appendFile(dest, section, "utf8")
|
|
190
|
+
log.appended("CLAUDE.md")
|
|
191
|
+
} else {
|
|
192
|
+
await fs.writeFile(dest, `# Claude Instructions\n${section}`, "utf8")
|
|
193
|
+
log.created("CLAUDE.md")
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async function mergeGeminiMd() {
|
|
198
|
+
const dest = path.join(TARGET, "GEMINI.md")
|
|
199
|
+
|
|
200
|
+
if (await exists(dest)) {
|
|
201
|
+
log.skipped("GEMINI.md (already present)")
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (!geminiAvailable()) {
|
|
206
|
+
log.skipped("GEMINI.md (Gemini CLI not detected — install it later to enable)")
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const src = path.join(QUORUM_ROOT, "GEMINI.md")
|
|
211
|
+
if (await exists(src)) {
|
|
212
|
+
await fs.copyFile(src, dest)
|
|
213
|
+
log.created("GEMINI.md")
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function updatePackageJson() {
|
|
218
|
+
log.section("Updating package.json")
|
|
219
|
+
|
|
220
|
+
const pkgPath = path.join(TARGET, "package.json")
|
|
221
|
+
let pkg
|
|
222
|
+
|
|
223
|
+
if (await exists(pkgPath)) {
|
|
224
|
+
pkg = await readJson(pkgPath)
|
|
225
|
+
} else {
|
|
226
|
+
pkg = { name: path.basename(TARGET), version: "0.1.0", private: true }
|
|
227
|
+
log.warn("No package.json found — creating a minimal one")
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
pkg.dependencies = pkg.dependencies ?? {}
|
|
231
|
+
pkg.optionalDependencies = pkg.optionalDependencies ?? {}
|
|
232
|
+
|
|
233
|
+
const added = []
|
|
234
|
+
|
|
235
|
+
for (const [name, version] of Object.entries(DEPS)) {
|
|
236
|
+
if (!pkg.dependencies[name]) {
|
|
237
|
+
pkg.dependencies[name] = version
|
|
238
|
+
added.push(name)
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for (const [name, version] of Object.entries(OPTIONAL_DEPS)) {
|
|
243
|
+
if (!pkg.optionalDependencies[name]) {
|
|
244
|
+
pkg.optionalDependencies[name] = version
|
|
245
|
+
added.push(`${name} (optional)`)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8")
|
|
250
|
+
|
|
251
|
+
if (added.length > 0) {
|
|
252
|
+
log.appended(`package.json — added: ${added.join(", ")}`)
|
|
253
|
+
} else {
|
|
254
|
+
log.skipped("package.json (all deps already present)")
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function updateGitignore() {
|
|
259
|
+
log.section("Updating .gitignore")
|
|
260
|
+
|
|
261
|
+
const dest = path.join(TARGET, ".gitignore")
|
|
262
|
+
const block = [
|
|
263
|
+
"",
|
|
264
|
+
"# Quorum — Chronicle",
|
|
265
|
+
"# entries/ is a binary vector store — do not commit",
|
|
266
|
+
".chronicle/entries/",
|
|
267
|
+
".chronicle/query-log.jsonl",
|
|
268
|
+
"",
|
|
269
|
+
].join("\n")
|
|
270
|
+
|
|
271
|
+
if (await exists(dest)) {
|
|
272
|
+
const existing = await fs.readFile(dest, "utf8")
|
|
273
|
+
if (existing.includes(".chronicle/entries/")) {
|
|
274
|
+
log.skipped(".gitignore (already present)")
|
|
275
|
+
return
|
|
276
|
+
}
|
|
277
|
+
await fs.appendFile(dest, block, "utf8")
|
|
278
|
+
log.appended(".gitignore")
|
|
279
|
+
} else {
|
|
280
|
+
await fs.writeFile(dest, block.trimStart(), "utf8")
|
|
281
|
+
log.created(".gitignore")
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function createChronicle() {
|
|
286
|
+
log.section("Creating Chronicle")
|
|
287
|
+
|
|
288
|
+
await fs.mkdir(path.join(TARGET, ".chronicle", "proposals"), { recursive: true })
|
|
289
|
+
log.created(".chronicle/proposals/")
|
|
290
|
+
|
|
291
|
+
await fs.mkdir(path.join(TARGET, ".chronicle", "committed"), { recursive: true })
|
|
292
|
+
log.created(".chronicle/committed/")
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// ── Main ───────────────────────────────────────────────────────────────────
|
|
296
|
+
|
|
297
|
+
async function main() {
|
|
298
|
+
console.log(c.bold("\nQuorum init"))
|
|
299
|
+
console.log(`Target: ${c.dim(TARGET)}\n`)
|
|
300
|
+
|
|
301
|
+
if (TARGET === QUORUM_ROOT) {
|
|
302
|
+
console.log(c.yellow("Run this from your project directory, not the Quorum repo itself."))
|
|
303
|
+
process.exit(1)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
await guardAlreadyInitialized()
|
|
307
|
+
await copyModules()
|
|
308
|
+
await mergeCopilotInstructions()
|
|
309
|
+
await mergeAgentsMd()
|
|
310
|
+
await mergeClaudeMd()
|
|
311
|
+
await mergeGeminiMd()
|
|
312
|
+
await updatePackageJson()
|
|
313
|
+
await updateGitignore()
|
|
314
|
+
await createChronicle()
|
|
315
|
+
|
|
316
|
+
const hasGemini = geminiAvailable()
|
|
317
|
+
|
|
318
|
+
console.log(`\n${c.green("✓ Quorum initialized.")}`)
|
|
319
|
+
console.log("\nNext steps:")
|
|
320
|
+
console.log(" 1. npm install")
|
|
321
|
+
console.log(" 2. Wire setup() into your entry point:\n")
|
|
322
|
+
console.log(c.dim(' import { setup } from "./quorum/modules/setup"'))
|
|
323
|
+
console.log(c.dim(' const { oracle, evaluate, deliberate } = await setup({ llm: yourProvider })'))
|
|
324
|
+
console.log("\n Or tell your AI: \"follow quorum/SETUP.md\"")
|
|
325
|
+
|
|
326
|
+
if (!hasGemini) {
|
|
327
|
+
console.log(`\n ${c.dim("Optional: install Gemini CLI for large-context assistance")}`)
|
|
328
|
+
console.log(c.dim(" npm install -g @google/gemini-cli + set GEMINI_API_KEY"))
|
|
329
|
+
console.log(c.dim(" See quorum/SETUP.md Step 10 for details."))
|
|
330
|
+
} else {
|
|
331
|
+
console.log(`\n ${c.green("✓ Gemini CLI detected")} — GEMINI.md written. Set GEMINI_API_KEY if not already set.`)
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
console.log("")
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
async function cli() {
|
|
338
|
+
const command = process.argv[2] ?? ""
|
|
339
|
+
|
|
340
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
341
|
+
console.log(`\n${c.bold("quorum")} — portable reasoning layer for agentic codebases\n`)
|
|
342
|
+
console.log("Usage:")
|
|
343
|
+
console.log(` ${c.blue("npx @balpal4495/quorum init")} Scaffold Quorum into a project (or meld into an existing one)`)
|
|
344
|
+
console.log(` ${c.blue("npx quorum --version")} Print version\n`)
|
|
345
|
+
return
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (command === "--version" || command === "-v" || command === "version") {
|
|
349
|
+
console.log("0.1.0")
|
|
350
|
+
return
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (command === "init") {
|
|
354
|
+
await main()
|
|
355
|
+
return
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.error(c.red(`\nUnknown command: ${command}`))
|
|
359
|
+
console.error("Run 'npx quorum help' for usage.")
|
|
360
|
+
process.exit(1)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
cli().catch((err) => {
|
|
364
|
+
console.error(c.red("\nQuorum failed:"), err.message)
|
|
365
|
+
process.exit(1)
|
|
366
|
+
})
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# modules/ — Agent Instructions
|
|
2
|
+
|
|
3
|
+
Supplements the root `AGENTS.md` / `copilot-instructions.md` with module-specific internals.
|
|
4
|
+
When working inside this folder, follow these rules in addition to the root guidelines.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## File ownership
|
|
9
|
+
|
|
10
|
+
### Oracle
|
|
11
|
+
| File | Owns |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `oracle/query.ts` | Two-pass retrieval (vector → BM25 → RRF fusion). Score threshold. Query log. |
|
|
14
|
+
| `oracle/bm25.ts` | BM25 scoring algorithm. Domain term extraction for query enrichment. |
|
|
15
|
+
| `oracle/propose.ts` | `propose()` + `commit()`. The human-gated write path. Do not add auto-commit logic here. |
|
|
16
|
+
| `oracle/log.ts` | Best-effort JSONL query log writer. Must never throw to callers. |
|
|
17
|
+
| `oracle/adapters/lance-db.ts` | LanceDB `VectorStore` implementation. Swappable — do not couple oracle internals to this. |
|
|
18
|
+
| `oracle/adapters/xenova-embedder.ts` | Local ONNX embedder. Swappable — do not couple oracle internals to this. |
|
|
19
|
+
|
|
20
|
+
### Jury
|
|
21
|
+
| File | Owns |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `jury/schema.ts` | Zod schema for structured LLM output. Source of truth for `JuryOutput` shape. |
|
|
24
|
+
| `jury/evaluate.ts` | Four-dimension evaluation. **`council_brief` is always overridden from confidence here — do not remove this enforcement.** |
|
|
25
|
+
|
|
26
|
+
### Council
|
|
27
|
+
| File | Owns |
|
|
28
|
+
|---|---|
|
|
29
|
+
| `council/personas.ts` | Default advisor personas. Safe to extend. Do not remove existing personas without good reason. |
|
|
30
|
+
| `council/frame.ts` | Sets deliberation tone from `council_brief`. Challenge vs pressure-test framing lives here. |
|
|
31
|
+
| `council/advisors.ts` | Parallel advisor fan-out. Advisors must cite Oracle entry IDs — enforced in the prompt. |
|
|
32
|
+
| `council/reviewers.ts` | Anonymisation of advisor responses + parallel reviewer fan-out. Anonymisation must happen before reviewers see responses. |
|
|
33
|
+
| `council/chairman.ts` | Verdict synthesis + Zod validation. Throws on bad output — do not add fallbacks. |
|
|
34
|
+
| `council/deliberate.ts` | Full pipeline orchestration. Calls `oracle.propose()` at the end — never `oracle.commit()`. |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Extension points
|
|
39
|
+
|
|
40
|
+
**Swap the vector store** — implement `VectorStore` from `oracle/types.ts` and pass it to `createOracleClient()` or `setup()`.
|
|
41
|
+
|
|
42
|
+
**Swap the embedder** — pass `embedder: yourFn` to `setup()`. Must return a consistent-dimension float array.
|
|
43
|
+
|
|
44
|
+
**Add advisor personas** — extend `DEFAULT_PERSONAS` in `council/personas.ts`, or pass a custom personas array directly to `fanOutAdvisors()`.
|
|
45
|
+
|
|
46
|
+
**Use different models per step** — pass `models` to `setup()` or `council.deliberate()` deps. Cheaper models for advisors, stronger for chairman is the intended pattern.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Invariants — do not break these
|
|
51
|
+
|
|
52
|
+
- `oracle.commit()` is never called without explicit human input. `deliberate()` calls `propose()` only.
|
|
53
|
+
- `jury/evaluate.ts` always computes `council_brief` from `confidence` after parsing — never trusts the LLM value.
|
|
54
|
+
- `chairman.ts` and `jury/evaluate.ts` throw on schema validation failure. Do not add try/catch that swallows these errors.
|
|
55
|
+
- Query logging in `oracle/log.ts` is always best-effort — callers must not fail because of a log write error.
|
|
56
|
+
- `VectorStore` and `embedder` are always injected — never imported directly inside Oracle logic.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Tests
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx vitest run modules/
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Tests live in `__tests__/` inside each module folder. Use `vi.fn()` for LLM providers and vector stores — never call a real LLM in tests.
|