@jamesaphoenix/tx-core 0.4.3 → 0.4.5

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 (94) hide show
  1. package/dist/db.d.ts.map +1 -1
  2. package/dist/db.js +8 -2
  3. package/dist/db.js.map +1 -1
  4. package/dist/layer.d.ts +3 -3
  5. package/dist/services/auto-sync-service.d.ts +1 -1
  6. package/dist/services/cycle-scan-service.d.ts.map +1 -1
  7. package/dist/services/cycle-scan-service.js +6 -2
  8. package/dist/services/cycle-scan-service.js.map +1 -1
  9. package/dist/services/learning-service.d.ts +1 -1
  10. package/dist/services/orchestrator-service.d.ts +1 -1
  11. package/dist/services/orchestrator-service.d.ts.map +1 -1
  12. package/dist/services/orchestrator-service.js +14 -13
  13. package/dist/services/orchestrator-service.js.map +1 -1
  14. package/dist/services/retriever-service.d.ts +2 -2
  15. package/dist/services/sync-service.d.ts.map +1 -1
  16. package/dist/services/sync-service.js +9 -8
  17. package/dist/services/sync-service.js.map +1 -1
  18. package/dist/services/task-service.d.ts.map +1 -1
  19. package/dist/services/task-service.js +3 -2
  20. package/dist/services/task-service.js.map +1 -1
  21. package/dist/services/worker-process.d.ts +1 -1
  22. package/dist/services/worker-process.d.ts.map +1 -1
  23. package/dist/services/worker-process.js +9 -2
  24. package/dist/services/worker-process.js.map +1 -1
  25. package/dist/worker/run-worker.d.ts +1 -1
  26. package/package.json +1 -1
  27. package/dist/mappers/anchor.d.ts +0 -28
  28. package/dist/mappers/anchor.d.ts.map +0 -1
  29. package/dist/mappers/anchor.js +0 -105
  30. package/dist/mappers/anchor.js.map +0 -1
  31. package/dist/mappers/candidate.d.ts +0 -25
  32. package/dist/mappers/candidate.d.ts.map +0 -1
  33. package/dist/mappers/candidate.js +0 -83
  34. package/dist/mappers/candidate.js.map +0 -1
  35. package/dist/mappers/edge.d.ts +0 -19
  36. package/dist/mappers/edge.d.ts.map +0 -1
  37. package/dist/mappers/edge.js +0 -81
  38. package/dist/mappers/edge.js.map +0 -1
  39. package/dist/repo/anchor-repo.d.ts +0 -52
  40. package/dist/repo/anchor-repo.d.ts.map +0 -1
  41. package/dist/repo/anchor-repo.js +0 -245
  42. package/dist/repo/anchor-repo.js.map +0 -1
  43. package/dist/repo/candidate-repo.d.ts +0 -16
  44. package/dist/repo/candidate-repo.d.ts.map +0 -1
  45. package/dist/repo/candidate-repo.js +0 -164
  46. package/dist/repo/candidate-repo.js.map +0 -1
  47. package/dist/repo/compaction-repo.d.ts +0 -41
  48. package/dist/repo/compaction-repo.d.ts.map +0 -1
  49. package/dist/repo/compaction-repo.js +0 -84
  50. package/dist/repo/compaction-repo.js.map +0 -1
  51. package/dist/repo/edge-repo.d.ts +0 -26
  52. package/dist/repo/edge-repo.d.ts.map +0 -1
  53. package/dist/repo/edge-repo.js +0 -258
  54. package/dist/repo/edge-repo.js.map +0 -1
  55. package/dist/services/anchor-service.d.ts +0 -147
  56. package/dist/services/anchor-service.d.ts.map +0 -1
  57. package/dist/services/anchor-service.js +0 -540
  58. package/dist/services/anchor-service.js.map +0 -1
  59. package/dist/services/anchor-verification.d.ts +0 -102
  60. package/dist/services/anchor-verification.d.ts.map +0 -1
  61. package/dist/services/anchor-verification.js +0 -817
  62. package/dist/services/anchor-verification.js.map +0 -1
  63. package/dist/services/ast-grep-service.d.ts +0 -58
  64. package/dist/services/ast-grep-service.d.ts.map +0 -1
  65. package/dist/services/ast-grep-service.js +0 -427
  66. package/dist/services/ast-grep-service.js.map +0 -1
  67. package/dist/services/candidate-extractor-service.d.ts +0 -56
  68. package/dist/services/candidate-extractor-service.d.ts.map +0 -1
  69. package/dist/services/candidate-extractor-service.js +0 -365
  70. package/dist/services/candidate-extractor-service.js.map +0 -1
  71. package/dist/services/compaction-service.d.ts +0 -105
  72. package/dist/services/compaction-service.d.ts.map +0 -1
  73. package/dist/services/compaction-service.js +0 -369
  74. package/dist/services/compaction-service.js.map +0 -1
  75. package/dist/services/edge-service.d.ts +0 -78
  76. package/dist/services/edge-service.d.ts.map +0 -1
  77. package/dist/services/edge-service.js +0 -158
  78. package/dist/services/edge-service.js.map +0 -1
  79. package/dist/services/feedback-tracker.d.ts +0 -64
  80. package/dist/services/feedback-tracker.d.ts.map +0 -1
  81. package/dist/services/feedback-tracker.js +0 -110
  82. package/dist/services/feedback-tracker.js.map +0 -1
  83. package/dist/services/graph-expansion.d.ts +0 -158
  84. package/dist/services/graph-expansion.d.ts.map +0 -1
  85. package/dist/services/graph-expansion.js +0 -487
  86. package/dist/services/graph-expansion.js.map +0 -1
  87. package/dist/services/promotion-service.d.ts +0 -67
  88. package/dist/services/promotion-service.d.ts.map +0 -1
  89. package/dist/services/promotion-service.js +0 -151
  90. package/dist/services/promotion-service.js.map +0 -1
  91. package/dist/services/swarm-verification.d.ts +0 -104
  92. package/dist/services/swarm-verification.d.ts.map +0 -1
  93. package/dist/services/swarm-verification.js +0 -406
  94. package/dist/services/swarm-verification.js.map +0 -1
@@ -1,151 +0,0 @@
1
- import { Context, Effect, Layer } from "effect";
2
- import { CandidateNotFoundError, DatabaseError, ValidationError } from "../errors.js";
3
- import { CandidateRepository } from "../repo/candidate-repo.js";
4
- import { LearningService } from "./learning-service.js";
5
- import { EdgeService } from "./edge-service.js";
6
- /**
7
- * PromotionService manages the lifecycle of learning candidates,
8
- * including listing, promoting to learnings, rejecting, and auto-promotion.
9
- *
10
- * @see PRD-015 for the knowledge promotion pipeline
11
- */
12
- export class PromotionService extends Context.Tag("PromotionService")() {
13
- }
14
- /** Duplicate detection threshold for auto-promotion (0.85 = high similarity) */
15
- const DUPLICATE_MIN_SCORE = 0.85;
16
- export const PromotionServiceLive = Layer.effect(PromotionService, Effect.gen(function* () {
17
- const candidateRepo = yield* CandidateRepository;
18
- const learningService = yield* LearningService;
19
- const edgeService = yield* EdgeService;
20
- /**
21
- * Promote a single candidate with optional reviewer identifier.
22
- * Internal helper shared by promote() and autoPromote().
23
- */
24
- const promoteCandidate = (candidate, reviewedBy) => Effect.gen(function* () {
25
- // Create learning from candidate content
26
- // Map ValidationError to DatabaseError (validation should not fail for existing candidates)
27
- const learning = yield* Effect.mapError(learningService.create({
28
- content: candidate.content,
29
- sourceType: "run",
30
- sourceRef: candidate.sourceRunId ?? candidate.sourceFile,
31
- category: candidate.category
32
- }), (error) => error._tag === "ValidationError"
33
- ? new DatabaseError({ cause: error })
34
- : error);
35
- // Create DERIVED_FROM edge for provenance tracking
36
- // Link the learning back to its source (run or task)
37
- if (candidate.sourceRunId) {
38
- yield* Effect.catchAll(edgeService.createEdge({
39
- edgeType: "DERIVED_FROM",
40
- sourceType: "learning",
41
- sourceId: String(learning.id),
42
- targetType: "run",
43
- targetId: candidate.sourceRunId,
44
- weight: 1.0
45
- }), () => Effect.void // Ignore edge creation failures
46
- );
47
- }
48
- else if (candidate.sourceTaskId) {
49
- yield* Effect.catchAll(edgeService.createEdge({
50
- edgeType: "DERIVED_FROM",
51
- sourceType: "learning",
52
- sourceId: String(learning.id),
53
- targetType: "task",
54
- targetId: candidate.sourceTaskId,
55
- weight: 1.0
56
- }), () => Effect.void // Ignore edge creation failures
57
- );
58
- }
59
- // Update candidate status to promoted
60
- const now = new Date();
61
- const updatedCandidate = yield* candidateRepo.update(candidate.id, {
62
- status: "promoted",
63
- reviewedAt: now,
64
- reviewedBy,
65
- promotedLearningId: learning.id
66
- });
67
- if (!updatedCandidate) {
68
- return yield* Effect.fail(new CandidateNotFoundError({ id: candidate.id }));
69
- }
70
- return { candidate: updatedCandidate, learning };
71
- });
72
- return {
73
- list: (filter) => candidateRepo.findByFilter(filter),
74
- promote: (id) => Effect.gen(function* () {
75
- const candidate = yield* candidateRepo.findById(id);
76
- if (!candidate) {
77
- return yield* Effect.fail(new CandidateNotFoundError({ id }));
78
- }
79
- return yield* promoteCandidate(candidate, "manual");
80
- }),
81
- reject: (id, reason) => Effect.gen(function* () {
82
- // Validate reason is provided
83
- if (!reason || reason.trim().length === 0) {
84
- return yield* Effect.fail(new ValidationError({ reason: "Rejection reason is required" }));
85
- }
86
- const candidate = yield* candidateRepo.findById(id);
87
- if (!candidate) {
88
- return yield* Effect.fail(new CandidateNotFoundError({ id }));
89
- }
90
- const now = new Date();
91
- const updatedCandidate = yield* candidateRepo.update(id, {
92
- status: "rejected",
93
- reviewedAt: now,
94
- reviewedBy: "manual",
95
- rejectionReason: reason.trim()
96
- });
97
- if (!updatedCandidate) {
98
- return yield* Effect.fail(new CandidateNotFoundError({ id }));
99
- }
100
- return updatedCandidate;
101
- }),
102
- autoPromote: () => Effect.gen(function* () {
103
- // Get all pending high-confidence candidates
104
- const candidates = yield* candidateRepo.findByFilter({
105
- status: "pending",
106
- confidence: "high"
107
- });
108
- let promoted = 0;
109
- let skipped = 0;
110
- let failed = 0;
111
- const learningIds = [];
112
- for (const candidate of candidates) {
113
- // Check for duplicates using semantic search
114
- const searchResult = yield* Effect.catchAll(learningService.search({
115
- query: candidate.content,
116
- limit: 1,
117
- minScore: DUPLICATE_MIN_SCORE
118
- }), () => Effect.succeed([]));
119
- // If a highly similar learning exists, skip this candidate
120
- if (searchResult.length > 0) {
121
- skipped++;
122
- // Mark as merged with the existing learning
123
- yield* Effect.catchAll(candidateRepo.update(candidate.id, {
124
- status: "merged",
125
- reviewedAt: new Date(),
126
- reviewedBy: "auto",
127
- promotedLearningId: searchResult[0].id
128
- }), () => Effect.void);
129
- continue;
130
- }
131
- // Promote the candidate
132
- const result = yield* Effect.either(promoteCandidate(candidate, "auto"));
133
- if (result._tag === "Right") {
134
- promoted++;
135
- learningIds.push(result.right.learning.id);
136
- }
137
- else {
138
- failed++;
139
- }
140
- }
141
- return {
142
- promoted,
143
- skipped,
144
- failed,
145
- learningIds
146
- };
147
- }),
148
- getPending: () => candidateRepo.findByFilter({ status: "pending" })
149
- };
150
- }));
151
- //# sourceMappingURL=promotion-service.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"promotion-service.js","sourceRoot":"","sources":["../../src/services/promotion-service.ts"],"names":[],"mappings":"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"}
@@ -1,104 +0,0 @@
1
- /**
2
- * SwarmVerificationService - Bulk invalidation with concurrent verification agents
3
- *
4
- * For large batches of anchors, spawns up to 4 concurrent verification agents.
5
- * Coordinates via job queue pattern using Effect's concurrency primitives.
6
- * Aggregates results with majority vote for edge cases.
7
- * Tracks swarm metrics for observability.
8
- *
9
- * @see docs/prd/PRD-017-invalidation-maintenance.md - IM-004: Bulk invalidation via agent swarm
10
- */
11
- import { Context, Effect, Layer } from "effect";
12
- import { AnchorVerificationService, type VerificationResult, type VerifyOptions } from "./anchor-verification.js";
13
- import { AnchorRepository } from "../repo/anchor-repo.js";
14
- import { DatabaseError, ValidationError } from "../errors.js";
15
- import type { AnchorStatus } from "@jamesaphoenix/tx-types";
16
- /** A batch of anchor IDs to verify */
17
- export interface VerificationBatch {
18
- readonly batchId: number;
19
- readonly anchorIds: readonly number[];
20
- }
21
- /** Result of a single agent's verification of a batch */
22
- export interface BatchResult {
23
- readonly batchId: number;
24
- readonly results: readonly VerificationResult[];
25
- readonly duration: number;
26
- readonly errors: number;
27
- }
28
- /** Swarm verification metrics */
29
- export interface SwarmMetrics {
30
- readonly totalAnchors: number;
31
- readonly totalBatches: number;
32
- readonly agentsUsed: number;
33
- readonly duration: number;
34
- /** Time spent per agent (for load balancing analysis) */
35
- readonly agentDurations: readonly number[];
36
- /** Counts by action type */
37
- readonly unchanged: number;
38
- readonly selfHealed: number;
39
- readonly drifted: number;
40
- readonly invalid: number;
41
- readonly errors: number;
42
- /** Edge cases requiring human review (tie votes) */
43
- readonly needsReview: number;
44
- }
45
- /** Complete swarm verification result */
46
- export interface SwarmVerificationResult {
47
- readonly metrics: SwarmMetrics;
48
- readonly results: readonly VerificationResult[];
49
- /** Anchor IDs that require human review due to tie votes */
50
- readonly reviewRequired: readonly number[];
51
- }
52
- /** Options for swarm verification */
53
- export interface SwarmVerifyOptions extends VerifyOptions {
54
- /** Batch size per agent (default: 10) */
55
- readonly batchSize?: number;
56
- /** Max concurrent agents (default: 4) */
57
- readonly maxConcurrent?: number;
58
- /** Force swarm even for small batches */
59
- readonly forceSwarm?: boolean;
60
- }
61
- /** Majority vote result for an anchor */
62
- export interface VoteResult {
63
- readonly anchorId: number;
64
- readonly votes: Map<AnchorStatus | "error", number>;
65
- readonly consensus: AnchorStatus | null;
66
- readonly needsReview: boolean;
67
- }
68
- declare const SwarmVerificationService_base: Context.TagClass<SwarmVerificationService, "SwarmVerificationService", {
69
- /**
70
- * Verify anchors using concurrent agents.
71
- * Automatically partitions into batches and spawns agents.
72
- */
73
- readonly verifyAnchors: (anchorIds: readonly number[], options?: SwarmVerifyOptions) => Effect.Effect<SwarmVerificationResult, DatabaseError>;
74
- /**
75
- * Verify all valid anchors using swarm.
76
- */
77
- readonly verifyAll: (options?: SwarmVerifyOptions) => Effect.Effect<SwarmVerificationResult, DatabaseError>;
78
- /**
79
- * Verify anchors for files matching glob pattern using swarm.
80
- */
81
- readonly verifyGlob: (globPattern: string, options?: SwarmVerifyOptions) => Effect.Effect<SwarmVerificationResult, DatabaseError>;
82
- /**
83
- * Verify anchors affected by a list of changed files.
84
- * Typically called from git hooks after large commits.
85
- */
86
- readonly verifyChangedFiles: (filePaths: readonly string[], options?: SwarmVerifyOptions) => Effect.Effect<SwarmVerificationResult, DatabaseError>;
87
- }>;
88
- export declare class SwarmVerificationService extends SwarmVerificationService_base {
89
- }
90
- /**
91
- * Calculate majority vote for conflicting results on the same anchor.
92
- * Used when multiple agents verify the same anchor (edge cases).
93
- *
94
- * Rules (per PRD-017):
95
- * - If 3/4 agents say valid, it's valid
96
- * - Tie = mark for human review
97
- *
98
- * Exported for use in LLM-assisted verification scenarios where multiple
99
- * agents may verify the same anchor for edge cases.
100
- */
101
- export declare const calculateMajorityVote: (results: readonly VerificationResult[]) => Effect.Effect<VoteResult, ValidationError>;
102
- export declare const SwarmVerificationServiceLive: Layer.Layer<SwarmVerificationService, never, AnchorRepository | AnchorVerificationService>;
103
- export {};
104
- //# sourceMappingURL=swarm-verification.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"swarm-verification.d.ts","sourceRoot":"","sources":["../../src/services/swarm-verification.ts"],"names":[],"mappings":"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"}
@@ -1,406 +0,0 @@
1
- /**
2
- * SwarmVerificationService - Bulk invalidation with concurrent verification agents
3
- *
4
- * For large batches of anchors, spawns up to 4 concurrent verification agents.
5
- * Coordinates via job queue pattern using Effect's concurrency primitives.
6
- * Aggregates results with majority vote for edge cases.
7
- * Tracks swarm metrics for observability.
8
- *
9
- * @see docs/prd/PRD-017-invalidation-maintenance.md - IM-004: Bulk invalidation via agent swarm
10
- */
11
- import { Context, Effect, Layer, Option, Queue, Fiber, Ref } from "effect";
12
- import { AnchorVerificationService } from "./anchor-verification.js";
13
- import { AnchorRepository } from "../repo/anchor-repo.js";
14
- import { ValidationError } from "../errors.js";
15
- import { matchesGlob } from "../utils/glob.js";
16
- // =============================================================================
17
- // Configuration Constants
18
- // =============================================================================
19
- /** Default batch size per agent (PRD-017: batches of 10) */
20
- const DEFAULT_BATCH_SIZE = 10;
21
- /** Maximum concurrent agents (PRD-017: up to 4) */
22
- const MAX_CONCURRENT_AGENTS = 4;
23
- /** Minimum batch size to trigger swarm (sequential is fine for small batches) */
24
- const SWARM_THRESHOLD = 20;
25
- // =============================================================================
26
- // Service Definition
27
- // =============================================================================
28
- export class SwarmVerificationService extends Context.Tag("SwarmVerificationService")() {
29
- }
30
- // =============================================================================
31
- // Utility Functions
32
- // =============================================================================
33
- /**
34
- * Partition anchor IDs into batches.
35
- */
36
- const partitionIntoBatches = (anchorIds, batchSize) => {
37
- const batches = [];
38
- for (let i = 0; i < anchorIds.length; i += batchSize) {
39
- batches.push({
40
- batchId: batches.length,
41
- anchorIds: anchorIds.slice(i, i + batchSize)
42
- });
43
- }
44
- return batches;
45
- };
46
- /**
47
- * Calculate majority vote for conflicting results on the same anchor.
48
- * Used when multiple agents verify the same anchor (edge cases).
49
- *
50
- * Rules (per PRD-017):
51
- * - If 3/4 agents say valid, it's valid
52
- * - Tie = mark for human review
53
- *
54
- * Exported for use in LLM-assisted verification scenarios where multiple
55
- * agents may verify the same anchor for edge cases.
56
- */
57
- export const calculateMajorityVote = (results) => Effect.gen(function* () {
58
- if (results.length === 0) {
59
- return yield* Effect.fail(new ValidationError({
60
- reason: "Cannot calculate majority vote with empty results"
61
- }));
62
- }
63
- const anchorId = results[0].anchorId;
64
- const votes = new Map();
65
- for (const result of results) {
66
- const status = result.newStatus;
67
- votes.set(status, (votes.get(status) ?? 0) + 1);
68
- }
69
- // Find the status with most votes
70
- let maxVotes = 0;
71
- let consensus = null;
72
- let tieCount = 0;
73
- for (const [status, count] of votes) {
74
- if (status === "error")
75
- continue; // Don't count errors in consensus
76
- if (count > maxVotes) {
77
- maxVotes = count;
78
- consensus = status;
79
- tieCount = 1;
80
- }
81
- else if (count === maxVotes) {
82
- tieCount++;
83
- }
84
- }
85
- // Tie = needs human review
86
- const needsReview = tieCount > 1;
87
- return {
88
- anchorId,
89
- votes,
90
- consensus: needsReview ? null : consensus,
91
- needsReview
92
- };
93
- });
94
- /**
95
- * Aggregate batch results into final metrics.
96
- */
97
- const aggregateResults = (batchResults, startTime, agentCount) => {
98
- let unchanged = 0;
99
- let selfHealed = 0;
100
- let drifted = 0;
101
- let invalid = 0;
102
- let errors = 0;
103
- const allResults = [];
104
- const agentDurations = [];
105
- for (const batch of batchResults) {
106
- agentDurations.push(batch.duration);
107
- errors += batch.errors;
108
- for (const result of batch.results) {
109
- allResults.push(result);
110
- switch (result.action) {
111
- case "unchanged":
112
- unchanged++;
113
- break;
114
- case "self_healed":
115
- selfHealed++;
116
- break;
117
- case "drifted":
118
- drifted++;
119
- break;
120
- case "invalidated":
121
- invalid++;
122
- break;
123
- }
124
- }
125
- }
126
- return {
127
- metrics: {
128
- totalAnchors: allResults.length + errors,
129
- totalBatches: batchResults.length,
130
- agentsUsed: agentCount,
131
- duration: Date.now() - startTime,
132
- agentDurations,
133
- unchanged,
134
- selfHealed,
135
- drifted,
136
- invalid,
137
- errors
138
- },
139
- results: allResults
140
- };
141
- };
142
- // =============================================================================
143
- // Service Implementation
144
- // =============================================================================
145
- export const SwarmVerificationServiceLive = Layer.effect(SwarmVerificationService, Effect.gen(function* () {
146
- const anchorVerification = yield* AnchorVerificationService;
147
- const anchorRepo = yield* AnchorRepository;
148
- /**
149
- * Process a single batch of anchors using the verification service.
150
- */
151
- const processB = (batch, options) => Effect.gen(function* () {
152
- const startTime = Date.now();
153
- const results = [];
154
- let errors = 0;
155
- // Process each anchor in the batch
156
- for (const anchorId of batch.anchorIds) {
157
- const result = yield* anchorVerification.verify(anchorId, options).pipe(Effect.catchAll(() => {
158
- errors++;
159
- return Effect.succeed(null);
160
- }));
161
- if (result) {
162
- results.push(result);
163
- }
164
- }
165
- return {
166
- batchId: batch.batchId,
167
- results,
168
- duration: Date.now() - startTime,
169
- errors
170
- };
171
- });
172
- /**
173
- * Run swarm verification with concurrent agents.
174
- */
175
- const runSwarm = (anchorIds, options) => Effect.gen(function* () {
176
- const startTime = Date.now();
177
- const batchSize = options.batchSize ?? DEFAULT_BATCH_SIZE;
178
- const maxConcurrent = options.maxConcurrent ?? MAX_CONCURRENT_AGENTS;
179
- // Partition into batches
180
- const batches = partitionIntoBatches(anchorIds, batchSize);
181
- if (batches.length === 0) {
182
- return {
183
- metrics: {
184
- totalAnchors: 0,
185
- totalBatches: 0,
186
- agentsUsed: 0,
187
- duration: 0,
188
- agentDurations: [],
189
- unchanged: 0,
190
- selfHealed: 0,
191
- drifted: 0,
192
- invalid: 0,
193
- errors: 0,
194
- needsReview: 0
195
- },
196
- results: [],
197
- reviewRequired: []
198
- };
199
- }
200
- // Determine actual concurrency (don't spawn more agents than batches)
201
- const agentCount = Math.min(batches.length, maxConcurrent);
202
- // Create a queue for job distribution
203
- const queue = yield* Queue.bounded(batches.length);
204
- // Enqueue all batches
205
- for (const batch of batches) {
206
- yield* Queue.offer(queue, batch);
207
- }
208
- // Counter to track active work
209
- const completedBatches = yield* Ref.make([]);
210
- // Worker function: continuously pull batches from queue
211
- const worker = (_workerId) => Effect.gen(function* () {
212
- while (true) {
213
- // Try to take a batch (non-blocking)
214
- const maybeBatch = yield* Queue.poll(queue);
215
- if (Option.isNone(maybeBatch)) {
216
- // No more batches, worker done
217
- break;
218
- }
219
- const batch = maybeBatch.value;
220
- const result = yield* processB(batch, options);
221
- // Record result
222
- yield* Ref.update(completedBatches, (results) => [...results, result]);
223
- }
224
- });
225
- // Spawn agents as fibers
226
- const fibers = [];
227
- for (let i = 0; i < agentCount; i++) {
228
- const fiber = yield* Effect.fork(worker(i));
229
- fibers.push(fiber);
230
- }
231
- // Wait for all agents to complete
232
- for (const fiber of fibers) {
233
- yield* Fiber.join(fiber);
234
- }
235
- // Shutdown queue
236
- yield* Queue.shutdown(queue);
237
- // Get all batch results
238
- const batchResults = yield* Ref.get(completedBatches);
239
- // Aggregate results
240
- const { metrics, results } = aggregateResults(batchResults, startTime, agentCount);
241
- // For now, we don't have multi-agent verification of the same anchor,
242
- // so no majority voting needed. reviewRequired is empty.
243
- // This would be used if we added LLM-assisted verification where
244
- // multiple agents verify the same anchor for edge cases.
245
- const reviewRequired = [];
246
- return {
247
- metrics: {
248
- ...metrics,
249
- needsReview: reviewRequired.length
250
- },
251
- results,
252
- reviewRequired
253
- };
254
- });
255
- /**
256
- * Simple sequential verification for small batches.
257
- */
258
- const runSequential = (anchorIds, options) => Effect.gen(function* () {
259
- // Handle empty input
260
- if (anchorIds.length === 0) {
261
- return {
262
- metrics: {
263
- totalAnchors: 0,
264
- totalBatches: 0,
265
- agentsUsed: 0,
266
- duration: 0,
267
- agentDurations: [],
268
- unchanged: 0,
269
- selfHealed: 0,
270
- drifted: 0,
271
- invalid: 0,
272
- errors: 0,
273
- needsReview: 0
274
- },
275
- results: [],
276
- reviewRequired: []
277
- };
278
- }
279
- const startTime = Date.now();
280
- const results = [];
281
- let errors = 0;
282
- let unchanged = 0;
283
- let selfHealed = 0;
284
- let drifted = 0;
285
- let invalid = 0;
286
- for (const anchorId of anchorIds) {
287
- const result = yield* anchorVerification.verify(anchorId, options).pipe(Effect.catchAll(() => {
288
- errors++;
289
- return Effect.succeed(null);
290
- }));
291
- if (result) {
292
- results.push(result);
293
- switch (result.action) {
294
- case "unchanged":
295
- unchanged++;
296
- break;
297
- case "self_healed":
298
- selfHealed++;
299
- break;
300
- case "drifted":
301
- drifted++;
302
- break;
303
- case "invalidated":
304
- invalid++;
305
- break;
306
- }
307
- }
308
- }
309
- return {
310
- metrics: {
311
- totalAnchors: results.length + errors,
312
- totalBatches: 1,
313
- agentsUsed: 1,
314
- duration: Date.now() - startTime,
315
- agentDurations: [Date.now() - startTime],
316
- unchanged,
317
- selfHealed,
318
- drifted,
319
- invalid,
320
- errors,
321
- needsReview: 0
322
- },
323
- results,
324
- reviewRequired: []
325
- };
326
- });
327
- return {
328
- verifyAnchors: (anchorIds, options = {}) => Effect.gen(function* () {
329
- // Use swarm for large batches, sequential for small ones
330
- const useSwarm = options.forceSwarm || anchorIds.length >= SWARM_THRESHOLD;
331
- if (useSwarm) {
332
- return yield* runSwarm(anchorIds, options);
333
- }
334
- else {
335
- return yield* runSequential(anchorIds, options);
336
- }
337
- }),
338
- verifyAll: (options = {}) => Effect.gen(function* () {
339
- const anchors = yield* anchorRepo.findAllValid(100_000);
340
- const anchorIds = anchors
341
- .filter((a) => !options.skipPinned || !a.pinned)
342
- .map((a) => a.id);
343
- const useSwarm = options.forceSwarm || anchorIds.length >= SWARM_THRESHOLD;
344
- if (useSwarm) {
345
- return yield* runSwarm(anchorIds, {
346
- ...options,
347
- detectedBy: options.detectedBy ?? "periodic"
348
- });
349
- }
350
- else {
351
- return yield* runSequential(anchorIds, {
352
- ...options,
353
- detectedBy: options.detectedBy ?? "periodic"
354
- });
355
- }
356
- }),
357
- verifyGlob: (globPattern, options = {}) => Effect.gen(function* () {
358
- const allAnchors = yield* anchorRepo.findAll(100_000);
359
- const matchingIds = allAnchors
360
- .filter((a) => matchesGlob(a.filePath, globPattern))
361
- .filter((a) => !options.skipPinned || !a.pinned)
362
- .map((a) => a.id);
363
- const useSwarm = options.forceSwarm || matchingIds.length >= SWARM_THRESHOLD;
364
- if (useSwarm) {
365
- return yield* runSwarm(matchingIds, {
366
- ...options,
367
- detectedBy: options.detectedBy ?? "manual"
368
- });
369
- }
370
- else {
371
- return yield* runSequential(matchingIds, {
372
- ...options,
373
- detectedBy: options.detectedBy ?? "manual"
374
- });
375
- }
376
- }),
377
- verifyChangedFiles: (filePaths, options = {}) => Effect.gen(function* () {
378
- // Find anchors for all changed files
379
- const anchorIdSet = new Set();
380
- for (const filePath of filePaths) {
381
- const anchors = yield* anchorRepo.findByFilePath(filePath);
382
- for (const anchor of anchors) {
383
- if (!options.skipPinned || !anchor.pinned) {
384
- anchorIdSet.add(anchor.id);
385
- }
386
- }
387
- }
388
- const anchorIds = Array.from(anchorIdSet);
389
- // Git hook context typically means large changes, use swarm
390
- const useSwarm = options.forceSwarm || anchorIds.length >= SWARM_THRESHOLD;
391
- if (useSwarm) {
392
- return yield* runSwarm(anchorIds, {
393
- ...options,
394
- detectedBy: options.detectedBy ?? "git_hook"
395
- });
396
- }
397
- else {
398
- return yield* runSequential(anchorIds, {
399
- ...options,
400
- detectedBy: options.detectedBy ?? "git_hook"
401
- });
402
- }
403
- })
404
- };
405
- }));
406
- //# sourceMappingURL=swarm-verification.js.map