openxiangda-devkit-core 2.63.3 → 2.65.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.
@@ -15,6 +15,7 @@ export declare const APP_SPEC_LIMITS: {
15
15
  readonly maximumContextBytes: number;
16
16
  readonly maximumDirectoryEntries: 256;
17
17
  readonly maximumHistoryDepth: 1;
18
+ readonly maximumReadbackEntries: 128;
18
19
  };
19
20
  export type AppSpecRisk = "L1" | "L2" | "L3";
20
21
  export type AppSpecCurrentSpecStatus = "pending" | "merged" | "not-applicable";
@@ -58,6 +59,14 @@ export interface AppSpecDocumentSummary {
58
59
  requirementCount: number;
59
60
  acceptanceScenarioCount: number;
60
61
  }
62
+ export interface AppSpecDocumentReadback {
63
+ path: string;
64
+ actualBytes: number | null;
65
+ readBytes: number;
66
+ mode: 'full' | 'history-index' | 'unavailable';
67
+ reason: 'read' | 'file-unreadable' | 'file-invalid' | 'file-size-budget' | 'file-count-budget' | 'total-size-budget' | 'metadata-invalid' | 'changed-during-read' | 'directory-entry-budget' | 'directory-invalid';
68
+ maximumBytes: number | null;
69
+ }
61
70
  export interface AppSpecContext {
62
71
  schemaVersion: typeof APP_SPEC_SCHEMAS.context;
63
72
  enabled: boolean;
@@ -89,8 +98,18 @@ export interface AppSpecContext {
89
98
  contextBudget: {
90
99
  maximumBytes: number;
91
100
  contentBytes: number;
101
+ requestedContentBytes: number;
102
+ truncated: boolean;
103
+ omitted: Array<AppSpecDocumentSummary & {
104
+ reason: 'context-budget';
105
+ actualBytes: number;
106
+ }>;
107
+ };
108
+ documentReadback: {
109
+ entries: AppSpecDocumentReadback[];
110
+ total: number;
111
+ unavailable: number;
92
112
  truncated: boolean;
93
- omitted: AppSpecDocumentSummary[];
94
113
  };
95
114
  stats: {
96
115
  files: number;
@@ -205,8 +224,12 @@ export declare function summarizeAppSpecContext(context: AppSpecContext): {
205
224
  contextBudget: {
206
225
  maximumBytes: number;
207
226
  contentBytes: number;
227
+ requestedContentBytes: number;
208
228
  truncated: boolean;
209
- omitted: AppSpecDocumentSummary[];
229
+ omitted: Array<AppSpecDocumentSummary & {
230
+ reason: "context-budget";
231
+ actualBytes: number;
232
+ }>;
210
233
  };
211
234
  stats: {
212
235
  files: number;
@@ -1 +1 @@
1
- {"version":3,"file":"app-spec.d.ts","sourceRoot":"","sources":["../src/app-spec.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app-spec.d.ts","sourceRoot":"","sources":["../src/app-spec.ts"],"names":[],"mappings":"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"}
package/dist/app-spec.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { designAssetReader } from './design-assets.js';
3
- import { existsSync, openSync, readSync, closeSync, lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
3
+ import { existsSync, openSync, readSync, closeSync, lstatSync, fstatSync, constants, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from "node:fs";
4
4
  import { basename, dirname, join, relative, resolve } from "node:path";
5
5
  import { SCHEMA_VERSIONS, } from "openxiangda-contracts";
6
6
  export const APP_SPEC_SCHEMAS = {
@@ -18,6 +18,7 @@ export const APP_SPEC_LIMITS = {
18
18
  maximumContextBytes: 256 * 1024,
19
19
  maximumDirectoryEntries: 256,
20
20
  maximumHistoryDepth: 1,
21
+ maximumReadbackEntries: 128,
21
22
  };
22
23
  const APP_ID = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
23
24
  const CHANGE_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
@@ -247,6 +248,7 @@ export function inspectAppSpec(root, contract, selector, historyOffset = 0) {
247
248
  designs: budgeted.designs,
248
249
  historyPage: collection.historyPage,
249
250
  contextBudget: budgeted.contextBudget,
251
+ documentReadback: documentReadback(collection.readback, selector),
250
252
  stats: {
251
253
  files: allDocuments.length,
252
254
  bytes: collection.bytes,
@@ -318,9 +320,11 @@ function emptyContext(contract, selector, diagnostics = [], enabled = false) {
318
320
  contextBudget: {
319
321
  maximumBytes: APP_SPEC_LIMITS.maximumContextBytes,
320
322
  contentBytes: 0,
323
+ requestedContentBytes: 0,
321
324
  truncated: false,
322
325
  omitted: [],
323
326
  },
327
+ documentReadback: { entries: [], total: 0, unavailable: 0, truncated: false },
324
328
  stats: {
325
329
  files: 0,
326
330
  bytes: 0,
@@ -396,52 +400,71 @@ function collectDocuments(root, diagnostics, selector, historyOffset = 0) {
396
400
  decisions: [],
397
401
  designs: [],
398
402
  bytes: 0,
403
+ readback: [],
399
404
  historyPage: { offset: historyOffset, limit: 50, total: 0, nextOffset: null },
400
405
  };
401
406
  const candidates = [];
407
+ const discover = (directory) => markdownFiles(directory, false, diagnostics, root, 0, (path, reason) => result.readback.push({
408
+ path: relativePath(root, path), actualBytes: null, readBytes: 0, mode: 'unavailable', reason, maximumBytes: null,
409
+ }));
402
410
  if (existsSync(paths.app))
403
411
  candidates.push({ path: paths.app, kind: "app" });
404
- candidates.push(...markdownFiles(paths.capabilities, false, diagnostics, root).map(path => ({
412
+ candidates.push(...discover(paths.capabilities).map(path => ({
405
413
  path,
406
414
  kind: "capability",
407
- })), ...markdownFiles(paths.activeChanges, false, diagnostics, root).map(path => ({
415
+ })), ...discover(paths.activeChanges).map(path => ({
408
416
  path,
409
417
  kind: "change",
410
- })), ...markdownFiles(paths.decisions, false, diagnostics, root).map(path => ({
418
+ })), ...discover(paths.decisions).map(path => ({
411
419
  path,
412
420
  kind: "decision",
413
421
  })));
414
422
  for (const directory of ['product', 'experience', 'design', 'reviews']) {
415
- candidates.push(...markdownFiles(join(paths.root, directory), false, diagnostics, root).map(path => ({ path, kind: 'design' })));
423
+ candidates.push(...discover(join(paths.root, directory)).map(path => ({ path, kind: 'design' })));
416
424
  }
417
425
  if (candidates.length > APP_SPEC_LIMITS.maximumFiles) {
418
426
  diagnostics.push(issue("error", "APPSPEC_FILE_LIMIT_EXCEEDED", `AppSpec 文件数 ${candidates.length} 超过上限 ${APP_SPEC_LIMITS.maximumFiles}`, "appspec"));
419
- candidates.splice(APP_SPEC_LIMITS.maximumFiles);
427
+ for (const skipped of candidates.splice(APP_SPEC_LIMITS.maximumFiles))
428
+ result.readback.push({
429
+ path: relativePath(root, skipped.path), actualBytes: null, readBytes: 0, mode: 'unavailable', reason: 'file-count-budget', maximumBytes: null,
430
+ });
420
431
  }
421
432
  const history = historyCandidates(root, selector, historyOffset, diagnostics);
422
433
  result.historyPage = history.page;
423
434
  candidates.push(...history.paths.map(path => ({ path, kind: "change", history: true })));
424
435
  let historyBytes = 0;
425
- for (const candidate of candidates.sort((left, right) => Number(!!left.history) - Number(!!right.history) || left.path.localeCompare(right.path))) {
436
+ const ordered = candidates.sort((left, right) => Number(!!left.history) - Number(!!right.history) || left.path.localeCompare(right.path));
437
+ for (const [candidateIndex, candidate] of ordered.entries()) {
438
+ const unread = (reason, actualBytes, maximumBytes = APP_SPEC_LIMITS.maximumFileBytes, bytesRead = 0) => result.readback.push({
439
+ path: relativePath(root, candidate.path), actualBytes, readBytes: bytesRead, mode: 'unavailable', reason, maximumBytes,
440
+ });
426
441
  let stat;
427
442
  try {
428
443
  stat = lstatSync(candidate.path);
429
444
  }
430
445
  catch {
446
+ unread('file-unreadable', null);
431
447
  diagnostics.push(issue("error", "APPSPEC_FILE_UNREADABLE", "AppSpec 文件无法读取", relativePath(root, candidate.path)));
432
448
  continue;
433
449
  }
434
450
  if (!stat.isFile() || stat.isSymbolicLink()) {
451
+ unread('file-invalid', null, null);
435
452
  diagnostics.push(issue("error", "APPSPEC_FILE_INVALID", "AppSpec 只允许普通 Markdown 文件,不能使用符号链接", relativePath(root, candidate.path)));
436
453
  continue;
437
454
  }
438
455
  if (stat.size > APP_SPEC_LIMITS.maximumFileBytes) {
439
- diagnostics.push(issue("error", "APPSPEC_FILE_SIZE_EXCEEDED", `AppSpec 单文件超过 ${APP_SPEC_LIMITS.maximumFileBytes} 字节上限`, relativePath(root, candidate.path)));
456
+ unread('file-size-budget', stat.size);
457
+ diagnostics.push(issue("error", "APPSPEC_FILE_SIZE_EXCEEDED", `AppSpec 文件实际 ${stat.size} 字节,超过 ${APP_SPEC_LIMITS.maximumFileBytes} 字节上限;文件已发现但未读取`, relativePath(root, candidate.path), { actualBytes: stat.size, maximumBytes: APP_SPEC_LIMITS.maximumFileBytes, reason: 'file-size-budget' }));
440
458
  continue;
441
459
  }
442
460
  const indexOnly = candidate.history && basename(candidate.path) !== `${selector}.md`;
443
461
  const readBytes = indexOnly ? Math.min(stat.size, 8192) : stat.size;
444
462
  if ((candidate.history ? historyBytes : result.bytes) + readBytes > APP_SPEC_LIMITS.maximumTotalBytes) {
463
+ unread('total-size-budget', stat.size, APP_SPEC_LIMITS.maximumTotalBytes);
464
+ for (const skipped of ordered.slice(candidateIndex + 1))
465
+ result.readback.push({
466
+ path: relativePath(root, skipped.path), actualBytes: null, readBytes: 0, mode: 'unavailable', reason: 'total-size-budget', maximumBytes: APP_SPEC_LIMITS.maximumTotalBytes,
467
+ });
445
468
  diagnostics.push(issue("error", "APPSPEC_TOTAL_SIZE_EXCEEDED", `AppSpec 总大小超过 ${APP_SPEC_LIMITS.maximumTotalBytes} 字节上限`, "appspec"));
446
469
  break;
447
470
  }
@@ -449,23 +472,26 @@ function collectDocuments(root, diagnostics, selector, historyOffset = 0) {
449
472
  historyBytes += readBytes;
450
473
  else
451
474
  result.bytes += readBytes;
452
- let content;
453
- if (indexOnly) {
454
- const descriptor = openSync(candidate.path, 'r');
455
- try {
456
- const buffer = Buffer.alloc(readBytes);
457
- const bytes = readSync(descriptor, buffer, 0, readBytes, 0);
458
- content = buffer.subarray(0, bytes).toString('utf8').match(/^---\r?\n[\s\S]*?\r?\n---/)?.[0] || '';
459
- }
460
- finally {
461
- closeSync(descriptor);
462
- }
475
+ let content, bytesRead;
476
+ try {
477
+ const readback = readBoundedDocument(candidate.path, stat, readBytes, Boolean(indexOnly));
478
+ content = readback.content;
479
+ bytesRead = readback.bytesRead;
480
+ }
481
+ catch (error) {
482
+ const changed = error.code === 'APPSPEC_FILE_CHANGED';
483
+ const read = Number(error.bytesRead || 0);
484
+ unread(changed ? 'changed-during-read' : 'file-unreadable', changed ? null : stat.size, APP_SPEC_LIMITS.maximumFileBytes, Number.isSafeInteger(read) && read >= 0 ? read : 0);
485
+ diagnostics.push(issue('error', changed ? 'APPSPEC_FILE_CHANGED' : 'APPSPEC_FILE_UNREADABLE', changed ? 'AppSpec 文件在读取期间发生变化;保留原资料并重新读取' : 'AppSpec 文件已发现但无法完成有界读取', relativePath(root, candidate.path)));
486
+ continue;
463
487
  }
464
- else
465
- content = readFileSync(candidate.path, "utf8");
466
488
  const document = parseAppSpecDocument(root, candidate.path, candidate.kind, content, diagnostics);
467
- if (!document)
489
+ if (!document) {
490
+ unread('metadata-invalid', stat.size, APP_SPEC_LIMITS.maximumFileBytes, bytesRead);
468
491
  continue;
492
+ }
493
+ result.readback.push({ path: document.path, actualBytes: stat.size, readBytes: bytesRead,
494
+ mode: indexOnly ? 'history-index' : 'full', reason: 'read', maximumBytes: APP_SPEC_LIMITS.maximumFileBytes });
469
495
  if (document.kind === 'design' && 'assets' in document.metadata) {
470
496
  try {
471
497
  const assets = document.metadata.assets;
@@ -492,9 +518,44 @@ function collectDocuments(root, diagnostics, selector, historyOffset = 0) {
492
518
  }
493
519
  return result;
494
520
  }
521
+ function readBoundedDocument(path, expected, limit, indexOnly) {
522
+ const descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW || 0) | (constants.O_NONBLOCK || 0));
523
+ let bytesRead = 0;
524
+ try {
525
+ const before = fstatSync(descriptor);
526
+ if (!before.isFile() || before.size !== expected.size || before.mtimeMs !== expected.mtimeMs
527
+ || before.size > APP_SPEC_LIMITS.maximumFileBytes)
528
+ throw stableError('APPSPEC_FILE_CHANGED', path);
529
+ const buffer = Buffer.alloc(limit + (indexOnly ? 0 : 1));
530
+ while (bytesRead < buffer.length) {
531
+ const count = readSync(descriptor, buffer, bytesRead, buffer.length - bytesRead, bytesRead);
532
+ if (!count)
533
+ break;
534
+ bytesRead += count;
535
+ }
536
+ const after = fstatSync(descriptor);
537
+ if (after.size !== before.size || after.mtimeMs !== before.mtimeMs || (!indexOnly && bytesRead !== before.size))
538
+ throw stableError('APPSPEC_FILE_CHANGED', path);
539
+ const content = buffer.subarray(0, bytesRead).toString('utf8');
540
+ return { content: indexOnly ? content.match(/^---\r?\n[\s\S]*?\r?\n---/)?.[0] || '' : content, bytesRead };
541
+ }
542
+ catch (error) {
543
+ throw Object.assign(error, { bytesRead });
544
+ }
545
+ finally {
546
+ closeSync(descriptor);
547
+ }
548
+ }
549
+ function documentReadback(entries, selector) {
550
+ const prioritized = [...entries].sort((a, b) => Number(b.path === 'appspec/app.md' || basename(b.path, '.md') === selector)
551
+ - Number(a.path === 'appspec/app.md' || basename(a.path, '.md') === selector));
552
+ return { entries: prioritized.slice(0, APP_SPEC_LIMITS.maximumReadbackEntries), total: entries.length,
553
+ unavailable: entries.filter(entry => entry.mode === 'unavailable').length, truncated: entries.length > APP_SPEC_LIMITS.maximumReadbackEntries };
554
+ }
495
555
  function validateDocuments(collection, contract, diagnostics) {
496
556
  if (collection.app.length !== 1) {
497
- diagnostics.push(issue("error", "APPSPEC_APPLICATION_REQUIRED", "启用 AppSpec 后必须且只能存在一份 appspec/app.md", "appspec/app.md"));
557
+ const unavailable = collection.readback.some(entry => entry.path === 'appspec/app.md' && entry.mode === 'unavailable');
558
+ diagnostics.push(issue("error", unavailable ? "APPSPEC_APPLICATION_UNAVAILABLE" : "APPSPEC_APPLICATION_REQUIRED", unavailable ? "appspec/app.md 已发现但未成功读取;先处理读取元数据中的原因,不重建原总纲或确认" : "启用 AppSpec 后必须且只能存在一份 appspec/app.md", "appspec/app.md"));
498
559
  }
499
560
  const app = collection.app[0];
500
561
  if (app && app.id !== contract.appCode) {
@@ -530,7 +591,7 @@ function validateDocuments(collection, contract, diagnostics) {
530
591
  const referenceSeverity = finalState ? "error" : "warning";
531
592
  for (const id of document.references.capabilities) {
532
593
  if (!capabilityIds.has(id)) {
533
- diagnostics.push(issue(referenceSeverity, "APPSPEC_CHANGE_CAPABILITY_UNKNOWN", `ChangeSpec 引用的能力 ${id} 不存在`, document.path));
594
+ diagnostics.push(issue(referenceSeverity, "APPSPEC_CHANGE_CAPABILITY_UNKNOWN", collection.readback.some(entry => entry.mode === 'unavailable') ? `ChangeSpec 引用的能力 ${id} 未能在成功读取的文档中确认;先核对 documentReadback` : `ChangeSpec 引用的能力 ${id} 不存在`, document.path));
534
595
  }
535
596
  }
536
597
  for (const id of document.references.requirements) {
@@ -540,7 +601,7 @@ function validateDocuments(collection, contract, diagnostics) {
540
601
  }
541
602
  for (const id of document.references.decisions) {
542
603
  if (!decisionIds.has(id)) {
543
- diagnostics.push(issue(referenceSeverity, "APPSPEC_CHANGE_DECISION_UNKNOWN", `ChangeSpec 引用的决策 ${id} 不存在`, document.path));
604
+ diagnostics.push(issue(referenceSeverity, "APPSPEC_CHANGE_DECISION_UNKNOWN", collection.readback.some(entry => entry.mode === 'unavailable') ? `ChangeSpec 引用的决策 ${id} 未能在成功读取的文档中确认;先核对 documentReadback` : `ChangeSpec 引用的决策 ${id} 不存在`, document.path));
544
605
  }
545
606
  }
546
607
  validateReferences(document, resourceCodes, actionCodes, referenceSeverity, diagnostics);
@@ -737,7 +798,8 @@ function selectBaseContext(collection, selector, diagnostics) {
737
798
  decisions: [decision],
738
799
  };
739
800
  }
740
- diagnostics.push(issue("warning", "APPSPEC_SELECTOR_NOT_FOUND", `未找到 AppSpec selector ${selector},已返回应用总纲和空的相关上下文`, "appspec"));
801
+ const unavailable = collection.readback.filter(entry => entry.mode === 'unavailable');
802
+ diagnostics.push(issue("warning", unavailable.length ? "APPSPEC_SELECTOR_UNRESOLVED" : "APPSPEC_SELECTOR_NOT_FOUND", unavailable.length ? `AppSpec selector ${selector} 未能在成功读取的记录中解析;${unavailable.length} 份文件不可用,先核对 documentReadback 的路径和实际原因` : `未找到 AppSpec selector ${selector},已返回应用总纲和空的相关上下文`, "appspec"));
741
803
  return {
742
804
  primary: null,
743
805
  capabilities: [],
@@ -769,7 +831,7 @@ function selectContext(collection, selector, diagnostics) {
769
831
  for (const id of document.references.documents) {
770
832
  const target = available.find(item => item.id === id);
771
833
  if (!target)
772
- diagnostics.push(issue('error', 'APPSPEC_DESIGN_REFERENCE_UNKNOWN', `设计引用 ${id} 不存在;先恢复资料或修正引用`, document.path));
834
+ diagnostics.push(issue('error', 'APPSPEC_DESIGN_REFERENCE_UNKNOWN', collection.readback.some(entry => entry.mode === 'unavailable') ? `设计引用 ${id} 未能在成功读取的文档中确认;先核对 documentReadback 中的实际原因` : `设计引用 ${id} 不存在;先恢复资料或修正引用`, document.path));
773
835
  else if (!seen.has(id))
774
836
  queue.push(target);
775
837
  }
@@ -802,6 +864,7 @@ function applyContextBudget(application, selected) {
802
864
  const included = new Set();
803
865
  const omitted = [];
804
866
  let contentBytes = 0;
867
+ const requestedContentBytes = prioritized.reduce((sum, document) => sum + Buffer.byteLength(document.content, 'utf8'), 0);
805
868
  for (const document of prioritized) {
806
869
  const bytes = Buffer.byteLength(document.content, "utf8");
807
870
  if (contentBytes + bytes > APP_SPEC_LIMITS.maximumContextBytes) {
@@ -822,8 +885,9 @@ function applyContextBudget(application, selected) {
822
885
  contextBudget: {
823
886
  maximumBytes: APP_SPEC_LIMITS.maximumContextBytes,
824
887
  contentBytes,
888
+ requestedContentBytes,
825
889
  truncated: omitted.length > 0,
826
- omitted: omitted.map(summarize),
890
+ omitted: omitted.map(document => ({ ...summarize(document), reason: 'context-budget', actualBytes: Buffer.byteLength(document.content, 'utf8') })),
827
891
  },
828
892
  };
829
893
  }
@@ -1077,10 +1141,11 @@ function duplicateDiagnostics(values, id, code, diagnostics) {
1077
1141
  }
1078
1142
  }
1079
1143
  }
1080
- function markdownFiles(directory, recursive, diagnostics, root, depth = 0) {
1144
+ function markdownFiles(directory, recursive, diagnostics, root, depth = 0, onUnavailable) {
1081
1145
  if (!existsSync(directory))
1082
1146
  return [];
1083
1147
  if (!isRegularDirectory(directory)) {
1148
+ onUnavailable?.(directory, 'directory-invalid');
1084
1149
  diagnostics.push(issue("error", "APPSPEC_DIRECTORY_INVALID", "AppSpec 子目录必须是普通目录,不能是符号链接", relativePath(root, directory)));
1085
1150
  return [];
1086
1151
  }
@@ -1088,11 +1153,15 @@ function markdownFiles(directory, recursive, diagnostics, root, depth = 0) {
1088
1153
  const entries = readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
1089
1154
  if (entries.length > APP_SPEC_LIMITS.maximumDirectoryEntries) {
1090
1155
  diagnostics.push(issue("error", "APPSPEC_DIRECTORY_ENTRY_LIMIT_EXCEEDED", `AppSpec 单目录条目数超过 ${APP_SPEC_LIMITS.maximumDirectoryEntries} 上限`, relativePath(root, directory)));
1091
- entries.splice(APP_SPEC_LIMITS.maximumDirectoryEntries);
1156
+ for (const skipped of entries.splice(APP_SPEC_LIMITS.maximumDirectoryEntries)) {
1157
+ if (skipped.name.endsWith('.md'))
1158
+ onUnavailable?.(join(directory, skipped.name), 'directory-entry-budget');
1159
+ }
1092
1160
  }
1093
1161
  for (const entry of entries) {
1094
1162
  const path = join(directory, entry.name);
1095
1163
  if (entry.isSymbolicLink()) {
1164
+ onUnavailable?.(path, entry.name.endsWith('.md') ? 'file-invalid' : 'directory-invalid');
1096
1165
  diagnostics.push(issue("error", "APPSPEC_SYMLINK_FORBIDDEN", "AppSpec 不读取符号链接", relativePath(root, path)));
1097
1166
  continue;
1098
1167
  }
@@ -1101,7 +1170,7 @@ function markdownFiles(directory, recursive, diagnostics, root, depth = 0) {
1101
1170
  diagnostics.push(issue("error", "APPSPEC_HISTORY_DEPTH_EXCEEDED", "AppSpec 历史目录只允许 history/<year>/*.md", relativePath(root, path)));
1102
1171
  }
1103
1172
  else {
1104
- files.push(...markdownFiles(path, true, diagnostics, root, depth + 1));
1173
+ files.push(...markdownFiles(path, true, diagnostics, root, depth + 1, onUnavailable));
1105
1174
  }
1106
1175
  }
1107
1176
  else if (entry.isFile() && entry.name.endsWith(".md"))
@@ -1360,13 +1429,14 @@ function assertInside(root, path) {
1360
1429
  function stableError(code, detail) {
1361
1430
  return Object.assign(new Error(`${code}: ${detail}`), { code, retryable: false });
1362
1431
  }
1363
- function issue(severity, code, message, path) {
1432
+ function issue(severity, code, message, path, details) {
1364
1433
  return {
1365
1434
  schemaVersion: SCHEMA_VERSIONS.diagnostic,
1366
1435
  code,
1367
1436
  severity,
1368
1437
  message,
1369
1438
  path,
1439
+ ...(details ? { details } : {}),
1370
1440
  retryable: false,
1371
1441
  remediation: severity === "error"
1372
1442
  ? "修正记录后运行 openxiangda spec check;普通 check 可继续,正式发布按阶段核对"