@mrciphersmith/keryx 0.3.83 → 0.3.84

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 (2) hide show
  1. package/dist/cli.js +170 -31
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -21098,7 +21098,7 @@ var package_default;
21098
21098
  var init_package = __esm(() => {
21099
21099
  package_default = {
21100
21100
  name: "@mrciphersmith/keryx",
21101
- version: "0.3.83",
21101
+ version: "0.3.84",
21102
21102
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
21103
21103
  private: false,
21104
21104
  publishConfig: {
@@ -180440,7 +180440,7 @@ var init_project_reviewers = __esm(() => {
180440
180440
  });
180441
180441
 
180442
180442
  // src/gdskills/import-skills.ts
180443
- import { readdir as readdir57, readFile as readFile178, stat as stat37 } from "fs/promises";
180443
+ import { lstat as lstat19, readdir as readdir57, readFile as readFile178, stat as stat37 } from "fs/promises";
180444
180444
  import { homedir as homedir15 } from "os";
180445
180445
  import path342 from "path";
180446
180446
  function emptyOnlyError(label) {
@@ -180469,10 +180469,18 @@ async function importProjectSkills(options) {
180469
180469
  const existingFlags = await projectReviewerFlags(options.projectRoot);
180470
180470
  const plans = await Promise.all(sources.map((source) => planOne(options, source)));
180471
180471
  const refusedPackages = new Set(plans.flatMap((plan) => ("row" in plan) && isRefusedStatus(plan.row.status) ? [plan.row.name] : []));
180472
- const ruleDecisions = await decideReferencedRules(options, sources.filter((source) => !skippedAsDeprecated(source)).filter((source) => options.force === true || !BUNDLED_NAMES.has(source.name)), refusedPackages);
180472
+ const candidates = sources.filter((source) => !skippedAsDeprecated(source)).filter((source) => options.force === true || !BUNDLED_NAMES.has(source.name));
180473
+ const ruleDecisions = await decideReferencedRules(options, candidates, refusedPackages);
180474
+ const templatePlans = [];
180475
+ for (const source of candidates) {
180476
+ if (source.sourcePath === undefined || refusedPackages.has(source.name))
180477
+ continue;
180478
+ templatePlans.push(...await planTemplates(options, { module: source.module, name: source.name, sourcePath: source.sourcePath }, options.force === true));
180479
+ }
180473
180480
  await refuseUncontainedWrites(label, options.projectRoot, [
180474
180481
  ...plans.flatMap((plan) => ("write" in plan) ? projectSkillWritePaths(plan.write.module, plan.write.name, "single") : []),
180475
- ...ruleDecisions.flatMap((decision) => ("placement" in decision) ? [decision.placement.target] : [])
180482
+ ...ruleDecisions.flatMap((decision) => ("placement" in decision) ? [decision.placement.target] : []),
180483
+ ...templatePlans.flatMap((plan) => ("write" in plan) ? [plan.write.target] : [])
180476
180484
  ]);
180477
180485
  const imported = [];
180478
180486
  for (const plan of plans) {
@@ -180483,11 +180491,13 @@ async function importProjectSkills(options) {
180483
180491
  for (const decision of ruleDecisions) {
180484
180492
  rules.push("row" in decision ? decision.row : options.dryRun ? unwrittenRow(decision.placement, [decision.dryRunReason, ...gateNotes(decision.gate, true)].filter(isString4).join(" \u2014 ") || undefined, decision.gate.security) : await writePlacedRule(options, decision.placement, decision.gate));
180485
180493
  }
180494
+ const templates = await writeTemplates(options, templatePlans);
180486
180495
  return {
180487
180496
  from: options.from,
180488
180497
  only: options.only ?? [],
180489
180498
  imported,
180490
180499
  rules,
180500
+ templates,
180491
180501
  dryRun: options.dryRun === true,
180492
180502
  force: options.force === true
180493
180503
  };
@@ -180517,18 +180527,29 @@ async function updateProjectSkills(options) {
180517
180527
  for (const entry of selected) {
180518
180528
  plans.push(await planUpdate(options, entry));
180519
180529
  }
180520
- await refuseUncontainedWrites("keryx skills update", options.projectRoot, plans.flatMap((plan) => ("content" in plan) ? projectSkillWritePaths(projectSkillSlug(plan.entry.module), projectSkillSlug(plan.entry.name), "single") : []));
180530
+ const templatePlans = [];
180531
+ for (const plan of plans) {
180532
+ if (!("content" in plan) || plan.sourcePath === undefined)
180533
+ continue;
180534
+ templatePlans.push(...await planTemplates(options, { module: projectSkillSlug(plan.entry.module), name: projectSkillSlug(plan.entry.name), sourcePath: plan.sourcePath }, true));
180535
+ }
180536
+ await refuseUncontainedWrites("keryx skills update", options.projectRoot, [
180537
+ ...plans.flatMap((plan) => ("content" in plan) ? projectSkillWritePaths(projectSkillSlug(plan.entry.module), projectSkillSlug(plan.entry.name), "single") : []),
180538
+ ...templatePlans.flatMap((plan) => ("write" in plan) ? [plan.write.target] : [])
180539
+ ]);
180521
180540
  const existingFlags = await projectReviewerFlags(options.projectRoot);
180522
180541
  const imported = [];
180523
180542
  for (const plan of plans) {
180524
180543
  imported.push("row" in plan ? plan.row : await writeUpdate(options, plan));
180525
180544
  }
180526
180545
  addFlagWarnings(imported, plans.map((plan) => ("content" in plan) ? plan : undefined), existingFlags);
180546
+ const templates = await writeTemplates(options, templatePlans);
180527
180547
  return {
180528
180548
  from: options.from ?? "(each skill Origin)",
180529
180549
  only: [],
180530
180550
  imported,
180531
180551
  rules: [],
180552
+ templates,
180532
180553
  dryRun: options.dryRun === true,
180533
180554
  force: true
180534
180555
  };
@@ -180566,9 +180587,10 @@ function renderImportProjectSkillsMarkdown(result) {
180566
180587
  if (result.imported.some((row) => row.status === "skipped" && row.reason === DEPRECATED_REASON)) {
180567
180588
  lines.push("A package whose frontmatter says `deprecated: true` is skipped in a tree import. To import one anyway, pass its own directory as --from.", "");
180568
180589
  }
180569
- if (result.imported.some((row) => isRefusedStatus(row.status)) || result.rules.some((rule) => isRefusedStatus(rule.status))) {
180590
+ if (result.imported.some((row) => isRefusedStatus(row.status)) || result.rules.some((rule) => isRefusedStatus(rule.status)) || result.templates.some((template) => isRefusedStatus(template.status))) {
180570
180591
  lines.push(refusedNote(result), "");
180571
180592
  }
180593
+ lines.push(...renderTemplateLines(result.templates));
180572
180594
  if (result.rules.length > 0) {
180573
180595
  lines.push("## rules the skills cite", "");
180574
180596
  for (const rule of result.rules) {
@@ -180620,9 +180642,10 @@ function renderUpdateProjectSkillsMarkdown(result) {
180620
180642
  }
180621
180643
  }
180622
180644
  lines.push("");
180623
- if (result.imported.some((row) => isRefusedStatus(row.status))) {
180645
+ if (result.imported.some((row) => isRefusedStatus(row.status)) || result.templates.some((template) => isRefusedStatus(template.status))) {
180624
180646
  lines.push(refusedNote(result), "");
180625
180647
  }
180648
+ lines.push(...renderTemplateLines(result.templates));
180626
180649
  if (result.imported.some((row) => row.module === PROJECT_REVIEWER_MODULE && isWrittenRow(row))) {
180627
180650
  lines.push(`Reviewers: \`keryx review reviewers\` must list every updated ${PROJECT_REVIEWER_MODULE}/* name. That is the same call review-orchestrator makes.`);
180628
180651
  }
@@ -180927,12 +180950,12 @@ function isRefusedStatus(status) {
180927
180950
  return status === "refused" || status === "would-refuse";
180928
180951
  }
180929
180952
  function refusedNote(result) {
180930
- const refused = [...result.imported, ...result.rules].filter((row) => isRefusedStatus(row.status));
180953
+ const refused = [...result.imported, ...result.rules, ...result.templates].filter((row) => isRefusedStatus(row.status));
180931
180954
  const overridable = refused.some((row) => row.security?.findings.some((finding) => finding.policyId.startsWith("prompt-injection.")));
180932
180955
  return overridable ? `${REFUSED_NOTE} ${ALLOW_FLAGGED_HINT}` : REFUSED_NOTE;
180933
180956
  }
180934
180957
  function exitNonZeroOnRefusal(result) {
180935
- if ([...result.imported, ...result.rules].some((row) => row.status === "refused")) {
180958
+ if ([...result.imported, ...result.rules, ...result.templates].some((row) => row.status === "refused")) {
180936
180959
  process.exitCode = 1;
180937
180960
  }
180938
180961
  }
@@ -181131,6 +181154,7 @@ async function planUpdate(options, entry) {
181131
181154
  return skippedRow("no Origin recorded", "");
181132
181155
  }
181133
181156
  let content;
181157
+ let sourcePath;
181134
181158
  if (isHttpsSkillUrl(origin)) {
181135
181159
  const fetcher = options.fetcher ?? DEFAULT_FETCHER;
181136
181160
  const response = await fetcher(githubBlobToRaw(origin));
@@ -181144,6 +181168,7 @@ async function planUpdate(options, entry) {
181144
181168
  return skippedRow("origin file can no longer be read", origin);
181145
181169
  }
181146
181170
  content = await readFile178(resolved, "utf8");
181171
+ sourcePath = resolved;
181147
181172
  origin = portableOriginRef(resolved, options.projectRoot);
181148
181173
  }
181149
181174
  const verdict = await gateText(options.projectRoot, plannedSkillText(content, { name: entry.name, module: entry.module, origin }), toPosix(path342.join(entry.path, "SKILL.md")), options.allowFlagged === true);
@@ -181152,7 +181177,7 @@ async function planUpdate(options, entry) {
181152
181177
  row: refusedSkillRow({ name: entry.name, module: entry.module, path: entry.path, origin }, verdict, options.dryRun === true)
181153
181178
  };
181154
181179
  }
181155
- return { entry, origin, content, gate: verdict };
181180
+ return { entry, origin, content, gate: verdict, ...sourcePath !== undefined ? { sourcePath } : {} };
181156
181181
  }
181157
181182
  async function writeUpdate(options, { entry, origin, content, gate }) {
181158
181183
  const notes = reviewNotes(content, entry.module);
@@ -181394,6 +181419,106 @@ async function decideReferencedRules(options, sources, refusedPackages) {
181394
181419
  }
181395
181420
  return decisions;
181396
181421
  }
181422
+ async function listTemplateFiles(sourcePath) {
181423
+ const root = path342.join(path342.dirname(sourcePath), TEMPLATES_DIR);
181424
+ try {
181425
+ if (!(await lstat19(root)).isDirectory())
181426
+ return [];
181427
+ } catch {
181428
+ return [];
181429
+ }
181430
+ const files = [];
181431
+ const walk = async (dir, prefix) => {
181432
+ const entries = (await readdir57(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name));
181433
+ for (const entry of entries) {
181434
+ const rel = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
181435
+ if (entry.isDirectory())
181436
+ await walk(path342.join(dir, entry.name), rel);
181437
+ else if (entry.isFile())
181438
+ files.push(rel);
181439
+ }
181440
+ };
181441
+ await walk(root, "");
181442
+ return files;
181443
+ }
181444
+ async function planTemplates(options, pkg, replace) {
181445
+ const plans = [];
181446
+ const packageKey = `${pkg.module}/${pkg.name}`;
181447
+ const packagePath = projectSkillPackagePath(pkg.module, pkg.name);
181448
+ for (const file of await listTemplateFiles(pkg.sourcePath)) {
181449
+ const target = `${packagePath}/${TEMPLATES_DIR}/${file}`;
181450
+ const base = { package: packageKey, file, path: target };
181451
+ const bytes = await readFile178(path342.join(path342.dirname(pkg.sourcePath), TEMPLATES_DIR, file));
181452
+ const text = bytes.toString("utf8");
181453
+ if (!Buffer.from(text, "utf8").equals(bytes)) {
181454
+ plans.push({ row: { ...base, status: "skipped", reason: "not UTF-8 text; the importer copies text templates only" } });
181455
+ continue;
181456
+ }
181457
+ const targetAbs = path342.join(options.projectRoot, target);
181458
+ const exists = await pathExists(targetAbs);
181459
+ if (exists && !await isRegularFile(targetAbs)) {
181460
+ plans.push({ row: { ...base, status: "skipped", reason: `${target} is not a regular file; nothing was written there` } });
181461
+ continue;
181462
+ }
181463
+ const existing = exists ? await readFile178(targetAbs, "utf8") : undefined;
181464
+ if (existing === text) {
181465
+ plans.push({ row: { ...base, status: "unchanged" } });
181466
+ continue;
181467
+ }
181468
+ const verdict = await gateText(options.projectRoot, text, target, options.allowFlagged === true);
181469
+ if (verdict.kind === "write" && existing === verdict.content) {
181470
+ plans.push({ row: { ...base, status: "unchanged" } });
181471
+ continue;
181472
+ }
181473
+ if (existing !== undefined && !replace) {
181474
+ plans.push({ row: { ...base, status: "differs", reason: "left as it is; pass --force to replace it with the package's" } });
181475
+ continue;
181476
+ }
181477
+ if (verdict.kind !== "write") {
181478
+ plans.push({
181479
+ row: {
181480
+ ...base,
181481
+ status: options.dryRun ? "would-refuse" : "refused",
181482
+ reason: verdict.kind === "blocked" ? `blocked by the security gate: ${verdict.reason}` : verdict.reason,
181483
+ ...verdict.kind === "refuse" ? { security: verdict.security } : {}
181484
+ }
181485
+ });
181486
+ continue;
181487
+ }
181488
+ plans.push({ write: { row: base, target, exists: existing !== undefined, gate: verdict } });
181489
+ }
181490
+ return plans;
181491
+ }
181492
+ async function writeTemplates(options, plans) {
181493
+ const rows = [];
181494
+ for (const plan of plans) {
181495
+ if ("row" in plan) {
181496
+ rows.push(plan.row);
181497
+ continue;
181498
+ }
181499
+ const { row, target, exists, gate } = plan.write;
181500
+ const parts = gateRowParts(gate, options.dryRun === true);
181501
+ if (options.dryRun !== true)
181502
+ await writeContained(options.projectRoot, target, gate.content);
181503
+ rows.push({
181504
+ ...row,
181505
+ status: options.dryRun === true ? exists ? "would-overwrite" : "would-import" : exists ? "overwritten" : "imported",
181506
+ ...parts.reason !== undefined ? { reason: parts.reason } : {},
181507
+ ...parts.security !== undefined ? { security: parts.security } : {}
181508
+ });
181509
+ }
181510
+ return rows;
181511
+ }
181512
+ function renderTemplateLines(templates) {
181513
+ if (templates.length === 0)
181514
+ return [];
181515
+ const lines = ["## templates the packages ship", ""];
181516
+ for (const template of templates) {
181517
+ lines.push(`- ${template.package} ${TEMPLATES_DIR}/${template.file}: ${template.status}${template.reason ? ` \u2014 ${template.reason}` : ""}`);
181518
+ }
181519
+ lines.push("");
181520
+ return lines;
181521
+ }
181397
181522
  function inferNameFromPath(filePath, content) {
181398
181523
  const fromFrontmatter = frontmatterName(content);
181399
181524
  if (fromFrontmatter)
@@ -181577,7 +181702,7 @@ A package directory, or a SKILL.md or URL name, with no letter or digit is
181577
181702
  refused: it has no name to be written under.
181578
181703
 
181579
181704
  Before anything is written, every destination \u2014 the package, the registry in
181580
- .metaproject/metaproject.json, the catalog and each rule \u2014 is checked for a
181705
+ .metaproject/metaproject.json, the catalog, each rule and each template \u2014 is checked for a
181581
181706
  symlink on the way that resolves outside the project. One is refused, and the
181582
181707
  import writes nothing; --dry-run refuses it the same way.
181583
181708
 
@@ -181588,6 +181713,18 @@ name, the copy goes to .metaproject/rules/project/<dir>/<name>.mdc, which a
181588
181713
  reviewer's reference resolves to first; an existing copy there that differs is
181589
181714
  replaced only with --force.
181590
181715
 
181716
+ A package's templates/ directory (a report overlay, say) is copied, with its
181717
+ subdirectories, to .metaproject/project-skills/<module>/<name>/templates/, byte
181718
+ for byte, so the skill reads it from the project and not from the repository it
181719
+ was imported from. Only regular text files are copied; a symlink or a non-UTF-8
181720
+ file is not. Each file gets a row: imported (would-import on a dry run),
181721
+ unchanged when the project has it, differs when the project's copy is another
181722
+ file \u2014 left as it is, and replaced only with --force. Templates go through the
181723
+ same security gate as SKILL.md. A package that is already in the project still
181724
+ gets its templates, as it gets its rules. A URL source has none.
181725
+ \`keryx skills update\` re-reads them with SKILL.md; \`keryx skills remove\` deletes
181726
+ them with the package.
181727
+
181591
181728
  Examples:
181592
181729
  keryx skills import --from ./overlays --module review --only 'review-house-*'
181593
181730
  keryx skills import --from ./overlays/skills/review-house-api --module review
@@ -181599,6 +181736,8 @@ function printSkillsUpdateHelp() {
181599
181736
  console.log(`keryx skills update
181600
181737
 
181601
181738
  Re-read a project-skill's Origin and overwrite SKILL.md when the source moved on.
181739
+ The templates/ directory beside a local Origin is re-read with it, and each
181740
+ file in it replaces the project's copy.
181602
181741
 
181603
181742
  Usage:
181604
181743
  keryx skills update <module>/<name> [--from <new-origin>] [--dry-run] [--allow-flagged] [--json]
@@ -181618,7 +181757,7 @@ Examples:
181618
181757
  var BUNDLED_NAMES, PATHS_NONE_WARNING = 'paths: none \u2014 dispatched on every round. Declare `metadata.paths: "<glob>, <glob>"` in its frontmatter to gate it on the diff.', DEFAULT_FETCHER = async (url) => {
181619
181758
  const response = await fetch(url);
181620
181759
  return { ok: response.ok, status: response.status, text: await response.text() };
181621
- }, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString4 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
181760
+ }, DEPRECATED_REASON = "deprecated", KNOWN_CATEGORIES, isString4 = (value) => value !== undefined, REFUSED_NOTE = "A refused file was not written.", ALLOW_FLAGGED_HINT = "A prompt-injection refusal can be overridden after you read the file: run the command again with --allow-flagged.", IMPORT_HEADER_LABELS, IMPORT_HEADER_LINE, BYTE_ORDER_MARK = 65279, TEMPLATES_DIR = "templates", ALLOW_FLAGGED_FLAG = "--allow-flagged", SECURITY_GATE_HELP = `Security gate: a SKILL.md or rule is written as the gate returns it, so a secret is
181622
181761
  masked and the row says it was redacted, and any other finding is reported on the
181623
181762
  row. Text with a prompt-injection finding is refused in every security mode: the
181624
181763
  row is \`refused\` (\`would-refuse\` on a dry run) with the gate's own summary,
@@ -181660,7 +181799,7 @@ var init_import_skills = __esm(() => {
181660
181799
  });
181661
181800
 
181662
181801
  // src/gdskills/remove-skill.ts
181663
- import { lstat as lstat19, readdir as readdir58, readFile as readFile179, realpath as realpath23 } from "fs/promises";
181802
+ import { lstat as lstat20, readdir as readdir58, readFile as readFile179, realpath as realpath23 } from "fs/promises";
181664
181803
  import path343 from "path";
181665
181804
  async function removeProjectSkill(projectRoot, options) {
181666
181805
  if (!await pathExists(path343.join(projectRoot, ".metaproject"))) {
@@ -181855,7 +181994,7 @@ function normalizeSkillKey(input) {
181855
181994
  }
181856
181995
  async function lstatOrMissing(target) {
181857
181996
  try {
181858
- return await lstat19(target);
181997
+ return await lstat20(target);
181859
181998
  } catch (error) {
181860
181999
  if (isMissingPathError(error))
181861
182000
  return;
@@ -181920,7 +182059,7 @@ async function locateRewritable(root, relativePath) {
181920
182059
  if (absolute === root || !isPathInside(root, absolute)) {
181921
182060
  throw new Error(`${LABEL}: ${relativePath} resolves to ${absolute}, outside the project. Refusing to rewrite a file the project does not own. ${UNCHANGED}`);
181922
182061
  }
181923
- if (!(await lstat19(absolute)).isFile()) {
182062
+ if (!(await lstat20(absolute)).isFile()) {
181924
182063
  throw new Error(`${LABEL}: ${relativePath} is not a regular file. ${UNCHANGED}`);
181925
182064
  }
181926
182065
  const resolvedPath = toPosix(path343.relative(root, absolute));
@@ -195636,7 +195775,7 @@ var init_tools3 = __esm(() => {
195636
195775
 
195637
195776
  // src/mcp/resources.ts
195638
195777
  import path364 from "path";
195639
- import { lstat as lstat20, readdir as readdir66, realpath as realpath25, stat as stat39 } from "fs/promises";
195778
+ import { lstat as lstat21, readdir as readdir66, realpath as realpath25, stat as stat39 } from "fs/promises";
195640
195779
  function mimeForPath(filePath) {
195641
195780
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
195642
195781
  return "application/json";
@@ -195659,7 +195798,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
195659
195798
  if (!await pathExists(root)) {
195660
195799
  return [];
195661
195800
  }
195662
- const ownerInfo = await lstat20(ownerRoot).catch(() => null);
195801
+ const ownerInfo = await lstat21(ownerRoot).catch(() => null);
195663
195802
  if (!ownerInfo || !ownerInfo.isDirectory() || ownerInfo.isSymbolicLink())
195664
195803
  return [];
195665
195804
  const ownerReal = await realpath25(ownerRoot).catch(() => null);
@@ -218207,7 +218346,7 @@ var init_types13 = __esm(() => {
218207
218346
  });
218208
218347
 
218209
218348
  // src/bundle/paths.ts
218210
- import { lstat as lstat21, readFile as readFile200 } from "fs/promises";
218349
+ import { lstat as lstat22, readFile as readFile200 } from "fs/promises";
218211
218350
  import path391 from "path";
218212
218351
  function scopeRoot(scope, ctx) {
218213
218352
  if (scope === "user") {
@@ -218401,7 +218540,7 @@ async function resolveContainedPathForWrite(root, relPath) {
218401
218540
  async function refuseSymlinkChain(root, relPath) {
218402
218541
  const rootResolved = path391.resolve(root);
218403
218542
  try {
218404
- const rootStat = await lstat21(rootResolved);
218543
+ const rootStat = await lstat22(rootResolved);
218405
218544
  if (rootStat.isSymbolicLink()) {
218406
218545
  return {
218407
218546
  ok: false,
@@ -218414,7 +218553,7 @@ async function refuseSymlinkChain(root, relPath) {
218414
218553
  for (const segment of segments) {
218415
218554
  current = path391.join(current, segment);
218416
218555
  try {
218417
- const stat = await lstat21(current);
218556
+ const stat = await lstat22(current);
218418
218557
  if (stat.isSymbolicLink()) {
218419
218558
  return {
218420
218559
  ok: false,
@@ -218464,7 +218603,7 @@ var init_paths5 = __esm(() => {
218464
218603
  });
218465
218604
 
218466
218605
  // src/bundle/archive.ts
218467
- import { readdir as readdir69, readFile as readFile201, lstat as lstat22 } from "fs/promises";
218606
+ import { readdir as readdir69, readFile as readFile201, lstat as lstat23 } from "fs/promises";
218468
218607
  import path392 from "path";
218469
218608
  import { gzipSync, gunzipSync } from "zlib";
218470
218609
  function resolveLimits(limits) {
@@ -218672,7 +218811,7 @@ async function openBundle(bundlePath, limits) {
218672
218811
  const resolved = resolveLimits(limits);
218673
218812
  let stat;
218674
218813
  try {
218675
- stat = await lstat22(bundlePath);
218814
+ stat = await lstat23(bundlePath);
218676
218815
  } catch {
218677
218816
  return { ok: false, refusal: { reason: BUNDLE_REFUSAL.notABundle, message: `no such path: ${bundlePath}` } };
218678
218817
  }
@@ -218686,7 +218825,7 @@ async function openBundle(bundlePath, limits) {
218686
218825
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
218687
218826
  const abs = path392.join(dir, entry.name);
218688
218827
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
218689
- const entryStat = await lstat22(abs);
218828
+ const entryStat = await lstat23(abs);
218690
218829
  if (entryStat.isSymbolicLink()) {
218691
218830
  return { reason: BUNDLE_REFUSAL.symlinkRefused, path: rel, message: `${rel} is a symlink; bundles may not contain symlinks` };
218692
218831
  }
@@ -219561,7 +219700,7 @@ var init_learned_pattern_schema = __esm(() => {
219561
219700
 
219562
219701
  // src/bundle/export.ts
219563
219702
  import { execFileSync as execFileSync3 } from "child_process";
219564
- import { mkdir as mkdir81, readdir as readdir70, readFile as readFile202, writeFile as writeFile57, lstat as lstat23 } from "fs/promises";
219703
+ import { mkdir as mkdir81, readdir as readdir70, readFile as readFile202, writeFile as writeFile57, lstat as lstat24 } from "fs/promises";
219565
219704
  import { existsSync as existsSync60 } from "fs";
219566
219705
  import path393 from "path";
219567
219706
  function globToRegExp5(glob) {
@@ -219648,7 +219787,7 @@ async function walkFiles2(dir, relPrefix, out, skipped) {
219648
219787
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
219649
219788
  const abs = path393.join(dir, entry.name);
219650
219789
  const rel = relPrefix.length > 0 ? `${relPrefix}/${entry.name}` : entry.name;
219651
- const st = await lstat23(abs);
219790
+ const st = await lstat24(abs);
219652
219791
  if (st.isSymbolicLink()) {
219653
219792
  skipped.push({ path: rel, reason: "symlink" });
219654
219793
  continue;
@@ -219696,7 +219835,7 @@ async function collectAgents(root, skipped) {
219696
219835
  const candidates = [];
219697
219836
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
219698
219837
  const abs = path393.join(base, entry.name);
219699
- const st = await lstat23(abs);
219838
+ const st = await lstat24(abs);
219700
219839
  if (st.isSymbolicLink()) {
219701
219840
  skipped.push({ path: `agents/${entry.name}`, reason: "symlink" });
219702
219841
  continue;
@@ -219721,7 +219860,7 @@ async function collectMemory(root, skipped) {
219721
219860
  }
219722
219861
  for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
219723
219862
  const abs = path393.join(base, entry.name);
219724
- const st = await lstat23(abs);
219863
+ const st = await lstat24(abs);
219725
219864
  if (st.isSymbolicLink()) {
219726
219865
  skipped.push({ path: `memory/${folder}/${entry.name}`, reason: "symlink" });
219727
219866
  continue;
@@ -219737,7 +219876,7 @@ async function collectHookConfig(root) {
219737
219876
  const abs = path393.join(root, "hooks.json");
219738
219877
  if (!existsSync60(abs))
219739
219878
  return [];
219740
- const st = await lstat23(abs);
219879
+ const st = await lstat24(abs);
219741
219880
  if (st.isSymbolicLink() || !st.isFile())
219742
219881
  return [];
219743
219882
  return [{ bundlePath: "hooks.json", kind: "hook-config", absolutePath: abs }];
@@ -220118,7 +220257,7 @@ var init_applied_state = __esm(() => {
220118
220257
  });
220119
220258
 
220120
220259
  // src/lib/private-dir.ts
220121
- import { lstat as lstat24, mkdir as mkdir82, open as open11, readFile as readFile204 } from "fs/promises";
220260
+ import { lstat as lstat25, mkdir as mkdir82, open as open11, readFile as readFile204 } from "fs/promises";
220122
220261
  import path395 from "path";
220123
220262
  async function checkPrivateDirGitignore(dir, root) {
220124
220263
  if (root !== undefined) {
@@ -220137,7 +220276,7 @@ async function checkPrivateDirGitignore(dir, root) {
220137
220276
  }
220138
220277
  let dirStats;
220139
220278
  try {
220140
- dirStats = await lstat24(dir);
220279
+ dirStats = await lstat25(dir);
220141
220280
  } catch (error) {
220142
220281
  if (isNotFound(error)) {
220143
220282
  return { ok: true, action: "create" };
@@ -220158,7 +220297,7 @@ async function checkPrivateDirGitignore(dir, root) {
220158
220297
  const gitignorePath = path395.join(dir, ".gitignore");
220159
220298
  let stats;
220160
220299
  try {
220161
- stats = await lstat24(gitignorePath);
220300
+ stats = await lstat25(gitignorePath);
220162
220301
  } catch (error) {
220163
220302
  if (isNotFound(error)) {
220164
220303
  return { ok: true, action: "create" };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.83",
3
+ "version": "0.3.84",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {