@mandipadk7/kavi 0.1.2 → 0.1.4
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/README.md +11 -65
- package/dist/config.js +8 -2
- package/dist/daemon.js +357 -0
- package/dist/doctor.js +33 -0
- package/dist/git.js +101 -2
- package/dist/main.js +171 -23
- package/dist/reviews.js +188 -0
- package/dist/router.js +69 -0
- package/dist/rpc.js +45 -0
- package/dist/session.js +27 -0
- package/dist/task-artifacts.js +28 -1
- package/dist/tui.js +381 -8
- package/package.json +2 -1
package/dist/router.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
import { CodexAppServerClient } from "./codex-app-server.js";
|
|
2
3
|
import { findClaimConflicts } from "./decision-ledger.js";
|
|
3
4
|
import { nowIso } from "./paths.js";
|
|
@@ -43,6 +44,55 @@ function normalizeClaimedPaths(paths) {
|
|
|
43
44
|
...new Set(paths.map((item)=>item.trim()).filter(Boolean))
|
|
44
45
|
].sort();
|
|
45
46
|
}
|
|
47
|
+
function normalizePathPattern(value) {
|
|
48
|
+
const trimmed = value.trim().replaceAll("\\", "/");
|
|
49
|
+
const withoutPrefix = trimmed.startsWith("./") ? trimmed.slice(2) : trimmed;
|
|
50
|
+
const normalized = path.posix.normalize(withoutPrefix);
|
|
51
|
+
return normalized === "." ? "" : normalized.replace(/^\/+/, "");
|
|
52
|
+
}
|
|
53
|
+
function globToRegex(pattern) {
|
|
54
|
+
const normalized = normalizePathPattern(pattern);
|
|
55
|
+
const escaped = normalized.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
56
|
+
const regexSource = escaped.replaceAll("**", "::double-star::").replaceAll("*", "[^/]*").replaceAll("::double-star::", ".*");
|
|
57
|
+
return new RegExp(`^${regexSource}$`);
|
|
58
|
+
}
|
|
59
|
+
function matchesPattern(filePath, pattern) {
|
|
60
|
+
const normalizedPath = normalizePathPattern(filePath);
|
|
61
|
+
const normalizedPattern = normalizePathPattern(pattern);
|
|
62
|
+
if (!normalizedPath || !normalizedPattern) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
return globToRegex(normalizedPattern).test(normalizedPath);
|
|
66
|
+
}
|
|
67
|
+
function countPathMatches(filePaths, patterns) {
|
|
68
|
+
if (filePaths.length === 0 || patterns.length === 0) {
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
return filePaths.filter((filePath)=>patterns.some((pattern)=>matchesPattern(filePath, pattern))).length;
|
|
72
|
+
}
|
|
73
|
+
function buildPathOwnershipDecision(prompt, config) {
|
|
74
|
+
const claimedPaths = extractPromptPathHints(prompt);
|
|
75
|
+
if (claimedPaths.length === 0) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
const codexMatches = countPathMatches(claimedPaths, config.routing.codexPaths);
|
|
79
|
+
const claudeMatches = countPathMatches(claimedPaths, config.routing.claudePaths);
|
|
80
|
+
if (codexMatches === 0 && claudeMatches === 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (codexMatches === claudeMatches) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
const owner = codexMatches > claudeMatches ? "codex" : "claude";
|
|
87
|
+
const matchedPatterns = owner === "codex" ? config.routing.codexPaths : config.routing.claudePaths;
|
|
88
|
+
return {
|
|
89
|
+
owner,
|
|
90
|
+
strategy: "manual",
|
|
91
|
+
confidence: 0.97,
|
|
92
|
+
reason: `Matched explicit ${owner} path ownership rules for: ${claimedPaths.join(", ")}.`,
|
|
93
|
+
claimedPaths
|
|
94
|
+
};
|
|
95
|
+
}
|
|
46
96
|
export function extractPromptPathHints(prompt) {
|
|
47
97
|
const candidates = [];
|
|
48
98
|
const quotedMatches = prompt.matchAll(/[`'"]([^`'"\n]+)[`'"]/g);
|
|
@@ -59,6 +109,10 @@ export function extractPromptPathHints(prompt) {
|
|
|
59
109
|
return normalizeClaimedPaths(candidates);
|
|
60
110
|
}
|
|
61
111
|
export function routePrompt(prompt, config) {
|
|
112
|
+
const pathDecision = buildPathOwnershipDecision(prompt, config);
|
|
113
|
+
if (pathDecision) {
|
|
114
|
+
return pathDecision.owner;
|
|
115
|
+
}
|
|
62
116
|
if (containsKeyword(prompt, config.routing.frontendKeywords)) {
|
|
63
117
|
return "claude";
|
|
64
118
|
}
|
|
@@ -92,6 +146,11 @@ function buildKeywordDecision(prompt, config) {
|
|
|
92
146
|
return null;
|
|
93
147
|
}
|
|
94
148
|
function buildRouterPrompt(prompt, session) {
|
|
149
|
+
const ownershipRules = [
|
|
150
|
+
...session.config.routing.codexPaths.map((pattern)=>`- codex: ${pattern}`),
|
|
151
|
+
...session.config.routing.claudePaths.map((pattern)=>`- claude: ${pattern}`)
|
|
152
|
+
].join("\n");
|
|
153
|
+
const promptHints = extractPromptPathHints(prompt);
|
|
95
154
|
const activeClaims = session.pathClaims.filter((claim)=>claim.status === "active").map((claim)=>`- ${claim.agent}: ${claim.paths.join(", ")}`).join("\n");
|
|
96
155
|
return [
|
|
97
156
|
"Route this task between Codex and Claude.",
|
|
@@ -103,6 +162,12 @@ function buildRouterPrompt(prompt, session) {
|
|
|
103
162
|
"Task prompt:",
|
|
104
163
|
prompt,
|
|
105
164
|
"",
|
|
165
|
+
"Explicit path ownership rules:",
|
|
166
|
+
ownershipRules || "- none",
|
|
167
|
+
"",
|
|
168
|
+
"Path hints extracted from the prompt:",
|
|
169
|
+
promptHints.length > 0 ? promptHints.map((item)=>`- ${item}`).join("\n") : "- none",
|
|
170
|
+
"",
|
|
106
171
|
"Active path claims:",
|
|
107
172
|
activeClaims || "- none"
|
|
108
173
|
].join("\n");
|
|
@@ -174,6 +239,10 @@ function applyClaimRouting(session, decision) {
|
|
|
174
239
|
};
|
|
175
240
|
}
|
|
176
241
|
export async function routeTask(prompt, session, _paths) {
|
|
242
|
+
const pathDecision = buildPathOwnershipDecision(prompt, session.config);
|
|
243
|
+
if (pathDecision) {
|
|
244
|
+
return applyClaimRouting(session, pathDecision);
|
|
245
|
+
}
|
|
177
246
|
const heuristic = buildKeywordDecision(prompt, session.config);
|
|
178
247
|
if (heuristic) {
|
|
179
248
|
return applyClaimRouting(session, heuristic);
|
package/dist/rpc.js
CHANGED
|
@@ -122,6 +122,51 @@ export async function rpcRecentEvents(paths, limit) {
|
|
|
122
122
|
limit
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
|
+
export async function rpcAddReviewNote(paths, params) {
|
|
126
|
+
await sendRpcRequest(paths, "addReviewNote", {
|
|
127
|
+
agent: params.agent,
|
|
128
|
+
taskId: params.taskId,
|
|
129
|
+
filePath: params.filePath,
|
|
130
|
+
hunkIndex: params.hunkIndex,
|
|
131
|
+
hunkHeader: params.hunkHeader,
|
|
132
|
+
disposition: params.disposition,
|
|
133
|
+
assignee: params.assignee ?? null,
|
|
134
|
+
body: params.body
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
export async function rpcUpdateReviewNote(paths, params) {
|
|
138
|
+
await sendRpcRequest(paths, "updateReviewNote", {
|
|
139
|
+
noteId: params.noteId,
|
|
140
|
+
...typeof params.body === "string" ? {
|
|
141
|
+
body: params.body
|
|
142
|
+
} : {},
|
|
143
|
+
...params.disposition ? {
|
|
144
|
+
disposition: params.disposition
|
|
145
|
+
} : {},
|
|
146
|
+
...params.assignee === undefined ? {} : {
|
|
147
|
+
assignee: params.assignee
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
export async function rpcAddReviewReply(paths, params) {
|
|
152
|
+
await sendRpcRequest(paths, "addReviewReply", {
|
|
153
|
+
noteId: params.noteId,
|
|
154
|
+
body: params.body
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
export async function rpcSetReviewNoteStatus(paths, params) {
|
|
158
|
+
await sendRpcRequest(paths, "setReviewNoteStatus", {
|
|
159
|
+
noteId: params.noteId,
|
|
160
|
+
status: params.status
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
export async function rpcEnqueueReviewFollowUp(paths, params) {
|
|
164
|
+
await sendRpcRequest(paths, "enqueueReviewFollowUp", {
|
|
165
|
+
noteId: params.noteId,
|
|
166
|
+
owner: params.owner,
|
|
167
|
+
mode: params.mode
|
|
168
|
+
});
|
|
169
|
+
}
|
|
125
170
|
export async function rpcWorktreeDiff(paths, agent, filePath) {
|
|
126
171
|
return await sendRpcRequest(paths, "worktreeDiff", {
|
|
127
172
|
agent,
|
package/dist/session.js
CHANGED
|
@@ -35,6 +35,7 @@ export async function createSessionRecord(paths, config, runtime, sessionId, bas
|
|
|
35
35
|
peerMessages: [],
|
|
36
36
|
decisions: [],
|
|
37
37
|
pathClaims: [],
|
|
38
|
+
reviewNotes: [],
|
|
38
39
|
agentStatus: {
|
|
39
40
|
codex: initialAgentStatus("codex", "codex-app-server"),
|
|
40
41
|
claude: initialAgentStatus("claude", "claude-print")
|
|
@@ -55,6 +56,32 @@ export async function loadSessionRecord(paths) {
|
|
|
55
56
|
})) : [];
|
|
56
57
|
record.decisions = Array.isArray(record.decisions) ? record.decisions : [];
|
|
57
58
|
record.pathClaims = Array.isArray(record.pathClaims) ? record.pathClaims : [];
|
|
59
|
+
record.reviewNotes = Array.isArray(record.reviewNotes) ? record.reviewNotes.map((note)=>({
|
|
60
|
+
...note,
|
|
61
|
+
body: typeof note.body === "string" ? note.body : "",
|
|
62
|
+
assignee: note.assignee === "codex" || note.assignee === "claude" || note.assignee === "operator" ? note.assignee : null,
|
|
63
|
+
taskId: typeof note.taskId === "string" ? note.taskId : null,
|
|
64
|
+
hunkIndex: typeof note.hunkIndex === "number" ? note.hunkIndex : null,
|
|
65
|
+
hunkHeader: typeof note.hunkHeader === "string" ? note.hunkHeader : null,
|
|
66
|
+
disposition: note.disposition === "approve" || note.disposition === "concern" || note.disposition === "question" || note.disposition === "accepted_risk" || note.disposition === "wont_fix" ? note.disposition : "note",
|
|
67
|
+
status: note.status === "resolved" ? "resolved" : "open",
|
|
68
|
+
comments: Array.isArray(note.comments) ? note.comments.map((comment)=>({
|
|
69
|
+
id: String(comment.id),
|
|
70
|
+
body: typeof comment.body === "string" ? comment.body : "",
|
|
71
|
+
createdAt: String(comment.createdAt),
|
|
72
|
+
updatedAt: String(comment.updatedAt)
|
|
73
|
+
})) : typeof note.body === "string" && note.body ? [
|
|
74
|
+
{
|
|
75
|
+
id: `${note.id}-root`,
|
|
76
|
+
body: note.body,
|
|
77
|
+
createdAt: typeof note.createdAt === "string" ? note.createdAt : nowIso(),
|
|
78
|
+
updatedAt: typeof note.updatedAt === "string" ? note.updatedAt : nowIso()
|
|
79
|
+
}
|
|
80
|
+
] : [],
|
|
81
|
+
resolvedAt: typeof note.resolvedAt === "string" ? note.resolvedAt : null,
|
|
82
|
+
landedAt: typeof note.landedAt === "string" ? note.landedAt : null,
|
|
83
|
+
followUpTaskIds: Array.isArray(note.followUpTaskIds) ? note.followUpTaskIds.map((item)=>String(item)) : []
|
|
84
|
+
})) : [];
|
|
58
85
|
return record;
|
|
59
86
|
}
|
|
60
87
|
export async function saveSessionRecord(paths, record) {
|
package/dist/task-artifacts.js
CHANGED
|
@@ -9,7 +9,34 @@ function normalizeArtifact(artifact) {
|
|
|
9
9
|
...artifact,
|
|
10
10
|
routeReason: typeof artifact.routeReason === "string" ? artifact.routeReason : null,
|
|
11
11
|
claimedPaths: Array.isArray(artifact.claimedPaths) ? artifact.claimedPaths.map((item)=>String(item)) : [],
|
|
12
|
-
decisionReplay: Array.isArray(artifact.decisionReplay) ? artifact.decisionReplay.map((item)=>String(item)) : []
|
|
12
|
+
decisionReplay: Array.isArray(artifact.decisionReplay) ? artifact.decisionReplay.map((item)=>String(item)) : [],
|
|
13
|
+
reviewNotes: Array.isArray(artifact.reviewNotes) ? artifact.reviewNotes.map((note)=>({
|
|
14
|
+
...note,
|
|
15
|
+
assignee: note.assignee === "codex" || note.assignee === "claude" || note.assignee === "operator" ? note.assignee : null,
|
|
16
|
+
taskId: typeof note.taskId === "string" ? note.taskId : null,
|
|
17
|
+
hunkIndex: typeof note.hunkIndex === "number" ? note.hunkIndex : null,
|
|
18
|
+
hunkHeader: typeof note.hunkHeader === "string" ? note.hunkHeader : null,
|
|
19
|
+
disposition: note.disposition === "approve" || note.disposition === "concern" || note.disposition === "question" || note.disposition === "accepted_risk" || note.disposition === "wont_fix" ? note.disposition : "note",
|
|
20
|
+
status: note.status === "resolved" ? "resolved" : "open",
|
|
21
|
+
summary: typeof note.summary === "string" ? note.summary : "",
|
|
22
|
+
body: typeof note.body === "string" ? note.body : "",
|
|
23
|
+
comments: Array.isArray(note.comments) ? note.comments.map((comment)=>({
|
|
24
|
+
id: String(comment.id),
|
|
25
|
+
body: typeof comment.body === "string" ? comment.body : "",
|
|
26
|
+
createdAt: String(comment.createdAt),
|
|
27
|
+
updatedAt: String(comment.updatedAt)
|
|
28
|
+
})) : typeof note.body === "string" && note.body ? [
|
|
29
|
+
{
|
|
30
|
+
id: `${note.id}-root`,
|
|
31
|
+
body: note.body,
|
|
32
|
+
createdAt: typeof note.createdAt === "string" ? note.createdAt : artifact.startedAt,
|
|
33
|
+
updatedAt: typeof note.updatedAt === "string" ? note.updatedAt : artifact.finishedAt
|
|
34
|
+
}
|
|
35
|
+
] : [],
|
|
36
|
+
resolvedAt: typeof note.resolvedAt === "string" ? note.resolvedAt : null,
|
|
37
|
+
landedAt: typeof note.landedAt === "string" ? note.landedAt : null,
|
|
38
|
+
followUpTaskIds: Array.isArray(note.followUpTaskIds) ? note.followUpTaskIds.map((item)=>String(item)) : []
|
|
39
|
+
})) : []
|
|
13
40
|
};
|
|
14
41
|
}
|
|
15
42
|
export async function saveTaskArtifact(paths, artifact) {
|