@sprig-and-prose/sprig-universe 0.1.1 → 0.3.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.
@@ -5,6 +5,18 @@ universe Amaranthine {
5
5
  progress your character.
6
6
  }
7
7
 
8
+ repository AmaranthineRepo {
9
+ url { 'https://example.com/amaranthine' }
10
+ }
11
+
12
+ reference PlayerRouter in AmaranthineRepo {
13
+ paths { '/backends/api/src/routers/players.js' }
14
+ }
15
+
16
+ reference ItemData in AmaranthineRepo {
17
+ paths { '/data/items/*.yaml' }
18
+ }
19
+
8
20
  series Player {
9
21
  describe {
10
22
  The player is central to everything. From chat to skills to inventory,
@@ -12,10 +24,7 @@ universe Amaranthine {
12
24
  }
13
25
 
14
26
  references {
15
- reference {
16
- repository { 'amaranthine' }
17
- paths { '/backends/api/src/routers/players.js' }
18
- }
27
+ PlayerRouter
19
28
  }
20
29
  }
21
30
 
@@ -25,10 +34,7 @@ universe Amaranthine {
25
34
  }
26
35
 
27
36
  references {
28
- reference {
29
- repository { 'amaranthine' }
30
- paths { '/data/items/*.yaml' }
31
- }
37
+ ItemData
32
38
  }
33
39
  }
34
40
 
@@ -1,4 +1,12 @@
1
1
  universe Amaranthine {
2
+ repository AmaranthineRepo {
3
+ url { 'https://example.com/amaranthine' }
4
+ }
5
+
6
+ reference ItemsData in AmaranthineRepo {
7
+ paths { '/data/items.yaml' }
8
+ }
9
+
2
10
  series Items {
3
11
  describe {
4
12
  Items are objects that can exist in the world.
@@ -6,10 +14,7 @@ universe Amaranthine {
6
14
  }
7
15
 
8
16
  references {
9
- reference {
10
- repository { 'amaranthine' }
11
- paths { '/data/items.yaml' }
12
- }
17
+ ItemsData
13
18
  }
14
19
  }
15
20
 
@@ -1,4 +1,8 @@
1
1
  universe Amaranthine {
2
+ reference SkillsData in AmaranthineRepo {
3
+ paths { '/data/skills.yaml' }
4
+ }
5
+
2
6
  series Skills {
3
7
  describe {
4
8
  Skills represent abilities that players can learn.
@@ -6,10 +10,7 @@ universe Amaranthine {
6
10
  }
7
11
 
8
12
  references {
9
- reference {
10
- repository { 'amaranthine' }
11
- paths { '/data/skills.yaml' }
12
- }
13
+ SkillsData
13
14
  }
14
15
  }
15
16
 
@@ -1,11 +1,13 @@
1
1
  universe Test {
2
- reference ItemRouter {
3
- repository { 'amaranthine-backend' }
2
+ repository BackendRepo {
3
+ url { 'https://example.com/backend' }
4
+ }
5
+
6
+ reference ItemRouter in BackendRepo {
4
7
  paths { '/src/routers/items.ts' }
5
8
  }
6
9
 
7
- reference ItemRouter {
8
- repository { 'amaranthine-backend' }
10
+ reference ItemRouter in BackendRepo {
9
11
  paths { '/src/routers/items-v2.ts' }
10
12
  }
11
13
 
@@ -0,0 +1,19 @@
1
+ universe Test {
2
+ repository Repo {
3
+ url { 'https://example.com/base' }
4
+ }
5
+
6
+ reference Alpha in Repo {
7
+ paths { '/alpha' }
8
+ }
9
+
10
+ reference Beta in Repo {
11
+ paths { '/beta' }
12
+ }
13
+
14
+ concept Widget {
15
+ references { Alpha Beta }
16
+ }
17
+ }
18
+
19
+
@@ -0,0 +1,15 @@
1
+ universe Test {
2
+ repository Repo {
3
+ url { 'https://example.com' }
4
+ }
5
+
6
+ reference PathsRef in Repo {
7
+ paths { '/a', '/b', '/c' }
8
+ }
9
+
10
+ series Things {
11
+ references { PathsRef, }
12
+ }
13
+ }
14
+
15
+
@@ -0,0 +1,14 @@
1
+ universe Test {
2
+ repository Repo {
3
+ url { 'https://example.com/base' }
4
+ }
5
+
6
+ concept Widget {
7
+ reference in Repo {
8
+ paths { '/widgets/loader.js' }
9
+ kind { 'data loader' }
10
+ }
11
+ }
12
+ }
13
+
14
+
@@ -0,0 +1,9 @@
1
+ universe Test {
2
+ reference ConfluenceDoc {
3
+ url { 'https://example.com/docs/confluence' }
4
+ kind { 'documentation' }
5
+ title { 'Confluence Doc' }
6
+ }
7
+ }
8
+
9
+
@@ -0,0 +1,11 @@
1
+ universe Test {
2
+ repository ExternalRepo {
3
+ url { 'https://example.com/base/' }
4
+ }
5
+
6
+ reference DataFiles in ExternalRepo {
7
+ paths { '/data/items' 'data/other' }
8
+ }
9
+ }
10
+
11
+
@@ -0,0 +1,7 @@
1
+ universe Test {
2
+ concept Widget {
3
+ references { MissingRef }
4
+ }
5
+ }
6
+
7
+
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @fileoverview Tests for repositories + references
3
+ */
4
+
5
+ import { test } from 'node:test';
6
+ import { readFileSync } from 'fs';
7
+ import { join } from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname } from 'path';
10
+ import { parseFiles } from '../src/index.js';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ const fixturesDir = join(__dirname, 'fixtures');
15
+
16
+ function loadGraph(fixtureName) {
17
+ const file = join(fixturesDir, fixtureName);
18
+ const text = readFileSync(file, 'utf-8');
19
+ return parseFiles([{ file, text }]);
20
+ }
21
+
22
+ test('raw url reference parses with computed urls', () => {
23
+ const graph = loadGraph('reference-raw-url.prose');
24
+ const ref = graph.references['Test:reference:ConfluenceDoc'];
25
+ assert(ref !== undefined, 'Reference should exist');
26
+ assert(ref.urls.length === 1, 'Reference should have one URL');
27
+ assert(
28
+ ref.urls[0] === 'https://example.com/docs/confluence',
29
+ 'Reference should preserve raw URL',
30
+ );
31
+ });
32
+
33
+ test('repository references compute urls with join rules', () => {
34
+ const graph = loadGraph('reference-repo-paths.prose');
35
+ const ref = graph.references['Test:reference:DataFiles'];
36
+ assert(ref !== undefined, 'Reference should exist');
37
+ assert(ref.urls.length === 2, 'Reference should have two URLs');
38
+ assert(
39
+ ref.urls[0] === 'https://example.com/base/data/items',
40
+ 'Reference should join base + leading-slash path',
41
+ );
42
+ assert(
43
+ ref.urls[1] === 'https://example.com/base/data/other',
44
+ 'Reference should join base + non-leading path',
45
+ );
46
+ });
47
+
48
+ test('commas are ignored in paths and references lists', () => {
49
+ const graph = loadGraph('reference-commas.prose');
50
+ const ref = graph.references['Test:reference:PathsRef'];
51
+ assert(ref !== undefined, 'Reference should exist');
52
+ assert(ref.paths.length === 3, 'Paths list should contain 3 entries');
53
+
54
+ const seriesNode = graph.nodes['Test:series:Things'];
55
+ assert(seriesNode !== undefined, 'Series node should exist');
56
+ assert(seriesNode.references?.length === 1, 'Series should have one reference attached');
57
+ assert(seriesNode.references[0] === 'Test:reference:PathsRef', 'Reference ID should resolve');
58
+ });
59
+
60
+ test('references list attaches to concepts and resolves', () => {
61
+ const graph = loadGraph('reference-attachments.prose');
62
+ const conceptNode = graph.nodes['Test:concept:Widget'];
63
+ assert(conceptNode !== undefined, 'Concept node should exist');
64
+ assert(conceptNode.references?.length === 2, 'Concept should have two references');
65
+ assert(
66
+ conceptNode.references.includes('Test:reference:Alpha'),
67
+ 'Concept should include Alpha reference',
68
+ );
69
+ assert(
70
+ conceptNode.references.includes('Test:reference:Beta'),
71
+ 'Concept should include Beta reference',
72
+ );
73
+ });
74
+
75
+ test('unknown reference names emit a calm error but do not crash', () => {
76
+ const graph = loadGraph('reference-unknown.prose');
77
+ const errors = graph.diagnostics.filter((d) => d.severity === 'error');
78
+ const unknownErrors = errors.filter((e) => e.message.includes('Unknown reference'));
79
+ assert(unknownErrors.length > 0, 'Should have error for unknown reference');
80
+ assert(graph.universes.Test !== undefined, 'Graph should still be constructed');
81
+ });
82
+
83
+ test('inline reference in block attaches with derived name', () => {
84
+ const graph = loadGraph('reference-inline.prose');
85
+ const conceptNode = graph.nodes['Test:concept:Widget'];
86
+ assert(conceptNode !== undefined, 'Concept node should exist');
87
+ assert(conceptNode.references?.length === 1, 'Concept should have one reference');
88
+ const refId = conceptNode.references[0];
89
+ const ref = graph.references[refId];
90
+ assert(ref !== undefined, 'Reference should exist');
91
+ assert(ref.urls.length === 1, 'Reference should have one URL');
92
+ });
93
+
94
+ /**
95
+ * Simple assertion helper
96
+ * @param {boolean} condition
97
+ * @param {string} message
98
+ */
99
+ function assert(condition, message) {
100
+ if (!condition) {
101
+ throw new Error(message);
102
+ }
103
+ }
104
+
105
+
@@ -73,15 +73,19 @@ test('parses amaranthine-mini.prose', () => {
73
73
  'Normalized field should be a string',
74
74
  );
75
75
 
76
- // Check references block (parsed correctly, not as unknown)
76
+ // Check references list
77
77
  assert(playerNode.references !== undefined, 'Player should have references');
78
78
  assert(playerNode.references.length > 0, 'Player should have at least one reference');
79
- const ref = playerNode.references[0];
80
- assert(ref !== undefined, 'Player should have a reference');
81
- assert(ref.repository === 'amaranthine', 'Reference should have repository');
82
- assert(ref.paths !== undefined, 'Reference should have paths');
83
- assert(ref.paths.length > 0, 'Reference should have at least one path');
84
- assert(ref.source !== undefined, 'Reference should have source location');
79
+ const refId = playerNode.references[0];
80
+ assert(refId !== undefined, 'Player should have a reference id');
81
+ const ref = graph.references[refId];
82
+ assert(ref !== undefined, 'Reference should exist in graph');
83
+ assert(ref.name === 'PlayerRouter', 'Reference should have correct name');
84
+ assert(ref.urls.length === 1, 'Reference should have one URL');
85
+ assert(
86
+ ref.urls[0] === 'https://example.com/amaranthine/backends/api/src/routers/players.js',
87
+ 'Reference should have correct computed URL',
88
+ );
85
89
 
86
90
  // Check source spans
87
91
  assert(universeNode.source !== undefined, 'Universe node should have source span');
@@ -428,6 +432,9 @@ test('merges multiple files with same universe name', () => {
428
432
  // References should be merged (both files had references)
429
433
  assert(universeNode.references !== undefined, 'Universe should have references');
430
434
  assert(universeNode.references.length === 2, 'Universe should have 2 references (one from each file)');
435
+ for (const refId of universeNode.references) {
436
+ assert(graph.references[refId] !== undefined, 'Reference id should resolve');
437
+ }
431
438
 
432
439
  // No errors should be present
433
440
  const errors = graph.diagnostics.filter((d) => d.severity === 'error');
@@ -517,8 +524,10 @@ test('preserves source locations for merged content', () => {
517
524
  assert(universeNode.references !== undefined, 'Universe should have references');
518
525
  assert(universeNode.references.length === 2, 'Should have 2 references');
519
526
 
520
- // Check that references have source locations
521
- for (const ref of universeNode.references) {
527
+ // Check that reference models have source locations
528
+ for (const refId of universeNode.references) {
529
+ const ref = graph.references[refId];
530
+ assert(ref !== undefined, 'Reference should exist in graph');
522
531
  assert(ref.source !== undefined, 'Reference should have source location');
523
532
  assert(ref.source.file !== undefined, 'Reference source should have file path');
524
533
  }
@@ -576,41 +585,6 @@ test('deterministic output order', () => {
576
585
  );
577
586
  });
578
587
 
579
- test('parses named reference blocks at universe scope', () => {
580
- const file = join(fixturesDir, 'named-reference.prose');
581
- const text = readFileSync(file, 'utf-8');
582
- const graph = parseFiles([{ file, text }]);
583
-
584
- // Check universe exists
585
- assert(graph.universes.Test !== undefined, 'Universe Test should exist');
586
-
587
- const universeNodeId = graph.universes.Test.root;
588
- const universeNode = graph.nodes[universeNodeId];
589
- assert(universeNode !== undefined, 'Universe node should exist');
590
-
591
- // Check named reference exists in registry
592
- assert(graph.referencesByName !== undefined, 'referencesByName registry should exist');
593
- assert(graph.referencesByName.Test !== undefined, 'Universe Test should have referencesByName');
594
- assert(graph.referencesByName.Test.ItemRouter !== undefined, 'ItemRouter named reference should exist');
595
-
596
- const namedRef = graph.referencesByName.Test.ItemRouter;
597
- assert(namedRef.name === 'ItemRouter', 'Named reference should have correct name');
598
- assert(namedRef.repository === 'amaranthine-backend', 'Named reference should have repository');
599
- assert(namedRef.paths.length === 1, 'Named reference should have paths');
600
- assert(namedRef.paths[0] === '/src/routers/items.ts', 'Named reference should have correct path');
601
- assert(namedRef.describe !== undefined, 'Named reference should have describe');
602
- assert(namedRef.describe.raw.includes('Routes that implement item endpoints'), 'Named reference describe should contain expected text');
603
- assert(namedRef.source !== undefined, 'Named reference should have source location');
604
-
605
- // Check that inline reference still works
606
- const itemsNodeId = `${universeNodeId.split(':')[0]}:series:Items`;
607
- const itemsNode = graph.nodes[itemsNodeId];
608
- assert(itemsNode !== undefined, 'Items series should exist');
609
- assert(itemsNode.references !== undefined, 'Items series should have references');
610
- assert(itemsNode.references.length === 1, 'Items series should have one inline reference');
611
- assert(itemsNode.references[0].repository === 'amaranthine-backend', 'Inline reference should have repository');
612
- });
613
-
614
588
  test('parses named document blocks at universe scope', () => {
615
589
  const file = join(fixturesDir, 'named-document.prose');
616
590
  const text = readFileSync(file, 'utf-8');
@@ -643,43 +617,24 @@ test('parses named document blocks at universe scope', () => {
643
617
  assert(itemsNode.documentation[0].kind === 'internal', 'Inline document should have kind');
644
618
  });
645
619
 
646
- test('detects duplicate named reference and document names', () => {
620
+ test('detects duplicate named document names', () => {
647
621
  const file = join(fixturesDir, 'named-duplicate.prose');
648
622
  const text = readFileSync(file, 'utf-8');
649
623
  const graph = parseFiles([{ file, text }]);
650
624
 
651
- // Should have errors for duplicate names (2 errors per duplicate: one for duplicate, one pointing to first)
625
+ // Should have errors for duplicate named documents (2 errors per duplicate: one for duplicate, one pointing to first)
652
626
  const errors = graph.diagnostics.filter((d) => d.severity === 'error');
653
- const duplicateRefErrors = errors.filter((e) => e.message.includes('Duplicate named reference') || e.message.includes('First declaration of named reference'));
654
627
  const duplicateDocErrors = errors.filter((e) => e.message.includes('Duplicate named document') || e.message.includes('First declaration of named document'));
655
628
 
656
- assert(duplicateRefErrors.length >= 2, `Should have errors for duplicate named reference (duplicate + first declaration), got ${duplicateRefErrors.length}: ${duplicateRefErrors.map(e => e.message).join('; ')}`);
657
629
  assert(duplicateDocErrors.length >= 2, `Should have errors for duplicate named document (duplicate + first declaration), got ${duplicateDocErrors.length}: ${duplicateDocErrors.map(e => e.message).join('; ')}`);
658
630
 
659
- // Check that error messages include both locations
660
- const refError = duplicateRefErrors.find((e) => e.message.includes('ItemRouter') && e.message.includes('Duplicate'));
661
- assert(refError !== undefined, 'Should have error for duplicate ItemRouter');
662
- assert(refError.source !== undefined, 'Error should have source location');
663
- assert(refError.message.includes('First declared at'), 'Error should mention first declaration location');
664
-
665
631
  const docError = duplicateDocErrors.find((e) => e.message.includes('ItemsDesignDoc') && e.message.includes('Duplicate'));
666
632
  assert(docError !== undefined, 'Should have error for duplicate ItemsDesignDoc');
667
633
  assert(docError.source !== undefined, 'Error should have source location');
668
634
  assert(docError.message.includes('First declared at'), 'Error should mention first declaration location');
669
635
  });
670
636
 
671
- test('preserves source locations for named blocks', () => {
672
- const file = join(fixturesDir, 'named-reference.prose');
673
- const text = readFileSync(file, 'utf-8');
674
- const graph = parseFiles([{ file, text }]);
675
-
676
- const namedRef = graph.referencesByName.Test.ItemRouter;
677
- assert(namedRef.source !== undefined, 'Named reference should have source');
678
- assert(namedRef.source.file === file, 'Named reference source should reference correct file');
679
- assert(namedRef.source.start !== undefined, 'Named reference source should have start');
680
- assert(namedRef.source.end !== undefined, 'Named reference source should have end');
681
- assert(namedRef.source.start.offset < namedRef.source.end.offset, 'Source offsets should be monotonic');
682
-
637
+ test('preserves source locations for named documents', () => {
683
638
  const file2 = join(fixturesDir, 'named-document.prose');
684
639
  const text2 = readFileSync(file2, 'utf-8');
685
640
  const graph2 = parseFiles([{ file: file2, text: text2 }]);
@@ -691,106 +646,6 @@ test('preserves source locations for named blocks', () => {
691
646
  assert(namedDoc.source.end !== undefined, 'Named document source should have end');
692
647
  });
693
648
 
694
- test('parses using blocks in references', () => {
695
- const file = join(fixturesDir, 'using-in-references.prose');
696
- const text = readFileSync(file, 'utf-8');
697
- const graph = parseFiles([{ file, text }]);
698
-
699
- // Check universe exists
700
- assert(graph.universes.Test !== undefined, 'Universe Test should exist');
701
-
702
- const universeNodeId = graph.universes.Test.root;
703
-
704
- // Check that named references exist
705
- assert(graph.referencesByName.Test.ItemRouter !== undefined, 'ItemRouter named reference should exist');
706
- assert(graph.referencesByName.Test.PlayerRouter !== undefined, 'PlayerRouter named reference should exist');
707
-
708
- // Check Items series has using block resolved
709
- const itemsNodeId = `${universeNodeId.split(':')[0]}:series:Items`;
710
- const itemsNode = graph.nodes[itemsNodeId];
711
- assert(itemsNode !== undefined, 'Items series should exist');
712
- assert(itemsNode.references !== undefined, 'Items series should have references');
713
- assert(itemsNode.references.length === 1, 'Items series should have one reference from using');
714
- assert(itemsNode.references[0].repository === 'amaranthine-backend', 'Resolved reference should have repository');
715
- assert(itemsNode.references[0].paths[0] === '/src/routers/items.ts', 'Resolved reference should have correct path');
716
- assert(itemsNode.references[0].describe !== undefined, 'Resolved reference should have describe');
717
- });
718
-
719
- test('parses mixed inline and using blocks in references', () => {
720
- const file = join(fixturesDir, 'using-in-references.prose');
721
- const text = readFileSync(file, 'utf-8');
722
- const graph = parseFiles([{ file, text }]);
723
-
724
- const universeNodeId = graph.universes.Test.root;
725
-
726
- // Check Players series has both inline and using references
727
- const playersNodeId = `${universeNodeId.split(':')[0]}:series:Players`;
728
- const playersNode = graph.nodes[playersNodeId];
729
- assert(playersNode !== undefined, 'Players series should exist');
730
- assert(playersNode.references !== undefined, 'Players series should have references');
731
- assert(playersNode.references.length === 2, 'Players series should have 2 references (inline + using)');
732
-
733
- // First should be inline reference
734
- assert(playersNode.references[0].repository === 'amaranthine-backend', 'First reference should have repository');
735
- assert(playersNode.references[0].paths[0] === '/src/players/helpers.ts', 'First reference should be inline');
736
-
737
- // Second should be resolved from using
738
- assert(playersNode.references[1].repository === 'amaranthine-backend', 'Second reference should have repository');
739
- assert(playersNode.references[1].paths[0] === '/src/routers/players.ts', 'Second reference should be from using');
740
- });
741
-
742
- test('parses multiple names in using block', () => {
743
- const file = join(fixturesDir, 'using-in-references.prose');
744
- const text = readFileSync(file, 'utf-8');
745
- const graph = parseFiles([{ file, text }]);
746
-
747
- const universeNodeId = graph.universes.Test.root;
748
-
749
- // Check Mixed series has multiple references from single using block
750
- const mixedNodeId = `${universeNodeId.split(':')[0]}:series:Mixed`;
751
- const mixedNode = graph.nodes[mixedNodeId];
752
- assert(mixedNode !== undefined, 'Mixed series should exist');
753
- assert(mixedNode.references !== undefined, 'Mixed series should have references');
754
- assert(mixedNode.references.length === 2, 'Mixed series should have 2 references from using block');
755
-
756
- // Both should be resolved
757
- assert(mixedNode.references[0].paths[0] === '/src/routers/items.ts', 'First reference should be ItemRouter');
758
- assert(mixedNode.references[1].paths[0] === '/src/routers/players.ts', 'Second reference should be PlayerRouter');
759
- });
760
-
761
- test('errors on unknown reference name in using block', () => {
762
- const file = join(fixturesDir, 'using-unknown.prose');
763
- const text = readFileSync(file, 'utf-8');
764
- const graph = parseFiles([{ file, text }]);
765
-
766
- // Should have error for unknown reference
767
- const errors = graph.diagnostics.filter((d) => d.severity === 'error');
768
- const unknownRefErrors = errors.filter((e) => e.message.includes('Unknown reference'));
769
-
770
- assert(unknownRefErrors.length > 0, 'Should have error for unknown reference');
771
- const error = unknownRefErrors.find((e) => e.message.includes('UnknownRouter'));
772
- assert(error !== undefined, 'Should have error for UnknownRouter');
773
- assert(error.source !== undefined, 'Error should have source location');
774
- assert(error.message.includes("Unknown reference 'UnknownRouter'"), 'Error message should mention UnknownRouter');
775
- });
776
-
777
- test('inline references still work unchanged', () => {
778
- const file = join(fixturesDir, 'amaranthine-mini.prose');
779
- const text = readFileSync(file, 'utf-8');
780
- const graph = parseFiles([{ file, text }]);
781
-
782
- const universeNodeId = graph.universes.Amaranthine.root;
783
-
784
- // Check Player series has inline references (baseline)
785
- const playerNodeId = `${universeNodeId.split(':')[0]}:series:Player`;
786
- const playerNode = graph.nodes[playerNodeId];
787
- assert(playerNode !== undefined, 'Player series should exist');
788
- assert(playerNode.references !== undefined, 'Player should have references');
789
- assert(playerNode.references.length === 1, 'Player should have one inline reference');
790
- assert(playerNode.references[0].repository === 'amaranthine', 'Inline reference should have repository');
791
- assert(playerNode.references[0].paths[0] === '/backends/api/src/routers/players.js', 'Inline reference should have path');
792
- });
793
-
794
649
  /**
795
650
  * Simple assertion helper
796
651
  * @param {boolean} condition
@@ -1,29 +0,0 @@
1
- /**
2
- * @fileoverview GitHub repository handler for Sprig
3
- *
4
- * This repository handler provides functionality for working with GitHub repositories.
5
- * It can be extended to support cloning, fetching, and other GitHub-specific operations.
6
- */
7
-
8
- /**
9
- * Repository handler interface
10
- * @typedef {Object} RepositoryHandler
11
- * @property {string} kind - Repository kind identifier
12
- * @property {Function} [clone] - Clone repository function (optional)
13
- * @property {Function} [fetch] - Fetch repository function (optional)
14
- */
15
-
16
- /**
17
- * GitHub repository handler
18
- * @param {Record<string, string | number>} options - Repository options
19
- * @returns {RepositoryHandler}
20
- */
21
- export function createGitHubRepository(options) {
22
- return {
23
- kind: 'sprig-repository-github',
24
- // Future: Add clone, fetch, and other GitHub-specific operations here
25
- };
26
- }
27
-
28
- export default createGitHubRepository;
29
-