@semiont/make-meaning 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,715 @@
1
+ // src/resource-context.ts
2
+ import { FilesystemViewStorage } from "@semiont/event-sourcing";
3
+ import { FilesystemRepresentationStore } from "@semiont/content";
4
+ import { getPrimaryRepresentation, decodeRepresentation } from "@semiont/api-client";
5
+ var ResourceContext = class {
6
+ /**
7
+ * Get resource metadata from view storage
8
+ */
9
+ static async getResourceMetadata(resourceId, config) {
10
+ const basePath = config.services.filesystem.path;
11
+ const projectRoot = config._metadata?.projectRoot;
12
+ const viewStorage = new FilesystemViewStorage(basePath, projectRoot);
13
+ const view = await viewStorage.get(resourceId);
14
+ if (!view) {
15
+ return null;
16
+ }
17
+ return view.resource;
18
+ }
19
+ /**
20
+ * List all resources by scanning view storage
21
+ */
22
+ static async listResources(filters, config) {
23
+ const basePath = config.services.filesystem.path;
24
+ const projectRoot = config._metadata?.projectRoot;
25
+ const viewStorage = new FilesystemViewStorage(basePath, projectRoot);
26
+ const allViews = await viewStorage.getAll();
27
+ const resources = [];
28
+ for (const view of allViews) {
29
+ const doc = view.resource;
30
+ if (filters?.archived !== void 0 && doc.archived !== filters.archived) {
31
+ continue;
32
+ }
33
+ if (filters?.search) {
34
+ const searchLower = filters.search.toLowerCase();
35
+ if (!doc.name.toLowerCase().includes(searchLower)) {
36
+ continue;
37
+ }
38
+ }
39
+ resources.push(doc);
40
+ }
41
+ resources.sort((a, b) => {
42
+ const aTime = a.dateCreated ? new Date(a.dateCreated).getTime() : 0;
43
+ const bTime = b.dateCreated ? new Date(b.dateCreated).getTime() : 0;
44
+ return bTime - aTime;
45
+ });
46
+ return resources;
47
+ }
48
+ /**
49
+ * Add content previews to resources (for search results)
50
+ * Retrieves and decodes the first 200 characters of each resource's primary representation
51
+ */
52
+ static async addContentPreviews(resources, config) {
53
+ const basePath = config.services.filesystem.path;
54
+ const projectRoot = config._metadata?.projectRoot;
55
+ const repStore = new FilesystemRepresentationStore({ basePath }, projectRoot);
56
+ return await Promise.all(
57
+ resources.map(async (doc) => {
58
+ try {
59
+ const primaryRep = getPrimaryRepresentation(doc);
60
+ if (primaryRep?.checksum && primaryRep?.mediaType) {
61
+ const contentBuffer = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
62
+ const contentPreview = decodeRepresentation(contentBuffer, primaryRep.mediaType).slice(0, 200);
63
+ return { ...doc, content: contentPreview };
64
+ }
65
+ return { ...doc, content: "" };
66
+ } catch {
67
+ return { ...doc, content: "" };
68
+ }
69
+ })
70
+ );
71
+ }
72
+ };
73
+
74
+ // src/annotation-context.ts
75
+ import { generateResourceSummary, generateText } from "@semiont/inference";
76
+ import {
77
+ getBodySource,
78
+ getTargetSource,
79
+ getTargetSelector,
80
+ getResourceEntityTypes,
81
+ getTextPositionSelector,
82
+ getPrimaryRepresentation as getPrimaryRepresentation2,
83
+ decodeRepresentation as decodeRepresentation2
84
+ } from "@semiont/api-client";
85
+ import { FilesystemRepresentationStore as FilesystemRepresentationStore2 } from "@semiont/content";
86
+ import { FilesystemViewStorage as FilesystemViewStorage2 } from "@semiont/event-sourcing";
87
+ import { resourceId as createResourceId, uriToResourceId } from "@semiont/core";
88
+ import { getEntityTypes } from "@semiont/ontology";
89
+ var AnnotationContext = class {
90
+ /**
91
+ * Build LLM context for an annotation
92
+ *
93
+ * @param annotationUri - Full annotation URI (e.g., http://localhost:4000/annotations/abc123)
94
+ * @param resourceId - Source resource ID
95
+ * @param config - Application configuration
96
+ * @param options - Context building options
97
+ * @returns Rich context for LLM processing
98
+ * @throws Error if annotation or resource not found
99
+ */
100
+ static async buildLLMContext(annotationUri, resourceId, config, options = {}) {
101
+ const {
102
+ includeSourceContext = true,
103
+ includeTargetContext = true,
104
+ contextWindow = 1e3
105
+ } = options;
106
+ if (contextWindow < 100 || contextWindow > 5e3) {
107
+ throw new Error("contextWindow must be between 100 and 5000");
108
+ }
109
+ console.log(`[AnnotationContext] buildLLMContext called with annotationUri=${annotationUri}, resourceId=${resourceId}`);
110
+ const basePath = config.services.filesystem.path;
111
+ console.log(`[AnnotationContext] basePath=${basePath}`);
112
+ const projectRoot = config._metadata?.projectRoot;
113
+ const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
114
+ const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
115
+ console.log(`[AnnotationContext] Getting view for resourceId=${resourceId}`);
116
+ let sourceView;
117
+ try {
118
+ sourceView = await viewStorage.get(resourceId);
119
+ console.log(`[AnnotationContext] Got view:`, !!sourceView);
120
+ if (!sourceView) {
121
+ throw new Error("Source resource not found");
122
+ }
123
+ } catch (error) {
124
+ console.error(`[AnnotationContext] Error getting view:`, error);
125
+ throw error;
126
+ }
127
+ console.log(`[AnnotationContext] Looking for annotation ${annotationUri} in resource ${resourceId}`);
128
+ console.log(`[AnnotationContext] View has ${sourceView.annotations.annotations.length} annotations`);
129
+ console.log(`[AnnotationContext] First 5 annotation IDs:`, sourceView.annotations.annotations.slice(0, 5).map((a) => a.id));
130
+ const annotation = sourceView.annotations.annotations.find((a) => a.id === annotationUri);
131
+ console.log(`[AnnotationContext] Found annotation:`, !!annotation);
132
+ if (!annotation) {
133
+ throw new Error("Annotation not found in view");
134
+ }
135
+ const targetSource = getTargetSource(annotation.target);
136
+ const targetResourceId = targetSource.split("/").pop();
137
+ console.log(`[AnnotationContext] Target source: ${targetSource}, Expected resource ID: ${resourceId}, Extracted ID: ${targetResourceId}`);
138
+ if (targetResourceId !== resourceId) {
139
+ throw new Error(`Annotation target resource ID (${targetResourceId}) does not match expected resource ID (${resourceId})`);
140
+ }
141
+ const sourceDoc = sourceView.resource;
142
+ const bodySource = getBodySource(annotation.body);
143
+ let targetDoc = null;
144
+ if (bodySource) {
145
+ const parts = bodySource.split("/");
146
+ const lastPart = parts[parts.length - 1];
147
+ if (!lastPart) {
148
+ throw new Error(`Invalid body source URI: ${bodySource}`);
149
+ }
150
+ const targetResourceId2 = createResourceId(lastPart);
151
+ const targetView = await viewStorage.get(targetResourceId2);
152
+ targetDoc = targetView?.resource || null;
153
+ }
154
+ let sourceContext;
155
+ if (includeSourceContext) {
156
+ const primaryRep = getPrimaryRepresentation2(sourceDoc);
157
+ if (!primaryRep?.checksum || !primaryRep?.mediaType) {
158
+ throw new Error("Source content not found");
159
+ }
160
+ const sourceContent = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
161
+ const contentStr = decodeRepresentation2(sourceContent, primaryRep.mediaType);
162
+ const targetSelectorRaw = getTargetSelector(annotation.target);
163
+ const targetSelector = Array.isArray(targetSelectorRaw) ? targetSelectorRaw[0] : targetSelectorRaw;
164
+ console.log(`[AnnotationContext] Target selector type:`, targetSelector?.type);
165
+ if (!targetSelector) {
166
+ console.warn(`[AnnotationContext] No target selector found`);
167
+ } else if (targetSelector.type === "TextPositionSelector") {
168
+ const selector = targetSelector;
169
+ const start = selector.start;
170
+ const end = selector.end;
171
+ const before = contentStr.slice(Math.max(0, start - contextWindow), start);
172
+ const selected = contentStr.slice(start, end);
173
+ const after = contentStr.slice(end, Math.min(contentStr.length, end + contextWindow));
174
+ sourceContext = { before, selected, after };
175
+ console.log(`[AnnotationContext] Built source context using TextPositionSelector (${start}-${end})`);
176
+ } else if (targetSelector.type === "TextQuoteSelector") {
177
+ const selector = targetSelector;
178
+ const exact = selector.exact;
179
+ const index = contentStr.indexOf(exact);
180
+ if (index !== -1) {
181
+ const start = index;
182
+ const end = index + exact.length;
183
+ const before = contentStr.slice(Math.max(0, start - contextWindow), start);
184
+ const selected = exact;
185
+ const after = contentStr.slice(end, Math.min(contentStr.length, end + contextWindow));
186
+ sourceContext = { before, selected, after };
187
+ console.log(`[AnnotationContext] Built source context using TextQuoteSelector (found at ${index})`);
188
+ } else {
189
+ console.warn(`[AnnotationContext] TextQuoteSelector exact text not found in content: "${exact.substring(0, 50)}..."`);
190
+ }
191
+ } else {
192
+ console.warn(`[AnnotationContext] Unknown selector type: ${targetSelector.type}`);
193
+ }
194
+ }
195
+ let targetContext;
196
+ if (includeTargetContext && targetDoc) {
197
+ const targetRep = getPrimaryRepresentation2(targetDoc);
198
+ if (targetRep?.checksum && targetRep?.mediaType) {
199
+ const targetContent = await repStore.retrieve(targetRep.checksum, targetRep.mediaType);
200
+ const contentStr = decodeRepresentation2(targetContent, targetRep.mediaType);
201
+ targetContext = {
202
+ content: contentStr.slice(0, contextWindow * 2),
203
+ summary: await generateResourceSummary(targetDoc.name, contentStr, getResourceEntityTypes(targetDoc), config)
204
+ };
205
+ }
206
+ }
207
+ const suggestedResolution = void 0;
208
+ const generationContext = sourceContext ? {
209
+ sourceContext: {
210
+ before: sourceContext.before || "",
211
+ selected: sourceContext.selected,
212
+ after: sourceContext.after || ""
213
+ },
214
+ metadata: {
215
+ resourceType: "document",
216
+ language: sourceDoc.language,
217
+ entityTypes: getEntityTypes(annotation)
218
+ }
219
+ } : void 0;
220
+ const response = {
221
+ annotation,
222
+ sourceResource: sourceDoc,
223
+ targetResource: targetDoc,
224
+ ...generationContext ? { context: generationContext } : {},
225
+ ...sourceContext ? { sourceContext } : {},
226
+ // Keep for backward compatibility
227
+ ...targetContext ? { targetContext } : {},
228
+ ...suggestedResolution ? { suggestedResolution } : {}
229
+ };
230
+ return response;
231
+ }
232
+ /**
233
+ * Get resource annotations from view storage (fast path)
234
+ * Throws if view missing
235
+ */
236
+ static async getResourceAnnotations(resourceId, config) {
237
+ if (!config.services?.filesystem?.path) {
238
+ throw new Error("Filesystem path not found in configuration");
239
+ }
240
+ const basePath = config.services.filesystem.path;
241
+ const projectRoot = config._metadata?.projectRoot;
242
+ const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
243
+ const view = await viewStorage.get(resourceId);
244
+ if (!view) {
245
+ throw new Error(`Resource ${resourceId} not found in view storage`);
246
+ }
247
+ return view.annotations;
248
+ }
249
+ /**
250
+ * Get all annotations
251
+ * @returns Array of all annotation objects
252
+ */
253
+ static async getAllAnnotations(resourceId, config) {
254
+ const annotations = await this.getResourceAnnotations(resourceId, config);
255
+ return await this.enrichResolvedReferences(annotations.annotations, config);
256
+ }
257
+ /**
258
+ * Enrich reference annotations with resolved document names
259
+ * Adds _resolvedDocumentName property to annotations that link to documents
260
+ * @private
261
+ */
262
+ static async enrichResolvedReferences(annotations, config) {
263
+ if (!config.services?.filesystem?.path) {
264
+ return annotations;
265
+ }
266
+ const resolvedUris = /* @__PURE__ */ new Set();
267
+ for (const ann of annotations) {
268
+ if (ann.motivation === "linking" && ann.body) {
269
+ const body = Array.isArray(ann.body) ? ann.body : [ann.body];
270
+ for (const item of body) {
271
+ if (item.purpose === "linking" && item.source) {
272
+ resolvedUris.add(item.source);
273
+ }
274
+ }
275
+ }
276
+ }
277
+ if (resolvedUris.size === 0) {
278
+ return annotations;
279
+ }
280
+ const basePath = config.services.filesystem.path;
281
+ const projectRoot = config._metadata?.projectRoot;
282
+ const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
283
+ const metadataPromises = Array.from(resolvedUris).map(async (uri) => {
284
+ const docId = uri.split("/resources/")[1];
285
+ if (!docId) return null;
286
+ try {
287
+ const view = await viewStorage.get(docId);
288
+ if (view?.resource?.name) {
289
+ return {
290
+ uri,
291
+ metadata: {
292
+ name: view.resource.name,
293
+ mediaType: view.resource.mediaType
294
+ }
295
+ };
296
+ }
297
+ } catch (e) {
298
+ }
299
+ return null;
300
+ });
301
+ const results = await Promise.all(metadataPromises);
302
+ const uriToMetadata = /* @__PURE__ */ new Map();
303
+ for (const result of results) {
304
+ if (result) {
305
+ uriToMetadata.set(result.uri, result.metadata);
306
+ }
307
+ }
308
+ return annotations.map((ann) => {
309
+ if (ann.motivation === "linking" && ann.body) {
310
+ const body = Array.isArray(ann.body) ? ann.body : [ann.body];
311
+ for (const item of body) {
312
+ if (item.purpose === "linking" && item.source) {
313
+ const metadata = uriToMetadata.get(item.source);
314
+ if (metadata) {
315
+ return {
316
+ ...ann,
317
+ _resolvedDocumentName: metadata.name,
318
+ _resolvedDocumentMediaType: metadata.mediaType
319
+ };
320
+ }
321
+ }
322
+ }
323
+ }
324
+ return ann;
325
+ });
326
+ }
327
+ /**
328
+ * Get resource stats (version info)
329
+ * @returns Version and timestamp info for the annotations
330
+ */
331
+ static async getResourceStats(resourceId, config) {
332
+ const annotations = await this.getResourceAnnotations(resourceId, config);
333
+ return {
334
+ resourceId: annotations.resourceId,
335
+ version: annotations.version,
336
+ updatedAt: annotations.updatedAt
337
+ };
338
+ }
339
+ /**
340
+ * Check if resource exists in view storage
341
+ */
342
+ static async resourceExists(resourceId, config) {
343
+ if (!config.services?.filesystem?.path) {
344
+ throw new Error("Filesystem path not found in configuration");
345
+ }
346
+ const basePath = config.services.filesystem.path;
347
+ const projectRoot = config._metadata?.projectRoot;
348
+ const viewStorage = new FilesystemViewStorage2(basePath, projectRoot);
349
+ return await viewStorage.exists(resourceId);
350
+ }
351
+ /**
352
+ * Get a single annotation by ID
353
+ * O(1) lookup using resource ID to access view storage
354
+ */
355
+ static async getAnnotation(annotationId, resourceId, config) {
356
+ const annotations = await this.getResourceAnnotations(resourceId, config);
357
+ return annotations.annotations.find((a) => {
358
+ const shortId = a.id.split("/").pop();
359
+ return shortId === annotationId;
360
+ }) || null;
361
+ }
362
+ /**
363
+ * List annotations with optional filtering
364
+ * @param filters - Optional filters like resourceId and type
365
+ * @throws Error if resourceId not provided (cross-resource queries not supported in view storage)
366
+ */
367
+ static async listAnnotations(filters, config) {
368
+ if (!filters?.resourceId) {
369
+ throw new Error("resourceId is required for annotation listing - cross-resource queries not supported in view storage");
370
+ }
371
+ return await this.getAllAnnotations(filters.resourceId, config);
372
+ }
373
+ /**
374
+ * Get annotation context (selected text with surrounding context)
375
+ */
376
+ static async getAnnotationContext(annotationId, resourceId, contextBefore, contextAfter, config) {
377
+ const basePath = config.services.filesystem.path;
378
+ const projectRoot = config._metadata?.projectRoot;
379
+ const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
380
+ const annotation = await this.getAnnotation(annotationId, resourceId, config);
381
+ if (!annotation) {
382
+ throw new Error("Annotation not found");
383
+ }
384
+ const resource = await ResourceContext.getResourceMetadata(
385
+ uriToResourceId(getTargetSource(annotation.target)),
386
+ config
387
+ );
388
+ if (!resource) {
389
+ throw new Error("Resource not found");
390
+ }
391
+ const contentStr = await this.getResourceContent(resource, repStore);
392
+ const context = this.extractAnnotationContext(annotation, contentStr, contextBefore, contextAfter);
393
+ return {
394
+ annotation,
395
+ context,
396
+ resource: {
397
+ "@context": resource["@context"],
398
+ "@id": resource["@id"],
399
+ name: resource.name,
400
+ entityTypes: resource.entityTypes,
401
+ representations: resource.representations,
402
+ archived: resource.archived,
403
+ creationMethod: resource.creationMethod,
404
+ wasAttributedTo: resource.wasAttributedTo,
405
+ dateCreated: resource.dateCreated
406
+ }
407
+ };
408
+ }
409
+ /**
410
+ * Generate AI summary of annotation in context
411
+ */
412
+ static async generateAnnotationSummary(annotationId, resourceId, config) {
413
+ const basePath = config.services.filesystem.path;
414
+ const projectRoot = config._metadata?.projectRoot;
415
+ const repStore = new FilesystemRepresentationStore2({ basePath }, projectRoot);
416
+ const annotation = await this.getAnnotation(annotationId, resourceId, config);
417
+ if (!annotation) {
418
+ throw new Error("Annotation not found");
419
+ }
420
+ const resource = await ResourceContext.getResourceMetadata(
421
+ uriToResourceId(getTargetSource(annotation.target)),
422
+ config
423
+ );
424
+ if (!resource) {
425
+ throw new Error("Resource not found");
426
+ }
427
+ const contentStr = await this.getResourceContent(resource, repStore);
428
+ const contextSize = 500;
429
+ const context = this.extractAnnotationContext(annotation, contentStr, contextSize, contextSize);
430
+ const annotationEntityTypes = getEntityTypes(annotation);
431
+ const summary = await this.generateSummary(resource, context, annotationEntityTypes, config);
432
+ return {
433
+ summary,
434
+ relevantFields: {
435
+ resourceId: resource.id,
436
+ resourceName: resource.name,
437
+ entityTypes: annotationEntityTypes
438
+ },
439
+ context: {
440
+ before: context.before.substring(Math.max(0, context.before.length - 200)),
441
+ // Last 200 chars
442
+ selected: context.selected,
443
+ after: context.after.substring(0, 200)
444
+ // First 200 chars
445
+ }
446
+ };
447
+ }
448
+ /**
449
+ * Get resource content as string
450
+ */
451
+ static async getResourceContent(resource, repStore) {
452
+ const primaryRep = getPrimaryRepresentation2(resource);
453
+ if (!primaryRep?.checksum || !primaryRep?.mediaType) {
454
+ throw new Error("Resource content not found");
455
+ }
456
+ const content = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
457
+ return decodeRepresentation2(content, primaryRep.mediaType);
458
+ }
459
+ /**
460
+ * Extract annotation context from resource content
461
+ */
462
+ static extractAnnotationContext(annotation, contentStr, contextBefore, contextAfter) {
463
+ const targetSelector = getTargetSelector(annotation.target);
464
+ const posSelector = targetSelector ? getTextPositionSelector(targetSelector) : null;
465
+ if (!posSelector) {
466
+ throw new Error("TextPositionSelector required for context");
467
+ }
468
+ const selStart = posSelector.start;
469
+ const selEnd = posSelector.end;
470
+ const start = Math.max(0, selStart - contextBefore);
471
+ const end = Math.min(contentStr.length, selEnd + contextAfter);
472
+ return {
473
+ before: contentStr.substring(start, selStart),
474
+ selected: contentStr.substring(selStart, selEnd),
475
+ after: contentStr.substring(selEnd, end)
476
+ };
477
+ }
478
+ /**
479
+ * Generate LLM summary of annotation in context
480
+ */
481
+ static async generateSummary(resource, context, entityTypes, config) {
482
+ const summaryPrompt = `Summarize this text in context:
483
+
484
+ Context before: "${context.before.substring(Math.max(0, context.before.length - 200))}"
485
+ Selected exact: "${context.selected}"
486
+ Context after: "${context.after.substring(0, 200)}"
487
+
488
+ Resource: ${resource.name}
489
+ Entity types: ${entityTypes.join(", ")}`;
490
+ return await generateText(summaryPrompt, config, 500, 0.5);
491
+ }
492
+ };
493
+
494
+ // src/graph-context.ts
495
+ import { getGraphDatabase } from "@semiont/graph";
496
+ import { resourceIdToURI } from "@semiont/core";
497
+ var GraphContext = class {
498
+ /**
499
+ * Get all resources referencing this resource (backlinks)
500
+ * Requires graph traversal - must use graph database
501
+ */
502
+ static async getBacklinks(resourceId, config) {
503
+ const graphDb = await getGraphDatabase(config);
504
+ const resourceUri = resourceIdToURI(resourceId, config.services.backend.publicURL);
505
+ return await graphDb.getResourceReferencedBy(resourceUri);
506
+ }
507
+ /**
508
+ * Find shortest path between two resources
509
+ * Requires graph traversal - must use graph database
510
+ */
511
+ static async findPath(fromResourceId, toResourceId, config, maxDepth) {
512
+ const graphDb = await getGraphDatabase(config);
513
+ return await graphDb.findPath(fromResourceId, toResourceId, maxDepth);
514
+ }
515
+ /**
516
+ * Get resource connections (graph edges)
517
+ * Requires graph traversal - must use graph database
518
+ */
519
+ static async getResourceConnections(resourceId, config) {
520
+ const graphDb = await getGraphDatabase(config);
521
+ return await graphDb.getResourceConnections(resourceId);
522
+ }
523
+ /**
524
+ * Search resources by name (cross-resource query)
525
+ * Requires full-text search - must use graph database
526
+ */
527
+ static async searchResources(query, config, limit) {
528
+ const graphDb = await getGraphDatabase(config);
529
+ return await graphDb.searchResources(query, limit);
530
+ }
531
+ };
532
+
533
+ // src/annotation-detection.ts
534
+ import { FilesystemRepresentationStore as FilesystemRepresentationStore3 } from "@semiont/content";
535
+ import { getPrimaryRepresentation as getPrimaryRepresentation3, decodeRepresentation as decodeRepresentation3 } from "@semiont/api-client";
536
+ import {
537
+ MotivationPrompts,
538
+ MotivationParsers,
539
+ generateText as generateText2
540
+ } from "@semiont/inference";
541
+ import { getTagSchema, getSchemaCategory } from "@semiont/ontology";
542
+ var AnnotationDetection = class {
543
+ /**
544
+ * Detect comments in a resource
545
+ *
546
+ * @param resourceId - The resource to analyze
547
+ * @param config - Environment configuration
548
+ * @param instructions - Optional user instructions for comment generation
549
+ * @param tone - Optional tone guidance (e.g., "academic", "conversational")
550
+ * @param density - Optional target number of comments per 2000 words
551
+ * @returns Array of validated comment matches
552
+ */
553
+ static async detectComments(resourceId, config, instructions, tone, density) {
554
+ const resource = await ResourceContext.getResourceMetadata(resourceId, config);
555
+ if (!resource) {
556
+ throw new Error(`Resource ${resourceId} not found`);
557
+ }
558
+ const content = await this.loadResourceContent(resourceId, config);
559
+ if (!content) {
560
+ throw new Error(`Could not load content for resource ${resourceId}`);
561
+ }
562
+ const prompt = MotivationPrompts.buildCommentPrompt(content, instructions, tone, density);
563
+ const response = await generateText2(
564
+ prompt,
565
+ config,
566
+ 3e3,
567
+ // maxTokens: Higher than highlights/assessments due to comment text
568
+ 0.4
569
+ // temperature: Slightly higher to allow creative context
570
+ );
571
+ return MotivationParsers.parseComments(response, content);
572
+ }
573
+ /**
574
+ * Detect highlights in a resource
575
+ *
576
+ * @param resourceId - The resource to analyze
577
+ * @param config - Environment configuration
578
+ * @param instructions - Optional user instructions for highlight selection
579
+ * @param density - Optional target number of highlights per 2000 words
580
+ * @returns Array of validated highlight matches
581
+ */
582
+ static async detectHighlights(resourceId, config, instructions, density) {
583
+ const resource = await ResourceContext.getResourceMetadata(resourceId, config);
584
+ if (!resource) {
585
+ throw new Error(`Resource ${resourceId} not found`);
586
+ }
587
+ const content = await this.loadResourceContent(resourceId, config);
588
+ if (!content) {
589
+ throw new Error(`Could not load content for resource ${resourceId}`);
590
+ }
591
+ const prompt = MotivationPrompts.buildHighlightPrompt(content, instructions, density);
592
+ const response = await generateText2(
593
+ prompt,
594
+ config,
595
+ 2e3,
596
+ // maxTokens: Lower than comments/assessments (no body text)
597
+ 0.3
598
+ // temperature: Low for consistent importance judgments
599
+ );
600
+ return MotivationParsers.parseHighlights(response, content);
601
+ }
602
+ /**
603
+ * Detect assessments in a resource
604
+ *
605
+ * @param resourceId - The resource to analyze
606
+ * @param config - Environment configuration
607
+ * @param instructions - Optional user instructions for assessment generation
608
+ * @param tone - Optional tone guidance (e.g., "critical", "supportive")
609
+ * @param density - Optional target number of assessments per 2000 words
610
+ * @returns Array of validated assessment matches
611
+ */
612
+ static async detectAssessments(resourceId, config, instructions, tone, density) {
613
+ const resource = await ResourceContext.getResourceMetadata(resourceId, config);
614
+ if (!resource) {
615
+ throw new Error(`Resource ${resourceId} not found`);
616
+ }
617
+ const content = await this.loadResourceContent(resourceId, config);
618
+ if (!content) {
619
+ throw new Error(`Could not load content for resource ${resourceId}`);
620
+ }
621
+ const prompt = MotivationPrompts.buildAssessmentPrompt(content, instructions, tone, density);
622
+ const response = await generateText2(
623
+ prompt,
624
+ config,
625
+ 3e3,
626
+ // maxTokens: Higher for assessment text
627
+ 0.3
628
+ // temperature: Lower for analytical consistency
629
+ );
630
+ return MotivationParsers.parseAssessments(response, content);
631
+ }
632
+ /**
633
+ * Detect tags in a resource for a specific category
634
+ *
635
+ * @param resourceId - The resource to analyze
636
+ * @param config - Environment configuration
637
+ * @param schemaId - The tag schema identifier (e.g., "irac", "imrad")
638
+ * @param category - The specific category to detect
639
+ * @returns Array of validated tag matches
640
+ */
641
+ static async detectTags(resourceId, config, schemaId, category) {
642
+ const schema = getTagSchema(schemaId);
643
+ if (!schema) {
644
+ throw new Error(`Invalid tag schema: ${schemaId}`);
645
+ }
646
+ const categoryInfo = getSchemaCategory(schemaId, category);
647
+ if (!categoryInfo) {
648
+ throw new Error(`Invalid category "${category}" for schema ${schemaId}`);
649
+ }
650
+ const resource = await ResourceContext.getResourceMetadata(resourceId, config);
651
+ if (!resource) {
652
+ throw new Error(`Resource ${resourceId} not found`);
653
+ }
654
+ const content = await this.loadResourceContent(resourceId, config);
655
+ if (!content) {
656
+ throw new Error(`Could not load content for resource ${resourceId}`);
657
+ }
658
+ const prompt = MotivationPrompts.buildTagPrompt(
659
+ content,
660
+ category,
661
+ schema.name,
662
+ schema.description,
663
+ schema.domain,
664
+ categoryInfo.description,
665
+ categoryInfo.examples
666
+ );
667
+ const response = await generateText2(
668
+ prompt,
669
+ config,
670
+ 4e3,
671
+ // maxTokens: Higher for full document analysis
672
+ 0.2
673
+ // temperature: Lower for structural consistency
674
+ );
675
+ const parsedTags = MotivationParsers.parseTags(response);
676
+ return MotivationParsers.validateTagOffsets(parsedTags, content, category);
677
+ }
678
+ /**
679
+ * Load resource content from representation store
680
+ * Helper method used by all detection methods
681
+ *
682
+ * @param resourceId - The resource ID to load
683
+ * @param config - Environment configuration
684
+ * @returns Resource content as string, or null if not available
685
+ */
686
+ static async loadResourceContent(resourceId, config) {
687
+ const resource = await ResourceContext.getResourceMetadata(resourceId, config);
688
+ if (!resource) return null;
689
+ const primaryRep = getPrimaryRepresentation3(resource);
690
+ if (!primaryRep) return null;
691
+ const baseMediaType = primaryRep.mediaType?.split(";")[0]?.trim() || "";
692
+ if (baseMediaType !== "text/plain" && baseMediaType !== "text/markdown") {
693
+ return null;
694
+ }
695
+ if (!primaryRep.checksum || !primaryRep.mediaType) return null;
696
+ const basePath = config.services.filesystem.path;
697
+ const projectRoot = config._metadata?.projectRoot;
698
+ const repStore = new FilesystemRepresentationStore3({ basePath }, projectRoot);
699
+ const contentBuffer = await repStore.retrieve(primaryRep.checksum, primaryRep.mediaType);
700
+ return decodeRepresentation3(contentBuffer, primaryRep.mediaType);
701
+ }
702
+ };
703
+
704
+ // src/index.ts
705
+ var PACKAGE_NAME = "@semiont/make-meaning";
706
+ var VERSION = "0.1.0";
707
+ export {
708
+ AnnotationContext,
709
+ AnnotationDetection,
710
+ GraphContext,
711
+ PACKAGE_NAME,
712
+ ResourceContext,
713
+ VERSION
714
+ };
715
+ //# sourceMappingURL=index.js.map