@kb-labs/commit-core 2.94.0 → 2.96.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/dist/analyzer/index.js +10 -13
- package/dist/analyzer/index.js.map +1 -1
- package/dist/applier/index.js +113 -21
- package/dist/applier/index.js.map +1 -1
- package/dist/generator/index.js +10 -13
- package/dist/generator/index.js.map +1 -1
- package/dist/index.js +124 -22
- package/dist/index.js.map +1 -1
- package/dist/storage/index.d.ts +1 -0
- package/dist/storage/index.js +12 -3
- package/dist/storage/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -17,20 +17,17 @@ async function getGitStatus(cwd) {
|
|
|
17
17
|
untracked: status.not_added.filter((f) => !shouldIgnoreFile(f))
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
var IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
|
|
21
|
+
"node_modules",
|
|
22
|
+
".git",
|
|
23
|
+
"dist",
|
|
24
|
+
"build",
|
|
25
|
+
".next",
|
|
26
|
+
".turbo",
|
|
27
|
+
"coverage"
|
|
28
|
+
]);
|
|
20
29
|
function shouldIgnoreFile(file) {
|
|
21
|
-
|
|
22
|
-
"node_modules/",
|
|
23
|
-
".git/",
|
|
24
|
-
"dist/",
|
|
25
|
-
"build/",
|
|
26
|
-
".next/",
|
|
27
|
-
".turbo/",
|
|
28
|
-
"coverage/",
|
|
29
|
-
".cache/",
|
|
30
|
-
".temp/",
|
|
31
|
-
"tmp/"
|
|
32
|
-
];
|
|
33
|
-
return ignoredPaths.some((path) => file.includes(path));
|
|
30
|
+
return file.split("/").some((segment) => IGNORED_SEGMENTS.has(segment));
|
|
34
31
|
}
|
|
35
32
|
function getAllChangedFiles(status) {
|
|
36
33
|
const allFiles = [
|
|
@@ -2769,11 +2766,44 @@ function getErrorType(error) {
|
|
|
2769
2766
|
const preview = error.message.substring(0, 50).replace(/\n/g, " ");
|
|
2770
2767
|
return `error: ${preview}${error.message.length > 50 ? "..." : ""}`;
|
|
2771
2768
|
}
|
|
2769
|
+
var GIT_HOOK_NAMES = [
|
|
2770
|
+
"pre-commit",
|
|
2771
|
+
"commit-msg",
|
|
2772
|
+
"post-commit",
|
|
2773
|
+
"prepare-commit-msg",
|
|
2774
|
+
"pre-push"
|
|
2775
|
+
];
|
|
2776
|
+
function cleanGitError(message) {
|
|
2777
|
+
return message.split("\n").filter((line) => !line.trimStart().startsWith("hint:")).join("\n").trim();
|
|
2778
|
+
}
|
|
2779
|
+
function formatCommitError(message) {
|
|
2780
|
+
const cleaned = cleanGitError(message);
|
|
2781
|
+
const isHookError = /hook\s+(failed|exited|returned)/i.test(cleaned) || /husky/i.test(cleaned) || /lint-staged/i.test(cleaned) || GIT_HOOK_NAMES.some((h) => cleaned.includes(h));
|
|
2782
|
+
if (isHookError) {
|
|
2783
|
+
const match = cleaned.match(
|
|
2784
|
+
/(pre-commit|commit-msg|post-commit|prepare-commit-msg|pre-push)/i
|
|
2785
|
+
);
|
|
2786
|
+
const hookName = match?.[1] ?? "git hook";
|
|
2787
|
+
return `${hookName} hook failed:
|
|
2788
|
+
${cleaned}`;
|
|
2789
|
+
}
|
|
2790
|
+
return cleaned;
|
|
2791
|
+
}
|
|
2772
2792
|
async function applyCommitPlan(cwd, plan, options) {
|
|
2773
2793
|
const appliedCommits = [];
|
|
2774
2794
|
const errors = [];
|
|
2795
|
+
const logger = useLogger();
|
|
2796
|
+
const planFileCount = plan.commits.reduce((sum, c) => sum + c.files.length, 0);
|
|
2797
|
+
await logger.info("apply: start", {
|
|
2798
|
+
scope: options?.scope,
|
|
2799
|
+
cwd,
|
|
2800
|
+
commits: plan.commits.length,
|
|
2801
|
+
planFiles: planFileCount,
|
|
2802
|
+
force: !!options?.force
|
|
2803
|
+
});
|
|
2775
2804
|
const integrityErrors = validatePlanIntegrity(plan);
|
|
2776
2805
|
if (integrityErrors.length > 0) {
|
|
2806
|
+
await logger.warn("apply: plan integrity failed", { errors: integrityErrors });
|
|
2777
2807
|
return {
|
|
2778
2808
|
success: false,
|
|
2779
2809
|
appliedCommits: [],
|
|
@@ -2799,8 +2829,8 @@ async function applyCommitPlan(cwd, plan, options) {
|
|
|
2799
2829
|
message: formatCommitMessage(commit)
|
|
2800
2830
|
});
|
|
2801
2831
|
} catch (error) {
|
|
2802
|
-
const
|
|
2803
|
-
errors.push(`Failed to apply commit ${commit.id}: ${
|
|
2832
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
2833
|
+
errors.push(`Failed to apply commit ${commit.id}: ${formatCommitError(raw)}`);
|
|
2804
2834
|
break;
|
|
2805
2835
|
}
|
|
2806
2836
|
}
|
|
@@ -2818,6 +2848,10 @@ function validatePlanIntegrity(plan) {
|
|
|
2818
2848
|
errors.push(`Commit ${commit.id} has no files`);
|
|
2819
2849
|
continue;
|
|
2820
2850
|
}
|
|
2851
|
+
if (!commit.message.trim()) {
|
|
2852
|
+
errors.push(`Commit ${commit.id} has empty message`);
|
|
2853
|
+
continue;
|
|
2854
|
+
}
|
|
2821
2855
|
for (const file of commit.files) {
|
|
2822
2856
|
const firstCommit = seenInCommit.get(file);
|
|
2823
2857
|
if (firstCommit) {
|
|
@@ -2841,9 +2875,44 @@ async function applyCommit(cwd, commit) {
|
|
|
2841
2875
|
}
|
|
2842
2876
|
const [repoPath, fileInfos] = Array.from(filesByRepo.entries())[0];
|
|
2843
2877
|
const git = simpleGit(repoPath);
|
|
2844
|
-
await git.
|
|
2878
|
+
const statusBefore = await git.status();
|
|
2879
|
+
if (statusBefore.staged.length > 0) {
|
|
2880
|
+
const restored = await git.raw(["restore", "--staged", "--", ...statusBefore.staged]).then(() => true).catch(() => false);
|
|
2881
|
+
if (!restored) {
|
|
2882
|
+
for (const f of statusBefore.staged) {
|
|
2883
|
+
await git.raw(["rm", "--cached", "--", f]).catch(() => {
|
|
2884
|
+
});
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2845
2888
|
for (const { relativePath } of fileInfos) {
|
|
2846
|
-
|
|
2889
|
+
try {
|
|
2890
|
+
await git.add(relativePath);
|
|
2891
|
+
} catch (addErr) {
|
|
2892
|
+
const msg = addErr instanceof Error ? addErr.message : String(addErr);
|
|
2893
|
+
if (msg.includes("ignored by one of your .gitignore files")) {
|
|
2894
|
+
const isTracked = await git.raw(["ls-files", "--error-unmatch", relativePath]).then(() => true).catch(() => false);
|
|
2895
|
+
if (isTracked) {
|
|
2896
|
+
await git.raw(["add", "-f", relativePath]);
|
|
2897
|
+
} else {
|
|
2898
|
+
throw new Error(
|
|
2899
|
+
`File is gitignored and untracked, cannot stage: ${relativePath}`
|
|
2900
|
+
);
|
|
2901
|
+
}
|
|
2902
|
+
} else {
|
|
2903
|
+
throw new Error(cleanGitError(msg));
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
}
|
|
2907
|
+
const statusAfterAdd = await git.status();
|
|
2908
|
+
const stagedSet = new Set(statusAfterAdd.staged);
|
|
2909
|
+
const anyStagedForCommit = fileInfos.some(
|
|
2910
|
+
(fi) => stagedSet.has(fi.relativePath)
|
|
2911
|
+
);
|
|
2912
|
+
if (!anyStagedForCommit) {
|
|
2913
|
+
throw new Error(
|
|
2914
|
+
`Nothing staged for this commit \u2014 files may already be committed or have no changes`
|
|
2915
|
+
);
|
|
2847
2916
|
}
|
|
2848
2917
|
const message = formatCommitMessage(commit);
|
|
2849
2918
|
const result = await git.commit(message);
|
|
@@ -2893,8 +2962,14 @@ ${commit.body}`;
|
|
|
2893
2962
|
}
|
|
2894
2963
|
return message;
|
|
2895
2964
|
}
|
|
2896
|
-
async function checkStaleness(cwd, plan,
|
|
2965
|
+
async function checkStaleness(cwd, plan, scope) {
|
|
2966
|
+
const logger = useLogger();
|
|
2897
2967
|
const planFiles = new Set(plan.commits.flatMap((c) => c.files));
|
|
2968
|
+
await logger.debug("checkStaleness: start", {
|
|
2969
|
+
scope,
|
|
2970
|
+
cwd,
|
|
2971
|
+
planFiles: [...planFiles]
|
|
2972
|
+
});
|
|
2898
2973
|
if (planFiles.size === 0) {
|
|
2899
2974
|
return { isStale: false, reason: "" };
|
|
2900
2975
|
}
|
|
@@ -2902,8 +2977,22 @@ async function checkStaleness(cwd, plan, _scope) {
|
|
|
2902
2977
|
for (const [repoPath, fileInfos] of filesByRepo) {
|
|
2903
2978
|
const currentStatus = await getGitStatus(repoPath);
|
|
2904
2979
|
const currentFiles = new Set(getAllChangedFiles(currentStatus));
|
|
2980
|
+
await logger.debug("checkStaleness: repo status", {
|
|
2981
|
+
repoPath,
|
|
2982
|
+
staged: currentStatus.staged,
|
|
2983
|
+
unstaged: currentStatus.unstaged,
|
|
2984
|
+
untracked: currentStatus.untracked,
|
|
2985
|
+
expected: fileInfos.map((f) => f.relativePath)
|
|
2986
|
+
});
|
|
2905
2987
|
for (const { relativePath, originalPath } of fileInfos) {
|
|
2906
2988
|
if (!currentFiles.has(relativePath)) {
|
|
2989
|
+
await logger.warn("checkStaleness: file not in current changes", {
|
|
2990
|
+
scope,
|
|
2991
|
+
repoPath,
|
|
2992
|
+
originalPath,
|
|
2993
|
+
relativePath,
|
|
2994
|
+
currentFiles: [...currentFiles]
|
|
2995
|
+
});
|
|
2907
2996
|
return {
|
|
2908
2997
|
isStale: true,
|
|
2909
2998
|
reason: `File no longer has changes: ${originalPath}. Regenerate plan or use --force.`
|
|
@@ -2945,7 +3034,8 @@ async function pushCommits(cwd, options) {
|
|
|
2945
3034
|
commitsPushed: commitsToPush
|
|
2946
3035
|
};
|
|
2947
3036
|
} catch (error) {
|
|
2948
|
-
const
|
|
3037
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
3038
|
+
const message = cleanGitError2(raw);
|
|
2949
3039
|
const branch = await getCurrentBranch(cwd).catch(() => "unknown");
|
|
2950
3040
|
return {
|
|
2951
3041
|
success: false,
|
|
@@ -2956,6 +3046,9 @@ async function pushCommits(cwd, options) {
|
|
|
2956
3046
|
};
|
|
2957
3047
|
}
|
|
2958
3048
|
}
|
|
3049
|
+
function cleanGitError2(message) {
|
|
3050
|
+
return message.split("\n").filter((line) => !line.trimStart().startsWith("hint:")).join("\n").trim();
|
|
3051
|
+
}
|
|
2959
3052
|
async function countCommitsToPush(git, remote, branch) {
|
|
2960
3053
|
try {
|
|
2961
3054
|
await git.fetch(remote, branch);
|
|
@@ -3021,12 +3114,21 @@ async function loadPlan(cwd, scope = "root") {
|
|
|
3021
3114
|
const data = JSON.parse(content);
|
|
3022
3115
|
const result = CommitPlanSchema.safeParse(data);
|
|
3023
3116
|
if (!result.success) {
|
|
3024
|
-
|
|
3025
|
-
|
|
3117
|
+
throw new Error(
|
|
3118
|
+
`Commit plan is corrupted and cannot be loaded. Run 'kb commit:generate' to create a new plan.
|
|
3119
|
+
` + result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n")
|
|
3120
|
+
);
|
|
3026
3121
|
}
|
|
3027
3122
|
return result.data;
|
|
3028
3123
|
} catch (err) {
|
|
3029
|
-
|
|
3124
|
+
if (err instanceof SyntaxError) {
|
|
3125
|
+
throw new Error(
|
|
3126
|
+
`Commit plan file is not valid JSON. Run 'kb commit:generate' to create a new plan.`
|
|
3127
|
+
);
|
|
3128
|
+
}
|
|
3129
|
+
if (err instanceof Error && err.message.startsWith("Commit plan is corrupted")) {
|
|
3130
|
+
throw err;
|
|
3131
|
+
}
|
|
3030
3132
|
return null;
|
|
3031
3133
|
}
|
|
3032
3134
|
}
|