duocode 0.1.0
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/.env.example +36 -0
- package/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/ast/context.d.ts +16 -0
- package/dist/ast/context.js +37 -0
- package/dist/ast/context.js.map +1 -0
- package/dist/ast/diff.d.ts +27 -0
- package/dist/ast/diff.js +44 -0
- package/dist/ast/diff.js.map +1 -0
- package/dist/ast/locks.d.ts +47 -0
- package/dist/ast/locks.js +88 -0
- package/dist/ast/locks.js.map +1 -0
- package/dist/ast/merge.d.ts +22 -0
- package/dist/ast/merge.js +120 -0
- package/dist/ast/merge.js.map +1 -0
- package/dist/ast/ownership.d.ts +31 -0
- package/dist/ast/ownership.js +111 -0
- package/dist/ast/ownership.js.map +1 -0
- package/dist/ast/parser.d.ts +44 -0
- package/dist/ast/parser.js +134 -0
- package/dist/ast/parser.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +423 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/doctor.d.ts +5 -0
- package/dist/commands/doctor.js +63 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/duo.d.ts +9 -0
- package/dist/commands/duo.js +285 -0
- package/dist/commands/duo.js.map +1 -0
- package/dist/commands/github.d.ts +2 -0
- package/dist/commands/github.js +85 -0
- package/dist/commands/github.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +33 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/negotiation.d.ts +2 -0
- package/dist/commands/negotiation.js +160 -0
- package/dist/commands/negotiation.js.map +1 -0
- package/dist/commands/repl_commands.d.ts +26 -0
- package/dist/commands/repl_commands.js +226 -0
- package/dist/commands/repl_commands.js.map +1 -0
- package/dist/commands/shell.d.ts +1 -0
- package/dist/commands/shell.js +110 -0
- package/dist/commands/shell.js.map +1 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +231 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/task.d.ts +2 -0
- package/dist/commands/task.js +215 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/config/loader.d.ts +193 -0
- package/dist/config/loader.js +106 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/context/project_context.d.ts +79 -0
- package/dist/context/project_context.js +292 -0
- package/dist/context/project_context.js.map +1 -0
- package/dist/context/token_budget.d.ts +35 -0
- package/dist/context/token_budget.js +81 -0
- package/dist/context/token_budget.js.map +1 -0
- package/dist/db/queries.d.ts +121 -0
- package/dist/db/queries.js +109 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +110 -0
- package/dist/db/schema.js +346 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/duo/duo_orchestrator.d.ts +50 -0
- package/dist/duo/duo_orchestrator.js +510 -0
- package/dist/duo/duo_orchestrator.js.map +1 -0
- package/dist/duo/duo_session.d.ts +47 -0
- package/dist/duo/duo_session.js +127 -0
- package/dist/duo/duo_session.js.map +1 -0
- package/dist/duo/duo_types.d.ts +168 -0
- package/dist/duo/duo_types.js +53 -0
- package/dist/duo/duo_types.js.map +1 -0
- package/dist/duo/session_store.d.ts +71 -0
- package/dist/duo/session_store.js +177 -0
- package/dist/duo/session_store.js.map +1 -0
- package/dist/git/worktree.d.ts +21 -0
- package/dist/git/worktree.js +86 -0
- package/dist/git/worktree.js.map +1 -0
- package/dist/github/cache.d.ts +23 -0
- package/dist/github/cache.js +67 -0
- package/dist/github/cache.js.map +1 -0
- package/dist/github/issues.d.ts +17 -0
- package/dist/github/issues.js +93 -0
- package/dist/github/issues.js.map +1 -0
- package/dist/github/mcp_client.d.ts +57 -0
- package/dist/github/mcp_client.js +214 -0
- package/dist/github/mcp_client.js.map +1 -0
- package/dist/github/sync.d.ts +11 -0
- package/dist/github/sync.js +65 -0
- package/dist/github/sync.js.map +1 -0
- package/dist/github/webhook.d.ts +25 -0
- package/dist/github/webhook.js +197 -0
- package/dist/github/webhook.js.map +1 -0
- package/dist/negotiation/index.d.ts +1 -0
- package/dist/negotiation/index.js +2 -0
- package/dist/negotiation/index.js.map +1 -0
- package/dist/negotiation/protocol.d.ts +62 -0
- package/dist/negotiation/protocol.js +188 -0
- package/dist/negotiation/protocol.js.map +1 -0
- package/dist/orchestrator/complexity_scorer.d.ts +2 -0
- package/dist/orchestrator/complexity_scorer.js +79 -0
- package/dist/orchestrator/complexity_scorer.js.map +1 -0
- package/dist/orchestrator/dependency_graph.d.ts +7 -0
- package/dist/orchestrator/dependency_graph.js +73 -0
- package/dist/orchestrator/dependency_graph.js.map +1 -0
- package/dist/orchestrator/intent_parser.d.ts +11 -0
- package/dist/orchestrator/intent_parser.js +116 -0
- package/dist/orchestrator/intent_parser.js.map +1 -0
- package/dist/orchestrator/task_runner.d.ts +56 -0
- package/dist/orchestrator/task_runner.js +181 -0
- package/dist/orchestrator/task_runner.js.map +1 -0
- package/dist/orchestrator/types.d.ts +44 -0
- package/dist/orchestrator/types.js +21 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/providers/anthropic.d.ts +12 -0
- package/dist/providers/anthropic.js +258 -0
- package/dist/providers/anthropic.js.map +1 -0
- package/dist/providers/auction.d.ts +42 -0
- package/dist/providers/auction.js +190 -0
- package/dist/providers/auction.js.map +1 -0
- package/dist/providers/base.d.ts +103 -0
- package/dist/providers/base.js +2 -0
- package/dist/providers/base.js.map +1 -0
- package/dist/providers/cost_tracker.d.ts +45 -0
- package/dist/providers/cost_tracker.js +111 -0
- package/dist/providers/cost_tracker.js.map +1 -0
- package/dist/providers/duo_pair_router.d.ts +11 -0
- package/dist/providers/duo_pair_router.js +67 -0
- package/dist/providers/duo_pair_router.js.map +1 -0
- package/dist/providers/factory.d.ts +7 -0
- package/dist/providers/factory.js +130 -0
- package/dist/providers/factory.js.map +1 -0
- package/dist/providers/grading_rubric.d.ts +37 -0
- package/dist/providers/grading_rubric.js +238 -0
- package/dist/providers/grading_rubric.js.map +1 -0
- package/dist/providers/openai.d.ts +12 -0
- package/dist/providers/openai.js +229 -0
- package/dist/providers/openai.js.map +1 -0
- package/dist/providers/openrouter.d.ts +14 -0
- package/dist/providers/openrouter.js +178 -0
- package/dist/providers/openrouter.js.map +1 -0
- package/dist/providers/performance_tracker.d.ts +21 -0
- package/dist/providers/performance_tracker.js +63 -0
- package/dist/providers/performance_tracker.js.map +1 -0
- package/dist/providers/registry_loader.d.ts +6 -0
- package/dist/providers/registry_loader.js +54 -0
- package/dist/providers/registry_loader.js.map +1 -0
- package/dist/providers/retry.d.ts +66 -0
- package/dist/providers/retry.js +203 -0
- package/dist/providers/retry.js.map +1 -0
- package/dist/providers/role_scorer.d.ts +16 -0
- package/dist/providers/role_scorer.js +16 -0
- package/dist/providers/role_scorer.js.map +1 -0
- package/dist/providers/router.d.ts +84 -0
- package/dist/providers/router.js +542 -0
- package/dist/providers/router.js.map +1 -0
- package/dist/security/credentials.d.ts +6 -0
- package/dist/security/credentials.js +16 -0
- package/dist/security/credentials.js.map +1 -0
- package/dist/setup/browser.d.ts +1 -0
- package/dist/setup/browser.js +12 -0
- package/dist/setup/browser.js.map +1 -0
- package/dist/setup/global_config.d.ts +14 -0
- package/dist/setup/global_config.js +54 -0
- package/dist/setup/global_config.js.map +1 -0
- package/dist/setup/wizard.d.ts +2 -0
- package/dist/setup/wizard.js +206 -0
- package/dist/setup/wizard.js.map +1 -0
- package/dist/tools/agent_loop.d.ts +38 -0
- package/dist/tools/agent_loop.js +72 -0
- package/dist/tools/agent_loop.js.map +1 -0
- package/dist/tools/approval.d.ts +64 -0
- package/dist/tools/approval.js +172 -0
- package/dist/tools/approval.js.map +1 -0
- package/dist/tools/checkpoint.d.ts +65 -0
- package/dist/tools/checkpoint.js +342 -0
- package/dist/tools/checkpoint.js.map +1 -0
- package/dist/tools/definitions.d.ts +13 -0
- package/dist/tools/definitions.js +103 -0
- package/dist/tools/definitions.js.map +1 -0
- package/dist/tools/diff_display.d.ts +46 -0
- package/dist/tools/diff_display.js +298 -0
- package/dist/tools/diff_display.js.map +1 -0
- package/dist/tools/executor.d.ts +12 -0
- package/dist/tools/executor.js +340 -0
- package/dist/tools/executor.js.map +1 -0
- package/dist/tools/permissions.d.ts +17 -0
- package/dist/tools/permissions.js +139 -0
- package/dist/tools/permissions.js.map +1 -0
- package/dist/tools/tool_types.d.ts +48 -0
- package/dist/tools/tool_types.js +7 -0
- package/dist/tools/tool_types.js.map +1 -0
- package/dist/ui/banner.d.ts +4 -0
- package/dist/ui/banner.js +104 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/callbacks.d.ts +30 -0
- package/dist/ui/callbacks.js +132 -0
- package/dist/ui/callbacks.js.map +1 -0
- package/dist/ui/colors.d.ts +14 -0
- package/dist/ui/colors.js +28 -0
- package/dist/ui/colors.js.map +1 -0
- package/dist/ui/dashboard.d.ts +51 -0
- package/dist/ui/dashboard.js +181 -0
- package/dist/ui/dashboard.js.map +1 -0
- package/dist/ui/leaderboard.d.ts +16 -0
- package/dist/ui/leaderboard.js +43 -0
- package/dist/ui/leaderboard.js.map +1 -0
- package/dist/ui/logger.d.ts +28 -0
- package/dist/ui/logger.js +117 -0
- package/dist/ui/logger.js.map +1 -0
- package/dist/ui/progress.d.ts +16 -0
- package/dist/ui/progress.js +62 -0
- package/dist/ui/progress.js.map +1 -0
- package/dist/ui/tokenizer.d.ts +5 -0
- package/dist/ui/tokenizer.js +54 -0
- package/dist/ui/tokenizer.js.map +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const MODE_CYCLE = ["code", "plan", "execute", "debug"];
|
|
2
|
+
export const MODE_LABELS = {
|
|
3
|
+
code: "Code",
|
|
4
|
+
plan: "Plan",
|
|
5
|
+
execute: "Execute",
|
|
6
|
+
debug: "Debug",
|
|
7
|
+
};
|
|
8
|
+
export const MODE_DESCRIPTIONS = {
|
|
9
|
+
code: "Full pipeline: architect plan → review → executor",
|
|
10
|
+
plan: "Architect only — generates plan, stops before execution",
|
|
11
|
+
execute: "Full pipeline, no pauses — auto-confirms everything",
|
|
12
|
+
debug: "Debug-focused — wraps prompt for error analysis and fixes",
|
|
13
|
+
};
|
|
14
|
+
const DEFAULT_MAX_CONTEXT_TURNS = 3;
|
|
15
|
+
const DEFAULT_MAX_PLAN_CHARS = 2000;
|
|
16
|
+
const DEFAULT_MAX_OUTPUT_CHARS = 3000;
|
|
17
|
+
function truncate(text, maxChars) {
|
|
18
|
+
if (maxChars <= 0)
|
|
19
|
+
return "";
|
|
20
|
+
if (text.length <= maxChars)
|
|
21
|
+
return text;
|
|
22
|
+
const marker = "[...truncated] ";
|
|
23
|
+
const budget = Math.max(0, maxChars - marker.length);
|
|
24
|
+
return marker + text.slice(text.length - budget);
|
|
25
|
+
}
|
|
26
|
+
export class DuoSession {
|
|
27
|
+
turns = [];
|
|
28
|
+
totalCostUsd = 0;
|
|
29
|
+
_assignment = null;
|
|
30
|
+
_gradingEnabled = true;
|
|
31
|
+
_autoConfirm = false;
|
|
32
|
+
_mode = "code";
|
|
33
|
+
_pendingPlan = null;
|
|
34
|
+
addTurn(turn) {
|
|
35
|
+
this.turns.push(turn);
|
|
36
|
+
this.totalCostUsd += turn.costUsd;
|
|
37
|
+
}
|
|
38
|
+
clear() {
|
|
39
|
+
this.turns = [];
|
|
40
|
+
this.totalCostUsd = 0;
|
|
41
|
+
this._assignment = null;
|
|
42
|
+
this._pendingPlan = null;
|
|
43
|
+
// Note: _mode and _gradingEnabled are intentionally NOT reset by clear().
|
|
44
|
+
}
|
|
45
|
+
getTurnCount() {
|
|
46
|
+
return this.turns.length;
|
|
47
|
+
}
|
|
48
|
+
getTotalCost() {
|
|
49
|
+
return this.totalCostUsd;
|
|
50
|
+
}
|
|
51
|
+
getTurns() {
|
|
52
|
+
return this.turns;
|
|
53
|
+
}
|
|
54
|
+
get assignment() {
|
|
55
|
+
return this._assignment;
|
|
56
|
+
}
|
|
57
|
+
set assignment(value) {
|
|
58
|
+
this._assignment = value;
|
|
59
|
+
}
|
|
60
|
+
get gradingEnabled() {
|
|
61
|
+
return this._gradingEnabled;
|
|
62
|
+
}
|
|
63
|
+
toggleGrading() {
|
|
64
|
+
this._gradingEnabled = !this._gradingEnabled;
|
|
65
|
+
return this._gradingEnabled;
|
|
66
|
+
}
|
|
67
|
+
get autoConfirm() {
|
|
68
|
+
return this._autoConfirm;
|
|
69
|
+
}
|
|
70
|
+
toggleAutoConfirm() {
|
|
71
|
+
this._autoConfirm = !this._autoConfirm;
|
|
72
|
+
return this._autoConfirm;
|
|
73
|
+
}
|
|
74
|
+
get mode() {
|
|
75
|
+
return this._mode;
|
|
76
|
+
}
|
|
77
|
+
cycleMode() {
|
|
78
|
+
const idx = MODE_CYCLE.indexOf(this._mode);
|
|
79
|
+
this._mode = MODE_CYCLE[(idx + 1) % MODE_CYCLE.length];
|
|
80
|
+
return this._mode;
|
|
81
|
+
}
|
|
82
|
+
setMode(mode) {
|
|
83
|
+
this._mode = mode;
|
|
84
|
+
}
|
|
85
|
+
get pendingPlan() {
|
|
86
|
+
return this._pendingPlan;
|
|
87
|
+
}
|
|
88
|
+
storePlan(plan) {
|
|
89
|
+
this._pendingPlan = plan;
|
|
90
|
+
}
|
|
91
|
+
clearPendingPlan() {
|
|
92
|
+
this._pendingPlan = null;
|
|
93
|
+
}
|
|
94
|
+
buildContext(maxTurns = DEFAULT_MAX_CONTEXT_TURNS, maxPlanChars = DEFAULT_MAX_PLAN_CHARS, maxOutputChars = DEFAULT_MAX_OUTPUT_CHARS) {
|
|
95
|
+
if (this.turns.length === 0)
|
|
96
|
+
return "";
|
|
97
|
+
const recentTurns = this.turns.slice(-maxTurns);
|
|
98
|
+
const parts = ["## Previous conversation\n"];
|
|
99
|
+
for (let i = 0; i < recentTurns.length; i++) {
|
|
100
|
+
const turn = recentTurns[i];
|
|
101
|
+
const turnIndex = this.turns.length - recentTurns.length + i + 1;
|
|
102
|
+
parts.push(`### Turn ${turnIndex}: "${turn.prompt}"`);
|
|
103
|
+
parts.push(`**Architect plan:**\n${truncate(turn.architectPlan, maxPlanChars)}`);
|
|
104
|
+
parts.push(`**Executor output:**\n${truncate(turn.executorOutput, maxOutputChars)}`);
|
|
105
|
+
parts.push("");
|
|
106
|
+
}
|
|
107
|
+
return parts.join("\n");
|
|
108
|
+
}
|
|
109
|
+
buildHistorySummary() {
|
|
110
|
+
if (this.turns.length === 0)
|
|
111
|
+
return "No conversation history.";
|
|
112
|
+
const lines = [];
|
|
113
|
+
for (let i = 0; i < this.turns.length; i++) {
|
|
114
|
+
const turn = this.turns[i];
|
|
115
|
+
const promptPreview = turn.prompt.length > 60
|
|
116
|
+
? turn.prompt.slice(0, 57) + "..."
|
|
117
|
+
: turn.prompt;
|
|
118
|
+
const grade = turn.grading
|
|
119
|
+
? ` | grade: ${turn.grading.compositeScore.toFixed(2)}/5`
|
|
120
|
+
: "";
|
|
121
|
+
lines.push(` #${i + 1} "${promptPreview}" ($${turn.costUsd.toFixed(4)}, ${turn.durationMs}ms${grade})`);
|
|
122
|
+
}
|
|
123
|
+
lines.push(`\n Total: ${this.turns.length} turns, $${this.totalCostUsd.toFixed(4)}`);
|
|
124
|
+
return lines.join("\n");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=duo_session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duo_session.js","sourceRoot":"","sources":["../../src/duo/duo_session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { ModelProfile } from "../providers/base.js";
|
|
3
|
+
import type { FileChange } from "../tools/tool_types.js";
|
|
4
|
+
export type DuoRole = "architect" | "executor";
|
|
5
|
+
export declare const ARCHITECT_CAPABILITIES: readonly ["deep_reasoning", "architecture_design", "api_design", "data_modeling", "security_analysis", "code_review"];
|
|
6
|
+
export declare const EXECUTOR_CAPABILITIES: readonly ["code_generation", "code_refactoring", "test_generation", "bug_diagnosis", "performance_optimization", "documentation"];
|
|
7
|
+
export type ArchitectCapability = (typeof ARCHITECT_CAPABILITIES)[number];
|
|
8
|
+
export type ExecutorCapability = (typeof EXECUTOR_CAPABILITIES)[number];
|
|
9
|
+
export interface DuoPairSlot {
|
|
10
|
+
profile: ModelProfile;
|
|
11
|
+
role: DuoRole;
|
|
12
|
+
roleScore: number;
|
|
13
|
+
reasons: string[];
|
|
14
|
+
}
|
|
15
|
+
export interface DuoPairAssignment {
|
|
16
|
+
architect: DuoPairSlot;
|
|
17
|
+
executor: DuoPairSlot;
|
|
18
|
+
pairScore: number;
|
|
19
|
+
sameModel: boolean;
|
|
20
|
+
rationale: string;
|
|
21
|
+
}
|
|
22
|
+
export declare const GRADING_DIMENSIONS: readonly ["correctness", "security", "efficiency", "robustness", "maintainability", "usability"];
|
|
23
|
+
export type GradingDimension = (typeof GRADING_DIMENSIONS)[number];
|
|
24
|
+
export declare const DIMENSION_WEIGHTS: Record<GradingDimension, number>;
|
|
25
|
+
export interface DimensionScore {
|
|
26
|
+
dimension: GradingDimension;
|
|
27
|
+
score: number;
|
|
28
|
+
reasoning: string;
|
|
29
|
+
}
|
|
30
|
+
export interface GradingResult {
|
|
31
|
+
model: string;
|
|
32
|
+
role: DuoRole;
|
|
33
|
+
dimensions: DimensionScore[];
|
|
34
|
+
compositeScore: number;
|
|
35
|
+
gradedBy: string;
|
|
36
|
+
judgeRationale?: string;
|
|
37
|
+
taskId?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare const DEFAULT_ELO = 1500;
|
|
40
|
+
export declare const DEFAULT_K_FACTOR = 32;
|
|
41
|
+
export interface EloRecord {
|
|
42
|
+
model: string;
|
|
43
|
+
role: DuoRole;
|
|
44
|
+
capabilityBucket: string;
|
|
45
|
+
elo: number;
|
|
46
|
+
matchCount: number;
|
|
47
|
+
}
|
|
48
|
+
export interface EloMatchResult {
|
|
49
|
+
winnerModel: string;
|
|
50
|
+
loserModel: string;
|
|
51
|
+
role: DuoRole;
|
|
52
|
+
capabilityBucket: string;
|
|
53
|
+
isDraw: boolean;
|
|
54
|
+
scoreDelta: number;
|
|
55
|
+
}
|
|
56
|
+
export interface CostEstimate {
|
|
57
|
+
architectModel: string;
|
|
58
|
+
executorModel: string;
|
|
59
|
+
architectCostUsd: number;
|
|
60
|
+
executorCostUsd: number;
|
|
61
|
+
totalEstimatedCostUsd: number;
|
|
62
|
+
estimatedInputTokens: number;
|
|
63
|
+
estimatedOutputTokens: number;
|
|
64
|
+
}
|
|
65
|
+
export type ConfirmationAction = "proceed" | "abort" | "pick-architect" | "pick-executor";
|
|
66
|
+
export type ConfirmationCallback = (estimate: CostEstimate) => Promise<ConfirmationAction>;
|
|
67
|
+
export interface ModelPickerOption {
|
|
68
|
+
model: string;
|
|
69
|
+
provider: string;
|
|
70
|
+
roleAffinity: number;
|
|
71
|
+
estimatedCostUsd: number;
|
|
72
|
+
isCurrent: boolean;
|
|
73
|
+
}
|
|
74
|
+
export type ModelPickerCallback = (role: DuoRole, options: ModelPickerOption[], currentModel: string) => Promise<string | null>;
|
|
75
|
+
export type ArchitectReviewCallback = (architectOutput: string) => Promise<boolean>;
|
|
76
|
+
export interface DuoPairRequest {
|
|
77
|
+
prompt: string;
|
|
78
|
+
conversationContext?: string;
|
|
79
|
+
preferredArchitect?: string;
|
|
80
|
+
preferredExecutor?: string;
|
|
81
|
+
judgeModel?: string;
|
|
82
|
+
enableGrading?: boolean;
|
|
83
|
+
requireConfirmation?: boolean;
|
|
84
|
+
architectOnly?: boolean;
|
|
85
|
+
precomputedPlan?: string;
|
|
86
|
+
/** Enable agentic tool use in the executor phase */
|
|
87
|
+
enableTools?: boolean;
|
|
88
|
+
/** Project root for file operations (required when enableTools is true) */
|
|
89
|
+
projectRoot?: string;
|
|
90
|
+
/** Pre-built project context (file tree + relevant file contents) */
|
|
91
|
+
projectContext?: string;
|
|
92
|
+
/** Files included in the project context (for display) */
|
|
93
|
+
includedFiles?: Array<{
|
|
94
|
+
path: string;
|
|
95
|
+
reason: string;
|
|
96
|
+
}>;
|
|
97
|
+
}
|
|
98
|
+
export declare const DuoPairRequestSchema: z.ZodObject<{
|
|
99
|
+
prompt: z.ZodString;
|
|
100
|
+
conversationContext: z.ZodOptional<z.ZodString>;
|
|
101
|
+
preferredArchitect: z.ZodOptional<z.ZodString>;
|
|
102
|
+
preferredExecutor: z.ZodOptional<z.ZodString>;
|
|
103
|
+
judgeModel: z.ZodOptional<z.ZodString>;
|
|
104
|
+
enableGrading: z.ZodOptional<z.ZodBoolean>;
|
|
105
|
+
requireConfirmation: z.ZodOptional<z.ZodBoolean>;
|
|
106
|
+
architectOnly: z.ZodOptional<z.ZodBoolean>;
|
|
107
|
+
precomputedPlan: z.ZodOptional<z.ZodString>;
|
|
108
|
+
enableTools: z.ZodOptional<z.ZodBoolean>;
|
|
109
|
+
projectRoot: z.ZodOptional<z.ZodString>;
|
|
110
|
+
projectContext: z.ZodOptional<z.ZodString>;
|
|
111
|
+
includedFiles: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
112
|
+
path: z.ZodString;
|
|
113
|
+
reason: z.ZodString;
|
|
114
|
+
}, "strip", z.ZodTypeAny, {
|
|
115
|
+
path: string;
|
|
116
|
+
reason: string;
|
|
117
|
+
}, {
|
|
118
|
+
path: string;
|
|
119
|
+
reason: string;
|
|
120
|
+
}>, "many">>;
|
|
121
|
+
}, "strip", z.ZodTypeAny, {
|
|
122
|
+
prompt: string;
|
|
123
|
+
preferredArchitect?: string | undefined;
|
|
124
|
+
preferredExecutor?: string | undefined;
|
|
125
|
+
conversationContext?: string | undefined;
|
|
126
|
+
judgeModel?: string | undefined;
|
|
127
|
+
enableGrading?: boolean | undefined;
|
|
128
|
+
requireConfirmation?: boolean | undefined;
|
|
129
|
+
architectOnly?: boolean | undefined;
|
|
130
|
+
precomputedPlan?: string | undefined;
|
|
131
|
+
enableTools?: boolean | undefined;
|
|
132
|
+
projectRoot?: string | undefined;
|
|
133
|
+
projectContext?: string | undefined;
|
|
134
|
+
includedFiles?: {
|
|
135
|
+
path: string;
|
|
136
|
+
reason: string;
|
|
137
|
+
}[] | undefined;
|
|
138
|
+
}, {
|
|
139
|
+
prompt: string;
|
|
140
|
+
preferredArchitect?: string | undefined;
|
|
141
|
+
preferredExecutor?: string | undefined;
|
|
142
|
+
conversationContext?: string | undefined;
|
|
143
|
+
judgeModel?: string | undefined;
|
|
144
|
+
enableGrading?: boolean | undefined;
|
|
145
|
+
requireConfirmation?: boolean | undefined;
|
|
146
|
+
architectOnly?: boolean | undefined;
|
|
147
|
+
precomputedPlan?: string | undefined;
|
|
148
|
+
enableTools?: boolean | undefined;
|
|
149
|
+
projectRoot?: string | undefined;
|
|
150
|
+
projectContext?: string | undefined;
|
|
151
|
+
includedFiles?: {
|
|
152
|
+
path: string;
|
|
153
|
+
reason: string;
|
|
154
|
+
}[] | undefined;
|
|
155
|
+
}>;
|
|
156
|
+
export interface DuoPipelineResult {
|
|
157
|
+
assignment: DuoPairAssignment;
|
|
158
|
+
architectOutput: string;
|
|
159
|
+
executorOutput: string;
|
|
160
|
+
grading?: GradingResult;
|
|
161
|
+
architectGrading?: GradingResult;
|
|
162
|
+
totalCostUsd: number;
|
|
163
|
+
durationMs: number;
|
|
164
|
+
/** Files created/edited during tool execution */
|
|
165
|
+
fileChanges?: FileChange[];
|
|
166
|
+
/** Number of tool calls made during executor phase */
|
|
167
|
+
toolCallCount?: number;
|
|
168
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const ARCHITECT_CAPABILITIES = [
|
|
3
|
+
"deep_reasoning",
|
|
4
|
+
"architecture_design",
|
|
5
|
+
"api_design",
|
|
6
|
+
"data_modeling",
|
|
7
|
+
"security_analysis",
|
|
8
|
+
"code_review",
|
|
9
|
+
];
|
|
10
|
+
export const EXECUTOR_CAPABILITIES = [
|
|
11
|
+
"code_generation",
|
|
12
|
+
"code_refactoring",
|
|
13
|
+
"test_generation",
|
|
14
|
+
"bug_diagnosis",
|
|
15
|
+
"performance_optimization",
|
|
16
|
+
"documentation",
|
|
17
|
+
];
|
|
18
|
+
// ── Grading (ISO/IEC 25010) ────────────────────────────────────────
|
|
19
|
+
export const GRADING_DIMENSIONS = [
|
|
20
|
+
"correctness",
|
|
21
|
+
"security",
|
|
22
|
+
"efficiency",
|
|
23
|
+
"robustness",
|
|
24
|
+
"maintainability",
|
|
25
|
+
"usability",
|
|
26
|
+
];
|
|
27
|
+
export const DIMENSION_WEIGHTS = {
|
|
28
|
+
correctness: 0.30,
|
|
29
|
+
security: 0.15,
|
|
30
|
+
efficiency: 0.15,
|
|
31
|
+
robustness: 0.15,
|
|
32
|
+
maintainability: 0.15,
|
|
33
|
+
usability: 0.10,
|
|
34
|
+
};
|
|
35
|
+
// ── ELO ────────────────────────────────────────────────────────────
|
|
36
|
+
export const DEFAULT_ELO = 1500;
|
|
37
|
+
export const DEFAULT_K_FACTOR = 32;
|
|
38
|
+
export const DuoPairRequestSchema = z.object({
|
|
39
|
+
prompt: z.string().min(1),
|
|
40
|
+
conversationContext: z.string().optional(),
|
|
41
|
+
preferredArchitect: z.string().optional(),
|
|
42
|
+
preferredExecutor: z.string().optional(),
|
|
43
|
+
judgeModel: z.string().optional(),
|
|
44
|
+
enableGrading: z.boolean().optional(),
|
|
45
|
+
requireConfirmation: z.boolean().optional(),
|
|
46
|
+
architectOnly: z.boolean().optional(),
|
|
47
|
+
precomputedPlan: z.string().optional(),
|
|
48
|
+
enableTools: z.boolean().optional(),
|
|
49
|
+
projectRoot: z.string().optional(),
|
|
50
|
+
projectContext: z.string().optional(),
|
|
51
|
+
includedFiles: z.array(z.object({ path: z.string(), reason: z.string() })).optional(),
|
|
52
|
+
});
|
|
53
|
+
//# sourceMappingURL=duo_types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"duo_types.js","sourceRoot":"","sources":["../../src/duo/duo_types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACpC,gBAAgB;IAChB,qBAAqB;IACrB,YAAY;IACZ,eAAe;IACf,mBAAmB;IACnB,aAAa;CACL,CAAC;AAEX,MAAM,CAAC,MAAM,qBAAqB,GAAG;IACnC,iBAAiB;IACjB,kBAAkB;IAClB,iBAAiB;IACjB,eAAe;IACf,0BAA0B;IAC1B,eAAe;CACP,CAAC;AAsBX,sEAAsE;AAEtE,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,aAAa;IACb,UAAU;IACV,YAAY;IACZ,YAAY;IACZ,iBAAiB;IACjB,WAAW;CACH,CAAC;AAIX,MAAM,CAAC,MAAM,iBAAiB,GAAqC;IACjE,WAAW,EAAE,IAAI;IACjB,QAAQ,EAAE,IAAI;IACd,UAAU,EAAE,IAAI;IAChB,UAAU,EAAE,IAAI;IAChB,eAAe,EAAE,IAAI;IACrB,SAAS,EAAE,IAAI;CAChB,CAAC;AAkBF,sEAAsE;AAEtE,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC;AAChC,MAAM,CAAC,MAAM,gBAAgB,GAAG,EAAE,CAAC;AAsEnC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzC,iBAAiB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACxC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACrC,mBAAmB,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC3C,aAAa,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACrC,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACtC,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IACnC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,cAAc,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACrC,aAAa,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE;CACtF,CAAC,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session serialization: save/resume/list DuoSession state to disk.
|
|
3
|
+
* Stored as JSON in `.duocode/sessions/`.
|
|
4
|
+
*/
|
|
5
|
+
import type { DuoPairAssignment } from "./duo_types.js";
|
|
6
|
+
import { DuoSession, type ConversationTurn, type InteractiveMode, type PendingPlan } from "./duo_session.js";
|
|
7
|
+
export interface SerializedSession {
|
|
8
|
+
id: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
updatedAt: number;
|
|
11
|
+
turns: ConversationTurn[];
|
|
12
|
+
totalCostUsd: number;
|
|
13
|
+
mode: InteractiveMode;
|
|
14
|
+
gradingEnabled: boolean;
|
|
15
|
+
autoConfirm: boolean;
|
|
16
|
+
assignment: DuoPairAssignment | null;
|
|
17
|
+
pendingPlan: PendingPlan | null;
|
|
18
|
+
/** First prompt preview for listing */
|
|
19
|
+
promptPreview: string;
|
|
20
|
+
}
|
|
21
|
+
export interface CompactedTurn {
|
|
22
|
+
prompt: string;
|
|
23
|
+
architectPlan: string;
|
|
24
|
+
executorOutput: string;
|
|
25
|
+
grading?: ConversationTurn["grading"];
|
|
26
|
+
costUsd: number;
|
|
27
|
+
durationMs: number;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Summarize older turns into a condensed block.
|
|
32
|
+
* Keeps the last `keep` turns intact and compresses the rest.
|
|
33
|
+
*/
|
|
34
|
+
export declare function compactTurns(turns: readonly ConversationTurn[], keep?: number): ConversationTurn[];
|
|
35
|
+
export declare class SessionStore {
|
|
36
|
+
private readonly dir;
|
|
37
|
+
private pendingSave;
|
|
38
|
+
constructor(projectRoot: string);
|
|
39
|
+
private ensureDir;
|
|
40
|
+
private sessionPath;
|
|
41
|
+
/**
|
|
42
|
+
* Serialize current DuoSession to disk.
|
|
43
|
+
* Returns the session ID.
|
|
44
|
+
*/
|
|
45
|
+
save(session: DuoSession, existingId?: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Non-blocking save: schedules a write-behind save.
|
|
48
|
+
* Does not block the REPL loop.
|
|
49
|
+
*/
|
|
50
|
+
saveAsync(session: DuoSession, existingId?: string): string;
|
|
51
|
+
/**
|
|
52
|
+
* Wait for any pending async save to complete.
|
|
53
|
+
*/
|
|
54
|
+
flush(): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Load a session from disk and restore into a DuoSession instance.
|
|
57
|
+
*/
|
|
58
|
+
resume(id: string): {
|
|
59
|
+
session: DuoSession;
|
|
60
|
+
data: SerializedSession;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* List saved sessions, sorted by most recent first.
|
|
64
|
+
*/
|
|
65
|
+
list(): SerializedSession[];
|
|
66
|
+
/**
|
|
67
|
+
* Delete a saved session.
|
|
68
|
+
*/
|
|
69
|
+
delete(id: string): boolean;
|
|
70
|
+
private getCreatedAt;
|
|
71
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session serialization: save/resume/list DuoSession state to disk.
|
|
3
|
+
* Stored as JSON in `.duocode/sessions/`.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import { DuoSession, } from "./duo_session.js";
|
|
9
|
+
/**
|
|
10
|
+
* Summarize older turns into a condensed block.
|
|
11
|
+
* Keeps the last `keep` turns intact and compresses the rest.
|
|
12
|
+
*/
|
|
13
|
+
export function compactTurns(turns, keep = 5) {
|
|
14
|
+
if (turns.length <= keep)
|
|
15
|
+
return [...turns];
|
|
16
|
+
const older = turns.slice(0, turns.length - keep);
|
|
17
|
+
const recent = turns.slice(turns.length - keep);
|
|
18
|
+
// Build a condensed summary turn
|
|
19
|
+
const summaryLines = older.map((t, i) => `Turn ${i + 1}: "${t.prompt.slice(0, 80)}" ($${t.costUsd.toFixed(4)}, ${t.durationMs}ms)`);
|
|
20
|
+
const summaryTurn = {
|
|
21
|
+
prompt: "[compacted summary]",
|
|
22
|
+
architectPlan: `Compacted ${older.length} earlier turns:\n${summaryLines.join("\n")}`,
|
|
23
|
+
executorOutput: "",
|
|
24
|
+
costUsd: older.reduce((s, t) => s + t.costUsd, 0),
|
|
25
|
+
durationMs: older.reduce((s, t) => s + t.durationMs, 0),
|
|
26
|
+
timestamp: older[0].timestamp,
|
|
27
|
+
};
|
|
28
|
+
return [summaryTurn, ...recent];
|
|
29
|
+
}
|
|
30
|
+
// ── Session Store ───────────────────────────────────────────────
|
|
31
|
+
export class SessionStore {
|
|
32
|
+
dir;
|
|
33
|
+
pendingSave = null;
|
|
34
|
+
constructor(projectRoot) {
|
|
35
|
+
this.dir = path.join(projectRoot, ".duocode", "sessions");
|
|
36
|
+
}
|
|
37
|
+
ensureDir() {
|
|
38
|
+
if (!fs.existsSync(this.dir)) {
|
|
39
|
+
fs.mkdirSync(this.dir, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
sessionPath(id) {
|
|
43
|
+
return path.join(this.dir, `${id}.json`);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Serialize current DuoSession to disk.
|
|
47
|
+
* Returns the session ID.
|
|
48
|
+
*/
|
|
49
|
+
save(session, existingId) {
|
|
50
|
+
this.ensureDir();
|
|
51
|
+
const id = existingId ?? crypto.randomUUID();
|
|
52
|
+
const turns = session.getTurns();
|
|
53
|
+
const firstPrompt = turns.length > 0 ? turns[0].prompt : "(empty)";
|
|
54
|
+
const data = {
|
|
55
|
+
id,
|
|
56
|
+
createdAt: existingId ? this.getCreatedAt(id) : Date.now(),
|
|
57
|
+
updatedAt: Date.now(),
|
|
58
|
+
turns: [...turns],
|
|
59
|
+
totalCostUsd: session.getTotalCost(),
|
|
60
|
+
mode: session.mode,
|
|
61
|
+
gradingEnabled: session.gradingEnabled,
|
|
62
|
+
autoConfirm: session.autoConfirm,
|
|
63
|
+
assignment: session.assignment,
|
|
64
|
+
pendingPlan: session.pendingPlan,
|
|
65
|
+
promptPreview: firstPrompt.length > 60
|
|
66
|
+
? firstPrompt.slice(0, 57) + "..."
|
|
67
|
+
: firstPrompt,
|
|
68
|
+
};
|
|
69
|
+
fs.writeFileSync(this.sessionPath(id), JSON.stringify(data, null, 2));
|
|
70
|
+
return id;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Non-blocking save: schedules a write-behind save.
|
|
74
|
+
* Does not block the REPL loop.
|
|
75
|
+
*/
|
|
76
|
+
saveAsync(session, existingId) {
|
|
77
|
+
const id = existingId ?? crypto.randomUUID();
|
|
78
|
+
this.pendingSave = new Promise((resolve) => {
|
|
79
|
+
// Use setImmediate to defer to next tick
|
|
80
|
+
setImmediate(() => {
|
|
81
|
+
try {
|
|
82
|
+
this.save(session, id);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Write-behind — don't crash on save failure
|
|
86
|
+
}
|
|
87
|
+
resolve();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
return id;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Wait for any pending async save to complete.
|
|
94
|
+
*/
|
|
95
|
+
async flush() {
|
|
96
|
+
if (this.pendingSave) {
|
|
97
|
+
await this.pendingSave;
|
|
98
|
+
this.pendingSave = null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Load a session from disk and restore into a DuoSession instance.
|
|
103
|
+
*/
|
|
104
|
+
resume(id) {
|
|
105
|
+
const filePath = this.sessionPath(id);
|
|
106
|
+
if (!fs.existsSync(filePath)) {
|
|
107
|
+
throw new Error(`Session not found: ${id}`);
|
|
108
|
+
}
|
|
109
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
110
|
+
const data = JSON.parse(raw);
|
|
111
|
+
const session = new DuoSession();
|
|
112
|
+
// Restore turns
|
|
113
|
+
for (const turn of data.turns) {
|
|
114
|
+
session.addTurn(turn);
|
|
115
|
+
}
|
|
116
|
+
// Restore mode and toggles
|
|
117
|
+
session.setMode(data.mode);
|
|
118
|
+
if (data.gradingEnabled !== session.gradingEnabled) {
|
|
119
|
+
session.toggleGrading();
|
|
120
|
+
}
|
|
121
|
+
if (data.autoConfirm !== session.autoConfirm) {
|
|
122
|
+
session.toggleAutoConfirm();
|
|
123
|
+
}
|
|
124
|
+
// Restore assignment
|
|
125
|
+
session.assignment = data.assignment;
|
|
126
|
+
// Restore pending plan
|
|
127
|
+
if (data.pendingPlan) {
|
|
128
|
+
session.storePlan(data.pendingPlan);
|
|
129
|
+
}
|
|
130
|
+
return { session, data };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* List saved sessions, sorted by most recent first.
|
|
134
|
+
*/
|
|
135
|
+
list() {
|
|
136
|
+
if (!fs.existsSync(this.dir))
|
|
137
|
+
return [];
|
|
138
|
+
const files = fs.readdirSync(this.dir).filter((f) => f.endsWith(".json"));
|
|
139
|
+
const sessions = [];
|
|
140
|
+
for (const file of files) {
|
|
141
|
+
try {
|
|
142
|
+
const raw = fs.readFileSync(path.join(this.dir, file), "utf-8");
|
|
143
|
+
sessions.push(JSON.parse(raw));
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// Skip corrupted files
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Delete a saved session.
|
|
153
|
+
*/
|
|
154
|
+
delete(id) {
|
|
155
|
+
const filePath = this.sessionPath(id);
|
|
156
|
+
if (fs.existsSync(filePath)) {
|
|
157
|
+
fs.unlinkSync(filePath);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
getCreatedAt(id) {
|
|
163
|
+
const filePath = this.sessionPath(id);
|
|
164
|
+
if (fs.existsSync(filePath)) {
|
|
165
|
+
try {
|
|
166
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
167
|
+
const data = JSON.parse(raw);
|
|
168
|
+
return data.createdAt;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Fallback
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return Date.now();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=session_store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session_store.js","sourceRoot":"","sources":["../../src/duo/session_store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface WorktreeEntry {
|
|
2
|
+
path: string;
|
|
3
|
+
head: string;
|
|
4
|
+
branch: string | null;
|
|
5
|
+
bare: boolean;
|
|
6
|
+
detached: boolean;
|
|
7
|
+
locked: boolean;
|
|
8
|
+
prunable: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function ensureGitRepository(repoPath: string): void;
|
|
11
|
+
export declare function sanitizeBranchName(raw: string): string;
|
|
12
|
+
export declare function createWorktree(params: {
|
|
13
|
+
repoPath: string;
|
|
14
|
+
worktreePath: string;
|
|
15
|
+
branchName: string;
|
|
16
|
+
baseRef?: string;
|
|
17
|
+
}): void;
|
|
18
|
+
export declare function removeWorktree(repoPath: string, worktreePath: string, force?: boolean): void;
|
|
19
|
+
export declare function pruneWorktrees(repoPath: string): void;
|
|
20
|
+
export declare function listWorktrees(repoPath: string): WorktreeEntry[];
|
|
21
|
+
export declare function findWorktreeByBranch(repoPath: string, branchName: string): WorktreeEntry | null;
|