@fortemi/core 2026.7.13 → 2026.7.14

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.
@@ -1,4 +1,4 @@
1
- import Ajv2020 from 'ajv/dist/2020.js';
1
+ import Ajv20202 from 'ajv/dist/2020.js';
2
2
  import { gzipSync, gunzipSync } from 'fflate';
3
3
  import '@noble/hashes/sha256';
4
4
  import { blake3 } from '@noble/hashes/blake3';
@@ -153,6 +153,86 @@ function validateOptionalRichMetadata(item, index, errors) {
153
153
  if (item.compatibility !== void 0 && !isPlainRecord(item.compatibility)) {
154
154
  errors.push("items[" + index + "].compatibility must be an object");
155
155
  }
156
+ if (item.operational_state !== void 0) {
157
+ const at = "items[" + index + "].operational_state";
158
+ if (!isPlainRecord(item.operational_state)) {
159
+ errors.push(at + " must be an object");
160
+ } else {
161
+ const state = item.operational_state;
162
+ const allowedClassifications = /* @__PURE__ */ new Set([
163
+ "fresh",
164
+ "historical",
165
+ "superseded",
166
+ "contradicted",
167
+ "needs-source"
168
+ ]);
169
+ const allowedConfidence = /* @__PURE__ */ new Set(["source", "candidate", "reviewed", "rejected"]);
170
+ const stringFields = [
171
+ "source_repo",
172
+ "source_kind",
173
+ "source_id",
174
+ "observed_state",
175
+ "observed_at",
176
+ "source_updated_at",
177
+ "evidence_url",
178
+ "evidence_path",
179
+ "observer",
180
+ "stale_after"
181
+ ];
182
+ const arrayFields = ["supersedes", "contradicts"];
183
+ const allowedFields = /* @__PURE__ */ new Set([
184
+ ...stringFields,
185
+ ...arrayFields,
186
+ "classification",
187
+ "confidence",
188
+ "current_action_selector"
189
+ ]);
190
+ if (!allowedClassifications.has(String(state.classification))) {
191
+ errors.push(at + ".classification must be fresh, historical, superseded, contradicted, or needs-source");
192
+ }
193
+ for (const field of stringFields) {
194
+ if (state[field] !== void 0 && !hasString(state[field])) {
195
+ errors.push(at + "." + field + " must be a non-empty string");
196
+ }
197
+ }
198
+ for (const field of ["observed_at", "source_updated_at", "stale_after"]) {
199
+ if (state[field] !== void 0 && (typeof state[field] !== "string" || Number.isNaN(Date.parse(state[field])))) {
200
+ errors.push(at + "." + field + " must be a date-time string");
201
+ }
202
+ }
203
+ for (const field of arrayFields) {
204
+ if (!isOptionalStringArray(state[field])) {
205
+ errors.push(at + "." + field + " must be a string array");
206
+ }
207
+ }
208
+ if (state.confidence !== void 0 && !allowedConfidence.has(String(state.confidence))) {
209
+ errors.push(at + ".confidence must be source, candidate, reviewed, or rejected");
210
+ }
211
+ if (state.current_action_selector !== void 0 && typeof state.current_action_selector !== "boolean") {
212
+ errors.push(at + ".current_action_selector must be a boolean");
213
+ }
214
+ if (Object.keys(state).some((field) => !allowedFields.has(field))) {
215
+ errors.push(at + " contains unsupported fields");
216
+ }
217
+ }
218
+ }
219
+ if (item.state_transfer !== void 0) {
220
+ const at = "items[" + index + "].state_transfer";
221
+ if (!isPlainRecord(item.state_transfer)) {
222
+ errors.push(at + " must be an object");
223
+ } else {
224
+ const stateTransfer = item.state_transfer;
225
+ const deletedAt = stateTransfer.deleted_at;
226
+ if (!Object.hasOwn(stateTransfer, "deleted_at")) {
227
+ errors.push(at + ".deleted_at is required");
228
+ } else if (deletedAt !== null && (typeof deletedAt !== "string" || Number.isNaN(Date.parse(deletedAt)))) {
229
+ errors.push(at + ".deleted_at must be a date-time string or null");
230
+ }
231
+ if (Object.keys(stateTransfer).some((field) => field !== "deleted_at")) {
232
+ errors.push(at + " contains unsupported fields");
233
+ }
234
+ }
235
+ }
156
236
  }
157
237
  function isPrivacyClassification(value) {
158
238
  return value === "private" || value === "sanitized" || value === "public";
@@ -182,7 +262,9 @@ var V2_ONLY_RECORD_FIELDS = [
182
262
  "skos_concepts",
183
263
  "skos_relations",
184
264
  "provenance_events",
185
- "compatibility"
265
+ "compatibility",
266
+ "operational_state",
267
+ "state_transfer"
186
268
  ];
187
269
  var V2_ONLY_SOURCE_FIELDS = ["origin", "generated", "checksum", "updated_at"];
188
270
  var V2_ONLY_RELATIONSHIP_FIELDS = ["target_path", "direction", "metadata"];
@@ -292,6 +374,727 @@ function validateAiwgFortemiIndexExport(value) {
292
374
  return { valid: errors.length === 0, errors, counts };
293
375
  }
294
376
 
377
+ // schemas/aiwg-fortemi-index-export.schema.json
378
+ var aiwg_fortemi_index_export_schema_default = {
379
+ $schema: "https://json-schema.org/draft/2020-12/schema",
380
+ $id: "https://aiwg.io/schemas/aiwg-fortemi-index-export.json",
381
+ title: "AIWG Fortemi Index Export",
382
+ type: "object",
383
+ additionalProperties: false,
384
+ required: ["schema_version", "generated_at", "source", "items"],
385
+ properties: {
386
+ schema_version: {
387
+ enum: ["aiwg.fortemi.index.export.v1", "aiwg.fortemi.index.export.v2"]
388
+ },
389
+ generated_at: {
390
+ type: "string",
391
+ format: "date-time"
392
+ },
393
+ source: {
394
+ type: "object",
395
+ additionalProperties: false,
396
+ required: ["repo", "privacy"],
397
+ properties: {
398
+ repo: {
399
+ type: "string",
400
+ minLength: 1
401
+ },
402
+ privacy: {
403
+ $ref: "#/$defs/privacy"
404
+ },
405
+ graph: {
406
+ type: "string",
407
+ minLength: 1
408
+ }
409
+ }
410
+ },
411
+ compatibility: {
412
+ type: "object",
413
+ additionalProperties: false,
414
+ required: ["previous_schema_version", "strategy"],
415
+ properties: {
416
+ previous_schema_version: {
417
+ const: "aiwg.fortemi.index.export.v1"
418
+ },
419
+ strategy: {
420
+ const: "supported"
421
+ }
422
+ }
423
+ },
424
+ items: {
425
+ type: "array",
426
+ items: {
427
+ $ref: "#/$defs/record"
428
+ }
429
+ }
430
+ },
431
+ allOf: [
432
+ {
433
+ if: {
434
+ properties: {
435
+ schema_version: {
436
+ const: "aiwg.fortemi.index.export.v1"
437
+ }
438
+ }
439
+ },
440
+ then: {
441
+ not: {
442
+ required: ["compatibility"]
443
+ },
444
+ properties: {
445
+ source: {
446
+ not: {
447
+ required: ["graph"]
448
+ }
449
+ },
450
+ items: {
451
+ items: {
452
+ allOf: [
453
+ {
454
+ properties: {
455
+ schema_version: {
456
+ const: "aiwg.fortemi.index.record.v1"
457
+ }
458
+ }
459
+ },
460
+ {
461
+ $ref: "#/$defs/v1RecordCompatibility"
462
+ }
463
+ ]
464
+ }
465
+ }
466
+ }
467
+ }
468
+ },
469
+ {
470
+ if: {
471
+ properties: {
472
+ schema_version: {
473
+ const: "aiwg.fortemi.index.export.v2"
474
+ }
475
+ }
476
+ },
477
+ then: {
478
+ required: ["compatibility"],
479
+ properties: {
480
+ source: {
481
+ required: ["graph"]
482
+ },
483
+ items: {
484
+ items: {
485
+ properties: {
486
+ schema_version: {
487
+ const: "aiwg.fortemi.index.record.v2"
488
+ }
489
+ }
490
+ }
491
+ }
492
+ }
493
+ }
494
+ }
495
+ ],
496
+ $defs: {
497
+ privacy: {
498
+ enum: ["private", "sanitized", "public"]
499
+ },
500
+ recordType: {
501
+ type: "string",
502
+ minLength: 1
503
+ },
504
+ stringArray: {
505
+ type: "array",
506
+ items: {
507
+ type: "string"
508
+ }
509
+ },
510
+ numberArray: {
511
+ type: "array",
512
+ items: {
513
+ type: "number"
514
+ }
515
+ },
516
+ v1RecordCompatibility: {
517
+ type: "object",
518
+ not: {
519
+ anyOf: [
520
+ {
521
+ required: ["name"]
522
+ },
523
+ {
524
+ required: ["summary"]
525
+ },
526
+ {
527
+ required: ["search"]
528
+ },
529
+ {
530
+ required: ["chunks"]
531
+ },
532
+ {
533
+ required: ["embeddings"]
534
+ },
535
+ {
536
+ required: ["compatibility"]
537
+ },
538
+ {
539
+ required: ["skos_concepts"]
540
+ },
541
+ {
542
+ required: ["skos_relations"]
543
+ },
544
+ {
545
+ required: ["provenance_events"]
546
+ },
547
+ {
548
+ required: ["operational_state"]
549
+ },
550
+ {
551
+ required: ["state_transfer"]
552
+ }
553
+ ]
554
+ },
555
+ properties: {
556
+ source: {
557
+ not: {
558
+ anyOf: [
559
+ {
560
+ required: ["origin"]
561
+ },
562
+ {
563
+ required: ["generated"]
564
+ },
565
+ {
566
+ required: ["checksum"]
567
+ },
568
+ {
569
+ required: ["updated_at"]
570
+ }
571
+ ]
572
+ }
573
+ },
574
+ relationships: {
575
+ items: {
576
+ not: {
577
+ anyOf: [
578
+ {
579
+ required: ["target_path"]
580
+ },
581
+ {
582
+ required: ["direction"]
583
+ },
584
+ {
585
+ required: ["label"]
586
+ },
587
+ {
588
+ required: ["confidence"]
589
+ },
590
+ {
591
+ required: ["privacy"]
592
+ },
593
+ {
594
+ required: ["metadata"]
595
+ }
596
+ ]
597
+ }
598
+ }
599
+ },
600
+ privacy: {
601
+ not: {
602
+ required: ["locality"]
603
+ }
604
+ }
605
+ }
606
+ },
607
+ record: {
608
+ type: "object",
609
+ additionalProperties: false,
610
+ required: [
611
+ "schema_version",
612
+ "id",
613
+ "type",
614
+ "source",
615
+ "title",
616
+ "text",
617
+ "facets",
618
+ "tags",
619
+ "concepts",
620
+ "relationships",
621
+ "provenance",
622
+ "privacy",
623
+ "updated_at"
624
+ ],
625
+ properties: {
626
+ schema_version: {
627
+ enum: [
628
+ "aiwg.fortemi.index.record.v1",
629
+ "aiwg.fortemi.index.record.v2"
630
+ ]
631
+ },
632
+ id: {
633
+ type: "string",
634
+ minLength: 1
635
+ },
636
+ type: {
637
+ $ref: "#/$defs/recordType"
638
+ },
639
+ source: {
640
+ type: "object",
641
+ additionalProperties: false,
642
+ required: ["path", "repo_relative_path", "locator"],
643
+ properties: {
644
+ path: {
645
+ type: "string",
646
+ minLength: 1
647
+ },
648
+ repo_relative_path: {
649
+ type: "string",
650
+ minLength: 1
651
+ },
652
+ locator: {
653
+ type: "string",
654
+ minLength: 1
655
+ },
656
+ origin: {
657
+ type: "string",
658
+ minLength: 1
659
+ },
660
+ generated: {
661
+ type: "boolean"
662
+ },
663
+ checksum: {
664
+ type: "string"
665
+ },
666
+ updated_at: {
667
+ type: "string",
668
+ format: "date-time"
669
+ }
670
+ }
671
+ },
672
+ title: {
673
+ type: "string"
674
+ },
675
+ name: {
676
+ type: "string"
677
+ },
678
+ summary: {
679
+ type: "string"
680
+ },
681
+ text: {
682
+ type: "string"
683
+ },
684
+ search: {
685
+ type: "object",
686
+ additionalProperties: false,
687
+ required: [
688
+ "title",
689
+ "body",
690
+ "triggers",
691
+ "aliases",
692
+ "tags",
693
+ "frontmatter"
694
+ ],
695
+ properties: {
696
+ title: {
697
+ type: "string"
698
+ },
699
+ name: {
700
+ type: "string"
701
+ },
702
+ summary: {
703
+ type: "string"
704
+ },
705
+ body: {
706
+ type: "string"
707
+ },
708
+ triggers: {
709
+ $ref: "#/$defs/stringArray"
710
+ },
711
+ aliases: {
712
+ $ref: "#/$defs/stringArray"
713
+ },
714
+ capability: {
715
+ type: "string"
716
+ },
717
+ tags: {
718
+ $ref: "#/$defs/stringArray"
719
+ },
720
+ phase: {
721
+ type: "string"
722
+ },
723
+ type: {
724
+ type: "string"
725
+ },
726
+ frontmatter: {
727
+ type: "object"
728
+ }
729
+ }
730
+ },
731
+ facets: {
732
+ type: "object",
733
+ additionalProperties: {
734
+ $ref: "#/$defs/stringArray"
735
+ }
736
+ },
737
+ tags: {
738
+ $ref: "#/$defs/stringArray"
739
+ },
740
+ concepts: {
741
+ $ref: "#/$defs/stringArray"
742
+ },
743
+ relationships: {
744
+ type: "array",
745
+ items: {
746
+ type: "object",
747
+ additionalProperties: false,
748
+ required: ["type", "target_id"],
749
+ properties: {
750
+ type: {
751
+ type: "string",
752
+ minLength: 1
753
+ },
754
+ target_id: {
755
+ type: "string",
756
+ minLength: 1
757
+ },
758
+ source_path: {
759
+ type: "string"
760
+ },
761
+ target_path: {
762
+ type: "string"
763
+ },
764
+ direction: {
765
+ enum: ["upstream", "downstream", "related"]
766
+ },
767
+ label: {
768
+ type: "string"
769
+ },
770
+ confidence: {
771
+ type: "number"
772
+ },
773
+ privacy: {
774
+ $ref: "#/$defs/privacy"
775
+ },
776
+ metadata: {
777
+ type: "object"
778
+ }
779
+ }
780
+ }
781
+ },
782
+ provenance: {
783
+ type: "array",
784
+ minItems: 1,
785
+ items: {
786
+ type: "object",
787
+ additionalProperties: false,
788
+ required: ["field", "source", "path", "confidence", "privacy"],
789
+ properties: {
790
+ field: {
791
+ type: "string",
792
+ minLength: 1
793
+ },
794
+ source: {
795
+ type: "string",
796
+ minLength: 1
797
+ },
798
+ path: {
799
+ type: "string",
800
+ minLength: 1
801
+ },
802
+ confidence: {
803
+ enum: ["source", "candidate", "reviewed", "rejected"]
804
+ },
805
+ privacy: {
806
+ $ref: "#/$defs/privacy"
807
+ }
808
+ }
809
+ }
810
+ },
811
+ privacy: {
812
+ type: "object",
813
+ additionalProperties: false,
814
+ required: ["classification", "pii"],
815
+ properties: {
816
+ classification: {
817
+ $ref: "#/$defs/privacy"
818
+ },
819
+ pii: {
820
+ type: "boolean"
821
+ },
822
+ locality: {
823
+ enum: ["project", "framework", "external"]
824
+ }
825
+ }
826
+ },
827
+ chunks: {
828
+ type: "array",
829
+ items: {
830
+ type: "object",
831
+ additionalProperties: false,
832
+ properties: {
833
+ id: {
834
+ type: "string",
835
+ minLength: 1
836
+ },
837
+ text: {
838
+ type: "string"
839
+ },
840
+ body: {
841
+ type: "string"
842
+ },
843
+ summary: {
844
+ type: "string"
845
+ },
846
+ source_path: {
847
+ type: "string"
848
+ },
849
+ metadata: {
850
+ type: "object"
851
+ },
852
+ checksum: {
853
+ type: "string"
854
+ }
855
+ }
856
+ }
857
+ },
858
+ embeddings: {
859
+ type: "array",
860
+ items: {
861
+ type: "object",
862
+ additionalProperties: false,
863
+ properties: {
864
+ id: {
865
+ type: "string"
866
+ },
867
+ model: {
868
+ type: "string"
869
+ },
870
+ embedding: {
871
+ $ref: "#/$defs/numberArray"
872
+ },
873
+ vector: {
874
+ $ref: "#/$defs/numberArray"
875
+ },
876
+ granularity: {
877
+ type: "string"
878
+ },
879
+ source_path: {
880
+ type: "string"
881
+ },
882
+ metadata: {
883
+ type: "object"
884
+ },
885
+ chunk_id: {
886
+ type: "string"
887
+ },
888
+ vector_ref: {
889
+ type: "string"
890
+ },
891
+ input_hash: {
892
+ type: "string"
893
+ }
894
+ }
895
+ }
896
+ },
897
+ compatibility: {
898
+ type: "object"
899
+ },
900
+ skos_concepts: {
901
+ type: "array",
902
+ items: {
903
+ type: "object",
904
+ additionalProperties: false,
905
+ required: ["id", "prefLabel"],
906
+ properties: {
907
+ id: {
908
+ type: "string",
909
+ minLength: 1
910
+ },
911
+ prefLabel: {
912
+ type: "string",
913
+ minLength: 1
914
+ },
915
+ definition: {
916
+ type: "string"
917
+ },
918
+ scheme: {
919
+ type: "string"
920
+ },
921
+ notation: {
922
+ type: "string"
923
+ },
924
+ uri: {
925
+ type: "string"
926
+ },
927
+ altLabels: {
928
+ $ref: "#/$defs/stringArray"
929
+ },
930
+ metadata: {
931
+ type: "object"
932
+ }
933
+ }
934
+ }
935
+ },
936
+ skos_relations: {
937
+ type: "array",
938
+ items: {
939
+ type: "object",
940
+ additionalProperties: false,
941
+ required: ["type", "source_id", "target_id"],
942
+ properties: {
943
+ type: {
944
+ type: "string",
945
+ minLength: 1
946
+ },
947
+ source_id: {
948
+ type: "string",
949
+ minLength: 1
950
+ },
951
+ target_id: {
952
+ type: "string",
953
+ minLength: 1
954
+ },
955
+ source_path: {
956
+ type: "string"
957
+ },
958
+ metadata: {
959
+ type: "object"
960
+ }
961
+ }
962
+ }
963
+ },
964
+ provenance_events: {
965
+ type: "array",
966
+ items: {
967
+ type: "object",
968
+ additionalProperties: false,
969
+ required: ["activity"],
970
+ properties: {
971
+ id: {
972
+ type: "string"
973
+ },
974
+ activity: {
975
+ type: "string",
976
+ minLength: 1
977
+ },
978
+ agent: {
979
+ type: "string"
980
+ },
981
+ started_at: {
982
+ type: "string",
983
+ format: "date-time"
984
+ },
985
+ ended_at: {
986
+ type: "string",
987
+ format: "date-time"
988
+ },
989
+ source: {
990
+ type: "string"
991
+ },
992
+ path: {
993
+ type: "string"
994
+ },
995
+ confidence: {
996
+ enum: ["source", "candidate", "reviewed", "rejected"]
997
+ },
998
+ privacy: {
999
+ $ref: "#/$defs/privacy"
1000
+ },
1001
+ attributes: {
1002
+ type: "object"
1003
+ }
1004
+ }
1005
+ }
1006
+ },
1007
+ operational_state: {
1008
+ $ref: "#/$defs/operationalState"
1009
+ },
1010
+ state_transfer: {
1011
+ $ref: "#/$defs/stateTransfer"
1012
+ },
1013
+ updated_at: {
1014
+ type: "string",
1015
+ format: "date-time"
1016
+ }
1017
+ }
1018
+ },
1019
+ operationalState: {
1020
+ type: "object",
1021
+ additionalProperties: false,
1022
+ required: ["classification"],
1023
+ properties: {
1024
+ source_repo: { type: "string", minLength: 1 },
1025
+ source_kind: { type: "string", minLength: 1 },
1026
+ source_id: { type: "string", minLength: 1 },
1027
+ observed_state: { type: "string", minLength: 1 },
1028
+ observed_at: { type: "string", format: "date-time" },
1029
+ source_updated_at: { type: "string", format: "date-time" },
1030
+ evidence_url: { type: "string", format: "uri" },
1031
+ evidence_path: { type: "string", minLength: 1 },
1032
+ observer: { type: "string", minLength: 1 },
1033
+ supersedes: { $ref: "#/$defs/stringArray" },
1034
+ contradicts: { $ref: "#/$defs/stringArray" },
1035
+ stale_after: { type: "string", format: "date-time" },
1036
+ classification: {
1037
+ enum: ["fresh", "historical", "superseded", "contradicted", "needs-source"]
1038
+ },
1039
+ confidence: {
1040
+ enum: ["source", "candidate", "reviewed", "rejected"]
1041
+ },
1042
+ current_action_selector: { type: "boolean" }
1043
+ }
1044
+ },
1045
+ stateTransfer: {
1046
+ type: "object",
1047
+ additionalProperties: false,
1048
+ required: ["deleted_at"],
1049
+ properties: {
1050
+ deleted_at: {
1051
+ type: ["string", "null"],
1052
+ format: "date-time"
1053
+ }
1054
+ }
1055
+ }
1056
+ }
1057
+ };
1058
+
1059
+ // src/aiwg-index-schema.ts
1060
+ var ajvInstance;
1061
+ var exportValidator;
1062
+ function getAjv() {
1063
+ if (!ajvInstance) {
1064
+ ajvInstance = new Ajv20202({
1065
+ allErrors: true,
1066
+ strict: false,
1067
+ validateFormats: false
1068
+ });
1069
+ ajvInstance.addSchema(aiwg_fortemi_index_export_schema_default);
1070
+ }
1071
+ return ajvInstance;
1072
+ }
1073
+ function formatErrors(errors) {
1074
+ return (errors ?? []).map((error) => {
1075
+ const path = error.instancePath || "(root)";
1076
+ return `${path} ${error.message ?? "is invalid"}`;
1077
+ });
1078
+ }
1079
+ function getExportValidator() {
1080
+ exportValidator ??= getAjv().getSchema(aiwg_fortemi_index_export_schema_default.$id) ?? getAjv().compile(aiwg_fortemi_index_export_schema_default);
1081
+ return exportValidator;
1082
+ }
1083
+ function validateAiwgFortemiIndexExportSchema(value) {
1084
+ const validate = getExportValidator();
1085
+ const schemaValue = value && typeof value === "object" && Array.isArray(value.items) ? {
1086
+ ...value,
1087
+ items: value.items.map((item) => {
1088
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
1089
+ const schemaRecord = { ...item };
1090
+ Reflect.deleteProperty(schemaRecord, "binary_sources");
1091
+ return schemaRecord;
1092
+ })
1093
+ } : value;
1094
+ const valid = validate(schemaValue);
1095
+ return { valid, errors: formatErrors(validate.errors) };
1096
+ }
1097
+
295
1098
  // src/shard/checksum.ts
296
1099
  async function sha256Hex(data) {
297
1100
  const buf = new ArrayBuffer(data.byteLength);
@@ -8658,7 +9461,7 @@ function addCanonicalFormats(ajv) {
8658
9461
  }
8659
9462
  function getCoreAjv() {
8660
9463
  if (!coreAjvInstance) {
8661
- coreAjvInstance = new Ajv2020({
9464
+ coreAjvInstance = new Ajv20202({
8662
9465
  allErrors: true,
8663
9466
  strict: true,
8664
9467
  validateFormats: true
@@ -8678,7 +9481,7 @@ function getCoreAjv() {
8678
9481
  }
8679
9482
  function getFullAjv() {
8680
9483
  if (!fullAjvInstance) {
8681
- fullAjvInstance = new Ajv2020({
9484
+ fullAjvInstance = new Ajv20202({
8682
9485
  allErrors: true,
8683
9486
  strict: true,
8684
9487
  validateFormats: true
@@ -8720,7 +9523,7 @@ function fullValidatorFor(name, version = CURRENT_SHARD_VERSION) {
8720
9523
  fullValidators.set(key, validator);
8721
9524
  return validator;
8722
9525
  }
8723
- function formatErrors(errors) {
9526
+ function formatErrors2(errors) {
8724
9527
  return (errors ?? []).map((error) => {
8725
9528
  const path = error.instancePath || "(root)";
8726
9529
  return `${path} ${error.message ?? "is invalid"}`;
@@ -8827,7 +9630,7 @@ function validateCoreV1Structure(files, manifest) {
8827
9630
  }
8828
9631
  const manifestValidator = coreValidatorFor("manifest", schemaVersion);
8829
9632
  if (!manifestValidator(manifest)) {
8830
- return formatErrors(manifestValidator.errors).map((error) => `manifest.json ${error}`);
9633
+ return formatErrors2(manifestValidator.errors).map((error) => `manifest.json ${error}`);
8831
9634
  }
8832
9635
  const components = manifest.components;
8833
9636
  const expectedFiles = new Set(components.map((component) => CORE_V1_COMPONENT_FILES[component].file));
@@ -8854,7 +9657,7 @@ function validateCoreV1Structure(files, manifest) {
8854
9657
  for (const [index, record] of parsed.records.entries()) {
8855
9658
  if (!validator(record)) {
8856
9659
  errors.push(
8857
- ...formatErrors(validator.errors).map((error) => `${spec.file}[${index}] ${error}`)
9660
+ ...formatErrors2(validator.errors).map((error) => `${spec.file}[${index}] ${error}`)
8858
9661
  );
8859
9662
  }
8860
9663
  }
@@ -8880,7 +9683,7 @@ function validateFullV1Structure(files, manifest) {
8880
9683
  }
8881
9684
  const manifestValidator = fullValidatorFor("manifest", schemaVersion);
8882
9685
  if (!manifestValidator(manifest)) {
8883
- return formatErrors(manifestValidator.errors).map((error) => `manifest.json ${error}`);
9686
+ return formatErrors2(manifestValidator.errors).map((error) => `manifest.json ${error}`);
8884
9687
  }
8885
9688
  const components = manifest.components;
8886
9689
  const expectedFiles = new Set(
@@ -8909,7 +9712,7 @@ function validateFullV1Structure(files, manifest) {
8909
9712
  for (const [index, record] of parsed.records.entries()) {
8910
9713
  if (!validator(record)) {
8911
9714
  errors.push(
8912
- ...formatErrors(validator.errors).map((error) => `${spec.file}[${index}] ${error}`)
9715
+ ...formatErrors2(validator.errors).map((error) => `${spec.file}[${index}] ${error}`)
8913
9716
  );
8914
9717
  }
8915
9718
  }
@@ -8945,7 +9748,7 @@ function validateFullV1Structure(files, manifest) {
8945
9748
  const signatureValidator = fullValidatorFor("signature", schemaVersion);
8946
9749
  if (!signatureValidator(signature)) {
8947
9750
  errors.push(
8948
- ...formatErrors(signatureValidator.errors).map(
9751
+ ...formatErrors2(signatureValidator.errors).map(
8949
9752
  (error) => `signature.json ${error}`
8950
9753
  )
8951
9754
  );
@@ -9825,6 +10628,11 @@ function aiwgRecordContent(record) {
9825
10628
  if (chunks?.length) return chunks.join("\n\n");
9826
10629
  return record.search?.summary ?? "";
9827
10630
  }
10631
+ function aiwgRecordCollectionPaths(record) {
10632
+ const segments = record.source.repo_relative_path.split("/").filter((segment) => segment.length > 0 && segment !== ".");
10633
+ const directorySegments = segments.slice(0, -1);
10634
+ return directorySegments.map((_, index) => directorySegments.slice(0, index + 1).join("/"));
10635
+ }
9828
10636
  function aiwgShardMetadata(index, record) {
9829
10637
  return {
9830
10638
  aiwg_fortemi_index: {
@@ -9841,31 +10649,61 @@ function aiwgShardMetadata(index, record) {
9841
10649
  function encodeJsonLines(values) {
9842
10650
  return shardEncoder.encode(values.map((value) => JSON.stringify(value)).join("\n"));
9843
10651
  }
10652
+ function validateAiwgIndexForShard(index) {
10653
+ const contractValidation = validateAiwgFortemiIndexExportSchema(index);
10654
+ const semanticValidation = validateAiwgFortemiIndexExport(index);
10655
+ const errors = [...contractValidation.errors, ...semanticValidation.errors];
10656
+ if (errors.length > 0) {
10657
+ throw new Error(`Invalid AIWG Fortemi index export:
10658
+ ${errors.join("\n")}`);
10659
+ }
10660
+ }
9844
10661
  async function aiwgFortemiIndexToKnowledgeShard(index, options = {}) {
9845
10662
  if (options.includeNativeRichComponents) {
9846
10663
  throw new Error(
9847
10664
  "Native AIWG full-v1 conversion requires aiwgFortemiIndexToKnowledgeShardWithReport so semantic losses cannot be discarded"
9848
10665
  );
9849
10666
  }
9850
- const validation = validateAiwgFortemiIndexExport(index);
9851
- if (!validation.valid) {
9852
- throw new Error(`Invalid AIWG Fortemi index export:
9853
- ${validation.errors.join("\n")}`);
9854
- }
10667
+ validateAiwgIndexForShard(index);
9855
10668
  if (index.schema_version !== "aiwg.fortemi.index.export.v2") {
9856
10669
  throw new Error("Knowledge Shard conversion requires aiwg.fortemi.index.export.v2");
9857
10670
  }
9858
10671
  const createdAt2 = aiwgShardTimestamp(options.createdAt ?? index.generated_at, (/* @__PURE__ */ new Date()).toISOString());
9859
10672
  const noteIds = new Map(index.items.map((record) => [record.id, aiwgShardUuid("record", record.id)]));
10673
+ const collectionPaths = [...new Set(index.items.flatMap(aiwgRecordCollectionPaths))].sort();
10674
+ const collectionIds = new Map(
10675
+ collectionPaths.map((path) => [path, aiwgShardUuid("collection", path)])
10676
+ );
10677
+ const collectionNoteCounts = /* @__PURE__ */ new Map();
10678
+ for (const record of index.items) {
10679
+ const collectionPath = aiwgRecordCollectionPaths(record).at(-1);
10680
+ if (collectionPath) {
10681
+ collectionNoteCounts.set(collectionPath, (collectionNoteCounts.get(collectionPath) ?? 0) + 1);
10682
+ }
10683
+ }
10684
+ const collections = collectionPaths.map((path) => {
10685
+ const segments = path.split("/");
10686
+ const parentPath = segments.slice(0, -1).join("/");
10687
+ return {
10688
+ id: collectionIds.get(path),
10689
+ name: segments.at(-1),
10690
+ description: null,
10691
+ parent_id: parentPath ? collectionIds.get(parentPath) : null,
10692
+ created_at: createdAt2,
10693
+ note_count: collectionNoteCounts.get(path) ?? 0
10694
+ };
10695
+ });
9860
10696
  const notes = index.items.map((record) => {
9861
10697
  const content = aiwgRecordContent(record);
10698
+ const recordCollectionPaths = aiwgRecordCollectionPaths(record);
10699
+ const collectionPath = recordCollectionPaths.at(-1);
9862
10700
  return {
9863
10701
  id: noteIds.get(record.id),
9864
10702
  title: aiwgRecordTitle(record),
9865
10703
  original_content: content,
9866
10704
  revised_content: content,
9867
10705
  metadata: aiwgShardMetadata(index, record),
9868
- collection_id: null,
10706
+ collection_id: collectionPath ? collectionIds.get(collectionPath) : null,
9869
10707
  attachments: [],
9870
10708
  format: "markdown",
9871
10709
  source: "aiwg-index",
@@ -9874,7 +10712,7 @@ ${validation.errors.join("\n")}`);
9874
10712
  tags: [...new Set(record.tags)].sort(),
9875
10713
  created_at: aiwgShardTimestamp(record.source.updated_at ?? record.updated_at, createdAt2),
9876
10714
  updated_at: aiwgShardTimestamp(record.updated_at, createdAt2),
9877
- deleted_at: null
10715
+ deleted_at: record.state_transfer?.deleted_at ?? null
9878
10716
  };
9879
10717
  });
9880
10718
  const links = [];
@@ -9903,21 +10741,22 @@ ${validation.errors.join("\n")}`);
9903
10741
  }
9904
10742
  }
9905
10743
  const files = /* @__PURE__ */ new Map();
9906
- const components = ["notes", "tags"];
10744
+ const components = ["notes"];
9907
10745
  const counts = {
9908
- notes: notes.length,
9909
- tags: [...new Set(notes.flatMap((note) => note.tags))].length
10746
+ notes: notes.length
9910
10747
  };
9911
10748
  files.set("notes.jsonl", encodeJsonLines(notes));
9912
- files.set("tags.json", shardEncoder.encode(JSON.stringify(
9913
- [...new Set(notes.flatMap((note) => note.tags))].sort().map((name) => ({ name, created_at: createdAt2 }))
9914
- )));
9915
10749
  const addComponent = (component, filename, values, jsonLines = true) => {
9916
10750
  if (values.length === 0) return;
9917
10751
  components.push(component);
9918
10752
  counts[component] = values.length;
9919
10753
  files.set(filename, jsonLines ? encodeJsonLines(values) : shardEncoder.encode(JSON.stringify(values)));
9920
10754
  };
10755
+ addComponent("collections", "collections.json", collections, false);
10756
+ const tags = [...new Set(notes.flatMap((note) => note.tags))].sort().map((name) => ({ name, created_at: createdAt2 }));
10757
+ components.push("tags");
10758
+ counts.tags = tags.length;
10759
+ files.set("tags.json", shardEncoder.encode(JSON.stringify(tags)));
9921
10760
  addComponent("links", "links.jsonl", links);
9922
10761
  const checksums = {};
9923
10762
  for (const [filename, bytes] of files) checksums[filename] = await sha256Hex(bytes);
@@ -9956,11 +10795,7 @@ ${validation.errors.join("\n")}`);
9956
10795
  return packTarGz(files);
9957
10796
  }
9958
10797
  async function aiwgFortemiIndexToKnowledgeShardWithReport(index, options = {}) {
9959
- const validation = validateAiwgFortemiIndexExport(index);
9960
- if (!validation.valid) {
9961
- throw new Error(`Invalid AIWG Fortemi index export:
9962
- ${validation.errors.join("\n")}`);
9963
- }
10798
+ validateAiwgIndexForShard(index);
9964
10799
  if (index.schema_version !== "aiwg.fortemi.index.export.v2") {
9965
10800
  throw new Error("Full-v1 Knowledge Shard conversion requires aiwg.fortemi.index.export.v2");
9966
10801
  }