@oddessentials/odd-docs 2.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/LICENSE +81 -0
- package/dist/cli/index.js +1481 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +224 -0
- package/dist/index.js +1173 -0
- package/dist/index.js.map +1 -0
- package/package.json +86 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
// src/core/ir/builder.ts
|
|
2
|
+
import { createHash } from "crypto";
|
|
3
|
+
|
|
4
|
+
// src/core/parser/schemaParser.ts
|
|
5
|
+
function parseSchema(schema) {
|
|
6
|
+
if (!schema) return [];
|
|
7
|
+
const jsonSchema = schema;
|
|
8
|
+
const parameters = [];
|
|
9
|
+
const required = new Set(jsonSchema.required ?? []);
|
|
10
|
+
if (jsonSchema.properties) {
|
|
11
|
+
for (const [name, propSchema] of Object.entries(jsonSchema.properties)) {
|
|
12
|
+
parameters.push(parseProperty(name, propSchema, required.has(name)));
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return parameters;
|
|
16
|
+
}
|
|
17
|
+
function parseProperty(name, schema, isRequired) {
|
|
18
|
+
const param = {
|
|
19
|
+
name,
|
|
20
|
+
type: resolveType(schema),
|
|
21
|
+
required: isRequired
|
|
22
|
+
};
|
|
23
|
+
if (schema.description) {
|
|
24
|
+
param.description = schema.description;
|
|
25
|
+
}
|
|
26
|
+
if (schema.default !== void 0) {
|
|
27
|
+
param.default = schema.default;
|
|
28
|
+
}
|
|
29
|
+
if (schema.enum) {
|
|
30
|
+
param.enum = schema.enum;
|
|
31
|
+
}
|
|
32
|
+
const constraints = buildConstraints(schema);
|
|
33
|
+
if (constraints) {
|
|
34
|
+
param.constraints = constraints;
|
|
35
|
+
}
|
|
36
|
+
return param;
|
|
37
|
+
}
|
|
38
|
+
function resolveType(schema) {
|
|
39
|
+
if (schema.$ref) {
|
|
40
|
+
const refParts = schema.$ref.split("/");
|
|
41
|
+
return refParts[refParts.length - 1];
|
|
42
|
+
}
|
|
43
|
+
if (schema.enum) {
|
|
44
|
+
return "enum";
|
|
45
|
+
}
|
|
46
|
+
if (schema.type === "array" && schema.items) {
|
|
47
|
+
return `${resolveType(schema.items)}[]`;
|
|
48
|
+
}
|
|
49
|
+
return schema.type ?? "unknown";
|
|
50
|
+
}
|
|
51
|
+
function buildConstraints(schema) {
|
|
52
|
+
const parts = [];
|
|
53
|
+
if (schema.minimum !== void 0) {
|
|
54
|
+
parts.push(`min: ${schema.minimum}`);
|
|
55
|
+
}
|
|
56
|
+
if (schema.maximum !== void 0) {
|
|
57
|
+
parts.push(`max: ${schema.maximum}`);
|
|
58
|
+
}
|
|
59
|
+
if (schema.minLength !== void 0) {
|
|
60
|
+
parts.push(`minLength: ${schema.minLength}`);
|
|
61
|
+
}
|
|
62
|
+
if (schema.maxLength !== void 0) {
|
|
63
|
+
parts.push(`maxLength: ${schema.maxLength}`);
|
|
64
|
+
}
|
|
65
|
+
if (schema.pattern) {
|
|
66
|
+
parts.push(`pattern: ${schema.pattern}`);
|
|
67
|
+
}
|
|
68
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
69
|
+
}
|
|
70
|
+
function enrichSchemaSection(section) {
|
|
71
|
+
return {
|
|
72
|
+
...section,
|
|
73
|
+
parameters: parseSchema(section.schema)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// src/core/ir/builder.ts
|
|
78
|
+
function buildDocIR(manifest) {
|
|
79
|
+
const enrichedInputs = enrichSchemaSection(manifest.inputs);
|
|
80
|
+
const ir = {
|
|
81
|
+
version: "1.0.0",
|
|
82
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
83
|
+
determinismKey: "",
|
|
84
|
+
// Computed below
|
|
85
|
+
entity: manifest.entity,
|
|
86
|
+
inputs: enrichedInputs,
|
|
87
|
+
constraints: manifest.constraints,
|
|
88
|
+
lifecycle: manifest.lifecycle,
|
|
89
|
+
provenance: {
|
|
90
|
+
entity: "manifest",
|
|
91
|
+
inputs: "manifest"
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
ir.determinismKey = computeDeterminismKey(ir);
|
|
95
|
+
return ir;
|
|
96
|
+
}
|
|
97
|
+
function computeDeterminismKey(ir) {
|
|
98
|
+
const canonical = JSON.stringify({
|
|
99
|
+
entity: ir.entity,
|
|
100
|
+
inputs: ir.inputs,
|
|
101
|
+
constraints: ir.constraints,
|
|
102
|
+
lifecycle: ir.lifecycle
|
|
103
|
+
});
|
|
104
|
+
const hash = createHash("sha256").update(canonical).digest("hex");
|
|
105
|
+
return `sha256:${hash}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// src/core/ir/merger.ts
|
|
109
|
+
var PRECEDENCE = ["introspection", "manifest", "overlay", "narrative"];
|
|
110
|
+
function getPrecedence(source) {
|
|
111
|
+
const index = PRECEDENCE.indexOf(source);
|
|
112
|
+
return index === -1 ? PRECEDENCE.length : index;
|
|
113
|
+
}
|
|
114
|
+
function mergeDocIR(sources) {
|
|
115
|
+
const sorted = [...sources].sort((a, b) => getPrecedence(a.source) - getPrecedence(b.source));
|
|
116
|
+
let merged = {
|
|
117
|
+
version: "1.0.0",
|
|
118
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
119
|
+
determinismKey: "",
|
|
120
|
+
entity: { type: "tool", id: "", version: "" },
|
|
121
|
+
inputs: { provenance: "manifest" },
|
|
122
|
+
provenance: {}
|
|
123
|
+
};
|
|
124
|
+
for (const { ir, source } of sorted) {
|
|
125
|
+
merged = mergePartial(merged, ir, source);
|
|
126
|
+
}
|
|
127
|
+
if (merged.inputs?.schema) {
|
|
128
|
+
merged.inputs = enrichSchemaSection(merged.inputs);
|
|
129
|
+
}
|
|
130
|
+
if (merged.outputs?.schema) {
|
|
131
|
+
merged.outputs = enrichSchemaSection(merged.outputs);
|
|
132
|
+
}
|
|
133
|
+
merged.determinismKey = computeDeterminismKey(merged);
|
|
134
|
+
return merged;
|
|
135
|
+
}
|
|
136
|
+
function mergePartial(base, partial, source) {
|
|
137
|
+
const result = { ...base };
|
|
138
|
+
if (partial.entity) {
|
|
139
|
+
const baseProvenance = base.provenance?.entity;
|
|
140
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
141
|
+
result.entity = {
|
|
142
|
+
...result.entity,
|
|
143
|
+
...partial.entity
|
|
144
|
+
};
|
|
145
|
+
result.provenance = { ...result.provenance, entity: source };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (partial.inputs) {
|
|
149
|
+
const baseProvenance = base.inputs?.provenance;
|
|
150
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
151
|
+
result.inputs = {
|
|
152
|
+
...result.inputs,
|
|
153
|
+
...partial.inputs,
|
|
154
|
+
provenance: source
|
|
155
|
+
};
|
|
156
|
+
result.provenance = { ...result.provenance, inputs: source };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (partial.outputs) {
|
|
160
|
+
const baseProvenance = base.outputs?.provenance;
|
|
161
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
162
|
+
result.outputs = {
|
|
163
|
+
...result.outputs,
|
|
164
|
+
...partial.outputs,
|
|
165
|
+
provenance: source
|
|
166
|
+
};
|
|
167
|
+
result.provenance = { ...result.provenance, outputs: source };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (partial.overview) {
|
|
171
|
+
result.overview = result.overview ?? {};
|
|
172
|
+
if (partial.overview.intent && !result.overview.intent) {
|
|
173
|
+
result.overview.intent = partial.overview.intent;
|
|
174
|
+
}
|
|
175
|
+
if (partial.overview.useCases) {
|
|
176
|
+
const existing = new Set(result.overview.useCases ?? []);
|
|
177
|
+
for (const useCase of partial.overview.useCases) {
|
|
178
|
+
existing.add(useCase);
|
|
179
|
+
}
|
|
180
|
+
result.overview.useCases = [...existing];
|
|
181
|
+
}
|
|
182
|
+
if (partial.overview.sideEffects) {
|
|
183
|
+
result.overview.sideEffects = mergeSideEffects(
|
|
184
|
+
result.overview.sideEffects ?? [],
|
|
185
|
+
partial.overview.sideEffects,
|
|
186
|
+
source
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (partial.constraints) {
|
|
191
|
+
result.constraints = result.constraints ?? {};
|
|
192
|
+
if (partial.constraints.capabilities) {
|
|
193
|
+
result.constraints.capabilities = mergeCapabilities(
|
|
194
|
+
result.constraints.capabilities ?? [],
|
|
195
|
+
partial.constraints.capabilities,
|
|
196
|
+
source
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
if (partial.constraints.timeoutMs !== void 0) {
|
|
200
|
+
const baseProvenance = base.provenance?.constraints;
|
|
201
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
202
|
+
result.constraints.timeoutMs = partial.constraints.timeoutMs;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
if (partial.constraints.resourceLimits) {
|
|
206
|
+
const baseProvenance = base.provenance?.constraints;
|
|
207
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
208
|
+
result.constraints.resourceLimits = {
|
|
209
|
+
...result.constraints.resourceLimits,
|
|
210
|
+
...partial.constraints.resourceLimits
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
if (partial.narrative) {
|
|
216
|
+
result.narrative = result.narrative ?? {};
|
|
217
|
+
if (partial.narrative.examples) {
|
|
218
|
+
result.narrative.examples = [
|
|
219
|
+
...result.narrative.examples ?? [],
|
|
220
|
+
...partial.narrative.examples
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
if (partial.narrative.notes) {
|
|
224
|
+
result.narrative.notes = [...result.narrative.notes ?? [], ...partial.narrative.notes];
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (partial.errors) {
|
|
228
|
+
result.errors = mergeErrors(result.errors ?? [], partial.errors, source);
|
|
229
|
+
}
|
|
230
|
+
if (partial.lifecycle) {
|
|
231
|
+
const baseProvenance = base.provenance?.lifecycle;
|
|
232
|
+
if (!baseProvenance || getPrecedence(source) <= getPrecedence(baseProvenance)) {
|
|
233
|
+
result.lifecycle = {
|
|
234
|
+
...result.lifecycle,
|
|
235
|
+
...partial.lifecycle
|
|
236
|
+
};
|
|
237
|
+
result.provenance = { ...result.provenance, lifecycle: source };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
function mergeSideEffects(base, incoming, source) {
|
|
243
|
+
const byType = /* @__PURE__ */ new Map();
|
|
244
|
+
for (const effect of base) {
|
|
245
|
+
byType.set(effect.type, effect);
|
|
246
|
+
}
|
|
247
|
+
for (const effect of incoming) {
|
|
248
|
+
const existing = byType.get(effect.type);
|
|
249
|
+
if (!existing || getPrecedence(source) <= getPrecedence(existing.provenance ?? "narrative")) {
|
|
250
|
+
byType.set(effect.type, { ...effect, provenance: source });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return [...byType.values()].sort((a, b) => a.type.localeCompare(b.type));
|
|
254
|
+
}
|
|
255
|
+
function mergeCapabilities(base, incoming, source) {
|
|
256
|
+
const byType = /* @__PURE__ */ new Map();
|
|
257
|
+
for (const cap of base) {
|
|
258
|
+
byType.set(cap.type, cap);
|
|
259
|
+
}
|
|
260
|
+
for (const cap of incoming) {
|
|
261
|
+
const existing = byType.get(cap.type);
|
|
262
|
+
if (!existing || getPrecedence(source) <= getPrecedence(existing.provenance ?? "narrative")) {
|
|
263
|
+
byType.set(cap.type, { ...cap, provenance: source });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return [...byType.values()].sort((a, b) => a.type.localeCompare(b.type));
|
|
267
|
+
}
|
|
268
|
+
function mergeErrors(base, incoming, source) {
|
|
269
|
+
if (!base) return incoming;
|
|
270
|
+
if (!incoming) return base;
|
|
271
|
+
const byCode = /* @__PURE__ */ new Map();
|
|
272
|
+
for (const err of base) {
|
|
273
|
+
byCode.set(err.code, err);
|
|
274
|
+
}
|
|
275
|
+
for (const err of incoming) {
|
|
276
|
+
const existing = byCode.get(err.code);
|
|
277
|
+
if (!existing || getPrecedence(source) <= getPrecedence(existing.provenance ?? "narrative")) {
|
|
278
|
+
byCode.set(err.code, { ...err, provenance: source });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return [...byCode.values()].sort((a, b) => a.code.localeCompare(b.code));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// src/core/capabilities.ts
|
|
285
|
+
var KNOWN_CAPABILITIES = [
|
|
286
|
+
"network",
|
|
287
|
+
"filesystem",
|
|
288
|
+
"secrets",
|
|
289
|
+
"exec",
|
|
290
|
+
"subprocess",
|
|
291
|
+
"database",
|
|
292
|
+
"queue"
|
|
293
|
+
];
|
|
294
|
+
var SAFETY_AFFECTING_PATTERNS = ["network", "exec", "subprocess", "write", "delete"];
|
|
295
|
+
function isKnownCapability(capability) {
|
|
296
|
+
return KNOWN_CAPABILITIES.includes(capability);
|
|
297
|
+
}
|
|
298
|
+
function isSafetyAffecting(capability) {
|
|
299
|
+
return SAFETY_AFFECTING_PATTERNS.some(
|
|
300
|
+
(pattern) => capability.toLowerCase().includes(pattern.toLowerCase())
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/core/ir/validator.ts
|
|
305
|
+
function validateDocIR(ir, options = {}) {
|
|
306
|
+
const issues = [];
|
|
307
|
+
if (!ir.entity.id) {
|
|
308
|
+
issues.push({
|
|
309
|
+
severity: "error",
|
|
310
|
+
code: "MISSING_TOOL_ID",
|
|
311
|
+
message: "Tool ID is required",
|
|
312
|
+
path: "entity.id"
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (!ir.entity.version) {
|
|
316
|
+
issues.push({
|
|
317
|
+
severity: "error",
|
|
318
|
+
code: "MISSING_VERSION",
|
|
319
|
+
message: "Version is required",
|
|
320
|
+
path: "entity.version"
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (!ir.inputs?.schema && !ir.inputs?.parameters?.length) {
|
|
324
|
+
issues.push({
|
|
325
|
+
severity: "error",
|
|
326
|
+
code: "MISSING_PARAMETERS_SCHEMA",
|
|
327
|
+
message: "Parameters schema is required",
|
|
328
|
+
path: "inputs"
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
if (ir.constraints?.capabilities) {
|
|
332
|
+
for (const cap of ir.constraints.capabilities) {
|
|
333
|
+
if (!isKnownCapability(cap.type)) {
|
|
334
|
+
const isSafety = isSafetyAffecting(cap.type);
|
|
335
|
+
issues.push({
|
|
336
|
+
severity: options.strict && isSafety ? "error" : "warning",
|
|
337
|
+
code: "UNKNOWN_CAPABILITY",
|
|
338
|
+
message: `Unknown capability: ${cap.type}${isSafety ? " (safety-affecting)" : ""}`,
|
|
339
|
+
path: `constraints.capabilities.${cap.type}`,
|
|
340
|
+
provenance: cap.provenance
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
issues.push(...checkStructuralContradictions(ir));
|
|
346
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
347
|
+
return {
|
|
348
|
+
valid: !hasErrors,
|
|
349
|
+
issues
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
function checkStructuralContradictions(ir) {
|
|
353
|
+
const issues = [];
|
|
354
|
+
if (ir.narrative?.notes && ir.inputs?.parameters) {
|
|
355
|
+
const schemaParams = new Set(ir.inputs.parameters.map((p) => p.name.toLowerCase()));
|
|
356
|
+
for (const note of ir.narrative.notes) {
|
|
357
|
+
const paramMentions = note.match(/(?:parameter|param)\s+[`"']?(\w+)[`"']?/gi) ?? [];
|
|
358
|
+
const theParamMentions = note.match(/the\s+[`"']?(\w+)[`"']?\s+parameter/gi) ?? [];
|
|
359
|
+
for (const match of [...paramMentions, ...theParamMentions]) {
|
|
360
|
+
const paramName = match.replace(/.*?[`"']?(\w+)[`"']?.*/i, "$1").toLowerCase();
|
|
361
|
+
if (paramName && !schemaParams.has(paramName) && paramName !== "parameter") {
|
|
362
|
+
issues.push({
|
|
363
|
+
severity: "error",
|
|
364
|
+
code: "NARRATIVE_REFERENCES_UNDEFINED_PARAM",
|
|
365
|
+
message: `Narrative references parameter "${paramName}" not in schema`,
|
|
366
|
+
path: "narrative.notes",
|
|
367
|
+
provenance: "narrative"
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (ir.narrative?.examples && ir.inputs?.parameters) {
|
|
374
|
+
const schemaParams = new Set(ir.inputs.parameters.map((p) => p.name));
|
|
375
|
+
for (const example of ir.narrative.examples) {
|
|
376
|
+
if (example.input && typeof example.input === "object") {
|
|
377
|
+
for (const key of Object.keys(example.input)) {
|
|
378
|
+
if (!schemaParams.has(key)) {
|
|
379
|
+
issues.push({
|
|
380
|
+
severity: "warning",
|
|
381
|
+
code: "EXAMPLE_USES_UNDEFINED_PARAM",
|
|
382
|
+
message: `Example uses parameter "${key}" not in schema`,
|
|
383
|
+
path: `narrative.examples.${example.title ?? "unnamed"}`,
|
|
384
|
+
provenance: "narrative"
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (ir.overview?.sideEffects && ir.constraints?.capabilities) {
|
|
392
|
+
const capTypes = new Set(ir.constraints.capabilities.map((c) => c.type));
|
|
393
|
+
for (const effect of ir.overview.sideEffects) {
|
|
394
|
+
const expectedCap = mapSideEffectToCapability(effect.type);
|
|
395
|
+
if (expectedCap && !capTypes.has(expectedCap)) {
|
|
396
|
+
issues.push({
|
|
397
|
+
severity: "error",
|
|
398
|
+
code: "SIDE_EFFECT_CAPABILITY_MISMATCH",
|
|
399
|
+
message: `Side effect "${effect.type}" requires capability "${expectedCap}" which is not declared`,
|
|
400
|
+
path: `overview.sideEffects.${effect.type}`,
|
|
401
|
+
provenance: effect.provenance
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return issues;
|
|
407
|
+
}
|
|
408
|
+
function mapSideEffectToCapability(effectType) {
|
|
409
|
+
const mapping = {
|
|
410
|
+
filesystem: "filesystem",
|
|
411
|
+
network: "network",
|
|
412
|
+
secrets: "secrets",
|
|
413
|
+
exec: "exec",
|
|
414
|
+
subprocess: "subprocess",
|
|
415
|
+
database: "database",
|
|
416
|
+
queue: "queue"
|
|
417
|
+
};
|
|
418
|
+
return mapping[effectType] ?? null;
|
|
419
|
+
}
|
|
420
|
+
function formatValidationResult(result, repoPath) {
|
|
421
|
+
const lines = [];
|
|
422
|
+
lines.push(`Validating: ${repoPath}`);
|
|
423
|
+
lines.push("");
|
|
424
|
+
if (result.valid) {
|
|
425
|
+
lines.push("\u2713 Validation passed");
|
|
426
|
+
} else {
|
|
427
|
+
lines.push("\u2717 Validation failed");
|
|
428
|
+
}
|
|
429
|
+
lines.push("");
|
|
430
|
+
const errors = result.issues.filter((i) => i.severity === "error");
|
|
431
|
+
const warnings = result.issues.filter((i) => i.severity === "warning");
|
|
432
|
+
if (errors.length > 0) {
|
|
433
|
+
lines.push(`Errors (${errors.length}):`);
|
|
434
|
+
for (const issue of errors) {
|
|
435
|
+
lines.push(` \u2717 [${issue.code}] ${issue.message}`);
|
|
436
|
+
if (issue.path) {
|
|
437
|
+
lines.push(` at ${issue.path}`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
lines.push("");
|
|
441
|
+
}
|
|
442
|
+
if (warnings.length > 0) {
|
|
443
|
+
lines.push(`Warnings (${warnings.length}):`);
|
|
444
|
+
for (const issue of warnings) {
|
|
445
|
+
lines.push(` \u26A0 [${issue.code}] ${issue.message}`);
|
|
446
|
+
if (issue.path) {
|
|
447
|
+
lines.push(` at ${issue.path}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
lines.push("");
|
|
451
|
+
}
|
|
452
|
+
return lines.join("\n");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// src/core/parser/manifestParser.ts
|
|
456
|
+
import { readFile } from "fs/promises";
|
|
457
|
+
import { join } from "path";
|
|
458
|
+
async function parseManifest(repoPath) {
|
|
459
|
+
const manifestPath = join(repoPath, "manifest.json");
|
|
460
|
+
let content;
|
|
461
|
+
try {
|
|
462
|
+
content = await readFile(manifestPath, "utf-8");
|
|
463
|
+
} catch {
|
|
464
|
+
throw new Error(`Manifest not found: ${manifestPath}`);
|
|
465
|
+
}
|
|
466
|
+
let manifest;
|
|
467
|
+
try {
|
|
468
|
+
manifest = JSON.parse(content);
|
|
469
|
+
} catch {
|
|
470
|
+
throw new Error(`Invalid JSON in manifest: ${manifestPath}`);
|
|
471
|
+
}
|
|
472
|
+
if (!manifest.tool_id) {
|
|
473
|
+
throw new Error("Manifest missing required field: tool_id");
|
|
474
|
+
}
|
|
475
|
+
if (!manifest.version) {
|
|
476
|
+
throw new Error("Manifest missing required field: version");
|
|
477
|
+
}
|
|
478
|
+
const capabilities = [];
|
|
479
|
+
if (manifest.capabilities) {
|
|
480
|
+
for (const [type, values] of Object.entries(manifest.capabilities)) {
|
|
481
|
+
if (Array.isArray(values) && values.length > 0) {
|
|
482
|
+
capabilities.push({ type, values, provenance: "manifest" });
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const result = {
|
|
487
|
+
entity: {
|
|
488
|
+
type: "tool",
|
|
489
|
+
id: manifest.tool_id,
|
|
490
|
+
version: manifest.version,
|
|
491
|
+
description: manifest.description
|
|
492
|
+
},
|
|
493
|
+
inputs: {
|
|
494
|
+
schema: manifest.parameters,
|
|
495
|
+
provenance: "manifest"
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
if (capabilities.length > 0 || manifest.timeout_ms || manifest.resource_limits) {
|
|
499
|
+
result.constraints = {
|
|
500
|
+
capabilities: capabilities.length > 0 ? capabilities : void 0,
|
|
501
|
+
timeoutMs: manifest.timeout_ms,
|
|
502
|
+
resourceLimits: manifest.resource_limits ? {
|
|
503
|
+
memoryMb: manifest.resource_limits.memory_mb,
|
|
504
|
+
cpuCores: manifest.resource_limits.cpu_cores
|
|
505
|
+
} : void 0
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
if (manifest.deprecation) {
|
|
509
|
+
result.lifecycle = {
|
|
510
|
+
version: manifest.version,
|
|
511
|
+
deprecation: {
|
|
512
|
+
deprecatedAt: manifest.deprecation.deprecated_at,
|
|
513
|
+
sunsetDate: manifest.deprecation.sunset_date,
|
|
514
|
+
migrationUrl: manifest.deprecation.migration_url
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
return result;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/core/parser/mcpIntrospection.ts
|
|
522
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
523
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
524
|
+
async function introspectMcpServer(options) {
|
|
525
|
+
if (!options.serverCommand) {
|
|
526
|
+
throw new Error("Server command is required for introspection");
|
|
527
|
+
}
|
|
528
|
+
const transport = new StdioClientTransport({
|
|
529
|
+
command: options.serverCommand,
|
|
530
|
+
args: options.serverArgs ?? []
|
|
531
|
+
});
|
|
532
|
+
const client = new Client(
|
|
533
|
+
{
|
|
534
|
+
name: "odd-docs",
|
|
535
|
+
version: "0.1.0"
|
|
536
|
+
},
|
|
537
|
+
{
|
|
538
|
+
capabilities: {}
|
|
539
|
+
}
|
|
540
|
+
);
|
|
541
|
+
const result = {};
|
|
542
|
+
try {
|
|
543
|
+
await client.connect(transport);
|
|
544
|
+
try {
|
|
545
|
+
const toolsResponse = await client.listTools();
|
|
546
|
+
result.tools = toolsResponse.tools.map((t) => ({
|
|
547
|
+
name: t.name,
|
|
548
|
+
description: t.description,
|
|
549
|
+
inputSchema: t.inputSchema
|
|
550
|
+
}));
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const resourcesResponse = await client.listResources();
|
|
555
|
+
result.resources = resourcesResponse.resources.map((r) => ({
|
|
556
|
+
uri: r.uri,
|
|
557
|
+
name: r.name,
|
|
558
|
+
description: r.description,
|
|
559
|
+
mimeType: r.mimeType
|
|
560
|
+
}));
|
|
561
|
+
} catch {
|
|
562
|
+
}
|
|
563
|
+
try {
|
|
564
|
+
const promptsResponse = await client.listPrompts();
|
|
565
|
+
result.prompts = promptsResponse.prompts.map((p) => ({
|
|
566
|
+
name: p.name,
|
|
567
|
+
description: p.description,
|
|
568
|
+
arguments: p.arguments
|
|
569
|
+
}));
|
|
570
|
+
} catch {
|
|
571
|
+
}
|
|
572
|
+
} finally {
|
|
573
|
+
await client.close();
|
|
574
|
+
}
|
|
575
|
+
return result;
|
|
576
|
+
}
|
|
577
|
+
function introspectionToPartialIR(introspection, toolName) {
|
|
578
|
+
const provenance = "introspection";
|
|
579
|
+
const tool = toolName ? introspection.tools?.find((t) => t.name === toolName) : introspection.tools?.[0];
|
|
580
|
+
if (!tool) {
|
|
581
|
+
return { provenance: { source: provenance } };
|
|
582
|
+
}
|
|
583
|
+
const inputs = {
|
|
584
|
+
schema: tool.inputSchema,
|
|
585
|
+
provenance
|
|
586
|
+
};
|
|
587
|
+
const capabilities = [];
|
|
588
|
+
if (introspection.resources?.length) {
|
|
589
|
+
capabilities.push({
|
|
590
|
+
type: "resources",
|
|
591
|
+
values: introspection.resources.map((r) => r.uri),
|
|
592
|
+
provenance
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
return {
|
|
596
|
+
entity: {
|
|
597
|
+
type: "tool",
|
|
598
|
+
id: tool.name,
|
|
599
|
+
version: "0.0.0",
|
|
600
|
+
// Runtime doesn't provide version
|
|
601
|
+
description: tool.description
|
|
602
|
+
},
|
|
603
|
+
inputs,
|
|
604
|
+
constraints: capabilities.length > 0 ? { capabilities } : void 0,
|
|
605
|
+
provenance: {
|
|
606
|
+
entity: provenance,
|
|
607
|
+
inputs: provenance
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function parseIntrospectTarget(target) {
|
|
612
|
+
if (target.startsWith("stdio://")) {
|
|
613
|
+
const parts2 = target.slice(8).split(" ");
|
|
614
|
+
return {
|
|
615
|
+
serverCommand: parts2[0],
|
|
616
|
+
serverArgs: parts2.slice(1)
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
if (target.startsWith("npx ")) {
|
|
620
|
+
const parts2 = target.split(" ");
|
|
621
|
+
return {
|
|
622
|
+
serverCommand: "npx",
|
|
623
|
+
serverArgs: parts2.slice(1)
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
if (target.startsWith("node ")) {
|
|
627
|
+
const parts2 = target.split(" ");
|
|
628
|
+
return {
|
|
629
|
+
serverCommand: "node",
|
|
630
|
+
serverArgs: parts2.slice(1)
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
const parts = target.split(" ");
|
|
634
|
+
return {
|
|
635
|
+
serverCommand: parts[0],
|
|
636
|
+
serverArgs: parts.slice(1)
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// src/core/renderer/markdownRenderer.ts
|
|
641
|
+
function renderMarkdown(ir) {
|
|
642
|
+
const lines = [];
|
|
643
|
+
lines.push(`# ${ir.entity.id}`);
|
|
644
|
+
lines.push("");
|
|
645
|
+
lines.push(`**Version:** ${ir.entity.version} `);
|
|
646
|
+
lines.push(`**Type:** ${ir.entity.type} `);
|
|
647
|
+
if (ir.entity.description) {
|
|
648
|
+
lines.push("");
|
|
649
|
+
lines.push(ir.entity.description);
|
|
650
|
+
}
|
|
651
|
+
lines.push("");
|
|
652
|
+
if (ir.overview) {
|
|
653
|
+
lines.push("## Overview");
|
|
654
|
+
lines.push("");
|
|
655
|
+
if (ir.overview.intent) {
|
|
656
|
+
lines.push(`**Intent:** ${ir.overview.intent}`);
|
|
657
|
+
lines.push("");
|
|
658
|
+
}
|
|
659
|
+
if (ir.overview.useCases?.length) {
|
|
660
|
+
lines.push("### Use Cases");
|
|
661
|
+
lines.push("");
|
|
662
|
+
for (const useCase of ir.overview.useCases) {
|
|
663
|
+
lines.push(`- ${useCase}`);
|
|
664
|
+
}
|
|
665
|
+
lines.push("");
|
|
666
|
+
}
|
|
667
|
+
if (ir.overview.sideEffects?.length) {
|
|
668
|
+
lines.push("### Side Effects");
|
|
669
|
+
lines.push("");
|
|
670
|
+
for (const effect of ir.overview.sideEffects) {
|
|
671
|
+
lines.push(
|
|
672
|
+
`- **${effect.type}**: ${effect.description} ${provenanceBadge(effect.provenance)}`
|
|
673
|
+
);
|
|
674
|
+
}
|
|
675
|
+
lines.push("");
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (ir.inputs?.parameters?.length) {
|
|
679
|
+
lines.push("## Inputs");
|
|
680
|
+
lines.push("");
|
|
681
|
+
lines.push(provenanceBadge(ir.inputs.provenance));
|
|
682
|
+
lines.push("");
|
|
683
|
+
lines.push(renderParameterTable(ir.inputs.parameters));
|
|
684
|
+
lines.push("");
|
|
685
|
+
}
|
|
686
|
+
if (ir.outputs?.parameters?.length) {
|
|
687
|
+
lines.push("## Outputs");
|
|
688
|
+
lines.push("");
|
|
689
|
+
lines.push(provenanceBadge(ir.outputs.provenance));
|
|
690
|
+
lines.push("");
|
|
691
|
+
lines.push(renderParameterTable(ir.outputs.parameters));
|
|
692
|
+
lines.push("");
|
|
693
|
+
}
|
|
694
|
+
if (ir.constraints) {
|
|
695
|
+
lines.push("## Constraints");
|
|
696
|
+
lines.push("");
|
|
697
|
+
if (ir.constraints.capabilities?.length) {
|
|
698
|
+
lines.push("### Capabilities");
|
|
699
|
+
lines.push("");
|
|
700
|
+
for (const cap of ir.constraints.capabilities) {
|
|
701
|
+
const values = cap.values?.join(", ") ?? "enabled";
|
|
702
|
+
lines.push(`- **${cap.type}**: ${values} ${provenanceBadge(cap.provenance)}`);
|
|
703
|
+
}
|
|
704
|
+
lines.push("");
|
|
705
|
+
}
|
|
706
|
+
if (ir.constraints.timeoutMs) {
|
|
707
|
+
lines.push(`**Timeout:** ${ir.constraints.timeoutMs}ms`);
|
|
708
|
+
lines.push("");
|
|
709
|
+
}
|
|
710
|
+
if (ir.constraints.resourceLimits) {
|
|
711
|
+
lines.push("### Resource Limits");
|
|
712
|
+
lines.push("");
|
|
713
|
+
if (ir.constraints.resourceLimits.memoryMb) {
|
|
714
|
+
lines.push(`- Memory: ${ir.constraints.resourceLimits.memoryMb} MB`);
|
|
715
|
+
}
|
|
716
|
+
if (ir.constraints.resourceLimits.cpuCores) {
|
|
717
|
+
lines.push(`- CPU: ${ir.constraints.resourceLimits.cpuCores} cores`);
|
|
718
|
+
}
|
|
719
|
+
lines.push("");
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (ir.errors?.length) {
|
|
723
|
+
lines.push("## Errors");
|
|
724
|
+
lines.push("");
|
|
725
|
+
lines.push("| Code | Description | Recovery |");
|
|
726
|
+
lines.push("|------|-------------|----------|");
|
|
727
|
+
for (const err of ir.errors) {
|
|
728
|
+
lines.push(`| \`${err.code}\` | ${err.description ?? ""} | ${err.recovery ?? ""} |`);
|
|
729
|
+
}
|
|
730
|
+
lines.push("");
|
|
731
|
+
}
|
|
732
|
+
if (ir.lifecycle?.deprecation) {
|
|
733
|
+
lines.push("## Lifecycle");
|
|
734
|
+
lines.push("");
|
|
735
|
+
lines.push("> [!WARNING]");
|
|
736
|
+
lines.push(`> This tool is deprecated as of ${ir.lifecycle.deprecation.deprecatedAt}.`);
|
|
737
|
+
lines.push(`> Sunset date: ${ir.lifecycle.deprecation.sunsetDate}`);
|
|
738
|
+
if (ir.lifecycle.deprecation.migrationUrl) {
|
|
739
|
+
lines.push(`> Migration guide: ${ir.lifecycle.deprecation.migrationUrl}`);
|
|
740
|
+
}
|
|
741
|
+
lines.push("");
|
|
742
|
+
}
|
|
743
|
+
if (ir.narrative?.examples?.length) {
|
|
744
|
+
lines.push("## Examples");
|
|
745
|
+
lines.push("");
|
|
746
|
+
for (const example of ir.narrative.examples) {
|
|
747
|
+
if (example.title) {
|
|
748
|
+
lines.push(`### ${example.title}`);
|
|
749
|
+
lines.push("");
|
|
750
|
+
}
|
|
751
|
+
if (example.description) {
|
|
752
|
+
lines.push(example.description);
|
|
753
|
+
lines.push("");
|
|
754
|
+
}
|
|
755
|
+
if (example.input) {
|
|
756
|
+
lines.push("**Input:**");
|
|
757
|
+
lines.push("```json");
|
|
758
|
+
lines.push(JSON.stringify(example.input, null, 2));
|
|
759
|
+
lines.push("```");
|
|
760
|
+
lines.push("");
|
|
761
|
+
}
|
|
762
|
+
if (example.output) {
|
|
763
|
+
lines.push("**Output:**");
|
|
764
|
+
lines.push("```json");
|
|
765
|
+
lines.push(JSON.stringify(example.output, null, 2));
|
|
766
|
+
lines.push("```");
|
|
767
|
+
lines.push("");
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
if (ir.narrative?.notes?.length) {
|
|
772
|
+
lines.push("## Notes");
|
|
773
|
+
lines.push("");
|
|
774
|
+
lines.push("*[author notes]*");
|
|
775
|
+
lines.push("");
|
|
776
|
+
for (const note of ir.narrative.notes) {
|
|
777
|
+
lines.push(note);
|
|
778
|
+
lines.push("");
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
lines.push("---");
|
|
782
|
+
lines.push("");
|
|
783
|
+
lines.push(`*Generated at ${ir.generatedAt}* `);
|
|
784
|
+
lines.push(`*Determinism key: \`${ir.determinismKey}\`*`);
|
|
785
|
+
return lines.join("\n");
|
|
786
|
+
}
|
|
787
|
+
function renderParameterTable(params) {
|
|
788
|
+
const lines = [];
|
|
789
|
+
lines.push("| Name | Type | Required | Default | Description |");
|
|
790
|
+
lines.push("|------|------|----------|---------|-------------|");
|
|
791
|
+
for (const param of params) {
|
|
792
|
+
const required = param.required ? "\u2713" : "";
|
|
793
|
+
const defaultVal = param.default !== void 0 ? `\`${JSON.stringify(param.default)}\`` : "";
|
|
794
|
+
const desc = param.description ?? "";
|
|
795
|
+
lines.push(`| \`${param.name}\` | \`${param.type}\` | ${required} | ${defaultVal} | ${desc} |`);
|
|
796
|
+
}
|
|
797
|
+
return lines.join("\n");
|
|
798
|
+
}
|
|
799
|
+
function provenanceBadge(source) {
|
|
800
|
+
if (!source) return "";
|
|
801
|
+
const badges = {
|
|
802
|
+
introspection: "`[from introspection]`",
|
|
803
|
+
manifest: "`[from schema]`",
|
|
804
|
+
overlay: "`[from overlay]`",
|
|
805
|
+
narrative: "`[author notes]`"
|
|
806
|
+
};
|
|
807
|
+
return badges[source] ?? "";
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/core/renderer/htmlRenderer.ts
|
|
811
|
+
import { marked } from "marked";
|
|
812
|
+
function renderHTML(ir) {
|
|
813
|
+
const markdown = renderMarkdown(ir);
|
|
814
|
+
const htmlContent = marked.parse(markdown);
|
|
815
|
+
return `<!DOCTYPE html>
|
|
816
|
+
<html lang="en" data-theme="light">
|
|
817
|
+
<head>
|
|
818
|
+
<meta charset="UTF-8">
|
|
819
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
820
|
+
<meta name="description" content="${escapeHtml(ir.entity.description ?? `Documentation for ${ir.entity.id}`)}">
|
|
821
|
+
<title>${escapeHtml(ir.entity.id)} - odd-docs</title>
|
|
822
|
+
<style>
|
|
823
|
+
${getThemeStyles()}
|
|
824
|
+
</style>
|
|
825
|
+
</head>
|
|
826
|
+
<body>
|
|
827
|
+
<div class="container">
|
|
828
|
+
<header>
|
|
829
|
+
<nav class="breadcrumb">
|
|
830
|
+
<a href="index.html">Home</a> / <span>${escapeHtml(ir.entity.id)}</span>
|
|
831
|
+
</nav>
|
|
832
|
+
</header>
|
|
833
|
+
<main class="content">
|
|
834
|
+
${htmlContent}
|
|
835
|
+
</main>
|
|
836
|
+
<footer>
|
|
837
|
+
<p class="meta">
|
|
838
|
+
Generated by <a href="https://github.com/oddessentials/odd-docs">odd-docs</a> at ${ir.generatedAt}
|
|
839
|
+
</p>
|
|
840
|
+
<p class="determinism">
|
|
841
|
+
<code>${ir.determinismKey}</code>
|
|
842
|
+
</p>
|
|
843
|
+
</footer>
|
|
844
|
+
</div>
|
|
845
|
+
<script>
|
|
846
|
+
${getThemeScript()}
|
|
847
|
+
</script>
|
|
848
|
+
</body>
|
|
849
|
+
</html>`;
|
|
850
|
+
}
|
|
851
|
+
function renderIndexHTML(docs) {
|
|
852
|
+
const items = docs.map(
|
|
853
|
+
(ir) => `
|
|
854
|
+
<li>
|
|
855
|
+
<a href="${ir.entity.id}.html">
|
|
856
|
+
<strong>${escapeHtml(ir.entity.id)}</strong>
|
|
857
|
+
<span class="version">v${ir.entity.version}</span>
|
|
858
|
+
</a>
|
|
859
|
+
<p>${escapeHtml(ir.entity.description ?? "")}</p>
|
|
860
|
+
</li>`
|
|
861
|
+
).join("\n");
|
|
862
|
+
return `<!DOCTYPE html>
|
|
863
|
+
<html lang="en" data-theme="light">
|
|
864
|
+
<head>
|
|
865
|
+
<meta charset="UTF-8">
|
|
866
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
867
|
+
<title>Documentation - odd-docs</title>
|
|
868
|
+
<style>
|
|
869
|
+
${getThemeStyles()}
|
|
870
|
+
</style>
|
|
871
|
+
</head>
|
|
872
|
+
<body>
|
|
873
|
+
<div class="container">
|
|
874
|
+
<header>
|
|
875
|
+
<h1>Documentation</h1>
|
|
876
|
+
</header>
|
|
877
|
+
<main class="content">
|
|
878
|
+
<ul class="doc-list">
|
|
879
|
+
${items}
|
|
880
|
+
</ul>
|
|
881
|
+
</main>
|
|
882
|
+
<footer>
|
|
883
|
+
<p class="meta">Generated by <a href="https://github.com/oddessentials/odd-docs">odd-docs</a></p>
|
|
884
|
+
</footer>
|
|
885
|
+
</div>
|
|
886
|
+
<script>
|
|
887
|
+
${getThemeScript()}
|
|
888
|
+
</script>
|
|
889
|
+
</body>
|
|
890
|
+
</html>`;
|
|
891
|
+
}
|
|
892
|
+
function escapeHtml(str) {
|
|
893
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
894
|
+
}
|
|
895
|
+
function getThemeStyles() {
|
|
896
|
+
return `
|
|
897
|
+
/* CSS Variables for theming */
|
|
898
|
+
:root {
|
|
899
|
+
/* Colors - neutral palette */
|
|
900
|
+
--color-bg: #ffffff;
|
|
901
|
+
--color-bg-secondary: #f8f9fa;
|
|
902
|
+
--color-text: #1a1a2e;
|
|
903
|
+
--color-text-muted: #6c757d;
|
|
904
|
+
--color-border: #dee2e6;
|
|
905
|
+
--color-link: #0066cc;
|
|
906
|
+
--color-link-hover: #004499;
|
|
907
|
+
--color-accent: #0066cc;
|
|
908
|
+
--color-success: #28a745;
|
|
909
|
+
--color-warning: #ffc107;
|
|
910
|
+
--color-error: #dc3545;
|
|
911
|
+
|
|
912
|
+
/* Typography */
|
|
913
|
+
--font-sans: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
914
|
+
--font-mono: 'SF Mono', Monaco, 'Cascadia Code', monospace;
|
|
915
|
+
--font-size-base: 16px;
|
|
916
|
+
--line-height: 1.6;
|
|
917
|
+
|
|
918
|
+
/* Spacing */
|
|
919
|
+
--space-xs: 0.25rem;
|
|
920
|
+
--space-sm: 0.5rem;
|
|
921
|
+
--space-md: 1rem;
|
|
922
|
+
--space-lg: 1.5rem;
|
|
923
|
+
--space-xl: 2rem;
|
|
924
|
+
|
|
925
|
+
/* Layout */
|
|
926
|
+
--max-width: 800px;
|
|
927
|
+
--border-radius: 4px;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
[data-theme="dark"] {
|
|
931
|
+
--color-bg: #1a1a2e;
|
|
932
|
+
--color-bg-secondary: #16213e;
|
|
933
|
+
--color-text: #e8e8e8;
|
|
934
|
+
--color-text-muted: #a0a0a0;
|
|
935
|
+
--color-border: #3a3a5e;
|
|
936
|
+
--color-link: #66b3ff;
|
|
937
|
+
--color-link-hover: #99ccff;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/* Reset */
|
|
941
|
+
*, *::before, *::after {
|
|
942
|
+
box-sizing: border-box;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
body {
|
|
946
|
+
margin: 0;
|
|
947
|
+
padding: 0;
|
|
948
|
+
font-family: var(--font-sans);
|
|
949
|
+
font-size: var(--font-size-base);
|
|
950
|
+
line-height: var(--line-height);
|
|
951
|
+
color: var(--color-text);
|
|
952
|
+
background: var(--color-bg);
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/* Container */
|
|
956
|
+
.container {
|
|
957
|
+
max-width: var(--max-width);
|
|
958
|
+
margin: 0 auto;
|
|
959
|
+
padding: var(--space-lg);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
/* Typography */
|
|
963
|
+
h1, h2, h3, h4, h5, h6 {
|
|
964
|
+
margin-top: var(--space-xl);
|
|
965
|
+
margin-bottom: var(--space-md);
|
|
966
|
+
line-height: 1.3;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
h1 { font-size: 2rem; }
|
|
970
|
+
h2 { font-size: 1.5rem; border-bottom: 1px solid var(--color-border); padding-bottom: var(--space-sm); }
|
|
971
|
+
h3 { font-size: 1.25rem; }
|
|
972
|
+
|
|
973
|
+
p { margin: var(--space-md) 0; }
|
|
974
|
+
|
|
975
|
+
a {
|
|
976
|
+
color: var(--color-link);
|
|
977
|
+
text-decoration: none;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
a:hover {
|
|
981
|
+
color: var(--color-link-hover);
|
|
982
|
+
text-decoration: underline;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/* Code */
|
|
986
|
+
code {
|
|
987
|
+
font-family: var(--font-mono);
|
|
988
|
+
font-size: 0.9em;
|
|
989
|
+
background: var(--color-bg-secondary);
|
|
990
|
+
padding: var(--space-xs) var(--space-sm);
|
|
991
|
+
border-radius: var(--border-radius);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
pre {
|
|
995
|
+
background: var(--color-bg-secondary);
|
|
996
|
+
padding: var(--space-md);
|
|
997
|
+
border-radius: var(--border-radius);
|
|
998
|
+
overflow-x: auto;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
pre code {
|
|
1002
|
+
background: none;
|
|
1003
|
+
padding: 0;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
/* Tables */
|
|
1007
|
+
table {
|
|
1008
|
+
width: 100%;
|
|
1009
|
+
border-collapse: collapse;
|
|
1010
|
+
margin: var(--space-md) 0;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
th, td {
|
|
1014
|
+
padding: var(--space-sm) var(--space-md);
|
|
1015
|
+
border: 1px solid var(--color-border);
|
|
1016
|
+
text-align: left;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
th {
|
|
1020
|
+
background: var(--color-bg-secondary);
|
|
1021
|
+
font-weight: 600;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
/* Provenance badges */
|
|
1025
|
+
code[class*="from"] {
|
|
1026
|
+
font-size: 0.75em;
|
|
1027
|
+
color: var(--color-text-muted);
|
|
1028
|
+
background: transparent;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/* Blockquotes (for warnings/notes) */
|
|
1032
|
+
blockquote {
|
|
1033
|
+
margin: var(--space-md) 0;
|
|
1034
|
+
padding: var(--space-md);
|
|
1035
|
+
border-left: 4px solid var(--color-warning);
|
|
1036
|
+
background: var(--color-bg-secondary);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
blockquote p:first-child { margin-top: 0; }
|
|
1040
|
+
blockquote p:last-child { margin-bottom: 0; }
|
|
1041
|
+
|
|
1042
|
+
/* Lists */
|
|
1043
|
+
ul, ol {
|
|
1044
|
+
padding-left: var(--space-lg);
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
li { margin: var(--space-sm) 0; }
|
|
1048
|
+
|
|
1049
|
+
/* Doc list (index page) */
|
|
1050
|
+
.doc-list {
|
|
1051
|
+
list-style: none;
|
|
1052
|
+
padding: 0;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.doc-list li {
|
|
1056
|
+
padding: var(--space-md);
|
|
1057
|
+
border: 1px solid var(--color-border);
|
|
1058
|
+
border-radius: var(--border-radius);
|
|
1059
|
+
margin-bottom: var(--space-md);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
.doc-list a {
|
|
1063
|
+
display: flex;
|
|
1064
|
+
align-items: center;
|
|
1065
|
+
gap: var(--space-sm);
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.doc-list .version {
|
|
1069
|
+
color: var(--color-text-muted);
|
|
1070
|
+
font-size: 0.875em;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.doc-list p {
|
|
1074
|
+
margin: var(--space-sm) 0 0;
|
|
1075
|
+
color: var(--color-text-muted);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
/* Header */
|
|
1079
|
+
header {
|
|
1080
|
+
margin-bottom: var(--space-lg);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
.breadcrumb {
|
|
1084
|
+
font-size: 0.875em;
|
|
1085
|
+
color: var(--color-text-muted);
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.breadcrumb a {
|
|
1089
|
+
color: inherit;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/* Footer */
|
|
1093
|
+
footer {
|
|
1094
|
+
margin-top: var(--space-xl);
|
|
1095
|
+
padding-top: var(--space-lg);
|
|
1096
|
+
border-top: 1px solid var(--color-border);
|
|
1097
|
+
font-size: 0.875em;
|
|
1098
|
+
color: var(--color-text-muted);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
.determinism code {
|
|
1102
|
+
font-size: 0.75em;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/* Theme toggle */
|
|
1106
|
+
.theme-toggle {
|
|
1107
|
+
position: fixed;
|
|
1108
|
+
top: var(--space-md);
|
|
1109
|
+
right: var(--space-md);
|
|
1110
|
+
padding: var(--space-sm) var(--space-md);
|
|
1111
|
+
border: 1px solid var(--color-border);
|
|
1112
|
+
border-radius: var(--border-radius);
|
|
1113
|
+
background: var(--color-bg);
|
|
1114
|
+
cursor: pointer;
|
|
1115
|
+
font-size: 0.875em;
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
/* Responsive */
|
|
1119
|
+
@media (max-width: 600px) {
|
|
1120
|
+
.container { padding: var(--space-md); }
|
|
1121
|
+
h1 { font-size: 1.5rem; }
|
|
1122
|
+
h2 { font-size: 1.25rem; }
|
|
1123
|
+
table { font-size: 0.875em; }
|
|
1124
|
+
}
|
|
1125
|
+
`;
|
|
1126
|
+
}
|
|
1127
|
+
function getThemeScript() {
|
|
1128
|
+
return `
|
|
1129
|
+
// Theme toggle
|
|
1130
|
+
const toggle = document.createElement('button');
|
|
1131
|
+
toggle.className = 'theme-toggle';
|
|
1132
|
+
toggle.textContent = '\u{1F319}';
|
|
1133
|
+
toggle.onclick = () => {
|
|
1134
|
+
const html = document.documentElement;
|
|
1135
|
+
const isDark = html.dataset.theme === 'dark';
|
|
1136
|
+
html.dataset.theme = isDark ? 'light' : 'dark';
|
|
1137
|
+
toggle.textContent = isDark ? '\u{1F319}' : '\u2600\uFE0F';
|
|
1138
|
+
localStorage.setItem('theme', html.dataset.theme);
|
|
1139
|
+
};
|
|
1140
|
+
document.body.appendChild(toggle);
|
|
1141
|
+
|
|
1142
|
+
// Restore saved theme
|
|
1143
|
+
const saved = localStorage.getItem('theme');
|
|
1144
|
+
if (saved) {
|
|
1145
|
+
document.documentElement.dataset.theme = saved;
|
|
1146
|
+
toggle.textContent = saved === 'dark' ? '\u2600\uFE0F' : '\u{1F319}';
|
|
1147
|
+
} else if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
1148
|
+
document.documentElement.dataset.theme = 'dark';
|
|
1149
|
+
toggle.textContent = '\u2600\uFE0F';
|
|
1150
|
+
}
|
|
1151
|
+
`;
|
|
1152
|
+
}
|
|
1153
|
+
export {
|
|
1154
|
+
KNOWN_CAPABILITIES,
|
|
1155
|
+
SAFETY_AFFECTING_PATTERNS,
|
|
1156
|
+
buildDocIR,
|
|
1157
|
+
computeDeterminismKey,
|
|
1158
|
+
enrichSchemaSection,
|
|
1159
|
+
formatValidationResult,
|
|
1160
|
+
introspectMcpServer,
|
|
1161
|
+
introspectionToPartialIR,
|
|
1162
|
+
isKnownCapability,
|
|
1163
|
+
isSafetyAffecting,
|
|
1164
|
+
mergeDocIR,
|
|
1165
|
+
parseIntrospectTarget,
|
|
1166
|
+
parseManifest,
|
|
1167
|
+
parseSchema,
|
|
1168
|
+
renderHTML,
|
|
1169
|
+
renderIndexHTML,
|
|
1170
|
+
renderMarkdown,
|
|
1171
|
+
validateDocIR
|
|
1172
|
+
};
|
|
1173
|
+
//# sourceMappingURL=index.js.map
|