spec-up-t 1.5.0 → 1.6.0-beta.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/src/init.js CHANGED
@@ -8,14 +8,14 @@ async function initialize() {
8
8
  try {
9
9
  // Ensure the .cache directory exists
10
10
  await fs.ensureDir(outputDir);
11
-
11
+
12
12
  // Check if the init script has already run
13
13
  if (await fs.pathExists(initFlagPath)) {
14
14
  return;
15
15
  }
16
16
 
17
17
  // Place the init script here
18
-
18
+
19
19
  // End of the init script
20
20
 
21
21
  // Create the init flag file
@@ -23,7 +23,11 @@ async function initialize() {
23
23
 
24
24
  Logger.success('Initialization complete.');
25
25
  } catch (error) {
26
- Logger.error(`Initialization failed: ${error.message}`);
26
+ Logger.error('Initialization failed', {
27
+ context: 'Failed to set up spec-up-t boilerplate files',
28
+ hint: 'Ensure you have write permissions in the current directory. Try running with appropriate permissions or in a different directory',
29
+ details: error.message
30
+ });
27
31
  }
28
32
  }
29
33
 
@@ -33,7 +33,7 @@ function extractCurrentFile(token, globalState) {
33
33
  * @param {Object} config - Configuration object containing specs and settings
34
34
  * @param {Object} globalState - Global state object containing definitions, references, etc.
35
35
  * @param {Object} token - The markdown-it token being processed
36
- * @param {string} type - The type of construct (def, ref, xref, tref)
36
+ * @param {string} type - The type of construct (def, ref, iref, xref, tref)
37
37
  * @param {string} primary - The primary content/term
38
38
  * @returns {string} The rendered HTML for the construct
39
39
  */
@@ -45,6 +45,8 @@ function parseTemplateTag(config, globalState, token, type, primary) {
45
45
  switch (type) {
46
46
  case 'def':
47
47
  return parseDef(globalState, token, primary, currentFile);
48
+ case 'iref':
49
+ return parseIref(globalState, primary);
48
50
  case 'xref':
49
51
  return parseXref(config, token);
50
52
  case 'tref':
@@ -130,6 +132,25 @@ function parseRef(globalState, primary) {
130
132
  return `<a class="term-reference" href="#term:${termId}">${primary}</a>`;
131
133
  }
132
134
 
135
+ /**
136
+ * Processes [[iref: term]] constructs
137
+ * Creates a placeholder that will be replaced client-side with a copy of the term definition
138
+ * This allows inline copying of existing term definitions from the terms-and-definitions-list
139
+ * @param {Object} globalState - Global state to track inline references
140
+ * @param {string} primary - The term to inline copy
141
+ * @returns {string} HTML placeholder element that will be replaced by client-side script
142
+ */
143
+ function parseIref(globalState, primary) {
144
+ // Track this inline reference for validation purposes
145
+ globalState.references.push(primary);
146
+
147
+ // Create a placeholder span with data attribute containing the term to copy
148
+ // The client-side script (insert-irefs.js) will find this and replace it with
149
+ // a copy of the actual <dt> and <dd> elements from the terms-and-definitions-list
150
+ const termId = primary.replace(whitespace.oneOrMore, '-').toLowerCase();
151
+ return `<span class="iref-placeholder" data-iref-term="${termId}" data-iref-original="${primary}"></span>`;
152
+ }
153
+
133
154
  /**
134
155
  * Processes [[xref: spec, term, alias, ...]] constructs
135
156
  * Creates links to external specification terms with tooltips
@@ -298,6 +319,7 @@ module.exports = {
298
319
  createTemplateTagParser,
299
320
  // Export individual functions for testing purposes
300
321
  parseDef,
322
+ parseIref,
301
323
  parseXref,
302
324
  parseTref,
303
325
  parseRef,
@@ -46,7 +46,11 @@ function normalizeSpecConfiguration(config, { noSpecsMessage }) {
46
46
  const specs = Array.isArray(config?.specs) ? config.specs : [];
47
47
 
48
48
  if (specs.length === 0) {
49
- Logger.error(noSpecsMessage);
49
+ Logger.error(noSpecsMessage, {
50
+ context: 'specs.json is missing or has no specs array',
51
+ hint: 'Create a valid specs.json file in your project root. Run "npm run init" or copy from: https://github.com/trustoverip/spec-up-t-starter-pack',
52
+ details: 'The specs array is required to configure your specification'
53
+ });
50
54
  return null;
51
55
  }
52
56
 
@@ -65,7 +69,11 @@ function normalizeSpecConfiguration(config, { noSpecsMessage }) {
65
69
  */
66
70
  function extendXTrefs(config, xtrefs) {
67
71
  if (config.specs[0].external_specs_repos) {
68
- Logger.warn('Your specs.json file uses an outdated structure. Update it using: https://github.com/trustoverip/spec-up-t/blob/master/src/install-from-boilerplate/boilerplate/specs.json');
72
+ Logger.warn('Your specs.json file uses an outdated structure', {
73
+ context: 'The "external_specs_repos" field is deprecated',
74
+ hint: 'Update to the new structure using "external_specs" instead. See: https://github.com/trustoverip/spec-up-t/blob/master/src/install-from-boilerplate/boilerplate/specs.json',
75
+ details: 'External references may not work correctly with the old structure'
76
+ });
69
77
  return;
70
78
  }
71
79
 
@@ -118,7 +126,11 @@ function extendXTrefs(config, xtrefs) {
118
126
  try {
119
127
  xtref.branch = getCurrentBranch();
120
128
  } catch (error) {
121
- Logger.warn(`Could not get current branch for xtref ${xtref.externalSpec}:${xtref.term}: ${error.message}`);
129
+ Logger.warn(`Could not get current branch for xtref ${xtref.externalSpec}:${xtref.term}`, {
130
+ context: 'Git branch detection failed for external reference',
131
+ hint: 'Ensure you\'re in a git repository, or specify github_repo_branch in specs.json external_specs configuration',
132
+ details: `${error.message}. Using default: main`
133
+ });
122
134
  xtref.branch = 'main';
123
135
  }
124
136
  });
@@ -153,7 +165,7 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
153
165
  }
154
166
 
155
167
  const userInput = readlineSync.question(
156
- `❌ This external reference is not a valid URL:
168
+ `❌ This external reference is not a valid URL:
157
169
 
158
170
  Repository: ${repo.url},
159
171
 
@@ -195,14 +207,22 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
195
207
  const termsDir = spec?.spec_terms_directory;
196
208
 
197
209
  if (!specDir || !termsDir) {
198
- Logger.warn(`Spec entry is missing spec_directory or spec_terms_directory: ${JSON.stringify(spec)}`);
210
+ Logger.warn(`Spec entry is missing spec_directory or spec_terms_directory`, {
211
+ context: 'Invalid specs.json configuration',
212
+ hint: 'Ensure each spec in specs.json has both "spec_directory" and "spec_terms_directory" fields',
213
+ details: `Incomplete spec entry: ${JSON.stringify(spec)}`
214
+ });
199
215
  return directories;
200
216
  }
201
217
 
202
218
  const resolvedDir = path.join(specDir, termsDir);
203
219
 
204
220
  if (!fs.existsSync(resolvedDir)) {
205
- Logger.warn(`Spec terms directory does not exist: ${resolvedDir}`);
221
+ Logger.warn(`Spec terms directory does not exist: ${resolvedDir}`, {
222
+ context: 'Directory specified in specs.json not found',
223
+ hint: 'Create the directory or update the path in specs.json. Typically this should be "spec/term-definitions" or similar',
224
+ details: `Expected path: ${resolvedDir}`
225
+ });
206
226
  return directories;
207
227
  }
208
228
 
@@ -211,7 +231,11 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
211
231
  }, []);
212
232
 
213
233
  if (specTermsDirectories.length === 0) {
214
- Logger.warn('No spec terms directories found. Skipping external reference collection.');
234
+ Logger.warn('No spec terms directories found. Skipping external reference collection', {
235
+ context: 'Cannot collect external references without valid terminology directories',
236
+ hint: 'Check specs.json configuration. Ensure spec_directory and spec_terms_directory point to existing directories',
237
+ details: 'External trefs and xrefs will not be processed'
238
+ });
215
239
  return;
216
240
  }
217
241
 
@@ -249,7 +273,7 @@ function processExternalReferences(config, GITHUB_API_TOKEN) {
249
273
  function collectExternalReferences(options = {}) {
250
274
  // Start collecting messages if requested
251
275
  const shouldCollectMessages = options.collectMessages !== false; // Collect by default
252
-
276
+
253
277
  if (shouldCollectMessages) {
254
278
  messageCollector.clearMessages();
255
279
  messageCollector.startCollecting('collectExternalReferences');
@@ -273,8 +297,11 @@ function collectExternalReferences(options = {}) {
273
297
  const GITHUB_API_TOKEN = options.pat || process.env.GITHUB_API_TOKEN;
274
298
 
275
299
  if (!GITHUB_API_TOKEN) {
276
- Logger.warn('No GitHub Personal Access Token (PAT) found. Running without authentication (may hit rate limits).');
277
- Logger.info('For better performance, set up a PAT: https://trustoverip.github.io/spec-up-t-website/docs/getting-started/github-token\n');
300
+ Logger.warn('No GitHub Personal Access Token (PAT) found. Running without authentication', {
301
+ context: 'GitHub API requests will use unauthenticated access',
302
+ hint: 'Set GITHUB_PAT environment variable to increase rate limit from 60 to 5000 requests/hour. See: https://trustoverip.github.io/spec-up-t-website/docs/getting-started/github-token',
303
+ details: 'You may hit rate limits when fetching many external references'
304
+ });
278
305
  }
279
306
 
280
307
  // Communicate that the expected external_specs array is missing entirely.
@@ -282,7 +309,7 @@ function collectExternalReferences(options = {}) {
282
309
  Logger.info(
283
310
  'No external_specs array found on the first spec entry in specs.json. External reference collection is skipped.'
284
311
  );
285
-
312
+
286
313
  if (shouldCollectMessages) {
287
314
  messageCollector.stopCollecting();
288
315
  messageCollector.saveMessages().then(path => {
@@ -300,7 +327,7 @@ function collectExternalReferences(options = {}) {
300
327
  Logger.info(
301
328
  'The external_specs array in specs.json is empty. Add external repositories to collect external references.'
302
329
  );
303
-
330
+
304
331
  if (shouldCollectMessages) {
305
332
  messageCollector.stopCollecting();
306
333
  messageCollector.saveMessages().then(path => {
@@ -333,14 +360,14 @@ function collectExternalReferences(options = {}) {
333
360
  })
334
361
  .catch(error => {
335
362
  Logger.error('Rendering failed after collecting external references.', error);
336
-
363
+
337
364
  if (shouldCollectMessages) {
338
365
  messageCollector.stopCollecting();
339
366
  messageCollector.saveMessages().catch(() => {
340
367
  // Silent fail on save error
341
368
  });
342
369
  }
343
-
370
+
344
371
  throw error;
345
372
  });
346
373
  } else {
@@ -17,7 +17,11 @@ function validateReferences(references, definitions, render) {
17
17
  }
18
18
  );
19
19
  if (unresolvedRefs.length > 0) {
20
- Logger.warn(`Unresolved References: ${unresolvedRefs.join(',')}`);
20
+ Logger.warn(`Unresolved References: ${unresolvedRefs.join(',')}`, {
21
+ context: 'These terms are referenced in your spec but not defined',
22
+ hint: 'Add [[def: term]] definitions for these terms in your terminology files, or check for typos in [[ref: term]] references',
23
+ details: `Count: ${unresolvedRefs.length} unresolved term(s)`
24
+ });
21
25
  }
22
26
 
23
27
  const danglingDefs = [];
@@ -38,7 +42,11 @@ function validateReferences(references, definitions, render) {
38
42
  }
39
43
  })
40
44
  if (danglingDefs.length > 0) {
41
- Logger.warn(`Dangling Definitions: ${danglingDefs.join(',')}`);
45
+ Logger.warn(`Dangling Definitions: ${danglingDefs.join(',')}`, {
46
+ context: 'These terms are defined but never referenced in your spec',
47
+ hint: 'Add [[ref: term]] references where needed.',
48
+ details: `Count: ${danglingDefs.length} unused definition(s)`
49
+ });
42
50
  }
43
51
  }
44
52
 
@@ -71,7 +79,11 @@ async function fetchExternalSpecs(spec) {
71
79
  const msg = f.error.response
72
80
  ? `HTTP ${f.error.response.status} for ${f.url}`
73
81
  : `Network error for ${f.url}: ${f.error.message}`;
74
- Logger.error("External spec fetch failed:", msg);
82
+ Logger.error("External spec fetch failed", {
83
+ context: `Attempting to fetch external terminology from: ${f.url}`,
84
+ hint: 'Verify the URL is correct in specs.json under external_specs[].gh_page. Ensure the external spec has published their GitHub Pages site',
85
+ details: msg
86
+ });
75
87
  });
76
88
  }
77
89
 
@@ -93,7 +105,11 @@ async function fetchExternalSpecs(spec) {
93
105
 
94
106
  return extractedTerms;
95
107
  } catch (e) {
96
- Logger.error("Unexpected error in fetchExternalSpecs:", e);
108
+ Logger.error("Unexpected error in fetchExternalSpecs", {
109
+ context: 'Failed while fetching terms from external specifications',
110
+ hint: 'Check your internet connection and verify all external_specs URLs in specs.json are valid. Run with GITHUB_PAT set if accessing private repos',
111
+ details: e.message
112
+ });
97
113
  return [];
98
114
  }
99
115
  }
@@ -132,7 +148,7 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
132
148
  if (existingIndex >= 0) {
133
149
  // Get the existing entry to check if it's a tref
134
150
  const existingXtref = allXTrefs.xtrefs[existingIndex];
135
-
151
+
136
152
  // Update existing entry - preserve the existing metadata but add/update content
137
153
  allXTrefs.xtrefs[existingIndex] = {
138
154
  ...existingXtref,
@@ -151,26 +167,34 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
151
167
 
152
168
  if (isExternalTref && isTref) {
153
169
  // Build a readable list of source files for the error message
154
- const sourceFilesList = existingXtref.sourceFile
155
- ? existingXtref.sourceFile
170
+ const sourceFilesList = existingXtref.sourceFile
171
+ ? existingXtref.sourceFile
156
172
  : (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
157
-
173
+
158
174
  // Construct the external repository URL
159
175
  const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
160
-
161
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED TREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec} is itself a tref (transcluded from another spec). This creates a chain of external references.`);
176
+
177
+ Logger.error(`NESTED TREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec}`, {
178
+ context: `Origin: ${sourceFilesList} - This term is itself transcluded from another spec`,
179
+ hint: 'Avoid chaining external references (tref → tref). Reference the original source spec directly, or define the term locally in your spec',
180
+ details: `This creates a complex dependency chain. Repository: ${externalRepoUrl}`
181
+ });
162
182
  }
163
183
 
164
184
  if (isExternalTref && isXref) {
165
185
  // Build a readable list of source files for the warning message
166
- const sourceFilesList = existingXtref.sourceFile
167
- ? existingXtref.sourceFile
186
+ const sourceFilesList = existingXtref.sourceFile
187
+ ? existingXtref.sourceFile
168
188
  : (existingXtref.sourceFiles || []).map(sf => sf.file).join(', ');
169
-
189
+
170
190
  // Construct the external repository URL
171
191
  const externalRepoUrl = existingXtref.ghPageUrl || existingXtref.repoUrl || `https://github.com/${existingXtref.owner}/${existingXtref.repo}`;
172
-
173
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED XREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec} is itself a tref (transcluded from another spec). This xref points to a term that is already transcluded from elsewhere, creating a chain of external references. (${externalRepoUrl})`);
192
+
193
+ Logger.error(`NESTED XREF DETECTED: Term "${existingXtref.term}" in ${existingXtref.externalSpec}`, {
194
+ context: `Origin: ${sourceFilesList} - This xref points to a term that is already transcluded from elsewhere`,
195
+ hint: 'Use [[xref]] only for terms directly defined in the external spec. For nested references, either reference the original source or define the term locally',
196
+ details: `This creates a chain of external references. Repository: ${externalRepoUrl}`
197
+ });
174
198
  }
175
199
 
176
200
  matchedCount++;
@@ -191,7 +215,11 @@ async function mergeXrefTermsIntoAllXTrefs(xrefTerms, outputPathJSON, outputPath
191
215
  Logger.success(`Merged xref terms: ${matchedCount} matched, ${skippedCount} skipped (not referenced). Total entries: ${allXTrefs.xtrefs.length}`);
192
216
 
193
217
  } catch (error) {
194
- Logger.error('Error merging xref terms into allXTrefs:', error.message);
218
+ Logger.error('Error merging xref terms into allXTrefs', {
219
+ context: 'Failed while merging external reference terms into the xtrefs cache',
220
+ hint: 'This may indicate corrupted cache files in .cache/xtrefs/. Try deleting the .cache directory and running the build again',
221
+ details: error.message
222
+ });
195
223
  }
196
224
  }
197
225
 
@@ -219,18 +247,18 @@ function extractTermsFromHtml(externalSpec, html) {
219
247
  batch.each((index, termElement) => {
220
248
  try {
221
249
  const $termElement = $(termElement);
222
-
250
+
223
251
  // The id can be on the dt element itself OR on span(s) inside it
224
252
  // Some terms have multiple nested spans with different IDs (e.g., aliases)
225
253
  // We need to extract ALL term IDs from the dt element
226
254
  const termIds = [];
227
-
255
+
228
256
  // First check if dt itself has an id
229
257
  const dtId = $termElement.attr('id');
230
258
  if (dtId && dtId.includes('term:')) {
231
259
  termIds.push(dtId.replace('term:', ''));
232
260
  }
233
-
261
+
234
262
  // Then find all spans with ids that contain 'term:'
235
263
  $termElement.find('span[id*="term:"]').each((i, span) => {
236
264
  const spanId = $(span).attr('id');
@@ -241,7 +269,7 @@ function extractTermsFromHtml(externalSpec, html) {
241
269
  }
242
270
  }
243
271
  });
244
-
272
+
245
273
  // Skip if no valid term IDs found
246
274
  if (termIds.length === 0) {
247
275
  return;
@@ -252,12 +280,12 @@ function extractTermsFromHtml(externalSpec, html) {
252
280
  const dtClasses = $termElement.attr('class');
253
281
  const classArray = dtClasses ? dtClasses.split(/\s+/).filter(Boolean) : [];
254
282
  const termClasses = classArray.filter(cls => cls === 'term-local' || cls === 'term-external');
255
-
283
+
256
284
  const dd = $termElement.next('dd');
257
285
 
258
286
  if (dd.length > 0) {
259
287
  const ddContent = $.html(dd); // Store the complete DD content once
260
-
288
+
261
289
  // Create a term object for each ID found in this dt element
262
290
  // This handles cases where one term definition has multiple aliases
263
291
  termIds.forEach(termName => {
@@ -275,7 +303,11 @@ function extractTermsFromHtml(externalSpec, html) {
275
303
  });
276
304
  }
277
305
  } catch (termError) {
278
- Logger.warn(`Error processing term in ${externalSpec}:`, termError.message);
306
+ Logger.warn(`Error processing term in ${externalSpec}`, {
307
+ context: 'Failed to extract a specific term from external spec HTML',
308
+ hint: 'The external spec may have malformed HTML or non-standard term definitions. Contact the spec maintainer if this persists',
309
+ details: termError.message
310
+ });
279
311
  }
280
312
  });
281
313
 
@@ -289,7 +321,11 @@ function extractTermsFromHtml(externalSpec, html) {
289
321
  return terms;
290
322
 
291
323
  } catch (error) {
292
- Logger.error(`Error extracting terms from external spec '${externalSpec}':`, error.message);
324
+ Logger.error(`Error extracting terms from external spec '${externalSpec}'`, {
325
+ context: 'Failed while parsing HTML from external spec to extract term definitions',
326
+ hint: 'Verify that the external spec is a valid spec-up-t generated document with a proper terms-and-definitions section',
327
+ details: error.message
328
+ });
293
329
  return [];
294
330
  }
295
331
  }
@@ -14,7 +14,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
14
14
 
15
15
  allXTrefs.xtrefs = allXTrefs.xtrefs.filter(xtref => {
16
16
  if (!xtref.owner || !xtref.repo || !xtref.repoUrl) {
17
- Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`);
17
+ Logger.error(`Removing incomplete reference: ${xtref.externalSpec}, ${xtref.term}`, {
18
+ context: 'External reference is missing required repository information',
19
+ hint: 'Verify external_specs configuration in specs.json includes github_repo_url for each entry',
20
+ details: `Missing: ${!xtref.owner ? 'owner' : ''} ${!xtref.repo ? 'repo' : ''} ${!xtref.repoUrl ? 'repoUrl' : ''}`
21
+ });
18
22
  return false;
19
23
  }
20
24
  return true;
@@ -51,7 +55,11 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
51
55
  );
52
56
 
53
57
  if (!allTermsData) {
54
- Logger.error(`Could not fetch terms from repository ${repoKey} (${repoUrl})`);
58
+ Logger.error(`Could not fetch terms from repository ${repoKey}`, {
59
+ context: `Failed to retrieve terminology from ${repoUrl}`,
60
+ hint: 'Ensure the repository exists, is accessible, and has published its spec. If it\'s private, set GITHUB_PAT environment variable',
61
+ details: `Repository: ${repoUrl}. Check if GitHub Pages is enabled or if the repo has a valid specs.json`
62
+ });
55
63
  repoGroup.xtrefs.forEach(xtref => {
56
64
  xtref.commitHash = 'not found';
57
65
  xtref.content = 'This term was not found in the external repository.';
@@ -81,26 +89,34 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
81
89
 
82
90
  if (isExternalTref && isTref) {
83
91
  // Build a readable list of source files for the error message
84
- const sourceFilesList = xtref.sourceFile
85
- ? xtref.sourceFile
92
+ const sourceFilesList = xtref.sourceFile
93
+ ? xtref.sourceFile
86
94
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
87
-
95
+
88
96
  // Construct the external repository URL
89
97
  const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
90
-
91
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED TREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec} is itself a tref (transcluded from another spec). This creates a chain of external references.`);
98
+
99
+ Logger.error(`NESTED TREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
100
+ context: `Origin: ${sourceFilesList} - This term is itself a tref transcluded from another spec`,
101
+ hint: 'Avoid chaining trefs (tref → tref). Either reference the original source spec directly, or define the term locally',
102
+ details: `Repository: ${externalRepoUrl}. Nested trefs create complex dependency chains`
103
+ });
92
104
  }
93
105
 
94
106
  if (isExternalTref && isXref) {
95
107
  // Build a readable list of source files for the warning message
96
- const sourceFilesList = xtref.sourceFile
97
- ? xtref.sourceFile
108
+ const sourceFilesList = xtref.sourceFile
109
+ ? xtref.sourceFile
98
110
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
99
-
111
+
100
112
  // Construct the external repository URL
101
113
  const externalRepoUrl = xtref.ghPageUrl || xtref.repoUrl || `https://github.com/${xtref.owner}/${xtref.repo}`;
102
-
103
- Logger.error(`Origin: ${sourceFilesList} 👉 NESTED XREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec} is itself a tref (transcluded from another spec). This xref points to a term that is already transcluded from elsewhere, creating a chain of external references. (${externalRepoUrl})`);
114
+
115
+ Logger.error(`NESTED XREF DETECTED: Term "${xtref.term}" in ${xtref.externalSpec}`, {
116
+ context: `Origin: ${sourceFilesList} - This xref points to a term that is already transcluded elsewhere`,
117
+ hint: 'Use [[xref]] only for terms directly defined in the external spec. For nested refs, reference the original source',
118
+ details: `Repository: ${externalRepoUrl}. This creates a chain of external references`
119
+ });
104
120
  }
105
121
 
106
122
  Logger.success(`Match found for term: ${xtref.term} in ${xtref.externalSpec}`);
@@ -108,7 +124,7 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
108
124
  xtref.commitHash = 'not found';
109
125
  xtref.content = 'This term was not found in the external repository.';
110
126
  xtref.avatarUrl = null;
111
-
127
+
112
128
  // Build a readable list of source files for the error message.
113
129
  // Two possible data structures exist:
114
130
  // 1. xtref.sourceFile is a STRING like "primitive.md"
@@ -119,15 +135,19 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
119
135
  // - Otherwise → extract file names from the sourceFiles array:
120
136
  // - .map(sf => sf.file) extracts just the filename from each object
121
137
  // - .join(', ') combines them into a comma-separated string
122
- const sourceFilesList = xtref.sourceFile
123
- ? xtref.sourceFile
138
+ const sourceFilesList = xtref.sourceFile
139
+ ? xtref.sourceFile
124
140
  : (xtref.sourceFiles || []).map(sf => sf.file).join(', ');
125
141
 
126
142
  // Prefer an explicit repo URL if provided on the xtref, otherwise
127
143
  // build a standard GitHub URL from the owner/repo.
128
144
  const githubUrl = xtref.repoUrl || `https://github.com/${repoKey}`;
129
145
 
130
- Logger.error(`Origin: ${sourceFilesList} 👉 No match found for term: ${xtref.term} in ${xtref.externalSpec} (${githubUrl})`);
146
+ Logger.error(`No match found for term: ${xtref.term} in ${xtref.externalSpec}`, {
147
+ context: `Origin: ${sourceFilesList} - Term not found in external repository`,
148
+ hint: 'Check if the term exists in the external spec. Verify spelling, ensure the external spec has published, and confirm the term is in their terminology section',
149
+ details: `Repository: ${githubUrl}. The term may have been renamed or removed`
150
+ });
131
151
  }
132
152
  }
133
153
 
@@ -138,10 +158,14 @@ async function processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, ou
138
158
  const allXTrefsStr = JSON.stringify(allXTrefs, null, 2);
139
159
  fs.writeFileSync(outputPathJSON, allXTrefsStr, 'utf8');
140
160
  const jsPayload = `const allXTrefs = ${allXTrefsStr};`;
141
- fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
142
- fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
161
+ fs.writeFileSync(outputPathJS, jsPayload, 'utf8');
162
+ fs.writeFileSync(outputPathJSTimeStamped, jsPayload, 'utf8');
143
163
  } catch (error) {
144
- Logger.error('An error occurred:', error);
164
+ Logger.error('An error occurred during xtrefs processing', {
165
+ context: 'Failed while processing external references and fetching terms',
166
+ hint: 'Check your internet connection, verify GITHUB_PAT is set if needed, and ensure specs.json external_specs configuration is correct',
167
+ details: error.message
168
+ });
145
169
  }
146
170
  }
147
171