@rashidee/co2 1.3.5 → 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 +893 -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 +28 -6
  11. package/plugin/skills/util-gencicdscript/references/cicd-app-template.md +128 -2
  12. package/plugin/skills/util-plancicd/SKILL.md +73 -4
  13. package/static/assets/{abnfDiagram-VRR7QNED-DPeOXHkD.js → abnfDiagram-VRR7QNED-CzKpxD9q.js} +1 -1
  14. package/static/assets/{arc-Bb2ZEtXe.js → arc-0jj0p8Qe.js} +1 -1
  15. package/static/assets/{architectureDiagram-ZJ3FMSHR-D4QooL3I.js → architectureDiagram-ZJ3FMSHR-DXUL6S5t.js} +1 -1
  16. package/static/assets/{blockDiagram-677ZJIJ3-BBGaxcqB.js → blockDiagram-677ZJIJ3-BBjsQESd.js} +1 -1
  17. package/static/assets/{c4Diagram-LMCZKHZV-BuFUMezL.js → c4Diagram-LMCZKHZV-CTsLC95S.js} +1 -1
  18. package/static/assets/channel-1MKDXK8w.js +1 -0
  19. package/static/assets/{chunk-2Q5K7J3B-MrR2dFOK.js → chunk-2Q5K7J3B-BpisBKYx.js} +1 -1
  20. package/static/assets/{chunk-32BRIVSS-KMxu8q-J.js → chunk-32BRIVSS-DAfsr4ph.js} +1 -1
  21. package/static/assets/{chunk-5VM5RSS4-Dbm1PUMI.js → chunk-5VM5RSS4-Z3FtY-B6.js} +1 -1
  22. package/static/assets/{chunk-EX3LRPZG-C0dIkyxF.js → chunk-EX3LRPZG-FI9smQaN.js} +1 -1
  23. package/static/assets/{chunk-JWPE2WC7-DbWRh-D_.js → chunk-JWPE2WC7-sOygQj_B.js} +1 -1
  24. package/static/assets/{chunk-MOJQB5TN-Do9cetyI.js → chunk-MOJQB5TN-DCSpai7w.js} +1 -1
  25. package/static/assets/{chunk-RYQCIY6F-CyFiM1A0.js → chunk-RYQCIY6F-CchqtfnI.js} +1 -1
  26. package/static/assets/{chunk-V7JOEXUC-CoFBSdV8.js → chunk-V7JOEXUC-DaOzsfx7.js} +1 -1
  27. package/static/assets/{chunk-VR4S4FIN-ZJvwfHHK.js → chunk-VR4S4FIN-DmxuoVTy.js} +1 -1
  28. package/static/assets/{chunk-XXDRQBXY-D8HPek0x.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-Cirxlh75.js → cose-bilkent-JH36ORCC-CO7_eF6b.js} +1 -1
  32. package/static/assets/{cynefin-VYW2F7L2-DJsuSCkt.js → cynefin-VYW2F7L2-CzQkWtdY.js} +1 -1
  33. package/static/assets/{cynefinDiagram-TSTJHNR4-uBfDZheC.js → cynefinDiagram-TSTJHNR4-BN2o8rFa.js} +1 -1
  34. package/static/assets/{dagre-VKFMJZFB-BI0z1jT0.js → dagre-VKFMJZFB-GLjBE-Xs.js} +1 -1
  35. package/static/assets/{diagram-FQU43EPY-EHIThO2P.js → diagram-FQU43EPY-Cyv7saju.js} +1 -1
  36. package/static/assets/{diagram-G47NLZAW-Dy2XDYy0.js → diagram-G47NLZAW-B9PmZJ3U.js} +1 -1
  37. package/static/assets/{diagram-NH7WQ7WH-C4g4puV-.js → diagram-NH7WQ7WH-ByKD8yJU.js} +1 -1
  38. package/static/assets/{diagram-OA4YK3LP-CQ16Gfax.js → diagram-OA4YK3LP-BvbW7fIS.js} +1 -1
  39. package/static/assets/{diagram-WEI45ONY-B4FV_zLb.js → diagram-WEI45ONY-BgXZ9SJ8.js} +1 -1
  40. package/static/assets/{ebnfDiagram-CCIWWBDH-CHmsZ-vG.js → ebnfDiagram-CCIWWBDH-CXEEJ26B.js} +1 -1
  41. package/static/assets/{erDiagram-Q63AITRT-DCRi5XIy.js → erDiagram-Q63AITRT-CcHs_S16.js} +1 -1
  42. package/static/assets/{flowDiagram-23GEKE2U-BtIBkFJl.js → flowDiagram-23GEKE2U-Cwg3I3GK.js} +1 -1
  43. package/static/assets/{ganttDiagram-NO4QXBWP-CL02h1xy.js → ganttDiagram-NO4QXBWP-ky6w5Lvy.js} +1 -1
  44. package/static/assets/{gitGraphDiagram-IHSO6WYX-v3ovWEkW.js → gitGraphDiagram-IHSO6WYX-DN0eRQ7r.js} +1 -1
  45. package/static/assets/{index-BeEg-X0n.js → index-CCC4-Ibh.js} +172 -170
  46. package/static/assets/{index-Dn_JY-18.css → index-Cz5Fcq3G.css} +1 -1
  47. package/static/assets/{infoDiagram-FWYZ7A6U-CQqDjZFw.js → infoDiagram-FWYZ7A6U-COojw6OC.js} +1 -1
  48. package/static/assets/{ishikawaDiagram-FXEZZL3T-BbtSBTuq.js → ishikawaDiagram-FXEZZL3T-BR5w5ZqM.js} +1 -1
  49. package/static/assets/{journeyDiagram-5HDEW3XC-BYiMuolz.js → journeyDiagram-5HDEW3XC-B5WtTEBM.js} +1 -1
  50. package/static/assets/{kanban-definition-HUTT4EX6-ok10yIH4.js → kanban-definition-HUTT4EX6-DnQ_zg4E.js} +1 -1
  51. package/static/assets/{linear-BzIhXjex.js → linear-C5kGzenz.js} +1 -1
  52. package/static/assets/{mindmap-definition-LN4V7U3C-ByHM6OsT.js → mindmap-definition-LN4V7U3C-BMksxBso.js} +1 -1
  53. package/static/assets/{pegDiagram-2B236MQR-36ZAozwu.js → pegDiagram-2B236MQR-BKrF97iU.js} +1 -1
  54. package/static/assets/{pieDiagram-ENE6RG2P-CiI5CYaa.js → pieDiagram-ENE6RG2P-DZAQkZwC.js} +1 -1
  55. package/static/assets/{quadrantDiagram-ABIIQ3AL-CqfESErc.js → quadrantDiagram-ABIIQ3AL-Dx0-5odM.js} +1 -1
  56. package/static/assets/{railroadDiagram-RFXS5EU6-n-HvWpNy.js → railroadDiagram-RFXS5EU6-BbY-CxL_.js} +1 -1
  57. package/static/assets/{requirementDiagram-TGXJPOKE-a4x2DB1M.js → requirementDiagram-TGXJPOKE-ItSYgvcN.js} +1 -1
  58. package/static/assets/{sankeyDiagram-HTMAVEWB-s-Q6aijB.js → sankeyDiagram-HTMAVEWB-CjIuRRYE.js} +1 -1
  59. package/static/assets/{sequenceDiagram-DBY2YBRQ-DNZkVRTf.js → sequenceDiagram-DBY2YBRQ-B277PT2z.js} +1 -1
  60. package/static/assets/{sizeCapture-X5ZJPWSS-B5Ciccrp.js → sizeCapture-X5ZJPWSS-BYc46yWE.js} +1 -1
  61. package/static/assets/{stateDiagram-2N3HPSRC-BlFZbi2g.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-BXRFSwye.js → swimlanes-5IMT3BWC-DMxA2jQL.js} +2 -2
  64. package/static/assets/swimlanesDiagram-G3AALYLV-qx8IX4cU.js +8 -0
  65. package/static/assets/{timeline-definition-FHXFAJF6-D9DwlOxs.js → timeline-definition-FHXFAJF6-CB8iORNI.js} +1 -1
  66. package/static/assets/{vennDiagram-L72KCM5P-C80mLh7C.js → vennDiagram-L72KCM5P-CHwAX4cP.js} +1 -1
  67. package/static/assets/{wardleyDiagram-EHGQE667-ESSoASoQ.js → wardleyDiagram-EHGQE667-DL19kD5s.js} +1 -1
  68. package/static/assets/{xychartDiagram-FW5EYKEG-BHN68FBi.js → xychartDiagram-FW5EYKEG-b271oXxd.js} +1 -1
  69. package/static/index.html +2 -2
  70. package/static/assets/channel-8qA4Tgiu.js +0 -1
  71. package/static/assets/classDiagram-OUVF2IWQ-Q3gj4hh8.js +0 -1
  72. package/static/assets/classDiagram-v2-EOCWNBFH-Q3gj4hh8.js +0 -1
  73. package/static/assets/stateDiagram-v2-6OUMAXLB-BclL0t8p.js +0 -1
  74. package/static/assets/swimlanesDiagram-G3AALYLV-CuNyWLnI.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.5",
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}`];
20787
20876
  }
20788
- function deriveVersionBranch(semver) {
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
+ };
20949
+ }
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
  }
@@ -22490,12 +22696,12 @@ function createProject(db, workspacePath, name, code, baseIdea, createdBy) {
22490
22696
  return projectId;
22491
22697
  }
22492
22698
  function scaffoldProjectFolder(workspacePath, folderName, name, baseIdea) {
22493
- const projectDir = join16(workspacePath, folderName);
22494
- mkdirSync6(projectDir, { recursive: true });
22495
- mkdirSync6(join16(projectDir, "execution"), { recursive: true });
22496
- mkdirSync6(join16(projectDir, "context"), { recursive: true });
22497
- mkdirSync6(join16(projectDir, "context", "references"), { recursive: true });
22498
- 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 });
22499
22705
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
22500
22706
  const template = [
22501
22707
  "---",
@@ -22535,9 +22741,9 @@ function scaffoldProjectFolder(workspacePath, folderName, name, baseIdea) {
22535
22741
  "# Feedback",
22536
22742
  "<!-- Human answers appear here -->"
22537
22743
  ].join("\n");
22538
- writeFileSync7(join16(projectDir, `${folderName}.brainstorm.md`), template, "utf8");
22744
+ writeFileSync7(join16(projectDir2, `${folderName}.brainstorm.md`), template, "utf8");
22539
22745
  writeFileSync7(
22540
- join16(projectDir, "context", "references", "brainstorm-protocol.md"),
22746
+ join16(projectDir2, "context", "references", "brainstorm-protocol.md"),
22541
22747
  "# Brainstorm Protocol\n\n[Installed by the co2 plugin]\n",
22542
22748
  "utf8"
22543
22749
  );
@@ -22636,6 +22842,9 @@ function getProjectDetail(db, workspacePath, projectId) {
22636
22842
  workflowPhase: project.workflowPhase,
22637
22843
  creationStatus,
22638
22844
  isArchived: project.isArchived,
22845
+ gitRemoteUrl: project.gitRemoteUrl ?? null,
22846
+ defaultBranch: project.defaultBranch ?? "main",
22847
+ gitProvider: project.gitProvider ?? "github",
22639
22848
  applications: apps,
22640
22849
  createdAt: project.createdAt
22641
22850
  };
@@ -22691,11 +22900,11 @@ function getProjectDetailSummary(db, projectId) {
22691
22900
  function regenerateCO2Files(db, workspacePath, projectId) {
22692
22901
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
22693
22902
  if (!project) throw svcError10("NOT_FOUND", 404);
22694
- const projectDir = join16(workspacePath, project.workspaceFolderName);
22695
- mkdirSync6(projectDir, { recursive: true });
22903
+ const projectDir2 = join16(workspacePath, project.workspaceFolderName);
22904
+ mkdirSync6(projectDir2, { recursive: true });
22696
22905
  const apps = db.select().from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
22697
22906
  writeFileSync7(
22698
- join16(projectDir, "CLAUDE.md"),
22907
+ join16(projectDir2, "CLAUDE.md"),
22699
22908
  project.rawClaudeMd ?? buildClaudeMd(project.name, apps),
22700
22909
  "utf8"
22701
22910
  );
@@ -22708,8 +22917,8 @@ function regenerateCO2Files(db, workspacePath, projectId) {
22708
22917
  }
22709
22918
  for (const app of apps) {
22710
22919
  const mods = db.select().from(projectModules).where(and(eq(projectModules.applicationId, app.id), eq(projectModules.isDeleted, 0))).all();
22711
- const appRel = resolveAppFolderName(projectDir, app.name);
22712
- const appDir = join16(projectDir, appRel, "context");
22920
+ const appRel = resolveAppFolderName(projectDir2, app.name);
22921
+ const appDir = join16(projectDir2, appRel, "context");
22713
22922
  mkdirSync6(appDir, { recursive: true });
22714
22923
  writeFileSync7(
22715
22924
  join16(appDir, "PRD.md"),
@@ -22766,10 +22975,10 @@ function toKebabCase(name) {
22766
22975
  function rescanClaudeMd(db, workspacePath, projectId) {
22767
22976
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
22768
22977
  if (!project) throw svcError10("NOT_FOUND", 404);
22769
- const projectDir = join16(workspacePath, project.workspaceFolderName);
22978
+ const projectDir2 = join16(workspacePath, project.workspaceFolderName);
22770
22979
  const claudeMdPath = [
22771
- join16(projectDir, "CLAUDE.md"),
22772
- join16(projectDir, "source", "CLAUDE.md")
22980
+ join16(projectDir2, "CLAUDE.md"),
22981
+ join16(projectDir2, "source", "CLAUDE.md")
22773
22982
  ].find((p) => existsSync14(p));
22774
22983
  if (!claudeMdPath) throw svcError10("CLAUDE_MD_MISSING", 404);
22775
22984
  const content = readFileSync9(claudeMdPath, "utf8");
@@ -22860,6 +23069,18 @@ function deriveAppCode(name) {
22860
23069
  const initials = name.split(/[\s_-]+/).filter(Boolean).map((w) => w[0].toUpperCase()).join("");
22861
23070
  return (initials || name.slice(0, 2)).slice(0, 4);
22862
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
+ }
22863
23084
  function archiveProject(db, projectId, archivedBy) {
22864
23085
  const project = db.select({ id: projects.id }).from(projects).where(eq(projects.id, projectId)).limit(1).get();
22865
23086
  if (!project) throw svcError10("NOT_FOUND", 404);
@@ -23520,9 +23741,9 @@ function writeBugMdFile(db, workspacePath, app, projectFolderName) {
23520
23741
  db.select({ id: prdVersions.id, semver: prdVersions.semver }).from(prdVersions).where(eq(prdVersions.projectId, app.projectId)).all().map((v) => [v.id, v.semver])
23521
23742
  );
23522
23743
  const content = buildBugMd(modules, bugs, semverByVersionId);
23523
- const projectDir = join17(workspacePath, projectFolderName);
23524
- const appRel = resolveAppFolderName(projectDir, app.name);
23525
- 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");
23526
23747
  mkdirSync7(appContextDir, { recursive: true });
23527
23748
  writeFileSync8(join17(appContextDir, "BUG.md"), content, "utf8");
23528
23749
  return { content, bugCount: bugs.length };
@@ -23557,8 +23778,8 @@ function syncBugMdToDisk(db, workspacePath, applicationId, bugId, mode = "replac
23557
23778
  const project = db.select({ workspaceFolderName: projects.workspaceFolderName }).from(projects).where(eq(projects.id, app.projectId)).get();
23558
23779
  if (!project) return;
23559
23780
  if (bugId !== void 0) {
23560
- const projectDir = join17(workspacePath, project.workspaceFolderName);
23561
- 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");
23562
23783
  if (existsSync15(file)) {
23563
23784
  const bug = db.select().from(bugReports).where(and(eq(bugReports.id, bugId), eq(bugReports.isDeleted, 0))).get();
23564
23785
  if (bug) {
@@ -23800,6 +24021,332 @@ function documentRoutes(db, dataDir) {
23800
24021
  return app;
23801
24022
  }
23802
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
+
23803
24350
  // ../server/src/features/notification/routes.ts
23804
24351
  function notificationRoutes(db) {
23805
24352
  const app = new Hono2();
@@ -23830,7 +24377,7 @@ function notificationRoutes(db) {
23830
24377
  }
23831
24378
 
23832
24379
  // ../server/src/features/prd-wizard/routes.ts
23833
- function errorResponse7(c, e) {
24380
+ function errorResponse8(c, e) {
23834
24381
  const err = e;
23835
24382
  const status = err.status ?? 500;
23836
24383
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -23859,7 +24406,7 @@ function prdWizardRoutes(db, workspacePath) {
23859
24406
  startWizardApplyWatcher(db, workspacePath, requestId);
23860
24407
  return c.json({ requestId, filePath, status: "generated" }, 201);
23861
24408
  } catch (e) {
23862
- return errorResponse7(c, e);
24409
+ return errorResponse8(c, e);
23863
24410
  }
23864
24411
  });
23865
24412
  app.get("/:projectId", (c) => {
@@ -23871,14 +24418,14 @@ function prdWizardRoutes(db, workspacePath) {
23871
24418
  try {
23872
24419
  return c.json(listPrdWizardRequests(db, projectId, prdVersionId));
23873
24420
  } catch (e) {
23874
- return errorResponse7(c, e);
24421
+ return errorResponse8(c, e);
23875
24422
  }
23876
24423
  });
23877
24424
  return app;
23878
24425
  }
23879
24426
 
23880
24427
  // ../server/src/features/project-config/routes.ts
23881
- function errorResponse8(c, e) {
24428
+ function errorResponse9(c, e) {
23882
24429
  const err = e;
23883
24430
  const status = err.status ?? 500;
23884
24431
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -23895,7 +24442,7 @@ function projectConfigRoutes(db, workspacePath) {
23895
24442
  try {
23896
24443
  return c.json(getProjectEnvironment(db, projectId));
23897
24444
  } catch (e) {
23898
- return errorResponse8(c, e);
24445
+ return errorResponse9(c, e);
23899
24446
  }
23900
24447
  });
23901
24448
  app.patch("/:projectId/environment", zValidator("json", saveWorkspaceConfigSchema), (c) => {
@@ -23906,7 +24453,7 @@ function projectConfigRoutes(db, workspacePath) {
23906
24453
  saveProjectEnvironment(db, workspacePath, projectId, c.req.valid("json").content)
23907
24454
  );
23908
24455
  } catch (e) {
23909
- return errorResponse8(c, e);
24456
+ return errorResponse9(c, e);
23910
24457
  }
23911
24458
  });
23912
24459
  app.post(
@@ -23929,7 +24476,7 @@ function projectConfigRoutes(db, workspacePath) {
23929
24476
  )
23930
24477
  );
23931
24478
  } catch (e) {
23932
- return errorResponse8(c, e);
24479
+ return errorResponse9(c, e);
23933
24480
  }
23934
24481
  }
23935
24482
  );
@@ -23939,7 +24486,7 @@ function projectConfigRoutes(db, workspacePath) {
23939
24486
  try {
23940
24487
  return c.json(getProjectDevtool(db, projectId));
23941
24488
  } catch (e) {
23942
- return errorResponse8(c, e);
24489
+ return errorResponse9(c, e);
23943
24490
  }
23944
24491
  });
23945
24492
  app.patch("/:projectId/devtool", zValidator("json", saveWorkspaceConfigSchema), (c) => {
@@ -23948,7 +24495,7 @@ function projectConfigRoutes(db, workspacePath) {
23948
24495
  try {
23949
24496
  return c.json(saveProjectDevtool(db, workspacePath, projectId, c.req.valid("json").content));
23950
24497
  } catch (e) {
23951
- return errorResponse8(c, e);
24498
+ return errorResponse9(c, e);
23952
24499
  }
23953
24500
  });
23954
24501
  app.post(
@@ -23965,7 +24512,7 @@ function projectConfigRoutes(db, workspacePath) {
23965
24512
  importProjectDevtool(db, workspacePath, projectId, c.req.valid("json").content, user.id)
23966
24513
  );
23967
24514
  } catch (e) {
23968
- return errorResponse8(c, e);
24515
+ return errorResponse9(c, e);
23969
24516
  }
23970
24517
  }
23971
24518
  );
@@ -23973,8 +24520,8 @@ function projectConfigRoutes(db, workspacePath) {
23973
24520
  }
23974
24521
 
23975
24522
  // ../server/src/features/project-import/scanner.ts
23976
- import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
23977
- 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";
23978
24525
 
23979
24526
  // ../server/src/features/project-import/changelog-md.ts
23980
24527
  var VERSION_HEADING = /^##\s+v?(\d+\.\d+\.\d+)\b/;
@@ -24122,64 +24669,64 @@ function svcError13(code, status) {
24122
24669
  return Object.assign(new Error(code), { code, status });
24123
24670
  }
24124
24671
  function readIfExists(path) {
24125
- return existsSync17(path) ? readFileSync12(path, "utf8") : null;
24672
+ return existsSync18(path) ? readFileSync13(path, "utf8") : null;
24126
24673
  }
24127
24674
  function emptyArtifactCounts() {
24128
24675
  return { erd: 0, data_model: 0, mockup: 0, technical_spec: 0, test_spec: 0, source_code: 0 };
24129
24676
  }
24130
24677
  function countArtifacts(appDir) {
24131
24678
  const counts = emptyArtifactCounts();
24132
- const contextDir = join19(appDir, "context");
24133
- const modelDir = join19(contextDir, "model");
24134
- if (existsSync17(modelDir)) {
24679
+ const contextDir = join20(appDir, "context");
24680
+ const modelDir = join20(contextDir, "model");
24681
+ if (existsSync18(modelDir)) {
24135
24682
  for (const slug of readdirSync3(modelDir)) {
24136
24683
  try {
24137
- if (!statSync5(join19(modelDir, slug)).isDirectory()) continue;
24684
+ if (!statSync5(join20(modelDir, slug)).isDirectory()) continue;
24138
24685
  } catch {
24139
24686
  continue;
24140
24687
  }
24141
- 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")))
24142
24689
  counts.erd++;
24143
- if (existsSync17(join19(modelDir, slug, "model.md"))) counts.data_model++;
24144
- if (existsSync17(join19(contextDir, "specification", slug, "SPEC.md"))) counts.technical_spec++;
24145
- 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++;
24146
24693
  }
24147
24694
  }
24148
24695
  for (const [sub, key, file] of [
24149
24696
  ["specification", "technical_spec", "SPEC.md"],
24150
24697
  ["test", "test_spec", "TEST_SPEC.md"]
24151
24698
  ]) {
24152
- const dir = join19(contextDir, sub);
24153
- if (!existsSync17(dir)) continue;
24699
+ const dir = join20(contextDir, sub);
24700
+ if (!existsSync18(dir)) continue;
24154
24701
  let total = 0;
24155
24702
  for (const slug of readdirSync3(dir)) {
24156
24703
  try {
24157
- 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++;
24158
24705
  } catch {
24159
24706
  }
24160
24707
  }
24161
24708
  counts[key] = Math.max(counts[key], total);
24162
24709
  }
24163
- 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")))
24164
24711
  counts.mockup = 1;
24165
24712
  counts.source_code = sweepSourceFiles(appDir).length;
24166
24713
  return counts;
24167
24714
  }
24168
24715
  function findClaudeMd(folderDir) {
24169
- if (existsSync17(join19(folderDir, "CLAUDE.md"))) return { relPath: "CLAUDE.md", prefix: "" };
24170
- 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")))
24171
24718
  return { relPath: "source/CLAUDE.md", prefix: "source" };
24172
24719
  return null;
24173
24720
  }
24174
24721
  function listImportCandidates(db, workspacePath) {
24175
- if (!existsSync17(workspacePath)) return [];
24722
+ if (!existsSync18(workspacePath)) return [];
24176
24723
  const registered = new Set(
24177
24724
  db.select({ folder: projects.workspaceFolderName }).from(projects).all().map((r) => r.folder)
24178
24725
  );
24179
24726
  const candidates = [];
24180
24727
  for (const entry of readdirSync3(workspacePath)) {
24181
24728
  if (entry.startsWith(".") || registered.has(entry)) continue;
24182
- const dir = join19(workspacePath, entry);
24729
+ const dir = join20(workspacePath, entry);
24183
24730
  try {
24184
24731
  if (!statSync5(dir).isDirectory()) continue;
24185
24732
  } catch {
@@ -24187,14 +24734,14 @@ function listImportCandidates(db, workspacePath) {
24187
24734
  }
24188
24735
  const found = findClaudeMd(dir);
24189
24736
  if (!found) continue;
24190
- const detail = parseProjectDetail(
24191
- readFileSync12(join19(workspacePath, entry, found.relPath), "utf8")
24737
+ const detail2 = parseProjectDetail(
24738
+ readFileSync13(join20(workspacePath, entry, found.relPath), "utf8")
24192
24739
  );
24193
24740
  candidates.push({
24194
24741
  folderName: entry,
24195
24742
  claudeMdPath: `${entry}/${found.relPath}`,
24196
- projectName: detail.projectName,
24197
- projectCode: detail.projectCode
24743
+ projectName: detail2.projectName,
24744
+ projectCode: detail2.projectCode
24198
24745
  });
24199
24746
  }
24200
24747
  return candidates.sort((a, b) => a.folderName.localeCompare(b.folderName));
@@ -24202,22 +24749,22 @@ function listImportCandidates(db, workspacePath) {
24202
24749
  function scanRepo(db, workspacePath, folderName) {
24203
24750
  const registered = db.select({ id: projects.id }).from(projects).where(eq(projects.workspaceFolderName, folderName)).limit(1).get();
24204
24751
  if (registered) throw svcError13("FOLDER_ALREADY_IMPORTED", 409);
24205
- const folderDir = join19(workspacePath, folderName);
24206
- const found = existsSync17(folderDir) ? findClaudeMd(folderDir) : null;
24752
+ const folderDir = join20(workspacePath, folderName);
24753
+ const found = existsSync18(folderDir) ? findClaudeMd(folderDir) : null;
24207
24754
  if (!found) throw svcError13("CLAUDE_MD_MISSING", 404);
24208
- const repoRoot = found.prefix ? join19(folderDir, found.prefix) : folderDir;
24209
- const claudeMdRaw = readFileSync12(join19(folderDir, found.relPath), "utf8");
24210
- 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);
24211
24758
  const claudeApps = parseClaudeMdApplications(claudeMdRaw);
24212
24759
  const claudeModules = new Set(parseModuleNames(claudeMdRaw).map((m) => m.name));
24213
24760
  const warnings = [];
24214
- if (detail.projectName) {
24215
- 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();
24216
24763
  if (nameTaken) {
24217
24764
  warnings.push({
24218
24765
  level: "warning",
24219
24766
  code: "DUPLICATE_PROJECT_NAME",
24220
- 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.`
24221
24768
  });
24222
24769
  }
24223
24770
  }
@@ -24229,10 +24776,10 @@ function scanRepo(db, workspacePath, folderName) {
24229
24776
  for (const claude of claudeApps) {
24230
24777
  const name = toKebabCase(claude.heading);
24231
24778
  const resolvedInRoot = resolveAppFolderName(repoRoot, name);
24232
- const folderExists = existsSync17(join19(repoRoot, resolvedInRoot));
24779
+ const folderExists = existsSync18(join20(repoRoot, resolvedInRoot));
24233
24780
  const folderRel = folderExists ? found.prefix ? `${found.prefix}/${resolvedInRoot}` : resolvedInRoot : null;
24234
- const appDir = folderRel ? join19(folderDir, folderRel) : null;
24235
- 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;
24236
24783
  const prdModules = prdRaw ? parsePrdMarkdown(prdRaw) : [];
24237
24784
  const versions = /* @__PURE__ */ new Set();
24238
24785
  for (const m of prdModules) {
@@ -24243,19 +24790,19 @@ function scanRepo(db, workspacePath, folderName) {
24243
24790
  for (const v of versions) semverUnion.add(v);
24244
24791
  const derived = prdRaw ? deriveRequirementIdAppCode(prdRaw, name) : { code: appCodeFromInitials(name), derivedFromIds: false };
24245
24792
  const requirementIdAppCode = claude.requirementIdAppCode ?? derived.code;
24246
- const bugMdRaw = appDir ? readIfExists(join19(appDir, "context", "BUG.md")) : null;
24793
+ const bugMdRaw = appDir ? readIfExists(join20(appDir, "context", "BUG.md")) : null;
24247
24794
  const bugItems = bugMdRaw ? parseBugMd(bugMdRaw) : [];
24248
- 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;
24249
24796
  const bugMaster = bugMasterRaw ? parseBugMaster(bugMasterRaw) : /* @__PURE__ */ new Map();
24250
24797
  for (const item of bugItems) if (item.semver) semverUnion.add(item.semver);
24251
24798
  for (const [, m] of bugMaster) if (m.semver) semverUnion.add(m.semver);
24252
- const preparePath = appDir ? join19(appDir, "context", "prepare", "PREPARE_MASTER.md") : "";
24253
- const developPath = appDir ? join19(appDir, "context", "develop", "IMPLEMENTATION_MASTER.md") : "";
24254
- const prepareMasterExists = !!appDir && existsSync17(preparePath);
24255
- 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);
24256
24803
  const prepareCompleted = prepareMasterExists && parseTrackingStatus(preparePath) === "COMPLETED";
24257
24804
  const developCompleted = developMasterExists && parseTrackingStatus(developPath) === "COMPLETED";
24258
- const changelogRaw = appDir ? readIfExists(join19(appDir, "CHANGELOG.md")) : null;
24805
+ const changelogRaw = appDir ? readIfExists(join20(appDir, "CHANGELOG.md")) : null;
24259
24806
  const changelogEntries = changelogRaw ? parseChangelogConductorEntries(changelogRaw) : [];
24260
24807
  if (!folderRel) {
24261
24808
  warnings.push({
@@ -24337,21 +24884,28 @@ function scanRepo(db, workspacePath, folderName) {
24337
24884
  }
24338
24885
  const versionUnion = [...semverUnion].sort(compareSemver);
24339
24886
  const workflowPhase = anyBugs ? "qa" : anyDevelopCompleted ? "developed" : anyPrepareMaster ? "prepared" : "brainstorm";
24340
- const environmentMd = readIfExists(join19(repoRoot, "ENVIRONMENT.md")) ?? readIfExists(join19(folderDir, "ENVIRONMENT.md"));
24341
- 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";
24342
24893
  return {
24343
24894
  folderName,
24344
24895
  sourcePrefix: found.prefix,
24345
24896
  claudeMdRelPath: found.relPath,
24346
24897
  claudeMdRaw,
24347
- projectName: detail.projectName,
24348
- projectCode: detail.projectCode,
24898
+ projectName: detail2.projectName,
24899
+ projectCode: detail2.projectCode,
24349
24900
  apps,
24350
24901
  versionUnion,
24351
24902
  currentVersion: versionUnion.at(-1) ?? null,
24352
24903
  workflowPhase,
24353
24904
  environmentMd,
24354
24905
  devtoolMd,
24906
+ gitRemoteUrl,
24907
+ defaultBranch,
24908
+ gitProvider,
24355
24909
  warnings
24356
24910
  };
24357
24911
  }
@@ -24390,11 +24944,11 @@ function scanProject(db, workspacePath, folderName) {
24390
24944
  }
24391
24945
 
24392
24946
  // ../server/src/features/project-import/service.ts
24393
- import { join as join21, relative as relative2 } from "path";
24947
+ import { join as join22, relative as relative2 } from "path";
24394
24948
 
24395
24949
  // ../server/src/features/workflow/service.ts
24396
- import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
24397
- 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";
24398
24952
  var ACTIVE_RUN_STATUSES = ["pending", "in_progress"];
24399
24953
  function svcError14(code, status) {
24400
24954
  return Object.assign(new Error(code), { code, status });
@@ -24536,9 +25090,9 @@ function validateAllAppsPending(db, projectId, applicationIds, conductorType, pr
24536
25090
  const notPending = applicationIds.filter((id) => !pending.has(id));
24537
25091
  if (notPending.length > 0) throw svcError14("APPS_NOT_PENDING", 422);
24538
25092
  }
24539
- function resolveRunTrackingFilePath(projectDir, appName, conductorType) {
25093
+ function resolveRunTrackingFilePath(projectDir2, appName, conductorType) {
24540
25094
  const master = TRACKING_FILE_MAP[conductorType] ?? "";
24541
- return join20(projectDir, resolveAppFolderName(projectDir, appName), master);
25095
+ return join21(projectDir2, resolveAppFolderName(projectDir2, appName), master);
24542
25096
  }
24543
25097
  function buildWorkflowPath(username) {
24544
25098
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").replace(/\..+$/, "").replace("T", "-");
@@ -24611,7 +25165,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24611
25165
  const ver = db.select({ semver: prdVersions.semver }).from(prdVersions).where(eq(prdVersions.id, prdVersionId)).get();
24612
25166
  const semver = ver?.semver ?? "latest";
24613
25167
  const relPath = buildWorkflowPath(creatingUser.username);
24614
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25168
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24615
25169
  const content = buildWorkflowMarkdown(
24616
25170
  input.conductorType,
24617
25171
  apps.map((a) => a.name),
@@ -24619,8 +25173,8 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24619
25173
  project.workspaceFolderName,
24620
25174
  relPath
24621
25175
  );
24622
- mkdirSync9(join20(projectDir, "workflow", creatingUser.username), { recursive: true });
24623
- writeFileSync10(join20(projectDir, relPath), content, "utf8");
25176
+ mkdirSync9(join21(projectDir2, "workflow", creatingUser.username), { recursive: true });
25177
+ writeFileSync11(join21(projectDir2, relPath), content, "utf8");
24624
25178
  const now = (/* @__PURE__ */ new Date()).toISOString();
24625
25179
  const wfm = db.insert(workflowMarkdowns).values({
24626
25180
  projectId: input.projectId,
@@ -24636,7 +25190,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24636
25190
  const runApps = [];
24637
25191
  for (const appId of input.applicationIds) {
24638
25192
  const appName = apps.find((a) => a.id === appId)?.name ?? "";
24639
- const trackingFilePath = resolveRunTrackingFilePath(projectDir, appName, input.conductorType);
25193
+ const trackingFilePath = resolveRunTrackingFilePath(projectDir2, appName, input.conductorType);
24640
25194
  const run = db.insert(conductorRuns).values({
24641
25195
  projectId: input.projectId,
24642
25196
  applicationId: appId,
@@ -24660,7 +25214,7 @@ function generateWorkflowMarkdown(db, workspacePath, input, creatingUser, watche
24660
25214
  startWatcher(
24661
25215
  db,
24662
25216
  runId,
24663
- () => resolveRunTrackingFilePath(projectDir, appName, input.conductorType),
25217
+ () => resolveRunTrackingFilePath(projectDir2, appName, input.conductorType),
24664
25218
  creatingUser.id,
24665
25219
  watcherIntervalMs,
24666
25220
  (id, status) => {
@@ -24699,13 +25253,13 @@ function backfillWorkflowHistoryFromChangelogs(db, workspacePath, projectId) {
24699
25253
  if (!project) return 0;
24700
25254
  const apps = db.select({ id: applications.id, name: applications.name }).from(applications).where(and(eq(applications.projectId, projectId), eq(applications.isDeleted, 0))).all();
24701
25255
  if (apps.length === 0) return 0;
24702
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25256
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24703
25257
  const collected = [];
24704
25258
  for (const app of apps) {
24705
- const appRel = resolveAppFolderName(projectDir, app.name);
24706
- const changelogPath = join20(projectDir, appRel, "CHANGELOG.md");
24707
- if (!existsSync18(changelogPath)) continue;
24708
- 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"))) {
24709
25263
  collected.push({ appName: app.name, appRel, entry });
24710
25264
  }
24711
25265
  }
@@ -24751,7 +25305,7 @@ function getActiveRun(db, projectId, workspacePath) {
24751
25305
  function resumeWatchers(db, workspacePath, projectId, watcherIntervalMs) {
24752
25306
  const project = db.select().from(projects).where(eq(projects.id, projectId)).get();
24753
25307
  if (!project) return;
24754
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25308
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24755
25309
  const stale = db.select({ run: conductorRuns, appName: applications.name }).from(conductorRuns).innerJoin(applications, eq(applications.id, conductorRuns.applicationId)).where(
24756
25310
  and(
24757
25311
  eq(conductorRuns.projectId, projectId),
@@ -24766,7 +25320,7 @@ function resumeWatchers(db, workspacePath, projectId, watcherIntervalMs) {
24766
25320
  db,
24767
25321
  run.id,
24768
25322
  // [BUG-027] Re-resolve per poll — heals rows persisted with a stale/project-level path.
24769
- () => resolveRunTrackingFilePath(projectDir, appName, conductorType),
25323
+ () => resolveRunTrackingFilePath(projectDir2, appName, conductorType),
24770
25324
  run.startedBy ?? 1,
24771
25325
  watcherIntervalMs,
24772
25326
  (id, status) => {
@@ -24801,7 +25355,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24801
25355
  if (!wfm) throw svcError14("RUN_NOT_FOUND", 404);
24802
25356
  const project = db.select().from(projects).where(eq(projects.id, wfm.projectId)).get();
24803
25357
  if (!project) throw svcError14("PROJECT_NOT_FOUND", 404);
24804
- const projectDir = join20(workspacePath, project.workspaceFolderName);
25358
+ const projectDir2 = join21(workspacePath, project.workspaceFolderName);
24805
25359
  const runs = db.select({ run: conductorRuns, appName: applications.name }).from(conductorRuns).innerJoin(applications, eq(applications.id, conductorRuns.applicationId)).where(
24806
25360
  and(
24807
25361
  eq(conductorRuns.workflowMarkdownId, workflowMarkdownId),
@@ -24810,7 +25364,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24810
25364
  ).all();
24811
25365
  for (const { run, appName } of runs) {
24812
25366
  const conductorType = run.conductorType;
24813
- const freshPath = resolveRunTrackingFilePath(projectDir, appName, conductorType);
25367
+ const freshPath = resolveRunTrackingFilePath(projectDir2, appName, conductorType);
24814
25368
  if (freshPath !== run.trackingFilePath) {
24815
25369
  db.update(conductorRuns).set({ trackingFilePath: freshPath }).where(eq(conductorRuns.id, run.id)).run();
24816
25370
  }
@@ -24830,7 +25384,7 @@ function rescanWorkflowRun(db, workspacePath, workflowMarkdownId, watcherInterva
24830
25384
  startWatcher(
24831
25385
  db,
24832
25386
  run.id,
24833
- () => resolveRunTrackingFilePath(projectDir, appName, conductorType),
25387
+ () => resolveRunTrackingFilePath(projectDir2, appName, conductorType),
24834
25388
  run.startedBy ?? 1,
24835
25389
  watcherIntervalMs,
24836
25390
  (id, terminalStatus) => {
@@ -24862,12 +25416,12 @@ function getWorkflowRunFileContent(db, workspacePath, workflowMarkdownId) {
24862
25416
  if (!wfm) throw svcError14("RUN_NOT_FOUND", 404);
24863
25417
  const project = db.select().from(projects).where(eq(projects.id, wfm.projectId)).get();
24864
25418
  if (!project) throw svcError14("PROJECT_NOT_FOUND", 404);
24865
- const projectDir = resolve2(workspacePath, project.workspaceFolderName);
24866
- const absolute = resolve2(projectDir, wfm.filePath);
24867
- 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))
24868
25422
  throw svcError14("INVALID_PATH", 400);
24869
- if (!existsSync18(absolute)) throw svcError14("FILE_NOT_FOUND", 404);
24870
- 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") };
24871
25425
  }
24872
25426
  function getRunApplicationChangelog(db, workspacePath, workflowMarkdownId, appName) {
24873
25427
  const wfm = db.select().from(workflowMarkdowns).where(eq(workflowMarkdowns.id, workflowMarkdownId)).get();
@@ -24882,16 +25436,16 @@ function getRunApplicationChangelog(db, workspacePath, workflowMarkdownId, appNa
24882
25436
  runAppNames = appIds.length > 0 ? db.select({ name: applications.name }).from(applications).where(inArray(applications.id, appIds)).all().map((a) => a.name) : [];
24883
25437
  }
24884
25438
  if (!runAppNames.includes(appName)) throw svcError14("APP_NOT_IN_RUN", 404);
24885
- const projectDir = resolve2(workspacePath, project.workspaceFolderName);
24886
- const appFolder = resolveAppFolderName(projectDir, appName);
24887
- const absolute = resolve2(projectDir, appFolder, "CHANGELOG.md");
24888
- 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))
24889
25443
  throw svcError14("INVALID_PATH", 400);
24890
- if (!existsSync18(absolute)) throw svcError14("CHANGELOG_NOT_FOUND", 404);
25444
+ if (!existsSync19(absolute)) throw svcError14("CHANGELOG_NOT_FOUND", 404);
24891
25445
  return {
24892
25446
  applicationName: appName,
24893
25447
  filePath: relative(workspacePath, absolute).replace(/\\/g, "/"),
24894
- content: readFileSync13(absolute, "utf-8")
25448
+ content: readFileSync14(absolute, "utf-8")
24895
25449
  };
24896
25450
  }
24897
25451
  function toDetail(db, projectFolder, wfm, workspacePath) {
@@ -24926,20 +25480,20 @@ function toDetail(db, projectFolder, wfm, workspacePath) {
24926
25480
  const appNames = appRows.map((a) => a.name);
24927
25481
  let appBreakdown;
24928
25482
  if (workspacePath) {
24929
- const projectDir = join20(workspacePath, projectFolder);
25483
+ const projectDir2 = join21(workspacePath, projectFolder);
24930
25484
  appBreakdown = runs.map((r) => {
24931
25485
  const appName = nameOf.get(r.applicationId) ?? `app #${r.applicationId}`;
24932
- const trackingPath = resolveRunTrackingFilePath(projectDir, appName, r.conductorType);
25486
+ const trackingPath = resolveRunTrackingFilePath(projectDir2, appName, r.conductorType);
24933
25487
  return {
24934
25488
  applicationName: appName,
24935
25489
  status: r.status,
24936
- trackingFileExists: existsSync18(trackingPath),
25490
+ trackingFileExists: existsSync19(trackingPath),
24937
25491
  trackingFilePath: relative(workspacePath, trackingPath).replace(/\\/g, "/")
24938
25492
  };
24939
25493
  });
24940
25494
  }
24941
25495
  const completedAt = runs.every((r) => r.completedAt) ? runs.map((r) => r.completedAt).sort().at(-1) ?? null : null;
24942
- const git3 = runs.find((r) => r.gitOperationStatus && r.gitOperationStatus !== "not_started");
25496
+ const git4 = runs.find((r) => r.gitOperationStatus && r.gitOperationStatus !== "not_started");
24943
25497
  return {
24944
25498
  id: wfm.id,
24945
25499
  conductorType: wfm.conductorType,
@@ -24949,9 +25503,9 @@ function toDetail(db, projectFolder, wfm, workspacePath) {
24949
25503
  filePath: wfm.filePath,
24950
25504
  executeCommand: buildExecuteCommand(projectFolder, wfm.filePath),
24951
25505
  versionLabel: wfm.versionLabel,
24952
- gitOperationStatus: git3?.gitOperationStatus ?? "not_started",
24953
- gitBranchCreated: git3?.gitBranchCreated ?? null,
24954
- gitCommitHash: git3?.gitCommitHash ?? null,
25506
+ gitOperationStatus: git4?.gitOperationStatus ?? "not_started",
25507
+ gitBranchCreated: git4?.gitBranchCreated ?? null,
25508
+ gitCommitHash: git4?.gitCommitHash ?? null,
24955
25509
  completedAt,
24956
25510
  createdAt: wfm.createdAt
24957
25511
  };
@@ -24972,7 +25526,7 @@ function importProject(db, workspacePath, input, userId) {
24972
25526
  const nameTaken = db.select({ id: projects.id }).from(projects).where(eq(projects.name, name)).limit(1).get();
24973
25527
  if (nameTaken) throw svcError15("PROJECT_EXISTS", 409);
24974
25528
  const items = [];
24975
- 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 } : {} });
24976
25530
  const projectId = db.transaction((rawTx) => {
24977
25531
  const tx = rawTx;
24978
25532
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -24985,6 +25539,9 @@ function importProject(db, workspacePath, input, userId) {
24985
25539
  // bypasses the lazy brainstorm-rescan path
24986
25540
  workflowPhase: scan.workflowPhase,
24987
25541
  rawClaudeMd: scan.claudeMdRaw,
25542
+ gitRemoteUrl: scan.gitRemoteUrl ?? null,
25543
+ defaultBranch: scan.defaultBranch,
25544
+ gitProvider: scan.gitProvider,
24988
25545
  importedAt: now,
24989
25546
  importedBy: userId,
24990
25547
  createdBy: userId,
@@ -25068,7 +25625,7 @@ function importProject(db, workspacePath, input, userId) {
25068
25625
  snapshotPrdContent: JSON.stringify(prdSnapshot),
25069
25626
  snapshotBugContent: JSON.stringify(bugSnapshot)
25070
25627
  }).where(eq(prdVersions.id, current.id)).run();
25071
- const projectDir = join21(workspacePath, input.folderName);
25628
+ const projectDir2 = join22(workspacePath, input.folderName);
25072
25629
  for (const app of scan.apps) {
25073
25630
  const appId = appIdByName.get(app.name);
25074
25631
  const hasContextArtifacts = app.artifactCounts.erd + app.artifactCounts.data_model + app.artifactCounts.mockup + app.artifactCounts.technical_spec + app.artifactCounts.test_spec > 0;
@@ -25085,8 +25642,8 @@ function importProject(db, workspacePath, input, userId) {
25085
25642
  const trackingPathFor = (type) => toPosix(
25086
25643
  relative2(
25087
25644
  workspacePath,
25088
- join21(
25089
- projectDir,
25645
+ join22(
25646
+ projectDir2,
25090
25647
  app.folderRel,
25091
25648
  type === "prepare" ? "context/prepare/PREPARE_MASTER.md" : type === "develop" ? "context/develop/IMPLEMENTATION_MASTER.md" : "context/bug/BUG_MASTER.md"
25092
25649
  )
@@ -25311,7 +25868,7 @@ ${item.body}`.match(REQUIREMENT_ID)?.[0] ?? null;
25311
25868
  }
25312
25869
 
25313
25870
  // ../server/src/features/project-import/routes.ts
25314
- function errorResponse9(c, e) {
25871
+ function errorResponse10(c, e) {
25315
25872
  const err = e;
25316
25873
  const status = err.status ?? 500;
25317
25874
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25322,14 +25879,14 @@ function projectImportRoutes(db, workspacePath) {
25322
25879
  try {
25323
25880
  return c.json(listImportCandidates(db, workspacePath));
25324
25881
  } catch (e) {
25325
- return errorResponse9(c, e);
25882
+ return errorResponse10(c, e);
25326
25883
  }
25327
25884
  });
25328
25885
  app.post("/scan", requireRole("admin"), zValidator("json", scanImportRequestSchema), (c) => {
25329
25886
  try {
25330
25887
  return c.json(scanProject(db, workspacePath, c.req.valid("json").folderName));
25331
25888
  } catch (e) {
25332
- return errorResponse9(c, e);
25889
+ return errorResponse10(c, e);
25333
25890
  }
25334
25891
  });
25335
25892
  app.post("/", requireRole("admin"), zValidator("json", runImportRequestSchema), (c) => {
@@ -25337,14 +25894,14 @@ function projectImportRoutes(db, workspacePath) {
25337
25894
  try {
25338
25895
  return c.json(importProject(db, workspacePath, c.req.valid("json"), user.id), 201);
25339
25896
  } catch (e) {
25340
- return errorResponse9(c, e);
25897
+ return errorResponse10(c, e);
25341
25898
  }
25342
25899
  });
25343
25900
  return app;
25344
25901
  }
25345
25902
 
25346
25903
  // ../server/src/features/project/routes.ts
25347
- function errorResponse10(c, e) {
25904
+ function errorResponse11(c, e) {
25348
25905
  const err = e;
25349
25906
  const status = err.status ?? 500;
25350
25907
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25362,7 +25919,7 @@ function projectRoutes(db, workspacePath) {
25362
25919
  const id = createProject(db, workspacePath, name, code, baseIdea, user.id);
25363
25920
  return c.json({ id }, 201);
25364
25921
  } catch (e) {
25365
- return errorResponse10(c, e);
25922
+ return errorResponse11(c, e);
25366
25923
  }
25367
25924
  });
25368
25925
  app.get("/:id/detail", (c) => {
@@ -25371,7 +25928,7 @@ function projectRoutes(db, workspacePath) {
25371
25928
  try {
25372
25929
  return c.json(getProjectDetailSummary(db, id));
25373
25930
  } catch (e) {
25374
- return errorResponse10(c, e);
25931
+ return errorResponse11(c, e);
25375
25932
  }
25376
25933
  });
25377
25934
  app.post("/:id/scaffold", requireRole("admin"), (c) => {
@@ -25381,7 +25938,7 @@ function projectRoutes(db, workspacePath) {
25381
25938
  scaffoldProject(db, workspacePath, id);
25382
25939
  return c.json({ ok: true });
25383
25940
  } catch (e) {
25384
- return errorResponse10(c, e);
25941
+ return errorResponse11(c, e);
25385
25942
  }
25386
25943
  });
25387
25944
  app.get("/:id/brainstorm-md", (c) => {
@@ -25390,7 +25947,7 @@ function projectRoutes(db, workspacePath) {
25390
25947
  try {
25391
25948
  return c.json(getBrainstormMd(db, workspacePath, id));
25392
25949
  } catch (e) {
25393
- return errorResponse10(c, e);
25950
+ return errorResponse11(c, e);
25394
25951
  }
25395
25952
  });
25396
25953
  app.patch("/:id/brainstorm-md", zValidator("json", updateBrainstormMdSchema), (c) => {
@@ -25400,7 +25957,7 @@ function projectRoutes(db, workspacePath) {
25400
25957
  updateBrainstormMd(db, workspacePath, id, c.req.valid("json").content);
25401
25958
  return c.json({ ok: true });
25402
25959
  } catch (e) {
25403
- return errorResponse10(c, e);
25960
+ return errorResponse11(c, e);
25404
25961
  }
25405
25962
  });
25406
25963
  app.get("/:id", (c) => {
@@ -25409,7 +25966,7 @@ function projectRoutes(db, workspacePath) {
25409
25966
  try {
25410
25967
  return c.json(getProjectDetail(db, workspacePath, id));
25411
25968
  } catch (e) {
25412
- return errorResponse10(c, e);
25969
+ return errorResponse11(c, e);
25413
25970
  }
25414
25971
  });
25415
25972
  app.post("/:id/regenerate", (c) => {
@@ -25419,7 +25976,7 @@ function projectRoutes(db, workspacePath) {
25419
25976
  regenerateCO2Files(db, workspacePath, id);
25420
25977
  return c.json({ ok: true });
25421
25978
  } catch (e) {
25422
- return errorResponse10(c, e);
25979
+ return errorResponse11(c, e);
25423
25980
  }
25424
25981
  });
25425
25982
  app.post("/:id/rescan-claude-md", (c) => {
@@ -25429,7 +25986,7 @@ function projectRoutes(db, workspacePath) {
25429
25986
  const imported = rescanClaudeMd(db, workspacePath, id);
25430
25987
  return c.json({ ok: true, imported });
25431
25988
  } catch (e) {
25432
- return errorResponse10(c, e);
25989
+ return errorResponse11(c, e);
25433
25990
  }
25434
25991
  });
25435
25992
  app.patch("/:id/archive", requireRole("admin"), (c) => {
@@ -25440,15 +25997,29 @@ function projectRoutes(db, workspacePath) {
25440
25997
  archiveProject(db, id, user.id);
25441
25998
  return c.json({ ok: true });
25442
25999
  } catch (e) {
25443
- 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);
25444
26015
  }
25445
26016
  });
25446
26017
  return app;
25447
26018
  }
25448
26019
 
25449
26020
  // ../server/src/features/shared-context/service.ts
25450
- import { existsSync as existsSync19, mkdirSync as mkdirSync10, readFileSync as readFileSync14, readdirSync as readdirSync4, statSync as statSync6, writeFileSync as writeFileSync11 } from "fs";
25451
- 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";
25452
26023
  function svcError16(code, status) {
25453
26024
  return Object.assign(new Error(code), { code, status });
25454
26025
  }
@@ -25474,7 +26045,7 @@ function mimeForName(name) {
25474
26045
  function resolveSharedRoot(db, workspacePath, projectId) {
25475
26046
  const project = db.select().from(projects).where(eq(projects.id, projectId)).limit(1).get();
25476
26047
  if (!project) throw svcError16("NOT_FOUND", 404);
25477
- const root = join22(workspacePath, project.workspaceFolderName, "shared_context");
26048
+ const root = join23(workspacePath, project.workspaceFolderName, "shared_context");
25478
26049
  mkdirSync10(root, { recursive: true });
25479
26050
  return root;
25480
26051
  }
@@ -25488,9 +26059,9 @@ function safeResolve(root, relPath) {
25488
26059
  return full;
25489
26060
  }
25490
26061
  function sanitizeSegment(name) {
25491
- const safe = name.replace(/[^a-zA-Z0-9._-]/g, "_");
25492
- if (!safe || safe === "." || safe === "..") throw svcError16("INVALID_NAME", 400);
25493
- 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;
25494
26065
  }
25495
26066
  function walk(dir, relBase) {
25496
26067
  const entries = readdirSync4(dir, { withFileTypes: true });
@@ -25502,10 +26073,10 @@ function walk(dir, relBase) {
25502
26073
  name: entry.name,
25503
26074
  path: rel,
25504
26075
  type: "folder",
25505
- children: walk(join22(dir, entry.name), rel)
26076
+ children: walk(join23(dir, entry.name), rel)
25506
26077
  });
25507
26078
  } else if (entry.isFile()) {
25508
- const stat = statSync6(join22(dir, entry.name));
26079
+ const stat = statSync6(join23(dir, entry.name));
25509
26080
  nodes.push({
25510
26081
  name: entry.name,
25511
26082
  path: rel,
@@ -25536,28 +26107,28 @@ function uploadFile(db, workspacePath, projectId, folder, file) {
25536
26107
  if (file.bytes.length > MAX_DOCUMENT_BYTES) throw svcError16("FILE_TOO_LARGE", 413);
25537
26108
  const root = resolveSharedRoot(db, workspacePath, projectId);
25538
26109
  const dir = folder ? safeResolve(root, folder) : root;
25539
- if (folder && !existsSync19(dir)) throw svcError16("FOLDER_NOT_FOUND", 404);
26110
+ if (folder && !existsSync20(dir)) throw svcError16("FOLDER_NOT_FOUND", 404);
25540
26111
  const safeName = sanitizeSegment(file.name);
25541
- writeFileSync11(join22(dir, safeName), file.bytes);
26112
+ writeFileSync12(join23(dir, safeName), file.bytes);
25542
26113
  return { path: folder ? `${folder}/${safeName}` : safeName };
25543
26114
  }
25544
26115
  function getFileContent(db, workspacePath, projectId, relPath) {
25545
26116
  const root = resolveSharedRoot(db, workspacePath, projectId);
25546
26117
  const full = safeResolve(root, relPath);
25547
- if (!existsSync19(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26118
+ if (!existsSync20(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
25548
26119
  const name = relPath.split("/").pop() ?? relPath;
25549
- return { mimeType: mimeForName(name), bytes: readFileSync14(full), name };
26120
+ return { mimeType: mimeForName(name), bytes: readFileSync15(full), name };
25550
26121
  }
25551
26122
  function saveMarkdown(db, workspacePath, projectId, relPath, content) {
25552
26123
  if (mimeForName(relPath) !== "text/markdown") throw svcError16("NOT_MARKDOWN", 400);
25553
26124
  const root = resolveSharedRoot(db, workspacePath, projectId);
25554
26125
  const full = safeResolve(root, relPath);
25555
- if (!existsSync19(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
25556
- writeFileSync11(full, content, "utf8");
26126
+ if (!existsSync20(full) || !statSync6(full).isFile()) throw svcError16("NOT_FOUND", 404);
26127
+ writeFileSync12(full, content, "utf8");
25557
26128
  }
25558
26129
 
25559
26130
  // ../server/src/features/shared-context/routes.ts
25560
- function errorResponse11(c, e) {
26131
+ function errorResponse12(c, e) {
25561
26132
  const err = e;
25562
26133
  const status = err.status ?? 500;
25563
26134
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25573,7 +26144,7 @@ function sharedContextRoutes(db, workspacePath) {
25573
26144
  try {
25574
26145
  return c.json({ tree: listTree(db, workspacePath, projectId) });
25575
26146
  } catch (e) {
25576
- return errorResponse11(c, e);
26147
+ return errorResponse12(c, e);
25577
26148
  }
25578
26149
  });
25579
26150
  app.post("/:projectId/folders", async (c) => {
@@ -25584,7 +26155,7 @@ function sharedContextRoutes(db, workspacePath) {
25584
26155
  try {
25585
26156
  return c.json({ path: createFolder(db, workspacePath, projectId, body.path) });
25586
26157
  } catch (e) {
25587
- return errorResponse11(c, e);
26158
+ return errorResponse12(c, e);
25588
26159
  }
25589
26160
  });
25590
26161
  app.post("/:projectId/upload", async (c) => {
@@ -25599,7 +26170,7 @@ function sharedContextRoutes(db, workspacePath) {
25599
26170
  const { path } = uploadFile(db, workspacePath, projectId, folder, { name: file.name, bytes });
25600
26171
  return c.json({ path });
25601
26172
  } catch (e) {
25602
- return errorResponse11(c, e);
26173
+ return errorResponse12(c, e);
25603
26174
  }
25604
26175
  });
25605
26176
  app.get("/:projectId/content", (c) => {
@@ -25618,7 +26189,7 @@ function sharedContextRoutes(db, workspacePath) {
25618
26189
  }
25619
26190
  });
25620
26191
  } catch (e) {
25621
- return errorResponse11(c, e);
26192
+ return errorResponse12(c, e);
25622
26193
  }
25623
26194
  });
25624
26195
  app.put("/:projectId/content", async (c) => {
@@ -25632,7 +26203,7 @@ function sharedContextRoutes(db, workspacePath) {
25632
26203
  saveMarkdown(db, workspacePath, projectId, body.path, body.content);
25633
26204
  return c.json({ ok: true });
25634
26205
  } catch (e) {
25635
- return errorResponse11(c, e);
26206
+ return errorResponse12(c, e);
25636
26207
  }
25637
26208
  });
25638
26209
  return app;
@@ -25697,8 +26268,8 @@ function svcError17(code, status) {
25697
26268
  return Object.assign(new Error(code), { code, status });
25698
26269
  }
25699
26270
  function toSafe(user) {
25700
- const { passwordHash: _omit, ...safe } = user;
25701
- return safe;
26271
+ const { passwordHash: _omit, ...safe2 } = user;
26272
+ return safe2;
25702
26273
  }
25703
26274
  function loginUser(db, input, clientIp) {
25704
26275
  const rateLimitKey = `${input.username}:${clientIp}`;
@@ -25775,8 +26346,8 @@ function getUserDetail(db, id) {
25775
26346
  const admin = db.select({ displayName: users.displayName }).from(users).where(eq(users.id, user.reactivatedBy)).limit(1).get();
25776
26347
  reactivatedByName = admin?.displayName ?? null;
25777
26348
  }
25778
- const { passwordHash: _omit, ...safe } = user;
25779
- return { ...safe, reactivatedByName };
26349
+ const { passwordHash: _omit, ...safe2 } = user;
26350
+ return { ...safe2, reactivatedByName };
25780
26351
  }
25781
26352
  function adminUpdateUser(db, targetId, input) {
25782
26353
  const now = (/* @__PURE__ */ new Date()).toISOString();
@@ -25837,7 +26408,7 @@ function listAssignments(db, userId) {
25837
26408
 
25838
26409
  // ../server/src/features/user/auth-routes.ts
25839
26410
  var SESSION_MAX_AGE = 30 * 24 * 60 * 60;
25840
- function errorResponse12(c, e) {
26411
+ function errorResponse13(c, e) {
25841
26412
  const err = e;
25842
26413
  const status = err.status ?? 500;
25843
26414
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25858,7 +26429,7 @@ function authRoutes(db) {
25858
26429
  });
25859
26430
  return c.json({ mustChangePassword });
25860
26431
  } catch (e) {
25861
- return errorResponse12(c, e);
26432
+ return errorResponse13(c, e);
25862
26433
  }
25863
26434
  });
25864
26435
  app.post("/logout", (c) => {
@@ -25882,14 +26453,14 @@ function authRoutes(db) {
25882
26453
  });
25883
26454
  return c.json({ ok: true });
25884
26455
  } catch (e) {
25885
- return errorResponse12(c, e);
26456
+ return errorResponse13(c, e);
25886
26457
  }
25887
26458
  });
25888
26459
  app.get("/me", (c) => {
25889
26460
  const user = c.get("user");
25890
26461
  if (!user) return c.json({ code: "UNAUTHORIZED" }, 401);
25891
- const { passwordHash: _omit, ...safe } = user;
25892
- return c.json(safe);
26462
+ const { passwordHash: _omit, ...safe2 } = user;
26463
+ return c.json(safe2);
25893
26464
  });
25894
26465
  app.patch("/me", zValidator("json", updateProfileSchema), (c) => {
25895
26466
  const user = c.get("user");
@@ -25901,7 +26472,7 @@ function authRoutes(db) {
25901
26472
  }
25902
26473
 
25903
26474
  // ../server/src/features/user/routes.ts
25904
- function errorResponse13(c, e) {
26475
+ function errorResponse14(c, e) {
25905
26476
  const err = e;
25906
26477
  const status = err.status ?? 500;
25907
26478
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -25915,7 +26486,7 @@ function userRoutes(db) {
25915
26486
  const id = createUser(db, c.req.valid("json"));
25916
26487
  return c.json({ id }, 201);
25917
26488
  } catch (e) {
25918
- return errorResponse13(c, e);
26489
+ return errorResponse14(c, e);
25919
26490
  }
25920
26491
  });
25921
26492
  app.get("/:id", requireRole("admin"), (c) => {
@@ -25939,7 +26510,7 @@ function userRoutes(db) {
25939
26510
  deactivateUser(db, id, admin.id);
25940
26511
  return c.json({ ok: true });
25941
26512
  } catch (e) {
25942
- return errorResponse13(c, e);
26513
+ return errorResponse14(c, e);
25943
26514
  }
25944
26515
  });
25945
26516
  app.patch(
@@ -25961,7 +26532,7 @@ function userRoutes(db) {
25961
26532
  reactivateUser(db, id, admin.id);
25962
26533
  return c.json({ ok: true });
25963
26534
  } catch (e) {
25964
- return errorResponse13(c, e);
26535
+ return errorResponse14(c, e);
25965
26536
  }
25966
26537
  });
25967
26538
  app.get("/:id/project-assignments", requireRole("admin"), (c) => {
@@ -25981,7 +26552,7 @@ function userRoutes(db) {
25981
26552
  const assignmentId = assignProject(db, id, c.req.valid("json").projectId, admin.id);
25982
26553
  return c.json({ id: assignmentId }, 201);
25983
26554
  } catch (e) {
25984
- return errorResponse13(c, e);
26555
+ return errorResponse14(c, e);
25985
26556
  }
25986
26557
  }
25987
26558
  );
@@ -25996,7 +26567,7 @@ function userRoutes(db) {
25996
26567
  }
25997
26568
 
25998
26569
  // ../server/src/features/version/routes.ts
25999
- function errorResponse14(c, e) {
26570
+ function errorResponse15(c, e) {
26000
26571
  const err = e;
26001
26572
  const status = err.status ?? 500;
26002
26573
  const message = e instanceof Error && err.code ? e.message : void 0;
@@ -26011,7 +26582,7 @@ function versionRoutes(db, workspacePath) {
26011
26582
  try {
26012
26583
  return c.json(listVersions(db, projectId, user.id));
26013
26584
  } catch (e) {
26014
- return errorResponse14(c, e);
26585
+ return errorResponse15(c, e);
26015
26586
  }
26016
26587
  });
26017
26588
  app.get("/:projectId/current", (c) => {
@@ -26021,7 +26592,7 @@ function versionRoutes(db, workspacePath) {
26021
26592
  try {
26022
26593
  return c.json(getCurrentVersion(db, projectId, user.id));
26023
26594
  } catch (e) {
26024
- return errorResponse14(c, e);
26595
+ return errorResponse15(c, e);
26025
26596
  }
26026
26597
  });
26027
26598
  app.get("/:projectId/can-add", (c) => {
@@ -26037,14 +26608,14 @@ function versionRoutes(db, workspacePath) {
26037
26608
  const result = addVersion(db, projectId, c.req.valid("json").versionType, user.id, workspacePath);
26038
26609
  return c.json(result);
26039
26610
  } catch (e) {
26040
- return errorResponse14(c, e);
26611
+ return errorResponse15(c, e);
26041
26612
  }
26042
26613
  });
26043
26614
  return app;
26044
26615
  }
26045
26616
 
26046
26617
  // ../server/src/features/workflow/routes.ts
26047
- function errorResponse15(c, e) {
26618
+ function errorResponse16(c, e) {
26048
26619
  const err = e;
26049
26620
  const status = err.status ?? 500;
26050
26621
  return c.json({ code: err.code ?? "INTERNAL_ERROR" }, status);
@@ -26056,7 +26627,7 @@ function workflowRoutes(db, workspacePath) {
26056
26627
  try {
26057
26628
  return c.json(getPendingApps(db, projectId, conductorType, versionId));
26058
26629
  } catch (e) {
26059
- return errorResponse15(c, e);
26630
+ return errorResponse16(c, e);
26060
26631
  }
26061
26632
  });
26062
26633
  app.get("/phase-completion", zValidator("query", pendingAppsQuerySchema), (c) => {
@@ -26064,7 +26635,7 @@ function workflowRoutes(db, workspacePath) {
26064
26635
  try {
26065
26636
  return c.json(getPhaseCompletion(db, projectId, conductorType, versionId));
26066
26637
  } catch (e) {
26067
- return errorResponse15(c, e);
26638
+ return errorResponse16(c, e);
26068
26639
  }
26069
26640
  });
26070
26641
  app.get("/runs", (c) => {
@@ -26075,7 +26646,7 @@ function workflowRoutes(db, workspacePath) {
26075
26646
  resumeWatchers(db, workspacePath, projectId);
26076
26647
  return c.json(listWorkflowRuns(db, projectId));
26077
26648
  } catch (e) {
26078
- return errorResponse15(c, e);
26649
+ return errorResponse16(c, e);
26079
26650
  }
26080
26651
  });
26081
26652
  app.get("/active-run", (c) => {
@@ -26085,7 +26656,7 @@ function workflowRoutes(db, workspacePath) {
26085
26656
  resumeWatchers(db, workspacePath, projectId);
26086
26657
  return c.json(getActiveRun(db, projectId, workspacePath));
26087
26658
  } catch (e) {
26088
- return errorResponse15(c, e);
26659
+ return errorResponse16(c, e);
26089
26660
  }
26090
26661
  });
26091
26662
  app.get("/runs/:id", (c) => {
@@ -26094,7 +26665,7 @@ function workflowRoutes(db, workspacePath) {
26094
26665
  try {
26095
26666
  return c.json(getWorkflowRun(db, id, workspacePath));
26096
26667
  } catch (e) {
26097
- return errorResponse15(c, e);
26668
+ return errorResponse16(c, e);
26098
26669
  }
26099
26670
  });
26100
26671
  app.get("/runs/:id/file-content", (c) => {
@@ -26103,7 +26674,7 @@ function workflowRoutes(db, workspacePath) {
26103
26674
  try {
26104
26675
  return c.json(getWorkflowRunFileContent(db, workspacePath, id));
26105
26676
  } catch (e) {
26106
- return errorResponse15(c, e);
26677
+ return errorResponse16(c, e);
26107
26678
  }
26108
26679
  });
26109
26680
  app.get("/runs/:id/changelog", (c) => {
@@ -26114,7 +26685,7 @@ function workflowRoutes(db, workspacePath) {
26114
26685
  try {
26115
26686
  return c.json(getRunApplicationChangelog(db, workspacePath, id, appName));
26116
26687
  } catch (e) {
26117
- return errorResponse15(c, e);
26688
+ return errorResponse16(c, e);
26118
26689
  }
26119
26690
  });
26120
26691
  app.post("/runs/:id/force-stop", (c) => {
@@ -26124,7 +26695,7 @@ function workflowRoutes(db, workspacePath) {
26124
26695
  forceStopWorkflowRun(db, id);
26125
26696
  return c.json({ ok: true });
26126
26697
  } catch (e) {
26127
- return errorResponse15(c, e);
26698
+ return errorResponse16(c, e);
26128
26699
  }
26129
26700
  });
26130
26701
  app.post("/runs/:id/rescan", (c) => {
@@ -26133,7 +26704,7 @@ function workflowRoutes(db, workspacePath) {
26133
26704
  try {
26134
26705
  return c.json(rescanWorkflowRun(db, workspacePath, id));
26135
26706
  } catch (e) {
26136
- return errorResponse15(c, e);
26707
+ return errorResponse16(c, e);
26137
26708
  }
26138
26709
  });
26139
26710
  app.post("/runs", zValidator("json", createWorkflowSchema), (c) => {
@@ -26145,7 +26716,7 @@ function workflowRoutes(db, workspacePath) {
26145
26716
  });
26146
26717
  return c.json(result, 201);
26147
26718
  } catch (e) {
26148
- return errorResponse15(c, e);
26719
+ return errorResponse16(c, e);
26149
26720
  }
26150
26721
  });
26151
26722
  return app;
@@ -26285,11 +26856,11 @@ function closeTerminalSessions(projectId, reason) {
26285
26856
  }
26286
26857
  function buildPtyLaunch(workspacePath, folderName) {
26287
26858
  const isWindows = process.platform === "win32";
26288
- const join29 = (a, b) => `${a.replace(/[\\/]+$/, "")}/${b}`;
26859
+ const join30 = (a, b) => `${a.replace(/[\\/]+$/, "")}/${b}`;
26289
26860
  return {
26290
26861
  shell: isWindows ? "powershell.exe" : process.env["SHELL"] ?? "bash",
26291
26862
  args: [],
26292
- cwd: join29(workspacePath, folderName),
26863
+ cwd: join30(workspacePath, folderName),
26293
26864
  autoCommand: `${CLAUDE_COMMAND}\r`
26294
26865
  };
26295
26866
  }
@@ -26466,16 +27037,16 @@ function terminalRoutes(db) {
26466
27037
  import { execSync } from "child_process";
26467
27038
 
26468
27039
  // ../server/src/lib/claude-plugin.ts
26469
- 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";
26470
27041
  import { homedir } from "os";
26471
- import { join as join23 } from "path";
27042
+ import { join as join24 } from "path";
26472
27043
  var PLUGIN_NAME = "co2-skills";
26473
- function detectCo2SkillsPlugin(claudeDir = join23(homedir(), ".claude")) {
27044
+ function detectCo2SkillsPlugin(claudeDir = join24(homedir(), ".claude")) {
26474
27045
  return readRegistry(claudeDir) ?? readCacheDir(claudeDir);
26475
27046
  }
26476
27047
  function readRegistry(claudeDir) {
26477
27048
  try {
26478
- const raw2 = readFileSync15(join23(claudeDir, "plugins", "installed_plugins.json"), "utf8");
27049
+ const raw2 = readFileSync16(join24(claudeDir, "plugins", "installed_plugins.json"), "utf8");
26479
27050
  const json = JSON.parse(raw2);
26480
27051
  for (const [key, entries] of Object.entries(json.plugins ?? {})) {
26481
27052
  if (!key.startsWith(`${PLUGIN_NAME}@`)) continue;
@@ -26488,8 +27059,8 @@ function readRegistry(claudeDir) {
26488
27059
  }
26489
27060
  }
26490
27061
  function readCacheDir(claudeDir) {
26491
- const base = join23(claudeDir, "plugins", "cache", "co2", PLUGIN_NAME);
26492
- if (!existsSync20(base)) return { installed: false };
27062
+ const base = join24(claudeDir, "plugins", "cache", "co2", PLUGIN_NAME);
27063
+ if (!existsSync21(base)) return { installed: false };
26493
27064
  try {
26494
27065
  const versions = readdirSync5(base).filter((d) => /^\d+\.\d+/.test(d)).sort();
26495
27066
  return { installed: true, version: versions.at(-1) };
@@ -26543,8 +27114,8 @@ function workspaceRoutes(_db, config, port, host, workspacePath) {
26543
27114
  }
26544
27115
 
26545
27116
  // ../server/src/lib/config.ts
26546
- import { existsSync as existsSync21, readFileSync as readFileSync16, writeFileSync as writeFileSync12 } from "fs";
26547
- 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";
26548
27119
  var configSchema = external_exports.object({
26549
27120
  port: external_exports.number().int().min(1).max(65535).optional(),
26550
27121
  host: external_exports.string().optional(),
@@ -26552,16 +27123,16 @@ var configSchema = external_exports.object({
26552
27123
  provisioningState: external_exports.enum(["pending", "complete"]).optional()
26553
27124
  });
26554
27125
  function loadConfig(dataDir) {
26555
- const file = join24(dataDir, "config.json");
26556
- if (!existsSync21(file)) return {};
27126
+ const file = join25(dataDir, "config.json");
27127
+ if (!existsSync22(file)) return {};
26557
27128
  try {
26558
- return configSchema.parse(JSON.parse(readFileSync16(file, "utf8")));
27129
+ return configSchema.parse(JSON.parse(readFileSync17(file, "utf8")));
26559
27130
  } catch {
26560
27131
  return {};
26561
27132
  }
26562
27133
  }
26563
27134
  function saveConfig(dataDir, config) {
26564
- writeFileSync12(join24(dataDir, "config.json"), JSON.stringify(config, null, 2));
27135
+ writeFileSync13(join25(dataDir, "config.json"), JSON.stringify(config, null, 2));
26565
27136
  }
26566
27137
 
26567
27138
  // ../server/src/lib/logger.ts
@@ -26589,8 +27160,8 @@ function loggerMiddleware() {
26589
27160
  }
26590
27161
 
26591
27162
  // ../server/src/lib/static.ts
26592
- import { existsSync as existsSync22, readFileSync as readFileSync17, statSync as statSync7 } from "fs";
26593
- 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";
26594
27165
  var MIME = {
26595
27166
  ".html": "text/html; charset=utf-8",
26596
27167
  ".js": "text/javascript; charset=utf-8",
@@ -26614,7 +27185,7 @@ function safeResolve2(rootDir, relPath) {
26614
27185
  return target;
26615
27186
  }
26616
27187
  function serveSpa(staticDir) {
26617
- const indexPath = join25(staticDir, "index.html");
27188
+ const indexPath = join26(staticDir, "index.html");
26618
27189
  return async (c, next) => {
26619
27190
  if (c.req.method !== "GET" && c.req.method !== "HEAD") {
26620
27191
  await next();
@@ -26627,14 +27198,14 @@ function serveSpa(staticDir) {
26627
27198
  }
26628
27199
  const rel = path === "/" ? "/index.html" : decodeURIComponent(path);
26629
27200
  const filePath = safeResolve2(staticDir, rel);
26630
- if (filePath && existsSync22(filePath) && statSync7(filePath).isFile()) {
26631
- const body = readFileSync17(filePath);
27201
+ if (filePath && existsSync23(filePath) && statSync7(filePath).isFile()) {
27202
+ const body = readFileSync18(filePath);
26632
27203
  return c.body(body, 200, {
26633
27204
  "Content-Type": MIME[extname(filePath)] ?? "application/octet-stream"
26634
27205
  });
26635
27206
  }
26636
- if (existsSync22(indexPath)) {
26637
- 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" });
26638
27209
  }
26639
27210
  await next();
26640
27211
  };
@@ -26678,6 +27249,7 @@ function createApp(db, opts) {
26678
27249
  api.route("/defects", defects);
26679
27250
  api.route("/quality", defects);
26680
27251
  api.route("/dashboard", dashboardRoutes(db));
27252
+ api.route("/git", gitRoutes(db, opts.workspacePath));
26681
27253
  app.route("/api", api);
26682
27254
  if (opts.staticDir) {
26683
27255
  app.use("*", serveSpa(opts.staticDir));
@@ -26686,8 +27258,8 @@ function createApp(db, opts) {
26686
27258
  }
26687
27259
 
26688
27260
  // ../server/src/db/index.ts
26689
- import { existsSync as existsSync23 } from "fs";
26690
- 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";
26691
27263
  import { fileURLToPath as fileURLToPath2 } from "url";
26692
27264
  import Database from "better-sqlite3";
26693
27265
 
@@ -26891,16 +27463,16 @@ function resolveMigrationsFolder() {
26891
27463
  const here = dirname2(fileURLToPath2(import.meta.url));
26892
27464
  const candidates = [
26893
27465
  process.env["CO2_CLI_MIGRATIONS_DIR"],
26894
- join26(here, "../../drizzle"),
27466
+ join27(here, "../../drizzle"),
26895
27467
  // source: src/db -> packages/server/drizzle
26896
- join26(here, "../drizzle"),
27468
+ join27(here, "../drizzle"),
26897
27469
  // bundled: cli/dist -> cli/drizzle
26898
- join26(here, "drizzle")
27470
+ join27(here, "drizzle")
26899
27471
  ].filter((c) => Boolean(c));
26900
27472
  for (const c of candidates) {
26901
- if (existsSync23(join26(c, "meta", "_journal.json"))) return c;
27473
+ if (existsSync24(join27(c, "meta", "_journal.json"))) return c;
26902
27474
  }
26903
- return join26(here, "../../drizzle");
27475
+ return join27(here, "../../drizzle");
26904
27476
  }
26905
27477
  function openDb(dbPath, migrationsFolder = resolveMigrationsFolder()) {
26906
27478
  const sqlite = new Database(dbPath);
@@ -26934,7 +27506,7 @@ function bootstrap(db) {
26934
27506
  // ../server/src/lib/data-dir.ts
26935
27507
  import { mkdirSync as mkdirSync11 } from "fs";
26936
27508
  import { homedir as homedir2 } from "os";
26937
- import { join as join27 } from "path";
27509
+ import { join as join28 } from "path";
26938
27510
  function resolveDataDir(override) {
26939
27511
  const dir = override ?? defaultDataDir();
26940
27512
  mkdirSync11(dir, { recursive: true, mode: 448 });
@@ -26943,11 +27515,11 @@ function resolveDataDir(override) {
26943
27515
  function defaultDataDir() {
26944
27516
  switch (process.platform) {
26945
27517
  case "darwin":
26946
- return join27(homedir2(), "Library", "Application Support", "co2-cli");
27518
+ return join28(homedir2(), "Library", "Application Support", "co2-cli");
26947
27519
  case "win32":
26948
- return join27(process.env["LOCALAPPDATA"] ?? join27(homedir2(), "AppData", "Local"), "co2-cli");
27520
+ return join28(process.env["LOCALAPPDATA"] ?? join28(homedir2(), "AppData", "Local"), "co2-cli");
26949
27521
  default:
26950
- 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");
26951
27523
  }
26952
27524
  }
26953
27525
 
@@ -27601,8 +28173,8 @@ var startOptionsSchema = external_exports.object({
27601
28173
  workspacePath: external_exports.string().optional()
27602
28174
  });
27603
28175
  function resolveStaticDir() {
27604
- const dir = join28(dirname3(fileURLToPath3(import.meta.url)), "../static");
27605
- 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;
27606
28178
  }
27607
28179
  function registerStart(program3, version2) {
27608
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) => {
@@ -27611,11 +28183,11 @@ function registerStart(program3, version2) {
27611
28183
  const config = loadConfig(dataDir);
27612
28184
  const port = opts.port ?? (process.env["APP_PORT"] ? Number(process.env["APP_PORT"]) : void 0) ?? config.port ?? 3001;
27613
28185
  const host = opts.host ?? process.env["APP_HOST"] ?? config.host ?? "0.0.0.0";
27614
- 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");
27615
28187
  if (opts.port !== void 0 || opts.workspacePath !== void 0) {
27616
28188
  saveConfig(dataDir, { ...config, port, workspacePath });
27617
28189
  }
27618
- const db = openDb(join28(dataDir, "app.db"));
28190
+ const db = openDb(join29(dataDir, "app.db"));
27619
28191
  const credentials = bootstrap(db);
27620
28192
  const app = createApp(db, {
27621
28193
  dataDir,