@balpal4495/quorum 0.3.0 → 0.4.2

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.
@@ -0,0 +1,244 @@
1
+ import { promises as fs } from "fs"
2
+ import path from "path"
3
+ import { fileURLToPath } from "url"
4
+ import { execSync } from "child_process"
5
+ import { createRequire } from "module"
6
+ import { c, log } from "../shared/colors.js"
7
+
8
+ const _require = createRequire(import.meta.url)
9
+
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
+ const QUORUM_ROOT = path.resolve(__dirname, "../..")
12
+
13
+ const DEPS = { zod: "^3.23.0" }
14
+ const OPTIONAL_DEPS = {
15
+ vectordb: "^0.4.0",
16
+ "@xenova/transformers": "^2.17.0",
17
+ }
18
+
19
+ async function exists(p) {
20
+ return fs.access(p).then(() => true).catch(() => false)
21
+ }
22
+
23
+ async function readJson(p) {
24
+ return JSON.parse(await fs.readFile(p, "utf8"))
25
+ }
26
+
27
+ function geminiAvailable() {
28
+ try { execSync("which gemini", { stdio: "ignore" }); return true } catch { return false }
29
+ }
30
+
31
+ async function guardAlreadyInitialized(target) {
32
+ if (await exists(path.join(target, "quorum", "modules"))) {
33
+ console.log(c.yellow("\nQuorum is already initialized in this project."))
34
+ console.log("Remove quorum/ first if you want to reinitialize.\n")
35
+ process.exit(0)
36
+ }
37
+ }
38
+
39
+ async function copyModules(target) {
40
+ log.section("Copying modules")
41
+ const src = path.join(QUORUM_ROOT, "modules")
42
+ const dest = path.join(target, "quorum", "modules")
43
+ await fs.cp(src, dest, {
44
+ recursive: true,
45
+ filter: (src) =>
46
+ !src.includes("__tests__") &&
47
+ !src.includes(".test.ts") &&
48
+ !src.includes(".spec.ts"),
49
+ })
50
+ log.created("quorum/modules/")
51
+ await fs.copyFile(
52
+ path.join(QUORUM_ROOT, "SETUP.md"),
53
+ path.join(target, "quorum", "SETUP.md"),
54
+ )
55
+ log.created("quorum/SETUP.md")
56
+ }
57
+
58
+ async function copyEvals(target) {
59
+ const src = path.join(QUORUM_ROOT, "evals")
60
+ const dest = path.join(target, "quorum", "evals")
61
+ await fs.cp(src, dest, { recursive: true })
62
+ log.created("quorum/evals/")
63
+ }
64
+
65
+ async function mergeCopilotInstructions(target) {
66
+ log.section("Merging AI instruction files")
67
+ const src = path.join(QUORUM_ROOT, ".github", "copilot-instructions.md")
68
+ const dest = path.join(target, ".github", "copilot-instructions.md")
69
+ const content = await fs.readFile(src, "utf8")
70
+ await fs.mkdir(path.join(target, ".github"), { recursive: true })
71
+ if (await exists(dest)) {
72
+ const existing = await fs.readFile(dest, "utf8")
73
+ if (existing.includes("<!-- quorum -->")) { log.skipped(".github/copilot-instructions.md (already present)"); return }
74
+ await fs.appendFile(dest, `\n\n---\n\n<!-- quorum -->\n${content}`, "utf8")
75
+ log.appended(".github/copilot-instructions.md")
76
+ } else {
77
+ await fs.writeFile(dest, content, "utf8")
78
+ log.created(".github/copilot-instructions.md")
79
+ }
80
+ }
81
+
82
+ async function mergeAgentsMd(target) {
83
+ const dest = path.join(target, "AGENTS.md")
84
+ const section = [
85
+ "", "## Quorum modules", "",
86
+ "See [quorum/modules/AGENTS.md](quorum/modules/AGENTS.md) for Oracle, Jury, and Council internals.",
87
+ "See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.", "",
88
+ ].join("\n")
89
+ if (await exists(dest)) {
90
+ const existing = await fs.readFile(dest, "utf8")
91
+ if (existing.includes("quorum/modules/AGENTS.md")) { log.skipped("AGENTS.md (already present)"); return }
92
+ await fs.appendFile(dest, section, "utf8")
93
+ log.appended("AGENTS.md")
94
+ } else {
95
+ await fs.writeFile(dest, `# Agent Instructions\n${section}`, "utf8")
96
+ log.created("AGENTS.md")
97
+ }
98
+ }
99
+
100
+ async function mergeClaudeMd(target) {
101
+ const dest = path.join(target, "CLAUDE.md")
102
+ const section = `
103
+ ## Quorum modules
104
+
105
+ See [quorum/modules/CLAUDE.md](quorum/modules/CLAUDE.md) for Oracle, Jury, and Council internals.
106
+ See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.
107
+
108
+ ## Gemini CLI (optional assistant)
109
+
110
+ Before attempting any Gemini call, check availability:
111
+
112
+ \`\`\`bash
113
+ which gemini 2>/dev/null
114
+ \`\`\`
115
+
116
+ If the command returns empty, skip this section entirely. The project is fully functional
117
+ without Gemini. Never try to install it or ask the user to install it mid-task.
118
+
119
+ If Gemini is available, use it as a large-context assistant for tasks that require
120
+ surveying many files at once — it can hold the entire codebase in a single context window.
121
+
122
+ \`\`\`bash
123
+ # The Bash tool does not auto-source shell profiles — always prefix with source:
124
+ source ~/.zshrc && gemini -p "Summarise the public API across all modules"
125
+ source ~/.zshrc && gemini -p "I'm about to change X. What should I watch out for?"
126
+ \`\`\`
127
+
128
+ You reason about Gemini's output — it assists, you decide. Never pass its response to the
129
+ user unfiltered. If Gemini contradicts what you know from reading the code, trust your reading.
130
+ `
131
+ if (await exists(dest)) {
132
+ const existing = await fs.readFile(dest, "utf8")
133
+ if (existing.includes("quorum/modules/CLAUDE.md")) { log.skipped("CLAUDE.md (already present)"); return }
134
+ await fs.appendFile(dest, section, "utf8")
135
+ log.appended("CLAUDE.md")
136
+ } else {
137
+ await fs.writeFile(dest, `# Claude Instructions\n${section}`, "utf8")
138
+ log.created("CLAUDE.md")
139
+ }
140
+ }
141
+
142
+ async function mergeGeminiMd(target) {
143
+ const dest = path.join(target, "GEMINI.md")
144
+ if (await exists(dest)) { log.skipped("GEMINI.md (already present)"); return }
145
+ if (!geminiAvailable()) { log.skipped("GEMINI.md (Gemini CLI not detected — install it later to enable)"); return }
146
+ const src = path.join(QUORUM_ROOT, "GEMINI.md")
147
+ if (await exists(src)) { await fs.copyFile(src, dest); log.created("GEMINI.md") }
148
+ }
149
+
150
+ async function updatePackageJson(target) {
151
+ log.section("Updating package.json")
152
+ const pkgPath = path.join(target, "package.json")
153
+ let pkg
154
+ if (await exists(pkgPath)) {
155
+ pkg = await readJson(pkgPath)
156
+ } else {
157
+ pkg = { name: path.basename(target), version: "0.1.0", private: true }
158
+ log.warn("No package.json found — creating a minimal one")
159
+ }
160
+ pkg.dependencies = pkg.dependencies ?? {}
161
+ pkg.optionalDependencies = pkg.optionalDependencies ?? {}
162
+ const added = []
163
+ for (const [name, version] of Object.entries(DEPS)) {
164
+ if (!pkg.dependencies[name]) { pkg.dependencies[name] = version; added.push(name) }
165
+ }
166
+ for (const [name, version] of Object.entries(OPTIONAL_DEPS)) {
167
+ if (!pkg.optionalDependencies[name]) { pkg.optionalDependencies[name] = version; added.push(`${name} (optional)`) }
168
+ }
169
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8")
170
+ if (added.length > 0) {
171
+ log.appended(`package.json — added: ${added.join(", ")}`)
172
+ } else {
173
+ log.skipped("package.json (all deps already present)")
174
+ }
175
+ }
176
+
177
+ async function updateGitignore(target) {
178
+ log.section("Updating .gitignore")
179
+ const dest = path.join(target, ".gitignore")
180
+ const block = [
181
+ "", "# Quorum — Chronicle",
182
+ "# entries/ is a binary vector store — do not commit",
183
+ ".chronicle/entries/", ".chronicle/query-log.jsonl", "",
184
+ ].join("\n")
185
+ if (await exists(dest)) {
186
+ const existing = await fs.readFile(dest, "utf8")
187
+ if (existing.includes(".chronicle/entries/")) { log.skipped(".gitignore (already present)"); return }
188
+ await fs.appendFile(dest, block, "utf8")
189
+ log.appended(".gitignore")
190
+ } else {
191
+ await fs.writeFile(dest, block.trimStart(), "utf8")
192
+ log.created(".gitignore")
193
+ }
194
+ }
195
+
196
+ async function createChronicle(target) {
197
+ log.section("Creating Chronicle")
198
+ await fs.mkdir(path.join(target, ".chronicle", "proposals"), { recursive: true })
199
+ log.created(".chronicle/proposals/")
200
+ await fs.mkdir(path.join(target, ".chronicle", "committed"), { recursive: true })
201
+ log.created(".chronicle/committed/")
202
+ }
203
+
204
+ export async function run(PKG_VERSION) {
205
+ const target = process.cwd()
206
+
207
+ console.log(c.bold("\nQuorum init"))
208
+ console.log(`Target: ${c.dim(target)}\n`)
209
+
210
+ if (target === QUORUM_ROOT) {
211
+ console.log(c.yellow("Run this from your project directory, not the Quorum repo itself."))
212
+ process.exit(1)
213
+ }
214
+
215
+ await guardAlreadyInitialized(target)
216
+ await copyModules(target)
217
+ await copyEvals(target)
218
+ await mergeCopilotInstructions(target)
219
+ await mergeAgentsMd(target)
220
+ await mergeClaudeMd(target)
221
+ await mergeGeminiMd(target)
222
+ await updatePackageJson(target)
223
+ await updateGitignore(target)
224
+ await createChronicle(target)
225
+
226
+ const hasGemini = geminiAvailable()
227
+
228
+ console.log(`\n${c.green("✓ Quorum initialized.")}`)
229
+ console.log("\nNext steps:")
230
+ console.log(" 1. npm install")
231
+ console.log(" 2. Wire setup() into your entry point:\n")
232
+ console.log(c.dim(' import { setup } from "./quorum/modules/setup"'))
233
+ console.log(c.dim(' const { oracle, evaluate, deliberate } = await setup({ llm: yourProvider })'))
234
+ console.log("\n Or tell your AI: \"follow quorum/SETUP.md\"")
235
+
236
+ if (!hasGemini) {
237
+ console.log(`\n ${c.dim("Optional: install Gemini CLI for large-context assistance")}`)
238
+ console.log(c.dim(" npm install -g @google/gemini-cli + set GEMINI_API_KEY"))
239
+ console.log(c.dim(" See quorum/SETUP.md Step 10 for details."))
240
+ } else {
241
+ console.log(`\n ${c.green("✓ Gemini CLI detected")} — GEMINI.md written. Set GEMINI_API_KEY if not already set.`)
242
+ }
243
+ console.log("")
244
+ }
@@ -0,0 +1,160 @@
1
+ import { promises as fs, Dirent } from "fs"
2
+ import path from "path"
3
+ import { c } from "../shared/colors.js"
4
+ import { findChronicleDir, readCommitted } from "../shared/chronicle.js"
5
+
6
+ // Mirrors the ignored dirs in modules/sentinel/coverage.ts
7
+ const IGNORED_DIRS = new Set(["node_modules", "dist", ".git", ".chronicle", "coverage", "__tests__"])
8
+ const TEST_SUFFIXES = [".test.ts", ".spec.ts", ".test.js", ".spec.js"]
9
+
10
+ function parseArgs(argv) {
11
+ const args = { subcommand: "coverage", codebasePath: process.cwd(), json: false }
12
+ for (let i = 0; i < argv.length; i++) {
13
+ if (argv[i] === "coverage" || argv[i] === "drift") { args.subcommand = argv[i]; continue }
14
+ if ((argv[i] === "--path" || argv[i] === "-p") && argv[i + 1]) { args.codebasePath = argv[++i]; continue }
15
+ if (argv[i] === "--json") { args.json = true; continue }
16
+ }
17
+ return args
18
+ }
19
+
20
+ async function walkFiles(dir, extensions, excludeTestFiles) {
21
+ const results = []
22
+ async function recurse(current) {
23
+ let entries
24
+ try { entries = await fs.readdir(current, { withFileTypes: true, encoding: "utf8" }) } catch { return }
25
+ for (const entry of entries) {
26
+ if (entry.isDirectory()) {
27
+ if (!IGNORED_DIRS.has(entry.name)) await recurse(path.join(current, entry.name))
28
+ } else if (extensions.some(ext => entry.name.endsWith(ext))) {
29
+ if (excludeTestFiles && TEST_SUFFIXES.some(s => entry.name.endsWith(s))) continue
30
+ results.push(path.join(current, entry.name))
31
+ }
32
+ }
33
+ }
34
+ await recurse(dir)
35
+ return results
36
+ }
37
+
38
+ function isCovered(relativePath, entries) {
39
+ const matched = []
40
+ const normalised = relativePath.replace(/\\/g, "/")
41
+ for (const entry of entries) {
42
+ const hits = (entry.affected_areas ?? []).some(area => {
43
+ const normArea = area.replace(/\\/g, "/")
44
+ return normalised.includes(normArea) || normArea.includes(normalised)
45
+ })
46
+ if (hits) matched.push(entry.id)
47
+ }
48
+ return { covered: matched.length > 0, entryIds: matched }
49
+ }
50
+
51
+ function barChart(pct, width = 20) {
52
+ const filled = Math.round((pct / 100) * width)
53
+ const bar = "█".repeat(filled) + "░".repeat(width - filled)
54
+ const color = pct === 0 ? c.red : pct < 50 ? c.yellow : c.green
55
+ return color(bar)
56
+ }
57
+
58
+ async function runCoverage(args) {
59
+ const extensions = [".ts"]
60
+ const chronicleDir = await findChronicleDir(process.cwd())
61
+ const codebasePath = path.resolve(args.codebasePath)
62
+
63
+ if (!chronicleDir) {
64
+ console.error(`\n${c.red("No .chronicle/ directory found.")} Run ${c.bold("quorum init")} first.\n`)
65
+ process.exit(1)
66
+ }
67
+
68
+ const [entries, files] = await Promise.all([
69
+ readCommitted(chronicleDir),
70
+ walkFiles(codebasePath, extensions, true),
71
+ ])
72
+
73
+ const coverageByFile = files.map(absolute => {
74
+ const relative = path.relative(codebasePath, absolute).replace(/\\/g, "/")
75
+ const { covered, entryIds } = isCovered(relative, entries)
76
+ return { file: relative, covered, entryIds }
77
+ })
78
+
79
+ const covered = coverageByFile.filter(f => f.covered)
80
+ const uncovered = coverageByFile.filter(f => !f.covered)
81
+ const pct = files.length === 0 ? 0 : Math.round((covered.length / files.length) * 100)
82
+
83
+ if (args.json) {
84
+ console.log(JSON.stringify({
85
+ totalFiles: files.length,
86
+ coveredFiles: covered.length,
87
+ uncoveredFiles: uncovered.map(f => f.file),
88
+ coverageByFile,
89
+ percentage: pct,
90
+ }, null, 2))
91
+ return
92
+ }
93
+
94
+ // ── Header ─────────────────────────────────────────────────────────────────
95
+ console.log(`\n${c.bold("Chronicle coverage")} ${c.dim(codebasePath)}\n`)
96
+ console.log(` ${barChart(pct)} ${pct}% (${covered.length}/${files.length} files)\n`)
97
+
98
+ if (entries.length === 0) {
99
+ console.log(c.dim(" No committed Chronicle entries — nothing to match against.\n"))
100
+ return
101
+ }
102
+
103
+ if (files.length === 0) {
104
+ console.log(c.dim(` No .ts files found under ${codebasePath}\n`))
105
+ console.log(c.dim(` Tip: quorum sentinel coverage --path ./modules\n`))
106
+ return
107
+ }
108
+
109
+ // ── Covered files ──────────────────────────────────────────────────────────
110
+ if (covered.length > 0) {
111
+ console.log(c.bold("Covered"))
112
+ for (const f of covered) {
113
+ console.log(` ${c.green("✓")} ${f.file} ${c.dim(`(${f.entryIds.length} ${f.entryIds.length === 1 ? "entry" : "entries"})`)}`)
114
+ }
115
+ console.log("")
116
+ }
117
+
118
+ // ── Uncovered files ────────────────────────────────────────────────────────
119
+ if (uncovered.length > 0) {
120
+ console.log(c.bold("Uncovered") + c.dim(" (no Chronicle entries reference these files)"))
121
+ const show = uncovered.slice(0, 20)
122
+ for (const f of show) {
123
+ console.log(` ${c.dim("✗")} ${f.file}`)
124
+ }
125
+ if (uncovered.length > 20) {
126
+ console.log(` ${c.dim(`… and ${uncovered.length - 20} more`)}`)
127
+ }
128
+ console.log("")
129
+ }
130
+
131
+ // ── Tip ────────────────────────────────────────────────────────────────────
132
+ if (pct < 100) {
133
+ console.log(c.dim(` Tip: add Chronicle entries for uncovered files via oracle.propose() in your app,`))
134
+ console.log(c.dim(` then run quorum commit <id> to index them.\n`))
135
+ }
136
+ }
137
+
138
+ export async function run(argv) {
139
+ const args = parseArgs(argv)
140
+
141
+ if (args.subcommand === "drift") {
142
+ console.log(`\n${c.yellow("quorum sentinel drift")} requires an LLM provider and is not available as a standalone CLI command.`)
143
+ console.log(c.dim("\nUse the sentinelAssertions() helper in your test suite instead:"))
144
+ console.log(c.dim("\n import { sentinelAssertions } from \"./quorum/modules/sentinel\""))
145
+ console.log(c.dim(" const assertions = sentinelAssertions({ llm: yourProvider })"))
146
+ console.log(c.dim(" describe(\"sentinel\", () => { assertions.forEach(a => a()) })\n"))
147
+ process.exit(0)
148
+ }
149
+
150
+ if (args.subcommand === "coverage" || !argv.length) {
151
+ await runCoverage(args)
152
+ return
153
+ }
154
+
155
+ console.error(`\n${c.bold("quorum sentinel")} — Chronicle analysis\n`)
156
+ console.error("Subcommands:")
157
+ console.error(` quorum sentinel coverage [--path <dir>] Chronicle coverage of source files`)
158
+ console.error(` quorum sentinel coverage --json Machine-readable output`)
159
+ console.error(` quorum sentinel drift (requires LLM — use sentinelAssertions() instead)\n`)
160
+ }
@@ -0,0 +1,117 @@
1
+ import { c } from "../shared/colors.js"
2
+ import { findChronicleDir, readProposals, readCommitted, entryText } from "../shared/chronicle.js"
3
+
4
+ function relativeTime(isoString) {
5
+ const ms = Date.now() - new Date(isoString).getTime()
6
+ const mins = Math.floor(ms / 60_000)
7
+ const hours = Math.floor(ms / 3_600_000)
8
+ const days = Math.floor(ms / 86_400_000)
9
+ if (mins < 2) return "just now"
10
+ if (mins < 60) return `${mins}m ago`
11
+ if (hours < 24) return `${hours}h ago`
12
+ if (days < 30) return `${days}d ago`
13
+ return new Date(isoString).toISOString().slice(0, 10)
14
+ }
15
+
16
+ function statusBadge(status) {
17
+ switch (status) {
18
+ case "accepted": return c.green("accepted")
19
+ case "refuted": return c.red("refuted")
20
+ case "superseded":return c.yellow("superseded")
21
+ default: return c.dim(status ?? "unknown")
22
+ }
23
+ }
24
+
25
+ export async function run(argv) {
26
+ const jsonMode = argv.includes("--json")
27
+ const cwd = process.cwd()
28
+
29
+ const chronicleDir = await findChronicleDir(cwd)
30
+
31
+ if (!chronicleDir) {
32
+ if (jsonMode) {
33
+ console.log(JSON.stringify({ error: "No .chronicle directory found", initialized: false }))
34
+ } else {
35
+ console.log(`\n${c.yellow("No .chronicle/ found")} in ${cwd} or any parent directory.`)
36
+ console.log(`${c.dim("Run")} quorum init ${c.dim("first.")}\n`)
37
+ }
38
+ process.exit(1)
39
+ }
40
+
41
+ const [proposals, committed] = await Promise.all([
42
+ readProposals(chronicleDir),
43
+ readCommitted(chronicleDir),
44
+ ])
45
+
46
+ if (jsonMode) {
47
+ console.log(JSON.stringify({
48
+ initialized: true,
49
+ chronicleDir,
50
+ proposals: proposals.length,
51
+ committed: committed.length,
52
+ pendingProposals: proposals.map(p => ({
53
+ id: p.proposalId,
54
+ key_insight: p.key_insight,
55
+ affected_areas: p.affected_areas,
56
+ })),
57
+ recentEntries: committed.slice(0, 5).map(e => ({
58
+ id: e.id,
59
+ status: e.status,
60
+ key_insight: e.key_insight,
61
+ timestamp: e.timestamp,
62
+ })),
63
+ }, null, 2))
64
+ return
65
+ }
66
+
67
+ // ── Header ────────────────────────────────────────────────────────────────
68
+ console.log(`\n${c.bold("Chronicle status")} ${c.dim(chronicleDir)}\n`)
69
+
70
+ // ── Counts ────────────────────────────────────────────────────────────────
71
+ const acceptedCount = committed.filter(e => e.status === "accepted").length
72
+ const refutedCount = committed.filter(e => e.status === "refuted").length
73
+ const otherCount = committed.length - acceptedCount - refutedCount
74
+
75
+ console.log(` ${c.bold(String(committed.length).padStart(4))} committed entries ` +
76
+ `(${c.green(acceptedCount + " accepted")}, ${c.red(refutedCount + " refuted")}${otherCount ? `, ${otherCount} other` : ""})`)
77
+ console.log(` ${c.bold(String(proposals.length).padStart(4))} pending proposals\n`)
78
+
79
+ // ── Pending proposals ─────────────────────────────────────────────────────
80
+ if (proposals.length > 0) {
81
+ console.log(c.bold("Pending proposals") + c.dim(" (awaiting quorum commit <id>)"))
82
+ for (const p of proposals) {
83
+ const insight = (entryText(p) ?? "").slice(0, 72)
84
+ const areas = (p.affected_areas ?? []).join(", ").slice(0, 40)
85
+ console.log(` ${c.cyan(p.proposalId.slice(0, 8))} ${insight}`)
86
+ if (areas) console.log(` ${c.dim(areas)}`)
87
+ }
88
+ console.log("")
89
+ }
90
+
91
+ // ── Recent committed entries ───────────────────────────────────────────────
92
+ if (committed.length > 0) {
93
+ console.log(c.bold("Recent entries"))
94
+ for (const e of committed.slice(0, 8)) {
95
+ const insight = (entryText(e) ?? "").slice(0, 65)
96
+ const when = relativeTime(e.timestamp)
97
+ const areas = (e.affected_areas ?? []).join(", ").slice(0, 35)
98
+ console.log(
99
+ ` ${c.dim(e.id.slice(0, 8))} [${statusBadge(e.status)}] ` +
100
+ `${insight} ${c.dim(when)}`
101
+ )
102
+ if (areas) console.log(` ${c.dim(areas)}`)
103
+ }
104
+ if (committed.length > 8) {
105
+ console.log(` ${c.dim(`… and ${committed.length - 8} more`)}`)
106
+ }
107
+ console.log("")
108
+ } else {
109
+ console.log(c.dim(" No committed entries yet.\n"))
110
+ }
111
+
112
+ // ── Hint ──────────────────────────────────────────────────────────────────
113
+ if (proposals.length > 0) {
114
+ console.log(c.dim(` Tip: quorum commit <first-8-chars-of-id> to approve a proposal`))
115
+ console.log(c.dim(` quorum commit --list to see full proposal details\n`))
116
+ }
117
+ }
package/bin/init.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Drops Quorum into an existing Node.js project.
6
6
  * Run from the target project root:
7
7
  *
8
- * npx github:balpal4495/Quorum init
8
+ * npx @balpal4495/quorum@latest init
9
9
  *
10
10
  * Zero external dependencies — uses only Node.js built-ins.
11
11
  * Requires Node.js 18+.
@@ -104,6 +104,13 @@ async function copyModules() {
104
104
  log.created("quorum/SETUP.md")
105
105
  }
106
106
 
107
+ async function copyEvals() {
108
+ const src = path.join(QUORUM_ROOT, "evals")
109
+ const dest = path.join(TARGET, "quorum", "evals")
110
+ await fs.cp(src, dest, { recursive: true })
111
+ log.created("quorum/evals/")
112
+ }
113
+
107
114
  async function mergeCopilotInstructions() {
108
115
  log.section("Merging AI instruction files")
109
116
 
@@ -309,6 +316,7 @@ async function main() {
309
316
 
310
317
  await guardAlreadyInitialized()
311
318
  await copyModules()
319
+ await copyEvals()
312
320
  await mergeCopilotInstructions()
313
321
  await mergeAgentsMd()
314
322
  await mergeClaudeMd()
package/bin/quorum.js ADDED
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env node
2
+ import { createRequire } from "module"
3
+ import { fileURLToPath } from "url"
4
+ import path from "path"
5
+
6
+ const _require = createRequire(import.meta.url)
7
+ const PKG_VERSION = _require("../package.json").version
8
+
9
+ const c = {
10
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
11
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
12
+ blue: (s) => `\x1b[34m${s}\x1b[0m`,
13
+ dim: (s) => `\x1b[90m${s}\x1b[0m`,
14
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
15
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
16
+ }
17
+
18
+ function help() {
19
+ console.log(`
20
+ ${c.bold("quorum")} ${c.dim(`v${PKG_VERSION}`)} — portable reasoning layer for agentic codebases
21
+
22
+ ${c.bold("Usage:")}
23
+ ${c.cyan("quorum init")} Scaffold Quorum into a project
24
+ ${c.cyan("quorum status")} Show Chronicle health and pending proposals
25
+ ${c.cyan("quorum check")} --outcome <x> --design <y> Preflight + risk (no LLM)
26
+ ${c.cyan("quorum commit")} <id> Approve and index a Chronicle proposal
27
+ ${c.cyan("quorum sentinel")} [coverage] Chronicle coverage of source files
28
+ ${c.cyan("quorum --version")} Print version
29
+
30
+ ${c.bold("quorum check")} flags:
31
+ --outcome -o What you want to achieve
32
+ --design -d How you plan to do it
33
+ --json Machine-readable JSON output
34
+ (also accepts JSON on stdin: ${c.dim('echo \'{"outcome":"…","design":"…"}\' | quorum check')})
35
+
36
+ ${c.bold("quorum commit")} flags:
37
+ --dry-run Preview without writing
38
+ --list List pending proposals
39
+
40
+ ${c.bold("quorum sentinel")} subcommands:
41
+ coverage [--path <dir>] Chronicle coverage for .ts files (default: cwd)
42
+ drift (requires LLM — use sentinelAssertions() instead)
43
+ --json Machine-readable output
44
+
45
+ ${c.bold("Exit codes")} (quorum check):
46
+ 0 low / medium risk
47
+ 1 high risk
48
+ 2 critical risk
49
+ `)
50
+ }
51
+
52
+ async function cli() {
53
+ const [,, command = "", ...rest] = process.argv
54
+
55
+ if (!command || command === "help" || command === "--help" || command === "-h") {
56
+ help(); return
57
+ }
58
+
59
+ if (command === "--version" || command === "-v" || command === "version") {
60
+ console.log(PKG_VERSION); return
61
+ }
62
+
63
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
64
+
65
+ if (command === "init") {
66
+ const { run } = await import(path.join(__dirname, "commands/init.js"))
67
+ await run(PKG_VERSION)
68
+ return
69
+ }
70
+
71
+ if (command === "status") {
72
+ const { run } = await import(path.join(__dirname, "commands/status.js"))
73
+ await run(rest)
74
+ return
75
+ }
76
+
77
+ if (command === "check") {
78
+ const { run } = await import(path.join(__dirname, "commands/check.js"))
79
+ await run(rest)
80
+ return
81
+ }
82
+
83
+ if (command === "commit") {
84
+ const { run } = await import(path.join(__dirname, "commands/commit.js"))
85
+ await run(rest)
86
+ return
87
+ }
88
+
89
+ if (command === "sentinel") {
90
+ const { run } = await import(path.join(__dirname, "commands/sentinel.js"))
91
+ await run(rest)
92
+ return
93
+ }
94
+
95
+ console.error(`${c.red(`Unknown command: ${command}`)}`)
96
+ console.error(`Run ${c.bold("quorum help")} for usage.`)
97
+ process.exit(1)
98
+ }
99
+
100
+ cli().catch((err) => {
101
+ console.error(`\x1b[31m\nQuorum failed:\x1b[0m ${err.message}`)
102
+ process.exit(1)
103
+ })