@kb-labs/commit-cli 0.5.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.
Files changed (118) hide show
  1. package/README.md +120 -0
  2. package/dist/cli/commands/apply.d.ts +22 -0
  3. package/dist/cli/commands/apply.js +132 -0
  4. package/dist/cli/commands/apply.js.map +1 -0
  5. package/dist/cli/commands/flags.d.ts +99 -0
  6. package/dist/cli/commands/flags.js +73 -0
  7. package/dist/cli/commands/flags.js.map +1 -0
  8. package/dist/cli/commands/generate.d.ts +45 -0
  9. package/dist/cli/commands/generate.js +149 -0
  10. package/dist/cli/commands/generate.js.map +1 -0
  11. package/dist/cli/commands/index.d.ts +1 -0
  12. package/dist/cli/commands/index.js +73 -0
  13. package/dist/cli/commands/index.js.map +1 -0
  14. package/dist/cli/commands/open.d.ts +50 -0
  15. package/dist/cli/commands/open.js +80 -0
  16. package/dist/cli/commands/open.js.map +1 -0
  17. package/dist/cli/commands/push.d.ts +18 -0
  18. package/dist/cli/commands/push.js +71 -0
  19. package/dist/cli/commands/push.js.map +1 -0
  20. package/dist/cli/commands/reset.d.ts +15 -0
  21. package/dist/cli/commands/reset.js +52 -0
  22. package/dist/cli/commands/reset.js.map +1 -0
  23. package/dist/cli/commands/run.d.ts +51 -0
  24. package/dist/cli/commands/run.js +190 -0
  25. package/dist/cli/commands/run.js.map +1 -0
  26. package/dist/index.d.ts +5 -0
  27. package/dist/index.js +916 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/lifecycle/setup.d.ts +53 -0
  30. package/dist/lifecycle/setup.js +96 -0
  31. package/dist/lifecycle/setup.js.map +1 -0
  32. package/dist/manifest.d.ts +207 -0
  33. package/dist/manifest.js +833 -0
  34. package/dist/manifest.js.map +1 -0
  35. package/dist/rest/handlers/actions-handler.d.ts +16 -0
  36. package/dist/rest/handlers/actions-handler.js +15 -0
  37. package/dist/rest/handlers/actions-handler.js.map +1 -0
  38. package/dist/rest/handlers/apply-handler.d.ts +26 -0
  39. package/dist/rest/handlers/apply-handler.js +115 -0
  40. package/dist/rest/handlers/apply-handler.js.map +1 -0
  41. package/dist/rest/handlers/diff-handler.d.ts +20 -0
  42. package/dist/rest/handlers/diff-handler.js +64 -0
  43. package/dist/rest/handlers/diff-handler.js.map +1 -0
  44. package/dist/rest/handlers/files-handler.d.ts +16 -0
  45. package/dist/rest/handlers/files-handler.js +143 -0
  46. package/dist/rest/handlers/files-handler.js.map +1 -0
  47. package/dist/rest/handlers/generate-handler.d.ts +66 -0
  48. package/dist/rest/handlers/generate-handler.js +97 -0
  49. package/dist/rest/handlers/generate-handler.js.map +1 -0
  50. package/dist/rest/handlers/git-status-handler.d.ts +13 -0
  51. package/dist/rest/handlers/git-status-handler.js +58 -0
  52. package/dist/rest/handlers/git-status-handler.js.map +1 -0
  53. package/dist/rest/handlers/patch-plan-handler.d.ts +23 -0
  54. package/dist/rest/handlers/patch-plan-handler.js +50 -0
  55. package/dist/rest/handlers/patch-plan-handler.js.map +1 -0
  56. package/dist/rest/handlers/plan-handler.d.ts +50 -0
  57. package/dist/rest/handlers/plan-handler.js +32 -0
  58. package/dist/rest/handlers/plan-handler.js.map +1 -0
  59. package/dist/rest/handlers/push-handler.d.ts +25 -0
  60. package/dist/rest/handlers/push-handler.js +75 -0
  61. package/dist/rest/handlers/push-handler.js.map +1 -0
  62. package/dist/rest/handlers/regenerate-handler.d.ts +37 -0
  63. package/dist/rest/handlers/regenerate-handler.js +124 -0
  64. package/dist/rest/handlers/regenerate-handler.js.map +1 -0
  65. package/dist/rest/handlers/reset-handler.d.ts +17 -0
  66. package/dist/rest/handlers/reset-handler.js +30 -0
  67. package/dist/rest/handlers/reset-handler.js.map +1 -0
  68. package/dist/rest/handlers/scope-resolver.d.ts +15 -0
  69. package/dist/rest/handlers/scope-resolver.js +12 -0
  70. package/dist/rest/handlers/scope-resolver.js.map +1 -0
  71. package/dist/rest/handlers/scopes-handler.d.ts +12 -0
  72. package/dist/rest/handlers/scopes-handler.js +24 -0
  73. package/dist/rest/handlers/scopes-handler.js.map +1 -0
  74. package/dist/rest/handlers/status-handler.d.ts +27 -0
  75. package/dist/rest/handlers/status-handler.js +91 -0
  76. package/dist/rest/handlers/status-handler.js.map +1 -0
  77. package/dist/rest/handlers/summarize-handler.d.ts +21 -0
  78. package/dist/rest/handlers/summarize-handler.js +106 -0
  79. package/dist/rest/handlers/summarize-handler.js.map +1 -0
  80. package/dist/widgets/220.js +446 -0
  81. package/dist/widgets/220.js.map +1 -0
  82. package/dist/widgets/331.js +2 -0
  83. package/dist/widgets/331.js.map +1 -0
  84. package/dist/widgets/403.js +2 -0
  85. package/dist/widgets/403.js.map +1 -0
  86. package/dist/widgets/406.js +35 -0
  87. package/dist/widgets/406.js.map +1 -0
  88. package/dist/widgets/455.js +2 -0
  89. package/dist/widgets/455.js.map +1 -0
  90. package/dist/widgets/482.js +2 -0
  91. package/dist/widgets/482.js.map +1 -0
  92. package/dist/widgets/485.js +2 -0
  93. package/dist/widgets/485.js.map +1 -0
  94. package/dist/widgets/527.js +2 -0
  95. package/dist/widgets/527.js.map +1 -0
  96. package/dist/widgets/628.js +2 -0
  97. package/dist/widgets/628.js.map +1 -0
  98. package/dist/widgets/694.js +2 -0
  99. package/dist/widgets/694.js.map +1 -0
  100. package/dist/widgets/712.js +2 -0
  101. package/dist/widgets/712.js.map +1 -0
  102. package/dist/widgets/866.js +2 -0
  103. package/dist/widgets/866.js.map +1 -0
  104. package/dist/widgets/915.js +39 -0
  105. package/dist/widgets/915.js.map +1 -0
  106. package/dist/widgets/957.js +10 -0
  107. package/dist/widgets/957.js.map +1 -0
  108. package/dist/widgets/983.js +2 -0
  109. package/dist/widgets/983.js.map +1 -0
  110. package/dist/widgets/@mf-types.d.ts +3 -0
  111. package/dist/widgets/@mf-types.zip +0 -0
  112. package/dist/widgets/__federation_expose_CommitOverview.js +2 -0
  113. package/dist/widgets/__federation_expose_CommitOverview.js.map +1 -0
  114. package/dist/widgets/mf-manifest.json +260 -0
  115. package/dist/widgets/mf-stats.json +302 -0
  116. package/dist/widgets/remoteEntry.js +7 -0
  117. package/dist/widgets/remoteEntry.js.map +1 -0
  118. package/package.json +95 -0
@@ -0,0 +1,124 @@
1
+ import { defineHandler, useLogger, useLLM, useConfig } from '@kb-labs/sdk';
2
+ import { resolveCommitConfig, COMMIT_CACHE_PREFIX } from '@kb-labs/commit-contracts';
3
+ import { loadPlan, savePlan } from '@kb-labs/commit-core/storage';
4
+ import { getFileSummaries, getFileDiffs } from '@kb-labs/commit-core/analyzer';
5
+ import { SYSTEM_PROMPT_WITH_DIFF, COMMIT_PLAN_TOOL } from '@kb-labs/commit-core/generator';
6
+ import * as path from 'path';
7
+
8
+ // src/rest/handlers/regenerate-handler.ts
9
+ function resolveScopePath(baseCwd, scopeId = "root", scopes) {
10
+ const scopeDef = scopes?.find((s) => s.id === scopeId);
11
+ const relativePath = scopeDef?.path ?? (scopeId === "root" ? "." : scopeId);
12
+ return relativePath === "." ? baseCwd : path.join(baseCwd, relativePath);
13
+ }
14
+
15
+ // src/rest/handlers/regenerate-handler.ts
16
+ var regenerate_handler_default = defineHandler({
17
+ async execute(ctx, input) {
18
+ const logger = useLogger();
19
+ const { scope = "root", commitId, instruction } = input.body ?? {};
20
+ if (!commitId) {
21
+ throw new Error("commitId is required");
22
+ }
23
+ const plan = await loadPlan(ctx.cwd, scope);
24
+ if (!plan) {
25
+ throw new Error("No commit plan found. Generate a plan first.");
26
+ }
27
+ const commitIndex = plan.commits.findIndex((c) => c.id === commitId);
28
+ if (commitIndex === -1) {
29
+ throw new Error(`Commit "${commitId}" not found in plan`);
30
+ }
31
+ const existingCommit = plan.commits[commitIndex];
32
+ const files = existingCommit.files;
33
+ if (files.length === 0) {
34
+ throw new Error("Commit has no files to regenerate");
35
+ }
36
+ const llm = useLLM();
37
+ if (!llm || !llm.chatWithTools) {
38
+ throw new Error("LLM is not available for regeneration");
39
+ }
40
+ try {
41
+ const fileConfig = await useConfig();
42
+ const config = resolveCommitConfig(fileConfig ?? {});
43
+ const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);
44
+ const summaries = await getFileSummaries(scopeCwd, files);
45
+ const diffs = await getFileDiffs(scopeCwd, files);
46
+ const fileContext = summaries.map((s) => {
47
+ const diff = diffs.get(s.path) || "";
48
+ return `File: ${s.path} (${s.status}, +${s.additions}/-${s.deletions})${diff ? `
49
+ Diff:
50
+ ${diff}` : ""}`;
51
+ }).join("\n\n");
52
+ const instructionText = instruction ? `
53
+
54
+ User instruction: ${instruction}` : "";
55
+ const userPrompt = `Analyze these files and generate exactly ONE commit following conventional commit conventions.
56
+ These files were previously grouped as: ${existingCommit.type}(${existingCommit.scope || ""}): ${existingCommit.message}
57
+
58
+ Re-analyze them and generate an improved commit message.${instructionText}
59
+
60
+ Files to analyze:
61
+ ${fileContext}`;
62
+ const messages = [
63
+ { role: "system", content: SYSTEM_PROMPT_WITH_DIFF },
64
+ { role: "user", content: userPrompt }
65
+ ];
66
+ const response = await llm.chatWithTools(messages, {
67
+ tools: [COMMIT_PLAN_TOOL],
68
+ toolChoice: {
69
+ type: "function",
70
+ function: { name: "generate_commit_plan" }
71
+ },
72
+ temperature: 0.3
73
+ });
74
+ const toolCall = response.toolCalls?.[0];
75
+ if (!toolCall || toolCall.name !== "generate_commit_plan") {
76
+ throw new Error("LLM did not call generate_commit_plan tool");
77
+ }
78
+ const toolArgs = toolCall.input;
79
+ const regeneratedCommit = toolArgs.commits[0];
80
+ if (!regeneratedCommit) {
81
+ throw new Error("LLM returned no commits");
82
+ }
83
+ regeneratedCommit.id = existingCommit.id;
84
+ regeneratedCommit.files = existingCommit.files;
85
+ plan.commits[commitIndex] = regeneratedCommit;
86
+ await savePlan(ctx.cwd, plan, scope);
87
+ const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;
88
+ await ctx.platform.cache.delete(appliedCacheKey);
89
+ await logger.info("[regenerate-handler] Commit regenerated", {
90
+ scope,
91
+ commitId,
92
+ oldMessage: `${existingCommit.type}: ${existingCommit.message}`,
93
+ newMessage: `${regeneratedCommit.type}: ${regeneratedCommit.message}`
94
+ });
95
+ if (ctx.platform.analytics) {
96
+ await ctx.platform.analytics.track("commit.regenerate.success", {
97
+ scope,
98
+ commitId,
99
+ hadInstruction: !!instruction,
100
+ filesCount: files.length
101
+ });
102
+ }
103
+ return {
104
+ success: true,
105
+ scope,
106
+ commitId,
107
+ commit: regeneratedCommit
108
+ };
109
+ } catch (error) {
110
+ if (ctx.platform.analytics) {
111
+ await ctx.platform.analytics.track("commit.regenerate.error", {
112
+ scope,
113
+ commitId,
114
+ error: error instanceof Error ? error.message : String(error)
115
+ });
116
+ }
117
+ throw new Error(`Failed to regenerate commit: ${error}`);
118
+ }
119
+ }
120
+ });
121
+
122
+ export { regenerate_handler_default as default };
123
+ //# sourceMappingURL=regenerate-handler.js.map
124
+ //# sourceMappingURL=regenerate-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/scope-resolver.ts","../../../src/rest/handlers/regenerate-handler.ts"],"names":[],"mappings":"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","file":"regenerate-handler.js","sourcesContent":["import * as path from 'node:path';\nimport type { CommitScope } from '@kb-labs/commit-contracts';\n\n/**\n * Resolves a scope id to an absolute filesystem path.\n *\n * Looks up the scope in the provided scopes array by id.\n * Falls back to treating the id as a relative path from baseCwd (legacy compat).\n *\n * @param baseCwd - Workspace root (ctx.cwd)\n * @param scopeId - Scope identifier, e.g. \"root\", \"public/kb-labs\"\n * @param scopes - Configured scopes from CommitPluginConfig.scope.scopes\n */\nexport function resolveScopePath(\n baseCwd: string,\n scopeId: string = 'root',\n scopes?: CommitScope[],\n): string {\n const scopeDef = scopes?.find((s) => s.id === scopeId);\n const relativePath = scopeDef?.path ?? (scopeId === 'root' ? '.' : scopeId);\n return relativePath === '.' ? baseCwd : path.join(baseCwd, relativePath);\n}\n","import { defineHandler, useConfig, type PluginContextV3, type RestInput, useLLM, useLogger } from '@kb-labs/sdk';\nimport type { LLMMessage } from '@kb-labs/sdk';\nimport {\n COMMIT_CACHE_PREFIX,\n type RegenerateCommitRequest,\n type RegenerateCommitResponse,\n type CommitGroup,\n type CommitPluginConfig,\n resolveCommitConfig,\n} from '@kb-labs/commit-contracts';\nimport { loadPlan, savePlan } from '@kb-labs/commit-core/storage';\nimport { getFileSummaries, getFileDiffs } from '@kb-labs/commit-core/analyzer';\nimport { COMMIT_PLAN_TOOL, SYSTEM_PROMPT_WITH_DIFF } from '@kb-labs/commit-core/generator';\nimport { resolveScopePath } from './scope-resolver';\n\n/**\n * POST /regenerate-commit handler\n *\n * Re-analyzes files from a single commit using LLM and replaces it in the plan.\n * Uses Phase 2 approach (with diff context) for best results.\n */\nexport default defineHandler({\n async execute(ctx: PluginContextV3, input: RestInput<unknown, RegenerateCommitRequest>): Promise<RegenerateCommitResponse> {\n const logger = useLogger();\n const { scope = 'root', commitId, instruction } = input.body ?? {};\n\n if (!commitId) {\n throw new Error('commitId is required');\n }\n\n const plan = await loadPlan(ctx.cwd, scope);\n if (!plan) {\n throw new Error('No commit plan found. Generate a plan first.');\n }\n\n const commitIndex = plan.commits.findIndex(c => c.id === commitId);\n if (commitIndex === -1) {\n throw new Error(`Commit \"${commitId}\" not found in plan`);\n }\n\n const existingCommit = plan.commits[commitIndex]!;\n const files = existingCommit.files;\n\n if (files.length === 0) {\n throw new Error('Commit has no files to regenerate');\n }\n\n const llm = useLLM();\n if (!llm || !llm.chatWithTools) {\n throw new Error('LLM is not available for regeneration');\n }\n\n try {\n // Resolve scope for file operations\n const fileConfig = await useConfig<Partial<CommitPluginConfig>>();\n const config = resolveCommitConfig(fileConfig ?? {});\n const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);\n\n // Get fresh file summaries and diffs\n const summaries = await getFileSummaries(scopeCwd, files);\n const diffs = await getFileDiffs(scopeCwd, files);\n\n // Build prompt for single commit regeneration\n const fileContext = summaries.map(s => {\n const diff = diffs.get(s.path) || '';\n return `File: ${s.path} (${s.status}, +${s.additions}/-${s.deletions})${diff ? `\\nDiff:\\n${diff}` : ''}`;\n }).join('\\n\\n');\n\n const instructionText = instruction\n ? `\\n\\nUser instruction: ${instruction}`\n : '';\n\n const userPrompt = `Analyze these files and generate exactly ONE commit following conventional commit conventions.\nThese files were previously grouped as: ${existingCommit.type}(${existingCommit.scope || ''}): ${existingCommit.message}\n\nRe-analyze them and generate an improved commit message.${instructionText}\n\nFiles to analyze:\n${fileContext}`;\n\n const messages: LLMMessage[] = [\n { role: 'system', content: SYSTEM_PROMPT_WITH_DIFF },\n { role: 'user', content: userPrompt },\n ];\n\n const response = await llm.chatWithTools(messages, {\n tools: [COMMIT_PLAN_TOOL],\n toolChoice: {\n type: 'function',\n function: { name: 'generate_commit_plan' },\n },\n temperature: 0.3,\n });\n\n const toolCall = response.toolCalls?.[0];\n if (!toolCall || toolCall.name !== 'generate_commit_plan') {\n throw new Error('LLM did not call generate_commit_plan tool');\n }\n\n const toolArgs = toolCall.input as { commits: CommitGroup[] };\n\n // Take the first commit from LLM output (we asked for exactly one)\n const regeneratedCommit = toolArgs.commits[0];\n if (!regeneratedCommit) {\n throw new Error('LLM returned no commits');\n }\n\n // Preserve the original commit ID and files\n regeneratedCommit.id = existingCommit.id;\n regeneratedCommit.files = existingCommit.files;\n\n // Replace in plan\n plan.commits[commitIndex] = regeneratedCommit;\n await savePlan(ctx.cwd, plan, scope);\n\n // Regenerated plan is no longer considered applied.\n const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;\n await ctx.platform.cache.delete(appliedCacheKey);\n\n await logger.info('[regenerate-handler] Commit regenerated', {\n scope,\n commitId,\n oldMessage: `${existingCommit.type}: ${existingCommit.message}`,\n newMessage: `${regeneratedCommit.type}: ${regeneratedCommit.message}`,\n });\n\n // Track analytics\n if (ctx.platform.analytics) {\n await ctx.platform.analytics.track('commit.regenerate.success', {\n scope,\n commitId,\n hadInstruction: !!instruction,\n filesCount: files.length,\n });\n }\n\n return {\n success: true,\n scope,\n commitId,\n commit: regeneratedCommit,\n };\n } catch (error) {\n if (ctx.platform.analytics) {\n await ctx.platform.analytics.track('commit.regenerate.error', {\n scope,\n commitId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n\n throw new Error(`Failed to regenerate commit: ${error}`);\n }\n },\n});\n"]}
@@ -0,0 +1,17 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { RestInput } from '@kb-labs/sdk';
3
+
4
+ /**
5
+ * DELETE /plan handler
6
+ *
7
+ * Deletes the current commit plan.
8
+ */
9
+ declare const _default: _kb_labs_shared_command_kit.Handler<unknown, RestInput<{
10
+ scope?: string;
11
+ }, unknown, unknown>, {
12
+ message: string;
13
+ scope: string;
14
+ success: boolean;
15
+ }>;
16
+
17
+ export { _default as default };
@@ -0,0 +1,30 @@
1
+ import { defineHandler } from '@kb-labs/sdk';
2
+ import { COMMIT_CACHE_PREFIX } from '@kb-labs/commit-contracts';
3
+ import { clearPlan } from '@kb-labs/commit-core/storage';
4
+
5
+ // src/rest/handlers/reset-handler.ts
6
+ var reset_handler_default = defineHandler({
7
+ async execute(ctx, input) {
8
+ const scope = input.query?.scope || "root";
9
+ try {
10
+ await clearPlan(ctx.cwd, scope);
11
+ const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;
12
+ await ctx.platform.cache.delete(appliedCacheKey);
13
+ return {
14
+ success: true,
15
+ message: "Commit plan deleted successfully",
16
+ scope
17
+ };
18
+ } catch (error) {
19
+ return {
20
+ success: false,
21
+ message: `Failed to delete plan: ${error}`,
22
+ scope
23
+ };
24
+ }
25
+ }
26
+ });
27
+
28
+ export { reset_handler_default as default };
29
+ //# sourceMappingURL=reset-handler.js.map
30
+ //# sourceMappingURL=reset-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/reset-handler.ts"],"names":[],"mappings":";;;;;AAYA,IAAO,wBAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CAAQ,GAAA,EAAsB,KAAA,EAAuE;AACzG,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,KAAA,EAAO,KAAA,IAAS,MAAA;AAEpC,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,CAAU,GAAA,CAAI,GAAA,EAAK,KAAK,CAAA;AAC9B,MAAA,MAAM,eAAA,GAAkB,CAAA,EAAG,mBAAmB,CAAA,aAAA,EAAgB,KAAK,CAAA,CAAA;AACnE,MAAA,MAAM,GAAA,CAAI,QAAA,CAAS,KAAA,CAAM,MAAA,CAAO,eAAe,CAAA;AAE/C,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,IAAA;AAAA,QACT,OAAA,EAAS,kCAAA;AAAA,QACT;AAAA,OACF;AAAA,IACF,SAAS,KAAA,EAAO;AACd,MAAA,OAAO;AAAA,QACL,OAAA,EAAS,KAAA;AAAA,QACT,OAAA,EAAS,0BAA0B,KAAK,CAAA,CAAA;AAAA,QACxC;AAAA,OACF;AAAA,IACF;AAAA,EACF;AACF,CAAC","file":"reset-handler.js","sourcesContent":["import { defineHandler, type PluginContextV3, type RestInput } from '@kb-labs/sdk';\nimport {\n COMMIT_CACHE_PREFIX,\n type ResetResponse,\n} from '@kb-labs/commit-contracts';\nimport { clearPlan } from '@kb-labs/commit-core/storage';\n\n/**\n * DELETE /plan handler\n *\n * Deletes the current commit plan.\n */\nexport default defineHandler({\n async execute(ctx: PluginContextV3, input: RestInput<{ scope?: string }, unknown>): Promise<ResetResponse> {\n const scope = input.query?.scope || 'root';\n\n try {\n await clearPlan(ctx.cwd, scope);\n const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;\n await ctx.platform.cache.delete(appliedCacheKey);\n\n return {\n success: true,\n message: 'Commit plan deleted successfully',\n scope,\n };\n } catch (error) {\n return {\n success: false,\n message: `Failed to delete plan: ${error}`,\n scope,\n };\n }\n },\n});\n"]}
@@ -0,0 +1,15 @@
1
+ import { CommitScope } from '@kb-labs/commit-contracts';
2
+
3
+ /**
4
+ * Resolves a scope id to an absolute filesystem path.
5
+ *
6
+ * Looks up the scope in the provided scopes array by id.
7
+ * Falls back to treating the id as a relative path from baseCwd (legacy compat).
8
+ *
9
+ * @param baseCwd - Workspace root (ctx.cwd)
10
+ * @param scopeId - Scope identifier, e.g. "root", "public/kb-labs"
11
+ * @param scopes - Configured scopes from CommitPluginConfig.scope.scopes
12
+ */
13
+ declare function resolveScopePath(baseCwd: string, scopeId?: string, scopes?: CommitScope[]): string;
14
+
15
+ export { resolveScopePath };
@@ -0,0 +1,12 @@
1
+ import * as path from 'path';
2
+
3
+ // src/rest/handlers/scope-resolver.ts
4
+ function resolveScopePath(baseCwd, scopeId = "root", scopes) {
5
+ const scopeDef = scopes?.find((s) => s.id === scopeId);
6
+ const relativePath = scopeDef?.path ?? (scopeId === "root" ? "." : scopeId);
7
+ return relativePath === "." ? baseCwd : path.join(baseCwd, relativePath);
8
+ }
9
+
10
+ export { resolveScopePath };
11
+ //# sourceMappingURL=scope-resolver.js.map
12
+ //# sourceMappingURL=scope-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/scope-resolver.ts"],"names":[],"mappings":";;;AAaO,SAAS,gBAAA,CACd,OAAA,EACA,OAAA,GAAkB,MAAA,EAClB,MAAA,EACQ;AACR,EAAA,MAAM,WAAW,MAAA,EAAQ,IAAA,CAAK,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,OAAO,CAAA;AACrD,EAAA,MAAM,YAAA,GAAe,QAAA,EAAU,IAAA,KAAS,OAAA,KAAY,SAAS,GAAA,GAAM,OAAA,CAAA;AACnE,EAAA,OAAO,YAAA,KAAiB,GAAA,GAAM,OAAA,GAAe,IAAA,CAAA,IAAA,CAAK,SAAS,YAAY,CAAA;AACzE","file":"scope-resolver.js","sourcesContent":["import * as path from 'node:path';\nimport type { CommitScope } from '@kb-labs/commit-contracts';\n\n/**\n * Resolves a scope id to an absolute filesystem path.\n *\n * Looks up the scope in the provided scopes array by id.\n * Falls back to treating the id as a relative path from baseCwd (legacy compat).\n *\n * @param baseCwd - Workspace root (ctx.cwd)\n * @param scopeId - Scope identifier, e.g. \"root\", \"public/kb-labs\"\n * @param scopes - Configured scopes from CommitPluginConfig.scope.scopes\n */\nexport function resolveScopePath(\n baseCwd: string,\n scopeId: string = 'root',\n scopes?: CommitScope[],\n): string {\n const scopeDef = scopes?.find((s) => s.id === scopeId);\n const relativePath = scopeDef?.path ?? (scopeId === 'root' ? '.' : scopeId);\n return relativePath === '.' ? baseCwd : path.join(baseCwd, relativePath);\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { SelectData } from '@kb-labs/sdk';
3
+
4
+ /**
5
+ * GET /scopes handler
6
+ *
7
+ * Returns available scopes from kb.config.json (plugins.commit.scope.scopes).
8
+ * Falls back to a single "root" scope if not configured.
9
+ */
10
+ declare const _default: _kb_labs_shared_command_kit.Handler<unknown, unknown, SelectData>;
11
+
12
+ export { _default as default };
@@ -0,0 +1,24 @@
1
+ import { defineHandler, useConfig } from '@kb-labs/sdk';
2
+ import { resolveCommitConfig } from '@kb-labs/commit-contracts';
3
+
4
+ // src/rest/handlers/scopes-handler.ts
5
+ var scopes_handler_default = defineHandler({
6
+ async execute(_ctx, _input) {
7
+ const fileConfig = await useConfig();
8
+ const config = resolveCommitConfig(fileConfig ?? {});
9
+ const scopes = config.scope?.scopes ?? [{ id: "root", label: "root", path: "." }];
10
+ const defaultId = config.scope?.default ?? scopes[0]?.id ?? "root";
11
+ return {
12
+ value: defaultId,
13
+ options: scopes.map((s) => ({
14
+ value: s.id,
15
+ label: s.label,
16
+ description: s.description
17
+ }))
18
+ };
19
+ }
20
+ });
21
+
22
+ export { scopes_handler_default as default };
23
+ //# sourceMappingURL=scopes-handler.js.map
24
+ //# sourceMappingURL=scopes-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/scopes-handler.ts"],"names":[],"mappings":";;;;AASA,IAAO,yBAAQ,aAAA,CAAc;AAAA,EAC3B,MAAM,OAAA,CAAQ,IAAA,EAAuB,MAAA,EAAsC;AACzE,IAAA,MAAM,UAAA,GAAa,MAAM,SAAA,EAAuC;AAChE,IAAA,MAAM,MAAA,GAAS,mBAAA,CAAoB,UAAA,IAAc,EAAE,CAAA;AAEnD,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,KAAA,EAAO,MAAA,IAAU,CAAC,EAAE,EAAA,EAAI,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,GAAA,EAAK,CAAA;AAChF,IAAA,MAAM,YAAY,MAAA,CAAO,KAAA,EAAO,WAAW,MAAA,CAAO,CAAC,GAAG,EAAA,IAAM,MAAA;AAE5D,IAAA,OAAO;AAAA,MACL,KAAA,EAAO,SAAA;AAAA,MACP,OAAA,EAAS,MAAA,CAAO,GAAA,CAAI,CAAC,CAAA,MAAO;AAAA,QAC1B,OAAO,CAAA,CAAE,EAAA;AAAA,QACT,OAAO,CAAA,CAAE,KAAA;AAAA,QACT,aAAa,CAAA,CAAE;AAAA,OACjB,CAAE;AAAA,KACJ;AAAA,EACF;AACF,CAAC","file":"scopes-handler.js","sourcesContent":["import { defineHandler, useConfig, type PluginContextV3, type SelectData } from '@kb-labs/sdk';\nimport { type CommitPluginConfig, resolveCommitConfig } from '@kb-labs/commit-contracts';\n\n/**\n * GET /scopes handler\n *\n * Returns available scopes from kb.config.json (plugins.commit.scope.scopes).\n * Falls back to a single \"root\" scope if not configured.\n */\nexport default defineHandler({\n async execute(_ctx: PluginContextV3, _input: unknown): Promise<SelectData> {\n const fileConfig = await useConfig<Partial<CommitPluginConfig>>();\n const config = resolveCommitConfig(fileConfig ?? {});\n\n const scopes = config.scope?.scopes ?? [{ id: 'root', label: 'root', path: '.' }];\n const defaultId = config.scope?.default ?? scopes[0]?.id ?? 'root';\n\n return {\n value: defaultId,\n options: scopes.map((s) => ({\n value: s.id,\n label: s.label,\n description: s.description,\n })),\n };\n },\n});\n"]}
@@ -0,0 +1,27 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { RestInput } from '@kb-labs/sdk';
3
+
4
+ /**
5
+ * GET /status handler
6
+ *
7
+ * Returns current status for a scope according to StatusResponse contract.
8
+ * Uses ctx.platform.cache for git status caching.
9
+ */
10
+ declare const _default: _kb_labs_shared_command_kit.Handler<unknown, RestInput<{
11
+ scope?: string;
12
+ }, unknown, unknown>, {
13
+ scope: string;
14
+ hasPlan: boolean;
15
+ planStatus: "applied" | "pushed" | "idle" | "ready";
16
+ filesChanged: number;
17
+ commitsInPlan: number;
18
+ commitsApplied: number;
19
+ gitStatus?: {
20
+ staged: string[];
21
+ unstaged: string[];
22
+ untracked: string[];
23
+ } | undefined;
24
+ planTimestamp?: string | undefined;
25
+ }>;
26
+
27
+ export { _default as default };
@@ -0,0 +1,91 @@
1
+ import { defineHandler, useConfig } from '@kb-labs/sdk';
2
+ import { loadPlan } from '@kb-labs/commit-core/storage';
3
+ import { getGitStatus } from '@kb-labs/commit-core/analyzer';
4
+ import { COMMIT_CACHE_PREFIX, resolveCommitConfig } from '@kb-labs/commit-contracts';
5
+ import * as path from 'path';
6
+
7
+ // src/rest/handlers/status-handler.ts
8
+ function resolveScopePath(baseCwd, scopeId = "root", scopes) {
9
+ const scopeDef = scopes?.find((s) => s.id === scopeId);
10
+ const relativePath = scopeDef?.path ?? (scopeId === "root" ? "." : scopeId);
11
+ return relativePath === "." ? baseCwd : path.join(baseCwd, relativePath);
12
+ }
13
+
14
+ // src/rest/handlers/status-handler.ts
15
+ var STATUS_CACHE_TTL = 5e3;
16
+ var status_handler_default = defineHandler({
17
+ async execute(ctx, input) {
18
+ const scope = input.query?.scope || "root";
19
+ ctx.platform.logger.info(`[status-handler] Fetching status for scope: ${scope}`);
20
+ try {
21
+ const plan = await loadPlan(ctx.cwd, scope);
22
+ ctx.platform.logger.info(`[status-handler] Plan loaded: ${!!plan}`);
23
+ let filesChanged = 0;
24
+ let gitStatus = null;
25
+ const cacheKey = `${COMMIT_CACHE_PREFIX}git-status:${scope}`;
26
+ ctx.platform.logger.info(`[status-handler] Cache key: ${cacheKey}`);
27
+ const cached = await ctx.platform.cache.get(cacheKey);
28
+ ctx.platform.logger.info(`[status-handler] Cache hit: ${!!cached}`);
29
+ if (cached !== null && cached !== void 0) {
30
+ const cachedData = cached;
31
+ filesChanged = cachedData.count;
32
+ gitStatus = cachedData.status;
33
+ } else {
34
+ const fileConfig = await useConfig();
35
+ const config = resolveCommitConfig(fileConfig ?? {});
36
+ const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);
37
+ ctx.platform.logger.info(`[status-handler] Resolved scope CWD: ${scopeCwd}`);
38
+ gitStatus = await getGitStatus(scopeCwd);
39
+ ctx.platform.logger.info(`[status-handler] Git status fetched - staged: ${gitStatus.staged.length}, unstaged: ${gitStatus.unstaged.length}, untracked: ${gitStatus.untracked.length}`);
40
+ filesChanged = gitStatus.staged.length + gitStatus.unstaged.length + gitStatus.untracked.length;
41
+ ctx.platform.logger.info(`[status-handler] Total files changed: ${filesChanged}`);
42
+ await ctx.platform.cache.set(
43
+ cacheKey,
44
+ { count: filesChanged, status: gitStatus },
45
+ STATUS_CACHE_TTL
46
+ );
47
+ }
48
+ let planStatus = "idle";
49
+ let commitsApplied = 0;
50
+ if (plan) {
51
+ const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;
52
+ const appliedData = await ctx.platform.cache.get(appliedCacheKey);
53
+ if (appliedData) {
54
+ const applied = appliedData;
55
+ if (!applied.planCreatedAt || applied.planCreatedAt === plan.createdAt) {
56
+ commitsApplied = applied.commitsApplied;
57
+ planStatus = "applied";
58
+ } else {
59
+ await ctx.platform.cache.delete(appliedCacheKey);
60
+ planStatus = "ready";
61
+ }
62
+ } else {
63
+ planStatus = "ready";
64
+ }
65
+ }
66
+ return {
67
+ scope,
68
+ hasPlan: !!plan,
69
+ planStatus,
70
+ filesChanged,
71
+ commitsInPlan: plan?.commits.length || 0,
72
+ commitsApplied,
73
+ planTimestamp: plan?.createdAt,
74
+ gitStatus: gitStatus || void 0
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ scope,
79
+ hasPlan: false,
80
+ planStatus: "idle",
81
+ filesChanged: 0,
82
+ commitsInPlan: 0,
83
+ commitsApplied: 0
84
+ };
85
+ }
86
+ }
87
+ });
88
+
89
+ export { status_handler_default as default };
90
+ //# sourceMappingURL=status-handler.js.map
91
+ //# sourceMappingURL=status-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/scope-resolver.ts","../../../src/rest/handlers/status-handler.ts"],"names":[],"mappings":"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","file":"status-handler.js","sourcesContent":["import * as path from 'node:path';\nimport type { CommitScope } from '@kb-labs/commit-contracts';\n\n/**\n * Resolves a scope id to an absolute filesystem path.\n *\n * Looks up the scope in the provided scopes array by id.\n * Falls back to treating the id as a relative path from baseCwd (legacy compat).\n *\n * @param baseCwd - Workspace root (ctx.cwd)\n * @param scopeId - Scope identifier, e.g. \"root\", \"public/kb-labs\"\n * @param scopes - Configured scopes from CommitPluginConfig.scope.scopes\n */\nexport function resolveScopePath(\n baseCwd: string,\n scopeId: string = 'root',\n scopes?: CommitScope[],\n): string {\n const scopeDef = scopes?.find((s) => s.id === scopeId);\n const relativePath = scopeDef?.path ?? (scopeId === 'root' ? '.' : scopeId);\n return relativePath === '.' ? baseCwd : path.join(baseCwd, relativePath);\n}\n","import { defineHandler, useConfig, type RestInput, type PluginContextV3 } from '@kb-labs/sdk';\nimport { loadPlan } from '@kb-labs/commit-core/storage';\nimport { getGitStatus } from '@kb-labs/commit-core/analyzer';\nimport { COMMIT_CACHE_PREFIX, type StatusResponse, type PlanStatus, type CommitPluginConfig, resolveCommitConfig } from '@kb-labs/commit-contracts';\nimport { resolveScopePath } from './scope-resolver';\n\nconst STATUS_CACHE_TTL = 5000; // 5 seconds\n\n/**\n * GET /status handler\n *\n * Returns current status for a scope according to StatusResponse contract.\n * Uses ctx.platform.cache for git status caching.\n */\nexport default defineHandler({\n async execute(ctx: PluginContextV3, input: RestInput<{ scope?: string }>): Promise<StatusResponse> {\n const scope = input.query?.scope || 'root';\n\n ctx.platform.logger.info(`[status-handler] Fetching status for scope: ${scope}`);\n\n try {\n // Load current plan\n const plan = await loadPlan(ctx.cwd, scope);\n ctx.platform.logger.info(`[status-handler] Plan loaded: ${!!plan}`);\n\n // Get git status (with platform cache)\n let filesChanged = 0;\n let gitStatus = null;\n const cacheKey = `${COMMIT_CACHE_PREFIX}git-status:${scope}`;\n ctx.platform.logger.info(`[status-handler] Cache key: ${cacheKey}`);\n\n // Try to get from cache\n const cached = await ctx.platform.cache.get(cacheKey);\n ctx.platform.logger.info(`[status-handler] Cache hit: ${!!cached}`);\n\n if (cached !== null && cached !== undefined) {\n // Use cached value\n const cachedData = cached as { count: number; status: any };\n filesChanged = cachedData.count;\n gitStatus = cachedData.status;\n } else {\n // Fetch fresh git status\n // Resolve scope to actual directory path (same as files-handler)\n const fileConfig = await useConfig<Partial<CommitPluginConfig>>();\n const config = resolveCommitConfig(fileConfig ?? {});\n const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);\n ctx.platform.logger.info(`[status-handler] Resolved scope CWD: ${scopeCwd}`);\n\n gitStatus = await getGitStatus(scopeCwd);\n ctx.platform.logger.info(`[status-handler] Git status fetched - staged: ${gitStatus.staged.length}, unstaged: ${gitStatus.unstaged.length}, untracked: ${gitStatus.untracked.length}`);\n\n filesChanged =\n gitStatus.staged.length +\n gitStatus.unstaged.length +\n gitStatus.untracked.length;\n\n ctx.platform.logger.info(`[status-handler] Total files changed: ${filesChanged}`);\n\n // Cache the result\n await ctx.platform.cache.set(\n cacheKey,\n { count: filesChanged, status: gitStatus },\n STATUS_CACHE_TTL\n );\n }\n\n // Determine plan status\n let planStatus: PlanStatus = 'idle';\n let commitsApplied = 0;\n\n if (plan) {\n // Check if commits were applied (stored in cache after apply)\n const appliedCacheKey = `${COMMIT_CACHE_PREFIX}plan-applied:${scope}`;\n const appliedData = await ctx.platform.cache.get(appliedCacheKey);\n\n if (appliedData) {\n const applied = appliedData as { commitsApplied: number; planCreatedAt?: string };\n // Only trust applied marker if it belongs to the current plan version.\n if (!applied.planCreatedAt || applied.planCreatedAt === plan.createdAt) {\n commitsApplied = applied.commitsApplied;\n planStatus = 'applied';\n } else {\n await ctx.platform.cache.delete(appliedCacheKey);\n planStatus = 'ready';\n }\n } else {\n planStatus = 'ready';\n }\n }\n\n return {\n scope,\n hasPlan: !!plan,\n planStatus,\n filesChanged,\n commitsInPlan: plan?.commits.length || 0,\n commitsApplied,\n planTimestamp: plan?.createdAt,\n gitStatus: gitStatus || undefined,\n };\n } catch (error) {\n return {\n scope,\n hasPlan: false,\n planStatus: 'idle',\n filesChanged: 0,\n commitsInPlan: 0,\n commitsApplied: 0,\n };\n }\n },\n});\n"]}
@@ -0,0 +1,21 @@
1
+ import * as _kb_labs_shared_command_kit from '@kb-labs/shared-command-kit';
2
+ import { RestInput } from '@kb-labs/sdk';
3
+
4
+ /**
5
+ * POST /summarize handler
6
+ *
7
+ * Summarizes changes using LLM. Can summarize:
8
+ * - All changes in scope (if no file specified)
9
+ * - Specific file changes (if file specified)
10
+ */
11
+ declare const _default: _kb_labs_shared_command_kit.Handler<unknown, RestInput<unknown, {
12
+ scope: string;
13
+ file?: string | undefined;
14
+ }, unknown>, {
15
+ scope: string;
16
+ summary: string;
17
+ tokensUsed?: number | undefined;
18
+ file?: string | undefined;
19
+ }>;
20
+
21
+ export { _default as default };
@@ -0,0 +1,106 @@
1
+ import { defineHandler, useLogger, useLLM, useConfig } from '@kb-labs/sdk';
2
+ import { resolveCommitConfig } from '@kb-labs/commit-contracts';
3
+ import { getFileDiff, getGitStatus, getAllChangedFiles } from '@kb-labs/commit-core/analyzer';
4
+ import * as path from 'path';
5
+
6
+ // src/rest/handlers/summarize-handler.ts
7
+ function resolveScopePath(baseCwd, scopeId = "root", scopes) {
8
+ const scopeDef = scopes?.find((s) => s.id === scopeId);
9
+ const relativePath = scopeDef?.path ?? (scopeId === "root" ? "." : scopeId);
10
+ return relativePath === "." ? baseCwd : path.join(baseCwd, relativePath);
11
+ }
12
+
13
+ // src/rest/handlers/summarize-handler.ts
14
+ var summarize_handler_default = defineHandler({
15
+ async execute(ctx, input) {
16
+ const logger = useLogger();
17
+ const llm = useLLM();
18
+ if (!llm) {
19
+ throw new Error("LLM is not available. Please configure an LLM adapter.");
20
+ }
21
+ const { scope = "root", file } = input.body || {};
22
+ logger.info("[summarize-handler] Request received", { scope, file });
23
+ try {
24
+ const fileConfig = await useConfig();
25
+ const config = resolveCommitConfig(fileConfig ?? {});
26
+ const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);
27
+ logger.info("[summarize-handler] Resolved scope", { scope, scopeCwd });
28
+ let prompt;
29
+ if (file) {
30
+ logger.info("[summarize-handler] Summarizing file", { file });
31
+ const diffResult = await getFileDiff(scopeCwd, file);
32
+ prompt = `Analyze this git diff and provide a concise summary of what changed in this file.
33
+
34
+ File: ${file}
35
+
36
+ Diff:
37
+ \`\`\`diff
38
+ ${diffResult.diff}
39
+ \`\`\`
40
+
41
+ Provide a 2-3 sentence summary focusing on:
42
+ - What was added, modified, or removed
43
+ - The purpose/intent of the changes
44
+ - Any notable impacts
45
+
46
+ Keep it concise and technical.`;
47
+ } else {
48
+ logger.info("[summarize-handler] Summarizing all changes");
49
+ const gitStatus = await getGitStatus(scopeCwd);
50
+ const files = getAllChangedFiles(gitStatus);
51
+ if (files.length === 0) {
52
+ return {
53
+ scope,
54
+ summary: "No changes detected in this scope."
55
+ };
56
+ }
57
+ const filesToSummarize = files.slice(0, 10);
58
+ const diffs = await Promise.all(
59
+ filesToSummarize.map(async (f) => {
60
+ const diff = await getFileDiff(scopeCwd, f);
61
+ return { file: f, diff: diff.diff, additions: diff.additions, deletions: diff.deletions };
62
+ })
63
+ );
64
+ const diffsText = diffs.map((d) => `### ${d.file} (+${d.additions} -${d.deletions})
65
+ \`\`\`diff
66
+ ${d.diff}
67
+ \`\`\``).join("\n\n");
68
+ prompt = `Analyze these git diffs and provide a high-level summary of all changes in this scope.
69
+
70
+ Total files changed: ${files.length}
71
+ ${files.length > 10 ? `(Showing first 10 files)` : ""}
72
+
73
+ ${diffsText}
74
+
75
+ Provide a 3-5 sentence summary covering:
76
+ - Overall theme of the changes
77
+ - Key features added, bugs fixed, or refactorings done
78
+ - Any notable architectural changes
79
+
80
+ Keep it concise and at a high level.`;
81
+ }
82
+ logger.info("[summarize-handler] Calling LLM");
83
+ const result = await llm.complete(prompt, {
84
+ systemPrompt: "You are a technical code reviewer. Provide concise, accurate summaries of code changes.",
85
+ temperature: 0.3,
86
+ maxTokens: 500
87
+ });
88
+ logger.info("[summarize-handler] LLM response received", {
89
+ tokensUsed: result.usage.promptTokens + result.usage.completionTokens
90
+ });
91
+ return {
92
+ scope,
93
+ file,
94
+ summary: result.content.trim(),
95
+ tokensUsed: result.usage.promptTokens + result.usage.completionTokens
96
+ };
97
+ } catch (error) {
98
+ logger.error("[summarize-handler] Error", error);
99
+ throw new Error(`Failed to summarize changes: ${error}`);
100
+ }
101
+ }
102
+ });
103
+
104
+ export { summarize_handler_default as default };
105
+ //# sourceMappingURL=summarize-handler.js.map
106
+ //# sourceMappingURL=summarize-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/rest/handlers/scope-resolver.ts","../../../src/rest/handlers/summarize-handler.ts"],"names":[],"mappings":"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","file":"summarize-handler.js","sourcesContent":["import * as path from 'node:path';\nimport type { CommitScope } from '@kb-labs/commit-contracts';\n\n/**\n * Resolves a scope id to an absolute filesystem path.\n *\n * Looks up the scope in the provided scopes array by id.\n * Falls back to treating the id as a relative path from baseCwd (legacy compat).\n *\n * @param baseCwd - Workspace root (ctx.cwd)\n * @param scopeId - Scope identifier, e.g. \"root\", \"public/kb-labs\"\n * @param scopes - Configured scopes from CommitPluginConfig.scope.scopes\n */\nexport function resolveScopePath(\n baseCwd: string,\n scopeId: string = 'root',\n scopes?: CommitScope[],\n): string {\n const scopeDef = scopes?.find((s) => s.id === scopeId);\n const relativePath = scopeDef?.path ?? (scopeId === 'root' ? '.' : scopeId);\n return relativePath === '.' ? baseCwd : path.join(baseCwd, relativePath);\n}\n","import { defineHandler, useConfig, type PluginContextV3, type RestInput, useLogger, useLLM } from '@kb-labs/sdk';\nimport { type SummarizeRequest, type SummarizeResponse, type CommitPluginConfig, resolveCommitConfig } from '@kb-labs/commit-contracts';\nimport { getFileDiff, getAllChangedFiles, getGitStatus } from '@kb-labs/commit-core/analyzer';\nimport { resolveScopePath } from './scope-resolver';\n\n/**\n * POST /summarize handler\n *\n * Summarizes changes using LLM. Can summarize:\n * - All changes in scope (if no file specified)\n * - Specific file changes (if file specified)\n */\nexport default defineHandler({\n async execute(ctx: PluginContextV3, input: RestInput<unknown, SummarizeRequest>): Promise<SummarizeResponse> {\n const logger = useLogger();\n const llm = useLLM();\n\n if (!llm) {\n throw new Error('LLM is not available. Please configure an LLM adapter.');\n }\n\n const { scope = 'root', file } = input.body || {};\n\n logger.info('[summarize-handler] Request received', { scope, file });\n\n try {\n // Resolve scope to actual directory path\n const fileConfig = await useConfig<Partial<CommitPluginConfig>>();\n const config = resolveCommitConfig(fileConfig ?? {});\n const scopeCwd = resolveScopePath(ctx.cwd, scope, config.scope?.scopes);\n logger.info('[summarize-handler] Resolved scope', { scope, scopeCwd });\n\n let prompt: string;\n\n if (file) {\n // Summarize specific file\n logger.info('[summarize-handler] Summarizing file', { file });\n\n // Get file diff (git runs FROM scopeCwd)\n const diffResult = await getFileDiff(scopeCwd, file);\n\n prompt = `Analyze this git diff and provide a concise summary of what changed in this file.\n\nFile: ${file}\n\nDiff:\n\\`\\`\\`diff\n${diffResult.diff}\n\\`\\`\\`\n\nProvide a 2-3 sentence summary focusing on:\n- What was added, modified, or removed\n- The purpose/intent of the changes\n- Any notable impacts\n\nKeep it concise and technical.`;\n } else {\n // Summarize all changes\n logger.info('[summarize-handler] Summarizing all changes');\n\n // Get git status (git runs FROM scopeCwd)\n const gitStatus = await getGitStatus(scopeCwd);\n const files = getAllChangedFiles(gitStatus);\n\n if (files.length === 0) {\n return {\n scope,\n summary: 'No changes detected in this scope.',\n };\n }\n\n // Get diffs for all files (limit to first 10 to avoid token overflow)\n const filesToSummarize = files.slice(0, 10);\n const diffs = await Promise.all(\n filesToSummarize.map(async (f) => {\n const diff = await getFileDiff(scopeCwd, f);\n return { file: f, diff: diff.diff, additions: diff.additions, deletions: diff.deletions };\n })\n );\n\n const diffsText = diffs\n .map((d) => `### ${d.file} (+${d.additions} -${d.deletions})\\n\\`\\`\\`diff\\n${d.diff}\\n\\`\\`\\``)\n .join('\\n\\n');\n\n prompt = `Analyze these git diffs and provide a high-level summary of all changes in this scope.\n\nTotal files changed: ${files.length}\n${files.length > 10 ? `(Showing first 10 files)` : ''}\n\n${diffsText}\n\nProvide a 3-5 sentence summary covering:\n- Overall theme of the changes\n- Key features added, bugs fixed, or refactorings done\n- Any notable architectural changes\n\nKeep it concise and at a high level.`;\n }\n\n // Call LLM\n logger.info('[summarize-handler] Calling LLM');\n const result = await llm.complete(prompt, {\n systemPrompt: 'You are a technical code reviewer. Provide concise, accurate summaries of code changes.',\n temperature: 0.3,\n maxTokens: 500,\n });\n\n logger.info('[summarize-handler] LLM response received', {\n tokensUsed: result.usage.promptTokens + result.usage.completionTokens,\n });\n\n return {\n scope,\n file,\n summary: result.content.trim(),\n tokensUsed: result.usage.promptTokens + result.usage.completionTokens,\n };\n } catch (error) {\n logger.error('[summarize-handler] Error', error as Error);\n throw new Error(`Failed to summarize changes: ${error}`);\n }\n },\n});\n"]}