contextgit 0.0.2 → 0.0.4

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 (163) hide show
  1. package/dist/bootstrap.d.ts +10 -0
  2. package/dist/bootstrap.d.ts.map +1 -0
  3. package/dist/bootstrap.js +43 -0
  4. package/dist/bootstrap.js.map +1 -0
  5. package/dist/commands/branch.d.ts +13 -0
  6. package/dist/commands/branch.d.ts.map +1 -0
  7. package/dist/commands/branch.js +52 -0
  8. package/dist/commands/branch.js.map +1 -0
  9. package/dist/commands/claim.d.ts +15 -0
  10. package/dist/commands/claim.d.ts.map +1 -0
  11. package/dist/commands/claim.js +60 -0
  12. package/dist/commands/claim.js.map +1 -0
  13. package/dist/commands/commit.d.ts +17 -0
  14. package/dist/commands/commit.d.ts.map +1 -0
  15. package/dist/commands/commit.js +83 -0
  16. package/dist/commands/commit.js.map +1 -0
  17. package/dist/commands/context.d.ts +9 -0
  18. package/dist/commands/context.d.ts.map +1 -0
  19. package/dist/commands/context.js +38 -0
  20. package/dist/commands/context.js.map +1 -0
  21. package/dist/commands/doctor.d.ts +6 -0
  22. package/dist/commands/doctor.d.ts.map +1 -0
  23. package/dist/commands/doctor.js +84 -0
  24. package/dist/commands/doctor.js.map +1 -0
  25. package/dist/commands/init.d.ts +10 -0
  26. package/dist/commands/init.d.ts.map +1 -0
  27. package/dist/commands/init.js +184 -0
  28. package/dist/commands/init.js.map +1 -0
  29. package/dist/commands/keygen.d.ts +10 -0
  30. package/dist/commands/keygen.d.ts.map +1 -0
  31. package/dist/commands/keygen.js +57 -0
  32. package/dist/commands/keygen.js.map +1 -0
  33. package/dist/commands/log.d.ts +13 -0
  34. package/dist/commands/log.d.ts.map +1 -0
  35. package/dist/commands/log.js +91 -0
  36. package/dist/commands/log.js.map +1 -0
  37. package/dist/commands/merge.d.ts +12 -0
  38. package/dist/commands/merge.d.ts.map +1 -0
  39. package/dist/commands/merge.js +29 -0
  40. package/dist/commands/merge.js.map +1 -0
  41. package/dist/commands/pull.d.ts +10 -0
  42. package/dist/commands/pull.d.ts.map +1 -0
  43. package/dist/commands/pull.js +123 -0
  44. package/dist/commands/pull.js.map +1 -0
  45. package/dist/commands/push.d.ts +10 -0
  46. package/dist/commands/push.d.ts.map +1 -0
  47. package/dist/commands/push.js +141 -0
  48. package/dist/commands/push.js.map +1 -0
  49. package/dist/commands/remote-show.d.ts +6 -0
  50. package/dist/commands/remote-show.d.ts.map +1 -0
  51. package/dist/commands/remote-show.js +71 -0
  52. package/dist/commands/remote-show.js.map +1 -0
  53. package/dist/commands/search.d.ts +11 -0
  54. package/dist/commands/search.d.ts.map +1 -0
  55. package/dist/commands/search.js +47 -0
  56. package/dist/commands/search.js.map +1 -0
  57. package/dist/commands/serve.d.ts +9 -0
  58. package/dist/commands/serve.d.ts.map +1 -0
  59. package/dist/commands/serve.js +51 -0
  60. package/dist/commands/serve.js.map +1 -0
  61. package/dist/commands/set-remote.d.ts +9 -0
  62. package/dist/commands/set-remote.d.ts.map +1 -0
  63. package/dist/commands/set-remote.js +26 -0
  64. package/dist/commands/set-remote.js.map +1 -0
  65. package/dist/commands/status.d.ts +6 -0
  66. package/dist/commands/status.d.ts.map +1 -0
  67. package/dist/commands/status.js +54 -0
  68. package/dist/commands/status.js.map +1 -0
  69. package/dist/commands/unclaim.d.ts +9 -0
  70. package/dist/commands/unclaim.d.ts.map +1 -0
  71. package/dist/commands/unclaim.js +22 -0
  72. package/dist/commands/unclaim.js.map +1 -0
  73. package/dist/config.d.ts +19 -0
  74. package/dist/config.d.ts.map +1 -0
  75. package/dist/config.js +58 -0
  76. package/dist/config.js.map +1 -0
  77. package/dist/git-hooks.d.ts +6 -0
  78. package/dist/git-hooks.d.ts.map +1 -0
  79. package/dist/git-hooks.js +58 -0
  80. package/dist/git-hooks.js.map +1 -0
  81. package/package.json +24 -18
  82. package/.claude/settings.local.json +0 -41
  83. package/.contextgit/config.json +0 -10
  84. package/.contextgit/system-prompt.md +0 -4
  85. package/.github/workflows/contextgit-ci.yml +0 -40
  86. package/CLAUDE.md +0 -123
  87. package/CLAUDE.md.next +0 -65
  88. package/docs/ContextGit_ARCHITECTURE_v3.md +0 -1141
  89. package/docs/ContextGit_DELTA.md +0 -84
  90. package/docs/ContextGit_PHASE1_PLAN.md +0 -177
  91. package/docs/ContextGit_PHASE2_PLAN.md +0 -535
  92. package/docs/ContextGit_PRD_v4.md +0 -488
  93. package/docs/decisions.md +0 -370
  94. package/packages/api/package.json +0 -25
  95. package/packages/api/src/bootstrap.ts +0 -64
  96. package/packages/api/src/config.ts +0 -45
  97. package/packages/api/src/index.ts +0 -17
  98. package/packages/api/src/middleware/auth.test.ts +0 -83
  99. package/packages/api/src/middleware/auth.ts +0 -41
  100. package/packages/api/src/remote-store.test.ts +0 -301
  101. package/packages/api/src/router.ts +0 -121
  102. package/packages/api/src/server-config.ts +0 -34
  103. package/packages/api/src/server.ts +0 -38
  104. package/packages/api/src/store-router.ts +0 -241
  105. package/packages/api/tsconfig.json +0 -8
  106. package/packages/cli/package.json +0 -29
  107. package/packages/cli/src/bootstrap.ts +0 -68
  108. package/packages/cli/src/commands/branch.ts +0 -58
  109. package/packages/cli/src/commands/claim.ts +0 -58
  110. package/packages/cli/src/commands/commit.ts +0 -79
  111. package/packages/cli/src/commands/context.ts +0 -46
  112. package/packages/cli/src/commands/doctor.ts +0 -99
  113. package/packages/cli/src/commands/init.ts +0 -141
  114. package/packages/cli/src/commands/keygen.ts +0 -65
  115. package/packages/cli/src/commands/log.ts +0 -103
  116. package/packages/cli/src/commands/merge.ts +0 -36
  117. package/packages/cli/src/commands/pull.ts +0 -145
  118. package/packages/cli/src/commands/push.ts +0 -158
  119. package/packages/cli/src/commands/remote-show.ts +0 -87
  120. package/packages/cli/src/commands/search.ts +0 -54
  121. package/packages/cli/src/commands/serve.ts +0 -61
  122. package/packages/cli/src/commands/set-remote.ts +0 -30
  123. package/packages/cli/src/commands/status.ts +0 -62
  124. package/packages/cli/src/commands/unclaim.ts +0 -28
  125. package/packages/cli/src/config.ts +0 -64
  126. package/packages/cli/src/git-hooks.ts +0 -61
  127. package/packages/cli/tsconfig.json +0 -9
  128. package/packages/core/package.json +0 -28
  129. package/packages/core/src/embeddings.test.ts +0 -58
  130. package/packages/core/src/embeddings.ts +0 -75
  131. package/packages/core/src/engine.ts +0 -274
  132. package/packages/core/src/index.ts +0 -6
  133. package/packages/core/src/snapshot.ts +0 -82
  134. package/packages/core/src/summarizer.test.ts +0 -120
  135. package/packages/core/src/summarizer.ts +0 -113
  136. package/packages/core/src/threads.ts +0 -29
  137. package/packages/core/src/types.ts +0 -240
  138. package/packages/core/tsconfig.json +0 -9
  139. package/packages/mcp/package.json +0 -31
  140. package/packages/mcp/src/auto-snapshot.ts +0 -83
  141. package/packages/mcp/src/config.ts +0 -53
  142. package/packages/mcp/src/git-sync.ts +0 -94
  143. package/packages/mcp/src/index.ts +0 -19
  144. package/packages/mcp/src/server.ts +0 -377
  145. package/packages/mcp/tsconfig.json +0 -9
  146. package/packages/store/package.json +0 -30
  147. package/packages/store/src/branch-merge.test.ts +0 -127
  148. package/packages/store/src/engine-integration.test.ts +0 -93
  149. package/packages/store/src/index.ts +0 -3
  150. package/packages/store/src/interface.ts +0 -62
  151. package/packages/store/src/local/claims.test.ts +0 -190
  152. package/packages/store/src/local/index.ts +0 -380
  153. package/packages/store/src/local/local-store.test.ts +0 -164
  154. package/packages/store/src/local/migrations.ts +0 -99
  155. package/packages/store/src/local/queries.ts +0 -760
  156. package/packages/store/src/local/schema.ts +0 -157
  157. package/packages/store/src/remote/index.ts +0 -300
  158. package/packages/store/tsconfig.json +0 -9
  159. package/pnpm-workspace.yaml +0 -2
  160. package/scripts/build.sh +0 -28
  161. package/tsconfig.base.json +0 -14
  162. package/vitest.config.ts +0 -15
  163. /package/{packages/cli/bin → bin}/run.js +0 -0
@@ -1,58 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest'
2
- import { EmbeddingService } from './embeddings.js'
3
-
4
- // Fake pipeline that returns a predictable 384-dim vector
5
- function makeFakePipeline() {
6
- return async (_text: string, _opts: unknown) => ({
7
- data: new Float32Array(384).fill(0.5),
8
- })
9
- }
10
-
11
- function makeBrokenPipeline() {
12
- return Promise.reject(new Error('model load failed'))
13
- }
14
-
15
- describe('EmbeddingService', () => {
16
- it('returns a Float32Array of length 384 on success', async () => {
17
- const svc = new EmbeddingService({
18
- pipelineFactory: async () => makeFakePipeline(),
19
- })
20
- const vec = await svc.embed('hello world')
21
- expect(vec).toBeInstanceOf(Float32Array)
22
- expect(vec!.length).toBe(384)
23
- })
24
-
25
- it('returns null when pipeline load fails', async () => {
26
- const svc = new EmbeddingService({
27
- pipelineFactory: async () => { throw new Error('load error') },
28
- })
29
- const vec = await svc.embed('hello')
30
- expect(vec).toBeNull()
31
- })
32
-
33
- it('returns null when pipeline call throws', async () => {
34
- const svc = new EmbeddingService({
35
- pipelineFactory: async () => {
36
- return async () => { throw new Error('inference error') }
37
- },
38
- })
39
- const vec = await svc.embed('hello')
40
- expect(vec).toBeNull()
41
- })
42
-
43
- it('never throws — always returns null on any error', async () => {
44
- const svc = new EmbeddingService({
45
- pipelineFactory: () => makeBrokenPipeline() as never,
46
- })
47
- await expect(svc.embed('anything')).resolves.toBeNull()
48
- })
49
-
50
- it('loads the pipeline only once across multiple embeds', async () => {
51
- const factory = vi.fn(async () => makeFakePipeline())
52
- const svc = new EmbeddingService({ pipelineFactory: factory })
53
- await svc.embed('a')
54
- await svc.embed('b')
55
- await svc.embed('c')
56
- expect(factory).toHaveBeenCalledTimes(1)
57
- })
58
- })
@@ -1,75 +0,0 @@
1
- // EmbeddingService — generates 384-dim sentence embeddings using
2
- // @xenova/transformers (all-MiniLM-L6-v2, runs fully local, no API key).
3
- //
4
- // Usage:
5
- // const svc = new EmbeddingService()
6
- // const vector = await svc.embed('some text') // Float32Array | null
7
- //
8
- // Load failure is silently swallowed — callers receive null and should fall
9
- // back to full-text search. Never let embedding errors propagate outward.
10
-
11
- type PipelineFn = (text: string, options: { pooling: string; normalize: boolean }) => Promise<{ data: Float32Array }>
12
-
13
- export interface EmbeddingServiceOptions {
14
- /** Override the pipeline factory — for tests. */
15
- pipelineFactory?: (task: string, model: string) => Promise<PipelineFn>
16
- }
17
-
18
- export class EmbeddingService {
19
- private pipeline: PipelineFn | null = null
20
- private loadPromise: Promise<void> | null = null
21
- private readonly pipelineFactory: (task: string, model: string) => Promise<PipelineFn>
22
-
23
- static readonly MODEL = 'Xenova/all-MiniLM-L6-v2'
24
- static readonly DIMS = 384
25
-
26
- constructor(options: EmbeddingServiceOptions = {}) {
27
- this.pipelineFactory = options.pipelineFactory ?? EmbeddingService.defaultPipelineFactory
28
- }
29
-
30
- /**
31
- * Generate a 384-dim embedding for `text`.
32
- * Returns null if the model is unavailable or any error occurs.
33
- */
34
- async embed(text: string): Promise<Float32Array | null> {
35
- try {
36
- await this.ensureLoaded()
37
- if (!this.pipeline) return null
38
- const result = await this.pipeline(text, { pooling: 'mean', normalize: true })
39
- return result.data
40
- } catch {
41
- return null
42
- }
43
- }
44
-
45
- /** True once the model has been loaded at least once (even if it failed). */
46
- get isReady(): boolean {
47
- return this.loadPromise !== null
48
- }
49
-
50
- private async ensureLoaded(): Promise<void> {
51
- if (this.pipeline) return
52
- if (!this.loadPromise) {
53
- this.loadPromise = this.load()
54
- }
55
- await this.loadPromise
56
- }
57
-
58
- private async load(): Promise<void> {
59
- try {
60
- this.pipeline = await this.pipelineFactory('feature-extraction', EmbeddingService.MODEL)
61
- } catch {
62
- this.pipeline = null
63
- }
64
- }
65
-
66
- private static async defaultPipelineFactory(_task: string, model: string): Promise<PipelineFn> {
67
- // Dynamic import so the heavy @xenova/transformers bundle is only loaded
68
- // when embeddings are actually needed.
69
- const { pipeline } = await import('@xenova/transformers')
70
- // Cast task to any to avoid strict PipelineType enum mismatch — we always
71
- // pass 'feature-extraction' which is valid at runtime.
72
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
73
- return pipeline('feature-extraction' as any, model) as unknown as PipelineFn
74
- }
75
- }
@@ -1,274 +0,0 @@
1
- // ContextEngine — pure business logic layer, no I/O.
2
- // All persistence is delegated to the injected ContextStore.
3
- //
4
- // Usage:
5
- // const engine = new ContextEngine(store, 'agent-1', 'dev', 'claude-code', 'interactive')
6
- // await engine.init(projectId, branchId)
7
- // await engine.commit({ message: '...', content: '...' })
8
- // const snapshot = await engine.context('global')
9
-
10
- import type {
11
- AgentRole,
12
- Branch,
13
- Commit,
14
- CommitType,
15
- ContextScope,
16
- SearchResult,
17
- SessionSnapshot,
18
- WorkflowType,
19
- } from './types.js'
20
- import { RollingSummarizer } from './summarizer.js'
21
- import { EmbeddingService } from './embeddings.js'
22
-
23
- // ─── Store subset the engine requires ─────────────────────────────────────────
24
-
25
- interface EngineBranchInput {
26
- projectId: string
27
- name: string
28
- gitBranch: string
29
- parentBranchId?: string
30
- }
31
-
32
- export interface EngineStore {
33
- getBranch(id: string): Promise<{ headCommitId?: string; gitBranch?: string; name?: string } | null>
34
- getCommit(id: string): Promise<{ summary: string } | null>
35
- createBranch(input: EngineBranchInput): Promise<Branch>
36
- createCommit(input: EngineCommitStoreInput): Promise<Commit>
37
- mergeBranch(sourceBranchId: string, targetBranchId: string, summary: string): Promise<Commit>
38
- getSessionSnapshot(projectId: string, branchId: string): Promise<SessionSnapshot>
39
- upsertAgent(agent: EngineAgentInput): Promise<unknown>
40
- indexEmbedding(commitId: string, vector: Float32Array): Promise<void>
41
- semanticSearch(vector: Float32Array, projectId: string, limit: number): Promise<SearchResult[]>
42
- }
43
-
44
- interface EngineCommitStoreInput {
45
- branchId: string
46
- agentId: string
47
- agentRole: AgentRole
48
- tool: string
49
- workflowType: WorkflowType
50
- message: string
51
- content: string
52
- summary: string
53
- commitType: CommitType
54
- gitCommitSha?: string
55
- ciRunId?: string
56
- pipelineName?: string
57
- threads?: {
58
- open?: string[]
59
- close?: Array<{ id: string; note: string }>
60
- }
61
- }
62
-
63
- interface EngineAgentInput {
64
- id: string
65
- projectId: string
66
- role: AgentRole
67
- tool: string
68
- workflowType: WorkflowType
69
- }
70
-
71
- // ─── Public API ───────────────────────────────────────────────────────────────
72
-
73
- export interface EngineCommitInput {
74
- message: string
75
- content: string
76
- commitType?: CommitType
77
- gitCommitSha?: string
78
- ciRunId?: string
79
- pipelineName?: string
80
- threads?: {
81
- open?: string[]
82
- close?: Array<{ id: string; note: string }>
83
- }
84
- }
85
-
86
- export interface EngineOptions {
87
- summarizer?: RollingSummarizer
88
- embeddingService?: EmbeddingService
89
- }
90
-
91
- export class ContextEngine {
92
- private projectId = ''
93
- private branchId = ''
94
- private readonly summarizer: RollingSummarizer
95
- private readonly embeddings: EmbeddingService | null
96
-
97
- constructor(
98
- private readonly store: EngineStore,
99
- private readonly agentId: string,
100
- private readonly agentRole: AgentRole,
101
- private readonly tool: string,
102
- private readonly workflowType: WorkflowType,
103
- options: EngineOptions = {},
104
- ) {
105
- this.summarizer = options.summarizer ?? new RollingSummarizer()
106
- this.embeddings = options.embeddingService ?? null
107
- }
108
-
109
- /**
110
- * Bind the engine to a project + branch and register this agent.
111
- * Must be called before commit() or context().
112
- */
113
- async init(projectId: string, branchId: string): Promise<void> {
114
- this.projectId = projectId
115
- this.branchId = branchId
116
- await this.store.upsertAgent({
117
- id: this.agentId,
118
- projectId,
119
- role: this.agentRole,
120
- tool: this.tool,
121
- workflowType: this.workflowType,
122
- })
123
- }
124
-
125
- /**
126
- * Persist a context commit. Summary is generated automatically via the
127
- * RollingSummarizer (Week 1: string truncation; Week 2: Claude Haiku).
128
- */
129
- async commit(input: EngineCommitInput): Promise<Commit> {
130
- this.assertInitialized()
131
-
132
- // Fetch previous summary from branch HEAD (if any)
133
- let previousSummary = ''
134
- const branch = await this.store.getBranch(this.branchId)
135
- if (branch?.headCommitId) {
136
- const head = await this.store.getCommit(branch.headCommitId)
137
- previousSummary = head?.summary ?? ''
138
- }
139
-
140
- const summary = await this.summarizer.summarize(input.content, previousSummary, 'branch')
141
-
142
- const commit = await this.store.createCommit({
143
- branchId: this.branchId,
144
- agentId: this.agentId,
145
- agentRole: this.agentRole,
146
- tool: this.tool,
147
- workflowType: this.workflowType,
148
- message: input.message,
149
- content: input.content,
150
- summary,
151
- commitType: input.commitType ?? 'manual',
152
- gitCommitSha: input.gitCommitSha,
153
- ciRunId: input.ciRunId,
154
- pipelineName: input.pipelineName,
155
- threads: input.threads,
156
- })
157
-
158
- // Generate and index embedding asynchronously — never block the commit.
159
- if (this.embeddings) {
160
- const text = `${input.message}\n${input.content}`
161
- this.embeddings.embed(text).then(vector => {
162
- if (vector) return this.store.indexEmbedding(commit.id, vector)
163
- }).catch(() => { /* swallow — indexing is best-effort */ })
164
- }
165
-
166
- return commit
167
- }
168
-
169
- /**
170
- * Create a new branch from the current branch.
171
- * Writes a `branch-init` commit on the new branch carrying the parent HEAD
172
- * summary forward so the branch starts with full context.
173
- */
174
- async branch(gitBranch: string, name?: string): Promise<Branch> {
175
- this.assertInitialized()
176
-
177
- // Carry parent HEAD summary into the new branch
178
- const parentBranch = await this.store.getBranch(this.branchId)
179
- let parentSummary = ''
180
- if (parentBranch?.headCommitId) {
181
- const head = await this.store.getCommit(parentBranch.headCommitId)
182
- parentSummary = head?.summary ?? ''
183
- }
184
-
185
- const newBranch = await this.store.createBranch({
186
- projectId: this.projectId,
187
- name: name ?? gitBranch,
188
- gitBranch,
189
- parentBranchId: this.branchId,
190
- })
191
-
192
- // branch-init commit: carries parent summary, no rolling summarization needed
193
- await this.store.createCommit({
194
- branchId: newBranch.id,
195
- agentId: this.agentId,
196
- agentRole: this.agentRole,
197
- tool: this.tool,
198
- workflowType: this.workflowType,
199
- message: `Branch ${gitBranch} created from ${parentBranch?.gitBranch ?? this.branchId}`,
200
- content: parentSummary,
201
- summary: parentSummary,
202
- commitType: 'branch-init',
203
- })
204
-
205
- return newBranch
206
- }
207
-
208
- /**
209
- * Merge a source branch into the current branch.
210
- * Generates a rolling summary for the merge commit, carries open threads
211
- * from source to target, and marks the source branch as merged.
212
- */
213
- async merge(sourceBranchId: string): Promise<Commit> {
214
- this.assertInitialized()
215
-
216
- // Source HEAD summary
217
- const sourceBranch = await this.store.getBranch(sourceBranchId)
218
- let sourceSummary = ''
219
- if (sourceBranch?.headCommitId) {
220
- const head = await this.store.getCommit(sourceBranch.headCommitId)
221
- sourceSummary = head?.summary ?? ''
222
- }
223
-
224
- // Target (current branch) HEAD summary as the rolling base
225
- const targetBranch = await this.store.getBranch(this.branchId)
226
- let targetSummary = ''
227
- if (targetBranch?.headCommitId) {
228
- const head = await this.store.getCommit(targetBranch.headCommitId)
229
- targetSummary = head?.summary ?? ''
230
- }
231
-
232
- const mergeContent = `Merged ${sourceBranch?.name ?? sourceBranchId}: ${sourceSummary}`
233
- const mergeSummary = await this.summarizer.summarize(mergeContent, targetSummary, 'branch')
234
-
235
- return this.store.mergeBranch(sourceBranchId, this.branchId, mergeSummary)
236
- }
237
-
238
- /**
239
- * Retrieve a SessionSnapshot for the current project+branch.
240
- *
241
- * scope='global' → full project snapshot (project summary + branch state)
242
- * scope='branch' → same as global for now (branch-scoped view, Week 2)
243
- * Other scopes → throws until implemented in later weeks
244
- */
245
- async context(scope: ContextScope): Promise<SessionSnapshot> {
246
- this.assertInitialized()
247
-
248
- if (scope === 'global' || scope === 'branch') {
249
- return this.store.getSessionSnapshot(this.projectId, this.branchId)
250
- }
251
-
252
- throw new Error(`context scope '${scope}' is not yet implemented`)
253
- }
254
-
255
- /**
256
- * Semantic search over commits in the current project.
257
- * Requires an EmbeddingService to have been passed at construction time;
258
- * returns an empty array if embeddings are unavailable.
259
- */
260
- async semanticSearch(query: string, projectId: string, limit = 5): Promise<SearchResult[]> {
261
- if (!this.embeddings) return []
262
- const vector = await this.embeddings.embed(query)
263
- if (!vector) return []
264
- return this.store.semanticSearch(vector, projectId, limit)
265
- }
266
-
267
- private assertInitialized(): void {
268
- if (!this.projectId || !this.branchId) {
269
- throw new Error(
270
- 'ContextEngine not initialized — call engine.init(projectId, branchId) first.',
271
- )
272
- }
273
- }
274
- }
@@ -1,6 +0,0 @@
1
- export * from './types.js'
2
- export * from './summarizer.js'
3
- export * from './snapshot.js'
4
- export * from './threads.js'
5
- export * from './engine.js'
6
- export * from './embeddings.js'
@@ -1,82 +0,0 @@
1
- // SnapshotFormatter — converts a SessionSnapshot to one of the 3 output formats.
2
- // Moved from store/local/index.ts (inline) to core so all consumers share the same output.
3
-
4
- import type { SessionSnapshot, SnapshotFormat } from './types.js'
5
-
6
- export class SnapshotFormatter {
7
- format(snapshot: SessionSnapshot, fmt: SnapshotFormat): string {
8
- const { projectSummary, branchName, branchSummary, recentCommits, openThreads, activeClaims } = snapshot
9
-
10
- if (fmt === 'json') {
11
- return JSON.stringify(snapshot, null, 2)
12
- }
13
-
14
- if (fmt === 'agents-md') {
15
- const commits = recentCommits
16
- .map(
17
- (c) =>
18
- `- [${c.createdAt.toISOString()}] "${c.message}" by ${c.agentRole} via ${c.tool} (${c.workflowType})`,
19
- )
20
- .join('\n')
21
- const threads = openThreads
22
- .map(
23
- (t) =>
24
- `- [ ] ${t.description} (opened ${t.createdAt.toLocaleDateString()}, ${t.workflowType ?? 'interactive'})`,
25
- )
26
- .join('\n')
27
- const claims = activeClaims
28
- .map((cl) => `- [${cl.status}] ${cl.task} by ${cl.agentId} (${cl.role})`)
29
- .join('\n')
30
- return [
31
- `## Project State`,
32
- projectSummary || '(no summary yet)',
33
- ``,
34
- `## Current Branch: ${branchName}`,
35
- branchSummary || '(no branch summary yet)',
36
- ``,
37
- `## Recent Activity`,
38
- commits || '(no commits yet)',
39
- ``,
40
- `## Open Threads`,
41
- threads || '(none)',
42
- ``,
43
- `## Active Claims`,
44
- claims || '(none)',
45
- ].join('\n')
46
- }
47
-
48
- // text (default)
49
- const commits = recentCommits
50
- .map(
51
- (c) =>
52
- `[${c.createdAt.toISOString()}] "${c.message}" by ${c.agentRole} via ${c.tool} (${c.workflowType})`,
53
- )
54
- .join('\n')
55
- const threads = openThreads
56
- .map(
57
- (t) =>
58
- `[ ] ${t.description} (opened ${t.createdAt.toLocaleDateString()}, ${t.workflowType ?? 'interactive'})`,
59
- )
60
- .join('\n')
61
- const claims = activeClaims
62
- .map((cl) => `[${cl.status}] ${cl.task} by ${cl.agentId} (${cl.role})`)
63
- .join('\n')
64
-
65
- return [
66
- `=== PROJECT STATE ===`,
67
- projectSummary || '(no summary yet)',
68
- ``,
69
- `=== CURRENT BRANCH: ${branchName} ===`,
70
- branchSummary || '(no branch summary yet)',
71
- ``,
72
- `=== LAST 3 COMMITS ===`,
73
- commits || '(none)',
74
- ``,
75
- `=== OPEN THREADS ===`,
76
- threads || '(none)',
77
- ``,
78
- `=== ACTIVE CLAIMS ===`,
79
- claims || '(none)',
80
- ].join('\n')
81
- }
82
- }
@@ -1,120 +0,0 @@
1
- // Week 2 summarizer tests.
2
- // All API calls are mocked — no network required.
3
-
4
- import { describe, it, expect, vi, beforeEach } from 'vitest'
5
- import Anthropic from '@anthropic-ai/sdk'
6
- import { RollingSummarizer } from './summarizer.js'
7
-
8
- // ─── Helpers ─────────────────────────────────────────────────────────────────
9
-
10
- function makeClient(responseText: string): Anthropic {
11
- const client = new Anthropic({ apiKey: 'test-key' })
12
- vi.spyOn(client.messages, 'create').mockResolvedValue({
13
- id: 'msg_test',
14
- type: 'message',
15
- role: 'assistant',
16
- content: [{ type: 'text', text: responseText }],
17
- model: 'claude-haiku-4-5',
18
- stop_reason: 'end_turn',
19
- stop_sequence: null,
20
- usage: { input_tokens: 10, output_tokens: 20 },
21
- } as Awaited<ReturnType<typeof client.messages.create>>)
22
- return client
23
- }
24
-
25
- function makeFailingClient(): Anthropic {
26
- const client = new Anthropic({ apiKey: 'test-key' })
27
- vi.spyOn(client.messages, 'create').mockRejectedValue(
28
- new Error('Simulated API failure'),
29
- )
30
- return client
31
- }
32
-
33
- // ─── Tests ───────────────────────────────────────────────────────────────────
34
-
35
- describe('RollingSummarizer', () => {
36
- beforeEach(() => {
37
- vi.restoreAllMocks()
38
- })
39
-
40
- it('returns Claude summary when API succeeds', async () => {
41
- const claudeReply = 'Concise summary from Claude Haiku.'
42
- const summarizer = new RollingSummarizer({ client: makeClient(claudeReply) })
43
-
44
- const result = await summarizer.summarize('some content', '', 'branch')
45
-
46
- expect(result).toBe(claudeReply)
47
- })
48
-
49
- it('passes previous summary and new content to Claude', async () => {
50
- const client = makeClient('merged summary')
51
- const summarizer = new RollingSummarizer({ client })
52
-
53
- await summarizer.summarize('new work', 'old summary', 'branch')
54
-
55
- const callArg = (client.messages.create as ReturnType<typeof vi.fn>).mock
56
- .calls[0][0] as Anthropic.MessageCreateParamsNonStreaming
57
- const userContent = callArg.messages[0].content as string
58
- expect(userContent).toContain('old summary')
59
- expect(userContent).toContain('new work')
60
- })
61
-
62
- it('falls back to string truncation when API fails', async () => {
63
- const summarizer = new RollingSummarizer({ client: makeFailingClient() })
64
-
65
- const result = await summarizer.summarize('beta', 'alpha', 'branch')
66
-
67
- // Fallback: concatenate and keep; both fit in 2000 chars
68
- expect(result).toContain('alpha')
69
- expect(result).toContain('beta')
70
- })
71
-
72
- it('never throws when API fails', async () => {
73
- const summarizer = new RollingSummarizer({ client: makeFailingClient() })
74
-
75
- await expect(
76
- summarizer.summarize('content', '', 'branch'),
77
- ).resolves.toBeDefined()
78
- })
79
-
80
- it('falls back to string truncation when no client is injected and ANTHROPIC_API_KEY is absent', async () => {
81
- const savedKey = process.env['ANTHROPIC_API_KEY']
82
- delete process.env['ANTHROPIC_API_KEY']
83
-
84
- try {
85
- const summarizer = new RollingSummarizer()
86
- const result = await summarizer.summarize('beta content', 'alpha content', 'branch')
87
- expect(result).toContain('alpha content')
88
- expect(result).toContain('beta content')
89
- } finally {
90
- if (savedKey !== undefined) process.env['ANTHROPIC_API_KEY'] = savedKey
91
- }
92
- })
93
-
94
- it('truncates from the start (keeps newest) when fallback overflows budget', async () => {
95
- const savedKey = process.env['ANTHROPIC_API_KEY']
96
- delete process.env['ANTHROPIC_API_KEY']
97
-
98
- try {
99
- const summarizer = new RollingSummarizer({ maxBranchChars: 20 })
100
- const result = await summarizer.summarize('NEWEST', 'A'.repeat(30), 'branch')
101
- // Budget is 20 chars; tail of the combined string should end with NEWEST
102
- expect(result).toHaveLength(20)
103
- expect(result.endsWith('NEWEST')).toBe(true)
104
- } finally {
105
- if (savedKey !== undefined) process.env['ANTHROPIC_API_KEY'] = savedKey
106
- }
107
- })
108
-
109
- it('respects maxChars for Claude response (truncates to budget)', async () => {
110
- const longReply = 'X'.repeat(5000)
111
- const summarizer = new RollingSummarizer({
112
- client: makeClient(longReply),
113
- maxBranchChars: 100,
114
- })
115
-
116
- const result = await summarizer.summarize('content', '', 'branch')
117
-
118
- expect(result).toHaveLength(100)
119
- })
120
- })