@balpal4495/quorum 2.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.
@@ -2,11 +2,14 @@
2
2
 
3
3
  ## Architecture
4
4
 
5
- This project uses three portable reasoning modules: **Oracle**, **Jury**, and **Council**.
6
- They form the knowledge and validation layer for all agentic work in this codebase.
5
+ This project uses six portable reasoning modules: **Advisor**, **Oracle**, **Jury**, **Council**, **Sentinel**, and **Compass**.
6
+ They form the knowledge, validation, and product-direction layer for all agentic work in this codebase.
7
7
 
8
8
  ```
9
- oracle.query()jury.evaluate() → council.deliberate() → human gate → Executor
9
+ Advisorplain-language Chronicle queries
10
+ Oracle → Jury → Council → human gate → Executor
11
+ Sentinel → coverage + drift
12
+ Compass → product-direction synthesis (behaviours, pathways, bets, scoring)
10
13
  ```
11
14
 
12
15
  Source: `modules/` — see [modules/README.md](modules/README.md) for full API reference.
@@ -35,20 +38,23 @@ There are no auto-commits. Do not attempt to bypass this gate.
35
38
 
36
39
  | Module | What it does | LLM? |
37
40
  |---|---|---|
41
+ | `ask()` | Plain-language question answered from Chronicle — validates internally, retries up to 2× | Yes |
38
42
  | `oracle.query()` | Retrieves relevant Chronicle entries by semantic + BM25 search | No |
39
43
  | `oracle.propose()` | Stages a new entry for human review | No |
40
44
  | `oracle.commit()` | Indexes an approved entry — human-triggered only | No |
41
45
  | `jury.evaluate()` | Scores a design against evidence across 4 dimensions | Yes |
42
46
  | `council.deliberate()` | Adversarial validation via advisor/reviewer fan-out | Yes |
47
+ | `sentinel` | Coverage reporting, drift detection, and PR coverage maps | Optional |
48
+ | `compass` | Product-direction synthesis — behaviours, opportunities, pathways, bets, idea scoring | Optional |
43
49
 
44
50
  ---
45
51
 
46
52
  ## Setup
47
53
 
48
54
  ```typescript
49
- import { setup } from "./modules/setup"
55
+ import { setup } from "@balpal4495/quorum"
50
56
 
51
- const { oracle, evaluate, deliberate } = await setup({ llm: yourProvider })
57
+ const { oracle, evaluate, deliberate, ask, compass } = await setup({ llm: yourProvider })
52
58
  ```
53
59
 
54
60
  `setup()` creates Chronicle directories, warms the embedder, and wires all dependencies.
@@ -87,8 +93,25 @@ After `council.deliberate()`:
87
93
 
88
94
  ---
89
95
 
96
+ ## CLI quick reference
97
+
98
+ ```bash
99
+ quorum advisor brief # full Chronicle summary, no LLM
100
+ quorum advisor query "topic" # keyword search, no LLM
101
+ quorum advisor "plain-language question" # synthesised answer via LLM
102
+ quorum check --outcome "..." --design "..." # instant risk triage
103
+ quorum commit --list # review pending proposals
104
+ quorum commit <id> # approve a Chronicle entry
105
+ quorum compass map # map current product behaviours (no LLM)
106
+ quorum compass brief # product-direction summary (LLM)
107
+ quorum compass pathways --goal "..." # generate product pathways (LLM)
108
+ quorum compass score "idea" # score a product idea (LLM)
109
+ ```
110
+
111
+ ---
112
+
90
113
  ## Build and test
91
114
 
92
115
  ```bash
93
- npx vitest run modules/
116
+ npx vitest run modules/ evals/
94
117
  ```
package/README.md CHANGED
@@ -28,6 +28,23 @@ Every new session starts from zero. The same mistakes get proposed again. The sa
28
28
 
29
29
  ---
30
30
 
31
+ ## Why not just use agent instructions?
32
+
33
+ Agent instructions tell the AI how to behave.
34
+ Quorum tells the AI what the project has already learned.
35
+
36
+ | | Agent instructions | Chronicle |
37
+ |---|---|---|
38
+ | Content | Rules and preferences | Decisions, rejections, outcomes |
39
+ | Growth | Static — you write them | Grows — the agent stages, you approve |
40
+ | Durability | Easy to overwrite or ignore | Git-backed, survives config changes |
41
+ | Failed approaches | Not tracked | Hard stops on refuted patterns |
42
+ | Human approval | Not required | Required for every indexed entry |
43
+
44
+ Instructions are a starting point. Chronicle is accumulated project knowledge.
45
+
46
+ ---
47
+
31
48
  ## What changes after Quorum
32
49
 
33
50
  **Without Quorum:**
@@ -125,6 +142,10 @@ Every PR merge posts a growth comment showing what Chronicle learned. `quorum ev
125
142
  | See whether memory is growing | `quorum growth` |
126
143
  | Consolidate stale or duplicate entries | `quorum evolve` |
127
144
  | Find undocumented areas | `quorum sentinel coverage` |
145
+ | Understand what the product currently does | `quorum compass map` |
146
+ | Generate product pathways toward a goal | `quorum compass pathways --goal "..."` |
147
+ | Score a product idea | `quorum compass score "add Slack integration"` |
148
+ | Stage a direction decision for Chronicle | `quorum compass propose --from-last` |
128
149
 
129
150
  ---
130
151
 
@@ -154,12 +175,57 @@ npx @balpal4495/quorum@latest init
154
175
  npm install
155
176
  ```
156
177
 
157
- Then install the CLI globally for terminal access:
178
+ Then run Quorum from your project:
179
+
180
+ ```bash
181
+ npx quorum advisor brief
182
+ npx quorum advisor "what has the team decided about auth?"
183
+ npx quorum check --outcome "..." --design "..."
184
+ ```
185
+
186
+ **Optional — install the CLI globally:**
158
187
 
159
188
  ```bash
160
189
  npm install -g @balpal4495/quorum
190
+ quorum advisor brief
161
191
  ```
162
192
 
193
+ > **v2 architecture:** Implementation lives in the npm package (`node_modules/@balpal4495/quorum`). Project memory lives in your repo (`.chronicle/`). Agent docs bridge the two (`quorum/CLAUDE.md`, `.github/copilot-instructions.md`). Nothing is copied into your project that you need to maintain.
194
+
195
+ ---
196
+
197
+ ## Upgrading from v1
198
+
199
+ If your project has a `quorum/modules/` folder (the v1 vendored pattern), migrate in one step:
200
+
201
+ ```bash
202
+ quorum migrate-v2
203
+ ```
204
+
205
+ After any `npm update @balpal4495/quorum`, refresh agent instruction files:
206
+
207
+ ```bash
208
+ quorum sync
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Runtime model
214
+
215
+ The CLI works in plain Node 18+.
216
+
217
+ Programmatic imports are TypeScript-native and require a TS-aware runtime such as `tsx`, `ts-node`, Bun, or a bundler. Plain `node` will not resolve `.ts` files.
218
+
219
+ ```bash
220
+ # recommended for scripts
221
+ npx tsx your-script.ts
222
+
223
+ # or Bun
224
+ bun your-script.ts
225
+ ```
226
+
227
+ For most host-project use cases the CLI is sufficient and requires no loader. See [modules/README.md](modules/README.md) for the full programmatic API.
228
+
163
229
  ---
164
230
 
165
231
  ## Command reference
@@ -394,7 +460,7 @@ The next person touching that table has the full reasoning. They don't repeat th
394
460
 
395
461
  ## How it works under the hood
396
462
 
397
- You do not need to understand these internals to use Quorum. They are installed into `quorum/modules/` so any AI working in the codebase can inspect and use them directly.
463
+ You do not need to understand these internals to use Quorum. They live in `node_modules/@balpal4495/quorum` after install. The `quorum/` folder that `init` creates in your project contains agent-readable docs and Chronicle data — not module source.
398
464
 
399
465
  | Module | What it does | LLM |
400
466
  |---|---|---|
@@ -403,6 +469,7 @@ You do not need to understand these internals to use Quorum. They are installed
403
469
  | **Jury** | Evaluates a design against Chronicle evidence. Four-dimension confidence score, deterministic preflight, hard-blocker gaps. | Yes |
404
470
  | **Council** | Adversarial panel — advisors challenge independently, reviewers critique anonymously, Chairman gives a structured verdict. Risk-scaled fan-out. | Yes |
405
471
  | **Sentinel** | Coverage reporting (which files Chronicle knows about), drift detection (are entries still accurate), PR coverage maps. | Optional |
472
+ | **Compass** | Product-direction layer — maps current behaviours from code and docs, identifies gaps and opportunities, generates pathways, strategic bets, and idea scores grounded in Chronicle evidence. All writes go through `oracle.propose()`. | Optional |
406
473
 
407
474
  ### How Jury works
408
475
 
@@ -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
+ }
@@ -33,13 +33,19 @@ async function guardAlreadyInitialized(target) {
33
33
  async function writeQuorumDocs(target) {
34
34
  log.section("Writing Quorum docs")
35
35
  await fs.mkdir(path.join(target, "quorum"), { recursive: true })
36
- for (const file of ["CLAUDE.md", "AGENTS.md"]) {
37
- const src = path.join(QUORUM_ROOT, "modules", file)
38
- const dest = path.join(target, "quorum", file)
39
- if (await exists(src)) {
40
- await fs.copyFile(src, dest)
41
- log.created(`quorum/${file}`)
42
- }
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")
43
49
  }
44
50
  await fs.copyFile(
45
51
  path.join(QUORUM_ROOT, "SETUP.md"),