@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sprig-and-prose/sprig-universe",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "description": "Minimal universe parser for sprig",
6
6
  "main": "src/index.js",
package/src/ast.js CHANGED
@@ -23,37 +23,23 @@
23
23
  */
24
24
 
25
25
  /**
26
- * @typedef {Object} ReferenceBlock
26
+ * @typedef {Object} ReferenceDecl
27
27
  * @property {string} kind - Always 'reference'
28
- * @property {string} repository - Repository name (string literal)
29
- * @property {string[]} paths - Array of path strings (string literals)
30
- * @property {string} [referenceKind] - Optional reference kind (e.g., 'api', 'data')
28
+ * @property {string} [name] - Optional reference name (identifier)
29
+ * @property {string} [repositoryName] - Optional repository name from "in RepoName"
30
+ * @property {string} [url] - Optional raw URL (string literal)
31
+ * @property {string[]} [paths] - Optional array of path strings (string literals)
32
+ * @property {string} [referenceKind] - Optional reference kind (user-facing classification)
31
33
  * @property {DescribeBlock} [describe] - Optional describe block
32
- * @property {SourceSpan} source - Source span
33
- */
34
-
35
- /**
36
- * @typedef {Object} NamedReferenceBlock
37
- * @property {string} kind - Always 'named-reference'
38
- * @property {string} name - Reference name (identifier)
39
- * @property {string} repository - Repository name (string literal)
40
- * @property {string[]} paths - Array of path strings (string literals)
41
- * @property {string} [referenceKind] - Optional reference kind (e.g., 'api', 'data')
42
- * @property {DescribeBlock} [describe] - Optional describe block
43
- * @property {SourceSpan} source - Source span
44
- */
45
-
46
- /**
47
- * @typedef {Object} UsingInReferencesBlock
48
- * @property {string} kind - Always 'using-in-references'
49
- * @property {string[]} names - Array of identifier names to reference
34
+ * @property {NoteBlock} [note] - Optional note block
35
+ * @property {TitleBlock} [title] - Optional title block
50
36
  * @property {SourceSpan} source - Source span
51
37
  */
52
38
 
53
39
  /**
54
40
  * @typedef {Object} ReferencesBlock
55
41
  * @property {string} kind - Always 'references'
56
- * @property {Array<ReferenceBlock | UsingInReferencesBlock>} references - Array of reference blocks and using blocks
42
+ * @property {Array<{ name: string, source: SourceSpan }>} items - Array of identifier paths to references
57
43
  * @property {SourceSpan} source - Source span
58
44
  */
59
45
 
@@ -91,6 +77,13 @@
91
77
  * @property {SourceSpan} source - Source span
92
78
  */
93
79
 
80
+ /**
81
+ * @typedef {Object} NoteBlock
82
+ * @property {string} kind - Always 'note'
83
+ * @property {string} raw - Raw text content (without outer braces)
84
+ * @property {SourceSpan} source - Source span
85
+ */
86
+
94
87
  /**
95
88
  * @typedef {Object} TitleBlock
96
89
  * @property {string} kind - Always 'title'
@@ -102,8 +95,11 @@
102
95
  * @typedef {Object} RepositoryDecl
103
96
  * @property {string} kind - Always 'repository'
104
97
  * @property {string} name - Repository name (identifier)
105
- * @property {string | number} repositoryKind - Repository kind (string, identifier, or number)
106
- * @property {Record<string, string | number>} options - Repository options
98
+ * @property {string} [parentName] - Optional parent name (from "in ParentName")
99
+ * @property {string} [url] - Repository base URL (string literal)
100
+ * @property {DescribeBlock} [describe] - Optional describe block
101
+ * @property {NoteBlock} [note] - Optional note block
102
+ * @property {TitleBlock} [title] - Optional title block
107
103
  * @property {SourceSpan} source - Source span
108
104
  */
109
105
 
@@ -111,7 +107,7 @@
111
107
  * @typedef {Object} UniverseDecl
112
108
  * @property {string} kind - Always 'universe'
113
109
  * @property {string} name - Universe name
114
- * @property {Array<AnthologyDecl | SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | TitleBlock | NamedReferenceBlock | NamedDocumentBlock | RepositoryDecl | UnknownBlock>} body - Body declarations
110
+ * @property {Array<AnthologyDecl | SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | TitleBlock | RepositoryDecl | ReferenceDecl | NamedDocumentBlock | UnknownBlock>} body - Body declarations
115
111
  * @property {SourceSpan} source - Source span
116
112
  */
117
113
 
@@ -119,7 +115,8 @@
119
115
  * @typedef {Object} AnthologyDecl
120
116
  * @property {string} kind - Always 'anthology'
121
117
  * @property {string} name - Anthology name
122
- * @property {Array<DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | UnknownBlock>} body - Body declarations
118
+ * @property {string} [parentName] - Optional parent universe name (from "in UniverseName")
119
+ * @property {Array<SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | RepositoryDecl | ReferenceDecl | UnknownBlock>} body - Body declarations
123
120
  * @property {SourceSpan} source - Source span
124
121
  */
125
122
 
@@ -128,7 +125,7 @@
128
125
  * @property {string} kind - Always 'series'
129
126
  * @property {string} name - Series name
130
127
  * @property {string} [parentName] - Optional parent anthology name (from "in AnthologyName")
131
- * @property {Array<BookDecl | ChapterDecl | DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | UnknownBlock>} body - Body declarations
128
+ * @property {Array<BookDecl | ChapterDecl | DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | RepositoryDecl | ReferenceDecl | UnknownBlock>} body - Body declarations
132
129
  * @property {SourceSpan} source - Source span
133
130
  */
134
131
 
@@ -137,7 +134,7 @@
137
134
  * @property {string} kind - Always 'book'
138
135
  * @property {string} name - Book name
139
136
  * @property {string} parentName - Parent name (from "in ParentName")
140
- * @property {Array<ChapterDecl | DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | UnknownBlock>} body - Body declarations
137
+ * @property {Array<ChapterDecl | DescribeBlock | TitleBlock | ReferencesBlock | DocumentationBlock | RepositoryDecl | ReferenceDecl | UnknownBlock>} body - Body declarations
141
138
  * @property {SourceSpan} source - Source span
142
139
  */
143
140
 
@@ -146,7 +143,7 @@
146
143
  * @property {string} kind - Always 'chapter'
147
144
  * @property {string} name - Chapter name
148
145
  * @property {string} parentName - Parent name (from "in ParentName")
149
- * @property {Array<DescribeBlock | ReferencesBlock | DocumentationBlock | UnknownBlock>} body - Body declarations
146
+ * @property {Array<DescribeBlock | ReferencesBlock | DocumentationBlock | RepositoryDecl | ReferenceDecl | UnknownBlock>} body - Body declarations
150
147
  * @property {SourceSpan} source - Source span
151
148
  */
152
149
 
@@ -155,7 +152,7 @@
155
152
  * @property {string} kind - Always 'concept'
156
153
  * @property {string} name - Concept name
157
154
  * @property {string} [parentName] - Optional parent name (from "in ParentName")
158
- * @property {Array<DescribeBlock | ReferencesBlock | DocumentationBlock | UnknownBlock>} body - Body declarations
155
+ * @property {Array<DescribeBlock | ReferencesBlock | DocumentationBlock | RepositoryDecl | ReferenceDecl | UnknownBlock>} body - Body declarations
159
156
  * @property {SourceSpan} source - Source span
160
157
  */
161
158
 
@@ -244,7 +241,7 @@
244
241
  */
245
242
 
246
243
  /**
247
- * @typedef {UniverseDecl | AnthologyDecl | SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | TitleBlock | FromBlock | RelationshipsBlock | ReferencesBlock | ReferenceBlock | NamedReferenceBlock | DocumentationBlock | DocumentBlock | NamedDocumentBlock | RepositoryDecl | UnknownBlock | SceneDecl | UsingBlock | ActorDecl | TypeBlock | IdentityBlock | SourceBlock | TransformsBlock | TransformBlock} ASTNode
244
+ * @typedef {UniverseDecl | AnthologyDecl | SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | NoteBlock | TitleBlock | FromBlock | RelationshipsBlock | ReferencesBlock | ReferenceDecl | DocumentationBlock | DocumentBlock | NamedDocumentBlock | RepositoryDecl | UnknownBlock | SceneDecl | UsingBlock | ActorDecl | TypeBlock | IdentityBlock | SourceBlock | TransformsBlock | TransformBlock} ASTNode
248
245
  */
249
246
 
250
247
  /**
@@ -252,6 +249,7 @@
252
249
  * @property {string} file - File path
253
250
  * @property {UniverseDecl[]} universes - Top-level universe declarations
254
251
  * @property {SceneDecl[]} scenes - Top-level scene declarations
252
+ * @property {Array<AnthologyDecl | SeriesDecl | BookDecl | ChapterDecl | ConceptDecl | RelatesDecl | DescribeBlock | TitleBlock | RepositoryDecl | ReferenceDecl | ReferencesBlock | DocumentationBlock | NamedDocumentBlock | UnknownBlock>} [topLevelDecls] - Unscoped top-level declarations
255
253
  * @property {SourceSpan} [source] - File-level source span
256
254
  */
257
255
 
package/src/cli.js CHANGED
@@ -12,10 +12,9 @@ import { validateScenes } from './validator.js';
12
12
  import chokidar from 'chokidar';
13
13
  import { globSync } from 'glob';
14
14
 
15
- // Get the directory where this CLI script is located (sprig-universe package root)
15
+ // Get the directory where this CLI script is located
16
16
  const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = dirname(__filename);
18
- const PACKAGE_ROOT = resolve(__dirname, '..');
19
18
 
20
19
  const DEFAULT_EXCLUDES = ['.sprig/**', 'dist/**', 'node_modules/**', '.git/**'];
21
20
 
@@ -24,7 +23,6 @@ function printGlobalHelp() {
24
23
  console.log('Commands:');
25
24
  console.log(' compile Compile universe and scene files to manifests');
26
25
  console.log(' watch Watch universe and scene files and recompile on change');
27
- console.log(' check:references Validate repository references');
28
26
  console.log(' validate Validate scene file sources');
29
27
  console.log(' parse Parse files (legacy command)');
30
28
  console.log('');
@@ -79,21 +77,6 @@ function printValidateHelp() {
79
77
  console.log(' --quiet Suppress observable header output');
80
78
  }
81
79
 
82
- function printCheckReferencesHelp() {
83
- console.log('Usage: sprig-universe check:references [--root <path>] [--quiet]');
84
- console.log('');
85
- console.log('Validates repository references in the universe manifest.');
86
- console.log('');
87
- console.log('Discovery:');
88
- console.log(' - Walks upward from current directory (or --root) to find universe.prose');
89
- console.log(' - Loads all **/*.prose files under the discovered root');
90
- console.log(' - Excludes: .sprig/**, dist/**, node_modules/**, .git/**');
91
- console.log('');
92
- console.log('Options:');
93
- console.log(' --root <path> Override discovery start path (default: current directory)');
94
- console.log(' --quiet Suppress observable header output');
95
- }
96
-
97
80
  function printParseHelp() {
98
81
  console.log('Usage: sprig-universe parse <fileOrDir> [--out <path>]');
99
82
  console.log('');
@@ -253,90 +236,6 @@ function findSceneFiles(dir) {
253
236
  return files;
254
237
  }
255
238
 
256
- /**
257
- * Validate repository kinds exist in repositories directory
258
- * @param {Record<string, any>} repositories - Repository config
259
- * @param {string} universeRoot - Universe root directory (unused, kept for API compatibility)
260
- * @returns {boolean} - True if valid, false otherwise
261
- */
262
- function validateRepositoryKinds(repositories, universeRoot) {
263
- // Repositories are located in the sprig-universe package directory, not in the universe project
264
- const repositoriesDir = join(PACKAGE_ROOT, 'repositories');
265
- const errors = [];
266
-
267
- for (const [repoName, repoConfig] of Object.entries(repositories)) {
268
- const kind = repoConfig.kind;
269
- if (!kind) {
270
- errors.push(`Repository "${repoName}" has no kind specified`);
271
- continue;
272
- }
273
-
274
- const kindDir = join(repositoriesDir, kind);
275
- const indexFile = join(kindDir, 'index.js');
276
-
277
- if (!existsSync(indexFile)) {
278
- errors.push(`Repository kind "${kind}" not found. Expected: ${indexFile}`);
279
- }
280
- }
281
-
282
- if (errors.length > 0) {
283
- console.error('Error: Invalid repository configurations:');
284
- for (const error of errors) {
285
- console.error(` - ${error}`);
286
- }
287
- return false;
288
- }
289
-
290
- return true;
291
- }
292
-
293
- /**
294
- * Validate repository references
295
- * @param {any} graph - Parsed graph
296
- * @param {Record<string, any>} repositories - Repository config
297
- * @returns {boolean} - True if valid, false otherwise
298
- */
299
- function validateReferences(graph, repositories) {
300
- const repoKeys = new Set(Object.keys(repositories || {}));
301
- /** @type {Map<string, { count: number, examples: Array<{ file: string, line: number }> }>} */
302
- const unknownRepos = new Map();
303
-
304
- for (const node of Object.values(graph.nodes)) {
305
- if (!node.references) continue;
306
-
307
- for (const ref of node.references) {
308
- if (repoKeys.has(ref.repository)) continue;
309
-
310
- const source = ref.source;
311
- const entry = unknownRepos.get(ref.repository) || { count: 0, examples: [] };
312
- entry.count += 1;
313
-
314
- if (entry.examples.length < 3 && source?.file && source?.start?.line) {
315
- entry.examples.push({ file: source.file, line: source.start.line });
316
- }
317
-
318
- unknownRepos.set(ref.repository, entry);
319
- }
320
- }
321
-
322
- if (unknownRepos.size === 0) {
323
- return true;
324
- }
325
-
326
- console.error('Error: Reference(s) to unknown repositories:');
327
- for (const [repo, info] of unknownRepos.entries()) {
328
- const exampleText =
329
- info.examples.length > 0
330
- ? ` (e.g. ${info.examples
331
- .map((ex) => `${ex.file}:${ex.line}`)
332
- .join(', ')})`
333
- : '';
334
- console.error(` - ${repo}: ${info.count} occurrence(s)${exampleText}`);
335
- }
336
-
337
- return false;
338
- }
339
-
340
239
  /**
341
240
  * Main CLI entry point
342
241
  */
@@ -364,10 +263,6 @@ function main() {
364
263
  printValidateHelp();
365
264
  return;
366
265
  }
367
- if (command === 'check:references') {
368
- printCheckReferencesHelp();
369
- return;
370
- }
371
266
  if (command === 'parse') {
372
267
  printParseHelp();
373
268
  return;
@@ -379,11 +274,6 @@ function main() {
379
274
  return;
380
275
  }
381
276
 
382
- if (command === 'check:references') {
383
- handleCheckReferences(commandArgs);
384
- return;
385
- }
386
-
387
277
  if (command === 'validate' || command === 'check') {
388
278
  handleValidate(commandArgs);
389
279
  return;
@@ -630,16 +520,6 @@ function compileUniverseWithFiles(universeRoot, files, silent = false) {
630
520
  return false;
631
521
  }
632
522
 
633
- // Validate repository kinds exist
634
- if (!validateRepositoryKinds(graph.repositories || {}, universeRoot)) {
635
- return false;
636
- }
637
-
638
- // Validate references
639
- if (!validateReferences(graph, graph.repositories || {})) {
640
- return false;
641
- }
642
-
643
523
  // Get series names for status message
644
524
  const seriesNames = Object.values(graph.nodes)
645
525
  .filter((node) => node.kind === 'series')
@@ -715,7 +595,6 @@ function compileUniverseWithFiles(universeRoot, files, silent = false) {
715
595
  */
716
596
  function compileUniverse(config, configPath, silent = false) {
717
597
  const universeConfig = config.universe;
718
- const repositories = config.repositories || {};
719
598
 
720
599
  if (!universeConfig) {
721
600
  console.error('Error: Missing "universe" section in config');
@@ -756,11 +635,6 @@ function compileUniverse(config, configPath, silent = false) {
756
635
  try {
757
636
  const graph = parseFiles(fileContents);
758
637
 
759
- // Validate references
760
- if (!validateReferences(graph, repositories)) {
761
- return false;
762
- }
763
-
764
638
  // Get series names for status message
765
639
  const seriesNames = Object.values(graph.nodes)
766
640
  .filter((node) => node.kind === 'series')
@@ -770,7 +644,6 @@ function compileUniverse(config, configPath, silent = false) {
770
644
  // Add repositories and metadata to manifest
771
645
  const manifest = {
772
646
  ...graph,
773
- repositories,
774
647
  generatedAt: new Date().toISOString(),
775
648
  };
776
649
 
@@ -1358,88 +1231,6 @@ function handleWatch(args) {
1358
1231
  });
1359
1232
  }
1360
1233
 
1361
- /**
1362
- * Handle check:references command
1363
- * @param {string[]} args
1364
- */
1365
- function handleCheckReferences(args) {
1366
- const quiet = hasQuietFlag(args);
1367
- const rootStart = resolveRootPath(args);
1368
-
1369
- // Discover universe root
1370
- const universeRoot = discoverUniverseRoot(rootStart);
1371
- if (!universeRoot) {
1372
- console.error(`Error: Could not find universe.prose marker. Searched upward from: ${rootStart}`);
1373
- process.exit(1);
1374
- }
1375
-
1376
- // Load prose files using new discovery mechanism
1377
- const proseFiles = loadProseFiles(universeRoot);
1378
- if (proseFiles.length === 0) {
1379
- console.error(`Error: No .prose files found under root: ${universeRoot}`);
1380
- process.exit(1);
1381
- }
1382
-
1383
- // Parse files to get universe info for header
1384
- const fileContents = proseFiles.map((file) => ({
1385
- file,
1386
- text: readFileSync(file, 'utf-8'),
1387
- }));
1388
-
1389
- let graph;
1390
- try {
1391
- graph = parseFiles(fileContents);
1392
- } catch (error) {
1393
- console.error(`Error parsing files: ${error.message}`);
1394
- process.exit(1);
1395
- }
1396
-
1397
- // Validate exactly one universe
1398
- const validation = validateUniverseCount(graph);
1399
- if (!validation.valid) {
1400
- console.error(`Error: ${validation.error}`);
1401
- process.exit(1);
1402
- }
1403
-
1404
- // Print observable header unless quiet
1405
- if (!quiet) {
1406
- printObservableHeader(validation.universeName, universeRoot, proseFiles.length);
1407
- }
1408
-
1409
- // Validate repository kinds exist
1410
- if (!validateRepositoryKinds(graph.repositories || {}, universeRoot)) {
1411
- console.error('❌ Repository kind validation failed');
1412
- process.exit(1);
1413
- }
1414
-
1415
- // Validate references
1416
- if (!validateReferences(graph, graph.repositories || {})) {
1417
- console.error('❌ Reference validation failed');
1418
- process.exit(1);
1419
- }
1420
-
1421
- // Check for parsing errors
1422
- const hasErrors = graph.diagnostics.some((d) => d.severity === 'error');
1423
- if (hasErrors) {
1424
- process.exit(1);
1425
- }
1426
-
1427
- // Display warnings
1428
- const warnings = graph.diagnostics.filter((d) => d.severity === 'warning');
1429
- if (warnings.length > 0) {
1430
- console.warn(`\n⚠️ ${warnings.length} warning(s):`);
1431
- for (const warning of warnings) {
1432
- const source = warning.source
1433
- ? `${warning.source.file}:${warning.source.start.line}:${warning.source.start.col}`
1434
- : 'unknown location';
1435
- console.warn(` ${source}: ${warning.message}`);
1436
- }
1437
- console.warn('');
1438
- }
1439
-
1440
- console.log('✅ All references are valid');
1441
- }
1442
-
1443
1234
  /**
1444
1235
  * Handle parse command (legacy)
1445
1236
  * @param {string[]} args