@vibe-x/agent-better-checkpoint 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.mjs +153 -6
- package/package.json +8 -2
- package/platform/unix/alloc_patch.sh +122 -0
- package/platform/unix/check_uncommitted.sh +22 -5
- package/platform/unix/checkpoint.sh +99 -13
- package/platform/win/alloc_patch.ps1 +142 -0
- package/platform/win/check_uncommitted.ps1 +18 -3
- package/platform/win/checkpoint.ps1 +96 -14
- package/scripts/release-check.mjs +143 -0
- package/skill/SKILL.md +35 -13
package/bin/cli.mjs
CHANGED
|
@@ -38,7 +38,7 @@ const CONFIG_TEMPLATE = join(PLATFORM_DIR, 'config.template.yml');
|
|
|
38
38
|
// ============================================================
|
|
39
39
|
|
|
40
40
|
function parseArgs(argv) {
|
|
41
|
-
const args = { platform: null, uninstall: false, target: null };
|
|
41
|
+
const args = { platform: null, uninstall: false, target: null, activate: false };
|
|
42
42
|
for (let i = 2; i < argv.length; i++) {
|
|
43
43
|
switch (argv[i]) {
|
|
44
44
|
case '--platform':
|
|
@@ -58,6 +58,9 @@ function parseArgs(argv) {
|
|
|
58
58
|
case '--uninstall':
|
|
59
59
|
args.uninstall = true;
|
|
60
60
|
break;
|
|
61
|
+
case '--activate':
|
|
62
|
+
args.activate = true;
|
|
63
|
+
break;
|
|
61
64
|
case '--help':
|
|
62
65
|
case '-h':
|
|
63
66
|
printHelp();
|
|
@@ -82,6 +85,7 @@ Usage:
|
|
|
82
85
|
Options:
|
|
83
86
|
--platform <cursor|claude> Target AI platform (auto-detected if omitted)
|
|
84
87
|
--target <path> Project-only install (no global). Use . for cwd
|
|
88
|
+
--activate Activate checkpoint rules in current project (AGENTS.md only)
|
|
85
89
|
--uninstall Remove installed files and hook registrations
|
|
86
90
|
-h, --help Show this help message
|
|
87
91
|
`);
|
|
@@ -229,11 +233,14 @@ function installScripts(osType) {
|
|
|
229
233
|
|
|
230
234
|
// 双端脚本都安装,方便跨平台使用
|
|
231
235
|
copyFileSafe(join(PLATFORM_DIR, 'unix', 'checkpoint.sh'), join(scriptsDir, 'checkpoint.sh'));
|
|
236
|
+
copyFileSafe(join(PLATFORM_DIR, 'unix', 'alloc_patch.sh'), join(scriptsDir, 'alloc_patch.sh'));
|
|
232
237
|
copyFileSafe(join(PLATFORM_DIR, 'unix', 'check_uncommitted.sh'), join(hooksDir, 'check_uncommitted.sh'));
|
|
233
238
|
setExecutable(join(scriptsDir, 'checkpoint.sh'));
|
|
239
|
+
setExecutable(join(scriptsDir, 'alloc_patch.sh'));
|
|
234
240
|
setExecutable(join(hooksDir, 'check_uncommitted.sh'));
|
|
235
241
|
|
|
236
242
|
copyFileSafe(join(PLATFORM_DIR, 'win', 'checkpoint.ps1'), join(scriptsDir, 'checkpoint.ps1'));
|
|
243
|
+
copyFileSafe(join(PLATFORM_DIR, 'win', 'alloc_patch.ps1'), join(scriptsDir, 'alloc_patch.ps1'));
|
|
237
244
|
copyFileSafe(join(PLATFORM_DIR, 'win', 'check_uncommitted.ps1'), join(hooksDir, 'check_uncommitted.ps1'));
|
|
238
245
|
|
|
239
246
|
console.log(` Scripts → ${scriptsDir}/`);
|
|
@@ -301,12 +308,18 @@ function registerCursorHook(osType) {
|
|
|
301
308
|
function installProjectOnly(targetDir, aiPlatform, osType) {
|
|
302
309
|
const root = resolve(targetDir);
|
|
303
310
|
|
|
304
|
-
// .vibe-x/agent-better-checkpoint: checkpoint 脚本 + config
|
|
311
|
+
// .vibe-x/agent-better-checkpoint: checkpoint 脚本 + helper + config
|
|
305
312
|
const vibeXBase = join(root, '.vibe-x', 'agent-better-checkpoint');
|
|
306
313
|
ensureDir(vibeXBase);
|
|
307
314
|
copyFileSafe(join(PLATFORM_DIR, 'unix', 'checkpoint.sh'), join(vibeXBase, 'checkpoint.sh'));
|
|
315
|
+
copyFileSafe(join(PLATFORM_DIR, 'unix', 'alloc_patch.sh'), join(vibeXBase, 'alloc_patch.sh'));
|
|
316
|
+
copyFileSafe(join(PLATFORM_DIR, 'unix', 'check_uncommitted.sh'), join(vibeXBase, 'check_uncommitted.sh'));
|
|
308
317
|
copyFileSafe(join(PLATFORM_DIR, 'win', 'checkpoint.ps1'), join(vibeXBase, 'checkpoint.ps1'));
|
|
318
|
+
copyFileSafe(join(PLATFORM_DIR, 'win', 'alloc_patch.ps1'), join(vibeXBase, 'alloc_patch.ps1'));
|
|
319
|
+
copyFileSafe(join(PLATFORM_DIR, 'win', 'check_uncommitted.ps1'), join(vibeXBase, 'check_uncommitted.ps1'));
|
|
309
320
|
setExecutable(join(vibeXBase, 'checkpoint.sh'));
|
|
321
|
+
setExecutable(join(vibeXBase, 'alloc_patch.sh'));
|
|
322
|
+
setExecutable(join(vibeXBase, 'check_uncommitted.sh'));
|
|
310
323
|
const configDest = join(vibeXBase, 'config.yml');
|
|
311
324
|
if (!existsSync(configDest) && existsSync(CONFIG_TEMPLATE)) {
|
|
312
325
|
copyFileSafe(CONFIG_TEMPLATE, configDest);
|
|
@@ -356,9 +369,22 @@ function uninstallProjectOnly(targetDir, aiPlatform) {
|
|
|
356
369
|
const vibeXBase = join(root, '.vibe-x', 'agent-better-checkpoint');
|
|
357
370
|
const skillRoot = aiPlatform === 'cursor' ? '.cursor' : '.claude';
|
|
358
371
|
const skillDir = join(root, skillRoot, 'skills', SKILL_NAME);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
372
|
+
const checkpointShPath = join(vibeXBase, 'checkpoint.sh');
|
|
373
|
+
const checkpointPs1Path = join(vibeXBase, 'checkpoint.ps1');
|
|
374
|
+
const allocPatchShPath = join(vibeXBase, 'alloc_patch.sh');
|
|
375
|
+
const allocPatchPs1Path = join(vibeXBase, 'alloc_patch.ps1');
|
|
376
|
+
const checkUncommittedShPath = join(vibeXBase, 'check_uncommitted.sh');
|
|
377
|
+
const checkUncommittedPs1Path = join(vibeXBase, 'check_uncommitted.ps1');
|
|
378
|
+
for (const filePath of [
|
|
379
|
+
checkpointShPath,
|
|
380
|
+
checkpointPs1Path,
|
|
381
|
+
allocPatchShPath,
|
|
382
|
+
allocPatchPs1Path,
|
|
383
|
+
checkUncommittedShPath,
|
|
384
|
+
checkUncommittedPs1Path,
|
|
385
|
+
join(vibeXBase, 'config.yml'),
|
|
386
|
+
]) {
|
|
387
|
+
if (existsSync(filePath)) rmSync(filePath, { force: true });
|
|
362
388
|
}
|
|
363
389
|
|
|
364
390
|
if (existsSync(skillDir)) {
|
|
@@ -503,6 +529,123 @@ function unregisterClaudeHook() {
|
|
|
503
529
|
console.log(` Cleaned config: ${settingsPath}`);
|
|
504
530
|
}
|
|
505
531
|
|
|
532
|
+
// ============================================================
|
|
533
|
+
// Activate logic (AGENTS.md only, with installation check)
|
|
534
|
+
// ============================================================
|
|
535
|
+
|
|
536
|
+
const SUPPORTED_PLATFORMS = ['cursor', 'claude'];
|
|
537
|
+
|
|
538
|
+
function checkGlobalInstallation() {
|
|
539
|
+
const home = homedir();
|
|
540
|
+
const results = {
|
|
541
|
+
hasScripts: existsSync(join(INSTALL_BASE, 'scripts', 'checkpoint.sh')) ||
|
|
542
|
+
existsSync(join(INSTALL_BASE, 'scripts', 'checkpoint.ps1')),
|
|
543
|
+
platforms: {}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
for (const p of SUPPORTED_PLATFORMS) {
|
|
547
|
+
const skillDir = p === 'cursor' ? '.cursor' : '.claude';
|
|
548
|
+
results.platforms[p] = {
|
|
549
|
+
hasSkill: existsSync(join(home, skillDir, 'skills', SKILL_NAME, 'SKILL.md'))
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
results.hasAnySkill = SUPPORTED_PLATFORMS.some(p => results.platforms[p].hasSkill);
|
|
554
|
+
results.isFullyInstalled = results.hasScripts && results.hasAnySkill;
|
|
555
|
+
return results;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function checkProjectInstallation(targetDir) {
|
|
559
|
+
const root = resolve(targetDir);
|
|
560
|
+
const results = {
|
|
561
|
+
hasScripts: existsSync(join(root, '.vibe-x', 'agent-better-checkpoint', 'checkpoint.sh')) ||
|
|
562
|
+
existsSync(join(root, '.vibe-x', 'agent-better-checkpoint', 'checkpoint.ps1')),
|
|
563
|
+
platforms: {}
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
for (const p of SUPPORTED_PLATFORMS) {
|
|
567
|
+
const skillDir = p === 'cursor' ? '.cursor' : '.claude';
|
|
568
|
+
results.platforms[p] = {
|
|
569
|
+
hasSkill: existsSync(join(root, skillDir, 'skills', SKILL_NAME, 'SKILL.md'))
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
results.hasAnySkill = SUPPORTED_PLATFORMS.some(p => results.platforms[p].hasSkill);
|
|
574
|
+
results.isFullyInstalled = results.hasScripts && results.hasAnySkill;
|
|
575
|
+
return results;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
function activateProject(targetDir) {
|
|
579
|
+
const root = resolve(targetDir);
|
|
580
|
+
|
|
581
|
+
// Check if already has AGENTS.md block
|
|
582
|
+
const agentsMdPath = join(root, 'AGENTS.md');
|
|
583
|
+
if (existsSync(agentsMdPath)) {
|
|
584
|
+
const content = readFileSync(agentsMdPath, 'utf-8');
|
|
585
|
+
if (content.includes(AGENTS_BLOCK_START)) {
|
|
586
|
+
console.log(`\n⚠️ AGENTS.md already contains checkpoint rules. Nothing to do.`);
|
|
587
|
+
return;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Step 1: Check project-level installation (all platforms)
|
|
592
|
+
const projectStatus = checkProjectInstallation(root);
|
|
593
|
+
if (projectStatus.isFullyInstalled) {
|
|
594
|
+
// Project has both scripts and skill for at least one platform
|
|
595
|
+
console.log(`\n[Activate] Adding checkpoint rules to ${root}...`);
|
|
596
|
+
injectAgentsMdBlock(root);
|
|
597
|
+
console.log(`\n✅ Activation complete!`);
|
|
598
|
+
console.log(`\nThe AI agent will now follow checkpoint commit rules in this project.`);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Step 2: Check global installation (all platforms)
|
|
603
|
+
const globalStatus = checkGlobalInstallation();
|
|
604
|
+
if (globalStatus.isFullyInstalled) {
|
|
605
|
+
// Global has both scripts and skill for at least one platform
|
|
606
|
+
console.log(`\n[Activate] Adding checkpoint rules to ${root}...`);
|
|
607
|
+
injectAgentsMdBlock(root);
|
|
608
|
+
console.log(`\n✅ Activation complete!`);
|
|
609
|
+
console.log(`\nThe AI agent will now follow checkpoint commit rules in this project.`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Step 3: Neither project nor global is fully installed - provide detailed diagnosis
|
|
614
|
+
const hasAnyProjectSkill = projectStatus.hasAnySkill;
|
|
615
|
+
const hasAnyGlobalSkill = globalStatus.hasAnySkill;
|
|
616
|
+
const hasAnyProjectScripts = projectStatus.hasScripts;
|
|
617
|
+
const hasAnyGlobalScripts = globalStatus.hasScripts;
|
|
618
|
+
|
|
619
|
+
const hasAnySkill = hasAnyProjectSkill || hasAnyGlobalSkill;
|
|
620
|
+
const hasAnyScripts = hasAnyProjectScripts || hasAnyGlobalScripts;
|
|
621
|
+
|
|
622
|
+
if (!hasAnySkill && !hasAnyScripts) {
|
|
623
|
+
console.log(`\n⚠️ No agent-better-checkpoint installation detected.`);
|
|
624
|
+
console.log(`\nChecked locations:`);
|
|
625
|
+
console.log(` Project: ${root}`);
|
|
626
|
+
console.log(` Global: ${INSTALL_BASE}`);
|
|
627
|
+
console.log(`\nPlease install first:`);
|
|
628
|
+
console.log(` Global: npx @vibe-x/agent-better-checkpoint`);
|
|
629
|
+
console.log(` Project-only: npx @vibe-x/agent-better-checkpoint --target . --platform cursor|claude`);
|
|
630
|
+
console.log(`\nThen run --activate again.`);
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
if (!hasAnySkill) {
|
|
635
|
+
console.log(`\n⚠️ Checkpoint scripts found, but skill (SKILL.md) is missing.`);
|
|
636
|
+
console.log(`\nThe AI agent needs the skill to know how to commit. Please run:`);
|
|
637
|
+
console.log(` npx @vibe-x/agent-better-checkpoint --platform cursor|claude`);
|
|
638
|
+
process.exit(1);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
if (!hasAnyScripts) {
|
|
642
|
+
console.log(`\n⚠️ Skill found, but checkpoint scripts are missing.`);
|
|
643
|
+
console.log(`\nPlease reinstall to get the scripts:`);
|
|
644
|
+
console.log(` npx @vibe-x/agent-better-checkpoint`);
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
506
649
|
// ============================================================
|
|
507
650
|
// Main entry
|
|
508
651
|
// ============================================================
|
|
@@ -513,7 +656,7 @@ function main() {
|
|
|
513
656
|
const aiPlatform = args.platform || detectAIPlatform();
|
|
514
657
|
const projectTargetDir = args.target ? resolve(args.target) : null;
|
|
515
658
|
|
|
516
|
-
if (!aiPlatform && !projectTargetDir && !args.uninstall) {
|
|
659
|
+
if (!aiPlatform && !projectTargetDir && !args.uninstall && !args.activate) {
|
|
517
660
|
console.error(
|
|
518
661
|
'Error: could not detect AI platform.\n' +
|
|
519
662
|
'Please specify: npx @vibe-x/agent-better-checkpoint --platform cursor|claude'
|
|
@@ -556,6 +699,10 @@ function main() {
|
|
|
556
699
|
if (platforms.length === 0) console.log('\nNo global installation found.');
|
|
557
700
|
}
|
|
558
701
|
console.log('\n✅ Uninstallation complete!');
|
|
702
|
+
} else if (args.activate) {
|
|
703
|
+
// Activate: only inject AGENTS.md, check installation first
|
|
704
|
+
const targetDir = projectTargetDir || process.cwd();
|
|
705
|
+
activateProject(targetDir);
|
|
559
706
|
} else {
|
|
560
707
|
if (projectTargetDir) {
|
|
561
708
|
if (!aiPlatform) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vibe-x/agent-better-checkpoint",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "Semantic Git checkpoint commits for AI coding sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
"bin/",
|
|
11
11
|
"platform/",
|
|
12
12
|
"skill/",
|
|
13
|
-
"LICENSE"
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"scripts/"
|
|
14
15
|
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"release:check": "node ./scripts/release-check.mjs",
|
|
18
|
+
"preversion": "npm run release:check",
|
|
19
|
+
"prepublishOnly": "npm run release:check"
|
|
20
|
+
},
|
|
15
21
|
"keywords": [
|
|
16
22
|
"ai",
|
|
17
23
|
"agent",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# alloc_patch.sh — Allocate a temporary patch file for hunk-level checkpoint commits
|
|
4
|
+
#
|
|
5
|
+
# Creates a writable patch path under ~/.vibe-x/agent-better-checkpoint/tmp/
|
|
6
|
+
# and returns a one-line JSON payload with id, createdAt, path, and ttlHours.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
# alloc_patch.sh --workspace /path/to/repo
|
|
10
|
+
# alloc_patch.sh --help
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
TTL_HOURS=48
|
|
15
|
+
TMP_ROOT="${HOME}/.vibe-x/agent-better-checkpoint/tmp"
|
|
16
|
+
WORKSPACE=""
|
|
17
|
+
|
|
18
|
+
usage() {
|
|
19
|
+
cat <<'EOF'
|
|
20
|
+
alloc_patch.sh — Allocate a temporary patch file for checkpoint hunks
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
alloc_patch.sh --workspace <path>
|
|
24
|
+
alloc_patch.sh --help
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--workspace <path> Workspace root used to derive a stable temp namespace
|
|
28
|
+
--help Show this help message
|
|
29
|
+
EOF
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
error() {
|
|
33
|
+
local code="$1"
|
|
34
|
+
shift
|
|
35
|
+
echo "${code}: $*" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
json_escape() {
|
|
40
|
+
local str="$1"
|
|
41
|
+
str="${str//\\/\\\\}"
|
|
42
|
+
str="${str//\"/\\\"}"
|
|
43
|
+
str="${str//$'\n'/\\n}"
|
|
44
|
+
str="${str//$'\r'/\\r}"
|
|
45
|
+
str="${str//$'\t'/\\t}"
|
|
46
|
+
printf '%s' "$str"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
workspace_hash() {
|
|
50
|
+
local input="$1"
|
|
51
|
+
if command -v shasum >/dev/null 2>&1; then
|
|
52
|
+
printf '%s' "$input" | shasum -a 256 | awk '{print substr($1,1,8)}'
|
|
53
|
+
elif command -v sha256sum >/dev/null 2>&1; then
|
|
54
|
+
printf '%s' "$input" | sha256sum | awk '{print substr($1,1,8)}'
|
|
55
|
+
else
|
|
56
|
+
printf '%s' "$input" | cksum | awk '{print $1}'
|
|
57
|
+
fi
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
random_id() {
|
|
61
|
+
local id
|
|
62
|
+
id=$(LC_ALL=C tr -dc 'A-Z0-9' < /dev/urandom | head -c 10 || true)
|
|
63
|
+
if [[ -z "$id" ]]; then
|
|
64
|
+
id="$(date -u +%s)"
|
|
65
|
+
fi
|
|
66
|
+
printf '%s' "$id"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
cleanup_expired() {
|
|
70
|
+
local root="$1"
|
|
71
|
+
[[ -d "$root" ]] || return 0
|
|
72
|
+
|
|
73
|
+
find "$root" -type f -name 'patch-*.patch' -mtime +1 -exec rm -f {} + 2>/dev/null || true
|
|
74
|
+
find "$root" -type d -empty -mindepth 1 -exec rmdir {} + 2>/dev/null || true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
while [[ $# -gt 0 ]]; do
|
|
78
|
+
case "$1" in
|
|
79
|
+
--workspace)
|
|
80
|
+
WORKSPACE="${2:-}"
|
|
81
|
+
shift 2
|
|
82
|
+
;;
|
|
83
|
+
--help|-h)
|
|
84
|
+
usage
|
|
85
|
+
exit 0
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
error "ABC_TEMP_ALLOC_FAILED" "Unknown option: $1"
|
|
89
|
+
;;
|
|
90
|
+
esac
|
|
91
|
+
done
|
|
92
|
+
|
|
93
|
+
if [[ -z "$WORKSPACE" ]]; then
|
|
94
|
+
error "ABC_TEMP_ALLOC_FAILED" "Missing required --workspace argument. Please provide the workspace root path."
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
if ! mkdir -p "$TMP_ROOT" 2>/dev/null; then
|
|
98
|
+
error "ABC_TEMP_ALLOC_FAILED" "Failed to create the temp root at ${TMP_ROOT}. Please check directory permissions."
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
cleanup_expired "$TMP_ROOT"
|
|
102
|
+
|
|
103
|
+
WORKSPACE_HASH=$(workspace_hash "$WORKSPACE")
|
|
104
|
+
WORKSPACE_DIR="${TMP_ROOT}/${WORKSPACE_HASH}"
|
|
105
|
+
if ! mkdir -p "$WORKSPACE_DIR" 2>/dev/null; then
|
|
106
|
+
error "ABC_TEMP_ALLOC_FAILED" "Failed to create the workspace temp directory at ${WORKSPACE_DIR}. Please check directory permissions."
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
STAMP=$(date -u +%Y%m%dT%H%M%SZ)
|
|
110
|
+
CREATED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
111
|
+
ID=$(random_id)
|
|
112
|
+
PATCH_PATH="${WORKSPACE_DIR}/patch-${STAMP}-${ID}.patch"
|
|
113
|
+
|
|
114
|
+
if ! : > "$PATCH_PATH" 2>/dev/null; then
|
|
115
|
+
error "ABC_TEMP_ALLOC_FAILED" "Failed to create a temporary patch file at ${PATCH_PATH}. Please check directory permissions and available disk space."
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
printf '{"ok":true,"id":"%s","createdAt":"%s","path":"%s","ttlHours":%s}\n' \
|
|
119
|
+
"$(json_escape "$ID")" \
|
|
120
|
+
"$(json_escape "$CREATED_AT")" \
|
|
121
|
+
"$(json_escape "$PATCH_PATH")" \
|
|
122
|
+
"$TTL_HOURS"
|
|
@@ -427,7 +427,7 @@ build_and_output_reminder() {
|
|
|
427
427
|
changes_indented=$(echo "$changes" | sed 's/^/ /')
|
|
428
428
|
|
|
429
429
|
# Project-local script; fallback to global
|
|
430
|
-
local checkpoint_cmd_sh checkpoint_cmd_ps1
|
|
430
|
+
local checkpoint_cmd_sh checkpoint_cmd_ps1 alloc_patch_cmd_sh alloc_patch_cmd_ps1
|
|
431
431
|
if [[ -f "${workspace}/.vibe-x/agent-better-checkpoint/checkpoint.sh" ]]; then
|
|
432
432
|
checkpoint_cmd_sh=".vibe-x/agent-better-checkpoint/checkpoint.sh"
|
|
433
433
|
else
|
|
@@ -438,6 +438,16 @@ build_and_output_reminder() {
|
|
|
438
438
|
else
|
|
439
439
|
checkpoint_cmd_ps1='\$env:USERPROFILE/.vibe-x/agent-better-checkpoint/scripts/checkpoint.ps1'
|
|
440
440
|
fi
|
|
441
|
+
if [[ -f "${workspace}/.vibe-x/agent-better-checkpoint/alloc_patch.sh" ]]; then
|
|
442
|
+
alloc_patch_cmd_sh=".vibe-x/agent-better-checkpoint/alloc_patch.sh"
|
|
443
|
+
else
|
|
444
|
+
alloc_patch_cmd_sh="~/.vibe-x/agent-better-checkpoint/scripts/alloc_patch.sh"
|
|
445
|
+
fi
|
|
446
|
+
if [[ -f "${workspace}/.vibe-x/agent-better-checkpoint/alloc_patch.ps1" ]]; then
|
|
447
|
+
alloc_patch_cmd_ps1='.\\.vibe-x\\agent-better-checkpoint\\alloc_patch.ps1'
|
|
448
|
+
else
|
|
449
|
+
alloc_patch_cmd_ps1='\$env:USERPROFILE/.vibe-x/agent-better-checkpoint/scripts/alloc_patch.ps1'
|
|
450
|
+
fi
|
|
441
451
|
|
|
442
452
|
local reminder
|
|
443
453
|
reminder="## ⚠️ Uncommitted Changes Detected
|
|
@@ -449,17 +459,24 @@ There are uncommitted changes in the workspace. Please create a checkpoint commi
|
|
|
449
459
|
${changes_indented}
|
|
450
460
|
\`\`\`
|
|
451
461
|
|
|
452
|
-
**Action Required**:
|
|
462
|
+
**Action Required**: Allocate a patch file, write only the selected hunks for this conversation, then run the checkpoint script.
|
|
453
463
|
|
|
454
464
|
**macOS/Linux:**
|
|
455
465
|
\`\`\`bash
|
|
456
|
-
${
|
|
466
|
+
PATCH_JSON=\$(${alloc_patch_cmd_sh} --workspace \"${workspace}\")
|
|
467
|
+
PATCH_PATH=\$(printf '%s' \"\$PATCH_JSON\" | python3 -c 'import sys,json; print(json.load(sys.stdin)["path"])')
|
|
468
|
+
# Write only the selected hunks for this conversation to \"\$PATCH_PATH\" as a unified diff patch.
|
|
469
|
+
${checkpoint_cmd_sh} \"checkpoint(<scope>): <description>\" \"<user-prompt>\" --type fallback --patch-file \"\$PATCH_PATH\"
|
|
457
470
|
\`\`\`
|
|
458
471
|
|
|
459
472
|
**Windows (PowerShell):**
|
|
460
473
|
\`\`\`powershell
|
|
461
|
-
powershell -File \"${
|
|
462
|
-
|
|
474
|
+
\$patch = powershell -File \"${alloc_patch_cmd_ps1}\" -Workspace \"${workspace}\" | ConvertFrom-Json
|
|
475
|
+
# Write only the selected hunks for this conversation to \$patch.path as a unified diff patch.
|
|
476
|
+
powershell -File \"${checkpoint_cmd_ps1}\" \"checkpoint(<scope>): <description>\" \"<user-prompt>\" -Type fallback -PatchFile \$patch.path
|
|
477
|
+
\`\`\`
|
|
478
|
+
|
|
479
|
+
If the checkpoint script returns an \`ABC_PATCH_*\` error, do not fall back to a full commit automatically. Explain the conflict and ask the user how to proceed."
|
|
463
480
|
|
|
464
481
|
output_block "$reminder" "$platform"
|
|
465
482
|
}
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
# AI provides descriptive content (subject + body). This script:
|
|
6
6
|
# 1. Truncates user-prompt (≤60 chars, head+tail)
|
|
7
7
|
# 2. Appends metadata via git interpret-trailers
|
|
8
|
-
# 3. Runs git add -A && git commit
|
|
8
|
+
# 3. Runs git add -A && git commit, or applies a selected patch to the index first
|
|
9
9
|
#
|
|
10
10
|
# Usage:
|
|
11
|
-
# checkpoint.sh <message> [user-prompt] [--type auto|fallback]
|
|
11
|
+
# checkpoint.sh <message> [user-prompt] [--type auto|fallback] [--patch-file <path>]
|
|
12
12
|
|
|
13
13
|
set -euo pipefail
|
|
14
14
|
|
|
@@ -18,6 +18,7 @@ set -euo pipefail
|
|
|
18
18
|
MESSAGE="${1:-}"
|
|
19
19
|
USER_PROMPT="${2:-}"
|
|
20
20
|
CHECKPOINT_TYPE="auto"
|
|
21
|
+
PATCH_FILE=""
|
|
21
22
|
|
|
22
23
|
shift 2 2>/dev/null || true
|
|
23
24
|
while [[ $# -gt 0 ]]; do
|
|
@@ -26,18 +27,44 @@ while [[ $# -gt 0 ]]; do
|
|
|
26
27
|
CHECKPOINT_TYPE="${2:-auto}"
|
|
27
28
|
shift 2
|
|
28
29
|
;;
|
|
30
|
+
--patch-file)
|
|
31
|
+
PATCH_FILE="${2:-}"
|
|
32
|
+
shift 2
|
|
33
|
+
;;
|
|
34
|
+
--help|-h)
|
|
35
|
+
cat <<'EOF'
|
|
36
|
+
checkpoint.sh — Create semantic Git checkpoint commits
|
|
37
|
+
|
|
38
|
+
Usage:
|
|
39
|
+
checkpoint.sh <message> [user-prompt] [--type auto|fallback] [--patch-file <path>]
|
|
40
|
+
|
|
41
|
+
Options:
|
|
42
|
+
--type <auto|fallback> Checkpoint type metadata
|
|
43
|
+
--patch-file <path> Apply only the selected patch hunks to the index before commit
|
|
44
|
+
--help Show this help message
|
|
45
|
+
EOF
|
|
46
|
+
exit 0
|
|
47
|
+
;;
|
|
29
48
|
*)
|
|
30
|
-
|
|
49
|
+
echo "Unknown option: $1" >&2
|
|
50
|
+
exit 1
|
|
31
51
|
;;
|
|
32
52
|
esac
|
|
33
53
|
done
|
|
34
54
|
|
|
35
55
|
if [[ -z "$MESSAGE" ]]; then
|
|
36
56
|
echo "Error: commit message is required" >&2
|
|
37
|
-
echo "Usage: checkpoint.sh <message> [user-prompt] [--type auto|fallback]" >&2
|
|
57
|
+
echo "Usage: checkpoint.sh <message> [user-prompt] [--type auto|fallback] [--patch-file <path>]" >&2
|
|
38
58
|
exit 1
|
|
39
59
|
fi
|
|
40
60
|
|
|
61
|
+
fail_checkpoint() {
|
|
62
|
+
local code="$1"
|
|
63
|
+
shift
|
|
64
|
+
echo "${code}: $*" >&2
|
|
65
|
+
exit 1
|
|
66
|
+
}
|
|
67
|
+
|
|
41
68
|
# ============================================================
|
|
42
69
|
# Platform detection
|
|
43
70
|
# ============================================================
|
|
@@ -60,10 +87,23 @@ AGENT_PLATFORM=$(detect_platform)
|
|
|
60
87
|
truncate_prompt() {
|
|
61
88
|
local prompt="$1"
|
|
62
89
|
local max_len=60
|
|
90
|
+
|
|
91
|
+
# Ensure UTF-8 character-based (not byte-based) string operations.
|
|
92
|
+
# Without this, ${#prompt} and ${prompt:0:n} count bytes in C locale,
|
|
93
|
+
# which breaks multi-byte characters (e.g., Chinese) mid-character.
|
|
94
|
+
local old_lc_all="${LC_ALL:-}"
|
|
95
|
+
export LC_ALL=en_US.UTF-8
|
|
96
|
+
|
|
63
97
|
local len=${#prompt}
|
|
64
98
|
|
|
65
99
|
if [[ $len -le $max_len ]]; then
|
|
66
100
|
echo "$prompt"
|
|
101
|
+
# Restore LC_ALL
|
|
102
|
+
if [[ -n "$old_lc_all" ]]; then
|
|
103
|
+
export LC_ALL="$old_lc_all"
|
|
104
|
+
else
|
|
105
|
+
unset LC_ALL
|
|
106
|
+
fi
|
|
67
107
|
return
|
|
68
108
|
fi
|
|
69
109
|
|
|
@@ -71,6 +111,14 @@ truncate_prompt() {
|
|
|
71
111
|
local tail_len=$(( max_len - 3 - head_len ))
|
|
72
112
|
local head="${prompt:0:$head_len}"
|
|
73
113
|
local tail="${prompt:$((len - tail_len)):$tail_len}"
|
|
114
|
+
|
|
115
|
+
# Restore LC_ALL
|
|
116
|
+
if [[ -n "$old_lc_all" ]]; then
|
|
117
|
+
export LC_ALL="$old_lc_all"
|
|
118
|
+
else
|
|
119
|
+
unset LC_ALL
|
|
120
|
+
fi
|
|
121
|
+
|
|
74
122
|
echo "${head}...${tail}"
|
|
75
123
|
}
|
|
76
124
|
|
|
@@ -98,15 +146,44 @@ has_changes() {
|
|
|
98
146
|
return 1
|
|
99
147
|
}
|
|
100
148
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
fi
|
|
149
|
+
has_staged_changes() {
|
|
150
|
+
if ! git diff --cached --quiet 2>/dev/null; then
|
|
151
|
+
return 0
|
|
152
|
+
fi
|
|
153
|
+
return 1
|
|
154
|
+
}
|
|
105
155
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
156
|
+
if [[ -z "$PATCH_FILE" ]]; then
|
|
157
|
+
if ! has_changes; then
|
|
158
|
+
echo "No changes to commit."
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
161
|
+
|
|
162
|
+
# ============================================================
|
|
163
|
+
# git add -A
|
|
164
|
+
# ============================================================
|
|
165
|
+
git add -A
|
|
166
|
+
else
|
|
167
|
+
if [[ ! -f "$PATCH_FILE" ]]; then
|
|
168
|
+
fail_checkpoint "ABC_PATCH_FILE_MISSING" "Patch file not found at ${PATCH_FILE}. Please allocate and write the patch file before running checkpoint."
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if [[ ! -s "$PATCH_FILE" ]]; then
|
|
172
|
+
fail_checkpoint "ABC_PATCH_FILE_EMPTY" "Patch file at ${PATCH_FILE} is empty. Please write at least one selected hunk before running checkpoint."
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if ! git apply --cached --check "$PATCH_FILE" >/dev/null 2>&1; then
|
|
176
|
+
fail_checkpoint "ABC_PATCH_APPLY_FAILED" "Failed to apply the selected patch cleanly to the Git index. The target hunks may have drifted or overlap with other uncommitted edits."
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
if ! git apply --cached "$PATCH_FILE" >/dev/null 2>&1; then
|
|
180
|
+
fail_checkpoint "ABC_PATCH_APPLY_FAILED" "Failed to apply the selected patch to the Git index after the dry run succeeded. Please verify the patch file and retry."
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if ! has_staged_changes; then
|
|
184
|
+
fail_checkpoint "ABC_PATCH_NO_STAGED_CHANGES" "The selected patch did not produce any staged changes. The target hunks may already be staged or no longer match the current repository state."
|
|
185
|
+
fi
|
|
186
|
+
fi
|
|
110
187
|
|
|
111
188
|
# ============================================================
|
|
112
189
|
# Build trailers and commit
|
|
@@ -120,6 +197,15 @@ if [[ -n "$TRUNCATED_PROMPT" ]]; then
|
|
|
120
197
|
TRAILER_ARGS+=(--trailer "User-Prompt: ${TRUNCATED_PROMPT}")
|
|
121
198
|
fi
|
|
122
199
|
|
|
123
|
-
echo "$MESSAGE" | git interpret-trailers "${TRAILER_ARGS[@]}" | git commit -F
|
|
200
|
+
if ! echo "$MESSAGE" | git interpret-trailers "${TRAILER_ARGS[@]}" | git commit -F -; then
|
|
201
|
+
if [[ -n "$PATCH_FILE" ]]; then
|
|
202
|
+
fail_checkpoint "ABC_PATCH_COMMIT_FAILED" "Git commit failed after staging the selected patch. Review the repository state and retry when ready."
|
|
203
|
+
fi
|
|
204
|
+
exit 1
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
if [[ -n "$PATCH_FILE" ]]; then
|
|
208
|
+
rm -f "$PATCH_FILE"
|
|
209
|
+
fi
|
|
124
210
|
|
|
125
211
|
echo "Checkpoint committed successfully."
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
alloc_patch.ps1 — Allocate a temporary patch file for hunk-level checkpoint commits
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Creates a writable patch path under ~/.vibe-x/agent-better-checkpoint/tmp/
|
|
7
|
+
and returns a one-line JSON payload with id, createdAt, path, and ttlHours.
|
|
8
|
+
|
|
9
|
+
.PARAMETER Workspace
|
|
10
|
+
Workspace root used to derive a stable temp namespace. Required.
|
|
11
|
+
|
|
12
|
+
.EXAMPLE
|
|
13
|
+
.\alloc_patch.ps1 -Workspace C:\repo
|
|
14
|
+
#>
|
|
15
|
+
|
|
16
|
+
param(
|
|
17
|
+
[string]$Workspace = "",
|
|
18
|
+
[switch]$Help
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
$ErrorActionPreference = "Stop"
|
|
22
|
+
$TTL_HOURS = 48
|
|
23
|
+
$TMP_ROOT = Join-Path $HOME ".vibe-x/agent-better-checkpoint/tmp"
|
|
24
|
+
|
|
25
|
+
function Show-Usage {
|
|
26
|
+
@"
|
|
27
|
+
alloc_patch.ps1 — Allocate a temporary patch file for checkpoint hunks
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
alloc_patch.ps1 -Workspace <path>
|
|
31
|
+
alloc_patch.ps1 -Help
|
|
32
|
+
|
|
33
|
+
Options:
|
|
34
|
+
-Workspace <path> Workspace root used to derive a stable temp namespace
|
|
35
|
+
-Help Show this help message
|
|
36
|
+
"@ | Write-Output
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function Fail-Alloc {
|
|
40
|
+
param(
|
|
41
|
+
[string]$Code,
|
|
42
|
+
[string]$Message
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
[Console]::Error.WriteLine("${Code}: ${Message}")
|
|
46
|
+
exit 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function Get-WorkspaceHash {
|
|
50
|
+
param([string]$InputText)
|
|
51
|
+
|
|
52
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
53
|
+
try {
|
|
54
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($InputText)
|
|
55
|
+
$hashBytes = $sha.ComputeHash($bytes)
|
|
56
|
+
$hex = -join ($hashBytes | ForEach-Object { $_.ToString("x2") })
|
|
57
|
+
return $hex.Substring(0, 8)
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
$sha.Dispose()
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function Get-RandomId {
|
|
65
|
+
$bytes = New-Object byte[] 6
|
|
66
|
+
[System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
|
|
67
|
+
$hex = -join ($bytes | ForEach-Object { $_.ToString("X2") })
|
|
68
|
+
return $hex.Substring(0, 10)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function Cleanup-Expired {
|
|
72
|
+
param([string]$Root)
|
|
73
|
+
|
|
74
|
+
if (-not (Test-Path $Root -PathType Container)) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
$cutoff = (Get-Date).ToUniversalTime().AddHours(-$TTL_HOURS)
|
|
79
|
+
Get-ChildItem -Path $Root -Recurse -File -Filter 'patch-*.patch' -ErrorAction SilentlyContinue |
|
|
80
|
+
Where-Object { $_.LastWriteTimeUtc -lt $cutoff } |
|
|
81
|
+
ForEach-Object {
|
|
82
|
+
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
Get-ChildItem -Path $Root -Recurse -Directory -ErrorAction SilentlyContinue |
|
|
86
|
+
Sort-Object FullName -Descending |
|
|
87
|
+
ForEach-Object {
|
|
88
|
+
if (-not (Get-ChildItem -Path $_.FullName -Force -ErrorAction SilentlyContinue)) {
|
|
89
|
+
Remove-Item $_.FullName -Force -ErrorAction SilentlyContinue
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if ($Help) {
|
|
95
|
+
Show-Usage
|
|
96
|
+
exit 0
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (-not $Workspace) {
|
|
100
|
+
Fail-Alloc -Code "ABC_TEMP_ALLOC_FAILED" -Message "Missing required -Workspace argument. Please provide the workspace root path."
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
New-Item -ItemType Directory -Path $TMP_ROOT -Force | Out-Null
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
Fail-Alloc -Code "ABC_TEMP_ALLOC_FAILED" -Message "Failed to create the temp root at $TMP_ROOT. Please check directory permissions."
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
Cleanup-Expired -Root $TMP_ROOT
|
|
111
|
+
|
|
112
|
+
$workspaceHash = Get-WorkspaceHash -InputText $Workspace
|
|
113
|
+
$workspaceDir = Join-Path $TMP_ROOT $workspaceHash
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
New-Item -ItemType Directory -Path $workspaceDir -Force | Out-Null
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
Fail-Alloc -Code "ABC_TEMP_ALLOC_FAILED" -Message "Failed to create the workspace temp directory at $workspaceDir. Please check directory permissions."
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
$stamp = (Get-Date).ToUniversalTime().ToString("yyyyMMddTHHmmssZ")
|
|
123
|
+
$createdAt = (Get-Date).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ssZ")
|
|
124
|
+
$id = Get-RandomId
|
|
125
|
+
$patchPath = Join-Path $workspaceDir "patch-$stamp-$id.patch"
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
New-Item -ItemType File -Path $patchPath -Force | Out-Null
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
Fail-Alloc -Code "ABC_TEMP_ALLOC_FAILED" -Message "Failed to create a temporary patch file at $patchPath. Please check directory permissions and available disk space."
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
$result = [ordered]@{
|
|
135
|
+
ok = $true
|
|
136
|
+
id = $id
|
|
137
|
+
createdAt = $createdAt
|
|
138
|
+
path = $patchPath
|
|
139
|
+
ttlHours = $TTL_HOURS
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
$result | ConvertTo-Json -Compress | Write-Output
|
|
@@ -288,12 +288,20 @@ function Build-Reminder {
|
|
|
288
288
|
# Project-local script; fallback to global
|
|
289
289
|
$checkpointSh = "~/.vibe-x/agent-better-checkpoint/scripts/checkpoint.sh"
|
|
290
290
|
$checkpointPs1 = "`$env:USERPROFILE/.vibe-x/agent-better-checkpoint/scripts/checkpoint.ps1"
|
|
291
|
+
$allocPatchSh = "~/.vibe-x/agent-better-checkpoint/scripts/alloc_patch.sh"
|
|
292
|
+
$allocPatchPs1 = "`$env:USERPROFILE/.vibe-x/agent-better-checkpoint/scripts/alloc_patch.ps1"
|
|
291
293
|
if (Test-Path (Join-Path $Workspace ".vibe-x/agent-better-checkpoint/checkpoint.sh")) {
|
|
292
294
|
$checkpointSh = ".vibe-x/agent-better-checkpoint/checkpoint.sh"
|
|
293
295
|
}
|
|
294
296
|
if (Test-Path (Join-Path $Workspace ".vibe-x/agent-better-checkpoint/checkpoint.ps1")) {
|
|
295
297
|
$checkpointPs1 = ".\.vibe-x\agent-better-checkpoint\checkpoint.ps1"
|
|
296
298
|
}
|
|
299
|
+
if (Test-Path (Join-Path $Workspace ".vibe-x/agent-better-checkpoint/alloc_patch.sh")) {
|
|
300
|
+
$allocPatchSh = ".vibe-x/agent-better-checkpoint/alloc_patch.sh"
|
|
301
|
+
}
|
|
302
|
+
if (Test-Path (Join-Path $Workspace ".vibe-x/agent-better-checkpoint/alloc_patch.ps1")) {
|
|
303
|
+
$allocPatchPs1 = ".\.vibe-x\agent-better-checkpoint\alloc_patch.ps1"
|
|
304
|
+
}
|
|
297
305
|
|
|
298
306
|
return @"
|
|
299
307
|
## ⚠️ Uncommitted Changes Detected
|
|
@@ -305,17 +313,24 @@ There are uncommitted changes in the workspace. Please create a checkpoint commi
|
|
|
305
313
|
$ChangesIndented
|
|
306
314
|
``````
|
|
307
315
|
|
|
308
|
-
**Action Required**:
|
|
316
|
+
**Action Required**: Allocate a patch file, write only the selected hunks for this conversation, then run the checkpoint script.
|
|
309
317
|
|
|
310
318
|
**macOS/Linux:**
|
|
311
319
|
``````bash
|
|
312
|
-
$
|
|
320
|
+
PATCH_JSON=$($allocPatchSh --workspace "$Workspace")
|
|
321
|
+
PATCH_PATH=$(printf '%s' "$PATCH_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin)["path"])')
|
|
322
|
+
# Write only the selected hunks for this conversation to "$PATCH_PATH" as a unified diff patch.
|
|
323
|
+
$checkpointSh "checkpoint(<scope>): <description>" "<user-prompt>" --type fallback --patch-file "$PATCH_PATH"
|
|
313
324
|
``````
|
|
314
325
|
|
|
315
326
|
**Windows (PowerShell):**
|
|
316
327
|
``````powershell
|
|
317
|
-
powershell -File "$
|
|
328
|
+
$patch = powershell -File "$allocPatchPs1" -Workspace "$Workspace" | ConvertFrom-Json
|
|
329
|
+
# Write only the selected hunks for this conversation to $patch.path as a unified diff patch.
|
|
330
|
+
powershell -File "$checkpointPs1" "checkpoint(<scope>): <description>" "<user-prompt>" -Type fallback -PatchFile $patch.path
|
|
318
331
|
``````
|
|
332
|
+
|
|
333
|
+
If the checkpoint script returns an `ABC_PATCH_*` error, do not fall back to a full commit automatically. Explain the conflict and ask the user how to proceed.
|
|
319
334
|
"@
|
|
320
335
|
}
|
|
321
336
|
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
AI provides descriptive content (subject + body). This script:
|
|
7
7
|
1. Truncates user-prompt (≤60 chars, head+tail)
|
|
8
8
|
2. Appends metadata via git interpret-trailers
|
|
9
|
-
3. Runs git add -A && git commit
|
|
9
|
+
3. Runs git add -A && git commit, or applies a selected patch to the index first
|
|
10
10
|
|
|
11
11
|
.PARAMETER Message
|
|
12
12
|
Full commit message (subject + blank line + body). Required.
|
|
@@ -17,22 +17,65 @@
|
|
|
17
17
|
.PARAMETER Type
|
|
18
18
|
Checkpoint type: "auto" (default) or "fallback".
|
|
19
19
|
|
|
20
|
+
.PARAMETER PatchFile
|
|
21
|
+
Optional patch file path. When provided, only the selected hunks are staged before commit.
|
|
22
|
+
|
|
20
23
|
.EXAMPLE
|
|
21
24
|
.\checkpoint.ps1 "checkpoint(auth): add JWT refresh" "implement token refresh"
|
|
22
25
|
#>
|
|
23
26
|
|
|
24
27
|
param(
|
|
25
|
-
[Parameter(
|
|
26
|
-
[string]$Message,
|
|
28
|
+
[Parameter(Position = 0)]
|
|
29
|
+
[string]$Message = "",
|
|
27
30
|
|
|
28
31
|
[Parameter(Position = 1)]
|
|
29
32
|
[string]$UserPrompt = "",
|
|
30
33
|
|
|
31
|
-
[string]$Type = "auto"
|
|
34
|
+
[string]$Type = "auto",
|
|
35
|
+
|
|
36
|
+
[string]$PatchFile = "",
|
|
37
|
+
|
|
38
|
+
[switch]$Help
|
|
32
39
|
)
|
|
33
40
|
|
|
34
41
|
$ErrorActionPreference = "Stop"
|
|
35
42
|
|
|
43
|
+
function Show-Usage {
|
|
44
|
+
@"
|
|
45
|
+
checkpoint.ps1 — Create semantic Git checkpoint commits
|
|
46
|
+
|
|
47
|
+
Usage:
|
|
48
|
+
checkpoint.ps1 <message> [user-prompt] [-Type auto|fallback] [-PatchFile <path>]
|
|
49
|
+
checkpoint.ps1 -Help
|
|
50
|
+
|
|
51
|
+
Options:
|
|
52
|
+
-Type <auto|fallback> Checkpoint type metadata
|
|
53
|
+
-PatchFile <path> Apply only the selected patch hunks to the index before commit
|
|
54
|
+
-Help Show this help message
|
|
55
|
+
"@ | Write-Output
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function Fail-Checkpoint {
|
|
59
|
+
param(
|
|
60
|
+
[string]$Code,
|
|
61
|
+
[string]$MessageText
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
[Console]::Error.WriteLine("${Code}: ${MessageText}")
|
|
65
|
+
exit 1
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if ($Help) {
|
|
69
|
+
Show-Usage
|
|
70
|
+
exit 0
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (-not $Message) {
|
|
74
|
+
[Console]::Error.WriteLine("Error: commit message is required")
|
|
75
|
+
[Console]::Error.WriteLine("Usage: checkpoint.ps1 <message> [user-prompt] [-Type auto|fallback] [-PatchFile <path>]")
|
|
76
|
+
exit 1
|
|
77
|
+
}
|
|
78
|
+
|
|
36
79
|
# ============================================================
|
|
37
80
|
# Platform detection
|
|
38
81
|
# ============================================================
|
|
@@ -80,11 +123,11 @@ if ($UserPrompt) {
|
|
|
80
123
|
|
|
81
124
|
function Test-HasChanges {
|
|
82
125
|
# Staged changes
|
|
83
|
-
|
|
126
|
+
git diff --cached --quiet 2>$null
|
|
84
127
|
if ($LASTEXITCODE -ne 0) { return $true }
|
|
85
128
|
|
|
86
129
|
# Unstaged changes
|
|
87
|
-
|
|
130
|
+
git diff --quiet 2>$null
|
|
88
131
|
if ($LASTEXITCODE -ne 0) { return $true }
|
|
89
132
|
|
|
90
133
|
# Untracked files
|
|
@@ -94,16 +137,46 @@ function Test-HasChanges {
|
|
|
94
137
|
return $false
|
|
95
138
|
}
|
|
96
139
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
140
|
+
function Test-HasStagedChanges {
|
|
141
|
+
git diff --cached --quiet 2>$null
|
|
142
|
+
return $LASTEXITCODE -ne 0
|
|
100
143
|
}
|
|
101
144
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
145
|
+
if (-not $PatchFile) {
|
|
146
|
+
if (-not (Test-HasChanges)) {
|
|
147
|
+
Write-Host "No changes to commit."
|
|
148
|
+
exit 0
|
|
149
|
+
}
|
|
105
150
|
|
|
106
|
-
|
|
151
|
+
# ============================================================
|
|
152
|
+
# git add -A
|
|
153
|
+
# ============================================================
|
|
154
|
+
git add -A
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
if (-not (Test-Path $PatchFile -PathType Leaf)) {
|
|
158
|
+
Fail-Checkpoint -Code "ABC_PATCH_FILE_MISSING" -MessageText "Patch file not found at $PatchFile. Please allocate and write the patch file before running checkpoint."
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
$patchInfo = Get-Item $PatchFile
|
|
162
|
+
if ($patchInfo.Length -le 0) {
|
|
163
|
+
Fail-Checkpoint -Code "ABC_PATCH_FILE_EMPTY" -MessageText "Patch file at $PatchFile is empty. Please write at least one selected hunk before running checkpoint."
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
git apply --cached --check -- $PatchFile 2>$null
|
|
167
|
+
if ($LASTEXITCODE -ne 0) {
|
|
168
|
+
Fail-Checkpoint -Code "ABC_PATCH_APPLY_FAILED" -MessageText "Failed to apply the selected patch cleanly to the Git index. The target hunks may have drifted or overlap with other uncommitted edits."
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
git apply --cached -- $PatchFile 2>$null
|
|
172
|
+
if ($LASTEXITCODE -ne 0) {
|
|
173
|
+
Fail-Checkpoint -Code "ABC_PATCH_APPLY_FAILED" -MessageText "Failed to apply the selected patch to the Git index after the dry run succeeded. Please verify the patch file and retry."
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (-not (Test-HasStagedChanges)) {
|
|
177
|
+
Fail-Checkpoint -Code "ABC_PATCH_NO_STAGED_CHANGES" -MessageText "The selected patch did not produce any staged changes. The target hunks may already be staged or no longer match the current repository state."
|
|
178
|
+
}
|
|
179
|
+
}
|
|
107
180
|
|
|
108
181
|
# ============================================================
|
|
109
182
|
# Build trailers and commit
|
|
@@ -118,7 +191,16 @@ if ($TruncatedPrompt) {
|
|
|
118
191
|
$TrailerArgs += @("--trailer", "User-Prompt: $TruncatedPrompt")
|
|
119
192
|
}
|
|
120
193
|
|
|
121
|
-
# Pipe message → git interpret-trailers → git commit
|
|
122
194
|
$Message | git interpret-trailers @TrailerArgs | git commit -F -
|
|
195
|
+
if ($LASTEXITCODE -ne 0) {
|
|
196
|
+
if ($PatchFile) {
|
|
197
|
+
Fail-Checkpoint -Code "ABC_PATCH_COMMIT_FAILED" -MessageText "Git commit failed after staging the selected patch. Review the repository state and retry when ready."
|
|
198
|
+
}
|
|
199
|
+
exit $LASTEXITCODE
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if ($PatchFile -and (Test-Path $PatchFile -PathType Leaf)) {
|
|
203
|
+
Remove-Item $PatchFile -Force -ErrorAction SilentlyContinue
|
|
204
|
+
}
|
|
123
205
|
|
|
124
206
|
Write-Host "Checkpoint committed successfully."
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Release Gate for agent-better-checkpoint.
|
|
4
|
+
* Fails fast when common release omissions are detected.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
|
|
11
|
+
function fail(msg) {
|
|
12
|
+
console.error(`\n[release:check] ${msg}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function ok(msg) {
|
|
17
|
+
console.log(`[release:check] OK: ${msg}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fileExists(path) {
|
|
21
|
+
try {
|
|
22
|
+
readFileSync(path);
|
|
23
|
+
return true;
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sha256(text) {
|
|
30
|
+
return createHash('sha256').update(text, 'utf8').digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readText(path) {
|
|
34
|
+
return readFileSync(path, 'utf8');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function git(args) {
|
|
38
|
+
return execFileSync('git', args, { encoding: 'utf8' }).trim();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function ensureCleanWorktree() {
|
|
42
|
+
const out = git(['status', '--porcelain']);
|
|
43
|
+
if (out.length !== 0) fail('Working tree is not clean. Commit or stash changes before release.');
|
|
44
|
+
ok('working tree clean');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function ensureChangelogHasVersion(version) {
|
|
48
|
+
const changelog = readText('CHANGELOG.md');
|
|
49
|
+
if (!changelog.includes(`## [${version}]`)) {
|
|
50
|
+
fail(`CHANGELOG.md does not contain an entry for version ${version}.`);
|
|
51
|
+
}
|
|
52
|
+
ok(`CHANGELOG contains ${version}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function ensureFilesInSync(sourcePath, mirrorPath, label) {
|
|
56
|
+
if (!fileExists(mirrorPath)) {
|
|
57
|
+
console.warn(`[release:check] WARN: ${mirrorPath} not found; skipping sync check for ${label}.`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const sourceText = readText(sourcePath).replace(/\r\n/g, '\n');
|
|
62
|
+
const mirrorText = readText(mirrorPath).replace(/\r\n/g, '\n');
|
|
63
|
+
|
|
64
|
+
if (sha256(sourceText) !== sha256(mirrorText)) {
|
|
65
|
+
fail(`${label} out of sync:\n- ${sourcePath}\n- ${mirrorPath}\nPlease update both or regenerate project-local copy.`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ok(`${label} copies are in sync`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function ensureProjectLocalScriptsInSync() {
|
|
72
|
+
const pairs = [
|
|
73
|
+
{
|
|
74
|
+
sourcePath: 'platform/unix/checkpoint.sh',
|
|
75
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/checkpoint.sh',
|
|
76
|
+
label: 'unix checkpoint.sh',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
sourcePath: 'platform/unix/check_uncommitted.sh',
|
|
80
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/check_uncommitted.sh',
|
|
81
|
+
label: 'unix check_uncommitted.sh',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
sourcePath: 'platform/win/checkpoint.ps1',
|
|
85
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/checkpoint.ps1',
|
|
86
|
+
label: 'windows checkpoint.ps1',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
sourcePath: 'platform/win/check_uncommitted.ps1',
|
|
90
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/check_uncommitted.ps1',
|
|
91
|
+
label: 'windows check_uncommitted.ps1',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
sourcePath: 'platform/unix/alloc_patch.sh',
|
|
95
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/alloc_patch.sh',
|
|
96
|
+
label: 'unix alloc_patch.sh',
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
sourcePath: 'platform/win/alloc_patch.ps1',
|
|
100
|
+
mirrorPath: '.vibe-x/agent-better-checkpoint/alloc_patch.ps1',
|
|
101
|
+
label: 'windows alloc_patch.ps1',
|
|
102
|
+
},
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
for (const pair of pairs) {
|
|
106
|
+
ensureFilesInSync(pair.sourcePath, pair.mirrorPath, pair.label);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ensureSkillVersionMatches(version) {
|
|
111
|
+
const skill = readText('skill/SKILL.md');
|
|
112
|
+
const requiredSnippets = [
|
|
113
|
+
`version: "${version}"`,
|
|
114
|
+
`@vibe-x/agent-better-checkpoint@${version}`,
|
|
115
|
+
`**Version**: ${version}`,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const missing = requiredSnippets.filter((snippet) => !skill.includes(snippet));
|
|
119
|
+
if (missing.length > 0) {
|
|
120
|
+
fail(`skill/SKILL.md is missing current version references for ${version}:\n- ${missing.join('\n- ')}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
ok(`skill/SKILL.md references ${version}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function main() {
|
|
127
|
+
if (!fileExists('package.json')) fail('package.json not found (run from repo root).');
|
|
128
|
+
if (!fileExists('CHANGELOG.md')) fail('CHANGELOG.md not found.');
|
|
129
|
+
if (!fileExists('platform/unix/checkpoint.sh')) fail('platform/unix/checkpoint.sh not found.');
|
|
130
|
+
|
|
131
|
+
const pkg = JSON.parse(readText('package.json'));
|
|
132
|
+
const version = pkg.version;
|
|
133
|
+
if (!version) fail('package.json missing version field.');
|
|
134
|
+
|
|
135
|
+
ensureCleanWorktree();
|
|
136
|
+
ensureProjectLocalScriptsInSync();
|
|
137
|
+
ensureChangelogHasVersion(version);
|
|
138
|
+
ensureSkillVersionMatches(version);
|
|
139
|
+
|
|
140
|
+
ok(`release checks passed for ${pkg.name}@${version}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
main();
|
package/skill/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-better-checkpoint
|
|
|
3
3
|
description: "Creates semantic Git checkpoint commits during AI coding sessions with Conventional Commits format and Git Trailers. Use after completing code edits, or when user says 'commit changes', 'create checkpoint', or 'save my progress'."
|
|
4
4
|
license: MIT
|
|
5
5
|
metadata:
|
|
6
|
-
version: "0.3.
|
|
6
|
+
version: "0.3.4"
|
|
7
7
|
author: "alienzhou"
|
|
8
8
|
category: "version-control"
|
|
9
9
|
---
|
|
@@ -31,7 +31,7 @@ Both `.sh` and `.ps1` are always installed regardless of current OS.
|
|
|
31
31
|
If neither exists, run:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
npx @vibe-x/agent-better-checkpoint@0.3.
|
|
34
|
+
npx @vibe-x/agent-better-checkpoint@0.3.4
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
Without `--target`: installs globally. With `--target .`: project-only (skill + hooks in `.cursor/`, scripts in `.vibe-x/`), no global changes.
|
|
@@ -99,14 +99,16 @@ Switch to flex-column layout with collapsible sidebar.
|
|
|
99
99
|
|
|
100
100
|
## 🛠️ How to Commit
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Default to the patch-based flow when you can clearly identify the hunks from the current logical unit of work. Fall back to the full-workspace commit flow only when the user explicitly wants everything committed together.
|
|
103
103
|
|
|
104
104
|
**Prefer project-local when present**, fall back to global:
|
|
105
105
|
|
|
106
|
-
|
|
|
107
|
-
|
|
108
|
-
|
|
|
109
|
-
|
|
|
106
|
+
| Script | macOS/Linux | Windows |
|
|
107
|
+
|--------|-------------|---------|
|
|
108
|
+
| checkpoint | `.vibe-x/agent-better-checkpoint/checkpoint.sh` | `powershell -File ".vibe-x\agent-better-checkpoint\checkpoint.ps1"` |
|
|
109
|
+
| alloc_patch | `.vibe-x/agent-better-checkpoint/alloc_patch.sh` | `powershell -File ".vibe-x\agent-better-checkpoint\alloc_patch.ps1"` |
|
|
110
|
+
| global checkpoint | `~/.vibe-x/agent-better-checkpoint/scripts/checkpoint.sh` | `powershell -File "$env:USERPROFILE\.vibe-x\agent-better-checkpoint\scripts\checkpoint.ps1"` |
|
|
111
|
+
| global alloc_patch | `~/.vibe-x/agent-better-checkpoint/scripts/alloc_patch.sh` | `powershell -File "$env:USERPROFILE\.vibe-x\agent-better-checkpoint\scripts\alloc_patch.ps1"` |
|
|
110
112
|
|
|
111
113
|
### Parameters:
|
|
112
114
|
|
|
@@ -115,31 +117,51 @@ Call the checkpoint script after composing your message. Both `.sh` and `.ps1` a
|
|
|
115
117
|
| message (1st arg) | Yes | Full commit message (subject + blank line + body) |
|
|
116
118
|
| user-prompt (2nd arg) | No | The user's original prompt/request |
|
|
117
119
|
| `--type` / `-Type` | No | `auto` (default) or `fallback` |
|
|
120
|
+
| `--patch-file` / `-PatchFile` | No | Apply only the selected hunks from a unified diff patch before commit |
|
|
118
121
|
|
|
119
|
-
###
|
|
122
|
+
### Recommended patch flow (macOS/Linux):
|
|
120
123
|
|
|
121
124
|
```bash
|
|
125
|
+
PATCH_JSON=$(.vibe-x/agent-better-checkpoint/alloc_patch.sh --workspace "$PWD")
|
|
126
|
+
PATCH_PATH=$(printf '%s' "$PATCH_JSON" | python3 -c 'import sys,json; print(json.load(sys.stdin)["path"])')
|
|
127
|
+
# Write only the selected hunks for this logical unit to "$PATCH_PATH" as a unified diff patch.
|
|
122
128
|
.vibe-x/agent-better-checkpoint/checkpoint.sh \
|
|
123
129
|
"checkpoint(auth): add JWT token refresh logic
|
|
124
130
|
|
|
125
131
|
Implement automatic token refresh when access token expires.
|
|
126
132
|
Uses refresh token rotation for security." \
|
|
127
|
-
"帮我实现 token 刷新机制"
|
|
133
|
+
"帮我实现 token 刷新机制" \
|
|
134
|
+
--patch-file "$PATCH_PATH"
|
|
128
135
|
```
|
|
129
136
|
|
|
130
|
-
###
|
|
137
|
+
### Recommended patch flow (Windows):
|
|
131
138
|
|
|
132
139
|
```powershell
|
|
140
|
+
$patch = powershell -File ".vibe-x\agent-better-checkpoint\alloc_patch.ps1" -Workspace (Get-Location).Path | ConvertFrom-Json
|
|
141
|
+
# Write only the selected hunks for this logical unit to $patch.path as a unified diff patch.
|
|
133
142
|
powershell -File ".vibe-x\agent-better-checkpoint\checkpoint.ps1" `
|
|
134
143
|
"checkpoint(auth): add JWT token refresh logic`n`nImplement automatic token refresh when access token expires.`nUses refresh token rotation for security." `
|
|
144
|
+
"帮我实现 token 刷新机制" `
|
|
145
|
+
-PatchFile $patch.path
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Full-workspace fallback example (macOS/Linux):
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
.vibe-x/agent-better-checkpoint/checkpoint.sh \
|
|
152
|
+
"checkpoint(auth): add JWT token refresh logic
|
|
153
|
+
|
|
154
|
+
Implement automatic token refresh when access token expires.
|
|
155
|
+
Uses refresh token rotation for security." \
|
|
135
156
|
"帮我实现 token 刷新机制"
|
|
136
157
|
```
|
|
137
158
|
|
|
138
159
|
### What the script does:
|
|
139
160
|
1. Truncates user prompt to ≤60 characters (head...tail)
|
|
140
161
|
2. Appends Git Trailers: `Agent`, `Checkpoint-Type`, `User-Prompt`
|
|
141
|
-
3.
|
|
142
|
-
4.
|
|
162
|
+
3. Either runs `git add -A && git commit` or applies the selected patch hunks and then commits
|
|
163
|
+
4. Returns `ABC_PATCH_*` errors when the selected patch cannot be committed safely
|
|
164
|
+
5. Exits gracefully if there are no changes
|
|
143
165
|
|
|
144
166
|
---
|
|
145
167
|
|
|
@@ -167,4 +189,4 @@ This should feel natural — commit as you go, like any good developer.
|
|
|
167
189
|
|
|
168
190
|
---
|
|
169
191
|
|
|
170
|
-
**Version**: 0.3.
|
|
192
|
+
**Version**: 0.3.4
|