@mandipadk7/kavi 0.1.7 → 1.0.1
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 +21 -7
- package/dist/adapters/claude.js +59 -43
- package/dist/adapters/codex.js +38 -20
- package/dist/codex-app-server.js +8 -7
- package/dist/config.js +4 -3
- package/dist/daemon.js +107 -3
- package/dist/git.js +61 -6
- package/dist/main.js +378 -71
- package/dist/paths.js +2 -0
- package/dist/recommendations.js +205 -19
- package/dist/reports.js +118 -0
- package/dist/rpc.js +25 -1
- package/dist/session.js +14 -1
- package/dist/tui.js +385 -56
- package/dist/workflow.js +450 -0
- package/package.json +1 -1
package/dist/main.js
CHANGED
|
@@ -10,12 +10,13 @@ import { KaviDaemon } from "./daemon.js";
|
|
|
10
10
|
import { addDecisionRecord, buildClaimHotspots, releasePathClaims, upsertPathClaim } from "./decision-ledger.js";
|
|
11
11
|
import { runDoctor } from "./doctor.js";
|
|
12
12
|
import { writeJson } from "./fs.js";
|
|
13
|
-
import { createGitignoreEntries, detectRepoRoot, ensureBootstrapCommit, ensureGitRepository, ensureWorktrees, findRepoRoot, findOverlappingWorktreePaths, landBranches, resolveTargetBranch } from "./git.js";
|
|
13
|
+
import { createGitignoreEntries, detectRepoRoot, ensureBootstrapCommit, ensureGitRepository, ensureWorktrees, findRepoRoot, findOverlappingWorktreePaths, getBranchCommit, landBranches, listWorktreeChangedPaths, resolveTargetBranch } from "./git.js";
|
|
14
14
|
import { loadPackageInfo } from "./package-info.js";
|
|
15
15
|
import { buildSessionId, resolveAppPaths } from "./paths.js";
|
|
16
16
|
import { isProcessAlive, runCommand, runInteractiveCommand, spawnDetachedNode } from "./process.js";
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { buildLandReport, loadLatestLandReport, saveLandReport } from "./reports.js";
|
|
18
|
+
import { pingRpc, readSnapshot, rpcDismissRecommendation, rpcEnqueueTask, rpcNotifyExternalUpdate, rpcKickoff, rpcRecentEvents, rpcResolveApproval, rpcSetFullAccessMode, rpcRestoreRecommendation, rpcShutdown, rpcTaskArtifact } from "./rpc.js";
|
|
19
|
+
import { buildOperatorRecommendations, buildRecommendationActionPlan, dismissOperatorRecommendation, restoreOperatorRecommendation } from "./recommendations.js";
|
|
19
20
|
import { findOwnershipRuleConflicts } from "./ownership.js";
|
|
20
21
|
import { filterReviewNotes, markReviewNotesLandedForTasks } from "./reviews.js";
|
|
21
22
|
import { resolveSessionRuntime } from "./runtime.js";
|
|
@@ -24,6 +25,7 @@ import { createSessionRecord, loadSessionRecord, readRecentEvents, recordEvent,
|
|
|
24
25
|
import { listTaskArtifacts, loadTaskArtifact, saveTaskArtifact } from "./task-artifacts.js";
|
|
25
26
|
import { attachTui } from "./tui.js";
|
|
26
27
|
import { buildUpdatePlan, parseRegistryVersion } from "./update.js";
|
|
28
|
+
import { buildWorkflowActivity, buildWorkflowResult, buildWorkflowSummary } from "./workflow.js";
|
|
27
29
|
const HEARTBEAT_STALE_MS = 10_000;
|
|
28
30
|
const CLAUDE_AUTO_ALLOW_TOOLS = new Set([
|
|
29
31
|
"Read",
|
|
@@ -31,6 +33,9 @@ const CLAUDE_AUTO_ALLOW_TOOLS = new Set([
|
|
|
31
33
|
"Grep",
|
|
32
34
|
"LS"
|
|
33
35
|
]);
|
|
36
|
+
function hasFlag(args, name) {
|
|
37
|
+
return args.includes(name);
|
|
38
|
+
}
|
|
34
39
|
function getFlag(args, name) {
|
|
35
40
|
const index = args.indexOf(name);
|
|
36
41
|
if (index === -1) {
|
|
@@ -58,6 +63,24 @@ function getOptionalFilter(args, name) {
|
|
|
58
63
|
}
|
|
59
64
|
return value;
|
|
60
65
|
}
|
|
66
|
+
function parseRecommendationKind(value) {
|
|
67
|
+
if (!value) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (value === "all" || value === "handoff" || value === "integration" || value === "ownership-config") {
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`Unsupported recommendation kind "${value}".`);
|
|
74
|
+
}
|
|
75
|
+
function parseRecommendationStatus(value) {
|
|
76
|
+
if (!value) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (value === "all" || value === "active" || value === "dismissed") {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
throw new Error(`Unsupported recommendation status "${value}".`);
|
|
83
|
+
}
|
|
61
84
|
async function readStdinText() {
|
|
62
85
|
if (process.stdin.isTTY) {
|
|
63
86
|
return "";
|
|
@@ -92,16 +115,21 @@ function renderUsage() {
|
|
|
92
115
|
" kavi init [--home] [--no-commit]",
|
|
93
116
|
" kavi doctor [--json]",
|
|
94
117
|
" kavi update [--check] [--dry-run] [--yes] [--tag latest|beta] [--version X.Y.Z]",
|
|
95
|
-
" kavi start [--goal \"...\"]",
|
|
96
|
-
" kavi open [--goal \"...\"]",
|
|
118
|
+
" kavi start [--goal \"...\"] [--approve-all]",
|
|
119
|
+
" kavi open [--goal \"...\"] [--approve-all]",
|
|
97
120
|
" kavi resume",
|
|
121
|
+
" kavi summary [--json]",
|
|
122
|
+
" kavi result [--json]",
|
|
98
123
|
" kavi status [--json]",
|
|
124
|
+
" kavi activity [--json] [--limit N]",
|
|
99
125
|
" kavi route [--json] [--no-ai] <prompt>",
|
|
100
126
|
" kavi routes [--json] [--limit N]",
|
|
101
127
|
" kavi paths [--json]",
|
|
102
128
|
" kavi task [--agent codex|claude|auto] <prompt>",
|
|
103
|
-
" kavi recommend [--json]",
|
|
104
|
-
" kavi recommend-apply <recommendation-id>",
|
|
129
|
+
" kavi recommend [--json] [--all] [--kind handoff|integration|ownership-config] [--status active|dismissed] [--agent codex|claude|operator]",
|
|
130
|
+
" kavi recommend-apply <recommendation-id> [--force]",
|
|
131
|
+
" kavi recommend-dismiss <recommendation-id> [--reason \"...\"]",
|
|
132
|
+
" kavi recommend-restore <recommendation-id>",
|
|
105
133
|
" kavi tasks [--json]",
|
|
106
134
|
" kavi task-output <task-id|latest> [--json]",
|
|
107
135
|
" kavi decisions [--json] [--limit N]",
|
|
@@ -335,7 +363,10 @@ async function commandUpdate(cwd, args) {
|
|
|
335
363
|
}
|
|
336
364
|
console.log(`Updated ${packageInfo.name} from ${packageInfo.version} to ${targetVersion}.`);
|
|
337
365
|
}
|
|
338
|
-
|
|
366
|
+
function renderFullAccessWarning() {
|
|
367
|
+
return "WARNING: approve-all is enabled. Claude and Codex will run with full access, and Kavi approval prompts will be bypassed for future turns.";
|
|
368
|
+
}
|
|
369
|
+
async function startOrAttachSession(cwd, goal, enableFullAccessMode) {
|
|
339
370
|
const prepared = await prepareProjectContext(cwd, {
|
|
340
371
|
createRepository: true,
|
|
341
372
|
ensureHeadCommit: false,
|
|
@@ -346,6 +377,11 @@ async function startOrAttachSession(cwd, goal) {
|
|
|
346
377
|
try {
|
|
347
378
|
const session = await loadSessionRecord(paths);
|
|
348
379
|
if (isSessionLive(session) && await pingRpc(paths)) {
|
|
380
|
+
if (enableFullAccessMode && !session.fullAccessMode) {
|
|
381
|
+
await rpcSetFullAccessMode(paths, {
|
|
382
|
+
enabled: true
|
|
383
|
+
});
|
|
384
|
+
}
|
|
349
385
|
if (goal) {
|
|
350
386
|
await rpcKickoff(paths, goal);
|
|
351
387
|
}
|
|
@@ -366,7 +402,7 @@ async function startOrAttachSession(cwd, goal) {
|
|
|
366
402
|
const rpcEndpoint = paths.socketPath;
|
|
367
403
|
await fs.writeFile(paths.commandsFile, "", "utf8");
|
|
368
404
|
const worktrees = await ensureWorktrees(repoRoot, paths, sessionId, config, baseCommit);
|
|
369
|
-
await createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint);
|
|
405
|
+
await createSessionRecord(paths, config, runtime, sessionId, baseCommit, worktrees, goal, rpcEndpoint, enableFullAccessMode);
|
|
370
406
|
if (prepared.createdRepository) {
|
|
371
407
|
await recordEvent(paths, sessionId, "repo.initialized", {
|
|
372
408
|
repoRoot
|
|
@@ -425,6 +461,29 @@ async function tryRpcSnapshot(paths) {
|
|
|
425
461
|
}
|
|
426
462
|
return await readSnapshot(paths);
|
|
427
463
|
}
|
|
464
|
+
async function loadSnapshot(paths, eventLimit = 80) {
|
|
465
|
+
const rpcSnapshot = await tryRpcSnapshot(paths);
|
|
466
|
+
if (rpcSnapshot) {
|
|
467
|
+
return rpcSnapshot;
|
|
468
|
+
}
|
|
469
|
+
const session = await loadSessionRecord(paths);
|
|
470
|
+
const approvals = await listApprovalRequests(paths, {
|
|
471
|
+
includeResolved: true
|
|
472
|
+
});
|
|
473
|
+
const events = await readRecentEvents(paths, eventLimit);
|
|
474
|
+
const worktreeDiffs = await Promise.all(session.worktrees.map(async (worktree)=>({
|
|
475
|
+
agent: worktree.agent,
|
|
476
|
+
paths: await listWorktreeChangedPaths(worktree.path, session.baseCommit)
|
|
477
|
+
})));
|
|
478
|
+
const latestLandReport = await loadLatestLandReport(paths);
|
|
479
|
+
return {
|
|
480
|
+
session,
|
|
481
|
+
approvals,
|
|
482
|
+
events,
|
|
483
|
+
worktreeDiffs,
|
|
484
|
+
latestLandReport
|
|
485
|
+
};
|
|
486
|
+
}
|
|
428
487
|
async function notifyOperatorSurface(paths, reason) {
|
|
429
488
|
if (!await pingRpc(paths)) {
|
|
430
489
|
return;
|
|
@@ -454,9 +513,13 @@ function buildRouteAnalytics(tasks) {
|
|
|
454
513
|
}
|
|
455
514
|
async function commandOpen(cwd, args) {
|
|
456
515
|
const goal = getGoal(args);
|
|
457
|
-
await startOrAttachSession(cwd, goal);
|
|
516
|
+
await startOrAttachSession(cwd, goal, hasFlag(args, "--approve-all"));
|
|
458
517
|
const repoRoot = await detectRepoRoot(cwd);
|
|
459
518
|
const paths = resolveAppPaths(repoRoot);
|
|
519
|
+
const session = await loadSessionRecord(paths);
|
|
520
|
+
if (session.fullAccessMode) {
|
|
521
|
+
console.log(renderFullAccessWarning());
|
|
522
|
+
}
|
|
460
523
|
await attachTui(paths);
|
|
461
524
|
}
|
|
462
525
|
async function commandResume(cwd) {
|
|
@@ -466,28 +529,35 @@ async function commandResume(cwd) {
|
|
|
466
529
|
}
|
|
467
530
|
async function commandStart(cwd, args) {
|
|
468
531
|
const goal = getGoal(args);
|
|
469
|
-
const socketPath = await startOrAttachSession(cwd, goal);
|
|
532
|
+
const socketPath = await startOrAttachSession(cwd, goal, hasFlag(args, "--approve-all"));
|
|
470
533
|
const repoRoot = await detectRepoRoot(cwd);
|
|
471
534
|
const paths = resolveAppPaths(repoRoot);
|
|
472
535
|
const session = await loadSessionRecord(paths);
|
|
473
536
|
console.log(`Started Kavi session ${session.id}`);
|
|
474
537
|
console.log(`Repo: ${repoRoot}`);
|
|
475
538
|
console.log(`Control: ${socketPath}`);
|
|
539
|
+
console.log(`Access: ${session.fullAccessMode ? "approve-all" : "standard"}`);
|
|
476
540
|
console.log(`Runtime: node=${session.runtime.nodeExecutable} codex=${session.runtime.codexExecutable} claude=${session.runtime.claudeExecutable}`);
|
|
541
|
+
if (session.fullAccessMode) {
|
|
542
|
+
console.log(renderFullAccessWarning());
|
|
543
|
+
}
|
|
477
544
|
for (const worktree of session.worktrees){
|
|
478
545
|
console.log(`- ${worktree.agent}: ${worktree.path}`);
|
|
479
546
|
}
|
|
480
547
|
}
|
|
481
548
|
async function commandStatus(cwd, args) {
|
|
482
549
|
const { paths } = await requireSession(cwd);
|
|
483
|
-
const
|
|
484
|
-
const session =
|
|
485
|
-
const pendingApprovals =
|
|
550
|
+
const snapshot = await loadSnapshot(paths, 60);
|
|
551
|
+
const session = snapshot.session;
|
|
552
|
+
const pendingApprovals = snapshot.approvals.filter((item)=>item.status === "pending");
|
|
486
553
|
const heartbeatAgeMs = sessionHeartbeatAgeMs(session);
|
|
487
554
|
const routeAnalytics = buildRouteAnalytics(session.tasks);
|
|
488
555
|
const ownershipConflicts = findOwnershipRuleConflicts(session.config);
|
|
489
556
|
const claimHotspots = buildClaimHotspots(session);
|
|
490
|
-
const recommendations = buildOperatorRecommendations(session
|
|
557
|
+
const recommendations = buildOperatorRecommendations(session, {
|
|
558
|
+
includeDismissed: true
|
|
559
|
+
});
|
|
560
|
+
const workflowSummary = buildWorkflowSummary(snapshot);
|
|
491
561
|
const payload = {
|
|
492
562
|
id: session.id,
|
|
493
563
|
status: session.status,
|
|
@@ -496,9 +566,16 @@ async function commandStatus(cwd, args) {
|
|
|
496
566
|
goal: session.goal,
|
|
497
567
|
daemonPid: session.daemonPid,
|
|
498
568
|
daemonHeartbeatAt: session.daemonHeartbeatAt,
|
|
569
|
+
fullAccessMode: session.fullAccessMode,
|
|
499
570
|
daemonHealthy: isSessionLive(session),
|
|
500
|
-
rpcConnected:
|
|
571
|
+
rpcConnected: await pingRpc(paths),
|
|
501
572
|
heartbeatAgeMs,
|
|
573
|
+
workflowStage: workflowSummary.stage,
|
|
574
|
+
latestLandReport: workflowSummary.latestLandReport ? {
|
|
575
|
+
id: workflowSummary.latestLandReport.id,
|
|
576
|
+
createdAt: workflowSummary.latestLandReport.createdAt,
|
|
577
|
+
targetBranch: workflowSummary.latestLandReport.targetBranch
|
|
578
|
+
} : null,
|
|
502
579
|
runtime: session.runtime,
|
|
503
580
|
taskCounts: {
|
|
504
581
|
total: session.tasks.length,
|
|
@@ -523,6 +600,9 @@ async function commandStatus(cwd, args) {
|
|
|
523
600
|
},
|
|
524
601
|
recommendationCounts: {
|
|
525
602
|
total: recommendations.length,
|
|
603
|
+
active: recommendations.filter((item)=>item.status === "active").length,
|
|
604
|
+
dismissed: recommendations.filter((item)=>item.status === "dismissed").length,
|
|
605
|
+
withOpenFollowUps: recommendations.filter((item)=>item.openFollowUpTaskIds.length > 0).length,
|
|
526
606
|
integration: recommendations.filter((item)=>item.kind === "integration").length,
|
|
527
607
|
handoff: recommendations.filter((item)=>item.kind === "handoff").length,
|
|
528
608
|
ownershipConfig: recommendations.filter((item)=>item.kind === "ownership-config").length
|
|
@@ -544,7 +624,12 @@ async function commandStatus(cwd, args) {
|
|
|
544
624
|
console.log(`Status: ${payload.status}${payload.daemonHealthy ? " (healthy)" : " (stale or stopped)"}`);
|
|
545
625
|
console.log(`Repo: ${payload.repoRoot}`);
|
|
546
626
|
console.log(`Control: ${payload.socketPath}${payload.rpcConnected ? " (connected)" : " (disconnected)"}`);
|
|
627
|
+
console.log(`Access: ${payload.fullAccessMode ? "approve-all" : "standard"}`);
|
|
547
628
|
console.log(`Goal: ${payload.goal ?? "-"}`);
|
|
629
|
+
console.log(`Workflow stage: ${payload.workflowStage.label} | ${payload.workflowStage.detail}`);
|
|
630
|
+
if (payload.latestLandReport) {
|
|
631
|
+
console.log(`Latest land: ${payload.latestLandReport.createdAt} -> ${payload.latestLandReport.targetBranch}`);
|
|
632
|
+
}
|
|
548
633
|
console.log(`Daemon PID: ${payload.daemonPid ?? "-"}`);
|
|
549
634
|
console.log(`Heartbeat: ${payload.daemonHeartbeatAt ?? "-"}${heartbeatAgeMs === null ? "" : ` (${heartbeatAgeMs} ms ago)`}`);
|
|
550
635
|
console.log(`Runtime: node=${payload.runtime.nodeExecutable} codex=${payload.runtime.codexExecutable} claude=${payload.runtime.claudeExecutable}`);
|
|
@@ -553,7 +638,7 @@ async function commandStatus(cwd, args) {
|
|
|
553
638
|
console.log(`Reviews: open=${payload.reviewCounts.open} total=${payload.reviewCounts.total}`);
|
|
554
639
|
console.log(`Decisions: total=${payload.decisionCounts.total}`);
|
|
555
640
|
console.log(`Path claims: active=${payload.pathClaimCounts.active}`);
|
|
556
|
-
console.log(`Recommendations: total=${payload.recommendationCounts.total} integration=${payload.recommendationCounts.integration} handoff=${payload.recommendationCounts.handoff} ownership-config=${payload.recommendationCounts.ownershipConfig}`);
|
|
641
|
+
console.log(`Recommendations: total=${payload.recommendationCounts.total} active=${payload.recommendationCounts.active} dismissed=${payload.recommendationCounts.dismissed} open-followups=${payload.recommendationCounts.withOpenFollowUps} integration=${payload.recommendationCounts.integration} handoff=${payload.recommendationCounts.handoff} ownership-config=${payload.recommendationCounts.ownershipConfig}`);
|
|
557
642
|
console.log(`Routes: codex=${payload.routeCounts.byOwner.codex} claude=${payload.routeCounts.byOwner.claude} | strategies=${Object.entries(payload.routeCounts.byStrategy).map(([strategy, count])=>`${strategy}:${count}`).join(", ") || "-"}`);
|
|
558
643
|
console.log(`Ownership conflicts: ${payload.ownershipConflicts} | Claim hotspots: ${payload.claimHotspots}`);
|
|
559
644
|
console.log(`Routing ownership: codex=${payload.routingOwnership.codexPaths.join(", ") || "-"} | claude=${payload.routingOwnership.claudePaths.join(", ") || "-"}`);
|
|
@@ -561,6 +646,128 @@ async function commandStatus(cwd, args) {
|
|
|
561
646
|
console.log(`- ${worktree.agent}: ${worktree.path}`);
|
|
562
647
|
}
|
|
563
648
|
}
|
|
649
|
+
async function commandSummary(cwd, args) {
|
|
650
|
+
const { paths } = await requireSession(cwd);
|
|
651
|
+
const snapshot = await loadSnapshot(paths, 120);
|
|
652
|
+
const artifacts = await listTaskArtifacts(paths);
|
|
653
|
+
const summary = buildWorkflowSummary(snapshot, artifacts);
|
|
654
|
+
const result = buildWorkflowResult(snapshot, artifacts);
|
|
655
|
+
if (args.includes("--json")) {
|
|
656
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
console.log(`Goal: ${summary.goal ?? "-"}`);
|
|
660
|
+
console.log(`Stage: ${summary.stage.label} | ${summary.stage.detail}`);
|
|
661
|
+
console.log(`Headline: ${result.headline}`);
|
|
662
|
+
console.log(`Tasks: pending=${summary.taskCounts.pending} running=${summary.taskCounts.running} blocked=${summary.taskCounts.blocked} completed=${summary.taskCounts.completed} failed=${summary.taskCounts.failed}`);
|
|
663
|
+
console.log(`Approvals: pending=${summary.approvalCounts.pending} | Reviews: open=${summary.reviewCounts.open} | Recommendations: active=${summary.recommendationCounts.active} dismissed=${summary.recommendationCounts.dismissed}`);
|
|
664
|
+
console.log(`Land readiness: ${summary.landReadiness.state}`);
|
|
665
|
+
if (summary.latestLandReport) {
|
|
666
|
+
console.log(`Latest landed result: ${summary.latestLandReport.createdAt} -> ${summary.latestLandReport.targetBranch}`);
|
|
667
|
+
}
|
|
668
|
+
if (summary.landReadiness.blockers.length > 0) {
|
|
669
|
+
console.log("Blockers:");
|
|
670
|
+
for (const blocker of summary.landReadiness.blockers){
|
|
671
|
+
console.log(`- ${blocker}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
if (summary.landReadiness.warnings.length > 0) {
|
|
675
|
+
console.log("Warnings:");
|
|
676
|
+
for (const warning of summary.landReadiness.warnings){
|
|
677
|
+
console.log(`- ${warning}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
console.log("Current changes:");
|
|
681
|
+
for (const changeSet of summary.changedByAgent){
|
|
682
|
+
console.log(`- ${changeSet.agent}: ${changeSet.paths.length} path(s)${changeSet.paths.length > 0 ? ` | ${changeSet.paths.join(", ")}` : ""}`);
|
|
683
|
+
}
|
|
684
|
+
if (summary.completedTasks.length > 0) {
|
|
685
|
+
console.log("Completed results:");
|
|
686
|
+
for (const task of summary.completedTasks.slice(0, 8)){
|
|
687
|
+
console.log(`- ${task.taskId} | ${task.owner} | ${task.title} | ${task.summary}${task.claimedPaths.length > 0 ? ` | paths=${task.claimedPaths.join(", ")}` : ""}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (summary.recentActivity.length > 0) {
|
|
691
|
+
console.log("Recent activity:");
|
|
692
|
+
for (const entry of summary.recentActivity.slice(0, 8)){
|
|
693
|
+
console.log(`- ${entry.timestamp} | ${entry.title} | ${entry.detail}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (summary.landReadiness.nextActions.length > 0) {
|
|
697
|
+
console.log("Next actions:");
|
|
698
|
+
for (const action of summary.landReadiness.nextActions){
|
|
699
|
+
console.log(`- ${action}`);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function commandResult(cwd, args) {
|
|
704
|
+
const { paths } = await requireSession(cwd);
|
|
705
|
+
const snapshot = await loadSnapshot(paths, 120);
|
|
706
|
+
const artifacts = await listTaskArtifacts(paths);
|
|
707
|
+
const result = buildWorkflowResult(snapshot, artifacts);
|
|
708
|
+
if (args.includes("--json")) {
|
|
709
|
+
console.log(JSON.stringify(result, null, 2));
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
console.log(`Goal: ${result.goal ?? "-"}`);
|
|
713
|
+
console.log(`Stage: ${result.stage.label} | ${result.stage.detail}`);
|
|
714
|
+
console.log(`Headline: ${result.headline}`);
|
|
715
|
+
if (result.latestLandReport) {
|
|
716
|
+
console.log(`Latest land: ${result.latestLandReport.createdAt} | ${result.latestLandReport.targetBranch}`);
|
|
717
|
+
console.log(`Validation: ${result.latestLandReport.validationCommand.trim() || "(none configured)"} | ${result.latestLandReport.validationStatus} | ${result.latestLandReport.validationDetail}`);
|
|
718
|
+
console.log(`Review threads landed: ${result.latestLandReport.reviewThreadsLanded}`);
|
|
719
|
+
} else {
|
|
720
|
+
console.log("Latest land: none yet");
|
|
721
|
+
}
|
|
722
|
+
console.log("Agent results:");
|
|
723
|
+
for (const agent of result.agentResults){
|
|
724
|
+
console.log(`- ${agent.agent}: completed=${agent.completedTaskCount} | latest=${agent.latestTaskTitle ?? "-"} | ${agent.latestSummary ?? "No completed result yet."}`);
|
|
725
|
+
if (agent.changedPaths.length > 0) {
|
|
726
|
+
console.log(` unlanded: ${agent.changedPaths.join(", ")}`);
|
|
727
|
+
} else if (agent.landedPaths.length > 0) {
|
|
728
|
+
console.log(` landed: ${agent.landedPaths.join(", ")}`);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (result.completedTasks.length > 0) {
|
|
732
|
+
console.log("Completed outputs:");
|
|
733
|
+
for (const task of result.completedTasks.slice(0, 8)){
|
|
734
|
+
console.log(`- ${task.owner} | ${task.title} | ${task.summary}${task.claimedPaths.length > 0 ? ` | paths=${task.claimedPaths.join(", ")}` : ""}`);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (result.latestLandReport?.summary.length) {
|
|
738
|
+
console.log("Merged result summary:");
|
|
739
|
+
for (const line of result.latestLandReport.summary){
|
|
740
|
+
console.log(`- ${line}`);
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
console.log("Result summary:");
|
|
744
|
+
for (const line of result.summaryLines.slice(0, 6)){
|
|
745
|
+
console.log(`- ${line}`);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (result.nextActions.length > 0) {
|
|
749
|
+
console.log("Next actions:");
|
|
750
|
+
for (const action of result.nextActions){
|
|
751
|
+
console.log(`- ${action}`);
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
async function commandActivity(cwd, args) {
|
|
756
|
+
const { paths } = await requireSession(cwd);
|
|
757
|
+
const limitArg = getFlag(args, "--limit");
|
|
758
|
+
const limit = limitArg ? Number(limitArg) : 30;
|
|
759
|
+
const snapshot = await loadSnapshot(paths, Math.max(50, Number.isFinite(limit) ? limit : 30));
|
|
760
|
+
const artifacts = await listTaskArtifacts(paths);
|
|
761
|
+
const activity = buildWorkflowActivity(snapshot, artifacts, Math.max(1, Number.isFinite(limit) ? limit : 30));
|
|
762
|
+
if (args.includes("--json")) {
|
|
763
|
+
console.log(JSON.stringify(activity, null, 2));
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
for (const entry of activity){
|
|
767
|
+
console.log(`${entry.timestamp} ${entry.title}`);
|
|
768
|
+
console.log(` ${entry.detail}`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
564
771
|
async function commandRoute(cwd, args) {
|
|
565
772
|
const prompt = getGoal(args);
|
|
566
773
|
if (!prompt) {
|
|
@@ -645,7 +852,18 @@ async function commandRecommend(cwd, args) {
|
|
|
645
852
|
const { paths } = await requireSession(cwd);
|
|
646
853
|
const rpcSnapshot = await tryRpcSnapshot(paths);
|
|
647
854
|
const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
|
|
648
|
-
const
|
|
855
|
+
const kind = parseRecommendationKind(getOptionalFilter(args, "--kind"));
|
|
856
|
+
const status = parseRecommendationStatus(getOptionalFilter(args, "--status"));
|
|
857
|
+
const targetAgent = getOptionalFilter(args, "--agent");
|
|
858
|
+
if (targetAgent && targetAgent !== "codex" && targetAgent !== "claude" && targetAgent !== "operator" && targetAgent !== "all") {
|
|
859
|
+
throw new Error(`Unsupported recommendation agent "${targetAgent}".`);
|
|
860
|
+
}
|
|
861
|
+
const recommendations = buildOperatorRecommendations(session, {
|
|
862
|
+
includeDismissed: args.includes("--all") || status === "dismissed" || status === "all",
|
|
863
|
+
kind: kind ?? undefined,
|
|
864
|
+
status: status ?? undefined,
|
|
865
|
+
targetAgent: targetAgent ?? undefined
|
|
866
|
+
});
|
|
649
867
|
if (args.includes("--json")) {
|
|
650
868
|
console.log(JSON.stringify(recommendations, null, 2));
|
|
651
869
|
return;
|
|
@@ -655,9 +873,13 @@ async function commandRecommend(cwd, args) {
|
|
|
655
873
|
return;
|
|
656
874
|
}
|
|
657
875
|
for (const recommendation of recommendations){
|
|
658
|
-
console.log(`${recommendation.id} | ${recommendation.kind} | ${recommendation.targetAgent ?? "-"}`);
|
|
876
|
+
console.log(`${recommendation.id} | ${recommendation.status} | ${recommendation.kind} | ${recommendation.targetAgent ?? "-"}`);
|
|
659
877
|
console.log(` title: ${recommendation.title}`);
|
|
660
878
|
console.log(` detail: ${recommendation.detail}`);
|
|
879
|
+
console.log(` open follow-ups: ${recommendation.openFollowUpTaskIds.join(", ") || "-"}`);
|
|
880
|
+
if (recommendation.dismissedReason) {
|
|
881
|
+
console.log(` dismissed reason: ${recommendation.dismissedReason}`);
|
|
882
|
+
}
|
|
661
883
|
console.log(` command: ${recommendation.commandHint}`);
|
|
662
884
|
}
|
|
663
885
|
}
|
|
@@ -669,65 +891,82 @@ async function commandRecommendApply(cwd, args) {
|
|
|
669
891
|
if (!recommendationId) {
|
|
670
892
|
throw new Error("A recommendation id is required. Example: kavi recommend-apply integration:src/ui/App.tsx");
|
|
671
893
|
}
|
|
672
|
-
const
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
if (recommendation.kind === "ownership-config") {
|
|
677
|
-
throw new Error(`Recommendation ${recommendation.id} is advisory only. Run "${recommendation.commandHint}" and update the config manually.`);
|
|
678
|
-
}
|
|
679
|
-
const owner = recommendation.targetAgent === "operator" || recommendation.targetAgent === null ? "codex" : recommendation.targetAgent;
|
|
680
|
-
const prompt = recommendation.kind === "integration" ? [
|
|
681
|
-
"Coordinate and resolve overlapping agent work before landing.",
|
|
682
|
-
recommendation.detail,
|
|
683
|
-
recommendation.filePath ? `Primary hotspot: ${recommendation.filePath}` : null
|
|
684
|
-
].filter(Boolean).join("\n") : [
|
|
685
|
-
"Pick up ownership-aware handoff work from Kavi.",
|
|
686
|
-
recommendation.detail,
|
|
687
|
-
recommendation.filePath ? `Focus path: ${recommendation.filePath}` : null
|
|
688
|
-
].filter(Boolean).join("\n");
|
|
689
|
-
const routeDecision = {
|
|
690
|
-
owner,
|
|
691
|
-
strategy: "manual",
|
|
692
|
-
confidence: 1,
|
|
693
|
-
reason: `Queued from Kavi recommendation ${recommendation.id}.`,
|
|
694
|
-
claimedPaths: recommendation.filePath ? [
|
|
695
|
-
recommendation.filePath
|
|
696
|
-
] : [],
|
|
697
|
-
metadata: {
|
|
698
|
-
recommendationId: recommendation.id,
|
|
699
|
-
recommendationKind: recommendation.kind
|
|
700
|
-
}
|
|
701
|
-
};
|
|
894
|
+
const plan = buildRecommendationActionPlan(session, recommendationId, {
|
|
895
|
+
force: args.includes("--force")
|
|
896
|
+
});
|
|
702
897
|
if (rpcSnapshot) {
|
|
703
898
|
await rpcEnqueueTask(paths, {
|
|
704
|
-
owner:
|
|
705
|
-
prompt,
|
|
706
|
-
routeReason:
|
|
707
|
-
routeMetadata:
|
|
708
|
-
claimedPaths:
|
|
709
|
-
routeStrategy:
|
|
710
|
-
routeConfidence:
|
|
899
|
+
owner: plan.owner,
|
|
900
|
+
prompt: plan.prompt,
|
|
901
|
+
routeReason: plan.routeReason,
|
|
902
|
+
routeMetadata: plan.routeMetadata,
|
|
903
|
+
claimedPaths: plan.claimedPaths,
|
|
904
|
+
routeStrategy: plan.routeStrategy,
|
|
905
|
+
routeConfidence: plan.routeConfidence,
|
|
906
|
+
recommendationId: plan.recommendation.id,
|
|
907
|
+
recommendationKind: plan.recommendation.kind
|
|
711
908
|
});
|
|
712
909
|
} else {
|
|
713
910
|
await appendCommand(paths, "enqueue", {
|
|
714
|
-
owner:
|
|
715
|
-
prompt,
|
|
716
|
-
routeReason:
|
|
717
|
-
routeMetadata:
|
|
718
|
-
claimedPaths:
|
|
719
|
-
routeStrategy:
|
|
720
|
-
routeConfidence:
|
|
911
|
+
owner: plan.owner,
|
|
912
|
+
prompt: plan.prompt,
|
|
913
|
+
routeReason: plan.routeReason,
|
|
914
|
+
routeMetadata: plan.routeMetadata,
|
|
915
|
+
claimedPaths: plan.claimedPaths,
|
|
916
|
+
routeStrategy: plan.routeStrategy,
|
|
917
|
+
routeConfidence: plan.routeConfidence,
|
|
918
|
+
recommendationId: plan.recommendation.id,
|
|
919
|
+
recommendationKind: plan.recommendation.kind
|
|
721
920
|
});
|
|
722
921
|
}
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
922
|
+
console.log(`Queued ${plan.owner} task from recommendation ${plan.recommendation.id}.`);
|
|
923
|
+
}
|
|
924
|
+
async function commandRecommendDismiss(cwd, args) {
|
|
925
|
+
const { paths } = await requireSession(cwd);
|
|
926
|
+
const recommendationId = args.find((arg)=>!arg.startsWith("--"));
|
|
927
|
+
if (!recommendationId) {
|
|
928
|
+
throw new Error("A recommendation id is required. Example: kavi recommend-dismiss integration:src/ui");
|
|
929
|
+
}
|
|
930
|
+
const reason = getOptionalFilter(args, "--reason");
|
|
931
|
+
const rpcSnapshot = await tryRpcSnapshot(paths);
|
|
932
|
+
const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
|
|
933
|
+
const recommendation = dismissOperatorRecommendation(session, recommendationId, reason);
|
|
934
|
+
if (rpcSnapshot) {
|
|
935
|
+
await rpcDismissRecommendation(paths, {
|
|
936
|
+
recommendationId,
|
|
937
|
+
reason
|
|
938
|
+
});
|
|
939
|
+
} else {
|
|
940
|
+
await saveSessionRecord(paths, session);
|
|
941
|
+
await recordEvent(paths, session.id, "recommendation.dismissed", {
|
|
942
|
+
recommendationId,
|
|
943
|
+
reason
|
|
944
|
+
});
|
|
945
|
+
await notifyOperatorSurface(paths, "recommendation.dismissed");
|
|
946
|
+
}
|
|
947
|
+
console.log(`Dismissed recommendation ${recommendation.id}.`);
|
|
948
|
+
}
|
|
949
|
+
async function commandRecommendRestore(cwd, args) {
|
|
950
|
+
const { paths } = await requireSession(cwd);
|
|
951
|
+
const recommendationId = args.find((arg)=>!arg.startsWith("--"));
|
|
952
|
+
if (!recommendationId) {
|
|
953
|
+
throw new Error("A recommendation id is required. Example: kavi recommend-restore integration:src/ui");
|
|
954
|
+
}
|
|
955
|
+
const rpcSnapshot = await tryRpcSnapshot(paths);
|
|
956
|
+
const session = rpcSnapshot?.session ?? await loadSessionRecord(paths);
|
|
957
|
+
const recommendation = restoreOperatorRecommendation(session, recommendationId);
|
|
958
|
+
if (rpcSnapshot) {
|
|
959
|
+
await rpcRestoreRecommendation(paths, {
|
|
960
|
+
recommendationId
|
|
961
|
+
});
|
|
962
|
+
} else {
|
|
963
|
+
await saveSessionRecord(paths, session);
|
|
964
|
+
await recordEvent(paths, session.id, "recommendation.restored", {
|
|
965
|
+
recommendationId
|
|
966
|
+
});
|
|
967
|
+
await notifyOperatorSurface(paths, "recommendation.restored");
|
|
968
|
+
}
|
|
969
|
+
console.log(`Restored recommendation ${recommendation.id}.`);
|
|
731
970
|
}
|
|
732
971
|
async function commandPaths(cwd, args) {
|
|
733
972
|
const repoRoot = await findRepoRoot(cwd) ?? cwd;
|
|
@@ -743,6 +982,7 @@ async function commandPaths(cwd, args) {
|
|
|
743
982
|
integrationRoot: paths.integrationRoot,
|
|
744
983
|
stateFile: paths.stateFile,
|
|
745
984
|
eventsFile: paths.eventsFile,
|
|
985
|
+
reportsDir: paths.reportsDir,
|
|
746
986
|
approvalsFile: paths.approvalsFile,
|
|
747
987
|
commandsFile: paths.commandsFile,
|
|
748
988
|
socketPath: paths.socketPath,
|
|
@@ -764,6 +1004,7 @@ async function commandPaths(cwd, args) {
|
|
|
764
1004
|
console.log(`Integration: ${payload.integrationRoot}`);
|
|
765
1005
|
console.log(`State file: ${payload.stateFile}`);
|
|
766
1006
|
console.log(`Events file: ${payload.eventsFile}`);
|
|
1007
|
+
console.log(`Reports: ${payload.reportsDir}`);
|
|
767
1008
|
console.log(`Approvals file: ${payload.approvalsFile}`);
|
|
768
1009
|
console.log(`Command queue: ${payload.commandsFile}`);
|
|
769
1010
|
console.log(`Control socket: ${payload.socketPath}`);
|
|
@@ -1113,6 +1354,10 @@ async function commandLand(cwd) {
|
|
|
1113
1354
|
const paths = resolveAppPaths(repoRoot);
|
|
1114
1355
|
const session = await loadSessionRecord(paths);
|
|
1115
1356
|
const targetBranch = await resolveTargetBranch(repoRoot, session.config.baseBranch);
|
|
1357
|
+
const preLandChanges = await Promise.all(session.worktrees.map(async (worktree)=>({
|
|
1358
|
+
agent: worktree.agent,
|
|
1359
|
+
paths: await listWorktreeChangedPaths(worktree.path, session.baseCommit)
|
|
1360
|
+
})));
|
|
1116
1361
|
const overlappingPaths = await findOverlappingWorktreePaths(session.worktrees, session.baseCommit);
|
|
1117
1362
|
if (overlappingPaths.length > 0) {
|
|
1118
1363
|
const existing = session.tasks.find((task)=>task.status === "pending" && task.title === "Resolve integration overlap");
|
|
@@ -1158,6 +1403,10 @@ async function commandLand(cwd) {
|
|
|
1158
1403
|
overlappingPaths
|
|
1159
1404
|
});
|
|
1160
1405
|
console.log("Landing blocked because both agent worktrees changed overlapping paths.");
|
|
1406
|
+
console.log("Current change surface:");
|
|
1407
|
+
for (const changeSet of preLandChanges){
|
|
1408
|
+
console.log(`- ${changeSet.agent}: ${changeSet.paths.length} path(s)${changeSet.paths.length > 0 ? ` | ${changeSet.paths.join(", ")}` : ""}`);
|
|
1409
|
+
}
|
|
1161
1410
|
console.log("Queued integration task for codex:");
|
|
1162
1411
|
for (const filePath of overlappingPaths){
|
|
1163
1412
|
console.log(`- ${filePath}`);
|
|
@@ -1175,6 +1424,7 @@ async function commandLand(cwd) {
|
|
|
1175
1424
|
const releasedClaims = releasePathClaims(session, {
|
|
1176
1425
|
note: `Released after landing into ${targetBranch}.`
|
|
1177
1426
|
});
|
|
1427
|
+
session.baseCommit = await getBranchCommit(repoRoot, targetBranch);
|
|
1178
1428
|
for (const claim of releasedClaims){
|
|
1179
1429
|
addDecisionRecord(session, {
|
|
1180
1430
|
kind: "integration",
|
|
@@ -1236,10 +1486,40 @@ async function commandLand(cwd) {
|
|
|
1236
1486
|
artifact.reviewNotes = session.reviewNotes.filter((note)=>note.taskId === taskId);
|
|
1237
1487
|
await saveTaskArtifact(paths, artifact);
|
|
1238
1488
|
}
|
|
1489
|
+
const artifacts = await listTaskArtifacts(paths);
|
|
1490
|
+
const postLandSnapshot = await loadSnapshot(paths, 60);
|
|
1491
|
+
const postLandSummary = buildWorkflowSummary(postLandSnapshot, artifacts);
|
|
1492
|
+
const landReport = buildLandReport({
|
|
1493
|
+
id: buildSessionId(),
|
|
1494
|
+
sessionId: session.id,
|
|
1495
|
+
goal: session.goal,
|
|
1496
|
+
createdAt: new Date().toISOString(),
|
|
1497
|
+
targetBranch,
|
|
1498
|
+
integrationBranch: result.integrationBranch,
|
|
1499
|
+
integrationPath: result.integrationPath,
|
|
1500
|
+
validationCommand: session.config.validationCommand,
|
|
1501
|
+
validationStatus: result.validation.status,
|
|
1502
|
+
validationDetail: result.validation.detail,
|
|
1503
|
+
changedByAgent: preLandChanges,
|
|
1504
|
+
completedTasks: postLandSummary.completedTasks,
|
|
1505
|
+
snapshotCommits: result.snapshotCommits,
|
|
1506
|
+
commandsRun: result.commandsRun,
|
|
1507
|
+
reviewThreadsLanded: landedReviewNotes.length,
|
|
1508
|
+
openReviewThreadsRemaining: session.reviewNotes.filter((note)=>note.status === "open").length
|
|
1509
|
+
});
|
|
1510
|
+
await saveLandReport(paths, landReport);
|
|
1239
1511
|
await notifyOperatorSurface(paths, "land.completed");
|
|
1240
1512
|
console.log(`Landed branches into ${targetBranch}`);
|
|
1241
1513
|
console.log(`Integration branch: ${result.integrationBranch}`);
|
|
1242
1514
|
console.log(`Integration worktree: ${result.integrationPath}`);
|
|
1515
|
+
console.log("Merged change surface:");
|
|
1516
|
+
for (const changeSet of preLandChanges){
|
|
1517
|
+
console.log(`- ${changeSet.agent}: ${changeSet.paths.length} path(s)${changeSet.paths.length > 0 ? ` | ${changeSet.paths.join(", ")}` : ""}`);
|
|
1518
|
+
}
|
|
1519
|
+
console.log(`Validation: ${result.validation.command || "(none configured)"} | ${result.validation.status} | ${result.validation.detail}`);
|
|
1520
|
+
console.log(`Review threads landed: ${landedReviewNotes.length}`);
|
|
1521
|
+
console.log(`Result report: ${landReport.id}`);
|
|
1522
|
+
console.log("Inspect result: kavi result");
|
|
1243
1523
|
for (const snapshot of result.snapshotCommits){
|
|
1244
1524
|
console.log(`Snapshot ${snapshot.agent}: ${snapshot.commit}${snapshot.createdCommit ? " (created)" : " (unchanged)"}`);
|
|
1245
1525
|
}
|
|
@@ -1269,6 +1549,18 @@ async function commandHook(args) {
|
|
|
1269
1549
|
};
|
|
1270
1550
|
if (session && agent === "claude" && eventName === "PreToolUse") {
|
|
1271
1551
|
const descriptor = describeToolUse(payload);
|
|
1552
|
+
if (session.fullAccessMode) {
|
|
1553
|
+
console.log(JSON.stringify({
|
|
1554
|
+
continue: true,
|
|
1555
|
+
suppressOutput: true,
|
|
1556
|
+
hookSpecificOutput: {
|
|
1557
|
+
hookEventName: "PreToolUse",
|
|
1558
|
+
permissionDecision: "allow",
|
|
1559
|
+
permissionDecisionReason: `Kavi approve-all bypassed approval: ${descriptor.summary}`
|
|
1560
|
+
}
|
|
1561
|
+
}));
|
|
1562
|
+
return;
|
|
1563
|
+
}
|
|
1272
1564
|
if (CLAUDE_AUTO_ALLOW_TOOLS.has(descriptor.toolName)) {
|
|
1273
1565
|
await recordEvent(paths, session.id, "approval.auto_allowed", {
|
|
1274
1566
|
agent,
|
|
@@ -1386,9 +1678,18 @@ async function main() {
|
|
|
1386
1678
|
case "resume":
|
|
1387
1679
|
await commandResume(cwd);
|
|
1388
1680
|
break;
|
|
1681
|
+
case "summary":
|
|
1682
|
+
await commandSummary(cwd, args);
|
|
1683
|
+
break;
|
|
1684
|
+
case "result":
|
|
1685
|
+
await commandResult(cwd, args);
|
|
1686
|
+
break;
|
|
1389
1687
|
case "status":
|
|
1390
1688
|
await commandStatus(cwd, args);
|
|
1391
1689
|
break;
|
|
1690
|
+
case "activity":
|
|
1691
|
+
await commandActivity(cwd, args);
|
|
1692
|
+
break;
|
|
1392
1693
|
case "route":
|
|
1393
1694
|
await commandRoute(cwd, args);
|
|
1394
1695
|
break;
|
|
@@ -1407,6 +1708,12 @@ async function main() {
|
|
|
1407
1708
|
case "recommend-apply":
|
|
1408
1709
|
await commandRecommendApply(cwd, args);
|
|
1409
1710
|
break;
|
|
1711
|
+
case "recommend-dismiss":
|
|
1712
|
+
await commandRecommendDismiss(cwd, args);
|
|
1713
|
+
break;
|
|
1714
|
+
case "recommend-restore":
|
|
1715
|
+
await commandRecommendRestore(cwd, args);
|
|
1716
|
+
break;
|
|
1410
1717
|
case "tasks":
|
|
1411
1718
|
await commandTasks(cwd, args);
|
|
1412
1719
|
break;
|