@mrciphersmith/keryx 0.2.97 → 0.2.98

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 (165) hide show
  1. package/dist/cli.js +569 -235
  2. package/dist/core.js +1 -1
  3. package/package.json +1 -1
  4. package/src/gdskills/bundled/rules/core/api-contracts.mdc +1 -0
  5. package/src/gdskills/bundled/rules/core/code-style-patterns.mdc +1 -0
  6. package/src/gdskills/bundled/rules/core/database-patterns.mdc +1 -0
  7. package/src/gdskills/bundled/rules/core/documentation-management.mdc +33 -38
  8. package/src/gdskills/bundled/rules/core/error-handling.mdc +1 -11
  9. package/src/gdskills/bundled/rules/core/execution-metrics.md +1 -2
  10. package/src/gdskills/bundled/rules/core/frontend-assistant.mdc +1 -0
  11. package/src/gdskills/bundled/rules/core/git-concurrency.mdc +101 -0
  12. package/src/gdskills/bundled/rules/core/implementation-plans.mdc +23 -11
  13. package/src/gdskills/bundled/rules/core/mobx-store-template.mdc +1 -0
  14. package/src/gdskills/bundled/rules/core/nestjs-dto.mdc +1 -0
  15. package/src/gdskills/bundled/rules/core/playwright-testing.mdc +1 -0
  16. package/src/gdskills/bundled/rules/core/requirements-management.mdc +15 -11
  17. package/src/gdskills/bundled/rules/core/rule-management-workflow.mdc +29 -14
  18. package/src/gdskills/bundled/rules/core/shared-definitions.mdc +1 -1
  19. package/src/gdskills/bundled/rules/core/skill-lifecycle.mdc +9 -5
  20. package/src/gdskills/bundled/rules/core/skills-storage-workflow.mdc +59 -16
  21. package/src/gdskills/bundled/rules/core/storybook-guidelines.mdc +1 -0
  22. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.codex.md +48 -71
  23. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.cursor.md +48 -71
  24. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.md +48 -71
  25. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.opencode.md +48 -71
  26. package/src/gdskills/bundled/skills/orchestration/code-verifier/SKILL.zed.md +48 -71
  27. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.codex.md +4 -4
  28. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.cursor.md +4 -4
  29. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.md +4 -4
  30. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.opencode.md +4 -4
  31. package/src/gdskills/bundled/skills/orchestration/context-collector/SKILL.zed.md +4 -4
  32. package/src/gdskills/bundled/skills/orchestration/context-collector/orchestrator-prompt.md +2 -2
  33. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.codex.md +12 -22
  34. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.cursor.md +12 -22
  35. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.detail.md +12 -22
  36. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.md +12 -22
  37. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.opencode.md +12 -22
  38. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/SKILL.zed.md +12 -22
  39. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.md +2 -2
  40. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/analysis-request.template.md +1 -1
  41. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/input-contract.schema.json +4 -4
  42. package/src/gdskills/bundled/skills/orchestration/feature-analyzer/orchestrator-prompt.md +2 -2
  43. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.codex.md +2 -2
  44. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.cursor.md +2 -2
  45. package/src/gdskills/bundled/skills/orchestration/feature-dev/SKILL.md +2 -2
  46. package/src/gdskills/bundled/skills/orchestration/flow-orchestrator/SKILL.md +46 -4
  47. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.codex.md +2 -2
  48. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.cursor.md +2 -2
  49. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.md +2 -2
  50. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.opencode.md +2 -2
  51. package/src/gdskills/bundled/skills/orchestration/issue-analyzer/SKILL.zed.md +2 -2
  52. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.codex.md +3 -3
  53. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.cursor.md +3 -3
  54. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.md +3 -3
  55. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.opencode.md +3 -3
  56. package/src/gdskills/bundled/skills/orchestration/job-documenter/SKILL.zed.md +3 -3
  57. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.codex.md +65 -23
  58. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.cursor.md +65 -23
  59. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.md +65 -23
  60. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.opencode.md +65 -23
  61. package/src/gdskills/bundled/skills/orchestration/job-orchestrator/SKILL.zed.md +65 -23
  62. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.codex.md +19 -10
  63. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.cursor.md +19 -10
  64. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.md +19 -10
  65. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.opencode.md +19 -10
  66. package/src/gdskills/bundled/skills/orchestration/task-implementer/SKILL.zed.md +19 -10
  67. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.codex.md +1 -1
  68. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.cursor.md +1 -1
  69. package/src/gdskills/bundled/skills/planning/brainstorm/SKILL.md +1 -1
  70. package/src/gdskills/bundled/skills/planning/interview/SKILL.codex.md +1 -1
  71. package/src/gdskills/bundled/skills/planning/interview/SKILL.cursor.md +1 -1
  72. package/src/gdskills/bundled/skills/planning/interview/SKILL.md +1 -1
  73. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.codex.md +1 -1
  74. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.cursor.md +1 -1
  75. package/src/gdskills/bundled/skills/planning/interviewer/SKILL.md +1 -1
  76. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.codex.md +7 -7
  77. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.cursor.md +7 -7
  78. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.md +7 -7
  79. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.opencode.md +7 -7
  80. package/src/gdskills/bundled/skills/planning/prd-creator/SKILL.zed.md +7 -7
  81. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.codex.md +1 -1
  82. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.cursor.md +1 -1
  83. package/src/gdskills/bundled/skills/platform/claude-md-management/SKILL.md +1 -1
  84. package/src/gdskills/bundled/skills/platform/hookify/SKILL.codex.md +1 -1
  85. package/src/gdskills/bundled/skills/platform/hookify/SKILL.cursor.md +1 -1
  86. package/src/gdskills/bundled/skills/platform/hookify/SKILL.md +1 -1
  87. package/src/gdskills/bundled/skills/quality/changelog/SKILL.codex.md +1 -1
  88. package/src/gdskills/bundled/skills/quality/changelog/SKILL.cursor.md +1 -1
  89. package/src/gdskills/bundled/skills/quality/changelog/SKILL.md +1 -1
  90. package/src/gdskills/bundled/skills/quality/commit/SKILL.codex.md +2 -2
  91. package/src/gdskills/bundled/skills/quality/commit/SKILL.cursor.md +2 -2
  92. package/src/gdskills/bundled/skills/quality/commit/SKILL.md +2 -2
  93. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.codex.md +1 -1
  94. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.cursor.md +1 -1
  95. package/src/gdskills/bundled/skills/quality/db-migrate/SKILL.md +1 -1
  96. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.codex.md +1 -1
  97. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.cursor.md +1 -1
  98. package/src/gdskills/bundled/skills/quality/dependency-update/SKILL.md +1 -1
  99. package/src/gdskills/bundled/skills/quality/deploy/SKILL.codex.md +1 -1
  100. package/src/gdskills/bundled/skills/quality/deploy/SKILL.cursor.md +1 -1
  101. package/src/gdskills/bundled/skills/quality/deploy/SKILL.md +1 -1
  102. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.codex.md +1 -1
  103. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.cursor.md +1 -1
  104. package/src/gdskills/bundled/skills/quality/perf-check/SKILL.md +1 -1
  105. package/src/gdskills/bundled/skills/quality/pr/SKILL.codex.md +1 -1
  106. package/src/gdskills/bundled/skills/quality/pr/SKILL.cursor.md +1 -1
  107. package/src/gdskills/bundled/skills/quality/pr/SKILL.md +1 -1
  108. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.codex.md +2 -2
  109. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.cursor.md +2 -2
  110. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.md +2 -2
  111. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.opencode.md +2 -2
  112. package/src/gdskills/bundled/skills/quality/pr-issue-documenter/SKILL.zed.md +2 -2
  113. package/src/gdskills/bundled/skills/quality/push/SKILL.codex.md +1 -1
  114. package/src/gdskills/bundled/skills/quality/push/SKILL.cursor.md +1 -1
  115. package/src/gdskills/bundled/skills/quality/push/SKILL.md +1 -1
  116. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.codex.md +7 -2
  117. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.cursor.md +7 -2
  118. package/src/gdskills/bundled/skills/quality/test-gen/SKILL.md +7 -2
  119. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.codex.md +15 -9
  120. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.cursor.md +15 -9
  121. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.md +15 -9
  122. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.opencode.md +15 -9
  123. package/src/gdskills/bundled/skills/quality/tests-creator/SKILL.zed.md +15 -9
  124. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.codex.md +2 -2
  125. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.cursor.md +2 -2
  126. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.md +2 -2
  127. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.opencode.md +2 -2
  128. package/src/gdskills/bundled/skills/review/code-ai-review/SKILL.zed.md +2 -2
  129. package/src/gdskills/bundled/skills/review/code-learned-review/SKILL.codex.md +2 -2
  130. package/src/gdskills/bundled/skills/review/code-learned-review/SKILL.cursor.md +2 -2
  131. package/src/gdskills/bundled/skills/review/code-learned-review/SKILL.md +2 -2
  132. package/src/gdskills/bundled/skills/review/code-learned-review/SKILL.opencode.md +2 -2
  133. package/src/gdskills/bundled/skills/review/code-learned-review/SKILL.zed.md +2 -2
  134. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.codex.md +2 -2
  135. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.cursor.md +2 -2
  136. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.md +2 -2
  137. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.opencode.md +2 -2
  138. package/src/gdskills/bundled/skills/review/code-mobx-store-review/SKILL.zed.md +2 -2
  139. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.codex.md +2 -2
  140. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.cursor.md +2 -2
  141. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.md +2 -2
  142. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.opencode.md +2 -2
  143. package/src/gdskills/bundled/skills/review/code-style-review/SKILL.zed.md +2 -2
  144. package/src/gdskills/bundled/skills/review/review-architecture/SKILL.md +2 -2
  145. package/src/gdskills/bundled/skills/review/review-backend/SKILL.md +3 -3
  146. package/src/gdskills/bundled/skills/review/review-clean-code/SKILL.md +2 -2
  147. package/src/gdskills/bundled/skills/review/review-core-boundaries/SKILL.md +1 -1
  148. package/src/gdskills/bundled/skills/review/review-flow-graph/SKILL.md +1 -1
  149. package/src/gdskills/bundled/skills/review/review-frontend/SKILL.md +2 -2
  150. package/src/gdskills/bundled/skills/review/review-frontend-conventions/SKILL.md +1 -1
  151. package/src/gdskills/bundled/skills/review/review-highload/SKILL.md +2 -2
  152. package/src/gdskills/bundled/skills/review/review-layout/SKILL.md +1 -1
  153. package/src/gdskills/bundled/skills/review/review-logic/SKILL.md +2 -2
  154. package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +40 -35
  155. package/src/gdskills/bundled/skills/review/review-orchestrator/input-contract.schema.json +1 -2
  156. package/src/gdskills/bundled/skills/review/review-orchestrator/review-context.schema.json +1 -5
  157. package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-input.schema.json +53 -9
  158. package/src/gdskills/bundled/skills/review/review-performance/SKILL.md +2 -2
  159. package/src/gdskills/bundled/skills/review/review-pr-feedback/SKILL.md +6 -6
  160. package/src/gdskills/bundled/skills/review/review-security-code/SKILL.md +2 -2
  161. package/src/gdskills/bundled/skills/review/review-style/SKILL.md +3 -3
  162. package/src/gdskills/bundled/skills/review/review-testing-practices/SKILL.md +1 -1
  163. package/src/gdskills/bundled/skills/review/review-verifier/SKILL.md +1 -1
  164. package/src/gdskills/bundled/rules/core/review-agent-profile.mdc +0 -49
  165. package/src/gdskills/bundled/rules/core/review-strict-profile.mdc +0 -48
package/dist/cli.js CHANGED
@@ -301,6 +301,13 @@ var init_provider_config = __esm(() => {
301
301
  // src/lib/shell-config.ts
302
302
  import { existsSync as existsSync2 } from "fs";
303
303
  import path3 from "path";
304
+ function noteSavedCredentialEnv(keys) {
305
+ for (const key of keys)
306
+ savedCredentialKeys.add(key);
307
+ }
308
+ function savedCredentialEnvKeys() {
309
+ return savedCredentialKeys;
310
+ }
304
311
  function shellConfigPath(dir) {
305
312
  return path3.join(keryxConfigDir(dir), "auth.json");
306
313
  }
@@ -383,10 +390,13 @@ function applySavedApiKeys(dir) {
383
390
  }
384
391
  }
385
392
  } catch {}
393
+ noteSavedCredentialEnv(applied);
386
394
  return applied;
387
395
  }
396
+ var savedCredentialKeys;
388
397
  var init_shell_config = __esm(() => {
389
398
  init_config_dir();
399
+ savedCredentialKeys = new Set;
390
400
  });
391
401
 
392
402
  // src/lib/oauth/grants.ts
@@ -472,6 +482,7 @@ function applyOAuthAccessToEnv(dir) {
472
482
  applied.push(envKey);
473
483
  }
474
484
  }
485
+ noteSavedCredentialEnv(applied);
475
486
  return applied;
476
487
  }
477
488
  function grantFromTokens(method, tokens, now = Date.now) {
@@ -571,10 +582,61 @@ function customCompatProviders(dir) {
571
582
  function allOpenAiCompatProviders(dir) {
572
583
  return [...OPENAI_COMPAT_PROVIDERS, ...customCompatProviders(dir)];
573
584
  }
585
+ function modelsErrorDetail(body) {
586
+ const text = body.slice(0, MODELS_ERROR_BODY_LIMIT).trim();
587
+ if (text.length === 0) {
588
+ return;
589
+ }
590
+ let parsed;
591
+ try {
592
+ parsed = JSON.parse(text);
593
+ } catch {
594
+ parsed = undefined;
595
+ }
596
+ if (typeof parsed === "object" && parsed !== null) {
597
+ const record = parsed;
598
+ const error = record.error;
599
+ if (typeof error === "string" && error.length > 0) {
600
+ return error;
601
+ }
602
+ if (typeof error === "object" && error !== null) {
603
+ const message = error.message;
604
+ if (typeof message === "string" && message.length > 0) {
605
+ return message;
606
+ }
607
+ }
608
+ for (const field of ["detail", "message"]) {
609
+ const value = record[field];
610
+ if (typeof value === "string" && value.length > 0) {
611
+ return value;
612
+ }
613
+ }
614
+ return;
615
+ }
616
+ if (text.startsWith("<") || text.includes("<html")) {
617
+ return;
618
+ }
619
+ const firstLine = text.split(`
620
+ `, 1)[0]?.trim();
621
+ return firstLine !== undefined && firstLine.length > 0 ? firstLine : undefined;
622
+ }
623
+ function modelsFailureLine(providerLabel, failure) {
624
+ const detail = "detail" in failure && failure.detail !== undefined ? ` \u2014 ${failure.detail}` : "";
625
+ switch (failure.kind) {
626
+ case "rejected":
627
+ return `${providerLabel} rejected the credential (HTTP ${failure.status})${detail}`;
628
+ case "http":
629
+ return `${providerLabel} could not list models (HTTP ${failure.status})${detail}`;
630
+ case "unreachable":
631
+ return `${providerLabel} could not be reached${detail}`;
632
+ case "empty":
633
+ return `${providerLabel} reports no models`;
634
+ }
635
+ }
574
636
  async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts) {
575
637
  const url = `${provider.baseUrl.replace(/\/+$/, "")}${provider.modelsPath ?? DEFAULT_MODELS_PATH}`;
576
638
  const timeoutMs = opts?.timeoutMs ?? MODELS_FETCH_TIMEOUT_MS;
577
- const fallback = { models: [], source: "fallback" };
639
+ const fallback = (failure) => ({ models: [], source: "fallback", failure });
578
640
  const controller = new AbortController;
579
641
  const timer = setTimeout(() => controller.abort(), timeoutMs);
580
642
  try {
@@ -584,7 +646,9 @@ async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts)
584
646
  }
585
647
  const res = await fetchFn(url, init);
586
648
  if (!res.ok) {
587
- return fallback;
649
+ const detail = modelsErrorDetail(await res.text().catch(() => ""));
650
+ const kind = res.status === 401 || res.status === 403 ? "rejected" : "http";
651
+ return fallback({ kind, status: res.status, ...detail === undefined ? {} : { detail } });
588
652
  }
589
653
  const body = await res.json();
590
654
  const ids = Array.isArray(body?.data) ? body.data.map((m) => {
@@ -597,11 +661,13 @@ async function fetchOpenAiCompatModelsDetailed(fetchFn, provider, apiKey, opts)
597
661
  return "";
598
662
  }).filter((id) => id.length > 0) : [];
599
663
  if (ids.length === 0) {
600
- return fallback;
664
+ return fallback({ kind: "empty" });
601
665
  }
602
666
  return { models: Array.from(new Set(ids)).sort(), source: "live" };
603
- } catch {
604
- return fallback;
667
+ } catch (err) {
668
+ const aborted = controller.signal.aborted;
669
+ const message = err instanceof Error ? err.message : String(err);
670
+ return fallback({ kind: "unreachable", detail: aborted ? `timed out after ${timeoutMs}ms` : message });
605
671
  } finally {
606
672
  clearTimeout(timer);
607
673
  }
@@ -821,7 +887,7 @@ optimisation. With no second family configured it reports single-family review
821
887
  with a stated reason and exits 0 \u2014 that is a normal configuration, not an error.
822
888
  `);
823
889
  }
824
- var DEFAULT_MODELS_PATH = "/v1/models", OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4, BALANCE_FETCH_TIMEOUT_MS = 8000;
890
+ var DEFAULT_MODELS_PATH = "/v1/models", OPENAI_COMPAT_PROVIDERS, MODELS_FETCH_TIMEOUT_MS = 1e4, MODELS_ERROR_BODY_LIMIT = 4096, BALANCE_FETCH_TIMEOUT_MS = 8000;
825
891
  var init_providers = __esm(() => {
826
892
  init_provider_config();
827
893
  init_grants();
@@ -13250,7 +13316,7 @@ var init_staleness = __esm(() => {
13250
13316
  });
13251
13317
 
13252
13318
  // src/wiki/staleness.ts
13253
- import { createHash as createHash6 } from "crypto";
13319
+ import { createHash as createHash7 } from "crypto";
13254
13320
  import { readFile as readFile29 } from "fs/promises";
13255
13321
  import path68 from "path";
13256
13322
  async function computePageNodeHash(cwd, keyFiles, graph) {
@@ -13261,7 +13327,7 @@ async function computePageNodeHash(cwd, keyFiles, graph) {
13261
13327
  if (knownPaths.has(file)) {
13262
13328
  try {
13263
13329
  const content = await readFile29(path68.join(cwd, file), "utf8");
13264
- digest = createHash6("sha256").update(content).digest("hex");
13330
+ digest = createHash7("sha256").update(content).digest("hex");
13265
13331
  } catch {
13266
13332
  digest = "<missing>";
13267
13333
  }
@@ -13271,7 +13337,7 @@ async function computePageNodeHash(cwd, keyFiles, graph) {
13271
13337
  entries.sort((a, b) => a.path.localeCompare(b.path));
13272
13338
  const combined = entries.map((entry) => `${entry.path}:${entry.digest}`).join(`
13273
13339
  `);
13274
- return createHash6("sha256").update(combined).digest("hex");
13340
+ return createHash7("sha256").update(combined).digest("hex");
13275
13341
  }
13276
13342
  function isPageUnchangedSinceLastEnrich(pagePath, currentHash, completedNodeHashes) {
13277
13343
  const previous = completedNodeHashes?.[pagePath];
@@ -13325,7 +13391,7 @@ var init_staleness2 = __esm(() => {
13325
13391
  });
13326
13392
 
13327
13393
  // src/wiki/provenance.ts
13328
- import { createHash as createHash7 } from "crypto";
13394
+ import { createHash as createHash8 } from "crypto";
13329
13395
  function parseProvenance(content) {
13330
13396
  const verifiedAt = singleLineField(content, "VerifiedAt");
13331
13397
  const verifiedScope = singleLineField(content, "VerifiedScope");
@@ -13337,7 +13403,7 @@ function parseProvenance(content) {
13337
13403
  }
13338
13404
  async function computeVerifiedScope(cwd, describePaths, graph) {
13339
13405
  if (describePaths.length === 0) {
13340
- return `sha256:${createHash7("sha256").update("<empty-describe-set>").digest("hex")}`;
13406
+ return `sha256:${createHash8("sha256").update("<empty-describe-set>").digest("hex")}`;
13341
13407
  }
13342
13408
  return `sha256:${await computePageNodeHash(cwd, describePaths, graph)}`;
13343
13409
  }
@@ -13542,7 +13608,7 @@ var init_collect = __esm(() => {
13542
13608
  });
13543
13609
 
13544
13610
  // src/wiki/section-marker.ts
13545
- import { createHash as createHash8 } from "crypto";
13611
+ import { createHash as createHash9 } from "crypto";
13546
13612
  import { readFile as readFile31 } from "fs/promises";
13547
13613
  function parseMarkers(content) {
13548
13614
  const lines = content.split(`
@@ -13673,7 +13739,7 @@ function proposeSectionId(headingText, fallbackOrdinal) {
13673
13739
  return slug.length > 0 && ID_RE.test(slug) ? slug : `section-${fallbackOrdinal}`;
13674
13740
  }
13675
13741
  function digest(value) {
13676
- return createHash8("sha256").update(value).digest("hex");
13742
+ return createHash9("sha256").update(value).digest("hex");
13677
13743
  }
13678
13744
  function planPageMigration(relativePath, content) {
13679
13745
  const lines = content.split(`
@@ -13813,7 +13879,7 @@ var init_section_marker = __esm(() => {
13813
13879
  });
13814
13880
 
13815
13881
  // src/wiki/section-index.ts
13816
- import { createHash as createHash9 } from "crypto";
13882
+ import { createHash as createHash10 } from "crypto";
13817
13883
  function resolveWikiPageIdentity(relativePath, content) {
13818
13884
  const markers = parseMarkers(content);
13819
13885
  if (markers.page) {
@@ -13905,7 +13971,7 @@ function detectLanguage(text) {
13905
13971
  return ru ? "ru" : "en";
13906
13972
  }
13907
13973
  function digestOf2(value) {
13908
- return createHash9("sha256").update(value).digest("hex");
13974
+ return createHash10("sha256").update(value).digest("hex");
13909
13975
  }
13910
13976
  function fieldFromLines(lines, name) {
13911
13977
  const pattern = new RegExp(`^${name}:\\s*(.+)$`, "i");
@@ -14436,7 +14502,7 @@ async function runRuntimeEmbedder(dep, asset, texts, dims) {
14436
14502
  var MEMORY_EMBEDDING_ID = "memory.embedding", DEFAULT_EMBED_DIMS = 96;
14437
14503
 
14438
14504
  // src/memory/embedding/index.ts
14439
- import { createHash as createHash10 } from "crypto";
14505
+ import { createHash as createHash11 } from "crypto";
14440
14506
  import { mkdir as mkdir21, readFile as readFile32, writeFile as writeFile24 } from "fs/promises";
14441
14507
  import path70 from "path";
14442
14508
  function embeddingsDir(cwd) {
@@ -14449,7 +14515,7 @@ ${entry.tags.join(" ")}
14449
14515
  ${entry.details}`.trim();
14450
14516
  }
14451
14517
  function contentHash(text) {
14452
- return createHash10("sha256").update(text).digest("hex").slice(0, 16);
14518
+ return createHash11("sha256").update(text).digest("hex").slice(0, 16);
14453
14519
  }
14454
14520
  async function buildEmbeddingIndex(cwd, entries, embedder, model, now) {
14455
14521
  const sorted = [...entries].sort((a, b) => a.relativePath.localeCompare(b.relativePath));
@@ -15181,7 +15247,7 @@ var init_backlinks = __esm(() => {
15181
15247
  });
15182
15248
 
15183
15249
  // src/ctx/assembly.ts
15184
- import { createHash as createHash11 } from "crypto";
15250
+ import { createHash as createHash12 } from "crypto";
15185
15251
  import { mkdir as mkdir22, rename as rename2, writeFile as writeFile25 } from "fs/promises";
15186
15252
  import path73 from "path";
15187
15253
  function assembleContext(input) {
@@ -15236,7 +15302,7 @@ async function recordContextAssembly(input) {
15236
15302
  await rename2(temporary, target);
15237
15303
  }
15238
15304
  function traceIdFor(correlationId, outcome) {
15239
- return createHash11("sha256").update(`${outcome}\x00${correlationId}`).digest("hex").slice(0, 24);
15305
+ return createHash12("sha256").update(`${outcome}\x00${correlationId}`).digest("hex").slice(0, 24);
15240
15306
  }
15241
15307
  function traceRefFor(traceId) {
15242
15308
  return `./.metaproject/context-operations/traces/${traceId}.json`;
@@ -16095,9 +16161,9 @@ var init_evidence = __esm(() => {
16095
16161
  });
16096
16162
 
16097
16163
  // src/wiki/managed-block.ts
16098
- import { createHash as createHash12 } from "crypto";
16164
+ import { createHash as createHash13 } from "crypto";
16099
16165
  function hashBlockContent(content) {
16100
- return createHash12("sha256").update(content.trim()).digest("hex");
16166
+ return createHash13("sha256").update(content.trim()).digest("hex");
16101
16167
  }
16102
16168
  function findManagedBlock(content) {
16103
16169
  const lines = content.split(`
@@ -16348,7 +16414,7 @@ var init_template_structure = __esm(() => {
16348
16414
 
16349
16415
  // src/wiki/service.ts
16350
16416
  import { spawn as spawn2 } from "child_process";
16351
- import { createHash as createHash13 } from "crypto";
16417
+ import { createHash as createHash14 } from "crypto";
16352
16418
  import { mkdir as mkdir23, readFile as readFile35, readdir as readdir7, rm as rm6, writeFile as writeFile26 } from "fs/promises";
16353
16419
  import path75 from "path";
16354
16420
  function wikiRootPath2(cwd) {
@@ -16650,7 +16716,7 @@ function resolveEvidenceScope(cwd, override) {
16650
16716
  const absolute = path75.resolve(cwd);
16651
16717
  return {
16652
16718
  projectId: override?.projectId ?? (path75.basename(absolute) || "project"),
16653
- checkoutId: override?.checkoutId ?? `checkout:${createHash13("sha256").update(absolute).digest("hex").slice(0, 16)}`,
16719
+ checkoutId: override?.checkoutId ?? `checkout:${createHash14("sha256").update(absolute).digest("hex").slice(0, 16)}`,
16654
16720
  ...override?.workspaceId ? { workspaceId: override.workspaceId } : {},
16655
16721
  ...override?.taskId ? { taskId: override.taskId } : {}
16656
16722
  };
@@ -17456,7 +17522,7 @@ var init_service3 = __esm(() => {
17456
17522
  });
17457
17523
 
17458
17524
  // src/gdgraph/wiki-layer.ts
17459
- import { createHash as createHash14 } from "crypto";
17525
+ import { createHash as createHash15 } from "crypto";
17460
17526
  import { mkdir as mkdir24, stat as stat8, writeFile as writeFile27 } from "fs/promises";
17461
17527
  import path76 from "path";
17462
17528
  function storageDir(projectRoot) {
@@ -17513,7 +17579,7 @@ function buildWikiLayer(input) {
17513
17579
  function computeFingerprints(fileRecords, mtimes) {
17514
17580
  return fileRecords.map((record) => ({
17515
17581
  path: record.path,
17516
- contentHash: createHash14("sha256").update(record.content).digest("hex"),
17582
+ contentHash: createHash15("sha256").update(record.content).digest("hex"),
17517
17583
  mtimeMs: mtimes.get(record.path) ?? 0
17518
17584
  })).sort((a, b) => a.path.localeCompare(b.path));
17519
17585
  }
@@ -22153,7 +22219,7 @@ var init_slate = __esm(() => {
22153
22219
  });
22154
22220
 
22155
22221
  // src/flow/store.ts
22156
- import { createHash as createHash15 } from "crypto";
22222
+ import { createHash as createHash16 } from "crypto";
22157
22223
  import { appendFile as appendFile3, mkdir as mkdir30, readFile as readFile48, readdir as readdir9 } from "fs/promises";
22158
22224
  import path90 from "path";
22159
22225
  function flowsRoot(cwd) {
@@ -22325,7 +22391,7 @@ async function acChecksum(cwd, dir) {
22325
22391
  `).split(`
22326
22392
  `).map((line) => line.trimEnd()).join(`
22327
22393
  `).trim();
22328
- return `sha256:${createHash15("sha256").update(normalized).digest("hex")}`;
22394
+ return `sha256:${createHash16("sha256").update(normalized).digest("hex")}`;
22329
22395
  }
22330
22396
  async function assertAcIntact(cwd, dir, flow) {
22331
22397
  if (!flow.acChecksum) {
@@ -22365,7 +22431,7 @@ var init_slate_course = __esm(() => {
22365
22431
  });
22366
22432
 
22367
22433
  // src/sac/trusted-wrap-up.ts
22368
- import { createHash as createHash16, randomUUID as randomUUID7 } from "crypto";
22434
+ import { createHash as createHash17, randomUUID as randomUUID7 } from "crypto";
22369
22435
  function createTrustedWrapUpAuthority(input) {
22370
22436
  const now = input.now ?? (() => new Date);
22371
22437
  return Object.freeze({
@@ -22397,7 +22463,7 @@ function createTrustedWrapUpAuthority(input) {
22397
22463
  });
22398
22464
  }
22399
22465
  function digest2(value) {
22400
- return createHash16("sha256").update(value).digest("hex");
22466
+ return createHash17("sha256").update(value).digest("hex");
22401
22467
  }
22402
22468
  var issued, consumed;
22403
22469
  var init_trusted_wrap_up = __esm(() => {
@@ -22407,7 +22473,7 @@ var init_trusted_wrap_up = __esm(() => {
22407
22473
 
22408
22474
  // src/sac/index.ts
22409
22475
  import { access as access2, readFile as readFile49, realpath as realpath5 } from "fs/promises";
22410
- import { createHash as createHash17 } from "crypto";
22476
+ import { createHash as createHash18 } from "crypto";
22411
22477
  import path91 from "path";
22412
22478
  import { fileURLToPath as fileURLToPath5 } from "url";
22413
22479
  async function resolveSacNormativeSchemaPath(fileName, searchFrom = [fileURLToPath5(new URL(".", import.meta.url)), process.cwd()]) {
@@ -23085,7 +23151,7 @@ async function validateSacContract(input) {
23085
23151
  return { valid: errors.length === 0, errors };
23086
23152
  }
23087
23153
  function hashSacRecord(value) {
23088
- return createHash17("sha256").update(typeof value === "string" ? value : JSON.stringify(value)).digest("hex");
23154
+ return createHash18("sha256").update(typeof value === "string" ? value : JSON.stringify(value)).digest("hex");
23089
23155
  }
23090
23156
  async function resolveWorkspaceReference(input) {
23091
23157
  if (!workspacePathPattern.test(input.uri) || path91.isAbsolute(input.uri) || /^[a-z][a-z0-9+.-]*:/i.test(input.uri) || input.uri.includes("\\"))
@@ -23196,7 +23262,7 @@ var init_secure_resource_read = __esm(() => {
23196
23262
 
23197
23263
  // src/sac/workspace-service.ts
23198
23264
  import { mkdir as mkdir31, readdir as readdir10, readFile as readFile50 } from "fs/promises";
23199
- import { createHash as createHash18, randomUUID as randomUUID8 } from "crypto";
23265
+ import { createHash as createHash19, randomUUID as randomUUID8 } from "crypto";
23200
23266
  import path93 from "path";
23201
23267
 
23202
23268
  class WorkspaceService {
@@ -23486,7 +23552,7 @@ class WorkspaceService {
23486
23552
  }
23487
23553
  contentDigest(absolutePath) {
23488
23554
  try {
23489
- return createHash18("sha256").update(readWorkspaceFileNoFollow(this.root, absolutePath)).digest("hex");
23555
+ return createHash19("sha256").update(readWorkspaceFileNoFollow(this.root, absolutePath)).digest("hex");
23490
23556
  } catch {
23491
23557
  return;
23492
23558
  }
@@ -23618,7 +23684,7 @@ var init_workspace_service = __esm(() => {
23618
23684
  });
23619
23685
 
23620
23686
  // src/sac/guarded-owner-writer.ts
23621
- import { createHash as createHash19 } from "crypto";
23687
+ import { createHash as createHash20 } from "crypto";
23622
23688
  function createGuardedOwnerWriter(input) {
23623
23689
  const recoverReceipt = input.recoverReceipt;
23624
23690
  return Object.freeze({
@@ -23643,7 +23709,7 @@ function receiptMatchesIntent(input) {
23643
23709
  return binding.owner === input.owner && binding.bindingHash === bindingHash(input.owner, input.intent) && binding.intentRef === input.intent.intentRef && binding.proposalId === input.intent.proposalId && binding.proposalRevision === input.intent.proposalRevision && binding.workspaceId === input.intent.workspaceId && binding.correlationId === input.intent.correlationId && binding.idempotencyKey === input.intent.idempotencyKey && binding.reviewerSubject === input.intent.reviewerSubject && binding.reviewerAuthority === input.intent.reviewerAuthority && binding.policyRevision === input.intent.policyRevision;
23644
23710
  }
23645
23711
  function bindingHash(owner, intent) {
23646
- return createHash19("sha256").update(JSON.stringify({ owner, ...intent })).digest("hex");
23712
+ return createHash20("sha256").update(JSON.stringify({ owner, ...intent })).digest("hex");
23647
23713
  }
23648
23714
  var init_guarded_owner_writer = () => {};
23649
23715
 
@@ -24296,7 +24362,7 @@ var init_wrap_up_evidence = __esm(() => {
24296
24362
  });
24297
24363
 
24298
24364
  // src/sac/session-wrap-up.ts
24299
- import { createHash as createHash20 } from "crypto";
24365
+ import { createHash as createHash21 } from "crypto";
24300
24366
  import { mkdir as mkdir32, writeFile as writeFile33 } from "fs/promises";
24301
24367
  import path95 from "path";
24302
24368
  function sessionEvidenceRef(workspaceId, sessionId) {
@@ -24362,12 +24428,12 @@ async function resolveSessionWrapUp(input) {
24362
24428
  await writeFile33(path95.join(input.cwd, noticeRelPath), floor.notice, "utf8");
24363
24429
  const observedAt = now().toISOString();
24364
24430
  const evidence = [
24365
- { kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision: createHash20("sha256").update(safeWrapUp.content).digest("hex"), observedAt },
24366
- { kind: diffKind, uri: `./${diffRelPath}`, revision: createHash20("sha256").update(safeDiff.content).digest("hex"), observedAt },
24367
- { kind: "session", uri: `./${relPath}`, revision: createHash20("sha256").update(safeSession.content).digest("hex"), observedAt }
24431
+ { kind: "wrap-up", uri: `./${wrapUpRelPath}`, revision: createHash21("sha256").update(safeWrapUp.content).digest("hex"), observedAt },
24432
+ { kind: diffKind, uri: `./${diffRelPath}`, revision: createHash21("sha256").update(safeDiff.content).digest("hex"), observedAt },
24433
+ { kind: "session", uri: `./${relPath}`, revision: createHash21("sha256").update(safeSession.content).digest("hex"), observedAt }
24368
24434
  ];
24369
24435
  if (floor.notice !== undefined) {
24370
- evidence.push({ kind: REDACTION_NOTICE_KIND, uri: `./${noticeRelPath}`, revision: createHash20("sha256").update(floor.notice).digest("hex"), observedAt });
24436
+ evidence.push({ kind: REDACTION_NOTICE_KIND, uri: `./${noticeRelPath}`, revision: createHash21("sha256").update(floor.notice).digest("hex"), observedAt });
24371
24437
  }
24372
24438
  return {
24373
24439
  workspaceId: input.workspaceId,
@@ -24393,7 +24459,7 @@ var init_session_wrap_up = __esm(() => {
24393
24459
  });
24394
24460
 
24395
24461
  // src/sac/review-confirm-token.ts
24396
- import { createHash as createHash21, randomBytes as randomBytes3 } from "crypto";
24462
+ import { createHash as createHash22, randomBytes as randomBytes3 } from "crypto";
24397
24463
  import { readFile as readFile51 } from "fs/promises";
24398
24464
  import path96 from "path";
24399
24465
  function confirmTokenPath(cwd, workspaceId, proposalId) {
@@ -24403,7 +24469,7 @@ function confirmReceiptPath(cwd, workspaceId, proposalId, idempotencyKey) {
24403
24469
  return path96.join(cwd, ".metaproject", "workspaces", workspaceId, "proposals", `${proposalId}.${sha256(idempotencyKey)}.confirm-receipt.json`);
24404
24470
  }
24405
24471
  function sha256(text) {
24406
- return createHash21("sha256").update(text).digest("hex");
24472
+ return createHash22("sha256").update(text).digest("hex");
24407
24473
  }
24408
24474
  async function mintConfirmToken(cwd, workspaceId, proposalId, deps = {}) {
24409
24475
  const now = deps.now ?? (() => new Date);
@@ -24564,7 +24630,7 @@ function round(value) {
24564
24630
  var init_dedup = () => {};
24565
24631
 
24566
24632
  // src/sac/proposal-evidence.ts
24567
- import { createHash as createHash22 } from "crypto";
24633
+ import { createHash as createHash23 } from "crypto";
24568
24634
  import { readFile as readFile52, rm as rm8 } from "fs/promises";
24569
24635
  import path97 from "path";
24570
24636
  function proposalPath(cwd, workspaceId, proposalId) {
@@ -24592,7 +24658,7 @@ async function readVerifiedProposalEvidence(cwd, workspaceId, proposalId) {
24592
24658
  } catch {
24593
24659
  return { ok: false, code: "evidence_file_unreadable" };
24594
24660
  }
24595
- if (createHash22("sha256").update(content).digest("hex") !== evidence.revision) {
24661
+ if (createHash23("sha256").update(content).digest("hex") !== evidence.revision) {
24596
24662
  return { ok: false, code: "evidence_revision_mismatch" };
24597
24663
  }
24598
24664
  return { proposal, evidence, content };
@@ -24603,7 +24669,7 @@ async function readSidecarNote(cwd, workspaceId, proposalId) {
24603
24669
  });
24604
24670
  }
24605
24671
  function digestOf3(content) {
24606
- return createHash22("sha256").update(content).digest("hex");
24672
+ return createHash23("sha256").update(content).digest("hex");
24607
24673
  }
24608
24674
  async function readTargetVersion(absolutePath) {
24609
24675
  try {
@@ -25295,7 +25361,7 @@ var init_wiki_owner_writer = __esm(() => {
25295
25361
  });
25296
25362
 
25297
25363
  // src/gdskills/project-skills.ts
25298
- import { createHash as createHash23 } from "crypto";
25364
+ import { createHash as createHash24 } from "crypto";
25299
25365
  import { homedir as homedir3 } from "os";
25300
25366
  import { mkdir as mkdir34, readFile as readFile57, stat as stat11 } from "fs/promises";
25301
25367
  import path101 from "path";
@@ -25363,7 +25429,7 @@ function resolveOriginPath(ref, projectRoot) {
25363
25429
  return path101.resolve(projectRoot, trimmed);
25364
25430
  }
25365
25431
  function hashOriginContent(content) {
25366
- return `sha256:${createHash23("sha256").update(content).digest("hex")}`;
25432
+ return `sha256:${createHash24("sha256").update(content).digest("hex")}`;
25367
25433
  }
25368
25434
  async function readSkillOrigin(projectRoot, ref, now = () => new Date) {
25369
25435
  const resolved = resolveOriginPath(ref, projectRoot);
@@ -25623,7 +25689,7 @@ ${filesToRead}
25623
25689
 
25624
25690
  ## Verification
25625
25691
 
25626
- - Current state: not verified.
25692
+ - Verification status: see \`verification.md\` (written by \`keryx skills verify\`).
25627
25693
  - Run: \`keryx skills verify ${moduleName}/${skillName}\`
25628
25694
  `;
25629
25695
  }
@@ -25874,7 +25940,7 @@ var init_skill_owner_writer = __esm(() => {
25874
25940
  });
25875
25941
 
25876
25942
  // src/sac/proposal-lifecycle.ts
25877
- import { createHash as createHash24, randomUUID as randomUUID11 } from "crypto";
25943
+ import { createHash as createHash25, randomUUID as randomUUID11 } from "crypto";
25878
25944
  import { appendFile as appendFile4, mkdir as mkdir35, readdir as readdir12, readFile as readFile59 } from "fs/promises";
25879
25945
  import path102 from "path";
25880
25946
  function supportsOwnerWriteRecovery(writer) {
@@ -26311,7 +26377,7 @@ class ProposalLifecycleService {
26311
26377
  }
26312
26378
  }
26313
26379
  function hash(value) {
26314
- return createHash24("sha256").update(value).digest("hex");
26380
+ return createHash25("sha256").update(value).digest("hex");
26315
26381
  }
26316
26382
  function recordHash(value) {
26317
26383
  return hash(JSON.stringify(value));
@@ -26535,7 +26601,7 @@ var init_workspace_resolve = __esm(() => {
26535
26601
  });
26536
26602
 
26537
26603
  // src/sac/machine-wrap-up.ts
26538
- import { createHash as createHash25, randomUUID as randomUUID13 } from "crypto";
26604
+ import { createHash as createHash26, randomUUID as randomUUID13 } from "crypto";
26539
26605
  import { mkdir as mkdir36 } from "fs/promises";
26540
26606
  import path103 from "path";
26541
26607
  function groupSeedsByKind(slate) {
@@ -26550,7 +26616,7 @@ function groupSeedsByKind(slate) {
26550
26616
  return map;
26551
26617
  }
26552
26618
  function sha2562(value) {
26553
- return createHash25("sha256").update(value).digest("hex");
26619
+ return createHash26("sha256").update(value).digest("hex");
26554
26620
  }
26555
26621
  function mechanicalSummary(diff, course) {
26556
26622
  return `Mechanical wrap-up summary (model turn unavailable or timed out):
@@ -28496,9 +28562,9 @@ var init_orchestrate = __esm(() => {
28496
28562
  });
28497
28563
 
28498
28564
  // src/harness/policy/profiles.ts
28499
- import { createHash as createHash26 } from "crypto";
28565
+ import { createHash as createHash27 } from "crypto";
28500
28566
  function sha256Hex2(input) {
28501
- return createHash26("sha256").update(input, "utf8").digest("hex");
28567
+ return createHash27("sha256").update(input, "utf8").digest("hex");
28502
28568
  }
28503
28569
  function readOnlyReview() {
28504
28570
  return {
@@ -29718,7 +29784,7 @@ var init_supersede = __esm(() => {
29718
29784
  });
29719
29785
 
29720
29786
  // src/memory/service.ts
29721
- import { createHash as createHash27 } from "crypto";
29787
+ import { createHash as createHash28 } from "crypto";
29722
29788
  import { mkdir as mkdir38, readFile as readFile63, writeFile as writeFile36 } from "fs/promises";
29723
29789
  import path110 from "path";
29724
29790
  function dataRoot(cwd) {
@@ -29775,7 +29841,7 @@ function createMemoryService() {
29775
29841
  const indexPath = path110.join(indexDir, "index.json");
29776
29842
  await writeFile36(indexPath, `${JSON.stringify({
29777
29843
  catalogVersion: 1,
29778
- sourceFingerprint: createHash27("sha256").update(JSON.stringify(entries.map((e) => ({
29844
+ sourceFingerprint: createHash28("sha256").update(JSON.stringify(entries.map((e) => ({
29779
29845
  path: e.relativePath,
29780
29846
  content: [e.title, e.type, e.status, e.confidence, e.summary, e.details, e.tags, e.scopes, e.validFrom, e.validTo].join("\x00")
29781
29847
  })))).digest("hex"),
@@ -35918,7 +35984,7 @@ var init_github = __esm(() => {
35918
35984
  });
35919
35985
 
35920
35986
  // src/harness/tool/metaproject-adapter.ts
35921
- import { lstat as lstat3, readdir as readdir17, readFile as readFile74, realpath as realpath6, stat as stat14 } from "fs/promises";
35987
+ import { lstat as lstat4, readdir as readdir17, readFile as readFile74, realpath as realpath6, stat as stat14 } from "fs/promises";
35922
35988
  import { isAbsolute, join, relative, resolve } from "path";
35923
35989
  function isMissingExecutable(cause) {
35924
35990
  const message = cause instanceof Error ? cause.message : String(cause);
@@ -35974,12 +36040,16 @@ function flattenAttribution(attribution) {
35974
36040
  }
35975
36041
  async function projectRemovalTrail(cwd, query) {
35976
36042
  const lookup = searchRemovals(await loadDeletionTrail(cwd), query);
36043
+ if (lookup.verdict === "trail-absent") {
36044
+ return { verdict: "trail-absent", summary: TRAIL_ABSENT_SUMMARY };
36045
+ }
36046
+ const summary = clipAutomaticRecallText(lookup.reason.split(`${cwd}/`).join(""), MAX_REMOVAL_SUMMARY_BYTES);
35977
36047
  if (lookup.verdict !== "recorded-removed") {
35978
- return { verdict: lookup.verdict, summary: clipAutomaticRecallText(lookup.reason, MAX_REMOVAL_SUMMARY_BYTES) };
36048
+ return { verdict: lookup.verdict, summary };
35979
36049
  }
35980
36050
  return {
35981
36051
  verdict: "recorded-removed",
35982
- summary: clipAutomaticRecallText(lookup.reason, MAX_REMOVAL_SUMMARY_BYTES),
36052
+ summary,
35983
36053
  totalRemovals: lookup.occurrences.length,
35984
36054
  removals: lookup.occurrences.slice(0, MAX_REMOVAL_RECORDS).map((item) => ({
35985
36055
  layer: item.layer,
@@ -36049,7 +36119,7 @@ async function parseCatalogSummaries(cwd) {
36049
36119
  async function walkSkillCatalog(cwd) {
36050
36120
  const root = join(cwd, ".metaproject", "skills", "gdskills");
36051
36121
  const entries = [];
36052
- const rootInfo = await lstat3(root).catch(() => null);
36122
+ const rootInfo = await lstat4(root).catch(() => null);
36053
36123
  if (!rootInfo || !rootInfo.isDirectory() || rootInfo.isSymbolicLink())
36054
36124
  return entries;
36055
36125
  const rootReal = await realpath6(root).catch(() => null);
@@ -36735,7 +36805,7 @@ function createMetaprojectAdapter(cwd, overrides = {}) {
36735
36805
  }
36736
36806
  };
36737
36807
  }
36738
- var DEFAULT_DEPS, MAX_EXCERPT_BYTES = 400, MAX_QUERY_BYTES2 = 4096, UNKNOWN_PROVENANCE = "unknown", MAX_SEARCH_OUTPUT_BYTES = 20000, SEARCH_CODE_RG_MISSING, MAX_REMOVAL_RECORDS = 5, MAX_REMOVAL_SUMMARY_BYTES = 1600;
36808
+ var DEFAULT_DEPS, MAX_EXCERPT_BYTES = 400, MAX_QUERY_BYTES2 = 4096, UNKNOWN_PROVENANCE = "unknown", MAX_SEARCH_OUTPUT_BYTES = 20000, SEARCH_CODE_RG_MISSING, MAX_REMOVAL_RECORDS = 5, MAX_REMOVAL_SUMMARY_BYTES = 1600, TRAIL_ABSENT_SUMMARY = 'no removal has ever been recorded in this project (it has no deletion trail) \u2014 not the claim "this never existed".';
36739
36809
  var init_metaproject_adapter = __esm(() => {
36740
36810
  init_wiki_freshness();
36741
36811
  init_fs();
@@ -38150,7 +38220,7 @@ var init_metaproject_operations = __esm(() => {
38150
38220
  });
38151
38221
 
38152
38222
  // src/wiki/deep-enrich.ts
38153
- import { randomUUID as randomUUID15, createHash as createHash28 } from "crypto";
38223
+ import { randomUUID as randomUUID15, createHash as createHash29 } from "crypto";
38154
38224
  function composeAbortSignals(external, timeout) {
38155
38225
  if (external === undefined) {
38156
38226
  return { signal: timeout, dispose: () => {} };
@@ -38181,7 +38251,7 @@ function errorMessage2(cause) {
38181
38251
  return cause instanceof Error ? cause.message : String(cause);
38182
38252
  }
38183
38253
  function sha256Hex3(text) {
38184
- return createHash28("sha256").update(text).digest("hex");
38254
+ return createHash29("sha256").update(text).digest("hex");
38185
38255
  }
38186
38256
  function buildDeepEnrichTools(port) {
38187
38257
  const deepOps = METAPROJECT_OPERATIONS.filter((op) => DEEP_ENRICH_OPS.includes(op.name));
@@ -41172,15 +41242,43 @@ async function refreshProviderGrant(provider, http, dir) {
41172
41242
  saveOAuthGrant(provider, next, dir);
41173
41243
  return next;
41174
41244
  }
41245
+ async function refreshSavedGrants(http, dir, providers = SESSION_REFRESHED_GRANTS) {
41246
+ const warnings = [];
41247
+ const now = http.now ?? Date.now;
41248
+ for (const provider of providers) {
41249
+ if (!SESSION_REFRESHED_GRANTS.includes(provider))
41250
+ continue;
41251
+ const grant = loadOAuthGrant(provider, dir);
41252
+ if (grant === undefined)
41253
+ continue;
41254
+ if (grant.refresh === undefined || grant.refresh.length === 0) {
41255
+ if (grant.expires !== undefined && grant.expires <= now()) {
41256
+ warnings.push(`${provider}: the saved login has expired and holds no refresh token. Run \`keryx auth login ${provider}\`.`);
41257
+ }
41258
+ continue;
41259
+ }
41260
+ if (!grantNeedsRefresh(grant, http.now))
41261
+ continue;
41262
+ try {
41263
+ await refreshProviderGrant(provider, http, dir);
41264
+ } catch (cause) {
41265
+ const reason = cause instanceof Error ? cause.message : String(cause);
41266
+ warnings.push(`${provider}: the saved login is expiring or expired and could not be refreshed (${reason}). ` + `Run \`keryx auth login ${provider}\`.`);
41267
+ }
41268
+ }
41269
+ return warnings;
41270
+ }
41175
41271
  function logoutProvider(provider, dir) {
41176
41272
  deleteOAuthGrant(provider, dir);
41177
41273
  }
41274
+ var SESSION_REFRESHED_GRANTS;
41178
41275
  var init_login = __esm(() => {
41179
41276
  init_catalog();
41180
41277
  init_copilot();
41181
41278
  init_device_code();
41182
41279
  init_grants();
41183
41280
  init_openai_codex();
41281
+ SESSION_REFRESHED_GRANTS = ["grok", "github-copilot"];
41184
41282
  });
41185
41283
 
41186
41284
  // src/cli.ts
@@ -41193,7 +41291,7 @@ import {
41193
41291
  chmod as chmod2,
41194
41292
  mkdir as mkdir14,
41195
41293
  readFile as readFile23,
41196
- unlink,
41294
+ unlink as unlink2,
41197
41295
  writeFile as writeFile16
41198
41296
  } from "fs/promises";
41199
41297
  import { existsSync as existsSync11 } from "fs";
@@ -42784,7 +42882,7 @@ var BUNDLED_GDSKILLS = [
42784
42882
  "If the request asks to create, run, resume, track, or finish a managed flow and Task Manager is enabled, route implementation work to `gdskills/orchestration/flow-orchestrator/SKILL.md` before `job-orchestrator`.",
42785
42883
  "Prefer project-local skills and module manifests before broad raw file search.",
42786
42884
  "Route to the narrowest applicable skill and record unavailable modules explicitly."
42787
- ], ["any repository task", "route context", "which skill should be used", "ordinary product-development request", "agent should decide tools"]),
42885
+ ], ["any repository task", "route context", "which skill should be used", "ordinary product-development request", "agent should decide tools"], "Use when a request's routing to a Metaproject module, skill, or project-skill is unclear and needs deciding before any work starts. NOT for: picking between gdgraph, gdctx, gdwiki, memory, and health once the module is already known (see context-router)."),
42788
42886
  skill("context-router", "core", ["minimal", "recommended", "full"], "Choose between gdgraph, gdctx, gdwiki, memory, health, and project-skills before raw file reads.", [
42789
42887
  "Start from the user's goal, not from command names.",
42790
42888
  "Use gdgraph for file relationships and affected context.",
@@ -42794,13 +42892,13 @@ var BUNDLED_GDSKILLS = [
42794
42892
  "Use health for normalized quality and gate status; use testing for test selection and test context.",
42795
42893
  "Prefer MCP tools/resources for these capabilities when connected; otherwise use the matching skill and CLI command.",
42796
42894
  "Use project-skills for known modules, components, stores, services, and domain entities."
42797
- ], ["find files", "understand code", "collect context", "what should I inspect", "agent routing"]),
42895
+ ], ["find files", "understand code", "collect context", "what should I inspect", "agent routing"], "Use when the Metaproject module or skill is already known and the next step is picking gdgraph, gdctx, gdwiki, memory, health, or project-skills before reading raw files. NOT for: deciding which top-level skill or module should handle a request (see metaproject-router)."),
42798
42896
  skill("entity-skill-router", "core", ["minimal", "recommended", "full"], "Select relevant project-skills for known modules, components, stores, services, and domain entities.", [
42799
42897
  "Check `.metaproject/project-skills` for matching module/entity skills.",
42800
42898
  "Use gdgraph affected context to find nearby entities when the target is a file.",
42801
42899
  "Load only the matching project-skill and directly referenced files.",
42802
42900
  "If no project-skill exists, suggest creating one with `keryx skills generate`."
42803
- ], ["project skill", "component pattern", "module-specific work"]),
42901
+ ], ["project skill", "component pattern", "module-specific work"], "Use when a known module, component, store, service, or domain entity already has a project-skill that should be loaded instead of searched for from scratch. NOT for: scaffolding a project-skill that does not exist yet (see entity-skill-creator), checking one against current code (see entity-skill-verifier), or applying a finding to one (see entity-skill-learner)."),
42804
42902
  skill("entity-skill-creator", "core", ["minimal", "recommended", "full"], "Create canonical project-skills from a path, symbol, wiki page, module, component, store, service, or domain entity.", [
42805
42903
  "Normalize the target into module, entity, files, symbols, and wiki references.",
42806
42904
  "Collect evidence from gdgraph, gdctx, gdwiki, health, and memory when available.",
@@ -42809,7 +42907,7 @@ var BUNDLED_GDSKILLS = [
42809
42907
  "To refresh a project-skill from its Origin (or a new file), run `keryx skills update <module>/<name> [--from <origin>]`.",
42810
42908
  "Run `keryx skills route <target>` and `keryx skills inspect <module>/<skill-name>` to confirm registration and routing.",
42811
42909
  "Run `keryx skills verify <module>/<skill-name>` and finish with `keryx skills status`."
42812
- ], ["create skill", "generate project skill", "new entity skill", "\u0441\u043E\u0437\u0434\u0430\u0439 \u0441\u043A\u0438\u043B", "\u0441\u043E\u0437\u0434\u0430\u0439 \u0441\u043A\u0438\u043B\u043B \u0434\u043B\u044F <path>", "import skill", "import skills from", "update skill from", "\u043F\u043E\u0434\u0442\u044F\u043D\u0438 \u0441\u043A\u0438\u043B\u044B", "\u043E\u0431\u043D\u043E\u0432\u0438 \u0441\u043A\u0438\u043B"]),
42910
+ ], ["create skill", "generate project skill", "new entity skill", "\u0441\u043E\u0437\u0434\u0430\u0439 \u0441\u043A\u0438\u043B", "\u0441\u043E\u0437\u0434\u0430\u0439 \u0441\u043A\u0438\u043B\u043B \u0434\u043B\u044F <path>", "import skill", "import skills from", "update skill from", "\u043F\u043E\u0434\u0442\u044F\u043D\u0438 \u0441\u043A\u0438\u043B\u044B", "\u043E\u0431\u043D\u043E\u0432\u0438 \u0441\u043A\u0438\u043B"], "Use when no project-skill exists yet for a target module, component, store, service, or domain entity, or one needs scaffolding, importing, or refreshing from its origin. NOT for: loading a project-skill that already exists (see entity-skill-router)."),
42813
42911
  skill("reviewer-skill-creator", "core", ["recommended", "full"], "Create a project-local reviewer for review-orchestrator from a rules file, review profile, or written team standard.", [
42814
42912
  "Read the source in full and sort it into method, convention, and persona before writing anything.",
42815
42913
  "Scaffold with `keryx skills create <target> --module review --name <reviewer> --note <gist> --origin <source file>`; the target is a routing key, the note is the prose.",
@@ -42819,18 +42917,18 @@ var BUNDLED_GDSKILLS = [
42819
42917
  "Confirm with `keryx review reviewers` \u2014 creating files is not registration, and registration is not discovery.",
42820
42918
  "For existing SKILL.md packages, run `keryx skills import --from <dir|file|https-url> --module <module>`; `keryx review import` is the review-shaped alias with the review-vantage-* prefix."
42821
42919
  ], ["create a reviewer", "new reviewer for review-orchestrator", "make a reviewer from this profile", "\u0441\u043E\u0437\u0434\u0430\u0439 \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430", "\u0441\u043E\u0437\u0434\u0430\u0439 \u043D\u043E\u0432\u043E\u0433\u043E \u0440\u0435\u0432\u044C\u044E\u0435\u0440\u0430 \u043D\u0430 \u043E\u0441\u043D\u043E\u0432\u0430\u043D\u0438\u0438", "import vantage reviewers", "import overlay reviewers"]),
42822
- skill("entity-skill-verifier", "core", ["minimal", "recommended", "full"], "Verify project-skills against current code, graph, wiki, health, memory, tests, and review lessons.", [
42823
- "Resolve candidate skills through ownership and gdgraph affected context.",
42824
- "Compare skill claims with current code, wiki decisions, health reports, and memory.",
42825
- "Classify each skill as fresh, stale, needs-review, or blocked.",
42826
- "Write a verification report and only update generated sections when policy allows it."
42827
- ], ["verify skill", "skill-verify-skill", "stale skill"]),
42920
+ skill("entity-skill-verifier", "core", ["minimal", "recommended", "full"], "Run `keryx skills verify` to check a project-skill's required files, SKILL.md metadata, manifest registration, target-path existence, and evidence artifacts (gdgraph, gdctx, validated gdwiki, Code Health, canonical accepted memory), then classify it as fresh, needs-review, stale, or blocked. The command does not read the skill's prose or compare it against current code \u2014 that comparison is a manual agent step.", [
42921
+ "Resolve the target project-skill through gdgraph affected context or `keryx skills route <target>`.",
42922
+ "Run `keryx skills verify <module>/<skill-name>` (`--dry-run` previews without writing).",
42923
+ "The command checks required files, SKILL.md metadata (version, target, last-verified), manifest registration, target path existence, and evidence artifacts for gdgraph, gdctx, gdwiki, Code Health, and memory consultation, then classifies the skill fresh, needs-review, stale, or blocked and writes the JSON report plus `verification.md`.",
42924
+ "Manually read the skill and the code, wiki, and health evidence it points at to check its claims \u2014 the command does not do this comparison \u2014 then route stale or blocked findings to entity-skill-learner."
42925
+ ], ["verify skill", "skill-verify-skill", "stale skill"], "Use when an existing project-skill needs classifying \u2014 fresh, needs-review, stale, or blocked \u2014 from its required files, metadata, manifest registration, target-path existence, and evidence artifacts (gdgraph, gdctx, gdwiki, Code Health, memory), followed by a manual read of its claims against current code as this skill's next step. NOT for: applying a review, test, or health finding to update a skill (see entity-skill-learner)."),
42828
42926
  skill("entity-skill-learner", "core", ["minimal", "recommended", "full"], "Update project-skills from review findings, test failures, health reports, memory entries, and verifier reports.", [
42829
42927
  "Parse the source report and map findings to project-skills.",
42830
42928
  "Classify lessons as anti-patterns, checklist changes, template changes, workflow changes, or architecture rules.",
42831
42929
  "Respect manual sections and autonomy policy.",
42832
42930
  "Increment version and append `skill-changelog.md` entries with provenance."
42833
- ], ["learn from review", "update skill", "skill lesson"]),
42931
+ ], ["learn from review", "update skill", "skill lesson"], "Use when a review finding, test failure, health report, memory entry, or verifier report points at a project-skill that needs updating. NOT for: checking a skill's current accuracy with no source report driving it (see entity-skill-verifier)."),
42834
42932
  skill("job-orchestrator", "orchestration", ["recommended", "full"], "Run full task pipelines: clarify, collect context, plan, implement, verify, review, and summarize.", [
42835
42933
  "Clarify ambiguity with interviewer-style questions only when required.",
42836
42934
  "Collect compact context through Metaproject modules before implementation.",
@@ -43177,7 +43275,7 @@ var BUNDLED_GDSKILLS = [
43177
43275
  "Find existing root agent entrypoints.",
43178
43276
  "Keep managed Metaproject blocks idempotent.",
43179
43277
  "Ensure local `.metaproject/index.md` and skill catalog are first-class references."
43180
- ], ["agents.md", "claude.md", "entrypoint"]),
43278
+ ], ["agents.md", "claude.md", "entrypoint"], "Use when AGENTS.md or CLAUDE.md needs its managed Metaproject block added, refreshed, or kept idempotent. NOT for: splitting an oversized entrypoint into rules and project-skills (see agent-entrypoint-distiller)."),
43181
43279
  skill("agent-entrypoint-distiller", "platform", ["minimal", "recommended", "full"], "Split large AGENTS.md/CLAUDE.md files into high-priority Metaproject rules and project-specific skills.", [
43182
43280
  "Run `keryx rules distill` when the user asks to decompose a large CLAUDE.md/AGENTS.md.",
43183
43281
  "Keep root entrypoints compact: non-project/highest-priority instructions plus `.metaproject/index.md` routing.",
@@ -43187,7 +43285,7 @@ var BUNDLED_GDSKILLS = [
43187
43285
  "Install hooks only when explicitly enabled.",
43188
43286
  "Keep hooks lightweight and idempotent.",
43189
43287
  "Avoid network and destructive behavior inside hooks."
43190
- ], ["install hook", "git hook", "post-commit"]),
43288
+ ], ["install hook", "git hook", "post-commit"], "Use when a lightweight, explicitly-enabled git hook for graph, health, or skill verification needs installing or checking. NOT for: general hook design guidance not tied to these three (see hookify)."),
43191
43289
  skill("hookify", "platform", ["full"], "Use hook guidance for safe hook design and installation.", [
43192
43290
  "Detect existing hooks and preserve user content.",
43193
43291
  "Install idempotent managed blocks.",
@@ -43202,23 +43300,23 @@ var BUNDLED_GDSKILLS = [
43202
43300
  "Read bundled and project-local skill metadata.",
43203
43301
  "Generate concise catalog entries grouped by category.",
43204
43302
  "Keep catalog deterministic and local-first."
43205
- ], ["skill catalog", "list skills", "skills registry"]),
43303
+ ], ["skill catalog", "list skills", "skills registry"], "Use when `.metaproject/skills/catalog.md` or the machine-readable skill registry is missing or out of date with bundled and project-local skill metadata. NOT for: turning skills into runtime artifacts (see skill-runtime-exporter) or pushing them to local runtimes (see skill-sync)."),
43206
43304
  skill("skill-runtime-exporter", "platform", ["full"], "Export canonical skills to runtime-compatible Codex or Claude artifacts.", [
43207
43305
  "Read canonical skill packages.",
43208
43306
  "Remove management-only files from runtime exports.",
43209
43307
  "Keep runtime `SKILL.md` concise with references, scripts, and assets as needed."
43210
- ], ["export skill", "runtime skill", "codex skill"]),
43308
+ ], ["export skill", "runtime skill", "codex skill"], "Use when canonical skill packages need turning into runtime-compatible Codex or Claude artifacts, stripped of management-only files. NOT for: regenerating the skill catalog (see skill-catalog-manager) or pushing the export to local runtimes (see skill-sync)."),
43211
43309
  skill("skill-sync", "platform", ["full"], "Sync exported runtime skills to configured local runtimes only when explicitly enabled.", [
43212
43310
  "Read configured runtime targets.",
43213
43311
  "Validate runtime skill packages before sync.",
43214
43312
  "Sync only selected skills and report changed files."
43215
- ], ["sync skills", "install runtime skills", "global skill sync"])
43313
+ ], ["sync skills", "install runtime skills", "global skill sync"], "Use when already-exported runtime skills need pushing to configured local runtimes, and only when sync is explicitly enabled. NOT for: producing the runtime export itself (see skill-runtime-exporter).")
43216
43314
  ];
43217
- function skill(name, category, profiles, purpose, workflow, triggers) {
43315
+ function skill(name, category, profiles, purpose, workflow, triggers, descriptionOverride) {
43218
43316
  return {
43219
43317
  name,
43220
43318
  category,
43221
- description: `Use when ${purpose.charAt(0).toLowerCase()}${purpose.slice(1)}`,
43319
+ description: descriptionOverride ?? `Use when ${purpose.charAt(0).toLowerCase()}${purpose.slice(1)}`,
43222
43320
  purpose,
43223
43321
  workflow,
43224
43322
  triggers,
@@ -43243,9 +43341,10 @@ function renderBundledSkill(skillEntry) {
43243
43341
  const workflow = skillEntry.workflow.map((step, index) => `${index + 1}. ${step}`).join(`
43244
43342
  `);
43245
43343
  const commandContract = renderAgentCommandContract(skillEntry);
43344
+ const descriptionField = skillEntry.description.includes(":") ? `description: ${JSON.stringify(skillEntry.description)}` : `description: ${skillEntry.description}`;
43246
43345
  return `---
43247
43346
  name: ${skillEntry.name}
43248
- description: ${skillEntry.description}
43347
+ ${descriptionField}
43249
43348
  ---
43250
43349
 
43251
43350
  # ${skillEntry.name}
@@ -43380,8 +43479,9 @@ ${sections}
43380
43479
 
43381
43480
  // src/gdskills/install.ts
43382
43481
  init_fs();
43383
- import { cp, copyFile, mkdir as mkdir7, readFile as readFile10, writeFile as writeFile7 } from "fs/promises";
43482
+ import { cp, copyFile, lstat as lstat3, mkdir as mkdir7, readFile as readFile10, unlink, writeFile as writeFile7 } from "fs/promises";
43384
43483
  import { existsSync as existsSync9 } from "fs";
43484
+ import { createHash as createHash5 } from "crypto";
43385
43485
  import path31 from "path";
43386
43486
  import { fileURLToPath as fileURLToPath3 } from "url";
43387
43487
 
@@ -43742,6 +43842,27 @@ function relativeContractPath(contract) {
43742
43842
  return contract.sourcePath ?? path30.join("src", "gdskills", "contracts", contract.fileName);
43743
43843
  }
43744
43844
 
43845
+ // src/gdskills/retired-rules.ts
43846
+ var RETIRED_RULES = [
43847
+ {
43848
+ fileName: "review-agent-profile.mdc",
43849
+ shippedSha256: [
43850
+ "e29a832821e501fa8a0acb5130007db1e21f1cee29f8ab6b2a1a7b35f9945281"
43851
+ ],
43852
+ reason: "Byte-identical duplicate of code-review-ai-assistant.mdc \u2014 nothing distinguished the two, and only the latter was ever routed to."
43853
+ },
43854
+ {
43855
+ fileName: "review-strict-profile.mdc",
43856
+ shippedSha256: [
43857
+ "2e8847426bfba76ece8a208952402edadc6a062920aed07a721902b635a3f0e6",
43858
+ "bc7e6df820682f75cbc1349e70d98b5692aa1c4bae903e28f41a67edb7a5a484"
43859
+ ],
43860
+ reason: "Outlived the review-strict skill it backed; review-orchestrator's Step 6 now resolves the model tier directly via `keryx review tier` instead."
43861
+ }
43862
+ ];
43863
+ var LARGEST_SHIPPED_RETIRED_RULE_BYTES = 2248;
43864
+ var RETIRED_RULE_SIZE_CAP_BYTES = LARGEST_SHIPPED_RETIRED_RULE_BYTES * 4;
43865
+
43745
43866
  // src/gdskills/install.ts
43746
43867
  async function installGdskills(metaprojectRoot, profile, options = {}) {
43747
43868
  const skills = getBundledSkillsForProfile(profile);
@@ -43773,18 +43894,20 @@ async function installGdskills(metaprojectRoot, profile, options = {}) {
43773
43894
  }
43774
43895
  }
43775
43896
  await installBundledSharedSkills(skillsRoot);
43776
- await installBundledRules(metaprojectRoot);
43897
+ const retiredRuleOutcomes = await installBundledRules(metaprojectRoot);
43777
43898
  const catalogPath = path31.join(metaprojectRoot, "skills", "catalog.md");
43778
43899
  await writeFile7(catalogPath, await preserveProjectSkillsSection(catalogPath, renderGdskillsCatalog(profile)), "utf8");
43779
43900
  const manifestPath = path31.join(metaprojectRoot, "modules", "gdskills.md");
43780
43901
  await writeFile7(manifestPath, renderGdskillsManifest(profile), "utf8");
43781
43902
  await installContracts(contractsRoot);
43903
+ const warnings = retiredRuleOutcomes.map(retiredRuleWarning).filter((warning) => warning !== null);
43782
43904
  return {
43783
43905
  profile,
43784
43906
  installedSkills: skills.length,
43785
43907
  skillsRoot,
43786
43908
  catalogPath,
43787
- manifestPath
43909
+ manifestPath,
43910
+ warnings
43788
43911
  };
43789
43912
  }
43790
43913
  async function installBundledSharedSkills(skillsRoot) {
@@ -43796,10 +43919,83 @@ async function installBundledSharedSkills(skillsRoot) {
43796
43919
  }
43797
43920
  async function installBundledRules(metaprojectRoot) {
43798
43921
  const rulesSource = bundledRulesSourcePath();
43922
+ const rulesTarget = path31.join(metaprojectRoot, "rules", "core");
43799
43923
  if (!existsSync9(rulesSource)) {
43800
- return;
43924
+ return [];
43925
+ }
43926
+ await cp(rulesSource, rulesTarget, { recursive: true, force: true });
43927
+ return removeUnmodifiedRetiredRules(rulesTarget);
43928
+ }
43929
+ async function removeUnmodifiedRetiredRules(rulesTarget, fsOps = { unlink }) {
43930
+ const outcomes = [];
43931
+ for (const retired of RETIRED_RULES) {
43932
+ const installedPath = path31.join(rulesTarget, retired.fileName);
43933
+ let stage = "lstat";
43934
+ try {
43935
+ const stats = await lstat3(installedPath);
43936
+ if (!stats.isFile()) {
43937
+ outcomes.push({ fileName: retired.fileName, action: "kept-not-regular-file" });
43938
+ continue;
43939
+ }
43940
+ if (stats.size > RETIRED_RULE_SIZE_CAP_BYTES) {
43941
+ outcomes.push({ fileName: retired.fileName, action: "kept-oversized", sizeBytes: stats.size });
43942
+ continue;
43943
+ }
43944
+ stage = "read";
43945
+ const content = await readFile10(installedPath);
43946
+ const hash = createHash5("sha256").update(normalizeRetiredRuleContent(content)).digest("hex");
43947
+ if (retired.shippedSha256.includes(hash)) {
43948
+ stage = "unlink";
43949
+ await fsOps.unlink(installedPath);
43950
+ outcomes.push({ fileName: retired.fileName, action: "removed" });
43951
+ } else {
43952
+ outcomes.push({ fileName: retired.fileName, action: "kept-modified" });
43953
+ }
43954
+ } catch (error) {
43955
+ if (isErrnoException(error) && error.code === "ENOENT") {
43956
+ continue;
43957
+ }
43958
+ outcomes.push({
43959
+ fileName: retired.fileName,
43960
+ action: "kept-error",
43961
+ stage,
43962
+ errorCode: isErrnoException(error) && error.code ? error.code : "UNKNOWN"
43963
+ });
43964
+ }
43965
+ }
43966
+ return outcomes;
43967
+ }
43968
+ function normalizeRetiredRuleContent(content) {
43969
+ let text = content.toString("utf8");
43970
+ if (text.charCodeAt(0) === 65279) {
43971
+ text = text.slice(1);
43972
+ }
43973
+ return text.replace(/\r\n/g, `
43974
+ `);
43975
+ }
43976
+ function isErrnoException(error) {
43977
+ return error instanceof Error && "code" in error;
43978
+ }
43979
+ function retiredRuleWarning(outcome) {
43980
+ if (outcome.action === "removed") {
43981
+ return null;
43982
+ }
43983
+ const reason = RETIRED_RULES.find((retired) => retired.fileName === outcome.fileName)?.reason ?? "no longer shipped by keryx";
43984
+ const prefix = `${outcome.fileName} is no longer shipped by keryx (${reason})`;
43985
+ switch (outcome.action) {
43986
+ case "kept-modified":
43987
+ return `${prefix}; kept because it differs from every shipped version \u2014 delete it, or rename it if you still rely on it`;
43988
+ case "kept-not-regular-file":
43989
+ return `${prefix}; kept because it is not a regular file (a symlink, directory, or similar) \u2014 keryx will not read or remove it; delete or rename it yourself if appropriate`;
43990
+ case "kept-oversized":
43991
+ return `${prefix}; kept because it is ${outcome.sizeBytes} bytes, far larger than any version keryx ever shipped under this name \u2014 inspect it, then delete or rename it if appropriate`;
43992
+ case "kept-error":
43993
+ return outcome.stage === "unlink" ? `${prefix}; matches a shipped version but could not be removed (${outcome.errorCode}) \u2014 delete it by hand` : `${prefix}; kept because it could not be read (${outcome.errorCode}) \u2014 inspect it, then delete or rename it if appropriate`;
43994
+ default: {
43995
+ const exhaustive = outcome;
43996
+ throw new Error(`unhandled retired rule outcome: ${JSON.stringify(exhaustive)}`);
43997
+ }
43801
43998
  }
43802
- await cp(rulesSource, path31.join(metaprojectRoot, "rules", "core"), { recursive: true, force: true });
43803
43999
  }
43804
44000
  async function preserveProjectSkillsSection(catalogPath, nextCatalog) {
43805
44001
  if (!await pathExists(catalogPath)) {
@@ -46105,13 +46301,33 @@ function escapeHtml(value) {
46105
46301
  function renderProjectRulesReadme() {
46106
46302
  return `# Project Rules
46107
46303
 
46108
- This directory stores repository-level instructions imported from root agent entrypoints such as \`AGENTS.md\` or \`CLAUDE.md\`.
46304
+ This directory holds two different kinds of rule files. Do not treat them the same way.
46109
46305
 
46110
- Rules:
46306
+ ## Imported entrypoint rules (this directory)
46307
+
46308
+ The \`.md\` files here (e.g. \`agents-md.md\`, \`claude-md.md\`) are repository-level instructions
46309
+ imported from root agent entrypoints such as \`AGENTS.md\` or \`CLAUDE.md\`.
46111
46310
 
46112
46311
  - treat files here as high-priority agent-readable mirrors of root instructions;
46113
46312
  - update the root entrypoint first when changing project-wide instructions;
46114
46313
  - rerun \`keryx rules sync\`, \`keryx init\`, or \`keryx update\` to resync imported rule files.
46314
+
46315
+ ## Core rule library (\`core/*.mdc\`)
46316
+
46317
+ \`core/*.mdc\` is an on-demand rule library, not something loaded automatically. A rule file is
46318
+ read only when a skill or \`routing.md\` cites it by path.
46319
+
46320
+ - \`alwaysApply\` and \`globs\` in each rule's frontmatter are Cursor-format metadata for Cursor's
46321
+ own auto-attach behavior; keryx itself does not read or act on either field;
46322
+ - a stack-specific rule (e.g. \`nestjs-dto.mdc\`, \`mobx-store-template.mdc\`) declares the stack(s)
46323
+ it applies to via a \`stack_requires\` frontmatter key, using the same tag vocabulary as a
46324
+ skill's \`metadata.stack_requires\`; it is only relevant to projects on that stack;
46325
+ - the bundled tree (\`src/gdskills/bundled/rules/core/\`) is the source of truth \u2014 \`core/\` here is
46326
+ an installed mirror, and \`keryx init\`/\`keryx update\` overwrite it wholesale on install;
46327
+ a rule retired from the bundled tree is removed from here too when it is an unmodified shipped
46328
+ copy; otherwise it is kept (with a warning that says why \u2014 edited, not a regular file, oversized,
46329
+ or unreadable/unremovable), since discarding it silently could lose your changes or hide a
46330
+ filesystem problem.
46115
46331
  `;
46116
46332
  }
46117
46333
  function renderImportedAgentRules({
@@ -46119,7 +46335,7 @@ function renderImportedAgentRules({
46119
46335
  content
46120
46336
  }) {
46121
46337
  const body = extractAgentRuleBody(content);
46122
- const normalizedBody = body.length > 0 ? body : `This root entrypoint delegates agent routing to \`.metaproject/index.md\`.
46338
+ const normalizedBody = body.length > 0 && !isHeadingOnlyBody(body) ? body : `This root entrypoint delegates agent routing to \`.metaproject/index.md\`.
46123
46339
 
46124
46340
  Read \`.metaproject/index.md\` first, then follow the high-priority rules, skills, and module references listed there.`;
46125
46341
  return `---
@@ -46143,6 +46359,9 @@ This file is generated from the repository root agent entrypoint. Edit \`${sourc
46143
46359
  ${normalizedBody}
46144
46360
  `;
46145
46361
  }
46362
+ function isHeadingOnlyBody(body) {
46363
+ return body.replace(/^#[^\n]*\n?/, "").trim().length === 0;
46364
+ }
46146
46365
  function extractAgentRuleBody(content) {
46147
46366
  const marker = "<!-- keryx:index -->";
46148
46367
  const endMarker = "<!-- /keryx:index -->";
@@ -47243,13 +47462,13 @@ import { readFile as readFile17, rm as rm3 } from "fs/promises";
47243
47462
  import path44 from "path";
47244
47463
 
47245
47464
  // src/lib/install-plan.ts
47246
- import { createHash as createHash5 } from "crypto";
47465
+ import { createHash as createHash6 } from "crypto";
47247
47466
  import { readFile as readFile16 } from "fs/promises";
47248
47467
  import path43 from "path";
47249
47468
  // package.json
47250
47469
  var package_default = {
47251
47470
  name: "@mrciphersmith/keryx",
47252
- version: "0.2.97",
47471
+ version: "0.2.98",
47253
47472
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
47254
47473
  private: false,
47255
47474
  publishConfig: {
@@ -47390,7 +47609,7 @@ function parseDivergenceResolution(args) {
47390
47609
  return;
47391
47610
  }
47392
47611
  function digestOf(content) {
47393
- return `sha256:${createHash5("sha256").update(content, "utf8").digest("hex")}`;
47612
+ return `sha256:${createHash6("sha256").update(content, "utf8").digest("hex")}`;
47394
47613
  }
47395
47614
  function installJournalPath(metaprojectRoot) {
47396
47615
  return path43.join(metaprojectRoot, INSTALL_JOURNAL_RELATIVE_PATH);
@@ -49612,8 +49831,10 @@ async function initCommand(args) {
49612
49831
  await installGdwikiPostCommitHook(projectRoot);
49613
49832
  }
49614
49833
  }
49834
+ let gdskillsWarnings = [];
49615
49835
  if (enableGdskills) {
49616
- await installGdskills(metaprojectRoot, gdskillsProfile);
49836
+ const gdskillsInstallResult = await installGdskills(metaprojectRoot, gdskillsProfile);
49837
+ gdskillsWarnings = gdskillsInstallResult.warnings;
49617
49838
  if (enableGdskillsHook) {
49618
49839
  await installGdskillsPostCommitHook(projectRoot);
49619
49840
  }
@@ -49867,6 +50088,12 @@ async function initCommand(args) {
49867
50088
  if (enableSac) {
49868
50089
  statusLine("sac", true, "shared agent context: cross-session workspace propose/review (opt-in)");
49869
50090
  }
50091
+ if (gdskillsWarnings.length > 0) {
50092
+ heading("Warnings");
50093
+ for (const warning of gdskillsWarnings) {
50094
+ note(warning);
50095
+ }
50096
+ }
49870
50097
  const gitHooksRoot = await resolveGitHooksRoot(projectRoot);
49871
50098
  const hookLines = [];
49872
50099
  if (enableGdgraph) {
@@ -50240,7 +50467,7 @@ Use this skill when a task requires code graph context, dependency impact analys
50240
50467
  4. Use \`keryx gdgraph affected <target>\` before implementation or review.
50241
50468
  `;
50242
50469
  if (await readFile23(legacyReadmePath, "utf8") === legacyContent) {
50243
- await unlink(legacyReadmePath);
50470
+ await unlink2(legacyReadmePath);
50244
50471
  }
50245
50472
  }
50246
50473
  function runtimeSourcePath(relativePath) {
@@ -53184,7 +53411,7 @@ function readLiteral(source, start, quote) {
53184
53411
 
53185
53412
  // src/wiki/freshness/queue.ts
53186
53413
  init_fs();
53187
- import { readFile as readFile43, rename as rename3, stat as stat10, unlink as unlink2, writeFile as writeFile30 } from "fs/promises";
53414
+ import { readFile as readFile43, rename as rename3, stat as stat10, unlink as unlink3, writeFile as writeFile30 } from "fs/promises";
53188
53415
  import path86 from "path";
53189
53416
  var QUEUE_FILE = "freshness-queue.jsonl";
53190
53417
  var ROTATED_FILE = "freshness-queue.1.jsonl";
@@ -53225,7 +53452,7 @@ async function drainQueue(cwd) {
53225
53452
  async function clearQueue(cwd) {
53226
53453
  for (const file of [rotatedQueuePath(cwd), queuePath(cwd)]) {
53227
53454
  if (await pathExists(file)) {
53228
- await unlink2(file).catch(() => {
53455
+ await unlink3(file).catch(() => {
53229
53456
  return;
53230
53457
  });
53231
53458
  }
@@ -57464,6 +57691,8 @@ var BUNDLED_SKILL_CHECKS = [
57464
57691
  "frontmatter:name-unique",
57465
57692
  "frontmatter:description",
57466
57693
  "frontmatter:metadata",
57694
+ "frontmatter:harness-claude",
57695
+ "frontmatter:category",
57467
57696
  "catalog:registered",
57468
57697
  "model:concrete-declaration",
57469
57698
  "persona:name",
@@ -57608,17 +57837,60 @@ var INSTALLED_PREFIXES = [
57608
57837
  var GENERATED_PATH_ROOTS = [
57609
57838
  { prefix: "rules/entrypoints/", producedBy: "keryx rules distill" }
57610
57839
  ];
57611
- var PATH_REFERENCE = /(?:^|[\s"'`([])((?:\.metaproject\/|skills\/|rules\/|scripts\/)[\w./@-]*[\w.@-])/g;
57612
- function normaliseReferencePath(reference) {
57840
+ var PATH_REFERENCE = /(?:^|[\s"'`([])((?:\.metaproject\/|skills\/|rules\/|scripts\/)[\w./@*-]*[\w.@*-])/g;
57841
+ function resolveInstalledReference(reference, known) {
57613
57842
  for (const [from, to] of INSTALLED_PREFIXES) {
57614
57843
  if (reference.startsWith(from))
57615
57844
  return to + reference.slice(from.length);
57616
57845
  }
57617
57846
  if (reference.startsWith(".metaproject/"))
57618
57847
  return;
57619
- const root = reference.split("/")[0] ?? "";
57848
+ const segments = reference.split("/");
57849
+ const root = segments[0] ?? "";
57850
+ if (root === "skills") {
57851
+ if (segments.length === 3 && known.has(segments[2] ?? ""))
57852
+ return reference;
57853
+ return null;
57854
+ }
57620
57855
  return CHECKED_PATH_ROOTS.includes(root) ? reference : undefined;
57621
57856
  }
57857
+ function scanCrossReferences(text, root, known, checkSkillNames, add) {
57858
+ text.split(`
57859
+ `).forEach((line, index) => {
57860
+ if (checkSkillNames) {
57861
+ for (const pattern of SKILL_REFERENCE_PATTERNS) {
57862
+ for (const match of line.matchAll(pattern)) {
57863
+ const referenced = match[1];
57864
+ if (!referenced.includes("-"))
57865
+ continue;
57866
+ if (known.has(referenced))
57867
+ continue;
57868
+ if (KNOWN_EXTERNAL_SKILL_REFERENCES.has(referenced))
57869
+ continue;
57870
+ add("xref:skill", index + 1, `names a skill \`${referenced}\` that this tree does not ship; either bundle it or stop naming it.`);
57871
+ }
57872
+ }
57873
+ }
57874
+ for (const match of line.matchAll(PATH_REFERENCE)) {
57875
+ const raw = match[1];
57876
+ if (raw.includes("<") || raw.includes("*") || raw.includes("$"))
57877
+ continue;
57878
+ const resolved = resolveInstalledReference(raw, known);
57879
+ if (resolved === undefined)
57880
+ continue;
57881
+ if (resolved === null) {
57882
+ add("xref:path", index + 1, `path \`${raw}\` names the SOURCE tree's own layout, not an installed one \u2014 an installed project has no top-level \`skills/\`, only \`.metaproject/skills/gdskills/\u2026\`. Write \`.metaproject/skills/gdskills/${raw.slice("skills/".length)}\` (or the \`skills/gdskills/\u2026\` form), or, to name the whole skill rather than a file in it, drop everything past its directory.`);
57883
+ continue;
57884
+ }
57885
+ if (GENERATED_PATH_ROOTS.some((entry) => resolved === entry.prefix.replace(/\/$/, "") || resolved.startsWith(entry.prefix))) {
57886
+ continue;
57887
+ }
57888
+ if (existsSync24(path143.join(root, resolved)))
57889
+ continue;
57890
+ add("xref:path", index + 1, `path \`${raw}\` resolves to nothing under the shipped tree (looked for \`${resolved}\`).`);
57891
+ }
57892
+ });
57893
+ }
57622
57894
  var SKILL_DOCUMENT_NAMES = new Set(HARNESS_SKILL_RUNTIMES.map((runtime) => skillBuildFileName(runtime)));
57623
57895
  var KNOWN_SKILL_COMPANION_DOCUMENTS = new Map([
57624
57896
  [
@@ -57650,6 +57922,12 @@ function bundledSkillFiles(root) {
57650
57922
  function bundledSkillDocuments(root) {
57651
57923
  return walkSkillDocuments(root, (name) => SKILL_DOCUMENT_NAMES.has(name));
57652
57924
  }
57925
+ function bundledRuleFiles(root) {
57926
+ const rulesRoot = path143.join(root, "rules", "core");
57927
+ if (!existsSync24(rulesRoot))
57928
+ return [];
57929
+ return readdirSync2(rulesRoot, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => path143.join(rulesRoot, entry.name)).sort();
57930
+ }
57653
57931
  function defaultBundledRoot() {
57654
57932
  const directPath = path143.join(import.meta.dir, "bundled");
57655
57933
  if (existsSync24(directPath))
@@ -57672,6 +57950,7 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
57672
57950
  const skillDir = path143.dirname(file);
57673
57951
  const skill = path143.basename(skillDir);
57674
57952
  const rel = path143.relative(skillsRoot, file).split(path143.sep).join("/");
57953
+ const category = path143.dirname(rel).split("/")[0] ?? "";
57675
57954
  const text = readFileSync6(file, "utf8");
57676
57955
  const add = (check, line, message) => {
57677
57956
  findings.push({ check, skill, file: rel, line, message });
@@ -57710,9 +57989,25 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
57710
57989
  if (metadataVersion === null) {
57711
57990
  add("frontmatter:metadata", 1, "frontmatter `metadata` declares no `version`; without one a skill cannot be said to have changed.");
57712
57991
  }
57992
+ const metadataCategory = /^\s{2,}category\s*:\s*(.+)$/m.exec(block);
57993
+ if (metadataCategory !== null) {
57994
+ const declaredCategory = (metadataCategory[1] ?? "").trim().replace(/^["']|["']$/g, "");
57995
+ if (declaredCategory.length > 0 && declaredCategory !== category) {
57996
+ add("frontmatter:category", 1, `frontmatter \`metadata.category\` is "${declaredCategory}" but this skill ships under \`${category}/${skill}\`; set it to "${category}" or drop the field.`);
57997
+ }
57998
+ }
57999
+ }
58000
+ if (path143.basename(file) === "SKILL.md") {
58001
+ const compatibleHarnesses = /^\s{2,}compatible_harnesses\s*:\s*(.+)$/m.exec(block);
58002
+ if (compatibleHarnesses !== null) {
58003
+ const declared = (compatibleHarnesses[1] ?? "").trim().replace(/^["']|["']$/g, "");
58004
+ const harnesses = declared.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
58005
+ if (!harnesses.includes("claude")) {
58006
+ add("frontmatter:harness-claude", 1, `frontmatter \`compatible_harnesses\` ("${declared}") omits \`claude\`, but this file IS the Claude build \u2014 add \`claude\` to the list.`);
58007
+ }
58008
+ }
57713
58009
  }
57714
58010
  }
57715
- const category = path143.dirname(rel).split("/")[0] ?? "";
57716
58011
  if (!catalogued.has(`${category}/${skill}`)) {
57717
58012
  add("catalog:registered", null, `BUNDLED_GDSKILLS (src/gdskills/catalog.ts) does not name \`${category}/${skill}\`, so \`keryx skills install\` never copies this directory and no user ever sees it.`);
57718
58013
  }
@@ -57726,35 +58021,15 @@ function evaluateBundledTree(root = defaultBundledRoot()) {
57726
58021
  for (const offender of homePathOffenders(text)) {
57727
58022
  add("path:personal-home", offender.line, offender.why);
57728
58023
  }
57729
- text.split(`
57730
- `).forEach((line, index) => {
57731
- for (const pattern of SKILL_REFERENCE_PATTERNS) {
57732
- for (const match of line.matchAll(pattern)) {
57733
- const referenced = match[1];
57734
- if (!referenced.includes("-"))
57735
- continue;
57736
- if (known.has(referenced))
57737
- continue;
57738
- if (KNOWN_EXTERNAL_SKILL_REFERENCES.has(referenced))
57739
- continue;
57740
- add("xref:skill", index + 1, `names a skill \`${referenced}\` that this tree does not ship; either bundle it or stop naming it.`);
57741
- }
57742
- }
57743
- for (const match of line.matchAll(PATH_REFERENCE)) {
57744
- const raw = match[1];
57745
- if (raw.includes("<") || raw.includes("*") || raw.includes("$"))
57746
- continue;
57747
- const normalised = normaliseReferencePath(raw);
57748
- if (normalised === undefined)
57749
- continue;
57750
- if (GENERATED_PATH_ROOTS.some((entry) => normalised === entry.prefix.replace(/\/$/, "") || normalised.startsWith(entry.prefix))) {
57751
- continue;
57752
- }
57753
- if (existsSync24(path143.join(root, normalised)))
57754
- continue;
57755
- add("xref:path", index + 1, `path \`${raw}\` resolves to nothing under the shipped tree (looked for \`${normalised}\`).`);
57756
- }
57757
- });
58024
+ scanCrossReferences(text, root, known, true, add);
58025
+ }
58026
+ for (const file of bundledRuleFiles(root)) {
58027
+ const rel = path143.relative(root, file).split(path143.sep).join("/");
58028
+ const text = readFileSync6(file, "utf8");
58029
+ const add = (check, line, message) => {
58030
+ findings.push({ check, skill: path143.basename(file, path143.extname(file)), file: rel, line, message });
58031
+ };
58032
+ scanCrossReferences(text, root, known, false, add);
57758
58033
  }
57759
58034
  for (const dir of [...new Set(files.map((file) => path143.dirname(file)))].sort()) {
57760
58035
  for (const entry of readdirSync2(dir, { withFileTypes: true })) {
@@ -57878,6 +58153,12 @@ async function skillsCommand(args) {
57878
58153
  note(`skills ${relativeToCwd(result.skillsRoot)}`);
57879
58154
  note(`catalog ${relativeToCwd(result.catalogPath)}`);
57880
58155
  note(`manifest ${relativeToCwd(result.manifestPath)}`);
58156
+ if (result.warnings.length > 0) {
58157
+ console.log("Warnings:");
58158
+ for (const warning of result.warnings) {
58159
+ console.log(`- ${warning}`);
58160
+ }
58161
+ }
57881
58162
  nextSteps([
57882
58163
  `Browse the catalog: ${style.cyan("keryx skills catalog")}.`,
57883
58164
  `Route a request: ${style.cyan("keryx skills route <target>")}.`
@@ -64758,14 +65039,17 @@ init_pr_comments();
64758
65039
  init_loop();
64759
65040
 
64760
65041
  // src/review/stack.ts
64761
- var STACK_TAGS = ["nestjs", "react", "mobx", "prisma"];
65042
+ var STACK_TAGS = ["nestjs", "react", "mobx", "prisma", "playwright", "sql", "http-server"];
64762
65043
  function isStackTag(value) {
64763
65044
  return STACK_TAGS.includes(value);
64764
65045
  }
64765
65046
  var TAG_MARKERS = {
64766
65047
  react: ["react", "react-dom"],
64767
65048
  mobx: ["mobx", "mobx-react", "mobx-react-lite"],
64768
- prisma: ["prisma", "@prisma/client"]
65049
+ prisma: ["prisma", "@prisma/client"],
65050
+ playwright: ["@playwright/test", "playwright"],
65051
+ sql: ["prisma", "@prisma/client", "typeorm", "sequelize", "knex", "drizzle-orm", "pg", "mysql2", "better-sqlite3", "kysely"],
65052
+ "http-server": ["express", "fastify", "koa", "hono", "@nestjs/core", "@hapi/hapi"]
64769
65053
  };
64770
65054
  var DEPENDENCY_FIELDS = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"];
64771
65055
  async function detectProjectStack(cwd, options = {}) {
@@ -68127,14 +68411,14 @@ init_fs();
68127
68411
  init_json();
68128
68412
 
68129
68413
  // src/security/detect/mcp.ts
68130
- import { createHash as createHash29 } from "crypto";
68414
+ import { createHash as createHash30 } from "crypto";
68131
68415
  function hashToolDefinition(tool) {
68132
68416
  const canonical = JSON.stringify({
68133
68417
  name: tool.name,
68134
68418
  description: tool.description ?? "",
68135
68419
  inputSchema: tool.inputSchema ?? null
68136
68420
  });
68137
- return createHash29("sha256").update(canonical).digest("hex");
68421
+ return createHash30("sha256").update(canonical).digest("hex");
68138
68422
  }
68139
68423
  var POISONING_PATTERNS = [
68140
68424
  {
@@ -69236,7 +69520,7 @@ function wrapBwrap(command, profile, launcherPath = BWRAP_PROGRAM) {
69236
69520
  }
69237
69521
 
69238
69522
  // src/harness/process/sandbox/adapter.ts
69239
- import { createHash as createHash30 } from "crypto";
69523
+ import { createHash as createHash31 } from "crypto";
69240
69524
 
69241
69525
  // src/harness/process/sandbox/wrap.ts
69242
69526
  function wrapWithSandbox(command, profile, opts) {
@@ -69266,7 +69550,7 @@ function wrapWithSandbox(command, profile, opts) {
69266
69550
  function spawnError(command, message) {
69267
69551
  return {
69268
69552
  kind: "spawn-error",
69269
- observedHash: createHash30("sha256").update(`${command.path}
69553
+ observedHash: createHash31("sha256").update(`${command.path}
69270
69554
  ${command.argv.join(" ")}`, "utf8").digest("hex"),
69271
69555
  errorMessage: message
69272
69556
  };
@@ -71455,7 +71739,7 @@ async function closeServers(states) {
71455
71739
 
71456
71740
  // src/mcp-servers/trust.ts
71457
71741
  init_config_dir();
71458
- import { createHash as createHash31 } from "crypto";
71742
+ import { createHash as createHash32 } from "crypto";
71459
71743
  import path166 from "path";
71460
71744
  function trustFile(configDir) {
71461
71745
  return path166.join(configDir ?? ensureKeryxConfigDir(), "mcp-servers-trust.json");
@@ -71470,7 +71754,7 @@ function serverFingerprint(server) {
71470
71754
  headers: sortedKeys(server.headers),
71471
71755
  bearer_token_env_var: server.bearer_token_env_var ?? null
71472
71756
  });
71473
- return createHash31("sha256").update(material).digest("hex").slice(0, 32);
71757
+ return createHash32("sha256").update(material).digest("hex").slice(0, 32);
71474
71758
  }
71475
71759
  function sortedKeys(values) {
71476
71760
  return Object.entries(values ?? {}).sort(([a], [b]) => a.localeCompare(b));
@@ -72369,7 +72653,7 @@ function resolveInteractive(explicit, stdin, stdout) {
72369
72653
  function bothStreamsAreATerminal(stdin, stdout) {
72370
72654
  return stdin.isTTY === true && stdout.isTTY === true;
72371
72655
  }
72372
- function openAuthorisationUrl(url, log, platform = process.platform, env = process.env) {
72656
+ function openAuthorisationUrl(url, log, platform = process.platform, env = process.env, open = openVerificationUrl) {
72373
72657
  const plan = browserOpenPlan(url.toString(), platform, env);
72374
72658
  if (plan === undefined) {
72375
72659
  log("No graphical session detected, so nothing was opened.");
@@ -72379,7 +72663,7 @@ function openAuthorisationUrl(url, log, platform = process.platform, env = proce
72379
72663
  `);
72380
72664
  return;
72381
72665
  }
72382
- openVerificationUrl(url.toString());
72666
+ open(url.toString(), { platform, env });
72383
72667
  }
72384
72668
  function removeCommand(args, deps) {
72385
72669
  const own = splitAtSeparator(args).own;
@@ -72779,17 +73063,17 @@ init_sac();
72779
73063
  init_workspace_service();
72780
73064
  init_store2();
72781
73065
  init_fs();
72782
- import { createHash as createHash34, randomUUID as randomUUID17 } from "crypto";
73066
+ import { createHash as createHash35, randomUUID as randomUUID17 } from "crypto";
72783
73067
  import { appendFile as appendFile7, mkdir as mkdir58, open as open2, readFile as readFile93, rename as rename7, rm as rm12, stat as stat16, writeFile as writeFile50 } from "fs/promises";
72784
73068
  import path170 from "path";
72785
73069
 
72786
73070
  // src/sac/policy-experiment.ts
72787
- import { createHash as createHash33, createHmac as createHmac2 } from "crypto";
73071
+ import { createHash as createHash34, createHmac as createHmac2 } from "crypto";
72788
73072
 
72789
73073
  // src/sac/receipt-integrity.ts
72790
73074
  init_sac();
72791
- import { createHash as createHash32 } from "crypto";
72792
- var sha2563 = (value) => createHash32("sha256").update(value, "utf8").digest("hex");
73075
+ import { createHash as createHash33 } from "crypto";
73076
+ var sha2563 = (value) => createHash33("sha256").update(value, "utf8").digest("hex");
72793
73077
  function bodyOf(receipt) {
72794
73078
  const { integrity: _integrity, ...body } = receipt;
72795
73079
  return body;
@@ -72846,7 +73130,7 @@ function stableValue(value) {
72846
73130
  return value;
72847
73131
  }
72848
73132
  var stableJson2 = (value) => JSON.stringify(stableValue(value));
72849
- var sha2564 = (value) => createHash33("sha256").update(stableJson2(value), "utf8").digest("hex");
73133
+ var sha2564 = (value) => createHash34("sha256").update(stableJson2(value), "utf8").digest("hex");
72850
73134
  var workspacePathPattern2 = /^\.\/(?!.*(?:^|\/)\.\.(?:\/|$))(?:[A-Za-z0-9._-]+\/)*[A-Za-z0-9._-]+$/;
72851
73135
  var trustedOutcomes = new WeakMap;
72852
73136
  var trustedSandboxEvidence = new WeakMap;
@@ -72866,7 +73150,7 @@ function policyCorpusDigest(manifest, rows, quarantine) {
72866
73150
  function splitFor(receiptHash, outcome, split) {
72867
73151
  if (outcome.caseClass === "adversarial")
72868
73152
  return "adversarial";
72869
- const byte = Number.parseInt(createHash33("sha256").update(`${split.seed}\x00${receiptHash}`).digest("hex").slice(0, 2), 16);
73153
+ const byte = Number.parseInt(createHash34("sha256").update(`${split.seed}\x00${receiptHash}`).digest("hex").slice(0, 2), 16);
72870
73154
  return byte / 256 * 100 < split.holdoutPercent ? "holdout" : "train";
72871
73155
  }
72872
73156
  function quarantineCounts(entries) {
@@ -72950,12 +73234,12 @@ var stableValue2 = (value) => {
72950
73234
  return value;
72951
73235
  };
72952
73236
  var stableJson3 = (value) => JSON.stringify(stableValue2(value));
72953
- var sha2565 = (value) => createHash34("sha256").update(stableJson3(value), "utf8").digest("hex");
73237
+ var sha2565 = (value) => createHash35("sha256").update(stableJson3(value), "utf8").digest("hex");
72954
73238
  var isImmutableVersion2 = (value) => typeof value === "string" && value.length >= 3 && value.length <= 256 && /^[A-Za-z0-9][A-Za-z0-9._:+-]*$/.test(value) && /\d/.test(value) && !immutableVersionPattern.test(value);
72955
73239
  var receiptHashPattern2 = /^[a-f0-9]{64}$/;
72956
73240
  var ledgerDigestChunkBytes = 64 * 1024;
72957
73241
  var isMissing = (error) => error instanceof Error && error.code === "ENOENT";
72958
- var checkpointHash = (body) => createHash34("sha256").update(JSON.stringify(body), "utf8").digest("hex");
73242
+ var checkpointHash = (body) => createHash35("sha256").update(JSON.stringify(body), "utf8").digest("hex");
72959
73243
  async function ledgerByteLength(ledger) {
72960
73244
  const value = await stat16(ledger, { bigint: true });
72961
73245
  if (value.size > BigInt(Number.MAX_SAFE_INTEGER))
@@ -72963,7 +73247,7 @@ async function ledgerByteLength(ledger) {
72963
73247
  return Number(value.size);
72964
73248
  }
72965
73249
  async function digestLedgerPrefix(handle, byteLength) {
72966
- const digest = createHash34("sha256");
73250
+ const digest = createHash35("sha256");
72967
73251
  if (byteLength === 0)
72968
73252
  return digest;
72969
73253
  const buffer = Buffer.alloc(Math.min(ledgerDigestChunkBytes, byteLength));
@@ -73087,7 +73371,7 @@ async function auditLedger(ledger, checkpointPath, verifier) {
73087
73371
  const verification = await verifier(receipts);
73088
73372
  if (!verification.ok)
73089
73373
  throw new Error(`invalid access receipt ledger: ${verification.reason} at record ${verification.firstInvalidIndex}`);
73090
- const digest = createHash34("sha256").update(bytes);
73374
+ const digest = createHash35("sha256").update(bytes);
73091
73375
  const tailOffset = receipts.length === 0 ? 0 : bytes.lastIndexOf(10, bytes.length - 2) + 1;
73092
73376
  const state = { ledgerBytes: bytes.length, recordCount: receipts.length, headHash: verification.headHash, tailOffset, digest };
73093
73377
  await writeCheckpoint(checkpointPath, { schemaVersion: "1.1", ledgerBytes: state.ledgerBytes, recordCount: state.recordCount, headHash: state.headHash, tailOffset, contentDigest: digest.copy().digest("hex") });
@@ -73103,7 +73387,7 @@ async function resolveLedgerState(ledger, checkpointPath, verifier) {
73103
73387
  throw error;
73104
73388
  if (document.present)
73105
73389
  throw new Error("invalid access receipt ledger: orphaned-checkpoint");
73106
- return { ledgerBytes: 0, recordCount: 0, headHash: "GENESIS", tailOffset: 0, digest: createHash34("sha256") };
73390
+ return { ledgerBytes: 0, recordCount: 0, headHash: "GENESIS", tailOffset: 0, digest: createHash35("sha256") };
73107
73391
  }
73108
73392
  if (document.checkpoint) {
73109
73393
  const fast = await fastCheckpointState(ledger, document.checkpoint, ledgerBytes);
@@ -73122,7 +73406,7 @@ var isConfigRecord = (value) => typeof value.enabled === "boolean" && typeof val
73122
73406
  async function readPinnedJson(input) {
73123
73407
  const ref = await resolveWorkspaceReference({ workspaceRoot: input.workspaceRoot, kind: "artifact", uri: input.uri });
73124
73408
  const raw = await readFile93(ref, "utf8");
73125
- const digest = createHash34("sha256").update(raw).digest("hex");
73409
+ const digest = createHash35("sha256").update(raw).digest("hex");
73126
73410
  if (digest !== input.digest)
73127
73411
  throw new Error("policy experiment pinned artifact digest mismatch");
73128
73412
  return { artifact: JSON.parse(raw), digest };
@@ -73552,7 +73836,7 @@ function createLocalFwkReadService(cwd, opts) {
73552
73836
  const facts = await Promise.all(manifest.resources.filter((resource) => resource.kind === "evidence").map(async (resource, index) => {
73553
73837
  try {
73554
73838
  const raw = await workspaces.readResourceForActor({ actorContext, workspaceId, resource });
73555
- const revision = createHash34("sha256").update(raw).digest("hex");
73839
+ const revision = createHash35("sha256").update(raw).digest("hex");
73556
73840
  return { id: `fact-${index}`, uri: resource.uri, revision: resource.revision ?? revision, observedAt: manifest.updatedAt, expiresAt: "9999-12-31T23:59:59Z", trust: "primary", visible: true, statement: `Evidence reference ${resource.uri}`, status: resource.revision === revision || resource.revision === undefined ? "fresh" : "stale" };
73557
73841
  } catch (error) {
73558
73842
  withhold(error);
@@ -73562,7 +73846,7 @@ function createLocalFwkReadService(cwd, opts) {
73562
73846
  const knowHow = await Promise.all(manifest.resources.filter((resource) => resource.kind === "wiki" || resource.kind === "memory" || resource.kind === "skill").map(async (resource, index) => {
73563
73847
  try {
73564
73848
  const raw = await workspaces.readResourceForActor({ actorContext, workspaceId, resource, encoding: "utf8" });
73565
- const revision = createHash34("sha256").update(raw).digest("hex");
73849
+ const revision = createHash35("sha256").update(raw).digest("hex");
73566
73850
  const accepted = /^Status:\s*(accepted|reviewed)\s*$/mi.test(raw);
73567
73851
  return { id: `knowhow-${index}`, kind: resource.kind, uri: resource.uri, revision: resource.revision ?? revision, trust: "accepted", status: resource.revision === revision || resource.revision === undefined ? "fresh" : "stale", accepted, visible: true };
73568
73852
  } catch (error) {
@@ -74381,7 +74665,7 @@ function buildToolRegistry() {
74381
74665
  init_fs();
74382
74666
  init_contained_read();
74383
74667
  import path173 from "path";
74384
- import { lstat as lstat4, readdir as readdir32, realpath as realpath7, stat as stat17 } from "fs/promises";
74668
+ import { lstat as lstat5, readdir as readdir32, realpath as realpath7, stat as stat17 } from "fs/promises";
74385
74669
  var URI_PREFIX = "metaproject://";
74386
74670
  function mimeForPath(filePath) {
74387
74671
  if (filePath.endsWith(".json") || filePath.endsWith(".jsonl")) {
@@ -74406,7 +74690,7 @@ async function walkFiles(root, ownerRoot = root, ancestors = new Set) {
74406
74690
  if (!await pathExists(root)) {
74407
74691
  return [];
74408
74692
  }
74409
- const ownerInfo = await lstat4(ownerRoot).catch(() => null);
74693
+ const ownerInfo = await lstat5(ownerRoot).catch(() => null);
74410
74694
  if (!ownerInfo || !ownerInfo.isDirectory() || ownerInfo.isSymbolicLink())
74411
74695
  return [];
74412
74696
  const ownerReal = await realpath7(ownerRoot).catch(() => null);
@@ -74942,7 +75226,7 @@ init_make_provider();
74942
75226
  init_profiles();
74943
75227
 
74944
75228
  // src/harness/run/run.ts
74945
- import { createHash as createHash38 } from "crypto";
75229
+ import { createHash as createHash39 } from "crypto";
74946
75230
 
74947
75231
  // src/harness/completion/gate.ts
74948
75232
  var SCHEMA_VERSION3 = 1;
@@ -74993,10 +75277,10 @@ function evaluateCompletion(input, deps) {
74993
75277
  }
74994
75278
 
74995
75279
  // src/harness/evidence/redaction.ts
74996
- import { createHash as createHash35 } from "crypto";
75280
+ import { createHash as createHash36 } from "crypto";
74997
75281
  var CLEAN_CATEGORY = "none";
74998
75282
  function hashContent(content) {
74999
- return createHash35("sha256").update(content, "utf8").digest("hex");
75283
+ return createHash36("sha256").update(content, "utf8").digest("hex");
75000
75284
  }
75001
75285
  function redactForPersistence(content, deps) {
75002
75286
  const scan = deps.scan(content);
@@ -75047,7 +75331,7 @@ async function metaprojectBlastRadius(port, target) {
75047
75331
  }
75048
75332
 
75049
75333
  // src/harness/session/session.ts
75050
- import { createHash as createHash36 } from "crypto";
75334
+ import { createHash as createHash37 } from "crypto";
75051
75335
  var SCHEMA_VERSION4 = 1;
75052
75336
  function canonicalStringify(value) {
75053
75337
  if (Array.isArray(value)) {
@@ -75061,7 +75345,7 @@ function canonicalStringify(value) {
75061
75345
  return JSON.stringify(value) ?? "null";
75062
75346
  }
75063
75347
  function sha2566(input) {
75064
- return createHash36("sha256").update(input, "utf8").digest("hex");
75348
+ return createHash37("sha256").update(input, "utf8").digest("hex");
75065
75349
  }
75066
75350
  function deepFreeze(value) {
75067
75351
  if (value !== null && typeof value === "object") {
@@ -75153,11 +75437,11 @@ class AppendOnlySession {
75153
75437
  }
75154
75438
 
75155
75439
  // src/harness/context/manifest.ts
75156
- import { createHash as createHash37 } from "crypto";
75440
+ import { createHash as createHash38 } from "crypto";
75157
75441
  var MAX_CONTEXT_BYTES = 2 * 1024 * 1024;
75158
75442
  var MAX_CONTEXT_TOKENS = 200000;
75159
75443
  function sha2567(input) {
75160
- return createHash37("sha256").update(input, "utf8").digest("hex");
75444
+ return createHash38("sha256").update(input, "utf8").digest("hex");
75161
75445
  }
75162
75446
  function normalizeSource2(value) {
75163
75447
  const record = typeof value === "object" && value !== null ? value : {};
@@ -75283,7 +75567,7 @@ function canonicalize2(value) {
75283
75567
  return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalize2(record[key])}`).join(",")}}`;
75284
75568
  }
75285
75569
  function sha2568(input) {
75286
- return createHash38("sha256").update(input, "utf8").digest("hex");
75570
+ return createHash39("sha256").update(input, "utf8").digest("hex");
75287
75571
  }
75288
75572
  function uniqueInOrder2(values) {
75289
75573
  const seen = new Set;
@@ -75721,7 +76005,7 @@ function replayOffline(fixture, run, deps) {
75721
76005
  }
75722
76006
 
75723
76007
  // src/harness/tool/registry.ts
75724
- import { createHash as createHash39 } from "crypto";
76008
+ import { createHash as createHash40 } from "crypto";
75725
76009
  function canonicalize3(value) {
75726
76010
  if (value === null || typeof value !== "object") {
75727
76011
  return JSON.stringify(value) ?? "null";
@@ -75735,7 +76019,7 @@ function canonicalize3(value) {
75735
76019
  return `{${entries.join(",")}}`;
75736
76020
  }
75737
76021
  function sha2569(input) {
75738
- return createHash39("sha256").update(input, "utf8").digest("hex");
76022
+ return createHash40("sha256").update(input, "utf8").digest("hex");
75739
76023
  }
75740
76024
  function definitionHash(definition) {
75741
76025
  return sha2569(canonicalize3(definition));
@@ -75886,11 +76170,11 @@ function runContainedProcess(input, deps) {
75886
76170
 
75887
76171
  // src/harness/process/real-process-adapter.ts
75888
76172
  import { spawnSync } from "child_process";
75889
- import { createHash as createHash40 } from "crypto";
76173
+ import { createHash as createHash41 } from "crypto";
75890
76174
  var DEFAULT_TIMEOUT_MS = 5000;
75891
76175
  var DEFAULT_MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
75892
76176
  function sha256Hex4(input) {
75893
- return createHash40("sha256").update(input, "utf8").digest("hex");
76177
+ return createHash41("sha256").update(input, "utf8").digest("hex");
75894
76178
  }
75895
76179
  function byteLength(chunk) {
75896
76180
  if (chunk === null || chunk === undefined)
@@ -77807,12 +78091,14 @@ function resolveShellRestrictedMasks(env, sandboxConfigDir, projectRoot) {
77807
78091
  tlsTerminate: result.resolution.tlsTerminate
77808
78092
  };
77809
78093
  }
77810
- async function resolveShellEnv() {
78094
+ async function resolveShellEnv(configDir) {
77811
78095
  await Promise.resolve().then(() => init_shell_config());
77812
- applySavedApiKeys();
78096
+ const passSaved = process.env.KERYX_SHELL_PASS_SAVED_KEYS === "1";
78097
+ const source = passSaved ? envWithSavedApiKeys(process.env, configDir) : process.env;
78098
+ const withheld = passSaved ? new Set : savedCredentialEnvKeys();
77813
78099
  const env = {};
77814
- for (const [key, value] of Object.entries(process.env)) {
77815
- if (typeof value === "string")
78100
+ for (const [key, value] of Object.entries(source)) {
78101
+ if (typeof value === "string" && !withheld.has(key))
77816
78102
  env[key] = value;
77817
78103
  }
77818
78104
  return env;
@@ -77833,7 +78119,8 @@ async function resolveShellSpawn(root, command, baseEnv, detectOpts) {
77833
78119
  }
77834
78120
  let netClose = async () => {};
77835
78121
  if (profile.network === "restricted") {
77836
- const masks = resolveShellRestrictedMasks(env, undefined, root);
78122
+ await Promise.resolve().then(() => init_shell_config());
78123
+ const masks = resolveShellRestrictedMasks(envWithSavedApiKeys(env), undefined, root);
77837
78124
  if (!masks.ok)
77838
78125
  return { ok: false, error: `shell_exec: ${masks.reason}` };
77839
78126
  const net = await setupNetworkRun(profile, {
@@ -79391,7 +79678,7 @@ init_command_risk();
79391
79678
  init_shell_syntax();
79392
79679
  import { existsSync as existsSync30 } from "fs";
79393
79680
  import path181 from "path";
79394
- import { createHash as createHash41 } from "crypto";
79681
+ import { createHash as createHash42 } from "crypto";
79395
79682
  var PREFIX_BANNED = new Set([
79396
79683
  "sh",
79397
79684
  "bash",
@@ -79711,7 +79998,7 @@ function shellPermissionsFingerprint(dir) {
79711
79998
  if (!read.ok) {
79712
79999
  return "";
79713
80000
  }
79714
- return createHash41("sha256").update(read.text, "utf8").digest("hex");
80001
+ return createHash42("sha256").update(read.text, "utf8").digest("hex");
79715
80002
  } catch {
79716
80003
  return "";
79717
80004
  }
@@ -80203,7 +80490,7 @@ function createDefaultSearchProviderController(configDir) {
80203
80490
  return new SearchProviderController(registry, configDir);
80204
80491
  }
80205
80492
  // src/harness/tool/builtin/spawn-subagent-tool.ts
80206
- import { createHash as createHash42, randomUUID as randomUUID24 } from "crypto";
80493
+ import { createHash as createHash43, randomUUID as randomUUID24 } from "crypto";
80207
80494
  import { mkdtemp, rm as rm13 } from "fs/promises";
80208
80495
  import { tmpdir as tmpdir4 } from "os";
80209
80496
  import path183 from "path";
@@ -80253,7 +80540,7 @@ function readExternalRuntimeRequest(input) {
80253
80540
  return raw.kind === "external" ? raw : undefined;
80254
80541
  }
80255
80542
  function sha25610(text) {
80256
- return createHash42("sha256").update(text).digest("hex");
80543
+ return createHash43("sha256").update(text).digest("hex");
80257
80544
  }
80258
80545
  var parentShellPolicy = shellParentProfile;
80259
80546
  var childReadOnlyPolicy = shellChildReadOnlyProfile;
@@ -92740,6 +93027,9 @@ function promptApiKeyStep(otui, r, opts) {
92740
93027
  const box = overlayBox(otui, r, "key-picker");
92741
93028
  r.root.add(box);
92742
93029
  box.add(new otui.TextRenderable(r, { id: "kp-title", content: otui.t`${otui.bold(`Paste your ${opts.label} API key`)} ${otui.dim("(Enter \xB7 Esc to go back)")}` }));
93030
+ if (opts.notice !== undefined) {
93031
+ box.add(new otui.TextRenderable(r, { id: "kp-notice", content: otui.t`${otui.red("\u2717")} ${opts.notice}`, marginTop: 1 }));
93032
+ }
92743
93033
  box.add(new otui.TextRenderable(r, {
92744
93034
  id: "kp-note",
92745
93035
  content: otui.t`${otui.dim(`Set as ${opts.envKey} \xB7 saved to your keryx config dir (owner-only, 0600)`)}`,
@@ -92859,9 +93149,14 @@ function runDeviceLoginInTui(otui, r, provider) {
92859
93149
  });
92860
93150
  });
92861
93151
  }
92862
- async function modelsForPicker(prov) {
92863
- const result = await resolveModelsForPicker(globalThis.fetch, prov, process.env);
92864
- return result.models;
93152
+ async function modelsForPicker(prov, deps = {}) {
93153
+ return await resolveModelsForPicker(deps.fetch ?? globalThis.fetch, prov, deps.env ?? process.env);
93154
+ }
93155
+ function modelPickerNotice(label, result) {
93156
+ if (result.models.length > 0 || result.failure === undefined) {
93157
+ return;
93158
+ }
93159
+ return modelsFailureLine(label, result.failure);
92865
93160
  }
92866
93161
  function pickProviderStep(otui, r, detected) {
92867
93162
  return new Promise((resolve) => {
@@ -92948,44 +93243,66 @@ function selectProviderModelInTui(otui, r, detected, options = {}) {
92948
93243
  continue;
92949
93244
  }
92950
93245
  if (!options.onlyConnected && selectedBaseUrl !== undefined)
92951
- saveProviderBaseUrl(prov.name, selectedBaseUrl);
93246
+ saveProviderBaseUrl(prov.name, selectedBaseUrl, options.configDir);
92952
93247
  const selectedProvider = selectedBaseUrl === undefined ? prov : { ...prov, baseUrl: selectedBaseUrl };
92953
93248
  const envKey = prov.envKey;
92954
- if (!options.onlyConnected && envKey !== undefined) {
92955
- const existingKey = process.env[envKey];
92956
- const hasOauth = oauthAccessToken(prov.name) !== undefined;
92957
- if ((existingKey === undefined || existingKey.length === 0) && !hasOauth) {
92958
- const offered = [];
92959
- if (catalogAllows(prov.name, "device-code"))
92960
- offered.push("device-code");
92961
- if (catalogMethods(prov.name).includes("api-key"))
92962
- offered.push("api-key");
92963
- let method = offered.length === 1 ? offered[0] : undefined;
92964
- if (offered.length > 1) {
92965
- method = await pickAuthMethodStep(otui, r, prov.label ?? prov.name, offered);
92966
- if (method === undefined) {
92967
- continue;
92968
- }
93249
+ const label = prov.label ?? prov.name;
93250
+ const collectCredential = async (notice) => {
93251
+ if (options.onlyConnected || envKey === undefined) {
93252
+ return "ok";
93253
+ }
93254
+ if (notice === undefined) {
93255
+ const existingKey = (options.env ?? process.env)[envKey];
93256
+ const hasOauth = oauthAccessToken(prov.name, options.configDir) !== undefined;
93257
+ if (existingKey !== undefined && existingKey.length > 0 || hasOauth) {
93258
+ return "ok";
92969
93259
  }
92970
- if (method === "device-code") {
92971
- const ok = await runDeviceLoginInTui(otui, r, prov.name);
92972
- if (!ok) {
92973
- continue;
92974
- }
92975
- } else {
92976
- const kr = await promptApiKeyStep(otui, r, { label: prov.label ?? prov.name, envKey });
92977
- if (kr.kind === "back") {
92978
- continue;
92979
- }
92980
- if (kr.kind === "key") {
92981
- process.env[envKey] = kr.value;
92982
- saveApiKey(envKey, kr.value);
92983
- }
93260
+ }
93261
+ const offered = [];
93262
+ if (catalogAllows(prov.name, "device-code"))
93263
+ offered.push("device-code");
93264
+ if (catalogMethods(prov.name).includes("api-key"))
93265
+ offered.push("api-key");
93266
+ let method = offered.length === 1 ? offered[0] : undefined;
93267
+ if (offered.length > 1) {
93268
+ method = await pickAuthMethodStep(otui, r, label, offered);
93269
+ if (method === undefined) {
93270
+ return "back";
92984
93271
  }
92985
93272
  }
93273
+ if (method === "device-code") {
93274
+ return await runDeviceLoginInTui(otui, r, prov.name) ? "ok" : "back";
93275
+ }
93276
+ const kr = await promptApiKeyStep(otui, r, {
93277
+ label,
93278
+ envKey,
93279
+ ...notice === undefined ? {} : { notice }
93280
+ });
93281
+ if (kr.kind === "back") {
93282
+ return "back";
93283
+ }
93284
+ if (kr.kind === "key") {
93285
+ process.env[envKey] = kr.value;
93286
+ noteSavedCredentialEnv([envKey]);
93287
+ saveApiKey(envKey, kr.value, options.configDir);
93288
+ }
93289
+ return "ok";
93290
+ };
93291
+ if (await collectCredential() === "back") {
93292
+ continue;
93293
+ }
93294
+ const modelDeps = {
93295
+ ...options.fetch === undefined ? {} : { fetch: options.fetch },
93296
+ ...options.env === undefined ? {} : { env: options.env }
93297
+ };
93298
+ let models = await modelsForPicker(selectedProvider, modelDeps);
93299
+ if (models.failure?.kind === "rejected") {
93300
+ if (await collectCredential(modelsFailureLine(label, models.failure)) === "back") {
93301
+ continue;
93302
+ }
93303
+ models = await modelsForPicker(selectedProvider, modelDeps);
92986
93304
  }
92987
- const models = await modelsForPicker(selectedProvider);
92988
- const model = await pickModelInTui(otui, r, models);
93305
+ const model = await pickModelInTui(otui, r, models.models, modelPickerNotice(label, models));
92989
93306
  if (model === undefined) {
92990
93307
  continue;
92991
93308
  }
@@ -92999,13 +93316,16 @@ function adaptiveSelectHeight(count, available, per = 1) {
92999
93316
  const min = Math.max(1, Math.floor(available / 4));
93000
93317
  return Math.min(available, Math.max(min, count * per));
93001
93318
  }
93002
- function pickModelInTui(otui, r, models) {
93319
+ function pickModelInTui(otui, r, models, notice) {
93003
93320
  return new Promise((resolve) => {
93004
93321
  const all = models;
93005
- const NO_MODELS = "(no models found)";
93322
+ const NO_MODELS = notice ?? "(no models found)";
93006
93323
  const box = overlayBox(otui, r, "model-picker");
93007
93324
  r.root.add(box);
93008
93325
  box.add(new otui.TextRenderable(r, { id: "mp-title", content: otui.t`${otui.bold("Select a model")}` }));
93326
+ if (all.length === 0 && notice !== undefined) {
93327
+ box.add(new otui.TextRenderable(r, { id: "mp-notice", content: otui.t`${otui.red("\u2717")} ${notice}` }));
93328
+ }
93009
93329
  const filterLine = new otui.TextRenderable(r, { id: "mp-filter", content: otui.t`${otui.dim("type to filter \xB7 \u2191/\u2193 Enter \xB7 Esc to go back")}` });
93010
93330
  box.add(filterLine);
93011
93331
  const NO_MATCH = "(no match)";
@@ -95101,8 +95421,9 @@ ${formatThemeList(getThemeId())}`);
95101
95421
  (async () => {
95102
95422
  const detected = opts.redetect !== undefined ? await opts.redetect() : opts.detected;
95103
95423
  const prov = detected.find((d) => d.name === currentSel.provider);
95104
- const models = prov !== undefined ? await modelsForPicker(prov) : [];
95105
- const chosen = await chrome.withOverlay(() => pickModelInTui(otui, r, models));
95424
+ const models = prov !== undefined ? await modelsForPicker(prov) : undefined;
95425
+ const notice = prov === undefined || models === undefined ? undefined : modelPickerNotice(prov.label ?? prov.name, models);
95426
+ const chosen = await chrome.withOverlay(() => pickModelInTui(otui, r, models?.models ?? [], notice));
95106
95427
  if (chosen !== undefined) {
95107
95428
  await switchTo(currentSel.baseUrl === undefined ? { provider: currentSel.provider, model: chosen } : { provider: currentSel.provider, model: chosen, baseUrl: currentSel.baseUrl });
95108
95429
  try {
@@ -95777,7 +96098,8 @@ async function launchTuiChatShell(opts) {
95777
96098
  return await selectProviderModelInTui(otui, r, detected);
95778
96099
  }
95779
96100
  const prov = detected.find((d) => d.name === only);
95780
- const chosen = await pickModelInTui(otui, r, prov !== undefined ? await modelsForPicker(prov) : []);
96101
+ const models = prov === undefined ? undefined : await modelsForPicker(prov);
96102
+ const chosen = await pickModelInTui(otui, r, models?.models ?? [], prov === undefined || models === undefined ? undefined : modelPickerNotice(prov.label ?? prov.name, models));
95781
96103
  if (chosen === undefined) {
95782
96104
  return;
95783
96105
  }
@@ -96167,17 +96489,19 @@ function oauthCredentialsFor(name) {
96167
96489
  }
96168
96490
  function realSelectProviderModel(baseUrl) {
96169
96491
  return async (io, opts) => {
96492
+ const env = envWithSavedApiKeys(process.env);
96170
96493
  const detected = await detectProviders({
96171
96494
  fetch: globalThis.fetch,
96172
- env: process.env,
96495
+ env,
96173
96496
  platform: process.platform,
96174
96497
  ...baseUrl !== undefined ? { baseUrl } : {}
96175
96498
  });
96176
96499
  const filtered = opts?.onlyProvider !== undefined ? detected.filter((d) => d.name === opts.onlyProvider) : detected;
96177
96500
  const list = filtered.length > 0 ? filtered : detected;
96178
- return pickProviderModel(io, list, { fetch: globalThis.fetch, env: process.env });
96501
+ return pickProviderModel(io, list, { fetch: globalThis.fetch, env });
96179
96502
  };
96180
96503
  }
96504
+ var GRANT_REFRESH_TIMEOUT_MS = 5000;
96181
96505
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
96182
96506
  var PROMPT_MARK = "\u276F ";
96183
96507
  var GUTTER = " ";
@@ -97051,14 +97375,6 @@ ${GUTTER}${turnSeparator()}
97051
97375
  }
97052
97376
  async function resolveTuiStartup(opts) {
97053
97377
  const savedCfg = loadShellConfig(opts.configDir);
97054
- await Promise.resolve().then(() => init_login());
97055
- const oauthFetch = (input, init) => globalThis.fetch(input, init);
97056
- await refreshProviderGrant("grok", { fetch: oauthFetch }, opts.configDir).catch(() => {
97057
- return;
97058
- });
97059
- await refreshProviderGrant("github-copilot", { fetch: oauthFetch }, opts.configDir).catch(() => {
97060
- return;
97061
- });
97062
97378
  const appliedKeys = applySavedApiKeys(opts.configDir);
97063
97379
  const { providerArg, modelArg, baseUrl } = opts;
97064
97380
  if (providerArg !== undefined && modelArg !== undefined) {
@@ -97237,6 +97553,15 @@ Example: keryx shell --provider ollama --model llama3.1:latest`);
97237
97553
  const modelArg = flags.modelArg;
97238
97554
  let baseUrl = flags.baseUrl;
97239
97555
  let modeFlag = flags.modeFlag;
97556
+ {
97557
+ await Promise.resolve().then(() => init_login());
97558
+ const oauthFetch = (input, init) => globalThis.fetch(input, init);
97559
+ const http = { fetch: oauthFetch, signal: AbortSignal.timeout(GRANT_REFRESH_TIMEOUT_MS) };
97560
+ const warnings = await refreshSavedGrants(http, runtime.cacheDir, providerArg === undefined ? undefined : [providerArg]);
97561
+ for (const warning of warnings)
97562
+ process.stderr.write(`keryx: ${warning}
97563
+ `);
97564
+ }
97240
97565
  const surface = chooseShellSurface(flags, runtime.isTty ?? process.stdout.isTTY === true);
97241
97566
  if (surface !== "readline") {
97242
97567
  const cwd = process.cwd();
@@ -97862,7 +98187,7 @@ init_serve_config();
97862
98187
 
97863
98188
  // src/lib/serve-credential.ts
97864
98189
  init_config_dir();
97865
- import { createHash as createHash43, randomBytes as randomBytes5, randomUUID as randomUUID30 } from "crypto";
98190
+ import { createHash as createHash44, randomBytes as randomBytes5, randomUUID as randomUUID30 } from "crypto";
97866
98191
  import {
97867
98192
  chmodSync as chmodSync4,
97868
98193
  closeSync as closeSync3,
@@ -97889,10 +98214,10 @@ function constantTimeEqual(a, b) {
97889
98214
  return difference === 0;
97890
98215
  }
97891
98216
  function hashToken(salt, token) {
97892
- return createHash43("sha256").update(Buffer.from(salt, "hex")).update(Buffer.from(token, "utf8")).digest("hex");
98217
+ return createHash44("sha256").update(Buffer.from(salt, "hex")).update(Buffer.from(token, "utf8")).digest("hex");
97893
98218
  }
97894
98219
  function credentialFingerprint(record) {
97895
- return createHash43("sha256").update(`keryx-serve-fingerprint:${record.hash}`).digest("hex").slice(0, 8);
98220
+ return createHash44("sha256").update(`keryx-serve-fingerprint:${record.hash}`).digest("hex").slice(0, 8);
97896
98221
  }
97897
98222
  function isValidRecord(value) {
97898
98223
  if (typeof value !== "object" || value === null) {
@@ -98108,7 +98433,7 @@ class AuthFailureThrottle {
98108
98433
 
98109
98434
  // src/lib/serve-turn-store.ts
98110
98435
  init_config_dir();
98111
- import { createHash as createHash44 } from "crypto";
98436
+ import { createHash as createHash45 } from "crypto";
98112
98437
  import { existsSync as existsSync34, readdirSync as readdirSync3, rmSync as rmSync2 } from "fs";
98113
98438
  import path193 from "path";
98114
98439
  var MAX_TURN_EVENTS = 1e4;
@@ -98120,11 +98445,11 @@ function turnDir(turnId, dir) {
98120
98445
  }
98121
98446
  function keyPath(project, idempotencyKey, dir) {
98122
98447
  const projectBytes = Buffer.byteLength(project, "utf8");
98123
- const digest = createHash44("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
98448
+ const digest = createHash45("sha256").update(`${projectBytes}:${project}\x00${idempotencyKey}`, "utf8").digest("hex");
98124
98449
  return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
98125
98450
  }
98126
98451
  function legacyKeyPath(idempotencyKey, dir) {
98127
- const digest = createHash44("sha256").update(idempotencyKey, "utf8").digest("hex");
98452
+ const digest = createHash45("sha256").update(idempotencyKey, "utf8").digest("hex");
98128
98453
  return path193.join(turnsRoot(dir), "keys", `${digest}.json`);
98129
98454
  }
98130
98455
  function adoptLegacyClaim(project, idempotencyKey, dir) {
@@ -99480,6 +99805,12 @@ async function updateCommand(args = []) {
99480
99805
  if (summary.modules.sac) {
99481
99806
  statusLine("sac", true, "shared agent context: cross-session workspace propose/review (opt-in)");
99482
99807
  }
99808
+ if (summary.gdskillsWarnings.length > 0) {
99809
+ heading("Warnings");
99810
+ for (const warning of summary.gdskillsWarnings) {
99811
+ note(warning);
99812
+ }
99813
+ }
99483
99814
  const steps = [];
99484
99815
  if (options.hooks) {
99485
99816
  await runPostUpdateHooks(projectRoot);
@@ -99637,8 +99968,10 @@ async function refreshServiceFiles(projectRoot, options) {
99637
99968
  await writeTextIfMissing4(path195.join(metaprojectRoot, "modules", "sac.md"), renderSacManifest());
99638
99969
  await writeTextIfChanged4(path195.join(metaprojectRoot, "skills", "sac", "SKILL.md"), renderSacSkillReadme());
99639
99970
  }
99971
+ let gdskillsWarnings = [];
99640
99972
  if (enableGdskills) {
99641
- await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
99973
+ const gdskillsInstallResult = await installGdskills(metaprojectRoot, gdskillsProfile, { createDataDirs: false });
99974
+ gdskillsWarnings = gdskillsInstallResult.warnings;
99642
99975
  if (manifest.modules?.gdskills?.hooks?.gitPostCommit) {
99643
99976
  await installManagedHook2(projectRoot, "post-commit", "gdskills-post-commit", renderGdskillsPostCommitHook());
99644
99977
  }
@@ -99738,6 +100071,7 @@ async function refreshServiceFiles(projectRoot, options) {
99738
100071
  sac: enableSac
99739
100072
  },
99740
100073
  gdskillsProfile,
100074
+ gdskillsWarnings,
99741
100075
  backfilledTasks: backfillTasks,
99742
100076
  recoveredManifest
99743
100077
  };