schub 0.1.15 → 0.1.17

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.
@@ -32152,6 +32152,136 @@ var createFilesService = (db, storageRoot) => {
32152
32152
  };
32153
32153
  };
32154
32154
 
32155
+ // ../schub-services/src/services/git-diff.ts
32156
+ import { exec } from "node:child_process";
32157
+ import { promisify } from "node:util";
32158
+ var execAsync = promisify(exec);
32159
+ function normalizeHeaderPath(input) {
32160
+ if (!input)
32161
+ return null;
32162
+ const token = input.split("\t")[0].trim();
32163
+ if (token === "/dev/null")
32164
+ return null;
32165
+ let normalized = token;
32166
+ if (normalized.startsWith("a/") || normalized.startsWith("b/")) {
32167
+ normalized = normalized.slice(2);
32168
+ }
32169
+ normalized = normalized.replace(/^\/+/, "").replace(/\\/g, "/");
32170
+ return normalized;
32171
+ }
32172
+ var createGitDiffService = () => {
32173
+ const parseDiff = (diffText) => {
32174
+ if (!diffText || typeof diffText !== "string")
32175
+ return [];
32176
+ const changes = new Map;
32177
+ let currentPath = null;
32178
+ let lastMinusHeader = null;
32179
+ let inHunk = false;
32180
+ const lines = diffText.split(/\r?\n/);
32181
+ for (const line of lines) {
32182
+ if (line.startsWith("--- ")) {
32183
+ lastMinusHeader = normalizeHeaderPath(line.slice(4));
32184
+ inHunk = false;
32185
+ continue;
32186
+ }
32187
+ if (line.startsWith("+++ ")) {
32188
+ const plusPath = normalizeHeaderPath(line.slice(4));
32189
+ currentPath = plusPath ?? lastMinusHeader;
32190
+ if (currentPath && !changes.has(currentPath)) {
32191
+ changes.set(currentPath, { additions: 0, deletions: 0 });
32192
+ }
32193
+ inHunk = false;
32194
+ continue;
32195
+ }
32196
+ if (!currentPath)
32197
+ continue;
32198
+ if (line.startsWith("@@")) {
32199
+ inHunk = true;
32200
+ continue;
32201
+ }
32202
+ if (!inHunk)
32203
+ continue;
32204
+ if (line.startsWith("+") && !(line.startsWith("+++ ") || line === "+++")) {
32205
+ const current = changes.get(currentPath);
32206
+ if (current)
32207
+ current.additions += 1;
32208
+ continue;
32209
+ }
32210
+ if (line.startsWith("-") && !(line.startsWith("--- ") || line === "---")) {
32211
+ const current = changes.get(currentPath);
32212
+ if (current)
32213
+ current.deletions += 1;
32214
+ }
32215
+ }
32216
+ return Array.from(changes.entries()).map(([filePath, { additions, deletions }]) => ({
32217
+ filePath,
32218
+ additions,
32219
+ deletions
32220
+ }));
32221
+ };
32222
+ const isGitRepo = async (repoPath) => {
32223
+ try {
32224
+ const { stdout } = await execAsync("git rev-parse --git-dir", {
32225
+ cwd: repoPath,
32226
+ timeout: 5000
32227
+ });
32228
+ return stdout.trim().length > 0;
32229
+ } catch {
32230
+ return false;
32231
+ }
32232
+ };
32233
+ const executeDiff = async (repoPath, args) => {
32234
+ try {
32235
+ const { stdout } = await execAsync(`git diff ${args.join(" ")}`, {
32236
+ cwd: repoPath,
32237
+ timeout: 30000,
32238
+ maxBuffer: 10 * 1024 * 1024
32239
+ });
32240
+ return { success: true, diff: stdout };
32241
+ } catch (error48) {
32242
+ const message = error48 instanceof Error ? error48.message : String(error48);
32243
+ return { success: false, error: message };
32244
+ }
32245
+ };
32246
+ const getUnstagedDiff = async (repoPath) => {
32247
+ const result = await executeDiff(repoPath, ["HEAD"]);
32248
+ if (!result.success) {
32249
+ throw new Error(`Failed to get unstaged diff: ${result.error}`);
32250
+ }
32251
+ return result.diff;
32252
+ };
32253
+ const getStagedDiff = async (repoPath) => {
32254
+ const result = await executeDiff(repoPath, ["--cached", "HEAD"]);
32255
+ if (!result.success) {
32256
+ throw new Error(`Failed to get staged diff: ${result.error}`);
32257
+ }
32258
+ return result.diff;
32259
+ };
32260
+ const getAllDiff = async (repoPath) => {
32261
+ return getUnstagedDiff(repoPath);
32262
+ };
32263
+ const getDiffByMode = async (repoPath, mode) => {
32264
+ switch (mode) {
32265
+ case "unstaged":
32266
+ return getUnstagedDiff(repoPath);
32267
+ case "staged":
32268
+ return getStagedDiff(repoPath);
32269
+ case "all":
32270
+ return getAllDiff(repoPath);
32271
+ default:
32272
+ throw new Error(`Invalid diff mode: ${mode}`);
32273
+ }
32274
+ };
32275
+ return {
32276
+ parseDiff,
32277
+ isGitRepo,
32278
+ getUnstagedDiff,
32279
+ getStagedDiff,
32280
+ getAllDiff,
32281
+ getDiffByMode
32282
+ };
32283
+ };
32284
+
32155
32285
  // ../schub-services/src/services/opencode.ts
32156
32286
  import { spawn } from "node:child_process";
32157
32287
  import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
@@ -33895,6 +34025,7 @@ var createServices = (config2 = {}) => {
33895
34025
  const agents = createAgentsService();
33896
34026
  const docs = createDocsService();
33897
34027
  const files2 = createFilesService(db, storageRoot);
34028
+ const gitDiff = createGitDiffService();
33898
34029
  const opencode = createOpencodeService();
33899
34030
  const proposalStatuses = createProposalStatusesService(db);
33900
34031
  const projects2 = createProjectsService(db);
@@ -33909,6 +34040,7 @@ var createServices = (config2 = {}) => {
33909
34040
  agents,
33910
34041
  docs,
33911
34042
  files: files2,
34043
+ gitDiff,
33912
34044
  opencode,
33913
34045
  proposalStatuses,
33914
34046
  projects: projects2,
@@ -34795,6 +34927,230 @@ var registerImageRoutes = (router) => {
34795
34927
  ]);
34796
34928
  };
34797
34929
 
34930
+ // ../schub-api/src/routes/project-template-assets.ts
34931
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "node:fs";
34932
+ import { dirname as dirname2, join as join4, resolve as resolve2 } from "node:path";
34933
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
34934
+ var PROJECT_TEMPLATE_TYPES = ["template", "skill"];
34935
+ var projectTemplateAssetSchema = exports_external.object({
34936
+ id: exports_external.string(),
34937
+ project_id: exports_external.string().nullable(),
34938
+ name: exports_external.string(),
34939
+ template_type: exports_external.enum(PROJECT_TEMPLATE_TYPES),
34940
+ content: exports_external.string(),
34941
+ created_at: exports_external.string(),
34942
+ updated_at: exports_external.string()
34943
+ });
34944
+ var projectIdParamsSchema = exports_external.object({
34945
+ project_id: exports_external.string()
34946
+ });
34947
+ var projectTemplateAssetParamsSchema = exports_external.object({
34948
+ project_id: exports_external.string(),
34949
+ asset_id: exports_external.string()
34950
+ });
34951
+ var updateProjectTemplateAssetSchema = exports_external.object({
34952
+ content: exports_external.string()
34953
+ });
34954
+ var DEFAULT_PROJECT_TEMPLATE_ASSETS = [
34955
+ {
34956
+ name: "create-proposal/proposal-template.md",
34957
+ template_type: "template",
34958
+ source_path: "templates/create-proposal/proposal-template.md"
34959
+ },
34960
+ {
34961
+ name: "create-proposal/cookbook-template.md",
34962
+ template_type: "template",
34963
+ source_path: "templates/create-proposal/cookbook-template.md"
34964
+ },
34965
+ {
34966
+ name: "create-proposal/adr-template.md",
34967
+ template_type: "template",
34968
+ source_path: "templates/create-proposal/adr-template.md"
34969
+ },
34970
+ {
34971
+ name: "create-ticket/ticket-template.md",
34972
+ template_type: "template",
34973
+ source_path: "templates/create-ticket/ticket-template.md"
34974
+ },
34975
+ {
34976
+ name: "create-tasks/task-template.md",
34977
+ template_type: "template",
34978
+ source_path: "templates/create-tasks/task-template.md"
34979
+ },
34980
+ {
34981
+ name: "review-proposal/review-me-template.md",
34982
+ template_type: "template",
34983
+ source_path: "templates/review-proposal/review-me-template.md"
34984
+ },
34985
+ {
34986
+ name: "review-proposal/q&a-template.md",
34987
+ template_type: "template",
34988
+ source_path: "templates/review-proposal/q&a-template.md"
34989
+ },
34990
+ { name: "create-proposal/SKILL.md", template_type: "skill", source_path: "skills/create-proposal/SKILL.md" },
34991
+ { name: "create-ticket/SKILL.md", template_type: "skill", source_path: "skills/create-ticket/SKILL.md" },
34992
+ {
34993
+ name: "create-tickets-for-proposal/SKILL.md",
34994
+ template_type: "skill",
34995
+ source_path: "skills/create-tickets-for-proposal/SKILL.md"
34996
+ },
34997
+ { name: "implement-ticket/SKILL.md", template_type: "skill", source_path: "skills/implement-ticket/SKILL.md" },
34998
+ { name: "review-proposal/SKILL.md", template_type: "skill", source_path: "skills/review-proposal/SKILL.md" }
34999
+ ];
35000
+ var templateTypeOrder = new Map(PROJECT_TEMPLATE_TYPES.map((type, index) => [type, index]));
35001
+ var isProjectTemplateType = (value) => PROJECT_TEMPLATE_TYPES.includes(value);
35002
+ var isSchubPackageRoot = (rootPath) => existsSync3(join4(rootPath, "templates")) && existsSync3(join4(rootPath, "skills"));
35003
+ var resolveBundledSchubRoot = () => {
35004
+ const moduleRoot = resolve2(dirname2(fileURLToPath2(import.meta.url)), "../../../schub");
35005
+ const workspaceRoot = resolve2(process.cwd(), "packages", "schub");
35006
+ const siblingRoot = resolve2(process.cwd(), "..", "schub");
35007
+ const localRoot = resolve2(process.cwd());
35008
+ const candidates = [moduleRoot, workspaceRoot, siblingRoot, localRoot];
35009
+ return candidates.find(isSchubPackageRoot) ?? null;
35010
+ };
35011
+ var loadBundledTemplateAssets = () => {
35012
+ const schubRoot = resolveBundledSchubRoot();
35013
+ if (!schubRoot)
35014
+ return [];
35015
+ return DEFAULT_PROJECT_TEMPLATE_ASSETS.flatMap((asset) => {
35016
+ const storagePath = join4(schubRoot, asset.source_path);
35017
+ if (!existsSync3(storagePath)) {
35018
+ return [];
35019
+ }
35020
+ return [
35021
+ {
35022
+ name: asset.name,
35023
+ template_type: asset.template_type,
35024
+ content: readFileSync2(storagePath, "utf8")
35025
+ }
35026
+ ];
35027
+ });
35028
+ };
35029
+ var sortAssets = (assets) => [...assets].sort((left, right) => {
35030
+ const leftType = templateTypeOrder.get(left.template_type) ?? Number.MAX_SAFE_INTEGER;
35031
+ const rightType = templateTypeOrder.get(right.template_type) ?? Number.MAX_SAFE_INTEGER;
35032
+ if (leftType !== rightType) {
35033
+ return leftType - rightType;
35034
+ }
35035
+ return left.name.localeCompare(right.name);
35036
+ });
35037
+ var seedMissingAssets = (context, projectId) => {
35038
+ const services = context.get("services");
35039
+ const existing = services.templates.list().filter((asset) => asset.project_id === projectId && isProjectTemplateType(asset.template_type));
35040
+ const existingKeys = new Set(existing.map((asset) => `${asset.template_type}:${asset.name}`));
35041
+ const defaults = loadBundledTemplateAssets();
35042
+ defaults.forEach((asset) => {
35043
+ const key = `${asset.template_type}:${asset.name}`;
35044
+ if (existingKeys.has(key))
35045
+ return;
35046
+ services.templates.create({
35047
+ project_id: projectId,
35048
+ name: asset.name,
35049
+ template_type: asset.template_type,
35050
+ content: asset.content
35051
+ });
35052
+ });
35053
+ };
35054
+ var registerProjectTemplateAssetRoutes = (router) => {
35055
+ const listRoute = createRoute({
35056
+ method: "get",
35057
+ path: "/projects/:project_id/template-assets",
35058
+ tags: ["Projects"],
35059
+ summary: "List project template and skill assets",
35060
+ request: {
35061
+ params: projectIdParamsSchema
35062
+ },
35063
+ responses: {
35064
+ 200: {
35065
+ description: "Project template assets",
35066
+ content: {
35067
+ "application/json": {
35068
+ schema: apiSuccessSchema(exports_external.array(projectTemplateAssetSchema))
35069
+ }
35070
+ }
35071
+ },
35072
+ 404: {
35073
+ description: "Project not found",
35074
+ content: {
35075
+ "application/json": {
35076
+ schema: apiErrorSchema
35077
+ }
35078
+ }
35079
+ }
35080
+ }
35081
+ });
35082
+ const listHandler = (c) => {
35083
+ const { project_id } = c.req.valid("param");
35084
+ const services = c.get("services");
35085
+ const project = services.projects.get(project_id);
35086
+ if (!project) {
35087
+ return c.json(errorResponse("Project not found"), 404);
35088
+ }
35089
+ seedMissingAssets(c, project_id);
35090
+ const assets = sortAssets(services.templates.list().filter((asset) => asset.project_id === project_id && isProjectTemplateType(asset.template_type)));
35091
+ return c.json(okResponse(assets), 200);
35092
+ };
35093
+ router.openapi(listRoute, listHandler);
35094
+ const updateRoute = createRoute({
35095
+ method: "put",
35096
+ path: "/projects/:project_id/template-assets/:asset_id",
35097
+ tags: ["Projects"],
35098
+ summary: "Update project template or skill asset",
35099
+ request: {
35100
+ params: projectTemplateAssetParamsSchema,
35101
+ body: {
35102
+ content: {
35103
+ "application/json": {
35104
+ schema: updateProjectTemplateAssetSchema
35105
+ }
35106
+ }
35107
+ }
35108
+ },
35109
+ responses: {
35110
+ 200: {
35111
+ description: "Updated project template asset",
35112
+ content: {
35113
+ "application/json": {
35114
+ schema: apiSuccessSchema(projectTemplateAssetSchema)
35115
+ }
35116
+ }
35117
+ },
35118
+ 404: {
35119
+ description: "Asset not found",
35120
+ content: {
35121
+ "application/json": {
35122
+ schema: apiErrorSchema
35123
+ }
35124
+ }
35125
+ }
35126
+ }
35127
+ });
35128
+ const updateHandler = (c) => {
35129
+ const { project_id, asset_id } = c.req.valid("param");
35130
+ const { content } = c.req.valid("json");
35131
+ const services = c.get("services");
35132
+ const project = services.projects.get(project_id);
35133
+ if (!project) {
35134
+ return c.json(errorResponse("Project not found"), 404);
35135
+ }
35136
+ const existing = services.templates.get(asset_id);
35137
+ if (!existing || existing.project_id !== project_id || !isProjectTemplateType(existing.template_type)) {
35138
+ return c.json(errorResponse("Template asset not found"), 404);
35139
+ }
35140
+ const updated = services.templates.update(asset_id, {
35141
+ project_id,
35142
+ name: existing.name,
35143
+ template_type: existing.template_type,
35144
+ content
35145
+ });
35146
+ if (!updated) {
35147
+ return c.json(errorResponse("Template asset not found"), 404);
35148
+ }
35149
+ return c.json(okResponse(updated), 200);
35150
+ };
35151
+ router.openapi(updateRoute, updateHandler);
35152
+ };
35153
+
34798
35154
  // ../schub-api/src/routes/projects.ts
34799
35155
  var projectSchema = exports_external.object({
34800
35156
  id: exports_external.string(),
@@ -34821,7 +35177,7 @@ var createProjectSchema = exports_external.object({
34821
35177
  var updateProjectSchema = exports_external.object({
34822
35178
  name: exports_external.string().min(1)
34823
35179
  });
34824
- var projectIdParamsSchema = exports_external.object({
35180
+ var projectIdParamsSchema2 = exports_external.object({
34825
35181
  id: exports_external.string()
34826
35182
  });
34827
35183
  var projectRepoParamsSchema = exports_external.object({
@@ -34891,7 +35247,7 @@ var registerProjectRoutes = (router, upgradeWebSocket) => {
34891
35247
  tags: ["Projects"],
34892
35248
  summary: "Get project",
34893
35249
  request: {
34894
- params: projectIdParamsSchema
35250
+ params: projectIdParamsSchema2
34895
35251
  },
34896
35252
  responses: {
34897
35253
  200: {
@@ -34927,7 +35283,7 @@ var registerProjectRoutes = (router, upgradeWebSocket) => {
34927
35283
  tags: ["Projects"],
34928
35284
  summary: "Update project",
34929
35285
  request: {
34930
- params: projectIdParamsSchema,
35286
+ params: projectIdParamsSchema2,
34931
35287
  body: {
34932
35288
  content: {
34933
35289
  "application/json": {
@@ -34971,7 +35327,7 @@ var registerProjectRoutes = (router, upgradeWebSocket) => {
34971
35327
  tags: ["Projects"],
34972
35328
  summary: "Delete project",
34973
35329
  request: {
34974
- params: projectIdParamsSchema
35330
+ params: projectIdParamsSchema2
34975
35331
  },
34976
35332
  responses: {
34977
35333
  204: {
@@ -35002,7 +35358,7 @@ var registerProjectRoutes = (router, upgradeWebSocket) => {
35002
35358
  tags: ["Projects"],
35003
35359
  summary: "List project repositories",
35004
35360
  request: {
35005
- params: projectIdParamsSchema
35361
+ params: projectIdParamsSchema2
35006
35362
  },
35007
35363
  responses: {
35008
35364
  200: {
@@ -35027,7 +35383,7 @@ var registerProjectRoutes = (router, upgradeWebSocket) => {
35027
35383
  tags: ["Projects"],
35028
35384
  summary: "Add project repository",
35029
35385
  request: {
35030
- params: projectIdParamsSchema,
35386
+ params: projectIdParamsSchema2,
35031
35387
  body: {
35032
35388
  content: {
35033
35389
  "application/json": {
@@ -35625,8 +35981,8 @@ var registerProposalRoutes = (router) => {
35625
35981
  // ../schub-api/src/routes/repos.ts
35626
35982
  import { execFile } from "node:child_process";
35627
35983
  import { mkdir as mkdir2 } from "node:fs/promises";
35628
- import { join as join4, resolve as resolve2 } from "node:path";
35629
- import { promisify } from "node:util";
35984
+ import { join as join5, resolve as resolve3 } from "node:path";
35985
+ import { promisify as promisify2 } from "node:util";
35630
35986
  var repoSchema2 = exports_external.object({
35631
35987
  id: exports_external.string(),
35632
35988
  name: exports_external.string(),
@@ -35656,7 +36012,7 @@ var repoIdParamsSchema = exports_external.object({
35656
36012
  var updateRepoSchema = exports_external.object({
35657
36013
  display_name: exports_external.string().nullable().optional()
35658
36014
  });
35659
- var execFileAsync = promisify(execFile);
36015
+ var execFileAsync = promisify2(execFile);
35660
36016
  var normalizeBranchDate = (value) => {
35661
36017
  if (!value) {
35662
36018
  return new Date().toISOString();
@@ -35803,9 +36159,9 @@ var registerRepoRoutes = (router) => {
35803
36159
  });
35804
36160
  const initHandler = async (c) => {
35805
36161
  const payload = c.req.valid("json");
35806
- const repoPath = resolve2(payload.parent_path, payload.folder_name);
36162
+ const repoPath = resolve3(payload.parent_path, payload.folder_name);
35807
36163
  await mkdir2(repoPath, { recursive: true });
35808
- await mkdir2(join4(repoPath, ".git"), { recursive: true });
36164
+ await mkdir2(join5(repoPath, ".git"), { recursive: true });
35809
36165
  const repo = c.get("services").repos.register({
35810
36166
  path: repoPath,
35811
36167
  display_name: payload.display_name ?? payload.folder_name
@@ -36837,8 +37193,8 @@ var registerTicketStatusRoutes = (router) => {
36837
37193
 
36838
37194
  // ../schub-api/src/routes/tickets.ts
36839
37195
  import { spawnSync as spawnSync2 } from "node:child_process";
36840
- import { existsSync as existsSync3, mkdirSync } from "node:fs";
36841
- import { join as join5 } from "node:path";
37196
+ import { existsSync as existsSync4, mkdirSync } from "node:fs";
37197
+ import { join as join6 } from "node:path";
36842
37198
  var runGit = (cwd, args) => {
36843
37199
  return spawnSync2("git", args, {
36844
37200
  cwd,
@@ -36852,16 +37208,16 @@ var createAttemptWorktree = (options) => {
36852
37208
  throw new Error("Repository is not a git repository.");
36853
37209
  }
36854
37210
  const gitRoot = gitRootResult.stdout.trim();
36855
- const worktreePath = join5(gitRoot, ".schub", "worktrees", options.ticketShorthand, `attempt-${options.attemptNumber}`);
37211
+ const worktreePath = join6(gitRoot, ".schub", "worktrees", options.ticketShorthand, `attempt-${options.attemptNumber}`);
36856
37212
  const branchName = `ticket/${options.ticketShorthand}/attempt-${options.attemptNumber}`;
36857
- if (existsSync3(worktreePath)) {
37213
+ if (existsSync4(worktreePath)) {
36858
37214
  throw new Error(`Worktree path already exists: ${worktreePath}`);
36859
37215
  }
36860
37216
  const branchCheck = runGit(gitRoot, ["show-ref", "--verify", "--quiet", `refs/heads/${branchName}`]);
36861
37217
  if (branchCheck.status === 0) {
36862
37218
  throw new Error(`Branch ${branchName} already exists.`);
36863
37219
  }
36864
- mkdirSync(join5(gitRoot, ".schub", "worktrees", options.ticketShorthand), { recursive: true });
37220
+ mkdirSync(join6(gitRoot, ".schub", "worktrees", options.ticketShorthand), { recursive: true });
36865
37221
  const worktreeArgs = ["worktree", "add", "-b", branchName, worktreePath];
36866
37222
  const baseBranch = options.baseBranch?.trim();
36867
37223
  if (baseBranch) {
@@ -37484,7 +37840,113 @@ var registerWorkspaceImageRoutes = (router) => {
37484
37840
  };
37485
37841
 
37486
37842
  // ../schub-api/src/routes/workspaces.ts
37843
+ var workspaceDiffQuerySchema = exports_external.object({
37844
+ mode: exports_external.enum(["unstaged", "staged", "all"])
37845
+ });
37846
+ var workspaceIdParamsSchema = exports_external.object({
37847
+ workspaceId: exports_external.string().uuid()
37848
+ });
37849
+ var fileChangeSchema = exports_external.object({
37850
+ filePath: exports_external.string(),
37851
+ additions: exports_external.number(),
37852
+ deletions: exports_external.number()
37853
+ });
37854
+ var workspaceDiffDataSchema = exports_external.object({
37855
+ workspace_id: exports_external.string(),
37856
+ mode: exports_external.enum(["unstaged", "staged", "all"]),
37857
+ diff_text: exports_external.string(),
37858
+ files: exports_external.array(fileChangeSchema),
37859
+ totals: exports_external.object({
37860
+ additions: exports_external.number(),
37861
+ deletions: exports_external.number(),
37862
+ file_count: exports_external.number()
37863
+ })
37864
+ });
37487
37865
  var registerWorkspaceRoutes = (router) => {
37866
+ const diffRoute = createRoute({
37867
+ method: "get",
37868
+ path: "/workspaces/:workspaceId/diff",
37869
+ tags: ["Workspaces"],
37870
+ summary: "Get workspace diff",
37871
+ request: {
37872
+ params: workspaceIdParamsSchema,
37873
+ query: workspaceDiffQuerySchema
37874
+ },
37875
+ responses: {
37876
+ 200: {
37877
+ description: "Workspace diff",
37878
+ content: {
37879
+ "application/json": {
37880
+ schema: apiSuccessSchema(workspaceDiffDataSchema)
37881
+ }
37882
+ }
37883
+ },
37884
+ 400: {
37885
+ description: "Bad request",
37886
+ content: {
37887
+ "application/json": {
37888
+ schema: apiErrorSchema
37889
+ }
37890
+ }
37891
+ },
37892
+ 404: {
37893
+ description: "Workspace or repository not found",
37894
+ content: {
37895
+ "application/json": {
37896
+ schema: apiErrorSchema
37897
+ }
37898
+ }
37899
+ },
37900
+ 500: {
37901
+ description: "Failed to get diff",
37902
+ content: {
37903
+ "application/json": {
37904
+ schema: apiErrorSchema
37905
+ }
37906
+ }
37907
+ }
37908
+ }
37909
+ });
37910
+ const diffHandler = async (c) => {
37911
+ const { workspaceId } = c.req.valid("param");
37912
+ const { mode } = c.req.valid("query");
37913
+ const services = c.get("services");
37914
+ const workspace = services.workspaces.get(workspaceId);
37915
+ if (!workspace) {
37916
+ return c.json(errorResponse("Workspace not found"), 404);
37917
+ }
37918
+ if (!workspace.repo_id) {
37919
+ return c.json(errorResponse("Workspace has no associated repository"), 400);
37920
+ }
37921
+ const repo = services.repos.get(workspace.repo_id);
37922
+ if (!repo) {
37923
+ return c.json(errorResponse("Repository not found"), 404);
37924
+ }
37925
+ const isGitRepo2 = await services.gitDiff.isGitRepo(repo.path);
37926
+ if (!isGitRepo2) {
37927
+ return c.json(errorResponse("Path is not a git repository"), 400);
37928
+ }
37929
+ try {
37930
+ const diffText = await services.gitDiff.getDiffByMode(repo.path, mode);
37931
+ const files2 = services.gitDiff.parseDiff(diffText);
37932
+ const totals = files2.reduce((acc, file2) => ({
37933
+ additions: acc.additions + file2.additions,
37934
+ deletions: acc.deletions + file2.deletions,
37935
+ file_count: acc.file_count + 1
37936
+ }), { additions: 0, deletions: 0, file_count: 0 });
37937
+ return c.json(okResponse({
37938
+ workspace_id: workspaceId,
37939
+ mode,
37940
+ diff_text: diffText,
37941
+ files: files2,
37942
+ totals
37943
+ }), 200);
37944
+ } catch (error48) {
37945
+ const message = error48 instanceof Error ? error48.message : "Failed to get diff";
37946
+ return c.json(errorResponse(message), 500);
37947
+ }
37948
+ };
37949
+ router.openapi(diffRoute, diffHandler);
37488
37950
  registerPlaceholderRoutes(router, "Workspaces", [
37489
37951
  { method: "get", path: "/ticket-attempts" },
37490
37952
  { method: "post", path: "/ticket-attempts" },
@@ -37529,6 +37991,7 @@ var createRoutes = (options) => {
37529
37991
  registerContainerRoutes(routes);
37530
37992
  registerDocsRoutes(routes);
37531
37993
  registerProjectRoutes(routes, options.upgradeWebSocket);
37994
+ registerProjectTemplateAssetRoutes(routes);
37532
37995
  registerTicketStatusRoutes(routes);
37533
37996
  registerProposalRoutes(routes);
37534
37997
  registerTicketRoutes(routes);
@@ -1 +1 @@
1
- import{c as f,r as u,k as E,j as a,o as p}from"./index-DhqXvDTi.js";function x(c){const e=f.c(5),{equation:i,inline:n}=c,l=u.useRef(null);let o,t;e[0]!==i||e[1]!==n?(o=()=>{const s=l.current;s!==null&&E.render(i,s,{displayMode:!n,errorColor:"#cc0000",output:"html",strict:"warn",throwOnError:!1,trust:!1})},t=[i,n],e[0]=i,e[1]=n,e[2]=o,e[3]=t):(o=e[2],t=e[3]),u.useEffect(o,t);let r;return e[4]===Symbol.for("react.memo_cache_sentinel")?(r=a.jsxs(a.Fragment,{children:[a.jsx("img",{src:"#",alt:""}),a.jsx("span",{ref:l}),a.jsx("img",{src:"#",alt:""})]}),e[4]=r):r=e[4],r}class d extends u.Component{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e){this.props.onError(e)}render(){return this.state.hasError?null:this.props.children}}function j(c){const e=f.c(8),{equation:i,inline:n}=c,[l]=p(),[o]=u.useState(i);let t;e[0]!==l?(t=m=>l._onError(m),e[0]=l,e[1]=t):t=e[1];let r;e[2]!==o||e[3]!==n?(r=a.jsx(x,{equation:o,inline:n}),e[2]=o,e[3]=n,e[4]=r):r=e[4];let s;return e[5]!==t||e[6]!==r?(s=a.jsx(d,{onError:t,children:r}),e[5]=t,e[6]=r,e[7]=s):s=e[7],s}export{j as default};
1
+ import{c as f,r as u,k as E,j as a,o as p}from"./index-DgZ7OKyk.js";function x(c){const e=f.c(5),{equation:i,inline:n}=c,l=u.useRef(null);let o,t;e[0]!==i||e[1]!==n?(o=()=>{const s=l.current;s!==null&&E.render(i,s,{displayMode:!n,errorColor:"#cc0000",output:"html",strict:"warn",throwOnError:!1,trust:!1})},t=[i,n],e[0]=i,e[1]=n,e[2]=o,e[3]=t):(o=e[2],t=e[3]),u.useEffect(o,t);let r;return e[4]===Symbol.for("react.memo_cache_sentinel")?(r=a.jsxs(a.Fragment,{children:[a.jsx("img",{src:"#",alt:""}),a.jsx("span",{ref:l}),a.jsx("img",{src:"#",alt:""})]}),e[4]=r):r=e[4],r}class d extends u.Component{state={hasError:!1};static getDerivedStateFromError(){return{hasError:!0}}componentDidCatch(e){this.props.onError(e)}render(){return this.state.hasError?null:this.props.children}}function j(c){const e=f.c(8),{equation:i,inline:n}=c,[l]=p(),[o]=u.useState(i);let t;e[0]!==l?(t=m=>l._onError(m),e[0]=l,e[1]=t):t=e[1];let r;e[2]!==o||e[3]!==n?(r=a.jsx(x,{equation:o,inline:n}),e[2]=o,e[3]=n,e[4]=r):r=e[4];let s;return e[5]!==t||e[6]!==r?(s=a.jsx(d,{onError:t,children:r}),e[5]=t,e[6]=r,e[7]=s):s=e[7],s}export{j as default};