@moldea.ai/core 5.0.1 → 7.0.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/README.md +12 -11
- package/dist/analysis-worker.js +180 -0
- package/dist/analysis-worker.js.map +1 -0
- package/dist/binding-coverage/index.d.ts +26 -0
- package/dist/binding-coverage/index.d.ts.map +1 -0
- package/dist/chunks/core-Cdeiu6xG.js +3364 -0
- package/dist/chunks/core-Cdeiu6xG.js.map +1 -0
- package/dist/chunks/errors-BE4DS3Ak.js +435 -0
- package/dist/chunks/errors-BE4DS3Ak.js.map +1 -0
- package/dist/chunks/{exceptions-HS9ofVy2.js → exceptions-IXsorE8t.js} +27 -28
- package/dist/chunks/exceptions-IXsorE8t.js.map +1 -0
- package/dist/chunks/lifecycle-D_RvxhDC.js +67 -0
- package/dist/chunks/lifecycle-D_RvxhDC.js.map +1 -0
- package/dist/chunks/options-5x9gu2Qb.js +604 -0
- package/dist/chunks/options-5x9gu2Qb.js.map +1 -0
- package/dist/chunks/repository-rpc-B_y0Kspf.js +124 -0
- package/dist/chunks/repository-rpc-B_y0Kspf.js.map +1 -0
- package/dist/chunks/scope-AGMcHUE_.js +1827 -0
- package/dist/chunks/scope-AGMcHUE_.js.map +1 -0
- package/dist/chunks/syntax-CVvExx4R.js +137 -0
- package/dist/chunks/syntax-CVvExx4R.js.map +1 -0
- package/dist/constants/index.d.ts +2 -1
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/contracts/index.d.ts +37 -27
- package/dist/contracts/index.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -4
- package/dist/core/index.d.ts.map +1 -1
- package/dist/decision/index.d.ts +1 -1
- package/dist/decision-graph/index.d.ts +9 -7
- package/dist/decision-graph/index.d.ts.map +1 -1
- package/dist/diagnostic-utilities/index.d.ts +6 -2
- package/dist/diagnostic-utilities/index.d.ts.map +1 -1
- package/dist/diagnostics/index.d.ts +21 -33
- package/dist/diagnostics/index.d.ts.map +1 -1
- package/dist/exceptions/index.d.ts +25 -10
- package/dist/exceptions/index.d.ts.map +1 -1
- package/dist/export-inventory/index.d.ts +3 -0
- package/dist/export-inventory/index.d.ts.map +1 -0
- package/dist/export-inventory/inventory.d.ts +22 -0
- package/dist/export-inventory/inventory.d.ts.map +1 -0
- package/dist/export-inventory/syntax.d.ts +4 -0
- package/dist/export-inventory/syntax.d.ts.map +1 -0
- package/dist/export-inventory/types.d.ts +9 -0
- package/dist/export-inventory/types.d.ts.map +1 -0
- package/dist/format/index.d.ts +8 -0
- package/dist/format/index.d.ts.map +1 -1
- package/dist/impact-pattern-validation/index.d.ts +2 -0
- package/dist/impact-pattern-validation/index.d.ts.map +1 -0
- package/dist/impact-pattern-validation/inspection.d.ts +24 -0
- package/dist/impact-pattern-validation/inspection.d.ts.map +1 -0
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -5776
- package/dist/integration-validation/index.d.ts +4 -0
- package/dist/integration-validation/index.d.ts.map +1 -0
- package/dist/integration-validation/inspection.d.ts +18 -0
- package/dist/integration-validation/inspection.d.ts.map +1 -0
- package/dist/integration-validation/parser.d.ts +9 -0
- package/dist/integration-validation/parser.d.ts.map +1 -0
- package/dist/integration-validation/types.d.ts +13 -0
- package/dist/integration-validation/types.d.ts.map +1 -0
- package/dist/manifest/index.d.ts +2 -4
- package/dist/manifest/index.d.ts.map +1 -1
- package/dist/manifest-validation/index.d.ts +7 -10
- package/dist/manifest-validation/index.d.ts.map +1 -1
- package/dist/node/index.d.ts +4 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node-inspection/analysis-worker.d.ts +2 -0
- package/dist/node-inspection/analysis-worker.d.ts.map +1 -0
- package/dist/node-inspection/client.d.ts +28 -0
- package/dist/node-inspection/client.d.ts.map +1 -0
- package/dist/node-inspection/constants.d.ts +21 -0
- package/dist/node-inspection/constants.d.ts.map +1 -0
- package/dist/node-inspection/errors.d.ts +11 -0
- package/dist/node-inspection/errors.d.ts.map +1 -0
- package/dist/node-inspection/lifecycle.d.ts +15 -0
- package/dist/node-inspection/lifecycle.d.ts.map +1 -0
- package/dist/node-inspection/protocol.d.ts +20 -0
- package/dist/node-inspection/protocol.d.ts.map +1 -0
- package/dist/node-inspection/repository-rpc.d.ts +28 -0
- package/dist/node-inspection/repository-rpc.d.ts.map +1 -0
- package/dist/node-inspection/runner.d.ts +2 -0
- package/dist/node-inspection/runner.d.ts.map +1 -0
- package/dist/node-inspection/types.d.ts +95 -0
- package/dist/node-inspection/types.d.ts.map +1 -0
- package/dist/node-runner.js +149 -0
- package/dist/node-runner.js.map +1 -0
- package/dist/node.js +335 -0
- package/dist/node.js.map +1 -0
- package/dist/options/index.d.ts +2 -14
- package/dist/options/index.d.ts.map +1 -1
- package/dist/project-inspection/index.d.ts +21 -1
- package/dist/project-inspection/index.d.ts.map +1 -1
- package/dist/project-validation/index.d.ts +33 -11
- package/dist/project-validation/index.d.ts.map +1 -1
- package/dist/repository-inspection-session/index.d.ts +2 -4
- package/dist/repository-inspection-session/index.d.ts.map +1 -1
- package/dist/repository-reference-validation/index.d.ts +12 -6
- package/dist/repository-reference-validation/index.d.ts.map +1 -1
- package/dist/retained-memory/index.d.ts +18 -0
- package/dist/retained-memory/index.d.ts.map +1 -0
- package/dist/scope/index.d.ts +3 -0
- package/dist/scope/index.d.ts.map +1 -0
- package/dist/scope/scope.d.ts +13 -0
- package/dist/scope/scope.d.ts.map +1 -0
- package/dist/scope-matching/index.d.ts +2 -1
- package/dist/scope-matching/index.d.ts.map +1 -1
- package/dist/scope-matching/matching.d.ts +11 -2
- package/dist/scope-matching/matching.d.ts.map +1 -1
- package/dist/scope-matching/patterns.d.ts +4 -0
- package/dist/scope-matching/patterns.d.ts.map +1 -0
- package/dist/scope.js +17 -0
- package/dist/scope.js.map +1 -0
- package/dist/universal-project-inspection/index.d.ts +13 -11
- package/dist/universal-project-inspection/index.d.ts.map +1 -1
- package/dist/warning-acceptance/acceptance.d.ts +11 -0
- package/dist/warning-acceptance/acceptance.d.ts.map +1 -0
- package/dist/warning-acceptance/index.d.ts +2 -0
- package/dist/warning-acceptance/index.d.ts.map +1 -0
- package/package.json +14 -6
- package/dist/adapter/index.d.ts +0 -131
- package/dist/adapter/index.d.ts.map +0 -1
- package/dist/adapter-availability/index.d.ts +0 -15
- package/dist/adapter-availability/index.d.ts.map +0 -1
- package/dist/adapter-execution/index.d.ts +0 -31
- package/dist/adapter-execution/index.d.ts.map +0 -1
- package/dist/adapter-repository/index.d.ts +0 -27
- package/dist/adapter-repository/index.d.ts.map +0 -1
- package/dist/adapter-retained-memory/index.d.ts +0 -8
- package/dist/adapter-retained-memory/index.d.ts.map +0 -1
- package/dist/adapter-validation/index.d.ts +0 -43
- package/dist/adapter-validation/index.d.ts.map +0 -1
- package/dist/adapter.js +0 -115
- package/dist/adapter.js.map +0 -1
- package/dist/chunks/exceptions-HS9ofVy2.js.map +0 -1
- package/dist/index.js.map +0 -1
|
@@ -0,0 +1,3364 @@
|
|
|
1
|
+
import { E as parseDecisionIdFromPath, S as isRuntimeGuidancePath, T as isVariableId, a as escapeJsonPointerSegment, c as freezeRecursively, d as hasNonWhitespace, g as isContextPath, i as createCoreDiagnosticCollector, k as trimRepositoryFormatWhitespace, l as compareExactStrings, n as normalizeCoreOptions, o as normalizeDiagnostics, r as countDiagnosticsBySeverity, s as createNullPrototypeRecord, t as createCoreOperationOptionsSnapshot, u as countUnicodeScalars } from "./options-5x9gu2Qb.js";
|
|
2
|
+
import { n as CoreOperationException } from "./exceptions-IXsorE8t.js";
|
|
3
|
+
import { a as createSourceLocator, c as calculateContentDigest, i as parseStrictYaml, l as calculateNormalizedTextDigest, n as inspectManifestDocument, o as collectManifestScopeDeclarations, r as parseManifestDocument, s as matchesRepositoryGlob, t as matchManifestScope, u as normalizeTextDocument } from "./scope-AGMcHUE_.js";
|
|
4
|
+
import { REPOSITORY_ROOT, RepositorySourceException, compareRepositoryPaths, createRepositoryIdentity, isRepositoryPath, parseRepositoryPath } from "@moldea.ai/repository";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __exportAll = (all, no_symbols) => {
|
|
8
|
+
let target = {};
|
|
9
|
+
for (var name in all) __defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true
|
|
12
|
+
});
|
|
13
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
14
|
+
return target;
|
|
15
|
+
};
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region src/canonical-content-page/index.ts
|
|
18
|
+
var decoder = new TextDecoder("utf-8", {
|
|
19
|
+
fatal: true,
|
|
20
|
+
ignoreBOM: true
|
|
21
|
+
});
|
|
22
|
+
var invalidArgument$2 = () => {
|
|
23
|
+
throw new CoreOperationException({
|
|
24
|
+
code: "INVALID_ARGUMENT",
|
|
25
|
+
operation: "read-canonical-content-page"
|
|
26
|
+
});
|
|
27
|
+
};
|
|
28
|
+
var invalidContent = () => {
|
|
29
|
+
throw new CoreOperationException({
|
|
30
|
+
code: "CONTENT_INVALID",
|
|
31
|
+
operation: "read-canonical-content-page"
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
var isRepositoryReader$1 = (candidate) => typeof candidate === "object" && candidate !== null && typeof candidate.getEntry === "function" && typeof candidate.readFilePage === "function";
|
|
35
|
+
var readBytes = async (repository, path, offset, maxBytes, signal) => {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
let byteLength = 0;
|
|
38
|
+
let nextOffset = offset;
|
|
39
|
+
let lastPage = null;
|
|
40
|
+
let totalBytes = null;
|
|
41
|
+
while (byteLength < maxBytes) {
|
|
42
|
+
const page = await repository.readFilePage(path, {
|
|
43
|
+
maxBytes: maxBytes - byteLength,
|
|
44
|
+
offset: nextOffset,
|
|
45
|
+
...signal === void 0 ? {} : { signal }
|
|
46
|
+
});
|
|
47
|
+
if (page.snapshot.id !== repository.snapshot.id || page.offset !== nextOffset || page.bytes.byteLength > maxBytes - byteLength || page.totalBytes < page.offset + page.bytes.byteLength || totalBytes !== null && page.totalBytes !== totalBytes || page.bytes.byteLength === 0 && !page.isComplete) throw new RepositorySourceException({
|
|
48
|
+
code: "INVALID_SOURCE_DATA",
|
|
49
|
+
operation: "read-file-page",
|
|
50
|
+
path,
|
|
51
|
+
retryable: false
|
|
52
|
+
});
|
|
53
|
+
chunks.push(page.bytes);
|
|
54
|
+
byteLength += page.bytes.byteLength;
|
|
55
|
+
totalBytes = page.totalBytes;
|
|
56
|
+
lastPage = page;
|
|
57
|
+
if (page.isComplete || byteLength === maxBytes) break;
|
|
58
|
+
if (page.nextOffset !== page.offset + page.bytes.byteLength) throw new RepositorySourceException({
|
|
59
|
+
code: "INVALID_SOURCE_DATA",
|
|
60
|
+
operation: "read-file-page",
|
|
61
|
+
path,
|
|
62
|
+
retryable: false
|
|
63
|
+
});
|
|
64
|
+
nextOffset = page.nextOffset;
|
|
65
|
+
}
|
|
66
|
+
if (lastPage === null) return invalidArgument$2();
|
|
67
|
+
const bytes = new Uint8Array(byteLength);
|
|
68
|
+
let writeOffset = 0;
|
|
69
|
+
for (const chunk of chunks) {
|
|
70
|
+
bytes.set(chunk, writeOffset);
|
|
71
|
+
writeOffset += chunk.byteLength;
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
bytes,
|
|
75
|
+
lastPage
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
var decodeSafePrefix = (bytes, maximumBytes) => {
|
|
79
|
+
const minimumCandidate = Math.max(0, maximumBytes - 3);
|
|
80
|
+
for (let end = maximumBytes; end >= minimumCandidate; end -= 1) try {
|
|
81
|
+
const content = decoder.decode(bytes.subarray(0, end));
|
|
82
|
+
if (end === 0 && bytes.byteLength > 0) continue;
|
|
83
|
+
return [content, end];
|
|
84
|
+
} catch {}
|
|
85
|
+
return invalidContent();
|
|
86
|
+
};
|
|
87
|
+
/** Reads one explicit canonical file range without materializing the complete file. */
|
|
88
|
+
var readCanonicalContentPage = async (input, options) => {
|
|
89
|
+
if (typeof input !== "object" || input === null || Reflect.ownKeys(input).some((key) => key !== "maxBytes" && key !== "offset" && key !== "path" && key !== "repository" && key !== "signal") || !isRepositoryReader$1(input.repository) || !Number.isSafeInteger(input.maxBytes) || input.maxBytes < 1 || input.maxBytes > options.limits.maxFileBytes || !Number.isSafeInteger(input.offset) || input.offset < 0) return invalidArgument$2();
|
|
90
|
+
const path = parseRepositoryPath(input.path);
|
|
91
|
+
if (!path.startsWith("/moldea/")) return invalidArgument$2();
|
|
92
|
+
const operationOptions = input.signal === void 0 ? void 0 : { signal: input.signal };
|
|
93
|
+
const entry = await input.repository.getEntry(path, operationOptions);
|
|
94
|
+
if (entry === null) throw new RepositorySourceException({
|
|
95
|
+
code: "ENTRY_NOT_FOUND",
|
|
96
|
+
operation: "read-file-page",
|
|
97
|
+
path,
|
|
98
|
+
retryable: false
|
|
99
|
+
});
|
|
100
|
+
if (entry.type !== "file" || entry.byteLength === null) throw new RepositorySourceException({
|
|
101
|
+
code: "ENTRY_NOT_FILE",
|
|
102
|
+
operation: "read-file-page",
|
|
103
|
+
path,
|
|
104
|
+
retryable: false
|
|
105
|
+
});
|
|
106
|
+
if (entry.byteLength > options.limits.maxFileBytes) throw new CoreOperationException({
|
|
107
|
+
code: "RESOURCE_LIMIT_EXCEEDED",
|
|
108
|
+
limit: "maxFileBytes",
|
|
109
|
+
limitMaximum: options.limits.maxFileBytes,
|
|
110
|
+
nextAction: "reduce-input-or-increase-limit",
|
|
111
|
+
observedUsage: entry.byteLength,
|
|
112
|
+
operation: "read-canonical-content-page"
|
|
113
|
+
});
|
|
114
|
+
if (input.offset > entry.byteLength) return invalidArgument$2();
|
|
115
|
+
if (input.offset === entry.byteLength) return freezeRecursively({
|
|
116
|
+
byteEnd: input.offset,
|
|
117
|
+
byteStart: input.offset,
|
|
118
|
+
content: "",
|
|
119
|
+
contentIdentity: entry.contentIdentity,
|
|
120
|
+
isComplete: true,
|
|
121
|
+
nextOffset: null,
|
|
122
|
+
path,
|
|
123
|
+
source: input.repository.snapshot,
|
|
124
|
+
totalBytes: entry.byteLength
|
|
125
|
+
});
|
|
126
|
+
const lookaheadBytes = Math.min(entry.byteLength - input.offset, input.maxBytes + 3);
|
|
127
|
+
const { bytes, lastPage } = await readBytes(input.repository, path, input.offset, lookaheadBytes, input.signal);
|
|
128
|
+
if (lastPage.totalBytes !== entry.byteLength || (bytes[0] & 192) === 128) return invalidArgument$2();
|
|
129
|
+
const [content, consumedBytes] = decodeSafePrefix(bytes, Math.min(input.maxBytes, bytes.byteLength));
|
|
130
|
+
const byteEnd = input.offset + consumedBytes;
|
|
131
|
+
const isComplete = byteEnd === entry.byteLength;
|
|
132
|
+
return freezeRecursively({
|
|
133
|
+
byteEnd,
|
|
134
|
+
byteStart: input.offset,
|
|
135
|
+
content,
|
|
136
|
+
contentIdentity: entry.contentIdentity,
|
|
137
|
+
isComplete,
|
|
138
|
+
nextOffset: isComplete ? null : byteEnd,
|
|
139
|
+
path,
|
|
140
|
+
source: input.repository.snapshot,
|
|
141
|
+
totalBytes: entry.byteLength
|
|
142
|
+
});
|
|
143
|
+
};
|
|
144
|
+
//#endregion
|
|
145
|
+
//#region src/decision-validation/index.ts
|
|
146
|
+
var DECISION_STATUSES = /* @__PURE__ */ new Set([
|
|
147
|
+
"accepted",
|
|
148
|
+
"proposed",
|
|
149
|
+
"rejected",
|
|
150
|
+
"superseded"
|
|
151
|
+
]);
|
|
152
|
+
var DECISION_ID_PATTERN = /^\d{13}$/u;
|
|
153
|
+
var CREATED_AT_PATTERN = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}Z$/u;
|
|
154
|
+
var getEntity = (decisionId) => {
|
|
155
|
+
return decisionId === null ? null : { decisionId };
|
|
156
|
+
};
|
|
157
|
+
var addInvalidFrontmatter = (context, range, reason, pointer = null) => {
|
|
158
|
+
context.diagnostics.add({
|
|
159
|
+
code: "MOLDEA_DECISION_FRONTMATTER_INVALID",
|
|
160
|
+
details: { reason },
|
|
161
|
+
entity: getEntity(context.decisionId),
|
|
162
|
+
path: context.path,
|
|
163
|
+
pointer,
|
|
164
|
+
range
|
|
165
|
+
});
|
|
166
|
+
};
|
|
167
|
+
var isCanonicalCreatedAt = (value) => {
|
|
168
|
+
if (!CREATED_AT_PATTERN.test(value)) return false;
|
|
169
|
+
const timestamp = Date.parse(value);
|
|
170
|
+
return Number.isFinite(timestamp) && new Date(timestamp).toISOString() === value;
|
|
171
|
+
};
|
|
172
|
+
var readMapping = (node, context) => {
|
|
173
|
+
if (node.kind !== "mapping") {
|
|
174
|
+
addInvalidFrontmatter(context, node.range, "mapping-required");
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
const entries = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const entry of node.entries) {
|
|
179
|
+
if (entry.key.kind !== "scalar" || typeof entry.key.value !== "string") {
|
|
180
|
+
addInvalidFrontmatter(context, entry.key.range, "string-key-required");
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const key = entry.key.value;
|
|
184
|
+
if (key !== "status" && key !== "createdAt" && key !== "supersedes") {
|
|
185
|
+
context.diagnostics.add({
|
|
186
|
+
code: "MOLDEA_DECISION_PROPERTY_UNKNOWN",
|
|
187
|
+
entity: getEntity(context.decisionId),
|
|
188
|
+
path: context.path,
|
|
189
|
+
pointer: `/${escapeJsonPointerSegment(key)}`,
|
|
190
|
+
range: entry.key.range
|
|
191
|
+
});
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
entries.set(key, entry.value);
|
|
195
|
+
}
|
|
196
|
+
return entries;
|
|
197
|
+
};
|
|
198
|
+
var readStatus = (node, context) => {
|
|
199
|
+
if (node?.kind !== "scalar" || typeof node.value !== "string" || !DECISION_STATUSES.has(node.value)) {
|
|
200
|
+
context.diagnostics.add({
|
|
201
|
+
code: "MOLDEA_DECISION_STATUS_INVALID",
|
|
202
|
+
entity: getEntity(context.decisionId),
|
|
203
|
+
path: context.path,
|
|
204
|
+
pointer: "/status",
|
|
205
|
+
range: node?.range ?? null
|
|
206
|
+
});
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
return node.value;
|
|
210
|
+
};
|
|
211
|
+
var readCreatedAt = (node, context) => {
|
|
212
|
+
if (node?.kind !== "scalar" || typeof node.value !== "string" || !isCanonicalCreatedAt(node.value)) {
|
|
213
|
+
context.diagnostics.add({
|
|
214
|
+
code: "MOLDEA_DECISION_CREATED_AT_INVALID",
|
|
215
|
+
entity: getEntity(context.decisionId),
|
|
216
|
+
path: context.path,
|
|
217
|
+
pointer: "/createdAt",
|
|
218
|
+
range: node?.range ?? null
|
|
219
|
+
});
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
if (context.decisionId !== null && Date.parse(node.value) !== Number(context.decisionId)) context.diagnostics.add({
|
|
223
|
+
code: "MOLDEA_DECISION_TIMESTAMP_MISMATCH",
|
|
224
|
+
entity: getEntity(context.decisionId),
|
|
225
|
+
path: context.path,
|
|
226
|
+
pointer: "/createdAt",
|
|
227
|
+
range: node.range
|
|
228
|
+
});
|
|
229
|
+
return node.value;
|
|
230
|
+
};
|
|
231
|
+
var readSupersedes = (node, context) => {
|
|
232
|
+
if (node === void 0) return [];
|
|
233
|
+
if (node.kind !== "sequence" || node.items.length === 0) {
|
|
234
|
+
addInvalidFrontmatter(context, node.range, "non-empty-supersedes-sequence-required", "/supersedes");
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const supersedes = [];
|
|
238
|
+
const seen = /* @__PURE__ */ new Set();
|
|
239
|
+
node.items.forEach((item, index) => {
|
|
240
|
+
const pointer = `/supersedes/${index}`;
|
|
241
|
+
if (item.kind !== "scalar" || typeof item.value !== "string" || !DECISION_ID_PATTERN.test(item.value)) {
|
|
242
|
+
addInvalidFrontmatter(context, item.range, "decision-id-string-required", pointer);
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (seen.has(item.value)) {
|
|
246
|
+
context.diagnostics.add({
|
|
247
|
+
code: "MOLDEA_ID_DUPLICATE",
|
|
248
|
+
entity: getEntity(context.decisionId),
|
|
249
|
+
path: context.path,
|
|
250
|
+
pointer,
|
|
251
|
+
range: item.range
|
|
252
|
+
});
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
seen.add(item.value);
|
|
256
|
+
supersedes.push(item.value);
|
|
257
|
+
if (item.value === context.decisionId) context.diagnostics.add({
|
|
258
|
+
code: "MOLDEA_DECISION_SELF_SUPERSESSION",
|
|
259
|
+
entity: getEntity(context.decisionId),
|
|
260
|
+
path: context.path,
|
|
261
|
+
pointer,
|
|
262
|
+
range: item.range
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
return supersedes.sort(compareExactStrings);
|
|
266
|
+
};
|
|
267
|
+
/**
|
|
268
|
+
* Locates exact decision frontmatter delimiters and preserves the complete Markdown body.
|
|
269
|
+
* @param value The complete normalized decision document.
|
|
270
|
+
* @param locator The full-document scalar-aware source locator.
|
|
271
|
+
* @param context The decision diagnostic context.
|
|
272
|
+
* @returns The YAML prefix and exact body, or `null` when delimiters are unavailable.
|
|
273
|
+
*/
|
|
274
|
+
var extractDecisionSections = (value, locator, context) => {
|
|
275
|
+
const firstLineEnd = value.indexOf("\n");
|
|
276
|
+
const openingLineEnd = firstLineEnd === -1 ? value.length : firstLineEnd;
|
|
277
|
+
if (value.slice(0, openingLineEnd) !== "---") {
|
|
278
|
+
context.diagnostics.add({
|
|
279
|
+
code: "MOLDEA_DECISION_FRONTMATTER_MISSING",
|
|
280
|
+
entity: getEntity(context.decisionId),
|
|
281
|
+
path: context.path,
|
|
282
|
+
range: locator.locateRange(0, openingLineEnd)
|
|
283
|
+
});
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
let lineStart = firstLineEnd === -1 ? value.length : firstLineEnd + 1;
|
|
287
|
+
while (lineStart <= value.length) {
|
|
288
|
+
const nextLineBreak = value.indexOf("\n", lineStart);
|
|
289
|
+
const lineEnd = nextLineBreak === -1 ? value.length : nextLineBreak;
|
|
290
|
+
if (value.slice(lineStart, lineEnd) === "---") {
|
|
291
|
+
const bodyStart = nextLineBreak === -1 ? lineEnd : nextLineBreak + 1;
|
|
292
|
+
return {
|
|
293
|
+
body: value.slice(bodyStart),
|
|
294
|
+
bodyRange: locator.locateRange(bodyStart, value.length),
|
|
295
|
+
yaml: value.slice(0, lineStart)
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
if (nextLineBreak === -1) break;
|
|
299
|
+
lineStart = nextLineBreak + 1;
|
|
300
|
+
}
|
|
301
|
+
context.diagnostics.add({
|
|
302
|
+
code: "MOLDEA_DECISION_FRONTMATTER_MISSING",
|
|
303
|
+
entity: getEntity(context.decisionId),
|
|
304
|
+
path: context.path,
|
|
305
|
+
range: locator.locateRange(value.length, value.length)
|
|
306
|
+
});
|
|
307
|
+
return null;
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Validates one parsed decision-frontmatter mapping without resolving other files.
|
|
311
|
+
* @param node The strict parser-neutral YAML root.
|
|
312
|
+
* @param context The decision diagnostic context.
|
|
313
|
+
* @returns Canonical decision metadata when every document-level rule is valid.
|
|
314
|
+
*/
|
|
315
|
+
var validateDecisionFrontmatter = (node, context) => {
|
|
316
|
+
const initialDiagnosticCount = context.diagnostics.size;
|
|
317
|
+
const entries = readMapping(node, context);
|
|
318
|
+
if (entries === null) return null;
|
|
319
|
+
const status = readStatus(entries.get("status"), context);
|
|
320
|
+
const createdAt = readCreatedAt(entries.get("createdAt"), context);
|
|
321
|
+
const supersedes = readSupersedes(entries.get("supersedes"), context);
|
|
322
|
+
if (status === null || createdAt === null || supersedes === null || context.diagnostics.size > initialDiagnosticCount) return null;
|
|
323
|
+
return {
|
|
324
|
+
createdAt,
|
|
325
|
+
status,
|
|
326
|
+
supersedes
|
|
327
|
+
};
|
|
328
|
+
};
|
|
329
|
+
/** Adds the decision-body diagnostic when the preserved Markdown has no content. */
|
|
330
|
+
var validateDecisionBody = (body, range, context) => {
|
|
331
|
+
if (hasNonWhitespace(body)) return true;
|
|
332
|
+
context.diagnostics.add({
|
|
333
|
+
code: "MOLDEA_DECISION_BODY_EMPTY",
|
|
334
|
+
entity: getEntity(context.decisionId),
|
|
335
|
+
path: context.path,
|
|
336
|
+
range
|
|
337
|
+
});
|
|
338
|
+
return false;
|
|
339
|
+
};
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/decision/index.ts
|
|
342
|
+
/**
|
|
343
|
+
* Parses and validates one complete decision document without resolving cross-file relationships.
|
|
344
|
+
* @param input The canonical logical path and exact decision text or bytes.
|
|
345
|
+
* @param options The immutable Core limit snapshot.
|
|
346
|
+
* @param operation The owning public or repository-level operation for typed failures.
|
|
347
|
+
* @returns A frozen all-or-nothing decision parse result.
|
|
348
|
+
* @throws
|
|
349
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
350
|
+
* - INVALID_ARGUMENT: The Core operation received an invalid argument.
|
|
351
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
352
|
+
*/
|
|
353
|
+
var parseDecisionDocument = async (input, options, operation = "parse-decision") => {
|
|
354
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
355
|
+
const normalized = normalizeTextDocument(input, options.limits, operation);
|
|
356
|
+
const path = parseRepositoryPath(input.path);
|
|
357
|
+
const diagnostics = createCoreDiagnosticCollector(options.limits, operation);
|
|
358
|
+
const decisionId = parseDecisionIdFromPath(path);
|
|
359
|
+
if (decisionId === null) diagnostics.add({
|
|
360
|
+
code: "MOLDEA_DECISION_FILENAME_INVALID",
|
|
361
|
+
path
|
|
362
|
+
});
|
|
363
|
+
for (const diagnostic of normalized.diagnostics) diagnostics.merge(diagnostic);
|
|
364
|
+
if (!normalized.valid || normalized.text === null) return freezeRecursively({
|
|
365
|
+
decision: null,
|
|
366
|
+
diagnostics: diagnostics.finalize(),
|
|
367
|
+
valid: false
|
|
368
|
+
});
|
|
369
|
+
const context = {
|
|
370
|
+
decisionId,
|
|
371
|
+
diagnostics,
|
|
372
|
+
path
|
|
373
|
+
};
|
|
374
|
+
const locator = createSourceLocator(normalized.text.value);
|
|
375
|
+
const sections = extractDecisionSections(normalized.text.value, locator, context);
|
|
376
|
+
let metadata = null;
|
|
377
|
+
if (sections !== null) {
|
|
378
|
+
validateDecisionBody(sections.body, sections.bodyRange, context);
|
|
379
|
+
const parsed = parseStrictYaml(sections.yaml, path, locator, diagnostics);
|
|
380
|
+
if (parsed.valid && parsed.value !== null) metadata = validateDecisionFrontmatter(parsed.value, context);
|
|
381
|
+
}
|
|
382
|
+
if (decisionId === null || sections === null || metadata === null || diagnostics.size > 0) return freezeRecursively({
|
|
383
|
+
decision: null,
|
|
384
|
+
diagnostics: diagnostics.finalize(),
|
|
385
|
+
valid: false
|
|
386
|
+
});
|
|
387
|
+
const digest = await calculateNormalizedTextDigest(normalized.text);
|
|
388
|
+
const asset = {
|
|
389
|
+
content: normalized.text.value,
|
|
390
|
+
digest,
|
|
391
|
+
path,
|
|
392
|
+
scalarLength: normalized.text.scalarLength,
|
|
393
|
+
utf8ByteLength: normalized.text.utf8ByteLength
|
|
394
|
+
};
|
|
395
|
+
return freezeRecursively({
|
|
396
|
+
decision: {
|
|
397
|
+
asset,
|
|
398
|
+
body: sections.body,
|
|
399
|
+
createdAt: metadata.createdAt,
|
|
400
|
+
id: decisionId,
|
|
401
|
+
path,
|
|
402
|
+
status: metadata.status,
|
|
403
|
+
supersedes: metadata.supersedes
|
|
404
|
+
},
|
|
405
|
+
diagnostics: diagnostics.finalize(),
|
|
406
|
+
valid: true
|
|
407
|
+
});
|
|
408
|
+
};
|
|
409
|
+
//#endregion
|
|
410
|
+
//#region src/project-agent-assignments/index.ts
|
|
411
|
+
/** Iterates content-free agent-to-runtime assignments without retaining an intermediate array. */
|
|
412
|
+
var iterateProjectAgentAssignments = function* (project) {
|
|
413
|
+
for (const agent of project.agents) yield {
|
|
414
|
+
agentId: agent.id,
|
|
415
|
+
runtimeId: agent.declaration.runtime.id
|
|
416
|
+
};
|
|
417
|
+
};
|
|
418
|
+
//#endregion
|
|
419
|
+
//#region src/project-metadata/index.ts
|
|
420
|
+
var createAssetItem = (asset, kind, agentId = null, decisionId = null) => ({
|
|
421
|
+
agentId,
|
|
422
|
+
byteLength: asset.utf8ByteLength,
|
|
423
|
+
canonicalDigest: null,
|
|
424
|
+
decisionId,
|
|
425
|
+
digest: asset.digest,
|
|
426
|
+
kind,
|
|
427
|
+
path: asset.path,
|
|
428
|
+
scalarLength: asset.scalarLength
|
|
429
|
+
});
|
|
430
|
+
/** Iterates content-free asset metadata without retaining a second project-sized collection. */
|
|
431
|
+
var iterateProjectMetadata = function* (project) {
|
|
432
|
+
yield createAssetItem(project.manifest.asset, "manifest");
|
|
433
|
+
yield createAssetItem(project.project, "project");
|
|
434
|
+
for (const context of project.context) yield createAssetItem(context.asset, "context");
|
|
435
|
+
for (const { decision } of project.decisions) yield createAssetItem(decision.asset, "decision", null, decision.id);
|
|
436
|
+
for (const runtime of project.runtimes) yield createAssetItem(runtime.asset, "runtime-guidance");
|
|
437
|
+
for (const agent of project.agents) {
|
|
438
|
+
yield createAssetItem(agent.description.asset, "agent-description", agent.id);
|
|
439
|
+
yield createAssetItem(agent.instruction, "agent-instruction", agent.id);
|
|
440
|
+
if (agent.handoffDescription !== null) yield createAssetItem(agent.handoffDescription.asset, "agent-handoff-description", agent.id);
|
|
441
|
+
for (const mirror of agent.mirrors) yield {
|
|
442
|
+
agentId: agent.id,
|
|
443
|
+
byteLength: mirror.byteLength,
|
|
444
|
+
canonicalDigest: mirror.canonicalDigest,
|
|
445
|
+
decisionId: null,
|
|
446
|
+
digest: mirror.digest,
|
|
447
|
+
kind: "mirror",
|
|
448
|
+
path: mirror.path,
|
|
449
|
+
scalarLength: mirror.scalarLength
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
/** Creates the content-free summary for one validated project snapshot. */
|
|
454
|
+
var createProjectSummary = (project) => ({
|
|
455
|
+
counts: {
|
|
456
|
+
agents: project.agents.length,
|
|
457
|
+
context: project.context.length,
|
|
458
|
+
decisions: project.decisions.length,
|
|
459
|
+
mirrors: project.agents.reduce((count, agent) => count + agent.mirrors.length, 0),
|
|
460
|
+
runtimes: project.runtimes.length,
|
|
461
|
+
unresolved: project.agents.reduce((count, agent) => count + Object.keys(agent.declaration.unresolved ?? {}).length, Object.keys(project.unresolved).length)
|
|
462
|
+
},
|
|
463
|
+
manifestDigest: project.manifest.asset.digest,
|
|
464
|
+
manifestPath: project.manifest.asset.path,
|
|
465
|
+
projectDigest: project.project.digest,
|
|
466
|
+
projectPath: project.project.path
|
|
467
|
+
});
|
|
468
|
+
//#endregion
|
|
469
|
+
//#region src/binding-coverage/index.ts
|
|
470
|
+
/** Counts every declared, existing binding by its actual export-check outcome. */
|
|
471
|
+
var countBindingCoverage = (coverage) => ({
|
|
472
|
+
bindings: coverage.length,
|
|
473
|
+
exportsPassed: coverage.filter((record) => record.exportCheck === "passed").length,
|
|
474
|
+
exportsFailed: coverage.filter((record) => record.exportCheck === "failed").length,
|
|
475
|
+
exportsNotChecked: coverage.filter((record) => record.exportCheck === "not-checked").length
|
|
476
|
+
});
|
|
477
|
+
//#endregion
|
|
478
|
+
//#region src/retained-memory/index.ts
|
|
479
|
+
var ledgers = /* @__PURE__ */ new WeakMap();
|
|
480
|
+
/** Registers the sole memory owner for one internal reader. */
|
|
481
|
+
var registerRetainedMemory = (reader, ledger) => {
|
|
482
|
+
if (ledgers.has(reader)) throw new TypeError("The inspection reader is already registered.");
|
|
483
|
+
ledgers.set(reader, ledger);
|
|
484
|
+
};
|
|
485
|
+
/** Returns the ledger shared by all phases of a repository inspection. */
|
|
486
|
+
var getRetainedMemory = (reader) => {
|
|
487
|
+
const ledger = ledgers.get(reader);
|
|
488
|
+
if (ledger === void 0) throw new TypeError("The inspection reader has no retained-memory ledger.");
|
|
489
|
+
return ledger;
|
|
490
|
+
};
|
|
491
|
+
/** Reserves retained coverage in production sessions and leaves standalone file-check readers untracked. */
|
|
492
|
+
var reserveRetainedBytesIfTracked = (reader, bytes) => {
|
|
493
|
+
ledgers.get(reader)?.reserve(bytes);
|
|
494
|
+
};
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region src/repository-inspection-session/index.ts
|
|
497
|
+
var MANIFEST_PATH$3 = parseRepositoryPath("/moldea/moldea.yaml");
|
|
498
|
+
var SOURCE_PAGE_ENTRIES = 256;
|
|
499
|
+
var SOURCE_PAGE_BYTES = 65536;
|
|
500
|
+
var CANONICAL_BYTE_RETENTION_MULTIPLIER = 4;
|
|
501
|
+
var RETAINED_PATH_FIXED_BYTES = 64;
|
|
502
|
+
var RETAINED_PATH_BYTE_MULTIPLIER = 2;
|
|
503
|
+
var encoder$1 = new TextEncoder();
|
|
504
|
+
var invalidSourceData$3 = (operation, path) => {
|
|
505
|
+
throw new RepositorySourceException({
|
|
506
|
+
code: "INVALID_SOURCE_DATA",
|
|
507
|
+
operation,
|
|
508
|
+
path,
|
|
509
|
+
retryable: false
|
|
510
|
+
});
|
|
511
|
+
};
|
|
512
|
+
var copyReaderEntry$1 = (candidate, operation, fallbackPath, expectedPath) => {
|
|
513
|
+
if (typeof candidate !== "object" || candidate === null) return invalidSourceData$3(operation, fallbackPath);
|
|
514
|
+
const record = candidate;
|
|
515
|
+
const pathCandidate = record["path"];
|
|
516
|
+
const typeCandidate = record["type"];
|
|
517
|
+
const byteLength = record["byteLength"];
|
|
518
|
+
const contentIdentity = record["contentIdentity"];
|
|
519
|
+
if (typeof pathCandidate !== "string" || !isRepositoryPath(pathCandidate)) return invalidSourceData$3(operation, fallbackPath);
|
|
520
|
+
const path = parseRepositoryPath(pathCandidate);
|
|
521
|
+
if (expectedPath !== void 0 && path !== expectedPath) return invalidSourceData$3(operation, path);
|
|
522
|
+
if (typeCandidate !== "file" && typeCandidate !== "directory" && typeCandidate !== "symlink") return invalidSourceData$3(operation, path);
|
|
523
|
+
if (typeCandidate === "file" ? !Number.isSafeInteger(byteLength) || byteLength < 0 || contentIdentity !== null && typeof contentIdentity !== "string" : byteLength !== null || contentIdentity !== null) return invalidSourceData$3(operation, path);
|
|
524
|
+
return {
|
|
525
|
+
byteLength,
|
|
526
|
+
contentIdentity,
|
|
527
|
+
path,
|
|
528
|
+
type: typeCandidate
|
|
529
|
+
};
|
|
530
|
+
};
|
|
531
|
+
var isStrictDescendant = (path, prefix) => {
|
|
532
|
+
if (path === prefix) return false;
|
|
533
|
+
const descendantPrefix = prefix === REPOSITORY_ROOT ? REPOSITORY_ROOT : `${prefix}/`;
|
|
534
|
+
return path.startsWith(descendantPrefix);
|
|
535
|
+
};
|
|
536
|
+
var throwResourceLimitExceeded = (limit, limitMaximum, observedUsage) => {
|
|
537
|
+
throw new CoreOperationException({
|
|
538
|
+
code: "RESOURCE_LIMIT_EXCEEDED",
|
|
539
|
+
limit,
|
|
540
|
+
limitMaximum,
|
|
541
|
+
nextAction: "reduce-input-or-increase-limit",
|
|
542
|
+
observedUsage,
|
|
543
|
+
operation: "validate-project"
|
|
544
|
+
});
|
|
545
|
+
};
|
|
546
|
+
var createSourceOptions = (sessionSignal, operationSignal) => {
|
|
547
|
+
const signal = sessionSignal === void 0 ? operationSignal : operationSignal === void 0 || operationSignal === sessionSignal ? sessionSignal : AbortSignal.any([sessionSignal, operationSignal]);
|
|
548
|
+
return signal === void 0 ? void 0 : { signal };
|
|
549
|
+
};
|
|
550
|
+
/**
|
|
551
|
+
* Creates the isolated reader state for one repository inspection.
|
|
552
|
+
* @param repository The source-neutral reader bound to one coherent snapshot.
|
|
553
|
+
* @param limits The immutable resource limits for the inspection.
|
|
554
|
+
* @param signal Optional cancellation shared by every inspection operation.
|
|
555
|
+
* @returns A session that pages source metadata and range-reads bounded canonical files.
|
|
556
|
+
* @throws
|
|
557
|
+
* - ABORTED: The Core operation was aborted.
|
|
558
|
+
*/
|
|
559
|
+
var createRepositoryInspectionSession = (repository, limits, signal) => {
|
|
560
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
561
|
+
let canonicalBytes = 0;
|
|
562
|
+
let peakRetainedBytes = 0;
|
|
563
|
+
let retainedBytes = 0;
|
|
564
|
+
let totalBytesRead = 0;
|
|
565
|
+
const createAbortedException = (abortedSignal) => {
|
|
566
|
+
return new CoreOperationException({
|
|
567
|
+
cause: abortedSignal.reason,
|
|
568
|
+
code: "ABORTED",
|
|
569
|
+
operation: "validate-project"
|
|
570
|
+
});
|
|
571
|
+
};
|
|
572
|
+
const reserveRetainedBytes = (byteLength) => {
|
|
573
|
+
if (!Number.isSafeInteger(byteLength) || byteLength < 0 || byteLength > limits.maxRetainedBytes - retainedBytes) return throwResourceLimitExceeded("maxRetainedBytes", limits.maxRetainedBytes, retainedBytes + byteLength);
|
|
574
|
+
retainedBytes += byteLength;
|
|
575
|
+
peakRetainedBytes = Math.max(peakRetainedBytes, retainedBytes);
|
|
576
|
+
};
|
|
577
|
+
const observeTransientBytes = (byteLength) => {
|
|
578
|
+
if (!Number.isSafeInteger(byteLength) || byteLength < 0 || byteLength > limits.maxRetainedBytes - retainedBytes) return throwResourceLimitExceeded("maxRetainedBytes", limits.maxRetainedBytes, retainedBytes + byteLength);
|
|
579
|
+
peakRetainedBytes = Math.max(peakRetainedBytes, retainedBytes + byteLength);
|
|
580
|
+
};
|
|
581
|
+
const registerPath = (path) => {
|
|
582
|
+
if (path === REPOSITORY_ROOT || seenPaths.has(path)) return;
|
|
583
|
+
if (seenPaths.size >= limits.maxEntries) return throwResourceLimitExceeded("maxEntries", limits.maxEntries, seenPaths.size + 1);
|
|
584
|
+
reserveRetainedBytes(RETAINED_PATH_FIXED_BYTES + encoder$1.encode(path).byteLength * RETAINED_PATH_BYTE_MULTIPLIER);
|
|
585
|
+
seenPaths.add(path);
|
|
586
|
+
};
|
|
587
|
+
const throwIfSignalAborted = (operationSignal) => {
|
|
588
|
+
const abortedSignal = signal?.aborted ? signal : operationSignal?.aborted ? operationSignal : null;
|
|
589
|
+
if (abortedSignal !== null) throw createAbortedException(abortedSignal);
|
|
590
|
+
};
|
|
591
|
+
throwIfSignalAborted();
|
|
592
|
+
const reserveReadBytes = (byteLength) => {
|
|
593
|
+
if (byteLength > limits.maxTotalBytesRead - totalBytesRead) return throwResourceLimitExceeded("maxTotalBytesRead", limits.maxTotalBytesRead, totalBytesRead + byteLength);
|
|
594
|
+
totalBytesRead += byteLength;
|
|
595
|
+
};
|
|
596
|
+
const getEntry = async (path, options) => {
|
|
597
|
+
const parsedPath = parseRepositoryPath(path);
|
|
598
|
+
throwIfSignalAborted(options?.signal);
|
|
599
|
+
registerPath(parsedPath);
|
|
600
|
+
const candidate = await repository.getEntry(parsedPath, createSourceOptions(signal, options?.signal));
|
|
601
|
+
throwIfSignalAborted(options?.signal);
|
|
602
|
+
return candidate === null ? null : copyReaderEntry$1(candidate, "get-entry", parsedPath, parsedPath);
|
|
603
|
+
};
|
|
604
|
+
const iterateEntries = (options) => {
|
|
605
|
+
const prefix = options?.prefix === void 0 ? REPOSITORY_ROOT : parseRepositoryPath(options.prefix);
|
|
606
|
+
return { async *[Symbol.asyncIterator]() {
|
|
607
|
+
registerPath(prefix);
|
|
608
|
+
const yieldedPaths = /* @__PURE__ */ new Set();
|
|
609
|
+
let cursor;
|
|
610
|
+
let previousPath = null;
|
|
611
|
+
do {
|
|
612
|
+
throwIfSignalAborted(options?.signal);
|
|
613
|
+
const page = await repository.listEntriesPage({
|
|
614
|
+
...cursor === void 0 ? {} : { cursor },
|
|
615
|
+
maxEntries: Math.min(SOURCE_PAGE_ENTRIES, limits.maxEntries),
|
|
616
|
+
prefix,
|
|
617
|
+
...createSourceOptions(signal, options?.signal) ?? {}
|
|
618
|
+
});
|
|
619
|
+
if (page.snapshot.id !== repository.snapshot.id || page.entries.length === 0 && !page.isComplete) return invalidSourceData$3("list-entries-page", prefix);
|
|
620
|
+
for (const candidate of page.entries) {
|
|
621
|
+
throwIfSignalAborted(options?.signal);
|
|
622
|
+
const entry = copyReaderEntry$1(candidate, "list-entries-page", prefix);
|
|
623
|
+
if (!isStrictDescendant(entry.path, prefix) || yieldedPaths.has(entry.path) || previousPath !== null && compareRepositoryPaths(previousPath, entry.path) >= 0) return invalidSourceData$3("list-entries-page", entry.path);
|
|
624
|
+
yieldedPaths.add(entry.path);
|
|
625
|
+
previousPath = entry.path;
|
|
626
|
+
registerPath(entry.path);
|
|
627
|
+
yield entry;
|
|
628
|
+
}
|
|
629
|
+
if (page.isComplete) {
|
|
630
|
+
if (page.nextCursor !== null) return invalidSourceData$3("list-entries-page", prefix);
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (page.nextCursor === null || page.nextCursor === cursor) return invalidSourceData$3("list-entries-page", prefix);
|
|
634
|
+
cursor = page.nextCursor;
|
|
635
|
+
} while (true);
|
|
636
|
+
} };
|
|
637
|
+
};
|
|
638
|
+
const loadFile = async (path, operationSignal, isSource = false) => {
|
|
639
|
+
const multiplier = isSource ? 1 : CANONICAL_BYTE_RETENTION_MULTIPLIER;
|
|
640
|
+
const fileLimit = path === MANIFEST_PATH$3 ? "maxManifestBytes" : "maxFileBytes";
|
|
641
|
+
const availableRetainedBytes = limits.maxRetainedBytes - retainedBytes;
|
|
642
|
+
if (availableRetainedBytes < 1) return throwResourceLimitExceeded("maxRetainedBytes", limits.maxRetainedBytes, retainedBytes + 1);
|
|
643
|
+
const firstPageMaximumBytes = Math.min(SOURCE_PAGE_BYTES, Math.max(1, limits[fileLimit]), availableRetainedBytes);
|
|
644
|
+
observeTransientBytes(firstPageMaximumBytes);
|
|
645
|
+
const firstPage = await repository.readFilePage(path, {
|
|
646
|
+
maxBytes: firstPageMaximumBytes,
|
|
647
|
+
offset: 0,
|
|
648
|
+
...createSourceOptions(signal, operationSignal) ?? {}
|
|
649
|
+
});
|
|
650
|
+
throwIfSignalAborted(operationSignal);
|
|
651
|
+
if (firstPage.snapshot.id !== repository.snapshot.id || firstPage.offset !== 0 || !(firstPage.bytes instanceof Uint8Array) || !Number.isSafeInteger(firstPage.totalBytes) || firstPage.totalBytes < 0 || firstPage.bytes.byteLength > SOURCE_PAGE_BYTES || firstPage.bytes.byteLength > firstPage.totalBytes || firstPage.bytes.byteLength === 0 && !firstPage.isComplete) return invalidSourceData$3("read-file-page", path);
|
|
652
|
+
if (firstPage.totalBytes > limits[fileLimit]) return throwResourceLimitExceeded(fileLimit, limits[fileLimit], firstPage.totalBytes);
|
|
653
|
+
const firstPageBytes = firstPage.bytes.byteLength;
|
|
654
|
+
const retainedBytesAvailableForCanonicalContent = limits.maxRetainedBytes - retainedBytes - firstPageBytes;
|
|
655
|
+
if (retainedBytesAvailableForCanonicalContent < 0 || firstPage.totalBytes > Math.floor(retainedBytesAvailableForCanonicalContent / multiplier)) {
|
|
656
|
+
const projectedUsage = retainedBytes + firstPageBytes + firstPage.totalBytes * multiplier;
|
|
657
|
+
return throwResourceLimitExceeded("maxRetainedBytes", limits.maxRetainedBytes, Number.isSafeInteger(projectedUsage) ? projectedUsage : Number.MAX_SAFE_INTEGER);
|
|
658
|
+
}
|
|
659
|
+
reserveReadBytes(firstPage.totalBytes);
|
|
660
|
+
const canonicalRetainedBytes = firstPage.totalBytes * multiplier;
|
|
661
|
+
observeTransientBytes(canonicalRetainedBytes + firstPageBytes);
|
|
662
|
+
reserveRetainedBytes(canonicalRetainedBytes);
|
|
663
|
+
if (!isSource) canonicalBytes += firstPage.totalBytes;
|
|
664
|
+
const content = new Uint8Array(firstPage.totalBytes);
|
|
665
|
+
let offset = 0;
|
|
666
|
+
let page = firstPage;
|
|
667
|
+
let isFirstPage = true;
|
|
668
|
+
while (isFirstPage || offset < content.byteLength) {
|
|
669
|
+
isFirstPage = false;
|
|
670
|
+
if (page.snapshot.id !== repository.snapshot.id || page.offset !== offset || page.totalBytes !== content.byteLength || !(page.bytes instanceof Uint8Array) || page.bytes.byteLength > SOURCE_PAGE_BYTES || page.bytes.byteLength > content.byteLength - offset || page.bytes.byteLength === 0 && !page.isComplete) return invalidSourceData$3("read-file-page", path);
|
|
671
|
+
content.set(page.bytes, offset);
|
|
672
|
+
offset += page.bytes.byteLength;
|
|
673
|
+
if (page.isComplete) {
|
|
674
|
+
if (page.nextOffset !== null || offset !== content.byteLength) return invalidSourceData$3("read-file-page", path);
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
if (page.nextOffset !== offset) return invalidSourceData$3("read-file-page", path);
|
|
678
|
+
page = await repository.readFilePage(path, {
|
|
679
|
+
maxBytes: Math.min(SOURCE_PAGE_BYTES, Math.max(1, content.byteLength - offset)),
|
|
680
|
+
offset,
|
|
681
|
+
...createSourceOptions(signal, operationSignal) ?? {}
|
|
682
|
+
});
|
|
683
|
+
throwIfSignalAborted(operationSignal);
|
|
684
|
+
}
|
|
685
|
+
return content;
|
|
686
|
+
};
|
|
687
|
+
const awaitWithCancellation = async (promise, operationSignal) => {
|
|
688
|
+
const callerSignal = createSourceOptions(signal, operationSignal)?.signal;
|
|
689
|
+
if (callerSignal === void 0) return promise;
|
|
690
|
+
return new Promise((resolve, reject) => {
|
|
691
|
+
const cleanup = () => callerSignal.removeEventListener("abort", abort);
|
|
692
|
+
const abort = () => {
|
|
693
|
+
cleanup();
|
|
694
|
+
reject(createAbortedException(callerSignal));
|
|
695
|
+
};
|
|
696
|
+
if (callerSignal.aborted) abort();
|
|
697
|
+
else callerSignal.addEventListener("abort", abort, { once: true });
|
|
698
|
+
promise.then((result) => {
|
|
699
|
+
cleanup();
|
|
700
|
+
resolve(result);
|
|
701
|
+
}, (error) => {
|
|
702
|
+
cleanup();
|
|
703
|
+
reject(error instanceof Error ? error : new RepositorySourceException({
|
|
704
|
+
cause: error,
|
|
705
|
+
code: "INVALID_SOURCE_DATA",
|
|
706
|
+
operation: "read-file-page",
|
|
707
|
+
path: null,
|
|
708
|
+
retryable: false
|
|
709
|
+
}));
|
|
710
|
+
});
|
|
711
|
+
});
|
|
712
|
+
};
|
|
713
|
+
const readCompleteFile = async (path, options) => {
|
|
714
|
+
const parsedPath = parseRepositoryPath(path);
|
|
715
|
+
throwIfSignalAborted(options?.signal);
|
|
716
|
+
registerPath(parsedPath);
|
|
717
|
+
const content = await awaitWithCancellation(loadFile(parsedPath, options?.signal), options?.signal);
|
|
718
|
+
throwIfSignalAborted(options?.signal);
|
|
719
|
+
return content.slice();
|
|
720
|
+
};
|
|
721
|
+
const reader = Object.freeze({
|
|
722
|
+
getEntry,
|
|
723
|
+
iterateEntries,
|
|
724
|
+
readCompleteFile
|
|
725
|
+
});
|
|
726
|
+
registerRetainedMemory(reader, {
|
|
727
|
+
reserve: reserveRetainedBytes,
|
|
728
|
+
tryReserve: (bytes) => {
|
|
729
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > limits.maxRetainedBytes - retainedBytes) return false;
|
|
730
|
+
reserveRetainedBytes(bytes);
|
|
731
|
+
return true;
|
|
732
|
+
},
|
|
733
|
+
release: (bytes) => {
|
|
734
|
+
if (!Number.isSafeInteger(bytes) || bytes < 0 || bytes > retainedBytes) throw new TypeError("The retained-memory release is invalid.");
|
|
735
|
+
retainedBytes -= bytes;
|
|
736
|
+
},
|
|
737
|
+
readSourceFile: async (path) => {
|
|
738
|
+
throwIfSignalAborted();
|
|
739
|
+
registerPath(path);
|
|
740
|
+
const bytes = await awaitWithCancellation(loadFile(path, void 0, true));
|
|
741
|
+
throwIfSignalAborted();
|
|
742
|
+
let isReleased = false;
|
|
743
|
+
return {
|
|
744
|
+
bytes,
|
|
745
|
+
release: () => {
|
|
746
|
+
if (!isReleased) retainedBytes -= bytes.byteLength;
|
|
747
|
+
isReleased = true;
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
});
|
|
752
|
+
return Object.freeze({
|
|
753
|
+
getResourceUsage: () => Object.freeze({
|
|
754
|
+
canonicalBytes,
|
|
755
|
+
peakRetainedBytes,
|
|
756
|
+
retainedBytes,
|
|
757
|
+
totalBytesRead
|
|
758
|
+
}),
|
|
759
|
+
reader,
|
|
760
|
+
throwIfAborted: throwIfSignalAborted
|
|
761
|
+
});
|
|
762
|
+
};
|
|
763
|
+
//#endregion
|
|
764
|
+
//#region src/agent-asset-validation/index.ts
|
|
765
|
+
var OPENING_HEADING_PATTERN = /^#{1,6} /u;
|
|
766
|
+
/** Finds the first nonblank line at or after one normalized line index. */
|
|
767
|
+
var findNonblankLine = (lines, startIndex) => {
|
|
768
|
+
for (let index = startIndex; index < lines.length; index += 1) {
|
|
769
|
+
const line = lines[index];
|
|
770
|
+
if (line !== void 0 && hasNonWhitespace(line)) return index;
|
|
771
|
+
}
|
|
772
|
+
return null;
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* Validates the effective value of one agent description asset.
|
|
776
|
+
* @param asset The normalized complete description or handoff-description asset.
|
|
777
|
+
* @param agentId The owning registered agent ID.
|
|
778
|
+
* @param kind The description role determining its diagnostic code.
|
|
779
|
+
* @param limits The Core diagnostic budget.
|
|
780
|
+
* @returns The frozen effective description when every version 1 rule passes.
|
|
781
|
+
* @throws
|
|
782
|
+
* - RESOURCE_LIMIT_EXCEEDED: The repository-level diagnostic budget was exceeded.
|
|
783
|
+
*/
|
|
784
|
+
var validateAgentDescription = (asset, agentId, kind, limits) => {
|
|
785
|
+
const diagnostics = createCoreDiagnosticCollector(limits, "validate-project");
|
|
786
|
+
const value = trimRepositoryFormatWhitespace(asset.content);
|
|
787
|
+
const scalarLength = countUnicodeScalars(value);
|
|
788
|
+
const code = kind === "description" ? "MOLDEA_AGENT_DESCRIPTION_INVALID" : "MOLDEA_AGENT_HANDOFF_DESCRIPTION_INVALID";
|
|
789
|
+
if (scalarLength === 0) diagnostics.add({
|
|
790
|
+
code,
|
|
791
|
+
details: { reason: "empty" },
|
|
792
|
+
entity: { agentId },
|
|
793
|
+
path: asset.path
|
|
794
|
+
});
|
|
795
|
+
if (scalarLength > 1e3) diagnostics.add({
|
|
796
|
+
code,
|
|
797
|
+
details: { reason: "too-long" },
|
|
798
|
+
entity: { agentId },
|
|
799
|
+
path: asset.path
|
|
800
|
+
});
|
|
801
|
+
if (value.includes("{{") || value.includes("}}")) diagnostics.add({
|
|
802
|
+
code,
|
|
803
|
+
details: { reason: "runtime-variable-delimiter" },
|
|
804
|
+
entity: { agentId },
|
|
805
|
+
path: asset.path
|
|
806
|
+
});
|
|
807
|
+
const finalizedDiagnostics = diagnostics.finalize();
|
|
808
|
+
return freezeRecursively({
|
|
809
|
+
description: finalizedDiagnostics.length === 0 ? {
|
|
810
|
+
asset,
|
|
811
|
+
scalarLength,
|
|
812
|
+
value
|
|
813
|
+
} : null,
|
|
814
|
+
diagnostics: finalizedDiagnostics,
|
|
815
|
+
valid: finalizedDiagnostics.length === 0
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
/**
|
|
819
|
+
* Validates one canonical instruction's non-empty content and opening identity.
|
|
820
|
+
* @param asset The normalized complete instruction asset.
|
|
821
|
+
* @param agentId The owning registered agent ID.
|
|
822
|
+
* @param limits The Core diagnostic budget.
|
|
823
|
+
* @returns The frozen instruction result, retaining non-empty content for later validation.
|
|
824
|
+
* @throws
|
|
825
|
+
* - RESOURCE_LIMIT_EXCEEDED: The repository-level diagnostic budget was exceeded.
|
|
826
|
+
*/
|
|
827
|
+
var validateAgentInstruction = (asset, agentId, limits) => {
|
|
828
|
+
const diagnostics = createCoreDiagnosticCollector(limits, "validate-project");
|
|
829
|
+
if (!hasNonWhitespace(asset.content)) {
|
|
830
|
+
diagnostics.add({
|
|
831
|
+
code: "MOLDEA_AGENT_INSTRUCTION_EMPTY",
|
|
832
|
+
entity: { agentId },
|
|
833
|
+
path: asset.path
|
|
834
|
+
});
|
|
835
|
+
return freezeRecursively({
|
|
836
|
+
diagnostics: diagnostics.finalize(),
|
|
837
|
+
instruction: null,
|
|
838
|
+
valid: false
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
const identityToken = `\`${agentId}\``;
|
|
842
|
+
const lines = asset.content.split("\n");
|
|
843
|
+
const openingIndex = findNonblankLine(lines, 0);
|
|
844
|
+
let identityIndex = openingIndex;
|
|
845
|
+
if (openingIndex !== null && OPENING_HEADING_PATTERN.test(lines[openingIndex] ?? "")) identityIndex = findNonblankLine(lines, openingIndex + 1);
|
|
846
|
+
if (identityIndex === null || !(lines[identityIndex] ?? "").includes(identityToken)) diagnostics.add({
|
|
847
|
+
code: "MOLDEA_AGENT_IDENTITY_INVALID",
|
|
848
|
+
details: { reason: asset.content.includes(identityToken) ? "misplaced" : "missing" },
|
|
849
|
+
entity: { agentId },
|
|
850
|
+
path: asset.path
|
|
851
|
+
});
|
|
852
|
+
const finalizedDiagnostics = diagnostics.finalize();
|
|
853
|
+
return freezeRecursively({
|
|
854
|
+
diagnostics: finalizedDiagnostics,
|
|
855
|
+
instruction: asset,
|
|
856
|
+
valid: finalizedDiagnostics.length === 0
|
|
857
|
+
});
|
|
858
|
+
};
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/repository-text/index.ts
|
|
861
|
+
var invalidSourceData$2 = (path) => {
|
|
862
|
+
throw new RepositorySourceException({
|
|
863
|
+
code: "INVALID_SOURCE_DATA",
|
|
864
|
+
operation: "read-file-page",
|
|
865
|
+
path,
|
|
866
|
+
retryable: false
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
/**
|
|
870
|
+
* Reads and normalizes one canonical Markdown asset through a repository reader.
|
|
871
|
+
* @param repository The coherent source-neutral repository reader.
|
|
872
|
+
* @param path The discovered regular-file path to read exactly once.
|
|
873
|
+
* @param limits The Core limits applied to the source bytes and diagnostics.
|
|
874
|
+
* @param signal Optional cancellation forwarded to the repository read.
|
|
875
|
+
* @returns A deeply immutable indexed asset or its deterministic text diagnostics.
|
|
876
|
+
* @throws
|
|
877
|
+
* - ENTRY_NOT_FOUND: The discovered file disappeared from the reader snapshot.
|
|
878
|
+
* - ENTRY_NOT_FILE: The discovered entry is no longer a regular file.
|
|
879
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
880
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
881
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the read.
|
|
882
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
883
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
884
|
+
* - ABORTED: Repository inspection or the repository read was aborted.
|
|
885
|
+
*/
|
|
886
|
+
var readRepositoryTextAsset = async (repository, path, limits, signal) => {
|
|
887
|
+
const content = await repository.readCompleteFile(path, signal === void 0 ? void 0 : { signal });
|
|
888
|
+
if (!(content instanceof Uint8Array)) return invalidSourceData$2(path);
|
|
889
|
+
const normalized = normalizeTextDocument({
|
|
890
|
+
content,
|
|
891
|
+
path
|
|
892
|
+
}, limits, "validate-project");
|
|
893
|
+
if (!normalized.valid || normalized.text === null) return freezeRecursively({
|
|
894
|
+
asset: null,
|
|
895
|
+
diagnostics: normalized.diagnostics,
|
|
896
|
+
valid: false
|
|
897
|
+
});
|
|
898
|
+
const asset = {
|
|
899
|
+
content: normalized.text.value,
|
|
900
|
+
digest: await calculateNormalizedTextDigest(normalized.text),
|
|
901
|
+
path,
|
|
902
|
+
scalarLength: normalized.text.scalarLength,
|
|
903
|
+
utf8ByteLength: normalized.text.utf8ByteLength
|
|
904
|
+
};
|
|
905
|
+
return freezeRecursively({
|
|
906
|
+
asset,
|
|
907
|
+
diagnostics: [],
|
|
908
|
+
valid: true
|
|
909
|
+
});
|
|
910
|
+
};
|
|
911
|
+
//#endregion
|
|
912
|
+
//#region src/runtime-placeholder-validation/index.ts
|
|
913
|
+
var readBraceRunEnd = (content, startOffset, brace) => {
|
|
914
|
+
let endOffset = startOffset + 1;
|
|
915
|
+
while (content[endOffset] === brace) endOffset += 1;
|
|
916
|
+
return endOffset;
|
|
917
|
+
};
|
|
918
|
+
/** Finds the end of the next closing-delimiter run without rescanning prior content. */
|
|
919
|
+
var findClosingRunEnd = (content, startOffset) => {
|
|
920
|
+
let cursor = startOffset;
|
|
921
|
+
while (cursor < content.length) {
|
|
922
|
+
if (content[cursor] !== "}") {
|
|
923
|
+
cursor += 1;
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const runEnd = readBraceRunEnd(content, cursor, "}");
|
|
927
|
+
if (runEnd - cursor >= 2) return runEnd;
|
|
928
|
+
cursor = runEnd;
|
|
929
|
+
}
|
|
930
|
+
return content.length;
|
|
931
|
+
};
|
|
932
|
+
/** Adds one malformed-placeholder diagnostic for an exact normalized source span. */
|
|
933
|
+
var addMalformedDiagnostic = (collector, instruction, agentId, reason, range) => {
|
|
934
|
+
collector.add({
|
|
935
|
+
code: "MOLDEA_VARIABLE_PLACEHOLDER_MALFORMED",
|
|
936
|
+
details: { reason },
|
|
937
|
+
entity: { agentId },
|
|
938
|
+
path: instruction.path,
|
|
939
|
+
range
|
|
940
|
+
});
|
|
941
|
+
};
|
|
942
|
+
/** Records a valid placeholder occurrence and its first normalized source range. */
|
|
943
|
+
var recordOccurrence = (occurrences, variableId, range) => {
|
|
944
|
+
const occurrence = occurrences.get(variableId);
|
|
945
|
+
if (occurrence === void 0) {
|
|
946
|
+
occurrences.set(variableId, {
|
|
947
|
+
count: 1,
|
|
948
|
+
firstRange: range
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
occurrence.count += 1;
|
|
953
|
+
};
|
|
954
|
+
/**
|
|
955
|
+
* Validates runtime-variable placeholders in one normalized canonical agent instruction.
|
|
956
|
+
* @param manifestPath The canonical manifest path containing variable declarations.
|
|
957
|
+
* @param agentId The owning agent ID.
|
|
958
|
+
* @param instruction The normalized non-empty canonical instruction asset.
|
|
959
|
+
* @param declaredVariables The owning agent's normalized variable declarations.
|
|
960
|
+
* @param limits The resource limits applied to emitted diagnostics.
|
|
961
|
+
* @returns Frozen deterministic placeholder diagnostics.
|
|
962
|
+
* @throws
|
|
963
|
+
* - RESOURCE_LIMIT_EXCEEDED: The repository-level diagnostic budget was exceeded.
|
|
964
|
+
*/
|
|
965
|
+
var validateRuntimePlaceholders = (manifestPath, agentId, instruction, declaredVariables, limits) => {
|
|
966
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
967
|
+
const locator = createSourceLocator(instruction.content);
|
|
968
|
+
const occurrences = /* @__PURE__ */ new Map();
|
|
969
|
+
let cursor = 0;
|
|
970
|
+
while (cursor < instruction.content.length) {
|
|
971
|
+
const character = instruction.content[cursor];
|
|
972
|
+
if (character === "}") {
|
|
973
|
+
const runEnd = readBraceRunEnd(instruction.content, cursor, "}");
|
|
974
|
+
const runLength = runEnd - cursor;
|
|
975
|
+
if (runLength >= 2) addMalformedDiagnostic(collector, instruction, agentId, runLength === 2 ? "unmatched-closing" : "invalid-closing-delimiter", locator.locateRange(cursor, runEnd));
|
|
976
|
+
cursor = runEnd;
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
if (character !== "{") {
|
|
980
|
+
cursor += 1;
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
const openingEnd = readBraceRunEnd(instruction.content, cursor, "{");
|
|
984
|
+
const openingLength = openingEnd - cursor;
|
|
985
|
+
if (openingLength === 1) {
|
|
986
|
+
cursor = openingEnd;
|
|
987
|
+
continue;
|
|
988
|
+
}
|
|
989
|
+
if (openingLength > 2) {
|
|
990
|
+
addMalformedDiagnostic(collector, instruction, agentId, "invalid-opening-delimiter", locator.locateRange(cursor, openingEnd));
|
|
991
|
+
cursor = openingEnd;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
let candidateCursor = openingEnd;
|
|
995
|
+
let isCandidateComplete = false;
|
|
996
|
+
while (candidateCursor < instruction.content.length) {
|
|
997
|
+
const candidateCharacter = instruction.content[candidateCursor];
|
|
998
|
+
if (candidateCharacter === "{") {
|
|
999
|
+
const nestedOpeningEnd = readBraceRunEnd(instruction.content, candidateCursor, "{");
|
|
1000
|
+
if (nestedOpeningEnd - candidateCursor >= 2) {
|
|
1001
|
+
const malformedEnd = findClosingRunEnd(instruction.content, nestedOpeningEnd);
|
|
1002
|
+
addMalformedDiagnostic(collector, instruction, agentId, "nested-opening", locator.locateRange(cursor, malformedEnd));
|
|
1003
|
+
cursor = malformedEnd;
|
|
1004
|
+
isCandidateComplete = true;
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
candidateCursor = nestedOpeningEnd;
|
|
1008
|
+
continue;
|
|
1009
|
+
}
|
|
1010
|
+
if (candidateCharacter !== "}") {
|
|
1011
|
+
candidateCursor += 1;
|
|
1012
|
+
continue;
|
|
1013
|
+
}
|
|
1014
|
+
const closingEnd = readBraceRunEnd(instruction.content, candidateCursor, "}");
|
|
1015
|
+
const closingLength = closingEnd - candidateCursor;
|
|
1016
|
+
if (closingLength === 1) {
|
|
1017
|
+
candidateCursor = closingEnd;
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (closingLength > 2) {
|
|
1021
|
+
addMalformedDiagnostic(collector, instruction, agentId, "invalid-closing-delimiter", locator.locateRange(cursor, closingEnd));
|
|
1022
|
+
cursor = closingEnd;
|
|
1023
|
+
isCandidateComplete = true;
|
|
1024
|
+
break;
|
|
1025
|
+
}
|
|
1026
|
+
const variableId = instruction.content.slice(openingEnd, candidateCursor);
|
|
1027
|
+
const placeholderRange = locator.locateRange(cursor, closingEnd);
|
|
1028
|
+
if (isVariableId(variableId)) recordOccurrence(occurrences, variableId, placeholderRange);
|
|
1029
|
+
else addMalformedDiagnostic(collector, instruction, agentId, "invalid-variable-id", placeholderRange);
|
|
1030
|
+
cursor = closingEnd;
|
|
1031
|
+
isCandidateComplete = true;
|
|
1032
|
+
break;
|
|
1033
|
+
}
|
|
1034
|
+
if (!isCandidateComplete) {
|
|
1035
|
+
addMalformedDiagnostic(collector, instruction, agentId, "unmatched-opening", locator.locateRange(cursor, instruction.content.length));
|
|
1036
|
+
cursor = instruction.content.length;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
for (const variableId of [...occurrences.keys()].sort(compareExactStrings)) {
|
|
1040
|
+
if (declaredVariables[variableId] !== void 0) continue;
|
|
1041
|
+
const occurrence = occurrences.get(variableId);
|
|
1042
|
+
if (occurrence === void 0) continue;
|
|
1043
|
+
collector.add({
|
|
1044
|
+
code: "MOLDEA_VARIABLE_UNDECLARED",
|
|
1045
|
+
details: { occurrences: occurrence.count },
|
|
1046
|
+
entity: {
|
|
1047
|
+
agentId,
|
|
1048
|
+
variableId
|
|
1049
|
+
},
|
|
1050
|
+
path: instruction.path,
|
|
1051
|
+
range: occurrence.firstRange
|
|
1052
|
+
});
|
|
1053
|
+
}
|
|
1054
|
+
for (const variableId of Object.keys(declaredVariables).sort(compareExactStrings)) {
|
|
1055
|
+
if (occurrences.has(variableId)) continue;
|
|
1056
|
+
collector.add({
|
|
1057
|
+
code: "MOLDEA_VARIABLE_UNUSED",
|
|
1058
|
+
entity: {
|
|
1059
|
+
agentId,
|
|
1060
|
+
variableId
|
|
1061
|
+
},
|
|
1062
|
+
path: manifestPath,
|
|
1063
|
+
pointer: `/agents/${escapeJsonPointerSegment(agentId)}/variables/${escapeJsonPointerSegment(variableId)}`
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
return collector.finalize();
|
|
1067
|
+
};
|
|
1068
|
+
//#endregion
|
|
1069
|
+
//#region src/agent-assets/index.ts
|
|
1070
|
+
var MANIFEST_PATH$2 = parseRepositoryPath("/moldea/moldea.yaml");
|
|
1071
|
+
var addDiagnostics$4 = (collector, diagnostics) => {
|
|
1072
|
+
for (const diagnostic of diagnostics) collector.merge(diagnostic);
|
|
1073
|
+
};
|
|
1074
|
+
var isBlockedPath$2 = (path, blockedPaths) => {
|
|
1075
|
+
let candidate = path;
|
|
1076
|
+
while (candidate.length > 0) {
|
|
1077
|
+
if (blockedPaths.has(candidate)) return true;
|
|
1078
|
+
const separatorIndex = candidate.lastIndexOf("/");
|
|
1079
|
+
if (separatorIndex <= 0) return false;
|
|
1080
|
+
candidate = candidate.slice(0, separatorIndex);
|
|
1081
|
+
}
|
|
1082
|
+
return false;
|
|
1083
|
+
};
|
|
1084
|
+
/** Reads and validates a required or optional description asset. */
|
|
1085
|
+
var readDescription = async (repository, path, agentId, kind, options, collector, signal) => {
|
|
1086
|
+
const readResult = await readRepositoryTextAsset(repository, path, options.limits, signal);
|
|
1087
|
+
addDiagnostics$4(collector, readResult.diagnostics);
|
|
1088
|
+
if (readResult.asset === null) return null;
|
|
1089
|
+
const validation = validateAgentDescription(readResult.asset, agentId, kind, options.limits);
|
|
1090
|
+
addDiagnostics$4(collector, validation.diagnostics);
|
|
1091
|
+
return validation.description;
|
|
1092
|
+
};
|
|
1093
|
+
/** Reads and validates a mandatory instruction asset. */
|
|
1094
|
+
var readInstruction = async (repository, path, agentId, declaration, options, collector, signal) => {
|
|
1095
|
+
const readResult = await readRepositoryTextAsset(repository, path, options.limits, signal);
|
|
1096
|
+
addDiagnostics$4(collector, readResult.diagnostics);
|
|
1097
|
+
if (readResult.asset === null) return null;
|
|
1098
|
+
const validation = validateAgentInstruction(readResult.asset, agentId, options.limits);
|
|
1099
|
+
addDiagnostics$4(collector, validation.diagnostics);
|
|
1100
|
+
if (validation.instruction !== null) addDiagnostics$4(collector, validateRuntimePlaceholders(MANIFEST_PATH$2, agentId, validation.instruction, declaration.variables ?? {}, options.limits));
|
|
1101
|
+
return validation.instruction;
|
|
1102
|
+
};
|
|
1103
|
+
/**
|
|
1104
|
+
* Reconciles registered agents with their convention-owned directories and Markdown assets.
|
|
1105
|
+
* @param repository The coherent source-neutral repository reader.
|
|
1106
|
+
* @param manifest The normalized manifest containing registered agents.
|
|
1107
|
+
* @param discovery The canonical inventory and path-owned structural diagnostics.
|
|
1108
|
+
* @param options The immutable Core configuration snapshot.
|
|
1109
|
+
* @param signal Optional cancellation forwarded to every repository read.
|
|
1110
|
+
* @returns Deeply immutable registered-agent assets and deterministic diagnostics.
|
|
1111
|
+
* @throws
|
|
1112
|
+
* - ENTRY_NOT_FOUND: A discovered agent asset disappeared from the reader snapshot.
|
|
1113
|
+
* - ENTRY_NOT_FILE: A discovered agent asset is no longer a regular file.
|
|
1114
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
1115
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
1116
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during agent reads.
|
|
1117
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
1118
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
1119
|
+
* - ABORTED: Agent inspection or a repository operation was aborted.
|
|
1120
|
+
*/
|
|
1121
|
+
var inspectAgentAssets = async (repository, manifest, discovery, options, signal) => {
|
|
1122
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
1123
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
1124
|
+
const agents = [];
|
|
1125
|
+
const registeredAgents = manifest.agents ?? {};
|
|
1126
|
+
const registeredIds = new Set(Object.keys(registeredAgents));
|
|
1127
|
+
const discoveredById = new Map(discovery.inventory.agents.map((agent) => [agent.id, agent]));
|
|
1128
|
+
const blockedPaths = new Set(discovery.diagnostics.flatMap((diagnostic) => diagnostic.path === null ? [] : [diagnostic.path]));
|
|
1129
|
+
for (const discovered of discovery.inventory.agents) if (!registeredIds.has(discovered.id)) collector.add({
|
|
1130
|
+
code: "MOLDEA_AGENT_DIRECTORY_UNREGISTERED",
|
|
1131
|
+
entity: { agentId: discovered.id },
|
|
1132
|
+
path: discovered.path
|
|
1133
|
+
});
|
|
1134
|
+
for (const agentId of [...registeredIds].sort(compareExactStrings)) {
|
|
1135
|
+
const declaration = registeredAgents[agentId];
|
|
1136
|
+
if (declaration === void 0) continue;
|
|
1137
|
+
const discovered = discoveredById.get(agentId);
|
|
1138
|
+
if (discovered === void 0) {
|
|
1139
|
+
const directoryPath = parseRepositoryPath(`/moldea/agents/${agentId}`);
|
|
1140
|
+
if (!isBlockedPath$2(directoryPath, blockedPaths)) collector.add({
|
|
1141
|
+
code: "MOLDEA_AGENT_DIRECTORY_MISSING",
|
|
1142
|
+
entity: { agentId },
|
|
1143
|
+
path: directoryPath
|
|
1144
|
+
});
|
|
1145
|
+
agents.push({
|
|
1146
|
+
declaration,
|
|
1147
|
+
description: null,
|
|
1148
|
+
handoffDescription: null,
|
|
1149
|
+
id: agentId,
|
|
1150
|
+
instruction: null
|
|
1151
|
+
});
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
let description = null;
|
|
1155
|
+
let instruction = null;
|
|
1156
|
+
let handoffDescription = null;
|
|
1157
|
+
if (discovered.description === null) {
|
|
1158
|
+
const path = parseRepositoryPath(`${discovered.path}/description.md`);
|
|
1159
|
+
if (!isBlockedPath$2(path, blockedPaths)) collector.add({
|
|
1160
|
+
code: "MOLDEA_AGENT_DESCRIPTION_MISSING",
|
|
1161
|
+
entity: { agentId },
|
|
1162
|
+
path
|
|
1163
|
+
});
|
|
1164
|
+
} else description = await readDescription(repository, discovered.description, agentId, "description", options, collector, signal);
|
|
1165
|
+
if (discovered.instruction === null) {
|
|
1166
|
+
const path = parseRepositoryPath(`${discovered.path}/instruction.md`);
|
|
1167
|
+
if (!isBlockedPath$2(path, blockedPaths)) collector.add({
|
|
1168
|
+
code: "MOLDEA_AGENT_INSTRUCTION_MISSING",
|
|
1169
|
+
entity: { agentId },
|
|
1170
|
+
path
|
|
1171
|
+
});
|
|
1172
|
+
} else instruction = await readInstruction(repository, discovered.instruction, agentId, declaration, options, collector, signal);
|
|
1173
|
+
if (discovered.handoffDescription !== null) handoffDescription = await readDescription(repository, discovered.handoffDescription, agentId, "handoff-description", options, collector, signal);
|
|
1174
|
+
agents.push({
|
|
1175
|
+
declaration,
|
|
1176
|
+
description,
|
|
1177
|
+
handoffDescription,
|
|
1178
|
+
id: agentId,
|
|
1179
|
+
instruction
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
const diagnostics = collector.finalize();
|
|
1183
|
+
return freezeRecursively({
|
|
1184
|
+
agents,
|
|
1185
|
+
diagnostics,
|
|
1186
|
+
valid: diagnostics.length === 0
|
|
1187
|
+
});
|
|
1188
|
+
};
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/canonical-discovery-validation/index.ts
|
|
1191
|
+
var AGENT_ASSET_PATTERN = /^\/moldea\/agents\/([^/]+)\/(description|instruction|handoff-description)\.md$/u;
|
|
1192
|
+
var AGENT_DIRECTORY_PATTERN = /^\/moldea\/agents\/([^/]+)$/u;
|
|
1193
|
+
var DECISION_CANDIDATE_PATTERN = /^\/moldea\/decisions\/[^/]+\.md$/u;
|
|
1194
|
+
var readCanonicalFile = (path) => {
|
|
1195
|
+
if (path === "/moldea/moldea.yaml") return {
|
|
1196
|
+
agentId: null,
|
|
1197
|
+
fileKind: "manifest",
|
|
1198
|
+
kind: "canonical-file"
|
|
1199
|
+
};
|
|
1200
|
+
if (path === "/moldea/project.md") return {
|
|
1201
|
+
agentId: null,
|
|
1202
|
+
fileKind: "project",
|
|
1203
|
+
kind: "canonical-file"
|
|
1204
|
+
};
|
|
1205
|
+
if (isContextPath(path, false)) return {
|
|
1206
|
+
agentId: null,
|
|
1207
|
+
fileKind: "context",
|
|
1208
|
+
kind: "canonical-file"
|
|
1209
|
+
};
|
|
1210
|
+
if (DECISION_CANDIDATE_PATTERN.test(path)) return {
|
|
1211
|
+
agentId: null,
|
|
1212
|
+
fileKind: "decision",
|
|
1213
|
+
kind: "canonical-file"
|
|
1214
|
+
};
|
|
1215
|
+
if (isRuntimeGuidancePath(path)) return {
|
|
1216
|
+
agentId: null,
|
|
1217
|
+
fileKind: "runtime-guidance",
|
|
1218
|
+
kind: "canonical-file"
|
|
1219
|
+
};
|
|
1220
|
+
const agentAsset = AGENT_ASSET_PATTERN.exec(path);
|
|
1221
|
+
if (agentAsset === null) return null;
|
|
1222
|
+
const agentId = agentAsset[1];
|
|
1223
|
+
const fileName = agentAsset[2];
|
|
1224
|
+
if (agentId === void 0 || fileName === void 0) return null;
|
|
1225
|
+
return {
|
|
1226
|
+
agentId,
|
|
1227
|
+
fileKind: fileName === "description" ? "agent-description" : fileName === "instruction" ? "agent-instruction" : "agent-handoff-description",
|
|
1228
|
+
kind: "canonical-file"
|
|
1229
|
+
};
|
|
1230
|
+
};
|
|
1231
|
+
var readAgentDirectoryId = (path) => {
|
|
1232
|
+
return AGENT_DIRECTORY_PATTERN.exec(path)?.[1] ?? null;
|
|
1233
|
+
};
|
|
1234
|
+
var isRequiredDirectoryPath = (path) => {
|
|
1235
|
+
return path === "/moldea" || path === "/moldea/context" || path === "/moldea/decisions" || path === "/moldea/runtimes" || path === "/moldea/agents";
|
|
1236
|
+
};
|
|
1237
|
+
var isPermittedStructuralDirectoryPath = (path) => {
|
|
1238
|
+
return isRequiredDirectoryPath(path) || path.startsWith("/moldea/context/") || path.startsWith("/moldea/runtimes/");
|
|
1239
|
+
};
|
|
1240
|
+
/**
|
|
1241
|
+
* Classifies one reader entry against the version 1 canonical layout.
|
|
1242
|
+
* @param entry The detached entry beneath the canonical moldea root.
|
|
1243
|
+
* @returns Its canonical category or the precise structural failure category.
|
|
1244
|
+
*/
|
|
1245
|
+
var classifyCanonicalEntry = (entry) => {
|
|
1246
|
+
const canonicalFile = readCanonicalFile(entry.path);
|
|
1247
|
+
if (canonicalFile !== null) {
|
|
1248
|
+
if (entry.type === "file") return canonicalFile;
|
|
1249
|
+
return entry.type === "symlink" ? { kind: "canonical-asset-symlink" } : {
|
|
1250
|
+
expectedType: "file",
|
|
1251
|
+
kind: "entry-type-invalid"
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
const agentId = readAgentDirectoryId(entry.path);
|
|
1255
|
+
if (agentId !== null) return entry.type === "directory" ? {
|
|
1256
|
+
agentId,
|
|
1257
|
+
kind: "agent-directory"
|
|
1258
|
+
} : {
|
|
1259
|
+
expectedType: "directory",
|
|
1260
|
+
kind: "entry-type-invalid"
|
|
1261
|
+
};
|
|
1262
|
+
if (isRequiredDirectoryPath(entry.path)) return entry.type === "directory" ? { kind: "structural-directory" } : {
|
|
1263
|
+
expectedType: "directory",
|
|
1264
|
+
kind: "entry-type-invalid"
|
|
1265
|
+
};
|
|
1266
|
+
if (entry.type === "directory") return isPermittedStructuralDirectoryPath(entry.path) ? { kind: "structural-directory" } : { kind: "ignored-directory" };
|
|
1267
|
+
return { kind: "unrecognized" };
|
|
1268
|
+
};
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/canonical-discovery/index.ts
|
|
1271
|
+
var MOLDEA_ROOT$1 = parseRepositoryPath("/moldea");
|
|
1272
|
+
var MANIFEST_PATH$1 = parseRepositoryPath("/moldea/moldea.yaml");
|
|
1273
|
+
var PROJECT_PATH$1 = parseRepositoryPath("/moldea/project.md");
|
|
1274
|
+
var createWorkingInventory = () => ({
|
|
1275
|
+
agents: /* @__PURE__ */ new Map(),
|
|
1276
|
+
context: /* @__PURE__ */ new Set(),
|
|
1277
|
+
decisions: /* @__PURE__ */ new Set(),
|
|
1278
|
+
manifest: null,
|
|
1279
|
+
project: null,
|
|
1280
|
+
runtimeGuidance: /* @__PURE__ */ new Set()
|
|
1281
|
+
});
|
|
1282
|
+
var invalidSourceData$1 = (operation, path) => {
|
|
1283
|
+
throw new RepositorySourceException({
|
|
1284
|
+
code: "INVALID_SOURCE_DATA",
|
|
1285
|
+
operation,
|
|
1286
|
+
path,
|
|
1287
|
+
retryable: false
|
|
1288
|
+
});
|
|
1289
|
+
};
|
|
1290
|
+
var copyReaderEntry = (candidate, operation, expectedPath) => {
|
|
1291
|
+
if (typeof candidate !== "object" || candidate === null) return invalidSourceData$1(operation, expectedPath ?? null);
|
|
1292
|
+
const record = candidate;
|
|
1293
|
+
const pathCandidate = record["path"];
|
|
1294
|
+
const typeCandidate = record["type"];
|
|
1295
|
+
const byteLength = record["byteLength"];
|
|
1296
|
+
const contentIdentity = record["contentIdentity"];
|
|
1297
|
+
if (typeof pathCandidate !== "string" || !isRepositoryPath(pathCandidate)) return invalidSourceData$1(operation, expectedPath ?? null);
|
|
1298
|
+
const path = parseRepositoryPath(pathCandidate);
|
|
1299
|
+
if (expectedPath !== void 0 && path !== expectedPath) return invalidSourceData$1(operation, path);
|
|
1300
|
+
if (typeCandidate !== "file" && typeCandidate !== "directory" && typeCandidate !== "symlink") return invalidSourceData$1(operation, path);
|
|
1301
|
+
if (typeCandidate === "file" ? !Number.isSafeInteger(byteLength) || byteLength < 0 || contentIdentity !== null && typeof contentIdentity !== "string" : byteLength !== null || contentIdentity !== null) return invalidSourceData$1(operation, path);
|
|
1302
|
+
return {
|
|
1303
|
+
byteLength,
|
|
1304
|
+
contentIdentity,
|
|
1305
|
+
path,
|
|
1306
|
+
type: typeCandidate
|
|
1307
|
+
};
|
|
1308
|
+
};
|
|
1309
|
+
var readExactEntry = async (repository, path, signal) => {
|
|
1310
|
+
const candidate = await repository.getEntry(path, signal === void 0 ? void 0 : { signal });
|
|
1311
|
+
return candidate === null ? null : copyReaderEntry(candidate, "get-entry", path);
|
|
1312
|
+
};
|
|
1313
|
+
var addEntryTypeDiagnostic = (diagnostics, entry, expectedType) => {
|
|
1314
|
+
diagnostics.add({
|
|
1315
|
+
code: "MOLDEA_ENTRY_TYPE_INVALID",
|
|
1316
|
+
details: {
|
|
1317
|
+
actualType: entry.type,
|
|
1318
|
+
expectedType
|
|
1319
|
+
},
|
|
1320
|
+
path: entry.path
|
|
1321
|
+
});
|
|
1322
|
+
};
|
|
1323
|
+
var getAgent = (agents, agentId) => {
|
|
1324
|
+
const existing = agents.get(agentId);
|
|
1325
|
+
if (existing !== void 0) return existing;
|
|
1326
|
+
const created = {
|
|
1327
|
+
description: null,
|
|
1328
|
+
handoffDescription: null,
|
|
1329
|
+
id: agentId,
|
|
1330
|
+
instruction: null,
|
|
1331
|
+
path: parseRepositoryPath(`/moldea/agents/${agentId}`)
|
|
1332
|
+
};
|
|
1333
|
+
agents.set(agentId, created);
|
|
1334
|
+
return created;
|
|
1335
|
+
};
|
|
1336
|
+
var retainCanonicalFile = (inventory, path, fileKind, agentId) => {
|
|
1337
|
+
switch (fileKind) {
|
|
1338
|
+
case "manifest":
|
|
1339
|
+
inventory.manifest = path;
|
|
1340
|
+
return;
|
|
1341
|
+
case "project":
|
|
1342
|
+
inventory.project = path;
|
|
1343
|
+
return;
|
|
1344
|
+
case "context":
|
|
1345
|
+
inventory.context.add(path);
|
|
1346
|
+
return;
|
|
1347
|
+
case "decision":
|
|
1348
|
+
inventory.decisions.add(path);
|
|
1349
|
+
return;
|
|
1350
|
+
case "runtime-guidance":
|
|
1351
|
+
inventory.runtimeGuidance.add(path);
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
if (agentId === null) return invalidSourceData$1("list-entries-page", path);
|
|
1355
|
+
const agent = getAgent(inventory.agents, agentId);
|
|
1356
|
+
if (fileKind === "agent-description") agent.description = path;
|
|
1357
|
+
else if (fileKind === "agent-instruction") agent.instruction = path;
|
|
1358
|
+
else agent.handoffDescription = path;
|
|
1359
|
+
};
|
|
1360
|
+
var finalizeInventory = (working) => {
|
|
1361
|
+
const sortPaths = (paths) => {
|
|
1362
|
+
return [...paths].sort(compareExactStrings);
|
|
1363
|
+
};
|
|
1364
|
+
const agents = [...working.agents.values()].sort((left, right) => compareExactStrings(left.id, right.id)).map((agent) => freezeRecursively({ ...agent }));
|
|
1365
|
+
return freezeRecursively({
|
|
1366
|
+
agents,
|
|
1367
|
+
context: sortPaths(working.context),
|
|
1368
|
+
decisions: sortPaths(working.decisions),
|
|
1369
|
+
manifest: working.manifest,
|
|
1370
|
+
project: working.project,
|
|
1371
|
+
runtimeGuidance: sortPaths(working.runtimeGuidance)
|
|
1372
|
+
});
|
|
1373
|
+
};
|
|
1374
|
+
var finalizeResult = (working, diagnostics) => {
|
|
1375
|
+
const finalizedDiagnostics = diagnostics.finalize();
|
|
1376
|
+
return freezeRecursively({
|
|
1377
|
+
diagnostics: finalizedDiagnostics,
|
|
1378
|
+
inventory: finalizeInventory(working),
|
|
1379
|
+
valid: finalizedDiagnostics.length === 0
|
|
1380
|
+
});
|
|
1381
|
+
};
|
|
1382
|
+
var retainFoundationEntry = (entry, expectedPath, missingCode, working, diagnostics) => {
|
|
1383
|
+
if (entry === null) {
|
|
1384
|
+
diagnostics.add({
|
|
1385
|
+
code: missingCode,
|
|
1386
|
+
path: expectedPath
|
|
1387
|
+
});
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
if (entry.type === "file") {
|
|
1391
|
+
if (expectedPath === MANIFEST_PATH$1) working.manifest = expectedPath;
|
|
1392
|
+
else working.project = expectedPath;
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
if (entry.type === "symlink") {
|
|
1396
|
+
diagnostics.add({
|
|
1397
|
+
code: "MOLDEA_CANONICAL_ASSET_SYMLINK",
|
|
1398
|
+
path: expectedPath
|
|
1399
|
+
});
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
addEntryTypeDiagnostic(diagnostics, entry, "file");
|
|
1403
|
+
};
|
|
1404
|
+
var checkFoundationListing = (listedEntry, exactEntry) => {
|
|
1405
|
+
if (exactEntry === null || exactEntry.type !== listedEntry.type) return invalidSourceData$1("list-entries-page", listedEntry.path);
|
|
1406
|
+
};
|
|
1407
|
+
/**
|
|
1408
|
+
* Discovers and classifies version 1 canonical assets through one repository snapshot.
|
|
1409
|
+
* @param repository The coherent source-neutral repository reader.
|
|
1410
|
+
* @param limits The immutable Core limits governing diagnostic collection.
|
|
1411
|
+
* @param signal Optional cancellation forwarded to every repository operation.
|
|
1412
|
+
* @param foundations Optional exact foundation entries already observed by this inspection.
|
|
1413
|
+
* @returns A deeply immutable inventory and its deterministic structural diagnostics.
|
|
1414
|
+
* @throws
|
|
1415
|
+
* - ENTRY_NOT_FOUND: The reader snapshot became inconsistent during discovery.
|
|
1416
|
+
* - ENTRY_NOT_DIRECTORY: The reader snapshot became inconsistent during discovery.
|
|
1417
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
1418
|
+
* - SOURCE_UNAVAILABLE: The repository source was unavailable.
|
|
1419
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during discovery.
|
|
1420
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
1421
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
1422
|
+
* - ABORTED: Discovery or a repository operation was aborted.
|
|
1423
|
+
*/
|
|
1424
|
+
var discoverCanonicalAssets = async (repository, limits, signal, foundations) => {
|
|
1425
|
+
const diagnostics = createCoreDiagnosticCollector(limits, "validate-project");
|
|
1426
|
+
const working = createWorkingInventory();
|
|
1427
|
+
const moldeaEntry = foundations === void 0 ? await readExactEntry(repository, MOLDEA_ROOT$1, signal) : foundations.moldeaRoot;
|
|
1428
|
+
if (moldeaEntry === null) {
|
|
1429
|
+
diagnostics.add({
|
|
1430
|
+
code: "MOLDEA_MANIFEST_MISSING",
|
|
1431
|
+
path: MANIFEST_PATH$1
|
|
1432
|
+
});
|
|
1433
|
+
diagnostics.add({
|
|
1434
|
+
code: "MOLDEA_PROJECT_FILE_MISSING",
|
|
1435
|
+
path: PROJECT_PATH$1
|
|
1436
|
+
});
|
|
1437
|
+
return finalizeResult(working, diagnostics);
|
|
1438
|
+
}
|
|
1439
|
+
if (moldeaEntry.type !== "directory") {
|
|
1440
|
+
addEntryTypeDiagnostic(diagnostics, moldeaEntry, "directory");
|
|
1441
|
+
return finalizeResult(working, diagnostics);
|
|
1442
|
+
}
|
|
1443
|
+
const manifestEntry = foundations === void 0 ? await readExactEntry(repository, MANIFEST_PATH$1, signal) : foundations.manifest;
|
|
1444
|
+
const projectEntry = foundations === void 0 ? await readExactEntry(repository, PROJECT_PATH$1, signal) : foundations.project;
|
|
1445
|
+
retainFoundationEntry(manifestEntry, MANIFEST_PATH$1, "MOLDEA_MANIFEST_MISSING", working, diagnostics);
|
|
1446
|
+
retainFoundationEntry(projectEntry, PROJECT_PATH$1, "MOLDEA_PROJECT_FILE_MISSING", working, diagnostics);
|
|
1447
|
+
const seenPaths = /* @__PURE__ */ new Set();
|
|
1448
|
+
let listedManifest = false;
|
|
1449
|
+
let listedProject = false;
|
|
1450
|
+
const listOptions = signal === void 0 ? { prefix: MOLDEA_ROOT$1 } : {
|
|
1451
|
+
prefix: MOLDEA_ROOT$1,
|
|
1452
|
+
signal
|
|
1453
|
+
};
|
|
1454
|
+
for await (const candidate of repository.iterateEntries(listOptions)) {
|
|
1455
|
+
const entry = copyReaderEntry(candidate, "list-entries-page");
|
|
1456
|
+
if (!entry.path.startsWith("/moldea/")) return invalidSourceData$1("list-entries-page", entry.path);
|
|
1457
|
+
if (seenPaths.has(entry.path)) return invalidSourceData$1("list-entries-page", entry.path);
|
|
1458
|
+
seenPaths.add(entry.path);
|
|
1459
|
+
if (entry.path === MANIFEST_PATH$1) {
|
|
1460
|
+
checkFoundationListing(entry, manifestEntry);
|
|
1461
|
+
listedManifest = true;
|
|
1462
|
+
continue;
|
|
1463
|
+
}
|
|
1464
|
+
if (entry.path === PROJECT_PATH$1) {
|
|
1465
|
+
checkFoundationListing(entry, projectEntry);
|
|
1466
|
+
listedProject = true;
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
const classification = classifyCanonicalEntry(entry);
|
|
1470
|
+
switch (classification.kind) {
|
|
1471
|
+
case "canonical-file":
|
|
1472
|
+
retainCanonicalFile(working, entry.path, classification.fileKind, classification.agentId);
|
|
1473
|
+
break;
|
|
1474
|
+
case "agent-directory":
|
|
1475
|
+
getAgent(working.agents, classification.agentId);
|
|
1476
|
+
break;
|
|
1477
|
+
case "canonical-asset-symlink":
|
|
1478
|
+
diagnostics.add({
|
|
1479
|
+
code: "MOLDEA_CANONICAL_ASSET_SYMLINK",
|
|
1480
|
+
path: entry.path
|
|
1481
|
+
});
|
|
1482
|
+
break;
|
|
1483
|
+
case "entry-type-invalid":
|
|
1484
|
+
addEntryTypeDiagnostic(diagnostics, entry, classification.expectedType);
|
|
1485
|
+
break;
|
|
1486
|
+
case "unrecognized": diagnostics.add({
|
|
1487
|
+
code: "MOLDEA_CANONICAL_PATH_UNRECOGNIZED",
|
|
1488
|
+
details: { entryType: entry.type },
|
|
1489
|
+
path: entry.path
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
if (manifestEntry !== null !== listedManifest) return invalidSourceData$1("list-entries-page", MANIFEST_PATH$1);
|
|
1494
|
+
if (projectEntry !== null !== listedProject) return invalidSourceData$1("list-entries-page", PROJECT_PATH$1);
|
|
1495
|
+
return finalizeResult(working, diagnostics);
|
|
1496
|
+
};
|
|
1497
|
+
//#endregion
|
|
1498
|
+
//#region src/decision-graph-validation/index.ts
|
|
1499
|
+
var sortCandidates = (candidates) => {
|
|
1500
|
+
return [...candidates].sort((left, right) => compareExactStrings(left.path, right.path));
|
|
1501
|
+
};
|
|
1502
|
+
var buildGraphIndex = (candidates) => {
|
|
1503
|
+
const mutableGroups = /* @__PURE__ */ new Map();
|
|
1504
|
+
for (const candidate of sortCandidates(candidates)) {
|
|
1505
|
+
if (candidate.id === null) continue;
|
|
1506
|
+
const group = mutableGroups.get(candidate.id) ?? [];
|
|
1507
|
+
group.push(candidate);
|
|
1508
|
+
mutableGroups.set(candidate.id, group);
|
|
1509
|
+
}
|
|
1510
|
+
const candidateGroups = /* @__PURE__ */ new Map();
|
|
1511
|
+
const nodes = /* @__PURE__ */ new Map();
|
|
1512
|
+
for (const id of [...mutableGroups.keys()].sort(compareExactStrings)) {
|
|
1513
|
+
const group = mutableGroups.get(id) ?? [];
|
|
1514
|
+
candidateGroups.set(id, group);
|
|
1515
|
+
if (group.length === 1 && group[0]?.decision !== null) {
|
|
1516
|
+
const decision = group[0]?.decision;
|
|
1517
|
+
if (decision !== void 0) nodes.set(id, decision);
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
const edges = /* @__PURE__ */ new Map();
|
|
1521
|
+
for (const [id, decision] of nodes) {
|
|
1522
|
+
const resolvedTargets = decision.supersedes.filter((targetId) => {
|
|
1523
|
+
const group = candidateGroups.get(targetId);
|
|
1524
|
+
return group?.length === 1 && group[0]?.decision !== null;
|
|
1525
|
+
});
|
|
1526
|
+
edges.set(id, resolvedTargets);
|
|
1527
|
+
}
|
|
1528
|
+
return {
|
|
1529
|
+
candidateGroups,
|
|
1530
|
+
edges,
|
|
1531
|
+
nodes
|
|
1532
|
+
};
|
|
1533
|
+
};
|
|
1534
|
+
var collectDuplicateDiagnostics = (index, diagnostics) => {
|
|
1535
|
+
for (const [id, candidates] of index.candidateGroups) {
|
|
1536
|
+
if (candidates.length < 2) continue;
|
|
1537
|
+
for (const candidate of candidates) diagnostics.add({
|
|
1538
|
+
code: "MOLDEA_DECISION_ID_DUPLICATE",
|
|
1539
|
+
details: { occurrences: candidates.length },
|
|
1540
|
+
entity: { decisionId: id },
|
|
1541
|
+
path: candidate.path
|
|
1542
|
+
});
|
|
1543
|
+
}
|
|
1544
|
+
};
|
|
1545
|
+
var collectReferenceDiagnostics = (index, diagnostics) => {
|
|
1546
|
+
for (const decision of index.nodes.values()) for (const targetId of decision.supersedes) {
|
|
1547
|
+
if (index.candidateGroups.has(targetId)) continue;
|
|
1548
|
+
diagnostics.add({
|
|
1549
|
+
code: "MOLDEA_DECISION_REFERENCE_MISSING",
|
|
1550
|
+
details: { referencedDecisionId: targetId },
|
|
1551
|
+
entity: { decisionId: decision.id },
|
|
1552
|
+
path: decision.path,
|
|
1553
|
+
pointer: "/supersedes"
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
var collectStatusDiagnostics = (index, diagnostics) => {
|
|
1558
|
+
const activelySupersededIds = /* @__PURE__ */ new Set();
|
|
1559
|
+
for (const decision of index.nodes.values()) {
|
|
1560
|
+
if (decision.status !== "accepted" && decision.status !== "superseded") continue;
|
|
1561
|
+
for (const targetId of index.edges.get(decision.id) ?? []) {
|
|
1562
|
+
const target = index.nodes.get(targetId);
|
|
1563
|
+
if (target === void 0) continue;
|
|
1564
|
+
if (target.status === "superseded") {
|
|
1565
|
+
activelySupersededIds.add(targetId);
|
|
1566
|
+
continue;
|
|
1567
|
+
}
|
|
1568
|
+
diagnostics.add({
|
|
1569
|
+
code: "MOLDEA_DECISION_SUPERSESSION_STATUS_INVALID",
|
|
1570
|
+
details: {
|
|
1571
|
+
referencedDecisionId: targetId,
|
|
1572
|
+
sourceStatus: decision.status,
|
|
1573
|
+
targetStatus: target.status
|
|
1574
|
+
},
|
|
1575
|
+
entity: { decisionId: decision.id },
|
|
1576
|
+
path: decision.path,
|
|
1577
|
+
pointer: "/supersedes"
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
return activelySupersededIds;
|
|
1582
|
+
};
|
|
1583
|
+
var collectOrphanDiagnostics = (index, activelySupersededIds, diagnostics) => {
|
|
1584
|
+
for (const decision of index.nodes.values()) if (decision.status === "superseded" && !activelySupersededIds.has(decision.id)) diagnostics.add({
|
|
1585
|
+
code: "MOLDEA_DECISION_SUPERSEDED_ORPHAN",
|
|
1586
|
+
entity: { decisionId: decision.id },
|
|
1587
|
+
path: decision.path
|
|
1588
|
+
});
|
|
1589
|
+
};
|
|
1590
|
+
var calculateFinishOrder = (index) => {
|
|
1591
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1592
|
+
const finished = [];
|
|
1593
|
+
for (const startId of index.nodes.keys()) {
|
|
1594
|
+
if (visited.has(startId)) continue;
|
|
1595
|
+
const stack = [{
|
|
1596
|
+
id: startId,
|
|
1597
|
+
nextNeighbor: 0
|
|
1598
|
+
}];
|
|
1599
|
+
visited.add(startId);
|
|
1600
|
+
while (stack.length > 0) {
|
|
1601
|
+
const current = stack.at(-1);
|
|
1602
|
+
if (current === void 0) break;
|
|
1603
|
+
const neighbor = (index.edges.get(current.id) ?? [])[current.nextNeighbor];
|
|
1604
|
+
if (neighbor === void 0) {
|
|
1605
|
+
finished.push(current.id);
|
|
1606
|
+
stack.pop();
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
current.nextNeighbor += 1;
|
|
1610
|
+
if (!visited.has(neighbor)) {
|
|
1611
|
+
visited.add(neighbor);
|
|
1612
|
+
stack.push({
|
|
1613
|
+
id: neighbor,
|
|
1614
|
+
nextNeighbor: 0
|
|
1615
|
+
});
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
return finished;
|
|
1620
|
+
};
|
|
1621
|
+
var buildReverseEdges = (index) => {
|
|
1622
|
+
const reverseEdges = /* @__PURE__ */ new Map();
|
|
1623
|
+
for (const id of index.nodes.keys()) reverseEdges.set(id, []);
|
|
1624
|
+
for (const [sourceId, targets] of index.edges) for (const targetId of targets) reverseEdges.get(targetId)?.push(sourceId);
|
|
1625
|
+
for (const sources of reverseEdges.values()) sources.sort(compareExactStrings);
|
|
1626
|
+
return reverseEdges;
|
|
1627
|
+
};
|
|
1628
|
+
var findStronglyConnectedComponents = (index) => {
|
|
1629
|
+
const reverseEdges = buildReverseEdges(index);
|
|
1630
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1631
|
+
const components = [];
|
|
1632
|
+
for (const startId of [...calculateFinishOrder(index)].reverse()) {
|
|
1633
|
+
if (visited.has(startId)) continue;
|
|
1634
|
+
const component = [];
|
|
1635
|
+
const stack = [startId];
|
|
1636
|
+
visited.add(startId);
|
|
1637
|
+
while (stack.length > 0) {
|
|
1638
|
+
const currentId = stack.pop();
|
|
1639
|
+
if (currentId === void 0) break;
|
|
1640
|
+
component.push(currentId);
|
|
1641
|
+
for (const sourceId of [...reverseEdges.get(currentId) ?? []].reverse()) if (!visited.has(sourceId)) {
|
|
1642
|
+
visited.add(sourceId);
|
|
1643
|
+
stack.push(sourceId);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
components.push(component.sort(compareExactStrings));
|
|
1647
|
+
}
|
|
1648
|
+
return components.sort((left, right) => compareExactStrings(left[0] ?? "", right[0] ?? ""));
|
|
1649
|
+
};
|
|
1650
|
+
var collectCycleDiagnostics = (index, diagnostics) => {
|
|
1651
|
+
for (const component of findStronglyConnectedComponents(index)) {
|
|
1652
|
+
if (component.length < 2) continue;
|
|
1653
|
+
const cycleRepresentativeDecisionId = component[0];
|
|
1654
|
+
if (cycleRepresentativeDecisionId === void 0) continue;
|
|
1655
|
+
for (const decisionId of component) {
|
|
1656
|
+
const decision = index.nodes.get(decisionId);
|
|
1657
|
+
if (decision !== void 0) diagnostics.add({
|
|
1658
|
+
code: "MOLDEA_DECISION_SUPERSESSION_CYCLE",
|
|
1659
|
+
details: {
|
|
1660
|
+
cycleRepresentativeDecisionId,
|
|
1661
|
+
cycleSize: component.length
|
|
1662
|
+
},
|
|
1663
|
+
entity: { decisionId },
|
|
1664
|
+
path: decision.path,
|
|
1665
|
+
pointer: "/supersedes"
|
|
1666
|
+
});
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
};
|
|
1670
|
+
/**
|
|
1671
|
+
* Validates every trustworthy cross-file relationship in one decision candidate set.
|
|
1672
|
+
* @param candidates Discovered candidates with valid parsed decisions where available.
|
|
1673
|
+
* @param limits The immutable Core limits governing repository-level diagnostics.
|
|
1674
|
+
* @returns Deterministically sorted, deeply immutable graph diagnostics.
|
|
1675
|
+
* @throws
|
|
1676
|
+
* - RESOURCE_LIMIT_EXCEEDED: The repository-level diagnostic budget was exceeded.
|
|
1677
|
+
*/
|
|
1678
|
+
var validateDecisionGraph = (candidates, limits) => {
|
|
1679
|
+
const diagnostics = createCoreDiagnosticCollector(limits, "validate-project");
|
|
1680
|
+
const index = buildGraphIndex(candidates);
|
|
1681
|
+
collectDuplicateDiagnostics(index, diagnostics);
|
|
1682
|
+
collectReferenceDiagnostics(index, diagnostics);
|
|
1683
|
+
collectCycleDiagnostics(index, diagnostics);
|
|
1684
|
+
collectOrphanDiagnostics(index, collectStatusDiagnostics(index, diagnostics), diagnostics);
|
|
1685
|
+
return diagnostics.finalize();
|
|
1686
|
+
};
|
|
1687
|
+
//#endregion
|
|
1688
|
+
//#region src/decision-graph/index.ts
|
|
1689
|
+
var invalidSourceData = (path) => {
|
|
1690
|
+
throw new RepositorySourceException({
|
|
1691
|
+
code: "INVALID_SOURCE_DATA",
|
|
1692
|
+
operation: "read-file-page",
|
|
1693
|
+
path,
|
|
1694
|
+
retryable: false
|
|
1695
|
+
});
|
|
1696
|
+
};
|
|
1697
|
+
var sortDecisions = (decisions) => {
|
|
1698
|
+
return [...decisions].sort((left, right) => {
|
|
1699
|
+
return compareExactStrings(left.id, right.id) || compareExactStrings(left.path, right.path);
|
|
1700
|
+
});
|
|
1701
|
+
};
|
|
1702
|
+
/**
|
|
1703
|
+
* Reads, parses, and validates every discovered decision through one repository snapshot.
|
|
1704
|
+
* @param repository The coherent source-neutral repository reader.
|
|
1705
|
+
* @param paths The decision candidates retained by canonical discovery.
|
|
1706
|
+
* @param options The immutable Core limit snapshot.
|
|
1707
|
+
* @param signal Optional cancellation forwarded to every repository read.
|
|
1708
|
+
* @returns A deeply immutable internal decision set and its deterministic diagnostics.
|
|
1709
|
+
* @throws
|
|
1710
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
1711
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
1712
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
1713
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
1714
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
1715
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
1716
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
1717
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
1718
|
+
* - ABORTED: The Core operation was aborted.
|
|
1719
|
+
* - ABORTED: The repository operation was aborted.
|
|
1720
|
+
*/
|
|
1721
|
+
var readDecisionGraph = async (repository, paths, options, signal) => {
|
|
1722
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
1723
|
+
const diagnostics = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
1724
|
+
const candidates = [];
|
|
1725
|
+
const decisions = [];
|
|
1726
|
+
const readOptions = signal === void 0 ? void 0 : { signal };
|
|
1727
|
+
for (const path of [...paths].sort(compareExactStrings)) {
|
|
1728
|
+
const content = await repository.readCompleteFile(path, readOptions);
|
|
1729
|
+
if (!(content instanceof Uint8Array)) return invalidSourceData(path);
|
|
1730
|
+
const parsed = await parseDecisionDocument({
|
|
1731
|
+
content: content.slice(),
|
|
1732
|
+
path
|
|
1733
|
+
}, options, "validate-project");
|
|
1734
|
+
for (const diagnostic of parsed.diagnostics) diagnostics.merge(diagnostic);
|
|
1735
|
+
candidates.push({
|
|
1736
|
+
decision: parsed.decision,
|
|
1737
|
+
id: parseDecisionIdFromPath(path),
|
|
1738
|
+
path
|
|
1739
|
+
});
|
|
1740
|
+
if (parsed.decision !== null) decisions.push(parsed.decision);
|
|
1741
|
+
}
|
|
1742
|
+
for (const diagnostic of validateDecisionGraph(candidates, options.limits)) diagnostics.merge(diagnostic);
|
|
1743
|
+
const finalizedDiagnostics = diagnostics.finalize();
|
|
1744
|
+
return freezeRecursively({
|
|
1745
|
+
decisions: sortDecisions(decisions),
|
|
1746
|
+
diagnostics: finalizedDiagnostics,
|
|
1747
|
+
valid: finalizedDiagnostics.length === 0
|
|
1748
|
+
});
|
|
1749
|
+
};
|
|
1750
|
+
//#endregion
|
|
1751
|
+
//#region src/manifest-relationship-validation/index.ts
|
|
1752
|
+
/** Creates diagnostic entity metadata for a decision relationship. */
|
|
1753
|
+
var createDecisionEntity = (decisionPath, agentId) => {
|
|
1754
|
+
const decisionId = parseDecisionIdFromPath(decisionPath);
|
|
1755
|
+
return {
|
|
1756
|
+
...agentId === void 0 ? {} : { agentId },
|
|
1757
|
+
...decisionId === null ? {} : { decisionId }
|
|
1758
|
+
};
|
|
1759
|
+
};
|
|
1760
|
+
/** Adds a missing-context diagnostic unless discovery owns the target-path failure. */
|
|
1761
|
+
var validateContextRelationship = (referencedPath, pointer, validContextPaths, context, agentId) => {
|
|
1762
|
+
if (validContextPaths.has(referencedPath) || context.blockedPaths.has(referencedPath)) return;
|
|
1763
|
+
context.collector.add({
|
|
1764
|
+
code: "MOLDEA_REFERENCE_MISSING",
|
|
1765
|
+
details: { referencedPath },
|
|
1766
|
+
entity: agentId === void 0 ? null : { agentId },
|
|
1767
|
+
path: context.manifestPath,
|
|
1768
|
+
pointer
|
|
1769
|
+
});
|
|
1770
|
+
};
|
|
1771
|
+
/** Adds missing or inactive diagnostics for a decision relationship. */
|
|
1772
|
+
var validateDecisionRelationship = (referencedPath, pointer, context, agentId) => {
|
|
1773
|
+
if (!context.discoveredDecisionPaths.has(referencedPath)) {
|
|
1774
|
+
if (context.blockedPaths.has(referencedPath)) return;
|
|
1775
|
+
context.collector.add({
|
|
1776
|
+
code: "MOLDEA_DECISION_REFERENCE_MISSING",
|
|
1777
|
+
details: { referencedPath },
|
|
1778
|
+
entity: createDecisionEntity(referencedPath, agentId),
|
|
1779
|
+
path: context.manifestPath,
|
|
1780
|
+
pointer
|
|
1781
|
+
});
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
const parsedDecision = context.parsedDecisionsByPath.get(referencedPath);
|
|
1785
|
+
if (parsedDecision === void 0 || parsedDecision.status === "accepted") return;
|
|
1786
|
+
context.collector.add({
|
|
1787
|
+
code: "MOLDEA_DECISION_RELATIONSHIP_INACTIVE",
|
|
1788
|
+
details: {
|
|
1789
|
+
referencedPath,
|
|
1790
|
+
targetStatus: parsedDecision.status
|
|
1791
|
+
},
|
|
1792
|
+
entity: createDecisionEntity(referencedPath, agentId),
|
|
1793
|
+
path: context.manifestPath,
|
|
1794
|
+
pointer
|
|
1795
|
+
});
|
|
1796
|
+
};
|
|
1797
|
+
/**
|
|
1798
|
+
* Validates manifest and agent relationships against canonical discovery and parsed decisions.
|
|
1799
|
+
* @param manifestPath The canonical manifest path used for relationship diagnostics.
|
|
1800
|
+
* @param manifest The normalized manifest contract to validate.
|
|
1801
|
+
* @param discovery The canonical repository discovery result.
|
|
1802
|
+
* @param decisions The successfully parsed canonical decisions.
|
|
1803
|
+
* @param limits The resource limits applied to emitted diagnostics.
|
|
1804
|
+
* @returns The frozen deterministic relationship diagnostics.
|
|
1805
|
+
* @throws
|
|
1806
|
+
* - RESOURCE_LIMIT_EXCEEDED: The repository-level diagnostic budget was exceeded.
|
|
1807
|
+
*/
|
|
1808
|
+
var validateManifestRelationships = (manifestPath, manifest, discovery, decisions, limits) => {
|
|
1809
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
1810
|
+
const validAgentContextPaths = new Set(discovery.inventory.context);
|
|
1811
|
+
const validTopLevelContextPaths = new Set(validAgentContextPaths);
|
|
1812
|
+
if (discovery.inventory.project !== null) validTopLevelContextPaths.add(discovery.inventory.project);
|
|
1813
|
+
const validationContext = {
|
|
1814
|
+
blockedPaths: new Set(discovery.diagnostics.flatMap((diagnostic) => diagnostic.path === null ? [] : [diagnostic.path])),
|
|
1815
|
+
collector,
|
|
1816
|
+
discoveredDecisionPaths: new Set(discovery.inventory.decisions),
|
|
1817
|
+
manifestPath,
|
|
1818
|
+
parsedDecisionsByPath: new Map(decisions.map((decision) => [decision.path, decision])),
|
|
1819
|
+
validAgentContextPaths,
|
|
1820
|
+
validTopLevelContextPaths
|
|
1821
|
+
};
|
|
1822
|
+
for (const referencedPathValue of Object.keys(manifest.context ?? {}).sort(compareExactStrings)) validateContextRelationship(parseRepositoryPath(referencedPathValue), `/context/${escapeJsonPointerSegment(referencedPathValue)}`, validationContext.validTopLevelContextPaths, validationContext);
|
|
1823
|
+
for (const referencedPathValue of Object.keys(manifest.decisions ?? {}).sort(compareExactStrings)) validateDecisionRelationship(parseRepositoryPath(referencedPathValue), `/decisions/${escapeJsonPointerSegment(referencedPathValue)}`, validationContext);
|
|
1824
|
+
for (const agentId of Object.keys(manifest.agents ?? {}).sort(compareExactStrings)) {
|
|
1825
|
+
const agent = manifest.agents?.[agentId];
|
|
1826
|
+
if (agent === void 0) continue;
|
|
1827
|
+
const escapedAgentId = escapeJsonPointerSegment(agentId);
|
|
1828
|
+
for (const referencedPath of [...agent.context ?? []].sort(compareExactStrings)) validateContextRelationship(referencedPath, `/agents/${escapedAgentId}/context`, validationContext.validAgentContextPaths, validationContext, agentId);
|
|
1829
|
+
for (const referencedPath of [...agent.decisions ?? []].sort(compareExactStrings)) validateDecisionRelationship(referencedPath, `/agents/${escapedAgentId}/decisions`, validationContext, agentId);
|
|
1830
|
+
}
|
|
1831
|
+
return collector.finalize();
|
|
1832
|
+
};
|
|
1833
|
+
//#endregion
|
|
1834
|
+
//#region src/integration-validation/parser.ts
|
|
1835
|
+
var START = "<!-- moldea:start -->";
|
|
1836
|
+
var END = "<!-- moldea:end -->";
|
|
1837
|
+
/** Finds one ordered marker pair on complete lines, matching the skill writer's region semantics. */
|
|
1838
|
+
var parseManagedIntegrationRegion = (text) => {
|
|
1839
|
+
const start = text.indexOf(START);
|
|
1840
|
+
const end = text.indexOf(END);
|
|
1841
|
+
if (start < 0 || end < start || text.indexOf(START, start + 21) >= 0 || text.indexOf(END, end + 19) >= 0) return null;
|
|
1842
|
+
const startsLine = (offset) => offset === 0 || offset === 1 && text[0] === "" || text[offset - 1] === "\n";
|
|
1843
|
+
if (!startsLine(start) || !startsLine(end)) return null;
|
|
1844
|
+
const afterStart = start + 21;
|
|
1845
|
+
const lineEnding = text.startsWith("\r\n", afterStart) ? "\r\n" : text[afterStart] === "\n" ? "\n" : null;
|
|
1846
|
+
if (lineEnding === null) return null;
|
|
1847
|
+
const afterEnd = end + 19;
|
|
1848
|
+
const endLength = text.startsWith("\r\n", afterEnd) ? 2 : text[afterEnd] === "\n" ? 1 : 0;
|
|
1849
|
+
if (endLength === 0 && afterEnd !== text.length) return null;
|
|
1850
|
+
return {
|
|
1851
|
+
start,
|
|
1852
|
+
end: afterEnd + endLength,
|
|
1853
|
+
lineEnding
|
|
1854
|
+
};
|
|
1855
|
+
};
|
|
1856
|
+
/**
|
|
1857
|
+
* Extracts the relative installation path from the fixed relevance-gate command without executing it.
|
|
1858
|
+
* @returns The safe installation path or the reason it could not be obtained.
|
|
1859
|
+
*/
|
|
1860
|
+
var parseIntegrationBlock = (text) => {
|
|
1861
|
+
const region = parseManagedIntegrationRegion(text);
|
|
1862
|
+
if (region === null) return {
|
|
1863
|
+
kind: "missing",
|
|
1864
|
+
reason: text.includes(START) || text.includes(END) ? "block-invalid" : "block-missing"
|
|
1865
|
+
};
|
|
1866
|
+
const matches = text.slice(region.start, region.end).matchAll(/^node "<repo-root>\/([^"\r\n]+)\/scripts\/relevance-gate\.mjs"(?:[ \t\r]|$)/gm);
|
|
1867
|
+
const first = matches.next().value;
|
|
1868
|
+
if (first === void 0 || !matches.next().done) return {
|
|
1869
|
+
kind: "missing",
|
|
1870
|
+
reason: "skill-path-missing"
|
|
1871
|
+
};
|
|
1872
|
+
const skillPath = first[1];
|
|
1873
|
+
if (skillPath === void 0 || !/^[A-Za-z0-9._/-]+$/.test(skillPath) || skillPath.startsWith("/") || skillPath.endsWith("/") || skillPath.includes("//") || /(?:^|\/)\.{1,2}(?:\/|$)/.test(skillPath)) return {
|
|
1874
|
+
kind: "missing",
|
|
1875
|
+
reason: "skill-path-invalid"
|
|
1876
|
+
};
|
|
1877
|
+
return {
|
|
1878
|
+
kind: "found",
|
|
1879
|
+
skillPath
|
|
1880
|
+
};
|
|
1881
|
+
};
|
|
1882
|
+
//#endregion
|
|
1883
|
+
//#region src/integration-validation/inspection.ts
|
|
1884
|
+
var AGENTS_PATH = parseRepositoryPath("/AGENTS.md");
|
|
1885
|
+
/**
|
|
1886
|
+
* Reads bounded untrusted text with a reservation for decoding and temporary string copies.
|
|
1887
|
+
* @returns Decoded text or null for invalid UTF-8, with its reservation-release operation.
|
|
1888
|
+
* @throws
|
|
1889
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
1890
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
1891
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
1892
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
1893
|
+
* - ABORTED: The Core operation was aborted.
|
|
1894
|
+
* - ABORTED: The repository operation was aborted.
|
|
1895
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
1896
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
1897
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
1898
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
1899
|
+
*/
|
|
1900
|
+
var readText = async (ledger, path) => {
|
|
1901
|
+
const source = await ledger.readSourceFile(path);
|
|
1902
|
+
const reservation = source.bytes.byteLength * 8;
|
|
1903
|
+
let isReserved = false;
|
|
1904
|
+
try {
|
|
1905
|
+
ledger.reserve(reservation);
|
|
1906
|
+
isReserved = true;
|
|
1907
|
+
let text;
|
|
1908
|
+
try {
|
|
1909
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(source.bytes);
|
|
1910
|
+
} catch {
|
|
1911
|
+
text = null;
|
|
1912
|
+
}
|
|
1913
|
+
return {
|
|
1914
|
+
text,
|
|
1915
|
+
release: () => ledger.release(reservation)
|
|
1916
|
+
};
|
|
1917
|
+
} catch (error) {
|
|
1918
|
+
if (isReserved) ledger.release(reservation);
|
|
1919
|
+
throw error;
|
|
1920
|
+
} finally {
|
|
1921
|
+
source.release();
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
/** Distinguishes missing and non-regular files without an extra entry lookup or following symlinks. */
|
|
1925
|
+
var getAbsentKind = (error) => error instanceof RepositorySourceException ? error.code === "ENTRY_NOT_FOUND" ? "missing" : error.code === "ENTRY_NOT_FILE" ? "non-regular" : null : null;
|
|
1926
|
+
/**
|
|
1927
|
+
* Checks the root integration against its installed asset with at most two reads and two lookups.
|
|
1928
|
+
* @returns Completed content-free integration warnings, never an execution or runtime-wiring claim.
|
|
1929
|
+
* @throws
|
|
1930
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
1931
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
1932
|
+
* - ABORTED: The Core operation was aborted.
|
|
1933
|
+
* - ABORTED: The repository operation was aborted.
|
|
1934
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
1935
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
1936
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
1937
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
1938
|
+
*/
|
|
1939
|
+
var inspectCodingAgentIntegration = async (reader, limits, signal) => {
|
|
1940
|
+
const ledger = getRetainedMemory(reader);
|
|
1941
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
1942
|
+
const report = (code, details) => {
|
|
1943
|
+
collector.add({
|
|
1944
|
+
code,
|
|
1945
|
+
path: AGENTS_PATH,
|
|
1946
|
+
subject: AGENTS_PATH,
|
|
1947
|
+
qualifier: null,
|
|
1948
|
+
entity: { integrationPath: AGENTS_PATH },
|
|
1949
|
+
details
|
|
1950
|
+
});
|
|
1951
|
+
return collector.finalize();
|
|
1952
|
+
};
|
|
1953
|
+
let document;
|
|
1954
|
+
try {
|
|
1955
|
+
document = await readText(ledger, AGENTS_PATH);
|
|
1956
|
+
} catch (error) {
|
|
1957
|
+
const entryKind = getAbsentKind(error);
|
|
1958
|
+
if (entryKind !== null) return report("MOLDEA_INTEGRATION_BLOCK_MISSING", { entryKind });
|
|
1959
|
+
throw error;
|
|
1960
|
+
}
|
|
1961
|
+
try {
|
|
1962
|
+
if (document.text === null) return report("MOLDEA_INTEGRATION_BLOCK_MISSING", {
|
|
1963
|
+
entryKind: "file",
|
|
1964
|
+
reason: "invalid-utf8"
|
|
1965
|
+
});
|
|
1966
|
+
const parsed = parseIntegrationBlock(document.text);
|
|
1967
|
+
if (parsed.kind === "missing") return report(parsed.reason.startsWith("block-") ? "MOLDEA_INTEGRATION_BLOCK_MISSING" : "MOLDEA_INTEGRATION_SKILL_MISSING", {
|
|
1968
|
+
entryKind: "file",
|
|
1969
|
+
reason: parsed.reason
|
|
1970
|
+
});
|
|
1971
|
+
const { skillPath } = parsed;
|
|
1972
|
+
for (const file of ["SKILL.md", "scripts/relevance-gate.mjs"]) {
|
|
1973
|
+
const path = parseRepositoryPath(`/${skillPath}/${file}`);
|
|
1974
|
+
const entry = await reader.getEntry(path, signal === void 0 ? void 0 : { signal });
|
|
1975
|
+
if (entry?.type !== "file") return report("MOLDEA_INTEGRATION_SKILL_MISSING", {
|
|
1976
|
+
referencedPath: `/${skillPath}`,
|
|
1977
|
+
missingFile: path,
|
|
1978
|
+
entryKind: entry?.type ?? "missing"
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
const assetPath = parseRepositoryPath(`/${skillPath}/assets/managed-agents-block.md`);
|
|
1982
|
+
let asset;
|
|
1983
|
+
try {
|
|
1984
|
+
asset = await readText(ledger, assetPath);
|
|
1985
|
+
} catch (error) {
|
|
1986
|
+
const entryKind = getAbsentKind(error);
|
|
1987
|
+
if (entryKind !== null) return report("MOLDEA_INTEGRATION_SKILL_MISSING", {
|
|
1988
|
+
referencedPath: `/${skillPath}`,
|
|
1989
|
+
missingFile: assetPath,
|
|
1990
|
+
entryKind
|
|
1991
|
+
});
|
|
1992
|
+
throw error;
|
|
1993
|
+
}
|
|
1994
|
+
try {
|
|
1995
|
+
if (asset.text === null) return report("MOLDEA_INTEGRATION_BLOCK_STALE", {
|
|
1996
|
+
referencedPath: `/${skillPath}`,
|
|
1997
|
+
reason: "invalid-utf8-asset"
|
|
1998
|
+
});
|
|
1999
|
+
const region = parseManagedIntegrationRegion(document.text);
|
|
2000
|
+
let occurrences = 0;
|
|
2001
|
+
let offset = 0;
|
|
2002
|
+
while ((offset = asset.text.indexOf("{{skill-path}}", offset)) !== -1) {
|
|
2003
|
+
occurrences++;
|
|
2004
|
+
offset += 14;
|
|
2005
|
+
}
|
|
2006
|
+
const renderedReservation = (asset.text.length + occurrences * skillPath.length) * 8;
|
|
2007
|
+
ledger.reserve(renderedReservation);
|
|
2008
|
+
try {
|
|
2009
|
+
const expected = asset.text.replaceAll("{{skill-path}}", skillPath).replace(/\r\n?/g, "\n").replaceAll("\n", region.lineEnding);
|
|
2010
|
+
if (document.text.slice(region.start, region.end) !== expected) return report("MOLDEA_INTEGRATION_BLOCK_STALE", { referencedPath: `/${skillPath}` });
|
|
2011
|
+
} finally {
|
|
2012
|
+
ledger.release(renderedReservation);
|
|
2013
|
+
}
|
|
2014
|
+
return collector.finalize();
|
|
2015
|
+
} finally {
|
|
2016
|
+
asset.release();
|
|
2017
|
+
}
|
|
2018
|
+
} finally {
|
|
2019
|
+
document.release();
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
//#endregion
|
|
2023
|
+
//#region src/impact-pattern-validation/inspection.ts
|
|
2024
|
+
/** Identifies the canonical document owning an impact relationship. */
|
|
2025
|
+
var getOwner = (owner) => {
|
|
2026
|
+
if (owner.kind === "context") return {
|
|
2027
|
+
subject: parseRepositoryPath(owner.id),
|
|
2028
|
+
entity: { contextPath: parseRepositoryPath(owner.id) }
|
|
2029
|
+
};
|
|
2030
|
+
if (owner.kind === "decision") return {
|
|
2031
|
+
subject: parseRepositoryPath(owner.id),
|
|
2032
|
+
entity: { decisionId: parseDecisionIdFromPath(parseRepositoryPath(owner.id)) ?? owner.id }
|
|
2033
|
+
};
|
|
2034
|
+
const agentId = owner.agentId ?? owner.id;
|
|
2035
|
+
return {
|
|
2036
|
+
subject: parseRepositoryPath(`/moldea/agents/${agentId}/instruction.md`),
|
|
2037
|
+
entity: {
|
|
2038
|
+
agentId,
|
|
2039
|
+
...owner.kind === "tool" || owner.kind === "skill" ? {
|
|
2040
|
+
capabilityId: owner.id,
|
|
2041
|
+
capabilityKind: owner.kind
|
|
2042
|
+
} : {}
|
|
2043
|
+
}
|
|
2044
|
+
};
|
|
2045
|
+
};
|
|
2046
|
+
/** Groups patterns by their complete fixed directory prefix to share source traversal. */
|
|
2047
|
+
var groupPatterns = (manifest) => {
|
|
2048
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2049
|
+
for (const declaration of collectManifestScopeDeclarations(manifest)) {
|
|
2050
|
+
if (declaration.field !== "affectedBy" || declaration.declaration.kind !== "glob") continue;
|
|
2051
|
+
const pattern = declaration.declaration.pattern;
|
|
2052
|
+
const segments = pattern.slice(1).split("/");
|
|
2053
|
+
const wildcard = segments.findIndex((segment) => segment.includes("*"));
|
|
2054
|
+
const prefix = parseRepositoryPath("/" + segments.slice(0, wildcard).join("/"));
|
|
2055
|
+
const group = groups.get(prefix) ?? [];
|
|
2056
|
+
group.push({
|
|
2057
|
+
declaration,
|
|
2058
|
+
pattern,
|
|
2059
|
+
segments
|
|
2060
|
+
});
|
|
2061
|
+
groups.set(prefix, group);
|
|
2062
|
+
}
|
|
2063
|
+
return groups;
|
|
2064
|
+
};
|
|
2065
|
+
/**
|
|
2066
|
+
* Checks globs only after required inspection, sharing bounded traversals and stopping on matches.
|
|
2067
|
+
* @returns Completed warnings, or no warnings when any traversal exhausts its resource budget.
|
|
2068
|
+
* @throws
|
|
2069
|
+
* - ABORTED: The Core operation was aborted.
|
|
2070
|
+
* - ABORTED: The repository operation was aborted.
|
|
2071
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
2072
|
+
* - ENTRY_NOT_DIRECTORY: The requested repository entry is not a directory.
|
|
2073
|
+
* - PROVIDER_INCOMPLETE: The repository provider cannot expose a complete result.
|
|
2074
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2075
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2076
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
2077
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
2078
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2079
|
+
*/
|
|
2080
|
+
var inspectImpactPatterns = async (reader, manifest, limits, signal) => {
|
|
2081
|
+
const unmatched = [];
|
|
2082
|
+
try {
|
|
2083
|
+
for (const [prefix, patterns] of groupPatterns(manifest)) {
|
|
2084
|
+
const pending = new Set(patterns);
|
|
2085
|
+
if ((await reader.getEntry(prefix, signal === void 0 ? void 0 : { signal }))?.type === "directory") for await (const candidate of reader.iterateEntries({
|
|
2086
|
+
prefix,
|
|
2087
|
+
...signal === void 0 ? {} : { signal }
|
|
2088
|
+
})) {
|
|
2089
|
+
if (candidate.type !== "file") continue;
|
|
2090
|
+
for (const pattern of pending) if (matchesRepositoryGlob(pattern.segments, candidate.path)) pending.delete(pattern);
|
|
2091
|
+
if (pending.size === 0) break;
|
|
2092
|
+
}
|
|
2093
|
+
unmatched.push(...pending);
|
|
2094
|
+
}
|
|
2095
|
+
} catch (error) {
|
|
2096
|
+
if (error instanceof CoreOperationException && error.code === "RESOURCE_LIMIT_EXCEEDED" && error.limit === "maxEntries" || error instanceof RepositorySourceException && error.code === "RESOURCE_LIMIT_EXCEEDED") return {
|
|
2097
|
+
completed: false,
|
|
2098
|
+
diagnostics: []
|
|
2099
|
+
};
|
|
2100
|
+
throw error;
|
|
2101
|
+
}
|
|
2102
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
2103
|
+
for (const { declaration, pattern } of unmatched) {
|
|
2104
|
+
const { subject, entity } = getOwner(declaration.owner);
|
|
2105
|
+
collector.add({
|
|
2106
|
+
code: "MOLDEA_IMPACT_PATTERN_UNMATCHED",
|
|
2107
|
+
path: parseRepositoryPath("/moldea/moldea.yaml"),
|
|
2108
|
+
pointer: declaration.pointer,
|
|
2109
|
+
subject,
|
|
2110
|
+
qualifier: pattern,
|
|
2111
|
+
entity,
|
|
2112
|
+
details: {
|
|
2113
|
+
pattern,
|
|
2114
|
+
owner: subject
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
return {
|
|
2119
|
+
completed: true,
|
|
2120
|
+
diagnostics: collector.finalize()
|
|
2121
|
+
};
|
|
2122
|
+
};
|
|
2123
|
+
//#endregion
|
|
2124
|
+
//#region src/warning-acceptance/acceptance.ts
|
|
2125
|
+
var identity = (entry) => JSON.stringify([
|
|
2126
|
+
entry.code,
|
|
2127
|
+
entry.subject,
|
|
2128
|
+
entry.qualifier ?? null
|
|
2129
|
+
]);
|
|
2130
|
+
/**
|
|
2131
|
+
* Applies explicit manifest acceptances only to checks that completed, retaining all findings.
|
|
2132
|
+
* @returns Normalized diagnostics with acceptance flags, reasons, and obsolete-acceptance warnings.
|
|
2133
|
+
* @throws
|
|
2134
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2135
|
+
*/
|
|
2136
|
+
var applyWarningAcceptances = (diagnostics, entries, completed, limits) => {
|
|
2137
|
+
const acceptances = new Map(entries.map((entry, index) => [identity(entry), {
|
|
2138
|
+
entry,
|
|
2139
|
+
index
|
|
2140
|
+
}]));
|
|
2141
|
+
const matched = /* @__PURE__ */ new Set();
|
|
2142
|
+
const results = diagnostics.map((diagnostic) => {
|
|
2143
|
+
if (diagnostic.severity !== "warning" || !diagnostic.acceptable || !completed.has(diagnostic.code)) return diagnostic;
|
|
2144
|
+
const key = identity(diagnostic);
|
|
2145
|
+
const acceptance = acceptances.get(key);
|
|
2146
|
+
if (acceptance === void 0) return diagnostic;
|
|
2147
|
+
matched.add(key);
|
|
2148
|
+
return freezeRecursively({
|
|
2149
|
+
...diagnostic,
|
|
2150
|
+
accepted: true,
|
|
2151
|
+
reason: acceptance.entry.reason
|
|
2152
|
+
});
|
|
2153
|
+
});
|
|
2154
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
2155
|
+
for (const [key, { entry, index }] of acceptances) {
|
|
2156
|
+
if (matched.has(key) || !completed.has(entry.code)) continue;
|
|
2157
|
+
const pointer = `/acceptedWarnings/${index}`;
|
|
2158
|
+
collector.add({
|
|
2159
|
+
code: "MOLDEA_ACCEPTANCE_UNMATCHED",
|
|
2160
|
+
path: parseRepositoryPath("/moldea/moldea.yaml"),
|
|
2161
|
+
pointer,
|
|
2162
|
+
subject: parseRepositoryPath("/moldea/moldea.yaml"),
|
|
2163
|
+
qualifier: pointer,
|
|
2164
|
+
details: {
|
|
2165
|
+
code: entry.code,
|
|
2166
|
+
subject: entry.subject,
|
|
2167
|
+
qualifier: entry.qualifier ?? null
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
}
|
|
2171
|
+
return normalizeDiagnostics([...results, ...collector.finalize()]);
|
|
2172
|
+
};
|
|
2173
|
+
//#endregion
|
|
2174
|
+
//#region src/mirrors/index.ts
|
|
2175
|
+
var addDiagnostics$3 = (collector, diagnostics) => {
|
|
2176
|
+
for (const diagnostic of diagnostics) collector.merge(diagnostic);
|
|
2177
|
+
};
|
|
2178
|
+
/** Adds one declaration-owned mirror diagnostic with safe target metadata. */
|
|
2179
|
+
var addMirrorDiagnostic = (collector, code, manifestPath, mirrorPath, pointer, agentId, entry) => {
|
|
2180
|
+
collector.add({
|
|
2181
|
+
code,
|
|
2182
|
+
details: {
|
|
2183
|
+
...entry === void 0 ? {} : { actualType: entry.type },
|
|
2184
|
+
mirrorPath
|
|
2185
|
+
},
|
|
2186
|
+
entity: { agentId },
|
|
2187
|
+
path: manifestPath,
|
|
2188
|
+
pointer
|
|
2189
|
+
});
|
|
2190
|
+
};
|
|
2191
|
+
/** Resolves, normalizes, and compares one declared mirror. */
|
|
2192
|
+
var inspectMirror = async (repository, manifestPath, canonicalInstruction, mirrorPath, pointer, agentId, options, collector, operationOptions, signal) => {
|
|
2193
|
+
const entry = await repository.getEntry(mirrorPath, operationOptions);
|
|
2194
|
+
if (entry === null) {
|
|
2195
|
+
addMirrorDiagnostic(collector, "MOLDEA_MIRROR_MISSING", manifestPath, mirrorPath, pointer, agentId);
|
|
2196
|
+
return null;
|
|
2197
|
+
}
|
|
2198
|
+
if (entry.type === "symlink") {
|
|
2199
|
+
addMirrorDiagnostic(collector, "MOLDEA_MIRROR_SYMLINK", manifestPath, mirrorPath, pointer, agentId, entry);
|
|
2200
|
+
return null;
|
|
2201
|
+
}
|
|
2202
|
+
if (entry.type !== "file") {
|
|
2203
|
+
addMirrorDiagnostic(collector, "MOLDEA_MIRROR_NOT_FILE", manifestPath, mirrorPath, pointer, agentId, entry);
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
const mirrorResult = await readRepositoryTextAsset(repository, mirrorPath, options.limits, signal);
|
|
2207
|
+
addDiagnostics$3(collector, mirrorResult.diagnostics);
|
|
2208
|
+
if (mirrorResult.asset === null) return null;
|
|
2209
|
+
if (mirrorResult.asset.content !== canonicalInstruction.content) {
|
|
2210
|
+
addMirrorDiagnostic(collector, "MOLDEA_MIRROR_STALE", manifestPath, mirrorPath, pointer, agentId);
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2213
|
+
return {
|
|
2214
|
+
byteLength: mirrorResult.asset.utf8ByteLength,
|
|
2215
|
+
canonicalDigest: canonicalInstruction.digest,
|
|
2216
|
+
digest: mirrorResult.asset.digest,
|
|
2217
|
+
path: mirrorPath,
|
|
2218
|
+
scalarLength: mirrorResult.asset.scalarLength
|
|
2219
|
+
};
|
|
2220
|
+
};
|
|
2221
|
+
/**
|
|
2222
|
+
* Compares every declared mirror with its available normalized canonical instruction.
|
|
2223
|
+
* @param repository The budget-aware reader for the active inspection session.
|
|
2224
|
+
* @param manifestPath The canonical manifest path used for declaration diagnostics.
|
|
2225
|
+
* @param agents The registered agent assets and normalized instruction prerequisites.
|
|
2226
|
+
* @param options The immutable Core configuration snapshot.
|
|
2227
|
+
* @param signal Optional cancellation forwarded to every mirror lookup and read.
|
|
2228
|
+
* @returns Deeply immutable index-ready mirrors and deterministic diagnostics.
|
|
2229
|
+
* @throws
|
|
2230
|
+
* - INVALID_REPOSITORY_PATH: A repository path is invalid.
|
|
2231
|
+
* - ENTRY_NOT_FOUND: A confirmed mirror file disappeared from the reader snapshot.
|
|
2232
|
+
* - ENTRY_NOT_FILE: A confirmed mirror file changed type during inspection.
|
|
2233
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2234
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2235
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during mirror inspection.
|
|
2236
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
2237
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
2238
|
+
* - ABORTED: Mirror inspection or a repository operation was aborted.
|
|
2239
|
+
*/
|
|
2240
|
+
var inspectMirrors = async (repository, manifestPath, agents, options, signal) => {
|
|
2241
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
2242
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
2243
|
+
const agentMirrors = [];
|
|
2244
|
+
const operationOptions = signal === void 0 ? void 0 : { signal };
|
|
2245
|
+
for (const agent of [...agents].sort((left, right) => compareExactStrings(left.id, right.id))) {
|
|
2246
|
+
const mirrors = [];
|
|
2247
|
+
if (agent.instruction !== null) {
|
|
2248
|
+
const declaredMirrors = [...agent.declaration.mirrors ?? []].sort(compareExactStrings);
|
|
2249
|
+
for (const [index, mirrorPath] of declaredMirrors.entries()) {
|
|
2250
|
+
const mirror = await inspectMirror(repository, manifestPath, agent.instruction, mirrorPath, `/agents/${escapeJsonPointerSegment(agent.id)}/mirrors/${index}`, agent.id, options, collector, operationOptions, signal);
|
|
2251
|
+
if (mirror !== null) mirrors.push(mirror);
|
|
2252
|
+
}
|
|
2253
|
+
}
|
|
2254
|
+
agentMirrors.push({
|
|
2255
|
+
id: agent.id,
|
|
2256
|
+
mirrors
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
const diagnostics = collector.finalize();
|
|
2260
|
+
return freezeRecursively({
|
|
2261
|
+
agentMirrors,
|
|
2262
|
+
diagnostics,
|
|
2263
|
+
valid: diagnostics.length === 0
|
|
2264
|
+
});
|
|
2265
|
+
};
|
|
2266
|
+
//#endregion
|
|
2267
|
+
//#region src/project-assets/index.ts
|
|
2268
|
+
var addDiagnostics$2 = (collector, diagnostics) => {
|
|
2269
|
+
for (const diagnostic of diagnostics) collector.merge(diagnostic);
|
|
2270
|
+
};
|
|
2271
|
+
/**
|
|
2272
|
+
* Reads and validates the project foundation before canonical enumeration.
|
|
2273
|
+
* @param repository The coherent source-neutral reader for the active inspection session.
|
|
2274
|
+
* @param path The canonical project path when its exact entry is a regular file.
|
|
2275
|
+
* @param options The immutable Core configuration snapshot.
|
|
2276
|
+
* @param signal Optional cancellation forwarded to the repository read.
|
|
2277
|
+
* @returns The deeply immutable project asset or deterministic content diagnostics.
|
|
2278
|
+
* @throws
|
|
2279
|
+
* - ENTRY_NOT_FOUND: The located project file disappeared from the snapshot.
|
|
2280
|
+
* - ENTRY_NOT_FILE: The located project entry changed type during inspection.
|
|
2281
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2282
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2283
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the project read.
|
|
2284
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
2285
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
2286
|
+
* - ABORTED: Project inspection or the repository read was aborted.
|
|
2287
|
+
*/
|
|
2288
|
+
var readProjectFile = async (repository, path, options, signal) => {
|
|
2289
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
2290
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
2291
|
+
let project = null;
|
|
2292
|
+
if (path !== null) {
|
|
2293
|
+
const result = await readRepositoryTextAsset(repository, path, options.limits, signal);
|
|
2294
|
+
addDiagnostics$2(collector, result.diagnostics);
|
|
2295
|
+
if (result.asset !== null) {
|
|
2296
|
+
if (hasNonWhitespace(result.asset.content)) project = result.asset;
|
|
2297
|
+
else collector.add({
|
|
2298
|
+
code: "MOLDEA_PROJECT_FILE_EMPTY",
|
|
2299
|
+
path: result.asset.path
|
|
2300
|
+
});
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
const diagnostics = collector.finalize();
|
|
2304
|
+
return freezeRecursively({
|
|
2305
|
+
diagnostics,
|
|
2306
|
+
project,
|
|
2307
|
+
valid: diagnostics.length === 0
|
|
2308
|
+
});
|
|
2309
|
+
};
|
|
2310
|
+
/**
|
|
2311
|
+
* Reads the project foundation and every discovered focused-context asset.
|
|
2312
|
+
* @param repository The coherent source-neutral reader for the active inspection session.
|
|
2313
|
+
* @param manifest The normalized manifest used to attach context relationships, when valid.
|
|
2314
|
+
* @param discovery The canonical inventory and path-owned structural diagnostics.
|
|
2315
|
+
* @param options The immutable Core configuration snapshot.
|
|
2316
|
+
* @param signal Optional cancellation forwarded to every repository read.
|
|
2317
|
+
* @param projectFile Optional project foundation already read before canonical enumeration.
|
|
2318
|
+
* @returns Deeply immutable valid assets and deterministic content diagnostics.
|
|
2319
|
+
* @throws
|
|
2320
|
+
* - ENTRY_NOT_FOUND: A discovered project or context file disappeared from the snapshot.
|
|
2321
|
+
* - ENTRY_NOT_FILE: A discovered project or context entry changed type during inspection.
|
|
2322
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2323
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2324
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during asset reads.
|
|
2325
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
2326
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
2327
|
+
* - ABORTED: Project asset inspection or a repository operation was aborted.
|
|
2328
|
+
*/
|
|
2329
|
+
var readProjectAssets = async (repository, manifest, discovery, options, signal, projectFile) => {
|
|
2330
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
2331
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
2332
|
+
const context = [];
|
|
2333
|
+
const inspectedProject = projectFile ?? await readProjectFile(repository, discovery.inventory.project, options, signal);
|
|
2334
|
+
addDiagnostics$2(collector, inspectedProject.diagnostics);
|
|
2335
|
+
for (const path of [...discovery.inventory.context].sort(compareExactStrings)) {
|
|
2336
|
+
const result = await readRepositoryTextAsset(repository, path, options.limits, signal);
|
|
2337
|
+
addDiagnostics$2(collector, result.diagnostics);
|
|
2338
|
+
if (result.asset === null) continue;
|
|
2339
|
+
if (!hasNonWhitespace(result.asset.content)) {
|
|
2340
|
+
collector.add({
|
|
2341
|
+
code: "MOLDEA_CONTEXT_FILE_EMPTY",
|
|
2342
|
+
path
|
|
2343
|
+
});
|
|
2344
|
+
continue;
|
|
2345
|
+
}
|
|
2346
|
+
context.push({
|
|
2347
|
+
asset: result.asset,
|
|
2348
|
+
relationships: manifest?.context?.[path] ?? null
|
|
2349
|
+
});
|
|
2350
|
+
}
|
|
2351
|
+
const diagnostics = collector.finalize();
|
|
2352
|
+
return freezeRecursively({
|
|
2353
|
+
context,
|
|
2354
|
+
diagnostics,
|
|
2355
|
+
project: inspectedProject.project,
|
|
2356
|
+
valid: diagnostics.length === 0
|
|
2357
|
+
});
|
|
2358
|
+
};
|
|
2359
|
+
//#endregion
|
|
2360
|
+
//#region src/project-index/index.ts
|
|
2361
|
+
var createIndexedDecisions = (decisions, manifest) => {
|
|
2362
|
+
return [...decisions].sort((left, right) => compareExactStrings(left.id, right.id) || compareExactStrings(left.path, right.path)).map((decision) => ({
|
|
2363
|
+
decision,
|
|
2364
|
+
relationships: manifest.value.decisions?.[decision.path] ?? null
|
|
2365
|
+
}));
|
|
2366
|
+
};
|
|
2367
|
+
var createIndexedAgents = (agents, agentMirrors) => {
|
|
2368
|
+
const mirrorsByAgent = new Map(agentMirrors.map((entry) => [entry.id, entry.mirrors]));
|
|
2369
|
+
const indexedAgents = [];
|
|
2370
|
+
if (mirrorsByAgent.size !== agents.length) return null;
|
|
2371
|
+
for (const agent of [...agents].sort((left, right) => compareExactStrings(left.id, right.id))) {
|
|
2372
|
+
const mirrors = mirrorsByAgent.get(agent.id);
|
|
2373
|
+
if (agent.description === null || agent.instruction === null || mirrors === void 0) return null;
|
|
2374
|
+
indexedAgents.push({
|
|
2375
|
+
context: [...agent.declaration.context ?? []].sort(compareExactStrings),
|
|
2376
|
+
decisions: [...agent.declaration.decisions ?? []].sort(compareExactStrings),
|
|
2377
|
+
declaration: agent.declaration,
|
|
2378
|
+
description: agent.description,
|
|
2379
|
+
handoffDescription: agent.handoffDescription,
|
|
2380
|
+
id: agent.id,
|
|
2381
|
+
instruction: agent.instruction,
|
|
2382
|
+
mirrors: [...mirrors].sort((left, right) => compareExactStrings(left.path, right.path))
|
|
2383
|
+
});
|
|
2384
|
+
}
|
|
2385
|
+
return indexedAgents;
|
|
2386
|
+
};
|
|
2387
|
+
/**
|
|
2388
|
+
* Constructs one deterministic provisional index from complete universal validation outputs.
|
|
2389
|
+
* @param input The normalized assets and relationships retained by universal inspection.
|
|
2390
|
+
* @returns The deeply immutable project index, or null when a required asset is unavailable.
|
|
2391
|
+
*/
|
|
2392
|
+
var createProjectIndex = (input) => {
|
|
2393
|
+
const agents = createIndexedAgents(input.agents, input.agentMirrors);
|
|
2394
|
+
if (agents === null) return null;
|
|
2395
|
+
const unresolved = input.manifest.value.unresolved ?? createNullPrototypeRecord([]);
|
|
2396
|
+
return freezeRecursively({
|
|
2397
|
+
agents,
|
|
2398
|
+
context: [...input.context].sort((left, right) => compareExactStrings(left.asset.path, right.asset.path)),
|
|
2399
|
+
decisions: createIndexedDecisions(input.decisions, input.manifest),
|
|
2400
|
+
formatVersion: 1,
|
|
2401
|
+
manifest: input.manifest,
|
|
2402
|
+
project: input.project,
|
|
2403
|
+
runtimes: [...input.runtimes].sort((left, right) => compareExactStrings(left.asset.path, right.asset.path)),
|
|
2404
|
+
unresolved
|
|
2405
|
+
});
|
|
2406
|
+
};
|
|
2407
|
+
//#endregion
|
|
2408
|
+
//#region src/export-inventory/inventory.ts
|
|
2409
|
+
/** Identifies syntax-only source formats with explicit runtime export semantics. */
|
|
2410
|
+
var isSupportedExportSource = (path) => !/\.d\.(?:ts|tsx|mts|cts)$/.test(path) && /\.(?:ts|tsx|mts|cts|js|mjs|jsx)$/.test(path);
|
|
2411
|
+
/**
|
|
2412
|
+
* Reads and parses a declared source once, retaining only its export-name set.
|
|
2413
|
+
* @returns A conclusive inventory or the precise reason analysis could not run.
|
|
2414
|
+
* @throws
|
|
2415
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2416
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
2417
|
+
* - ABORTED: The Core operation was aborted.
|
|
2418
|
+
* - ABORTED: The repository operation was aborted.
|
|
2419
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2420
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2421
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
2422
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
2423
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
2424
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
2425
|
+
*/
|
|
2426
|
+
var readExportInventory = async (reader, path) => {
|
|
2427
|
+
if (!isSupportedExportSource(path)) return {
|
|
2428
|
+
kind: "not-checked",
|
|
2429
|
+
reason: "unsupported-file-type"
|
|
2430
|
+
};
|
|
2431
|
+
const ledger = getRetainedMemory(reader);
|
|
2432
|
+
const source = await ledger.readSourceFile(path);
|
|
2433
|
+
const reservation = 65536 + source.bytes.byteLength * 520;
|
|
2434
|
+
try {
|
|
2435
|
+
if (!ledger.tryReserve(reservation)) return {
|
|
2436
|
+
kind: "not-checked",
|
|
2437
|
+
reason: "resource-limit"
|
|
2438
|
+
};
|
|
2439
|
+
try {
|
|
2440
|
+
const { inventoryExports } = await import("./syntax-CVvExx4R.js");
|
|
2441
|
+
const result = inventoryExports(path, source.bytes);
|
|
2442
|
+
if (result.kind === "parsed") {
|
|
2443
|
+
let bytes = 64;
|
|
2444
|
+
for (const name of result.names) bytes += 64 + name.length * 2;
|
|
2445
|
+
ledger.reserve(bytes);
|
|
2446
|
+
}
|
|
2447
|
+
return result;
|
|
2448
|
+
} finally {
|
|
2449
|
+
ledger.release(reservation);
|
|
2450
|
+
}
|
|
2451
|
+
} finally {
|
|
2452
|
+
source.release();
|
|
2453
|
+
}
|
|
2454
|
+
};
|
|
2455
|
+
//#endregion
|
|
2456
|
+
//#region src/repository-reference-validation/index.ts
|
|
2457
|
+
var childPointer = (pointer, segment) => {
|
|
2458
|
+
return `${pointer}/${escapeJsonPointerSegment(segment)}`;
|
|
2459
|
+
};
|
|
2460
|
+
var compareNullableStrings = (left, right) => {
|
|
2461
|
+
if (left === void 0) return right === void 0 ? 0 : -1;
|
|
2462
|
+
return right === void 0 ? 1 : compareExactStrings(left, right);
|
|
2463
|
+
};
|
|
2464
|
+
var compareReferences = (left, right) => {
|
|
2465
|
+
return compareExactStrings(left.path, right.path) || compareNullableStrings(left.symbol, right.symbol);
|
|
2466
|
+
};
|
|
2467
|
+
/** Adds sorted repository-reference array entries with their normalized indexes. */
|
|
2468
|
+
var addReferenceCandidates = (candidates, references, pointer, entity, relationship) => {
|
|
2469
|
+
for (const [index, reference] of [...references ?? []].sort(compareReferences).entries()) candidates.push({
|
|
2470
|
+
entity,
|
|
2471
|
+
kind: "reference",
|
|
2472
|
+
path: reference.path,
|
|
2473
|
+
pointer: childPointer(pointer, String(index)),
|
|
2474
|
+
...relationship === void 0 ? {} : { relationship },
|
|
2475
|
+
...reference.symbol === void 0 ? {} : { symbol: reference.symbol }
|
|
2476
|
+
});
|
|
2477
|
+
};
|
|
2478
|
+
/** Adds sorted exact impact paths while retaining skipped glob indexes. */
|
|
2479
|
+
var addImpactCandidates = (candidates, affectedBy, pointer, entity) => {
|
|
2480
|
+
for (const [index, pattern] of [...affectedBy ?? []].sort(compareExactStrings).entries()) {
|
|
2481
|
+
if (pattern.includes("*")) continue;
|
|
2482
|
+
candidates.push({
|
|
2483
|
+
entity,
|
|
2484
|
+
kind: "impact",
|
|
2485
|
+
path: parseRepositoryPath(pattern),
|
|
2486
|
+
pointer: childPointer(pointer, String(index))
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
};
|
|
2490
|
+
/** Adds repository-backed declarations owned by one context or decision relationship. */
|
|
2491
|
+
var addRelationshipCandidates = (candidates, relationship, pointer, entity) => {
|
|
2492
|
+
addReferenceCandidates(candidates, relationship.bindings, childPointer(pointer, "bindings"), entity, pointer.startsWith("/context/") ? "context-binding" : "decision-binding");
|
|
2493
|
+
addImpactCandidates(candidates, relationship.affectedBy, childPointer(pointer, "affectedBy"), entity);
|
|
2494
|
+
};
|
|
2495
|
+
/** Adds related repository references from sorted unresolved requirements. */
|
|
2496
|
+
var addUnresolvedCandidates = (candidates, requirements, pointer, entity) => {
|
|
2497
|
+
for (const requirementId of Object.keys(requirements ?? {}).sort(compareExactStrings)) {
|
|
2498
|
+
const requirement = requirements?.[requirementId];
|
|
2499
|
+
if (requirement === void 0) continue;
|
|
2500
|
+
addReferenceCandidates(candidates, requirement.related, childPointer(childPointer(pointer, requirementId), "related"), entity);
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
/** Adds one capability's implementation, optional bindings, and exact impact paths. */
|
|
2504
|
+
var addCapabilityCandidates = (candidates, capability, capabilityKind, capabilityId, agentId, collectionPointer, additionalReferences = []) => {
|
|
2505
|
+
const capabilityPointer = childPointer(collectionPointer, capabilityId);
|
|
2506
|
+
const capabilityEntity = {
|
|
2507
|
+
agentId,
|
|
2508
|
+
capabilityId,
|
|
2509
|
+
capabilityKind
|
|
2510
|
+
};
|
|
2511
|
+
candidates.push({
|
|
2512
|
+
entity: capabilityEntity,
|
|
2513
|
+
kind: `${capabilityKind}-implementation`,
|
|
2514
|
+
relationship: `${capabilityKind}-implementation`,
|
|
2515
|
+
...capability.implementation.symbol === void 0 ? {} : { symbol: capability.implementation.symbol },
|
|
2516
|
+
path: capability.implementation.path,
|
|
2517
|
+
pointer: childPointer(capabilityPointer, "implementation")
|
|
2518
|
+
});
|
|
2519
|
+
for (const [property, reference] of [["registration", capability.registration], ...additionalReferences]) if (reference !== void 0) candidates.push({
|
|
2520
|
+
entity: capabilityEntity,
|
|
2521
|
+
kind: "reference",
|
|
2522
|
+
path: reference.path,
|
|
2523
|
+
pointer: childPointer(capabilityPointer, property),
|
|
2524
|
+
relationship: `${capabilityKind}-${property === "inputSchema" ? "input-schema" : property === "outputSchema" ? "output-schema" : "registration"}`,
|
|
2525
|
+
...reference.symbol === void 0 ? {} : { symbol: reference.symbol }
|
|
2526
|
+
});
|
|
2527
|
+
addImpactCandidates(candidates, capability.affectedBy, childPointer(capabilityPointer, "affectedBy"), capabilityEntity);
|
|
2528
|
+
};
|
|
2529
|
+
/** Adds all repository-backed declarations owned by one normalized agent. */
|
|
2530
|
+
var addAgentCandidates = (candidates, agentId, agent) => {
|
|
2531
|
+
const pointer = `/agents/${escapeJsonPointerSegment(agentId)}`;
|
|
2532
|
+
const entity = { agentId };
|
|
2533
|
+
const bindingsPointer = childPointer(pointer, "bindings");
|
|
2534
|
+
const bindings = agent.bindings;
|
|
2535
|
+
for (const property of [
|
|
2536
|
+
"runtimeAgent",
|
|
2537
|
+
"inputSchema",
|
|
2538
|
+
"outputSchema",
|
|
2539
|
+
"instructionLoader"
|
|
2540
|
+
]) {
|
|
2541
|
+
const reference = bindings?.[property];
|
|
2542
|
+
if (reference !== void 0) candidates.push({
|
|
2543
|
+
entity,
|
|
2544
|
+
kind: "reference",
|
|
2545
|
+
path: reference.path,
|
|
2546
|
+
pointer: childPointer(bindingsPointer, property),
|
|
2547
|
+
relationship: {
|
|
2548
|
+
runtimeAgent: "runtime-agent",
|
|
2549
|
+
inputSchema: "agent-input-schema",
|
|
2550
|
+
outputSchema: "agent-output-schema",
|
|
2551
|
+
instructionLoader: "instruction-loader"
|
|
2552
|
+
}[property],
|
|
2553
|
+
...reference.symbol === void 0 ? {} : { symbol: reference.symbol }
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
for (const variableId of Object.keys(bindings?.variableProviders ?? {}).sort(compareExactStrings)) {
|
|
2557
|
+
const reference = bindings?.variableProviders?.[variableId];
|
|
2558
|
+
if (reference !== void 0) candidates.push({
|
|
2559
|
+
entity: {
|
|
2560
|
+
agentId,
|
|
2561
|
+
variableId
|
|
2562
|
+
},
|
|
2563
|
+
relationship: "variable-provider",
|
|
2564
|
+
...reference.symbol === void 0 ? {} : { symbol: reference.symbol },
|
|
2565
|
+
kind: "reference",
|
|
2566
|
+
path: reference.path,
|
|
2567
|
+
pointer: childPointer(childPointer(bindingsPointer, "variableProviders"), variableId)
|
|
2568
|
+
});
|
|
2569
|
+
}
|
|
2570
|
+
addImpactCandidates(candidates, agent.affectedBy, childPointer(pointer, "affectedBy"), entity);
|
|
2571
|
+
const toolsPointer = childPointer(pointer, "tools");
|
|
2572
|
+
for (const capabilityId of Object.keys(agent.tools ?? {}).sort(compareExactStrings)) {
|
|
2573
|
+
const capability = agent.tools?.[capabilityId];
|
|
2574
|
+
if (capability !== void 0) addCapabilityCandidates(candidates, capability, "tool", capabilityId, agentId, toolsPointer, [["inputSchema", capability.inputSchema], ["outputSchema", capability.outputSchema]]);
|
|
2575
|
+
}
|
|
2576
|
+
const skillsPointer = childPointer(pointer, "skills");
|
|
2577
|
+
for (const capabilityId of Object.keys(agent.skills ?? {}).sort(compareExactStrings)) {
|
|
2578
|
+
const capability = agent.skills?.[capabilityId];
|
|
2579
|
+
if (capability !== void 0) addCapabilityCandidates(candidates, capability, "skill", capabilityId, agentId, skillsPointer);
|
|
2580
|
+
}
|
|
2581
|
+
addUnresolvedCandidates(candidates, agent.unresolved, childPointer(pointer, "unresolved"), entity);
|
|
2582
|
+
};
|
|
2583
|
+
/** Collects every repository-backed manifest declaration in deterministic pointer order. */
|
|
2584
|
+
var collectCandidates = (manifest) => {
|
|
2585
|
+
const candidates = [];
|
|
2586
|
+
for (const property of ["context", "decisions"]) {
|
|
2587
|
+
const relationships = manifest[property];
|
|
2588
|
+
for (const referencedPath of Object.keys(relationships ?? {}).sort(compareExactStrings)) {
|
|
2589
|
+
const relationship = relationships?.[referencedPath];
|
|
2590
|
+
if (relationship === void 0) continue;
|
|
2591
|
+
const pointer = childPointer(`/${property}`, referencedPath);
|
|
2592
|
+
const decisionId = property === "decisions" ? parseDecisionIdFromPath(parseRepositoryPath(referencedPath)) : null;
|
|
2593
|
+
addRelationshipCandidates(candidates, relationship, pointer, decisionId === null ? { contextPath: parseRepositoryPath(referencedPath) } : { decisionId });
|
|
2594
|
+
}
|
|
2595
|
+
}
|
|
2596
|
+
addUnresolvedCandidates(candidates, manifest.unresolved, "/unresolved", null);
|
|
2597
|
+
for (const agentId of Object.keys(manifest.agents ?? {}).sort(compareExactStrings)) {
|
|
2598
|
+
const agent = manifest.agents?.[agentId];
|
|
2599
|
+
if (agent !== void 0) addAgentCandidates(candidates, agentId, agent);
|
|
2600
|
+
}
|
|
2601
|
+
return candidates.sort((left, right) => compareExactStrings(left.pointer, right.pointer));
|
|
2602
|
+
};
|
|
2603
|
+
/** Tests whether discovery already owns a diagnostic for a path or one of its ancestors. */
|
|
2604
|
+
var isBlockedPath$1 = (path, blockedPaths) => {
|
|
2605
|
+
let candidate = path;
|
|
2606
|
+
while (candidate.length > 0) {
|
|
2607
|
+
if (blockedPaths.has(candidate)) return true;
|
|
2608
|
+
const separatorIndex = candidate.lastIndexOf("/");
|
|
2609
|
+
if (separatorIndex <= 0) return false;
|
|
2610
|
+
candidate = candidate.slice(0, separatorIndex);
|
|
2611
|
+
}
|
|
2612
|
+
return false;
|
|
2613
|
+
};
|
|
2614
|
+
/** Creates normalized diagnostic details for one failed exact lookup. */
|
|
2615
|
+
var createCandidateDetails = (candidate, entry) => {
|
|
2616
|
+
const details = { [candidate.kind === "impact" ? "impactPath" : "referencedPath"]: candidate.path };
|
|
2617
|
+
if (entry !== null) details["actualType"] = entry.type;
|
|
2618
|
+
if (candidate.kind === "tool-implementation" || candidate.kind === "skill-implementation") details["reason"] = entry === null ? "missing" : entry.type === "symlink" ? "symlink" : "not-file";
|
|
2619
|
+
return details;
|
|
2620
|
+
};
|
|
2621
|
+
/** Selects the most-specific diagnostic code for one failed exact lookup. */
|
|
2622
|
+
var getCandidateCode = (candidate, entry) => {
|
|
2623
|
+
if (entry?.type === "file") return null;
|
|
2624
|
+
if (candidate.kind === "tool-implementation") return "MOLDEA_TOOL_IMPLEMENTATION_MISSING";
|
|
2625
|
+
if (candidate.kind === "skill-implementation") return "MOLDEA_SKILL_IMPLEMENTATION_MISSING";
|
|
2626
|
+
if (candidate.kind === "impact") return entry === null ? "MOLDEA_IMPACT_PATH_MISSING" : "MOLDEA_IMPACT_PATH_NOT_FILE";
|
|
2627
|
+
if (entry === null) return "MOLDEA_REFERENCE_MISSING";
|
|
2628
|
+
return entry.type === "symlink" ? "MOLDEA_REFERENCE_SYMLINK" : "MOLDEA_REFERENCE_NOT_FILE";
|
|
2629
|
+
};
|
|
2630
|
+
/** Adds the one most-specific diagnostic for a resolved repository candidate. */
|
|
2631
|
+
var validateCandidate = (candidate, entry, manifestPath, collector) => {
|
|
2632
|
+
const code = getCandidateCode(candidate, entry);
|
|
2633
|
+
if (code === null) return;
|
|
2634
|
+
collector.add({
|
|
2635
|
+
code,
|
|
2636
|
+
details: createCandidateDetails(candidate, entry),
|
|
2637
|
+
entity: candidate.entity,
|
|
2638
|
+
path: manifestPath,
|
|
2639
|
+
pointer: candidate.pointer
|
|
2640
|
+
});
|
|
2641
|
+
};
|
|
2642
|
+
/**
|
|
2643
|
+
* Validates every manifest repository reference, binding, and exact impact path.
|
|
2644
|
+
* @param repository The budget-aware reader for the active inspection session.
|
|
2645
|
+
* @param manifestPath The canonical manifest path used for diagnostics.
|
|
2646
|
+
* @param manifest The normalized valid manifest contract to inspect.
|
|
2647
|
+
* @param discovery Canonical discovery state used to suppress dependent path diagnostics.
|
|
2648
|
+
* @param limits The resource limits applied to emitted diagnostics.
|
|
2649
|
+
* @param signal Optional cancellation forwarded to exact repository lookups.
|
|
2650
|
+
* @param coverage Operation-owned binding coverage populated only for regular files.
|
|
2651
|
+
* @returns Deeply immutable deterministic repository-reference diagnostics.
|
|
2652
|
+
* @throws
|
|
2653
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
2654
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
2655
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
2656
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2657
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2658
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
2659
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
2660
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2661
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
2662
|
+
* - ABORTED: The Core operation was aborted.
|
|
2663
|
+
* - ABORTED: The repository operation was aborted.
|
|
2664
|
+
*/
|
|
2665
|
+
var validateRepositoryReferences = async (repository, manifestPath, manifest, discovery, limits, signal, coverage = []) => {
|
|
2666
|
+
const collector = createCoreDiagnosticCollector(limits, "validate-project");
|
|
2667
|
+
const blockedPaths = new Set(discovery.diagnostics.flatMap((diagnostic) => diagnostic.path === null ? [] : [diagnostic.path]));
|
|
2668
|
+
const inventories = /* @__PURE__ */ new Map();
|
|
2669
|
+
const entryCache = /* @__PURE__ */ new Map();
|
|
2670
|
+
const operationOptions = signal === void 0 ? void 0 : { signal };
|
|
2671
|
+
for (const candidate of collectCandidates(manifest)) {
|
|
2672
|
+
if (isBlockedPath$1(candidate.path, blockedPaths)) continue;
|
|
2673
|
+
let entryPromise = entryCache.get(candidate.path);
|
|
2674
|
+
if (entryPromise === void 0) {
|
|
2675
|
+
entryPromise = repository.getEntry(candidate.path, operationOptions);
|
|
2676
|
+
entryCache.set(candidate.path, entryPromise);
|
|
2677
|
+
}
|
|
2678
|
+
const entry = await entryPromise;
|
|
2679
|
+
validateCandidate(candidate, entry, manifestPath, collector);
|
|
2680
|
+
if (entry?.type !== "file" || candidate.relationship === void 0) continue;
|
|
2681
|
+
if (coverage.length >= limits.maxCoverage) throw new CoreOperationException({
|
|
2682
|
+
code: "RESOURCE_LIMIT_EXCEEDED",
|
|
2683
|
+
operation: "validate-project",
|
|
2684
|
+
limit: "maxCoverage",
|
|
2685
|
+
limitMaximum: limits.maxCoverage,
|
|
2686
|
+
observedUsage: coverage.length + 1,
|
|
2687
|
+
nextAction: "reduce-input-or-increase-limit"
|
|
2688
|
+
});
|
|
2689
|
+
const base = {
|
|
2690
|
+
entity: candidate.entity ?? {},
|
|
2691
|
+
relationship: candidate.relationship,
|
|
2692
|
+
path: candidate.path,
|
|
2693
|
+
symbol: candidate.symbol ?? null,
|
|
2694
|
+
fileCheck: "passed"
|
|
2695
|
+
};
|
|
2696
|
+
let record;
|
|
2697
|
+
if (candidate.symbol === void 0) record = {
|
|
2698
|
+
...base,
|
|
2699
|
+
exportCheck: "not-checked",
|
|
2700
|
+
exportNotCheckedReason: "no-symbol"
|
|
2701
|
+
};
|
|
2702
|
+
else {
|
|
2703
|
+
let inventory = inventories.get(candidate.path);
|
|
2704
|
+
if (inventory === void 0) {
|
|
2705
|
+
inventory = await readExportInventory(repository, candidate.path);
|
|
2706
|
+
inventories.set(candidate.path, inventory);
|
|
2707
|
+
}
|
|
2708
|
+
if (inventory.kind === "not-checked") record = {
|
|
2709
|
+
...base,
|
|
2710
|
+
exportCheck: "not-checked",
|
|
2711
|
+
exportNotCheckedReason: inventory.reason
|
|
2712
|
+
};
|
|
2713
|
+
else if (!inventory.complete) record = {
|
|
2714
|
+
...base,
|
|
2715
|
+
exportCheck: "not-checked",
|
|
2716
|
+
exportNotCheckedReason: "unresolved-exports"
|
|
2717
|
+
};
|
|
2718
|
+
else if (inventory.names.has(candidate.symbol)) record = {
|
|
2719
|
+
...base,
|
|
2720
|
+
exportCheck: "passed"
|
|
2721
|
+
};
|
|
2722
|
+
else {
|
|
2723
|
+
record = {
|
|
2724
|
+
...base,
|
|
2725
|
+
exportCheck: "failed"
|
|
2726
|
+
};
|
|
2727
|
+
collector.add({
|
|
2728
|
+
code: "MOLDEA_REFERENCE_EXPORT_MISSING",
|
|
2729
|
+
entity: candidate.entity,
|
|
2730
|
+
path: manifestPath,
|
|
2731
|
+
pointer: candidate.pointer,
|
|
2732
|
+
details: {
|
|
2733
|
+
referencedPath: candidate.path,
|
|
2734
|
+
symbol: candidate.symbol,
|
|
2735
|
+
relationship: candidate.relationship
|
|
2736
|
+
}
|
|
2737
|
+
});
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
2740
|
+
reserveRetainedBytesIfTracked(repository, 512 + JSON.stringify(record).length * 2);
|
|
2741
|
+
coverage.push(record);
|
|
2742
|
+
}
|
|
2743
|
+
return collector.finalize();
|
|
2744
|
+
};
|
|
2745
|
+
//#endregion
|
|
2746
|
+
//#region src/runtime-guidance/index.ts
|
|
2747
|
+
var addDiagnostics$1 = (collector, diagnostics) => {
|
|
2748
|
+
for (const diagnostic of diagnostics) collector.merge(diagnostic);
|
|
2749
|
+
};
|
|
2750
|
+
var isBlockedPath = (path, blockedPaths) => {
|
|
2751
|
+
let candidate = path;
|
|
2752
|
+
while (candidate.length > 0) {
|
|
2753
|
+
if (blockedPaths.has(candidate)) return true;
|
|
2754
|
+
const separatorIndex = candidate.lastIndexOf("/");
|
|
2755
|
+
if (separatorIndex <= 0) return false;
|
|
2756
|
+
candidate = candidate.slice(0, separatorIndex);
|
|
2757
|
+
}
|
|
2758
|
+
return false;
|
|
2759
|
+
};
|
|
2760
|
+
/**
|
|
2761
|
+
* Reads all discovered runtime guidance and validates every manifest guidance relationship.
|
|
2762
|
+
* @param repository The coherent source-neutral repository reader.
|
|
2763
|
+
* @param manifestPath The canonical manifest path used for relationship diagnostics.
|
|
2764
|
+
* @param manifest The normalized manifest declaring optional agent guidance paths, when valid.
|
|
2765
|
+
* @param discovery The canonical inventory and path-owned structural diagnostics.
|
|
2766
|
+
* @param options The immutable Core configuration snapshot.
|
|
2767
|
+
* @param signal Optional cancellation forwarded to every repository read.
|
|
2768
|
+
* @returns Deeply immutable valid guidance assets and deterministic diagnostics.
|
|
2769
|
+
* @throws
|
|
2770
|
+
* - ENTRY_NOT_FOUND: A discovered guidance file disappeared from the reader snapshot.
|
|
2771
|
+
* - ENTRY_NOT_FILE: A discovered guidance entry is no longer a regular file.
|
|
2772
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2773
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2774
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during guidance reads.
|
|
2775
|
+
* - INVALID_SOURCE_DATA: The repository reader returned invalid contract data.
|
|
2776
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core or repository resource limit was exceeded.
|
|
2777
|
+
* - ABORTED: Guidance inspection or a repository operation was aborted.
|
|
2778
|
+
*/
|
|
2779
|
+
var readRuntimeGuidance = async (repository, manifestPath, manifest, discovery, options, signal) => {
|
|
2780
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
2781
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
2782
|
+
const runtimes = [];
|
|
2783
|
+
const discoveredPaths = new Set(discovery.inventory.runtimeGuidance);
|
|
2784
|
+
const blockedPaths = new Set(discovery.diagnostics.flatMap((diagnostic) => diagnostic.path === null ? [] : [diagnostic.path]));
|
|
2785
|
+
for (const path of [...discoveredPaths].sort(compareExactStrings)) {
|
|
2786
|
+
const result = await readRepositoryTextAsset(repository, path, options.limits, signal);
|
|
2787
|
+
addDiagnostics$1(collector, result.diagnostics);
|
|
2788
|
+
if (result.asset === null) continue;
|
|
2789
|
+
if (!hasNonWhitespace(result.asset.content)) {
|
|
2790
|
+
collector.add({
|
|
2791
|
+
code: "MOLDEA_RUNTIME_GUIDANCE_EMPTY",
|
|
2792
|
+
path
|
|
2793
|
+
});
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
runtimes.push({ asset: result.asset });
|
|
2797
|
+
}
|
|
2798
|
+
for (const agentId of Object.keys(manifest?.agents ?? {}).sort(compareExactStrings)) {
|
|
2799
|
+
const guidance = manifest?.agents?.[agentId]?.runtime.guidance;
|
|
2800
|
+
if (guidance === void 0 || discoveredPaths.has(guidance) || isBlockedPath(guidance, blockedPaths)) continue;
|
|
2801
|
+
collector.add({
|
|
2802
|
+
code: "MOLDEA_RUNTIME_GUIDANCE_MISSING",
|
|
2803
|
+
details: { referencedPath: guidance },
|
|
2804
|
+
entity: { agentId },
|
|
2805
|
+
path: manifestPath,
|
|
2806
|
+
pointer: `/agents/${escapeJsonPointerSegment(agentId)}/runtime/guidance`
|
|
2807
|
+
});
|
|
2808
|
+
}
|
|
2809
|
+
const diagnostics = collector.finalize();
|
|
2810
|
+
return freezeRecursively({
|
|
2811
|
+
diagnostics,
|
|
2812
|
+
runtimes,
|
|
2813
|
+
valid: diagnostics.length === 0
|
|
2814
|
+
});
|
|
2815
|
+
};
|
|
2816
|
+
//#endregion
|
|
2817
|
+
//#region src/universal-project-inspection/index.ts
|
|
2818
|
+
var MOLDEA_ROOT = parseRepositoryPath("/moldea");
|
|
2819
|
+
var MANIFEST_PATH = parseRepositoryPath("/moldea/moldea.yaml");
|
|
2820
|
+
var PROJECT_PATH = parseRepositoryPath("/moldea/project.md");
|
|
2821
|
+
var addDiagnostics = (collector, diagnostics) => {
|
|
2822
|
+
for (const diagnostic of diagnostics) collector.merge(diagnostic);
|
|
2823
|
+
};
|
|
2824
|
+
/**
|
|
2825
|
+
* Executes every universal repository-format phase with syntax-only export checks.
|
|
2826
|
+
* @param context The shared inspection session and optional cancellation signal.
|
|
2827
|
+
* @param options The immutable Core configuration snapshot.
|
|
2828
|
+
* @returns The frozen provisional index only when no universal diagnostic remains.
|
|
2829
|
+
* @throws
|
|
2830
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
2831
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
2832
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
2833
|
+
* - ENTRY_NOT_DIRECTORY: The requested repository entry is not a directory.
|
|
2834
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2835
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2836
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
2837
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
2838
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2839
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
2840
|
+
* - ABORTED: The Core operation was aborted.
|
|
2841
|
+
* - ABORTED: The repository operation was aborted.
|
|
2842
|
+
*/
|
|
2843
|
+
var inspectUniversalProject = async (context, options) => {
|
|
2844
|
+
options = createCoreOperationOptionsSnapshot(options);
|
|
2845
|
+
const { session, signal } = context;
|
|
2846
|
+
const coverage = [];
|
|
2847
|
+
const collector = createCoreDiagnosticCollector(options.limits, "validate-project");
|
|
2848
|
+
const operationOptions = signal === void 0 ? void 0 : { signal };
|
|
2849
|
+
const moldeaRoot = await session.reader.getEntry(MOLDEA_ROOT, operationOptions);
|
|
2850
|
+
let manifestEntry = null;
|
|
2851
|
+
let projectEntry = null;
|
|
2852
|
+
let indexedManifest = null;
|
|
2853
|
+
let formatVersion = null;
|
|
2854
|
+
if (moldeaRoot?.type === "directory") {
|
|
2855
|
+
manifestEntry = await session.reader.getEntry(MANIFEST_PATH, operationOptions);
|
|
2856
|
+
if (manifestEntry?.type === "file") {
|
|
2857
|
+
const content = await session.reader.readCompleteFile(MANIFEST_PATH, operationOptions);
|
|
2858
|
+
const parsedManifest = await inspectManifestDocument({
|
|
2859
|
+
content,
|
|
2860
|
+
path: MANIFEST_PATH
|
|
2861
|
+
}, options, "validate-project");
|
|
2862
|
+
formatVersion = parsedManifest.formatVersion;
|
|
2863
|
+
addDiagnostics(collector, parsedManifest.diagnostics);
|
|
2864
|
+
if (parsedManifest.asset !== null && parsedManifest.manifest !== null) indexedManifest = {
|
|
2865
|
+
asset: parsedManifest.asset,
|
|
2866
|
+
value: parsedManifest.manifest
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
projectEntry = await session.reader.getEntry(PROJECT_PATH, operationOptions);
|
|
2870
|
+
}
|
|
2871
|
+
const projectFile = await readProjectFile(session.reader, projectEntry?.type === "file" ? PROJECT_PATH : null, options, signal);
|
|
2872
|
+
addDiagnostics(collector, projectFile.diagnostics);
|
|
2873
|
+
const discovery = await discoverCanonicalAssets(session.reader, options.limits, signal, {
|
|
2874
|
+
manifest: manifestEntry,
|
|
2875
|
+
moldeaRoot,
|
|
2876
|
+
project: projectEntry
|
|
2877
|
+
});
|
|
2878
|
+
addDiagnostics(collector, discovery.diagnostics);
|
|
2879
|
+
session.throwIfAborted();
|
|
2880
|
+
const projectAssets = await readProjectAssets(session.reader, indexedManifest?.value ?? null, discovery, options, signal, projectFile);
|
|
2881
|
+
addDiagnostics(collector, projectAssets.diagnostics);
|
|
2882
|
+
const runtimeGuidance = await readRuntimeGuidance(session.reader, discovery.inventory.manifest ?? MANIFEST_PATH, indexedManifest?.value ?? null, discovery, options, signal);
|
|
2883
|
+
addDiagnostics(collector, runtimeGuidance.diagnostics);
|
|
2884
|
+
const decisionGraph = await readDecisionGraph(session.reader, discovery.inventory.decisions, options, signal);
|
|
2885
|
+
addDiagnostics(collector, decisionGraph.diagnostics);
|
|
2886
|
+
let agents = [];
|
|
2887
|
+
let agentMirrors = [];
|
|
2888
|
+
if (indexedManifest !== null) {
|
|
2889
|
+
const manifestPath = indexedManifest.asset.path;
|
|
2890
|
+
const agentAssets = await inspectAgentAssets(session.reader, indexedManifest.value, discovery, options, signal);
|
|
2891
|
+
agents = agentAssets.agents;
|
|
2892
|
+
addDiagnostics(collector, agentAssets.diagnostics);
|
|
2893
|
+
addDiagnostics(collector, validateManifestRelationships(manifestPath, indexedManifest.value, discovery, decisionGraph.decisions, options.limits));
|
|
2894
|
+
addDiagnostics(collector, await validateRepositoryReferences(session.reader, manifestPath, indexedManifest.value, discovery, options.limits, signal, coverage));
|
|
2895
|
+
const mirrorInspection = await inspectMirrors(session.reader, manifestPath, agents, options, signal);
|
|
2896
|
+
agentMirrors = mirrorInspection.agentMirrors;
|
|
2897
|
+
addDiagnostics(collector, mirrorInspection.diagnostics);
|
|
2898
|
+
}
|
|
2899
|
+
session.throwIfAborted();
|
|
2900
|
+
addDiagnostics(collector, await inspectCodingAgentIntegration(session.reader, options.limits, signal));
|
|
2901
|
+
const patterns = indexedManifest === null ? {
|
|
2902
|
+
completed: false,
|
|
2903
|
+
diagnostics: []
|
|
2904
|
+
} : await inspectImpactPatterns(session.reader, indexedManifest.value, options.limits, signal);
|
|
2905
|
+
session.throwIfAborted();
|
|
2906
|
+
addDiagnostics(collector, patterns.diagnostics);
|
|
2907
|
+
const diagnostics = applyWarningAcceptances(collector.finalize(), indexedManifest?.value.acceptedWarnings ?? [], /* @__PURE__ */ new Set([...[
|
|
2908
|
+
"MOLDEA_INTEGRATION_BLOCK_MISSING",
|
|
2909
|
+
"MOLDEA_INTEGRATION_SKILL_MISSING",
|
|
2910
|
+
"MOLDEA_INTEGRATION_BLOCK_STALE"
|
|
2911
|
+
], ...patterns.completed ? ["MOLDEA_IMPACT_PATTERN_UNMATCHED"] : []]), options.limits);
|
|
2912
|
+
const project = !diagnostics.some((diagnostic) => diagnostic.severity === "error") && indexedManifest !== null && projectAssets.project !== null ? createProjectIndex({
|
|
2913
|
+
agentMirrors,
|
|
2914
|
+
agents,
|
|
2915
|
+
context: projectAssets.context,
|
|
2916
|
+
decisions: decisionGraph.decisions,
|
|
2917
|
+
manifest: indexedManifest,
|
|
2918
|
+
project: projectAssets.project,
|
|
2919
|
+
runtimes: runtimeGuidance.runtimes
|
|
2920
|
+
}) : null;
|
|
2921
|
+
return freezeRecursively({
|
|
2922
|
+
diagnostics,
|
|
2923
|
+
coverage,
|
|
2924
|
+
formatVersion,
|
|
2925
|
+
project
|
|
2926
|
+
});
|
|
2927
|
+
};
|
|
2928
|
+
//#endregion
|
|
2929
|
+
//#region src/project-validation/index.ts
|
|
2930
|
+
var isRecord = (candidate) => {
|
|
2931
|
+
return typeof candidate === "object" && candidate !== null && !Array.isArray(candidate);
|
|
2932
|
+
};
|
|
2933
|
+
var invalidArgument$1 = () => {
|
|
2934
|
+
throw new CoreOperationException({
|
|
2935
|
+
code: "INVALID_ARGUMENT",
|
|
2936
|
+
operation: "validate-project"
|
|
2937
|
+
});
|
|
2938
|
+
};
|
|
2939
|
+
var isRepositoryReader = (candidate) => {
|
|
2940
|
+
return isRecord(candidate) && isRecord(candidate["snapshot"]) && typeof candidate["snapshot"]["id"] === "string" && typeof candidate["snapshot"]["sourceKind"] === "string" && typeof candidate["compare"] === "function" && typeof candidate["getEntry"] === "function" && typeof candidate["listEntriesPage"] === "function" && typeof candidate["readFilePage"] === "function";
|
|
2941
|
+
};
|
|
2942
|
+
var isAbortSignal = (candidate) => {
|
|
2943
|
+
return isRecord(candidate) && typeof candidate["aborted"] === "boolean" && typeof candidate["addEventListener"] === "function" && typeof candidate["removeEventListener"] === "function";
|
|
2944
|
+
};
|
|
2945
|
+
var validateInput = (candidate) => {
|
|
2946
|
+
try {
|
|
2947
|
+
if (!isRecord(candidate)) return invalidArgument$1();
|
|
2948
|
+
const repository = candidate["repository"];
|
|
2949
|
+
const signal = candidate["signal"];
|
|
2950
|
+
if (Reflect.ownKeys(candidate).some((key) => key !== "repository" && key !== "signal") || !isRepositoryReader(repository) || signal !== void 0 && !isAbortSignal(signal)) return invalidArgument$1();
|
|
2951
|
+
return {
|
|
2952
|
+
repository,
|
|
2953
|
+
...signal === void 0 ? {} : { signal }
|
|
2954
|
+
};
|
|
2955
|
+
} catch (error) {
|
|
2956
|
+
if (error instanceof CoreOperationException) throw error;
|
|
2957
|
+
return invalidArgument$1();
|
|
2958
|
+
}
|
|
2959
|
+
};
|
|
2960
|
+
/**
|
|
2961
|
+
* Inspects one coherent repository snapshot through universal file and export validation.
|
|
2962
|
+
* @param input The untrusted source-neutral reader and optional cancellation signal.
|
|
2963
|
+
* @param options The immutable Core configuration snapshot.
|
|
2964
|
+
* @returns A promise resolving to the frozen all-or-nothing project inspection result.
|
|
2965
|
+
* @throws
|
|
2966
|
+
* - INVALID_ARGUMENT: The Core operation received an invalid argument.
|
|
2967
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
2968
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
2969
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
2970
|
+
* - ENTRY_NOT_DIRECTORY: The requested repository entry is not a directory.
|
|
2971
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
2972
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
2973
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
2974
|
+
* - PROVIDER_INCOMPLETE: The repository provider cannot expose a complete result.
|
|
2975
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
2976
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
2977
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
2978
|
+
* - ABORTED: The repository operation was aborted.
|
|
2979
|
+
* - ABORTED: The Core operation was aborted.
|
|
2980
|
+
*/
|
|
2981
|
+
var validateProjectState = async (input, options) => {
|
|
2982
|
+
const validatedInput = validateInput(input);
|
|
2983
|
+
const operationOptions = createCoreOperationOptionsSnapshot(options);
|
|
2984
|
+
const session = createRepositoryInspectionSession(validatedInput.repository, operationOptions.limits, validatedInput.signal);
|
|
2985
|
+
const universal = await inspectUniversalProject({
|
|
2986
|
+
session,
|
|
2987
|
+
...validatedInput.signal === void 0 ? {} : { signal: validatedInput.signal }
|
|
2988
|
+
}, operationOptions);
|
|
2989
|
+
session.throwIfAborted();
|
|
2990
|
+
const counts = countDiagnosticsBySeverity(universal.diagnostics);
|
|
2991
|
+
return freezeRecursively({
|
|
2992
|
+
project: universal.project,
|
|
2993
|
+
resourceUsage: session.getResourceUsage(),
|
|
2994
|
+
result: {
|
|
2995
|
+
diagnostics: universal.diagnostics,
|
|
2996
|
+
...counts,
|
|
2997
|
+
coverage: universal.coverage,
|
|
2998
|
+
coverageCounts: countBindingCoverage(universal.coverage),
|
|
2999
|
+
formatVersion: universal.formatVersion,
|
|
3000
|
+
source: validatedInput.repository.snapshot,
|
|
3001
|
+
summary: universal.project === null ? null : createProjectSummary(universal.project),
|
|
3002
|
+
valid: counts.errorCount === 0
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
};
|
|
3006
|
+
/**
|
|
3007
|
+
* Validates one project without returning canonical document bodies.
|
|
3008
|
+
* @param input The source-neutral reader and optional cancellation signal.
|
|
3009
|
+
* @param options The immutable operation limits.
|
|
3010
|
+
* @returns A promise resolving to the complete validation result.
|
|
3011
|
+
* @throws
|
|
3012
|
+
* - INVALID_ARGUMENT: The Core operation received an invalid argument.
|
|
3013
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
3014
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
3015
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
3016
|
+
* - ENTRY_NOT_DIRECTORY: The requested repository entry is not a directory.
|
|
3017
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
3018
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
3019
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
3020
|
+
* - PROVIDER_INCOMPLETE: The repository provider cannot expose a complete result.
|
|
3021
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
3022
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
3023
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
3024
|
+
* - ABORTED: The repository operation was aborted.
|
|
3025
|
+
* - ABORTED: The Core operation was aborted.
|
|
3026
|
+
*/
|
|
3027
|
+
var validateProject = async (input, options) => (await validateProjectState(input, options)).result;
|
|
3028
|
+
//#endregion
|
|
3029
|
+
//#region src/project-inspection/index.ts
|
|
3030
|
+
var CURSOR_PREFIX = "core7";
|
|
3031
|
+
var PREPARED_FIXED_BYTES = 512;
|
|
3032
|
+
var PREPARED_ITEM_FIXED_BYTES = 64;
|
|
3033
|
+
var PREPARED_TEXT_BYTE_MULTIPLIER = 2;
|
|
3034
|
+
var PREPARED_VIEW_REFERENCE_BYTES = 8;
|
|
3035
|
+
var encoder = new TextEncoder();
|
|
3036
|
+
var invalidArgument = () => {
|
|
3037
|
+
throw new CoreOperationException({
|
|
3038
|
+
code: "INVALID_ARGUMENT",
|
|
3039
|
+
operation: "create-project-inspection"
|
|
3040
|
+
});
|
|
3041
|
+
};
|
|
3042
|
+
var resourceLimitExceeded = (limitMaximum, observedUsage) => {
|
|
3043
|
+
throw new CoreOperationException({
|
|
3044
|
+
code: "RESOURCE_LIMIT_EXCEEDED",
|
|
3045
|
+
limit: "maxRetainedBytes",
|
|
3046
|
+
limitMaximum,
|
|
3047
|
+
nextAction: "reduce-input-or-increase-limit",
|
|
3048
|
+
observedUsage,
|
|
3049
|
+
operation: "create-project-inspection"
|
|
3050
|
+
});
|
|
3051
|
+
};
|
|
3052
|
+
/** Serializes content-free inspection state with recursively sorted object keys. */
|
|
3053
|
+
var serializeDeterministically = (candidate) => {
|
|
3054
|
+
if (candidate === null || typeof candidate !== "object") {
|
|
3055
|
+
const serialized = JSON.stringify(candidate);
|
|
3056
|
+
return serialized === void 0 ? "null" : serialized;
|
|
3057
|
+
}
|
|
3058
|
+
if (Array.isArray(candidate)) return `[${candidate.map((item) => serializeDeterministically(item)).join(",")}]`;
|
|
3059
|
+
return `{${Object.entries(candidate).filter(([, value]) => value !== void 0).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, value]) => `${JSON.stringify(key)}:${serializeDeterministically(value)}`).join(",")}}`;
|
|
3060
|
+
};
|
|
3061
|
+
var createIdentity = (parts) => createRepositoryIdentity((function* () {
|
|
3062
|
+
for (const part of parts) yield encoder.encode(part);
|
|
3063
|
+
})());
|
|
3064
|
+
var createItemBaseKey = (item) => {
|
|
3065
|
+
if (item.kind === "agent") return [
|
|
3066
|
+
"3",
|
|
3067
|
+
item.agent.agentId,
|
|
3068
|
+
item.agent.runtimeId
|
|
3069
|
+
].join("\0");
|
|
3070
|
+
if (item.kind === "metadata") return [
|
|
3071
|
+
"0",
|
|
3072
|
+
item.metadata.path,
|
|
3073
|
+
item.metadata.kind,
|
|
3074
|
+
item.metadata.agentId ?? "",
|
|
3075
|
+
item.metadata.decisionId ?? "",
|
|
3076
|
+
item.metadata.digest
|
|
3077
|
+
].join(":");
|
|
3078
|
+
if (item.kind === "diagnostic") return [
|
|
3079
|
+
"1",
|
|
3080
|
+
item.diagnostic.source,
|
|
3081
|
+
item.diagnostic.code,
|
|
3082
|
+
item.diagnostic.path ?? "",
|
|
3083
|
+
createIdentity([serializeDeterministically(item.diagnostic)])
|
|
3084
|
+
].join(":");
|
|
3085
|
+
return [
|
|
3086
|
+
"2",
|
|
3087
|
+
item.coverage.path,
|
|
3088
|
+
item.coverage.relationship,
|
|
3089
|
+
item.coverage.entity.agentId ?? "",
|
|
3090
|
+
createIdentity([serializeDeterministically(item.coverage)])
|
|
3091
|
+
].join(":");
|
|
3092
|
+
};
|
|
3093
|
+
/** Assigns stable unique keys once without relying on collection offsets. */
|
|
3094
|
+
var createKeyedItems = (items) => {
|
|
3095
|
+
const sorted = Array.from(items, (item) => ({
|
|
3096
|
+
item,
|
|
3097
|
+
key: createItemBaseKey(item)
|
|
3098
|
+
})).sort((left, right) => left.key < right.key ? -1 : left.key > right.key ? 1 : 0);
|
|
3099
|
+
let previousBaseKey = null;
|
|
3100
|
+
let occurrence = 0;
|
|
3101
|
+
for (const item of sorted) {
|
|
3102
|
+
const baseKey = item.key;
|
|
3103
|
+
occurrence = baseKey === previousBaseKey ? occurrence + 1 : 0;
|
|
3104
|
+
previousBaseKey = baseKey;
|
|
3105
|
+
item.key = `${baseKey}:${String(occurrence).padStart(10, "0")}`;
|
|
3106
|
+
}
|
|
3107
|
+
return sorted;
|
|
3108
|
+
};
|
|
3109
|
+
var createCursorChecksum = (inspectionDigest, lastKey, view) => createIdentity([
|
|
3110
|
+
CURSOR_PREFIX,
|
|
3111
|
+
inspectionDigest,
|
|
3112
|
+
lastKey,
|
|
3113
|
+
view
|
|
3114
|
+
]);
|
|
3115
|
+
var encodeCursor = (inspectionDigest, view, lastKey) => [
|
|
3116
|
+
CURSOR_PREFIX,
|
|
3117
|
+
view,
|
|
3118
|
+
encodeURIComponent(lastKey),
|
|
3119
|
+
inspectionDigest,
|
|
3120
|
+
createCursorChecksum(inspectionDigest, lastKey, view)
|
|
3121
|
+
].join(":");
|
|
3122
|
+
var decodeCursor = (cursor, inspectionDigest, view) => {
|
|
3123
|
+
if (cursor === void 0) return null;
|
|
3124
|
+
const match = /^core7:(all|diagnostics|coverage|metadata):([^:]+):(sha256:[0-9a-f]{64}):(sha256:[0-9a-f]{64})$/u.exec(cursor);
|
|
3125
|
+
if (match === null || match[1] !== view || match[3] !== inspectionDigest) return invalidArgument();
|
|
3126
|
+
let lastKey;
|
|
3127
|
+
try {
|
|
3128
|
+
lastKey = decodeURIComponent(match[2]);
|
|
3129
|
+
} catch {
|
|
3130
|
+
return invalidArgument();
|
|
3131
|
+
}
|
|
3132
|
+
if (lastKey.length === 0 || match[4] !== createCursorChecksum(inspectionDigest, lastKey, view)) return invalidArgument();
|
|
3133
|
+
return lastKey;
|
|
3134
|
+
};
|
|
3135
|
+
var createViews = (items) => freezeRecursively({
|
|
3136
|
+
all: items,
|
|
3137
|
+
diagnostics: items.filter(({ item }) => item.kind === "diagnostic"),
|
|
3138
|
+
coverage: items.filter(({ item }) => item.kind === "coverage"),
|
|
3139
|
+
metadata: items.filter(({ item }) => item.kind === "metadata" || item.kind === "agent")
|
|
3140
|
+
});
|
|
3141
|
+
var addRetainedBytes = (current, additional, retainedBeforePreparation, maximum) => {
|
|
3142
|
+
const projectedUsage = retainedBeforePreparation + current + additional;
|
|
3143
|
+
if (!Number.isSafeInteger(additional) || additional < 0 || !Number.isSafeInteger(projectedUsage) || projectedUsage > maximum) return resourceLimitExceeded(maximum, Number.isSafeInteger(projectedUsage) ? projectedUsage : Number.MAX_SAFE_INTEGER);
|
|
3144
|
+
return current + additional;
|
|
3145
|
+
};
|
|
3146
|
+
/** Iterates every content-free inspection item without retaining preparation state. */
|
|
3147
|
+
var iterateProjectInspectionItems = function* (state) {
|
|
3148
|
+
if (state.project !== null) for (const metadata of iterateProjectMetadata(state.project)) yield {
|
|
3149
|
+
kind: "metadata",
|
|
3150
|
+
metadata
|
|
3151
|
+
};
|
|
3152
|
+
for (const diagnostic of state.result.diagnostics) yield {
|
|
3153
|
+
diagnostic,
|
|
3154
|
+
kind: "diagnostic"
|
|
3155
|
+
};
|
|
3156
|
+
for (const coverage of state.result.coverage) yield {
|
|
3157
|
+
coverage,
|
|
3158
|
+
kind: "coverage"
|
|
3159
|
+
};
|
|
3160
|
+
if (state.project !== null) for (const agent of iterateProjectAgentAssignments(state.project)) yield {
|
|
3161
|
+
agent,
|
|
3162
|
+
kind: "agent"
|
|
3163
|
+
};
|
|
3164
|
+
};
|
|
3165
|
+
/** Preflights the complete logical prepared-state cost before retaining its collections. */
|
|
3166
|
+
var estimatePreparedProjectInspection = (state, baseCounts, maximum) => {
|
|
3167
|
+
const retainedBeforePreparation = state.resourceUsage.retainedBytes;
|
|
3168
|
+
let bytes = addRetainedBytes(0, PREPARED_FIXED_BYTES, retainedBeforePreparation, maximum);
|
|
3169
|
+
let metadataCount = 0;
|
|
3170
|
+
for (const item of iterateProjectInspectionItems(state)) {
|
|
3171
|
+
const key = `${createItemBaseKey(item)}:0000000000`;
|
|
3172
|
+
const encodedBytes = encoder.encode(key).byteLength + encoder.encode(serializeDeterministically(item)).byteLength;
|
|
3173
|
+
bytes = addRetainedBytes(bytes, PREPARED_ITEM_FIXED_BYTES + encodedBytes * PREPARED_TEXT_BYTE_MULTIPLIER + PREPARED_VIEW_REFERENCE_BYTES, retainedBeforePreparation, maximum);
|
|
3174
|
+
if (item.kind === "metadata") metadataCount += 1;
|
|
3175
|
+
}
|
|
3176
|
+
const counts = {
|
|
3177
|
+
agents: baseCounts.agents,
|
|
3178
|
+
context: baseCounts.context,
|
|
3179
|
+
decisions: baseCounts.decisions,
|
|
3180
|
+
diagnostics: baseCounts.diagnostics,
|
|
3181
|
+
errors: baseCounts.errors,
|
|
3182
|
+
coverage: baseCounts.coverage,
|
|
3183
|
+
metadata: metadataCount,
|
|
3184
|
+
mirrors: baseCounts.mirrors,
|
|
3185
|
+
runtimes: baseCounts.runtimes,
|
|
3186
|
+
unresolved: baseCounts.unresolved,
|
|
3187
|
+
warnings: baseCounts.warnings,
|
|
3188
|
+
acceptedWarnings: baseCounts.acceptedWarnings
|
|
3189
|
+
};
|
|
3190
|
+
bytes = addRetainedBytes(bytes, encoder.encode(serializeDeterministically({
|
|
3191
|
+
counts,
|
|
3192
|
+
formatVersion: state.result.formatVersion,
|
|
3193
|
+
inspectionDigest: "sha256:0000000000000000000000000000000000000000000000000000000000000000",
|
|
3194
|
+
source: state.result.source,
|
|
3195
|
+
summary: state.result.summary,
|
|
3196
|
+
valid: state.result.valid
|
|
3197
|
+
})).byteLength * PREPARED_TEXT_BYTE_MULTIPLIER, retainedBeforePreparation, maximum);
|
|
3198
|
+
return {
|
|
3199
|
+
counts,
|
|
3200
|
+
preparedBytes: bytes
|
|
3201
|
+
};
|
|
3202
|
+
};
|
|
3203
|
+
/** Creates the inspection digest without materializing a project-sized key projection. */
|
|
3204
|
+
var createInspectionDigest = (counts, coverageCounts, formatVersion, items, summary, valid) => createIdentity((function* () {
|
|
3205
|
+
yield "core7-prepared-inspection";
|
|
3206
|
+
yield serializeDeterministically({
|
|
3207
|
+
counts,
|
|
3208
|
+
coverageCounts,
|
|
3209
|
+
formatVersion,
|
|
3210
|
+
summary,
|
|
3211
|
+
valid
|
|
3212
|
+
});
|
|
3213
|
+
for (const { key } of items) yield key;
|
|
3214
|
+
})());
|
|
3215
|
+
var findStartIndex = (items, lastKey) => {
|
|
3216
|
+
if (lastKey === null) return 0;
|
|
3217
|
+
let lower = 0;
|
|
3218
|
+
let upper = items.length - 1;
|
|
3219
|
+
while (lower <= upper) {
|
|
3220
|
+
const middle = lower + Math.floor((upper - lower) / 2);
|
|
3221
|
+
const candidate = items[middle];
|
|
3222
|
+
if (candidate === void 0) return invalidArgument();
|
|
3223
|
+
if (candidate.key === lastKey) return middle + 1;
|
|
3224
|
+
if (candidate.key < lastKey) lower = middle + 1;
|
|
3225
|
+
else upper = middle - 1;
|
|
3226
|
+
}
|
|
3227
|
+
return invalidArgument();
|
|
3228
|
+
};
|
|
3229
|
+
/** Creates the returned inspection from content-free state only. */
|
|
3230
|
+
var createPreparedProjectInspection = (input) => {
|
|
3231
|
+
const { counts, coverageCounts, formatVersion, items, maxEntries, preparedBytes, resourceUsage: validationUsage, source, summary, valid } = input;
|
|
3232
|
+
const views = createViews(items);
|
|
3233
|
+
const inspectionDigest = createInspectionDigest(counts, coverageCounts, formatVersion, items, summary, valid);
|
|
3234
|
+
const resourceUsage = freezeRecursively({
|
|
3235
|
+
canonicalBytes: validationUsage.canonicalBytes,
|
|
3236
|
+
peakRetainedBytes: Math.max(validationUsage.peakRetainedBytes, validationUsage.retainedBytes + preparedBytes),
|
|
3237
|
+
preparedBytes,
|
|
3238
|
+
retainedBytes: preparedBytes,
|
|
3239
|
+
totalBytesRead: validationUsage.totalBytesRead
|
|
3240
|
+
});
|
|
3241
|
+
const readPage = (pageInput) => {
|
|
3242
|
+
if (typeof pageInput !== "object" || pageInput === null || Reflect.ownKeys(pageInput).some((key) => key !== "cursor" && key !== "maxItems" && key !== "view") || !Number.isSafeInteger(pageInput.maxItems) || pageInput.maxItems < 1 || pageInput.maxItems > maxEntries || pageInput.cursor !== void 0 && typeof pageInput.cursor !== "string" || pageInput.view !== "all" && pageInput.view !== "diagnostics" && pageInput.view !== "coverage" && pageInput.view !== "metadata") return invalidArgument();
|
|
3243
|
+
const viewItems = views[pageInput.view];
|
|
3244
|
+
const startIndex = findStartIndex(viewItems, decodeCursor(pageInput.cursor, inspectionDigest, pageInput.view));
|
|
3245
|
+
const records = viewItems.slice(startIndex, startIndex + pageInput.maxItems).map(({ item, key }, index) => {
|
|
3246
|
+
return {
|
|
3247
|
+
item,
|
|
3248
|
+
nextCursor: startIndex + index + 1 >= viewItems.length ? null : encodeCursor(inspectionDigest, pageInput.view, key)
|
|
3249
|
+
};
|
|
3250
|
+
});
|
|
3251
|
+
const nextCursor = records.at(-1)?.nextCursor ?? null;
|
|
3252
|
+
return freezeRecursively({
|
|
3253
|
+
counts,
|
|
3254
|
+
coverageCounts,
|
|
3255
|
+
formatVersion,
|
|
3256
|
+
inspectionDigest,
|
|
3257
|
+
page: {
|
|
3258
|
+
isComplete: nextCursor === null,
|
|
3259
|
+
nextCursor,
|
|
3260
|
+
records,
|
|
3261
|
+
totalItems: viewItems.length
|
|
3262
|
+
},
|
|
3263
|
+
source,
|
|
3264
|
+
summary,
|
|
3265
|
+
valid,
|
|
3266
|
+
view: pageInput.view
|
|
3267
|
+
});
|
|
3268
|
+
};
|
|
3269
|
+
return freezeRecursively({
|
|
3270
|
+
counts,
|
|
3271
|
+
coverageCounts,
|
|
3272
|
+
formatVersion,
|
|
3273
|
+
inspectionDigest,
|
|
3274
|
+
readPage,
|
|
3275
|
+
resourceUsage,
|
|
3276
|
+
source,
|
|
3277
|
+
summary,
|
|
3278
|
+
valid
|
|
3279
|
+
});
|
|
3280
|
+
};
|
|
3281
|
+
/**
|
|
3282
|
+
* Prepares one immutable content-free inspection for synchronous bounded page reads.
|
|
3283
|
+
* @param input The source-neutral reader and optional cancellation signal.
|
|
3284
|
+
* @param options The immutable operation limits.
|
|
3285
|
+
* @returns A promise resolving to the complete prepared inspection.
|
|
3286
|
+
* @throws
|
|
3287
|
+
* - INVALID_ARGUMENT: The Core operation received an invalid argument.
|
|
3288
|
+
* - INVALID_REPOSITORY_PATH: The repository path is invalid.
|
|
3289
|
+
* - ENTRY_NOT_FOUND: The requested repository entry was not found.
|
|
3290
|
+
* - ENTRY_NOT_FILE: The requested repository entry is not a file.
|
|
3291
|
+
* - ENTRY_NOT_DIRECTORY: The requested repository entry is not a directory.
|
|
3292
|
+
* - ACCESS_DENIED: Access to the repository source was denied.
|
|
3293
|
+
* - SOURCE_UNAVAILABLE: The repository source is unavailable.
|
|
3294
|
+
* - SNAPSHOT_CHANGED: The repository snapshot changed during the operation.
|
|
3295
|
+
* - PROVIDER_INCOMPLETE: The repository provider cannot expose a complete result.
|
|
3296
|
+
* - INVALID_SOURCE_DATA: The repository source returned invalid data.
|
|
3297
|
+
* - RESOURCE_LIMIT_EXCEEDED: A named repository resource limit was exceeded.
|
|
3298
|
+
* - RESOURCE_LIMIT_EXCEEDED: A Core resource limit was exceeded.
|
|
3299
|
+
* - ABORTED: The repository operation was aborted.
|
|
3300
|
+
* - ABORTED: The Core operation was aborted.
|
|
3301
|
+
*/
|
|
3302
|
+
var createProjectInspection = async (input, options) => {
|
|
3303
|
+
const state = await validateProjectState(input, options);
|
|
3304
|
+
const estimate = estimatePreparedProjectInspection(state, {
|
|
3305
|
+
agents: state.result.summary?.counts.agents ?? 0,
|
|
3306
|
+
context: state.result.summary?.counts.context ?? 0,
|
|
3307
|
+
decisions: state.result.summary?.counts.decisions ?? 0,
|
|
3308
|
+
diagnostics: state.result.diagnostics.length,
|
|
3309
|
+
errors: state.result.errorCount,
|
|
3310
|
+
coverage: state.result.coverage.length,
|
|
3311
|
+
mirrors: state.result.summary?.counts.mirrors ?? 0,
|
|
3312
|
+
runtimes: state.result.summary?.counts.runtimes ?? 0,
|
|
3313
|
+
unresolved: state.result.summary?.counts.unresolved ?? 0,
|
|
3314
|
+
warnings: state.result.warningCount,
|
|
3315
|
+
acceptedWarnings: state.result.acceptedWarningCount
|
|
3316
|
+
}, options.limits.maxRetainedBytes);
|
|
3317
|
+
const counts = freezeRecursively(estimate.counts);
|
|
3318
|
+
const items = freezeRecursively(createKeyedItems(iterateProjectInspectionItems(state)));
|
|
3319
|
+
return createPreparedProjectInspection({
|
|
3320
|
+
counts,
|
|
3321
|
+
coverageCounts: state.result.coverageCounts,
|
|
3322
|
+
formatVersion: state.result.formatVersion,
|
|
3323
|
+
items,
|
|
3324
|
+
maxEntries: options.limits.maxEntries,
|
|
3325
|
+
preparedBytes: estimate.preparedBytes,
|
|
3326
|
+
resourceUsage: state.resourceUsage,
|
|
3327
|
+
source: state.result.source,
|
|
3328
|
+
summary: state.result.summary,
|
|
3329
|
+
valid: state.result.valid
|
|
3330
|
+
});
|
|
3331
|
+
};
|
|
3332
|
+
//#endregion
|
|
3333
|
+
//#region src/core/index.ts
|
|
3334
|
+
var core_exports = /* @__PURE__ */ __exportAll({ createCore: () => createCore });
|
|
3335
|
+
/**
|
|
3336
|
+
* Creates one immutable Core instance from detached configuration snapshots.
|
|
3337
|
+
* @param options Optional resource-limit overrides.
|
|
3338
|
+
* @returns A frozen Core instance safe for concurrent independent operations.
|
|
3339
|
+
* @throws
|
|
3340
|
+
* - INVALID_RESOURCE_LIMIT: A Core resource limit is invalid.
|
|
3341
|
+
*/
|
|
3342
|
+
var createCore = (options) => {
|
|
3343
|
+
const snapshot = normalizeCoreOptions(options);
|
|
3344
|
+
return freezeRecursively({
|
|
3345
|
+
calculateContentDigest: (input) => {
|
|
3346
|
+
const operation = createCoreOperationOptionsSnapshot(snapshot);
|
|
3347
|
+
return calculateContentDigest(input, operation.limits);
|
|
3348
|
+
},
|
|
3349
|
+
createProjectInspection: (input) => createProjectInspection(input, createCoreOperationOptionsSnapshot(snapshot)),
|
|
3350
|
+
validateProject: (input) => validateProject(input, createCoreOperationOptionsSnapshot(snapshot)),
|
|
3351
|
+
matchManifestScope: (input) => matchManifestScope(input, createCoreOperationOptionsSnapshot(snapshot)),
|
|
3352
|
+
normalizeText: (input) => {
|
|
3353
|
+
const operation = createCoreOperationOptionsSnapshot(snapshot);
|
|
3354
|
+
return normalizeTextDocument(input, operation.limits, "normalize-text");
|
|
3355
|
+
},
|
|
3356
|
+
parseDecision: (input) => parseDecisionDocument(input, createCoreOperationOptionsSnapshot(snapshot)),
|
|
3357
|
+
parseManifest: (input) => parseManifestDocument(input, createCoreOperationOptionsSnapshot(snapshot)),
|
|
3358
|
+
readCanonicalContentPage: (input) => readCanonicalContentPage(input, createCoreOperationOptionsSnapshot(snapshot))
|
|
3359
|
+
});
|
|
3360
|
+
};
|
|
3361
|
+
//#endregion
|
|
3362
|
+
export { createCore as n, parseIntegrationBlock as r, core_exports as t };
|
|
3363
|
+
|
|
3364
|
+
//# sourceMappingURL=core-Cdeiu6xG.js.map
|