kennel 0.2.32 → 0.2.33
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.
- package/dist/cli/dispatch.js +5 -0
- package/dist/cli/usage.js +5 -0
- package/dist/core/runtime.js +458 -166
- package/dist/llm/command.js +10 -3
- package/dist/llm/worker.js +297 -32
- package/dist/notes/command.d.ts +1 -0
- package/dist/notes/command.js +556 -0
- package/dist/package/preview.js +481 -188
- package/dist/site/server-plugins.js +51 -10
- package/package.json +3 -2
- package/schemas/config.schema.json +11 -0
- package/web/assets/{abnfDiagram-VRR7QNED-DPZyna8O.js → abnfDiagram-VRR7QNED-CpP1VHKI.js} +1 -1
- package/web/assets/{arc-BAcU9y_V.js → arc-kL4AxfvR.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-D5yd7Cag.js → architectureDiagram-ZJ3FMSHR-BWaRktRJ.js} +1 -1
- package/web/assets/{ask-tray-PMT6VVXN-C4dJH21Q.js → ask-tray-PMT6VVXN-FqPDpwtw.js} +3 -3
- package/web/assets/{blockDiagram-677ZJIJ3-C7LSGyxV.js → blockDiagram-677ZJIJ3-B07uVoPU.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-BW2KfoQr.js → c4Diagram-LMCZKHZV-uvoaQC09.js} +1 -1
- package/web/assets/channel-Djmf1Ik1.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-4Og6VAFG.js → chunk-2Q5K7J3B-BH3SaHqk.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-BYOowQl5.js → chunk-32BRIVSS-SPD8Eraw.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-C1m-lq64.js → chunk-5VM5RSS4-DNeOgOuZ.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-B2hriRpJ.js → chunk-EX3LRPZG-SJQ-vKGz.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-C6RsnzVA.js → chunk-JWPE2WC7-Dy_9wZ2Y.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-yKbMmUWf.js → chunk-MOJQB5TN-CuIx80Vn.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-BOokm3Qc.js → chunk-RYQCIY6F-DDG61RKR.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-CKW-H5An.js → chunk-V7JOEXUC-7OMVhaoH.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-BU5pFrup.js → chunk-VR4S4FIN-Y-HNaSmV.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-DYtdSJHU.js → chunk-XXDRQBXY-CtG0MPgC.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-hlDS9RWf.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-hlDS9RWf.js +1 -0
- package/web/assets/{cose-bilkent-JH36ORCC-BZeXtC6E.js → cose-bilkent-JH36ORCC-BZaPMYcl.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-BSDVyzMI.js → cynefin-VYW2F7L2-DxAlRHA1.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-CReoDBYo.js → cynefinDiagram-TSTJHNR4-gjgzmagS.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-CBLLzMxd.js → dagre-VKFMJZFB-DvLPeYuw.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-BcGWiZXZ.js → diagram-FQU43EPY-443LFnzG.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-BXUYZA9N.js → diagram-G47NLZAW-CqfUewot.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-BrvrZs7N.js → diagram-NH7WQ7WH-D-FElUGW.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-7lnVcjXL.js → diagram-OA4YK3LP-DbidUAp5.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-s58_bMFf.js → diagram-WEI45ONY-CPLeckCZ.js} +1 -1
- package/web/assets/{ebnfDiagram-CCIWWBDH-BemWecoD.js → ebnfDiagram-CCIWWBDH-DR0rqZzN.js} +1 -1
- package/web/assets/{entity-search-page-G6QEQNXT--sKP5dP3.js → entity-search-page-G6QEQNXT-CnG8d-at.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-ClVJddch.js → erDiagram-Q63AITRT-DMXv_8Tj.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-xzUJu-i_.js → flowDiagram-23GEKE2U-DnbQjZs0.js} +1 -1
- package/web/assets/{ganttDiagram-NO4QXBWP-CXQmtF5g.js → ganttDiagram-NO4QXBWP-DBKIrjj8.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-DCdljcLl.js → gitGraphDiagram-IHSO6WYX-BqqxKWT_.js} +1 -1
- package/web/assets/{index-CtTx1o2d.js → index-Bd3ghcW-.js} +5 -5
- package/web/assets/{index-vjrhIAJn.css → index-Dt8OzQCe.css} +1 -1
- package/web/assets/{infoDiagram-FWYZ7A6U-B11SJYSK.js → infoDiagram-FWYZ7A6U-Ba6Fs17y.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-B2CCGlAm.js → ishikawaDiagram-FXEZZL3T-D3BOtFjy.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-CBPETzOk.js → journeyDiagram-5HDEW3XC-MbzFPQm8.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-unhQ3RPE.js → kanban-definition-HUTT4EX6-CurBmwu1.js} +1 -1
- package/web/assets/{linear-DuP_pG51.js → linear-CQIr4JjI.js} +1 -1
- package/web/assets/{linked-session-header-M7WY6IGF-DwlNCJlj.js → linked-session-header-M7WY6IGF-BBgN8zTq.js} +1 -1
- package/web/assets/{mermaid.core-BlYNzkpn.js → mermaid.core-bE6Vl3bZ.js} +4 -4
- package/web/assets/{mindmap-definition-LN4V7U3C-CtKHN6O1.js → mindmap-definition-LN4V7U3C-C1a46OGU.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CRyz6XWY.js → pegDiagram-2B236MQR-DZxMOxek.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-BXfkPm3Q.js → pieDiagram-ENE6RG2P-BN0XOKLi.js} +1 -1
- package/web/assets/{plugin-host-modules-5NQgs5Cq.js → plugin-host-modules-CaRD5_5q.js} +1 -1
- package/web/assets/{project-search-dialog-HJFGW4DG-DPly4OiB.js → project-search-dialog-HJFGW4DG-h_dHeh7g.js} +1 -1
- package/web/assets/{project-search-page-NKQEBGPF-Cc6HV7BN.js → project-search-page-NKQEBGPF-2AzjjLEC.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-Drb_qWex.js → quadrantDiagram-ABIIQ3AL-C6IegTP-.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-DCKk2Pix.js → railroadDiagram-RFXS5EU6-bsjyAl-q.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-6WwZwcpD.js → requirementDiagram-TGXJPOKE-Cl8EZWy7.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-DGGk3WCE.js → sankeyDiagram-HTMAVEWB-DhK4rRux.js} +1 -1
- package/web/assets/{selection-popup-532EHNCZ-yPhTrk7L.js → selection-popup-532EHNCZ-E2iQ2gQQ.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-a4tGi3DH.js → sequenceDiagram-DBY2YBRQ-DQTaPSD6.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-CHehBmy9.js → sizeCapture-X5ZJPWSS-k77bZ1zg.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-Bn9GSWgV.js → stateDiagram-2N3HPSRC-Cv_5WuZJ.js} +1 -1
- package/web/assets/{stateDiagram-v2-6OUMAXLB-DI7fdBd7.js → stateDiagram-v2-6OUMAXLB-DJ1Lrskh.js} +1 -1
- package/web/assets/{swimlanes-5IMT3BWC-BX0WRzfR.js → swimlanes-5IMT3BWC-gBtAjG3z.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-DF1K9voG.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-9Ni2kIzq.js → timeline-definition-FHXFAJF6-CFjnM1Qq.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-rfBSNh3U.js → vennDiagram-L72KCM5P-CeMWrThY.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-CzziImCC.js → wardleyDiagram-EHGQE667-R-ZaAOAI.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-DTLI4h5R.js → xychartDiagram-FW5EYKEG-BFXoMXiN.js} +1 -1
- package/web/index.html +2 -2
- package/web/assets/channel-um92dqpm.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-h7sFEJBg.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-h7sFEJBg.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-OGHC9nqi.js +0 -8
package/dist/llm/command.js
CHANGED
|
@@ -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
|
|
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
|
|
21
|
-
modes can still include
|
|
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
|
|
package/dist/llm/worker.js
CHANGED
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
readSync as readSync2,
|
|
20
20
|
statSync as statSync3
|
|
21
21
|
} from "node:fs";
|
|
22
|
-
import { dirname as
|
|
23
|
-
import
|
|
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 =
|
|
32
|
-
if (statSync3(
|
|
31
|
+
for (let directory = folder;; directory = dirname4(directory)) {
|
|
32
|
+
if (statSync3(join4(directory, ".git"), { throwIfNoEntry: false })) {
|
|
33
33
|
return directory;
|
|
34
34
|
}
|
|
35
|
-
if (
|
|
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 =
|
|
42
|
-
const config = statSync3(
|
|
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 ||
|
|
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
|
|
54
|
+
return local4.readRaggleProjectConfig(directory);
|
|
55
55
|
} catch (error) {
|
|
56
|
-
throw new Error(`Invalid Raggle config ${
|
|
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(
|
|
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 ?
|
|
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 ?
|
|
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 =
|
|
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 ||
|
|
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
|
|
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 =
|
|
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 } =
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 = [...
|
|
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>;
|