@farming-labs/docs 0.1.53 → 0.1.54

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.
@@ -9,6 +9,7 @@ import matter from "gray-matter";
9
9
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
10
10
  import path from "node:path";
11
11
  import pc from "picocolors";
12
+ import { execFileSync } from "node:child_process";
12
13
 
13
14
  //#region src/cli/agent.ts
14
15
  const DEFAULT_TTC_BASE_URL = "https://api.thetokencompany.com";
@@ -50,6 +51,10 @@ function parseAgentCompactArgs(argv) {
50
51
  parsed.dryRun = true;
51
52
  continue;
52
53
  }
54
+ if (arg === "--changed") {
55
+ parsed.changed = true;
56
+ continue;
57
+ }
53
58
  if (arg === "--stale") {
54
59
  parsed.stale = true;
55
60
  continue;
@@ -175,6 +180,62 @@ function resolveCompressionEndpoint(rawBaseUrl) {
175
180
  if (/\/v1\/compress\/?$/i.test(baseUrl)) return baseUrl.replace(/\/+$/, "");
176
181
  return `${baseUrl.replace(/\/+$/, "")}/v1/compress`;
177
182
  }
183
+ function runGitCommand(rootDir, args) {
184
+ return execFileSync("git", args, {
185
+ cwd: rootDir,
186
+ encoding: "utf-8"
187
+ }).trim();
188
+ }
189
+ function normalizeGitRelativePath(value) {
190
+ return value.replace(/\\/g, "/").replace(/^\.\/+/, "").replace(/^\/+/, "");
191
+ }
192
+ function listGitChangedFiles(rootDir, contentDir) {
193
+ let gitRoot;
194
+ try {
195
+ gitRoot = runGitCommand(rootDir, ["rev-parse", "--show-toplevel"]);
196
+ } catch {
197
+ throw new Error("Use --changed inside a git repository.");
198
+ }
199
+ const contentDirAbs = path.resolve(rootDir, contentDir);
200
+ const relativeContentDir = normalizeGitRelativePath(path.relative(gitRoot, contentDirAbs));
201
+ if (relativeContentDir.startsWith("../")) throw new Error("Configured contentDir must live inside the current git repository for --changed.");
202
+ const pathspec = relativeContentDir || ".";
203
+ const changedFiles = /* @__PURE__ */ new Set();
204
+ const commands = [
205
+ [
206
+ "diff",
207
+ "--name-only",
208
+ "--",
209
+ pathspec
210
+ ],
211
+ [
212
+ "diff",
213
+ "--name-only",
214
+ "--cached",
215
+ "--",
216
+ pathspec
217
+ ],
218
+ [
219
+ "ls-files",
220
+ "--others",
221
+ "--exclude-standard",
222
+ "--",
223
+ pathspec
224
+ ]
225
+ ];
226
+ for (const args of commands) {
227
+ const output = runGitCommand(gitRoot, args);
228
+ if (!output) continue;
229
+ for (const line of output.split("\n")) {
230
+ const normalized = normalizeGitRelativePath(line.trim());
231
+ if (!normalized) continue;
232
+ const absolutePath = path.resolve(gitRoot, normalized);
233
+ const relativeToRoot = normalizeGitRelativePath(path.relative(rootDir, absolutePath));
234
+ if (relativeToRoot && !relativeToRoot.startsWith("../")) changedFiles.add(relativeToRoot);
235
+ }
236
+ }
237
+ return changedFiles;
238
+ }
178
239
  function resolveCompressionApiKey(explicitApiKey, explicitApiKeyEnv) {
179
240
  const candidateKeys = [
180
241
  explicitApiKeyEnv,
@@ -261,6 +322,13 @@ function readCurrentAgentDocument(target) {
261
322
  if (!target.hasAgentFile || !existsSync(target.agentPath)) return void 0;
262
323
  return parseGeneratedAgentDocument(readFileSync(target.agentPath, "utf-8"));
263
324
  }
325
+ function shouldCompactChangedAgentFile(target) {
326
+ const currentDocument = readCurrentAgentDocument(target);
327
+ if (!currentDocument) return false;
328
+ if (!currentDocument.provenance) return true;
329
+ if (currentDocument.provenance.sourceKind !== "agent-md") return false;
330
+ return hashGeneratedAgentContent(currentDocument.content) !== currentDocument.provenance.outputHash;
331
+ }
264
332
  function resolveSourceKindForCompaction(target, currentDocument) {
265
333
  if (!target.hasAgentFile) return "resolved-page";
266
334
  if (currentDocument?.provenance?.sourceKind === "resolved-page") return "resolved-page";
@@ -397,6 +465,15 @@ function resolveSelectedPages(pages, targets, entry, requested, includeAll) {
397
465
  }
398
466
  return resolved;
399
467
  }
468
+ function filterChangedPages(rootDir, contentDir, selectedPages) {
469
+ const changedFiles = listGitChangedFiles(rootDir, contentDir);
470
+ return selectedPages.filter(({ target }) => {
471
+ const relativePagePath = normalizeGitRelativePath(path.relative(rootDir, target.pagePath));
472
+ if (changedFiles.has(relativePagePath)) return true;
473
+ const relativeAgentPath = normalizeGitRelativePath(path.relative(rootDir, target.agentPath));
474
+ return changedFiles.has(relativeAgentPath) && shouldCompactChangedAgentFile(target);
475
+ });
476
+ }
400
477
  async function compactAgentDocs(options = {}) {
401
478
  const rootDir = process.cwd();
402
479
  const loadedEnv = loadProjectEnv(rootDir);
@@ -410,7 +487,7 @@ async function compactAgentDocs(options = {}) {
410
487
  if (resolvedOptions.all && resolvedOptions.pages && resolvedOptions.pages.length > 0) throw new Error("Use either --all or specific page arguments, not both.");
411
488
  if (resolvedOptions.includeMissing && !resolvedOptions.stale) throw new Error("Use --include-missing together with --stale.");
412
489
  const requestedPages = resolvedOptions.pages?.filter((value) => value.trim().length > 0) ?? [];
413
- if (!resolvedOptions.all && requestedPages.length === 0 && !resolvedOptions.stale) throw new Error("Pass --all, --stale, or at least one docs page slug/path to compact.");
490
+ if (!resolvedOptions.all && requestedPages.length === 0 && !resolvedOptions.stale && !resolvedOptions.changed) throw new Error("Pass --all, --changed, --stale, or at least one docs page slug/path to compact.");
414
491
  const pages = await createFilesystemDocsMcpSource({
415
492
  rootDir,
416
493
  entry,
@@ -418,8 +495,15 @@ async function compactAgentDocs(options = {}) {
418
495
  siteTitle
419
496
  }).getPages();
420
497
  if (pages.length === 0) throw new Error(`No docs content was found under ${contentDir}.`);
421
- const selectedPages = resolveSelectedPages(pages, scanDocsPageTargets(rootDir, contentDir, entry), entry, requestedPages, resolvedOptions.all === true || resolvedOptions.stale === true && requestedPages.length === 0);
422
- if (selectedPages.length === 0) throw new Error("No compactable docs pages matched the request.");
498
+ const selectedPages = resolveSelectedPages(pages, scanDocsPageTargets(rootDir, contentDir, entry), entry, requestedPages, resolvedOptions.all === true || resolvedOptions.stale === true && requestedPages.length === 0 || resolvedOptions.changed === true && requestedPages.length === 0);
499
+ const filteredPages = resolvedOptions.changed ? filterChangedPages(rootDir, contentDir, selectedPages) : selectedPages;
500
+ if (filteredPages.length === 0) {
501
+ if (resolvedOptions.changed) {
502
+ console.log(pc.green("No changed docs pages needed compaction."));
503
+ return;
504
+ }
505
+ throw new Error("No compactable docs pages matched the request.");
506
+ }
423
507
  let created = 0;
424
508
  let overwritten = 0;
425
509
  let processed = 0;
@@ -428,7 +512,7 @@ async function compactAgentDocs(options = {}) {
428
512
  let skippedUnknown = 0;
429
513
  let skippedMissing = 0;
430
514
  const requestedExplicitPages = requestedPages.length > 0;
431
- for (const { page, target } of selectedPages) {
515
+ for (const { page, target } of filteredPages) {
432
516
  const state = inspectAgentCompactionState(page, target, resolvedOptions);
433
517
  if (resolvedOptions.stale) {
434
518
  if (state.status === "fresh") {
@@ -461,7 +545,7 @@ async function compactAgentDocs(options = {}) {
461
545
  else created += 1;
462
546
  processed += 1;
463
547
  }
464
- if (resolvedOptions.dryRun) processed = selectedPages.length - skippedFresh - skippedModified - skippedUnknown - skippedMissing;
548
+ if (resolvedOptions.dryRun) processed = filteredPages.length - skippedFresh - skippedModified - skippedUnknown - skippedMissing;
465
549
  if (resolvedOptions.stale && processed === 0) {
466
550
  console.log(pc.green("No stale generated agent.md files needed updates."));
467
551
  if (skippedFresh + skippedModified + skippedUnknown + skippedMissing > 0) console.log(pc.dim(`Skipped ${skippedFresh} fresh, ${skippedModified} modified, ${skippedUnknown} unknown, and ${skippedMissing} missing page${skippedFresh + skippedModified + skippedUnknown + skippedMissing === 1 ? "" : "s"}.`));
@@ -483,6 +567,7 @@ ${pc.dim("Examples:")}
483
567
  ${pc.cyan("npx @farming-labs/docs@latest agent compact /docs/installation")}
484
568
  ${pc.cyan("npx @farming-labs/docs@latest agent compact --page installation --page configuration")}
485
569
  ${pc.cyan("npx @farming-labs/docs@latest agent compact --all")}
570
+ ${pc.cyan("npx @farming-labs/docs@latest agent compact --changed")}
486
571
  ${pc.cyan("npx @farming-labs/docs@latest agent compact --stale")}
487
572
  ${pc.cyan("npx @farming-labs/docs@latest agent compact --stale --include-missing")}
488
573
 
@@ -492,6 +577,7 @@ ${pc.dim("Per-page override:")}
492
577
  ${pc.dim("Options:")}
493
578
  ${pc.cyan("--all")} Compact every folder-based docs page under the configured contentDir
494
579
  ${pc.cyan("--page <slug|path>")} Add a page explicitly (repeatable); positional page args work too
580
+ ${pc.cyan("--changed")} Compact only docs pages changed in the current git working tree
495
581
  ${pc.cyan("--stale")} Re-compact only stale generated agent.md files
496
582
  ${pc.cyan("--include-missing")} With ${pc.cyan("--stale")}, also create missing agent.md files for explicit pages or pages that define ${pc.cyan("agent.tokenBudget")}
497
583
  ${pc.cyan("--config <path>")} Use a custom docs config path instead of ${pc.dim("docs.config.ts[x]")}
@@ -75,7 +75,7 @@ async function main() {
75
75
  const { runMcp } = await import("../mcp-C-TmMrdw.mjs");
76
76
  await runMcp(mcpOptions);
77
77
  } else if (parsedCommand.command === "agent" && subcommand === "compact") {
78
- const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-C3rj3o1l.mjs");
78
+ const { compactAgentDocs, parseAgentCompactArgs, printAgentCompactHelp } = await import("../agent-B-6iQbS2.mjs");
79
79
  const agentCompactOptions = parseAgentCompactArgs(args.slice(2));
80
80
  if (agentCompactOptions.help) {
81
81
  printAgentCompactHelp();
@@ -85,11 +85,11 @@ async function main() {
85
85
  } else if (parsedCommand.command === "agent") {
86
86
  console.error(pc.red(`Unknown agent subcommand: ${subcommand ?? "(missing)"}`));
87
87
  console.error();
88
- const { printAgentCompactHelp } = await import("../agent-C3rj3o1l.mjs");
88
+ const { printAgentCompactHelp } = await import("../agent-B-6iQbS2.mjs");
89
89
  printAgentCompactHelp();
90
90
  process.exit(1);
91
91
  } else if (parsedCommand.command === "doctor") {
92
- const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-B0xEGzBU.mjs");
92
+ const { parseDoctorArgs, printDoctorHelp, runDoctor } = await import("../doctor-BiY_aSS1.mjs");
93
93
  const doctorOptions = parseDoctorArgs(args.slice(1));
94
94
  if (doctorOptions.help) {
95
95
  printDoctorHelp();
@@ -152,6 +152,7 @@ ${pc.dim("Options for mcp:")}
152
152
  ${pc.dim("Options for agent compact:")}
153
153
  ${pc.cyan("agent compact <page...>")} Compact pages and write sibling ${pc.dim("agent.md")} files
154
154
  ${pc.cyan("agent compact --all")} Compact every folder-based docs page
155
+ ${pc.cyan("agent compact --changed")} Compact only docs pages changed in the current git working tree
155
156
  ${pc.cyan("agent compact --stale")} Refresh only stale generated ${pc.dim("agent.md")} files
156
157
  ${pc.cyan("--page <slug|path>")} Repeatable explicit page flag; positional page args work too
157
158
  ${pc.cyan("--include-missing")} With ${pc.cyan("--stale")}, also create explicit or token-budget pages missing ${pc.dim("agent.md")}
@@ -3,7 +3,7 @@ import "./api-reference-GDAEzQn1.mjs";
3
3
  import { createFilesystemDocsMcpSource, resolveDocsMcpConfig } from "./mcp.mjs";
4
4
  import "./server.mjs";
5
5
  import { a as loadProjectEnv, c as readNavTitle, d as readTopLevelStringProperty, f as resolveDocsConfigPath, i as loadDocsConfigModule, o as readBooleanProperty, p as resolveDocsContentDir, r as extractTopLevelConfigObject, t as extractNestedObjectLiteral } from "./config-Si-yUfM_.mjs";
6
- import { inspectAgentCompactionState, scanDocsPageTargets } from "./agent-C3rj3o1l.mjs";
6
+ import { inspectAgentCompactionState, scanDocsPageTargets } from "./agent-B-6iQbS2.mjs";
7
7
  import { t as detectFramework } from "./utils-CpTFbAiS.mjs";
8
8
  import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
9
9
  import path from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",