ralph-cli-sandboxed 0.6.2 → 0.6.3
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/commands/branch.js +113 -78
- package/dist/commands/chat.js +90 -30
- package/dist/commands/docker.js +172 -0
- package/dist/commands/fix-prd.js +2 -2
- package/dist/commands/help.js +2 -2
- package/dist/commands/prd.js +22 -2
- package/dist/commands/progress.js +2 -1
- package/dist/commands/run.js +17 -0
- package/dist/utils/prd-validator.d.ts +5 -0
- package/dist/utils/prd-validator.js +75 -6
- package/dist/utils/vcs-git.d.ts +36 -0
- package/dist/utils/vcs-git.js +193 -0
- package/dist/utils/vcs-jj.d.ts +36 -0
- package/dist/utils/vcs-jj.js +214 -0
- package/dist/utils/vcs.d.ts +85 -0
- package/dist/utils/vcs.js +56 -0
- package/docs/PRD-GENERATOR.md +2 -0
- package/package.json +1 -1
package/dist/commands/help.js
CHANGED
|
@@ -92,7 +92,7 @@ BRANCH SUBCOMMANDS:
|
|
|
92
92
|
branch list List all branches and their status
|
|
93
93
|
branch merge <name> Merge a branch worktree into the base branch
|
|
94
94
|
branch delete <name> Delete a branch and remove its worktree
|
|
95
|
-
branch pr <name> Create a
|
|
95
|
+
branch pr <name> Create a pull request for a branch using gh CLI
|
|
96
96
|
|
|
97
97
|
PROGRESS SUBCOMMANDS:
|
|
98
98
|
progress summarize Add a PRD entry to summarize and compact progress.txt
|
|
@@ -149,7 +149,7 @@ EXAMPLES:
|
|
|
149
149
|
ralph branch list # List all branches and their PRD status
|
|
150
150
|
ralph branch merge feat/login # Merge feat/login branch into base branch
|
|
151
151
|
ralph branch delete feat/old # Delete branch and its worktree
|
|
152
|
-
ralph branch pr feat/login #
|
|
152
|
+
ralph branch pr feat/login # Create a GitHub PR for feat/login
|
|
153
153
|
ralph progress summarize # Add PRD entry to summarize progress.txt
|
|
154
154
|
|
|
155
155
|
CONFIGURATION:
|
package/dist/commands/prd.js
CHANGED
|
@@ -5,6 +5,7 @@ import { getRalphDir, getPrdFiles } from "../utils/config.js";
|
|
|
5
5
|
import { convert as prdConvert } from "./prd-convert.js";
|
|
6
6
|
import { DEFAULT_PRD_YAML } from "../templates/prompts.js";
|
|
7
7
|
import YAML from "yaml";
|
|
8
|
+
import { robustYamlParse } from "../utils/prd-validator.js";
|
|
8
9
|
const PRD_FILE_JSON = "prd.json";
|
|
9
10
|
const PRD_FILE_YAML = "prd.yaml";
|
|
10
11
|
const CATEGORIES = ["ui", "feature", "bugfix", "setup", "development", "testing", "docs"];
|
|
@@ -29,7 +30,7 @@ function parsePrdFile(path) {
|
|
|
29
30
|
try {
|
|
30
31
|
let result;
|
|
31
32
|
if (ext === ".yaml" || ext === ".yml") {
|
|
32
|
-
result =
|
|
33
|
+
result = robustYamlParse(content);
|
|
33
34
|
}
|
|
34
35
|
else {
|
|
35
36
|
result = JSON.parse(content);
|
|
@@ -227,6 +228,24 @@ export function prdStatus(headOnly = false) {
|
|
|
227
228
|
Object.entries(byCategory).forEach(([cat, stats]) => {
|
|
228
229
|
console.log(` ${cat}: ${stats.pass}/${stats.total}`);
|
|
229
230
|
});
|
|
231
|
+
// By branch (only if any entries have a branch)
|
|
232
|
+
const hasBranches = prd.some((e) => e.branch);
|
|
233
|
+
if (hasBranches) {
|
|
234
|
+
const byBranch = {};
|
|
235
|
+
prd.forEach((entry) => {
|
|
236
|
+
const key = entry.branch || "(no branch)";
|
|
237
|
+
if (!byBranch[key]) {
|
|
238
|
+
byBranch[key] = { pass: 0, total: 0 };
|
|
239
|
+
}
|
|
240
|
+
byBranch[key].total++;
|
|
241
|
+
if (entry.passes)
|
|
242
|
+
byBranch[key].pass++;
|
|
243
|
+
});
|
|
244
|
+
console.log("\n By branch:");
|
|
245
|
+
Object.entries(byBranch).forEach(([br, stats]) => {
|
|
246
|
+
console.log(` ${br}: ${stats.pass}/${stats.total}`);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
230
249
|
if (passing === total) {
|
|
231
250
|
console.log("\n \x1b[32m\u2713 All requirements complete!\x1b[0m");
|
|
232
251
|
}
|
|
@@ -234,7 +253,8 @@ export function prdStatus(headOnly = false) {
|
|
|
234
253
|
const remaining = prd.filter((e) => !e.passes);
|
|
235
254
|
console.log(`\n Remaining (${remaining.length}):`);
|
|
236
255
|
remaining.forEach((entry) => {
|
|
237
|
-
|
|
256
|
+
const branchTag = entry.branch ? ` \x1b[36m(${entry.branch})\x1b[0m` : "";
|
|
257
|
+
console.log(` - [${entry.category}] ${entry.description}${branchTag}`);
|
|
238
258
|
});
|
|
239
259
|
}
|
|
240
260
|
}
|
|
@@ -3,6 +3,7 @@ import { extname, join } from "path";
|
|
|
3
3
|
import { getRalphDir, getPrdFiles } from "../utils/config.js";
|
|
4
4
|
import { DEFAULT_PRD_YAML } from "../templates/prompts.js";
|
|
5
5
|
import YAML from "yaml";
|
|
6
|
+
import { robustYamlParse } from "../utils/prd-validator.js";
|
|
6
7
|
const PRD_FILE_YAML = "prd.yaml";
|
|
7
8
|
const PRD_FILE_JSON = "prd.json";
|
|
8
9
|
function getPrdPath() {
|
|
@@ -18,7 +19,7 @@ function parsePrdFile(path) {
|
|
|
18
19
|
try {
|
|
19
20
|
let result;
|
|
20
21
|
if (ext === ".yaml" || ext === ".yml") {
|
|
21
|
-
result =
|
|
22
|
+
result = robustYamlParse(content);
|
|
22
23
|
}
|
|
23
24
|
else {
|
|
24
25
|
result = JSON.parse(content);
|
package/dist/commands/run.js
CHANGED
|
@@ -888,15 +888,32 @@ export async function run(args) {
|
|
|
888
888
|
// Default to "main"
|
|
889
889
|
}
|
|
890
890
|
}
|
|
891
|
+
// Merge items tagged with the base branch into the no-branch group,
|
|
892
|
+
// so they run in /workspace instead of creating a worktree.
|
|
893
|
+
const baseBranchItems = branchGroups.get(baseBranch);
|
|
894
|
+
if (baseBranchItems && baseBranchItems.length > 0) {
|
|
895
|
+
const noBranch = branchGroups.get("") || [];
|
|
896
|
+
branchGroups.set("", [...noBranch, ...baseBranchItems]);
|
|
897
|
+
branchGroups.delete(baseBranch);
|
|
898
|
+
}
|
|
891
899
|
// Find the first incomplete item to determine which group to process.
|
|
892
900
|
// If resuming from a previous interruption, prioritize the resumed branch.
|
|
893
901
|
let targetBranch;
|
|
894
902
|
if (resumedBranchState) {
|
|
895
903
|
targetBranch = resumedBranchState.currentBranch;
|
|
904
|
+
// If resumed branch is the base branch, treat as no-branch
|
|
905
|
+
if (targetBranch === baseBranch) {
|
|
906
|
+
targetBranch = "";
|
|
907
|
+
clearBranchState();
|
|
908
|
+
}
|
|
896
909
|
}
|
|
897
910
|
else {
|
|
898
911
|
const firstIncomplete = itemsForIteration.find((item) => !item.passes);
|
|
899
912
|
targetBranch = firstIncomplete?.branch || "";
|
|
913
|
+
// If the target matches the base branch, treat as no-branch
|
|
914
|
+
if (targetBranch === baseBranch) {
|
|
915
|
+
targetBranch = "";
|
|
916
|
+
}
|
|
900
917
|
}
|
|
901
918
|
if (targetBranch !== "" && worktreesAvailable && hasCommits) {
|
|
902
919
|
// Process this one branch group in its worktree
|
|
@@ -57,6 +57,11 @@ export declare function findLatestBackup(prdPath: string): string | null;
|
|
|
57
57
|
* @param backupPath - Absolute path to the backup file containing the corrupted PRD
|
|
58
58
|
*/
|
|
59
59
|
export declare function createTemplatePrd(backupPath?: string): PrdEntry[];
|
|
60
|
+
/**
|
|
61
|
+
* Robustly parses YAML content, applying automatic fixes for common
|
|
62
|
+
* LLM-generated YAML issues (multiline strings, embedded quotes).
|
|
63
|
+
*/
|
|
64
|
+
export declare function robustYamlParse(content: string): unknown;
|
|
60
65
|
/**
|
|
61
66
|
* Reads and parses a YAML PRD file.
|
|
62
67
|
* Attempts to fix common LLM-caused YAML issues before parsing.
|
|
@@ -425,6 +425,64 @@ export function createTemplatePrd(backupPath) {
|
|
|
425
425
|
},
|
|
426
426
|
];
|
|
427
427
|
}
|
|
428
|
+
/**
|
|
429
|
+
* Checks if a YAML plain scalar value contains embedded double-quoted segments
|
|
430
|
+
* with YAML special characters that would cause parsing issues.
|
|
431
|
+
*
|
|
432
|
+
* Example: Add "server: { port: 9999 }" to vite.config.ts
|
|
433
|
+
* The parser interprets "server: { port: 9999 }" as a double-quoted scalar,
|
|
434
|
+
* then finds unexpected text after the closing quote.
|
|
435
|
+
*/
|
|
436
|
+
function hasProblematicEmbeddedQuotes(value) {
|
|
437
|
+
// Look for "...special chars..." followed by more text
|
|
438
|
+
const regex = /"[^"]*[:{}\[\]][^"]*"/g;
|
|
439
|
+
let match;
|
|
440
|
+
while ((match = regex.exec(value)) !== null) {
|
|
441
|
+
const afterQuote = value.substring(match.index + match[0].length);
|
|
442
|
+
if (afterQuote.trim().length > 0) {
|
|
443
|
+
return true;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Fixes YAML values that contain embedded double-quoted strings with special characters.
|
|
450
|
+
*
|
|
451
|
+
* Example problematic line:
|
|
452
|
+
* - Add "server: { port: 9999 }" to vite.config.ts
|
|
453
|
+
*
|
|
454
|
+
* The YAML parser interprets "server: { port: 9999 }" as a double-quoted scalar,
|
|
455
|
+
* then chokes on the trailing text. Fix: wrap the entire value in single quotes.
|
|
456
|
+
*/
|
|
457
|
+
function fixYamlEmbeddedQuotes(yaml) {
|
|
458
|
+
const lines = yaml.split("\n");
|
|
459
|
+
const result = [];
|
|
460
|
+
for (const line of lines) {
|
|
461
|
+
// Try list item: ` - value`
|
|
462
|
+
let match = line.match(/^(\s*-\s+)(.+)$/);
|
|
463
|
+
if (!match) {
|
|
464
|
+
// Try key-value: ` key: value`
|
|
465
|
+
match = line.match(/^(\s*[a-zA-Z_][a-zA-Z0-9_]*:\s+)(.+)$/);
|
|
466
|
+
}
|
|
467
|
+
if (match) {
|
|
468
|
+
const prefix = match[1];
|
|
469
|
+
const value = match[2];
|
|
470
|
+
// Skip if already quoted
|
|
471
|
+
if (value.startsWith('"') || value.startsWith("'") || value.startsWith("|") || value.startsWith(">")) {
|
|
472
|
+
result.push(line);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (hasProblematicEmbeddedQuotes(value)) {
|
|
476
|
+
// Wrap in single quotes, escaping existing single quotes by doubling
|
|
477
|
+
const escaped = value.replace(/'/g, "''");
|
|
478
|
+
result.push(`${prefix}'${escaped}'`);
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
result.push(line);
|
|
483
|
+
}
|
|
484
|
+
return result.join("\n");
|
|
485
|
+
}
|
|
428
486
|
/**
|
|
429
487
|
* Fixes common YAML issues caused by LLMs writing multi-line strings incorrectly.
|
|
430
488
|
* The main issue is list items that span multiple lines without proper quoting:
|
|
@@ -492,6 +550,19 @@ function fixYamlMultilineStrings(yaml) {
|
|
|
492
550
|
}
|
|
493
551
|
return result.join("\n");
|
|
494
552
|
}
|
|
553
|
+
/**
|
|
554
|
+
* Robustly parses YAML content, applying automatic fixes for common
|
|
555
|
+
* LLM-generated YAML issues (multiline strings, embedded quotes).
|
|
556
|
+
*/
|
|
557
|
+
export function robustYamlParse(content) {
|
|
558
|
+
try {
|
|
559
|
+
return YAML.parse(content);
|
|
560
|
+
}
|
|
561
|
+
catch {
|
|
562
|
+
const fixed = fixYamlEmbeddedQuotes(fixYamlMultilineStrings(content));
|
|
563
|
+
return YAML.parse(fixed);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
495
566
|
/**
|
|
496
567
|
* Common wrapper keys that LLMs use to wrap PRD arrays.
|
|
497
568
|
* If parsed content is an object with one of these keys containing an array,
|
|
@@ -524,14 +595,13 @@ function unwrapPrdContent(content) {
|
|
|
524
595
|
export function readYamlPrdFile(prdPath) {
|
|
525
596
|
try {
|
|
526
597
|
const raw = readFileSync(prdPath, "utf-8");
|
|
527
|
-
// Try parsing as-is first
|
|
598
|
+
// Try parsing as-is first, then with fixes for common LLM issues
|
|
528
599
|
let content;
|
|
529
600
|
try {
|
|
530
601
|
content = YAML.parse(raw);
|
|
531
602
|
}
|
|
532
603
|
catch {
|
|
533
|
-
|
|
534
|
-
const fixed = fixYamlMultilineStrings(raw);
|
|
604
|
+
const fixed = fixYamlEmbeddedQuotes(fixYamlMultilineStrings(raw));
|
|
535
605
|
content = YAML.parse(fixed);
|
|
536
606
|
}
|
|
537
607
|
// Unwrap if wrapped in common object structure
|
|
@@ -556,13 +626,12 @@ export function readPrdFile(prdPath) {
|
|
|
556
626
|
// Parse based on file extension
|
|
557
627
|
let content;
|
|
558
628
|
if (ext === ".yaml" || ext === ".yml") {
|
|
559
|
-
// Try parsing as-is first
|
|
629
|
+
// Try parsing as-is first, then with fixes for common LLM issues
|
|
560
630
|
try {
|
|
561
631
|
content = YAML.parse(raw);
|
|
562
632
|
}
|
|
563
633
|
catch {
|
|
564
|
-
|
|
565
|
-
const fixed = fixYamlMultilineStrings(raw);
|
|
634
|
+
const fixed = fixYamlEmbeddedQuotes(fixYamlMultilineStrings(raw));
|
|
566
635
|
content = YAML.parse(fixed);
|
|
567
636
|
// Unwrap if wrapped in common object structure
|
|
568
637
|
content = unwrapPrdContent(content);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitProvider — implements VcsProvider using git commands.
|
|
3
|
+
*/
|
|
4
|
+
import type { VcsProvider, VcsExecOptions, MergeResult, DiffPattern } from "./vcs.js";
|
|
5
|
+
export declare class GitProvider implements VcsProvider {
|
|
6
|
+
readonly type: "git";
|
|
7
|
+
hasCommits(opts?: VcsExecOptions): boolean;
|
|
8
|
+
getCurrentBranch(opts?: VcsExecOptions): string;
|
|
9
|
+
branchExists(name: string, opts?: VcsExecOptions): boolean;
|
|
10
|
+
status(opts?: VcsExecOptions): string;
|
|
11
|
+
createBranch(name: string, opts?: VcsExecOptions): void;
|
|
12
|
+
deleteBranch(name: string, opts?: VcsExecOptions): void;
|
|
13
|
+
diff(opts?: VcsExecOptions): string;
|
|
14
|
+
diffStaged(opts?: VcsExecOptions): string;
|
|
15
|
+
diffAll(opts?: VcsExecOptions): string;
|
|
16
|
+
showCommit(revisionOffset: number, opts?: VcsExecOptions): string;
|
|
17
|
+
commit(message: string, opts?: VcsExecOptions): void;
|
|
18
|
+
merge(branchName: string, opts?: VcsExecOptions): MergeResult;
|
|
19
|
+
mergeAbort(opts?: VcsExecOptions): void;
|
|
20
|
+
getConflictingFiles(opts?: VcsExecOptions): string[];
|
|
21
|
+
getRemote(opts?: VcsExecOptions): string;
|
|
22
|
+
hasUpstream(branchName: string, opts?: VcsExecOptions): boolean;
|
|
23
|
+
push(remote: string, branchName: string, opts?: VcsExecOptions): void;
|
|
24
|
+
logBetween(base: string, head: string, opts?: VcsExecOptions): string;
|
|
25
|
+
createWorkspace(path: string, branch: string, createBranch: boolean, opts?: VcsExecOptions): void;
|
|
26
|
+
removeWorkspace(path: string, force?: boolean, opts?: VcsExecOptions): void;
|
|
27
|
+
getDockerConfigCommands(dockerConfig?: {
|
|
28
|
+
git?: {
|
|
29
|
+
name?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
};
|
|
32
|
+
}): string[];
|
|
33
|
+
getDockerInstallSnippet(): string;
|
|
34
|
+
getCommitInstruction(): string;
|
|
35
|
+
getDiffPatterns(): DiffPattern[];
|
|
36
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitProvider — implements VcsProvider using git commands.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
export class GitProvider {
|
|
6
|
+
type = "git";
|
|
7
|
+
hasCommits(opts) {
|
|
8
|
+
try {
|
|
9
|
+
execSync("git rev-parse HEAD", { stdio: "pipe", ...opts });
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
getCurrentBranch(opts) {
|
|
17
|
+
try {
|
|
18
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
19
|
+
encoding: "utf-8",
|
|
20
|
+
...opts,
|
|
21
|
+
}).trim();
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return "main";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
branchExists(name, opts) {
|
|
28
|
+
try {
|
|
29
|
+
execSync(`git rev-parse --verify "${name}"`, { stdio: "pipe", ...opts });
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
status(opts) {
|
|
37
|
+
return execSync("git status --porcelain", {
|
|
38
|
+
encoding: "utf-8",
|
|
39
|
+
...opts,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
createBranch(name, opts) {
|
|
43
|
+
execSync(`git checkout -b "${name}"`, { stdio: "pipe", ...opts });
|
|
44
|
+
}
|
|
45
|
+
deleteBranch(name, opts) {
|
|
46
|
+
execSync(`git branch -D "${name}"`, { stdio: "pipe", ...opts });
|
|
47
|
+
}
|
|
48
|
+
diff(opts) {
|
|
49
|
+
return execSync("git diff", {
|
|
50
|
+
encoding: "utf-8",
|
|
51
|
+
maxBuffer: 1024 * 1024,
|
|
52
|
+
timeout: 10000,
|
|
53
|
+
...opts,
|
|
54
|
+
}).trim();
|
|
55
|
+
}
|
|
56
|
+
diffStaged(opts) {
|
|
57
|
+
return execSync("git diff --cached", {
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
maxBuffer: 1024 * 1024,
|
|
60
|
+
timeout: 10000,
|
|
61
|
+
...opts,
|
|
62
|
+
}).trim();
|
|
63
|
+
}
|
|
64
|
+
diffAll(opts) {
|
|
65
|
+
return execSync("git diff HEAD", {
|
|
66
|
+
encoding: "utf-8",
|
|
67
|
+
maxBuffer: 1024 * 1024,
|
|
68
|
+
timeout: 10000,
|
|
69
|
+
...opts,
|
|
70
|
+
}).trim();
|
|
71
|
+
}
|
|
72
|
+
showCommit(revisionOffset, opts) {
|
|
73
|
+
const ref = revisionOffset === 0 ? "HEAD" : `HEAD~${revisionOffset}`;
|
|
74
|
+
return execSync(`git show ${ref} --stat --patch`, {
|
|
75
|
+
encoding: "utf-8",
|
|
76
|
+
maxBuffer: 1024 * 1024,
|
|
77
|
+
timeout: 10000,
|
|
78
|
+
...opts,
|
|
79
|
+
}).trim();
|
|
80
|
+
}
|
|
81
|
+
commit(message, opts) {
|
|
82
|
+
execSync(`git add -A && git commit -m "${message.replace(/"/g, '\\"')}"`, {
|
|
83
|
+
stdio: "pipe",
|
|
84
|
+
...opts,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
merge(branchName, opts) {
|
|
88
|
+
try {
|
|
89
|
+
execSync(`git merge "${branchName}" --no-edit`, { stdio: "pipe", ...opts });
|
|
90
|
+
return { success: true };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
const conflictingFiles = this.getConflictingFiles(opts);
|
|
94
|
+
return { success: false, conflictingFiles };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
mergeAbort(opts) {
|
|
98
|
+
try {
|
|
99
|
+
execSync("git merge --abort", { stdio: "pipe", ...opts });
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
// Ignore if nothing to abort
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
getConflictingFiles(opts) {
|
|
106
|
+
try {
|
|
107
|
+
const status = execSync("git status --porcelain", { encoding: "utf-8", ...opts });
|
|
108
|
+
return status
|
|
109
|
+
.split("\n")
|
|
110
|
+
.filter((line) => line.startsWith("UU") ||
|
|
111
|
+
line.startsWith("AA") ||
|
|
112
|
+
line.startsWith("DD") ||
|
|
113
|
+
line.startsWith("AU") ||
|
|
114
|
+
line.startsWith("UA") ||
|
|
115
|
+
line.startsWith("DU") ||
|
|
116
|
+
line.startsWith("UD"))
|
|
117
|
+
.map((line) => line.substring(3).trim());
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return [];
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
getRemote(opts) {
|
|
124
|
+
return execSync("git remote", { encoding: "utf-8", ...opts }).trim().split("\n")[0];
|
|
125
|
+
}
|
|
126
|
+
hasUpstream(branchName, opts) {
|
|
127
|
+
try {
|
|
128
|
+
execSync(`git rev-parse --abbrev-ref "${branchName}@{upstream}"`, {
|
|
129
|
+
stdio: "pipe",
|
|
130
|
+
...opts,
|
|
131
|
+
});
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
push(remote, branchName, opts) {
|
|
139
|
+
execSync(`git push -u "${remote}" "${branchName}"`, { stdio: "pipe", ...opts });
|
|
140
|
+
}
|
|
141
|
+
logBetween(base, head, opts) {
|
|
142
|
+
return execSync(`git log "${base}..${head}" --oneline --no-decorate`, {
|
|
143
|
+
encoding: "utf-8",
|
|
144
|
+
...opts,
|
|
145
|
+
}).trim();
|
|
146
|
+
}
|
|
147
|
+
createWorkspace(path, branch, createBranch, opts) {
|
|
148
|
+
if (createBranch) {
|
|
149
|
+
execSync(`git worktree add -b "${branch}" "${path}"`, { stdio: "pipe", ...opts });
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
execSync(`git worktree add "${path}" "${branch}"`, { stdio: "pipe", ...opts });
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
removeWorkspace(path, force, opts) {
|
|
156
|
+
const forceFlag = force ? " --force" : "";
|
|
157
|
+
execSync(`git worktree remove "${path}"${forceFlag}`, { stdio: "pipe", ...opts });
|
|
158
|
+
}
|
|
159
|
+
getDockerConfigCommands(dockerConfig) {
|
|
160
|
+
const commands = [`git config --global init.defaultBranch main`];
|
|
161
|
+
if (dockerConfig?.git?.name) {
|
|
162
|
+
commands.push(`git config --global user.name "${dockerConfig.git.name}"`);
|
|
163
|
+
}
|
|
164
|
+
if (dockerConfig?.git?.email) {
|
|
165
|
+
commands.push(`git config --global user.email "${dockerConfig.git.email}"`);
|
|
166
|
+
}
|
|
167
|
+
return commands;
|
|
168
|
+
}
|
|
169
|
+
getDockerInstallSnippet() {
|
|
170
|
+
// Git is already installed in the base Docker image
|
|
171
|
+
return "";
|
|
172
|
+
}
|
|
173
|
+
getCommitInstruction() {
|
|
174
|
+
return "Create a git commit with a descriptive message for this feature";
|
|
175
|
+
}
|
|
176
|
+
getDiffPatterns() {
|
|
177
|
+
return [
|
|
178
|
+
{ pattern: /^(diff|changes)$/i, command: "git diff", description: "unstaged changes" },
|
|
179
|
+
{ pattern: /^staged$/i, command: "git diff --cached", description: "staged changes" },
|
|
180
|
+
{
|
|
181
|
+
pattern: /^(last|last\s*commit)$/i,
|
|
182
|
+
command: "git show HEAD --stat --patch",
|
|
183
|
+
description: "last commit",
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
pattern: /^HEAD~(\d+)$/i,
|
|
187
|
+
command: "git show HEAD~$1 --stat --patch",
|
|
188
|
+
description: "commit",
|
|
189
|
+
},
|
|
190
|
+
{ pattern: /^all$/i, command: "git diff HEAD", description: "all uncommitted changes" },
|
|
191
|
+
];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JjProvider — implements VcsProvider using Jujutsu (jj) commands.
|
|
3
|
+
*/
|
|
4
|
+
import type { VcsProvider, VcsExecOptions, MergeResult, DiffPattern } from "./vcs.js";
|
|
5
|
+
export declare class JjProvider implements VcsProvider {
|
|
6
|
+
readonly type: "jj";
|
|
7
|
+
hasCommits(opts?: VcsExecOptions): boolean;
|
|
8
|
+
getCurrentBranch(opts?: VcsExecOptions): string;
|
|
9
|
+
branchExists(name: string, opts?: VcsExecOptions): boolean;
|
|
10
|
+
status(opts?: VcsExecOptions): string;
|
|
11
|
+
createBranch(name: string, opts?: VcsExecOptions): void;
|
|
12
|
+
deleteBranch(name: string, opts?: VcsExecOptions): void;
|
|
13
|
+
diff(opts?: VcsExecOptions): string;
|
|
14
|
+
diffStaged(_opts?: VcsExecOptions): string;
|
|
15
|
+
diffAll(opts?: VcsExecOptions): string;
|
|
16
|
+
showCommit(revisionOffset: number, opts?: VcsExecOptions): string;
|
|
17
|
+
commit(message: string, opts?: VcsExecOptions): void;
|
|
18
|
+
merge(branchName: string, opts?: VcsExecOptions): MergeResult;
|
|
19
|
+
mergeAbort(opts?: VcsExecOptions): void;
|
|
20
|
+
getConflictingFiles(opts?: VcsExecOptions): string[];
|
|
21
|
+
getRemote(opts?: VcsExecOptions): string;
|
|
22
|
+
hasUpstream(branchName: string, opts?: VcsExecOptions): boolean;
|
|
23
|
+
push(remote: string, branchName: string, opts?: VcsExecOptions): void;
|
|
24
|
+
logBetween(base: string, head: string, opts?: VcsExecOptions): string;
|
|
25
|
+
createWorkspace(path: string, _branch: string, _createBranch: boolean, opts?: VcsExecOptions): void;
|
|
26
|
+
removeWorkspace(path: string, _force?: boolean, opts?: VcsExecOptions): void;
|
|
27
|
+
getDockerConfigCommands(dockerConfig?: {
|
|
28
|
+
git?: {
|
|
29
|
+
name?: string;
|
|
30
|
+
email?: string;
|
|
31
|
+
};
|
|
32
|
+
}): string[];
|
|
33
|
+
getDockerInstallSnippet(): string;
|
|
34
|
+
getCommitInstruction(): string;
|
|
35
|
+
getDiffPatterns(): DiffPattern[];
|
|
36
|
+
}
|