@fortemi/core 2026.7.0 → 2026.7.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.
- package/README.md +31 -19
- package/dist/aiwg-index.d.ts +58 -5
- package/dist/aiwg-index.js +145 -24
- package/dist/aiwg-index.js.map +1 -1
- package/dist/index.d.ts +31 -31
- package/dist/index.js +146 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -126,10 +126,11 @@ Use this subpath for Pagenary-style command palettes, static docs search, and
|
|
|
126
126
|
vanilla JavaScript review surfaces. The top-level `@fortemi/core` export remains
|
|
127
127
|
available for full archive/runtime integrations.
|
|
128
128
|
|
|
129
|
-
The
|
|
130
|
-
`
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
The AIWG index adapter accepts both `aiwg.fortemi.index.export.v1` and
|
|
130
|
+
`aiwg.fortemi.index.export.v2` envelopes. The v1 record contract keeps the
|
|
131
|
+
existing flat fields for filtering and search: `facets`, `tags`, `concepts`,
|
|
132
|
+
`relationships`, and `provenance`. Static consumers that render metadata tabs
|
|
133
|
+
can also read optional rich fields from full records:
|
|
133
134
|
|
|
134
135
|
- `skos_concepts`: concept ids plus labels, definitions, schemes, notation, URIs,
|
|
135
136
|
alternate labels, and metadata.
|
|
@@ -140,10 +141,14 @@ records:
|
|
|
140
141
|
- `relationships[*].metadata`: optional relationship labels, confidence,
|
|
141
142
|
privacy, and structured metadata.
|
|
142
143
|
|
|
143
|
-
These fields are additive to `aiwg.fortemi.index.record.v1`.
|
|
144
|
-
can
|
|
145
|
-
|
|
146
|
-
|
|
144
|
+
These fields are additive to `aiwg.fortemi.index.record.v1`. Version 2 records
|
|
145
|
+
can also carry AIWG migration fields such as `search`, `chunks`, `embeddings`,
|
|
146
|
+
`source.origin`, `source.generated`, `source.checksum`, `source.updated_at`,
|
|
147
|
+
`privacy.locality`, and compatibility metadata. Query helpers use v2 `search`
|
|
148
|
+
and `chunks` text when the old top-level `title`/`text` fields are absent.
|
|
149
|
+
Existing consumers can ignore the v2 fields and continue querying the flat
|
|
150
|
+
fields. Chunked indexes with a projection keep rich metadata in detail records;
|
|
151
|
+
call `getRecord(id)` before rendering a metadata panel.
|
|
147
152
|
|
|
148
153
|
Large static indexes can use the chunked browser path instead of downloading one
|
|
149
154
|
full `aiwg.fortemi.index.export.v1` file. Host a manifest plus deterministic part
|
|
@@ -179,9 +184,11 @@ results, but the controller keeps only a bounded part cache and never sets a
|
|
|
179
184
|
materialized full export in `getIndex()`.
|
|
180
185
|
|
|
181
186
|
AIWG records can use project-specific string types such as `aiwg.skill`,
|
|
182
|
-
`aiwg.command`, `aiwg.rule`, `aiwg.
|
|
183
|
-
|
|
184
|
-
research
|
|
187
|
+
`aiwg.agent`, `aiwg.command`, `aiwg.rule`, `aiwg.flow`, `aiwg.bundle`,
|
|
188
|
+
`aiwg.kb.page`, `aiwg.memory.entry`, `aiwg.issue`, `research.ref`, and
|
|
189
|
+
`aiwg.research.profile`. Validation still enforces required record fields, but
|
|
190
|
+
it does not require unknown AIWG, research, KB, memory, issue, or documentation
|
|
191
|
+
domains to collapse into `aiwg.artifact`.
|
|
185
192
|
|
|
186
193
|
For AIWG command-palette parity, opt into discovery ranking instead of the
|
|
187
194
|
default deterministic substring lookup:
|
|
@@ -203,14 +210,19 @@ Chunked indexes also support graph navigation without loading a full export:
|
|
|
203
210
|
const neighbors = await controller.neighbors('aiwg:requirement:search', {
|
|
204
211
|
direction: 'both',
|
|
205
212
|
relationshipType: 'cites',
|
|
213
|
+
relationshipDirection: 'downstream',
|
|
206
214
|
})
|
|
207
215
|
|
|
208
216
|
const graph = await controller.toCommunityGraphChunked()
|
|
209
217
|
```
|
|
210
218
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
219
|
+
Relationship edges preserve v2 `source_path`, `target_path`, and
|
|
220
|
+
`direction: 'upstream' | 'downstream' | 'related'` values. Use
|
|
221
|
+
`direction: 'in' | 'out' | 'both'` for graph orientation around a node and
|
|
222
|
+
`relationshipDirection` for AIWG dependency/citation/profile/KB direction
|
|
223
|
+
semantics. When scan parts are projected, relationship traversal scans the parts
|
|
224
|
+
and lazy-loads detail records only when relationships are not present in the
|
|
225
|
+
scan projection.
|
|
214
226
|
|
|
215
227
|
Static semantic search uses an optional sidecar contract:
|
|
216
228
|
|
|
@@ -281,11 +293,11 @@ pnpm add @fortemi/react @fortemi/core react
|
|
|
281
293
|
|
|
282
294
|
## Documentation
|
|
283
295
|
|
|
284
|
-
- [Repository API](https://github.com/Fortemi/fortemi-react/blob/main/docs/api-reference.md#repositories)
|
|
285
|
-
- [Integration guide](https://github.com/Fortemi/fortemi-react/blob/main/docs/integration.md)
|
|
286
|
-
- [Search guide](https://github.com/Fortemi/fortemi-react/blob/main/docs/search.md)
|
|
287
|
-
- [Extending Fortemi](https://github.com/Fortemi/fortemi-react/blob/main/docs/extending.md)
|
|
288
|
-
- [Supply-chain controls](https://github.com/Fortemi/fortemi-react/blob/main/docs/security/supply-chain.md)
|
|
296
|
+
- [Repository API](https://github.com/Fortemi/fortemi-react/blob/main/docs/content/api-reference.md#repositories)
|
|
297
|
+
- [Integration guide](https://github.com/Fortemi/fortemi-react/blob/main/docs/content/guides/integration.md)
|
|
298
|
+
- [Search guide](https://github.com/Fortemi/fortemi-react/blob/main/docs/content/guides/search.md)
|
|
299
|
+
- [Extending Fortemi](https://github.com/Fortemi/fortemi-react/blob/main/docs/content/advanced/extending.md)
|
|
300
|
+
- [Supply-chain controls](https://github.com/Fortemi/fortemi-react/blob/main/docs/content/security/supply-chain.md)
|
|
289
301
|
|
|
290
302
|
## License
|
|
291
303
|
|
package/dist/aiwg-index.d.ts
CHANGED
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
type AiwgFortemiKnownRecordType = 'crm.contact' | 'crm.organization' | 'crm.event' | 'crm.interaction' | 'aiwg.artifact' | 'docs.page';
|
|
2
2
|
type AiwgFortemiRecordType = AiwgFortemiKnownRecordType | `aiwg.${string}` | `research.${string}` | `docs.${string}` | string;
|
|
3
|
+
type AiwgFortemiRecordSchemaVersion = 'aiwg.fortemi.index.record.v1' | 'aiwg.fortemi.index.record.v2';
|
|
4
|
+
type AiwgFortemiIndexExportSchemaVersion = 'aiwg.fortemi.index.export.v1' | 'aiwg.fortemi.index.export.v2';
|
|
3
5
|
type AiwgPrivacyClassification = 'private' | 'sanitized' | 'public';
|
|
4
6
|
type AiwgProvenanceConfidence = 'source' | 'candidate' | 'reviewed' | 'rejected';
|
|
5
7
|
type AiwgReviewAction = 'accept' | 'reject' | 'defer';
|
|
8
|
+
type AiwgFortemiRelationshipDirection = 'upstream' | 'downstream' | 'related';
|
|
6
9
|
interface AiwgFortemiRecordSource {
|
|
7
10
|
path: string;
|
|
8
11
|
repo_relative_path: string;
|
|
9
12
|
locator: string;
|
|
13
|
+
origin?: string;
|
|
14
|
+
generated?: boolean;
|
|
15
|
+
checksum?: string;
|
|
16
|
+
updated_at?: string;
|
|
10
17
|
}
|
|
11
18
|
interface AiwgFortemiRelationship {
|
|
12
19
|
type: string;
|
|
13
20
|
target_id: string;
|
|
14
21
|
source_path?: string;
|
|
22
|
+
target_path?: string;
|
|
23
|
+
direction?: AiwgFortemiRelationshipDirection;
|
|
15
24
|
label?: string;
|
|
16
25
|
confidence?: number;
|
|
17
26
|
privacy?: AiwgPrivacyClassification;
|
|
@@ -54,18 +63,53 @@ interface AiwgFortemiProvenanceEvent {
|
|
|
54
63
|
privacy?: AiwgPrivacyClassification;
|
|
55
64
|
attributes?: Record<string, unknown>;
|
|
56
65
|
}
|
|
66
|
+
interface AiwgFortemiSearchProjection {
|
|
67
|
+
title?: string;
|
|
68
|
+
name?: string;
|
|
69
|
+
summary?: string;
|
|
70
|
+
body?: string;
|
|
71
|
+
triggers?: string[];
|
|
72
|
+
aliases?: string[];
|
|
73
|
+
capability?: string;
|
|
74
|
+
tags?: string[];
|
|
75
|
+
phase?: string;
|
|
76
|
+
type?: string;
|
|
77
|
+
frontmatter?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
interface AiwgFortemiChunk {
|
|
80
|
+
id?: string;
|
|
81
|
+
text?: string;
|
|
82
|
+
body?: string;
|
|
83
|
+
summary?: string;
|
|
84
|
+
source_path?: string;
|
|
85
|
+
metadata?: Record<string, unknown>;
|
|
86
|
+
}
|
|
87
|
+
interface AiwgFortemiRecordEmbedding {
|
|
88
|
+
id?: string;
|
|
89
|
+
embedding?: number[];
|
|
90
|
+
vector?: number[];
|
|
91
|
+
model?: string;
|
|
92
|
+
granularity?: string;
|
|
93
|
+
input_hash?: string;
|
|
94
|
+
source_path?: string;
|
|
95
|
+
metadata?: Record<string, unknown>;
|
|
96
|
+
}
|
|
57
97
|
interface AiwgFortemiRecord {
|
|
58
|
-
schema_version:
|
|
98
|
+
schema_version: AiwgFortemiRecordSchemaVersion;
|
|
59
99
|
id: string;
|
|
60
100
|
type: AiwgFortemiRecordType;
|
|
61
101
|
source: AiwgFortemiRecordSource;
|
|
62
|
-
title
|
|
63
|
-
text
|
|
102
|
+
title?: string;
|
|
103
|
+
text?: string;
|
|
64
104
|
facets: Record<string, string[]>;
|
|
65
105
|
tags: string[];
|
|
66
106
|
concepts: string[];
|
|
67
107
|
relationships: AiwgFortemiRelationship[];
|
|
68
108
|
provenance: AiwgFortemiProvenance[];
|
|
109
|
+
search?: AiwgFortemiSearchProjection;
|
|
110
|
+
chunks?: AiwgFortemiChunk[];
|
|
111
|
+
embeddings?: AiwgFortemiRecordEmbedding[];
|
|
112
|
+
compatibility?: Record<string, unknown>;
|
|
69
113
|
/** Optional rich SKOS metadata for static consumers that need labels/definitions without opening a shard. */
|
|
70
114
|
skos_concepts?: AiwgFortemiSkosConcept[];
|
|
71
115
|
/** Optional SKOS relationship edges among concepts referenced by this record. */
|
|
@@ -75,17 +119,23 @@ interface AiwgFortemiRecord {
|
|
|
75
119
|
privacy: {
|
|
76
120
|
classification: AiwgPrivacyClassification;
|
|
77
121
|
pii: boolean;
|
|
122
|
+
locality?: string;
|
|
78
123
|
};
|
|
79
124
|
updated_at: string;
|
|
80
125
|
}
|
|
81
126
|
interface AiwgFortemiIndexExport {
|
|
82
|
-
schema_version:
|
|
127
|
+
schema_version: AiwgFortemiIndexExportSchemaVersion;
|
|
83
128
|
generated_at: string;
|
|
84
129
|
source: {
|
|
85
130
|
repo: string;
|
|
86
131
|
privacy: AiwgPrivacyClassification;
|
|
132
|
+
origin?: string;
|
|
133
|
+
generated?: boolean;
|
|
134
|
+
checksum?: string;
|
|
135
|
+
updated_at?: string;
|
|
87
136
|
};
|
|
88
137
|
items: AiwgFortemiRecord[];
|
|
138
|
+
compatibility?: Record<string, unknown>;
|
|
89
139
|
}
|
|
90
140
|
interface AiwgFortemiChunkPartRef {
|
|
91
141
|
href: string;
|
|
@@ -215,6 +265,7 @@ type AiwgRelationshipSetOperation = 'intersection' | 'union' | 'difference';
|
|
|
215
265
|
interface AiwgRelationshipTraversalOptions {
|
|
216
266
|
direction?: AiwgRelationshipDirection;
|
|
217
267
|
relationshipType?: string;
|
|
268
|
+
relationshipDirection?: AiwgFortemiRelationshipDirection;
|
|
218
269
|
limit?: number;
|
|
219
270
|
}
|
|
220
271
|
interface AiwgRelationshipQueryOptions extends AiwgRelationshipTraversalOptions {
|
|
@@ -227,6 +278,8 @@ interface AiwgRelationshipEdgeSummary {
|
|
|
227
278
|
target_id: string;
|
|
228
279
|
type: string;
|
|
229
280
|
source_path?: string;
|
|
281
|
+
target_path?: string;
|
|
282
|
+
direction?: AiwgFortemiRelationshipDirection;
|
|
230
283
|
}
|
|
231
284
|
interface AiwgRelationshipNodeSummary {
|
|
232
285
|
id: string;
|
|
@@ -381,4 +434,4 @@ declare function aiwgFortemiIndexToCommunityGraph(index: AiwgFortemiIndexExport,
|
|
|
381
434
|
}[];
|
|
382
435
|
};
|
|
383
436
|
|
|
384
|
-
export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgDetailIdEncoding, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiKnownRecordType, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiProvenanceEvent, type AiwgFortemiRecord, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgFortemiSkosConcept, type AiwgFortemiSkosRelation, type AiwgFortemiSkosRelationType, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgRelationshipDirection, type AiwgRelationshipEdgeSummary, type AiwgRelationshipNodeSummary, type AiwgRelationshipQueryOptions, type AiwgRelationshipSetOperation, type AiwgRelationshipSetOptions, type AiwgRelationshipSetResult, type AiwgRelationshipTraversalOptions, type AiwgRelationshipTraversalResult, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, type AiwgStaticDuplicatePair, type AiwgStaticEmbeddingRecord, type AiwgStaticEmbeddingSet, type AiwgStaticHybridQueryOptions, type AiwgStaticSemanticQueryOptions, type AiwgStaticSemanticResult, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
|
|
437
|
+
export { AIWG_SCAN_REQUIRED_FIELDS, type AiwgChunkedIndexBuildOptions, type AiwgChunkedIndexBuildResult, type AiwgChunkedIndexDetailLoader, type AiwgChunkedIndexLoadOptions, type AiwgChunkedIndexLoader, type AiwgChunkedIndexProgress, type AiwgChunkedIndexProgressPhase, type AiwgChunkedIndexQueryOptions, type AiwgChunkedIndexQueryResult, type AiwgChunkedIndexValidationResult, type AiwgDetailIdEncoding, type AiwgFortemiChunk, type AiwgFortemiChunkDetailRef, type AiwgFortemiChunkManifest, type AiwgFortemiChunkPart, type AiwgFortemiChunkPartRef, type AiwgFortemiIndexExport, type AiwgFortemiIndexExportSchemaVersion, type AiwgFortemiKnownRecordType, type AiwgFortemiProjectedRecord, type AiwgFortemiProvenance, type AiwgFortemiProvenanceEvent, type AiwgFortemiRecord, type AiwgFortemiRecordEmbedding, type AiwgFortemiRecordSchemaVersion, type AiwgFortemiRecordSource, type AiwgFortemiRecordType, type AiwgFortemiRelationship, type AiwgFortemiRelationshipDirection, type AiwgFortemiSearchProjection, type AiwgFortemiSkosConcept, type AiwgFortemiSkosRelation, type AiwgFortemiSkosRelationType, type AiwgIndexController, type AiwgIndexControllerListener, type AiwgIndexControllerSnapshot, type AiwgIndexGraphOptions, type AiwgIndexQueryMatch, type AiwgIndexQueryOptions, type AiwgIndexQueryRankedItem, type AiwgIndexQueryResult, type AiwgIndexQueryWeights, type AiwgIndexValidationResult, type AiwgPrivacyClassification, type AiwgProvenanceConfidence, type AiwgRelationshipDirection, type AiwgRelationshipEdgeSummary, type AiwgRelationshipNodeSummary, type AiwgRelationshipQueryOptions, type AiwgRelationshipSetOperation, type AiwgRelationshipSetOptions, type AiwgRelationshipSetResult, type AiwgRelationshipTraversalOptions, type AiwgRelationshipTraversalResult, type AiwgReviewAction, type AiwgReviewDecision, type AiwgReviewDecisionExport, type AiwgReviewInput, type AiwgStaticDuplicatePair, type AiwgStaticEmbeddingRecord, type AiwgStaticEmbeddingSet, type AiwgStaticHybridQueryOptions, type AiwgStaticSemanticQueryOptions, type AiwgStaticSemanticResult, aiwgDetailHrefForId, aiwgFortemiIndexToCommunityGraph, assertAiwgFortemiChunkManifest, assertAiwgFortemiChunkPart, assertAiwgFortemiIndexExport, assertAiwgStaticEmbeddingSet, buildAiwgChunkedIndex, createAiwgFetchChunkLoader, createAiwgFetchDetailLoader, createAiwgIndexController, createAiwgReviewDecisionExport, encodeAiwgDetailId, findAiwgStaticDuplicatePairs, getAiwgFortemiFacets, queryAiwgFortemiIndex, queryAiwgHybridIndex, queryAiwgSemanticIndex, validateAiwgFortemiChunkManifest, validateAiwgFortemiChunkPart, validateAiwgFortemiIndexExport, validateAiwgStaticEmbeddingSet };
|
package/dist/aiwg-index.js
CHANGED
|
@@ -15,8 +15,6 @@ var REQUIRED_RECORD_FIELDS = [
|
|
|
15
15
|
"id",
|
|
16
16
|
"type",
|
|
17
17
|
"source",
|
|
18
|
-
"title",
|
|
19
|
-
"text",
|
|
20
18
|
"facets",
|
|
21
19
|
"tags",
|
|
22
20
|
"concepts",
|
|
@@ -57,6 +55,12 @@ function isPlainRecord(value) {
|
|
|
57
55
|
function isOptionalStringArray(value) {
|
|
58
56
|
return value === void 0 || Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
59
57
|
}
|
|
58
|
+
function isSupportedIndexSchemaVersion(value) {
|
|
59
|
+
return value === "aiwg.fortemi.index.export.v1" || value === "aiwg.fortemi.index.export.v2";
|
|
60
|
+
}
|
|
61
|
+
function isSupportedRecordSchemaVersion(value) {
|
|
62
|
+
return value === "aiwg.fortemi.index.record.v1" || value === "aiwg.fortemi.index.record.v2";
|
|
63
|
+
}
|
|
60
64
|
function validateOptionalRichMetadata(item, index, errors) {
|
|
61
65
|
if (item.skos_concepts !== void 0) {
|
|
62
66
|
if (!Array.isArray(item.skos_concepts)) {
|
|
@@ -103,28 +107,80 @@ function validateOptionalRichMetadata(item, index, errors) {
|
|
|
103
107
|
if (relationship.metadata !== void 0 && !isPlainRecord(relationship.metadata)) {
|
|
104
108
|
errors.push("items[" + index + "].relationships[" + relationshipIndex + "].metadata must be an object");
|
|
105
109
|
}
|
|
110
|
+
if (relationship.direction !== void 0 && relationship.direction !== "upstream" && relationship.direction !== "downstream" && relationship.direction !== "related") {
|
|
111
|
+
errors.push("items[" + index + "].relationships[" + relationshipIndex + "].direction must be upstream, downstream, or related");
|
|
112
|
+
}
|
|
113
|
+
if (relationship.target_path !== void 0 && typeof relationship.target_path !== "string") {
|
|
114
|
+
errors.push("items[" + index + "].relationships[" + relationshipIndex + "].target_path must be a string");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (item.search !== void 0) {
|
|
119
|
+
if (!isPlainRecord(item.search)) {
|
|
120
|
+
errors.push("items[" + index + "].search must be an object");
|
|
121
|
+
} else {
|
|
122
|
+
if (!isOptionalStringArray(item.search.triggers)) errors.push("items[" + index + "].search.triggers must be a string array");
|
|
123
|
+
if (!isOptionalStringArray(item.search.aliases)) errors.push("items[" + index + "].search.aliases must be a string array");
|
|
124
|
+
if (!isOptionalStringArray(item.search.tags)) errors.push("items[" + index + "].search.tags must be a string array");
|
|
125
|
+
if (item.search.frontmatter !== void 0 && !isPlainRecord(item.search.frontmatter)) {
|
|
126
|
+
errors.push("items[" + index + "].search.frontmatter must be an object");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (item.chunks !== void 0) {
|
|
131
|
+
if (!Array.isArray(item.chunks)) {
|
|
132
|
+
errors.push("items[" + index + "].chunks must be an array when present");
|
|
133
|
+
} else {
|
|
134
|
+
for (const [chunkIndex, chunk] of item.chunks.entries()) {
|
|
135
|
+
if (!isPlainRecord(chunk)) errors.push("items[" + index + "].chunks[" + chunkIndex + "] must be an object");
|
|
136
|
+
if (chunk.metadata !== void 0 && !isPlainRecord(chunk.metadata)) {
|
|
137
|
+
errors.push("items[" + index + "].chunks[" + chunkIndex + "].metadata must be an object");
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (item.embeddings !== void 0) {
|
|
143
|
+
if (!Array.isArray(item.embeddings)) {
|
|
144
|
+
errors.push("items[" + index + "].embeddings must be an array when present");
|
|
145
|
+
} else {
|
|
146
|
+
for (const [embeddingIndex, embedding] of item.embeddings.entries()) {
|
|
147
|
+
if (!isPlainRecord(embedding)) errors.push("items[" + index + "].embeddings[" + embeddingIndex + "] must be an object");
|
|
148
|
+
const vector = embedding.embedding ?? embedding.vector;
|
|
149
|
+
if (vector !== void 0 && (!Array.isArray(vector) || !vector.every((entry) => typeof entry === "number"))) {
|
|
150
|
+
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "].embedding/vector must be a number array");
|
|
151
|
+
}
|
|
152
|
+
if (embedding.metadata !== void 0 && !isPlainRecord(embedding.metadata)) {
|
|
153
|
+
errors.push("items[" + index + "].embeddings[" + embeddingIndex + "].metadata must be an object");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
106
156
|
}
|
|
107
157
|
}
|
|
158
|
+
if (item.compatibility !== void 0 && !isPlainRecord(item.compatibility)) {
|
|
159
|
+
errors.push("items[" + index + "].compatibility must be an object");
|
|
160
|
+
}
|
|
108
161
|
}
|
|
109
162
|
function validateAiwgFortemiIndexExport(value) {
|
|
110
163
|
const errors = [];
|
|
111
164
|
const counts = {};
|
|
112
165
|
const data = value;
|
|
113
|
-
if (data?.schema_version
|
|
114
|
-
errors.push("schema_version must be aiwg.fortemi.index.export.v1");
|
|
166
|
+
if (!isSupportedIndexSchemaVersion(data?.schema_version)) {
|
|
167
|
+
errors.push("schema_version must be aiwg.fortemi.index.export.v1 or aiwg.fortemi.index.export.v2");
|
|
115
168
|
}
|
|
116
169
|
if (!hasString(data?.generated_at)) errors.push("generated_at is required");
|
|
117
170
|
if (!hasString(data?.source?.repo)) errors.push("source.repo is required");
|
|
118
171
|
if (!hasString(data?.source?.privacy)) errors.push("source.privacy is required");
|
|
119
172
|
if (!Array.isArray(data?.items)) errors.push("items must be an array");
|
|
173
|
+
if (data.compatibility !== void 0 && !isPlainRecord(data.compatibility)) {
|
|
174
|
+
errors.push("compatibility must be an object");
|
|
175
|
+
}
|
|
120
176
|
const ids = /* @__PURE__ */ new Set();
|
|
121
177
|
let previousId = "";
|
|
122
178
|
for (const [index, item] of (data.items ?? []).entries()) {
|
|
123
179
|
for (const field of REQUIRED_RECORD_FIELDS) {
|
|
124
180
|
if (!(field in item)) errors.push("items[" + index + "]." + field + " is required");
|
|
125
181
|
}
|
|
126
|
-
if (item.schema_version
|
|
127
|
-
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1");
|
|
182
|
+
if (!isSupportedRecordSchemaVersion(item.schema_version)) {
|
|
183
|
+
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1 or aiwg.fortemi.index.record.v2");
|
|
128
184
|
}
|
|
129
185
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
130
186
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
@@ -138,6 +194,12 @@ function validateAiwgFortemiIndexExport(value) {
|
|
|
138
194
|
if (!hasString(item.source?.path)) errors.push("items[" + index + "].source.path is required");
|
|
139
195
|
if (!hasString(item.source?.repo_relative_path)) errors.push("items[" + index + "].source.repo_relative_path is required");
|
|
140
196
|
if (!hasString(item.source?.locator)) errors.push("items[" + index + "].source.locator is required");
|
|
197
|
+
if (typeof item.title !== "string" && typeof item.search?.title !== "string" && typeof item.search?.name !== "string") {
|
|
198
|
+
errors.push("items[" + index + "].title or search.title/search.name is required");
|
|
199
|
+
}
|
|
200
|
+
if (typeof item.text !== "string" && typeof item.search?.body !== "string" && typeof item.search?.summary !== "string") {
|
|
201
|
+
errors.push("items[" + index + "].text or search.body/search.summary is required");
|
|
202
|
+
}
|
|
141
203
|
if (!Array.isArray(item.tags)) errors.push("items[" + index + "].tags must be an array");
|
|
142
204
|
if (!Array.isArray(item.concepts)) errors.push("items[" + index + "].concepts must be an array");
|
|
143
205
|
if (!Array.isArray(item.relationships)) errors.push("items[" + index + "].relationships must be an array");
|
|
@@ -218,8 +280,8 @@ function validateProjectedRecords(items) {
|
|
|
218
280
|
const ids = /* @__PURE__ */ new Set();
|
|
219
281
|
let previousId = "";
|
|
220
282
|
for (const [index, item] of items.entries()) {
|
|
221
|
-
if (item.schema_version
|
|
222
|
-
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1");
|
|
283
|
+
if (!isSupportedRecordSchemaVersion(item.schema_version)) {
|
|
284
|
+
errors.push("items[" + index + "].schema_version must be aiwg.fortemi.index.record.v1 or aiwg.fortemi.index.record.v2");
|
|
223
285
|
}
|
|
224
286
|
if (!hasString(item.id)) errors.push("items[" + index + "].id is required");
|
|
225
287
|
if (hasString(item.id) && ids.has(item.id)) errors.push("duplicate id: " + item.id);
|
|
@@ -229,8 +291,12 @@ function validateProjectedRecords(items) {
|
|
|
229
291
|
}
|
|
230
292
|
if (hasString(item.id)) previousId = item.id;
|
|
231
293
|
if (!hasString(item.type)) errors.push("items[" + index + "].type must be a non-empty string");
|
|
232
|
-
if (
|
|
233
|
-
|
|
294
|
+
if (typeof item.title !== "string" && typeof item.search?.title !== "string" && typeof item.search?.name !== "string") {
|
|
295
|
+
errors.push("items[" + index + "].title or search.title/search.name is required");
|
|
296
|
+
}
|
|
297
|
+
if (typeof item.text !== "string" && typeof item.search?.body !== "string" && typeof item.search?.summary !== "string") {
|
|
298
|
+
errors.push("items[" + index + "].text or search.body/search.summary is required");
|
|
299
|
+
}
|
|
234
300
|
if (!item.facets || typeof item.facets !== "object" || Array.isArray(item.facets)) {
|
|
235
301
|
errors.push("items[" + index + "].facets must be an object");
|
|
236
302
|
}
|
|
@@ -322,6 +388,38 @@ function getAiwgFortemiFacets(items) {
|
|
|
322
388
|
}
|
|
323
389
|
return result;
|
|
324
390
|
}
|
|
391
|
+
function recordTitle(item) {
|
|
392
|
+
return item.title ?? item.search?.title ?? item.search?.name ?? item.id;
|
|
393
|
+
}
|
|
394
|
+
function recordText(item) {
|
|
395
|
+
return item.text ?? item.search?.body ?? item.search?.summary ?? item.chunks?.map((chunk) => chunk.text ?? chunk.body ?? chunk.summary ?? "").filter(Boolean).join("\n") ?? "";
|
|
396
|
+
}
|
|
397
|
+
function recordSearchValues(item) {
|
|
398
|
+
const search = item.search;
|
|
399
|
+
const values = [
|
|
400
|
+
item.id,
|
|
401
|
+
recordTitle(item),
|
|
402
|
+
recordText(item),
|
|
403
|
+
search?.title,
|
|
404
|
+
search?.name,
|
|
405
|
+
search?.summary,
|
|
406
|
+
search?.body,
|
|
407
|
+
search?.capability,
|
|
408
|
+
search?.phase,
|
|
409
|
+
search?.type,
|
|
410
|
+
...search?.triggers ?? [],
|
|
411
|
+
...search?.aliases ?? [],
|
|
412
|
+
...search?.tags ?? [],
|
|
413
|
+
...(item.chunks ?? []).flatMap((chunk) => [chunk.text, chunk.body, chunk.summary, chunk.source_path])
|
|
414
|
+
];
|
|
415
|
+
if (search?.frontmatter) {
|
|
416
|
+
for (const value of Object.values(search.frontmatter)) {
|
|
417
|
+
if (typeof value === "string") values.push(value);
|
|
418
|
+
else if (Array.isArray(value)) values.push(...value.filter((entry) => typeof entry === "string"));
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return values.filter((value) => typeof value === "string" && value.length > 0);
|
|
422
|
+
}
|
|
325
423
|
function buildAiwgChunkedIndex(index, options = {}) {
|
|
326
424
|
const partSize = hasPositiveInteger(options.partSize) ? options.partSize : 500;
|
|
327
425
|
const projection = options.projection;
|
|
@@ -383,14 +481,21 @@ function matchesFacetFilters(item, filters) {
|
|
|
383
481
|
function queryMatches(item, q) {
|
|
384
482
|
if (!q) return [];
|
|
385
483
|
const matches = [];
|
|
386
|
-
|
|
387
|
-
|
|
484
|
+
const title = recordTitle(item);
|
|
485
|
+
const text = recordText(item);
|
|
486
|
+
if (title.toLowerCase().includes(q)) matches.push({ field: "title", value: title });
|
|
487
|
+
if (text.toLowerCase().includes(q)) matches.push({ field: "text", value: text });
|
|
388
488
|
for (const tag of item.tags) {
|
|
389
489
|
if (tag.toLowerCase().includes(q)) matches.push({ field: "tag", value: tag });
|
|
390
490
|
}
|
|
391
491
|
for (const concept of item.concepts) {
|
|
392
492
|
if (concept.toLowerCase().includes(q)) matches.push({ field: "concept", value: concept });
|
|
393
493
|
}
|
|
494
|
+
for (const value of recordSearchValues(item)) {
|
|
495
|
+
if (value !== title && value !== text && value.toLowerCase().includes(q)) {
|
|
496
|
+
matches.push({ field: "text", value, score: DEFAULT_QUERY_WEIGHTS.text });
|
|
497
|
+
}
|
|
498
|
+
}
|
|
394
499
|
return matches;
|
|
395
500
|
}
|
|
396
501
|
var DISCOVERY_STOPWORDS = /* @__PURE__ */ new Set([
|
|
@@ -446,17 +551,28 @@ function discoveryMatches(item, query) {
|
|
|
446
551
|
const idParts = item.id.split(/[:/]/);
|
|
447
552
|
const names = [
|
|
448
553
|
item.id,
|
|
449
|
-
item
|
|
554
|
+
recordTitle(item),
|
|
555
|
+
item.search?.name,
|
|
556
|
+
item.search?.title,
|
|
557
|
+
...item.search?.aliases ?? [],
|
|
450
558
|
...idParts,
|
|
451
559
|
...facetValues(item, ["name", "canonical_name", "command", "skill", "agent", "rule"])
|
|
452
|
-
].filter(
|
|
453
|
-
const triggers =
|
|
560
|
+
].filter((value) => hasString(value));
|
|
561
|
+
const triggers = [
|
|
562
|
+
...facetValues(item, ["trigger", "triggers", "trigger_phrase", "trigger_phrases"]),
|
|
563
|
+
...item.search?.triggers ?? []
|
|
564
|
+
];
|
|
454
565
|
const capabilities = [
|
|
455
566
|
...facetValues(item, ["capability", "capabilities", "summary", "description"]),
|
|
567
|
+
item.search?.capability,
|
|
568
|
+
item.search?.summary,
|
|
569
|
+
item.search?.phase,
|
|
570
|
+
item.search?.type,
|
|
571
|
+
...item.search?.tags ?? [],
|
|
456
572
|
...item.concepts,
|
|
457
573
|
...item.tags
|
|
458
|
-
];
|
|
459
|
-
const sourceValues = [item.source?.path, item.source?.repo_relative_path, item.source?.locator].filter(
|
|
574
|
+
].filter((value) => hasString(value));
|
|
575
|
+
const sourceValues = [item.source?.path, item.source?.repo_relative_path, item.source?.locator].filter((value) => hasString(value));
|
|
460
576
|
for (const name of names) {
|
|
461
577
|
const canonicalName = canonicalDiscoveryName(name);
|
|
462
578
|
if (!canonicalName) continue;
|
|
@@ -466,8 +582,9 @@ function discoveryMatches(item, query) {
|
|
|
466
582
|
addDiscoveryMatch(matches, { field: "id", value: name, score: 48, reason: "near canonical name" });
|
|
467
583
|
}
|
|
468
584
|
}
|
|
469
|
-
const
|
|
470
|
-
|
|
585
|
+
const title = recordTitle(item);
|
|
586
|
+
const titleOverlap = tokenOverlapScore(tokens, title);
|
|
587
|
+
if (titleOverlap > 0) addDiscoveryMatch(matches, { field: "title", value: title, score: 18 * titleOverlap, reason: "title token overlap" });
|
|
471
588
|
for (const trigger of triggers) {
|
|
472
589
|
const overlap = tokenOverlapScore(tokens, trigger);
|
|
473
590
|
if (overlap > 0) addDiscoveryMatch(matches, { field: "facet", value: trigger, score: 34 * overlap, reason: "trigger phrase" });
|
|
@@ -476,8 +593,9 @@ function discoveryMatches(item, query) {
|
|
|
476
593
|
const overlap = tokenOverlapScore(tokens, capability);
|
|
477
594
|
if (overlap > 0) addDiscoveryMatch(matches, { field: "concept", value: capability, score: 22 * overlap, reason: "capability overlap" });
|
|
478
595
|
}
|
|
479
|
-
const
|
|
480
|
-
|
|
596
|
+
const text = recordText(item);
|
|
597
|
+
const textOverlap = tokenOverlapScore(tokens, text);
|
|
598
|
+
if (textOverlap > 0) addDiscoveryMatch(matches, { field: "text", value: text, score: 8 * textOverlap, reason: "body token overlap" });
|
|
481
599
|
for (const source of sourceValues) {
|
|
482
600
|
const overlap = tokenOverlapScore(tokens, source);
|
|
483
601
|
if (overlap > 0) addDiscoveryMatch(matches, { field: "source", value: source, score: 2 * overlap, reason: "path overlap" });
|
|
@@ -505,7 +623,7 @@ function createSnippet(item, matches, q, maxLength) {
|
|
|
505
623
|
const textMatch = matches.find((match) => match.field === "text");
|
|
506
624
|
const titleMatch = matches.find((match) => match.field === "title");
|
|
507
625
|
const firstMatch = textMatch ?? titleMatch ?? matches[0];
|
|
508
|
-
return clipSnippet(firstMatch?.value ?? item
|
|
626
|
+
return clipSnippet(firstMatch?.value ?? recordText(item), q, maxLength);
|
|
509
627
|
}
|
|
510
628
|
function createRankedEntries(items, q, options, ordinalBase = 0) {
|
|
511
629
|
const weights = { ...DEFAULT_QUERY_WEIGHTS, ...options.weights };
|
|
@@ -767,13 +885,16 @@ function edgeFromRelationship(sourceId, relationship) {
|
|
|
767
885
|
source_id: sourceId,
|
|
768
886
|
target_id: relationship.target_id,
|
|
769
887
|
type: relationship.type,
|
|
770
|
-
...relationship.source_path ? { source_path: relationship.source_path } : {}
|
|
888
|
+
...relationship.source_path ? { source_path: relationship.source_path } : {},
|
|
889
|
+
...relationship.target_path ? { target_path: relationship.target_path } : {},
|
|
890
|
+
...relationship.direction ? { direction: relationship.direction } : {}
|
|
771
891
|
};
|
|
772
892
|
}
|
|
773
893
|
function relationshipMatches(edge, options = {}) {
|
|
774
894
|
const type = relationshipTypeFilter(options);
|
|
775
895
|
const direction = options.direction ?? "both";
|
|
776
896
|
if (type && edge.type !== type) return false;
|
|
897
|
+
if (options.relationshipDirection && edge.direction !== options.relationshipDirection) return false;
|
|
777
898
|
if (options.sourceId && edge.source_id !== options.sourceId) return false;
|
|
778
899
|
if (options.targetId && edge.target_id !== options.targetId) return false;
|
|
779
900
|
if (direction === "out" && options.targetId && edge.target_id !== options.targetId) return false;
|
|
@@ -781,7 +902,7 @@ function relationshipMatches(edge, options = {}) {
|
|
|
781
902
|
return true;
|
|
782
903
|
}
|
|
783
904
|
function nodeSummary(item) {
|
|
784
|
-
return { id: item.id, type: item.type, title: item
|
|
905
|
+
return { id: item.id, type: item.type, title: recordTitle(item) };
|
|
785
906
|
}
|
|
786
907
|
function addNode(nodes, item) {
|
|
787
908
|
if (item) nodes.set(item.id, nodeSummary(item));
|