kennel 0.2.32 → 0.2.34

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 (81) hide show
  1. package/dist/cli/dispatch.js +5 -0
  2. package/dist/cli/usage.js +5 -0
  3. package/dist/core/runtime.js +458 -166
  4. package/dist/llm/command.js +10 -3
  5. package/dist/llm/worker.js +297 -32
  6. package/dist/notes/command.d.ts +1 -0
  7. package/dist/notes/command.js +556 -0
  8. package/dist/package/preview.js +481 -188
  9. package/dist/site/server-plugins.js +51 -10
  10. package/package.json +6 -3
  11. package/schemas/config.schema.json +11 -0
  12. package/scripts/verify-config.mjs +59 -0
  13. package/web/assets/{abnfDiagram-VRR7QNED-DPZyna8O.js → abnfDiagram-VRR7QNED-CpP1VHKI.js} +1 -1
  14. package/web/assets/{arc-BAcU9y_V.js → arc-kL4AxfvR.js} +1 -1
  15. package/web/assets/{architectureDiagram-ZJ3FMSHR-D5yd7Cag.js → architectureDiagram-ZJ3FMSHR-BWaRktRJ.js} +1 -1
  16. package/web/assets/{ask-tray-PMT6VVXN-C4dJH21Q.js → ask-tray-PMT6VVXN-FqPDpwtw.js} +3 -3
  17. package/web/assets/{blockDiagram-677ZJIJ3-C7LSGyxV.js → blockDiagram-677ZJIJ3-B07uVoPU.js} +1 -1
  18. package/web/assets/{c4Diagram-LMCZKHZV-BW2KfoQr.js → c4Diagram-LMCZKHZV-uvoaQC09.js} +1 -1
  19. package/web/assets/channel-Djmf1Ik1.js +1 -0
  20. package/web/assets/{chunk-2Q5K7J3B-4Og6VAFG.js → chunk-2Q5K7J3B-BH3SaHqk.js} +1 -1
  21. package/web/assets/{chunk-32BRIVSS-BYOowQl5.js → chunk-32BRIVSS-SPD8Eraw.js} +1 -1
  22. package/web/assets/{chunk-5VM5RSS4-C1m-lq64.js → chunk-5VM5RSS4-DNeOgOuZ.js} +1 -1
  23. package/web/assets/{chunk-EX3LRPZG-B2hriRpJ.js → chunk-EX3LRPZG-SJQ-vKGz.js} +1 -1
  24. package/web/assets/{chunk-JWPE2WC7-C6RsnzVA.js → chunk-JWPE2WC7-Dy_9wZ2Y.js} +1 -1
  25. package/web/assets/{chunk-MOJQB5TN-yKbMmUWf.js → chunk-MOJQB5TN-CuIx80Vn.js} +1 -1
  26. package/web/assets/{chunk-RYQCIY6F-BOokm3Qc.js → chunk-RYQCIY6F-DDG61RKR.js} +1 -1
  27. package/web/assets/{chunk-V7JOEXUC-CKW-H5An.js → chunk-V7JOEXUC-7OMVhaoH.js} +1 -1
  28. package/web/assets/{chunk-VR4S4FIN-BU5pFrup.js → chunk-VR4S4FIN-Y-HNaSmV.js} +1 -1
  29. package/web/assets/{chunk-XXDRQBXY-DYtdSJHU.js → chunk-XXDRQBXY-CtG0MPgC.js} +1 -1
  30. package/web/assets/classDiagram-OUVF2IWQ-hlDS9RWf.js +1 -0
  31. package/web/assets/classDiagram-v2-EOCWNBFH-hlDS9RWf.js +1 -0
  32. package/web/assets/{cose-bilkent-JH36ORCC-BZeXtC6E.js → cose-bilkent-JH36ORCC-BZaPMYcl.js} +1 -1
  33. package/web/assets/{cynefin-VYW2F7L2-BSDVyzMI.js → cynefin-VYW2F7L2-DxAlRHA1.js} +1 -1
  34. package/web/assets/{cynefinDiagram-TSTJHNR4-CReoDBYo.js → cynefinDiagram-TSTJHNR4-gjgzmagS.js} +1 -1
  35. package/web/assets/{dagre-VKFMJZFB-CBLLzMxd.js → dagre-VKFMJZFB-DvLPeYuw.js} +1 -1
  36. package/web/assets/{diagram-FQU43EPY-BcGWiZXZ.js → diagram-FQU43EPY-443LFnzG.js} +1 -1
  37. package/web/assets/{diagram-G47NLZAW-BXUYZA9N.js → diagram-G47NLZAW-CqfUewot.js} +1 -1
  38. package/web/assets/{diagram-NH7WQ7WH-BrvrZs7N.js → diagram-NH7WQ7WH-D-FElUGW.js} +1 -1
  39. package/web/assets/{diagram-OA4YK3LP-7lnVcjXL.js → diagram-OA4YK3LP-DbidUAp5.js} +1 -1
  40. package/web/assets/{diagram-WEI45ONY-s58_bMFf.js → diagram-WEI45ONY-CPLeckCZ.js} +1 -1
  41. package/web/assets/{ebnfDiagram-CCIWWBDH-BemWecoD.js → ebnfDiagram-CCIWWBDH-DR0rqZzN.js} +1 -1
  42. package/web/assets/{entity-search-page-G6QEQNXT--sKP5dP3.js → entity-search-page-G6QEQNXT-CnG8d-at.js} +1 -1
  43. package/web/assets/{erDiagram-Q63AITRT-ClVJddch.js → erDiagram-Q63AITRT-DMXv_8Tj.js} +1 -1
  44. package/web/assets/{flowDiagram-23GEKE2U-xzUJu-i_.js → flowDiagram-23GEKE2U-DnbQjZs0.js} +1 -1
  45. package/web/assets/{ganttDiagram-NO4QXBWP-CXQmtF5g.js → ganttDiagram-NO4QXBWP-DBKIrjj8.js} +1 -1
  46. package/web/assets/{gitGraphDiagram-IHSO6WYX-DCdljcLl.js → gitGraphDiagram-IHSO6WYX-BqqxKWT_.js} +1 -1
  47. package/web/assets/{index-CtTx1o2d.js → index-Bd3ghcW-.js} +5 -5
  48. package/web/assets/{index-vjrhIAJn.css → index-Dt8OzQCe.css} +1 -1
  49. package/web/assets/{infoDiagram-FWYZ7A6U-B11SJYSK.js → infoDiagram-FWYZ7A6U-Ba6Fs17y.js} +1 -1
  50. package/web/assets/{ishikawaDiagram-FXEZZL3T-B2CCGlAm.js → ishikawaDiagram-FXEZZL3T-D3BOtFjy.js} +1 -1
  51. package/web/assets/{journeyDiagram-5HDEW3XC-CBPETzOk.js → journeyDiagram-5HDEW3XC-MbzFPQm8.js} +1 -1
  52. package/web/assets/{kanban-definition-HUTT4EX6-unhQ3RPE.js → kanban-definition-HUTT4EX6-CurBmwu1.js} +1 -1
  53. package/web/assets/{linear-DuP_pG51.js → linear-CQIr4JjI.js} +1 -1
  54. package/web/assets/{linked-session-header-M7WY6IGF-DwlNCJlj.js → linked-session-header-M7WY6IGF-BBgN8zTq.js} +1 -1
  55. package/web/assets/{mermaid.core-BlYNzkpn.js → mermaid.core-bE6Vl3bZ.js} +4 -4
  56. package/web/assets/{mindmap-definition-LN4V7U3C-CtKHN6O1.js → mindmap-definition-LN4V7U3C-C1a46OGU.js} +1 -1
  57. package/web/assets/{pegDiagram-2B236MQR-CRyz6XWY.js → pegDiagram-2B236MQR-DZxMOxek.js} +1 -1
  58. package/web/assets/{pieDiagram-ENE6RG2P-BXfkPm3Q.js → pieDiagram-ENE6RG2P-BN0XOKLi.js} +1 -1
  59. package/web/assets/{plugin-host-modules-5NQgs5Cq.js → plugin-host-modules-CaRD5_5q.js} +1 -1
  60. package/web/assets/{project-search-dialog-HJFGW4DG-DPly4OiB.js → project-search-dialog-HJFGW4DG-h_dHeh7g.js} +1 -1
  61. package/web/assets/{project-search-page-NKQEBGPF-Cc6HV7BN.js → project-search-page-NKQEBGPF-2AzjjLEC.js} +1 -1
  62. package/web/assets/{quadrantDiagram-ABIIQ3AL-Drb_qWex.js → quadrantDiagram-ABIIQ3AL-C6IegTP-.js} +1 -1
  63. package/web/assets/{railroadDiagram-RFXS5EU6-DCKk2Pix.js → railroadDiagram-RFXS5EU6-bsjyAl-q.js} +1 -1
  64. package/web/assets/{requirementDiagram-TGXJPOKE-6WwZwcpD.js → requirementDiagram-TGXJPOKE-Cl8EZWy7.js} +1 -1
  65. package/web/assets/{sankeyDiagram-HTMAVEWB-DGGk3WCE.js → sankeyDiagram-HTMAVEWB-DhK4rRux.js} +1 -1
  66. package/web/assets/{selection-popup-532EHNCZ-yPhTrk7L.js → selection-popup-532EHNCZ-E2iQ2gQQ.js} +1 -1
  67. package/web/assets/{sequenceDiagram-DBY2YBRQ-a4tGi3DH.js → sequenceDiagram-DBY2YBRQ-DQTaPSD6.js} +1 -1
  68. package/web/assets/{sizeCapture-X5ZJPWSS-CHehBmy9.js → sizeCapture-X5ZJPWSS-k77bZ1zg.js} +1 -1
  69. package/web/assets/{stateDiagram-2N3HPSRC-Bn9GSWgV.js → stateDiagram-2N3HPSRC-Cv_5WuZJ.js} +1 -1
  70. package/web/assets/{stateDiagram-v2-6OUMAXLB-DI7fdBd7.js → stateDiagram-v2-6OUMAXLB-DJ1Lrskh.js} +1 -1
  71. package/web/assets/{swimlanes-5IMT3BWC-BX0WRzfR.js → swimlanes-5IMT3BWC-gBtAjG3z.js} +2 -2
  72. package/web/assets/swimlanesDiagram-G3AALYLV-DF1K9voG.js +8 -0
  73. package/web/assets/{timeline-definition-FHXFAJF6-9Ni2kIzq.js → timeline-definition-FHXFAJF6-CFjnM1Qq.js} +1 -1
  74. package/web/assets/{vennDiagram-L72KCM5P-rfBSNh3U.js → vennDiagram-L72KCM5P-CeMWrThY.js} +1 -1
  75. package/web/assets/{wardleyDiagram-EHGQE667-CzziImCC.js → wardleyDiagram-EHGQE667-R-ZaAOAI.js} +1 -1
  76. package/web/assets/{xychartDiagram-FW5EYKEG-DTLI4h5R.js → xychartDiagram-FW5EYKEG-BFXoMXiN.js} +1 -1
  77. package/web/index.html +2 -2
  78. package/web/assets/channel-um92dqpm.js +0 -1
  79. package/web/assets/classDiagram-OUVF2IWQ-h7sFEJBg.js +0 -1
  80. package/web/assets/classDiagram-v2-EOCWNBFH-h7sFEJBg.js +0 -1
  81. package/web/assets/swimlanesDiagram-G3AALYLV-OGHC9nqi.js +0 -8
@@ -13,12 +13,19 @@ const LLM_USAGE = `Usage:
13
13
  Prints only applicable AGENTS.md, personal instructions, and the research
14
14
  write destination. The folder defaults to the current directory. Startup
15
15
  reads registered workspace configs without scanning notes or the project catalog.
16
+ Folders inside the configured Raggle base repository (rgl base) omit the
17
+ generated write destination and linked repository context. Elsewhere, the
18
+ destination is an explicit Raggle notes link when one exists; otherwise,
19
+ with repositoryNotes enabled in the Kennel user config, it is the repository
20
+ notes folder (kennel notes path), looked up read-only: startup registers nothing.
16
21
 
17
- full Add folder details, all links, local subpaths, and plugin sections.
22
+ full Add folder details and plugin sections; outside the base,
23
+ also add all links and local subpaths.
18
24
  --depth <count> Add links and plugin sections, limited to a positive graph depth.
19
25
  --profile <name> Use this personal instruction profile.
20
- --no-instructions Omit personal profiles and all inlined instruction files. Expanded
21
- modes can still include graph paths and plugin context.
26
+ --no-instructions Omit personal profiles, all inlined instruction files, and the
27
+ generated write destination. Expanded modes can still include
28
+ graph paths and plugin context.
22
29
  --no-plugins Omit plugins in expanded modes. Startup never loads plugins.
23
30
  --copy Copy the message to the clipboard instead of printing it.
24
31
 
@@ -19,8 +19,8 @@ import {
19
19
  readSync as readSync2,
20
20
  statSync as statSync3
21
21
  } from "node:fs";
22
- import { dirname as dirname3, join as join3, relative as relative2 } from "node:path";
23
- import local3 from "@raggle-ai/local";
22
+ import { dirname as dirname4, join as join4, relative as relative4 } from "node:path";
23
+ import local4 from "@raggle-ai/local";
24
24
  function unique(values) {
25
25
  return [...new Set(values)];
26
26
  }
@@ -28,39 +28,39 @@ function singleLine(text) {
28
28
  return text.replace(/\s+/g, " ").trim();
29
29
  }
30
30
  function gitRoot2(folder) {
31
- for (let directory = folder;; directory = dirname3(directory)) {
32
- if (statSync3(join3(directory, ".git"), { throwIfNoEntry: false })) {
31
+ for (let directory = folder;; directory = dirname4(directory)) {
32
+ if (statSync3(join4(directory, ".git"), { throwIfNoEntry: false })) {
33
33
  return directory;
34
34
  }
35
- if (dirname3(directory) === directory)
35
+ if (dirname4(directory) === directory)
36
36
  return;
37
37
  }
38
38
  }
39
39
  function projectConfigDirectory(folder) {
40
40
  const boundary = gitRoot2(folder) ?? folder;
41
- for (let directory = folder;; directory = dirname3(directory)) {
42
- const config = statSync3(join3(directory, "raggle.json"), {
41
+ for (let directory = folder;; directory = dirname4(directory)) {
42
+ const config = statSync3(join4(directory, "raggle.json"), {
43
43
  throwIfNoEntry: false
44
44
  });
45
45
  if (config?.isFile())
46
46
  return directory;
47
- if (directory === boundary || dirname3(directory) === directory) {
47
+ if (directory === boundary || dirname4(directory) === directory) {
48
48
  return boundary;
49
49
  }
50
50
  }
51
51
  }
52
52
  function readProjectConfig(directory) {
53
53
  try {
54
- return local3.readRaggleProjectConfig(directory);
54
+ return local4.readRaggleProjectConfig(directory);
55
55
  } catch (error) {
56
- throw new Error(`Invalid Raggle config ${join3(directory, "raggle.json")}: ${error.message}`);
56
+ throw new Error(`Invalid Raggle config ${join4(directory, "raggle.json")}: ${error.message}`);
57
57
  }
58
58
  }
59
59
  function readReadmePrefix(worktree) {
60
60
  for (const name of readmeNames) {
61
61
  let fd;
62
62
  try {
63
- fd = openSync2(join3(worktree, name), constants2.O_RDONLY | (constants2.O_NONBLOCK ?? 0));
63
+ fd = openSync2(join4(worktree, name), constants2.O_RDONLY | (constants2.O_NONBLOCK ?? 0));
64
64
  if (!fstatSync2(fd).isFile())
65
65
  continue;
66
66
  const buffer = Buffer.alloc(maxReadmeBytes);
@@ -93,7 +93,7 @@ function projectSummary(project) {
93
93
  }
94
94
  function repositoryLabel(project) {
95
95
  const remoteUrl = project.remoteMismatch?.actualRemoteUrl ?? project.remoteUrl;
96
- const browserUrl = remoteUrl ? local3.remoteToBrowserUrl(remoteUrl) ?? remoteUrl : undefined;
96
+ const browserUrl = remoteUrl ? local4.remoteToBrowserUrl(remoteUrl) ?? remoteUrl : undefined;
97
97
  const worktree = canonicalPath(project.worktree);
98
98
  return browserUrl ? `${worktree} (${browserUrl})` : worktree;
99
99
  }
@@ -103,7 +103,7 @@ function repositoryKeys(project) {
103
103
  project.remoteUrl,
104
104
  ...project.relatedIds ?? []
105
105
  ].flatMap((url) => {
106
- const key = url ? local3.repositoryLocationKey(url) : undefined;
106
+ const key = url ? local4.repositoryLocationKey(url) : undefined;
107
107
  return key ? [key] : [];
108
108
  });
109
109
  }
@@ -118,7 +118,7 @@ function missingLinks(config, connected) {
118
118
  return [];
119
119
  const present = new Set([...connected.linked, ...connected.linking, ...connected.local].flatMap(repositoryKeys));
120
120
  return direct.filter(({ repository }) => {
121
- const key = local3.repositoryLocationKey(repository);
121
+ const key = local4.repositoryLocationKey(repository);
122
122
  return Boolean(key) && !isOrganisationKey(key) && !present.has(key);
123
123
  });
124
124
  }
@@ -187,7 +187,7 @@ function treeLines(root, projects, relations, direction, type, reasons) {
187
187
  return lines;
188
188
  }
189
189
  function localSubpathName(folder, project) {
190
- return project.relativePath || relative2(canonicalPath(folder), canonicalPath(project.worktree));
190
+ return project.relativePath || relative4(canonicalPath(folder), canonicalPath(project.worktree));
191
191
  }
192
192
  function modeLabel(depth) {
193
193
  if (depth === "full")
@@ -196,9 +196,24 @@ function modeLabel(depth) {
196
196
  }
197
197
  async function buildCoreContext(options) {
198
198
  const { folder, depth, signal } = options;
199
+ signal.throwIfAborted();
200
+ if (options.localOnly) {
201
+ return {
202
+ blocks: [
203
+ [
204
+ "## Folder",
205
+ "",
206
+ `- Path: ${folder}`,
207
+ "- Context: current folder only"
208
+ ].join(`
209
+ `)
210
+ ],
211
+ linkedFolders: []
212
+ };
213
+ }
199
214
  const configDirectory = projectConfigDirectory(folder);
200
215
  const config = readProjectConfig(configDirectory);
201
- const connected = await local3.mergePersonalRelations(folder, await local3.listConnectedProjects(folder, {
216
+ const connected = await local4.mergePersonalRelations(folder, await local4.listConnectedProjects(folder, {
202
217
  depth
203
218
  }));
204
219
  signal.throwIfAborted();
@@ -219,7 +234,7 @@ async function buildCoreContext(options) {
219
234
  const linkedKeys = new Set(connected.linked.map((project) => canonicalPath(project.worktree)));
220
235
  const localSubpaths = connected.local.filter((project) => !linkedKeys.has(canonicalPath(project.worktree))).map((project) => localSubpathName(folder, project)).filter((name) => name && name !== ".");
221
236
  const missing = missingLinks(config, connected);
222
- const configFile = join3(configDirectory, "raggle.json");
237
+ const configFile = join4(configDirectory, "raggle.json");
223
238
  const blocks = [
224
239
  [
225
240
  "## Folder",
@@ -280,7 +295,7 @@ async function buildCoreContext(options) {
280
295
  }
281
296
  var canonicalPath, readmeNames, maxReadmeBytes, maxSummaryLength = 280;
282
297
  var init_context = __esm(() => {
283
- ({ canonicalPath } = local3);
298
+ ({ canonicalPath } = local4);
284
299
  readmeNames = ["README.md", "README.markdown", "README", "readme.md"];
285
300
  maxReadmeBytes = 16 * 1024;
286
301
  });
@@ -379,6 +394,7 @@ async function runContributors(contributors, context) {
379
394
  output = await contributor.llm({
380
395
  folder: context.folder,
381
396
  linkedFolders: [...context.linkedFolders],
397
+ ...context.managedFolders?.length ? { managedFolders: [...context.managedFolders] } : {},
382
398
  signal: context.signal
383
399
  });
384
400
  } catch (error) {
@@ -575,8 +591,9 @@ async function buildAgentContext(options) {
575
591
  const { folder, signal } = options;
576
592
  const settings = local2.readRaggleUserSettings();
577
593
  const checkout = gitRoot(folder);
594
+ const locations = options.localOnly ? {} : settings.repositoryLocations;
578
595
  let remote;
579
- if (checkout) {
596
+ if (checkout && !options.localOnly) {
580
597
  try {
581
598
  remote = await local2.gitRemoteUrl(checkout);
582
599
  } catch (error) {
@@ -592,16 +609,16 @@ async function buildAgentContext(options) {
592
609
  };
593
610
  if (remote)
594
611
  addIdentity(remote);
595
- for (const [repository, location] of Object.entries(settings.repositoryLocations)) {
612
+ for (const [repository, location] of Object.entries(locations)) {
596
613
  const preferred = local2.canonicalPath(location.preferredWorktree);
597
614
  if (exists(preferred) && checkout && gitRoot(preferred) === checkout) {
598
615
  addIdentity(repository);
599
616
  }
600
617
  }
601
- const roots = [
618
+ const roots = options.localOnly ? [folder] : [
602
619
  folder,
603
620
  ...settings.projectDirectories,
604
- ...Object.values(settings.repositoryLocations).map((entry) => entry.preferredWorktree),
621
+ ...Object.values(locations).map((entry) => entry.preferredWorktree),
605
622
  ...local2.getResolvedWorkspaces().map((entry) => entry.worktree),
606
623
  ...settings.baseRepository ? [settings.baseRepository] : []
607
624
  ];
@@ -613,7 +630,7 @@ async function buildAgentContext(options) {
613
630
  return Boolean(key && identities.has(key));
614
631
  };
615
632
  const configErrors = new Map;
616
- for (const owner of configFolders(roots)) {
633
+ for (const owner of configFolders(roots, options.localOnly ? checkout : undefined)) {
617
634
  signal.throwIfAborted();
618
635
  let config;
619
636
  try {
@@ -644,15 +661,25 @@ async function buildAgentContext(options) {
644
661
  const destination = [...notes][0] ?? [...projectNotes][0];
645
662
  const sources = new Set;
646
663
  const addChain = (source, boundary) => {
647
- for (const ancestor of configFolders([source], boundary).reverse())
664
+ const root = boundary ?? gitRoot(source);
665
+ const chain = [];
666
+ for (let current = local2.canonicalPath(source);; current = dirname2(current)) {
667
+ chain.push(current);
668
+ if (current === root || dirname2(current) === current)
669
+ break;
670
+ }
671
+ for (const ancestor of chain.reverse())
648
672
  sources.add(ancestor);
649
- sources.add(source);
650
673
  };
651
674
  const outerOrganisations = [...organisations].filter((source) => ![...organisations].some((parent) => parent !== source && inside(source, parent)));
652
675
  for (const source of outerOrganisations)
653
676
  addChain(source);
654
677
  if (destination)
655
678
  addChain(destination);
679
+ for (const source of options.instructionSources ?? []) {
680
+ if (exists(source))
681
+ addChain(source);
682
+ }
656
683
  if (checkout)
657
684
  sources.add(checkout);
658
685
  if (folder !== checkout)
@@ -679,11 +706,9 @@ async function buildAgentContext(options) {
679
706
  blocks.push(instruction.content.trim());
680
707
  }
681
708
  }
682
- if (destination && destination !== folder) {
683
- blocks.push(`Write any planning or research content you create during this session (markdown, html files etc) to ${destination}`);
684
- }
685
709
  return {
686
710
  blocks,
711
+ ...destination ? { destination } : {},
687
712
  linkedFolders: [...new Set([...notes, ...projectNotes])].filter((path) => path !== folder)
688
713
  };
689
714
  }
@@ -731,6 +756,236 @@ function renderMessage(blocks) {
731
756
  ` : "";
732
757
  }
733
758
 
759
+ // packages/llm/src/routing.ts
760
+ import { isAbsolute as isAbsolute2, relative as relative3, sep as sep3 } from "node:path";
761
+ import local3 from "@raggle-ai/local";
762
+
763
+ // packages/notes/src/store.ts
764
+ import { execFile } from "node:child_process";
765
+ import {
766
+ mkdir,
767
+ readdir,
768
+ readFile,
769
+ realpath,
770
+ rename,
771
+ rm,
772
+ stat,
773
+ writeFile
774
+ } from "node:fs/promises";
775
+ import { homedir } from "node:os";
776
+ import { basename, dirname as dirname3, join as join3, relative as relative2, resolve, sep as sep2 } from "node:path";
777
+ import { promisify } from "node:util";
778
+ var run = promisify(execFile);
779
+ var REPOSITORY_ID_KEY = "kennel.repoId";
780
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u;
781
+ var markdownExtensions = new Set([".md", ".markdown"]);
782
+ var caseInsensitiveHosts = new Set(["github.com", "gitlab.com", "bitbucket.org"]);
783
+ class RepositoryNotesError extends Error {
784
+ code;
785
+ constructor(code, message) {
786
+ super(message);
787
+ this.name = "RepositoryNotesError";
788
+ this.code = code;
789
+ }
790
+ }
791
+ function kennelConfigHome() {
792
+ const xdg = process.env.XDG_CONFIG_HOME?.trim();
793
+ return xdg ? xdg : join3(homedir(), ".config");
794
+ }
795
+ function kennelConfigFile() {
796
+ return join3(kennelConfigHome(), "kennel", "config.json");
797
+ }
798
+ function repositoryNotesRoot() {
799
+ return join3(kennelConfigHome(), "kennel", "repos");
800
+ }
801
+ function isRepositoryId(value) {
802
+ return typeof value === "string" && UUID.test(value);
803
+ }
804
+ function managedNotesDirectory(id) {
805
+ if (!isRepositoryId(id)) {
806
+ throw new RepositoryNotesError("invalid-id", `Invalid repository id: ${id}`);
807
+ }
808
+ return join3(repositoryNotesRoot(), id);
809
+ }
810
+ async function readRepositoryNotesSettings() {
811
+ const file = kennelConfigFile();
812
+ let text;
813
+ try {
814
+ text = await readFile(file, "utf8");
815
+ } catch (error) {
816
+ if (error.code === "ENOENT") {
817
+ return { enabled: false };
818
+ }
819
+ throw new RepositoryNotesError("config-invalid", `Cannot read ${file}: ${errorMessage(error)}`);
820
+ }
821
+ let value;
822
+ try {
823
+ value = JSON.parse(text);
824
+ } catch (error) {
825
+ throw new RepositoryNotesError("config-invalid", `Invalid JSON in ${file}: ${errorMessage(error)}`);
826
+ }
827
+ if (!isRecord(value)) {
828
+ throw new RepositoryNotesError("config-invalid", `${file} must hold an object`);
829
+ }
830
+ const section = value.repositoryNotes;
831
+ if (section === undefined)
832
+ return { enabled: false };
833
+ if (!isRecord(section) || section.enabled !== undefined && typeof section.enabled !== "boolean") {
834
+ throw new RepositoryNotesError("config-invalid", `${file}: repositoryNotes must be an object with a boolean "enabled" field`);
835
+ }
836
+ return { enabled: section.enabled === true };
837
+ }
838
+ async function git(args, cwd) {
839
+ const { stdout } = await run("git", args, {
840
+ cwd,
841
+ env: { ...process.env, GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" },
842
+ maxBuffer: 1024 * 1024
843
+ });
844
+ return stdout.replace(/\r?\n$/u, "");
845
+ }
846
+ function gitExitCode(error) {
847
+ const code = error.code;
848
+ return typeof code === "number" ? code : undefined;
849
+ }
850
+ async function resolveCheckout(folder) {
851
+ let start;
852
+ try {
853
+ start = await realpath(resolve(folder));
854
+ if (!(await stat(start)).isDirectory())
855
+ start = dirname3(start);
856
+ } catch {
857
+ throw new RepositoryNotesError("not-git", `Folder does not exist: ${folder}`);
858
+ }
859
+ let output;
860
+ try {
861
+ output = await git(["rev-parse", "--path-format=absolute", "--show-toplevel", "--git-common-dir"], start);
862
+ } catch (error) {
863
+ if (gitExitCode(error) === 128) {
864
+ throw new RepositoryNotesError("not-git", `Not a Git repository: ${start}. Repository notes need a Git checkout.`);
865
+ }
866
+ throw error;
867
+ }
868
+ const [top, common] = output.split(/\r?\n/u);
869
+ if (!top || !common) {
870
+ throw new RepositoryNotesError("not-git", `Not a Git work tree: ${start}`);
871
+ }
872
+ const root = await realpath(top);
873
+ const commonDir = await realpath(common);
874
+ return { root, commonDir, configFile: join3(commonDir, "config") };
875
+ }
876
+ async function readBinding(checkout) {
877
+ let value;
878
+ try {
879
+ value = await git(["config", "--file", checkout.configFile, "--get", REPOSITORY_ID_KEY], checkout.root);
880
+ } catch (error) {
881
+ if (gitExitCode(error) === 1)
882
+ return;
883
+ throw error;
884
+ }
885
+ const id = value.trim();
886
+ if (!id)
887
+ return;
888
+ if (!isRepositoryId(id)) {
889
+ throw new RepositoryNotesError("invalid-binding", `${checkout.configFile} has an invalid ${REPOSITORY_ID_KEY} value. Remove it or run "kennel notes new-identity".`);
890
+ }
891
+ return id;
892
+ }
893
+ function isRecord(value) {
894
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
895
+ }
896
+ function errorMessage(error) {
897
+ return error instanceof Error ? error.message : String(error);
898
+ }
899
+ async function lookupRepositoryNotes(folder) {
900
+ let checkout;
901
+ try {
902
+ checkout = await resolveCheckout(folder);
903
+ } catch (error) {
904
+ if (error instanceof RepositoryNotesError && error.code === "not-git") {
905
+ return { status: "not-git", folder };
906
+ }
907
+ throw error;
908
+ }
909
+ const id = await readBinding(checkout);
910
+ if (!id)
911
+ return { status: "unregistered", checkout, root: checkout.root };
912
+ return {
913
+ status: "registered",
914
+ checkout,
915
+ id,
916
+ notesDirectory: managedNotesDirectory(id),
917
+ root: checkout.root
918
+ };
919
+ }
920
+
921
+ // packages/llm/src/routing.ts
922
+ async function managedNotesState(folder) {
923
+ const base = local3.readRaggleUserSettings().baseRepository;
924
+ if (base) {
925
+ const baseFolder = local3.canonicalPath(base);
926
+ const path = relative3(baseFolder, folder);
927
+ if (path === "" || !isAbsolute2(path) && path !== ".." && !path.startsWith(`..${sep3}`)) {
928
+ const [checkout, baseCheckout] = await Promise.all([
929
+ resolveCheckout(folder),
930
+ resolveCheckout(baseFolder)
931
+ ]);
932
+ if (checkout.commonDir === baseCheckout.commonDir) {
933
+ return { status: "base" };
934
+ }
935
+ }
936
+ }
937
+ const settings = await readRepositoryNotesSettings();
938
+ if (!settings.enabled)
939
+ return { status: "disabled" };
940
+ const lookup = await lookupRepositoryNotes(folder);
941
+ if (lookup.status === "not-git")
942
+ return { status: "not-git" };
943
+ if (lookup.status === "unregistered") {
944
+ return { status: "unregistered", root: lookup.root };
945
+ }
946
+ return {
947
+ status: "registered",
948
+ notesDirectory: lookup.notesDirectory,
949
+ root: lookup.root
950
+ };
951
+ }
952
+ function shellQuote(value) {
953
+ return `"${value.replace(/[\\"$`]/gu, (character) => `\\${character}`)}"`;
954
+ }
955
+ var managedRules = [
956
+ "Keep code and requested repository documentation in the repository.",
957
+ "Stop and report the failure if the destination cannot be resolved or written.",
958
+ "Report the saved path and offer a preview link of the note with `kennel pop <file-path>` for the user to view in their browser."
959
+ ];
960
+ function routingInstruction({
961
+ explicit,
962
+ folder,
963
+ managed
964
+ }) {
965
+ if (managed.status === "base")
966
+ return;
967
+ const message = [
968
+ "Unless the task or applicable project instructions name another destination,",
969
+ "write research, plans, meeting notes, and session summaries as Markdown to",
970
+ "the following local folder:"
971
+ ].join(" ");
972
+ if (explicit) {
973
+ if (explicit === folder)
974
+ return;
975
+ return `Write any planning or research content you create during this session (markdown, html files etc) to ${explicit}`;
976
+ }
977
+ if (managed.status === "registered") {
978
+ return [`${message} ${managed.notesDirectory}.`, ...managedRules].join(" ");
979
+ }
980
+ if (managed.status === "unregistered") {
981
+ return [
982
+ `${message} the notesDirectory returned by \`kennel notes path ${shellQuote(folder)} --json\`. Run this command before the first such write.`,
983
+ ...managedRules
984
+ ].join(" ");
985
+ }
986
+ return;
987
+ }
988
+
734
989
  // packages/llm/src/index.ts
735
990
  var MAX_CONTEXT_BYTES = 1024 * 1024;
736
991
  function realDirectory(folder) {
@@ -760,12 +1015,20 @@ async function buildLlmContext(options) {
760
1015
  signal.throwIfAborted();
761
1016
  const folder = realDirectory(options.folder);
762
1017
  const settings = includeInstructions ? readInstructionSettings() : undefined;
1018
+ const managed = settings || depth !== undefined ? await managedNotesState(folder) : undefined;
763
1019
  const agents = settings || depth !== undefined ? await buildAgentContext({
764
1020
  folder,
765
1021
  instructionBytes: settings?.maxBytes,
766
- signal
1022
+ signal,
1023
+ localOnly: managed?.status === "base",
1024
+ instructionSources: managed?.status === "registered" ? [managed.notesDirectory] : []
767
1025
  }) : { blocks: [], linkedFolders: [] };
768
- let blocks = agents.blocks;
1026
+ const routing = settings && managed ? routingInstruction({
1027
+ explicit: agents.destination,
1028
+ folder,
1029
+ managed
1030
+ }) : undefined;
1031
+ let blocks = [...agents.blocks, ...routing ? [routing] : []];
769
1032
  let pluginBlock;
770
1033
  if (depth !== undefined) {
771
1034
  await Promise.resolve().then(() => init_context());
@@ -773,16 +1036,18 @@ async function buildLlmContext(options) {
773
1036
  const core = await buildCoreContext({
774
1037
  folder,
775
1038
  depth,
776
- signal
1039
+ signal,
1040
+ localOnly: managed?.status === "base"
777
1041
  });
778
1042
  const contributions = await runContributors(options.plugins ?? [], {
779
1043
  folder,
780
1044
  linkedFolders: [
781
1045
  ...new Set([...agents.linkedFolders, ...core.linkedFolders])
782
1046
  ],
1047
+ managedFolders: managed?.status === "registered" ? [managed.notesDirectory] : [],
783
1048
  signal
784
1049
  });
785
- blocks = [...agents.blocks, ...core.blocks];
1050
+ blocks = [...blocks, ...core.blocks];
786
1051
  pluginBlock = pluginContextBlock(contributions);
787
1052
  }
788
1053
  const personal = settings ? readPersonalInstructions(folder, settings, profile) : undefined;
@@ -0,0 +1 @@
1
+ export declare function notes(args: string[]): Promise<void>;