@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.
Files changed (39) hide show
  1. package/.github/copilot-instructions.md +94 -0
  2. package/CLAUDE.md +86 -0
  3. package/GEMINI.md +73 -0
  4. package/LICENSE +21 -0
  5. package/README.md +202 -0
  6. package/SETUP.md +256 -0
  7. package/bin/init.js +366 -0
  8. package/modules/AGENTS.md +66 -0
  9. package/modules/CLAUDE.md +64 -0
  10. package/modules/README.md +251 -0
  11. package/modules/council/advisors.ts +68 -0
  12. package/modules/council/chairman.ts +112 -0
  13. package/modules/council/deliberate.ts +106 -0
  14. package/modules/council/frame.ts +54 -0
  15. package/modules/council/index.ts +4 -0
  16. package/modules/council/personas.ts +57 -0
  17. package/modules/council/reviewers.ts +81 -0
  18. package/modules/council/types.ts +45 -0
  19. package/modules/jury/evaluate.ts +112 -0
  20. package/modules/jury/index.ts +3 -0
  21. package/modules/jury/schema.ts +15 -0
  22. package/modules/jury/types.ts +31 -0
  23. package/modules/oracle/adapters/lance-db.ts +81 -0
  24. package/modules/oracle/adapters/xenova-embedder.ts +43 -0
  25. package/modules/oracle/bm25.ts +92 -0
  26. package/modules/oracle/index.ts +36 -0
  27. package/modules/oracle/log.ts +15 -0
  28. package/modules/oracle/propose.ts +148 -0
  29. package/modules/oracle/query.ts +145 -0
  30. package/modules/oracle/summary.ts +115 -0
  31. package/modules/oracle/types.ts +32 -0
  32. package/modules/sentinel/assert.ts +95 -0
  33. package/modules/sentinel/coverage.ts +106 -0
  34. package/modules/sentinel/drift.ts +159 -0
  35. package/modules/sentinel/index.ts +6 -0
  36. package/modules/sentinel/review.ts +207 -0
  37. package/modules/setup.ts +153 -0
  38. package/modules/shared/types.ts +148 -0
  39. 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.