@balpal4495/quorum 3.0.4 → 3.1.1

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 (242) hide show
  1. package/bin/commands/compass.js +31 -12
  2. package/dist/advisor/ask.d.ts +13 -0
  3. package/dist/advisor/ask.d.ts.map +1 -0
  4. package/dist/advisor/ask.js +67 -0
  5. package/dist/advisor/ask.js.map +1 -0
  6. package/dist/advisor/index.d.ts +3 -0
  7. package/dist/advisor/index.d.ts.map +1 -0
  8. package/dist/advisor/index.js +2 -0
  9. package/dist/advisor/index.js.map +1 -0
  10. package/dist/advisor/prompt.d.ts +5 -0
  11. package/dist/advisor/prompt.d.ts.map +1 -0
  12. package/{modules/advisor/prompt.ts → dist/advisor/prompt.js} +22 -26
  13. package/dist/advisor/prompt.js.map +1 -0
  14. package/dist/advisor/types.d.ts +23 -0
  15. package/dist/advisor/types.d.ts.map +1 -0
  16. package/dist/advisor/types.js +2 -0
  17. package/dist/advisor/types.js.map +1 -0
  18. package/dist/compass/behavior.d.ts +4 -0
  19. package/dist/compass/behavior.d.ts.map +1 -0
  20. package/dist/compass/behavior.js +138 -0
  21. package/dist/compass/behavior.js.map +1 -0
  22. package/dist/compass/create.d.ts +3 -0
  23. package/dist/compass/create.d.ts.map +1 -0
  24. package/dist/compass/create.js +289 -0
  25. package/dist/compass/create.js.map +1 -0
  26. package/dist/compass/evidence/collect.d.ts +11 -0
  27. package/dist/compass/evidence/collect.d.ts.map +1 -0
  28. package/dist/compass/evidence/collect.js +86 -0
  29. package/dist/compass/evidence/collect.js.map +1 -0
  30. package/dist/compass/index.d.ts +8 -0
  31. package/dist/compass/index.d.ts.map +1 -0
  32. package/dist/compass/index.js +8 -0
  33. package/dist/compass/index.js.map +1 -0
  34. package/dist/compass/prompts/index.d.ts +28 -0
  35. package/dist/compass/prompts/index.d.ts.map +1 -0
  36. package/{modules/compass/prompts/index.ts → dist/compass/prompts/index.js} +13 -38
  37. package/dist/compass/prompts/index.js.map +1 -0
  38. package/dist/compass/prompts/system.d.ts +2 -0
  39. package/dist/compass/prompts/system.d.ts.map +1 -0
  40. package/{modules/compass/prompts/system.ts → dist/compass/prompts/system.js} +2 -1
  41. package/dist/compass/prompts/system.js.map +1 -0
  42. package/dist/compass/propose.d.ts +15 -0
  43. package/dist/compass/propose.d.ts.map +1 -0
  44. package/dist/compass/propose.js +128 -0
  45. package/dist/compass/propose.js.map +1 -0
  46. package/dist/compass/schemas.d.ts +1271 -0
  47. package/dist/compass/schemas.d.ts.map +1 -0
  48. package/dist/compass/schemas.js +113 -0
  49. package/dist/compass/schemas.js.map +1 -0
  50. package/dist/compass/score.d.ts +25 -0
  51. package/dist/compass/score.d.ts.map +1 -0
  52. package/dist/compass/score.js +89 -0
  53. package/dist/compass/score.js.map +1 -0
  54. package/dist/compass/sources/index.d.ts +9 -0
  55. package/dist/compass/sources/index.d.ts.map +1 -0
  56. package/dist/compass/sources/index.js +408 -0
  57. package/dist/compass/sources/index.js.map +1 -0
  58. package/dist/compass/types.d.ts +334 -0
  59. package/dist/compass/types.d.ts.map +1 -0
  60. package/dist/compass/types.js +2 -0
  61. package/dist/compass/types.js.map +1 -0
  62. package/dist/council/advisors.d.ts +15 -0
  63. package/dist/council/advisors.d.ts.map +1 -0
  64. package/dist/council/advisors.js +46 -0
  65. package/dist/council/advisors.js.map +1 -0
  66. package/dist/council/chairman.d.ts +13 -0
  67. package/dist/council/chairman.d.ts.map +1 -0
  68. package/dist/council/chairman.js +145 -0
  69. package/dist/council/chairman.js.map +1 -0
  70. package/dist/council/deliberate.d.ts +22 -0
  71. package/dist/council/deliberate.d.ts.map +1 -0
  72. package/dist/council/deliberate.js +99 -0
  73. package/dist/council/deliberate.js.map +1 -0
  74. package/dist/council/frame.d.ts +8 -0
  75. package/dist/council/frame.d.ts.map +1 -0
  76. package/dist/council/frame.js +40 -0
  77. package/dist/council/frame.js.map +1 -0
  78. package/dist/council/index.d.ts +6 -0
  79. package/dist/council/index.d.ts.map +1 -0
  80. package/dist/council/index.js +4 -0
  81. package/dist/council/index.js.map +1 -0
  82. package/dist/council/personas.d.ts +18 -0
  83. package/dist/council/personas.d.ts.map +1 -0
  84. package/dist/council/personas.js +44 -0
  85. package/dist/council/personas.js.map +1 -0
  86. package/dist/council/reviewers.d.ts +13 -0
  87. package/dist/council/reviewers.d.ts.map +1 -0
  88. package/dist/council/reviewers.js +59 -0
  89. package/dist/council/reviewers.js.map +1 -0
  90. package/dist/council/risk.d.ts +16 -0
  91. package/dist/council/risk.d.ts.map +1 -0
  92. package/dist/council/risk.js +74 -0
  93. package/dist/council/risk.js.map +1 -0
  94. package/dist/council/types.d.ts +95 -0
  95. package/dist/council/types.d.ts.map +1 -0
  96. package/dist/council/types.js +2 -0
  97. package/dist/council/types.js.map +1 -0
  98. package/dist/jury/evaluate.d.ts +13 -0
  99. package/dist/jury/evaluate.d.ts.map +1 -0
  100. package/{modules/jury/evaluate.ts → dist/jury/evaluate.js} +60 -84
  101. package/dist/jury/evaluate.js.map +1 -0
  102. package/dist/jury/index.d.ts +6 -0
  103. package/dist/jury/index.d.ts.map +1 -0
  104. package/dist/jury/index.js +4 -0
  105. package/dist/jury/index.js.map +1 -0
  106. package/dist/jury/preflight.d.ts +26 -0
  107. package/dist/jury/preflight.d.ts.map +1 -0
  108. package/dist/jury/preflight.js +71 -0
  109. package/dist/jury/preflight.js.map +1 -0
  110. package/dist/jury/schema.d.ts +57 -0
  111. package/dist/jury/schema.d.ts.map +1 -0
  112. package/dist/jury/schema.js +21 -0
  113. package/dist/jury/schema.js.map +1 -0
  114. package/dist/jury/types.d.ts +47 -0
  115. package/dist/jury/types.d.ts.map +1 -0
  116. package/dist/jury/types.js +2 -0
  117. package/dist/jury/types.js.map +1 -0
  118. package/dist/oracle/adapters/lance-db.d.ts +15 -0
  119. package/dist/oracle/adapters/lance-db.d.ts.map +1 -0
  120. package/dist/oracle/adapters/lance-db.js +68 -0
  121. package/dist/oracle/adapters/lance-db.js.map +1 -0
  122. package/dist/oracle/adapters/xenova-embedder.d.ts +21 -0
  123. package/dist/oracle/adapters/xenova-embedder.d.ts.map +1 -0
  124. package/dist/oracle/adapters/xenova-embedder.js +36 -0
  125. package/dist/oracle/adapters/xenova-embedder.js.map +1 -0
  126. package/dist/oracle/bm25.d.ts +20 -0
  127. package/dist/oracle/bm25.d.ts.map +1 -0
  128. package/dist/oracle/bm25.js +82 -0
  129. package/dist/oracle/bm25.js.map +1 -0
  130. package/dist/oracle/index.d.ts +21 -0
  131. package/dist/oracle/index.d.ts.map +1 -0
  132. package/dist/oracle/index.js +25 -0
  133. package/dist/oracle/index.js.map +1 -0
  134. package/dist/oracle/log.d.ts +6 -0
  135. package/dist/oracle/log.d.ts.map +1 -0
  136. package/dist/oracle/log.js +12 -0
  137. package/dist/oracle/log.js.map +1 -0
  138. package/dist/oracle/propose.d.ts +25 -0
  139. package/dist/oracle/propose.d.ts.map +1 -0
  140. package/dist/oracle/propose.js +133 -0
  141. package/dist/oracle/propose.js.map +1 -0
  142. package/dist/oracle/query.d.ts +17 -0
  143. package/dist/oracle/query.d.ts.map +1 -0
  144. package/dist/oracle/query.js +106 -0
  145. package/dist/oracle/query.js.map +1 -0
  146. package/dist/oracle/summary.d.ts +11 -0
  147. package/dist/oracle/summary.d.ts.map +1 -0
  148. package/dist/oracle/summary.js +102 -0
  149. package/dist/oracle/summary.js.map +1 -0
  150. package/dist/oracle/types.d.ts +31 -0
  151. package/dist/oracle/types.d.ts.map +1 -0
  152. package/dist/oracle/types.js +2 -0
  153. package/dist/oracle/types.js.map +1 -0
  154. package/dist/sentinel/assert.d.ts +28 -0
  155. package/dist/sentinel/assert.d.ts.map +1 -0
  156. package/dist/sentinel/assert.js +63 -0
  157. package/dist/sentinel/assert.js.map +1 -0
  158. package/dist/sentinel/coverage.d.ts +14 -0
  159. package/dist/sentinel/coverage.d.ts.map +1 -0
  160. package/dist/sentinel/coverage.js +96 -0
  161. package/dist/sentinel/coverage.js.map +1 -0
  162. package/dist/sentinel/drift.d.ts +12 -0
  163. package/dist/sentinel/drift.d.ts.map +1 -0
  164. package/dist/sentinel/drift.js +149 -0
  165. package/dist/sentinel/drift.js.map +1 -0
  166. package/dist/sentinel/index.d.ts +7 -0
  167. package/dist/sentinel/index.d.ts.map +1 -0
  168. package/dist/sentinel/index.js +5 -0
  169. package/dist/sentinel/index.js.map +1 -0
  170. package/dist/sentinel/review.d.ts +15 -0
  171. package/dist/sentinel/review.d.ts.map +1 -0
  172. package/dist/sentinel/review.js +177 -0
  173. package/dist/sentinel/review.js.map +1 -0
  174. package/dist/setup.d.ts +103 -0
  175. package/dist/setup.d.ts.map +1 -0
  176. package/dist/setup.js +87 -0
  177. package/dist/setup.js.map +1 -0
  178. package/dist/shared/types.d.ts +173 -0
  179. package/dist/shared/types.d.ts.map +1 -0
  180. package/dist/shared/types.js +16 -0
  181. package/dist/shared/types.js.map +1 -0
  182. package/package.json +13 -8
  183. package/.github/copilot-instructions.md +0 -117
  184. package/CLAUDE.md +0 -146
  185. package/GEMINI.md +0 -73
  186. package/SETUP.md +0 -264
  187. package/evals/__tests__/eval.test.ts +0 -31
  188. package/evals/cases/auth_hs256_rejected.json +0 -46
  189. package/evals/cases/auth_rs256_valid.json +0 -30
  190. package/evals/cases/cache_missing_lock.json +0 -31
  191. package/evals/cases/db_naive_not_null.json +0 -32
  192. package/evals/cases/logging_pii_leak.json +0 -32
  193. package/evals/cases/migration_with_rollback.json +0 -43
  194. package/evals/cases/no_evidence_novel_design.json +0 -16
  195. package/evals/cases/payment_no_idempotency.json +0 -33
  196. package/evals/cases/redis_session_rejected.json +0 -32
  197. package/evals/cases/safe_refactor.json +0 -17
  198. package/evals/runner.ts +0 -226
  199. package/modules/AGENTS.md +0 -78
  200. package/modules/CLAUDE.md +0 -93
  201. package/modules/README.md +0 -504
  202. package/modules/advisor/ask.ts +0 -87
  203. package/modules/advisor/index.ts +0 -2
  204. package/modules/advisor/types.ts +0 -26
  205. package/modules/compass/behavior.ts +0 -161
  206. package/modules/compass/create.ts +0 -365
  207. package/modules/compass/evidence/collect.ts +0 -109
  208. package/modules/compass/index.ts +0 -7
  209. package/modules/compass/propose.ts +0 -152
  210. package/modules/compass/schemas.ts +0 -121
  211. package/modules/compass/score.ts +0 -77
  212. package/modules/compass/sources/index.ts +0 -413
  213. package/modules/compass/types.ts +0 -431
  214. package/modules/council/advisors.ts +0 -71
  215. package/modules/council/chairman.ts +0 -183
  216. package/modules/council/deliberate.ts +0 -141
  217. package/modules/council/frame.ts +0 -54
  218. package/modules/council/index.ts +0 -9
  219. package/modules/council/personas.ts +0 -57
  220. package/modules/council/reviewers.ts +0 -82
  221. package/modules/council/risk.ts +0 -89
  222. package/modules/council/types.ts +0 -107
  223. package/modules/jury/index.ts +0 -5
  224. package/modules/jury/preflight.ts +0 -101
  225. package/modules/jury/schema.ts +0 -24
  226. package/modules/jury/types.ts +0 -50
  227. package/modules/oracle/adapters/lance-db.ts +0 -81
  228. package/modules/oracle/adapters/xenova-embedder.ts +0 -43
  229. package/modules/oracle/bm25.ts +0 -92
  230. package/modules/oracle/index.ts +0 -36
  231. package/modules/oracle/log.ts +0 -15
  232. package/modules/oracle/propose.ts +0 -164
  233. package/modules/oracle/query.ts +0 -146
  234. package/modules/oracle/summary.ts +0 -116
  235. package/modules/oracle/types.ts +0 -32
  236. package/modules/sentinel/assert.ts +0 -95
  237. package/modules/sentinel/coverage.ts +0 -106
  238. package/modules/sentinel/drift.ts +0 -163
  239. package/modules/sentinel/index.ts +0 -6
  240. package/modules/sentinel/review.ts +0 -208
  241. package/modules/setup.ts +0 -202
  242. package/modules/shared/types.ts +0 -193
@@ -1,26 +0,0 @@
1
- import type { OracleResult, LLMProvider } from "../shared/types"
2
-
3
- export interface AdvisorInput {
4
- question: string
5
- evidence: OracleResult[]
6
- }
7
-
8
- export interface AdvisorAnswer {
9
- confidence: number
10
- what_we_know: string
11
- risks: string[]
12
- blockers: string[]
13
- recommendation: string
14
- next_step: string
15
- }
16
-
17
- export interface AdvisorOutput extends AdvisorAnswer {
18
- question: string
19
- /** Number of retries taken before the answer met the satisfaction threshold. */
20
- retries: number
21
- }
22
-
23
- export interface AdvisorDeps {
24
- llm: LLMProvider
25
- model?: string
26
- }
@@ -1,161 +0,0 @@
1
- import { randomUUID } from "crypto"
2
- import type {
3
- ProductBehavior, ProductBehaviorGap, ProductBehaviorContradiction, BehaviorMap,
4
- BehaviorMapInput, ProductSourceFinding, CompassEvidenceRef,
5
- } from "./types"
6
-
7
- // ── Deterministic behaviour mapping from source findings ─────────────────────
8
-
9
- export function mapBehaviorsFromFindings(
10
- findings: ProductSourceFinding[],
11
- input: BehaviorMapInput = {},
12
- ): BehaviorMap {
13
- const behaviors: ProductBehavior[] = []
14
- const gaps: ProductBehaviorGap[] = []
15
- const contradictions: ProductBehaviorContradiction[] = []
16
-
17
- // Group CLI commands into behaviours
18
- const cliFindings = findings.filter(f => f.kind === "cli")
19
- for (const f of cliFindings) {
20
- behaviors.push({
21
- id: `behavior-cli-${f.id}`,
22
- area: inferArea(f),
23
- name: f.title,
24
- description: f.summary,
25
- current_behavior: f.summary,
26
- evidence: [findingToRef(f)],
27
- basis: ["implemented"],
28
- confidence: f.confidence,
29
- })
30
- }
31
-
32
- // Extract documented user flows from docs findings
33
- const docsFindings = findings.filter(f => f.kind === "docs" && f.tags.includes("cli"))
34
- for (const f of docsFindings) {
35
- // Only add if not already covered by a CLI finding
36
- const alreadyPresent = behaviors.some(b =>
37
- b.current_behavior.toLowerCase().includes(extractCommand(f.summary).toLowerCase()) &&
38
- extractCommand(f.summary).length > 3,
39
- )
40
- if (!alreadyPresent && extractCommand(f.summary)) {
41
- behaviors.push({
42
- id: `behavior-docs-${f.id}`,
43
- area: inferArea(f),
44
- name: `Documented: ${f.title}`,
45
- description: f.summary,
46
- current_behavior: f.summary,
47
- evidence: [findingToRef(f)],
48
- basis: ["documented"],
49
- confidence: f.confidence * 0.9,
50
- })
51
- }
52
- }
53
-
54
- // Cross-reference: documented claims without implementation
55
- const docsHeadings = findings.filter(f => f.kind === "docs" && !f.tags.includes("cli"))
56
- const implementedAreas = new Set(behaviors.map(b => b.area))
57
-
58
- // Detect gaps: central product promises with no CLI surface
59
- const EXPECTED_AREAS = ["onboarding", "chronicle", "advisor", "review"]
60
- for (const expected of EXPECTED_AREAS) {
61
- const hasBehavior = behaviors.some(b => b.area === expected || b.name.toLowerCase().includes(expected))
62
- if (!hasBehavior) {
63
- const docRef = docsHeadings.find(f => f.summary.toLowerCase().includes(expected))
64
- gaps.push({
65
- id: `gap-${expected}`,
66
- area: expected,
67
- gap: `No first-class CLI command found for '${expected}'.`,
68
- why_it_matters: `'${expected}' appears in product docs but has no dedicated CLI surface.`,
69
- evidence: docRef ? [findingToRef(docRef)] : [],
70
- confidence: 0.7,
71
- })
72
- }
73
- }
74
-
75
- // Gap: no product-direction module (Compass itself)
76
- const hasCompass = behaviors.some(b => b.name.toLowerCase().includes("compass"))
77
- if (!hasCompass) {
78
- gaps.push({
79
- id: "gap-product-direction",
80
- area: "product direction",
81
- gap: "No product behaviour mapping or direction module currently exists.",
82
- why_it_matters: "Quorum helps agents avoid repeating engineering mistakes, but has no module to help avoid repeating product-direction mistakes.",
83
- evidence: [],
84
- confidence: 0.93,
85
- })
86
- }
87
-
88
- // Filter by area if provided
89
- const filteredBehaviors = input.area
90
- ? behaviors.filter(b =>
91
- b.area.toLowerCase().includes(input.area!.toLowerCase()) ||
92
- b.name.toLowerCase().includes(input.area!.toLowerCase()),
93
- )
94
- : behaviors
95
-
96
- const overallConfidence = filteredBehaviors.length === 0
97
- ? 0.5
98
- : filteredBehaviors.reduce((s, b) => s + b.confidence, 0) / filteredBehaviors.length
99
-
100
- return {
101
- generated_at: new Date().toISOString(),
102
- area: input.area,
103
- behaviors: filteredBehaviors,
104
- gaps: input.area
105
- ? gaps.filter(g => g.area.toLowerCase().includes(input.area!.toLowerCase()))
106
- : gaps,
107
- contradictions,
108
- confidence: Math.round(overallConfidence * 100) / 100,
109
- }
110
- }
111
-
112
- // ── Extract documented behaviors for LLM context ─────────────────────────────
113
-
114
- export function summarizeBehaviorMap(map: BehaviorMap): string {
115
- const lines: string[] = []
116
-
117
- if (map.behaviors.length > 0) {
118
- lines.push("## Current behaviours")
119
- for (const b of map.behaviors.slice(0, 20)) {
120
- lines.push(` ✓ ${b.current_behavior.slice(0, 100)}`)
121
- }
122
- }
123
-
124
- if (map.gaps.length > 0) {
125
- lines.push("\n## Gaps")
126
- for (const g of map.gaps) {
127
- lines.push(` ? ${g.gap}`)
128
- }
129
- }
130
-
131
- return lines.join("\n")
132
- }
133
-
134
- // ── Helpers ───────────────────────────────────────────────────────────────────
135
-
136
- function findingToRef(f: ProductSourceFinding): CompassEvidenceRef {
137
- return {
138
- id: f.id,
139
- kind: f.kind,
140
- source: f.source,
141
- path: f.path,
142
- summary: f.summary,
143
- confidence: f.confidence,
144
- }
145
- }
146
-
147
- function inferArea(f: ProductSourceFinding): string {
148
- if (f.tags.includes("onboarding") || f.tags.includes("init")) return "onboarding"
149
- if (f.tags.includes("chronicle") || f.tags.includes("commit") || f.tags.includes("proposal")) return "chronicle review"
150
- if (f.tags.includes("advisor")) return "memory retrieval"
151
- if (f.tags.includes("sentinel")) return "coverage"
152
- if (f.tags.includes("compass")) return "product direction"
153
- if (f.tags.includes("auth")) return "auth"
154
- if (f.tags.includes("cli")) return "cli"
155
- return "general"
156
- }
157
-
158
- function extractCommand(text: string): string {
159
- const match = text.match(/`(quorum [^`]+)`/)
160
- return match?.[1] ?? ""
161
- }
@@ -1,365 +0,0 @@
1
- import { randomUUID } from "crypto"
2
- import type { LLMProvider } from "../shared/types"
3
- import type {
4
- Compass, CreateCompassOptions,
5
- CompassBrief, CompassBriefInput,
6
- BehaviorMap, BehaviorMapInput,
7
- BehaviorAnswer, BehaviorQuestionInput,
8
- ProductOpportunity, OpportunitiesInput,
9
- ProductPathway, PathwaysInput,
10
- ProductBet, BigBetsInput,
11
- ProductIdeaScore, ScoreIdeaInput,
12
- ProductBrief, ProductBriefInput,
13
- CompassProposalInput, CompassProposalResult,
14
- CompassOutcomeInput, CompassOutcomeResultPayload,
15
- } from "./types"
16
- import { defaultSources } from "./sources/index"
17
- import { collectBearings, collectTerrain, formatBearingsForPrompt, formatTerrainForPrompt } from "./evidence/collect"
18
- import { mapBehaviorsFromFindings, summarizeBehaviorMap } from "./behavior"
19
- import { computeScore, scoreToRecommendation, explainScore } from "./score"
20
- import { stageProposal, stageOutcome } from "./propose"
21
- import { COMPASS_SYSTEM_PROMPT } from "./prompts/system"
22
- import {
23
- buildBriefPrompt, buildPathwaysPrompt,
24
- buildBetsPrompt, buildScorePrompt,
25
- } from "./prompts/index"
26
- import {
27
- CompassBriefLLMSchema, PathwaysLLMSchema,
28
- BetsLLMSchema, ProductIdeaScoreSchema,
29
- } from "./schemas"
30
-
31
- function parseLLMJson(raw: string): unknown {
32
- const cleaned = raw.replace(/^```(?:json)?\s*/m, "").replace(/\s*```$/m, "").trim()
33
- return JSON.parse(cleaned)
34
- }
35
-
36
- async function callLLM(
37
- llm: LLMProvider | undefined,
38
- userPrompt: string,
39
- model?: string,
40
- ): Promise<string> {
41
- if (!llm) throw new Error("Compass: LLM provider is required for this command. Set ANTHROPIC_API_KEY, OPENAI_API_KEY, or pass llm to setup().")
42
- return llm(
43
- [
44
- { role: "system" as const, content: COMPASS_SYSTEM_PROMPT },
45
- { role: "user" as const, content: userPrompt },
46
- ],
47
- model,
48
- )
49
- }
50
-
51
- export function createCompass(options: CreateCompassOptions): Compass {
52
- const {
53
- oracle,
54
- llm,
55
- rootDir = process.cwd(),
56
- chronicleDir = ".chronicle",
57
- sources = defaultSources(),
58
- models = {},
59
- minimumEvidence = "balanced",
60
- } = options
61
-
62
- // ── Shared context helpers ─────────────────────────────────────────────────
63
-
64
- async function getContext(area?: string) {
65
- const [bearings, terrain] = await Promise.all([
66
- collectBearings(oracle, area),
67
- collectTerrain(sources, rootDir, area),
68
- ])
69
- const chronicleContext = formatBearingsForPrompt(bearings)
70
- const behaviorContext = formatTerrainForPrompt(terrain.findings)
71
- const behaviorMap = mapBehaviorsFromFindings(terrain.findings, { area })
72
- return { bearings, terrain, chronicleContext, behaviorContext, behaviorMap }
73
- }
74
-
75
- // ── brief ──────────────────────────────────────────────────────────────────
76
-
77
- async function brief(input?: CompassBriefInput): Promise<CompassBrief> {
78
- const { chronicleContext, behaviorContext, terrain, behaviorMap } = await getContext(input?.area)
79
-
80
- if (!llm) {
81
- // Deterministic fallback
82
- return {
83
- generated_at: new Date().toISOString(),
84
- area: input?.area,
85
- product_direction: "Unable to synthesize direction — no LLM configured. See Chronicle and behaviour map for raw evidence.",
86
- known_from_chronicle: [],
87
- known_from_behavior: behaviorMap.behaviors.slice(0, 5).map(b => b.current_behavior),
88
- inferred: [],
89
- assumptions: [],
90
- unknowns: ["LLM not configured — full synthesis unavailable."],
91
- opportunities: [],
92
- missing_evidence: ["LLM provider required for synthesis"],
93
- recommended_next_step: "Run: quorum advisor brief",
94
- confidence: 0.4,
95
- }
96
- }
97
-
98
- const raw = await callLLM(llm, buildBriefPrompt({ chronicleContext, behaviorContext, area: input?.area }), models.brief)
99
- let parsed: unknown
100
- try { parsed = parseLLMJson(raw) } catch {
101
- throw new Error(`Compass brief: LLM returned non-JSON. Raw (first 300): ${raw.slice(0, 300)}`)
102
- }
103
-
104
- const result = CompassBriefLLMSchema.safeParse(parsed)
105
- if (!result.success) throw new Error(`Compass brief: LLM output failed validation: ${JSON.stringify(result.error.issues)}`)
106
-
107
- const d = result.data
108
- // Build opportunity items from behavior map gaps
109
- const opportunities: ProductOpportunity[] = behaviorMap.gaps.slice(0, 3).map((g, i) => ({
110
- id: `opp-gap-${i}`,
111
- title: g.gap,
112
- area: g.area,
113
- why_it_matters: g.why_it_matters,
114
- evidence_strength: "inferred" as const,
115
- suggested_next_step: "Run: quorum compass map",
116
- evidence: g.evidence,
117
- confidence: g.confidence,
118
- }))
119
-
120
- return {
121
- generated_at: new Date().toISOString(),
122
- area: input?.area,
123
- ...d,
124
- opportunities,
125
- }
126
- }
127
-
128
- // ── mapBehaviors ────────────────────────────────────────────────────────────
129
-
130
- async function mapBehaviors(input?: BehaviorMapInput): Promise<BehaviorMap> {
131
- const terrain = await collectTerrain(sources, rootDir, input?.area)
132
- return mapBehaviorsFromFindings(terrain.findings, input)
133
- }
134
-
135
- // ── behavior (single question) ──────────────────────────────────────────────
136
-
137
- async function behavior(input: BehaviorQuestionInput): Promise<BehaviorAnswer> {
138
- const terrain = await collectTerrain(sources, rootDir, input.area)
139
- const map = mapBehaviorsFromFindings(terrain.findings, { area: input.area })
140
-
141
- const relevant = terrain.findings.filter(f =>
142
- f.summary.toLowerCase().includes(input.question.toLowerCase().split(" ").slice(0, 3).join(" ")) ||
143
- f.tags.some(t => input.question.toLowerCase().includes(t)),
144
- ).slice(0, 10)
145
-
146
- if (!llm || input.deterministic) {
147
- // Deterministic answer from behaviour map
148
- const what_exists = map.behaviors.slice(0, 6).map(b => b.current_behavior)
149
- const what_missing = map.gaps.slice(0, 4).map(g => g.gap)
150
- return {
151
- question: input.question,
152
- what_exists,
153
- what_appears_missing: what_missing,
154
- product_implication: map.gaps.length > 0
155
- ? `The area has ${map.behaviors.length} documented behaviours but ${map.gaps.length} notable gaps.`
156
- : `The area appears well-covered with ${map.behaviors.length} documented behaviours.`,
157
- evidence: relevant.map(f => ({ id: f.id, kind: f.kind, source: f.source, path: f.path, summary: f.summary, confidence: f.confidence })),
158
- confidence: map.confidence,
159
- }
160
- }
161
-
162
- // LLM-backed synthesis
163
- const contextLines = [
164
- "## Behaviours found",
165
- ...map.behaviors.slice(0, 10).map(b => ` ✓ ${b.current_behavior}`),
166
- "## Gaps found",
167
- ...map.gaps.slice(0, 5).map(g => ` ? ${g.gap}`),
168
- ].join("\n")
169
-
170
- const prompt = `Answer this product-behaviour question by combining repo evidence with known behaviours.
171
-
172
- Question: ${input.question}
173
-
174
- ${contextLines}
175
-
176
- Return JSON:
177
- {
178
- "question": "${input.question}",
179
- "what_exists": ["<existing behaviour>"],
180
- "what_appears_missing": ["<missing capability>"],
181
- "product_implication": "<one sentence product implication>",
182
- "evidence": [{ "id": "<id>", "kind": "<kind>", "source": "<source>", "summary": "<summary>", "confidence": <0–1> }],
183
- "confidence": <0–1>
184
- }`
185
-
186
- const raw = await callLLM(llm, prompt, models.brief)
187
- try {
188
- const parsed = parseLLMJson(raw) as BehaviorAnswer
189
- return { ...parsed, question: input.question }
190
- } catch {
191
- // Fall back to deterministic
192
- return behavior({ ...input, deterministic: true })
193
- }
194
- }
195
-
196
- // ── opportunities ───────────────────────────────────────────────────────────
197
-
198
- async function opportunities(input?: OpportunitiesInput): Promise<ProductOpportunity[]> {
199
- const terrain = await collectTerrain(sources, rootDir, input?.area)
200
- const map = mapBehaviorsFromFindings(terrain.findings, { area: input?.area })
201
-
202
- const opps: ProductOpportunity[] = map.gaps.map((g, i) => ({
203
- id: `opp-${i}`,
204
- title: g.gap,
205
- area: g.area,
206
- why_it_matters: g.why_it_matters,
207
- evidence_strength: g.confidence >= 0.7 ? "strong" as const
208
- : g.confidence >= 0.5 ? "medium" as const
209
- : "inferred" as const,
210
- suggested_next_step: `quorum compass pathways --goal "${g.gap.slice(0, 50)}"`,
211
- evidence: g.evidence,
212
- confidence: g.confidence,
213
- }))
214
-
215
- const limited = input?.limit ? opps.slice(0, input.limit) : opps
216
- return input?.goal
217
- ? limited.filter(o => o.title.toLowerCase().includes(input.goal!.toLowerCase()) || o.area.toLowerCase().includes(input.goal!.toLowerCase()))
218
- : limited
219
- }
220
-
221
- // ── pathways ────────────────────────────────────────────────────────────────
222
-
223
- async function pathways(input: PathwaysInput): Promise<ProductPathway[]> {
224
- const { chronicleContext, behaviorContext } = await getContext(input.area)
225
- const raw = await callLLM(
226
- llm,
227
- buildPathwaysPrompt({ ...input, chronicleContext, behaviorContext, limit: input.limit ?? 5 }),
228
- models.pathways,
229
- )
230
-
231
- let parsed: unknown
232
- try { parsed = parseLLMJson(raw) } catch {
233
- throw new Error(`Compass pathways: LLM returned non-JSON. Raw (first 300): ${raw.slice(0, 300)}`)
234
- }
235
- const result = PathwaysLLMSchema.safeParse(parsed)
236
- if (!result.success) throw new Error(`Compass pathways: LLM output failed validation: ${JSON.stringify(result.error.issues)}`)
237
-
238
- // Recompute scores deterministically
239
- return result.data.pathways.map(p => ({
240
- ...p,
241
- scores: computeScore(p.scores),
242
- }))
243
- }
244
-
245
- // ── bigBets ─────────────────────────────────────────────────────────────────
246
-
247
- async function bigBets(input: BigBetsInput): Promise<ProductBet[]> {
248
- const { chronicleContext, behaviorContext } = await getContext()
249
- const raw = await callLLM(
250
- llm,
251
- buildBetsPrompt({ ...input, chronicleContext, behaviorContext }),
252
- models.bets,
253
- )
254
-
255
- let parsed: unknown
256
- try { parsed = parseLLMJson(raw) } catch {
257
- throw new Error(`Compass bets: LLM returned non-JSON. Raw (first 300): ${raw.slice(0, 300)}`)
258
- }
259
- const result = BetsLLMSchema.safeParse(parsed)
260
- if (!result.success) throw new Error(`Compass bets: LLM output failed validation: ${JSON.stringify(result.error.issues)}`)
261
-
262
- return result.data.bets.map(b => ({
263
- ...b,
264
- scores: computeScore(b.scores),
265
- }))
266
- }
267
-
268
- // ── scoreIdea ───────────────────────────────────────────────────────────────
269
-
270
- async function scoreIdea(input: ScoreIdeaInput): Promise<ProductIdeaScore> {
271
- const { chronicleContext, behaviorContext } = await getContext()
272
- const raw = await callLLM(
273
- llm,
274
- buildScorePrompt({ ...input, chronicleContext, behaviorContext }),
275
- models.score,
276
- )
277
-
278
- let parsed: unknown
279
- try { parsed = parseLLMJson(raw) } catch {
280
- throw new Error(`Compass score: LLM returned non-JSON. Raw (first 300): ${raw.slice(0, 300)}`)
281
- }
282
- const result = ProductIdeaScoreSchema.safeParse(parsed)
283
- if (!result.success) throw new Error(`Compass score: LLM output failed validation: ${JSON.stringify(result.error.issues)}`)
284
-
285
- const d = result.data
286
- return {
287
- ...d,
288
- scores: computeScore(d.scores),
289
- recommendation: scoreToRecommendation(d.scores.total),
290
- }
291
- }
292
-
293
- // ── productBrief ─────────────────────────────────────────────────────────────
294
-
295
- async function productBrief(input: ProductBriefInput): Promise<ProductBrief> {
296
- const { chronicleContext, behaviorContext, terrain } = await getContext()
297
-
298
- const prompt = `Generate a product brief for implementation planning.
299
-
300
- Title: ${input.title}
301
- ${input.idea ? `Idea: ${input.idea}` : ""}
302
- ${input.context ? `Context: ${input.context}` : ""}
303
-
304
- ## Chronicle evidence
305
- ${chronicleContext}
306
-
307
- ## Current behaviour
308
- ${behaviorContext}
309
-
310
- Return JSON:
311
- {
312
- "title": "${input.title}",
313
- "problem": "<problem being solved>",
314
- "target_user": "<who>",
315
- "current_behavior": ["<relevant current behaviour>"],
316
- "product_opportunity": "<gap or need>",
317
- "recommended_solution": "<recommended approach>",
318
- "smallest_useful_version": "<minimum useful version>",
319
- "non_goals": ["<explicit non-goal>"],
320
- "user_flow": ["<step in user flow>"],
321
- "implementation_notes": ["<implementation note>"],
322
- "dependencies": ["<dependency>"],
323
- "risks": ["<risk>"],
324
- "open_questions": ["<open question>"],
325
- "assumptions": ["<assumption>"],
326
- "validation_signals": ["<signal>"],
327
- "invalidation_signals": ["<signal>"],
328
- "evidence": [{ "id": "<id>", "kind": "<kind>", "source": "<source>", "summary": "<summary>", "confidence": <0–1> }],
329
- "suggested_quorum_checks": ["<quorum check command>"]
330
- }`
331
-
332
- const raw = await callLLM(llm, prompt, models.brief)
333
- try {
334
- const parsed = parseLLMJson(raw) as ProductBrief
335
- return parsed
336
- } catch {
337
- throw new Error(`Compass productBrief: LLM returned non-JSON. Raw (first 300): ${raw.slice(0, 300)}`)
338
- }
339
- }
340
-
341
- // ── propose ──────────────────────────────────────────────────────────────────
342
-
343
- async function propose(input: CompassProposalInput): Promise<CompassProposalResult> {
344
- return stageProposal(input, oracle)
345
- }
346
-
347
- // ── outcome ──────────────────────────────────────────────────────────────────
348
-
349
- async function outcome(input: CompassOutcomeInput): Promise<CompassOutcomeResultPayload> {
350
- return stageOutcome(input, oracle)
351
- }
352
-
353
- return {
354
- brief,
355
- mapBehaviors,
356
- behavior,
357
- opportunities,
358
- pathways,
359
- bigBets,
360
- scoreIdea,
361
- productBrief,
362
- propose,
363
- outcome,
364
- }
365
- }
@@ -1,109 +0,0 @@
1
- import type { OracleClient, OracleResult } from "../../shared/types"
2
- import type { CompassEvidenceRef, ProductBearing, ProductSource, ProductSourceFinding } from "../types"
3
- import { randomUUID } from "crypto"
4
-
5
- // ── Bearings from Chronicle ───────────────────────────────────────────────────
6
-
7
- export async function collectBearings(
8
- oracle: OracleClient,
9
- area?: string,
10
- ): Promise<ProductBearing[]> {
11
- const queries = [
12
- area ?? "product direction goals decisions",
13
- "rejected approaches refuted alternatives",
14
- "constraints scope out-of-scope",
15
- ]
16
-
17
- const seen = new Set<string>()
18
- const bearings: ProductBearing[] = []
19
-
20
- for (const q of queries) {
21
- let results: OracleResult[]
22
- try { results = await oracle.query(q, { limit: 8 }) } catch { continue }
23
-
24
- for (const entry of results) {
25
- if (seen.has(entry.id)) continue
26
- seen.add(entry.id)
27
-
28
- const text = entry.decision ?? entry.key_insight
29
- bearings.push({
30
- id: `bearing-${entry.id.slice(0, 8)}`,
31
- title: entry.topic ?? text.slice(0, 60),
32
- summary: text,
33
- area: entry.scope?.[0],
34
- evidence: [
35
- {
36
- id: randomUUID().slice(0, 8),
37
- kind: "chronicle",
38
- source: ".chronicle/committed",
39
- entry_id: entry.id,
40
- summary: text,
41
- confidence: entry.confidence,
42
- },
43
- ],
44
- confidence: entry.confidence,
45
- })
46
- }
47
- }
48
-
49
- return bearings
50
- }
51
-
52
- // ── Terrain from source scanners ──────────────────────────────────────────────
53
-
54
- export interface TerrainResult {
55
- findings: ProductSourceFinding[]
56
- evidenceRefs: CompassEvidenceRef[]
57
- }
58
-
59
- export async function collectTerrain(
60
- sources: ProductSource[],
61
- rootDir: string,
62
- area?: string,
63
- ): Promise<TerrainResult> {
64
- const allFindings: ProductSourceFinding[] = []
65
-
66
- for (const source of sources) {
67
- const found = await source.scan({ rootDir, area })
68
- allFindings.push(...found)
69
- }
70
-
71
- const evidenceRefs: CompassEvidenceRef[] = allFindings.map(f => ({
72
- id: f.id,
73
- kind: f.kind,
74
- source: f.source,
75
- path: f.path,
76
- line: f.line,
77
- summary: f.summary,
78
- confidence: f.confidence,
79
- }))
80
-
81
- return { findings: allFindings, evidenceRefs }
82
- }
83
-
84
- // ── Format helpers for LLM prompts ───────────────────────────────────────────
85
-
86
- export function formatBearingsForPrompt(bearings: ProductBearing[]): string {
87
- if (bearings.length === 0) return "No Chronicle entries found."
88
- return bearings
89
- .map(b => `[${b.id}] (confidence: ${b.confidence.toFixed(2)}) ${b.summary}`)
90
- .join("\n")
91
- }
92
-
93
- export function formatTerrainForPrompt(findings: ProductSourceFinding[], limit = 40): string {
94
- if (findings.length === 0) return "No product behaviour found in sources."
95
-
96
- // Group by kind
97
- const groups: Record<string, ProductSourceFinding[]> = {}
98
- for (const f of findings.slice(0, limit)) {
99
- groups[f.kind] = groups[f.kind] ?? []
100
- groups[f.kind].push(f)
101
- }
102
-
103
- return Object.entries(groups)
104
- .map(([kind, items]) => {
105
- const lines = items.slice(0, 10).map(i => ` - ${i.title}: ${i.summary.slice(0, 100)}`)
106
- return `${kind.toUpperCase()}:\n${lines.join("\n")}`
107
- })
108
- .join("\n\n")
109
- }
@@ -1,7 +0,0 @@
1
- export { createCompass } from "./create"
2
- export { defaultSources, docsSource, packageSource, cliSource, repoSource, testsSource, configSource } from "./sources/index"
3
- export { mapBehaviorsFromFindings, summarizeBehaviorMap } from "./behavior"
4
- export { computeScore, scoreToRecommendation, scoreToLabel, explainScore } from "./score"
5
- export { collectBearings, collectTerrain, formatBearingsForPrompt, formatTerrainForPrompt } from "./evidence/collect"
6
- export { stageProposal, stageOutcome } from "./propose"
7
- export * from "./types"