@rashidee/co2 1.3.4 → 1.3.6

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 (74) hide show
  1. package/dist/.co2-dat/app.db +0 -0
  2. package/dist/.co2-dat/app.db-shm +0 -0
  3. package/dist/.co2-dat/app.db-wal +0 -0
  4. package/dist/index.js +916 -321
  5. package/drizzle/0017_git_remote_url.sql +3 -0
  6. package/drizzle/0018_default_branch.sql +3 -0
  7. package/drizzle/0019_git_provider.sql +3 -0
  8. package/drizzle/meta/_journal.json +21 -0
  9. package/package.json +1 -1
  10. package/plugin/skills/util-gencicdscript/SKILL.md +41 -9
  11. package/plugin/skills/util-gencicdscript/references/cicd-app-template.md +210 -21
  12. package/plugin/skills/util-plancicd/SKILL.md +88 -5
  13. package/static/assets/{abnfDiagram-VRR7QNED-BwxjNDj8.js → abnfDiagram-VRR7QNED-CzKpxD9q.js} +1 -1
  14. package/static/assets/{arc-Bf00Zylw.js → arc-0jj0p8Qe.js} +1 -1
  15. package/static/assets/{architectureDiagram-ZJ3FMSHR-CRUg8Wt3.js → architectureDiagram-ZJ3FMSHR-DXUL6S5t.js} +1 -1
  16. package/static/assets/{blockDiagram-677ZJIJ3-Dy-JP_0N.js → blockDiagram-677ZJIJ3-BBjsQESd.js} +1 -1
  17. package/static/assets/{c4Diagram-LMCZKHZV-obM4PBxI.js → c4Diagram-LMCZKHZV-CTsLC95S.js} +1 -1
  18. package/static/assets/channel-1MKDXK8w.js +1 -0
  19. package/static/assets/{chunk-2Q5K7J3B-DoXzquQ1.js → chunk-2Q5K7J3B-BpisBKYx.js} +1 -1
  20. package/static/assets/{chunk-32BRIVSS-B6VgB7K8.js → chunk-32BRIVSS-DAfsr4ph.js} +1 -1
  21. package/static/assets/{chunk-5VM5RSS4-BmZS0Ftn.js → chunk-5VM5RSS4-Z3FtY-B6.js} +1 -1
  22. package/static/assets/{chunk-EX3LRPZG-DdOLrDUP.js → chunk-EX3LRPZG-FI9smQaN.js} +1 -1
  23. package/static/assets/{chunk-JWPE2WC7-RVdgT8k5.js → chunk-JWPE2WC7-sOygQj_B.js} +1 -1
  24. package/static/assets/{chunk-MOJQB5TN-BbMHSSXk.js → chunk-MOJQB5TN-DCSpai7w.js} +1 -1
  25. package/static/assets/{chunk-RYQCIY6F-CBOyejV-.js → chunk-RYQCIY6F-CchqtfnI.js} +1 -1
  26. package/static/assets/{chunk-V7JOEXUC-DVvl5ZDu.js → chunk-V7JOEXUC-DaOzsfx7.js} +1 -1
  27. package/static/assets/{chunk-VR4S4FIN-TKs-_Nin.js → chunk-VR4S4FIN-DmxuoVTy.js} +1 -1
  28. package/static/assets/{chunk-XXDRQBXY-BuwUqVAV.js → chunk-XXDRQBXY-ChERxfri.js} +1 -1
  29. package/static/assets/classDiagram-OUVF2IWQ-bADaUwoJ.js +1 -0
  30. package/static/assets/classDiagram-v2-EOCWNBFH-bADaUwoJ.js +1 -0
  31. package/static/assets/{cose-bilkent-JH36ORCC-bVLOVjVk.js → cose-bilkent-JH36ORCC-CO7_eF6b.js} +1 -1
  32. package/static/assets/{cynefin-VYW2F7L2-B7HPqh-Z.js → cynefin-VYW2F7L2-CzQkWtdY.js} +1 -1
  33. package/static/assets/{cynefinDiagram-TSTJHNR4-0d2yxXQG.js → cynefinDiagram-TSTJHNR4-BN2o8rFa.js} +1 -1
  34. package/static/assets/{dagre-VKFMJZFB-D7ErrCay.js → dagre-VKFMJZFB-GLjBE-Xs.js} +1 -1
  35. package/static/assets/{diagram-FQU43EPY-C36bU8jh.js → diagram-FQU43EPY-Cyv7saju.js} +1 -1
  36. package/static/assets/{diagram-G47NLZAW-BzaLh10R.js → diagram-G47NLZAW-B9PmZJ3U.js} +1 -1
  37. package/static/assets/{diagram-NH7WQ7WH-v-Zsf5gF.js → diagram-NH7WQ7WH-ByKD8yJU.js} +1 -1
  38. package/static/assets/{diagram-OA4YK3LP-CRbJBq_k.js → diagram-OA4YK3LP-BvbW7fIS.js} +1 -1
  39. package/static/assets/{diagram-WEI45ONY-8fV5Plo4.js → diagram-WEI45ONY-BgXZ9SJ8.js} +1 -1
  40. package/static/assets/{ebnfDiagram-CCIWWBDH-5gIe63LE.js → ebnfDiagram-CCIWWBDH-CXEEJ26B.js} +1 -1
  41. package/static/assets/{erDiagram-Q63AITRT-C3IcAHjG.js → erDiagram-Q63AITRT-CcHs_S16.js} +1 -1
  42. package/static/assets/{flowDiagram-23GEKE2U-KQjlOZK9.js → flowDiagram-23GEKE2U-Cwg3I3GK.js} +1 -1
  43. package/static/assets/{ganttDiagram-NO4QXBWP-Dq3c2GZf.js → ganttDiagram-NO4QXBWP-ky6w5Lvy.js} +1 -1
  44. package/static/assets/{gitGraphDiagram-IHSO6WYX-BYtnaU27.js → gitGraphDiagram-IHSO6WYX-DN0eRQ7r.js} +1 -1
  45. package/static/assets/{index-CgNepcKp.js → index-CCC4-Ibh.js} +174 -172
  46. package/static/assets/{index-Dn_JY-18.css → index-Cz5Fcq3G.css} +1 -1
  47. package/static/assets/{infoDiagram-FWYZ7A6U-CxqBig2C.js → infoDiagram-FWYZ7A6U-COojw6OC.js} +1 -1
  48. package/static/assets/{ishikawaDiagram-FXEZZL3T-0HfzC1wj.js → ishikawaDiagram-FXEZZL3T-BR5w5ZqM.js} +1 -1
  49. package/static/assets/{journeyDiagram-5HDEW3XC-umkaBeJl.js → journeyDiagram-5HDEW3XC-B5WtTEBM.js} +1 -1
  50. package/static/assets/{kanban-definition-HUTT4EX6-DsBos3y7.js → kanban-definition-HUTT4EX6-DnQ_zg4E.js} +1 -1
  51. package/static/assets/{linear-DtNdgaOB.js → linear-C5kGzenz.js} +1 -1
  52. package/static/assets/{mindmap-definition-LN4V7U3C-BThlwYQu.js → mindmap-definition-LN4V7U3C-BMksxBso.js} +1 -1
  53. package/static/assets/{pegDiagram-2B236MQR-DoxrNvw3.js → pegDiagram-2B236MQR-BKrF97iU.js} +1 -1
  54. package/static/assets/{pieDiagram-ENE6RG2P-BqCJUG4Q.js → pieDiagram-ENE6RG2P-DZAQkZwC.js} +1 -1
  55. package/static/assets/{quadrantDiagram-ABIIQ3AL-4GVp_BSP.js → quadrantDiagram-ABIIQ3AL-Dx0-5odM.js} +1 -1
  56. package/static/assets/{railroadDiagram-RFXS5EU6-CU341ElP.js → railroadDiagram-RFXS5EU6-BbY-CxL_.js} +1 -1
  57. package/static/assets/{requirementDiagram-TGXJPOKE-DoBSr-cX.js → requirementDiagram-TGXJPOKE-ItSYgvcN.js} +1 -1
  58. package/static/assets/{sankeyDiagram-HTMAVEWB-CJ__IqT6.js → sankeyDiagram-HTMAVEWB-CjIuRRYE.js} +1 -1
  59. package/static/assets/{sequenceDiagram-DBY2YBRQ-BkNwP7Io.js → sequenceDiagram-DBY2YBRQ-B277PT2z.js} +1 -1
  60. package/static/assets/{sizeCapture-X5ZJPWSS-B-2Xu5n9.js → sizeCapture-X5ZJPWSS-BYc46yWE.js} +1 -1
  61. package/static/assets/{stateDiagram-2N3HPSRC-DPbpX5_5.js → stateDiagram-2N3HPSRC-Cq41gYoq.js} +1 -1
  62. package/static/assets/stateDiagram-v2-6OUMAXLB-B9V3_HpU.js +1 -0
  63. package/static/assets/{swimlanes-5IMT3BWC-CnBZNoN8.js → swimlanes-5IMT3BWC-DMxA2jQL.js} +2 -2
  64. package/static/assets/swimlanesDiagram-G3AALYLV-qx8IX4cU.js +8 -0
  65. package/static/assets/{timeline-definition-FHXFAJF6-iVUc1pZx.js → timeline-definition-FHXFAJF6-CB8iORNI.js} +1 -1
  66. package/static/assets/{vennDiagram-L72KCM5P-CL66_DTK.js → vennDiagram-L72KCM5P-CHwAX4cP.js} +1 -1
  67. package/static/assets/{wardleyDiagram-EHGQE667-DQHcCKUc.js → wardleyDiagram-EHGQE667-DL19kD5s.js} +1 -1
  68. package/static/assets/{xychartDiagram-FW5EYKEG-B8thIce8.js → xychartDiagram-FW5EYKEG-b271oXxd.js} +1 -1
  69. package/static/index.html +2 -2
  70. package/static/assets/channel-BeHa2ubV.js +0 -1
  71. package/static/assets/classDiagram-OUVF2IWQ-B0BHpjWc.js +0 -1
  72. package/static/assets/classDiagram-v2-EOCWNBFH-B0BHpjWc.js +0 -1
  73. package/static/assets/stateDiagram-v2-6OUMAXLB-1Opbis3l.js +0 -1
  74. package/static/assets/swimlanesDiagram-G3AALYLV-aoTIjn7w.js +0 -8
package/dist/index.js CHANGED
@@ -1267,8 +1267,8 @@ var require_command = __commonJS({
1267
1267
  * @return {Command} `this` command for chaining
1268
1268
  */
1269
1269
  arguments(names) {
1270
- names.trim().split(/ +/).forEach((detail) => {
1271
- this.argument(detail);
1270
+ names.trim().split(/ +/).forEach((detail2) => {
1271
+ this.argument(detail2);
1272
1272
  });
1273
1273
  return this;
1274
1274
  }
@@ -6809,7 +6809,7 @@ var {
6809
6809
  // package.json
6810
6810
  var package_default = {
6811
6811
  name: "@rashidee/co2",
6812
- version: "1.3.4",
6812
+ version: "1.3.6",
6813
6813
  description: "Compound Context Studio \u2014 self-hosted team context authoring for CO2 projects",
6814
6814
  type: "module",
6815
6815
  license: "MIT",
@@ -6937,9 +6937,9 @@ ${install.output}`));
6937
6937
  }
6938
6938
 
6939
6939
  // src/commands/start.ts
6940
- import { existsSync as existsSync24 } from "fs";
6940
+ import { existsSync as existsSync25 } from "fs";
6941
6941
  import { networkInterfaces } from "os";
6942
- import { dirname as dirname3, join as join28 } from "path";
6942
+ import { dirname as dirname3, join as join29 } from "path";
6943
6943
  import { fileURLToPath as fileURLToPath3 } from "url";
6944
6944
 
6945
6945
  // ../../node_modules/.pnpm/hono@4.12.27/node_modules/hono/dist/compose.js
@@ -10241,7 +10241,7 @@ function sql(strings, ...params) {
10241
10241
  return new SQL([new StringChunk(str)]);
10242
10242
  }
10243
10243
  sql2.raw = raw2;
10244
- function join29(chunks, separator) {
10244
+ function join30(chunks, separator) {
10245
10245
  const result = [];
10246
10246
  for (const [i, chunk] of chunks.entries()) {
10247
10247
  if (i > 0 && separator !== void 0) {
@@ -10251,7 +10251,7 @@ function sql(strings, ...params) {
10251
10251
  }
10252
10252
  return new SQL(result);
10253
10253
  }
10254
- sql2.join = join29;
10254
+ sql2.join = join30;
10255
10255
  function identifier(value) {
10256
10256
  return new Name(value);
10257
10257
  }
@@ -12760,7 +12760,7 @@ var SQLiteSelectQueryBuilderBase = class extends TypedQueryBuilder {
12760
12760
  return (table, on) => {
12761
12761
  const baseTableName = this.tableName;
12762
12762
  const tableName = getTableLikeName(table);
12763
- if (typeof tableName === "string" && this.config.joins?.some((join29) => join29.alias === tableName)) {
12763
+ if (typeof tableName === "string" && this.config.joins?.some((join30) => join30.alias === tableName)) {
12764
12764
  throw new Error(`Alias "${tableName}" is already used in this query`);
12765
12765
  }
12766
12766
  if (!this.isPartialSelect) {
@@ -13563,7 +13563,7 @@ var SQLiteUpdateBase = class extends QueryPromise {
13563
13563
  createJoin(joinType) {
13564
13564
  return (table, on) => {
13565
13565
  const tableName = getTableLikeName(table);
13566
- if (typeof tableName === "string" && this.config.joins.some((join29) => join29.alias === tableName)) {
13566
+ if (typeof tableName === "string" && this.config.joins.some((join30) => join30.alias === tableName)) {
13567
13567
  throw new Error(`Alias "${tableName}" is already used in this query`);
13568
13568
  }
13569
13569
  if (typeof on === "function") {
@@ -14490,6 +14490,17 @@ var projects = sqliteTable("projects", {
14490
14490
  // existing CO2 repo (no scaffolding, no brainstorm). importedBy is a soft ref → users.id.
14491
14491
  importedAt: text("imported_at"),
14492
14492
  importedBy: integer("imported_by"),
14493
+ // [v2.2.0] Per-project git remote (push destination). Auto-seeded from an imported repo's
14494
+ // origin; editable in project detail. Never carries an embedded credential.
14495
+ gitRemoteUrl: text("git_remote_url"),
14496
+ // [v2.3.0] Per-project default (trunk) branch — the branch release branches merge back into on
14497
+ // a version cut (runVersionCutGit). Editable in the project detail Configuration tab; defaults
14498
+ // to `main`. Existing rows backfill to `main`.
14499
+ defaultBranch: text("default_branch").notNull().default("main"),
14500
+ // [v2.3.0] Git hosting provider — drives the PAT auth-username convention (GitHub
14501
+ // `x-access-token` vs GitLab `oauth2`) when the acting user has no configured Git name.
14502
+ // Auto-seeded from an imported repo's origin host; defaults to `github`.
14503
+ gitProvider: text("git_provider", { enum: ["github", "gitlab"] }).notNull().default("github"),
14493
14504
  createdBy: integer("created_by").notNull(),
14494
14505
  createdAt: text("created_at").notNull(),
14495
14506
  updatedAt: text("updated_at").notNull()
@@ -19079,6 +19090,68 @@ var documentSchema = external_exports.object({
19079
19090
  });
19080
19091
  var MAX_DOCUMENT_BYTES = 25 * 1024 * 1024;
19081
19092
 
19093
+ // ../core/src/schemas/git.ts
19094
+ var gitFileStatusSchema = external_exports.enum([
19095
+ "modified",
19096
+ "added",
19097
+ "deleted",
19098
+ "renamed",
19099
+ "untracked",
19100
+ "conflicted",
19101
+ "typechange"
19102
+ ]);
19103
+ var gitFileSchema = external_exports.object({
19104
+ path: external_exports.string(),
19105
+ status: gitFileStatusSchema,
19106
+ staged: external_exports.boolean(),
19107
+ unstaged: external_exports.boolean(),
19108
+ conflicted: external_exports.boolean(),
19109
+ /** Raw porcelain XY codes, retained for display / debugging. */
19110
+ indexStatus: external_exports.string(),
19111
+ worktreeStatus: external_exports.string()
19112
+ });
19113
+ var gitStatusResponseSchema = external_exports.object({
19114
+ /** false when the project folder is missing or has no git repository. */
19115
+ available: external_exports.boolean(),
19116
+ branch: external_exports.string().nullable(),
19117
+ upstream: external_exports.string().nullable(),
19118
+ ahead: external_exports.number(),
19119
+ behind: external_exports.number(),
19120
+ clean: external_exports.boolean(),
19121
+ hasConflicts: external_exports.boolean(),
19122
+ files: external_exports.array(gitFileSchema),
19123
+ /** Convenience: files.length (the dirty count the topbar indicator badges). */
19124
+ changedCount: external_exports.number()
19125
+ });
19126
+ var gitDiffResponseSchema = external_exports.object({
19127
+ path: external_exports.string(),
19128
+ status: gitFileStatusSchema,
19129
+ binary: external_exports.boolean(),
19130
+ conflicted: external_exports.boolean(),
19131
+ /** Non-conflicted compare — HEAD (before) vs working tree (after). null where absent. */
19132
+ oldContent: external_exports.string().nullable(),
19133
+ newContent: external_exports.string().nullable(),
19134
+ /** Conflicted 3-way — base (merged ancestor), ours (local), theirs (remote). null where absent. */
19135
+ base: external_exports.string().nullable(),
19136
+ ours: external_exports.string().nullable(),
19137
+ theirs: external_exports.string().nullable()
19138
+ });
19139
+ var gitCommitRequestSchema = external_exports.object({
19140
+ message: external_exports.string().trim().min(1, "A commit message is required").max(500),
19141
+ paths: external_exports.array(external_exports.string()).optional()
19142
+ });
19143
+ var gitDiscardRequestSchema = external_exports.object({
19144
+ paths: external_exports.array(external_exports.string()).min(1, "Select at least one file to roll back")
19145
+ });
19146
+ var gitResolveRequestSchema = external_exports.object({
19147
+ path: external_exports.string().min(1),
19148
+ resolution: external_exports.enum(["ours", "theirs", "manual"]),
19149
+ content: external_exports.string().optional()
19150
+ }).refine((v) => v.resolution !== "manual" || v.content !== void 0, {
19151
+ message: "Manual resolution requires the merged content",
19152
+ path: ["content"]
19153
+ });
19154
+
19082
19155
  // ../core/src/schemas/import.ts
19083
19156
  var importCandidateSchema = external_exports.object({
19084
19157
  folderName: external_exports.string(),
@@ -19287,6 +19360,16 @@ var brainstormMdResponseSchema = external_exports.object({
19287
19360
  var updateBrainstormMdSchema = external_exports.object({
19288
19361
  content: external_exports.string().min(1, "Brainstorm content must not be empty")
19289
19362
  });
19363
+ var gitUrlOrEmpty = /^$|^https:\/\/[^@/\s]+(\/\S*)?$|^(git@|ssh:\/\/)\S+$/;
19364
+ var branchNameOrEmpty = /^$|^(?!.*\.\.)[A-Za-z0-9][A-Za-z0-9._/-]*$/;
19365
+ var updateProjectGitRemoteSchema = external_exports.object({
19366
+ gitRemoteUrl: external_exports.string().trim().max(500).regex(gitUrlOrEmpty, "Must be empty or an https:// or ssh/git@ URL"),
19367
+ // [v2.3.0] Default (trunk) branch releases merge back into on a version cut. Optional for
19368
+ // backward compatibility; empty/omitted resolves to `main` server-side.
19369
+ defaultBranch: external_exports.string().trim().max(255).regex(branchNameOrEmpty, "Must be a valid branch name").default("main"),
19370
+ // [v2.3.0] Git hosting provider — drives the PAT auth-username convention. Defaults to `github`.
19371
+ gitProvider: external_exports.enum(["github", "gitlab"]).default("github")
19372
+ });
19290
19373
  var projectSummarySchema = external_exports.object({
19291
19374
  id: external_exports.number(),
19292
19375
  name: external_exports.string(),
@@ -19304,6 +19387,12 @@ var projectDetailSchema = external_exports.object({
19304
19387
  workflowPhase: workflowPhaseSchema,
19305
19388
  creationStatus: creationStatusSchema.nullable(),
19306
19389
  isArchived: external_exports.boolean(),
19390
+ // [v2.2.0] Push destination for Source Control (imported repos auto-seed it from origin).
19391
+ gitRemoteUrl: external_exports.string().nullable(),
19392
+ // [v2.3.0] Default (trunk) branch releases merge back into on a version cut (defaults to `main`).
19393
+ defaultBranch: external_exports.string(),
19394
+ // [v2.3.0] Git hosting provider (drives the PAT auth-username convention; defaults to `github`).
19395
+ gitProvider: external_exports.enum(["github", "gitlab"]),
19307
19396
  applications: external_exports.array(
19308
19397
  external_exports.object({
19309
19398
  id: external_exports.number(),
@@ -19682,11 +19771,11 @@ process.once("SIGINT", () => {
19682
19771
  function resolveHub(db, workspacePath, projectId) {
19683
19772
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
19684
19773
  if (!project) throw svcError("PROJECT_NOT_FOUND", 404);
19685
- const projectDir = join2(workspacePath, project.workspaceFolderName);
19686
- const hubDir = [join2(projectDir, "mockup"), join2(projectDir, "source", "mockup")].find(
19774
+ const projectDir2 = join2(workspacePath, project.workspaceFolderName);
19775
+ const hubDir = [join2(projectDir2, "mockup"), join2(projectDir2, "source", "mockup")].find(
19687
19776
  (d) => existsSync2(join2(d, "server.js"))
19688
19777
  );
19689
- if (!hubDir) return { hubDir: join2(projectDir, "mockup"), present: false, port: 3e3 };
19778
+ if (!hubDir) return { hubDir: join2(projectDir2, "mockup"), present: false, port: 3e3 };
19690
19779
  let port = 3e3;
19691
19780
  try {
19692
19781
  const config = JSON.parse(readFileSync2(join2(hubDir, "mockup.config.json"), "utf8"));
@@ -19782,17 +19871,17 @@ function registerArtifact(db, conductorRunId, applicationId, artifactType, fileP
19782
19871
  updatedAt: now
19783
19872
  }).returning({ id: artifacts.id }).get().id;
19784
19873
  }
19785
- function resolveAppFolderName(projectDir, appName) {
19874
+ function resolveAppFolderName(projectDir2, appName) {
19786
19875
  const candidates = [appName, appName.replace(/-/g, "_"), appName.replace(/_/g, "-")];
19787
19876
  for (const candidate of candidates) {
19788
- if (existsSync3(join3(projectDir, candidate))) return candidate;
19789
- if (existsSync3(join3(projectDir, "source", candidate))) return `source/${candidate}`;
19877
+ if (existsSync3(join3(projectDir2, candidate))) return candidate;
19878
+ if (existsSync3(join3(projectDir2, "source", candidate))) return `source/${candidate}`;
19790
19879
  }
19791
19880
  const normalize2 = (s) => s.replace(/^\d+[_-]/, "").toLowerCase().replace(/[^a-z0-9]/g, "");
19792
19881
  const target = normalize2(appName);
19793
19882
  const prefixMatches = [];
19794
19883
  for (const base of ["", "source"]) {
19795
- const dir = base ? join3(projectDir, base) : projectDir;
19884
+ const dir = base ? join3(projectDir2, base) : projectDir2;
19796
19885
  if (!existsSync3(dir)) continue;
19797
19886
  for (const entry of readdirSync(dir)) {
19798
19887
  try {
@@ -19847,9 +19936,9 @@ function registerConductorArtifacts(db, workspacePath, runId) {
19847
19936
  const project = db.select().from(projects).where(eq(projects.id, run.projectId)).get();
19848
19937
  const app = db.select().from(applications).where(eq(applications.id, run.applicationId)).get();
19849
19938
  if (!project || !app) return;
19850
- const projectDir = join3(workspacePath, project.workspaceFolderName);
19851
- const appRel = resolveAppFolderName(projectDir, app.name);
19852
- const contextDir = join3(projectDir, appRel, "context");
19939
+ const projectDir2 = join3(workspacePath, project.workspaceFolderName);
19940
+ const appRel = resolveAppFolderName(projectDir2, app.name);
19941
+ const contextDir = join3(projectDir2, appRel, "context");
19853
19942
  const slugs = /* @__PURE__ */ new Set();
19854
19943
  for (const sub of ["model", "specification", "test"]) {
19855
19944
  const dir = join3(contextDir, sub);
@@ -19891,7 +19980,7 @@ function registerConductorArtifacts(db, workspacePath, runId) {
19891
19980
  }
19892
19981
  }
19893
19982
  if (run.conductorType !== "prepare") {
19894
- for (const srcRel of sweepSourceFiles(join3(projectDir, appRel))) {
19983
+ for (const srcRel of sweepSourceFiles(join3(projectDir2, appRel))) {
19895
19984
  registerArtifact(db, runId, app.id, "source_code", `${appRel}/${srcRel}`, srcRel);
19896
19985
  }
19897
19986
  }
@@ -19949,9 +20038,9 @@ function getModuleModel(db, workspacePath, projectId, applicationId, moduleName)
19949
20038
  const app = db.select().from(applications).where(eq(applications.id, applicationId)).get();
19950
20039
  if (!app) throw svcError2("APP_NOT_FOUND", 404);
19951
20040
  const kebab = moduleName.toLowerCase().replace(/\s+/g, "-");
19952
- const projectDir = join3(workspacePath, project.workspaceFolderName);
19953
- const appRel = resolveAppFolderName(projectDir, app.name);
19954
- const modelPath = join3(projectDir, appRel, "context", "model", kebab, "model.md");
20041
+ const projectDir2 = join3(workspacePath, project.workspaceFolderName);
20042
+ const appRel = resolveAppFolderName(projectDir2, app.name);
20043
+ const modelPath = join3(projectDir2, appRel, "context", "model", kebab, "model.md");
19955
20044
  if (!existsSync3(modelPath)) throw svcError2("MODEL_NOT_FOUND", 404);
19956
20045
  return { content: readFileSync3(modelPath, "utf8") };
19957
20046
  }
@@ -20777,17 +20866,101 @@ function writeSectionToDisk(db, workspacePath, projectId, applicationId, section
20777
20866
  import { execFileSync } from "child_process";
20778
20867
  import { existsSync as existsSync6 } from "fs";
20779
20868
  import { join as join6 } from "path";
20780
- function git(cwd, args) {
20781
- return execFileSync("git", args, {
20782
- cwd,
20783
- stdio: "pipe",
20784
- timeout: 6e4,
20785
- env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
20786
- }).toString().trim();
20869
+ function defaultAuthUsername(provider) {
20870
+ return provider === "gitlab" ? "oauth2" : "x-access-token";
20871
+ }
20872
+ function buildPushAuthArgs(auth) {
20873
+ if (!auth) return [];
20874
+ const b64 = Buffer.from(`${auth.username}:${auth.token}`).toString("base64");
20875
+ return ["-c", `http.extraHeader=AUTHORIZATION: basic ${b64}`];
20876
+ }
20877
+ function redactAuthHeaderFromError(e, b64) {
20878
+ const err = e;
20879
+ if (typeof err.message === "string") err.message = err.message.split(b64).join("***");
20880
+ if (typeof err.cmd === "string") err.cmd = err.cmd.split(b64).join("***");
20881
+ if (Array.isArray(err.spawnargs)) {
20882
+ err.spawnargs = err.spawnargs.map((a) => typeof a === "string" ? a.split(b64).join("***") : a);
20883
+ }
20884
+ }
20885
+ function git(cwd, args, auth) {
20886
+ try {
20887
+ return execFileSync("git", [...buildPushAuthArgs(auth), ...args], {
20888
+ cwd,
20889
+ stdio: "pipe",
20890
+ timeout: 6e4,
20891
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
20892
+ }).toString().trim();
20893
+ } catch (e) {
20894
+ if (auth) {
20895
+ const b64 = Buffer.from(`${auth.username}:${auth.token}`).toString("base64");
20896
+ redactAuthHeaderFromError(e, b64);
20897
+ }
20898
+ throw e;
20899
+ }
20900
+ }
20901
+ function readCurrentBranch(projectDir2) {
20902
+ try {
20903
+ return git(projectDir2, ["branch", "--show-current"]);
20904
+ } catch {
20905
+ return "";
20906
+ }
20907
+ }
20908
+ function readOriginUrl(projectDir2) {
20909
+ try {
20910
+ const url = git(projectDir2, ["config", "--get", "remote.origin.url"]);
20911
+ return url === "" ? void 0 : url;
20912
+ } catch {
20913
+ return void 0;
20914
+ }
20915
+ }
20916
+ function stripHttpsCredentials(url) {
20917
+ const scheme = url.startsWith("https://") ? "https://" : url.startsWith("http://") ? "http://" : null;
20918
+ if (!scheme) return url;
20919
+ const rest = url.slice(scheme.length);
20920
+ const slash = rest.indexOf("/");
20921
+ const authority = slash === -1 ? rest : rest.slice(0, slash);
20922
+ const at = authority.indexOf("@");
20923
+ if (at === -1) return url;
20924
+ return scheme + rest.slice(at + 1);
20925
+ }
20926
+ function ensureOrigin(projectDir2, url) {
20927
+ const remotes = git(projectDir2, ["remote"]).split("\n");
20928
+ if (remotes.includes("origin")) git(projectDir2, ["remote", "set-url", "origin", url]);
20929
+ else git(projectDir2, ["remote", "add", "origin", url]);
20930
+ }
20931
+ function resolveGitPushContext(db, projectId, userId) {
20932
+ const project = db.select({
20933
+ gitRemoteUrl: projects.gitRemoteUrl,
20934
+ defaultBranch: projects.defaultBranch,
20935
+ gitProvider: projects.gitProvider
20936
+ }).from(projects).where(eq(projects.id, projectId)).get();
20937
+ const remoteUrl = project?.gitRemoteUrl ?? void 0;
20938
+ const defaultBranch = project?.defaultBranch ?? void 0;
20939
+ const provider = project?.gitProvider ?? void 0;
20940
+ if (!userId) return { remoteUrl, defaultBranch, provider };
20941
+ const user = db.select({ gitName: users.gitName, gitPat: users.gitPat }).from(users).where(eq(users.id, userId)).get();
20942
+ if (!user?.gitPat) return { remoteUrl, defaultBranch, provider };
20943
+ return {
20944
+ remoteUrl,
20945
+ defaultBranch,
20946
+ provider,
20947
+ auth: { username: user.gitName || defaultAuthUsername(provider), token: user.gitPat }
20948
+ };
20787
20949
  }
20788
- function deriveVersionBranch(semver) {
20950
+ function deriveVersionBranch(semver, trunk = "main") {
20789
20951
  const s = semver ?? "v1.0.0";
20790
- return s === "v1.0.0" ? "main" : `release-${s.replace(/^v/, "")}`;
20952
+ return s === "v1.0.0" ? trunk : `release-${s.replace(/^v/, "")}`;
20953
+ }
20954
+ function detectTrunkBranch(projectDir2, startingBranch) {
20955
+ try {
20956
+ const ref = git(projectDir2, ["symbolic-ref", "--quiet", "--short", "refs/remotes/origin/HEAD"]);
20957
+ const name = ref.startsWith("origin/") ? ref.slice("origin/".length) : "";
20958
+ if (name) return name;
20959
+ } catch {
20960
+ }
20961
+ if (branchExists(projectDir2, "main")) return "main";
20962
+ if (branchExists(projectDir2, "master")) return "master";
20963
+ return startingBranch || "main";
20791
20964
  }
20792
20965
  var GitCutError = class extends Error {
20793
20966
  code = "GIT_OPERATION_FAILED";
@@ -20795,117 +20968,120 @@ var GitCutError = class extends Error {
20795
20968
  };
20796
20969
  function gitErrorDetail(e) {
20797
20970
  const stderr = e.stderr?.toString().trim();
20798
- const detail = stderr || e.message || String(e);
20799
- return detail.replace(/(https?:\/\/)[^@/\s]+@/gi, "$1***@");
20971
+ const detail2 = stderr || e.message || String(e);
20972
+ return detail2.replace(/(https?:\/\/)[^@/\s]+@/gi, "$1***@").replace(/authorization:\s*basic\s+\S+/gi, "AUTHORIZATION: basic ***");
20800
20973
  }
20801
- function branchExists(projectDir, branch) {
20974
+ function branchExists(projectDir2, branch) {
20802
20975
  try {
20803
- git(projectDir, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
20976
+ git(projectDir2, ["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]);
20804
20977
  return true;
20805
20978
  } catch {
20806
20979
  return false;
20807
20980
  }
20808
20981
  }
20809
- function runVersionCutGit(projectDir, prevSemver, newSemver) {
20810
- const previousBranch = deriveVersionBranch(prevSemver);
20982
+ function runVersionCutGit(projectDir2, prevSemver, newSemver, opts) {
20811
20983
  const newBranch = `release-${newSemver.replace(/^v/, "")}`;
20812
- if (!existsSync6(join6(projectDir, ".git"))) {
20984
+ if (!existsSync6(join6(projectDir2, ".git"))) {
20813
20985
  throw new GitCutError(
20814
- `${projectDir} has no git repository \u2014 run "git init" in the project folder before adding a version`
20986
+ `${projectDir2} has no git repository \u2014 run "git init" in the project folder before adding a version`
20815
20987
  );
20816
20988
  }
20817
20989
  const fail = (step, e) => {
20818
20990
  throw new GitCutError(`${step} failed: ${gitErrorDetail(e)}`);
20819
20991
  };
20820
- const startingBranch = git(projectDir, ["branch", "--show-current"]);
20992
+ const startingBranch = git(projectDir2, ["branch", "--show-current"]);
20821
20993
  if (startingBranch === "") {
20822
20994
  throw new GitCutError(
20823
20995
  "the repository is in detached HEAD state \u2014 check out a branch before adding a version"
20824
20996
  );
20825
20997
  }
20998
+ const trunk = opts?.defaultBranch?.trim() || detectTrunkBranch(projectDir2, startingBranch);
20999
+ const previousBranch = deriveVersionBranch(prevSemver, trunk);
20826
21000
  try {
20827
- if (git(projectDir, ["status", "--porcelain"]) !== "") {
20828
- git(projectDir, ["add", "-A"]);
20829
- git(projectDir, ["commit", "-m", `chore(version): finalize ${prevSemver}`]);
21001
+ if (git(projectDir2, ["status", "--porcelain"]) !== "") {
21002
+ git(projectDir2, ["add", "-A"]);
21003
+ git(projectDir2, ["commit", "-m", `chore(version): finalize ${prevSemver}`]);
20830
21004
  }
20831
21005
  } catch (e) {
20832
21006
  fail("committing pending changes", e);
20833
21007
  }
20834
21008
  try {
20835
- if (branchExists(projectDir, "main")) git(projectDir, ["checkout", "main"]);
20836
- else git(projectDir, ["checkout", "-b", "main"]);
21009
+ if (branchExists(projectDir2, trunk)) git(projectDir2, ["checkout", trunk]);
21010
+ else git(projectDir2, ["checkout", "-b", trunk]);
20837
21011
  } catch (e) {
20838
- fail("checking out main", e);
21012
+ fail(`checking out ${trunk}`, e);
20839
21013
  }
20840
- const mergeSource = branchExists(projectDir, previousBranch) ? previousBranch : startingBranch;
20841
- if (mergeSource !== "main") {
21014
+ const mergeSource = branchExists(projectDir2, previousBranch) ? previousBranch : startingBranch;
21015
+ if (mergeSource !== trunk) {
20842
21016
  try {
20843
- git(projectDir, ["merge", mergeSource, "-X", "theirs", "--no-edit"]);
21017
+ git(projectDir2, ["merge", mergeSource, "-X", "theirs", "--no-edit"]);
20844
21018
  } catch (e) {
20845
21019
  try {
20846
- git(projectDir, ["merge", "--abort"]);
21020
+ git(projectDir2, ["merge", "--abort"]);
20847
21021
  } catch {
20848
21022
  }
20849
21023
  try {
20850
- git(projectDir, ["checkout", startingBranch]);
21024
+ git(projectDir2, ["checkout", startingBranch]);
20851
21025
  } catch {
20852
21026
  }
20853
- fail(`merging ${mergeSource} into main`, e);
21027
+ fail(`merging ${mergeSource} into ${trunk}`, e);
20854
21028
  }
20855
21029
  }
20856
- if (branchExists(projectDir, newBranch)) {
21030
+ if (branchExists(projectDir2, newBranch)) {
20857
21031
  let newBranchMerged = true;
20858
21032
  try {
20859
- git(projectDir, ["merge-base", "--is-ancestor", newBranch, "main"]);
21033
+ git(projectDir2, ["merge-base", "--is-ancestor", newBranch, trunk]);
20860
21034
  } catch {
20861
21035
  newBranchMerged = false;
20862
21036
  }
20863
21037
  if (!newBranchMerged) {
20864
21038
  try {
20865
- git(projectDir, ["merge", newBranch, "-X", "theirs", "--no-edit"]);
21039
+ git(projectDir2, ["merge", newBranch, "-X", "theirs", "--no-edit"]);
20866
21040
  } catch (e) {
20867
21041
  try {
20868
- git(projectDir, ["merge", "--abort"]);
21042
+ git(projectDir2, ["merge", "--abort"]);
20869
21043
  } catch {
20870
21044
  }
20871
- fail(`merging ${newBranch} into main`, e);
21045
+ fail(`merging ${newBranch} into ${trunk}`, e);
20872
21046
  }
20873
21047
  }
20874
21048
  }
20875
21049
  let commitHash = "";
20876
21050
  try {
20877
- git(projectDir, ["checkout", "-B", newBranch]);
20878
- commitHash = git(projectDir, ["rev-parse", "--short", "HEAD"]);
21051
+ git(projectDir2, ["checkout", "-B", newBranch]);
21052
+ commitHash = git(projectDir2, ["rev-parse", "--short", "HEAD"]);
20879
21053
  } catch (e) {
20880
21054
  fail(`creating ${newBranch}`, e);
20881
21055
  }
20882
21056
  let pushed = false;
20883
21057
  try {
20884
- const remotes = git(projectDir, ["remote"]);
21058
+ if (opts?.remoteUrl) ensureOrigin(projectDir2, opts.remoteUrl);
21059
+ const remotes = git(projectDir2, ["remote"]);
20885
21060
  if (remotes.split("\n").includes("origin")) {
20886
- git(projectDir, ["push", "--set-upstream", "origin", "main", newBranch]);
21061
+ git(projectDir2, ["push", "--set-upstream", "origin", trunk, newBranch], opts?.auth);
20887
21062
  pushed = true;
20888
21063
  }
20889
21064
  } catch (e) {
20890
- fail(`pushing main and ${newBranch}`, e);
21065
+ fail(`pushing ${trunk} and ${newBranch}`, e);
20891
21066
  }
20892
21067
  return { previousBranch, newBranch, commitHash, pushed };
20893
21068
  }
20894
- function runProjectGit(projectDir, message) {
20895
- if (!existsSync6(join6(projectDir, ".git"))) {
21069
+ function runProjectGit(projectDir2, message, opts) {
21070
+ if (!existsSync6(join6(projectDir2, ".git"))) {
20896
21071
  return { branch: "", commitHash: "", status: "skipped" };
20897
21072
  }
20898
21073
  try {
20899
- git(projectDir, ["add", "-A"]);
21074
+ git(projectDir2, ["add", "-A"]);
20900
21075
  try {
20901
- git(projectDir, ["commit", "-m", message]);
21076
+ git(projectDir2, ["commit", "-m", message]);
20902
21077
  } catch {
20903
21078
  }
20904
- const branch = git(projectDir, ["branch", "--show-current"]);
20905
- const commitHash = git(projectDir, ["rev-parse", "--short", "HEAD"]);
20906
- const remotes = git(projectDir, ["remote"]);
21079
+ const branch = git(projectDir2, ["branch", "--show-current"]);
21080
+ const commitHash = git(projectDir2, ["rev-parse", "--short", "HEAD"]);
21081
+ if (opts?.remoteUrl) ensureOrigin(projectDir2, opts.remoteUrl);
21082
+ const remotes = git(projectDir2, ["remote"]);
20907
21083
  if (remotes.split("\n").includes("origin")) {
20908
- git(projectDir, ["push", "--set-upstream", "origin", "HEAD"]);
21084
+ git(projectDir2, ["push", "--set-upstream", "origin", "HEAD"], opts?.auth);
20909
21085
  }
20910
21086
  return { branch, commitHash, status: "completed" };
20911
21087
  } catch {
@@ -20933,7 +21109,12 @@ function runPostConductorGit(db, workspacePath, workflowMarkdownId) {
20933
21109
  };
20934
21110
  const cwd = join6(workspacePath, project.workspaceFolderName);
20935
21111
  const semver = wfm.versionLabel ?? "latest";
20936
- const result = runProjectGit(cwd, `chore(${wfm.conductorType}): ${semver} conductor output`);
21112
+ const pushCtx = resolveGitPushContext(db, project.id, wfm.createdBy);
21113
+ const result = runProjectGit(
21114
+ cwd,
21115
+ `chore(${wfm.conductorType}): ${semver} conductor output`,
21116
+ pushCtx
21117
+ );
20937
21118
  finish(result.status, result.branch || null, result.commitHash || null);
20938
21119
  return result;
20939
21120
  }
@@ -21027,7 +21208,13 @@ function addVersion(db, projectId, versionType, createdBy, workspacePath) {
21027
21208
  const now = (/* @__PURE__ */ new Date()).toISOString();
21028
21209
  const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
21029
21210
  if (!project) throw svcError4("PROJECT_NOT_FOUND", 404);
21030
- runVersionCutGit(join7(workspacePath, project.workspaceFolderName), currentSemver, newSemver);
21211
+ const pushCtx = resolveGitPushContext(db, projectId, createdBy);
21212
+ runVersionCutGit(
21213
+ join7(workspacePath, project.workspaceFolderName),
21214
+ currentSemver,
21215
+ newSemver,
21216
+ pushCtx
21217
+ );
21031
21218
  const apps = db.select({ id: applications.id }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
21032
21219
  const snapshot = {};
21033
21220
  for (const app of apps) {
@@ -21174,7 +21361,8 @@ function syncFromDisk(db, workspacePath, projectFolder, session) {
21174
21361
  try {
21175
21362
  const result = runProjectGit(
21176
21363
  join8(workspacePath, projectFolder),
21177
- `chore(brainstorm): session "${session.title ?? session.id}" completed`
21364
+ `chore(brainstorm): session "${session.title ?? session.id}" completed`,
21365
+ resolveGitPushContext(db, session.projectId, session.createdBy)
21178
21366
  );
21179
21367
  if (session.createdBy) {
21180
21368
  createNotification(
@@ -21515,8 +21703,8 @@ function cicdGuardTokenValid(projectId, token) {
21515
21703
  function resolveCicdApp(db, workspacePath, projectId) {
21516
21704
  const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
21517
21705
  if (!project) throw svcError6("PROJECT_NOT_FOUND", 404);
21518
- const projectDir = join9(workspacePath, project.workspaceFolderName);
21519
- const appDir = join9(projectDir, "cicd", "app");
21706
+ const projectDir2 = join9(workspacePath, project.workspaceFolderName);
21707
+ const appDir = join9(projectDir2, "cicd", "app");
21520
21708
  const present = existsSync8(join9(appDir, "server.js"));
21521
21709
  let port = DEFAULT_CICD_APP_PORT;
21522
21710
  try {
@@ -21524,7 +21712,7 @@ function resolveCicdApp(db, workspacePath, projectId) {
21524
21712
  if (typeof config.port === "number") port = config.port;
21525
21713
  } catch {
21526
21714
  }
21527
- return { appDir, projectDir, present, port };
21715
+ return { appDir, projectDir: projectDir2, present, port };
21528
21716
  }
21529
21717
  function probeApp(port, timeoutMs = 1e3) {
21530
21718
  return new Promise((resolve5) => {
@@ -21703,7 +21891,8 @@ function generatePrdWizardMarkdown(db, workspacePath, requestId, targetModule) {
21703
21891
  try {
21704
21892
  const result = runProjectGit(
21705
21893
  join10(workspacePath, project.workspaceFolderName),
21706
- `chore(prd-wizard): ${filename} generated`
21894
+ `chore(prd-wizard): ${filename} generated`,
21895
+ resolveGitPushContext(db, project.id, req.createdBy)
21707
21896
  );
21708
21897
  createNotification(db, req.createdBy, "conductor_progress", "PRD Wizard markdown committed", {
21709
21898
  body: result.status === "completed" ? `git commit ${result.commitHash} on ${result.branch}` : `git ${result.status}`,
@@ -21721,12 +21910,12 @@ function snapshotPrdFiles(db, workspacePath, projectId) {
21721
21910
  const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
21722
21911
  const snapshot = /* @__PURE__ */ new Map();
21723
21912
  if (!project) return snapshot;
21724
- const projectDir = join10(workspacePath, project.workspaceFolderName);
21913
+ const projectDir2 = join10(workspacePath, project.workspaceFolderName);
21725
21914
  const apps = db.select({ name: applications.name }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
21726
21915
  for (const app of apps) {
21727
- const appRel = resolveAppFolderName(projectDir, app.name);
21916
+ const appRel = resolveAppFolderName(projectDir2, app.name);
21728
21917
  for (const rel of ["context/PRD.md", "CHANGELOG.md"]) {
21729
- const abs = join10(projectDir, appRel, ...rel.split("/"));
21918
+ const abs = join10(projectDir2, appRel, ...rel.split("/"));
21730
21919
  if (!existsSync9(abs)) continue;
21731
21920
  const stat = statSync2(abs);
21732
21921
  snapshot.set(abs, `${stat.mtimeMs}:${stat.size}`);
@@ -21766,7 +21955,8 @@ function pollWizardApply(db, workspacePath, requestId, state, nowMs) {
21766
21955
  if (project) {
21767
21956
  const result = runProjectGit(
21768
21957
  join10(workspacePath, project.workspaceFolderName),
21769
- `chore(prd-wizard): applied PRD updates (request ${requestId})`
21958
+ `chore(prd-wizard): applied PRD updates (request ${requestId})`,
21959
+ resolveGitPushContext(db, project.id, req.createdBy)
21770
21960
  );
21771
21961
  createNotification(db, req.createdBy, "conductor_complete", "PRD updates applied", {
21772
21962
  body: result.status === "completed" ? `git commit ${result.commitHash} on ${result.branch}` : `git ${result.status}`,
@@ -21820,20 +22010,20 @@ function git2(cwd, args) {
21820
22010
  env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
21821
22011
  }).toString().trim();
21822
22012
  }
21823
- function checkGitState(projectDir) {
21824
- if (!existsSync10(join11(projectDir, ".git"))) {
22013
+ function checkGitState(projectDir2) {
22014
+ if (!existsSync10(join11(projectDir2, ".git"))) {
21825
22015
  return { hasRepo: false, clean: false, pushed: false, currentBranch: null };
21826
22016
  }
21827
22017
  let currentBranch = null;
21828
22018
  let clean = false;
21829
22019
  let pushed = true;
21830
22020
  try {
21831
- currentBranch = git2(projectDir, ["branch", "--show-current"]) || null;
21832
- clean = git2(projectDir, ["status", "--porcelain"]) === "";
21833
- const remotes = git2(projectDir, ["remote"]).split("\n").filter(Boolean);
22021
+ currentBranch = git2(projectDir2, ["branch", "--show-current"]) || null;
22022
+ clean = git2(projectDir2, ["status", "--porcelain"]) === "";
22023
+ const remotes = git2(projectDir2, ["remote"]).split("\n").filter(Boolean);
21834
22024
  if (remotes.includes("origin")) {
21835
22025
  try {
21836
- pushed = git2(projectDir, ["rev-list", "--count", "@{u}..HEAD"]) === "0";
22026
+ pushed = git2(projectDir2, ["rev-list", "--count", "@{u}..HEAD"]) === "0";
21837
22027
  } catch {
21838
22028
  pushed = false;
21839
22029
  }
@@ -21845,12 +22035,12 @@ function checkGitState(projectDir) {
21845
22035
  }
21846
22036
  var GIT_STATE_TTL_MS = 3e3;
21847
22037
  var gitStateCache = /* @__PURE__ */ new Map();
21848
- function cachedCheckGitState(projectDir) {
21849
- const cached = gitStateCache.get(projectDir);
22038
+ function cachedCheckGitState(projectDir2) {
22039
+ const cached = gitStateCache.get(projectDir2);
21850
22040
  const now = Date.now();
21851
22041
  if (cached && now - cached.at < GIT_STATE_TTL_MS) return cached.state;
21852
- const state = checkGitState(projectDir);
21853
- gitStateCache.set(projectDir, { state, at: now });
22042
+ const state = checkGitState(projectDir2);
22043
+ gitStateCache.set(projectDir2, { state, at: now });
21854
22044
  return state;
21855
22045
  }
21856
22046
  function evaluateBuildGuard(db, workspacePath, projectId, versionId) {
@@ -21875,8 +22065,8 @@ function evaluateBuildGuard(db, workspacePath, projectId, versionId) {
21875
22065
  const applying = db.select({ id: prdWizardRequests.id }).from(prdWizardRequests).where(and(eq(prdWizardRequests.projectId, projectId), inArray(prdWizardRequests.id, applyIds))).limit(1).get();
21876
22066
  if (applying) reasons.push("a PRD Wizard update is being applied");
21877
22067
  }
21878
- const projectDir = join11(workspacePath, project.workspaceFolderName);
21879
- const gitState = cachedCheckGitState(projectDir);
22068
+ const projectDir2 = join11(workspacePath, project.workspaceFolderName);
22069
+ const gitState = cachedCheckGitState(projectDir2);
21880
22070
  if (!gitState.hasRepo) reasons.push("the project folder has no git repository");
21881
22071
  else {
21882
22072
  if (!gitState.clean) reasons.push("uncommitted changes in the working tree");
@@ -22061,15 +22251,19 @@ function requestPlan(db, workspacePath, projectId, userId) {
22061
22251
  stopCicdWatcher(setup.id, "generation");
22062
22252
  const project = requireProject(db, projectId);
22063
22253
  const apps = db.select({ name: applications.name }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
22064
- const projectDir = join13(workspacePath, project.workspaceFolderName);
22065
- const executionDir = join13(projectDir, "execution");
22254
+ const projectDir2 = join13(workspacePath, project.workspaceFolderName);
22255
+ const executionDir = join13(projectDir2, "execution");
22066
22256
  mkdirSync4(executionDir, { recursive: true });
22067
22257
  const filename = `cicd-plan-${setup.id}-${Date.now()}.md`;
22068
22258
  const filePath = join13(executionDir, filename);
22069
22259
  const relative3 = `execution/${filename}`;
22070
22260
  writeFileSync5(
22071
22261
  filePath,
22072
- buildPlanRequestMarkdown(project.name, apps.map((a) => a.name), relative3),
22262
+ buildPlanRequestMarkdown(
22263
+ project.name,
22264
+ apps.map((a) => a.name),
22265
+ relative3
22266
+ ),
22073
22267
  "utf8"
22074
22268
  );
22075
22269
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -22080,7 +22274,11 @@ function requestPlan(db, workspacePath, projectId, userId) {
22080
22274
  relatedEntityId: setup.id
22081
22275
  });
22082
22276
  try {
22083
- runProjectGit(projectDir, `chore(cicd): plan request ${filename}`);
22277
+ runProjectGit(
22278
+ projectDir2,
22279
+ `chore(cicd): plan request ${filename}`,
22280
+ resolveGitPushContext(db, projectId, userId)
22281
+ );
22084
22282
  } catch {
22085
22283
  }
22086
22284
  startCicdWatcher(db, workspacePath, setup.id, "plan", userId);
@@ -22104,9 +22302,9 @@ function pollCicdTracking(db, workspacePath, setupId, kind, notifyUserId) {
22104
22302
  if (setup.status !== expected) return "stopped";
22105
22303
  const project = db.select().from(projects).where(eq(projects.id, setup.projectId)).get();
22106
22304
  if (!project) return "stopped";
22107
- const projectDir = join13(workspacePath, project.workspaceFolderName);
22305
+ const projectDir2 = join13(workspacePath, project.workspaceFolderName);
22108
22306
  const trackingPath = join13(
22109
- projectDir,
22307
+ projectDir2,
22110
22308
  ...(kind === "plan" ? CICD_PLAN_RELATIVE : CICD_GENERATION_RELATIVE).split("/")
22111
22309
  );
22112
22310
  const requestedAt = kind === "plan" ? setup.planRequestedAt : setup.generateRequestedAt;
@@ -22136,7 +22334,11 @@ function pollCicdTracking(db, workspacePath, setupId, kind, notifyUserId) {
22136
22334
  relatedEntityId: setupId
22137
22335
  });
22138
22336
  try {
22139
- runProjectGit(projectDir, "chore(cicd): CI/CD app generated");
22337
+ runProjectGit(
22338
+ projectDir2,
22339
+ "chore(cicd): CI/CD app generated",
22340
+ resolveGitPushContext(db, setup.projectId, notifyUserId)
22341
+ );
22140
22342
  } catch {
22141
22343
  }
22142
22344
  }
@@ -22172,8 +22374,8 @@ function approvePlan(db, workspacePath, projectId, userId) {
22172
22374
  const setup = getOrCreateSetup(db, projectId, userId);
22173
22375
  if (setup.status !== "planned") throw svcError8("CICD_INVALID_STATE", 409);
22174
22376
  const project = requireProject(db, projectId);
22175
- const projectDir = join13(workspacePath, project.workspaceFolderName);
22176
- const planPath = join13(projectDir, ...CICD_PLAN_RELATIVE.split("/"));
22377
+ const projectDir2 = join13(workspacePath, project.workspaceFolderName);
22378
+ const planPath = join13(projectDir2, ...CICD_PLAN_RELATIVE.split("/"));
22177
22379
  if (parseTrackingStatus(planPath) !== "COMPLETED") throw svcError8("CICD_PLAN_NOT_READY", 409);
22178
22380
  const now = (/* @__PURE__ */ new Date()).toISOString();
22179
22381
  appendFileSync(planPath, `
@@ -22181,7 +22383,11 @@ function approvePlan(db, workspacePath, projectId, userId) {
22181
22383
  `);
22182
22384
  db.update(cicdSetups).set({ status: "approved", approvedBy: userId, approvedAt: now, updatedAt: now }).where(eq(cicdSetups.id, setup.id)).run();
22183
22385
  try {
22184
- runProjectGit(projectDir, "chore(cicd): deployment plan approved");
22386
+ runProjectGit(
22387
+ projectDir2,
22388
+ "chore(cicd): deployment plan approved",
22389
+ resolveGitPushContext(db, projectId, userId)
22390
+ );
22185
22391
  } catch {
22186
22392
  }
22187
22393
  }
@@ -22248,6 +22454,29 @@ function cicdRoutes(db, workspacePath, studioPort) {
22248
22454
  return errorResponse3(c, e);
22249
22455
  }
22250
22456
  });
22457
+ app.post("/:projectId/rescan", async (c) => {
22458
+ const projectId = parseProjectId(c);
22459
+ if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
22460
+ const user = c.get("user");
22461
+ try {
22462
+ const setup = getOrCreateSetup(db, projectId, user.id);
22463
+ if (setup.status === "planning") {
22464
+ pollCicdTracking(db, workspacePath, setup.id, "plan", user.id);
22465
+ } else if (setup.status === "generating") {
22466
+ pollCicdTracking(db, workspacePath, setup.id, "generation", user.id);
22467
+ }
22468
+ const fresh = getOrCreateSetup(db, projectId, user.id);
22469
+ if (fresh.status === "planning" && !hasCicdWatcher(fresh.id, "plan")) {
22470
+ startCicdWatcher(db, workspacePath, fresh.id, "plan", user.id);
22471
+ }
22472
+ if (fresh.status === "generating" && !hasCicdWatcher(fresh.id, "generation")) {
22473
+ startCicdWatcher(db, workspacePath, fresh.id, "generation", user.id);
22474
+ }
22475
+ return c.json(await toDto8(c, fresh));
22476
+ } catch (e) {
22477
+ return errorResponse3(c, e);
22478
+ }
22479
+ });
22251
22480
  app.post("/:projectId/plan", (c) => {
22252
22481
  const projectId = parseProjectId(c);
22253
22482
  if (projectId === null) return c.json({ code: "INVALID_ID" }, 400);
@@ -22467,12 +22696,12 @@ function createProject(db, workspacePath, name, code, baseIdea, createdBy) {
22467
22696
  return projectId;
22468
22697
  }
22469
22698
  function scaffoldProjectFolder(workspacePath, folderName, name, baseIdea) {
22470
- const projectDir = join16(workspacePath, folderName);
22471
- mkdirSync6(projectDir, { recursive: true });
22472
- mkdirSync6(join16(projectDir, "execution"), { recursive: true });
22473
- mkdirSync6(join16(projectDir, "context"), { recursive: true });
22474
- mkdirSync6(join16(projectDir, "context", "references"), { recursive: true });
22475
- mkdirSync6(join16(projectDir, "shared_context"), { recursive: true });
22699
+ const projectDir2 = join16(workspacePath, folderName);
22700
+ mkdirSync6(projectDir2, { recursive: true });
22701
+ mkdirSync6(join16(projectDir2, "execution"), { recursive: true });
22702
+ mkdirSync6(join16(projectDir2, "context"), { recursive: true });
22703
+ mkdirSync6(join16(projectDir2, "context", "references"), { recursive: true });
22704
+ mkdirSync6(join16(projectDir2, "shared_context"), { recursive: true });
22476
22705
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22477
22706
  const template = [
22478
22707
  "---",
@@ -22512,9 +22741,9 @@ function scaffoldProjectFolder(workspacePath, folderName, name, baseIdea) {
22512
22741
  "# Feedback",
22513
22742
  "<!-- Human answers appear here -->"
22514
22743
  ].join("\n");
22515
- writeFileSync7(join16(projectDir, `${folderName}.brainstorm.md`), template, "utf8");
22744
+ writeFileSync7(join16(projectDir2, `${folderName}.brainstorm.md`), template, "utf8");
22516
22745
  writeFileSync7(
22517
- join16(projectDir, "context", "references", "brainstorm-protocol.md"),
22746
+ join16(projectDir2, "context", "references", "brainstorm-protocol.md"),
22518
22747
  "# Brainstorm Protocol\n\n[Installed by the co2 plugin]\n",
22519
22748
  "utf8"
22520
22749
  );
@@ -22613,6 +22842,9 @@ function getProjectDetail(db, workspacePath, projectId) {
22613
22842
  workflowPhase: project.workflowPhase,
22614
22843
  creationStatus,
22615
22844
  isArchived: project.isArchived,
22845
+ gitRemoteUrl: project.gitRemoteUrl ?? null,
22846
+ defaultBranch: project.defaultBranch ?? "main",
22847
+ gitProvider: project.gitProvider ?? "github",
22616
22848
  applications: apps,
22617
22849
  createdAt: project.createdAt
22618
22850
  };
@@ -22668,11 +22900,11 @@ function getProjectDetailSummary(db, projectId) {
22668
22900
  function regenerateCO2Files(db, workspacePath, projectId) {
22669
22901
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
22670
22902
  if (!project) throw svcError10("NOT_FOUND", 404);
22671
- const projectDir = join16(workspacePath, project.workspaceFolderName);
22672
- mkdirSync6(projectDir, { recursive: true });
22903
+ const projectDir2 = join16(workspacePath, project.workspaceFolderName);
22904
+ mkdirSync6(projectDir2, { recursive: true });
22673
22905
  const apps = db.select().from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
22674
22906
  writeFileSync7(
22675
- join16(projectDir, "CLAUDE.md"),
22907
+ join16(projectDir2, "CLAUDE.md"),
22676
22908
  project.rawClaudeMd ?? buildClaudeMd(project.name, apps),
22677
22909
  "utf8"
22678
22910
  );
@@ -22685,8 +22917,8 @@ function regenerateCO2Files(db, workspacePath, projectId) {
22685
22917
  }
22686
22918
  for (const app of apps) {
22687
22919
  const mods = db.select().from(projectModules).where(and(eq(projectModules.applicationId, app.id), eq(projectModules.isDeleted, 0))).all();
22688
- const appRel = resolveAppFolderName(projectDir, app.name);
22689
- const appDir = join16(projectDir, appRel, "context");
22920
+ const appRel = resolveAppFolderName(projectDir2, app.name);
22921
+ const appDir = join16(projectDir2, appRel, "context");
22690
22922
  mkdirSync6(appDir, { recursive: true });
22691
22923
  writeFileSync7(
22692
22924
  join16(appDir, "PRD.md"),
@@ -22743,10 +22975,10 @@ function toKebabCase(name) {
22743
22975
  function rescanClaudeMd(db, workspacePath, projectId) {
22744
22976
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
22745
22977
  if (!project) throw svcError10("NOT_FOUND", 404);
22746
- const projectDir = join16(workspacePath, project.workspaceFolderName);
22978
+ const projectDir2 = join16(workspacePath, project.workspaceFolderName);
22747
22979
  const claudeMdPath = [
22748
- join16(projectDir, "CLAUDE.md"),
22749
- join16(projectDir, "source", "CLAUDE.md")
22980
+ join16(projectDir2, "CLAUDE.md"),
22981
+ join16(projectDir2, "source", "CLAUDE.md")
22750
22982
  ].find((p) => existsSync14(p));
22751
22983
  if (!claudeMdPath) throw svcError10("CLAUDE_MD_MISSING", 404);
22752
22984
  const content = readFileSync9(claudeMdPath, "utf8");
@@ -22837,6 +23069,18 @@ function deriveAppCode(name) {
22837
23069
  const initials = name.split(/[\s_-]+/).filter(Boolean).map((w) => w[0].toUpperCase()).join("");
22838
23070
  return (initials || name.slice(0, 2)).slice(0, 4);
22839
23071
  }
23072
+ function updateProjectGitRemote(db, projectId, gitRemoteUrl, opts = {}) {
23073
+ const project = db.select({ id: projects.id }).from(projects).where(eq(projects.id, projectId)).limit(1).get();
23074
+ if (!project) throw svcError10("NOT_FOUND", 404);
23075
+ const trimmed = gitRemoteUrl.trim();
23076
+ const set = { gitRemoteUrl: trimmed === "" ? null : trimmed, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
23077
+ if (opts.defaultBranch !== void 0) {
23078
+ const b = opts.defaultBranch.trim();
23079
+ set.defaultBranch = b === "" ? "main" : b;
23080
+ }
23081
+ if (opts.gitProvider !== void 0) set.gitProvider = opts.gitProvider;
23082
+ db.update(projects).set(set).where(eq(projects.id, projectId)).run();
23083
+ }
22840
23084
  function archiveProject(db, projectId, archivedBy) {
22841
23085
  const project = db.select({ id: projects.id }).from(projects).where(eq(projects.id, projectId)).limit(1).get();
22842
23086
  if (!project) throw svcError10("NOT_FOUND", 404);
@@ -23497,9 +23741,9 @@ function writeBugMdFile(db, workspacePath, app, projectFolderName) {
23497
23741
  db.select({ id: prdVersions.id, semver: prdVersions.semver }).from(prdVersions).where(eq(prdVersions.projectId, app.projectId)).all().map((v) => [v.id, v.semver])
23498
23742
  );
23499
23743
  const content = buildBugMd(modules, bugs, semverByVersionId);
23500
- const projectDir = join17(workspacePath, projectFolderName);
23501
- const appRel = resolveAppFolderName(projectDir, app.name);
23502
- const appContextDir = join17(projectDir, appRel, "context");
23744
+ const projectDir2 = join17(workspacePath, projectFolderName);
23745
+ const appRel = resolveAppFolderName(projectDir2, app.name);
23746
+ const appContextDir = join17(projectDir2, appRel, "context");
23503
23747
  mkdirSync7(appContextDir, { recursive: true });
23504
23748
  writeFileSync8(join17(appContextDir, "BUG.md"), content, "utf8");
23505
23749
  return { content, bugCount: bugs.length };
@@ -23534,8 +23778,8 @@ function syncBugMdToDisk(db, workspacePath, applicationId, bugId, mode = "replac
23534
23778
  const project = db.select({ workspaceFolderName: projects.workspaceFolderName }).from(projects).where(eq(projects.id, app.projectId)).get();
23535
23779
  if (!project) return;
23536
23780
  if (bugId !== void 0) {
23537
- const projectDir = join17(workspacePath, project.workspaceFolderName);
23538
- const file = join17(projectDir, resolveAppFolderName(projectDir, app.name), "context", "BUG.md");
23781
+ const projectDir2 = join17(workspacePath, project.workspaceFolderName);
23782
+ const file = join17(projectDir2, resolveAppFolderName(projectDir2, app.name), "context", "BUG.md");
23539
23783
  if (existsSync15(file)) {
23540
23784
  const bug = db.select().from(bugReports).where(and(eq(bugReports.id, bugId), eq(bugReports.isDeleted, 0))).get();
23541
23785
  if (bug) {
@@ -23777,6 +24021,332 @@ function documentRoutes(db, dataDir) {
23777
24021
  return app;
23778
24022
  }
23779
24023
 
24024
+ // ../server/src/features/git/service.ts
24025
+ import { execFileSync as execFileSync3 } from "child_process";
24026
+ import { existsSync as existsSync17, readFileSync as readFileSync12, rmSync, writeFileSync as writeFileSync10 } from "fs";
24027
+ import { join as join19 } from "path";
24028
+ var GitServiceError = class extends Error {
24029
+ code;
24030
+ status;
24031
+ constructor(code, status, message) {
24032
+ super(message ?? code);
24033
+ this.code = code;
24034
+ this.status = status;
24035
+ }
24036
+ };
24037
+ function git3(cwd, args) {
24038
+ return execFileSync3("git", args, {
24039
+ cwd,
24040
+ stdio: "pipe",
24041
+ timeout: 3e4,
24042
+ maxBuffer: 64 * 1024 * 1024,
24043
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
24044
+ }).toString().trim();
24045
+ }
24046
+ function gitOrNull(cwd, args) {
24047
+ try {
24048
+ return execFileSync3("git", args, {
24049
+ cwd,
24050
+ stdio: "pipe",
24051
+ timeout: 3e4,
24052
+ maxBuffer: 64 * 1024 * 1024,
24053
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
24054
+ }).toString();
24055
+ } catch {
24056
+ return null;
24057
+ }
24058
+ }
24059
+ function projectDir(db, workspacePath, projectId) {
24060
+ const project = db.select({ folder: projects.workspaceFolderName }).from(projects).where(eq(projects.id, projectId)).get();
24061
+ if (!project) throw new GitServiceError("PROJECT_NOT_FOUND", 404);
24062
+ return join19(workspacePath, project.folder);
24063
+ }
24064
+ function isGitRepo(dir) {
24065
+ return existsSync17(join19(dir, ".git"));
24066
+ }
24067
+ function classifyStatus(x, y) {
24068
+ const conflictPairs = /* @__PURE__ */ new Set(["DD", "AU", "UD", "UA", "DU", "AA", "UU"]);
24069
+ const xy = `${x}${y}`;
24070
+ if (conflictPairs.has(xy)) {
24071
+ return { status: "conflicted", staged: false, unstaged: true, conflicted: true };
24072
+ }
24073
+ if (x === "?" && y === "?") {
24074
+ return { status: "untracked", staged: false, unstaged: true, conflicted: false };
24075
+ }
24076
+ const staged = x !== " " && x !== "?";
24077
+ const unstaged = y !== " " && y !== "?";
24078
+ const code = x !== " " && x !== "?" ? x : y;
24079
+ const status = code === "A" ? "added" : code === "D" ? "deleted" : code === "R" ? "renamed" : code === "T" ? "typechange" : "modified";
24080
+ return { status, staged, unstaged, conflicted: false };
24081
+ }
24082
+ function parseStatus2(raw2) {
24083
+ const parsed = { branch: null, upstream: null, ahead: 0, behind: 0, files: [] };
24084
+ const records = raw2.split("\0");
24085
+ for (let i = 0; i < records.length; i++) {
24086
+ const rec = records[i];
24087
+ if (rec === "" || rec === void 0) continue;
24088
+ if (rec.startsWith("## ")) {
24089
+ const info = rec.slice(3);
24090
+ const noCommits = info.match(/^No commits yet on (.+)$/);
24091
+ const head = info.split(" ")[0] ?? "";
24092
+ const dots = head.indexOf("...");
24093
+ parsed.branch = noCommits ? noCommits[1] ?? null : dots === -1 ? head : head.slice(0, dots);
24094
+ if (dots !== -1) parsed.upstream = head.slice(dots + 3);
24095
+ parsed.ahead = Number(info.match(/ahead (\d+)/)?.[1] ?? 0);
24096
+ parsed.behind = Number(info.match(/behind (\d+)/)?.[1] ?? 0);
24097
+ continue;
24098
+ }
24099
+ const x = rec[0] ?? " ";
24100
+ const y = rec[1] ?? " ";
24101
+ const path = rec.slice(3);
24102
+ if (x === "R" || x === "C" || y === "R" || y === "C") i++;
24103
+ const cls = classifyStatus(x, y);
24104
+ parsed.files.push({
24105
+ path,
24106
+ status: cls.status,
24107
+ staged: cls.staged,
24108
+ unstaged: cls.unstaged,
24109
+ conflicted: cls.conflicted,
24110
+ indexStatus: x,
24111
+ worktreeStatus: y
24112
+ });
24113
+ }
24114
+ return parsed;
24115
+ }
24116
+ function getGitStatus(db, workspacePath, projectId) {
24117
+ const dir = projectDir(db, workspacePath, projectId);
24118
+ const empty = {
24119
+ available: false,
24120
+ branch: null,
24121
+ upstream: null,
24122
+ ahead: 0,
24123
+ behind: 0,
24124
+ clean: true,
24125
+ hasConflicts: false,
24126
+ files: [],
24127
+ changedCount: 0
24128
+ };
24129
+ if (!existsSync17(dir) || !isGitRepo(dir)) return empty;
24130
+ let raw2;
24131
+ try {
24132
+ raw2 = execFileSync3("git", ["status", "--porcelain=v1", "-b", "-z"], {
24133
+ cwd: dir,
24134
+ stdio: "pipe",
24135
+ timeout: 3e4,
24136
+ maxBuffer: 64 * 1024 * 1024,
24137
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" }
24138
+ }).toString();
24139
+ } catch {
24140
+ return empty;
24141
+ }
24142
+ const p = parseStatus2(raw2);
24143
+ return {
24144
+ available: true,
24145
+ branch: p.branch,
24146
+ upstream: p.upstream,
24147
+ ahead: p.ahead,
24148
+ behind: p.behind,
24149
+ clean: p.files.length === 0,
24150
+ hasConflicts: p.files.some((f) => f.conflicted),
24151
+ files: p.files,
24152
+ changedCount: p.files.length
24153
+ };
24154
+ }
24155
+ function looksBinary(s) {
24156
+ return s?.includes("\0") ?? false;
24157
+ }
24158
+ function getFileDiff(db, workspacePath, projectId, path) {
24159
+ const dir = projectDir(db, workspacePath, projectId);
24160
+ if (!existsSync17(dir) || !isGitRepo(dir)) {
24161
+ throw new GitServiceError("NO_GIT_REPO", 404, "No git repository for this project");
24162
+ }
24163
+ const status = getGitStatus(db, workspacePath, projectId);
24164
+ const file = status.files.find((f) => f.path === path);
24165
+ const cls = file?.status ?? "modified";
24166
+ const conflicted = file?.conflicted ?? false;
24167
+ if (conflicted) {
24168
+ const base = gitOrNull(dir, ["show", `:1:${path}`]);
24169
+ const ours = gitOrNull(dir, ["show", `:2:${path}`]);
24170
+ const theirs = gitOrNull(dir, ["show", `:3:${path}`]);
24171
+ const working = readWorking(dir, path);
24172
+ const binary2 = looksBinary(base) || looksBinary(ours) || looksBinary(theirs);
24173
+ return {
24174
+ path,
24175
+ status: cls,
24176
+ binary: binary2,
24177
+ conflicted: true,
24178
+ oldContent: ours,
24179
+ // pre-populate the merge editor with our side by default
24180
+ newContent: working,
24181
+ base,
24182
+ ours,
24183
+ theirs
24184
+ };
24185
+ }
24186
+ const oldContent = gitOrNull(dir, ["show", `HEAD:${path}`]);
24187
+ const newContent = cls === "deleted" ? null : readWorking(dir, path);
24188
+ const binary = looksBinary(oldContent) || looksBinary(newContent);
24189
+ return {
24190
+ path,
24191
+ status: cls,
24192
+ binary,
24193
+ conflicted: false,
24194
+ oldContent: cls === "added" || cls === "untracked" ? null : oldContent,
24195
+ newContent,
24196
+ base: null,
24197
+ ours: null,
24198
+ theirs: null
24199
+ };
24200
+ }
24201
+ function readWorking(dir, path) {
24202
+ const abs = join19(dir, path);
24203
+ if (!existsSync17(abs)) return null;
24204
+ try {
24205
+ return readFileSync12(abs, "utf8");
24206
+ } catch {
24207
+ return null;
24208
+ }
24209
+ }
24210
+ function commitChanges(db, workspacePath, projectId, input) {
24211
+ const dir = projectDir(db, workspacePath, projectId);
24212
+ if (!existsSync17(dir) || !isGitRepo(dir)) {
24213
+ throw new GitServiceError("NO_GIT_REPO", 404, "No git repository for this project");
24214
+ }
24215
+ try {
24216
+ if (input.paths && input.paths.length > 0) git3(dir, ["add", "--", ...input.paths]);
24217
+ else git3(dir, ["add", "-A"]);
24218
+ } catch (e) {
24219
+ throw new GitServiceError("GIT_ADD_FAILED", 409, detail(e));
24220
+ }
24221
+ try {
24222
+ git3(dir, ["diff", "--cached", "--quiet"]);
24223
+ throw new GitServiceError("NOTHING_TO_COMMIT", 400, "No staged changes to commit");
24224
+ } catch (e) {
24225
+ if (e instanceof GitServiceError) throw e;
24226
+ }
24227
+ try {
24228
+ git3(dir, ["commit", "-m", input.message]);
24229
+ } catch (e) {
24230
+ throw new GitServiceError("GIT_COMMIT_FAILED", 409, detail(e));
24231
+ }
24232
+ const branch = safe(() => git3(dir, ["branch", "--show-current"])) ?? "";
24233
+ const commitHash = safe(() => git3(dir, ["rev-parse", "--short", "HEAD"])) ?? "";
24234
+ return { committed: true, branch, commitHash };
24235
+ }
24236
+ function discardChanges(db, workspacePath, projectId, paths) {
24237
+ const dir = projectDir(db, workspacePath, projectId);
24238
+ if (!existsSync17(dir) || !isGitRepo(dir)) {
24239
+ throw new GitServiceError("NO_GIT_REPO", 404, "No git repository for this project");
24240
+ }
24241
+ for (const path of paths) {
24242
+ safe(() => git3(dir, ["reset", "-q", "HEAD", "--", path]));
24243
+ const inHead = safe(() => git3(dir, ["cat-file", "-e", `HEAD:${path}`])) !== null;
24244
+ if (inHead) {
24245
+ try {
24246
+ git3(dir, ["checkout", "--", path]);
24247
+ } catch (e) {
24248
+ throw new GitServiceError("GIT_DISCARD_FAILED", 409, detail(e));
24249
+ }
24250
+ } else {
24251
+ const abs = join19(dir, path);
24252
+ if (existsSync17(abs)) rmSync(abs, { force: true });
24253
+ }
24254
+ }
24255
+ }
24256
+ function resolveConflict(db, workspacePath, projectId, input) {
24257
+ const dir = projectDir(db, workspacePath, projectId);
24258
+ if (!existsSync17(dir) || !isGitRepo(dir)) {
24259
+ throw new GitServiceError("NO_GIT_REPO", 404, "No git repository for this project");
24260
+ }
24261
+ try {
24262
+ if (input.resolution === "manual") {
24263
+ if (input.content === void 0) {
24264
+ throw new GitServiceError("CONTENT_REQUIRED", 400, "Manual resolution requires content");
24265
+ }
24266
+ writeFileSync10(join19(dir, input.path), input.content, "utf8");
24267
+ } else {
24268
+ git3(dir, ["checkout", `--${input.resolution}`, "--", input.path]);
24269
+ }
24270
+ git3(dir, ["add", "--", input.path]);
24271
+ } catch (e) {
24272
+ if (e instanceof GitServiceError) throw e;
24273
+ throw new GitServiceError("GIT_RESOLVE_FAILED", 409, detail(e));
24274
+ }
24275
+ }
24276
+ function detail(e) {
24277
+ const stderr = e.stderr?.toString().trim();
24278
+ return stderr || e.message || String(e);
24279
+ }
24280
+ function safe(fn) {
24281
+ try {
24282
+ return fn();
24283
+ } catch {
24284
+ return null;
24285
+ }
24286
+ }
24287
+
24288
+ // ../server/src/features/git/routes.ts
24289
+ function errorResponse7(c, e) {
24290
+ const err = e;
24291
+ const status = err.status ?? 500;
24292
+ return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
24293
+ }
24294
+ function gitRoutes(db, workspacePath) {
24295
+ const app = new Hono2();
24296
+ app.get("/status/:projectId", (c) => {
24297
+ const projectId = Number(c.req.param("projectId"));
24298
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24299
+ try {
24300
+ return c.json(getGitStatus(db, workspacePath, projectId));
24301
+ } catch (e) {
24302
+ return errorResponse7(c, e);
24303
+ }
24304
+ });
24305
+ app.get("/diff/:projectId", (c) => {
24306
+ const projectId = Number(c.req.param("projectId"));
24307
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24308
+ const path = c.req.query("path");
24309
+ if (!path) return c.json({ code: "PATH_REQUIRED" }, 400);
24310
+ try {
24311
+ return c.json(getFileDiff(db, workspacePath, projectId, path));
24312
+ } catch (e) {
24313
+ return errorResponse7(c, e);
24314
+ }
24315
+ });
24316
+ app.post("/commit/:projectId", zValidator("json", gitCommitRequestSchema), (c) => {
24317
+ const projectId = Number(c.req.param("projectId"));
24318
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24319
+ try {
24320
+ const body = c.req.valid("json");
24321
+ return c.json(commitChanges(db, workspacePath, projectId, body));
24322
+ } catch (e) {
24323
+ return errorResponse7(c, e);
24324
+ }
24325
+ });
24326
+ app.post("/discard/:projectId", zValidator("json", gitDiscardRequestSchema), (c) => {
24327
+ const projectId = Number(c.req.param("projectId"));
24328
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24329
+ try {
24330
+ discardChanges(db, workspacePath, projectId, c.req.valid("json").paths);
24331
+ return c.json({ ok: true });
24332
+ } catch (e) {
24333
+ return errorResponse7(c, e);
24334
+ }
24335
+ });
24336
+ app.post("/resolve/:projectId", zValidator("json", gitResolveRequestSchema), (c) => {
24337
+ const projectId = Number(c.req.param("projectId"));
24338
+ if (Number.isNaN(projectId)) return c.json({ code: "INVALID_ID" }, 400);
24339
+ try {
24340
+ resolveConflict(db, workspacePath, projectId, c.req.valid("json"));
24341
+ return c.json({ ok: true });
24342
+ } catch (e) {
24343
+ if (e instanceof GitServiceError) return errorResponse7(c, e);
24344
+ return errorResponse7(c, e);
24345
+ }
24346
+ });
24347
+ return app;
24348
+ }
24349
+
23780
24350
  // ../server/src/features/notification/routes.ts
23781
24351
  function notificationRoutes(db) {
23782
24352
  const app = new Hono2();
@@ -23807,7 +24377,7 @@ function notificationRoutes(db) {
23807
24377
  }
23808
24378
 
23809
24379
  // ../server/src/features/prd-wizard/routes.ts
23810
- function errorResponse7(c, e) {
24380
+ function errorResponse8(c, e) {
23811
24381
  const err = e;
23812
24382
  const status = err.status ?? 500;
23813
24383
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -23836,7 +24406,7 @@ function prdWizardRoutes(db, workspacePath) {
23836
24406
  startWizardApplyWatcher(db, workspacePath, requestId);
23837
24407
  return c.json({ requestId, filePath, status: "generated" }, 201);
23838
24408
  } catch (e) {
23839
- return errorResponse7(c, e);
24409
+ return errorResponse8(c, e);
23840
24410
  }
23841
24411
  });
23842
24412
  app.get("/:projectId", (c) => {
@@ -23848,14 +24418,14 @@ function prdWizardRoutes(db, workspacePath) {
23848
24418
  try {
23849
24419
  return c.json(listPrdWizardRequests(db, projectId, prdVersionId));
23850
24420
  } catch (e) {
23851
- return errorResponse7(c, e);
24421
+ return errorResponse8(c, e);
23852
24422
  }
23853
24423
  });
23854
24424
  return app;
23855
24425
  }
23856
24426
 
23857
24427
  // ../server/src/features/project-config/routes.ts
23858
- function errorResponse8(c, e) {
24428
+ function errorResponse9(c, e) {
23859
24429
  const err = e;
23860
24430
  const status = err.status ?? 500;
23861
24431
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -23872,7 +24442,7 @@ function projectConfigRoutes(db, workspacePath) {
23872
24442
  try {
23873
24443
  return c.json(getProjectEnvironment(db, projectId));
23874
24444
  } catch (e) {
23875
- return errorResponse8(c, e);
24445
+ return errorResponse9(c, e);
23876
24446
  }
23877
24447
  });
23878
24448
  app.patch("/:projectId/environment", zValidator("json", saveWorkspaceConfigSchema), (c) => {
@@ -23883,7 +24453,7 @@ function projectConfigRoutes(db, workspacePath) {
23883
24453
  saveProjectEnvironment(db, workspacePath, projectId, c.req.valid("json").content)
23884
24454
  );
23885
24455
  } catch (e) {
23886
- return errorResponse8(c, e);
24456
+ return errorResponse9(c, e);
23887
24457
  }
23888
24458
  });
23889
24459
  app.post(
@@ -23906,7 +24476,7 @@ function projectConfigRoutes(db, workspacePath) {
23906
24476
  )
23907
24477
  );
23908
24478
  } catch (e) {
23909
- return errorResponse8(c, e);
24479
+ return errorResponse9(c, e);
23910
24480
  }
23911
24481
  }
23912
24482
  );
@@ -23916,7 +24486,7 @@ function projectConfigRoutes(db, workspacePath) {
23916
24486
  try {
23917
24487
  return c.json(getProjectDevtool(db, projectId));
23918
24488
  } catch (e) {
23919
- return errorResponse8(c, e);
24489
+ return errorResponse9(c, e);
23920
24490
  }
23921
24491
  });
23922
24492
  app.patch("/:projectId/devtool", zValidator("json", saveWorkspaceConfigSchema), (c) => {
@@ -23925,7 +24495,7 @@ function projectConfigRoutes(db, workspacePath) {
23925
24495
  try {
23926
24496
  return c.json(saveProjectDevtool(db, workspacePath, projectId, c.req.valid("json").content));
23927
24497
  } catch (e) {
23928
- return errorResponse8(c, e);
24498
+ return errorResponse9(c, e);
23929
24499
  }
23930
24500
  });
23931
24501
  app.post(
@@ -23942,7 +24512,7 @@ function projectConfigRoutes(db, workspacePath) {
23942
24512
  importProjectDevtool(db, workspacePath, projectId, c.req.valid("json").content, user.id)
23943
24513
  );
23944
24514
  } catch (e) {
23945
- return errorResponse8(c, e);
24515
+ return errorResponse9(c, e);
23946
24516
  }
23947
24517
  }
23948
24518
  );
@@ -23950,8 +24520,8 @@ function projectConfigRoutes(db, workspacePath) {
23950
24520
  }
23951
24521
 
23952
24522
  // ../server/src/features/project-import/scanner.ts
23953
- import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
23954
- import { join as join19 } from "path";
24523
+ import { existsSync as existsSync18, readFileSync as readFileSync13, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
24524
+ import { join as join20 } from "path";
23955
24525
 
23956
24526
  // ../server/src/features/project-import/changelog-md.ts
23957
24527
  var VERSION_HEADING = /^##\s+v?(\d+\.\d+\.\d+)\b/;
@@ -24099,64 +24669,64 @@ function svcError13(code, status) {
24099
24669
  return Object.assign(new Error(code), { code, status });
24100
24670
  }
24101
24671
  function readIfExists(path) {
24102
- return existsSync17(path) ? readFileSync12(path, "utf8") : null;
24672
+ return existsSync18(path) ? readFileSync13(path, "utf8") : null;
24103
24673
  }
24104
24674
  function emptyArtifactCounts() {
24105
24675
  return { erd: 0, data_model: 0, mockup: 0, technical_spec: 0, test_spec: 0, source_code: 0 };
24106
24676
  }
24107
24677
  function countArtifacts(appDir) {
24108
24678
  const counts = emptyArtifactCounts();
24109
- const contextDir = join19(appDir, "context");
24110
- const modelDir = join19(contextDir, "model");
24111
- if (existsSync17(modelDir)) {
24679
+ const contextDir = join20(appDir, "context");
24680
+ const modelDir = join20(contextDir, "model");
24681
+ if (existsSync18(modelDir)) {
24112
24682
  for (const slug of readdirSync3(modelDir)) {
24113
24683
  try {
24114
- if (!statSync5(join19(modelDir, slug)).isDirectory()) continue;
24684
+ if (!statSync5(join20(modelDir, slug)).isDirectory()) continue;
24115
24685
  } catch {
24116
24686
  continue;
24117
24687
  }
24118
- if (existsSync17(join19(modelDir, slug, "erd.mermaid")) || existsSync17(join19(modelDir, slug, "document-model.mermaid")))
24688
+ if (existsSync18(join20(modelDir, slug, "erd.mermaid")) || existsSync18(join20(modelDir, slug, "document-model.mermaid")))
24119
24689
  counts.erd++;
24120
- if (existsSync17(join19(modelDir, slug, "model.md"))) counts.data_model++;
24121
- if (existsSync17(join19(contextDir, "specification", slug, "SPEC.md"))) counts.technical_spec++;
24122
- if (existsSync17(join19(contextDir, "test", slug, "TEST_SPEC.md"))) counts.test_spec++;
24690
+ if (existsSync18(join20(modelDir, slug, "model.md"))) counts.data_model++;
24691
+ if (existsSync18(join20(contextDir, "specification", slug, "SPEC.md"))) counts.technical_spec++;
24692
+ if (existsSync18(join20(contextDir, "test", slug, "TEST_SPEC.md"))) counts.test_spec++;
24123
24693
  }
24124
24694
  }
24125
24695
  for (const [sub, key, file] of [
24126
24696
  ["specification", "technical_spec", "SPEC.md"],
24127
24697
  ["test", "test_spec", "TEST_SPEC.md"]
24128
24698
  ]) {
24129
- const dir = join19(contextDir, sub);
24130
- if (!existsSync17(dir)) continue;
24699
+ const dir = join20(contextDir, sub);
24700
+ if (!existsSync18(dir)) continue;
24131
24701
  let total = 0;
24132
24702
  for (const slug of readdirSync3(dir)) {
24133
24703
  try {
24134
- if (statSync5(join19(dir, slug)).isDirectory() && existsSync17(join19(dir, slug, file))) total++;
24704
+ if (statSync5(join20(dir, slug)).isDirectory() && existsSync18(join20(dir, slug, file))) total++;
24135
24705
  } catch {
24136
24706
  }
24137
24707
  }
24138
24708
  counts[key] = Math.max(counts[key], total);
24139
24709
  }
24140
- if (existsSync17(join19(contextDir, "mockup", "MOCKUP.html")) || existsSync17(join19(contextDir, "mockup", "index.html")))
24710
+ if (existsSync18(join20(contextDir, "mockup", "MOCKUP.html")) || existsSync18(join20(contextDir, "mockup", "index.html")))
24141
24711
  counts.mockup = 1;
24142
24712
  counts.source_code = sweepSourceFiles(appDir).length;
24143
24713
  return counts;
24144
24714
  }
24145
24715
  function findClaudeMd(folderDir) {
24146
- if (existsSync17(join19(folderDir, "CLAUDE.md"))) return { relPath: "CLAUDE.md", prefix: "" };
24147
- if (existsSync17(join19(folderDir, "source", "CLAUDE.md")))
24716
+ if (existsSync18(join20(folderDir, "CLAUDE.md"))) return { relPath: "CLAUDE.md", prefix: "" };
24717
+ if (existsSync18(join20(folderDir, "source", "CLAUDE.md")))
24148
24718
  return { relPath: "source/CLAUDE.md", prefix: "source" };
24149
24719
  return null;
24150
24720
  }
24151
24721
  function listImportCandidates(db, workspacePath) {
24152
- if (!existsSync17(workspacePath)) return [];
24722
+ if (!existsSync18(workspacePath)) return [];
24153
24723
  const registered = new Set(
24154
24724
  db.select({ folder: projects.workspaceFolderName }).from(projects).all().map((r) => r.folder)
24155
24725
  );
24156
24726
  const candidates = [];
24157
24727
  for (const entry of readdirSync3(workspacePath)) {
24158
24728
  if (entry.startsWith(".") || registered.has(entry)) continue;
24159
- const dir = join19(workspacePath, entry);
24729
+ const dir = join20(workspacePath, entry);
24160
24730
  try {
24161
24731
  if (!statSync5(dir).isDirectory()) continue;
24162
24732
  } catch {
@@ -24164,14 +24734,14 @@ function listImportCandidates(db, workspacePath) {
24164
24734
  }
24165
24735
  const found = findClaudeMd(dir);
24166
24736
  if (!found) continue;
24167
- const detail = parseProjectDetail(
24168
- readFileSync12(join19(workspacePath, entry, found.relPath), "utf8")
24737
+ const detail2 = parseProjectDetail(
24738
+ readFileSync13(join20(workspacePath, entry, found.relPath), "utf8")
24169
24739
  );
24170
24740
  candidates.push({
24171
24741
  folderName: entry,
24172
24742
  claudeMdPath: `${entry}/${found.relPath}`,
24173
- projectName: detail.projectName,
24174
- projectCode: detail.projectCode
24743
+ projectName: detail2.projectName,
24744
+ projectCode: detail2.projectCode
24175
24745
  });
24176
24746
  }
24177
24747
  return candidates.sort((a, b) => a.folderName.localeCompare(b.folderName));
@@ -24179,22 +24749,22 @@ function listImportCandidates(db, workspacePath) {
24179
24749
  function scanRepo(db, workspacePath, folderName) {
24180
24750
  const registered = db.select({ id: projects.id }).from(projects).where(eq(projects.workspaceFolderName, folderName)).limit(1).get();
24181
24751
  if (registered) throw svcError13("FOLDER_ALREADY_IMPORTED", 409);
24182
- const folderDir = join19(workspacePath, folderName);
24183
- const found = existsSync17(folderDir) ? findClaudeMd(folderDir) : null;
24752
+ const folderDir = join20(workspacePath, folderName);
24753
+ const found = existsSync18(folderDir) ? findClaudeMd(folderDir) : null;
24184
24754
  if (!found) throw svcError13("CLAUDE_MD_MISSING", 404);
24185
- const repoRoot = found.prefix ? join19(folderDir, found.prefix) : folderDir;
24186
- const claudeMdRaw = readFileSync12(join19(folderDir, found.relPath), "utf8");
24187
- const detail = parseProjectDetail(claudeMdRaw);
24755
+ const repoRoot = found.prefix ? join20(folderDir, found.prefix) : folderDir;
24756
+ const claudeMdRaw = readFileSync13(join20(folderDir, found.relPath), "utf8");
24757
+ const detail2 = parseProjectDetail(claudeMdRaw);
24188
24758
  const claudeApps = parseClaudeMdApplications(claudeMdRaw);
24189
24759
  const claudeModules = new Set(parseModuleNames(claudeMdRaw).map((m) => m.name));
24190
24760
  const warnings = [];
24191
- if (detail.projectName) {
24192
- const nameTaken = db.select({ id: projects.id }).from(projects).where(eq(projects.name, detail.projectName)).limit(1).get();
24761
+ if (detail2.projectName) {
24762
+ const nameTaken = db.select({ id: projects.id }).from(projects).where(eq(projects.name, detail2.projectName)).limit(1).get();
24193
24763
  if (nameTaken) {
24194
24764
  warnings.push({
24195
24765
  level: "warning",
24196
24766
  code: "DUPLICATE_PROJECT_NAME",
24197
- message: `A project named "${detail.projectName}" already exists \u2014 rename before importing.`
24767
+ message: `A project named "${detail2.projectName}" already exists \u2014 rename before importing.`
24198
24768
  });
24199
24769
  }
24200
24770
  }
@@ -24206,10 +24776,10 @@ function scanRepo(db, workspacePath, folderName) {
24206
24776
  for (const claude of claudeApps) {
24207
24777
  const name = toKebabCase(claude.heading);
24208
24778
  const resolvedInRoot = resolveAppFolderName(repoRoot, name);
24209
- const folderExists = existsSync17(join19(repoRoot, resolvedInRoot));
24779
+ const folderExists = existsSync18(join20(repoRoot, resolvedInRoot));
24210
24780
  const folderRel = folderExists ? found.prefix ? `${found.prefix}/${resolvedInRoot}` : resolvedInRoot : null;
24211
- const appDir = folderRel ? join19(folderDir, folderRel) : null;
24212
- const prdRaw = appDir ? readIfExists(join19(appDir, "context", "PRD.md")) : null;
24781
+ const appDir = folderRel ? join20(folderDir, folderRel) : null;
24782
+ const prdRaw = appDir ? readIfExists(join20(appDir, "context", "PRD.md")) : null;
24213
24783
  const prdModules = prdRaw ? parsePrdMarkdown(prdRaw) : [];
24214
24784
  const versions = /* @__PURE__ */ new Set();
24215
24785
  for (const m of prdModules) {
@@ -24220,19 +24790,19 @@ function scanRepo(db, workspacePath, folderName) {
24220
24790
  for (const v of versions) semverUnion.add(v);
24221
24791
  const derived = prdRaw ? deriveRequirementIdAppCode(prdRaw, name) : { code: appCodeFromInitials(name), derivedFromIds: false };
24222
24792
  const requirementIdAppCode = claude.requirementIdAppCode ?? derived.code;
24223
- const bugMdRaw = appDir ? readIfExists(join19(appDir, "context", "BUG.md")) : null;
24793
+ const bugMdRaw = appDir ? readIfExists(join20(appDir, "context", "BUG.md")) : null;
24224
24794
  const bugItems = bugMdRaw ? parseBugMd(bugMdRaw) : [];
24225
- const bugMasterRaw = appDir ? readIfExists(join19(appDir, "context", "bug", "BUG_MASTER.md")) : null;
24795
+ const bugMasterRaw = appDir ? readIfExists(join20(appDir, "context", "bug", "BUG_MASTER.md")) : null;
24226
24796
  const bugMaster = bugMasterRaw ? parseBugMaster(bugMasterRaw) : /* @__PURE__ */ new Map();
24227
24797
  for (const item of bugItems) if (item.semver) semverUnion.add(item.semver);
24228
24798
  for (const [, m] of bugMaster) if (m.semver) semverUnion.add(m.semver);
24229
- const preparePath = appDir ? join19(appDir, "context", "prepare", "PREPARE_MASTER.md") : "";
24230
- const developPath = appDir ? join19(appDir, "context", "develop", "IMPLEMENTATION_MASTER.md") : "";
24231
- const prepareMasterExists = !!appDir && existsSync17(preparePath);
24232
- const developMasterExists = !!appDir && existsSync17(developPath);
24799
+ const preparePath = appDir ? join20(appDir, "context", "prepare", "PREPARE_MASTER.md") : "";
24800
+ const developPath = appDir ? join20(appDir, "context", "develop", "IMPLEMENTATION_MASTER.md") : "";
24801
+ const prepareMasterExists = !!appDir && existsSync18(preparePath);
24802
+ const developMasterExists = !!appDir && existsSync18(developPath);
24233
24803
  const prepareCompleted = prepareMasterExists && parseTrackingStatus(preparePath) === "COMPLETED";
24234
24804
  const developCompleted = developMasterExists && parseTrackingStatus(developPath) === "COMPLETED";
24235
- const changelogRaw = appDir ? readIfExists(join19(appDir, "CHANGELOG.md")) : null;
24805
+ const changelogRaw = appDir ? readIfExists(join20(appDir, "CHANGELOG.md")) : null;
24236
24806
  const changelogEntries = changelogRaw ? parseChangelogConductorEntries(changelogRaw) : [];
24237
24807
  if (!folderRel) {
24238
24808
  warnings.push({
@@ -24314,21 +24884,28 @@ function scanRepo(db, workspacePath, folderName) {
24314
24884
  }
24315
24885
  const versionUnion = [...semverUnion].sort(compareSemver);
24316
24886
  const workflowPhase = anyBugs ? "qa" : anyDevelopCompleted ? "developed" : anyPrepareMaster ? "prepared" : "brainstorm";
24317
- const environmentMd = readIfExists(join19(repoRoot, "ENVIRONMENT.md")) ?? readIfExists(join19(folderDir, "ENVIRONMENT.md"));
24318
- const devtoolMd = readIfExists(join19(repoRoot, "DEVTOOL.md")) ?? readIfExists(join19(folderDir, "DEVTOOL.md"));
24887
+ const environmentMd = readIfExists(join20(repoRoot, "ENVIRONMENT.md")) ?? readIfExists(join20(folderDir, "ENVIRONMENT.md"));
24888
+ const devtoolMd = readIfExists(join20(repoRoot, "DEVTOOL.md")) ?? readIfExists(join20(folderDir, "DEVTOOL.md"));
24889
+ const origin = readOriginUrl(folderDir);
24890
+ const gitRemoteUrl = origin ? stripHttpsCredentials(origin) : origin;
24891
+ const defaultBranch = existsSync18(join20(folderDir, ".git")) ? detectTrunkBranch(folderDir, readCurrentBranch(folderDir)) : "main";
24892
+ const gitProvider = /gitlab/i.test(gitRemoteUrl ?? "") ? "gitlab" : "github";
24319
24893
  return {
24320
24894
  folderName,
24321
24895
  sourcePrefix: found.prefix,
24322
24896
  claudeMdRelPath: found.relPath,
24323
24897
  claudeMdRaw,
24324
- projectName: detail.projectName,
24325
- projectCode: detail.projectCode,
24898
+ projectName: detail2.projectName,
24899
+ projectCode: detail2.projectCode,
24326
24900
  apps,
24327
24901
  versionUnion,
24328
24902
  currentVersion: versionUnion.at(-1) ?? null,
24329
24903
  workflowPhase,
24330
24904
  environmentMd,
24331
24905
  devtoolMd,
24906
+ gitRemoteUrl,
24907
+ defaultBranch,
24908
+ gitProvider,
24332
24909
  warnings
24333
24910
  };
24334
24911
  }
@@ -24367,11 +24944,11 @@ function scanProject(db, workspacePath, folderName) {
24367
24944
  }
24368
24945
 
24369
24946
  // ../server/src/features/project-import/service.ts
24370
- import { join as join21, relative as relative2 } from "path";
24947
+ import { join as join22, relative as relative2 } from "path";
24371
24948
 
24372
24949
  // ../server/src/features/workflow/service.ts
24373
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
24374
- import { join as join20, relative, resolve as resolve2, sep } from "path";
24950
+ import { existsSync as existsSync19, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync11 } from "fs";
24951
+ import { join as join21, relative, resolve as resolve2, sep } from "path";
24375
24952
  var ACTIVE_RUN_STATUSES = ["pending", "in_progress"];
24376
24953
  function svcError14(code, status) {
24377
24954
  return Object.assign(new Error(code), { code, status });
@@ -24513,9 +25090,9 @@ function validateAllAppsPending(db, projectId, applicationIds, conductorType, pr
24513
25090
  const notPending = applicationIds.filter((id) => !pending.has(id));
24514
25091
  if (notPending.length > 0) throw svcError14("APPS_NOT_PENDING", 422);
24515
25092
  }
24516
- function resolveRunTrackingFilePath(projectDir, appName, conductorType) {
25093
+ function resolveRunTrackingFilePath(projectDir2, appName, conductorType) {
24517
25094
  const master = TRACKING_FILE_MAP[conductorType] ?? "";
24518
- return join20(projectDir, resolveAppFolderName(projectDir, appName), master);
25095
+ return join21(projectDir2, resolveAppFolderName(projectDir2, appName), master);
24519
25096
  }
24520
25097
  function buildWorkflowPath(username) {
24521
25098
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
@@ -24588,7 +25165,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24588
25165
  const ver = db.select({ semver: prdVersions.semver }).from(prdVersions).where(eq(prdVersions.id, prdVersionId)).get();
24589
25166
  const semver = ver?.semver ?? "latest";
24590
25167
  const relPath = buildWorkflowPath(creatingUser.username);
24591
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25168
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24592
25169
  const content = buildWorkflowMarkdown(
24593
25170
  input.conductorType,
24594
25171
  apps.map((a) => a.name),
@@ -24596,8 +25173,8 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24596
25173
  project.workspaceFolderName,
24597
25174
  relPath
24598
25175
  );
24599
- mkdirSync9(join20(projectDir, "workflow", creatingUser.username), { recursive: true });
24600
- writeFileSync10(join20(projectDir, relPath), content, "utf8");
25176
+ mkdirSync9(join21(projectDir2, "workflow", creatingUser.username), { recursive: true });
25177
+ writeFileSync11(join21(projectDir2, relPath), content, "utf8");
24601
25178
  const now = (/* @__PURE__ */ new Date()).toISOString();
24602
25179
  const wfm = db.insert(workflowMarkdowns).values({
24603
25180
  projectId: input.projectId,
@@ -24613,7 +25190,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24613
25190
  const runApps = [];
24614
25191
  for (const appId of input.applicationIds) {
24615
25192
  const appName = apps.find((a) => a.id === appId)?.name ?? "";
24616
- const trackingFilePath = resolveRunTrackingFilePath(projectDir, appName, input.conductorType);
25193
+ const trackingFilePath = resolveRunTrackingFilePath(projectDir2, appName, input.conductorType);
24617
25194
  const run = db.insert(conductorRuns).values({
24618
25195
  projectId: input.projectId,
24619
25196
  applicationId: appId,
@@ -24637,7 +25214,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24637
25214
  startWatcher(
24638
25215
  db,
24639
25216
  runId,
24640
- () => resolveRunTrackingFilePath(projectDir, appName, input.conductorType),
25217
+ () => resolveRunTrackingFilePath(projectDir2, appName, input.conductorType),
24641
25218
  creatingUser.id,
24642
25219
  watcherIntervalMs,
24643
25220
  (id, status) => {
@@ -24676,13 +25253,13 @@ function backfillWorkflowHistoryFromChangelogs(db, workspacePath, projectId) {
24676
25253
  if (!project) return 0;
24677
25254
  const apps = db.select({ id: applications.id, name: applications.name }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
24678
25255
  if (apps.length === 0) return 0;
24679
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25256
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24680
25257
  const collected = [];
24681
25258
  for (const app of apps) {
24682
- const appRel = resolveAppFolderName(projectDir, app.name);
24683
- const changelogPath = join20(projectDir, appRel, "CHANGELOG.md");
24684
- if (!existsSync18(changelogPath)) continue;
24685
- for (const entry of parseChangelogConductorEntries(readFileSync13(changelogPath, "utf8"))) {
25259
+ const appRel = resolveAppFolderName(projectDir2, app.name);
25260
+ const changelogPath = join21(projectDir2, appRel, "CHANGELOG.md");
25261
+ if (!existsSync19(changelogPath)) continue;
25262
+ for (const entry of parseChangelogConductorEntries(readFileSync14(changelogPath, "utf8"))) {
24686
25263
  collected.push({ appName: app.name, appRel, entry });
24687
25264
  }
24688
25265
  }
@@ -24728,7 +25305,7 @@ function getActiveRun(db, projectId, workspacePath) {
24728
25305
  function resumeWatchers(db, workspacePath, projectId, watcherIntervalMs) {
24729
25306
  const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
24730
25307
  if (!project) return;
24731
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25308
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24732
25309
  const stale = db.select({ run: conductorRuns, appName: applications.name }).from(conductorRuns).innerJoin(applications, eq(applications.id, conductorRuns.applicationId)).where(
24733
25310
  and(
24734
25311
  eq(conductorRuns.projectId, projectId),
@@ -24743,7 +25320,7 @@ function resumeWatchers(db, workspacePath, projectId, watcherIntervalMs) {
24743
25320
  db,
24744
25321
  run.id,
24745
25322
  // [BUG-027] Re-resolve per poll — heals rows persisted with a stale/project-level path.
24746
- () => resolveRunTrackingFilePath(projectDir, appName, conductorType),
25323
+ () => resolveRunTrackingFilePath(projectDir2, appName, conductorType),
24747
25324
  run.startedBy ?? 1,
24748
25325
  watcherIntervalMs,
24749
25326
  (id, status) => {
@@ -24778,7 +25355,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24778
25355
  if (!wfm) throw svcError14("RUN_NOT_FOUND", 404);
24779
25356
  const project = db.select().from(projects).where(eq(projects.id, wfm.projectId)).get();
24780
25357
  if (!project) throw svcError14("PROJECT_NOT_FOUND", 404);
24781
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25358
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24782
25359
  const runs = db.select({ run: conductorRuns, appName: applications.name }).from(conductorRuns).innerJoin(applications, eq(applications.id, conductorRuns.applicationId)).where(
24783
25360
  and(
24784
25361
  eq(conductorRuns.workflowMarkdownId, workflowMarkdownId),
@@ -24787,7 +25364,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24787
25364
  ).all();
24788
25365
  for (const { run, appName } of runs) {
24789
25366
  const conductorType = run.conductorType;
24790
- const freshPath = resolveRunTrackingFilePath(projectDir, appName, conductorType);
25367
+ const freshPath = resolveRunTrackingFilePath(projectDir2, appName, conductorType);
24791
25368
  if (freshPath !== run.trackingFilePath) {
24792
25369
  db.update(conductorRuns).set({ trackingFilePath: freshPath }).where(eq(conductorRuns.id, run.id)).run();
24793
25370
  }
@@ -24807,7 +25384,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24807
25384
  startWatcher(
24808
25385
  db,
24809
25386
  run.id,
24810
- () => resolveRunTrackingFilePath(projectDir, appName, conductorType),
25387
+ () => resolveRunTrackingFilePath(projectDir2, appName, conductorType),
24811
25388
  run.startedBy ?? 1,
24812
25389
  watcherIntervalMs,
24813
25390
  (id, terminalStatus) => {
@@ -24839,12 +25416,12 @@ function getWorkflowRunFileContent(db, workspacePath, workflowMarkdownId) {
24839
25416
  if (!wfm) throw svcError14("RUN_NOT_FOUND", 404);
24840
25417
  const project = db.select().from(projects).where(eq(projects.id, wfm.projectId)).get();
24841
25418
  if (!project) throw svcError14("PROJECT_NOT_FOUND", 404);
24842
- const projectDir = resolve2(workspacePath, project.workspaceFolderName);
24843
- const absolute = resolve2(projectDir, wfm.filePath);
24844
- if (absolute !== projectDir && !absolute.startsWith(projectDir + sep))
25419
+ const projectDir2 = resolve2(workspacePath, project.workspaceFolderName);
25420
+ const absolute = resolve2(projectDir2, wfm.filePath);
25421
+ if (absolute !== projectDir2 && !absolute.startsWith(projectDir2 + sep))
24845
25422
  throw svcError14("INVALID_PATH", 400);
24846
- if (!existsSync18(absolute)) throw svcError14("FILE_NOT_FOUND", 404);
24847
- return { filePath: wfm.filePath, content: readFileSync13(absolute, "utf-8") };
25423
+ if (!existsSync19(absolute)) throw svcError14("FILE_NOT_FOUND", 404);
25424
+ return { filePath: wfm.filePath, content: readFileSync14(absolute, "utf-8") };
24848
25425
  }
24849
25426
  function getRunApplicationChangelog(db, workspacePath, workflowMarkdownId, appName) {
24850
25427
  const wfm = db.select().from(workflowMarkdowns).where(eq(workflowMarkdowns.id, workflowMarkdownId)).get();
@@ -24859,16 +25436,16 @@ function getRunApplicationChangelog(db, workspacePath, workflowMarkdownId, appNa
24859
25436
  runAppNames = appIds.length > 0 ? db.select({ name: applications.name }).from(applications).where(inArray(applications.id, appIds)).all().map((a) => a.name) : [];
24860
25437
  }
24861
25438
  if (!runAppNames.includes(appName)) throw svcError14("APP_NOT_IN_RUN", 404);
24862
- const projectDir = resolve2(workspacePath, project.workspaceFolderName);
24863
- const appFolder = resolveAppFolderName(projectDir, appName);
24864
- const absolute = resolve2(projectDir, appFolder, "CHANGELOG.md");
24865
- if (absolute !== projectDir && !absolute.startsWith(projectDir + sep))
25439
+ const projectDir2 = resolve2(workspacePath, project.workspaceFolderName);
25440
+ const appFolder = resolveAppFolderName(projectDir2, appName);
25441
+ const absolute = resolve2(projectDir2, appFolder, "CHANGELOG.md");
25442
+ if (absolute !== projectDir2 && !absolute.startsWith(projectDir2 + sep))
24866
25443
  throw svcError14("INVALID_PATH", 400);
24867
- if (!existsSync18(absolute)) throw svcError14("CHANGELOG_NOT_FOUND", 404);
25444
+ if (!existsSync19(absolute)) throw svcError14("CHANGELOG_NOT_FOUND", 404);
24868
25445
  return {
24869
25446
  applicationName: appName,
24870
25447
  filePath: relative(workspacePath, absolute).replace(/\\/g, "/"),
24871
- content: readFileSync13(absolute, "utf-8")
25448
+ content: readFileSync14(absolute, "utf-8")
24872
25449
  };
24873
25450
  }
24874
25451
  function toDetail(db, projectFolder, wfm, workspacePath) {
@@ -24903,20 +25480,20 @@ function toDetail(db, projectFolder, wfm, workspacePath) {
24903
25480
  const appNames = appRows.map((a) => a.name);
24904
25481
  let appBreakdown;
24905
25482
  if (workspacePath) {
24906
- const projectDir = join20(workspacePath, projectFolder);
25483
+ const projectDir2 = join21(workspacePath, projectFolder);
24907
25484
  appBreakdown = runs.map((r) => {
24908
25485
  const appName = nameOf.get(r.applicationId) ?? `app #${r.applicationId}`;
24909
- const trackingPath = resolveRunTrackingFilePath(projectDir, appName, r.conductorType);
25486
+ const trackingPath = resolveRunTrackingFilePath(projectDir2, appName, r.conductorType);
24910
25487
  return {
24911
25488
  applicationName: appName,
24912
25489
  status: r.status,
24913
- trackingFileExists: existsSync18(trackingPath),
25490
+ trackingFileExists: existsSync19(trackingPath),
24914
25491
  trackingFilePath: relative(workspacePath, trackingPath).replace(/\\/g, "/")
24915
25492
  };
24916
25493
  });
24917
25494
  }
24918
25495
  const completedAt = runs.every((r) => r.completedAt) ? runs.map((r) => r.completedAt).sort().at(-1) ?? null : null;
24919
- const git3 = runs.find((r) => r.gitOperationStatus && r.gitOperationStatus !== "not_started");
25496
+ const git4 = runs.find((r) => r.gitOperationStatus && r.gitOperationStatus !== "not_started");
24920
25497
  return {
24921
25498
  id: wfm.id,
24922
25499
  conductorType: wfm.conductorType,
@@ -24926,9 +25503,9 @@ function toDetail(db, projectFolder, wfm, workspacePath) {
24926
25503
  filePath: wfm.filePath,
24927
25504
  executeCommand: buildExecuteCommand(projectFolder, wfm.filePath),
24928
25505
  versionLabel: wfm.versionLabel,
24929
- gitOperationStatus: git3?.gitOperationStatus ?? "not_started",
24930
- gitBranchCreated: git3?.gitBranchCreated ?? null,
24931
- gitCommitHash: git3?.gitCommitHash ?? null,
25506
+ gitOperationStatus: git4?.gitOperationStatus ?? "not_started",
25507
+ gitBranchCreated: git4?.gitBranchCreated ?? null,
25508
+ gitCommitHash: git4?.gitCommitHash ?? null,
24932
25509
  completedAt,
24933
25510
  createdAt: wfm.createdAt
24934
25511
  };
@@ -24949,7 +25526,7 @@ function importProject(db, workspacePath, input, userId) {
24949
25526
  const nameTaken = db.select({ id: projects.id }).from(projects).where(eq(projects.name, name)).limit(1).get();
24950
25527
  if (nameTaken) throw svcError15("PROJECT_EXISTS", 409);
24951
25528
  const items = [];
24952
- const report = (entity, action, label, detail) => items.push({ entity, action, label, ...detail ? { detail } : {} });
25529
+ const report = (entity, action, label, detail2) => items.push({ entity, action, label, ...detail2 ? { detail: detail2 } : {} });
24953
25530
  const projectId = db.transaction((rawTx) => {
24954
25531
  const tx = rawTx;
24955
25532
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -24962,6 +25539,9 @@ function importProject(db, workspacePath, input, userId) {
24962
25539
  // bypasses the lazy brainstorm-rescan path
24963
25540
  workflowPhase: scan.workflowPhase,
24964
25541
  rawClaudeMd: scan.claudeMdRaw,
25542
+ gitRemoteUrl: scan.gitRemoteUrl ?? null,
25543
+ defaultBranch: scan.defaultBranch,
25544
+ gitProvider: scan.gitProvider,
24965
25545
  importedAt: now,
24966
25546
  importedBy: userId,
24967
25547
  createdBy: userId,
@@ -25045,7 +25625,7 @@ function importProject(db, workspacePath, input, userId) {
25045
25625
  snapshotPrdContent: JSON.stringify(prdSnapshot),
25046
25626
  snapshotBugContent: JSON.stringify(bugSnapshot)
25047
25627
  }).where(eq(prdVersions.id, current.id)).run();
25048
- const projectDir = join21(workspacePath, input.folderName);
25628
+ const projectDir2 = join22(workspacePath, input.folderName);
25049
25629
  for (const app of scan.apps) {
25050
25630
  const appId = appIdByName.get(app.name);
25051
25631
  const hasContextArtifacts = app.artifactCounts.erd + app.artifactCounts.data_model + app.artifactCounts.mockup + app.artifactCounts.technical_spec + app.artifactCounts.test_spec > 0;
@@ -25062,8 +25642,8 @@ function importProject(db, workspacePath, input, userId) {
25062
25642
  const trackingPathFor = (type) => toPosix(
25063
25643
  relative2(
25064
25644
  workspacePath,
25065
- join21(
25066
- projectDir,
25645
+ join22(
25646
+ projectDir2,
25067
25647
  app.folderRel,
25068
25648
  type === "prepare" ? "context/prepare/PREPARE_MASTER.md" : type === "develop" ? "context/develop/IMPLEMENTATION_MASTER.md" : "context/bug/BUG_MASTER.md"
25069
25649
  )
@@ -25288,7 +25868,7 @@ ${item.body}`.match(REQUIREMENT_ID)?.[0] ?? null;
25288
25868
  }
25289
25869
 
25290
25870
  // ../server/src/features/project-import/routes.ts
25291
- function errorResponse9(c, e) {
25871
+ function errorResponse10(c, e) {
25292
25872
  const err = e;
25293
25873
  const status = err.status ?? 500;
25294
25874
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25299,14 +25879,14 @@ function projectImportRoutes(db, workspacePath) {
25299
25879
  try {
25300
25880
  return c.json(listImportCandidates(db, workspacePath));
25301
25881
  } catch (e) {
25302
- return errorResponse9(c, e);
25882
+ return errorResponse10(c, e);
25303
25883
  }
25304
25884
  });
25305
25885
  app.post("/scan", requireRole("admin"), zValidator("json", scanImportRequestSchema), (c) => {
25306
25886
  try {
25307
25887
  return c.json(scanProject(db, workspacePath, c.req.valid("json").folderName));
25308
25888
  } catch (e) {
25309
- return errorResponse9(c, e);
25889
+ return errorResponse10(c, e);
25310
25890
  }
25311
25891
  });
25312
25892
  app.post("/", requireRole("admin"), zValidator("json", runImportRequestSchema), (c) => {
@@ -25314,14 +25894,14 @@ function projectImportRoutes(db, workspacePath) {
25314
25894
  try {
25315
25895
  return c.json(importProject(db, workspacePath, c.req.valid("json"), user.id), 201);
25316
25896
  } catch (e) {
25317
- return errorResponse9(c, e);
25897
+ return errorResponse10(c, e);
25318
25898
  }
25319
25899
  });
25320
25900
  return app;
25321
25901
  }
25322
25902
 
25323
25903
  // ../server/src/features/project/routes.ts
25324
- function errorResponse10(c, e) {
25904
+ function errorResponse11(c, e) {
25325
25905
  const err = e;
25326
25906
  const status = err.status ?? 500;
25327
25907
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25339,7 +25919,7 @@ function projectRoutes(db, workspacePath) {
25339
25919
  const id = createProject(db, workspacePath, name, code, baseIdea, user.id);
25340
25920
  return c.json({ id }, 201);
25341
25921
  } catch (e) {
25342
- return errorResponse10(c, e);
25922
+ return errorResponse11(c, e);
25343
25923
  }
25344
25924
  });
25345
25925
  app.get("/:id/detail", (c) => {
@@ -25348,7 +25928,7 @@ function projectRoutes(db, workspacePath) {
25348
25928
  try {
25349
25929
  return c.json(getProjectDetailSummary(db, id));
25350
25930
  } catch (e) {
25351
- return errorResponse10(c, e);
25931
+ return errorResponse11(c, e);
25352
25932
  }
25353
25933
  });
25354
25934
  app.post("/:id/scaffold", requireRole("admin"), (c) => {
@@ -25358,7 +25938,7 @@ function projectRoutes(db, workspacePath) {
25358
25938
  scaffoldProject(db, workspacePath, id);
25359
25939
  return c.json({ ok: true });
25360
25940
  } catch (e) {
25361
- return errorResponse10(c, e);
25941
+ return errorResponse11(c, e);
25362
25942
  }
25363
25943
  });
25364
25944
  app.get("/:id/brainstorm-md", (c) => {
@@ -25367,7 +25947,7 @@ function projectRoutes(db, workspacePath) {
25367
25947
  try {
25368
25948
  return c.json(getBrainstormMd(db, workspacePath, id));
25369
25949
  } catch (e) {
25370
- return errorResponse10(c, e);
25950
+ return errorResponse11(c, e);
25371
25951
  }
25372
25952
  });
25373
25953
  app.patch("/:id/brainstorm-md", zValidator("json", updateBrainstormMdSchema), (c) => {
@@ -25377,7 +25957,7 @@ function projectRoutes(db, workspacePath) {
25377
25957
  updateBrainstormMd(db, workspacePath, id, c.req.valid("json").content);
25378
25958
  return c.json({ ok: true });
25379
25959
  } catch (e) {
25380
- return errorResponse10(c, e);
25960
+ return errorResponse11(c, e);
25381
25961
  }
25382
25962
  });
25383
25963
  app.get("/:id", (c) => {
@@ -25386,7 +25966,7 @@ function projectRoutes(db, workspacePath) {
25386
25966
  try {
25387
25967
  return c.json(getProjectDetail(db, workspacePath, id));
25388
25968
  } catch (e) {
25389
- return errorResponse10(c, e);
25969
+ return errorResponse11(c, e);
25390
25970
  }
25391
25971
  });
25392
25972
  app.post("/:id/regenerate", (c) => {
@@ -25396,7 +25976,7 @@ function projectRoutes(db, workspacePath) {
25396
25976
  regenerateCO2Files(db, workspacePath, id);
25397
25977
  return c.json({ ok: true });
25398
25978
  } catch (e) {
25399
- return errorResponse10(c, e);
25979
+ return errorResponse11(c, e);
25400
25980
  }
25401
25981
  });
25402
25982
  app.post("/:id/rescan-claude-md", (c) => {
@@ -25406,7 +25986,7 @@ function projectRoutes(db, workspacePath) {
25406
25986
  const imported = rescanClaudeMd(db, workspacePath, id);
25407
25987
  return c.json({ ok: true, imported });
25408
25988
  } catch (e) {
25409
- return errorResponse10(c, e);
25989
+ return errorResponse11(c, e);
25410
25990
  }
25411
25991
  });
25412
25992
  app.patch("/:id/archive", requireRole("admin"), (c) => {
@@ -25417,15 +25997,29 @@ function projectRoutes(db, workspacePath) {
25417
25997
  archiveProject(db, id, user.id);
25418
25998
  return c.json({ ok: true });
25419
25999
  } catch (e) {
25420
- return errorResponse10(c, e);
26000
+ return errorResponse11(c, e);
26001
+ }
26002
+ });
26003
+ app.patch("/:id", zValidator("json", updateProjectGitRemoteSchema), (c) => {
26004
+ const id = Number(c.req.param("id"));
26005
+ if (Number.isNaN(id)) return c.json({ code: "INVALID_ID" }, 400);
26006
+ try {
26007
+ const body = c.req.valid("json");
26008
+ updateProjectGitRemote(db, id, body.gitRemoteUrl, {
26009
+ defaultBranch: body.defaultBranch,
26010
+ gitProvider: body.gitProvider
26011
+ });
26012
+ return c.json({ ok: true });
26013
+ } catch (e) {
26014
+ return errorResponse11(c, e);
25421
26015
  }
25422
26016
  });
25423
26017
  return app;
25424
26018
  }
25425
26019
 
25426
26020
  // ../server/src/features/shared-context/service.ts
25427
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync14, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync11 } from "fs";
25428
- import { join as join22, resolve as resolve3, sep as sep2 } from "path";
26021
+ import { existsSync as existsSync20, mkdirSync as mkdirSync10, readFileSync as readFileSync15, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync12 } from "fs";
26022
+ import { join as join23, resolve as resolve3, sep as sep2 } from "path";
25429
26023
  function svcError16(code, status) {
25430
26024
  return Object.assign(new Error(code), { code, status });
25431
26025
  }
@@ -25451,7 +26045,7 @@ function mimeForName(name) {
25451
26045
  function resolveSharedRoot(db, workspacePath, projectId) {
25452
26046
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
25453
26047
  if (!project) throw svcError16("NOT_FOUND", 404);
25454
- const root = join22(workspacePath, project.workspaceFolderName, "shared_context");
26048
+ const root = join23(workspacePath, project.workspaceFolderName, "shared_context");
25455
26049
  mkdirSync10(root, { recursive: true });
25456
26050
  return root;
25457
26051
  }
@@ -25465,9 +26059,9 @@ function safeResolve(root, relPath) {
25465
26059
  return full;
25466
26060
  }
25467
26061
  function sanitizeSegment(name) {
25468
- const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
25469
- if (!safe || safe === "." || safe === "..") throw svcError16("INVALID_NAME", 400);
25470
- return safe;
26062
+ const safe2 = name.replace(/[^a-zA-Z0-9._-]/g, "_");
26063
+ if (!safe2 || safe2 === "." || safe2 === "..") throw svcError16("INVALID_NAME", 400);
26064
+ return safe2;
25471
26065
  }
25472
26066
  function walk(dir, relBase) {
25473
26067
  const entries = readdirSync4(dir, { withFileTypes: true });
@@ -25479,10 +26073,10 @@ function walk(dir, relBase) {
25479
26073
  name: entry.name,
25480
26074
  path: rel,
25481
26075
  type: "folder",
25482
- children: walk(join22(dir, entry.name), rel)
26076
+ children: walk(join23(dir, entry.name), rel)
25483
26077
  });
25484
26078
  } else if (entry.isFile()) {
25485
- const stat = statSync6(join22(dir, entry.name));
26079
+ const stat = statSync6(join23(dir, entry.name));
25486
26080
  nodes.push({
25487
26081
  name: entry.name,
25488
26082
  path: rel,
@@ -25513,28 +26107,28 @@ function uploadFile(db, workspacePath, projectId, folder, file) {
25513
26107
  if (file.bytes.length > MAX_DOCUMENT_BYTES) throw svcError16("FILE_TOO_LARGE", 413);
25514
26108
  const root = resolveSharedRoot(db, workspacePath, projectId);
25515
26109
  const dir = folder ? safeResolve(root, folder) : root;
25516
- if (folder && !existsSync19(dir)) throw svcError16("FOLDER_NOT_FOUND", 404);
26110
+ if (folder && !existsSync20(dir)) throw svcError16("FOLDER_NOT_FOUND", 404);
25517
26111
  const safeName = sanitizeSegment(file.name);
25518
- writeFileSync11(join22(dir, safeName), file.bytes);
26112
+ writeFileSync12(join23(dir, safeName), file.bytes);
25519
26113
  return { path: folder ? `${folder}/${safeName}` : safeName };
25520
26114
  }
25521
26115
  function getFileContent(db, workspacePath, projectId, relPath) {
25522
26116
  const root = resolveSharedRoot(db, workspacePath, projectId);
25523
26117
  const full = safeResolve(root, relPath);
25524
- if (!existsSync19(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26118
+ if (!existsSync20(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
25525
26119
  const name = relPath.split("/").pop() ?? relPath;
25526
- return { mimeType: mimeForName(name), bytes: readFileSync14(full), name };
26120
+ return { mimeType: mimeForName(name), bytes: readFileSync15(full), name };
25527
26121
  }
25528
26122
  function saveMarkdown(db, workspacePath, projectId, relPath, content) {
25529
26123
  if (mimeForName(relPath) !== "text/markdown") throw svcError16("NOT_MARKDOWN", 400);
25530
26124
  const root = resolveSharedRoot(db, workspacePath, projectId);
25531
26125
  const full = safeResolve(root, relPath);
25532
- if (!existsSync19(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
25533
- writeFileSync11(full, content, "utf8");
26126
+ if (!existsSync20(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26127
+ writeFileSync12(full, content, "utf8");
25534
26128
  }
25535
26129
 
25536
26130
  // ../server/src/features/shared-context/routes.ts
25537
- function errorResponse11(c, e) {
26131
+ function errorResponse12(c, e) {
25538
26132
  const err = e;
25539
26133
  const status = err.status ?? 500;
25540
26134
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25550,7 +26144,7 @@ function sharedContextRoutes(db, workspacePath) {
25550
26144
  try {
25551
26145
  return c.json({ tree: listTree(db, workspacePath, projectId) });
25552
26146
  } catch (e) {
25553
- return errorResponse11(c, e);
26147
+ return errorResponse12(c, e);
25554
26148
  }
25555
26149
  });
25556
26150
  app.post("/:projectId/folders", async (c) => {
@@ -25561,7 +26155,7 @@ function sharedContextRoutes(db, workspacePath) {
25561
26155
  try {
25562
26156
  return c.json({ path: createFolder(db, workspacePath, projectId, body.path) });
25563
26157
  } catch (e) {
25564
- return errorResponse11(c, e);
26158
+ return errorResponse12(c, e);
25565
26159
  }
25566
26160
  });
25567
26161
  app.post("/:projectId/upload", async (c) => {
@@ -25576,7 +26170,7 @@ function sharedContextRoutes(db, workspacePath) {
25576
26170
  const { path } = uploadFile(db, workspacePath, projectId, folder, { name: file.name, bytes });
25577
26171
  return c.json({ path });
25578
26172
  } catch (e) {
25579
- return errorResponse11(c, e);
26173
+ return errorResponse12(c, e);
25580
26174
  }
25581
26175
  });
25582
26176
  app.get("/:projectId/content", (c) => {
@@ -25595,7 +26189,7 @@ function sharedContextRoutes(db, workspacePath) {
25595
26189
  }
25596
26190
  });
25597
26191
  } catch (e) {
25598
- return errorResponse11(c, e);
26192
+ return errorResponse12(c, e);
25599
26193
  }
25600
26194
  });
25601
26195
  app.put("/:projectId/content", async (c) => {
@@ -25609,7 +26203,7 @@ function sharedContextRoutes(db, workspacePath) {
25609
26203
  saveMarkdown(db, workspacePath, projectId, body.path, body.content);
25610
26204
  return c.json({ ok: true });
25611
26205
  } catch (e) {
25612
- return errorResponse11(c, e);
26206
+ return errorResponse12(c, e);
25613
26207
  }
25614
26208
  });
25615
26209
  return app;
@@ -25674,8 +26268,8 @@ function svcError17(code, status) {
25674
26268
  return Object.assign(new Error(code), { code, status });
25675
26269
  }
25676
26270
  function toSafe(user) {
25677
- const { passwordHash: _omit, ...safe } = user;
25678
- return safe;
26271
+ const { passwordHash: _omit, ...safe2 } = user;
26272
+ return safe2;
25679
26273
  }
25680
26274
  function loginUser(db, input, clientIp) {
25681
26275
  const rateLimitKey = `${input.username}:${clientIp}`;
@@ -25752,8 +26346,8 @@ function getUserDetail(db, id) {
25752
26346
  const admin = db.select({ displayName: users.displayName }).from(users).where(eq(users.id, user.reactivatedBy)).limit(1).get();
25753
26347
  reactivatedByName = admin?.displayName ?? null;
25754
26348
  }
25755
- const { passwordHash: _omit, ...safe } = user;
25756
- return { ...safe, reactivatedByName };
26349
+ const { passwordHash: _omit, ...safe2 } = user;
26350
+ return { ...safe2, reactivatedByName };
25757
26351
  }
25758
26352
  function adminUpdateUser(db, targetId, input) {
25759
26353
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -25814,7 +26408,7 @@ function listAssignments(db, userId) {
25814
26408
 
25815
26409
  // ../server/src/features/user/auth-routes.ts
25816
26410
  var SESSION_MAX_AGE = 30 * 24 * 60 * 60;
25817
- function errorResponse12(c, e) {
26411
+ function errorResponse13(c, e) {
25818
26412
  const err = e;
25819
26413
  const status = err.status ?? 500;
25820
26414
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25835,7 +26429,7 @@ function authRoutes(db) {
25835
26429
  });
25836
26430
  return c.json({ mustChangePassword });
25837
26431
  } catch (e) {
25838
- return errorResponse12(c, e);
26432
+ return errorResponse13(c, e);
25839
26433
  }
25840
26434
  });
25841
26435
  app.post("/logout", (c) => {
@@ -25859,14 +26453,14 @@ function authRoutes(db) {
25859
26453
  });
25860
26454
  return c.json({ ok: true });
25861
26455
  } catch (e) {
25862
- return errorResponse12(c, e);
26456
+ return errorResponse13(c, e);
25863
26457
  }
25864
26458
  });
25865
26459
  app.get("/me", (c) => {
25866
26460
  const user = c.get("user");
25867
26461
  if (!user) return c.json({ code: "UNAUTHORIZED" }, 401);
25868
- const { passwordHash: _omit, ...safe } = user;
25869
- return c.json(safe);
26462
+ const { passwordHash: _omit, ...safe2 } = user;
26463
+ return c.json(safe2);
25870
26464
  });
25871
26465
  app.patch("/me", zValidator("json", updateProfileSchema), (c) => {
25872
26466
  const user = c.get("user");
@@ -25878,7 +26472,7 @@ function authRoutes(db) {
25878
26472
  }
25879
26473
 
25880
26474
  // ../server/src/features/user/routes.ts
25881
- function errorResponse13(c, e) {
26475
+ function errorResponse14(c, e) {
25882
26476
  const err = e;
25883
26477
  const status = err.status ?? 500;
25884
26478
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25892,7 +26486,7 @@ function userRoutes(db) {
25892
26486
  const id = createUser(db, c.req.valid("json"));
25893
26487
  return c.json({ id }, 201);
25894
26488
  } catch (e) {
25895
- return errorResponse13(c, e);
26489
+ return errorResponse14(c, e);
25896
26490
  }
25897
26491
  });
25898
26492
  app.get("/:id", requireRole("admin"), (c) => {
@@ -25916,7 +26510,7 @@ function userRoutes(db) {
25916
26510
  deactivateUser(db, id, admin.id);
25917
26511
  return c.json({ ok: true });
25918
26512
  } catch (e) {
25919
- return errorResponse13(c, e);
26513
+ return errorResponse14(c, e);
25920
26514
  }
25921
26515
  });
25922
26516
  app.patch(
@@ -25938,7 +26532,7 @@ function userRoutes(db) {
25938
26532
  reactivateUser(db, id, admin.id);
25939
26533
  return c.json({ ok: true });
25940
26534
  } catch (e) {
25941
- return errorResponse13(c, e);
26535
+ return errorResponse14(c, e);
25942
26536
  }
25943
26537
  });
25944
26538
  app.get("/:id/project-assignments", requireRole("admin"), (c) => {
@@ -25958,7 +26552,7 @@ function userRoutes(db) {
25958
26552
  const assignmentId = assignProject(db, id, c.req.valid("json").projectId, admin.id);
25959
26553
  return c.json({ id: assignmentId }, 201);
25960
26554
  } catch (e) {
25961
- return errorResponse13(c, e);
26555
+ return errorResponse14(c, e);
25962
26556
  }
25963
26557
  }
25964
26558
  );
@@ -25973,7 +26567,7 @@ function userRoutes(db) {
25973
26567
  }
25974
26568
 
25975
26569
  // ../server/src/features/version/routes.ts
25976
- function errorResponse14(c, e) {
26570
+ function errorResponse15(c, e) {
25977
26571
  const err = e;
25978
26572
  const status = err.status ?? 500;
25979
26573
  const message = e instanceof Error && err.code ? e.message : void 0;
@@ -25988,7 +26582,7 @@ function versionRoutes(db, workspacePath) {
25988
26582
  try {
25989
26583
  return c.json(listVersions(db, projectId, user.id));
25990
26584
  } catch (e) {
25991
- return errorResponse14(c, e);
26585
+ return errorResponse15(c, e);
25992
26586
  }
25993
26587
  });
25994
26588
  app.get("/:projectId/current", (c) => {
@@ -25998,7 +26592,7 @@ function versionRoutes(db, workspacePath) {
25998
26592
  try {
25999
26593
  return c.json(getCurrentVersion(db, projectId, user.id));
26000
26594
  } catch (e) {
26001
- return errorResponse14(c, e);
26595
+ return errorResponse15(c, e);
26002
26596
  }
26003
26597
  });
26004
26598
  app.get("/:projectId/can-add", (c) => {
@@ -26014,14 +26608,14 @@ function versionRoutes(db, workspacePath) {
26014
26608
  const result = addVersion(db, projectId, c.req.valid("json").versionType, user.id, workspacePath);
26015
26609
  return c.json(result);
26016
26610
  } catch (e) {
26017
- return errorResponse14(c, e);
26611
+ return errorResponse15(c, e);
26018
26612
  }
26019
26613
  });
26020
26614
  return app;
26021
26615
  }
26022
26616
 
26023
26617
  // ../server/src/features/workflow/routes.ts
26024
- function errorResponse15(c, e) {
26618
+ function errorResponse16(c, e) {
26025
26619
  const err = e;
26026
26620
  const status = err.status ?? 500;
26027
26621
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -26033,7 +26627,7 @@ function workflowRoutes(db, workspacePath) {
26033
26627
  try {
26034
26628
  return c.json(getPendingApps(db, projectId, conductorType, versionId));
26035
26629
  } catch (e) {
26036
- return errorResponse15(c, e);
26630
+ return errorResponse16(c, e);
26037
26631
  }
26038
26632
  });
26039
26633
  app.get("/phase-completion", zValidator("query", pendingAppsQuerySchema), (c) => {
@@ -26041,7 +26635,7 @@ function workflowRoutes(db, workspacePath) {
26041
26635
  try {
26042
26636
  return c.json(getPhaseCompletion(db, projectId, conductorType, versionId));
26043
26637
  } catch (e) {
26044
- return errorResponse15(c, e);
26638
+ return errorResponse16(c, e);
26045
26639
  }
26046
26640
  });
26047
26641
  app.get("/runs", (c) => {
@@ -26052,7 +26646,7 @@ function workflowRoutes(db, workspacePath) {
26052
26646
  resumeWatchers(db, workspacePath, projectId);
26053
26647
  return c.json(listWorkflowRuns(db, projectId));
26054
26648
  } catch (e) {
26055
- return errorResponse15(c, e);
26649
+ return errorResponse16(c, e);
26056
26650
  }
26057
26651
  });
26058
26652
  app.get("/active-run", (c) => {
@@ -26062,7 +26656,7 @@ function workflowRoutes(db, workspacePath) {
26062
26656
  resumeWatchers(db, workspacePath, projectId);
26063
26657
  return c.json(getActiveRun(db, projectId, workspacePath));
26064
26658
  } catch (e) {
26065
- return errorResponse15(c, e);
26659
+ return errorResponse16(c, e);
26066
26660
  }
26067
26661
  });
26068
26662
  app.get("/runs/:id", (c) => {
@@ -26071,7 +26665,7 @@ function workflowRoutes(db, workspacePath) {
26071
26665
  try {
26072
26666
  return c.json(getWorkflowRun(db, id, workspacePath));
26073
26667
  } catch (e) {
26074
- return errorResponse15(c, e);
26668
+ return errorResponse16(c, e);
26075
26669
  }
26076
26670
  });
26077
26671
  app.get("/runs/:id/file-content", (c) => {
@@ -26080,7 +26674,7 @@ function workflowRoutes(db, workspacePath) {
26080
26674
  try {
26081
26675
  return c.json(getWorkflowRunFileContent(db, workspacePath, id));
26082
26676
  } catch (e) {
26083
- return errorResponse15(c, e);
26677
+ return errorResponse16(c, e);
26084
26678
  }
26085
26679
  });
26086
26680
  app.get("/runs/:id/changelog", (c) => {
@@ -26091,7 +26685,7 @@ function workflowRoutes(db, workspacePath) {
26091
26685
  try {
26092
26686
  return c.json(getRunApplicationChangelog(db, workspacePath, id, appName));
26093
26687
  } catch (e) {
26094
- return errorResponse15(c, e);
26688
+ return errorResponse16(c, e);
26095
26689
  }
26096
26690
  });
26097
26691
  app.post("/runs/:id/force-stop", (c) => {
@@ -26101,7 +26695,7 @@ function workflowRoutes(db, workspacePath) {
26101
26695
  forceStopWorkflowRun(db, id);
26102
26696
  return c.json({ ok: true });
26103
26697
  } catch (e) {
26104
- return errorResponse15(c, e);
26698
+ return errorResponse16(c, e);
26105
26699
  }
26106
26700
  });
26107
26701
  app.post("/runs/:id/rescan", (c) => {
@@ -26110,7 +26704,7 @@ function workflowRoutes(db, workspacePath) {
26110
26704
  try {
26111
26705
  return c.json(rescanWorkflowRun(db, workspacePath, id));
26112
26706
  } catch (e) {
26113
- return errorResponse15(c, e);
26707
+ return errorResponse16(c, e);
26114
26708
  }
26115
26709
  });
26116
26710
  app.post("/runs", zValidator("json", createWorkflowSchema), (c) => {
@@ -26122,7 +26716,7 @@ function workflowRoutes(db, workspacePath) {
26122
26716
  });
26123
26717
  return c.json(result, 201);
26124
26718
  } catch (e) {
26125
- return errorResponse15(c, e);
26719
+ return errorResponse16(c, e);
26126
26720
  }
26127
26721
  });
26128
26722
  return app;
@@ -26262,11 +26856,11 @@ function closeTerminalSessions(projectId, reason) {
26262
26856
  }
26263
26857
  function buildPtyLaunch(workspacePath, folderName) {
26264
26858
  const isWindows = process.platform === "win32";
26265
- const join29 = (a, b) => `${a.replace(/[\\/]+$/, "")}/${b}`;
26859
+ const join30 = (a, b) => `${a.replace(/[\\/]+$/, "")}/${b}`;
26266
26860
  return {
26267
26861
  shell: isWindows ? "powershell.exe" : process.env["SHELL"] ?? "bash",
26268
26862
  args: [],
26269
- cwd: join29(workspacePath, folderName),
26863
+ cwd: join30(workspacePath, folderName),
26270
26864
  autoCommand: `${CLAUDE_COMMAND}\r`
26271
26865
  };
26272
26866
  }
@@ -26443,16 +27037,16 @@ function terminalRoutes(db) {
26443
27037
  import { execSync } from "child_process";
26444
27038
 
26445
27039
  // ../server/src/lib/claude-plugin.ts
26446
- import { existsSync as existsSync20, readFileSync as readFileSync15, readdirSync as readdirSync5 } from "fs";
27040
+ import { existsSync as existsSync21, readFileSync as readFileSync16, readdirSync as readdirSync5 } from "fs";
26447
27041
  import { homedir } from "os";
26448
- import { join as join23 } from "path";
27042
+ import { join as join24 } from "path";
26449
27043
  var PLUGIN_NAME = "co2-skills";
26450
- function detectCo2SkillsPlugin(claudeDir = join23(homedir(), ".claude")) {
27044
+ function detectCo2SkillsPlugin(claudeDir = join24(homedir(), ".claude")) {
26451
27045
  return readRegistry(claudeDir) ?? readCacheDir(claudeDir);
26452
27046
  }
26453
27047
  function readRegistry(claudeDir) {
26454
27048
  try {
26455
- const raw2 = readFileSync15(join23(claudeDir, "plugins", "installed_plugins.json"), "utf8");
27049
+ const raw2 = readFileSync16(join24(claudeDir, "plugins", "installed_plugins.json"), "utf8");
26456
27050
  const json = JSON.parse(raw2);
26457
27051
  for (const [key, entries] of Object.entries(json.plugins ?? {})) {
26458
27052
  if (!key.startsWith(`${PLUGIN_NAME}@`)) continue;
@@ -26465,8 +27059,8 @@ function readRegistry(claudeDir) {
26465
27059
  }
26466
27060
  }
26467
27061
  function readCacheDir(claudeDir) {
26468
- const base = join23(claudeDir, "plugins", "cache", "co2", PLUGIN_NAME);
26469
- if (!existsSync20(base)) return { installed: false };
27062
+ const base = join24(claudeDir, "plugins", "cache", "co2", PLUGIN_NAME);
27063
+ if (!existsSync21(base)) return { installed: false };
26470
27064
  try {
26471
27065
  const versions = readdirSync5(base).filter((d) => /^\d+\.\d+/.test(d)).sort();
26472
27066
  return { installed: true, version: versions.at(-1) };
@@ -26520,8 +27114,8 @@ function workspaceRoutes(_db, config, port, host, workspacePath) {
26520
27114
  }
26521
27115
 
26522
27116
  // ../server/src/lib/config.ts
26523
- import { existsSync as existsSync21, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
26524
- import { join as join24 } from "path";
27117
+ import { existsSync as existsSync22, readFileSync as readFileSync17, writeFileSync as writeFileSync13 } from "fs";
27118
+ import { join as join25 } from "path";
26525
27119
  var configSchema = external_exports.object({
26526
27120
  port: external_exports.number().int().min(1).max(65535).optional(),
26527
27121
  host: external_exports.string().optional(),
@@ -26529,16 +27123,16 @@ var configSchema = external_exports.object({
26529
27123
  provisioningState: external_exports.enum(["pending", "complete"]).optional()
26530
27124
  });
26531
27125
  function loadConfig(dataDir) {
26532
- const file = join24(dataDir, "config.json");
26533
- if (!existsSync21(file)) return {};
27126
+ const file = join25(dataDir, "config.json");
27127
+ if (!existsSync22(file)) return {};
26534
27128
  try {
26535
- return configSchema.parse(JSON.parse(readFileSync16(file, "utf8")));
27129
+ return configSchema.parse(JSON.parse(readFileSync17(file, "utf8")));
26536
27130
  } catch {
26537
27131
  return {};
26538
27132
  }
26539
27133
  }
26540
27134
  function saveConfig(dataDir, config) {
26541
- writeFileSync12(join24(dataDir, "config.json"), JSON.stringify(config, null, 2));
27135
+ writeFileSync13(join25(dataDir, "config.json"), JSON.stringify(config, null, 2));
26542
27136
  }
26543
27137
 
26544
27138
  // ../server/src/lib/logger.ts
@@ -26566,8 +27160,8 @@ function loggerMiddleware() {
26566
27160
  }
26567
27161
 
26568
27162
  // ../server/src/lib/static.ts
26569
- import { existsSync as existsSync22, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
26570
- import { extname, join as join25, normalize, resolve as resolve4, sep as sep3 } from "path";
27163
+ import { existsSync as existsSync23, readFileSync as readFileSync18, statSync as statSync7 } from "fs";
27164
+ import { extname, join as join26, normalize, resolve as resolve4, sep as sep3 } from "path";
26571
27165
  var MIME = {
26572
27166
  ".html": "text/html; charset=utf-8",
26573
27167
  ".js": "text/javascript; charset=utf-8",
@@ -26591,7 +27185,7 @@ function safeResolve2(rootDir, relPath) {
26591
27185
  return target;
26592
27186
  }
26593
27187
  function serveSpa(staticDir) {
26594
- const indexPath = join25(staticDir, "index.html");
27188
+ const indexPath = join26(staticDir, "index.html");
26595
27189
  return async (c, next) => {
26596
27190
  if (c.req.method !== "GET" && c.req.method !== "HEAD") {
26597
27191
  await next();
@@ -26604,14 +27198,14 @@ function serveSpa(staticDir) {
26604
27198
  }
26605
27199
  const rel = path === "/" ? "/index.html" : decodeURIComponent(path);
26606
27200
  const filePath = safeResolve2(staticDir, rel);
26607
- if (filePath && existsSync22(filePath) && statSync7(filePath).isFile()) {
26608
- const body = readFileSync17(filePath);
27201
+ if (filePath && existsSync23(filePath) && statSync7(filePath).isFile()) {
27202
+ const body = readFileSync18(filePath);
26609
27203
  return c.body(body, 200, {
26610
27204
  "Content-Type": MIME[extname(filePath)] ?? "application/octet-stream"
26611
27205
  });
26612
27206
  }
26613
- if (existsSync22(indexPath)) {
26614
- return c.body(readFileSync17(indexPath), 200, { "Content-Type": "text/html; charset=utf-8" });
27207
+ if (existsSync23(indexPath)) {
27208
+ return c.body(readFileSync18(indexPath), 200, { "Content-Type": "text/html; charset=utf-8" });
26615
27209
  }
26616
27210
  await next();
26617
27211
  };
@@ -26655,6 +27249,7 @@ function createApp(db, opts) {
26655
27249
  api.route("/defects", defects);
26656
27250
  api.route("/quality", defects);
26657
27251
  api.route("/dashboard", dashboardRoutes(db));
27252
+ api.route("/git", gitRoutes(db, opts.workspacePath));
26658
27253
  app.route("/api", api);
26659
27254
  if (opts.staticDir) {
26660
27255
  app.use("*", serveSpa(opts.staticDir));
@@ -26663,8 +27258,8 @@ function createApp(db, opts) {
26663
27258
  }
26664
27259
 
26665
27260
  // ../server/src/db/index.ts
26666
- import { existsSync as existsSync23 } from "fs";
26667
- import { dirname as dirname2, join as join26 } from "path";
27261
+ import { existsSync as existsSync24 } from "fs";
27262
+ import { dirname as dirname2, join as join27 } from "path";
26668
27263
  import { fileURLToPath as fileURLToPath2 } from "url";
26669
27264
  import Database from "better-sqlite3";
26670
27265
 
@@ -26868,16 +27463,16 @@ function resolveMigrationsFolder() {
26868
27463
  const here = dirname2(fileURLToPath2(import.meta.url));
26869
27464
  const candidates = [
26870
27465
  process.env["CO2_CLI_MIGRATIONS_DIR"],
26871
- join26(here, "../../drizzle"),
27466
+ join27(here, "../../drizzle"),
26872
27467
  // source: src/db -> packages/server/drizzle
26873
- join26(here, "../drizzle"),
27468
+ join27(here, "../drizzle"),
26874
27469
  // bundled: cli/dist -> cli/drizzle
26875
- join26(here, "drizzle")
27470
+ join27(here, "drizzle")
26876
27471
  ].filter((c) => Boolean(c));
26877
27472
  for (const c of candidates) {
26878
- if (existsSync23(join26(c, "meta", "_journal.json"))) return c;
27473
+ if (existsSync24(join27(c, "meta", "_journal.json"))) return c;
26879
27474
  }
26880
- return join26(here, "../../drizzle");
27475
+ return join27(here, "../../drizzle");
26881
27476
  }
26882
27477
  function openDb(dbPath, migrationsFolder = resolveMigrationsFolder()) {
26883
27478
  const sqlite = new Database(dbPath);
@@ -26911,7 +27506,7 @@ function bootstrap(db) {
26911
27506
  // ../server/src/lib/data-dir.ts
26912
27507
  import { mkdirSync as mkdirSync11 } from "fs";
26913
27508
  import { homedir as homedir2 } from "os";
26914
- import { join as join27 } from "path";
27509
+ import { join as join28 } from "path";
26915
27510
  function resolveDataDir(override) {
26916
27511
  const dir = override ?? defaultDataDir();
26917
27512
  mkdirSync11(dir, { recursive: true, mode: 448 });
@@ -26920,11 +27515,11 @@ function resolveDataDir(override) {
26920
27515
  function defaultDataDir() {
26921
27516
  switch (process.platform) {
26922
27517
  case "darwin":
26923
- return join27(homedir2(), "Library", "Application Support", "co2-cli");
27518
+ return join28(homedir2(), "Library", "Application Support", "co2-cli");
26924
27519
  case "win32":
26925
- return join27(process.env["LOCALAPPDATA"] ?? join27(homedir2(), "AppData", "Local"), "co2-cli");
27520
+ return join28(process.env["LOCALAPPDATA"] ?? join28(homedir2(), "AppData", "Local"), "co2-cli");
26926
27521
  default:
26927
- return join27(process.env["XDG_DATA_HOME"] ?? join27(homedir2(), ".local", "share"), "co2-cli");
27522
+ return join28(process.env["XDG_DATA_HOME"] ?? join28(homedir2(), ".local", "share"), "co2-cli");
26928
27523
  }
26929
27524
  }
26930
27525
 
@@ -27578,8 +28173,8 @@ var startOptionsSchema = external_exports.object({
27578
28173
  workspacePath: external_exports.string().optional()
27579
28174
  });
27580
28175
  function resolveStaticDir() {
27581
- const dir = join28(dirname3(fileURLToPath3(import.meta.url)), "../static");
27582
- return existsSync24(join28(dir, "index.html")) ? dir : void 0;
28176
+ const dir = join29(dirname3(fileURLToPath3(import.meta.url)), "../static");
28177
+ return existsSync25(join29(dir, "index.html")) ? dir : void 0;
27583
28178
  }
27584
28179
  function registerStart(program3, version2) {
27585
28180
  program3.command("start").description("Start the co2-cli web application (foreground)").option("-p, --port <n>", "Listen port (default: 3001)").option("-H, --host <addr>", "Listen host (default: 0.0.0.0 for LAN access)").option("-d, --data-dir <path>", "Override the data directory").option("-w, --workspace-path <path>", "Override the Workspace root folder").action(async (rawOpts) => {
@@ -27588,11 +28183,11 @@ function registerStart(program3, version2) {
27588
28183
  const config = loadConfig(dataDir);
27589
28184
  const port = opts.port ?? (process.env["APP_PORT"] ? Number(process.env["APP_PORT"]) : void 0) ?? config.port ?? 3001;
27590
28185
  const host = opts.host ?? process.env["APP_HOST"] ?? config.host ?? "0.0.0.0";
27591
- const workspacePath = opts.workspacePath ?? process.env["APP_WORKSPACE_PATH"] ?? config.workspacePath ?? join28(process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".", "workspace");
28186
+ const workspacePath = opts.workspacePath ?? process.env["APP_WORKSPACE_PATH"] ?? config.workspacePath ?? join29(process.env["HOME"] ?? process.env["USERPROFILE"] ?? ".", "workspace");
27592
28187
  if (opts.port !== void 0 || opts.workspacePath !== void 0) {
27593
28188
  saveConfig(dataDir, { ...config, port, workspacePath });
27594
28189
  }
27595
- const db = openDb(join28(dataDir, "app.db"));
28190
+ const db = openDb(join29(dataDir, "app.db"));
27596
28191
  const credentials = bootstrap(db);
27597
28192
  const app = createApp(db, {
27598
28193
  dataDir,