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.
- package/dist/app-spec.d.ts +25 -2
- package/dist/app-spec.d.ts.map +1 -1
- package/dist/app-spec.js +102 -32
- package/dist/app-spec.js.map +1 -1
- package/dist/application-operations.d.ts +40 -0
- package/dist/application-operations.d.ts.map +1 -1
- package/dist/application-operations.js +14 -3
- package/dist/application-operations.js.map +1 -1
- package/dist/application-services.d.ts +42 -6
- package/dist/application-services.d.ts.map +1 -1
- package/dist/compiler/package-compiler.js +1 -1
- package/dist/compiler/package-compiler.js.map +1 -1
- package/dist/developer-operations.d.ts +10 -2
- package/dist/developer-operations.d.ts.map +1 -1
- package/dist/documentation.d.ts +7 -3
- package/dist/documentation.d.ts.map +1 -1
- package/dist/documentation.js +1 -0
- package/dist/documentation.js.map +1 -1
- package/dist/session.d.ts +1 -1
- package/dist/workflow-source-sync.d.ts.map +1 -1
- package/dist/workflow-source-sync.js +51 -8
- package/dist/workflow-source-sync.js.map +1 -1
- package/dist/workspace.js +1 -1
- package/dist/workspace.js.map +1 -1
- package/package.json +2 -2
package/dist/app-spec.d.ts
CHANGED
|
@@ -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;
|
package/dist/app-spec.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-spec.d.ts","sourceRoot":"","sources":["../src/app-spec.ts"],"names":[],"mappings":"AACA,OAAO,EAAqB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;
|
|
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(...
|
|
412
|
+
candidates.push(...discover(paths.capabilities).map(path => ({
|
|
405
413
|
path,
|
|
406
414
|
kind: "capability",
|
|
407
|
-
})), ...
|
|
415
|
+
})), ...discover(paths.activeChanges).map(path => ({
|
|
408
416
|
path,
|
|
409
417
|
kind: "change",
|
|
410
|
-
})), ...
|
|
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(...
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
const
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 可继续,正式发布按阶段核对"
|