@balpal4495/quorum 1.0.0 → 3.0.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.
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env node
2
+ import path from "path"
3
+ import { c } from "../shared/colors.js"
4
+ import { findChronicleDir } from "../shared/chronicle.js"
5
+ import { detectProvider } from "../shared/llm.js"
6
+
7
+ // ── Helpers ───────────────────────────────────────────────────────────────────
8
+
9
+ function help() {
10
+ console.log(`
11
+ ${c.bold("quorum compass")} — product-direction synthesis
12
+
13
+ ${c.bold("Usage:")}
14
+ quorum compass <subcommand> [options]
15
+
16
+ ${c.bold("Subcommands:")}
17
+ brief Summarise current product direction (LLM)
18
+ map Map current product behaviours from code + docs (no LLM)
19
+ behavior Answer a product-behaviour question
20
+ opportunities List gaps and opportunities from the behaviour map
21
+ pathways Generate product pathways toward a goal (LLM)
22
+ bets Generate strategic big bets (LLM)
23
+ score <idea> Score a product idea (LLM)
24
+ spec <title> Generate a lightweight product brief (LLM)
25
+ propose Stage a Chronicle entry from a Compass artifact
26
+ outcome Record the outcome of a prior bet or pathway
27
+
28
+ ${c.bold("Options:")}
29
+ --area <tag> Focus on a specific product area
30
+ --goal <text> Goal for pathways / bets
31
+ --horizon <text> Horizon for bets (e.g. "6 months")
32
+ --appetite small|medium|large
33
+ --limit <n> Max results to return
34
+ --json Output raw JSON
35
+ --help Show this help
36
+
37
+ ${c.bold("Examples:")}
38
+ quorum compass brief
39
+ quorum compass map
40
+ quorum compass map --area advisor
41
+ quorum compass pathways --goal "onboard new agents faster"
42
+ quorum compass bets --horizon "6 months"
43
+ quorum compass score "add Slack integration"
44
+ quorum compass spec "Smart retry backoff"
45
+ quorum compass opportunities --limit 5
46
+ quorum compass propose --from-last
47
+ quorum compass outcome --entry-id <id> --result validated`)
48
+ }
49
+
50
+ // ── Render helpers ────────────────────────────────────────────────────────────
51
+
52
+ function renderBrief(brief) {
53
+ console.log(`\n${c.bold("Compass Brief")} ${c.dim(`(confidence: ${(brief.confidence * 100).toFixed(0)}%)`)}`)
54
+ console.log(`\n${c.bold("Direction:")} ${brief.product_direction}`)
55
+
56
+ if (brief.known_from_chronicle?.length) {
57
+ console.log(`\n${c.bold("From Chronicle:")}`)
58
+ brief.known_from_chronicle.forEach(item => console.log(` ${c.green("✓")} ${item}`))
59
+ }
60
+ if (brief.known_from_behavior?.length) {
61
+ console.log(`\n${c.bold("From code/docs:")}`)
62
+ brief.known_from_behavior.slice(0, 6).forEach(item => console.log(` ${c.green("✓")} ${item}`))
63
+ }
64
+ if (brief.inferred?.length) {
65
+ console.log(`\n${c.bold("Inferred:")}`)
66
+ brief.inferred.forEach(item => console.log(` ${c.yellow("~")} ${item}`))
67
+ }
68
+ if (brief.unknowns?.length) {
69
+ console.log(`\n${c.bold("Unknowns:")}`)
70
+ brief.unknowns.forEach(item => console.log(` ${c.dim("?")} ${item}`))
71
+ }
72
+ if (brief.opportunities?.length) {
73
+ console.log(`\n${c.bold("Opportunities:")}`)
74
+ brief.opportunities.slice(0, 4).forEach(o => console.log(` ${c.cyan("→")} ${o.title}`))
75
+ }
76
+ if (brief.recommended_next_step) {
77
+ console.log(`\n${c.bold("Next step:")} ${brief.recommended_next_step}`)
78
+ }
79
+ }
80
+
81
+ function renderBehaviorMap(map) {
82
+ console.log(`\n${c.bold("Behaviour Map")} ${map.area ? c.dim(`(area: ${map.area})`) : ""} ${c.dim(`(confidence: ${(map.confidence * 100).toFixed(0)}%)`)}`)
83
+
84
+ if (map.behaviors.length > 0) {
85
+ console.log(`\n${c.bold(`Behaviours (${map.behaviors.length}):`)}`)
86
+ map.behaviors.slice(0, 20).forEach(b => {
87
+ console.log(` ${c.green("✓")} ${b.current_behavior.slice(0, 100)}`)
88
+ })
89
+ } else {
90
+ console.log(`\n ${c.dim("No behaviours found.")}`)
91
+ }
92
+
93
+ if (map.gaps.length > 0) {
94
+ console.log(`\n${c.bold(`Gaps (${map.gaps.length}):`)}`)
95
+ map.gaps.forEach(g => {
96
+ console.log(` ${c.yellow("?")} [${g.area}] ${g.gap}`)
97
+ })
98
+ }
99
+
100
+ if (map.contradictions?.length) {
101
+ console.log(`\n${c.bold(`Contradictions (${map.contradictions.length}):`)}`)
102
+ map.contradictions.slice(0, 5).forEach(ct => {
103
+ console.log(` ${c.red("!")} ${ct.description ?? JSON.stringify(ct).slice(0, 80)}`)
104
+ })
105
+ }
106
+ }
107
+
108
+ function renderPathways(pathways) {
109
+ console.log(`\n${c.bold(`Pathways (${pathways.length})`)}`)
110
+ pathways.forEach((p, i) => {
111
+ const score = p.scores?.total ?? "?"
112
+ const label =
113
+ score >= 85 ? c.green(`${score}`) :
114
+ score >= 70 ? c.cyan(`${score}`) :
115
+ score >= 55 ? c.yellow(`${score}`) :
116
+ c.dim(`${score}`)
117
+
118
+ console.log(`\n${c.bold(`${i + 1}. ${p.title}`)} ${c.dim("[")}${label}${c.dim("]")}`)
119
+ if (p.opportunity) console.log(` ${p.opportunity}`)
120
+ if (p.smallest_useful_version) console.log(` ${c.dim("Start:")} ${p.smallest_useful_version}`)
121
+ if (p.suggested_next_step) console.log(` ${c.dim("Next:")} ${p.suggested_next_step}`)
122
+ if (p.assumptions?.length) {
123
+ console.log(` ${c.dim("Assumes:")} ${p.assumptions[0]}`)
124
+ }
125
+ })
126
+ }
127
+
128
+ function renderBets(bets) {
129
+ console.log(`\n${c.bold(`Strategic Bets (${bets.length})`)}`)
130
+ bets.forEach((b, i) => {
131
+ const score = b.scores?.total ?? "?"
132
+ console.log(`\n${c.bold(`${i + 1}. ${b.title}`)} ${c.dim(`[${score}]`)}`)
133
+ console.log(` ${c.dim("Thesis:")} ${b.thesis}`)
134
+ if (b.first_experiment) console.log(` ${c.dim("First test:")} ${b.first_experiment}`)
135
+ if (b.kill_criteria?.length) console.log(` ${c.red("Kill if:")} ${b.kill_criteria[0]}`)
136
+ if (b.assumptions?.length) console.log(` ${c.dim("Assumes:")} ${b.assumptions[0]}`)
137
+ })
138
+ }
139
+
140
+ function renderScore(score) {
141
+ const total = score.scores?.total ?? 0
142
+ const label =
143
+ total >= 85 ? c.green("Very strong — pursue") :
144
+ total >= 70 ? c.cyan("Strong — pursue small test") :
145
+ total >= 55 ? c.yellow("Plausible — investigate more") :
146
+ total >= 40 ? c.dim("Weak — defer") :
147
+ c.red("Avoid")
148
+
149
+ console.log(`\n${c.bold(`Score: ${total}/100`)} — ${label}`)
150
+ console.log(`Idea: ${score.idea}`)
151
+ if (score.summary) console.log(`Summary: ${score.summary}`)
152
+
153
+ if (score.supporting_reasons?.length) {
154
+ console.log(`\n${c.bold("Strengths:")}`)
155
+ score.supporting_reasons.forEach(r => console.log(` ${c.green("+")} ${r}`))
156
+ }
157
+ if (score.risks?.length) {
158
+ console.log(`\n${c.bold("Risks:")}`)
159
+ score.risks.forEach(r => console.log(` ${c.red("-")} ${r}`))
160
+ }
161
+ if (score.open_questions?.length) {
162
+ console.log(`\n${c.bold("Open questions:")}`)
163
+ score.open_questions.forEach(q => console.log(` ${c.dim("?")} ${q}`))
164
+ }
165
+ if (score.suggested_next_step) {
166
+ console.log(`\n${c.bold("Next step:")} ${score.suggested_next_step}`)
167
+ }
168
+ }
169
+
170
+ function renderOpportunities(opps) {
171
+ if (!opps.length) {
172
+ console.log(c.dim("\nNo gaps or opportunities found from current sources."))
173
+ return
174
+ }
175
+ console.log(`\n${c.bold(`Opportunities (${opps.length})`)}`)
176
+ opps.forEach((o, i) => {
177
+ const conf = `${(o.confidence * 100).toFixed(0)}%`
178
+ console.log(`\n${c.bold(`${i + 1}. ${o.title}`)} ${c.dim(`[${o.area}] [${o.evidence_strength}] [${conf}]`)}`)
179
+ if (o.why_it_matters) console.log(` ${o.why_it_matters}`)
180
+ if (o.suggested_next_step) console.log(` ${c.dim("Next:")} ${o.suggested_next_step}`)
181
+ })
182
+ }
183
+
184
+ function renderProductBrief(brief) {
185
+ console.log(`\n${c.bold(`Product Brief: ${brief.title}`)}`)
186
+ if (brief.problem) console.log(`\n${c.bold("Problem:")} ${brief.problem}`)
187
+ if (brief.target_user) console.log(`${c.bold("Target user:")} ${brief.target_user}`)
188
+ if (brief.recommended_solution) {
189
+ console.log(`\n${c.bold("Recommended solution:")}`)
190
+ console.log(` ${brief.recommended_solution}`)
191
+ }
192
+ if (brief.smallest_useful_version) {
193
+ console.log(`\n${c.bold("Smallest useful version:")}`)
194
+ console.log(` ${brief.smallest_useful_version}`)
195
+ }
196
+ if (brief.non_goals?.length) {
197
+ console.log(`\n${c.bold("Non-goals:")}`)
198
+ brief.non_goals.forEach(g => console.log(` ${c.dim("✗")} ${g}`))
199
+ }
200
+ if (brief.risks?.length) {
201
+ console.log(`\n${c.bold("Risks:")}`)
202
+ brief.risks.forEach(r => console.log(` ${c.red("-")} ${r}`))
203
+ }
204
+ if (brief.open_questions?.length) {
205
+ console.log(`\n${c.bold("Open questions:")}`)
206
+ brief.open_questions.forEach(q => console.log(` ${c.dim("?")} ${q}`))
207
+ }
208
+ if (brief.suggested_quorum_checks?.length) {
209
+ console.log(`\n${c.bold("Quorum checks:")}`)
210
+ brief.suggested_quorum_checks.forEach(ch => console.log(` ${c.cyan("$")} ${ch}`))
211
+ }
212
+ }
213
+
214
+ // ── Last-run artifact cache (used by --from-last) ─────────────────────────────
215
+
216
+ let _lastArtifact = null
217
+
218
+ // ── Main ─────────────────────────────────────────────────────────────────────
219
+
220
+ export async function run(argv) {
221
+ const [subcommand, ...rest] = argv
222
+
223
+ if (!subcommand || subcommand === "--help" || subcommand === "-h") {
224
+ help()
225
+ return
226
+ }
227
+
228
+ const flags = {}
229
+ const positional = []
230
+ for (let i = 0; i < rest.length; i++) {
231
+ const a = rest[i]
232
+ if (a.startsWith("--")) {
233
+ const key = a.slice(2)
234
+ const val = rest[i + 1] && !rest[i + 1].startsWith("--") ? rest[++i] : true
235
+ flags[key] = val
236
+ } else {
237
+ positional.push(a)
238
+ }
239
+ }
240
+
241
+ const area = flags["area"]
242
+ const goal = flags["goal"] || positional.join(" ") || undefined
243
+ const horizon = flags["horizon"] || undefined
244
+ const appetite = flags["appetite"] || undefined
245
+ const limitN = flags["limit"] ? parseInt(flags["limit"], 10) : undefined
246
+ const jsonMode = Boolean(flags["json"])
247
+ const entryId = flags["entry-id"] || flags["entryId"] || undefined
248
+ const result = flags["result"] || undefined
249
+
250
+ // ── Load Compass module ────────────────────────────────────────────────────
251
+
252
+ const rootDir = process.cwd()
253
+ const chronicleDir = findChronicleDir(rootDir)
254
+
255
+ if (!chronicleDir) {
256
+ console.error(c.red("Error: Chronicle not found. Run 'quorum init' first."))
257
+ process.exit(1)
258
+ }
259
+
260
+ // Lazy import to keep CLI startup fast
261
+ const { createCompass } = await import("../../modules/compass/create.js")
262
+ const { defaultSources } = await import("../../modules/compass/sources/index.js")
263
+ const { createOracleClient } = await import("../../modules/oracle/index.js")
264
+ const { createLanceDBStore } = await import("../../modules/oracle/adapters/lance-db.js")
265
+ const { xenovaEmbed } = await import("../../modules/oracle/adapters/xenova-embedder.js")
266
+
267
+ const vectorStore = await createLanceDBStore(chronicleDir)
268
+ const oracle = createOracleClient({ embedder: xenovaEmbed, vectorStore, chronicleDir })
269
+
270
+ // Only load LLM for subcommands that need it
271
+ const NO_LLM_CMDS = new Set(["map", "opportunities"])
272
+ const llm = NO_LLM_CMDS.has(subcommand) ? undefined : detectProvider()
273
+
274
+ const compass = createCompass({
275
+ oracle,
276
+ llm,
277
+ rootDir,
278
+ chronicleDir,
279
+ sources: defaultSources(),
280
+ })
281
+
282
+ // ── Route subcommand ───────────────────────────────────────────────────────
283
+
284
+ try {
285
+ switch (subcommand) {
286
+ case "brief": {
287
+ const data = await compass.brief({ area })
288
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
289
+ renderBrief(data)
290
+ break
291
+ }
292
+
293
+ case "map": {
294
+ const data = await compass.mapBehaviors({ area })
295
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
296
+ renderBehaviorMap(data)
297
+ break
298
+ }
299
+
300
+ case "behavior": {
301
+ const question = goal || positional.join(" ")
302
+ if (!question) {
303
+ console.error(c.red('Error: provide a question, e.g. quorum compass behavior "what does quorum do for onboarding?"'))
304
+ process.exit(1)
305
+ }
306
+ const data = await compass.behavior({ question, area })
307
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
308
+ console.log(`\n${c.bold("Behaviour answer:")} ${data.product_implication}`)
309
+ if (data.what_exists?.length) {
310
+ console.log(`\n${c.bold("What exists:")}`)
311
+ data.what_exists.forEach(e => console.log(` ${c.green("✓")} ${e}`))
312
+ }
313
+ if (data.what_appears_missing?.length) {
314
+ console.log(`\n${c.bold("Appears missing:")}`)
315
+ data.what_appears_missing.forEach(m => console.log(` ${c.yellow("?")} ${m}`))
316
+ }
317
+ break
318
+ }
319
+
320
+ case "opportunities": {
321
+ const data = await compass.opportunities({ area, goal, limit: limitN })
322
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
323
+ renderOpportunities(data)
324
+ break
325
+ }
326
+
327
+ case "pathways": {
328
+ if (!goal) {
329
+ console.error(c.red('Error: --goal is required. Example: quorum compass pathways --goal "onboard new agents faster"'))
330
+ process.exit(1)
331
+ }
332
+ const data = await compass.pathways({ goal, horizon, appetite, area, limit: limitN })
333
+ _lastArtifact = { kind: "product_pathway", items: data }
334
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
335
+ renderPathways(data)
336
+ console.log(c.dim("\nTip: run 'quorum compass propose --from-last' to stage a Chronicle entry."))
337
+ break
338
+ }
339
+
340
+ case "bets": {
341
+ const data = await compass.bigBets({ horizon, goal, appetite })
342
+ _lastArtifact = { kind: "product_bet", items: data }
343
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
344
+ renderBets(data)
345
+ console.log(c.dim("\nTip: run 'quorum compass propose --from-last' to stage a Chronicle entry."))
346
+ break
347
+ }
348
+
349
+ case "score": {
350
+ const idea = goal || positional.join(" ")
351
+ if (!idea) {
352
+ console.error(c.red('Error: provide an idea. Example: quorum compass score "add Slack integration"'))
353
+ process.exit(1)
354
+ }
355
+ const data = await compass.scoreIdea({ idea })
356
+ _lastArtifact = { kind: "product_idea_score", items: [data] }
357
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
358
+ renderScore(data)
359
+ break
360
+ }
361
+
362
+ case "spec": {
363
+ const title = goal || positional.join(" ")
364
+ if (!title) {
365
+ console.error(c.red('Error: provide a title. Example: quorum compass spec "Smart retry backoff"'))
366
+ process.exit(1)
367
+ }
368
+ const data = await compass.productBrief({ title })
369
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
370
+ renderProductBrief(data)
371
+ break
372
+ }
373
+
374
+ case "propose": {
375
+ if (flags["from-last"]) {
376
+ if (!_lastArtifact?.items?.length) {
377
+ console.error(c.red("Error: no Compass artifact in memory. Run pathways/bets/score first in the same session."))
378
+ process.exit(1)
379
+ }
380
+ const item = _lastArtifact.items[0]
381
+ const result = await compass.propose({ artifact_kind: _lastArtifact.kind, payload: item })
382
+ console.log(c.green(`\n✓ ${result.message}`))
383
+ break
384
+ }
385
+ console.error(c.red('Error: provide --from-last. Example: quorum compass propose --from-last'))
386
+ process.exit(1)
387
+ break
388
+ }
389
+
390
+ case "outcome": {
391
+ if (!entryId) {
392
+ console.error(c.red("Error: --entry-id is required. Example: quorum compass outcome --entry-id abc123 --result validated"))
393
+ process.exit(1)
394
+ }
395
+ if (!result) {
396
+ console.error(c.red("Error: --result is required. Values: validated, partially-validated, invalidated, unclear, superseded"))
397
+ process.exit(1)
398
+ }
399
+ const note = flags["note"] || undefined
400
+ const data = await compass.outcome({ entry_id: entryId, result, note })
401
+ if (jsonMode) { console.log(JSON.stringify(data, null, 2)); break }
402
+ console.log(c.green(`\n✓ ${data.message}`))
403
+ break
404
+ }
405
+
406
+ default: {
407
+ console.error(c.red(`Unknown subcommand: ${subcommand}`))
408
+ help()
409
+ process.exit(1)
410
+ }
411
+ }
412
+ } catch (err) {
413
+ if (err.message?.includes("LLM provider is required") || err.message?.includes("No LLM provider")) {
414
+ console.error(c.red(`\nError: ${err.message}`))
415
+ console.error(c.dim("Set ANTHROPIC_API_KEY or OPENAI_API_KEY to use this subcommand."))
416
+ } else {
417
+ console.error(c.red(`\nCompass error: ${err.message ?? err}`))
418
+ if (process.env.DEBUG) console.error(err.stack)
419
+ }
420
+ process.exit(1)
421
+ }
422
+ }
@@ -10,12 +10,6 @@ const _require = createRequire(import.meta.url)
10
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
11
  const QUORUM_ROOT = path.resolve(__dirname, "../..")
12
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
13
  async function exists(p) {
20
14
  return fs.access(p).then(() => true).catch(() => false)
21
15
  }
@@ -29,25 +23,30 @@ function geminiAvailable() {
29
23
  }
30
24
 
31
25
  async function guardAlreadyInitialized(target) {
32
- if (await exists(path.join(target, "quorum", "modules"))) {
26
+ if (await exists(path.join(target, ".quorum-version"))) {
33
27
  console.log(c.yellow("\nQuorum is already initialized in this project."))
34
- console.log("Remove quorum/ first if you want to reinitialize.\n")
28
+ console.log("Run 'npm update @balpal4495/quorum' to upgrade to the latest version.\n")
35
29
  process.exit(0)
36
30
  }
37
31
  }
38
32
 
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/")
33
+ async function writeQuorumDocs(target) {
34
+ log.section("Writing Quorum docs")
35
+ await fs.mkdir(path.join(target, "quorum"), { recursive: true })
36
+ // Host-facing CLAUDE.md — CLI-first operational guide, not module internals
37
+ const claudeSrc = path.join(QUORUM_ROOT, "bin", "templates", "CLAUDE.md")
38
+ const claudeDest = path.join(target, "quorum", "CLAUDE.md")
39
+ if (await exists(claudeSrc)) {
40
+ await fs.copyFile(claudeSrc, claudeDest)
41
+ log.created("quorum/CLAUDE.md")
42
+ }
43
+ // AGENTS.md — module file ownership map
44
+ const agentsSrc = path.join(QUORUM_ROOT, "modules", "AGENTS.md")
45
+ const agentsDest = path.join(target, "quorum", "AGENTS.md")
46
+ if (await exists(agentsSrc)) {
47
+ await fs.copyFile(agentsSrc, agentsDest)
48
+ log.created("quorum/AGENTS.md")
49
+ }
51
50
  await fs.copyFile(
52
51
  path.join(QUORUM_ROOT, "SETUP.md"),
53
52
  path.join(target, "quorum", "SETUP.md"),
@@ -55,11 +54,9 @@ async function copyModules(target) {
55
54
  log.created("quorum/SETUP.md")
56
55
  }
57
56
 
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/")
57
+ async function writeQuorumVersion(target, version) {
58
+ await fs.writeFile(path.join(target, ".quorum-version"), version + "\n", "utf8")
59
+ log.created(".quorum-version")
63
60
  }
64
61
 
65
62
  async function mergeCopilotInstructions(target) {
@@ -67,14 +64,15 @@ async function mergeCopilotInstructions(target) {
67
64
  const src = path.join(QUORUM_ROOT, ".github", "copilot-instructions.md")
68
65
  const dest = path.join(target, ".github", "copilot-instructions.md")
69
66
  const content = await fs.readFile(src, "utf8")
67
+ const block = `<!-- quorum:start -->\n${content}\n<!-- quorum:end -->`
70
68
  await fs.mkdir(path.join(target, ".github"), { recursive: true })
71
69
  if (await exists(dest)) {
72
70
  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")
71
+ if (existing.includes("<!-- quorum:start -->")) { log.skipped(".github/copilot-instructions.md (already present)"); return }
72
+ await fs.appendFile(dest, `\n\n---\n\n${block}`, "utf8")
75
73
  log.appended(".github/copilot-instructions.md")
76
74
  } else {
77
- await fs.writeFile(dest, content, "utf8")
75
+ await fs.writeFile(dest, block, "utf8")
78
76
  log.created(".github/copilot-instructions.md")
79
77
  }
80
78
  }
@@ -82,13 +80,18 @@ async function mergeCopilotInstructions(target) {
82
80
  async function mergeAgentsMd(target) {
83
81
  const dest = path.join(target, "AGENTS.md")
84
82
  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.", "",
83
+ "",
84
+ "<!-- quorum:start -->",
85
+ "## Quorum",
86
+ "",
87
+ "See [quorum/AGENTS.md](quorum/AGENTS.md) for module file ownership and internals.",
88
+ "See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.",
89
+ "<!-- quorum:end -->",
90
+ "",
88
91
  ].join("\n")
89
92
  if (await exists(dest)) {
90
93
  const existing = await fs.readFile(dest, "utf8")
91
- if (existing.includes("quorum/modules/AGENTS.md")) { log.skipped("AGENTS.md (already present)"); return }
94
+ if (existing.includes("<!-- quorum:start -->")) { log.skipped("AGENTS.md (already present)"); return }
92
95
  await fs.appendFile(dest, section, "utf8")
93
96
  log.appended("AGENTS.md")
94
97
  } else {
@@ -98,11 +101,12 @@ async function mergeAgentsMd(target) {
98
101
  }
99
102
 
100
103
  async function mergeClaudeMd(target) {
101
- const dest = path.join(target, "CLAUDE.md")
104
+ const dest = path.join(target, "CLAUDE.md")
102
105
  const section = `
103
- ## Quorum modules
106
+ <!-- quorum:start -->
107
+ ## Quorum
104
108
 
105
- See [quorum/modules/CLAUDE.md](quorum/modules/CLAUDE.md) for Oracle, Jury, and Council internals.
109
+ See [quorum/CLAUDE.md](quorum/CLAUDE.md) for design decisions and invariants.
106
110
  See [.github/copilot-instructions.md](.github/copilot-instructions.md) for workflow rules.
107
111
 
108
112
  ## Gemini CLI (optional assistant)
@@ -127,10 +131,11 @@ source ~/.zshrc && gemini -p "I'm about to change X. What should I watch out for
127
131
 
128
132
  You reason about Gemini's output — it assists, you decide. Never pass its response to the
129
133
  user unfiltered. If Gemini contradicts what you know from reading the code, trust your reading.
134
+ <!-- quorum:end -->
130
135
  `
131
136
  if (await exists(dest)) {
132
137
  const existing = await fs.readFile(dest, "utf8")
133
- if (existing.includes("quorum/modules/CLAUDE.md")) { log.skipped("CLAUDE.md (already present)"); return }
138
+ if (existing.includes("<!-- quorum:start -->")) { log.skipped("CLAUDE.md (already present)"); return }
134
139
  await fs.appendFile(dest, section, "utf8")
135
140
  log.appended("CLAUDE.md")
136
141
  } else {
@@ -147,7 +152,7 @@ async function mergeGeminiMd(target) {
147
152
  if (await exists(src)) { await fs.copyFile(src, dest); log.created("GEMINI.md") }
148
153
  }
149
154
 
150
- async function updatePackageJson(target) {
155
+ async function updatePackageJson(target, version) {
151
156
  log.section("Updating package.json")
152
157
  const pkgPath = path.join(target, "package.json")
153
158
  let pkg
@@ -157,30 +162,27 @@ async function updatePackageJson(target) {
157
162
  pkg = { name: path.basename(target), version: "0.1.0", private: true }
158
163
  log.warn("No package.json found — creating a minimal one")
159
164
  }
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)`) }
165
+ pkg.devDependencies = pkg.devDependencies ?? {}
166
+ const quorumRange = `^${version}`
167
+ if (pkg.devDependencies["@balpal4495/quorum"] || pkg.dependencies?.["@balpal4495/quorum"]) {
168
+ log.skipped("package.json (@balpal4495/quorum already present)")
169
+ return
168
170
  }
171
+ pkg.devDependencies["@balpal4495/quorum"] = quorumRange
169
172
  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
- }
173
+ log.appended(`package.json added @balpal4495/quorum@${quorumRange} to devDependencies`)
175
174
  }
176
175
 
177
176
  async function updateGitignore(target) {
178
177
  log.section("Updating .gitignore")
179
178
  const dest = path.join(target, ".gitignore")
180
179
  const block = [
181
- "", "# Quorum — Chronicle",
180
+ "",
181
+ "# Quorum — Chronicle",
182
182
  "# entries/ is a binary vector store — do not commit",
183
- ".chronicle/entries/", ".chronicle/query-log.jsonl", "",
183
+ ".chronicle/entries/",
184
+ ".chronicle/query-log.jsonl",
185
+ "",
184
186
  ].join("\n")
185
187
  if (await exists(dest)) {
186
188
  const existing = await fs.readFile(dest, "utf8")
@@ -195,7 +197,7 @@ async function updateGitignore(target) {
195
197
 
196
198
  async function createChronicle(target) {
197
199
  log.section("Creating Chronicle")
198
- await fs.mkdir(path.join(target, ".chronicle", "proposals"), { recursive: true })
200
+ await fs.mkdir(path.join(target, ".chronicle", "proposals"), { recursive: true })
199
201
  log.created(".chronicle/proposals/")
200
202
  await fs.mkdir(path.join(target, ".chronicle", "committed"), { recursive: true })
201
203
  log.created(".chronicle/committed/")
@@ -213,30 +215,33 @@ export async function run(PKG_VERSION) {
213
215
  }
214
216
 
215
217
  await guardAlreadyInitialized(target)
216
- await copyModules(target)
217
- await copyEvals(target)
218
+ await writeQuorumDocs(target)
218
219
  await mergeCopilotInstructions(target)
219
220
  await mergeAgentsMd(target)
220
221
  await mergeClaudeMd(target)
221
222
  await mergeGeminiMd(target)
222
- await updatePackageJson(target)
223
+ await updatePackageJson(target, PKG_VERSION)
223
224
  await updateGitignore(target)
224
225
  await createChronicle(target)
226
+ await writeQuorumVersion(target, PKG_VERSION)
225
227
 
226
228
  const hasGemini = geminiAvailable()
227
229
 
228
- console.log(`\n${c.green("✓ Quorum initialized.")}`)
230
+ console.log(`\n${c.green("✓ Quorum initialized.")} ${c.dim(`(v${PKG_VERSION})`)}`)
229
231
  console.log("\nNext steps:")
230
232
  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(" 2. Use the CLI:")
234
+ console.log(c.dim(" quorum advisor brief"))
235
+ console.log(c.dim(' quorum advisor "what has the team decided about X?"'))
236
+ console.log(c.dim(" quorum check --outcome '...' --design '...'"))
237
+ console.log("\n For programmatic use:")
238
+ console.log(c.dim(' import { setup } from "@balpal4495/quorum"'))
233
239
  console.log(c.dim(' const { oracle, evaluate, deliberate } = await setup({ llm: yourProvider })'))
234
240
  console.log("\n Or tell your AI: \"follow quorum/SETUP.md\"")
235
241
 
236
242
  if (!hasGemini) {
237
243
  console.log(`\n ${c.dim("Optional: install Gemini CLI for large-context assistance")}`)
238
244
  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
245
  } else {
241
246
  console.log(`\n ${c.green("✓ Gemini CLI detected")} — GEMINI.md written. Set GEMINI_API_KEY if not already set.`)
242
247
  }