@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/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) {
@@ -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) {