@agentskit/doc-bridge 1.10.1 → 1.11.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +8 -4
  3. package/action.yml +1 -1
  4. package/dist/cli/program.js +111 -6
  5. package/dist/cli/program.js.map +1 -1
  6. package/dist/config/index.d.ts +1 -1
  7. package/dist/config/index.js +2 -0
  8. package/dist/config/index.js.map +1 -1
  9. package/dist/{index-7wYGbllW.d.ts → index-CimDq6_e.d.ts} +2 -0
  10. package/dist/index.d.ts +51 -5
  11. package/dist/index.js +98 -4
  12. package/dist/index.js.map +1 -1
  13. package/docs/agent-corpus/audit.md +25 -0
  14. package/docs/agent-corpus/bin.md +26 -0
  15. package/docs/agent-corpus/budget.md +26 -0
  16. package/docs/agent-corpus/config.md +33 -0
  17. package/docs/agent-corpus/federation.md +25 -0
  18. package/docs/agent-corpus/findings.md +25 -0
  19. package/docs/agent-corpus/fixes.md +25 -0
  20. package/docs/agent-corpus/lib.md +29 -0
  21. package/docs/agent-corpus/metrics.md +26 -0
  22. package/docs/agent-corpus/parity.md +33 -0
  23. package/docs/agent-corpus/playbook.md +22 -0
  24. package/docs/agent-corpus/plugins.md +23 -0
  25. package/docs/agent-corpus/report.md +27 -0
  26. package/docs/agent-corpus/retrieval.md +33 -0
  27. package/docs/agent-corpus/rules.md +30 -0
  28. package/docs/agent-corpus/safety.md +23 -0
  29. package/docs/agent-corpus/schemas.md +34 -0
  30. package/docs/agent-corpus/scripts.md +29 -0
  31. package/docs/agent-corpus/shims.md +26 -0
  32. package/docs/parity/public-claims-v1.json +18 -1
  33. package/docs/spec/config-v1.md +23 -1
  34. package/mcpb/manifest.json +1 -1
  35. package/package.json +2 -2
  36. package/skills/doc-bridge-handoff/scripts/resolve-handoff.mjs +1 -1
  37. package/src/cli/program.ts +23 -2
  38. package/src/config/schema.ts +2 -0
  39. package/src/discovery/reproducibility.ts +118 -0
  40. package/src/doctor/run-doctor.ts +24 -0
  41. package/src/gates/run-gates.ts +44 -0
  42. package/src/index.ts +5 -0
  43. package/src/version.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.11.1
4
+
5
+ ### Patch Changes
6
+
7
+ - faf29f5: Stop reporting a path an ignore rule rescues as unreproducible.
8
+
9
+ `git check-ignore --verbose` emits a record for every path that *matched* a rule, including the path
10
+ a `!` negation rescues — and such a path is precisely one Git does not ignore. The reproducibility
11
+ check read the record without reading the pattern, so `dist/*` plus `!dist/keep.js` reported
12
+ `dist/keep.js` as a reason the index could not be rebuilt from a clean checkout. The opposite of
13
+ what the rule says, stated confidently.
14
+
15
+ The window is narrow: `check-ignore` stops reporting a path once it is committed, and a path rescued
16
+ by a negation is usually committed, which is the point of rescuing it. It shows up while such a path
17
+ is still untracked — a file added but not yet committed.
18
+
19
+ Found by turning the new `index-reproducible` gate on in this repository, which had to add
20
+ `!.doc-bridge/index.json` to its own `.gitignore` to commit the index the gate verifies.
21
+
22
+ ## 1.11.0
23
+
24
+ ### Minor Changes
25
+
26
+ - 25f16f3: Say when a committed index could not have come from a clean checkout.
27
+
28
+ A scan walks what is on disk and has no reason to consult `.gitignore`, so a generated module or
29
+ document silently joins the corpus on a machine that has built and silently leaves it on one that
30
+ has not. That is harmless while the index is rebuilt on every run. It is a defect the moment the
31
+ index is committed so a gate can verify it — which is the setup `index-freshness` exists for: two
32
+ checkouts of the same commit then produce different artifacts, the gate reports staleness that
33
+ nothing caused, and regenerating cannot fix it because the next machine disagrees in the other
34
+ direction.
35
+
36
+ Dogfooding found this the expensive way. On a 25-package monorepo the freshness gate passed locally
37
+ and failed in CI, twice, because `pnpm lint` had generated one `.ts` file into the corpus before the
38
+ index was written; CI lints a narrower scope and never had the file. Diagnosing it took reading two
39
+ CI logs and diffing file inventories. Git already knew the answer.
40
+
41
+ `ak-docs index` now names those paths as it writes, and `ak-docs doctor` raises an
42
+ `index-not-reproducible` warning, each with the ignore rule that matched — for example
43
+ `apps/docs-next/lib/ask-context.ts (apps/docs-next/.gitignore:13:lib/ask-context.ts)` — so the fix
44
+ is one lookup away: add the path to `safety.exclude`.
45
+
46
+ Both are unconditional and neither changes an exit code or the doctor's score. Enforcement is the
47
+ new `index-reproducible` gate, which is in no preset and has to be requested with
48
+ `gates.include: ['index-reproducible']`, because turning it on for every consumer would fail gates
49
+ that pass for good reasons.
50
+
51
+ The check reports success rather than failure when the index is not committed or the project is not
52
+ a Git checkout, since there is nothing to reproduce in either case, and it does not flag a file that
53
+ is both tracked and matched by an ignore rule — being committed is the point.
54
+
3
55
  ## 1.10.1
4
56
 
5
57
  ### Patch Changes
package/README.md CHANGED
@@ -297,19 +297,23 @@ gates without silently rebuilding them. It rejects non-exact package versions.
297
297
  See the [Marketplace guide](docs/MARKETPLACE.md).
298
298
 
299
299
  The guide is pinned to the published stable Action release `v1.7.45`; the
300
- checked-in package version is `1.8.0`.
300
+ checked-in package version is `1.11.1`.
301
301
 
302
302
  Coverage is repository-specific. Run `ak-docs doctor --badge` locally to emit
303
303
  current handoff and human-bridge badges, or `pnpm coverage:badge` in CI; this
304
304
  README intentionally avoids publishing a stale static percentage.
305
305
 
306
- Or locally:
306
+ Or locally, as two steps rather than one, because they answer different questions:
307
307
 
308
308
  ```bash
309
- ak-docs index && ak-docs gate run
309
+ ak-docs index # after changing docs or config — then review and commit the result
310
+ ak-docs gate run # verifies the committed index, which is what CI verifies
310
311
  ```
311
312
 
312
- Gate fails with `Index is stale. Run: ak-docs index` — same check in CI annotations.
313
+ Chaining them (`index && gate run`) cannot report a stale index: it gates an artifact written a
314
+ second earlier against a rebuild of the same tree. The gate's value is that the committed index
315
+ and the repository agree, so run it the way CI does, against what is committed. It fails with
316
+ `Index is stale. Run: ak-docs index` — the same check, and the same annotation, as in CI.
313
317
 
314
318
  ## Product surface
315
319
 
package/action.yml CHANGED
@@ -20,7 +20,7 @@ inputs:
20
20
  package-version:
21
21
  description: Exact @agentskit/doc-bridge npm version (kept in sync with this Action release)
22
22
  required: false
23
- default: '1.10.1'
23
+ default: '1.11.1'
24
24
 
25
25
  runs:
26
26
  using: composite
@@ -430,6 +430,7 @@ var GatesConfigSchema = z.object({
430
430
  include: z.array(
431
431
  z.enum([
432
432
  "index-freshness",
433
+ "index-reproducible",
433
434
  "human-guide-links",
434
435
  "link-rot",
435
436
  "okf-type",
@@ -442,6 +443,7 @@ var GatesConfigSchema = z.object({
442
443
  exclude: z.array(
443
444
  z.enum([
444
445
  "index-freshness",
446
+ "index-reproducible",
445
447
  "human-guide-links",
446
448
  "link-rot",
447
449
  "okf-type",
@@ -4060,7 +4062,7 @@ var buildLookup = (config, packages, corpus, indexOutFile, humanDocs = {}, root
4060
4062
  };
4061
4063
 
4062
4064
  // src/version.ts
4063
- var PACKAGE_VERSION = "1.10.1";
4065
+ var PACKAGE_VERSION = "1.11.1";
4064
4066
 
4065
4067
  // src/index-builder/capabilities.ts
4066
4068
  var renderCapabilitiesJson = (config, index, paths) => {
@@ -8025,6 +8027,50 @@ var loadFreshDocBridgeIndex = (root, config) => {
8025
8027
  return index;
8026
8028
  };
8027
8029
 
8030
+ // src/discovery/reproducibility.ts
8031
+ import { execFileSync as execFileSync2 } from "child_process";
8032
+ var NOT_CHECKED = (skipped) => ({
8033
+ checked: false,
8034
+ skipped,
8035
+ ignored: []
8036
+ });
8037
+ var git = (root, args, options = {}) => {
8038
+ try {
8039
+ return execFileSync2("git", args, {
8040
+ cwd: root,
8041
+ encoding: "utf8",
8042
+ stdio: ["pipe", "pipe", "ignore"],
8043
+ maxBuffer: 64 * 1024 * 1024,
8044
+ ...options.input === void 0 ? {} : { input: options.input }
8045
+ });
8046
+ } catch (error) {
8047
+ const status = error.status;
8048
+ if (options.allowExit1 && status === 1) return error.stdout ?? "";
8049
+ return void 0;
8050
+ }
8051
+ };
8052
+ var checkIndexReproducibility = (root, indexPath, paths) => {
8053
+ if (git(root, ["rev-parse", "--is-inside-work-tree"]) === void 0) return NOT_CHECKED("no-git");
8054
+ if (git(root, ["ls-files", "--error-unmatch", "--", indexPath]) === void 0) {
8055
+ return NOT_CHECKED("index-untracked");
8056
+ }
8057
+ if (paths.length === 0) return { checked: true, ignored: [] };
8058
+ const unique3 = [...new Set(paths)].sort();
8059
+ const output = git(root, ["check-ignore", "--verbose", "-z", "--stdin"], {
8060
+ input: `${unique3.join("\0")}\0`,
8061
+ allowExit1: true
8062
+ });
8063
+ if (output === void 0) return NOT_CHECKED("no-git");
8064
+ const fields = output.split("\0");
8065
+ const ignored = [];
8066
+ for (let index = 0; index + 3 < fields.length; index += 4) {
8067
+ const [source, line, pattern, path] = [fields[index], fields[index + 1], fields[index + 2], fields[index + 3]];
8068
+ if (!path || pattern?.startsWith("!")) continue;
8069
+ ignored.push({ path, rule: `${source ?? "?"}:${line ?? "?"}:${pattern ?? "?"}` });
8070
+ }
8071
+ return { checked: true, ignored: ignored.sort((left, right) => left.path.localeCompare(right.path)) };
8072
+ };
8073
+
8028
8074
  // src/gates/run-gates.ts
8029
8075
  var RESERVED_GATE_IDS = /* @__PURE__ */ new Set(["link-rot", "routing-currency", "bootstrap-size"]);
8030
8076
  var runGate = (root, config, id) => {
@@ -8042,6 +8088,38 @@ var runGate = (root, config, id) => {
8042
8088
  if (id === "human-guide-links") return runHumanGuideLinksGate(root, config);
8043
8089
  if (id === "okf-type") return runOkfTypeGate(root, config);
8044
8090
  if (id === "docs-style") return runDocsStyleGate(root, config);
8091
+ if (id === "index-reproducible") {
8092
+ let index;
8093
+ try {
8094
+ index = loadDocBridgeIndex(root, config);
8095
+ } catch (error) {
8096
+ if (error instanceof IndexNotFoundError) return { id, ok: false, message: error.message };
8097
+ throw error;
8098
+ }
8099
+ const result = checkIndexReproducibility(
8100
+ root,
8101
+ config.index?.outFile ?? ".doc-bridge/index.json",
8102
+ index.knowledge.map((entry) => entry.path)
8103
+ );
8104
+ if (!result.checked) {
8105
+ return {
8106
+ id,
8107
+ ok: true,
8108
+ message: result.skipped === "index-untracked" ? "Index is not committed, so nothing has to reproduce it" : "Not a Git checkout; reproducibility was not checked"
8109
+ };
8110
+ }
8111
+ if (result.ignored.length === 0) {
8112
+ return { id, ok: true, message: "Every indexed path is committed" };
8113
+ }
8114
+ const sample = result.ignored.slice(0, 5).map((entry) => `${entry.path} (${entry.rule})`);
8115
+ return {
8116
+ id,
8117
+ ok: false,
8118
+ message: `${result.ignored.length} indexed path(s) are ignored by Git, so a clean checkout builds a different index. Add them to safety.exclude.`,
8119
+ expected: "every indexed path is committed",
8120
+ actual: sample.join(", ")
8121
+ };
8122
+ }
8045
8123
  if (id !== "index-freshness") throw new Error(`Unsupported gate "${id}"`);
8046
8124
  let current;
8047
8125
  try {
@@ -9072,7 +9150,7 @@ var draftMemoryPromotion = (classifications) => {
9072
9150
  };
9073
9151
 
9074
9152
  // src/memory/github-pr.ts
9075
- import { execFileSync as execFileSync2, spawnSync } from "child_process";
9153
+ import { execFileSync as execFileSync3, spawnSync } from "child_process";
9076
9154
  import { existsSync as existsSync15, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
9077
9155
  import { join as join15 } from "path";
9078
9156
  var run = (cmd, args, cwd) => {
@@ -9199,7 +9277,7 @@ ${auth.out}`
9199
9277
  ];
9200
9278
  let prUrl = "";
9201
9279
  try {
9202
- prUrl = execFileSync2("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
9280
+ prUrl = execFileSync3("gh", prArgs, { cwd: root, encoding: "utf8" }).trim();
9203
9281
  } catch (error) {
9204
9282
  const message = error instanceof Error ? error.message : String(error);
9205
9283
  return {
@@ -10170,6 +10248,16 @@ var buildIssues = (coverage) => {
10170
10248
  action: "ak-docs index"
10171
10249
  });
10172
10250
  }
10251
+ const { ignored } = coverage.reproducibility;
10252
+ if (ignored.length > 0) {
10253
+ const sample = ignored.slice(0, 5).map((entry) => `${entry.path} (${entry.rule})`);
10254
+ issues.push({
10255
+ severity: "warn",
10256
+ code: "index-not-reproducible",
10257
+ message: `${ignored.length} indexed path(s) are ignored by Git, so a clean checkout builds a different index: ${sample.join(", ")}${ignored.length > sample.length ? `, and ${ignored.length - sample.length} more` : ""}.`,
10258
+ action: "edit doc-bridge.config.json # add the generated paths to safety.exclude"
10259
+ });
10260
+ }
10173
10261
  for (const id of coverage.packages.missingAgentDoc) {
10174
10262
  issues.push({
10175
10263
  severity: "warn",
@@ -10307,7 +10395,12 @@ var runDoctor = (root, config) => {
10307
10395
  message: freshnessMessage,
10308
10396
  hasIndex
10309
10397
  },
10310
- gates
10398
+ gates,
10399
+ reproducibility: checkIndexReproducibility(
10400
+ root,
10401
+ config.index?.outFile ?? ".doc-bridge/index.json",
10402
+ index.knowledge.map((entry) => entry.path)
10403
+ )
10311
10404
  };
10312
10405
  const issues = buildIssues(coverage);
10313
10406
  const score = computeScore(coverage);
@@ -16983,7 +17076,7 @@ var loadProject = (configPath) => {
16983
17076
  const root = projectRootFromConfigPath(path, config.project?.root);
16984
17077
  return { config, configPath: path, root };
16985
17078
  };
16986
- var indexDiagnostics = (config, result) => {
17079
+ var indexDiagnostics = (root, config, result) => {
16987
17080
  const diagnostics = [];
16988
17081
  const onlyDoc = result.index.knowledge.length === 1 ? result.index.knowledge[0] : void 0;
16989
17082
  if (onlyDoc?.path === config.corpus.agent.index) {
@@ -16998,6 +17091,18 @@ var indexDiagnostics = (config, result) => {
16998
17091
  "No ownership handoffs yet. Add routing.options.ownership, package frontmatter (package + editRoot), or a monorepo plugin."
16999
17092
  );
17000
17093
  }
17094
+ const reproducibility = checkIndexReproducibility(
17095
+ root,
17096
+ config.index?.outFile ?? ".doc-bridge/index.json",
17097
+ result.index.knowledge.map((entry) => entry.path)
17098
+ );
17099
+ if (reproducibility.ignored.length > 0) {
17100
+ const sample = reproducibility.ignored.slice(0, 5).map((entry) => `${entry.path} (${entry.rule})`);
17101
+ diagnostics.push(
17102
+ `${reproducibility.ignored.length} indexed path(s) are ignored by Git: ${sample.join(", ")}${reproducibility.ignored.length > sample.length ? `, and ${reproducibility.ignored.length - sample.length} more` : ""}.`,
17103
+ "A clean checkout will not have them, so it builds a different index. Add them to safety.exclude in doc-bridge.config.json."
17104
+ );
17105
+ }
17001
17106
  return diagnostics;
17002
17107
  };
17003
17108
  var diagnosticNextCommands = (config, result) => {
@@ -18125,7 +18230,7 @@ var runCli2 = (argv) => {
18125
18230
  });
18126
18231
  }
18127
18232
  const result = buildDocBridgeIndex({ root, config });
18128
- const diagnostics = indexDiagnostics(config, result);
18233
+ const diagnostics = indexDiagnostics(root, config, result);
18129
18234
  const handoffCount = Object.keys(result.index.handoffs ?? {}).length;
18130
18235
  writeJson2({
18131
18236
  ok: true,