@agentskit/doc-bridge 1.0.2 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/CONTRIBUTING.md +7 -0
- package/README.md +81 -15
- package/SECURITY.md +1 -1
- package/action.yml +2 -2
- package/dist/cli/program.js +1241 -309
- package/dist/cli/program.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.js +338 -13
- package/dist/config/index.js.map +1 -1
- package/dist/{index-CPUJbTbg.d.ts → index-DGI9TBLE.d.ts} +906 -11
- package/dist/index.d.ts +65 -11
- package/dist/index.js +1084 -171
- package/dist/index.js.map +1 -1
- package/docs/RELEASE.md +19 -21
- package/docs/getting-started.md +27 -2
- package/docs/landing/assets/doc-bridge-hero.webp +0 -0
- package/docs/landing/assets/doc-bridge-surfaces.webp +0 -0
- package/docs/landing/assets/doc-bridge-two-way.webp +0 -0
- package/docs/landing/index.html +70 -10
- package/docs/playbook/doc-bridge-pattern.md +2 -2
- package/docs/recipes/index-pipeline.md +2 -2
- package/docs/schemas/memory-candidate-v1.md +10 -1
- package/docs/spec/cli.md +1 -0
- package/docs/spec/config-v1.md +51 -6
- package/docs/spec/documentation-standard-v1.md +131 -0
- package/ecosystem-claims.json +187 -0
- package/ecosystem-upstream.json +9 -0
- package/ecosystem.json +231 -0
- package/package.json +11 -1
- package/scripts/check-ecosystem-upstream.mjs +50 -0
- package/src/cli/program.ts +36 -3
- package/src/config/index.ts +7 -1
- package/src/config/load-config.ts +4 -14
- package/src/config/schema.ts +91 -0
- package/src/conformance/documentation-standard-v1.ts +502 -0
- package/src/conformance/ecosystem-contract.ts +175 -0
- package/src/gates/run-gates.ts +33 -4
- package/src/index-builder/human-adapters/core.ts +12 -5
- package/src/index-builder/human-adapters/docusaurus.ts +29 -44
- package/src/index-builder/human-adapters/index.ts +15 -3
- package/src/index-builder/scan-corpus.ts +6 -6
- package/src/index.ts +17 -0
- package/src/lib/bounded-text.ts +25 -0
- package/src/lib/paths.ts +20 -2
- package/src/lib/static-js-literal.ts +261 -0
- package/src/lib/walk.ts +23 -4
- package/src/version.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -137,7 +137,8 @@ var GatesConfigSchema = z.object({
|
|
|
137
137
|
"okf-type",
|
|
138
138
|
"docs-style",
|
|
139
139
|
"routing-currency",
|
|
140
|
-
"bootstrap-size"
|
|
140
|
+
"bootstrap-size",
|
|
141
|
+
"documentation-standard-v1"
|
|
141
142
|
])
|
|
142
143
|
).max(16).optional(),
|
|
143
144
|
exclude: z.array(
|
|
@@ -148,7 +149,8 @@ var GatesConfigSchema = z.object({
|
|
|
148
149
|
"okf-type",
|
|
149
150
|
"docs-style",
|
|
150
151
|
"routing-currency",
|
|
151
|
-
"bootstrap-size"
|
|
152
|
+
"bootstrap-size",
|
|
153
|
+
"documentation-standard-v1"
|
|
152
154
|
])
|
|
153
155
|
).max(16).optional(),
|
|
154
156
|
options: z.record(z.string(), z.unknown()).optional()
|
|
@@ -227,6 +229,69 @@ var FederationConfigSchema = z.object({
|
|
|
227
229
|
}).strict()
|
|
228
230
|
).max(16).optional()
|
|
229
231
|
}).strict();
|
|
232
|
+
var DocumentationEvidenceFileSchema = z.object({
|
|
233
|
+
path: z.string().min(1).max(512),
|
|
234
|
+
contains: z.array(z.string().min(1).max(512)).min(1).max(32)
|
|
235
|
+
}).strict();
|
|
236
|
+
var DocumentationLinkEvidenceSchema = z.object({
|
|
237
|
+
url: z.string().url().max(512),
|
|
238
|
+
paths: z.array(z.string().min(1).max(512)).min(1).max(32)
|
|
239
|
+
}).strict();
|
|
240
|
+
var DocumentationQuickstartEvidenceSchema = z.object({
|
|
241
|
+
id: z.string().regex(/^[a-z][a-z0-9-]*$/).max(128),
|
|
242
|
+
doc: z.string().min(1).max(512),
|
|
243
|
+
test: z.string().min(1).max(512),
|
|
244
|
+
command: z.string().min(1).max(512),
|
|
245
|
+
testContains: z.array(z.string().min(1).max(256)).min(1).max(16)
|
|
246
|
+
}).strict();
|
|
247
|
+
var EcosystemContractEvidenceSchema = z.object({
|
|
248
|
+
manifest: z.string().min(1).max(512),
|
|
249
|
+
claims: z.string().min(1).max(512),
|
|
250
|
+
productId: z.string().regex(/^[a-z][a-z0-9-]*$/).max(128)
|
|
251
|
+
}).strict();
|
|
252
|
+
var DocumentationStandardRuleIdSchema = z.enum([
|
|
253
|
+
"human-docs",
|
|
254
|
+
"llms-and-raw-source",
|
|
255
|
+
"agent-handoffs",
|
|
256
|
+
"contribution",
|
|
257
|
+
"metadata",
|
|
258
|
+
"cross-links",
|
|
259
|
+
"tested-quickstarts",
|
|
260
|
+
"visual-explanations",
|
|
261
|
+
"structured-diagrams"
|
|
262
|
+
]);
|
|
263
|
+
var DocumentationStandardExceptionSchema = z.object({
|
|
264
|
+
ruleId: DocumentationStandardRuleIdSchema,
|
|
265
|
+
reason: z.string().min(10).max(1024),
|
|
266
|
+
approvedBy: z.string().min(1).max(256),
|
|
267
|
+
trackingUrl: z.string().url().max(512)
|
|
268
|
+
}).strict();
|
|
269
|
+
var DocumentationStandardV1ConfigSchema = z.object({
|
|
270
|
+
rawSources: z.array(z.string().min(1).max(512)).max(64).optional(),
|
|
271
|
+
contributionPaths: z.array(z.string().min(1).max(512)).max(16).optional(),
|
|
272
|
+
metadata: z.array(DocumentationEvidenceFileSchema).max(32).optional(),
|
|
273
|
+
links: z.array(DocumentationLinkEvidenceSchema).max(64).optional(),
|
|
274
|
+
quickstarts: z.array(DocumentationQuickstartEvidenceSchema).max(32).optional(),
|
|
275
|
+
visuals: z.array(z.string().min(1).max(512)).max(64).optional(),
|
|
276
|
+
diagrams: z.array(DocumentationEvidenceFileSchema).max(32).optional(),
|
|
277
|
+
ecosystemContract: EcosystemContractEvidenceSchema.optional(),
|
|
278
|
+
exceptions: z.array(DocumentationStandardExceptionSchema).max(32).optional()
|
|
279
|
+
}).strict().superRefine((value, context) => {
|
|
280
|
+
const seen = /* @__PURE__ */ new Set();
|
|
281
|
+
for (const [index, exception] of (value.exceptions ?? []).entries()) {
|
|
282
|
+
if (seen.has(exception.ruleId)) {
|
|
283
|
+
context.addIssue({
|
|
284
|
+
code: "custom",
|
|
285
|
+
path: ["exceptions", index, "ruleId"],
|
|
286
|
+
message: `Duplicate exception for rule ${exception.ruleId}`
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
seen.add(exception.ruleId);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
var ConformanceConfigSchema = z.object({
|
|
293
|
+
documentationStandardV1: DocumentationStandardV1ConfigSchema.optional()
|
|
294
|
+
}).strict();
|
|
230
295
|
var DocBridgeConfigV1Schema = z.object({
|
|
231
296
|
schemaVersion: z.literal(CONFIG_SCHEMA_VERSION),
|
|
232
297
|
project: z.object({
|
|
@@ -242,7 +307,8 @@ var DocBridgeConfigV1Schema = z.object({
|
|
|
242
307
|
gates: GatesConfigSchema.optional(),
|
|
243
308
|
surfaces: SurfacesConfigSchema.optional(),
|
|
244
309
|
intelligence: IntelligenceConfigSchema.optional(),
|
|
245
|
-
federation: FederationConfigSchema.optional()
|
|
310
|
+
federation: FederationConfigSchema.optional(),
|
|
311
|
+
conformance: ConformanceConfigSchema.optional()
|
|
246
312
|
}).strict();
|
|
247
313
|
|
|
248
314
|
// src/config/define-config.ts
|
|
@@ -251,7 +317,268 @@ var defineConfig = (config) => applyConfigDefaults(DocBridgeConfigV1Schema.parse
|
|
|
251
317
|
// src/config/load-config.ts
|
|
252
318
|
import { existsSync, readFileSync } from "fs";
|
|
253
319
|
import { basename, dirname, join, resolve } from "path";
|
|
254
|
-
|
|
320
|
+
|
|
321
|
+
// src/lib/static-js-literal.ts
|
|
322
|
+
var stripComments = (source) => {
|
|
323
|
+
let out = "";
|
|
324
|
+
let index = 0;
|
|
325
|
+
let quote;
|
|
326
|
+
while (index < source.length) {
|
|
327
|
+
const char = source[index] ?? "";
|
|
328
|
+
const next = source[index + 1] ?? "";
|
|
329
|
+
if (quote) {
|
|
330
|
+
out += char;
|
|
331
|
+
if (char === "\\") {
|
|
332
|
+
out += next;
|
|
333
|
+
index += 2;
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
336
|
+
if (char === quote) quote = void 0;
|
|
337
|
+
index += 1;
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
341
|
+
quote = char;
|
|
342
|
+
out += char;
|
|
343
|
+
index += 1;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (char === "/" && next === "/") {
|
|
347
|
+
while (index < source.length && source[index] !== "\n") index += 1;
|
|
348
|
+
out += "\n";
|
|
349
|
+
index += 1;
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
if (char === "/" && next === "*") {
|
|
353
|
+
index += 2;
|
|
354
|
+
while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) index += 1;
|
|
355
|
+
index += 2;
|
|
356
|
+
out += " ";
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
out += char;
|
|
360
|
+
index += 1;
|
|
361
|
+
}
|
|
362
|
+
if (quote) throw new Error("Unterminated string in static JavaScript literal.");
|
|
363
|
+
return out;
|
|
364
|
+
};
|
|
365
|
+
var LiteralParser = class {
|
|
366
|
+
constructor(source) {
|
|
367
|
+
this.source = source;
|
|
368
|
+
}
|
|
369
|
+
source;
|
|
370
|
+
index = 0;
|
|
371
|
+
parseAt(index) {
|
|
372
|
+
this.index = index;
|
|
373
|
+
const value = this.value();
|
|
374
|
+
this.space();
|
|
375
|
+
return value;
|
|
376
|
+
}
|
|
377
|
+
space() {
|
|
378
|
+
while (/\s/.test(this.source[this.index] ?? "")) this.index += 1;
|
|
379
|
+
}
|
|
380
|
+
value() {
|
|
381
|
+
this.space();
|
|
382
|
+
const char = this.source[this.index];
|
|
383
|
+
if (char === "{") return this.object();
|
|
384
|
+
if (char === "[") return this.array();
|
|
385
|
+
if (char === '"' || char === "'") return this.string();
|
|
386
|
+
const word = this.word();
|
|
387
|
+
if (word === "true") return true;
|
|
388
|
+
if (word === "false") return false;
|
|
389
|
+
if (word === "null") return null;
|
|
390
|
+
if (/^-?\d+(?:\.\d+)?$/.test(word)) return Number(word);
|
|
391
|
+
throw new Error(`Unsupported dynamic sidebar expression near "${word || char || "EOF"}".`);
|
|
392
|
+
}
|
|
393
|
+
object() {
|
|
394
|
+
const result = {};
|
|
395
|
+
this.index += 1;
|
|
396
|
+
this.space();
|
|
397
|
+
while (this.source[this.index] !== "}") {
|
|
398
|
+
const key = this.source[this.index] === '"' || this.source[this.index] === "'" ? this.string() : this.word();
|
|
399
|
+
if (!key) throw new Error("Static sidebar object key is required.");
|
|
400
|
+
this.space();
|
|
401
|
+
if (this.source[this.index] !== ":") throw new Error(`Static sidebar key "${key}" requires a literal value.`);
|
|
402
|
+
this.index += 1;
|
|
403
|
+
result[key] = this.value();
|
|
404
|
+
this.space();
|
|
405
|
+
if (this.source[this.index] === ",") {
|
|
406
|
+
this.index += 1;
|
|
407
|
+
this.space();
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (this.source[this.index] !== "}") throw new Error("Expected comma or closing brace in static sidebar.");
|
|
411
|
+
}
|
|
412
|
+
this.index += 1;
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
array() {
|
|
416
|
+
const result = [];
|
|
417
|
+
this.index += 1;
|
|
418
|
+
this.space();
|
|
419
|
+
while (this.source[this.index] !== "]") {
|
|
420
|
+
result.push(this.value());
|
|
421
|
+
this.space();
|
|
422
|
+
if (this.source[this.index] === ",") {
|
|
423
|
+
this.index += 1;
|
|
424
|
+
this.space();
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (this.source[this.index] !== "]") throw new Error("Expected comma or closing bracket in static sidebar.");
|
|
428
|
+
}
|
|
429
|
+
this.index += 1;
|
|
430
|
+
return result;
|
|
431
|
+
}
|
|
432
|
+
string() {
|
|
433
|
+
const quote = this.source[this.index] ?? "";
|
|
434
|
+
this.index += 1;
|
|
435
|
+
let result = "";
|
|
436
|
+
while (this.index < this.source.length) {
|
|
437
|
+
const char = this.source[this.index] ?? "";
|
|
438
|
+
if (char === quote) {
|
|
439
|
+
this.index += 1;
|
|
440
|
+
return result;
|
|
441
|
+
}
|
|
442
|
+
if (char === "\\") {
|
|
443
|
+
const escaped = this.source[this.index + 1];
|
|
444
|
+
if (escaped === void 0) break;
|
|
445
|
+
const escapes = { n: "\n", r: "\r", t: " " };
|
|
446
|
+
result += escapes[escaped] ?? escaped;
|
|
447
|
+
this.index += 2;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
result += char;
|
|
451
|
+
this.index += 1;
|
|
452
|
+
}
|
|
453
|
+
throw new Error("Unterminated string in static sidebar.");
|
|
454
|
+
}
|
|
455
|
+
word() {
|
|
456
|
+
this.space();
|
|
457
|
+
const start = this.index;
|
|
458
|
+
while (/[A-Za-z0-9_$.-]/.test(this.source[this.index] ?? "")) this.index += 1;
|
|
459
|
+
return this.source.slice(start, this.index);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
var maskStrings = (source) => {
|
|
463
|
+
const chars = [...source];
|
|
464
|
+
let quote;
|
|
465
|
+
let previousToken;
|
|
466
|
+
const regexPrefixKeywords = /* @__PURE__ */ new Set([
|
|
467
|
+
"await",
|
|
468
|
+
"case",
|
|
469
|
+
"delete",
|
|
470
|
+
"do",
|
|
471
|
+
"else",
|
|
472
|
+
"in",
|
|
473
|
+
"instanceof",
|
|
474
|
+
"new",
|
|
475
|
+
"of",
|
|
476
|
+
"return",
|
|
477
|
+
"throw",
|
|
478
|
+
"typeof",
|
|
479
|
+
"void",
|
|
480
|
+
"yield"
|
|
481
|
+
]);
|
|
482
|
+
const canStartRegex = () => previousToken === void 0 || "=(:,[!&|?{};".includes(previousToken) || previousToken === "=>" || regexPrefixKeywords.has(previousToken);
|
|
483
|
+
for (let index = 0; index < chars.length; index += 1) {
|
|
484
|
+
const char = chars[index] ?? "";
|
|
485
|
+
if (quote) {
|
|
486
|
+
chars[index] = " ";
|
|
487
|
+
if (char === "\\") {
|
|
488
|
+
if (index + 1 < chars.length) chars[index + 1] = " ";
|
|
489
|
+
index += 1;
|
|
490
|
+
} else if (char === quote) {
|
|
491
|
+
quote = void 0;
|
|
492
|
+
previousToken = "literal";
|
|
493
|
+
}
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
497
|
+
quote = char;
|
|
498
|
+
chars[index] = " ";
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (char === "/" && canStartRegex()) {
|
|
502
|
+
chars[index] = " ";
|
|
503
|
+
let inClass = false;
|
|
504
|
+
for (index += 1; index < chars.length; index += 1) {
|
|
505
|
+
const regexChar = chars[index] ?? "";
|
|
506
|
+
chars[index] = " ";
|
|
507
|
+
if (regexChar === "\\") {
|
|
508
|
+
if (index + 1 < chars.length) chars[index + 1] = " ";
|
|
509
|
+
index += 1;
|
|
510
|
+
} else if (regexChar === "[") {
|
|
511
|
+
inClass = true;
|
|
512
|
+
} else if (regexChar === "]") {
|
|
513
|
+
inClass = false;
|
|
514
|
+
} else if (regexChar === "/" && !inClass) {
|
|
515
|
+
while (/[A-Za-z]/.test(chars[index + 1] ?? "")) {
|
|
516
|
+
index += 1;
|
|
517
|
+
chars[index] = " ";
|
|
518
|
+
}
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
previousToken = "literal";
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
if (/[A-Za-z_$]/.test(char)) {
|
|
526
|
+
const start = index;
|
|
527
|
+
while (/[A-Za-z0-9_$]/.test(chars[index + 1] ?? "")) index += 1;
|
|
528
|
+
previousToken = chars.slice(start, index + 1).join("");
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
531
|
+
if (char === "=" && chars[index + 1] === ">") {
|
|
532
|
+
previousToken = "=>";
|
|
533
|
+
index += 1;
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
if (!/\s/.test(char)) previousToken = char;
|
|
537
|
+
}
|
|
538
|
+
return chars.join("");
|
|
539
|
+
};
|
|
540
|
+
var nonSpace = (source, start) => {
|
|
541
|
+
let index = start;
|
|
542
|
+
while (/\s/.test(source[index] ?? "")) index += 1;
|
|
543
|
+
return index;
|
|
544
|
+
};
|
|
545
|
+
var escapedRegex = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
546
|
+
var parseStaticJsObject = (source) => {
|
|
547
|
+
const clean = stripComments(source);
|
|
548
|
+
const mask = maskStrings(clean);
|
|
549
|
+
const parseBinding = (identifier) => {
|
|
550
|
+
const declaration = new RegExp(`\\b(?:const|let|var)\\s+${escapedRegex(identifier)}(?:\\s*:[^=;]+)?\\s*=`).exec(mask);
|
|
551
|
+
if (!declaration) throw new Error(`Exported binding "${identifier}" must reference a static object declaration.`);
|
|
552
|
+
const start = nonSpace(mask, declaration.index + declaration[0].length);
|
|
553
|
+
if (mask[start] !== "{") throw new Error(`Exported binding "${identifier}" must be a static object literal.`);
|
|
554
|
+
return new LiteralParser(clean).parseAt(start);
|
|
555
|
+
};
|
|
556
|
+
const commonJs = /\bmodule\.exports\s*=/.exec(mask);
|
|
557
|
+
if (commonJs) {
|
|
558
|
+
const start = nonSpace(mask, commonJs.index + commonJs[0].length);
|
|
559
|
+
if (mask[start] === "{") return new LiteralParser(clean).parseAt(start);
|
|
560
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(mask.slice(start))?.[0];
|
|
561
|
+
if (identifier) return parseBinding(identifier);
|
|
562
|
+
throw new Error("module.exports must be assigned a static object literal or binding.");
|
|
563
|
+
}
|
|
564
|
+
const exported = /\bexport\s+default\b/.exec(mask);
|
|
565
|
+
if (exported) {
|
|
566
|
+
let start = nonSpace(mask, exported.index + exported[0].length);
|
|
567
|
+
if (mask.slice(start).startsWith("defineConfig")) {
|
|
568
|
+
start = nonSpace(mask, start + "defineConfig".length);
|
|
569
|
+
if (mask[start] !== "(") throw new Error("defineConfig must be called with a static object literal.");
|
|
570
|
+
start = nonSpace(mask, start + 1);
|
|
571
|
+
if (mask[start] !== "{") throw new Error("defineConfig must be called with a static object literal.");
|
|
572
|
+
return new LiteralParser(clean).parseAt(start);
|
|
573
|
+
}
|
|
574
|
+
if (mask[start] === "{") return new LiteralParser(clean).parseAt(start);
|
|
575
|
+
const identifier = /^[A-Za-z_$][\w$]*/.exec(mask.slice(start))?.[0];
|
|
576
|
+
if (identifier) return parseBinding(identifier);
|
|
577
|
+
}
|
|
578
|
+
throw new Error("Docusaurus sidebar must export a static object or assign one to a const.");
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
// src/config/load-config.ts
|
|
255
582
|
var CONFIG_CANDIDATES = [
|
|
256
583
|
"doc-bridge.config.ts",
|
|
257
584
|
"doc-bridge.config.mts",
|
|
@@ -295,22 +622,16 @@ var parseJsonConfig = (raw, path) => {
|
|
|
295
622
|
}
|
|
296
623
|
};
|
|
297
624
|
var parseCodeConfig = (raw, path) => {
|
|
298
|
-
const
|
|
299
|
-
if (/\bimport\b/.test(
|
|
625
|
+
const unsupportedImports = raw.replace(/import\s+type\s+[\s\S]*?;?\n/g, "").replace(/import\s+\{\s*defineConfig\s*\}\s+from\s+['"]@agentskit\/doc-bridge(?:\/config)?['"];?\n?/g, "");
|
|
626
|
+
if (/\bimport\b/.test(unsupportedImports)) {
|
|
300
627
|
throw new Error(`Unsupported import in ${path}. Static config only supports defineConfig imports.`);
|
|
301
628
|
}
|
|
302
|
-
const sandbox = {
|
|
303
|
-
module: { exports: {} },
|
|
304
|
-
exports: {},
|
|
305
|
-
defineConfig: (config) => config
|
|
306
|
-
};
|
|
307
629
|
try {
|
|
308
|
-
|
|
630
|
+
return parseStaticJsObject(raw);
|
|
309
631
|
} catch (error) {
|
|
310
632
|
const message = error instanceof Error ? error.message : String(error);
|
|
311
633
|
throw new Error(`Failed to load config at ${path}: ${message}`);
|
|
312
634
|
}
|
|
313
|
-
return sandbox.module.exports.default;
|
|
314
635
|
};
|
|
315
636
|
var parseConfig = (input) => {
|
|
316
637
|
const result = DocBridgeConfigV1Schema.safeParse(input);
|
|
@@ -707,23 +1028,33 @@ ${zodIssues(result.error).map((i) => ` - ${i.path}: ${i.message}`).join("\n")}`
|
|
|
707
1028
|
};
|
|
708
1029
|
|
|
709
1030
|
// src/index-builder/build-index.ts
|
|
710
|
-
import { mkdirSync, readFileSync as
|
|
711
|
-
import { dirname as dirname3, join as
|
|
1031
|
+
import { mkdirSync, readFileSync as readFileSync6, writeFileSync } from "fs";
|
|
1032
|
+
import { dirname as dirname3, join as join7 } from "path";
|
|
712
1033
|
|
|
713
1034
|
// src/lib/paths.ts
|
|
714
|
-
import {
|
|
1035
|
+
import { existsSync as existsSync2, realpathSync } from "fs";
|
|
1036
|
+
import { isAbsolute, relative, resolve as resolve2, sep } from "path";
|
|
715
1037
|
var toPosix = (value) => value.split("\\").join("/");
|
|
1038
|
+
var containedProjectPath = (root, path) => {
|
|
1039
|
+
const projectRoot = realpathSync.native(resolve2(root));
|
|
1040
|
+
const unresolved = resolve2(projectRoot, path);
|
|
1041
|
+
const unresolvedRelative = relative(projectRoot, unresolved);
|
|
1042
|
+
if (isAbsolute(unresolvedRelative) || unresolvedRelative === ".." || unresolvedRelative.startsWith(`..${sep}`)) return void 0;
|
|
1043
|
+
const canonical = existsSync2(unresolved) ? realpathSync.native(unresolved) : unresolved;
|
|
1044
|
+
const canonicalRelative = relative(projectRoot, canonical);
|
|
1045
|
+
return isAbsolute(canonicalRelative) || canonicalRelative === ".." || canonicalRelative.startsWith(`..${sep}`) ? void 0 : canonical;
|
|
1046
|
+
};
|
|
716
1047
|
|
|
717
1048
|
// src/lib/package-manager.ts
|
|
718
|
-
import { existsSync as
|
|
1049
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
719
1050
|
import { join as join2 } from "path";
|
|
720
1051
|
var detectPackageManager = (root) => {
|
|
721
|
-
if (
|
|
1052
|
+
if (existsSync3(join2(root, "pnpm-lock.yaml")) || existsSync3(join2(root, "pnpm-workspace.yaml"))) {
|
|
722
1053
|
return "pnpm";
|
|
723
1054
|
}
|
|
724
|
-
if (
|
|
725
|
-
if (
|
|
726
|
-
if (
|
|
1055
|
+
if (existsSync3(join2(root, "yarn.lock"))) return "yarn";
|
|
1056
|
+
if (existsSync3(join2(root, "bun.lockb")) || existsSync3(join2(root, "bun.lock"))) return "bun";
|
|
1057
|
+
if (existsSync3(join2(root, "package-lock.json"))) return "npm";
|
|
727
1058
|
try {
|
|
728
1059
|
const pkg = JSON.parse(readFileSync2(join2(root, "package.json"), "utf8"));
|
|
729
1060
|
const pm = pkg.packageManager?.split("@")[0];
|
|
@@ -744,7 +1075,7 @@ var defaultChecksForTarget = (root, opts) => {
|
|
|
744
1075
|
} catch {
|
|
745
1076
|
}
|
|
746
1077
|
}
|
|
747
|
-
const isWorkspace =
|
|
1078
|
+
const isWorkspace = existsSync3(join2(root, "pnpm-workspace.yaml")) || existsSync3(join2(root, "lerna.json")) || Boolean(
|
|
748
1079
|
(() => {
|
|
749
1080
|
try {
|
|
750
1081
|
const pkg = JSON.parse(readFileSync2(join2(root, "package.json"), "utf8"));
|
|
@@ -778,9 +1109,24 @@ var defaultChecksForTarget = (root, opts) => {
|
|
|
778
1109
|
return opts.strict ? ["npm test", "npm run lint"] : ["npm test"];
|
|
779
1110
|
};
|
|
780
1111
|
|
|
781
|
-
// src/
|
|
782
|
-
import { readFileSync as readFileSync3 } from "fs";
|
|
783
|
-
|
|
1112
|
+
// src/lib/bounded-text.ts
|
|
1113
|
+
import { readFileSync as readFileSync3, statSync } from "fs";
|
|
1114
|
+
var MAX_DOCUMENT_BYTES = 4 * 1024 * 1024;
|
|
1115
|
+
var MAX_CORPUS_BYTES = 64 * 1024 * 1024;
|
|
1116
|
+
var readBoundedText = (path, budget, limits) => {
|
|
1117
|
+
const maxFileBytes = limits?.maxFileBytes ?? MAX_DOCUMENT_BYTES;
|
|
1118
|
+
const maxCorpusBytes = limits?.maxCorpusBytes ?? MAX_CORPUS_BYTES;
|
|
1119
|
+
const stat = statSync(path);
|
|
1120
|
+
if (!stat.isFile()) throw new Error(`Documentation path is not a regular file: ${path}`);
|
|
1121
|
+
if (stat.size > maxFileBytes) {
|
|
1122
|
+
throw new Error(`Documentation file exceeds the ${maxFileBytes} byte limit: ${path}`);
|
|
1123
|
+
}
|
|
1124
|
+
if (budget.used + stat.size > maxCorpusBytes) {
|
|
1125
|
+
throw new Error(`Documentation corpus exceeds the ${maxCorpusBytes} byte read budget.`);
|
|
1126
|
+
}
|
|
1127
|
+
budget.used += stat.size;
|
|
1128
|
+
return readFileSync3(path, "utf8");
|
|
1129
|
+
};
|
|
784
1130
|
|
|
785
1131
|
// src/lib/markdown.ts
|
|
786
1132
|
var parseFrontmatter = (markdown) => {
|
|
@@ -871,14 +1217,24 @@ var slugFromPath = (relPath) => {
|
|
|
871
1217
|
};
|
|
872
1218
|
|
|
873
1219
|
// src/lib/walk.ts
|
|
874
|
-
import { readdirSync,
|
|
1220
|
+
import { lstatSync, readdirSync, realpathSync as realpathSync2 } from "fs";
|
|
875
1221
|
import { join as join3 } from "path";
|
|
876
1222
|
var DEFAULT_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "coverage", ".doc-bridge"]);
|
|
1223
|
+
var DEFAULT_MAX_FILES = 1e4;
|
|
877
1224
|
var walkFiles = (root, opts) => {
|
|
878
1225
|
const extensions = opts?.extensions ?? [".md"];
|
|
879
1226
|
const skip = opts?.skipDirs ?? DEFAULT_SKIP;
|
|
880
1227
|
const out = [];
|
|
1228
|
+
const visited = /* @__PURE__ */ new Set();
|
|
881
1229
|
const visit = (dir) => {
|
|
1230
|
+
let canonicalDir;
|
|
1231
|
+
try {
|
|
1232
|
+
canonicalDir = realpathSync2.native(dir);
|
|
1233
|
+
} catch {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
if (visited.has(canonicalDir)) return;
|
|
1237
|
+
visited.add(canonicalDir);
|
|
882
1238
|
let entries = [];
|
|
883
1239
|
try {
|
|
884
1240
|
entries = readdirSync(dir);
|
|
@@ -889,10 +1245,11 @@ var walkFiles = (root, opts) => {
|
|
|
889
1245
|
const abs = join3(dir, name);
|
|
890
1246
|
let st;
|
|
891
1247
|
try {
|
|
892
|
-
st =
|
|
1248
|
+
st = lstatSync(abs);
|
|
893
1249
|
} catch {
|
|
894
1250
|
continue;
|
|
895
1251
|
}
|
|
1252
|
+
if (st.isSymbolicLink()) continue;
|
|
896
1253
|
if (st.isDirectory()) {
|
|
897
1254
|
if (skip.has(name)) continue;
|
|
898
1255
|
visit(abs);
|
|
@@ -900,6 +1257,9 @@ var walkFiles = (root, opts) => {
|
|
|
900
1257
|
}
|
|
901
1258
|
if (!st.isFile()) continue;
|
|
902
1259
|
if (extensions.some((ext) => name.endsWith(ext))) {
|
|
1260
|
+
if (out.length >= (opts?.maxFiles ?? DEFAULT_MAX_FILES)) {
|
|
1261
|
+
throw new Error(`Documentation corpus exceeds the ${opts?.maxFiles ?? DEFAULT_MAX_FILES} file limit.`);
|
|
1262
|
+
}
|
|
903
1263
|
out.push(toPosix(abs));
|
|
904
1264
|
}
|
|
905
1265
|
}
|
|
@@ -910,13 +1270,15 @@ var walkFiles = (root, opts) => {
|
|
|
910
1270
|
|
|
911
1271
|
// src/index-builder/scan-corpus.ts
|
|
912
1272
|
var scanAgentCorpus = (root, config) => {
|
|
913
|
-
const agentRoot =
|
|
1273
|
+
const agentRoot = containedProjectPath(root, config.corpus.agent.root);
|
|
1274
|
+
if (!agentRoot) throw new Error("Agent corpus root escapes the project root.");
|
|
914
1275
|
const files = walkFiles(agentRoot, { extensions: [".md", ".mdx"] });
|
|
915
1276
|
const corpusRelRoot = toPosix(config.corpus.agent.root);
|
|
1277
|
+
const budget = { used: 0 };
|
|
916
1278
|
return files.map((abs) => {
|
|
917
1279
|
const relToCorpus = toPosix(abs.replace(`${toPosix(agentRoot)}/`, ""));
|
|
918
1280
|
const relPath = `${corpusRelRoot}/${relToCorpus}`;
|
|
919
|
-
const raw =
|
|
1281
|
+
const raw = readBoundedText(abs, budget);
|
|
920
1282
|
const { data: frontmatter } = parseFrontmatter(raw);
|
|
921
1283
|
const id = frontmatterString(frontmatter, "id") ?? frontmatterString(frontmatter, "package") ?? slugFromPath(relToCorpus);
|
|
922
1284
|
const title = firstHeading(raw) ?? id;
|
|
@@ -1172,7 +1534,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
|
|
|
1172
1534
|
};
|
|
1173
1535
|
|
|
1174
1536
|
// src/version.ts
|
|
1175
|
-
var PACKAGE_VERSION = "1.
|
|
1537
|
+
var PACKAGE_VERSION = "1.1.1";
|
|
1176
1538
|
|
|
1177
1539
|
// src/index-builder/capabilities.ts
|
|
1178
1540
|
var renderCapabilitiesJson = (config, index, paths) => {
|
|
@@ -1240,14 +1602,16 @@ ${lines.join("\n")}
|
|
|
1240
1602
|
`;
|
|
1241
1603
|
};
|
|
1242
1604
|
|
|
1605
|
+
// src/index-builder/human-adapters/index.ts
|
|
1606
|
+
import { realpathSync as realpathSync4 } from "fs";
|
|
1607
|
+
import { resolve as resolve4, sep as sep3 } from "path";
|
|
1608
|
+
|
|
1243
1609
|
// src/index-builder/human-adapters/docusaurus.ts
|
|
1244
|
-
import { existsSync as
|
|
1245
|
-
import { join as join6 } from "path";
|
|
1246
|
-
import vm2 from "vm";
|
|
1610
|
+
import { existsSync as existsSync4 } from "fs";
|
|
1247
1611
|
|
|
1248
1612
|
// src/index-builder/human-adapters/core.ts
|
|
1249
|
-
import {
|
|
1250
|
-
import {
|
|
1613
|
+
import { realpathSync as realpathSync3 } from "fs";
|
|
1614
|
+
import { isAbsolute as isAbsolute2, relative as relative2, resolve as resolve3, sep as sep2 } from "path";
|
|
1251
1615
|
var optionString = (options, keys) => {
|
|
1252
1616
|
for (const key of keys) {
|
|
1253
1617
|
const value = options?.[key];
|
|
@@ -1278,10 +1642,16 @@ var humanUrl = (slug2, urlPrefix) => {
|
|
|
1278
1642
|
};
|
|
1279
1643
|
var scanMarkdownDocs = (root, humanRoot, options) => {
|
|
1280
1644
|
const out = [];
|
|
1281
|
-
const
|
|
1645
|
+
const projectRoot = realpathSync3.native(resolve3(root));
|
|
1646
|
+
const absRoot = containedProjectPath(root, humanRoot);
|
|
1647
|
+
if (!absRoot) return out;
|
|
1648
|
+
const budget = { used: 0 };
|
|
1282
1649
|
for (const abs of walkFiles(absRoot, { extensions: [".md", ".mdx"] })) {
|
|
1650
|
+
const canonical = realpathSync3.native(abs);
|
|
1651
|
+
const fileRelative = relative2(projectRoot, canonical);
|
|
1652
|
+
if (isAbsolute2(fileRelative) || fileRelative === ".." || fileRelative.startsWith(`..${sep2}`)) continue;
|
|
1283
1653
|
const relToHumanRoot = toPosix(abs.replace(`${toPosix(absRoot)}/`, ""));
|
|
1284
|
-
const raw =
|
|
1654
|
+
const raw = readBoundedText(abs, budget);
|
|
1285
1655
|
if (options?.includeRelPath && !options.includeRelPath(relToHumanRoot, raw)) continue;
|
|
1286
1656
|
out.push({
|
|
1287
1657
|
id: options?.idForDoc?.(relToHumanRoot, raw) ?? docId(relToHumanRoot, raw),
|
|
@@ -1319,41 +1689,31 @@ var docusaurusRecordId = (relPath, raw) => {
|
|
|
1319
1689
|
const frontmatter = parseFrontmatter2(raw);
|
|
1320
1690
|
return frontmatter.package ?? frontmatter.module ?? docusaurusSidebarId(relPath, raw);
|
|
1321
1691
|
};
|
|
1322
|
-
var
|
|
1323
|
-
if (!existsSync3(file)) return void 0;
|
|
1324
|
-
const raw = readFileSync5(file, "utf8").replace(/import\s+type\s+[\s\S]*?;?\n/g, "").replace(/:\s*[A-Za-z0-9_.$<>{}\[\],\s]+(?=\s*=)/g, "").replace(/\s+satisfies\s+[A-Za-z0-9_.$<>{}\[\],\s]+(?=\s*(?:;|\n|$))/g, "").replace(/export\s+default/, "module.exports =");
|
|
1325
|
-
const sandbox = { module: { exports: {} }, exports: {} };
|
|
1326
|
-
vm2.runInNewContext(raw, sandbox, { timeout: 250 });
|
|
1327
|
-
return sandbox.module.exports;
|
|
1328
|
-
};
|
|
1329
|
-
var visitSidebar = (value, filter) => {
|
|
1330
|
-
if (typeof value === "string") {
|
|
1331
|
-
filter.ids.add(value);
|
|
1332
|
-
return;
|
|
1333
|
-
}
|
|
1334
|
-
if (Array.isArray(value)) {
|
|
1335
|
-
for (const item2 of value) visitSidebar(item2, filter);
|
|
1336
|
-
return;
|
|
1337
|
-
}
|
|
1338
|
-
if (!value || typeof value !== "object") return;
|
|
1339
|
-
const item = value;
|
|
1340
|
-
if ((item.type === "doc" || item.type === "ref") && typeof item.id === "string") {
|
|
1341
|
-
filter.ids.add(item.id);
|
|
1342
|
-
}
|
|
1343
|
-
if (item.type === "autogenerated" && typeof item.dirName === "string") {
|
|
1344
|
-
filter.autogenDirs.push(item.dirName);
|
|
1345
|
-
}
|
|
1346
|
-
if (Array.isArray(item.items)) visitSidebar(item.items, filter);
|
|
1347
|
-
if (item.link && typeof item.link === "object") visitSidebar(item.link, filter);
|
|
1348
|
-
for (const child of Object.values(item)) {
|
|
1349
|
-
if (Array.isArray(child)) visitSidebar(child, filter);
|
|
1350
|
-
}
|
|
1351
|
-
};
|
|
1352
|
-
var readSidebars = (root, sidebarsFile) => {
|
|
1692
|
+
var readSidebars = (sidebarsFile) => {
|
|
1353
1693
|
if (!sidebarsFile) return { enabled: false, ids: /* @__PURE__ */ new Set(), autogenDirs: [] };
|
|
1354
|
-
|
|
1694
|
+
if (!existsSync4(sidebarsFile)) return { enabled: false, ids: /* @__PURE__ */ new Set(), autogenDirs: [] };
|
|
1695
|
+
const raw = readBoundedText(sidebarsFile, { used: 0 }, { maxFileBytes: 1048576, maxCorpusBytes: 1048576 });
|
|
1355
1696
|
const filter = { ids: /* @__PURE__ */ new Set(), autogenDirs: [] };
|
|
1356
|
-
|
|
1697
|
+
const visit = (value) => {
|
|
1698
|
+
if (typeof value === "string") {
|
|
1699
|
+
filter.ids.add(value);
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1702
|
+
if (Array.isArray(value)) {
|
|
1703
|
+
for (const item2 of value) visit(item2);
|
|
1704
|
+
return;
|
|
1705
|
+
}
|
|
1706
|
+
if (!value || typeof value !== "object") return;
|
|
1707
|
+
const item = value;
|
|
1708
|
+
if ((item.type === "doc" || item.type === "ref") && typeof item.id === "string") filter.ids.add(item.id);
|
|
1709
|
+
if (item.type === "autogenerated" && typeof item.dirName === "string") filter.autogenDirs.push(item.dirName);
|
|
1710
|
+
if (Array.isArray(item.items)) visit(item.items);
|
|
1711
|
+
if (item.link && typeof item.link === "object") visit(item.link);
|
|
1712
|
+
for (const child of Object.values(item)) {
|
|
1713
|
+
if (Array.isArray(child)) visit(child);
|
|
1714
|
+
}
|
|
1715
|
+
};
|
|
1716
|
+
visit(parseStaticJsObject(raw));
|
|
1357
1717
|
return { enabled: true, ...filter };
|
|
1358
1718
|
};
|
|
1359
1719
|
var isIncludedBySidebar = (filter, id) => {
|
|
@@ -1369,7 +1729,8 @@ var docusaurusAdapter = {
|
|
|
1369
1729
|
scan: ({ root, config }) => {
|
|
1370
1730
|
const docsDir = optionString(config.options, ["docsDir", "root"]);
|
|
1371
1731
|
if (!docsDir) return [];
|
|
1372
|
-
const
|
|
1732
|
+
const sidebarsFile = optionString(config.options, ["sidebarsFile"]);
|
|
1733
|
+
const sidebarFilter = readSidebars(sidebarsFile ? containedProjectPath(root, sidebarsFile) : void 0);
|
|
1373
1734
|
return scanMarkdownDocs(root, docsDir, {
|
|
1374
1735
|
includeRelPath: (relPath, raw) => isIncludedBySidebar(sidebarFilter, docusaurusSidebarId(relPath, raw)),
|
|
1375
1736
|
idForDoc: docusaurusRecordId,
|
|
@@ -1380,13 +1741,13 @@ var docusaurusAdapter = {
|
|
|
1380
1741
|
};
|
|
1381
1742
|
|
|
1382
1743
|
// src/index-builder/human-adapters/fumadocs.ts
|
|
1383
|
-
import { existsSync as
|
|
1384
|
-
import { join as
|
|
1744
|
+
import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
|
|
1745
|
+
import { join as join4 } from "path";
|
|
1385
1746
|
var readMetaPages = (dir) => {
|
|
1386
|
-
const file =
|
|
1387
|
-
if (!
|
|
1747
|
+
const file = join4(dir, "meta.json");
|
|
1748
|
+
if (!existsSync5(file)) return void 0;
|
|
1388
1749
|
try {
|
|
1389
|
-
const meta = JSON.parse(
|
|
1750
|
+
const meta = JSON.parse(readFileSync4(file, "utf8"));
|
|
1390
1751
|
return Array.isArray(meta.pages) ? meta.pages.filter((page) => typeof page === "string") : void 0;
|
|
1391
1752
|
} catch {
|
|
1392
1753
|
return void 0;
|
|
@@ -1401,7 +1762,7 @@ var isListedByMeta = (contentRoot, relPath) => {
|
|
|
1401
1762
|
if (isDotFile(relPath)) return false;
|
|
1402
1763
|
const parts = relPath.split("/");
|
|
1403
1764
|
for (let i = 0; i < parts.length; i += 1) {
|
|
1404
|
-
const dir =
|
|
1765
|
+
const dir = join4(contentRoot, ...parts.slice(0, i));
|
|
1405
1766
|
const pages = readMetaPages(dir);
|
|
1406
1767
|
if (!pages?.length || pages.includes("...")) continue;
|
|
1407
1768
|
const key = pageKey(parts[i] ?? "");
|
|
@@ -1414,7 +1775,7 @@ var fumadocsAdapter = {
|
|
|
1414
1775
|
scan: ({ root, config }) => {
|
|
1415
1776
|
const contentDir = optionString(config.options, ["contentDir", "root"]);
|
|
1416
1777
|
if (!contentDir) return [];
|
|
1417
|
-
const contentRoot =
|
|
1778
|
+
const contentRoot = join4(root, contentDir);
|
|
1418
1779
|
const excludePrefixes = Array.isArray(config.options?.excludePrefixes) ? config.options.excludePrefixes.filter((v) => typeof v === "string") : typeof config.options?.excludePrefix === "string" ? [config.options.excludePrefix] : [];
|
|
1419
1780
|
if (!excludePrefixes.includes("for-agents")) excludePrefixes.push("for-agents");
|
|
1420
1781
|
return scanMarkdownDocs(root, contentDir, {
|
|
@@ -1450,15 +1811,23 @@ var humanConfigs = (config) => {
|
|
|
1450
1811
|
if (!human) return [];
|
|
1451
1812
|
return Array.isArray(human) ? human : [human];
|
|
1452
1813
|
};
|
|
1814
|
+
var canonicalPath = (path) => {
|
|
1815
|
+
try {
|
|
1816
|
+
return realpathSync4.native(path);
|
|
1817
|
+
} catch {
|
|
1818
|
+
return resolve4(path);
|
|
1819
|
+
}
|
|
1820
|
+
};
|
|
1453
1821
|
var scanHumanDocRecords = (root, config) => {
|
|
1454
1822
|
const out = [];
|
|
1455
1823
|
const seen = /* @__PURE__ */ new Set();
|
|
1456
|
-
const agentRoot = config.corpus.agent.root
|
|
1824
|
+
const agentRoot = canonicalPath(resolve4(root, config.corpus.agent.root));
|
|
1457
1825
|
for (const human of humanConfigs(config)) {
|
|
1458
1826
|
const adapter = ADAPTERS.find((candidate) => candidate.plugin === human.plugin);
|
|
1459
1827
|
if (!adapter) continue;
|
|
1460
1828
|
for (const record of adapter.scan({ root, config: human })) {
|
|
1461
|
-
|
|
1829
|
+
const recordPath = canonicalPath(record.path);
|
|
1830
|
+
if (recordPath === agentRoot || recordPath.startsWith(`${agentRoot}${sep3}`) || record.path.includes("/for-agents/") || record.path.endsWith("/for-agents")) {
|
|
1462
1831
|
continue;
|
|
1463
1832
|
}
|
|
1464
1833
|
if (seen.has(record.id)) continue;
|
|
@@ -1471,26 +1840,26 @@ var scanHumanDocRecords = (root, config) => {
|
|
|
1471
1840
|
var scanHumanDocs = (root, config) => Object.fromEntries(scanHumanDocRecords(root, config).map((doc) => [doc.id, doc.url]));
|
|
1472
1841
|
|
|
1473
1842
|
// src/index-builder/plugins/pnpm-monorepo.ts
|
|
1474
|
-
import { existsSync as
|
|
1475
|
-
import { basename as basename2, join as
|
|
1843
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
|
|
1844
|
+
import { basename as basename2, join as join6 } from "path";
|
|
1476
1845
|
|
|
1477
1846
|
// src/lib/glob-expand.ts
|
|
1478
|
-
import { existsSync as
|
|
1479
|
-
import { join as
|
|
1847
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1848
|
+
import { join as join5 } from "path";
|
|
1480
1849
|
var expandWorkspaceGlobs = (root, patterns) => {
|
|
1481
1850
|
const dirs = /* @__PURE__ */ new Set();
|
|
1482
1851
|
for (const pattern of patterns) {
|
|
1483
1852
|
const normalized = toPosix(pattern).replace(/\/$/, "");
|
|
1484
1853
|
if (!normalized.includes("*")) {
|
|
1485
|
-
const abs =
|
|
1486
|
-
if (
|
|
1854
|
+
const abs = join5(root, normalized);
|
|
1855
|
+
if (existsSync6(abs)) dirs.add(toPosix(abs));
|
|
1487
1856
|
continue;
|
|
1488
1857
|
}
|
|
1489
1858
|
const star = normalized.indexOf("*");
|
|
1490
|
-
const base =
|
|
1491
|
-
if (!
|
|
1859
|
+
const base = join5(root, normalized.slice(0, star).replace(/\/$/, ""));
|
|
1860
|
+
if (!existsSync6(base)) continue;
|
|
1492
1861
|
for (const name of readdirSync2(base)) {
|
|
1493
|
-
const abs =
|
|
1862
|
+
const abs = join5(base, name);
|
|
1494
1863
|
try {
|
|
1495
1864
|
if (statSync2(abs).isDirectory()) dirs.add(toPosix(abs));
|
|
1496
1865
|
} catch {
|
|
@@ -1522,10 +1891,10 @@ var parsePnpmWorkspace = (yaml) => {
|
|
|
1522
1891
|
return patterns;
|
|
1523
1892
|
};
|
|
1524
1893
|
var readPackageJson = (dir) => {
|
|
1525
|
-
const file =
|
|
1526
|
-
if (!
|
|
1894
|
+
const file = join6(dir, "package.json");
|
|
1895
|
+
if (!existsSync7(file)) return null;
|
|
1527
1896
|
try {
|
|
1528
|
-
return JSON.parse(
|
|
1897
|
+
return JSON.parse(readFileSync5(file, "utf8"));
|
|
1529
1898
|
} catch {
|
|
1530
1899
|
return null;
|
|
1531
1900
|
}
|
|
@@ -1534,9 +1903,9 @@ var discoverPnpmPackages = (root, config) => {
|
|
|
1534
1903
|
const explicit = config.routing?.options?.packages;
|
|
1535
1904
|
let patterns = explicit;
|
|
1536
1905
|
if (!patterns?.length) {
|
|
1537
|
-
const workspaceFile =
|
|
1538
|
-
if (
|
|
1539
|
-
patterns = parsePnpmWorkspace(
|
|
1906
|
+
const workspaceFile = join6(root, "pnpm-workspace.yaml");
|
|
1907
|
+
if (existsSync7(workspaceFile)) {
|
|
1908
|
+
patterns = parsePnpmWorkspace(readFileSync5(workspaceFile, "utf8"));
|
|
1540
1909
|
}
|
|
1541
1910
|
}
|
|
1542
1911
|
if (!patterns?.length) return [];
|
|
@@ -1557,7 +1926,7 @@ var discoverPnpmPackages = (root, config) => {
|
|
|
1557
1926
|
var projectName = (root, config) => {
|
|
1558
1927
|
if (config.project?.name) return config.project.name;
|
|
1559
1928
|
try {
|
|
1560
|
-
const pkg = JSON.parse(
|
|
1929
|
+
const pkg = JSON.parse(readFileSync6(join7(root, "package.json"), "utf8"));
|
|
1561
1930
|
if (pkg.name) return pkg.name;
|
|
1562
1931
|
} catch {
|
|
1563
1932
|
}
|
|
@@ -1565,7 +1934,7 @@ var projectName = (root, config) => {
|
|
|
1565
1934
|
};
|
|
1566
1935
|
var existingGeneratedAt = (indexPath, contentHash) => {
|
|
1567
1936
|
try {
|
|
1568
|
-
const index = JSON.parse(
|
|
1937
|
+
const index = JSON.parse(readFileSync6(indexPath, "utf8"));
|
|
1569
1938
|
return index.contentHash === contentHash && typeof index.generatedAt === "string" ? index.generatedAt : void 0;
|
|
1570
1939
|
} catch {
|
|
1571
1940
|
return void 0;
|
|
@@ -1576,7 +1945,7 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
1576
1945
|
const config = opts.config;
|
|
1577
1946
|
const write = opts.write ?? true;
|
|
1578
1947
|
const outFile = config.index?.outFile ?? ".doc-bridge/index.json";
|
|
1579
|
-
const indexPath =
|
|
1948
|
+
const indexPath = join7(root, outFile);
|
|
1580
1949
|
const corpus = scanAgentCorpus(root, config);
|
|
1581
1950
|
const knowledge = corpus.map(({ absPath: _a, relPath: _r, frontmatter: _f, ...entry }) => entry);
|
|
1582
1951
|
const shouldDiscover = config.routing?.plugin === "pnpm-monorepo" || Boolean(config.routing?.options?.packages?.length) || config.routing?.plugin === "npm-workspaces" || config.routing?.plugin === "yarn-workspaces";
|
|
@@ -1611,7 +1980,7 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
1611
1980
|
if (config.index?.llmsTxt?.enabled !== false) {
|
|
1612
1981
|
const llmsOut = config.index?.llmsTxt?.outFile ?? "llms.txt";
|
|
1613
1982
|
llmsTxtRelPath = toPosix(llmsOut);
|
|
1614
|
-
llmsTxtPath =
|
|
1983
|
+
llmsTxtPath = join7(root, llmsOut);
|
|
1615
1984
|
if (write) {
|
|
1616
1985
|
writeFileSync(
|
|
1617
1986
|
llmsTxtPath,
|
|
@@ -1623,7 +1992,7 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
1623
1992
|
let capabilitiesPath;
|
|
1624
1993
|
if (config.index?.capabilities?.enabled !== false) {
|
|
1625
1994
|
const capabilitiesOut = config.index?.capabilities?.outFile ?? ".doc-bridge/capabilities.json";
|
|
1626
|
-
capabilitiesPath =
|
|
1995
|
+
capabilitiesPath = join7(root, capabilitiesOut);
|
|
1627
1996
|
if (write) {
|
|
1628
1997
|
mkdirSync(dirname3(capabilitiesPath), { recursive: true });
|
|
1629
1998
|
writeFileSync(
|
|
@@ -1645,11 +2014,531 @@ var buildDocBridgeIndex = (opts) => {
|
|
|
1645
2014
|
};
|
|
1646
2015
|
|
|
1647
2016
|
// src/gates/run-gates.ts
|
|
1648
|
-
import { readFileSync as
|
|
2017
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
2018
|
+
|
|
2019
|
+
// src/conformance/documentation-standard-v1.ts
|
|
2020
|
+
import { existsSync as existsSync8, readFileSync as readFileSync7, realpathSync as realpathSync5, statSync as statSync3 } from "fs";
|
|
2021
|
+
import { isAbsolute as isAbsolute3, relative as relative3, resolve as resolve5, sep as sep4 } from "path";
|
|
2022
|
+
|
|
2023
|
+
// src/conformance/ecosystem-contract.ts
|
|
2024
|
+
import { z as z5 } from "zod";
|
|
2025
|
+
var NonEmptyStringSchema = z5.string().refine((value) => value.trim().length > 0, "must be non-empty");
|
|
2026
|
+
var HttpsUrlSchema = z5.string().url().refine((value) => value.startsWith("https://"), "must use https");
|
|
2027
|
+
var RepoSchema = z5.string().regex(/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/);
|
|
2028
|
+
var SlugSchema = z5.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
2029
|
+
var SurfaceSchema = z5.object({
|
|
2030
|
+
home: HttpsUrlSchema.optional(),
|
|
2031
|
+
docs: HttpsUrlSchema.optional(),
|
|
2032
|
+
llms: HttpsUrlSchema.optional(),
|
|
2033
|
+
stats: HttpsUrlSchema.optional(),
|
|
2034
|
+
documentation: z5.enum(["fumadocs", "repository"]),
|
|
2035
|
+
chat: z5.enum(["agentschat", "custom", "none"])
|
|
2036
|
+
}).passthrough();
|
|
2037
|
+
var ProductSchema = z5.object({
|
|
2038
|
+
id: SlugSchema,
|
|
2039
|
+
name: NonEmptyStringSchema,
|
|
2040
|
+
shortName: NonEmptyStringSchema,
|
|
2041
|
+
kind: NonEmptyStringSchema,
|
|
2042
|
+
role: NonEmptyStringSchema,
|
|
2043
|
+
promise: NonEmptyStringSchema,
|
|
2044
|
+
maturity: z5.enum(["planning", "alpha", "beta", "stable", "deprecated"]),
|
|
2045
|
+
repo: RepoSchema,
|
|
2046
|
+
accent: z5.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2047
|
+
surfaces: SurfaceSchema,
|
|
2048
|
+
navigation: z5.object({
|
|
2049
|
+
showInBar: z5.boolean(),
|
|
2050
|
+
order: z5.number().int().nonnegative().optional(),
|
|
2051
|
+
next: z5.array(SlugSchema)
|
|
2052
|
+
}).passthrough()
|
|
2053
|
+
}).passthrough();
|
|
2054
|
+
var LegacyPropertySchema = z5.object({
|
|
2055
|
+
id: SlugSchema,
|
|
2056
|
+
name: NonEmptyStringSchema,
|
|
2057
|
+
barLabel: NonEmptyStringSchema,
|
|
2058
|
+
domain: NonEmptyStringSchema,
|
|
2059
|
+
url: HttpsUrlSchema,
|
|
2060
|
+
repo: RepoSchema,
|
|
2061
|
+
tagline: NonEmptyStringSchema,
|
|
2062
|
+
kind: NonEmptyStringSchema,
|
|
2063
|
+
accent: z5.string().regex(/^#[0-9A-Fa-f]{6}$/),
|
|
2064
|
+
llms: HttpsUrlSchema.optional(),
|
|
2065
|
+
stats: HttpsUrlSchema.optional()
|
|
2066
|
+
}).passthrough();
|
|
2067
|
+
var ManifestSchema = z5.object({
|
|
2068
|
+
schemaVersion: z5.literal(2),
|
|
2069
|
+
parentBrand: z5.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema }).passthrough(),
|
|
2070
|
+
products: z5.array(ProductSchema).min(1),
|
|
2071
|
+
properties: z5.array(LegacyPropertySchema).length(4),
|
|
2072
|
+
builder: z5.object({ id: NonEmptyStringSchema, name: NonEmptyStringSchema, url: HttpsUrlSchema }).passthrough().optional()
|
|
2073
|
+
}).passthrough();
|
|
2074
|
+
var EvidenceSchema = z5.discriminatedUnion("type", [
|
|
2075
|
+
z5.object({
|
|
2076
|
+
type: z5.literal("repository-derivation"),
|
|
2077
|
+
repo: RepoSchema,
|
|
2078
|
+
path: NonEmptyStringSchema,
|
|
2079
|
+
summary: NonEmptyStringSchema
|
|
2080
|
+
}).passthrough(),
|
|
2081
|
+
z5.object({ type: z5.literal("endpoint"), url: HttpsUrlSchema, summary: NonEmptyStringSchema }).passthrough()
|
|
2082
|
+
]);
|
|
2083
|
+
var ClaimSchema = z5.object({
|
|
2084
|
+
id: NonEmptyStringSchema,
|
|
2085
|
+
value: z5.number().finite().nonnegative(),
|
|
2086
|
+
noun: NonEmptyStringSchema,
|
|
2087
|
+
conservativeFloor: z5.number().int().nonnegative().optional(),
|
|
2088
|
+
evidence: EvidenceSchema
|
|
2089
|
+
}).passthrough();
|
|
2090
|
+
var ClaimProductSchema = z5.object({
|
|
2091
|
+
productId: SlugSchema,
|
|
2092
|
+
source: z5.discriminatedUnion("type", [
|
|
2093
|
+
z5.object({ type: z5.literal("endpoint"), url: HttpsUrlSchema }).passthrough(),
|
|
2094
|
+
z5.object({ type: z5.literal("repository"), repo: RepoSchema }).passthrough()
|
|
2095
|
+
]),
|
|
2096
|
+
verification: z5.enum(["verified", "declared"]),
|
|
2097
|
+
claims: z5.array(ClaimSchema)
|
|
2098
|
+
}).passthrough();
|
|
2099
|
+
var ClaimsSchema = z5.object({
|
|
2100
|
+
schemaVersion: z5.literal(1),
|
|
2101
|
+
manifestSchemaVersion: z5.literal(2),
|
|
2102
|
+
products: z5.array(ClaimProductSchema)
|
|
2103
|
+
}).passthrough();
|
|
2104
|
+
var LEGACY_PRODUCT_IDS = ["agentskit", "akos", "playbook", "registry"];
|
|
2105
|
+
var parseCanonicalEcosystemContract = (manifestInput, claimsInput) => {
|
|
2106
|
+
const manifest = ManifestSchema.parse(manifestInput);
|
|
2107
|
+
const claims = ClaimsSchema.parse(claimsInput);
|
|
2108
|
+
const products = new Map(manifest.products.map((product) => [product.id, product]));
|
|
2109
|
+
if (products.size !== manifest.products.length) throw new Error("Manifest product IDs must be unique.");
|
|
2110
|
+
const navigationOrders = /* @__PURE__ */ new Set();
|
|
2111
|
+
for (const product of manifest.products) {
|
|
2112
|
+
if (product.surfaces.documentation === "fumadocs" && !product.surfaces.docs) {
|
|
2113
|
+
throw new Error(`Product ${product.id} requires a docs surface for Fumadocs.`);
|
|
2114
|
+
}
|
|
2115
|
+
if (product.navigation.showInBar) {
|
|
2116
|
+
if (!product.surfaces.home || product.navigation.order === void 0) {
|
|
2117
|
+
throw new Error(`Product ${product.id} requires home and order for shared navigation.`);
|
|
2118
|
+
}
|
|
2119
|
+
if (navigationOrders.has(product.navigation.order)) throw new Error("Navigation orders must be unique.");
|
|
2120
|
+
navigationOrders.add(product.navigation.order);
|
|
2121
|
+
}
|
|
2122
|
+
const next = new Set(product.navigation.next);
|
|
2123
|
+
if (next.size !== product.navigation.next.length || next.has(product.id)) {
|
|
2124
|
+
throw new Error(`Product ${product.id} has duplicate or self-referential navigation.`);
|
|
2125
|
+
}
|
|
2126
|
+
for (const nextId of next) {
|
|
2127
|
+
if (!products.has(nextId)) throw new Error(`Product ${product.id} references unknown product ${nextId}.`);
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
for (const [index, id] of LEGACY_PRODUCT_IDS.entries()) {
|
|
2131
|
+
const legacy = manifest.properties[index];
|
|
2132
|
+
const product = products.get(id);
|
|
2133
|
+
if (!legacy || !product || legacy.id !== id || !product.surfaces.home) {
|
|
2134
|
+
throw new Error(`Legacy property ${index} must project product ${id}.`);
|
|
2135
|
+
}
|
|
2136
|
+
const expected = {
|
|
2137
|
+
name: product.name,
|
|
2138
|
+
barLabel: product.shortName,
|
|
2139
|
+
domain: new URL(product.surfaces.home).host,
|
|
2140
|
+
url: product.surfaces.home,
|
|
2141
|
+
repo: product.repo,
|
|
2142
|
+
tagline: product.promise,
|
|
2143
|
+
kind: product.kind,
|
|
2144
|
+
accent: product.accent,
|
|
2145
|
+
llms: product.surfaces.llms,
|
|
2146
|
+
stats: product.surfaces.stats
|
|
2147
|
+
};
|
|
2148
|
+
for (const [key, value] of Object.entries(expected)) {
|
|
2149
|
+
if (legacy[key] !== value) throw new Error(`Legacy ${id}.${key} must match v2.`);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
const claimProducts = new Map(claims.products.map((product) => [product.productId, product]));
|
|
2153
|
+
if (claimProducts.size !== claims.products.length || claimProducts.size !== products.size) {
|
|
2154
|
+
throw new Error("Claims must include every manifest product exactly once.");
|
|
2155
|
+
}
|
|
2156
|
+
for (const [productId, product] of products) {
|
|
2157
|
+
const claimProduct = claimProducts.get(productId);
|
|
2158
|
+
if (!claimProduct) throw new Error(`Claims are missing product ${productId}.`);
|
|
2159
|
+
if (claimProduct.source.type === "endpoint") {
|
|
2160
|
+
if (claimProduct.source.url !== product.surfaces.stats) throw new Error(`Claims source for ${productId} must match stats.`);
|
|
2161
|
+
} else if (claimProduct.source.repo !== product.repo) {
|
|
2162
|
+
throw new Error(`Claims source for ${productId} must match repo.`);
|
|
2163
|
+
}
|
|
2164
|
+
if (claimProduct.verification === "declared" && claimProduct.claims.length > 0) {
|
|
2165
|
+
throw new Error(`Declared product ${productId} cannot publish claims.`);
|
|
2166
|
+
}
|
|
2167
|
+
const claimIds = /* @__PURE__ */ new Set();
|
|
2168
|
+
for (const claim of claimProduct.claims) {
|
|
2169
|
+
if (claimIds.has(claim.id)) throw new Error(`Product ${productId} has duplicate claim ${claim.id}.`);
|
|
2170
|
+
claimIds.add(claim.id);
|
|
2171
|
+
if (claim.conservativeFloor !== void 0 && claim.conservativeFloor > claim.value) {
|
|
2172
|
+
throw new Error(`Claim ${productId}:${claim.id} has a floor above its value.`);
|
|
2173
|
+
}
|
|
2174
|
+
if (claim.evidence.type === "repository-derivation" && claim.evidence.repo !== product.repo) {
|
|
2175
|
+
throw new Error(`Claim ${productId}:${claim.id} evidence must match the product repo.`);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
return { manifest, claims };
|
|
2180
|
+
};
|
|
2181
|
+
|
|
2182
|
+
// src/conformance/documentation-standard-v1.ts
|
|
2183
|
+
var DOCUMENTATION_STANDARD_V1_ID = "documentation-standard-v1";
|
|
2184
|
+
var DOCUMENTATION_STANDARD_V1_STATUS = "stable";
|
|
2185
|
+
var MAX_TEXT_EVIDENCE_BYTES = 4 * 1024 * 1024;
|
|
2186
|
+
var safePath = (root, path) => {
|
|
2187
|
+
const rootAbs = realpathSync5.native(resolve5(root));
|
|
2188
|
+
const unresolved = resolve5(rootAbs, path);
|
|
2189
|
+
const unresolvedRel = relative3(rootAbs, unresolved);
|
|
2190
|
+
if (isAbsolute3(unresolvedRel) || unresolvedRel === ".." || unresolvedRel.startsWith(`..${sep4}`)) return void 0;
|
|
2191
|
+
if (!existsSync8(unresolved)) return unresolved;
|
|
2192
|
+
try {
|
|
2193
|
+
const abs = realpathSync5.native(unresolved);
|
|
2194
|
+
const rel = relative3(rootAbs, abs);
|
|
2195
|
+
return !isAbsolute3(rel) && rel !== ".." && !rel.startsWith(`..${sep4}`) ? abs : void 0;
|
|
2196
|
+
} catch {
|
|
2197
|
+
return void 0;
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
var fileEvidence = (root, path, options) => {
|
|
2201
|
+
const abs = safePath(root, path);
|
|
2202
|
+
if (!abs) {
|
|
2203
|
+
return {
|
|
2204
|
+
exists: false,
|
|
2205
|
+
content: "",
|
|
2206
|
+
evidence: { path, detail: "Path escapes the project root." }
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
if (!existsSync8(abs)) {
|
|
2210
|
+
return { exists: false, content: "", evidence: { path, detail: "File does not exist." } };
|
|
2211
|
+
}
|
|
2212
|
+
try {
|
|
2213
|
+
const stat = statSync3(abs);
|
|
2214
|
+
if (!stat.isFile()) {
|
|
2215
|
+
return { exists: false, content: "", evidence: { path, detail: "Path is not a regular file." } };
|
|
2216
|
+
}
|
|
2217
|
+
if (stat.size === 0) {
|
|
2218
|
+
return { exists: false, content: "", evidence: { path, detail: "File is empty." } };
|
|
2219
|
+
}
|
|
2220
|
+
if (options?.readContent === false) {
|
|
2221
|
+
return {
|
|
2222
|
+
exists: true,
|
|
2223
|
+
content: "",
|
|
2224
|
+
evidence: { path: toPosix(relative3(resolve5(root), abs)) || ".", detail: "File exists and is non-empty." }
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
if (stat.size > MAX_TEXT_EVIDENCE_BYTES) {
|
|
2228
|
+
return {
|
|
2229
|
+
exists: false,
|
|
2230
|
+
content: "",
|
|
2231
|
+
evidence: { path, detail: `Text evidence exceeds ${MAX_TEXT_EVIDENCE_BYTES} bytes.` }
|
|
2232
|
+
};
|
|
2233
|
+
}
|
|
2234
|
+
const content = readFileSync7(abs, "utf8");
|
|
2235
|
+
return {
|
|
2236
|
+
exists: content.trim().length > 0,
|
|
2237
|
+
content,
|
|
2238
|
+
evidence: {
|
|
2239
|
+
path: toPosix(relative3(resolve5(root), abs)) || ".",
|
|
2240
|
+
detail: content.trim().length > 0 ? "File exists and is non-empty." : "File is empty."
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
} catch {
|
|
2244
|
+
return { exists: false, content: "", evidence: { path, detail: "File is not readable text." } };
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
var resultWithException = (draft, options) => {
|
|
2248
|
+
if (draft.passed) {
|
|
2249
|
+
const { passed: _passed2, ...result2 } = draft;
|
|
2250
|
+
return { ...result2, status: "pass", ok: true };
|
|
2251
|
+
}
|
|
2252
|
+
const exception = options.exceptions?.find((candidate) => candidate.ruleId === draft.id);
|
|
2253
|
+
const { passed: _passed, ...result } = draft;
|
|
2254
|
+
if (!exception) return { ...result, status: "fail", ok: false };
|
|
2255
|
+
return {
|
|
2256
|
+
...result,
|
|
2257
|
+
status: "excepted",
|
|
2258
|
+
ok: true,
|
|
2259
|
+
exception: {
|
|
2260
|
+
reason: exception.reason,
|
|
2261
|
+
approvedBy: exception.approvedBy,
|
|
2262
|
+
trackingUrl: exception.trackingUrl
|
|
2263
|
+
}
|
|
2264
|
+
};
|
|
2265
|
+
};
|
|
2266
|
+
var humanDocsRule = (root, config) => {
|
|
2267
|
+
const docs = scanHumanDocRecords(root, config);
|
|
2268
|
+
return {
|
|
2269
|
+
id: "human-docs",
|
|
2270
|
+
level: "required",
|
|
2271
|
+
passed: docs.length > 0,
|
|
2272
|
+
message: docs.length > 0 ? `Found ${docs.length} human document(s).` : "No human documentation was discovered.",
|
|
2273
|
+
evidence: docs.slice(0, 10).map((doc) => ({
|
|
2274
|
+
path: toPosix(relative3(resolve5(root), doc.path)),
|
|
2275
|
+
detail: `Human route: ${doc.url}`
|
|
2276
|
+
})),
|
|
2277
|
+
remediation: {
|
|
2278
|
+
command: "edit doc-bridge.config.json",
|
|
2279
|
+
detail: "Configure corpus.human with a supported adapter and a non-agent documentation root."
|
|
2280
|
+
}
|
|
2281
|
+
};
|
|
2282
|
+
};
|
|
2283
|
+
var llmsRule = (root, config, options) => {
|
|
2284
|
+
const llmsPath = config.index?.llmsTxt?.outFile ?? "llms.txt";
|
|
2285
|
+
const llmsKey = safePath(root, llmsPath) ?? resolve5(root, llmsPath);
|
|
2286
|
+
const rawSources = /* @__PURE__ */ new Map();
|
|
2287
|
+
for (const path of options.rawSources ?? []) {
|
|
2288
|
+
const key = safePath(root, path) ?? resolve5(root, path);
|
|
2289
|
+
if (key !== llmsKey && !rawSources.has(key)) rawSources.set(key, path);
|
|
2290
|
+
}
|
|
2291
|
+
const paths = [llmsPath, ...rawSources.values()];
|
|
2292
|
+
const evidence = paths.map((path) => fileEvidence(root, path));
|
|
2293
|
+
const generated = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2294
|
+
const expectedLlms = renderLlmsTxt(config, generated.knowledge, generated.project?.name ?? "project");
|
|
2295
|
+
const llmsIsFresh = evidence[0]?.content === expectedLlms;
|
|
2296
|
+
if (evidence[0]?.exists) {
|
|
2297
|
+
evidence[0] = {
|
|
2298
|
+
...evidence[0],
|
|
2299
|
+
evidence: {
|
|
2300
|
+
...evidence[0].evidence,
|
|
2301
|
+
detail: llmsIsFresh ? "File matches the deterministic ak-docs output." : "File is stale or was not generated by the current ak-docs inputs."
|
|
2302
|
+
}
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
const passed = config.index?.llmsTxt?.enabled !== false && paths.length > 1 && llmsIsFresh && evidence.every((item) => item.exists);
|
|
2306
|
+
return {
|
|
2307
|
+
id: "llms-and-raw-source",
|
|
2308
|
+
level: "required",
|
|
2309
|
+
passed,
|
|
2310
|
+
message: passed ? `Resolved llms.txt and ${paths.length - 1} raw source(s).` : "llms.txt must be enabled, current, and accompanied by at least one readable raw source.",
|
|
2311
|
+
evidence: evidence.map((item) => item.evidence),
|
|
2312
|
+
remediation: {
|
|
2313
|
+
command: "ak-docs index",
|
|
2314
|
+
detail: "Generate llms.txt and configure conformance.documentationStandardV1.rawSources."
|
|
2315
|
+
}
|
|
2316
|
+
};
|
|
2317
|
+
};
|
|
2318
|
+
var normalizedUrl = (value) => value.replace(/\/$/, "");
|
|
2319
|
+
var ecosystemContract = (root, options) => {
|
|
2320
|
+
const declaration = options.ecosystemContract;
|
|
2321
|
+
if (!declaration) {
|
|
2322
|
+
return {
|
|
2323
|
+
passed: false,
|
|
2324
|
+
urls: /* @__PURE__ */ new Set(),
|
|
2325
|
+
evidence: [{ path: "doc-bridge.config.json", detail: "Canonical ecosystem contract evidence is not declared." }]
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
const manifestFile = fileEvidence(root, declaration.manifest);
|
|
2329
|
+
const claimsFile = fileEvidence(root, declaration.claims);
|
|
2330
|
+
const evidence = [manifestFile.evidence, claimsFile.evidence];
|
|
2331
|
+
if (!manifestFile.exists || !claimsFile.exists) return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
2332
|
+
try {
|
|
2333
|
+
const manifest = JSON.parse(manifestFile.content);
|
|
2334
|
+
const claims = JSON.parse(claimsFile.content);
|
|
2335
|
+
const contract = parseCanonicalEcosystemContract(manifest, claims);
|
|
2336
|
+
const productIds = contract.manifest.products.map((product) => product.id);
|
|
2337
|
+
if (!productIds.includes(declaration.productId)) throw new Error(`Manifest is missing product ${declaration.productId}.`);
|
|
2338
|
+
const urls = /* @__PURE__ */ new Set();
|
|
2339
|
+
for (const product of contract.manifest.products) {
|
|
2340
|
+
for (const value of [
|
|
2341
|
+
product.surfaces.home,
|
|
2342
|
+
product.surfaces.docs,
|
|
2343
|
+
product.surfaces.llms,
|
|
2344
|
+
product.surfaces.stats
|
|
2345
|
+
]) {
|
|
2346
|
+
if (typeof value === "string" && /^https:\/\//.test(value)) urls.add(normalizedUrl(value));
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
evidence[0] = { path: declaration.manifest, detail: `Validated ${productIds.length} canonical product(s), including ${declaration.productId}.` };
|
|
2350
|
+
evidence[1] = { path: declaration.claims, detail: `Validated claim-ledger identity for ${contract.claims.products.length} product(s).` };
|
|
2351
|
+
return { passed: urls.size > 0, urls, evidence };
|
|
2352
|
+
} catch (error) {
|
|
2353
|
+
evidence.push({
|
|
2354
|
+
path: `${declaration.manifest}, ${declaration.claims}`,
|
|
2355
|
+
detail: error instanceof Error ? error.message : "Canonical ecosystem contract is invalid."
|
|
2356
|
+
});
|
|
2357
|
+
return { passed: false, urls: /* @__PURE__ */ new Set(), evidence };
|
|
2358
|
+
}
|
|
2359
|
+
};
|
|
2360
|
+
var handoffsRule = (root, config) => {
|
|
2361
|
+
const index = buildDocBridgeIndex({ root, config, write: false }).index;
|
|
2362
|
+
const handoffs = Object.values(index.handoffs ?? {});
|
|
2363
|
+
const ready = handoffs.filter(
|
|
2364
|
+
(handoff) => handoff.startHere.length > 0 && handoff.editRoots.length > 0 && handoff.checks.length > 0 && (handoff.bridge?.humanDoc === "linked" || handoff.bridge?.humanDoc === "external")
|
|
2365
|
+
);
|
|
2366
|
+
return {
|
|
2367
|
+
id: "agent-handoffs",
|
|
2368
|
+
level: "required",
|
|
2369
|
+
passed: ready.length > 0 && ready.length === handoffs.length,
|
|
2370
|
+
message: ready.length > 0 && ready.length === handoffs.length ? `${ready.length} handoff(s) are action-ready and human-linked.` : `${ready.length}/${handoffs.length} handoff(s) are action-ready and human-linked.`,
|
|
2371
|
+
evidence: handoffs.map((handoff) => ({
|
|
2372
|
+
path: handoff.startHere,
|
|
2373
|
+
detail: `${handoff.target.id}: ${handoff.editRoots.length} edit root(s), ${handoff.checks.length} check(s), bridge=${handoff.bridge?.humanDoc ?? "none"}`
|
|
2374
|
+
})),
|
|
2375
|
+
remediation: {
|
|
2376
|
+
command: "ak-docs bootstrap agent-docs && ak-docs index",
|
|
2377
|
+
detail: "Add ownership checks and a resolvable humanDoc for every handoff."
|
|
2378
|
+
}
|
|
2379
|
+
};
|
|
2380
|
+
};
|
|
2381
|
+
var contributionRule = (root, options) => {
|
|
2382
|
+
const paths = options.contributionPaths?.length ? options.contributionPaths : ["CONTRIBUTING.md"];
|
|
2383
|
+
const evidence = paths.map((path) => fileEvidence(root, path));
|
|
2384
|
+
return {
|
|
2385
|
+
id: "contribution",
|
|
2386
|
+
level: "required",
|
|
2387
|
+
passed: evidence.some((item) => item.exists),
|
|
2388
|
+
message: evidence.some((item) => item.exists) ? "Contribution guidance is available." : "Contribution guidance is missing.",
|
|
2389
|
+
evidence: evidence.map((item) => item.evidence),
|
|
2390
|
+
remediation: {
|
|
2391
|
+
command: "edit CONTRIBUTING.md",
|
|
2392
|
+
detail: "Document setup, validation commands, and the pull-request workflow."
|
|
2393
|
+
}
|
|
2394
|
+
};
|
|
2395
|
+
};
|
|
2396
|
+
var markersRule = (root, options, kind, level) => {
|
|
2397
|
+
const declarations = options[kind === "metadata" ? "metadata" : "diagrams"] ?? [];
|
|
2398
|
+
const evidence = [];
|
|
2399
|
+
let passed = declarations.length > 0;
|
|
2400
|
+
for (const declaration of declarations) {
|
|
2401
|
+
const file = fileEvidence(root, declaration.path);
|
|
2402
|
+
const missing = declaration.contains.filter((marker) => !file.content.includes(marker));
|
|
2403
|
+
if (!file.exists || missing.length > 0) passed = false;
|
|
2404
|
+
evidence.push({
|
|
2405
|
+
path: declaration.path,
|
|
2406
|
+
detail: !file.exists ? file.evidence.detail : missing.length ? `Missing marker(s): ${missing.join(", ")}` : `Found marker(s): ${declaration.contains.join(", ")}`
|
|
2407
|
+
});
|
|
2408
|
+
}
|
|
2409
|
+
return {
|
|
2410
|
+
id: kind,
|
|
2411
|
+
level,
|
|
2412
|
+
passed,
|
|
2413
|
+
message: passed ? `${kind} evidence is complete.` : `${kind} evidence is incomplete.`,
|
|
2414
|
+
evidence,
|
|
2415
|
+
remediation: {
|
|
2416
|
+
command: "edit doc-bridge.config.json",
|
|
2417
|
+
detail: `Declare ${kind} evidence paths and markers that exist in the repository.`
|
|
2418
|
+
}
|
|
2419
|
+
};
|
|
2420
|
+
};
|
|
2421
|
+
var linksRule = (root, options) => {
|
|
2422
|
+
const links = options.links ?? [];
|
|
2423
|
+
const contract = ecosystemContract(root, options);
|
|
2424
|
+
const evidence = [...contract.evidence];
|
|
2425
|
+
let passed = links.length > 0 && contract.passed;
|
|
2426
|
+
for (const link of links) {
|
|
2427
|
+
const sources = link.paths.map((path) => ({ path, file: fileEvidence(root, path) }));
|
|
2428
|
+
const matches = sources.filter(({ file }) => file.exists && file.content.includes(link.url));
|
|
2429
|
+
const canonical = contract.urls.has(normalizedUrl(link.url));
|
|
2430
|
+
if (matches.length === 0 || !canonical) passed = false;
|
|
2431
|
+
evidence.push({
|
|
2432
|
+
path: sources.map((source) => source.path).join(", "),
|
|
2433
|
+
detail: matches.length === 0 ? `Missing ${link.url}` : canonical ? `Found canonical ecosystem URL ${link.url}` : `Found ${link.url}, but it is absent from the canonical ecosystem manifest.`
|
|
2434
|
+
});
|
|
2435
|
+
}
|
|
2436
|
+
return {
|
|
2437
|
+
id: "cross-links",
|
|
2438
|
+
level: "required",
|
|
2439
|
+
passed,
|
|
2440
|
+
message: passed ? `${links.length} required ecosystem link(s) resolve in source.` : "One or more required ecosystem links are missing from source.",
|
|
2441
|
+
evidence,
|
|
2442
|
+
remediation: {
|
|
2443
|
+
command: "edit README.md",
|
|
2444
|
+
detail: "Sync the canonical ecosystem snapshots and add each configured canonical URL to a declared documentation source."
|
|
2445
|
+
}
|
|
2446
|
+
};
|
|
2447
|
+
};
|
|
2448
|
+
var quickstartsRule = (root, options) => {
|
|
2449
|
+
const quickstarts = options.quickstarts ?? [];
|
|
2450
|
+
const evidence = [];
|
|
2451
|
+
let passed = quickstarts.length > 0;
|
|
2452
|
+
for (const quickstart of quickstarts) {
|
|
2453
|
+
const doc = fileEvidence(root, quickstart.doc);
|
|
2454
|
+
const test = fileEvidence(root, quickstart.test);
|
|
2455
|
+
const missingMarkers = quickstart.testContains.filter((marker) => !test.content.includes(marker));
|
|
2456
|
+
if (!doc.exists || !test.exists || missingMarkers.length > 0 || !quickstart.command.trim()) passed = false;
|
|
2457
|
+
evidence.push(
|
|
2458
|
+
{ path: quickstart.doc, detail: `${quickstart.id}: ${doc.evidence.detail}` },
|
|
2459
|
+
{
|
|
2460
|
+
path: quickstart.test,
|
|
2461
|
+
detail: missingMarkers.length ? `${quickstart.id}: missing test marker(s): ${missingMarkers.join(", ")}` : `${quickstart.id}: test evidence; CI command: ${quickstart.command}`
|
|
2462
|
+
}
|
|
2463
|
+
);
|
|
2464
|
+
}
|
|
2465
|
+
return {
|
|
2466
|
+
id: "tested-quickstarts",
|
|
2467
|
+
level: "required",
|
|
2468
|
+
passed,
|
|
2469
|
+
message: passed ? `${quickstarts.length} quickstart(s) have executable test evidence.` : "Quickstart test evidence is incomplete.",
|
|
2470
|
+
evidence,
|
|
2471
|
+
remediation: {
|
|
2472
|
+
command: "pnpm test",
|
|
2473
|
+
detail: "Map every quickstart to a documentation path, test file, identifying marker, and CI command."
|
|
2474
|
+
}
|
|
2475
|
+
};
|
|
2476
|
+
};
|
|
2477
|
+
var visualsRule = (root, options) => {
|
|
2478
|
+
const visuals = options.visuals ?? [];
|
|
2479
|
+
const evidence = visuals.map((path) => fileEvidence(root, path, { readContent: false }));
|
|
2480
|
+
return {
|
|
2481
|
+
id: "visual-explanations",
|
|
2482
|
+
level: "recommended",
|
|
2483
|
+
passed: visuals.length > 0 && evidence.every((item) => item.exists),
|
|
2484
|
+
message: visuals.length > 0 && evidence.every((item) => item.exists) ? `${visuals.length} visual asset(s) found.` : "Visual explanation evidence is incomplete.",
|
|
2485
|
+
evidence: evidence.map((item) => item.evidence),
|
|
2486
|
+
remediation: {
|
|
2487
|
+
command: "edit doc-bridge.config.json",
|
|
2488
|
+
detail: "Declare the images or animations that explain the product workflow."
|
|
2489
|
+
}
|
|
2490
|
+
};
|
|
2491
|
+
};
|
|
2492
|
+
var count = (results, level, status) => results.filter((result) => result.level === level && result.status === status).length;
|
|
2493
|
+
var runDocumentationStandardV1 = (root, config) => {
|
|
2494
|
+
const options = config.conformance?.documentationStandardV1 ?? {};
|
|
2495
|
+
const drafts = [
|
|
2496
|
+
humanDocsRule(root, config),
|
|
2497
|
+
llmsRule(root, config, options),
|
|
2498
|
+
handoffsRule(root, config),
|
|
2499
|
+
contributionRule(root, options),
|
|
2500
|
+
markersRule(root, options, "metadata", "required"),
|
|
2501
|
+
linksRule(root, options),
|
|
2502
|
+
quickstartsRule(root, options),
|
|
2503
|
+
visualsRule(root, options),
|
|
2504
|
+
markersRule(root, options, "structured-diagrams", "recommended")
|
|
2505
|
+
];
|
|
2506
|
+
const results = drafts.map((draft) => resultWithException(draft, options));
|
|
2507
|
+
return {
|
|
2508
|
+
schemaVersion: 1,
|
|
2509
|
+
profile: { id: DOCUMENTATION_STANDARD_V1_ID, version: 1, status: DOCUMENTATION_STANDARD_V1_STATUS },
|
|
2510
|
+
ok: results.filter((result) => result.level === "required").every((result) => result.ok),
|
|
2511
|
+
recommendedOk: results.filter((result) => result.level === "recommended").every((result) => result.ok),
|
|
2512
|
+
summary: {
|
|
2513
|
+
required: {
|
|
2514
|
+
passed: count(results, "required", "pass"),
|
|
2515
|
+
failed: count(results, "required", "fail"),
|
|
2516
|
+
excepted: count(results, "required", "excepted")
|
|
2517
|
+
},
|
|
2518
|
+
recommended: {
|
|
2519
|
+
passed: count(results, "recommended", "pass"),
|
|
2520
|
+
failed: count(results, "recommended", "fail"),
|
|
2521
|
+
excepted: count(results, "recommended", "excepted")
|
|
2522
|
+
}
|
|
2523
|
+
},
|
|
2524
|
+
results
|
|
2525
|
+
};
|
|
2526
|
+
};
|
|
2527
|
+
var formatDocumentationStandardText = (report) => [
|
|
2528
|
+
`Documentation Standard v1 (${report.profile.status})`,
|
|
2529
|
+
`Required: ${report.summary.required.passed} passed \xB7 ${report.summary.required.failed} failed \xB7 ${report.summary.required.excepted} excepted`,
|
|
2530
|
+
`Recommended: ${report.summary.recommended.passed} passed \xB7 ${report.summary.recommended.failed} failed \xB7 ${report.summary.recommended.excepted} excepted`,
|
|
2531
|
+
"",
|
|
2532
|
+
...report.results.flatMap((result) => [
|
|
2533
|
+
`${result.status === "pass" ? "PASS" : result.status === "excepted" ? "EXCEPTED" : "FAIL"} [${result.level}] ${result.id}: ${result.message}`,
|
|
2534
|
+
...result.evidence.map((evidence) => ` evidence: ${evidence.path} \u2014 ${evidence.detail}`),
|
|
2535
|
+
...result.exception ? [` exception: ${result.exception.reason} \u2014 ${result.exception.approvedBy} (${result.exception.trackingUrl})`] : result.ok ? [] : [` fix: ${result.remediation.command} \u2014 ${result.remediation.detail}`]
|
|
2536
|
+
])
|
|
2537
|
+
];
|
|
1649
2538
|
|
|
1650
2539
|
// src/query/load-index.ts
|
|
1651
|
-
import { existsSync as
|
|
1652
|
-
import { join as
|
|
2540
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
|
|
2541
|
+
import { join as join8, resolve as resolve6 } from "path";
|
|
1653
2542
|
var IndexNotFoundError = class extends Error {
|
|
1654
2543
|
constructor(path) {
|
|
1655
2544
|
super(`Missing index at ${path}. Run: ak-docs index`);
|
|
@@ -1658,18 +2547,29 @@ var IndexNotFoundError = class extends Error {
|
|
|
1658
2547
|
}
|
|
1659
2548
|
path;
|
|
1660
2549
|
};
|
|
1661
|
-
var indexFilePath = (root, config) =>
|
|
2550
|
+
var indexFilePath = (root, config) => join8(root, config.index?.outFile ?? ".doc-bridge/index.json");
|
|
1662
2551
|
var loadDocBridgeIndex = (root, config) => {
|
|
1663
2552
|
const path = indexFilePath(root, config);
|
|
1664
|
-
if (!
|
|
1665
|
-
const raw = JSON.parse(
|
|
2553
|
+
if (!existsSync9(path)) throw new IndexNotFoundError(path);
|
|
2554
|
+
const raw = JSON.parse(readFileSync8(path, "utf8"));
|
|
1666
2555
|
return parseDocBridgeIndex(raw);
|
|
1667
2556
|
};
|
|
1668
|
-
var resolveRoot = (cwd) =>
|
|
2557
|
+
var resolveRoot = (cwd) => resolve6(cwd ?? process.cwd());
|
|
1669
2558
|
|
|
1670
2559
|
// src/gates/run-gates.ts
|
|
1671
|
-
var
|
|
2560
|
+
var RESERVED_GATE_IDS = /* @__PURE__ */ new Set(["link-rot", "routing-currency", "bootstrap-size"]);
|
|
1672
2561
|
var runGate = (root, config, id) => {
|
|
2562
|
+
if (id === "documentation-standard-v1") {
|
|
2563
|
+
const details = runDocumentationStandardV1(root, config);
|
|
2564
|
+
return {
|
|
2565
|
+
id,
|
|
2566
|
+
ok: details.ok,
|
|
2567
|
+
message: details.ok ? "Documentation Standard v1 required rules pass" : `${details.summary.required.failed} Documentation Standard v1 required rule(s) failed`,
|
|
2568
|
+
expected: "all required rules pass or have approved exceptions",
|
|
2569
|
+
actual: `${details.summary.required.passed} passed, ${details.summary.required.failed} failed, ${details.summary.required.excepted} excepted`,
|
|
2570
|
+
details
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
1673
2573
|
if (id === "human-guide-links") return runHumanGuideLinksGate(root, config);
|
|
1674
2574
|
if (id === "okf-type") return runOkfTypeGate(root, config);
|
|
1675
2575
|
if (id === "docs-style") return runDocsStyleGate(root, config);
|
|
@@ -1727,7 +2627,7 @@ var runOkfTypeGate = (root, config) => {
|
|
|
1727
2627
|
const required = config.corpus.agent.okf?.requireType ?? config.gates?.preset === "strict";
|
|
1728
2628
|
if (!required) return { id: "okf-type", ok: true, message: "OKF type frontmatter not required" };
|
|
1729
2629
|
const allowed = config.corpus.agent.okf?.allowedTypes;
|
|
1730
|
-
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(
|
|
2630
|
+
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({ path: doc.path, type: frontmatterType(readFileSync9(doc.absPath, "utf8")) })).filter((doc) => !doc.type || allowed && !allowed.includes(doc.type));
|
|
1731
2631
|
if (bad.length) {
|
|
1732
2632
|
return {
|
|
1733
2633
|
id: "okf-type",
|
|
@@ -1801,7 +2701,7 @@ var runDocsStyleGate = (root, config) => {
|
|
|
1801
2701
|
}
|
|
1802
2702
|
const bad = scanAgentCorpus(root, config).filter((doc) => doc.path !== config.corpus.agent.index).map((doc) => ({
|
|
1803
2703
|
path: doc.path,
|
|
1804
|
-
missing: missingStyleRules(
|
|
2704
|
+
missing: missingStyleRules(readFileSync9(doc.absPath, "utf8"), required)
|
|
1805
2705
|
})).filter((doc) => doc.missing.length > 0);
|
|
1806
2706
|
if (bad.length) {
|
|
1807
2707
|
return {
|
|
@@ -1831,18 +2731,24 @@ var resolveGateIds = (config) => {
|
|
|
1831
2731
|
) : ["index-freshness", "human-guide-links"]
|
|
1832
2732
|
);
|
|
1833
2733
|
for (const id of config.gates?.include ?? []) {
|
|
1834
|
-
if (
|
|
2734
|
+
if (RESERVED_GATE_IDS.has(id)) {
|
|
2735
|
+
process.emitWarning(`Gate "${id}" is reserved and has no runtime implementation; it was not executed.`, {
|
|
2736
|
+
code: "AK_DOCS_RESERVED_GATE"
|
|
2737
|
+
});
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
ids.add(id);
|
|
1835
2741
|
}
|
|
1836
2742
|
for (const id of config.gates?.exclude ?? []) {
|
|
1837
|
-
if (
|
|
2743
|
+
if (!RESERVED_GATE_IDS.has(id)) ids.delete(id);
|
|
1838
2744
|
}
|
|
1839
2745
|
return [...ids];
|
|
1840
2746
|
};
|
|
1841
2747
|
|
|
1842
2748
|
// src/mcp/server.ts
|
|
1843
|
-
import { readFileSync as
|
|
1844
|
-
import { relative, resolve as
|
|
1845
|
-
import { z as
|
|
2749
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync6 } from "fs";
|
|
2750
|
+
import { relative as relative4, resolve as resolve7 } from "path";
|
|
2751
|
+
import { z as z6, ZodError } from "zod";
|
|
1846
2752
|
|
|
1847
2753
|
// src/query/search.ts
|
|
1848
2754
|
var tokenize = (value) => value.toLowerCase().split(/[^a-z0-9@/_-]+/).filter((t) => t.length >= 2);
|
|
@@ -1960,15 +2866,15 @@ var createDocBridgeRetriever = (index, options = {}) => ({
|
|
|
1960
2866
|
});
|
|
1961
2867
|
|
|
1962
2868
|
// src/memory/ingest.ts
|
|
1963
|
-
import { existsSync as
|
|
1964
|
-
import { join as
|
|
2869
|
+
import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
|
|
2870
|
+
import { join as join9 } from "path";
|
|
1965
2871
|
var memoryFact = (raw, id) => firstParagraph(raw) ?? firstHeading(raw) ?? id;
|
|
1966
2872
|
var relativePath = (root, abs) => toPosix(abs).replace(`${toPosix(root)}/`, "");
|
|
1967
2873
|
var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
1968
|
-
if (!
|
|
2874
|
+
if (!existsSync10(dir)) return [];
|
|
1969
2875
|
return walkFiles(dir, { extensions: [".md", ".mdc"] }).map((abs) => {
|
|
1970
2876
|
const rel = relativePath(root, abs);
|
|
1971
|
-
const raw =
|
|
2877
|
+
const raw = readFileSync10(abs, "utf8");
|
|
1972
2878
|
const id = slugFromPath(rel);
|
|
1973
2879
|
return {
|
|
1974
2880
|
schemaVersion: 1,
|
|
@@ -1983,10 +2889,10 @@ var ingestMarkdownDir = (root, dir, source, confidence) => {
|
|
|
1983
2889
|
});
|
|
1984
2890
|
};
|
|
1985
2891
|
var ingestCursorRules = (root) => {
|
|
1986
|
-
const dir =
|
|
2892
|
+
const dir = join9(root, ".cursor", "rules");
|
|
1987
2893
|
return ingestMarkdownDir(root, dir, "cursor", 0.6);
|
|
1988
2894
|
};
|
|
1989
|
-
var ingestAgentMemory = (root) => ingestMarkdownDir(root,
|
|
2895
|
+
var ingestAgentMemory = (root) => ingestMarkdownDir(root, join9(root, ".agent-memory"), "agent-memory", 0.7);
|
|
1990
2896
|
var ingestMemoryCandidates = (root) => [
|
|
1991
2897
|
...ingestAgentMemory(root),
|
|
1992
2898
|
...ingestCursorRules(root)
|
|
@@ -2240,21 +3146,21 @@ var MCP_TOOLS = [
|
|
|
2240
3146
|
}
|
|
2241
3147
|
];
|
|
2242
3148
|
var asRecord = (value) => value && typeof value === "object" ? value : {};
|
|
2243
|
-
var HandoffResolveArgsSchema =
|
|
2244
|
-
id:
|
|
2245
|
-
kind:
|
|
3149
|
+
var HandoffResolveArgsSchema = z6.object({
|
|
3150
|
+
id: z6.string().min(1),
|
|
3151
|
+
kind: z6.enum(["package", "ownership"]).optional()
|
|
2246
3152
|
});
|
|
2247
|
-
var DocSearchArgsSchema =
|
|
2248
|
-
term:
|
|
2249
|
-
limit:
|
|
3153
|
+
var DocSearchArgsSchema = z6.object({
|
|
3154
|
+
term: z6.string().min(1),
|
|
3155
|
+
limit: z6.number().int().positive().max(100).optional()
|
|
2250
3156
|
});
|
|
2251
|
-
var RetrieverQueryArgsSchema =
|
|
2252
|
-
query:
|
|
2253
|
-
limit:
|
|
3157
|
+
var RetrieverQueryArgsSchema = z6.object({
|
|
3158
|
+
query: z6.string().min(1),
|
|
3159
|
+
limit: z6.number().int().positive().max(100).optional()
|
|
2254
3160
|
});
|
|
2255
|
-
var DocGetArgsSchema =
|
|
2256
|
-
id:
|
|
2257
|
-
path:
|
|
3161
|
+
var DocGetArgsSchema = z6.object({
|
|
3162
|
+
id: z6.string().min(1).optional(),
|
|
3163
|
+
path: z6.string().min(1).optional()
|
|
2258
3164
|
}).refine((args) => args.id || args.path, "doc.get requires id or path");
|
|
2259
3165
|
var parseToolArgs = (tool, schema, value) => {
|
|
2260
3166
|
try {
|
|
@@ -2286,12 +3192,12 @@ var findDocPath = (index, args) => {
|
|
|
2286
3192
|
return doc.path;
|
|
2287
3193
|
};
|
|
2288
3194
|
var resolveDocPath = (root, relPath) => {
|
|
2289
|
-
const rootAbs =
|
|
2290
|
-
const unresolved =
|
|
2291
|
-
const unresolvedRel =
|
|
3195
|
+
const rootAbs = realpathSync6.native(root);
|
|
3196
|
+
const unresolved = resolve7(rootAbs, relPath);
|
|
3197
|
+
const unresolvedRel = relative4(rootAbs, unresolved);
|
|
2292
3198
|
if (unresolvedRel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2293
|
-
const abs =
|
|
2294
|
-
const rel =
|
|
3199
|
+
const abs = realpathSync6.native(unresolved);
|
|
3200
|
+
const rel = relative4(rootAbs, abs);
|
|
2295
3201
|
if (rel.startsWith("..")) throw new Error("doc.get path escapes project root");
|
|
2296
3202
|
return abs;
|
|
2297
3203
|
};
|
|
@@ -2325,7 +3231,7 @@ var handleMcpRequest = (ctx, request) => {
|
|
|
2325
3231
|
}
|
|
2326
3232
|
if (name === "doc.get") {
|
|
2327
3233
|
const relPath = findDocPath(index(), parseToolArgs("doc.get", DocGetArgsSchema, args));
|
|
2328
|
-
return textResult(
|
|
3234
|
+
return textResult(readFileSync11(resolveDocPath(ctx.root, relPath), "utf8"));
|
|
2329
3235
|
}
|
|
2330
3236
|
if (name === "gate.status") return textResult(runGates(ctx.root, ctx.config));
|
|
2331
3237
|
if (name === "retriever.query") {
|
|
@@ -2403,9 +3309,9 @@ var startMcpStdioServer = (ctx) => {
|
|
|
2403
3309
|
};
|
|
2404
3310
|
|
|
2405
3311
|
// src/mcp/install.ts
|
|
2406
|
-
import { existsSync as
|
|
3312
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync2, readFileSync as readFileSync12, writeFileSync as writeFileSync2 } from "fs";
|
|
2407
3313
|
import { homedir } from "os";
|
|
2408
|
-
import { dirname as dirname4, join as
|
|
3314
|
+
import { dirname as dirname4, join as join10, resolve as resolve8 } from "path";
|
|
2409
3315
|
var SERVER_NAME = "ak-docs";
|
|
2410
3316
|
var mcpServerEntry = (root) => ({
|
|
2411
3317
|
command: "npx",
|
|
@@ -2413,9 +3319,9 @@ var mcpServerEntry = (root) => ({
|
|
|
2413
3319
|
cwd: root
|
|
2414
3320
|
});
|
|
2415
3321
|
var readJson = (path) => {
|
|
2416
|
-
if (!
|
|
3322
|
+
if (!existsSync11(path)) return {};
|
|
2417
3323
|
try {
|
|
2418
|
-
const parsed = JSON.parse(
|
|
3324
|
+
const parsed = JSON.parse(readFileSync12(path, "utf8"));
|
|
2419
3325
|
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
2420
3326
|
} catch {
|
|
2421
3327
|
return {};
|
|
@@ -2427,12 +3333,12 @@ var writeJson = (path, value) => {
|
|
|
2427
3333
|
`, "utf8");
|
|
2428
3334
|
};
|
|
2429
3335
|
var resolveTargetPath = (target, root) => {
|
|
2430
|
-
if (target === "cursor") return
|
|
2431
|
-
return
|
|
3336
|
+
if (target === "cursor") return resolve8(root, ".cursor", "mcp.json");
|
|
3337
|
+
return join10(homedir(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
2432
3338
|
};
|
|
2433
3339
|
var installMcpConfig = (root, target) => {
|
|
2434
3340
|
const configPath = resolveTargetPath(target, root);
|
|
2435
|
-
const created = !
|
|
3341
|
+
const created = !existsSync11(configPath);
|
|
2436
3342
|
const existing = readJson(configPath);
|
|
2437
3343
|
const servers = existing.mcpServers && typeof existing.mcpServers === "object" && !Array.isArray(existing.mcpServers) ? { ...existing.mcpServers } : {};
|
|
2438
3344
|
servers[SERVER_NAME] = mcpServerEntry(root);
|
|
@@ -2698,22 +3604,22 @@ var formatDoctorText = (report) => {
|
|
|
2698
3604
|
};
|
|
2699
3605
|
|
|
2700
3606
|
// src/index-builder/watch-index.ts
|
|
2701
|
-
import { existsSync as
|
|
2702
|
-
import { dirname as dirname5, resolve as
|
|
3607
|
+
import { existsSync as existsSync12, watch } from "fs";
|
|
3608
|
+
import { dirname as dirname5, resolve as resolve9 } from "path";
|
|
2703
3609
|
var WATCH_PATTERN = /\.(md|mdx|json|ya?ml|mdc)$/i;
|
|
2704
3610
|
var collectWatchRoots = (root, config, configPath) => {
|
|
2705
3611
|
const roots = /* @__PURE__ */ new Set();
|
|
2706
|
-
roots.add(
|
|
3612
|
+
roots.add(resolve9(root, config.corpus.agent.root));
|
|
2707
3613
|
const humanSources = config.corpus.human ? Array.isArray(config.corpus.human) ? config.corpus.human : [config.corpus.human] : [];
|
|
2708
3614
|
for (const source of humanSources) {
|
|
2709
3615
|
const humanOpts = source.options ?? {};
|
|
2710
3616
|
for (const key of ["contentDir", "docsDir", "root"]) {
|
|
2711
3617
|
const value = humanOpts[key];
|
|
2712
|
-
if (typeof value === "string" && value.length) roots.add(
|
|
3618
|
+
if (typeof value === "string" && value.length) roots.add(resolve9(root, value));
|
|
2713
3619
|
}
|
|
2714
3620
|
}
|
|
2715
|
-
if (configPath) roots.add(dirname5(
|
|
2716
|
-
return [...roots].filter((dir) =>
|
|
3621
|
+
if (configPath) roots.add(dirname5(resolve9(configPath)));
|
|
3622
|
+
return [...roots].filter((dir) => existsSync12(dir));
|
|
2717
3623
|
};
|
|
2718
3624
|
var watchDocBridgeIndex = (opts) => {
|
|
2719
3625
|
const debounceMs = opts.debounceMs ?? 350;
|
|
@@ -2754,8 +3660,8 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
2754
3660
|
rebuild();
|
|
2755
3661
|
});
|
|
2756
3662
|
}
|
|
2757
|
-
const configDir =
|
|
2758
|
-
if (
|
|
3663
|
+
const configDir = resolve9(opts.root);
|
|
3664
|
+
if (existsSync12(configDir)) {
|
|
2759
3665
|
watch(configDir, (_event, filename) => {
|
|
2760
3666
|
if (!filename || !/doc-bridge\.config/.test(filename)) return;
|
|
2761
3667
|
rebuild();
|
|
@@ -2774,8 +3680,8 @@ var watchDocBridgeIndex = (opts) => {
|
|
|
2774
3680
|
|
|
2775
3681
|
// src/memory/github-pr.ts
|
|
2776
3682
|
import { execFileSync, spawnSync } from "child_process";
|
|
2777
|
-
import { existsSync as
|
|
2778
|
-
import { join as
|
|
3683
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
3684
|
+
import { join as join12 } from "path";
|
|
2779
3685
|
var run = (cmd, args, cwd) => {
|
|
2780
3686
|
const result = spawnSync(cmd, [...args], { cwd, encoding: "utf8" });
|
|
2781
3687
|
const out = `${result.stdout ?? ""}${result.stderr ?? ""}`.trim();
|
|
@@ -2783,10 +3689,10 @@ var run = (cmd, args, cwd) => {
|
|
|
2783
3689
|
};
|
|
2784
3690
|
var hasGh = () => run("gh", ["--version"], process.cwd()).ok;
|
|
2785
3691
|
var slug = () => (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
2786
|
-
var defaultPromotionDraftPath = (root) =>
|
|
3692
|
+
var defaultPromotionDraftPath = (root) => join12(root, ".doc-bridge", "drafts", `memory-promotion-${slug()}.md`);
|
|
2787
3693
|
var writePromotionDraft = (root, draft, path) => {
|
|
2788
3694
|
const draftPath = path ?? defaultPromotionDraftPath(root);
|
|
2789
|
-
mkdirSync3(
|
|
3695
|
+
mkdirSync3(join12(root, ".doc-bridge", "drafts"), { recursive: true });
|
|
2790
3696
|
writeFileSync3(draftPath, `${draft.body}
|
|
2791
3697
|
`, "utf8");
|
|
2792
3698
|
return draftPath;
|
|
@@ -2822,7 +3728,7 @@ var promoteMemoryToGithubPr = (root, draft, options = {}) => {
|
|
|
2822
3728
|
message: `Wrote draft to ${relDraft}. Run the printed git/gh commands to open a draft PR.`
|
|
2823
3729
|
};
|
|
2824
3730
|
}
|
|
2825
|
-
if (!
|
|
3731
|
+
if (!existsSync13(join12(root, ".git"))) {
|
|
2826
3732
|
return {
|
|
2827
3733
|
ok: false,
|
|
2828
3734
|
dryRun: false,
|
|
@@ -2925,8 +3831,8 @@ ${auth.out}`
|
|
|
2925
3831
|
};
|
|
2926
3832
|
|
|
2927
3833
|
// src/federation/llms.ts
|
|
2928
|
-
import { existsSync as
|
|
2929
|
-
import { resolve as
|
|
3834
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
|
|
3835
|
+
import { resolve as resolve10 } from "path";
|
|
2930
3836
|
var tokenize2 = (value) => value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 2);
|
|
2931
3837
|
var scoreText = (query, text) => {
|
|
2932
3838
|
const hay = text.toLowerCase();
|
|
@@ -2941,9 +3847,9 @@ var defaultFetchText = async (url) => {
|
|
|
2941
3847
|
var sourceText = async (root, source, fetchText) => {
|
|
2942
3848
|
try {
|
|
2943
3849
|
if (/^https?:\/\//.test(source)) return await fetchText(source);
|
|
2944
|
-
const path =
|
|
2945
|
-
if (!
|
|
2946
|
-
return
|
|
3850
|
+
const path = resolve10(root, source);
|
|
3851
|
+
if (!existsSync14(path)) return null;
|
|
3852
|
+
return readFileSync13(path, "utf8");
|
|
2947
3853
|
} catch {
|
|
2948
3854
|
return null;
|
|
2949
3855
|
}
|
|
@@ -3028,11 +3934,11 @@ var retrieveHybridChunks = async (root, config, index, query, options = {}) => {
|
|
|
3028
3934
|
};
|
|
3029
3935
|
|
|
3030
3936
|
// src/intelligence/rag.ts
|
|
3031
|
-
import { readFileSync as
|
|
3032
|
-
import { join as
|
|
3937
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
3938
|
+
import { join as join14 } from "path";
|
|
3033
3939
|
|
|
3034
3940
|
// src/intelligence/adapter.ts
|
|
3035
|
-
import { join as
|
|
3941
|
+
import { join as join13 } from "path";
|
|
3036
3942
|
|
|
3037
3943
|
// src/intelligence/peers.ts
|
|
3038
3944
|
var PeerMissingError = class extends Error {
|
|
@@ -3159,7 +4065,7 @@ var resolveIntelligenceRuntime = async (config) => {
|
|
|
3159
4065
|
}
|
|
3160
4066
|
return { adapter, embed, provider, ...model ? { model } : {} };
|
|
3161
4067
|
};
|
|
3162
|
-
var defaultVectorStorePath = (root) =>
|
|
4068
|
+
var defaultVectorStorePath = (root) => join13(root, ".doc-bridge", "vectors");
|
|
3163
4069
|
|
|
3164
4070
|
// src/intelligence/rag.ts
|
|
3165
4071
|
var loadDocuments = (root, index, sources) => {
|
|
@@ -3167,10 +4073,10 @@ var loadDocuments = (root, index, sources) => {
|
|
|
3167
4073
|
const docs = [];
|
|
3168
4074
|
if (includeAgent) {
|
|
3169
4075
|
for (const entry of index.knowledge) {
|
|
3170
|
-
const abs =
|
|
4076
|
+
const abs = join14(root, entry.path);
|
|
3171
4077
|
let content = "";
|
|
3172
4078
|
try {
|
|
3173
|
-
content =
|
|
4079
|
+
content = readFileSync14(abs, "utf8");
|
|
3174
4080
|
} catch {
|
|
3175
4081
|
content = [entry.title, entry.description].filter(Boolean).join("\n\n");
|
|
3176
4082
|
}
|
|
@@ -3188,7 +4094,7 @@ var createDocBridgeRag = async (root, config, index) => {
|
|
|
3188
4094
|
const { embed } = await resolveIntelligenceRuntime(config);
|
|
3189
4095
|
const ragMod = await importPeer("@agentskit/rag");
|
|
3190
4096
|
const memoryMod = await importPeer("@agentskit/memory");
|
|
3191
|
-
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ?
|
|
4097
|
+
const storePath = typeof config.intelligence?.retriever?.options?.storePath === "string" ? join14(root, config.intelligence.retriever.options.storePath) : defaultVectorStorePath(root);
|
|
3192
4098
|
const store = memoryMod.fileVectorMemory({ path: storePath });
|
|
3193
4099
|
const rag = ragMod.createRAG({
|
|
3194
4100
|
embed,
|
|
@@ -3447,12 +4353,17 @@ export {
|
|
|
3447
4353
|
AgentHandoffV1Schema,
|
|
3448
4354
|
AgentSearchV1Schema,
|
|
3449
4355
|
ConfigNotFoundError,
|
|
4356
|
+
DOCUMENTATION_STANDARD_V1_ID,
|
|
4357
|
+
DOCUMENTATION_STANDARD_V1_STATUS,
|
|
3450
4358
|
DOC_BRIDGE_PATTERN_ID,
|
|
3451
4359
|
DOC_BRIDGE_PATTERN_META,
|
|
3452
4360
|
DocBridgeConfigV1Schema,
|
|
3453
4361
|
DocBridgeIndexV1JsonSchema,
|
|
3454
4362
|
DocBridgeIndexV1Schema,
|
|
3455
4363
|
DocBridgeJsonSchemas,
|
|
4364
|
+
DocumentationStandardRuleIdSchema,
|
|
4365
|
+
DocumentationStandardV1ConfigSchema,
|
|
4366
|
+
EcosystemContractEvidenceSchema,
|
|
3456
4367
|
HANDOFF_SCHEMA_VERSION,
|
|
3457
4368
|
HandoffBridgeSchema,
|
|
3458
4369
|
HandoffTargetTypeSchema,
|
|
@@ -3482,6 +4393,7 @@ export {
|
|
|
3482
4393
|
formatDoctorBadgeJson,
|
|
3483
4394
|
formatDoctorBadgeMarkdown,
|
|
3484
4395
|
formatDoctorText,
|
|
4396
|
+
formatDocumentationStandardText,
|
|
3485
4397
|
handleMcpRequest,
|
|
3486
4398
|
indexFilePath,
|
|
3487
4399
|
ingestAgentMemory,
|
|
@@ -3509,6 +4421,7 @@ export {
|
|
|
3509
4421
|
retrieveHybridChunks,
|
|
3510
4422
|
runChatOnce,
|
|
3511
4423
|
runDoctor,
|
|
4424
|
+
runDocumentationStandardV1,
|
|
3512
4425
|
runGate,
|
|
3513
4426
|
runGates,
|
|
3514
4427
|
runQuery,
|